From 2e5cfcafee57418131ff7c4f26c94265e820d851 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 24 Feb 2018 10:29:27 +0100 Subject: [PATCH 001/956] PVS-Studio static analysis corrections (3) issue #137 --- devices/limesdr/devicelimesdr.cpp | 8 ++--- devices/limesdr/devicelimesdrparam.h | 23 ++++++++++++++ plugins/channelrx/demodbfm/bfmdemod.cpp | 23 +++++++------- plugins/channeltx/modatv/atvmod.cpp | 4 +-- .../channeltx/udpsink/udpsinkudphandler.cpp | 5 +++- .../bladerfoutput/bladerfoutput.cpp | 7 +---- .../bladerfoutput/bladerfoutputplugin.cpp | 30 +++++++++---------- .../bladerfoutput/bladerfoutputthread.cpp | 2 ++ .../samplesink/filesink/filesinkoutput.cpp | 8 +---- .../samplesink/hackrfoutput/hackrfoutput.cpp | 7 +---- .../hackrfoutput/hackrfoutputthread.cpp | 1 + .../limesdroutput/limesdroutput.cpp | 21 +++---------- .../limesdroutput/limesdroutputthread.cpp | 2 ++ .../plutosdroutput/plutosdroutput.cpp | 25 +++++++--------- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 8 ++--- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 7 +---- .../sdrdaemonsink/sdrdaemonsinkthread.h | 2 +- .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 2 +- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 2 +- plugins/samplesource/airspy/airspyinput.cpp | 10 ++----- plugins/samplesource/airspy/airspythread.cpp | 2 ++ .../bladerfinput/bladerfinput.cpp | 7 +---- .../bladerfinput/bladerfinputplugin.cpp | 30 +++++++++---------- .../bladerfinput/bladerfinputthread.cpp | 2 ++ plugins/samplesource/fcdpro/fcdproinput.cpp | 7 +---- .../fcdproplus/fcdproplusinput.cpp | 7 +---- .../filesource/filesourceinput.cpp | 7 +---- .../samplesource/hackrfinput/hackrfinput.cpp | 7 +---- .../hackrfinput/hackrfinputthread.cpp | 2 ++ .../limesdrinput/limesdrinput.cpp | 21 +++---------- .../limesdrinput/limesdrinputthread.cpp | 2 ++ plugins/samplesource/perseus/perseusinput.cpp | 16 +++------- .../samplesource/perseus/perseusthread.cpp | 4 ++- plugins/samplesource/perseus/perseusthread.h | 2 +- .../plutosdrinput/plutosdrinput.cpp | 25 +++++++--------- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 8 +---- .../sdrdaemonsource/sdrdaemonsourcebuffer.cpp | 4 +++ .../sdrdaemonsourceudphandler.cpp | 2 +- .../sdrdaemonsourceudphandler.h | 2 +- plugins/samplesource/sdrplay/sdrplayinput.cpp | 7 +---- .../testsource/testsourceinput.cpp | 8 +---- sdrbase/audio/audiofifo.cpp | 1 - sdrbase/dsp/dvserialengine.cpp | 2 +- sdrbase/dsp/dvserialworker.cpp | 1 + sdrbase/dsp/dvserialworker.h | 2 +- sdrbase/dsp/fftfilt.h | 5 ++-- sdrbase/util/doublebuffer.h | 19 ++++++++++++ sdrbase/util/movingaverage.h | 4 +-- sdrgui/dsp/scopevismulti.cpp | 3 ++ sdrgui/dsp/scopevisng.cpp | 3 ++ sdrgui/gui/glscope.cpp | 1 + sdrgui/gui/glspectrum.cpp | 6 ++-- 52 files changed, 187 insertions(+), 229 deletions(-) diff --git a/devices/limesdr/devicelimesdr.cpp b/devices/limesdr/devicelimesdr.cpp index 659e7fad1..c091dbc7b 100644 --- a/devices/limesdr/devicelimesdr.cpp +++ b/devices/limesdr/devicelimesdr.cpp @@ -180,10 +180,10 @@ bool DeviceLimeSDR::SetRBBPGA_dB(lms_device_t *device, std::size_t chan, float v int rcc_ctl_pga_rbb = (430.0*pow(0.65, (g_pga_rbb/10.0))-110.35)/20.4516 + 16; int c_ctl_pga_rbb = 0; - if (0 <= g_pga_rbb && g_pga_rbb < 8) c_ctl_pga_rbb = 3; - if (8 <= g_pga_rbb && g_pga_rbb < 13) c_ctl_pga_rbb = 2; - if (13 <= g_pga_rbb && g_pga_rbb < 21) c_ctl_pga_rbb = 1; - if (21 <= g_pga_rbb) c_ctl_pga_rbb = 0; + if (g_pga_rbb < 8) { c_ctl_pga_rbb = 3; } + if (8 <= g_pga_rbb && g_pga_rbb < 13) { c_ctl_pga_rbb = 2; } + if (13 <= g_pga_rbb && g_pga_rbb < 21) { c_ctl_pga_rbb = 1; } + if (21 <= g_pga_rbb) { c_ctl_pga_rbb = 0; } if (LMS_WriteParam(device, LMS7param(RCC_CTL_PGA_RBB), rcc_ctl_pga_rbb) < 0) { diff --git a/devices/limesdr/devicelimesdrparam.h b/devices/limesdr/devicelimesdrparam.h index 34dfd8925..1bc6cdd78 100644 --- a/devices/limesdr/devicelimesdrparam.h +++ b/devices/limesdr/devicelimesdrparam.h @@ -53,6 +53,29 @@ struct DeviceLimeSDRParams m_rxFrequency(1e6), m_txFrequency(1e6) { + m_lpfRangeRx.max = 0.0f; + m_lpfRangeRx.min = 0.0f; + m_lpfRangeRx.step = 0.0f; + + m_lpfRangeTx.max = 0.0f; + m_lpfRangeTx.min = 0.0f; + m_lpfRangeTx.step = 0.0f; + + m_loRangeRx.max = 0.0f; + m_loRangeRx.min = 0.0f; + m_loRangeRx.step = 0.0f; + + m_loRangeTx.max = 0.0f; + m_loRangeTx.min = 0.0f; + m_loRangeTx.step = 0.0f; + + m_srRangeRx.max = 0.0f; + m_srRangeRx.min = 0.0f; + m_srRangeRx.step = 0.0f; + + m_srRangeTx.max = 0.0f; + m_srRangeTx.min = 0.0f; + m_srRangeTx.step = 0.0f; } /** diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index e5a22fe3f..7ce345792 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -232,18 +232,12 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto Real deemph_l, deemph_r; // Pre-emphasis is applied on each channel before multiplexing m_deemphasisFilterX.process(ci.real() + sampleStereo, deemph_l); m_deemphasisFilterY.process(ci.real() - sampleStereo, deemph_r); - if (m_settings.m_lsbStereo) - { - m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume); - m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume); - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); - } - else - { - m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume); - m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume); - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); - } + m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume); + m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume); + + if (m_settings.m_copyAudioToUDP) { + m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); + } } else { @@ -252,7 +246,10 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto quint16 sample = (qint16)(deemph * (1<<12) * m_settings.m_volume); m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); + + if (m_settings.m_copyAudioToUDP) { + m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); + } } ++m_audioBufferFill; diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index 01ae25a16..56cb8f5b8 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -448,7 +448,7 @@ void ATVMod::pullVideo(Real& sample) resizeCamera(); } - if (camera.m_videoFPSCount < camera.m_videoFPSManualEnable ? camera.m_videoFPSManual : camera.m_videoFPS) + if (camera.m_videoFPSCount < (camera.m_videoFPSManualEnable ? camera.m_videoFPSManual : camera.m_videoFPS)) { camera.m_videoPrevFPSCount = (int) camera.m_videoFPSCount; camera.m_videoFPSCount += (camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq); @@ -456,7 +456,7 @@ void ATVMod::pullVideo(Real& sample) else { camera.m_videoPrevFPSCount = 0; - camera.m_videoFPSCount = camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq; + camera.m_videoFPSCount = (camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq); } } } diff --git a/plugins/channeltx/udpsink/udpsinkudphandler.cpp b/plugins/channeltx/udpsink/udpsinkudphandler.cpp index ef84c4c24..b37134d10 100644 --- a/plugins/channeltx/udpsink/udpsinkudphandler.cpp +++ b/plugins/channeltx/udpsink/udpsinkudphandler.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "udpsinkmsg.h" #include "udpsinkudphandler.h" @@ -41,6 +42,7 @@ UDPSinkUDPHandler::UDPSinkUDPHandler() : m_feedbackMessageQueue(0) { m_udpBuf = new udpBlk_t[m_minNbUDPFrames]; + std::fill(m_udpDump, m_udpDump + m_udpBlockSize + 8192, 0); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages())); } @@ -180,7 +182,8 @@ void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) else { m_rwDelta = m_writeIndex; // raw R/W delta estimate - float d = (m_rwDelta - (m_nbUDPFrames/2))/(float) m_nbUDPFrames; + int nbUDPFrames2 = m_nbUDPFrames/2; + float d = (m_rwDelta - nbUDPFrames2)/(float) m_nbUDPFrames; //qDebug("UDPSinkUDPHandler::advanceReadPointer: w: %02d d: %f", m_writeIndex, d); if ((d < -0.45) || (d > 0.45)) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp index 1b3f08147..2bfd38ee1 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp @@ -134,12 +134,7 @@ bool BladerfOutput::start() if (m_running) stop(); - if((m_bladerfThread = new BladerfOutputThread(m_dev, &m_sampleSourceFifo)) == 0) - { - qCritical("BladerfOutput::start: out of memory"); - stop(); - return false; - } + m_bladerfThread = new BladerfOutputThread(m_dev, &m_sampleSourceFifo); // mutexLocker.unlock(); applySettings(m_settings, true); diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp index 31e626882..a8a484d99 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp @@ -58,23 +58,23 @@ PluginInterface::SamplingDevices BladerfOutputPlugin::enumSampleSinks() int count = bladerf_get_device_list(&devinfo); - for(int i = 0; i < count; i++) - { - QString displayedName(QString("BladeRF[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); - - result.append(SamplingDevice(displayedName, - m_hardwareID, - m_deviceTypeID, - QString(devinfo[i].serial), - i, - PluginInterface::SamplingDevice::PhysicalDevice, - false, - 1, - 0)); - } - if (devinfo) { + for(int i = 0; i < count; i++) + { + QString displayedName(QString("BladeRF[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); + + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + false, + 1, + 0)); + } + bladerf_free_device_list(devinfo); // Valgrind memcheck } diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp index 94eddc475..daa028e66 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "bladerfoutputthread.h" @@ -28,6 +29,7 @@ BladerfOutputThread::BladerfOutputThread(struct bladerf* dev, SampleSourceFifo* m_log2Interp(0), m_fcPos(0) { + std::fill(m_buf, m_buf + 2*BLADERFOUTPUT_BLOCKSIZE, 0); } BladerfOutputThread::~BladerfOutputThread() diff --git a/plugins/samplesink/filesink/filesinkoutput.cpp b/plugins/samplesink/filesink/filesinkoutput.cpp index 1e1d302b2..b7e47615c 100644 --- a/plugins/samplesink/filesink/filesinkoutput.cpp +++ b/plugins/samplesink/filesink/filesinkoutput.cpp @@ -90,13 +90,7 @@ bool FileSinkOutput::start() openFileStream(); - if((m_fileSinkThread = new FileSinkThread(&m_ofstream, &m_sampleSourceFifo)) == 0) - { - qCritical("out of memory"); - stop(); - return false; - } - + m_fileSinkThread = new FileSinkThread(&m_ofstream, &m_sampleSourceFifo); m_fileSinkThread->setSamplerate(m_settings.m_sampleRate); m_fileSinkThread->setLog2Interpolation(m_settings.m_log2Interp); m_fileSinkThread->connectTimer(m_masterTimer); diff --git a/plugins/samplesink/hackrfoutput/hackrfoutput.cpp b/plugins/samplesink/hackrfoutput/hackrfoutput.cpp index 7e497f8f2..2fb819264 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutput.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutput.cpp @@ -116,12 +116,7 @@ bool HackRFOutput::start() if (m_running) stop(); - if((m_hackRFThread = new HackRFOutputThread(m_dev, &m_sampleSourceFifo)) == 0) - { - qCritical("HackRFOutput::start: out of memory"); - stop(); - return false; - } + m_hackRFThread = new HackRFOutputThread(m_dev, &m_sampleSourceFifo); // mutexLocker.unlock(); diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputthread.cpp b/plugins/samplesink/hackrfoutput/hackrfoutputthread.cpp index 12f497976..0223c33b8 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputthread.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutputthread.cpp @@ -29,6 +29,7 @@ HackRFOutputThread::HackRFOutputThread(hackrf_device* dev, SampleSourceFifo* sam m_sampleFifo(sampleFifo), m_log2Interp(0) { + std::fill(m_buf, m_buf + 2*HACKRF_BLOCKSIZE, 0); } HackRFOutputThread::~HackRFOutputThread() diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp index 7b2aac50a..6650f3618 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp @@ -111,9 +111,6 @@ bool LimeSDROutput::openDevice() // check if the requested channel is busy and abort if so (should not happen if device management is working correctly) - char *busyChannels = new char[deviceParams->m_nbTxChannels]; - memset(busyChannels, 0, deviceParams->m_nbTxChannels); - for (unsigned int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++) { DeviceSinkAPI *buddy = m_deviceAPI->getSinkBuddies()[i]; @@ -122,13 +119,11 @@ bool LimeSDROutput::openDevice() if (buddyShared->m_channel == requestedChannel) { qCritical("LimeSDROutput::openDevice: cannot open busy channel %u", requestedChannel); - delete[] busyChannels; return false; } } m_deviceShared.m_channel = requestedChannel; // acknowledge the requested channel - delete[] busyChannels; } // look for Rx buddies and get reference to common parameters // take the first Rx channel @@ -370,20 +365,12 @@ bool LimeSDROutput::start() return false; } - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_limeSDROutputThread = new LimeSDROutputThread(&m_streamId, &m_sampleSourceFifo)) == 0) - { - qCritical("LimeSDROutput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("LimeSDROutput::start: thread created"); - } + m_limeSDROutputThread = new LimeSDROutputThread(&m_streamId, &m_sampleSourceFifo); + qDebug("LimeSDROutput::start: thread created"); + + applySettings(m_settings, true); m_limeSDROutputThread->setLog2Interpolation(m_settings.m_log2SoftInterp); diff --git a/plugins/samplesink/limesdroutput/limesdroutputthread.cpp b/plugins/samplesink/limesdroutput/limesdroutputthread.cpp index 218f35523..3dac3cf8f 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputthread.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputthread.cpp @@ -15,6 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "limesdroutputthread.h" #include "limesdroutputsettings.h" @@ -27,6 +28,7 @@ LimeSDROutputThread::LimeSDROutputThread(lms_stream_t* stream, SampleSourceFifo* m_log2Interp(0), m_fcPos(LimeSDROutputSettings::FC_POS_CENTER) { + std::fill(m_buf, m_buf + 2*LIMESDROUTPUT_BLOCKSIZE, 0); } LimeSDROutputThread::~LimeSDROutputThread() diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.cpp b/plugins/samplesink/plutosdroutput/plutosdroutput.cpp index 6d8d90aed..374ab452c 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.cpp @@ -42,6 +42,13 @@ PlutoSDROutput::PlutoSDROutput(DeviceSinkAPI *deviceAPI) : m_plutoTxBuffer(0), m_plutoSDROutputThread(0) { + m_deviceSampleRates.m_addaConnvRate = 0; + m_deviceSampleRates.m_bbRateHz = 0; + m_deviceSampleRates.m_firRate = 0; + m_deviceSampleRates.m_hb1Rate = 0; + m_deviceSampleRates.m_hb2Rate = 0; + m_deviceSampleRates.m_hb3Rate = 0; + suspendBuddies(); openDevice(); resumeBuddies(); @@ -72,20 +79,12 @@ bool PlutoSDROutput::start() if (m_running) stop(); - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_plutoSDROutputThread = new PlutoSDROutputThread(PLUTOSDR_BLOCKSIZE_SAMPLES, m_deviceShared.m_deviceParams->getBox(), &m_sampleSourceFifo)) == 0) - { - qCritical("PlutoSDROutput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("PlutoSDROutput::start: thread created"); - } + m_plutoSDROutputThread = new PlutoSDROutputThread(PLUTOSDR_BLOCKSIZE_SAMPLES, m_deviceShared.m_deviceParams->getBox(), &m_sampleSourceFifo); + qDebug("PlutoSDROutput::start: thread created"); + + applySettings(m_settings, true); m_plutoSDROutputThread->setLog2Interpolation(m_settings.m_log2Interp); m_plutoSDROutputThread->startWork(); @@ -257,11 +256,9 @@ bool PlutoSDROutput::openDevice() m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API // acquire the channel - suspendBuddies(); DevicePlutoSDRBox *plutoBox = m_deviceShared.m_deviceParams->getBox(); plutoBox->openTx(); m_plutoTxBuffer = plutoBox->createTxBuffer(PLUTOSDR_BLOCKSIZE_SAMPLES, false); // PlutoSDR buffer size is counted in number of (I,Q) samples - resumeBuddies(); return true; } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 3e844d322..96773dc4c 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -601,15 +601,11 @@ void SDRdaemonSinkGui::tick() samplesCorr = 127*8; quickStart = true; } - else if (queueLength < 8) + else if (queueLength < 16) { samplesCorr = ((8 - queueLength)*16)/m_nbSinceLastFlowCheck; } - else if (queueLength > 8) - { - samplesCorr = ((8 - queueLength)*16)/m_nbSinceLastFlowCheck; - } - else if (queueLength > 16) + else { samplesCorr = -127*16; quickStart = true; diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 23fed01e9..f9469cb3e 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -64,12 +64,7 @@ bool SDRdaemonSinkOutput::start() QMutexLocker mutexLocker(&m_mutex); qDebug() << "SDRdaemonSinkOutput::start"; - if((m_sdrDaemonSinkThread = new SDRdaemonSinkThread(&m_sampleSourceFifo)) == 0) - { - qCritical("out of memory"); - stop(); - return false; - } + m_sdrDaemonSinkThread = new SDRdaemonSinkThread(&m_sampleSourceFifo); m_sdrDaemonSinkThread->setRemoteAddress(m_settings.m_address, m_settings.m_dataPort); m_sdrDaemonSinkThread->setCenterFrequency(m_settings.m_centerFrequency); m_sdrDaemonSinkThread->setSamplerate(m_settings.m_sampleRate); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h index 6a17cdd4e..581a6cb88 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h @@ -63,7 +63,7 @@ public: private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; - bool m_running; + volatile bool m_running; int m_samplesChunkSize; SampleSourceFifo* m_sampleFifo; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index 3637c29fa..ce7747b2e 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -115,7 +115,7 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk metaData.m_crc32 = crc32.checksum(); - memset((void *) &m_superBlock, 0, m_udpSize); + memset((void *) &m_superBlock, 0, sizeof(m_superBlock)); m_superBlock.header.frameIndex = m_frameCount; m_superBlock.header.blockIndex = m_txBlockIndex; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index a0a374e24..825a4b12a 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -244,7 +244,7 @@ private slots: private: void encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay); - bool m_running; + volatile bool m_running; CM256 m_cm256; //!< CM256 library object bool m_cm256Valid; //!< true if CM256 library is initialized correctly UDPSocket m_socket; diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index ae0c53355..450c46a30 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -161,15 +161,9 @@ bool AirspyInput::start() return false; } - if (m_running) stop(); - - if((m_airspyThread = new AirspyThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("AirspyInput::start: out of memory"); - stop(); - return false; - } + if (m_running) { stop(); } + m_airspyThread = new AirspyThread(m_dev, &m_sampleFifo); m_airspyThread->setSamplerate(m_sampleRates[m_settings.m_devSampleRateIndex]); m_airspyThread->setLog2Decimation(m_settings.m_log2Decim); m_airspyThread->setFcPos((int) m_settings.m_fcPos); diff --git a/plugins/samplesource/airspy/airspythread.cpp b/plugins/samplesource/airspy/airspythread.cpp index 3b2424ba5..7d03055d4 100644 --- a/plugins/samplesource/airspy/airspythread.cpp +++ b/plugins/samplesource/airspy/airspythread.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "airspythread.h" @@ -34,6 +35,7 @@ AirspyThread::AirspyThread(struct airspy_device* dev, SampleSinkFifo* sampleFifo m_fcPos(0) { m_this = this; + std::fill(m_buf, m_buf + 2*AIRSPY_BLOCKSIZE, 0); } AirspyThread::~AirspyThread() diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 8a655c874..ef4901a13 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -146,12 +146,7 @@ bool BladerfInput::start() if (m_running) stop(); - if((m_bladerfThread = new BladerfInputThread(m_dev, &m_sampleFifo)) == 0) { - qCritical("BladerfInput::start: out of memory"); - stop(); - return false; - } - + m_bladerfThread = new BladerfInputThread(m_dev, &m_sampleFifo); m_bladerfThread->setLog2Decimation(m_settings.m_log2Decim); m_bladerfThread->setFcPos((int) m_settings.m_fcPos); diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index 288d56cbb..02a2ebd34 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -59,23 +59,23 @@ PluginInterface::SamplingDevices BlderfInputPlugin::enumSampleSources() int count = bladerf_get_device_list(&devinfo); - for(int i = 0; i < count; i++) - { - QString displayedName(QString("BladeRF[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); + if (devinfo) + { + for(int i = 0; i < count; i++) + { + QString displayedName(QString("BladeRF[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); - result.append(SamplingDevice(displayedName, - m_hardwareID, - m_deviceTypeID, - QString(devinfo[i].serial), - i, - PluginInterface::SamplingDevice::PhysicalDevice, - true, - 1, - 0)); - } + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + true, + 1, + 0)); + } - if (devinfo) - { bladerf_free_device_list(devinfo); // Valgrind memcheck } diff --git a/plugins/samplesource/bladerfinput/bladerfinputthread.cpp b/plugins/samplesource/bladerfinput/bladerfinputthread.cpp index 7805d91fa..e0dec6855 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputthread.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputthread.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "dsp/samplesinkfifo.h" @@ -31,6 +32,7 @@ BladerfInputThread::BladerfInputThread(struct bladerf* dev, SampleSinkFifo* samp m_log2Decim(0), m_fcPos(0) { + std::fill(m_buf, m_buf + 2*BLADERF_BLOCKSIZE, 0); } BladerfInputThread::~BladerfInputThread() diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index 314581121..e02e5df17 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -121,12 +121,7 @@ bool FCDProInput::start() return false; } - if ((m_FCDThread = new FCDProThread(&m_sampleFifo)) == NULL) - { - qCritical("out of memory"); - return false; - } - + m_FCDThread = new FCDProThread(&m_sampleFifo); m_FCDThread->startWork(); // mutexLocker.unlock(); diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index e350933f0..92be2bab8 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -115,12 +115,7 @@ bool FCDProPlusInput::start() return false; } - if ((m_FCDThread = new FCDProPlusThread(&m_sampleFifo)) == NULL) - { - qCritical("out of memory"); - return false; - } - + m_FCDThread = new FCDProPlusThread(&m_sampleFifo); m_FCDThread->startWork(); // mutexLocker.unlock(); diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 6a6a8f946..b7b859c85 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -145,12 +145,7 @@ bool FileSourceInput::start() //openFileStream(); - if((m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo)) == NULL) { - qCritical("out of memory"); - stop(); - return false; - } - + m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo); m_fileSourceThread->setSampleRateAndSize(m_sampleRate, m_sampleSize); m_fileSourceThread->connectTimer(m_masterTimer); m_fileSourceThread->startWork(); diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index 29c3a74a1..3c51c7fad 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -132,12 +132,7 @@ bool HackRFInput::start() if (m_running) stop(); - if ((m_hackRFThread = new HackRFInputThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("HackRFInput::start: out of memory"); - stop(); - return false; - } + m_hackRFThread = new HackRFInputThread(m_dev, &m_sampleFifo); // mutexLocker.unlock(); diff --git a/plugins/samplesource/hackrfinput/hackrfinputthread.cpp b/plugins/samplesource/hackrfinput/hackrfinputthread.cpp index db002490a..c0a9dfc57 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputthread.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputthread.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "dsp/samplesinkfifo.h" @@ -32,6 +33,7 @@ HackRFInputThread::HackRFInputThread(hackrf_device* dev, SampleSinkFifo* sampleF m_log2Decim(0), m_fcPos(0) { + std::fill(m_buf, m_buf + 2*HACKRF_BLOCKSIZE, 0); } HackRFInputThread::~HackRFInputThread() diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index f30b3270a..da15bf064 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -129,9 +129,6 @@ bool LimeSDRInput::openDevice() // check if the requested channel is busy and abort if so (should not happen if device management is working correctly) - char *busyChannels = new char[deviceParams->m_nbRxChannels]; - memset(busyChannels, 0, deviceParams->m_nbRxChannels); - for (unsigned int i = 0; i < m_deviceAPI->getSourceBuddies().size(); i++) { DeviceSourceAPI *buddy = m_deviceAPI->getSourceBuddies()[i]; @@ -140,13 +137,11 @@ bool LimeSDRInput::openDevice() if (buddyShared->m_channel == requestedChannel) { qCritical("LimeSDRInput::openDevice: cannot open busy channel %u", requestedChannel); - delete[] busyChannels; return false; } } m_deviceShared.m_channel = requestedChannel; // acknowledge the requested channel - delete[] busyChannels; } // look for Tx buddies and get reference to common parameters // take the first Rx channel @@ -389,20 +384,12 @@ bool LimeSDRInput::start() return false; } - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_limeSDRInputThread = new LimeSDRInputThread(&m_streamId, &m_sampleFifo)) == 0) - { - qCritical("LimeSDRInput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("LimeSDRInput::start: thread created"); - } + m_limeSDRInputThread = new LimeSDRInputThread(&m_streamId, &m_sampleFifo); + qDebug("LimeSDRInput::start: thread created"); + + applySettings(m_settings, true); m_limeSDRInputThread->setLog2Decimation(m_settings.m_log2SoftDecim); diff --git a/plugins/samplesource/limesdrinput/limesdrinputthread.cpp b/plugins/samplesource/limesdrinput/limesdrinputthread.cpp index 5d32b4060..208455b30 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputthread.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputthread.cpp @@ -15,6 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "limesdrinputsettings.h" #include "limesdrinputthread.h" @@ -27,6 +28,7 @@ LimeSDRInputThread::LimeSDRInputThread(lms_stream_t* stream, SampleSinkFifo* sam m_sampleFifo(sampleFifo), m_log2Decim(0) { + std::fill(m_buf, m_buf + 2*LIMESDR_BLOCKSIZE, 0); } LimeSDRInputThread::~LimeSDRInputThread() diff --git a/plugins/samplesource/perseus/perseusinput.cpp b/plugins/samplesource/perseus/perseusinput.cpp index 893accd0a..50f45c380 100644 --- a/plugins/samplesource/perseus/perseusinput.cpp +++ b/plugins/samplesource/perseus/perseusinput.cpp @@ -68,20 +68,12 @@ bool PerseusInput::start() { if (m_running) stop(); - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_perseusThread = new PerseusThread(m_perseusDescriptor, &m_sampleFifo)) == 0) - { - qCritical("PerseusInput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("PerseusInput::start: thread created"); - } + m_perseusThread = new PerseusThread(m_perseusDescriptor, &m_sampleFifo); + qDebug("PerseusInput::start: thread created"); + + applySettings(m_settings, true); m_perseusThread->setLog2Decimation(m_settings.m_log2Decim); m_perseusThread->startWork(); diff --git a/plugins/samplesource/perseus/perseusthread.cpp b/plugins/samplesource/perseus/perseusthread.cpp index a64b84e47..ec0441a78 100644 --- a/plugins/samplesource/perseus/perseusthread.cpp +++ b/plugins/samplesource/perseus/perseusthread.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB // + // Copyright (C) 2018 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 // @@ -15,6 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "perseusthread.h" PerseusThread *PerseusThread::m_this = 0; @@ -28,6 +29,7 @@ PerseusThread::PerseusThread(perseus_descr* dev, SampleSinkFifo* sampleFifo, QOb m_log2Decim(0) { m_this = this; + std::fill(m_buf, m_buf + 2*PERSEUS_NBSAMPLES, 0); } PerseusThread::~PerseusThread() diff --git a/plugins/samplesource/perseus/perseusthread.h b/plugins/samplesource/perseus/perseusthread.h index 6fe21e93d..46d75c649 100644 --- a/plugins/samplesource/perseus/perseusthread.h +++ b/plugins/samplesource/perseus/perseusthread.h @@ -42,7 +42,7 @@ public: private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; - bool m_running; + volatile bool m_running; perseus_descr* m_dev; qint32 m_buf[2*PERSEUS_NBSAMPLES]; diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index 4025c3c5f..7086a02b0 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -44,6 +44,13 @@ PlutoSDRInput::PlutoSDRInput(DeviceSourceAPI *deviceAPI) : m_plutoRxBuffer(0), m_plutoSDRInputThread(0) { + m_deviceSampleRates.m_addaConnvRate = 0; + m_deviceSampleRates.m_bbRateHz = 0; + m_deviceSampleRates.m_firRate = 0; + m_deviceSampleRates.m_hb1Rate = 0; + m_deviceSampleRates.m_hb2Rate = 0; + m_deviceSampleRates.m_hb3Rate = 0; + suspendBuddies(); openDevice(); resumeBuddies(); @@ -81,20 +88,12 @@ bool PlutoSDRInput::start() if (m_running) stop(); - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_plutoSDRInputThread = new PlutoSDRInputThread(PLUTOSDR_BLOCKSIZE_SAMPLES, m_deviceShared.m_deviceParams->getBox(), &m_sampleFifo)) == 0) - { - qCritical("PlutoSDRInput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("PlutoSDRInput::start: thread created"); - } + m_plutoSDRInputThread = new PlutoSDRInputThread(PLUTOSDR_BLOCKSIZE_SAMPLES, m_deviceShared.m_deviceParams->getBox(), &m_sampleFifo); + qDebug("PlutoSDRInput::start: thread created"); + + applySettings(m_settings, true); m_plutoSDRInputThread->setLog2Decimation(m_settings.m_log2Decim); m_plutoSDRInputThread->startWork(); @@ -287,11 +286,9 @@ bool PlutoSDRInput::openDevice() m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API // acquire the channel - suspendBuddies(); DevicePlutoSDRBox *plutoBox = m_deviceShared.m_deviceParams->getBox(); plutoBox->openRx(); m_plutoRxBuffer = plutoBox->createRxBuffer(PLUTOSDR_BLOCKSIZE_SAMPLES, false); - resumeBuddies(); return true; } diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index 5a0b711dc..76156dc11 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -187,13 +187,7 @@ bool RTLSDRInput::start() if (m_running) stop(); - if ((m_rtlSDRThread = new RTLSDRThread(m_dev, &m_sampleFifo)) == NULL) - { - qCritical("RTLSDRInput::start: out of memory"); - stop(); - return false; - } - + m_rtlSDRThread = new RTLSDRThread(m_dev, &m_sampleFifo); m_rtlSDRThread->setSamplerate(m_settings.m_devSampleRate); m_rtlSDRThread->setLog2Decimation(m_settings.m_log2Decim); m_rtlSDRThread->setFcPos((int) m_settings.m_fcPos); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp index 1ab67e08c..594434cf2 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include "sdrdaemonsourcebuffer.h" @@ -62,6 +63,9 @@ SDRdaemonSourceBuffer::SDRdaemonSourceBuffer(uint32_t throttlems) : } else { m_cm256_OK = true; } + + std::fill(m_decoderSlots, m_decoderSlots + nbDecoderSlots, DecoderSlot()); + std::fill(m_frames, m_frames + nbDecoderSlots, BufferFrame()); } SDRdaemonSourceBuffer::~SDRdaemonSourceBuffer() diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index a60062a85..5ae6f0778 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -31,6 +31,7 @@ SDRdaemonSourceUDPHandler::SDRdaemonSourceUDPHandler(SampleSinkFifo *sampleFifo, m_masterTimer(deviceAPI->getMasterTimer()), m_masterTimerConnected(false), m_running(false), + m_rateDivider(1000/SDRDAEMONSOURCE_THROTTLE_MS), m_sdrDaemonBuffer(m_rateDivider), m_dataSocket(0), m_dataAddress(QHostAddress::LocalHost), @@ -54,7 +55,6 @@ SDRdaemonSourceUDPHandler::SDRdaemonSourceUDPHandler(SampleSinkFifo *sampleFifo, m_converterBuffer(0), m_converterBufferNbSamples(0), m_throttleToggle(false), - m_rateDivider(1000/SDRDAEMONSOURCE_THROTTLE_MS), m_autoCorrBuffer(true) { m_udpBuf = new char[SDRdaemonSourceBuffer::m_udpPayloadSize]; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h index 0f686329a..bbb4c0799 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h @@ -55,6 +55,7 @@ private: const QTimer& m_masterTimer; bool m_masterTimerConnected; bool m_running; + uint32_t m_rateDivider; SDRdaemonSourceBuffer m_sdrDaemonBuffer; QUdpSocket *m_dataSocket; QHostAddress m_dataAddress; @@ -80,7 +81,6 @@ private: int32_t *m_converterBuffer; uint32_t m_converterBufferNbSamples; bool m_throttleToggle; - uint32_t m_rateDivider; bool m_autoCorrBuffer; void connectTimer(); diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index ce013c784..3dd8db149 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -168,12 +168,7 @@ bool SDRPlayInput::start() return false; } - if((m_sdrPlayThread = new SDRPlayThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("SDRPlayInput::start: failed to create thread"); - return false; - } - + m_sdrPlayThread = new SDRPlayThread(m_dev, &m_sampleFifo); m_sdrPlayThread->setLog2Decimation(m_settings.m_log2Decim); m_sdrPlayThread->setFcPos((int) m_settings.m_fcPos); diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index d1aa8d40a..4221c3a02 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -74,13 +74,7 @@ bool TestSourceInput::start() if (m_running) stop(); - if ((m_testSourceThread = new TestSourceThread(&m_sampleFifo)) == 0) - { - qCritical("TestSourceInput::start: out of memory"); - stop(); - return false; - } - + m_testSourceThread = new TestSourceThread(&m_sampleFifo); m_testSourceThread->setSamplerate(m_settings.m_sampleRate); m_testSourceThread->connectTimer(m_masterTimer); m_testSourceThread->startWork(); diff --git a/sdrbase/audio/audiofifo.cpp b/sdrbase/audio/audiofifo.cpp index 92734fb84..78bdf4ea0 100644 --- a/sdrbase/audio/audiofifo.cpp +++ b/sdrbase/audio/audiofifo.cpp @@ -266,7 +266,6 @@ bool AudioFifo::create(uint32_t numSamples) m_fifo = 0; } - m_size = 0; m_fill = 0; m_head = 0; m_tail = 0; diff --git a/sdrbase/dsp/dvserialengine.cpp b/sdrbase/dsp/dvserialengine.cpp index d042a2019..fa075382a 100644 --- a/sdrbase/dsp/dvserialengine.cpp +++ b/sdrbase/dsp/dvserialengine.cpp @@ -150,7 +150,7 @@ void DVSerialEngine::getComList() const char* sysdir = "/sys/class/tty/"; // Scan through /sys/class/tty - it contains all tty-devices in the system - n = scandir(sysdir, &namelist, NULL, NULL); + n = scandir(sysdir, &namelist, NULL, alphasort); if (n < 0) perror("scandir"); else diff --git a/sdrbase/dsp/dvserialworker.cpp b/sdrbase/dsp/dvserialworker.cpp index 8db6935a5..ed59009ee 100644 --- a/sdrbase/dsp/dvserialworker.cpp +++ b/sdrbase/dsp/dvserialworker.cpp @@ -33,6 +33,7 @@ DVSerialWorker::DVSerialWorker() : m_audioBuffer.resize(48000); m_audioBufferFill = 0; m_audioFifo = 0; + memset(m_dvAudioSamples, 0, SerialDV::MBE_AUDIO_BLOCK_SIZE*sizeof(short)); } DVSerialWorker::~DVSerialWorker() diff --git a/sdrbase/dsp/dvserialworker.h b/sdrbase/dsp/dvserialworker.h index da7347037..52af0acdb 100644 --- a/sdrbase/dsp/dvserialworker.h +++ b/sdrbase/dsp/dvserialworker.h @@ -131,7 +131,7 @@ private: void upsample6(short *in, int nbSamplesIn, unsigned char channels); SerialDV::DVController m_dvController; - bool m_running; + volatile bool m_running; int m_currentGainIn; int m_currentGainOut; short m_dvAudioSamples[SerialDV::MBE_AUDIO_BLOCK_SIZE]; diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index cb8c2b923..6318cf2d5 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -48,8 +48,9 @@ protected: int window; inline float fsinc(float fc, int i, int len) { - return (i == len/2) ? 2.0 * fc: - sin(2 * M_PI * fc * (i - (len/2))) / (M_PI * (i - (len/2))); + int len2 = len/2; + return (i == len2) ? 2.0 * fc: + sin(2 * M_PI * fc * (i - len2)) / (M_PI * (i - len2)); } inline float _blackman(int i, int len) { diff --git a/sdrbase/util/doublebuffer.h b/sdrbase/util/doublebuffer.h index 6e27b81f4..b76db989a 100644 --- a/sdrbase/util/doublebuffer.h +++ b/sdrbase/util/doublebuffer.h @@ -32,6 +32,25 @@ public: ~DoubleBufferSimple() {} + DoubleBufferSimple(const DoubleBufferSimple& other) + { + m_size = other.m_size; + m_data = other.m_data; + m_current = m_data.begin(); + } + + DoubleBufferSimple& operator=(const DoubleBufferSimple& other) + { + if (&other == this) { + return *this; + } + + m_size = other.m_size; + m_data = other.m_data; + m_current = m_data.begin(); + return *this; + } + void resize(int size) { m_size = size; diff --git a/sdrbase/util/movingaverage.h b/sdrbase/util/movingaverage.h index 326921526..4a6276f95 100644 --- a/sdrbase/util/movingaverage.h +++ b/sdrbase/util/movingaverage.h @@ -55,8 +55,8 @@ class MovingAverageUtil } } - double asDouble() const { return m_total / N; } - float asFloat() const { return m_total / N; } + double asDouble() const { return ((double)m_total) / N; } + float asFloat() const { return ((float)m_total) / N; } operator T() const { return m_total / N; } private: diff --git a/sdrgui/dsp/scopevismulti.cpp b/sdrgui/dsp/scopevismulti.cpp index 42303f69a..7318d18cb 100644 --- a/sdrgui/dsp/scopevismulti.cpp +++ b/sdrgui/dsp/scopevismulti.cpp @@ -61,6 +61,9 @@ ScopeVisMulti::ScopeVisMulti(GLScopeMulti* glScope) : { setObjectName("ScopeVisNG"); m_glScope->setTraces(&m_traces.m_tracesData, &m_traces.m_traces[0]); + for (int i = 0; i < (int) nbProjectionTypes; i++) { + m_projectorCache[i] = 0.0; + } } ScopeVisMulti::~ScopeVisMulti() diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index b34a7de2a..19195f4d6 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -64,6 +64,9 @@ ScopeVisNG::ScopeVisNG(GLScopeNG* glScope) : setObjectName("ScopeVisNG"); m_traceDiscreteMemory.resize(m_traceChunkSize); // arbitrary m_glScope->setTraces(&m_traces.m_tracesData, &m_traces.m_traces[0]); + for (int i = 0; i < (int) nbProjectionTypes; i++) { + m_projectorCache[i] = 0.0; + } } ScopeVisNG::~ScopeVisNG() diff --git a/sdrgui/gui/glscope.cpp b/sdrgui/gui/glscope.cpp index 9a7b812e6..29db0558a 100644 --- a/sdrgui/gui/glscope.cpp +++ b/sdrgui/gui/glscope.cpp @@ -86,6 +86,7 @@ GLScope::GLScope(QWidget* parent) : m_x2Scale.setOrientation(Qt::Horizontal); m_powerOverlayFont.setBold(true); m_powerOverlayFont.setPointSize(font().pointSize()+1); + memset(m_sampleRates, 0, (1<m_channelMarker->getLowCutoff(); // negative bandwidth - pw = (dv->m_channelMarker->getBandwidth() / 2); // positive bandwidth + int bw = dv->m_channelMarker->getBandwidth() / 2; + pw = (qreal) bw; // positive bandwidth } else if (sidebands == ChannelMarker::lsb) { pw = dv->m_channelMarker->getLowCutoff(); - nw = (dv->m_channelMarker->getBandwidth() / 2); + int bw = dv->m_channelMarker->getBandwidth() / 2; + nw = (qreal) bw; } else if (sidebands == ChannelMarker::vusb) { nw = -dv->m_channelMarker->getOppositeBandwidth(); // negative bandwidth pw = dv->m_channelMarker->getBandwidth(); // positive bandwidth From 3354c774fccee8b07ca825ada5802e063f7bc009 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 25 Feb 2018 00:07:08 +0100 Subject: [PATCH 002/956] DATV demod: fixed compilation warnings --- plugins/channelrx/CMakeLists.txt | 4 +- plugins/channelrx/demoddatv/CMakeLists.txt | 2 - .../channelrx/demoddatv/datvconstellation.h | 120 +- plugins/channelrx/demoddatv/datvdemod.cpp | 308 +- plugins/channelrx/demoddatv/datvdemod.h | 489 ++-- plugins/channelrx/demoddatv/datvdemodgui.cpp | 36 +- plugins/channelrx/demoddatv/leansdr/dsp.h | 677 +++-- plugins/channelrx/demoddatv/leansdr/dvb.h | 2449 +++++++++------- .../channelrx/demoddatv/leansdr/framework.h | 603 ++-- plugins/channelrx/demoddatv/leansdr/hdlc.h | 520 ++-- plugins/channelrx/demoddatv/leansdr/sdr.h | 2580 +++++++++-------- 11 files changed, 4361 insertions(+), 3427 deletions(-) diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 6f1dd1ec6..e8d70170a 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -16,9 +16,9 @@ if(LIBDSDCC_FOUND AND LIBMBE_FOUND) add_subdirectory(demoddsd) endif(LIBDSDCC_FOUND AND LIBMBE_FOUND) -if (NOT RX_SAMPLE_24BIT) +#if (NOT RX_SAMPLE_24BIT) add_subdirectory(demoddatv) -endif() +#endif() if (BUILD_DEBIAN) add_subdirectory(demoddsd) diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt index 3135612ef..5b4f5115a 100644 --- a/plugins/channelrx/demoddatv/CMakeLists.txt +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -24,8 +24,6 @@ set(datv_FORMS datvdemodgui.ui ) -set (CMAKE_CXX_FLAGS "-Wno-unused-variable -Wno-deprecated-declarations") - include_directories( . ${CMAKE_CURRENT_BINARY_DIR} diff --git a/plugins/channelrx/demoddatv/datvconstellation.h b/plugins/channelrx/demoddatv/datvconstellation.h index 9f95a7a15..a343cbfe2 100644 --- a/plugins/channelrx/demoddatv/datvconstellation.h +++ b/plugins/channelrx/demoddatv/datvconstellation.h @@ -27,72 +27,82 @@ namespace leansdr { - static const int DEFAULT_GUI_DECIMATION = 64; +static const int DEFAULT_GUI_DECIMATION = 64; - template struct datvconstellation : runnable +template struct datvconstellation: runnable +{ + T xymin; + T xymax; + unsigned long decimation; + unsigned long pixels_per_frame; + cstln_lut<256> **cstln; // Optional ptr to optional constellation + pipereader > in; + unsigned long phase; + DATVScreen *m_objDATVScreen; + + datvconstellation( + scheduler *sch, + pipebuf > &_in, + T _xymin, + T _xymax, + const char *_name = 0, + DATVScreen * objDATVScreen = 0) : + runnable(sch, _name ? _name : _in.name), + xymin(_xymin), + xymax(_xymax), + decimation(DEFAULT_GUI_DECIMATION), + pixels_per_frame(1024), + cstln(0), + in(_in), + phase(0), + m_objDATVScreen(objDATVScreen) { - T xymin, xymax; - unsigned long decimation; - unsigned long pixels_per_frame; - cstln_lut<256> **cstln; // Optional ptr to optional constellation - DATVScreen *m_objDATVScreen; + } - - datvconstellation(scheduler *sch, pipebuf< complex > &_in, T _xymin, T _xymax, const char *_name=NULL, DATVScreen * objDATVScreen=NULL) : - runnable(sch, _name?_name:_in.name), - xymin(_xymin), - xymax(_xymax), - decimation(DEFAULT_GUI_DECIMATION), - pixels_per_frame(1024), - cstln(NULL), - in(_in), - phase(0), - m_objDATVScreen(objDATVScreen) - { - - } - - void run() - { + void run() + { //Symbols - while ( in.readable() >= pixels_per_frame ) + while (in.readable() >= pixels_per_frame) { - if ( ! phase ) + if (!phase) { m_objDATVScreen->resetImage(); - complex *p = in.rd(), *pend = p+pixels_per_frame; + complex *p = in.rd(), *pend = p + pixels_per_frame; - for ( ; pselectRow(256*(p->re-xymin)/(xymax-xymin)); - m_objDATVScreen->setDataColor(256- 256*((p->im-xymin)/(xymax-xymin)),255,0,255); + m_objDATVScreen->selectRow( + 256 * (p->re - xymin) / (xymax - xymin)); + m_objDATVScreen->setDataColor( + 256 - 256 * ((p->im - xymin) / (xymax - xymin)), + 255, 0, 255); } } - if ( cstln && (*cstln) ) + if (cstln && (*cstln)) { - // Plot constellation points + // Plot constellation points - for ( int i=0; i<(*cstln)->nsymbols; ++i ) - { - complex *p = &(*cstln)->symbols[i]; - int x = 256*(p->re-xymin)/(xymax-xymin); - int y = 256 - 256*(p->im-xymin)/(xymax-xymin); - - for ( int d=-4; d<=4; ++d ) + for (int i = 0; i < (*cstln)->nsymbols; ++i) { - m_objDATVScreen->selectRow(x+d); - m_objDATVScreen->setDataColor(y,5,250,250); - m_objDATVScreen->selectRow(x); - m_objDATVScreen->setDataColor(y+d,5,250,250); + complex *p = &(*cstln)->symbols[i]; + int x = 256 * (p->re - xymin) / (xymax - xymin); + int y = 256 - 256 * (p->im - xymin) / (xymax - xymin); + + for (int d = -4; d <= 4; ++d) + { + m_objDATVScreen->selectRow(x + d); + m_objDATVScreen->setDataColor(y, 5, 250, 250); + m_objDATVScreen->selectRow(x); + m_objDATVScreen->setDataColor(y + d, 5, 250, 250); + } } - } } m_objDATVScreen->renderImage(NULL); @@ -101,27 +111,25 @@ namespace leansdr in.read(pixels_per_frame); - if ( ++phase >= decimation ) + if (++phase >= decimation) { phase = 0; } - } - } + } + } - //private: - pipereader< complex > in; - unsigned long phase; - //gfx g; + //private: + //gfx g; - void draw_begin() - { + void draw_begin() + { //g.clear(); //g.setfg(0, 255, 0); //g.line(g.w/2,0, g.w/2, g.h); //g.line(0,g.h/2, g.w,g.h/2); - } + } - }; +}; } diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index df1022e53..7b4e0ec99 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -38,21 +38,22 @@ MESSAGE_CLASS_DEFINITION(DATVDemod::MsgConfigureChannelizer, Message) DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_objSettingsMutex(QMutex::NonRecursive), - m_objRegisteredDATVScreen(NULL), - m_objVideoStream(NULL), - m_objRegisteredVideoRender(NULL), - m_objRenderThread(NULL), - m_enmModulation(BPSK /*DATV_FM1*/), m_blnNeedConfigUpdate(false), - m_blnRenderingVideo(false) + m_deviceAPI(deviceAPI), + m_objRegisteredDATVScreen(NULL), + m_objRegisteredVideoRender(NULL), + m_objVideoStream(NULL), + m_objRenderThread(NULL), + m_blnRenderingVideo(false), + m_enmModulation(BPSK /*DATV_FM1*/), + m_objSettingsMutex(QMutex::NonRecursive) { setObjectName("DATVDemod"); + qDebug("DATVDemod::DATVDemod: sizeof FixReal: %lu: SDR_RX_SAMP_SZ: %u", sizeof(FixReal), (unsigned int) SDR_RX_SAMP_SZ); //*************** DATV PARAMETERS *************** m_blnInitialized=false; - CleanUpDATVFramework(false); + CleanUpDATVFramework(); m_objVideoStream = new DATVideostream(); @@ -102,6 +103,7 @@ DATVDemod::~DATVDemod() bool DATVDemod::SetDATVScreen(DATVScreen *objScreen) { m_objRegisteredDATVScreen = objScreen; + return true; } DATVideostream * DATVDemod::SetVideoRender(DATVideoRender *objScreen) @@ -240,185 +242,185 @@ void DATVDemod::InitDATVParameters(int intMsps, m_blnInitialized=true; } -void DATVDemod::CleanUpDATVFramework(bool blnRelease) +void DATVDemod::CleanUpDATVFramework() { //if(blnRelease==true) - if(false) - { - if(m_objScheduler!=NULL) - { - m_objScheduler->shutdown(); - delete m_objScheduler; - } +// if (false) +// { +// if(m_objScheduler!=NULL) +// { +// m_objScheduler->shutdown(); +// delete m_objScheduler; +// } +// +// // INPUT +// if(p_rawiq!=NULL) delete p_rawiq; +// if(p_rawiq_writer!=NULL) delete p_rawiq_writer; +// if(p_preprocessed!=NULL) delete p_preprocessed; +// +// // NOTCH FILTER +// if(r_auto_notch!=NULL) delete r_auto_notch; +// if(p_autonotched!=NULL) delete p_autonotched; +// +// // FREQUENCY CORRECTION : DEROTATOR +// if(p_derot!=NULL) delete p_derot; +// if(r_derot!=NULL) delete r_derot; +// +// // CNR ESTIMATION +// if(p_cnr!=NULL) delete p_cnr; +// if(r_cnr!=NULL) delete r_cnr; +// +// //FILTERING +// if(r_resample!=NULL) delete r_resample; +// if(p_resampled!=NULL) delete p_resampled; +// if(coeffs!=NULL) delete coeffs; +// +// // OUTPUT PREPROCESSED DATA +// if(sampler!=NULL) delete sampler; +// if(coeffs_sampler!=NULL) delete coeffs_sampler; +// if(p_symbols!=NULL) delete p_symbols; +// if(p_freq!=NULL) delete p_freq; +// if(p_ss!=NULL) delete p_ss; +// if(p_mer!=NULL) delete p_mer; +// if(p_sampled!=NULL) delete p_sampled; +// +// //DECIMATION +// if(p_decimated!=NULL) delete p_decimated; +// if(p_decim!=NULL) delete p_decim; +// if(r_ppout!=NULL) delete r_ppout; +// +// //GENERIC CONSTELLATION RECEIVER +// if(m_objDemodulator!=NULL) delete m_objDemodulator; +// +// //DECONVOLUTION AND SYNCHRONIZATION +// if(p_bytes!=NULL) delete p_bytes; +// if(r_deconv!=NULL) delete r_deconv; +// if(r!=NULL) delete r; +// if(p_descrambled!=NULL) delete p_descrambled; +// if(p_frames!=NULL) delete p_frames; +// if(r_etr192_descrambler!=NULL) delete r_etr192_descrambler; +// if(r_sync!=NULL) delete r_sync; +// if(p_mpegbytes!=NULL) delete p_mpegbytes; +// if(p_lock!=NULL) delete p_lock; +// if(p_locktime!=NULL) delete p_locktime; +// if(r_sync_mpeg!=NULL) delete r_sync_mpeg; +// +// +// // DEINTERLEAVING +// if(p_rspackets!=NULL) delete p_rspackets; +// if(r_deinter!=NULL) delete r_deinter; +// if(p_vbitcount!=NULL) delete p_vbitcount; +// if(p_verrcount!=NULL) delete p_verrcount; +// if(p_rtspackets!=NULL) delete p_rtspackets; +// if(r_rsdec!=NULL) delete r_rsdec; +// +// //BER ESTIMATION +// if(p_vber!=NULL) delete p_vber; +// if(r_vber!=NULL) delete r_vber; +// +// // DERANDOMIZATION +// if(p_tspackets!=NULL) delete p_tspackets; +// if(r_derand!=NULL) delete r_derand; +// +// +// //OUTPUT : To remove +// if(r_stdout!=NULL) delete r_stdout; +// if(r_videoplayer!=NULL) delete r_videoplayer; +// +// //CONSTELLATION +// if(r_scope_symbols!=NULL) delete r_scope_symbols; +// +// } - // INPUT - if(p_rawiq!=NULL) delete p_rawiq; - if(p_rawiq_writer!=NULL) delete p_rawiq_writer; - if(p_preprocessed!=NULL) delete p_preprocessed; - - // NOTCH FILTER - if(r_auto_notch!=NULL) delete r_auto_notch; - if(p_autonotched!=NULL) delete p_autonotched; - - // FREQUENCY CORRECTION : DEROTATOR - if(p_derot!=NULL) delete p_derot; - if(r_derot!=NULL) delete r_derot; - - // CNR ESTIMATION - if(p_cnr!=NULL) delete p_cnr; - if(r_cnr!=NULL) delete r_cnr; - - //FILTERING - if(r_resample!=NULL) delete r_resample; - if(p_resampled!=NULL) delete p_resampled; - if(coeffs!=NULL) delete coeffs; - - // OUTPUT PREPROCESSED DATA - if(sampler!=NULL) delete sampler; - if(coeffs_sampler!=NULL) delete coeffs_sampler; - if(p_symbols!=NULL) delete p_symbols; - if(p_freq!=NULL) delete p_freq; - if(p_ss!=NULL) delete p_ss; - if(p_mer!=NULL) delete p_mer; - if(p_sampled!=NULL) delete p_sampled; - - //DECIMATION - if(p_decimated!=NULL) delete p_decimated; - if(p_decim!=NULL) delete p_decim; - if(r_ppout!=NULL) delete r_ppout; - - //GENERIC CONSTELLATION RECEIVER - if(m_objDemodulator!=NULL) delete m_objDemodulator; - - //DECONVOLUTION AND SYNCHRONIZATION - if(p_bytes!=NULL) delete p_bytes; - if(r_deconv!=NULL) delete r_deconv; - if(r!=NULL) delete r; - if(p_descrambled!=NULL) delete p_descrambled; - if(p_frames!=NULL) delete p_frames; - if(r_etr192_descrambler!=NULL) delete r_etr192_descrambler; - if(r_sync!=NULL) delete r_sync; - if(p_mpegbytes!=NULL) delete p_mpegbytes; - if(p_lock!=NULL) delete p_lock; - if(p_locktime!=NULL) delete p_locktime; - if(r_sync_mpeg!=NULL) delete r_sync_mpeg; - - - // DEINTERLEAVING - if(p_rspackets!=NULL) delete p_rspackets; - if(r_deinter!=NULL) delete r_deinter; - if(p_vbitcount!=NULL) delete p_vbitcount; - if(p_verrcount!=NULL) delete p_verrcount; - if(p_rtspackets!=NULL) delete p_rtspackets; - if(r_rsdec!=NULL) delete r_rsdec; - - //BER ESTIMATION - if(p_vber!=NULL) delete p_vber; - if(r_vber!=NULL) delete r_vber; - - // DERANDOMIZATION - if(p_tspackets!=NULL) delete p_tspackets; - if(r_derand!=NULL) delete r_derand; - - - //OUTPUT : To remove - if(r_stdout!=NULL) delete r_stdout; - if(r_videoplayer!=NULL) delete r_videoplayer; - - //CONSTELLATION - if(r_scope_symbols!=NULL) delete r_scope_symbols; - - } - - m_objScheduler=NULL; + m_objScheduler = 0; // INPUT - p_rawiq = NULL; - p_rawiq_writer = NULL; + p_rawiq = 0; + p_rawiq_writer = 0; - p_preprocessed = NULL; + p_preprocessed = 0; // NOTCH FILTER - r_auto_notch = NULL; - p_autonotched = NULL; + r_auto_notch = 0; + p_autonotched = 0; // FREQUENCY CORRECTION : DEROTATOR - p_derot = NULL; - r_derot=NULL; + p_derot = 0; + r_derot = 0; // CNR ESTIMATION - p_cnr = NULL; - r_cnr = NULL; + p_cnr = 0; + r_cnr = 0; //FILTERING - r_resample = NULL; - p_resampled = NULL; - coeffs = NULL; - ncoeffs=0; + r_resample = 0; + p_resampled = 0; + coeffs = 0; + ncoeffs = 0; // OUTPUT PREPROCESSED DATA - sampler = NULL; - coeffs_sampler=NULL; - ncoeffs_sampler=0; + sampler = 0; + coeffs_sampler = 0; + ncoeffs_sampler = 0; - p_symbols = NULL; - p_freq = NULL; - p_ss = NULL; - p_mer = NULL; - p_sampled = NULL; + p_symbols = 0; + p_freq = 0; + p_ss = 0; + p_mer = 0; + p_sampled = 0; //DECIMATION - p_decimated = NULL; - p_decim = NULL; - r_ppout = NULL; + p_decimated = 0; + p_decim = 0; + r_ppout = 0; //GENERIC CONSTELLATION RECEIVER - m_objDemodulator = NULL; + m_objDemodulator = 0; //DECONVOLUTION AND SYNCHRONIZATION - p_bytes=NULL; - r_deconv=NULL; - r = NULL; + p_bytes = 0; + r_deconv = 0; + r = 0; - p_descrambled = NULL; - p_frames = NULL; - r_etr192_descrambler = NULL; - r_sync = NULL; + p_descrambled = 0; + p_frames = 0; + r_etr192_descrambler = 0; + r_sync = 0; - p_mpegbytes = NULL; - p_lock = NULL; - p_locktime = NULL; - r_sync_mpeg = NULL; + p_mpegbytes = 0; + p_lock = 0; + p_locktime = 0; + r_sync_mpeg = 0; // DEINTERLEAVING - p_rspackets = NULL; - r_deinter = NULL; + p_rspackets = 0; + r_deinter = 0; - p_vbitcount = NULL; - p_verrcount = NULL; - p_rtspackets = NULL; - r_rsdec = NULL; + p_vbitcount = 0; + p_verrcount = 0; + p_rtspackets = 0; + r_rsdec = 0; //BER ESTIMATION - p_vber = NULL; - r_vber = NULL; + p_vber = 0; + r_vber = 0; // DERANDOMIZATION - p_tspackets = NULL; - r_derand = NULL; + p_tspackets = 0; + r_derand = 0; //OUTPUT : To remove - r_stdout = NULL; - r_videoplayer = NULL; + r_stdout = 0; + r_videoplayer = 0; //CONSTELLATION - r_scope_symbols = NULL; + r_scope_symbols = 0; } void DATVDemod::InitDATVFramework() @@ -516,7 +518,7 @@ void DATVDemod::InitDATVFramework() m_lngExpectedReadIQ = BUF_BASEBAND; - CleanUpDATVFramework(true); + CleanUpDATVFramework(); m_objScheduler = new scheduler(); @@ -819,7 +821,7 @@ void DATVDemod::InitDATVFramework() m_blnDVBInitialized=true; } -void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) +void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { float fltI; float fltQ; @@ -964,12 +966,12 @@ bool DATVDemod::handleMessage(const Message& cmd) m_channelizer->configure(m_channelizer->getInputMessageQueue(), m_channelizer->getInputSampleRate(), - m_objRunning.intCenterFrequency); + cfg.getCenterFrequency()); + //m_objRunning.intCenterFrequency); - - - qDebug() << "ATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate() - << " centerFrequency: " << m_objRunning.intCenterFrequency; + qDebug() << "DATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate() + << " centerFrequency: " << cfg.getCenterFrequency(); + //<< " centerFrequency: " << m_objRunning.intCenterFrequency; return true; } diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 399e596e9..8796f7229 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -43,8 +43,6 @@ class DownChannelizer; #endif - - #include "datvconstellation.h" #include "datvvideoplayer.h" @@ -70,68 +68,81 @@ class DownChannelizer; using namespace leansdr; -enum DATVModulation { BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 }; -enum dvb_version { DVB_S, DVB_S2 }; -enum dvb_sampler { SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC }; - -inline int decimation(float Fin, float Fout) { int d = Fin / Fout; return max(d, 1); } - -struct config -{ - dvb_version standard; - dvb_sampler sampler; - - int buf_factor; // Buffer sizing - float Fs; // Sampling frequency (Hz) - float Fderot; // Shift the signal (Hz). Note: Ftune is faster - int anf; // Number of auto notch filters - bool cnr; // Measure CNR - unsigned int decim; // Decimation, 0=auto - float Fm; // QPSK symbol rate (Hz) - cstln_lut<256>::predef constellation; - code_rate fec; - float Ftune; // Bias frequency for the QPSK demodulator (Hz) - bool allow_drift; - bool fastlock; - bool viterbi; - bool hard_metric; - bool resample; - float resample_rej; // Approx. filter rejection in dB - int rrc_steps; // Discrete steps between symbols, 0=auto - float rrc_rej; // Approx. RRC filter rejection in dB - float rolloff; // Roll-off 0..1 - bool hdlc; // Expect HDLC frames instead of MPEG packets - bool packetized; // Output frames with 16-bit BE length - float Finfo; // Desired refresh rate on fd_info (Hz) - - config() : buf_factor(4), - Fs(2.4e6), - Fderot(0), - anf(1), - cnr(false), - decim(0), - Fm(2e6), - standard(DVB_S), - constellation(cstln_lut<256>::QPSK), - fec(FEC12), - Ftune(0), - allow_drift(false), - fastlock(true), - viterbi(false), - hard_metric(false), - resample(false), - resample_rej(10), - sampler(SAMP_LINEAR), - rrc_steps(0), - rrc_rej(10), - rolloff(0.35), - hdlc(false), - packetized(false), - Finfo(5) - { - } +enum DATVModulation +{ + BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 +}; +enum dvb_version +{ + DVB_S, DVB_S2 +}; +enum dvb_sampler +{ + SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC }; +inline int decimation(float Fin, float Fout) +{ + int d = Fin / Fout; + return max(d, 1); +} + +struct config +{ + dvb_version standard; + dvb_sampler sampler; + + int buf_factor; // Buffer sizing + float Fs; // Sampling frequency (Hz) + float Fderot; // Shift the signal (Hz). Note: Ftune is faster + int anf; // Number of auto notch filters + bool cnr; // Measure CNR + unsigned int decim; // Decimation, 0=auto + float Fm; // QPSK symbol rate (Hz) + cstln_lut<256>::predef constellation; + code_rate fec; + float Ftune; // Bias frequency for the QPSK demodulator (Hz) + bool allow_drift; + bool fastlock; + bool viterbi; + bool hard_metric; + bool resample; + float resample_rej; // Approx. filter rejection in dB + int rrc_steps; // Discrete steps between symbols, 0=auto + float rrc_rej; // Approx. RRC filter rejection in dB + float rolloff; // Roll-off 0..1 + bool hdlc; // Expect HDLC frames instead of MPEG packets + bool packetized; // Output frames with 16-bit BE length + float Finfo; // Desired refresh rate on fd_info (Hz) + + config() : + standard(DVB_S), + sampler(SAMP_LINEAR), + buf_factor(4), + Fs(2.4e6), + Fderot(0), + anf(1), + cnr(false), + decim(0), + Fm(2e6), + constellation(cstln_lut<256>::QPSK), + fec(FEC12), + Ftune(0), + allow_drift(false), + fastlock(true), + viterbi(false), + hard_metric(false), + resample(false), + resample_rej(10), + rrc_steps(0), + rrc_rej(10), + rolloff(0.35), + hdlc(false), + packetized(false), + Finfo(5) + { + } +}; struct DATVConfig { @@ -152,118 +163,205 @@ struct DATVConfig bool blnViterbi; DATVConfig() : - intMsps(1024000), - intRFBandwidth(1024000), - intCenterFrequency(0), - enmStandard(DVB_S), - enmModulation(BPSK), - enmFEC(FEC12), - intSampleRate(1024000), - intSymbolRate(250000), - intNotchFilters(1), - blnAllowDrift(false), - blnFastLock(false), - blnHDLC(false), - blnHardMetric(false), - blnResample(false), - blnViterbi(false) + intMsps(1024000), + intRFBandwidth(1024000), + intCenterFrequency(0), + enmStandard(DVB_S), + enmModulation(BPSK), + enmFEC(FEC12), + intSampleRate(1024000), + intSymbolRate(250000), + intNotchFilters(1), + blnAllowDrift(false), + blnFastLock(false), + blnHDLC(false), + blnHardMetric(false), + blnResample(false), + blnViterbi(false) { } }; - -class DATVDemod : public BasebandSampleSink, public ChannelSinkAPI +class DATVDemod: public BasebandSampleSink, public ChannelSinkAPI { - Q_OBJECT +Q_OBJECT public: + class MsgConfigureChannelizer: public Message + { + MESSAGE_CLASS_DECLARATION + + public: + int getCenterFrequency() const + { + return m_centerFrequency; + } + + static MsgConfigureChannelizer* create(int centerFrequency) + { + return new MsgConfigureChannelizer(centerFrequency); + } + + private: + int m_centerFrequency; + + MsgConfigureChannelizer(int centerFrequency) : + Message(), m_centerFrequency(centerFrequency) + { + } + }; DATVDemod(DeviceSourceAPI *); ~DATVDemod(); - virtual void destroy() { delete this; } - virtual void getIdentifier(QString& id) { id = objectName(); } - virtual void getTitle(QString& title) { title = objectName(); } - virtual qint64 getCenterFrequency() const { return m_objRunning.intCenterFrequency; } + virtual void destroy() + { + delete this; + } + virtual void getIdentifier(QString& id) + { + id = objectName(); + } + virtual void getTitle(QString& title) + { + title = objectName(); + } + virtual qint64 getCenterFrequency() const + { + return m_objRunning.intCenterFrequency; + } - virtual QByteArray serialize() const { return QByteArray(); } - virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } + virtual QByteArray serialize() const + { + return QByteArray(); + } + virtual bool deserialize(const QByteArray& data __attribute__((unused))) + { + return false; + } + void configure( + MessageQueue* objMessageQueue, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + bool blnHDLC, + bool blnHardMetric, + bool blnResample, + bool blnViterbi); - - void configure(MessageQueue* objMessageQueue, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi); - - 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 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); bool SetDATVScreen(DATVScreen *objScreen); DATVideostream * SetVideoRender(DATVideoRender *objScreen); bool PlayVideo(bool blnStartStop); - void InitDATVParameters(int intMsps, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSampleRate, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi); + void InitDATVParameters( + int intMsps, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSampleRate, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + bool blnHDLC, + bool blnHardMetric, + bool blnResample, + bool blnViterbi); - void CleanUpDATVFramework(bool blnRelease); + void CleanUpDATVFramework(); int GetSampleRate(); void InitDATVFramework(); static const QString m_channelIdURI; static const QString m_channelId; - - class MsgConfigureChannelizer : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int centerFrequency) - { - return new MsgConfigureChannelizer(centerFrequency); - } - - private: - int m_centerFrequency; - - MsgConfigureChannelizer(int centerFrequency) : - Message(), - m_centerFrequency(centerFrequency) - { } - }; - - - private: + class MsgConfigureDATVDemod: public Message + { + MESSAGE_CLASS_DECLARATION + + public: + static MsgConfigureDATVDemod* create( + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + bool blnHDLC, + bool blnHardMetric, + bool blnResample, + bool blnViterbi) + { + return new MsgConfigureDATVDemod( + intRFBandwidth, + intCenterFrequency, + enmStandard, + enmModulation, + enmFEC, + intSymbolRate, + intNotchFilters, + blnAllowDrift, + blnFastLock, + blnHDLC, + blnHardMetric, + blnResample, + blnViterbi); + } + + DATVConfig m_objMsgConfig; + + private: + MsgConfigureDATVDemod( + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + bool blnHDLC, + bool blnHardMetric, + bool blnResample, + bool blnViterbi) : + Message() + { + m_objMsgConfig.intRFBandwidth = intRFBandwidth; + m_objMsgConfig.intCenterFrequency = intCenterFrequency; + m_objMsgConfig.enmStandard = enmStandard; + m_objMsgConfig.enmModulation = enmModulation; + m_objMsgConfig.enmFEC = enmFEC; + m_objMsgConfig.intSymbolRate = intSymbolRate; + m_objMsgConfig.intNotchFilters = intNotchFilters; + m_objMsgConfig.blnAllowDrift = blnAllowDrift; + m_objMsgConfig.blnFastLock = blnFastLock; + m_objMsgConfig.blnHDLC = blnHDLC; + m_objMsgConfig.blnHardMetric = blnHardMetric; + m_objMsgConfig.blnResample = blnResample; + m_objMsgConfig.blnViterbi = blnViterbi; + } + }; unsigned long m_lngExpectedReadIQ; unsigned long m_lngReadIQ; @@ -305,7 +403,7 @@ private: cnr_fft *r_cnr; //FILTERING - fir_filter *r_resample; + fir_filter *r_resample; pipebuf *p_resampled; float *coeffs; int ncoeffs; @@ -344,18 +442,17 @@ private: pipebuf *p_mpegbytes; pipebuf *p_lock; pipebuf *p_locktime; - mpeg_sync *r_sync_mpeg; - + mpeg_sync *r_sync_mpeg; // DEINTERLEAVING - pipebuf< rspacket > *p_rspackets; + pipebuf > *p_rspackets; deinterleaver *r_deinter; // REED-SOLOMON pipebuf *p_vbitcount; pipebuf *p_verrcount; pipebuf *p_rtspackets; - rs_decoder *r_rsdec; + rs_decoder *r_rsdec; // BER ESTIMATION pipebuf *p_vber; @@ -365,7 +462,6 @@ private: pipebuf *p_tspackets; derandomizer *r_derand; - //OUTPUT file_writer *r_stdout; datvvideoplayer *r_videoplayer; @@ -373,87 +469,26 @@ private: //CONSTELLATION datvconstellation *r_scope_symbols; + DeviceSourceAPI* m_deviceAPI; -private: + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; - DeviceSourceAPI* m_deviceAPI; + //*************** DATV PARAMETERS *************** + DATVScreen * m_objRegisteredDATVScreen; + DATVideoRender * m_objRegisteredVideoRender; + DATVideostream * m_objVideoStream; + DATVideoRenderThread * m_objRenderThread; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; + fftfilt * m_objRFFilter; + NCO m_objNCO; - //*************** DATV PARAMETERS *************** - DATVScreen * m_objRegisteredDATVScreen; - DATVideoRender * m_objRegisteredVideoRender; - DATVideostream * m_objVideoStream; - DATVideoRenderThread * m_objRenderThread; + bool m_blnInitialized; + bool m_blnRenderingVideo; - fftfilt * m_objRFFilter; - NCO m_objNCO; - - bool m_blnInitialized; - bool m_blnRenderingVideo; - - DATVModulation m_enmModulation; - - //QElapsedTimer m_objTimer; -private: - - class MsgConfigureDATVDemod : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - static MsgConfigureDATVDemod* create(int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) - { - return new MsgConfigureDATVDemod(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,blnHDLC,blnHardMetric,blnResample, blnViterbi); - } - - DATVConfig m_objMsgConfig; - - private: - MsgConfigureDATVDemod(int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) : - Message() - { - m_objMsgConfig.intRFBandwidth = intRFBandwidth; - m_objMsgConfig.intCenterFrequency = intCenterFrequency; - m_objMsgConfig.enmStandard = enmStandard; - m_objMsgConfig.enmModulation = enmModulation; - m_objMsgConfig.enmFEC = enmFEC; - m_objMsgConfig.intSymbolRate = intSymbolRate; - m_objMsgConfig.intNotchFilters = intNotchFilters; - m_objMsgConfig.blnAllowDrift = blnAllowDrift; - m_objMsgConfig.blnFastLock = blnFastLock; - m_objMsgConfig.blnHDLC = blnHDLC; - m_objMsgConfig.blnHardMetric = blnHardMetric; - m_objMsgConfig.blnResample = blnResample; - m_objMsgConfig.blnViterbi = blnViterbi; - } - }; + DATVModulation m_enmModulation; + //QElapsedTimer m_objTimer; DATVConfig m_objRunning; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index 254d6049f..88ae39d06 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -47,7 +47,7 @@ DATVDemodGUI* DATVDemodGUI::create(PluginAPI* objPluginAPI, } void DATVDemodGUI::destroy() -{ +{ delete this; } @@ -208,7 +208,7 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) } } -bool DATVDemodGUI::handleMessage(const Message& objMessage) +bool DATVDemodGUI::handleMessage(const Message& objMessage __attribute__((unused))) { return false; } @@ -233,7 +233,7 @@ void DATVDemodGUI::channelSampleRateChanged() applySettings(); } -void DATVDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) +void DATVDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { } @@ -258,7 +258,7 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba m_objChannelMarker(this), m_blnBasicSettingsShown(false), m_blnDoApplySettings(true) -{ +{ ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); @@ -491,9 +491,9 @@ void DATVDemodGUI::enterEvent(QEvent*) } void DATVDemodGUI::tick() -{ +{ if((m_intLastDecodedData-m_intPreviousDecodedData)>=0) - { + { m_intLastSpeed = 8*(m_intLastDecodedData-m_intPreviousDecodedData); ui->lblRate->setText(QString("Speed: %1b/s").arg(formatBytes(m_intLastSpeed))); } @@ -506,12 +506,12 @@ void DATVDemodGUI::tick() return; } -void DATVDemodGUI::on_cmbStandard_currentIndexChanged(const QString &arg1) -{ +void DATVDemodGUI::on_cmbStandard_currentIndexChanged(const QString &arg1 __attribute__((unused))) +{ applySettings(); } -void DATVDemodGUI::on_cmbModulation_currentIndexChanged(const QString &arg1) +void DATVDemodGUI::on_cmbModulation_currentIndexChanged(const QString &arg1 __attribute__((unused))) { QString strModulation; QString strFEC; @@ -563,8 +563,8 @@ void DATVDemodGUI::on_cmbModulation_currentIndexChanged(const QString &arg1) } -void DATVDemodGUI::on_cmbFEC_currentIndexChanged(const QString &arg1) -{ +void DATVDemodGUI::on_cmbFEC_currentIndexChanged(const QString &arg1 __attribute__((unused))) +{ QString strFEC; strFEC = ui->cmbFEC->currentText(); @@ -625,7 +625,7 @@ void DATVDemodGUI::on_chkHardMetric_clicked() /* void DATVDemodGUI::on_pushButton_clicked() { - applySettings(); + applySettings(); } */ @@ -641,12 +641,12 @@ void DATVDemodGUI::on_spiSampleRate_valueChanged(int arg1) } */ -void DATVDemodGUI::on_spiSymbolRate_valueChanged(int arg1) +void DATVDemodGUI::on_spiSymbolRate_valueChanged(int arg1 __attribute__((unused))) { applySettings(); } -void DATVDemodGUI::on_spiNotchFilters_valueChanged(int arg1) +void DATVDemodGUI::on_spiNotchFilters_valueChanged(int arg1 __attribute__((unused))) { applySettings(); } @@ -701,8 +701,8 @@ void DATVDemodGUI::on_pushButton_4_clicked() } -void DATVDemodGUI::on_mouseEvent(QMouseEvent* obj) -{ +void DATVDemodGUI::on_mouseEvent(QMouseEvent* obj __attribute__((unused))) +{ } QString DATVDemodGUI::formatBytes(qint64 intBytes) @@ -724,7 +724,7 @@ QString DATVDemodGUI::formatBytes(qint64 intBytes) } -void DATVDemodGUI::on_StreamDataAvailable(int *intPackets, int *intBytes, int *intPercent, qint64 *intTotalReceived) +void DATVDemodGUI::on_StreamDataAvailable(int *intPackets __attribute__((unused)), int *intBytes, int *intPercent, qint64 *intTotalReceived) { ui->lblStatus->setText(QString("Decod: %1B").arg(formatBytes(*intTotalReceived))); m_intLastDecodedData = *intTotalReceived; @@ -742,7 +742,7 @@ void DATVDemodGUI::on_StreamDataAvailable(int *intPackets, int *intBytes, int *i } -void DATVDemodGUI::on_spiBandwidth_valueChanged(int arg1) +void DATVDemodGUI::on_spiBandwidth_valueChanged(int arg1 __attribute__((unused))) { applySettings(); } diff --git a/plugins/channelrx/demoddatv/leansdr/dsp.h b/plugins/channelrx/demoddatv/leansdr/dsp.h index cc66d73c5..e5a97b99e 100644 --- a/plugins/channelrx/demoddatv/leansdr/dsp.h +++ b/plugins/channelrx/demoddatv/leansdr/dsp.h @@ -5,347 +5,438 @@ #include "leansdr/framework.h" #include "leansdr/math.h" -namespace leansdr { +namespace leansdr +{ - ////////////////////////////////////////////////////////////////////// - // DSP blocks - ////////////////////////////////////////////////////////////////////// - - // [cconverter] converts complex streams between numric types, - // with optional ofsetting and rational scaling. - template - struct cconverter : runnable { - cconverter(scheduler *sch, pipebuf< complex > &_in, - pipebuf< complex > &_out) - : runnable(sch, "cconverter"), - in(_in), out(_out) { +////////////////////////////////////////////////////////////////////// +// DSP blocks +////////////////////////////////////////////////////////////////////// + +// [cconverter] converts complex streams between numric types, +// with optional ofsetting and rational scaling. +template +struct cconverter: runnable +{ + cconverter(scheduler *sch, pipebuf > &_in, + pipebuf > &_out) : + runnable(sch, "cconverter"), in(_in), out(_out) + { } - void run() { - unsigned long count = min(in.readable(), out.writable()); - complex *pin=in.rd(), *pend=pin+count; - complex *pout = out.wr(); - for ( ; pinre = Zout + (pin->re-(Tin)Zin)*Gn/Gd; - pout->im = Zout + (pin->im-(Tin)Zin)*Gn/Gd; - } - in.read(count); - out.written(count); + + void run() + { + unsigned long count = min(in.readable(), out.writable()); + complex *pin = in.rd(), *pend = pin + count; + complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout) + { + pout->re = Zout + (pin->re - (Tin) Zin) * Gn / Gd; + pout->im = Zout + (pin->im - (Tin) Zin) * Gn / Gd; + } + in.read(count); + out.written(count); } - private: - pipereader< complex > in; - pipewriter< complex > out; - }; - - template - struct cfft_engine { - const int n; - cfft_engine(int _n) : n(_n), invsqrtn(1.0/sqrt(n)) { - // Compute log2(n) - logn = 0; - for ( int t=n; t>1; t>>=1 ) ++logn; - // Bit reversal - bitrev = new int[n]; - for ( int i=0; i>b)&1); - } - // Float constants - omega = new complex[n]; - omega_rev = new complex[n]; - for ( int i=0; i > in; + pipewriter > out; +}; + +template +struct cfft_engine +{ + const unsigned int n; + + cfft_engine(unsigned int _n) : + n(_n), invsqrtn(1.0 / sqrt(n)) + { + // Compute log2(n) + logn = 0; + for (int t = n; t > 1; t >>= 1) + ++logn; + // Bit reversal + bitrev = new int[n]; + for (unsigned int i = 0; i < n; ++i) + { + bitrev[i] = 0; + for (int b = 0; b < logn; ++b) + bitrev[i] = (bitrev[i] << 1) | ((i >> b) & 1); + } + // Float constants + omega = new complex [n]; + omega_rev = new complex [n]; + for (unsigned int i = 0; i < n; ++i) + { + float a = 2.0 * M_PI * i / n; + omega_rev[i].re = (omega[i].re = cosf(a)); + omega_rev[i].im = -(omega[i].im = sinf(a)); + } } - void inplace(complex *data, bool reverse=false) { - // Bit-reversal permutation - for ( int i=0; i tmp=data[i]; data[i]=data[r]; data[r]=tmp; } - } - complex *om = reverse ? omega_rev : omega; - // Danielson-Lanczos - for ( int i=0; i &w = om[k*dom]; - complex &dqk = data[q+k]; - complex x(w.re*dqk.re - w.im*dqk.im, - w.re*dqk.im + w.im*dqk.re); - data[q+k].re = data[p+k].re - x.re; - data[q+k].im = data[p+k].im - x.im; - data[p+k].re = data[p+k].re + x.re; - data[p+k].im = data[p+k].im + x.im; - } - } - } - if ( reverse ) { - float invn = 1.0 / n; - for ( int i=0; i *data, bool reverse = false) + { + // Bit-reversal permutation + for (unsigned int i = 0; i < n; ++i) + { + unsigned int r = bitrev[i]; + if (r < i) + { + complex tmp = data[i]; + data[i] = data[r]; + data[r] = tmp; + } + } + complex *om = reverse ? omega_rev : omega; + // Danielson-Lanczos + for (int i = 0; i < logn; ++i) + { + int hbs = 1 << i; + int dom = 1 << (logn - 1 - i); + for (int j = 0; j < dom; ++j) + { + int p = j * hbs * 2, q = p + hbs; + for (int k = 0; k < hbs; ++k) + { + complex &w = om[k * dom]; + complex &dqk = data[q + k]; + complex x(w.re * dqk.re - w.im * dqk.im, + w.re * dqk.im + w.im * dqk.re); + data[q + k].re = data[p + k].re - x.re; + data[q + k].im = data[p + k].im - x.im; + data[p + k].re = data[p + k].re + x.re; + data[p + k].im = data[p + k].im + x.im; + } + } + } + if (reverse) + { + float invn = 1.0 / n; + for (unsigned int i = 0; i < n; ++i) + { + data[i].re *= invn; + data[i].im *= invn; + } + } } - private: + +private: int logn; int *bitrev; complex *omega, *omega_rev; float invsqrtn; - }; - - template - struct adder : runnable { - adder(scheduler *sch, - pipebuf &_in1, pipebuf &_in2, pipebuf &_out) - : runnable(sch, "adder"), - in1(_in1), in2(_in2), out(_out) { +}; + +template +struct adder: runnable +{ + adder(scheduler *sch, pipebuf &_in1, pipebuf &_in2, pipebuf &_out) : + runnable(sch, "adder"), + in1(_in1), + in2(_in2), + out(_out) + { } - void run() { - int n = out.writable(); - if ( in1.readable() < n ) n = in1.readable(); - if ( in2.readable() < n ) n = in2.readable(); - T *pin1=in1.rd(), *pin2=in2.rd(), *pout=out.wr(), *pend=pout+n; - while ( pout < pend ) *pout++ = *pin1++ + *pin2++; - in1.read(n); - in2.read(n); - out.written(n); + + void run() + { + int n = out.writable(); + if (in1.readable() < n) + n = in1.readable(); + if (in2.readable() < n) + n = in2.readable(); + T *pin1 = in1.rd(), *pin2 = in2.rd(), *pout = out.wr(), *pend = pout + + n; + while (pout < pend) + *pout++ = *pin1++ + *pin2++; + in1.read(n); + in2.read(n); + out.written(n); } - private: + +private: pipereader in1, in2; pipewriter out; - }; - - template - struct scaler : runnable { +}; + +template +struct scaler: runnable +{ Tscale scale; - scaler(scheduler *sch, Tscale _scale, - pipebuf &_in, pipebuf &_out) - : runnable(sch, "scaler"), - scale(_scale), - in(_in), out(_out) { + + scaler(scheduler *sch, Tscale _scale, pipebuf &_in, pipebuf &_out) : + runnable(sch, "scaler"), + scale(_scale), + in(_in), + out(_out) + { } - void run() { - unsigned long count = min(in.readable(), out.writable()); - Tin *pin=in.rd(), *pend=pin+count; - Tout *pout = out.wr(); - for ( ; pin in; pipewriter out; - }; - - // [awgb_c] generates complex white gaussian noise. - - template - struct wgn_c : runnable { - wgn_c(scheduler *sch, pipebuf< complex > &_out) - : runnable(sch, "awgn"), stddev(1.0), out(_out) { +}; + +// [awgb_c] generates complex white gaussian noise. + +template +struct wgn_c: runnable +{ + wgn_c(scheduler *sch, pipebuf > &_out) : + runnable(sch, "awgn"), + stddev(1.0), + out(_out) + { } - void run() { - int n = out.writable(); - complex *pout=out.wr(), *pend=pout+n; - while ( pout < pend ) { - // TAOCP - float x, y, r2; - do { - x = 2*drand48() - 1; - y = 2*drand48() - 1; - r2 = x*x + y*y; - } while ( r2==0 || r2>=1 ); - float k = sqrtf(-logf(r2)/r2) * stddev; - pout->re = k*x; - pout->im = k*y; - ++pout; - } - out.written(n); + + void run() + { + int n = out.writable(); + complex *pout = out.wr(), *pend = pout + n; + while (pout < pend) + { + // TAOCP + float x, y, r2; + do + { + x = 2 * drand48() - 1; + y = 2 * drand48() - 1; + r2 = x * x + y * y; + } while (r2 == 0 || r2 >= 1); + float k = sqrtf(-logf(r2) / r2) * stddev; + pout->re = k * x; + pout->im = k * y; + ++pout; + } + out.written(n); } + float stddev; - private: - pipewriter< complex > out; - }; - - template - struct naive_lowpass : runnable { - naive_lowpass(scheduler *sch, pipebuf &_in, pipebuf &_out, int _w) - : runnable(sch, "lowpass"), in(_in), out(_out), w(_w) { + +private: + pipewriter > out; +}; + +template +struct naive_lowpass: runnable +{ + naive_lowpass(scheduler *sch, pipebuf &_in, pipebuf &_out, int _w) : + runnable(sch, "lowpass"), + in(_in), + out(_out), + w(_w) + { } - - void run() { - if ( in.readable() < w ) return; - unsigned long count = min(in.readable()-w, out.writable()); - T *pin=in.rd(), *pend=pin+count; - T *pout = out.wr(); - float k = 1.0 / w; - for ( ; pin in; pipewriter out; int w; - }; +}; - template - struct fir_filter : runnable { - fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs, - pipebuf &_in, pipebuf &_out, - unsigned int _decim=1) - : runnable(sch, "fir_filter"), - ncoeffs(_ncoeffs), coeffs(_coeffs), - in(_in), out(_out), - decim(_decim), - freq_tap(NULL), tap_multiplier(1), freq_tol(0.1) { - shifted_coeffs = new T[ncoeffs]; - set_freq(0); +template +struct fir_filter: runnable +{ + fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs, pipebuf &_in, pipebuf &_out, unsigned int _decim = 1) : + runnable(sch, "fir_filter"), + freq_tap(NULL), + tap_multiplier(1), + freq_tol(0.1), + ncoeffs(_ncoeffs), + coeffs(_coeffs), + in(_in), + out(_out), + decim(_decim) + { + shifted_coeffs = new T[ncoeffs]; + set_freq(0); } - - void run() { - if ( in.readable() < ncoeffs ) return; - if ( freq_tap ) { - float new_freq = *freq_tap * tap_multiplier; - if ( fabs(current_freq-new_freq) > freq_tol ) { - if ( sch->verbose ) - fprintf(stderr, "Shifting filter %f -> %f\n", - current_freq, new_freq); - set_freq(new_freq); - } - } + void run() + { + if (in.readable() < ncoeffs) + return; - unsigned long count = min((in.readable()-ncoeffs)/decim, - out.writable()); - T *pin=in.rd()+ncoeffs, *pend=pin+count*decim, *pout=out.wr(); - // TBD use coeffs when current_freq=0 (fewer mults if float) - for ( ; pin freq_tol) + { + if (sch->verbose) + fprintf(stderr, "Shifting filter %f -> %f\n", current_freq, + new_freq); + set_freq(new_freq); + } + } + + unsigned long count = min((in.readable() - ncoeffs) / decim, + out.writable()); + T *pin = in.rd() + ncoeffs, *pend = pin + count * decim, *pout = + out.wr(); + // TBD use coeffs when current_freq=0 (fewer mults if float) + for (; pin < pend; pin += decim, ++pout) + { + T *pc = shifted_coeffs; + T *pi = pin; + T x = 0; + for (unsigned int i = ncoeffs; i--; ++pc, --pi) + x = x + (*pc) * (*pi); + *pout = x; + } + in.read(count * decim); + out.written(count); } - - private: + +public: + float *freq_tap; + float tap_multiplier; + float freq_tol; + +private: unsigned int ncoeffs; Tc *coeffs; pipereader in; pipewriter out; unsigned int decim; - - T *shifted_coeffs; + T *shifted_coeffs; float current_freq; - void set_freq(float f) { - for ( int i=0; i +struct fir_resampler: runnable +{ + fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs, pipebuf &_in, pipebuf &_out, int _interp = 1, int _decim = 1) : + runnable(sch, "fir_resampler"), + ncoeffs(_ncoeffs), + coeffs(_coeffs), + interp(_interp), + decim(_decim), + in(_in), + out(_out, interp), + freq_tap(NULL), + tap_multiplier(1), + freq_tol(0.1) + { + if (decim != 1) + fail("fir_resampler: decim not implemented"); // TBD + shifted_coeffs = new T[ncoeffs]; + set_freq(0); + } + + void run() + { + if (in.readable() < ncoeffs) + return; + + if (freq_tap) + { + float new_freq = *freq_tap * tap_multiplier; + if (fabs(current_freq - new_freq) > freq_tol) + { + if (sch->verbose) + fprintf(stderr, "Shifting filter %f -> %f\n", current_freq, + new_freq); + set_freq(new_freq); + } + } + + if (in.readable() * interp < ncoeffs) + return; + unsigned long count = min((in.readable() * interp - ncoeffs) / interp, + out.writable() / interp); + int latency = (ncoeffs + interp) / interp; + T *pin = in.rd() + latency, *pend = pin + count, *pout = out.wr(); + // TBD use coeffs when current_freq=0 (fewer mults if float) + for (; pin < pend; ++pin) + { + for (int i = 0; i < interp; ++i, ++pout) + { + T *pi = pin; + T *pc = shifted_coeffs + i, *pcend = shifted_coeffs + ncoeffs; + T x = 0; + for (; pc < pcend; pc += interp, --pi) + x = x + (*pc) * (*pi); + *pout = x; + } + } + in.read(count); + out.written(count * interp); + } + +public: float *freq_tap; float tap_multiplier; float freq_tol; - }; // fir_filter - - // FIR FILTER WITH INTERPOLATION AND DECIMATION - - template - struct fir_resampler : runnable { - fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs, - pipebuf &_in, pipebuf &_out, - int _interp=1, int _decim=1) - : runnable(sch, "fir_resampler"), - ncoeffs(_ncoeffs), coeffs(_coeffs), - interp(_interp), decim(_decim), - in(_in), out(_out,interp), - freq_tap(NULL), tap_multiplier(1), freq_tol(0.1) - { - if ( decim != 1 ) fail("fir_resampler: decim not implemented"); // TBD - shifted_coeffs = new T[ncoeffs]; - set_freq(0); - } - - void run() { - if ( in.readable() < ncoeffs ) return; - - if ( freq_tap ) { - float new_freq = *freq_tap * tap_multiplier; - if ( fabs(current_freq-new_freq) > freq_tol ) { - if ( sch->verbose ) - fprintf(stderr, "Shifting filter %f -> %f\n", - current_freq, new_freq); - set_freq(new_freq); - } - } - - if ( in.readable()*interp < ncoeffs ) return; - unsigned long count = min((in.readable()*interp-ncoeffs)/interp, - out.writable()/interp); - int latency = (ncoeffs+interp) / interp; - T *pin=in.rd()+latency, *pend=pin+count, *pout=out.wr(); - // TBD use coeffs when current_freq=0 (fewer mults if float) - for ( ; pin in; pipewriter out; - - public: - float *freq_tap; - float tap_multiplier; - float freq_tol; - - private: - T *shifted_coeffs; + T *shifted_coeffs; float current_freq; - void set_freq(float f) { - for ( int i=0; i * make_dvbs2_constellation(cstln_lut<256>::predef c, - code_rate r) { - float gamma1=1, gamma2=1, gamma3=1; - switch ( c ) { +inline cstln_lut<256> * make_dvbs2_constellation(cstln_lut<256>::predef c, + code_rate r) +{ + float gamma1 = 1, gamma2 = 1, gamma3 = 1; + switch (c) + { case cstln_lut<256>::APSK16: - // EN 302 307, section 5.4.3, Table 9 - switch ( r ) { - case FEC23: - case FEC46: gamma1 = 3.15; break; - case FEC34: gamma1 = 2.85; break; - case FEC45: gamma1 = 2.75; break; - case FEC56: gamma1 = 2.70; break; - case FEC89: gamma1 = 2.60; break; - case FEC910: gamma1 = 2.57; break; - default: fail("Code rate not supported with APSK16"); - } - break; + // EN 302 307, section 5.4.3, Table 9 + switch (r) + { + case FEC23: + case FEC46: + gamma1 = 3.15; + break; + case FEC34: + gamma1 = 2.85; + break; + case FEC45: + gamma1 = 2.75; + break; + case FEC56: + gamma1 = 2.70; + break; + case FEC89: + gamma1 = 2.60; + break; + case FEC910: + gamma1 = 2.57; + break; + default: + fail("Code rate not supported with APSK16"); + } + break; case cstln_lut<256>::APSK32: - // EN 302 307, section 5.4.4, Table 10 - switch ( r ) { - case FEC34: gamma1 = 2.84; gamma2 = 5.27; break; - case FEC45: gamma1 = 2.72; gamma2 = 4.87; break; - case FEC56: gamma1 = 2.64; gamma2 = 4.64; break; - case FEC89: gamma1 = 2.54; gamma2 = 4.33; break; - case FEC910: gamma1 = 2.53; gamma2 = 4.30; break; - default: fail("Code rate not supported with APSK32"); - } - break; + // EN 302 307, section 5.4.4, Table 10 + switch (r) + { + case FEC34: + gamma1 = 2.84; + gamma2 = 5.27; + break; + case FEC45: + gamma1 = 2.72; + gamma2 = 4.87; + break; + case FEC56: + gamma1 = 2.64; + gamma2 = 4.64; + break; + case FEC89: + gamma1 = 2.54; + gamma2 = 4.33; + break; + case FEC910: + gamma1 = 2.53; + gamma2 = 4.30; + break; + default: + fail("Code rate not supported with APSK32"); + } + break; case cstln_lut<256>::APSK64E: - // EN 302 307-2, section 5.4.5, Table 13f - gamma1 = 2.4; gamma2 = 4.3; gamma3 = 7; - break; + // EN 302 307-2, section 5.4.5, Table 13f + gamma1 = 2.4; + gamma2 = 4.3; + gamma3 = 7; + break; default: - break; + break; } return new cstln_lut<256>(c, gamma1, gamma2, gamma3); - } +} - // EN 300 421, section 4.4.3, table 2 Punctured code, G1=0171, G2=0133 - static const int DVBS_G1 = 0171; - static const int DVBS_G2 = 0133; +// EN 300 421, section 4.4.3, table 2 Punctured code, G1=0171, G2=0133 +static const int DVBS_G1 = 0171; +static const int DVBS_G2 = 0133; -// G1 = 0b1111001 +// G1 = 0b1111001 // G2 = 0b1011011 // // G1 = [ 1 1 1 1 0 0 1 ] @@ -98,86 +137,95 @@ namespace leansdr { // IQ = [ Q1; I1; ... Q10; I10 ] = C * S // // D * C == [ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ] -// +// // D = [ 0 1 0 1 1 1 0 1 1 1 0 0 0 0] -// D = 0x3ba +// D = 0x3ba - template - struct deconvol_sync : runnable { - deconvol_sync(scheduler *sch, - pipebuf &_in, - pipebuf &_out, - uint32_t gX, uint32_t gY, - uint32_t pX, uint32_t pY) - : runnable(sch, "deconvol_sync"), - fastlock(false), - in(_in), out(_out,SIZE_RSPACKET), - skip(0) { - conv = new uint32_t[2]; - conv[0] = gX; - conv[1] = gY; - nG = 2; - punct = new uint32_t[2]; - punct[0] = pX; - punct[1] = pY; - punctperiod = 0; - punctweight = 0; - for ( int i=0; i<2; ++i ) { - int nbits = log2(punct[i]) + 1; - if ( nbits > punctperiod ) punctperiod = nbits; - punctweight += hamming_weight(punct[i]); - } - if ( sch->verbose ) - fprintf(stderr, "puncturing %d/%d\n", punctperiod, punctweight); - deconv = new iq_t[punctperiod]; - deconv2 = new iq_t[punctperiod]; - inverse_convolution(); - init_syncs(); - locked = &syncs[0]; +template +struct deconvol_sync: runnable +{ + deconvol_sync(scheduler *sch, pipebuf &_in, + pipebuf &_out, uint32_t gX, uint32_t gY, uint32_t pX, + uint32_t pY) : + runnable(sch, "deconvol_sync"), fastlock(false), in(_in), out(_out, + SIZE_RSPACKET), skip(0) + { + conv = new uint32_t[2]; + conv[0] = gX; + conv[1] = gY; + nG = 2; + punct = new uint32_t[2]; + punct[0] = pX; + punct[1] = pY; + punctperiod = 0; + punctweight = 0; + for (int i = 0; i < 2; ++i) + { + int nbits = log2(punct[i]) + 1; + if (nbits > punctperiod) + punctperiod = nbits; + punctweight += hamming_weight(punct[i]); + } + if (sch->verbose) + fprintf(stderr, "puncturing %d/%d\n", punctperiod, punctweight); + deconv = new iq_t[punctperiod]; + deconv2 = new iq_t[punctperiod]; + inverse_convolution(); + init_syncs(); + locked = &syncs[0]; } typedef uint64_t signal_t; typedef uint64_t iq_t; - static int log2(uint64_t x) { - int n = -1; - for ( ; x; ++n,x>>=1 ) ; - return n; + static int log2(uint64_t x) + { + int n = -1; + for (; x; ++n, x >>= 1) + ; + return n; } - iq_t convolve(signal_t s) { - int sbits = log2(s) + 1; - iq_t iq = 0; - unsigned char state = 0; - for ( int b=sbits-1; b>=0; --b ) { // Feed into convolver, MSB first - unsigned char bit = (s>>b) & 1; - state = (state>>1) | (bit<<6); // Shift register - for ( int j=0; j= 0; --b) + { // Feed into convolver, MSB first + unsigned char bit = (s >> b) & 1; + state = (state >> 1) | (bit << 6); // Shift register + for (int j = 0; j < nG; ++j) + { + unsigned char xy = parity(state & conv[j]); // Taps + if (punct[j] & (1 << (b % punctperiod))) + iq = (iq << 1) | xy; + } + } + return iq; } - - void run() { - run_decoding(); + + void run() + { + run_decoding(); } - - void next_sync() { - if ( fastlock ) fail("Bug: next_sync() called with fastlock"); - ++locked; - if ( locked == &syncs[NSYNCS] ) { - locked = &syncs[0]; - // Try next symbol alignment (for FEC other than 1/2) - skip = 1; - } + + void next_sync() + { + if (fastlock) + fail("Bug: next_sync() called with fastlock"); + ++locked; + if (locked == &syncs[NSYNCS]) + { + locked = &syncs[0]; + // Try next symbol alignment (for FEC other than 1/2) + skip = 1; + } } bool fastlock; - private: +private: static const int maxsbits = 64; iq_t response[maxsbits]; @@ -185,161 +233,204 @@ namespace leansdr { //static const int traceback = 48; // For code rate 7/8 static const int traceback = 64; // For code rate 7/8 with fastlock - void solve_rec(iq_t prefix, int nprefix, signal_t exp, iq_t *best) { - if ( prefix > *best ) return; - if ( nprefix > sizeof(prefix)*8 ) return; - int solved = 1; - for ( int b=0; b>b)&1) ) { - // Current candidate does not solve this column. - if ( (response[b]>>nprefix) == 0 ) - // No more bits to trace back. - return; - solved = 0; - } - } - if ( solved ) { *best = prefix; return; } - solve_rec(prefix, nprefix+1, exp, best); - solve_rec(prefix|((iq_t)1< *best) + return; + if (nprefix > sizeof(prefix) * 8) + return; + int solved = 1; + for (int b = 0; b < maxsbits; ++b) + { + if (parity(prefix & response[b]) != ((exp >> b) & 1)) + { + // Current candidate does not solve this column. + if ((response[b] >> nprefix) == 0) + // No more bits to trace back. + return; + solved = 0; + } + } + if (solved) + { + *best = prefix; + return; + } + solve_rec(prefix, nprefix + 1, exp, best); + solve_rec(prefix | ((iq_t) 1 << nprefix), nprefix + 1, exp, best); } static const int LATENCY = 0; - void inverse_convolution() { - for ( int sbit=0; sbitdebug ) { - for ( int b=0; b sizeof(iq_t)*8 ) - fail("Bug: traceback exceeds register size"); - if ( log2(deconv[b])+1 > traceback ) - fail("traceback insufficient for deconvolution"); - if ( log2(deconv2[b])+1 > traceback ) - fail("traceback insufficient for deconvolution (alt)"); - } + if (sch->debug) + { + for (int b = 0; b < punctperiod; ++b) + fprintf(stderr, "deconv[%d]=0x%016llx %d taps / %d bits\n", b, + (unsigned long long) deconv[b], + hamming_weight(deconv[b]), log2(deconv[b]) + 1); + } + + // Sanity check + for (int b = 0; b < punctperiod; ++b) + { + for (int i = 0; i < maxsbits; ++i) + { + iq_t iq = convolve((iq_t) 1 << (LATENCY + i)); + int expect = (b == i) ? 1 : 0; + int d = parity(iq & deconv[b]); + if (d != expect) + fail("Failed to inverse convolutional coding"); + int d2 = parity(iq & deconv2[b]); + if (d2 != expect) + fail("Failed to inverse convolutional coding (alt)"); + } + if (traceback > sizeof(iq_t) * 8) + fail("Bug: traceback exceeds register size"); + if (log2(deconv[b]) + 1 > traceback) + fail("traceback insufficient for deconvolution"); + if (log2(deconv2[b]) + 1 > traceback) + fail("traceback insufficient for deconvolution (alt)"); + } } static const int NSYNCS = 4; - struct sync_t { - u8 lut[2][2]; // lut[(re>0)?1:0][(im>0)?1:0] = 0b000000IQ - iq_t in; - int n_in; - signal_t out; - int n_out; - // Auxiliary shift register for fastlock - iq_t in2; - int n_in2, n_out2; + struct sync_t + { + u8 lut[2][2]; // lut[(re>0)?1:0][(im>0)?1:0] = 0b000000IQ + iq_t in; + int n_in; + signal_t out; + int n_out; + // Auxiliary shift register for fastlock + iq_t in2; + int n_in2, n_out2; } syncs[NSYNCS]; - void init_syncs() { - // EN 300 421, section 4.5, Figure 5 QPSK constellation - // Four rotations * two conjugations. - // 180° rotation is detected as polarity inversion in mpeg_sync. - for ( int sync_id=0; sync_id 5 bytes // 7/8 32 symbols -> 7 bytes - inline Tbyte readbyte(sync_t *s, softsymbol *&p) { - while ( s->n_out < 8 ) { - iq_t iq = s->in; - while ( s->n_in < traceback ) { - u8 iqbits = s->lut[(p->symbol&2)?1:0][p->symbol&1]; - ++p; - iq = (iq<<2) | iqbits; - s->n_in += 2; - } - s->in = iq; - for ( int b=punctperiod-1; b>=0; --b ) { - u8 bit = parity(iq&deconv[b]); - s->out = (s->out<<1) | bit; - } - s->n_out += punctperiod; - s->n_in -= punctweight; - } - Tbyte res = (s->out >> (s->n_out-8)) & 255; - s->n_out -= 8; - return res; + inline Tbyte readbyte(sync_t *s, softsymbol *&p) + { + while (s->n_out < 8) + { + iq_t iq = s->in; + while (s->n_in < traceback) + { + u8 iqbits = s->lut[(p->symbol & 2) ? 1 : 0][p->symbol & 1]; + ++p; + iq = (iq << 2) | iqbits; + s->n_in += 2; + } + s->in = iq; + for (int b = punctperiod - 1; b >= 0; --b) + { + u8 bit = parity(iq & deconv[b]); + s->out = (s->out << 1) | bit; + } + s->n_out += punctperiod; + s->n_in -= punctweight; + } + Tbyte res = (s->out >> (s->n_out - 8)) & 255; + s->n_out -= 8; + return res; } - inline unsigned long readerrors(sync_t *s, softsymbol *&p) { - unsigned long res = 0; - while ( s->n_out2 < 8 ) { - iq_t iq = s->in2; - while ( s->n_in2 < traceback ) { - u8 iqbits = s->lut[(p->symbol&2)?1:0][p->symbol&1]; - ++p; - iq = (iq<<2) | iqbits; - s->n_in2 += 2; - } - s->in2 = iq; - for ( int b=punctperiod-1; b>=0; --b ) { - u8 bit = parity(iq&deconv[b]); - u8 bit2 = parity(iq&deconv2[b]); - if ( bit2 != bit ) ++res; - } - s->n_out2 += punctperiod; - s->n_in2 -= punctweight; - } - s->n_out2 -= 8; - return res; + inline unsigned long readerrors(sync_t *s, softsymbol *&p) + { + unsigned long res = 0; + while (s->n_out2 < 8) + { + iq_t iq = s->in2; + while (s->n_in2 < traceback) + { + u8 iqbits = s->lut[(p->symbol & 2) ? 1 : 0][p->symbol & 1]; + ++p; + iq = (iq << 2) | iqbits; + s->n_in2 += 2; + } + s->in2 = iq; + for (int b = punctperiod - 1; b >= 0; --b) + { + u8 bit = parity(iq & deconv[b]); + u8 bit2 = parity(iq & deconv2[b]); + if (bit2 != bit) + ++res; + } + s->n_out2 += punctperiod; + s->n_in2 -= punctweight; + } + s->n_out2 -= 8; + return res; } - void run_decoding() { - in.read(skip); - skip = 0; + void run_decoding() + { + in.read(skip); + skip = 0; - // 8 byte margin to fill the deconvolver - if ( in.readable() < 64 ) return; - int maxrd = (in.readable()-64) / (punctweight/2) * punctperiod / 8; - int maxwr = out.writable(); - int n = (maxrddebug ) - fprintf(stderr, "{%d->%d}\n", - (int)(locked-syncs), (int)(best-syncs)); - locked = best; - } - // If deconvolution bit error rate > 33%, try next sample alignment - if ( errors_best > n*8/3 ) { - // fprintf(stderr, ">"); - skip = 1; - } - } + // 8 byte margin to fill the deconvolver + if (in.readable() < 64) + return; + int maxrd = (in.readable() - 64) / (punctweight / 2) * punctperiod / 8; + int maxwr = out.writable(); + unsigned int n = (maxrd < maxwr) ? maxrd : maxwr; + if (!n) + return; + // Require enough symbols to discriminate in fastlock mode + // (threshold must be less than size of rspacket) + if (n < 32) + return; - softsymbol *pin=in.rd(), *pin0=pin; - Tbyte *pout=out.wr(), *pout0=pout; - while ( n-- ) - *pout++ = readbyte(locked, pin); - in.read(pin-pin0); - out.written(pout-pout0); - } + if (fastlock) + { + // Try all sync alignments + unsigned long errors_best = 1 << 30; + sync_t *best = &syncs[0]; + for (sync_t *s = syncs; s < syncs + NSYNCS; ++s) + { + softsymbol *pin = in.rd(); + unsigned long errors = 0; + for (int c = n; c--;) + errors += readerrors(s, pin); + if (errors < errors_best) + { + errors_best = errors; + best = s; + } + } + if (best != locked) + { + // Another alignment produces fewer bit errors + if (sch->debug) + fprintf(stderr, "{%d->%d}\n", (int) (locked - syncs), + (int) (best - syncs)); + locked = best; + } + // If deconvolution bit error rate > 33%, try next sample alignment + if (errors_best > n * 8 / 3) + { + // fprintf(stderr, ">"); + skip = 1; + } + } + + softsymbol *pin = in.rd(), *pin0 = pin; + Tbyte *pout = out.wr(), *pout0 = pout; + while (n--) + *pout++ = readbyte(locked, pin); + in.read(pin - pin0); + out.written(pout - pout0); + } pipereader in; pipewriter out; @@ -456,410 +565,479 @@ namespace leansdr { sync_t *locked; int skip; - }; +}; - typedef deconvol_sync deconvol_sync_simple; +typedef deconvol_sync deconvol_sync_simple; - inline deconvol_sync_simple *make_deconvol_sync_simple(scheduler *sch, - pipebuf &_in, - pipebuf &_out, - enum code_rate rate) { +inline deconvol_sync_simple *make_deconvol_sync_simple(scheduler *sch, + pipebuf &_in, pipebuf &_out, enum code_rate rate) +{ // EN 300 421, section 4.4.3 Inner coding uint32_t pX, pY; - switch ( rate ) { + switch (rate) + { case FEC12: - pX = 0x1; // 1 - pY = 0x1; // 1 - break; + pX = 0x1; // 1 + pY = 0x1; // 1 + break; case FEC23: case FEC46: - pX = 0xa; // 1010 (Handle as FEC4/6, no half-symbols) - pY = 0xf; // 1111 - break; + pX = 0xa; // 1010 (Handle as FEC4/6, no half-symbols) + pY = 0xf; // 1111 + break; case FEC34: - pX = 0x5; // 101 - pY = 0x6; // 110 - break; + pX = 0x5; // 101 + pY = 0x6; // 110 + break; case FEC56: - pX = 0x15; // 10101 - pY = 0x1a; // 11010 - break; + pX = 0x15; // 10101 + pY = 0x1a; // 11010 + break; case FEC78: - pX = 0x45; // 1000101 - pY = 0x7a; // 1111010 - break; + pX = 0x45; // 1000101 + pY = 0x7a; // 1111010 + break; default: - //fail("Code rate not implemented"); - // For testing DVB-S2 constellations. - fprintf(stderr, "Code rate not implemented; proceeding anyway\n"); - pX = pY = 1; + //fail("Code rate not implemented"); + // For testing DVB-S2 constellations. + fprintf(stderr, "Code rate not implemented; proceeding anyway\n"); + pX = pY = 1; } return new deconvol_sync_simple(sch, _in, _out, DVBS_G1, DVBS_G2, pX, pY); - } +} +// CONVOLUTIONAL ENCODER - // CONVOLUTIONAL ENCODER +static const uint16_t polys_fec12[] = +{ DVBS_G1, DVBS_G2 // X1Y1 + }; +static const uint16_t polys_fec23[] = +{ DVBS_G1, DVBS_G2, DVBS_G2 << 1 // X1Y1Y2 +}; +// Same code rate as 2/3, usable with QPSK +static const uint16_t polys_fec46[] = +{ DVBS_G1, DVBS_G2, DVBS_G2 << 1, // X1Y1Y2 +DVBS_G1 << 2, DVBS_G2 << 2, DVBS_G2 << 3 // X3Y3Y4 +}; +static const uint16_t polys_fec34[] = +{ DVBS_G1, DVBS_G2, // X1Y1 + DVBS_G2 << 1, DVBS_G1 << 2 // Y2X3 +}; +static const uint16_t polys_fec45[] = +{ // Non standard + DVBS_G1, DVBS_G2, // X1Y1 + DVBS_G2 << 1, DVBS_G1 << 2, // Y2X3 + DVBS_G1 << 3 // X4 + }; +static const uint16_t polys_fec56[] = +{ DVBS_G1, DVBS_G2, // X1Y1 + DVBS_G2 << 1, DVBS_G1 << 2, // Y2X3 + DVBS_G2 << 3, DVBS_G1 << 4 // Y4X5 +}; +static const uint16_t polys_fec78[] = +{ DVBS_G1, DVBS_G2, // X1Y1 + DVBS_G2 << 1, DVBS_G2 << 2, // Y2Y3 + DVBS_G2 << 3, DVBS_G1 << 4, // Y4X5 + DVBS_G2 << 5, DVBS_G1 << 6 // Y6X7 +}; - static const uint16_t polys_fec12[] = { - DVBS_G1, DVBS_G2 // X1Y1 - }; - static const uint16_t polys_fec23[] = { - DVBS_G1, DVBS_G2, DVBS_G2<<1 // X1Y1Y2 - }; - // Same code rate as 2/3, usable with QPSK - static const uint16_t polys_fec46[] = { - DVBS_G1, DVBS_G2, DVBS_G2<<1, // X1Y1Y2 - DVBS_G1<<2, DVBS_G2<<2, DVBS_G2<<3 // X3Y3Y4 - }; - static const uint16_t polys_fec34[] = { - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2<<1, DVBS_G1<<2 // Y2X3 - }; - static const uint16_t polys_fec45[] = { // Non standard - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2<<1, DVBS_G1<<2, // Y2X3 - DVBS_G1<<3 // X4 - }; - static const uint16_t polys_fec56[] = { - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2<<1, DVBS_G1<<2, // Y2X3 - DVBS_G2<<3, DVBS_G1<<4 // Y4X5 - }; - static const uint16_t polys_fec78[] = { - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2<<1, DVBS_G2<<2, // Y2Y3 - DVBS_G2<<3, DVBS_G1<<4, // Y4X5 - DVBS_G2<<5, DVBS_G1<<6 // Y6X7 - }; - - // FEC parameters, for convolutional coding only (not S2). - static struct fec_spec { +// FEC parameters, for convolutional coding only (not S2). +static struct fec_spec +{ int bits_in; // Entering the convolutional coder int bits_out; // Exiting the convolutional coder const uint16_t *polys; // [bits_out] - } fec_specs[FEC_MAX] = { - [FEC12] = { 1, 2, polys_fec12 }, - [FEC23] = { 2, 3, polys_fec23 }, - [FEC46] = { 4, 6, polys_fec46 }, - [FEC34] = { 3, 4, polys_fec34 }, - [FEC56] = { 5, 6, polys_fec56 }, - [FEC78] = { 7, 8, polys_fec78 }, - [FEC45] = { 4, 5, polys_fec45 }, // Non-standard - }; +} fec_specs[FEC_MAX] = +{ [FEC12] = +{ 1, 2, polys_fec12 }, [FEC23] = +{ 2, 3, polys_fec23 }, [FEC46] = +{ 4, 6, polys_fec46 }, [FEC34] = +{ 3, 4, polys_fec34 }, [FEC56] = +{ 5, 6, polys_fec56 }, [FEC78] = +{ 7, 8, polys_fec78 }, [FEC45] = +{ 4, 5, polys_fec45 }, // Non-standard + }; - struct dvb_convol : runnable { +struct dvb_convol: runnable +{ typedef u8 uncoded_byte; typedef u8 hardsymbol; - dvb_convol(scheduler *sch, - pipebuf &_in, - pipebuf &_out, - code_rate fec, - int bits_per_symbol) - : runnable(sch, "dvb_convol"), - in(_in), out(_out,64) // BPSK 7/8: 7 bytes in, 64 symbols out + dvb_convol(scheduler *sch, pipebuf &_in, + pipebuf &_out, code_rate fec, int bits_per_symbol) : + runnable(sch, "dvb_convol"), in(_in), out(_out, 64) // BPSK 7/8: 7 bytes in, 64 symbols out { - fec_spec *fs = &fec_specs[fec]; - if ( ! fs->bits_in ) fail("Unexpected FEC"); - convol.bits_in = fs->bits_in; - convol.bits_out = fs->bits_out; - convol.polys = fs->polys; - convol.bps = bits_per_symbol; - // FEC must output a whole number of IQ symbols - if ( convol.bits_out % convol.bps ) - fail("Code rate not suitable for this constellation"); + fec_spec *fs = &fec_specs[fec]; + if (!fs->bits_in) + fail("Unexpected FEC"); + convol.bits_in = fs->bits_in; + convol.bits_out = fs->bits_out; + convol.polys = fs->polys; + convol.bps = bits_per_symbol; + // FEC must output a whole number of IQ symbols + if (convol.bits_out % convol.bps) + fail("Code rate not suitable for this constellation"); } - void run() { - int count = min(in.readable(), out.writable()*convol.bps/ - convol.bits_out*convol.bits_in/8); - // Process in multiples of the puncturing period and of 8 bits. - int chunk = convol.bits_in; - count = (count/chunk) * chunk; - convol.encode(in.rd(), out.wr(), count); - in.read(count); - int nout = count*8/convol.bits_in*convol.bits_out/convol.bps; - out.written(nout); + void run() + { + int count = min(in.readable(), + out.writable() * convol.bps / convol.bits_out * convol.bits_in + / 8); + // Process in multiples of the puncturing period and of 8 bits. + int chunk = convol.bits_in; + count = (count / chunk) * chunk; + convol.encode(in.rd(), out.wr(), count); + in.read(count); + int nout = count * 8 / convol.bits_in * convol.bits_out / convol.bps; + out.written(nout); } - private: +private: pipereader in; pipewriter out; convol_multipoly convol; - }; // dvb_convol +}; +// dvb_convol +// NEW ALGEBRAIC DECONVOLUTION - // NEW ALGEBRAIC DECONVOLUTION +// QPSK 1/2 only; +// With DVB-S polynomials hardcoded. - // QPSK 1/2 only; - // With DVB-S polynomials hardcoded. - - template - struct dvb_deconvol_sync : runnable { +template +struct dvb_deconvol_sync: runnable +{ typedef u8 decoded_byte; int resync_period; static const int chunk_size = 64; // At least 2*sizeof(Thist)/8 - dvb_deconvol_sync(scheduler *sch, - pipebuf &_in, - pipebuf &_out) - : runnable(sch, "deconvol_sync_multipoly"), - resync_period(32), - in(_in), out(_out,chunk_size), - resync_phase(0) + dvb_deconvol_sync(scheduler *sch, pipebuf &_in, + pipebuf &_out) : + runnable(sch, "deconvol_sync_multipoly"), resync_period(32), in( + _in), out(_out, chunk_size), resync_phase(0) { - init_syncs(); - locked = &syncs[0]; + init_syncs(); + locked = &syncs[0]; } - void run() { + void run() + { - while ( in.readable() >= chunk_size*8 && - out.writable() >= chunk_size ) { - int errors_best = 1 << 30; - sync_t *best = NULL; - for ( sync_t *s=syncs; sdeconv.run(pin, s->lut, pout, chunk_size); - if ( nerrors < errors_best ) { errors_best=nerrors; best=s; } - } - in.read(chunk_size*8); - out.written(chunk_size); - if ( best != locked ) { - if ( sch->debug ) fprintf(stderr, "%%%d", (int)(best-syncs)); - locked = best; - } - if ( ++resync_phase >= resync_period ) resync_phase = 0; - } // Work to do + while (in.readable() >= chunk_size * 8 && out.writable() >= chunk_size) + { + int errors_best = 1 << 30; + sync_t *best = NULL; + for (sync_t *s = syncs; s < syncs + NSYNCS; ++s) + { + if (resync_phase != 0 && s != locked) + // Decode only the currently-locked alignment + continue; + Tin *pin = in.rd(); + static decoded_byte dummy[chunk_size]; + decoded_byte *pout = (s == locked) ? out.wr() : dummy; + int nerrors = s->deconv.run(pin, s->lut, pout, chunk_size); + if (nerrors < errors_best) + { + errors_best = nerrors; + best = s; + } + } + in.read(chunk_size * 8); + out.written(chunk_size); + if (best != locked) + { + if (sch->debug) + fprintf(stderr, "%%%d", (int) (best - syncs)); + locked = best; + } + if (++resync_phase >= resync_period) + resync_phase = 0; + } // Work to do } // run() - private: +private: pipereader in; pipewriter out; int resync_phase; - + static const int NSYNCS = 4; - struct sync_t { - deconvol_poly2 deconv; - u8 lut[4]; // TBD Swap and flip bits in the polynomials instead. + struct sync_t + { + deconvol_poly2 deconv; + u8 lut[4]; // TBD Swap and flip bits in the polynomials instead. } syncs[NSYNCS]; sync_t *locked; - void init_syncs() { - for ( int s=0; s dvb_deconvol_sync_soft; +typedef dvb_deconvol_sync dvb_deconvol_sync_hard; - typedef dvb_deconvol_sync dvb_deconvol_sync_soft; - typedef dvb_deconvol_sync dvb_deconvol_sync_hard; +// BIT ALIGNMENT AND MPEG SYNC DETECTION - - // BIT ALIGNMENT AND MPEG SYNC DETECTION - - template - struct mpeg_sync : runnable { +template +struct mpeg_sync: runnable +{ int scan_syncs, want_syncs; unsigned long lock_timeout; bool fastlock; int resync_period; - mpeg_sync(scheduler *sch, - pipebuf &_in, - pipebuf &_out, - deconvol_sync *_deconv, - pipebuf *_state_out=NULL, - pipebuf *_locktime_out=NULL) - : runnable(sch, "sync_detect"), - scan_syncs(8), want_syncs(4), - lock_timeout(4), - fastlock(false), - resync_period(1), - in(_in), out(_out, SIZE_RSPACKET*(scan_syncs+1)), - deconv(_deconv), - polarity(0), - resync_phase(0), - bitphase(0), synchronized(false), - next_sync_count(0), - report_state(true) { - state_out = _state_out ? new pipewriter(*_state_out) : NULL; - locktime_out = - _locktime_out ? new pipewriter(*_locktime_out) : NULL; - } - - void run() { - if ( report_state && state_out && state_out->writable()>=1 ) { - // Report unlocked state on first invocation. - state_out->write(0); - report_state = false; - } - if ( synchronized ) - run_decoding(); - else { - if ( fastlock ) run_searching_fast(); else run_searching(); - } + mpeg_sync( + scheduler *sch, + pipebuf &_in, + pipebuf &_out, + deconvol_sync *_deconv, + pipebuf *_state_out = NULL, + pipebuf *_locktime_out = NULL) : + runnable(sch, "sync_detect"), + scan_syncs(8), + want_syncs(4), + lock_timeout(4), + fastlock(false), + resync_period(1), + in(_in), + out(_out, SIZE_RSPACKET * (scan_syncs + 1)), + deconv(_deconv), + polarity(0), + resync_phase(0), + bitphase(0), + synchronized(false), + next_sync_count(0), + phase8(-1), + lock_timeleft(0), + locktime(0), + report_state(true) + { + state_out = _state_out ? new pipewriter(*_state_out) : NULL; + locktime_out = locktime_out ? new pipewriter(*_locktime_out) : NULL; } - void run_searching() { - bool next_sync = false; - int chunk = SIZE_RSPACKET * scan_syncs; - while ( in.readable() >= chunk+1 && // Need 1 ahead for bit shifting - out.writable() >= chunk && // Use as temp buffer - ( !state_out || state_out->writable()>=1 ) ) { - if ( search_sync() ) return; - in.read(chunk); - // Switch to next bit alignment - ++bitphase; - if ( bitphase == 8 ) { - bitphase = 0; - next_sync = true; - } - } - - if ( next_sync ) { - // No lock this time - ++next_sync_count; - if ( next_sync_count >= 3 ) { - // After a few cycles without a lock, resync the deconvolver. - next_sync_count = 0; - if ( deconv ) deconv->next_sync(); - } - } + void run() + { + if (report_state && state_out && state_out->writable() >= 1) + { + // Report unlocked state on first invocation. + state_out->write(0); + report_state = false; + } + if (synchronized) + run_decoding(); + else + { + if (fastlock) + run_searching_fast(); + else + run_searching(); + } } - void run_searching_fast() { - int chunk = SIZE_RSPACKET * scan_syncs; - while ( in.readable() >= chunk+1 && // Need 1 ahead for bit shifting - out.writable() >= chunk && // Use as temp buffer - ( !state_out || state_out->writable()>=1 ) ) { - if ( resync_phase == 0 ) { - // Try all bit alighments - for ( bitphase=0; bitphase<=7; ++bitphase ) { - if ( search_sync() ) return; - } - } - in.read(SIZE_RSPACKET); - if ( ++resync_phase >= resync_period ) resync_phase = 0; - } + void run_searching() + { + bool next_sync = false; + unsigned int chunk = SIZE_RSPACKET * scan_syncs; + + while (in.readable() >= chunk + 1 && // Need 1 ahead for bit shifting + out.writable() >= chunk && // Use as temp buffer + (!state_out || state_out->writable() >= 1)) + { + if (search_sync()) + return; + in.read(chunk); + // Switch to next bit alignment + ++bitphase; + if (bitphase == 8) + { + bitphase = 0; + next_sync = true; + } + } + + if (next_sync) + { + // No lock this time + ++next_sync_count; + if (next_sync_count >= 3) + { + // After a few cycles without a lock, resync the deconvolver. + next_sync_count = 0; + if (deconv) + deconv->next_sync(); + } + } } - bool search_sync() { - int chunk = SIZE_RSPACKET * scan_syncs; - // Bit-shift [scan_sync] packets according to current [bitphase] - Tbyte *pin = in.rd(), *pend = pin+chunk; - Tbyte *pout = out.wr(); - unsigned short w = *pin++; - for ( ; pin<=pend; ++pin,++pout ) { - w = (w<<8) | *pin; - *pout = w >> bitphase; - } - // Search for [want_sync] start codes at all 204 offsets - for ( int i=0; i nsyncs_n) - { polarity=0; nsyncs=nsyncs_p; phase8=phase8_p; } - else - { polarity=-1; nsyncs=nsyncs_n; phase8=phase8_n; } - if ( nsyncs>=want_syncs && phase8>=0 ) { - if ( sch->debug ) fprintf(stderr, "Locked\n"); - if ( ! i ) { // Avoid fixpoint detection in scheduler - i = SIZE_RSPACKET; - phase8 = (phase8+1) & 7; - } - in.read(i); // Skip to first start code - synchronized = true; - lock_timeleft = lock_timeout; - locktime = 0; - if ( state_out ) - state_out->write(1); - return true; - } - } - return false; + void run_searching_fast() + { + unsigned int chunk = SIZE_RSPACKET * scan_syncs; + + while (in.readable() >= chunk + 1 && // Need 1 ahead for bit shifting + out.writable() >= chunk && // Use as temp buffer + (!state_out || state_out->writable() >= 1)) + { + if (resync_phase == 0) + { + // Try all bit alighments + for (bitphase = 0; bitphase <= 7; ++bitphase) + { + if (search_sync()) + return; + } + } + in.read(SIZE_RSPACKET); + if (++resync_phase >= resync_period) + resync_phase = 0; + } } - void run_decoding() { - while ( in.readable() >= SIZE_RSPACKET+1 && // +1 for bit shifting - out.writable() >= SIZE_RSPACKET && - ( !state_out || state_out->writable()>=1 ) && - ( !locktime_out || locktime_out->writable()>=1 ) ) { - Tbyte *pin = in.rd(), *pend = pin+SIZE_RSPACKET; - Tbyte *pout = out.wr(); - unsigned short w = *pin++; - for ( ; pin<=pend; ++pin,++pout ) { - w = (w<<8) | *pin; - *pout = (w >> bitphase) ^ polarity; - } - in.read(SIZE_RSPACKET); - Tbyte syncbyte = *out.wr(); - out.written(SIZE_RSPACKET); - ++locktime; - if ( locktime_out ) - locktime_out->write(locktime); - // Reset timer if sync byte is correct - Tbyte expected = phase8 ? MPEG_SYNC : MPEG_SYNC_INV; - if ( syncbyte == expected ) lock_timeleft = lock_timeout; - phase8 = (phase8+1) & 7; - --lock_timeleft; - if ( ! lock_timeleft ) { - if ( sch->debug ) fprintf(stderr, "Unlocked\n"); - synchronized = false; - next_sync_count = 0; - if ( state_out ) - state_out->write(0); - return; - } - } + bool search_sync() + { + int chunk = SIZE_RSPACKET * scan_syncs; + // Bit-shift [scan_sync] packets according to current [bitphase] + Tbyte *pin = in.rd(), *pend = pin + chunk; + Tbyte *pout = out.wr(); + unsigned short w = *pin++; + for (; pin <= pend; ++pin, ++pout) + { + w = (w << 8) | *pin; + *pout = w >> bitphase; + } + // Search for [want_sync] start codes at all 204 offsets + for (int i = 0; i < SIZE_RSPACKET; ++i) + { + int nsyncs_p = 0, nsyncs_n = 0; // # start codes assuming pos/neg polarity + int phase8_p = -1, phase8_n = -1; // Position in sequence of 8 packets + Tbyte *p = &out.wr()[i]; + for (int j = 0; j < scan_syncs; ++j, p += SIZE_RSPACKET) + { + Tbyte b = *p; + if (b == MPEG_SYNC) + { + ++nsyncs_p; + phase8_n = (8 - j) & 7; + } + if (b == MPEG_SYNC_INV) + { + ++nsyncs_n; + phase8_p = (8 - j) & 7; + } + } + // Detect most likely polarity + int nsyncs; + if (nsyncs_p > nsyncs_n) + { + polarity = 0; + nsyncs = nsyncs_p; + phase8 = phase8_p; + } + else + { + polarity = -1; + nsyncs = nsyncs_n; + phase8 = phase8_n; + } + if (nsyncs >= want_syncs && phase8 >= 0) + { + if (sch->debug) + fprintf(stderr, "Locked\n"); + if (!i) + { // Avoid fixpoint detection in scheduler + i = SIZE_RSPACKET; + phase8 = (phase8 + 1) & 7; + } + in.read(i); // Skip to first start code + synchronized = true; + lock_timeleft = lock_timeout; + locktime = 0; + if (state_out) + state_out->write(1); + return true; + } + } + return false; } - private: + void run_decoding() + { + while (in.readable() >= SIZE_RSPACKET + 1 + && // +1 for bit shifting + out.writable() >= SIZE_RSPACKET + && (!state_out || state_out->writable() >= 1) + && (!locktime_out || locktime_out->writable() >= 1)) + { + Tbyte *pin = in.rd(), *pend = pin + SIZE_RSPACKET; + Tbyte *pout = out.wr(); + unsigned short w = *pin++; + for (; pin <= pend; ++pin, ++pout) + { + w = (w << 8) | *pin; + *pout = (w >> bitphase) ^ polarity; + } + in.read(SIZE_RSPACKET); + Tbyte syncbyte = *out.wr(); + out.written(SIZE_RSPACKET); + ++locktime; + if (locktime_out) + locktime_out->write(locktime); + // Reset timer if sync byte is correct + Tbyte expected = phase8 ? MPEG_SYNC : MPEG_SYNC_INV; + if (syncbyte == expected) + lock_timeleft = lock_timeout; + phase8 = (phase8 + 1) & 7; + --lock_timeleft; + if (!lock_timeleft) + { + if (sch->debug) + fprintf(stderr, "Unlocked\n"); + synchronized = false; + next_sync_count = 0; + if (state_out) + state_out->write(0); + return; + } + } + } + +private: pipereader in; pipewriter out; - deconvol_sync *deconv; + deconvol_sync *deconv; unsigned char polarity; // XOR mask, 0 or 0xff int resync_phase; int bitphase; @@ -871,330 +1049,376 @@ namespace leansdr { pipewriter *state_out; pipewriter *locktime_out; bool report_state; - }; +}; +template +struct rspacket +{ + Tbyte data[SIZE_RSPACKET]; +}; - template - struct rspacket { Tbyte data[SIZE_RSPACKET]; }; +// INTERLEAVER - - // INTERLEAVER - - struct interleaver : runnable { - interleaver(scheduler *sch, pipebuf< rspacket > &_in, - pipebuf< u8 > &_out) - : runnable(sch, "interleaver"), - in(_in), out(_out,SIZE_RSPACKET) { +struct interleaver: runnable +{ + interleaver(scheduler *sch, pipebuf > &_in, pipebuf &_out) : + runnable(sch, "interleaver"), in(_in), out(_out, SIZE_RSPACKET) + { } - void run() { - while ( in.readable() >= 12 && - out.writable() >= SIZE_RSPACKET ) { - rspacket *pin=in.rd(); - u8 *pout = out.wr(); - int delay = 0; - for ( int i=0; i= 12 && out.writable() >= SIZE_RSPACKET) + { + rspacket *pin = in.rd(); + u8 *pout = out.wr(); + int delay = 0; + for (int i = 0; i < SIZE_RSPACKET; + ++i, ++pout, delay = (delay + 1) % 12) + *pout = pin[11 - delay].data[i]; + in.read(1); + out.written(SIZE_RSPACKET); + } } - private: - pipereader< rspacket > in; +private: + pipereader > in; pipewriter out; - }; // interleaver +}; +// interleaver +// DEINTERLEAVER - // DEINTERLEAVER - - template - struct deinterleaver : runnable { +template +struct deinterleaver: runnable +{ deinterleaver(scheduler *sch, pipebuf &_in, - pipebuf< rspacket > &_out) - : runnable(sch, "deinterleaver"), - in(_in), out(_out) { + pipebuf > &_out) : + runnable(sch, "deinterleaver"), in(_in), out(_out) + { } - void run() { - while ( in.readable() >= 17*11*12+SIZE_RSPACKET && - out.writable() >= 1 ) { - Tbyte *pin = in.rd()+17*11*12, *pend=pin+SIZE_RSPACKET; - Tbyte *pout= out.wr()->data; - for ( int delay=17*11; pin= 17 * 11 * 12 + SIZE_RSPACKET + && out.writable() >= 1) + { + Tbyte *pin = in.rd() + 17 * 11 * 12, *pend = pin + SIZE_RSPACKET; + Tbyte *pout = out.wr()->data; + for (int delay = 17 * 11; pin < pend; + ++pin, ++pout, delay = (delay - 17 + 17 * 12) % (17 * 12)) + *pout = pin[-delay * 12]; + in.read(SIZE_RSPACKET); + out.written(1); + } } - private: +private: pipereader in; - pipewriter< rspacket > out; - }; // deinterleaver + pipewriter > out; +}; +// deinterleaver +static const int SIZE_TSPACKET = 188; +struct tspacket +{ + u8 data[SIZE_TSPACKET]; +}; - static const int SIZE_TSPACKET = 188; - struct tspacket { u8 data[SIZE_TSPACKET]; }; +// RS ENCODER - - // RS ENCODER - - struct rs_encoder : runnable { - rs_encoder(scheduler *sch, - pipebuf &_in, pipebuf< rspacket > &_out) - : runnable(sch, "RS encoder"), - in(_in), out(_out) { } - - void run() { - while ( in.readable()>=1 && out.writable()>=1 ) { - u8 *pin = in.rd()->data; - u8 *pout = out.wr()->data; - // The first 188 bytes are the uncoded message P(X) - memcpy(pout, pin, SIZE_TSPACKET); - // Append 16 RS parity bytes R(X) = - (P(X)*X^16 mod G(X)) - // so that G(X) divides the coded message S(X) = P(X)*X^16 - R(X). - rs.encode(pout); - in.read(1); - out.written(1); - } +struct rs_encoder: runnable +{ + rs_encoder(scheduler *sch, pipebuf &_in, + pipebuf > &_out) : + runnable(sch, "RS encoder"), in(_in), out(_out) + { } - private: + + void run() + { + while (in.readable() >= 1 && out.writable() >= 1) + { + u8 *pin = in.rd()->data; + u8 *pout = out.wr()->data; + // The first 188 bytes are the uncoded message P(X) + memcpy(pout, pin, SIZE_TSPACKET); + // Append 16 RS parity bytes R(X) = - (P(X)*X^16 mod G(X)) + // so that G(X) divides the coded message S(X) = P(X)*X^16 - R(X). + rs.encode(pout); + in.read(1); + out.written(1); + } + } +private: rs_engine rs; pipereader in; - pipewriter< rspacket > out; - }; // rs_encoder + pipewriter > out; +}; +// rs_encoder +// RS DECODER - // RS DECODER - - template - struct rs_decoder : runnable { +template +struct rs_decoder: runnable +{ rs_engine rs; - rs_decoder(scheduler *sch, - pipebuf< rspacket > &_in, - pipebuf &_out, - pipebuf *_bitcount=NULL, - pipebuf *_errcount=NULL) - : runnable(sch, "RS decoder"), - in(_in), out(_out) { - bitcount = _bitcount ? new pipewriter(*_bitcount) : NULL; - errcount = _errcount ? new pipewriter(*_errcount) : NULL; + rs_decoder(scheduler *sch, pipebuf > &_in, + pipebuf &_out, pipebuf *_bitcount = NULL, + pipebuf *_errcount = NULL) : + runnable(sch, "RS decoder"), in(_in), out(_out) + { + bitcount = _bitcount ? new pipewriter(*_bitcount) : NULL; + errcount = _errcount ? new pipewriter(*_errcount) : NULL; } - void run() { - if ( bitcount && bitcount->writable()<1 ) return; - if ( errcount && errcount->writable()<1 ) return; + void run() + { + if (bitcount && bitcount->writable() < 1) + return; + if (errcount && errcount->writable() < 1) + return; - int nbits=0, nerrs=0; + int nbits = 0, nerrs = 0; - while ( in.readable()>=1 && out.writable()>=1 ) { - Tbyte *pin = in.rd()->data; - u8 *pout = out.wr()->data; + while (in.readable() >= 1 && out.writable() >= 1) + { + Tbyte *pin = in.rd()->data; + u8 *pout = out.wr()->data; - nbits += SIZE_RSPACKET * 8; + nbits += SIZE_RSPACKET * 8; - // The message is the first 188 bytes. - if ( sizeof(Tbyte) == 1 ) - memcpy(pout, pin, SIZE_TSPACKET); - else - fail("Erasures not implemented"); + // The message is the first 188 bytes. + if (sizeof(Tbyte) == 1) + memcpy(pout, pin, SIZE_TSPACKET); + else + fail("Erasures not implemented"); - u8 synd[16]; - bool corrupted = rs.syndromes(pin, synd); + u8 synd[16]; + bool corrupted = rs.syndromes(pin, synd); #if 0 - if ( ! corrupted ) { - // Test BM - fprintf(stderr, "Simulating errors\n"); - pin[203] ^= 42; - pin[202] ^= 99; - corrupted = rs.syndromes(pin, synd); - } + if ( ! corrupted ) + { + // Test BM + fprintf(stderr, "Simulating errors\n"); + pin[203] ^= 42; + pin[202] ^= 99; + corrupted = rs.syndromes(pin, synd); + } #endif - if ( ! corrupted ) { - if ( sch->debug ) - fprintf(stderr, "_"); // Packet received without errors. - } else { - corrupted = rs.correct(synd, pout, pin, &nerrs); - if ( sch->debug ) { - if ( ! corrupted ) - fprintf(stderr, "."); // Errors were corrected. - else - fprintf(stderr, "!"); // Packet still corrupted. - } - } + if (!corrupted) + { + if (sch->debug) + fprintf(stderr, "_"); // Packet received without errors. + } + else + { + corrupted = rs.correct(synd, pout, pin, &nerrs); + if (sch->debug) + { + if (!corrupted) + fprintf(stderr, "."); // Errors were corrected. + else + fprintf(stderr, "!"); // Packet still corrupted. + } + } - in.read(1); - - // Output corrupted packets (with a special mark) - // otherwise the derandomizer will lose synchronization. - if ( corrupted ) pout[0] ^= MPEG_SYNC_CORRUPTED; - out.written(1); + in.read(1); - } - if ( nbits ) { - if ( bitcount ) bitcount->write(nbits); - if ( errcount ) errcount->write(nerrs); - } + // Output corrupted packets (with a special mark) + // otherwise the derandomizer will lose synchronization. + if (corrupted) + pout[0] ^= MPEG_SYNC_CORRUPTED; + out.written(1); + + } + if (nbits) + { + if (bitcount) + bitcount->write(nbits); + if (errcount) + errcount->write(nerrs); + } } - private: - pipereader< rspacket > in; +private: + pipereader > in; pipewriter out; pipewriter *bitcount, *errcount; - }; // rs_decoder +}; +// rs_decoder +// RANDOMIZER - // RANDOMIZER - - struct randomizer : runnable { - randomizer(scheduler *sch, - pipebuf &_in, pipebuf &_out) - : runnable(sch, "derandomizer"), - in(_in), out(_out) { - precompute_pattern(); - pos = pattern; - pattern_end = pattern + sizeof(pattern)/sizeof(pattern[0]); +struct randomizer: runnable +{ + randomizer(scheduler *sch, pipebuf &_in, pipebuf &_out) : + runnable(sch, "derandomizer"), in(_in), out(_out) + { + precompute_pattern(); + pos = pattern; + pattern_end = pattern + sizeof(pattern) / sizeof(pattern[0]); } - void precompute_pattern() { - // EN 300 421, section 4.4.1 Transport multiplex adaptation - pattern[0] = 0xff; // Invert one in eight sync bytes - unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) - for ( int i=1; i<188*8; ++i ) { - u8 out = 0; - for ( int n=8; n--; ) { - int bit = ((st>>13) ^ (st>>14)) & 1; // Taps - out = (out<<1) | bit; // MSB first - st = (st<<1) | bit; // Feedback - } - pattern[i] = (i%188) ? out : 0; // Inhibit on sync bytes - } + void precompute_pattern() + { + // EN 300 421, section 4.4.1 Transport multiplex adaptation + pattern[0] = 0xff; // Invert one in eight sync bytes + unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) + for (int i = 1; i < 188 * 8; ++i) + { + u8 out = 0; + for (int n = 8; n--;) + { + int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps + out = (out << 1) | bit; // MSB first + st = (st << 1) | bit; // Feedback + } + pattern[i] = (i % 188) ? out : 0; // Inhibit on sync bytes + } } - void run() { - while ( in.readable()>=1 && out.writable()>=1 ) { - u8 *pin = in.rd()->data, *pend = pin+SIZE_TSPACKET; - u8 *pout= out.wr()->data; - if ( pin[0] != MPEG_SYNC ) - fprintf(stderr, "randomizer: bad MPEG sync %02x\n", pin[0]); - for ( ; pin= 1 && out.writable() >= 1) + { + u8 *pin = in.rd()->data, *pend = pin + SIZE_TSPACKET; + u8 *pout = out.wr()->data; + if (pin[0] != MPEG_SYNC) + fprintf(stderr, "randomizer: bad MPEG sync %02x\n", pin[0]); + for (; pin < pend; ++pin, ++pout, ++pos) + *pout = *pin ^ *pos; + if (pos == pattern_end) + pos = pattern; + in.read(1); + out.written(1); + } } - private: - u8 pattern[188*8], *pattern_end, *pos; +private: + u8 pattern[188 * 8], *pattern_end, *pos; pipereader in; pipewriter out; - }; // randomizer +}; +// randomizer +// DERANDOMIZER - // DERANDOMIZER - - struct derandomizer : runnable { - derandomizer(scheduler *sch, - pipebuf &_in, pipebuf &_out) - : runnable(sch, "derandomizer"), - in(_in), out(_out) { - precompute_pattern(); - pos = pattern; - pattern_end = pattern + sizeof(pattern)/sizeof(pattern[0]); +struct derandomizer: runnable +{ + derandomizer(scheduler *sch, pipebuf &_in, + pipebuf &_out) : + runnable(sch, "derandomizer"), in(_in), out(_out) + { + precompute_pattern(); + pos = pattern; + pattern_end = pattern + sizeof(pattern) / sizeof(pattern[0]); } - void precompute_pattern() { - // EN 300 421, section 4.4.1 Transport multiplex adaptation - pattern[0] = 0xff; // Restore the inverted sync byte - unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) - for ( int i=1; i<188*8; ++i ) { - u8 out = 0; - for ( int n=8; n--; ) { - int bit = ((st>>13) ^ (st>>14)) & 1; // Taps - out = (out<<1) | bit; // MSB first - st = (st<<1) | bit; // Feedback - } - pattern[i] = (i%188) ? out : 0; // Inhibit on sync bytes - } + void precompute_pattern() + { + // EN 300 421, section 4.4.1 Transport multiplex adaptation + pattern[0] = 0xff; // Restore the inverted sync byte + unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) + for (int i = 1; i < 188 * 8; ++i) + { + u8 out = 0; + for (int n = 8; n--;) + { + int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps + out = (out << 1) | bit; // MSB first + st = (st << 1) | bit; // Feedback + } + pattern[i] = (i % 188) ? out : 0; // Inhibit on sync bytes + } } - void run() { - while ( in.readable()>=1 && out.writable()>=1 ) { - u8 *pin = in.rd()->data, *pend = pin+SIZE_TSPACKET; - u8 *pout= out.wr()->data; - if ( pin[0] == MPEG_SYNC_INV || - pin[0] == (MPEG_SYNC_INV^MPEG_SYNC_CORRUPTED) ) { - if ( pos != pattern ) { - if ( sch->debug ) - fprintf(stderr, "derandomizer: resynchronizing\n"); - pos = pattern; - } - } - for ( ; pin= 1 && out.writable() >= 1) + { + u8 *pin = in.rd()->data, *pend = pin + SIZE_TSPACKET; + u8 *pout = out.wr()->data; + if (pin[0] == MPEG_SYNC_INV + || pin[0] == (MPEG_SYNC_INV ^ MPEG_SYNC_CORRUPTED)) + { + if (pos != pattern) + { + if (sch->debug) + fprintf(stderr, "derandomizer: resynchronizing\n"); + pos = pattern; + } + } + for (; pin < pend; ++pin, ++pout, ++pos) + *pout = *pin ^ *pos; + if (pos == pattern_end) + pos = pattern; + in.read(1); - u8 sync = out.wr()->data[0]; - if ( sync == MPEG_SYNC ) { - out.written(1); - } else { - if ( sync != (MPEG_SYNC^MPEG_SYNC_CORRUPTED) ) - if ( sch->debug ) fprintf(stderr, "(%02x)", sync); - out.wr()->data[1] |= 0x80; // Set the Transport Error Indicator bit - // We could output corrupted packets here, in case the - // MPEG decoder can use them somehow. - //out.written(1); - } - } + u8 sync = out.wr()->data[0]; + if (sync == MPEG_SYNC) + { + out.written(1); + } + else + { + if (sync != (MPEG_SYNC ^ MPEG_SYNC_CORRUPTED)) + if (sch->debug) + fprintf(stderr, "(%02x)", sync); + out.wr()->data[1] |= 0x80; // Set the Transport Error Indicator bit + // We could output corrupted packets here, in case the + // MPEG decoder can use them somehow. + //out.written(1); + } + } } - private: - u8 pattern[188*8], *pattern_end, *pos; +private: + u8 pattern[188 * 8], *pattern_end, *pos; pipereader in; pipewriter out; - }; // derandomizer +}; +// derandomizer +// VITERBI DECODING +// Supports all code rates and constellations +// Simplified metric to support large constellations. - // VITERBI DECODING - // Supports all code rates and constellations - // Simplified metric to support large constellations. +// This version implements puncturing by expanding the trellis. +// TBD Compare performance vs skipping updates in a 1/2 trellis. - // This version implements puncturing by expanding the trellis. - // TBD Compare performance vs skipping updates in a 1/2 trellis. - - struct viterbi_sync : runnable { +struct viterbi_sync: runnable +{ typedef uint8_t TS, TCS, TUS; typedef int32_t TBM; // Only 16 bits per IQ, but several IQ per Viterbi CS typedef int32_t TPM; - typedef viterbi_dec_interface dvb_dec_interface; + typedef viterbi_dec_interface dvb_dec_interface; // 1/2: 6 bits of state, 1 bit in, 2 bits out - typedef bitpath path_12; - typedef trellis trellis_12; - typedef viterbi_dec dvb_dec_12; + typedef bitpath path_12; + typedef trellis trellis_12; + typedef viterbi_dec dvb_dec_12; // 2/3: 6 bits of state, 2 bits in, 3 bits out - typedef bitpath path_23; - typedef trellis trellis_23; - typedef viterbi_dec dvb_dec_23; + typedef bitpath path_23; + typedef trellis trellis_23; + typedef viterbi_dec dvb_dec_23; // 4/6: 6 bits of state, 4 bits in, 6 bits out - typedef bitpath path_46; - typedef trellis trellis_46; - typedef viterbi_dec dvb_dec_46; + typedef bitpath path_46; + typedef trellis trellis_46; + typedef viterbi_dec dvb_dec_46; // 3/4: 6 bits of state, 3 bits in, 4 bits out - typedef bitpath path_34; - typedef trellis trellis_34; - typedef viterbi_dec dvb_dec_34; + typedef bitpath path_34; + typedef trellis trellis_34; + typedef viterbi_dec dvb_dec_34; // 4/5: 6 bits of state, 4 bits in, 5 bits out (non-standard) - typedef bitpath path_45; - typedef trellis trellis_45; - typedef viterbi_dec dvb_dec_45; + typedef bitpath path_45; + typedef trellis trellis_45; + typedef viterbi_dec dvb_dec_45; // 5/6: 6 bits of state, 5 bits in, 6 bits out - typedef bitpath path_56; - typedef trellis trellis_56; - typedef viterbi_dec dvb_dec_56; + typedef bitpath path_56; + typedef trellis trellis_56; + typedef viterbi_dec dvb_dec_56; // QPSK 7/8: 6 bits of state, 7 bits in, 8 bits out - typedef bitpath path_78; - typedef trellis trellis_78; - typedef viterbi_dec dvb_dec_78; + typedef bitpath path_78; + typedef trellis trellis_78; + typedef viterbi_dec dvb_dec_78; - private: +private: pipereader in; pipewriter out; cstln_lut<256> *cstln; @@ -1202,204 +1426,243 @@ namespace leansdr { int bits_per_symbol; // Bits per IQ symbol (not per coded symbol) int nsyncs; int nshifts; - struct sync { - int shift; - dvb_dec_interface *dec; - TCS *map; // [nsymbols] - } *syncs; // [nsyncs] + struct sync + { + int shift; + dvb_dec_interface *dec; + TCS *map; // [nsymbols] + }*syncs; // [nsyncs] int current_sync; static const int chunk_size = 128; int resync_phase; - public: +public: int resync_period; - viterbi_sync(scheduler *sch, - pipebuf &_in, pipebuf &_out, - cstln_lut<256> *_cstln, code_rate cr) - : runnable(sch, "viterbi_sync"), - in(_in), out(_out, chunk_size), - cstln(_cstln), - current_sync(0), - resync_phase(0), - resync_period(32) // 1/32 = 9% synchronization overhead TBD + viterbi_sync(scheduler *sch, pipebuf &_in, + pipebuf &_out, cstln_lut<256> *_cstln, code_rate cr) : + runnable(sch, "viterbi_sync"), in(_in), out(_out, chunk_size), cstln( + _cstln), current_sync(0), resync_phase(0), resync_period(32) // 1/32 = 9% synchronization overhead TBD { - bits_per_symbol = log2i(cstln->nsymbols); - fec = &fec_specs[cr]; - { // Sanity check: FEC block size must be a multiple of label size. - int symbols_per_block = fec->bits_out / bits_per_symbol; - if ( bits_per_symbol*symbols_per_block != fec->bits_out ) - fail("Code rate not suitable for this constellation"); - } - int nconj; - switch ( cstln->nsymbols ) { - case 2: nconj = 1; break; // Conjugation is not relevant for BPSK - default: nconj = 2; break; - } + bits_per_symbol = log2i(cstln->nsymbols); + fec = &fec_specs[cr]; + { // Sanity check: FEC block size must be a multiple of label size. + int symbols_per_block = fec->bits_out / bits_per_symbol; + if (bits_per_symbol * symbols_per_block != fec->bits_out) + fail("Code rate not suitable for this constellation"); + } + int nconj; + switch (cstln->nsymbols) + { + case 2: + nconj = 1; + break; // Conjugation is not relevant for BPSK + default: + nconj = 2; + break; + } - int nrotations; - switch ( cstln->nsymbols ) { - case 2: - case 4: - // For BPSK and QPSK, 180° rotation is handled as - // polarity inversion in mpeg_sync. - nrotations = cstln->nrotations/2; - break; - default: - nrotations = cstln->nrotations; - break; - } - nshifts = fec->bits_out / bits_per_symbol; - nsyncs = nconj * nrotations * nshifts; + int nrotations; + switch (cstln->nsymbols) + { + case 2: + case 4: + // For BPSK and QPSK, 180° rotation is handled as + // polarity inversion in mpeg_sync. + nrotations = cstln->nrotations / 2; + break; + default: + nrotations = cstln->nrotations; + break; + } + nshifts = fec->bits_out / bits_per_symbol; + nsyncs = nconj * nrotations * nshifts; - // TBD Many HOM constellations are labelled in such a way - // that certain rot/conj combinations are equivalent to - // polarity inversion. We could reduce nsyncs. + // TBD Many HOM constellations are labelled in such a way + // that certain rot/conj combinations are equivalent to + // polarity inversion. We could reduce nsyncs. - syncs = new sync[nsyncs]; + syncs = new sync[nsyncs]; - for ( int s=0; snrotations); + for (int s = 0; s < nsyncs; ++s) + { + // Bit pattern [shift|conj|rot] + int rot = s % nrotations; + int conj = (s / nrotations) % nconj; + int shift = s / nrotations / nconj; + syncs[s].shift = shift; + if (shift) // Reuse identical map + syncs[s].map = syncs[conj * nrotations + rot].map; + else + syncs[s].map = init_map(conj, + 2 * M_PI * rot / cstln->nrotations); #if 0 - fprintf(stderr, "sync %3d: conj%d offs%d rot%d/%d map:", - s, conj, syncs[s].shift, rot, cstln->nrotations); - for ( int i=0; insymbols; ++i ) - fprintf(stderr, " %2d", syncs[s].map[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "sync %3d: conj%d offs%d rot%d/%d map:", + s, conj, syncs[s].shift, rot, cstln->nrotations); + for ( int i=0; insymbols; ++i ) + fprintf(stderr, " %2d", syncs[s].map[i]); + fprintf(stderr, "\n"); #endif - } + } - if ( cr == FEC12 ) { - trellis_12 *trell = new trellis_12(); - trell->init_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_12(trell); + } + else if (cr == FEC23) + { + trellis_23 *trell = new trellis_23(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_23(trell); + } + else if (cr == FEC46) + { + trellis_46 *trell = new trellis_46(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_46(trell); + } + else if (cr == FEC34) + { + trellis_34 *trell = new trellis_34(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_34(trell); + } + else if (cr == FEC45) + { + trellis_45 *trell = new trellis_45(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_45(trell); + } + else if (cr == FEC56) + { + trellis_56 *trell = new trellis_56(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_56(trell); + } + else if (cr == FEC78) + { + trellis_78 *trell = new trellis_78(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_78(trell); + } + else + fail("CR not supported"); } - TCS *init_map(bool conj, float angle) { - // Each constellation has its own pattern for labels. - // Here we simply tabulate systematically. - TCS *map = new TCS[cstln->nsymbols]; - float ca=cosf(angle), sa=sinf(angle); - for ( int i=0; insymbols; ++i ) { - int8_t I = cstln->symbols[i].re; - int8_t Q = cstln->symbols[i].im; - if ( conj ) Q = -Q; - int8_t RI = I*ca - Q*sa; - int8_t RQ = I*sa + Q*ca; - cstln_lut<256>::result *pr = cstln->lookup(RI, RQ); - map[i] = pr->ss.symbol; - } - return map; + TCS *init_map(bool conj, float angle) + { + // Each constellation has its own pattern for labels. + // Here we simply tabulate systematically. + TCS *map = new TCS[cstln->nsymbols]; + float ca = cosf(angle), sa = sinf(angle); + for (int i = 0; i < cstln->nsymbols; ++i) + { + int8_t I = cstln->symbols[i].re; + int8_t Q = cstln->symbols[i].im; + if (conj) + Q = -Q; + int8_t RI = I * ca - Q * sa; + int8_t RQ = I * sa + Q * ca; + cstln_lut<256>::result *pr = cstln->lookup(RI, RQ); + map[i] = pr->ss.symbol; + } + return map; } - inline TUS update_sync(int s, softsymbol *pin, TPM *discr) { - // Read one FEC ouput block - pin += syncs[s].shift; - TCS cs = 0; - TBM cost = 0; - for ( int i=0; isymbol]; - cost += pin->cost; - } - return syncs[s].dec->update(cs, cost, discr); + inline TUS update_sync(int s, softsymbol *pin, TPM *discr) + { + // Read one FEC ouput block + pin += syncs[s].shift; + TCS cs = 0; + TBM cost = 0; + for (int i = 0; i < nshifts; ++i, ++pin) + { + cs = (cs << bits_per_symbol) | syncs[s].map[pin->symbol]; + cost += pin->cost; + } + return syncs[s].dec->update(cs, cost, discr); } - void run() { - // Number of FEC blocks to fill the bitpath depth. - // Before that we cannot discriminate between synchronizers - int discr_delay = 64 / fec->bits_in; + void run() + { + // Number of FEC blocks to fill the bitpath depth. + // Before that we cannot discriminate between synchronizers + int discr_delay = 64 / fec->bits_in; - // Process [chunk_size] FEC blocks at a time + // Process [chunk_size] FEC blocks at a time - while ( in.readable() >= nshifts*chunk_size + (nshifts-1) && - out.writable()*8 >= fec->bits_in*chunk_size ) { - TPM totaldiscr[nsyncs]; - for ( int s=0; s= nshifts * chunk_size + (nshifts - 1) + && ((long) out.writable() * 8) >= fec->bits_in * chunk_size) + { + TPM totaldiscr[nsyncs]; + for (int s = 0; s < nsyncs; ++s) + totaldiscr[s] = 0; - uint64_t outstream = 0; - int nout = 0; - softsymbol *pin = in.rd(); - for ( int blocknum=0; blocknumbits_in) | result; - nout += fec->bits_in; - if ( blocknum >= discr_delay ) totaldiscr[current_sync] += discr; - if ( ! resync_phase ) { - // Every [resync_period] chunks, also run the other decoders. - for ( int s=0; s= discr_delay ) totaldiscr[s] += discr; - } - } - while ( nout >= 8 ) { - out.write(outstream>>(nout-8)); - nout -= 8; - } - } // chunk_size - in.read(chunk_size*nshifts); - if ( nout ) fail("overlapping out"); - if ( ! resync_phase ) { - // Switch to another decoder ? - int best = current_sync; - for ( int s=0; s totaldiscr[best] ) best = s; - if ( best != current_sync ) { - if ( sch->debug ) fprintf(stderr, "{%d->%d}", current_sync, best); - current_sync = best; - } - } - if ( ++resync_phase >= resync_period ) resync_phase = 0; - } + uint64_t outstream = 0; + int nout = 0; + softsymbol *pin = in.rd(); + for (int blocknum = 0; blocknum < chunk_size; ++blocknum, pin += + nshifts) + { + TPM discr; + TUS result = update_sync(current_sync, pin, &discr); + outstream = (outstream << fec->bits_in) | result; + nout += fec->bits_in; + if (blocknum >= discr_delay) + totaldiscr[current_sync] += discr; + if (!resync_phase) + { + // Every [resync_period] chunks, also run the other decoders. + for (int s = 0; s < nsyncs; ++s) + { + if (s == current_sync) + continue; + TPM discr; + (void) update_sync(s, pin, &discr); + if (blocknum >= discr_delay) + totaldiscr[s] += discr; + } + } + while (nout >= 8) + { + out.write(outstream >> (nout - 8)); + nout -= 8; + } + } // chunk_size + in.read(chunk_size * nshifts); + if (nout) + fail("overlapping out"); + if (!resync_phase) + { + // Switch to another decoder ? + int best = current_sync; + for (int s = 0; s < nsyncs; ++s) + if (totaldiscr[s] > totaldiscr[best]) + best = s; + if (best != current_sync) + { + if (sch->debug) + fprintf(stderr, "{%d->%d}", current_sync, best); + current_sync = best; + } + } + if (++resync_phase >= resync_period) + resync_phase = 0; + } } - }; // viterbi_sync +}; +// viterbi_sync - - -} // namespace +}// namespace #endif // LEANSDR_DVB_H diff --git a/plugins/channelrx/demoddatv/leansdr/framework.h b/plugins/channelrx/demoddatv/leansdr/framework.h index 449c4864c..07e929b49 100644 --- a/plugins/channelrx/demoddatv/leansdr/framework.h +++ b/plugins/channelrx/demoddatv/leansdr/framework.h @@ -1,54 +1,104 @@ #ifndef LEANSDR_FRAMEWORK_H #define LEANSDR_FRAMEWORK_H +#include #include #include #include #include -namespace leansdr { - - inline void fatal(const char *s) { perror(s); exit(1); } - inline void fail(const char *s) { fprintf(stderr, "** %s\n", s); exit(1); } - - ////////////////////////////////////////////////////////////////////// - // DSP framework - ////////////////////////////////////////////////////////////////////// - - // [pipebuf] is a FIFO buffer with multiple readers. - // [pipewriter] is a client-side hook for writing into a [pipebuf]. - // [pipereader] is a client-side hook reading from a [pipebuf]. - // [runnable] is anything that moves data between [pipebufs]. - // [scheduler] is a global context which invokes [runnables] until fixpoint. - - static const int MAX_PIPES = 64; - static const int MAX_RUNNABLES = 64; - static const int MAX_READERS = 8; - - struct pipebuf_common { - virtual int sizeofT() { return 0; } - virtual long long hash() { return 0; } - virtual void dump(size_t *total_bufs) { } - const char *name; - pipebuf_common(const char *_name) : name(_name) { } - }; +namespace leansdr +{ + +inline void fatal(const char *s) +{ + perror(s); + exit(1); +} + +inline void fail(const char *s) +{ + fprintf(stderr, "leansdr::fail: %s\n", s); + exit(1); +} + +////////////////////////////////////////////////////////////////////// +// DSP framework +////////////////////////////////////////////////////////////////////// + +// [pipebuf] is a FIFO buffer with multiple readers. +// [pipewriter] is a client-side hook for writing into a [pipebuf]. +// [pipereader] is a client-side hook reading from a [pipebuf]. +// [runnable] is anything that moves data between [pipebufs]. +// [scheduler] is a global context which invokes [runnables] until fixpoint. + +static const int MAX_PIPES = 64; +static const int MAX_RUNNABLES = 64; +static const int MAX_READERS = 8; + +struct pipebuf_common +{ + virtual int sizeofT() + { + return 0; + } + + virtual long long hash() + { + return 0; + } + + virtual void dump(std::size_t *total_bufs __attribute__((unused))) + { + } + + pipebuf_common(const char *_name) : + name(_name) + { + } + + virtual ~pipebuf_common() + { + } - struct runnable_common { const char *name; - runnable_common(const char *_name) : name(_name) { } - virtual void run() { } - virtual void shutdown() { } +}; + +struct runnable_common +{ + runnable_common(const char *_name) : + name(_name) + { + } + + virtual ~runnable_common() + { + } + + virtual void run() + { + } + + virtual void shutdown() + { + } + #ifdef DEBUG - ~runnable_common() { fprintf(stderr, "Deallocating %s !\n", name); } + ~runnable_common() + { fprintf(stderr, "Deallocating %s !\n", name);} #endif - }; - - struct window_placement { + + const char *name; +}; + +struct window_placement +{ const char *name; // NULL to terminate int x, y, w, h; - }; +}; - struct scheduler { +struct scheduler +{ pipebuf_common *pipes[MAX_PIPES]; int npipes; runnable_common *runnables[MAX_RUNNABLES]; @@ -56,213 +106,362 @@ namespace leansdr { window_placement *windows; bool verbose, debug; - scheduler() - : npipes(0), nrunnables(0), windows(NULL), - verbose(false), debug(false) { + scheduler() : + npipes(0), nrunnables(0), windows(NULL), verbose(false), debug( + false) + { } - void add_pipe(pipebuf_common *p) { - if ( npipes == MAX_PIPES ) fail("MAX_PIPES"); - pipes[npipes++] = p; + + void add_pipe(pipebuf_common *p) + { + if (npipes == MAX_PIPES) { + fail("MAX_PIPES"); + } + pipes[npipes++] = p; } - void add_runnable(runnable_common *r) { - if ( nrunnables == MAX_RUNNABLES ) fail("MAX_RUNNABLES"); - runnables[nrunnables++] = r; + + void add_runnable(runnable_common *r) + { + if (nrunnables == MAX_RUNNABLES) { + fail("MAX_RUNNABLES"); + } + runnables[nrunnables++] = r; } - void step() { - for ( int i=0; irun(); + + void step() + { + for (int i = 0; i < nrunnables; ++i) { + runnables[i]->run(); + } } - void run() { - unsigned long long prev_hash = 0; - while ( 1 ) { - step(); - unsigned long long h = hash(); - if ( h == prev_hash ) break; - prev_hash = h; - } + + void run() + { + unsigned long long prev_hash = 0; + + while (1) + { + step(); + unsigned long long h = hash(); + if (h == prev_hash) { + break; + } + prev_hash = h; + } } - void shutdown() { - for ( int i=0; ishutdown(); + + void shutdown() + { + for (int i = 0; i < nrunnables; ++i) { + runnables[i]->shutdown(); + } } - unsigned long long hash() { - unsigned long long h = 0; - for ( int i=0; ihash(); - return h; + + unsigned long long hash() + { + unsigned long long h = 0; + for (int i = 0; i < npipes; ++i) { + h += (1 + i) * pipes[i]->hash(); + } + return h; } - - void dump() { - fprintf(stderr, "\n"); - size_t total_bufs = 0; - for ( int i=0; idump(&total_bufs); - fprintf(stderr, "Total buffer memory: %ld KiB\n", - (unsigned long)total_bufs/1024); + + void dump() + { + fprintf(stderr, "\n"); + std::size_t total_bufs = 0; + for (int i = 0; i < npipes; ++i) { + pipes[i]->dump(&total_bufs); + } + fprintf(stderr, "leansdr::scheduler::dump Total buffer memory: %ld KiB\n", (unsigned long) total_bufs / 1024); } - }; - - struct runnable : runnable_common { - runnable(scheduler *_sch, const char *name) - : runnable_common(name), sch(_sch) { - sch->add_runnable(this); +}; + +struct runnable: runnable_common +{ + runnable(scheduler *_sch, const char *name) : + runnable_common(name), sch(_sch) + { + sch->add_runnable(this); } - protected: +protected: scheduler *sch; - }; - - template - struct pipebuf : pipebuf_common { +}; + +template +struct pipebuf: pipebuf_common +{ T *buf; T *rds[MAX_READERS]; int nrd; T *wr; T *end; - int sizeofT() { return sizeof(T); } - pipebuf(scheduler *sch, const char *name, unsigned long size) - : pipebuf_common(name), - buf(new T[size]), nrd(0), wr(buf), end(buf+size), - min_write(1), - total_written(0), total_read(0) { - sch->add_pipe(this); + + int sizeofT() + { + return sizeof(T); } - int add_reader() { - if ( nrd == MAX_READERS ) fail("too many readers"); - rds[nrd] = wr; - return nrd++; + + pipebuf(scheduler *sch, const char *name, unsigned long size) : + pipebuf_common(name), buf(new T[size]), nrd(0), wr(buf), end( + buf + size), min_write(1), total_written(0), total_read(0) + { + sch->add_pipe(this); } - void pack() { - T *rd = wr; - for ( int i=0; i - struct pipewriter { +}; + +template +struct pipewriter +{ pipebuf &buf; - pipewriter(pipebuf &_buf, unsigned long min_write=1) - : buf(_buf) { - if ( min_write > buf.min_write ) buf.min_write = min_write; - } - // Return number of items writable at this->wr, 0 if full. - unsigned long writable() { - if ( buf.end-buf.wr < buf.min_write ) buf.pack(); - return buf.end - buf.wr; - } - T *wr() { return buf.wr; } - void written(unsigned long n) { - if ( buf.wr+n > buf.end ) { - fprintf(stderr, "Bug: overflow to %s\n", buf.name); - exit(1); - } - buf.wr += n; - buf.total_written += n; - } - void write(const T &e) { - *wr() = e; - written(1); - } - }; - // Convenience functions for working with optional pipes + pipewriter(pipebuf &_buf, unsigned long min_write = 1) : + buf(_buf) + { + if (min_write > buf.min_write) { + buf.min_write = min_write; + } + } - template - pipewriter *opt_writer(pipebuf *buf) { + /** Return number of items writable at this->wr, 0 if full. */ + unsigned long writable() + { + if (buf.end < buf.wr) + { + fprintf(stderr, "leansdr::pipewriter::writable: Bug: overflow to %s\n", buf.name); + exit(1); + } + + unsigned long delta = buf.end - buf.wr; + + if (delta < buf.min_write) { + buf.pack(); + } + + return delta; + } + + T *wr() + { + return buf.wr; + } + + void written(unsigned long n) + { + if (buf.wr + n > buf.end) + { + fprintf(stderr, "leansdr::pipewriter::written: Bug: overflow to %s\n", buf.name); + exit(1); + } + buf.wr += n; + buf.total_written += n; + } + + void write(const T &e) + { + *wr() = e; + written(1); + } +}; + +// Convenience functions for working with optional pipes + +template +pipewriter *opt_writer(pipebuf *buf) +{ return buf ? new pipewriter(*buf) : NULL; - } +} - template - bool opt_writable(pipewriter *p, int n=1) { - return (p==NULL) || p->writable()>=n; - } +template +bool opt_writable(pipewriter *p, unsigned int n = 1) +{ + return (p == NULL) || p->writable() >= n; +} - template - void opt_write(pipewriter *p, T val) { - if ( p ) p->write(val); - } +template +void opt_write(pipewriter *p, T val) +{ + if (p) { + p->write(val); + } +} - template - struct pipereader { +template +struct pipereader +{ pipebuf &buf; int id; - pipereader(pipebuf &_buf) : buf(_buf), id(_buf.add_reader()) { } - unsigned long readable() { return buf.wr - buf.rds[id]; } - T *rd() { return buf.rds[id]; } - void read(unsigned long n) { - if ( buf.rds[id]+n > buf.wr ) { - fprintf(stderr, "Bug: underflow from %s\n", buf.name); - exit(1); - } - buf.rds[id] += n; - buf.total_read += n; + + pipereader(pipebuf &_buf) : + buf(_buf), id(_buf.add_reader()) + { } - }; - - // Math functions for templates - - template T gen_sqrt(T x); - inline float gen_sqrt(float x) { return sqrtf(x); } - inline unsigned int gen_sqrt(unsigned int x) { return sqrtl(x); } - inline long double gen_sqrt(long double x) { return sqrtl(x); } - template T gen_abs(T x); - inline float gen_abs(float x) { return fabsf(x); } - inline int gen_abs(int x) { return abs(x); } - inline long int gen_abs(long int x) { return labs(x); } + unsigned long readable() + { + return buf.wr - buf.rds[id]; + } - template T gen_hypot(T x, T y); - inline float gen_hypot(float x, float y) { return hypotf(x,y); } - inline long double gen_hypot(long double x, long double y) - { return hypotl(x,y); } + T *rd() + { + return buf.rds[id]; + } - template T gen_atan2(T y, T x); - inline float gen_atan2(float y, float x) { return atan2f(y,x); } - inline long double gen_atan2(long double y, long double x) - { return atan2l(y,x); } + void read(unsigned long n) + { + if (buf.rds[id] + n > buf.wr) + { + fprintf(stderr, "leansdr::pipereader::read: Bug: underflow from %s\n", buf.name); + exit(1); + } + buf.rds[id] += n; + buf.total_read += n; + } +}; - template - T min(const T &x, const T &y) { return (x - T max(const T &x, const T &y) { return (x T gen_sqrt(T x); +inline float gen_sqrt(float x) +{ + return sqrtf(x); +} - // Abreviations for integer types +inline unsigned int gen_sqrt(unsigned int x) +{ + return sqrtl(x); +} - typedef unsigned char u8; - typedef unsigned short u16; - typedef unsigned long u32; - typedef signed char s8; - typedef signed short s16; - typedef signed long s32; +inline long double gen_sqrt(long double x) +{ + return sqrtl(x); +} + +template T gen_abs(T x); +inline float gen_abs(float x) +{ + return fabsf(x); +} + +inline int gen_abs(int x) +{ + return abs(x); +} + +inline long int gen_abs(long int x) +{ + return labs(x); +} + +template T gen_hypot(T x, T y); +inline float gen_hypot(float x, float y) +{ + return hypotf(x, y); +} + +inline long double gen_hypot(long double x, long double y) +{ + return hypotl(x, y); +} + +template T gen_atan2(T y, T x); +inline float gen_atan2(float y, float x) +{ + return atan2f(y, x); +} + +inline long double gen_atan2(long double y, long double x) +{ + return atan2l(y, x); +} + +template +T min(const T &x, const T &y) +{ + return (x < y) ? x : y; +} + +template +T max(const T &x, const T &y) +{ + return (x < y) ? y : x; +} + +// Abreviations for integer types + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned long u32; +typedef signed char s8; +typedef signed short s16; +typedef signed long s32; } // namespace diff --git a/plugins/channelrx/demoddatv/leansdr/hdlc.h b/plugins/channelrx/demoddatv/leansdr/hdlc.h index efc24c685..7c8dc9f66 100644 --- a/plugins/channelrx/demoddatv/leansdr/hdlc.h +++ b/plugins/channelrx/demoddatv/leansdr/hdlc.h @@ -3,107 +3,144 @@ #include "leansdr/framework.h" -namespace leansdr { +namespace leansdr +{ - // HDLC deframer - - struct hdlc_dec { +// HDLC deframer + +struct hdlc_dec +{ hdlc_dec(int _minframesize, // Including CRC, excluding HDLC flags. - int _maxframesize, - bool _invert) - : minframesize(_minframesize), maxframesize(_maxframesize), - invertmask(_invert?0xff:0), - framebuf(new u8[maxframesize]), - debug(false) + int _maxframesize, bool _invert) : + minframesize(_minframesize), maxframesize(_maxframesize), invertmask( + _invert ? 0xff : 0), framebuf(new u8[maxframesize]), debug( + false) { - reset(); + reset(); + } + + void reset() + { + shiftreg = 0; + inframe = false; + } + + void begin_frame() + { + framesize = 0; + crc16 = crc16_init; } - - void reset() { shiftreg=0; inframe=false; } - void begin_frame() { framesize=0; crc16=crc16_init; } - // Decode (*ppin)[count] as MSB-packed HDLC bitstream. // Return pointer to buffer[*pdatasize], or NULL if no valid frame. // Return number of discarded bytes in *discarded. // Return number of checksum errors in *fcs_errors. // *ppin will have increased by at least 1 (unless count==0). - u8 *decode(u8 **ppin, int count, - int *pdatasize, int *hdlc_errors, int *fcs_errors) { - *hdlc_errors = 0; - *fcs_errors = 0; - *pdatasize = -1; - u8 *pin=*ppin, *pend=pin+count; - for ( ; pin>1) | bit_in; - if ( ! inframe ) { - if ( shiftreg == 0x7e ) { // HDLC flag 01111110 - inframe = true; - nbits_out = 0; - begin_frame(); - } - } else { - if ( (shiftreg&0xfe) == 0x7c ) { // 0111110x HDLC stuffing - // Unstuff this 0 - } else if ( shiftreg == 0x7e ) { // 01111110 HDLC flag - if ( nbits_out != 7 ) { - // Not at byte boundary - if ( debug ) fprintf(stderr, "^"); - ++*hdlc_errors; - } else { - // Checksum - crc16 ^= 0xffff; - if ( framesize<2 || framesize= minframesize ) ++*fcs_errors; - } else { - if ( debug ) fprintf(stderr, "_"); - // This will trigger output, but we finish the byte first. - *pdatasize = framesize-2; - } - } - nbits_out = 0; - begin_frame(); - // Keep processing up to 7 remaining bits from byte_in. - // Special cases 0111111 and 1111111 cannot affect *pdatasize. - } else if ( shiftreg == 0xfe ) { // 11111110 HDLC invalid - if ( framesize ) { - if ( debug ) fprintf(stderr, "^"); - ++*hdlc_errors; - } - inframe = false; - } else { // Data bit - byte_out = (byte_out>>1) | bit_in; // HDLC is LSB first - ++nbits_out; - if ( nbits_out == 8 ) { - if ( framesize < maxframesize ) { - framebuf[framesize++] = byte_out; - crc16_byte(byte_out); - } - nbits_out = 0; - } - } - } // inframe - } // bits - if ( *pdatasize != -1 ) { - // Found a complete frame - *ppin = pin+1; - return framebuf; - } - } - *ppin = pin; - return NULL; + u8 *decode(u8 **ppin, int count, int *pdatasize, int *hdlc_errors, + int *fcs_errors) + { + *hdlc_errors = 0; + *fcs_errors = 0; + *pdatasize = -1; + u8 *pin = *ppin, *pend = pin + count; + for (; pin < pend; ++pin) + { + u8 byte_in = (*pin) ^ invertmask; + for (int bits = 8; bits--; byte_in <<= 1) + { + u8 bit_in = byte_in & 128; + shiftreg = (shiftreg >> 1) | bit_in; + if (!inframe) + { + if (shiftreg == 0x7e) + { // HDLC flag 01111110 + inframe = true; + nbits_out = 0; + begin_frame(); + } + } + else + { + if ((shiftreg & 0xfe) == 0x7c) + { // 0111110x HDLC stuffing + // Unstuff this 0 + } + else if (shiftreg == 0x7e) + { // 01111110 HDLC flag + if (nbits_out != 7) + { + // Not at byte boundary + if (debug) + fprintf(stderr, "^"); + ++*hdlc_errors; + } + else + { + // Checksum + crc16 ^= 0xffff; + if (framesize < 2 || framesize < minframesize + || crc16 != crc16_check) + { + if (debug) + fprintf(stderr, "!"); + ++*hdlc_errors; + // Do not report random noise as FCS errors + if (framesize >= minframesize) + ++*fcs_errors; + } + else + { + if (debug) + fprintf(stderr, "_"); + // This will trigger output, but we finish the byte first. + *pdatasize = framesize - 2; + } + } + nbits_out = 0; + begin_frame(); + // Keep processing up to 7 remaining bits from byte_in. + // Special cases 0111111 and 1111111 cannot affect *pdatasize. + } + else if (shiftreg == 0xfe) + { // 11111110 HDLC invalid + if (framesize) + { + if (debug) + fprintf(stderr, "^"); + ++*hdlc_errors; + } + inframe = false; + } + else + { // Data bit + byte_out = (byte_out >> 1) | bit_in; // HDLC is LSB first + ++nbits_out; + if (nbits_out == 8) + { + if (framesize < maxframesize) + { + framebuf[framesize++] = byte_out; + crc16_byte(byte_out); + } + nbits_out = 0; + } + } + } // inframe + } // bits + if (*pdatasize != -1) + { + // Found a complete frame + *ppin = pin + 1; + return framebuf; + } + } + *ppin = pin; + return NULL; } - private: +private: // Config int minframesize, maxframesize; u8 invertmask; @@ -119,151 +156,170 @@ namespace leansdr { static const u16 crc16_init = 0xffff; static const u16 crc16_poly = 0x8408; // 0x1021 MSB-first static const u16 crc16_check = 0x0f47; - void crc16_byte(u8 data) { - crc16 ^= data; - for ( int bit=8; bit--; ) - crc16 = (crc16&1) ? (crc16>>1)^crc16_poly : (crc16>>1); - } - - public: - bool debug; - }; // hdlc_dec - - - // HDLC synchronizer with polarity detection - - struct hdlc_sync : runnable { - hdlc_sync(scheduler *sch, - pipebuf &_in, // Packed bits - pipebuf &_out, // Bytes - int _minframesize, // Including CRC, excluding HDLC flags. - int _maxframesize, - // Status - pipebuf *_lock_out=NULL, - pipebuf *_framecount_out=NULL, - pipebuf *_fcserrcount_out=NULL, - pipebuf *_hdlcbytecount_out=NULL, - pipebuf *_databytecount_out=NULL) - : runnable(sch, "hdlc_sync"), - minframesize(_minframesize), - maxframesize(_maxframesize), - chunk_size(maxframesize+2), - in(_in), out(_out, _maxframesize+chunk_size), - lock_out(opt_writer(_lock_out)), - framecount_out(opt_writer(_framecount_out)), - fcserrcount_out(opt_writer(_fcserrcount_out)), - hdlcbytecount_out(opt_writer(_hdlcbytecount_out)), - databytecount_out(opt_writer(_databytecount_out)), - cur_sync(0), resync_phase(0), - lock_state(false), - resync_period(32), - header16(false) + void crc16_byte(u8 data) { - for ( int s=0; sdebug = sch->debug; - errslot = 0; + crc16 ^= data; + for (int bit = 8; bit--;) + crc16 = (crc16 & 1) ? (crc16 >> 1) ^ crc16_poly : (crc16 >> 1); } - void run() { - if ( ! opt_writable(lock_out) || - ! opt_writable(framecount_out) || - ! opt_writable(fcserrcount_out) || - ! opt_writable(hdlcbytecount_out) || - ! opt_writable(databytecount_out) ) return; +public: + bool debug; +}; +// hdlc_dec - bool previous_lock_state = lock_state; - int fcserrcount=0, framecount=0; - int hdlcbytecount=0, databytecount=0; +// HDLC synchronizer with polarity detection - // Note: hdlc_dec may already hold one frame ready for output. - while ( in.readable() >= chunk_size && - out.writable() >= maxframesize+chunk_size ) { - if ( ! resync_phase ) { - // Once every resync_phase, try all decoders - for ( int s=0; sreset(); - syncs[s].errhist[errslot] = 0; - for ( u8 *pin=in.rd(), *pend=pin+chunk_size; pindecode(&pin, pend-pin, &datasize, - &hdlc_errors, &fcs_errors); - syncs[s].errhist[errslot] += hdlc_errors; - if ( s == cur_sync ) { - if ( f ) { - lock_state = true; - output_frame(f, datasize); - databytecount += datasize; - ++framecount; - } - fcserrcount += fcs_errors; - framecount += fcs_errors; - } - } - } - errslot = (errslot+1) % NERRHIST; - // Switch to another sync option ? - // Compare total error counts over about NERRHIST frames. - int total_errors[NSYNCS]; - for ( int s=0; sdebug ) fprintf(stderr, "[%d:%d->%d:%d]", - cur_sync, total_errors[cur_sync], - best, total_errors[best]); - // No verbose messages on candidate syncs - syncs[cur_sync].dec->debug = false; - cur_sync = best; - syncs[cur_sync].dec->debug = sch->debug; - } - } else { - // Use only the currently selected decoder - for ( u8 *pin=in.rd(), *pend=pin+chunk_size; pindecode(&pin, pend-pin, &datasize, - &hdlc_errors, &fcs_errors); - if ( f ) { - lock_state = true; - output_frame(f, datasize); - databytecount += datasize; - ++framecount; - } - fcserrcount += fcs_errors; - framecount += fcs_errors; - } - } // resync_phase - in.read(chunk_size); - hdlcbytecount += chunk_size; - if ( ++resync_phase >= resync_period ) resync_phase = 0; - } // Work to do - - if ( lock_state != previous_lock_state ) - opt_write(lock_out, lock_state?1:0); - opt_write(framecount_out, framecount); - opt_write(fcserrcount_out, fcserrcount); - opt_write(hdlcbytecount_out, hdlcbytecount); - opt_write(databytecount_out, databytecount); +struct hdlc_sync: runnable +{ + hdlc_sync(scheduler *sch, + pipebuf &_in, // Packed bits + pipebuf &_out, // Bytes + int _minframesize, // Including CRC, excluding HDLC flags. + int _maxframesize, + // Status + pipebuf *_lock_out = NULL, + pipebuf *_framecount_out = NULL, + pipebuf *_fcserrcount_out = NULL, + pipebuf *_hdlcbytecount_out = NULL, + pipebuf *_databytecount_out = NULL) : + runnable(sch, "hdlc_sync"), minframesize(_minframesize), maxframesize( + _maxframesize), chunk_size(maxframesize + 2), in(_in), out( + _out, _maxframesize + chunk_size), lock_out( + opt_writer(_lock_out)), framecount_out( + opt_writer(_framecount_out)), fcserrcount_out( + opt_writer(_fcserrcount_out)), hdlcbytecount_out( + opt_writer(_hdlcbytecount_out)), databytecount_out( + opt_writer(_databytecount_out)), cur_sync(0), resync_phase( + 0), lock_state(false), resync_period(32), header16(false) + { + for (int s = 0; s < NSYNCS; ++s) + { + syncs[s].dec = new hdlc_dec(minframesize, maxframesize, s != 0); + for (int h = 0; h < NERRHIST; ++h) + syncs[s].errhist[h] = 0; + } + syncs[cur_sync].dec->debug = sch->debug; + errslot = 0; } - private: - void output_frame(u8 *f, int size) { - if ( header16 ) { - // Removed 16-bit CRC, add 16-bit prefix -> Still <= maxframesize. - out.write(size >> 8); - out.write(size & 255); - } - memcpy(out.wr(), f, size); - out.written(size); - opt_write(framecount_out, 1); + void run() + { + if (!opt_writable(lock_out) || !opt_writable(framecount_out) + || !opt_writable(fcserrcount_out) + || !opt_writable(hdlcbytecount_out) + || !opt_writable(databytecount_out)) + return; + + bool previous_lock_state = lock_state; + int fcserrcount = 0, framecount = 0; + int hdlcbytecount = 0, databytecount = 0; + + // Note: hdlc_dec may already hold one frame ready for output. + while ((long) in.readable() >= chunk_size + && (long) out.writable() >= maxframesize + chunk_size) + { + if (!resync_phase) + { + // Once every resync_phase, try all decoders + for (int s = 0; s < NSYNCS; ++s) + { + if (s != cur_sync) + syncs[s].dec->reset(); + syncs[s].errhist[errslot] = 0; + for (u8 *pin = in.rd(), *pend = pin + chunk_size; + pin < pend;) + { + int datasize, hdlc_errors, fcs_errors; + u8 *f = syncs[s].dec->decode(&pin, pend - pin, + &datasize, &hdlc_errors, &fcs_errors); + syncs[s].errhist[errslot] += hdlc_errors; + if (s == cur_sync) + { + if (f) + { + lock_state = true; + output_frame(f, datasize); + databytecount += datasize; + ++framecount; + } + fcserrcount += fcs_errors; + framecount += fcs_errors; + } + } + } + errslot = (errslot + 1) % NERRHIST; + // Switch to another sync option ? + // Compare total error counts over about NERRHIST frames. + int total_errors[NSYNCS]; + for (int s = 0; s < NSYNCS; ++s) + { + total_errors[s] = 0; + for (int h = 0; h < NERRHIST; ++h) + total_errors[s] += syncs[s].errhist[h]; + } + int best = cur_sync; + for (int s = 0; s < NSYNCS; ++s) + if (total_errors[s] < total_errors[best]) + best = s; + if (best != cur_sync) + { + lock_state = false; + if (sch->debug) + fprintf(stderr, "[%d:%d->%d:%d]", cur_sync, + total_errors[cur_sync], best, + total_errors[best]); + // No verbose messages on candidate syncs + syncs[cur_sync].dec->debug = false; + cur_sync = best; + syncs[cur_sync].dec->debug = sch->debug; + } + } + else + { + // Use only the currently selected decoder + for (u8 *pin = in.rd(), *pend = pin + chunk_size; pin < pend;) + { + int datasize, hdlc_errors, fcs_errors; + u8 *f = syncs[cur_sync].dec->decode(&pin, pend - pin, + &datasize, &hdlc_errors, &fcs_errors); + if (f) + { + lock_state = true; + output_frame(f, datasize); + databytecount += datasize; + ++framecount; + } + fcserrcount += fcs_errors; + framecount += fcs_errors; + } + } // resync_phase + in.read(chunk_size); + hdlcbytecount += chunk_size; + if (++resync_phase >= resync_period) + resync_phase = 0; + } // Work to do + + if (lock_state != previous_lock_state) + opt_write(lock_out, lock_state ? 1 : 0); + opt_write(framecount_out, framecount); + opt_write(fcserrcount_out, fcserrcount); + opt_write(hdlcbytecount_out, hdlcbytecount); + opt_write(databytecount_out, databytecount); + } + +private: + void output_frame(u8 *f, int size) + { + if (header16) + { + // Removed 16-bit CRC, add 16-bit prefix -> Still <= maxframesize. + out.write(size >> 8); + out.write(size & 255); + } + memcpy(out.wr(), f, size); + out.written(size); + opt_write(framecount_out, 1); } int minframesize, maxframesize; @@ -275,19 +331,21 @@ namespace leansdr { pipewriter *hdlcbytecount_out, *databytecount_out; static const int NSYNCS = 2; // Two possible polarities static const int NERRHIST = 2; // Compare error counts over two frames - struct { - hdlc_dec *dec; - int errhist[NERRHIST]; + struct + { + hdlc_dec *dec; + int errhist[NERRHIST]; } syncs[NSYNCS]; int errslot; int cur_sync; int resync_phase; bool lock_state; - public: +public: int resync_period; bool header16; // Output length prefix - }; // hdlc_sync +}; +// hdlc_sync -} // namespace +}// namespace #endif // LEANSDR_HDLC_H diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h index fbe0161c7..781daa140 100644 --- a/plugins/channelrx/demoddatv/leansdr/sdr.h +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -4,684 +4,838 @@ #include "leansdr/math.h" #include "leansdr/dsp.h" -namespace leansdr { +namespace leansdr +{ - // Abbreviations for floating-point types +// Abbreviations for floating-point types - typedef float f32; +typedef float f32; - typedef complex cu8; - typedef complex cs8; - typedef complex cu16; - typedef complex cs16; - typedef complex cf32; +typedef complex cu8; +typedef complex cs8; +typedef complex cu16; +typedef complex cs16; +typedef complex cf32; +////////////////////////////////////////////////////////////////////// +// SDR blocks +////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - // SDR blocks - ////////////////////////////////////////////////////////////////////// - - // AUTO-NOTCH FILTER +// AUTO-NOTCH FILTER - // Periodically detects the [n__slots] strongest peaks with a FFT, - // removes them with a first-order filter. +// Periodically detects the [n__slots] strongest peaks with a FFT, +// removes them with a first-order filter. - - - template - struct auto_notch : runnable { +template +struct auto_notch: runnable +{ int decimation; float k; - auto_notch(scheduler *sch, pipebuf< complex > &_in, - pipebuf< complex > &_out, int _n__slots, - T _agc_rms_setpoint) - : runnable(sch, "auto_notch"), - decimation(1024*4096), k(0.002), // k(0.01) - fft(4096), - in(_in), out(_out,fft.n), - n__slots(_n__slots), __slots(new slot[n__slots]), - phase(0), gain(1), agc_rms_setpoint(_agc_rms_setpoint) { - for ( int s=0; s[fft.n]; - } + + auto_notch(scheduler *sch, pipebuf > &_in, pipebuf > &_out, int _n__slots, T _agc_rms_setpoint) : + runnable(sch, "auto_notch"), + decimation(1024 * 4096), + k(0.002), // k(0.01) + fft(4096), + in(_in), + out(_out, fft.n), + n__slots(_n__slots), + __slots(new slot[n__slots]), + phase(0), + gain(1), + agc_rms_setpoint(_agc_rms_setpoint) + { + for (int s = 0; s < n__slots; ++s) + { + __slots[s].i = -1; + __slots[s].expj = new complex [fft.n]; + } } - void run() { - while ( in.readable()>=fft.n && out.writable()>=fft.n ) { - phase += fft.n; - if ( phase >= decimation ) { - phase -= decimation; - detect(); - } - process(); - in.read(fft.n); - out.written(fft.n); - } + + void run() + { + while (in.readable() >= fft.n && out.writable() >= fft.n) + { + phase += fft.n; + if (phase >= decimation) + { + phase -= decimation; + detect(); + } + process(); + in.read(fft.n); + out.written(fft.n); + } } - void detect() { - complex *pin = in.rd(); - complex data[fft.n]; - float m0=0, m2=0; - for ( int i=0; i m0 ) m0 = gen_abs(pin[i].re); - if ( gen_abs(pin[i].im) > m0 ) m0 = gen_abs(pin[i].im); - } - if ( agc_rms_setpoint && m2 ) { - float rms = gen_sqrt(m2/fft.n); - if ( sch->debug ) fprintf(stderr, "(pow %f max %f)", rms, m0); - float new_gain = agc_rms_setpoint / rms; - gain = gain*0.9 + new_gain*0.1; - } - fft.inplace(data, true); - float amp[fft.n]; - for ( int i=0; i amp[iamax] ) iamax=i; - if ( iamax != s->i ) { - if ( sch->debug ) - fprintf(stderr, "%s: slot %d new peak %d -> %d\n", - name, (int)(s-__slots), s->i, iamax); - s->i = iamax; - s->estim.re = 0; - s->estim.im = 0; - s->estt = 0; - for ( int i=0; ii * i / fft.n; - s->expj[i].re = cosf(a); - s->expj[i].im = sinf(a); - } - } - amp[iamax] = 0; - if ( iamax-1 >= 0 ) amp[iamax-1] = 0; - if ( iamax+1 < fft.n ) amp[iamax+1] = 0; - } + + void detect() + { + complex *pin = in.rd(); + complex data[fft.n]; + float m0 = 0, m2 = 0; + + for (unsigned int i = 0; i < fft.n; ++i) + { + data[i].re = pin[i].re; + data[i].im = pin[i].im; + m2 += (float) pin[i].re * pin[i].re + (float) pin[i].im * pin[i].im; + if (gen_abs(pin[i].re) > m0) + m0 = gen_abs(pin[i].re); + if (gen_abs(pin[i].im) > m0) + m0 = gen_abs(pin[i].im); + } + + if (agc_rms_setpoint && m2) + { + float rms = gen_sqrt(m2 / fft.n); + if (sch->debug) + fprintf(stderr, "(pow %f max %f)", rms, m0); + float new_gain = agc_rms_setpoint / rms; + gain = gain * 0.9 + new_gain * 0.1; + } + + fft.inplace(data, true); + float amp[fft.n]; + + for (unsigned int i = 0; i < fft.n; ++i) { + amp[i] = hypotf(data[i].re, data[i].im); + } + + for (slot *s = __slots; s < __slots + n__slots; ++s) + { + int iamax = 0; + for (unsigned int i = 0; i < fft.n; ++i) + { + if (amp[i] > amp[iamax]) { + iamax = i; + } + } + if (iamax != s->i) + { + if (sch->debug) + fprintf(stderr, "%s: slot %d new peak %d -> %d\n", name, + (int) (s - __slots), s->i, iamax); + s->i = iamax; + s->estim.re = 0; + s->estim.im = 0; + s->estt = 0; + for (unsigned int i = 0; i < fft.n; ++i) + { + float a = 2 * M_PI * s->i * i / fft.n; + s->expj[i].re = cosf(a); + s->expj[i].im = sinf(a); + } + } + amp[iamax] = 0; + if (iamax - 1 >= 0) { + amp[iamax - 1] = 0; + } + if (iamax + 1 < (int) fft.n) { + amp[iamax + 1] = 0; + } + } } - void process() { - complex *pin=in.rd(), *pend=pin+fft.n, *pout=out.wr(); - for ( slot *s=__slots; s<__slots+n__slots; ++s ) s->ej = s->expj; - for ( ; pin out = *pin; - // TODO Optimize for n__slots==1 ? - for ( slot *s=__slots; s<__slots+n__slots; ++s->ej,++s ) { - complex bb(pin->re*s->ej->re + pin->im*s->ej->im, - -pin->re*s->ej->im + pin->im*s->ej->re); - s->estim.re = bb.re*k + s->estim.re*(1-k); - s->estim.im = bb.im*k + s->estim.im*(1-k); - complex sub(s->estim.re*s->ej->re - s->estim.im*s->ej->im, - s->estim.re*s->ej->im + s->estim.im*s->ej->re); - out.re -= sub.re; - out.im -= sub.im; - } - pout->re = gain * out.re; - pout->im = gain * out.im; - } + + void process() + { + complex *pin = in.rd(), *pend = pin + fft.n, *pout = out.wr(); + for (slot *s = __slots; s < __slots + n__slots; ++s) + s->ej = s->expj; + for (; pin < pend; ++pin, ++pout) + { + complex out = *pin; + // TODO Optimize for n__slots==1 ? + for (slot *s = __slots; s < __slots + n__slots; ++s->ej, ++s) + { + complex bb(pin->re * s->ej->re + pin->im * s->ej->im, + -pin->re * s->ej->im + pin->im * s->ej->re); + s->estim.re = bb.re * k + s->estim.re * (1 - k); + s->estim.im = bb.im * k + s->estim.im * (1 - k); + complex sub( + s->estim.re * s->ej->re - s->estim.im * s->ej->im, + s->estim.re * s->ej->im + s->estim.im * s->ej->re); + out.re -= sub.re; + out.im -= sub.im; + } + pout->re = gain * out.re; + pout->im = gain * out.im; + } } - - private: + +private: cfft_engine fft; - pipereader< complex > in; - pipewriter< complex > out; + pipereader > in; + pipewriter > out; int n__slots; - struct slot { - int i; - complex estim; - complex *expj, *ej; - int estt; - } *__slots; + struct slot + { + int i; + complex estim; + complex *expj, *ej; + int estt; + }*__slots; + int phase; float gain; T agc_rms_setpoint; - }; +}; +// SIGNAL STRENGTH ESTIMATOR - // SIGNAL STRENGTH ESTIMATOR +// Outputs RMS values. - // Outputs RMS values. - - template - struct ss_estimator : runnable { +template +struct ss_estimator: runnable +{ unsigned long window_size; // Samples per estimation unsigned long decimation; // Output rate - ss_estimator(scheduler *sch, pipebuf< complex > &_in, pipebuf &_out) - : runnable(sch, "SS estimator"), - window_size(1024), decimation(1024), - in(_in), out(_out), - phase(0) { + + ss_estimator(scheduler *sch, pipebuf > &_in, pipebuf &_out) : + runnable(sch, "SS estimator"), + window_size(1024), + decimation(1024), + in(_in), + out(_out), + phase(0) + { } - void run() { - while ( in.readable()>=window_size && out.writable()>=1 ) { - phase += window_size; - if ( phase >= decimation ) { - phase -= decimation; - complex *p=in.rd(), *pend=p+window_size; - float s = 0; - for ( ; pre*p->re + (float)p->im*p->im; - out.write(sqrtf(s/window_size)); - } - in.read(window_size); - } + + void run() + { + while (in.readable() >= window_size && out.writable() >= 1) + { + phase += window_size; + if (phase >= decimation) + { + phase -= decimation; + complex *p = in.rd(), *pend = p + window_size; + float s = 0; + for (; p < pend; ++p) + s += (float) p->re * p->re + (float) p->im * p->im; + out.write(sqrtf(s / window_size)); + } + in.read(window_size); + } } - private: - pipereader< complex > in; + +private: + pipereader > in; pipewriter out; unsigned long phase; - }; +}; - template - struct ss_amp_estimator : runnable { +template +struct ss_amp_estimator: runnable +{ unsigned long window_size; // Samples per estimation unsigned long decimation; // Output rate - ss_amp_estimator(scheduler *sch, pipebuf< complex > &_in, - pipebuf &_out_ss, - pipebuf &_out_ampmin, pipebuf &_out_ampmax) - : runnable(sch, "SS estimator"), - window_size(1024), decimation(1024), - in(_in), out_ss(_out_ss), - out_ampmin(_out_ampmin), out_ampmax(_out_ampmax), - phase(0) { + + ss_amp_estimator(scheduler *sch, pipebuf > &_in, pipebuf &_out_ss, pipebuf &_out_ampmin, pipebuf &_out_ampmax) : + runnable(sch, "SS estimator"), + window_size(1024), + decimation(1024), + in(_in), + out_ss(_out_ss), + out_ampmin(_out_ampmin), + out_ampmax(_out_ampmax), + phase(0) + { } - void run() { - while ( in.readable() >= window_size && - out_ss.writable() >= 1 && - out_ampmin.writable() >= 1 && - out_ampmax.writable() >= 1 ) { - phase += window_size; - if ( phase >= decimation ) { - phase -= decimation; - complex *p=in.rd(), *pend=p+window_size; - float s2 = 0; - float amin=1e38, amax=0; - for ( ; pre*p->re + (float)p->im*p->im; - s2 += mag2; - float mag = sqrtf(mag2); - if ( mag < amin ) amin = mag; - if ( mag > amax ) amax = mag; - } - out_ss.write(sqrtf(s2/window_size)); - out_ampmin.write(amin); - out_ampmax.write(amax); - } - in.read(window_size); - } + + void run() + { + while (in.readable() >= window_size && out_ss.writable() >= 1 + && out_ampmin.writable() >= 1 && out_ampmax.writable() >= 1) + { + phase += window_size; + if (phase >= decimation) + { + phase -= decimation; + complex *p = in.rd(), *pend = p + window_size; + float s2 = 0; + float amin = 1e38, amax = 0; + for (; p < pend; ++p) + { + float mag2 = (float) p->re * p->re + (float) p->im * p->im; + s2 += mag2; + float mag = sqrtf(mag2); + if (mag < amin) + amin = mag; + if (mag > amax) + amax = mag; + } + out_ss.write(sqrtf(s2 / window_size)); + out_ampmin.write(amin); + out_ampmax.write(amax); + } + in.read(window_size); + } } - private: - pipereader< complex > in; + +private: + pipereader > in; pipewriter out_ss, out_ampmin, out_ampmax; unsigned long phase; - }; - - // AGC +}; - template - struct simple_agc : runnable { +// AGC + +template +struct simple_agc: runnable +{ float out_rms; // Desired RMS output power float bw; // Bandwidth float estimated; // Input power - simple_agc(scheduler *sch, - pipebuf< complex > &_in, - pipebuf< complex > &_out) - : runnable(sch, "AGC"), - out_rms(1), bw(0.001), estimated(0), - in(_in), out(_out) { + + simple_agc(scheduler *sch, pipebuf > &_in, pipebuf > &_out) : + runnable(sch, "AGC"), + out_rms(1), + bw(0.001), + estimated(0), + in(_in), + out(_out) + { } - private: - pipereader< complex > in; - pipewriter< complex > out; + +private: + pipereader > in; + pipewriter > out; static const int chunk_size = 128; - void run() { - while ( in.readable() >= chunk_size && - out.writable() >= chunk_size ) { - complex *pin=in.rd(), *pend=pin+chunk_size; - float amp2 = 0; - for ( ; pinre*pin->re + pin->im*pin->im; - amp2 /= chunk_size; - if ( ! estimated ) estimated = amp2; - estimated = estimated*(1-bw) + amp2*bw; - float gain = estimated ? out_rms / sqrtf(estimated) : 0; - pin = in.rd(); - complex *pout = out.wr(); - float bwcomp = 1 - bw; - for ( ; pinre = pin->re * gain; - pout->im = pin->im * gain; - } - in.read(chunk_size); - out.written(chunk_size); - } + + void run() + { + while (in.readable() >= chunk_size && out.writable() >= chunk_size) + { + complex *pin = in.rd(), *pend = pin + chunk_size; + float amp2 = 0; + for (; pin < pend; ++pin) + amp2 += pin->re * pin->re + pin->im * pin->im; + amp2 /= chunk_size; + if (!estimated) + estimated = amp2; + estimated = estimated * (1 - bw) + amp2 * bw; + float gain = estimated ? out_rms / sqrtf(estimated) : 0; + pin = in.rd(); + complex *pout = out.wr(); + float bwcomp = 1 - bw; + for (; pin < pend; ++pin, ++pout) + { + pout->re = pin->re * gain; + pout->im = pin->im * gain; + } + in.read(chunk_size); + out.written(chunk_size); + } } - }; // simple_agc +}; +// simple_agc +typedef uint16_t u_angle; // [0,2PI[ in 65536 steps +typedef int16_t s_angle; // [-PI,PI[ in 65536 steps - typedef uint16_t u_angle; // [0,2PI[ in 65536 steps - typedef int16_t s_angle; // [-PI,PI[ in 65536 steps +// GENERIC CONSTELLATION DECODING BY LOOK-UP TABLE. +// Metrics and phase errors are pre-computed on a RxR grid. +// R must be a power of 2. +// Up to 256 symbols. - // GENERIC CONSTELLATION DECODING BY LOOK-UP TABLE. - - // Metrics and phase errors are pre-computed on a RxR grid. - // R must be a power of 2. - // Up to 256 symbols. - - struct softsymbol { +struct softsymbol +{ int16_t cost; // For Viterbi with TBM=int16_t uint8_t symbol; - }; +}; - // Target RMS amplitude for AGC - //const float cstln_amp = 73; // Best for 32APSK 9/10 - //const float cstln_amp = 90; // Best for QPSK - //const float cstln_amp = 64; // Best for BPSK - //const float cstln_amp = 75; // Best for BPSK at 45° - const float cstln_amp = 75; // Trade-off +// Target RMS amplitude for AGC +//const float cstln_amp = 73; // Best for 32APSK 9/10 +//const float cstln_amp = 90; // Best for QPSK +//const float cstln_amp = 64; // Best for BPSK +//const float cstln_amp = 75; // Best for BPSK at 45° +const float cstln_amp = 75; // Trade-off - template - struct cstln_lut { +template +struct cstln_lut +{ complex *symbols; int nsymbols; int nrotations; - enum predef { - BPSK, // DVB-S2 (and DVB-S variant) - QPSK, // DVB-S - PSK8, APSK16, APSK32, // DVB-S2 - APSK64E, // DVB-S2X - QAM16, QAM64, QAM256 // For experimentation only + enum predef + { + BPSK, // DVB-S2 (and DVB-S variant) + QPSK, // DVB-S + PSK8, + APSK16, + APSK32, // DVB-S2 + APSK64E, // DVB-S2X + QAM16, + QAM64, + QAM256 // For experimentation only }; - cstln_lut(predef type, float gamma1=1, float gamma2=1, float gamma3=1) { - switch ( type ) { - case BPSK: - nrotations = 2; - nsymbols = 2; - symbols = new complex[nsymbols]; + cstln_lut(predef type, float gamma1 = 1, float gamma2 = 1, float gamma3 = 1) + { + switch (type) + { + case BPSK: + nrotations = 2; + nsymbols = 2; + symbols = new complex [nsymbols]; #if 0 // BPSK at 0° - symbols[0] = polar(1, 2, 0); - symbols[1] = polar(1, 2, 1); + symbols[0] = polar(1, 2, 0); + symbols[1] = polar(1, 2, 1); #else // BPSK at 45° - symbols[0] = polar(1, 8, 1); - symbols[1] = polar(1, 8, 5); + symbols[0] = polar(1, 8, 1); + symbols[1] = polar(1, 8, 5); #endif - make_lut_from_symbols(); - break; - case QPSK: - // EN 300 421, section 4.5 Baseband shaping and modulation - // EN 302 307, section 5.4.1 - nrotations = 4; - nsymbols = 4; - symbols = new complex[nsymbols]; - symbols[0] = polar(1, 4, 0.5); - symbols[1] = polar(1, 4, 3.5); - symbols[2] = polar(1, 4, 1.5); - symbols[3] = polar(1, 4, 2.5); - make_lut_from_symbols(); - break; - case PSK8: - // EN 302 307, section 5.4.2 - nrotations = 8; - nsymbols = 8; - symbols = new complex[nsymbols]; - symbols[0] = polar(1, 8, 1); - symbols[1] = polar(1, 8, 0); - symbols[2] = polar(1, 8, 4); - symbols[3] = polar(1, 8, 5); - symbols[4] = polar(1, 8, 2); - symbols[5] = polar(1, 8, 7); - symbols[6] = polar(1, 8, 3); - symbols[7] = polar(1, 8, 6); - make_lut_from_symbols(); - break; - case APSK16: { - // EN 302 307, section 5.4.3 - float r1 = sqrtf(4 / (1+3*gamma1*gamma1)); - float r2 = gamma1 * r1; - nrotations = 4; - nsymbols = 16; - symbols = new complex[nsymbols]; - symbols[0] = polar(r2, 12, 1.5); - symbols[1] = polar(r2, 12, 10.5); - symbols[2] = polar(r2, 12, 4.5); - symbols[3] = polar(r2, 12, 7.5); - symbols[4] = polar(r2, 12, 0.5); - symbols[5] = polar(r2, 12, 11.5); - symbols[6] = polar(r2, 12, 5.5); - symbols[7] = polar(r2, 12, 6.5); - symbols[8] = polar(r2, 12, 2.5); - symbols[9] = polar(r2, 12, 9.5); - symbols[10] = polar(r2, 12, 3.5); - symbols[11] = polar(r2, 12, 8.5); - symbols[12] = polar(r1, 4, 0.5); - symbols[13] = polar(r1, 4, 3.5); - symbols[14] = polar(r1, 4, 1.5); - symbols[15] = polar(r1, 4, 2.5); - make_lut_from_symbols(); - break; - } - case APSK32: { - // EN 302 307, section 5.4.3 - float r1 = sqrtf(8 / (1+3*gamma1*gamma1+4*gamma2*gamma2)); - float r2 = gamma1 * r1; - float r3 = gamma2 * r1; - nrotations = 4; - nsymbols = 32; - symbols = new complex[nsymbols]; - symbols[0] = polar(r2, 12, 1.5); - symbols[1] = polar(r2, 12, 2.5); - symbols[2] = polar(r2, 12, 10.5); - symbols[3] = polar(r2, 12, 9.5); - symbols[4] = polar(r2, 12, 4.5); - symbols[5] = polar(r2, 12, 3.5); - symbols[6] = polar(r2, 12, 7.5); - symbols[7] = polar(r2, 12, 8.5); - symbols[8] = polar(r3, 16, 1 ); - symbols[9] = polar(r3, 16, 3 ); - symbols[10] = polar(r3, 16, 14 ); - symbols[11] = polar(r3, 16, 12 ); - symbols[12] = polar(r3, 16, 6 ); - symbols[13] = polar(r3, 16, 4 ); - symbols[14] = polar(r3, 16, 9 ); - symbols[15] = polar(r3, 16, 11 ); - symbols[16] = polar(r2, 12, 0.5); - symbols[17] = polar(r1, 4, 0.5); - symbols[18] = polar(r2, 12, 11.5); - symbols[19] = polar(r1, 4, 3.5); - symbols[20] = polar(r2, 12, 5.5); - symbols[21] = polar(r1, 4, 1.5); - symbols[22] = polar(r2, 12, 6.5); - symbols[23] = polar(r1, 4, 2.5); - symbols[24] = polar(r3, 16, 0 ); - symbols[25] = polar(r3, 16, 2 ); - symbols[26] = polar(r3, 16, 15 ); - symbols[27] = polar(r3, 16, 13 ); - symbols[28] = polar(r3, 16, 7 ); - symbols[29] = polar(r3, 16, 5 ); - symbols[30] = polar(r3, 16, 8 ); - symbols[31] = polar(r3, 16, 10 ); - make_lut_from_symbols(); - break; - } - case APSK64E: { - // EN 302 307-2, section 5.4.5, Table 13e - float r1 = - sqrtf(64 / (4+12*gamma1*gamma1+20*gamma2*gamma2+28*gamma3*gamma3)); - float r2 = gamma1 * r1; - float r3 = gamma2 * r1; - float r4 = gamma3 * r1; - nrotations = 4; - nsymbols = 64; - symbols = new complex[nsymbols]; - polar2( 0, r4, 1.0/ 4, 7.0/4, 3.0/ 4, 5.0/ 4); - polar2( 4, r4, 13.0/28, 43.0/28, 15.0/28, 41.0/28); - polar2( 8, r4, 1.0/28, 55.0/28, 27.0/28, 29.0/28); - polar2(12, r1, 1.0/ 4, 7.0/ 4, 3.0/ 4, 5.0/ 4); - polar2(16, r4, 9.0/28, 47.0/28, 19.0/28, 37.0/28); - polar2(20, r4, 11.0/28, 45.0/28, 17.0/28, 39.0/28); - polar2(24, r3, 1.0/20, 39.0/20, 19.0/20, 21.0/20); - polar2(28, r2, 1.0/12, 23.0/12, 11.0/12, 13.0/12); - polar2(32, r4, 5.0/28, 51.0/28, 23.0/28, 33.0/28); - polar2(36, r3, 9.0/20, 31.0/20, 11.0/20, 29.0/20); - polar2(40, r4, 3.0/28, 53.0/28, 25.0/28, 31.0/28); - polar2(44, r2, 5.0/12, 19.0/12, 7.0/12, 17.0/12); - polar2(48, r3, 1.0/ 4, 7.0/ 4, 3.0/ 4, 5.0/ 4); - polar2(52, r3, 7.0/20, 33.0/20, 13.0/20, 27.0/20); - polar2(56, r3, 3.0/20, 37.0/20, 17.0/20, 23.0/20); - polar2(60, r2, 1.0/ 4, 7.0/ 4, 3.0/ 4, 5.0/ 4); - make_lut_from_symbols(); - break; - } - case QAM16: - make_qam(16); - break; - case QAM64: - make_qam(64); - break; - case QAM256: - make_qam(256); - break; - default: - fail("Constellation not implemented"); - } + make_lut_from_symbols(); + break; + case QPSK: + // EN 300 421, section 4.5 Baseband shaping and modulation + // EN 302 307, section 5.4.1 + nrotations = 4; + nsymbols = 4; + symbols = new complex [nsymbols]; + symbols[0] = polar(1, 4, 0.5); + symbols[1] = polar(1, 4, 3.5); + symbols[2] = polar(1, 4, 1.5); + symbols[3] = polar(1, 4, 2.5); + make_lut_from_symbols(); + break; + case PSK8: + // EN 302 307, section 5.4.2 + nrotations = 8; + nsymbols = 8; + symbols = new complex [nsymbols]; + symbols[0] = polar(1, 8, 1); + symbols[1] = polar(1, 8, 0); + symbols[2] = polar(1, 8, 4); + symbols[3] = polar(1, 8, 5); + symbols[4] = polar(1, 8, 2); + symbols[5] = polar(1, 8, 7); + symbols[6] = polar(1, 8, 3); + symbols[7] = polar(1, 8, 6); + make_lut_from_symbols(); + break; + case APSK16: + { + // EN 302 307, section 5.4.3 + float r1 = sqrtf(4 / (1 + 3 * gamma1 * gamma1)); + float r2 = gamma1 * r1; + nrotations = 4; + nsymbols = 16; + symbols = new complex [nsymbols]; + symbols[0] = polar(r2, 12, 1.5); + symbols[1] = polar(r2, 12, 10.5); + symbols[2] = polar(r2, 12, 4.5); + symbols[3] = polar(r2, 12, 7.5); + symbols[4] = polar(r2, 12, 0.5); + symbols[5] = polar(r2, 12, 11.5); + symbols[6] = polar(r2, 12, 5.5); + symbols[7] = polar(r2, 12, 6.5); + symbols[8] = polar(r2, 12, 2.5); + symbols[9] = polar(r2, 12, 9.5); + symbols[10] = polar(r2, 12, 3.5); + symbols[11] = polar(r2, 12, 8.5); + symbols[12] = polar(r1, 4, 0.5); + symbols[13] = polar(r1, 4, 3.5); + symbols[14] = polar(r1, 4, 1.5); + symbols[15] = polar(r1, 4, 2.5); + make_lut_from_symbols(); + break; + } + case APSK32: + { + // EN 302 307, section 5.4.3 + float r1 = sqrtf( + 8 / (1 + 3 * gamma1 * gamma1 + 4 * gamma2 * gamma2)); + float r2 = gamma1 * r1; + float r3 = gamma2 * r1; + nrotations = 4; + nsymbols = 32; + symbols = new complex [nsymbols]; + symbols[0] = polar(r2, 12, 1.5); + symbols[1] = polar(r2, 12, 2.5); + symbols[2] = polar(r2, 12, 10.5); + symbols[3] = polar(r2, 12, 9.5); + symbols[4] = polar(r2, 12, 4.5); + symbols[5] = polar(r2, 12, 3.5); + symbols[6] = polar(r2, 12, 7.5); + symbols[7] = polar(r2, 12, 8.5); + symbols[8] = polar(r3, 16, 1); + symbols[9] = polar(r3, 16, 3); + symbols[10] = polar(r3, 16, 14); + symbols[11] = polar(r3, 16, 12); + symbols[12] = polar(r3, 16, 6); + symbols[13] = polar(r3, 16, 4); + symbols[14] = polar(r3, 16, 9); + symbols[15] = polar(r3, 16, 11); + symbols[16] = polar(r2, 12, 0.5); + symbols[17] = polar(r1, 4, 0.5); + symbols[18] = polar(r2, 12, 11.5); + symbols[19] = polar(r1, 4, 3.5); + symbols[20] = polar(r2, 12, 5.5); + symbols[21] = polar(r1, 4, 1.5); + symbols[22] = polar(r2, 12, 6.5); + symbols[23] = polar(r1, 4, 2.5); + symbols[24] = polar(r3, 16, 0); + symbols[25] = polar(r3, 16, 2); + symbols[26] = polar(r3, 16, 15); + symbols[27] = polar(r3, 16, 13); + symbols[28] = polar(r3, 16, 7); + symbols[29] = polar(r3, 16, 5); + symbols[30] = polar(r3, 16, 8); + symbols[31] = polar(r3, 16, 10); + make_lut_from_symbols(); + break; + } + case APSK64E: + { + // EN 302 307-2, section 5.4.5, Table 13e + float r1 = sqrtf( + 64 + / (4 + 12 * gamma1 * gamma1 + 20 * gamma2 * gamma2 + + 28 * gamma3 * gamma3)); + float r2 = gamma1 * r1; + float r3 = gamma2 * r1; + float r4 = gamma3 * r1; + nrotations = 4; + nsymbols = 64; + symbols = new complex [nsymbols]; + polar2(0, r4, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); + polar2(4, r4, 13.0 / 28, 43.0 / 28, 15.0 / 28, 41.0 / 28); + polar2(8, r4, 1.0 / 28, 55.0 / 28, 27.0 / 28, 29.0 / 28); + polar2(12, r1, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); + polar2(16, r4, 9.0 / 28, 47.0 / 28, 19.0 / 28, 37.0 / 28); + polar2(20, r4, 11.0 / 28, 45.0 / 28, 17.0 / 28, 39.0 / 28); + polar2(24, r3, 1.0 / 20, 39.0 / 20, 19.0 / 20, 21.0 / 20); + polar2(28, r2, 1.0 / 12, 23.0 / 12, 11.0 / 12, 13.0 / 12); + polar2(32, r4, 5.0 / 28, 51.0 / 28, 23.0 / 28, 33.0 / 28); + polar2(36, r3, 9.0 / 20, 31.0 / 20, 11.0 / 20, 29.0 / 20); + polar2(40, r4, 3.0 / 28, 53.0 / 28, 25.0 / 28, 31.0 / 28); + polar2(44, r2, 5.0 / 12, 19.0 / 12, 7.0 / 12, 17.0 / 12); + polar2(48, r3, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); + polar2(52, r3, 7.0 / 20, 33.0 / 20, 13.0 / 20, 27.0 / 20); + polar2(56, r3, 3.0 / 20, 37.0 / 20, 17.0 / 20, 23.0 / 20); + polar2(60, r2, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); + make_lut_from_symbols(); + break; + } + case QAM16: + make_qam(16); + break; + case QAM64: + make_qam(64); + break; + case QAM256: + make_qam(256); + break; + default: + fail("Constellation not implemented"); + } } - struct result { - struct softsymbol ss; - s_angle phase_error; + struct result + { + struct softsymbol ss; + s_angle phase_error; }; - inline result *lookup(float I, float Q) { - // Handling of overflows beyond the lookup table: - // - For BPSK/QPSK/8PSK we only care about the phase, - // so the following is harmless and improves locking at low SNR. - // - For amplitude modulations this is not appropriate. - // However, if there is enough noise to cause overflow, - // demodulation would probably fail anyway. - // - // Comment-out for better throughput at high SNR. + inline result *lookup(float I, float Q) + { + // Handling of overflows beyond the lookup table: + // - For BPSK/QPSK/8PSK we only care about the phase, + // so the following is harmless and improves locking at low SNR. + // - For amplitude modulations this is not appropriate. + // However, if there is enough noise to cause overflow, + // demodulation would probably fail anyway. + // + // Comment-out for better throughput at high SNR. #if 1 - while ( I<-128 || I>127 || Q<-128 || Q>127 ) { - I *= 0.5; - Q *= 0.5; - } + while (I < -128 || I > 127 || Q < -128 || Q > 127) + { + I *= 0.5; + Q *= 0.5; + } #endif - return &lut[(u8)(s8)I][(u8)(s8)Q]; + return &lut[(u8) (s8) I][(u8) (s8) Q]; } - inline result *lookup(int I, int Q) { - // Ignore wrapping modulo 256 - return &lut[(u8)I][(u8)Q]; + + inline result *lookup(int I, int Q) + { + // Ignore wrapping modulo 256 + return &lut[(u8) I][(u8) Q]; } - private: - complex polar(float r, int n, float i) { - float a = i * 2*M_PI / n; - return complex(r*cosf(a)*cstln_amp, r*sinf(a)*cstln_amp); + +private: + complex polar(float r, int n, float i) + { + float a = i * 2 * M_PI / n; + return complex(r * cosf(a) * cstln_amp, + r * sinf(a) * cstln_amp); } + // Helper function for some constellation tables - void polar2(int i, float r, float a0, float a1, float a2, float a3) { - float a[] = { a0, a1, a2, a3 }; - for ( int j=0; j<4; ++j ) { - float phi = a[j] * M_PI; - symbols[i+j] = complex(r*cosf(phi)*cstln_amp, - r*sinf(phi)*cstln_amp); - } + void polar2(int i, float r, float a0, float a1, float a2, float a3) + { + float a[] = + { a0, a1, a2, a3 }; + for (int j = 0; j < 4; ++j) + { + float phi = a[j] * M_PI; + symbols[i + j] = complex(r * cosf(phi) * cstln_amp, + r * sinf(phi) * cstln_amp); + } } - void make_qam(int n) { - nrotations = 4; - nsymbols = n; - symbols = new complex[nsymbols]; - int m = sqrtl(n); - float scale; - { // Average power in first quadrant with unit grid - int q = m / 2; - float avgpower = 2*(q*0.25+(q-1)*q/2+(q-1)*q*(2*q-1)/6) / q; - scale = 1.0 / sqrtf(avgpower); - } - // Arbitrary mapping - int s = 0; - for ( int x=0; x [nsymbols]; + int m = sqrtl(n); + float scale; + { // Average power in first quadrant with unit grid + int q = m / 2; + float avgpower = 2 + * (q * 0.25 + (q - 1) * q / 2 + + (q - 1) * q * (2 * q - 1) / 6) / q; + scale = 1.0 / sqrtf(avgpower); + } + // Arbitrary mapping + int s = 0; + for (int x = 0; x < m; ++x) + for (int y = 0; y < m; ++y) + { + float I = x - (float) (m - 1) / 2; + float Q = y - (float) (m - 1) / 2; + symbols[s].re = I * scale * cstln_amp; + symbols[s].im = Q * scale * cstln_amp; + ++s; + } + make_lut_from_symbols(); } + result lut[R][R]; - void make_lut_from_symbols() { - for ( int I=-R/2; I Suitable for Viterbi with partial metrics. - uint8_t nearest = 0; - int32_t cost=R*R*2, cost2=R*R*2; - for ( int s=0; s 32767 ) cost = 32767; - if ( cost2 > 32767 ) cost2 = 32767; - pr->ss.cost = cost - cost2; - pr->ss.symbol = nearest; - float ph_symbol = atan2f(symbols[pr->ss.symbol].im, - symbols[pr->ss.symbol].re); - float ph_err = atan2f(Q,I) - ph_symbol; - pr->phase_error = (s32)(ph_err * 65536 / (2*M_PI)); // Mod 65536 - } + + void make_lut_from_symbols() + { + for (int I = -R / 2; I < R / 2; ++I) + for (int Q = -R / 2; Q < R / 2; ++Q) + { + result *pr = &lut[I & (R - 1)][Q & (R - 1)]; + // Simplified metric: + // Distance to nearest minus distance to second-nearest. + // Null at edge of decision regions + // => Suitable for Viterbi with partial metrics. + uint8_t nearest = 0; + int32_t cost = R * R * 2, cost2 = R * R * 2; + for (int s = 0; s < nsymbols; ++s) + { + int32_t d2 = (I - symbols[s].re) * (I - symbols[s].re) + + (Q - symbols[s].im) * (Q - symbols[s].im); + if (d2 < cost) + { + cost2 = cost; + cost = d2; + nearest = s; + } + else if (d2 < cost2) + { + cost2 = d2; + } + } + if (cost > 32767) + cost = 32767; + if (cost2 > 32767) + cost2 = 32767; + pr->ss.cost = cost - cost2; + pr->ss.symbol = nearest; + float ph_symbol = atan2f(symbols[pr->ss.symbol].im, + symbols[pr->ss.symbol].re); + float ph_err = atan2f(Q, I) - ph_symbol; + pr->phase_error = (s32) (ph_err * 65536 / (2 * M_PI)); // Mod 65536 + } } - public: +public: // Convert soft metric to Hamming distance - void harden() { - for ( int i=0; icost < 0 ) ss->cost = -1; - if ( ss->cost > 0 ) ss->cost = 1; - } // for I,Q - } + void harden() + { + for (int i = 0; i < R; ++i) + for (int q = 0; q < R; ++q) + { + softsymbol *ss = &lut[i][q].ss; + if (ss->cost < 0) + ss->cost = -1; + if (ss->cost > 0) + ss->cost = 1; + } // for I,Q + } - }; // cstln_lut +}; +// cstln_lut - static const char *cstln_names[] = { - [cstln_lut<256>::BPSK] = "BPSK", - [cstln_lut<256>::QPSK] = "QPSK", - [cstln_lut<256>::PSK8] = "8PSK", - [cstln_lut<256>::APSK16] = "16APSK", - [cstln_lut<256>::APSK32] = "32APSK", - [cstln_lut<256>::APSK64E] = "64APSKe", - [cstln_lut<256>::QAM16] = "16QAM", - [cstln_lut<256>::QAM64] = "64QAM", - [cstln_lut<256>::QAM256] = "256QAM" - }; +//static const char *cstln_names[] = +//{ [cstln_lut<256>::BPSK] = "BPSK", +// [cstln_lut<256>::QPSK] = "QPSK", +// [cstln_lut<256>::PSK8] = "8PSK", +// [cstln_lut<256>::APSK16] = "16APSK", +// [cstln_lut<256>::APSK32] = "32APSK", +// [cstln_lut<256>::APSK64E] = "64APSKe", +// [cstln_lut<256>::QAM16] = "16QAM", +// [cstln_lut<256>::QAM64] = "64QAM", +// [cstln_lut<256>::QAM256] = "256QAM" +//}; - // SAMPLER INTERFACE FOR CSTLN_RECEIVER - - template - struct sampler_interface { +// SAMPLER INTERFACE FOR CSTLN_RECEIVER + +template +struct sampler_interface +{ virtual complex interp(const complex *pin, float mu, float phase) = 0; - virtual void update_freq(float freqw) { } // 65536 = 1 Hz - virtual int readahead() { return 0; } - }; + virtual void update_freq(float freqw __attribute__((unused))) + { + } // 65536 = 1 Hz - // NEAREST-SAMPLE SAMPLER FOR CSTLN_RECEIVER - // Suitable for bandpass-filtered, oversampled signals only - - template - struct nearest_sampler : sampler_interface { - int readahead() { return 0; } - complex interp(const complex *pin, float mu, float phase) { - return pin[0]*trig.expi(-phase); + virtual int readahead() + { + return 0; } - private: + + virtual ~sampler_interface() + { + } +}; + +// NEAREST-SAMPLE SAMPLER FOR CSTLN_RECEIVER +// Suitable for bandpass-filtered, oversampled signals only + +template +struct nearest_sampler: sampler_interface +{ + int readahead() + { + return 0; + } + + complex interp(const complex *pin, float mu __attribute__((unused)), float phase) + { + return pin[0] * trig.expi(-phase); + } + +private: trig16 trig; - }; // nearest_sampler +}; +// nearest_sampler +// LINEAR SAMPLER FOR CSTLN_RECEIVER - // LINEAR SAMPLER FOR CSTLN_RECEIVER - - template - struct linear_sampler : sampler_interface { - int readahead() { return 1; } - - complex interp(const complex *pin, float mu, float phase) { - // Derotate pin[0] and pin[1] - complex s0 = pin[0]*trig.expi(-phase); - complex s1 = pin[1]*trig.expi(-(phase+freqw)); - // Interpolate linearly - return s0*(1-mu) + s1*mu; +template +struct linear_sampler: sampler_interface +{ + int readahead() + { + return 1; } - void update_freq(float _freqw) { freqw = _freqw; } + complex interp(const complex *pin, float mu, float phase) + { + // Derotate pin[0] and pin[1] + complex s0 = pin[0] * trig.expi(-phase); + complex s1 = pin[1] * trig.expi(-(phase + freqw)); + // Interpolate linearly + return s0 * (1 - mu) + s1 * mu; + } - private: + void update_freq(float _freqw) + { + freqw = _freqw; + } + +private: trig16 trig; float freqw; - }; // linear_sampler +}; +// linear_sampler +// FIR SAMPLER FOR CSTLN_RECEIVER - // FIR SAMPLER FOR CSTLN_RECEIVER - - template - struct fir_sampler : sampler_interface { - fir_sampler(int _ncoeffs, Tc *_coeffs, int _subsampling=1) - : ncoeffs(_ncoeffs), coeffs(_coeffs), subsampling(_subsampling), - shifted_coeffs(new complex[ncoeffs]), - update_freq_phase(0) +template +struct fir_sampler: sampler_interface +{ + fir_sampler(int _ncoeffs, Tc *_coeffs, int _subsampling = 1) : + ncoeffs(_ncoeffs), coeffs(_coeffs), subsampling(_subsampling), shifted_coeffs( + new complex [ncoeffs]), update_freq_phase(0) { } - int readahead() { return ncoeffs-1; } - - complex interp(const complex *pin, float mu, float phase) { - // Apply FIR filter with subsampling - complex acc(0, 0); - complex *pc = shifted_coeffs + (int)((1-mu)*subsampling); - complex *pcend = shifted_coeffs + ncoeffs; - if ( subsampling == 1 ) { - // Special case for heavily oversampled signals, - // where filtering is expensive. - // gcc-4.9.2 can vectorize this form with NEON on ARM. - while ( pc < pcend ) - acc += (*pc++)*(*pin++); - } else { - // Not vectorized because the coefficients are not - // guaranteed to be contiguous in memory. - for ( ; pc interp(const complex *pin, float mu, float phase) + { + // Apply FIR filter with subsampling + complex acc(0, 0); + complex *pc = shifted_coeffs + (int) ((1 - mu) * subsampling); + complex *pcend = shifted_coeffs + ncoeffs; + if (subsampling == 1) + { + // Special case for heavily oversampled signals, + // where filtering is expensive. + // gcc-4.9.2 can vectorize this form with NEON on ARM. + while (pc < pcend) + acc += (*pc++) * (*pin++); + } + else + { + // Not vectorized because the coefficients are not + // guaranteed to be contiguous in memory. + for (; pc < pcend; pc += subsampling, ++pin) + acc += (*pc) * (*pin); + } + // Derotate + return trig.expi(-phase) * acc; } - private: - void do_update_freq(float freqw) { - float f = freqw / subsampling; - for ( int i=0; i - struct cstln_receiver : runnable { +template +struct cstln_receiver: runnable +{ sampler_interface *sampler; cstln_lut<256> *cstln; unsigned long meas_decimation; // Measurement rate @@ -691,226 +845,267 @@ namespace leansdr { bool allow_drift; // Follow carrier beyond safe limits static const unsigned int chunk_size = 128; float kest; - - cstln_receiver(scheduler *sch, - sampler_interface *_sampler, - pipebuf< complex > &_in, - pipebuf &_out, - pipebuf *_freq_out=NULL, - pipebuf *_ss_out=NULL, - pipebuf *_mer_out=NULL, - pipebuf *_cstln_out=NULL) - : runnable(sch, "Constellation receiver"), - sampler(_sampler), - cstln(NULL), - meas_decimation(1048576), - pll_adjustment(1.0), - allow_drift(false), - kest(0.01), - in(_in), out(_out, chunk_size), - est_insp(cstln_amp*cstln_amp), agc_gain(1), - mu(0), phase(0), - est_sp(0), est_ep(0), - meas_count(0) { - set_omega(1); - set_freq(0); - freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; - ss_out = _ss_out ? new pipewriter(*_ss_out) : NULL; - mer_out = _mer_out ? new pipewriter(*_mer_out) : NULL; - cstln_out = _cstln_out ? new pipewriter(*_cstln_out) : NULL; - memset(hist, 0, sizeof(hist)); - } - - void set_omega(float _omega, float tol=10e-6) { - omega = _omega; - min_omega = omega * (1-tol); - max_omega = omega * (1+tol); - update_freq_limits(); - } - - void set_freq(float freq) { - freqw = freq * 65536; - update_freq_limits(); - refresh_freq_tap(); + + cstln_receiver( + scheduler *sch, + sampler_interface *_sampler, + pipebuf > &_in, + pipebuf &_out, + pipebuf *_freq_out = NULL, + pipebuf *_ss_out = NULL, + pipebuf *_mer_out = NULL, + pipebuf *_cstln_out = NULL) : + runnable(sch, "Constellation receiver"), + sampler(_sampler), + cstln(NULL), + meas_decimation(1048576), + pll_adjustment(1.0), + allow_drift(false), + kest(0.01), + in(_in), + out(_out, chunk_size), + est_insp(cstln_amp * cstln_amp), + agc_gain(1), + mu(0), + phase(0), + est_sp(0), + est_ep(0), + meas_count(0) + { + set_omega(1); + set_freq(0); + freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; + ss_out = _ss_out ? new pipewriter(*_ss_out) : NULL; + mer_out = _mer_out ? new pipewriter(*_mer_out) : NULL; + cstln_out = _cstln_out ? new pipewriter(*_cstln_out) : NULL; + memset(hist, 0, sizeof(hist)); } - void set_allow_drift(bool d) { - allow_drift = d; + void set_omega(float _omega, float tol = 10e-6) + { + omega = _omega; + min_omega = omega * (1 - tol); + max_omega = omega * (1 + tol); + update_freq_limits(); } - void update_freq_limits() { - // Prevent PLL from crossing +-SR/n/2 and locking at +-SR/n. - int n = 4; - if ( cstln ) { - switch ( cstln->nsymbols ) { - case 2: n = 2; break; // BPSK - case 4: n = 4; break; // QPSK - case 8: n = 8; break; // 8PSK - case 16: n = 12; break; // 16APSK - case 32: n = 16; break; // 32APSK - default: n = 4; break; - } - } - min_freqw = freqw - 65536/max_omega/n/2; - max_freqw = freqw + 65536/max_omega/n/2; + void set_freq(float freq) + { + freqw = freq * 65536; + update_freq_limits(); + refresh_freq_tap(); } - - void run() { - if ( ! cstln ) fail("constellation not set"); - - // Magic constants that work with the qa recordings. - float freq_alpha = 0.04; - float freq_beta = 0.0012 / omega * pll_adjustment; - float gain_mu = 0.02 / (cstln_amp*cstln_amp) * 2; - int max_meas = chunk_size/meas_decimation + 1; - // Large margin on output_size because mu adjustments - // can lead to more than chunk_size/min_omega symbols. - while ( in.readable() >= chunk_size+sampler->readahead() && - out.writable() >= chunk_size && - ( !freq_out || freq_out ->writable()>=max_meas ) && - ( !ss_out || ss_out ->writable()>=max_meas ) && - ( !mer_out || mer_out ->writable()>=max_meas ) && - ( !cstln_out || cstln_out->writable()>=max_meas ) ) { - - sampler->update_freq(freqw); - - complex *pin=in.rd(), *pin0=pin, *pend=pin+chunk_size; - softsymbol *pout=out.wr(), *pout0=pout; - - // These are scoped outside the loop for SS and MER estimation. - complex sg; // Symbol before AGC; - complex s; // For MER estimation and constellation viewer - complex *cstln_point = NULL; - - while ( pin < pend ) { - // Here mu is the time of the next symbol counted from 0 at pin. - if ( mu < 1 ) { - // Here 0<=mu<1 is the fractional time of the next symbol - // between pin and pin+1. - sg = sampler->interp(pin, mu, phase); - s = sg * agc_gain; - - // Constellation look-up - cstln_lut<256>::result *cr = cstln->lookup(s.re, s.im); - *pout = cr->ss; - ++pout; - - // PLL - phase += cr->phase_error * freq_alpha; - freqw += cr->phase_error * freq_beta; - - // Modified Mueller and Müller - // mu[k]=real((c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) - // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) - // p = received signals - // c = decisions (constellation points) - hist[2] = hist[1]; - hist[1] = hist[0]; - hist[0].p.re = s.re; - hist[0].p.im = s.im; - cstln_point = &cstln->symbols[cr->ss.symbol]; - hist[0].c.re = cstln_point->re; - hist[0].c.im = cstln_point->im; - float muerr = - ( (hist[0].p.re-hist[2].p.re)*hist[1].c.re + - (hist[0].p.im-hist[2].p.im)*hist[1].c.im ) - - ( (hist[0].c.re-hist[2].c.re)*hist[1].p.re + - (hist[0].c.im-hist[2].c.im)*hist[1].p.im ); - float mucorr = muerr * gain_mu; - const float max_mucorr = 0.1; - // TBD Optimize out statically - if ( mucorr < -max_mucorr ) mucorr = -max_mucorr; - if ( mucorr > max_mucorr ) mucorr = max_mucorr; - mu += mucorr; - mu += omega; // Next symbol time; - } // mu<1 - - // Next sample - ++pin; - --mu; - phase += freqw; - } // chunk_size - - in.read(pin-pin0); - out.written(pout-pout0); - - // Normalize phase so that it never exceeds 32 bits. - // Max freqw is 2^31/65536/chunk_size = 256 Hz - // (this may happen with leandvb --drift --decim). - phase = fmodf(phase, 65536); - - if ( cstln_point ) { - - // Output the last interpolated PSK symbol, max once per chunk_size - if ( cstln_out ) - cstln_out->write(s); - - // AGC - // For APSK we must do AGC on the symbols, not the whole signal. - // TODO Use a better estimator at low SNR. - float insp = sg.re*sg.re + sg.im*sg.im; - est_insp = insp*kest + est_insp*(1-kest); - if ( est_insp ) - agc_gain = cstln_amp / gen_sqrt(est_insp); - - // SS and MER - complex ev(s.re-cstln_point->re, s.im-cstln_point->im); - float sig_power, ev_power; - if ( cstln->nsymbols == 2 ) { - // Special case for BPSK: Ignore quadrature component of noise. - // TBD Projection on I axis assumes BPSK at 45° - float sig_real = (cstln_point->re+cstln_point->im) * 0.707; - float ev_real = (ev.re+ev.im) * 0.707; - sig_power = sig_real * sig_real; - ev_power = ev_real * ev_real; - } else { - sig_power = - (int)cstln_point->re*cstln_point->re + - (int)cstln_point->im*cstln_point->im; - ev_power = ev.re*ev.re + ev.im*ev.im; - } - est_sp = sig_power*kest + est_sp*(1-kest); - est_ep = ev_power*kest + est_ep*(1-kest); - - } - - // This is best done periodically ouside the inner loop, - // but will cause non-deterministic output. - - if ( ! allow_drift ) { - if ( freqw < min_freqw || freqw > max_freqw ) - freqw = (max_freqw+min_freqw) / 2; - } - - // Output measurements - - refresh_freq_tap(); - - meas_count += pin-pin0; - while ( meas_count >= meas_decimation ) { - meas_count -= meas_decimation; - if ( freq_out ) - freq_out->write(freq_tap); - if ( ss_out ) - ss_out->write(sqrtf(est_insp)); - if ( mer_out ) - mer_out->write(est_ep ? 10*logf(est_sp/est_ep)/logf(10) : 0); - } - - } // Work to do + void set_allow_drift(bool d) + { + allow_drift = d; } - + + void update_freq_limits() + { + // Prevent PLL from crossing +-SR/n/2 and locking at +-SR/n. + int n = 4; + if (cstln) + { + switch (cstln->nsymbols) + { + case 2: + n = 2; + break; // BPSK + case 4: + n = 4; + break; // QPSK + case 8: + n = 8; + break; // 8PSK + case 16: + n = 12; + break; // 16APSK + case 32: + n = 16; + break; // 32APSK + default: + n = 4; + break; + } + } + min_freqw = freqw - 65536 / max_omega / n / 2; + max_freqw = freqw + 65536 / max_omega / n / 2; + } + + void run() + { + if (!cstln) + fail("constellation not set"); + + // Magic constants that work with the qa recordings. + float freq_alpha = 0.04; + float freq_beta = 0.0012 / omega * pll_adjustment; + float gain_mu = 0.02 / (cstln_amp * cstln_amp) * 2; + + unsigned int max_meas = chunk_size / meas_decimation + 1; + // Large margin on output_size because mu adjustments + // can lead to more than chunk_size/min_omega symbols. + while (in.readable() >= chunk_size + sampler->readahead() + && out.writable() >= chunk_size + && (!freq_out || freq_out->writable() >= max_meas) + && (!ss_out || ss_out->writable() >= max_meas) + && (!mer_out || mer_out->writable() >= max_meas) + && (!cstln_out || cstln_out->writable() >= max_meas)) + { + + sampler->update_freq(freqw); + + complex *pin = in.rd(), *pin0 = pin, *pend = pin + chunk_size; + softsymbol *pout = out.wr(), *pout0 = pout; + + // These are scoped outside the loop for SS and MER estimation. + complex sg; // Symbol before AGC; + complex s; // For MER estimation and constellation viewer + complex *cstln_point = NULL; + + while (pin < pend) + { + // Here mu is the time of the next symbol counted from 0 at pin. + if (mu < 1) + { + // Here 0<=mu<1 is the fractional time of the next symbol + // between pin and pin+1. + sg = sampler->interp(pin, mu, phase); + s = sg * agc_gain; + + // Constellation look-up + cstln_lut<256>::result *cr = cstln->lookup(s.re, s.im); + *pout = cr->ss; + ++pout; + + // PLL + phase += cr->phase_error * freq_alpha; + freqw += cr->phase_error * freq_beta; + + // Modified Mueller and Müller + // mu[k]=real((c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) + // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) + // p = received signals + // c = decisions (constellation points) + hist[2] = hist[1]; + hist[1] = hist[0]; + hist[0].p.re = s.re; + hist[0].p.im = s.im; + cstln_point = &cstln->symbols[cr->ss.symbol]; + hist[0].c.re = cstln_point->re; + hist[0].c.im = cstln_point->im; + float muerr = ((hist[0].p.re - hist[2].p.re) * hist[1].c.re + + (hist[0].p.im - hist[2].p.im) * hist[1].c.im) + - ((hist[0].c.re - hist[2].c.re) * hist[1].p.re + + (hist[0].c.im - hist[2].c.im) + * hist[1].p.im); + float mucorr = muerr * gain_mu; + const float max_mucorr = 0.1; + // TBD Optimize out statically + if (mucorr < -max_mucorr) + mucorr = -max_mucorr; + if (mucorr > max_mucorr) + mucorr = max_mucorr; + mu += mucorr; + mu += omega; // Next symbol time; + } // mu<1 + + // Next sample + ++pin; + --mu; + phase += freqw; + } // chunk_size + + in.read(pin - pin0); + out.written(pout - pout0); + + // Normalize phase so that it never exceeds 32 bits. + // Max freqw is 2^31/65536/chunk_size = 256 Hz + // (this may happen with leandvb --drift --decim). + phase = fmodf(phase, 65536); + + if (cstln_point) + { + + // Output the last interpolated PSK symbol, max once per chunk_size + if (cstln_out) + cstln_out->write(s); + + // AGC + // For APSK we must do AGC on the symbols, not the whole signal. + // TODO Use a better estimator at low SNR. + float insp = sg.re * sg.re + sg.im * sg.im; + est_insp = insp * kest + est_insp * (1 - kest); + if (est_insp) + agc_gain = cstln_amp / gen_sqrt(est_insp); + + // SS and MER + complex ev(s.re - cstln_point->re, + s.im - cstln_point->im); + float sig_power, ev_power; + if (cstln->nsymbols == 2) + { + // Special case for BPSK: Ignore quadrature component of noise. + // TBD Projection on I axis assumes BPSK at 45° + float sig_real = (cstln_point->re + cstln_point->im) + * 0.707; + float ev_real = (ev.re + ev.im) * 0.707; + sig_power = sig_real * sig_real; + ev_power = ev_real * ev_real; + } + else + { + sig_power = (int) cstln_point->re * cstln_point->re + + (int) cstln_point->im * cstln_point->im; + ev_power = ev.re * ev.re + ev.im * ev.im; + } + est_sp = sig_power * kest + est_sp * (1 - kest); + est_ep = ev_power * kest + est_ep * (1 - kest); + + } + + // This is best done periodically ouside the inner loop, + // but will cause non-deterministic output. + + if (!allow_drift) + { + if (freqw < min_freqw || freqw > max_freqw) + freqw = (max_freqw + min_freqw) / 2; + } + + // Output measurements + + refresh_freq_tap(); + + meas_count += pin - pin0; + while (meas_count >= meas_decimation) + { + meas_count -= meas_decimation; + if (freq_out) + freq_out->write(freq_tap); + if (ss_out) + ss_out->write(sqrtf(est_insp)); + if (mer_out) + mer_out->write( + est_ep ? 10 * logf(est_sp / est_ep) / logf(10) : 0); + } + + } // Work to do + } + float freq_tap; - void refresh_freq_tap() { - freq_tap = freqw / 65536; + void refresh_freq_tap() + { + freq_tap = freqw / 65536; } - private: - struct { - complex p; // Received symbol - complex c; // Matched constellation point +private: + struct + { + complex p; // Received symbol + complex c; // Matched constellation point } hist[3]; - pipereader< complex > in; + pipereader > in; pipewriter out; float est_insp, agc_gain; float mu; // PSK time expressed in clock ticks @@ -921,16 +1116,16 @@ namespace leansdr { unsigned long meas_count; pipewriter *freq_out, *ss_out, *mer_out; pipewriter *cstln_out; - }; - - - // FAST QPSK RECEIVER +}; - // Optimized for u8 input, no AGC, uses phase information only. - // Outputs hard symbols. +// FAST QPSK RECEIVER - template - struct fast_qpsk_receiver : runnable { +// Optimized for u8 input, no AGC, uses phase information only. +// Outputs hard symbols. + +template +struct fast_qpsk_receiver: runnable +{ typedef u8 hardsymbol; unsigned long meas_decimation; // Measurement rate float omega, min_omega, max_omega; // Samples per symbol @@ -938,231 +1133,272 @@ namespace leansdr { float pll_adjustment; bool allow_drift; // Follow carrier beyond safe limits static const unsigned int chunk_size = 128; - - fast_qpsk_receiver(scheduler *sch, - pipebuf< complex > &_in, - pipebuf &_out, - pipebuf *_freq_out=NULL, - pipebuf< complex > *_cstln_out=NULL) - : runnable(sch, "Fast QPSK receiver"), - meas_decimation(1048576), - pll_adjustment(1.0), - allow_drift(false), - in(_in), out(_out, chunk_size), - mu(0), phase(0), - meas_count(0) + + fast_qpsk_receiver( + scheduler *sch, + pipebuf > &_in, + pipebuf &_out, + pipebuf *_freq_out = NULL, + pipebuf > *_cstln_out = NULL) : + runnable(sch, "Fast QPSK receiver"), + meas_decimation(1048576), + pll_adjustment(1.0), + allow_drift(false), + in(_in), + out(_out, chunk_size), + mu(0), + phase(0), + meas_count(0) { - set_omega(1); - set_freq(0); - freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; - cstln_out = _cstln_out ? new pipewriter< complex >(*_cstln_out) : NULL; - memset(hist, 0, sizeof(hist)); - init_lookup_tables(); - } - - void set_omega(float _omega, float tol=10e-6) { - omega = _omega; - min_omega = omega * (1-tol); - max_omega = omega * (1+tol); - update_freq_limits(); - } - - void set_freq(float freq) { - freqw = freq * 65536; - update_freq_limits(); + set_omega(1); + set_freq(0); + freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; + cstln_out = + _cstln_out ? new pipewriter >(*_cstln_out) : NULL; + memset(hist, 0, sizeof(hist)); + init_lookup_tables(); } - void update_freq_limits() { - // Prevent PLL from locking at +-symbolrate/4. - // TODO The +-SR/8 limit is suitable for QPSK only. - min_freqw = freqw - 65536/max_omega/8; - max_freqw = freqw + 65536/max_omega/8; + void set_omega(float _omega, float tol = 10e-6) + { + omega = _omega; + min_omega = omega * (1 - tol); + max_omega = omega * (1 + tol); + update_freq_limits(); + } + + void set_freq(float freq) + { + freqw = freq * 65536; + update_freq_limits(); + } + + void update_freq_limits() + { + // Prevent PLL from locking at +-symbolrate/4. + // TODO The +-SR/8 limit is suitable for QPSK only. + min_freqw = freqw - 65536 / max_omega / 8; + max_freqw = freqw + 65536 / max_omega / 8; } static const int RLUT_BITS = 8; static const int RLUT_ANGLES = 1 << RLUT_BITS; - void run() { - // Magic constants that work with the qa recordings. - signed long freq_alpha = 0.04 * 65536; - signed long freq_beta = 0.0012 * 256 * 65536 / omega * pll_adjustment; - if ( ! freq_beta ) fail("Excessive oversampling"); + void run() + { + // Magic constants that work with the qa recordings. + signed long freq_alpha = 0.04 * 65536; + signed long freq_beta = 0.0012 * 256 * 65536 / omega * pll_adjustment; + if (!freq_beta) + fail("Excessive oversampling"); - float gain_mu = 0.02 / (cstln_amp*cstln_amp) * 2; + float gain_mu = 0.02 / (cstln_amp * cstln_amp) * 2; - int max_meas = chunk_size/meas_decimation + 1; - // Largin margin on output_size because mu adjustments - // can lead to more than chunk_size/min_omega symbols. - while ( in.readable() >= chunk_size+1 && // +1 for interpolation - out.writable() >= chunk_size && - ( !freq_out || freq_out ->writable()>=max_meas ) && - ( !cstln_out || cstln_out->writable()>=max_meas ) ) { - - complex *pin=in.rd(), *pin0=pin, *pend=pin+chunk_size; - hardsymbol *pout=out.wr(), *pout0=pout; + int max_meas = chunk_size / meas_decimation + 1; + // Largin margin on output_size because mu adjustments + // can lead to more than chunk_size/min_omega symbols. + while (in.readable() >= chunk_size + 1 + && // +1 for interpolation + out.writable() >= chunk_size + && (!freq_out || freq_out->writable() >= max_meas) + && (!cstln_out || cstln_out->writable() >= max_meas)) + { - cu8 s; - u_angle symbol_arg = 0; // Exported for constellation viewer + complex *pin = in.rd(), *pin0 = pin, *pend = pin + chunk_size; + hardsymbol *pout = out.wr(), *pout0 = pout; - while ( pin < pend ) { - // Here mu is the time of the next symbol counted from 0 at pin. - if ( mu < 1 ) { - // Here 0<=mu<1 is the fractional time of the next symbol - // between pin and pin+1. + cu8 s; + u_angle symbol_arg = 0; // Exported for constellation viewer - // Derotate and interpolate + while (pin < pend) + { + // Here mu is the time of the next symbol counted from 0 at pin. + if (mu < 1) + { + // Here 0<=mu<1 is the fractional time of the next symbol + // between pin and pin+1. + + // Derotate and interpolate #if 0 // Phase only (does not work) - // Careful with the float/signed/unsigned casts - u_angle a0 = fast_arg(pin[0]) - phase; - u_angle a1 = fast_arg(pin[1]) - (phase+freqw); - s_angle da = a1 - a0; - symbol_arg = a0 + (s_angle)(da*mu); - s = arg_to_symbol(symbol_arg); + // Careful with the float/signed/unsigned casts + u_angle a0 = fast_arg(pin[0]) - phase; + u_angle a1 = fast_arg(pin[1]) - (phase+freqw); + s_angle da = a1 - a0; + symbol_arg = a0 + (s_angle)(da*mu); + s = arg_to_symbol(symbol_arg); #elif 1 // Linear by lookup-table. 1.2M on bench3bishs - polar *p0 = &lut_polar[pin[0].re][pin[0].im]; - u_angle a0 = (u_angle)(p0->a-phase) >> (16-RLUT_BITS); - cu8 *p0r = &lut_rect[a0][p0->r>>1]; - polar *p1 = &lut_polar[pin[1].re][pin[1].im]; - u_angle a1 = (u_angle)(p1->a-(phase+freqw)) >> (16-RLUT_BITS); - cu8 *p1r = &lut_rect[a1][p1->r>>1]; - s.re = (int)(p0r->re + (p1r->re-p0r->re)*mu); - s.im = (int)(p0r->im + (p1r->im-p0r->im)*mu); - symbol_arg = fast_arg(s); + polar *p0 = &lut_polar[pin[0].re][pin[0].im]; + u_angle a0 = (u_angle) (p0->a - phase) >> (16 - RLUT_BITS); + cu8 *p0r = &lut_rect[a0][p0->r >> 1]; + polar *p1 = &lut_polar[pin[1].re][pin[1].im]; + u_angle a1 = (u_angle) (p1->a - (phase + freqw)) + >> (16 - RLUT_BITS); + cu8 *p1r = &lut_rect[a1][p1->r >> 1]; + s.re = (int) (p0r->re + (p1r->re - p0r->re) * mu); + s.im = (int) (p0r->im + (p1r->im - p0r->im) * mu); + symbol_arg = fast_arg(s); #else // Linear floating-point, for reference - float a0 = -(int)phase*M_PI/32768; - float cosa0=cosf(a0), sina0=sinf(a0); - complex - p0r(((float)pin[0].re-128)*cosa0 - ((float)pin[0].im-128)*sina0, - ((float)pin[0].re-128)*sina0 + ((float)pin[0].im-128)*cosa0); - float a1 = -(int)(phase+freqw)*M_PI/32768; - float cosa1=cosf(a1), sina1=sinf(a1); - complex - p1r(((float)pin[1].re-128)*cosa1 - ((float)pin[1].im-128)*sina1, - ((float)pin[1].re-128)*sina1 + ((float)pin[1].im-128)*cosa1); - s.re = (int)(128 + p0r.re + (p1r.re-p0r.re)*mu); - s.im = (int)(128 + p0r.im + (p1r.im-p0r.im)*mu); - symbol_arg = fast_arg(s); + float a0 = -(int)phase*M_PI/32768; + float cosa0=cosf(a0), sina0=sinf(a0); + complex + p0r(((float)pin[0].re-128)*cosa0 - ((float)pin[0].im-128)*sina0, + ((float)pin[0].re-128)*sina0 + ((float)pin[0].im-128)*cosa0); + float a1 = -(int)(phase+freqw)*M_PI/32768; + float cosa1=cosf(a1), sina1=sinf(a1); + complex + p1r(((float)pin[1].re-128)*cosa1 - ((float)pin[1].im-128)*sina1, + ((float)pin[1].re-128)*sina1 + ((float)pin[1].im-128)*cosa1); + s.re = (int)(128 + p0r.re + (p1r.re-p0r.re)*mu); + s.im = (int)(128 + p0r.im + (p1r.im-p0r.im)*mu); + symbol_arg = fast_arg(s); #endif - int quadrant = symbol_arg >> 14; - static unsigned char quadrant_to_symbol[4] = { 0, 2, 3, 1 }; - *pout = quadrant_to_symbol[quadrant]; - ++pout; + int quadrant = symbol_arg >> 14; + static unsigned char quadrant_to_symbol[4] = + { 0, 2, 3, 1 }; + *pout = quadrant_to_symbol[quadrant]; + ++pout; - // PLL - s_angle phase_error = (s_angle)(symbol_arg&16383) - 8192; - phase += (phase_error * freq_alpha + 32768) >> 16; - freqw += (phase_error * freq_beta + 32768*256) >> 24; - - // Modified Mueller and Müller - // mu[k]=real((c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) - // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) - // p = received signals - // c = decisions (constellation points) - hist[2] = hist[1]; - hist[1] = hist[0]; + // PLL + s_angle phase_error = (s_angle) (symbol_arg & 16383) - 8192; + phase += (phase_error * freq_alpha + 32768) >> 16; + freqw += (phase_error * freq_beta + 32768 * 256) >> 24; + + // Modified Mueller and Müller + // mu[k]=real((c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) + // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) + // p = received signals + // c = decisions (constellation points) + hist[2] = hist[1]; + hist[1] = hist[0]; #define HIST_FLOAT 0 #if HIST_FLOAT - hist[0].p.re = (float)s.re - 128; - hist[0].p.im = (float)s.im - 128; + hist[0].p.re = (float)s.re - 128; + hist[0].p.im = (float)s.im - 128; - cu8 cp = arg_to_symbol((symbol_arg&49152)+8192); - hist[0].c.re = (float)cp.re - 128; - hist[0].c.im = (float)cp.im - 128; + cu8 cp = arg_to_symbol((symbol_arg&49152)+8192); + hist[0].c.re = (float)cp.re - 128; + hist[0].c.im = (float)cp.im - 128; - float muerr = - ( (hist[0].p.re-hist[2].p.re)*hist[1].c.re + - (hist[0].p.im-hist[2].p.im)*hist[1].c.im ) - - ( (hist[0].c.re-hist[2].c.re)*hist[1].p.re + - (hist[0].c.im-hist[2].c.im)*hist[1].p.im ); + float muerr = + ( (hist[0].p.re-hist[2].p.re)*hist[1].c.re + + (hist[0].p.im-hist[2].p.im)*hist[1].c.im ) - + ( (hist[0].c.re-hist[2].c.re)*hist[1].p.re + + (hist[0].c.im-hist[2].c.im)*hist[1].p.im ); #else - hist[0].p = s; - hist[0].c = arg_to_symbol((symbol_arg&49152)+8192); + hist[0].p = s; + hist[0].c = arg_to_symbol((symbol_arg & 49152) + 8192); - int muerr = - ( (signed char)(hist[0].p.re-hist[2].p.re)*((int)hist[1].c.re-128) + - (signed char)(hist[0].p.im-hist[2].p.im)*((int)hist[1].c.im-128) ) - - ( (signed char)(hist[0].c.re-hist[2].c.re)*((int)hist[1].p.re-128) + - (signed char)(hist[0].c.im-hist[2].c.im)*((int)hist[1].p.im-128) ); + int muerr = + ((signed char) (hist[0].p.re - hist[2].p.re) + * ((int) hist[1].c.re - 128) + + (signed char) (hist[0].p.im - hist[2].p.im) + * ((int) hist[1].c.im - 128)) + - ((signed char) (hist[0].c.re + - hist[2].c.re) + * ((int) hist[1].p.re - 128) + + (signed char) (hist[0].c.im + - hist[2].c.im) + * ((int) hist[1].p.im - 128)); #endif - float mucorr = muerr * gain_mu; - const float max_mucorr = 0.1; - // TBD Optimize out statically - if ( mucorr < -max_mucorr ) mucorr = -max_mucorr; - if ( mucorr > max_mucorr ) mucorr = max_mucorr; - mu += mucorr; - mu += omega; // Next symbol time; - } // mu<1 - - // Next sample - ++pin; - --mu; - phase += freqw; - } // chunk_size - - in.read(pin-pin0); - out.written(pout-pout0); + float mucorr = muerr * gain_mu; + const float max_mucorr = 0.1; + // TBD Optimize out statically + if (mucorr < -max_mucorr) + mucorr = -max_mucorr; + if (mucorr > max_mucorr) + mucorr = max_mucorr; + mu += mucorr; + mu += omega; // Next symbol time; + } // mu<1 - if ( symbol_arg && cstln_out ) - // Output the last interpolated PSK symbol, max once per chunk_size - cstln_out->write(s); - - // This is best done periodically ouside the inner loop, - // but will cause non-deterministic output. - - if ( ! allow_drift ) { - if ( freqw < min_freqw || freqw > max_freqw ) - freqw = (max_freqw+min_freqw) / 2; - } - - // Output measurements - - meas_count += pin-pin0; - while ( meas_count >= meas_decimation ) { - meas_count -= meas_decimation; - if ( freq_out ) - freq_out->write((float)freqw / 65536); - } - - } // Work to do - } - - private: + // Next sample + ++pin; + --mu; + phase += freqw; + } // chunk_size - struct polar { u_angle a; unsigned char r; } lut_polar[256][256]; - u_angle fast_arg(const cu8 &c) { - // TBD read cu8 as u16 index, same endianness as in init() - return lut_polar[c.re][c.im].a; + in.read(pin - pin0); + out.written(pout - pout0); + + if (symbol_arg && cstln_out) + // Output the last interpolated PSK symbol, max once per chunk_size + cstln_out->write(s); + + // This is best done periodically ouside the inner loop, + // but will cause non-deterministic output. + + if (!allow_drift) + { + if (freqw < min_freqw || freqw > max_freqw) + freqw = (max_freqw + min_freqw) / 2; + } + + // Output measurements + + meas_count += pin - pin0; + while (meas_count >= meas_decimation) + { + meas_count -= meas_decimation; + if (freq_out) + freq_out->write((float) freqw / 65536); + } + + } // Work to do + } + +private: + + struct polar + { + u_angle a; + unsigned char r; + } lut_polar[256][256]; + u_angle fast_arg(const cu8 &c) + { + // TBD read cu8 as u16 index, same endianness as in init() + return lut_polar[c.re][c.im].a; } cu8 lut_rect[RLUT_ANGLES][256]; cu8 lut_sincos[65536]; - cu8 arg_to_symbol(u_angle a) { return lut_sincos[a]; } - void init_lookup_tables() { - for ( int i=0; i<256; ++i ) - for ( int q=0; q<256; ++q ) { - // Don't cast float to unsigned directly - lut_polar[i][q].a = (s_angle)(atan2f(q-128,i-128)*65536/(2*M_PI)); - lut_polar[i][q].r = (int)hypotf(i-128,q-128); - } - for ( unsigned long a=0; a<65536; ++a ) { - float f = 2*M_PI * a / 65536; - lut_sincos[a].re = 128 + cstln_amp*cosf(f); - lut_sincos[a].im = 128 + cstln_amp*sinf(f); - } - for ( int a=0; a p; // Received symbol - complex c; // Matched constellation point + complex p; // Received symbol + complex c;// Matched constellation point #else - cu8 p; // Received symbol - cu8 c; // Matched constellation point + cu8 p; // Received symbol + cu8 c; // Matched constellation point #endif } hist[3]; pipereader in; @@ -1172,224 +1408,268 @@ namespace leansdr { unsigned long meas_count; pipewriter *freq_out, *mer_out; pipewriter *cstln_out; - }; // fast_qpsk_receiver - +}; +// fast_qpsk_receiver - // CONSTELLATION TRANSMITTER +// CONSTELLATION TRANSMITTER - // Maps symbols to I/Q points. +// Maps symbols to I/Q points. - template - struct cstln_transmitter : runnable { +template +struct cstln_transmitter: runnable +{ cstln_lut<256> *cstln; - cstln_transmitter(scheduler *sch, - pipebuf &_in, pipebuf< complex > &_out) - : runnable(sch, "cstln_transmitter"), - in(_in), out(_out) + + cstln_transmitter(scheduler *sch, pipebuf &_in, pipebuf > &_out) : + runnable(sch, "cstln_transmitter"), + cstln(0), + in(_in), + out(_out) { } - void run() { - if ( ! cstln ) fail("constellation not set"); - int count = min(in.readable(), out.writable()); - u8 *pin=in.rd(), *pend=pin+count; - complex *pout = out.wr(); - for ( ; pin *cp = &cstln->symbols[*pin]; - pout->re = Zout + cp->re; - pout->im = Zout + cp->im; - } - in.read(count); - out.written(count); + + void run() + { + if (!cstln) + fail("constellation not set"); + int count = min(in.readable(), out.writable()); + u8 *pin = in.rd(), *pend = pin + count; + complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout) + { + complex *cp = &cstln->symbols[*pin]; + pout->re = Zout + cp->re; + pout->im = Zout + cp->im; + } + in.read(count); + out.written(count); } - private: + +private: pipereader in; - pipewriter< complex > out; - }; // cstln_transmitter + pipewriter > out; +}; +// cstln_transmitter +// FREQUENCY SHIFTER - // FREQUENCY SHIFTER +// Resolution is sample_freq/65536. - // Resolution is sample_freq/65536. - - template - struct rotator : runnable { - rotator(scheduler *sch, pipebuf< complex > &_in, - pipebuf< complex > &_out, float freq) - : runnable(sch, "rotator"), - in(_in), out(_out), index(0) { - int ifreq = freq * 65536; - if ( sch->debug ) - fprintf(stderr, "Rotate: req=%f real=%f\n", freq, ifreq/65536.0); - for ( int i=0; i<65536; ++i ) { - lut_cos[i] = cosf(2*M_PI * i * ifreq / 65536); - lut_sin[i] = sinf(2*M_PI * i * ifreq / 65536); - } +template +struct rotator: runnable +{ + rotator(scheduler *sch, pipebuf > &_in, pipebuf > &_out, float freq) : + runnable(sch, "rotator"), + in(_in), + out(_out), + index(0) + { + int ifreq = freq * 65536; + if (sch->debug) + fprintf(stderr, "Rotate: req=%f real=%f\n", freq, ifreq / 65536.0); + for (int i = 0; i < 65536; ++i) + { + lut_cos[i] = cosf(2 * M_PI * i * ifreq / 65536); + lut_sin[i] = sinf(2 * M_PI * i * ifreq / 65536); + } } - void run() { - unsigned long count = min(in.readable(), out.writable()); - complex *pin = in.rd(), *pend = pin+count; - complex *pout = out.wr(); - for ( ; pinre = pin->re*c - pin->im*s; - pout->im = pin->re*s + pin->im*c; - } - in.read(count); - out.written(count); + + void run() + { + unsigned long count = min(in.readable(), out.writable()); + complex *pin = in.rd(), *pend = pin + count; + complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout, ++index) + { + float c = lut_cos[index]; + float s = lut_sin[index]; + pout->re = pin->re * c - pin->im * s; + pout->im = pin->re * s + pin->im * c; + } + in.read(count); + out.written(count); } - private: - pipereader< complex > in; - pipewriter< complex > out; + +private: + pipereader > in; + pipewriter > out; float lut_cos[65536]; float lut_sin[65536]; unsigned short index; // Current phase - }; // rotator +}; +// rotator +// SPECTRUM-BASED CNR ESTIMATOR - // SPECTRUM-BASED CNR ESTIMATOR +// Assumes that the spectrum is as follows: +// +// ---|--noise---|-roll-off-|---carrier+noise----|-roll-off-|---noise--|--- +// | (bw/2) | (bw) | (bw/2) | (bw) | (bw/2) | +// +// Maximum roll-off 0.5 - // Assumes that the spectrum is as follows: - // - // ---|--noise---|-roll-off-|---carrier+noise----|-roll-off-|---noise--|--- - // | (bw/2) | (bw) | (bw/2) | (bw) | (bw/2) | - // - // Maximum roll-off 0.5 - - template - struct cnr_fft : runnable { - cnr_fft(scheduler *sch, pipebuf< complex > &_in, pipebuf &_out, - float _bandwidth, int nfft=4096) - : runnable(sch, "cnr_fft"), - bandwidth(_bandwidth), freq_tap(NULL), tap_multiplier(1), - decimation(1048576), kavg(0.1), - in(_in), out(_out), - fft(nfft), avgpower(NULL), phase(0) { - if ( bandwidth > 0.25 ) - fail("CNR estimator requires Fsampling > 4x Fsignal"); +template +struct cnr_fft: runnable +{ + cnr_fft(scheduler *sch, pipebuf > &_in, pipebuf &_out, float _bandwidth, int nfft = 4096) : + runnable(sch, "cnr_fft"), + bandwidth(_bandwidth), + freq_tap(NULL), + tap_multiplier(1), + decimation(1048576), + kavg(0.1), + in(_in), + out(_out), + fft(nfft), + avgpower(NULL), + phase(0) + { + if (bandwidth > 0.25) + fail("CNR estimator requires Fsampling > 4x Fsignal"); } float bandwidth; - float *freq_tap, tap_multiplier; + float *freq_tap, tap_multiplier; int decimation; float kavg; - void run() { - while ( in.readable()>=fft.n && out.writable()>=1 ) { - phase += fft.n; - if ( phase >= decimation ) { - phase -= decimation; - do_cnr(); - } - in.read(fft.n); - } - } - - private: - - void do_cnr() { - float center_freq = freq_tap ? *freq_tap * tap_multiplier : 0; - int icf = floor(center_freq*fft.n+0.5); - complex data[fft.n]; - memcpy(data, in.rd(), fft.n*sizeof(data[0])); - fft.inplace(data, true); - T power[fft.n]; - for ( int i=0; i0 && n2>0) ? 10 * logf(c2/n2)/logf(10) : -50; - out.write(cnr); + void run() + { + while (in.readable() >= fft.n && out.writable() >= 1) + { + phase += fft.n; + if (phase >= decimation) + { + phase -= decimation; + do_cnr(); + } + in.read(fft.n); + } } - float avg__slots(int i0, int i1) { // i0 <= i1 - T s = 0; - for ( int i=i0; i<=i1; ++i ) s += avgpower[i&(fft.n-1)]; - return s / (i1-i0+1); +private: + + void do_cnr() + { + float center_freq = freq_tap ? *freq_tap * tap_multiplier : 0; + int icf = floor(center_freq * fft.n + 0.5); + complex data[fft.n]; + memcpy(data, in.rd(), fft.n * sizeof(data[0])); + fft.inplace(data, true); + T power[fft.n]; + for (unsigned int i = 0; i < fft.n; ++i) + power[i] = data[i].re * data[i].re + data[i].im * data[i].im; + if (!avgpower) + { + // Initialize with first spectrum + avgpower = new T[fft.n]; + memcpy(avgpower, power, fft.n * sizeof(avgpower[0])); + } + // Accumulate and low-pass filter + for (unsigned int i = 0; i < fft.n; ++i) + avgpower[i] = avgpower[i] * (1 - kavg) + power[i] * kavg; + + int bw__slots = (bandwidth / 4) * fft.n; + if (!bw__slots) + return; + // Measure carrier+noise in center band + float c2plusn2 = avg__slots(icf - bw__slots, icf + bw__slots); + // Measure noise left and right of roll-off zones + float n2 = (avg__slots(icf - bw__slots * 4, icf - bw__slots * 3) + + avg__slots(icf + bw__slots * 3, icf + bw__slots * 4)) / 2; + float c2 = c2plusn2 - n2; + float cnr = (c2 > 0 && n2 > 0) ? 10 * logf(c2 / n2) / logf(10) : -50; + out.write(cnr); } - - pipereader< complex > in; - pipewriter< float > out; + + float avg__slots(int i0, int i1) + { // i0 <= i1 + T s = 0; + for (int i = i0; i <= i1; ++i) + s += avgpower[i & (fft.n - 1)]; + return s / (i1 - i0 + 1); + } + + pipereader > in; + pipewriter out; cfft_engine fft; T *avgpower; int phase; - }; // cnr_fft +}; +// cnr_fft - template - struct spectrum : runnable { +template +struct spectrum: runnable +{ static const int nfft = 1024; - spectrum(scheduler *sch, pipebuf< complex > &_in, - pipebuf &_out) - : runnable(sch, "spectrum"), - decimation(1048576), kavg(0.1), - in(_in), out(_out), - fft(nfft), avgpower(NULL), phase(0) { + spectrum(scheduler *sch, pipebuf > &_in, pipebuf &_out) : + runnable(sch, "spectrum"), + decimation(1048576), + kavg(0.1), + in(_in), + out(_out), + fft(nfft), + avgpower(NULL), + phase(0) + { } int decimation; float kavg; - void run() { - while ( in.readable()>=fft.n && out.writable()>=1 ) { - phase += fft.n; - if ( phase >= decimation ) { - phase -= decimation; - do_spectrum(); - } - in.read(fft.n); - } + void run() + { + while (in.readable() >= fft.n && out.writable() >= 1) + { + phase += fft.n; + if (phase >= decimation) + { + phase -= decimation; + do_spectrum(); + } + in.read(fft.n); + } } - private: +private: - void do_spectrum() { - complex data[fft.n]; - memcpy(data, in.rd(), fft.n*sizeof(data[0])); - fft.inplace(data, true); - float power[nfft]; - for ( int i=0; i data[fft.n]; + memcpy(data, in.rd(), fft.n * sizeof(data[0])); + fft.inplace(data, true); + float power[nfft]; + for (int i = 0; i < fft.n; ++i) + power[i] = (float) data[i].re * data[i].re + + (float) data[i].im * data[i].im; + if (!avgpower) + { + // Initialize with first spectrum + avgpower = new float[fft.n]; + memcpy(avgpower, power, fft.n * sizeof(avgpower[0])); + } + // Accumulate and low-pass filter + for (int i = 0; i < fft.n; ++i) + avgpower[i] = avgpower[i] * (1 - kavg) + power[i] * kavg; - // Reuse power[] - for ( int i=0; i > in; - pipewriter< float[nfft] > out; + pipereader > in; + pipewriter out; cfft_engine fft; T *avgpower; int phase; - }; // spectrum +}; +// spectrum - -} // namespace +}// namespace #endif // LEANSDR_SDR_H From 400c712ca8bf10729f4dbe61d0b24c4d6a3eb708 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 25 Feb 2018 00:31:19 +0100 Subject: [PATCH 003/956] Bumped to v3.13.0 --- app/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 ++++++ plugins/channelrx/CMakeLists.txt | 5 +---- plugins/channelrx/demoddatv/datvdemodplugin.cpp | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 2ca68d006..68301c610 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.12.0"); + QCoreApplication::setApplicationVersion("3.13.0"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 3febc1a5c..741c68b06 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.12.0"); + QCoreApplication::setApplicationVersion("3.13.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 5978e8a35..0245f38c1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (3.13.0-1) unstable; urgency=medium + + * DATV (Digital Amateur TV) demodulator. + + -- Edouard Griffiths, F4EXB Sun, 25 Feb 2018 12:14:18 +0100 + sdrangel (3.12.0-1) unstable; urgency=medium * Perseus support. diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index e8d70170a..86d6bc75e 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -11,15 +11,12 @@ add_subdirectory(demodwfm) add_subdirectory(chanalyzer) add_subdirectory(chanalyzerng) add_subdirectory(demodatv) +add_subdirectory(demoddatv) if(LIBDSDCC_FOUND AND LIBMBE_FOUND) add_subdirectory(demoddsd) endif(LIBDSDCC_FOUND AND LIBMBE_FOUND) -#if (NOT RX_SAMPLE_24BIT) - add_subdirectory(demoddatv) -#endif() - if (BUILD_DEBIAN) add_subdirectory(demoddsd) endif (BUILD_DEBIAN) diff --git a/plugins/channelrx/demoddatv/datvdemodplugin.cpp b/plugins/channelrx/demoddatv/datvdemodplugin.cpp index 4b52fbc09..690d17ea2 100644 --- a/plugins/channelrx/demoddatv/datvdemodplugin.cpp +++ b/plugins/channelrx/demoddatv/datvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DATVDemodPlugin::m_ptrPluginDescriptor = { QString("DATV Demodulator"), - QString("3.2.0"), + QString("3.13.0"), QString("(c) F4HKW for SDRAngel using LeanSDR framework (c) F4DAV"), QString("https://github.com/f4exb/sdrangel"), true, From 916a284b48e3bafd9e717112dcb81cca184cb090 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 25 Feb 2018 03:21:01 +0100 Subject: [PATCH 004/956] DATV demod: message formatting and boolean test correction --- plugins/channelrx/demoddatv/datvideorender.cpp | 10 +++++----- plugins/channelrx/demoddatv/datvideorender.h | 13 +++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvideorender.cpp b/plugins/channelrx/demoddatv/datvideorender.cpp index c16cc4a0a..41e58a21d 100644 --- a/plugins/channelrx/demoddatv/datvideorender.cpp +++ b/plugins/channelrx/demoddatv/datvideorender.cpp @@ -341,14 +341,14 @@ bool DATVideoRender::RenderStream() int intGotFrame; bool blnNeedRenderingSetup; - if(m_blnIsOpen=false) + if (!m_blnIsOpen) { - qDebug() << "DATVideoProcess::RenderStream Stream not open"; + qDebug() << "DATVideoProcess::RenderStream: Stream not open"; return false; } - if(m_blnRunning==true) + if (m_blnRunning) { return false; } @@ -508,9 +508,9 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) return false; } - if(m_blnIsOpen=false) + if (!m_blnIsOpen) { - qDebug() << "DATVideoProcess::CloseStream Stream not open"; + qDebug() << "DATVideoProcess::CloseStream: Stream not open"; return false; } diff --git a/plugins/channelrx/demoddatv/datvideorender.h b/plugins/channelrx/demoddatv/datvideorender.h index a6fb1860d..3d44cad3a 100644 --- a/plugins/channelrx/demoddatv/datvideorender.h +++ b/plugins/channelrx/demoddatv/datvideorender.h @@ -160,12 +160,13 @@ class DATVideoRenderThread: public QThread if(m_objRenderer->OpenStream(m_objStream)) { - printf("PID: %d W: %d H: %d Codec: %s Data: %s %s\r\n",m_objRenderer->MetaData.PID - ,m_objRenderer->MetaData.Width - ,m_objRenderer->MetaData.Height - ,m_objRenderer->MetaData.CodecDescription.toStdString().c_str() - ,m_objRenderer->MetaData.Program.toStdString().c_str() - ,m_objRenderer->MetaData.Stream.toStdString().c_str()); + qInfo("DATVideoRenderThread::run: PID: %d W: %d H: %d Codec: %s Data: %s Service: %s", + m_objRenderer->MetaData.PID, + m_objRenderer->MetaData.Width, + m_objRenderer->MetaData.Height, + m_objRenderer->MetaData.CodecDescription.toStdString().c_str(), + m_objRenderer->MetaData.Program.toStdString().c_str(), + m_objRenderer->MetaData.Stream.toStdString().c_str()); m_blnRenderingVideo=true; } From e53da4e9a85da72e784d333b361983ef4ffbeb8f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 25 Feb 2018 03:22:30 +0100 Subject: [PATCH 005/956] DATV demod: make sure that when baseband rate changes the channelizer is reconfigured to get all available bandwidth --- plugins/channelrx/demoddatv/datvdemod.cpp | 642 +++++++++++----------- plugins/channelrx/demoddatv/datvdemod.h | 2 + sdrbase/dsp/downchannelizer.h | 1 + 3 files changed, 320 insertions(+), 325 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 7b4e0ec99..c0a0e0fb2 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -29,7 +29,6 @@ #include "dsp/threadedbasebandsamplesink.h" #include "device/devicesourceapi.h" - const QString DATVDemod::m_channelIdURI = "sdrangel.channel.demoddatv"; const QString DATVDemod::m_channelId = "DATVDemod"; @@ -37,22 +36,22 @@ MESSAGE_CLASS_DEFINITION(DATVDemod::MsgConfigureDATVDemod, Message) MESSAGE_CLASS_DEFINITION(DATVDemod::MsgConfigureChannelizer, Message) DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : - ChannelSinkAPI(m_channelIdURI), - m_blnNeedConfigUpdate(false), - m_deviceAPI(deviceAPI), - m_objRegisteredDATVScreen(NULL), - m_objRegisteredVideoRender(NULL), - m_objVideoStream(NULL), - m_objRenderThread(NULL), - m_blnRenderingVideo(false), - m_enmModulation(BPSK /*DATV_FM1*/), - m_objSettingsMutex(QMutex::NonRecursive) + ChannelSinkAPI(m_channelIdURI), + m_blnNeedConfigUpdate(false), + m_deviceAPI(deviceAPI), + m_objRegisteredDATVScreen(0), + m_objRegisteredVideoRender(0), + m_objVideoStream(0), + m_objRenderThread(0), + m_blnRenderingVideo(false), + m_enmModulation(BPSK /*DATV_FM1*/), + m_objSettingsMutex(QMutex::NonRecursive) { setObjectName("DATVDemod"); qDebug("DATVDemod::DATVDemod: sizeof FixReal: %lu: SDR_RX_SAMP_SZ: %u", sizeof(FixReal), (unsigned int) SDR_RX_SAMP_SZ); //*************** DATV PARAMETERS *************** - m_blnInitialized=false; + m_blnInitialized = false; CleanUpDATVFramework(); m_objVideoStream = new DATVideostream(); @@ -65,30 +64,28 @@ DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : m_deviceAPI->addChannelAPI(this); connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - } DATVDemod::~DATVDemod() { - m_blnInitialized=false; + m_blnInitialized = false; - - if(m_objRenderThread!=NULL) + if (m_objRenderThread != NULL) { - if(m_objRenderThread->isRunning()) + if (m_objRenderThread->isRunning()) { - m_objRenderThread->stopRendering(); + m_objRenderThread->stopRendering(); } } //CleanUpDATVFramework(true); - if(m_objRFFilter!=NULL) + if (m_objRFFilter != NULL) { //delete m_objRFFilter; } - if(m_objVideoStream!=NULL) + if (m_objVideoStream != NULL) { //m_objVideoStream->close(); //delete m_objVideoStream; @@ -110,42 +107,41 @@ DATVideostream * DATVDemod::SetVideoRender(DATVideoRender *objScreen) { m_objRegisteredVideoRender = objScreen; - m_objRenderThread = new DATVideoRenderThread(m_objRegisteredVideoRender,m_objVideoStream); + m_objRenderThread = new DATVideoRenderThread(m_objRegisteredVideoRender, m_objVideoStream); return m_objVideoStream; } - bool DATVDemod::PlayVideo(bool blnStartStop) { - if(m_objVideoStream==NULL) + if (m_objVideoStream == NULL) { return false; } - if(m_objRegisteredVideoRender==NULL) + if (m_objRegisteredVideoRender == NULL) { return false; } - if(m_objRenderThread==NULL) + if (m_objRenderThread == NULL) { return false; } - if(m_objRenderThread->isRunning()) + if (m_objRenderThread->isRunning()) { - if(blnStartStop==true) - { - m_objRenderThread->stopRendering(); - } + if (blnStartStop == true) + { + m_objRenderThread->stopRendering(); + } - return true; + return true; } - m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender,m_objVideoStream); - m_objVideoStream->MultiThreaded=true; + m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender, m_objVideoStream); + m_objVideoStream->MultiThreaded = true; m_objRenderThread->start(); //m_objVideoStream->MultiThreaded=false; @@ -154,54 +150,69 @@ bool DATVDemod::PlayVideo(bool blnStartStop) return true; } -void DATVDemod::configure(MessageQueue* objMessageQueue, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) +void DATVDemod::configure( + MessageQueue* objMessageQueue, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + bool blnHDLC, + bool blnHardMetric, + bool blnResample, + bool blnViterbi) { - Message* msgCmd = MsgConfigureDATVDemod::create(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,blnHDLC,blnHardMetric,blnResample, blnViterbi); + Message* msgCmd = MsgConfigureDATVDemod::create( + intRFBandwidth, + intCenterFrequency, + enmStandard, + enmModulation, + enmFEC, + intSymbolRate, + intNotchFilters, + blnAllowDrift, + blnFastLock, + blnHDLC, + blnHardMetric, + blnResample, + blnViterbi); objMessageQueue->push(msgCmd); } -void DATVDemod::InitDATVParameters(int intMsps, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSampleRate, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) +void DATVDemod::InitDATVParameters( + int intMsps, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSampleRate, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + bool blnHDLC, + bool blnHardMetric, + bool blnResample, + bool blnViterbi) { Real fltLowCut; Real fltHiCut; - m_blnInitialized=false; + m_blnInitialized = false; m_objSettingsMutex.lock(); //Recalibrage du filtre passe bande - fltLowCut = -((float)intRFBandwidth / 2.0) / (float)intMsps; - fltHiCut = ((float)intRFBandwidth / 2.0) / (float)intMsps; + fltLowCut = -((float) intRFBandwidth / 2.0) / (float) intMsps; + fltHiCut = ((float) intRFBandwidth / 2.0) / (float) intMsps; m_objRFFilter->create_filter(fltLowCut, fltHiCut); - m_objNCO.setFreq(-(float)intCenterFrequency,(float)intMsps); + m_objNCO.setFreq(-(float) intCenterFrequency, (float) intMsps); //Mise à jour de la config @@ -221,25 +232,24 @@ void DATVDemod::InitDATVParameters(int intMsps, m_objRunning.blnResample = blnResample; m_objRunning.blnViterbi = blnViterbi; - qDebug() << "DATVDemod::InitDATVParameters:" - << " - Msps: " << intMsps - << " - Sample Rate: " << intSampleRate - << " - Symbol Rate: " << intSymbolRate - << " - Modulation: " << enmModulation - << " - Notch Filters: " << intNotchFilters - << " - Allow Drift: " << blnAllowDrift - << " - Fast Lock: " << blnFastLock - << " - HDLC: " << blnHDLC - << " - HARD METRIC: " << blnHardMetric - << " - Resample: " << blnResample - << " - Viterbi: " << blnViterbi; - + qDebug() << "DATVDemod::InitDATVParameters:" + << " - Msps: " << intMsps + << " - Sample Rate: " << intSampleRate + << " - Symbol Rate: " << intSymbolRate + << " - Modulation: " << enmModulation + << " - Notch Filters: " << intNotchFilters + << " - Allow Drift: " << blnAllowDrift + << " - Fast Lock: " << blnFastLock + << " - HDLC: " << blnHDLC + << " - HARD METRIC: " << blnHardMetric + << " - Resample: " << blnResample + << " - Viterbi: " << blnViterbi; m_objSettingsMutex.unlock(); - m_blnNeedConfigUpdate=true; + m_blnNeedConfigUpdate = true; - m_blnInitialized=true; + m_blnInitialized = true; } void DATVDemod::CleanUpDATVFramework() @@ -393,7 +403,6 @@ void DATVDemod::CleanUpDATVFramework() p_locktime = 0; r_sync_mpeg = 0; - // DEINTERLEAVING p_rspackets = 0; r_deinter = 0; @@ -403,30 +412,26 @@ void DATVDemod::CleanUpDATVFramework() p_rtspackets = 0; r_rsdec = 0; - //BER ESTIMATION p_vber = 0; - r_vber = 0; - + r_vber = 0; // DERANDOMIZATION p_tspackets = 0; r_derand = 0; - //OUTPUT : To remove r_stdout = 0; r_videoplayer = 0; - //CONSTELLATION r_scope_symbols = 0; } void DATVDemod::InitDATVFramework() { - m_blnDVBInitialized=false; - m_lngReadIQ=0; + m_blnDVBInitialized = false; + m_lngReadIQ = 0; m_objCfg.standard = m_objRunning.enmStandard; @@ -435,46 +440,46 @@ void DATVDemod::InitDATVFramework() m_objCfg.Fm = (float) m_objRunning.intSymbolRate; m_objCfg.fastlock = m_objRunning.blnFastLock; - switch(m_objRunning.enmModulation) + switch (m_objRunning.enmModulation) { - case BPSK: - m_objCfg.constellation = cstln_lut<256>::BPSK; + case BPSK: + m_objCfg.constellation = cstln_lut<256>::BPSK; break; - case QPSK: - m_objCfg.constellation = cstln_lut<256>::QPSK; + case QPSK: + m_objCfg.constellation = cstln_lut<256>::QPSK; break; - case PSK8: - m_objCfg.constellation = cstln_lut<256>::PSK8; + case PSK8: + m_objCfg.constellation = cstln_lut<256>::PSK8; break; - case APSK16: - m_objCfg.constellation = cstln_lut<256>::APSK16; + case APSK16: + m_objCfg.constellation = cstln_lut<256>::APSK16; break; - case APSK32: - m_objCfg.constellation = cstln_lut<256>::APSK32; + case APSK32: + m_objCfg.constellation = cstln_lut<256>::APSK32; break; - case APSK64E: - m_objCfg.constellation = cstln_lut<256>::APSK64E; + case APSK64E: + m_objCfg.constellation = cstln_lut<256>::APSK64E; break; - case QAM16: - m_objCfg.constellation = cstln_lut<256>::QAM16; + case QAM16: + m_objCfg.constellation = cstln_lut<256>::QAM16; break; - case QAM64: - m_objCfg.constellation = cstln_lut<256>::QAM64; + case QAM64: + m_objCfg.constellation = cstln_lut<256>::QAM64; break; - case QAM256: - m_objCfg.constellation = cstln_lut<256>::QAM256; + case QAM256: + m_objCfg.constellation = cstln_lut<256>::QAM256; break; - default: - m_objCfg.constellation = cstln_lut<256>::BPSK; + default: + m_objCfg.constellation = cstln_lut<256>::BPSK; break; } @@ -485,7 +490,6 @@ void DATVDemod::InitDATVFramework() m_objCfg.resample = m_objRunning.blnResample; m_objCfg.viterbi = m_objRunning.blnViterbi; - // Min buffer size for baseband data // scopes: 1024 // ss_estimator: 1024 @@ -515,8 +519,7 @@ void DATVDemod::InitDATVFramework() // Min buffer size for misc measurements: 1 BUF_SLOW = m_objCfg.buf_factor; - m_lngExpectedReadIQ = BUF_BASEBAND; - + m_lngExpectedReadIQ = BUF_BASEBAND; CleanUpDATVFramework(); @@ -529,20 +532,19 @@ void DATVDemod::InitDATVFramework() // NOTCH FILTER - if ( m_objCfg.anf ) + if (m_objCfg.anf) { p_autonotched = new pipebuf(m_objScheduler, "autonotched", BUF_BASEBAND); r_auto_notch = new auto_notch(m_objScheduler, *p_preprocessed, *p_autonotched, m_objCfg.anf, 0); p_preprocessed = p_autonotched; } - // FREQUENCY CORRECTION - if ( m_objCfg.Fderot ) + if (m_objCfg.Fderot) { p_derot = new pipebuf(m_objScheduler, "derotated", BUF_BASEBAND); - r_derot = new rotator(m_objScheduler, *p_preprocessed, *p_derot, -m_objCfg.Fderot/m_objCfg.Fs); + r_derot = new rotator(m_objScheduler, *p_preprocessed, *p_derot, -m_objCfg.Fderot / m_objCfg.Fs); p_preprocessed = p_derot; } @@ -550,9 +552,9 @@ void DATVDemod::InitDATVFramework() p_cnr = new pipebuf(m_objScheduler, "cnr", BUF_SLOW); - if ( m_objCfg.cnr ) + if (m_objCfg.cnr) { - r_cnr = new cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm/m_objCfg.Fs); + r_cnr = new cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm / m_objCfg.Fs); r_cnr->decimation = decimation(m_objCfg.Fs, 1); // 1 Hz } @@ -560,168 +562,164 @@ void DATVDemod::InitDATVFramework() int decim = 1; - if ( m_objCfg.resample ) + if (m_objCfg.resample) { - // Lowpass-filter and decimate. - if ( m_objCfg.decim ) - { - decim = m_objCfg.decim; - } - else - { - // Decimate to just above 4 samples per symbol - float target_Fs = m_objCfg.Fm * 4; - decim = m_objCfg.Fs / target_Fs; - if ( decim < 1 ) + // Lowpass-filter and decimate. + if (m_objCfg.decim) { - decim = 1; + decim = m_objCfg.decim; + } + else + { + // Decimate to just above 4 samples per symbol + float target_Fs = m_objCfg.Fm * 4; + decim = m_objCfg.Fs / target_Fs; + if (decim < 1) + { + decim = 1; + } } - } - float transition = (m_objCfg.Fm/2) * m_objCfg.rolloff; - int order = m_objCfg.resample_rej * m_objCfg.Fs / (22*transition); - order = ((order+1)/2) * 2; // Make even + float transition = (m_objCfg.Fm / 2) * m_objCfg.rolloff; + int order = m_objCfg.resample_rej * m_objCfg.Fs / (22 * transition); + order = ((order + 1) / 2) * 2; // Make even - p_resampled = new pipebuf(m_objScheduler, "resampled", BUF_BASEBAND); + p_resampled = new pipebuf(m_objScheduler, "resampled", BUF_BASEBAND); +#if 1 // Cut in middle of roll-off region + float Fcut = (m_objCfg.Fm / 2) * (1 + m_objCfg.rolloff / 2) / m_objCfg.Fs; +#else // Cut at beginning of roll-off region + float Fcut = (m_objCfg.Fm/2) / cfg.Fs; +#endif - #if 1 // Cut in middle of roll-off region - float Fcut = (m_objCfg.Fm/2) * (1+m_objCfg.rolloff/2) / m_objCfg.Fs; - #else // Cut at beginning of roll-off region - float Fcut = (m_objCfg.Fm/2) / cfg.Fs; - #endif + ncoeffs = filtergen::lowpass(order, Fcut, &coeffs); - ncoeffs = filtergen::lowpass(order, Fcut, &coeffs); + filtergen::normalize_dcgain(ncoeffs, coeffs, 1); - filtergen::normalize_dcgain(ncoeffs, coeffs, 1); - - r_resample = new fir_filter(m_objScheduler, ncoeffs, coeffs, *p_preprocessed, *p_resampled, decim); - p_preprocessed = p_resampled; - m_objCfg.Fs /= decim; + r_resample = new fir_filter(m_objScheduler, ncoeffs, coeffs, *p_preprocessed, *p_resampled, decim); + p_preprocessed = p_resampled; + m_objCfg.Fs /= decim; } // DECIMATION // (Unless already done in resampler) - if ( !m_objCfg.resample && m_objCfg.decim>1 ) + if (!m_objCfg.resample && m_objCfg.decim > 1) { - decim = m_objCfg.decim; + decim = m_objCfg.decim; - p_decimated = new pipebuf(m_objScheduler, "decimated", BUF_BASEBAND); - p_decim = new decimator(m_objScheduler, decim, *p_preprocessed, *p_decimated); - p_preprocessed = p_decimated; - m_objCfg.Fs /= decim; + p_decimated = new pipebuf(m_objScheduler, "decimated", BUF_BASEBAND); + p_decim = new decimator(m_objScheduler, decim, *p_preprocessed, *p_decimated); + p_preprocessed = p_decimated; + m_objCfg.Fs /= decim; } //Resampling FS - // Generic constellation receiver p_symbols = new pipebuf(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS); - p_freq = new pipebuf (m_objScheduler, "freq", BUF_SLOW); - p_ss = new pipebuf (m_objScheduler, "SS", BUF_SLOW); - p_mer = new pipebuf (m_objScheduler, "MER", BUF_SLOW); - p_sampled = new pipebuf (m_objScheduler, "PSK symbols", BUF_BASEBAND); + p_freq = new pipebuf(m_objScheduler, "freq", BUF_SLOW); + p_ss = new pipebuf(m_objScheduler, "SS", BUF_SLOW); + p_mer = new pipebuf(m_objScheduler, "MER", BUF_SLOW); + p_sampled = new pipebuf(m_objScheduler, "PSK symbols", BUF_BASEBAND); - switch ( m_objCfg.sampler ) + switch (m_objCfg.sampler) { - case SAMP_NEAREST: - sampler = new nearest_sampler(); - break; + case SAMP_NEAREST: + sampler = new nearest_sampler(); + break; - case SAMP_LINEAR: - sampler = new linear_sampler(); - break; + case SAMP_LINEAR: + sampler = new linear_sampler(); + break; - case SAMP_RRC: + case SAMP_RRC: + { + + if (m_objCfg.rrc_steps == 0) { - - - if ( m_objCfg.rrc_steps == 0 ) - { // At least 64 discrete sampling points between symbols - m_objCfg.rrc_steps = max(1, (int)(64*m_objCfg.Fm / m_objCfg.Fs)); - } - - float Frrc = m_objCfg.Fs * m_objCfg.rrc_steps; // Sample freq of the RRC filter - float transition = (m_objCfg.Fm/2) * m_objCfg.rolloff; - int order = m_objCfg.rrc_rej * Frrc / (22*transition); - ncoeffs_sampler = filtergen::root_raised_cosine(order, m_objCfg.Fm/Frrc, m_objCfg.rolloff, &coeffs_sampler); - - sampler = new fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); - break; + m_objCfg.rrc_steps = max(1, (int) (64 * m_objCfg.Fm / m_objCfg.Fs)); } - default: - fatal("Interpolator not implemented"); + float Frrc = m_objCfg.Fs * m_objCfg.rrc_steps; // Sample freq of the RRC filter + float transition = (m_objCfg.Fm / 2) * m_objCfg.rolloff; + int order = m_objCfg.rrc_rej * Frrc / (22 * transition); + ncoeffs_sampler = filtergen::root_raised_cosine(order, m_objCfg.Fm / Frrc, m_objCfg.rolloff, &coeffs_sampler); + + sampler = new fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); + break; + } + + default: + fatal("Interpolator not implemented"); } m_objDemodulator = new cstln_receiver(m_objScheduler, sampler, *p_preprocessed, *p_symbols, p_freq, p_ss, p_mer, p_sampled); - if ( m_objCfg.standard == DVB_S ) + if (m_objCfg.standard == DVB_S) { - if ( m_objCfg.constellation != cstln_lut<256>::QPSK && m_objCfg.constellation != cstln_lut<256>::BPSK ) - { - fprintf(stderr, "Warning: non-standard constellation for DVB-S\n"); - } + if (m_objCfg.constellation != cstln_lut<256>::QPSK && m_objCfg.constellation != cstln_lut<256>::BPSK) + { + qWarning("DATVDemod::InitDATVFramework: non-standard constellation for DVB-S"); + } } - if ( m_objCfg.standard == DVB_S2 ) + if (m_objCfg.standard == DVB_S2) { - // For DVB-S2 testing only. - // Constellation should be determined from PL signalling. - fprintf(stderr, "DVB-S2: Testing symbol sampler only.\n"); + // For DVB-S2 testing only. + // Constellation should be determined from PL signalling. + qWarning("DATVDemod::InitDATVFramework: DVB-S2: Testing symbol sampler only."); } m_objDemodulator->cstln = make_dvbs2_constellation(m_objCfg.constellation, m_objCfg.fec); - if ( m_objCfg.hard_metric ) + if (m_objCfg.hard_metric) { - m_objDemodulator->cstln->harden(); + m_objDemodulator->cstln->harden(); } - m_objDemodulator->set_omega(m_objCfg.Fs/m_objCfg.Fm); + m_objDemodulator->set_omega(m_objCfg.Fs / m_objCfg.Fm); - if ( m_objCfg.Ftune ) + if (m_objCfg.Ftune) { - m_objDemodulator->set_freq(m_objCfg.Ftune/m_objCfg.Fs); + m_objDemodulator->set_freq(m_objCfg.Ftune / m_objCfg.Fs); } - if ( m_objCfg.allow_drift ) + if (m_objCfg.allow_drift) { - m_objDemodulator->set_allow_drift(true); + m_objDemodulator->set_allow_drift(true); } - if ( m_objCfg.viterbi ) + if (m_objCfg.viterbi) { - m_objDemodulator->pll_adjustment /= 6; + m_objDemodulator->pll_adjustment /= 6; } m_objDemodulator->meas_decimation = decimation(m_objCfg.Fs, m_objCfg.Finfo); // TRACKING FILTERS - if ( r_resample ) + if (r_resample) { - r_resample->freq_tap = &m_objDemodulator->freq_tap; - r_resample->tap_multiplier = 1.0 / decim; - r_resample->freq_tol = m_objCfg.Fm/(m_objCfg.Fs*decim) * 0.1; + r_resample->freq_tap = &m_objDemodulator->freq_tap; + r_resample->tap_multiplier = 1.0 / decim; + r_resample->freq_tol = m_objCfg.Fm / (m_objCfg.Fs * decim) * 0.1; } - - if ( r_cnr ) + if (r_cnr) { - r_cnr->freq_tap = &m_objDemodulator->freq_tap; - r_cnr->tap_multiplier = 1.0 / decim; + r_cnr->freq_tap = &m_objDemodulator->freq_tap; + r_cnr->tap_multiplier = 1.0 / decim; } //constellation - m_objRegisteredDATVScreen->resizeDATVScreen(256,256); + m_objRegisteredDATVScreen->resizeDATVScreen(256, 256); - r_scope_symbols = new datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredDATVScreen); + r_scope_symbols = new datvconstellation(m_objScheduler, *p_sampled, -128, 128, NULL, m_objRegisteredDATVScreen); r_scope_symbols->decimation = 1; r_scope_symbols->cstln = &m_objDemodulator->cstln; @@ -731,94 +729,88 @@ void DATVDemod::InitDATVFramework() r_deconv = NULL; - if ( m_objCfg.viterbi ) + if (m_objCfg.viterbi) { - if ( m_objCfg.fec==FEC23 && (m_objDemodulator->cstln->nsymbols==4 || m_objDemodulator->cstln->nsymbols==64) ) - { - m_objCfg.fec = FEC46; - } + if (m_objCfg.fec == FEC23 && (m_objDemodulator->cstln->nsymbols == 4 || m_objDemodulator->cstln->nsymbols == 64)) + { + m_objCfg.fec = FEC46; + } - //To uncomment -> Linking Problem : undefined symbol: _ZN7leansdr21viterbi_dec_interfaceIhhiiE6updateEPiS2_ - r = new viterbi_sync(m_objScheduler, (*p_symbols), (*p_bytes), m_objDemodulator->cstln, m_objCfg.fec); + //To uncomment -> Linking Problem : undefined symbol: _ZN7leansdr21viterbi_dec_interfaceIhhiiE6updateEPiS2_ + r = new viterbi_sync(m_objScheduler, (*p_symbols), (*p_bytes), m_objDemodulator->cstln, m_objCfg.fec); - if ( m_objCfg.fastlock ) - { - r->resync_period = 1; - } + if (m_objCfg.fastlock) + { + r->resync_period = 1; + } } else { - r_deconv = make_deconvol_sync_simple(m_objScheduler, (*p_symbols), (*p_bytes), m_objCfg.fec); - r_deconv->fastlock = m_objCfg.fastlock; + r_deconv = make_deconvol_sync_simple(m_objScheduler, (*p_symbols), (*p_bytes), m_objCfg.fec); + r_deconv->fastlock = m_objCfg.fastlock; } - - if ( m_objCfg.hdlc ) + if (m_objCfg.hdlc) { - p_descrambled = new pipebuf(m_objScheduler, "descrambled", BUF_MPEGBYTES); - r_etr192_descrambler = new etr192_descrambler(m_objScheduler, (*p_bytes), *p_descrambled); - p_frames = new pipebuf(m_objScheduler, "frames", BUF_MPEGBYTES); - r_sync = new hdlc_sync(m_objScheduler, *p_descrambled, *p_frames, 2, 278); + p_descrambled = new pipebuf(m_objScheduler, "descrambled", BUF_MPEGBYTES); + r_etr192_descrambler = new etr192_descrambler(m_objScheduler, (*p_bytes), *p_descrambled); + p_frames = new pipebuf(m_objScheduler, "frames", BUF_MPEGBYTES); + r_sync = new hdlc_sync(m_objScheduler, *p_descrambled, *p_frames, 2, 278); - if ( m_objCfg.fastlock ) - { - r_sync->resync_period = 1; - } + if (m_objCfg.fastlock) + { + r_sync->resync_period = 1; + } - if ( m_objCfg.packetized ) - { - r_sync->header16 = true; - } + if (m_objCfg.packetized) + { + r_sync->header16 = true; + } } - p_mpegbytes = new pipebuf (m_objScheduler, "mpegbytes", BUF_MPEGBYTES); - p_lock = new pipebuf (m_objScheduler, "lock", BUF_SLOW); - p_locktime = new pipebuf (m_objScheduler, "locktime", BUF_PACKETS); + p_mpegbytes = new pipebuf(m_objScheduler, "mpegbytes", BUF_MPEGBYTES); + p_lock = new pipebuf(m_objScheduler, "lock", BUF_SLOW); + p_locktime = new pipebuf(m_objScheduler, "locktime", BUF_PACKETS); - if ( ! m_objCfg.hdlc ) + if (!m_objCfg.hdlc) { - r_sync_mpeg = new mpeg_sync(m_objScheduler, *p_bytes, *p_mpegbytes, r_deconv, p_lock, p_locktime); - r_sync_mpeg->fastlock = m_objCfg.fastlock; + r_sync_mpeg = new mpeg_sync(m_objScheduler, *p_bytes, *p_mpegbytes, r_deconv, p_lock, p_locktime); + r_sync_mpeg->fastlock = m_objCfg.fastlock; } // DEINTERLEAVING - p_rspackets = new pipebuf< rspacket >(m_objScheduler, "RS-enc packets", BUF_PACKETS); + p_rspackets = new pipebuf >(m_objScheduler, "RS-enc packets", BUF_PACKETS); r_deinter = new deinterleaver(m_objScheduler, *p_mpegbytes, *p_rspackets); - // REED-SOLOMON p_vbitcount = new pipebuf(m_objScheduler, "Bits processed", BUF_PACKETS); p_verrcount = new pipebuf(m_objScheduler, "Bits corrected", BUF_PACKETS); p_rtspackets = new pipebuf(m_objScheduler, "rand TS packets", BUF_PACKETS); - r_rsdec = new rs_decoder (m_objScheduler, *p_rspackets, *p_rtspackets, p_vbitcount, p_verrcount); - + r_rsdec = new rs_decoder(m_objScheduler, *p_rspackets, *p_rtspackets, p_vbitcount, p_verrcount); // BER ESTIMATION - - p_vber = new pipebuf (m_objScheduler, "VBER", BUF_SLOW); - r_vber = new rate_estimator (m_objScheduler, *p_verrcount, *p_vbitcount, *p_vber); - r_vber->sample_size = m_objCfg.Fm/2; // About twice per second, depending on CR + p_vber = new pipebuf(m_objScheduler, "VBER", BUF_SLOW); + r_vber = new rate_estimator(m_objScheduler, *p_verrcount, *p_vbitcount, *p_vber); + r_vber->sample_size = m_objCfg.Fm / 2; // About twice per second, depending on CR // Require resolution better than 2E-5 - if ( r_vber->sample_size < 50000 ) + if (r_vber->sample_size < 50000) { r_vber->sample_size = 50000; } - // DERANDOMIZATION p_tspackets = new pipebuf(m_objScheduler, "TS packets", BUF_PACKETS); r_derand = new derandomizer(m_objScheduler, *p_rtspackets, *p_tspackets); - // OUTPUT - r_videoplayer = new datvvideoplayer(m_objScheduler, *p_tspackets,m_objVideoStream); + r_videoplayer = new datvvideoplayer(m_objScheduler, *p_tspackets, m_objVideoStream); - m_blnDVBInitialized=true; + m_blnDVBInitialized = true; } void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) @@ -830,7 +822,6 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect fftfilt::cmplx *objRF; int intRFOut; - #ifdef EXTENDED_DIRECT_SAMPLE qint16 * ptrBufferToRelease=NULL; @@ -860,28 +851,27 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect fltQ = it->imag(); #endif - //********** demodulation ********** - if((m_blnDVBInitialized==false) || (m_blnNeedConfigUpdate==true)) + if ((m_blnDVBInitialized == false) || (m_blnNeedConfigUpdate == true)) { - m_blnNeedConfigUpdate=false; + m_blnNeedConfigUpdate = false; InitDATVFramework(); } //********** iq stream **************** - if(m_lngReadIQ>p_rawiq_writer->writable()) + if (m_lngReadIQ > p_rawiq_writer->writable()) { m_objScheduler->step(); m_objRegisteredDATVScreen->renderImage(NULL); - m_lngReadIQ=0; + m_lngReadIQ = 0; p_rawiq_writer = new pipewriter(*p_rawiq); } - if(false) + if (false) { objIQ.re = fltI; objIQ.im = fltQ; @@ -893,25 +883,24 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect else { - Complex objC(fltI,fltQ); + Complex objC(fltI, fltQ); objC *= m_objNCO.nextIQ(); intRFOut = m_objRFFilter->runFilt(objC, &objRF); // filter RF before demod - for (int intI = 0 ; intI < intRFOut; intI++) + for (int intI = 0; intI < intRFOut; intI++) { objIQ.re = objRF->real(); objIQ.im = objRF->imag(); p_rawiq_writer->write(objIQ); - objRF ++; + objRF++; m_lngReadIQ++; } } - //********** demodulation ********** } @@ -939,61 +928,52 @@ void DATVDemod::stop() bool DATVDemod::handleMessage(const Message& cmd) { - qDebug() << "DATVDemod::handleMessage"; - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { + { DownChannelizer::MsgChannelizerNotification& objNotif = (DownChannelizer::MsgChannelizerNotification&) cmd; - if(m_objRunning.intMsps!=objNotif.getSampleRate()) + qDebug() << "DATVDemod::handleMessage: MsgChannelizerNotification:" << " intMsps: " << objNotif.getSampleRate(); + + if (m_objRunning.intMsps != objNotif.getSampleRate()) { m_objRunning.intMsps = objNotif.getSampleRate(); m_objRunning.intSampleRate = m_objRunning.intMsps; - printf("Sample Rate: %d\r\n",m_objRunning.intSampleRate ); + qDebug("DATVDemod::handleMessage: Sample Rate: %d", m_objRunning.intSampleRate); ApplySettings(); } - qDebug() << "DATVDemod::handleMessage: MsgChannelizerNotification:" - << " intMsps: " << m_objRunning.intMsps; - return true; - } + } else if (MsgConfigureChannelizer::match(cmd)) { MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - m_channelizer->getInputSampleRate(), - cfg.getCenterFrequency()); - //m_objRunning.intCenterFrequency); - - qDebug() << "DATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate() + qDebug() << "DATVDemod::handleMessage: MsgConfigureChannelizer:" + << " sampleRate: " << m_channelizer->getInputSampleRate() << " centerFrequency: " << cfg.getCenterFrequency(); - //<< " centerFrequency: " << m_objRunning.intCenterFrequency; + + m_channelizer->configure(m_channelizer->getInputMessageQueue(), m_channelizer->getInputSampleRate(), cfg.getCenterFrequency()); + //m_objRunning.intCenterFrequency); + //<< " centerFrequency: " << m_objRunning.intCenterFrequency; return true; } else if (MsgConfigureDATVDemod::match(cmd)) - { + { + qDebug() << "DATVDemod::handleMessage: MsgConfigureDATVDemod"; + MsgConfigureDATVDemod& objCfg = (MsgConfigureDATVDemod&) cmd; - - if((objCfg.m_objMsgConfig.blnAllowDrift != m_objRunning.blnAllowDrift) - || (objCfg.m_objMsgConfig.intRFBandwidth != m_objRunning.intRFBandwidth) - || (objCfg.m_objMsgConfig.intCenterFrequency != m_objRunning.intCenterFrequency) - || (objCfg.m_objMsgConfig.blnFastLock != m_objRunning.blnFastLock) - || (objCfg.m_objMsgConfig.blnHardMetric != m_objRunning.blnHardMetric) - || (objCfg.m_objMsgConfig.blnHDLC != m_objRunning.blnHDLC) - || (objCfg.m_objMsgConfig.blnResample != m_objRunning.blnResample) - || (objCfg.m_objMsgConfig.blnViterbi != m_objRunning.blnViterbi) - || (objCfg.m_objMsgConfig.enmFEC != m_objRunning.enmFEC) - || (objCfg.m_objMsgConfig.enmModulation != m_objRunning.enmModulation) - || (objCfg.m_objMsgConfig.enmStandard != m_objRunning.enmStandard) - || (objCfg.m_objMsgConfig.intNotchFilters != m_objRunning.intNotchFilters) - || (objCfg.m_objMsgConfig.intSymbolRate != m_objRunning.intSymbolRate)) - { + if ((objCfg.m_objMsgConfig.blnAllowDrift != m_objRunning.blnAllowDrift) || (objCfg.m_objMsgConfig.intRFBandwidth != m_objRunning.intRFBandwidth) + || (objCfg.m_objMsgConfig.intCenterFrequency != m_objRunning.intCenterFrequency) || (objCfg.m_objMsgConfig.blnFastLock != m_objRunning.blnFastLock) + || (objCfg.m_objMsgConfig.blnHardMetric != m_objRunning.blnHardMetric) || (objCfg.m_objMsgConfig.blnHDLC != m_objRunning.blnHDLC) + || (objCfg.m_objMsgConfig.blnResample != m_objRunning.blnResample) || (objCfg.m_objMsgConfig.blnViterbi != m_objRunning.blnViterbi) + || (objCfg.m_objMsgConfig.enmFEC != m_objRunning.enmFEC) || (objCfg.m_objMsgConfig.enmModulation != m_objRunning.enmModulation) + || (objCfg.m_objMsgConfig.enmStandard != m_objRunning.enmStandard) || (objCfg.m_objMsgConfig.intNotchFilters != m_objRunning.intNotchFilters) + || (objCfg.m_objMsgConfig.intSymbolRate != m_objRunning.intSymbolRate)) + { m_objRunning.blnAllowDrift = objCfg.m_objMsgConfig.blnAllowDrift; m_objRunning.blnFastLock = objCfg.m_objMsgConfig.blnFastLock; m_objRunning.blnHardMetric = objCfg.m_objMsgConfig.blnHardMetric; @@ -1009,42 +989,47 @@ bool DATVDemod::handleMessage(const Message& cmd) m_objRunning.intCenterFrequency = objCfg.m_objMsgConfig.intCenterFrequency; ApplySettings(); - } + } - - return true; - } - else - { - return false; - } + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + qDebug() << "DATVDemod::handleMessage: DSPSignalNotification"; + return true; + } + else + { + return false; + } } void DATVDemod::ApplySettings() { - if(m_objRunning.intMsps==0) + if (m_objRunning.intMsps == 0) { return; } //m_objSettingsMutex.lock(); - InitDATVParameters(m_objRunning.intMsps, - m_objRunning.intRFBandwidth, - m_objRunning.intCenterFrequency, - m_objRunning.enmStandard, - m_objRunning.enmModulation, - m_objRunning.enmFEC, - m_objRunning.intSampleRate, - m_objRunning.intSymbolRate, - m_objRunning.intNotchFilters, - m_objRunning.blnAllowDrift, - m_objRunning.blnFastLock, - m_objRunning.blnHDLC, - m_objRunning.blnHardMetric, - m_objRunning.blnResample, - m_objRunning.blnViterbi); + InitDATVParameters( + m_objRunning.intMsps, + m_objRunning.intRFBandwidth, + m_objRunning.intCenterFrequency, + m_objRunning.enmStandard, + m_objRunning.enmModulation, + m_objRunning.enmFEC, + m_objRunning.intSampleRate, + m_objRunning.intSymbolRate, + m_objRunning.intNotchFilters, + m_objRunning.blnAllowDrift, + m_objRunning.blnFastLock, + m_objRunning.blnHDLC, + m_objRunning.blnHardMetric, + m_objRunning.blnResample, + m_objRunning.blnViterbi); } @@ -1053,3 +1038,10 @@ int DATVDemod::GetSampleRate() return m_objRunning.intMsps; } +void DATVDemod::channelSampleRateChanged() +{ + qDebug("DATVDemod::channelSampleRateChanged"); + // reconfigure to get full available bandwidth + m_channelizer->configure(m_channelizer->getInputMessageQueue(), m_channelizer->getInputSampleRate(), m_channelizer->getRequestedCenterFrequency()); + // TODO: forward to GUI if necessary +} diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 8796f7229..161711c12 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -496,6 +496,8 @@ private: void ApplySettings(); +private slots: + void channelSampleRateChanged(); }; #endif // INCLUDE_DATVDEMOD_H diff --git a/sdrbase/dsp/downchannelizer.h b/sdrbase/dsp/downchannelizer.h index 96d031354..f95678c08 100644 --- a/sdrbase/dsp/downchannelizer.h +++ b/sdrbase/dsp/downchannelizer.h @@ -68,6 +68,7 @@ public: void configure(MessageQueue* messageQueue, int sampleRate, int centerFrequency); int getInputSampleRate() const { return m_inputSampleRate; } + int getRequestedCenterFrequency() const { return m_requestedCenterFrequency; } virtual void start(); virtual void stop(); From 96014f0efb0493eeb194f47f4446ff17db4a8428 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 25 Feb 2018 10:13:51 +0100 Subject: [PATCH 006/956] Windows build: added missing PlutoSDR output plugin in the deployment script --- windows.install.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/windows.install.bat b/windows.install.bat index 93c10c828..d2719e14a 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -67,3 +67,4 @@ copy plugins\samplesink\filesink\%1\outputfilesink.dll %2\plugins\samplesink copy plugins\samplesink\bladerfoutput\%1\outputbladerf.dll %2\plugins\samplesink copy plugins\samplesink\hackrfoutput\%1\outputhackrf.dll %2\plugins\samplesink copy plugins\samplesink\limesdroutput\%1\outputlimesdr.dll %2\plugins\samplesink +copy plugins\samplesink\plutosdroutput\%1\outputplutosdr.dll %2\plugins\samplesink From b8147ffacc8bab8f7ca563ab7d3eab6a32957f81 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 25 Feb 2018 19:31:15 +0100 Subject: [PATCH 007/956] qrtplib: draft (2) --- qrtplib/CMakeLists.txt | 23 +- qrtplib/rtppacket.cpp | 441 +++++++++++++++++++++++++++++++++++ qrtplib/rtppacket.h | 288 +++++++++++++++++++++++ qrtplib/rtppacketbuilder.cpp | 37 ++- qrtplib/rtprandom.cpp | 33 +-- qrtplib/rtprawpacket.h | 183 +++++++++++++++ qrtplib/rtpstructs.h | 130 +++++++++++ qrtplib/rtptimeutilities.cpp | 4 +- qrtplib/rtptimeutilities.h | 26 +-- 9 files changed, 1107 insertions(+), 58 deletions(-) create mode 100644 qrtplib/rtppacket.cpp create mode 100644 qrtplib/rtppacket.h create mode 100644 qrtplib/rtprawpacket.h create mode 100644 qrtplib/rtpstructs.h diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 65dcc3bb7..2de5f94ac 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -1,22 +1,25 @@ project(qrtplib) set(qrtplib_SOURCES - rtptimeutilities.cpp - rtprandomurandom.cpp - rtprandomrand48.cpp - rtprandom.cpp rtperrors.cpp + rtppacket.cpp + rtprandom.cpp + rtprandomrand48.cpp + rtprandomurandom.cpp + rtptimeutilities.cpp ) set(qrtplib_HEADERS - rtptimeutilities.h - rtprandom.h - rtprandomurandom.h - rtprandomrand48.h - rtprandom.h - rtpinternalutils.h rtperrors.h rtpdefines.h + rtpinternalutils.h + rtppacket.h + rtprandom.h + rtprandomrand48.h + rtprandomurandom.h + rtprawpacket.h + rtpstructs.h + rtptimeutilities.h ) include_directories( diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp new file mode 100644 index 000000000..15a28b8fe --- /dev/null +++ b/qrtplib/rtppacket.cpp @@ -0,0 +1,441 @@ +/* + Rewritten to fit into the Qt Network framework + Copyright (c) 2018 Edouard Griffiths, F4EXB + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtppacket.h" +#include "rtpstructs.h" +#include "rtpdefines.h" +#include "rtperrors.h" +#include "rtprawpacket.h" +#include + +#ifdef RTPDEBUG + #include +#endif // RTPDEBUG + +//#include "rtpdebug.h" + +namespace qrtplib +{ + +void RTPPacket::Clear() +{ + hasextension = false; + hasmarker = false; + numcsrcs = 0; + payloadtype = 0; + extseqnr = 0; + timestamp = 0; + ssrc = 0; + packet = 0; + payload = 0; + packetlength = 0; + payloadlength = 0; + extid = 0; + extension = 0; + extensionlength = 0; + error = 0; + externalbuffer = false; + + uint32_t endianTest32 = 1; + uint8_t *ptr = (uint8_t*) &endianTest32; + m_littleEndian = (*ptr == 1); +} + +RTPPacket::RTPPacket(RTPRawPacket &rawpack) : receivetime(rawpack.GetReceiveTime()) +{ + Clear(); + error = ParseRawPacket(rawpack); +} + +RTPPacket::RTPPacket( + uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + std::size_t maxpacksize) : receivetime(0,0) +{ + Clear(); + + error = BuildPacket( + payloadtype, + payloaddata, + payloadlen, + seqnr, + timestamp, + ssrc, + gotmarker, + numcsrcs, + csrcs, + gotextension, + extensionid, + extensionlen_numwords, + extensiondata, + 0, + maxpacksize); +} + +RTPPacket::RTPPacket( + uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + std::size_t buffersize) : receivetime(0,0) +{ + Clear(); + + if (buffer == 0) { + error = ERR_RTP_PACKET_EXTERNALBUFFERNULL; + } else if (buffersize <= 0) { + error = ERR_RTP_PACKET_ILLEGALBUFFERSIZE; + } else { + error = BuildPacket( + payloadtype, + payloaddata, + payloadlen, + seqnr, + timestamp, + ssrc, + gotmarker, + numcsrcs, + csrcs, + gotextension, + extensionid, + extensionlen_numwords, + extensiondata, + buffer, + buffersize); + } +} + +int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) +{ + uint8_t *packetbytes; + std::size_t packetlen; + uint8_t payloadtype; + RTPHeader *rtpheader; + bool marker; + int csrccount; + bool hasextension; + int payloadoffset,payloadlength; + int numpadbytes; + RTPExtensionHeader *rtpextheader; + + if (!rawpack.IsRTP()) { // If we didn't receive it on the RTP port, we'll ignore it + return ERR_RTP_PACKET_INVALIDPACKET; + } + + // The length should be at least the size of the RTP header + packetlen = rawpack.GetDataLength(); + + if (packetlen < sizeof(RTPHeader)) { + return ERR_RTP_PACKET_INVALIDPACKET; + } + + packetbytes = (uint8_t *)rawpack.GetData(); + rtpheader = (RTPHeader *)packetbytes; + + // The version number should be correct + if (rtpheader->version != RTP_VERSION) { + return ERR_RTP_PACKET_INVALIDPACKET; + } + + // We'll check if this is possibly a RTCP packet. For this to be possible + // the marker bit and payload type combined should be either an SR or RR + // identifier + marker = (rtpheader->marker == 0)?false:true; + payloadtype = rtpheader->payloadtype; + + if (marker) + { + if (payloadtype == (RTP_RTCPTYPE_SR & 127)) { // don't check high bit (this was the marker!!) + return ERR_RTP_PACKET_INVALIDPACKET; + } + if (payloadtype == (RTP_RTCPTYPE_RR & 127)) { + return ERR_RTP_PACKET_INVALIDPACKET; + } + } + + csrccount = rtpheader->csrccount; + payloadoffset = sizeof(RTPHeader)+(int)(csrccount*sizeof(uint32_t)); + + if (rtpheader->padding) // adjust payload length to take padding into account + { + numpadbytes = (int)packetbytes[packetlen-1]; // last byte contains number of padding bytes + if (numpadbytes <= 0) { + return ERR_RTP_PACKET_INVALIDPACKET; + } + } + else + { + numpadbytes = 0; + } + + hasextension = (rtpheader->extension != 0); + + if (hasextension) // got header extension + { + rtpextheader = (RTPExtensionHeader *)(packetbytes+payloadoffset); + payloadoffset += sizeof(RTPExtensionHeader); + + uint16_t exthdrlen = qToHost(rtpextheader->length); // ntohs(rtpextheader->length); + payloadoffset += ((int)exthdrlen)*sizeof(uint32_t); + } + else + { + rtpextheader = 0; + } + + payloadlength = packetlen-numpadbytes-payloadoffset; + + if (payloadlength < 0) { + return ERR_RTP_PACKET_INVALIDPACKET; + } + + // Now, we've got a valid packet, so we can create a new instance of RTPPacket + // and fill in the members + + RTPPacket::hasextension = hasextension; + + if (hasextension) + { + RTPPacket::extid = qToHost(rtpextheader->extid); // ntohs(rtpextheader->extid); + RTPPacket::extensionlength = ((int)qToHost(rtpextheader->length))*sizeof(uint32_t); // ((int)ntohs(rtpextheader->length))*sizeof(uint32_t); + RTPPacket::extension = ((uint8_t *)rtpextheader)+sizeof(RTPExtensionHeader); + } + + RTPPacket::hasmarker = marker; + RTPPacket::numcsrcs = csrccount; + RTPPacket::payloadtype = payloadtype; + + // Note: we don't fill in the EXTENDED sequence number here, since we + // don't have information about the source here. We just fill in the low + // 16 bits + RTPPacket::extseqnr = (uint32_t) qToHost(rtpheader->sequencenumber); // ntohs(rtpheader->sequencenumber); + + RTPPacket::timestamp = qToHost(rtpheader->timestamp); // ntohl(rtpheader->timestamp); + RTPPacket::ssrc = qToHost(rtpheader->ssrc); // ntohl(rtpheader->ssrc); + RTPPacket::packet = packetbytes; + RTPPacket::payload = packetbytes+payloadoffset; + RTPPacket::packetlength = packetlen; + RTPPacket::payloadlength = payloadlength; + + // We'll zero the data of the raw packet, since we're using it here now! + rawpack.ZeroData(); + + return 0; +} + +uint32_t RTPPacket::GetCSRC(int num) const +{ + if (num >= numcsrcs) { + return 0; + } + + uint8_t *csrcpos; + uint32_t *csrcval_nbo; + uint32_t csrcval_hbo; + + csrcpos = packet+sizeof(RTPHeader)+num*sizeof(uint32_t); + csrcval_nbo = (uint32_t *)csrcpos; + csrcval_hbo = qToHost(*csrcval_nbo); // ntohl(*csrcval_nbo); + return csrcval_hbo; +} + +int RTPPacket::BuildPacket( + uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + std::size_t maxsize) +{ + if (numcsrcs > RTP_MAXCSRCS) { + return ERR_RTP_PACKET_TOOMANYCSRCS; + } + + if (payloadtype > 127) { // high bit should not be used + return ERR_RTP_PACKET_BADPAYLOADTYPE; + } + if (payloadtype == 72 || payloadtype == 73) { // could cause confusion with rtcp types + return ERR_RTP_PACKET_BADPAYLOADTYPE; + } + + packetlength = sizeof(RTPHeader); + packetlength += sizeof(uint32_t)*((std::size_t)numcsrcs); + + if (gotextension) + { + packetlength += sizeof(RTPExtensionHeader); + packetlength += sizeof(uint32_t)*((size_t)extensionlen_numwords); + } + + packetlength += payloadlen; + + if (maxsize > 0 && packetlength > maxsize) + { + packetlength = 0; + return ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE; + } + + // Ok, now we'll just fill in... + + RTPHeader *rtphdr; + + if (buffer == 0) + { + packet = new uint8_t[packetlength]; + if (packet == 0) + { + packetlength = 0; + return ERR_RTP_OUTOFMEM; + } + externalbuffer = false; + } + else + { + packet = (uint8_t *)buffer; + externalbuffer = true; + } + + RTPPacket::hasmarker = gotmarker; + RTPPacket::hasextension = gotextension; + RTPPacket::numcsrcs = numcsrcs; + RTPPacket::payloadtype = payloadtype; + RTPPacket::extseqnr = (uint32_t)seqnr; + RTPPacket::timestamp = timestamp; + RTPPacket::ssrc = ssrc; + RTPPacket::payloadlength = payloadlen; + RTPPacket::extid = extensionid; + RTPPacket::extensionlength = ((std::size_t)extensionlen_numwords)*sizeof(uint32_t); + + rtphdr = (RTPHeader *)packet; + rtphdr->version = RTP_VERSION; + rtphdr->padding = 0; + if (gotmarker) + rtphdr->marker = 1; + else + rtphdr->marker = 0; + if (gotextension) + rtphdr->extension = 1; + else + rtphdr->extension = 0; + rtphdr->csrccount = numcsrcs; + rtphdr->payloadtype = payloadtype&127; // make sure high bit isn't set + rtphdr->sequencenumber = qToBigEndian(seqnr); // htons(seqnr); + rtphdr->timestamp = qToBigEndian(timestamp); // htonl(timestamp); + rtphdr->ssrc = qToBigEndian(ssrc); // htonl(ssrc); + + uint32_t *curcsrc; + int i; + curcsrc = (uint32_t *)(packet+sizeof(RTPHeader)); + + for (i = 0 ; i < numcsrcs ; i++,curcsrc++) { + *curcsrc = qToBigEndian(csrcs[i]); // htonl(csrcs[i]); + } + + payload = packet+sizeof(RTPHeader)+((std::size_t)numcsrcs)*sizeof(uint32_t); + + if (gotextension) + { + RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *)payload; + + rtpexthdr->extid = qToBigEndian(extensionid); // htons(extensionid); + rtpexthdr->length = qToBigEndian((uint16_t)extensionlen_numwords); // htons((uint16_t)extensionlen_numwords); + + payload += sizeof(RTPExtensionHeader); + memcpy(payload,extensiondata,RTPPacket::extensionlength); + + payload += RTPPacket::extensionlength; + } + + memcpy(payload,payloaddata,payloadlen); + return 0; +} + +#ifdef RTPDEBUG +void RTPPacket::Dump() +{ + int i; + + printf("Payload type: %d\n",(int)GetPayloadType()); + printf("Extended sequence number: 0x%08x\n",GetExtendedSequenceNumber()); + printf("Timestamp: 0x%08x\n",GetTimestamp()); + printf("SSRC: 0x%08x\n",GetSSRC()); + printf("Marker: %s\n",HasMarker()?"yes":"no"); + printf("CSRC count: %d\n",GetCSRCCount()); + for (i = 0 ; i < GetCSRCCount() ; i++) + printf(" CSRC[%02d]: 0x%08x\n",i,GetCSRC(i)); + printf("Payload: %s\n",GetPayloadData()); + printf("Payload length: %d\n",GetPayloadLength()); + printf("Packet length: %d\n",GetPacketLength()); + printf("Extension: %s\n",HasExtension()?"yes":"no"); + if (HasExtension()) + { + printf(" Extension ID: 0x%04x\n",GetExtensionID()); + printf(" Extension data: %s\n",GetExtensionData()); + printf(" Extension length: %d\n",GetExtensionLength()); + } +} +#endif // RTPDEBUG + +} // end namespace + diff --git a/qrtplib/rtppacket.h b/qrtplib/rtppacket.h new file mode 100644 index 000000000..6728d21e8 --- /dev/null +++ b/qrtplib/rtppacket.h @@ -0,0 +1,288 @@ +/* + Rewritten to fit into the Qt Network framework + Copyright (c) 2018 Edouard Griffiths, F4EXB + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtppacket.h + */ + +#ifndef RTPPACKET_H + +#define RTPPACKET_H + +//#include "rtpconfig.h" +//#include "rtptypes.h" +#include "rtptimeutilities.h" +//#include "rtpmemoryobject.h" +#include +#include + +namespace qrtplib +{ + +class RTPRawPacket; + +/** Represents an RTP Packet. + * The RTPPacket class can be used to parse a RTPRawPacket instance if it represents RTP data. + * The class can also be used to create a new RTP packet according to the parameters specified by + * the user. + */ +class RTPPacket +{ +public: + /** Creates an RTPPacket instance based upon the data in \c rawpack. + * Creates an RTPPacket instance based upon the data in \c rawpack. + * If successful, the data is moved from the raw packet to the RTPPacket instance. + */ + RTPPacket(RTPRawPacket &rawpack); + + /** Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. + * Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. + * If \c maxpacksize is not equal to zero, an error is generated if the total packet size would exceed + * \c maxpacksize. The arguments of the constructor are self-explanatory. Note that the size of a header + * extension is specified in a number of 32-bit words. + */ + RTPPacket( + uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + std::size_t maxpacksize); + + /** This constructor is similar to the other constructor, but here data is stored in an external buffer + * \c buffer with size \c buffersize. */ + RTPPacket(uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + std::size_t buffersize); + + virtual ~RTPPacket() + { + if (packet && !externalbuffer){ + delete[] packet; + //RTPDeleteByteArray(packet); + } + } + + /** If an error occurred in one of the constructors, this function returns the error code. */ + int GetCreationError() const + { + return error; + } + + /** Returns \c true if the RTP packet has a header extension and \c false otherwise. */ + bool HasExtension() const + { + return hasextension; + } + + /** Returns \c true if the marker bit was set and \c false otherwise. */ + bool HasMarker() const + { + return hasmarker; + } + + /** Returns the number of CSRCs contained in this packet. */ + int GetCSRCCount() const + { + return numcsrcs; + } + + /** Returns a specific CSRC identifier. + * Returns a specific CSRC identifier. The parameter \c num can go from 0 to GetCSRCCount()-1. + */ + uint32_t GetCSRC(int num) const; + + /** Returns the payload type of the packet. */ + uint8_t GetPayloadType() const + { + return payloadtype; + } + + /** Returns the extended sequence number of the packet. + * Returns the extended sequence number of the packet. When the packet is just received, + * only the low $16$ bits will be set. The high 16 bits can be filled in later. + */ + uint32_t GetExtendedSequenceNumber() const + { + return extseqnr; + } + + /** Returns the sequence number of this packet. */ + uint16_t GetSequenceNumber() const + { + return (uint16_t)(extseqnr&0x0000FFFF); + } + + /** Sets the extended sequence number of this packet to \c seq. */ + void SetExtendedSequenceNumber(uint32_t seq) + { + extseqnr = seq; + } + + /** Returns the timestamp of this packet. */ + uint32_t GetTimestamp() const + { + return timestamp; + } + + /** Returns the SSRC identifier stored in this packet. */ + uint32_t GetSSRC() const + { + return ssrc; + } + + /** Returns a pointer to the data of the entire packet. */ + uint8_t *GetPacketData() const + { + return packet; + } + + /** Returns a pointer to the actual payload data. */ + uint8_t *GetPayloadData() const + { + return payload; + } + + /** Returns the length of the entire packet. */ + std::size_t GetPacketLength() const + { + return packetlength; + } + + /** Returns the payload length. */ + std::size_t GetPayloadLength() const + { + return payloadlength; + } + + /** If a header extension is present, this function returns the extension identifier. */ + uint16_t GetExtensionID() const + { + return extid; + } + + /** Returns the length of the header extension data. */ + uint8_t *GetExtensionData() const + { + return extension; + } + + /** Returns the length of the header extension data. */ + std::size_t GetExtensionLength() const + { + return extensionlength; + } +#ifdef RTPDEBUG + void Dump(); +#endif // RTPDEBUG + + /** Returns the time at which this packet was received. + * When an RTPPacket instance is created from an RTPRawPacket instance, the raw packet's + * reception time is stored in the RTPPacket instance. This function then retrieves that + * time. + */ + RTPTime GetReceiveTime() const + { + return receivetime; + } + +private: + void Clear(); + int ParseRawPacket(RTPRawPacket &rawpack); + int BuildPacket( + uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + std::size_t maxsize); + + template + T qToHost(const T& x) const { + return m_littleEndian ? qToLittleEndian(x) : qToBigEndian(x); + } + + int error; + bool m_littleEndian; + + bool hasextension,hasmarker; + int numcsrcs; + + uint8_t payloadtype; + uint32_t extseqnr,timestamp,ssrc; + uint8_t *packet,*payload; + std::size_t packetlength,payloadlength; + + uint16_t extid; + uint8_t *extension; + std::size_t extensionlength; + + bool externalbuffer; + + RTPTime receivetime; +}; + +} // end namespace + +#endif // RTPPACKET_H + diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib/rtppacketbuilder.cpp index bc91c3583..5cee190d8 100644 --- a/qrtplib/rtppacketbuilder.cpp +++ b/qrtplib/rtppacketbuilder.cpp @@ -9,7 +9,7 @@ This library was developed at the Expertise Centre for Digital Media (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for + (http://www.uhasselt.be). The library is based upon work done for my thesis at the School for Knowledge Technology (Belgium/The Netherlands). Permission is hereby granted, free of charge, to any person obtaining a @@ -46,6 +46,21 @@ namespace qrtplib RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r) : rtprnd(r), lastwallclocktime(0,0) { init = false; + deftsset = false; + defaultpayloadtype = 0; + lastrtptimestamp = 0; + ssrc = 0; + numcsrcs = 0; + defmarkset = false; + defaultmark = false; + defaulttimestampinc = 0; + timestamp = 0; + buffer = 0; + numpackets = 0; + seqnr = 0; + numpayloadbytes = 0; + prevrtptimestamp = 0; + defptset = false; } RTPPacketBuilder::~RTPPacketBuilder() @@ -62,22 +77,22 @@ int RTPPacketBuilder::Init(std::size_t max) if (max <= 0) { return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; } - + maxpacksize = max; buffer = new uint8_t[max]; if (buffer == 0) { return ERR_RTP_OUTOFMEM; } packetlength = 0; - + CreateNewSSRC(); deftsset = false; defptset = false; defmarkset = false; - + numcsrcs = 0; - + init = true; return 0; } @@ -102,7 +117,7 @@ int RTPPacketBuilder::SetMaximumPacketSize(std::size_t max) if (newbuf == 0) { return ERR_RTP_OUTOFMEM; } - + delete[] buffer; buffer = newbuf; maxpacksize = max; @@ -135,7 +150,7 @@ int RTPPacketBuilder::DeleteCSRC(uint32_t csrc) if (!init) { return ERR_RTP_PACKBUILD_NOTINIT; } - + int i = 0; bool found = false; @@ -151,7 +166,7 @@ int RTPPacketBuilder::DeleteCSRC(uint32_t csrc) if (!found) { return ERR_RTP_PACKBUILD_CSRCNOTINLIST; } - + // move the last csrc in the place of the deleted one numcsrcs--; if (numcsrcs > 0 && numcsrcs != i) { @@ -184,13 +199,13 @@ uint32_t RTPPacketBuilder::CreateNewSSRC() uint32_t RTPPacketBuilder::CreateNewSSRC(RTPSources &sources) { bool found; - + do { ssrc = rtprnd.GetRandom32(); found = sources.GotEntry(ssrc); } while (found); - + timestamp = rtprnd.GetRandom32(); seqnr = rtprnd.GetRandom16(); @@ -273,7 +288,7 @@ int RTPPacketBuilder::PrivateBuildPacket(const void *data, std::size_t len, lastrtptimestamp = timestamp; prevrtptimestamp = timestamp; } - + numpayloadbytes += (uint32_t)p.GetPayloadLength(); numpackets++; timestamp += timestampinc; diff --git a/qrtplib/rtprandom.cpp b/qrtplib/rtprandom.cpp index 56b3bd165..1c28c5df4 100644 --- a/qrtplib/rtprandom.cpp +++ b/qrtplib/rtprandom.cpp @@ -9,7 +9,7 @@ This library was developed at the Expertise Centre for Digital Media (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for + (http://www.uhasselt.be). The library is based upon work done for my thesis at the School for Knowledge Technology (Belgium/The Netherlands). Permission is hereby granted, free of charge, to any person obtaining a @@ -35,13 +35,11 @@ #include "rtprandom.h" #include "rtprandomurandom.h" #include "rtprandomrand48.h" + #include -#ifndef WIN32 - #include -#else - #include - #include -#endif // WIN32 +#include + +#include //#include "rtpdebug.h" @@ -51,24 +49,15 @@ namespace qrtplib uint32_t RTPRandom::PickSeed() { uint32_t x; -// TODO: do it for MinGW see sdrbase/util/uid.h,cpp + x = (uint32_t) getpid(); + QDateTime currentDateTime = QDateTime::currentDateTime(); + x += currentDateTime.toTime_t(); #if defined(WIN32) - x = (uint32_t)GetCurrentProcessId(); - - FILETIME ft; - SYSTEMTIME st; - - GetSystemTime(&st); - SystemTimeToFileTime(&st,&ft); - - x += ft.dwLowDateTime; - x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); + x += QDateTime::currentMSecsSinceEpoch() % 1000; #else - x = (uint32_t)getpid(); - x += (uint32_t)time(0); x += (uint32_t)clock(); - x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); #endif + x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); return x; } @@ -82,7 +71,7 @@ RTPRandom *RTPRandom::CreateDefaultRandomNumberGenerator() delete r; rRet = new RTPRandomRand48(); } - + return rRet; } diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h new file mode 100644 index 000000000..0c18caa26 --- /dev/null +++ b/qrtplib/rtprawpacket.h @@ -0,0 +1,183 @@ +/* + Rewritten to fit into the Qt Network framework + Copyright (c) 2018 Edouard Griffiths, F4EXB + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtprawpacket.h + */ + +#ifndef RTPRAWPACKET_H + +#define RTPRAWPACKET_H + +//#include "rtpconfig.h" +#include "rtptimeutilities.h" +//#include "rtpaddress.h" +//#include "rtptypes.h" +//#include "rtpmemoryobject.h" +#include +#include + +namespace qrtplib +{ + +/** This class is used by the transmission component to store the incoming RTP and RTCP data in. */ +class RTPRawPacket +{ +public: + /** Creates an instance which stores data from \c data with length \c datalen. + * Creates an instance which stores data from \c data with length \c datalen. Only the pointer + * to the data is stored, no actual copy is made! The address from which this packet originated + * is set to \c address and the time at which the packet was received is set to \c recvtime. + * The flag which indicates whether this data is RTP or RTCP data is set to \c rtp. A memory + * manager can be installed as well. + */ + RTPRawPacket(uint8_t *data, std::size_t datalen, QHostAddress *address, RTPTime &recvtime, bool rtp); + ~RTPRawPacket(); + + /** Returns the pointer to the data which is contained in this packet. */ + uint8_t *GetData() + { + return packetdata; + } + + /** Returns the length of the packet described by this instance. */ + std::size_t GetDataLength() const + { + return packetdatalength; + } + + /** Returns the time at which this packet was received. */ + RTPTime GetReceiveTime() const + { + return receivetime; + } + + /** Returns the address stored in this packet. */ + const QHostAddress *GetSenderAddress() const + { + return senderaddress; + } + + /** Returns \c true if this data is RTP data, \c false if it is RTCP data. */ + bool IsRTP() const + { + return isrtp; + } + + /** Sets the pointer to the data stored in this packet to zero. + * Sets the pointer to the data stored in this packet to zero. This will prevent + * a \c delete call for the actual data when the destructor of RTPRawPacket is called. + * This function is used by the RTPPacket and RTCPCompoundPacket classes to obtain + * the packet data (without having to copy it) and to make sure the data isn't deleted + * when the destructor of RTPRawPacket is called. + */ + void ZeroData() + { + packetdata = 0; + packetdatalength = 0; + } + + /** Allocates a number of bytes for RTP or RTCP data using the memory manager that + * was used for this raw packet instance, can be useful if the RTPRawPacket::SetData + * function will be used. */ + uint8_t *AllocateBytes(bool isrtp, int recvlen) const; + + /** Deallocates the previously stored data and replaces it with the data that's + * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ + void SetData(uint8_t *data, std::size_t datalen); + + /** Deallocates the currently stored RTPAddress instance and replaces it + * with the one that's specified (you probably don't need this function). */ + void SetSenderAddress(QHostAddress *address); +private: + void DeleteData(); + + uint8_t *packetdata; + std::size_t packetdatalength; + RTPTime receivetime; + QHostAddress *senderaddress; + bool isrtp; +}; + +inline RTPRawPacket::RTPRawPacket( + uint8_t *data, + std::size_t datalen, + QHostAddress *address, + RTPTime &recvtime, + bool rtp): receivetime(recvtime) +{ + packetdata = data; + packetdatalength = datalen; + senderaddress = address; + isrtp = rtp; +} + +inline RTPRawPacket::~RTPRawPacket() +{ + DeleteData(); +} + +inline void RTPRawPacket::DeleteData() +{ + if (packetdata) { + delete[] packetdata; + } + + packetdata = 0; +} + +inline uint8_t *RTPRawPacket::AllocateBytes(bool isrtp __attribute__((unused)), int recvlen) const +{ + return new uint8_t[recvlen]; +} + +inline void RTPRawPacket::SetData(uint8_t *data, std::size_t datalen) +{ + if (packetdata) { + delete[] packetdata; + } + + packetdata = data; + packetdatalength = datalen; +} + +inline void RTPRawPacket::SetSenderAddress(QHostAddress *address) +{ + senderaddress = address; +} + +} // end namespace + +#endif // RTPRAWPACKET_H + diff --git a/qrtplib/rtpstructs.h b/qrtplib/rtpstructs.h new file mode 100644 index 000000000..859674fe7 --- /dev/null +++ b/qrtplib/rtpstructs.h @@ -0,0 +1,130 @@ +/* + Rewritten to fit into the Qt Network framework + Copyright (c) 2018 Edouard Griffiths, F4EXB + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpstructs.h + */ + +#ifndef RTPSTRUCTS_H + +#define RTPSTRUCTS_H + +//#include "rtpconfig.h" +//#include "rtptypes.h" + +namespace qrtplib +{ + +struct RTPHeader +{ +#ifdef RTP_BIG_ENDIAN + uint8_t version:2; + uint8_t padding:1; + uint8_t extension:1; + uint8_t csrccount:4; + + uint8_t marker:1; + uint8_t payloadtype:7; +#else // little endian + uint8_t csrccount:4; + uint8_t extension:1; + uint8_t padding:1; + uint8_t version:2; + + uint8_t payloadtype:7; + uint8_t marker:1; +#endif // RTP_BIG_ENDIAN + + uint16_t sequencenumber; + uint32_t timestamp; + uint32_t ssrc; +}; + +struct RTPExtensionHeader +{ + uint16_t extid; + uint16_t length; +}; + +struct RTPSourceIdentifier +{ + uint32_t ssrc; +}; + +struct RTCPCommonHeader +{ +#ifdef RTP_BIG_ENDIAN + uint8_t version:2; + uint8_t padding:1; + uint8_t count:5; +#else // little endian + uint8_t count:5; + uint8_t padding:1; + uint8_t version:2; +#endif // RTP_BIG_ENDIAN + + uint8_t packettype; + uint16_t length; +}; + +struct RTCPSenderReport +{ + uint32_t ntptime_msw; + uint32_t ntptime_lsw; + uint32_t rtptimestamp; + uint32_t packetcount; + uint32_t octetcount; +}; + +struct RTCPReceiverReport +{ + uint32_t ssrc; // Identifies about which SSRC's data this report is... + uint8_t fractionlost; + uint8_t packetslost[3]; + uint32_t exthighseqnr; + uint32_t jitter; + uint32_t lsr; + uint32_t dlsr; +}; + +struct RTCPSDESHeader +{ + uint8_t sdesid; + uint8_t length; +}; + +} // end namespace + +#endif // RTPSTRUCTS + diff --git a/qrtplib/rtptimeutilities.cpp b/qrtplib/rtptimeutilities.cpp index 9554d57fd..451a6d8cf 100644 --- a/qrtplib/rtptimeutilities.cpp +++ b/qrtplib/rtptimeutilities.cpp @@ -9,7 +9,7 @@ This library was developed at the Expertise Centre for Digital Media (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for + (http://www.uhasselt.be). The library is based upon work done for my thesis at the School for Knowledge Technology (Belgium/The Netherlands). Permission is hereby granted, free of charge, to any person obtaining a @@ -35,7 +35,7 @@ //#include "rtpconfig.h" #include "rtptimeutilities.h" -namespace jrtplib +namespace qrtplib { RTPTimeInitializerObject::RTPTimeInitializerObject() diff --git a/qrtplib/rtptimeutilities.h b/qrtplib/rtptimeutilities.h index 9c1078efa..ffdd93fb7 100644 --- a/qrtplib/rtptimeutilities.h +++ b/qrtplib/rtptimeutilities.h @@ -9,7 +9,7 @@ This library was developed at the Expertise Centre for Digital Media (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for + (http://www.uhasselt.be). The library is based upon work done for my thesis at the School for Knowledge Technology (Belgium/The Netherlands). Permission is hereby granted, free of charge, to any person obtaining a @@ -51,11 +51,11 @@ #define C1000000 1000000ULL #define CEPOCH 11644473600000000ULL -namespace jrtplib +namespace qrtplib { /** - * This is a simple wrapper for the most significant word (MSW) and least + * This is a simple wrapper for the most significant word (MSW) and least * significant word (LSW) of an NTP timestamp. */ class RTPNTPTime @@ -74,25 +74,25 @@ private: }; /** This class is used to specify wallclock time, delay intervals etc. - * This class is used to specify wallclock time, delay intervals etc. + * This class is used to specify wallclock time, delay intervals etc. * It stores a number of seconds and a number of microseconds. */ class RTPTime { public: - /** Returns an RTPTime instance representing the current wallclock time. - * Returns an RTPTime instance representing the current wallclock time. This is expressed + /** Returns an RTPTime instance representing the current wallclock time. + * Returns an RTPTime instance representing the current wallclock time. This is expressed * as a number of seconds since 00:00:00 UTC, January 1, 1970. */ static RTPTime CurrentTime(); /** This function waits the amount of time specified in \c delay. */ static void Wait(const RTPTime &delay); - + /** Creates an RTPTime instance representing \c t, which is expressed in units of seconds. */ RTPTime(double t); - /** Creates an instance that corresponds to \c ntptime. + /** Creates an instance that corresponds to \c ntptime. * Creates an instance that corresponds to \c ntptime. If * the conversion cannot be made, both the seconds and the * microseconds are set to zero. @@ -155,7 +155,7 @@ inline RTPTime::RTPTime(RTPNTPTime ntptime) else { uint32_t sec = ntptime.GetMSW() - RTP_NTPTIMEOFFSET; - + double x = (double)ntptime.GetLSW(); x /= (65536.0*65536.0); x *= 1000000.0; @@ -233,7 +233,7 @@ inline RTPTime RTPTime::CurrentTime() inline RTPTime RTPTime::CurrentTime() { struct timeval tv; - + gettimeofday(&tv,0); return RTPTime((uint64_t)tv.tv_sec,(uint32_t)tv.tv_usec); } @@ -260,13 +260,13 @@ inline void RTPTime::Wait(const RTPTime &delay) } inline RTPTime &RTPTime::operator-=(const RTPTime &t) -{ +{ m_t -= t.m_t; return *this; } inline RTPTime &RTPTime::operator+=(const RTPTime &t) -{ +{ m_t += t.m_t; return *this; } @@ -279,7 +279,7 @@ inline RTPNTPTime RTPTime::GetNTPTime() const uint32_t msw = sec+RTP_NTPTIMEOFFSET; uint32_t lsw; double x; - + x = microsec/1000000.0; x *= (65536.0*65536.0); lsw = (uint32_t)x; From a483b58028830fe32eccb0910c87c7a822b39a92 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 26 Feb 2018 01:02:33 +0100 Subject: [PATCH 008/956] DATV demodulator: leansdr: removed calls to exit --- .../demoddatv/leansdr/convolutional.h | 473 +++++++++------ plugins/channelrx/demoddatv/leansdr/dsp.h | 6 +- plugins/channelrx/demoddatv/leansdr/dvb.h | 66 ++- .../channelrx/demoddatv/leansdr/framework.h | 35 +- plugins/channelrx/demoddatv/leansdr/generic.h | 555 ++++++++++-------- plugins/channelrx/demoddatv/leansdr/rs.h | 445 ++++++++------ plugins/channelrx/demoddatv/leansdr/sdr.h | 23 +- plugins/channelrx/demoddatv/leansdr/viterbi.h | 479 ++++++++------- 8 files changed, 1205 insertions(+), 877 deletions(-) diff --git a/plugins/channelrx/demoddatv/leansdr/convolutional.h b/plugins/channelrx/demoddatv/leansdr/convolutional.h index 2b6e909d9..aa751dc06 100644 --- a/plugins/channelrx/demoddatv/leansdr/convolutional.h +++ b/plugins/channelrx/demoddatv/leansdr/convolutional.h @@ -1,103 +1,131 @@ #ifndef LEANSDR_CONVOLUTIONAL_H #define LEANSDR_CONVOLUTIONAL_H -namespace leansdr { +namespace leansdr +{ - // ALGEBRAIC DECONVOLUTION +// ALGEBRAIC DECONVOLUTION - // QPSK 1/2 only. - // This is a straightforward implementation, provided for reference. - // deconvol_poly2 is functionally equivalent and much faster. +// QPSK 1/2 only. +// This is a straightforward implementation, provided for reference. +// deconvol_poly2 is functionally equivalent and much faster. - template - struct deconvol_poly { +template +struct deconvol_poly +{ typedef u8 hardsymbol; // Support soft of float input - inline u8 SYMVAL(const hardsymbol *s) { return *s; } - inline u8 SYMVAL(const softsymbol *s) { return s->symbol; } + inline u8 SYMVAL(const hardsymbol *s) + { + return *s; + } + inline u8 SYMVAL(const softsymbol *s) + { + return s->symbol; + } typedef u8 decoded_byte; - deconvol_poly() : hist(0) { } + deconvol_poly() : + hist(0) + { + } // Remap and deconvolve [nb*8] symbols into [nb] bytes. // Return estimated number of bit errors. - int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) { - int nerrors = 0; - int halfway = nb / 2; - for ( ; nb--; ++pout ) { - decoded_byte byte = 0; - for ( int bit=8; bit--; ++pin) { - hist = (hist<<2) | remap[SYMVAL(pin)]; - byte = (byte<<1) | parity(hist&POLY_DECONVOL); - if ( nb < halfway ) - nerrors += parity(hist&POLY_ERRORS); - } - *pout = byte; - } - return nerrors; + int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) + { + int nerrors = 0; + int halfway = nb / 2; + for (; nb--; ++pout) + { + decoded_byte byte = 0; + for (int bit = 8; bit--; ++pin) + { + hist = (hist << 2) | remap[SYMVAL(pin)]; + byte = (byte << 1) | parity(hist & POLY_DECONVOL); + if (nb < halfway) + nerrors += parity(hist & POLY_ERRORS); + } + *pout = byte; + } + return nerrors; } - private: +private: Thist hist; - }; // deconvol_poly +}; +// deconvol_poly +// ALGEBRAIC DECONVOLUTION, OPTIMIZED - // ALGEBRAIC DECONVOLUTION, OPTIMIZED +// QPSK 1/2 only. +// Functionally equivalent to deconvol_poly, +// but processing 32 bits in parallel. - // QPSK 1/2 only. - // Functionally equivalent to deconvol_poly, - // but processing 32 bits in parallel. - - template - struct deconvol_poly2 { +template +struct deconvol_poly2 +{ typedef u8 hardsymbol; // Support instanciation of template with soft of float input - inline u8 SYMVAL(const hardsymbol *s) { return *s; } - inline u8 SYMVAL(const softsymbol *s) { return s->symbol; } + inline u8 SYMVAL(const hardsymbol *s) + { + return *s; + } + inline u8 SYMVAL(const softsymbol *s) + { + return s->symbol; + } typedef u8 decoded_byte; - deconvol_poly2() : inI(0), inQ(0) { } - - // Remap and deconvolve [nb*8] symbols into [nb] bytes. - // Return estimated number of bit errors. + deconvol_poly2() : + inI(0), inQ(0) + { + } - int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) { - if ( nb & (sizeof(Thist)-1) ) - fail("Must deconvolve sizeof(Thist) bytes at a time"); - nb /= sizeof(Thist); - unsigned long nerrors = 0; - int halfway = nb / 2; - Thist histI=inI, histQ=inQ; - for ( ; nb--; ) { - // This is where we convolve bits in parallel. - Thist wd = 0; // decoded bits - Thist we = 0; // error bits (should be 0) + // Remap and deconvolve [nb*8] symbols into [nb] bytes. + // Return estimated number of bit errors or -1 if error. + + int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) + { + if (nb & (sizeof(Thist) - 1)) { + fail("deconvol_poly2::run", "Must deconvolve sizeof(Thist) bytes at a time"); + return -1; + } + nb /= sizeof(Thist); + unsigned long nerrors = 0; + int halfway = nb / 2; + Thist histI = inI, histQ = inQ; + for (; nb--;) + { + // This is where we convolve bits in parallel. + Thist wd = 0; // decoded bits + Thist we = 0; // error bits (should be 0) #if 0 - // Trust gcc to unroll and evaluate the bit tests at compile-time. - for ( int bit=sizeof(Thist)*8; bit--; ++pin ) { - u8 iq = remap[SYMVAL(pin)]; - histI = (histI<<1) | (iq>>1); - histQ = (histQ<<1) | (iq&1); - if ( POLY_DECONVOL & ((Tpoly)2<<(2*bit)) ) wd ^= histI; - if ( POLY_DECONVOL & ((Tpoly)1<<(2*bit)) ) wd ^= histQ; - if ( POLY_ERRORS & ((Tpoly)2<<(2*bit)) ) we ^= histI; - if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; - } + // Trust gcc to unroll and evaluate the bit tests at compile-time. + for ( int bit=sizeof(Thist)*8; bit--; ++pin ) + { + u8 iq = remap[SYMVAL(pin)]; + histI = (histI<<1) | (iq>>1); + histQ = (histQ<<1) | (iq&1); + if ( POLY_DECONVOL & ((Tpoly)2<<(2*bit)) ) wd ^= histI; + if ( POLY_DECONVOL & ((Tpoly)1<<(2*bit)) ) wd ^= histQ; + if ( POLY_ERRORS & ((Tpoly)2<<(2*bit)) ) we ^= histI; + if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; + } #else - // Unroll manually. + // Unroll manually. #define LOOP(bit) { \ u8 iq = remap[SYMVAL(pin)]; \ histI = (histI<<1) | (iq>>1); \ @@ -108,150 +136,231 @@ namespace leansdr { if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; \ ++pin; \ } - // Don't shift by more than the operand width - switch ( sizeof(Thist)*8 ) { + // Don't shift by more than the operand width + switch (sizeof(Thist) * 8) + { #if 0 // Not needed yet - avoid compiler warnings - case 64: - LOOP(63); LOOP(62); LOOP(61); LOOP(60); - LOOP(59); LOOP(58); LOOP(57); LOOP(56); - LOOP(55); LOOP(54); LOOP(53); LOOP(52); - LOOP(51); LOOP(50); LOOP(49); LOOP(48); - LOOP(47); LOOP(46); LOOP(45); LOOP(44); - LOOP(43); LOOP(42); LOOP(41); LOOP(40); - LOOP(39); LOOP(38); LOOP(37); LOOP(36); - LOOP(35); LOOP(34); LOOP(33); LOOP(32); - // Fall-through + case 64: + LOOP(63); LOOP(62); LOOP(61); LOOP(60); + LOOP(59); LOOP(58); LOOP(57); LOOP(56); + LOOP(55); LOOP(54); LOOP(53); LOOP(52); + LOOP(51); LOOP(50); LOOP(49); LOOP(48); + LOOP(47); LOOP(46); LOOP(45); LOOP(44); + LOOP(43); LOOP(42); LOOP(41); LOOP(40); + LOOP(39); LOOP(38); LOOP(37); LOOP(36); + LOOP(35); LOOP(34); LOOP(33); LOOP(32); + // Fall-through #endif - case 32: - LOOP(31); LOOP(30); LOOP(29); LOOP(28); - LOOP(27); LOOP(26); LOOP(25); LOOP(24); - LOOP(23); LOOP(22); LOOP(21); LOOP(20); - LOOP(19); LOOP(18); LOOP(17); LOOP(16); - // Fall-through - case 16: - LOOP(15); LOOP(14); LOOP(13); LOOP(12); - LOOP(11); LOOP(10); LOOP( 9); LOOP( 8); - // Fall-through - case 8: - LOOP( 7); LOOP( 6); LOOP( 5); LOOP( 4); - LOOP( 3); LOOP( 2); LOOP( 1); LOOP( 0); - break; - default: - fail("Thist not supported"); - } + case 32: + LOOP(31) + ; + LOOP(30) + ; + LOOP(29) + ; + LOOP(28) + ; + LOOP(27) + ; + LOOP(26) + ; + LOOP(25) + ; + LOOP(24) + ; + LOOP(23) + ; + LOOP(22) + ; + LOOP(21) + ; + LOOP(20) + ; + LOOP(19) + ; + LOOP(18) + ; + LOOP(17) + ; + LOOP(16) + ; + // Fall-through + case 16: + LOOP(15) + ; + LOOP(14) + ; + LOOP(13) + ; + LOOP(12) + ; + LOOP(11) + ; + LOOP(10) + ; + LOOP(9) + ; + LOOP(8) + ; + // Fall-through + case 8: + LOOP(7) + ; + LOOP(6) + ; + LOOP(5) + ; + LOOP(4) + ; + LOOP(3) + ; + LOOP(2) + ; + LOOP(1) + ; + LOOP(0) + ; + break; + default: + fail("deconvol_poly2::run", "Thist not supported (1)"); + return -1; + } #undef LOOP #endif - switch ( sizeof(Thist)*8 ) { + switch (sizeof(Thist) * 8) + { #if 0 // Not needed yet - avoid compiler warnings - case 64: - *pout++ = wd >> 56; - *pout++ = wd >> 48; - *pout++ = wd >> 40; - *pout++ = wd >> 32; - // Fall-through + case 64: + *pout++ = wd >> 56; + *pout++ = wd >> 48; + *pout++ = wd >> 40; + *pout++ = wd >> 32; + // Fall-through #endif - case 32: - *pout++ = wd >> 24; - *pout++ = wd >> 16; - // Fall-through - case 16: - *pout++ = wd >> 8; - // Fall-through - case 8: - *pout++ = wd; - break; - default: - fail("Thist not supported"); - } - // Count errors when the shift registers are full - if ( nb < halfway ) nerrors += hamming_weight(we); - } - inI = histI; - inQ = histQ; - return nerrors; + case 32: + *pout++ = wd >> 24; + *pout++ = wd >> 16; + // Fall-through + case 16: + *pout++ = wd >> 8; + // Fall-through + case 8: + *pout++ = wd; + break; + default: + fail("deconvol_poly2::run", "Thist not supported (2)"); + return -1; + } + // Count errors when the shift registers are full + if (nb < halfway) + nerrors += hamming_weight(we); + } + inI = histI; + inQ = histQ; + return nerrors; } - private: +private: Thist inI, inQ; - }; // deconvol_poly2 +}; +// deconvol_poly2 +// CONVOLUTIONAL ENCODER - // CONVOLUTIONAL ENCODER +// QPSK 1/2 only. - // QPSK 1/2 only. - - template - struct convol_poly2 { +template +struct convol_poly2 +{ typedef u8 uncoded_byte; typedef u8 hardsymbol; - convol_poly2() : hist(0) { } - + convol_poly2() : + hist(0) + { + } + // Convolve [count] bytes into [count*8] symbols, and remap. - void run(const uncoded_byte *pin, const u8 remap[], - hardsymbol *pout, int count) { - for ( ; count--; ++pin ) { - uncoded_byte b = *pin; - for ( int bit=8; bit--; ++pout ) { - hist = (hist>>1) | (((b>>bit)&1)<<6); - u8 s = (parity(hist&POLY1)<<1) | parity(hist&POLY2); - *pout = remap[s]; - } - } + void run(const uncoded_byte *pin, const u8 remap[], hardsymbol *pout, int count) + { + for (; count--; ++pin) + { + uncoded_byte b = *pin; + for (int bit = 8; bit--; ++pout) + { + hist = (hist >> 1) | (((b >> bit) & 1) << 6); + u8 s = (parity(hist & POLY1) << 1) | parity(hist & POLY2); + *pout = remap[s]; + } + } } - private: +private: Thist hist; - }; // convol_poly2 +}; +// convol_poly2 - // Generic BPSK..256QAM and puncturing +// Generic BPSK..256QAM and puncturing - template - struct convol_multipoly { +template +struct convol_multipoly +{ typedef u8 uncoded_byte; typedef u8 hardsymbol; int bits_in, bits_out, bps; const Thist *polys; // [bits_out] - convol_multipoly() - : bits_in(0), bits_out(0), bps(0), - hist(0), nhist(0), sersymb(0), nsersymb(0) - { } - - void encode(const uncoded_byte *pin, hardsymbol *pout, int count) { - if ( !bits_in || !bits_out || !bps ) - fatal("convol_multipoly not configured"); - hardsymbol symbmask = (1<>1) | ((Thist)((b>>bit)&1)<<(HISTSIZE-1)); - ++nhist; - if ( nhist == bits_in ) { - for ( int p=0; p= bps ) { - hardsymbol s = (sersymb >> (nsersymb-bps)) & symbmask; - *pout++ = s; - nsersymb -= bps; - } - } - } - } - // Ensure deterministic output size - // TBD We can relax this - if ( nhist || nsersymb ) fatal("partial run"); + convol_multipoly() : + bits_in(0), bits_out(0), bps(0), polys(0), hist(0), nhist(0), sersymb(0), nsersymb(0) + { } - private: + + void encode(const uncoded_byte *pin, hardsymbol *pout, int count) + { + if (!bits_in || !bits_out || !bps) + { + fatal("leansdr::convol_multipoly::encode: convol_multipoly not configured"); + return; + } + hardsymbol symbmask = (1 << bps) - 1; + for (; count--; ++pin) + { + uncoded_byte b = *pin; + for (int bit = 8; bit--;) + { + hist = (hist >> 1) | ((Thist) ((b >> bit) & 1) << (HISTSIZE - 1)); + ++nhist; + if (nhist == bits_in) + { + for (int p = 0; p < bits_out; ++p) + { + int b = parity((Thist) (hist & polys[p])); + sersymb = (sersymb << 1) | b; + } + nhist = 0; + nsersymb += bits_out; + while (nsersymb >= bps) + { + hardsymbol s = (sersymb >> (nsersymb - bps)) & symbmask; + *pout++ = s; + nsersymb -= bps; + } + } + } + } + // Ensure deterministic output size + // TBD We can relax this + if (nhist || nsersymb) { + fatal("leansdr::convol_multipoly::encode: partial run"); + } + } +private: Thist hist; int nhist; Thist sersymb; int nsersymb; - }; // convol_multipoly +}; +// convol_multipoly -} // namespace +}// namespace #endif // LEANSDR_CONVOLUTIONAL_H diff --git a/plugins/channelrx/demoddatv/leansdr/dsp.h b/plugins/channelrx/demoddatv/leansdr/dsp.h index e5a97b99e..f790212b8 100644 --- a/plugins/channelrx/demoddatv/leansdr/dsp.h +++ b/plugins/channelrx/demoddatv/leansdr/dsp.h @@ -362,8 +362,10 @@ struct fir_resampler: runnable tap_multiplier(1), freq_tol(0.1) { - if (decim != 1) - fail("fir_resampler: decim not implemented"); // TBD + if (decim != 1) { + fail("fir_resampler::fir_resampler", "decim not implemented"); // TBD + return; + } shifted_coeffs = new T[ncoeffs]; set_freq(0); } diff --git a/plugins/channelrx/demoddatv/leansdr/dvb.h b/plugins/channelrx/demoddatv/leansdr/dvb.h index 373ad893c..4f4957445 100644 --- a/plugins/channelrx/demoddatv/leansdr/dvb.h +++ b/plugins/channelrx/demoddatv/leansdr/dvb.h @@ -59,7 +59,8 @@ inline cstln_lut<256> * make_dvbs2_constellation(cstln_lut<256>::predef c, gamma1 = 2.57; break; default: - fail("Code rate not supported with APSK16"); + fail("cstln_lut<256>::make_dvbs2_constellation", "Code rate not supported with APSK16"); + return 0; } break; case cstln_lut<256>::APSK32: @@ -87,7 +88,8 @@ inline cstln_lut<256> * make_dvbs2_constellation(cstln_lut<256>::predef c, gamma2 = 4.30; break; default: - fail("Code rate not supported with APSK32"); + fail("cstln_lut<256>::make_dvbs2_constellation", "Code rate not supported with APSK32"); + return 0; } break; case cstln_lut<256>::APSK64E: @@ -213,7 +215,10 @@ struct deconvol_sync: runnable void next_sync() { if (fastlock) - fail("Bug: next_sync() called with fastlock"); + { + fail("deconvol_sync::next_sync", "Bug: next_sync() called with fastlock"); + return; + } ++locked; if (locked == &syncs[NSYNCS]) { @@ -325,7 +330,10 @@ private: if (d == 0x00000000fb11d640LL) d2 = 0x00fbea3c8679c980LL; if (d2 == d) - fail("Alt polynomial not provided"); + { + fail("deconvol_sync::inverse_convolution", "Alt polynomial not provided"); + return; + } deconv2[b] = d2; } @@ -346,17 +354,27 @@ private: int expect = (b == i) ? 1 : 0; int d = parity(iq & deconv[b]); if (d != expect) - fail("Failed to inverse convolutional coding"); + { + fail("deconvol_sync::inverse_convolution", "Failed to inverse convolutional coding"); + } int d2 = parity(iq & deconv2[b]); if (d2 != expect) - fail("Failed to inverse convolutional coding (alt)"); + { + fail("deconvol_sync::inverse_convolution", "Failed to inverse convolutional coding (alt)"); + } } if (traceback > sizeof(iq_t) * 8) - fail("Bug: traceback exceeds register size"); + { + fail("deconvol_sync::inverse_convolution", "Bug: traceback exceeds register size"); + } if (log2(deconv[b]) + 1 > traceback) - fail("traceback insufficient for deconvolution"); + { + fail("deconvol_sync::inverse_convolution", "traceback insufficient for deconvolution"); + } if (log2(deconv2[b]) + 1 > traceback) - fail("traceback insufficient for deconvolution (alt)"); + { + fail("deconvol_sync::inverse_convolution", "traceback insufficient for deconvolution (alt)"); + } } } @@ -668,14 +686,20 @@ struct dvb_convol: runnable { fec_spec *fs = &fec_specs[fec]; if (!fs->bits_in) - fail("Unexpected FEC"); + { + fail("dvb_convol::dvb_convol", "Unexpected FEC"); + return; + } convol.bits_in = fs->bits_in; convol.bits_out = fs->bits_out; convol.polys = fs->polys; convol.bps = bits_per_symbol; // FEC must output a whole number of IQ symbols if (convol.bits_out % convol.bps) - fail("Code rate not suitable for this constellation"); + { + fail("dvb_convol::dvb_convol", "Code rate not suitable for this constellation"); + return; + } } void run() @@ -1187,7 +1211,10 @@ struct rs_decoder: runnable if (sizeof(Tbyte) == 1) memcpy(pout, pin, SIZE_TSPACKET); else - fail("Erasures not implemented"); + { + fail("rs_decoder::run", "Erasures not implemented"); + return; + } u8 synd[16]; bool corrupted = rs.syndromes(pin, synd); @@ -1448,7 +1475,10 @@ public: { // Sanity check: FEC block size must be a multiple of label size. int symbols_per_block = fec->bits_out / bits_per_symbol; if (bits_per_symbol * symbols_per_block != fec->bits_out) - fail("Code rate not suitable for this constellation"); + { + fail("viterbi_sync::viterbi_sync", "Code rate not suitable for this constellation"); + return; + } } int nconj; switch (cstln->nsymbols) @@ -1554,7 +1584,10 @@ public: syncs[s].dec = new dvb_dec_78(trell); } else - fail("CR not supported"); + { + fail("viterbi_sync::viterbi_sync", "CR not supported"); + return; + } } @@ -1640,7 +1673,10 @@ public: } // chunk_size in.read(chunk_size * nshifts); if (nout) - fail("overlapping out"); + { + fail("viterbi_sync::run", "overlapping out"); + return; + } if (!resync_phase) { // Switch to another decoder ? diff --git a/plugins/channelrx/demoddatv/leansdr/framework.h b/plugins/channelrx/demoddatv/leansdr/framework.h index 07e929b49..e705b2db6 100644 --- a/plugins/channelrx/demoddatv/leansdr/framework.h +++ b/plugins/channelrx/demoddatv/leansdr/framework.h @@ -13,13 +13,11 @@ namespace leansdr inline void fatal(const char *s) { perror(s); - exit(1); } -inline void fail(const char *s) +inline void fail(const char *f, const char *s) { - fprintf(stderr, "leansdr::fail: %s\n", s); - exit(1); + fprintf(stderr, "leansdr::%s: %s\n", f, s); } ////////////////////////////////////////////////////////////////////// @@ -114,16 +112,19 @@ struct scheduler void add_pipe(pipebuf_common *p) { - if (npipes == MAX_PIPES) { - fail("MAX_PIPES"); + if (npipes == MAX_PIPES) + { + fail("scheduler::add_pipe", "MAX_PIPES"); + return; } pipes[npipes++] = p; } void add_runnable(runnable_common *r) { - if (nrunnables == MAX_RUNNABLES) { - fail("MAX_RUNNABLES"); + if (nrunnables == MAX_RUNNABLES) + { + fail("scheduler::add_runnable", "MAX_RUNNABLES"); } runnables[nrunnables++] = r; } @@ -211,8 +212,10 @@ struct pipebuf: pipebuf_common int add_reader() { - if (nrd == MAX_READERS) { - fail("too many readers"); + if (nrd == MAX_READERS) + { + fail("pipebuf::add_reader", "too many readers"); + return nrd; } rds[nrd] = wr; return nrd++; @@ -295,8 +298,8 @@ struct pipewriter { if (buf.end < buf.wr) { - fprintf(stderr, "leansdr::pipewriter::writable: Bug: overflow to %s\n", buf.name); - exit(1); + fprintf(stderr, "leansdr::pipewriter::writable: overflow in %s buffer\n", buf.name); + return 0; } unsigned long delta = buf.end - buf.wr; @@ -317,8 +320,8 @@ struct pipewriter { if (buf.wr + n > buf.end) { - fprintf(stderr, "leansdr::pipewriter::written: Bug: overflow to %s\n", buf.name); - exit(1); + fprintf(stderr, "leansdr::pipewriter::written: overflow in %s buffer\n", buf.name); + return; } buf.wr += n; buf.total_written += n; @@ -378,8 +381,8 @@ struct pipereader { if (buf.rds[id] + n > buf.wr) { - fprintf(stderr, "leansdr::pipereader::read: Bug: underflow from %s\n", buf.name); - exit(1); + fprintf(stderr, "leansdr::pipereader::read: underflow in %s buffer\n", buf.name); + return; } buf.rds[id] += n; buf.total_read += n; diff --git a/plugins/channelrx/demoddatv/leansdr/generic.h b/plugins/channelrx/demoddatv/leansdr/generic.h index 2a4329f54..eb44ac05b 100644 --- a/plugins/channelrx/demoddatv/leansdr/generic.h +++ b/plugins/channelrx/demoddatv/leansdr/generic.h @@ -6,7 +6,8 @@ #include "leansdr/math.h" -namespace leansdr { +namespace leansdr +{ ////////////////////////////////////////////////////////////////////// // Simple blocks @@ -16,103 +17,144 @@ namespace leansdr { // If the file descriptor is seekable, data can be looped. template -struct file_reader : runnable { - file_reader(scheduler *sch, int _fdin, pipebuf &_out) - : runnable(sch, _out.name), - loop(false), - fdin(_fdin), out(_out) - { - } - void run() { - size_t size = out.writable() * sizeof(T); - if ( ! size ) return; - - again: - ssize_t nr = read(fdin, out.wr(), size); - if ( nr < 0 ) fatal("read"); - if ( ! nr ) { - if ( ! loop ) return; - if ( sch->debug ) fprintf(stderr, "%s looping\n", name); - off_t res = lseek(fdin, 0, SEEK_SET); - if ( res == (off_t)-1 ) fatal("lseek"); - goto again; +struct file_reader: runnable +{ + file_reader(scheduler *sch, int _fdin, pipebuf &_out) : + runnable(sch, _out.name), loop(false), fdin(_fdin), out(_out) + { } + void run() + { + size_t size = out.writable() * sizeof(T); + if (!size) + return; - // Always stop at element boundary (may block) - size_t partial = nr % sizeof(T); - size_t remain = partial ? sizeof(T)-partial : 0; - while ( remain ) { - if ( sch->debug ) fprintf(stderr, "+"); - ssize_t nr2 = read(fdin, (char*)out.wr()+nr, remain); - if ( nr2 <= 0 ) fatal("partial read"); - nr += nr2; - remain -= nr2; + again: ssize_t nr = read(fdin, out.wr(), size); + if (nr < 0) + { + fatal("leansdr::file_reader::run: read"); + return; + } + if (!nr) + { + if (!loop) + return; + if (sch->debug) + fprintf(stderr, "leansdr::file_reader::run: %s looping\n", name); + off_t res = lseek(fdin, 0, SEEK_SET); + if (res == (off_t) -1) + { + fatal("leansdr::file_reader::run: lseek"); + return; + } + goto again; + } + + // Always stop at element boundary (may block) + size_t partial = nr % sizeof(T); + size_t remain = partial ? sizeof(T) - partial : 0; + while (remain) + { + if (sch->debug) + fprintf(stderr, "+"); + ssize_t nr2 = read(fdin, (char*) out.wr() + nr, remain); + if (nr2 <= 0) + { + fatal("leansdr::file_reader::run: partial read"); + return; + } + nr += nr2; + remain -= nr2; + } + + out.written(nr / sizeof(T)); } - - out.written(nr / sizeof(T)); - } - bool loop; + bool loop; private: - int fdin; - pipewriter out; + int fdin; + pipewriter out; }; // [file_writer] writes raw data from a [pipebuf] to a file descriptor. template -struct file_writer : runnable { - file_writer(scheduler *sch, pipebuf &_in, int _fdout) : - runnable(sch, _in.name), - in(_in), fdout(_fdout) { - } - void run() { - int size = in.readable() * sizeof(T); - if ( ! size ) return; - int nw = write(fdout, in.rd(), size); - if ( ! nw ) fatal("pipe"); - if ( nw < 0 ) fatal("write"); - if ( nw % sizeof(T) ) fatal("partial write"); - in.read(nw/sizeof(T)); - } +struct file_writer: runnable +{ + file_writer(scheduler *sch, pipebuf &_in, int _fdout) : + runnable(sch, _in.name), in(_in), fdout(_fdout) + { + } + void run() + { + int size = in.readable() * sizeof(T); + if (!size) + return; + int nw = write(fdout, in.rd(), size); + if (!nw) + { + fatal("leansdr::file_writer::run: pipe"); + return; + } + if (nw < 0) + { + fatal("leansdr::file_writer::run: write"); + return; + } + if (nw % sizeof(T)) + { + fatal("leansdr::file_writer::run:partial write"); + return; + } + in.read(nw / sizeof(T)); + } private: - pipereader in; - int fdout; + pipereader in; + int fdout; }; // [file_printer] writes data from a [pipebuf] to a file descriptor, // with printf-style formatting and optional scaling. template -struct file_printer : runnable { - file_printer(scheduler *sch, const char *_format, - pipebuf &_in, int _fdout, - int _decimation=1) : - runnable(sch, _in.name), - scale(1), decimation(_decimation), - in(_in), format(_format), fdout(_fdout), phase(0) { - } - void run() { - int n = in.readable(); - T *pin=in.rd(), *pend=pin+n; - for ( ; pin= decimation ) { - phase -= decimation; - char buf[256]; - int len = snprintf(buf, sizeof(buf), format, (*pin)*scale); - if ( len < 0 ) fatal("obsolete glibc"); - int nw = write(fdout, buf, len); - if ( nw != len ) fatal("partial write"); - } +struct file_printer: runnable +{ + file_printer(scheduler *sch, const char *_format, pipebuf &_in, int _fdout, int _decimation = 1) : + runnable(sch, _in.name), scale(1), decimation(_decimation), in(_in), format(_format), fdout(_fdout), phase(0) + { } - in.read(n); - } - T scale; - int decimation; + void run() + { + int n = in.readable(); + T *pin = in.rd(), *pend = pin + n; + for (; pin < pend; ++pin) + { + if (++phase >= decimation) + { + phase -= decimation; + char buf[256]; + int len = snprintf(buf, sizeof(buf), format, (*pin) * scale); + if (len < 0) + { + fatal("leansdr::file_printer::run: obsolete glibc"); + return; + } + int nw = write(fdout, buf, len); + if (nw != len) + { + fatal("leansdr::file_printer::run: partial write"); + return; + } + } + } + in.read(n); + } + T scale; + int decimation; private: - pipereader in; - const char *format; - int fdout; - int phase; + pipereader in; + const char *format; + int fdout; + int phase; }; // [file_carrayprinter] writes all data available from a [pipebuf] @@ -120,229 +162,246 @@ private: // Special case for complex. template -struct file_carrayprinter : runnable { - file_carrayprinter(scheduler *sch, - const char *_head, - const char *_format, - const char *_sep, - const char *_tail, - pipebuf< complex > &_in, int _fdout) : - runnable(sch, _in.name), - scale(1), fixed_size(0), in(_in), - head(_head), format(_format), sep(_sep), tail(_tail), - fout(fdopen(_fdout,"w")) { - } - void run() { - int n, nmin = fixed_size ? fixed_size : 1; - while ( (n=in.readable()) >= nmin ) { - if ( fixed_size ) n = fixed_size; - if ( fout ) { - fprintf(fout, head, n); - complex *pin = in.rd(); - for ( int i=0; i > &_in, int _fdout) : + runnable(sch, _in.name), scale(1), fixed_size(0), in(_in), head(_head), format(_format), sep(_sep), tail(_tail), fout(fdopen(_fdout, "w")) + { } - } - T scale; - int fixed_size; // Number of elements per batch, or 0. + void run() + { + int n, nmin = fixed_size ? fixed_size : 1; + while ((n = in.readable()) >= nmin) + { + if (fixed_size) + n = fixed_size; + if (fout) + { + fprintf(fout, head, n); + complex *pin = in.rd(); + for (int i = 0; i < n; ++i) + { + if (i) + fprintf(fout, "%s", sep); + fprintf(fout, format, pin[i].re * scale, pin[i].im * scale); + } + fprintf(fout, "%s", tail); + } + fflush(fout); + in.read(n); + } + } + T scale; + int fixed_size; // Number of elements per batch, or 0. private: - pipereader< complex > in; - const char *head, *format, *sep, *tail; - FILE *fout; + pipereader > in; + const char *head, *format, *sep, *tail; + FILE *fout; }; template -struct file_vectorprinter : runnable { - file_vectorprinter(scheduler *sch, - const char *_head, - const char *_format, - const char *_sep, - const char *_tail, - pipebuf &_in, int _fdout) : - runnable(sch, _in.name), scale(1), in(_in), - head(_head), format(_format), sep(_sep), tail(_tail) { - fout = fdopen(_fdout,"w"); - if ( ! fout ) fatal("fdopen"); - } - void run() { - while ( in.readable() >= 1 ) { - fprintf(fout, head, N); - T (*pin)[N] = in.rd(); - for ( int i=0; i &_in, int _fdout) : + runnable(sch, _in.name), scale(1), in(_in), head(_head), format(_format), sep(_sep), tail(_tail) + { + fout = fdopen(_fdout, "w"); + if (!fout) + { + fatal("leansdr::file_vectorprinter::file_vectorprinter: fdopen"); + } } - fflush(fout); - } - T scale; + void run() + { + while (in.readable() >= 1) + { + fprintf(fout, head, N); + T (*pin)[N] = in.rd(); + for (int i = 0; i < N; ++i) + { + if (i) + fprintf(fout, "%s", sep); + fprintf(fout, format, (*pin)[i] * scale); + } + fprintf(fout, "%s", tail); + in.read(1); + } + fflush(fout); + } + T scale; private: - pipereader in; - const char *head, *format, *sep, *tail; - FILE *fout; + pipereader in; + const char *head, *format, *sep, *tail; + FILE *fout; }; // [itemcounter] writes the number of input items to the output [pipebuf]. // [Tout] must be a numeric type. template -struct itemcounter : runnable { - itemcounter(scheduler *sch, pipebuf &_in, pipebuf &_out) - : runnable(sch, "itemcounter"), - in(_in), out(_out) { - } - void run() { - if ( out.writable() < 1 ) return; - unsigned long count = in.readable(); - if ( ! count ) return; - out.write(count); - in.read(count); - } +struct itemcounter: runnable +{ + itemcounter(scheduler *sch, pipebuf &_in, pipebuf &_out) : + runnable(sch, "itemcounter"), in(_in), out(_out) + { + } + void run() + { + if (out.writable() < 1) + return; + unsigned long count = in.readable(); + if (!count) + return; + out.write(count); + in.read(count); + } private: - pipereader in; - pipewriter out; + pipereader in; + pipewriter out; }; // [decimator] forwards 1 in N sample. template -struct decimator : runnable { - unsigned int d; +struct decimator: runnable +{ + unsigned int d; - decimator(scheduler *sch, int _d, pipebuf &_in, pipebuf &_out) - : runnable(sch, "decimator"), - d(_d), - in(_in), out(_out) { - } - void run() { - unsigned long count = min(in.readable()/d, out.writable()); - T *pin=in.rd(), *pend=pin+count*d, *pout=out.wr(); - for ( ; pin &_in, pipebuf &_out) : + runnable(sch, "decimator"), d(_d), in(_in), out(_out) + { + } + void run() + { + unsigned long count = min(in.readable() / d, out.writable()); + T *pin = in.rd(), *pend = pin + count * d, *pout = out.wr(); + for (; pin < pend; pin += d, ++pout) + *pout = *pin; + in.read(count * d); + out.written(count); + } private: - pipereader in; - pipewriter out; + pipereader in; + pipewriter out; }; - // [rate_estimator] accumulates counts of two quantities - // and periodically outputs their ratio. +// [rate_estimator] accumulates counts of two quantities +// and periodically outputs their ratio. - template - struct rate_estimator : runnable { +template +struct rate_estimator: runnable +{ int sample_size; - rate_estimator(scheduler *sch, - pipebuf &_num, pipebuf &_den, - pipebuf &_rate) - : runnable(sch, "rate_estimator"), - sample_size(10000), - num(_num), den(_den), rate(_rate), - acc_num(0), acc_den(0) { + rate_estimator(scheduler *sch, pipebuf &_num, pipebuf &_den, pipebuf &_rate) : + runnable(sch, "rate_estimator"), sample_size(10000), num(_num), den(_den), rate(_rate), acc_num(0), acc_den(0) + { } - - void run() { - if ( rate.writable() < 1 ) return; - int count = min(num.readable(), den.readable()); - int *pnum=num.rd(), *pden=den.rd(); - for ( int n=count; n--; ++pnum,++pden ) { - acc_num += *pnum; - acc_den += *pden; - } - num.read(count); - den.read(count); - if ( acc_den >= sample_size ) { - rate.write((float)acc_num / acc_den); - acc_num = acc_den = 0; - } + + void run() + { + if (rate.writable() < 1) + return; + int count = min(num.readable(), den.readable()); + int *pnum = num.rd(), *pden = den.rd(); + for (int n = count; n--; ++pnum, ++pden) + { + acc_num += *pnum; + acc_den += *pden; + } + num.read(count); + den.read(count); + if (acc_den >= sample_size) + { + rate.write((float) acc_num / acc_den); + acc_num = acc_den = 0; + } } - - private: + +private: pipereader num, den; pipewriter rate; T acc_num, acc_den; - }; +}; +// SERIALIZER - // SERIALIZER - - template - struct serializer : runnable { - serializer(scheduler *sch, pipebuf &_in, pipebuf &_out) - : nin(max((size_t)1,sizeof(Tin)/sizeof(Tout))), - nout(max((size_t)1,sizeof(Tout)/sizeof(Tin))), - in(_in), out(_out,nout) +template +struct serializer: runnable +{ + serializer(scheduler *sch, pipebuf &_in, pipebuf &_out) : + nin(max((size_t) 1, sizeof(Tin) / sizeof(Tout))), nout(max((size_t) 1, sizeof(Tout) / sizeof(Tin))), in(_in), out(_out, nout) { - if ( nin*sizeof(Tin) != nout*sizeof(Tout) ) - fail("serializer: incompatible sizes"); + if (nin * sizeof(Tin) != nout * sizeof(Tout)) + { + fail("serializer::serializer", "incompatible sizes"); + return; + } } - void run() { - while ( in.readable()>=nin && out.writable()>=nout ) { - memcpy(out.wr(), in.rd(), nout*sizeof(Tout)); - in.read(nin); - out.written(nout); - } + void run() + { + while (in.readable() >= nin && out.writable() >= nout) + { + memcpy(out.wr(), in.rd(), nout * sizeof(Tout)); + in.read(nin); + out.written(nout); + } } - private: +private: int nin, nout; pipereader in; pipewriter out; - }; // serializer +}; +// serializer +// [buffer_reader] reads from a user-supplied buffer. - // [buffer_reader] reads from a user-supplied buffer. - - template - struct buffer_reader : runnable { - buffer_reader(scheduler *sch, T *_data, int _count, pipebuf &_out) - : runnable(sch, "buffer_reader"), - data(_data), count(_count), out(_out), pos(0) { +template +struct buffer_reader: runnable +{ + buffer_reader(scheduler *sch, T *_data, int _count, pipebuf &_out) : + runnable(sch, "buffer_reader"), data(_data), count(_count), out(_out), pos(0) + { } - void run() { - int n = min(out.writable(), (unsigned long)(count-pos)); - memcpy(out.wr(), &data[pos], n*sizeof(T)); - pos += n; - out.written(n); + void run() + { + int n = min(out.writable(), (unsigned long) (count - pos)); + memcpy(out.wr(), &data[pos], n * sizeof(T)); + pos += n; + out.written(n); } - private: +private: T *data; int count; pipewriter out; int pos; - }; // buffer_reader +}; +// buffer_reader +// [buffer_writer] writes to a user-supplied buffer. - // [buffer_writer] writes to a user-supplied buffer. - - template - struct buffer_writer : runnable { - buffer_writer(scheduler *sch, pipebuf &_in, T *_data, int _count) - : runnable(sch, "buffer_reader"), - in(_in), data(_data), count(_count), pos(0) { +template +struct buffer_writer: runnable +{ + buffer_writer(scheduler *sch, pipebuf &_in, T *_data, int _count) : + runnable(sch, "buffer_reader"), in(_in), data(_data), count(_count), pos(0) + { } - void run() { - int n = min(in.readable(), (unsigned long)(count-pos)); - memcpy(&data[pos], in.rd(), n*sizeof(T)); - in.read(n); - pos += n; + void run() + { + int n = min(in.readable(), (unsigned long) (count - pos)); + memcpy(&data[pos], in.rd(), n * sizeof(T)); + in.read(n); + pos += n; } - private: +private: pipereader in; T *data; int count; int pos; - }; // buffer_writer +}; +// buffer_writer -} // namespace +}// namespace #endif // LEANSDR_GENERIC_H diff --git a/plugins/channelrx/demoddatv/leansdr/rs.h b/plugins/channelrx/demoddatv/leansdr/rs.h index fa68a4d06..0df614409 100644 --- a/plugins/channelrx/demoddatv/leansdr/rs.h +++ b/plugins/channelrx/demoddatv/leansdr/rs.h @@ -5,88 +5,118 @@ #define DEBUG_RS 0 -namespace leansdr { +namespace leansdr +{ - // Finite group GF(2^N). +// Finite group GF(2^N). - // GF(2) is the ring ({0,1},+,*). - // GF(2)[X] is the ring of polynomials with coefficients in GF(2). - // P(X) is an irreducible polynomial of GF(2)[X]. - // N is the degree of P(x). - // P is the bitfield representation of P(X), with degree 0 at LSB. - // GF(2)[X]/(P) is GF(2)[X] modulo P(X). - // (GF(2)[X]/(P), +) is a group with 2^N elements. - // (GF(2)[X]/(P)*, *) is a group with 2^N-1 elements. - // (GF(2)[X]/(P), +, *) is a field with 2^N elements, noted GF(2^N). - // Te is a C++ integer type for representing elements of GF(2^N). - // "0" is 0 - // "1" is 1 - // "2" is X - // "3" is X+1 - // "4" is X^2 - // Tp is a C++ integer type for representing P(X) (1 bit larger than Te). - // ALPHA is a primitive element of GF(2^N). Usually "2"=[X] is chosen. +// GF(2) is the ring ({0,1},+,*). +// GF(2)[X] is the ring of polynomials with coefficients in GF(2). +// P(X) is an irreducible polynomial of GF(2)[X]. +// N is the degree of P(x). +// P is the bitfield representation of P(X), with degree 0 at LSB. +// GF(2)[X]/(P) is GF(2)[X] modulo P(X). +// (GF(2)[X]/(P), +) is a group with 2^N elements. +// (GF(2)[X]/(P)*, *) is a group with 2^N-1 elements. +// (GF(2)[X]/(P), +, *) is a field with 2^N elements, noted GF(2^N). +// Te is a C++ integer type for representing elements of GF(2^N). +// "0" is 0 +// "1" is 1 +// "2" is X +// "3" is X+1 +// "4" is X^2 +// Tp is a C++ integer type for representing P(X) (1 bit larger than Te). +// ALPHA is a primitive element of GF(2^N). Usually "2"=[X] is chosen. - template - struct gf2x_p { - gf2x_p() { - if ( ALPHA != 2 ) fail("alpha!=2 not implemented"); - // Precompute log and exp tables. - Tp alpha_i = 1; - for ( Tp i=0; i<(1< +struct gf2x_p +{ + gf2x_p() + { + if (ALPHA != 2) + { + fail("gf2x_p::gf2x_p", "alpha!=2 not implemented"); + return; + } + // Precompute log and exp tables. + Tp alpha_i = 1; + for (Tp i = 0; i < (1 << N); ++i) + { + lut_exp[i] = alpha_i; + lut_exp[((1 << N) - 1) + i] = alpha_i; + lut_log[alpha_i] = i; + alpha_i <<= 1; // Multiply by alpha=[X] i.e. increase degrees + if (alpha_i & (1 << N)) + alpha_i ^= P; // Modulo P iteratively + } } static const Te alpha = ALPHA; - inline Te add(Te x, Te y) { return x ^ y; } // Addition modulo 2 - inline Te sub(Te x, Te y) { return x ^ y; } // Subtraction modulo 2 - inline Te mul(Te x, Te y) { - if ( !x || !y ) return 0; - return lut_exp[lut_log[x] + lut_log[y]]; + inline Te add(Te x, Te y) + { + return x ^ y; + } // Addition modulo 2 + inline Te sub(Te x, Te y) + { + return x ^ y; + } // Subtraction modulo 2 + inline Te mul(Te x, Te y) + { + if (!x || !y) + return 0; + return lut_exp[lut_log[x] + lut_log[y]]; } - inline Te div(Te x, Te y) { - //if ( ! y ) fail("div"); // TODO - if ( ! x ) return 0; - return lut_exp[lut_log[x] + ((1< gf; u8 G[17]; // { G_16, ..., G_0 } - rs_engine() { - // EN 300 421, section 4.4.2, Code Generator Polynomial - // G(X) = (X-alpha^0)*...*(X-alpha^15) - for ( int i=0; i<=16; ++i ) G[i] = (i==16) ? 1 : 0; // Init G=1 - for ( int d=0; d<16; ++d ) { - // Multiply by (X-alpha^d) - // G := X*G - alpha^d*G - for ( int i=0; i<=16; ++i ) - G[i] = gf.sub((i==16)?0:G[i+1], gf.mul(gf.exp(d),G[i])); - } + rs_engine() + { + // EN 300 421, section 4.4.2, Code Generator Polynomial + // G(X) = (X-alpha^0)*...*(X-alpha^15) + for (int i = 0; i <= 16; ++i) + G[i] = (i == 16) ? 1 : 0; // Init G=1 + for (int d = 0; d < 16; ++d) + { + // Multiply by (X-alpha^d) + // G := X*G - alpha^d*G + for (int i = 0; i <= 16; ++i) + G[i] = gf.sub((i == 16) ? 0 : G[i + 1], gf.mul(gf.exp(d), G[i])); + } #if DEBUG_RS - fprintf(stderr, "RS generator:"); - for ( int i=0; i<=16; ++i ) fprintf(stderr, " %02x", G[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "RS generator:"); + for ( int i=0; i<=16; ++i ) fprintf(stderr, " %02x", G[i]); + fprintf(stderr, "\n"); #endif } @@ -96,163 +126,190 @@ namespace leansdr { // By convention coefficients are listed by decreasing degree here, // so we can evaluate syndromes of the shortened code without // prepending with 51 zeroes. - bool syndromes(const u8 *poly, u8 *synd) { - bool corrupted = false; - for ( int i=0; i<16; ++i ) { - synd[i] = eval_poly_rev(poly, 204, gf.exp(i)); - if ( synd[i] ) corrupted = true; - } - return corrupted; + bool syndromes(const u8 *poly, u8 *synd) + { + bool corrupted = false; + for (int i = 0; i < 16; ++i) + { + synd[i] = eval_poly_rev(poly, 204, gf.exp(i)); + if (synd[i]) + corrupted = true; + } + return corrupted; } - u8 eval_poly_rev(const u8 *poly, int n, u8 x) { - // poly[0]*x^(n-1) + .. + poly[n-1]*x^0 with Hörner method. - u8 acc = 0; - for ( int i=0; i=0; --deg ) acc = gf.add(gf.mul(acc,x), poly[deg]); - return acc; + u8 eval_poly(const u8 *poly, int deg, u8 x) + { + // poly[0]*x^0 + .. + poly[deg]*x^deg with Hörner method. + u8 acc = 0; + for (; deg >= 0; --deg) + acc = gf.add(gf.mul(acc, x), poly[deg]); + return acc; } // Append parity symbols - void encode(u8 msg[204]) { - // TBD Avoid copying - u8 p[204]; - memcpy(p, msg, 188); - memset(p+188, 0, 16); - // p = msg*X^16 + void encode(u8 msg[204]) + { + // TBD Avoid copying + u8 p[204]; + memcpy(p, msg, 188); + memset(p + 188, 0, 16); + // p = msg*X^16 #if DEBUG_RS - fprintf(stderr, "uncoded:"); - for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "uncoded:"); + for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); + fprintf(stderr, "\n"); #endif - // Compute remainder modulo G - for ( int d=0; d<188; ++d ) { - // Clear monomial of degree d - if ( ! p[d] ) continue; - u8 k = gf.div(p[d], G[0]); - // p(X) := p(X) - k*G(X)*X^(188-d) - for ( int i=0; i<=16; ++i ) - p[d+i] = gf.sub(p[d+i], gf.mul(k,G[i])); - } + // Compute remainder modulo G + for (int d = 0; d < 188; ++d) + { + // Clear monomial of degree d + if (!p[d]) + continue; + u8 k = gf.div(p[d], G[0]); + // p(X) := p(X) - k*G(X)*X^(188-d) + for (int i = 0; i <= 16; ++i) + p[d + i] = gf.sub(p[d + i], gf.mul(k, G[i])); + } #if DEBUG_RS - fprintf(stderr, "coded:"); - for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "coded:"); + for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); + fprintf(stderr, "\n"); #endif - memcpy(msg+188, p+188, 16); + memcpy(msg + 188, p + 188, 16); } // Try to fix errors in pout[]. // If pin[] is provided, errors will be fixed in the original // message too and syndromes will be updated. - bool correct(u8 synd[16], u8 pout[188], - u8 pin[204]=NULL, int *bits_corrected=NULL) { - // Berlekamp - Massey - // http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm#Code_sample - u8 C[16] = { 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; // Max degree is L - u8 B[16] = { 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; - int L = 0; - int m = 1; - u8 b = 1; - for ( int n=0; n<16; ++n ) { - u8 d = synd[n]; - for ( int i=1; i<=L; ++i ) d ^= gf.mul(C[i], synd[n-i]); - if ( ! d ) { - ++m; - } else if ( 2*L <= n ) { - u8 T[16]; - memcpy(T, C, sizeof(T)); - for ( int i=0; i<16-m; ++i ) - C[m+i] ^= gf.mul(d, gf.mul(gf.inv(b),B[i])); - L = n + 1 - L; - memcpy(B, T, sizeof(B)); - b = d; - m = 1; - } else { - for ( int i=0; i<16-m; ++i ) - C[m+i] ^= gf.mul(d, gf.mul(gf.inv(b),B[i])); - ++m; - } - } - // L is the number of errors - // C of degree L is now the error locator polynomial (Lambda) + bool correct(u8 synd[16], u8 pout[188], u8 pin[204] = NULL, int *bits_corrected = NULL) + { + // Berlekamp - Massey + // http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm#Code_sample + u8 C[16] = + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Max degree is L + u8 B[16] = + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int L = 0; + int m = 1; + u8 b = 1; + for (int n = 0; n < 16; ++n) + { + u8 d = synd[n]; + for (int i = 1; i <= L; ++i) + d ^= gf.mul(C[i], synd[n - i]); + if (!d) + { + ++m; + } + else if (2 * L <= n) + { + u8 T[16]; + memcpy(T, C, sizeof(T)); + for (int i = 0; i < 16 - m; ++i) + C[m + i] ^= gf.mul(d, gf.mul(gf.inv(b), B[i])); + L = n + 1 - L; + memcpy(B, T, sizeof(B)); + b = d; + m = 1; + } + else + { + for (int i = 0; i < 16 - m; ++i) + C[m + i] ^= gf.mul(d, gf.mul(gf.inv(b), B[i])); + ++m; + } + } + // L is the number of errors + // C of degree L is now the error locator polynomial (Lambda) #if DEBUG_RS - fprintf(stderr, "[L=%d C=",L); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", C[i]); - fprintf(stderr, "]\n"); - fprintf(stderr, "[S="); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", synd[i]); - fprintf(stderr, "]\n"); + fprintf(stderr, "[L=%d C=",L); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", C[i]); + fprintf(stderr, "]\n"); + fprintf(stderr, "[S="); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", synd[i]); + fprintf(stderr, "]\n"); #endif - - // Forney - // http://en.wikipedia.org/wiki/Forney_algorithm (2t=16) - - // Compute Omega - u8 omega[16]; - memset(omega, 0, sizeof(omega)); - // TODO loops - for ( int i=0; i<16; ++i ) - for ( int j=0; j<16; ++j ) - if ( i+j < 16 ) omega[i+j] ^= gf.mul(synd[i], C[j]); + + // Forney + // http://en.wikipedia.org/wiki/Forney_algorithm (2t=16) + + // Compute Omega + u8 omega[16]; + memset(omega, 0, sizeof(omega)); + // TODO loops + for (int i = 0; i < 16; ++i) + for (int j = 0; j < 16; ++j) + if (i + j < 16) + omega[i + j] ^= gf.mul(synd[i], C[j]); #if DEBUG_RS - fprintf(stderr, "omega="); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", omega[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "omega="); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", omega[i]); + fprintf(stderr, "\n"); #endif - - // Compute Lambda' - u8 Cprime[15]; - for ( int i=0; i<15; ++i ) - Cprime[i] = (i&1) ? 0 : C[i+1]; + + // Compute Lambda' + u8 Cprime[15]; + for (int i = 0; i < 15; ++i) + Cprime[i] = (i & 1) ? 0 : C[i + 1]; #if DEBUG_RS - fprintf(stderr, "Cprime="); - for ( int i=0; i<15; ++i ) fprintf(stderr, " %d", Cprime[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "Cprime="); + for ( int i=0; i<15; ++i ) fprintf(stderr, " %d", Cprime[i]); + fprintf(stderr, "\n"); #endif - - // Find zeroes of C by exhaustive search? - // TODO Chien method - int roots_found = 0; - for ( int i=0; i<255; ++i ) { - u8 r = gf.exp(i); // Candidate root alpha^0..alpha^254 - u8 v = eval_poly(C, L, r); - if ( ! v ) { - // r is a root X_k^-1 of the error locator polynomial. - u8 xk = gf.inv(r); - int loc = (255-i) % 255; // == log(xk) + + // Find zeroes of C by exhaustive search? + // TODO Chien method + int roots_found = 0; + for (int i = 0; i < 255; ++i) + { + u8 r = gf.exp(i); // Candidate root alpha^0..alpha^254 + u8 v = eval_poly(C, L, r); + if (!v) + { + // r is a root X_k^-1 of the error locator polynomial. + u8 xk = gf.inv(r); + int loc = (255 - i) % 255; // == log(xk) #if DEBUG_RS - fprintf(stderr, "found root=%d, inv=%d, loc=%d\n", r, xk, loc); + fprintf(stderr, "found root=%d, inv=%d, loc=%d\n", r, xk, loc); #endif - if ( loc < 204 ) { - // Evaluate e_k - u8 num = gf.mul(xk, eval_poly(omega, L, r)); - u8 den = eval_poly(Cprime, 14, r); - u8 e = gf.div(num, den); - // Subtract e from coefficient of degree loc. - // Note: Coeffients listed by decreasing degree in pin[] and pout[]. - if ( bits_corrected ) *bits_corrected += hamming_weight(e); - if ( loc >= 16 ) pout[203-loc] ^= e; - if ( pin ) pin[203-loc] ^= e; - } - if ( ++roots_found == L ) break; - } - } - if ( pin ) - return syndromes(pin, synd); - else - return false; + if (loc < 204) + { + // Evaluate e_k + u8 num = gf.mul(xk, eval_poly(omega, L, r)); + u8 den = eval_poly(Cprime, 14, r); + u8 e = gf.div(num, den); + // Subtract e from coefficient of degree loc. + // Note: Coeffients listed by decreasing degree in pin[] and pout[]. + if (bits_corrected) + *bits_corrected += hamming_weight(e); + if (loc >= 16) + pout[203 - loc] ^= e; + if (pin) + pin[203 - loc] ^= e; + } + if (++roots_found == L) + break; + } + } + if (pin) + return syndromes(pin, synd); + else + return false; } - }; +}; } // namespace diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h index 781daa140..e1e4904d4 100644 --- a/plugins/channelrx/demoddatv/leansdr/sdr.h +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -531,7 +531,8 @@ struct cstln_lut make_qam(256); break; default: - fail("Constellation not implemented"); + fail("cstln_lut::cstln_lut", "Constellation not implemented"); + return; } } struct result @@ -936,7 +937,10 @@ struct cstln_receiver: runnable void run() { if (!cstln) - fail("constellation not set"); + { + fail("cstln_lut::run", "constellation not set"); + return; + } // Magic constants that work with the qa recordings. float freq_alpha = 0.04; @@ -1190,7 +1194,10 @@ struct fast_qpsk_receiver: runnable signed long freq_alpha = 0.04 * 65536; signed long freq_beta = 0.0012 * 256 * 65536 / omega * pll_adjustment; if (!freq_beta) - fail("Excessive oversampling"); + { + fail("fast_qpsk_receiver::run", "Excessive oversampling"); + return; + } float gain_mu = 0.02 / (cstln_amp * cstln_amp) * 2; @@ -1431,7 +1438,10 @@ struct cstln_transmitter: runnable void run() { if (!cstln) - fail("constellation not set"); + { + fail("cstln_transmitter::run", "constellation not set"); + return; + } int count = min(in.readable(), out.writable()); u8 *pin = in.rd(), *pend = pin + count; complex *pout = out.wr(); @@ -1524,8 +1534,9 @@ struct cnr_fft: runnable avgpower(NULL), phase(0) { - if (bandwidth > 0.25) - fail("CNR estimator requires Fsampling > 4x Fsignal"); + if (bandwidth > 0.25) { + fail("cnr_fft::cnr_fft", "CNR estimator requires Fsampling > 4x Fsignal"); + } } float bandwidth; diff --git a/plugins/channelrx/demoddatv/leansdr/viterbi.h b/plugins/channelrx/demoddatv/leansdr/viterbi.h index 96bbff967..e4121ce73 100644 --- a/plugins/channelrx/demoddatv/leansdr/viterbi.h +++ b/plugins/channelrx/demoddatv/leansdr/viterbi.h @@ -11,271 +11,322 @@ // TBD This is very inefficient. For a specific trellis all loops // can be be unrolled. -namespace leansdr { +namespace leansdr +{ - // TS is an integer type for a least NSTATES+1 states. - // NSTATES is the number of states (e.g. 2^(K-1)). - // TUS is an integer type for uncoded symbols (branch identifiers). - // NUS is the number of uncoded symbols. - // TCS is an integer type for coded symbols (branch labels). - // NCS is the number of coded symbols. - // TP is a type for representing paths. - // TPM, TBM are unsigned integer types for path/branch metrics. - // TPM is at least as wide as TBM. +// TS is an integer type for a least NSTATES+1 states. +// NSTATES is the number of states (e.g. 2^(K-1)). +// TUS is an integer type for uncoded symbols (branch identifiers). +// NUS is the number of uncoded symbols. +// TCS is an integer type for coded symbols (branch labels). +// NCS is the number of coded symbols. +// TP is a type for representing paths. +// TPM, TBM are unsigned integer types for path/branch metrics. +// TPM is at least as wide as TBM. - template - struct trellis { - static const int NOSTATE = NSTATES+1; +template +struct trellis +{ + static const int NOSTATE = NSTATES + 1; - struct state { - struct branch { - TS pred; // Predecessor state or NOSTATE - TUS us; // Uncoded symbol - } branches[NCS]; // Incoming branches indexed by coded symbol + struct state + { + struct branch + { + TS pred; // Predecessor state or NOSTATE + TUS us; // Uncoded symbol + } branches[NCS]; // Incoming branches indexed by coded symbol } states[NSTATES]; - - trellis() { - for ( TS s=0; spred != NOSTATE ) { - fprintf(stderr, "Invalid convolutional code\n"); - exit(1); - } - b->pred = s; - b->us = us; - } - } + for (TS s = 0; s < NSTATES; ++s) + { + for (TUS us = 0; us < NUS; ++us) + { + // Run the convolutional encoder from state s with input us + uint64_t shiftreg = s; // TBD type + // Reverse bits + TUS us_rev = 0; + for (int b = 1; b < NUS; b *= 2) + if (us & b) + us_rev |= (NUS / 2 / b); + shiftreg |= us_rev * NSTATES; + uint32_t cs = 0; // TBD type + for (int g = 0; g < nG; ++g) + cs = (cs << 1) | parity(shiftreg & G[g]); + shiftreg /= NUS; // Shift bits for 1 uncoded symbol + // [us] at state [s] emits [cs] and leads to state [shiftreg]. + typename state::branch *b = &states[shiftreg].branches[cs]; + if (b->pred != NOSTATE) + { + fprintf(stderr, "leansdr::trellis::init_convolutional: Invalid convolutional code\n"); + return; + } + b->pred = s; + b->us = us; + } + } } - void dump() { - for ( int s=0; spred == NOSTATE ) - fprintf(stderr, " - "); - else - fprintf(stderr, " %02x+%x", b->pred, b->us); - } - fprintf(stderr, "\n"); - } + void dump() + { + for (int s = 0; s < NSTATES; ++s) + { + fprintf(stderr, "State %02x:", s); + for (int cs = 0; cs < NCS; ++cs) + { + typename state::branch *b = &states[s].branches[cs]; + if (b->pred == NOSTATE) + fprintf(stderr, " - "); + else + fprintf(stderr, " %02x+%x", b->pred, b->us); + } + fprintf(stderr, "\n"); + } } - }; +}; - // Interface that hides the templated internals. - template - struct viterbi_dec_interface { - virtual TUS update(TBM costs[], TPM *quality=NULL)=0; - virtual TUS update(TCS s, TBM cost, TPM *quality=NULL)=0; - virtual TUS update(int nm, TCS cs[], TBM costs[], TPM *quality=NULL)=0; - }; +// Interface that hides the templated internals. +template +struct viterbi_dec_interface +{ + virtual TUS update(TBM costs[], TPM *quality = NULL)=0; + virtual TUS update(TCS s, TBM cost, TPM *quality = NULL)=0; + virtual TUS update(int nm, TCS cs[], TBM costs[], TPM *quality = NULL)=0; +}; - template - struct viterbi_dec : viterbi_dec_interface { +template +struct viterbi_dec: viterbi_dec_interface +{ trellis *trell; - struct state { - TPM cost; // Metric of best path leading to this state - TP path; // Best path leading to this state + struct state + { + TPM cost; // Metric of best path leading to this state + TP path; // Best path leading to this state }; typedef state statebank[NSTATES]; state statebanks[2][NSTATES]; statebank *states, *newstates; // Alternate between banks viterbi_dec(trellis *_trellis) : - trell(_trellis) + trell(_trellis) { - states = &statebanks[0]; - newstates = &statebanks[1]; - for ( TS s=0; smax_tpm; max_tpm=max_tpm*2+1 ) ; - } + states = &statebanks[0]; + newstates = &statebanks[1]; + for (TS s = 0; s < NSTATES; ++s) + (*states)[s].cost = 0; + // Determine max value that can fit in TPM + max_tpm = (TPM) 0 - 1; + if (max_tpm < 0) + { + // TPM is signed + for (max_tpm = 0; max_tpm * 2 + 1 > max_tpm; max_tpm = max_tpm * 2 + 1) + ; + } } // Update with full metric - - TUS update(TBM costs[NCS], TPM *quality=NULL) { - TPM best_tpm = max_tpm, best2_tpm = max_tpm; - TS best_state = 0; - // Update all states - for ( int s=0; s::state::branch *best_b = NULL; - // Select best branch - for ( int cs=0; cs::state::branch *b = - &trell->states[s].branches[cs]; - if ( b->pred == trell->NOSTATE ) continue; - TPM m = (*states)[b->pred].cost + costs[cs]; - if ( m <= best_m ) { // <= guarantees one match - best_m = m; - best_b = b; - } - } - (*newstates)[s].path = (*states)[best_b->pred].path; - (*newstates)[s].path.append(best_b->us); - (*newstates)[s].cost = best_m; - // Select best and second-best states - if ( best_m < best_tpm ) { - best_state = s; - best2_tpm = best_tpm; - best_tpm = best_m; - } else if ( best_m < best2_tpm ) - best2_tpm = best_m; - } - // Swap banks - { statebank *tmp=states; states=newstates; newstates=tmp; } - // Prevent overflow of path metrics - for ( TS s=0; s::state::branch *best_b = NULL; + // Select best branch + for (int cs = 0; cs < NCS; ++cs) + { + typename trellis::state::branch *b = &trell->states[s].branches[cs]; + if (b->pred == trell->NOSTATE) + continue; + TPM m = (*states)[b->pred].cost + costs[cs]; + if (m <= best_m) + { // <= guarantees one match + best_m = m; + best_b = b; + } + } + (*newstates)[s].path = (*states)[best_b->pred].path; + (*newstates)[s].path.append(best_b->us); + (*newstates)[s].cost = best_m; + // Select best and second-best states + if (best_m < best_tpm) + { + best_state = s; + best2_tpm = best_tpm; + best_tpm = best_m; + } + else if (best_m < best2_tpm) + best2_tpm = best_m; + } + // Swap banks + { + statebank *tmp = states; + states = newstates; + newstates = tmp; + } + // Prevent overflow of path metrics + for (TS s = 0; s < NSTATES; ++s) + (*states)[s].cost -= best_tpm; #if 0 - // Observe that the min-max range remains bounded - fprintf(stderr,"-%2d = [", best_tpm); - for ( TS s=0; s::state::branch *best_b = NULL; - for ( int im=0; im::state::branch *b = - &trell->states[s].branches[cs[im]]; - if ( b->pred == trell->NOSTATE ) continue; - TPM m = (*states)[b->pred].cost + costs[im]; - if ( m <= best_m ) { // <= guarantees one match - best_m = m; - best_b = b; - } - } - if ( nm != NCS ) { - // Also scan the other branches. - // We actually rescan the branches with metrics. - // This works because costs are negative. - for ( int cs=0; cs::state::branch *b = - &trell->states[s].branches[cs]; - if ( b->pred == trell->NOSTATE ) continue; - TPM m = (*states)[b->pred].cost; - if ( m <= best_m ) { - best_m = m; - best_b = b; - } - } - } - (*newstates)[s].path = (*states)[best_b->pred].path; - (*newstates)[s].path.append(best_b->us); - (*newstates)[s].cost = best_m; - // Select best states - if ( best_m < best_tpm ) { - best_state = s; - best2_tpm = best_tpm; - best_tpm = best_m; - } else if ( best_m < best2_tpm ) - best2_tpm = best_m; - } - // Swap banks - { statebank *tmp=states; states=newstates; newstates=tmp; } - // Prevent overflow of path metrics - for ( TS s=0; s::state::branch *best_b = NULL; + for (int im = 0; im < nm; ++im) + { + typename trellis::state::branch *b = &trell->states[s].branches[cs[im]]; + if (b->pred == trell->NOSTATE) + continue; + TPM m = (*states)[b->pred].cost + costs[im]; + if (m <= best_m) + { // <= guarantees one match + best_m = m; + best_b = b; + } + } + if (nm != NCS) + { + // Also scan the other branches. + // We actually rescan the branches with metrics. + // This works because costs are negative. + for (int cs = 0; cs < NCS; ++cs) + { + typename trellis::state::branch *b = &trell->states[s].branches[cs]; + if (b->pred == trell->NOSTATE) + continue; + TPM m = (*states)[b->pred].cost; + if (m <= best_m) + { + best_m = m; + best_b = b; + } + } + } + (*newstates)[s].path = (*states)[best_b->pred].path; + (*newstates)[s].path.append(best_b->us); + (*newstates)[s].cost = best_m; + // Select best states + if (best_m < best_tpm) + { + best_state = s; + best2_tpm = best_tpm; + best_tpm = best_m; + } + else if (best_m < best2_tpm) + best2_tpm = best_m; + } + // Swap banks + { + statebank *tmp = states; + states = newstates; + newstates = tmp; + } + // Prevent overflow of path metrics + for (TS s = 0; s < NSTATES; ++s) + (*states)[s].cost -= best_tpm; #if 0 - // Observe that the min-max range remains bounded - fprintf(stderr,"-%2d = [", best_tpm); - for ( TS s=0; s - struct bitpath { +template +struct bitpath +{ T val; - bitpath() : val(0) { } - void append(TUS us) { val = (val<>(DEPTH-1)*NBITS) & ((1<> (DEPTH - 1) * NBITS) & ((1 << NBITS) - 1); + } +}; } // namespace From 1436844fb36793a4aeab590b1cf66764d79ad6eb Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 26 Feb 2018 01:04:45 +0100 Subject: [PATCH 009/956] DATV demodulator: improvements --- .../channelrx/demoddatv/datvconstellation.h | 46 +- plugins/channelrx/demoddatv/datvdemod.cpp | 1051 ++++++++--------- plugins/channelrx/demoddatv/datvdemod.h | 428 +++---- plugins/channelrx/demoddatv/datvdemodgui.cpp | 213 ++-- plugins/channelrx/demoddatv/datvdemodgui.h | 19 +- plugins/channelrx/demoddatv/datvdemodgui.ui | 426 ++++--- .../channelrx/demoddatv/datvdemodplugin.cpp | 5 +- .../channelrx/demoddatv/datvideorender.cpp | 89 +- plugins/channelrx/demoddatv/datvideorender.h | 45 +- .../channelrx/demoddatv/datvideostream.cpp | 24 +- plugins/channelrx/demoddatv/datvideostream.h | 1 + plugins/channelrx/demoddatv/datvscreen.cpp | 19 +- plugins/channelrx/demoddatv/datvscreen.h | 4 - plugins/channelrx/demoddatv/datvvideoplayer.h | 52 +- plugins/channelrx/demoddatv/glshaderarray.cpp | 4 +- 15 files changed, 1285 insertions(+), 1141 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvconstellation.h b/plugins/channelrx/demoddatv/datvconstellation.h index a343cbfe2..2289eb010 100644 --- a/plugins/channelrx/demoddatv/datvconstellation.h +++ b/plugins/channelrx/demoddatv/datvconstellation.h @@ -31,31 +31,24 @@ static const int DEFAULT_GUI_DECIMATION = 64; template struct datvconstellation: runnable { - T xymin; - T xymax; + T xymin, xymax; unsigned long decimation; unsigned long pixels_per_frame; cstln_lut<256> **cstln; // Optional ptr to optional constellation + DATVScreen *m_objDATVScreen; pipereader > in; unsigned long phase; - DATVScreen *m_objDATVScreen; - datvconstellation( - scheduler *sch, - pipebuf > &_in, - T _xymin, - T _xymax, - const char *_name = 0, - DATVScreen * objDATVScreen = 0) : - runnable(sch, _name ? _name : _in.name), - xymin(_xymin), - xymax(_xymax), - decimation(DEFAULT_GUI_DECIMATION), - pixels_per_frame(1024), - cstln(0), - in(_in), - phase(0), - m_objDATVScreen(objDATVScreen) + datvconstellation(scheduler *sch, pipebuf > &_in, T _xymin, T _xymax, const char *_name = NULL, DATVScreen * objDATVScreen = NULL) : + runnable(sch, _name ? _name : _in.name), + xymin(_xymin), + xymax(_xymax), + decimation(DEFAULT_GUI_DECIMATION), + pixels_per_frame(1024), + cstln(NULL), + m_objDATVScreen(objDATVScreen), + in(_in), + phase(0) { } @@ -76,11 +69,8 @@ template struct datvconstellation: runnable if (m_objDATVScreen != NULL) { - m_objDATVScreen->selectRow( - 256 * (p->re - xymin) / (xymax - xymin)); - m_objDATVScreen->setDataColor( - 256 - 256 * ((p->im - xymin) / (xymax - xymin)), - 255, 0, 255); + m_objDATVScreen->selectRow(256 * (p->re - xymin) / (xymax - xymin)); + m_objDATVScreen->setDataColor(256 - 256 * ((p->im - xymin) / (xymax - xymin)), 255, 0, 255); } } @@ -106,7 +96,6 @@ template struct datvconstellation: runnable } m_objDATVScreen->renderImage(NULL); - } in.read(pixels_per_frame); @@ -118,15 +107,8 @@ template struct datvconstellation: runnable } } - //private: - //gfx g; - void draw_begin() { - //g.clear(); - //g.setfg(0, 255, 0); - //g.line(g.w/2,0, g.w/2, g.h); - //g.line(0,g.h/2, g.w,g.h/2); } }; diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index c0a0e0fb2..73a411c6b 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -36,23 +36,23 @@ MESSAGE_CLASS_DEFINITION(DATVDemod::MsgConfigureDATVDemod, Message) MESSAGE_CLASS_DEFINITION(DATVDemod::MsgConfigureChannelizer, Message) DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : - ChannelSinkAPI(m_channelIdURI), - m_blnNeedConfigUpdate(false), - m_deviceAPI(deviceAPI), - m_objRegisteredDATVScreen(0), - m_objRegisteredVideoRender(0), - m_objVideoStream(0), - m_objRenderThread(0), - m_blnRenderingVideo(false), - m_enmModulation(BPSK /*DATV_FM1*/), - m_objSettingsMutex(QMutex::NonRecursive) + ChannelSinkAPI(m_channelIdURI), + m_blnNeedConfigUpdate(false), + m_deviceAPI(deviceAPI), + m_objRegisteredDATVScreen(NULL), + m_objRegisteredVideoRender(NULL), + m_objVideoStream(NULL), + m_objRenderThread(NULL), + m_blnRenderingVideo(false), + m_blnStartStopVideo(false), + m_enmModulation(BPSK /*DATV_FM1*/), + m_objSettingsMutex(QMutex::NonRecursive) { setObjectName("DATVDemod"); - qDebug("DATVDemod::DATVDemod: sizeof FixReal: %lu: SDR_RX_SAMP_SZ: %u", sizeof(FixReal), (unsigned int) SDR_RX_SAMP_SZ); //*************** DATV PARAMETERS *************** - m_blnInitialized = false; - CleanUpDATVFramework(); + m_blnInitialized=false; + CleanUpDATVFramework(false); m_objVideoStream = new DATVideostream(); @@ -64,32 +64,33 @@ DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : m_deviceAPI->addChannelAPI(this); connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); + + //To setup correct Sample Rate + channelSampleRateChanged(); } DATVDemod::~DATVDemod() { - m_blnInitialized = false; + m_blnInitialized=false; - if (m_objRenderThread != NULL) + if(m_objVideoStream!=NULL) { - if (m_objRenderThread->isRunning()) + //Immediately exit from DATVideoStream if waiting for data before killing thread + m_objVideoStream->ThreadTimeOut=0; + } + + if(m_objRenderThread!=NULL) + { + if(m_objRenderThread->isRunning()) { - m_objRenderThread->stopRendering(); + m_objRenderThread->stopRendering(); + m_objRenderThread->quit(); } + + m_objRenderThread->wait(2000); } - //CleanUpDATVFramework(true); - - if (m_objRFFilter != NULL) - { - //delete m_objRFFilter; - } - - if (m_objVideoStream != NULL) - { - //m_objVideoStream->close(); - //delete m_objVideoStream; - } + CleanUpDATVFramework(true); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); @@ -97,6 +98,20 @@ DATVDemod::~DATVDemod() delete m_channelizer; } +void DATVDemod::channelSampleRateChanged() +{ + qDebug() << "DATVDemod::channelSampleRateChanged:" + << " intMsps: " << m_channelizer->getInputSampleRate(); + + if(m_objRunning.intMsps!=m_channelizer->getInputSampleRate()) + { + m_objRunning.intMsps = m_channelizer->getInputSampleRate(); + m_objRunning.intSampleRate = m_objRunning.intMsps; + + ApplySettings(); + } +} + bool DATVDemod::SetDATVScreen(DATVScreen *objScreen) { m_objRegisteredDATVScreen = objScreen; @@ -107,114 +122,110 @@ DATVideostream * DATVDemod::SetVideoRender(DATVideoRender *objScreen) { m_objRegisteredVideoRender = objScreen; - m_objRenderThread = new DATVideoRenderThread(m_objRegisteredVideoRender, m_objVideoStream); + m_objRenderThread = new DATVideoRenderThread(m_objRegisteredVideoRender,m_objVideoStream); return m_objVideoStream; } + bool DATVDemod::PlayVideo(bool blnStartStop) { - if (m_objVideoStream == NULL) + if(m_objVideoStream==NULL) { return false; } - if (m_objRegisteredVideoRender == NULL) + if(m_objRegisteredVideoRender==NULL) { return false; } - if (m_objRenderThread == NULL) + if(m_objRenderThread==NULL) { return false; } - if (m_objRenderThread->isRunning()) + if (m_blnStartStopVideo && !blnStartStop) { - if (blnStartStop == true) - { - m_objRenderThread->stopRendering(); - } - return true; } - m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender, m_objVideoStream); - m_objVideoStream->MultiThreaded = true; - m_objRenderThread->start(); + if(blnStartStop==true) + { + m_blnStartStopVideo=true; + } - //m_objVideoStream->MultiThreaded=false; - //m_objRenderThread->run(); + if(m_objRenderThread->isRunning()) + { + if(blnStartStop==true) + { + m_objRenderThread->stopRendering(); + } + + return true; + } + + m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender,m_objVideoStream); + m_objVideoStream->MultiThreaded=true; + m_objVideoStream->ThreadTimeOut=5000; //5000 ms + m_objRenderThread->start(); return true; } -void DATVDemod::configure( - MessageQueue* objMessageQueue, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) +void DATVDemod::configure(MessageQueue* objMessageQueue, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intExcursion) { - Message* msgCmd = MsgConfigureDATVDemod::create( - intRFBandwidth, - intCenterFrequency, - enmStandard, - enmModulation, - enmFEC, - intSymbolRate, - intNotchFilters, - blnAllowDrift, - blnFastLock, - blnHDLC, - blnHardMetric, - blnResample, - blnViterbi); + Message* msgCmd = MsgConfigureDATVDemod::create(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,enmFilter,blnHardMetric,fltRollOff, blnViterbi,intExcursion); objMessageQueue->push(msgCmd); } -void DATVDemod::InitDATVParameters( - int intMsps, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSampleRate, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) +void DATVDemod::InitDATVParameters(int intMsps, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSampleRate, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intExcursion) { Real fltLowCut; Real fltHiCut; - m_blnInitialized = false; - m_objSettingsMutex.lock(); - //Recalibrage du filtre passe bande + m_blnInitialized=false; - fltLowCut = -((float) intRFBandwidth / 2.0) / (float) intMsps; - fltHiCut = ((float) intRFBandwidth / 2.0) / (float) intMsps; + //Bandpass filter shaping + + fltLowCut = -((float)intRFBandwidth / 2.0) / (float)intMsps; + fltHiCut = ((float)intRFBandwidth / 2.0) / (float)intMsps; m_objRFFilter->create_filter(fltLowCut, fltHiCut); - m_objNCO.setFreq(-(float) intCenterFrequency, (float) intMsps); + m_objNCO.setFreq(-(float)intCenterFrequency,(float)intMsps); - //Mise à jour de la config + //Config update m_objRunning.intMsps = intMsps; m_objRunning.intCenterFrequency = intCenterFrequency; @@ -227,211 +238,221 @@ void DATVDemod::InitDATVParameters( m_objRunning.intNotchFilters = intNotchFilters; m_objRunning.blnAllowDrift = blnAllowDrift; m_objRunning.blnFastLock = blnFastLock; - m_objRunning.blnHDLC = blnHDLC; + m_objRunning.enmFilter = enmFilter; m_objRunning.blnHardMetric = blnHardMetric; - m_objRunning.blnResample = blnResample; + m_objRunning.fltRollOff = fltRollOff; m_objRunning.blnViterbi = blnViterbi; + m_objRunning.intExcursion = intExcursion; - qDebug() << "DATVDemod::InitDATVParameters:" - << " - Msps: " << intMsps - << " - Sample Rate: " << intSampleRate - << " - Symbol Rate: " << intSymbolRate - << " - Modulation: " << enmModulation - << " - Notch Filters: " << intNotchFilters - << " - Allow Drift: " << blnAllowDrift - << " - Fast Lock: " << blnFastLock - << " - HDLC: " << blnHDLC - << " - HARD METRIC: " << blnHardMetric - << " - Resample: " << blnResample - << " - Viterbi: " << blnViterbi; + m_blnInitialized=true; m_objSettingsMutex.unlock(); - m_blnNeedConfigUpdate = true; - - m_blnInitialized = true; + m_blnNeedConfigUpdate=true; } -void DATVDemod::CleanUpDATVFramework() +void DATVDemod::CleanUpDATVFramework(bool blnRelease) { - //if(blnRelease==true) -// if (false) -// { -// if(m_objScheduler!=NULL) -// { -// m_objScheduler->shutdown(); -// delete m_objScheduler; -// } -// -// // INPUT -// if(p_rawiq!=NULL) delete p_rawiq; -// if(p_rawiq_writer!=NULL) delete p_rawiq_writer; -// if(p_preprocessed!=NULL) delete p_preprocessed; -// -// // NOTCH FILTER -// if(r_auto_notch!=NULL) delete r_auto_notch; -// if(p_autonotched!=NULL) delete p_autonotched; -// -// // FREQUENCY CORRECTION : DEROTATOR -// if(p_derot!=NULL) delete p_derot; -// if(r_derot!=NULL) delete r_derot; -// -// // CNR ESTIMATION -// if(p_cnr!=NULL) delete p_cnr; -// if(r_cnr!=NULL) delete r_cnr; -// -// //FILTERING -// if(r_resample!=NULL) delete r_resample; -// if(p_resampled!=NULL) delete p_resampled; -// if(coeffs!=NULL) delete coeffs; -// -// // OUTPUT PREPROCESSED DATA -// if(sampler!=NULL) delete sampler; -// if(coeffs_sampler!=NULL) delete coeffs_sampler; -// if(p_symbols!=NULL) delete p_symbols; -// if(p_freq!=NULL) delete p_freq; -// if(p_ss!=NULL) delete p_ss; -// if(p_mer!=NULL) delete p_mer; -// if(p_sampled!=NULL) delete p_sampled; -// -// //DECIMATION -// if(p_decimated!=NULL) delete p_decimated; -// if(p_decim!=NULL) delete p_decim; -// if(r_ppout!=NULL) delete r_ppout; -// -// //GENERIC CONSTELLATION RECEIVER -// if(m_objDemodulator!=NULL) delete m_objDemodulator; -// -// //DECONVOLUTION AND SYNCHRONIZATION -// if(p_bytes!=NULL) delete p_bytes; -// if(r_deconv!=NULL) delete r_deconv; -// if(r!=NULL) delete r; -// if(p_descrambled!=NULL) delete p_descrambled; -// if(p_frames!=NULL) delete p_frames; -// if(r_etr192_descrambler!=NULL) delete r_etr192_descrambler; -// if(r_sync!=NULL) delete r_sync; -// if(p_mpegbytes!=NULL) delete p_mpegbytes; -// if(p_lock!=NULL) delete p_lock; -// if(p_locktime!=NULL) delete p_locktime; -// if(r_sync_mpeg!=NULL) delete r_sync_mpeg; -// -// -// // DEINTERLEAVING -// if(p_rspackets!=NULL) delete p_rspackets; -// if(r_deinter!=NULL) delete r_deinter; -// if(p_vbitcount!=NULL) delete p_vbitcount; -// if(p_verrcount!=NULL) delete p_verrcount; -// if(p_rtspackets!=NULL) delete p_rtspackets; -// if(r_rsdec!=NULL) delete r_rsdec; -// -// //BER ESTIMATION -// if(p_vber!=NULL) delete p_vber; -// if(r_vber!=NULL) delete r_vber; -// -// // DERANDOMIZATION -// if(p_tspackets!=NULL) delete p_tspackets; -// if(r_derand!=NULL) delete r_derand; -// -// -// //OUTPUT : To remove -// if(r_stdout!=NULL) delete r_stdout; -// if(r_videoplayer!=NULL) delete r_videoplayer; -// -// //CONSTELLATION -// if(r_scope_symbols!=NULL) delete r_scope_symbols; -// -// } + if(blnRelease==true) + { + if(m_objScheduler!=NULL) + { + m_objScheduler->shutdown(); + delete m_objScheduler; + } - m_objScheduler = 0; + // NOTCH FILTER + + if(r_auto_notch!=NULL) delete r_auto_notch; + if(p_autonotched!=NULL) delete p_autonotched; + + // FREQUENCY CORRECTION : DEROTATOR + if(p_derot!=NULL) delete p_derot; + if(r_derot!=NULL) delete r_derot; + + // CNR ESTIMATION + if(p_cnr!=NULL) delete p_cnr; + if(r_cnr!=NULL) delete r_cnr; + + //FILTERING + if(r_resample!=NULL) delete r_resample; + if(p_resampled!=NULL) delete p_resampled; + if(coeffs!=NULL) delete coeffs; + + // OUTPUT PREPROCESSED DATA + if(sampler!=NULL) delete sampler; + if(coeffs_sampler!=NULL) delete coeffs_sampler; + if(p_symbols!=NULL) delete p_symbols; + if(p_freq!=NULL) delete p_freq; + if(p_ss!=NULL) delete p_ss; + if(p_mer!=NULL) delete p_mer; + if(p_sampled!=NULL) delete p_sampled; + + //DECIMATION + if(p_decimated!=NULL) delete p_decimated; + if(p_decim!=NULL) delete p_decim; + if(r_ppout!=NULL) delete r_ppout; + + + //GENERIC CONSTELLATION RECEIVER + if(m_objDemodulator!=NULL) delete m_objDemodulator; + + //DECONVOLUTION AND SYNCHRONIZATION + if(p_bytes!=NULL) delete p_bytes; + if(r_deconv!=NULL) delete r_deconv; + if(r!=NULL) delete r; + if(p_descrambled!=NULL) delete p_descrambled; + if(p_frames!=NULL) delete p_frames; + if(r_etr192_descrambler!=NULL) delete r_etr192_descrambler; + if(r_sync!=NULL) delete r_sync; + if(p_mpegbytes!=NULL) delete p_mpegbytes; + if(p_lock!=NULL) delete p_lock; + if(p_locktime!=NULL) delete p_locktime; + if(r_sync_mpeg!=NULL) delete r_sync_mpeg; + + + // DEINTERLEAVING + if(p_rspackets!=NULL) delete p_rspackets; + if(r_deinter!=NULL) delete r_deinter; + if(p_vbitcount!=NULL) delete p_vbitcount; + if(p_verrcount!=NULL) delete p_verrcount; + if(p_rtspackets!=NULL) delete p_rtspackets; + if(r_rsdec!=NULL) delete r_rsdec; + + //BER ESTIMATION + if(p_vber!=NULL) delete p_vber; + if(r_vber!=NULL) delete r_vber; + + // DERANDOMIZATION + if(p_tspackets!=NULL) delete p_tspackets; + if(r_derand!=NULL) delete r_derand; + + + //OUTPUT : To remove + if(r_stdout!=NULL) delete r_stdout; + if(r_videoplayer!=NULL) delete r_videoplayer; + + //CONSTELLATION + if(r_scope_symbols!=NULL) delete r_scope_symbols; + + // INPUT + //if(p_rawiq!=NULL) delete p_rawiq; + //if(p_rawiq_writer!=NULL) delete p_rawiq_writer; + //if(p_preprocessed!=NULL) delete p_preprocessed; + + + } + + m_objScheduler=NULL; // INPUT - p_rawiq = 0; - p_rawiq_writer = 0; + p_rawiq = NULL; + p_rawiq_writer = NULL; - p_preprocessed = 0; + p_preprocessed = NULL; // NOTCH FILTER - r_auto_notch = 0; - p_autonotched = 0; + r_auto_notch = NULL; + p_autonotched = NULL; // FREQUENCY CORRECTION : DEROTATOR - p_derot = 0; - r_derot = 0; + p_derot = NULL; + r_derot=NULL; // CNR ESTIMATION - p_cnr = 0; - r_cnr = 0; + p_cnr = NULL; + r_cnr = NULL; //FILTERING - r_resample = 0; - p_resampled = 0; - coeffs = 0; - ncoeffs = 0; + r_resample = NULL; + p_resampled = NULL; + coeffs = NULL; + ncoeffs=0; // OUTPUT PREPROCESSED DATA - sampler = 0; - coeffs_sampler = 0; - ncoeffs_sampler = 0; + sampler = NULL; + coeffs_sampler=NULL; + ncoeffs_sampler=0; - p_symbols = 0; - p_freq = 0; - p_ss = 0; - p_mer = 0; - p_sampled = 0; + p_symbols = NULL; + p_freq = NULL; + p_ss = NULL; + p_mer = NULL; + p_sampled = NULL; //DECIMATION - p_decimated = 0; - p_decim = 0; - r_ppout = 0; + p_decimated = NULL; + p_decim = NULL; + r_ppout = NULL; //GENERIC CONSTELLATION RECEIVER - m_objDemodulator = 0; + m_objDemodulator = NULL; //DECONVOLUTION AND SYNCHRONIZATION - p_bytes = 0; - r_deconv = 0; - r = 0; + p_bytes=NULL; + r_deconv=NULL; + r = NULL; - p_descrambled = 0; - p_frames = 0; - r_etr192_descrambler = 0; - r_sync = 0; + p_descrambled = NULL; + p_frames = NULL; + r_etr192_descrambler = NULL; + r_sync = NULL; + + p_mpegbytes = NULL; + p_lock = NULL; + p_locktime = NULL; + r_sync_mpeg = NULL; - p_mpegbytes = 0; - p_lock = 0; - p_locktime = 0; - r_sync_mpeg = 0; // DEINTERLEAVING - p_rspackets = 0; - r_deinter = 0; + p_rspackets = NULL; + r_deinter = NULL; + + p_vbitcount = NULL; + p_verrcount = NULL; + p_rtspackets = NULL; + r_rsdec = NULL; - p_vbitcount = 0; - p_verrcount = 0; - p_rtspackets = 0; - r_rsdec = 0; //BER ESTIMATION - p_vber = 0; - r_vber = 0; + p_vber = NULL; + r_vber = NULL; + // DERANDOMIZATION - p_tspackets = 0; - r_derand = 0; + p_tspackets = NULL; + r_derand = NULL; + //OUTPUT : To remove - r_stdout = 0; - r_videoplayer = 0; + r_stdout = NULL; + r_videoplayer = NULL; + //CONSTELLATION - r_scope_symbols = 0; + r_scope_symbols = NULL; } void DATVDemod::InitDATVFramework() { - m_blnDVBInitialized = false; - m_lngReadIQ = 0; + m_blnDVBInitialized=false; + m_lngReadIQ=0; + CleanUpDATVFramework(false); + + qDebug() << "DATVDemod::InitDATVParameters:" + << " - Msps: " << m_objRunning.intMsps + << " - Sample Rate: " << m_objRunning.intSampleRate + << " - Symbol Rate: " << m_objRunning.intSymbolRate + << " - Modulation: " << m_objRunning.enmModulation + << " - Notch Filters: " << m_objRunning.intNotchFilters + << " - Allow Drift: " << m_objRunning.blnAllowDrift + << " - Fast Lock: " << m_objRunning.blnFastLock + << " - Filter: " << m_objRunning.enmFilter + << " - HARD METRIC: " << m_objRunning.blnHardMetric + << " - RollOff: " << m_objRunning.fltRollOff + << " - Viterbi: " << m_objRunning.blnViterbi + << " - Excursion: " << m_objRunning.intExcursion; m_objCfg.standard = m_objRunning.enmStandard; @@ -440,56 +461,61 @@ void DATVDemod::InitDATVFramework() m_objCfg.Fm = (float) m_objRunning.intSymbolRate; m_objCfg.fastlock = m_objRunning.blnFastLock; - switch (m_objRunning.enmModulation) + m_objCfg.sampler = m_objRunning.enmFilter; + m_objCfg.rolloff=m_objRunning.fltRollOff; //0...1 + m_objCfg.rrc_rej=(float) m_objRunning.intExcursion; //dB + m_objCfg.rrc_steps=0; //auto + + switch(m_objRunning.enmModulation) { - case BPSK: - m_objCfg.constellation = cstln_lut<256>::BPSK; + case BPSK: + m_objCfg.constellation = cstln_lut<256>::BPSK; break; - case QPSK: - m_objCfg.constellation = cstln_lut<256>::QPSK; + case QPSK: + m_objCfg.constellation = cstln_lut<256>::QPSK; break; - case PSK8: - m_objCfg.constellation = cstln_lut<256>::PSK8; + case PSK8: + m_objCfg.constellation = cstln_lut<256>::PSK8; break; - case APSK16: - m_objCfg.constellation = cstln_lut<256>::APSK16; + case APSK16: + m_objCfg.constellation = cstln_lut<256>::APSK16; break; - case APSK32: - m_objCfg.constellation = cstln_lut<256>::APSK32; + case APSK32: + m_objCfg.constellation = cstln_lut<256>::APSK32; break; - case APSK64E: - m_objCfg.constellation = cstln_lut<256>::APSK64E; + case APSK64E: + m_objCfg.constellation = cstln_lut<256>::APSK64E; break; - case QAM16: - m_objCfg.constellation = cstln_lut<256>::QAM16; + case QAM16: + m_objCfg.constellation = cstln_lut<256>::QAM16; break; - case QAM64: - m_objCfg.constellation = cstln_lut<256>::QAM64; + case QAM64: + m_objCfg.constellation = cstln_lut<256>::QAM64; break; - case QAM256: - m_objCfg.constellation = cstln_lut<256>::QAM256; + case QAM256: + m_objCfg.constellation = cstln_lut<256>::QAM256; break; - default: - m_objCfg.constellation = cstln_lut<256>::BPSK; + default: + m_objCfg.constellation = cstln_lut<256>::BPSK; break; } m_objCfg.allow_drift = m_objRunning.blnAllowDrift; m_objCfg.anf = m_objRunning.intNotchFilters; m_objCfg.hard_metric = m_objRunning.blnHardMetric; - m_objCfg.hdlc = m_objRunning.blnHDLC; - m_objCfg.resample = m_objRunning.blnResample; + m_objCfg.sampler = m_objRunning.enmFilter; m_objCfg.viterbi = m_objRunning.blnViterbi; + // Min buffer size for baseband data // scopes: 1024 // ss_estimator: 1024 @@ -519,9 +545,7 @@ void DATVDemod::InitDATVFramework() // Min buffer size for misc measurements: 1 BUF_SLOW = m_objCfg.buf_factor; - m_lngExpectedReadIQ = BUF_BASEBAND; - - CleanUpDATVFramework(); + m_lngExpectedReadIQ = BUF_BASEBAND; m_objScheduler = new scheduler(); @@ -532,29 +556,25 @@ void DATVDemod::InitDATVFramework() // NOTCH FILTER - if (m_objCfg.anf) + if ( m_objCfg.anf>0 ) { p_autonotched = new pipebuf(m_objScheduler, "autonotched", BUF_BASEBAND); r_auto_notch = new auto_notch(m_objScheduler, *p_preprocessed, *p_autonotched, m_objCfg.anf, 0); p_preprocessed = p_autonotched; } + // FREQUENCY CORRECTION - if (m_objCfg.Fderot) - { - p_derot = new pipebuf(m_objScheduler, "derotated", BUF_BASEBAND); - r_derot = new rotator(m_objScheduler, *p_preprocessed, *p_derot, -m_objCfg.Fderot / m_objCfg.Fs); - p_preprocessed = p_derot; - } + //******** -> if ( m_objCfg.Fderot>0 ) // CNR ESTIMATION p_cnr = new pipebuf(m_objScheduler, "cnr", BUF_SLOW); - if (m_objCfg.cnr) + if ( m_objCfg.cnr==true ) { - r_cnr = new cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm / m_objCfg.Fs); + r_cnr = new cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm/m_objCfg.Fs); r_cnr->decimation = decimation(m_objCfg.Fs, 1); // 1 Hz } @@ -562,164 +582,118 @@ void DATVDemod::InitDATVFramework() int decim = 1; - if (m_objCfg.resample) - { - // Lowpass-filter and decimate. - if (m_objCfg.decim) - { - decim = m_objCfg.decim; - } - else - { - // Decimate to just above 4 samples per symbol - float target_Fs = m_objCfg.Fm * 4; - decim = m_objCfg.Fs / target_Fs; - if (decim < 1) - { - decim = 1; - } - } + //******** -> if ( m_objCfg.resample ) - float transition = (m_objCfg.Fm / 2) * m_objCfg.rolloff; - int order = m_objCfg.resample_rej * m_objCfg.Fs / (22 * transition); - order = ((order + 1) / 2) * 2; // Make even - - p_resampled = new pipebuf(m_objScheduler, "resampled", BUF_BASEBAND); - -#if 1 // Cut in middle of roll-off region - float Fcut = (m_objCfg.Fm / 2) * (1 + m_objCfg.rolloff / 2) / m_objCfg.Fs; -#else // Cut at beginning of roll-off region - float Fcut = (m_objCfg.Fm/2) / cfg.Fs; -#endif - - ncoeffs = filtergen::lowpass(order, Fcut, &coeffs); - - filtergen::normalize_dcgain(ncoeffs, coeffs, 1); - - r_resample = new fir_filter(m_objScheduler, ncoeffs, coeffs, *p_preprocessed, *p_resampled, decim); - p_preprocessed = p_resampled; - m_objCfg.Fs /= decim; - } // DECIMATION // (Unless already done in resampler) - if (!m_objCfg.resample && m_objCfg.decim > 1) - { - decim = m_objCfg.decim; - - p_decimated = new pipebuf(m_objScheduler, "decimated", BUF_BASEBAND); - p_decim = new decimator(m_objScheduler, decim, *p_preprocessed, *p_decimated); - p_preprocessed = p_decimated; - m_objCfg.Fs /= decim; - } + //******** -> if ( !m_objCfg.resample && m_objCfg.decim>1 ) //Resampling FS + // Generic constellation receiver p_symbols = new pipebuf(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS); - p_freq = new pipebuf(m_objScheduler, "freq", BUF_SLOW); - p_ss = new pipebuf(m_objScheduler, "SS", BUF_SLOW); - p_mer = new pipebuf(m_objScheduler, "MER", BUF_SLOW); - p_sampled = new pipebuf(m_objScheduler, "PSK symbols", BUF_BASEBAND); + p_freq = new pipebuf (m_objScheduler, "freq", BUF_SLOW); + p_ss = new pipebuf (m_objScheduler, "SS", BUF_SLOW); + p_mer = new pipebuf (m_objScheduler, "MER", BUF_SLOW); + p_sampled = new pipebuf (m_objScheduler, "PSK symbols", BUF_BASEBAND); - switch (m_objCfg.sampler) + switch ( m_objCfg.sampler ) { - case SAMP_NEAREST: - sampler = new nearest_sampler(); - break; + case SAMP_NEAREST: + sampler = new nearest_sampler(); + break; - case SAMP_LINEAR: - sampler = new linear_sampler(); - break; + case SAMP_LINEAR: + sampler = new linear_sampler(); + break; - case SAMP_RRC: - { - - if (m_objCfg.rrc_steps == 0) + case SAMP_RRC: { + + + if ( m_objCfg.rrc_steps == 0 ) + { // At least 64 discrete sampling points between symbols - m_objCfg.rrc_steps = max(1, (int) (64 * m_objCfg.Fm / m_objCfg.Fs)); + m_objCfg.rrc_steps = max(1, (int)(64*m_objCfg.Fm / m_objCfg.Fs)); + } + + float Frrc = m_objCfg.Fs * m_objCfg.rrc_steps; // Sample freq of the RRC filter + float transition = (m_objCfg.Fm/2) * m_objCfg.rolloff; + int order = m_objCfg.rrc_rej * Frrc / (22*transition); + ncoeffs_sampler = filtergen::root_raised_cosine(order, m_objCfg.Fm/Frrc, m_objCfg.rolloff, &coeffs_sampler); + + sampler = new fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); + break; } - float Frrc = m_objCfg.Fs * m_objCfg.rrc_steps; // Sample freq of the RRC filter - float transition = (m_objCfg.Fm / 2) * m_objCfg.rolloff; - int order = m_objCfg.rrc_rej * Frrc / (22 * transition); - ncoeffs_sampler = filtergen::root_raised_cosine(order, m_objCfg.Fm / Frrc, m_objCfg.rolloff, &coeffs_sampler); - - sampler = new fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); - break; - } - - default: - fatal("Interpolator not implemented"); + default: + qCritical("DATVDemod::InitDATVFramework: Interpolator not implemented"); + return; } m_objDemodulator = new cstln_receiver(m_objScheduler, sampler, *p_preprocessed, *p_symbols, p_freq, p_ss, p_mer, p_sampled); - if (m_objCfg.standard == DVB_S) + if ( m_objCfg.standard == DVB_S ) { - if (m_objCfg.constellation != cstln_lut<256>::QPSK && m_objCfg.constellation != cstln_lut<256>::BPSK) - { - qWarning("DATVDemod::InitDATVFramework: non-standard constellation for DVB-S"); - } + if ( m_objCfg.constellation != cstln_lut<256>::QPSK && m_objCfg.constellation != cstln_lut<256>::BPSK ) + { + fprintf(stderr, "Warning: non-standard constellation for DVB-S\n"); + } } - if (m_objCfg.standard == DVB_S2) + if ( m_objCfg.standard == DVB_S2 ) { - // For DVB-S2 testing only. - // Constellation should be determined from PL signalling. - qWarning("DATVDemod::InitDATVFramework: DVB-S2: Testing symbol sampler only."); + // For DVB-S2 testing only. + // Constellation should be determined from PL signalling. + fprintf(stderr, "DVB-S2: Testing symbol sampler only.\n"); } m_objDemodulator->cstln = make_dvbs2_constellation(m_objCfg.constellation, m_objCfg.fec); - if (m_objCfg.hard_metric) + if ( m_objCfg.hard_metric ) { - m_objDemodulator->cstln->harden(); + m_objDemodulator->cstln->harden(); } - m_objDemodulator->set_omega(m_objCfg.Fs / m_objCfg.Fm); + m_objDemodulator->set_omega(m_objCfg.Fs/m_objCfg.Fm); - if (m_objCfg.Ftune) + //******** if ( m_objCfg.Ftune ) + //{ + // m_objDemodulator->set_freq(m_objCfg.Ftune/m_objCfg.Fs); + //} + + if ( m_objCfg.allow_drift ) { - - m_objDemodulator->set_freq(m_objCfg.Ftune / m_objCfg.Fs); + m_objDemodulator->set_allow_drift(true); } - if (m_objCfg.allow_drift) + //******** -> if ( m_objCfg.viterbi ) + if ( m_objCfg.viterbi ) { - m_objDemodulator->set_allow_drift(true); + m_objDemodulator->pll_adjustment /= 6; } - if (m_objCfg.viterbi) - { - m_objDemodulator->pll_adjustment /= 6; - } m_objDemodulator->meas_decimation = decimation(m_objCfg.Fs, m_objCfg.Finfo); // TRACKING FILTERS - if (r_resample) - { - r_resample->freq_tap = &m_objDemodulator->freq_tap; - r_resample->tap_multiplier = 1.0 / decim; - r_resample->freq_tol = m_objCfg.Fm / (m_objCfg.Fs * decim) * 0.1; - } - if (r_cnr) + if ( r_cnr ) { - r_cnr->freq_tap = &m_objDemodulator->freq_tap; - r_cnr->tap_multiplier = 1.0 / decim; + r_cnr->freq_tap = &m_objDemodulator->freq_tap; + r_cnr->tap_multiplier = 1.0 / decim; } //constellation - m_objRegisteredDATVScreen->resizeDATVScreen(256, 256); + m_objRegisteredDATVScreen->resizeDATVScreen(256,256); - r_scope_symbols = new datvconstellation(m_objScheduler, *p_sampled, -128, 128, NULL, m_objRegisteredDATVScreen); + r_scope_symbols = new datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredDATVScreen); r_scope_symbols->decimation = 1; r_scope_symbols->cstln = &m_objDemodulator->cstln; @@ -729,20 +703,22 @@ void DATVDemod::InitDATVFramework() r_deconv = NULL; - if (m_objCfg.viterbi) + //******** -> if ( m_objCfg.viterbi ) + + if ( m_objCfg.viterbi ) { - if (m_objCfg.fec == FEC23 && (m_objDemodulator->cstln->nsymbols == 4 || m_objDemodulator->cstln->nsymbols == 64)) - { - m_objCfg.fec = FEC46; - } + if ( m_objCfg.fec==FEC23 && (m_objDemodulator->cstln->nsymbols==4 || m_objDemodulator->cstln->nsymbols==64) ) + { + m_objCfg.fec = FEC46; + } - //To uncomment -> Linking Problem : undefined symbol: _ZN7leansdr21viterbi_dec_interfaceIhhiiE6updateEPiS2_ - r = new viterbi_sync(m_objScheduler, (*p_symbols), (*p_bytes), m_objDemodulator->cstln, m_objCfg.fec); + //To uncomment -> Linking Problem : undefined symbol: _ZN7leansdr21viterbi_dec_interfaceIhhiiE6updateEPiS2_ + r = new viterbi_sync(m_objScheduler, (*p_symbols), (*p_bytes), m_objDemodulator->cstln, m_objCfg.fec); - if (m_objCfg.fastlock) - { - r->resync_period = 1; - } + if ( m_objCfg.fastlock ) + { + r->resync_period = 1; + } } else { @@ -750,71 +726,58 @@ void DATVDemod::InitDATVFramework() r_deconv->fastlock = m_objCfg.fastlock; } - if (m_objCfg.hdlc) - { - p_descrambled = new pipebuf(m_objScheduler, "descrambled", BUF_MPEGBYTES); - r_etr192_descrambler = new etr192_descrambler(m_objScheduler, (*p_bytes), *p_descrambled); - p_frames = new pipebuf(m_objScheduler, "frames", BUF_MPEGBYTES); - r_sync = new hdlc_sync(m_objScheduler, *p_descrambled, *p_frames, 2, 278); + //******* -> if ( m_objCfg.hdlc ) - if (m_objCfg.fastlock) - { - r_sync->resync_period = 1; - } + p_mpegbytes = new pipebuf (m_objScheduler, "mpegbytes", BUF_MPEGBYTES); + p_lock = new pipebuf (m_objScheduler, "lock", BUF_SLOW); + p_locktime = new pipebuf (m_objScheduler, "locktime", BUF_PACKETS); - if (m_objCfg.packetized) - { - r_sync->header16 = true; - } - - } - - p_mpegbytes = new pipebuf(m_objScheduler, "mpegbytes", BUF_MPEGBYTES); - p_lock = new pipebuf(m_objScheduler, "lock", BUF_SLOW); - p_locktime = new pipebuf(m_objScheduler, "locktime", BUF_PACKETS); - - if (!m_objCfg.hdlc) - { - r_sync_mpeg = new mpeg_sync(m_objScheduler, *p_bytes, *p_mpegbytes, r_deconv, p_lock, p_locktime); - r_sync_mpeg->fastlock = m_objCfg.fastlock; - } + r_sync_mpeg = new mpeg_sync(m_objScheduler, *p_bytes, *p_mpegbytes, r_deconv, p_lock, p_locktime); + r_sync_mpeg->fastlock = m_objCfg.fastlock; // DEINTERLEAVING - p_rspackets = new pipebuf >(m_objScheduler, "RS-enc packets", BUF_PACKETS); + p_rspackets = new pipebuf< rspacket >(m_objScheduler, "RS-enc packets", BUF_PACKETS); r_deinter = new deinterleaver(m_objScheduler, *p_mpegbytes, *p_rspackets); + // REED-SOLOMON p_vbitcount = new pipebuf(m_objScheduler, "Bits processed", BUF_PACKETS); p_verrcount = new pipebuf(m_objScheduler, "Bits corrected", BUF_PACKETS); p_rtspackets = new pipebuf(m_objScheduler, "rand TS packets", BUF_PACKETS); - r_rsdec = new rs_decoder(m_objScheduler, *p_rspackets, *p_rtspackets, p_vbitcount, p_verrcount); + r_rsdec = new rs_decoder (m_objScheduler, *p_rspackets, *p_rtspackets, p_vbitcount, p_verrcount); + // BER ESTIMATION - p_vber = new pipebuf(m_objScheduler, "VBER", BUF_SLOW); - r_vber = new rate_estimator(m_objScheduler, *p_verrcount, *p_vbitcount, *p_vber); - r_vber->sample_size = m_objCfg.Fm / 2; // About twice per second, depending on CR + + /* + p_vber = new pipebuf (m_objScheduler, "VBER", BUF_SLOW); + r_vber = new rate_estimator (m_objScheduler, *p_verrcount, *p_vbitcount, *p_vber); + r_vber->sample_size = m_objCfg.Fm/2; // About twice per second, depending on CR // Require resolution better than 2E-5 - if (r_vber->sample_size < 50000) + if ( r_vber->sample_size < 50000 ) { r_vber->sample_size = 50000; } + */ // DERANDOMIZATION p_tspackets = new pipebuf(m_objScheduler, "TS packets", BUF_PACKETS); r_derand = new derandomizer(m_objScheduler, *p_rtspackets, *p_tspackets); - // OUTPUT - r_videoplayer = new datvvideoplayer(m_objScheduler, *p_tspackets, m_objVideoStream); - m_blnDVBInitialized = true; + // OUTPUT + r_videoplayer = new datvvideoplayer(m_objScheduler, *p_tspackets,m_objVideoStream); + + m_blnDVBInitialized=true; } void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { + qint16 * ptrBufferToRelease=NULL; float fltI; float fltQ; cf32 objIQ; @@ -822,9 +785,10 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect fftfilt::cmplx *objRF; int intRFOut; + //********** Bis repetita : Let's rock and roll buddy ! ********** + #ifdef EXTENDED_DIRECT_SAMPLE - qint16 * ptrBufferToRelease=NULL; qint16 * ptrBuffer; qint32 intLen; @@ -843,6 +807,7 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect ptrBuffer ++; fltQ= ((qint32) (*ptrBuffer)) << 4; ptrBuffer ++; + #else for (SampleVector::const_iterator it = begin; it != end; ++it /* ++it **/) @@ -851,74 +816,75 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect fltQ = it->imag(); #endif + //********** demodulation ********** - if ((m_blnDVBInitialized == false) || (m_blnNeedConfigUpdate == true)) + + if (m_blnNeedConfigUpdate) { - m_blnNeedConfigUpdate = false; + + m_objSettingsMutex.lock(); + + m_blnNeedConfigUpdate=false; + InitDATVFramework(); + + m_objSettingsMutex.unlock(); + } + //********** iq stream **************** - if (m_lngReadIQ > p_rawiq_writer->writable()) + Complex objC(fltI,fltQ); + + objC *= m_objNCO.nextIQ(); + + intRFOut = m_objRFFilter->runFilt(objC, &objRF); // filter RF before demod + + for (int intI = 0 ; intI < intRFOut; intI++) { - m_objScheduler->step(); + objIQ.re = objRF->real(); + objIQ.im = objRF->imag(); - m_objRegisteredDATVScreen->renderImage(NULL); + objRF ++; - m_lngReadIQ = 0; - p_rawiq_writer = new pipewriter(*p_rawiq); - } - - if (false) - { - objIQ.re = fltI; - objIQ.im = fltQ; - - p_rawiq_writer->write(objIQ); - - m_lngReadIQ++; - } - else - { - - Complex objC(fltI, fltQ); - - objC *= m_objNCO.nextIQ(); - - intRFOut = m_objRFFilter->runFilt(objC, &objRF); // filter RF before demod - - for (int intI = 0; intI < intRFOut; intI++) + if (m_blnDVBInitialized + && (p_rawiq_writer!=NULL) + && (m_objScheduler!=NULL)) { - objIQ.re = objRF->real(); - objIQ.im = objRF->imag(); - p_rawiq_writer->write(objIQ); - - objRF++; m_lngReadIQ++; + + //Leave +1 by safety + if((m_lngReadIQ+1)>=p_rawiq_writer->writable()) + { + m_objScheduler->step(); + + m_lngReadIQ=0; + delete p_rawiq_writer; + p_rawiq_writer = new pipewriter(*p_rawiq); + } } + } //********** demodulation ********** } -#ifdef EXTENDED_DIRECT_SAMPLE if(ptrBufferToRelease!=NULL) { delete ptrBufferToRelease; } -#endif - //m_objSettingsMutex.unlock(); + } void DATVDemod::start() { - //m_objTimer.start(); + } void DATVDemod::stop() @@ -928,57 +894,34 @@ void DATVDemod::stop() bool DATVDemod::handleMessage(const Message& cmd) { - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& objNotif = (DownChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "DATVDemod::handleMessage"; - qDebug() << "DATVDemod::handleMessage: MsgChannelizerNotification:" << " intMsps: " << objNotif.getSampleRate(); - - if (m_objRunning.intMsps != objNotif.getSampleRate()) - { - m_objRunning.intMsps = objNotif.getSampleRate(); - m_objRunning.intSampleRate = m_objRunning.intMsps; - - qDebug("DATVDemod::handleMessage: Sample Rate: %d", m_objRunning.intSampleRate); - ApplySettings(); - } - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - - qDebug() << "DATVDemod::handleMessage: MsgConfigureChannelizer:" - << " sampleRate: " << m_channelizer->getInputSampleRate() - << " centerFrequency: " << cfg.getCenterFrequency(); - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), m_channelizer->getInputSampleRate(), cfg.getCenterFrequency()); - //m_objRunning.intCenterFrequency); - //<< " centerFrequency: " << m_objRunning.intCenterFrequency; - - return true; - } - else if (MsgConfigureDATVDemod::match(cmd)) - { - qDebug() << "DATVDemod::handleMessage: MsgConfigureDATVDemod"; + if (MsgConfigureDATVDemod::match(cmd)) + { MsgConfigureDATVDemod& objCfg = (MsgConfigureDATVDemod&) cmd; - if ((objCfg.m_objMsgConfig.blnAllowDrift != m_objRunning.blnAllowDrift) || (objCfg.m_objMsgConfig.intRFBandwidth != m_objRunning.intRFBandwidth) - || (objCfg.m_objMsgConfig.intCenterFrequency != m_objRunning.intCenterFrequency) || (objCfg.m_objMsgConfig.blnFastLock != m_objRunning.blnFastLock) - || (objCfg.m_objMsgConfig.blnHardMetric != m_objRunning.blnHardMetric) || (objCfg.m_objMsgConfig.blnHDLC != m_objRunning.blnHDLC) - || (objCfg.m_objMsgConfig.blnResample != m_objRunning.blnResample) || (objCfg.m_objMsgConfig.blnViterbi != m_objRunning.blnViterbi) - || (objCfg.m_objMsgConfig.enmFEC != m_objRunning.enmFEC) || (objCfg.m_objMsgConfig.enmModulation != m_objRunning.enmModulation) - || (objCfg.m_objMsgConfig.enmStandard != m_objRunning.enmStandard) || (objCfg.m_objMsgConfig.intNotchFilters != m_objRunning.intNotchFilters) - || (objCfg.m_objMsgConfig.intSymbolRate != m_objRunning.intSymbolRate)) - { + + if((objCfg.m_objMsgConfig.blnAllowDrift != m_objRunning.blnAllowDrift) + || (objCfg.m_objMsgConfig.intRFBandwidth != m_objRunning.intRFBandwidth) + || (objCfg.m_objMsgConfig.intCenterFrequency != m_objRunning.intCenterFrequency) + || (objCfg.m_objMsgConfig.blnFastLock != m_objRunning.blnFastLock) + || (objCfg.m_objMsgConfig.blnHardMetric != m_objRunning.blnHardMetric) + || (objCfg.m_objMsgConfig.enmFilter != m_objRunning.enmFilter) + || (objCfg.m_objMsgConfig.fltRollOff != m_objRunning.fltRollOff) + || (objCfg.m_objMsgConfig.blnViterbi != m_objRunning.blnViterbi) + || (objCfg.m_objMsgConfig.enmFEC != m_objRunning.enmFEC) + || (objCfg.m_objMsgConfig.enmModulation != m_objRunning.enmModulation) + || (objCfg.m_objMsgConfig.enmStandard != m_objRunning.enmStandard) + || (objCfg.m_objMsgConfig.intNotchFilters != m_objRunning.intNotchFilters) + || (objCfg.m_objMsgConfig.intSymbolRate != m_objRunning.intSymbolRate) + || (objCfg.m_objMsgConfig.intExcursion != m_objRunning.intExcursion)) + { m_objRunning.blnAllowDrift = objCfg.m_objMsgConfig.blnAllowDrift; m_objRunning.blnFastLock = objCfg.m_objMsgConfig.blnFastLock; m_objRunning.blnHardMetric = objCfg.m_objMsgConfig.blnHardMetric; - m_objRunning.blnHDLC = objCfg.m_objMsgConfig.blnHDLC; - m_objRunning.blnResample = objCfg.m_objMsgConfig.blnResample; + m_objRunning.enmFilter = objCfg.m_objMsgConfig.enmFilter; + m_objRunning.fltRollOff = objCfg.m_objMsgConfig.fltRollOff; m_objRunning.blnViterbi = objCfg.m_objMsgConfig.blnViterbi; m_objRunning.enmFEC = objCfg.m_objMsgConfig.enmFEC; m_objRunning.enmModulation = objCfg.m_objMsgConfig.enmModulation; @@ -987,49 +930,48 @@ bool DATVDemod::handleMessage(const Message& cmd) m_objRunning.intSymbolRate = objCfg.m_objMsgConfig.intSymbolRate; m_objRunning.intRFBandwidth = objCfg.m_objMsgConfig.intRFBandwidth; m_objRunning.intCenterFrequency = objCfg.m_objMsgConfig.intCenterFrequency; + m_objRunning.intExcursion = objCfg.m_objMsgConfig.intExcursion; + + qDebug() << "ATVDemod::handleMessage: MsgConfigureDATVDemod: sampleRate: " << m_objRunning.intMsps + << " sampleRate: " << m_objRunning.intSampleRate; ApplySettings(); - } + } - return true; - } - else if (DSPSignalNotification::match(cmd)) - { - qDebug() << "DATVDemod::handleMessage: DSPSignalNotification"; - return true; - } - else - { - return false; - } + + return true; + } + else + { + return false; + } } void DATVDemod::ApplySettings() { - if (m_objRunning.intMsps == 0) + if(m_objRunning.intMsps==0) { return; } - //m_objSettingsMutex.lock(); - InitDATVParameters( - m_objRunning.intMsps, - m_objRunning.intRFBandwidth, - m_objRunning.intCenterFrequency, - m_objRunning.enmStandard, - m_objRunning.enmModulation, - m_objRunning.enmFEC, - m_objRunning.intSampleRate, - m_objRunning.intSymbolRate, - m_objRunning.intNotchFilters, - m_objRunning.blnAllowDrift, - m_objRunning.blnFastLock, - m_objRunning.blnHDLC, - m_objRunning.blnHardMetric, - m_objRunning.blnResample, - m_objRunning.blnViterbi); + InitDATVParameters(m_objRunning.intMsps, + m_objRunning.intRFBandwidth, + m_objRunning.intCenterFrequency, + m_objRunning.enmStandard, + m_objRunning.enmModulation, + m_objRunning.enmFEC, + m_objRunning.intSampleRate, + m_objRunning.intSymbolRate, + m_objRunning.intNotchFilters, + m_objRunning.blnAllowDrift, + m_objRunning.blnFastLock, + m_objRunning.enmFilter, + m_objRunning.blnHardMetric, + m_objRunning.fltRollOff, + m_objRunning.blnViterbi, + m_objRunning.intExcursion); } @@ -1038,10 +980,3 @@ int DATVDemod::GetSampleRate() return m_objRunning.intMsps; } -void DATVDemod::channelSampleRateChanged() -{ - qDebug("DATVDemod::channelSampleRateChanged"); - // reconfigure to get full available bandwidth - m_channelizer->configure(m_channelizer->getInputMessageQueue(), m_channelizer->getInputSampleRate(), m_channelizer->getRequestedCenterFrequency()); - // TODO: forward to GUI if necessary -} diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 161711c12..be7906145 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -68,82 +68,70 @@ class DownChannelizer; using namespace leansdr; -enum DATVModulation -{ - BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 -}; -enum dvb_version -{ - DVB_S, DVB_S2 -}; -enum dvb_sampler -{ - SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC -}; +enum DATVModulation { BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 }; +enum dvb_version { DVB_S, DVB_S2 }; +enum dvb_sampler { SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC }; -inline int decimation(float Fin, float Fout) -{ - int d = Fin / Fout; - return max(d, 1); -} +inline int decimation(float Fin, float Fout) { int d = Fin / Fout; return max(d, 1); } struct config { - dvb_version standard; - dvb_sampler sampler; + dvb_version standard; + dvb_sampler sampler; - int buf_factor; // Buffer sizing - float Fs; // Sampling frequency (Hz) - float Fderot; // Shift the signal (Hz). Note: Ftune is faster - int anf; // Number of auto notch filters - bool cnr; // Measure CNR - unsigned int decim; // Decimation, 0=auto - float Fm; // QPSK symbol rate (Hz) - cstln_lut<256>::predef constellation; - code_rate fec; - float Ftune; // Bias frequency for the QPSK demodulator (Hz) - bool allow_drift; - bool fastlock; - bool viterbi; - bool hard_metric; - bool resample; - float resample_rej; // Approx. filter rejection in dB - int rrc_steps; // Discrete steps between symbols, 0=auto - float rrc_rej; // Approx. RRC filter rejection in dB - float rolloff; // Roll-off 0..1 - bool hdlc; // Expect HDLC frames instead of MPEG packets - bool packetized; // Output frames with 16-bit BE length - float Finfo; // Desired refresh rate on fd_info (Hz) + int buf_factor; // Buffer sizing + float Fs; // Sampling frequency (Hz) + float Fderot; // Shift the signal (Hz). Note: Ftune is faster + int anf; // Number of auto notch filters + bool cnr; // Measure CNR + unsigned int decim; // Decimation, 0=auto + float Fm; // QPSK symbol rate (Hz) + cstln_lut<256>::predef constellation; + code_rate fec; + float Ftune; // Bias frequency for the QPSK demodulator (Hz) + bool allow_drift; + bool fastlock; + bool viterbi; + bool hard_metric; + bool resample; + float resample_rej; // Approx. filter rejection in dB + int rrc_steps; // Discrete steps between symbols, 0=auto + float rrc_rej; // Approx. RRC filter rejection in dB + float rolloff; // Roll-off 0..1 + bool hdlc; // Expect HDLC frames instead of MPEG packets + bool packetized; // Output frames with 16-bit BE length + float Finfo; // Desired refresh rate on fd_info (Hz) - config() : - standard(DVB_S), - sampler(SAMP_LINEAR), - buf_factor(4), - Fs(2.4e6), - Fderot(0), - anf(1), - cnr(false), - decim(0), - Fm(2e6), - constellation(cstln_lut<256>::QPSK), - fec(FEC12), - Ftune(0), - allow_drift(false), - fastlock(true), - viterbi(false), - hard_metric(false), - resample(false), - resample_rej(10), - rrc_steps(0), - rrc_rej(10), - rolloff(0.35), - hdlc(false), - packetized(false), - Finfo(5) - { - } + config() : + standard(DVB_S), + sampler(SAMP_LINEAR), + buf_factor(4), + Fs(2.4e6), + Fderot(0), + anf(1), + cnr(false), + decim(0), + Fm(2e6), + constellation(cstln_lut<256>::QPSK), + fec(FEC12), + Ftune(0), + allow_drift(false), + fastlock(true), + viterbi(false), + hard_metric(false), + resample(false), + resample_rej(10), + rrc_steps(0), + rrc_rej(10), + rolloff(0.35), + hdlc(false), + packetized(false), + Finfo(5) + { + } }; + struct DATVConfig { int intMsps; @@ -157,110 +145,72 @@ struct DATVConfig int intNotchFilters; bool blnAllowDrift; bool blnFastLock; - bool blnHDLC; + dvb_sampler enmFilter; bool blnHardMetric; - bool blnResample; + float fltRollOff; bool blnViterbi; + int intExcursion; DATVConfig() : - intMsps(1024000), - intRFBandwidth(1024000), - intCenterFrequency(0), - enmStandard(DVB_S), - enmModulation(BPSK), - enmFEC(FEC12), - intSampleRate(1024000), - intSymbolRate(250000), - intNotchFilters(1), - blnAllowDrift(false), - blnFastLock(false), - blnHDLC(false), - blnHardMetric(false), - blnResample(false), - blnViterbi(false) + intMsps(1024000), + intRFBandwidth(1024000), + intCenterFrequency(0), + enmStandard(DVB_S), + enmModulation(BPSK), + enmFEC(FEC12), + intSampleRate(1024000), + intSymbolRate(250000), + intNotchFilters(1), + blnAllowDrift(false), + blnFastLock(false), + enmFilter(SAMP_LINEAR), + blnHardMetric(false), + fltRollOff(0.35), + blnViterbi(false), + intExcursion(10) { } }; -class DATVDemod: public BasebandSampleSink, public ChannelSinkAPI + +class DATVDemod : public BasebandSampleSink, public ChannelSinkAPI { -Q_OBJECT + Q_OBJECT public: - class MsgConfigureChannelizer: public Message - { - MESSAGE_CLASS_DECLARATION - - public: - int getCenterFrequency() const - { - return m_centerFrequency; - } - - static MsgConfigureChannelizer* create(int centerFrequency) - { - return new MsgConfigureChannelizer(centerFrequency); - } - - private: - int m_centerFrequency; - - MsgConfigureChannelizer(int centerFrequency) : - Message(), m_centerFrequency(centerFrequency) - { - } - }; DATVDemod(DeviceSourceAPI *); ~DATVDemod(); - virtual void destroy() - { - delete this; - } - virtual void getIdentifier(QString& id) - { - id = objectName(); - } - virtual void getTitle(QString& title) - { - title = objectName(); - } - virtual qint64 getCenterFrequency() const - { - return m_objRunning.intCenterFrequency; - } + virtual void destroy() { delete this; } + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = objectName(); } + virtual qint64 getCenterFrequency() const { return m_objRunning.intCenterFrequency; } - virtual QByteArray serialize() const - { - return QByteArray(); - } - virtual bool deserialize(const QByteArray& data __attribute__((unused))) - { - return false; - } + virtual QByteArray serialize() const { return QByteArray(); } + virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } void configure( - MessageQueue* objMessageQueue, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi); + MessageQueue* objMessageQueue, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intfltExcursion); - 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 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); bool SetDATVScreen(DATVScreen *objScreen); DATVideostream * SetVideoRender(DATVideoRender *objScreen); @@ -268,36 +218,62 @@ public: bool PlayVideo(bool blnStartStop); void InitDATVParameters( - int intMsps, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSampleRate, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi); + int intMsps, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSampleRate, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intEExcursion); - void CleanUpDATVFramework(); + void CleanUpDATVFramework(bool blnRelease); int GetSampleRate(); void InitDATVFramework(); static const QString m_channelIdURI; static const QString m_channelId; -private: - class MsgConfigureDATVDemod: public Message + + class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION + MESSAGE_CLASS_DECLARATION - public: - static MsgConfigureDATVDemod* create( + public: + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int centerFrequency) + { + return new MsgConfigureChannelizer(centerFrequency); + } + + private: + int m_centerFrequency; + + MsgConfigureChannelizer(int centerFrequency) : + Message(), + m_centerFrequency(centerFrequency) + { } + }; + +private slots: + void channelSampleRateChanged(); + +private: + class MsgConfigureDATVDemod : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + static MsgConfigureDATVDemod* create( int intRFBandwidth, int intCenterFrequency, dvb_version enmStandard, @@ -307,60 +283,50 @@ private: int intNotchFilters, bool blnAllowDrift, bool blnFastLock, - bool blnHDLC, + dvb_sampler enmFilter, bool blnHardMetric, - bool blnResample, - bool blnViterbi) - { - return new MsgConfigureDATVDemod( - intRFBandwidth, - intCenterFrequency, - enmStandard, - enmModulation, - enmFEC, - intSymbolRate, - intNotchFilters, - blnAllowDrift, - blnFastLock, - blnHDLC, - blnHardMetric, - blnResample, - blnViterbi); - } + float fltRollOff, + bool blnViterbi, + int intExcursion) + { + return new MsgConfigureDATVDemod(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,enmFilter,blnHardMetric,fltRollOff, blnViterbi, intExcursion); + } - DATVConfig m_objMsgConfig; + DATVConfig m_objMsgConfig; - private: - MsgConfigureDATVDemod( - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) : + private: + MsgConfigureDATVDemod( + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intExcursion) : Message() - { - m_objMsgConfig.intRFBandwidth = intRFBandwidth; - m_objMsgConfig.intCenterFrequency = intCenterFrequency; - m_objMsgConfig.enmStandard = enmStandard; - m_objMsgConfig.enmModulation = enmModulation; - m_objMsgConfig.enmFEC = enmFEC; - m_objMsgConfig.intSymbolRate = intSymbolRate; - m_objMsgConfig.intNotchFilters = intNotchFilters; - m_objMsgConfig.blnAllowDrift = blnAllowDrift; - m_objMsgConfig.blnFastLock = blnFastLock; - m_objMsgConfig.blnHDLC = blnHDLC; - m_objMsgConfig.blnHardMetric = blnHardMetric; - m_objMsgConfig.blnResample = blnResample; - m_objMsgConfig.blnViterbi = blnViterbi; - } + { + m_objMsgConfig.intRFBandwidth = intRFBandwidth; + m_objMsgConfig.intCenterFrequency = intCenterFrequency; + m_objMsgConfig.enmStandard = enmStandard; + m_objMsgConfig.enmModulation = enmModulation; + m_objMsgConfig.enmFEC = enmFEC; + m_objMsgConfig.intSymbolRate = intSymbolRate; + m_objMsgConfig.intNotchFilters = intNotchFilters; + m_objMsgConfig.blnAllowDrift = blnAllowDrift; + m_objMsgConfig.blnFastLock = blnFastLock; + m_objMsgConfig.enmFilter= enmFilter; + m_objMsgConfig.blnHardMetric = blnHardMetric; + m_objMsgConfig.fltRollOff = fltRollOff; + m_objMsgConfig.blnViterbi = blnViterbi; + m_objMsgConfig.intExcursion = intExcursion; + } }; unsigned long m_lngExpectedReadIQ; @@ -403,7 +369,7 @@ private: cnr_fft *r_cnr; //FILTERING - fir_filter *r_resample; + fir_filter *r_resample; pipebuf *p_resampled; float *coeffs; int ncoeffs; @@ -442,17 +408,18 @@ private: pipebuf *p_mpegbytes; pipebuf *p_lock; pipebuf *p_locktime; - mpeg_sync *r_sync_mpeg; + mpeg_sync *r_sync_mpeg; + // DEINTERLEAVING - pipebuf > *p_rspackets; + pipebuf< rspacket > *p_rspackets; deinterleaver *r_deinter; // REED-SOLOMON pipebuf *p_vbitcount; pipebuf *p_verrcount; pipebuf *p_rtspackets; - rs_decoder *r_rsdec; + rs_decoder *r_rsdec; // BER ESTIMATION pipebuf *p_vber; @@ -462,6 +429,7 @@ private: pipebuf *p_tspackets; derandomizer *r_derand; + //OUTPUT file_writer *r_stdout; datvvideoplayer *r_videoplayer; @@ -485,6 +453,7 @@ private: bool m_blnInitialized; bool m_blnRenderingVideo; + bool m_blnStartStopVideo; DATVModulation m_enmModulation; @@ -495,9 +464,6 @@ private: QMutex m_objSettingsMutex; void ApplySettings(); - -private slots: - void channelSampleRateChanged(); }; #endif // INCLUDE_DATVDEMOD_H diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index 88ae39d06..aa23c85e9 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -78,14 +78,14 @@ void DATVDemodGUI::resetToDefaults() ui->chkAllowDrift->setChecked(false); ui->chkFastlock->setChecked(true); - ui->chkHDLC->setChecked(false); ui->chkHardMetric->setChecked(false); - ui->chkResample->setChecked(false); ui->chkViterbi->setChecked(false); ui->cmbFEC->setCurrentIndex(0); ui->cmbModulation->setCurrentIndex(0); ui->cmbStandard->setCurrentIndex(0); + ui->cmbFilter->setCurrentIndex(0); + displayRRCParameters(false); ui->spiNotchFilters->setValue(1); ui->prgSynchro->setValue(0); @@ -94,6 +94,9 @@ void DATVDemodGUI::resetToDefaults() ui->spiBandwidth->setValue(512000); ui->spiSymbolRate->setValue(250000); + ui->spiRollOff->setValue(35); + ui->spiExcursion->setValue(10); + blockApplySettings(false); @@ -109,9 +112,9 @@ QByteArray DATVDemodGUI::serialize() const s.writeBool(3, ui->chkAllowDrift->isChecked()); s.writeBool(4, ui->chkFastlock->isChecked()); - s.writeBool(5, ui->chkHDLC->isChecked()); + s.writeS32(5, ui->cmbFilter->currentIndex()); s.writeBool(6, ui->chkHardMetric->isChecked()); - s.writeBool(7, ui->chkResample->isChecked()); + s.writeS32(7, ui->spiRollOff->value()); s.writeBool(8, ui->chkViterbi->isChecked()); s.writeS32(9, ui->cmbFEC->currentIndex()); @@ -121,6 +124,7 @@ QByteArray DATVDemodGUI::serialize() const s.writeS32(12, ui->spiNotchFilters->value()); s.writeS32(13, ui->spiBandwidth->value()); s.writeS32(14, ui->spiSymbolRate->value()); + s.writeS32(15, ui->spiExcursion->value()); return s.final(); } @@ -163,14 +167,16 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) d.readBool(4, &booltmp, false); ui->chkFastlock->setChecked(booltmp); - d.readBool(5, &booltmp, false); - ui->chkHDLC->setChecked(booltmp); + d.readS32(5, &tmp, false); + ui->cmbFilter->setCurrentIndex(tmp); + + displayRRCParameters((tmp==2)); d.readBool(6, &booltmp, false); ui->chkHardMetric->setChecked(booltmp); - d.readBool(7, &booltmp, false); - ui->chkResample->setChecked(booltmp); + d.readS32(7, &tmp, false); + ui->spiRollOff->setValue(tmp); d.readBool(8, &booltmp, false); ui->chkViterbi->setChecked(booltmp); @@ -194,6 +200,9 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) d.readS32(14, &tmp, 250000); ui->spiSymbolRate->setValue(tmp); + d.readS32(15, &tmp, false); + ui->spiExcursion->setValue(tmp); + blockApplySettings(false); m_objChannelMarker.blockSignals(false); @@ -227,11 +236,6 @@ void DATVDemodGUI::channelMarkerHighlightedByCursor() setHighlighted(m_objChannelMarker.getHighlighted()); } -void DATVDemodGUI::channelSampleRateChanged() -{ - qDebug("DATVDemodGUI::channelSampleRateChanged"); - applySettings(); -} void DATVDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { @@ -239,14 +243,6 @@ void DATVDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool void DATVDemodGUI::onMenuDoubleClicked() { - /* - if (!m_blnBasicSettingsShown) - { - m_blnBasicSettingsShown = true; - BasicChannelSettingsWidget* bcsw = new BasicChannelSettingsWidget(&m_objChannelMarker, this); - bcsw->show(); - } - */ } //DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceSourceAPI *objDeviceAPI, QWidget* objParent) : @@ -262,9 +258,7 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - //connect(this, SIGNAL(menuDoubleClickEvent()), this, SLOT(onMenuDoubleClicked())); - //m_objDATVDemod = new DATVDemod(); m_objDATVDemod = (DATVDemod*) rxChannel; m_objDATVDemod->setMessageQueueToGUI(getInputMessageQueue()); @@ -272,12 +266,7 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba connect(m_objDATVDemod->SetVideoRender(ui->screenTV_2),&DATVideostream::onDataPackets,this,&DATVDemodGUI::on_StreamDataAvailable); - - //connect(m_objChannelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - - - //m_objPluginAPI->addThreadedSink(m_objThreadedChannelizer); - //connect(&m_objPluginAPI->getMainWindow()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms + connect(ui->screenTV_2,&DATVideoRender::onMetaDataChanged,this,&DATVDemodGUI::on_StreamMetaDataChanged); m_intPreviousDecodedData=0; m_intLastDecodedData=0; @@ -295,7 +284,6 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba m_objChannelMarker.setCenterFrequency(0); m_objChannelMarker.blockSignals(false); m_objChannelMarker.setVisible(true); - //connect(&m_objChannelMarker, SIGNAL(changed()), this, SLOT(viewChanged())); connect(&m_objChannelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_objChannelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); @@ -304,8 +292,6 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba m_deviceUISet->addChannelMarker(&m_objChannelMarker); m_deviceUISet->addRollupWidget(this); - //ui->screenTV->connectTimer(m_objPluginAPI->getMainWindow()->getMasterTimer()); - ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); resetToDefaults(); // does applySettings() @@ -335,22 +321,18 @@ void DATVDemodGUI::applySettings() DATVModulation enmSelectedModulation; dvb_version enmVersion; code_rate enmFEC; + dvb_sampler enmSampler; if (m_blnDoApplySettings) { - - DATVDemod::MsgConfigureChannelizer *msgChan = DATVDemod::MsgConfigureChannelizer::create(m_objChannelMarker.getCenterFrequency()); m_objDATVDemod->getInputMessageQueue()->push(msgChan); //Bandwidth and center frequency m_objChannelMarker.setBandwidth(ui->spiBandwidth->value()); - //m_objChannelizer->configure(m_objChannelizer->getInputMessageQueue(), m_objChannelizer->getInputSampleRate(), m_objChannelMarker.getCenterFrequency()); setTitleColor(m_objChannelMarker.getColor()); - //DATV parameters: cmbStandard cmbModulation cmbFEC spiBandwidth spiSymbolRate spiNotchFilters chkAllowDrift chkFastlock chkHDLC chkHardMetric chkResample chkViterbi - strStandard = ui->cmbStandard->currentText(); if(strStandard=="DVB-S") @@ -412,6 +394,12 @@ void DATVDemodGUI::applySettings() enmSelectedModulation=BPSK; } + //Viterbi only for BPSK et QPSK + if((enmSelectedModulation!=BPSK) && (enmSelectedModulation!=QPSK)) + { + ui->chkViterbi->setChecked(false); + } + strFEC = ui->cmbFEC->currentText(); @@ -452,21 +440,36 @@ void DATVDemodGUI::applySettings() enmFEC=FEC12; } + if (ui->cmbFilter->currentIndex()==0) + { + enmSampler = SAMP_LINEAR; + } + else if(ui->cmbFilter->currentIndex()==1) + { + enmSampler = SAMP_NEAREST; + } + else + { + enmSampler = SAMP_RRC; + } - m_objDATVDemod->configure(m_objDATVDemod->getInputMessageQueue(), - m_objChannelMarker.getBandwidth(), - m_objChannelMarker.getCenterFrequency(), - enmVersion, - enmSelectedModulation, - enmFEC, - ui->spiSymbolRate->value(), - ui->spiNotchFilters->value(), - ui->chkAllowDrift->isChecked(), - ui->chkFastlock->isChecked(), - ui->chkHDLC->isChecked(), - ui->chkHardMetric->isChecked(), - ui->chkResample->isChecked(), - ui->chkViterbi->isChecked()); + + m_objDATVDemod->configure( + m_objDATVDemod->getInputMessageQueue(), + m_objChannelMarker.getBandwidth(), + m_objChannelMarker.getCenterFrequency(), + enmVersion, + enmSelectedModulation, + enmFEC, + ui->spiSymbolRate->value(), + ui->spiNotchFilters->value(), + ui->chkAllowDrift->isChecked(), + ui->chkFastlock->isChecked(), + enmSampler, + ui->chkHardMetric->isChecked(), + ((float)ui->spiRollOff->value())/100.0f, + ui->chkViterbi->isChecked(), + ui->spiExcursion->value()); qDebug() << "DATVDemodGUI::applySettings:" << " .inputSampleRate: " << 0 /*m_objChannelizer->getInputSampleRate()*/ @@ -622,25 +625,11 @@ void DATVDemodGUI::on_chkHardMetric_clicked() applySettings(); } -/* -void DATVDemodGUI::on_pushButton_clicked() -{ - applySettings(); -} -*/ - void DATVDemodGUI::on_pushButton_2_clicked() { resetToDefaults(); } -/* -void DATVDemodGUI::on_spiSampleRate_valueChanged(int arg1) -{ - applySettings(); -} -*/ - void DATVDemodGUI::on_spiSymbolRate_valueChanged(int arg1 __attribute__((unused))) { applySettings(); @@ -651,21 +640,11 @@ void DATVDemodGUI::on_spiNotchFilters_valueChanged(int arg1 __attribute__((unuse applySettings(); } -void DATVDemodGUI::on_chkHDLC_clicked() -{ - applySettings(); -} - void DATVDemodGUI::on_chkAllowDrift_clicked() { applySettings(); } -void DATVDemodGUI::on_chkResample_clicked() -{ - applySettings(); -} - void DATVDemodGUI::on_pushButton_3_clicked() { @@ -677,23 +656,6 @@ void DATVDemodGUI::on_pushButton_3_clicked() } } -/* -void DATVDemodGUI::on_mediaStateChanged(QMediaPlayer::State state) -{ - switch(state) - { - case QMediaPlayer::PlayingState: - ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPause)); - break; - default: - ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); - break; - } - - ui->lblReadStatus->setText(QString("%1").arg(strLitteralState)); - -} -*/ void DATVDemodGUI::on_pushButton_4_clicked() { @@ -726,7 +688,7 @@ QString DATVDemodGUI::formatBytes(qint64 intBytes) void DATVDemodGUI::on_StreamDataAvailable(int *intPackets __attribute__((unused)), int *intBytes, int *intPercent, qint64 *intTotalReceived) { - ui->lblStatus->setText(QString("Decod: %1B").arg(formatBytes(*intTotalReceived))); + ui->lblStatus->setText(QString("Data: %1B").arg(formatBytes(*intTotalReceived))); m_intLastDecodedData = *intTotalReceived; if((*intPercent)<100) @@ -752,3 +714,68 @@ void DATVDemodGUI::on_chkFastlock_clicked() { applySettings(); } + +void DATVDemodGUI::on_StreamMetaDataChanged(DataTSMetaData2 *objMetaData) +{ + QString strMetaData=""; + + if(objMetaData!=NULL) + { + + if(objMetaData->OK_TransportStream==true) + { + strMetaData.sprintf("PID: %d - Width: %d - Height: %d\r\n%s%s\r\nCodec: %s\r\n",objMetaData->PID + ,objMetaData->Width + ,objMetaData->Height + ,objMetaData->Program.toStdString().c_str() + ,objMetaData->Stream.toStdString().c_str() + ,objMetaData->CodecDescription.toStdString().c_str()); + + } + ui->textEdit->setText(strMetaData); + + ui->chkData->setChecked(objMetaData->OK_Data); + ui->chkTS->setChecked(objMetaData->OK_TransportStream); + ui->chkVS->setChecked(objMetaData->OK_VideoStream); + ui->chkDecoding->setChecked(objMetaData->OK_Decoding); + + if(objMetaData->OK_Decoding==true) + { + ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPause)); + } + else + { + ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + } + + if(objMetaData->Height>0) + { + ui->screenTV_2->setFixedWidth((int)objMetaData->Width*(270.0f/(float)objMetaData->Height)); + } + } +} + +void DATVDemodGUI::displayRRCParameters(bool blnVisible) +{ + ui->spiRollOff->setVisible(blnVisible); + ui->spiExcursion->setVisible(blnVisible); + ui->label_5->setVisible(blnVisible); + ui->label_6->setVisible(blnVisible); +} + +void DATVDemodGUI::on_cmbFilter_currentIndexChanged(int index __attribute__((unused))) +{ + displayRRCParameters((ui->cmbFilter->currentIndex()==2)); + + applySettings(); +} + +void DATVDemodGUI::on_spiRollOff_valueChanged(int arg1 __attribute__((unused))) +{ + applySettings(); +} + +void DATVDemodGUI::on_spiExcursion_valueChanged(int arg1 __attribute__((unused))) +{ + applySettings(); +} diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h index 1f1052d09..406197b43 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.h +++ b/plugins/channelrx/demoddatv/datvdemodgui.h @@ -81,40 +81,38 @@ private slots: void on_cmbFEC_currentIndexChanged(const QString &arg1); void on_chkViterbi_clicked(); void on_chkHardMetric_clicked(); - //void on_pushButton_clicked(); void on_pushButton_2_clicked(); - //void on_spiSampleRate_valueChanged(int arg1); - void on_spiSymbolRate_valueChanged(int arg1); void on_spiNotchFilters_valueChanged(int arg1); - void on_chkHDLC_clicked(); - void on_chkAllowDrift_clicked(); - void on_chkResample_clicked(); - void on_pushButton_3_clicked(); void on_pushButton_4_clicked(); void on_mouseEvent(QMouseEvent* obj); void on_StreamDataAvailable(int *intPackets, int *intBytes, int *intPercent, qint64 *intTotalReceived); + void on_StreamMetaDataChanged(DataTSMetaData2 *objMetaData); void on_spiBandwidth_valueChanged(int arg1); - void on_chkFastlock_clicked(); + void on_cmbFilter_currentIndexChanged(int index); + + void on_spiRollOff_valueChanged(int arg1); + + void on_spiExcursion_valueChanged(int arg1); + private: Ui::DATVDemodGUI* ui; PluginAPI* m_objPluginAPI; DeviceUISet* m_deviceUISet; - //DeviceSourceAPI* m_objDeviceAPI; ChannelMarker m_objChannelMarker; ThreadedBasebandSampleSink* m_objThreadedChannelizer; DownChannelizer* m_objChannelizer; @@ -132,7 +130,6 @@ private: bool m_blnDoApplySettings; bool m_blnButtonPlayClicked; - //explicit DATVDemodGUI(PluginAPI* objPluginAPI, DeviceSourceAPI *objDeviceAPI, QWidget* objParent = NULL); explicit DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* objParent = 0); virtual ~DATVDemodGUI(); @@ -140,6 +137,8 @@ private: void applySettings(); QString formatBytes(qint64 intBytes); + void displayRRCParameters(bool blnVisible); + void leaveEvent(QEvent*); void enterEvent(QEvent*); }; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index 4b374e721..6e77f6c43 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -7,7 +7,7 @@ 0 0 512 - 520 + 640 @@ -19,13 +19,13 @@ 512 - 520 + 640 512 - 520 + 640 @@ -97,6 +97,9 @@ 220 + + Signal constellation + @@ -128,16 +131,14 @@ 21 + + DVB Standard + DVB-S - - - DVB-S2 - - @@ -148,6 +149,9 @@ 21 + + Modulation scheme + BPSK @@ -203,6 +207,9 @@ 21 + + FEC ratio + 1/2 @@ -228,21 +235,6 @@ 7/8 - - - 4/5 - - - - - 8/9 - - - - - 9/10 - - @@ -253,6 +245,9 @@ 20 + + Fast signal decode + FAST LOCK @@ -261,11 +256,14 @@ 140 - 140 + 120 81 20 + + Viterbi algorithm (CPU intensive) + VITERBI @@ -279,6 +277,9 @@ 20 + + Constellation hardening + HARD METRIC @@ -309,19 +310,6 @@ Bandwidth - - - - 10 - 140 - 101 - 20 - - - - HDLC - - @@ -331,6 +319,9 @@ 20 + + Small frequency drift compensation + ALLOW DRIFT @@ -344,6 +335,9 @@ 23 + + Number of stray peaks to suppress + 32 @@ -361,28 +355,18 @@ Notch filter - - - - 140 - 120 - 85 - 20 - - - - RESAMPLE - - 70 - 190 + 200 181 20 + + Video buffer fill + 0 @@ -391,11 +375,14 @@ 10 - 170 + 180 111 16 + + Total number of bytes decoded + - @@ -404,7 +391,7 @@ 230 - 140 + 120 21 22 @@ -422,6 +409,9 @@ 23 + + Symbol rate + 1 @@ -441,6 +431,9 @@ 23 + + RF filter bandwidth + 1000 @@ -455,11 +448,14 @@ 130 - 170 + 180 121 16 + + Stream speed + - @@ -468,7 +464,7 @@ 10 - 190 + 200 61 15 @@ -477,46 +473,189 @@ Buffer: + + + + 10 + 150 + 91 + 22 + + + + Filter + + + + FIR LINEAR + + + + + FIR NEAREST + + + + + FIR RRC + + + + + + + 140 + 150 + 41 + 23 + + + + RRC filter roll off factor + + + 1 + + + 99 + + + 35 + + + + + + 106 + 150 + 28 + 23 + + + + R.off + + + + + + 180 + 150 + 28 + 23 + + + + Exc + + + + + + 210 + 150 + 41 + 23 + + + + Filter excursion (dB) + + + 1 + + + 99 + + + 10 + + 10 - 260 + 250 496 - 240 + 385 496 - 240 + 385 496 - 240 + 385 VIDEO Stream - + + + + 0 + 300 + 281 + 81 + + + + true + + + false + + + + + + 400 + 350 + 91 + 27 + + + + Full screen video (click in the image to return) + + + Full Screen + + + + + + 400 + 300 + 91 + 27 + + + + Start/Stop video streaming + + + Video + + + 0 20 - 358 - 211 + 488 + 272 - - - QLayout::SetMinimumSize - - + + @@ -526,16 +665,19 @@ - 356 - 200 + 480 + 270 - 356 - 200 + 355 + 270 + + Video + @@ -543,73 +685,93 @@ - + + + false + - 360 - 20 - 131 - 211 + 300 + 320 + 85 + 20 - - QFrame::StyledPanel + + Transport stream detected - - QFrame::Raised + + Transport + + + true + + + + + false + + + + 300 + 340 + 85 + 20 + + + + Video data detected + + + Video + + + true + + + + + false + + + + 300 + 360 + 85 + 20 + + + + Video being decoded + + + Decoding + + + true + + + + + false + + + + 300 + 300 + 85 + 20 + + + + Data being received + + + Data + + + true - - - - 10 - 10 - 111 - 27 - - - - Video - - - - - - 10 - 50 - 111 - 27 - - - - Full Screen - - - - - - 10 - 120 - 111 - 16 - - - - - - - - - - - 10 - 90 - 111 - 16 - - - - - - - diff --git a/plugins/channelrx/demoddatv/datvdemodplugin.cpp b/plugins/channelrx/demoddatv/datvdemodplugin.cpp index 690d17ea2..2e3dbe018 100644 --- a/plugins/channelrx/demoddatv/datvdemodplugin.cpp +++ b/plugins/channelrx/demoddatv/datvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DATVDemodPlugin::m_ptrPluginDescriptor = { QString("DATV Demodulator"), - QString("3.13.0"), + QString("3.2.0"), QString("(c) F4HKW for SDRAngel using LeanSDR framework (c) F4DAV"), QString("https://github.com/f4exb/sdrangel"), true, @@ -52,8 +52,7 @@ void DATVDemodPlugin::initPlugin(PluginAPI* ptrPluginAPI) m_ptrPluginAPI = ptrPluginAPI; // register DATV demodulator - m_ptrPluginAPI->registerRxChannel(DATVDemod::m_channelIdURI, DATVDemod::m_channelId, this); - + m_ptrPluginAPI->registerRxChannel(DATVDemod::m_channelIdURI, DATVDemod::m_channelId, this); } PluginInstanceGUI* DATVDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) diff --git a/plugins/channelrx/demoddatv/datvideorender.cpp b/plugins/channelrx/demoddatv/datvideorender.cpp index 41e58a21d..efe47e4dc 100644 --- a/plugins/channelrx/demoddatv/datvideorender.cpp +++ b/plugins/channelrx/demoddatv/datvideorender.cpp @@ -108,7 +108,7 @@ static int64_t SeekFunction(void* opaque, int64_t offset, int whence) return objStream->pos(); } -bool DATVideoRender::InitializeFFMPEG() +void DATVideoRender::ResetMetaData() { MetaData.CodecID=-1; MetaData.PID=-1; @@ -120,7 +120,18 @@ bool DATVideoRender::InitializeFFMPEG() MetaData.Channels=-1; MetaData.CodecDescription= ""; - if(m_blnIsFFMPEGInitialized==true) + MetaData.OK_Decoding=false; + MetaData.OK_TransportStream=false; + MetaData.OK_VideoStream=false; + + emit onMetaDataChanged(&MetaData); +} + +bool DATVideoRender::InitializeFFMPEG() +{ + ResetMetaData(); + + if(m_blnIsFFMPEGInitialized) { return false; } @@ -175,6 +186,8 @@ bool DATVideoRender::PreprocessStream() MetaData.PID = m_objFormatCtx->streams[m_intVideoStreamIndex]->id; MetaData.CodecID = m_objDecoderCtx->codec_id; + MetaData.OK_TransportStream = true; + MetaData.Program=""; MetaData.Stream=""; @@ -199,6 +212,8 @@ bool DATVideoRender::PreprocessStream() MetaData.Stream = QString("%1").arg(objBuffer); } + emit onMetaDataChanged(&MetaData); + //Decoder objCodec = avcodec_find_decoder(m_objDecoderCtx->codec_id); if(objCodec==NULL) @@ -238,10 +253,14 @@ bool DATVideoRender::PreprocessStream() MetaData.Width=m_objDecoderCtx->width; MetaData.Height=m_objDecoderCtx->height; - MetaData.BitRate=m_objDecoderCtx->bit_rate; + MetaData.BitRate= m_objDecoderCtx->bit_rate; MetaData.Channels=m_objDecoderCtx->channels; MetaData.CodecDescription= QString("%1").arg(objCodec->long_name); + MetaData.OK_VideoStream = true; + + emit onMetaDataChanged(&MetaData); + return true; } @@ -251,13 +270,11 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) unsigned char * ptrIOBuffer = NULL; AVIOContext * objIOCtx = NULL; - if(m_blnRunning==true) + if(m_blnRunning) { return false; } - //Only once execution - m_blnRunning=true; if(objDevice==NULL) { @@ -266,19 +283,40 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) return false; } - if(m_blnIsOpen==true) + + if(m_blnIsOpen) { qDebug() << "DATVideoProcess::OpenStream already open"; return false; } + if(objDevice->bytesAvailable()<=0) + { + + qDebug() << "DATVideoProcess::OpenStream no data available"; + + MetaData.OK_Data=false; + emit onMetaDataChanged(&MetaData); + + return false; + } + + //Only once execution + m_blnRunning=true; + + MetaData.OK_Data=true; + emit onMetaDataChanged(&MetaData); + + InitializeFFMPEG(); + if(!m_blnIsFFMPEGInitialized) { qDebug() << "DATVideoProcess::OpenStream FFMPEG not initialized"; + m_blnRunning=false; return false; } @@ -286,9 +324,11 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) { qDebug() << "DATVideoProcess::OpenStream cannot open QIODevice"; + m_blnRunning=false; return false; } + //Connect QIODevice to FFMPEG Reader m_objFormatCtx = avformat_alloc_context(); @@ -297,6 +337,7 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) { qDebug() << "DATVideoProcess::OpenStream cannot alloc format FFMPEG context"; + m_blnRunning=false; return false; } @@ -318,12 +359,13 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) { qDebug() << "DATVideoProcess::OpenStream cannot open stream"; + m_blnRunning=false; return false; } - if(!PreprocessStream()) { + m_blnRunning=false; return false; } @@ -341,14 +383,14 @@ bool DATVideoRender::RenderStream() int intGotFrame; bool blnNeedRenderingSetup; - if (!m_blnIsOpen) + if(!m_blnIsOpen) { - qDebug() << "DATVideoProcess::RenderStream: Stream not open"; + qDebug() << "DATVideoProcess::RenderStream Stream not open"; return false; } - if (m_blnRunning) + if(m_blnRunning) { return false; } @@ -413,7 +455,7 @@ bool DATVideoRender::RenderStream() av_opt_set_int(m_objSwsCtx,"dsth",m_objFrame->height,0); av_opt_set_int(m_objSwsCtx,"dst_format",AV_PIX_FMT_RGB24 ,0); - av_opt_set_int(m_objSwsCtx,"sws_flag",SWS_FAST_BILINEAR /* SWS_BICUBIC*/,0); + av_opt_set_int(m_objSwsCtx,"sws_flag", SWS_FAST_BILINEAR /* SWS_BICUBIC*/,0); if(sws_init_context(m_objSwsCtx, NULL, NULL)<0) { @@ -455,6 +497,8 @@ bool DATVideoRender::RenderStream() MetaData.Width = m_objFrame->width; MetaData.Height = m_objFrame->height; + MetaData.OK_Decoding = true; + emit onMetaDataChanged(&MetaData); } //Frame rendering @@ -482,25 +526,17 @@ bool DATVideoRender::RenderStream() m_blnRunning=false; - //AVDictionaryEntry *objRslt= av_dict_get(fmt_ctx->programs[video_stream_index]->metadata,"service_provider",NULL,0); - //char objErrBuf[1024]; - //memset(objErrBuf,0,1024); - //av_strerror(ret,objErrBuf,1024); - return true; } bool DATVideoRender::CloseStream(QIODevice *objDevice) { - if(m_blnRunning==true) + if(m_blnRunning) { return false; } - //Only once execution - m_blnRunning=true; - if(!objDevice) { qDebug() << "DATVideoProcess::CloseStream QIODevice is NULL"; @@ -508,9 +544,9 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) return false; } - if (!m_blnIsOpen) + if(!m_blnIsOpen) { - qDebug() << "DATVideoProcess::CloseStream: Stream not open"; + qDebug() << "DATVideoProcess::CloseStream Stream not open"; return false; } @@ -522,6 +558,9 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) return false; } + //Only once execution + m_blnRunning=true; + avformat_close_input(&m_objFormatCtx); m_objFormatCtx=NULL; @@ -531,7 +570,6 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) m_objDecoderCtx=NULL; } - if(m_objFrame) { av_frame_unref(m_objFrame); @@ -553,5 +591,8 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) m_intCurrentRenderWidth=-1; m_intCurrentRenderHeight=-1; + ResetMetaData(); + emit onMetaDataChanged(&MetaData); + return true; } diff --git a/plugins/channelrx/demoddatv/datvideorender.h b/plugins/channelrx/demoddatv/datvideorender.h index 3d44cad3a..b3341b9a2 100644 --- a/plugins/channelrx/demoddatv/datvideorender.h +++ b/plugins/channelrx/demoddatv/datvideorender.h @@ -39,13 +39,18 @@ extern "C" #include #include "libswscale/swscale.h" - } struct DataTSMetaData2 { int PID; int CodecID; + + bool OK_Data; + bool OK_Decoding; + bool OK_TransportStream; + bool OK_VideoStream; + QString Program; QString Stream; @@ -53,12 +58,16 @@ struct DataTSMetaData2 int Height; int BitRate; int Channels; + + QString CodecDescription; DataTSMetaData2() { PID=-1; CodecID=-1; + + Program=""; Stream=""; @@ -67,12 +76,19 @@ struct DataTSMetaData2 BitRate=-1; Channels=-1; CodecDescription=""; + + OK_Data=false; + OK_Decoding=false; + OK_TransportStream=false; + OK_VideoStream=false; + } }; class DATVideoRender : public DATVScreen { Q_OBJECT + public: explicit DATVideoRender(QWidget * parent); void SetFullScreen(bool blnFullScreen); @@ -106,18 +122,17 @@ private: bool InitializeFFMPEG(); bool PreprocessStream(); - - + void ResetMetaData(); protected: virtual bool eventFilter(QObject *obj, QEvent *event); signals: - -public slots: + void onMetaDataChanged(DataTSMetaData2 *objMetaData); }; +//To run Video Rendering with a dedicated thread class DATVideoRenderThread: public QThread { @@ -141,12 +156,11 @@ class DATVideoRenderThread: public QThread m_objRenderer = objRenderer; m_objStream = objStream; m_blnRenderingVideo=false; - } void run() { - if(m_blnRenderingVideo==true) + if(m_blnRenderingVideo) { return; } @@ -156,19 +170,11 @@ class DATVideoRenderThread: public QThread return ; } - m_blnRenderingVideo=false; + m_blnRenderingVideo = m_objRenderer->OpenStream(m_objStream); - if(m_objRenderer->OpenStream(m_objStream)) + if(!m_blnRenderingVideo) { - qInfo("DATVideoRenderThread::run: PID: %d W: %d H: %d Codec: %s Data: %s Service: %s", - m_objRenderer->MetaData.PID, - m_objRenderer->MetaData.Width, - m_objRenderer->MetaData.Height, - m_objRenderer->MetaData.CodecDescription.toStdString().c_str(), - m_objRenderer->MetaData.Program.toStdString().c_str(), - m_objRenderer->MetaData.Stream.toStdString().c_str()); - - m_blnRenderingVideo=true; + return; } while((m_objRenderer->RenderStream()) && (m_blnRenderingVideo==true)) @@ -177,6 +183,8 @@ class DATVideoRenderThread: public QThread m_objRenderer->CloseStream(m_objStream); + m_blnRenderingVideo=false; + } void stopRendering() @@ -186,7 +194,6 @@ class DATVideoRenderThread: public QThread private: - DATVideoRender *m_objRenderer; DATVideostream *m_objStream; bool m_blnRenderingVideo; diff --git a/plugins/channelrx/demoddatv/datvideostream.cpp b/plugins/channelrx/demoddatv/datvideostream.cpp index 88807f353..53016cb76 100644 --- a/plugins/channelrx/demoddatv/datvideostream.cpp +++ b/plugins/channelrx/demoddatv/datvideostream.cpp @@ -26,6 +26,7 @@ DATVideostream::DATVideostream(): m_intPacketReceived=0; m_intMemoryLimit = DefaultMemoryLimit; MultiThreaded=false; + ThreadTimeOut=-1; m_objeventLoop.connect(this,SIGNAL(onDataAvailable()), &m_objeventLoop, SLOT(quit()),Qt::QueuedConnection); } @@ -115,7 +116,7 @@ int DATVideostream::pushData(const char * chrData, int intSize) } bool DATVideostream::isSequential() const -{ +{ return true; } @@ -139,10 +140,11 @@ bool DATVideostream::open(OpenMode mode) //PROTECTED qint64 DATVideostream::readData(char *data, qint64 len) -{ +{ QByteArray objCurrentArray; int intEffectiveLen=0; int intExpectedLen=0; + int intThreadLoop=0; intExpectedLen = (int) len; @@ -160,15 +162,25 @@ qint64 DATVideostream::readData(char *data, qint64 len) //DATA in FIFO ? -> Waiting for DATA if((m_objFIFO.isEmpty()) || (m_objFIFO.count()=0) + { + if(intThreadLoop*5>ThreadTimeOut) + { + return -1; + } + } } } else @@ -212,12 +224,12 @@ qint64 DATVideostream::readData(char *data, qint64 len) return (qint64)intEffectiveLen; } -qint64 DATVideostream::writeData(const char *data, qint64 len) +qint64 DATVideostream::writeData(const char *data __attribute__((unused)), qint64 len __attribute__((unused))) { return 0; } -qint64 DATVideostream::readLineData(char *data, qint64 maxSize) -{ +qint64 DATVideostream::readLineData(char *data __attribute__((unused)), qint64 maxSize __attribute__((unused))) +{ return 0; } diff --git a/plugins/channelrx/demoddatv/datvideostream.h b/plugins/channelrx/demoddatv/datvideostream.h index 717ec5ea9..e9387ac69 100644 --- a/plugins/channelrx/demoddatv/datvideostream.h +++ b/plugins/channelrx/demoddatv/datvideostream.h @@ -37,6 +37,7 @@ public: ~DATVideostream(); bool MultiThreaded; + int ThreadTimeOut; int pushData(const char * chrData, int intSize); bool setMemoryLimit(int intMemoryLimit); diff --git a/plugins/channelrx/demoddatv/datvscreen.cpp b/plugins/channelrx/demoddatv/datvscreen.cpp index ea74c249b..754539294 100644 --- a/plugins/channelrx/demoddatv/datvscreen.cpp +++ b/plugins/channelrx/demoddatv/datvscreen.cpp @@ -52,7 +52,7 @@ DATVScreen::~DATVScreen() QRgb* DATVScreen::getRowBuffer(int intRow) { - if (m_blnGLContextInitialized == false) + if (!m_blnGLContextInitialized) { return NULL; } @@ -161,7 +161,7 @@ void DATVScreen::paintGL() m_objMutex.unlock(); } -void DATVScreen::mousePressEvent(QMouseEvent* event) +void DATVScreen::mousePressEvent(QMouseEvent* event __attribute__((unused))) { } @@ -194,13 +194,20 @@ bool DATVScreen::selectRow(int intLine) { return m_objGLShaderArray.SelectRow(intLine); } + else + { + return false; + } } bool DATVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue) { if (m_blnGLContextInitialized) - { - return m_objGLShaderArray.SetDataColor(intCol, - qRgb(intRed, intGreen, intBlue)); - } + { + return m_objGLShaderArray.SetDataColor(intCol, qRgb(intRed, intGreen, intBlue)); + } + else + { + return false; + } } diff --git a/plugins/channelrx/demoddatv/datvscreen.h b/plugins/channelrx/demoddatv/datvscreen.h index 05c2ceb68..7dc6b34e0 100644 --- a/plugins/channelrx/demoddatv/datvscreen.h +++ b/plugins/channelrx/demoddatv/datvscreen.h @@ -33,12 +33,8 @@ #include "util/export.h" #include "util/bitfieldindex.h" - - class QPainter; - - class SDRANGEL_API DATVScreen: public QGLWidget { Q_OBJECT diff --git a/plugins/channelrx/demoddatv/datvvideoplayer.h b/plugins/channelrx/demoddatv/datvvideoplayer.h index 3ea33e368..911e13fea 100644 --- a/plugins/channelrx/demoddatv/datvvideoplayer.h +++ b/plugins/channelrx/demoddatv/datvvideoplayer.h @@ -23,32 +23,42 @@ namespace leansdr { - template struct datvvideoplayer : runnable +template struct datvvideoplayer: runnable +{ + datvvideoplayer(scheduler *sch, pipebuf &_in, DATVideostream * objVideoStream) : + runnable(sch, _in.name), in(_in), m_objVideoStream(objVideoStream) { - datvvideoplayer(scheduler *sch, pipebuf &_in, DATVideostream * objVideoStream) : - runnable(sch, _in.name), - in(_in), - m_objVideoStream(objVideoStream) - { - } + } - void run() - { + void run() + { int size = in.readable() * sizeof(T); - if ( ! size ) return; + if (!size) + return; - int nw = m_objVideoStream->pushData((const char *)in.rd(),size); + int nw = m_objVideoStream->pushData((const char *) in.rd(), size); - if ( ! nw ) fatal("pipe"); - if ( nw < 0 ) fatal("write"); - if ( nw % sizeof(T) ) fatal("partial write"); - in.read(nw/sizeof(T)); - - } - private: - pipereader in; - DATVideostream * m_objVideoStream; - }; + if (!nw) + { + fatal("leansdr::datvvideoplayer::run: pipe"); + return; + } + if (nw < 0) + { + fatal("leansdr::datvvideoplayer::run: write"); + return; + } + if (nw % sizeof(T)) + { + fatal("leansdr::datvvideoplayer::run: partial write"); + return; + } + in.read(nw / sizeof(T)); + } +private: + pipereader in; + DATVideostream * m_objVideoStream; +}; } diff --git a/plugins/channelrx/demoddatv/glshaderarray.cpp b/plugins/channelrx/demoddatv/glshaderarray.cpp index bb4bae585..985b16397 100644 --- a/plugins/channelrx/demoddatv/glshaderarray.cpp +++ b/plugins/channelrx/demoddatv/glshaderarray.cpp @@ -126,7 +126,7 @@ void GLShaderArray::InitializeGL(int intCols, int intRows) QRgb * GLShaderArray::GetRowBuffer(int intRow) { - if (m_blnInitialized == false) + if (!m_blnInitialized) { return 0; } @@ -167,7 +167,7 @@ void GLShaderArray::RenderPixels(unsigned char *chrData) QRgb *ptrLine; - if (m_blnInitialized == false) + if (!m_blnInitialized) { return; } From 1e360fa63c752764d6eca773a804600e8ef8b80b Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 26 Feb 2018 01:13:48 +0100 Subject: [PATCH 010/956] DATV demodulator: leansdr: missing initialization --- plugins/channelrx/demoddatv/leansdr/sdr.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h index e1e4904d4..ebd4d9c23 100644 --- a/plugins/channelrx/demoddatv/leansdr/sdr.h +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -964,8 +964,8 @@ struct cstln_receiver: runnable softsymbol *pout = out.wr(), *pout0 = pout; // These are scoped outside the loop for SS and MER estimation. - complex sg; // Symbol before AGC; - complex s; // For MER estimation and constellation viewer + complex sg(0.0, 0.0); // Symbol before AGC; + complex s(0.0, 0.0); // For MER estimation and constellation viewer complex *cstln_point = NULL; while (pin < pend) From a406128c473745ef8fcfeca0cd7cd13bff6c93b0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 26 Feb 2018 01:33:50 +0100 Subject: [PATCH 011/956] DATV demod: GUI: added tooltip --- plugins/channelrx/demoddatv/datvdemodgui.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index 6e77f6c43..a7e2b3181 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -606,6 +606,9 @@ 81 + + Stream information + true From 7f067da9ccc4f198e3002a9050ea86381955e1f7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 01:09:59 +0100 Subject: [PATCH 012/956] qrtplib: reimport and compile --- {qrtplib => qrtplib.old}/CMakeLists.txt | 0 {qrtplib => qrtplib.old}/rtpdefines.h | 0 {qrtplib => qrtplib.old}/rtperrors.cpp | 7 ++++--- {qrtplib => qrtplib.old}/rtperrors.h | 0 {qrtplib => qrtplib.old}/rtpinternalutils.h | 0 {qrtplib => qrtplib.old}/rtppacket.cpp | 0 {qrtplib => qrtplib.old}/rtppacket.h | 3 +-- {qrtplib => qrtplib.old}/rtppacketbuilder.cpp | 10 ++++++---- {qrtplib => qrtplib.old}/rtppacketbuilder.h | 8 ++++---- {qrtplib => qrtplib.old}/rtprandom.cpp | 6 +++--- {qrtplib => qrtplib.old}/rtprandom.h | 0 {qrtplib => qrtplib.old}/rtprandomrand48.cpp | 2 +- {qrtplib => qrtplib.old}/rtprandomrand48.h | 2 +- {qrtplib => qrtplib.old}/rtprandomurandom.cpp | 5 +++-- {qrtplib => qrtplib.old}/rtprandomurandom.h | 2 +- {qrtplib => qrtplib.old}/rtprawpacket.h | 5 +---- {qrtplib => qrtplib.old}/rtpstructs.h | 0 {qrtplib => qrtplib.old}/rtptimeutilities.cpp | 2 +- {qrtplib => qrtplib.old}/rtptimeutilities.h | 0 19 files changed, 26 insertions(+), 26 deletions(-) rename {qrtplib => qrtplib.old}/CMakeLists.txt (100%) rename {qrtplib => qrtplib.old}/rtpdefines.h (100%) rename {qrtplib => qrtplib.old}/rtperrors.cpp (99%) rename {qrtplib => qrtplib.old}/rtperrors.h (100%) rename {qrtplib => qrtplib.old}/rtpinternalutils.h (100%) rename {qrtplib => qrtplib.old}/rtppacket.cpp (100%) rename {qrtplib => qrtplib.old}/rtppacket.h (99%) rename {qrtplib => qrtplib.old}/rtppacketbuilder.cpp (97%) rename {qrtplib => qrtplib.old}/rtppacketbuilder.h (98%) rename {qrtplib => qrtplib.old}/rtprandom.cpp (94%) rename {qrtplib => qrtplib.old}/rtprandom.h (100%) rename {qrtplib => qrtplib.old}/rtprandomrand48.cpp (98%) rename {qrtplib => qrtplib.old}/rtprandomrand48.h (98%) rename {qrtplib => qrtplib.old}/rtprandomurandom.cpp (97%) rename {qrtplib => qrtplib.old}/rtprandomurandom.h (98%) rename {qrtplib => qrtplib.old}/rtprawpacket.h (97%) rename {qrtplib => qrtplib.old}/rtpstructs.h (100%) rename {qrtplib => qrtplib.old}/rtptimeutilities.cpp (97%) rename {qrtplib => qrtplib.old}/rtptimeutilities.h (100%) diff --git a/qrtplib/CMakeLists.txt b/qrtplib.old/CMakeLists.txt similarity index 100% rename from qrtplib/CMakeLists.txt rename to qrtplib.old/CMakeLists.txt diff --git a/qrtplib/rtpdefines.h b/qrtplib.old/rtpdefines.h similarity index 100% rename from qrtplib/rtpdefines.h rename to qrtplib.old/rtpdefines.h diff --git a/qrtplib/rtperrors.cpp b/qrtplib.old/rtperrors.cpp similarity index 99% rename from qrtplib/rtperrors.cpp rename to qrtplib.old/rtperrors.cpp index 1b0eb4068..4723a288c 100644 --- a/qrtplib/rtperrors.cpp +++ b/qrtplib.old/rtperrors.cpp @@ -32,10 +32,11 @@ */ -#include "rtperrors.h" -#include "rtpdefines.h" -#include "rtpinternalutils.h" +#include "../qrtplib.old/rtperrors.h" + #include +#include "../qrtplib.old/rtpdefines.h" +#include "../qrtplib.old/rtpinternalutils.h" //#include "rtpdebug.h" diff --git a/qrtplib/rtperrors.h b/qrtplib.old/rtperrors.h similarity index 100% rename from qrtplib/rtperrors.h rename to qrtplib.old/rtperrors.h diff --git a/qrtplib/rtpinternalutils.h b/qrtplib.old/rtpinternalutils.h similarity index 100% rename from qrtplib/rtpinternalutils.h rename to qrtplib.old/rtpinternalutils.h diff --git a/qrtplib/rtppacket.cpp b/qrtplib.old/rtppacket.cpp similarity index 100% rename from qrtplib/rtppacket.cpp rename to qrtplib.old/rtppacket.cpp diff --git a/qrtplib/rtppacket.h b/qrtplib.old/rtppacket.h similarity index 99% rename from qrtplib/rtppacket.h rename to qrtplib.old/rtppacket.h index 6728d21e8..6f4f817a4 100644 --- a/qrtplib/rtppacket.h +++ b/qrtplib.old/rtppacket.h @@ -42,10 +42,9 @@ //#include "rtpconfig.h" //#include "rtptypes.h" -#include "rtptimeutilities.h" -//#include "rtpmemoryobject.h" #include #include +#include "../qrtplib.old/rtptimeutilities.h" namespace qrtplib { diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib.old/rtppacketbuilder.cpp similarity index 97% rename from qrtplib/rtppacketbuilder.cpp rename to qrtplib.old/rtppacketbuilder.cpp index 5cee190d8..3a281b42e 100644 --- a/qrtplib/rtppacketbuilder.cpp +++ b/qrtplib.old/rtppacketbuilder.cpp @@ -32,12 +32,14 @@ */ -#include "rtppacketbuilder.h" -#include "rtperrors.h" -#include "rtppacket.h" -#include "rtpsources.h" +#include "../qrtplib.old/rtppacketbuilder.h" + #include #include + +#include "../qrtplib.old/rtperrors.h" +#include "../qrtplib.old/rtppacket.h" +#include "../qrtplib.old/rtpsources.h" //#include "rtpdebug.h" namespace qrtplib diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib.old/rtppacketbuilder.h similarity index 98% rename from qrtplib/rtppacketbuilder.h rename to qrtplib.old/rtppacketbuilder.h index e0e30ba52..b421b33f1 100644 --- a/qrtplib/rtppacketbuilder.h +++ b/qrtplib.old/rtppacketbuilder.h @@ -41,10 +41,10 @@ #define RTPPACKETBUILDER_H //#include "rtpconfig.h" -#include "rtperrors.h" -#include "rtpdefines.h" -#include "rtprandom.h" -#include "rtptimeutilities.h" +#include "../qrtplib.old/rtpdefines.h" +#include "../qrtplib.old/rtperrors.h" +#include "../qrtplib.old/rtprandom.h" +#include "../qrtplib.old/rtptimeutilities.h" //#include "rtptypes.h" namespace qrtplib diff --git a/qrtplib/rtprandom.cpp b/qrtplib.old/rtprandom.cpp similarity index 94% rename from qrtplib/rtprandom.cpp rename to qrtplib.old/rtprandom.cpp index 1c28c5df4..6a6aaa207 100644 --- a/qrtplib/rtprandom.cpp +++ b/qrtplib.old/rtprandom.cpp @@ -32,14 +32,14 @@ */ -#include "rtprandom.h" -#include "rtprandomurandom.h" -#include "rtprandomrand48.h" +#include "../qrtplib.old/rtprandom.h" #include #include #include +#include "../qrtplib.old/rtprandomrand48.h" +#include "../qrtplib.old/rtprandomurandom.h" //#include "rtpdebug.h" diff --git a/qrtplib/rtprandom.h b/qrtplib.old/rtprandom.h similarity index 100% rename from qrtplib/rtprandom.h rename to qrtplib.old/rtprandom.h diff --git a/qrtplib/rtprandomrand48.cpp b/qrtplib.old/rtprandomrand48.cpp similarity index 98% rename from qrtplib/rtprandomrand48.cpp rename to qrtplib.old/rtprandomrand48.cpp index d14bed196..58c79e415 100644 --- a/qrtplib/rtprandomrand48.cpp +++ b/qrtplib.old/rtprandomrand48.cpp @@ -32,7 +32,7 @@ */ -#include "rtprandomrand48.h" +#include "../qrtplib.old/rtprandomrand48.h" namespace qrtplib { diff --git a/qrtplib/rtprandomrand48.h b/qrtplib.old/rtprandomrand48.h similarity index 98% rename from qrtplib/rtprandomrand48.h rename to qrtplib.old/rtprandomrand48.h index 10b36a7e1..dcc85c2a5 100644 --- a/qrtplib/rtprandomrand48.h +++ b/qrtplib.old/rtprandomrand48.h @@ -41,9 +41,9 @@ #define RTPRANDOMRAND48_H //#include "rtpconfig.h" -#include "rtprandom.h" #include #include +#include "../qrtplib.old/rtprandom.h" namespace qrtplib { diff --git a/qrtplib/rtprandomurandom.cpp b/qrtplib.old/rtprandomurandom.cpp similarity index 97% rename from qrtplib/rtprandomurandom.cpp rename to qrtplib.old/rtprandomurandom.cpp index ac3efa12f..fc909dbd6 100644 --- a/qrtplib/rtprandomurandom.cpp +++ b/qrtplib.old/rtprandomurandom.cpp @@ -32,8 +32,9 @@ */ -#include "rtprandomurandom.h" -#include "rtperrors.h" +#include "../qrtplib.old/rtprandomurandom.h" + +#include "../qrtplib.old/rtperrors.h" //#include "rtpdebug.h" diff --git a/qrtplib/rtprandomurandom.h b/qrtplib.old/rtprandomurandom.h similarity index 98% rename from qrtplib/rtprandomurandom.h rename to qrtplib.old/rtprandomurandom.h index 160820a94..272cc95c2 100644 --- a/qrtplib/rtprandomurandom.h +++ b/qrtplib.old/rtprandomurandom.h @@ -41,8 +41,8 @@ #define RTPRANDOMURANDOM_H //#include "rtpconfig.h" -#include "rtprandom.h" #include +#include "../qrtplib.old/rtprandom.h" namespace qrtplib { diff --git a/qrtplib/rtprawpacket.h b/qrtplib.old/rtprawpacket.h similarity index 97% rename from qrtplib/rtprawpacket.h rename to qrtplib.old/rtprawpacket.h index 0c18caa26..0a6ce8bd4 100644 --- a/qrtplib/rtprawpacket.h +++ b/qrtplib.old/rtprawpacket.h @@ -41,12 +41,9 @@ #define RTPRAWPACKET_H //#include "rtpconfig.h" -#include "rtptimeutilities.h" -//#include "rtpaddress.h" -//#include "rtptypes.h" -//#include "rtpmemoryobject.h" #include #include +#include "../qrtplib.old/rtptimeutilities.h" namespace qrtplib { diff --git a/qrtplib/rtpstructs.h b/qrtplib.old/rtpstructs.h similarity index 100% rename from qrtplib/rtpstructs.h rename to qrtplib.old/rtpstructs.h diff --git a/qrtplib/rtptimeutilities.cpp b/qrtplib.old/rtptimeutilities.cpp similarity index 97% rename from qrtplib/rtptimeutilities.cpp rename to qrtplib.old/rtptimeutilities.cpp index 451a6d8cf..d3df02cc7 100644 --- a/qrtplib/rtptimeutilities.cpp +++ b/qrtplib.old/rtptimeutilities.cpp @@ -33,7 +33,7 @@ */ //#include "rtpconfig.h" -#include "rtptimeutilities.h" +#include "../qrtplib.old/rtptimeutilities.h" namespace qrtplib { diff --git a/qrtplib/rtptimeutilities.h b/qrtplib.old/rtptimeutilities.h similarity index 100% rename from qrtplib/rtptimeutilities.h rename to qrtplib.old/rtptimeutilities.h From 30d019d39a3eeb4ad07b01a4c2eb98a1e744421f Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 01:35:16 +0100 Subject: [PATCH 013/956] qrtplib: removed debug --- qrtplib/CMakeLists.txt | 124 ++ .../extratransmitters/rtpfaketransmitter.cpp | 1389 ++++++++++++ .../extratransmitters/rtpfaketransmitter.h | 244 +++ qrtplib/rtcpapppacket.cpp | 65 + qrtplib/rtcpapppacket.h | 129 ++ qrtplib/rtcpbyepacket.cpp | 73 + qrtplib/rtcpbyepacket.h | 137 ++ qrtplib/rtcpcompoundpacket.cpp | 216 ++ qrtplib/rtcpcompoundpacket.h | 108 + qrtplib/rtcpcompoundpacketbuilder.cpp | 811 +++++++ qrtplib/rtcpcompoundpacketbuilder.h | 400 ++++ qrtplib/rtcppacket.h | 91 + qrtplib/rtcppacketbuilder.cpp | 737 +++++++ qrtplib/rtcppacketbuilder.h | 229 ++ qrtplib/rtcprrpacket.cpp | 68 + qrtplib/rtcprrpacket.h | 203 ++ qrtplib/rtcpscheduler.cpp | 419 ++++ qrtplib/rtcpscheduler.h | 189 ++ qrtplib/rtcpsdesinfo.cpp | 180 ++ qrtplib/rtcpsdesinfo.h | 220 ++ qrtplib/rtcpsdespacket.cpp | 141 ++ qrtplib/rtcpsdespacket.h | 383 ++++ qrtplib/rtcpsrpacket.cpp | 69 + qrtplib/rtcpsrpacket.h | 249 +++ qrtplib/rtcpunknownpacket.h | 73 + qrtplib/rtpabortdescriptors.cpp | 258 +++ qrtplib/rtpabortdescriptors.h | 103 + qrtplib/rtpaddress.h | 97 + qrtplib/rtpbyteaddress.cpp | 81 + qrtplib/rtpbyteaddress.h | 91 + qrtplib/rtpcollisionlist.cpp | 113 + qrtplib/rtpcollisionlist.h | 93 + qrtplib/rtpconfig.h | 111 + qrtplib/rtpdefines.h | 76 + qrtplib/rtperrors.cpp | 272 +++ qrtplib/rtperrors.h | 251 +++ qrtplib/rtpexternaltransmitter.cpp | 733 +++++++ qrtplib/rtpexternaltransmitter.h | 231 ++ qrtplib/rtphashtable.h | 308 +++ qrtplib/rtpinternalsourcedata.cpp | 294 +++ qrtplib/rtpinternalsourcedata.h | 135 ++ qrtplib/rtpinternalutils.h | 59 + qrtplib/rtpipv4address.cpp | 74 + qrtplib/rtpipv4address.h | 149 ++ qrtplib/rtpipv4destination.cpp | 51 + qrtplib/rtpipv4destination.h | 118 + qrtplib/rtpipv6address.cpp | 91 + qrtplib/rtpipv6address.h | 108 + qrtplib/rtpipv6destination.cpp | 61 + qrtplib/rtpipv6destination.h | 91 + qrtplib/rtpkeyhashtable.h | 310 +++ qrtplib/rtplibraryversion.cpp | 57 + qrtplib/rtplibraryversion.h | 77 + qrtplib/rtplibraryversioninternal.h | 46 + qrtplib/rtpmemorymanager.h | 245 +++ qrtplib/rtpmemoryobject.h | 74 + qrtplib/rtppacket.cpp | 317 +++ qrtplib/rtppacket.h | 180 ++ qrtplib/rtppacketbuilder.cpp | 267 +++ qrtplib/rtppacketbuilder.h | 274 +++ qrtplib/rtppollthread.cpp | 171 ++ qrtplib/rtppollthread.h | 79 + qrtplib/rtprandom.cpp | 105 + qrtplib/rtprandom.h | 79 + qrtplib/rtprandomrand48.cpp | 125 ++ qrtplib/rtprandomrand48.h | 75 + qrtplib/rtprandomrands.cpp | 196 ++ qrtplib/rtprandomrands.h | 70 + qrtplib/rtprandomurandom.cpp | 129 ++ qrtplib/rtprandomurandom.h | 68 + qrtplib/rtprawpacket.h | 160 ++ qrtplib/rtpsecuresession.cpp | 283 +++ qrtplib/rtpsecuresession.h | 132 ++ qrtplib/rtpselect.h | 190 ++ qrtplib/rtpsession.cpp | 1701 ++++++++++++++ qrtplib/rtpsession.h | 690 ++++++ qrtplib/rtpsessionparams.cpp | 100 + qrtplib/rtpsessionparams.h | 256 +++ qrtplib/rtpsessionsources.cpp | 141 ++ qrtplib/rtpsessionsources.h | 93 + qrtplib/rtpsocketutil.h | 61 + qrtplib/rtpsocketutilinternal.h | 79 + qrtplib/rtpsourcedata.cpp | 324 +++ qrtplib/rtpsourcedata.h | 476 ++++ qrtplib/rtpsources.cpp | 1277 +++++++++++ qrtplib/rtpsources.h | 406 ++++ qrtplib/rtpstructs.h | 128 ++ qrtplib/rtptcpaddress.cpp | 68 + qrtplib/rtptcpaddress.h | 84 + qrtplib/rtptcptransmitter.cpp | 924 ++++++++ qrtplib/rtptcptransmitter.h | 207 ++ qrtplib/rtptimeutilities.cpp | 49 + qrtplib/rtptimeutilities.h | 393 ++++ qrtplib/rtptransmitter.h | 260 +++ qrtplib/rtptypes.h | 5 + qrtplib/rtptypes_win.h | 53 + qrtplib/rtpudpv4transmitter.cpp | 1934 ++++++++++++++++ qrtplib/rtpudpv4transmitter.h | 367 ++++ qrtplib/rtpudpv4transmitternobind.cpp | 1952 +++++++++++++++++ qrtplib/rtpudpv4transmitternobind.h | 371 ++++ qrtplib/rtpudpv6transmitter.cpp | 1643 ++++++++++++++ qrtplib/rtpudpv6transmitter.h | 330 +++ 102 files changed, 29077 insertions(+) create mode 100644 qrtplib/CMakeLists.txt create mode 100644 qrtplib/extratransmitters/rtpfaketransmitter.cpp create mode 100644 qrtplib/extratransmitters/rtpfaketransmitter.h create mode 100644 qrtplib/rtcpapppacket.cpp create mode 100644 qrtplib/rtcpapppacket.h create mode 100644 qrtplib/rtcpbyepacket.cpp create mode 100644 qrtplib/rtcpbyepacket.h create mode 100644 qrtplib/rtcpcompoundpacket.cpp create mode 100644 qrtplib/rtcpcompoundpacket.h create mode 100644 qrtplib/rtcpcompoundpacketbuilder.cpp create mode 100644 qrtplib/rtcpcompoundpacketbuilder.h create mode 100644 qrtplib/rtcppacket.h create mode 100644 qrtplib/rtcppacketbuilder.cpp create mode 100644 qrtplib/rtcppacketbuilder.h create mode 100644 qrtplib/rtcprrpacket.cpp create mode 100644 qrtplib/rtcprrpacket.h create mode 100644 qrtplib/rtcpscheduler.cpp create mode 100644 qrtplib/rtcpscheduler.h create mode 100644 qrtplib/rtcpsdesinfo.cpp create mode 100644 qrtplib/rtcpsdesinfo.h create mode 100644 qrtplib/rtcpsdespacket.cpp create mode 100644 qrtplib/rtcpsdespacket.h create mode 100644 qrtplib/rtcpsrpacket.cpp create mode 100644 qrtplib/rtcpsrpacket.h create mode 100644 qrtplib/rtcpunknownpacket.h create mode 100644 qrtplib/rtpabortdescriptors.cpp create mode 100644 qrtplib/rtpabortdescriptors.h create mode 100644 qrtplib/rtpaddress.h create mode 100644 qrtplib/rtpbyteaddress.cpp create mode 100644 qrtplib/rtpbyteaddress.h create mode 100644 qrtplib/rtpcollisionlist.cpp create mode 100644 qrtplib/rtpcollisionlist.h create mode 100644 qrtplib/rtpconfig.h create mode 100644 qrtplib/rtpdefines.h create mode 100644 qrtplib/rtperrors.cpp create mode 100644 qrtplib/rtperrors.h create mode 100644 qrtplib/rtpexternaltransmitter.cpp create mode 100644 qrtplib/rtpexternaltransmitter.h create mode 100644 qrtplib/rtphashtable.h create mode 100644 qrtplib/rtpinternalsourcedata.cpp create mode 100644 qrtplib/rtpinternalsourcedata.h create mode 100644 qrtplib/rtpinternalutils.h create mode 100644 qrtplib/rtpipv4address.cpp create mode 100644 qrtplib/rtpipv4address.h create mode 100644 qrtplib/rtpipv4destination.cpp create mode 100644 qrtplib/rtpipv4destination.h create mode 100644 qrtplib/rtpipv6address.cpp create mode 100644 qrtplib/rtpipv6address.h create mode 100644 qrtplib/rtpipv6destination.cpp create mode 100644 qrtplib/rtpipv6destination.h create mode 100644 qrtplib/rtpkeyhashtable.h create mode 100644 qrtplib/rtplibraryversion.cpp create mode 100644 qrtplib/rtplibraryversion.h create mode 100644 qrtplib/rtplibraryversioninternal.h create mode 100644 qrtplib/rtpmemorymanager.h create mode 100644 qrtplib/rtpmemoryobject.h create mode 100644 qrtplib/rtppacket.cpp create mode 100644 qrtplib/rtppacket.h create mode 100644 qrtplib/rtppacketbuilder.cpp create mode 100644 qrtplib/rtppacketbuilder.h create mode 100644 qrtplib/rtppollthread.cpp create mode 100644 qrtplib/rtppollthread.h create mode 100644 qrtplib/rtprandom.cpp create mode 100644 qrtplib/rtprandom.h create mode 100644 qrtplib/rtprandomrand48.cpp create mode 100644 qrtplib/rtprandomrand48.h create mode 100644 qrtplib/rtprandomrands.cpp create mode 100644 qrtplib/rtprandomrands.h create mode 100644 qrtplib/rtprandomurandom.cpp create mode 100644 qrtplib/rtprandomurandom.h create mode 100644 qrtplib/rtprawpacket.h create mode 100644 qrtplib/rtpsecuresession.cpp create mode 100644 qrtplib/rtpsecuresession.h create mode 100644 qrtplib/rtpselect.h create mode 100644 qrtplib/rtpsession.cpp create mode 100644 qrtplib/rtpsession.h create mode 100644 qrtplib/rtpsessionparams.cpp create mode 100644 qrtplib/rtpsessionparams.h create mode 100644 qrtplib/rtpsessionsources.cpp create mode 100644 qrtplib/rtpsessionsources.h create mode 100644 qrtplib/rtpsocketutil.h create mode 100644 qrtplib/rtpsocketutilinternal.h create mode 100644 qrtplib/rtpsourcedata.cpp create mode 100644 qrtplib/rtpsourcedata.h create mode 100644 qrtplib/rtpsources.cpp create mode 100644 qrtplib/rtpsources.h create mode 100644 qrtplib/rtpstructs.h create mode 100644 qrtplib/rtptcpaddress.cpp create mode 100644 qrtplib/rtptcpaddress.h create mode 100644 qrtplib/rtptcptransmitter.cpp create mode 100644 qrtplib/rtptcptransmitter.h create mode 100644 qrtplib/rtptimeutilities.cpp create mode 100644 qrtplib/rtptimeutilities.h create mode 100644 qrtplib/rtptransmitter.h create mode 100644 qrtplib/rtptypes.h create mode 100644 qrtplib/rtptypes_win.h create mode 100644 qrtplib/rtpudpv4transmitter.cpp create mode 100644 qrtplib/rtpudpv4transmitter.h create mode 100644 qrtplib/rtpudpv4transmitternobind.cpp create mode 100644 qrtplib/rtpudpv4transmitternobind.h create mode 100644 qrtplib/rtpudpv6transmitter.cpp create mode 100644 qrtplib/rtpudpv6transmitter.h diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt new file mode 100644 index 000000000..60c020a04 --- /dev/null +++ b/qrtplib/CMakeLists.txt @@ -0,0 +1,124 @@ +project(qrtplib) + +set (qrtplib_HEADERS + rtcpapppacket.h + rtcpbyepacket.h + rtcpcompoundpacket.h + rtcpcompoundpacketbuilder.h + rtcppacket.h + rtcppacketbuilder.h + rtcprrpacket.h + rtcpscheduler.h + rtcpsdesinfo.h + rtcpsdespacket.h + rtcpsrpacket.h + rtcpunknownpacket.h + rtpaddress.h + rtpcollisionlist.h + rtpconfig.h + rtpdefines.h + rtperrors.h + rtphashtable.h + rtpinternalsourcedata.h + rtpipv4address.h + rtpipv4destination.h + rtpipv6address.h + rtpipv6destination.h + rtpkeyhashtable.h + rtplibraryversion.h + rtpmemorymanager.h + rtpmemoryobject.h + rtppacket.h + rtppacketbuilder.h + rtppollthread.h + rtprandom.h + rtprandomrand48.h + rtprandomrands.h + rtprandomurandom.h + rtprawpacket.h + rtpsession.h + rtpsessionparams.h + rtpsessionsources.h + rtpsourcedata.h + rtpsources.h + rtpstructs.h + rtptimeutilities.h + rtptransmitter.h + rtptypes_win.h + rtptypes.h + rtpudpv4transmitter.h + rtpudpv4transmitternobind.h + rtpudpv6transmitter.h + rtpbyteaddress.h + rtpexternaltransmitter.h + rtpsecuresession.h + rtpsocketutil.h + rtpabortdescriptors.h + rtpselect.h + rtptcpaddress.h + rtptcptransmitter.h + ) + +set(qrtplib_SOURCES + rtcpapppacket.cpp + rtcpbyepacket.cpp + rtcpcompoundpacket.cpp + rtcpcompoundpacketbuilder.cpp + rtcppacketbuilder.cpp + rtcprrpacket.cpp + rtcpscheduler.cpp + rtcpsdesinfo.cpp + rtcpsdespacket.cpp + rtcpsrpacket.cpp + rtpcollisionlist.cpp + rtperrors.cpp + rtpinternalsourcedata.cpp + rtpipv4address.cpp + rtpipv6address.cpp + rtpipv4destination.cpp + rtpipv6destination.cpp + rtplibraryversion.cpp + rtppacket.cpp + rtppacketbuilder.cpp + rtppollthread.cpp + rtprandom.cpp + rtprandomrand48.cpp + rtprandomrands.cpp + rtprandomurandom.cpp + rtpsession.cpp + rtpsessionparams.cpp + rtpsessionsources.cpp + rtpsourcedata.cpp + rtpsources.cpp + rtptimeutilities.cpp + rtpudpv4transmitter.cpp + rtpudpv4transmitternobind.cpp + rtpudpv6transmitter.cpp + rtpbyteaddress.cpp + rtpexternaltransmitter.cpp + rtpsecuresession.cpp + rtpabortdescriptors.cpp + rtptcpaddress.cpp + rtptcptransmitter.cpp + ) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_SHARED) + +add_library(qrtplib SHARED + ${qrtplib_SOURCES} + ${qrtplib_HEADERS_MOC} +) + +target_link_libraries(qrtplib + ${QT_LIBRARIES} +) + +qt5_use_modules(qrtplib Core Network) + +install(TARGETS qrtplib DESTINATION lib) diff --git a/qrtplib/extratransmitters/rtpfaketransmitter.cpp b/qrtplib/extratransmitters/rtpfaketransmitter.cpp new file mode 100644 index 000000000..6346792c1 --- /dev/null +++ b/qrtplib/extratransmitters/rtpfaketransmitter.cpp @@ -0,0 +1,1389 @@ +/* + + This class allows for jrtp to process packets without sending them out + anywhere. + The incoming messages are handed in to jrtp through the TransmissionParams + and can be retreived from jrtp through the normal polling mecanisms. + The outgoing RTP/RTCP packets are given to jrtp through the normal + session->SendPacket() and those packets are handed back out to the + client through a callback function (packet_ready_cb). + + example usage : Allows for integration of RTP into gstreamer. + + THIS HAS NOT BEEN TESTED WITH THREADS SO DON'T TRY + + Copyright (c) 2005 Philippe Khalaf + + This file is a part of JRTPLIB + Copyright (c) 1999-2004 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the "Expertisecentrum Digitale Media" + (http://www.edm.luc.ac.be), a research center of the "Limburgs Universitair + Centrum" (http://www.luc.ac.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpfaketransmitter.h" + +#include "rtprawpacket.h" +#include "rtpipv4address.h" +#include "rtptimeutilities.h" +#include + +#include +#include +#include +#include +#include + +#ifdef RTP_HAVE_SYS_FILIO +#include +#endif // RTP_HAVE_SYS_FILIO + +#define RTPIOCTL ioctl + +#define RTPFAKETRANS_MAXPACKSIZE 65535 +#define RTPFAKETRANS_IFREQBUFSIZE 8192 + +//#define RTPFAKETRANS_IS_MCASTADDR(x) (((x)&0xF0000000) == 0xE0000000) + +/*#define RTPFAKETRANS_MCASTMEMBERSHIP(socket,type,mcastip,status) {\ + struct ip_mreq mreq;\ + \ + mreq.imr_multiaddr.s_addr = htonl(mcastip);\ + mreq.imr_interface.s_addr = htonl(bindIP);\ + status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ + }*/ +#ifdef RTP_SUPPORT_THREAD + #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } + #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } + #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } + #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } +#else + #define MAINMUTEX_LOCK + #define MAINMUTEX_UNLOCK + #define WAITMUTEX_LOCK + #define WAITMUTEX_UNLOCK +#endif // RTP_SUPPORT_THREAD + +namespace qrtplib +{ + +RTPFakeTransmitter::RTPFakeTransmitter(RTPMemoryManager *mgr ) : RTPTransmitter(mgr), destinations(mgr,RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT),acceptignoreinfo(mgr,RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT) +{ + created = false; + init = false; +} + +RTPFakeTransmitter::~RTPFakeTransmitter() +{ + Destroy(); +} + +int RTPFakeTransmitter::Init(bool tsafe) +{ + if (init) + return ERR_RTP_FAKETRANS_ALREADYINIT; + + // bomb out if trying to use threads + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; + +#ifdef RTP_SUPPORT_THREAD + threadsafe = tsafe; + if (threadsafe) + { + int status; + + status = mainmutex.Init(); + if (status < 0) + return ERR_RTP_FAKETRANS_CANTINITMUTEX; + status = waitmutex.Init(); + if (status < 0) + return ERR_RTP_FAKETRANS_CANTINITMUTEX; + } +#else + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; +#endif // RTP_SUPPORT_THREAD + + init = true; + return 0; +} + +int RTPFakeTransmitter::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) +{ +// struct sockaddr_in addr; +// int status; + + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_ALREADYCREATED; + } + + // Obtain transmission parameters + + if (transparams == 0) + params = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) RTPFakeTransmissionParams; + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::UserDefinedProto) + return ERR_RTP_FAKETRANS_ILLEGALPARAMETERS; + params = (RTPFakeTransmissionParams *)transparams; + } + + // Check if portbase is even + //if (params->GetPortbase()%2 != 0) + //{ + // MAINMUTEX_UNLOCK + // return ERR_RTP_FAKETRANS_PORTBASENOTEVEN; + //} + + // Try to obtain local IP addresses + + localIPs = params->GetLocalIPList(); + if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them + { + int status; + + if ((status = CreateLocalIPList()) < 0) + { + MAINMUTEX_UNLOCK + return status; + } + } + +//#ifdef RTP_SUPPORT_IPV4MULTICAST +// if (SetMulticastTTL(params->GetMulticastTTL())) +// supportsmulticasting = true; +// else +// supportsmulticasting = false; +//#else // no multicast support enabled + supportsmulticasting = false; +//#endif // RTP_SUPPORT_IPV4MULTICAST + + if (maximumpacketsize > RTPFAKETRANS_MAXPACKSIZE) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG; + } + + maxpacksize = maximumpacketsize; + portbase = params->GetPortbase(); + multicastTTL = params->GetMulticastTTL(); + receivemode = RTPTransmitter::AcceptAll; + + localhostname = 0; + localhostnamelength = 0; + + waitingfordata = false; + created = true; + + MAINMUTEX_UNLOCK + return 0; +} + +void RTPFakeTransmitter::Destroy() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK; + return; + } + + if (localhostname) + { + RTPDeleteByteArray(localhostname,GetMemoryManager()); + localhostname = 0; + localhostnamelength = 0; + } + + destinations.Clear(); +#ifdef RTP_SUPPORT_IPV4MULTICAST +// multicastgroups.Clear(); +#endif // RTP_SUPPORT_IPV4MULTICAST + FlushPackets(); + ClearAcceptIgnoreInfo(); + localIPs.clear(); + created = false; + RTPDelete(params,GetMemoryManager()); + + MAINMUTEX_UNLOCK +} + +RTPTransmissionInfo *RTPFakeTransmitter::GetTransmissionInfo() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPFakeTransmissionInfo(localIPs, params); + MAINMUTEX_UNLOCK + return tinf; +} + +void RTPFakeTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *inf) +{ + if (!init) + return; + RTPDelete(inf,GetMemoryManager()); +} + +int RTPFakeTransmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + + if (localhostname == 0) + { + if (localIPs.empty()) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOLOCALIPS; + } + + std::list::const_iterator it; + std::list hostnames; + + for (it = localIPs.begin() ; it != localIPs.end() ; it++) + { + struct hostent *he; + uint8_t addr[4]; + uint32_t ip = (*it); + + addr[0] = (uint8_t)((ip>>24)&0xFF); + addr[1] = (uint8_t)((ip>>16)&0xFF); + addr[2] = (uint8_t)((ip>>8)&0xFF); + addr[3] = (uint8_t)(ip&0xFF); + he = gethostbyaddr((char *)addr,4,AF_INET); + if (he != 0) + { + std::string hname = std::string(he->h_name); + hostnames.push_back(hname); + } + } + + bool found = false; + + if (!hostnames.empty()) // try to select the most appropriate hostname + { + std::list::const_iterator it; + + for (it = hostnames.begin() ; !found && it != hostnames.end() ; it++) + { + if ((*it).find('.') != std::string::npos) + { + found = true; + localhostnamelength = (*it).length(); + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname,(*it).c_str(),localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } + } + + if (!found) // use an IP address + { + uint32_t ip; + int len; + char str[16]; + + it = localIPs.begin(); + ip = (*it); + + snprintf(str,16,"%d.%d.%d.%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF)); + len = strlen(str); + + localhostnamelength = len; + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength + 1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname,str,localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } + + if ((*bufferlength) < localhostnamelength) + { + *bufferlength = localhostnamelength; // tell the application the required size of the buffer + MAINMUTEX_UNLOCK + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; + } + + memcpy(buffer,localhostname,localhostnamelength); + *bufferlength = localhostnamelength; + + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPFakeTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) +{ + if (!init) + return false; + + if (addr == 0) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (created && addr->GetAddressType() == RTPAddress::IPv4Address) + { + const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; + bool found = false; + std::list::const_iterator it; + + it = localIPs.begin(); + while (!found && it != localIPs.end()) + { + if (addr2->GetIP() == *it) + found = true; + else + ++it; + } + + if (!found) + v = false; + else + { + if (addr2->GetPort() == params->GetPortbase()) // check for RTP port + v = true; + else if (addr2->GetPort() == (params->GetPortbase()+1)) // check for RTCP port + v = true; + else + v = false; + } + } + else + v = false; + + MAINMUTEX_UNLOCK + return v; +} + +int RTPFakeTransmitter::Poll() +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + int status; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + status = FakePoll(); // poll RTP socket + params->SetCurrentData(NULL); + MAINMUTEX_UNLOCK + return status; +} + +int RTPFakeTransmitter::WaitForIncomingData(const RTPTime &, bool *) +{ + return ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED; +} + +int RTPFakeTransmitter::AbortWait() +{ + return ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED; +} + +int RTPFakeTransmitter::SendRTPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG; + } + + + destinations.GotoFirstElement(); + // send to each destination + while (destinations.HasCurrentElement()) + { + (*params->GetPacketReadyCB())(params->GetPacketReadyCBData(), (uint8_t*)data, len, + destinations.GetCurrentElement().GetIP_NBO(), + destinations.GetCurrentElement().GetRTPPort_NBO(), + 1); + destinations.GotoNextElement(); + } + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPFakeTransmitter::SendRTCPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG; + } + + destinations.GotoFirstElement(); + // send to each destination + while (destinations.HasCurrentElement()) + { + (*params->GetPacketReadyCB())(params->GetPacketReadyCBData(), (uint8_t*)data, len, + destinations.GetCurrentElement().GetIP_NBO(), + destinations.GetCurrentElement().GetRTCPPort_NBO(), + 0); + destinations.GotoNextElement(); + } + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPFakeTransmitter::AddDestination(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; + } + + int status = destinations.AddElement(dest); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPFakeTransmitter::DeleteDestination(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; + } + + int status = destinations.DeleteElement(dest); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPFakeTransmitter::ClearDestinations() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created) + destinations.Clear(); + MAINMUTEX_UNLOCK +} + +bool RTPFakeTransmitter::SupportsMulticasting() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + v = supportsmulticasting; + + MAINMUTEX_UNLOCK + return v; +} + +#ifdef RTP_SUPPORT_IPV4MULTICAST + +int RTPFakeTransmitter::JoinMulticastGroup(const RTPAddress &) +{ +// hrrm wonder how will manage to get multicast info thru to the UDPSINK +/* if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + uint32_t mcastIP = address.GetIP(); + + if (!RTPFakeTRANS_IS_MCASTADDR(mcastIP)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.AddElement(mcastIP); + if (status >= 0) + { + RTPFakeTRANS_MCASTMEMBERSHIP(rtpsock,IP_ADD_MEMBERSHIP,mcastIP,status); + if (status != 0) + { + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP; + } + RTPFakeTRANS_MCASTMEMBERSHIP(rtcpsock,IP_ADD_MEMBERSHIP,mcastIP,status); + if (status != 0) + { + RTPFakeTRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP; + } + } + MAINMUTEX_UNLOCK + return status;*/ + return ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT; +} + +int RTPFakeTransmitter::LeaveMulticastGroup(const RTPAddress &) +{ + /* + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + uint32_t mcastIP = address.GetIP(); + + if (!RTPFakeTRANS_IS_MCASTADDR(mcastIP)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.DeleteElement(mcastIP); + if (status >= 0) + { + RTPFakeTRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + RTPFakeTRANS_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + status = 0; + } + + MAINMUTEX_UNLOCK + return status; + */ + return ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT; +} + +void RTPFakeTransmitter::LeaveAllMulticastGroups() +{ +/* if (!init) + return; + + MAINMUTEX_LOCK + if (created) + { + multicastgroups.GotoFirstElement(); + while (multicastgroups.HasCurrentElement()) + { + uint32_t mcastIP; + int status = 0; + + mcastIP = multicastgroups.GetCurrentElement(); + RTPFakeTRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + RTPFakeTRANS_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + multicastgroups.GotoNextElement(); + } + multicastgroups.Clear(); + } + MAINMUTEX_UNLOCK*/ +} + +#else // no multicast support + +int RTPFakeTransmitter::JoinMulticastGroup(const RTPAddress &addr) +{ + return ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT; +} + +int RTPFakeTransmitter::LeaveMulticastGroup(const RTPAddress &addr) +{ + return ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT; +} + +void RTPFakeTransmitter::LeaveAllMulticastGroups() +{ +} + +#endif // RTP_SUPPORT_IPV4MULTICAST + +int RTPFakeTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (m != receivemode) + { + receivemode = m; + acceptignoreinfo.Clear(); + } + MAINMUTEX_UNLOCK + return 0; +} + +int RTPFakeTransmitter::AddToIgnoreList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPFakeTransmitter::DeleteFromIgnoreList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPFakeTransmitter::ClearIgnoreList() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created && receivemode == RTPTransmitter::IgnoreSome) + ClearAcceptIgnoreInfo(); + MAINMUTEX_UNLOCK +} + +int RTPFakeTransmitter::AddToAcceptList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPFakeTransmitter::DeleteFromAcceptList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPFakeTransmitter::ClearAcceptList() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created && receivemode == RTPTransmitter::AcceptSome) + ClearAcceptIgnoreInfo(); + MAINMUTEX_UNLOCK +} + +int RTPFakeTransmitter::SetMaximumPacketSize(size_t s) +{ + if (!init) + return ERR_RTP_FAKETRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_NOTCREATED; + } + if (s > RTPFAKETRANS_MAXPACKSIZE) + { + MAINMUTEX_UNLOCK + return ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG; + } + maxpacksize = s; + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPFakeTransmitter::NewDataAvailable() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + { + if (rawpacketlist.empty()) + v = false; + else + v = true; + } + + MAINMUTEX_UNLOCK + return v; +} + +RTPRawPacket *RTPFakeTransmitter::GetNextPacket() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + + RTPRawPacket *p; + + if (!created) + { + MAINMUTEX_UNLOCK + return 0; + } + if (rawpacketlist.empty()) + { + MAINMUTEX_UNLOCK + return 0; + } + + p = *(rawpacketlist.begin()); + rawpacketlist.pop_front(); + + MAINMUTEX_UNLOCK + return p; +} + +// Here the private functions start... + +#ifdef RTP_SUPPORT_IPV4MULTICAST +bool RTPFakeTransmitter::SetMulticastTTL(uint8_t) +{ +/* int ttl2,status; + + ttl2 = (int)ttl; + status = setsockopt(rtpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); + if (status != 0) + return false; + status = setsockopt(rtcpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); + if (status != 0) + return false; + return true;*/ + return true; +} +#endif // RTP_SUPPORT_IPV4MULTICAST + +void RTPFakeTransmitter::FlushPackets() +{ + std::list::const_iterator it; + + for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) + RTPDelete(*it,GetMemoryManager()); + rawpacketlist.clear(); +} + +int RTPFakeTransmitter::FakePoll() +{ + uint8_t *data = NULL; + int data_len = 0; + uint32_t sourceaddr; + uint16_t sourceport; + bool rtp; + bool acceptdata; + + RTPTime curtime = RTPTime::CurrentTime(); + + data = params->GetCurrentData(); + data_len = params->GetCurrentDataLen(); + rtp = params->GetCurrentDataType(); + sourceaddr = params->GetCurrentDataAddr(); + sourceport = params->GetCurrentDataPort(); + // lets make sure we got something + if (data == NULL || data_len <= 0) + { + return 0; + } + + // let's make a RTPIPv4Address + RTPIPv4Address *addr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv4Address(sourceaddr, sourceport); + if (addr == 0) + { + return ERR_RTP_OUTOFMEM; + } + + // ok we got the src addr, now this should be the actual packet + uint8_t *datacopy; + datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[data_len]; + if (datacopy == 0) + { + RTPDelete(addr,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + memcpy(datacopy, data, data_len); + + // got data, process it + if (receivemode == RTPTransmitter::AcceptAll) + acceptdata = true; + else + acceptdata = ShouldAcceptData(addr->GetIP(),addr->GetPort()); + + if (acceptdata) + { + // adding packet to queue + RTPRawPacket *pack; + + pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,data_len,addr,curtime,rtp,GetMemoryManager()); + + if (pack == 0) + { + RTPDelete(addr,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + rawpacketlist.push_back(pack); + } + return 0; +} + +int RTPFakeTransmitter::ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port) +{ + acceptignoreinfo.GotoElement(ip); + if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists + { + PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); + + if (port == 0) // select all ports + { + portinf->all = true; + portinf->portlist.clear(); + } + else if (!portinf->all) + { + std::list::const_iterator it,begin,end; + + begin = portinf->portlist.begin(); + end = portinf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == port) // already in list + return 0; + } + portinf->portlist.push_front(port); + } + } + else // got to create an entry for this IP address + { + PortInfo *portinf; + int status; + + portinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO) PortInfo(); + if (port == 0) // select all ports + portinf->all = true; + else + portinf->portlist.push_front(port); + + status = acceptignoreinfo.AddElement(ip,portinf); + if (status < 0) + { + RTPDelete(portinf,GetMemoryManager()); + return status; + } + } + + return 0; +} + +void RTPFakeTransmitter::ClearAcceptIgnoreInfo() +{ + acceptignoreinfo.GotoFirstElement(); + while (acceptignoreinfo.HasCurrentElement()) + { + PortInfo *inf; + + inf = acceptignoreinfo.GetCurrentElement(); + RTPDelete(inf,GetMemoryManager()); + acceptignoreinfo.GotoNextElement(); + } + acceptignoreinfo.Clear(); +} + +int RTPFakeTransmitter::ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port) +{ + acceptignoreinfo.GotoElement(ip); + if (!acceptignoreinfo.HasCurrentElement()) + return ERR_RTP_FAKETRANS_NOSUCHENTRY; + + PortInfo *inf; + + inf = acceptignoreinfo.GetCurrentElement(); + if (port == 0) // delete all entries + { + inf->all = false; + inf->portlist.clear(); + } + else // a specific port was selected + { + if (inf->all) // currently, all ports are selected. Add the one to remove to the list + { + // we have to check if the list doesn't contain the port already + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == port) // already in list: this means we already deleted the entry + return ERR_RTP_FAKETRANS_NOSUCHENTRY; + } + inf->portlist.push_front(port); + } + else // check if we can find the port in the list + { + std::list::iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; ++it) + { + if (*it == port) // found it! + { + inf->portlist.erase(it); + return 0; + } + } + // didn't find it + return ERR_RTP_FAKETRANS_NOSUCHENTRY; + } + } + return 0; +} + +bool RTPFakeTransmitter::ShouldAcceptData(uint32_t srcip,uint16_t srcport) +{ + if (receivemode == RTPTransmitter::AcceptSome) + { + PortInfo *inf; + + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return false; + + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // only accept the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return true; + } + return false; + } + else // accept all, except the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return false; + } + return true; + } + } + else // IgnoreSome + { + PortInfo *inf; + + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return true; + + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // ignore the ports in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return false; + } + return true; + } + else // ignore all, except the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return true; + } + return false; + } + } + return true; +} + +int RTPFakeTransmitter::CreateLocalIPList() +{ + // first try to obtain the list from the network interface info + + if (!GetLocalIPList_Interfaces()) + { + // If this fails, we'll have to depend on DNS info + GetLocalIPList_DNS(); + } + AddLoopbackAddress(); + return 0; +} + +//#ifdef WIN32 + +bool RTPFakeTransmitter::GetLocalIPList_Interfaces() +{ + // REMINDER: got to find out how to do this + return false; +} +/* +#else // use ioctl + +bool RTPFakeTransmitter::GetLocalIPList_Interfaces() +{ + int status; + char buffer[RTPFakeTRANS_IFREQBUFSIZE]; + struct ifconf ifc; + struct ifreq *ifr; + struct sockaddr *sa; + char *startptr,*endptr; + int remlen; + + ifc.ifc_len = RTPFakeTRANS_IFREQBUFSIZE; + ifc.ifc_buf = buffer; + status = ioctl(rtpsock,SIOCGIFCONF,&ifc); + if (status < 0) + return false; + + startptr = (char *)ifc.ifc_req; + endptr = startptr + ifc.ifc_len; + remlen = ifc.ifc_len; + while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) + { + ifr = (struct ifreq *)startptr; + sa = &(ifr->ifr_addr); +#ifdef RTP_HAVE_SOCKADDR_LEN + if (sa->sa_len <= sizeof(struct sockaddr)) + { + if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; + + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); + } + else + { + int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); + + remlen -= l; + startptr += l; + } +#else // don't have sa_len in struct sockaddr + if (sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; + + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); + +#endif // RTP_HAVE_SOCKADDR_LEN + } + + if (localIPs.empty()) + return false; + return true; +} + +#endif // WIN32 +*/ +void RTPFakeTransmitter::GetLocalIPList_DNS() +{ + struct hostent *he; + char name[1024]; + uint32_t ip; + bool done; + int i,j; + + gethostname(name,1023); + name[1023] = 0; + he = gethostbyname(name); + if (he == 0) + return; + + ip = 0; + i = 0; + done = false; + while (!done) + { + if (he->h_addr_list[i] == NULL) + done = true; + else + { + ip = 0; + for (j = 0 ; j < 4 ; j++) + ip |= ((uint32_t)((unsigned char)he->h_addr_list[i][j])<<((3-j)*8)); + localIPs.push_back(ip); + i++; + } + } +} + +void RTPFakeTransmitter::AddLoopbackAddress() +{ + uint32_t loopbackaddr = (((uint32_t)127)<<24)|((uint32_t)1); + std::list::const_iterator it; + bool found = false; + + for (it = localIPs.begin() ; !found && it != localIPs.end() ; it++) + { + if (*it == loopbackaddr) + found = true; + } + + if (!found) + localIPs.push_back(loopbackaddr); +} + + +} // end namespace + diff --git a/qrtplib/extratransmitters/rtpfaketransmitter.h b/qrtplib/extratransmitters/rtpfaketransmitter.h new file mode 100644 index 000000000..6238cad2b --- /dev/null +++ b/qrtplib/extratransmitters/rtpfaketransmitter.h @@ -0,0 +1,244 @@ +/* + + This class allows for jrtp to process packets without sending them out + anywhere. + The incoming messages are handed in to jrtp through the TransmissionParams + and can be retreived from jrtp through the normal polling mecanisms. + The outgoing RTP/RTCP packets are given to jrtp through the normal + session->SendPacket() and those packets are handed back out to the + client through a callback function (packet_ready_cb). + + example usage : Allows for integration of RTP into gstreamer. + + Copyright (c) 2005 Philippe Khalaf + + This file is a part of JRTPLIB + Copyright (c) 1999-2004 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the "Expertisecentrum Digitale Media" + (http://www.edm.luc.ac.be), a research center of the "Limburgs Universitair + Centrum" (http://www.luc.ac.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef RTPFAKETRANSMITTER_H + +#define RTPFAKETRANSMITTER_H + +#include "rtpconfig.h" + +#include "rtptransmitter.h" +#include "rtpipv4destination.h" +#include "rtphashtable.h" +#include "rtpkeyhashtable.h" +#include + +#ifdef RTP_SUPPORT_THREAD + #include +#endif // RTP_SUPPORT_THREAD + +#define RTPFAKETRANS_HASHSIZE 8317 +#define RTPFAKETRANS_DEFAULTPORTBASE 5000 + +namespace qrtplib +{ + +// Definition of a callback that is called when a packet is ready for sending +// params (*data, data_len, dest_addr, dest_port, rtp [1 if rtp, 0 if rtcp]) +typedef void(*packet_ready_cb)(void*, uint8_t*, uint16_t, uint32_t, uint16_t, int rtp); + +class RTPFakeTransmissionParams : public RTPTransmissionParams +{ +public: + RTPFakeTransmissionParams():RTPTransmissionParams(RTPTransmitter::UserDefinedProto) { portbase = RTPFAKETRANS_DEFAULTPORTBASE; bindIP = 0; multicastTTL = 1; currentdata = NULL;} + void SetBindIP(uint32_t ip) { bindIP = ip; } + void SetPortbase(uint16_t pbase) { portbase = pbase; } + void SetMulticastTTL(uint8_t mcastTTL) { multicastTTL = mcastTTL; } + void SetLocalIPList(std::list &iplist) { localIPs = iplist; } + void ClearLocalIPList() { localIPs.clear(); } + void SetCurrentData(uint8_t *data) { currentdata = data; } + void SetCurrentDataLen(uint16_t len) { currentdatalen = len; } + void SetCurrentDataAddr(uint32_t addr) { currentdataaddr = addr; } + void SetCurrentDataPort(uint16_t port) { currentdataport = port; } + void SetCurrentDataType(bool type) { currentdatatype = type; } + void SetPacketReadyCB(packet_ready_cb cb) { packetreadycb = cb; }; + void SetPacketReadyCBData(void *data) { packetreadycbdata = data; }; + uint32_t GetBindIP() const { return bindIP; } + uint16_t GetPortbase() const { return portbase; } + uint8_t GetMulticastTTL() const { return multicastTTL; } + const std::list &GetLocalIPList() const { return localIPs; } + uint8_t* GetCurrentData() const { return currentdata; } + uint16_t GetCurrentDataLen() const { return currentdatalen; } + uint32_t GetCurrentDataAddr() const { return currentdataaddr; } + uint16_t GetCurrentDataPort() const { return currentdataport; } + bool GetCurrentDataType() const { return currentdatatype; } + packet_ready_cb GetPacketReadyCB() const { return packetreadycb; } + void* GetPacketReadyCBData() const { return packetreadycbdata; } +private: + uint16_t portbase; + uint32_t bindIP; + std::list localIPs; + uint8_t multicastTTL; + uint8_t* currentdata; + uint16_t currentdatalen; + uint32_t currentdataaddr; + uint16_t currentdataport; + bool currentdatatype; + packet_ready_cb packetreadycb; + void *packetreadycbdata; +}; + +class RTPFakeTransmissionInfo : public RTPTransmissionInfo +{ +public: + RTPFakeTransmissionInfo(std::list iplist, + RTPFakeTransmissionParams *transparams) : + RTPTransmissionInfo(RTPTransmitter::UserDefinedProto) + { localIPlist = iplist; params = transparams; } + + ~RTPFakeTransmissionInfo() { } + std::list GetLocalIPList() const { return localIPlist; } + RTPFakeTransmissionParams* GetTransParams() { return params; } +private: + std::list localIPlist; + RTPFakeTransmissionParams *params; +}; + +class RTPFakeTrans_GetHashIndex_IPv4Dest +{ +public: + static int GetIndex(const RTPIPv4Destination &d) { return d.GetIP()%RTPFAKETRANS_HASHSIZE; } +}; + +class RTPFakeTrans_GetHashIndex_uint32_t +{ +public: + static int GetIndex(const uint32_t &k) { return k%RTPFAKETRANS_HASHSIZE; } +}; + +#define RTPFAKETRANS_HEADERSIZE (20+8) + +class RTPFakeTransmitter : public RTPTransmitter +{ +public: + RTPFakeTransmitter(RTPMemoryManager *mgr); + ~RTPFakeTransmitter(); + + int Init(bool treadsafe); + int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() { return RTPFAKETRANS_HEADERSIZE; } + + int Poll(); + int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); + int AbortWait(); + + int SendRTPData(const void *data,size_t len); + int SendRTCPData(const void *data,size_t len); + + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); + + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); + + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); + + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); +private: + int CreateLocalIPList(); + bool GetLocalIPList_Interfaces(); + void GetLocalIPList_DNS(); + void AddLoopbackAddress(); + void FlushPackets(); + int FakePoll(); + int ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port); + int ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port); +#ifdef RTP_SUPPORT_IPV4MULTICAST + bool SetMulticastTTL(uint8_t ttl); +#endif // RTP_SUPPORT_IPV4MULTICAST + bool ShouldAcceptData(uint32_t srcip,uint16_t srcport); + void ClearAcceptIgnoreInfo(); + + RTPFakeTransmissionParams *params; + bool init; + bool created; + bool waitingfordata; + std::list localIPs; + uint16_t portbase; + uint8_t multicastTTL; + RTPTransmitter::ReceiveMode receivemode; + + uint8_t *localhostname; + size_t localhostnamelength; + + RTPHashTable destinations; +#ifdef RTP_SUPPORT_IPV4MULTICAST +// RTPHashTable multicastgroups; +#endif // RTP_SUPPORT_IPV4MULTICAST + std::list rawpacketlist; + + bool supportsmulticasting; + size_t maxpacksize; + + class PortInfo + { + public: + PortInfo() { all = false; } + + bool all; + std::list portlist; + }; + + RTPKeyHashTable acceptignoreinfo; + + int CreateAbortDescriptors(); + void DestroyAbortDescriptors(); + void AbortWaitInternal(); +#ifdef RTP_SUPPORT_THREAD + jthread::JMutex mainmutex,waitmutex; + int threadsafe; +#endif // RTP_SUPPORT_THREAD +}; + +} // end namespace + +#endif // RTPFAKETRANSMITTER_H + diff --git a/qrtplib/rtcpapppacket.cpp b/qrtplib/rtcpapppacket.cpp new file mode 100644 index 000000000..f6807bec2 --- /dev/null +++ b/qrtplib/rtcpapppacket.cpp @@ -0,0 +1,65 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcpapppacket.h" + +namespace qrtplib +{ + +RTCPAPPPacket::RTCPAPPPacket(uint8_t *data,size_t datalength) + : RTCPPacket(APP,data,datalength) +{ + knownformat = false; + + RTCPCommonHeader *hdr; + size_t len = datalength; + + hdr = (RTCPCommonHeader *)data; + if (hdr->padding) + { + uint8_t padcount = data[datalength-1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t)padcount) >= len) + return; + len -= (size_t)padcount; + } + + if (len < (sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2)) + return; + len -= (sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2); + appdatalen = len; + knownformat = true; +} + +} // end namespace + diff --git a/qrtplib/rtcpapppacket.h b/qrtplib/rtcpapppacket.h new file mode 100644 index 000000000..59828d905 --- /dev/null +++ b/qrtplib/rtcpapppacket.h @@ -0,0 +1,129 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpapppacket.h + */ + +#ifndef RTCPAPPPACKET_H + +#define RTCPAPPPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtpstructs.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP APP packet. */ +class JRTPLIB_IMPORTEXPORT RTCPAPPPacket : public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPAPPPacket(uint8_t *data,size_t datalen); + ~RTCPAPPPacket() { } + + /** Returns the subtype contained in the APP packet. */ + uint8_t GetSubType() const; + + /** Returns the SSRC of the source which sent this packet. */ + uint32_t GetSSRC() const; + + /** Returns the name contained in the APP packet. + * Returns the name contained in the APP packet. This alway consists of four bytes and is not NULL-terminated. + */ + uint8_t *GetName(); + + /** Returns a pointer to the actual data. */ + uint8_t *GetAPPData(); + + /** Returns the length of the actual data. */ + size_t GetAPPDataLength() const; +private: + size_t appdatalen; +}; + +inline uint8_t RTCPAPPPacket::GetSubType() const +{ + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; + return hdr->count; +} + +inline uint32_t RTCPAPPPacket::GetSSRC() const +{ + if (!knownformat) + return 0; + + uint32_t *ssrc = (uint32_t *)(data+sizeof(RTCPCommonHeader)); + return ntohl(*ssrc); +} + +inline uint8_t *RTCPAPPPacket::GetName() +{ + if (!knownformat) + return 0; + + return (data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); +} + +inline uint8_t *RTCPAPPPacket::GetAPPData() +{ + if (!knownformat) + return 0; + if (appdatalen == 0) + return 0; + return (data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2); +} + +inline size_t RTCPAPPPacket::GetAPPDataLength() const +{ + if (!knownformat) + return 0; + return appdatalen; +} + +} // end namespace + +#endif // RTCPAPPPACKET_H + diff --git a/qrtplib/rtcpbyepacket.cpp b/qrtplib/rtcpbyepacket.cpp new file mode 100644 index 000000000..a7115e5d1 --- /dev/null +++ b/qrtplib/rtcpbyepacket.cpp @@ -0,0 +1,73 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcpbyepacket.h" + +namespace qrtplib +{ + +RTCPBYEPacket::RTCPBYEPacket(uint8_t *data,size_t datalength) + : RTCPPacket(BYE,data,datalength) +{ + knownformat = false; + reasonoffset = 0; + + RTCPCommonHeader *hdr; + size_t len = datalength; + + hdr = (RTCPCommonHeader *)data; + if (hdr->padding) + { + uint8_t padcount = data[datalength-1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t)padcount) >= len) + return; + len -= (size_t)padcount; + } + + size_t ssrclen = ((size_t)(hdr->count))*sizeof(uint32_t) + sizeof(RTCPCommonHeader); + if (ssrclen > len) + return; + if (ssrclen < len) // there's probably a reason for leaving + { + uint8_t *reasonlength = (data+ssrclen); + size_t reaslen = (size_t)(*reasonlength); + if (reaslen > (len-ssrclen-1)) + return; + reasonoffset = ssrclen; + } + knownformat = true; +} + +} // end namespace + diff --git a/qrtplib/rtcpbyepacket.h b/qrtplib/rtcpbyepacket.h new file mode 100644 index 000000000..765fb19c9 --- /dev/null +++ b/qrtplib/rtcpbyepacket.h @@ -0,0 +1,137 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpbyepacket.h + */ + +#ifndef RTCPBYEPACKET_H + +#define RTCPBYEPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtpstructs.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP BYE packet. */ +class JRTPLIB_IMPORTEXPORT RTCPBYEPacket : public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPBYEPacket(uint8_t *data,size_t datalen); + ~RTCPBYEPacket() { } + + /** Returns the number of SSRC identifiers present in this BYE packet. */ + int GetSSRCCount() const; + + /** Returns the SSRC described by \c index which may have a value from 0 to GetSSRCCount()-1 + * (note that no check is performed to see if \c index is valid). + */ + uint32_t GetSSRC(int index) const; // note: no check is performed to see if index is valid! + + /** Returns true if the BYE packet contains a reason for leaving. */ + bool HasReasonForLeaving() const; + + /** Returns the length of the string which describes why the source(s) left. */ + size_t GetReasonLength() const; + + /** Returns the actual reason for leaving data. */ + uint8_t *GetReasonData(); + +private: + size_t reasonoffset; +}; + +inline int RTCPBYEPacket::GetSSRCCount() const +{ + if (!knownformat) + return 0; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; + return (int)(hdr->count); +} + +inline uint32_t RTCPBYEPacket::GetSSRC(int index) const +{ + if (!knownformat) + return 0; + uint32_t *ssrc = (uint32_t *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)*index); + return ntohl(*ssrc); +} + +inline bool RTCPBYEPacket::HasReasonForLeaving() const +{ + if (!knownformat) + return false; + if (reasonoffset == 0) + return false; + return true; +} + +inline size_t RTCPBYEPacket::GetReasonLength() const +{ + if (!knownformat) + return 0; + if (reasonoffset == 0) + return 0; + uint8_t *reasonlen = (data+reasonoffset); + return (size_t)(*reasonlen); +} + +inline uint8_t *RTCPBYEPacket::GetReasonData() +{ + if (!knownformat) + return 0; + if (reasonoffset == 0) + return 0; + uint8_t *reasonlen = (data+reasonoffset); + if ((*reasonlen) == 0) + return 0; + return (data+reasonoffset+1); +} + +} // end namespace + +#endif // RTCPBYEPACKET_H + diff --git a/qrtplib/rtcpcompoundpacket.cpp b/qrtplib/rtcpcompoundpacket.cpp new file mode 100644 index 000000000..21f43bbb5 --- /dev/null +++ b/qrtplib/rtcpcompoundpacket.cpp @@ -0,0 +1,216 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcpcompoundpacket.h" +#include "rtprawpacket.h" +#include "rtperrors.h" +#include "rtpstructs.h" +#include "rtpdefines.h" +#include "rtcpsrpacket.h" +#include "rtcprrpacket.h" +#include "rtcpsdespacket.h" +#include "rtcpbyepacket.h" +#include "rtcpapppacket.h" +#include "rtcpunknownpacket.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN + +namespace qrtplib +{ + +RTCPCompoundPacket::RTCPCompoundPacket(RTPRawPacket &rawpack, RTPMemoryManager *mgr) : RTPMemoryObject(mgr) +{ + compoundpacket = 0; + compoundpacketlength = 0; + error = 0; + + if (rawpack.IsRTP()) + { + error = ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + return; + } + + uint8_t *data = rawpack.GetData(); + size_t datalen = rawpack.GetDataLength(); + + error = ParseData(data,datalen); + if (error < 0) + return; + + compoundpacket = rawpack.GetData(); + compoundpacketlength = rawpack.GetDataLength(); + deletepacket = true; + + rawpack.ZeroData(); + + rtcppackit = rtcppacklist.begin(); +} + +RTCPCompoundPacket::RTCPCompoundPacket(uint8_t *packet, size_t packetlen, bool deletedata, RTPMemoryManager *mgr) : RTPMemoryObject(mgr) +{ + compoundpacket = 0; + compoundpacketlength = 0; + + error = ParseData(packet,packetlen); + if (error < 0) + return; + + compoundpacket = packet; + compoundpacketlength = packetlen; + deletepacket = deletedata; + + rtcppackit = rtcppacklist.begin(); +} + +RTCPCompoundPacket::RTCPCompoundPacket(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) +{ + compoundpacket = 0; + compoundpacketlength = 0; + error = 0; + deletepacket = true; +} + +int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen) +{ + bool first; + + if (datalen < sizeof(RTCPCommonHeader)) + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + + first = true; + + do + { + RTCPCommonHeader *rtcphdr; + size_t length; + + rtcphdr = (RTCPCommonHeader *)data; + if (rtcphdr->version != RTP_VERSION) // check version + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + if (first) + { + // Check if first packet is SR or RR + + first = false; + if ( ! (rtcphdr->packettype == RTP_RTCPTYPE_SR || rtcphdr->packettype == RTP_RTCPTYPE_RR)) + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + } + + length = (size_t)ntohs(rtcphdr->length); + length++; + length *= sizeof(uint32_t); + + if (length > datalen) // invalid length field + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + + if (rtcphdr->padding) + { + // check if it's the last packet + if (length != datalen) + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + } + + RTCPPacket *p; + + switch (rtcphdr->packettype) + { + case RTP_RTCPTYPE_SR: + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSRPACKET) RTCPSRPacket(data,length); + break; + case RTP_RTCPTYPE_RR: + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPRRPACKET) RTCPRRPacket(data,length); + break; + case RTP_RTCPTYPE_SDES: + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSDESPACKET) RTCPSDESPacket(data,length); + break; + case RTP_RTCPTYPE_BYE: + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPBYEPACKET) RTCPBYEPacket(data,length); + break; + case RTP_RTCPTYPE_APP: + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPAPPPACKET) RTCPAPPPacket(data,length); + break; + default: + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET) RTCPUnknownPacket(data,length); + } + + if (p == 0) + { + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + + rtcppacklist.push_back(p); + + datalen -= length; + data += length; + } while (datalen >= (size_t)sizeof(RTCPCommonHeader)); + + if (datalen != 0) // some remaining bytes + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + return 0; +} + +RTCPCompoundPacket::~RTCPCompoundPacket() +{ + ClearPacketList(); + if (compoundpacket && deletepacket) + RTPDeleteByteArray(compoundpacket,GetMemoryManager()); +} + +void RTCPCompoundPacket::ClearPacketList() +{ + std::list::const_iterator it; + + for (it = rtcppacklist.begin() ; it != rtcppacklist.end() ; it++) + RTPDelete(*it,GetMemoryManager()); + rtcppacklist.clear(); + rtcppackit = rtcppacklist.begin(); +} + +} // end namespace + diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h new file mode 100644 index 000000000..6041df7e4 --- /dev/null +++ b/qrtplib/rtcpcompoundpacket.h @@ -0,0 +1,108 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpcompoundpacket.h + */ + +#ifndef RTCPCOMPOUNDPACKET_H + +#define RTCPCOMPOUNDPACKET_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtpmemoryobject.h" +#include + +namespace qrtplib +{ + +class RTPRawPacket; +class RTCPPacket; + +/** Represents an RTCP compound packet. */ +class JRTPLIB_IMPORTEXPORT RTCPCompoundPacket : public RTPMemoryObject +{ +public: + /** Creates an RTCPCompoundPacket instance from the data in \c rawpack, installing a memory manager if specified. */ + RTCPCompoundPacket(RTPRawPacket &rawpack, RTPMemoryManager *memmgr = 0); + + /** Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. + * Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. The \c deletedata + * flag specifies if the data in \c packet should be deleted when the compound packet is destroyed. If + * specified, a memory manager will be installed. + */ + RTCPCompoundPacket(uint8_t *packet, size_t len, bool deletedata = true, RTPMemoryManager *memmgr = 0); +protected: + RTCPCompoundPacket(RTPMemoryManager *memmgr); // this is for the compoundpacket builder +public: + virtual ~RTCPCompoundPacket(); + + /** Checks if the RTCP compound packet was created successfully. + * If the raw packet data in the constructor could not be parsed, this function returns the error code of + * what went wrong. If the packet had an invalid format, the return value is \c ERR_RTP_RTCPCOMPOUND_INVALIDPACKET. + */ + int GetCreationError() { return error; } + + /** Returns a pointer to the data of the entire RTCP compound packet. */ + uint8_t *GetCompoundPacketData() { return compoundpacket; } + + /** Returns the size of the entire RTCP compound packet. */ + size_t GetCompoundPacketLength() { return compoundpacketlength; } + + /** Starts the iteration over the individual RTCP packets in the RTCP compound packet. */ + void GotoFirstPacket() { rtcppackit = rtcppacklist.begin(); } + + /** Returns a pointer to the next individual RTCP packet. + * Returns a pointer to the next individual RTCP packet. Note that no \c delete call may be done + * on the RTCPPacket instance which is returned. + */ + RTCPPacket *GetNextPacket() { if (rtcppackit == rtcppacklist.end()) return 0; RTCPPacket *p = *rtcppackit; rtcppackit++; return p; } + +protected: + void ClearPacketList(); + int ParseData(uint8_t *packet, size_t len); + + int error; + + uint8_t *compoundpacket; + size_t compoundpacketlength; + bool deletepacket; + + std::list rtcppacklist; + std::list::const_iterator rtcppackit; +}; + +} // end namespace + +#endif // RTCPCOMPOUNDPACKET_H + diff --git a/qrtplib/rtcpcompoundpacketbuilder.cpp b/qrtplib/rtcpcompoundpacketbuilder.cpp new file mode 100644 index 000000000..ce0c67f69 --- /dev/null +++ b/qrtplib/rtcpcompoundpacketbuilder.cpp @@ -0,0 +1,811 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcpcompoundpacketbuilder.h" +#include "rtcpsrpacket.h" +#include "rtcprrpacket.h" +#include "rtcpsdespacket.h" +#include "rtcpbyepacket.h" +#include "rtcpapppacket.h" +#ifdef RTP_SUPPORT_RTCPUNKNOWN + #include "rtcpunknownpacket.h" +#endif // RTP_SUPPORT_RTCPUNKNOWN +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN +#include + +namespace qrtplib +{ + +RTCPCompoundPacketBuilder::RTCPCompoundPacketBuilder(RTPMemoryManager *mgr) : RTCPCompoundPacket(mgr), report(mgr), sdes(mgr) +{ + byesize = 0; + appsize = 0; +#ifdef RTP_SUPPORT_RTCPUNKNOWN + unknownsize = 0; +#endif // RTP_SUPPORT_RTCPUNKNOWN + maximumpacketsize = 0; + buffer = 0; + external = false; + arebuilding = false; +} + +RTCPCompoundPacketBuilder::~RTCPCompoundPacketBuilder() +{ + if (external) + compoundpacket = 0; // make sure RTCPCompoundPacket doesn't delete the external buffer + ClearBuildBuffers(); +} + +void RTCPCompoundPacketBuilder::ClearBuildBuffers() +{ + report.Clear(); + sdes.Clear(); + + std::list::const_iterator it; + for (it = byepackets.begin() ; it != byepackets.end() ; it++) + { + if ((*it).packetdata) + RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); + } + for (it = apppackets.begin() ; it != apppackets.end() ; it++) + { + if ((*it).packetdata) + RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); + } +#ifdef RTP_SUPPORT_RTCPUNKNOWN + for (it = unknownpackets.begin() ; it != unknownpackets.end() ; it++) + { + if ((*it).packetdata) + RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); + } +#endif // RTP_SUPPORT_RTCPUNKNOWN + + byepackets.clear(); + apppackets.clear(); +#ifdef RTP_SUPPORT_RTCPUNKNOWN + unknownpackets.clear(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + byesize = 0; + appsize = 0; +#ifdef RTP_SUPPORT_RTCPUNKNOWN + unknownsize = 0; +#endif // RTP_SUPPORT_RTCPUNKNOWN +} + +int RTCPCompoundPacketBuilder::InitBuild(size_t maxpacketsize) +{ + if (arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; + if (compoundpacket) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT; + + if (maxpacketsize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL; + + maximumpacketsize = maxpacketsize; + buffer = 0; + external = false; + byesize = 0; + appsize = 0; +#ifdef RTP_SUPPORT_RTCPUNKNOWN + unknownsize = 0; +#endif // RTP_SUPPORT_RTCPUNKNOWN + + arebuilding = true; + return 0; +} + +int RTCPCompoundPacketBuilder::InitBuild(void *externalbuffer,size_t buffersize) +{ + if (arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; + if (compoundpacket) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT; + + if (buffersize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL; + + maximumpacketsize = buffersize; + buffer = (uint8_t *)externalbuffer; + external = true; + byesize = 0; + appsize = 0; +#ifdef RTP_SUPPORT_RTCPUNKNOWN + unknownsize = 0; +#endif // RTP_SUPPORT_RTCPUNKNOWN + + arebuilding = true; + return 0; +} + +int RTCPCompoundPacketBuilder::StartSenderReport(uint32_t senderssrc,const RTPNTPTime &ntptimestamp,uint32_t rtptimestamp, + uint32_t packetcount,uint32_t octetcount) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + + if (report.headerlength != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; + +#ifndef RTP_SUPPORT_RTCPUNKNOWN + size_t totalsize = byesize+appsize+sdes.NeededBytes(); +#else + size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + size_t sizeleft = maximumpacketsize-totalsize; + size_t neededsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+sizeof(RTCPSenderReport); + + if (neededsize > sizeleft) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + // fill in some things + + report.headerlength = sizeof(uint32_t)+sizeof(RTCPSenderReport); + report.isSR = true; + + uint32_t *ssrc = (uint32_t *)report.headerdata; + *ssrc = htonl(senderssrc); + + RTCPSenderReport *sr = (RTCPSenderReport *)(report.headerdata + sizeof(uint32_t)); + sr->ntptime_msw = htonl(ntptimestamp.GetMSW()); + sr->ntptime_lsw = htonl(ntptimestamp.GetLSW()); + sr->rtptimestamp = htonl(rtptimestamp); + sr->packetcount = htonl(packetcount); + sr->octetcount = htonl(octetcount); + + return 0; +} + +int RTCPCompoundPacketBuilder::StartReceiverReport(uint32_t senderssrc) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; + +#ifndef RTP_SUPPORT_RTCPUNKNOWN + size_t totalsize = byesize+appsize+sdes.NeededBytes(); +#else + size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + size_t sizeleft = maximumpacketsize-totalsize; + size_t neededsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t); + + if (neededsize > sizeleft) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + // fill in some things + + report.headerlength = sizeof(uint32_t); + report.isSR = false; + + uint32_t *ssrc = (uint32_t *)report.headerdata; + *ssrc = htonl(senderssrc); + + return 0; +} + +int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t packetslost,uint32_t exthighestseq, + uint32_t jitter,uint32_t lsr,uint32_t dlsr) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength == 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED; + +#ifndef RTP_SUPPORT_RTCPUNKNOWN + size_t totalothersize = byesize+appsize+sdes.NeededBytes(); +#else + size_t totalothersize = byesize+appsize+unknownsize+sdes.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + size_t reportsizewithextrablock = report.NeededBytesWithExtraReportBlock(); + + if ((totalothersize+reportsizewithextrablock) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPRECEIVERREPORT) uint8_t[sizeof(RTCPReceiverReport)]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + + RTCPReceiverReport *rr = (RTCPReceiverReport *)buf; + uint32_t *packlost = (uint32_t *)&packetslost; + uint32_t packlost2 = (*packlost); + + rr->ssrc = htonl(ssrc); + rr->fractionlost = fractionlost; + rr->packetslost[2] = (uint8_t)(packlost2&0xFF); + rr->packetslost[1] = (uint8_t)((packlost2>>8)&0xFF); + rr->packetslost[0] = (uint8_t)((packlost2>>16)&0xFF); + rr->exthighseqnr = htonl(exthighestseq); + rr->jitter = htonl(jitter); + rr->lsr = htonl(lsr); + rr->dlsr = htonl(dlsr); + + report.reportblocks.push_back(Buffer(buf,sizeof(RTCPReceiverReport))); + return 0; +} + +int RTCPCompoundPacketBuilder::AddSDESSource(uint32_t ssrc) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + +#ifndef RTP_SUPPORT_RTCPUNKNOWN + size_t totalotherbytes = byesize+appsize+report.NeededBytes(); +#else + size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + size_t sdessizewithextrasource = sdes.NeededBytesWithExtraSource(); + + if ((totalotherbytes + sdessizewithextrasource) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + int status; + + if ((status = sdes.AddSSRC(ssrc)) < 0) + return status; + return 0; +} + +int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t,const void *itemdata,uint8_t itemlength) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (sdes.sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + + uint8_t itemid; + + switch(t) + { + case RTCPSDESPacket::CNAME: + itemid = RTCP_SDES_ID_CNAME; + break; + case RTCPSDESPacket::NAME: + itemid = RTCP_SDES_ID_NAME; + break; + case RTCPSDESPacket::EMAIL: + itemid = RTCP_SDES_ID_EMAIL; + break; + case RTCPSDESPacket::PHONE: + itemid = RTCP_SDES_ID_PHONE; + break; + case RTCPSDESPacket::LOC: + itemid = RTCP_SDES_ID_LOCATION; + break; + case RTCPSDESPacket::TOOL: + itemid = RTCP_SDES_ID_TOOL; + break; + case RTCPSDESPacket::NOTE: + itemid = RTCP_SDES_ID_NOTE; + break; + default: + return ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE; + } + +#ifndef RTP_SUPPORT_RTCPUNKNOWN + size_t totalotherbytes = byesize+appsize+report.NeededBytes(); +#else + size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); + + if ((sdessizewithextraitem+totalotherbytes) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf; + size_t len; + + buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPSDESBLOCK) uint8_t[sizeof(RTCPSDESHeader)+(size_t)itemlength]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + len = sizeof(RTCPSDESHeader)+(size_t)itemlength; + + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(buf); + + sdeshdr->sdesid = itemid; + sdeshdr->length = itemlength; + if (itemlength != 0) + memcpy((buf + sizeof(RTCPSDESHeader)),itemdata,(size_t)itemlength); + + sdes.AddItem(buf,len); + return 0; +} + +#ifdef RTP_SUPPORT_SDESPRIV +int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata,uint8_t prefixlength,const void *valuedata, + uint8_t valuelength) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (sdes.sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + + size_t itemlength = ((size_t)prefixlength)+1+((size_t)valuelength); + if (itemlength > 255) + return ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG; + +#ifndef RTP_SUPPORT_RTCPUNKNOWN + size_t totalotherbytes = byesize+appsize+report.NeededBytes(); +#else + size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); + + if ((sdessizewithextraitem+totalotherbytes) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf; + size_t len; + + buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPSDESBLOCK) uint8_t[sizeof(RTCPSDESHeader)+itemlength]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + len = sizeof(RTCPSDESHeader)+(size_t)itemlength; + + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(buf); + + sdeshdr->sdesid = RTCP_SDES_ID_PRIVATE; + sdeshdr->length = itemlength; + + buf[sizeof(RTCPSDESHeader)] = prefixlength; + if (prefixlength != 0) + memcpy((buf+sizeof(RTCPSDESHeader)+1),prefixdata,(size_t)prefixlength); + if (valuelength != 0) + memcpy((buf+sizeof(RTCPSDESHeader)+1+(size_t)prefixlength),valuedata,(size_t)valuelength); + + sdes.AddItem(buf,len); + return 0; +} +#endif // RTP_SUPPORT_SDESPRIV + +int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs,uint8_t numssrcs,const void *reasondata,uint8_t reasonlength) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + + if (numssrcs > 31) + return ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS; + + size_t packsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)*((size_t)numssrcs); + size_t zerobytes = 0; + + if (reasonlength > 0) + { + packsize += 1; // 1 byte for the length; + packsize += (size_t)reasonlength; + + size_t r = (packsize&0x03); + if (r != 0) + { + zerobytes = 4-r; + packsize += zerobytes; + } + } + +#ifndef RTP_SUPPORT_RTCPUNKNOWN + size_t totalotherbytes = appsize+byesize+sdes.NeededBytes()+report.NeededBytes(); +#else + size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + + if ((totalotherbytes + packsize) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf; + size_t numwords; + + buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPBYEPACKET) uint8_t[packsize]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *)buf; + + hdr->version = 2; + hdr->padding = 0; + hdr->count = numssrcs; + + numwords = packsize/sizeof(uint32_t); + hdr->length = htons((uint16_t)(numwords-1)); + hdr->packettype = RTP_RTCPTYPE_BYE; + + uint32_t *sources = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); + uint8_t srcindex; + + for (srcindex = 0 ; srcindex < numssrcs ; srcindex++) + sources[srcindex] = htonl(ssrcs[srcindex]); + + if (reasonlength != 0) + { + size_t offset = sizeof(RTCPCommonHeader)+((size_t)numssrcs)*sizeof(uint32_t); + + buf[offset] = reasonlength; + memcpy((buf+offset+1),reasondata,(size_t)reasonlength); + for (size_t i = 0 ; i < zerobytes ; i++) + buf[packsize-1-i] = 0; + } + + byepackets.push_back(Buffer(buf,packsize)); + byesize += packsize; + + return 0; +} + +int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype,uint32_t ssrc,const uint8_t name[4],const void *appdata,size_t appdatalen) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (subtype > 31) + return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE; + if ((appdatalen%4) != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH; + + size_t appdatawords = appdatalen/4; + + if ((appdatawords+2) > 65535) + return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; + + size_t packsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2+appdatalen; +#ifndef RTP_SUPPORT_RTCPUNKNOWN + size_t totalotherbytes = appsize+byesize+sdes.NeededBytes()+report.NeededBytes(); +#else + size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + + if ((totalotherbytes + packsize) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf; + + buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPAPPPACKET) uint8_t[packsize]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *)buf; + + hdr->version = 2; + hdr->padding = 0; + hdr->count = subtype; + + hdr->length = htons((uint16_t)(appdatawords+2)); + hdr->packettype = RTP_RTCPTYPE_APP; + + uint32_t *source = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); + *source = htonl(ssrc); + + buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+0] = name[0]; + buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+1] = name[1]; + buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+2] = name[2]; + buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+3] = name[3]; + + if (appdatalen > 0) + memcpy((buf+sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2),appdata,appdatalen); + + apppackets.push_back(Buffer(buf,packsize)); + appsize += packsize; + + return 0; +} + +#ifdef RTP_SUPPORT_RTCPUNKNOWN + +int RTCPCompoundPacketBuilder::AddUnknownPacket(uint8_t payload_type, uint8_t subtype, uint32_t ssrc, const void *data, size_t len) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + + size_t datawords = len/4; + + if ((datawords+2) > 65535) + return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; + + size_t packsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+len; + size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); + + if ((totalotherbytes + packsize) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET) uint8_t[packsize]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *)buf; + + hdr->version = 2; + hdr->padding = 0; + hdr->count = subtype; + hdr->length = htons((uint16_t)(datawords+1)); + hdr->packettype = payload_type; + + uint32_t *source = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); + *source = htonl(ssrc); + + if (len > 0) + memcpy((buf+sizeof(RTCPCommonHeader)+sizeof(uint32_t)),data,len); + + unknownpackets.push_back(Buffer(buf,packsize)); + unknownsize += packsize; + + return 0; +} + +#endif // RTP_SUPPORT_RTCPUNKNOWN + +int RTCPCompoundPacketBuilder::EndBuild() +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength == 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT; + + uint8_t *buf; + size_t len; + +#ifndef RTP_SUPPORT_RTCPUNKNOWN + len = appsize+byesize+report.NeededBytes()+sdes.NeededBytes(); +#else + len = appsize+unknownsize+byesize+report.NeededBytes()+sdes.NeededBytes(); +#endif // RTP_SUPPORT_RTCPUNKNOWN + + if (!external) + { + buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPCOMPOUNDPACKET) uint8_t[len]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + } + else + buf = buffer; + + uint8_t *curbuf = buf; + RTCPPacket *p; + + // first, we'll add all report info + + { + bool firstpacket = true; + bool done = false; + std::list::const_iterator it = report.reportblocks.begin(); + do + { + RTCPCommonHeader *hdr = (RTCPCommonHeader *)curbuf; + size_t offset; + + hdr->version = 2; + hdr->padding = 0; + + if (firstpacket && report.isSR) + { + hdr->packettype = RTP_RTCPTYPE_SR; + memcpy((curbuf+sizeof(RTCPCommonHeader)),report.headerdata,report.headerlength); + offset = sizeof(RTCPCommonHeader)+report.headerlength; + } + else + { + hdr->packettype = RTP_RTCPTYPE_RR; + memcpy((curbuf+sizeof(RTCPCommonHeader)),report.headerdata,sizeof(uint32_t)); + offset = sizeof(RTCPCommonHeader)+sizeof(uint32_t); + } + firstpacket = false; + + uint8_t count = 0; + + while (it != report.reportblocks.end() && count < 31) + { + memcpy(curbuf+offset,(*it).packetdata,(*it).packetlength); + offset += (*it).packetlength; + count++; + it++; + } + + size_t numwords = offset/sizeof(uint32_t); + + hdr->length = htons((uint16_t)(numwords-1)); + hdr->count = count; + + // add entry in parent's list + if (hdr->packettype == RTP_RTCPTYPE_SR) + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSRPACKET) RTCPSRPacket(curbuf,offset); + else + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPRRPACKET) RTCPRRPacket(curbuf,offset); + if (p == 0) + { + if (!external) + RTPDeleteByteArray(buf,GetMemoryManager()); + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); + + curbuf += offset; + if (it == report.reportblocks.end()) + done = true; + } while (!done); + } + + // then, we'll add the sdes info + + if (!sdes.sdessources.empty()) + { + bool done = false; + std::list::const_iterator sourceit = sdes.sdessources.begin(); + + do + { + RTCPCommonHeader *hdr = (RTCPCommonHeader *)curbuf; + size_t offset = sizeof(RTCPCommonHeader); + + hdr->version = 2; + hdr->padding = 0; + hdr->packettype = RTP_RTCPTYPE_SDES; + + uint8_t sourcecount = 0; + + while (sourceit != sdes.sdessources.end() && sourcecount < 31) + { + uint32_t *ssrc = (uint32_t *)(curbuf+offset); + *ssrc = htonl((*sourceit)->ssrc); + offset += sizeof(uint32_t); + + std::list::const_iterator itemit,itemend; + + itemit = (*sourceit)->items.begin(); + itemend = (*sourceit)->items.end(); + while (itemit != itemend) + { + memcpy(curbuf+offset,(*itemit).packetdata,(*itemit).packetlength); + offset += (*itemit).packetlength; + itemit++; + } + + curbuf[offset] = 0; // end of item list; + offset++; + + size_t r = offset&0x03; + if (r != 0) // align to 32 bit boundary + { + size_t num = 4-r; + size_t i; + + for (i = 0 ; i < num ; i++) + curbuf[offset+i] = 0; + offset += num; + } + + sourceit++; + sourcecount++; + } + + size_t numwords = offset/4; + + hdr->count = sourcecount; + hdr->length = htons((uint16_t)(numwords-1)); + + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSDESPACKET) RTCPSDESPacket(curbuf,offset); + if (p == 0) + { + if (!external) + RTPDeleteByteArray(buf,GetMemoryManager()); + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); + + curbuf += offset; + if (sourceit == sdes.sdessources.end()) + done = true; + } while (!done); + } + + // adding the app data + + { + std::list::const_iterator it; + + for (it = apppackets.begin() ; it != apppackets.end() ; it++) + { + memcpy(curbuf,(*it).packetdata,(*it).packetlength); + + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPAPPPACKET) RTCPAPPPacket(curbuf,(*it).packetlength); + if (p == 0) + { + if (!external) + RTPDeleteByteArray(buf,GetMemoryManager()); + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); + + curbuf += (*it).packetlength; + } + } + +#ifdef RTP_SUPPORT_RTCPUNKNOWN + + // adding the unknown data + + { + std::list::const_iterator it; + + for (it = unknownpackets.begin() ; it != unknownpackets.end() ; it++) + { + memcpy(curbuf,(*it).packetdata,(*it).packetlength); + + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET) RTCPUnknownPacket(curbuf,(*it).packetlength); + if (p == 0) + { + if (!external) + RTPDeleteByteArray(buf,GetMemoryManager()); + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); + + curbuf += (*it).packetlength; + } + } + +#endif // RTP_SUPPORT_RTCPUNKNOWN + + // adding bye packets + + { + std::list::const_iterator it; + + for (it = byepackets.begin() ; it != byepackets.end() ; it++) + { + memcpy(curbuf,(*it).packetdata,(*it).packetlength); + + p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPBYEPACKET) RTCPBYEPacket(curbuf,(*it).packetlength); + if (p == 0) + { + if (!external) + RTPDeleteByteArray(buf,GetMemoryManager()); + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); + + curbuf += (*it).packetlength; + } + } + + compoundpacket = buf; + compoundpacketlength = len; + arebuilding = false; + ClearBuildBuffers(); + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h new file mode 100644 index 000000000..2c8f3a541 --- /dev/null +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -0,0 +1,400 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpcompoundpacketbuilder.h + */ + +#ifndef RTCPCOMPOUNDPACKETBUILDER_H + +#define RTCPCOMPOUNDPACKETBUILDER_H + +#include "rtpconfig.h" +#include "rtcpcompoundpacket.h" +#include "rtptimeutilities.h" +#include "rtcpsdespacket.h" +#include "rtperrors.h" +#include + +namespace qrtplib +{ + +class RTPMemoryManager; + +/** This class can be used to construct an RTCP compound packet. + * The RTCPCompoundPacketBuilder class can be used to construct an RTCP compound packet. It inherits the member + * functions of RTCPCompoundPacket which can be used to access the information in the compound packet once it has + * been built successfully. The member functions described below return \c ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT + * if the action would cause the maximum allowed size to be exceeded. + */ +class JRTPLIB_IMPORTEXPORT RTCPCompoundPacketBuilder : public RTCPCompoundPacket +{ +public: + /** Constructs an RTCPCompoundPacketBuilder instance, optionally installing a memory manager. */ + RTCPCompoundPacketBuilder(RTPMemoryManager *memmgr = 0); + ~RTCPCompoundPacketBuilder(); + + /** Starts building an RTCP compound packet with maximum size \c maxpacketsize. + * Starts building an RTCP compound packet with maximum size \c maxpacketsize. New memory will be allocated + * to store the packet. + */ + int InitBuild(size_t maxpacketsize); + + /** Starts building a RTCP compound packet. + * Starts building a RTCP compound packet. Data will be stored in \c externalbuffer which + * can contain \c buffersize bytes. + */ + int InitBuild(void *externalbuffer,size_t buffersize); + + /** Adds a sender report to the compound packet. + * Tells the packet builder that the packet should start with a sender report which will contain + * the sender information specified by this function's arguments. Once the sender report is started, + * report blocks can be added using the AddReportBlock function. + */ + int StartSenderReport(uint32_t senderssrc,const RTPNTPTime &ntptimestamp,uint32_t rtptimestamp, + uint32_t packetcount,uint32_t octetcount); + + /** Adds a receiver report to the compound packet. + * Tells the packet builder that the packet should start with a receiver report which will contain + * he sender SSRC \c senderssrc. Once the sender report is started, report blocks can be added using the + * AddReportBlock function. + */ + int StartReceiverReport(uint32_t senderssrc); + + /** Adds the report block information specified by the function's arguments. + * Adds the report block information specified by the function's arguments. If more than 31 report blocks + * are added, the builder will automatically use a new RTCP receiver report packet. + */ + int AddReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t packetslost,uint32_t exthighestseq, + uint32_t jitter,uint32_t lsr,uint32_t dlsr); + + /** Starts an SDES chunk for participant \c ssrc. */ + int AddSDESSource(uint32_t ssrc); + + /** Adds a normal (non-private) SDES item of type \c t to the current SDES chunk. + * Adds a normal (non-private) SDES item of type \c t to the current SDES chunk. The item's value + * will have length \c itemlength and will contain the data \c itemdata. + */ + int AddSDESNormalItem(RTCPSDESPacket::ItemType t,const void *itemdata,uint8_t itemlength); +#ifdef RTP_SUPPORT_SDESPRIV + /** Adds an SDES PRIV item described by the function's arguments to the current SDES chunk. */ + int AddSDESPrivateItem(const void *prefixdata,uint8_t prefixlength,const void *valuedata, + uint8_t valuelength); +#endif // RTP_SUPPORT_SDESPRIV + + /** Adds a BYE packet to the compound packet. + * Adds a BYE packet to the compound packet. It will contain \c numssrcs source identifiers specified in + * \c ssrcs and will indicate as reason for leaving the string of length \c reasonlength + * containing data \c reasondata. + */ + int AddBYEPacket(uint32_t *ssrcs,uint8_t numssrcs,const void *reasondata,uint8_t reasonlength); + + /** Adds the APP packet specified by the arguments to the compound packet. + * Adds the APP packet specified by the arguments to the compound packet. Note that \c appdatalen has to be + * a multiple of four. + */ + int AddAPPPacket(uint8_t subtype,uint32_t ssrc,const uint8_t name[4],const void *appdata,size_t appdatalen); + + /** Finishes building the compound packet. + * Finishes building the compound packet. If successful, the RTCPCompoundPacket member functions + * can be used to access the RTCP packet data. + */ + int EndBuild(); + +#ifdef RTP_SUPPORT_RTCPUNKNOWN + /** Adds the RTCP packet specified by the arguments to the compound packet. + * Adds the RTCP packet specified by the arguments to the compound packet. + */ + int AddUnknownPacket(uint8_t payload_type, uint8_t subtype, uint32_t ssrc, const void *data, size_t len); +#endif // RTP_SUPPORT_RTCPUNKNOWN +private: + class Buffer + { + public: + Buffer():packetdata(0),packetlength(0) { } + Buffer(uint8_t *data,size_t len):packetdata(data),packetlength(len) { } + + uint8_t *packetdata; + size_t packetlength; + }; + + class Report : public RTPMemoryObject + { + public: + Report(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) + { + headerdata = (uint8_t *)headerdata32; + isSR = false; + headerlength = 0; + } + ~Report() { Clear(); } + + void Clear() + { + std::list::const_iterator it; + for (it = reportblocks.begin() ; it != reportblocks.end() ; it++) + { + if ((*it).packetdata) + RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); + } + reportblocks.clear(); + isSR = false; + headerlength = 0; + } + + size_t NeededBytes() + { + size_t x,n,d,r; + n = reportblocks.size(); + if (n == 0) + { + if (headerlength == 0) + return 0; + x = sizeof(RTCPCommonHeader)+headerlength; + } + else + { + x = n*sizeof(RTCPReceiverReport); + d = n/31; // max 31 reportblocks per report + r = n%31; + if (r != 0) + d++; + x += d*(sizeof(RTCPCommonHeader)+sizeof(uint32_t)); /* header and SSRC */ + if (isSR) + x += sizeof(RTCPSenderReport); + } + return x; + } + + size_t NeededBytesWithExtraReportBlock() + { + size_t x,n,d,r; + n = reportblocks.size() + 1; // +1 for the extra block + x = n*sizeof(RTCPReceiverReport); + d = n/31; // max 31 reportblocks per report + r = n%31; + if (r != 0) + d++; + x += d*(sizeof(RTCPCommonHeader)+sizeof(uint32_t)); /* header and SSRC */ + if (isSR) + x += sizeof(RTCPSenderReport); + return x; + } + + bool isSR; + + uint8_t *headerdata; + uint32_t headerdata32[(sizeof(uint32_t)+sizeof(RTCPSenderReport))/sizeof(uint32_t)]; // either for ssrc and sender info or just ssrc + size_t headerlength; + std::list reportblocks; + }; + + class SDESSource : public RTPMemoryObject + { + public: + SDESSource(uint32_t s,RTPMemoryManager *mgr) : RTPMemoryObject(mgr),ssrc(s),totalitemsize(0) { } + ~SDESSource() + { + std::list::const_iterator it; + for (it = items.begin() ; it != items.end() ; it++) + { + if ((*it).packetdata) + RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); + } + items.clear(); + } + + size_t NeededBytes() + { + size_t x,r; + x = totalitemsize + 1; // +1 for the 0 byte which terminates the item list + r = x%sizeof(uint32_t); + if (r != 0) + x += (sizeof(uint32_t)-r); // make sure it ends on a 32 bit boundary + x += sizeof(uint32_t); // for ssrc + return x; + } + + size_t NeededBytesWithExtraItem(uint8_t itemdatalength) + { + size_t x,r; + x = totalitemsize + sizeof(RTCPSDESHeader) + (size_t)itemdatalength + 1; + r = x%sizeof(uint32_t); + if (r != 0) + x += (sizeof(uint32_t)-r); // make sure it ends on a 32 bit boundary + x += sizeof(uint32_t); // for ssrc + return x; + } + + void AddItem(uint8_t *buf,size_t len) + { + Buffer b(buf,len); + totalitemsize += len; + items.push_back(b); + } + + uint32_t ssrc; + std::list items; + private: + size_t totalitemsize; + }; + + class SDES : public RTPMemoryObject + { + public: + SDES(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) { sdesit = sdessources.end(); } + ~SDES() { Clear(); } + + void Clear() + { + std::list::const_iterator it; + + for (it = sdessources.begin() ; it != sdessources.end() ; it++) + RTPDelete(*it,GetMemoryManager()); + sdessources.clear(); + } + + int AddSSRC(uint32_t ssrc) + { + SDESSource *s = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_SDESSOURCE) SDESSource(ssrc,GetMemoryManager()); + if (s == 0) + return ERR_RTP_OUTOFMEM; + sdessources.push_back(s); + sdesit = sdessources.end(); + sdesit--; + return 0; + } + + int AddItem(uint8_t *buf,size_t len) + { + if (sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + (*sdesit)->AddItem(buf,len); + return 0; + } + + size_t NeededBytes() + { + std::list::const_iterator it; + size_t x = 0; + size_t n,d,r; + + if (sdessources.empty()) + return 0; + + for (it = sdessources.begin() ; it != sdessources.end() ; it++) + x += (*it)->NeededBytes(); + n = sdessources.size(); + d = n/31; + r = n%31; + if (r != 0) + d++; + x += d*sizeof(RTCPCommonHeader); + return x; + } + + size_t NeededBytesWithExtraItem(uint8_t itemdatalength) + { + std::list::const_iterator it; + size_t x = 0; + size_t n,d,r; + + if (sdessources.empty()) + return 0; + + for (it = sdessources.begin() ; it != sdesit ; it++) + x += (*it)->NeededBytes(); + x += (*sdesit)->NeededBytesWithExtraItem(itemdatalength); + n = sdessources.size(); + d = n/31; + r = n%31; + if (r != 0) + d++; + x += d*sizeof(RTCPCommonHeader); + return x; + } + + size_t NeededBytesWithExtraSource() + { + std::list::const_iterator it; + size_t x = 0; + size_t n,d,r; + + if (sdessources.empty()) + return 0; + + for (it = sdessources.begin() ; it != sdessources.end() ; it++) + x += (*it)->NeededBytes(); + + // for the extra source we'll need at least 8 bytes (ssrc and four 0 bytes) + x += sizeof(uint32_t)*2; + + n = sdessources.size() + 1; // also, the number of sources will increase + d = n/31; + r = n%31; + if (r != 0) + d++; + x += d*sizeof(RTCPCommonHeader); + return x; + } + + std::list sdessources; + private: + std::list::const_iterator sdesit; + }; + + size_t maximumpacketsize; + uint8_t *buffer; + bool external; + bool arebuilding; + + Report report; + SDES sdes; + + std::list byepackets; + size_t byesize; + + std::list apppackets; + size_t appsize; + +#ifdef RTP_SUPPORT_RTCPUNKNOWN + std::list unknownpackets; + size_t unknownsize; +#endif // RTP_SUPPORT_RTCPUNKNOWN + + void ClearBuildBuffers(); +}; + +} // end namespace + +#endif // RTCPCOMPOUNDPACKETBUILDER_H + diff --git a/qrtplib/rtcppacket.h b/qrtplib/rtcppacket.h new file mode 100644 index 000000000..32df3e682 --- /dev/null +++ b/qrtplib/rtcppacket.h @@ -0,0 +1,91 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcppacket.h + */ + +#ifndef RTCPPACKET_H + +#define RTCPPACKET_H + +#include "rtpconfig.h" +#include "rtptypes.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Base class for specific types of RTCP packets. */ +class JRTPLIB_IMPORTEXPORT RTCPPacket +{ +public: + /** Identifies the specific kind of RTCP packet. */ + enum PacketType + { + SR, /**< An RTCP sender report. */ + RR, /**< An RTCP receiver report. */ + SDES, /**< An RTCP source description packet. */ + BYE, /**< An RTCP bye packet. */ + APP, /**< An RTCP packet containing application specific data. */ + Unknown /**< The type of RTCP packet was not recognized. */ + }; +protected: + RTCPPacket(PacketType t,uint8_t *d,size_t dlen) : data(d),datalen(dlen),packettype(t) { knownformat = false; } +public: + virtual ~RTCPPacket() { } + + /** Returns \c true if the subclass was able to interpret the data and \c false otherwise. */ + bool IsKnownFormat() const { return knownformat; } + + /** Returns the actual packet type which the subclass implements. */ + PacketType GetPacketType() const { return packettype; } + + /** Returns a pointer to the data of this RTCP packet. */ + uint8_t *GetPacketData() { return data; } + + /** Returns the length of this RTCP packet. */ + size_t GetPacketLength() const { return datalen; } + +protected: + uint8_t *data; + size_t datalen; + bool knownformat; +private: + const PacketType packettype; +}; + +} // end namespace + +#endif // RTCPPACKET_H + diff --git a/qrtplib/rtcppacketbuilder.cpp b/qrtplib/rtcppacketbuilder.cpp new file mode 100644 index 000000000..fb05ba339 --- /dev/null +++ b/qrtplib/rtcppacketbuilder.cpp @@ -0,0 +1,737 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcppacketbuilder.h" +#include "rtpsources.h" +#include "rtppacketbuilder.h" +#include "rtcpscheduler.h" +#include "rtpsourcedata.h" +#include "rtcpcompoundpacketbuilder.h" +#include "rtpmemorymanager.h" + +namespace qrtplib +{ + +RTCPPacketBuilder::RTCPPacketBuilder(RTPSources &s,RTPPacketBuilder &pb,RTPMemoryManager *mgr) + : RTPMemoryObject(mgr),sources(s),rtppacketbuilder(pb),prevbuildtime(0,0),transmissiondelay(0,0),ownsdesinfo(mgr) +{ + init = false; + timeinit.Dummy(); +} + +RTCPPacketBuilder::~RTCPPacketBuilder() +{ + Destroy(); +} + +int RTCPPacketBuilder::Init(size_t maxpacksize,double tsunit,const void *cname,size_t cnamelen) +{ + if (init) + return ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT; + if (maxpacksize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE; + if (tsunit < 0.0) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT; + + if (cnamelen>255) + cnamelen = 255; + + maxpacketsize = maxpacksize; + timestampunit = tsunit; + + int status; + + if ((status = ownsdesinfo.SetCNAME((const uint8_t *)cname,cnamelen)) < 0) + return status; + + ClearAllSourceFlags(); + + interval_name = -1; + interval_email = -1; + interval_location = -1; + interval_phone = -1; + interval_tool = -1; + interval_note = -1; + + sdesbuildcount = 0; + transmissiondelay = RTPTime(0,0); + + firstpacket = true; + processingsdes = false; + init = true; + return 0; +} + +void RTCPPacketBuilder::Destroy() +{ + if (!init) + return; + ownsdesinfo.Clear(); + init = false; +} + +int RTCPPacketBuilder::BuildNextPacket(RTCPCompoundPacket **pack) +{ + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + + RTCPCompoundPacketBuilder *rtcpcomppack; + int status; + bool sender = false; + RTPSourceData *srcdat; + + *pack = 0; + + rtcpcomppack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPCOMPOUNDPACKETBUILDER) RTCPCompoundPacketBuilder(GetMemoryManager()); + if (rtcpcomppack == 0) + return ERR_RTP_OUTOFMEM; + + if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + { + if (srcdat->IsSender()) + sender = true; + } + + uint32_t ssrc = rtppacketbuilder.GetSSRC(); + RTPTime curtime = RTPTime::CurrentTime(); + + if (sender) + { + RTPTime rtppacktime = rtppacketbuilder.GetPacketTime(); + uint32_t rtppacktimestamp = rtppacketbuilder.GetPacketTimestamp(); + uint32_t packcount = rtppacketbuilder.GetPacketCount(); + uint32_t octetcount = rtppacketbuilder.GetPayloadOctetCount(); + RTPTime diff = curtime; + diff -= rtppacktime; + diff += transmissiondelay; // the sample being sampled at this very instant will need a larger timestamp + + uint32_t tsdiff = (uint32_t)((diff.GetDouble()/timestampunit)+0.5); + uint32_t rtptimestamp = rtppacktimestamp+tsdiff; + RTPNTPTime ntptimestamp = curtime.GetNTPTime(); + + if ((status = rtcpcomppack->StartSenderReport(ssrc,ntptimestamp,rtptimestamp,packcount,octetcount)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + else + { + if ((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + + uint8_t *owncname; + size_t owncnamelen; + + owncname = ownsdesinfo.GetCNAME(&owncnamelen); + + if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + + if (!processingsdes) + { + int added,skipped; + bool full,atendoflist; + + if ((status = FillInReportBlocks(rtcpcomppack,curtime,sources.GetTotalCount(),&full,&added,&skipped,&atendoflist)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + if (full && added == 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + } + + if (!full) + { + processingsdes = true; + sdesbuildcount++; + + ClearAllSourceFlags(); + + doname = false; + doemail = false; + doloc = false; + dophone = false; + dotool = false; + donote = false; + if (interval_name > 0 && ((sdesbuildcount%interval_name) == 0)) doname = true; + if (interval_email > 0 && ((sdesbuildcount%interval_email) == 0)) doemail = true; + if (interval_location > 0 && ((sdesbuildcount%interval_location) == 0)) doloc = true; + if (interval_phone > 0 && ((sdesbuildcount%interval_phone) == 0)) dophone = true; + if (interval_tool > 0 && ((sdesbuildcount%interval_tool) == 0)) dotool = true; + if (interval_note > 0 && ((sdesbuildcount%interval_note) == 0)) donote = true; + + bool processedall; + int itemcount; + + if ((status = FillInSDES(rtcpcomppack,&full,&processedall,&itemcount)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + if (processedall) + { + processingsdes = false; + ClearAllSDESFlags(); + if (!full && skipped > 0) + { + // if the packet isn't full and we skipped some + // sources that we already got in a previous packet, + // we can add some of them now + + bool atendoflist; + + if ((status = FillInReportBlocks(rtcpcomppack,curtime,skipped,&full,&added,&skipped,&atendoflist)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + } + } + } + } + else // previous sdes processing wasn't finished + { + bool processedall; + int itemcount; + bool full; + + if ((status = FillInSDES(rtcpcomppack,&full,&processedall,&itemcount)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + if (itemcount == 0) // Big problem: packet size is too small to let any progress happen + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + } + + if (processedall) + { + processingsdes = false; + ClearAllSDESFlags(); + if (!full) + { + // if the packet isn't full and we skipped some + // we can add some report blocks + + int added,skipped; + bool atendoflist; + + if ((status = FillInReportBlocks(rtcpcomppack,curtime,sources.GetTotalCount(),&full,&added,&skipped,&atendoflist)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + if (atendoflist) // filled in all possible sources + ClearAllSourceFlags(); + } + } + } + + if ((status = rtcpcomppack->EndBuild()) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + *pack = rtcpcomppack; + firstpacket = false; + prevbuildtime = curtime; + return 0; +} + +void RTCPPacketBuilder::ClearAllSourceFlags() +{ + if (sources.GotoFirstSource()) + { + do + { + RTPSourceData *srcdat = sources.GetCurrentSourceInfo(); + srcdat->SetProcessedInRTCP(false); + } while (sources.GotoNextSource()); + } +} + +int RTCPPacketBuilder::FillInReportBlocks(RTCPCompoundPacketBuilder *rtcpcomppack,const RTPTime &curtime,int maxcount,bool *full,int *added,int *skipped,bool *atendoflist) +{ + RTPSourceData *srcdat; + int addedcount = 0; + int skippedcount = 0; + bool done = false; + bool filled = false; + bool atend = false; + int status; + + if (sources.GotoFirstSource()) + { + do + { + bool shouldprocess = false; + + srcdat = sources.GetCurrentSourceInfo(); + if (!srcdat->IsOwnSSRC()) // don't send to ourselves + { + if (!srcdat->IsCSRC()) // p 35: no reports should go to CSRCs + { + if (srcdat->INF_HasSentData()) // if this isn't true, INF_GetLastRTPPacketTime() won't make any sense + { + if (firstpacket) + shouldprocess = true; + else + { + // p 35: only if rtp packets were received since the last RTP packet, a report block + // should be added + + RTPTime lastrtptime = srcdat->INF_GetLastRTPPacketTime(); + + if (lastrtptime > prevbuildtime) + shouldprocess = true; + } + } + } + } + + if (shouldprocess) + { + if (srcdat->IsProcessedInRTCP()) // already covered this one + { + skippedcount++; + } + else + { + uint32_t rr_ssrc = srcdat->GetSSRC(); + uint32_t num = srcdat->INF_GetNumPacketsReceivedInInterval(); + uint32_t prevseq = srcdat->INF_GetSavedExtendedSequenceNumber(); + uint32_t curseq = srcdat->INF_GetExtendedHighestSequenceNumber(); + uint32_t expected = curseq-prevseq; + uint8_t fraclost; + + if (expected < num) // got duplicates + fraclost = 0; + else + { + double lost = (double)(expected-num); + double frac = lost/((double)expected); + fraclost = (uint8_t)(frac*256.0); + } + + expected = curseq-srcdat->INF_GetBaseSequenceNumber(); + num = srcdat->INF_GetNumPacketsReceived(); + + uint32_t diff = expected-num; + int32_t *packlost = (int32_t *)&diff; + + uint32_t jitter = srcdat->INF_GetJitter(); + uint32_t lsr; + uint32_t dlsr; + + if (!srcdat->SR_HasInfo()) + { + lsr = 0; + dlsr = 0; + } + else + { + RTPNTPTime srtime = srcdat->SR_GetNTPTimestamp(); + uint32_t m = (srtime.GetMSW()&0xFFFF); + uint32_t l = ((srtime.GetLSW()>>16)&0xFFFF); + lsr = ((m<<16)|l); + + RTPTime diff = curtime; + diff -= srcdat->SR_GetReceiveTime(); + double diff2 = diff.GetDouble(); + diff2 *= 65536.0; + dlsr = (uint32_t)diff2; + } + + status = rtcpcomppack->AddReportBlock(rr_ssrc,fraclost,*packlost,curseq,jitter,lsr,dlsr); + if (status < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + done = true; + filled = true; + } + else + return status; + } + else + { + addedcount++; + if (addedcount >= maxcount) + { + done = true; + if (!sources.GotoNextSource()) + atend = true; + } + srcdat->INF_StartNewInterval(); + srcdat->SetProcessedInRTCP(true); + } + } + } + + if (!done) + { + if (!sources.GotoNextSource()) + { + atend = true; + done = true; + } + } + + } while (!done); + } + + *added = addedcount; + *skipped = skippedcount; + *full = filled; + + if (!atend) // search for available sources + { + bool shouldprocess = false; + + do + { + srcdat = sources.GetCurrentSourceInfo(); + if (!srcdat->IsOwnSSRC()) // don't send to ourselves + { + if (!srcdat->IsCSRC()) // p 35: no reports should go to CSRCs + { + if (srcdat->INF_HasSentData()) // if this isn't true, INF_GetLastRTPPacketTime() won't make any sense + { + if (firstpacket) + shouldprocess = true; + else + { + // p 35: only if rtp packets were received since the last RTP packet, a report block + // should be added + + RTPTime lastrtptime = srcdat->INF_GetLastRTPPacketTime(); + + if (lastrtptime > prevbuildtime) + shouldprocess = true; + } + } + } + } + + if (shouldprocess) + { + if (srcdat->IsProcessedInRTCP()) + shouldprocess = false; + } + + if (!shouldprocess) + { + if (!sources.GotoNextSource()) + atend = true; + } + + } while (!atend && !shouldprocess); + } + + *atendoflist = atend; + return 0; +} + +int RTCPPacketBuilder::FillInSDES(RTCPCompoundPacketBuilder *rtcpcomppack,bool *full,bool *processedall,int *added) +{ + int status; + uint8_t *data; + size_t datalen; + + *full = false; + *processedall = false; + *added = 0; + + // We don't need to add a SSRC for our own data, this is still set + // from adding the CNAME + if (doname) + { + if (!ownsdesinfo.ProcessedName()) + { + data = ownsdesinfo.GetName(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::NAME,data,datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedName(true); + } + } + if (doemail) + { + if (!ownsdesinfo.ProcessedEMail()) + { + data = ownsdesinfo.GetEMail(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::EMAIL,data,datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedEMail(true); + } + } + if (doloc) + { + if (!ownsdesinfo.ProcessedLocation()) + { + data = ownsdesinfo.GetLocation(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::LOC,data,datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedLocation(true); + } + } + if (dophone) + { + if (!ownsdesinfo.ProcessedPhone()) + { + data = ownsdesinfo.GetPhone(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::PHONE,data,datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedPhone(true); + } + } + if (dotool) + { + if (!ownsdesinfo.ProcessedTool()) + { + data = ownsdesinfo.GetTool(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::TOOL,data,datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedTool(true); + } + } + if (donote) + { + if (!ownsdesinfo.ProcessedNote()) + { + data = ownsdesinfo.GetNote(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::NOTE,data,datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedNote(true); + } + } + + *processedall = true; + return 0; +} + +void RTCPPacketBuilder::ClearAllSDESFlags() +{ + ownsdesinfo.ClearFlags(); +} + +int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack,const void *reason,size_t reasonlength,bool useSRifpossible) +{ + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + + RTCPCompoundPacketBuilder *rtcpcomppack; + int status; + + if (reasonlength > 255) + reasonlength = 255; + + *pack = 0; + + rtcpcomppack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPCOMPOUNDPACKETBUILDER) RTCPCompoundPacketBuilder(GetMemoryManager()); + if (rtcpcomppack == 0) + return ERR_RTP_OUTOFMEM; + + if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + uint32_t ssrc = rtppacketbuilder.GetSSRC(); + bool useSR = false; + + if (useSRifpossible) + { + RTPSourceData *srcdat; + + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + { + if (srcdat->IsSender()) + useSR = true; + } + } + + if (useSR) + { + RTPTime curtime = RTPTime::CurrentTime(); + RTPTime rtppacktime = rtppacketbuilder.GetPacketTime(); + uint32_t rtppacktimestamp = rtppacketbuilder.GetPacketTimestamp(); + uint32_t packcount = rtppacketbuilder.GetPacketCount(); + uint32_t octetcount = rtppacketbuilder.GetPayloadOctetCount(); + RTPTime diff = curtime; + diff -= rtppacktime; + + uint32_t tsdiff = (uint32_t)((diff.GetDouble()/timestampunit)+0.5); + uint32_t rtptimestamp = rtppacktimestamp+tsdiff; + RTPNTPTime ntptimestamp = curtime.GetNTPTime(); + + if ((status = rtcpcomppack->StartSenderReport(ssrc,ntptimestamp,rtptimestamp,packcount,octetcount)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + else + { + if ((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + + uint8_t *owncname; + size_t owncnamelen; + + owncname = ownsdesinfo.GetCNAME(&owncnamelen); + + if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + + uint32_t ssrcs[1]; + + ssrcs[0] = ssrc; + + if ((status = rtcpcomppack->AddBYEPacket(ssrcs,1,(const uint8_t *)reason,reasonlength)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + + if ((status = rtcpcomppack->EndBuild()) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + *pack = rtcpcomppack; + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtcppacketbuilder.h b/qrtplib/rtcppacketbuilder.h new file mode 100644 index 000000000..0d0c83704 --- /dev/null +++ b/qrtplib/rtcppacketbuilder.h @@ -0,0 +1,229 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcppacketbuilder.h + */ + +#ifndef RTCPPACKETBUILDER_H + +#define RTCPPACKETBUILDER_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtperrors.h" +#include "rtcpsdesinfo.h" +#include "rtptimeutilities.h" +#include "rtpmemoryobject.h" + +namespace qrtplib +{ + +class RTPSources; +class RTPPacketBuilder; +class RTCPScheduler; +class RTCPCompoundPacket; +class RTCPCompoundPacketBuilder; + +/** This class can be used to build RTCP compound packets, on a higher level than the RTCPCompoundPacketBuilder. + * The class RTCPPacketBuilder can be used to build RTCP compound packets. This class is more high-level + * than the RTCPCompoundPacketBuilder class: it uses the information of an RTPPacketBuilder instance and of + * an RTPSources instance to automatically generate the next compound packet which should be sent. It also + * provides functions to determine when SDES items other than the CNAME item should be sent. + */ +class JRTPLIB_IMPORTEXPORT RTCPPacketBuilder : public RTPMemoryObject +{ +public: + /** Creates an RTCPPacketBuilder instance. + * Creates an instance which will use the source table \c sources and the RTP packet builder + * \c rtppackbuilder to determine the information for the next RTCP compound packet. Optionally, + * the memory manager \c mgr can be installed. + */ + RTCPPacketBuilder(RTPSources &sources,RTPPacketBuilder &rtppackbuilder, RTPMemoryManager *mgr = 0); + ~RTCPPacketBuilder(); + + /** Initializes the builder. + * Initializes the builder to use the maximum allowed packet size \c maxpacksize, timestamp unit + * \c timestampunit and the SDES CNAME item specified by \c cname with length \c cnamelen. + * The timestamp unit is defined as a time interval divided by the timestamp interval corresponding to + * that interval: for 8000 Hz audio this would be 1/8000. + */ + int Init(size_t maxpacksize,double timestampunit,const void *cname,size_t cnamelen); + + /** Cleans up the builder. */ + void Destroy(); + + /** Sets the timestamp unit to be used to \c tsunit. + * Sets the timestamp unit to be used to \c tsunit. The timestamp unit is defined as a time interval + * divided by the timestamp interval corresponding to that interval: for 8000 Hz audio this would + * be 1/8000. + */ + int SetTimestampUnit(double tsunit) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; if (tsunit < 0) return ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT; timestampunit = tsunit; return 0; } + + /** Sets the maximum size allowed size of an RTCP compound packet to \c maxpacksize. */ + int SetMaximumPacketSize(size_t maxpacksize) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; if (maxpacksize < RTP_MINPACKETSIZE) return ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE; maxpacketsize = maxpacksize; return 0; } + + /** This function allows you to inform RTCP packet builder about the delay between sampling the first + * sample of a packet and sending the packet. + * This function allows you to inform RTCP packet builder about the delay between sampling the first + * sample of a packet and sending the packet. This delay is taken into account when calculating the + * relation between RTP timestamp and wallclock time, used for inter-media synchronization. + */ + int SetPreTransmissionDelay(const RTPTime &delay) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; transmissiondelay = delay; return 0; } + + /** Builds the next RTCP compound packet which should be sent and stores it in \c pack. */ + int BuildNextPacket(RTCPCompoundPacket **pack); + + /** Builds a BYE packet with reason for leaving specified by \c reason and length \c reasonlength. + * Builds a BYE packet with reason for leaving specified by \c reason and length \c reasonlength. If + * \c useSRifpossible is set to \c true, the RTCP compound packet will start with a sender report if + * allowed. Otherwise, a receiver report is used. + */ + int BuildBYEPacket(RTCPCompoundPacket **pack,const void *reason,size_t reasonlength,bool useSRifpossible = true); + + /** Sets the RTCP interval for the SDES name item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES name item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNameInterval(int count) { if (!init) return; interval_name = count; } + + /** Sets the RTCP interval for the SDES e-mail item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES e-mail item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetEMailInterval(int count) { if (!init) return; interval_email = count; } + + /** Sets the RTCP interval for the SDES location item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES location item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetLocationInterval(int count) { if (!init) return; interval_location = count; } + + /** Sets the RTCP interval for the SDES phone item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES phone item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetPhoneInterval(int count) { if (!init) return; interval_phone = count; } + + /** Sets the RTCP interval for the SDES tool item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES tool item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetToolInterval(int count) { if (!init) return; interval_tool = count; } + + /** Sets the RTCP interval for the SDES note item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES note item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNoteInterval(int count) { if (!init) return; interval_note = count; } + + /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ + int SetLocalName(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetName((const uint8_t *)s,len); } + + /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ + int SetLocalEMail(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetEMail((const uint8_t *)s,len); } + + /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ + int SetLocalLocation(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetLocation((const uint8_t *)s,len); } + + /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ + int SetLocalPhone(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetPhone((const uint8_t *)s,len); } + + /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ + int SetLocalTool(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetTool((const uint8_t *)s,len); } + + /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ + int SetLocalNote(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetNote((const uint8_t *)s,len); } + + /** Returns the own CNAME item with length \c len */ + uint8_t *GetLocalCNAME(size_t *len) const { if (!init) return 0; return ownsdesinfo.GetCNAME(len); } +private: + void ClearAllSourceFlags(); + int FillInReportBlocks(RTCPCompoundPacketBuilder *pack,const RTPTime &curtime,int maxcount,bool *full,int *added,int *skipped,bool *atendoflist); + int FillInSDES(RTCPCompoundPacketBuilder *pack,bool *full,bool *processedall,int *added); + void ClearAllSDESFlags(); + + RTPSources &sources; + RTPPacketBuilder &rtppacketbuilder; + + bool init; + size_t maxpacketsize; + double timestampunit; + bool firstpacket; + RTPTime prevbuildtime,transmissiondelay; + + class RTCPSDESInfoInternal : public RTCPSDESInfo + { + public: + RTCPSDESInfoInternal(RTPMemoryManager *mgr) : RTCPSDESInfo(mgr) { ClearFlags(); } + void ClearFlags() { pname = false; pemail = false; plocation = false; pphone = false; ptool = false; pnote = false; } + bool ProcessedName() const { return pname; } + bool ProcessedEMail() const { return pemail; } + bool ProcessedLocation() const { return plocation; } + bool ProcessedPhone() const { return pphone; } + bool ProcessedTool() const { return ptool; } + bool ProcessedNote() const { return pnote; } + void SetProcessedName(bool v) { pname = v; } + void SetProcessedEMail(bool v) { pemail = v; } + void SetProcessedLocation(bool v) { plocation = v; } + void SetProcessedPhone(bool v) { pphone = v; } + void SetProcessedTool(bool v) { ptool = v; } + void SetProcessedNote(bool v) { pnote = v; } + private: + bool pname,pemail,plocation,pphone,ptool,pnote; + }; + + RTCPSDESInfoInternal ownsdesinfo; + int interval_name,interval_email,interval_location; + int interval_phone,interval_tool,interval_note; + bool doname,doemail,doloc,dophone,dotool,donote; + bool processingsdes; + + int sdesbuildcount; +}; + +} // end namespace + +#endif // RTCPPACKETBUILDER_H + diff --git a/qrtplib/rtcprrpacket.cpp b/qrtplib/rtcprrpacket.cpp new file mode 100644 index 000000000..d6055bf80 --- /dev/null +++ b/qrtplib/rtcprrpacket.cpp @@ -0,0 +1,68 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcprrpacket.h" + +namespace qrtplib +{ + +RTCPRRPacket::RTCPRRPacket(uint8_t *data,size_t datalength) + : RTCPPacket(RR,data,datalength) +{ + knownformat = false; + + RTCPCommonHeader *hdr; + size_t len = datalength; + size_t expectedlength; + + hdr = (RTCPCommonHeader *)data; + if (hdr->padding) + { + uint8_t padcount = data[datalength-1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t)padcount) >= len) + return; + len -= (size_t)padcount; + } + + expectedlength = sizeof(RTCPCommonHeader)+sizeof(uint32_t); + expectedlength += sizeof(RTCPReceiverReport)*((int)hdr->count); + + if (expectedlength != len) + return; + + knownformat = true; +} + +} // end namespace + diff --git a/qrtplib/rtcprrpacket.h b/qrtplib/rtcprrpacket.h new file mode 100644 index 000000000..f779fe67f --- /dev/null +++ b/qrtplib/rtcprrpacket.h @@ -0,0 +1,203 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcprrpacket.h + */ + +#ifndef RTCPRRPACKET_H + +#define RTCPRRPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtpstructs.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP receiver report packet. */ +class JRTPLIB_IMPORTEXPORT RTCPRRPacket : public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it points + * to is valid as long as the class instance exists. + */ + RTCPRRPacket(uint8_t *data,size_t datalen); + ~RTCPRRPacket() { } + + /** Returns the SSRC of the participant who sent this packet. */ + uint32_t GetSenderSSRC() const; + + /** Returns the number of reception report blocks present in this packet. */ + int GetReceptionReportCount() const; + + /** Returns the SSRC of the reception report block described by \c index which may have a value + * from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetSSRC(int index) const; + + /** Returns the `fraction lost' field of the reception report described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint8_t GetFractionLost(int index) const; + + /** Returns the number of lost packets in the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + int32_t GetLostPacketCount(int index) const; + + /** Returns the extended highest sequence number of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetExtendedHighestSequenceNumber(int index) const; + + /** Returns the jitter field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetJitter(int index) const; + + /** Returns the LSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetLSR(int index) const; + + /** Returns the DLSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetDLSR(int index) const; + + +private: + RTCPReceiverReport *GotoReport(int index) const; +}; + +inline uint32_t RTCPRRPacket::GetSenderSSRC() const +{ + if (!knownformat) + return 0; + + uint32_t *ssrcptr = (uint32_t *)(data+sizeof(RTCPCommonHeader)); + return ntohl(*ssrcptr); +} +inline int RTCPRRPacket::GetReceptionReportCount() const +{ + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; + return ((int)hdr->count); +} + +inline RTCPReceiverReport *RTCPRRPacket::GotoReport(int index) const +{ + RTCPReceiverReport *r = (RTCPReceiverReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)+index*sizeof(RTCPReceiverReport)); + return r; +} + +inline uint32_t RTCPRRPacket::GetSSRC(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->ssrc); +} + +inline uint8_t RTCPRRPacket::GetFractionLost(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return r->fractionlost; +} + +inline int32_t RTCPRRPacket::GetLostPacketCount(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + uint32_t count = ((uint32_t)r->packetslost[2])|(((uint32_t)r->packetslost[1])<<8)|(((uint32_t)r->packetslost[0])<<16); + if ((count&0x00800000) != 0) // test for negative number + count |= 0xFF000000; + int32_t *count2 = (int32_t *)(&count); + return (*count2); +} + +inline uint32_t RTCPRRPacket::GetExtendedHighestSequenceNumber(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->exthighseqnr); +} + +inline uint32_t RTCPRRPacket::GetJitter(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->jitter); +} + +inline uint32_t RTCPRRPacket::GetLSR(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->lsr); +} + +inline uint32_t RTCPRRPacket::GetDLSR(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->dlsr); +} + +} // end namespace + +#endif // RTCPRRPACKET_H + diff --git a/qrtplib/rtcpscheduler.cpp b/qrtplib/rtcpscheduler.cpp new file mode 100644 index 000000000..b48180f5c --- /dev/null +++ b/qrtplib/rtcpscheduler.cpp @@ -0,0 +1,419 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcpscheduler.h" +#include "rtpsources.h" +#include "rtpdefines.h" +#include "rtcppacket.h" +#include "rtppacket.h" +#include "rtcpcompoundpacket.h" +#include "rtpsourcedata.h" + +#define RTCPSCHED_MININTERVAL 1.0 + +namespace qrtplib +{ + +RTCPSchedulerParams::RTCPSchedulerParams() : mininterval(RTCP_DEFAULTMININTERVAL) +{ + bandwidth = 1000; // TODO What is a good value here? + senderfraction = RTCP_DEFAULTSENDERFRACTION; + usehalfatstartup = RTCP_DEFAULTHALFATSTARTUP; + immediatebye = RTCP_DEFAULTIMMEDIATEBYE; + timeinit.Dummy(); +} + +RTCPSchedulerParams::~RTCPSchedulerParams() +{ +} + +int RTCPSchedulerParams::SetRTCPBandwidth(double bw) +{ + if (bw < 0.0) + return ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH; + bandwidth = bw; + return 0; +} + +int RTCPSchedulerParams::SetSenderBandwidthFraction(double fraction) +{ + if (fraction < 0.0 || fraction > 1.0) + return ERR_RTP_SCHEDPARAMS_BADFRACTION; + senderfraction = fraction; + return 0; +} + +int RTCPSchedulerParams::SetMinimumTransmissionInterval(const RTPTime &t) +{ + double t2 = t.GetDouble(); + + if (t2 < RTCPSCHED_MININTERVAL) + return ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL; + + mininterval = t; + return 0; +} + +RTCPScheduler::RTCPScheduler(RTPSources &s, RTPRandom &r) : sources(s),nextrtcptime(0,0),prevrtcptime(0,0),rtprand(r) +{ + Reset(); + + //std::cout << (void *)(&rtprand) << std::endl; +} + +RTCPScheduler::~RTCPScheduler() +{ +} + +void RTCPScheduler::Reset() +{ + headeroverhead = 0; // user has to set this to an appropriate value + hassentrtcp = false; + firstcall = true; + avgrtcppacksize = 1000; // TODO: what is a good value for this? + byescheduled = false; + sendbyenow = false; +} + +void RTCPScheduler::AnalyseIncoming(RTCPCompoundPacket &rtcpcomppack) +{ + bool isbye = false; + RTCPPacket *p; + + rtcpcomppack.GotoFirstPacket(); + while (!isbye && ((p = rtcpcomppack.GetNextPacket()) != 0)) + { + if (p->GetPacketType() == RTCPPacket::BYE) + isbye = true; + } + + if (!isbye) + { + size_t packsize = headeroverhead+rtcpcomppack.GetCompoundPacketLength(); + avgrtcppacksize = (size_t)((1.0/16.0)*((double)packsize)+(15.0/16.0)*((double)avgrtcppacksize)); + } + else + { + if (byescheduled) + { + size_t packsize = headeroverhead+rtcpcomppack.GetCompoundPacketLength(); + avgbyepacketsize = (size_t)((1.0/16.0)*((double)packsize)+(15.0/16.0)*((double)avgbyepacketsize)); + byemembers++; + } + } +} + +void RTCPScheduler::AnalyseOutgoing(RTCPCompoundPacket &rtcpcomppack) +{ + bool isbye = false; + RTCPPacket *p; + + rtcpcomppack.GotoFirstPacket(); + while (!isbye && ((p = rtcpcomppack.GetNextPacket()) != 0)) + { + if (p->GetPacketType() == RTCPPacket::BYE) + isbye = true; + } + + if (!isbye) + { + size_t packsize = headeroverhead+rtcpcomppack.GetCompoundPacketLength(); + avgrtcppacksize = (size_t)((1.0/16.0)*((double)packsize)+(15.0/16.0)*((double)avgrtcppacksize)); + } + + hassentrtcp = true; +} + +RTPTime RTCPScheduler::GetTransmissionDelay() +{ + if (firstcall) + { + firstcall = false; + prevrtcptime = RTPTime::CurrentTime(); + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + } + + RTPTime curtime = RTPTime::CurrentTime(); + + if (curtime > nextrtcptime) // packet should be sent + return RTPTime(0,0); + + RTPTime diff = nextrtcptime; + diff -= curtime; + + return diff; +} + +bool RTCPScheduler::IsTime() +{ + if (firstcall) + { + firstcall = false; + prevrtcptime = RTPTime::CurrentTime(); + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + return false; + } + + RTPTime currenttime = RTPTime::CurrentTime(); + +// // TODO: for debugging +// double diff = nextrtcptime.GetDouble() - currenttime.GetDouble(); +// +// std::cout << "Delay till next RTCP interval: " << diff << std::endl; + + if (currenttime < nextrtcptime) // timer has not yet expired + return false; + + RTPTime checktime(0,0); + + if (!byescheduled) + { + bool aresender = false; + RTPSourceData *srcdat; + + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + aresender = srcdat->IsSender(); + + checktime = CalculateTransmissionInterval(aresender); + } + else + checktime = CalculateBYETransmissionInterval(); + +// std::cout << "Calculated checktime: " << checktime.GetDouble() << std::endl; + + checktime += prevrtcptime; + + if (checktime <= currenttime) // Okay + { + byescheduled = false; + prevrtcptime = currenttime; + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + return true; + } + +// std::cout << "New delay: " << nextrtcptime.GetDouble() - currenttime.GetDouble() << std::endl; + + nextrtcptime = checktime; + pmembers = sources.GetActiveMemberCount(); + + return false; +} + +void RTCPScheduler::CalculateNextRTCPTime() +{ + bool aresender = false; + RTPSourceData *srcdat; + + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + aresender = srcdat->IsSender(); + + nextrtcptime = RTPTime::CurrentTime(); + nextrtcptime += CalculateTransmissionInterval(aresender); +} + +RTPTime RTCPScheduler::CalculateDeterministicInterval(bool sender /* = false */) +{ + int numsenders = sources.GetSenderCount(); + int numtotal = sources.GetActiveMemberCount(); + +// std::cout << "CalculateDeterministicInterval" << std::endl; +// std::cout << " numsenders: " << numsenders << std::endl; +// std::cout << " numtotal: " << numtotal << std::endl; + + // Try to avoid division by zero: + if (numtotal == 0) + numtotal++; + + double sfraction = ((double)numsenders)/((double)numtotal); + double C,n; + + if (sfraction <= schedparams.GetSenderBandwidthFraction()) + { + if (sender) + { + C = ((double)avgrtcppacksize)/(schedparams.GetSenderBandwidthFraction()*schedparams.GetRTCPBandwidth()); + n = (double)numsenders; + } + else + { + C = ((double)avgrtcppacksize)/((1.0-schedparams.GetSenderBandwidthFraction())*schedparams.GetRTCPBandwidth()); + n = (double)(numtotal-numsenders); + } + } + else + { + C = ((double)avgrtcppacksize)/schedparams.GetRTCPBandwidth(); + n = (double)numtotal; + } + + RTPTime Tmin = schedparams.GetMinimumTransmissionInterval(); + double tmin = Tmin.GetDouble(); + + if (!hassentrtcp && schedparams.GetUseHalfAtStartup()) + tmin /= 2.0; + + double ntimesC = n*C; + double Td = (tmin>ntimesC)?tmin:ntimesC; + + // TODO: for debugging +// std::cout << " Td: " << Td << std::endl; + + return RTPTime(Td); +} + +RTPTime RTCPScheduler::CalculateTransmissionInterval(bool sender) +{ + RTPTime Td = CalculateDeterministicInterval(sender); + double td,mul,T; + +// std::cout << "CalculateTransmissionInterval" << std::endl; + + td = Td.GetDouble(); + mul = rtprand.GetRandomDouble()+0.5; // gives random value between 0.5 and 1.5 + T = (td*mul)/1.21828; // see RFC 3550 p 30 + +// std::cout << " Td: " << td << std::endl; +// std::cout << " mul: " << mul << std::endl; +// std::cout << " T: " << T << std::endl; + + return RTPTime(T); +} + +void RTCPScheduler::PerformReverseReconsideration() +{ + if (firstcall) + return; + + double diff1,diff2; + int members = sources.GetActiveMemberCount(); + + RTPTime tc = RTPTime::CurrentTime(); + RTPTime tn_min_tc = nextrtcptime; + + if (tn_min_tc > tc) + tn_min_tc -= tc; + else + tn_min_tc = RTPTime(0,0); + +// std::cout << "+tn_min_tc0 " << nextrtcptime.GetDouble()-tc.GetDouble() << std::endl; +// std::cout << "-tn_min_tc0 " << -nextrtcptime.GetDouble()+tc.GetDouble() << std::endl; +// std::cout << "tn_min_tc " << tn_min_tc.GetDouble() << std::endl; + + RTPTime tc_min_tp = tc; + + if (tc_min_tp > prevrtcptime) + tc_min_tp -= prevrtcptime; + else + tc_min_tp = 0; + + if (pmembers == 0) // avoid division by zero + pmembers++; + + diff1 = (((double)members)/((double)pmembers))*tn_min_tc.GetDouble(); + diff2 = (((double)members)/((double)pmembers))*tc_min_tp.GetDouble(); + + nextrtcptime = tc; + prevrtcptime = tc; + nextrtcptime += RTPTime(diff1); + prevrtcptime -= RTPTime(diff2); + + pmembers = members; +} + +void RTCPScheduler::ScheduleBYEPacket(size_t packetsize) +{ + if (byescheduled) + return; + + if (firstcall) + { + firstcall = false; + pmembers = sources.GetActiveMemberCount(); + } + + byescheduled = true; + avgbyepacketsize = packetsize+headeroverhead; + + // For now, we will always use the BYE backoff algorithm as described in rfc 3550 p 33 + + byemembers = 1; + pbyemembers = 1; + + if (schedparams.GetRequestImmediateBYE() && sources.GetActiveMemberCount() < 50) // p 34 (top) + sendbyenow = true; + else + sendbyenow = false; + + prevrtcptime = RTPTime::CurrentTime(); + nextrtcptime = prevrtcptime; + nextrtcptime += CalculateBYETransmissionInterval(); +} + +void RTCPScheduler::ActiveMemberDecrease() +{ + if (sources.GetActiveMemberCount() < pmembers) + PerformReverseReconsideration(); +} + +RTPTime RTCPScheduler::CalculateBYETransmissionInterval() +{ + if (!byescheduled) + return RTPTime(0,0); + + if (sendbyenow) + return RTPTime(0,0); + + double C,n; + + C = ((double)avgbyepacketsize)/((1.0-schedparams.GetSenderBandwidthFraction())*schedparams.GetRTCPBandwidth()); + n = (double)byemembers; + + RTPTime Tmin = schedparams.GetMinimumTransmissionInterval(); + double tmin = Tmin.GetDouble(); + + if (schedparams.GetUseHalfAtStartup()) + tmin /= 2.0; + + double ntimesC = n*C; + double Td = (tmin>ntimesC)?tmin:ntimesC; + + double mul = rtprand.GetRandomDouble()+0.5; // gives random value between 0.5 and 1.5 + double T = (Td*mul)/1.21828; // see RFC 3550 p 30 + + return RTPTime(T); +} + +} // end namespace + diff --git a/qrtplib/rtcpscheduler.h b/qrtplib/rtcpscheduler.h new file mode 100644 index 000000000..14513c894 --- /dev/null +++ b/qrtplib/rtcpscheduler.h @@ -0,0 +1,189 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpscheduler.h + */ + +#ifndef RTCPSCHEDULER_H + +#define RTCPSCHEDULER_H + +#include "rtpconfig.h" +#include "rtptimeutilities.h" +#include "rtprandom.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; +class RTPPacket; +class RTPSources; + +/** Describes parameters used by the RTCPScheduler class. */ +class JRTPLIB_IMPORTEXPORT RTCPSchedulerParams +{ +public: + RTCPSchedulerParams(); + ~RTCPSchedulerParams(); + + /** Sets the RTCP bandwidth to be used to \c bw (in bytes per second). */ + int SetRTCPBandwidth(double bw); + + /** Returns the used RTCP bandwidth in bytes per second (default is 1000). */ + double GetRTCPBandwidth() const { return bandwidth; } + + /** Sets the fraction of the RTCP bandwidth reserved for senders to \c fraction. */ + int SetSenderBandwidthFraction(double fraction); + + /** Returns the fraction of the RTCP bandwidth reserved for senders (default is 25%). */ + double GetSenderBandwidthFraction() const { return senderfraction; } + + /** Sets the minimum (deterministic) interval between RTCP compound packets to \c t. */ + int SetMinimumTransmissionInterval(const RTPTime &t); + + /** Returns the minimum RTCP transmission interval (default is 5 seconds). */ + RTPTime GetMinimumTransmissionInterval() const { return mininterval; } + + /** If \c usehalf is \c true, only use half the minimum interval before sending the first RTCP compound packet. */ + void SetUseHalfAtStartup(bool usehalf) { usehalfatstartup = usehalf; } + + /** Returns \c true if only half the minimum interval should be used before sending the first RTCP compound packet + * (defualt is \c true). + */ + bool GetUseHalfAtStartup() const { return usehalfatstartup; } + + /** If \c v is \c true, the scheduler will schedule a BYE packet to be sent immediately if allowed. */ + void SetRequestImmediateBYE(bool v) { immediatebye = v; } + + /** Returns if the scheduler will schedule a BYE packet to be sent immediately if allowed + * (default is \c true). + */ + bool GetRequestImmediateBYE() const { return immediatebye; } +private: + double bandwidth; + double senderfraction; + RTPTime mininterval; + bool usehalfatstartup; + bool immediatebye; +}; + +/** This class determines when RTCP compound packets should be sent. */ +class JRTPLIB_IMPORTEXPORT RTCPScheduler +{ +public: + /** Creates an instance which will use the source table RTPSources to determine when RTCP compound + * packets should be scheduled. + * Creates an instance which will use the source table RTPSources to determine when RTCP compound + * packets should be scheduled. Note that for correct operation the \c sources instance should have information + * about the own SSRC (added by RTPSources::CreateOwnSSRC). You must also supply a random number + * generator \c rtprand which will be used for adding randomness to the RTCP intervals. + */ + RTCPScheduler(RTPSources &sources, RTPRandom &rtprand); + ~RTCPScheduler(); + + /** Resets the scheduler. */ + void Reset(); + + /** Sets the scheduler parameters to be used to \c params. */ + void SetParameters(const RTCPSchedulerParams ¶ms) { schedparams = params; } + + /** Returns the currently used scheduler parameters. */ + RTCPSchedulerParams GetParameters() const { return schedparams; } + + /** Sets the header overhead from underlying protocols (for example UDP and IP) to \c numbytes. */ + void SetHeaderOverhead(size_t numbytes) { headeroverhead = numbytes; } + + /** Returns the currently used header overhead. */ + size_t GetHeaderOverhead() const { return headeroverhead; } + + /** For each incoming RTCP compound packet, this function has to be called for the scheduler to work correctly. */ + void AnalyseIncoming(RTCPCompoundPacket &rtcpcomppack); + + /** For each outgoing RTCP compound packet, this function has to be called for the scheduler to work correctly. */ + void AnalyseOutgoing(RTCPCompoundPacket &rtcpcomppack); + + /** This function has to be called each time a member times out or sends a BYE packet. */ + void ActiveMemberDecrease(); + + /** Asks the scheduler to schedule an RTCP compound packet containing a BYE packetl; the compound packet + * has size \c packetsize. + */ + void ScheduleBYEPacket(size_t packetsize); + + /** Returns the delay after which an RTCP compound will possibly have to be sent. + * Returns the delay after which an RTCP compound will possibly have to be sent. The IsTime member function + * should be called afterwards to make sure that it actually is time to send an RTCP compound packet. + */ + RTPTime GetTransmissionDelay(); + + /** This function returns \c true if it's time to send an RTCP compound packet and \c false otherwise. + * This function returns \c true if it's time to send an RTCP compound packet and \c false otherwise. + * If the function returns \c true, it will also have calculated the next time at which a packet should + * be sent, so if it is called again right away, it will return \c false. + */ + bool IsTime(); + + /** Calculates the deterministic interval at this time. + * Calculates the deterministic interval at this time. This is used - in combination with a certain multiplier - + * to time out members, senders etc. + */ + RTPTime CalculateDeterministicInterval(bool sender = false); +private: + void CalculateNextRTCPTime(); + void PerformReverseReconsideration(); + RTPTime CalculateBYETransmissionInterval(); + RTPTime CalculateTransmissionInterval(bool sender); + + RTPSources &sources; + RTCPSchedulerParams schedparams; + size_t headeroverhead; + size_t avgrtcppacksize; + bool hassentrtcp; + bool firstcall; + RTPTime nextrtcptime; + RTPTime prevrtcptime; + int pmembers; + + // for BYE packet scheduling + bool byescheduled; + int byemembers,pbyemembers; + size_t avgbyepacketsize; + bool sendbyenow; + + RTPRandom &rtprand; +}; + +} // end namespace + +#endif // RTCPSCHEDULER_H + diff --git a/qrtplib/rtcpsdesinfo.cpp b/qrtplib/rtcpsdesinfo.cpp new file mode 100644 index 000000000..612ef6bca --- /dev/null +++ b/qrtplib/rtcpsdesinfo.cpp @@ -0,0 +1,180 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcpsdesinfo.h" + +namespace qrtplib +{ + +void RTCPSDESInfo::Clear() +{ +#ifdef RTP_SUPPORT_SDESPRIV + std::list::const_iterator it; + + for (it = privitems.begin() ; it != privitems.end() ; ++it) + RTPDelete(*it,GetMemoryManager()); + privitems.clear(); +#endif // RTP_SUPPORT_SDESPRIV +} + +#ifdef RTP_SUPPORT_SDESPRIV +int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix,size_t prefixlen,const uint8_t *value,size_t valuelen) +{ + std::list::const_iterator it; + bool found; + + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + size_t l; + + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix,p,l) == 0) + found = true; + else + ++it; + } + else + ++it; + } + + SDESPrivateItem *item; + + if (found) // replace the value for this entry + item = *it; + else // no entry for this prefix found... add it + { + if (privitems.size() >= RTP_MAXPRIVITEMS) // too many items present, just ignore it + return ERR_RTP_SDES_MAXPRIVITEMS; + + int status; + + item = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_SDESPRIVATEITEM) SDESPrivateItem(GetMemoryManager()); + if (item == 0) + return ERR_RTP_OUTOFMEM; + if ((status = item->SetPrefix(prefix,prefixlen)) < 0) + { + RTPDelete(item,GetMemoryManager()); + return status; + } + privitems.push_front(item); + } + return item->SetInfo(value,valuelen); +} + +int RTCPSDESInfo::DeletePrivatePrefix(const uint8_t *prefix,size_t prefixlen) +{ + std::list::iterator it; + bool found; + + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + size_t l; + + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix,p,l) == 0) + found = true; + else + ++it; + } + else + ++it; + } + if (!found) + return ERR_RTP_SDES_PREFIXNOTFOUND; + + RTPDelete(*it,GetMemoryManager()); + privitems.erase(it); + return 0; +} + +void RTCPSDESInfo::GotoFirstPrivateValue() +{ + curitem = privitems.begin(); +} + +bool RTCPSDESInfo::GetNextPrivateValue(uint8_t **prefix,size_t *prefixlen,uint8_t **value,size_t *valuelen) +{ + if (curitem == privitems.end()) + return false; + *prefix = (*curitem)->GetPrefix(prefixlen); + *value = (*curitem)->GetInfo(valuelen); + ++curitem; + return true; +} + +bool RTCPSDESInfo::GetPrivateValue(const uint8_t *prefix,size_t prefixlen,uint8_t **value,size_t *valuelen) const +{ + std::list::const_iterator it; + bool found; + + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + size_t l; + + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix,p,l) == 0) + found = true; + else + ++it; + } + else + ++it; + } + if (found) + *value = (*it)->GetInfo(valuelen); + return found; +} +#endif // RTP_SUPPORT_SDESPRIV + +} // end namespace + diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h new file mode 100644 index 000000000..30dff238b --- /dev/null +++ b/qrtplib/rtcpsdesinfo.h @@ -0,0 +1,220 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpsdesinfo.h + */ + +#ifndef RTCPSDESINFO_H + +#define RTCPSDESINFO_H + +#include "rtpconfig.h" +#include "rtperrors.h" +#include "rtpdefines.h" +#include "rtptypes.h" +#include "rtpmemoryobject.h" +#include +#include + +namespace qrtplib +{ + +/** The class RTCPSDESInfo is a container for RTCP SDES information. */ +class JRTPLIB_IMPORTEXPORT RTCPSDESInfo : public RTPMemoryObject +{ +public: + /** Constructs an instance, optionally installing a memory manager. */ + RTCPSDESInfo(RTPMemoryManager *mgr = 0) : RTPMemoryObject(mgr) { for (int i = 0 ; i < RTCP_SDES_NUMITEMS_NONPRIVATE ; i++) nonprivateitems[i].SetMemoryManager(mgr); } + virtual ~RTCPSDESInfo() { Clear(); } + + /** Clears all SDES information. */ + void Clear(); + + /** Sets the SDES CNAME item to \c s with length \c l. */ + int SetCNAME(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_CNAME-1,s,l); } + + /** Sets the SDES name item to \c s with length \c l. */ + int SetName(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_NAME-1,s,l); } + + /** Sets the SDES e-mail item to \c s with length \c l. */ + int SetEMail(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_EMAIL-1,s,l); } + + /** Sets the SDES phone item to \c s with length \c l. */ + int SetPhone(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_PHONE-1,s,l); } + + /** Sets the SDES location item to \c s with length \c l. */ + int SetLocation(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_LOCATION-1,s,l); } + + /** Sets the SDES tool item to \c s with length \c l. */ + int SetTool(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_TOOL-1,s,l); } + + /** Sets the SDES note item to \c s with length \c l. */ + int SetNote(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_NOTE-1,s,l); } + +#ifdef RTP_SUPPORT_SDESPRIV + /** Sets the entry for the prefix string specified by \c prefix with length \c prefixlen to contain + * the value string specified by \c value with length \c valuelen (if the maximum allowed + * number of prefixes was reached, the error code \c ERR_RTP_SDES_MAXPRIVITEMS is returned. + */ + int SetPrivateValue(const uint8_t *prefix,size_t prefixlen,const uint8_t *value,size_t valuelen); + + /** Deletes the entry for the prefix specified by \c s with length \c len. */ + int DeletePrivatePrefix(const uint8_t *s,size_t len); +#endif // RTP_SUPPORT_SDESPRIV + + /** Returns the SDES CNAME item and stores its length in \c len. */ + uint8_t *GetCNAME(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_CNAME-1,len); } + + /** Returns the SDES name item and stores its length in \c len. */ + uint8_t *GetName(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_NAME-1,len); } + + /** Returns the SDES e-mail item and stores its length in \c len. */ + uint8_t *GetEMail(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_EMAIL-1,len); } + + /** Returns the SDES phone item and stores its length in \c len. */ + uint8_t *GetPhone(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_PHONE-1,len); } + + /** Returns the SDES location item and stores its length in \c len. */ + uint8_t *GetLocation(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_LOCATION-1,len); } + + /** Returns the SDES tool item and stores its length in \c len. */ + uint8_t *GetTool(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_TOOL-1,len); } + + /** Returns the SDES note item and stores its length in \c len. */ + uint8_t *GetNote(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_NOTE-1,len); } +#ifdef RTP_SUPPORT_SDESPRIV + /** Starts the iteration over the stored SDES private item prefixes and their associated values. */ + void GotoFirstPrivateValue(); + + /** Returns SDES priv item information. + * If available, returns \c true and stores the next SDES + * private item prefix in \c prefix and its length in + * \c prefixlen. The associated value and its length are + * then stored in \c value and \c valuelen. Otherwise, + * it returns \c false. + */ + bool GetNextPrivateValue(uint8_t **prefix,size_t *prefixlen,uint8_t **value,size_t *valuelen); + + /** Returns SDES priv item information. + * Looks for the entry which corresponds to the SDES private + * item prefix \c prefix with length \c prefixlen. If found, + * the function returns \c true and stores the associated + * value and its length in \c value and \c valuelen + * respectively. + */ + bool GetPrivateValue(const uint8_t *prefix,size_t prefixlen,uint8_t **value,size_t *valuelen) const; +#endif // RTP_SUPPORT_SDESPRIV +private: + int SetNonPrivateItem(int itemno,const uint8_t *s,size_t l) { if (l > RTCP_SDES_MAXITEMLENGTH) return ERR_RTP_SDES_LENGTHTOOBIG; return nonprivateitems[itemno].SetInfo(s,l); } + uint8_t *GetNonPrivateItem(int itemno,size_t *len) const { return nonprivateitems[itemno].GetInfo(len); } + + class SDESItem : public RTPMemoryObject + { + public: + SDESItem(RTPMemoryManager *mgr = 0) : RTPMemoryObject(mgr) + { + str = 0; + length = 0; + } + void SetMemoryManager(RTPMemoryManager *mgr) + { + RTPMemoryObject::SetMemoryManager(mgr); + } + ~SDESItem() + { + if (str) + RTPDeleteByteArray(str,GetMemoryManager()); + } + uint8_t *GetInfo(size_t *len) const { *len = length; return str; } + int SetInfo(const uint8_t *s,size_t len) { return SetString(&str,&length,s,len); } + protected: + int SetString(uint8_t **dest,size_t *destlen,const uint8_t *s,size_t len) + { + if (len <= 0) + { + if (*dest) + RTPDeleteByteArray((*dest),GetMemoryManager()); + *dest = 0; + *destlen = 0; + } + else + { + len = (len>RTCP_SDES_MAXITEMLENGTH)?RTCP_SDES_MAXITEMLENGTH:len; + uint8_t *str2 = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_SDESITEM) uint8_t[len]; + if (str2 == 0) + return ERR_RTP_OUTOFMEM; + memcpy(str2,s,len); + *destlen = len; + if (*dest) + RTPDeleteByteArray((*dest),GetMemoryManager()); + *dest = str2; + } + return 0; + } + private: + uint8_t *str; + size_t length; + }; + + SDESItem nonprivateitems[RTCP_SDES_NUMITEMS_NONPRIVATE]; + +#ifdef RTP_SUPPORT_SDESPRIV + class SDESPrivateItem : public SDESItem + { + public: + SDESPrivateItem(RTPMemoryManager *mgr) : SDESItem(mgr) + { + prefixlen = 0; + prefix = 0; + } + ~SDESPrivateItem() + { + if (prefix) + RTPDeleteByteArray(prefix,GetMemoryManager()); + } + uint8_t *GetPrefix(size_t *len) const { *len = prefixlen; return prefix; } + int SetPrefix(const uint8_t *s,size_t len) { return SetString(&prefix,&prefixlen,s,len); } + private: + uint8_t *prefix; + size_t prefixlen; + }; + + std::list privitems; + std::list::const_iterator curitem; +#endif // RTP_SUPPORT_SDESPRIV +}; + +} // end namespace + +#endif // RTCPSDESINFO_H + diff --git a/qrtplib/rtcpsdespacket.cpp b/qrtplib/rtcpsdespacket.cpp new file mode 100644 index 000000000..6d1876a79 --- /dev/null +++ b/qrtplib/rtcpsdespacket.cpp @@ -0,0 +1,141 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcpsdespacket.h" + +namespace qrtplib +{ + +RTCPSDESPacket::RTCPSDESPacket(uint8_t *data,size_t datalength) + : RTCPPacket(SDES,data,datalength) +{ + knownformat = false; + currentchunk = 0; + itemoffset = 0; + curchunknum = 0; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; + size_t len = datalength; + + if (hdr->padding) + { + uint8_t padcount = data[datalength-1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t)padcount) >= len) + return; + len -= (size_t)padcount; + } + + if (hdr->count == 0) + { + if (len != sizeof(RTCPCommonHeader)) + return; + } + else + { + int ssrccount = (int)(hdr->count); + uint8_t *chunk; + int chunkoffset; + + if (len < sizeof(RTCPCommonHeader)) + return; + + len -= sizeof(RTCPCommonHeader); + chunk = data+sizeof(RTCPCommonHeader); + + while ((ssrccount > 0) && (len > 0)) + { + if (len < (sizeof(uint32_t)*2)) // chunk must contain at least a SSRC identifier + return; // and a (possibly empty) item + + len -= sizeof(uint32_t); + chunkoffset = sizeof(uint32_t); + + bool done = false; + while (!done) + { + if (len < 1) // at least a zero byte (end of item list) should be there + return; + + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(chunk+chunkoffset); + if (sdeshdr->sdesid == 0) // end of item list + { + len--; + chunkoffset++; + + size_t r = (chunkoffset&0x03); + if (r != 0) + { + size_t addoffset = 4-r; + + if (addoffset > len) + return; + len -= addoffset; + chunkoffset += addoffset; + } + done = true; + } + else + { + if (len < sizeof(RTCPSDESHeader)) + return; + + len -= sizeof(RTCPSDESHeader); + chunkoffset += sizeof(RTCPSDESHeader); + + size_t itemlen = (size_t)(sdeshdr->length); + if (itemlen > len) + return; + + len -= itemlen; + chunkoffset += itemlen; + } + } + + ssrccount--; + chunk += chunkoffset; + } + + // check for remaining bytes + if (len > 0) + return; + if (ssrccount > 0) + return; + } + + knownformat = true; +} + + +} // end namespace + diff --git a/qrtplib/rtcpsdespacket.h b/qrtplib/rtcpsdespacket.h new file mode 100644 index 000000000..7d6eb01f0 --- /dev/null +++ b/qrtplib/rtcpsdespacket.h @@ -0,0 +1,383 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpsdespacket.h + */ + +#ifndef RTCPSDESPACKET_H + +#define RTCPSDESPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtpstructs.h" +#include "rtpdefines.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN + +namespace qrtplib +{ + + class RTCPCompoundPacket; + +/** Describes an RTCP source description packet. */ +class JRTPLIB_IMPORTEXPORT RTCPSDESPacket : public RTCPPacket +{ +public: + /** Identifies the type of an SDES item. */ + enum ItemType + { + None, /**< Used when the iteration over the items has finished. */ + CNAME, /**< Used for a CNAME (canonical name) item. */ + NAME, /**< Used for a NAME item. */ + EMAIL, /**< Used for an EMAIL item. */ + PHONE, /**< Used for a PHONE item. */ + LOC, /**< Used for a LOC (location) item. */ + TOOL, /**< Used for a TOOL item. */ + NOTE, /**< Used for a NOTE item. */ + PRIV, /**< Used for a PRIV item. */ + Unknown /**< Used when there is an item present, but the type is not recognized. */ + }; + + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPSDESPacket(uint8_t *data,size_t datalen); + ~RTCPSDESPacket() { } + + /** Returns the number of SDES chunks in the SDES packet. + * Returns the number of SDES chunks in the SDES packet. Each chunk has its own SSRC identifier. + */ + int GetChunkCount() const; + + /** Starts the iteration over the chunks. + * Starts the iteration. If no SDES chunks are present, the function returns \c false. Otherwise, + * it returns \c true and sets the current chunk to be the first chunk. + */ + bool GotoFirstChunk(); + + /** Sets the current chunk to the next available chunk. + * Sets the current chunk to the next available chunk. If no next chunk is present, this function returns + * \c false, otherwise it returns \c true. + */ + bool GotoNextChunk(); + + /** Returns the SSRC identifier of the current chunk. */ + uint32_t GetChunkSSRC() const; + + /** Starts the iteration over the SDES items in the current chunk. + * Starts the iteration over the SDES items in the current chunk. If no SDES items are + * present, the function returns \c false. Otherwise, the function sets the current item + * to be the first one and returns \c true. + */ + bool GotoFirstItem(); + + /** Advances the iteration to the next item in the current chunk. + * If there's another item in the chunk, the current item is set to be the next one and the function + * returns \c true. Otherwise, the function returns \c false. + */ + bool GotoNextItem(); + + /** Returns the SDES item type of the current item in the current chunk. */ + ItemType GetItemType() const; + + /** Returns the item length of the current item in the current chunk. */ + size_t GetItemLength() const; + + /** Returns the item data of the current item in the current chunk. */ + uint8_t *GetItemData(); + +#ifdef RTP_SUPPORT_SDESPRIV + /** If the current item is an SDES PRIV item, this function returns the length of the + * prefix string of the private item. + */ + size_t GetPRIVPrefixLength() const; + + /** If the current item is an SDES PRIV item, this function returns actual data of the + * prefix string. + */ + uint8_t *GetPRIVPrefixData(); + + /** If the current item is an SDES PRIV item, this function returns the length of the + * value string of the private item. + */ + size_t GetPRIVValueLength() const; + + /** If the current item is an SDES PRIV item, this function returns actual value data of the + * private item. + */ + uint8_t *GetPRIVValueData(); +#endif // RTP_SUPPORT_SDESPRIV + +private: + uint8_t *currentchunk; + int curchunknum; + size_t itemoffset; +}; + +inline int RTCPSDESPacket::GetChunkCount() const +{ + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; + return ((int)hdr->count); +} + +inline bool RTCPSDESPacket::GotoFirstChunk() +{ + if (GetChunkCount() == 0) + { + currentchunk = 0; + return false; + } + currentchunk = data+sizeof(RTCPCommonHeader); + curchunknum = 1; + itemoffset = sizeof(uint32_t); + return true; +} + +inline bool RTCPSDESPacket::GotoNextChunk() +{ + if (!knownformat) + return false; + if (currentchunk == 0) + return false; + if (curchunknum == GetChunkCount()) + return false; + + size_t offset = sizeof(uint32_t); + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+sizeof(uint32_t)); + + while (sdeshdr->sdesid != 0) + { + offset += sizeof(RTCPSDESHeader); + offset += (size_t)(sdeshdr->length); + sdeshdr = (RTCPSDESHeader *)(currentchunk+offset); + } + offset++; // for the zero byte + if ((offset&0x03) != 0) + offset += (4-(offset&0x03)); + currentchunk += offset; + curchunknum++; + itemoffset = sizeof(uint32_t); + return true; +} + +inline uint32_t RTCPSDESPacket::GetChunkSSRC() const +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + uint32_t *ssrc = (uint32_t *)currentchunk; + return ntohl(*ssrc); +} + +inline bool RTCPSDESPacket::GotoFirstItem() +{ + if (!knownformat) + return false; + if (currentchunk == 0) + return false; + itemoffset = sizeof(uint32_t); + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + if (sdeshdr->sdesid == 0) + return false; + return true; +} + +inline bool RTCPSDESPacket::GotoNextItem() +{ + if (!knownformat) + return false; + if (currentchunk == 0) + return false; + + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + if (sdeshdr->sdesid == 0) + return false; + + size_t offset = itemoffset; + offset += sizeof(RTCPSDESHeader); + offset += (size_t)(sdeshdr->length); + sdeshdr = (RTCPSDESHeader *)(currentchunk+offset); + if (sdeshdr->sdesid == 0) + return false; + itemoffset = offset; + return true; +} + +inline RTCPSDESPacket::ItemType RTCPSDESPacket::GetItemType() const +{ + if (!knownformat) + return None; + if (currentchunk == 0) + return None; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + switch (sdeshdr->sdesid) + { + case 0: + return None; + case RTCP_SDES_ID_CNAME: + return CNAME; + case RTCP_SDES_ID_NAME: + return NAME; + case RTCP_SDES_ID_EMAIL: + return EMAIL; + case RTCP_SDES_ID_PHONE: + return PHONE; + case RTCP_SDES_ID_LOCATION: + return LOC; + case RTCP_SDES_ID_TOOL: + return TOOL; + case RTCP_SDES_ID_NOTE: + return NOTE; + case RTCP_SDES_ID_PRIVATE: + return PRIV; + default: + return Unknown; + } + return Unknown; +} + +inline size_t RTCPSDESPacket::GetItemLength() const +{ + if (!knownformat) + return None; + if (currentchunk == 0) + return None; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + if (sdeshdr->sdesid == 0) + return 0; + return (size_t)(sdeshdr->length); +} + +inline uint8_t *RTCPSDESPacket::GetItemData() +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + if (sdeshdr->sdesid == 0) + return 0; + return (currentchunk+itemoffset+sizeof(RTCPSDESHeader)); +} + +#ifdef RTP_SUPPORT_SDESPRIV +inline size_t RTCPSDESPacket::GetPRIVPrefixLength() const +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk+itemoffset+sizeof(RTCPSDESHeader); + size_t prefixlength = (size_t)(*preflen); + if (prefixlength > (size_t)((sdeshdr->length)-1)) + return 0; + return prefixlength; +} + +inline uint8_t *RTCPSDESPacket::GetPRIVPrefixData() +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk+itemoffset+sizeof(RTCPSDESHeader); + size_t prefixlength = (size_t)(*preflen); + if (prefixlength > (size_t)((sdeshdr->length)-1)) + return 0; + if (prefixlength == 0) + return 0; + return (currentchunk+itemoffset+sizeof(RTCPSDESHeader)+1); +} + +inline size_t RTCPSDESPacket::GetPRIVValueLength() const +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk+itemoffset+sizeof(RTCPSDESHeader); + size_t prefixlength = (size_t)(*preflen); + if (prefixlength > (size_t)((sdeshdr->length)-1)) + return 0; + return ((size_t)(sdeshdr->length))-prefixlength-1; +} + +inline uint8_t *RTCPSDESPacket::GetPRIVValueData() +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk+itemoffset+sizeof(RTCPSDESHeader); + size_t prefixlength = (size_t)(*preflen); + if (prefixlength > (size_t)((sdeshdr->length)-1)) + return 0; + size_t valuelen = ((size_t)(sdeshdr->length))-prefixlength-1; + if (valuelen == 0) + return 0; + return (currentchunk+itemoffset+sizeof(RTCPSDESHeader)+1+prefixlength); +} + +#endif // RTP_SUPPORT_SDESPRIV + +} // end namespace + +#endif // RTCPSDESPACKET_H + diff --git a/qrtplib/rtcpsrpacket.cpp b/qrtplib/rtcpsrpacket.cpp new file mode 100644 index 000000000..1d7418163 --- /dev/null +++ b/qrtplib/rtcpsrpacket.cpp @@ -0,0 +1,69 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtcpsrpacket.h" + +namespace qrtplib +{ + +RTCPSRPacket::RTCPSRPacket(uint8_t *data,size_t datalength) + : RTCPPacket(SR,data,datalength) +{ + knownformat = false; + + RTCPCommonHeader *hdr; + size_t len = datalength; + size_t expectedlength; + + hdr = (RTCPCommonHeader *)data; + if (hdr->padding) + { + uint8_t padcount = data[datalength-1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t)padcount) >= len) + return; + len -= (size_t)padcount; + } + + expectedlength = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+sizeof(RTCPSenderReport); + expectedlength += sizeof(RTCPReceiverReport)*((int)hdr->count); + + if (expectedlength != len) + return; + + knownformat = true; +} + + +} // end namespace + diff --git a/qrtplib/rtcpsrpacket.h b/qrtplib/rtcpsrpacket.h new file mode 100644 index 000000000..85af7280f --- /dev/null +++ b/qrtplib/rtcpsrpacket.h @@ -0,0 +1,249 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpsrpacket.h + */ + +#ifndef RTCPSRPACKET_H + +#define RTCPSRPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtptimeutilities.h" +#include "rtpstructs.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP sender report packet. */ +class JRTPLIB_IMPORTEXPORT RTCPSRPacket : public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPSRPacket(uint8_t *data,size_t datalength); + ~RTCPSRPacket() { } + + /** Returns the SSRC of the participant who sent this packet. */ + uint32_t GetSenderSSRC() const; + + /** Returns the NTP timestamp contained in the sender report. */ + RTPNTPTime GetNTPTimestamp() const; + + /** Returns the RTP timestamp contained in the sender report. */ + uint32_t GetRTPTimestamp() const; + + /** Returns the sender's packet count contained in the sender report. */ + uint32_t GetSenderPacketCount() const; + + /** Returns the sender's octet count contained in the sender report. */ + uint32_t GetSenderOctetCount() const; + + /** Returns the number of reception report blocks present in this packet. */ + int GetReceptionReportCount() const; + + /** Returns the SSRC of the reception report block described by \c index which may have a value + * from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetSSRC(int index) const; + + /** Returns the `fraction lost' field of the reception report described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint8_t GetFractionLost(int index) const; + + /** Returns the number of lost packets in the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + int32_t GetLostPacketCount(int index) const; + + /** Returns the extended highest sequence number of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetExtendedHighestSequenceNumber(int index) const; + + /** Returns the jitter field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetJitter(int index) const; + + /** Returns the LSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetLSR(int index) const; + + /** Returns the DLSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetDLSR(int index) const; + +private: + RTCPReceiverReport *GotoReport(int index) const; +}; + +inline uint32_t RTCPSRPacket::GetSenderSSRC() const +{ + if (!knownformat) + return 0; + + uint32_t *ssrcptr = (uint32_t *)(data+sizeof(RTCPCommonHeader)); + return ntohl(*ssrcptr); +} + +inline RTPNTPTime RTCPSRPacket::GetNTPTimestamp() const +{ + if (!knownformat) + return RTPNTPTime(0,0); + + RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); + return RTPNTPTime(ntohl(sr->ntptime_msw),ntohl(sr->ntptime_lsw)); +} + +inline uint32_t RTCPSRPacket::GetRTPTimestamp() const +{ + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); + return ntohl(sr->rtptimestamp); +} + +inline uint32_t RTCPSRPacket::GetSenderPacketCount() const +{ + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); + return ntohl(sr->packetcount); +} + +inline uint32_t RTCPSRPacket::GetSenderOctetCount() const +{ + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); + return ntohl(sr->octetcount); +} + +inline int RTCPSRPacket::GetReceptionReportCount() const +{ + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; + return ((int)hdr->count); +} + +inline RTCPReceiverReport *RTCPSRPacket::GotoReport(int index) const +{ + RTCPReceiverReport *r = (RTCPReceiverReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)+sizeof(RTCPSenderReport)+index*sizeof(RTCPReceiverReport)); + return r; +} + +inline uint32_t RTCPSRPacket::GetSSRC(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->ssrc); +} + +inline uint8_t RTCPSRPacket::GetFractionLost(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return r->fractionlost; +} + +inline int32_t RTCPSRPacket::GetLostPacketCount(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + uint32_t count = ((uint32_t)r->packetslost[2])|(((uint32_t)r->packetslost[1])<<8)|(((uint32_t)r->packetslost[0])<<16); + if ((count&0x00800000) != 0) // test for negative number + count |= 0xFF000000; + int32_t *count2 = (int32_t *)(&count); + return (*count2); +} + +inline uint32_t RTCPSRPacket::GetExtendedHighestSequenceNumber(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->exthighseqnr); +} + +inline uint32_t RTCPSRPacket::GetJitter(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->jitter); +} + +inline uint32_t RTCPSRPacket::GetLSR(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->lsr); +} + +inline uint32_t RTCPSRPacket::GetDLSR(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return ntohl(r->dlsr); +} + +} // end namespace + +#endif // RTCPSRPACKET_H + diff --git a/qrtplib/rtcpunknownpacket.h b/qrtplib/rtcpunknownpacket.h new file mode 100644 index 000000000..79786539c --- /dev/null +++ b/qrtplib/rtcpunknownpacket.h @@ -0,0 +1,73 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtcpunknownpacket.h + */ + +#ifndef RTCPUNKNOWNPACKET_H + +#define RTCPUNKNOWNPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP packet of unknown type. + * Describes an RTCP packet of unknown type. This class doesn't have any extra member functions besides + * the ones it inherited. Note that since an unknown packet type doesn't have any format to check + * against, the IsKnownFormat function will trivially return \c true. + */ +class JRTPLIB_IMPORTEXPORT RTCPUnknownPacket : public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPUnknownPacket(uint8_t *data,size_t datalen) : RTCPPacket(Unknown,data,datalen) + { + // Since we don't expect a format, we'll trivially put knownformat = true + knownformat = true; + } + ~RTCPUnknownPacket() { } +}; + +} // end namespace + +#endif // RTCPUNKNOWNPACKET_H + diff --git a/qrtplib/rtpabortdescriptors.cpp b/qrtplib/rtpabortdescriptors.cpp new file mode 100644 index 000000000..2aec6d4a8 --- /dev/null +++ b/qrtplib/rtpabortdescriptors.cpp @@ -0,0 +1,258 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpabortdescriptors.h" +#include "rtpsocketutilinternal.h" +#include "rtperrors.h" +#include "rtpselect.h" + +namespace qrtplib +{ + +RTPAbortDescriptors::RTPAbortDescriptors() +{ + m_descriptors[0] = RTPSOCKERR; + m_descriptors[1] = RTPSOCKERR; + m_init = false; +} + +RTPAbortDescriptors::~RTPAbortDescriptors() +{ + Destroy(); +} + +#ifdef RTP_SOCKETTYPE_WINSOCK + +int RTPAbortDescriptors::Init() +{ + if (m_init) + return ERR_RTP_ABORTDESC_ALREADYINIT; + + SOCKET listensock; + int size; + struct sockaddr_in addr; + + listensock = socket(PF_INET,SOCK_STREAM,0); + if (listensock == RTPSOCKERR) + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + if (bind(listensock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(listensock); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } + + memset(&addr,0,sizeof(struct sockaddr_in)); + size = sizeof(struct sockaddr_in); + if (getsockname(listensock,(struct sockaddr*)&addr,&size) != 0) + { + RTPCLOSE(listensock); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } + + unsigned short connectport = ntohs(addr.sin_port); + + m_descriptors[0] = socket(PF_INET,SOCK_STREAM,0); + if (m_descriptors[0] == RTPSOCKERR) + { + RTPCLOSE(listensock); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } + + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + if (bind(m_descriptors[0],(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(listensock); + RTPCLOSE(m_descriptors[0]); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } + + if (listen(listensock,1) != 0) + { + RTPCLOSE(listensock); + RTPCLOSE(m_descriptors[0]); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } + + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(connectport); + + if (connect(m_descriptors[0],(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(listensock); + RTPCLOSE(m_descriptors[0]); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } + + memset(&addr,0,sizeof(struct sockaddr_in)); + size = sizeof(struct sockaddr_in); + m_descriptors[1] = accept(listensock,(struct sockaddr *)&addr,&size); + if (m_descriptors[1] == RTPSOCKERR) + { + RTPCLOSE(listensock); + RTPCLOSE(m_descriptors[0]); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } + + // okay, got the connection, close the listening socket + + RTPCLOSE(listensock); + + m_init = true; + return 0; +} + +void RTPAbortDescriptors::Destroy() +{ + if (!m_init) + return; + + RTPCLOSE(m_descriptors[0]); + RTPCLOSE(m_descriptors[1]); + m_descriptors[0] = RTPSOCKERR; + m_descriptors[1] = RTPSOCKERR; + + m_init = false; +} + +int RTPAbortDescriptors::SendAbortSignal() +{ + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; + + send(m_descriptors[1],"*",1,0); + return 0; +} + +int RTPAbortDescriptors::ReadSignallingByte() +{ + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; + + char buf[1]; + + recv(m_descriptors[0],buf,1,0); + return 0; +} + +#else // unix-style + +int RTPAbortDescriptors::Init() +{ + if (m_init) + return ERR_RTP_ABORTDESC_ALREADYINIT; + + if (pipe(m_descriptors) < 0) + return ERR_RTP_ABORTDESC_CANTCREATEPIPE; + + m_init = true; + return 0; +} + +void RTPAbortDescriptors::Destroy() +{ + if (!m_init) + return; + + close(m_descriptors[0]); + close(m_descriptors[1]); + m_descriptors[0] = RTPSOCKERR; + m_descriptors[1] = RTPSOCKERR; + + m_init = false; +} + +int RTPAbortDescriptors::SendAbortSignal() +{ + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; + + if (write(m_descriptors[1],"*",1)) + { + // To get rid of __wur related compiler warnings + } + + return 0; +} + +int RTPAbortDescriptors::ReadSignallingByte() +{ + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; + + unsigned char buf[1]; + + if (read(m_descriptors[0],buf,1)) + { + // To get rid of __wur related compiler warnings + } + return 0; +} + +#endif // RTP_SOCKETTYPE_WINSOCK + +// Keep calling 'ReadSignallingByte' until there's no byte left +int RTPAbortDescriptors::ClearAbortSignal() +{ + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; + + bool done = false; + while (!done) + { + int8_t isset = 0; + + // Not used: struct timeval tv = { 0, 0 }; + + int status = RTPSelect(&m_descriptors[0], &isset, 1, RTPTime(0)); + if (status < 0) + return status; + + if (!isset) + done = true; + else + { + int status = ReadSignallingByte(); + if (status < 0) + return status; + } + } + + return 0; +} + +} // end namespace diff --git a/qrtplib/rtpabortdescriptors.h b/qrtplib/rtpabortdescriptors.h new file mode 100644 index 000000000..52b9fb23a --- /dev/null +++ b/qrtplib/rtpabortdescriptors.h @@ -0,0 +1,103 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpabortdescriptors.h + */ + +#ifndef RTPABORTDESCRIPTORS_H + +#define RTPABORTDESCRIPTORS_H + +#include "rtpconfig.h" +#include "rtpsocketutil.h" + +namespace qrtplib +{ + +/** + * Helper class for several RTPTransmitter instances, to be able to cancel a + * call to 'select', 'poll' or 'WSAPoll'. + * + * This is a helper class for several RTPTransmitter instances. Typically a + * call to 'select' (or 'poll' or 'WSAPoll', depending on the platform) is used + * to wait for incoming data for a certain time. To be able to cancel this wait + * from another thread, this class provides a socket descriptor that's compatible + * with e.g. the 'select' call, and to which data can be sent using + * RTPAbortDescriptors::SendAbortSignal. If the descriptor is included in the + * 'select' call, the function will detect incoming data and the function stops + * waiting for incoming data. + * + * The class can be useful in case you'd like to create an implementation which + * uses a single poll thread for several RTPSession and RTPTransmitter instances. + * This idea is further illustrated in `example8.cpp`. + */ +class JRTPLIB_IMPORTEXPORT RTPAbortDescriptors +{ +public: + RTPAbortDescriptors(); + ~RTPAbortDescriptors(); + + /** Initializes this instance. */ + int Init(); + + /** Returns the socket descriptor that can be included in a call to + * 'select' (for example).*/ + SocketType GetAbortSocket() const { return m_descriptors[0]; } + + /** Returns a flag indicating if this instance was initialized. */ + bool IsInitialized() const { return m_init; } + + /** De-initializes this instance. */ + void Destroy(); + + /** Send a signal to the socket that's returned by RTPAbortDescriptors::GetAbortSocket, + * causing the 'select' call to detect that data is available, making the call + * end. */ + int SendAbortSignal(); + + /** For each RTPAbortDescriptors::SendAbortSignal function that's called, a call + * to this function can be made to clear the state again. */ + int ReadSignallingByte(); + + /** Similar to ReadSignallingByte::ReadSignallingByte, this function clears the signalling + * state, but this also works independently from the amount of times that + * RTPAbortDescriptors::SendAbortSignal was called. */ + int ClearAbortSignal(); +private: + SocketType m_descriptors[2]; + bool m_init; +}; + +} // end namespace + +#endif // RTPABORTDESCRIPTORS_H diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h new file mode 100644 index 000000000..9b6698a4e --- /dev/null +++ b/qrtplib/rtpaddress.h @@ -0,0 +1,97 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpaddress.h + */ + +#ifndef RTPADDRESS_H + +#define RTPADDRESS_H + +#include "rtpconfig.h" +#include + +namespace qrtplib +{ + +class RTPMemoryManager; + +/** This class is an abstract class which is used to specify destinations, multicast groups etc. */ +class JRTPLIB_IMPORTEXPORT RTPAddress +{ +public: + /** Identifies the actual implementation being used. */ + enum AddressType + { + IPv4Address, /**< Used by the UDP over IPv4 transmitter. */ + IPv6Address, /**< Used by the UDP over IPv6 transmitter. */ + ByteAddress, /**< A very general type of address, consisting of a port number and a number of bytes representing the host address. */ + UserDefinedAddress, /**< Can be useful for a user-defined transmitter. */ + TCPAddress /**< Used by the TCP transmitter. */ + }; + + /** Returns the type of address the actual implementation represents. */ + AddressType GetAddressType() const { return addresstype; } + + /** Creates a copy of the RTPAddress instance. + * Creates a copy of the RTPAddress instance. If \c mgr is not NULL, the + * corresponding memory manager will be used to allocate the memory for the address + * copy. + */ + virtual RTPAddress *CreateCopy(RTPMemoryManager *mgr) const = 0; + + /** Checks if the address \c addr is the same address as the one this instance represents. + * Checks if the address \c addr is the same address as the one this instance represents. + * Implementations must be able to handle a NULL argument. + */ + virtual bool IsSameAddress(const RTPAddress *addr) const = 0; + + /** Checks if the address \c addr represents the same host as this instance. + * Checks if the address \c addr represents the same host as this instance. Implementations + * must be able to handle a NULL argument. + */ + virtual bool IsFromSameHost(const RTPAddress *addr) const = 0; + + + virtual ~RTPAddress() { } +protected: + // only allow subclasses to be created + RTPAddress(const AddressType t) : addresstype(t) { } +private: + const AddressType addresstype; +}; + +} // end namespace + +#endif // RTPADDRESS_H + diff --git a/qrtplib/rtpbyteaddress.cpp b/qrtplib/rtpbyteaddress.cpp new file mode 100644 index 000000000..55bca7c24 --- /dev/null +++ b/qrtplib/rtpbyteaddress.cpp @@ -0,0 +1,81 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpbyteaddress.h" +#include "rtpmemorymanager.h" + +namespace qrtplib +{ + +bool RTPByteAddress::IsSameAddress(const RTPAddress *addr) const +{ + if (addr == 0) + return false; + if (addr->GetAddressType() != ByteAddress) + return false; + + const RTPByteAddress *addr2 = (const RTPByteAddress *)addr; + + if (addr2->addresslength != addresslength) + return false; + if (addresslength == 0 || (memcmp(hostaddress, addr2->hostaddress, addresslength) == 0)) + { + if (port == addr2->port) + return true; + } + return false; +} + +bool RTPByteAddress::IsFromSameHost(const RTPAddress *addr) const +{ + if (addr == 0) + return false; + if (addr->GetAddressType() != ByteAddress) + return false; + + const RTPByteAddress *addr2 = (const RTPByteAddress *)addr; + + if (addr2->addresslength != addresslength) + return false; + if (addresslength == 0 || (memcmp(hostaddress, addr2->hostaddress, addresslength) == 0)) + return true; + return false; +} + +RTPAddress *RTPByteAddress::CreateCopy(RTPMemoryManager *mgr) const +{ + JRTPLIB_UNUSED(mgr); // possibly unused + RTPByteAddress *a = RTPNew(mgr, RTPMEM_TYPE_CLASS_RTPADDRESS) RTPByteAddress(hostaddress, addresslength, port); + return a; +} + +} // end namespace diff --git a/qrtplib/rtpbyteaddress.h b/qrtplib/rtpbyteaddress.h new file mode 100644 index 000000000..b5f5711ad --- /dev/null +++ b/qrtplib/rtpbyteaddress.h @@ -0,0 +1,91 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpbyteaddress.h + */ + +#ifndef RTPBYTEADDRESS_H + +#define RTPBYTEADDRESS_H + +#include "rtpconfig.h" +#include "rtpaddress.h" +#include "rtptypes.h" +#include + +#define RTPBYTEADDRESS_MAXLENGTH 128 + +namespace qrtplib +{ + +class RTPMemoryManager; + +/** A very general kind of address consisting of a port number and a number of bytes describing the host address. + * A very general kind of address, consisting of a port number and a number of bytes describing the host address. + */ +class JRTPLIB_IMPORTEXPORT RTPByteAddress : public RTPAddress +{ +public: + /** Creates an instance of the class using \c addrlen bytes of \c hostaddress as host identification, + * and using \c port as the port number. */ + RTPByteAddress(const uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH], size_t addrlen, uint16_t port = 0) : RTPAddress(ByteAddress) { if (addrlen > RTPBYTEADDRESS_MAXLENGTH) addrlen = RTPBYTEADDRESS_MAXLENGTH; memcpy(RTPByteAddress::hostaddress, hostaddress, addrlen); RTPByteAddress::addresslength = addrlen; RTPByteAddress::port = port; } + + /** Sets the host address to the first \c addrlen bytes of \c hostaddress. */ + void SetHostAddress(const uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH], size_t addrlen) { if (addrlen > RTPBYTEADDRESS_MAXLENGTH) addrlen = RTPBYTEADDRESS_MAXLENGTH; memcpy(RTPByteAddress::hostaddress, hostaddress, addrlen); RTPByteAddress::addresslength = addrlen; } + + /** Sets the port number to \c port. */ + void SetPort(uint16_t port) { RTPByteAddress::port = port; } + + /** Returns a pointer to the stored host address. */ + const uint8_t *GetHostAddress() const { return hostaddress; } + + /** Returns the length in bytes of the stored host address. */ + size_t GetHostAddressLength() const { return addresslength; } + + /** Returns the port number stored in this instance. */ + uint16_t GetPort() const { return port; } + + RTPAddress *CreateCopy(RTPMemoryManager *mgr) const; + bool IsSameAddress(const RTPAddress *addr) const; + bool IsFromSameHost(const RTPAddress *addr) const; + +private: + uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH]; + size_t addresslength; + uint16_t port; +}; + +} // end namespace + +#endif // RTPBYTEADDRESS_H + diff --git a/qrtplib/rtpcollisionlist.cpp b/qrtplib/rtpcollisionlist.cpp new file mode 100644 index 000000000..4aa156032 --- /dev/null +++ b/qrtplib/rtpcollisionlist.cpp @@ -0,0 +1,113 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpcollisionlist.h" +#include "rtperrors.h" +#include "rtpmemorymanager.h" + +namespace qrtplib +{ + +RTPCollisionList::RTPCollisionList(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) +{ + timeinit.Dummy(); +} + +void RTPCollisionList::Clear() +{ + std::list::iterator it; + + for (it = addresslist.begin() ; it != addresslist.end() ; it++) + RTPDelete((*it).addr,GetMemoryManager()); + addresslist.clear(); +} + +int RTPCollisionList::UpdateAddress(const RTPAddress *addr,const RTPTime &receivetime,bool *created) +{ + if (addr == 0) + return ERR_RTP_COLLISIONLIST_BADADDRESS; + + std::list::iterator it; + + for (it = addresslist.begin() ; it != addresslist.end() ; it++) + { + if (((*it).addr)->IsSameAddress(addr)) + { + (*it).recvtime = receivetime; + *created = false; + return 0; + } + } + + RTPAddress *newaddr = addr->CreateCopy(GetMemoryManager()); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; + + addresslist.push_back(AddressAndTime(newaddr,receivetime)); + *created = true; + return 0; +} + +bool RTPCollisionList::HasAddress(const RTPAddress *addr) const +{ + std::list::const_iterator it; + + for (it = addresslist.begin() ; it != addresslist.end() ; it++) + { + if (((*it).addr)->IsSameAddress(addr)) + return true; + } + + return false; +} + +void RTPCollisionList::Timeout(const RTPTime ¤ttime,const RTPTime &timeoutdelay) +{ + std::list::iterator it; + RTPTime checktime = currenttime; + checktime -= timeoutdelay; + + it = addresslist.begin(); + while(it != addresslist.end()) + { + if ((*it).recvtime < checktime) // timeout + { + RTPDelete((*it).addr,GetMemoryManager()); + it = addresslist.erase(it); + } + else + it++; + } +} + +} // end namespace + diff --git a/qrtplib/rtpcollisionlist.h b/qrtplib/rtpcollisionlist.h new file mode 100644 index 000000000..b0ff4ea4f --- /dev/null +++ b/qrtplib/rtpcollisionlist.h @@ -0,0 +1,93 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpcollisionlist.h + */ + +#ifndef RTPCOLLISIONLIST_H + +#define RTPCOLLISIONLIST_H + +#include "rtpconfig.h" +#include "rtpaddress.h" +#include "rtptimeutilities.h" +#include "rtpmemoryobject.h" +#include + +namespace qrtplib +{ + +class RTPAddress; + +/** This class represents a list of addresses from which SSRC collisions were detected. */ +class JRTPLIB_IMPORTEXPORT RTPCollisionList : public RTPMemoryObject +{ +public: + /** Constructs an instance, optionally installing a memory manager. */ + RTPCollisionList(RTPMemoryManager *mgr = 0); + ~RTPCollisionList() { Clear(); } + + /** Clears the list of addresses. */ + void Clear(); + + /** Updates the entry for address \c addr to indicate that a collision was detected at time \c receivetime. + * Updates the entry for address \c addr to indicate that a collision was detected at time \c receivetime. + * If the entry did not exist yet, the flag \c created is set to \c true, otherwise it is set to \c false. + */ + int UpdateAddress(const RTPAddress *addr,const RTPTime &receivetime,bool *created); + + /** Returns \c true} if the address \c addr appears in the list. */ + bool HasAddress(const RTPAddress *addr) const; + + /** Assuming that the current time is given by \c currenttime, this function times out entries which + * haven't been updated in the previous time interval specified by \c timeoutdelay. + */ + void Timeout(const RTPTime ¤ttime,const RTPTime &timeoutdelay); + +private: + class AddressAndTime + { + public: + AddressAndTime(RTPAddress *a,const RTPTime &t) : addr(a),recvtime(t) { } + + RTPAddress *addr; + RTPTime recvtime; + }; + + std::list addresslist; +}; + +} // end namespace + +#endif // RTPCOLLISIONLIST_H + diff --git a/qrtplib/rtpconfig.h b/qrtplib/rtpconfig.h new file mode 100644 index 000000000..ea951d86a --- /dev/null +++ b/qrtplib/rtpconfig.h @@ -0,0 +1,111 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef RTPCONFIG_UNIX_H + +#define RTPCONFIG_UNIX_H + +#ifndef JRTPLIB_UNUSED +/** + * Provide a macro to use for marking method parameters as unused. + */ +#define JRTPLIB_UNUSED(x) (void)(x) +#endif // JRTPLIB_UNUSED + +#define JRTPLIB_IMPORT +#define JRTPLIB_EXPORT +#ifdef JRTPLIB_COMPILING + #define JRTPLIB_IMPORTEXPORT JRTPLIB_EXPORT +#else + #define JRTPLIB_IMPORTEXPORT JRTPLIB_IMPORT +#endif // JRTPLIB_COMPILING + +// Don't have + +// Don't have + +// Little endian system + +#define RTP_SOCKLENTYPE_UINT + +// No sa_len member in struct sockaddr + +#define RTP_SUPPORT_IPV4MULTICAST + +// No support for JThread was enabled + +#define RTP_SUPPORT_SDESPRIV + +#define RTP_SUPPORT_PROBATION + +#define RTP_SUPPORT_GETLOGINR + +#define RTP_SUPPORT_IPV6 + +#define RTP_SUPPORT_IPV6MULTICAST + +#define RTP_SUPPORT_IFADDRS + +#define RTP_SUPPORT_SENDAPP + +#define RTP_SUPPORT_MEMORYMANAGEMENT + +// No support for sending unknown RTCP packets + +#define RTP_SUPPORT_NETINET_IN + +// Not using winsock sockets + +// No QueryPerformanceCounter support + +// No ui64 suffix + +// Stdio snprintf version + +#define RTP_HAVE_ARRAYALLOC + +// No rand_s support + +// No strncpy_s support + +// No SRTP support + +#define RTP_HAVE_CLOCK_GETTIME + +#define RTP_HAVE_POLL + +// No 'WSAPoll' support + +#define RTP_HAVE_MSG_NOSIGNAL + +#endif // RTPCONFIG_UNIX_H + diff --git a/qrtplib/rtpdefines.h b/qrtplib/rtpdefines.h new file mode 100644 index 000000000..a18c05688 --- /dev/null +++ b/qrtplib/rtpdefines.h @@ -0,0 +1,76 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef RTPDEFINES_H + +#define RTPDEFINES_H + +#define RTP_VERSION 2 +#define RTP_MAXCSRCS 15 +#define RTP_MINPACKETSIZE 600 +#define RTP_DEFAULTPACKETSIZE 1400 +#define RTP_PROBATIONCOUNT 2 +#define RTP_MAXPRIVITEMS 256 +#define RTP_SENDERTIMEOUTMULTIPLIER 2 +#define RTP_BYETIMEOUTMULTIPLIER 1 +#define RTP_MEMBERTIMEOUTMULTIPLIER 5 +#define RTP_COLLISIONTIMEOUTMULTIPLIER 10 +#define RTP_NOTETTIMEOUTMULTIPLIER 25 +#define RTP_DEFAULTSESSIONBANDWIDTH 10000.0 + +#define RTP_RTCPTYPE_SR 200 +#define RTP_RTCPTYPE_RR 201 +#define RTP_RTCPTYPE_SDES 202 +#define RTP_RTCPTYPE_BYE 203 +#define RTP_RTCPTYPE_APP 204 + +#define RTCP_SDES_ID_CNAME 1 +#define RTCP_SDES_ID_NAME 2 +#define RTCP_SDES_ID_EMAIL 3 +#define RTCP_SDES_ID_PHONE 4 +#define RTCP_SDES_ID_LOCATION 5 +#define RTCP_SDES_ID_TOOL 6 +#define RTCP_SDES_ID_NOTE 7 +#define RTCP_SDES_ID_PRIVATE 8 +#define RTCP_SDES_NUMITEMS_NONPRIVATE 7 +#define RTCP_SDES_MAXITEMLENGTH 255 + +#define RTCP_BYE_MAXREASONLENGTH 255 +#define RTCP_DEFAULTMININTERVAL 5.0 +#define RTCP_DEFAULTBANDWIDTHFRACTION 0.05 +#define RTCP_DEFAULTSENDERFRACTION 0.25 +#define RTCP_DEFAULTHALFATSTARTUP true +#define RTCP_DEFAULTIMMEDIATEBYE true +#define RTCP_DEFAULTSRBYE true + +#endif // RTPDEFINES_H + diff --git a/qrtplib/rtperrors.cpp b/qrtplib/rtperrors.cpp new file mode 100644 index 000000000..54c989aa9 --- /dev/null +++ b/qrtplib/rtperrors.cpp @@ -0,0 +1,272 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtperrors.h" +#include "rtpdefines.h" +#include "rtpinternalutils.h" +#include + +namespace qrtplib +{ + +struct RTPErrorInfo +{ + int code; + const char *description; +}; + +static RTPErrorInfo ErrorDescriptions[]= +{ + { ERR_RTP_OUTOFMEM,"Out of memory" }, + { ERR_RTP_NOTHREADSUPPORT, "No JThread support was compiled in"}, + { ERR_RTP_COLLISIONLIST_BADADDRESS, "Passed invalid address (null) to collision list"}, + { ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS, "Element already exists in hash table"}, + { ERR_RTP_HASHTABLE_ELEMENTNOTFOUND, "Element not found in hash table"}, + { ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index"}, + { ERR_RTP_HASHTABLE_NOCURRENTELEMENT, "No current element selected in hash table"}, + { ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index"}, + { ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS, "Key value already exists in key hash table"}, + { ERR_RTP_KEYHASHTABLE_KEYNOTFOUND, "Key value not found in key hash table"}, + { ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT, "No current element selected in key hash table"}, + { ERR_RTP_PACKBUILD_ALREADYINIT, "RTP packet builder is already initialized"}, + { ERR_RTP_PACKBUILD_CSRCALREADYINLIST, "The specified CSRC is already in the RTP packet builder's CSRC list"}, + { ERR_RTP_PACKBUILD_CSRCLISTFULL, "The RTP packet builder's CSRC list already contains 15 entries"}, + { ERR_RTP_PACKBUILD_CSRCNOTINLIST, "The specified CSRC was not found in the RTP packet builder's CSRC list"}, + { ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET, "The RTP packet builder's default mark flag is not set"}, + { ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET, "The RTP packet builder's default payload type is not set"}, + { ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET, "The RTP packet builder's default timestamp increment is not set"}, + { ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE, "The specified maximum packet size for the RTP packet builder is invalid"}, + { ERR_RTP_PACKBUILD_NOTINIT, "The RTP packet builder is not initialized"}, + { ERR_RTP_PACKET_BADPAYLOADTYPE, "Invalid payload type"}, + { ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE, "Tried to create an RTP packet which whould exceed the specified maximum packet size"}, + { ERR_RTP_PACKET_EXTERNALBUFFERNULL, "Illegal value (null) passed as external buffer for the RTP packet"}, + { ERR_RTP_PACKET_ILLEGALBUFFERSIZE, "Illegal buffer size specified for the RTP packet"}, + { ERR_RTP_PACKET_INVALIDPACKET, "Invalid RTP packet format"}, + { ERR_RTP_PACKET_TOOMANYCSRCS, "More than 15 CSRCs specified for the RTP packet"}, + { ERR_RTP_POLLTHREAD_ALREADYRUNNING, "Poll thread is already running"}, + { ERR_RTP_POLLTHREAD_CANTINITMUTEX, "Can't initialize a mutex for the poll thread"}, + { ERR_RTP_POLLTHREAD_CANTSTARTTHREAD, "Can't start the poll thread"}, + { ERR_RTP_RTCPCOMPOUND_INVALIDPACKET, "Invalid RTCP compound packet format"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING, "Already building this RTCP compound packet"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT, "This RTCP compound packet is already built"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT, "There's already a SR or RR in this RTCP compound packet"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG, "The specified APP data length for the RTCP compound packet is too big"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL, "The specified buffer size for the RTCP comound packet is too small"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH, "The APP data length must be a multiple of four"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE, "The APP packet subtype must be smaller than 32"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE, "Invalid SDES item type specified for the RTCP compound packet"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL, "The specified maximum packet size for the RTCP compound packet is too small"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE, "Tried to add an SDES item to the RTCP compound packet when no SSRC was present"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT, "An RTCP compound packet must contain a SR or RR"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING, "The RTCP compound packet builder is not initialized"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT, "Adding this data would exceed the specified maximum RTCP compound packet size"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED, "Tried to add a report block to the RTCP compound packet when no SR or RR was started"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS, "Only 31 SSRCs will fit into a BYE packet for the RTCP compound packet"}, + { ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG, "The total data for the SDES PRIV item exceeds the maximum size (255 bytes) of an SDES item"}, + { ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT, "The RTCP packet builder is already initialized"}, + { ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE, "The specified maximum packet size for the RTCP packet builder is too small"}, + { ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT, "Speficied an illegal timestamp unit for the the RTCP packet builder"}, + { ERR_RTP_RTCPPACKETBUILDER_NOTINIT, "The RTCP packet builder was not initialized"}, + { ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON, "The RTCP compound packet filled sooner than expected"}, + { ERR_RTP_SCHEDPARAMS_BADFRACTION, "Illegal sender bandwidth fraction specified"}, + { ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL, "The minimum RTCP interval specified for the scheduler is too small"}, + { ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH, "Invalid RTCP bandwidth specified for the RTCP scheduler"}, + { ERR_RTP_SDES_LENGTHTOOBIG, "Specified size for the SDES item exceeds 255 bytes"}, + { ERR_RTP_SDES_PREFIXNOTFOUND, "The specified SDES PRIV prefix was not found"}, + { ERR_RTP_SESSION_ALREADYCREATED, "The session is already created"}, + { ERR_RTP_SESSION_CANTGETLOGINNAME, "Can't retrieve login name"}, + { ERR_RTP_SESSION_CANTINITMUTEX, "A mutex for the RTP session couldn't be initialized"}, + { ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL, "The maximum packet size specified for the RTP session is too small"}, + { ERR_RTP_SESSION_NOTCREATED, "The RTP session was not created"}, + { ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL, "The requested transmission protocol for the RTP session is not supported"}, + { ERR_RTP_SESSION_USINGPOLLTHREAD, "This function is not available when using the RTP poll thread feature"}, + { ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL, "A user-defined transmitter was requested but the supplied transmitter component is NULL"}, + { ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC, "Only one source can be marked as own SSRC in the source table"}, + { ERR_RTP_SOURCES_DONTHAVEOWNSSRC, "No source was marked as own SSRC in the source table"}, + { ERR_RTP_SOURCES_ILLEGALSDESTYPE, "Illegal SDES type specified for processing into the source table"}, + { ERR_RTP_SOURCES_SSRCEXISTS, "Can't create own SSRC because this SSRC identifier is already in the source table"}, + { ERR_RTP_UDPV4TRANS_ALREADYCREATED, "The transmitter was already created"}, + { ERR_RTP_UDPV4TRANS_ALREADYINIT, "The transmitter was already initialize"}, + { ERR_RTP_UDPV4TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data"}, + { ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed"}, + { ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed"}, + { ERR_RTP_UDPV4TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket"}, + { ERR_RTP_UDPV4TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, + { ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket"}, + { ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket"}, + { ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket"}, + { ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket"}, + { ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, + { ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, + { ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, + { ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, + { ERR_RTP_UDPV4TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, + { ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, + { ERR_RTP_UDPV4TRANS_NOSUCHENTRY, "Specified entry could not be found"}, + { ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, + { ERR_RTP_UDPV4TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, + { ERR_RTP_UDPV4TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, + { ERR_RTP_UDPV4TRANS_NOTWAITING, "The transmitter is not waiting for incoming data"}, + { ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, + { ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, + { ERR_RTP_UDPV6TRANS_ALREADYCREATED, "The transmitter was already created"}, + { ERR_RTP_UDPV6TRANS_ALREADYINIT, "The transmitter was already initialize"}, + { ERR_RTP_UDPV6TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data"}, + { ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed"}, + { ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed"}, + { ERR_RTP_UDPV6TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket"}, + { ERR_RTP_UDPV6TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, + { ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket"}, + { ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket"}, + { ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket"}, + { ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket"}, + { ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, + { ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, + { ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, + { ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, + { ERR_RTP_UDPV6TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, + { ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, + { ERR_RTP_UDPV6TRANS_NOSUCHENTRY, "Specified entry could not be found"}, + { ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, + { ERR_RTP_UDPV6TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, + { ERR_RTP_UDPV6TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, + { ERR_RTP_UDPV6TRANS_NOTWAITING, "The transmitter is not waiting for incoming data"}, + { ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, + { ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, + { ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL,"The hostname is larger than the specified buffer size"}, + { ERR_RTP_SDES_MAXPRIVITEMS,"The maximum number of SDES private item prefixes was reached"}, + { ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE,"An invalid probation type was specified"}, + { ERR_RTP_FAKETRANS_ALREADYCREATED, "The transmitter was already created"}, + { ERR_RTP_FAKETRANS_ALREADYINIT, "The transmitter was already initialize"}, + { ERR_RTP_FAKETRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, + { ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, + { ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, + { ERR_RTP_FAKETRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, + { ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, + { ERR_RTP_FAKETRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, + { ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, + { ERR_RTP_FAKETRANS_NOSUCHENTRY, "Specified entry could not be found"}, + { ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, + { ERR_RTP_FAKETRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, + { ERR_RTP_FAKETRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, + { ERR_RTP_FAKETRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, + { ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, + { ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED, "The WaitForIncomingData is not implemented in the Gst transmitter"}, + { ERR_RTP_RTPRANDOMURANDOM_CANTOPEN, "Unable to open /dev/urandom for reading"}, + { ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN, "The device /dev/urandom was already opened"}, + { ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED, "The rand_s call is not supported on this platform"}, + { ERR_RTP_EXTERNALTRANS_ALREADYCREATED, "The external transmission component was already created"}, + { ERR_RTP_EXTERNALTRANS_ALREADYINIT, "The external transmission component was already initialized"}, + { ERR_RTP_EXTERNALTRANS_ALREADYWAITING, "The external transmission component is already waiting for incoming data"}, + { ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE, "The external transmission component only supports accepting all incoming packets"}, + { ERR_RTP_EXTERNALTRANS_CANTINITMUTEX, "The external transmitter was unable to initialize a required mutex"}, + { ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS, "Only parameters of type RTPExternalTransmissionParams can be passed to the external transmission component"}, + { ERR_RTP_EXTERNALTRANS_NOACCEPTLIST, "The external transmitter does not have an accept list"}, + { ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED, "The external transmitter does not have a destination list"}, + { ERR_RTP_EXTERNALTRANS_NOIGNORELIST, "The external transmitter does not have an ignore list"}, + { ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT, "The external transmitter does not support the multicast functions"}, + { ERR_RTP_EXTERNALTRANS_NOSENDER, "No sender has been set for this external transmitter"}, + { ERR_RTP_EXTERNALTRANS_NOTCREATED, "The external transmitter has not been created yet"}, + { ERR_RTP_EXTERNALTRANS_NOTINIT, "The external transmitter has not been initialized yet"}, + { ERR_RTP_EXTERNALTRANS_NOTWAITING, "The external transmitter is not currently waiting for incoming data"}, + { ERR_RTP_EXTERNALTRANS_SENDERROR, "The external transmitter was unable to actually send the data"}, + { ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG, "The specified data size exceeds the maximum amount that has been set"}, + { ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT, "Unable to obtain the existing socket info using 'getsockname'"}, + { ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET, "The existing socket specified does not appear to be an IPv4 socket"}, + { ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET, "The existing socket that was specified does not have its port set yet"}, + { ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE, "Can't get the socket type of the specified existing socket"}, + { ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE, "The specified existing socket is not an UDP socket"}, + { ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET, "Can't get a valid socket when trying to choose a port automatically"}, + { ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET, "Can't seem to get RTP/RTCP ports automatically, too many attempts"}, + { ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED, "Flag to change data was requested, but OnChangeRTPOrRTCPData was not reimplemented"}, + { ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED, "The initialization function was already called"}, + { ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT, "Unable to initialize libsrtp context"}, + { ERR_RTP_SECURESESSION_CANTINITMUTEX, "Unable to initialize a mutex" }, + { ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED, "The libsrtp context initilization function must be called before it can be used"}, + { ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT, "There's not enough RTP or RTCP data to encrypt"}, + { ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA, "Unable to encrypt RTP data"}, + { ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA, "Unable to encrypt RTCP data"}, + { ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT, "There's not enough RTP or RTCP data to decrypt"}, + { ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA, "Unable to decrypt RTP data"}, + { ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA, "Unable to decrypt RTCP data"}, + { ERR_RTP_ABORTDESC_ALREADYINIT, "The RTPAbortDescriptors instance is already initialized" }, + { ERR_RTP_ABORTDESC_NOTINIT, "The RTPAbortDescriptors instance is not yet initialized" }, + { ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS, "Unable to create two connected TCP sockets for the abort descriptors" }, + { ERR_RTP_ABORTDESC_CANTCREATEPIPE, "Unable to create a pipe for the abort descriptors" }, + { ERR_RTP_SESSION_THREADSAFETYCONFLICT, "For the background poll thread to be used, thread safety must also be set" }, + { ERR_RTP_SELECT_ERRORINSELECT, "Error in the call to 'select'" }, + { ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE, "A socket descriptor value is too large for a call to 'select' (exceeds FD_SETSIZE)" }, + { ERR_RTP_SELECT_ERRORINPOLL, "Error in the call to 'poll' or 'WSAPoll'" }, + { ERR_RTP_TCPTRANS_NOTINIT, "The TCP transmitter is not yet initialized" }, + { ERR_RTP_TCPTRANS_ALREADYINIT, "The TCP transmitter is already initialized" }, + { ERR_RTP_TCPTRANS_NOTCREATED, "The TCP transmitter is not yet created" }, + { ERR_RTP_TCPTRANS_ALREADYCREATED, "The TCP transmitter is already created" }, + { ERR_RTP_TCPTRANS_ILLEGALPARAMETERS, "The parameters for the TCP transmitter are invalid" }, + { ERR_RTP_TCPTRANS_CANTINITMUTEX, "Unable to initialize a mutex during the initialization of the TCP transmitter" }, + { ERR_RTP_TCPTRANS_ALREADYWAITING, "The TCP transmitter is already waiting for data" }, + { ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE, "The address specified is not a valid address for the TCP transmitter" }, + { ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED, "No socket was specified in the address used for the TCP transmitter" }, + { ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT, "The TCP transmitter does not support multicasting" }, + { ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED, "The TCP transmitter does not support receive modes other than 'accept all'" }, + { ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size for the TCP transmitter is limited to 64KB" }, + { ERR_RTP_TCPTRANS_NOTWAITING, "The TCP transmitter is not waiting for data" }, + { ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS, "The specified destination address (socket) was already added to the destination list of the TCP transmitter" }, + { ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS, "The specified destination address (socket) was not found in the list of destinations of the TCP transmitter" }, + { ERR_RTP_TCPTRANS_ERRORINSEND, "An error occurred in the TCP transmitter while sending a packet" }, + { ERR_RTP_TCPTRANS_ERRORINRECV, "An error occurred in the TCP transmitter while receiving a packet" }, + { 0,0 } +}; + +std::string RTPGetErrorString(int errcode) +{ + int i; + + if (errcode >= 0) + return std::string("No error"); + + i = 0; + while (ErrorDescriptions[i].code != 0) + { + if (ErrorDescriptions[i].code == errcode) + return std::string(ErrorDescriptions[i].description); + i++; + } + + char str[16]; + + RTP_SNPRINTF(str,16,"(%d)",errcode); + + return std::string("Unknown error code") + std::string(str); +} + +} // end namespace + diff --git a/qrtplib/rtperrors.h b/qrtplib/rtperrors.h new file mode 100644 index 000000000..b454dce4e --- /dev/null +++ b/qrtplib/rtperrors.h @@ -0,0 +1,251 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtperrors.h + */ + +#ifndef RTPERRORS_H + +#define RTPERRORS_H + +#include "rtpconfig.h" +#include + +namespace qrtplib +{ + +/** Returns a string describing the error code \c errcode. */ +std::string JRTPLIB_IMPORTEXPORT RTPGetErrorString(int errcode); + +} // end namespace + +#define ERR_RTP_OUTOFMEM -1 +#define ERR_RTP_NOTHREADSUPPORT -2 +#define ERR_RTP_COLLISIONLIST_BADADDRESS -3 +#define ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS -4 +#define ERR_RTP_HASHTABLE_ELEMENTNOTFOUND -5 +#define ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX -6 +#define ERR_RTP_HASHTABLE_NOCURRENTELEMENT -7 +#define ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX -8 +#define ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS -9 +#define ERR_RTP_KEYHASHTABLE_KEYNOTFOUND -10 +#define ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT -11 +#define ERR_RTP_PACKBUILD_ALREADYINIT -12 +#define ERR_RTP_PACKBUILD_CSRCALREADYINLIST -13 +#define ERR_RTP_PACKBUILD_CSRCLISTFULL -14 +#define ERR_RTP_PACKBUILD_CSRCNOTINLIST -15 +#define ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET -16 +#define ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET -17 +#define ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET -18 +#define ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE -19 +#define ERR_RTP_PACKBUILD_NOTINIT -20 +#define ERR_RTP_PACKET_BADPAYLOADTYPE -21 +#define ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE -22 +#define ERR_RTP_PACKET_EXTERNALBUFFERNULL -23 +#define ERR_RTP_PACKET_ILLEGALBUFFERSIZE -24 +#define ERR_RTP_PACKET_INVALIDPACKET -25 +#define ERR_RTP_PACKET_TOOMANYCSRCS -26 +#define ERR_RTP_POLLTHREAD_ALREADYRUNNING -27 +#define ERR_RTP_POLLTHREAD_CANTINITMUTEX -28 +#define ERR_RTP_POLLTHREAD_CANTSTARTTHREAD -29 +#define ERR_RTP_RTCPCOMPOUND_INVALIDPACKET -30 +#define ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING -31 +#define ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT -32 +#define ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT -33 +#define ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG -34 +#define ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL -35 +#define ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH -36 +#define ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE -37 +#define ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE -38 +#define ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL -39 +#define ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE -40 +#define ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT -41 +#define ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING -42 +#define ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT -43 +#define ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED -44 +#define ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS -45 +#define ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG -46 +#define ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT -47 +#define ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE -48 +#define ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT -49 +#define ERR_RTP_RTCPPACKETBUILDER_NOTINIT -50 +#define ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON -51 +#define ERR_RTP_SCHEDPARAMS_BADFRACTION -52 +#define ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL -53 +#define ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH -54 +#define ERR_RTP_SDES_LENGTHTOOBIG -55 +#define ERR_RTP_SDES_MAXPRIVITEMS -56 +#define ERR_RTP_SDES_PREFIXNOTFOUND -57 +#define ERR_RTP_SESSION_ALREADYCREATED -58 +#define ERR_RTP_SESSION_CANTGETLOGINNAME -59 +#define ERR_RTP_SESSION_CANTINITMUTEX -60 +#define ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL -61 +#define ERR_RTP_SESSION_NOTCREATED -62 +#define ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL -63 +#define ERR_RTP_SESSION_USINGPOLLTHREAD -64 +#define ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC -65 +#define ERR_RTP_SOURCES_DONTHAVEOWNSSRC -66 +#define ERR_RTP_SOURCES_ILLEGALSDESTYPE -67 +#define ERR_RTP_SOURCES_SSRCEXISTS -68 +#define ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL -69 +#define ERR_RTP_UDPV4TRANS_ALREADYCREATED -70 +#define ERR_RTP_UDPV4TRANS_ALREADYINIT -71 +#define ERR_RTP_UDPV4TRANS_ALREADYWAITING -72 +#define ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET -73 +#define ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET -74 +#define ERR_RTP_UDPV4TRANS_CANTCREATESOCKET -75 +#define ERR_RTP_UDPV4TRANS_CANTINITMUTEX -76 +#define ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF -77 +#define ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF -78 +#define ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF -79 +#define ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF -80 +#define ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP -81 +#define ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE -82 +#define ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS -83 +#define ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE -84 +#define ERR_RTP_UDPV4TRANS_NOLOCALIPS -85 +#define ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT -86 +#define ERR_RTP_UDPV4TRANS_NOSUCHENTRY -87 +#define ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS -88 +#define ERR_RTP_UDPV4TRANS_NOTCREATED -89 +#define ERR_RTP_UDPV4TRANS_NOTINIT -90 +#define ERR_RTP_UDPV4TRANS_NOTWAITING -91 +#define ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN -92 +#define ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG -93 +#define ERR_RTP_UDPV6TRANS_ALREADYCREATED -94 +#define ERR_RTP_UDPV6TRANS_ALREADYINIT -95 +#define ERR_RTP_UDPV6TRANS_ALREADYWAITING -96 +#define ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET -97 +#define ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET -98 +#define ERR_RTP_UDPV6TRANS_CANTCREATESOCKET -99 +#define ERR_RTP_UDPV6TRANS_CANTINITMUTEX -100 +#define ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF -101 +#define ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF -102 +#define ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF -103 +#define ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF -104 +#define ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP -105 +#define ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE -106 +#define ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS -107 +#define ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE -108 +#define ERR_RTP_UDPV6TRANS_NOLOCALIPS -109 +#define ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT -110 +#define ERR_RTP_UDPV6TRANS_NOSUCHENTRY -111 +#define ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS -112 +#define ERR_RTP_UDPV6TRANS_NOTCREATED -113 +#define ERR_RTP_UDPV6TRANS_NOTINIT -114 +#define ERR_RTP_UDPV6TRANS_NOTWAITING -115 +#define ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN -116 +#define ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG -117 +#define ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE -118 +#define ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL -119 +#define ERR_RTP_FAKETRANS_ALREADYCREATED -120 +#define ERR_RTP_FAKETRANS_ALREADYINIT -121 +#define ERR_RTP_FAKETRANS_CANTINITMUTEX -122 +#define ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP -123 +#define ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE -124 +#define ERR_RTP_FAKETRANS_ILLEGALPARAMETERS -125 +#define ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE -126 +#define ERR_RTP_FAKETRANS_NOLOCALIPS -127 +#define ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT -128 +#define ERR_RTP_FAKETRANS_NOSUCHENTRY -129 +#define ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS -130 +#define ERR_RTP_FAKETRANS_NOTCREATED -131 +#define ERR_RTP_FAKETRANS_NOTINIT -132 +#define ERR_RTP_FAKETRANS_PORTBASENOTEVEN -133 +#define ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG -134 +#define ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED -135 +#define ERR_RTP_RTPRANDOMURANDOM_CANTOPEN -136 +#define ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN -137 +#define ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED -138 +#define ERR_RTP_EXTERNALTRANS_ALREADYCREATED -139 +#define ERR_RTP_EXTERNALTRANS_ALREADYINIT -140 +#define ERR_RTP_EXTERNALTRANS_ALREADYWAITING -141 +#define ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE -142 +#define ERR_RTP_EXTERNALTRANS_CANTINITMUTEX -143 +#define ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS -144 +#define ERR_RTP_EXTERNALTRANS_NOACCEPTLIST -145 +#define ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED -146 +#define ERR_RTP_EXTERNALTRANS_NOIGNORELIST -147 +#define ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT -148 +#define ERR_RTP_EXTERNALTRANS_NOSENDER -149 +#define ERR_RTP_EXTERNALTRANS_NOTCREATED -150 +#define ERR_RTP_EXTERNALTRANS_NOTINIT -151 +#define ERR_RTP_EXTERNALTRANS_NOTWAITING -152 +#define ERR_RTP_EXTERNALTRANS_SENDERROR -153 +#define ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG -154 +#define ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT -155 +#define ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET -156 +#define ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET -157 +#define ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE -158 +#define ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE -159 +#define ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET -160 +#define ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET -161 +#define ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED -162 +#define ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED -163 +#define ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT -164 +#define ERR_RTP_SECURESESSION_CANTINITMUTEX -165 +#define ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED -166 +#define ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT -167 +#define ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA -168 +#define ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA -169 +#define ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT -170 +#define ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA -171 +#define ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA -172 +#define ERR_RTP_ABORTDESC_ALREADYINIT -173 +#define ERR_RTP_ABORTDESC_NOTINIT -174 +#define ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS -175 +#define ERR_RTP_ABORTDESC_CANTCREATEPIPE -176 +#define ERR_RTP_SESSION_THREADSAFETYCONFLICT -177 +#define ERR_RTP_SELECT_ERRORINSELECT -178 +#define ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE -179 +#define ERR_RTP_SELECT_ERRORINPOLL -180 +#define ERR_RTP_TCPTRANS_NOTINIT -181 +#define ERR_RTP_TCPTRANS_ALREADYINIT -182 +#define ERR_RTP_TCPTRANS_ALREADYCREATED -183 +#define ERR_RTP_TCPTRANS_ILLEGALPARAMETERS -184 +#define ERR_RTP_TCPTRANS_CANTINITMUTEX -185 +#define ERR_RTP_TCPTRANS_ALREADYWAITING -186 +#define ERR_RTP_TCPTRANS_NOTCREATED -187 +#define ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE -188 +#define ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED -189 +#define ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT -190 +#define ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED -191 +#define ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG -192 +#define ERR_RTP_TCPTRANS_NOTWAITING -193 +#define ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS -194 +#define ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS -195 +#define ERR_RTP_TCPTRANS_ERRORINSEND -196 +#define ERR_RTP_TCPTRANS_ERRORINRECV -197 + +#endif // RTPERRORS_H + diff --git a/qrtplib/rtpexternaltransmitter.cpp b/qrtplib/rtpexternaltransmitter.cpp new file mode 100644 index 000000000..1cd2ed791 --- /dev/null +++ b/qrtplib/rtpexternaltransmitter.cpp @@ -0,0 +1,733 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpexternaltransmitter.h" +#include "rtprawpacket.h" +#include "rtptimeutilities.h" +#include "rtpdefines.h" +#include "rtperrors.h" +#include "rtpsocketutilinternal.h" +#include "rtpselect.h" +#include +#include + +#include + +#ifdef RTP_SUPPORT_THREAD + #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } + #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } + #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } + #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } +#else + #define MAINMUTEX_LOCK + #define MAINMUTEX_UNLOCK + #define WAITMUTEX_LOCK + #define WAITMUTEX_UNLOCK +#endif // RTP_SUPPORT_THREAD + +namespace qrtplib +{ + +RTPExternalTransmitter::RTPExternalTransmitter(RTPMemoryManager *mgr) : RTPTransmitter(mgr), packetinjector((RTPExternalTransmitter *)this) +{ + created = false; + init = false; +} + +RTPExternalTransmitter::~RTPExternalTransmitter() +{ + Destroy(); +} + +int RTPExternalTransmitter::Init(bool tsafe) +{ + if (init) + return ERR_RTP_EXTERNALTRANS_ALREADYINIT; + +#ifdef RTP_SUPPORT_THREAD + threadsafe = tsafe; + if (threadsafe) + { + int status; + + status = mainmutex.Init(); + if (status < 0) + return ERR_RTP_EXTERNALTRANS_CANTINITMUTEX; + status = waitmutex.Init(); + if (status < 0) + return ERR_RTP_EXTERNALTRANS_CANTINITMUTEX; + } +#else + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; +#endif // RTP_SUPPORT_THREAD + + init = true; + return 0; +} + +int RTPExternalTransmitter::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) +{ + const RTPExternalTransmissionParams *params; + int status; + + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_ALREADYCREATED; + } + + // Obtain transmission parameters + + if (transparams == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; + } + if (transparams->GetTransmissionProtocol() != RTPTransmitter::ExternalProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; + } + + params = (const RTPExternalTransmissionParams *)transparams; + + if ((status = m_abortDesc.Init()) < 0) + { + MAINMUTEX_UNLOCK + return status; + } + m_abortCount = 0; + + maxpacksize = maximumpacketsize; + sender = params->GetSender(); + headersize = params->GetAdditionalHeaderSize(); + + localhostname = 0; + localhostnamelength = 0; + + waitingfordata = false; + created = true; + MAINMUTEX_UNLOCK + return 0; +} + +void RTPExternalTransmitter::Destroy() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK; + return; + } + + if (localhostname) + { + RTPDeleteByteArray(localhostname,GetMemoryManager()); + localhostname = 0; + localhostnamelength = 0; + } + + FlushPackets(); + created = false; + + if (waitingfordata) + { + m_abortDesc.SendAbortSignal(); + m_abortCount++; + m_abortDesc.Destroy(); + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK + } + else + m_abortDesc.Destroy(); + + MAINMUTEX_UNLOCK +} + +RTPTransmissionInfo *RTPExternalTransmitter::GetTransmissionInfo() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPExternalTransmissionInfo(&packetinjector); + MAINMUTEX_UNLOCK + return tinf; +} + +void RTPExternalTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) +{ + if (!init) + return; + + RTPDelete(i, GetMemoryManager()); +} + +int RTPExternalTransmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) +{ + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + + if (localhostname == 0) + { + // We'll just use 'gethostname' for simplicity + + char name[1024]; + + if (gethostname(name,1023) != 0) + strcpy(name, "localhost"); // failsafe + else + name[1023] = 0; // ensure null-termination + + localhostnamelength = strlen(name); + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + + memcpy(localhostname, name, localhostnamelength); + localhostname[localhostnamelength] = 0; + } + + if ((*bufferlength) < localhostnamelength) + { + *bufferlength = localhostnamelength; // tell the application the required size of the buffer + MAINMUTEX_UNLOCK + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; + } + + memcpy(buffer,localhostname,localhostnamelength); + *bufferlength = localhostnamelength; + + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPExternalTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) +{ + MAINMUTEX_LOCK + bool value = false; + if (sender) + value = sender->ComesFromThisSender(addr); + MAINMUTEX_UNLOCK + return value; +} + +int RTPExternalTransmitter::Poll() +{ + return 0; +} + +int RTPExternalTransmitter::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +{ + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (waitingfordata) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_ALREADYWAITING; + } + + waitingfordata = true; + + if (!rawpacketlist.empty()) + { + if (dataavailable != 0) + *dataavailable = true; + waitingfordata = false; + MAINMUTEX_UNLOCK + return 0; + } + + WAITMUTEX_LOCK + MAINMUTEX_UNLOCK + + int8_t isset = 0; + SocketType abortSock = m_abortDesc.GetAbortSocket(); + int status = RTPSelect(&abortSock, &isset, 1, delay); + if (status < 0) + { + MAINMUTEX_LOCK + waitingfordata = false; + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return status; + } + + MAINMUTEX_LOCK + waitingfordata = false; + if (!created) // destroy called + { + MAINMUTEX_UNLOCK; + WAITMUTEX_UNLOCK + return 0; + } + + // if aborted, read from abort buffer + if (isset) + { + m_abortDesc.ClearAbortSignal(); + m_abortCount = 0; + } + + if (dataavailable != 0) + { + if (rawpacketlist.empty()) + *dataavailable = false; + else + *dataavailable = true; + } + + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return 0; +} + +int RTPExternalTransmitter::AbortWait() +{ + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (!waitingfordata) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOTWAITING; + } + + m_abortDesc.SendAbortSignal(); + m_abortCount++; + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPExternalTransmitter::SendRTPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; + } + + if (!sender) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOSENDER; + } + + MAINMUTEX_UNLOCK + + if (!sender->SendRTP(data, len)) + return ERR_RTP_EXTERNALTRANS_SENDERROR; + + return 0; +} + +int RTPExternalTransmitter::SendRTCPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; + } + + if (!sender) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOSENDER; + } + MAINMUTEX_UNLOCK + + if (!sender->SendRTCP(data, len)) + return ERR_RTP_EXTERNALTRANS_SENDERROR; + + return 0; +} + +int RTPExternalTransmitter::AddDestination(const RTPAddress &) +{ + return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; +} + +int RTPExternalTransmitter::DeleteDestination(const RTPAddress &) +{ + return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; +} + +void RTPExternalTransmitter::ClearDestinations() +{ +} + +bool RTPExternalTransmitter::SupportsMulticasting() +{ + return false; +} + +int RTPExternalTransmitter::JoinMulticastGroup(const RTPAddress &) +{ + return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; +} + +int RTPExternalTransmitter::LeaveMulticastGroup(const RTPAddress &) +{ + return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; +} + +void RTPExternalTransmitter::LeaveAllMulticastGroups() +{ +} + +int RTPExternalTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (m != RTPTransmitter::AcceptAll) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE; + } + MAINMUTEX_UNLOCK + return 0; +} + +int RTPExternalTransmitter::AddToIgnoreList(const RTPAddress &) +{ + return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; +} + +int RTPExternalTransmitter::DeleteFromIgnoreList(const RTPAddress &) +{ + return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; +} + +void RTPExternalTransmitter::ClearIgnoreList() +{ +} + +int RTPExternalTransmitter::AddToAcceptList(const RTPAddress &) +{ + return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; +} + +int RTPExternalTransmitter::DeleteFromAcceptList(const RTPAddress &) +{ + return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; +} + +void RTPExternalTransmitter::ClearAcceptList() +{ +} + +int RTPExternalTransmitter::SetMaximumPacketSize(size_t s) +{ + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + maxpacksize = s; + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPExternalTransmitter::NewDataAvailable() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + { + if (rawpacketlist.empty()) + v = false; + else + v = true; + } + + MAINMUTEX_UNLOCK + return v; +} + +RTPRawPacket *RTPExternalTransmitter::GetNextPacket() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + + RTPRawPacket *p; + + if (!created) + { + MAINMUTEX_UNLOCK + return 0; + } + if (rawpacketlist.empty()) + { + MAINMUTEX_UNLOCK + return 0; + } + + p = *(rawpacketlist.begin()); + rawpacketlist.pop_front(); + + MAINMUTEX_UNLOCK + return p; +} + +// Here the private functions start... + +void RTPExternalTransmitter::FlushPackets() +{ + std::list::const_iterator it; + + for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) + RTPDelete(*it,GetMemoryManager()); + rawpacketlist.clear(); +} + +void RTPExternalTransmitter::InjectRTP(const void *data, size_t len, const RTPAddress &a) +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return; + } + + RTPAddress *addr = a.CreateCopy(GetMemoryManager()); + if (addr == 0) + return; + + uint8_t *datacopy; + + datacopy = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET) uint8_t[len]; + if (datacopy == 0) + { + RTPDelete(addr,GetMemoryManager()); + return; + } + memcpy(datacopy, data, len); + + RTPTime curtime = RTPTime::CurrentTime(); + RTPRawPacket *pack; + + pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,len,addr,curtime,true,GetMemoryManager()); + if (pack == 0) + { + RTPDelete(addr,GetMemoryManager()); + RTPDeleteByteArray(localhostname,GetMemoryManager()); + return; + } + rawpacketlist.push_back(pack); + + if (m_abortCount == 0) + { + m_abortDesc.SendAbortSignal(); + m_abortCount++; + } + + MAINMUTEX_UNLOCK +} + +void RTPExternalTransmitter::InjectRTCP(const void *data, size_t len, const RTPAddress &a) +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return; + } + + RTPAddress *addr = a.CreateCopy(GetMemoryManager()); + if (addr == 0) + return; + + uint8_t *datacopy; + + datacopy = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[len]; + if (datacopy == 0) + { + RTPDelete(addr,GetMemoryManager()); + return; + } + memcpy(datacopy, data, len); + + RTPTime curtime = RTPTime::CurrentTime(); + RTPRawPacket *pack; + + pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,len,addr,curtime,false,GetMemoryManager()); + if (pack == 0) + { + RTPDelete(addr,GetMemoryManager()); + RTPDeleteByteArray(localhostname,GetMemoryManager()); + return; + } + rawpacketlist.push_back(pack); + + if (m_abortCount == 0) + { + m_abortDesc.SendAbortSignal(); + m_abortCount++; + } + + MAINMUTEX_UNLOCK +} + +void RTPExternalTransmitter::InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a) +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return; + } + + RTPAddress *addr = a.CreateCopy(GetMemoryManager()); + if (addr == 0) + return; + + uint8_t *datacopy; + bool rtp = true; + + if (len >= 2) + { + const uint8_t *pData = (const uint8_t *)data; + if (pData[1] >= 200 && pData[1] <= 204) + rtp = false; + } + + datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[len]; + if (datacopy == 0) + { + RTPDelete(addr,GetMemoryManager()); + return; + } + memcpy(datacopy, data, len); + + RTPTime curtime = RTPTime::CurrentTime(); + RTPRawPacket *pack; + + pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,len,addr,curtime,rtp,GetMemoryManager()); + if (pack == 0) + { + RTPDelete(addr,GetMemoryManager()); + RTPDeleteByteArray(localhostname,GetMemoryManager()); + return; + } + rawpacketlist.push_back(pack); + + if (m_abortCount == 0) + { + m_abortDesc.SendAbortSignal(); + m_abortCount++; + } + + MAINMUTEX_UNLOCK + +} + +} // end namespace + diff --git a/qrtplib/rtpexternaltransmitter.h b/qrtplib/rtpexternaltransmitter.h new file mode 100644 index 000000000..72574a129 --- /dev/null +++ b/qrtplib/rtpexternaltransmitter.h @@ -0,0 +1,231 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpexternaltransmitter.h + */ + +#ifndef RTPEXTERNALTRANSMITTER_H + +#define RTPEXTERNALTRANSMITTER_H + +#include "rtpconfig.h" +#include "rtptransmitter.h" +#include "rtpabortdescriptors.h" +#include + +#ifdef RTP_SUPPORT_THREAD + #include +#endif // RTP_SUPPORT_THREAD + +namespace qrtplib +{ + +class RTPExternalTransmitter; + +/** Base class to specify a mechanism to transmit RTP packets outside of this library. + * Base class to specify a mechanism to transmit RTP packets outside of this library. When + * you want to use your own mechanism to transmit RTP packets, you need to specify that + * you'll be using the external transmission component, and derive a class from this base + * class. An instance should then be specified in the RTPExternalTransmissionParams object, + * so that the transmitter will call the \c SendRTP, \c SendRTCP and \c ComesFromThisSender + * methods of this instance when needed. + */ +class JRTPLIB_IMPORTEXPORT RTPExternalSender +{ +public: + RTPExternalSender() { } + virtual ~RTPExternalSender() { } + + /** This member function will be called when RTP data needs to be transmitted. */ + virtual bool SendRTP(const void *data, size_t len) = 0; + + /** This member function will be called when an RTCP packet needs to be transmitted. */ + virtual bool SendRTCP(const void *data, size_t len) = 0; + + /** Used to identify if an RTPAddress instance originated from this sender (to be able to detect own packets). */ + virtual bool ComesFromThisSender(const RTPAddress *a) = 0; +}; + +/** Interface to inject incoming RTP and RTCP packets into the library. + * Interface to inject incoming RTP and RTCP packets into the library. When you have your own + * mechanism to receive incoming RTP/RTCP data, you'll need to pass these packets to the library. + * By first retrieving the RTPExternalTransmissionInfo instance for the external transmitter you'll + * be using, you can obtain the associated RTPExternalPacketInjecter instance. By calling it's + * member functions, you can then inject RTP or RTCP data into the library for further processing. + */ +class JRTPLIB_IMPORTEXPORT RTPExternalPacketInjecter +{ +public: + RTPExternalPacketInjecter(RTPExternalTransmitter *trans) { transmitter = trans; } + ~RTPExternalPacketInjecter() { } + + /** This function can be called to insert an RTP packet into the transmission component. */ + void InjectRTP(const void *data, size_t len, const RTPAddress &a); + + /** This function can be called to insert an RTCP packet into the transmission component. */ + void InjectRTCP(const void *data, size_t len, const RTPAddress &a); + + /** Use this function to inject an RTP or RTCP packet and the transmitter will try to figure out which type of packet it is. */ + void InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a); +private: + RTPExternalTransmitter *transmitter; +}; + +/** Parameters to initialize a transmitter of type RTPExternalTransmitter. */ +class JRTPLIB_IMPORTEXPORT RTPExternalTransmissionParams : public RTPTransmissionParams +{ +public: + /** Using this constructor you can specify which RTPExternalSender object you'll be using + * and how much the additional header overhead for each packet will be. */ + RTPExternalTransmissionParams(RTPExternalSender *s, int headeroverhead):RTPTransmissionParams(RTPTransmitter::ExternalProto) { sender = s; headersize = headeroverhead; } + + RTPExternalSender *GetSender() const { return sender; } + int GetAdditionalHeaderSize() const { return headersize; } +private: + RTPExternalSender *sender; + int headersize; +}; + +/** Additional information about the external transmission component. */ +class JRTPLIB_IMPORTEXPORT RTPExternalTransmissionInfo : public RTPTransmissionInfo +{ +public: + RTPExternalTransmissionInfo(RTPExternalPacketInjecter *p) : RTPTransmissionInfo(RTPTransmitter::ExternalProto) { packetinjector = p; } + + /** Tells you which RTPExternalPacketInjecter you need to use to pass RTP or RTCP + * data on to the transmission component. */ + RTPExternalPacketInjecter *GetPacketInjector() const { return packetinjector; } +private: + RTPExternalPacketInjecter *packetinjector; +}; + +/** A transmission component which will use user specified functions to transmit the data and + * which will expose functions to inject received RTP or RTCP data into this component. + * A transmission component which will use user specified functions to transmit the data and + * which will expose functions to inject received RTP or RTCP data into this component. Use + * a class derived from RTPExternalSender to specify the functions which need to be used for + * sending the data. Obtain the RTPExternalTransmissionInfo object associated with this + * transmitter to obtain the functions needed to pass RTP/RTCP packets on to the transmitter. + */ +class JRTPLIB_IMPORTEXPORT RTPExternalTransmitter : public RTPTransmitter +{ +public: + RTPExternalTransmitter(RTPMemoryManager *mgr); + ~RTPExternalTransmitter(); + + int Init(bool treadsafe); + int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() { return headersize; } + + int Poll(); + int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); + int AbortWait(); + + int SendRTPData(const void *data,size_t len); + int SendRTCPData(const void *data,size_t len); + + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); + + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); + + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); + + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); + + void InjectRTP(const void *data, size_t len, const RTPAddress &a); + void InjectRTCP(const void *data, size_t len, const RTPAddress &a); + void InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a); +private: + void FlushPackets(); + + bool init; + bool created; + bool waitingfordata; + RTPExternalSender *sender; + RTPExternalPacketInjecter packetinjector; + + std::list rawpacketlist; + + uint8_t *localhostname; + size_t localhostnamelength; + + size_t maxpacksize; + int headersize; + + RTPAbortDescriptors m_abortDesc; + int m_abortCount; +#ifdef RTP_SUPPORT_THREAD + jthread::JMutex mainmutex,waitmutex; + int threadsafe; +#endif // RTP_SUPPORT_THREAD +}; + +inline void RTPExternalPacketInjecter::InjectRTP(const void *data, size_t len, const RTPAddress &a) +{ + transmitter->InjectRTP(data, len, a); +} + +inline void RTPExternalPacketInjecter::InjectRTCP(const void *data, size_t len, const RTPAddress &a) +{ + transmitter->InjectRTCP(data, len, a); +} + +inline void RTPExternalPacketInjecter::InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a) +{ + transmitter->InjectRTPorRTCP(data, len, a); +} + +} // end namespace + +#endif // RTPTCPSOCKETTRANSMITTER_H + + diff --git a/qrtplib/rtphashtable.h b/qrtplib/rtphashtable.h new file mode 100644 index 000000000..4920384f4 --- /dev/null +++ b/qrtplib/rtphashtable.h @@ -0,0 +1,308 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef RTPHASHTABLE_H + +#define RTPHASHTABLE_H + +/** + * \file rtphashtable.h + */ + +#include "rtperrors.h" +#include "rtpmemoryobject.h" + +namespace qrtplib +{ + +//template +template +class RTPHashTable : public RTPMemoryObject +{ +public: + RTPHashTable(RTPMemoryManager *mgr = 0, int memtype = RTPMEM_TYPE_OTHER); + ~RTPHashTable() { Clear(); } + + void GotoFirstElement() { curhashelem = firsthashelem; } + void GotoLastElement() { curhashelem = lasthashelem; } + bool HasCurrentElement() { return (curhashelem == 0)?false:true; } + int DeleteCurrentElement(); + Element &GetCurrentElement() { return curhashelem->GetElement(); } + int GotoElement(const Element &e); + bool HasElement(const Element &e); + void GotoNextElement(); + void GotoPreviousElement(); + void Clear(); + + int AddElement(const Element &elem); + int DeleteElement(const Element &elem); + +private: + class HashElement + { + public: + HashElement(const Element &e,int index):element(e) { hashprev = 0; hashnext = 0; listnext = 0; listprev = 0; hashindex = index; } + int GetHashIndex() { return hashindex; } + Element &GetElement() { return element; } + + private: + int hashindex; + Element element; + public: + HashElement *hashprev,*hashnext; + HashElement *listprev,*listnext; + }; + + HashElement *table[hashsize]; + HashElement *firsthashelem,*lasthashelem; + HashElement *curhashelem; +#ifdef RTP_SUPPORT_MEMORYMANAGEMENT + int memorytype; +#endif // RTP_SUPPORT_MEMORYMANAGEMENT +}; + +template +inline RTPHashTable::RTPHashTable(RTPMemoryManager *mgr,int memtype) : RTPMemoryObject(mgr) +{ + JRTPLIB_UNUSED(memtype); // possibly unused + + for (int i = 0 ; i < hashsize ; i++) + table[i] = 0; + firsthashelem = 0; + lasthashelem = 0; +#ifdef RTP_SUPPORT_MEMORYMANAGEMENT + memorytype = memtype; +#endif // RTP_SUPPORT_MEMORYMANAGEMENT +} + +template +inline int RTPHashTable::DeleteCurrentElement() +{ + if (curhashelem) + { + HashElement *tmp1,*tmp2; + int index; + + // First, relink elements in current hash bucket + + index = curhashelem->GetHashIndex(); + tmp1 = curhashelem->hashprev; + tmp2 = curhashelem->hashnext; + if (tmp1 == 0) // no previous element in hash bucket + { + table[index] = tmp2; + if (tmp2 != 0) + tmp2->hashprev = 0; + } + else // there is a previous element in the hash bucket + { + tmp1->hashnext = tmp2; + if (tmp2 != 0) + tmp2->hashprev = tmp1; + } + + // Relink elements in list + + tmp1 = curhashelem->listprev; + tmp2 = curhashelem->listnext; + if (tmp1 == 0) // curhashelem is first in list + { + firsthashelem = tmp2; + if (tmp2 != 0) + tmp2->listprev = 0; + else // curhashelem is also last in list + lasthashelem = 0; + } + else + { + tmp1->listnext = tmp2; + if (tmp2 != 0) + tmp2->listprev = tmp1; + else // curhashelem is last in list + lasthashelem = tmp1; + } + + // finally, with everything being relinked, we can delete curhashelem + RTPDelete(curhashelem,GetMemoryManager()); + curhashelem = tmp2; // Set to next element in the list + } + else + return ERR_RTP_HASHTABLE_NOCURRENTELEMENT; + return 0; +} + +template +inline int RTPHashTable::GotoElement(const Element &e) +{ + int index; + bool found; + + index = GetIndex::GetIndex(e); + if (index >= hashsize) + return ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + + curhashelem = table[index]; + found = false; + while(!found && curhashelem != 0) + { + if (curhashelem->GetElement() == e) + found = true; + else + curhashelem = curhashelem->hashnext; + } + if (!found) + return ERR_RTP_HASHTABLE_ELEMENTNOTFOUND; + return 0; +} + +template +inline bool RTPHashTable::HasElement(const Element &e) +{ + int index; + bool found; + HashElement *tmp; + + index = GetIndex::GetIndex(e); + if (index >= hashsize) + return false; + + tmp = table[index]; + found = false; + while(!found && tmp != 0) + { + if (tmp->GetElement() == e) + found = true; + else + tmp = tmp->hashnext; + } + return found; +} + +template +inline void RTPHashTable::GotoNextElement() +{ + if (curhashelem) + curhashelem = curhashelem->listnext; +} + +template +inline void RTPHashTable::GotoPreviousElement() +{ + if (curhashelem) + curhashelem = curhashelem->listprev; +} + +template +inline void RTPHashTable::Clear() +{ + HashElement *tmp1,*tmp2; + + for (int i = 0 ; i < hashsize ; i++) + table[i] = 0; + + tmp1 = firsthashelem; + while (tmp1 != 0) + { + tmp2 = tmp1->listnext; + RTPDelete(tmp1,GetMemoryManager()); + tmp1 = tmp2; + } + firsthashelem = 0; + lasthashelem = 0; +} + +template +inline int RTPHashTable::AddElement(const Element &elem) +{ + int index; + bool found; + HashElement *e,*newelem; + + index = GetIndex::GetIndex(elem); + if (index >= hashsize) + return ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + + e = table[index]; + found = false; + while(!found && e != 0) + { + if (e->GetElement() == elem) + found = true; + else + e = e->hashnext; + } + if (found) + return ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS; + + // Okay, the key doesn't exist, so we can add the new element in the hash table + + newelem = RTPNew(GetMemoryManager(),memorytype) HashElement(elem,index); + if (newelem == 0) + return ERR_RTP_OUTOFMEM; + + e = table[index]; + table[index] = newelem; + newelem->hashnext = e; + if (e != 0) + e->hashprev = newelem; + + // Now, we still got to add it to the linked list + + if (firsthashelem == 0) + { + firsthashelem = newelem; + lasthashelem = newelem; + } + else // there already are some elements in the list + { + lasthashelem->listnext = newelem; + newelem->listprev = lasthashelem; + lasthashelem = newelem; + } + return 0; +} + +template +inline int RTPHashTable::DeleteElement(const Element &elem) +{ + int status; + + status = GotoElement(elem); + if (status < 0) + return status; + return DeleteCurrentElement(); +} + +} // end namespace + +#endif // RTPHASHTABLE_H + diff --git a/qrtplib/rtpinternalsourcedata.cpp b/qrtplib/rtpinternalsourcedata.cpp new file mode 100644 index 000000000..8ab88749f --- /dev/null +++ b/qrtplib/rtpinternalsourcedata.cpp @@ -0,0 +1,294 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpinternalsourcedata.h" +#include "rtppacket.h" +#include + +#define RTPINTERNALSOURCEDATA_MAXPROBATIONPACKETS 32 + +namespace qrtplib +{ + +RTPInternalSourceData::RTPInternalSourceData(uint32_t ssrc,RTPSources::ProbationType probtype,RTPMemoryManager *mgr):RTPSourceData(ssrc,mgr) +{ + JRTPLIB_UNUSED(probtype); // possibly unused +#ifdef RTP_SUPPORT_PROBATION + probationtype = probtype; +#endif // RTP_SUPPORT_PROBATION +} + +RTPInternalSourceData::~RTPInternalSourceData() +{ +} + +// The following function should delete rtppack if necessary +int RTPInternalSourceData::ProcessRTPPacket(RTPPacket *rtppack,const RTPTime &receivetime,bool *stored,RTPSources *sources) +{ + bool accept,onprobation,applyprobation; + double tsunit; + + *stored = false; + + if (timestampunit < 0) + tsunit = INF_GetEstimatedTimestampUnit(); + else + tsunit = timestampunit; + +#ifdef RTP_SUPPORT_PROBATION + if (validated) // If the source is our own process, we can already be validated. No + applyprobation = false; // probation should be applied in that case. + else + { + if (probationtype == RTPSources::NoProbation) + applyprobation = false; + else + applyprobation = true; + } +#else + applyprobation = false; +#endif // RTP_SUPPORT_PROBATION + + stats.ProcessPacket(rtppack,receivetime,tsunit,ownssrc,&accept,applyprobation,&onprobation); + +#ifdef RTP_SUPPORT_PROBATION + switch (probationtype) + { + case RTPSources::ProbationStore: + if (!(onprobation || accept)) + return 0; + if (accept) + validated = true; + break; + case RTPSources::ProbationDiscard: + case RTPSources::NoProbation: + if (!accept) + return 0; + validated = true; + break; + default: + return ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE; + } +#else + if (!accept) + return 0; + validated = true; +#endif // RTP_SUPPORT_PROBATION; + + if (validated && !ownssrc) // for own ssrc these variables depend on the outgoing packets, not on the incoming + issender = true; + + bool isonprobation = !validated; + bool ispackethandled = false; + + sources->OnValidatedRTPPacket(this, rtppack, isonprobation, &ispackethandled); + if (ispackethandled) // Packet is already handled in the callback, no need to store it in the list + { + // Set 'stored' to true to avoid the packet being deallocated + *stored = true; + return 0; + } + + // Now, we can place the packet in the queue + + if (packetlist.empty()) + { + *stored = true; + packetlist.push_back(rtppack); + return 0; + } + + if (!validated) // still on probation + { + // Make sure that we don't buffer too much packets to avoid wasting memory + // on a bad source. Delete the packet in the queue with the lowest sequence + // number. + if (packetlist.size() == RTPINTERNALSOURCEDATA_MAXPROBATIONPACKETS) + { + RTPPacket *p = *(packetlist.begin()); + packetlist.pop_front(); + RTPDelete(p,GetMemoryManager()); + } + } + + // find the right position to insert the packet + + std::list::iterator it,start; + bool done = false; + uint32_t newseqnr = rtppack->GetExtendedSequenceNumber(); + + it = packetlist.end(); + --it; + start = packetlist.begin(); + + while (!done) + { + RTPPacket *p; + uint32_t seqnr; + + p = *it; + seqnr = p->GetExtendedSequenceNumber(); + if (seqnr > newseqnr) + { + if (it != start) + --it; + else // we're at the start of the list + { + *stored = true; + done = true; + packetlist.push_front(rtppack); + } + } + else if (seqnr < newseqnr) // insert after this packet + { + ++it; + packetlist.insert(it,rtppack); + done = true; + *stored = true; + } + else // they're equal !! Drop packet + { + done = true; + } + } + + return 0; +} + +int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid,const uint8_t *data,size_t itemlen,const RTPTime &receivetime,bool *cnamecollis) +{ + *cnamecollis = false; + + stats.SetLastMessageTime(receivetime); + + switch(sdesid) + { + case RTCP_SDES_ID_CNAME: + { + size_t curlen; + uint8_t *oldcname; + + // NOTE: we're going to make sure that the CNAME is only set once. + oldcname = SDESinf.GetCNAME(&curlen); + if (curlen == 0) + { + // if CNAME is set, the source is validated + SDESinf.SetCNAME(data,itemlen); + validated = true; + } + else // check if this CNAME is equal to the one that is already present + { + if (curlen != itemlen) + *cnamecollis = true; + else + { + if (memcmp(data,oldcname,itemlen) != 0) + *cnamecollis = true; + } + } + } + break; + case RTCP_SDES_ID_NAME: + { + size_t oldlen; + + SDESinf.GetName(&oldlen); + if (oldlen == 0) // Name not set + return SDESinf.SetName(data,itemlen); + } + break; + case RTCP_SDES_ID_EMAIL: + { + size_t oldlen; + + SDESinf.GetEMail(&oldlen); + if (oldlen == 0) + return SDESinf.SetEMail(data,itemlen); + } + break; + case RTCP_SDES_ID_PHONE: + return SDESinf.SetPhone(data,itemlen); + case RTCP_SDES_ID_LOCATION: + return SDESinf.SetLocation(data,itemlen); + case RTCP_SDES_ID_TOOL: + { + size_t oldlen; + + SDESinf.GetTool(&oldlen); + if (oldlen == 0) + return SDESinf.SetTool(data,itemlen); + } + break; + case RTCP_SDES_ID_NOTE: + stats.SetLastNoteTime(receivetime); + return SDESinf.SetNote(data,itemlen); + } + return 0; +} + +#ifdef RTP_SUPPORT_SDESPRIV + +int RTPInternalSourceData::ProcessPrivateSDESItem(const uint8_t *prefix,size_t prefixlen,const uint8_t *value,size_t valuelen,const RTPTime &receivetime) +{ + int status; + + stats.SetLastMessageTime(receivetime); + status = SDESinf.SetPrivateValue(prefix,prefixlen,value,valuelen); + if (status == ERR_RTP_SDES_MAXPRIVITEMS) + return 0; // don't stop processing just because the number of items is full + return status; +} + +#endif // RTP_SUPPORT_SDESPRIV + +int RTPInternalSourceData::ProcessBYEPacket(const uint8_t *reason,size_t reasonlen,const RTPTime &receivetime) +{ + if (byereason) + { + RTPDeleteByteArray(byereason,GetMemoryManager()); + byereason = 0; + byereasonlen = 0; + } + + byetime = receivetime; + byereason = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPBYEREASON) uint8_t[reasonlen]; + if (byereason == 0) + return ERR_RTP_OUTOFMEM; + memcpy(byereason,reason,reasonlen); + byereasonlen = reasonlen; + receivedbye = true; + stats.SetLastMessageTime(receivetime); + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtpinternalsourcedata.h b/qrtplib/rtpinternalsourcedata.h new file mode 100644 index 000000000..e5ef60732 --- /dev/null +++ b/qrtplib/rtpinternalsourcedata.h @@ -0,0 +1,135 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpinternalsourcedata.h + */ + +#ifndef RTPINTERNALSOURCEDATA_H + +#define RTPINTERNALSOURCEDATA_H + +#include "rtpconfig.h" +#include "rtpsourcedata.h" +#include "rtpaddress.h" +#include "rtptimeutilities.h" +#include "rtpsources.h" + +namespace qrtplib +{ + +class JRTPLIB_IMPORTEXPORT RTPInternalSourceData : public RTPSourceData +{ +public: + RTPInternalSourceData(uint32_t ssrc, RTPSources::ProbationType probtype, RTPMemoryManager *mgr = 0); + ~RTPInternalSourceData(); + + int ProcessRTPPacket(RTPPacket *rtppack,const RTPTime &receivetime,bool *stored, RTPSources *sources); + void ProcessSenderInfo(const RTPNTPTime &ntptime,uint32_t rtptime,uint32_t packetcount, + uint32_t octetcount,const RTPTime &receivetime) { SRprevinf = SRinf; SRinf.Set(ntptime,rtptime,packetcount,octetcount,receivetime); stats.SetLastMessageTime(receivetime); } + void ProcessReportBlock(uint8_t fractionlost,int32_t lostpackets,uint32_t exthighseqnr, + uint32_t jitter,uint32_t lsr,uint32_t dlsr, + const RTPTime &receivetime) { RRprevinf = RRinf; RRinf.Set(fractionlost,lostpackets,exthighseqnr,jitter,lsr,dlsr,receivetime); stats.SetLastMessageTime(receivetime); } + void UpdateMessageTime(const RTPTime &receivetime) { stats.SetLastMessageTime(receivetime); } + int ProcessSDESItem(uint8_t sdesid,const uint8_t *data,size_t itemlen,const RTPTime &receivetime,bool *cnamecollis); +#ifdef RTP_SUPPORT_SDESPRIV + int ProcessPrivateSDESItem(const uint8_t *prefix,size_t prefixlen,const uint8_t *value,size_t valuelen,const RTPTime &receivetime); +#endif // RTP_SUPPORT_SDESPRIV + int ProcessBYEPacket(const uint8_t *reason,size_t reasonlen,const RTPTime &receivetime); + + int SetRTPDataAddress(const RTPAddress *a); + int SetRTCPDataAddress(const RTPAddress *a); + + void ClearSenderFlag() { issender = false; } + void SentRTPPacket() { if (!ownssrc) return; RTPTime t = RTPTime::CurrentTime(); issender = true; stats.SetLastRTPPacketTime(t); stats.SetLastMessageTime(t); } + void SetOwnSSRC() { ownssrc = true; validated = true; } + void SetCSRC() { validated = true; iscsrc = true; } + void ClearNote() { SDESinf.SetNote(0,0); } + +#ifdef RTP_SUPPORT_PROBATION +private: + RTPSources::ProbationType probationtype; +#endif // RTP_SUPPORT_PROBATION +}; + +inline int RTPInternalSourceData::SetRTPDataAddress(const RTPAddress *a) +{ + if (a == 0) + { + if (rtpaddr) + { + RTPDelete(rtpaddr,GetMemoryManager()); + rtpaddr = 0; + } + } + else + { + RTPAddress *newaddr = a->CreateCopy(GetMemoryManager()); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; + + if (rtpaddr && a != rtpaddr) + RTPDelete(rtpaddr,GetMemoryManager()); + rtpaddr = newaddr; + } + isrtpaddrset = true; + return 0; +} + +inline int RTPInternalSourceData::SetRTCPDataAddress(const RTPAddress *a) +{ + if (a == 0) + { + if (rtcpaddr) + { + RTPDelete(rtcpaddr,GetMemoryManager()); + rtcpaddr = 0; + } + } + else + { + RTPAddress *newaddr = a->CreateCopy(GetMemoryManager()); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; + + if (rtcpaddr && a != rtcpaddr) + RTPDelete(rtcpaddr,GetMemoryManager()); + rtcpaddr = newaddr; + } + isrtcpaddrset = true; + return 0; +} + +} // end namespace + +#endif // RTPINTERNALSOURCEDATA_H + diff --git a/qrtplib/rtpinternalutils.h b/qrtplib/rtpinternalutils.h new file mode 100644 index 000000000..dcfde5307 --- /dev/null +++ b/qrtplib/rtpinternalutils.h @@ -0,0 +1,59 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2011 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef RTPINTERNALUTILS_H + +#define RTPINTERNALUTILS_H + +#include "rtpconfig.h" + +#if defined(RTP_HAVE_SNPRINTF_S) + #include + #include + #define RTP_SNPRINTF _snprintf_s +#elif defined(RTP_HAVE_SNPRINTF) + #include + #include + #define RTP_SNPRINTF _snprintf +#else + #include + #define RTP_SNPRINTF snprintf +#endif + +#ifdef RTP_HAVE_STRNCPY_S + #define RTP_STRNCPY(dest, src, len) strncpy_s((dest), (len), (src), _TRUNCATE) +#else + #define RTP_STRNCPY(dest, src, len) strncpy((dest), (src), (len)) +#endif // RTP_HAVE_STRNCPY_S + +#endif // RTPINTERNALUTILS_H + diff --git a/qrtplib/rtpipv4address.cpp b/qrtplib/rtpipv4address.cpp new file mode 100644 index 000000000..59d81c503 --- /dev/null +++ b/qrtplib/rtpipv4address.cpp @@ -0,0 +1,74 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpipv4address.h" +#include "rtpmemorymanager.h" + +namespace qrtplib +{ + +bool RTPIPv4Address::IsSameAddress(const RTPAddress *addr) const +{ + if (addr == 0) + return false; + if (addr->GetAddressType() != IPv4Address) + return false; + + const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; + if (addr2->GetIP() == ip && addr2->GetPort() == port) + return true; + return false; +} + +bool RTPIPv4Address::IsFromSameHost(const RTPAddress *addr) const +{ + if (addr == 0) + return false; + if (addr->GetAddressType() != IPv4Address) + return false; + + const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; + if (addr2->GetIP() == ip) + return true; + return false; +} + +RTPAddress *RTPIPv4Address::CreateCopy(RTPMemoryManager *mgr) const +{ + JRTPLIB_UNUSED(mgr); // possibly unused + RTPIPv4Address *a = RTPNew(mgr,RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv4Address(ip,port); + return a; +} + + +} // end namespace + diff --git a/qrtplib/rtpipv4address.h b/qrtplib/rtpipv4address.h new file mode 100644 index 000000000..864fdcfbc --- /dev/null +++ b/qrtplib/rtpipv4address.h @@ -0,0 +1,149 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpipv4address.h + */ + +#ifndef RTPIPV4ADDRESS_H + +#define RTPIPV4ADDRESS_H + +#include "rtpconfig.h" +#include "rtpaddress.h" +#include "rtptypes.h" + +namespace qrtplib +{ + +class RTPMemoryManager; + +/** Represents an IPv4 IP address and port. + * This class is used by the UDP over IPv4 transmission component. + * When an RTPIPv4Address is used in one of the multicast functions of the transmitter, the port + * number is ignored. When an instance is used in one of the accept or ignore functions of the + * transmitter, a zero port number represents all ports for the specified IP address. + */ +class JRTPLIB_IMPORTEXPORT RTPIPv4Address : public RTPAddress +{ +public: + /** Creates an instance with IP address \c ip and port number \c port (both + * are interpreted in host byte order), and possibly sets the RTCP multiplex flag + * (see RTPIPv4Address::UseRTCPMultiplexingOnTransmission). */ + RTPIPv4Address(uint32_t ip = 0, uint16_t port = 0,bool rtcpmux = false):RTPAddress(IPv4Address) + { + RTPIPv4Address::ip = ip; + RTPIPv4Address::port = port; + if (rtcpmux) + rtcpsendport = port; + else + rtcpsendport = port+1; + } + + /** Creates an instance with IP address \c ip and port number \c port (both + * are interpreted in host byte order), and sets a specific port to + * send RTCP packets to (see RTPIPv4Address::GetRTCPSendPort). */ + RTPIPv4Address(uint32_t ip, uint16_t port, uint16_t rtcpsendport):RTPAddress(IPv4Address) + { + RTPIPv4Address::ip = ip; + RTPIPv4Address::port = port; + RTPIPv4Address::rtcpsendport = rtcpsendport; + } + + /** Creates an instance with IP address \c ip and port number \c port (\c port is + * interpreted in host byte order) and possibly sets the RTCP multiplex flag + * (see RTPIPv4Address::UseRTCPMultiplexingOnTransmission). */ + RTPIPv4Address(const uint8_t ip[4],uint16_t port = 0,bool rtcpmux = false):RTPAddress(IPv4Address) + { + RTPIPv4Address::ip = (uint32_t)ip[3]; + RTPIPv4Address::ip |= (((uint32_t)ip[2])<<8); + RTPIPv4Address::ip |= (((uint32_t)ip[1])<<16); + RTPIPv4Address::ip |= (((uint32_t)ip[0])<<24); + + RTPIPv4Address::port = port; + if (rtcpmux) + rtcpsendport = port; + else + rtcpsendport = port+1; + } + + /** Creates an instance with IP address \c ip and port number \c port (both + * are interpreted in host byte order), and sets a specific port to + * send RTCP packets to (see RTPIPv4Address::GetRTCPSendPort). */ + RTPIPv4Address(const uint8_t ip[4],uint16_t port,uint16_t rtcpsendport):RTPAddress(IPv4Address) + { + RTPIPv4Address::ip = (uint32_t)ip[3]; + RTPIPv4Address::ip |= (((uint32_t)ip[2])<<8); + RTPIPv4Address::ip |= (((uint32_t)ip[1])<<16); + RTPIPv4Address::ip |= (((uint32_t)ip[0])<<24); + + RTPIPv4Address::port = port; + RTPIPv4Address::rtcpsendport = rtcpsendport; + } + + ~RTPIPv4Address() { } + + /** Sets the IP address for this instance to \c ip which is assumed to be in host byte order. */ + void SetIP(uint32_t ip) { RTPIPv4Address::ip = ip; } + + /** Sets the IP address of this instance to \c ip. */ + void SetIP(const uint8_t ip[4]) { RTPIPv4Address::ip = (uint32_t)ip[3]; RTPIPv4Address::ip |= (((uint32_t)ip[2])<<8); RTPIPv4Address::ip |= (((uint32_t)ip[1])<<16); RTPIPv4Address::ip |= (((uint32_t)ip[0])<<24); } + + /** Sets the port number for this instance to \c port which is interpreted in host byte order. */ + void SetPort(uint16_t port) { RTPIPv4Address::port = port; } + + /** Returns the IP address contained in this instance in host byte order. */ + uint32_t GetIP() const { return ip; } + + /** Returns the port number of this instance in host byte order. */ + uint16_t GetPort() const { return port; } + + /** For outgoing packets, this indicates to which port RTCP packets will be sent (can, + * be the same port as the RTP packets in case RTCP multiplexing is used). */ + uint16_t GetRTCPSendPort() const { return rtcpsendport; } + + RTPAddress *CreateCopy(RTPMemoryManager *mgr) const; + + // Note that these functions are only used for received packets, and for those + // the rtcpsendport variable is not important and should be ignored. + bool IsSameAddress(const RTPAddress *addr) const; + bool IsFromSameHost(const RTPAddress *addr) const; +private: + uint32_t ip; + uint16_t port; + uint16_t rtcpsendport; +}; + +} // end namespace + +#endif // RTPIPV4ADDRESS_H + diff --git a/qrtplib/rtpipv4destination.cpp b/qrtplib/rtpipv4destination.cpp new file mode 100644 index 000000000..acfd786a1 --- /dev/null +++ b/qrtplib/rtpipv4destination.cpp @@ -0,0 +1,51 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2011 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpipv4destination.h" +#include "rtpinternalutils.h" + +namespace qrtplib +{ + +std::string RTPIPv4Destination::GetDestinationString() const +{ + char str[24]; + uint32_t ip = GetIP(); + uint16_t portbase = ntohs(GetRTPPort_NBO()); + + RTP_SNPRINTF(str,24,"%d.%d.%d.%d:%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF),(int)(portbase)); + return std::string(str); +} + +} // end namespace + + diff --git a/qrtplib/rtpipv4destination.h b/qrtplib/rtpipv4destination.h new file mode 100644 index 000000000..457735ca8 --- /dev/null +++ b/qrtplib/rtpipv4destination.h @@ -0,0 +1,118 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpipv4destination.h + */ + +#ifndef RTPIPV4DESTINATION_H + +#define RTPIPV4DESTINATION_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtpipv4address.h" +#ifndef RTP_SOCKETTYPE_WINSOCK + #include + #include + #include +#endif // RTP_SOCKETTYPE_WINSOCK +#include +#include + +namespace qrtplib +{ + +class JRTPLIB_IMPORTEXPORT RTPIPv4Destination +{ +public: + RTPIPv4Destination() + { + ip = 0; + memset(&rtpaddr,0,sizeof(struct sockaddr_in)); + memset(&rtcpaddr,0,sizeof(struct sockaddr_in)); + } + + RTPIPv4Destination(uint32_t ip,uint16_t rtpport,uint16_t rtcpport) + { + memset(&rtpaddr,0,sizeof(struct sockaddr_in)); + memset(&rtcpaddr,0,sizeof(struct sockaddr_in)); + + rtpaddr.sin_family = AF_INET; + rtpaddr.sin_port = htons(rtpport); + rtpaddr.sin_addr.s_addr = htonl(ip); + + rtcpaddr.sin_family = AF_INET; + rtcpaddr.sin_port = htons(rtcpport); + rtcpaddr.sin_addr.s_addr = htonl(ip); + + RTPIPv4Destination::ip = ip; + } + + bool operator==(const RTPIPv4Destination &src) const + { + if (rtpaddr.sin_addr.s_addr == src.rtpaddr.sin_addr.s_addr && rtpaddr.sin_port == src.rtpaddr.sin_port) + return true; + return false; + } + uint32_t GetIP() const { return ip; } + // nbo = network byte order + uint32_t GetIP_NBO() const { return rtpaddr.sin_addr.s_addr; } + uint16_t GetRTPPort_NBO() const { return rtpaddr.sin_port; } + uint16_t GetRTCPPort_NBO() const { return rtcpaddr.sin_port; } + const struct sockaddr_in *GetRTPSockAddr() const { return &rtpaddr; } + const struct sockaddr_in *GetRTCPSockAddr() const { return &rtcpaddr; } + std::string GetDestinationString() const; + + static bool AddressToDestination(const RTPAddress &addr, RTPIPv4Destination &dest) + { + if (addr.GetAddressType() != RTPAddress::IPv4Address) + return false; + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + uint16_t rtpport = address.GetPort(); + uint16_t rtcpport = address.GetRTCPSendPort(); + + dest = RTPIPv4Destination(address.GetIP(),rtpport,rtcpport); + return true; + } + +private: + uint32_t ip; + struct sockaddr_in rtpaddr; + struct sockaddr_in rtcpaddr; +}; + +} // end namespace + +#endif // RTPIPV4DESTINATION_H + diff --git a/qrtplib/rtpipv6address.cpp b/qrtplib/rtpipv6address.cpp new file mode 100644 index 000000000..84c0ab796 --- /dev/null +++ b/qrtplib/rtpipv6address.cpp @@ -0,0 +1,91 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpipv6address.h" +#include "rtpmemorymanager.h" + +#ifdef RTP_SUPPORT_IPV6 + + +namespace qrtplib +{ + +RTPAddress *RTPIPv6Address::CreateCopy(RTPMemoryManager *mgr) const +{ + JRTPLIB_UNUSED(mgr); // possibly unused + RTPIPv6Address *newaddr = RTPNew(mgr,RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv6Address(ip,port); + return newaddr; +} + +bool RTPIPv6Address::IsSameAddress(const RTPAddress *addr) const +{ + if (addr == 0) + return false; + if (addr->GetAddressType() != RTPAddress::IPv6Address) + return false; + + const RTPIPv6Address *addr2 = (const RTPIPv6Address *)addr; + const uint8_t *ip2 = addr2->ip.s6_addr; + + if (port != addr2->port) + return false; + + for (int i = 0 ; i < 16 ; i++) + { + if (ip.s6_addr[i] != ip2[i]) + return false; + } + return true; +} + +bool RTPIPv6Address::IsFromSameHost(const RTPAddress *addr) const +{ + if (addr == 0) + return false; + if (addr->GetAddressType() != RTPAddress::IPv6Address) + return false; + + const RTPIPv6Address *addr2 = (const RTPIPv6Address *)addr; + const uint8_t *ip2 = addr2->ip.s6_addr; + for (int i = 0 ; i < 16 ; i++) + { + if (ip.s6_addr[i] != ip2[i]) + return false; + } + return true; +} + + +} // end namespace + +#endif // RTP_SUPPORT_IPV6 + diff --git a/qrtplib/rtpipv6address.h b/qrtplib/rtpipv6address.h new file mode 100644 index 000000000..c40edff64 --- /dev/null +++ b/qrtplib/rtpipv6address.h @@ -0,0 +1,108 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpipv6address.h + */ + +#ifndef RTPIPV6ADDRESS_H + +#define RTPIPV6ADDRESS_H + +#include "rtpconfig.h" + +#ifdef RTP_SUPPORT_IPV6 + +#include "rtpaddress.h" +#include "rtptypes.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN + +namespace qrtplib +{ + +/** Represents an IPv6 IP address and port. + * This class is used by the UDP over IPv4 transmission component. + * When an RTPIPv6Address is used in one of the multicast functions of the + * transmitter, the port number is ignored. When an instance is used in one of + * the accept or ignore functions of the transmitter, a zero port number represents + * all ports for the specified IP address. + */ +class JRTPLIB_IMPORTEXPORT RTPIPv6Address : public RTPAddress +{ +public: + /** Creates an instance with IP address and port number set to zero. */ + RTPIPv6Address():RTPAddress(IPv6Address) { for (int i = 0 ; i < 16 ; i++) ip.s6_addr[i] = 0; port = 0; } + + /** Creates an instance with IP address \c ip and port number \c port (the port number is assumed to be in + * host byte order). */ + RTPIPv6Address(const uint8_t ip[16],uint16_t port = 0):RTPAddress(IPv6Address) { SetIP(ip); RTPIPv6Address::port = port; } + + /** Creates an instance with IP address \c ip and port number \c port (the port number is assumed to be in + * host byte order). */ + RTPIPv6Address(in6_addr ip,uint16_t port = 0):RTPAddress(IPv6Address) { RTPIPv6Address::ip = ip; RTPIPv6Address::port = port; } + ~RTPIPv6Address() { } + + /** Sets the IP address for this instance to \c ip. */ + void SetIP(in6_addr ip) { RTPIPv6Address::ip = ip; } + + /** Sets the IP address for this instance to \c ip. */ + void SetIP(const uint8_t ip[16]) { for (int i = 0 ; i < 16 ; i++) RTPIPv6Address::ip.s6_addr[i] = ip[i]; } + + /** Sets the port number for this instance to \c port, which is interpreted in host byte order. */ + void SetPort(uint16_t port) { RTPIPv6Address::port = port; } + + /** Copies the IP address of this instance in \c ip. */ + void GetIP(uint8_t ip[16]) const { for (int i = 0 ; i < 16 ; i++) ip[i] = RTPIPv6Address::ip.s6_addr[i]; } + + /** Returns the IP address of this instance. */ + in6_addr GetIP() const { return ip; } + + /** Returns the port number contained in this instance in host byte order. */ + uint16_t GetPort() const { return port; } + + RTPAddress *CreateCopy(RTPMemoryManager *mgr) const; + bool IsSameAddress(const RTPAddress *addr) const; + bool IsFromSameHost(const RTPAddress *addr) const; + +private: + in6_addr ip; + uint16_t port; +}; + +} // end namespace + +#endif // RTP_SUPPORT_IPV6 + +#endif // RTPIPV6ADDRESS_H + diff --git a/qrtplib/rtpipv6destination.cpp b/qrtplib/rtpipv6destination.cpp new file mode 100644 index 000000000..d52c02e83 --- /dev/null +++ b/qrtplib/rtpipv6destination.cpp @@ -0,0 +1,61 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2011 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpipv6destination.h" + +#ifdef RTP_SUPPORT_IPV6 + +#include "rtpinternalutils.h" + +namespace qrtplib +{ + +std::string RTPIPv6Destination::GetDestinationString() const +{ + uint16_t ip16[8]; + char str[48]; + uint16_t portbase = ntohs(rtpaddr.sin6_port); + int i,j; + for (i = 0,j = 0 ; j < 8 ; j++,i += 2) + { + ip16[j] = (((uint16_t)rtpaddr.sin6_addr.s6_addr[i])<<8); + ip16[j] |= ((uint16_t)rtpaddr.sin6_addr.s6_addr[i+1]); + } + RTP_SNPRINTF(str,48,"%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X/%d",(int)ip16[0],(int)ip16[1],(int)ip16[2],(int)ip16[3],(int)ip16[4],(int)ip16[5],(int)ip16[6],(int)ip16[7],(int)portbase); + return std::string(str); +} + +} // end namespace + +#endif // RTP_SUPPORT_IPV6 + + diff --git a/qrtplib/rtpipv6destination.h b/qrtplib/rtpipv6destination.h new file mode 100644 index 000000000..c04808a35 --- /dev/null +++ b/qrtplib/rtpipv6destination.h @@ -0,0 +1,91 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpipv6destination.h + */ + +#ifndef RTPIPV6DESTINATION_H + +#define RTPIPV6DESTINATION_H + +#include "rtpconfig.h" + +#ifdef RTP_SUPPORT_IPV6 + +#include "rtptypes.h" +#include +#include +#ifndef RTP_SOCKETTYPE_WINSOCK + #include + #include + #include +#endif // RTP_SOCKETTYPE_WINSOCK + +namespace qrtplib +{ + +class JRTPLIB_IMPORTEXPORT RTPIPv6Destination +{ +public: + RTPIPv6Destination(in6_addr ip,uint16_t portbase) + { + memset(&rtpaddr,0,sizeof(struct sockaddr_in6)); + memset(&rtcpaddr,0,sizeof(struct sockaddr_in6)); + rtpaddr.sin6_family = AF_INET6; + rtpaddr.sin6_port = htons(portbase); + rtpaddr.sin6_addr = ip; + rtcpaddr.sin6_family = AF_INET6; + rtcpaddr.sin6_port = htons(portbase+1); + rtcpaddr.sin6_addr = ip; + } + in6_addr GetIP() const { return rtpaddr.sin6_addr; } + bool operator==(const RTPIPv6Destination &src) const + { + if (rtpaddr.sin6_port == src.rtpaddr.sin6_port && (memcmp(&(src.rtpaddr.sin6_addr),&(rtpaddr.sin6_addr),sizeof(in6_addr)) == 0)) + return true; + return false; + } + const struct sockaddr_in6 *GetRTPSockAddr() const { return &rtpaddr; } + const struct sockaddr_in6 *GetRTCPSockAddr() const { return &rtcpaddr; } + std::string GetDestinationString() const; +private: + struct sockaddr_in6 rtpaddr; + struct sockaddr_in6 rtcpaddr; +}; + +} // end namespace + +#endif // RTP_SUPPORT_IPV6 + +#endif // RTPIPV6DESTINATION_H + diff --git a/qrtplib/rtpkeyhashtable.h b/qrtplib/rtpkeyhashtable.h new file mode 100644 index 000000000..e6be72ee1 --- /dev/null +++ b/qrtplib/rtpkeyhashtable.h @@ -0,0 +1,310 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpkeyhashtable.h + */ + +#ifndef RTPKEYHASHTABLE_H + +#define RTPKEYHASHTABLE_H + +#include "rtpconfig.h" +#include "rtperrors.h" +#include "rtpmemoryobject.h" + +namespace qrtplib +{ + +template +class RTPKeyHashTable : public RTPMemoryObject +{ +public: + RTPKeyHashTable(RTPMemoryManager *mgr = 0,int memtype = RTPMEM_TYPE_OTHER); + ~RTPKeyHashTable() { Clear(); } + + void GotoFirstElement() { curhashelem = firsthashelem; } + void GotoLastElement() { curhashelem = lasthashelem; } + bool HasCurrentElement() { return (curhashelem == 0)?false:true; } + int DeleteCurrentElement(); + Element &GetCurrentElement() { return curhashelem->GetElement(); } + Key &GetCurrentKey() { return curhashelem->GetKey(); } + int GotoElement(const Key &k); + bool HasElement(const Key &k); + void GotoNextElement(); + void GotoPreviousElement(); + void Clear(); + + int AddElement(const Key &k,const Element &elem); + int DeleteElement(const Key &k); + +private: + class HashElement + { + public: + HashElement(const Key &k,const Element &e,int index):key(k),element(e) { hashprev = 0; hashnext = 0; listnext = 0; listprev = 0; hashindex = index; } + int GetHashIndex() { return hashindex; } + Key &GetKey() { return key; } + Element &GetElement() { return element; } + + private: + int hashindex; + Key key; + Element element; + public: + HashElement *hashprev,*hashnext; + HashElement *listprev,*listnext; + }; + + HashElement *table[hashsize]; + HashElement *firsthashelem,*lasthashelem; + HashElement *curhashelem; +#ifdef RTP_SUPPORT_MEMORYMANAGEMENT + int memorytype; +#endif // RTP_SUPPORT_MEMORYMANAGEMENT +}; + +template +inline RTPKeyHashTable::RTPKeyHashTable(RTPMemoryManager *mgr,int memtype) : RTPMemoryObject(mgr) +{ + JRTPLIB_UNUSED(memtype); // possibly unused + + for (int i = 0 ; i < hashsize ; i++) + table[i] = 0; + firsthashelem = 0; + lasthashelem = 0; +#ifdef RTP_SUPPORT_MEMORYMANAGEMENT + memorytype = memtype; +#endif // RTP_SUPPORT_MEMORYMANAGEMENT +} + +template +inline int RTPKeyHashTable::DeleteCurrentElement() +{ + if (curhashelem) + { + HashElement *tmp1,*tmp2; + int index; + + // First, relink elements in current hash bucket + + index = curhashelem->GetHashIndex(); + tmp1 = curhashelem->hashprev; + tmp2 = curhashelem->hashnext; + if (tmp1 == 0) // no previous element in hash bucket + { + table[index] = tmp2; + if (tmp2 != 0) + tmp2->hashprev = 0; + } + else // there is a previous element in the hash bucket + { + tmp1->hashnext = tmp2; + if (tmp2 != 0) + tmp2->hashprev = tmp1; + } + + // Relink elements in list + + tmp1 = curhashelem->listprev; + tmp2 = curhashelem->listnext; + if (tmp1 == 0) // curhashelem is first in list + { + firsthashelem = tmp2; + if (tmp2 != 0) + tmp2->listprev = 0; + else // curhashelem is also last in list + lasthashelem = 0; + } + else + { + tmp1->listnext = tmp2; + if (tmp2 != 0) + tmp2->listprev = tmp1; + else // curhashelem is last in list + lasthashelem = tmp1; + } + + // finally, with everything being relinked, we can delete curhashelem + RTPDelete(curhashelem,GetMemoryManager()); + curhashelem = tmp2; // Set to next element in list + } + else + return ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT; + return 0; +} + +template +inline int RTPKeyHashTable::GotoElement(const Key &k) +{ + int index; + bool found; + + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + + curhashelem = table[index]; + found = false; + while(!found && curhashelem != 0) + { + if (curhashelem->GetKey() == k) + found = true; + else + curhashelem = curhashelem->hashnext; + } + if (!found) + return ERR_RTP_KEYHASHTABLE_KEYNOTFOUND; + return 0; +} + +template +inline bool RTPKeyHashTable::HasElement(const Key &k) +{ + int index; + bool found; + HashElement *tmp; + + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return false; + + tmp = table[index]; + found = false; + while(!found && tmp != 0) + { + if (tmp->GetKey() == k) + found = true; + else + tmp = tmp->hashnext; + } + return found; +} + +template +inline void RTPKeyHashTable::GotoNextElement() +{ + if (curhashelem) + curhashelem = curhashelem->listnext; +} + +template +inline void RTPKeyHashTable::GotoPreviousElement() +{ + if (curhashelem) + curhashelem = curhashelem->listprev; +} + +template +inline void RTPKeyHashTable::Clear() +{ + HashElement *tmp1,*tmp2; + + for (int i = 0 ; i < hashsize ; i++) + table[i] = 0; + + tmp1 = firsthashelem; + while (tmp1 != 0) + { + tmp2 = tmp1->listnext; + RTPDelete(tmp1,GetMemoryManager()); + tmp1 = tmp2; + } + firsthashelem = 0; + lasthashelem = 0; +} + +template +inline int RTPKeyHashTable::AddElement(const Key &k,const Element &elem) +{ + int index; + bool found; + HashElement *e,*newelem; + + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + + e = table[index]; + found = false; + while(!found && e != 0) + { + if (e->GetKey() == k) + found = true; + else + e = e->hashnext; + } + if (found) + return ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS; + + // Okay, the key doesn't exist, so we can add the new element in the hash table + + newelem = RTPNew(GetMemoryManager(),memorytype) HashElement(k,elem,index); + if (newelem == 0) + return ERR_RTP_OUTOFMEM; + + e = table[index]; + table[index] = newelem; + newelem->hashnext = e; + if (e != 0) + e->hashprev = newelem; + + // Now, we still got to add it to the linked list + + if (firsthashelem == 0) + { + firsthashelem = newelem; + lasthashelem = newelem; + } + else // there already are some elements in the list + { + lasthashelem->listnext = newelem; + newelem->listprev = lasthashelem; + lasthashelem = newelem; + } + return 0; +} + +template +inline int RTPKeyHashTable::DeleteElement(const Key &k) +{ + int status; + + status = GotoElement(k); + if (status < 0) + return status; + return DeleteCurrentElement(); +} + +} // end namespace + +#endif // RTPKEYHASHTABLE_H diff --git a/qrtplib/rtplibraryversion.cpp b/qrtplib/rtplibraryversion.cpp new file mode 100644 index 000000000..f282c8df4 --- /dev/null +++ b/qrtplib/rtplibraryversion.cpp @@ -0,0 +1,57 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtplibraryversion.h" +#include "rtpdefines.h" +#include "rtplibraryversioninternal.h" +#include "rtpinternalutils.h" +#include + +namespace qrtplib +{ + +RTPLibraryVersion RTPLibraryVersion::GetVersion() +{ + return RTPLibraryVersion(JRTPLIB_VERSION_MAJOR, JRTPLIB_VERSION_MINOR, JRTPLIB_VERSION_DEBUG); +} + +std::string RTPLibraryVersion::GetVersionString() const +{ + char str[16]; + + RTP_SNPRINTF(str,16,"%d.%d.%d",majornr,minornr,debugnr); + + return std::string(str); +} + +} // end namespace + diff --git a/qrtplib/rtplibraryversion.h b/qrtplib/rtplibraryversion.h new file mode 100644 index 000000000..d26d6273a --- /dev/null +++ b/qrtplib/rtplibraryversion.h @@ -0,0 +1,77 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtplibraryversion.h + */ + +#ifndef RTPLIBRARYVERSION_H + +#define RTPLIBRARYVERSION_H + +#include "rtpconfig.h" +#include +#include + +namespace qrtplib +{ + +/** + * Used to provide information about the version of the library. + */ +class JRTPLIB_IMPORTEXPORT RTPLibraryVersion +{ +public: + /** Returns an instance of RTPLibraryVersion describing the version of the library. */ + static RTPLibraryVersion GetVersion(); +private: + RTPLibraryVersion(int major,int minor,int debug) { majornr = major; minornr = minor; debugnr = debug; } +public: + /** Returns the major version number. */ + int GetMajorNumber() const { return majornr; } + + /** Returns the minor version number. */ + int GetMinorNumber() const { return minornr; } + + /** Returns the debug version number. */ + int GetDebugNumber() const { return debugnr; } + + /** Returns a string describing the library version. */ + std::string GetVersionString() const; +private: + int debugnr,minornr,majornr; +}; + +} // end namespace + +#endif // RTPLIBRARYVERSION_H + diff --git a/qrtplib/rtplibraryversioninternal.h b/qrtplib/rtplibraryversioninternal.h new file mode 100644 index 000000000..03e3560f4 --- /dev/null +++ b/qrtplib/rtplibraryversioninternal.h @@ -0,0 +1,46 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtplibraryversioninternal.h + */ + +#ifndef RTPLIBRARYVERSIONINTERNAL_H + +#define RTPLIBRARYVERSIONINTERNAL_H + +#define JRTPLIB_VERSION_MAJOR 3 +#define JRTPLIB_VERSION_MINOR 11 +#define JRTPLIB_VERSION_DEBUG 1 + +#endif // RTPLIBRARYVERSIONINTERNAL_H + diff --git a/qrtplib/rtpmemorymanager.h b/qrtplib/rtpmemorymanager.h new file mode 100644 index 000000000..f677a8b93 --- /dev/null +++ b/qrtplib/rtpmemorymanager.h @@ -0,0 +1,245 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpmemorymanager.h + */ + +#ifndef RTPMEMORYMANAGER_H + +#define RTPMEMORYMANAGER_H + +#include "rtpconfig.h" +#include "rtptypes.h" + +/** Used to indicate a general kind of memory block. */ +#define RTPMEM_TYPE_OTHER 0 + +/** Buffer to store an incoming RTP packet. */ +#define RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET 1 + +/** Buffer to store an incoming RTCP packet. */ +#define RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET 2 + +/** Buffer to store an RTCP APP packet. */ +#define RTPMEM_TYPE_BUFFER_RTCPAPPPACKET 3 + +/** Buffer to store an RTCP BYE packet. */ +#define RTPMEM_TYPE_BUFFER_RTCPBYEPACKET 4 + +/** Buffer to store a BYE reason. */ +#define RTPMEM_TYPE_BUFFER_RTCPBYEREASON 5 + +/** Buffer to store an RTCP compound packet. */ +#define RTPMEM_TYPE_BUFFER_RTCPCOMPOUNDPACKET 6 + +/** Buffer to store an SDES block. */ +#define RTPMEM_TYPE_BUFFER_RTCPSDESBLOCK 7 + +/** Buffer to store an RTP packet. */ +#define RTPMEM_TYPE_BUFFER_RTPPACKET 8 + +/** Buffer used by an RTPPacketBuilder instance. */ +#define RTPMEM_TYPE_BUFFER_RTPPACKETBUILDERBUFFER 9 + +/** Buffer to store an SDES item. */ +#define RTPMEM_TYPE_BUFFER_SDESITEM 10 + +/** Hash element used in the accept/ignore table. */ +#define RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT 11 + +/** Buffer to store a PortInfo instance, used by the UDP over IPv4 and IPv6 transmitters. */ +#define RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO 12 + +/** Buffer to store a HashElement instance for the destination hash table. */ +#define RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT 13 + +/** Buffer to store a HashElement instance for the multicast hash table. */ +#define RTPMEM_TYPE_CLASS_MULTICASTHASHELEMENT 14 + +/** Buffer to store an instance of RTCPAPPPacket. */ +#define RTPMEM_TYPE_CLASS_RTCPAPPPACKET 15 + +/** Buffer to store an instance of RTCPBYEPacket. */ +#define RTPMEM_TYPE_CLASS_RTCPBYEPACKET 16 + +/** Buffer to store an instance of RTCPCompoundPacketBuilder. */ +#define RTPMEM_TYPE_CLASS_RTCPCOMPOUNDPACKETBUILDER 17 + +/** Buffer to store an RTCPReceiverReport instance. */ +#define RTPMEM_TYPE_CLASS_RTCPRECEIVERREPORT 18 + +/** Buffer to store an instance of RTCPRRPacket. */ +#define RTPMEM_TYPE_CLASS_RTCPRRPACKET 19 + +/** Buffer to store an instance of RTCPSDESPacket. */ +#define RTPMEM_TYPE_CLASS_RTCPSDESPACKET 20 + +/** Buffer to store an instance of RTCPSRPacket. */ +#define RTPMEM_TYPE_CLASS_RTCPSRPACKET 21 + +/** Buffer to store an instance of RTCPUnknownPacket. */ +#define RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET 22 + +/** Buffer to store an instance of an RTPAddress derived class. */ +#define RTPMEM_TYPE_CLASS_RTPADDRESS 23 + +/** Buffer to store an instance of RTPInternalSourceData. */ +#define RTPMEM_TYPE_CLASS_RTPINTERNALSOURCEDATA 24 + +/** Buffer to store an RTPPacket instance. */ +#define RTPMEM_TYPE_CLASS_RTPPACKET 25 + +/** Buffer to store an RTPPollThread instance. */ +#define RTPMEM_TYPE_CLASS_RTPPOLLTHREAD 26 + +/** Buffer to store an RTPRawPacket instance. */ +#define RTPMEM_TYPE_CLASS_RTPRAWPACKET 27 + +/** Buffer to store an RTPTransmissionInfo derived class. */ +#define RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO 28 + +/** Buffer to store an RTPTransmitter derived class. */ +#define RTPMEM_TYPE_CLASS_RTPTRANSMITTER 29 + +/** Buffer to store an SDESPrivateItem instance. */ +#define RTPMEM_TYPE_CLASS_SDESPRIVATEITEM 30 + +/** Buffer to store an SDESSource instance. */ +#define RTPMEM_TYPE_CLASS_SDESSOURCE 31 + +/** Buffer to store a HashElement instance for the source table. */ +#define RTPMEM_TYPE_CLASS_SOURCETABLEHASHELEMENT 32 + +/** Buffer that's used when encrypting a packet. */ +#define RTPMEM_TYPE_BUFFER_SRTPDATA 33 + +namespace qrtplib +{ + +/** A memory manager. */ +class JRTPLIB_IMPORTEXPORT RTPMemoryManager +{ +public: + RTPMemoryManager() { } + virtual ~RTPMemoryManager() { } + + /** Called to allocate \c numbytes of memory. + * Called to allocate \c numbytes of memory. The \c memtype parameter + * indicates what the purpose of the memory block is. Relevant values + * can be found in rtpmemorymanager.h . Note that the types starting with + * \c RTPMEM_TYPE_CLASS indicate fixed size buffers and that types starting + * with \c RTPMEM_TYPE_BUFFER indicate variable size buffers. + */ + virtual void *AllocateBuffer(size_t numbytes, int memtype) = 0; + + /** Frees the previously allocated memory block \c buffer */ + virtual void FreeBuffer(void *buffer) = 0; +}; + +} // end namespace + +#ifdef RTP_SUPPORT_MEMORYMANAGEMENT + +#include + +inline void *operator new(size_t numbytes, qrtplib::RTPMemoryManager *mgr, int memtype) +{ + if (mgr == 0) + return operator new(numbytes); + return mgr->AllocateBuffer(numbytes,memtype); +} + +inline void operator delete(void *buffer, qrtplib::RTPMemoryManager *mgr, int memtype) +{ + JRTPLIB_UNUSED(memtype); + if (mgr == 0) + operator delete(buffer); + else + mgr->FreeBuffer(buffer); +} + +#ifdef RTP_HAVE_ARRAYALLOC +inline void *operator new[](size_t numbytes, qrtplib::RTPMemoryManager *mgr, int memtype) +{ + if (mgr == 0) + return operator new[](numbytes); + return mgr->AllocateBuffer(numbytes,memtype); +} + +inline void operator delete[](void *buffer, qrtplib::RTPMemoryManager *mgr, int memtype) +{ + JRTPLIB_UNUSED(memtype); + if (mgr == 0) + operator delete[](buffer); + else + mgr->FreeBuffer(buffer); +} +#endif // RTP_HAVE_ARRAYALLOC + +namespace qrtplib +{ + +inline void RTPDeleteByteArray(uint8_t *buf, RTPMemoryManager *mgr) +{ + if (mgr == 0) + delete [] buf; + else + mgr->FreeBuffer(buf); +} + +template +inline void RTPDelete(ClassName *obj, RTPMemoryManager *mgr) +{ + if (mgr == 0) + delete obj; + else + { + obj->~ClassName(); + mgr->FreeBuffer(obj); + } +} + +} // end namespace + +#define RTPNew(a,b) new(a,b) + +#else + +#define RTPNew(a,b) new +#define RTPDelete(a,b) delete a +#define RTPDeleteByteArray(a,b) delete [] a; + +#endif // RTP_SUPPORT_MEMORYMANAGEMENT + +#endif // RTPMEMORYMANAGER_H + diff --git a/qrtplib/rtpmemoryobject.h b/qrtplib/rtpmemoryobject.h new file mode 100644 index 000000000..1053639d1 --- /dev/null +++ b/qrtplib/rtpmemoryobject.h @@ -0,0 +1,74 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpmemoryobject.h + */ + +#ifndef RTPMEMORYOBJECT_H + +#define RTPMEMORYOBJECT_H + +#include "rtpconfig.h" +#include "rtpmemorymanager.h" + +namespace qrtplib +{ + +class JRTPLIB_IMPORTEXPORT RTPMemoryObject +{ +protected: +#ifdef RTP_SUPPORT_MEMORYMANAGEMENT + RTPMemoryObject(RTPMemoryManager *memmgr) : mgr(memmgr) { } +#else + RTPMemoryObject(RTPMemoryManager *memmgr) { JRTPLIB_UNUSED(memmgr); } +#endif // RTP_SUPPORT_MEMORYMANAGEMENT + virtual ~RTPMemoryObject() { } + +#ifdef RTP_SUPPORT_MEMORYMANAGEMENT + RTPMemoryManager *GetMemoryManager() const { return mgr; } + void SetMemoryManager(RTPMemoryManager *m) { mgr = m; } +#else + RTPMemoryManager *GetMemoryManager() const { return 0; } + void SetMemoryManager(RTPMemoryManager *m) { JRTPLIB_UNUSED(m); } +#endif // RTP_SUPPORT_MEMORYMANAGEMENT + +#ifdef RTP_SUPPORT_MEMORYMANAGEMENT +private: + RTPMemoryManager *mgr; +#endif // RTP_SUPPORT_MEMORYMANAGEMENT +}; + +} // end namespace + +#endif // RTPMEMORYOBJECT_H + diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp new file mode 100644 index 000000000..ed31a36ac --- /dev/null +++ b/qrtplib/rtppacket.cpp @@ -0,0 +1,317 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtppacket.h" +#include "rtpstructs.h" +#include "rtpdefines.h" +#include "rtperrors.h" +#include "rtprawpacket.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN +#include + +namespace qrtplib +{ + +void RTPPacket::Clear() +{ + hasextension = false; + hasmarker = false; + numcsrcs = 0; + payloadtype = 0; + extseqnr = 0; + timestamp = 0; + ssrc = 0; + packet = 0; + payload = 0; + packetlength = 0; + payloadlength = 0; + extid = 0; + extension = 0; + extensionlength = 0; + error = 0; + externalbuffer = false; +} + +RTPPacket::RTPPacket(RTPRawPacket &rawpack,RTPMemoryManager *mgr) : RTPMemoryObject(mgr),receivetime(rawpack.GetReceiveTime()) +{ + Clear(); + error = ParseRawPacket(rawpack); +} + +RTPPacket::RTPPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, + uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, + bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, + size_t maxpacksize, RTPMemoryManager *mgr) : RTPMemoryObject(mgr),receivetime(0,0) +{ + Clear(); + error = BuildPacket(payloadtype,payloaddata,payloadlen,seqnr,timestamp,ssrc,gotmarker,numcsrcs, + csrcs,gotextension,extensionid,extensionlen_numwords,extensiondata,0,maxpacksize); +} + +RTPPacket::RTPPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, + uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, + bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, + void *buffer,size_t buffersize, RTPMemoryManager *mgr) : RTPMemoryObject(mgr),receivetime(0,0) +{ + Clear(); + if (buffer == 0) + error = ERR_RTP_PACKET_EXTERNALBUFFERNULL; + else if (buffersize <= 0) + error = ERR_RTP_PACKET_ILLEGALBUFFERSIZE; + else + error = BuildPacket(payloadtype,payloaddata,payloadlen,seqnr,timestamp,ssrc,gotmarker,numcsrcs, + csrcs,gotextension,extensionid,extensionlen_numwords,extensiondata,buffer,buffersize); +} + +int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) +{ + uint8_t *packetbytes; + size_t packetlen; + uint8_t payloadtype; + RTPHeader *rtpheader; + bool marker; + int csrccount; + bool hasextension; + int payloadoffset,payloadlength; + int numpadbytes; + RTPExtensionHeader *rtpextheader; + + if (!rawpack.IsRTP()) // If we didn't receive it on the RTP port, we'll ignore it + return ERR_RTP_PACKET_INVALIDPACKET; + + // The length should be at least the size of the RTP header + packetlen = rawpack.GetDataLength(); + if (packetlen < sizeof(RTPHeader)) + return ERR_RTP_PACKET_INVALIDPACKET; + + packetbytes = (uint8_t *)rawpack.GetData(); + rtpheader = (RTPHeader *)packetbytes; + + // The version number should be correct + if (rtpheader->version != RTP_VERSION) + return ERR_RTP_PACKET_INVALIDPACKET; + + // We'll check if this is possibly a RTCP packet. For this to be possible + // the marker bit and payload type combined should be either an SR or RR + // identifier + marker = (rtpheader->marker == 0)?false:true; + payloadtype = rtpheader->payloadtype; + if (marker) + { + if (payloadtype == (RTP_RTCPTYPE_SR & 127)) // don't check high bit (this was the marker!!) + return ERR_RTP_PACKET_INVALIDPACKET; + if (payloadtype == (RTP_RTCPTYPE_RR & 127)) + return ERR_RTP_PACKET_INVALIDPACKET; + } + + csrccount = rtpheader->csrccount; + payloadoffset = sizeof(RTPHeader)+(int)(csrccount*sizeof(uint32_t)); + + if (rtpheader->padding) // adjust payload length to take padding into account + { + numpadbytes = (int)packetbytes[packetlen-1]; // last byte contains number of padding bytes + if (numpadbytes <= 0) + return ERR_RTP_PACKET_INVALIDPACKET; + } + else + numpadbytes = 0; + + hasextension = (rtpheader->extension == 0)?false:true; + if (hasextension) // got header extension + { + rtpextheader = (RTPExtensionHeader *)(packetbytes+payloadoffset); + payloadoffset += sizeof(RTPExtensionHeader); + + uint16_t exthdrlen = ntohs(rtpextheader->length); + payloadoffset += ((int)exthdrlen)*sizeof(uint32_t); + } + else + { + rtpextheader = 0; + } + + payloadlength = packetlen-numpadbytes-payloadoffset; + if (payloadlength < 0) + return ERR_RTP_PACKET_INVALIDPACKET; + + // Now, we've got a valid packet, so we can create a new instance of RTPPacket + // and fill in the members + + RTPPacket::hasextension = hasextension; + if (hasextension) + { + RTPPacket::extid = ntohs(rtpextheader->extid); + RTPPacket::extensionlength = ((int)ntohs(rtpextheader->length))*sizeof(uint32_t); + RTPPacket::extension = ((uint8_t *)rtpextheader)+sizeof(RTPExtensionHeader); + } + + RTPPacket::hasmarker = marker; + RTPPacket::numcsrcs = csrccount; + RTPPacket::payloadtype = payloadtype; + + // Note: we don't fill in the EXTENDED sequence number here, since we + // don't have information about the source here. We just fill in the low + // 16 bits + RTPPacket::extseqnr = (uint32_t)ntohs(rtpheader->sequencenumber); + + RTPPacket::timestamp = ntohl(rtpheader->timestamp); + RTPPacket::ssrc = ntohl(rtpheader->ssrc); + RTPPacket::packet = packetbytes; + RTPPacket::payload = packetbytes+payloadoffset; + RTPPacket::packetlength = packetlen; + RTPPacket::payloadlength = payloadlength; + + // We'll zero the data of the raw packet, since we're using it here now! + rawpack.ZeroData(); + + return 0; +} + +uint32_t RTPPacket::GetCSRC(int num) const +{ + if (num >= numcsrcs) + return 0; + + uint8_t *csrcpos; + uint32_t *csrcval_nbo; + uint32_t csrcval_hbo; + + csrcpos = packet+sizeof(RTPHeader)+num*sizeof(uint32_t); + csrcval_nbo = (uint32_t *)csrcpos; + csrcval_hbo = ntohl(*csrcval_nbo); + return csrcval_hbo; +} + +int RTPPacket::BuildPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, + uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, + bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, + void *buffer,size_t maxsize) +{ + if (numcsrcs > RTP_MAXCSRCS) + return ERR_RTP_PACKET_TOOMANYCSRCS; + + if (payloadtype > 127) // high bit should not be used + return ERR_RTP_PACKET_BADPAYLOADTYPE; + if (payloadtype == 72 || payloadtype == 73) // could cause confusion with rtcp types + return ERR_RTP_PACKET_BADPAYLOADTYPE; + + packetlength = sizeof(RTPHeader); + packetlength += sizeof(uint32_t)*((size_t)numcsrcs); + if (gotextension) + { + packetlength += sizeof(RTPExtensionHeader); + packetlength += sizeof(uint32_t)*((size_t)extensionlen_numwords); + } + packetlength += payloadlen; + + if (maxsize > 0 && packetlength > maxsize) + { + packetlength = 0; + return ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE; + } + + // Ok, now we'll just fill in... + + RTPHeader *rtphdr; + + if (buffer == 0) + { + packet = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTPPACKET) uint8_t [packetlength]; + if (packet == 0) + { + packetlength = 0; + return ERR_RTP_OUTOFMEM; + } + externalbuffer = false; + } + else + { + packet = (uint8_t *)buffer; + externalbuffer = true; + } + + RTPPacket::hasmarker = gotmarker; + RTPPacket::hasextension = gotextension; + RTPPacket::numcsrcs = numcsrcs; + RTPPacket::payloadtype = payloadtype; + RTPPacket::extseqnr = (uint32_t)seqnr; + RTPPacket::timestamp = timestamp; + RTPPacket::ssrc = ssrc; + RTPPacket::payloadlength = payloadlen; + RTPPacket::extid = extensionid; + RTPPacket::extensionlength = ((size_t)extensionlen_numwords)*sizeof(uint32_t); + + rtphdr = (RTPHeader *)packet; + rtphdr->version = RTP_VERSION; + rtphdr->padding = 0; + if (gotmarker) + rtphdr->marker = 1; + else + rtphdr->marker = 0; + if (gotextension) + rtphdr->extension = 1; + else + rtphdr->extension = 0; + rtphdr->csrccount = numcsrcs; + rtphdr->payloadtype = payloadtype&127; // make sure high bit isn't set + rtphdr->sequencenumber = htons(seqnr); + rtphdr->timestamp = htonl(timestamp); + rtphdr->ssrc = htonl(ssrc); + + uint32_t *curcsrc; + int i; + + curcsrc = (uint32_t *)(packet+sizeof(RTPHeader)); + for (i = 0 ; i < numcsrcs ; i++,curcsrc++) + *curcsrc = htonl(csrcs[i]); + + payload = packet+sizeof(RTPHeader)+((size_t)numcsrcs)*sizeof(uint32_t); + if (gotextension) + { + RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *)payload; + + rtpexthdr->extid = htons(extensionid); + rtpexthdr->length = htons((uint16_t)extensionlen_numwords); + + payload += sizeof(RTPExtensionHeader); + memcpy(payload,extensiondata,RTPPacket::extensionlength); + + payload += RTPPacket::extensionlength; + } + memcpy(payload,payloaddata,payloadlen); + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtppacket.h b/qrtplib/rtppacket.h new file mode 100644 index 000000000..b620f0642 --- /dev/null +++ b/qrtplib/rtppacket.h @@ -0,0 +1,180 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtppacket.h + */ + +#ifndef RTPPACKET_H + +#define RTPPACKET_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtptimeutilities.h" +#include "rtpmemoryobject.h" + +namespace qrtplib +{ + +class RTPRawPacket; + +/** Represents an RTP Packet. + * The RTPPacket class can be used to parse a RTPRawPacket instance if it represents RTP data. + * The class can also be used to create a new RTP packet according to the parameters specified by + * the user. + */ +class JRTPLIB_IMPORTEXPORT RTPPacket : public RTPMemoryObject +{ +public: + /** Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. + * Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. + * If successful, the data is moved from the raw packet to the RTPPacket instance. + */ + RTPPacket(RTPRawPacket &rawpack,RTPMemoryManager *mgr = 0); + + /** Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. + * Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. + * If \c maxpacksize is not equal to zero, an error is generated if the total packet size would exceed + * \c maxpacksize. The arguments of the constructor are self-explanatory. Note that the size of a header + * extension is specified in a number of 32-bit words. A memory manager can be installed. + */ + RTPPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, + uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, + bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, + size_t maxpacksize, RTPMemoryManager *mgr = 0); + + /** This constructor is similar to the other constructor, but here data is stored in an external buffer + * \c buffer with size \c buffersize. */ + RTPPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, + uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, + bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, + void *buffer,size_t buffersize,RTPMemoryManager *mgr = 0); + + virtual ~RTPPacket() { if (packet && !externalbuffer) RTPDeleteByteArray(packet,GetMemoryManager()); } + + /** If an error occurred in one of the constructors, this function returns the error code. */ + int GetCreationError() const { return error; } + + /** Returns \c true if the RTP packet has a header extension and \c false otherwise. */ + bool HasExtension() const { return hasextension; } + + /** Returns \c true if the marker bit was set and \c false otherwise. */ + bool HasMarker() const { return hasmarker; } + + /** Returns the number of CSRCs contained in this packet. */ + int GetCSRCCount() const { return numcsrcs; } + + /** Returns a specific CSRC identifier. + * Returns a specific CSRC identifier. The parameter \c num can go from 0 to GetCSRCCount()-1. + */ + uint32_t GetCSRC(int num) const; + + /** Returns the payload type of the packet. */ + uint8_t GetPayloadType() const { return payloadtype; } + + /** Returns the extended sequence number of the packet. + * Returns the extended sequence number of the packet. When the packet is just received, + * only the low $16$ bits will be set. The high 16 bits can be filled in later. + */ + uint32_t GetExtendedSequenceNumber() const { return extseqnr; } + + /** Returns the sequence number of this packet. */ + uint16_t GetSequenceNumber() const { return (uint16_t)(extseqnr&0x0000FFFF); } + + /** Sets the extended sequence number of this packet to \c seq. */ + void SetExtendedSequenceNumber(uint32_t seq) { extseqnr = seq; } + + /** Returns the timestamp of this packet. */ + uint32_t GetTimestamp() const { return timestamp; } + + /** Returns the SSRC identifier stored in this packet. */ + uint32_t GetSSRC() const { return ssrc; } + + /** Returns a pointer to the data of the entire packet. */ + uint8_t *GetPacketData() const { return packet; } + + /** Returns a pointer to the actual payload data. */ + uint8_t *GetPayloadData() const { return payload; } + + /** Returns the length of the entire packet. */ + size_t GetPacketLength() const { return packetlength; } + + /** Returns the payload length. */ + size_t GetPayloadLength() const { return payloadlength; } + + /** If a header extension is present, this function returns the extension identifier. */ + uint16_t GetExtensionID() const { return extid; } + + /** Returns the length of the header extension data. */ + uint8_t *GetExtensionData() const { return extension; } + + /** Returns the length of the header extension data. */ + size_t GetExtensionLength() const { return extensionlength; } + + /** Returns the time at which this packet was received. + * When an RTPPacket instance is created from an RTPRawPacket instance, the raw packet's + * reception time is stored in the RTPPacket instance. This function then retrieves that + * time. + */ + RTPTime GetReceiveTime() const { return receivetime; } +private: + void Clear(); + int ParseRawPacket(RTPRawPacket &rawpack); + int BuildPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, + uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, + bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, + void *buffer,size_t maxsize); + + int error; + + bool hasextension,hasmarker; + int numcsrcs; + + uint8_t payloadtype; + uint32_t extseqnr,timestamp,ssrc; + uint8_t *packet,*payload; + size_t packetlength,payloadlength; + + uint16_t extid; + uint8_t *extension; + size_t extensionlength; + + bool externalbuffer; + + RTPTime receivetime; +}; + +} // end namespace + +#endif // RTPPACKET_H + diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib/rtppacketbuilder.cpp new file mode 100644 index 000000000..39b38ce97 --- /dev/null +++ b/qrtplib/rtppacketbuilder.cpp @@ -0,0 +1,267 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtppacketbuilder.h" +#include "rtperrors.h" +#include "rtppacket.h" +#include "rtpsources.h" +#include +#include + +namespace qrtplib +{ + +RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r,RTPMemoryManager *mgr) : RTPMemoryObject(mgr),rtprnd(r),lastwallclocktime(0,0) +{ + init = false; + timeinit.Dummy(); + + //std::cout << (void *)(&rtprnd) << std::endl; +} + +RTPPacketBuilder::~RTPPacketBuilder() +{ + Destroy(); +} + +int RTPPacketBuilder::Init(size_t max) +{ + if (init) + return ERR_RTP_PACKBUILD_ALREADYINIT; + if (max <= 0) + return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; + + maxpacksize = max; + buffer = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTPPACKETBUILDERBUFFER) uint8_t [max]; + if (buffer == 0) + return ERR_RTP_OUTOFMEM; + packetlength = 0; + + CreateNewSSRC(); + + deftsset = false; + defptset = false; + defmarkset = false; + + numcsrcs = 0; + + init = true; + return 0; +} + +void RTPPacketBuilder::Destroy() +{ + if (!init) + return; + RTPDeleteByteArray(buffer,GetMemoryManager()); + init = false; +} + +int RTPPacketBuilder::SetMaximumPacketSize(size_t max) +{ + uint8_t *newbuf; + + if (max <= 0) + return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; + newbuf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTPPACKETBUILDERBUFFER) uint8_t[max]; + if (newbuf == 0) + return ERR_RTP_OUTOFMEM; + + RTPDeleteByteArray(buffer,GetMemoryManager()); + buffer = newbuf; + maxpacksize = max; + return 0; +} + +int RTPPacketBuilder::AddCSRC(uint32_t csrc) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (numcsrcs >= RTP_MAXCSRCS) + return ERR_RTP_PACKBUILD_CSRCLISTFULL; + + int i; + + for (i = 0 ; i < numcsrcs ; i++) + { + if (csrcs[i] == csrc) + return ERR_RTP_PACKBUILD_CSRCALREADYINLIST; + } + csrcs[numcsrcs] = csrc; + numcsrcs++; + return 0; +} + +int RTPPacketBuilder::DeleteCSRC(uint32_t csrc) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + + int i = 0; + bool found = false; + + while (!found && i < numcsrcs) + { + if (csrcs[i] == csrc) + found = true; + else + i++; + } + + if (!found) + return ERR_RTP_PACKBUILD_CSRCNOTINLIST; + + // move the last csrc in the place of the deleted one + numcsrcs--; + if (numcsrcs > 0 && numcsrcs != i) + csrcs[i] = csrcs[numcsrcs]; + return 0; +} + +void RTPPacketBuilder::ClearCSRCList() +{ + if (!init) + return; + numcsrcs = 0; +} + +uint32_t RTPPacketBuilder::CreateNewSSRC() +{ + ssrc = rtprnd.GetRandom32(); + timestamp = rtprnd.GetRandom32(); + seqnr = rtprnd.GetRandom16(); + + // p 38: the count SHOULD be reset if the sender changes its SSRC identifier + numpayloadbytes = 0; + numpackets = 0; + return ssrc; +} + +uint32_t RTPPacketBuilder::CreateNewSSRC(RTPSources &sources) +{ + bool found; + + do + { + ssrc = rtprnd.GetRandom32(); + found = sources.GotEntry(ssrc); + } while (found); + + timestamp = rtprnd.GetRandom32(); + seqnr = rtprnd.GetRandom16(); + + // p 38: the count SHOULD be reset if the sender changes its SSRC identifier + numpayloadbytes = 0; + numpackets = 0; + return ssrc; +} + +int RTPPacketBuilder::BuildPacket(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!defptset) + return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; + if (!defmarkset) + return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,false); +} + +int RTPPacketBuilder::BuildPacket(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + return PrivateBuildPacket(data,len,pt,mark,timestampinc,false); +} + +int RTPPacketBuilder::BuildPacketEx(const void *data,size_t len, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!defptset) + return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; + if (!defmarkset) + return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,true,hdrextID,hdrextdata,numhdrextwords); +} + +int RTPPacketBuilder::BuildPacketEx(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + return PrivateBuildPacket(data,len,pt,mark,timestampinc,true,hdrextID,hdrextdata,numhdrextwords); + +} + +int RTPPacketBuilder::PrivateBuildPacket(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc,bool gotextension, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +{ + RTPPacket p(pt,data,len,seqnr,timestamp,ssrc,mark,numcsrcs,csrcs,gotextension,hdrextID, + (uint16_t)numhdrextwords,hdrextdata,buffer,maxpacksize,GetMemoryManager()); + int status = p.GetCreationError(); + + if (status < 0) + return status; + packetlength = p.GetPacketLength(); + + if (numpackets == 0) // first packet + { + lastwallclocktime = RTPTime::CurrentTime(); + lastrtptimestamp = timestamp; + prevrtptimestamp = timestamp; + } + else if (timestamp != prevrtptimestamp) + { + lastwallclocktime = RTPTime::CurrentTime(); + lastrtptimestamp = timestamp; + prevrtptimestamp = timestamp; + } + + numpayloadbytes += (uint32_t)p.GetPayloadLength(); + numpackets++; + timestamp += timestampinc; + seqnr++; + + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib/rtppacketbuilder.h new file mode 100644 index 000000000..c9498d5a6 --- /dev/null +++ b/qrtplib/rtppacketbuilder.h @@ -0,0 +1,274 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtppacketbuilder.h + */ + +#ifndef RTPPACKETBUILDER_H + +#define RTPPACKETBUILDER_H + +#include "rtpconfig.h" +#include "rtperrors.h" +#include "rtpdefines.h" +#include "rtprandom.h" +#include "rtptimeutilities.h" +#include "rtptypes.h" +#include "rtpmemoryobject.h" + +namespace qrtplib +{ + +class RTPSources; + +/** This class can be used to build RTP packets and is a bit more high-level than the RTPPacket + * class: it generates an SSRC identifier, keeps track of timestamp and sequence number etc. + */ +class JRTPLIB_IMPORTEXPORT RTPPacketBuilder : public RTPMemoryObject +{ +public: + /** Constructs an instance which will use \c rtprand for generating random numbers + * (used to initialize the SSRC value and sequence number), optionally installing a memory manager. + **/ + RTPPacketBuilder(RTPRandom &rtprand, RTPMemoryManager *mgr = 0); + ~RTPPacketBuilder(); + + /** Initializes the builder to only allow packets with a size below \c maxpacksize. */ + int Init(size_t maxpacksize); + + /** Cleans up the builder. */ + void Destroy(); + + /** Returns the number of packets which have been created with the current SSRC identifier. */ + uint32_t GetPacketCount() { if (!init) return 0; return numpackets; } + + /** Returns the number of payload octets which have been generated with this SSRC identifier. */ + uint32_t GetPayloadOctetCount() { if (!init) return 0; return numpayloadbytes; } + + /** Sets the maximum allowed packet size to \c maxpacksize. */ + int SetMaximumPacketSize(size_t maxpacksize); + + /** Adds a CSRC to the CSRC list which will be stored in the RTP packets. */ + int AddCSRC(uint32_t csrc); + + /** Deletes a CSRC from the list which will be stored in the RTP packets. */ + int DeleteCSRC(uint32_t csrc); + + /** Clears the CSRC list. */ + void ClearCSRCList(); + + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type, marker + * and timestamp increment used will be those that have been set using the \c SetDefault + * functions below. + */ + int BuildPacket(const void *data,size_t len); + + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type will be + * set to \c pt, the marker bit to \c mark and after building this packet, the timestamp will + * be incremented with \c timestamp. + */ + int BuildPacket(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc); + + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type, marker + * and timestamp increment used will be those that have been set using the \c SetDefault + * functions below. This packet will also contain an RTP header extension with identifier + * \c hdrextID and data \c hdrextdata. The length of the header extension data is given by + * \c numhdrextwords which expresses the length in a number of 32-bit words. + */ + int BuildPacketEx(const void *data,size_t len, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords); + + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type will be set + * to \c pt, the marker bit to \c mark and after building this packet, the timestamp will + * be incremented with \c timestamp. This packet will also contain an RTP header extension + * with identifier \c hdrextID and data \c hdrextdata. The length of the header extension + * data is given by \c numhdrextwords which expresses the length in a number of 32-bit words. + */ + int BuildPacketEx(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords); + + /** Returns a pointer to the last built RTP packet data. */ + uint8_t *GetPacket() { if (!init) return 0; return buffer; } + + /** Returns the size of the last built RTP packet. */ + size_t GetPacketLength() { if (!init) return 0; return packetlength; } + + /** Sets the default payload type to \c pt. */ + int SetDefaultPayloadType(uint8_t pt); + + /** Sets the default marker bit to \c m. */ + int SetDefaultMark(bool m); + + /** Sets the default timestamp increment to \c timestampinc. */ + int SetDefaultTimestampIncrement(uint32_t timestampinc); + + /** This function increments the timestamp with the amount given by \c inc. + * This function increments the timestamp with the amount given by \c inc. This can be useful + * if, for example, a packet was not sent because it contained only silence. Then, this function + * should be called to increment the timestamp with the appropriate amount so that the next packets + * will still be played at the correct time at other hosts. + */ + int IncrementTimestamp(uint32_t inc); + + /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. + * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. This can be useful if, for example, a packet was not sent because it contained only silence. + * Then, this function should be called to increment the timestamp with the appropriate amount so that the next + * packets will still be played at the correct time at other hosts. + */ + int IncrementTimestampDefault(); + + /** Creates a new SSRC to be used in generated packets. + * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and + * sequence number offsets. + */ + uint32_t CreateNewSSRC(); + + /** Creates a new SSRC to be used in generated packets. + * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and + * sequence number offsets. The source table \c sources is used to make sure that the chosen SSRC + * isn't used by another participant yet. + */ + uint32_t CreateNewSSRC(RTPSources &sources); + + /** Returns the current SSRC identifier. */ + uint32_t GetSSRC() const { if (!init) return 0; return ssrc; } + + /** Returns the current RTP timestamp. */ + uint32_t GetTimestamp() const { if (!init) return 0; return timestamp; } + + /** Returns the current sequence number. */ + uint16_t GetSequenceNumber() const { if (!init) return 0; return seqnr; } + + /** Returns the time at which a packet was generated. + * Returns the time at which a packet was generated. This is not necessarily the time at which + * the last RTP packet was generated: if the timestamp increment was zero, the time is not updated. + */ + RTPTime GetPacketTime() const { if (!init) return RTPTime(0,0); return lastwallclocktime; } + + /** Returns the RTP timestamp which corresponds to the time returned by the previous function. */ + uint32_t GetPacketTimestamp() const { if (!init) return 0; return lastrtptimestamp; } + + /** Sets a specific SSRC to be used. + * Sets a specific SSRC to be used. Does not create a new timestamp offset or sequence number + * offset. Does not reset the packet count or byte count. Think twice before using this! + */ + void AdjustSSRC(uint32_t s) { ssrc = s; } +private: + int PrivateBuildPacket(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc,bool gotextension, + uint16_t hdrextID = 0,const void *hdrextdata = 0,size_t numhdrextwords = 0); + + RTPRandom &rtprnd; + size_t maxpacksize; + uint8_t *buffer; + size_t packetlength; + + uint32_t numpayloadbytes; + uint32_t numpackets; + bool init; + + uint32_t ssrc; + uint32_t timestamp; + uint16_t seqnr; + + uint32_t defaulttimestampinc; + uint8_t defaultpayloadtype; + bool defaultmark; + + bool deftsset,defptset,defmarkset; + + uint32_t csrcs[RTP_MAXCSRCS]; + int numcsrcs; + + RTPTime lastwallclocktime; + uint32_t lastrtptimestamp; + uint32_t prevrtptimestamp; +}; + +inline int RTPPacketBuilder::SetDefaultPayloadType(uint8_t pt) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + defptset = true; + defaultpayloadtype = pt; + return 0; +} + +inline int RTPPacketBuilder::SetDefaultMark(bool m) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + defmarkset = true; + defaultmark = m; + return 0; +} + +inline int RTPPacketBuilder::SetDefaultTimestampIncrement(uint32_t timestampinc) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + deftsset = true; + defaulttimestampinc = timestampinc; + return 0; +} + +inline int RTPPacketBuilder::IncrementTimestamp(uint32_t inc) +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + timestamp += inc; + return 0; +} + +inline int RTPPacketBuilder::IncrementTimestampDefault() +{ + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + timestamp += defaulttimestampinc; + return 0; +} + +} // end namespace + +#endif // RTPPACKETBUILDER_H + diff --git a/qrtplib/rtppollthread.cpp b/qrtplib/rtppollthread.cpp new file mode 100644 index 000000000..76defe99b --- /dev/null +++ b/qrtplib/rtppollthread.cpp @@ -0,0 +1,171 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtppollthread.h" + +#ifdef RTP_SUPPORT_THREAD + +#include "rtpsession.h" +#include "rtcpscheduler.h" +#include "rtperrors.h" +#include "rtprawpacket.h" +#include +#include + +namespace qrtplib +{ + +RTPPollThread::RTPPollThread(RTPSession &session,RTCPScheduler &sched):rtpsession(session),rtcpsched(sched) +{ + stop = false; + transmitter = 0; + timeinit.Dummy(); +} + +RTPPollThread::~RTPPollThread() +{ + Stop(); +} + +int RTPPollThread::Start(RTPTransmitter *trans) +{ + if (JThread::IsRunning()) + return ERR_RTP_POLLTHREAD_ALREADYRUNNING; + + transmitter = trans; + if (!stopmutex.IsInitialized()) + { + if (stopmutex.Init() < 0) + return ERR_RTP_POLLTHREAD_CANTINITMUTEX; + } + stop = false; + if (JThread::Start() < 0) + return ERR_RTP_POLLTHREAD_CANTSTARTTHREAD; + return 0; +} + +void RTPPollThread::Stop() +{ + if (!IsRunning()) + return; + + stopmutex.Lock(); + stop = true; + stopmutex.Unlock(); + + if (transmitter) + transmitter->AbortWait(); + + RTPTime thetime = RTPTime::CurrentTime(); + bool done = false; + + while (JThread::IsRunning() && !done) + { + // wait max 5 sec + RTPTime curtime = RTPTime::CurrentTime(); + if ((curtime.GetDouble()-thetime.GetDouble()) > 5.0) + done = true; + RTPTime::Wait(RTPTime(0,10000)); + } + + if (JThread::IsRunning()) + { + std::cerr << "RTPPollThread: Warning! Having to kill thread!" << std::endl; + JThread::Kill(); + } + stop = false; + transmitter = 0; +} + +void *RTPPollThread::Thread() +{ + JThread::ThreadStarted(); + + bool stopthread; + + stopmutex.Lock(); + stopthread = stop; + stopmutex.Unlock(); + + rtpsession.OnPollThreadStart(stopthread); + + while (!stopthread) + { + int status; + + rtpsession.schedmutex.Lock(); + rtpsession.sourcesmutex.Lock(); + + RTPTime rtcpdelay = rtcpsched.GetTransmissionDelay(); + + rtpsession.sourcesmutex.Unlock(); + rtpsession.schedmutex.Unlock(); + + if ((status = transmitter->WaitForIncomingData(rtcpdelay)) < 0) + { + stopthread = true; + rtpsession.OnPollThreadError(status); + } + else + { + if ((status = transmitter->Poll()) < 0) + { + stopthread = true; + rtpsession.OnPollThreadError(status); + } + else + { + if ((status = rtpsession.ProcessPolledData()) < 0) + { + stopthread = true; + rtpsession.OnPollThreadError(status); + } + else + { + rtpsession.OnPollThreadStep(); + stopmutex.Lock(); + stopthread = stop; + stopmutex.Unlock(); + } + } + } + } + + rtpsession.OnPollThreadStop(); + + return 0; +} + +} // end namespace + +#endif // RTP_SUPPORT_THREAD + diff --git a/qrtplib/rtppollthread.h b/qrtplib/rtppollthread.h new file mode 100644 index 000000000..3864e15dd --- /dev/null +++ b/qrtplib/rtppollthread.h @@ -0,0 +1,79 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtppollthread.h + */ + +#ifndef RTPPOLLTHREAD_H + +#define RTPPOLLTHREAD_H + +#include "rtpconfig.h" + +#ifdef RTP_SUPPORT_THREAD + +#include "rtptransmitter.h" + +#include +#include +#include + +namespace qrtplib +{ + +class RTPSession; +class RTCPScheduler; + +class JRTPLIB_IMPORTEXPORT RTPPollThread : private jthread::JThread +{ +public: + RTPPollThread(RTPSession &session,RTCPScheduler &rtcpsched); + ~RTPPollThread(); + int Start(RTPTransmitter *trans); + void Stop(); +private: + void *Thread(); + + bool stop; + jthread::JMutex stopmutex; + RTPTransmitter *transmitter; + + RTPSession &rtpsession; + RTCPScheduler &rtcpsched; +}; + +} // end namespace + +#endif // RTP_SUPPORT_THREAD + +#endif // RTPPOLLTHREAD_H diff --git a/qrtplib/rtprandom.cpp b/qrtplib/rtprandom.cpp new file mode 100644 index 000000000..6bdcfa206 --- /dev/null +++ b/qrtplib/rtprandom.cpp @@ -0,0 +1,105 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#if defined(WIN32) && !defined(_WIN32_WCE) + #define _CRT_RAND_S +#endif // WIN32 || _WIN32_WCE + +#include "rtprandom.h" +#include "rtprandomrands.h" +#include "rtprandomurandom.h" +#include "rtprandomrand48.h" +#include +#ifndef WIN32 + #include +#else + #ifndef _WIN32_WCE + #include + #else + #include + #include + #endif // _WIN32_WINCE + #include +#endif // WIN32 + +namespace qrtplib +{ + +uint32_t RTPRandom::PickSeed() +{ + uint32_t x; +#if defined(WIN32) || defined(_WIN32_WINCE) +#ifndef _WIN32_WCE + x = (uint32_t)_getpid(); + x += (uint32_t)time(0); + x += (uint32_t)clock(); +#else + x = (uint32_t)GetCurrentProcessId(); + + FILETIME ft; + SYSTEMTIME st; + + GetSystemTime(&st); + SystemTimeToFileTime(&st,&ft); + + x += ft.dwLowDateTime; +#endif // _WIN32_WCE + x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); +#else + x = (uint32_t)getpid(); + x += (uint32_t)time(0); + x += (uint32_t)clock(); + x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); +#endif + return x; +} + +RTPRandom *RTPRandom::CreateDefaultRandomNumberGenerator() +{ +#ifdef RTP_HAVE_RAND_S + RTPRandomRandS *r = new RTPRandomRandS(); +#else + RTPRandomURandom *r = new RTPRandomURandom(); +#endif // RTP_HAVE_RAND_S + RTPRandom *rRet = r; + + if (r->Init() < 0) // fall back to rand48 + { + delete r; + rRet = new RTPRandomRand48(); + } + + return rRet; +} + +} // end namespace + diff --git a/qrtplib/rtprandom.h b/qrtplib/rtprandom.h new file mode 100644 index 000000000..c3778a97c --- /dev/null +++ b/qrtplib/rtprandom.h @@ -0,0 +1,79 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtprandom.h + */ + +#ifndef RTPRANDOM_H + +#define RTPRANDOM_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include + +#define RTPRANDOM_2POWMIN63 1.08420217248550443400745280086994171142578125e-19 + +namespace qrtplib +{ + +/** Interface for generating random numbers. */ +class JRTPLIB_IMPORTEXPORT RTPRandom +{ +public: + RTPRandom() { } + virtual ~RTPRandom() { } + + /** Returns a random eight bit value. */ + virtual uint8_t GetRandom8() = 0; + + /** Returns a random sixteen bit value. */ + virtual uint16_t GetRandom16() = 0; + + /** Returns a random thirty-two bit value. */ + virtual uint32_t GetRandom32() = 0; + + /** Returns a random number between $0.0$ and $1.0$. */ + virtual double GetRandomDouble() = 0; + + /** Can be used by subclasses to generate a seed for a random number generator. */ + uint32_t PickSeed(); + + /** Allocate a default random number generator based on your platform. */ + static RTPRandom *CreateDefaultRandomNumberGenerator(); +}; + +} // end namespace + +#endif // RTPRANDOM_H + diff --git a/qrtplib/rtprandomrand48.cpp b/qrtplib/rtprandomrand48.cpp new file mode 100644 index 000000000..0def976a2 --- /dev/null +++ b/qrtplib/rtprandomrand48.cpp @@ -0,0 +1,125 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtprandomrand48.h" + +namespace qrtplib +{ + +RTPRandomRand48::RTPRandomRand48() +{ + SetSeed(PickSeed()); +} + +RTPRandomRand48::RTPRandomRand48(uint32_t seed) +{ + SetSeed(seed); +} + +RTPRandomRand48::~RTPRandomRand48() +{ +} + +void RTPRandomRand48::SetSeed(uint32_t seed) +{ +#ifdef RTP_SUPPORT_THREAD + mutex.Init(); // TODO: check error! +#endif // RTP_SUPPORT_THREAD + +#ifdef RTP_HAVE_VSUINT64SUFFIX + state = ((uint64_t)seed) << 16 | 0x330Eui64; +#else + state = ((uint64_t)seed) << 16 | 0x330EULL; +#endif // RTP_HAVE_VSUINT64SUFFIX +} + +uint8_t RTPRandomRand48::GetRandom8() +{ + uint32_t x = ((GetRandom32() >> 24)&0xff); + + return (uint8_t)x; +} + +uint16_t RTPRandomRand48::GetRandom16() +{ + uint32_t x = ((GetRandom32() >> 16)&0xffff); + + return (uint16_t)x; +} + +uint32_t RTPRandomRand48::GetRandom32() +{ +#ifdef RTP_SUPPORT_THREAD + mutex.Lock(); +#endif // RTP_SUPPORT_THREAD + +#ifdef RTP_HAVE_VSUINT64SUFFIX + state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; + + uint32_t x = (uint32_t)((state>>16)&0xffffffffui64); +#else + state = ((0x5DEECE66DULL*state) + 0xBULL)&0x0000ffffffffffffULL; + + uint32_t x = (uint32_t)((state>>16)&0xffffffffULL); +#endif // RTP_HAVE_VSUINT64SUFFIX + +#ifdef RTP_SUPPORT_THREAD + mutex.Unlock(); +#endif // RTP_SUPPORT_THREAD + return x; +} + +double RTPRandomRand48::GetRandomDouble() +{ +#ifdef RTP_SUPPORT_THREAD + mutex.Lock(); +#endif // RTP_SUPPORT_THREAD + +#ifdef RTP_HAVE_VSUINT64SUFFIX + state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; + + int64_t x = (int64_t)state; +#else + state = ((0x5DEECE66DULL*state) + 0xBULL)&0x0000ffffffffffffULL; + + int64_t x = (int64_t)state; +#endif // RTP_HAVE_VSUINT64SUFFIX + +#ifdef RTP_SUPPORT_THREAD + mutex.Unlock(); +#endif // RTP_SUPPORT_THREAD + double y = 3.552713678800500929355621337890625e-15 * (double)x; + return y; +} + +} // end namespace + diff --git a/qrtplib/rtprandomrand48.h b/qrtplib/rtprandomrand48.h new file mode 100644 index 000000000..ef9c61790 --- /dev/null +++ b/qrtplib/rtprandomrand48.h @@ -0,0 +1,75 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtprandomrand48.h + */ + +#ifndef RTPRANDOMRAND48_H + +#define RTPRANDOMRAND48_H + +#include "rtpconfig.h" +#include "rtprandom.h" +#ifdef RTP_SUPPORT_THREAD + #include +#endif // RTP_SUPPORT_THREAD +#include + +namespace qrtplib +{ + +/** A random number generator using the algorithm of the rand48 set of functions. */ +class JRTPLIB_IMPORTEXPORT RTPRandomRand48 : public RTPRandom +{ +public: + RTPRandomRand48(); + RTPRandomRand48(uint32_t seed); + ~RTPRandomRand48(); + + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); +private: + void SetSeed(uint32_t seed); + +#ifdef RTP_SUPPORT_THREAD + jthread::JMutex mutex; +#endif // RTP_SUPPORT_THREAD + uint64_t state; +}; + +} // end namespace + +#endif // RTPRANDOMRAND48_H + diff --git a/qrtplib/rtprandomrands.cpp b/qrtplib/rtprandomrands.cpp new file mode 100644 index 000000000..8d937ed66 --- /dev/null +++ b/qrtplib/rtprandomrands.cpp @@ -0,0 +1,196 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpconfig.h" +#ifdef RTP_HAVE_RAND_S + #define _CRT_RAND_S +#endif // RTP_HAVE_RAND_S + +#include "rtprandomrands.h" +#include "rtperrors.h" +#include +#include + +// If compiling on VC 8 or later for full Windows, we'll attempt to use rand_s, +// which generates better random numbers. However, its only supported on Windows XP, +// Windows Server 2003, and later, so we'll do a run-time check to see if we can +// use it (it won't work on Windows 2000 SP4 for example). + +namespace qrtplib +{ + +#ifndef RTP_HAVE_RAND_S + +RTPRandomRandS::RTPRandomRandS() +{ + initialized = false; +} + +RTPRandomRandS::~RTPRandomRandS() +{ +} + +int RTPRandomRandS::Init() +{ + return ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED; +} + +uint8_t RTPRandomRandS::GetRandom8() +{ + return 0; +} + +uint16_t RTPRandomRandS::GetRandom16() +{ + return 0; +} + +uint32_t RTPRandomRandS::GetRandom32() +{ + return 0; +} + +double RTPRandomRandS::GetRandomDouble() +{ + return 0; +} + +#else + +RTPRandomRandS::RTPRandomRandS() +{ + initialized = false; +} + +RTPRandomRandS::~RTPRandomRandS() +{ +} + +int RTPRandomRandS::Init() +{ + if (initialized) // doesn't matter then + return 0; + + HMODULE hAdvApi32 = LoadLibrary(TEXT("ADVAPI32.DLL")); + if(hAdvApi32 != NULL) + { + if(NULL != GetProcAddress( hAdvApi32, "SystemFunction036" )) // RtlGenRandom + { + initialized = true; + } + FreeLibrary(hAdvApi32); + hAdvApi32 = NULL; + } + + if (!initialized) + return ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED; + return 0; +} + +// rand_s gives a number between 0 and UINT_MAX. We'll assume that UINT_MAX is at least 8 bits + +uint8_t RTPRandomRandS::GetRandom8() +{ + if (!initialized) + return 0; + + unsigned int r; + + rand_s(&r); + + return (uint8_t)(r&0xff); +} + +uint16_t RTPRandomRandS::GetRandom16() +{ + if (!initialized) + return 0; + + unsigned int r; + int shift = 0; + uint16_t x = 0; + + for (int i = 0 ; i < 2 ; i++, shift += 8) + { + rand_s(&r); + + x ^= ((uint16_t)r << shift); + } + + return x; +} + +uint32_t RTPRandomRandS::GetRandom32() +{ + if (!initialized) + return 0; + + unsigned int r; + int shift = 0; + uint32_t x = 0; + + for (int i = 0 ; i < 4 ; i++, shift += 8) + { + rand_s(&r); + + x ^= ((uint32_t)r << shift); + } + + return x; +} + +double RTPRandomRandS::GetRandomDouble() +{ + if (!initialized) + return 0; + + unsigned int r; + int shift = 0; + uint64_t x = 0; + + for (int i = 0 ; i < 8 ; i++, shift += 8) + { + rand_s(&r); + + x ^= ((uint64_t)r << shift); + } + + x &= 0x7fffffffffffffffULL; + + int64_t x2 = (int64_t)x; + return RTPRANDOM_2POWMIN63*(double)x2; +} + +#endif // RTP_HAVE_RAND_S + +} // end namespace + diff --git a/qrtplib/rtprandomrands.h b/qrtplib/rtprandomrands.h new file mode 100644 index 000000000..58b66e892 --- /dev/null +++ b/qrtplib/rtprandomrands.h @@ -0,0 +1,70 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtprandomrands.h + */ + +#ifndef RTPRANDOMRANDS_H + +#define RTPRANDOMRANDS_H + +#include "rtpconfig.h" +#include "rtprandom.h" + +namespace qrtplib +{ + +/** A random number generator which tries to use the \c rand_s function on the + * Win32 platform. + */ +class JRTPLIB_IMPORTEXPORT RTPRandomRandS : public RTPRandom +{ +public: + RTPRandomRandS(); + ~RTPRandomRandS(); + + /** Initialize the random number generator. */ + int Init(); + + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); +private: + bool initialized; +}; + +} // end namespace + +#endif // RTPRANDOMRANDS_H + diff --git a/qrtplib/rtprandomurandom.cpp b/qrtplib/rtprandomurandom.cpp new file mode 100644 index 000000000..b7b45003a --- /dev/null +++ b/qrtplib/rtprandomurandom.cpp @@ -0,0 +1,129 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtprandomurandom.h" +#include "rtperrors.h" + +namespace qrtplib +{ + +RTPRandomURandom::RTPRandomURandom() +{ + device = 0; +} + +RTPRandomURandom::~RTPRandomURandom() +{ + if (device) + fclose(device); +} + +int RTPRandomURandom::Init() +{ + if (device) + return ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN; + + device = fopen("/dev/urandom","rb"); + if (device == 0) + return ERR_RTP_RTPRANDOMURANDOM_CANTOPEN; + + return 0; +} + +uint8_t RTPRandomURandom::GetRandom8() +{ + if (!device) + return 0; + + uint8_t value; + + if (fread(&value, sizeof(uint8_t), 1, device) != sizeof(uint8_t)) { + return 0; + } + + return value; +} + +uint16_t RTPRandomURandom::GetRandom16() +{ + if (!device) + return 0; + + uint16_t value; + + if (fread(&value, sizeof(uint16_t), 1, device) != sizeof(uint16_t)) { + return 0; + } + + return value; +} + +uint32_t RTPRandomURandom::GetRandom32() +{ + if (!device) + return 0; + + uint32_t value; + + if (fread(&value, sizeof(uint32_t), 1, device) != sizeof(uint32_t)) { + return 0; + } + + return value; +} + +double RTPRandomURandom::GetRandomDouble() +{ + if (!device) + return 0; + + uint64_t value; + + if (fread(&value, sizeof(uint64_t), 1, device) != sizeof(uint64_t)) { + return 0; + } + +#ifdef RTP_HAVE_VSUINT64SUFFIX + value &= 0x7fffffffffffffffui64; +#else + value &= 0x7fffffffffffffffULL; +#endif // RTP_HAVE_VSUINT64SUFFIX + + int64_t value2 = (int64_t)value; + double x = RTPRANDOM_2POWMIN63*(double)value2; + + return x; + +} + +} // end namespace + diff --git a/qrtplib/rtprandomurandom.h b/qrtplib/rtprandomurandom.h new file mode 100644 index 000000000..550492c38 --- /dev/null +++ b/qrtplib/rtprandomurandom.h @@ -0,0 +1,68 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtprandomurandom.h + */ + +#ifndef RTPRANDOMURANDOM_H + +#define RTPRANDOMURANDOM_H + +#include "rtpconfig.h" +#include "rtprandom.h" +#include + +namespace qrtplib +{ + +/** A random number generator which uses bytes delivered by the /dev/urandom device. */ +class JRTPLIB_IMPORTEXPORT RTPRandomURandom : public RTPRandom +{ +public: + RTPRandomURandom(); + ~RTPRandomURandom(); + + /** Initialize the random number generator. */ + int Init(); + + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); +private: + FILE *device; +}; + +} // end namespace + +#endif // RTPRANDOMURANDOM_H diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h new file mode 100644 index 000000000..063564f83 --- /dev/null +++ b/qrtplib/rtprawpacket.h @@ -0,0 +1,160 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtprawpacket.h + */ + +#ifndef RTPRAWPACKET_H + +#define RTPRAWPACKET_H + +#include "rtpconfig.h" +#include "rtptimeutilities.h" +#include "rtpaddress.h" +#include "rtptypes.h" +#include "rtpmemoryobject.h" + +namespace qrtplib +{ + +/** This class is used by the transmission component to store the incoming RTP and RTCP data in. */ +class JRTPLIB_IMPORTEXPORT RTPRawPacket : public RTPMemoryObject +{ +public: + /** Creates an instance which stores data from \c data with length \c datalen. + * Creates an instance which stores data from \c data with length \c datalen. Only the pointer + * to the data is stored, no actual copy is made! The address from which this packet originated + * is set to \c address and the time at which the packet was received is set to \c recvtime. + * The flag which indicates whether this data is RTP or RTCP data is set to \c rtp. A memory + * manager can be installed as well. + */ + RTPRawPacket(uint8_t *data,size_t datalen,RTPAddress *address,RTPTime &recvtime,bool rtp,RTPMemoryManager *mgr = 0); + ~RTPRawPacket(); + + /** Returns the pointer to the data which is contained in this packet. */ + uint8_t *GetData() { return packetdata; } + + /** Returns the length of the packet described by this instance. */ + size_t GetDataLength() const { return packetdatalength; } + + /** Returns the time at which this packet was received. */ + RTPTime GetReceiveTime() const { return receivetime; } + + /** Returns the address stored in this packet. */ + const RTPAddress *GetSenderAddress() const { return senderaddress; } + + /** Returns \c true if this data is RTP data, \c false if it is RTCP data. */ + bool IsRTP() const { return isrtp; } + + /** Sets the pointer to the data stored in this packet to zero. + * Sets the pointer to the data stored in this packet to zero. This will prevent + * a \c delete call for the actual data when the destructor of RTPRawPacket is called. + * This function is used by the RTPPacket and RTCPCompoundPacket classes to obtain + * the packet data (without having to copy it) and to make sure the data isn't deleted + * when the destructor of RTPRawPacket is called. + */ + void ZeroData() { packetdata = 0; packetdatalength = 0; } + + /** Allocates a number of bytes for RTP or RTCP data using the memory manager that + * was used for this raw packet instance, can be useful if the RTPRawPacket::SetData + * function will be used. */ + uint8_t *AllocateBytes(bool isrtp, int recvlen) const; + + /** Deallocates the previously stored data and replaces it with the data that's + * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ + void SetData(uint8_t *data, size_t datalen); + + /** Deallocates the currently stored RTPAddress instance and replaces it + * with the one that's specified (you probably don't need this function). */ + void SetSenderAddress(RTPAddress *address); +private: + void DeleteData(); + + uint8_t *packetdata; + size_t packetdatalength; + RTPTime receivetime; + RTPAddress *senderaddress; + bool isrtp; +}; + +inline RTPRawPacket::RTPRawPacket(uint8_t *data,size_t datalen,RTPAddress *address,RTPTime &recvtime,bool rtp,RTPMemoryManager *mgr):RTPMemoryObject(mgr),receivetime(recvtime) +{ + packetdata = data; + packetdatalength = datalen; + senderaddress = address; + isrtp = rtp; +} + +inline RTPRawPacket::~RTPRawPacket() +{ + DeleteData(); +} + +inline void RTPRawPacket::DeleteData() +{ + if (packetdata) + RTPDeleteByteArray(packetdata,GetMemoryManager()); + if (senderaddress) + RTPDelete(senderaddress,GetMemoryManager()); + + packetdata = 0; + senderaddress = 0; +} + +inline uint8_t *RTPRawPacket::AllocateBytes(bool isrtp, int recvlen) const +{ + JRTPLIB_UNUSED(isrtp); // possibly unused + return RTPNew(GetMemoryManager(),(isrtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[recvlen]; +} + +inline void RTPRawPacket::SetData(uint8_t *data, size_t datalen) +{ + if (packetdata) + RTPDeleteByteArray(packetdata,GetMemoryManager()); + + packetdata = data; + packetdatalength = datalen; +} + +inline void RTPRawPacket::SetSenderAddress(RTPAddress *address) +{ + if (senderaddress) + RTPDelete(senderaddress, GetMemoryManager()); + + senderaddress = address; +} + +} // end namespace + +#endif // RTPRAWPACKET_H + diff --git a/qrtplib/rtpsecuresession.cpp b/qrtplib/rtpsecuresession.cpp new file mode 100644 index 000000000..b2e2228eb --- /dev/null +++ b/qrtplib/rtpsecuresession.cpp @@ -0,0 +1,283 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpsecuresession.h" + +#ifdef RTP_SUPPORT_SRTP + +#include "rtprawpacket.h" +#include +#include +#include +#include + +using namespace std; +using namespace jthread; + +namespace qrtplib +{ + +// SRTP library needs to be initialized already! + +RTPSecureSession::RTPSecureSession(RTPRandom *rnd, RTPMemoryManager *mgr) : RTPSession(rnd, mgr) +{ + // Make sure the OnChange... functions will be called + SetChangeIncomingData(true); + SetChangeOutgoingData(true); + m_pSRTPContext = 0; + m_lastSRTPError = 0; +} + +RTPSecureSession::~RTPSecureSession() +{ + if (m_pSRTPContext) + srtp_dealloc(m_pSRTPContext); +} + +int RTPSecureSession::InitializeSRTPContext() +{ +#ifdef RTP_SUPPORT_THREAD + if (!m_srtpLock.IsInitialized()) + { + if (m_srtpLock.Init() < 0) + return ERR_RTP_SECURESESSION_CANTINITMUTEX; + } + + JMutexAutoLock l(m_srtpLock); +#endif // RTP_SUPPORT_THREAD + + if (m_pSRTPContext) + return ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED; + + err_status_t result = srtp_create(&m_pSRTPContext, NULL); + if (result != err_status_ok) + { + m_pSRTPContext = 0; + m_lastSRTPError = (int)result; + return ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT; + } + + return 0; +} + +int RTPSecureSession::GetLastLibSRTPError() +{ +#ifdef RTP_SUPPORT_THREAD + JMutexAutoLock l(m_srtpLock); +#endif // RTP_SUPPORT_THREAD + + int err = m_lastSRTPError; + m_lastSRTPError = 0; // clear it + + return err; +} + +void RTPSecureSession::SetLastLibSRTPError(int err) +{ +#ifdef RTP_SUPPORT_THREAD + JMutexAutoLock l(m_srtpLock); +#endif // RTP_SUPPORT_THREAD + + m_lastSRTPError = err; +} + +srtp_ctx_t *RTPSecureSession::LockSRTPContext() +{ +#ifdef RTP_SUPPORT_THREAD + m_srtpLock.Lock(); +#endif // RTP_SUPPORT_THREAD + + if (!m_pSRTPContext) + { +#ifdef RTP_SUPPORT_THREAD + m_srtpLock.Unlock(); // Make sure the mutex is not locked on error +#endif // RTP_SUPPORT_THREAD + return 0; + } + + return m_pSRTPContext; +} + +int RTPSecureSession::UnlockSRTPContext() +{ + if (!m_pSRTPContext) + return ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED; + +#ifdef RTP_SUPPORT_THREAD + m_srtpLock.Unlock(); +#endif // RTP_SUPPORT_THREAD + return 0; +} + +int RTPSecureSession::encryptData(uint8_t *pData, int &dataLength, bool rtp) +{ + int length = dataLength; + + if (rtp) + { + if (length < (int)sizeof(uint32_t)*3) + return ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT ; + + err_status_t result = srtp_protect(m_pSRTPContext, (void *)pData, &length); + if (result != err_status_ok) + { + m_lastSRTPError = (int)result; + return ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA; + } + } + else // rtcp + { + if (length < (int)sizeof(uint32_t)*2) + return ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT; + + err_status_t result = srtp_protect_rtcp(m_pSRTPContext, (void *)pData, &length); + if (result != err_status_ok) + { + m_lastSRTPError = (int)result; + return ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA; + } + } + + dataLength = length; + + return 0; +} + +int RTPSecureSession::OnChangeRTPOrRTCPData(const void *origdata, size_t origlen, bool isrtp, void **senddata, size_t *sendlen) +{ + *senddata = 0; + + if (!origdata || origlen == 0) // Nothing to do in this case, packet can be ignored + return 0; + + srtp_ctx_t *pCtx = LockSRTPContext(); + if (pCtx == 0) + return ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED; + + // Need to add some extra bytes, and we'll add a few more to be really safe + uint8_t *pDataCopy = RTPNew(GetMemoryManager(), RTPMEM_TYPE_BUFFER_SRTPDATA) uint8_t [origlen + SRTP_MAX_TRAILER_LEN + 32]; + memcpy(pDataCopy, origdata, origlen); + + int status = 0; + int dataLength = (int)origlen; + + if ((status = encryptData(pDataCopy, dataLength, isrtp)) < 0) + { + UnlockSRTPContext(); + RTPDeleteByteArray(pDataCopy, GetMemoryManager()); + return status; + } + + UnlockSRTPContext(); + + *senddata = pDataCopy; + *sendlen = dataLength; + + return 0; +} + +int RTPSecureSession::decryptRawPacket(RTPRawPacket *rawpack, int *srtpError) +{ + *srtpError = 0; + + uint8_t *pData = rawpack->GetData(); + int dataLength = (int)rawpack->GetDataLength(); + + if (rawpack->IsRTP()) + { + if (dataLength < (int)sizeof(uint32_t)*3) + return ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT; + + err_status_t result = srtp_unprotect(m_pSRTPContext, (void*)pData, &dataLength); + if (result != err_status_ok) + { + *srtpError = result; + return ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA; + } + } + else // RTCP + { + if (dataLength < (int)sizeof(uint32_t)*2) + return ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT; + + err_status_t result = srtp_unprotect_rtcp(m_pSRTPContext, (void *)pData, &dataLength); + if (result != err_status_ok) + { + *srtpError = result; + return ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA; + } + } + + rawpack->ZeroData(); // make sure we don't delete the data we're going to store + rawpack->SetData(pData, (size_t)dataLength); + + return 0; +} + +bool RTPSecureSession::OnChangeIncomingData(RTPRawPacket *rawpack) +{ + if (!rawpack) + return false; + + srtp_ctx_t *pCtx = LockSRTPContext(); + if (pCtx == 0) + { + OnErrorChangeIncomingData(ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED, 0); + return false; + } + + int srtpErr = 0; + int status = decryptRawPacket(rawpack, &srtpErr); + UnlockSRTPContext(); + + if (status < 0) + { + OnErrorChangeIncomingData(status, srtpErr); + return false; + } + + return true; +} + +void RTPSecureSession::OnSentRTPOrRTCPData(void *senddata, size_t sendlen, bool isrtp) +{ + JRTPLIB_UNUSED(sendlen); + JRTPLIB_UNUSED(isrtp); + + if (senddata) + RTPDeleteByteArray((uint8_t *)senddata, GetMemoryManager()); +} + +} // end namespace + +#endif // RTP_SUPPORT_SRTP + diff --git a/qrtplib/rtpsecuresession.h b/qrtplib/rtpsecuresession.h new file mode 100644 index 000000000..bdfb8532e --- /dev/null +++ b/qrtplib/rtpsecuresession.h @@ -0,0 +1,132 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpsecuresession.h + */ + +#ifndef RTPSECURESESSION_H + +#define RTPSECURESESSION_H + +#include "rtpconfig.h" + +#ifdef RTP_SUPPORT_SRTP + +#include "rtpsession.h" + +#ifdef RTP_SUPPORT_THREAD + #include +#endif // RTP_SUPPORT_THREAD + +struct srtp_ctx_t; + +namespace qrtplib +{ + +class RTPCrypt; + +// SRTP library needs to be initialized already! + +/** RTPSession derived class that serves as a base class for an SRTP implementation. + * + * This is an RTPSession derived class that serves as a base class for an SRTP implementation. + * The class sets the RTPSession::SetChangeIncomingData and RTPSession::SetChangeOutgoingData + * flags, and implements RTPSession::OnChangeIncomingData, RTPSession::OnChangeRTPOrRTCPData + * and RTPSession::OnSentRTPOrRTCPData so that encryption and decryption is applied to packets. + * The encryption and decryption will be done using [libsrtp](https://github.com/cisco/libsrtp), + * which must be available at compile time. + * + * Your derived class should call RTPSecureSession::InitializeSRTPContext to initialize a context + * struct of `libsrtp`. When this succeeds, the context can be obtained and used with the + * RTPSecureSession::LockSRTPContext function, which also locks a mutex if thread support was + * available. After you're done using the context yourself (to set encryption parameters for + * SSRCs), you **must** release it again using RTPSecureSession::UnlockSRTPContext. + * + * See `example7.cpp` for an example of how to use this class. + */ +class JRTPLIB_IMPORTEXPORT RTPSecureSession : public RTPSession +{ +public: + /** Constructs an RTPSecureSession instance, see RTPSession::RTPSession + * for more information about the parameters. */ + RTPSecureSession(RTPRandom *rnd = 0, RTPMemoryManager *mgr = 0); + ~RTPSecureSession(); +protected: + /** Initializes the SRTP context, in case of an error it may be useful to inspect + * RTPSecureSession::GetLastLibSRTPError. */ + int InitializeSRTPContext(); + + /** This function locks a mutex and returns the `libsrtp` context that was + * created in RTPSecureSession::InitializeSRTPContext, so that you can further + * use it to specify encryption parameters for various sources; note that you + * **must** release the context again after use with the + * RTPSecureSession::UnlockSRTPContext function. */ + srtp_ctx_t *LockSRTPContext(); + + /** Releases the lock on the SRTP context that was obtained in + * RTPSecureSession::LockSRTPContext. */ + int UnlockSRTPContext(); + + /** Returns (and clears) the last error that was encountered when using a + * `libsrtp` based function. */ + int GetLastLibSRTPError(); + + void SetLastLibSRTPError(int err); + + /** In case the reimplementation of OnChangeIncomingData (which may take place + * in a background thread) encounters an error, this member function will be + * called; implement it in a derived class to receive notification of this. */ + virtual void OnErrorChangeIncomingData(int errcode, int libsrtperrorcode); + + int OnChangeRTPOrRTCPData(const void *origdata, size_t origlen, bool isrtp, void **senddata, size_t *sendlen); + bool OnChangeIncomingData(RTPRawPacket *rawpack); + void OnSentRTPOrRTCPData(void *senddata, size_t sendlen, bool isrtp); +private: + int encryptData(uint8_t *pData, int &dataLength, bool rtp); + int decryptRawPacket(RTPRawPacket *rawpack, int *srtpError); + + srtp_ctx_t *m_pSRTPContext; + int m_lastSRTPError; +#ifdef RTP_SUPPORT_THREAD + jthread::JMutex m_srtpLock; +#endif // RTP_SUPPORT_THREAD +}; + +inline void RTPSecureSession::OnErrorChangeIncomingData(int, int) { } + +} // end namespace + +#endif // RTP_SUPPORT_SRTP + +#endif // RTPSECURESESSION_H + diff --git a/qrtplib/rtpselect.h b/qrtplib/rtpselect.h new file mode 100644 index 000000000..cdba3928d --- /dev/null +++ b/qrtplib/rtpselect.h @@ -0,0 +1,190 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpselect.h + */ + +#ifndef RTPSELECT_H + +#define RTPSELECT_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtperrors.h" +#include "rtptimeutilities.h" +#include "rtpsocketutil.h" + +#if defined(RTP_HAVE_WSAPOLL) || defined(RTP_HAVE_POLL) + +#ifndef RTP_HAVE_WSAPOLL +#include +#include +#endif // !RTP_HAVE_WSAPOLL + +#include +#include + +namespace qrtplib +{ + +inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsocks, RTPTime timeout) +{ + using namespace std; + + vector fds(numsocks); + + for (size_t i = 0 ; i < numsocks ; i++) + { + fds[i].fd = sockets[i]; + fds[i].events = POLLIN; + fds[i].revents = 0; + readflags[i] = 0; + } + + int timeoutmsec = -1; + if (timeout.GetDouble() >= 0) + { + double dtimeoutmsec = timeout.GetDouble()*1000.0; + if (dtimeoutmsec > (numeric_limits::max)()) // parentheses to prevent windows 'max' macro expansion + dtimeoutmsec = (numeric_limits::max)(); + + timeoutmsec = (int)dtimeoutmsec; + } + +#ifdef RTP_HAVE_WSAPOLL + int status = WSAPoll(&(fds[0]), (ULONG)numsocks, timeoutmsec); + if (status < 0) + return ERR_RTP_SELECT_ERRORINPOLL; +#else + int status = poll(&(fds[0]), numsocks, timeoutmsec); + if (status < 0) + { + // We're just going to ignore an EINTR + if (errno == EINTR) + return 0; + return ERR_RTP_SELECT_ERRORINPOLL; + } +#endif // RTP_HAVE_WSAPOLL + + if (status > 0) + { + for (size_t i = 0 ; i < numsocks ; i++) + { + if (fds[i].revents) + readflags[i] = 1; + } + } + return status; +} + +} // end namespace + +#else + +#ifndef RTP_SOCKETTYPE_WINSOCK +#include +#include +#include +#include +#endif // !RTP_SOCKETTYPE_WINSOCK + +namespace qrtplib +{ + +/** Wrapper function around 'select', 'poll' or 'WSAPoll', depending on the + * availability on your platform. + * + * Wrapper function around 'select', 'poll' or 'WSAPoll', depending on the + * availability on your platform. The function will check the specified + * `sockets` for incoming data and sets the flags in `readflags` if so. + * A maximum time `timeout` will be waited for data to arrive, which is + * indefinitely if set to a negative value. The function returns the number + * of sockets that have data incoming. + */ +inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsocks, RTPTime timeout) +{ + struct timeval tv; + struct timeval *pTv = 0; + + if (timeout.GetDouble() >= 0) + { + tv.tv_sec = (long)timeout.GetSeconds(); + tv.tv_usec = timeout.GetMicroSeconds(); + pTv = &tv; + } + + fd_set fdset; + FD_ZERO(&fdset); + for (size_t i = 0 ; i < numsocks ; i++) + { +#ifndef RTP_SOCKETTYPE_WINSOCK + const int setsize = FD_SETSIZE; + // On windows it seems that comparing the socket value to FD_SETSIZE does + // not make sense + if (sockets[i] >= setsize) + return ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE; +#endif // RTP_SOCKETTYPE_WINSOCK + FD_SET(sockets[i], &fdset); + readflags[i] = 0; + } + + int status = select(FD_SETSIZE, &fdset, 0, 0, pTv); +#ifdef RTP_SOCKETTYPE_WINSOCK + if (status < 0) + return ERR_RTP_SELECT_ERRORINSELECT; +#else + if (status < 0) + { + // We're just going to ignore an EINTR + if (errno == EINTR) + return 0; + return ERR_RTP_SELECT_ERRORINSELECT; + } +#endif // RTP_HAVE_WSAPOLL + + if (status > 0) // some descriptors were set, check them + { + for (size_t i = 0 ; i < numsocks ; i++) + { + if (FD_ISSET(sockets[i], &fdset)) + readflags[i] = 1; + } + } + return status; +} + +} // end namespace + +#endif // RTP_HAVE_POLL || RTP_HAVE_WSAPOLL + +#endif // RTPSELECT_H diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp new file mode 100644 index 000000000..f5ee7bee2 --- /dev/null +++ b/qrtplib/rtpsession.cpp @@ -0,0 +1,1701 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpsession.h" +#include "rtperrors.h" +#include "rtppollthread.h" +#include "rtpudpv4transmitter.h" +#include "rtpudpv6transmitter.h" +#include "rtptcptransmitter.h" +#include "rtpexternaltransmitter.h" +#include "rtpsessionparams.h" +#include "rtpdefines.h" +#include "rtprawpacket.h" +#include "rtppacket.h" +#include "rtptimeutilities.h" +#include "rtpmemorymanager.h" +#include "rtprandomrand48.h" +#include "rtprandomrands.h" +#include "rtprandomurandom.h" +#ifdef RTP_SUPPORT_SENDAPP + #include "rtcpcompoundpacket.h" +#endif // RTP_SUPPORT_SENDAPP +#include "rtpinternalutils.h" +#ifndef WIN32 + #include + #include +#else + #include +#endif // WIN32 + + +#ifdef RTP_SUPPORT_THREAD + #define SOURCES_LOCK { if (needthreadsafety) sourcesmutex.Lock(); } + #define SOURCES_UNLOCK { if (needthreadsafety) sourcesmutex.Unlock(); } + #define BUILDER_LOCK { if (needthreadsafety) buildermutex.Lock(); } + #define BUILDER_UNLOCK { if (needthreadsafety) buildermutex.Unlock(); } + #define SCHED_LOCK { if (needthreadsafety) schedmutex.Lock(); } + #define SCHED_UNLOCK { if (needthreadsafety) schedmutex.Unlock(); } + #define PACKSENT_LOCK { if (needthreadsafety) packsentmutex.Lock(); } + #define PACKSENT_UNLOCK { if (needthreadsafety) packsentmutex.Unlock(); } +#else + #define SOURCES_LOCK + #define SOURCES_UNLOCK + #define BUILDER_LOCK + #define BUILDER_UNLOCK + #define SCHED_LOCK + #define SCHED_UNLOCK + #define PACKSENT_LOCK + #define PACKSENT_UNLOCK +#endif // RTP_SUPPORT_THREAD + +namespace qrtplib +{ + +RTPSession::RTPSession(RTPRandom *r,RTPMemoryManager *mgr) + : RTPMemoryObject(mgr),rtprnd(GetRandomNumberGenerator(r)),sources(*this,mgr),packetbuilder(*rtprnd,mgr),rtcpsched(sources,*rtprnd), + rtcpbuilder(sources,packetbuilder,mgr),collisionlist(mgr) +{ + // We're not going to set these flags in Create, so that the constructor of a derived class + // can already change them + m_changeIncomingData = false; + m_changeOutgoingData = false; + + created = false; + timeinit.Dummy(); + + //std::cout << (void *)(rtprnd) << std::endl; +} + +RTPSession::~RTPSession() +{ + Destroy(); + + if (deletertprnd) + delete rtprnd; +} + +int RTPSession::Create(const RTPSessionParams &sessparams,const RTPTransmissionParams *transparams /* = 0 */, + RTPTransmitter::TransmissionProtocol protocol) +{ + int status; + + if (created) + return ERR_RTP_SESSION_ALREADYCREATED; + + usingpollthread = sessparams.IsUsingPollThread(); + needthreadsafety = sessparams.NeedThreadSafety(); + if (usingpollthread && !needthreadsafety) + return ERR_RTP_SESSION_THREADSAFETYCONFLICT; + + useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); + sentpackets = false; + + // Check max packet size + + if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) + return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; + + // Initialize the transmission component + + rtptrans = 0; + switch(protocol) + { + case RTPTransmitter::IPv4UDPProto: + rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPUDPv4Transmitter(GetMemoryManager()); + break; +#ifdef RTP_SUPPORT_IPV6 + case RTPTransmitter::IPv6UDPProto: + rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPUDPv6Transmitter(GetMemoryManager()); + break; +#endif // RTP_SUPPORT_IPV6 + case RTPTransmitter::ExternalProto: + rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPExternalTransmitter(GetMemoryManager()); + break; + case RTPTransmitter::UserDefinedProto: + rtptrans = NewUserDefinedTransmitter(); + if (rtptrans == 0) + return ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL; + break; + case RTPTransmitter::TCPProto: + rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPTCPTransmitter(GetMemoryManager()); + break; + default: + return ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL; + } + + if (rtptrans == 0) + return ERR_RTP_OUTOFMEM; + if ((status = rtptrans->Init(needthreadsafety)) < 0) + { + RTPDelete(rtptrans,GetMemoryManager()); + return status; + } + if ((status = rtptrans->Create(maxpacksize,transparams)) < 0) + { + RTPDelete(rtptrans,GetMemoryManager()); + return status; + } + + deletetransmitter = true; + return InternalCreate(sessparams); +} + +int RTPSession::Create(const RTPSessionParams &sessparams,RTPTransmitter *transmitter) +{ + int status; + + if (created) + return ERR_RTP_SESSION_ALREADYCREATED; + + usingpollthread = sessparams.IsUsingPollThread(); + needthreadsafety = sessparams.NeedThreadSafety(); + if (usingpollthread && !needthreadsafety) + return ERR_RTP_SESSION_THREADSAFETYCONFLICT; + + useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); + sentpackets = false; + + // Check max packet size + + if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) + return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; + + rtptrans = transmitter; + + if ((status = rtptrans->SetMaximumPacketSize(maxpacksize)) < 0) + return status; + + deletetransmitter = false; + return InternalCreate(sessparams); +} + +int RTPSession::InternalCreate(const RTPSessionParams &sessparams) +{ + int status; + + // Initialize packet builder + + if ((status = packetbuilder.Init(maxpacksize)) < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + return status; + } + + if (sessparams.GetUsePredefinedSSRC()) + packetbuilder.AdjustSSRC(sessparams.GetPredefinedSSRC()); + +#ifdef RTP_SUPPORT_PROBATION + + // Set probation type + sources.SetProbationType(sessparams.GetProbationType()); + +#endif // RTP_SUPPORT_PROBATION + + // Add our own ssrc to the source table + + if ((status = sources.CreateOwnSSRC(packetbuilder.GetSSRC())) < 0) + { + packetbuilder.Destroy(); + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + return status; + } + + // Set the initial receive mode + + if ((status = rtptrans->SetReceiveMode(sessparams.GetReceiveMode())) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + return status; + } + + // Init the RTCP packet builder + + double timestampunit = sessparams.GetOwnTimestampUnit(); + uint8_t buf[1024]; + size_t buflen = 1024; + std::string forcedcname = sessparams.GetCNAME(); + + if (forcedcname.length() == 0) + { + if ((status = CreateCNAME(buf,&buflen,sessparams.GetResolveLocalHostname())) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + return status; + } + } + else + { + RTP_STRNCPY((char *)buf, forcedcname.c_str(), buflen); + buf[buflen-1] = 0; + buflen = strlen((char *)buf); + } + + if ((status = rtcpbuilder.Init(maxpacksize,timestampunit,buf,buflen)) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + return status; + } + + // Set scheduler parameters + + rtcpsched.Reset(); + rtcpsched.SetHeaderOverhead(rtptrans->GetHeaderOverhead()); + + RTCPSchedulerParams schedparams; + + sessionbandwidth = sessparams.GetSessionBandwidth(); + controlfragment = sessparams.GetControlTrafficFraction(); + + if ((status = schedparams.SetRTCPBandwidth(sessionbandwidth*controlfragment)) < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + if ((status = schedparams.SetSenderBandwidthFraction(sessparams.GetSenderControlBandwidthFraction())) < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + if ((status = schedparams.SetMinimumTransmissionInterval(sessparams.GetMinimumRTCPTransmissionInterval())) < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + schedparams.SetUseHalfAtStartup(sessparams.GetUseHalfRTCPIntervalAtStartup()); + schedparams.SetRequestImmediateBYE(sessparams.GetRequestImmediateBYE()); + + rtcpsched.SetParameters(schedparams); + + // copy other parameters + + acceptownpackets = sessparams.AcceptOwnPackets(); + membermultiplier = sessparams.GetSourceTimeoutMultiplier(); + sendermultiplier = sessparams.GetSenderTimeoutMultiplier(); + byemultiplier = sessparams.GetBYETimeoutMultiplier(); + collisionmultiplier = sessparams.GetCollisionTimeoutMultiplier(); + notemultiplier = sessparams.GetNoteTimeoutMultiplier(); + + // Do thread stuff if necessary + +#ifdef RTP_SUPPORT_THREAD + pollthread = 0; + if (usingpollthread) + { + if (!sourcesmutex.IsInitialized()) + { + if (sourcesmutex.Init() < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return ERR_RTP_SESSION_CANTINITMUTEX; + } + } + if (!buildermutex.IsInitialized()) + { + if (buildermutex.Init() < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return ERR_RTP_SESSION_CANTINITMUTEX; + } + } + if (!schedmutex.IsInitialized()) + { + if (schedmutex.Init() < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return ERR_RTP_SESSION_CANTINITMUTEX; + } + } + if (!packsentmutex.IsInitialized()) + { + if (packsentmutex.Init() < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return ERR_RTP_SESSION_CANTINITMUTEX; + } + } + + pollthread = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPPOLLTHREAD) RTPPollThread(*this,rtcpsched); + if (pollthread == 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return ERR_RTP_OUTOFMEM; + } + if ((status = pollthread->Start(rtptrans)) < 0) + { + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + RTPDelete(pollthread,GetMemoryManager()); + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + } +#endif // RTP_SUPPORT_THREAD + + created = true; + return 0; +} + + +void RTPSession::Destroy() +{ + if (!created) + return; + +#ifdef RTP_SUPPORT_THREAD + if (pollthread) + RTPDelete(pollthread,GetMemoryManager()); +#endif // RTP_SUPPORT_THREAD + + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + rtcpbuilder.Destroy(); + rtcpsched.Reset(); + collisionlist.Clear(); + sources.Clear(); + + std::list::const_iterator it; + + for (it = byepackets.begin() ; it != byepackets.end() ; it++) + RTPDelete(*it,GetMemoryManager()); + byepackets.clear(); + + created = false; +} + +void RTPSession::BYEDestroy(const RTPTime &maxwaittime,const void *reason,size_t reasonlength) +{ + if (!created) + return; + + // first, stop the thread so we have full control over all components + +#ifdef RTP_SUPPORT_THREAD + if (pollthread) + RTPDelete(pollthread,GetMemoryManager()); +#endif // RTP_SUPPORT_THREAD + + RTPTime stoptime = RTPTime::CurrentTime(); + stoptime += maxwaittime; + + // add bye packet to the list if we've sent data + + RTCPCompoundPacket *pack; + + if (sentpackets) + { + int status; + + reasonlength = (reasonlength>RTCP_BYE_MAXREASONLENGTH)?RTCP_BYE_MAXREASONLENGTH:reasonlength; + status = rtcpbuilder.BuildBYEPacket(&pack,reason,reasonlength,useSR_BYEifpossible); + if (status >= 0) + { + byepackets.push_back(pack); + + if (byepackets.size() == 1) + rtcpsched.ScheduleBYEPacket(pack->GetCompoundPacketLength()); + } + } + + if (!byepackets.empty()) + { + bool done = false; + + while (!done) + { + RTPTime curtime = RTPTime::CurrentTime(); + + if (curtime >= stoptime) + done = true; + + if (rtcpsched.IsTime()) + { + pack = *(byepackets.begin()); + byepackets.pop_front(); + + SendRTCPData(pack->GetCompoundPacketData(),pack->GetCompoundPacketLength()); + + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + + RTPDelete(pack,GetMemoryManager()); + if (!byepackets.empty()) // more bye packets to send, schedule them + rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); + else + done = true; + } + if (!done) + RTPTime::Wait(RTPTime(0,100000)); + } + } + + if (deletetransmitter) + RTPDelete(rtptrans,GetMemoryManager()); + packetbuilder.Destroy(); + rtcpbuilder.Destroy(); + rtcpsched.Reset(); + collisionlist.Clear(); + sources.Clear(); + + // clear rest of bye packets + std::list::const_iterator it; + + for (it = byepackets.begin() ; it != byepackets.end() ; it++) + RTPDelete(*it,GetMemoryManager()); + byepackets.clear(); + + created = false; +} + +bool RTPSession::IsActive() +{ + return created; +} + +uint32_t RTPSession::GetLocalSSRC() +{ + if (!created) + return 0; + + uint32_t ssrc; + + BUILDER_LOCK + ssrc = packetbuilder.GetSSRC(); + BUILDER_UNLOCK + return ssrc; +} + +int RTPSession::AddDestination(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddDestination(addr); +} + +int RTPSession::DeleteDestination(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteDestination(addr); +} + +void RTPSession::ClearDestinations() +{ + if (!created) + return; + rtptrans->ClearDestinations(); +} + +bool RTPSession::SupportsMulticasting() +{ + if (!created) + return false; + return rtptrans->SupportsMulticasting(); +} + +int RTPSession::JoinMulticastGroup(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->JoinMulticastGroup(addr); +} + +int RTPSession::LeaveMulticastGroup(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->LeaveMulticastGroup(addr); +} + +void RTPSession::LeaveAllMulticastGroups() +{ + if (!created) + return; + rtptrans->LeaveAllMulticastGroups(); +} + +int RTPSession::SendPacket(const void *data,size_t len) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + BUILDER_LOCK + if ((status = packetbuilder.BuildPacket(data,len)) < 0) + { + BUILDER_UNLOCK + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK + + SOURCES_LOCK + sources.SentRTPPacket(); + SOURCES_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + return 0; +} + +int RTPSession::SendPacket(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + BUILDER_LOCK + if ((status = packetbuilder.BuildPacket(data,len,pt,mark,timestampinc)) < 0) + { + BUILDER_UNLOCK + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK + + SOURCES_LOCK + sources.SentRTPPacket(); + SOURCES_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + return 0; +} + +int RTPSession::SendPacketEx(const void *data,size_t len, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + BUILDER_LOCK + if ((status = packetbuilder.BuildPacketEx(data,len,hdrextID,hdrextdata,numhdrextwords)) < 0) + { + BUILDER_UNLOCK + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK + + SOURCES_LOCK + sources.SentRTPPacket(); + SOURCES_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + return 0; +} + +int RTPSession::SendPacketEx(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + BUILDER_LOCK + if ((status = packetbuilder.BuildPacketEx(data,len,pt,mark,timestampinc,hdrextID,hdrextdata,numhdrextwords)) < 0) + { + BUILDER_UNLOCK + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK + + SOURCES_LOCK + sources.SentRTPPacket(); + SOURCES_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + return 0; +} + +#ifdef RTP_SUPPORT_SENDAPP + +int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, size_t appdatalen) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + BUILDER_LOCK + uint32_t ssrc = packetbuilder.GetSSRC(); + BUILDER_UNLOCK + + RTCPCompoundPacketBuilder pb(GetMemoryManager()); + + status = pb.InitBuild(maxpacksize); + + if(status < 0) + return status; + + //first packet in an rtcp compound packet should always be SR or RR + if((status = pb.StartReceiverReport(ssrc)) < 0) + return status; + + //add SDES packet with CNAME item + if ((status = pb.AddSDESSource(ssrc)) < 0) + return status; + + BUILDER_LOCK + size_t owncnamelen = 0; + uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); + + if ((status = pb.AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK + + //add our application specific packet + if((status = pb.AddAPPPacket(subtype, ssrc, name, appdata, appdatalen)) < 0) + return status; + + if((status = pb.EndBuild()) < 0) + return status; + + //send packet + status = SendRTCPData(pb.GetCompoundPacketData(),pb.GetCompoundPacketLength()); + if(status < 0) + return status; + + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + + return pb.GetCompoundPacketLength(); +} + +#endif // RTP_SUPPORT_SENDAPP + +#ifdef RTP_SUPPORT_RTCPUNKNOWN + +int RTPSession::SendUnknownPacket(bool sr, uint8_t payload_type, uint8_t subtype, const void *data, size_t len) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + BUILDER_LOCK + uint32_t ssrc = packetbuilder.GetSSRC(); + BUILDER_UNLOCK + + RTCPCompoundPacketBuilder* rtcpcomppack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPCOMPOUNDPACKETBUILDER) RTCPCompoundPacketBuilder(GetMemoryManager()); + if (rtcpcomppack == 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + + status = rtcpcomppack->InitBuild(maxpacksize); + if(status < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + if (sr) + { + // setup for the rtcp + RTPTime rtppacktime = packetbuilder.GetPacketTime(); + uint32_t rtppacktimestamp = packetbuilder.GetPacketTimestamp(); + uint32_t packcount = packetbuilder.GetPacketCount(); + uint32_t octetcount = packetbuilder.GetPayloadOctetCount(); + RTPTime curtime = RTPTime::CurrentTime(); + RTPTime diff = curtime; + diff -= rtppacktime; + diff += 1; // add transmission delay or RTPTime(0,0); + + double timestampunit = 90000; + + uint32_t tsdiff = (uint32_t)((diff.GetDouble()/timestampunit)+0.5); + uint32_t rtptimestamp = rtppacktimestamp+tsdiff; + RTPNTPTime ntptimestamp = curtime.GetNTPTime(); + + //first packet in an rtcp compound packet should always be SR or RR + if((status = rtcpcomppack->StartSenderReport(ssrc,ntptimestamp,rtptimestamp,packcount,octetcount)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + } + else + { + //first packet in an rtcp compound packet should always be SR or RR + if((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + } + + //add SDES packet with CNAME item + if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + BUILDER_LOCK + size_t owncnamelen = 0; + uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); + + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) + { + BUILDER_UNLOCK + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + BUILDER_UNLOCK + + //add our packet + if((status = rtcpcomppack->AddUnknownPacket(payload_type, subtype, ssrc, data, len)) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + if((status = rtcpcomppack->EndBuild()) < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + //send packet + status = SendRTCPData(rtcpcomppack->GetCompoundPacketData(), rtcpcomppack->GetCompoundPacketLength()); + if(status < 0) + { + RTPDelete(rtcpcomppack,GetMemoryManager()); + return status; + } + + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + + OnSendRTCPCompoundPacket(rtcpcomppack); // we'll place this after the actual send to avoid tampering + + int retlen = rtcpcomppack->GetCompoundPacketLength(); + + RTPDelete(rtcpcomppack,GetMemoryManager()); + return retlen; +} + +#endif // RTP_SUPPORT_RTCPUNKNOWN + +int RTPSession::SendRawData(const void *data, size_t len, bool usertpchannel) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + if (usertpchannel) + status = rtptrans->SendRTPData(data, len); + else + status = rtptrans->SendRTCPData(data, len); + return status; +} + +int RTPSession::SetDefaultPayloadType(uint8_t pt) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + BUILDER_LOCK + status = packetbuilder.SetDefaultPayloadType(pt); + BUILDER_UNLOCK + return status; +} + +int RTPSession::SetDefaultMark(bool m) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + BUILDER_LOCK + status = packetbuilder.SetDefaultMark(m); + BUILDER_UNLOCK + return status; +} + +int RTPSession::SetDefaultTimestampIncrement(uint32_t timestampinc) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + BUILDER_LOCK + status = packetbuilder.SetDefaultTimestampIncrement(timestampinc); + BUILDER_UNLOCK + return status; +} + +int RTPSession::IncrementTimestamp(uint32_t inc) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + BUILDER_LOCK + status = packetbuilder.IncrementTimestamp(inc); + BUILDER_UNLOCK + return status; +} + +int RTPSession::IncrementTimestampDefault() +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + BUILDER_LOCK + status = packetbuilder.IncrementTimestampDefault(); + BUILDER_UNLOCK + return status; +} + +int RTPSession::SetPreTransmissionDelay(const RTPTime &delay) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + BUILDER_LOCK + status = rtcpbuilder.SetPreTransmissionDelay(delay); + BUILDER_UNLOCK + return status; +} + +RTPTransmissionInfo *RTPSession::GetTransmissionInfo() +{ + if (!created) + return 0; + return rtptrans->GetTransmissionInfo(); +} + +void RTPSession::DeleteTransmissionInfo(RTPTransmissionInfo *inf) +{ + if (!created) + return; + rtptrans->DeleteTransmissionInfo(inf); +} + +int RTPSession::Poll() +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + if (usingpollthread) + return ERR_RTP_SESSION_USINGPOLLTHREAD; + if ((status = rtptrans->Poll()) < 0) + return status; + return ProcessPolledData(); +} + +int RTPSession::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + if (usingpollthread) + return ERR_RTP_SESSION_USINGPOLLTHREAD; + return rtptrans->WaitForIncomingData(delay,dataavailable); +} + +int RTPSession::AbortWait() +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + if (usingpollthread) + return ERR_RTP_SESSION_USINGPOLLTHREAD; + return rtptrans->AbortWait(); +} + +RTPTime RTPSession::GetRTCPDelay() +{ + if (!created) + return RTPTime(0,0); + if (usingpollthread) + return RTPTime(0,0); + + SOURCES_LOCK + SCHED_LOCK + RTPTime t = rtcpsched.GetTransmissionDelay(); + SCHED_UNLOCK + SOURCES_UNLOCK + return t; +} + +int RTPSession::BeginDataAccess() +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + SOURCES_LOCK + return 0; +} + +bool RTPSession::GotoFirstSource() +{ + if (!created) + return false; + return sources.GotoFirstSource(); +} + +bool RTPSession::GotoNextSource() +{ + if (!created) + return false; + return sources.GotoNextSource(); +} + +bool RTPSession::GotoPreviousSource() +{ + if (!created) + return false; + return sources.GotoPreviousSource(); +} + +bool RTPSession::GotoFirstSourceWithData() +{ + if (!created) + return false; + return sources.GotoFirstSourceWithData(); +} + +bool RTPSession::GotoNextSourceWithData() +{ + if (!created) + return false; + return sources.GotoNextSourceWithData(); +} + +bool RTPSession::GotoPreviousSourceWithData() +{ + if (!created) + return false; + return sources.GotoPreviousSourceWithData(); +} + +RTPSourceData *RTPSession::GetCurrentSourceInfo() +{ + if (!created) + return 0; + return sources.GetCurrentSourceInfo(); +} + +RTPSourceData *RTPSession::GetSourceInfo(uint32_t ssrc) +{ + if (!created) + return 0; + return sources.GetSourceInfo(ssrc); +} + +RTPPacket *RTPSession::GetNextPacket() +{ + if (!created) + return 0; + return sources.GetNextPacket(); +} + +uint16_t RTPSession::GetNextSequenceNumber() const +{ + return packetbuilder.GetSequenceNumber(); +} + +void RTPSession::DeletePacket(RTPPacket *p) +{ + RTPDelete(p,GetMemoryManager()); +} + +int RTPSession::EndDataAccess() +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + SOURCES_UNLOCK + return 0; +} + +int RTPSession::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->SetReceiveMode(m); +} + +int RTPSession::AddToIgnoreList(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddToIgnoreList(addr); +} + +int RTPSession::DeleteFromIgnoreList(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteFromIgnoreList(addr); +} + +void RTPSession::ClearIgnoreList() +{ + if (!created) + return; + rtptrans->ClearIgnoreList(); +} + +int RTPSession::AddToAcceptList(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddToAcceptList(addr); +} + +int RTPSession::DeleteFromAcceptList(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteFromAcceptList(addr); +} + +void RTPSession::ClearAcceptList() +{ + if (!created) + return; + rtptrans->ClearAcceptList(); +} + +int RTPSession::SetMaximumPacketSize(size_t s) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + if (s < RTP_MINPACKETSIZE) + return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; + + int status; + + if ((status = rtptrans->SetMaximumPacketSize(s)) < 0) + return status; + + BUILDER_LOCK + if ((status = packetbuilder.SetMaximumPacketSize(s)) < 0) + { + BUILDER_UNLOCK + // restore previous max packet size + rtptrans->SetMaximumPacketSize(maxpacksize); + return status; + } + if ((status = rtcpbuilder.SetMaximumPacketSize(s)) < 0) + { + // restore previous max packet size + packetbuilder.SetMaximumPacketSize(maxpacksize); + BUILDER_UNLOCK + rtptrans->SetMaximumPacketSize(maxpacksize); + return status; + } + BUILDER_UNLOCK + maxpacksize = s; + return 0; +} + +int RTPSession::SetSessionBandwidth(double bw) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + SCHED_LOCK + RTCPSchedulerParams p = rtcpsched.GetParameters(); + status = p.SetRTCPBandwidth(bw*controlfragment); + if (status >= 0) + { + rtcpsched.SetParameters(p); + sessionbandwidth = bw; + } + SCHED_UNLOCK + return status; +} + +int RTPSession::SetTimestampUnit(double u) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + BUILDER_LOCK + status = rtcpbuilder.SetTimestampUnit(u); + BUILDER_UNLOCK + return status; +} + +void RTPSession::SetNameInterval(int count) +{ + if (!created) + return; + BUILDER_LOCK + rtcpbuilder.SetNameInterval(count); + BUILDER_UNLOCK +} + +void RTPSession::SetEMailInterval(int count) +{ + if (!created) + return; + BUILDER_LOCK + rtcpbuilder.SetEMailInterval(count); + BUILDER_UNLOCK +} + +void RTPSession::SetLocationInterval(int count) +{ + if (!created) + return; + BUILDER_LOCK + rtcpbuilder.SetLocationInterval(count); + BUILDER_UNLOCK +} + +void RTPSession::SetPhoneInterval(int count) +{ + if (!created) + return; + BUILDER_LOCK + rtcpbuilder.SetPhoneInterval(count); + BUILDER_UNLOCK +} + +void RTPSession::SetToolInterval(int count) +{ + if (!created) + return; + BUILDER_LOCK + rtcpbuilder.SetToolInterval(count); + BUILDER_UNLOCK +} + +void RTPSession::SetNoteInterval(int count) +{ + if (!created) + return; + BUILDER_LOCK + rtcpbuilder.SetNoteInterval(count); + BUILDER_UNLOCK +} + +int RTPSession::SetLocalName(const void *s,size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + BUILDER_LOCK + status = rtcpbuilder.SetLocalName(s,len); + BUILDER_UNLOCK + return status; +} + +int RTPSession::SetLocalEMail(const void *s,size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + BUILDER_LOCK + status = rtcpbuilder.SetLocalEMail(s,len); + BUILDER_UNLOCK + return status; +} + +int RTPSession::SetLocalLocation(const void *s,size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + BUILDER_LOCK + status = rtcpbuilder.SetLocalLocation(s,len); + BUILDER_UNLOCK + return status; +} + +int RTPSession::SetLocalPhone(const void *s,size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + BUILDER_LOCK + status = rtcpbuilder.SetLocalPhone(s,len); + BUILDER_UNLOCK + return status; +} + +int RTPSession::SetLocalTool(const void *s,size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + BUILDER_LOCK + status = rtcpbuilder.SetLocalTool(s,len); + BUILDER_UNLOCK + return status; +} + +int RTPSession::SetLocalNote(const void *s,size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + BUILDER_LOCK + status = rtcpbuilder.SetLocalNote(s,len); + BUILDER_UNLOCK + return status; +} + +int RTPSession::ProcessPolledData() +{ + RTPRawPacket *rawpack; + int status; + + SOURCES_LOCK + while ((rawpack = rtptrans->GetNextPacket()) != 0) + { + if (m_changeIncomingData) + { + // Provide a way to change incoming data, for decryption for example + if (!OnChangeIncomingData(rawpack)) + { + RTPDelete(rawpack,GetMemoryManager()); + continue; + } + } + + sources.ClearOwnCollisionFlag(); + + // since our sources instance also uses the scheduler (analysis of incoming packets) + // we'll lock it + SCHED_LOCK + if ((status = sources.ProcessRawPacket(rawpack,rtptrans,acceptownpackets)) < 0) + { + SCHED_UNLOCK + SOURCES_UNLOCK + RTPDelete(rawpack,GetMemoryManager()); + return status; + } + SCHED_UNLOCK + + if (sources.DetectedOwnCollision()) // collision handling! + { + bool created; + + if ((status = collisionlist.UpdateAddress(rawpack->GetSenderAddress(),rawpack->GetReceiveTime(),&created)) < 0) + { + SOURCES_UNLOCK + RTPDelete(rawpack,GetMemoryManager()); + return status; + } + + if (created) // first time we've encountered this address, send bye packet and + { // change our own SSRC + PACKSENT_LOCK + bool hassentpackets = sentpackets; + PACKSENT_UNLOCK + + if (hassentpackets) + { + // Only send BYE packet if we've actually sent data using this + // SSRC + + RTCPCompoundPacket *rtcpcomppack; + + BUILDER_LOCK + if ((status = rtcpbuilder.BuildBYEPacket(&rtcpcomppack,0,0,useSR_BYEifpossible)) < 0) + { + BUILDER_UNLOCK + SOURCES_UNLOCK + RTPDelete(rawpack,GetMemoryManager()); + return status; + } + BUILDER_UNLOCK + + byepackets.push_back(rtcpcomppack); + if (byepackets.size() == 1) // was the first packet, schedule a BYE packet (otherwise there's already one scheduled) + { + SCHED_LOCK + rtcpsched.ScheduleBYEPacket(rtcpcomppack->GetCompoundPacketLength()); + SCHED_UNLOCK + } + } + // bye packet is built and scheduled, now change our SSRC + // and reset the packet count in the transmitter + + BUILDER_LOCK + uint32_t newssrc = packetbuilder.CreateNewSSRC(sources); + BUILDER_UNLOCK + + PACKSENT_LOCK + sentpackets = false; + PACKSENT_UNLOCK + + // remove old entry in source table and add new one + + if ((status = sources.DeleteOwnSSRC()) < 0) + { + SOURCES_UNLOCK + RTPDelete(rawpack,GetMemoryManager()); + return status; + } + if ((status = sources.CreateOwnSSRC(newssrc)) < 0) + { + SOURCES_UNLOCK + RTPDelete(rawpack,GetMemoryManager()); + return status; + } + } + } + RTPDelete(rawpack,GetMemoryManager()); + } + + SCHED_LOCK + RTPTime d = rtcpsched.CalculateDeterministicInterval(false); + SCHED_UNLOCK + + RTPTime t = RTPTime::CurrentTime(); + double Td = d.GetDouble(); + RTPTime sendertimeout = RTPTime(Td*sendermultiplier); + RTPTime generaltimeout = RTPTime(Td*membermultiplier); + RTPTime byetimeout = RTPTime(Td*byemultiplier); + RTPTime colltimeout = RTPTime(Td*collisionmultiplier); + RTPTime notetimeout = RTPTime(Td*notemultiplier); + + sources.MultipleTimeouts(t,sendertimeout,byetimeout,generaltimeout,notetimeout); + collisionlist.Timeout(t,colltimeout); + + // We'll check if it's time for RTCP stuff + + SCHED_LOCK + bool istime = rtcpsched.IsTime(); + SCHED_UNLOCK + + if (istime) + { + RTCPCompoundPacket *pack; + + // we'll check if there's a bye packet to send, or just a normal packet + + if (byepackets.empty()) + { + BUILDER_LOCK + if ((status = rtcpbuilder.BuildNextPacket(&pack)) < 0) + { + BUILDER_UNLOCK + SOURCES_UNLOCK + return status; + } + BUILDER_UNLOCK + if ((status = SendRTCPData(pack->GetCompoundPacketData(),pack->GetCompoundPacketLength())) < 0) + { + SOURCES_UNLOCK + RTPDelete(pack,GetMemoryManager()); + return status; + } + + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + } + else + { + pack = *(byepackets.begin()); + byepackets.pop_front(); + + if ((status = SendRTCPData(pack->GetCompoundPacketData(),pack->GetCompoundPacketLength())) < 0) + { + SOURCES_UNLOCK + RTPDelete(pack,GetMemoryManager()); + return status; + } + + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + + if (!byepackets.empty()) // more bye packets to send, schedule them + { + SCHED_LOCK + rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); + SCHED_UNLOCK + } + } + + SCHED_LOCK + rtcpsched.AnalyseOutgoing(*pack); + SCHED_UNLOCK + + RTPDelete(pack,GetMemoryManager()); + } + SOURCES_UNLOCK + return 0; +} + +int RTPSession::CreateCNAME(uint8_t *buffer,size_t *bufferlength,bool resolve) +{ +#ifndef WIN32 + bool gotlogin = true; +#ifdef RTP_SUPPORT_GETLOGINR + buffer[0] = 0; + if (getlogin_r((char *)buffer,*bufferlength) != 0) + gotlogin = false; + else + { + if (buffer[0] == 0) + gotlogin = false; + } + + if (!gotlogin) // try regular getlogin + { + char *loginname = getlogin(); + if (loginname == 0) + gotlogin = false; + else + strncpy((char *)buffer,loginname,*bufferlength); + } +#else + char *loginname = getlogin(); + if (loginname == 0) + gotlogin = false; + else + strncpy((char *)buffer,loginname,*bufferlength); +#endif // RTP_SUPPORT_GETLOGINR + if (!gotlogin) + { + char *logname = getenv("LOGNAME"); + if (logname == 0) + return ERR_RTP_SESSION_CANTGETLOGINNAME; + strncpy((char *)buffer,logname,*bufferlength); + } +#else // Win32 version + +#ifndef _WIN32_WCE + DWORD len = *bufferlength; + if (!GetUserName((LPTSTR)buffer,&len)) + RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); +#else + RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); +#endif // _WIN32_WCE + +#endif // WIN32 + buffer[*bufferlength-1] = 0; + + size_t offset = strlen((const char *)buffer); + if (offset < (*bufferlength-1)) + buffer[offset] = (uint8_t)'@'; + offset++; + + size_t buflen2 = *bufferlength-offset; + int status; + + if (resolve) + { + if ((status = rtptrans->GetLocalHostName(buffer+offset,&buflen2)) < 0) + return status; + *bufferlength = buflen2+offset; + } + else + { + char hostname[1024]; + + RTP_STRNCPY(hostname,"localhost",1024); // just in case gethostname fails + + gethostname(hostname,1024); + RTP_STRNCPY((char *)(buffer+offset),hostname,buflen2); + + *bufferlength = offset+strlen(hostname); + } + if (*bufferlength > RTCP_SDES_MAXITEMLENGTH) + *bufferlength = RTCP_SDES_MAXITEMLENGTH; + return 0; +} + +RTPRandom *RTPSession::GetRandomNumberGenerator(RTPRandom *r) +{ + RTPRandom *rnew = 0; + + if (r == 0) + { + rnew = RTPRandom::CreateDefaultRandomNumberGenerator(); + deletertprnd = true; + } + else + { + rnew = r; + deletertprnd = false; + } + + return rnew; +} + +int RTPSession::SendRTPData(const void *data, size_t len) +{ + if (!m_changeOutgoingData) + return rtptrans->SendRTPData(data, len); + + void *pSendData = 0; + size_t sendLen = 0; + int status = 0; + + status = OnChangeRTPOrRTCPData(data, len, true, &pSendData, &sendLen); + if (status < 0) + return status; + + if (pSendData) + { + status = rtptrans->SendRTPData(pSendData, sendLen); + OnSentRTPOrRTCPData(pSendData, sendLen, true); + } + + return status; +} + +int RTPSession::SendRTCPData(const void *data, size_t len) +{ + if (!m_changeOutgoingData) + return rtptrans->SendRTCPData(data, len); + + void *pSendData = 0; + size_t sendLen = 0; + int status = 0; + + status = OnChangeRTPOrRTCPData(data, len, false, &pSendData, &sendLen); + if (status < 0) + return status; + + if (pSendData) + { + status = rtptrans->SendRTCPData(pSendData, sendLen); + OnSentRTPOrRTCPData(pSendData, sendLen, false); + } + + return status; +} + +} // end namespace + diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h new file mode 100644 index 000000000..a60b2241e --- /dev/null +++ b/qrtplib/rtpsession.h @@ -0,0 +1,690 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpsession.h + */ + +#ifndef RTPSESSION_H + +#define RTPSESSION_H + +#include "rtpconfig.h" +#include "rtplibraryversion.h" +#include "rtppacketbuilder.h" +#include "rtpsessionsources.h" +#include "rtptransmitter.h" +#include "rtpcollisionlist.h" +#include "rtcpscheduler.h" +#include "rtcppacketbuilder.h" +#include "rtptimeutilities.h" +#include "rtcpcompoundpacketbuilder.h" +#include "rtpmemoryobject.h" +#include + +#ifdef RTP_SUPPORT_THREAD + #include +#endif // RTP_SUPPORT_THREAD + +namespace qrtplib +{ + +class RTPTransmitter; +class RTPSessionParams; +class RTPTransmissionParams; +class RTPAddress; +class RTPSourceData; +class RTPPacket; +class RTPPollThread; +class RTPTransmissionInfo; +class RTCPCompoundPacket; +class RTCPPacket; +class RTCPAPPPacket; + +/** High level class for using RTP. + * For most RTP based applications, the RTPSession class will probably be the one to use. It handles + * the RTCP part completely internally, so the user can focus on sending and receiving the actual data. + * In case you want to use SRTP, you should create an RTPSecureSession derived class. + * \note The RTPSession class is not meant to be thread safe. The user should use some kind of locking + * mechanism to prevent different threads from using the same RTPSession instance. + */ +class JRTPLIB_IMPORTEXPORT RTPSession : public RTPMemoryObject +{ +public: + /** Constructs an RTPSession instance, optionally using a specific instance of a random + * number generator, and optionally installing a memory manager. + * Constructs an RTPSession instance, optionally using a specific instance of a random + * number generator, and optionally installing a memory manager. If no random number generator + * is specified, the RTPSession object will try to use either a RTPRandomURandom or + * RTPRandomRandS instance. If neither is available on the current platform, a RTPRandomRand48 + * instance will be used instead. By specifying a random number generator yourself, it is + * possible to use the same generator in several RTPSession instances. + */ + RTPSession(RTPRandom *rnd = 0, RTPMemoryManager *mgr = 0); + virtual ~RTPSession(); + + /** Creates an RTP session. + * This function creates an RTP session with parameters \c sessparams, which will use a transmitter + * corresponding to \c proto. Parameters for this transmitter can be specified as well. If \c + * proto is of type RTPTransmitter::UserDefinedProto, the NewUserDefinedTransmitter function must + * be implemented. + */ + int Create(const RTPSessionParams &sessparams,const RTPTransmissionParams *transparams = 0, RTPTransmitter::TransmissionProtocol proto = RTPTransmitter::IPv4UDPProto); + + /** Creates an RTP session using \c transmitter as transmission component. + * This function creates an RTP session with parameters \c sessparams, which will use the + * transmission component \c transmitter. Initialization and destruction of the transmitter + * will not be done by the RTPSession class if this Create function is used. This function + * can be useful if you which to reuse the transmission component in another RTPSession + * instance, once the original RTPSession isn't using the transmitter anymore. + */ + int Create(const RTPSessionParams &sessparams,RTPTransmitter *transmitter); + + /** Leaves the session without sending a BYE packet. */ + void Destroy(); + + /** Sends a BYE packet and leaves the session. + * Sends a BYE packet and leaves the session. At most a time \c maxwaittime will be waited to + * send the BYE packet. If this time expires, the session will be left without sending a BYE packet. + * The BYE packet will contain as reason for leaving \c reason with length \c reasonlength. + */ + void BYEDestroy(const RTPTime &maxwaittime,const void *reason,size_t reasonlength); + + /** Returns whether the session has been created or not. */ + bool IsActive(); + + /** Returns our own SSRC. */ + uint32_t GetLocalSSRC(); + + /** Adds \c addr to the list of destinations. */ + int AddDestination(const RTPAddress &addr); + + /** Deletes \c addr from the list of destinations. */ + int DeleteDestination(const RTPAddress &addr); + + /** Clears the list of destinations. */ + void ClearDestinations(); + + /** Returns \c true if multicasting is supported. */ + bool SupportsMulticasting(); + + /** Joins the multicast group specified by \c addr. */ + int JoinMulticastGroup(const RTPAddress &addr); + + /** Leaves the multicast group specified by \c addr. */ + int LeaveMulticastGroup(const RTPAddress &addr); + + /** Leaves all multicast groups. */ + void LeaveAllMulticastGroups(); + + /** Sends the RTP packet with payload \c data which has length \c len. + * Sends the RTP packet with payload \c data which has length \c len. + * The used payload type, marker and timestamp increment will be those that have been set + * using the \c SetDefault member functions. + */ + int SendPacket(const void *data,size_t len); + + /** Sends the RTP packet with payload \c data which has length \c len. + * It will use payload type \c pt, marker \c mark and after the packet has been built, the + * timestamp will be incremented by \c timestampinc. + */ + int SendPacket(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc); + + /** Sends the RTP packet with payload \c data which has length \c len. + * The packet will contain a header extension with identifier \c hdrextID and containing data + * \c hdrextdata. The length of this data is given by \c numhdrextwords and is specified in a + * number of 32-bit words. The used payload type, marker and timestamp increment will be those that + * have been set using the \c SetDefault member functions. + */ + int SendPacketEx(const void *data,size_t len, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords); + + /** Sends the RTP packet with payload \c data which has length \c len. + * It will use payload type \c pt, marker \c mark and after the packet has been built, the + * timestamp will be incremented by \c timestampinc. The packet will contain a header + * extension with identifier \c hdrextID and containing data \c hdrextdata. The length + * of this data is given by \c numhdrextwords and is specified in a number of 32-bit words. + */ + int SendPacketEx(const void *data,size_t len, + uint8_t pt,bool mark,uint32_t timestampinc, + uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords); +#ifdef RTP_SUPPORT_SENDAPP + /** If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet + * containing an RTCP APP packet and sends it immediately. + * If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet + * containing an RTCP APP packet and sends it immediately. If successful, the function returns the number + * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP + * specification, so use with care. + */ + int SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, size_t appdatalen); +#endif // RTP_SUPPORT_SENDAPP + +#ifdef RTP_SUPPORT_RTCPUNKNOWN + /** Tries to send an Unknown packet immediately. + * Tries to send an Unknown packet immediately. If successful, the function returns the number + * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP + * specification, so use with care. Can send message along with a receiver report or a sender report + */ + int SendUnknownPacket(bool sr, uint8_t payload_type, uint8_t subtype, const void *data, size_t len); +#endif // RTP_SUPPORT_RTCPUNKNOWN + + /** With this function raw data can be sent directly over the RTP or + * RTCP channel (if they are different); the data is **not** passed through the + * RTPSession::OnChangeRTPOrRTCPData function. */ + int SendRawData(const void *data, size_t len, bool usertpchannel); + + /** Sets the default payload type for RTP packets to \c pt. */ + int SetDefaultPayloadType(uint8_t pt); + + /** Sets the default marker for RTP packets to \c m. */ + int SetDefaultMark(bool m); + + /** Sets the default value to increment the timestamp with to \c timestampinc. */ + int SetDefaultTimestampIncrement(uint32_t timestampinc); + + /** This function increments the timestamp with the amount given by \c inc. + * This function increments the timestamp with the amount given by \c inc. This can be useful + * if, for example, a packet was not sent because it contained only silence. Then, this function + * should be called to increment the timestamp with the appropriate amount so that the next packets + * will still be played at the correct time at other hosts. + */ + int IncrementTimestamp(uint32_t inc); + + /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. + * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. This can be useful if, for example, a packet was not sent because it contained only silence. + * Then, this function should be called to increment the timestamp with the appropriate amount so that the next + * packets will still be played at the correct time at other hosts. + */ + int IncrementTimestampDefault(); + + /** This function allows you to inform the library about the delay between sampling the first + * sample of a packet and sending the packet. + * This function allows you to inform the library about the delay between sampling the first + * sample of a packet and sending the packet. This delay is taken into account when calculating the + * relation between RTP timestamp and wallclock time, used for inter-media synchronization. + */ + int SetPreTransmissionDelay(const RTPTime &delay); + + /** This function returns an instance of a subclass of RTPTransmissionInfo which will give some + * additional information about the transmitter (a list of local IP addresses for example). + * This function returns an instance of a subclass of RTPTransmissionInfo which will give some + * additional information about the transmitter (a list of local IP addresses for example). The user + * has to free the returned instance when it is no longer needed, preferably using the DeleteTransmissionInfo + * function. + */ + RTPTransmissionInfo *GetTransmissionInfo(); + + /** Frees the memory used by the transmission information \c inf. */ + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + /** If you're not using the poll thread, this function must be called regularly to process incoming data + * and to send RTCP data when necessary. + */ + int Poll(); + + /** Waits at most a time \c delay until incoming data has been detected. + * Waits at most a time \c delay until incoming data has been detected. Only works when you're not + * using the poll thread. If \c dataavailable is not \c NULL, it should be set to \c true if data + * was actually read and to \c false otherwise. + */ + int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); + + /** If the previous function has been called, this one aborts the waiting (only works when you're not + * using the poll thread). + */ + int AbortWait(); + + /** Returns the time interval after which an RTCP compound packet may have to be sent (only works when + * you're not using the poll thread. + */ + RTPTime GetRTCPDelay(); + + /** The following member functions (till EndDataAccess}) need to be accessed between a call + * to BeginDataAccess and EndDataAccess. + * The BeginDataAccess function makes sure that the poll thread won't access the source table + * at the same time that you're using it. When the EndDataAccess is called, the lock on the + * source table is freed again. + */ + int BeginDataAccess(); + + /** Starts the iteration over the participants by going to the first member in the table. + * Starts the iteration over the participants by going to the first member in the table. + * If a member was found, the function returns \c true, otherwise it returns \c false. + */ + bool GotoFirstSource(); + + /** Sets the current source to be the next source in the table. + * Sets the current source to be the next source in the table. If we're already at the last + * source, the function returns \c false, otherwise it returns \c true. + */ + bool GotoNextSource(); + + /** Sets the current source to be the previous source in the table. + * Sets the current source to be the previous source in the table. If we're at the first source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoPreviousSource(); + + /** Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoFirstSourceWithData(); + + /** Sets the current source to be the next source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the next source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoNextSourceWithData(); + + /** Sets the current source to be the previous source in the table which has RTPPacket + * instances that we haven't extracted yet. + * Sets the current source to be the previous source in the table which has RTPPacket + * instances that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoPreviousSourceWithData(); + + /** Returns the \c RTPSourceData instance for the currently selected participant. */ + RTPSourceData *GetCurrentSourceInfo(); + + /** Returns the \c RTPSourceData instance for the participant identified by \c ssrc, + * or NULL if no such entry exists. + */ + RTPSourceData *GetSourceInfo(uint32_t ssrc); + + /** Extracts the next packet from the received packets queue of the current participant, + * or NULL if no more packets are available. + * Extracts the next packet from the received packets queue of the current participant, + * or NULL if no more packets are available. When the packet is no longer needed, its + * memory should be freed using the DeletePacket member function. + */ + RTPPacket *GetNextPacket(); + + /** Returns the Sequence Number that will be used in the next SendPacket function call. */ + uint16_t GetNextSequenceNumber() const; + + /** Frees the memory used by \c p. */ + void DeletePacket(RTPPacket *p); + + /** See BeginDataAccess. */ + int EndDataAccess(); + + /** Sets the receive mode to \c m. + * Sets the receive mode to \c m. Note that when the receive mode is changed, the list of + * addresses to be ignored ot accepted will be cleared. + */ + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + + /** Adds \c addr to the list of addresses to ignore. */ + int AddToIgnoreList(const RTPAddress &addr); + + /** Deletes \c addr from the list of addresses to ignore. */ + int DeleteFromIgnoreList(const RTPAddress &addr); + + /** Clears the list of addresses to ignore. */ + void ClearIgnoreList(); + + /** Adds \c addr to the list of addresses to accept. */ + int AddToAcceptList(const RTPAddress &addr); + + /** Deletes \c addr from the list of addresses to accept. */ + int DeleteFromAcceptList(const RTPAddress &addr); + + /** Clears the list of addresses to accept. */ + void ClearAcceptList(); + + /** Sets the maximum allowed packet size to \c s. */ + int SetMaximumPacketSize(size_t s); + + /** Sets the session bandwidth to \c bw, which is specified in bytes per second. */ + int SetSessionBandwidth(double bw); + + /** Sets the timestamp unit for our own data. + * Sets the timestamp unit for our own data. The timestamp unit is defined as a time interval in + * seconds divided by the corresponding timestamp interval. For example, for 8000 Hz audio, the + * timestamp unit would typically be 1/8000. Since this value is initially set to an illegal value, + * the user must set this to an allowed value to be able to create a session. + */ + int SetTimestampUnit(double u); + + /** Sets the RTCP interval for the SDES name item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES name item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNameInterval(int count); + + /** Sets the RTCP interval for the SDES e-mail item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES e-mail item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetEMailInterval(int count); + + /** Sets the RTCP interval for the SDES location item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES location item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetLocationInterval(int count); + + /** Sets the RTCP interval for the SDES phone item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES phone item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetPhoneInterval(int count); + + /** Sets the RTCP interval for the SDES tool item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES tool item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetToolInterval(int count); + + /** Sets the RTCP interval for the SDES note item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES note item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNoteInterval(int count); + + /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ + int SetLocalName(const void *s,size_t len); + + /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ + int SetLocalEMail(const void *s,size_t len); + + /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ + int SetLocalLocation(const void *s,size_t len); + + /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ + int SetLocalPhone(const void *s,size_t len); + + /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ + int SetLocalTool(const void *s,size_t len); + + /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ + int SetLocalNote(const void *s,size_t len); + +protected: + /** Allocate a user defined transmitter. + * In case you specified in the Create function that you want to use a + * user defined transmitter, you should override this function. The RTPTransmitter + * instance returned by this function will then be used to send and receive RTP and + * RTCP packets. Note that when the session is destroyed, this RTPTransmitter + * instance will be destroyed as well. + */ + virtual RTPTransmitter *NewUserDefinedTransmitter(); + + /** Is called when an incoming RTP packet is about to be processed. + * Is called when an incoming RTP packet is about to be processed. This is _not_ + * a good function to process an RTP packet in, in case you want to avoid iterating + * over the sources using the GotoFirst/GotoNext functions. In that case, the + * RTPSession::OnValidatedRTPPacket function should be used. + */ + virtual void OnRTPPacket(RTPPacket *pack,const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an incoming RTCP packet is about to be processed. */ + virtual void OnRTCPCompoundPacket(RTCPCompoundPacket *pack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Is called when an SSRC collision was detected. + * Is called when an SSRC collision was detected. The instance \c srcdat is the one present in + * the table, the address \c senderaddress is the one that collided with one of the addresses + * and \c isrtp indicates against which address of \c srcdat the check failed. + */ + virtual void OnSSRCCollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp); + + /** Is called when another CNAME was received than the one already present for source \c srcdat. */ + virtual void OnCNAMECollision(RTPSourceData *srcdat,const RTPAddress *senderaddress, + const uint8_t *cname,size_t cnamelength); + + /** Is called when a new entry \c srcdat is added to the source table. */ + virtual void OnNewSource(RTPSourceData *srcdat); + + /** Is called when the entry \c srcdat is about to be deleted from the source table. */ + virtual void OnRemoveSource(RTPSourceData *srcdat); + + /** Is called when participant \c srcdat is timed out. */ + virtual void OnTimeout(RTPSourceData *srcdat); + + /** Is called when participant \c srcdat is timed after having sent a BYE packet. */ + virtual void OnBYETimeout(RTPSourceData *srcdat); + + /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime + * from address \c senderaddress. + */ + virtual void OnAPPPacket(RTCPAPPPacket *apppacket,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Is called when an unknown RTCP packet type was detected. */ + virtual void OnUnknownPacketType(RTCPPacket *rtcppack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Is called when an unknown packet format for a known packet type was detected. */ + virtual void OnUnknownPacketFormat(RTCPPacket *rtcppack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Is called when the SDES NOTE item for source \c srcdat has been timed out. */ + virtual void OnNoteTimeout(RTPSourceData *srcdat); + + /** Is called when an RTCP sender report has been processed for this source. */ + virtual void OnRTCPSenderReport(RTPSourceData *srcdat); + + /** Is called when an RTCP receiver report has been processed for this source. */ + virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); + + /** Is called when a specific SDES item was received for this source. */ + virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, + const void *itemdata, size_t itemlength); +#ifdef RTP_SUPPORT_SDESPRIV + /** Is called when a specific SDES item of 'private' type was received for this source. */ + virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, + const void *valuedata, size_t valuelen); +#endif // RTP_SUPPORT_SDESPRIV + + /** Is called when a BYE packet has been processed for source \c srcdat. */ + virtual void OnBYEPacket(RTPSourceData *srcdat); + + /** Is called when an RTCP compound packet has just been sent (useful to inspect outgoing RTCP data). */ + virtual void OnSendRTCPCompoundPacket(RTCPCompoundPacket *pack); +#ifdef RTP_SUPPORT_THREAD + /** Is called when error \c errcode was detected in the poll thread. */ + virtual void OnPollThreadError(int errcode); + + /** Is called each time the poll thread loops. + * Is called each time the poll thread loops. This happens when incoming data was + * detected or when it's time to send an RTCP compound packet. + */ + virtual void OnPollThreadStep(); + + /** Is called when the poll thread is started. + * Is called when the poll thread is started. This happens just before entering the + * thread main loop. + * \param stop This can be used to stop the thread immediately without entering the loop. + */ + virtual void OnPollThreadStart(bool &stop); + + /** Is called when the poll thread is going to stop. + * Is called when the poll thread is going to stop. This happens just before termitating the thread. + */ + virtual void OnPollThreadStop(); +#endif // RTP_SUPPORT_THREAD + + /** If this is set to true, outgoing data will be passed through RTPSession::OnChangeRTPOrRTCPData + * and RTPSession::OnSentRTPOrRTCPData, allowing you to modify the data (e.g. to encrypt it). */ + void SetChangeOutgoingData(bool change) { m_changeOutgoingData = change; } + + /** If this is set to true, incoming data will be passed through RTPSession::OnChangeIncomingData, + * allowing you to modify the data (e.g. to decrypt it). */ + void SetChangeIncomingData(bool change) { m_changeIncomingData = change; } + + /** If RTPSession::SetChangeOutgoingData was sent to true, overriding this you can change the + * data packet that will actually be sent, for example adding encryption. + * If RTPSession::SetChangeOutgoingData was sent to true, overriding this you can change the + * data packet that will actually be sent, for example adding encryption. + * Note that no memory management will be performed on the `senddata` pointer you fill in, + * so if it needs to be deleted at some point you need to take care of this in some way + * yourself, a good way may be to do this in RTPSession::OnSentRTPOrRTCPData. If `senddata` is + * set to 0, no packet will be sent out. This also provides a way to turn off sending RTCP + * packets if desired. */ + virtual int OnChangeRTPOrRTCPData(const void *origdata, size_t origlen, bool isrtp, void **senddata, size_t *sendlen); + + /** This function is called when an RTP or RTCP packet was sent, it can be helpful + * when data was allocated in RTPSession::OnChangeRTPOrRTCPData to deallocate it + * here. */ + virtual void OnSentRTPOrRTCPData(void *senddata, size_t sendlen, bool isrtp); + + /** By overriding this function, the raw incoming data can be inspected + * and modified (e.g. for encryption). + * By overriding this function, the raw incoming data can be inspected + * and modified (e.g. for encryption). If the function returns `false`, + * the packet is discarded. + */ + virtual bool OnChangeIncomingData(RTPRawPacket *rawpack); + + /** Allows you to use an RTP packet from the specified source directly. + * Allows you to use an RTP packet from the specified source directly. If + * `ispackethandled` is set to `true`, the packet will no longer be stored in this + * source's packet list. Note that if you do set this flag, you'll need to + * deallocate the packet yourself at an appropriate time. + * + * The difference with RTPSession::OnRTPPacket is that that + * function will always process the RTP packet further and is therefore not + * really suited to actually do something with the data. + */ + virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); +private: + int InternalCreate(const RTPSessionParams &sessparams); + int CreateCNAME(uint8_t *buffer,size_t *bufferlength,bool resolve); + int ProcessPolledData(); + int ProcessRTCPCompoundPacket(RTCPCompoundPacket &rtcpcomppack,RTPRawPacket *pack); + RTPRandom *GetRandomNumberGenerator(RTPRandom *r); + int SendRTPData(const void *data, size_t len); + int SendRTCPData(const void *data, size_t len); + + RTPRandom *rtprnd; + bool deletertprnd; + + RTPTransmitter *rtptrans; + bool created; + bool deletetransmitter; + bool usingpollthread, needthreadsafety; + bool acceptownpackets; + bool useSR_BYEifpossible; + size_t maxpacksize; + double sessionbandwidth; + double controlfragment; + double sendermultiplier; + double byemultiplier; + double membermultiplier; + double collisionmultiplier; + double notemultiplier; + bool sentpackets; + + bool m_changeIncomingData, m_changeOutgoingData; + + RTPSessionSources sources; + RTPPacketBuilder packetbuilder; + RTCPScheduler rtcpsched; + RTCPPacketBuilder rtcpbuilder; + RTPCollisionList collisionlist; + + std::list byepackets; + +#ifdef RTP_SUPPORT_THREAD + RTPPollThread *pollthread; + jthread::JMutex sourcesmutex,buildermutex,schedmutex,packsentmutex; + + friend class RTPPollThread; +#endif // RTP_SUPPORT_THREAD + friend class RTPSessionSources; + friend class RTCPSessionPacketBuilder; +}; + +inline RTPTransmitter *RTPSession::NewUserDefinedTransmitter() { return 0; } +inline void RTPSession::OnRTPPacket(RTPPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSession::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSession::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool ) { } +inline void RTPSession::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, size_t ) { } +inline void RTPSession::OnNewSource(RTPSourceData *) { } +inline void RTPSession::OnRemoveSource(RTPSourceData *) { } +inline void RTPSession::OnTimeout(RTPSourceData *) { } +inline void RTPSession::OnBYETimeout(RTPSourceData *) { } +inline void RTPSession::OnAPPPacket(RTCPAPPPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSession::OnUnknownPacketType(RTCPPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSession::OnUnknownPacketFormat(RTCPPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSession::OnNoteTimeout(RTPSourceData *) { } +inline void RTPSession::OnRTCPSenderReport(RTPSourceData *) { } +inline void RTPSession::OnRTCPReceiverReport(RTPSourceData *) { } +inline void RTPSession::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, size_t) { } + +#ifdef RTP_SUPPORT_SDESPRIV +inline void RTPSession::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, size_t, const void *, size_t) { } +#endif // RTP_SUPPORT_SDESPRIV + +inline void RTPSession::OnBYEPacket(RTPSourceData *) { } +inline void RTPSession::OnSendRTCPCompoundPacket(RTCPCompoundPacket *) { } + +#ifdef RTP_SUPPORT_THREAD +inline void RTPSession::OnPollThreadError(int) { } +inline void RTPSession::OnPollThreadStep() { } +inline void RTPSession::OnPollThreadStart(bool &) { } +inline void RTPSession::OnPollThreadStop() { } +#endif // RTP_SUPPORT_THREAD + +inline int RTPSession::OnChangeRTPOrRTCPData(const void *, size_t, bool, void **, size_t *) { + return ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED; +} +inline void RTPSession::OnSentRTPOrRTCPData(void *, size_t, bool) { } +inline bool RTPSession::OnChangeIncomingData(RTPRawPacket *) { return true; } +inline void RTPSession::OnValidatedRTPPacket(RTPSourceData *, RTPPacket *, bool, bool *) { } + +} // end namespace + +#endif // RTPSESSION_H + diff --git a/qrtplib/rtpsessionparams.cpp b/qrtplib/rtpsessionparams.cpp new file mode 100644 index 000000000..fac2f8e7c --- /dev/null +++ b/qrtplib/rtpsessionparams.cpp @@ -0,0 +1,100 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpconfig.h" +#include "rtpsessionparams.h" +#include "rtpdefines.h" +#include "rtperrors.h" + +namespace qrtplib +{ + +RTPSessionParams::RTPSessionParams() : mininterval(0,0) +{ +#ifdef RTP_SUPPORT_THREAD + usepollthread = true; + m_needThreadSafety = true; +#else + usepollthread = false; + m_needThreadSafety = false; +#endif // RTP_SUPPORT_THREAD + maxpacksize = RTP_DEFAULTPACKETSIZE; + receivemode = RTPTransmitter::AcceptAll; + acceptown = false; + owntsunit = -1; // The user will have to set it to the correct value himself + resolvehostname = false; +#ifdef RTP_SUPPORT_PROBATION + probationtype = RTPSources::ProbationStore; +#endif // RTP_SUPPORT_PROBATION + + mininterval = RTPTime(RTCP_DEFAULTMININTERVAL); + sessionbandwidth = RTP_DEFAULTSESSIONBANDWIDTH; + controlfrac = RTCP_DEFAULTBANDWIDTHFRACTION; + senderfrac = RTCP_DEFAULTSENDERFRACTION; + usehalfatstartup = RTCP_DEFAULTHALFATSTARTUP; + immediatebye = RTCP_DEFAULTIMMEDIATEBYE; + SR_BYE = RTCP_DEFAULTSRBYE; + + sendermultiplier = RTP_SENDERTIMEOUTMULTIPLIER; + generaltimeoutmultiplier = RTP_MEMBERTIMEOUTMULTIPLIER; + byetimeoutmultiplier = RTP_BYETIMEOUTMULTIPLIER; + collisionmultiplier = RTP_COLLISIONTIMEOUTMULTIPLIER; + notemultiplier = RTP_NOTETTIMEOUTMULTIPLIER; + + usepredefinedssrc = false; + predefinedssrc = 0; +} + +int RTPSessionParams::SetUsePollThread(bool usethread) +{ +#ifndef RTP_SUPPORT_THREAD + JRTPLIB_UNUSED(usethread); + return ERR_RTP_NOTHREADSUPPORT; +#else + usepollthread = usethread; + return 0; +#endif // RTP_SUPPORT_THREAD +} + +int RTPSessionParams::SetNeedThreadSafety(bool s) +{ +#ifndef RTP_SUPPORT_THREAD + JRTPLIB_UNUSED(s); + return ERR_RTP_NOTHREADSUPPORT; +#else + m_needThreadSafety = s; + return 0; +#endif // RTP_SUPPORT_THREAD +} + +} // end namespace + diff --git a/qrtplib/rtpsessionparams.h b/qrtplib/rtpsessionparams.h new file mode 100644 index 000000000..c8efac478 --- /dev/null +++ b/qrtplib/rtpsessionparams.h @@ -0,0 +1,256 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpsessionparams.h + */ + +#ifndef RTPSESSIONPARAMS_H + +#define RTPSESSIONPARAMS_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtptransmitter.h" +#include "rtptimeutilities.h" +#include "rtpsources.h" + +namespace qrtplib +{ + +/** Describes the parameters for to be used by an RTPSession instance. + * Describes the parameters for to be used by an RTPSession instance. Note that the own timestamp + * unit must be set to a valid number, otherwise the session can't be created. + */ +class JRTPLIB_IMPORTEXPORT RTPSessionParams +{ +public: + RTPSessionParams(); + + /** If \c usethread is \c true, the session will use a poll thread to automatically process incoming + * data and to send RTCP packets when necessary. + */ + int SetUsePollThread(bool usethread); + + /** if `s` is `true`, the session will use mutexes in case multiple threads + * are at work. */ + int SetNeedThreadSafety(bool s); + + /** Returns whether the session should use a poll thread or not (default is \c true). */ + bool IsUsingPollThread() const { return usepollthread; } + + /** Sets the maximum allowed packet size for the session. */ + void SetMaximumPacketSize(size_t max) { maxpacksize = max; } + + /** Returns the maximum allowed packet size (default is 1400 bytes). */ + size_t GetMaximumPacketSize() const { return maxpacksize; } + + /** If the argument is \c true, the session should accept its own packets and store + * them accordingly in the source table. + */ + void SetAcceptOwnPackets(bool accept) { acceptown = accept; } + + /** Returns \c true if the session should accept its own packets (default is \c false). */ + bool AcceptOwnPackets() const { return acceptown; } + + /** Sets the receive mode to be used by the session. */ + void SetReceiveMode(RTPTransmitter::ReceiveMode recvmode) { receivemode = recvmode; } + + /** Sets the receive mode to be used by the session (default is: accept all packets). */ + RTPTransmitter::ReceiveMode GetReceiveMode() const { return receivemode; } + + /** Sets the timestamp unit for our own data. + * Sets the timestamp unit for our own data. The timestamp unit is defined as a time interval in + * seconds divided by the corresponding timestamp interval. For example, for 8000 Hz audio, the + * timestamp unit would typically be 1/8000. Since this value is initially set to an illegal value, + * the user must set this to an allowed value to be able to create a session. + */ + void SetOwnTimestampUnit(double tsunit) { owntsunit = tsunit; } + + /** Returns the currently set timestamp unit. */ + double GetOwnTimestampUnit() const { return owntsunit; } + + /** Sets a flag indicating if a DNS lookup should be done to determine our hostname (to construct a CNAME item). + * If \c v is set to \c true, the session will ask the transmitter to find a host name based upon the IP + * addresses in its list of local IP addresses. If set to \c false, a call to \c gethostname or something + * similar will be used to find the local hostname. Note that the first method might take some time. + */ + void SetResolveLocalHostname(bool v) { resolvehostname = v; } + + /** Returns whether the local hostname should be determined from the transmitter's list of local IP addresses + * or not (default is \c false). + */ + bool GetResolveLocalHostname() const { return resolvehostname; } +#ifdef RTP_SUPPORT_PROBATION + /** If probation support is enabled, this function sets the probation type to be used. */ + void SetProbationType(RTPSources::ProbationType probtype) { probationtype = probtype; } + + /** Returns the probation type which will be used (default is RTPSources::ProbationStore). */ + RTPSources::ProbationType GetProbationType() const { return probationtype; } +#endif // RTP_SUPPORT_PROBATION + + /** Sets the session bandwidth in bytes per second. */ + void SetSessionBandwidth(double sessbw) { sessionbandwidth = sessbw; } + + /** Returns the session bandwidth in bytes per second (default is 10000 bytes per second). */ + double GetSessionBandwidth() const { return sessionbandwidth; } + + /** Sets the fraction of the session bandwidth to be used for control traffic. */ + void SetControlTrafficFraction(double frac) { controlfrac = frac; } + + /** Returns the fraction of the session bandwidth that will be used for control traffic (default is 5%). */ + double GetControlTrafficFraction() const { return controlfrac; } + + /** Sets the minimum fraction of the control traffic that will be used by senders. */ + void SetSenderControlBandwidthFraction(double frac) { senderfrac = frac; } + + /** Returns the minimum fraction of the control traffic that will be used by senders (default is 25%). */ + double GetSenderControlBandwidthFraction() const { return senderfrac; } + + /** Set the minimal time interval between sending RTCP packets. */ + void SetMinimumRTCPTransmissionInterval(const RTPTime &t) { mininterval = t; } + + /** Returns the minimal time interval between sending RTCP packets (default is 5 seconds). */ + RTPTime GetMinimumRTCPTransmissionInterval() const { return mininterval; } + + /** If \c usehalf is set to \c true, the session will only wait half of the calculated RTCP + * interval before sending its first RTCP packet. + */ + void SetUseHalfRTCPIntervalAtStartup(bool usehalf) { usehalfatstartup = usehalf; } + + /** Returns whether the session will only wait half of the calculated RTCP interval before sending its + * first RTCP packet or not (default is \c true). + */ + bool GetUseHalfRTCPIntervalAtStartup() const { return usehalfatstartup; } + + /** If \c v is \c true, the session will send a BYE packet immediately if this is allowed. */ + void SetRequestImmediateBYE(bool v) { immediatebye = v; } + + /** Returns whether the session should send a BYE packet immediately (if allowed) or not (default is \c true). */ + bool GetRequestImmediateBYE() const { return immediatebye; } + + /** When sending a BYE packet, this indicates whether it will be part of an RTCP compound packet + * that begins with a sender report (if allowed) or a receiver report. + */ + void SetSenderReportForBYE(bool v) { SR_BYE = v; } + + /** Returns \c true if a BYE packet will be sent in an RTCP compound packet which starts with a + * sender report; if a receiver report will be used, the function returns \c false (default is \c true). + */ + bool GetSenderReportForBYE() const { return SR_BYE; } + + /** Sets the multiplier to be used when timing out senders. */ + void SetSenderTimeoutMultiplier(double m) { sendermultiplier = m; } + + /** Returns the multiplier to be used when timing out senders (default is 2). */ + double GetSenderTimeoutMultiplier() const { return sendermultiplier; } + + /** Sets the multiplier to be used when timing out members. */ + void SetSourceTimeoutMultiplier(double m) { generaltimeoutmultiplier = m; } + + /** Returns the multiplier to be used when timing out members (default is 5). */ + double GetSourceTimeoutMultiplier() const { return generaltimeoutmultiplier; } + + /** Sets the multiplier to be used when timing out a member after it has sent a BYE packet. */ + void SetBYETimeoutMultiplier(double m) { byetimeoutmultiplier = m; } + + /** Returns the multiplier to be used when timing out a member after it has sent a BYE packet (default is 1). */ + double GetBYETimeoutMultiplier() const { return byetimeoutmultiplier; } + + /** Sets the multiplier to be used when timing out entries in the collision table. */ + void SetCollisionTimeoutMultiplier(double m) { collisionmultiplier = m; } + + /** Returns the multiplier to be used when timing out entries in the collision table (default is 10). */ + double GetCollisionTimeoutMultiplier() const { return collisionmultiplier; } + + /** Sets the multiplier to be used when timing out SDES NOTE information. */ + void SetNoteTimeoutMultiplier(double m) { notemultiplier = m; } + + /** Returns the multiplier to be used when timing out SDES NOTE information (default is 25). */ + double GetNoteTimeoutMultiplier() const { return notemultiplier; } + + /** Sets a flag which indicates if a predefined SSRC identifier should be used. */ + void SetUsePredefinedSSRC(bool f) { usepredefinedssrc = f; } + + /** Returns a flag indicating if a predefined SSRC should be used. */ + bool GetUsePredefinedSSRC() const { return usepredefinedssrc; } + + /** Sets the SSRC which will be used if RTPSessionParams::GetUsePredefinedSSRC returns true. */ + void SetPredefinedSSRC(uint32_t ssrc) { predefinedssrc = ssrc; } + + /** Returns the SSRC which will be used if RTPSessionParams::GetUsePredefinedSSRC returns true. */ + uint32_t GetPredefinedSSRC() const { return predefinedssrc; } + + /** Forces this string to be used as the CNAME identifier. */ + void SetCNAME(const std::string &s) { cname = s; } + + /** Returns the currently set CNAME, is blank when this will be generated automatically (the default). */ + std::string GetCNAME() const { return cname; } + + /** Returns `true` if thread safety was requested using RTPSessionParams::SetNeedThreadSafety. */ + bool NeedThreadSafety() const { return m_needThreadSafety; } +private: + bool acceptown; + bool usepollthread; + size_t maxpacksize; + double owntsunit; + RTPTransmitter::ReceiveMode receivemode; + bool resolvehostname; +#ifdef RTP_SUPPORT_PROBATION + RTPSources::ProbationType probationtype; +#endif // RTP_SUPPORT_PROBATION + + double sessionbandwidth; + double controlfrac; + double senderfrac; + RTPTime mininterval; + bool usehalfatstartup; + bool immediatebye; + bool SR_BYE; + + double sendermultiplier; + double generaltimeoutmultiplier; + double byetimeoutmultiplier; + double collisionmultiplier; + double notemultiplier; + + bool usepredefinedssrc; + uint32_t predefinedssrc; + + std::string cname; + bool m_needThreadSafety; +}; + +} // end namespace + +#endif // RTPSESSIONPARAMS_H + diff --git a/qrtplib/rtpsessionsources.cpp b/qrtplib/rtpsessionsources.cpp new file mode 100644 index 000000000..0bdfa0c59 --- /dev/null +++ b/qrtplib/rtpsessionsources.cpp @@ -0,0 +1,141 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpsessionsources.h" +#include "rtpsession.h" +#include "rtpsourcedata.h" + +namespace qrtplib +{ + +void RTPSessionSources::OnRTPPacket(RTPPacket *pack,const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + rtpsession.OnRTPPacket(pack,receivetime,senderaddress); +} + +void RTPSessionSources::OnRTCPCompoundPacket(RTCPCompoundPacket *pack,const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + if (senderaddress != 0) // don't analyse own RTCP packets again (they're already analysed on their way out) + rtpsession.rtcpsched.AnalyseIncoming(*pack); + rtpsession.OnRTCPCompoundPacket(pack,receivetime,senderaddress); +} + +void RTPSessionSources::OnSSRCCollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp) +{ + if (srcdat->IsOwnSSRC()) + owncollision = true; + rtpsession.OnSSRCCollision(srcdat,senderaddress,isrtp); +} + +void RTPSessionSources::OnCNAMECollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,const uint8_t *cname,size_t cnamelength) +{ + rtpsession.OnCNAMECollision(srcdat,senderaddress,cname,cnamelength); +} + +void RTPSessionSources::OnNewSource(RTPSourceData *srcdat) +{ + rtpsession.OnNewSource(srcdat); +} + +void RTPSessionSources::OnRemoveSource(RTPSourceData *srcdat) +{ + rtpsession.OnRemoveSource(srcdat); +} + +void RTPSessionSources::OnTimeout(RTPSourceData *srcdat) +{ + rtpsession.rtcpsched.ActiveMemberDecrease(); + rtpsession.OnTimeout(srcdat); +} + +void RTPSessionSources::OnBYETimeout(RTPSourceData *srcdat) +{ + rtpsession.OnBYETimeout(srcdat); +} + +void RTPSessionSources::OnBYEPacket(RTPSourceData *srcdat) +{ + rtpsession.rtcpsched.ActiveMemberDecrease(); + rtpsession.OnBYEPacket(srcdat); +} + +void RTPSessionSources::OnAPPPacket(RTCPAPPPacket *apppacket,const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + rtpsession.OnAPPPacket(apppacket,receivetime,senderaddress); +} + +void RTPSessionSources::OnUnknownPacketType(RTCPPacket *rtcppack,const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + rtpsession.OnUnknownPacketType(rtcppack,receivetime,senderaddress); +} + +void RTPSessionSources::OnUnknownPacketFormat(RTCPPacket *rtcppack,const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + rtpsession.OnUnknownPacketFormat(rtcppack,receivetime,senderaddress); +} + +void RTPSessionSources::OnNoteTimeout(RTPSourceData *srcdat) +{ + rtpsession.OnNoteTimeout(srcdat); +} + +void RTPSessionSources::OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled) +{ + rtpsession.OnValidatedRTPPacket(srcdat, rtppack, isonprobation, ispackethandled); +} + +void RTPSessionSources::OnRTCPSenderReport(RTPSourceData *srcdat) +{ + rtpsession.OnRTCPSenderReport(srcdat); +} + +void RTPSessionSources::OnRTCPReceiverReport(RTPSourceData *srcdat) +{ + rtpsession.OnRTCPReceiverReport(srcdat); +} + +void RTPSessionSources::OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, + const void *itemdata, size_t itemlength) +{ + rtpsession.OnRTCPSDESItem(srcdat, t, itemdata, itemlength); +} + +#ifdef RTP_SUPPORT_SDESPRIV +void RTPSessionSources::OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, + const void *valuedata, size_t valuelen) +{ + rtpsession.OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); +} +#endif // RTP_SUPPORT_SDESPRIV + +} // end namespace + diff --git a/qrtplib/rtpsessionsources.h b/qrtplib/rtpsessionsources.h new file mode 100644 index 000000000..dddc035ee --- /dev/null +++ b/qrtplib/rtpsessionsources.h @@ -0,0 +1,93 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpsessionsources.h + */ + +#ifndef RTPSESSIONSOURCES_H + +#define RTPSESSIONSOURCES_H + +#include "rtpconfig.h" +#include "rtpsources.h" + +namespace qrtplib +{ + +class RTPSession; + +class JRTPLIB_IMPORTEXPORT RTPSessionSources : public RTPSources +{ +public: + RTPSessionSources(RTPSession &sess,RTPMemoryManager *mgr) : RTPSources(RTPSources::ProbationStore,mgr),rtpsession(sess) + { owncollision = false; } + ~RTPSessionSources() { } + void ClearOwnCollisionFlag() { owncollision = false; } + bool DetectedOwnCollision() const { return owncollision; } +private: + void OnRTPPacket(RTPPacket *pack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + void OnRTCPCompoundPacket(RTCPCompoundPacket *pack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + void OnSSRCCollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp); + void OnCNAMECollision(RTPSourceData *srcdat,const RTPAddress *senderaddress, + const uint8_t *cname,size_t cnamelength); + void OnNewSource(RTPSourceData *srcdat); + void OnRemoveSource(RTPSourceData *srcdat); + void OnTimeout(RTPSourceData *srcdat); + void OnBYETimeout(RTPSourceData *srcdat); + void OnBYEPacket(RTPSourceData *srcdat); + void OnAPPPacket(RTCPAPPPacket *apppacket,const RTPTime &receivetime, + const RTPAddress *senderaddress); + void OnUnknownPacketType(RTCPPacket *rtcppack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + void OnUnknownPacketFormat(RTCPPacket *rtcppack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + void OnNoteTimeout(RTPSourceData *srcdat); + void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); + void OnRTCPSenderReport(RTPSourceData *srcdat); + void OnRTCPReceiverReport(RTPSourceData *srcdat); + void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, + const void *itemdata, size_t itemlength); +#ifdef RTP_SUPPORT_SDESPRIV + void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, + const void *valuedata, size_t valuelen); +#endif // RTP_SUPPORT_SDESPRIV + + RTPSession &rtpsession; + bool owncollision; +}; + +} // end namespace + +#endif // RTPSESSIONSOURCES_H diff --git a/qrtplib/rtpsocketutil.h b/qrtplib/rtpsocketutil.h new file mode 100644 index 000000000..cee901089 --- /dev/null +++ b/qrtplib/rtpsocketutil.h @@ -0,0 +1,61 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpsocketutil.h + */ + +#ifndef RTPSOCKETUTIL_H + +#define RTPSOCKETUTIL_H + +#include "rtpconfig.h" +#ifdef RTP_SOCKETTYPE_WINSOCK +#include "rtptypes.h" +#endif // RTP_SOCKETTYPE_WINSOCK + +namespace qrtplib +{ + +#ifndef RTP_SOCKETTYPE_WINSOCK + +typedef int SocketType; + +#else + +typedef SOCKET SocketType; + +#endif // RTP_SOCKETTYPE_WINSOCK + +} // end namespace + +#endif // RTPSOCKETUTIL_H diff --git a/qrtplib/rtpsocketutilinternal.h b/qrtplib/rtpsocketutilinternal.h new file mode 100644 index 000000000..a2473efbf --- /dev/null +++ b/qrtplib/rtpsocketutilinternal.h @@ -0,0 +1,79 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpsocketutilinternal.h + */ + +#ifndef RTPSOCKETUTILINTERNAL_H + +#define RTPSOCKETUTILINTERNAL_H + +#ifdef RTP_SOCKETTYPE_WINSOCK + #define RTPSOCKERR INVALID_SOCKET + #define RTPCLOSE(x) closesocket(x) + #define RTPSOCKLENTYPE int + #define RTPIOCTL ioctlsocket +#else // not Win32 + #include + #include + #include + #include + #include + #include + #include + #include + + #ifdef RTP_HAVE_SYS_FILIO + #include + #endif // RTP_HAVE_SYS_FILIO + #ifdef RTP_HAVE_SYS_SOCKIO + #include + #endif // RTP_HAVE_SYS_SOCKIO + #ifdef RTP_SUPPORT_IFADDRS + #include + #endif // RTP_SUPPORT_IFADDRS + + #define RTPSOCKERR -1 + #define RTPCLOSE(x) close(x) + + #ifdef RTP_SOCKLENTYPE_UINT + #define RTPSOCKLENTYPE unsigned int + #else + #define RTPSOCKLENTYPE int + #endif // RTP_SOCKLENTYPE_UINT + + #define RTPIOCTL ioctl +#endif // RTP_SOCKETTYPE_WINSOCK + +#endif // RTPSOCKETUTILINTERNAL_H + diff --git a/qrtplib/rtpsourcedata.cpp b/qrtplib/rtpsourcedata.cpp new file mode 100644 index 000000000..8387b56d0 --- /dev/null +++ b/qrtplib/rtpsourcedata.cpp @@ -0,0 +1,324 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpsourcedata.h" +#include "rtpdefines.h" +#include "rtpaddress.h" +#include "rtpmemorymanager.h" +#ifdef RTP_SUPPORT_NETINET_IN + #include +#endif // RTP_SUPPORT_NETINET_IN + + +#define ACCEPTPACKETCODE \ + *accept = true; \ + \ + sentdata = true; \ + packetsreceived++; \ + numnewpackets++; \ + \ + if (pack->GetExtendedSequenceNumber() == 0) \ + { \ + baseseqnr = 0x0000FFFF; \ + numcycles = 0x00010000; \ + } \ + else \ + baseseqnr = pack->GetExtendedSequenceNumber() - 1; \ + \ + exthighseqnr = baseseqnr + 1; \ + prevpacktime = receivetime; \ + prevexthighseqnr = baseseqnr; \ + savedextseqnr = baseseqnr; \ + \ + pack->SetExtendedSequenceNumber(exthighseqnr); \ + \ + prevtimestamp = pack->GetTimestamp(); \ + lastmsgtime = prevpacktime; \ + if (!ownpacket) /* for own packet, this value is set on an outgoing packet */ \ + lastrtptime = prevpacktime; + +namespace qrtplib +{ + +void RTPSourceStats::ProcessPacket(RTPPacket *pack,const RTPTime &receivetime,double tsunit, + bool ownpacket,bool *accept,bool applyprobation,bool *onprobation) +{ + JRTPLIB_UNUSED(applyprobation); // possibly unused + + // Note that the sequence number in the RTP packet is still just the + // 16 bit number contained in the RTP header + + *onprobation = false; + + if (!sentdata) // no valid packets received yet + { +#ifdef RTP_SUPPORT_PROBATION + if (applyprobation) + { + bool acceptpack = false; + + if (probation) + { + uint16_t pseq; + uint32_t pseq2; + + pseq = prevseqnr; + pseq++; + pseq2 = (uint32_t)pseq; + if (pseq2 == pack->GetExtendedSequenceNumber()) // ok, its the next expected packet + { + prevseqnr = (uint16_t)pack->GetExtendedSequenceNumber(); + probation--; + if (probation == 0) // probation over + acceptpack = true; + else + *onprobation = true; + } + else // not next packet + { + probation = RTP_PROBATIONCOUNT; + prevseqnr = (uint16_t)pack->GetExtendedSequenceNumber(); + *onprobation = true; + } + } + else // first packet received with this SSRC ID, start probation + { + probation = RTP_PROBATIONCOUNT; + prevseqnr = (uint16_t)pack->GetExtendedSequenceNumber(); + *onprobation = true; + } + + if (acceptpack) + { + ACCEPTPACKETCODE + } + else + { + *accept = false; + lastmsgtime = receivetime; + } + } + else // No probation + { + ACCEPTPACKETCODE + } +#else // No compiled-in probation support + + ACCEPTPACKETCODE + +#endif // RTP_SUPPORT_PROBATION + } + else // already got packets + { + uint16_t maxseq16; + uint32_t extseqnr; + + // Adjust max extended sequence number and set extende seq nr of packet + + *accept = true; + packetsreceived++; + numnewpackets++; + + maxseq16 = (uint16_t)(exthighseqnr&0x0000FFFF); + if (pack->GetExtendedSequenceNumber() >= maxseq16) + { + extseqnr = numcycles+pack->GetExtendedSequenceNumber(); + exthighseqnr = extseqnr; + } + else + { + uint16_t dif1,dif2; + + dif1 = ((uint16_t)pack->GetExtendedSequenceNumber()); + dif1 -= maxseq16; + dif2 = maxseq16; + dif2 -= ((uint16_t)pack->GetExtendedSequenceNumber()); + if (dif1 < dif2) + { + numcycles += 0x00010000; + extseqnr = numcycles+pack->GetExtendedSequenceNumber(); + exthighseqnr = extseqnr; + } + else + extseqnr = numcycles+pack->GetExtendedSequenceNumber(); + } + + pack->SetExtendedSequenceNumber(extseqnr); + + // Calculate jitter + + if (tsunit > 0) + { +#if 0 + RTPTime curtime = receivetime; + double diffts1,diffts2,diff; + + curtime -= prevpacktime; + diffts1 = curtime.GetDouble()/tsunit; + diffts2 = (double)pack->GetTimestamp() - (double)prevtimestamp; + diff = diffts1 - diffts2; + if (diff < 0) + diff = -diff; + diff -= djitter; + diff /= 16.0; + djitter += diff; + jitter = (uint32_t)djitter; +#else +RTPTime curtime = receivetime; +double diffts1,diffts2,diff; +uint32_t curts = pack->GetTimestamp(); + +curtime -= prevpacktime; +diffts1 = curtime.GetDouble()/tsunit; + +if (curts > prevtimestamp) +{ + uint32_t unsigneddiff = curts - prevtimestamp; + + if (unsigneddiff < 0x10000000) // okay, curts realy is larger than prevtimestamp + diffts2 = (double)unsigneddiff; + else + { + // wraparound occurred and curts is actually smaller than prevtimestamp + + unsigneddiff = -unsigneddiff; // to get the actual difference (in absolute value) + diffts2 = -((double)unsigneddiff); + } +} +else if (curts < prevtimestamp) +{ + uint32_t unsigneddiff = prevtimestamp - curts; + + if (unsigneddiff < 0x10000000) // okay, curts really is smaller than prevtimestamp + diffts2 = -((double)unsigneddiff); // negative since we actually need curts-prevtimestamp + else + { + // wraparound occurred and curts is actually larger than prevtimestamp + + unsigneddiff = -unsigneddiff; // to get the actual difference (in absolute value) + diffts2 = (double)unsigneddiff; + } +} +else + diffts2 = 0; + +diff = diffts1 - diffts2; +if (diff < 0) + diff = -diff; +diff -= djitter; +diff /= 16.0; +djitter += diff; +jitter = (uint32_t)djitter; +#endif + } + else + { + djitter = 0; + jitter = 0; + } + + prevpacktime = receivetime; + prevtimestamp = pack->GetTimestamp(); + lastmsgtime = prevpacktime; + if (!ownpacket) // for own packet, this value is set on an outgoing packet + lastrtptime = prevpacktime; + } +} + +RTPSourceData::RTPSourceData(uint32_t s, RTPMemoryManager *mgr) : RTPMemoryObject(mgr),SDESinf(mgr),byetime(0,0) +{ + ssrc = s; + issender = false; + iscsrc = false; + timestampunit = -1; + receivedbye = false; + byereason = 0; + byereasonlen = 0; + rtpaddr = 0; + rtcpaddr = 0; + ownssrc = false; + validated = false; + processedinrtcp = false; + isrtpaddrset = false; + isrtcpaddrset = false; +} + +RTPSourceData::~RTPSourceData() +{ + FlushPackets(); + if (byereason) + RTPDeleteByteArray(byereason,GetMemoryManager()); + if (rtpaddr) + RTPDelete(rtpaddr,GetMemoryManager()); + if (rtcpaddr) + RTPDelete(rtcpaddr,GetMemoryManager()); +} + +double RTPSourceData::INF_GetEstimatedTimestampUnit() const +{ + if (!SRprevinf.HasInfo()) + return -1.0; + + RTPTime t1 = RTPTime(SRinf.GetNTPTimestamp()); + RTPTime t2 = RTPTime(SRprevinf.GetNTPTimestamp()); + if (t1.IsZero() || t2.IsZero()) // one of the times couldn't be calculated + return -1.0; + + if (t1 <= t2) + return -1.0; + + t1 -= t2; // get the time difference + + uint32_t tsdiff = SRinf.GetRTPTimestamp()-SRprevinf.GetRTPTimestamp(); + + return (t1.GetDouble()/((double)tsdiff)); +} + +RTPTime RTPSourceData::INF_GetRoundtripTime() const +{ + if (!RRinf.HasInfo()) + return RTPTime(0,0); + if (RRinf.GetDelaySinceLastSR() == 0 && RRinf.GetLastSRTimestamp() == 0) + return RTPTime(0,0); + + RTPNTPTime recvtime = RRinf.GetReceiveTime().GetNTPTime(); + uint32_t rtt = ((recvtime.GetMSW()&0xFFFF)<<16)|((recvtime.GetLSW()>>16)&0xFFFF); + rtt -= RRinf.GetLastSRTimestamp(); + rtt -= RRinf.GetDelaySinceLastSR(); + + double drtt = (((double)rtt)/65536.0); + return RTPTime(drtt); +} + +} // end namespace + + diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h new file mode 100644 index 000000000..ede7ed7c7 --- /dev/null +++ b/qrtplib/rtpsourcedata.h @@ -0,0 +1,476 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpsourcedata.h + */ + +#ifndef RTPSOURCEDATA_H + +#define RTPSOURCEDATA_H + +#include "rtpconfig.h" +#include "rtptimeutilities.h" +#include "rtppacket.h" +#include "rtcpsdesinfo.h" +#include "rtptypes.h" +#include "rtpsources.h" +#include "rtpmemoryobject.h" +#include + +namespace qrtplib +{ + +class RTPAddress; + +class JRTPLIB_IMPORTEXPORT RTCPSenderReportInfo +{ +public: + RTCPSenderReportInfo():ntptimestamp(0,0),receivetime(0,0) { hasinfo = false; rtptimestamp = 0; packetcount = 0; bytecount = 0; } + void Set(const RTPNTPTime &ntptime,uint32_t rtptime,uint32_t pcount, + uint32_t bcount,const RTPTime &rcvtime) { ntptimestamp = ntptime; rtptimestamp = rtptime; packetcount = pcount; bytecount = bcount; receivetime = rcvtime; hasinfo = true; } + + bool HasInfo() const { return hasinfo; } + RTPNTPTime GetNTPTimestamp() const { return ntptimestamp; } + uint32_t GetRTPTimestamp() const { return rtptimestamp; } + uint32_t GetPacketCount() const { return packetcount; } + uint32_t GetByteCount() const { return bytecount; } + RTPTime GetReceiveTime() const { return receivetime; } +private: + bool hasinfo; + RTPNTPTime ntptimestamp; + uint32_t rtptimestamp; + uint32_t packetcount; + uint32_t bytecount; + RTPTime receivetime; +}; + +class JRTPLIB_IMPORTEXPORT RTCPReceiverReportInfo +{ +public: + RTCPReceiverReportInfo():receivetime(0,0) { hasinfo = false; fractionlost = 0; packetslost = 0; exthighseqnr = 0; jitter = 0; lsr = 0; dlsr = 0; } + void Set(uint8_t fraclost,int32_t plost,uint32_t exthigh, + uint32_t jit,uint32_t l,uint32_t dl,const RTPTime &rcvtime) { fractionlost = ((double)fraclost)/256.0; packetslost = plost; exthighseqnr = exthigh; jitter = jit; lsr = l; dlsr = dl; receivetime = rcvtime; hasinfo = true; } + + bool HasInfo() const { return hasinfo; } + double GetFractionLost() const { return fractionlost; } + int32_t GetPacketsLost() const { return packetslost; } + uint32_t GetExtendedHighestSequenceNumber() const { return exthighseqnr; } + uint32_t GetJitter() const { return jitter; } + uint32_t GetLastSRTimestamp() const { return lsr; } + uint32_t GetDelaySinceLastSR() const { return dlsr; } + RTPTime GetReceiveTime() const { return receivetime; } +private: + bool hasinfo; + double fractionlost; + int32_t packetslost; + uint32_t exthighseqnr; + uint32_t jitter; + uint32_t lsr; + uint32_t dlsr; + RTPTime receivetime; +}; + +class JRTPLIB_IMPORTEXPORT RTPSourceStats +{ +public: + RTPSourceStats(); + void ProcessPacket(RTPPacket *pack,const RTPTime &receivetime,double tsunit,bool ownpacket,bool *accept,bool applyprobation,bool *onprobation); + + bool HasSentData() const { return sentdata; } + uint32_t GetNumPacketsReceived() const { return packetsreceived; } + uint32_t GetBaseSequenceNumber() const { return baseseqnr; } + uint32_t GetExtendedHighestSequenceNumber() const { return exthighseqnr; } + uint32_t GetJitter() const { return jitter; } + + int32_t GetNumPacketsReceivedInInterval() const { return numnewpackets; } + uint32_t GetSavedExtendedSequenceNumber() const { return savedextseqnr; } + void StartNewInterval() { numnewpackets = 0; savedextseqnr = exthighseqnr; } + + void SetLastMessageTime(const RTPTime &t) { lastmsgtime = t; } + RTPTime GetLastMessageTime() const { return lastmsgtime; } + void SetLastRTPPacketTime(const RTPTime &t) { lastrtptime = t; } + RTPTime GetLastRTPPacketTime() const { return lastrtptime; } + + void SetLastNoteTime(const RTPTime &t) { lastnotetime = t; } + RTPTime GetLastNoteTime() const { return lastnotetime; } +private: + bool sentdata; + uint32_t packetsreceived; + uint32_t numcycles; // shifted left 16 bits + uint32_t baseseqnr; + uint32_t exthighseqnr,prevexthighseqnr; + uint32_t jitter,prevtimestamp; + double djitter; + RTPTime prevpacktime; + RTPTime lastmsgtime; + RTPTime lastrtptime; + RTPTime lastnotetime; + uint32_t numnewpackets; + uint32_t savedextseqnr; +#ifdef RTP_SUPPORT_PROBATION + uint16_t prevseqnr; + int probation; +#endif // RTP_SUPPORT_PROBATION +}; + +inline RTPSourceStats::RTPSourceStats():prevpacktime(0,0),lastmsgtime(0,0),lastrtptime(0,0),lastnotetime(0,0) +{ + sentdata = false; + packetsreceived = 0; + baseseqnr = 0; + exthighseqnr = 0; + prevexthighseqnr = 0; + jitter = 0; + numcycles = 0; + numnewpackets = 0; + prevtimestamp = 0; + djitter = 0; + savedextseqnr = 0; +#ifdef RTP_SUPPORT_PROBATION + probation = 0; + prevseqnr = 0; +#endif // RTP_SUPPORT_PROBATION +} + +/** Describes an entry in the RTPSources source table. */ +class JRTPLIB_IMPORTEXPORT RTPSourceData : public RTPMemoryObject +{ +protected: + RTPSourceData(uint32_t ssrc, RTPMemoryManager *mgr = 0); + virtual ~RTPSourceData(); +public: + /** Extracts the first packet of this participants RTP packet queue. */ + RTPPacket *GetNextPacket(); + + /** Clears the participant's RTP packet list. */ + void FlushPackets(); + + /** Returns \c true if there are RTP packets which can be extracted. */ + bool HasData() const { if (!validated) return false; return packetlist.empty()?false:true; } + + /** Returns the SSRC identifier for this member. */ + uint32_t GetSSRC() const { return ssrc; } + + /** Returns \c true if the participant was added using the RTPSources member function CreateOwnSSRC and + * returns \c false otherwise. + */ + bool IsOwnSSRC() const { return ownssrc; } + + /** Returns \c true if the source identifier is actually a CSRC from an RTP packet. */ + bool IsCSRC() const { return iscsrc; } + + /** Returns \c true if this member is marked as a sender and \c false if not. */ + bool IsSender() const { return issender; } + + /** Returns \c true if the participant is validated, which is the case if a number of + * consecutive RTP packets have been received or if a CNAME item has been received for + * this participant. + */ + bool IsValidated() const { return validated; } + + /** Returns \c true if the source was validated and had not yet sent a BYE packet. */ + bool IsActive() const { if (!validated) return false; if (receivedbye) return false; return true; } + + /** This function is used by the RTCPPacketBuilder class to mark whether this participant's + * information has been processed in a report block or not. + */ + void SetProcessedInRTCP(bool v) { processedinrtcp = v; } + + /** This function is used by the RTCPPacketBuilder class and returns whether this participant + * has been processed in a report block or not. + */ + bool IsProcessedInRTCP() const { return processedinrtcp; } + + /** Returns \c true if the address from which this participant's RTP packets originate has + * already been set. + */ + bool IsRTPAddressSet() const { return isrtpaddrset; } + + /** Returns \c true if the address from which this participant's RTCP packets originate has + * already been set. + */ + bool IsRTCPAddressSet() const { return isrtcpaddrset; } + + /** Returns the address from which this participant's RTP packets originate. + * Returns the address from which this participant's RTP packets originate. If the address has + * been set and the returned value is NULL, this indicates that it originated from the local + * participant. + */ + const RTPAddress *GetRTPDataAddress() const { return rtpaddr; } + + /** Returns the address from which this participant's RTCP packets originate. + * Returns the address from which this participant's RTCP packets originate. If the address has + * been set and the returned value is NULL, this indicates that it originated from the local + * participant. + */ + const RTPAddress *GetRTCPDataAddress() const { return rtcpaddr; } + + /** Returns \c true if we received a BYE message for this participant and \c false otherwise. */ + bool ReceivedBYE() const { return receivedbye; } + + /** Returns the reason for leaving contained in the BYE packet of this participant. + * Returns the reason for leaving contained in the BYE packet of this participant. The length of + * the reason is stored in \c len. + */ + uint8_t *GetBYEReason(size_t *len) const { *len = byereasonlen; return byereason; } + + /** Returns the time at which the BYE packet was received. */ + RTPTime GetBYETime() const { return byetime; } + + /** Sets the value for the timestamp unit to be used in jitter calculations for data received from this participant. + * Sets the value for the timestamp unit to be used in jitter calculations for data received from this participant. + * If not set, the library uses an approximation for the timestamp unit which is calculated from two consecutive + * RTCP sender reports. The timestamp unit is defined as a time interval divided by the corresponding timestamp + * interval. For 8000 Hz audio this would be 1/8000. For video, often a timestamp unit of 1/90000 is used. + */ + void SetTimestampUnit(double tsu) { timestampunit = tsu; } + + /** Returns the timestamp unit used for this participant. */ + double GetTimestampUnit() const { return timestampunit; } + + /** Returns \c true if an RTCP sender report has been received from this participant. */ + bool SR_HasInfo() const { return SRinf.HasInfo(); } + + /** Returns the NTP timestamp contained in the last sender report. */ + RTPNTPTime SR_GetNTPTimestamp() const { return SRinf.GetNTPTimestamp(); } + + /** Returns the RTP timestamp contained in the last sender report. */ + uint32_t SR_GetRTPTimestamp() const { return SRinf.GetRTPTimestamp(); } + + /** Returns the packet count contained in the last sender report. */ + uint32_t SR_GetPacketCount() const { return SRinf.GetPacketCount(); } + + /** Returns the octet count contained in the last sender report. */ + uint32_t SR_GetByteCount() const { return SRinf.GetByteCount(); } + + /** Returns the time at which the last sender report was received. */ + RTPTime SR_GetReceiveTime() const { return SRinf.GetReceiveTime(); } + + /** Returns \c true if more than one RTCP sender report has been received. */ + bool SR_Prev_HasInfo() const { return SRprevinf.HasInfo(); } + + /** Returns the NTP timestamp contained in the second to last sender report. */ + RTPNTPTime SR_Prev_GetNTPTimestamp() const { return SRprevinf.GetNTPTimestamp(); } + + /** Returns the RTP timestamp contained in the second to last sender report. */ + uint32_t SR_Prev_GetRTPTimestamp() const { return SRprevinf.GetRTPTimestamp(); } + + /** Returns the packet count contained in the second to last sender report. */ + uint32_t SR_Prev_GetPacketCount() const { return SRprevinf.GetPacketCount(); } + + /** Returns the octet count contained in the second to last sender report. */ + uint32_t SR_Prev_GetByteCount() const { return SRprevinf.GetByteCount(); } + + /** Returns the time at which the second to last sender report was received. */ + RTPTime SR_Prev_GetReceiveTime() const { return SRprevinf.GetReceiveTime(); } + + /** Returns \c true if this participant sent a receiver report with information about the reception of our data. */ + bool RR_HasInfo() const { return RRinf.HasInfo(); } + + /** Returns the fraction lost value from the last report. */ + double RR_GetFractionLost() const { return RRinf.GetFractionLost(); } + + /** Returns the number of lost packets contained in the last report. */ + int32_t RR_GetPacketsLost() const { return RRinf.GetPacketsLost(); } + + /** Returns the extended highest sequence number contained in the last report. */ + uint32_t RR_GetExtendedHighestSequenceNumber() const { return RRinf.GetExtendedHighestSequenceNumber(); } + + /** Returns the jitter value from the last report. */ + uint32_t RR_GetJitter() const { return RRinf.GetJitter(); } + + /** Returns the LSR value from the last report. */ + uint32_t RR_GetLastSRTimestamp() const { return RRinf.GetLastSRTimestamp(); } + + /** Returns the DLSR value from the last report. */ + uint32_t RR_GetDelaySinceLastSR() const { return RRinf.GetDelaySinceLastSR(); } + + /** Returns the time at which the last report was received. */ + RTPTime RR_GetReceiveTime() const { return RRinf.GetReceiveTime(); } + + /** Returns \c true if this participant sent more than one receiver report with information + * about the reception of our data. + */ + bool RR_Prev_HasInfo() const { return RRprevinf.HasInfo(); } + + /** Returns the fraction lost value from the second to last report. */ + double RR_Prev_GetFractionLost() const { return RRprevinf.GetFractionLost(); } + + /** Returns the number of lost packets contained in the second to last report. */ + int32_t RR_Prev_GetPacketsLost() const { return RRprevinf.GetPacketsLost(); } + + /** Returns the extended highest sequence number contained in the second to last report. */ + uint32_t RR_Prev_GetExtendedHighestSequenceNumber() const { return RRprevinf.GetExtendedHighestSequenceNumber(); } + + /** Returns the jitter value from the second to last report. */ + uint32_t RR_Prev_GetJitter() const { return RRprevinf.GetJitter(); } + + /** Returns the LSR value from the second to last report. */ + uint32_t RR_Prev_GetLastSRTimestamp() const { return RRprevinf.GetLastSRTimestamp(); } + + /** Returns the DLSR value from the second to last report. */ + uint32_t RR_Prev_GetDelaySinceLastSR() const { return RRprevinf.GetDelaySinceLastSR(); } + + /** Returns the time at which the second to last report was received. */ + RTPTime RR_Prev_GetReceiveTime() const { return RRprevinf.GetReceiveTime(); } + + /** Returns \c true if validated RTP packets have been received from this participant. */ + bool INF_HasSentData() const { return stats.HasSentData(); } + + /** Returns the total number of received packets from this participant. */ + int32_t INF_GetNumPacketsReceived() const { return stats.GetNumPacketsReceived(); } + + /** Returns the base sequence number of this participant. */ + uint32_t INF_GetBaseSequenceNumber() const { return stats.GetBaseSequenceNumber(); } + + /** Returns the extended highest sequence number received from this participant. */ + uint32_t INF_GetExtendedHighestSequenceNumber() const { return stats.GetExtendedHighestSequenceNumber(); } + + /** Returns the current jitter value for this participant. */ + uint32_t INF_GetJitter() const { return stats.GetJitter(); } + + /** Returns the time at which something was last heard from this member. */ + RTPTime INF_GetLastMessageTime() const { return stats.GetLastMessageTime(); } + + /** Returns the time at which the last RTP packet was received. */ + RTPTime INF_GetLastRTPPacketTime() const { return stats.GetLastRTPPacketTime(); } + + /** Returns the estimated timestamp unit, calculated from two consecutive sender reports. */ + double INF_GetEstimatedTimestampUnit() const; + + /** Returns the number of packets received since a new interval was started with INF_StartNewInterval. */ + uint32_t INF_GetNumPacketsReceivedInInterval() const { return stats.GetNumPacketsReceivedInInterval(); } + + /** Returns the extended sequence number which was stored by the INF_StartNewInterval call. */ + uint32_t INF_GetSavedExtendedSequenceNumber() const { return stats.GetSavedExtendedSequenceNumber(); } + + /** Starts a new interval to count received packets in; this also stores the current extended highest sequence + * number to be able to calculate the packet loss during the interval. + */ + void INF_StartNewInterval() { stats.StartNewInterval(); } + + /** Estimates the round trip time by using the LSR and DLSR info from the last receiver report. */ + RTPTime INF_GetRoundtripTime() const; + + /** Returns the time at which the last SDES NOTE item was received. */ + RTPTime INF_GetLastSDESNoteTime() const { return stats.GetLastNoteTime(); } + + /** Returns a pointer to the SDES CNAME item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetCNAME(size_t *len) const { return SDESinf.GetCNAME(len); } + + /** Returns a pointer to the SDES name item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetName(size_t *len) const { return SDESinf.GetName(len); } + + /** Returns a pointer to the SDES e-mail item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetEMail(size_t *len) const { return SDESinf.GetEMail(len); } + + /** Returns a pointer to the SDES phone item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetPhone(size_t *len) const { return SDESinf.GetPhone(len); } + + /** Returns a pointer to the SDES location item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetLocation(size_t *len) const { return SDESinf.GetLocation(len); } + + /** Returns a pointer to the SDES tool item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetTool(size_t *len) const { return SDESinf.GetTool(len); } + + /** Returns a pointer to the SDES note item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetNote(size_t *len) const { return SDESinf.GetNote(len); } + +#ifdef RTP_SUPPORT_SDESPRIV + /** Starts the iteration over the stored SDES private item prefixes and their associated values. */ + void SDES_GotoFirstPrivateValue() { SDESinf.GotoFirstPrivateValue(); } + + /** If available, returns \c true and stores the next SDES private item prefix in \c prefix and its length in + * \c prefixlen; the associated value and its length are then stored in \c value and \c valuelen. + */ + bool SDES_GetNextPrivateValue(uint8_t **prefix,size_t *prefixlen,uint8_t **value,size_t *valuelen) { return SDESinf.GetNextPrivateValue(prefix,prefixlen,value,valuelen); } + + /** Looks for the entry which corresponds to the SDES private item prefix \c prefix with length + * \c prefixlen; if found, the function returns \c true and stores the associated value and + * its length in \c value and \c valuelen respectively. + */ + bool SDES_GetPrivateValue(uint8_t *prefix,size_t prefixlen,uint8_t **value,size_t *valuelen) const { return SDESinf.GetPrivateValue(prefix,prefixlen,value,valuelen); } +#endif // RTP_SUPPORT_SDESPRIV + +protected: + std::list packetlist; + + uint32_t ssrc; + bool ownssrc; + bool iscsrc; + double timestampunit; + bool receivedbye; + bool validated; + bool processedinrtcp; + bool issender; + + RTCPSenderReportInfo SRinf,SRprevinf; + RTCPReceiverReportInfo RRinf,RRprevinf; + RTPSourceStats stats; + RTCPSDESInfo SDESinf; + + bool isrtpaddrset,isrtcpaddrset; + RTPAddress *rtpaddr,*rtcpaddr; + + RTPTime byetime; + uint8_t *byereason; + size_t byereasonlen; +}; + +inline RTPPacket *RTPSourceData::GetNextPacket() +{ + if (!validated) + return 0; + + RTPPacket *p; + + if (packetlist.empty()) + return 0; + p = *(packetlist.begin()); + packetlist.pop_front(); + return p; +} + +inline void RTPSourceData::FlushPackets() +{ + std::list::const_iterator it; + + for (it = packetlist.begin() ; it != packetlist.end() ; ++it) + RTPDelete(*it,GetMemoryManager()); + packetlist.clear(); +} + +} // end namespace + +#endif // RTPSOURCEDATA_H + diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp new file mode 100644 index 000000000..9e5fb4ebc --- /dev/null +++ b/qrtplib/rtpsources.cpp @@ -0,0 +1,1277 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpsources.h" +#include "rtperrors.h" +#include "rtprawpacket.h" +#include "rtpinternalsourcedata.h" +#include "rtptimeutilities.h" +#include "rtpdefines.h" +#include "rtcpcompoundpacket.h" +#include "rtcppacket.h" +#include "rtcpapppacket.h" +#include "rtcpbyepacket.h" +#include "rtcpsdespacket.h" +#include "rtcpsrpacket.h" +#include "rtcprrpacket.h" +#include "rtptransmitter.h" + +namespace qrtplib +{ + +RTPSources::RTPSources(ProbationType probtype,RTPMemoryManager *mgr) : RTPMemoryObject(mgr),sourcelist(mgr,RTPMEM_TYPE_CLASS_SOURCETABLEHASHELEMENT) +{ + JRTPLIB_UNUSED(probtype); // possibly unused + + totalcount = 0; + sendercount = 0; + activecount = 0; + owndata = 0; +#ifdef RTP_SUPPORT_PROBATION + probationtype = probtype; +#endif // RTP_SUPPORT_PROBATION +} + +RTPSources::~RTPSources() +{ + Clear(); +} + +void RTPSources::Clear() +{ + ClearSourceList(); +} + +void RTPSources::ClearSourceList() +{ + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *sourcedata; + + sourcedata = sourcelist.GetCurrentElement(); + RTPDelete(sourcedata,GetMemoryManager()); + sourcelist.GotoNextElement(); + } + sourcelist.Clear(); + owndata = 0; + totalcount = 0; + sendercount = 0; + activecount = 0; +} + +int RTPSources::CreateOwnSSRC(uint32_t ssrc) +{ + if (owndata != 0) + return ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC; + if (GotEntry(ssrc)) + return ERR_RTP_SOURCES_SSRCEXISTS; + + int status; + bool created; + + status = ObtainSourceDataInstance(ssrc,&owndata,&created); + if (status < 0) + { + owndata = 0; // just to make sure + return status; + } + owndata->SetOwnSSRC(); + owndata->SetRTPDataAddress(0); + owndata->SetRTCPDataAddress(0); + + // we've created a validated ssrc, so we should increase activecount + activecount++; + + OnNewSource(owndata); + return 0; +} + +int RTPSources::DeleteOwnSSRC() +{ + if (owndata == 0) + return ERR_RTP_SOURCES_DONTHAVEOWNSSRC; + + uint32_t ssrc = owndata->GetSSRC(); + + sourcelist.GotoElement(ssrc); + sourcelist.DeleteCurrentElement(); + + totalcount--; + if (owndata->IsSender()) + sendercount--; + if (owndata->IsActive()) + activecount--; + + OnRemoveSource(owndata); + + RTPDelete(owndata,GetMemoryManager()); + owndata = 0; + return 0; +} + +void RTPSources::SentRTPPacket() +{ + if (owndata == 0) + return; + + bool prevsender = owndata->IsSender(); + + owndata->SentRTPPacket(); + if (!prevsender && owndata->IsSender()) + sendercount++; +} + +int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *rtptrans,bool acceptownpackets) +{ + RTPTransmitter *transmitters[1]; + int num; + + transmitters[0] = rtptrans; + if (rtptrans == 0) + num = 0; + else + num = 1; + return ProcessRawPacket(rawpack,transmitters,num,acceptownpackets); +} + +int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *rtptrans[],int numtrans,bool acceptownpackets) +{ + int status; + + if (rawpack->IsRTP()) // RTP packet + { + RTPPacket *rtppack; + + // First, we'll see if the packet can be parsed + rtppack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPPACKET) RTPPacket(*rawpack,GetMemoryManager()); + if (rtppack == 0) + return ERR_RTP_OUTOFMEM; + if ((status = rtppack->GetCreationError()) < 0) + { + if (status == ERR_RTP_PACKET_INVALIDPACKET) + { + RTPDelete(rtppack,GetMemoryManager()); + rtppack = 0; + } + else + { + RTPDelete(rtppack,GetMemoryManager()); + return status; + } + } + + // Check if the packet was valid + if (rtppack != 0) + { + bool stored = false; + bool ownpacket = false; + int i; + const RTPAddress *senderaddress = rawpack->GetSenderAddress(); + + for (i = 0 ; !ownpacket && i < numtrans ; i++) + { + if (rtptrans[i]->ComesFromThisTransmitter(senderaddress)) + ownpacket = true; + } + + // Check if the packet is our own. + if (ownpacket) + { + // Now it depends on the user's preference + // what to do with this packet: + if (acceptownpackets) + { + // sender addres for own packets has to be NULL! + if ((status = ProcessRTPPacket(rtppack,rawpack->GetReceiveTime(),0,&stored)) < 0) + { + if (!stored) + RTPDelete(rtppack,GetMemoryManager()); + return status; + } + } + } + else + { + if ((status = ProcessRTPPacket(rtppack,rawpack->GetReceiveTime(),senderaddress,&stored)) < 0) + { + if (!stored) + RTPDelete(rtppack,GetMemoryManager()); + return status; + } + } + if (!stored) + RTPDelete(rtppack,GetMemoryManager()); + } + } + else // RTCP packet + { + RTCPCompoundPacket rtcpcomppack(*rawpack,GetMemoryManager()); + bool valid = false; + + if ((status = rtcpcomppack.GetCreationError()) < 0) + { + if (status != ERR_RTP_RTCPCOMPOUND_INVALIDPACKET) + return status; + } + else + valid = true; + + + if (valid) + { + bool ownpacket = false; + int i; + const RTPAddress *senderaddress = rawpack->GetSenderAddress(); + + for (i = 0 ; !ownpacket && i < numtrans ; i++) + { + if (rtptrans[i]->ComesFromThisTransmitter(senderaddress)) + ownpacket = true; + } + + // First check if it's a packet of this session. + if (ownpacket) + { + if (acceptownpackets) + { + // sender address for own packets has to be NULL + status = ProcessRTCPCompoundPacket(&rtcpcomppack,rawpack->GetReceiveTime(),0); + if (status < 0) + return status; + } + } + else // not our own packet + { + status = ProcessRTCPCompoundPacket(&rtcpcomppack,rawpack->GetReceiveTime(),rawpack->GetSenderAddress()); + if (status < 0) + return status; + } + } + } + + return 0; +} + +int RTPSources::ProcessRTPPacket(RTPPacket *rtppack,const RTPTime &receivetime,const RTPAddress *senderaddress,bool *stored) +{ + uint32_t ssrc; + RTPInternalSourceData *srcdat; + int status; + bool created; + + OnRTPPacket(rtppack,receivetime,senderaddress); + + *stored = false; + + ssrc = rtppack->GetSSRC(); + if ((status = ObtainSourceDataInstance(ssrc,&srcdat,&created)) < 0) + return status; + + if (created) + { + if ((status = srcdat->SetRTPDataAddress(senderaddress)) < 0) + return status; + } + else // got a previously existing source + { + if (CheckCollision(srcdat,senderaddress,true)) + return 0; // ignore packet on collision + } + + bool prevsender = srcdat->IsSender(); + bool prevactive = srcdat->IsActive(); + + uint32_t CSRCs[RTP_MAXCSRCS]; + int numCSRCs = rtppack->GetCSRCCount(); + if (numCSRCs > RTP_MAXCSRCS) // shouldn't happen, but better to check than go out of bounds + numCSRCs = RTP_MAXCSRCS; + + for (int i = 0 ; i < numCSRCs ; i++) + CSRCs[i] = rtppack->GetCSRC(i); + + // The packet comes from a valid source, we can process it further now + // The following function should delete rtppack itself if something goes + // wrong + if ((status = srcdat->ProcessRTPPacket(rtppack,receivetime,stored,this)) < 0) + return status; + + // NOTE: we cannot use 'rtppack' anymore since it may have been deleted in + // OnValidatedRTPPacket + + if (!prevsender && srcdat->IsSender()) + sendercount++; + if (!prevactive && srcdat->IsActive()) + activecount++; + + if (created) + OnNewSource(srcdat); + + if (srcdat->IsValidated()) // process the CSRCs + { + RTPInternalSourceData *csrcdat; + bool createdcsrc; + + int num = numCSRCs; + int i; + + for (i = 0 ; i < num ; i++) + { + if ((status = ObtainSourceDataInstance(CSRCs[i],&csrcdat,&createdcsrc)) < 0) + return status; + if (createdcsrc) + { + csrcdat->SetCSRC(); + if (csrcdat->IsActive()) + activecount++; + OnNewSource(csrcdat); + } + else // already found an entry, possibly because of RTCP data + { + if (!CheckCollision(csrcdat,senderaddress,true)) + csrcdat->SetCSRC(); + } + } + } + + return 0; +} + +int RTPSources::ProcessRTCPCompoundPacket(RTCPCompoundPacket *rtcpcomppack,const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + RTCPPacket *rtcppack; + int status; + bool gotownssrc = ((owndata == 0)?false:true); + uint32_t ownssrc = ((owndata != 0)?owndata->GetSSRC():0); + + OnRTCPCompoundPacket(rtcpcomppack,receivetime,senderaddress); + + rtcpcomppack->GotoFirstPacket(); + while ((rtcppack = rtcpcomppack->GetNextPacket()) != 0) + { + if (rtcppack->IsKnownFormat()) + { + switch (rtcppack->GetPacketType()) + { + case RTCPPacket::SR: + { + RTCPSRPacket *p = (RTCPSRPacket *)rtcppack; + uint32_t senderssrc = p->GetSenderSSRC(); + + status = ProcessRTCPSenderInfo(senderssrc,p->GetNTPTimestamp(),p->GetRTPTimestamp(), + p->GetSenderPacketCount(),p->GetSenderOctetCount(), + receivetime,senderaddress); + if (status < 0) + return status; + + bool gotinfo = false; + if (gotownssrc) + { + int i; + int num = p->GetReceptionReportCount(); + for (i = 0 ; i < num ; i++) + { + if (p->GetSSRC(i) == ownssrc) // data is meant for us + { + gotinfo = true; + status = ProcessRTCPReportBlock(senderssrc,p->GetFractionLost(i),p->GetLostPacketCount(i), + p->GetExtendedHighestSequenceNumber(i),p->GetJitter(i),p->GetLSR(i), + p->GetDLSR(i),receivetime,senderaddress); + if (status < 0) + return status; + } + } + } + if (!gotinfo) + { + status = UpdateReceiveTime(senderssrc,receivetime,senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::RR: + { + RTCPRRPacket *p = (RTCPRRPacket *)rtcppack; + uint32_t senderssrc = p->GetSenderSSRC(); + + bool gotinfo = false; + + if (gotownssrc) + { + int i; + int num = p->GetReceptionReportCount(); + for (i = 0 ; i < num ; i++) + { + if (p->GetSSRC(i) == ownssrc) + { + gotinfo = true; + status = ProcessRTCPReportBlock(senderssrc,p->GetFractionLost(i),p->GetLostPacketCount(i), + p->GetExtendedHighestSequenceNumber(i),p->GetJitter(i),p->GetLSR(i), + p->GetDLSR(i),receivetime,senderaddress); + if (status < 0) + return status; + } + } + } + if (!gotinfo) + { + status = UpdateReceiveTime(senderssrc,receivetime,senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::SDES: + { + RTCPSDESPacket *p = (RTCPSDESPacket *)rtcppack; + + if (p->GotoFirstChunk()) + { + do + { + uint32_t sdesssrc = p->GetChunkSSRC(); + bool updated = false; + if (p->GotoFirstItem()) + { + do + { + RTCPSDESPacket::ItemType t; + + if ((t = p->GetItemType()) != RTCPSDESPacket::PRIV) + { + updated = true; + status = ProcessSDESNormalItem(sdesssrc,t,p->GetItemLength(),p->GetItemData(),receivetime,senderaddress); + if (status < 0) + return status; + } +#ifdef RTP_SUPPORT_SDESPRIV + else + { + updated = true; + status = ProcessSDESPrivateItem(sdesssrc,p->GetPRIVPrefixLength(),p->GetPRIVPrefixData(),p->GetPRIVValueLength(), + p->GetPRIVValueData(),receivetime,senderaddress); + if (status < 0) + return status; + } +#endif // RTP_SUPPORT_SDESPRIV + } while (p->GotoNextItem()); + } + if (!updated) + { + status = UpdateReceiveTime(sdesssrc,receivetime,senderaddress); + if (status < 0) + return status; + } + } while (p->GotoNextChunk()); + } + } + break; + case RTCPPacket::BYE: + { + RTCPBYEPacket *p = (RTCPBYEPacket *)rtcppack; + int i; + int num = p->GetSSRCCount(); + + for (i = 0 ; i < num ; i++) + { + uint32_t byessrc = p->GetSSRC(i); + status = ProcessBYE(byessrc,p->GetReasonLength(),p->GetReasonData(),receivetime,senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::APP: + { + RTCPAPPPacket *p = (RTCPAPPPacket *)rtcppack; + + OnAPPPacket(p,receivetime,senderaddress); + } + break; + case RTCPPacket::Unknown: + default: + { + OnUnknownPacketType(rtcppack,receivetime,senderaddress); + } + break; + } + } + else + { + OnUnknownPacketFormat(rtcppack,receivetime,senderaddress); + } + } + + return 0; +} + +bool RTPSources::GotoFirstSource() +{ + sourcelist.GotoFirstElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; +} + +bool RTPSources::GotoNextSource() +{ + sourcelist.GotoNextElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; +} + +bool RTPSources::GotoPreviousSource() +{ + sourcelist.GotoPreviousElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; +} + +bool RTPSources::GotoFirstSourceWithData() +{ + bool found = false; + + sourcelist.GotoFirstElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; + + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoNextElement(); + } + + return found; +} + +bool RTPSources::GotoNextSourceWithData() +{ + bool found = false; + + sourcelist.GotoNextElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; + + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoNextElement(); + } + + return found; +} + +bool RTPSources::GotoPreviousSourceWithData() +{ + bool found = false; + + sourcelist.GotoPreviousElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; + + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoPreviousElement(); + } + + return found; +} + +RTPSourceData *RTPSources::GetCurrentSourceInfo() +{ + if (!sourcelist.HasCurrentElement()) + return 0; + return sourcelist.GetCurrentElement(); +} + +RTPSourceData *RTPSources::GetSourceInfo(uint32_t ssrc) +{ + if (sourcelist.GotoElement(ssrc) < 0) + return 0; + if (!sourcelist.HasCurrentElement()) + return 0; + return sourcelist.GetCurrentElement(); +} + +bool RTPSources::GotEntry(uint32_t ssrc) +{ + return sourcelist.HasElement(ssrc); +} + +RTPPacket *RTPSources::GetNextPacket() +{ + if (!sourcelist.HasCurrentElement()) + return 0; + + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + RTPPacket *pack = srcdat->GetNextPacket(); + return pack; +} + +int RTPSources::ProcessRTCPSenderInfo(uint32_t ssrc,const RTPNTPTime &ntptime,uint32_t rtptime, + uint32_t packetcount,uint32_t octetcount,const RTPTime &receivetime, + const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + + status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + srcdat->ProcessSenderInfo(ntptime,rtptime,packetcount,octetcount,receivetime); + + // Call the callback + if (created) + OnNewSource(srcdat); + + OnRTCPSenderReport(srcdat); + + return 0; +} + +int RTPSources::ProcessRTCPReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t lostpackets, + uint32_t exthighseqnr,uint32_t jitter,uint32_t lsr, + uint32_t dlsr,const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + + status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + srcdat->ProcessReportBlock(fractionlost,lostpackets,exthighseqnr,jitter,lsr,dlsr,receivetime); + + // Call the callback + if (created) + OnNewSource(srcdat); + + OnRTCPReceiverReport(srcdat); + + return 0; +} + +int RTPSources::ProcessSDESNormalItem(uint32_t ssrc,RTCPSDESPacket::ItemType t,size_t itemlength, + const void *itemdata,const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created,cnamecollis; + int status; + uint8_t sdesid; + bool prevactive; + + switch(t) + { + case RTCPSDESPacket::CNAME: + sdesid = RTCP_SDES_ID_CNAME; + break; + case RTCPSDESPacket::NAME: + sdesid = RTCP_SDES_ID_NAME; + break; + case RTCPSDESPacket::EMAIL: + sdesid = RTCP_SDES_ID_EMAIL; + break; + case RTCPSDESPacket::PHONE: + sdesid = RTCP_SDES_ID_PHONE; + break; + case RTCPSDESPacket::LOC: + sdesid = RTCP_SDES_ID_LOCATION; + break; + case RTCPSDESPacket::TOOL: + sdesid = RTCP_SDES_ID_TOOL; + break; + case RTCPSDESPacket::NOTE: + sdesid = RTCP_SDES_ID_NOTE; + break; + default: + return ERR_RTP_SOURCES_ILLEGALSDESTYPE; + } + + status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + prevactive = srcdat->IsActive(); + status = srcdat->ProcessSDESItem(sdesid,(const uint8_t *)itemdata,itemlength,receivetime,&cnamecollis); + if (!prevactive && srcdat->IsActive()) + activecount++; + + // Call the callback + if (created) + OnNewSource(srcdat); + if (cnamecollis) + OnCNAMECollision(srcdat,senderaddress,(const uint8_t *)itemdata,itemlength); + + if (status >= 0) + OnRTCPSDESItem(srcdat, t, itemdata, itemlength); + + return status; +} + +#ifdef RTP_SUPPORT_SDESPRIV +int RTPSources::ProcessSDESPrivateItem(uint32_t ssrc,size_t prefixlen,const void *prefixdata, + size_t valuelen,const void *valuedata,const RTPTime &receivetime, + const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + + status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + status = srcdat->ProcessPrivateSDESItem((const uint8_t *)prefixdata,prefixlen,(const uint8_t *)valuedata,valuelen,receivetime); + // Call the callback + if (created) + OnNewSource(srcdat); + + if (status >= 0) + OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); + + return status; +} +#endif //RTP_SUPPORT_SDESPRIV + +int RTPSources::ProcessBYE(uint32_t ssrc,size_t reasonlength,const void *reasondata, + const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + bool prevactive; + + status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + // we'll ignore BYE packets for our own ssrc + if (srcdat == owndata) + return 0; + + prevactive = srcdat->IsActive(); + srcdat->ProcessBYEPacket((const uint8_t *)reasondata,reasonlength,receivetime); + if (prevactive && !srcdat->IsActive()) + activecount--; + + // Call the callback + if (created) + OnNewSource(srcdat); + OnBYEPacket(srcdat); + return 0; +} + +int RTPSources::ObtainSourceDataInstance(uint32_t ssrc,RTPInternalSourceData **srcdat,bool *created) +{ + RTPInternalSourceData *srcdat2; + int status; + + if (sourcelist.GotoElement(ssrc) < 0) // No entry for this source + { +#ifdef RTP_SUPPORT_PROBATION + srcdat2 = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPINTERNALSOURCEDATA) RTPInternalSourceData(ssrc,probationtype,GetMemoryManager()); +#else + srcdat2 = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPINTERNALSOURCEDATA) RTPInternalSourceData(ssrc,RTPSources::NoProbation,GetMemoryManager()); +#endif // RTP_SUPPORT_PROBATION + if (srcdat2 == 0) + return ERR_RTP_OUTOFMEM; + if ((status = sourcelist.AddElement(ssrc,srcdat2)) < 0) + { + RTPDelete(srcdat2,GetMemoryManager()); + return status; + } + *srcdat = srcdat2; + *created = true; + totalcount++; + } + else + { + *srcdat = sourcelist.GetCurrentElement(); + *created = false; + } + return 0; +} + + +int RTPSources::GetRTCPSourceData(uint32_t ssrc,const RTPAddress *senderaddress, + RTPInternalSourceData **srcdat2,bool *newsource) +{ + int status; + bool created; + RTPInternalSourceData *srcdat; + + *srcdat2 = 0; + + if ((status = ObtainSourceDataInstance(ssrc,&srcdat,&created)) < 0) + return status; + + if (created) + { + if ((status = srcdat->SetRTCPDataAddress(senderaddress)) < 0) + return status; + } + else // got a previously existing source + { + if (CheckCollision(srcdat,senderaddress,false)) + return 0; // ignore packet on collision + } + + *srcdat2 = srcdat; + *newsource = created; + + return 0; +} + +int RTPSources::UpdateReceiveTime(uint32_t ssrc,const RTPTime &receivetime,const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + + status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + // We got valid SSRC info + srcdat->UpdateMessageTime(receivetime); + + // Call the callback + if (created) + OnNewSource(srcdat); + + return 0; +} + +void RTPSources::Timeout(const RTPTime &curtime,const RTPTime &timeoutdelay) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + RTPTime lastmsgtime = srcdat->INF_GetLastMessageTime(); + + // we don't want to time out ourselves + if ((srcdat != owndata) && (lastmsgtime < checktime)) // timeout + { + + totalcount--; + if (srcdat->IsSender()) + sendercount--; + if (srcdat->IsActive()) + activecount--; + + sourcelist.DeleteCurrentElement(); + + OnTimeout(srcdat); + OnRemoveSource(srcdat); + RTPDelete(srcdat,GetMemoryManager()); + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; +} + +void RTPSources::SenderTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + + newtotalcount++; + if (srcdat->IsActive()) + newactivecount++; + + if (srcdat->IsSender()) + { + RTPTime lastrtppacktime = srcdat->INF_GetLastRTPPacketTime(); + + if (lastrtppacktime < checktime) // timeout + { + srcdat->ClearSenderFlag(); + sendercount--; + } + else + newsendercount++; + } + sourcelist.GotoNextElement(); + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; +} + +void RTPSources::BYETimeout(const RTPTime &curtime,const RTPTime &timeoutdelay) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + + if (srcdat->ReceivedBYE()) + { + RTPTime byetime = srcdat->GetBYETime(); + + if ((srcdat != owndata) && (checktime > byetime)) + { + totalcount--; + if (srcdat->IsSender()) + sendercount--; + if (srcdat->IsActive()) + activecount--; + sourcelist.DeleteCurrentElement(); + OnBYETimeout(srcdat); + OnRemoveSource(srcdat); + RTPDelete(srcdat,GetMemoryManager()); + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; +} + +void RTPSources::NoteTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + size_t notelen; + + srcdat->SDES_GetNote(¬elen); + if (notelen != 0) // Note has been set + { + RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); + + if (checktime > notetime) + { + srcdat->ClearNote(); + OnNoteTimeout(srcdat); + } + } + + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; + +} + +void RTPSources::MultipleTimeouts(const RTPTime &curtime,const RTPTime &sendertimeout,const RTPTime &byetimeout,const RTPTime &generaltimeout,const RTPTime ¬etimeout) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime senderchecktime = curtime; + RTPTime byechecktime = curtime; + RTPTime generaltchecktime = curtime; + RTPTime notechecktime = curtime; + senderchecktime -= sendertimeout; + byechecktime -= byetimeout; + generaltchecktime -= generaltimeout; + notechecktime -= notetimeout; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + bool deleted,issender,isactive; + bool byetimeout,normaltimeout,notetimeout; + + size_t notelen; + + issender = srcdat->IsSender(); + isactive = srcdat->IsActive(); + deleted = false; + byetimeout = false; + normaltimeout = false; + notetimeout = false; + + srcdat->SDES_GetNote(¬elen); + if (notelen != 0) // Note has been set + { + RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); + + if (notechecktime > notetime) + { + notetimeout = true; + srcdat->ClearNote(); + } + } + + if (srcdat->ReceivedBYE()) + { + RTPTime byetime = srcdat->GetBYETime(); + + if ((srcdat != owndata) && (byechecktime > byetime)) + { + sourcelist.DeleteCurrentElement(); + deleted = true; + byetimeout = true; + } + } + + if (!deleted) + { + RTPTime lastmsgtime = srcdat->INF_GetLastMessageTime(); + + if ((srcdat != owndata) && (lastmsgtime < generaltchecktime)) + { + sourcelist.DeleteCurrentElement(); + deleted = true; + normaltimeout = true; + } + } + + if (!deleted) + { + newtotalcount++; + + if (issender) + { + RTPTime lastrtppacktime = srcdat->INF_GetLastRTPPacketTime(); + + if (lastrtppacktime < senderchecktime) + { + srcdat->ClearSenderFlag(); + sendercount--; + } + else + newsendercount++; + } + + if (isactive) + newactivecount++; + + if (notetimeout) + OnNoteTimeout(srcdat); + + sourcelist.GotoNextElement(); + } + else // deleted entry + { + if (issender) + sendercount--; + if (isactive) + activecount--; + totalcount--; + + if (byetimeout) + OnBYETimeout(srcdat); + if (normaltimeout) + OnTimeout(srcdat); + OnRemoveSource(srcdat); + RTPDelete(srcdat,GetMemoryManager()); + } + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; +} + +bool RTPSources::CheckCollision(RTPInternalSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp) +{ + bool isset,otherisset; + const RTPAddress *addr,*otheraddr; + + if (isrtp) + { + isset = srcdat->IsRTPAddressSet(); + addr = srcdat->GetRTPDataAddress(); + otherisset = srcdat->IsRTCPAddressSet(); + otheraddr = srcdat->GetRTCPDataAddress(); + } + else + { + isset = srcdat->IsRTCPAddressSet(); + addr = srcdat->GetRTCPDataAddress(); + otherisset = srcdat->IsRTPAddressSet(); + otheraddr = srcdat->GetRTPDataAddress(); + } + + if (!isset) + { + if (otherisset) // got other address, can check if it comes from same host + { + if (otheraddr == 0) // other came from our own session + { + if (senderaddress != 0) + { + OnSSRCCollision(srcdat,senderaddress,isrtp); + return true; + } + + // Ok, store it + + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + else + { + if (!otheraddr->IsFromSameHost(senderaddress)) + { + OnSSRCCollision(srcdat,senderaddress,isrtp); + return true; + } + + // Ok, comes from same host, store the address + + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + } + else // no other address, store this one + { + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + } + else // already got an address + { + if (addr == 0) + { + if (senderaddress != 0) + { + OnSSRCCollision(srcdat,senderaddress,isrtp); + return true; + } + } + else + { + if (!addr->IsSameAddress(senderaddress)) + { + OnSSRCCollision(srcdat,senderaddress,isrtp); + return true; + } + } + } + + return false; +} + +} // end namespace + diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h new file mode 100644 index 000000000..c3927ba36 --- /dev/null +++ b/qrtplib/rtpsources.h @@ -0,0 +1,406 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpsources.h + */ + +#ifndef RTPSOURCES_H + +#define RTPSOURCES_H + +#include "rtpconfig.h" +#include "rtpkeyhashtable.h" +#include "rtcpsdespacket.h" +#include "rtptypes.h" +#include "rtpmemoryobject.h" + +#define RTPSOURCES_HASHSIZE 8317 + +namespace qrtplib +{ + +class JRTPLIB_IMPORTEXPORT RTPSources_GetHashIndex +{ +public: + static int GetIndex(const uint32_t &ssrc) { return ssrc%RTPSOURCES_HASHSIZE; } +}; + +class RTPNTPTime; +class RTPTransmitter; +class RTCPAPPPacket; +class RTPInternalSourceData; +class RTPRawPacket; +class RTPPacket; +class RTPTime; +class RTPAddress; +class RTPSourceData; + +/** Represents a table in which information about the participating sources is kept. + * Represents a table in which information about the participating sources is kept. The class has member + * functions to process RTP and RTCP data and to iterate over the participants. Note that a NULL address + * is used to identify packets from our own session. The class also provides some overridable functions + * which can be used to catch certain events (new SSRC, SSRC collision, ...). + */ +class JRTPLIB_IMPORTEXPORT RTPSources : public RTPMemoryObject +{ +public: + /** Type of probation to use for new sources. */ + enum ProbationType + { + NoProbation, /**< Don't use the probation algorithm; accept RTP packets immediately. */ + ProbationDiscard, /**< Discard incoming RTP packets originating from a source that's on probation. */ + ProbationStore /**< Store incoming RTP packet from a source that's on probation for later retrieval. */ + }; + + /** In the constructor you can select the probation type you'd like to use and also a memory manager. */ + RTPSources(ProbationType = ProbationStore,RTPMemoryManager *mgr = 0); + virtual ~RTPSources(); + + /** Clears the source table. */ + void Clear(); +#ifdef RTP_SUPPORT_PROBATION + /** Changes the current probation type. */ + void SetProbationType(ProbationType probtype) { probationtype = probtype; } +#endif // RTP_SUPPORT_PROBATION + + /** Creates an entry for our own SSRC identifier. */ + int CreateOwnSSRC(uint32_t ssrc); + + /** Deletes the entry for our own SSRC identifier. */ + int DeleteOwnSSRC(); + + /** This function should be called if our own session has sent an RTP packet. + * This function should be called if our own session has sent an RTP packet. + * For our own SSRC entry, the sender flag is updated based upon outgoing packets instead of incoming packets. + */ + void SentRTPPacket(); + + /** Processes a raw packet \c rawpack. + * Processes a raw packet \c rawpack. The instance \c trans will be used to check if this + * packet is one of our own packets. The flag \c acceptownpackets indicates whether own packets should be + * accepted or ignored. + */ + int ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *trans,bool acceptownpackets); + + /** Processes a raw packet \c rawpack. + * Processes a raw packet \c rawpack. Every transmitter in the array \c trans of length \c numtrans + * is used to check if the packet is from our own session. The flag \c acceptownpackets indicates + * whether own packets should be accepted or ignored. + */ + int ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *trans[],int numtrans,bool acceptownpackets); + + /** Processes an RTPPacket instance \c rtppack which was received at time \c receivetime and + * which originated from \c senderaddres. + * Processes an RTPPacket instance \c rtppack which was received at time \c receivetime and + * which originated from \c senderaddres. The \c senderaddress parameter must be NULL if + * the packet was sent by the local participant. The flag \c stored indicates whether the packet + * was stored in the table or not. If so, the \c rtppack instance may not be deleted. + */ + int ProcessRTPPacket(RTPPacket *rtppack,const RTPTime &receivetime,const RTPAddress *senderaddress,bool *stored); + + /** Processes the RTCP compound packet \c rtcpcomppack which was received at time \c receivetime from \c senderaddress. + * Processes the RTCP compound packet \c rtcpcomppack which was received at time \c receivetime from \c senderaddress. + * The \c senderaddress parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessRTCPCompoundPacket(RTCPCompoundPacket *rtcpcomppack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Process the sender information of SSRC \c ssrc into the source table. + * Process the sender information of SSRC \c ssrc into the source table. The information was received + * at time \c receivetime from address \c senderaddress. The \c senderaddress} parameter must be NULL + * if the packet was sent by the local participant. + */ + int ProcessRTCPSenderInfo(uint32_t ssrc,const RTPNTPTime &ntptime,uint32_t rtptime, + uint32_t packetcount,uint32_t octetcount,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Processes the report block information which was sent by participant \c ssrc into the source table. + * Processes the report block information which was sent by participant \c ssrc into the source table. + * The information was received at time \c receivetime from address \c senderaddress The \c senderaddress + * parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessRTCPReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t lostpackets, + uint32_t exthighseqnr,uint32_t jitter,uint32_t lsr, + uint32_t dlsr,const RTPTime &receivetime,const RTPAddress *senderaddress); + + /** Processes the non-private SDES item from source \c ssrc into the source table. + * Processes the non-private SDES item from source \c ssrc into the source table. The information was + * received at time \c receivetime from address \c senderaddress. The \c senderaddress parameter must + * be NULL if the packet was sent by the local participant. + */ + int ProcessSDESNormalItem(uint32_t ssrc,RTCPSDESPacket::ItemType t,size_t itemlength, + const void *itemdata,const RTPTime &receivetime,const RTPAddress *senderaddress); +#ifdef RTP_SUPPORT_SDESPRIV + /** Processes the SDES private item from source \c ssrc into the source table. + * Processes the SDES private item from source \c ssrc into the source table. The information was + * received at time \c receivetime from address \c senderaddress. The \c senderaddress + * parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessSDESPrivateItem(uint32_t ssrc,size_t prefixlen,const void *prefixdata, + size_t valuelen,const void *valuedata,const RTPTime &receivetime, + const RTPAddress *senderaddress); +#endif //RTP_SUPPORT_SDESPRIV + /** Processes the BYE message for SSRC \c ssrc. + * Processes the BYE message for SSRC \c ssrc. The information was received at time \c receivetime from + * address \c senderaddress. The \c senderaddress parameter must be NULL if the packet was sent by the + * local participant. + */ + int ProcessBYE(uint32_t ssrc,size_t reasonlength,const void *reasondata,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** If we heard from source \c ssrc, but no actual data was added to the source table (for example, if + * no report block was meant for us), this function can e used to indicate that something was received from + * this source. + * If we heard from source \c ssrc, but no actual data was added to the source table (for example, if + * no report block was meant for us), this function can e used to indicate that something was received from + * this source. This will prevent a premature timeout for this participant. The message was received at time + * \c receivetime from address \c senderaddress. The \c senderaddress parameter must be NULL if the + * packet was sent by the local participant. + */ + int UpdateReceiveTime(uint32_t ssrc,const RTPTime &receivetime,const RTPAddress *senderaddress); + + /** Starts the iteration over the participants by going to the first member in the table. + * Starts the iteration over the participants by going to the first member in the table. + * If a member was found, the function returns \c true, otherwise it returns \c false. + */ + bool GotoFirstSource(); + + /** Sets the current source to be the next source in the table. + * Sets the current source to be the next source in the table. If we're already at the last source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoNextSource(); + + /** Sets the current source to be the previous source in the table. + * Sets the current source to be the previous source in the table. If we're at the first source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoPreviousSource(); + + /** Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoFirstSourceWithData(); + + /** Sets the current source to be the next source in the table which has RTPPacket instances that + * we haven't extracted yet. + * Sets the current source to be the next source in the table which has RTPPacket instances that + * we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoNextSourceWithData(); + + /** Sets the current source to be the previous source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the previous source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoPreviousSourceWithData(); + + /** Returns the RTPSourceData instance for the currently selected participant. */ + RTPSourceData *GetCurrentSourceInfo(); + + /** Returns the RTPSourceData instance for the participant identified by \c ssrc, or + * NULL if no such entry exists. + */ + RTPSourceData *GetSourceInfo(uint32_t ssrc); + + /** Extracts the next packet from the received packets queue of the current participant. */ + RTPPacket *GetNextPacket(); + + /** Returns \c true if an entry for participant \c ssrc exists and \c false otherwise. */ + bool GotEntry(uint32_t ssrc); + + /** If present, it returns the RTPSourceData instance of the entry which was created by CreateOwnSSRC. */ + RTPSourceData *GetOwnSourceInfo() { return (RTPSourceData *)owndata; } + + /** Assuming that the current time is \c curtime, time out the members from whom we haven't heard + * during the previous time interval \c timeoutdelay. + */ + void Timeout(const RTPTime &curtime,const RTPTime &timeoutdelay); + + /** Assuming that the current time is \c curtime, remove the sender flag for senders from whom we haven't + * received any RTP packets during the previous time interval \c timeoutdelay. + */ + void SenderTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay); + + /** Assuming that the current time is \c curtime, remove the members who sent a BYE packet more than + * the time interval \c timeoutdelay ago. + */ + void BYETimeout(const RTPTime &curtime,const RTPTime &timeoutdelay); + + /** Assuming that the current time is \c curtime, clear the SDES NOTE items which haven't been updated + * during the previous time interval \c timeoutdelay. + */ + void NoteTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay); + + /** Combines the functions SenderTimeout, BYETimeout, Timeout and NoteTimeout. + * Combines the functions SenderTimeout, BYETimeout, Timeout and NoteTimeout. This is more efficient + * than calling all four functions since only one iteration is needed in this function. + */ + void MultipleTimeouts(const RTPTime &curtime,const RTPTime &sendertimeout, + const RTPTime &byetimeout,const RTPTime &generaltimeout, + const RTPTime ¬etimeout); + + /** Returns the number of participants which are marked as a sender. */ + int GetSenderCount() const { return sendercount; } + + /** Returns the total number of entries in the source table. */ + int GetTotalCount() const { return totalcount; } + + /** Returns the number of members which have been validated and which haven't sent a BYE packet yet. */ + int GetActiveMemberCount() const { return activecount; } + +protected: + /** Is called when an RTP packet is about to be processed. */ + virtual void OnRTPPacket(RTPPacket *pack,const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an RTCP compound packet is about to be processed. */ + virtual void OnRTCPCompoundPacket(RTCPCompoundPacket *pack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Is called when an SSRC collision was detected. + * Is called when an SSRC collision was detected. The instance \c srcdat is the one present in + * the table, the address \c senderaddress is the one that collided with one of the addresses + * and \c isrtp indicates against which address of \c srcdat the check failed. + */ + virtual void OnSSRCCollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp); + + /** Is called when another CNAME was received than the one already present for source \c srcdat. */ + virtual void OnCNAMECollision(RTPSourceData *srcdat,const RTPAddress *senderaddress, + const uint8_t *cname,size_t cnamelength); + + /** Is called when a new entry \c srcdat is added to the source table. */ + virtual void OnNewSource(RTPSourceData *srcdat); + + /** Is called when the entry \c srcdat is about to be deleted from the source table. */ + virtual void OnRemoveSource(RTPSourceData *srcdat); + + /** Is called when participant \c srcdat is timed out. */ + virtual void OnTimeout(RTPSourceData *srcdat); + + /** Is called when participant \c srcdat is timed after having sent a BYE packet. */ + virtual void OnBYETimeout(RTPSourceData *srcdat); + + /** Is called when a BYE packet has been processed for source \c srcdat. */ + virtual void OnBYEPacket(RTPSourceData *srcdat); + + /** Is called when an RTCP sender report has been processed for this source. */ + virtual void OnRTCPSenderReport(RTPSourceData *srcdat); + + /** Is called when an RTCP receiver report has been processed for this source. */ + virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); + + /** Is called when a specific SDES item was received for this source. */ + virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, + const void *itemdata, size_t itemlength); +#ifdef RTP_SUPPORT_SDESPRIV + /** Is called when a specific SDES item of 'private' type was received for this source. */ + virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, + const void *valuedata, size_t valuelen); +#endif // RTP_SUPPORT_SDESPRIV + + + /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime + * from address \c senderaddress. + */ + virtual void OnAPPPacket(RTCPAPPPacket *apppacket,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Is called when an unknown RTCP packet type was detected. */ + virtual void OnUnknownPacketType(RTCPPacket *rtcppack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Is called when an unknown packet format for a known packet type was detected. */ + virtual void OnUnknownPacketFormat(RTCPPacket *rtcppack,const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Is called when the SDES NOTE item for source \c srcdat has been timed out. */ + virtual void OnNoteTimeout(RTPSourceData *srcdat); + + /** Allows you to use an RTP packet from the specified source directly. + * Allows you to use an RTP packet from the specified source directly. If + * `ispackethandled` is set to `true`, the packet will no longer be stored in this + * source's packet list. */ + virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); +private: + void ClearSourceList(); + int ObtainSourceDataInstance(uint32_t ssrc,RTPInternalSourceData **srcdat,bool *created); + int GetRTCPSourceData(uint32_t ssrc,const RTPAddress *senderaddress,RTPInternalSourceData **srcdat,bool *newsource); + bool CheckCollision(RTPInternalSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp); + + RTPKeyHashTable sourcelist; + + int sendercount; + int totalcount; + int activecount; + +#ifdef RTP_SUPPORT_PROBATION + ProbationType probationtype; +#endif // RTP_SUPPORT_PROBATION + + RTPInternalSourceData *owndata; + + friend class RTPInternalSourceData; +}; + +// Inlining the default implementations to avoid unused-parameter errors. +inline void RTPSources::OnRTPPacket(RTPPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSources::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSources::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool) { } +inline void RTPSources::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, size_t) { } +inline void RTPSources::OnNewSource(RTPSourceData *) { } +inline void RTPSources::OnRemoveSource(RTPSourceData *) { } +inline void RTPSources::OnTimeout(RTPSourceData *) { } +inline void RTPSources::OnBYETimeout(RTPSourceData *) { } +inline void RTPSources::OnBYEPacket(RTPSourceData *) { } +inline void RTPSources::OnRTCPSenderReport(RTPSourceData *) { } +inline void RTPSources::OnRTCPReceiverReport(RTPSourceData *) { } +inline void RTPSources::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, size_t) { } +#ifdef RTP_SUPPORT_SDESPRIV +inline void RTPSources::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, size_t, const void *, size_t) { } +#endif // RTP_SUPPORT_SDESPRIV +inline void RTPSources::OnAPPPacket(RTCPAPPPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSources::OnUnknownPacketType(RTCPPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSources::OnUnknownPacketFormat(RTCPPacket *, const RTPTime &, const RTPAddress *) { } +inline void RTPSources::OnNoteTimeout(RTPSourceData *) { } +inline void RTPSources::OnValidatedRTPPacket(RTPSourceData *, RTPPacket *, bool, bool *) { } + +} // end namespace + +#endif // RTPSOURCES_H + diff --git a/qrtplib/rtpstructs.h b/qrtplib/rtpstructs.h new file mode 100644 index 000000000..e22a9f2ed --- /dev/null +++ b/qrtplib/rtpstructs.h @@ -0,0 +1,128 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpstructs.h + */ + +#ifndef RTPSTRUCTS_H + +#define RTPSTRUCTS_H + +#include "rtpconfig.h" +#include "rtptypes.h" + +namespace qrtplib +{ + +struct RTPHeader +{ +#ifdef RTP_BIG_ENDIAN + uint8_t version:2; + uint8_t padding:1; + uint8_t extension:1; + uint8_t csrccount:4; + + uint8_t marker:1; + uint8_t payloadtype:7; +#else // little endian + uint8_t csrccount:4; + uint8_t extension:1; + uint8_t padding:1; + uint8_t version:2; + + uint8_t payloadtype:7; + uint8_t marker:1; +#endif // RTP_BIG_ENDIAN + + uint16_t sequencenumber; + uint32_t timestamp; + uint32_t ssrc; +}; + +struct RTPExtensionHeader +{ + uint16_t extid; + uint16_t length; +}; + +struct RTPSourceIdentifier +{ + uint32_t ssrc; +}; + +struct RTCPCommonHeader +{ +#ifdef RTP_BIG_ENDIAN + uint8_t version:2; + uint8_t padding:1; + uint8_t count:5; +#else // little endian + uint8_t count:5; + uint8_t padding:1; + uint8_t version:2; +#endif // RTP_BIG_ENDIAN + + uint8_t packettype; + uint16_t length; +}; + +struct RTCPSenderReport +{ + uint32_t ntptime_msw; + uint32_t ntptime_lsw; + uint32_t rtptimestamp; + uint32_t packetcount; + uint32_t octetcount; +}; + +struct RTCPReceiverReport +{ + uint32_t ssrc; // Identifies about which SSRC's data this report is... + uint8_t fractionlost; + uint8_t packetslost[3]; + uint32_t exthighseqnr; + uint32_t jitter; + uint32_t lsr; + uint32_t dlsr; +}; + +struct RTCPSDESHeader +{ + uint8_t sdesid; + uint8_t length; +}; + +} // end namespace + +#endif // RTPSTRUCTS + diff --git a/qrtplib/rtptcpaddress.cpp b/qrtplib/rtptcpaddress.cpp new file mode 100644 index 000000000..ccb9f7851 --- /dev/null +++ b/qrtplib/rtptcpaddress.cpp @@ -0,0 +1,68 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtptcpaddress.h" +#include "rtpmemorymanager.h" + +namespace qrtplib +{ + +bool RTPTCPAddress::IsSameAddress(const RTPAddress *addr) const +{ + if (addr == 0) + return false; + if (addr->GetAddressType() != TCPAddress) + return false; + + const RTPTCPAddress *a = static_cast(addr); + + // We're using a socket to identify connections + if (a->m_socket == m_socket) + return true; + + return false; +} + +bool RTPTCPAddress::IsFromSameHost(const RTPAddress *addr) const +{ + return IsSameAddress(addr); +} + +RTPAddress *RTPTCPAddress::CreateCopy(RTPMemoryManager *mgr) const +{ + JRTPLIB_UNUSED(mgr); // possibly unused + RTPTCPAddress *a = RTPNew(mgr,RTPMEM_TYPE_CLASS_RTPADDRESS) RTPTCPAddress(m_socket); + return a; +} + +} // end namespace + diff --git a/qrtplib/rtptcpaddress.h b/qrtplib/rtptcpaddress.h new file mode 100644 index 000000000..b2921a0a3 --- /dev/null +++ b/qrtplib/rtptcpaddress.h @@ -0,0 +1,84 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtptcpaddress.h + */ + +#ifndef RTPTCPADDRESS_H + +#define RTPTCPADDRESS_H + +#include "rtpconfig.h" +#include "rtpaddress.h" +#include "rtptypes.h" +#include "rtpsocketutil.h" + +namespace qrtplib +{ + +class RTPMemoryManager; + +/** Represents a TCP 'address' and port. + * This class is used by the TCP transmission component, to specify which sockets + * should be used to send/receive data, and to know on which socket incoming data + * was received. + */ +class JRTPLIB_IMPORTEXPORT RTPTCPAddress : public RTPAddress +{ +public: + /** Creates an instance with which you can use a specific socket + * in the TCP transmitter (must be connected). */ + RTPTCPAddress(SocketType sock):RTPAddress(TCPAddress) + { + m_socket = sock; + } + + ~RTPTCPAddress() { } + + /** Returns the socket that was specified in the constructor. */ + SocketType GetSocket() const { return m_socket; } + + RTPAddress *CreateCopy(RTPMemoryManager *mgr) const; + + // Note that these functions are only used for received packets + bool IsSameAddress(const RTPAddress *addr) const; + bool IsFromSameHost(const RTPAddress *addr) const; + +private: + SocketType m_socket; +}; + +} // end namespace + +#endif // RTPTCPADDRESS_H + diff --git a/qrtplib/rtptcptransmitter.cpp b/qrtplib/rtptcptransmitter.cpp new file mode 100644 index 000000000..cc256558c --- /dev/null +++ b/qrtplib/rtptcptransmitter.cpp @@ -0,0 +1,924 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtptcptransmitter.h" +#include "rtprawpacket.h" +#include "rtptcpaddress.h" +#include "rtptimeutilities.h" +#include "rtpdefines.h" +#include "rtpstructs.h" +#include "rtpsocketutilinternal.h" +#include "rtpinternalutils.h" +#include "rtpselect.h" +#include +#include +#include + +#include + +using namespace std; + +#define RTPTCPTRANS_MAXPACKSIZE 65535 + +#ifdef RTP_SUPPORT_THREAD + #define MAINMUTEX_LOCK { if (m_threadsafe) m_mainMutex.Lock(); } + #define MAINMUTEX_UNLOCK { if (m_threadsafe) m_mainMutex.Unlock(); } + #define WAITMUTEX_LOCK { if (m_threadsafe) m_waitMutex.Lock(); } + #define WAITMUTEX_UNLOCK { if (m_threadsafe) m_waitMutex.Unlock(); } +#else + #define MAINMUTEX_LOCK + #define MAINMUTEX_UNLOCK + #define WAITMUTEX_LOCK + #define WAITMUTEX_UNLOCK +#endif // RTP_SUPPORT_THREAD + +namespace qrtplib +{ + +RTPTCPTransmitter::RTPTCPTransmitter(RTPMemoryManager *mgr) : RTPTransmitter(mgr) +{ + m_created = false; + m_init = false; +} + +RTPTCPTransmitter::~RTPTCPTransmitter() +{ + Destroy(); +} + +int RTPTCPTransmitter::Init(bool tsafe) +{ + if (m_init) + return ERR_RTP_TCPTRANS_ALREADYINIT; + +#ifdef RTP_SUPPORT_THREAD + m_threadsafe = tsafe; + if (m_threadsafe) + { + int status; + + status = m_mainMutex.Init(); + if (status < 0) + return ERR_RTP_TCPTRANS_CANTINITMUTEX; + status = m_waitMutex.Init(); + if (status < 0) + return ERR_RTP_TCPTRANS_CANTINITMUTEX; + } +#else + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; +#endif // RTP_SUPPORT_THREAD + + m_maxPackSize = RTPTCPTRANS_MAXPACKSIZE; + m_init = true; + return 0; +} + +int RTPTCPTransmitter::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) +{ + JRTPLIB_UNUSED(maximumpacketsize); + const RTPTCPTransmissionParams *params,defaultparams; + int status; + + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_ALREADYCREATED; + } + + // Obtain transmission parameters + + if (transparams == 0) + params = &defaultparams; + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::TCPProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_ILLEGALPARAMETERS; + } + params = static_cast(transparams); + } + + if (!params->GetCreatedAbortDescriptors()) + { + if ((status = m_abortDesc.Init()) < 0) + { + MAINMUTEX_UNLOCK + return status; + } + m_pAbortDesc = &m_abortDesc; + } + else + { + m_pAbortDesc = params->GetCreatedAbortDescriptors(); + if (!m_pAbortDesc->IsInitialized()) + { + MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; + } + } + + m_waitingForData = false; + m_created = true; + MAINMUTEX_UNLOCK + return 0; +} + +void RTPTCPTransmitter::Destroy() +{ + if (!m_init) + return; + + MAINMUTEX_LOCK + if (!m_created) + { + MAINMUTEX_UNLOCK; + return; + } + + ClearDestSockets(); + FlushPackets(); + m_created = false; + + if (m_waitingForData) + { + m_pAbortDesc->SendAbortSignal(); + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK + } + else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + + MAINMUTEX_UNLOCK +} + +RTPTransmissionInfo *RTPTCPTransmitter::GetTransmissionInfo() +{ + if (!m_init) + return 0; + + MAINMUTEX_LOCK + RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPTCPTransmissionInfo(); + MAINMUTEX_UNLOCK + return tinf; +} + +void RTPTCPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) +{ + if (!m_init) + return; + + RTPDelete(i, GetMemoryManager()); +} + +int RTPTCPTransmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) +{ + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTCREATED; + } + + if (m_localHostname.size() == 0) + { + // + // TODO + // TODO + // TODO + // TODO + // + m_localHostname.resize(9); + memcpy(&m_localHostname[0], "localhost", m_localHostname.size()); + } + + if ((*bufferlength) < m_localHostname.size()) + { + *bufferlength = m_localHostname.size(); // tell the application the required size of the buffer + MAINMUTEX_UNLOCK + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; + } + + memcpy(buffer,&m_localHostname[0], m_localHostname.size()); + *bufferlength = m_localHostname.size(); + + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPTCPTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) +{ + if (!m_init) + return false; + + if (addr == 0) + return false; + + MAINMUTEX_LOCK + + if (!m_created) + return false; + + if (addr->GetAddressType() != RTPAddress::TCPAddress) + return false; + + const RTPTCPAddress *pAddr = static_cast(addr); + bool v = false; + + JRTPLIB_UNUSED(pAddr); + // TODO: for now, we're assuming that we can't just send to the same transmitter + + MAINMUTEX_UNLOCK + return v; +} + +int RTPTCPTransmitter::Poll() +{ + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTCREATED; + } + + std::map::iterator it = m_destSockets.begin(); + std::map::iterator end = m_destSockets.end(); + int status = 0; + + vector errSockets; + + while (it != end) + { + SocketType sock = it->first; + status = PollSocket(sock, it->second); + if (status < 0) + { + // Stop immediately on out of memory + if (status == ERR_RTP_OUTOFMEM) + break; + else + { + errSockets.push_back(sock); + // Don't let this count as an error (due to a closed connection for example), + // otherwise the poll thread (if used) will stop because of this. Since there + // may be more than one connection, that's not desirable in general. + status = 0; + } + } + ++it; + } + MAINMUTEX_UNLOCK + + for (size_t i = 0 ; i < errSockets.size() ; i++) + OnReceiveError(errSockets[i]); + + return status; +} + +int RTPTCPTransmitter::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +{ + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTCREATED; + } + if (m_waitingForData) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_ALREADYWAITING; + } + + m_tmpSocks.resize(m_destSockets.size()+1); + m_tmpFlags.resize(m_tmpSocks.size()); + SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); + + std::map::iterator it = m_destSockets.begin(); + std::map::iterator end = m_destSockets.end(); + int idx = 0; + + while (it != end) + { + m_tmpSocks[idx] = it->first; + m_tmpFlags[idx] = 0; + ++it; + idx++; + } + m_tmpSocks[idx] = abortSocket; + m_tmpFlags[idx] = 0; + int idxAbort = idx; + + m_waitingForData = true; + + WAITMUTEX_LOCK + MAINMUTEX_UNLOCK + + //cout << "Waiting for " << delay.GetDouble() << " seconds for data on " << m_tmpSocks.size() << " sockets" << endl; + int status = RTPSelect(&m_tmpSocks[0], &m_tmpFlags[0], m_tmpSocks.size(), delay); + if (status < 0) + { + MAINMUTEX_LOCK + m_waitingForData = false; + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return status; + } + + MAINMUTEX_LOCK + m_waitingForData = false; + if (!m_created) // destroy called + { + MAINMUTEX_UNLOCK; + WAITMUTEX_UNLOCK + return 0; + } + + // if aborted, read from abort buffer + if (m_tmpFlags[idxAbort]) + m_pAbortDesc->ReadSignallingByte(); + + if (dataavailable != 0) + { + bool avail = false; + + for (size_t i = 0 ; i < m_tmpFlags.size() ; i++) + { + if (m_tmpFlags[i]) + { + avail = true; + //cout << "Data available!" << endl; + break; + } + } + + if (avail) + *dataavailable = true; + else + *dataavailable = false; + } + + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return 0; +} + +int RTPTCPTransmitter::AbortWait() +{ + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTCREATED; + } + if (!m_waitingForData) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTWAITING; + } + + m_pAbortDesc->SendAbortSignal(); + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPTCPTransmitter::SendRTPData(const void *data,size_t len) +{ + return SendRTPRTCPData(data, len); +} + +int RTPTCPTransmitter::SendRTCPData(const void *data,size_t len) +{ + return SendRTPRTCPData(data, len); +} + +int RTPTCPTransmitter::AddDestination(const RTPAddress &addr) +{ + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTCREATED; + } + + if (addr.GetAddressType() != RTPAddress::TCPAddress) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; + } + + const RTPTCPAddress &a = static_cast(addr); + SocketType s = a.GetSocket(); + if (s == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; + } + + int status = ValidateSocket(s); + if (status != 0) + { + MAINMUTEX_UNLOCK + return status; + } + + std::map::iterator it = m_destSockets.find(s); + if (it != m_destSockets.end()) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS; + } + m_destSockets[s] = SocketData(); + + // Because the sockets are also used for incoming data, we'll abort a wait + // that may be in progress, otherwise it could take a few seconds until the + // new socket is monitored for incoming data + m_pAbortDesc->SendAbortSignal(); + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPTCPTransmitter::DeleteDestination(const RTPAddress &addr) +{ + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTCREATED; + } + + if (addr.GetAddressType() != RTPAddress::TCPAddress) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; + } + + const RTPTCPAddress &a = static_cast(addr); + SocketType s = a.GetSocket(); + if (s == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; + } + + std::map::iterator it = m_destSockets.find(s); + if (it == m_destSockets.end()) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS; + } + + // Clean up possibly allocated memory + uint8_t *pBuf = it->second.ExtractDataBuffer(); + if (pBuf) + RTPDeleteByteArray(pBuf, GetMemoryManager()); + + m_destSockets.erase(it); + + MAINMUTEX_UNLOCK + return 0; +} + +void RTPTCPTransmitter::ClearDestinations() +{ + if (!m_init) + return; + + MAINMUTEX_LOCK + if (m_created) + ClearDestSockets(); + MAINMUTEX_UNLOCK +} + +bool RTPTCPTransmitter::SupportsMulticasting() +{ + return false; +} + +int RTPTCPTransmitter::JoinMulticastGroup(const RTPAddress &) +{ + return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; +} + +int RTPTCPTransmitter::LeaveMulticastGroup(const RTPAddress &) +{ + return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; +} + +void RTPTCPTransmitter::LeaveAllMulticastGroups() +{ +} + +int RTPTCPTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (m != RTPTransmitter::AcceptAll) + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; + return 0; +} + +int RTPTCPTransmitter::AddToIgnoreList(const RTPAddress &) +{ + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +} + +int RTPTCPTransmitter::DeleteFromIgnoreList(const RTPAddress &) +{ + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +} + +void RTPTCPTransmitter::ClearIgnoreList() +{ +} + +int RTPTCPTransmitter::AddToAcceptList(const RTPAddress &) +{ + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +} + +int RTPTCPTransmitter::DeleteFromAcceptList(const RTPAddress &) +{ + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +} + +void RTPTCPTransmitter::ClearAcceptList() +{ +} + +int RTPTCPTransmitter::SetMaximumPacketSize(size_t s) +{ + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTCREATED; + } + if (s > RTPTCPTRANS_MAXPACKSIZE) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; + } + m_maxPackSize = s; + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPTCPTransmitter::NewDataAvailable() +{ + if (!m_init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!m_created) + v = false; + else + { + if (m_rawpacketlist.empty()) + v = false; + else + v = true; + } + + MAINMUTEX_UNLOCK + return v; +} + +RTPRawPacket *RTPTCPTransmitter::GetNextPacket() +{ + if (!m_init) + return 0; + + MAINMUTEX_LOCK + + RTPRawPacket *p; + + if (!m_created) + { + MAINMUTEX_UNLOCK + return 0; + } + if (m_rawpacketlist.empty()) + { + MAINMUTEX_UNLOCK + return 0; + } + + p = *(m_rawpacketlist.begin()); + m_rawpacketlist.pop_front(); + + MAINMUTEX_UNLOCK + return p; +} + +// Here the private functions start... + +void RTPTCPTransmitter::FlushPackets() +{ + std::list::const_iterator it; + + for (it = m_rawpacketlist.begin() ; it != m_rawpacketlist.end() ; ++it) + RTPDelete(*it,GetMemoryManager()); + m_rawpacketlist.clear(); +} + +int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata) +{ +#ifdef RTP_SOCKETTYPE_WINSOCK + unsigned long len; +#else + size_t len; +#endif // RTP_SOCKETTYPE_WINSOCK + bool dataavailable; + + do + { + len = 0; + RTPIOCTL(sock, FIONREAD, &len); + + if (len <= 0) + dataavailable = false; + else + dataavailable = true; + + if (dataavailable) + { + RTPTime curtime = RTPTime::CurrentTime(); + int relevantLen = RTPTCPTRANS_MAXPACKSIZE+2; + + if ((int)len < relevantLen) + relevantLen = (int)len; + + bool complete = false; + int status = sdata.ProcessAvailableBytes(sock, relevantLen, complete, GetMemoryManager()); + if (status < 0) + return status; + + if (complete) + { + uint8_t *pBuf = sdata.ExtractDataBuffer(); + if (pBuf) + { + int dataLength = sdata.m_dataLength; + sdata.Reset(); + + RTPTCPAddress *pAddr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPTCPAddress(sock); + if (pAddr == 0) + return ERR_RTP_OUTOFMEM; + + bool isrtp = true; + if (dataLength > (int)sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *)pBuf; + uint8_t packettype = rtcpheader->packettype; + + if (packettype >= 200 && packettype <= 204) + isrtp = false; + } + + RTPRawPacket *pPack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp, GetMemoryManager()); + if (pPack == 0) + { + RTPDelete(pAddr,GetMemoryManager()); + RTPDeleteByteArray(pBuf,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + m_rawpacketlist.push_back(pPack); + } + } + } + } while (dataavailable); + + return 0; +} + +int RTPTCPTransmitter::SendRTPRTCPData(const void *data, size_t len) +{ + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_NOTCREATED; + } + if (len > RTPTCPTRANS_MAXPACKSIZE) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; + } + + std::map::iterator it = m_destSockets.begin(); + std::map::iterator end = m_destSockets.end(); + + vector errSockets; + int flags = 0; +#ifdef RTP_HAVE_MSG_NOSIGNAL + flags = MSG_NOSIGNAL; +#endif // RTP_HAVE_MSG_NOSIGNAL + + while (it != end) + { + uint8_t lengthBytes[2] = { (uint8_t)((len >> 8)&0xff), (uint8_t)(len&0xff) }; + SocketType sock = it->first; + + if (send(sock,(const char *)lengthBytes,2,flags) < 0 || + send(sock,(const char *)data,len,flags) < 0) + errSockets.push_back(sock); + ++it; + } + + MAINMUTEX_UNLOCK + + if (errSockets.size() != 0) + { + for (size_t i = 0 ; i < errSockets.size() ; i++) + OnSendError(errSockets[i]); + } + + // Don't return an error code to avoid the poll thread exiting + // due to one closed connection for example + + return 0; +} + +int RTPTCPTransmitter::ValidateSocket(SocketType) +{ + // TODO: should we even do a check (for a TCP socket)? + return 0; +} + +void RTPTCPTransmitter::ClearDestSockets() +{ + std::map::iterator it = m_destSockets.begin(); + std::map::iterator end = m_destSockets.end(); + + while (it != end) + { + uint8_t *pBuf = it->second.ExtractDataBuffer(); + if (pBuf) + RTPDeleteByteArray(pBuf, GetMemoryManager()); + + ++it; + } + m_destSockets.clear(); +} + +RTPTCPTransmitter::SocketData::SocketData() +{ + Reset(); +} + +void RTPTCPTransmitter::SocketData::Reset() +{ + m_lengthBufferOffset = 0; + m_dataLength = 0; + m_dataBufferOffset = 0; + m_pDataBuffer = 0; +} + +RTPTCPTransmitter::SocketData::~SocketData() +{ + assert(m_pDataBuffer == 0); // Should be deleted externally to avoid storing a memory manager in the class +} + +int RTPTCPTransmitter::SocketData::ProcessAvailableBytes(SocketType sock, int availLen, bool &complete, RTPMemoryManager *pMgr) +{ + JRTPLIB_UNUSED(pMgr); // possibly unused + + const int numLengthBuffer = 2; + if (m_lengthBufferOffset < numLengthBuffer) // first we need to get the length + { + assert(m_pDataBuffer == 0); + int num = numLengthBuffer-m_lengthBufferOffset; + if (num > availLen) + num = availLen; + + int r = 0; + if (num > 0) + { + r = (int)recv(sock, (char *)(m_lengthBuffer+m_lengthBufferOffset), num, 0); + if (r < 0) + return ERR_RTP_TCPTRANS_ERRORINRECV; + } + + m_lengthBufferOffset += r; + availLen -= r; + + assert(m_lengthBufferOffset <= numLengthBuffer); + if (m_lengthBufferOffset == numLengthBuffer) // we can constuct a length + { + int l = 0; + for (int i = numLengthBuffer-1, shift = 0 ; i >= 0 ; i--, shift += 8) + l |= ((int)m_lengthBuffer[i]) << shift; + + m_dataLength = l; + m_dataBufferOffset = 0; + + //cout << "Expecting " << m_dataLength << " bytes" << endl; + + // avoid allocation of length 0 + if (l == 0) + l = 1; + + // We don't yet know if it's an RTP or RTCP packet, so we'll stick to RTP + m_pDataBuffer = RTPNew(pMgr, RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET) uint8_t[l]; + if (m_pDataBuffer == 0) + return ERR_RTP_OUTOFMEM; + } + } + + if (m_lengthBufferOffset == numLengthBuffer && m_pDataBuffer) // the last one is to make sure we didn't run out of memory + { + if (m_dataBufferOffset < m_dataLength) + { + int num = m_dataLength-m_dataBufferOffset; + if (num > availLen) + num = availLen; + + int r = 0; + if (num > 0) + { + r = (int)recv(sock, (char *)(m_pDataBuffer+m_dataBufferOffset), num, 0); + if (r < 0) + return ERR_RTP_TCPTRANS_ERRORINRECV; + } + + m_dataBufferOffset += r; + availLen -= r; + } + + if (m_dataBufferOffset == m_dataLength) + complete = true; + } + return 0; +} + +} // end namespace + + diff --git a/qrtplib/rtptcptransmitter.h b/qrtplib/rtptcptransmitter.h new file mode 100644 index 000000000..ad1b0662f --- /dev/null +++ b/qrtplib/rtptcptransmitter.h @@ -0,0 +1,207 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtptcptransmitter.h + */ + +#ifndef RTPTCPTRANSMITTER_H + +#define RTPTCPTRANSMITTER_H + +#include "rtpconfig.h" +#include "rtptransmitter.h" +#include "rtpsocketutil.h" +#include "rtpabortdescriptors.h" +#include +#include +#include + +namespace qrtplib +{ + +/** Parameters for the TCP transmitter. */ +class JRTPLIB_IMPORTEXPORT RTPTCPTransmissionParams : public RTPTransmissionParams +{ +public: + RTPTCPTransmissionParams(); + + /** If non null, the specified abort descriptors will be used to cancel + * the function that's waiting for packets to arrive; set to null (the default) + * to let the transmitter create its own instance. */ + void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) { m_pAbortDesc = desc; } + + /** If non-null, this RTPAbortDescriptors instance will be used internally, + * which can be useful when creating your own poll thread for multiple + * sessions. */ + RTPAbortDescriptors *GetCreatedAbortDescriptors() const { return m_pAbortDesc; } +private: + RTPAbortDescriptors *m_pAbortDesc; +}; + +inline RTPTCPTransmissionParams::RTPTCPTransmissionParams() : RTPTransmissionParams(RTPTransmitter::TCPProto) +{ + m_pAbortDesc = 0; +} + +/** Additional information about the TCP transmitter. */ +class JRTPLIB_IMPORTEXPORT RTPTCPTransmissionInfo : public RTPTransmissionInfo +{ +public: + RTPTCPTransmissionInfo() : RTPTransmissionInfo(RTPTransmitter::TCPProto) { } + ~RTPTCPTransmissionInfo() { } +}; + +// TODO: this is for IPv4, and will only be valid if one rtp packet is in one tcp frame +#define RTPTCPTRANS_HEADERSIZE (20+20+2) // 20 IP, 20 TCP, 2 for framing (RFC 4571) + +/** A TCP transmission component. + * + * This class inherits the RTPTransmitter interface and implements a transmission component + * which uses TCP to send and receive RTP and RTCP data. The component's parameters + * are described by the class RTPTCPTransmissionParams. The functions which have an RTPAddress + * argument require an argument of RTPTCPAddress. The RTPTransmitter::GetTransmissionInfo member function + * returns an instance of type RTPTCPTransmissionInfo. + * + * After this transmission component was created, no data will actually be sent or received + * yet. You can specify over which TCP connections (which must be established first) data + * should be transmitted by using the RTPTransmitter::AddDestination member function. This + * takes an argument of type RTPTCPAddress, with which relevant the socket descriptor can + * be passed to the transmitter. + * + * These sockets will also be used to check for incoming RTP or RTCP data. The RTPTCPAddress + * instance that's associated with a received packet, will contain the socket descriptor + * on which the data was received. This descriptor can be obtained using RTPTCPAddress::GetSocket. + * + * To get notified of an error when sending over or receiving from a socket, override the + * RTPTCPTransmitter::OnSendError and RTPTCPTransmitter::OnReceiveError member functions. + */ +class JRTPLIB_IMPORTEXPORT RTPTCPTransmitter : public RTPTransmitter +{ +public: + RTPTCPTransmitter(RTPMemoryManager *mgr); + ~RTPTCPTransmitter(); + + int Init(bool treadsafe); + int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() { return RTPTCPTRANS_HEADERSIZE; } + + int Poll(); + int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); + int AbortWait(); + + int SendRTPData(const void *data,size_t len); + int SendRTCPData(const void *data,size_t len); + + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); + + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); + + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); + + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); +protected: + /** By overriding this function you can be notified of an error when sending over a socket. */ + virtual void OnSendError(SocketType sock); + /** By overriding this function you can be notified of an error when receiving from a socket. */ + virtual void OnReceiveError(SocketType sock); +private: + class SocketData + { + public: + SocketData(); + ~SocketData(); + void Reset(); + + uint8_t m_lengthBuffer[2]; + int m_lengthBufferOffset; + int m_dataLength; + int m_dataBufferOffset; + uint8_t *m_pDataBuffer; + + uint8_t *ExtractDataBuffer() { uint8_t *pTmp = m_pDataBuffer; m_pDataBuffer = 0; return pTmp; } + int ProcessAvailableBytes(SocketType sock, int availLen, bool &complete, RTPMemoryManager *pMgr); + }; + + int SendRTPRTCPData(const void *data,size_t len); + void FlushPackets(); + int PollSocket(SocketType sock, SocketData &sdata); + void ClearDestSockets(); + int ValidateSocket(SocketType s); + + bool m_init; + bool m_created; + bool m_waitingForData; + + std::map m_destSockets; + std::vector m_tmpSocks; + std::vector m_tmpFlags; + std::vector m_localHostname; + size_t m_maxPackSize; + + std::list m_rawpacketlist; + + RTPAbortDescriptors m_abortDesc; + RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified + +#ifdef RTP_SUPPORT_THREAD + jthread::JMutex m_mainMutex, m_waitMutex; + bool m_threadsafe; +#endif // RTP_SUPPORT_THREAD +}; + +inline void RTPTCPTransmitter::OnSendError(SocketType) { } +inline void RTPTCPTransmitter::OnReceiveError(SocketType) { } + +} // end namespace + +#endif // RTPTCPTRANSMITTER_H + diff --git a/qrtplib/rtptimeutilities.cpp b/qrtplib/rtptimeutilities.cpp new file mode 100644 index 000000000..fabd89222 --- /dev/null +++ b/qrtplib/rtptimeutilities.cpp @@ -0,0 +1,49 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpconfig.h" +#include "rtptimeutilities.h" + +namespace qrtplib +{ + +RTPTimeInitializerObject::RTPTimeInitializerObject() +{ + RTPTime curtime = RTPTime::CurrentTime(); + JRTPLIB_UNUSED(curtime); + dummy = -1; +} + +RTPTimeInitializerObject timeinit; + +} // end namespace + diff --git a/qrtplib/rtptimeutilities.h b/qrtplib/rtptimeutilities.h new file mode 100644 index 000000000..e180fc63b --- /dev/null +++ b/qrtplib/rtptimeutilities.h @@ -0,0 +1,393 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtptimeutilities.h + */ + +#ifndef RTPTIMEUTILITIES_H + +#define RTPTIMEUTILITIES_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#ifndef RTP_HAVE_QUERYPERFORMANCECOUNTER + #include + #include + #include +#endif // RTP_HAVE_QUERYPERFORMANCECOUNTER + +#define RTP_NTPTIMEOFFSET 2208988800UL + +#ifdef RTP_HAVE_VSUINT64SUFFIX +#define C1000000 1000000ui64 +#define CEPOCH 11644473600000000ui64 +#else +#define C1000000 1000000ULL +#define CEPOCH 11644473600000000ULL +#endif // RTP_HAVE_VSUINT64SUFFIX + +namespace qrtplib +{ + +/** + * This is a simple wrapper for the most significant word (MSW) and least + * significant word (LSW) of an NTP timestamp. + */ +class JRTPLIB_IMPORTEXPORT RTPNTPTime +{ +public: + /** This constructor creates and instance with MSW \c m and LSW \c l. */ + RTPNTPTime(uint32_t m,uint32_t l) { msw = m ; lsw = l; } + + /** Returns the most significant word. */ + uint32_t GetMSW() const { return msw; } + + /** Returns the least significant word. */ + uint32_t GetLSW() const { return lsw; } +private: + uint32_t msw,lsw; +}; + +/** This class is used to specify wallclock time, delay intervals etc. + * This class is used to specify wallclock time, delay intervals etc. + * It stores a number of seconds and a number of microseconds. + */ +class JRTPLIB_IMPORTEXPORT RTPTime +{ +public: + /** Returns an RTPTime instance representing the current wallclock time. + * Returns an RTPTime instance representing the current wallclock time. This is expressed + * as a number of seconds since 00:00:00 UTC, January 1, 1970. + */ + static RTPTime CurrentTime(); + + /** This function waits the amount of time specified in \c delay. */ + static void Wait(const RTPTime &delay); + + /** Creates an RTPTime instance representing \c t, which is expressed in units of seconds. */ + RTPTime(double t); + + /** Creates an instance that corresponds to \c ntptime. + * Creates an instance that corresponds to \c ntptime. If + * the conversion cannot be made, both the seconds and the + * microseconds are set to zero. + */ + RTPTime(RTPNTPTime ntptime); + + /** Creates an instance corresponding to \c seconds and \c microseconds. */ + RTPTime(int64_t seconds, uint32_t microseconds); + + /** Returns the number of seconds stored in this instance. */ + int64_t GetSeconds() const; + + /** Returns the number of microseconds stored in this instance. */ + uint32_t GetMicroSeconds() const; + + /** Returns the time stored in this instance, expressed in units of seconds. */ + double GetDouble() const { return m_t; } + + /** Returns the NTP time corresponding to the time stored in this instance. */ + RTPNTPTime GetNTPTime() const; + + RTPTime &operator-=(const RTPTime &t); + RTPTime &operator+=(const RTPTime &t); + bool operator<(const RTPTime &t) const; + bool operator>(const RTPTime &t) const; + bool operator<=(const RTPTime &t) const; + bool operator>=(const RTPTime &t) const; + + bool IsZero() const { return m_t == 0; } +private: +#ifdef RTP_HAVE_QUERYPERFORMANCECOUNTER + static inline uint64_t CalculateMicroseconds(uint64_t performancecount,uint64_t performancefrequency); +#endif // RTP_HAVE_QUERYPERFORMANCECOUNTER + + double m_t; +}; + +inline RTPTime::RTPTime(double t) +{ + m_t = t; +} + +inline RTPTime::RTPTime(int64_t seconds, uint32_t microseconds) +{ + if (seconds >= 0) + { + m_t = (double)seconds + 1e-6*(double)microseconds; + } + else + { + int64_t possec = -seconds; + + m_t = (double)possec + 1e-6*(double)microseconds; + m_t = -m_t; + } +} + +inline RTPTime::RTPTime(RTPNTPTime ntptime) +{ + if (ntptime.GetMSW() < RTP_NTPTIMEOFFSET) + { + m_t = 0; + } + else + { + uint32_t sec = ntptime.GetMSW() - RTP_NTPTIMEOFFSET; + + double x = (double)ntptime.GetLSW(); + x /= (65536.0*65536.0); + x *= 1000000.0; + uint32_t microsec = (uint32_t)x; + + m_t = (double)sec + 1e-6*(double)microsec; + } +} + +inline int64_t RTPTime::GetSeconds() const +{ + return (int64_t)m_t; +} + +inline uint32_t RTPTime::GetMicroSeconds() const +{ + uint32_t microsec; + + if (m_t >= 0) + { + int64_t sec = (int64_t)m_t; + microsec = (uint32_t)(1e6*(m_t - (double)sec) + 0.5); + } + else // m_t < 0 + { + int64_t sec = (int64_t)(-m_t); + microsec = (uint32_t)(1e6*((-m_t) - (double)sec) + 0.5); + } + + if (microsec >= 1000000) + return 999999; + // Unsigned, it can never be less than 0 + // if (microsec < 0) + // return 0; + return microsec; +} + +#ifdef RTP_HAVE_QUERYPERFORMANCECOUNTER + +inline uint64_t RTPTime::CalculateMicroseconds(uint64_t performancecount,uint64_t performancefrequency) +{ + uint64_t f = performancefrequency; + uint64_t a = performancecount; + uint64_t b = a/f; + uint64_t c = a%f; // a = b*f+c => (a*1000000)/f = b*1000000+(c*1000000)/f + + return b*C1000000+(c*C1000000)/f; +} + +inline RTPTime RTPTime::CurrentTime() +{ + static int inited = 0; + static uint64_t microseconds, initmicroseconds; + static LARGE_INTEGER performancefrequency; + + uint64_t emulate_microseconds, microdiff; + SYSTEMTIME systemtime; + FILETIME filetime; + + LARGE_INTEGER performancecount; + + QueryPerformanceCounter(&performancecount); + + if(!inited){ + inited = 1; + QueryPerformanceFrequency(&performancefrequency); + GetSystemTime(&systemtime); + SystemTimeToFileTime(&systemtime,&filetime); + microseconds = ( ((uint64_t)(filetime.dwHighDateTime) << 32) + (uint64_t)(filetime.dwLowDateTime) ) / (uint64_t)10; + microseconds-= CEPOCH; // EPOCH + initmicroseconds = CalculateMicroseconds(performancecount.QuadPart, performancefrequency.QuadPart); + } + + emulate_microseconds = CalculateMicroseconds(performancecount.QuadPart, performancefrequency.QuadPart); + + microdiff = emulate_microseconds - initmicroseconds; + + double t = 1e-6*(double)(microseconds + microdiff); + return RTPTime(t); +} + +inline void RTPTime::Wait(const RTPTime &delay) +{ + if (delay.m_t <= 0) + return; + + uint64_t sec = (uint64_t)delay.m_t; + uint32_t microsec = (uint32_t)(1e6*(delay.m_t-(double)sec)); + DWORD t = ((DWORD)sec)*1000+(((DWORD)microsec)/1000); + Sleep(t); +} + +#else // unix style + +#ifdef RTP_HAVE_CLOCK_GETTIME +inline double RTPTime_timespecToDouble(struct timespec &ts) +{ + return (double)ts.tv_sec + 1e-9*(double)ts.tv_nsec; +} + +inline RTPTime RTPTime::CurrentTime() +{ + static bool s_initialized = false; + static double s_startOffet = 0; + + if (!s_initialized) + { + s_initialized = true; + + // Get the corresponding times in system time and monotonic time + struct timespec tpSys, tpMono; + + clock_gettime(CLOCK_REALTIME, &tpSys); + clock_gettime(CLOCK_MONOTONIC, &tpMono); + + double tSys = RTPTime_timespecToDouble(tpSys); + double tMono = RTPTime_timespecToDouble(tpMono); + + s_startOffet = tSys - tMono; + return tSys; + } + + struct timespec tpMono; + clock_gettime(CLOCK_MONOTONIC, &tpMono); + + double tMono0 = RTPTime_timespecToDouble(tpMono); + return tMono0 + s_startOffet; +} + +#else // gettimeofday fallback + +inline RTPTime RTPTime::CurrentTime() +{ + struct timeval tv; + + gettimeofday(&tv,0); + return RTPTime((uint64_t)tv.tv_sec,(uint32_t)tv.tv_usec); +} +#endif // RTP_HAVE_CLOCK_GETTIME + +inline void RTPTime::Wait(const RTPTime &delay) +{ + if (delay.m_t <= 0) + return; + + uint64_t sec = (uint64_t)delay.m_t; + uint64_t nanosec = (uint32_t)(1e9*(delay.m_t-(double)sec)); + + struct timespec req,rem; + int ret; + + req.tv_sec = (time_t)sec; + req.tv_nsec = ((long)nanosec); + do + { + ret = nanosleep(&req,&rem); + req = rem; + } while (ret == -1 && errno == EINTR); +} + +#endif // RTP_HAVE_QUERYPERFORMANCECOUNTER + +inline RTPTime &RTPTime::operator-=(const RTPTime &t) +{ + m_t -= t.m_t; + return *this; +} + +inline RTPTime &RTPTime::operator+=(const RTPTime &t) +{ + m_t += t.m_t; + return *this; +} + +inline RTPNTPTime RTPTime::GetNTPTime() const +{ + uint32_t sec = (uint32_t)m_t; + uint32_t microsec = (uint32_t)((m_t - (double)sec)*1e6); + + uint32_t msw = sec+RTP_NTPTIMEOFFSET; + uint32_t lsw; + double x; + + x = microsec/1000000.0; + x *= (65536.0*65536.0); + lsw = (uint32_t)x; + + return RTPNTPTime(msw,lsw); +} + +inline bool RTPTime::operator<(const RTPTime &t) const +{ + return m_t < t.m_t; +} + +inline bool RTPTime::operator>(const RTPTime &t) const +{ + return m_t > t.m_t; +} + +inline bool RTPTime::operator<=(const RTPTime &t) const +{ + return m_t <= t.m_t; +} + +inline bool RTPTime::operator>=(const RTPTime &t) const +{ + return m_t >= t.m_t; +} + +class JRTPLIB_IMPORTEXPORT RTPTimeInitializerObject +{ +public: + RTPTimeInitializerObject(); + void Dummy() { dummy++; } +private: + int dummy; +}; + +extern RTPTimeInitializerObject timeinit; + +} // end namespace + + +#endif // RTPTIMEUTILITIES_H + diff --git a/qrtplib/rtptransmitter.h b/qrtplib/rtptransmitter.h new file mode 100644 index 000000000..bb6463a2c --- /dev/null +++ b/qrtplib/rtptransmitter.h @@ -0,0 +1,260 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtptransmitter.h + */ + +#ifndef RTPTRANSMITTER_H + +#define RTPTRANSMITTER_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtpmemoryobject.h" +#include "rtptimeutilities.h" + +namespace qrtplib +{ + +class RTPRawPacket; +class RTPAddress; +class RTPTransmissionParams; +class RTPTime; +class RTPTransmissionInfo; + +/** Abstract class from which actual transmission components should be derived. + * Abstract class from which actual transmission components should be derived. + * The abstract class RTPTransmitter specifies the interface for + * actual transmission components. Currently, three implementations exist: + * an UDP over IPv4 transmitter, an UDP over IPv6 transmitter and a transmitter + * which can be used to use an external transmission mechanism. + */ +class JRTPLIB_IMPORTEXPORT RTPTransmitter : public RTPMemoryObject +{ +public: + /** Used to identify a specific transmitter. + * If UserDefinedProto is used in the RTPSession::Create function, the RTPSession + * virtual member function NewUserDefinedTransmitter will be called to create + * a transmission component. + */ + enum TransmissionProtocol + { + IPv4UDPProto, /**< Specifies the internal UDP over IPv4 transmitter. */ + IPv6UDPProto, /**< Specifies the internal UDP over IPv6 transmitter. */ + TCPProto, /**< Specifies the internal TCP transmitter. */ + ExternalProto, /**< Specifies the transmitter which can send packets using an external mechanism, and which can have received packets injected into it - see RTPExternalTransmitter for additional information. */ + UserDefinedProto /**< Specifies a user defined, external transmitter. */ + }; + + /** Three kind of receive modes can be specified. */ + enum ReceiveMode + { + AcceptAll, /**< All incoming data is accepted, no matter where it originated from. */ + AcceptSome, /**< Only data coming from specific sources will be accepted. */ + IgnoreSome /**< All incoming data is accepted, except for data coming from a specific set of sources. */ + }; +protected: + /** Constructor in which you can specify a memory manager to use. */ + RTPTransmitter(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) { timeinit.Dummy(); } +public: + virtual ~RTPTransmitter() { } + + /** This function must be called before the transmission component can be used. + * This function must be called before the transmission component can be used. Depending on + * the value of \c threadsafe, the component will be created for thread-safe usage or not. + */ + virtual int Init(bool threadsafe) = 0; + + /** Prepares the component to be used. + * Prepares the component to be used. The parameter \c maxpacksize specifies the maximum size + * a packet can have: if the packet is larger it will not be transmitted. The \c transparams + * parameter specifies a pointer to an RTPTransmissionParams instance. This is also an abstract + * class and each actual component will define its own parameters by inheriting a class + * from RTPTransmissionParams. If \c transparams is NULL, the default transmission parameters + * for the component will be used. + */ + virtual int Create(size_t maxpacksize,const RTPTransmissionParams *transparams) = 0; + + /** By calling this function, buffers are cleared and the component cannot be used anymore. + * By calling this function, buffers are cleared and the component cannot be used anymore. + * Only when the Create function is called again can the component be used again. */ + virtual void Destroy() = 0; + + /** Returns additional information about the transmitter. + * This function returns an instance of a subclass of RTPTransmissionInfo which will give + * some additional information about the transmitter (a list of local IP addresses for example). + * Currently, either an instance of RTPUDPv4TransmissionInfo or RTPUDPv6TransmissionInfo is + * returned, depending on the type of the transmitter. The user has to deallocate the returned + * instance when it is no longer needed, which can be done using RTPTransmitter::DeleteTransmissionInfo. + */ + virtual RTPTransmissionInfo *GetTransmissionInfo() = 0; + + /** Deallocates the information returned by RTPTransmitter::GetTransmissionInfo . + * Deallocates the information returned by RTPTransmitter::GetTransmissionInfo . + */ + virtual void DeleteTransmissionInfo(RTPTransmissionInfo *inf) = 0; + + /** Looks up the local host name. + * Looks up the local host name based upon internal information about the local host's + * addresses. This function might take some time since a DNS query might be done. \c bufferlength + * should initially contain the number of bytes that may be stored in \c buffer. If the function + * succeeds, \c bufferlength is set to the number of bytes stored in \c buffer. Note that the data + * in \c buffer is not NULL-terminated. If the function fails because the buffer isn't large enough, + * it returns \c ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL and stores the number of bytes needed in + * \c bufferlength. + */ + virtual int GetLocalHostName(uint8_t *buffer,size_t *bufferlength) = 0; + + /** Returns \c true if the address specified by \c addr is one of the addresses of the transmitter. */ + virtual bool ComesFromThisTransmitter(const RTPAddress *addr) = 0; + + /** Returns the amount of bytes that will be added to the RTP packet by the underlying layers (excluding + * the link layer). */ + virtual size_t GetHeaderOverhead() = 0; + + /** Checks for incoming data and stores it. */ + virtual int Poll() = 0; + + /** Waits until incoming data is detected. + * Waits at most a time \c delay until incoming data has been detected. If \c dataavailable is not NULL, + * it should be set to \c true if data was actually read and to \c false otherwise. + */ + virtual int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0) = 0; + + /** If the previous function has been called, this one aborts the waiting. */ + virtual int AbortWait() = 0; + + /** Send a packet with length \c len containing \c data to all RTP addresses of the current destination list. */ + virtual int SendRTPData(const void *data,size_t len) = 0; + + /** Send a packet with length \c len containing \c data to all RTCP addresses of the current destination list. */ + virtual int SendRTCPData(const void *data,size_t len) = 0; + + /** Adds the address specified by \c addr to the list of destinations. */ + virtual int AddDestination(const RTPAddress &addr) = 0; + + /** Deletes the address specified by \c addr from the list of destinations. */ + virtual int DeleteDestination(const RTPAddress &addr) = 0; + + /** Clears the list of destinations. */ + virtual void ClearDestinations() = 0; + + /** Returns \c true if the transmission component supports multicasting. */ + virtual bool SupportsMulticasting() = 0; + + /** Joins the multicast group specified by \c addr. */ + virtual int JoinMulticastGroup(const RTPAddress &addr) = 0; + + /** Leaves the multicast group specified by \c addr. */ + virtual int LeaveMulticastGroup(const RTPAddress &addr) = 0; + + /** Leaves all the multicast groups that have been joined. */ + virtual void LeaveAllMulticastGroups() = 0; + + /** Sets the receive mode. + * Sets the receive mode to \c m, which is one of the following: RTPTransmitter::AcceptAll, + * RTPTransmitter::AcceptSome or RTPTransmitter::IgnoreSome. Note that if the receive + * mode is changed, all information about the addresses to ignore to accept is lost. + */ + virtual int SetReceiveMode(RTPTransmitter::ReceiveMode m) = 0; + + /** Adds \c addr to the list of addresses to ignore. */ + virtual int AddToIgnoreList(const RTPAddress &addr) = 0; + + /** Deletes \c addr from the list of addresses to accept. */ + virtual int DeleteFromIgnoreList(const RTPAddress &addr)= 0; + + /** Clears the list of addresses to ignore. */ + virtual void ClearIgnoreList() = 0; + + /** Adds \c addr to the list of addresses to accept. */ + virtual int AddToAcceptList(const RTPAddress &addr) = 0; + + /** Deletes \c addr from the list of addresses to accept. */ + virtual int DeleteFromAcceptList(const RTPAddress &addr) = 0; + + /** Clears the list of addresses to accept. */ + virtual void ClearAcceptList() = 0; + + /** Sets the maximum packet size which the transmitter should allow to \c s. */ + virtual int SetMaximumPacketSize(size_t s) = 0; + + /** Returns \c true if packets can be obtained using the GetNextPacket member function. */ + virtual bool NewDataAvailable() = 0; + + /** Returns the raw data of a received RTP packet (received during the Poll function) + * in an RTPRawPacket instance. */ + virtual RTPRawPacket *GetNextPacket() = 0; +}; + +/** Base class for transmission parameters. + * This class is an abstract class which will have a specific implementation for a + * specific kind of transmission component. All actual implementations inherit the + * GetTransmissionProtocol function which identifies the component type for which + * these parameters are valid. + */ +class JRTPLIB_IMPORTEXPORT RTPTransmissionParams +{ +protected: + RTPTransmissionParams(RTPTransmitter::TransmissionProtocol p) { protocol = p; } +public: + virtual ~RTPTransmissionParams() { } + + /** Returns the transmitter type for which these parameters are valid. */ + RTPTransmitter::TransmissionProtocol GetTransmissionProtocol() const { return protocol; } +private: + RTPTransmitter::TransmissionProtocol protocol; +}; + +/** Base class for additional information about the transmitter. + * This class is an abstract class which will have a specific implementation for a + * specific kind of transmission component. All actual implementations inherit the + * GetTransmissionProtocol function which identifies the component type for which + * these parameters are valid. + */ +class JRTPLIB_IMPORTEXPORT RTPTransmissionInfo +{ +protected: + RTPTransmissionInfo(RTPTransmitter::TransmissionProtocol p) { protocol = p; } +public: + virtual ~RTPTransmissionInfo() { } + /** Returns the transmitter type for which these parameters are valid. */ + RTPTransmitter::TransmissionProtocol GetTransmissionProtocol() const { return protocol; } +private: + RTPTransmitter::TransmissionProtocol protocol; +}; + +} // end namespace + +#endif // RTPTRANSMITTER_H + diff --git a/qrtplib/rtptypes.h b/qrtplib/rtptypes.h new file mode 100644 index 000000000..161238853 --- /dev/null +++ b/qrtplib/rtptypes.h @@ -0,0 +1,5 @@ +#include "rtpconfig.h" + + +#include +#include diff --git a/qrtplib/rtptypes_win.h b/qrtplib/rtptypes_win.h new file mode 100644 index 000000000..350e061a2 --- /dev/null +++ b/qrtplib/rtptypes_win.h @@ -0,0 +1,53 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef RTPTYPES_WIN_H + +#define RTPTYPES_WIN_H + +#ifndef INTTYPES_DEFINED + +#define INTTYPES_DEFINED + +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#endif // INTTYPES_DEFINED + +#endif // RTPTYPES_WIN_H + diff --git a/qrtplib/rtpudpv4transmitter.cpp b/qrtplib/rtpudpv4transmitter.cpp new file mode 100644 index 000000000..c4bbbd8a2 --- /dev/null +++ b/qrtplib/rtpudpv4transmitter.cpp @@ -0,0 +1,1934 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpudpv4transmitter.h" +#include "rtprawpacket.h" +#include "rtpipv4address.h" +#include "rtptimeutilities.h" +#include "rtpdefines.h" +#include "rtpstructs.h" +#include "rtpsocketutilinternal.h" +#include "rtpinternalutils.h" +#include "rtpselect.h" +#include +#include +#include + +#include + +using namespace std; + +#define RTPUDPV4TRANS_MAXPACKSIZE 65535 +#define RTPUDPV4TRANS_IFREQBUFSIZE 8192 + +#define RTPUDPV4TRANS_IS_MCASTADDR(x) (((x)&0xF0000000) == 0xE0000000) + +#define RTPUDPV4TRANS_MCASTMEMBERSHIP(socket,type,mcastip,status) {\ + struct ip_mreq mreq;\ + \ + mreq.imr_multiaddr.s_addr = htonl(mcastip);\ + mreq.imr_interface.s_addr = htonl(mcastifaceIP);\ + status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ + } +#ifdef RTP_SUPPORT_THREAD + #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } + #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } + #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } + #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } +#else + #define MAINMUTEX_LOCK + #define MAINMUTEX_UNLOCK + #define WAITMUTEX_LOCK + #define WAITMUTEX_UNLOCK +#endif // RTP_SUPPORT_THREAD + +#define CLOSESOCKETS do { \ + if (closesocketswhendone) \ + {\ + if (rtpsock != rtcpsock) \ + RTPCLOSE(rtcpsock); \ + RTPCLOSE(rtpsock); \ + } \ +} while(0) + + +namespace qrtplib +{ + +RTPUDPv4Transmitter::RTPUDPv4Transmitter(RTPMemoryManager *mgr) : RTPTransmitter(mgr),destinations(mgr,RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT), +#ifdef RTP_SUPPORT_IPV4MULTICAST + multicastgroups(mgr,RTPMEM_TYPE_CLASS_MULTICASTHASHELEMENT), +#endif // RTP_SUPPORT_IPV4MULTICAST + acceptignoreinfo(mgr,RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT) +{ + created = false; + init = false; +} + +RTPUDPv4Transmitter::~RTPUDPv4Transmitter() +{ + Destroy(); +} + +int RTPUDPv4Transmitter::Init(bool tsafe) +{ + if (init) + return ERR_RTP_UDPV4TRANS_ALREADYINIT; + +#ifdef RTP_SUPPORT_THREAD + threadsafe = tsafe; + if (threadsafe) + { + int status; + + status = mainmutex.Init(); + if (status < 0) + return ERR_RTP_UDPV4TRANS_CANTINITMUTEX; + status = waitmutex.Init(); + if (status < 0) + return ERR_RTP_UDPV4TRANS_CANTINITMUTEX; + } +#else + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; +#endif // RTP_SUPPORT_THREAD + + init = true; + return 0; +} + +int RTPUDPv4Transmitter::GetIPv4SocketPort(SocketType s, uint16_t *pPort) +{ + assert(pPort != 0); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(struct sockaddr_in)); + + RTPSOCKLENTYPE size = sizeof(struct sockaddr_in); + if (getsockname(s,(struct sockaddr*)&addr,&size) != 0) + return ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT; + + if (addr.sin_family != AF_INET) + return ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET; + + uint16_t port = ntohs(addr.sin_port); + if (port == 0) + return ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET; + + int type = 0; + RTPSOCKLENTYPE length = sizeof(type); + + if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*)&type, &length) != 0) + return ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE; + + if (type != SOCK_DGRAM) + return ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE; + + *pPort = port; + return 0; +} + +int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, + SocketType *pRtpSock, SocketType *pRtcpSock, + uint16_t *pRtpPort, uint16_t *pRtcpPort) +{ + const int maxAttempts = 1024; + int attempts = 0; + vector toClose; + + while (attempts++ < maxAttempts) + { + SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock == RTPSOCKERR) + { + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + + // First we get an automatically chosen port + + struct sockaddr_in addr; + memset(&addr,0,sizeof(struct sockaddr_in)); + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(sock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(sock); + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; + } + + uint16_t basePort = 0; + int status = GetIPv4SocketPort(sock, &basePort); + if (status < 0) + { + RTPCLOSE(sock); + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + return status; + } + + if (rtcpMux) // only need one socket + { + if (basePort%2 == 0 || allowOdd) + { + *pRtpSock = sock; + *pRtcpSock = sock; + *pRtpPort = basePort; + *pRtcpPort = basePort; + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + + return 0; + } + else + toClose.push_back(sock); + } + else + { + SocketType sock2 = socket(PF_INET, SOCK_DGRAM, 0); + if (sock2 == RTPSOCKERR) + { + RTPCLOSE(sock); + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + + // Try the next port or the previous port + uint16_t secondPort = basePort; + bool possiblyValid = false; + + if (basePort%2 == 0) + { + secondPort++; + possiblyValid = true; + } + else if (basePort > 1) // avoid landing on port 0 + { + secondPort--; + possiblyValid = true; + } + + if (possiblyValid) + { + memset(&addr,0,sizeof(struct sockaddr_in)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(secondPort); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(sock2,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) == 0) + { + // In this case, we have two consecutive port numbers, the lower of + // which is even + + if (basePort < secondPort) + { + *pRtpSock = sock; + *pRtcpSock = sock2; + *pRtpPort = basePort; + *pRtcpPort = secondPort; + } + else + { + *pRtpSock = sock2; + *pRtcpSock = sock; + *pRtpPort = secondPort; + *pRtcpPort = basePort; + } + + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + + return 0; + } + } + + toClose.push_back(sock); + toClose.push_back(sock2); + } + } + + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + + return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; +} + +int RTPUDPv4Transmitter::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) +{ + const RTPUDPv4TransmissionParams *params,defaultparams; + struct sockaddr_in addr; + RTPSOCKLENTYPE size; + int status; + + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ALREADYCREATED; + } + + // Obtain transmission parameters + + if (transparams == 0) + params = &defaultparams; + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; + } + params = (const RTPUDPv4TransmissionParams *)transparams; + } + + if (params->GetUseExistingSockets(rtpsock, rtcpsock)) + { + closesocketswhendone = false; + + // Determine the port numbers + int status = GetIPv4SocketPort(rtpsock, &m_rtpPort); + if (status < 0) + { + MAINMUTEX_UNLOCK + return status; + } + status = GetIPv4SocketPort(rtcpsock, &m_rtcpPort); + if (status < 0) + { + MAINMUTEX_UNLOCK + return status; + } + } + else + { + closesocketswhendone = true; + + if (params->GetPortbase() == 0) + { + int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), + &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); + if (status < 0) + { + MAINMUTEX_UNLOCK + return status; + } + } + else + { + // Check if portbase is even (if necessary) + if (!params->GetAllowOddPortbase() && params->GetPortbase()%2 != 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; + } + + // create sockets + + rtpsock = socket(PF_INET,SOCK_DGRAM,0); + if (rtpsock == RTPSOCKERR) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + + // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket + if (params->GetRTCPMultiplexing()) + rtcpsock = rtpsock; + else + { + rtcpsock = socket(PF_INET,SOCK_DGRAM,0); + if (rtcpsock == RTPSOCKERR) + { + RTPCLOSE(rtpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + } + + // bind sockets + + uint32_t bindIP = params->GetBindIP(); + + m_rtpPort = params->GetPortbase(); + + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(params->GetPortbase()); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(rtpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; + } + + if (rtpsock != rtcpsock) // no need to bind same socket twice when multiplexing + { + uint16_t rtpport = params->GetPortbase(); + uint16_t rtcpport = params->GetForcedRTCPPort(); + + if (rtcpport == 0) + { + rtcpport = rtpport; + if (rtcpport < 0xFFFF) + rtcpport++; + } + + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(rtcpport); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(rtcpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; + } + + m_rtcpPort = rtcpport; + } + else + m_rtcpPort = m_rtpPort; + } + + // set socket buffer sizes + + size = params->GetRTPReceiveBuffer(); + if (setsockopt(rtpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; + } + size = params->GetRTPSendBuffer(); + if (setsockopt(rtpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; + } + + if (rtpsock != rtcpsock) // no need to set RTCP flags when multiplexing + { + size = params->GetRTCPReceiveBuffer(); + if (setsockopt(rtcpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; + } + size = params->GetRTCPSendBuffer(); + if (setsockopt(rtcpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; + } + } + } + + // Try to obtain local IP addresses + + localIPs = params->GetLocalIPList(); + if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them + { + int status; + + if ((status = CreateLocalIPList()) < 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return status; + } + } + +#ifdef RTP_SUPPORT_IPV4MULTICAST + if (SetMulticastTTL(params->GetMulticastTTL())) + supportsmulticasting = true; + else + supportsmulticasting = false; +#else // no multicast support enabled + supportsmulticasting = false; +#endif // RTP_SUPPORT_IPV4MULTICAST + + if (maximumpacketsize > RTPUDPV4TRANS_MAXPACKSIZE) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + if (!params->GetCreatedAbortDescriptors()) + { + if ((status = m_abortDesc.Init()) < 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return status; + } + m_pAbortDesc = &m_abortDesc; + } + else + { + m_pAbortDesc = params->GetCreatedAbortDescriptors(); + if (!m_pAbortDesc->IsInitialized()) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; + } + } + + maxpacksize = maximumpacketsize; + multicastTTL = params->GetMulticastTTL(); + mcastifaceIP = params->GetMulticastInterfaceIP(); + receivemode = RTPTransmitter::AcceptAll; + + localhostname = 0; + localhostnamelength = 0; + + waitingfordata = false; + created = true; + MAINMUTEX_UNLOCK + return 0; +} + +void RTPUDPv4Transmitter::Destroy() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK; + return; + } + + if (localhostname) + { + RTPDeleteByteArray(localhostname,GetMemoryManager()); + localhostname = 0; + localhostnamelength = 0; + } + + CLOSESOCKETS; + destinations.Clear(); +#ifdef RTP_SUPPORT_IPV4MULTICAST + multicastgroups.Clear(); +#endif // RTP_SUPPORT_IPV4MULTICAST + FlushPackets(); + ClearAcceptIgnoreInfo(); + localIPs.clear(); + created = false; + + if (waitingfordata) + { + m_pAbortDesc->SendAbortSignal(); + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK + } + else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + + MAINMUTEX_UNLOCK +} + +RTPTransmissionInfo *RTPUDPv4Transmitter::GetTransmissionInfo() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPUDPv4TransmissionInfo(localIPs,rtpsock,rtcpsock,m_rtpPort,m_rtcpPort); + MAINMUTEX_UNLOCK + return tinf; +} + +void RTPUDPv4Transmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) +{ + if (!init) + return; + + RTPDelete(i, GetMemoryManager()); +} + +int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (localhostname == 0) + { + if (localIPs.empty()) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOLOCALIPS; + } + + std::list::const_iterator it; + std::list hostnames; + + for (it = localIPs.begin() ; it != localIPs.end() ; it++) + { + bool founddouble = false; + bool foundentry = true; + + while (!founddouble && foundentry) + { + struct hostent *he; + uint8_t addr[4]; + uint32_t ip = (*it); + + addr[0] = (uint8_t)((ip>>24)&0xFF); + addr[1] = (uint8_t)((ip>>16)&0xFF); + addr[2] = (uint8_t)((ip>>8)&0xFF); + addr[3] = (uint8_t)(ip&0xFF); + he = gethostbyaddr((char *)addr,4,AF_INET); + if (he != 0) + { + std::string hname = std::string(he->h_name); + std::list::const_iterator it; + + for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + hostnames.push_back(hname); + + int i = 0; + while (!founddouble && he->h_aliases[i] != 0) + { + std::string hname = std::string(he->h_aliases[i]); + + for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + { + hostnames.push_back(hname); + i++; + } + } + } + else + foundentry = false; + } + } + + bool found = false; + + if (!hostnames.empty()) // try to select the most appropriate hostname + { + std::list::const_iterator it; + + hostnames.sort(); + for (it = hostnames.begin() ; !found && it != hostnames.end() ; it++) + { + if ((*it).find('.') != std::string::npos) + { + found = true; + localhostnamelength = (*it).length(); + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname,(*it).c_str(),localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } + } + + if (!found) // use an IP address + { + uint32_t ip; + int len; + char str[16]; + + it = localIPs.begin(); + ip = (*it); + + RTP_SNPRINTF(str,16,"%d.%d.%d.%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF)); + len = strlen(str); + + localhostnamelength = len; + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength + 1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname,str,localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } + + if ((*bufferlength) < localhostnamelength) + { + *bufferlength = localhostnamelength; // tell the application the required size of the buffer + MAINMUTEX_UNLOCK + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; + } + + memcpy(buffer,localhostname,localhostnamelength); + *bufferlength = localhostnamelength; + + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPUDPv4Transmitter::ComesFromThisTransmitter(const RTPAddress *addr) +{ + if (!init) + return false; + + if (addr == 0) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (created && addr->GetAddressType() == RTPAddress::IPv4Address) + { + const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; + bool found = false; + std::list::const_iterator it; + + it = localIPs.begin(); + while (!found && it != localIPs.end()) + { + if (addr2->GetIP() == *it) + found = true; + else + ++it; + } + + if (!found) + v = false; + else + { + if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port + v = true; + else + v = false; + } + } + else + v = false; + + MAINMUTEX_UNLOCK + return v; +} + +int RTPUDPv4Transmitter::Poll() +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + int status; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + status = PollSocket(true); // poll RTP socket + if (rtpsock != rtcpsock) // no need to poll twice when multiplexing + { + if (status >= 0) + status = PollSocket(false); // poll RTCP socket + } + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4Transmitter::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (waitingfordata) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ALREADYWAITING; + } + + SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); + + SocketType socks[3] = { rtpsock, rtcpsock, abortSocket }; + int8_t readflags[3] = { 0, 0, 0 }; + const int idxRTP = 0; + const int idxRTCP = 1; + const int idxAbort = 2; + + waitingfordata = true; + + WAITMUTEX_LOCK + MAINMUTEX_UNLOCK + + int status = RTPSelect(socks, readflags, 3, delay); + if (status < 0) + { + MAINMUTEX_LOCK + waitingfordata = false; + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return status; + } + + MAINMUTEX_LOCK + waitingfordata = false; + if (!created) // destroy called + { + MAINMUTEX_UNLOCK; + WAITMUTEX_UNLOCK + return 0; + } + + // if aborted, read from abort buffer + if (readflags[idxAbort]) + m_pAbortDesc->ReadSignallingByte(); + + if (dataavailable != 0) + { + if (readflags[idxRTP] || readflags[idxRTCP]) + *dataavailable = true; + else + *dataavailable = false; + } + + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4Transmitter::AbortWait() +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (!waitingfordata) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTWAITING; + } + + m_pAbortDesc->SendAbortSignal(); + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4Transmitter::SendRTPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTPSockAddr(),sizeof(struct sockaddr_in)); + destinations.GotoNextElement(); + } + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4Transmitter::SendRTCPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtcpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTCPSockAddr(),sizeof(struct sockaddr_in)); + destinations.GotoNextElement(); + } + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4Transmitter::AddDestination(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + + int status = destinations.AddElement(dest); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4Transmitter::DeleteDestination(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + + int status = destinations.DeleteElement(dest); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv4Transmitter::ClearDestinations() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created) + destinations.Clear(); + MAINMUTEX_UNLOCK +} + +bool RTPUDPv4Transmitter::SupportsMulticasting() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + v = supportsmulticasting; + + MAINMUTEX_UNLOCK + return v; +} + +#ifdef RTP_SUPPORT_IPV4MULTICAST + +int RTPUDPv4Transmitter::JoinMulticastGroup(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + uint32_t mcastIP = address.GetIP(); + + if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.AddElement(mcastIP); + if (status >= 0) + { + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock,IP_ADD_MEMBERSHIP,mcastIP,status); + if (status != 0) + { + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + + if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing + { + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock,IP_ADD_MEMBERSHIP,mcastIP,status); + if (status != 0) + { + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + } + } + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + uint32_t mcastIP = address.GetIP(); + + if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.DeleteElement(mcastIP); + if (status >= 0) + { + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + + status = 0; + } + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv4Transmitter::LeaveAllMulticastGroups() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created) + { + multicastgroups.GotoFirstElement(); + while (multicastgroups.HasCurrentElement()) + { + uint32_t mcastIP; + int status = 0; + + mcastIP = multicastgroups.GetCurrentElement(); + + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + JRTPLIB_UNUSED(status); + + multicastgroups.GotoNextElement(); + } + multicastgroups.Clear(); + } + MAINMUTEX_UNLOCK +} + +#else // no multicast support + +int RTPUDPv4Transmitter::JoinMulticastGroup(const RTPAddress &addr) +{ + return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; +} + +int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) +{ + return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; +} + +void RTPUDPv4Transmitter::LeaveAllMulticastGroups() +{ +} + +#endif // RTP_SUPPORT_IPV4MULTICAST + +int RTPUDPv4Transmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (m != receivemode) + { + receivemode = m; + acceptignoreinfo.Clear(); + } + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4Transmitter::AddToIgnoreList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4Transmitter::DeleteFromIgnoreList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv4Transmitter::ClearIgnoreList() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created && receivemode == RTPTransmitter::IgnoreSome) + ClearAcceptIgnoreInfo(); + MAINMUTEX_UNLOCK +} + +int RTPUDPv4Transmitter::AddToAcceptList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4Transmitter::DeleteFromAcceptList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv4Transmitter::ClearAcceptList() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created && receivemode == RTPTransmitter::AcceptSome) + ClearAcceptIgnoreInfo(); + MAINMUTEX_UNLOCK +} + +int RTPUDPv4Transmitter::SetMaximumPacketSize(size_t s) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (s > RTPUDPV4TRANS_MAXPACKSIZE) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + maxpacksize = s; + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPUDPv4Transmitter::NewDataAvailable() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + { + if (rawpacketlist.empty()) + v = false; + else + v = true; + } + + MAINMUTEX_UNLOCK + return v; +} + +RTPRawPacket *RTPUDPv4Transmitter::GetNextPacket() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + + RTPRawPacket *p; + + if (!created) + { + MAINMUTEX_UNLOCK + return 0; + } + if (rawpacketlist.empty()) + { + MAINMUTEX_UNLOCK + return 0; + } + + p = *(rawpacketlist.begin()); + rawpacketlist.pop_front(); + + MAINMUTEX_UNLOCK + return p; +} + +// Here the private functions start... + +#ifdef RTP_SUPPORT_IPV4MULTICAST +bool RTPUDPv4Transmitter::SetMulticastTTL(uint8_t ttl) +{ + int ttl2,status; + + ttl2 = (int)ttl; + status = setsockopt(rtpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); + if (status != 0) + return false; + + if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing + { + status = setsockopt(rtcpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); + if (status != 0) + return false; + } + return true; +} +#endif // RTP_SUPPORT_IPV4MULTICAST + +void RTPUDPv4Transmitter::FlushPackets() +{ + std::list::const_iterator it; + + for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) + RTPDelete(*it,GetMemoryManager()); + rawpacketlist.clear(); +} + +int RTPUDPv4Transmitter::PollSocket(bool rtp) +{ + RTPSOCKLENTYPE fromlen; + int recvlen; + char packetbuffer[RTPUDPV4TRANS_MAXPACKSIZE]; +#ifdef RTP_SOCKETTYPE_WINSOCK + SOCKET sock; + unsigned long len; +#else + size_t len; + int sock; +#endif // RTP_SOCKETTYPE_WINSOCK + struct sockaddr_in srcaddr; + bool dataavailable; + + if (rtp) + sock = rtpsock; + else + sock = rtcpsock; + + do + { + len = 0; + RTPIOCTL(sock,FIONREAD,&len); + + if (len <= 0) // make sure a packet of length zero is not queued + { + // An alternative workaround would be to just use non-blocking sockets. + // However, since the user does have access to the sockets and I do not + // know how this would affect anyone else's code, I chose to do it using + // an extra select call in case ioctl says the length is zero. + + int8_t isset = 0; + int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); + if (status < 0) + return status; + + if (isset) + dataavailable = true; + else + dataavailable = false; + } + else + dataavailable = true; + + if (dataavailable) + { + RTPTime curtime = RTPTime::CurrentTime(); + fromlen = sizeof(struct sockaddr_in); + recvlen = recvfrom(sock,packetbuffer,RTPUDPV4TRANS_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); + if (recvlen > 0) + { + bool acceptdata; + + // got data, process it + if (receivemode == RTPTransmitter::AcceptAll) + acceptdata = true; + else + acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); + + if (acceptdata) + { + RTPRawPacket *pack; + RTPIPv4Address *addr; + uint8_t *datacopy; + + addr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); + if (addr == 0) + return ERR_RTP_OUTOFMEM; + datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[recvlen]; + if (datacopy == 0) + { + RTPDelete(addr,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + memcpy(datacopy,packetbuffer,recvlen); + + bool isrtp = rtp; + if (rtpsock == rtcpsock) // check payload type when multiplexing + { + isrtp = true; + + if ((size_t)recvlen > sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *)datacopy; + uint8_t packettype = rtcpheader->packettype; + + if (packettype >= 200 && packettype <= 204) + isrtp = false; + } + } + + pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,recvlen,addr,curtime,isrtp,GetMemoryManager()); + if (pack == 0) + { + RTPDelete(addr,GetMemoryManager()); + RTPDeleteByteArray(datacopy,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + rawpacketlist.push_back(pack); + } + } + } + } while (dataavailable); + + return 0; +} + +int RTPUDPv4Transmitter::ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port) +{ + acceptignoreinfo.GotoElement(ip); + if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists + { + PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); + + if (port == 0) // select all ports + { + portinf->all = true; + portinf->portlist.clear(); + } + else if (!portinf->all) + { + std::list::const_iterator it,begin,end; + + begin = portinf->portlist.begin(); + end = portinf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == port) // already in list + return 0; + } + portinf->portlist.push_front(port); + } + } + else // got to create an entry for this IP address + { + PortInfo *portinf; + int status; + + portinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO) PortInfo(); + if (port == 0) // select all ports + portinf->all = true; + else + portinf->portlist.push_front(port); + + status = acceptignoreinfo.AddElement(ip,portinf); + if (status < 0) + { + RTPDelete(portinf,GetMemoryManager()); + return status; + } + } + + return 0; +} + +void RTPUDPv4Transmitter::ClearAcceptIgnoreInfo() +{ + acceptignoreinfo.GotoFirstElement(); + while (acceptignoreinfo.HasCurrentElement()) + { + PortInfo *inf; + + inf = acceptignoreinfo.GetCurrentElement(); + RTPDelete(inf,GetMemoryManager()); + acceptignoreinfo.GotoNextElement(); + } + acceptignoreinfo.Clear(); +} + +int RTPUDPv4Transmitter::ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port) +{ + acceptignoreinfo.GotoElement(ip); + if (!acceptignoreinfo.HasCurrentElement()) + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + + PortInfo *inf; + + inf = acceptignoreinfo.GetCurrentElement(); + if (port == 0) // delete all entries + { + inf->all = false; + inf->portlist.clear(); + } + else // a specific port was selected + { + if (inf->all) // currently, all ports are selected. Add the one to remove to the list + { + // we have to check if the list doesn't contain the port already + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == port) // already in list: this means we already deleted the entry + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + } + inf->portlist.push_front(port); + } + else // check if we can find the port in the list + { + std::list::iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; ++it) + { + if (*it == port) // found it! + { + inf->portlist.erase(it); + return 0; + } + } + // didn't find it + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + } + } + return 0; +} + +bool RTPUDPv4Transmitter::ShouldAcceptData(uint32_t srcip,uint16_t srcport) +{ + if (receivemode == RTPTransmitter::AcceptSome) + { + PortInfo *inf; + + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return false; + + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // only accept the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return true; + } + return false; + } + else // accept all, except the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return false; + } + return true; + } + } + else // IgnoreSome + { + PortInfo *inf; + + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return true; + + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // ignore the ports in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return false; + } + return true; + } + else // ignore all, except the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return true; + } + return false; + } + } + return true; +} + +int RTPUDPv4Transmitter::CreateLocalIPList() +{ + // first try to obtain the list from the network interface info + + if (!GetLocalIPList_Interfaces()) + { + // If this fails, we'll have to depend on DNS info + GetLocalIPList_DNS(); + } + AddLoopbackAddress(); + return 0; +} + +#ifdef RTP_SOCKETTYPE_WINSOCK + +bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() +{ + unsigned char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; + DWORD outputsize; + DWORD numaddresses,i; + SOCKET_ADDRESS_LIST *addrlist; + + if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANS_IFREQBUFSIZE,&outputsize,NULL,NULL)) + return false; + + addrlist = (SOCKET_ADDRESS_LIST *)buffer; + numaddresses = addrlist->iAddressCount; + for (i = 0 ; i < numaddresses ; i++) + { + SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); + if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address + { + struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; + + localIPs.push_back(ntohl(addr->sin_addr.s_addr)); + } + } + + if (localIPs.empty()) + return false; + + return true; +} + +#else // use either getifaddrs or ioctl + +#ifdef RTP_SUPPORT_IFADDRS + +bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() +{ + struct ifaddrs *addrs,*tmp; + + getifaddrs(&addrs); + tmp = addrs; + + while (tmp != 0) + { + if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) + { + struct sockaddr_in *inaddr = (struct sockaddr_in *)tmp->ifa_addr; + localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); + } + tmp = tmp->ifa_next; + } + + freeifaddrs(addrs); + + if (localIPs.empty()) + return false; + return true; +} + +#else // user ioctl + +bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() +{ + int status; + char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; + struct ifconf ifc; + struct ifreq *ifr; + struct sockaddr *sa; + char *startptr,*endptr; + int remlen; + + ifc.ifc_len = RTPUDPV4TRANS_IFREQBUFSIZE; + ifc.ifc_buf = buffer; + status = ioctl(rtpsock,SIOCGIFCONF,&ifc); + if (status < 0) + return false; + + startptr = (char *)ifc.ifc_req; + endptr = startptr + ifc.ifc_len; + remlen = ifc.ifc_len; + while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) + { + ifr = (struct ifreq *)startptr; + sa = &(ifr->ifr_addr); +#ifdef RTP_HAVE_SOCKADDR_LEN + if (sa->sa_len <= sizeof(struct sockaddr)) + { + if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; + + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); + } + else + { + int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); + + remlen -= l; + startptr += l; + } +#else // don't have sa_len in struct sockaddr + if (sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; + + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); + +#endif // RTP_HAVE_SOCKADDR_LEN + } + + if (localIPs.empty()) + return false; + return true; +} + +#endif // RTP_SUPPORT_IFADDRS + +#endif // RTP_SOCKETTYPE_WINSOCK + +void RTPUDPv4Transmitter::GetLocalIPList_DNS() +{ + struct hostent *he; + char name[1024]; + bool done; + int i,j; + + gethostname(name,1023); + name[1023] = 0; + he = gethostbyname(name); + if (he == 0) + return; + + i = 0; + done = false; + while (!done) + { + if (he->h_addr_list[i] == NULL) + done = true; + else + { + uint32_t ip = 0; + + for (j = 0 ; j < 4 ; j++) + ip |= ((uint32_t)((unsigned char)he->h_addr_list[i][j])<<((3-j)*8)); + localIPs.push_back(ip); + i++; + } + } +} + +void RTPUDPv4Transmitter::AddLoopbackAddress() +{ + uint32_t loopbackaddr = (((uint32_t)127)<<24)|((uint32_t)1); + std::list::const_iterator it; + bool found = false; + + for (it = localIPs.begin() ; !found && it != localIPs.end() ; it++) + { + if (*it == loopbackaddr) + found = true; + } + + if (!found) + localIPs.push_back(loopbackaddr); +} + +} // end namespace + diff --git a/qrtplib/rtpudpv4transmitter.h b/qrtplib/rtpudpv4transmitter.h new file mode 100644 index 000000000..32f6c103d --- /dev/null +++ b/qrtplib/rtpudpv4transmitter.h @@ -0,0 +1,367 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpudpv4transmitter.h + */ + +#ifndef RTPUDPV4TRANSMITTER_H + +#define RTPUDPV4TRANSMITTER_H + +#include "rtpconfig.h" +#include "rtptransmitter.h" +#include "rtpipv4destination.h" +#include "rtphashtable.h" +#include "rtpkeyhashtable.h" +#include "rtpsocketutil.h" +#include "rtpabortdescriptors.h" +#include + +#ifdef RTP_SUPPORT_THREAD + #include +#endif // RTP_SUPPORT_THREAD + +#define RTPUDPV4TRANS_HASHSIZE 8317 +#define RTPUDPV4TRANS_DEFAULTPORTBASE 5000 + +#define RTPUDPV4TRANS_RTPRECEIVEBUFFER 32768 +#define RTPUDPV4TRANS_RTCPRECEIVEBUFFER 32768 +#define RTPUDPV4TRANS_RTPTRANSMITBUFFER 32768 +#define RTPUDPV4TRANS_RTCPTRANSMITBUFFER 32768 + +namespace qrtplib +{ + +/** Parameters for the UDP over IPv4 transmitter. */ +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionParams : public RTPTransmissionParams +{ +public: + RTPUDPv4TransmissionParams(); + + /** Sets the IP address which is used to bind the sockets to \c ip. */ + void SetBindIP(uint32_t ip) { bindIP = ip; } + + /** Sets the multicast interface IP address. */ + void SetMulticastInterfaceIP(uint32_t ip) { mcastifaceIP = ip; } + + /** Sets the RTP portbase to \c pbase, which has to be an even number + * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; + * a port number of zero will cause a port to be chosen automatically. */ + void SetPortbase(uint16_t pbase) { portbase = pbase; } + + /** Sets the multicast TTL to be used to \c mcastTTL. */ + void SetMulticastTTL(uint8_t mcastTTL) { multicastTTL = mcastTTL; } + + /** Passes a list of IP addresses which will be used as the local IP addresses. */ + void SetLocalIPList(std::list &iplist) { localIPs = iplist; } + + /** Clears the list of local IP addresses. + * Clears the list of local IP addresses. An empty list will make the transmission + * component itself determine the local IP addresses. + */ + void ClearLocalIPList() { localIPs.clear(); } + + /** Returns the IP address which will be used to bind the sockets. */ + uint32_t GetBindIP() const { return bindIP; } + + /** Returns the multicast interface IP address. */ + uint32_t GetMulticastInterfaceIP() const { return mcastifaceIP; } + + /** Returns the RTP portbase which will be used (default is 5000). */ + uint16_t GetPortbase() const { return portbase; } + + /** Returns the multicast TTL which will be used (default is 1). */ + uint8_t GetMulticastTTL() const { return multicastTTL; } + + /** Returns the list of local IP addresses. */ + const std::list &GetLocalIPList() const { return localIPs; } + + /** Sets the RTP socket's send buffer size. */ + void SetRTPSendBuffer(int s) { rtpsendbuf = s; } + + /** Sets the RTP socket's receive buffer size. */ + void SetRTPReceiveBuffer(int s) { rtprecvbuf = s; } + + /** Sets the RTCP socket's send buffer size. */ + void SetRTCPSendBuffer(int s) { rtcpsendbuf = s; } + + /** Sets the RTCP socket's receive buffer size. */ + void SetRTCPReceiveBuffer(int s) { rtcprecvbuf = s; } + + /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ + void SetRTCPMultiplexing(bool f) { rtcpmux = f; } + + /** Can be used to allow the RTP port base to be any number, not just even numbers. */ + void SetAllowOddPortbase(bool f) { allowoddportbase = f; } + + /** Force the RTCP socket to use a specific port, not necessarily one more than + * the RTP port (set this to zero to disable). */ + void SetForcedRTCPPort(uint16_t rtcpport) { forcedrtcpport = rtcpport; } + + /** Use sockets that have already been created, no checks on port numbers + * will be done, and no buffer sizes will be set; you'll need to close + * the sockets yourself when done, it will **not** be done automatically. */ + void SetUseExistingSockets(SocketType rtpsocket, SocketType rtcpsocket) { rtpsock = rtpsocket; rtcpsock = rtcpsocket; useexistingsockets = true; } + + /** If non null, the specified abort descriptors will be used to cancel + * the function that's waiting for packets to arrive; set to null (the default + * to let the transmitter create its own instance. */ + void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) { m_pAbortDesc = desc; } + + /** Returns the RTP socket's send buffer size. */ + int GetRTPSendBuffer() const { return rtpsendbuf; } + + /** Returns the RTP socket's receive buffer size. */ + int GetRTPReceiveBuffer() const { return rtprecvbuf; } + + /** Returns the RTCP socket's send buffer size. */ + int GetRTCPSendBuffer() const { return rtcpsendbuf; } + + /** Returns the RTCP socket's receive buffer size. */ + int GetRTCPReceiveBuffer() const { return rtcprecvbuf; } + + /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ + bool GetRTCPMultiplexing() const { return rtcpmux; } + + /** If true, any RTP portbase will be allowed, not just even numbers. */ + bool GetAllowOddPortbase() const { return allowoddportbase; } + + /** If non-zero, the specified port will be used to receive RTCP traffic. */ + uint16_t GetForcedRTCPPort() const { return forcedrtcpport; } + + /** Returns true and fills in sockets if existing sockets were set + * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ + bool GetUseExistingSockets(SocketType &rtpsocket, SocketType &rtcpsocket) const { if (!useexistingsockets) return false; rtpsocket = rtpsock; rtcpsocket = rtcpsock; return true; } + + /** If non-null, this RTPAbortDescriptors instance will be used internally, + * which can be useful when creating your own poll thread for multiple + * sessions. */ + RTPAbortDescriptors *GetCreatedAbortDescriptors() const { return m_pAbortDesc; } +private: + uint16_t portbase; + uint32_t bindIP, mcastifaceIP; + std::list localIPs; + uint8_t multicastTTL; + int rtpsendbuf, rtprecvbuf; + int rtcpsendbuf, rtcprecvbuf; + bool rtcpmux; + bool allowoddportbase; + uint16_t forcedrtcpport; + + SocketType rtpsock, rtcpsock; + bool useexistingsockets; + + RTPAbortDescriptors *m_pAbortDesc; +}; + +inline RTPUDPv4TransmissionParams::RTPUDPv4TransmissionParams() : RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) +{ + portbase = RTPUDPV4TRANS_DEFAULTPORTBASE; + bindIP = 0; + multicastTTL = 1; + mcastifaceIP = 0; + rtpsendbuf = RTPUDPV4TRANS_RTPTRANSMITBUFFER; + rtprecvbuf = RTPUDPV4TRANS_RTPRECEIVEBUFFER; + rtcpsendbuf = RTPUDPV4TRANS_RTCPTRANSMITBUFFER; + rtcprecvbuf = RTPUDPV4TRANS_RTCPRECEIVEBUFFER; + rtcpmux = false; + allowoddportbase = false; + forcedrtcpport = 0; + useexistingsockets = false; + rtpsock = 0; + rtcpsock = 0; + m_pAbortDesc = 0; +} + +/** Additional information about the UDP over IPv4 transmitter. */ +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionInfo : public RTPTransmissionInfo +{ +public: + RTPUDPv4TransmissionInfo(std::list iplist,SocketType rtpsock,SocketType rtcpsock, + uint16_t rtpport, uint16_t rtcpport) : RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) + { localIPlist = iplist; rtpsocket = rtpsock; rtcpsocket = rtcpsock; m_rtpPort = rtpport; m_rtcpPort = rtcpport; } + + ~RTPUDPv4TransmissionInfo() { } + + /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ + std::list GetLocalIPList() const { return localIPlist; } + + /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ + SocketType GetRTPSocket() const { return rtpsocket; } + + /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ + SocketType GetRTCPSocket() const { return rtcpsocket; } + + /** Returns the port number that the RTP socket receives packets on. */ + uint16_t GetRTPPort() const { return m_rtpPort; } + + /** Returns the port number that the RTCP socket receives packets on. */ + uint16_t GetRTCPPort() const { return m_rtcpPort; } +private: + std::list localIPlist; + SocketType rtpsocket,rtcpsocket; + uint16_t m_rtpPort, m_rtcpPort; +}; + +class JRTPLIB_IMPORTEXPORT RTPUDPv4Trans_GetHashIndex_IPv4Dest +{ +public: + static int GetIndex(const RTPIPv4Destination &d) { return d.GetIP()%RTPUDPV4TRANS_HASHSIZE; } +}; + +class JRTPLIB_IMPORTEXPORT RTPUDPv4Trans_GetHashIndex_uint32_t +{ +public: + static int GetIndex(const uint32_t &k) { return k%RTPUDPV4TRANS_HASHSIZE; } +}; + +#define RTPUDPV4TRANS_HEADERSIZE (20+8) + +/** An UDP over IPv4 transmission component. + * This class inherits the RTPTransmitter interface and implements a transmission component + * which uses UDP over IPv4 to send and receive RTP and RTCP data. The component's parameters + * are described by the class RTPUDPv4TransmissionParams. The functions which have an RTPAddress + * argument require an argument of RTPIPv4Address. The GetTransmissionInfo member function + * returns an instance of type RTPUDPv4TransmissionInfo. + */ +class JRTPLIB_IMPORTEXPORT RTPUDPv4Transmitter : public RTPTransmitter +{ +public: + RTPUDPv4Transmitter(RTPMemoryManager *mgr); + ~RTPUDPv4Transmitter(); + + int Init(bool treadsafe); + int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() { return RTPUDPV4TRANS_HEADERSIZE; } + + int Poll(); + int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); + int AbortWait(); + + int SendRTPData(const void *data,size_t len); + int SendRTCPData(const void *data,size_t len); + + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); + + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); + + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); + + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); + +private: + int CreateLocalIPList(); + bool GetLocalIPList_Interfaces(); + void GetLocalIPList_DNS(); + void AddLoopbackAddress(); + void FlushPackets(); + int PollSocket(bool rtp); + int ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port); + int ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port); +#ifdef RTP_SUPPORT_IPV4MULTICAST + bool SetMulticastTTL(uint8_t ttl); +#endif // RTP_SUPPORT_IPV4MULTICAST + bool ShouldAcceptData(uint32_t srcip,uint16_t srcport); + void ClearAcceptIgnoreInfo(); + + int GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, + SocketType *pRtpSock, SocketType *pRtcpSock, + uint16_t *pRtpPort, uint16_t *pRtcpPort); + static int GetIPv4SocketPort(SocketType s, uint16_t *pPort); + + bool init; + bool created; + bool waitingfordata; + SocketType rtpsock,rtcpsock; + uint32_t mcastifaceIP; + std::list localIPs; + uint16_t m_rtpPort, m_rtcpPort; + uint8_t multicastTTL; + RTPTransmitter::ReceiveMode receivemode; + + uint8_t *localhostname; + size_t localhostnamelength; + + RTPHashTable destinations; +#ifdef RTP_SUPPORT_IPV4MULTICAST + RTPHashTable multicastgroups; +#endif // RTP_SUPPORT_IPV4MULTICAST + std::list rawpacketlist; + + bool supportsmulticasting; + size_t maxpacksize; + + class PortInfo + { + public: + PortInfo() { all = false; } + + bool all; + std::list portlist; + }; + + RTPKeyHashTable acceptignoreinfo; + + bool closesocketswhendone; + RTPAbortDescriptors m_abortDesc; + RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified + +#ifdef RTP_SUPPORT_THREAD + jthread::JMutex mainmutex,waitmutex; + int threadsafe; +#endif // RTP_SUPPORT_THREAD +}; + +} // end namespace + +#endif // RTPUDPV4TRANSMITTER_H + diff --git a/qrtplib/rtpudpv4transmitternobind.cpp b/qrtplib/rtpudpv4transmitternobind.cpp new file mode 100644 index 000000000..d9632ada9 --- /dev/null +++ b/qrtplib/rtpudpv4transmitternobind.cpp @@ -0,0 +1,1952 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "rtpudpv4transmitternobind.h" +#include "rtprawpacket.h" +#include "rtpipv4address.h" +#include "rtptimeutilities.h" +#include "rtpdefines.h" +#include "rtpstructs.h" +#include "rtpsocketutilinternal.h" +#include "rtpinternalutils.h" +#include "rtpselect.h" +#include +#include +#include + +#include + +using namespace std; + +#define RTPUDPV4TRANSNOBIND_MAXPACKSIZE 65535 +#define RTPUDPV4TRANSNOBIND_IFREQBUFSIZE 8192 + +#define RTPUDPV4TRANSNOBIND_IS_MCASTADDR(x) (((x)&0xF0000000) == 0xE0000000) + +#define RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(socket,type,mcastip,status) {\ + struct ip_mreq mreq;\ + \ + mreq.imr_multiaddr.s_addr = htonl(mcastip);\ + mreq.imr_interface.s_addr = htonl(mcastifaceIP);\ + status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ + } +#ifdef RTP_SUPPORT_THREAD + #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } + #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } + #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } + #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } +#else + #define MAINMUTEX_LOCK + #define MAINMUTEX_UNLOCK + #define WAITMUTEX_LOCK + #define WAITMUTEX_UNLOCK +#endif // RTP_SUPPORT_THREAD + +#define CLOSESOCKETS do { \ + if (closesocketswhendone) \ + {\ + if (rtpsock != rtcpsock) \ + RTPCLOSE(rtcpsock); \ + RTPCLOSE(rtpsock); \ + } \ +} while(0) + + +namespace qrtplib +{ + +RTPUDPv4TransmitterNoBind::RTPUDPv4TransmitterNoBind(RTPMemoryManager *mgr) : + RTPTransmitter(mgr), + init(false), + created(false), + waitingfordata(false), + rtpsock(-1), + rtcpsock(-1), + mcastifaceIP(0), + m_rtpPort(0), + m_rtcpPort(0), + multicastTTL(0), + receivemode(AcceptAll), + localhostname(0), + localhostnamelength(0), + destinations(mgr,RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT), +#ifdef RTP_SUPPORT_IPV4MULTICAST + multicastgroups(mgr,RTPMEM_TYPE_CLASS_MULTICASTHASHELEMENT), +#endif // RTP_SUPPORT_IPV4MULTICAST + supportsmulticasting(false), + maxpacksize(0), + acceptignoreinfo(mgr,RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT), + closesocketswhendone(false), + m_pAbortDesc(0) +{ +} + +RTPUDPv4TransmitterNoBind::~RTPUDPv4TransmitterNoBind() +{ + Destroy(); +} + +int RTPUDPv4TransmitterNoBind::Init(bool tsafe) +{ + if (init) + return ERR_RTP_UDPV4TRANS_ALREADYINIT; + +#ifdef RTP_SUPPORT_THREAD + threadsafe = tsafe; + if (threadsafe) + { + int status; + + status = mainmutex.Init(); + if (status < 0) + return ERR_RTP_UDPV4TRANS_CANTINITMUTEX; + status = waitmutex.Init(); + if (status < 0) + return ERR_RTP_UDPV4TRANS_CANTINITMUTEX; + } +#else + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; +#endif // RTP_SUPPORT_THREAD + + init = true; + return 0; +} + +int RTPUDPv4TransmitterNoBind::GetIPv4SocketPort(SocketType s, uint16_t *pPort) +{ + assert(pPort != 0); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(struct sockaddr_in)); + + RTPSOCKLENTYPE size = sizeof(struct sockaddr_in); + if (getsockname(s,(struct sockaddr*)&addr,&size) != 0) + return ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT; + + if (addr.sin_family != AF_INET) + return ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET; + + uint16_t port = ntohs(addr.sin_port); + if (port == 0) + return ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET; + + int type = 0; + RTPSOCKLENTYPE length = sizeof(type); + + if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*)&type, &length) != 0) + return ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE; + + if (type != SOCK_DGRAM) + return ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE; + + *pPort = port; + return 0; +} + +int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, + SocketType *pRtpSock, SocketType *pRtcpSock, + uint16_t *pRtpPort, uint16_t *pRtcpPort) +{ + const int maxAttempts = 1024; + int attempts = 0; + vector toClose; + + while (attempts++ < maxAttempts) + { + SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock == RTPSOCKERR) + { + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + + // First we get an automatically chosen port + + struct sockaddr_in addr; + memset(&addr,0,sizeof(struct sockaddr_in)); + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(sock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(sock); + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; + } + + uint16_t basePort = 0; + int status = GetIPv4SocketPort(sock, &basePort); + if (status < 0) + { + RTPCLOSE(sock); + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + return status; + } + + if (rtcpMux) // only need one socket + { + if (basePort%2 == 0 || allowOdd) + { + *pRtpSock = sock; + *pRtcpSock = sock; + *pRtpPort = basePort; + *pRtcpPort = basePort; + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + + return 0; + } + else + toClose.push_back(sock); + } + else + { + SocketType sock2 = socket(PF_INET, SOCK_DGRAM, 0); + if (sock2 == RTPSOCKERR) + { + RTPCLOSE(sock); + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + + // Try the next port or the previous port + uint16_t secondPort = basePort; + bool possiblyValid = false; + + if (basePort%2 == 0) + { + secondPort++; + possiblyValid = true; + } + else if (basePort > 1) // avoid landing on port 0 + { + secondPort--; + possiblyValid = true; + } + + if (possiblyValid) + { + memset(&addr,0,sizeof(struct sockaddr_in)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(secondPort); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(sock2,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) == 0) + { + // In this case, we have two consecutive port numbers, the lower of + // which is even + + if (basePort < secondPort) + { + *pRtpSock = sock; + *pRtcpSock = sock2; + *pRtpPort = basePort; + *pRtcpPort = secondPort; + } + else + { + *pRtpSock = sock2; + *pRtcpSock = sock; + *pRtpPort = secondPort; + *pRtcpPort = basePort; + } + + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + + return 0; + } + } + + toClose.push_back(sock); + toClose.push_back(sock2); + } + } + + for (size_t i = 0 ; i < toClose.size() ; i++) + RTPCLOSE(toClose[i]); + + return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; +} + +int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) +{ + const RTPUDPv4TransmissionNoBindParams *params,defaultparams; +// struct sockaddr_in addr; + RTPSOCKLENTYPE size; + int status; + + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ALREADYCREATED; + } + + // Obtain transmission parameters + + if (transparams == 0) + params = &defaultparams; + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; + } + params = (const RTPUDPv4TransmissionNoBindParams *)transparams; + } + + if (params->GetUseExistingSockets(rtpsock, rtcpsock)) + { + closesocketswhendone = false; + + // Determine the port numbers. They are set to 0 if the sockets are not bound. + GetIPv4SocketPort(rtpsock, &m_rtpPort); + GetIPv4SocketPort(rtcpsock, &m_rtcpPort); + } + else + { + closesocketswhendone = true; + + if (params->GetPortbase() == 0) + { + int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), + &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); + if (status < 0) + { + MAINMUTEX_UNLOCK + return status; + } + } + else + { + // Check if portbase is even (if necessary) + if (!params->GetAllowOddPortbase() && params->GetPortbase()%2 != 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; + } + + // create sockets + + rtpsock = socket(PF_INET,SOCK_DGRAM,0); + if (rtpsock == RTPSOCKERR) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + + // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket + if (params->GetRTCPMultiplexing()) + rtcpsock = rtpsock; + else + { + rtcpsock = socket(PF_INET,SOCK_DGRAM,0); + if (rtcpsock == RTPSOCKERR) + { + RTPCLOSE(rtpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + } + + } + + // set socket buffer sizes + + size = params->GetRTPReceiveBuffer(); + if (setsockopt(rtpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; + } + size = params->GetRTPSendBuffer(); + if (setsockopt(rtpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; + } + + if (rtpsock != rtcpsock) // no need to set RTCP flags when multiplexing + { + size = params->GetRTCPReceiveBuffer(); + if (setsockopt(rtcpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; + } + size = params->GetRTCPSendBuffer(); + if (setsockopt(rtcpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; + } + } + } + + // Try to obtain local IP addresses + + localIPs = params->GetLocalIPList(); + if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them + { + int status; + + if ((status = CreateLocalIPList()) < 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return status; + } + } + +#ifdef RTP_SUPPORT_IPV4MULTICAST + if (SetMulticastTTL(params->GetMulticastTTL())) + supportsmulticasting = true; + else + supportsmulticasting = false; +#else // no multicast support enabled + supportsmulticasting = false; +#endif // RTP_SUPPORT_IPV4MULTICAST + + if (maximumpacketsize > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + if (!params->GetCreatedAbortDescriptors()) + { + if ((status = m_abortDesc.Init()) < 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return status; + } + m_pAbortDesc = &m_abortDesc; + } + else + { + m_pAbortDesc = params->GetCreatedAbortDescriptors(); + if (!m_pAbortDesc->IsInitialized()) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; + } + } + + maxpacksize = maximumpacketsize; + multicastTTL = params->GetMulticastTTL(); + mcastifaceIP = params->GetMulticastInterfaceIP(); + receivemode = RTPTransmitter::AcceptAll; + + localhostname = 0; + localhostnamelength = 0; + + waitingfordata = false; + created = true; + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transparams) +{ + if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; + } + + const RTPUDPv4TransmissionNoBindParams *params = (const RTPUDPv4TransmissionNoBindParams *)transparams; + + uint32_t bindIP = params->GetBindIP(); + m_rtpPort = params->GetPortbase(); + struct sockaddr_in addr; + + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(params->GetPortbase()); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(rtpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; + } + + if (rtpsock != rtcpsock) // no need to bind same socket twice when multiplexing + { + uint16_t rtpport = params->GetPortbase(); + uint16_t rtcpport = params->GetForcedRTCPPort(); + + if (rtcpport == 0) + { + rtcpport = rtpport; + if (rtcpport < 0xFFFF) + rtcpport++; + } + + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(rtcpport); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(rtcpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; + } + + m_rtcpPort = rtcpport; + } + else + m_rtcpPort = m_rtpPort; + + return 0; +} + +void RTPUDPv4TransmitterNoBind::Destroy() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK; + return; + } + + if (localhostname) + { + RTPDeleteByteArray(localhostname,GetMemoryManager()); + localhostname = 0; + localhostnamelength = 0; + } + + CLOSESOCKETS; + destinations.Clear(); +#ifdef RTP_SUPPORT_IPV4MULTICAST + multicastgroups.Clear(); +#endif // RTP_SUPPORT_IPV4MULTICAST + FlushPackets(); + ClearAcceptIgnoreInfo(); + localIPs.clear(); + created = false; + + if (waitingfordata) + { + m_pAbortDesc->SendAbortSignal(); + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK + } + else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + + MAINMUTEX_UNLOCK +} + +RTPTransmissionInfo *RTPUDPv4TransmitterNoBind::GetTransmissionInfo() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPUDPv4TransmissionNoBindInfo(localIPs,rtpsock,rtcpsock,m_rtpPort,m_rtcpPort); + MAINMUTEX_UNLOCK + return tinf; +} + +void RTPUDPv4TransmitterNoBind::DeleteTransmissionInfo(RTPTransmissionInfo *i) +{ + if (!init) + return; + + RTPDelete(i, GetMemoryManager()); +} + +int RTPUDPv4TransmitterNoBind::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (localhostname == 0) + { + if (localIPs.empty()) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOLOCALIPS; + } + + std::list::const_iterator it; + std::list hostnames; + + for (it = localIPs.begin() ; it != localIPs.end() ; it++) + { + bool founddouble = false; + bool foundentry = true; + + while (!founddouble && foundentry) + { + struct hostent *he; + uint8_t addr[4]; + uint32_t ip = (*it); + + addr[0] = (uint8_t)((ip>>24)&0xFF); + addr[1] = (uint8_t)((ip>>16)&0xFF); + addr[2] = (uint8_t)((ip>>8)&0xFF); + addr[3] = (uint8_t)(ip&0xFF); + he = gethostbyaddr((char *)addr,4,AF_INET); + if (he != 0) + { + std::string hname = std::string(he->h_name); + std::list::const_iterator it; + + for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + hostnames.push_back(hname); + + int i = 0; + while (!founddouble && he->h_aliases[i] != 0) + { + std::string hname = std::string(he->h_aliases[i]); + + for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + { + hostnames.push_back(hname); + i++; + } + } + } + else + foundentry = false; + } + } + + bool found = false; + + if (!hostnames.empty()) // try to select the most appropriate hostname + { + std::list::const_iterator it; + + hostnames.sort(); + for (it = hostnames.begin() ; !found && it != hostnames.end() ; it++) + { + if ((*it).find('.') != std::string::npos) + { + found = true; + localhostnamelength = (*it).length(); + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname,(*it).c_str(),localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } + } + + if (!found) // use an IP address + { + uint32_t ip; + int len; + char str[16]; + + it = localIPs.begin(); + ip = (*it); + + RTP_SNPRINTF(str,16,"%d.%d.%d.%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF)); + len = strlen(str); + + localhostnamelength = len; + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength + 1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname,str,localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } + + if ((*bufferlength) < localhostnamelength) + { + *bufferlength = localhostnamelength; // tell the application the required size of the buffer + MAINMUTEX_UNLOCK + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; + } + + memcpy(buffer,localhostname,localhostnamelength); + *bufferlength = localhostnamelength; + + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPUDPv4TransmitterNoBind::ComesFromThisTransmitter(const RTPAddress *addr) +{ + if (!init) + return false; + + if (addr == 0) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (created && addr->GetAddressType() == RTPAddress::IPv4Address) + { + const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; + bool found = false; + std::list::const_iterator it; + + it = localIPs.begin(); + while (!found && it != localIPs.end()) + { + if (addr2->GetIP() == *it) + found = true; + else + ++it; + } + + if (!found) + v = false; + else + { + if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port + v = true; + else + v = false; + } + } + else + v = false; + + MAINMUTEX_UNLOCK + return v; +} + +int RTPUDPv4TransmitterNoBind::Poll() +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + int status; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + status = PollSocket(true); // poll RTP socket + if (rtpsock != rtcpsock) // no need to poll twice when multiplexing + { + if (status >= 0) + status = PollSocket(false); // poll RTCP socket + } + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4TransmitterNoBind::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (waitingfordata) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ALREADYWAITING; + } + + SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); + + SocketType socks[3] = { rtpsock, rtcpsock, abortSocket }; + int8_t readflags[3] = { 0, 0, 0 }; + const int idxRTP = 0; + const int idxRTCP = 1; + const int idxAbort = 2; + + waitingfordata = true; + + WAITMUTEX_LOCK + MAINMUTEX_UNLOCK + + int status = RTPSelect(socks, readflags, 3, delay); + if (status < 0) + { + MAINMUTEX_LOCK + waitingfordata = false; + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return status; + } + + MAINMUTEX_LOCK + waitingfordata = false; + if (!created) // destroy called + { + MAINMUTEX_UNLOCK; + WAITMUTEX_UNLOCK + return 0; + } + + // if aborted, read from abort buffer + if (readflags[idxAbort]) + m_pAbortDesc->ReadSignallingByte(); + + if (dataavailable != 0) + { + if (readflags[idxRTP] || readflags[idxRTCP]) + *dataavailable = true; + else + *dataavailable = false; + } + + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4TransmitterNoBind::AbortWait() +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (!waitingfordata) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTWAITING; + } + + m_pAbortDesc->SendAbortSignal(); + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4TransmitterNoBind::SendRTPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTPSockAddr(),sizeof(struct sockaddr_in)); + destinations.GotoNextElement(); + } + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4TransmitterNoBind::SendRTCPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtcpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTCPSockAddr(),sizeof(struct sockaddr_in)); + destinations.GotoNextElement(); + } + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4TransmitterNoBind::AddDestination(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + + int status = destinations.AddElement(dest); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4TransmitterNoBind::DeleteDestination(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + + int status = destinations.DeleteElement(dest); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv4TransmitterNoBind::ClearDestinations() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created) + destinations.Clear(); + MAINMUTEX_UNLOCK +} + +bool RTPUDPv4TransmitterNoBind::SupportsMulticasting() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + v = supportsmulticasting; + + MAINMUTEX_UNLOCK + return v; +} + +#ifdef RTP_SUPPORT_IPV4MULTICAST + +int RTPUDPv4TransmitterNoBind::JoinMulticastGroup(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + uint32_t mcastIP = address.GetIP(); + + if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.AddElement(mcastIP); + if (status >= 0) + { + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock,IP_ADD_MEMBERSHIP,mcastIP,status); + if (status != 0) + { + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + + if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing + { + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock,IP_ADD_MEMBERSHIP,mcastIP,status); + if (status != 0) + { + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + } + } + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4TransmitterNoBind::LeaveMulticastGroup(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + uint32_t mcastIP = address.GetIP(); + + if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.DeleteElement(mcastIP); + if (status >= 0) + { + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + + status = 0; + } + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created) + { + multicastgroups.GotoFirstElement(); + while (multicastgroups.HasCurrentElement()) + { + uint32_t mcastIP; + int status = 0; + + mcastIP = multicastgroups.GetCurrentElement(); + + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); + JRTPLIB_UNUSED(status); + + multicastgroups.GotoNextElement(); + } + multicastgroups.Clear(); + } + MAINMUTEX_UNLOCK +} + +#else // no multicast support + +int RTPUDPv4TransmitterNoBind::JoinMulticastGroup(const RTPAddress &addr) +{ + return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; +} + +int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) +{ + return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; +} + +void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() +{ +} + +#endif // RTP_SUPPORT_IPV4MULTICAST + +int RTPUDPv4TransmitterNoBind::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (m != receivemode) + { + receivemode = m; + acceptignoreinfo.Clear(); + } + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv4TransmitterNoBind::AddToIgnoreList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4TransmitterNoBind::DeleteFromIgnoreList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv4TransmitterNoBind::ClearIgnoreList() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created && receivemode == RTPTransmitter::IgnoreSome) + ClearAcceptIgnoreInfo(); + MAINMUTEX_UNLOCK +} + +int RTPUDPv4TransmitterNoBind::AddToAcceptList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv4TransmitterNoBind::DeleteFromAcceptList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &)addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv4TransmitterNoBind::ClearAcceptList() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created && receivemode == RTPTransmitter::AcceptSome) + ClearAcceptIgnoreInfo(); + MAINMUTEX_UNLOCK +} + +int RTPUDPv4TransmitterNoBind::SetMaximumPacketSize(size_t s) +{ + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (s > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + maxpacksize = s; + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPUDPv4TransmitterNoBind::NewDataAvailable() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + { + if (rawpacketlist.empty()) + v = false; + else + v = true; + } + + MAINMUTEX_UNLOCK + return v; +} + +RTPRawPacket *RTPUDPv4TransmitterNoBind::GetNextPacket() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + + RTPRawPacket *p; + + if (!created) + { + MAINMUTEX_UNLOCK + return 0; + } + if (rawpacketlist.empty()) + { + MAINMUTEX_UNLOCK + return 0; + } + + p = *(rawpacketlist.begin()); + rawpacketlist.pop_front(); + + MAINMUTEX_UNLOCK + return p; +} + +// Here the private functions start... + +#ifdef RTP_SUPPORT_IPV4MULTICAST +bool RTPUDPv4TransmitterNoBind::SetMulticastTTL(uint8_t ttl) +{ + int ttl2,status; + + ttl2 = (int)ttl; + status = setsockopt(rtpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); + if (status != 0) + return false; + + if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing + { + status = setsockopt(rtcpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); + if (status != 0) + return false; + } + return true; +} +#endif // RTP_SUPPORT_IPV4MULTICAST + +void RTPUDPv4TransmitterNoBind::FlushPackets() +{ + std::list::const_iterator it; + + for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) + RTPDelete(*it,GetMemoryManager()); + rawpacketlist.clear(); +} + +int RTPUDPv4TransmitterNoBind::PollSocket(bool rtp) +{ + RTPSOCKLENTYPE fromlen; + int recvlen; + char packetbuffer[RTPUDPV4TRANSNOBIND_MAXPACKSIZE]; +#ifdef RTP_SOCKETTYPE_WINSOCK + SOCKET sock; + unsigned long len; +#else + size_t len; + int sock; +#endif // RTP_SOCKETTYPE_WINSOCK + struct sockaddr_in srcaddr; + bool dataavailable; + + if (rtp) + sock = rtpsock; + else + sock = rtcpsock; + + do + { + len = 0; + RTPIOCTL(sock,FIONREAD,&len); + + if (len <= 0) // make sure a packet of length zero is not queued + { + // An alternative workaround would be to just use non-blocking sockets. + // However, since the user does have access to the sockets and I do not + // know how this would affect anyone else's code, I chose to do it using + // an extra select call in case ioctl says the length is zero. + + int8_t isset = 0; + int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); + if (status < 0) + return status; + + if (isset) + dataavailable = true; + else + dataavailable = false; + } + else + dataavailable = true; + + if (dataavailable) + { + RTPTime curtime = RTPTime::CurrentTime(); + fromlen = sizeof(struct sockaddr_in); + recvlen = recvfrom(sock,packetbuffer,RTPUDPV4TRANSNOBIND_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); + if (recvlen > 0) + { + bool acceptdata; + + // got data, process it + if (receivemode == RTPTransmitter::AcceptAll) + acceptdata = true; + else + acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); + + if (acceptdata) + { + RTPRawPacket *pack; + RTPIPv4Address *addr; + uint8_t *datacopy; + + addr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); + if (addr == 0) + return ERR_RTP_OUTOFMEM; + datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[recvlen]; + if (datacopy == 0) + { + RTPDelete(addr,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + memcpy(datacopy,packetbuffer,recvlen); + + bool isrtp = rtp; + if (rtpsock == rtcpsock) // check payload type when multiplexing + { + isrtp = true; + + if ((size_t)recvlen > sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *)datacopy; + uint8_t packettype = rtcpheader->packettype; + + if (packettype >= 200 && packettype <= 204) + isrtp = false; + } + } + + pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,recvlen,addr,curtime,isrtp,GetMemoryManager()); + if (pack == 0) + { + RTPDelete(addr,GetMemoryManager()); + RTPDeleteByteArray(datacopy,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + rawpacketlist.push_back(pack); + } + } + } + } while (dataavailable); + + return 0; +} + +int RTPUDPv4TransmitterNoBind::ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port) +{ + acceptignoreinfo.GotoElement(ip); + if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists + { + PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); + + if (port == 0) // select all ports + { + portinf->all = true; + portinf->portlist.clear(); + } + else if (!portinf->all) + { + std::list::const_iterator it,begin,end; + + begin = portinf->portlist.begin(); + end = portinf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == port) // already in list + return 0; + } + portinf->portlist.push_front(port); + } + } + else // got to create an entry for this IP address + { + PortInfo *portinf; + int status; + + portinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO) PortInfo(); + if (port == 0) // select all ports + portinf->all = true; + else + portinf->portlist.push_front(port); + + status = acceptignoreinfo.AddElement(ip,portinf); + if (status < 0) + { + RTPDelete(portinf,GetMemoryManager()); + return status; + } + } + + return 0; +} + +void RTPUDPv4TransmitterNoBind::ClearAcceptIgnoreInfo() +{ + acceptignoreinfo.GotoFirstElement(); + while (acceptignoreinfo.HasCurrentElement()) + { + PortInfo *inf; + + inf = acceptignoreinfo.GetCurrentElement(); + RTPDelete(inf,GetMemoryManager()); + acceptignoreinfo.GotoNextElement(); + } + acceptignoreinfo.Clear(); +} + +int RTPUDPv4TransmitterNoBind::ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port) +{ + acceptignoreinfo.GotoElement(ip); + if (!acceptignoreinfo.HasCurrentElement()) + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + + PortInfo *inf; + + inf = acceptignoreinfo.GetCurrentElement(); + if (port == 0) // delete all entries + { + inf->all = false; + inf->portlist.clear(); + } + else // a specific port was selected + { + if (inf->all) // currently, all ports are selected. Add the one to remove to the list + { + // we have to check if the list doesn't contain the port already + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == port) // already in list: this means we already deleted the entry + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + } + inf->portlist.push_front(port); + } + else // check if we can find the port in the list + { + std::list::iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; ++it) + { + if (*it == port) // found it! + { + inf->portlist.erase(it); + return 0; + } + } + // didn't find it + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + } + } + return 0; +} + +bool RTPUDPv4TransmitterNoBind::ShouldAcceptData(uint32_t srcip,uint16_t srcport) +{ + if (receivemode == RTPTransmitter::AcceptSome) + { + PortInfo *inf; + + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return false; + + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // only accept the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return true; + } + return false; + } + else // accept all, except the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return false; + } + return true; + } + } + else // IgnoreSome + { + PortInfo *inf; + + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return true; + + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // ignore the ports in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return false; + } + return true; + } + else // ignore all, except the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return true; + } + return false; + } + } + return true; +} + +int RTPUDPv4TransmitterNoBind::CreateLocalIPList() +{ + // first try to obtain the list from the network interface info + + if (!GetLocalIPList_Interfaces()) + { + // If this fails, we'll have to depend on DNS info + GetLocalIPList_DNS(); + } + AddLoopbackAddress(); + return 0; +} + +#ifdef RTP_SOCKETTYPE_WINSOCK + +bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() +{ + unsigned char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; + DWORD outputsize; + DWORD numaddresses,i; + SOCKET_ADDRESS_LIST *addrlist; + + if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANSNOBIND_IFREQBUFSIZE,&outputsize,NULL,NULL)) + return false; + + addrlist = (SOCKET_ADDRESS_LIST *)buffer; + numaddresses = addrlist->iAddressCount; + for (i = 0 ; i < numaddresses ; i++) + { + SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); + if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address + { + struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; + + localIPs.push_back(ntohl(addr->sin_addr.s_addr)); + } + } + + if (localIPs.empty()) + return false; + + return true; +} + +#else // use either getifaddrs or ioctl + +#ifdef RTP_SUPPORT_IFADDRS + +bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() +{ + struct ifaddrs *addrs,*tmp; + + getifaddrs(&addrs); + tmp = addrs; + + while (tmp != 0) + { + if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) + { + struct sockaddr_in *inaddr = (struct sockaddr_in *)tmp->ifa_addr; + localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); + } + tmp = tmp->ifa_next; + } + + freeifaddrs(addrs); + + if (localIPs.empty()) + return false; + return true; +} + +#else // user ioctl + +bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() +{ + int status; + char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; + struct ifconf ifc; + struct ifreq *ifr; + struct sockaddr *sa; + char *startptr,*endptr; + int remlen; + + ifc.ifc_len = RTPUDPV4TRANSNOBIND_IFREQBUFSIZE; + ifc.ifc_buf = buffer; + status = ioctl(rtpsock,SIOCGIFCONF,&ifc); + if (status < 0) + return false; + + startptr = (char *)ifc.ifc_req; + endptr = startptr + ifc.ifc_len; + remlen = ifc.ifc_len; + while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) + { + ifr = (struct ifreq *)startptr; + sa = &(ifr->ifr_addr); +#ifdef RTP_HAVE_SOCKADDR_LEN + if (sa->sa_len <= sizeof(struct sockaddr)) + { + if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; + + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); + } + else + { + int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); + + remlen -= l; + startptr += l; + } +#else // don't have sa_len in struct sockaddr + if (sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; + + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); + +#endif // RTP_HAVE_SOCKADDR_LEN + } + + if (localIPs.empty()) + return false; + return true; +} + +#endif // RTP_SUPPORT_IFADDRS + +#endif // RTP_SOCKETTYPE_WINSOCK + +void RTPUDPv4TransmitterNoBind::GetLocalIPList_DNS() +{ + struct hostent *he; + char name[1024]; + bool done; + int i,j; + + gethostname(name,1023); + name[1023] = 0; + he = gethostbyname(name); + if (he == 0) + return; + + i = 0; + done = false; + while (!done) + { + if (he->h_addr_list[i] == NULL) + done = true; + else + { + uint32_t ip = 0; + + for (j = 0 ; j < 4 ; j++) + ip |= ((uint32_t)((unsigned char)he->h_addr_list[i][j])<<((3-j)*8)); + localIPs.push_back(ip); + i++; + } + } +} + +void RTPUDPv4TransmitterNoBind::AddLoopbackAddress() +{ + uint32_t loopbackaddr = (((uint32_t)127)<<24)|((uint32_t)1); + std::list::const_iterator it; + bool found = false; + + for (it = localIPs.begin() ; !found && it != localIPs.end() ; it++) + { + if (*it == loopbackaddr) + found = true; + } + + if (!found) + localIPs.push_back(loopbackaddr); +} + +} // end namespace + diff --git a/qrtplib/rtpudpv4transmitternobind.h b/qrtplib/rtpudpv4transmitternobind.h new file mode 100644 index 000000000..23aaa2918 --- /dev/null +++ b/qrtplib/rtpudpv4transmitternobind.h @@ -0,0 +1,371 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpudpv4transmitternobind.h + */ + +#ifndef RTPUDPV4TRANSMITTERNOBIND_H + +#define RTPUDPV4TRANSMITTERNOBIND_H + +#include "rtpconfig.h" +#include "rtptransmitter.h" +#include "rtpipv4destination.h" +#include "rtphashtable.h" +#include "rtpkeyhashtable.h" +#include "rtpsocketutil.h" +#include "rtpabortdescriptors.h" +#include + +#ifdef RTP_SUPPORT_THREAD + #include +#endif // RTP_SUPPORT_THREAD + +#define RTPUDPV4TRANSNOBIND_HASHSIZE 8317 +#define RTPUDPV4TRANSNOBIND_DEFAULTPORTBASE 5000 + +#define RTPUDPV4TRANSNOBIND_RTPRECEIVEBUFFER 32768 +#define RTPUDPV4TRANSNOBIND_RTCPRECEIVEBUFFER 32768 +#define RTPUDPV4TRANSNOBIND_RTPTRANSMITBUFFER 32768 +#define RTPUDPV4TRANSNOBIND_RTCPTRANSMITBUFFER 32768 + +namespace qrtplib +{ + +/** Parameters for the UDP over IPv4 transmitter that does not automatically bind sockets */ +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionNoBindParams : public RTPTransmissionParams +{ +public: + RTPUDPv4TransmissionNoBindParams(); + + /** Sets the IP address which is used to bind the sockets to \c ip. */ + void SetBindIP(uint32_t ip) { bindIP = ip; } + + /** Sets the multicast interface IP address. */ + void SetMulticastInterfaceIP(uint32_t ip) { mcastifaceIP = ip; } + + /** Sets the RTP portbase to \c pbase, which has to be an even number + * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; + * a port number of zero will cause a port to be chosen automatically. */ + void SetPortbase(uint16_t pbase) { portbase = pbase; } + + /** Sets the multicast TTL to be used to \c mcastTTL. */ + void SetMulticastTTL(uint8_t mcastTTL) { multicastTTL = mcastTTL; } + + /** Passes a list of IP addresses which will be used as the local IP addresses. */ + void SetLocalIPList(std::list &iplist) { localIPs = iplist; } + + /** Clears the list of local IP addresses. + * Clears the list of local IP addresses. An empty list will make the transmission + * component itself determine the local IP addresses. + */ + void ClearLocalIPList() { localIPs.clear(); } + + /** Returns the IP address which will be used to bind the sockets. */ + uint32_t GetBindIP() const { return bindIP; } + + /** Returns the multicast interface IP address. */ + uint32_t GetMulticastInterfaceIP() const { return mcastifaceIP; } + + /** Returns the RTP portbase which will be used (default is 5000). */ + uint16_t GetPortbase() const { return portbase; } + + /** Returns the multicast TTL which will be used (default is 1). */ + uint8_t GetMulticastTTL() const { return multicastTTL; } + + /** Returns the list of local IP addresses. */ + const std::list &GetLocalIPList() const { return localIPs; } + + /** Sets the RTP socket's send buffer size. */ + void SetRTPSendBuffer(int s) { rtpsendbuf = s; } + + /** Sets the RTP socket's receive buffer size. */ + void SetRTPReceiveBuffer(int s) { rtprecvbuf = s; } + + /** Sets the RTCP socket's send buffer size. */ + void SetRTCPSendBuffer(int s) { rtcpsendbuf = s; } + + /** Sets the RTCP socket's receive buffer size. */ + void SetRTCPReceiveBuffer(int s) { rtcprecvbuf = s; } + + /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ + void SetRTCPMultiplexing(bool f) { rtcpmux = f; } + + /** Can be used to allow the RTP port base to be any number, not just even numbers. */ + void SetAllowOddPortbase(bool f) { allowoddportbase = f; } + + /** Force the RTCP socket to use a specific port, not necessarily one more than + * the RTP port (set this to zero to disable). */ + void SetForcedRTCPPort(uint16_t rtcpport) { forcedrtcpport = rtcpport; } + + /** Use sockets that have already been created, no checks on port numbers + * will be done, and no buffer sizes will be set; you'll need to close + * the sockets yourself when done, it will **not** be done automatically. */ + void SetUseExistingSockets(SocketType rtpsocket, SocketType rtcpsocket) { rtpsock = rtpsocket; rtcpsock = rtcpsocket; useexistingsockets = true; } + + /** If non null, the specified abort descriptors will be used to cancel + * the function that's waiting for packets to arrive; set to null (the default + * to let the transmitter create its own instance. */ + void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) { m_pAbortDesc = desc; } + + /** Returns the RTP socket's send buffer size. */ + int GetRTPSendBuffer() const { return rtpsendbuf; } + + /** Returns the RTP socket's receive buffer size. */ + int GetRTPReceiveBuffer() const { return rtprecvbuf; } + + /** Returns the RTCP socket's send buffer size. */ + int GetRTCPSendBuffer() const { return rtcpsendbuf; } + + /** Returns the RTCP socket's receive buffer size. */ + int GetRTCPReceiveBuffer() const { return rtcprecvbuf; } + + /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ + bool GetRTCPMultiplexing() const { return rtcpmux; } + + /** If true, any RTP portbase will be allowed, not just even numbers. */ + bool GetAllowOddPortbase() const { return allowoddportbase; } + + /** If non-zero, the specified port will be used to receive RTCP traffic. */ + uint16_t GetForcedRTCPPort() const { return forcedrtcpport; } + + /** Returns true and fills in sockets if existing sockets were set + * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ + bool GetUseExistingSockets(SocketType &rtpsocket, SocketType &rtcpsocket) const { if (!useexistingsockets) return false; rtpsocket = rtpsock; rtcpsocket = rtcpsock; return true; } + + /** If non-null, this RTPAbortDescriptors instance will be used internally, + * which can be useful when creating your own poll thread for multiple + * sessions. */ + RTPAbortDescriptors *GetCreatedAbortDescriptors() const { return m_pAbortDesc; } +private: + uint16_t portbase; + uint32_t bindIP, mcastifaceIP; + std::list localIPs; + uint8_t multicastTTL; + int rtpsendbuf, rtprecvbuf; + int rtcpsendbuf, rtcprecvbuf; + bool rtcpmux; + bool allowoddportbase; + uint16_t forcedrtcpport; + + SocketType rtpsock, rtcpsock; + bool useexistingsockets; + + RTPAbortDescriptors *m_pAbortDesc; +}; + +inline RTPUDPv4TransmissionNoBindParams::RTPUDPv4TransmissionNoBindParams() : RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) +{ + portbase = RTPUDPV4TRANSNOBIND_DEFAULTPORTBASE; + bindIP = 0; + multicastTTL = 1; + mcastifaceIP = 0; + rtpsendbuf = RTPUDPV4TRANSNOBIND_RTPTRANSMITBUFFER; + rtprecvbuf = RTPUDPV4TRANSNOBIND_RTPRECEIVEBUFFER; + rtcpsendbuf = RTPUDPV4TRANSNOBIND_RTCPTRANSMITBUFFER; + rtcprecvbuf = RTPUDPV4TRANSNOBIND_RTCPRECEIVEBUFFER; + rtcpmux = false; + allowoddportbase = false; + forcedrtcpport = 0; + useexistingsockets = false; + rtpsock = 0; + rtcpsock = 0; + m_pAbortDesc = 0; +} + +/** Additional information about the UDP over IPv4 transmitter that does not automatically bind sockets. */ +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionNoBindInfo : public RTPTransmissionInfo +{ +public: + RTPUDPv4TransmissionNoBindInfo(std::list iplist,SocketType rtpsock,SocketType rtcpsock, + uint16_t rtpport, uint16_t rtcpport) : RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) + { localIPlist = iplist; rtpsocket = rtpsock; rtcpsocket = rtcpsock; m_rtpPort = rtpport; m_rtcpPort = rtcpport; } + + ~RTPUDPv4TransmissionNoBindInfo() { } + + /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ + std::list GetLocalIPList() const { return localIPlist; } + + /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ + SocketType GetRTPSocket() const { return rtpsocket; } + + /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ + SocketType GetRTCPSocket() const { return rtcpsocket; } + + /** Returns the port number that the RTP socket receives packets on. */ + uint16_t GetRTPPort() const { return m_rtpPort; } + + /** Returns the port number that the RTCP socket receives packets on. */ + uint16_t GetRTCPPort() const { return m_rtcpPort; } +private: + std::list localIPlist; + SocketType rtpsocket,rtcpsocket; + uint16_t m_rtpPort, m_rtcpPort; +}; + +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransNoBind_GetHashIndex_IPv4Dest +{ +public: + static int GetIndex(const RTPIPv4Destination &d) { return d.GetIP()%RTPUDPV4TRANSNOBIND_HASHSIZE; } +}; + +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransNoBind_GetHashIndex_uint32_t +{ +public: + static int GetIndex(const uint32_t &k) { return k%RTPUDPV4TRANSNOBIND_HASHSIZE; } +}; + +#define RTPUDPV4TRANSNOBIND_HEADERSIZE (20+8) + +/** An UDP over IPv4 transmission component. + * This class inherits the RTPTransmitter interface and implements a transmission component + * which uses UDP over IPv4 to send and receive RTP and RTCP data. The component's parameters + * are described by the class RTPUDPv4TransmissionNoBindParams. The functions which have an RTPAddress + * argument require an argument of RTPIPv4Address. The GetTransmissionInfo member function + * returns an instance of type RTPUDPv4TransmissionNoBindInfo. + * This flavor of a RTPUDPv4Transmitter class does not automatically bind sockets. Use the + * BindSockets method to do so. + */ +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmitterNoBind : public RTPTransmitter +{ +public: + RTPUDPv4TransmitterNoBind(RTPMemoryManager *mgr); + ~RTPUDPv4TransmitterNoBind(); + + int Init(bool treadsafe); + int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); + /** Bind the RTP and RTCP sockets to ports defined in the transmission parameters */ + int BindSockets(const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() { return RTPUDPV4TRANSNOBIND_HEADERSIZE; } + + int Poll(); + int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); + int AbortWait(); + + int SendRTPData(const void *data,size_t len); + int SendRTCPData(const void *data,size_t len); + + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); + + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); + + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); + + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); + +private: + int CreateLocalIPList(); + bool GetLocalIPList_Interfaces(); + void GetLocalIPList_DNS(); + void AddLoopbackAddress(); + void FlushPackets(); + int PollSocket(bool rtp); + int ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port); + int ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port); +#ifdef RTP_SUPPORT_IPV4MULTICAST + bool SetMulticastTTL(uint8_t ttl); +#endif // RTP_SUPPORT_IPV4MULTICAST + bool ShouldAcceptData(uint32_t srcip,uint16_t srcport); + void ClearAcceptIgnoreInfo(); + + int GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, + SocketType *pRtpSock, SocketType *pRtcpSock, + uint16_t *pRtpPort, uint16_t *pRtcpPort); + static int GetIPv4SocketPort(SocketType s, uint16_t *pPort); + + bool init; + bool created; + bool waitingfordata; + SocketType rtpsock,rtcpsock; + uint32_t mcastifaceIP; + std::list localIPs; + uint16_t m_rtpPort, m_rtcpPort; + uint8_t multicastTTL; + RTPTransmitter::ReceiveMode receivemode; + + uint8_t *localhostname; + size_t localhostnamelength; + + RTPHashTable destinations; +#ifdef RTP_SUPPORT_IPV4MULTICAST + RTPHashTable multicastgroups; +#endif // RTP_SUPPORT_IPV4MULTICAST + std::list rawpacketlist; + + bool supportsmulticasting; + size_t maxpacksize; + + class PortInfo + { + public: + PortInfo() { all = false; } + + bool all; + std::list portlist; + }; + + RTPKeyHashTable acceptignoreinfo; + + bool closesocketswhendone; + RTPAbortDescriptors m_abortDesc; + RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified + +#ifdef RTP_SUPPORT_THREAD + jthread::JMutex mainmutex,waitmutex; + int threadsafe; +#endif // RTP_SUPPORT_THREAD +}; + +} // end namespace + +#endif // RTPUDPV4TRANSMITTERNOBIND_H + diff --git a/qrtplib/rtpudpv6transmitter.cpp b/qrtplib/rtpudpv6transmitter.cpp new file mode 100644 index 000000000..848589739 --- /dev/null +++ b/qrtplib/rtpudpv6transmitter.cpp @@ -0,0 +1,1643 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +// This is for getaddrinfo when using mingw +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#endif + +#include "rtpudpv6transmitter.h" + +#ifdef RTP_SUPPORT_IPV6 + +#include "rtprawpacket.h" +#include "rtpipv6address.h" +#include "rtptimeutilities.h" +#include "rtpdefines.h" +#include "rtpsocketutilinternal.h" +#include "rtpinternalutils.h" +#include "rtpselect.h" +#include + +#define RTPUDPV6TRANS_MAXPACKSIZE 65535 +#define RTPUDPV6TRANS_IFREQBUFSIZE 8192 + +#define RTPUDPV6TRANS_IS_MCASTADDR(x) (x.s6_addr[0] == 0xFF) + +#define RTPUDPV6TRANS_MCASTMEMBERSHIP(socket,type,mcastip,status) {\ + struct ipv6_mreq mreq;\ + \ + mreq.ipv6mr_multiaddr = mcastip;\ + mreq.ipv6mr_interface = mcastifidx;\ + status = setsockopt(socket,IPPROTO_IPV6,type,(const char *)&mreq,sizeof(struct ipv6_mreq));\ + } +#ifdef RTP_SUPPORT_THREAD + #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } + #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } + #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } + #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } +#else + #define MAINMUTEX_LOCK + #define MAINMUTEX_UNLOCK + #define WAITMUTEX_LOCK + #define WAITMUTEX_UNLOCK +#endif // RTP_SUPPORT_THREAD + +inline bool operator==(const in6_addr &ip1,const in6_addr &ip2) +{ + if (memcmp(&ip1,&ip2,sizeof(in6_addr)) == 0) + return true; + return false; +} + +namespace qrtplib +{ + +RTPUDPv6Transmitter::RTPUDPv6Transmitter(RTPMemoryManager *mgr) : RTPTransmitter(mgr), + destinations(GetMemoryManager(),RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT), + multicastgroups(GetMemoryManager(),RTPMEM_TYPE_CLASS_MULTICASTHASHELEMENT), + acceptignoreinfo(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT) +{ + created = false; + init = false; +} + +RTPUDPv6Transmitter::~RTPUDPv6Transmitter() +{ + Destroy(); +} + +int RTPUDPv6Transmitter::Init(bool tsafe) +{ + if (init) + return ERR_RTP_UDPV6TRANS_ALREADYINIT; + +#ifdef RTP_SUPPORT_THREAD + threadsafe = tsafe; + if (threadsafe) + { + int status; + + status = mainmutex.Init(); + if (status < 0) + return ERR_RTP_UDPV6TRANS_CANTINITMUTEX; + status = waitmutex.Init(); + if (status < 0) + return ERR_RTP_UDPV6TRANS_CANTINITMUTEX; + } +#else + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; +#endif // RTP_SUPPORT_THREAD + + init = true; + return 0; +} + +int RTPUDPv6Transmitter::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) +{ + const RTPUDPv6TransmissionParams *params,defaultparams; + struct sockaddr_in6 addr; + RTPSOCKLENTYPE size; + int status; + + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_ALREADYCREATED; + } + + // Obtain transmission parameters + + if (transparams == 0) + params = &defaultparams; + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv6UDPProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS; + } + params = (const RTPUDPv6TransmissionParams *)transparams; + } + + // Check if portbase is even + if (params->GetPortbase()%2 != 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN; + } + + // create sockets + + rtpsock = socket(PF_INET6,SOCK_DGRAM,0); + if (rtpsock == RTPSOCKERR) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_CANTCREATESOCKET; + } + rtcpsock = socket(PF_INET6,SOCK_DGRAM,0); + if (rtcpsock == RTPSOCKERR) + { + RTPCLOSE(rtpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_CANTCREATESOCKET; + } + + // set socket buffer sizes + + size = params->GetRTPReceiveBuffer(); + if (setsockopt(rtpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF; + } + size = params->GetRTPSendBuffer(); + if (setsockopt(rtpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF; + } + size = params->GetRTCPReceiveBuffer(); + if (setsockopt(rtcpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF; + } + size = params->GetRTCPSendBuffer(); + if (setsockopt(rtcpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF; + } + + // bind sockets + + bindIP = params->GetBindIP(); + mcastifidx = params->GetMulticastInterfaceIndex(); + + memset(&addr,0,sizeof(struct sockaddr_in6)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(params->GetPortbase()); + addr.sin6_addr = bindIP; + if (bind(rtpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in6)) != 0) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET; + } + memset(&addr,0,sizeof(struct sockaddr_in6)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(params->GetPortbase()+1); + addr.sin6_addr = bindIP; + if (bind(rtcpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in6)) != 0) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET; + } + + // Try to obtain local IP addresses + + localIPs = params->GetLocalIPList(); + if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them + { + int status; + + if ((status = CreateLocalIPList()) < 0) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return status; + } + + } + +#ifdef RTP_SUPPORT_IPV6MULTICAST + if (SetMulticastTTL(params->GetMulticastTTL())) + supportsmulticasting = true; + else + supportsmulticasting = false; +#else // no multicast support enabled + supportsmulticasting = false; +#endif // RTP_SUPPORT_IPV6MULTICAST + + if (maximumpacketsize > RTPUDPV6TRANS_MAXPACKSIZE) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG; + } + + if (!params->GetCreatedAbortDescriptors()) + { + if ((status = m_abortDesc.Init()) < 0) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return status; + } + m_pAbortDesc = &m_abortDesc; + } + else + { + m_pAbortDesc = params->GetCreatedAbortDescriptors(); + if (!m_pAbortDesc->IsInitialized()) + { + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; + } + } + + maxpacksize = maximumpacketsize; + portbase = params->GetPortbase(); + multicastTTL = params->GetMulticastTTL(); + receivemode = RTPTransmitter::AcceptAll; + + localhostname = 0; + localhostnamelength = 0; + + waitingfordata = false; + created = true; + MAINMUTEX_UNLOCK + return 0; +} + +void RTPUDPv6Transmitter::Destroy() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK; + return; + } + + if (localhostname) + { + RTPDeleteByteArray(localhostname,GetMemoryManager()); + localhostname = 0; + localhostnamelength = 0; + } + + RTPCLOSE(rtpsock); + RTPCLOSE(rtcpsock); + destinations.Clear(); +#ifdef RTP_SUPPORT_IPV6MULTICAST + multicastgroups.Clear(); +#endif // RTP_SUPPORT_IPV6MULTICAST + FlushPackets(); + ClearAcceptIgnoreInfo(); + localIPs.clear(); + created = false; + + if (waitingfordata) + { + m_pAbortDesc->SendAbortSignal(); + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK + } + else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + + MAINMUTEX_UNLOCK +} + +RTPTransmissionInfo *RTPUDPv6Transmitter::GetTransmissionInfo() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPUDPv6TransmissionInfo(localIPs,rtpsock,rtcpsock,portbase,portbase+1); + MAINMUTEX_UNLOCK + return tinf; +} + +void RTPUDPv6Transmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) +{ + if (!init) + return; + + RTPDelete(i, GetMemoryManager()); +} + +int RTPUDPv6Transmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + + if (localhostname == 0) + { + if (localIPs.empty()) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOLOCALIPS; + } + + std::list::const_iterator it; + std::list hostnames; + + for (it = localIPs.begin() ; it != localIPs.end() ; it++) + { + bool founddouble = false; + bool foundentry = true; + + while (!founddouble && foundentry) + { + struct hostent *he; + in6_addr ip = (*it); + + he = gethostbyaddr((char *)&ip,sizeof(in6_addr),AF_INET6); + if (he != 0) + { + std::string hname = std::string(he->h_name); + std::list::const_iterator it; + + for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + hostnames.push_back(hname); + + int i = 0; + while (!founddouble && he->h_aliases[i] != 0) + { + std::string hname = std::string(he->h_aliases[i]); + + for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + { + hostnames.push_back(hname); + i++; + } + } + } + else + foundentry = false; + } + } + + bool found = false; + + if (!hostnames.empty()) // try to select the most appropriate hostname + { + std::list::const_iterator it; + + hostnames.sort(); + for (it = hostnames.begin() ; !found && it != hostnames.end() ; it++) + { + if ((*it).find('.') != std::string::npos) + { + found = true; + localhostnamelength = (*it).length(); + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname,(*it).c_str(),localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } + } + + if (!found) // use an IP address + { + in6_addr ip; + int len; + char str[48]; + uint16_t ip16[8]; + int i,j; + + it = localIPs.begin(); + ip = (*it); + + for (i = 0,j = 0 ; j < 8 ; j++,i += 2) + { + ip16[j] = (((uint16_t)ip.s6_addr[i])<<8); + ip16[j] |= ((uint16_t)ip.s6_addr[i+1]); + } + + RTP_SNPRINTF(str,48,"%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X",(int)ip16[0],(int)ip16[1],(int)ip16[2],(int)ip16[3],(int)ip16[4],(int)ip16[5],(int)ip16[6],(int)ip16[7]); + len = strlen(str); + + localhostnamelength = len; + localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname,str,localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } + + if ((*bufferlength) < localhostnamelength) + { + *bufferlength = localhostnamelength; // tell the application the required size of the buffer + MAINMUTEX_UNLOCK + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; + } + + memcpy(buffer,localhostname,localhostnamelength); + *bufferlength = localhostnamelength; + + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPUDPv6Transmitter::ComesFromThisTransmitter(const RTPAddress *addr) +{ + if (!init) + return false; + + if (addr == 0) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (created && addr->GetAddressType() == RTPAddress::IPv6Address) + { + const RTPIPv6Address *addr2 = (const RTPIPv6Address *)addr; + bool found = false; + std::list::const_iterator it; + + it = localIPs.begin(); + while (!found && it != localIPs.end()) + { + in6_addr itip = *it; + in6_addr addrip = addr2->GetIP(); + if (memcmp(&addrip,&itip,sizeof(in6_addr)) == 0) + found = true; + else + ++it; + } + + if (!found) + v = false; + else + { + if (addr2->GetPort() == portbase) // check for RTP port + v = true; + else if (addr2->GetPort() == (portbase+1)) // check for RTCP port + v = true; + else + v = false; + } + } + else + v = false; + + MAINMUTEX_UNLOCK + return v; +} + +int RTPUDPv6Transmitter::Poll() +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + int status; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + status = PollSocket(true); // poll RTP socket + if (status >= 0) + status = PollSocket(false); // poll RTCP socket + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv6Transmitter::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (waitingfordata) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_ALREADYWAITING; + } + + SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); + SocketType socks[3] = { rtpsock, rtcpsock, abortSocket }; + int8_t readflags[3] = { 0, 0, 0 }; + const int idxRTP = 0; + const int idxRTCP = 1; + const int idxAbort = 2; + + waitingfordata = true; + + WAITMUTEX_LOCK + MAINMUTEX_UNLOCK + + int status = RTPSelect(socks, readflags, 3, delay); + if (status < 0) + { + MAINMUTEX_LOCK + waitingfordata = false; + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return status; + } + + MAINMUTEX_LOCK + waitingfordata = false; + if (!created) // destroy called + { + MAINMUTEX_UNLOCK; + WAITMUTEX_UNLOCK + return 0; + } + + // if aborted, read from abort buffer + if (readflags[idxAbort]) + m_pAbortDesc->ReadSignallingByte(); + + if (dataavailable != 0) + { + if (readflags[idxRTP] || readflags[idxRTCP]) + *dataavailable = true; + else + *dataavailable = false; + } + + MAINMUTEX_UNLOCK + WAITMUTEX_UNLOCK + return 0; +} + +int RTPUDPv6Transmitter::AbortWait() +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (!waitingfordata) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTWAITING; + } + + m_pAbortDesc->SendAbortSignal(); + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv6Transmitter::SendRTPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG; + } + + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTPSockAddr(),sizeof(struct sockaddr_in6)); + destinations.GotoNextElement(); + } + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv6Transmitter::SendRTCPData(const void *data,size_t len) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (len > maxpacksize) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG; + } + + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtcpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTCPSockAddr(),sizeof(struct sockaddr_in6)); + destinations.GotoNextElement(); + } + + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv6Transmitter::AddDestination(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv6Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; + } + + RTPIPv6Address &address = (RTPIPv6Address &)addr; + RTPIPv6Destination dest(address.GetIP(),address.GetPort()); + int status = destinations.AddElement(dest); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv6Transmitter::DeleteDestination(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv6Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; + } + + RTPIPv6Address &address = (RTPIPv6Address &)addr; + RTPIPv6Destination dest(address.GetIP(),address.GetPort()); + int status = destinations.DeleteElement(dest); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv6Transmitter::ClearDestinations() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created) + destinations.Clear(); + MAINMUTEX_UNLOCK +} + +bool RTPUDPv6Transmitter::SupportsMulticasting() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + v = supportsmulticasting; + + MAINMUTEX_UNLOCK + return v; +} + +#ifdef RTP_SUPPORT_IPV6MULTICAST + +int RTPUDPv6Transmitter::JoinMulticastGroup(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv6Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; + } + + const RTPIPv6Address &address = (const RTPIPv6Address &)addr; + in6_addr mcastIP = address.GetIP(); + + if (!RTPUDPV6TRANS_IS_MCASTADDR(mcastIP)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.AddElement(mcastIP); + if (status >= 0) + { + RTPUDPV6TRANS_MCASTMEMBERSHIP(rtpsock,IPV6_JOIN_GROUP,mcastIP,status); + if (status != 0) + { + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP; + } + RTPUDPV6TRANS_MCASTMEMBERSHIP(rtcpsock,IPV6_JOIN_GROUP,mcastIP,status); + if (status != 0) + { + RTPUDPV6TRANS_MCASTMEMBERSHIP(rtpsock,IPV6_LEAVE_GROUP,mcastIP,status); + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP; + } + } + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv6Transmitter::LeaveMulticastGroup(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv6Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; + } + + const RTPIPv6Address &address = (const RTPIPv6Address &)addr; + in6_addr mcastIP = address.GetIP(); + + if (!RTPUDPV6TRANS_IS_MCASTADDR(mcastIP)) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.DeleteElement(mcastIP); + if (status >= 0) + { + RTPUDPV6TRANS_MCASTMEMBERSHIP(rtpsock,IPV6_LEAVE_GROUP,mcastIP,status); + RTPUDPV6TRANS_MCASTMEMBERSHIP(rtcpsock,IPV6_LEAVE_GROUP,mcastIP,status); + status = 0; + } + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv6Transmitter::LeaveAllMulticastGroups() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created) + { + multicastgroups.GotoFirstElement(); + while (multicastgroups.HasCurrentElement()) + { + in6_addr mcastIP; + int status = 0; + + mcastIP = multicastgroups.GetCurrentElement(); + RTPUDPV6TRANS_MCASTMEMBERSHIP(rtpsock,IPV6_LEAVE_GROUP,mcastIP,status); + RTPUDPV6TRANS_MCASTMEMBERSHIP(rtcpsock,IPV6_LEAVE_GROUP,mcastIP,status); + multicastgroups.GotoNextElement(); + JRTPLIB_UNUSED(status); + } + multicastgroups.Clear(); + } + MAINMUTEX_UNLOCK +} + +#else // no multicast support + +int RTPUDPv6Transmitter::JoinMulticastGroup(const RTPAddress &addr) +{ + return ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT; +} + +int RTPUDPv6Transmitter::LeaveMulticastGroup(const RTPAddress &addr) +{ + return ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT; +} + +void RTPUDPv6Transmitter::LeaveAllMulticastGroups() +{ +} + +#endif // RTP_SUPPORT_IPV6MULTICAST + +int RTPUDPv6Transmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (m != receivemode) + { + receivemode = m; + acceptignoreinfo.Clear(); + } + MAINMUTEX_UNLOCK + return 0; +} + +int RTPUDPv6Transmitter::AddToIgnoreList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv6Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv6Address &address = (const RTPIPv6Address &)addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv6Transmitter::DeleteFromIgnoreList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv6Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv6Address &address = (const RTPIPv6Address &)addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv6Transmitter::ClearIgnoreList() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created && receivemode == RTPTransmitter::IgnoreSome) + ClearAcceptIgnoreInfo(); + MAINMUTEX_UNLOCK +} + +int RTPUDPv6Transmitter::AddToAcceptList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv6Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv6Address &address = (const RTPIPv6Address &)addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +int RTPUDPv6Transmitter::DeleteFromAcceptList(const RTPAddress &addr) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + + int status; + + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv6Address) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv6Address &address = (const RTPIPv6Address &)addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); + + MAINMUTEX_UNLOCK + return status; +} + +void RTPUDPv6Transmitter::ClearAcceptList() +{ + if (!init) + return; + + MAINMUTEX_LOCK + if (created && receivemode == RTPTransmitter::AcceptSome) + ClearAcceptIgnoreInfo(); + MAINMUTEX_UNLOCK +} + +int RTPUDPv6Transmitter::SetMaximumPacketSize(size_t s) +{ + if (!init) + return ERR_RTP_UDPV6TRANS_NOTINIT; + + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_NOTCREATED; + } + if (s > RTPUDPV6TRANS_MAXPACKSIZE) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG; + } + maxpacksize = s; + MAINMUTEX_UNLOCK + return 0; +} + +bool RTPUDPv6Transmitter::NewDataAvailable() +{ + if (!init) + return false; + + MAINMUTEX_LOCK + + bool v; + + if (!created) + v = false; + else + { + if (rawpacketlist.empty()) + v = false; + else + v = true; + } + + MAINMUTEX_UNLOCK + return v; +} + +RTPRawPacket *RTPUDPv6Transmitter::GetNextPacket() +{ + if (!init) + return 0; + + MAINMUTEX_LOCK + + RTPRawPacket *p; + + if (!created) + { + MAINMUTEX_UNLOCK + return 0; + } + if (rawpacketlist.empty()) + { + MAINMUTEX_UNLOCK + return 0; + } + + p = *(rawpacketlist.begin()); + rawpacketlist.pop_front(); + + MAINMUTEX_UNLOCK + return p; +} + +// Here the private functions start... + + +#ifdef RTP_SUPPORT_IPV6MULTICAST +bool RTPUDPv6Transmitter::SetMulticastTTL(uint8_t ttl) +{ + int ttl2,status; + + ttl2 = (int)ttl; + status = setsockopt(rtpsock,IPPROTO_IPV6,IPV6_MULTICAST_HOPS,(const char *)&ttl2,sizeof(int)); + if (status != 0) + return false; + status = setsockopt(rtcpsock,IPPROTO_IPV6,IPV6_MULTICAST_HOPS,(const char *)&ttl2,sizeof(int)); + if (status != 0) + return false; + return true; +} +#endif // RTP_SUPPORT_IPV6MULTICAST + + +void RTPUDPv6Transmitter::FlushPackets() +{ + std::list::const_iterator it; + + for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) + RTPDelete(*it,GetMemoryManager()); + rawpacketlist.clear(); +} + +int RTPUDPv6Transmitter::PollSocket(bool rtp) +{ + RTPSOCKLENTYPE fromlen; + int recvlen; + char packetbuffer[RTPUDPV6TRANS_MAXPACKSIZE]; +#ifdef RTP_SOCKETTYPE_WINSOCK + SOCKET sock; + unsigned long len; +#else + size_t len; + int sock; +#endif // RTP_SOCKETTYPE_WINSOCK + struct sockaddr_in6 srcaddr; + bool dataavailable; + + if (rtp) + sock = rtpsock; + else + sock = rtcpsock; + + len = 0; + RTPIOCTL(sock,FIONREAD,&len); + + if (len <= 0) // make sure a packet of length zero is not queued + { + int8_t isset = 0; + int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); + if (status < 0) + return status; + + if (isset) + dataavailable = true; + else + dataavailable = false; + } + else + dataavailable = true; + + while (dataavailable) + { + RTPTime curtime = RTPTime::CurrentTime(); + fromlen = sizeof(struct sockaddr_in6); + recvlen = recvfrom(sock,packetbuffer,RTPUDPV6TRANS_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); + if (recvlen > 0) + { + bool acceptdata; + + // got data, process it + if (receivemode == RTPTransmitter::AcceptAll) + acceptdata = true; + else + acceptdata = ShouldAcceptData(srcaddr.sin6_addr,ntohs(srcaddr.sin6_port)); + + if (acceptdata) + { + RTPRawPacket *pack; + RTPIPv6Address *addr; + uint8_t *datacopy; + + addr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv6Address(srcaddr.sin6_addr,ntohs(srcaddr.sin6_port)); + if (addr == 0) + return ERR_RTP_OUTOFMEM; + datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[recvlen]; + if (datacopy == 0) + { + RTPDelete(addr,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + memcpy(datacopy,packetbuffer,recvlen); + + pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,recvlen,addr,curtime,rtp,GetMemoryManager()); + if (pack == 0) + { + RTPDelete(addr,GetMemoryManager()); + RTPDeleteByteArray(datacopy,GetMemoryManager()); + return ERR_RTP_OUTOFMEM; + } + rawpacketlist.push_back(pack); + } + } + len = 0; + RTPIOCTL(sock,FIONREAD,&len); + + if (len <= 0) // make sure a packet of length zero is not queued + { + int8_t isset = 0; + int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); + if (status < 0) + return status; + + if (isset) + dataavailable = true; + else + dataavailable = false; + } + else + dataavailable = true; + } + return 0; +} + +int RTPUDPv6Transmitter::ProcessAddAcceptIgnoreEntry(in6_addr ip,uint16_t port) +{ + acceptignoreinfo.GotoElement(ip); + if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists + { + PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); + + if (port == 0) // select all ports + { + portinf->all = true; + portinf->portlist.clear(); + } + else if (!portinf->all) + { + std::list::const_iterator it,begin,end; + + begin = portinf->portlist.begin(); + end = portinf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == port) // already in list + return 0; + } + portinf->portlist.push_front(port); + } + } + else // got to create an entry for this IP address + { + PortInfo *portinf; + int status; + + portinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO) PortInfo(); + if (port == 0) // select all ports + portinf->all = true; + else + portinf->portlist.push_front(port); + + status = acceptignoreinfo.AddElement(ip,portinf); + if (status < 0) + { + RTPDelete(portinf,GetMemoryManager()); + return status; + } + } + return 0; +} + +void RTPUDPv6Transmitter::ClearAcceptIgnoreInfo() +{ + acceptignoreinfo.GotoFirstElement(); + while (acceptignoreinfo.HasCurrentElement()) + { + PortInfo *inf; + + inf = acceptignoreinfo.GetCurrentElement(); + RTPDelete(inf,GetMemoryManager()); + acceptignoreinfo.GotoNextElement(); + } + acceptignoreinfo.Clear(); +} + +int RTPUDPv6Transmitter::ProcessDeleteAcceptIgnoreEntry(in6_addr ip,uint16_t port) +{ + acceptignoreinfo.GotoElement(ip); + if (!acceptignoreinfo.HasCurrentElement()) + return ERR_RTP_UDPV6TRANS_NOSUCHENTRY; + + PortInfo *inf; + + inf = acceptignoreinfo.GetCurrentElement(); + if (port == 0) // delete all entries + { + inf->all = false; + inf->portlist.clear(); + } + else // a specific port was selected + { + if (inf->all) // currently, all ports are selected. Add the one to remove to the list + { + // we have to check if the list doesn't contain the port already + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == port) // already in list: this means we already deleted the entry + return ERR_RTP_UDPV6TRANS_NOSUCHENTRY; + } + inf->portlist.push_front(port); + } + else // check if we can find the port in the list + { + std::list::iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; ++it) + { + if (*it == port) // found it! + { + inf->portlist.erase(it); + return 0; + } + } + // didn't find it + return ERR_RTP_UDPV6TRANS_NOSUCHENTRY; + } + } + return 0; +} + +bool RTPUDPv6Transmitter::ShouldAcceptData(in6_addr srcip,uint16_t srcport) +{ + if (receivemode == RTPTransmitter::AcceptSome) + { + PortInfo *inf; + + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return false; + + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // only accept the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return true; + } + return false; + } + else // accept all, except the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return false; + } + return true; + } + } + else // IgnoreSome + { + PortInfo *inf; + + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return true; + + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // ignore the ports in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return false; + } + return true; + } + else // ignore all, except the ones in the list + { + std::list::const_iterator it,begin,end; + + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin ; it != end ; it++) + { + if (*it == srcport) + return true; + } + return false; + } + } + return true; +} + +int RTPUDPv6Transmitter::CreateLocalIPList() +{ + // first try to obtain the list from the network interface info + + if (!GetLocalIPList_Interfaces()) + { + // If this fails, we'll have to depend on DNS info + GetLocalIPList_DNS(); + } + AddLoopbackAddress(); + return 0; +} + +#ifdef RTP_SOCKETTYPE_WINSOCK + +bool RTPUDPv6Transmitter::GetLocalIPList_Interfaces() +{ + unsigned char buffer[RTPUDPV6TRANS_IFREQBUFSIZE]; + DWORD outputsize; + DWORD numaddresses,i; + SOCKET_ADDRESS_LIST *addrlist; + + if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV6TRANS_IFREQBUFSIZE,&outputsize,NULL,NULL)) + return false; + + addrlist = (SOCKET_ADDRESS_LIST *)buffer; + numaddresses = addrlist->iAddressCount; + for (i = 0 ; i < numaddresses ; i++) + { + SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); + if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in6)) // IPv6 address + { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)sockaddr->lpSockaddr; + + localIPs.push_back(addr->sin6_addr); + } + } + + if (localIPs.empty()) + return false; + return true; +} + +#else + +#ifdef RTP_SUPPORT_IFADDRS + +bool RTPUDPv6Transmitter::GetLocalIPList_Interfaces() +{ + struct ifaddrs *addrs,*tmp; + + getifaddrs(&addrs); + tmp = addrs; + + while (tmp != 0) + { + if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET6) + { + struct sockaddr_in6 *inaddr = (struct sockaddr_in6 *)tmp->ifa_addr; + localIPs.push_back(inaddr->sin6_addr); + } + tmp = tmp->ifa_next; + } + + freeifaddrs(addrs); + + if (localIPs.empty()) + return false; + return true; +} + +#else + +bool RTPUDPv6Transmitter::GetLocalIPList_Interfaces() +{ + return false; +} + +#endif // RTP_SUPPORT_IFADDRS + +#endif // RTP_SOCKETTYPE_WINSOCK + +void RTPUDPv6Transmitter::GetLocalIPList_DNS() +{ + int status; + char name[1024]; + + gethostname(name,1023); + name[1023] = 0; + + struct addrinfo hints; + struct addrinfo *res,*tmp; + + memset(&hints,0,sizeof(struct addrinfo)); + hints.ai_family = AF_INET6; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + + if ((status = getaddrinfo(name,0,&hints,&res)) != 0) + return; + + tmp = res; + while (tmp != 0) + { + if (tmp->ai_family == AF_INET6) + { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)(tmp->ai_addr); + localIPs.push_back(addr->sin6_addr); + } + tmp = tmp->ai_next; + } + + freeaddrinfo(res); +} + +void RTPUDPv6Transmitter::AddLoopbackAddress() +{ + std::list::const_iterator it; + bool found = false; + + for (it = localIPs.begin() ; !found && it != localIPs.end() ; it++) + { + if ((*it) == in6addr_loopback) + found = true; + } + + if (!found) + localIPs.push_back(in6addr_loopback); +} + +} // end namespace + +#endif // RTP_SUPPORT_IPV6 + diff --git a/qrtplib/rtpudpv6transmitter.h b/qrtplib/rtpudpv6transmitter.h new file mode 100644 index 000000000..2a582035c --- /dev/null +++ b/qrtplib/rtpudpv6transmitter.h @@ -0,0 +1,330 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/** + * \file rtpudpv6transmitter.h + */ + +#ifndef RTPUDPV6TRANSMITTER_H + +#define RTPUDPV6TRANSMITTER_H + +#include "rtpconfig.h" + +#ifdef RTP_SUPPORT_IPV6 + +#include "rtptransmitter.h" +#include "rtpipv6destination.h" +#include "rtphashtable.h" +#include "rtpkeyhashtable.h" +#include "rtpsocketutil.h" +#include "rtpabortdescriptors.h" +#include +#include + +#ifdef RTP_SUPPORT_THREAD + #include +#endif // RTP_SUPPORT_THREAD + +#define RTPUDPV6TRANS_HASHSIZE 8317 +#define RTPUDPV6TRANS_DEFAULTPORTBASE 5000 + +#define RTPUDPV6TRANS_RTPRECEIVEBUFFER 32768 +#define RTPUDPV6TRANS_RTCPRECEIVEBUFFER 32768 +#define RTPUDPV6TRANS_RTPTRANSMITBUFFER 32768 +#define RTPUDPV6TRANS_RTCPTRANSMITBUFFER 32768 + +namespace qrtplib +{ + +/** Parameters for the UDP over IPv6 transmitter. */ +class JRTPLIB_IMPORTEXPORT RTPUDPv6TransmissionParams : public RTPTransmissionParams +{ +public: + RTPUDPv6TransmissionParams(); + + /** Sets the IP address which is used to bind the sockets to \c ip. */ + void SetBindIP(in6_addr ip) { bindIP = ip; } + + /** Sets the multicast interface index. */ + void SetMulticastInterfaceIndex(unsigned int idx) { mcastifidx = idx; } + + /** Sets the RTP portbase to \c pbase. This has to be an even number. */ + void SetPortbase(uint16_t pbase) { portbase = pbase; } + + /** Sets the multicast TTL to be used to \c mcastTTL. */ + void SetMulticastTTL(uint8_t mcastTTL) { multicastTTL = mcastTTL; } + + /** Passes a list of IP addresses which will be used as the local IP addresses. */ + void SetLocalIPList(std::list &iplist) { localIPs = iplist; } + + /** Clears the list of local IP addresses. + * Clears the list of local IP addresses. An empty list will make the transmission component + * itself determine the local IP addresses. + */ + void ClearLocalIPList() { localIPs.clear(); } + + /** Returns the IP address which will be used to bind the sockets. */ + in6_addr GetBindIP() const { return bindIP; } + + /** Returns the multicast interface index. */ + unsigned int GetMulticastInterfaceIndex() const { return mcastifidx; } + + /** Returns the RTP portbase which will be used (default is 5000). */ + uint16_t GetPortbase() const { return portbase; } + + /** Returns the multicast TTL which will be used (default is 1). */ + uint8_t GetMulticastTTL() const { return multicastTTL; } + + /** Returns the list of local IP addresses. */ + const std::list &GetLocalIPList() const { return localIPs; } + + /** Sets the RTP socket's send buffer size. */ + void SetRTPSendBuffer(int s) { rtpsendbuf = s; } + + /** Sets the RTP socket's receive buffer size. */ + void SetRTPReceiveBuffer(int s) { rtprecvbuf = s; } + + /** Sets the RTCP socket's send buffer size. */ + void SetRTCPSendBuffer(int s) { rtcpsendbuf = s; } + + /** Sets the RTCP socket's receive buffer size. */ + void SetRTCPReceiveBuffer(int s) { rtcprecvbuf = s; } + + /** If non null, the specified abort descriptors will be used to cancel + * the function that's waiting for packets to arrive; set to null (the default + * to let the transmitter create its own instance. */ + void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) { m_pAbortDesc = desc; } + + /** Returns the RTP socket's send buffer size. */ + int GetRTPSendBuffer() const { return rtpsendbuf; } + + /** Returns the RTP socket's receive buffer size. */ + int GetRTPReceiveBuffer() const { return rtprecvbuf; } + + /** Returns the RTCP socket's send buffer size. */ + int GetRTCPSendBuffer() const { return rtcpsendbuf; } + + /** Returns the RTCP socket's receive buffer size. */ + int GetRTCPReceiveBuffer() const { return rtcprecvbuf; } + + /** If non-null, this RTPAbortDescriptors instance will be used internally, + * which can be useful when creating your own poll thread for multiple + * sessions. */ + RTPAbortDescriptors *GetCreatedAbortDescriptors() const { return m_pAbortDesc; } +private: + uint16_t portbase; + in6_addr bindIP; + unsigned int mcastifidx; + std::list localIPs; + uint8_t multicastTTL; + int rtpsendbuf, rtprecvbuf; + int rtcpsendbuf, rtcprecvbuf; + + RTPAbortDescriptors *m_pAbortDesc; +}; + +inline RTPUDPv6TransmissionParams::RTPUDPv6TransmissionParams() + : RTPTransmissionParams(RTPTransmitter::IPv6UDPProto) +{ + portbase = RTPUDPV6TRANS_DEFAULTPORTBASE; + for (int i = 0 ; i < 16 ; i++) + bindIP.s6_addr[i] = 0; + + multicastTTL = 1; + mcastifidx = 0; + rtpsendbuf = RTPUDPV6TRANS_RTPTRANSMITBUFFER; + rtprecvbuf= RTPUDPV6TRANS_RTPRECEIVEBUFFER; + rtcpsendbuf = RTPUDPV6TRANS_RTCPTRANSMITBUFFER; + rtcprecvbuf = RTPUDPV6TRANS_RTCPRECEIVEBUFFER; + + m_pAbortDesc = 0; +} + +/** Additional information about the UDP over IPv6 transmitter. */ +class JRTPLIB_IMPORTEXPORT RTPUDPv6TransmissionInfo : public RTPTransmissionInfo +{ +public: + RTPUDPv6TransmissionInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, + uint16_t rtpport, uint16_t rtcpport) : RTPTransmissionInfo(RTPTransmitter::IPv6UDPProto) + { localIPlist = iplist; rtpsocket = rtpsock; rtcpsocket = rtcpsock; m_rtpPort = rtpport; m_rtcpPort = rtcpport; } + + ~RTPUDPv6TransmissionInfo() { } + + /** Returns the list of IPv6 addresses the transmitter considers to be the local IP addresses. */ + std::list GetLocalIPList() const { return localIPlist; } + + /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ + SocketType GetRTPSocket() const { return rtpsocket; } + + /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ + SocketType GetRTCPSocket() const { return rtcpsocket; } + + /** Returns the port number that the RTP socket receives packets on. */ + uint16_t GetRTPPort() const { return m_rtpPort; } + + /** Returns the port number that the RTCP socket receives packets on. */ + uint16_t GetRTCPPort() const { return m_rtcpPort; } +private: + std::list localIPlist; + SocketType rtpsocket,rtcpsocket; + uint16_t m_rtpPort, m_rtcpPort; +}; + +class JRTPLIB_IMPORTEXPORT RTPUDPv6Trans_GetHashIndex_IPv6Dest +{ +public: + static int GetIndex(const RTPIPv6Destination &d) { in6_addr ip = d.GetIP(); return ((((uint32_t)ip.s6_addr[12])<<24)|(((uint32_t)ip.s6_addr[13])<<16)|(((uint32_t)ip.s6_addr[14])<<8)|((uint32_t)ip.s6_addr[15]))%RTPUDPV6TRANS_HASHSIZE; } +}; + +class JRTPLIB_IMPORTEXPORT RTPUDPv6Trans_GetHashIndex_in6_addr +{ +public: + static int GetIndex(const in6_addr &ip) { return ((((uint32_t)ip.s6_addr[12])<<24)|(((uint32_t)ip.s6_addr[13])<<16)|(((uint32_t)ip.s6_addr[14])<<8)|((uint32_t)ip.s6_addr[15]))%RTPUDPV6TRANS_HASHSIZE; } +}; + +#define RTPUDPV6TRANS_HEADERSIZE (40+8) + +/** An UDP over IPv6 transmitter. + * This class inherits the RTPTransmitter interface and implements a transmission component + * which uses UDP over IPv6 to send and receive RTP and RTCP data. The component's parameters + * are described by the class RTPUDPv6TransmissionParams. The functions which have an RTPAddress + * argument require an argument of RTPIPv6Address. The GetTransmissionInfo member function + * returns an instance of type RTPUDPv6TransmissionInfo. + */ +class JRTPLIB_IMPORTEXPORT RTPUDPv6Transmitter : public RTPTransmitter +{ +public: + RTPUDPv6Transmitter(RTPMemoryManager *mgr); + ~RTPUDPv6Transmitter(); + + int Init(bool treadsafe); + int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() { return RTPUDPV6TRANS_HEADERSIZE; } + + int Poll(); + int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); + int AbortWait(); + + int SendRTPData(const void *data,size_t len); + int SendRTCPData(const void *data,size_t len); + + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); + + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); + + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); + + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); + +private: + int CreateLocalIPList(); + bool GetLocalIPList_Interfaces(); + void GetLocalIPList_DNS(); + void AddLoopbackAddress(); + void FlushPackets(); + int PollSocket(bool rtp); + int ProcessAddAcceptIgnoreEntry(in6_addr ip,uint16_t port); + int ProcessDeleteAcceptIgnoreEntry(in6_addr ip,uint16_t port); +#ifdef RTP_SUPPORT_IPV6MULTICAST + bool SetMulticastTTL(uint8_t ttl); +#endif // RTP_SUPPORT_IPV6MULTICAST + bool ShouldAcceptData(in6_addr srcip,uint16_t srcport); + void ClearAcceptIgnoreInfo(); + + bool init; + bool created; + bool waitingfordata; + SocketType rtpsock,rtcpsock; + in6_addr bindIP; + unsigned int mcastifidx; + std::list localIPs; + uint16_t portbase; + uint8_t multicastTTL; + RTPTransmitter::ReceiveMode receivemode; + + uint8_t *localhostname; + size_t localhostnamelength; + + RTPHashTable destinations; +#ifdef RTP_SUPPORT_IPV6MULTICAST + RTPHashTable multicastgroups; +#endif // RTP_SUPPORT_IPV6MULTICAST + std::list rawpacketlist; + + bool supportsmulticasting; + size_t maxpacksize; + + class PortInfo + { + public: + PortInfo() { all = false; } + + bool all; + std::list portlist; + }; + + RTPKeyHashTable acceptignoreinfo; + RTPAbortDescriptors m_abortDesc; + RTPAbortDescriptors *m_pAbortDesc; + +#ifdef RTP_SUPPORT_THREAD + jthread::JMutex mainmutex,waitmutex; + int threadsafe; +#endif // RTP_SUPPORT_THREAD +}; + +} // end namespace + +#endif // RTP_SUPPORT_IPV6 + +#endif // RTPUDPV6TRANSMITTER_H + From c15c8b79d9c7c1407dadba5f3717779b9faeaa7b Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 01:43:41 +0100 Subject: [PATCH 014/956] qrtplib: removed jthread --- qrtplib/CMakeLists.txt | 2 - .../extratransmitters/rtpfaketransmitter.cpp | 30 +-- .../extratransmitters/rtpfaketransmitter.h | 8 - qrtplib/rtpexternaltransmitter.cpp | 30 +-- qrtplib/rtpexternaltransmitter.h | 8 - qrtplib/rtppollthread.cpp | 171 ------------------ qrtplib/rtppollthread.h | 79 -------- qrtplib/rtprandomrand48.cpp | 15 -- qrtplib/rtprandomrand48.h | 6 - qrtplib/rtpsession.cpp | 114 +----------- qrtplib/rtpsession.h | 39 ---- qrtplib/rtpsessionparams.cpp | 15 -- qrtplib/rtptcptransmitter.cpp | 30 +-- qrtplib/rtptcptransmitter.h | 4 - qrtplib/rtpudpv4transmitter.cpp | 30 +-- qrtplib/rtpudpv4transmitter.h | 8 - qrtplib/rtpudpv4transmitternobind.cpp | 30 +-- qrtplib/rtpudpv4transmitternobind.h | 8 - qrtplib/rtpudpv6transmitter.cpp | 30 +-- qrtplib/rtpudpv6transmitter.h | 8 - 20 files changed, 32 insertions(+), 633 deletions(-) delete mode 100644 qrtplib/rtppollthread.cpp delete mode 100644 qrtplib/rtppollthread.h diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 60c020a04..60ff3cf98 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -30,7 +30,6 @@ set (qrtplib_HEADERS rtpmemoryobject.h rtppacket.h rtppacketbuilder.h - rtppollthread.h rtprandom.h rtprandomrand48.h rtprandomrands.h @@ -80,7 +79,6 @@ set(qrtplib_SOURCES rtplibraryversion.cpp rtppacket.cpp rtppacketbuilder.cpp - rtppollthread.cpp rtprandom.cpp rtprandomrand48.cpp rtprandomrands.cpp diff --git a/qrtplib/extratransmitters/rtpfaketransmitter.cpp b/qrtplib/extratransmitters/rtpfaketransmitter.cpp index 6346792c1..b2e590aca 100644 --- a/qrtplib/extratransmitters/rtpfaketransmitter.cpp +++ b/qrtplib/extratransmitters/rtpfaketransmitter.cpp @@ -75,17 +75,10 @@ mreq.imr_interface.s_addr = htonl(bindIP);\ status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ }*/ -#ifdef RTP_SUPPORT_THREAD - #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } - #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } - #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } - #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } -#else - #define MAINMUTEX_LOCK - #define MAINMUTEX_UNLOCK - #define WAITMUTEX_LOCK - #define WAITMUTEX_UNLOCK -#endif // RTP_SUPPORT_THREAD +#define MAINMUTEX_LOCK +#define MAINMUTEX_UNLOCK +#define WAITMUTEX_LOCK +#define WAITMUTEX_UNLOCK namespace qrtplib { @@ -110,23 +103,8 @@ int RTPFakeTransmitter::Init(bool tsafe) if (tsafe) return ERR_RTP_NOTHREADSUPPORT; -#ifdef RTP_SUPPORT_THREAD - threadsafe = tsafe; - if (threadsafe) - { - int status; - - status = mainmutex.Init(); - if (status < 0) - return ERR_RTP_FAKETRANS_CANTINITMUTEX; - status = waitmutex.Init(); - if (status < 0) - return ERR_RTP_FAKETRANS_CANTINITMUTEX; - } -#else if (tsafe) return ERR_RTP_NOTHREADSUPPORT; -#endif // RTP_SUPPORT_THREAD init = true; return 0; diff --git a/qrtplib/extratransmitters/rtpfaketransmitter.h b/qrtplib/extratransmitters/rtpfaketransmitter.h index 6238cad2b..629ec3612 100644 --- a/qrtplib/extratransmitters/rtpfaketransmitter.h +++ b/qrtplib/extratransmitters/rtpfaketransmitter.h @@ -54,10 +54,6 @@ #include "rtpkeyhashtable.h" #include -#ifdef RTP_SUPPORT_THREAD - #include -#endif // RTP_SUPPORT_THREAD - #define RTPFAKETRANS_HASHSIZE 8317 #define RTPFAKETRANS_DEFAULTPORTBASE 5000 @@ -232,10 +228,6 @@ private: int CreateAbortDescriptors(); void DestroyAbortDescriptors(); void AbortWaitInternal(); -#ifdef RTP_SUPPORT_THREAD - jthread::JMutex mainmutex,waitmutex; - int threadsafe; -#endif // RTP_SUPPORT_THREAD }; } // end namespace diff --git a/qrtplib/rtpexternaltransmitter.cpp b/qrtplib/rtpexternaltransmitter.cpp index 1cd2ed791..9d7016f97 100644 --- a/qrtplib/rtpexternaltransmitter.cpp +++ b/qrtplib/rtpexternaltransmitter.cpp @@ -42,17 +42,10 @@ #include -#ifdef RTP_SUPPORT_THREAD - #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } - #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } - #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } - #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } -#else - #define MAINMUTEX_LOCK - #define MAINMUTEX_UNLOCK - #define WAITMUTEX_LOCK - #define WAITMUTEX_UNLOCK -#endif // RTP_SUPPORT_THREAD +#define MAINMUTEX_LOCK +#define MAINMUTEX_UNLOCK +#define WAITMUTEX_LOCK +#define WAITMUTEX_UNLOCK namespace qrtplib { @@ -73,23 +66,8 @@ int RTPExternalTransmitter::Init(bool tsafe) if (init) return ERR_RTP_EXTERNALTRANS_ALREADYINIT; -#ifdef RTP_SUPPORT_THREAD - threadsafe = tsafe; - if (threadsafe) - { - int status; - - status = mainmutex.Init(); - if (status < 0) - return ERR_RTP_EXTERNALTRANS_CANTINITMUTEX; - status = waitmutex.Init(); - if (status < 0) - return ERR_RTP_EXTERNALTRANS_CANTINITMUTEX; - } -#else if (tsafe) return ERR_RTP_NOTHREADSUPPORT; -#endif // RTP_SUPPORT_THREAD init = true; return 0; diff --git a/qrtplib/rtpexternaltransmitter.h b/qrtplib/rtpexternaltransmitter.h index 72574a129..cad0ff9b3 100644 --- a/qrtplib/rtpexternaltransmitter.h +++ b/qrtplib/rtpexternaltransmitter.h @@ -43,10 +43,6 @@ #include "rtpabortdescriptors.h" #include -#ifdef RTP_SUPPORT_THREAD - #include -#endif // RTP_SUPPORT_THREAD - namespace qrtplib { @@ -203,10 +199,6 @@ private: RTPAbortDescriptors m_abortDesc; int m_abortCount; -#ifdef RTP_SUPPORT_THREAD - jthread::JMutex mainmutex,waitmutex; - int threadsafe; -#endif // RTP_SUPPORT_THREAD }; inline void RTPExternalPacketInjecter::InjectRTP(const void *data, size_t len, const RTPAddress &a) diff --git a/qrtplib/rtppollthread.cpp b/qrtplib/rtppollthread.cpp deleted file mode 100644 index 76defe99b..000000000 --- a/qrtplib/rtppollthread.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "rtppollthread.h" - -#ifdef RTP_SUPPORT_THREAD - -#include "rtpsession.h" -#include "rtcpscheduler.h" -#include "rtperrors.h" -#include "rtprawpacket.h" -#include -#include - -namespace qrtplib -{ - -RTPPollThread::RTPPollThread(RTPSession &session,RTCPScheduler &sched):rtpsession(session),rtcpsched(sched) -{ - stop = false; - transmitter = 0; - timeinit.Dummy(); -} - -RTPPollThread::~RTPPollThread() -{ - Stop(); -} - -int RTPPollThread::Start(RTPTransmitter *trans) -{ - if (JThread::IsRunning()) - return ERR_RTP_POLLTHREAD_ALREADYRUNNING; - - transmitter = trans; - if (!stopmutex.IsInitialized()) - { - if (stopmutex.Init() < 0) - return ERR_RTP_POLLTHREAD_CANTINITMUTEX; - } - stop = false; - if (JThread::Start() < 0) - return ERR_RTP_POLLTHREAD_CANTSTARTTHREAD; - return 0; -} - -void RTPPollThread::Stop() -{ - if (!IsRunning()) - return; - - stopmutex.Lock(); - stop = true; - stopmutex.Unlock(); - - if (transmitter) - transmitter->AbortWait(); - - RTPTime thetime = RTPTime::CurrentTime(); - bool done = false; - - while (JThread::IsRunning() && !done) - { - // wait max 5 sec - RTPTime curtime = RTPTime::CurrentTime(); - if ((curtime.GetDouble()-thetime.GetDouble()) > 5.0) - done = true; - RTPTime::Wait(RTPTime(0,10000)); - } - - if (JThread::IsRunning()) - { - std::cerr << "RTPPollThread: Warning! Having to kill thread!" << std::endl; - JThread::Kill(); - } - stop = false; - transmitter = 0; -} - -void *RTPPollThread::Thread() -{ - JThread::ThreadStarted(); - - bool stopthread; - - stopmutex.Lock(); - stopthread = stop; - stopmutex.Unlock(); - - rtpsession.OnPollThreadStart(stopthread); - - while (!stopthread) - { - int status; - - rtpsession.schedmutex.Lock(); - rtpsession.sourcesmutex.Lock(); - - RTPTime rtcpdelay = rtcpsched.GetTransmissionDelay(); - - rtpsession.sourcesmutex.Unlock(); - rtpsession.schedmutex.Unlock(); - - if ((status = transmitter->WaitForIncomingData(rtcpdelay)) < 0) - { - stopthread = true; - rtpsession.OnPollThreadError(status); - } - else - { - if ((status = transmitter->Poll()) < 0) - { - stopthread = true; - rtpsession.OnPollThreadError(status); - } - else - { - if ((status = rtpsession.ProcessPolledData()) < 0) - { - stopthread = true; - rtpsession.OnPollThreadError(status); - } - else - { - rtpsession.OnPollThreadStep(); - stopmutex.Lock(); - stopthread = stop; - stopmutex.Unlock(); - } - } - } - } - - rtpsession.OnPollThreadStop(); - - return 0; -} - -} // end namespace - -#endif // RTP_SUPPORT_THREAD - diff --git a/qrtplib/rtppollthread.h b/qrtplib/rtppollthread.h deleted file mode 100644 index 3864e15dd..000000000 --- a/qrtplib/rtppollthread.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtppollthread.h - */ - -#ifndef RTPPOLLTHREAD_H - -#define RTPPOLLTHREAD_H - -#include "rtpconfig.h" - -#ifdef RTP_SUPPORT_THREAD - -#include "rtptransmitter.h" - -#include -#include -#include - -namespace qrtplib -{ - -class RTPSession; -class RTCPScheduler; - -class JRTPLIB_IMPORTEXPORT RTPPollThread : private jthread::JThread -{ -public: - RTPPollThread(RTPSession &session,RTCPScheduler &rtcpsched); - ~RTPPollThread(); - int Start(RTPTransmitter *trans); - void Stop(); -private: - void *Thread(); - - bool stop; - jthread::JMutex stopmutex; - RTPTransmitter *transmitter; - - RTPSession &rtpsession; - RTCPScheduler &rtcpsched; -}; - -} // end namespace - -#endif // RTP_SUPPORT_THREAD - -#endif // RTPPOLLTHREAD_H diff --git a/qrtplib/rtprandomrand48.cpp b/qrtplib/rtprandomrand48.cpp index 0def976a2..43795718e 100644 --- a/qrtplib/rtprandomrand48.cpp +++ b/qrtplib/rtprandomrand48.cpp @@ -51,9 +51,6 @@ RTPRandomRand48::~RTPRandomRand48() void RTPRandomRand48::SetSeed(uint32_t seed) { -#ifdef RTP_SUPPORT_THREAD - mutex.Init(); // TODO: check error! -#endif // RTP_SUPPORT_THREAD #ifdef RTP_HAVE_VSUINT64SUFFIX state = ((uint64_t)seed) << 16 | 0x330Eui64; @@ -78,9 +75,6 @@ uint16_t RTPRandomRand48::GetRandom16() uint32_t RTPRandomRand48::GetRandom32() { -#ifdef RTP_SUPPORT_THREAD - mutex.Lock(); -#endif // RTP_SUPPORT_THREAD #ifdef RTP_HAVE_VSUINT64SUFFIX state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; @@ -92,17 +86,11 @@ uint32_t RTPRandomRand48::GetRandom32() uint32_t x = (uint32_t)((state>>16)&0xffffffffULL); #endif // RTP_HAVE_VSUINT64SUFFIX -#ifdef RTP_SUPPORT_THREAD - mutex.Unlock(); -#endif // RTP_SUPPORT_THREAD return x; } double RTPRandomRand48::GetRandomDouble() { -#ifdef RTP_SUPPORT_THREAD - mutex.Lock(); -#endif // RTP_SUPPORT_THREAD #ifdef RTP_HAVE_VSUINT64SUFFIX state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; @@ -114,9 +102,6 @@ double RTPRandomRand48::GetRandomDouble() int64_t x = (int64_t)state; #endif // RTP_HAVE_VSUINT64SUFFIX -#ifdef RTP_SUPPORT_THREAD - mutex.Unlock(); -#endif // RTP_SUPPORT_THREAD double y = 3.552713678800500929355621337890625e-15 * (double)x; return y; } diff --git a/qrtplib/rtprandomrand48.h b/qrtplib/rtprandomrand48.h index ef9c61790..36e9a83bc 100644 --- a/qrtplib/rtprandomrand48.h +++ b/qrtplib/rtprandomrand48.h @@ -40,9 +40,6 @@ #include "rtpconfig.h" #include "rtprandom.h" -#ifdef RTP_SUPPORT_THREAD - #include -#endif // RTP_SUPPORT_THREAD #include namespace qrtplib @@ -63,9 +60,6 @@ public: private: void SetSeed(uint32_t seed); -#ifdef RTP_SUPPORT_THREAD - jthread::JMutex mutex; -#endif // RTP_SUPPORT_THREAD uint64_t state; }; diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index f5ee7bee2..d5dabde2b 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -32,7 +32,6 @@ #include "rtpsession.h" #include "rtperrors.h" -#include "rtppollthread.h" #include "rtpudpv4transmitter.h" #include "rtpudpv6transmitter.h" #include "rtptcptransmitter.h" @@ -58,25 +57,14 @@ #endif // WIN32 -#ifdef RTP_SUPPORT_THREAD - #define SOURCES_LOCK { if (needthreadsafety) sourcesmutex.Lock(); } - #define SOURCES_UNLOCK { if (needthreadsafety) sourcesmutex.Unlock(); } - #define BUILDER_LOCK { if (needthreadsafety) buildermutex.Lock(); } - #define BUILDER_UNLOCK { if (needthreadsafety) buildermutex.Unlock(); } - #define SCHED_LOCK { if (needthreadsafety) schedmutex.Lock(); } - #define SCHED_UNLOCK { if (needthreadsafety) schedmutex.Unlock(); } - #define PACKSENT_LOCK { if (needthreadsafety) packsentmutex.Lock(); } - #define PACKSENT_UNLOCK { if (needthreadsafety) packsentmutex.Unlock(); } -#else - #define SOURCES_LOCK - #define SOURCES_UNLOCK - #define BUILDER_LOCK - #define BUILDER_UNLOCK - #define SCHED_LOCK - #define SCHED_UNLOCK - #define PACKSENT_LOCK - #define PACKSENT_UNLOCK -#endif // RTP_SUPPORT_THREAD +#define SOURCES_LOCK +#define SOURCES_UNLOCK +#define BUILDER_LOCK +#define BUILDER_UNLOCK +#define SCHED_LOCK +#define SCHED_UNLOCK +#define PACKSENT_LOCK +#define PACKSENT_UNLOCK namespace qrtplib { @@ -330,82 +318,6 @@ int RTPSession::InternalCreate(const RTPSessionParams &sessparams) // Do thread stuff if necessary -#ifdef RTP_SUPPORT_THREAD - pollthread = 0; - if (usingpollthread) - { - if (!sourcesmutex.IsInitialized()) - { - if (sourcesmutex.Init() < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return ERR_RTP_SESSION_CANTINITMUTEX; - } - } - if (!buildermutex.IsInitialized()) - { - if (buildermutex.Init() < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return ERR_RTP_SESSION_CANTINITMUTEX; - } - } - if (!schedmutex.IsInitialized()) - { - if (schedmutex.Init() < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return ERR_RTP_SESSION_CANTINITMUTEX; - } - } - if (!packsentmutex.IsInitialized()) - { - if (packsentmutex.Init() < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return ERR_RTP_SESSION_CANTINITMUTEX; - } - } - - pollthread = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPPOLLTHREAD) RTPPollThread(*this,rtcpsched); - if (pollthread == 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return ERR_RTP_OUTOFMEM; - } - if ((status = pollthread->Start(rtptrans)) < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - RTPDelete(pollthread,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return status; - } - } -#endif // RTP_SUPPORT_THREAD - created = true; return 0; } @@ -416,11 +328,6 @@ void RTPSession::Destroy() if (!created) return; -#ifdef RTP_SUPPORT_THREAD - if (pollthread) - RTPDelete(pollthread,GetMemoryManager()); -#endif // RTP_SUPPORT_THREAD - if (deletetransmitter) RTPDelete(rtptrans,GetMemoryManager()); packetbuilder.Destroy(); @@ -445,11 +352,6 @@ void RTPSession::BYEDestroy(const RTPTime &maxwaittime,const void *reason,size_t // first, stop the thread so we have full control over all components -#ifdef RTP_SUPPORT_THREAD - if (pollthread) - RTPDelete(pollthread,GetMemoryManager()); -#endif // RTP_SUPPORT_THREAD - RTPTime stoptime = RTPTime::CurrentTime(); stoptime += maxwaittime; diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index a60b2241e..7766003e0 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -51,10 +51,6 @@ #include "rtpmemoryobject.h" #include -#ifdef RTP_SUPPORT_THREAD - #include -#endif // RTP_SUPPORT_THREAD - namespace qrtplib { @@ -532,28 +528,6 @@ protected: /** Is called when an RTCP compound packet has just been sent (useful to inspect outgoing RTCP data). */ virtual void OnSendRTCPCompoundPacket(RTCPCompoundPacket *pack); -#ifdef RTP_SUPPORT_THREAD - /** Is called when error \c errcode was detected in the poll thread. */ - virtual void OnPollThreadError(int errcode); - - /** Is called each time the poll thread loops. - * Is called each time the poll thread loops. This happens when incoming data was - * detected or when it's time to send an RTCP compound packet. - */ - virtual void OnPollThreadStep(); - - /** Is called when the poll thread is started. - * Is called when the poll thread is started. This happens just before entering the - * thread main loop. - * \param stop This can be used to stop the thread immediately without entering the loop. - */ - virtual void OnPollThreadStart(bool &stop); - - /** Is called when the poll thread is going to stop. - * Is called when the poll thread is going to stop. This happens just before termitating the thread. - */ - virtual void OnPollThreadStop(); -#endif // RTP_SUPPORT_THREAD /** If this is set to true, outgoing data will be passed through RTPSession::OnChangeRTPOrRTCPData * and RTPSession::OnSentRTPOrRTCPData, allowing you to modify the data (e.g. to encrypt it). */ @@ -636,12 +610,6 @@ private: std::list byepackets; -#ifdef RTP_SUPPORT_THREAD - RTPPollThread *pollthread; - jthread::JMutex sourcesmutex,buildermutex,schedmutex,packsentmutex; - - friend class RTPPollThread; -#endif // RTP_SUPPORT_THREAD friend class RTPSessionSources; friend class RTCPSessionPacketBuilder; }; @@ -670,13 +638,6 @@ inline void RTPSession::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, siz inline void RTPSession::OnBYEPacket(RTPSourceData *) { } inline void RTPSession::OnSendRTCPCompoundPacket(RTCPCompoundPacket *) { } -#ifdef RTP_SUPPORT_THREAD -inline void RTPSession::OnPollThreadError(int) { } -inline void RTPSession::OnPollThreadStep() { } -inline void RTPSession::OnPollThreadStart(bool &) { } -inline void RTPSession::OnPollThreadStop() { } -#endif // RTP_SUPPORT_THREAD - inline int RTPSession::OnChangeRTPOrRTCPData(const void *, size_t, bool, void **, size_t *) { return ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED; } diff --git a/qrtplib/rtpsessionparams.cpp b/qrtplib/rtpsessionparams.cpp index fac2f8e7c..d8cb21da3 100644 --- a/qrtplib/rtpsessionparams.cpp +++ b/qrtplib/rtpsessionparams.cpp @@ -40,13 +40,8 @@ namespace qrtplib RTPSessionParams::RTPSessionParams() : mininterval(0,0) { -#ifdef RTP_SUPPORT_THREAD - usepollthread = true; - m_needThreadSafety = true; -#else usepollthread = false; m_needThreadSafety = false; -#endif // RTP_SUPPORT_THREAD maxpacksize = RTP_DEFAULTPACKETSIZE; receivemode = RTPTransmitter::AcceptAll; acceptown = false; @@ -76,24 +71,14 @@ RTPSessionParams::RTPSessionParams() : mininterval(0,0) int RTPSessionParams::SetUsePollThread(bool usethread) { -#ifndef RTP_SUPPORT_THREAD JRTPLIB_UNUSED(usethread); return ERR_RTP_NOTHREADSUPPORT; -#else - usepollthread = usethread; - return 0; -#endif // RTP_SUPPORT_THREAD } int RTPSessionParams::SetNeedThreadSafety(bool s) { -#ifndef RTP_SUPPORT_THREAD JRTPLIB_UNUSED(s); return ERR_RTP_NOTHREADSUPPORT; -#else - m_needThreadSafety = s; - return 0; -#endif // RTP_SUPPORT_THREAD } } // end namespace diff --git a/qrtplib/rtptcptransmitter.cpp b/qrtplib/rtptcptransmitter.cpp index cc256558c..9bb3f33ee 100644 --- a/qrtplib/rtptcptransmitter.cpp +++ b/qrtplib/rtptcptransmitter.cpp @@ -49,17 +49,10 @@ using namespace std; #define RTPTCPTRANS_MAXPACKSIZE 65535 -#ifdef RTP_SUPPORT_THREAD - #define MAINMUTEX_LOCK { if (m_threadsafe) m_mainMutex.Lock(); } - #define MAINMUTEX_UNLOCK { if (m_threadsafe) m_mainMutex.Unlock(); } - #define WAITMUTEX_LOCK { if (m_threadsafe) m_waitMutex.Lock(); } - #define WAITMUTEX_UNLOCK { if (m_threadsafe) m_waitMutex.Unlock(); } -#else - #define MAINMUTEX_LOCK - #define MAINMUTEX_UNLOCK - #define WAITMUTEX_LOCK - #define WAITMUTEX_UNLOCK -#endif // RTP_SUPPORT_THREAD +#define MAINMUTEX_LOCK +#define MAINMUTEX_UNLOCK +#define WAITMUTEX_LOCK +#define WAITMUTEX_UNLOCK namespace qrtplib { @@ -80,23 +73,8 @@ int RTPTCPTransmitter::Init(bool tsafe) if (m_init) return ERR_RTP_TCPTRANS_ALREADYINIT; -#ifdef RTP_SUPPORT_THREAD - m_threadsafe = tsafe; - if (m_threadsafe) - { - int status; - - status = m_mainMutex.Init(); - if (status < 0) - return ERR_RTP_TCPTRANS_CANTINITMUTEX; - status = m_waitMutex.Init(); - if (status < 0) - return ERR_RTP_TCPTRANS_CANTINITMUTEX; - } -#else if (tsafe) return ERR_RTP_NOTHREADSUPPORT; -#endif // RTP_SUPPORT_THREAD m_maxPackSize = RTPTCPTRANS_MAXPACKSIZE; m_init = true; diff --git a/qrtplib/rtptcptransmitter.h b/qrtplib/rtptcptransmitter.h index ad1b0662f..092ab37d2 100644 --- a/qrtplib/rtptcptransmitter.h +++ b/qrtplib/rtptcptransmitter.h @@ -192,10 +192,6 @@ private: RTPAbortDescriptors m_abortDesc; RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified -#ifdef RTP_SUPPORT_THREAD - jthread::JMutex m_mainMutex, m_waitMutex; - bool m_threadsafe; -#endif // RTP_SUPPORT_THREAD }; inline void RTPTCPTransmitter::OnSendError(SocketType) { } diff --git a/qrtplib/rtpudpv4transmitter.cpp b/qrtplib/rtpudpv4transmitter.cpp index c4bbbd8a2..f2eae3e01 100644 --- a/qrtplib/rtpudpv4transmitter.cpp +++ b/qrtplib/rtpudpv4transmitter.cpp @@ -59,17 +59,10 @@ using namespace std; mreq.imr_interface.s_addr = htonl(mcastifaceIP);\ status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ } -#ifdef RTP_SUPPORT_THREAD - #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } - #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } - #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } - #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } -#else - #define MAINMUTEX_LOCK - #define MAINMUTEX_UNLOCK - #define WAITMUTEX_LOCK - #define WAITMUTEX_UNLOCK -#endif // RTP_SUPPORT_THREAD +#define MAINMUTEX_LOCK +#define MAINMUTEX_UNLOCK +#define WAITMUTEX_LOCK +#define WAITMUTEX_UNLOCK #define CLOSESOCKETS do { \ if (closesocketswhendone) \ @@ -104,23 +97,8 @@ int RTPUDPv4Transmitter::Init(bool tsafe) if (init) return ERR_RTP_UDPV4TRANS_ALREADYINIT; -#ifdef RTP_SUPPORT_THREAD - threadsafe = tsafe; - if (threadsafe) - { - int status; - - status = mainmutex.Init(); - if (status < 0) - return ERR_RTP_UDPV4TRANS_CANTINITMUTEX; - status = waitmutex.Init(); - if (status < 0) - return ERR_RTP_UDPV4TRANS_CANTINITMUTEX; - } -#else if (tsafe) return ERR_RTP_NOTHREADSUPPORT; -#endif // RTP_SUPPORT_THREAD init = true; return 0; diff --git a/qrtplib/rtpudpv4transmitter.h b/qrtplib/rtpudpv4transmitter.h index 32f6c103d..cc7b0a542 100644 --- a/qrtplib/rtpudpv4transmitter.h +++ b/qrtplib/rtpudpv4transmitter.h @@ -47,10 +47,6 @@ #include "rtpabortdescriptors.h" #include -#ifdef RTP_SUPPORT_THREAD - #include -#endif // RTP_SUPPORT_THREAD - #define RTPUDPV4TRANS_HASHSIZE 8317 #define RTPUDPV4TRANS_DEFAULTPORTBASE 5000 @@ -355,10 +351,6 @@ private: RTPAbortDescriptors m_abortDesc; RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified -#ifdef RTP_SUPPORT_THREAD - jthread::JMutex mainmutex,waitmutex; - int threadsafe; -#endif // RTP_SUPPORT_THREAD }; } // end namespace diff --git a/qrtplib/rtpudpv4transmitternobind.cpp b/qrtplib/rtpudpv4transmitternobind.cpp index d9632ada9..ae4d9e48d 100644 --- a/qrtplib/rtpudpv4transmitternobind.cpp +++ b/qrtplib/rtpudpv4transmitternobind.cpp @@ -59,17 +59,10 @@ using namespace std; mreq.imr_interface.s_addr = htonl(mcastifaceIP);\ status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ } -#ifdef RTP_SUPPORT_THREAD - #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } - #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } - #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } - #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } -#else - #define MAINMUTEX_LOCK - #define MAINMUTEX_UNLOCK - #define WAITMUTEX_LOCK - #define WAITMUTEX_UNLOCK -#endif // RTP_SUPPORT_THREAD +#define MAINMUTEX_LOCK +#define MAINMUTEX_UNLOCK +#define WAITMUTEX_LOCK +#define WAITMUTEX_UNLOCK #define CLOSESOCKETS do { \ if (closesocketswhendone) \ @@ -120,23 +113,8 @@ int RTPUDPv4TransmitterNoBind::Init(bool tsafe) if (init) return ERR_RTP_UDPV4TRANS_ALREADYINIT; -#ifdef RTP_SUPPORT_THREAD - threadsafe = tsafe; - if (threadsafe) - { - int status; - - status = mainmutex.Init(); - if (status < 0) - return ERR_RTP_UDPV4TRANS_CANTINITMUTEX; - status = waitmutex.Init(); - if (status < 0) - return ERR_RTP_UDPV4TRANS_CANTINITMUTEX; - } -#else if (tsafe) return ERR_RTP_NOTHREADSUPPORT; -#endif // RTP_SUPPORT_THREAD init = true; return 0; diff --git a/qrtplib/rtpudpv4transmitternobind.h b/qrtplib/rtpudpv4transmitternobind.h index 23aaa2918..1c3d425d6 100644 --- a/qrtplib/rtpudpv4transmitternobind.h +++ b/qrtplib/rtpudpv4transmitternobind.h @@ -47,10 +47,6 @@ #include "rtpabortdescriptors.h" #include -#ifdef RTP_SUPPORT_THREAD - #include -#endif // RTP_SUPPORT_THREAD - #define RTPUDPV4TRANSNOBIND_HASHSIZE 8317 #define RTPUDPV4TRANSNOBIND_DEFAULTPORTBASE 5000 @@ -359,10 +355,6 @@ private: RTPAbortDescriptors m_abortDesc; RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified -#ifdef RTP_SUPPORT_THREAD - jthread::JMutex mainmutex,waitmutex; - int threadsafe; -#endif // RTP_SUPPORT_THREAD }; } // end namespace diff --git a/qrtplib/rtpudpv6transmitter.cpp b/qrtplib/rtpudpv6transmitter.cpp index 848589739..3caccfc3b 100644 --- a/qrtplib/rtpudpv6transmitter.cpp +++ b/qrtplib/rtpudpv6transmitter.cpp @@ -60,17 +60,10 @@ mreq.ipv6mr_interface = mcastifidx;\ status = setsockopt(socket,IPPROTO_IPV6,type,(const char *)&mreq,sizeof(struct ipv6_mreq));\ } -#ifdef RTP_SUPPORT_THREAD - #define MAINMUTEX_LOCK { if (threadsafe) mainmutex.Lock(); } - #define MAINMUTEX_UNLOCK { if (threadsafe) mainmutex.Unlock(); } - #define WAITMUTEX_LOCK { if (threadsafe) waitmutex.Lock(); } - #define WAITMUTEX_UNLOCK { if (threadsafe) waitmutex.Unlock(); } -#else - #define MAINMUTEX_LOCK - #define MAINMUTEX_UNLOCK - #define WAITMUTEX_LOCK - #define WAITMUTEX_UNLOCK -#endif // RTP_SUPPORT_THREAD +#define MAINMUTEX_LOCK +#define MAINMUTEX_UNLOCK +#define WAITMUTEX_LOCK +#define WAITMUTEX_UNLOCK inline bool operator==(const in6_addr &ip1,const in6_addr &ip2) { @@ -101,23 +94,8 @@ int RTPUDPv6Transmitter::Init(bool tsafe) if (init) return ERR_RTP_UDPV6TRANS_ALREADYINIT; -#ifdef RTP_SUPPORT_THREAD - threadsafe = tsafe; - if (threadsafe) - { - int status; - - status = mainmutex.Init(); - if (status < 0) - return ERR_RTP_UDPV6TRANS_CANTINITMUTEX; - status = waitmutex.Init(); - if (status < 0) - return ERR_RTP_UDPV6TRANS_CANTINITMUTEX; - } -#else if (tsafe) return ERR_RTP_NOTHREADSUPPORT; -#endif // RTP_SUPPORT_THREAD init = true; return 0; diff --git a/qrtplib/rtpudpv6transmitter.h b/qrtplib/rtpudpv6transmitter.h index 2a582035c..16fe6f9ef 100644 --- a/qrtplib/rtpudpv6transmitter.h +++ b/qrtplib/rtpudpv6transmitter.h @@ -51,10 +51,6 @@ #include #include -#ifdef RTP_SUPPORT_THREAD - #include -#endif // RTP_SUPPORT_THREAD - #define RTPUDPV6TRANS_HASHSIZE 8317 #define RTPUDPV6TRANS_DEFAULTPORTBASE 5000 @@ -316,10 +312,6 @@ private: RTPAbortDescriptors m_abortDesc; RTPAbortDescriptors *m_pAbortDesc; -#ifdef RTP_SUPPORT_THREAD - jthread::JMutex mainmutex,waitmutex; - int threadsafe; -#endif // RTP_SUPPORT_THREAD }; } // end namespace From ced185c2ff1dac8ed1b8aae5791286590e47cc26 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 01:45:48 +0100 Subject: [PATCH 015/956] qrtplib: removed secured session --- qrtplib/CMakeLists.txt | 2 - qrtplib/rtpsecuresession.cpp | 283 ----------------------------------- qrtplib/rtpsecuresession.h | 132 ---------------- 3 files changed, 417 deletions(-) delete mode 100644 qrtplib/rtpsecuresession.cpp delete mode 100644 qrtplib/rtpsecuresession.h diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 60ff3cf98..270f7107f 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -50,7 +50,6 @@ set (qrtplib_HEADERS rtpudpv6transmitter.h rtpbyteaddress.h rtpexternaltransmitter.h - rtpsecuresession.h rtpsocketutil.h rtpabortdescriptors.h rtpselect.h @@ -94,7 +93,6 @@ set(qrtplib_SOURCES rtpudpv6transmitter.cpp rtpbyteaddress.cpp rtpexternaltransmitter.cpp - rtpsecuresession.cpp rtpabortdescriptors.cpp rtptcpaddress.cpp rtptcptransmitter.cpp diff --git a/qrtplib/rtpsecuresession.cpp b/qrtplib/rtpsecuresession.cpp deleted file mode 100644 index b2e2228eb..000000000 --- a/qrtplib/rtpsecuresession.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "rtpsecuresession.h" - -#ifdef RTP_SUPPORT_SRTP - -#include "rtprawpacket.h" -#include -#include -#include -#include - -using namespace std; -using namespace jthread; - -namespace qrtplib -{ - -// SRTP library needs to be initialized already! - -RTPSecureSession::RTPSecureSession(RTPRandom *rnd, RTPMemoryManager *mgr) : RTPSession(rnd, mgr) -{ - // Make sure the OnChange... functions will be called - SetChangeIncomingData(true); - SetChangeOutgoingData(true); - m_pSRTPContext = 0; - m_lastSRTPError = 0; -} - -RTPSecureSession::~RTPSecureSession() -{ - if (m_pSRTPContext) - srtp_dealloc(m_pSRTPContext); -} - -int RTPSecureSession::InitializeSRTPContext() -{ -#ifdef RTP_SUPPORT_THREAD - if (!m_srtpLock.IsInitialized()) - { - if (m_srtpLock.Init() < 0) - return ERR_RTP_SECURESESSION_CANTINITMUTEX; - } - - JMutexAutoLock l(m_srtpLock); -#endif // RTP_SUPPORT_THREAD - - if (m_pSRTPContext) - return ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED; - - err_status_t result = srtp_create(&m_pSRTPContext, NULL); - if (result != err_status_ok) - { - m_pSRTPContext = 0; - m_lastSRTPError = (int)result; - return ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT; - } - - return 0; -} - -int RTPSecureSession::GetLastLibSRTPError() -{ -#ifdef RTP_SUPPORT_THREAD - JMutexAutoLock l(m_srtpLock); -#endif // RTP_SUPPORT_THREAD - - int err = m_lastSRTPError; - m_lastSRTPError = 0; // clear it - - return err; -} - -void RTPSecureSession::SetLastLibSRTPError(int err) -{ -#ifdef RTP_SUPPORT_THREAD - JMutexAutoLock l(m_srtpLock); -#endif // RTP_SUPPORT_THREAD - - m_lastSRTPError = err; -} - -srtp_ctx_t *RTPSecureSession::LockSRTPContext() -{ -#ifdef RTP_SUPPORT_THREAD - m_srtpLock.Lock(); -#endif // RTP_SUPPORT_THREAD - - if (!m_pSRTPContext) - { -#ifdef RTP_SUPPORT_THREAD - m_srtpLock.Unlock(); // Make sure the mutex is not locked on error -#endif // RTP_SUPPORT_THREAD - return 0; - } - - return m_pSRTPContext; -} - -int RTPSecureSession::UnlockSRTPContext() -{ - if (!m_pSRTPContext) - return ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED; - -#ifdef RTP_SUPPORT_THREAD - m_srtpLock.Unlock(); -#endif // RTP_SUPPORT_THREAD - return 0; -} - -int RTPSecureSession::encryptData(uint8_t *pData, int &dataLength, bool rtp) -{ - int length = dataLength; - - if (rtp) - { - if (length < (int)sizeof(uint32_t)*3) - return ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT ; - - err_status_t result = srtp_protect(m_pSRTPContext, (void *)pData, &length); - if (result != err_status_ok) - { - m_lastSRTPError = (int)result; - return ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA; - } - } - else // rtcp - { - if (length < (int)sizeof(uint32_t)*2) - return ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT; - - err_status_t result = srtp_protect_rtcp(m_pSRTPContext, (void *)pData, &length); - if (result != err_status_ok) - { - m_lastSRTPError = (int)result; - return ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA; - } - } - - dataLength = length; - - return 0; -} - -int RTPSecureSession::OnChangeRTPOrRTCPData(const void *origdata, size_t origlen, bool isrtp, void **senddata, size_t *sendlen) -{ - *senddata = 0; - - if (!origdata || origlen == 0) // Nothing to do in this case, packet can be ignored - return 0; - - srtp_ctx_t *pCtx = LockSRTPContext(); - if (pCtx == 0) - return ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED; - - // Need to add some extra bytes, and we'll add a few more to be really safe - uint8_t *pDataCopy = RTPNew(GetMemoryManager(), RTPMEM_TYPE_BUFFER_SRTPDATA) uint8_t [origlen + SRTP_MAX_TRAILER_LEN + 32]; - memcpy(pDataCopy, origdata, origlen); - - int status = 0; - int dataLength = (int)origlen; - - if ((status = encryptData(pDataCopy, dataLength, isrtp)) < 0) - { - UnlockSRTPContext(); - RTPDeleteByteArray(pDataCopy, GetMemoryManager()); - return status; - } - - UnlockSRTPContext(); - - *senddata = pDataCopy; - *sendlen = dataLength; - - return 0; -} - -int RTPSecureSession::decryptRawPacket(RTPRawPacket *rawpack, int *srtpError) -{ - *srtpError = 0; - - uint8_t *pData = rawpack->GetData(); - int dataLength = (int)rawpack->GetDataLength(); - - if (rawpack->IsRTP()) - { - if (dataLength < (int)sizeof(uint32_t)*3) - return ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT; - - err_status_t result = srtp_unprotect(m_pSRTPContext, (void*)pData, &dataLength); - if (result != err_status_ok) - { - *srtpError = result; - return ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA; - } - } - else // RTCP - { - if (dataLength < (int)sizeof(uint32_t)*2) - return ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT; - - err_status_t result = srtp_unprotect_rtcp(m_pSRTPContext, (void *)pData, &dataLength); - if (result != err_status_ok) - { - *srtpError = result; - return ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA; - } - } - - rawpack->ZeroData(); // make sure we don't delete the data we're going to store - rawpack->SetData(pData, (size_t)dataLength); - - return 0; -} - -bool RTPSecureSession::OnChangeIncomingData(RTPRawPacket *rawpack) -{ - if (!rawpack) - return false; - - srtp_ctx_t *pCtx = LockSRTPContext(); - if (pCtx == 0) - { - OnErrorChangeIncomingData(ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED, 0); - return false; - } - - int srtpErr = 0; - int status = decryptRawPacket(rawpack, &srtpErr); - UnlockSRTPContext(); - - if (status < 0) - { - OnErrorChangeIncomingData(status, srtpErr); - return false; - } - - return true; -} - -void RTPSecureSession::OnSentRTPOrRTCPData(void *senddata, size_t sendlen, bool isrtp) -{ - JRTPLIB_UNUSED(sendlen); - JRTPLIB_UNUSED(isrtp); - - if (senddata) - RTPDeleteByteArray((uint8_t *)senddata, GetMemoryManager()); -} - -} // end namespace - -#endif // RTP_SUPPORT_SRTP - diff --git a/qrtplib/rtpsecuresession.h b/qrtplib/rtpsecuresession.h deleted file mode 100644 index bdfb8532e..000000000 --- a/qrtplib/rtpsecuresession.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtpsecuresession.h - */ - -#ifndef RTPSECURESESSION_H - -#define RTPSECURESESSION_H - -#include "rtpconfig.h" - -#ifdef RTP_SUPPORT_SRTP - -#include "rtpsession.h" - -#ifdef RTP_SUPPORT_THREAD - #include -#endif // RTP_SUPPORT_THREAD - -struct srtp_ctx_t; - -namespace qrtplib -{ - -class RTPCrypt; - -// SRTP library needs to be initialized already! - -/** RTPSession derived class that serves as a base class for an SRTP implementation. - * - * This is an RTPSession derived class that serves as a base class for an SRTP implementation. - * The class sets the RTPSession::SetChangeIncomingData and RTPSession::SetChangeOutgoingData - * flags, and implements RTPSession::OnChangeIncomingData, RTPSession::OnChangeRTPOrRTCPData - * and RTPSession::OnSentRTPOrRTCPData so that encryption and decryption is applied to packets. - * The encryption and decryption will be done using [libsrtp](https://github.com/cisco/libsrtp), - * which must be available at compile time. - * - * Your derived class should call RTPSecureSession::InitializeSRTPContext to initialize a context - * struct of `libsrtp`. When this succeeds, the context can be obtained and used with the - * RTPSecureSession::LockSRTPContext function, which also locks a mutex if thread support was - * available. After you're done using the context yourself (to set encryption parameters for - * SSRCs), you **must** release it again using RTPSecureSession::UnlockSRTPContext. - * - * See `example7.cpp` for an example of how to use this class. - */ -class JRTPLIB_IMPORTEXPORT RTPSecureSession : public RTPSession -{ -public: - /** Constructs an RTPSecureSession instance, see RTPSession::RTPSession - * for more information about the parameters. */ - RTPSecureSession(RTPRandom *rnd = 0, RTPMemoryManager *mgr = 0); - ~RTPSecureSession(); -protected: - /** Initializes the SRTP context, in case of an error it may be useful to inspect - * RTPSecureSession::GetLastLibSRTPError. */ - int InitializeSRTPContext(); - - /** This function locks a mutex and returns the `libsrtp` context that was - * created in RTPSecureSession::InitializeSRTPContext, so that you can further - * use it to specify encryption parameters for various sources; note that you - * **must** release the context again after use with the - * RTPSecureSession::UnlockSRTPContext function. */ - srtp_ctx_t *LockSRTPContext(); - - /** Releases the lock on the SRTP context that was obtained in - * RTPSecureSession::LockSRTPContext. */ - int UnlockSRTPContext(); - - /** Returns (and clears) the last error that was encountered when using a - * `libsrtp` based function. */ - int GetLastLibSRTPError(); - - void SetLastLibSRTPError(int err); - - /** In case the reimplementation of OnChangeIncomingData (which may take place - * in a background thread) encounters an error, this member function will be - * called; implement it in a derived class to receive notification of this. */ - virtual void OnErrorChangeIncomingData(int errcode, int libsrtperrorcode); - - int OnChangeRTPOrRTCPData(const void *origdata, size_t origlen, bool isrtp, void **senddata, size_t *sendlen); - bool OnChangeIncomingData(RTPRawPacket *rawpack); - void OnSentRTPOrRTCPData(void *senddata, size_t sendlen, bool isrtp); -private: - int encryptData(uint8_t *pData, int &dataLength, bool rtp); - int decryptRawPacket(RTPRawPacket *rawpack, int *srtpError); - - srtp_ctx_t *m_pSRTPContext; - int m_lastSRTPError; -#ifdef RTP_SUPPORT_THREAD - jthread::JMutex m_srtpLock; -#endif // RTP_SUPPORT_THREAD -}; - -inline void RTPSecureSession::OnErrorChangeIncomingData(int, int) { } - -} // end namespace - -#endif // RTP_SUPPORT_SRTP - -#endif // RTPSECURESESSION_H - From c1c705b8bec1c1bcf35fd2ad74f4054be6d88372 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 02:14:45 +0100 Subject: [PATCH 016/956] qrtplib: removed IPV6 and netinet dependency --- qrtplib/CMakeLists.txt | 6 - qrtplib/rtcpapppacket.h | 7 +- qrtplib/rtcpbyepacket.h | 7 +- qrtplib/rtcpcompoundpacket.cpp | 5 +- qrtplib/rtcpcompoundpacket.h | 2 + qrtplib/rtcpcompoundpacketbuilder.cpp | 45 +- qrtplib/rtcpcompoundpacketbuilder.h | 2 + qrtplib/rtcprrpacket.h | 18 +- qrtplib/rtcpsdespacket.h | 7 +- qrtplib/rtcpsrpacket.h | 26 +- qrtplib/rtpconfig.h | 12 +- qrtplib/rtpipv6address.cpp | 91 -- qrtplib/rtpipv6address.h | 108 -- qrtplib/rtpipv6destination.cpp | 61 - qrtplib/rtpipv6destination.h | 91 -- qrtplib/rtppacket.cpp | 29 +- qrtplib/rtppacket.h | 2 + qrtplib/rtpsession.cpp | 6 - qrtplib/rtpsourcedata.cpp | 3 - qrtplib/rtpudpv6transmitter.cpp | 1621 ------------------------- qrtplib/rtpudpv6transmitter.h | 322 ----- 21 files changed, 78 insertions(+), 2393 deletions(-) delete mode 100644 qrtplib/rtpipv6address.cpp delete mode 100644 qrtplib/rtpipv6address.h delete mode 100644 qrtplib/rtpipv6destination.cpp delete mode 100644 qrtplib/rtpipv6destination.h delete mode 100644 qrtplib/rtpudpv6transmitter.cpp delete mode 100644 qrtplib/rtpudpv6transmitter.h diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 270f7107f..71382e15a 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -22,8 +22,6 @@ set (qrtplib_HEADERS rtpinternalsourcedata.h rtpipv4address.h rtpipv4destination.h - rtpipv6address.h - rtpipv6destination.h rtpkeyhashtable.h rtplibraryversion.h rtpmemorymanager.h @@ -47,7 +45,6 @@ set (qrtplib_HEADERS rtptypes.h rtpudpv4transmitter.h rtpudpv4transmitternobind.h - rtpudpv6transmitter.h rtpbyteaddress.h rtpexternaltransmitter.h rtpsocketutil.h @@ -72,9 +69,7 @@ set(qrtplib_SOURCES rtperrors.cpp rtpinternalsourcedata.cpp rtpipv4address.cpp - rtpipv6address.cpp rtpipv4destination.cpp - rtpipv6destination.cpp rtplibraryversion.cpp rtppacket.cpp rtppacketbuilder.cpp @@ -90,7 +85,6 @@ set(qrtplib_SOURCES rtptimeutilities.cpp rtpudpv4transmitter.cpp rtpudpv4transmitternobind.cpp - rtpudpv6transmitter.cpp rtpbyteaddress.cpp rtpexternaltransmitter.cpp rtpabortdescriptors.cpp diff --git a/qrtplib/rtcpapppacket.h b/qrtplib/rtcpapppacket.h index 59828d905..dacfb2265 100644 --- a/qrtplib/rtcpapppacket.h +++ b/qrtplib/rtcpapppacket.h @@ -41,9 +41,7 @@ #include "rtpconfig.h" #include "rtcppacket.h" #include "rtpstructs.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN +#include "rtpendian.h" namespace qrtplib { @@ -79,6 +77,7 @@ public: /** Returns the length of the actual data. */ size_t GetAPPDataLength() const; private: + RTPEndian m_endian; size_t appdatalen; }; @@ -96,7 +95,7 @@ inline uint32_t RTCPAPPPacket::GetSSRC() const return 0; uint32_t *ssrc = (uint32_t *)(data+sizeof(RTCPCommonHeader)); - return ntohl(*ssrc); + return m_endian.qToHost(*ssrc); } inline uint8_t *RTCPAPPPacket::GetName() diff --git a/qrtplib/rtcpbyepacket.h b/qrtplib/rtcpbyepacket.h index 765fb19c9..1da3676a3 100644 --- a/qrtplib/rtcpbyepacket.h +++ b/qrtplib/rtcpbyepacket.h @@ -41,9 +41,7 @@ #include "rtpconfig.h" #include "rtcppacket.h" #include "rtpstructs.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN +#include "rtpendian.h" namespace qrtplib { @@ -80,6 +78,7 @@ public: uint8_t *GetReasonData(); private: + RTPEndian m_endian; size_t reasonoffset; }; @@ -97,7 +96,7 @@ inline uint32_t RTCPBYEPacket::GetSSRC(int index) const if (!knownformat) return 0; uint32_t *ssrc = (uint32_t *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)*index); - return ntohl(*ssrc); + return m_endian.qToHost(*ssrc); } inline bool RTCPBYEPacket::HasReasonForLeaving() const diff --git a/qrtplib/rtcpcompoundpacket.cpp b/qrtplib/rtcpcompoundpacket.cpp index 21f43bbb5..a6456a1e9 100644 --- a/qrtplib/rtcpcompoundpacket.cpp +++ b/qrtplib/rtcpcompoundpacket.cpp @@ -41,9 +41,6 @@ #include "rtcpbyepacket.h" #include "rtcpapppacket.h" #include "rtcpunknownpacket.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN namespace qrtplib { @@ -132,7 +129,7 @@ int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen) } } - length = (size_t)ntohs(rtcphdr->length); + length = (size_t)m_endian.qToHost(rtcphdr->length); length++; length *= sizeof(uint32_t); diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h index 6041df7e4..3d9164d55 100644 --- a/qrtplib/rtcpcompoundpacket.h +++ b/qrtplib/rtcpcompoundpacket.h @@ -41,6 +41,7 @@ #include "rtpconfig.h" #include "rtptypes.h" #include "rtpmemoryobject.h" +#include "rtpendian.h" #include namespace qrtplib @@ -92,6 +93,7 @@ protected: void ClearPacketList(); int ParseData(uint8_t *packet, size_t len); + RTPEndian m_endian; int error; uint8_t *compoundpacket; diff --git a/qrtplib/rtcpcompoundpacketbuilder.cpp b/qrtplib/rtcpcompoundpacketbuilder.cpp index ce0c67f69..1a392e949 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.cpp +++ b/qrtplib/rtcpcompoundpacketbuilder.cpp @@ -39,9 +39,6 @@ #ifdef RTP_SUPPORT_RTCPUNKNOWN #include "rtcpunknownpacket.h" #endif // RTP_SUPPORT_RTCPUNKNOWN -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN #include namespace qrtplib @@ -175,14 +172,14 @@ int RTCPCompoundPacketBuilder::StartSenderReport(uint32_t senderssrc,const RTPNT report.isSR = true; uint32_t *ssrc = (uint32_t *)report.headerdata; - *ssrc = htonl(senderssrc); + *ssrc = qToBigEndian(senderssrc); RTCPSenderReport *sr = (RTCPSenderReport *)(report.headerdata + sizeof(uint32_t)); - sr->ntptime_msw = htonl(ntptimestamp.GetMSW()); - sr->ntptime_lsw = htonl(ntptimestamp.GetLSW()); - sr->rtptimestamp = htonl(rtptimestamp); - sr->packetcount = htonl(packetcount); - sr->octetcount = htonl(octetcount); + sr->ntptime_msw = qToBigEndian(ntptimestamp.GetMSW()); + sr->ntptime_lsw = qToBigEndian(ntptimestamp.GetLSW()); + sr->rtptimestamp = qToBigEndian(rtptimestamp); + sr->packetcount = qToBigEndian(packetcount); + sr->octetcount = qToBigEndian(octetcount); return 0; } @@ -211,7 +208,7 @@ int RTCPCompoundPacketBuilder::StartReceiverReport(uint32_t senderssrc) report.isSR = false; uint32_t *ssrc = (uint32_t *)report.headerdata; - *ssrc = htonl(senderssrc); + *ssrc = qToBigEndian(senderssrc); return 0; } @@ -242,15 +239,15 @@ int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc,uint8_t fractionlost uint32_t *packlost = (uint32_t *)&packetslost; uint32_t packlost2 = (*packlost); - rr->ssrc = htonl(ssrc); + rr->ssrc = qToBigEndian(ssrc); rr->fractionlost = fractionlost; rr->packetslost[2] = (uint8_t)(packlost2&0xFF); rr->packetslost[1] = (uint8_t)((packlost2>>8)&0xFF); rr->packetslost[0] = (uint8_t)((packlost2>>16)&0xFF); - rr->exthighseqnr = htonl(exthighestseq); - rr->jitter = htonl(jitter); - rr->lsr = htonl(lsr); - rr->dlsr = htonl(dlsr); + rr->exthighseqnr = qToBigEndian(exthighestseq); + rr->jitter = qToBigEndian(jitter); + rr->lsr = qToBigEndian(lsr); + rr->dlsr = qToBigEndian(dlsr); report.reportblocks.push_back(Buffer(buf,sizeof(RTCPReceiverReport))); return 0; @@ -437,14 +434,14 @@ int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs,uint8_t numssrcs,con hdr->count = numssrcs; numwords = packsize/sizeof(uint32_t); - hdr->length = htons((uint16_t)(numwords-1)); + hdr->length = qToBigEndian((uint16_t)(numwords-1)); hdr->packettype = RTP_RTCPTYPE_BYE; uint32_t *sources = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); uint8_t srcindex; for (srcindex = 0 ; srcindex < numssrcs ; srcindex++) - sources[srcindex] = htonl(ssrcs[srcindex]); + sources[srcindex] = qToBigEndian(ssrcs[srcindex]); if (reasonlength != 0) { @@ -498,11 +495,11 @@ int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype,uint32_t ssrc,const hdr->padding = 0; hdr->count = subtype; - hdr->length = htons((uint16_t)(appdatawords+2)); + hdr->length = qToBigEndian((uint16_t)(appdatawords+2)); hdr->packettype = RTP_RTCPTYPE_APP; uint32_t *source = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); - *source = htonl(ssrc); + *source = qToBigEndian(ssrc); buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+0] = name[0]; buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+1] = name[1]; @@ -545,11 +542,11 @@ int RTCPCompoundPacketBuilder::AddUnknownPacket(uint8_t payload_type, uint8_t su hdr->version = 2; hdr->padding = 0; hdr->count = subtype; - hdr->length = htons((uint16_t)(datawords+1)); + hdr->length = qToBigEndian((uint16_t)(datawords+1)); hdr->packettype = payload_type; uint32_t *source = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); - *source = htonl(ssrc); + *source = qToBigEndian(ssrc); if (len > 0) memcpy((buf+sizeof(RTCPCommonHeader)+sizeof(uint32_t)),data,len); @@ -630,7 +627,7 @@ int RTCPCompoundPacketBuilder::EndBuild() size_t numwords = offset/sizeof(uint32_t); - hdr->length = htons((uint16_t)(numwords-1)); + hdr->length = qToBigEndian((uint16_t)(numwords-1)); hdr->count = count; // add entry in parent's list @@ -674,7 +671,7 @@ int RTCPCompoundPacketBuilder::EndBuild() while (sourceit != sdes.sdessources.end() && sourcecount < 31) { uint32_t *ssrc = (uint32_t *)(curbuf+offset); - *ssrc = htonl((*sourceit)->ssrc); + *ssrc = qToBigEndian((*sourceit)->ssrc); offset += sizeof(uint32_t); std::list::const_iterator itemit,itemend; @@ -709,7 +706,7 @@ int RTCPCompoundPacketBuilder::EndBuild() size_t numwords = offset/4; hdr->count = sourcecount; - hdr->length = htons((uint16_t)(numwords-1)); + hdr->length = qToBigEndian((uint16_t)(numwords-1)); p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSDESPACKET) RTCPSDESPacket(curbuf,offset); if (p == 0) diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index 2c8f3a541..09caec6c5 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -43,6 +43,7 @@ #include "rtptimeutilities.h" #include "rtcpsdespacket.h" #include "rtperrors.h" +#include "rtpendian.h" #include namespace qrtplib @@ -372,6 +373,7 @@ private: std::list::const_iterator sdesit; }; + RTPEndian m_endian; size_t maximumpacketsize; uint8_t *buffer; bool external; diff --git a/qrtplib/rtcprrpacket.h b/qrtplib/rtcprrpacket.h index f779fe67f..9e380460e 100644 --- a/qrtplib/rtcprrpacket.h +++ b/qrtplib/rtcprrpacket.h @@ -41,9 +41,7 @@ #include "rtpconfig.h" #include "rtcppacket.h" #include "rtpstructs.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN +#include "rtpendian.h" namespace qrtplib { @@ -113,6 +111,8 @@ public: private: RTCPReceiverReport *GotoReport(int index) const; + + RTPEndian m_endian; }; inline uint32_t RTCPRRPacket::GetSenderSSRC() const @@ -121,7 +121,7 @@ inline uint32_t RTCPRRPacket::GetSenderSSRC() const return 0; uint32_t *ssrcptr = (uint32_t *)(data+sizeof(RTCPCommonHeader)); - return ntohl(*ssrcptr); + return m_endian.qToHost(*ssrcptr); } inline int RTCPRRPacket::GetReceptionReportCount() const { @@ -142,7 +142,7 @@ inline uint32_t RTCPRRPacket::GetSSRC(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->ssrc); + return m_endian.qToHost(r->ssrc); } inline uint8_t RTCPRRPacket::GetFractionLost(int index) const @@ -170,7 +170,7 @@ inline uint32_t RTCPRRPacket::GetExtendedHighestSequenceNumber(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->exthighseqnr); + return m_endian.qToHost(r->exthighseqnr); } inline uint32_t RTCPRRPacket::GetJitter(int index) const @@ -178,7 +178,7 @@ inline uint32_t RTCPRRPacket::GetJitter(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->jitter); + return m_endian.qToHost(r->jitter); } inline uint32_t RTCPRRPacket::GetLSR(int index) const @@ -186,7 +186,7 @@ inline uint32_t RTCPRRPacket::GetLSR(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->lsr); + return m_endian.qToHost(r->lsr); } inline uint32_t RTCPRRPacket::GetDLSR(int index) const @@ -194,7 +194,7 @@ inline uint32_t RTCPRRPacket::GetDLSR(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->dlsr); + return m_endian.qToHost(r->dlsr); } } // end namespace diff --git a/qrtplib/rtcpsdespacket.h b/qrtplib/rtcpsdespacket.h index 7d6eb01f0..874648fee 100644 --- a/qrtplib/rtcpsdespacket.h +++ b/qrtplib/rtcpsdespacket.h @@ -42,9 +42,7 @@ #include "rtcppacket.h" #include "rtpstructs.h" #include "rtpdefines.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN +#include "rtpendian.h" namespace qrtplib { @@ -143,6 +141,7 @@ public: #endif // RTP_SUPPORT_SDESPRIV private: + RTPEndian m_endian; uint8_t *currentchunk; int curchunknum; size_t itemoffset; @@ -203,7 +202,7 @@ inline uint32_t RTCPSDESPacket::GetChunkSSRC() const if (currentchunk == 0) return 0; uint32_t *ssrc = (uint32_t *)currentchunk; - return ntohl(*ssrc); + return m_endian.qToHost(*ssrc); } inline bool RTCPSDESPacket::GotoFirstItem() diff --git a/qrtplib/rtcpsrpacket.h b/qrtplib/rtcpsrpacket.h index 85af7280f..020b689f1 100644 --- a/qrtplib/rtcpsrpacket.h +++ b/qrtplib/rtcpsrpacket.h @@ -42,9 +42,7 @@ #include "rtcppacket.h" #include "rtptimeutilities.h" #include "rtpstructs.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN +#include "rtpendian.h" namespace qrtplib { @@ -125,6 +123,8 @@ public: private: RTCPReceiverReport *GotoReport(int index) const; + + RTPEndian m_endian; }; inline uint32_t RTCPSRPacket::GetSenderSSRC() const @@ -133,7 +133,7 @@ inline uint32_t RTCPSRPacket::GetSenderSSRC() const return 0; uint32_t *ssrcptr = (uint32_t *)(data+sizeof(RTCPCommonHeader)); - return ntohl(*ssrcptr); + return m_endian.qToHost(*ssrcptr); } inline RTPNTPTime RTCPSRPacket::GetNTPTimestamp() const @@ -142,7 +142,7 @@ inline RTPNTPTime RTCPSRPacket::GetNTPTimestamp() const return RTPNTPTime(0,0); RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); - return RTPNTPTime(ntohl(sr->ntptime_msw),ntohl(sr->ntptime_lsw)); + return RTPNTPTime(m_endian.qToHost(sr->ntptime_msw),m_endian.qToHost(sr->ntptime_lsw)); } inline uint32_t RTCPSRPacket::GetRTPTimestamp() const @@ -150,7 +150,7 @@ inline uint32_t RTCPSRPacket::GetRTPTimestamp() const if (!knownformat) return 0; RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); - return ntohl(sr->rtptimestamp); + return m_endian.qToHost(sr->rtptimestamp); } inline uint32_t RTCPSRPacket::GetSenderPacketCount() const @@ -158,7 +158,7 @@ inline uint32_t RTCPSRPacket::GetSenderPacketCount() const if (!knownformat) return 0; RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); - return ntohl(sr->packetcount); + return m_endian.qToHost(sr->packetcount); } inline uint32_t RTCPSRPacket::GetSenderOctetCount() const @@ -166,7 +166,7 @@ inline uint32_t RTCPSRPacket::GetSenderOctetCount() const if (!knownformat) return 0; RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); - return ntohl(sr->octetcount); + return m_endian.qToHost(sr->octetcount); } inline int RTCPSRPacket::GetReceptionReportCount() const @@ -188,7 +188,7 @@ inline uint32_t RTCPSRPacket::GetSSRC(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->ssrc); + return m_endian.qToHost(r->ssrc); } inline uint8_t RTCPSRPacket::GetFractionLost(int index) const @@ -216,7 +216,7 @@ inline uint32_t RTCPSRPacket::GetExtendedHighestSequenceNumber(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->exthighseqnr); + return m_endian.qToHost(r->exthighseqnr); } inline uint32_t RTCPSRPacket::GetJitter(int index) const @@ -224,7 +224,7 @@ inline uint32_t RTCPSRPacket::GetJitter(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->jitter); + return m_endian.qToHost(r->jitter); } inline uint32_t RTCPSRPacket::GetLSR(int index) const @@ -232,7 +232,7 @@ inline uint32_t RTCPSRPacket::GetLSR(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->lsr); + return m_endian.qToHost(r->lsr); } inline uint32_t RTCPSRPacket::GetDLSR(int index) const @@ -240,7 +240,7 @@ inline uint32_t RTCPSRPacket::GetDLSR(int index) const if (!knownformat) return 0; RTCPReceiverReport *r = GotoReport(index); - return ntohl(r->dlsr); + return m_endian.qToHost(r->dlsr); } } // end namespace diff --git a/qrtplib/rtpconfig.h b/qrtplib/rtpconfig.h index ea951d86a..95785122b 100644 --- a/qrtplib/rtpconfig.h +++ b/qrtplib/rtpconfig.h @@ -7,7 +7,7 @@ This library was developed at the Expertise Centre for Digital Media (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for + (http://www.uhasselt.be). The library is based upon work done for my thesis at the School for Knowledge Technology (Belgium/The Netherlands). Permission is hereby granted, free of charge, to any person obtaining a @@ -41,8 +41,8 @@ #define JRTPLIB_UNUSED(x) (void)(x) #endif // JRTPLIB_UNUSED -#define JRTPLIB_IMPORT -#define JRTPLIB_EXPORT +#define JRTPLIB_IMPORT +#define JRTPLIB_EXPORT #ifdef JRTPLIB_COMPILING #define JRTPLIB_IMPORTEXPORT JRTPLIB_EXPORT #else @@ -69,9 +69,9 @@ #define RTP_SUPPORT_GETLOGINR -#define RTP_SUPPORT_IPV6 +// no #define RTP_SUPPORT_IPV6 -#define RTP_SUPPORT_IPV6MULTICAST +// no #define RTP_SUPPORT_IPV6MULTICAST #define RTP_SUPPORT_IFADDRS @@ -81,7 +81,7 @@ // No support for sending unknown RTCP packets -#define RTP_SUPPORT_NETINET_IN +// no #define RTP_SUPPORT_NETINET_IN // Not using winsock sockets diff --git a/qrtplib/rtpipv6address.cpp b/qrtplib/rtpipv6address.cpp deleted file mode 100644 index 84c0ab796..000000000 --- a/qrtplib/rtpipv6address.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "rtpipv6address.h" -#include "rtpmemorymanager.h" - -#ifdef RTP_SUPPORT_IPV6 - - -namespace qrtplib -{ - -RTPAddress *RTPIPv6Address::CreateCopy(RTPMemoryManager *mgr) const -{ - JRTPLIB_UNUSED(mgr); // possibly unused - RTPIPv6Address *newaddr = RTPNew(mgr,RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv6Address(ip,port); - return newaddr; -} - -bool RTPIPv6Address::IsSameAddress(const RTPAddress *addr) const -{ - if (addr == 0) - return false; - if (addr->GetAddressType() != RTPAddress::IPv6Address) - return false; - - const RTPIPv6Address *addr2 = (const RTPIPv6Address *)addr; - const uint8_t *ip2 = addr2->ip.s6_addr; - - if (port != addr2->port) - return false; - - for (int i = 0 ; i < 16 ; i++) - { - if (ip.s6_addr[i] != ip2[i]) - return false; - } - return true; -} - -bool RTPIPv6Address::IsFromSameHost(const RTPAddress *addr) const -{ - if (addr == 0) - return false; - if (addr->GetAddressType() != RTPAddress::IPv6Address) - return false; - - const RTPIPv6Address *addr2 = (const RTPIPv6Address *)addr; - const uint8_t *ip2 = addr2->ip.s6_addr; - for (int i = 0 ; i < 16 ; i++) - { - if (ip.s6_addr[i] != ip2[i]) - return false; - } - return true; -} - - -} // end namespace - -#endif // RTP_SUPPORT_IPV6 - diff --git a/qrtplib/rtpipv6address.h b/qrtplib/rtpipv6address.h deleted file mode 100644 index c40edff64..000000000 --- a/qrtplib/rtpipv6address.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtpipv6address.h - */ - -#ifndef RTPIPV6ADDRESS_H - -#define RTPIPV6ADDRESS_H - -#include "rtpconfig.h" - -#ifdef RTP_SUPPORT_IPV6 - -#include "rtpaddress.h" -#include "rtptypes.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN - -namespace qrtplib -{ - -/** Represents an IPv6 IP address and port. - * This class is used by the UDP over IPv4 transmission component. - * When an RTPIPv6Address is used in one of the multicast functions of the - * transmitter, the port number is ignored. When an instance is used in one of - * the accept or ignore functions of the transmitter, a zero port number represents - * all ports for the specified IP address. - */ -class JRTPLIB_IMPORTEXPORT RTPIPv6Address : public RTPAddress -{ -public: - /** Creates an instance with IP address and port number set to zero. */ - RTPIPv6Address():RTPAddress(IPv6Address) { for (int i = 0 ; i < 16 ; i++) ip.s6_addr[i] = 0; port = 0; } - - /** Creates an instance with IP address \c ip and port number \c port (the port number is assumed to be in - * host byte order). */ - RTPIPv6Address(const uint8_t ip[16],uint16_t port = 0):RTPAddress(IPv6Address) { SetIP(ip); RTPIPv6Address::port = port; } - - /** Creates an instance with IP address \c ip and port number \c port (the port number is assumed to be in - * host byte order). */ - RTPIPv6Address(in6_addr ip,uint16_t port = 0):RTPAddress(IPv6Address) { RTPIPv6Address::ip = ip; RTPIPv6Address::port = port; } - ~RTPIPv6Address() { } - - /** Sets the IP address for this instance to \c ip. */ - void SetIP(in6_addr ip) { RTPIPv6Address::ip = ip; } - - /** Sets the IP address for this instance to \c ip. */ - void SetIP(const uint8_t ip[16]) { for (int i = 0 ; i < 16 ; i++) RTPIPv6Address::ip.s6_addr[i] = ip[i]; } - - /** Sets the port number for this instance to \c port, which is interpreted in host byte order. */ - void SetPort(uint16_t port) { RTPIPv6Address::port = port; } - - /** Copies the IP address of this instance in \c ip. */ - void GetIP(uint8_t ip[16]) const { for (int i = 0 ; i < 16 ; i++) ip[i] = RTPIPv6Address::ip.s6_addr[i]; } - - /** Returns the IP address of this instance. */ - in6_addr GetIP() const { return ip; } - - /** Returns the port number contained in this instance in host byte order. */ - uint16_t GetPort() const { return port; } - - RTPAddress *CreateCopy(RTPMemoryManager *mgr) const; - bool IsSameAddress(const RTPAddress *addr) const; - bool IsFromSameHost(const RTPAddress *addr) const; - -private: - in6_addr ip; - uint16_t port; -}; - -} // end namespace - -#endif // RTP_SUPPORT_IPV6 - -#endif // RTPIPV6ADDRESS_H - diff --git a/qrtplib/rtpipv6destination.cpp b/qrtplib/rtpipv6destination.cpp deleted file mode 100644 index d52c02e83..000000000 --- a/qrtplib/rtpipv6destination.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2011 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "rtpipv6destination.h" - -#ifdef RTP_SUPPORT_IPV6 - -#include "rtpinternalutils.h" - -namespace qrtplib -{ - -std::string RTPIPv6Destination::GetDestinationString() const -{ - uint16_t ip16[8]; - char str[48]; - uint16_t portbase = ntohs(rtpaddr.sin6_port); - int i,j; - for (i = 0,j = 0 ; j < 8 ; j++,i += 2) - { - ip16[j] = (((uint16_t)rtpaddr.sin6_addr.s6_addr[i])<<8); - ip16[j] |= ((uint16_t)rtpaddr.sin6_addr.s6_addr[i+1]); - } - RTP_SNPRINTF(str,48,"%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X/%d",(int)ip16[0],(int)ip16[1],(int)ip16[2],(int)ip16[3],(int)ip16[4],(int)ip16[5],(int)ip16[6],(int)ip16[7],(int)portbase); - return std::string(str); -} - -} // end namespace - -#endif // RTP_SUPPORT_IPV6 - - diff --git a/qrtplib/rtpipv6destination.h b/qrtplib/rtpipv6destination.h deleted file mode 100644 index c04808a35..000000000 --- a/qrtplib/rtpipv6destination.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtpipv6destination.h - */ - -#ifndef RTPIPV6DESTINATION_H - -#define RTPIPV6DESTINATION_H - -#include "rtpconfig.h" - -#ifdef RTP_SUPPORT_IPV6 - -#include "rtptypes.h" -#include -#include -#ifndef RTP_SOCKETTYPE_WINSOCK - #include - #include - #include -#endif // RTP_SOCKETTYPE_WINSOCK - -namespace qrtplib -{ - -class JRTPLIB_IMPORTEXPORT RTPIPv6Destination -{ -public: - RTPIPv6Destination(in6_addr ip,uint16_t portbase) - { - memset(&rtpaddr,0,sizeof(struct sockaddr_in6)); - memset(&rtcpaddr,0,sizeof(struct sockaddr_in6)); - rtpaddr.sin6_family = AF_INET6; - rtpaddr.sin6_port = htons(portbase); - rtpaddr.sin6_addr = ip; - rtcpaddr.sin6_family = AF_INET6; - rtcpaddr.sin6_port = htons(portbase+1); - rtcpaddr.sin6_addr = ip; - } - in6_addr GetIP() const { return rtpaddr.sin6_addr; } - bool operator==(const RTPIPv6Destination &src) const - { - if (rtpaddr.sin6_port == src.rtpaddr.sin6_port && (memcmp(&(src.rtpaddr.sin6_addr),&(rtpaddr.sin6_addr),sizeof(in6_addr)) == 0)) - return true; - return false; - } - const struct sockaddr_in6 *GetRTPSockAddr() const { return &rtpaddr; } - const struct sockaddr_in6 *GetRTCPSockAddr() const { return &rtcpaddr; } - std::string GetDestinationString() const; -private: - struct sockaddr_in6 rtpaddr; - struct sockaddr_in6 rtcpaddr; -}; - -} // end namespace - -#endif // RTP_SUPPORT_IPV6 - -#endif // RTPIPV6DESTINATION_H - diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp index ed31a36ac..46eaabcc2 100644 --- a/qrtplib/rtppacket.cpp +++ b/qrtplib/rtppacket.cpp @@ -35,9 +35,6 @@ #include "rtpdefines.h" #include "rtperrors.h" #include "rtprawpacket.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN #include namespace qrtplib @@ -153,7 +150,7 @@ int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) rtpextheader = (RTPExtensionHeader *)(packetbytes+payloadoffset); payloadoffset += sizeof(RTPExtensionHeader); - uint16_t exthdrlen = ntohs(rtpextheader->length); + uint16_t exthdrlen = m_endian.qToHost(rtpextheader->length); payloadoffset += ((int)exthdrlen)*sizeof(uint32_t); } else @@ -171,8 +168,8 @@ int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) RTPPacket::hasextension = hasextension; if (hasextension) { - RTPPacket::extid = ntohs(rtpextheader->extid); - RTPPacket::extensionlength = ((int)ntohs(rtpextheader->length))*sizeof(uint32_t); + RTPPacket::extid = m_endian.qToHost(rtpextheader->extid); + RTPPacket::extensionlength = ((int)m_endian.qToHost(rtpextheader->length))*sizeof(uint32_t); RTPPacket::extension = ((uint8_t *)rtpextheader)+sizeof(RTPExtensionHeader); } @@ -183,10 +180,10 @@ int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) // Note: we don't fill in the EXTENDED sequence number here, since we // don't have information about the source here. We just fill in the low // 16 bits - RTPPacket::extseqnr = (uint32_t)ntohs(rtpheader->sequencenumber); + RTPPacket::extseqnr = (uint32_t)m_endian.qToHost(rtpheader->sequencenumber); - RTPPacket::timestamp = ntohl(rtpheader->timestamp); - RTPPacket::ssrc = ntohl(rtpheader->ssrc); + RTPPacket::timestamp = m_endian.qToHost(rtpheader->timestamp); + RTPPacket::ssrc = m_endian.qToHost(rtpheader->ssrc); RTPPacket::packet = packetbytes; RTPPacket::payload = packetbytes+payloadoffset; RTPPacket::packetlength = packetlen; @@ -209,7 +206,7 @@ uint32_t RTPPacket::GetCSRC(int num) const csrcpos = packet+sizeof(RTPHeader)+num*sizeof(uint32_t); csrcval_nbo = (uint32_t *)csrcpos; - csrcval_hbo = ntohl(*csrcval_nbo); + csrcval_hbo = m_endian.qToHost(*csrcval_nbo); return csrcval_hbo; } @@ -285,24 +282,24 @@ int RTPPacket::BuildPacket(uint8_t payloadtype,const void *payloaddata,size_t pa rtphdr->extension = 0; rtphdr->csrccount = numcsrcs; rtphdr->payloadtype = payloadtype&127; // make sure high bit isn't set - rtphdr->sequencenumber = htons(seqnr); - rtphdr->timestamp = htonl(timestamp); - rtphdr->ssrc = htonl(ssrc); + rtphdr->sequencenumber = qToBigEndian(seqnr); + rtphdr->timestamp = qToBigEndian(timestamp); + rtphdr->ssrc = qToBigEndian(ssrc); uint32_t *curcsrc; int i; curcsrc = (uint32_t *)(packet+sizeof(RTPHeader)); for (i = 0 ; i < numcsrcs ; i++,curcsrc++) - *curcsrc = htonl(csrcs[i]); + *curcsrc = qToBigEndian(csrcs[i]); payload = packet+sizeof(RTPHeader)+((size_t)numcsrcs)*sizeof(uint32_t); if (gotextension) { RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *)payload; - rtpexthdr->extid = htons(extensionid); - rtpexthdr->length = htons((uint16_t)extensionlen_numwords); + rtpexthdr->extid = qToBigEndian(extensionid); + rtpexthdr->length = qToBigEndian((uint16_t)extensionlen_numwords); payload += sizeof(RTPExtensionHeader); memcpy(payload,extensiondata,RTPPacket::extensionlength); diff --git a/qrtplib/rtppacket.h b/qrtplib/rtppacket.h index b620f0642..fa4dea018 100644 --- a/qrtplib/rtppacket.h +++ b/qrtplib/rtppacket.h @@ -42,6 +42,7 @@ #include "rtptypes.h" #include "rtptimeutilities.h" #include "rtpmemoryobject.h" +#include "rtpendian.h" namespace qrtplib { @@ -155,6 +156,7 @@ private: bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, void *buffer,size_t maxsize); + RTPEndian m_endian; int error; bool hasextension,hasmarker; diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index d5dabde2b..7b5f83d3c 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -33,7 +33,6 @@ #include "rtpsession.h" #include "rtperrors.h" #include "rtpudpv4transmitter.h" -#include "rtpudpv6transmitter.h" #include "rtptcptransmitter.h" #include "rtpexternaltransmitter.h" #include "rtpsessionparams.h" @@ -121,11 +120,6 @@ int RTPSession::Create(const RTPSessionParams &sessparams,const RTPTransmissionP case RTPTransmitter::IPv4UDPProto: rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPUDPv4Transmitter(GetMemoryManager()); break; -#ifdef RTP_SUPPORT_IPV6 - case RTPTransmitter::IPv6UDPProto: - rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPUDPv6Transmitter(GetMemoryManager()); - break; -#endif // RTP_SUPPORT_IPV6 case RTPTransmitter::ExternalProto: rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPExternalTransmitter(GetMemoryManager()); break; diff --git a/qrtplib/rtpsourcedata.cpp b/qrtplib/rtpsourcedata.cpp index 8387b56d0..d02befacc 100644 --- a/qrtplib/rtpsourcedata.cpp +++ b/qrtplib/rtpsourcedata.cpp @@ -34,9 +34,6 @@ #include "rtpdefines.h" #include "rtpaddress.h" #include "rtpmemorymanager.h" -#ifdef RTP_SUPPORT_NETINET_IN - #include -#endif // RTP_SUPPORT_NETINET_IN #define ACCEPTPACKETCODE \ diff --git a/qrtplib/rtpudpv6transmitter.cpp b/qrtplib/rtpudpv6transmitter.cpp deleted file mode 100644 index 3caccfc3b..000000000 --- a/qrtplib/rtpudpv6transmitter.cpp +++ /dev/null @@ -1,1621 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -// This is for getaddrinfo when using mingw -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0600 -#endif - -#include "rtpudpv6transmitter.h" - -#ifdef RTP_SUPPORT_IPV6 - -#include "rtprawpacket.h" -#include "rtpipv6address.h" -#include "rtptimeutilities.h" -#include "rtpdefines.h" -#include "rtpsocketutilinternal.h" -#include "rtpinternalutils.h" -#include "rtpselect.h" -#include - -#define RTPUDPV6TRANS_MAXPACKSIZE 65535 -#define RTPUDPV6TRANS_IFREQBUFSIZE 8192 - -#define RTPUDPV6TRANS_IS_MCASTADDR(x) (x.s6_addr[0] == 0xFF) - -#define RTPUDPV6TRANS_MCASTMEMBERSHIP(socket,type,mcastip,status) {\ - struct ipv6_mreq mreq;\ - \ - mreq.ipv6mr_multiaddr = mcastip;\ - mreq.ipv6mr_interface = mcastifidx;\ - status = setsockopt(socket,IPPROTO_IPV6,type,(const char *)&mreq,sizeof(struct ipv6_mreq));\ - } -#define MAINMUTEX_LOCK -#define MAINMUTEX_UNLOCK -#define WAITMUTEX_LOCK -#define WAITMUTEX_UNLOCK - -inline bool operator==(const in6_addr &ip1,const in6_addr &ip2) -{ - if (memcmp(&ip1,&ip2,sizeof(in6_addr)) == 0) - return true; - return false; -} - -namespace qrtplib -{ - -RTPUDPv6Transmitter::RTPUDPv6Transmitter(RTPMemoryManager *mgr) : RTPTransmitter(mgr), - destinations(GetMemoryManager(),RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT), - multicastgroups(GetMemoryManager(),RTPMEM_TYPE_CLASS_MULTICASTHASHELEMENT), - acceptignoreinfo(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT) -{ - created = false; - init = false; -} - -RTPUDPv6Transmitter::~RTPUDPv6Transmitter() -{ - Destroy(); -} - -int RTPUDPv6Transmitter::Init(bool tsafe) -{ - if (init) - return ERR_RTP_UDPV6TRANS_ALREADYINIT; - - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; - - init = true; - return 0; -} - -int RTPUDPv6Transmitter::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) -{ - const RTPUDPv6TransmissionParams *params,defaultparams; - struct sockaddr_in6 addr; - RTPSOCKLENTYPE size; - int status; - - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_ALREADYCREATED; - } - - // Obtain transmission parameters - - if (transparams == 0) - params = &defaultparams; - else - { - if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv6UDPProto) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS; - } - params = (const RTPUDPv6TransmissionParams *)transparams; - } - - // Check if portbase is even - if (params->GetPortbase()%2 != 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN; - } - - // create sockets - - rtpsock = socket(PF_INET6,SOCK_DGRAM,0); - if (rtpsock == RTPSOCKERR) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_CANTCREATESOCKET; - } - rtcpsock = socket(PF_INET6,SOCK_DGRAM,0); - if (rtcpsock == RTPSOCKERR) - { - RTPCLOSE(rtpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_CANTCREATESOCKET; - } - - // set socket buffer sizes - - size = params->GetRTPReceiveBuffer(); - if (setsockopt(rtpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF; - } - size = params->GetRTPSendBuffer(); - if (setsockopt(rtpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF; - } - size = params->GetRTCPReceiveBuffer(); - if (setsockopt(rtcpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF; - } - size = params->GetRTCPSendBuffer(); - if (setsockopt(rtcpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF; - } - - // bind sockets - - bindIP = params->GetBindIP(); - mcastifidx = params->GetMulticastInterfaceIndex(); - - memset(&addr,0,sizeof(struct sockaddr_in6)); - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(params->GetPortbase()); - addr.sin6_addr = bindIP; - if (bind(rtpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in6)) != 0) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET; - } - memset(&addr,0,sizeof(struct sockaddr_in6)); - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(params->GetPortbase()+1); - addr.sin6_addr = bindIP; - if (bind(rtcpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in6)) != 0) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET; - } - - // Try to obtain local IP addresses - - localIPs = params->GetLocalIPList(); - if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them - { - int status; - - if ((status = CreateLocalIPList()) < 0) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return status; - } - - } - -#ifdef RTP_SUPPORT_IPV6MULTICAST - if (SetMulticastTTL(params->GetMulticastTTL())) - supportsmulticasting = true; - else - supportsmulticasting = false; -#else // no multicast support enabled - supportsmulticasting = false; -#endif // RTP_SUPPORT_IPV6MULTICAST - - if (maximumpacketsize > RTPUDPV6TRANS_MAXPACKSIZE) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG; - } - - if (!params->GetCreatedAbortDescriptors()) - { - if ((status = m_abortDesc.Init()) < 0) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return status; - } - m_pAbortDesc = &m_abortDesc; - } - else - { - m_pAbortDesc = params->GetCreatedAbortDescriptors(); - if (!m_pAbortDesc->IsInitialized()) - { - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_ABORTDESC_NOTINIT; - } - } - - maxpacksize = maximumpacketsize; - portbase = params->GetPortbase(); - multicastTTL = params->GetMulticastTTL(); - receivemode = RTPTransmitter::AcceptAll; - - localhostname = 0; - localhostnamelength = 0; - - waitingfordata = false; - created = true; - MAINMUTEX_UNLOCK - return 0; -} - -void RTPUDPv6Transmitter::Destroy() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK; - return; - } - - if (localhostname) - { - RTPDeleteByteArray(localhostname,GetMemoryManager()); - localhostname = 0; - localhostnamelength = 0; - } - - RTPCLOSE(rtpsock); - RTPCLOSE(rtcpsock); - destinations.Clear(); -#ifdef RTP_SUPPORT_IPV6MULTICAST - multicastgroups.Clear(); -#endif // RTP_SUPPORT_IPV6MULTICAST - FlushPackets(); - ClearAcceptIgnoreInfo(); - localIPs.clear(); - created = false; - - if (waitingfordata) - { - m_pAbortDesc->SendAbortSignal(); - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK - } - else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - - MAINMUTEX_UNLOCK -} - -RTPTransmissionInfo *RTPUDPv6Transmitter::GetTransmissionInfo() -{ - if (!init) - return 0; - - MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPUDPv6TransmissionInfo(localIPs,rtpsock,rtcpsock,portbase,portbase+1); - MAINMUTEX_UNLOCK - return tinf; -} - -void RTPUDPv6Transmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) -{ - if (!init) - return; - - RTPDelete(i, GetMemoryManager()); -} - -int RTPUDPv6Transmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - - if (localhostname == 0) - { - if (localIPs.empty()) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOLOCALIPS; - } - - std::list::const_iterator it; - std::list hostnames; - - for (it = localIPs.begin() ; it != localIPs.end() ; it++) - { - bool founddouble = false; - bool foundentry = true; - - while (!founddouble && foundentry) - { - struct hostent *he; - in6_addr ip = (*it); - - he = gethostbyaddr((char *)&ip,sizeof(in6_addr),AF_INET6); - if (he != 0) - { - std::string hname = std::string(he->h_name); - std::list::const_iterator it; - - for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) - hostnames.push_back(hname); - - int i = 0; - while (!founddouble && he->h_aliases[i] != 0) - { - std::string hname = std::string(he->h_aliases[i]); - - for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) - { - hostnames.push_back(hname); - i++; - } - } - } - else - foundentry = false; - } - } - - bool found = false; - - if (!hostnames.empty()) // try to select the most appropriate hostname - { - std::list::const_iterator it; - - hostnames.sort(); - for (it = hostnames.begin() ; !found && it != hostnames.end() ; it++) - { - if ((*it).find('.') != std::string::npos) - { - found = true; - localhostnamelength = (*it).length(); - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; - if (localhostname == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname,(*it).c_str(),localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - } - - if (!found) // use an IP address - { - in6_addr ip; - int len; - char str[48]; - uint16_t ip16[8]; - int i,j; - - it = localIPs.begin(); - ip = (*it); - - for (i = 0,j = 0 ; j < 8 ; j++,i += 2) - { - ip16[j] = (((uint16_t)ip.s6_addr[i])<<8); - ip16[j] |= ((uint16_t)ip.s6_addr[i+1]); - } - - RTP_SNPRINTF(str,48,"%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X",(int)ip16[0],(int)ip16[1],(int)ip16[2],(int)ip16[3],(int)ip16[4],(int)ip16[5],(int)ip16[6],(int)ip16[7]); - len = strlen(str); - - localhostnamelength = len; - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; - if (localhostname == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname,str,localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - - if ((*bufferlength) < localhostnamelength) - { - *bufferlength = localhostnamelength; // tell the application the required size of the buffer - MAINMUTEX_UNLOCK - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } - - memcpy(buffer,localhostname,localhostnamelength); - *bufferlength = localhostnamelength; - - MAINMUTEX_UNLOCK - return 0; -} - -bool RTPUDPv6Transmitter::ComesFromThisTransmitter(const RTPAddress *addr) -{ - if (!init) - return false; - - if (addr == 0) - return false; - - MAINMUTEX_LOCK - - bool v; - - if (created && addr->GetAddressType() == RTPAddress::IPv6Address) - { - const RTPIPv6Address *addr2 = (const RTPIPv6Address *)addr; - bool found = false; - std::list::const_iterator it; - - it = localIPs.begin(); - while (!found && it != localIPs.end()) - { - in6_addr itip = *it; - in6_addr addrip = addr2->GetIP(); - if (memcmp(&addrip,&itip,sizeof(in6_addr)) == 0) - found = true; - else - ++it; - } - - if (!found) - v = false; - else - { - if (addr2->GetPort() == portbase) // check for RTP port - v = true; - else if (addr2->GetPort() == (portbase+1)) // check for RTCP port - v = true; - else - v = false; - } - } - else - v = false; - - MAINMUTEX_UNLOCK - return v; -} - -int RTPUDPv6Transmitter::Poll() -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - int status; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - status = PollSocket(true); // poll RTP socket - if (status >= 0) - status = PollSocket(false); // poll RTCP socket - MAINMUTEX_UNLOCK - return status; -} - -int RTPUDPv6Transmitter::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (waitingfordata) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_ALREADYWAITING; - } - - SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); - SocketType socks[3] = { rtpsock, rtcpsock, abortSocket }; - int8_t readflags[3] = { 0, 0, 0 }; - const int idxRTP = 0; - const int idxRTCP = 1; - const int idxAbort = 2; - - waitingfordata = true; - - WAITMUTEX_LOCK - MAINMUTEX_UNLOCK - - int status = RTPSelect(socks, readflags, 3, delay); - if (status < 0) - { - MAINMUTEX_LOCK - waitingfordata = false; - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return status; - } - - MAINMUTEX_LOCK - waitingfordata = false; - if (!created) // destroy called - { - MAINMUTEX_UNLOCK; - WAITMUTEX_UNLOCK - return 0; - } - - // if aborted, read from abort buffer - if (readflags[idxAbort]) - m_pAbortDesc->ReadSignallingByte(); - - if (dataavailable != 0) - { - if (readflags[idxRTP] || readflags[idxRTCP]) - *dataavailable = true; - else - *dataavailable = false; - } - - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return 0; -} - -int RTPUDPv6Transmitter::AbortWait() -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (!waitingfordata) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTWAITING; - } - - m_pAbortDesc->SendAbortSignal(); - - MAINMUTEX_UNLOCK - return 0; -} - -int RTPUDPv6Transmitter::SendRTPData(const void *data,size_t len) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTPSockAddr(),sizeof(struct sockaddr_in6)); - destinations.GotoNextElement(); - } - - MAINMUTEX_UNLOCK - return 0; -} - -int RTPUDPv6Transmitter::SendRTCPData(const void *data,size_t len) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtcpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTCPSockAddr(),sizeof(struct sockaddr_in6)); - destinations.GotoNextElement(); - } - - MAINMUTEX_UNLOCK - return 0; -} - -int RTPUDPv6Transmitter::AddDestination(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv6Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; - } - - RTPIPv6Address &address = (RTPIPv6Address &)addr; - RTPIPv6Destination dest(address.GetIP(),address.GetPort()); - int status = destinations.AddElement(dest); - - MAINMUTEX_UNLOCK - return status; -} - -int RTPUDPv6Transmitter::DeleteDestination(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv6Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; - } - - RTPIPv6Address &address = (RTPIPv6Address &)addr; - RTPIPv6Destination dest(address.GetIP(),address.GetPort()); - int status = destinations.DeleteElement(dest); - - MAINMUTEX_UNLOCK - return status; -} - -void RTPUDPv6Transmitter::ClearDestinations() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (created) - destinations.Clear(); - MAINMUTEX_UNLOCK -} - -bool RTPUDPv6Transmitter::SupportsMulticasting() -{ - if (!init) - return false; - - MAINMUTEX_LOCK - - bool v; - - if (!created) - v = false; - else - v = supportsmulticasting; - - MAINMUTEX_UNLOCK - return v; -} - -#ifdef RTP_SUPPORT_IPV6MULTICAST - -int RTPUDPv6Transmitter::JoinMulticastGroup(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv6Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; - } - - const RTPIPv6Address &address = (const RTPIPv6Address &)addr; - in6_addr mcastIP = address.GetIP(); - - if (!RTPUDPV6TRANS_IS_MCASTADDR(mcastIP)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS; - } - - status = multicastgroups.AddElement(mcastIP); - if (status >= 0) - { - RTPUDPV6TRANS_MCASTMEMBERSHIP(rtpsock,IPV6_JOIN_GROUP,mcastIP,status); - if (status != 0) - { - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP; - } - RTPUDPV6TRANS_MCASTMEMBERSHIP(rtcpsock,IPV6_JOIN_GROUP,mcastIP,status); - if (status != 0) - { - RTPUDPV6TRANS_MCASTMEMBERSHIP(rtpsock,IPV6_LEAVE_GROUP,mcastIP,status); - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP; - } - } - MAINMUTEX_UNLOCK - return status; -} - -int RTPUDPv6Transmitter::LeaveMulticastGroup(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv6Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; - } - - const RTPIPv6Address &address = (const RTPIPv6Address &)addr; - in6_addr mcastIP = address.GetIP(); - - if (!RTPUDPV6TRANS_IS_MCASTADDR(mcastIP)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS; - } - - status = multicastgroups.DeleteElement(mcastIP); - if (status >= 0) - { - RTPUDPV6TRANS_MCASTMEMBERSHIP(rtpsock,IPV6_LEAVE_GROUP,mcastIP,status); - RTPUDPV6TRANS_MCASTMEMBERSHIP(rtcpsock,IPV6_LEAVE_GROUP,mcastIP,status); - status = 0; - } - - MAINMUTEX_UNLOCK - return status; -} - -void RTPUDPv6Transmitter::LeaveAllMulticastGroups() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (created) - { - multicastgroups.GotoFirstElement(); - while (multicastgroups.HasCurrentElement()) - { - in6_addr mcastIP; - int status = 0; - - mcastIP = multicastgroups.GetCurrentElement(); - RTPUDPV6TRANS_MCASTMEMBERSHIP(rtpsock,IPV6_LEAVE_GROUP,mcastIP,status); - RTPUDPV6TRANS_MCASTMEMBERSHIP(rtcpsock,IPV6_LEAVE_GROUP,mcastIP,status); - multicastgroups.GotoNextElement(); - JRTPLIB_UNUSED(status); - } - multicastgroups.Clear(); - } - MAINMUTEX_UNLOCK -} - -#else // no multicast support - -int RTPUDPv6Transmitter::JoinMulticastGroup(const RTPAddress &addr) -{ - return ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT; -} - -int RTPUDPv6Transmitter::LeaveMulticastGroup(const RTPAddress &addr) -{ - return ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT; -} - -void RTPUDPv6Transmitter::LeaveAllMulticastGroups() -{ -} - -#endif // RTP_SUPPORT_IPV6MULTICAST - -int RTPUDPv6Transmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (m != receivemode) - { - receivemode = m; - acceptignoreinfo.Clear(); - } - MAINMUTEX_UNLOCK - return 0; -} - -int RTPUDPv6Transmitter::AddToIgnoreList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv6Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv6Address &address = (const RTPIPv6Address &)addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); - - MAINMUTEX_UNLOCK - return status; -} - -int RTPUDPv6Transmitter::DeleteFromIgnoreList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv6Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv6Address &address = (const RTPIPv6Address &)addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); - - MAINMUTEX_UNLOCK - return status; -} - -void RTPUDPv6Transmitter::ClearIgnoreList() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (created && receivemode == RTPTransmitter::IgnoreSome) - ClearAcceptIgnoreInfo(); - MAINMUTEX_UNLOCK -} - -int RTPUDPv6Transmitter::AddToAcceptList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv6Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv6Address &address = (const RTPIPv6Address &)addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); - - MAINMUTEX_UNLOCK - return status; -} - -int RTPUDPv6Transmitter::DeleteFromAcceptList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv6Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv6Address &address = (const RTPIPv6Address &)addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); - - MAINMUTEX_UNLOCK - return status; -} - -void RTPUDPv6Transmitter::ClearAcceptList() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (created && receivemode == RTPTransmitter::AcceptSome) - ClearAcceptIgnoreInfo(); - MAINMUTEX_UNLOCK -} - -int RTPUDPv6Transmitter::SetMaximumPacketSize(size_t s) -{ - if (!init) - return ERR_RTP_UDPV6TRANS_NOTINIT; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_NOTCREATED; - } - if (s > RTPUDPV6TRANS_MAXPACKSIZE) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG; - } - maxpacksize = s; - MAINMUTEX_UNLOCK - return 0; -} - -bool RTPUDPv6Transmitter::NewDataAvailable() -{ - if (!init) - return false; - - MAINMUTEX_LOCK - - bool v; - - if (!created) - v = false; - else - { - if (rawpacketlist.empty()) - v = false; - else - v = true; - } - - MAINMUTEX_UNLOCK - return v; -} - -RTPRawPacket *RTPUDPv6Transmitter::GetNextPacket() -{ - if (!init) - return 0; - - MAINMUTEX_LOCK - - RTPRawPacket *p; - - if (!created) - { - MAINMUTEX_UNLOCK - return 0; - } - if (rawpacketlist.empty()) - { - MAINMUTEX_UNLOCK - return 0; - } - - p = *(rawpacketlist.begin()); - rawpacketlist.pop_front(); - - MAINMUTEX_UNLOCK - return p; -} - -// Here the private functions start... - - -#ifdef RTP_SUPPORT_IPV6MULTICAST -bool RTPUDPv6Transmitter::SetMulticastTTL(uint8_t ttl) -{ - int ttl2,status; - - ttl2 = (int)ttl; - status = setsockopt(rtpsock,IPPROTO_IPV6,IPV6_MULTICAST_HOPS,(const char *)&ttl2,sizeof(int)); - if (status != 0) - return false; - status = setsockopt(rtcpsock,IPPROTO_IPV6,IPV6_MULTICAST_HOPS,(const char *)&ttl2,sizeof(int)); - if (status != 0) - return false; - return true; -} -#endif // RTP_SUPPORT_IPV6MULTICAST - - -void RTPUDPv6Transmitter::FlushPackets() -{ - std::list::const_iterator it; - - for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) - RTPDelete(*it,GetMemoryManager()); - rawpacketlist.clear(); -} - -int RTPUDPv6Transmitter::PollSocket(bool rtp) -{ - RTPSOCKLENTYPE fromlen; - int recvlen; - char packetbuffer[RTPUDPV6TRANS_MAXPACKSIZE]; -#ifdef RTP_SOCKETTYPE_WINSOCK - SOCKET sock; - unsigned long len; -#else - size_t len; - int sock; -#endif // RTP_SOCKETTYPE_WINSOCK - struct sockaddr_in6 srcaddr; - bool dataavailable; - - if (rtp) - sock = rtpsock; - else - sock = rtcpsock; - - len = 0; - RTPIOCTL(sock,FIONREAD,&len); - - if (len <= 0) // make sure a packet of length zero is not queued - { - int8_t isset = 0; - int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); - if (status < 0) - return status; - - if (isset) - dataavailable = true; - else - dataavailable = false; - } - else - dataavailable = true; - - while (dataavailable) - { - RTPTime curtime = RTPTime::CurrentTime(); - fromlen = sizeof(struct sockaddr_in6); - recvlen = recvfrom(sock,packetbuffer,RTPUDPV6TRANS_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); - if (recvlen > 0) - { - bool acceptdata; - - // got data, process it - if (receivemode == RTPTransmitter::AcceptAll) - acceptdata = true; - else - acceptdata = ShouldAcceptData(srcaddr.sin6_addr,ntohs(srcaddr.sin6_port)); - - if (acceptdata) - { - RTPRawPacket *pack; - RTPIPv6Address *addr; - uint8_t *datacopy; - - addr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv6Address(srcaddr.sin6_addr,ntohs(srcaddr.sin6_port)); - if (addr == 0) - return ERR_RTP_OUTOFMEM; - datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[recvlen]; - if (datacopy == 0) - { - RTPDelete(addr,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - memcpy(datacopy,packetbuffer,recvlen); - - pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,recvlen,addr,curtime,rtp,GetMemoryManager()); - if (pack == 0) - { - RTPDelete(addr,GetMemoryManager()); - RTPDeleteByteArray(datacopy,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - rawpacketlist.push_back(pack); - } - } - len = 0; - RTPIOCTL(sock,FIONREAD,&len); - - if (len <= 0) // make sure a packet of length zero is not queued - { - int8_t isset = 0; - int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); - if (status < 0) - return status; - - if (isset) - dataavailable = true; - else - dataavailable = false; - } - else - dataavailable = true; - } - return 0; -} - -int RTPUDPv6Transmitter::ProcessAddAcceptIgnoreEntry(in6_addr ip,uint16_t port) -{ - acceptignoreinfo.GotoElement(ip); - if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists - { - PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); - - if (port == 0) // select all ports - { - portinf->all = true; - portinf->portlist.clear(); - } - else if (!portinf->all) - { - std::list::const_iterator it,begin,end; - - begin = portinf->portlist.begin(); - end = portinf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == port) // already in list - return 0; - } - portinf->portlist.push_front(port); - } - } - else // got to create an entry for this IP address - { - PortInfo *portinf; - int status; - - portinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO) PortInfo(); - if (port == 0) // select all ports - portinf->all = true; - else - portinf->portlist.push_front(port); - - status = acceptignoreinfo.AddElement(ip,portinf); - if (status < 0) - { - RTPDelete(portinf,GetMemoryManager()); - return status; - } - } - return 0; -} - -void RTPUDPv6Transmitter::ClearAcceptIgnoreInfo() -{ - acceptignoreinfo.GotoFirstElement(); - while (acceptignoreinfo.HasCurrentElement()) - { - PortInfo *inf; - - inf = acceptignoreinfo.GetCurrentElement(); - RTPDelete(inf,GetMemoryManager()); - acceptignoreinfo.GotoNextElement(); - } - acceptignoreinfo.Clear(); -} - -int RTPUDPv6Transmitter::ProcessDeleteAcceptIgnoreEntry(in6_addr ip,uint16_t port) -{ - acceptignoreinfo.GotoElement(ip); - if (!acceptignoreinfo.HasCurrentElement()) - return ERR_RTP_UDPV6TRANS_NOSUCHENTRY; - - PortInfo *inf; - - inf = acceptignoreinfo.GetCurrentElement(); - if (port == 0) // delete all entries - { - inf->all = false; - inf->portlist.clear(); - } - else // a specific port was selected - { - if (inf->all) // currently, all ports are selected. Add the one to remove to the list - { - // we have to check if the list doesn't contain the port already - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == port) // already in list: this means we already deleted the entry - return ERR_RTP_UDPV6TRANS_NOSUCHENTRY; - } - inf->portlist.push_front(port); - } - else // check if we can find the port in the list - { - std::list::iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; ++it) - { - if (*it == port) // found it! - { - inf->portlist.erase(it); - return 0; - } - } - // didn't find it - return ERR_RTP_UDPV6TRANS_NOSUCHENTRY; - } - } - return 0; -} - -bool RTPUDPv6Transmitter::ShouldAcceptData(in6_addr srcip,uint16_t srcport) -{ - if (receivemode == RTPTransmitter::AcceptSome) - { - PortInfo *inf; - - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return false; - - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // only accept the ones in the list - { - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return true; - } - return false; - } - else // accept all, except the ones in the list - { - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return false; - } - return true; - } - } - else // IgnoreSome - { - PortInfo *inf; - - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return true; - - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // ignore the ports in the list - { - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return false; - } - return true; - } - else // ignore all, except the ones in the list - { - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return true; - } - return false; - } - } - return true; -} - -int RTPUDPv6Transmitter::CreateLocalIPList() -{ - // first try to obtain the list from the network interface info - - if (!GetLocalIPList_Interfaces()) - { - // If this fails, we'll have to depend on DNS info - GetLocalIPList_DNS(); - } - AddLoopbackAddress(); - return 0; -} - -#ifdef RTP_SOCKETTYPE_WINSOCK - -bool RTPUDPv6Transmitter::GetLocalIPList_Interfaces() -{ - unsigned char buffer[RTPUDPV6TRANS_IFREQBUFSIZE]; - DWORD outputsize; - DWORD numaddresses,i; - SOCKET_ADDRESS_LIST *addrlist; - - if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV6TRANS_IFREQBUFSIZE,&outputsize,NULL,NULL)) - return false; - - addrlist = (SOCKET_ADDRESS_LIST *)buffer; - numaddresses = addrlist->iAddressCount; - for (i = 0 ; i < numaddresses ; i++) - { - SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); - if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in6)) // IPv6 address - { - struct sockaddr_in6 *addr = (struct sockaddr_in6 *)sockaddr->lpSockaddr; - - localIPs.push_back(addr->sin6_addr); - } - } - - if (localIPs.empty()) - return false; - return true; -} - -#else - -#ifdef RTP_SUPPORT_IFADDRS - -bool RTPUDPv6Transmitter::GetLocalIPList_Interfaces() -{ - struct ifaddrs *addrs,*tmp; - - getifaddrs(&addrs); - tmp = addrs; - - while (tmp != 0) - { - if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET6) - { - struct sockaddr_in6 *inaddr = (struct sockaddr_in6 *)tmp->ifa_addr; - localIPs.push_back(inaddr->sin6_addr); - } - tmp = tmp->ifa_next; - } - - freeifaddrs(addrs); - - if (localIPs.empty()) - return false; - return true; -} - -#else - -bool RTPUDPv6Transmitter::GetLocalIPList_Interfaces() -{ - return false; -} - -#endif // RTP_SUPPORT_IFADDRS - -#endif // RTP_SOCKETTYPE_WINSOCK - -void RTPUDPv6Transmitter::GetLocalIPList_DNS() -{ - int status; - char name[1024]; - - gethostname(name,1023); - name[1023] = 0; - - struct addrinfo hints; - struct addrinfo *res,*tmp; - - memset(&hints,0,sizeof(struct addrinfo)); - hints.ai_family = AF_INET6; - hints.ai_socktype = 0; - hints.ai_protocol = 0; - - if ((status = getaddrinfo(name,0,&hints,&res)) != 0) - return; - - tmp = res; - while (tmp != 0) - { - if (tmp->ai_family == AF_INET6) - { - struct sockaddr_in6 *addr = (struct sockaddr_in6 *)(tmp->ai_addr); - localIPs.push_back(addr->sin6_addr); - } - tmp = tmp->ai_next; - } - - freeaddrinfo(res); -} - -void RTPUDPv6Transmitter::AddLoopbackAddress() -{ - std::list::const_iterator it; - bool found = false; - - for (it = localIPs.begin() ; !found && it != localIPs.end() ; it++) - { - if ((*it) == in6addr_loopback) - found = true; - } - - if (!found) - localIPs.push_back(in6addr_loopback); -} - -} // end namespace - -#endif // RTP_SUPPORT_IPV6 - diff --git a/qrtplib/rtpudpv6transmitter.h b/qrtplib/rtpudpv6transmitter.h deleted file mode 100644 index 16fe6f9ef..000000000 --- a/qrtplib/rtpudpv6transmitter.h +++ /dev/null @@ -1,322 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtpudpv6transmitter.h - */ - -#ifndef RTPUDPV6TRANSMITTER_H - -#define RTPUDPV6TRANSMITTER_H - -#include "rtpconfig.h" - -#ifdef RTP_SUPPORT_IPV6 - -#include "rtptransmitter.h" -#include "rtpipv6destination.h" -#include "rtphashtable.h" -#include "rtpkeyhashtable.h" -#include "rtpsocketutil.h" -#include "rtpabortdescriptors.h" -#include -#include - -#define RTPUDPV6TRANS_HASHSIZE 8317 -#define RTPUDPV6TRANS_DEFAULTPORTBASE 5000 - -#define RTPUDPV6TRANS_RTPRECEIVEBUFFER 32768 -#define RTPUDPV6TRANS_RTCPRECEIVEBUFFER 32768 -#define RTPUDPV6TRANS_RTPTRANSMITBUFFER 32768 -#define RTPUDPV6TRANS_RTCPTRANSMITBUFFER 32768 - -namespace qrtplib -{ - -/** Parameters for the UDP over IPv6 transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv6TransmissionParams : public RTPTransmissionParams -{ -public: - RTPUDPv6TransmissionParams(); - - /** Sets the IP address which is used to bind the sockets to \c ip. */ - void SetBindIP(in6_addr ip) { bindIP = ip; } - - /** Sets the multicast interface index. */ - void SetMulticastInterfaceIndex(unsigned int idx) { mcastifidx = idx; } - - /** Sets the RTP portbase to \c pbase. This has to be an even number. */ - void SetPortbase(uint16_t pbase) { portbase = pbase; } - - /** Sets the multicast TTL to be used to \c mcastTTL. */ - void SetMulticastTTL(uint8_t mcastTTL) { multicastTTL = mcastTTL; } - - /** Passes a list of IP addresses which will be used as the local IP addresses. */ - void SetLocalIPList(std::list &iplist) { localIPs = iplist; } - - /** Clears the list of local IP addresses. - * Clears the list of local IP addresses. An empty list will make the transmission component - * itself determine the local IP addresses. - */ - void ClearLocalIPList() { localIPs.clear(); } - - /** Returns the IP address which will be used to bind the sockets. */ - in6_addr GetBindIP() const { return bindIP; } - - /** Returns the multicast interface index. */ - unsigned int GetMulticastInterfaceIndex() const { return mcastifidx; } - - /** Returns the RTP portbase which will be used (default is 5000). */ - uint16_t GetPortbase() const { return portbase; } - - /** Returns the multicast TTL which will be used (default is 1). */ - uint8_t GetMulticastTTL() const { return multicastTTL; } - - /** Returns the list of local IP addresses. */ - const std::list &GetLocalIPList() const { return localIPs; } - - /** Sets the RTP socket's send buffer size. */ - void SetRTPSendBuffer(int s) { rtpsendbuf = s; } - - /** Sets the RTP socket's receive buffer size. */ - void SetRTPReceiveBuffer(int s) { rtprecvbuf = s; } - - /** Sets the RTCP socket's send buffer size. */ - void SetRTCPSendBuffer(int s) { rtcpsendbuf = s; } - - /** Sets the RTCP socket's receive buffer size. */ - void SetRTCPReceiveBuffer(int s) { rtcprecvbuf = s; } - - /** If non null, the specified abort descriptors will be used to cancel - * the function that's waiting for packets to arrive; set to null (the default - * to let the transmitter create its own instance. */ - void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) { m_pAbortDesc = desc; } - - /** Returns the RTP socket's send buffer size. */ - int GetRTPSendBuffer() const { return rtpsendbuf; } - - /** Returns the RTP socket's receive buffer size. */ - int GetRTPReceiveBuffer() const { return rtprecvbuf; } - - /** Returns the RTCP socket's send buffer size. */ - int GetRTCPSendBuffer() const { return rtcpsendbuf; } - - /** Returns the RTCP socket's receive buffer size. */ - int GetRTCPReceiveBuffer() const { return rtcprecvbuf; } - - /** If non-null, this RTPAbortDescriptors instance will be used internally, - * which can be useful when creating your own poll thread for multiple - * sessions. */ - RTPAbortDescriptors *GetCreatedAbortDescriptors() const { return m_pAbortDesc; } -private: - uint16_t portbase; - in6_addr bindIP; - unsigned int mcastifidx; - std::list localIPs; - uint8_t multicastTTL; - int rtpsendbuf, rtprecvbuf; - int rtcpsendbuf, rtcprecvbuf; - - RTPAbortDescriptors *m_pAbortDesc; -}; - -inline RTPUDPv6TransmissionParams::RTPUDPv6TransmissionParams() - : RTPTransmissionParams(RTPTransmitter::IPv6UDPProto) -{ - portbase = RTPUDPV6TRANS_DEFAULTPORTBASE; - for (int i = 0 ; i < 16 ; i++) - bindIP.s6_addr[i] = 0; - - multicastTTL = 1; - mcastifidx = 0; - rtpsendbuf = RTPUDPV6TRANS_RTPTRANSMITBUFFER; - rtprecvbuf= RTPUDPV6TRANS_RTPRECEIVEBUFFER; - rtcpsendbuf = RTPUDPV6TRANS_RTCPTRANSMITBUFFER; - rtcprecvbuf = RTPUDPV6TRANS_RTCPRECEIVEBUFFER; - - m_pAbortDesc = 0; -} - -/** Additional information about the UDP over IPv6 transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv6TransmissionInfo : public RTPTransmissionInfo -{ -public: - RTPUDPv6TransmissionInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, - uint16_t rtpport, uint16_t rtcpport) : RTPTransmissionInfo(RTPTransmitter::IPv6UDPProto) - { localIPlist = iplist; rtpsocket = rtpsock; rtcpsocket = rtcpsock; m_rtpPort = rtpport; m_rtcpPort = rtcpport; } - - ~RTPUDPv6TransmissionInfo() { } - - /** Returns the list of IPv6 addresses the transmitter considers to be the local IP addresses. */ - std::list GetLocalIPList() const { return localIPlist; } - - /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ - SocketType GetRTPSocket() const { return rtpsocket; } - - /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ - SocketType GetRTCPSocket() const { return rtcpsocket; } - - /** Returns the port number that the RTP socket receives packets on. */ - uint16_t GetRTPPort() const { return m_rtpPort; } - - /** Returns the port number that the RTCP socket receives packets on. */ - uint16_t GetRTCPPort() const { return m_rtcpPort; } -private: - std::list localIPlist; - SocketType rtpsocket,rtcpsocket; - uint16_t m_rtpPort, m_rtcpPort; -}; - -class JRTPLIB_IMPORTEXPORT RTPUDPv6Trans_GetHashIndex_IPv6Dest -{ -public: - static int GetIndex(const RTPIPv6Destination &d) { in6_addr ip = d.GetIP(); return ((((uint32_t)ip.s6_addr[12])<<24)|(((uint32_t)ip.s6_addr[13])<<16)|(((uint32_t)ip.s6_addr[14])<<8)|((uint32_t)ip.s6_addr[15]))%RTPUDPV6TRANS_HASHSIZE; } -}; - -class JRTPLIB_IMPORTEXPORT RTPUDPv6Trans_GetHashIndex_in6_addr -{ -public: - static int GetIndex(const in6_addr &ip) { return ((((uint32_t)ip.s6_addr[12])<<24)|(((uint32_t)ip.s6_addr[13])<<16)|(((uint32_t)ip.s6_addr[14])<<8)|((uint32_t)ip.s6_addr[15]))%RTPUDPV6TRANS_HASHSIZE; } -}; - -#define RTPUDPV6TRANS_HEADERSIZE (40+8) - -/** An UDP over IPv6 transmitter. - * This class inherits the RTPTransmitter interface and implements a transmission component - * which uses UDP over IPv6 to send and receive RTP and RTCP data. The component's parameters - * are described by the class RTPUDPv6TransmissionParams. The functions which have an RTPAddress - * argument require an argument of RTPIPv6Address. The GetTransmissionInfo member function - * returns an instance of type RTPUDPv6TransmissionInfo. - */ -class JRTPLIB_IMPORTEXPORT RTPUDPv6Transmitter : public RTPTransmitter -{ -public: - RTPUDPv6Transmitter(RTPMemoryManager *mgr); - ~RTPUDPv6Transmitter(); - - int Init(bool treadsafe); - int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - - int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() { return RTPUDPV6TRANS_HEADERSIZE; } - - int Poll(); - int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); - int AbortWait(); - - int SendRTPData(const void *data,size_t len); - int SendRTCPData(const void *data,size_t len); - - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); - - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); - - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); - - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); - -private: - int CreateLocalIPList(); - bool GetLocalIPList_Interfaces(); - void GetLocalIPList_DNS(); - void AddLoopbackAddress(); - void FlushPackets(); - int PollSocket(bool rtp); - int ProcessAddAcceptIgnoreEntry(in6_addr ip,uint16_t port); - int ProcessDeleteAcceptIgnoreEntry(in6_addr ip,uint16_t port); -#ifdef RTP_SUPPORT_IPV6MULTICAST - bool SetMulticastTTL(uint8_t ttl); -#endif // RTP_SUPPORT_IPV6MULTICAST - bool ShouldAcceptData(in6_addr srcip,uint16_t srcport); - void ClearAcceptIgnoreInfo(); - - bool init; - bool created; - bool waitingfordata; - SocketType rtpsock,rtcpsock; - in6_addr bindIP; - unsigned int mcastifidx; - std::list localIPs; - uint16_t portbase; - uint8_t multicastTTL; - RTPTransmitter::ReceiveMode receivemode; - - uint8_t *localhostname; - size_t localhostnamelength; - - RTPHashTable destinations; -#ifdef RTP_SUPPORT_IPV6MULTICAST - RTPHashTable multicastgroups; -#endif // RTP_SUPPORT_IPV6MULTICAST - std::list rawpacketlist; - - bool supportsmulticasting; - size_t maxpacksize; - - class PortInfo - { - public: - PortInfo() { all = false; } - - bool all; - std::list portlist; - }; - - RTPKeyHashTable acceptignoreinfo; - RTPAbortDescriptors m_abortDesc; - RTPAbortDescriptors *m_pAbortDesc; - -}; - -} // end namespace - -#endif // RTP_SUPPORT_IPV6 - -#endif // RTPUDPV6TRANSMITTER_H - From 868b9631782cac8d83950611292d4a7892756f5d Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 02:16:18 +0100 Subject: [PATCH 017/956] qrtplib: added rtpendian.h in CmakeLists.txt --- qrtplib/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 71382e15a..3135b6ef4 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -17,6 +17,7 @@ set (qrtplib_HEADERS rtpcollisionlist.h rtpconfig.h rtpdefines.h + rtpendian.h rtperrors.h rtphashtable.h rtpinternalsourcedata.h From ed2f348bfa14ab7a22c080a6d0ee3195a5cbfabc Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 02:17:42 +0100 Subject: [PATCH 018/956] qrtplib: removed extra transmitters --- .../extratransmitters/rtpfaketransmitter.cpp | 1367 ----------------- .../extratransmitters/rtpfaketransmitter.h | 236 --- 2 files changed, 1603 deletions(-) delete mode 100644 qrtplib/extratransmitters/rtpfaketransmitter.cpp delete mode 100644 qrtplib/extratransmitters/rtpfaketransmitter.h diff --git a/qrtplib/extratransmitters/rtpfaketransmitter.cpp b/qrtplib/extratransmitters/rtpfaketransmitter.cpp deleted file mode 100644 index b2e590aca..000000000 --- a/qrtplib/extratransmitters/rtpfaketransmitter.cpp +++ /dev/null @@ -1,1367 +0,0 @@ -/* - - This class allows for jrtp to process packets without sending them out - anywhere. - The incoming messages are handed in to jrtp through the TransmissionParams - and can be retreived from jrtp through the normal polling mecanisms. - The outgoing RTP/RTCP packets are given to jrtp through the normal - session->SendPacket() and those packets are handed back out to the - client through a callback function (packet_ready_cb). - - example usage : Allows for integration of RTP into gstreamer. - - THIS HAS NOT BEEN TESTED WITH THREADS SO DON'T TRY - - Copyright (c) 2005 Philippe Khalaf - - This file is a part of JRTPLIB - Copyright (c) 1999-2004 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the "Expertisecentrum Digitale Media" - (http://www.edm.luc.ac.be), a research center of the "Limburgs Universitair - Centrum" (http://www.luc.ac.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "rtpfaketransmitter.h" - -#include "rtprawpacket.h" -#include "rtpipv4address.h" -#include "rtptimeutilities.h" -#include - -#include -#include -#include -#include -#include - -#ifdef RTP_HAVE_SYS_FILIO -#include -#endif // RTP_HAVE_SYS_FILIO - -#define RTPIOCTL ioctl - -#define RTPFAKETRANS_MAXPACKSIZE 65535 -#define RTPFAKETRANS_IFREQBUFSIZE 8192 - -//#define RTPFAKETRANS_IS_MCASTADDR(x) (((x)&0xF0000000) == 0xE0000000) - -/*#define RTPFAKETRANS_MCASTMEMBERSHIP(socket,type,mcastip,status) {\ - struct ip_mreq mreq;\ - \ - mreq.imr_multiaddr.s_addr = htonl(mcastip);\ - mreq.imr_interface.s_addr = htonl(bindIP);\ - status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ - }*/ -#define MAINMUTEX_LOCK -#define MAINMUTEX_UNLOCK -#define WAITMUTEX_LOCK -#define WAITMUTEX_UNLOCK - -namespace qrtplib -{ - -RTPFakeTransmitter::RTPFakeTransmitter(RTPMemoryManager *mgr ) : RTPTransmitter(mgr), destinations(mgr,RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT),acceptignoreinfo(mgr,RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT) -{ - created = false; - init = false; -} - -RTPFakeTransmitter::~RTPFakeTransmitter() -{ - Destroy(); -} - -int RTPFakeTransmitter::Init(bool tsafe) -{ - if (init) - return ERR_RTP_FAKETRANS_ALREADYINIT; - - // bomb out if trying to use threads - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; - - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; - - init = true; - return 0; -} - -int RTPFakeTransmitter::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) -{ -// struct sockaddr_in addr; -// int status; - - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_ALREADYCREATED; - } - - // Obtain transmission parameters - - if (transparams == 0) - params = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) RTPFakeTransmissionParams; - else - { - if (transparams->GetTransmissionProtocol() != RTPTransmitter::UserDefinedProto) - return ERR_RTP_FAKETRANS_ILLEGALPARAMETERS; - params = (RTPFakeTransmissionParams *)transparams; - } - - // Check if portbase is even - //if (params->GetPortbase()%2 != 0) - //{ - // MAINMUTEX_UNLOCK - // return ERR_RTP_FAKETRANS_PORTBASENOTEVEN; - //} - - // Try to obtain local IP addresses - - localIPs = params->GetLocalIPList(); - if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them - { - int status; - - if ((status = CreateLocalIPList()) < 0) - { - MAINMUTEX_UNLOCK - return status; - } - } - -//#ifdef RTP_SUPPORT_IPV4MULTICAST -// if (SetMulticastTTL(params->GetMulticastTTL())) -// supportsmulticasting = true; -// else -// supportsmulticasting = false; -//#else // no multicast support enabled - supportsmulticasting = false; -//#endif // RTP_SUPPORT_IPV4MULTICAST - - if (maximumpacketsize > RTPFAKETRANS_MAXPACKSIZE) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG; - } - - maxpacksize = maximumpacketsize; - portbase = params->GetPortbase(); - multicastTTL = params->GetMulticastTTL(); - receivemode = RTPTransmitter::AcceptAll; - - localhostname = 0; - localhostnamelength = 0; - - waitingfordata = false; - created = true; - - MAINMUTEX_UNLOCK - return 0; -} - -void RTPFakeTransmitter::Destroy() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK; - return; - } - - if (localhostname) - { - RTPDeleteByteArray(localhostname,GetMemoryManager()); - localhostname = 0; - localhostnamelength = 0; - } - - destinations.Clear(); -#ifdef RTP_SUPPORT_IPV4MULTICAST -// multicastgroups.Clear(); -#endif // RTP_SUPPORT_IPV4MULTICAST - FlushPackets(); - ClearAcceptIgnoreInfo(); - localIPs.clear(); - created = false; - RTPDelete(params,GetMemoryManager()); - - MAINMUTEX_UNLOCK -} - -RTPTransmissionInfo *RTPFakeTransmitter::GetTransmissionInfo() -{ - if (!init) - return 0; - - MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPFakeTransmissionInfo(localIPs, params); - MAINMUTEX_UNLOCK - return tinf; -} - -void RTPFakeTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *inf) -{ - if (!init) - return; - RTPDelete(inf,GetMemoryManager()); -} - -int RTPFakeTransmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - - if (localhostname == 0) - { - if (localIPs.empty()) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOLOCALIPS; - } - - std::list::const_iterator it; - std::list hostnames; - - for (it = localIPs.begin() ; it != localIPs.end() ; it++) - { - struct hostent *he; - uint8_t addr[4]; - uint32_t ip = (*it); - - addr[0] = (uint8_t)((ip>>24)&0xFF); - addr[1] = (uint8_t)((ip>>16)&0xFF); - addr[2] = (uint8_t)((ip>>8)&0xFF); - addr[3] = (uint8_t)(ip&0xFF); - he = gethostbyaddr((char *)addr,4,AF_INET); - if (he != 0) - { - std::string hname = std::string(he->h_name); - hostnames.push_back(hname); - } - } - - bool found = false; - - if (!hostnames.empty()) // try to select the most appropriate hostname - { - std::list::const_iterator it; - - for (it = hostnames.begin() ; !found && it != hostnames.end() ; it++) - { - if ((*it).find('.') != std::string::npos) - { - found = true; - localhostnamelength = (*it).length(); - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; - if (localhostname == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname,(*it).c_str(),localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - } - - if (!found) // use an IP address - { - uint32_t ip; - int len; - char str[16]; - - it = localIPs.begin(); - ip = (*it); - - snprintf(str,16,"%d.%d.%d.%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF)); - len = strlen(str); - - localhostnamelength = len; - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength + 1]; - if (localhostname == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname,str,localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - - if ((*bufferlength) < localhostnamelength) - { - *bufferlength = localhostnamelength; // tell the application the required size of the buffer - MAINMUTEX_UNLOCK - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } - - memcpy(buffer,localhostname,localhostnamelength); - *bufferlength = localhostnamelength; - - MAINMUTEX_UNLOCK - return 0; -} - -bool RTPFakeTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) -{ - if (!init) - return false; - - if (addr == 0) - return false; - - MAINMUTEX_LOCK - - bool v; - - if (created && addr->GetAddressType() == RTPAddress::IPv4Address) - { - const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; - bool found = false; - std::list::const_iterator it; - - it = localIPs.begin(); - while (!found && it != localIPs.end()) - { - if (addr2->GetIP() == *it) - found = true; - else - ++it; - } - - if (!found) - v = false; - else - { - if (addr2->GetPort() == params->GetPortbase()) // check for RTP port - v = true; - else if (addr2->GetPort() == (params->GetPortbase()+1)) // check for RTCP port - v = true; - else - v = false; - } - } - else - v = false; - - MAINMUTEX_UNLOCK - return v; -} - -int RTPFakeTransmitter::Poll() -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - int status; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - status = FakePoll(); // poll RTP socket - params->SetCurrentData(NULL); - MAINMUTEX_UNLOCK - return status; -} - -int RTPFakeTransmitter::WaitForIncomingData(const RTPTime &, bool *) -{ - return ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED; -} - -int RTPFakeTransmitter::AbortWait() -{ - return ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED; -} - -int RTPFakeTransmitter::SendRTPData(const void *data,size_t len) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG; - } - - - destinations.GotoFirstElement(); - // send to each destination - while (destinations.HasCurrentElement()) - { - (*params->GetPacketReadyCB())(params->GetPacketReadyCBData(), (uint8_t*)data, len, - destinations.GetCurrentElement().GetIP_NBO(), - destinations.GetCurrentElement().GetRTPPort_NBO(), - 1); - destinations.GotoNextElement(); - } - - MAINMUTEX_UNLOCK - return 0; -} - -int RTPFakeTransmitter::SendRTCPData(const void *data,size_t len) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - // send to each destination - while (destinations.HasCurrentElement()) - { - (*params->GetPacketReadyCB())(params->GetPacketReadyCBData(), (uint8_t*)data, len, - destinations.GetCurrentElement().GetIP_NBO(), - destinations.GetCurrentElement().GetRTCPPort_NBO(), - 0); - destinations.GotoNextElement(); - } - - MAINMUTEX_UNLOCK - return 0; -} - -int RTPFakeTransmitter::AddDestination(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; - } - - int status = destinations.AddElement(dest); - - MAINMUTEX_UNLOCK - return status; -} - -int RTPFakeTransmitter::DeleteDestination(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; - } - - int status = destinations.DeleteElement(dest); - - MAINMUTEX_UNLOCK - return status; -} - -void RTPFakeTransmitter::ClearDestinations() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (created) - destinations.Clear(); - MAINMUTEX_UNLOCK -} - -bool RTPFakeTransmitter::SupportsMulticasting() -{ - if (!init) - return false; - - MAINMUTEX_LOCK - - bool v; - - if (!created) - v = false; - else - v = supportsmulticasting; - - MAINMUTEX_UNLOCK - return v; -} - -#ifdef RTP_SUPPORT_IPV4MULTICAST - -int RTPFakeTransmitter::JoinMulticastGroup(const RTPAddress &) -{ -// hrrm wonder how will manage to get multicast info thru to the UDPSINK -/* if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - uint32_t mcastIP = address.GetIP(); - - if (!RTPFakeTRANS_IS_MCASTADDR(mcastIP)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS; - } - - status = multicastgroups.AddElement(mcastIP); - if (status >= 0) - { - RTPFakeTRANS_MCASTMEMBERSHIP(rtpsock,IP_ADD_MEMBERSHIP,mcastIP,status); - if (status != 0) - { - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP; - } - RTPFakeTRANS_MCASTMEMBERSHIP(rtcpsock,IP_ADD_MEMBERSHIP,mcastIP,status); - if (status != 0) - { - RTPFakeTRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP; - } - } - MAINMUTEX_UNLOCK - return status;*/ - return ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT; -} - -int RTPFakeTransmitter::LeaveMulticastGroup(const RTPAddress &) -{ - /* - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - uint32_t mcastIP = address.GetIP(); - - if (!RTPFakeTRANS_IS_MCASTADDR(mcastIP)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS; - } - - status = multicastgroups.DeleteElement(mcastIP); - if (status >= 0) - { - RTPFakeTRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - RTPFakeTRANS_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - status = 0; - } - - MAINMUTEX_UNLOCK - return status; - */ - return ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT; -} - -void RTPFakeTransmitter::LeaveAllMulticastGroups() -{ -/* if (!init) - return; - - MAINMUTEX_LOCK - if (created) - { - multicastgroups.GotoFirstElement(); - while (multicastgroups.HasCurrentElement()) - { - uint32_t mcastIP; - int status = 0; - - mcastIP = multicastgroups.GetCurrentElement(); - RTPFakeTRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - RTPFakeTRANS_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - multicastgroups.GotoNextElement(); - } - multicastgroups.Clear(); - } - MAINMUTEX_UNLOCK*/ -} - -#else // no multicast support - -int RTPFakeTransmitter::JoinMulticastGroup(const RTPAddress &addr) -{ - return ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT; -} - -int RTPFakeTransmitter::LeaveMulticastGroup(const RTPAddress &addr) -{ - return ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT; -} - -void RTPFakeTransmitter::LeaveAllMulticastGroups() -{ -} - -#endif // RTP_SUPPORT_IPV4MULTICAST - -int RTPFakeTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (m != receivemode) - { - receivemode = m; - acceptignoreinfo.Clear(); - } - MAINMUTEX_UNLOCK - return 0; -} - -int RTPFakeTransmitter::AddToIgnoreList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); - - MAINMUTEX_UNLOCK - return status; -} - -int RTPFakeTransmitter::DeleteFromIgnoreList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); - - MAINMUTEX_UNLOCK - return status; -} - -void RTPFakeTransmitter::ClearIgnoreList() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (created && receivemode == RTPTransmitter::IgnoreSome) - ClearAcceptIgnoreInfo(); - MAINMUTEX_UNLOCK -} - -int RTPFakeTransmitter::AddToAcceptList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); - - MAINMUTEX_UNLOCK - return status; -} - -int RTPFakeTransmitter::DeleteFromAcceptList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - - int status; - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); - - MAINMUTEX_UNLOCK - return status; -} - -void RTPFakeTransmitter::ClearAcceptList() -{ - if (!init) - return; - - MAINMUTEX_LOCK - if (created && receivemode == RTPTransmitter::AcceptSome) - ClearAcceptIgnoreInfo(); - MAINMUTEX_UNLOCK -} - -int RTPFakeTransmitter::SetMaximumPacketSize(size_t s) -{ - if (!init) - return ERR_RTP_FAKETRANS_NOTINIT; - - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_NOTCREATED; - } - if (s > RTPFAKETRANS_MAXPACKSIZE) - { - MAINMUTEX_UNLOCK - return ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG; - } - maxpacksize = s; - MAINMUTEX_UNLOCK - return 0; -} - -bool RTPFakeTransmitter::NewDataAvailable() -{ - if (!init) - return false; - - MAINMUTEX_LOCK - - bool v; - - if (!created) - v = false; - else - { - if (rawpacketlist.empty()) - v = false; - else - v = true; - } - - MAINMUTEX_UNLOCK - return v; -} - -RTPRawPacket *RTPFakeTransmitter::GetNextPacket() -{ - if (!init) - return 0; - - MAINMUTEX_LOCK - - RTPRawPacket *p; - - if (!created) - { - MAINMUTEX_UNLOCK - return 0; - } - if (rawpacketlist.empty()) - { - MAINMUTEX_UNLOCK - return 0; - } - - p = *(rawpacketlist.begin()); - rawpacketlist.pop_front(); - - MAINMUTEX_UNLOCK - return p; -} - -// Here the private functions start... - -#ifdef RTP_SUPPORT_IPV4MULTICAST -bool RTPFakeTransmitter::SetMulticastTTL(uint8_t) -{ -/* int ttl2,status; - - ttl2 = (int)ttl; - status = setsockopt(rtpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); - if (status != 0) - return false; - status = setsockopt(rtcpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); - if (status != 0) - return false; - return true;*/ - return true; -} -#endif // RTP_SUPPORT_IPV4MULTICAST - -void RTPFakeTransmitter::FlushPackets() -{ - std::list::const_iterator it; - - for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) - RTPDelete(*it,GetMemoryManager()); - rawpacketlist.clear(); -} - -int RTPFakeTransmitter::FakePoll() -{ - uint8_t *data = NULL; - int data_len = 0; - uint32_t sourceaddr; - uint16_t sourceport; - bool rtp; - bool acceptdata; - - RTPTime curtime = RTPTime::CurrentTime(); - - data = params->GetCurrentData(); - data_len = params->GetCurrentDataLen(); - rtp = params->GetCurrentDataType(); - sourceaddr = params->GetCurrentDataAddr(); - sourceport = params->GetCurrentDataPort(); - // lets make sure we got something - if (data == NULL || data_len <= 0) - { - return 0; - } - - // let's make a RTPIPv4Address - RTPIPv4Address *addr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv4Address(sourceaddr, sourceport); - if (addr == 0) - { - return ERR_RTP_OUTOFMEM; - } - - // ok we got the src addr, now this should be the actual packet - uint8_t *datacopy; - datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[data_len]; - if (datacopy == 0) - { - RTPDelete(addr,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - memcpy(datacopy, data, data_len); - - // got data, process it - if (receivemode == RTPTransmitter::AcceptAll) - acceptdata = true; - else - acceptdata = ShouldAcceptData(addr->GetIP(),addr->GetPort()); - - if (acceptdata) - { - // adding packet to queue - RTPRawPacket *pack; - - pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,data_len,addr,curtime,rtp,GetMemoryManager()); - - if (pack == 0) - { - RTPDelete(addr,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - rawpacketlist.push_back(pack); - } - return 0; -} - -int RTPFakeTransmitter::ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port) -{ - acceptignoreinfo.GotoElement(ip); - if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists - { - PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); - - if (port == 0) // select all ports - { - portinf->all = true; - portinf->portlist.clear(); - } - else if (!portinf->all) - { - std::list::const_iterator it,begin,end; - - begin = portinf->portlist.begin(); - end = portinf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == port) // already in list - return 0; - } - portinf->portlist.push_front(port); - } - } - else // got to create an entry for this IP address - { - PortInfo *portinf; - int status; - - portinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO) PortInfo(); - if (port == 0) // select all ports - portinf->all = true; - else - portinf->portlist.push_front(port); - - status = acceptignoreinfo.AddElement(ip,portinf); - if (status < 0) - { - RTPDelete(portinf,GetMemoryManager()); - return status; - } - } - - return 0; -} - -void RTPFakeTransmitter::ClearAcceptIgnoreInfo() -{ - acceptignoreinfo.GotoFirstElement(); - while (acceptignoreinfo.HasCurrentElement()) - { - PortInfo *inf; - - inf = acceptignoreinfo.GetCurrentElement(); - RTPDelete(inf,GetMemoryManager()); - acceptignoreinfo.GotoNextElement(); - } - acceptignoreinfo.Clear(); -} - -int RTPFakeTransmitter::ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port) -{ - acceptignoreinfo.GotoElement(ip); - if (!acceptignoreinfo.HasCurrentElement()) - return ERR_RTP_FAKETRANS_NOSUCHENTRY; - - PortInfo *inf; - - inf = acceptignoreinfo.GetCurrentElement(); - if (port == 0) // delete all entries - { - inf->all = false; - inf->portlist.clear(); - } - else // a specific port was selected - { - if (inf->all) // currently, all ports are selected. Add the one to remove to the list - { - // we have to check if the list doesn't contain the port already - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == port) // already in list: this means we already deleted the entry - return ERR_RTP_FAKETRANS_NOSUCHENTRY; - } - inf->portlist.push_front(port); - } - else // check if we can find the port in the list - { - std::list::iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; ++it) - { - if (*it == port) // found it! - { - inf->portlist.erase(it); - return 0; - } - } - // didn't find it - return ERR_RTP_FAKETRANS_NOSUCHENTRY; - } - } - return 0; -} - -bool RTPFakeTransmitter::ShouldAcceptData(uint32_t srcip,uint16_t srcport) -{ - if (receivemode == RTPTransmitter::AcceptSome) - { - PortInfo *inf; - - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return false; - - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // only accept the ones in the list - { - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return true; - } - return false; - } - else // accept all, except the ones in the list - { - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return false; - } - return true; - } - } - else // IgnoreSome - { - PortInfo *inf; - - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return true; - - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // ignore the ports in the list - { - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return false; - } - return true; - } - else // ignore all, except the ones in the list - { - std::list::const_iterator it,begin,end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return true; - } - return false; - } - } - return true; -} - -int RTPFakeTransmitter::CreateLocalIPList() -{ - // first try to obtain the list from the network interface info - - if (!GetLocalIPList_Interfaces()) - { - // If this fails, we'll have to depend on DNS info - GetLocalIPList_DNS(); - } - AddLoopbackAddress(); - return 0; -} - -//#ifdef WIN32 - -bool RTPFakeTransmitter::GetLocalIPList_Interfaces() -{ - // REMINDER: got to find out how to do this - return false; -} -/* -#else // use ioctl - -bool RTPFakeTransmitter::GetLocalIPList_Interfaces() -{ - int status; - char buffer[RTPFakeTRANS_IFREQBUFSIZE]; - struct ifconf ifc; - struct ifreq *ifr; - struct sockaddr *sa; - char *startptr,*endptr; - int remlen; - - ifc.ifc_len = RTPFakeTRANS_IFREQBUFSIZE; - ifc.ifc_buf = buffer; - status = ioctl(rtpsock,SIOCGIFCONF,&ifc); - if (status < 0) - return false; - - startptr = (char *)ifc.ifc_req; - endptr = startptr + ifc.ifc_len; - remlen = ifc.ifc_len; - while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) - { - ifr = (struct ifreq *)startptr; - sa = &(ifr->ifr_addr); -#ifdef RTP_HAVE_SOCKADDR_LEN - if (sa->sa_len <= sizeof(struct sockaddr)) - { - if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; - - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); - } - else - { - int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); - - remlen -= l; - startptr += l; - } -#else // don't have sa_len in struct sockaddr - if (sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; - - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); - -#endif // RTP_HAVE_SOCKADDR_LEN - } - - if (localIPs.empty()) - return false; - return true; -} - -#endif // WIN32 -*/ -void RTPFakeTransmitter::GetLocalIPList_DNS() -{ - struct hostent *he; - char name[1024]; - uint32_t ip; - bool done; - int i,j; - - gethostname(name,1023); - name[1023] = 0; - he = gethostbyname(name); - if (he == 0) - return; - - ip = 0; - i = 0; - done = false; - while (!done) - { - if (he->h_addr_list[i] == NULL) - done = true; - else - { - ip = 0; - for (j = 0 ; j < 4 ; j++) - ip |= ((uint32_t)((unsigned char)he->h_addr_list[i][j])<<((3-j)*8)); - localIPs.push_back(ip); - i++; - } - } -} - -void RTPFakeTransmitter::AddLoopbackAddress() -{ - uint32_t loopbackaddr = (((uint32_t)127)<<24)|((uint32_t)1); - std::list::const_iterator it; - bool found = false; - - for (it = localIPs.begin() ; !found && it != localIPs.end() ; it++) - { - if (*it == loopbackaddr) - found = true; - } - - if (!found) - localIPs.push_back(loopbackaddr); -} - - -} // end namespace - diff --git a/qrtplib/extratransmitters/rtpfaketransmitter.h b/qrtplib/extratransmitters/rtpfaketransmitter.h deleted file mode 100644 index 629ec3612..000000000 --- a/qrtplib/extratransmitters/rtpfaketransmitter.h +++ /dev/null @@ -1,236 +0,0 @@ -/* - - This class allows for jrtp to process packets without sending them out - anywhere. - The incoming messages are handed in to jrtp through the TransmissionParams - and can be retreived from jrtp through the normal polling mecanisms. - The outgoing RTP/RTCP packets are given to jrtp through the normal - session->SendPacket() and those packets are handed back out to the - client through a callback function (packet_ready_cb). - - example usage : Allows for integration of RTP into gstreamer. - - Copyright (c) 2005 Philippe Khalaf - - This file is a part of JRTPLIB - Copyright (c) 1999-2004 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the "Expertisecentrum Digitale Media" - (http://www.edm.luc.ac.be), a research center of the "Limburgs Universitair - Centrum" (http://www.luc.ac.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#ifndef RTPFAKETRANSMITTER_H - -#define RTPFAKETRANSMITTER_H - -#include "rtpconfig.h" - -#include "rtptransmitter.h" -#include "rtpipv4destination.h" -#include "rtphashtable.h" -#include "rtpkeyhashtable.h" -#include - -#define RTPFAKETRANS_HASHSIZE 8317 -#define RTPFAKETRANS_DEFAULTPORTBASE 5000 - -namespace qrtplib -{ - -// Definition of a callback that is called when a packet is ready for sending -// params (*data, data_len, dest_addr, dest_port, rtp [1 if rtp, 0 if rtcp]) -typedef void(*packet_ready_cb)(void*, uint8_t*, uint16_t, uint32_t, uint16_t, int rtp); - -class RTPFakeTransmissionParams : public RTPTransmissionParams -{ -public: - RTPFakeTransmissionParams():RTPTransmissionParams(RTPTransmitter::UserDefinedProto) { portbase = RTPFAKETRANS_DEFAULTPORTBASE; bindIP = 0; multicastTTL = 1; currentdata = NULL;} - void SetBindIP(uint32_t ip) { bindIP = ip; } - void SetPortbase(uint16_t pbase) { portbase = pbase; } - void SetMulticastTTL(uint8_t mcastTTL) { multicastTTL = mcastTTL; } - void SetLocalIPList(std::list &iplist) { localIPs = iplist; } - void ClearLocalIPList() { localIPs.clear(); } - void SetCurrentData(uint8_t *data) { currentdata = data; } - void SetCurrentDataLen(uint16_t len) { currentdatalen = len; } - void SetCurrentDataAddr(uint32_t addr) { currentdataaddr = addr; } - void SetCurrentDataPort(uint16_t port) { currentdataport = port; } - void SetCurrentDataType(bool type) { currentdatatype = type; } - void SetPacketReadyCB(packet_ready_cb cb) { packetreadycb = cb; }; - void SetPacketReadyCBData(void *data) { packetreadycbdata = data; }; - uint32_t GetBindIP() const { return bindIP; } - uint16_t GetPortbase() const { return portbase; } - uint8_t GetMulticastTTL() const { return multicastTTL; } - const std::list &GetLocalIPList() const { return localIPs; } - uint8_t* GetCurrentData() const { return currentdata; } - uint16_t GetCurrentDataLen() const { return currentdatalen; } - uint32_t GetCurrentDataAddr() const { return currentdataaddr; } - uint16_t GetCurrentDataPort() const { return currentdataport; } - bool GetCurrentDataType() const { return currentdatatype; } - packet_ready_cb GetPacketReadyCB() const { return packetreadycb; } - void* GetPacketReadyCBData() const { return packetreadycbdata; } -private: - uint16_t portbase; - uint32_t bindIP; - std::list localIPs; - uint8_t multicastTTL; - uint8_t* currentdata; - uint16_t currentdatalen; - uint32_t currentdataaddr; - uint16_t currentdataport; - bool currentdatatype; - packet_ready_cb packetreadycb; - void *packetreadycbdata; -}; - -class RTPFakeTransmissionInfo : public RTPTransmissionInfo -{ -public: - RTPFakeTransmissionInfo(std::list iplist, - RTPFakeTransmissionParams *transparams) : - RTPTransmissionInfo(RTPTransmitter::UserDefinedProto) - { localIPlist = iplist; params = transparams; } - - ~RTPFakeTransmissionInfo() { } - std::list GetLocalIPList() const { return localIPlist; } - RTPFakeTransmissionParams* GetTransParams() { return params; } -private: - std::list localIPlist; - RTPFakeTransmissionParams *params; -}; - -class RTPFakeTrans_GetHashIndex_IPv4Dest -{ -public: - static int GetIndex(const RTPIPv4Destination &d) { return d.GetIP()%RTPFAKETRANS_HASHSIZE; } -}; - -class RTPFakeTrans_GetHashIndex_uint32_t -{ -public: - static int GetIndex(const uint32_t &k) { return k%RTPFAKETRANS_HASHSIZE; } -}; - -#define RTPFAKETRANS_HEADERSIZE (20+8) - -class RTPFakeTransmitter : public RTPTransmitter -{ -public: - RTPFakeTransmitter(RTPMemoryManager *mgr); - ~RTPFakeTransmitter(); - - int Init(bool treadsafe); - int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - - int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() { return RTPFAKETRANS_HEADERSIZE; } - - int Poll(); - int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); - int AbortWait(); - - int SendRTPData(const void *data,size_t len); - int SendRTCPData(const void *data,size_t len); - - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); - - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); - - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); - - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); -private: - int CreateLocalIPList(); - bool GetLocalIPList_Interfaces(); - void GetLocalIPList_DNS(); - void AddLoopbackAddress(); - void FlushPackets(); - int FakePoll(); - int ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port); - int ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port); -#ifdef RTP_SUPPORT_IPV4MULTICAST - bool SetMulticastTTL(uint8_t ttl); -#endif // RTP_SUPPORT_IPV4MULTICAST - bool ShouldAcceptData(uint32_t srcip,uint16_t srcport); - void ClearAcceptIgnoreInfo(); - - RTPFakeTransmissionParams *params; - bool init; - bool created; - bool waitingfordata; - std::list localIPs; - uint16_t portbase; - uint8_t multicastTTL; - RTPTransmitter::ReceiveMode receivemode; - - uint8_t *localhostname; - size_t localhostnamelength; - - RTPHashTable destinations; -#ifdef RTP_SUPPORT_IPV4MULTICAST -// RTPHashTable multicastgroups; -#endif // RTP_SUPPORT_IPV4MULTICAST - std::list rawpacketlist; - - bool supportsmulticasting; - size_t maxpacksize; - - class PortInfo - { - public: - PortInfo() { all = false; } - - bool all; - std::list portlist; - }; - - RTPKeyHashTable acceptignoreinfo; - - int CreateAbortDescriptors(); - void DestroyAbortDescriptors(); - void AbortWaitInternal(); -}; - -} // end namespace - -#endif // RTPFAKETRANSMITTER_H - From 8c1293e213bc04f9ac4d5b97dbe637975e22ffa1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 02:44:27 +0100 Subject: [PATCH 019/956] qrtplib: restored new operator --- qrtplib/rtcpcompoundpacket.cpp | 12 ++++++------ qrtplib/rtcpcompoundpacketbuilder.cpp | 26 +++++++++++++------------- qrtplib/rtcpcompoundpacketbuilder.h | 2 +- qrtplib/rtcppacketbuilder.cpp | 4 ++-- qrtplib/rtcpsdesinfo.cpp | 2 +- qrtplib/rtcpsdesinfo.h | 2 +- qrtplib/rtpbyteaddress.cpp | 2 +- qrtplib/rtpconfig.h | 2 +- qrtplib/rtpexternaltransmitter.cpp | 16 ++++++++-------- qrtplib/rtphashtable.h | 2 +- qrtplib/rtpinternalsourcedata.cpp | 2 +- qrtplib/rtpipv4address.cpp | 2 +- qrtplib/rtpkeyhashtable.h | 2 +- qrtplib/rtpmemorymanager.h | 2 +- qrtplib/rtppacket.cpp | 2 +- qrtplib/rtppacketbuilder.cpp | 4 ++-- qrtplib/rtprawpacket.h | 2 +- qrtplib/rtpsession.cpp | 8 ++++---- qrtplib/rtpsources.cpp | 6 +++--- qrtplib/rtptcpaddress.cpp | 2 +- qrtplib/rtptcptransmitter.cpp | 8 ++++---- qrtplib/rtpudpv4transmitter.cpp | 14 +++++++------- qrtplib/rtpudpv4transmitternobind.cpp | 14 +++++++------- 23 files changed, 69 insertions(+), 69 deletions(-) diff --git a/qrtplib/rtcpcompoundpacket.cpp b/qrtplib/rtcpcompoundpacket.cpp index a6456a1e9..bafd577c7 100644 --- a/qrtplib/rtcpcompoundpacket.cpp +++ b/qrtplib/rtcpcompoundpacket.cpp @@ -154,22 +154,22 @@ int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen) switch (rtcphdr->packettype) { case RTP_RTCPTYPE_SR: - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSRPACKET) RTCPSRPacket(data,length); + p = new RTCPSRPacket(data,length); break; case RTP_RTCPTYPE_RR: - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPRRPACKET) RTCPRRPacket(data,length); + p = new RTCPRRPacket(data,length); break; case RTP_RTCPTYPE_SDES: - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSDESPACKET) RTCPSDESPacket(data,length); + p = new RTCPSDESPacket(data,length); break; case RTP_RTCPTYPE_BYE: - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPBYEPACKET) RTCPBYEPacket(data,length); + p = new RTCPBYEPacket(data,length); break; case RTP_RTCPTYPE_APP: - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPAPPPACKET) RTCPAPPPacket(data,length); + p = new RTCPAPPPacket(data,length); break; default: - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET) RTCPUnknownPacket(data,length); + p = new RTCPUnknownPacket(data,length); } if (p == 0) diff --git a/qrtplib/rtcpcompoundpacketbuilder.cpp b/qrtplib/rtcpcompoundpacketbuilder.cpp index 1a392e949..4f64d56ca 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.cpp +++ b/qrtplib/rtcpcompoundpacketbuilder.cpp @@ -231,7 +231,7 @@ int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc,uint8_t fractionlost if ((totalothersize+reportsizewithextrablock) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - uint8_t *buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPRECEIVERREPORT) uint8_t[sizeof(RTCPReceiverReport)]; + uint8_t *buf = new uint8_t[sizeof(RTCPReceiverReport)]; if (buf == 0) return ERR_RTP_OUTOFMEM; @@ -324,7 +324,7 @@ int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t,cons uint8_t *buf; size_t len; - buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPSDESBLOCK) uint8_t[sizeof(RTCPSDESHeader)+(size_t)itemlength]; + buf = new uint8_t[sizeof(RTCPSDESHeader)+(size_t)itemlength]; if (buf == 0) return ERR_RTP_OUTOFMEM; len = sizeof(RTCPSDESHeader)+(size_t)itemlength; @@ -366,7 +366,7 @@ int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata,uint8_t uint8_t *buf; size_t len; - buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPSDESBLOCK) uint8_t[sizeof(RTCPSDESHeader)+itemlength]; + buf = new uint8_t[sizeof(RTCPSDESHeader)+itemlength]; if (buf == 0) return ERR_RTP_OUTOFMEM; len = sizeof(RTCPSDESHeader)+(size_t)itemlength; @@ -423,7 +423,7 @@ int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs,uint8_t numssrcs,con uint8_t *buf; size_t numwords; - buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPBYEPACKET) uint8_t[packsize]; + buf = new uint8_t[packsize]; if (buf == 0) return ERR_RTP_OUTOFMEM; @@ -485,7 +485,7 @@ int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype,uint32_t ssrc,const uint8_t *buf; - buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPAPPPACKET) uint8_t[packsize]; + buf = new uint8_t[packsize]; if (buf == 0) return ERR_RTP_OUTOFMEM; @@ -533,7 +533,7 @@ int RTCPCompoundPacketBuilder::AddUnknownPacket(uint8_t payload_type, uint8_t su if ((totalotherbytes + packsize) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - uint8_t *buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET) uint8_t[packsize]; + uint8_t *buf = new uint8_t[packsize]; if (buf == 0) return ERR_RTP_OUTOFMEM; @@ -577,7 +577,7 @@ int RTCPCompoundPacketBuilder::EndBuild() if (!external) { - buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPCOMPOUNDPACKET) uint8_t[len]; + buf = new uint8_t[len]; if (buf == 0) return ERR_RTP_OUTOFMEM; } @@ -632,9 +632,9 @@ int RTCPCompoundPacketBuilder::EndBuild() // add entry in parent's list if (hdr->packettype == RTP_RTCPTYPE_SR) - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSRPACKET) RTCPSRPacket(curbuf,offset); + p = new RTCPSRPacket(curbuf,offset); else - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPRRPACKET) RTCPRRPacket(curbuf,offset); + p = new RTCPRRPacket(curbuf,offset); if (p == 0) { if (!external) @@ -708,7 +708,7 @@ int RTCPCompoundPacketBuilder::EndBuild() hdr->count = sourcecount; hdr->length = qToBigEndian((uint16_t)(numwords-1)); - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSDESPACKET) RTCPSDESPacket(curbuf,offset); + p = new RTCPSDESPacket(curbuf,offset); if (p == 0) { if (!external) @@ -733,7 +733,7 @@ int RTCPCompoundPacketBuilder::EndBuild() { memcpy(curbuf,(*it).packetdata,(*it).packetlength); - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPAPPPACKET) RTCPAPPPacket(curbuf,(*it).packetlength); + p = new RTCPAPPPacket(curbuf,(*it).packetlength); if (p == 0) { if (!external) @@ -758,7 +758,7 @@ int RTCPCompoundPacketBuilder::EndBuild() { memcpy(curbuf,(*it).packetdata,(*it).packetlength); - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET) RTCPUnknownPacket(curbuf,(*it).packetlength); + p = new RTCPUnknownPacket(curbuf,(*it).packetlength); if (p == 0) { if (!external) @@ -783,7 +783,7 @@ int RTCPCompoundPacketBuilder::EndBuild() { memcpy(curbuf,(*it).packetdata,(*it).packetlength); - p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPBYEPACKET) RTCPBYEPacket(curbuf,(*it).packetlength); + p = new RTCPBYEPacket(curbuf,(*it).packetlength); if (p == 0) { if (!external) diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index 09caec6c5..9c08bbe09 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -286,7 +286,7 @@ private: int AddSSRC(uint32_t ssrc) { - SDESSource *s = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_SDESSOURCE) SDESSource(ssrc,GetMemoryManager()); + SDESSource *s = new SDESSource(ssrc,GetMemoryManager()); if (s == 0) return ERR_RTP_OUTOFMEM; sdessources.push_back(s); diff --git a/qrtplib/rtcppacketbuilder.cpp b/qrtplib/rtcppacketbuilder.cpp index fb05ba339..4d74c3b76 100644 --- a/qrtplib/rtcppacketbuilder.cpp +++ b/qrtplib/rtcppacketbuilder.cpp @@ -111,7 +111,7 @@ int RTCPPacketBuilder::BuildNextPacket(RTCPCompoundPacket **pack) *pack = 0; - rtcpcomppack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPCOMPOUNDPACKETBUILDER) RTCPCompoundPacketBuilder(GetMemoryManager()); + rtcpcomppack = new RTCPCompoundPacketBuilder(GetMemoryManager()); if (rtcpcomppack == 0) return ERR_RTP_OUTOFMEM; @@ -634,7 +634,7 @@ int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack,const void *reas *pack = 0; - rtcpcomppack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPCOMPOUNDPACKETBUILDER) RTCPCompoundPacketBuilder(GetMemoryManager()); + rtcpcomppack = new RTCPCompoundPacketBuilder(GetMemoryManager()); if (rtcpcomppack == 0) return ERR_RTP_OUTOFMEM; diff --git a/qrtplib/rtcpsdesinfo.cpp b/qrtplib/rtcpsdesinfo.cpp index 612ef6bca..40840b95c 100644 --- a/qrtplib/rtcpsdesinfo.cpp +++ b/qrtplib/rtcpsdesinfo.cpp @@ -84,7 +84,7 @@ int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix,size_t prefixlen,const u int status; - item = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_SDESPRIVATEITEM) SDESPrivateItem(GetMemoryManager()); + item = new SDESPrivateItem(GetMemoryManager()); if (item == 0) return ERR_RTP_OUTOFMEM; if ((status = item->SetPrefix(prefix,prefixlen)) < 0) diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h index 30dff238b..894776828 100644 --- a/qrtplib/rtcpsdesinfo.h +++ b/qrtplib/rtcpsdesinfo.h @@ -170,7 +170,7 @@ private: else { len = (len>RTCP_SDES_MAXITEMLENGTH)?RTCP_SDES_MAXITEMLENGTH:len; - uint8_t *str2 = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_SDESITEM) uint8_t[len]; + uint8_t *str2 = new uint8_t[len]; if (str2 == 0) return ERR_RTP_OUTOFMEM; memcpy(str2,s,len); diff --git a/qrtplib/rtpbyteaddress.cpp b/qrtplib/rtpbyteaddress.cpp index 55bca7c24..ac3a88cb7 100644 --- a/qrtplib/rtpbyteaddress.cpp +++ b/qrtplib/rtpbyteaddress.cpp @@ -74,7 +74,7 @@ bool RTPByteAddress::IsFromSameHost(const RTPAddress *addr) const RTPAddress *RTPByteAddress::CreateCopy(RTPMemoryManager *mgr) const { JRTPLIB_UNUSED(mgr); // possibly unused - RTPByteAddress *a = RTPNew(mgr, RTPMEM_TYPE_CLASS_RTPADDRESS) RTPByteAddress(hostaddress, addresslength, port); + RTPByteAddress *a = new RTPByteAddress(hostaddress, addresslength, port); return a; } diff --git a/qrtplib/rtpconfig.h b/qrtplib/rtpconfig.h index 95785122b..67e602b2b 100644 --- a/qrtplib/rtpconfig.h +++ b/qrtplib/rtpconfig.h @@ -77,7 +77,7 @@ #define RTP_SUPPORT_SENDAPP -#define RTP_SUPPORT_MEMORYMANAGEMENT +// No #define RTP_SUPPORT_MEMORYMANAGEMENT // No support for sending unknown RTCP packets diff --git a/qrtplib/rtpexternaltransmitter.cpp b/qrtplib/rtpexternaltransmitter.cpp index 9d7016f97..740b533e5 100644 --- a/qrtplib/rtpexternaltransmitter.cpp +++ b/qrtplib/rtpexternaltransmitter.cpp @@ -167,7 +167,7 @@ RTPTransmissionInfo *RTPExternalTransmitter::GetTransmissionInfo() return 0; MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPExternalTransmissionInfo(&packetinjector); + RTPTransmissionInfo *tinf = new RTPExternalTransmissionInfo(&packetinjector); MAINMUTEX_UNLOCK return tinf; } @@ -204,7 +204,7 @@ int RTPExternalTransmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlengt name[1023] = 0; // ensure null-termination localhostnamelength = strlen(name); - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + localhostname = new uint8_t [localhostnamelength+1]; memcpy(localhostname, name, localhostnamelength); localhostname[localhostnamelength] = 0; @@ -575,7 +575,7 @@ void RTPExternalTransmitter::InjectRTP(const void *data, size_t len, const RTPAd uint8_t *datacopy; - datacopy = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET) uint8_t[len]; + datacopy = new uint8_t[len]; if (datacopy == 0) { RTPDelete(addr,GetMemoryManager()); @@ -586,7 +586,7 @@ void RTPExternalTransmitter::InjectRTP(const void *data, size_t len, const RTPAd RTPTime curtime = RTPTime::CurrentTime(); RTPRawPacket *pack; - pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,len,addr,curtime,true,GetMemoryManager()); + pack = new RTPRawPacket(datacopy,len,addr,curtime,true,GetMemoryManager()); if (pack == 0) { RTPDelete(addr,GetMemoryManager()); @@ -622,7 +622,7 @@ void RTPExternalTransmitter::InjectRTCP(const void *data, size_t len, const RTPA uint8_t *datacopy; - datacopy = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[len]; + datacopy = new uint8_t[len]; if (datacopy == 0) { RTPDelete(addr,GetMemoryManager()); @@ -633,7 +633,7 @@ void RTPExternalTransmitter::InjectRTCP(const void *data, size_t len, const RTPA RTPTime curtime = RTPTime::CurrentTime(); RTPRawPacket *pack; - pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,len,addr,curtime,false,GetMemoryManager()); + pack = new RTPRawPacket(datacopy,len,addr,curtime,false,GetMemoryManager()); if (pack == 0) { RTPDelete(addr,GetMemoryManager()); @@ -677,7 +677,7 @@ void RTPExternalTransmitter::InjectRTPorRTCP(const void *data, size_t len, const rtp = false; } - datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[len]; + datacopy = new uint8_t[len]; if (datacopy == 0) { RTPDelete(addr,GetMemoryManager()); @@ -688,7 +688,7 @@ void RTPExternalTransmitter::InjectRTPorRTCP(const void *data, size_t len, const RTPTime curtime = RTPTime::CurrentTime(); RTPRawPacket *pack; - pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,len,addr,curtime,rtp,GetMemoryManager()); + pack = new RTPRawPacket(datacopy,len,addr,curtime,rtp,GetMemoryManager()); if (pack == 0) { RTPDelete(addr,GetMemoryManager()); diff --git a/qrtplib/rtphashtable.h b/qrtplib/rtphashtable.h index 4920384f4..d95b0be00 100644 --- a/qrtplib/rtphashtable.h +++ b/qrtplib/rtphashtable.h @@ -265,7 +265,7 @@ inline int RTPHashTable::AddElement(const Element &el // Okay, the key doesn't exist, so we can add the new element in the hash table - newelem = RTPNew(GetMemoryManager(),memorytype) HashElement(elem,index); + newelem = new HashElement(elem,index); if (newelem == 0) return ERR_RTP_OUTOFMEM; diff --git a/qrtplib/rtpinternalsourcedata.cpp b/qrtplib/rtpinternalsourcedata.cpp index 8ab88749f..bd731ee47 100644 --- a/qrtplib/rtpinternalsourcedata.cpp +++ b/qrtplib/rtpinternalsourcedata.cpp @@ -280,7 +280,7 @@ int RTPInternalSourceData::ProcessBYEPacket(const uint8_t *reason,size_t reasonl } byetime = receivetime; - byereason = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTCPBYEREASON) uint8_t[reasonlen]; + byereason = new uint8_t[reasonlen]; if (byereason == 0) return ERR_RTP_OUTOFMEM; memcpy(byereason,reason,reasonlen); diff --git a/qrtplib/rtpipv4address.cpp b/qrtplib/rtpipv4address.cpp index 59d81c503..4d64309e3 100644 --- a/qrtplib/rtpipv4address.cpp +++ b/qrtplib/rtpipv4address.cpp @@ -65,7 +65,7 @@ bool RTPIPv4Address::IsFromSameHost(const RTPAddress *addr) const RTPAddress *RTPIPv4Address::CreateCopy(RTPMemoryManager *mgr) const { JRTPLIB_UNUSED(mgr); // possibly unused - RTPIPv4Address *a = RTPNew(mgr,RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv4Address(ip,port); + RTPIPv4Address *a = new RTPIPv4Address(ip,port); return a; } diff --git a/qrtplib/rtpkeyhashtable.h b/qrtplib/rtpkeyhashtable.h index e6be72ee1..5e51ad876 100644 --- a/qrtplib/rtpkeyhashtable.h +++ b/qrtplib/rtpkeyhashtable.h @@ -268,7 +268,7 @@ inline int RTPKeyHashTable::AddElement(const Key // Okay, the key doesn't exist, so we can add the new element in the hash table - newelem = RTPNew(GetMemoryManager(),memorytype) HashElement(k,elem,index); + newelem = new HashElement(k,elem,index); if (newelem == 0) return ERR_RTP_OUTOFMEM; diff --git a/qrtplib/rtpmemorymanager.h b/qrtplib/rtpmemorymanager.h index f677a8b93..4484a74fa 100644 --- a/qrtplib/rtpmemorymanager.h +++ b/qrtplib/rtpmemorymanager.h @@ -235,7 +235,7 @@ inline void RTPDelete(ClassName *obj, RTPMemoryManager *mgr) #else -#define RTPNew(a,b) new +//#define RTPNew(a,b) new #define RTPDelete(a,b) delete a #define RTPDeleteByteArray(a,b) delete [] a; diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp index 46eaabcc2..e71444c47 100644 --- a/qrtplib/rtppacket.cpp +++ b/qrtplib/rtppacket.cpp @@ -244,7 +244,7 @@ int RTPPacket::BuildPacket(uint8_t payloadtype,const void *payloaddata,size_t pa if (buffer == 0) { - packet = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTPPACKET) uint8_t [packetlength]; + packet = new uint8_t [packetlength]; if (packet == 0) { packetlength = 0; diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib/rtppacketbuilder.cpp index 39b38ce97..e3bdc3185 100644 --- a/qrtplib/rtppacketbuilder.cpp +++ b/qrtplib/rtppacketbuilder.cpp @@ -61,7 +61,7 @@ int RTPPacketBuilder::Init(size_t max) return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; maxpacksize = max; - buffer = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTPPACKETBUILDERBUFFER) uint8_t [max]; + buffer = new uint8_t [max]; if (buffer == 0) return ERR_RTP_OUTOFMEM; packetlength = 0; @@ -92,7 +92,7 @@ int RTPPacketBuilder::SetMaximumPacketSize(size_t max) if (max <= 0) return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; - newbuf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTPPACKETBUILDERBUFFER) uint8_t[max]; + newbuf = new uint8_t[max]; if (newbuf == 0) return ERR_RTP_OUTOFMEM; diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h index 063564f83..b63292a25 100644 --- a/qrtplib/rtprawpacket.h +++ b/qrtplib/rtprawpacket.h @@ -134,7 +134,7 @@ inline void RTPRawPacket::DeleteData() inline uint8_t *RTPRawPacket::AllocateBytes(bool isrtp, int recvlen) const { JRTPLIB_UNUSED(isrtp); // possibly unused - return RTPNew(GetMemoryManager(),(isrtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[recvlen]; + return new uint8_t[recvlen]; } inline void RTPRawPacket::SetData(uint8_t *data, size_t datalen) diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index 7b5f83d3c..fded610e1 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -118,10 +118,10 @@ int RTPSession::Create(const RTPSessionParams &sessparams,const RTPTransmissionP switch(protocol) { case RTPTransmitter::IPv4UDPProto: - rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPUDPv4Transmitter(GetMemoryManager()); + rtptrans = new RTPUDPv4Transmitter(GetMemoryManager()); break; case RTPTransmitter::ExternalProto: - rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPExternalTransmitter(GetMemoryManager()); + rtptrans = new RTPExternalTransmitter(GetMemoryManager()); break; case RTPTransmitter::UserDefinedProto: rtptrans = NewUserDefinedTransmitter(); @@ -129,7 +129,7 @@ int RTPSession::Create(const RTPSessionParams &sessparams,const RTPTransmissionP return ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL; break; case RTPTransmitter::TCPProto: - rtptrans = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMITTER) RTPTCPTransmitter(GetMemoryManager()); + rtptrans = new RTPTCPTransmitter(GetMemoryManager()); break; default: return ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL; @@ -677,7 +677,7 @@ int RTPSession::SendUnknownPacket(bool sr, uint8_t payload_type, uint8_t subtype uint32_t ssrc = packetbuilder.GetSSRC(); BUILDER_UNLOCK - RTCPCompoundPacketBuilder* rtcpcomppack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPCOMPOUNDPACKETBUILDER) RTCPCompoundPacketBuilder(GetMemoryManager()); + RTCPCompoundPacketBuilder* rtcpcomppack = new RTCPCompoundPacketBuilder(GetMemoryManager()); if (rtcpcomppack == 0) { RTPDelete(rtcpcomppack,GetMemoryManager()); diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp index 9e5fb4ebc..89dab14a2 100644 --- a/qrtplib/rtpsources.cpp +++ b/qrtplib/rtpsources.cpp @@ -173,7 +173,7 @@ int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *rtptrans[ RTPPacket *rtppack; // First, we'll see if the packet can be parsed - rtppack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPPACKET) RTPPacket(*rawpack,GetMemoryManager()); + rtppack = new RTPPacket(*rawpack,GetMemoryManager()); if (rtppack == 0) return ERR_RTP_OUTOFMEM; if ((status = rtppack->GetCreationError()) < 0) @@ -821,9 +821,9 @@ int RTPSources::ObtainSourceDataInstance(uint32_t ssrc,RTPInternalSourceData **s if (sourcelist.GotoElement(ssrc) < 0) // No entry for this source { #ifdef RTP_SUPPORT_PROBATION - srcdat2 = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPINTERNALSOURCEDATA) RTPInternalSourceData(ssrc,probationtype,GetMemoryManager()); + srcdat2 = new RTPInternalSourceData(ssrc,probationtype,GetMemoryManager()); #else - srcdat2 = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPINTERNALSOURCEDATA) RTPInternalSourceData(ssrc,RTPSources::NoProbation,GetMemoryManager()); + srcdat2 = new RTPInternalSourceData(ssrc,RTPSources::NoProbation,GetMemoryManager()); #endif // RTP_SUPPORT_PROBATION if (srcdat2 == 0) return ERR_RTP_OUTOFMEM; diff --git a/qrtplib/rtptcpaddress.cpp b/qrtplib/rtptcpaddress.cpp index ccb9f7851..980ba9661 100644 --- a/qrtplib/rtptcpaddress.cpp +++ b/qrtplib/rtptcpaddress.cpp @@ -60,7 +60,7 @@ bool RTPTCPAddress::IsFromSameHost(const RTPAddress *addr) const RTPAddress *RTPTCPAddress::CreateCopy(RTPMemoryManager *mgr) const { JRTPLIB_UNUSED(mgr); // possibly unused - RTPTCPAddress *a = RTPNew(mgr,RTPMEM_TYPE_CLASS_RTPADDRESS) RTPTCPAddress(m_socket); + RTPTCPAddress *a = new RTPTCPAddress(m_socket); return a; } diff --git a/qrtplib/rtptcptransmitter.cpp b/qrtplib/rtptcptransmitter.cpp index 9bb3f33ee..df441c27e 100644 --- a/qrtplib/rtptcptransmitter.cpp +++ b/qrtplib/rtptcptransmitter.cpp @@ -173,7 +173,7 @@ RTPTransmissionInfo *RTPTCPTransmitter::GetTransmissionInfo() return 0; MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPTCPTransmissionInfo(); + RTPTransmissionInfo *tinf = new RTPTCPTransmissionInfo(); MAINMUTEX_UNLOCK return tinf; } @@ -703,7 +703,7 @@ int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata) int dataLength = sdata.m_dataLength; sdata.Reset(); - RTPTCPAddress *pAddr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPTCPAddress(sock); + RTPTCPAddress *pAddr = new RTPTCPAddress(sock); if (pAddr == 0) return ERR_RTP_OUTOFMEM; @@ -717,7 +717,7 @@ int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata) isrtp = false; } - RTPRawPacket *pPack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp, GetMemoryManager()); + RTPRawPacket *pPack = new RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp, GetMemoryManager()); if (pPack == 0) { RTPDelete(pAddr,GetMemoryManager()); @@ -865,7 +865,7 @@ int RTPTCPTransmitter::SocketData::ProcessAvailableBytes(SocketType sock, int av l = 1; // We don't yet know if it's an RTP or RTCP packet, so we'll stick to RTP - m_pDataBuffer = RTPNew(pMgr, RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET) uint8_t[l]; + m_pDataBuffer = new uint8_t[l]; if (m_pDataBuffer == 0) return ERR_RTP_OUTOFMEM; } diff --git a/qrtplib/rtpudpv4transmitter.cpp b/qrtplib/rtpudpv4transmitter.cpp index f2eae3e01..2a2f28c23 100644 --- a/qrtplib/rtpudpv4transmitter.cpp +++ b/qrtplib/rtpudpv4transmitter.cpp @@ -559,7 +559,7 @@ RTPTransmissionInfo *RTPUDPv4Transmitter::GetTransmissionInfo() return 0; MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPUDPv4TransmissionInfo(localIPs,rtpsock,rtcpsock,m_rtpPort,m_rtcpPort); + RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionInfo(localIPs,rtpsock,rtcpsock,m_rtpPort,m_rtcpPort); MAINMUTEX_UNLOCK return tinf; } @@ -657,7 +657,7 @@ int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) { found = true; localhostnamelength = (*it).length(); - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + localhostname = new uint8_t [localhostnamelength+1]; if (localhostname == 0) { MAINMUTEX_UNLOCK @@ -682,7 +682,7 @@ int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) len = strlen(str); localhostnamelength = len; - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength + 1]; + localhostname = new uint8_t [localhostnamelength + 1]; if (localhostname == 0) { MAINMUTEX_UNLOCK @@ -1487,10 +1487,10 @@ int RTPUDPv4Transmitter::PollSocket(bool rtp) RTPIPv4Address *addr; uint8_t *datacopy; - addr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); + addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); if (addr == 0) return ERR_RTP_OUTOFMEM; - datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[recvlen]; + datacopy = new uint8_t[recvlen]; if (datacopy == 0) { RTPDelete(addr,GetMemoryManager()); @@ -1513,7 +1513,7 @@ int RTPUDPv4Transmitter::PollSocket(bool rtp) } } - pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,recvlen,addr,curtime,isrtp,GetMemoryManager()); + pack = new RTPRawPacket(datacopy,recvlen,addr,curtime,isrtp,GetMemoryManager()); if (pack == 0) { RTPDelete(addr,GetMemoryManager()); @@ -1560,7 +1560,7 @@ int RTPUDPv4Transmitter::ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port) PortInfo *portinf; int status; - portinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO) PortInfo(); + portinf = new PortInfo(); if (port == 0) // select all ports portinf->all = true; else diff --git a/qrtplib/rtpudpv4transmitternobind.cpp b/qrtplib/rtpudpv4transmitternobind.cpp index ae4d9e48d..538469bec 100644 --- a/qrtplib/rtpudpv4transmitternobind.cpp +++ b/qrtplib/rtpudpv4transmitternobind.cpp @@ -577,7 +577,7 @@ RTPTransmissionInfo *RTPUDPv4TransmitterNoBind::GetTransmissionInfo() return 0; MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO) RTPUDPv4TransmissionNoBindInfo(localIPs,rtpsock,rtcpsock,m_rtpPort,m_rtcpPort); + RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionNoBindInfo(localIPs,rtpsock,rtcpsock,m_rtpPort,m_rtcpPort); MAINMUTEX_UNLOCK return tinf; } @@ -675,7 +675,7 @@ int RTPUDPv4TransmitterNoBind::GetLocalHostName(uint8_t *buffer, size_t *bufferl { found = true; localhostnamelength = (*it).length(); - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; + localhostname = new uint8_t [localhostnamelength+1]; if (localhostname == 0) { MAINMUTEX_UNLOCK @@ -700,7 +700,7 @@ int RTPUDPv4TransmitterNoBind::GetLocalHostName(uint8_t *buffer, size_t *bufferl len = strlen(str); localhostnamelength = len; - localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength + 1]; + localhostname = new uint8_t [localhostnamelength + 1]; if (localhostname == 0) { MAINMUTEX_UNLOCK @@ -1505,10 +1505,10 @@ int RTPUDPv4TransmitterNoBind::PollSocket(bool rtp) RTPIPv4Address *addr; uint8_t *datacopy; - addr = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPADDRESS) RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); + addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); if (addr == 0) return ERR_RTP_OUTOFMEM; - datacopy = RTPNew(GetMemoryManager(),(rtp)?RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET:RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET) uint8_t[recvlen]; + datacopy = new uint8_t[recvlen]; if (datacopy == 0) { RTPDelete(addr,GetMemoryManager()); @@ -1531,7 +1531,7 @@ int RTPUDPv4TransmitterNoBind::PollSocket(bool rtp) } } - pack = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPRAWPACKET) RTPRawPacket(datacopy,recvlen,addr,curtime,isrtp,GetMemoryManager()); + pack = new RTPRawPacket(datacopy,recvlen,addr,curtime,isrtp,GetMemoryManager()); if (pack == 0) { RTPDelete(addr,GetMemoryManager()); @@ -1578,7 +1578,7 @@ int RTPUDPv4TransmitterNoBind::ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t PortInfo *portinf; int status; - portinf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO) PortInfo(); + portinf = new PortInfo(); if (port == 0) // select all ports portinf->all = true; else From 2393957834ca4d85228db70254c533037f986a54 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 08:42:22 +0100 Subject: [PATCH 020/956] DATV demod: some fixes to enhance stability --- plugins/channelrx/demoddatv/datvdemod.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 73a411c6b..8f7a28059 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -166,10 +166,13 @@ bool DATVDemod::PlayVideo(bool blnStartStop) return true; } - m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender,m_objVideoStream); - m_objVideoStream->MultiThreaded=true; - m_objVideoStream->ThreadTimeOut=5000; //5000 ms - m_objRenderThread->start(); + if(m_objVideoStream->bytesAvailable()>0) + { + m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender,m_objVideoStream); + m_objVideoStream->MultiThreaded=true; + m_objVideoStream->ThreadTimeOut=5000; //5000 ms + m_objRenderThread->start(); + } return true; } From 6bb5e165cfd354b1e170ba464c86bb6ac501eb54 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 19:13:18 +0100 Subject: [PATCH 021/956] qrtplib: added missing file --- qrtplib/rtpendian.h | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 qrtplib/rtpendian.h diff --git a/qrtplib/rtpendian.h b/qrtplib/rtpendian.h new file mode 100644 index 000000000..9c03534d4 --- /dev/null +++ b/qrtplib/rtpendian.h @@ -0,0 +1,39 @@ +/* + * rtpendian.h + * + * Created on: Feb 27, 2018 + * Author: f4exb + */ + +#ifndef QRTPLIB_RTPENDIAN_H_ +#define QRTPLIB_RTPENDIAN_H_ + +#include + +namespace qrtplib +{ + +class RTPEndian +{ +public: + RTPEndian() + { + uint32_t endianTest32 = 1; + uint8_t *ptr = (uint8_t*) &endianTest32; + m_isLittleEndian = (*ptr == 1); + } + + template + T qToHost(const T& x) const { + return m_isLittleEndian ? qToLittleEndian(x) : qToBigEndian(x); + } + +private: + bool m_isLittleEndian; +}; + +} + + + +#endif /* QRTPLIB_RTPENDIAN_H_ */ From 219cfb071263cf45cea6aad9e093bb0b605defd7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 19:26:52 +0100 Subject: [PATCH 022/956] Make DeviceUISet and DeviceSet classes as they are forward declared like this --- sdrgui/device/deviceuiset.h | 3 ++- sdrsrv/device/deviceset.cpp | 2 +- sdrsrv/device/deviceset.h | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sdrgui/device/deviceuiset.h b/sdrgui/device/deviceuiset.h index b2978f175..79c9bae3d 100644 --- a/sdrgui/device/deviceuiset.h +++ b/sdrgui/device/deviceuiset.h @@ -32,8 +32,9 @@ class DeviceSinkAPI; class ChannelMarker; class PluginAPI; -struct DeviceUISet +class DeviceUISet { +public: SpectrumVis *m_spectrumVis; GLSpectrum *m_spectrum; GLSpectrumGUI *m_spectrumGUI; diff --git a/sdrsrv/device/deviceset.cpp b/sdrsrv/device/deviceset.cpp index ae58535ea..469628f47 100644 --- a/sdrsrv/device/deviceset.cpp +++ b/sdrsrv/device/deviceset.cpp @@ -295,7 +295,7 @@ void DeviceSet::loadTxChannelSettings(const Preset *preset, PluginAPI *pluginAPI // everything, that is still "available" is not needed anymore for(int i = 0; i < openChannels.count(); i++) { - qDebug("DeviceUISet::loadChannelSettings: destroying spare channel [%s]", qPrintable(openChannels[i].m_channelName)); + qDebug("DeviceSet::loadChannelSettings: destroying spare channel [%s]", qPrintable(openChannels[i].m_channelName)); openChannels[i].m_channelSourceAPI->destroy(); } diff --git a/sdrsrv/device/deviceset.h b/sdrsrv/device/deviceset.h index 0eecab2d7..dd072c0f2 100644 --- a/sdrsrv/device/deviceset.h +++ b/sdrsrv/device/deviceset.h @@ -28,8 +28,9 @@ class ChannelSinkAPI; class ChannelSourceAPI; class Preset; -struct DeviceSet +class DeviceSet { +public: DSPDeviceSourceEngine *m_deviceSourceEngine; DeviceSourceAPI *m_deviceSourceAPI; DSPDeviceSinkEngine *m_deviceSinkEngine; From ff4a35b3e36cbe74eb91628c364e50a82c76da09 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 23:05:46 +0100 Subject: [PATCH 023/956] qrtplib: removed memory management --- qrtplib/CMakeLists.txt | 2 - qrtplib/rtcpapppacket.cpp | 86 +- qrtplib/rtcpapppacket.h | 130 +- qrtplib/rtcpbyepacket.cpp | 102 +- qrtplib/rtcpbyepacket.h | 146 +- qrtplib/rtcpcompoundpacket.cpp | 284 +-- qrtplib/rtcpcompoundpacket.h | 138 +- qrtplib/rtcpcompoundpacketbuilder.cpp | 1087 +++++----- qrtplib/rtcpcompoundpacketbuilder.h | 625 +++--- qrtplib/rtcppacket.h | 112 +- qrtplib/rtcppacketbuilder.cpp | 1201 +++++------ qrtplib/rtcppacketbuilder.h | 447 ++-- qrtplib/rtcprrpacket.cpp | 88 +- qrtplib/rtcprrpacket.h | 231 +- qrtplib/rtcpscheduler.cpp | 482 ++--- qrtplib/rtcpscheduler.h | 269 +-- qrtplib/rtcpsdesinfo.cpp | 256 +-- qrtplib/rtcpsdesinfo.h | 370 ++-- qrtplib/rtcpsdespacket.cpp | 209 +- qrtplib/rtcpsdespacket.h | 540 ++--- qrtplib/rtcpsrpacket.cpp | 89 +- qrtplib/rtcpsrpacket.h | 278 +-- qrtplib/rtcpunknownpacket.h | 73 +- qrtplib/rtpabortdescriptors.cpp | 314 +-- qrtplib/rtpabortdescriptors.h | 100 +- qrtplib/rtpaddress.h | 117 +- qrtplib/rtpbyteaddress.cpp | 100 +- qrtplib/rtpbyteaddress.h | 114 +- qrtplib/rtpcollisionlist.cpp | 141 +- qrtplib/rtpcollisionlist.h | 103 +- qrtplib/rtpconfig.h | 50 +- qrtplib/rtpdefines.h | 46 +- qrtplib/rtpendian.h | 5 +- qrtplib/rtperrors.cpp | 475 +++-- qrtplib/rtperrors.h | 46 +- qrtplib/rtpexternaltransmitter.cpp | 925 ++++---- qrtplib/rtpexternaltransmitter.h | 243 ++- qrtplib/rtphashtable.h | 471 +++-- qrtplib/rtpinternalsourcedata.cpp | 435 ++-- qrtplib/rtpinternalsourcedata.h | 196 +- qrtplib/rtpinternalutils.h | 66 +- qrtplib/rtpipv4address.cpp | 87 +- qrtplib/rtpipv4address.h | 217 +- qrtplib/rtpipv4destination.cpp | 57 +- qrtplib/rtpipv4destination.h | 160 +- qrtplib/rtpkeyhashtable.h | 484 +++-- qrtplib/rtplibraryversion.cpp | 54 +- qrtplib/rtplibraryversion.h | 84 +- qrtplib/rtplibraryversioninternal.h | 46 +- qrtplib/rtpmemorymanager.h | 245 --- qrtplib/rtpmemoryobject.h | 74 - qrtplib/rtppacket.cpp | 469 ++--- qrtplib/rtppacket.h | 277 ++- qrtplib/rtppacketbuilder.cpp | 333 ++- qrtplib/rtppacketbuilder.h | 400 ++-- qrtplib/rtprandom.cpp | 114 +- qrtplib/rtprandom.h | 78 +- qrtplib/rtprandomrand48.cpp | 84 +- qrtplib/rtprandomrand48.h | 66 +- qrtplib/rtprandomrands.cpp | 175 +- qrtplib/rtprandomrands.h | 66 +- qrtplib/rtprandomurandom.cpp | 132 +- qrtplib/rtprandomurandom.h | 66 +- qrtplib/rtprawpacket.h | 197 +- qrtplib/rtpselect.h | 224 +- qrtplib/rtpsession.cpp | 2139 ++++++++++--------- qrtplib/rtpsession.h | 1001 ++++----- qrtplib/rtpsessionparams.cpp | 101 +- qrtplib/rtpsessionparams.h | 458 ++-- qrtplib/rtpsessionsources.cpp | 114 +- qrtplib/rtpsessionsources.h | 117 +- qrtplib/rtpsocketutil.h | 49 +- qrtplib/rtpsocketutilinternal.h | 104 +- qrtplib/rtpsourcedata.cpp | 439 ++-- qrtplib/rtpsourcedata.h | 1092 ++++++---- qrtplib/rtpsources.cpp | 1948 +++++++++-------- qrtplib/rtpsources.h | 599 +++--- qrtplib/rtpstructs.h | 126 +- qrtplib/rtptcpaddress.cpp | 74 +- qrtplib/rtptcpaddress.h | 85 +- qrtplib/rtptcptransmitter.cpp | 1194 ++++++----- qrtplib/rtptcptransmitter.h | 238 ++- qrtplib/rtptimeutilities.cpp | 52 +- qrtplib/rtptimeutilities.h | 413 ++-- qrtplib/rtptransmitter.h | 330 +-- qrtplib/rtptypes.h | 1 - qrtplib/rtptypes_win.h | 46 +- qrtplib/rtpudpv4transmitter.cpp | 2802 ++++++++++++------------- qrtplib/rtpudpv4transmitter.h | 565 +++-- qrtplib/rtpudpv4transmitternobind.cpp | 2731 ++++++++++++------------ qrtplib/rtpudpv4transmitternobind.h | 567 +++-- 91 files changed, 16931 insertions(+), 15835 deletions(-) delete mode 100644 qrtplib/rtpmemorymanager.h delete mode 100644 qrtplib/rtpmemoryobject.h diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 3135b6ef4..482bb077e 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -25,8 +25,6 @@ set (qrtplib_HEADERS rtpipv4destination.h rtpkeyhashtable.h rtplibraryversion.h - rtpmemorymanager.h - rtpmemoryobject.h rtppacket.h rtppacketbuilder.h rtprandom.h diff --git a/qrtplib/rtcpapppacket.cpp b/qrtplib/rtcpapppacket.cpp index f6807bec2..7aeb9abd3 100644 --- a/qrtplib/rtcpapppacket.cpp +++ b/qrtplib/rtcpapppacket.cpp @@ -1,64 +1,64 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcpapppacket.h" namespace qrtplib { -RTCPAPPPacket::RTCPAPPPacket(uint8_t *data,size_t datalength) - : RTCPPacket(APP,data,datalength) +RTCPAPPPacket::RTCPAPPPacket(uint8_t *data, size_t datalength) : + RTCPPacket(APP, data, datalength) { - knownformat = false; + knownformat = false; - RTCPCommonHeader *hdr; - size_t len = datalength; + RTCPCommonHeader *hdr; + size_t len = datalength; - hdr = (RTCPCommonHeader *)data; - if (hdr->padding) - { - uint8_t padcount = data[datalength-1]; - if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) - return; - if (((size_t)padcount) >= len) - return; - len -= (size_t)padcount; - } + hdr = (RTCPCommonHeader *) data; + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t) padcount) >= len) + return; + len -= (size_t) padcount; + } - if (len < (sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2)) - return; - len -= (sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2); - appdatalen = len; - knownformat = true; + if (len < (sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2)) + return; + len -= (sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2); + appdatalen = len; + knownformat = true; } } // end namespace diff --git a/qrtplib/rtcpapppacket.h b/qrtplib/rtcpapppacket.h index dacfb2265..c6fe0bbd6 100644 --- a/qrtplib/rtcpapppacket.h +++ b/qrtplib/rtcpapppacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpapppacket.h @@ -49,77 +49,79 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP APP packet. */ -class JRTPLIB_IMPORTEXPORT RTCPAPPPacket : public RTCPPacket +class JRTPLIB_IMPORTEXPORT RTCPAPPPacket: public RTCPPacket { public: - /** Creates an instance based on the data in \c data with length \c datalen. - * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer - * is referenced inside the class (no copy of the data is made) one must make sure that the memory it - * points to is valid as long as the class instance exists. - */ - RTCPAPPPacket(uint8_t *data,size_t datalen); - ~RTCPAPPPacket() { } + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPAPPPacket(uint8_t *data, size_t datalen); + ~RTCPAPPPacket() + { + } - /** Returns the subtype contained in the APP packet. */ - uint8_t GetSubType() const; + /** Returns the subtype contained in the APP packet. */ + uint8_t GetSubType() const; - /** Returns the SSRC of the source which sent this packet. */ - uint32_t GetSSRC() const; + /** Returns the SSRC of the source which sent this packet. */ + uint32_t GetSSRC() const; - /** Returns the name contained in the APP packet. - * Returns the name contained in the APP packet. This alway consists of four bytes and is not NULL-terminated. - */ - uint8_t *GetName(); + /** Returns the name contained in the APP packet. + * Returns the name contained in the APP packet. This alway consists of four bytes and is not NULL-terminated. + */ + uint8_t *GetName(); - /** Returns a pointer to the actual data. */ - uint8_t *GetAPPData(); + /** Returns a pointer to the actual data. */ + uint8_t *GetAPPData(); - /** Returns the length of the actual data. */ - size_t GetAPPDataLength() const; + /** Returns the length of the actual data. */ + size_t GetAPPDataLength() const; private: - RTPEndian m_endian; - size_t appdatalen; + RTPEndian m_endian; + size_t appdatalen; }; inline uint8_t RTCPAPPPacket::GetSubType() const { - if (!knownformat) - return 0; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; - return hdr->count; + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return hdr->count; } inline uint32_t RTCPAPPPacket::GetSSRC() const { - if (!knownformat) - return 0; + if (!knownformat) + return 0; - uint32_t *ssrc = (uint32_t *)(data+sizeof(RTCPCommonHeader)); - return m_endian.qToHost(*ssrc); + uint32_t *ssrc = (uint32_t *) (data + sizeof(RTCPCommonHeader)); + return m_endian.qToHost(*ssrc); } inline uint8_t *RTCPAPPPacket::GetName() { - if (!knownformat) - return 0; + if (!knownformat) + return 0; - return (data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); + return (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); } inline uint8_t *RTCPAPPPacket::GetAPPData() { - if (!knownformat) - return 0; - if (appdatalen == 0) - return 0; - return (data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2); + if (!knownformat) + return 0; + if (appdatalen == 0) + return 0; + return (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2); } inline size_t RTCPAPPPacket::GetAPPDataLength() const { - if (!knownformat) - return 0; - return appdatalen; + if (!knownformat) + return 0; + return appdatalen; } } // end namespace diff --git a/qrtplib/rtcpbyepacket.cpp b/qrtplib/rtcpbyepacket.cpp index a7115e5d1..443f47797 100644 --- a/qrtplib/rtcpbyepacket.cpp +++ b/qrtplib/rtcpbyepacket.cpp @@ -1,72 +1,72 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcpbyepacket.h" namespace qrtplib { -RTCPBYEPacket::RTCPBYEPacket(uint8_t *data,size_t datalength) - : RTCPPacket(BYE,data,datalength) +RTCPBYEPacket::RTCPBYEPacket(uint8_t *data, size_t datalength) : + RTCPPacket(BYE, data, datalength) { - knownformat = false; - reasonoffset = 0; + knownformat = false; + reasonoffset = 0; - RTCPCommonHeader *hdr; - size_t len = datalength; + RTCPCommonHeader *hdr; + size_t len = datalength; - hdr = (RTCPCommonHeader *)data; - if (hdr->padding) - { - uint8_t padcount = data[datalength-1]; - if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) - return; - if (((size_t)padcount) >= len) - return; - len -= (size_t)padcount; - } + hdr = (RTCPCommonHeader *) data; + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t) padcount) >= len) + return; + len -= (size_t) padcount; + } - size_t ssrclen = ((size_t)(hdr->count))*sizeof(uint32_t) + sizeof(RTCPCommonHeader); - if (ssrclen > len) - return; - if (ssrclen < len) // there's probably a reason for leaving - { - uint8_t *reasonlength = (data+ssrclen); - size_t reaslen = (size_t)(*reasonlength); - if (reaslen > (len-ssrclen-1)) - return; - reasonoffset = ssrclen; - } - knownformat = true; + size_t ssrclen = ((size_t)(hdr->count)) * sizeof(uint32_t) + sizeof(RTCPCommonHeader); + if (ssrclen > len) + return; + if (ssrclen < len) // there's probably a reason for leaving + { + uint8_t *reasonlength = (data + ssrclen); + size_t reaslen = (size_t)(*reasonlength); + if (reaslen > (len - ssrclen - 1)) + return; + reasonoffset = ssrclen; + } + knownformat = true; } } // end namespace diff --git a/qrtplib/rtcpbyepacket.h b/qrtplib/rtcpbyepacket.h index 1da3676a3..a6022175f 100644 --- a/qrtplib/rtcpbyepacket.h +++ b/qrtplib/rtcpbyepacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpbyepacket.h @@ -49,85 +49,87 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP BYE packet. */ -class JRTPLIB_IMPORTEXPORT RTCPBYEPacket : public RTCPPacket +class JRTPLIB_IMPORTEXPORT RTCPBYEPacket: public RTCPPacket { public: - /** Creates an instance based on the data in \c data with length \c datalen. - * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer - * is referenced inside the class (no copy of the data is made) one must make sure that the memory it - * points to is valid as long as the class instance exists. - */ - RTCPBYEPacket(uint8_t *data,size_t datalen); - ~RTCPBYEPacket() { } + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPBYEPacket(uint8_t *data, size_t datalen); + ~RTCPBYEPacket() + { + } - /** Returns the number of SSRC identifiers present in this BYE packet. */ - int GetSSRCCount() const; + /** Returns the number of SSRC identifiers present in this BYE packet. */ + int GetSSRCCount() const; - /** Returns the SSRC described by \c index which may have a value from 0 to GetSSRCCount()-1 - * (note that no check is performed to see if \c index is valid). - */ - uint32_t GetSSRC(int index) const; // note: no check is performed to see if index is valid! + /** Returns the SSRC described by \c index which may have a value from 0 to GetSSRCCount()-1 + * (note that no check is performed to see if \c index is valid). + */ + uint32_t GetSSRC(int index) const; // note: no check is performed to see if index is valid! - /** Returns true if the BYE packet contains a reason for leaving. */ - bool HasReasonForLeaving() const; + /** Returns true if the BYE packet contains a reason for leaving. */ + bool HasReasonForLeaving() const; - /** Returns the length of the string which describes why the source(s) left. */ - size_t GetReasonLength() const; + /** Returns the length of the string which describes why the source(s) left. */ + size_t GetReasonLength() const; - /** Returns the actual reason for leaving data. */ - uint8_t *GetReasonData(); + /** Returns the actual reason for leaving data. */ + uint8_t *GetReasonData(); private: - RTPEndian m_endian; - size_t reasonoffset; + RTPEndian m_endian; + size_t reasonoffset; }; inline int RTCPBYEPacket::GetSSRCCount() const { - if (!knownformat) - return 0; + if (!knownformat) + return 0; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; - return (int)(hdr->count); + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return (int) (hdr->count); } inline uint32_t RTCPBYEPacket::GetSSRC(int index) const { - if (!knownformat) - return 0; - uint32_t *ssrc = (uint32_t *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)*index); - return m_endian.qToHost(*ssrc); + if (!knownformat) + return 0; + uint32_t *ssrc = (uint32_t *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) * index); + return m_endian.qToHost(*ssrc); } inline bool RTCPBYEPacket::HasReasonForLeaving() const { - if (!knownformat) - return false; - if (reasonoffset == 0) - return false; - return true; + if (!knownformat) + return false; + if (reasonoffset == 0) + return false; + return true; } inline size_t RTCPBYEPacket::GetReasonLength() const { - if (!knownformat) - return 0; - if (reasonoffset == 0) - return 0; - uint8_t *reasonlen = (data+reasonoffset); - return (size_t)(*reasonlen); + if (!knownformat) + return 0; + if (reasonoffset == 0) + return 0; + uint8_t *reasonlen = (data + reasonoffset); + return (size_t)(*reasonlen); } inline uint8_t *RTCPBYEPacket::GetReasonData() { - if (!knownformat) - return 0; - if (reasonoffset == 0) - return 0; - uint8_t *reasonlen = (data+reasonoffset); - if ((*reasonlen) == 0) - return 0; - return (data+reasonoffset+1); + if (!knownformat) + return 0; + if (reasonoffset == 0) + return 0; + uint8_t *reasonlen = (data + reasonoffset); + if ((*reasonlen) == 0) + return 0; + return (data + reasonoffset + 1); } } // end namespace diff --git a/qrtplib/rtcpcompoundpacket.cpp b/qrtplib/rtcpcompoundpacket.cpp index bafd577c7..4a0e1d228 100644 --- a/qrtplib/rtcpcompoundpacket.cpp +++ b/qrtplib/rtcpcompoundpacket.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcpcompoundpacket.h" #include "rtprawpacket.h" @@ -45,168 +45,168 @@ namespace qrtplib { -RTCPCompoundPacket::RTCPCompoundPacket(RTPRawPacket &rawpack, RTPMemoryManager *mgr) : RTPMemoryObject(mgr) +RTCPCompoundPacket::RTCPCompoundPacket(RTPRawPacket &rawpack) { - compoundpacket = 0; - compoundpacketlength = 0; - error = 0; + compoundpacket = 0; + compoundpacketlength = 0; + error = 0; - if (rawpack.IsRTP()) - { - error = ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; - return; - } + if (rawpack.IsRTP()) + { + error = ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + return; + } - uint8_t *data = rawpack.GetData(); - size_t datalen = rawpack.GetDataLength(); + uint8_t *data = rawpack.GetData(); + size_t datalen = rawpack.GetDataLength(); - error = ParseData(data,datalen); - if (error < 0) - return; + error = ParseData(data, datalen); + if (error < 0) + return; - compoundpacket = rawpack.GetData(); - compoundpacketlength = rawpack.GetDataLength(); - deletepacket = true; + compoundpacket = rawpack.GetData(); + compoundpacketlength = rawpack.GetDataLength(); + deletepacket = true; - rawpack.ZeroData(); + rawpack.ZeroData(); - rtcppackit = rtcppacklist.begin(); + rtcppackit = rtcppacklist.begin(); } -RTCPCompoundPacket::RTCPCompoundPacket(uint8_t *packet, size_t packetlen, bool deletedata, RTPMemoryManager *mgr) : RTPMemoryObject(mgr) +RTCPCompoundPacket::RTCPCompoundPacket(uint8_t *packet, size_t packetlen, bool deletedata) { - compoundpacket = 0; - compoundpacketlength = 0; + compoundpacket = 0; + compoundpacketlength = 0; - error = ParseData(packet,packetlen); - if (error < 0) - return; + error = ParseData(packet, packetlen); + if (error < 0) + return; - compoundpacket = packet; - compoundpacketlength = packetlen; - deletepacket = deletedata; + compoundpacket = packet; + compoundpacketlength = packetlen; + deletepacket = deletedata; - rtcppackit = rtcppacklist.begin(); + rtcppackit = rtcppacklist.begin(); } -RTCPCompoundPacket::RTCPCompoundPacket(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) +RTCPCompoundPacket::RTCPCompoundPacket() { - compoundpacket = 0; - compoundpacketlength = 0; - error = 0; - deletepacket = true; + compoundpacket = 0; + compoundpacketlength = 0; + error = 0; + deletepacket = true; } int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen) { - bool first; + bool first; - if (datalen < sizeof(RTCPCommonHeader)) - return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + if (datalen < sizeof(RTCPCommonHeader)) + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; - first = true; + first = true; - do - { - RTCPCommonHeader *rtcphdr; - size_t length; + do + { + RTCPCommonHeader *rtcphdr; + size_t length; - rtcphdr = (RTCPCommonHeader *)data; - if (rtcphdr->version != RTP_VERSION) // check version - { - ClearPacketList(); - return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; - } - if (first) - { - // Check if first packet is SR or RR + rtcphdr = (RTCPCommonHeader *) data; + if (rtcphdr->version != RTP_VERSION) // check version + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + if (first) + { + // Check if first packet is SR or RR - first = false; - if ( ! (rtcphdr->packettype == RTP_RTCPTYPE_SR || rtcphdr->packettype == RTP_RTCPTYPE_RR)) - { - ClearPacketList(); - return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; - } - } + first = false; + if (!(rtcphdr->packettype == RTP_RTCPTYPE_SR || rtcphdr->packettype == RTP_RTCPTYPE_RR)) + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + } - length = (size_t)m_endian.qToHost(rtcphdr->length); - length++; - length *= sizeof(uint32_t); + length = (size_t) m_endian.qToHost(rtcphdr->length); + length++; + length *= sizeof(uint32_t); - if (length > datalen) // invalid length field - { - ClearPacketList(); - return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; - } + if (length > datalen) // invalid length field + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } - if (rtcphdr->padding) - { - // check if it's the last packet - if (length != datalen) - { - ClearPacketList(); - return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; - } - } + if (rtcphdr->padding) + { + // check if it's the last packet + if (length != datalen) + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + } - RTCPPacket *p; + RTCPPacket *p; - switch (rtcphdr->packettype) - { - case RTP_RTCPTYPE_SR: - p = new RTCPSRPacket(data,length); - break; - case RTP_RTCPTYPE_RR: - p = new RTCPRRPacket(data,length); - break; - case RTP_RTCPTYPE_SDES: - p = new RTCPSDESPacket(data,length); - break; - case RTP_RTCPTYPE_BYE: - p = new RTCPBYEPacket(data,length); - break; - case RTP_RTCPTYPE_APP: - p = new RTCPAPPPacket(data,length); - break; - default: - p = new RTCPUnknownPacket(data,length); - } + switch (rtcphdr->packettype) + { + case RTP_RTCPTYPE_SR: + p = new RTCPSRPacket(data, length); + break; + case RTP_RTCPTYPE_RR: + p = new RTCPRRPacket(data, length); + break; + case RTP_RTCPTYPE_SDES: + p = new RTCPSDESPacket(data, length); + break; + case RTP_RTCPTYPE_BYE: + p = new RTCPBYEPacket(data, length); + break; + case RTP_RTCPTYPE_APP: + p = new RTCPAPPPacket(data, length); + break; + default: + p = new RTCPUnknownPacket(data, length); + } - if (p == 0) - { - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } + if (p == 0) + { + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } - rtcppacklist.push_back(p); + rtcppacklist.push_back(p); - datalen -= length; - data += length; - } while (datalen >= (size_t)sizeof(RTCPCommonHeader)); + datalen -= length; + data += length; + } while (datalen >= (size_t) sizeof(RTCPCommonHeader)); - if (datalen != 0) // some remaining bytes - { - ClearPacketList(); - return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; - } - return 0; + if (datalen != 0) // some remaining bytes + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + return 0; } RTCPCompoundPacket::~RTCPCompoundPacket() { - ClearPacketList(); - if (compoundpacket && deletepacket) - RTPDeleteByteArray(compoundpacket,GetMemoryManager()); + ClearPacketList(); + if (compoundpacket && deletepacket) + delete[] compoundpacket; } void RTCPCompoundPacket::ClearPacketList() { - std::list::const_iterator it; + std::list::const_iterator it; - for (it = rtcppacklist.begin() ; it != rtcppacklist.end() ; it++) - RTPDelete(*it,GetMemoryManager()); - rtcppacklist.clear(); - rtcppackit = rtcppacklist.begin(); + for (it = rtcppacklist.begin(); it != rtcppacklist.end(); it++) + delete *it; + rtcppacklist.clear(); + rtcppackit = rtcppacklist.begin(); } } // end namespace diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h index 3d9164d55..70b66bfc7 100644 --- a/qrtplib/rtcpcompoundpacket.h +++ b/qrtplib/rtcpcompoundpacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpcompoundpacket.h @@ -40,7 +40,6 @@ #include "rtpconfig.h" #include "rtptypes.h" -#include "rtpmemoryobject.h" #include "rtpendian.h" #include @@ -51,57 +50,76 @@ class RTPRawPacket; class RTCPPacket; /** Represents an RTCP compound packet. */ -class JRTPLIB_IMPORTEXPORT RTCPCompoundPacket : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTCPCompoundPacket { public: - /** Creates an RTCPCompoundPacket instance from the data in \c rawpack, installing a memory manager if specified. */ - RTCPCompoundPacket(RTPRawPacket &rawpack, RTPMemoryManager *memmgr = 0); + /** Creates an RTCPCompoundPacket instance from the data in \c rawpack, installing a memory manager if specified. */ + RTCPCompoundPacket(RTPRawPacket &rawpack); - /** Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. - * Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. The \c deletedata - * flag specifies if the data in \c packet should be deleted when the compound packet is destroyed. If - * specified, a memory manager will be installed. - */ - RTCPCompoundPacket(uint8_t *packet, size_t len, bool deletedata = true, RTPMemoryManager *memmgr = 0); + /** Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. + * Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. The \c deletedata + * flag specifies if the data in \c packet should be deleted when the compound packet is destroyed. If + * specified, a memory manager will be installed. + */ + RTCPCompoundPacket(uint8_t *packet, size_t len, bool deletedata = true); protected: - RTCPCompoundPacket(RTPMemoryManager *memmgr); // this is for the compoundpacket builder + RTCPCompoundPacket(); // this is for the compoundpacket builder public: - virtual ~RTCPCompoundPacket(); + virtual ~RTCPCompoundPacket(); - /** Checks if the RTCP compound packet was created successfully. - * If the raw packet data in the constructor could not be parsed, this function returns the error code of - * what went wrong. If the packet had an invalid format, the return value is \c ERR_RTP_RTCPCOMPOUND_INVALIDPACKET. - */ - int GetCreationError() { return error; } + /** Checks if the RTCP compound packet was created successfully. + * If the raw packet data in the constructor could not be parsed, this function returns the error code of + * what went wrong. If the packet had an invalid format, the return value is \c ERR_RTP_RTCPCOMPOUND_INVALIDPACKET. + */ + int GetCreationError() + { + return error; + } - /** Returns a pointer to the data of the entire RTCP compound packet. */ - uint8_t *GetCompoundPacketData() { return compoundpacket; } + /** Returns a pointer to the data of the entire RTCP compound packet. */ + uint8_t *GetCompoundPacketData() + { + return compoundpacket; + } - /** Returns the size of the entire RTCP compound packet. */ - size_t GetCompoundPacketLength() { return compoundpacketlength; } + /** Returns the size of the entire RTCP compound packet. */ + size_t GetCompoundPacketLength() + { + return compoundpacketlength; + } - /** Starts the iteration over the individual RTCP packets in the RTCP compound packet. */ - void GotoFirstPacket() { rtcppackit = rtcppacklist.begin(); } + /** Starts the iteration over the individual RTCP packets in the RTCP compound packet. */ + void GotoFirstPacket() + { + rtcppackit = rtcppacklist.begin(); + } - /** Returns a pointer to the next individual RTCP packet. - * Returns a pointer to the next individual RTCP packet. Note that no \c delete call may be done - * on the RTCPPacket instance which is returned. - */ - RTCPPacket *GetNextPacket() { if (rtcppackit == rtcppacklist.end()) return 0; RTCPPacket *p = *rtcppackit; rtcppackit++; return p; } + /** Returns a pointer to the next individual RTCP packet. + * Returns a pointer to the next individual RTCP packet. Note that no \c delete call may be done + * on the RTCPPacket instance which is returned. + */ + RTCPPacket *GetNextPacket() + { + if (rtcppackit == rtcppacklist.end()) + return 0; + RTCPPacket *p = *rtcppackit; + rtcppackit++; + return p; + } protected: - void ClearPacketList(); - int ParseData(uint8_t *packet, size_t len); + void ClearPacketList(); + int ParseData(uint8_t *packet, size_t len); - RTPEndian m_endian; - int error; + RTPEndian m_endian; + int error; - uint8_t *compoundpacket; - size_t compoundpacketlength; - bool deletepacket; + uint8_t *compoundpacket; + size_t compoundpacketlength; + bool deletepacket; - std::list rtcppacklist; - std::list::const_iterator rtcppackit; + std::list rtcppacklist; + std::list::const_iterator rtcppackit; }; } // end namespace diff --git a/qrtplib/rtcpcompoundpacketbuilder.cpp b/qrtplib/rtcpcompoundpacketbuilder.cpp index 4f64d56ca..13434293f 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.cpp +++ b/qrtplib/rtcpcompoundpacketbuilder.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcpcompoundpacketbuilder.h" #include "rtcpsrpacket.h" @@ -37,771 +37,768 @@ #include "rtcpbyepacket.h" #include "rtcpapppacket.h" #ifdef RTP_SUPPORT_RTCPUNKNOWN - #include "rtcpunknownpacket.h" +#include "rtcpunknownpacket.h" #endif // RTP_SUPPORT_RTCPUNKNOWN #include namespace qrtplib { -RTCPCompoundPacketBuilder::RTCPCompoundPacketBuilder(RTPMemoryManager *mgr) : RTCPCompoundPacket(mgr), report(mgr), sdes(mgr) +RTCPCompoundPacketBuilder::RTCPCompoundPacketBuilder() { - byesize = 0; - appsize = 0; + byesize = 0; + appsize = 0; #ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownsize = 0; + unknownsize = 0; #endif // RTP_SUPPORT_RTCPUNKNOWN - maximumpacketsize = 0; - buffer = 0; - external = false; - arebuilding = false; + maximumpacketsize = 0; + buffer = 0; + external = false; + arebuilding = false; } RTCPCompoundPacketBuilder::~RTCPCompoundPacketBuilder() { - if (external) - compoundpacket = 0; // make sure RTCPCompoundPacket doesn't delete the external buffer - ClearBuildBuffers(); + if (external) + compoundpacket = 0; // make sure RTCPCompoundPacket doesn't delete the external buffer + ClearBuildBuffers(); } void RTCPCompoundPacketBuilder::ClearBuildBuffers() { - report.Clear(); - sdes.Clear(); + report.Clear(); + sdes.Clear(); - std::list::const_iterator it; - for (it = byepackets.begin() ; it != byepackets.end() ; it++) - { - if ((*it).packetdata) - RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); - } - for (it = apppackets.begin() ; it != apppackets.end() ; it++) - { - if ((*it).packetdata) - RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); - } + std::list::const_iterator it; + for (it = byepackets.begin(); it != byepackets.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } + for (it = apppackets.begin(); it != apppackets.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } #ifdef RTP_SUPPORT_RTCPUNKNOWN - for (it = unknownpackets.begin() ; it != unknownpackets.end() ; it++) - { - if ((*it).packetdata) - RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); - } + for (it = unknownpackets.begin(); it != unknownpackets.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } #endif // RTP_SUPPORT_RTCPUNKNOWN - byepackets.clear(); - apppackets.clear(); + byepackets.clear(); + apppackets.clear(); #ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownpackets.clear(); + unknownpackets.clear(); #endif // RTP_SUPPORT_RTCPUNKNOWN - byesize = 0; - appsize = 0; + byesize = 0; + appsize = 0; #ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownsize = 0; + unknownsize = 0; #endif // RTP_SUPPORT_RTCPUNKNOWN } int RTCPCompoundPacketBuilder::InitBuild(size_t maxpacketsize) { - if (arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; - if (compoundpacket) - return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT; + if (arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; + if (compoundpacket) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT; - if (maxpacketsize < RTP_MINPACKETSIZE) - return ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL; + if (maxpacketsize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL; - maximumpacketsize = maxpacketsize; - buffer = 0; - external = false; - byesize = 0; - appsize = 0; + maximumpacketsize = maxpacketsize; + buffer = 0; + external = false; + byesize = 0; + appsize = 0; #ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownsize = 0; + unknownsize = 0; #endif // RTP_SUPPORT_RTCPUNKNOWN - arebuilding = true; - return 0; + arebuilding = true; + return 0; } -int RTCPCompoundPacketBuilder::InitBuild(void *externalbuffer,size_t buffersize) +int RTCPCompoundPacketBuilder::InitBuild(void *externalbuffer, size_t buffersize) { - if (arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; - if (compoundpacket) - return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT; + if (arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; + if (compoundpacket) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT; - if (buffersize < RTP_MINPACKETSIZE) - return ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL; + if (buffersize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL; - maximumpacketsize = buffersize; - buffer = (uint8_t *)externalbuffer; - external = true; - byesize = 0; - appsize = 0; + maximumpacketsize = buffersize; + buffer = (uint8_t *) externalbuffer; + external = true; + byesize = 0; + appsize = 0; #ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownsize = 0; + unknownsize = 0; #endif // RTP_SUPPORT_RTCPUNKNOWN - arebuilding = true; - return 0; + arebuilding = true; + return 0; } -int RTCPCompoundPacketBuilder::StartSenderReport(uint32_t senderssrc,const RTPNTPTime &ntptimestamp,uint32_t rtptimestamp, - uint32_t packetcount,uint32_t octetcount) +int RTCPCompoundPacketBuilder::StartSenderReport(uint32_t senderssrc, const RTPNTPTime &ntptimestamp, uint32_t rtptimestamp, uint32_t packetcount, uint32_t octetcount) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - if (report.headerlength != 0) - return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; + if (report.headerlength != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; #ifndef RTP_SUPPORT_RTCPUNKNOWN - size_t totalsize = byesize+appsize+sdes.NeededBytes(); + size_t totalsize = byesize + appsize + sdes.NeededBytes(); #else - size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes(); + size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - size_t sizeleft = maximumpacketsize-totalsize; - size_t neededsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+sizeof(RTCPSenderReport); + size_t sizeleft = maximumpacketsize - totalsize; + size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport); - if (neededsize > sizeleft) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if (neededsize > sizeleft) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - // fill in some things + // fill in some things - report.headerlength = sizeof(uint32_t)+sizeof(RTCPSenderReport); - report.isSR = true; + report.headerlength = sizeof(uint32_t) + sizeof(RTCPSenderReport); + report.isSR = true; - uint32_t *ssrc = (uint32_t *)report.headerdata; - *ssrc = qToBigEndian(senderssrc); + uint32_t *ssrc = (uint32_t *) report.headerdata; + *ssrc = qToBigEndian(senderssrc); - RTCPSenderReport *sr = (RTCPSenderReport *)(report.headerdata + sizeof(uint32_t)); - sr->ntptime_msw = qToBigEndian(ntptimestamp.GetMSW()); - sr->ntptime_lsw = qToBigEndian(ntptimestamp.GetLSW()); - sr->rtptimestamp = qToBigEndian(rtptimestamp); - sr->packetcount = qToBigEndian(packetcount); - sr->octetcount = qToBigEndian(octetcount); + RTCPSenderReport *sr = (RTCPSenderReport *) (report.headerdata + sizeof(uint32_t)); + sr->ntptime_msw = qToBigEndian(ntptimestamp.GetMSW()); + sr->ntptime_lsw = qToBigEndian(ntptimestamp.GetLSW()); + sr->rtptimestamp = qToBigEndian(rtptimestamp); + sr->packetcount = qToBigEndian(packetcount); + sr->octetcount = qToBigEndian(octetcount); - return 0; + return 0; } int RTCPCompoundPacketBuilder::StartReceiverReport(uint32_t senderssrc) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - if (report.headerlength != 0) - return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; #ifndef RTP_SUPPORT_RTCPUNKNOWN - size_t totalsize = byesize+appsize+sdes.NeededBytes(); + size_t totalsize = byesize + appsize + sdes.NeededBytes(); #else - size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes(); + size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - size_t sizeleft = maximumpacketsize-totalsize; - size_t neededsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t); + size_t sizeleft = maximumpacketsize - totalsize; + size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t); - if (neededsize > sizeleft) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if (neededsize > sizeleft) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - // fill in some things + // fill in some things - report.headerlength = sizeof(uint32_t); - report.isSR = false; + report.headerlength = sizeof(uint32_t); + report.isSR = false; - uint32_t *ssrc = (uint32_t *)report.headerdata; - *ssrc = qToBigEndian(senderssrc); + uint32_t *ssrc = (uint32_t *) report.headerdata; + *ssrc = qToBigEndian(senderssrc); - return 0; + return 0; } -int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t packetslost,uint32_t exthighestseq, - uint32_t jitter,uint32_t lsr,uint32_t dlsr) +int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc, uint8_t fractionlost, int32_t packetslost, uint32_t exthighestseq, uint32_t jitter, uint32_t lsr, uint32_t dlsr) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - if (report.headerlength == 0) - return ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength == 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED; #ifndef RTP_SUPPORT_RTCPUNKNOWN - size_t totalothersize = byesize+appsize+sdes.NeededBytes(); + size_t totalothersize = byesize + appsize + sdes.NeededBytes(); #else - size_t totalothersize = byesize+appsize+unknownsize+sdes.NeededBytes(); + size_t totalothersize = byesize+appsize+unknownsize+sdes.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - size_t reportsizewithextrablock = report.NeededBytesWithExtraReportBlock(); + size_t reportsizewithextrablock = report.NeededBytesWithExtraReportBlock(); - if ((totalothersize+reportsizewithextrablock) > maximumpacketsize) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if ((totalothersize + reportsizewithextrablock) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - uint8_t *buf = new uint8_t[sizeof(RTCPReceiverReport)]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; + uint8_t *buf = new uint8_t[sizeof(RTCPReceiverReport)]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; - RTCPReceiverReport *rr = (RTCPReceiverReport *)buf; - uint32_t *packlost = (uint32_t *)&packetslost; - uint32_t packlost2 = (*packlost); + RTCPReceiverReport *rr = (RTCPReceiverReport *) buf; + uint32_t *packlost = (uint32_t *) &packetslost; + uint32_t packlost2 = (*packlost); - rr->ssrc = qToBigEndian(ssrc); - rr->fractionlost = fractionlost; - rr->packetslost[2] = (uint8_t)(packlost2&0xFF); - rr->packetslost[1] = (uint8_t)((packlost2>>8)&0xFF); - rr->packetslost[0] = (uint8_t)((packlost2>>16)&0xFF); - rr->exthighseqnr = qToBigEndian(exthighestseq); - rr->jitter = qToBigEndian(jitter); - rr->lsr = qToBigEndian(lsr); - rr->dlsr = qToBigEndian(dlsr); + rr->ssrc = qToBigEndian(ssrc); + rr->fractionlost = fractionlost; + rr->packetslost[2] = (uint8_t) (packlost2 & 0xFF); + rr->packetslost[1] = (uint8_t) ((packlost2 >> 8) & 0xFF); + rr->packetslost[0] = (uint8_t) ((packlost2 >> 16) & 0xFF); + rr->exthighseqnr = qToBigEndian(exthighestseq); + rr->jitter = qToBigEndian(jitter); + rr->lsr = qToBigEndian(lsr); + rr->dlsr = qToBigEndian(dlsr); - report.reportblocks.push_back(Buffer(buf,sizeof(RTCPReceiverReport))); - return 0; + report.reportblocks.push_back(Buffer(buf, sizeof(RTCPReceiverReport))); + return 0; } int RTCPCompoundPacketBuilder::AddSDESSource(uint32_t ssrc) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; #ifndef RTP_SUPPORT_RTCPUNKNOWN - size_t totalotherbytes = byesize+appsize+report.NeededBytes(); + size_t totalotherbytes = byesize + appsize + report.NeededBytes(); #else - size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); + size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - size_t sdessizewithextrasource = sdes.NeededBytesWithExtraSource(); + size_t sdessizewithextrasource = sdes.NeededBytesWithExtraSource(); - if ((totalotherbytes + sdessizewithextrasource) > maximumpacketsize) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if ((totalotherbytes + sdessizewithextrasource) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - int status; + int status; - if ((status = sdes.AddSSRC(ssrc)) < 0) - return status; - return 0; + if ((status = sdes.AddSSRC(ssrc)) < 0) + return status; + return 0; } -int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t,const void *itemdata,uint8_t itemlength) +int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t, const void *itemdata, uint8_t itemlength) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - if (sdes.sdessources.empty()) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (sdes.sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; - uint8_t itemid; + uint8_t itemid; - switch(t) - { - case RTCPSDESPacket::CNAME: - itemid = RTCP_SDES_ID_CNAME; - break; - case RTCPSDESPacket::NAME: - itemid = RTCP_SDES_ID_NAME; - break; - case RTCPSDESPacket::EMAIL: - itemid = RTCP_SDES_ID_EMAIL; - break; - case RTCPSDESPacket::PHONE: - itemid = RTCP_SDES_ID_PHONE; - break; - case RTCPSDESPacket::LOC: - itemid = RTCP_SDES_ID_LOCATION; - break; - case RTCPSDESPacket::TOOL: - itemid = RTCP_SDES_ID_TOOL; - break; - case RTCPSDESPacket::NOTE: - itemid = RTCP_SDES_ID_NOTE; - break; - default: - return ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE; - } + switch (t) + { + case RTCPSDESPacket::CNAME: + itemid = RTCP_SDES_ID_CNAME; + break; + case RTCPSDESPacket::NAME: + itemid = RTCP_SDES_ID_NAME; + break; + case RTCPSDESPacket::EMAIL: + itemid = RTCP_SDES_ID_EMAIL; + break; + case RTCPSDESPacket::PHONE: + itemid = RTCP_SDES_ID_PHONE; + break; + case RTCPSDESPacket::LOC: + itemid = RTCP_SDES_ID_LOCATION; + break; + case RTCPSDESPacket::TOOL: + itemid = RTCP_SDES_ID_TOOL; + break; + case RTCPSDESPacket::NOTE: + itemid = RTCP_SDES_ID_NOTE; + break; + default: + return ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE; + } #ifndef RTP_SUPPORT_RTCPUNKNOWN - size_t totalotherbytes = byesize+appsize+report.NeededBytes(); + size_t totalotherbytes = byesize + appsize + report.NeededBytes(); #else - size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); + size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); + size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); - if ((sdessizewithextraitem+totalotherbytes) > maximumpacketsize) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if ((sdessizewithextraitem + totalotherbytes) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - uint8_t *buf; - size_t len; + uint8_t *buf; + size_t len; - buf = new uint8_t[sizeof(RTCPSDESHeader)+(size_t)itemlength]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; - len = sizeof(RTCPSDESHeader)+(size_t)itemlength; + buf = new uint8_t[sizeof(RTCPSDESHeader) + (size_t) itemlength]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + len = sizeof(RTCPSDESHeader) + (size_t) itemlength; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(buf); + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (buf); - sdeshdr->sdesid = itemid; - sdeshdr->length = itemlength; - if (itemlength != 0) - memcpy((buf + sizeof(RTCPSDESHeader)),itemdata,(size_t)itemlength); + sdeshdr->sdesid = itemid; + sdeshdr->length = itemlength; + if (itemlength != 0) + memcpy((buf + sizeof(RTCPSDESHeader)), itemdata, (size_t) itemlength); - sdes.AddItem(buf,len); - return 0; + sdes.AddItem(buf, len); + return 0; } #ifdef RTP_SUPPORT_SDESPRIV -int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata,uint8_t prefixlength,const void *valuedata, - uint8_t valuelength) +int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata, uint8_t prefixlength, const void *valuedata, uint8_t valuelength) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - if (sdes.sdessources.empty()) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (sdes.sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; - size_t itemlength = ((size_t)prefixlength)+1+((size_t)valuelength); - if (itemlength > 255) - return ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG; + size_t itemlength = ((size_t) prefixlength) + 1 + ((size_t) valuelength); + if (itemlength > 255) + return ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG; #ifndef RTP_SUPPORT_RTCPUNKNOWN - size_t totalotherbytes = byesize+appsize+report.NeededBytes(); + size_t totalotherbytes = byesize + appsize + report.NeededBytes(); #else - size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); + size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); + size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); - if ((sdessizewithextraitem+totalotherbytes) > maximumpacketsize) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if ((sdessizewithextraitem + totalotherbytes) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - uint8_t *buf; - size_t len; + uint8_t *buf; + size_t len; - buf = new uint8_t[sizeof(RTCPSDESHeader)+itemlength]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; - len = sizeof(RTCPSDESHeader)+(size_t)itemlength; + buf = new uint8_t[sizeof(RTCPSDESHeader) + itemlength]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + len = sizeof(RTCPSDESHeader) + (size_t) itemlength; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(buf); + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (buf); - sdeshdr->sdesid = RTCP_SDES_ID_PRIVATE; - sdeshdr->length = itemlength; + sdeshdr->sdesid = RTCP_SDES_ID_PRIVATE; + sdeshdr->length = itemlength; - buf[sizeof(RTCPSDESHeader)] = prefixlength; - if (prefixlength != 0) - memcpy((buf+sizeof(RTCPSDESHeader)+1),prefixdata,(size_t)prefixlength); - if (valuelength != 0) - memcpy((buf+sizeof(RTCPSDESHeader)+1+(size_t)prefixlength),valuedata,(size_t)valuelength); + buf[sizeof(RTCPSDESHeader)] = prefixlength; + if (prefixlength != 0) + memcpy((buf + sizeof(RTCPSDESHeader) + 1), prefixdata, (size_t) prefixlength); + if (valuelength != 0) + memcpy((buf + sizeof(RTCPSDESHeader) + 1 + (size_t) prefixlength), valuedata, (size_t) valuelength); - sdes.AddItem(buf,len); - return 0; + sdes.AddItem(buf, len); + return 0; } #endif // RTP_SUPPORT_SDESPRIV -int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs,uint8_t numssrcs,const void *reasondata,uint8_t reasonlength) +int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, const void *reasondata, uint8_t reasonlength) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - if (numssrcs > 31) - return ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS; + if (numssrcs > 31) + return ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS; - size_t packsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)*((size_t)numssrcs); - size_t zerobytes = 0; + size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * ((size_t) numssrcs); + size_t zerobytes = 0; - if (reasonlength > 0) - { - packsize += 1; // 1 byte for the length; - packsize += (size_t)reasonlength; + if (reasonlength > 0) + { + packsize += 1; // 1 byte for the length; + packsize += (size_t) reasonlength; - size_t r = (packsize&0x03); - if (r != 0) - { - zerobytes = 4-r; - packsize += zerobytes; - } - } + size_t r = (packsize & 0x03); + if (r != 0) + { + zerobytes = 4 - r; + packsize += zerobytes; + } + } #ifndef RTP_SUPPORT_RTCPUNKNOWN - size_t totalotherbytes = appsize+byesize+sdes.NeededBytes()+report.NeededBytes(); + size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); #else - size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); + size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - if ((totalotherbytes + packsize) > maximumpacketsize) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if ((totalotherbytes + packsize) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - uint8_t *buf; - size_t numwords; + uint8_t *buf; + size_t numwords; - buf = new uint8_t[packsize]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; + buf = new uint8_t[packsize]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)buf; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) buf; - hdr->version = 2; - hdr->padding = 0; - hdr->count = numssrcs; + hdr->version = 2; + hdr->padding = 0; + hdr->count = numssrcs; - numwords = packsize/sizeof(uint32_t); - hdr->length = qToBigEndian((uint16_t)(numwords-1)); - hdr->packettype = RTP_RTCPTYPE_BYE; + numwords = packsize / sizeof(uint32_t); + hdr->length = qToBigEndian((uint16_t) (numwords - 1)); + hdr->packettype = RTP_RTCPTYPE_BYE; - uint32_t *sources = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); - uint8_t srcindex; + uint32_t *sources = (uint32_t *) (buf + sizeof(RTCPCommonHeader)); + uint8_t srcindex; - for (srcindex = 0 ; srcindex < numssrcs ; srcindex++) - sources[srcindex] = qToBigEndian(ssrcs[srcindex]); + for (srcindex = 0; srcindex < numssrcs; srcindex++) + sources[srcindex] = qToBigEndian(ssrcs[srcindex]); - if (reasonlength != 0) - { - size_t offset = sizeof(RTCPCommonHeader)+((size_t)numssrcs)*sizeof(uint32_t); + if (reasonlength != 0) + { + size_t offset = sizeof(RTCPCommonHeader) + ((size_t) numssrcs) * sizeof(uint32_t); - buf[offset] = reasonlength; - memcpy((buf+offset+1),reasondata,(size_t)reasonlength); - for (size_t i = 0 ; i < zerobytes ; i++) - buf[packsize-1-i] = 0; - } + buf[offset] = reasonlength; + memcpy((buf + offset + 1), reasondata, (size_t) reasonlength); + for (size_t i = 0; i < zerobytes; i++) + buf[packsize - 1 - i] = 0; + } - byepackets.push_back(Buffer(buf,packsize)); - byesize += packsize; + byepackets.push_back(Buffer(buf, packsize)); + byesize += packsize; - return 0; + return 0; } -int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype,uint32_t ssrc,const uint8_t name[4],const void *appdata,size_t appdatalen) +int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype, uint32_t ssrc, const uint8_t name[4], const void *appdata, size_t appdatalen) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - if (subtype > 31) - return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE; - if ((appdatalen%4) != 0) - return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (subtype > 31) + return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE; + if ((appdatalen % 4) != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH; - size_t appdatawords = appdatalen/4; + size_t appdatawords = appdatalen / 4; - if ((appdatawords+2) > 65535) - return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; + if ((appdatawords + 2) > 65535) + return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; - size_t packsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2+appdatalen; + size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2 + appdatalen; #ifndef RTP_SUPPORT_RTCPUNKNOWN - size_t totalotherbytes = appsize+byesize+sdes.NeededBytes()+report.NeededBytes(); + size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); #else - size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); + size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - if ((totalotherbytes + packsize) > maximumpacketsize) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if ((totalotherbytes + packsize) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - uint8_t *buf; + uint8_t *buf; - buf = new uint8_t[packsize]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; + buf = new uint8_t[packsize]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)buf; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) buf; - hdr->version = 2; - hdr->padding = 0; - hdr->count = subtype; + hdr->version = 2; + hdr->padding = 0; + hdr->count = subtype; - hdr->length = qToBigEndian((uint16_t)(appdatawords+2)); - hdr->packettype = RTP_RTCPTYPE_APP; + hdr->length = qToBigEndian((uint16_t) (appdatawords + 2)); + hdr->packettype = RTP_RTCPTYPE_APP; - uint32_t *source = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); - *source = qToBigEndian(ssrc); + uint32_t *source = (uint32_t *) (buf + sizeof(RTCPCommonHeader)); + *source = qToBigEndian(ssrc); - buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+0] = name[0]; - buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+1] = name[1]; - buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+2] = name[2]; - buf[sizeof(RTCPCommonHeader)+sizeof(uint32_t)+3] = name[3]; + buf[sizeof(RTCPCommonHeader) + sizeof(uint32_t) + 0] = name[0]; + buf[sizeof(RTCPCommonHeader) + sizeof(uint32_t) + 1] = name[1]; + buf[sizeof(RTCPCommonHeader) + sizeof(uint32_t) + 2] = name[2]; + buf[sizeof(RTCPCommonHeader) + sizeof(uint32_t) + 3] = name[3]; - if (appdatalen > 0) - memcpy((buf+sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2),appdata,appdatalen); + if (appdatalen > 0) + memcpy((buf + sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2), appdata, appdatalen); - apppackets.push_back(Buffer(buf,packsize)); - appsize += packsize; + apppackets.push_back(Buffer(buf, packsize)); + appsize += packsize; - return 0; + return 0; } #ifdef RTP_SUPPORT_RTCPUNKNOWN int RTCPCompoundPacketBuilder::AddUnknownPacket(uint8_t payload_type, uint8_t subtype, uint32_t ssrc, const void *data, size_t len) { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - size_t datawords = len/4; + size_t datawords = len/4; - if ((datawords+2) > 65535) - return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; + if ((datawords+2) > 65535) + return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; - size_t packsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+len; - size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); + size_t packsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+len; + size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); - if ((totalotherbytes + packsize) > maximumpacketsize) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + if ((totalotherbytes + packsize) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - uint8_t *buf = new uint8_t[packsize]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; + uint8_t *buf = new uint8_t[packsize]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)buf; + RTCPCommonHeader *hdr = (RTCPCommonHeader *)buf; - hdr->version = 2; - hdr->padding = 0; - hdr->count = subtype; - hdr->length = qToBigEndian((uint16_t)(datawords+1)); - hdr->packettype = payload_type; + hdr->version = 2; + hdr->padding = 0; + hdr->count = subtype; + hdr->length = qToBigEndian((uint16_t)(datawords+1)); + hdr->packettype = payload_type; - uint32_t *source = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); - *source = qToBigEndian(ssrc); + uint32_t *source = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); + *source = qToBigEndian(ssrc); - if (len > 0) - memcpy((buf+sizeof(RTCPCommonHeader)+sizeof(uint32_t)),data,len); + if (len > 0) + memcpy((buf+sizeof(RTCPCommonHeader)+sizeof(uint32_t)),data,len); - unknownpackets.push_back(Buffer(buf,packsize)); - unknownsize += packsize; + unknownpackets.push_back(Buffer(buf,packsize)); + unknownsize += packsize; - return 0; + return 0; } #endif // RTP_SUPPORT_RTCPUNKNOWN int RTCPCompoundPacketBuilder::EndBuild() { - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - if (report.headerlength == 0) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT; + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength == 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT; - uint8_t *buf; - size_t len; + uint8_t *buf; + size_t len; #ifndef RTP_SUPPORT_RTCPUNKNOWN - len = appsize+byesize+report.NeededBytes()+sdes.NeededBytes(); + len = appsize + byesize + report.NeededBytes() + sdes.NeededBytes(); #else - len = appsize+unknownsize+byesize+report.NeededBytes()+sdes.NeededBytes(); + len = appsize+unknownsize+byesize+report.NeededBytes()+sdes.NeededBytes(); #endif // RTP_SUPPORT_RTCPUNKNOWN - if (!external) - { - buf = new uint8_t[len]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; - } - else - buf = buffer; + if (!external) + { + buf = new uint8_t[len]; + if (buf == 0) + return ERR_RTP_OUTOFMEM; + } + else + buf = buffer; - uint8_t *curbuf = buf; - RTCPPacket *p; + uint8_t *curbuf = buf; + RTCPPacket *p; - // first, we'll add all report info + // first, we'll add all report info - { - bool firstpacket = true; - bool done = false; - std::list::const_iterator it = report.reportblocks.begin(); - do - { - RTCPCommonHeader *hdr = (RTCPCommonHeader *)curbuf; - size_t offset; + { + bool firstpacket = true; + bool done = false; + std::list::const_iterator it = report.reportblocks.begin(); + do + { + RTCPCommonHeader *hdr = (RTCPCommonHeader *) curbuf; + size_t offset; - hdr->version = 2; - hdr->padding = 0; + hdr->version = 2; + hdr->padding = 0; - if (firstpacket && report.isSR) - { - hdr->packettype = RTP_RTCPTYPE_SR; - memcpy((curbuf+sizeof(RTCPCommonHeader)),report.headerdata,report.headerlength); - offset = sizeof(RTCPCommonHeader)+report.headerlength; - } - else - { - hdr->packettype = RTP_RTCPTYPE_RR; - memcpy((curbuf+sizeof(RTCPCommonHeader)),report.headerdata,sizeof(uint32_t)); - offset = sizeof(RTCPCommonHeader)+sizeof(uint32_t); - } - firstpacket = false; + if (firstpacket && report.isSR) + { + hdr->packettype = RTP_RTCPTYPE_SR; + memcpy((curbuf + sizeof(RTCPCommonHeader)), report.headerdata, report.headerlength); + offset = sizeof(RTCPCommonHeader) + report.headerlength; + } + else + { + hdr->packettype = RTP_RTCPTYPE_RR; + memcpy((curbuf + sizeof(RTCPCommonHeader)), report.headerdata, sizeof(uint32_t)); + offset = sizeof(RTCPCommonHeader) + sizeof(uint32_t); + } + firstpacket = false; - uint8_t count = 0; + uint8_t count = 0; - while (it != report.reportblocks.end() && count < 31) - { - memcpy(curbuf+offset,(*it).packetdata,(*it).packetlength); - offset += (*it).packetlength; - count++; - it++; - } + while (it != report.reportblocks.end() && count < 31) + { + memcpy(curbuf + offset, (*it).packetdata, (*it).packetlength); + offset += (*it).packetlength; + count++; + it++; + } - size_t numwords = offset/sizeof(uint32_t); + size_t numwords = offset / sizeof(uint32_t); - hdr->length = qToBigEndian((uint16_t)(numwords-1)); - hdr->count = count; + hdr->length = qToBigEndian((uint16_t) (numwords - 1)); + hdr->count = count; - // add entry in parent's list - if (hdr->packettype == RTP_RTCPTYPE_SR) - p = new RTCPSRPacket(curbuf,offset); - else - p = new RTCPRRPacket(curbuf,offset); - if (p == 0) - { - if (!external) - RTPDeleteByteArray(buf,GetMemoryManager()); - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } - rtcppacklist.push_back(p); + // add entry in parent's list + if (hdr->packettype == RTP_RTCPTYPE_SR) + p = new RTCPSRPacket(curbuf, offset); + else + p = new RTCPRRPacket(curbuf, offset); + if (p == 0) + { + if (!external) + delete[] buf; + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); - curbuf += offset; - if (it == report.reportblocks.end()) - done = true; - } while (!done); - } + curbuf += offset; + if (it == report.reportblocks.end()) + done = true; + } while (!done); + } - // then, we'll add the sdes info + // then, we'll add the sdes info - if (!sdes.sdessources.empty()) - { - bool done = false; - std::list::const_iterator sourceit = sdes.sdessources.begin(); + if (!sdes.sdessources.empty()) + { + bool done = false; + std::list::const_iterator sourceit = sdes.sdessources.begin(); - do - { - RTCPCommonHeader *hdr = (RTCPCommonHeader *)curbuf; - size_t offset = sizeof(RTCPCommonHeader); + do + { + RTCPCommonHeader *hdr = (RTCPCommonHeader *) curbuf; + size_t offset = sizeof(RTCPCommonHeader); - hdr->version = 2; - hdr->padding = 0; - hdr->packettype = RTP_RTCPTYPE_SDES; + hdr->version = 2; + hdr->padding = 0; + hdr->packettype = RTP_RTCPTYPE_SDES; - uint8_t sourcecount = 0; + uint8_t sourcecount = 0; - while (sourceit != sdes.sdessources.end() && sourcecount < 31) - { - uint32_t *ssrc = (uint32_t *)(curbuf+offset); - *ssrc = qToBigEndian((*sourceit)->ssrc); - offset += sizeof(uint32_t); + while (sourceit != sdes.sdessources.end() && sourcecount < 31) + { + uint32_t *ssrc = (uint32_t *) (curbuf + offset); + *ssrc = qToBigEndian((*sourceit)->ssrc); + offset += sizeof(uint32_t); - std::list::const_iterator itemit,itemend; + std::list::const_iterator itemit, itemend; - itemit = (*sourceit)->items.begin(); - itemend = (*sourceit)->items.end(); - while (itemit != itemend) - { - memcpy(curbuf+offset,(*itemit).packetdata,(*itemit).packetlength); - offset += (*itemit).packetlength; - itemit++; - } + itemit = (*sourceit)->items.begin(); + itemend = (*sourceit)->items.end(); + while (itemit != itemend) + { + memcpy(curbuf + offset, (*itemit).packetdata, (*itemit).packetlength); + offset += (*itemit).packetlength; + itemit++; + } - curbuf[offset] = 0; // end of item list; - offset++; + curbuf[offset] = 0; // end of item list; + offset++; - size_t r = offset&0x03; - if (r != 0) // align to 32 bit boundary - { - size_t num = 4-r; - size_t i; + size_t r = offset & 0x03; + if (r != 0) // align to 32 bit boundary + { + size_t num = 4 - r; + size_t i; - for (i = 0 ; i < num ; i++) - curbuf[offset+i] = 0; - offset += num; - } + for (i = 0; i < num; i++) + curbuf[offset + i] = 0; + offset += num; + } - sourceit++; - sourcecount++; - } + sourceit++; + sourcecount++; + } - size_t numwords = offset/4; + size_t numwords = offset / 4; - hdr->count = sourcecount; - hdr->length = qToBigEndian((uint16_t)(numwords-1)); + hdr->count = sourcecount; + hdr->length = qToBigEndian((uint16_t) (numwords - 1)); - p = new RTCPSDESPacket(curbuf,offset); - if (p == 0) - { - if (!external) - RTPDeleteByteArray(buf,GetMemoryManager()); - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } - rtcppacklist.push_back(p); + p = new RTCPSDESPacket(curbuf, offset); + if (p == 0) + { + if (!external) + delete[] buf; + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); - curbuf += offset; - if (sourceit == sdes.sdessources.end()) - done = true; - } while (!done); - } + curbuf += offset; + if (sourceit == sdes.sdessources.end()) + done = true; + } while (!done); + } - // adding the app data + // adding the app data - { - std::list::const_iterator it; + { + std::list::const_iterator it; - for (it = apppackets.begin() ; it != apppackets.end() ; it++) - { - memcpy(curbuf,(*it).packetdata,(*it).packetlength); + for (it = apppackets.begin(); it != apppackets.end(); it++) + { + memcpy(curbuf, (*it).packetdata, (*it).packetlength); - p = new RTCPAPPPacket(curbuf,(*it).packetlength); - if (p == 0) - { - if (!external) - RTPDeleteByteArray(buf,GetMemoryManager()); - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } - rtcppacklist.push_back(p); + p = new RTCPAPPPacket(curbuf, (*it).packetlength); + if (p == 0) + { + if (!external) + delete[] buf; + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); - curbuf += (*it).packetlength; - } - } + curbuf += (*it).packetlength; + } + } #ifdef RTP_SUPPORT_RTCPUNKNOWN - // adding the unknown data + // adding the unknown data - { - std::list::const_iterator it; + { + std::list::const_iterator it; - for (it = unknownpackets.begin() ; it != unknownpackets.end() ; it++) - { - memcpy(curbuf,(*it).packetdata,(*it).packetlength); + for (it = unknownpackets.begin(); it != unknownpackets.end(); it++) + { + memcpy(curbuf,(*it).packetdata,(*it).packetlength); - p = new RTCPUnknownPacket(curbuf,(*it).packetlength); - if (p == 0) - { - if (!external) - RTPDeleteByteArray(buf,GetMemoryManager()); - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } - rtcppacklist.push_back(p); + p = new RTCPUnknownPacket(curbuf,(*it).packetlength); + if (p == 0) + { + if (!external) + delete[] buf; + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); - curbuf += (*it).packetlength; - } - } + curbuf += (*it).packetlength; + } + } #endif // RTP_SUPPORT_RTCPUNKNOWN - // adding bye packets + // adding bye packets - { - std::list::const_iterator it; + { + std::list::const_iterator it; - for (it = byepackets.begin() ; it != byepackets.end() ; it++) - { - memcpy(curbuf,(*it).packetdata,(*it).packetlength); + for (it = byepackets.begin(); it != byepackets.end(); it++) + { + memcpy(curbuf, (*it).packetdata, (*it).packetlength); - p = new RTCPBYEPacket(curbuf,(*it).packetlength); - if (p == 0) - { - if (!external) - RTPDeleteByteArray(buf,GetMemoryManager()); - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } - rtcppacklist.push_back(p); + p = new RTCPBYEPacket(curbuf, (*it).packetlength); + if (p == 0) + { + if (!external) + delete[] buf; + ClearPacketList(); + return ERR_RTP_OUTOFMEM; + } + rtcppacklist.push_back(p); - curbuf += (*it).packetlength; - } - } + curbuf += (*it).packetlength; + } + } - compoundpacket = buf; - compoundpacketlength = len; - arebuilding = false; - ClearBuildBuffers(); - return 0; + compoundpacket = buf; + compoundpacketlength = len; + arebuilding = false; + ClearBuildBuffers(); + return 0; } } // end namespace diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index 9c08bbe09..ae0859dee 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpcompoundpacketbuilder.h @@ -57,343 +57,358 @@ class RTPMemoryManager; * been built successfully. The member functions described below return \c ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT * if the action would cause the maximum allowed size to be exceeded. */ -class JRTPLIB_IMPORTEXPORT RTCPCompoundPacketBuilder : public RTCPCompoundPacket +class JRTPLIB_IMPORTEXPORT RTCPCompoundPacketBuilder: public RTCPCompoundPacket { public: - /** Constructs an RTCPCompoundPacketBuilder instance, optionally installing a memory manager. */ - RTCPCompoundPacketBuilder(RTPMemoryManager *memmgr = 0); - ~RTCPCompoundPacketBuilder(); + /** Constructs an RTCPCompoundPacketBuilder instance, optionally installing a memory manager. */ + RTCPCompoundPacketBuilder(); + ~RTCPCompoundPacketBuilder(); - /** Starts building an RTCP compound packet with maximum size \c maxpacketsize. - * Starts building an RTCP compound packet with maximum size \c maxpacketsize. New memory will be allocated - * to store the packet. - */ - int InitBuild(size_t maxpacketsize); + /** Starts building an RTCP compound packet with maximum size \c maxpacketsize. + * Starts building an RTCP compound packet with maximum size \c maxpacketsize. New memory will be allocated + * to store the packet. + */ + int InitBuild(size_t maxpacketsize); - /** Starts building a RTCP compound packet. - * Starts building a RTCP compound packet. Data will be stored in \c externalbuffer which - * can contain \c buffersize bytes. - */ - int InitBuild(void *externalbuffer,size_t buffersize); + /** Starts building a RTCP compound packet. + * Starts building a RTCP compound packet. Data will be stored in \c externalbuffer which + * can contain \c buffersize bytes. + */ + int InitBuild(void *externalbuffer, size_t buffersize); - /** Adds a sender report to the compound packet. - * Tells the packet builder that the packet should start with a sender report which will contain - * the sender information specified by this function's arguments. Once the sender report is started, - * report blocks can be added using the AddReportBlock function. - */ - int StartSenderReport(uint32_t senderssrc,const RTPNTPTime &ntptimestamp,uint32_t rtptimestamp, - uint32_t packetcount,uint32_t octetcount); + /** Adds a sender report to the compound packet. + * Tells the packet builder that the packet should start with a sender report which will contain + * the sender information specified by this function's arguments. Once the sender report is started, + * report blocks can be added using the AddReportBlock function. + */ + int StartSenderReport(uint32_t senderssrc, const RTPNTPTime &ntptimestamp, uint32_t rtptimestamp, uint32_t packetcount, uint32_t octetcount); - /** Adds a receiver report to the compound packet. - * Tells the packet builder that the packet should start with a receiver report which will contain - * he sender SSRC \c senderssrc. Once the sender report is started, report blocks can be added using the - * AddReportBlock function. - */ - int StartReceiverReport(uint32_t senderssrc); + /** Adds a receiver report to the compound packet. + * Tells the packet builder that the packet should start with a receiver report which will contain + * he sender SSRC \c senderssrc. Once the sender report is started, report blocks can be added using the + * AddReportBlock function. + */ + int StartReceiverReport(uint32_t senderssrc); - /** Adds the report block information specified by the function's arguments. - * Adds the report block information specified by the function's arguments. If more than 31 report blocks - * are added, the builder will automatically use a new RTCP receiver report packet. - */ - int AddReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t packetslost,uint32_t exthighestseq, - uint32_t jitter,uint32_t lsr,uint32_t dlsr); + /** Adds the report block information specified by the function's arguments. + * Adds the report block information specified by the function's arguments. If more than 31 report blocks + * are added, the builder will automatically use a new RTCP receiver report packet. + */ + int AddReportBlock(uint32_t ssrc, uint8_t fractionlost, int32_t packetslost, uint32_t exthighestseq, uint32_t jitter, uint32_t lsr, uint32_t dlsr); - /** Starts an SDES chunk for participant \c ssrc. */ - int AddSDESSource(uint32_t ssrc); + /** Starts an SDES chunk for participant \c ssrc. */ + int AddSDESSource(uint32_t ssrc); - /** Adds a normal (non-private) SDES item of type \c t to the current SDES chunk. - * Adds a normal (non-private) SDES item of type \c t to the current SDES chunk. The item's value - * will have length \c itemlength and will contain the data \c itemdata. - */ - int AddSDESNormalItem(RTCPSDESPacket::ItemType t,const void *itemdata,uint8_t itemlength); + /** Adds a normal (non-private) SDES item of type \c t to the current SDES chunk. + * Adds a normal (non-private) SDES item of type \c t to the current SDES chunk. The item's value + * will have length \c itemlength and will contain the data \c itemdata. + */ + int AddSDESNormalItem(RTCPSDESPacket::ItemType t, const void *itemdata, uint8_t itemlength); #ifdef RTP_SUPPORT_SDESPRIV - /** Adds an SDES PRIV item described by the function's arguments to the current SDES chunk. */ - int AddSDESPrivateItem(const void *prefixdata,uint8_t prefixlength,const void *valuedata, - uint8_t valuelength); + /** Adds an SDES PRIV item described by the function's arguments to the current SDES chunk. */ + int AddSDESPrivateItem(const void *prefixdata, uint8_t prefixlength, const void *valuedata, uint8_t valuelength); #endif // RTP_SUPPORT_SDESPRIV - /** Adds a BYE packet to the compound packet. - * Adds a BYE packet to the compound packet. It will contain \c numssrcs source identifiers specified in - * \c ssrcs and will indicate as reason for leaving the string of length \c reasonlength - * containing data \c reasondata. - */ - int AddBYEPacket(uint32_t *ssrcs,uint8_t numssrcs,const void *reasondata,uint8_t reasonlength); + /** Adds a BYE packet to the compound packet. + * Adds a BYE packet to the compound packet. It will contain \c numssrcs source identifiers specified in + * \c ssrcs and will indicate as reason for leaving the string of length \c reasonlength + * containing data \c reasondata. + */ + int AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, const void *reasondata, uint8_t reasonlength); - /** Adds the APP packet specified by the arguments to the compound packet. - * Adds the APP packet specified by the arguments to the compound packet. Note that \c appdatalen has to be - * a multiple of four. - */ - int AddAPPPacket(uint8_t subtype,uint32_t ssrc,const uint8_t name[4],const void *appdata,size_t appdatalen); + /** Adds the APP packet specified by the arguments to the compound packet. + * Adds the APP packet specified by the arguments to the compound packet. Note that \c appdatalen has to be + * a multiple of four. + */ + int AddAPPPacket(uint8_t subtype, uint32_t ssrc, const uint8_t name[4], const void *appdata, size_t appdatalen); - /** Finishes building the compound packet. - * Finishes building the compound packet. If successful, the RTCPCompoundPacket member functions - * can be used to access the RTCP packet data. - */ - int EndBuild(); + /** Finishes building the compound packet. + * Finishes building the compound packet. If successful, the RTCPCompoundPacket member functions + * can be used to access the RTCP packet data. + */ + int EndBuild(); #ifdef RTP_SUPPORT_RTCPUNKNOWN - /** Adds the RTCP packet specified by the arguments to the compound packet. - * Adds the RTCP packet specified by the arguments to the compound packet. - */ - int AddUnknownPacket(uint8_t payload_type, uint8_t subtype, uint32_t ssrc, const void *data, size_t len); + /** Adds the RTCP packet specified by the arguments to the compound packet. + * Adds the RTCP packet specified by the arguments to the compound packet. + */ + int AddUnknownPacket(uint8_t payload_type, uint8_t subtype, uint32_t ssrc, const void *data, size_t len); #endif // RTP_SUPPORT_RTCPUNKNOWN private: - class Buffer - { - public: - Buffer():packetdata(0),packetlength(0) { } - Buffer(uint8_t *data,size_t len):packetdata(data),packetlength(len) { } + class Buffer + { + public: + Buffer() : + packetdata(0), packetlength(0) + { + } + Buffer(uint8_t *data, size_t len) : + packetdata(data), packetlength(len) + { + } - uint8_t *packetdata; - size_t packetlength; - }; + uint8_t *packetdata; + size_t packetlength; + }; - class Report : public RTPMemoryObject - { - public: - Report(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) - { - headerdata = (uint8_t *)headerdata32; - isSR = false; - headerlength = 0; - } - ~Report() { Clear(); } + class Report + { + public: + Report() + { + headerdata = (uint8_t *) headerdata32; + isSR = false; + headerlength = 0; + } + ~Report() + { + Clear(); + } - void Clear() - { - std::list::const_iterator it; - for (it = reportblocks.begin() ; it != reportblocks.end() ; it++) - { - if ((*it).packetdata) - RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); - } - reportblocks.clear(); - isSR = false; - headerlength = 0; - } + void Clear() + { + std::list::const_iterator it; + for (it = reportblocks.begin(); it != reportblocks.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } + reportblocks.clear(); + isSR = false; + headerlength = 0; + } - size_t NeededBytes() - { - size_t x,n,d,r; - n = reportblocks.size(); - if (n == 0) - { - if (headerlength == 0) - return 0; - x = sizeof(RTCPCommonHeader)+headerlength; - } - else - { - x = n*sizeof(RTCPReceiverReport); - d = n/31; // max 31 reportblocks per report - r = n%31; - if (r != 0) - d++; - x += d*(sizeof(RTCPCommonHeader)+sizeof(uint32_t)); /* header and SSRC */ - if (isSR) - x += sizeof(RTCPSenderReport); - } - return x; - } + size_t NeededBytes() + { + size_t x, n, d, r; + n = reportblocks.size(); + if (n == 0) + { + if (headerlength == 0) + return 0; + x = sizeof(RTCPCommonHeader) + headerlength; + } + else + { + x = n * sizeof(RTCPReceiverReport); + d = n / 31; // max 31 reportblocks per report + r = n % 31; + if (r != 0) + d++; + x += d * (sizeof(RTCPCommonHeader) + sizeof(uint32_t)); /* header and SSRC */ + if (isSR) + x += sizeof(RTCPSenderReport); + } + return x; + } - size_t NeededBytesWithExtraReportBlock() - { - size_t x,n,d,r; - n = reportblocks.size() + 1; // +1 for the extra block - x = n*sizeof(RTCPReceiverReport); - d = n/31; // max 31 reportblocks per report - r = n%31; - if (r != 0) - d++; - x += d*(sizeof(RTCPCommonHeader)+sizeof(uint32_t)); /* header and SSRC */ - if (isSR) - x += sizeof(RTCPSenderReport); - return x; - } + size_t NeededBytesWithExtraReportBlock() + { + size_t x, n, d, r; + n = reportblocks.size() + 1; // +1 for the extra block + x = n * sizeof(RTCPReceiverReport); + d = n / 31; // max 31 reportblocks per report + r = n % 31; + if (r != 0) + d++; + x += d * (sizeof(RTCPCommonHeader) + sizeof(uint32_t)); /* header and SSRC */ + if (isSR) + x += sizeof(RTCPSenderReport); + return x; + } - bool isSR; + bool isSR; - uint8_t *headerdata; - uint32_t headerdata32[(sizeof(uint32_t)+sizeof(RTCPSenderReport))/sizeof(uint32_t)]; // either for ssrc and sender info or just ssrc - size_t headerlength; - std::list reportblocks; - }; + uint8_t *headerdata; + uint32_t headerdata32[(sizeof(uint32_t) + sizeof(RTCPSenderReport)) / sizeof(uint32_t)]; // either for ssrc and sender info or just ssrc + size_t headerlength; + std::list reportblocks; + }; - class SDESSource : public RTPMemoryObject - { - public: - SDESSource(uint32_t s,RTPMemoryManager *mgr) : RTPMemoryObject(mgr),ssrc(s),totalitemsize(0) { } - ~SDESSource() - { - std::list::const_iterator it; - for (it = items.begin() ; it != items.end() ; it++) - { - if ((*it).packetdata) - RTPDeleteByteArray((*it).packetdata,GetMemoryManager()); - } - items.clear(); - } + class SDESSource + { + public: + SDESSource(uint32_t s) : + ssrc(s), totalitemsize(0) + { + } + ~SDESSource() + { + std::list::const_iterator it; + for (it = items.begin(); it != items.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } + items.clear(); + } - size_t NeededBytes() - { - size_t x,r; - x = totalitemsize + 1; // +1 for the 0 byte which terminates the item list - r = x%sizeof(uint32_t); - if (r != 0) - x += (sizeof(uint32_t)-r); // make sure it ends on a 32 bit boundary - x += sizeof(uint32_t); // for ssrc - return x; - } + size_t NeededBytes() + { + size_t x, r; + x = totalitemsize + 1; // +1 for the 0 byte which terminates the item list + r = x % sizeof(uint32_t); + if (r != 0) + x += (sizeof(uint32_t) - r); // make sure it ends on a 32 bit boundary + x += sizeof(uint32_t); // for ssrc + return x; + } - size_t NeededBytesWithExtraItem(uint8_t itemdatalength) - { - size_t x,r; - x = totalitemsize + sizeof(RTCPSDESHeader) + (size_t)itemdatalength + 1; - r = x%sizeof(uint32_t); - if (r != 0) - x += (sizeof(uint32_t)-r); // make sure it ends on a 32 bit boundary - x += sizeof(uint32_t); // for ssrc - return x; - } + size_t NeededBytesWithExtraItem(uint8_t itemdatalength) + { + size_t x, r; + x = totalitemsize + sizeof(RTCPSDESHeader) + (size_t) itemdatalength + 1; + r = x % sizeof(uint32_t); + if (r != 0) + x += (sizeof(uint32_t) - r); // make sure it ends on a 32 bit boundary + x += sizeof(uint32_t); // for ssrc + return x; + } - void AddItem(uint8_t *buf,size_t len) - { - Buffer b(buf,len); - totalitemsize += len; - items.push_back(b); - } + void AddItem(uint8_t *buf, size_t len) + { + Buffer b(buf, len); + totalitemsize += len; + items.push_back(b); + } - uint32_t ssrc; - std::list items; - private: - size_t totalitemsize; - }; + uint32_t ssrc; + std::list items; + private: + size_t totalitemsize; + }; - class SDES : public RTPMemoryObject - { - public: - SDES(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) { sdesit = sdessources.end(); } - ~SDES() { Clear(); } + class SDES + { + public: + SDES() + { + sdesit = sdessources.end(); + } + ~SDES() + { + Clear(); + } - void Clear() - { - std::list::const_iterator it; + void Clear() + { + std::list::const_iterator it; - for (it = sdessources.begin() ; it != sdessources.end() ; it++) - RTPDelete(*it,GetMemoryManager()); - sdessources.clear(); - } + for (it = sdessources.begin(); it != sdessources.end(); it++) + delete *it; + sdessources.clear(); + } - int AddSSRC(uint32_t ssrc) - { - SDESSource *s = new SDESSource(ssrc,GetMemoryManager()); - if (s == 0) - return ERR_RTP_OUTOFMEM; - sdessources.push_back(s); - sdesit = sdessources.end(); - sdesit--; - return 0; - } + int AddSSRC(uint32_t ssrc) + { + SDESSource *s = new SDESSource(ssrc); + if (s == 0) + return ERR_RTP_OUTOFMEM; + sdessources.push_back(s); + sdesit = sdessources.end(); + sdesit--; + return 0; + } - int AddItem(uint8_t *buf,size_t len) - { - if (sdessources.empty()) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; - (*sdesit)->AddItem(buf,len); - return 0; - } + int AddItem(uint8_t *buf, size_t len) + { + if (sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + (*sdesit)->AddItem(buf, len); + return 0; + } - size_t NeededBytes() - { - std::list::const_iterator it; - size_t x = 0; - size_t n,d,r; + size_t NeededBytes() + { + std::list::const_iterator it; + size_t x = 0; + size_t n, d, r; - if (sdessources.empty()) - return 0; + if (sdessources.empty()) + return 0; - for (it = sdessources.begin() ; it != sdessources.end() ; it++) - x += (*it)->NeededBytes(); - n = sdessources.size(); - d = n/31; - r = n%31; - if (r != 0) - d++; - x += d*sizeof(RTCPCommonHeader); - return x; - } + for (it = sdessources.begin(); it != sdessources.end(); it++) + x += (*it)->NeededBytes(); + n = sdessources.size(); + d = n / 31; + r = n % 31; + if (r != 0) + d++; + x += d * sizeof(RTCPCommonHeader); + return x; + } - size_t NeededBytesWithExtraItem(uint8_t itemdatalength) - { - std::list::const_iterator it; - size_t x = 0; - size_t n,d,r; + size_t NeededBytesWithExtraItem(uint8_t itemdatalength) + { + std::list::const_iterator it; + size_t x = 0; + size_t n, d, r; - if (sdessources.empty()) - return 0; + if (sdessources.empty()) + return 0; - for (it = sdessources.begin() ; it != sdesit ; it++) - x += (*it)->NeededBytes(); - x += (*sdesit)->NeededBytesWithExtraItem(itemdatalength); - n = sdessources.size(); - d = n/31; - r = n%31; - if (r != 0) - d++; - x += d*sizeof(RTCPCommonHeader); - return x; - } + for (it = sdessources.begin(); it != sdesit; it++) + x += (*it)->NeededBytes(); + x += (*sdesit)->NeededBytesWithExtraItem(itemdatalength); + n = sdessources.size(); + d = n / 31; + r = n % 31; + if (r != 0) + d++; + x += d * sizeof(RTCPCommonHeader); + return x; + } - size_t NeededBytesWithExtraSource() - { - std::list::const_iterator it; - size_t x = 0; - size_t n,d,r; + size_t NeededBytesWithExtraSource() + { + std::list::const_iterator it; + size_t x = 0; + size_t n, d, r; - if (sdessources.empty()) - return 0; + if (sdessources.empty()) + return 0; - for (it = sdessources.begin() ; it != sdessources.end() ; it++) - x += (*it)->NeededBytes(); + for (it = sdessources.begin(); it != sdessources.end(); it++) + x += (*it)->NeededBytes(); - // for the extra source we'll need at least 8 bytes (ssrc and four 0 bytes) - x += sizeof(uint32_t)*2; + // for the extra source we'll need at least 8 bytes (ssrc and four 0 bytes) + x += sizeof(uint32_t) * 2; - n = sdessources.size() + 1; // also, the number of sources will increase - d = n/31; - r = n%31; - if (r != 0) - d++; - x += d*sizeof(RTCPCommonHeader); - return x; - } + n = sdessources.size() + 1; // also, the number of sources will increase + d = n / 31; + r = n % 31; + if (r != 0) + d++; + x += d * sizeof(RTCPCommonHeader); + return x; + } - std::list sdessources; - private: - std::list::const_iterator sdesit; - }; + std::list sdessources; + private: + std::list::const_iterator sdesit; + }; - RTPEndian m_endian; - size_t maximumpacketsize; - uint8_t *buffer; - bool external; - bool arebuilding; + RTPEndian m_endian; + size_t maximumpacketsize; + uint8_t *buffer; + bool external; + bool arebuilding; - Report report; - SDES sdes; + Report report; + SDES sdes; - std::list byepackets; - size_t byesize; + std::list byepackets; + size_t byesize; - std::list apppackets; - size_t appsize; + std::list apppackets; + size_t appsize; #ifdef RTP_SUPPORT_RTCPUNKNOWN - std::list unknownpackets; - size_t unknownsize; + std::list unknownpackets; + size_t unknownsize; #endif // RTP_SUPPORT_RTCPUNKNOWN - void ClearBuildBuffers(); + void ClearBuildBuffers(); }; } // end namespace diff --git a/qrtplib/rtcppacket.h b/qrtplib/rtcppacket.h index 32df3e682..a50938724 100644 --- a/qrtplib/rtcppacket.h +++ b/qrtplib/rtcppacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcppacket.h @@ -50,39 +50,57 @@ class RTCPCompoundPacket; class JRTPLIB_IMPORTEXPORT RTCPPacket { public: - /** Identifies the specific kind of RTCP packet. */ - enum PacketType - { - SR, /**< An RTCP sender report. */ - RR, /**< An RTCP receiver report. */ - SDES, /**< An RTCP source description packet. */ - BYE, /**< An RTCP bye packet. */ - APP, /**< An RTCP packet containing application specific data. */ - Unknown /**< The type of RTCP packet was not recognized. */ - }; + /** Identifies the specific kind of RTCP packet. */ + enum PacketType + { + SR, /**< An RTCP sender report. */ + RR, /**< An RTCP receiver report. */ + SDES, /**< An RTCP source description packet. */ + BYE, /**< An RTCP bye packet. */ + APP, /**< An RTCP packet containing application specific data. */ + Unknown /**< The type of RTCP packet was not recognized. */ + }; protected: - RTCPPacket(PacketType t,uint8_t *d,size_t dlen) : data(d),datalen(dlen),packettype(t) { knownformat = false; } + RTCPPacket(PacketType t, uint8_t *d, size_t dlen) : + data(d), datalen(dlen), packettype(t) + { + knownformat = false; + } public: - virtual ~RTCPPacket() { } + virtual ~RTCPPacket() + { + } - /** Returns \c true if the subclass was able to interpret the data and \c false otherwise. */ - bool IsKnownFormat() const { return knownformat; } + /** Returns \c true if the subclass was able to interpret the data and \c false otherwise. */ + bool IsKnownFormat() const + { + return knownformat; + } - /** Returns the actual packet type which the subclass implements. */ - PacketType GetPacketType() const { return packettype; } + /** Returns the actual packet type which the subclass implements. */ + PacketType GetPacketType() const + { + return packettype; + } - /** Returns a pointer to the data of this RTCP packet. */ - uint8_t *GetPacketData() { return data; } + /** Returns a pointer to the data of this RTCP packet. */ + uint8_t *GetPacketData() + { + return data; + } - /** Returns the length of this RTCP packet. */ - size_t GetPacketLength() const { return datalen; } + /** Returns the length of this RTCP packet. */ + size_t GetPacketLength() const + { + return datalen; + } protected: - uint8_t *data; - size_t datalen; - bool knownformat; + uint8_t *data; + size_t datalen; + bool knownformat; private: - const PacketType packettype; + const PacketType packettype; }; } // end namespace diff --git a/qrtplib/rtcppacketbuilder.cpp b/qrtplib/rtcppacketbuilder.cpp index 4d74c3b76..8ebe26dcf 100644 --- a/qrtplib/rtcppacketbuilder.cpp +++ b/qrtplib/rtcppacketbuilder.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcppacketbuilder.h" #include "rtpsources.h" @@ -36,701 +36,706 @@ #include "rtcpscheduler.h" #include "rtpsourcedata.h" #include "rtcpcompoundpacketbuilder.h" -#include "rtpmemorymanager.h" namespace qrtplib { -RTCPPacketBuilder::RTCPPacketBuilder(RTPSources &s,RTPPacketBuilder &pb,RTPMemoryManager *mgr) - : RTPMemoryObject(mgr),sources(s),rtppacketbuilder(pb),prevbuildtime(0,0),transmissiondelay(0,0),ownsdesinfo(mgr) +RTCPPacketBuilder::RTCPPacketBuilder(RTPSources &s, RTPPacketBuilder &pb) : + sources(s), rtppacketbuilder(pb), prevbuildtime(0, 0), transmissiondelay(0, 0) { - init = false; - timeinit.Dummy(); + init = false; + timeinit.Dummy(); } RTCPPacketBuilder::~RTCPPacketBuilder() { - Destroy(); + Destroy(); } -int RTCPPacketBuilder::Init(size_t maxpacksize,double tsunit,const void *cname,size_t cnamelen) +int RTCPPacketBuilder::Init(size_t maxpacksize, double tsunit, const void *cname, size_t cnamelen) { - if (init) - return ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT; - if (maxpacksize < RTP_MINPACKETSIZE) - return ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE; - if (tsunit < 0.0) - return ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT; + if (init) + return ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT; + if (maxpacksize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE; + if (tsunit < 0.0) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT; - if (cnamelen>255) - cnamelen = 255; + if (cnamelen > 255) + cnamelen = 255; - maxpacketsize = maxpacksize; - timestampunit = tsunit; + maxpacketsize = maxpacksize; + timestampunit = tsunit; - int status; + int status; - if ((status = ownsdesinfo.SetCNAME((const uint8_t *)cname,cnamelen)) < 0) - return status; + if ((status = ownsdesinfo.SetCNAME((const uint8_t *) cname, cnamelen)) < 0) + return status; - ClearAllSourceFlags(); + ClearAllSourceFlags(); - interval_name = -1; - interval_email = -1; - interval_location = -1; - interval_phone = -1; - interval_tool = -1; - interval_note = -1; + interval_name = -1; + interval_email = -1; + interval_location = -1; + interval_phone = -1; + interval_tool = -1; + interval_note = -1; - sdesbuildcount = 0; - transmissiondelay = RTPTime(0,0); + sdesbuildcount = 0; + transmissiondelay = RTPTime(0, 0); - firstpacket = true; - processingsdes = false; - init = true; - return 0; + firstpacket = true; + processingsdes = false; + init = true; + return 0; } void RTCPPacketBuilder::Destroy() { - if (!init) - return; - ownsdesinfo.Clear(); - init = false; + if (!init) + return; + ownsdesinfo.Clear(); + init = false; } int RTCPPacketBuilder::BuildNextPacket(RTCPCompoundPacket **pack) { - if (!init) - return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; - RTCPCompoundPacketBuilder *rtcpcomppack; - int status; - bool sender = false; - RTPSourceData *srcdat; + RTCPCompoundPacketBuilder *rtcpcomppack; + int status; + bool sender = false; + RTPSourceData *srcdat; - *pack = 0; + *pack = 0; - rtcpcomppack = new RTCPCompoundPacketBuilder(GetMemoryManager()); - if (rtcpcomppack == 0) - return ERR_RTP_OUTOFMEM; + rtcpcomppack = new RTCPCompoundPacketBuilder(); + if (rtcpcomppack == 0) + return ERR_RTP_OUTOFMEM; - if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) + { + delete rtcpcomppack; + return status; + } - if ((srcdat = sources.GetOwnSourceInfo()) != 0) - { - if (srcdat->IsSender()) - sender = true; - } + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + { + if (srcdat->IsSender()) + sender = true; + } - uint32_t ssrc = rtppacketbuilder.GetSSRC(); - RTPTime curtime = RTPTime::CurrentTime(); + uint32_t ssrc = rtppacketbuilder.GetSSRC(); + RTPTime curtime = RTPTime::CurrentTime(); - if (sender) - { - RTPTime rtppacktime = rtppacketbuilder.GetPacketTime(); - uint32_t rtppacktimestamp = rtppacketbuilder.GetPacketTimestamp(); - uint32_t packcount = rtppacketbuilder.GetPacketCount(); - uint32_t octetcount = rtppacketbuilder.GetPayloadOctetCount(); - RTPTime diff = curtime; - diff -= rtppacktime; - diff += transmissiondelay; // the sample being sampled at this very instant will need a larger timestamp + if (sender) + { + RTPTime rtppacktime = rtppacketbuilder.GetPacketTime(); + uint32_t rtppacktimestamp = rtppacketbuilder.GetPacketTimestamp(); + uint32_t packcount = rtppacketbuilder.GetPacketCount(); + uint32_t octetcount = rtppacketbuilder.GetPayloadOctetCount(); + RTPTime diff = curtime; + diff -= rtppacktime; + diff += transmissiondelay; // the sample being sampled at this very instant will need a larger timestamp - uint32_t tsdiff = (uint32_t)((diff.GetDouble()/timestampunit)+0.5); - uint32_t rtptimestamp = rtppacktimestamp+tsdiff; - RTPNTPTime ntptimestamp = curtime.GetNTPTime(); + uint32_t tsdiff = (uint32_t) ((diff.GetDouble() / timestampunit) + 0.5); + uint32_t rtptimestamp = rtppacktimestamp + tsdiff; + RTPNTPTime ntptimestamp = curtime.GetNTPTime(); - if ((status = rtcpcomppack->StartSenderReport(ssrc,ntptimestamp,rtptimestamp,packcount,octetcount)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } - } - else - { - if ((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } - } + if ((status = rtcpcomppack->StartSenderReport(ssrc, ntptimestamp, rtptimestamp, packcount, octetcount)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + else + { + if ((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } - uint8_t *owncname; - size_t owncnamelen; + uint8_t *owncname; + size_t owncnamelen; - owncname = ownsdesinfo.GetCNAME(&owncnamelen); + owncname = ownsdesinfo.GetCNAME(&owncnamelen); - if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } + if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME, owncname, owncnamelen)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } - if (!processingsdes) - { - int added,skipped; - bool full,atendoflist; + if (!processingsdes) + { + int added, skipped; + bool full, atendoflist; - if ((status = FillInReportBlocks(rtcpcomppack,curtime,sources.GetTotalCount(),&full,&added,&skipped,&atendoflist)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + if ((status = FillInReportBlocks(rtcpcomppack, curtime, sources.GetTotalCount(), &full, &added, &skipped, &atendoflist)) < 0) + { + delete rtcpcomppack; + return status; + } - if (full && added == 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - } + if (full && added == 0) + { + delete rtcpcomppack; + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + } - if (!full) - { - processingsdes = true; - sdesbuildcount++; + if (!full) + { + processingsdes = true; + sdesbuildcount++; - ClearAllSourceFlags(); + ClearAllSourceFlags(); - doname = false; - doemail = false; - doloc = false; - dophone = false; - dotool = false; - donote = false; - if (interval_name > 0 && ((sdesbuildcount%interval_name) == 0)) doname = true; - if (interval_email > 0 && ((sdesbuildcount%interval_email) == 0)) doemail = true; - if (interval_location > 0 && ((sdesbuildcount%interval_location) == 0)) doloc = true; - if (interval_phone > 0 && ((sdesbuildcount%interval_phone) == 0)) dophone = true; - if (interval_tool > 0 && ((sdesbuildcount%interval_tool) == 0)) dotool = true; - if (interval_note > 0 && ((sdesbuildcount%interval_note) == 0)) donote = true; + doname = false; + doemail = false; + doloc = false; + dophone = false; + dotool = false; + donote = false; + if (interval_name > 0 && ((sdesbuildcount % interval_name) == 0)) + doname = true; + if (interval_email > 0 && ((sdesbuildcount % interval_email) == 0)) + doemail = true; + if (interval_location > 0 && ((sdesbuildcount % interval_location) == 0)) + doloc = true; + if (interval_phone > 0 && ((sdesbuildcount % interval_phone) == 0)) + dophone = true; + if (interval_tool > 0 && ((sdesbuildcount % interval_tool) == 0)) + dotool = true; + if (interval_note > 0 && ((sdesbuildcount % interval_note) == 0)) + donote = true; - bool processedall; - int itemcount; + bool processedall; + int itemcount; - if ((status = FillInSDES(rtcpcomppack,&full,&processedall,&itemcount)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + if ((status = FillInSDES(rtcpcomppack, &full, &processedall, &itemcount)) < 0) + { + delete rtcpcomppack; + return status; + } - if (processedall) - { - processingsdes = false; - ClearAllSDESFlags(); - if (!full && skipped > 0) - { - // if the packet isn't full and we skipped some - // sources that we already got in a previous packet, - // we can add some of them now + if (processedall) + { + processingsdes = false; + ClearAllSDESFlags(); + if (!full && skipped > 0) + { + // if the packet isn't full and we skipped some + // sources that we already got in a previous packet, + // we can add some of them now - bool atendoflist; + bool atendoflist; - if ((status = FillInReportBlocks(rtcpcomppack,curtime,skipped,&full,&added,&skipped,&atendoflist)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } - } - } - } - } - else // previous sdes processing wasn't finished - { - bool processedall; - int itemcount; - bool full; + if ((status = FillInReportBlocks(rtcpcomppack, curtime, skipped, &full, &added, &skipped, &atendoflist)) < 0) + { + delete rtcpcomppack; + return status; + } + } + } + } + } + else // previous sdes processing wasn't finished + { + bool processedall; + int itemcount; + bool full; - if ((status = FillInSDES(rtcpcomppack,&full,&processedall,&itemcount)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + if ((status = FillInSDES(rtcpcomppack, &full, &processedall, &itemcount)) < 0) + { + delete rtcpcomppack; + return status; + } - if (itemcount == 0) // Big problem: packet size is too small to let any progress happen - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - } + if (itemcount == 0) // Big problem: packet size is too small to let any progress happen + { + delete rtcpcomppack; + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + } - if (processedall) - { - processingsdes = false; - ClearAllSDESFlags(); - if (!full) - { - // if the packet isn't full and we skipped some - // we can add some report blocks + if (processedall) + { + processingsdes = false; + ClearAllSDESFlags(); + if (!full) + { + // if the packet isn't full and we skipped some + // we can add some report blocks - int added,skipped; - bool atendoflist; + int added, skipped; + bool atendoflist; - if ((status = FillInReportBlocks(rtcpcomppack,curtime,sources.GetTotalCount(),&full,&added,&skipped,&atendoflist)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } - if (atendoflist) // filled in all possible sources - ClearAllSourceFlags(); - } - } - } + if ((status = FillInReportBlocks(rtcpcomppack, curtime, sources.GetTotalCount(), &full, &added, &skipped, &atendoflist)) < 0) + { + delete rtcpcomppack; + return status; + } + if (atendoflist) // filled in all possible sources + ClearAllSourceFlags(); + } + } + } - if ((status = rtcpcomppack->EndBuild()) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + if ((status = rtcpcomppack->EndBuild()) < 0) + { + delete rtcpcomppack; + return status; + } - *pack = rtcpcomppack; - firstpacket = false; - prevbuildtime = curtime; - return 0; + *pack = rtcpcomppack; + firstpacket = false; + prevbuildtime = curtime; + return 0; } void RTCPPacketBuilder::ClearAllSourceFlags() { - if (sources.GotoFirstSource()) - { - do - { - RTPSourceData *srcdat = sources.GetCurrentSourceInfo(); - srcdat->SetProcessedInRTCP(false); - } while (sources.GotoNextSource()); - } + if (sources.GotoFirstSource()) + { + do + { + RTPSourceData *srcdat = sources.GetCurrentSourceInfo(); + srcdat->SetProcessedInRTCP(false); + } while (sources.GotoNextSource()); + } } -int RTCPPacketBuilder::FillInReportBlocks(RTCPCompoundPacketBuilder *rtcpcomppack,const RTPTime &curtime,int maxcount,bool *full,int *added,int *skipped,bool *atendoflist) +int RTCPPacketBuilder::FillInReportBlocks(RTCPCompoundPacketBuilder *rtcpcomppack, const RTPTime &curtime, int maxcount, bool *full, int *added, int *skipped, bool *atendoflist) { - RTPSourceData *srcdat; - int addedcount = 0; - int skippedcount = 0; - bool done = false; - bool filled = false; - bool atend = false; - int status; + RTPSourceData *srcdat; + int addedcount = 0; + int skippedcount = 0; + bool done = false; + bool filled = false; + bool atend = false; + int status; - if (sources.GotoFirstSource()) - { - do - { - bool shouldprocess = false; + if (sources.GotoFirstSource()) + { + do + { + bool shouldprocess = false; - srcdat = sources.GetCurrentSourceInfo(); - if (!srcdat->IsOwnSSRC()) // don't send to ourselves - { - if (!srcdat->IsCSRC()) // p 35: no reports should go to CSRCs - { - if (srcdat->INF_HasSentData()) // if this isn't true, INF_GetLastRTPPacketTime() won't make any sense - { - if (firstpacket) - shouldprocess = true; - else - { - // p 35: only if rtp packets were received since the last RTP packet, a report block - // should be added + srcdat = sources.GetCurrentSourceInfo(); + if (!srcdat->IsOwnSSRC()) // don't send to ourselves + { + if (!srcdat->IsCSRC()) // p 35: no reports should go to CSRCs + { + if (srcdat->INF_HasSentData()) // if this isn't true, INF_GetLastRTPPacketTime() won't make any sense + { + if (firstpacket) + shouldprocess = true; + else + { + // p 35: only if rtp packets were received since the last RTP packet, a report block + // should be added - RTPTime lastrtptime = srcdat->INF_GetLastRTPPacketTime(); + RTPTime lastrtptime = srcdat->INF_GetLastRTPPacketTime(); - if (lastrtptime > prevbuildtime) - shouldprocess = true; - } - } - } - } + if (lastrtptime > prevbuildtime) + shouldprocess = true; + } + } + } + } - if (shouldprocess) - { - if (srcdat->IsProcessedInRTCP()) // already covered this one - { - skippedcount++; - } - else - { - uint32_t rr_ssrc = srcdat->GetSSRC(); - uint32_t num = srcdat->INF_GetNumPacketsReceivedInInterval(); - uint32_t prevseq = srcdat->INF_GetSavedExtendedSequenceNumber(); - uint32_t curseq = srcdat->INF_GetExtendedHighestSequenceNumber(); - uint32_t expected = curseq-prevseq; - uint8_t fraclost; + if (shouldprocess) + { + if (srcdat->IsProcessedInRTCP()) // already covered this one + { + skippedcount++; + } + else + { + uint32_t rr_ssrc = srcdat->GetSSRC(); + uint32_t num = srcdat->INF_GetNumPacketsReceivedInInterval(); + uint32_t prevseq = srcdat->INF_GetSavedExtendedSequenceNumber(); + uint32_t curseq = srcdat->INF_GetExtendedHighestSequenceNumber(); + uint32_t expected = curseq - prevseq; + uint8_t fraclost; - if (expected < num) // got duplicates - fraclost = 0; - else - { - double lost = (double)(expected-num); - double frac = lost/((double)expected); - fraclost = (uint8_t)(frac*256.0); - } + if (expected < num) // got duplicates + fraclost = 0; + else + { + double lost = (double) (expected - num); + double frac = lost / ((double) expected); + fraclost = (uint8_t) (frac * 256.0); + } - expected = curseq-srcdat->INF_GetBaseSequenceNumber(); - num = srcdat->INF_GetNumPacketsReceived(); + expected = curseq - srcdat->INF_GetBaseSequenceNumber(); + num = srcdat->INF_GetNumPacketsReceived(); - uint32_t diff = expected-num; - int32_t *packlost = (int32_t *)&diff; + uint32_t diff = expected - num; + int32_t *packlost = (int32_t *) &diff; - uint32_t jitter = srcdat->INF_GetJitter(); - uint32_t lsr; - uint32_t dlsr; + uint32_t jitter = srcdat->INF_GetJitter(); + uint32_t lsr; + uint32_t dlsr; - if (!srcdat->SR_HasInfo()) - { - lsr = 0; - dlsr = 0; - } - else - { - RTPNTPTime srtime = srcdat->SR_GetNTPTimestamp(); - uint32_t m = (srtime.GetMSW()&0xFFFF); - uint32_t l = ((srtime.GetLSW()>>16)&0xFFFF); - lsr = ((m<<16)|l); + if (!srcdat->SR_HasInfo()) + { + lsr = 0; + dlsr = 0; + } + else + { + RTPNTPTime srtime = srcdat->SR_GetNTPTimestamp(); + uint32_t m = (srtime.GetMSW() & 0xFFFF); + uint32_t l = ((srtime.GetLSW() >> 16) & 0xFFFF); + lsr = ((m << 16) | l); - RTPTime diff = curtime; - diff -= srcdat->SR_GetReceiveTime(); - double diff2 = diff.GetDouble(); - diff2 *= 65536.0; - dlsr = (uint32_t)diff2; - } + RTPTime diff = curtime; + diff -= srcdat->SR_GetReceiveTime(); + double diff2 = diff.GetDouble(); + diff2 *= 65536.0; + dlsr = (uint32_t) diff2; + } - status = rtcpcomppack->AddReportBlock(rr_ssrc,fraclost,*packlost,curseq,jitter,lsr,dlsr); - if (status < 0) - { - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - { - done = true; - filled = true; - } - else - return status; - } - else - { - addedcount++; - if (addedcount >= maxcount) - { - done = true; - if (!sources.GotoNextSource()) - atend = true; - } - srcdat->INF_StartNewInterval(); - srcdat->SetProcessedInRTCP(true); - } - } - } + status = rtcpcomppack->AddReportBlock(rr_ssrc, fraclost, *packlost, curseq, jitter, lsr, dlsr); + if (status < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + done = true; + filled = true; + } + else + return status; + } + else + { + addedcount++; + if (addedcount >= maxcount) + { + done = true; + if (!sources.GotoNextSource()) + atend = true; + } + srcdat->INF_StartNewInterval(); + srcdat->SetProcessedInRTCP(true); + } + } + } - if (!done) - { - if (!sources.GotoNextSource()) - { - atend = true; - done = true; - } - } + if (!done) + { + if (!sources.GotoNextSource()) + { + atend = true; + done = true; + } + } - } while (!done); - } + } while (!done); + } - *added = addedcount; - *skipped = skippedcount; - *full = filled; + *added = addedcount; + *skipped = skippedcount; + *full = filled; - if (!atend) // search for available sources - { - bool shouldprocess = false; + if (!atend) // search for available sources + { + bool shouldprocess = false; - do - { - srcdat = sources.GetCurrentSourceInfo(); - if (!srcdat->IsOwnSSRC()) // don't send to ourselves - { - if (!srcdat->IsCSRC()) // p 35: no reports should go to CSRCs - { - if (srcdat->INF_HasSentData()) // if this isn't true, INF_GetLastRTPPacketTime() won't make any sense - { - if (firstpacket) - shouldprocess = true; - else - { - // p 35: only if rtp packets were received since the last RTP packet, a report block - // should be added + do + { + srcdat = sources.GetCurrentSourceInfo(); + if (!srcdat->IsOwnSSRC()) // don't send to ourselves + { + if (!srcdat->IsCSRC()) // p 35: no reports should go to CSRCs + { + if (srcdat->INF_HasSentData()) // if this isn't true, INF_GetLastRTPPacketTime() won't make any sense + { + if (firstpacket) + shouldprocess = true; + else + { + // p 35: only if rtp packets were received since the last RTP packet, a report block + // should be added - RTPTime lastrtptime = srcdat->INF_GetLastRTPPacketTime(); + RTPTime lastrtptime = srcdat->INF_GetLastRTPPacketTime(); - if (lastrtptime > prevbuildtime) - shouldprocess = true; - } - } - } - } + if (lastrtptime > prevbuildtime) + shouldprocess = true; + } + } + } + } - if (shouldprocess) - { - if (srcdat->IsProcessedInRTCP()) - shouldprocess = false; - } + if (shouldprocess) + { + if (srcdat->IsProcessedInRTCP()) + shouldprocess = false; + } - if (!shouldprocess) - { - if (!sources.GotoNextSource()) - atend = true; - } + if (!shouldprocess) + { + if (!sources.GotoNextSource()) + atend = true; + } - } while (!atend && !shouldprocess); - } + } while (!atend && !shouldprocess); + } - *atendoflist = atend; - return 0; + *atendoflist = atend; + return 0; } -int RTCPPacketBuilder::FillInSDES(RTCPCompoundPacketBuilder *rtcpcomppack,bool *full,bool *processedall,int *added) +int RTCPPacketBuilder::FillInSDES(RTCPCompoundPacketBuilder *rtcpcomppack, bool *full, bool *processedall, int *added) { - int status; - uint8_t *data; - size_t datalen; + int status; + uint8_t *data; + size_t datalen; - *full = false; - *processedall = false; - *added = 0; + *full = false; + *processedall = false; + *added = 0; - // We don't need to add a SSRC for our own data, this is still set - // from adding the CNAME - if (doname) - { - if (!ownsdesinfo.ProcessedName()) - { - data = ownsdesinfo.GetName(&datalen); - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::NAME,data,datalen)) < 0) - { - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - { - *full = true; - return 0; - } - } - (*added)++; - ownsdesinfo.SetProcessedName(true); - } - } - if (doemail) - { - if (!ownsdesinfo.ProcessedEMail()) - { - data = ownsdesinfo.GetEMail(&datalen); - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::EMAIL,data,datalen)) < 0) - { - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - { - *full = true; - return 0; - } - } - (*added)++; - ownsdesinfo.SetProcessedEMail(true); - } - } - if (doloc) - { - if (!ownsdesinfo.ProcessedLocation()) - { - data = ownsdesinfo.GetLocation(&datalen); - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::LOC,data,datalen)) < 0) - { - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - { - *full = true; - return 0; - } - } - (*added)++; - ownsdesinfo.SetProcessedLocation(true); - } - } - if (dophone) - { - if (!ownsdesinfo.ProcessedPhone()) - { - data = ownsdesinfo.GetPhone(&datalen); - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::PHONE,data,datalen)) < 0) - { - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - { - *full = true; - return 0; - } - } - (*added)++; - ownsdesinfo.SetProcessedPhone(true); - } - } - if (dotool) - { - if (!ownsdesinfo.ProcessedTool()) - { - data = ownsdesinfo.GetTool(&datalen); - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::TOOL,data,datalen)) < 0) - { - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - { - *full = true; - return 0; - } - } - (*added)++; - ownsdesinfo.SetProcessedTool(true); - } - } - if (donote) - { - if (!ownsdesinfo.ProcessedNote()) - { - data = ownsdesinfo.GetNote(&datalen); - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::NOTE,data,datalen)) < 0) - { - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - { - *full = true; - return 0; - } - } - (*added)++; - ownsdesinfo.SetProcessedNote(true); - } - } + // We don't need to add a SSRC for our own data, this is still set + // from adding the CNAME + if (doname) + { + if (!ownsdesinfo.ProcessedName()) + { + data = ownsdesinfo.GetName(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::NAME, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedName(true); + } + } + if (doemail) + { + if (!ownsdesinfo.ProcessedEMail()) + { + data = ownsdesinfo.GetEMail(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::EMAIL, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedEMail(true); + } + } + if (doloc) + { + if (!ownsdesinfo.ProcessedLocation()) + { + data = ownsdesinfo.GetLocation(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::LOC, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedLocation(true); + } + } + if (dophone) + { + if (!ownsdesinfo.ProcessedPhone()) + { + data = ownsdesinfo.GetPhone(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::PHONE, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedPhone(true); + } + } + if (dotool) + { + if (!ownsdesinfo.ProcessedTool()) + { + data = ownsdesinfo.GetTool(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::TOOL, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedTool(true); + } + } + if (donote) + { + if (!ownsdesinfo.ProcessedNote()) + { + data = ownsdesinfo.GetNote(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::NOTE, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedNote(true); + } + } - *processedall = true; - return 0; + *processedall = true; + return 0; } void RTCPPacketBuilder::ClearAllSDESFlags() { - ownsdesinfo.ClearFlags(); + ownsdesinfo.ClearFlags(); } -int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack,const void *reason,size_t reasonlength,bool useSRifpossible) +int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack, const void *reason, size_t reasonlength, bool useSRifpossible) { - if (!init) - return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; - RTCPCompoundPacketBuilder *rtcpcomppack; - int status; + RTCPCompoundPacketBuilder *rtcpcomppack; + int status; - if (reasonlength > 255) - reasonlength = 255; + if (reasonlength > 255) + reasonlength = 255; - *pack = 0; + *pack = 0; - rtcpcomppack = new RTCPCompoundPacketBuilder(GetMemoryManager()); - if (rtcpcomppack == 0) - return ERR_RTP_OUTOFMEM; + rtcpcomppack = new RTCPCompoundPacketBuilder(); + if (rtcpcomppack == 0) + return ERR_RTP_OUTOFMEM; - if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) + { + delete rtcpcomppack; + return status; + } - uint32_t ssrc = rtppacketbuilder.GetSSRC(); - bool useSR = false; + uint32_t ssrc = rtppacketbuilder.GetSSRC(); + bool useSR = false; - if (useSRifpossible) - { - RTPSourceData *srcdat; + if (useSRifpossible) + { + RTPSourceData *srcdat; - if ((srcdat = sources.GetOwnSourceInfo()) != 0) - { - if (srcdat->IsSender()) - useSR = true; - } - } + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + { + if (srcdat->IsSender()) + useSR = true; + } + } - if (useSR) - { - RTPTime curtime = RTPTime::CurrentTime(); - RTPTime rtppacktime = rtppacketbuilder.GetPacketTime(); - uint32_t rtppacktimestamp = rtppacketbuilder.GetPacketTimestamp(); - uint32_t packcount = rtppacketbuilder.GetPacketCount(); - uint32_t octetcount = rtppacketbuilder.GetPayloadOctetCount(); - RTPTime diff = curtime; - diff -= rtppacktime; + if (useSR) + { + RTPTime curtime = RTPTime::CurrentTime(); + RTPTime rtppacktime = rtppacketbuilder.GetPacketTime(); + uint32_t rtppacktimestamp = rtppacketbuilder.GetPacketTimestamp(); + uint32_t packcount = rtppacketbuilder.GetPacketCount(); + uint32_t octetcount = rtppacketbuilder.GetPayloadOctetCount(); + RTPTime diff = curtime; + diff -= rtppacktime; - uint32_t tsdiff = (uint32_t)((diff.GetDouble()/timestampunit)+0.5); - uint32_t rtptimestamp = rtppacktimestamp+tsdiff; - RTPNTPTime ntptimestamp = curtime.GetNTPTime(); + uint32_t tsdiff = (uint32_t) ((diff.GetDouble() / timestampunit) + 0.5); + uint32_t rtptimestamp = rtppacktimestamp + tsdiff; + RTPNTPTime ntptimestamp = curtime.GetNTPTime(); - if ((status = rtcpcomppack->StartSenderReport(ssrc,ntptimestamp,rtptimestamp,packcount,octetcount)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } - } - else - { - if ((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } - } + if ((status = rtcpcomppack->StartSenderReport(ssrc, ntptimestamp, rtptimestamp, packcount, octetcount)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + else + { + if ((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } - uint8_t *owncname; - size_t owncnamelen; + uint8_t *owncname; + size_t owncnamelen; - owncname = ownsdesinfo.GetCNAME(&owncnamelen); + owncname = ownsdesinfo.GetCNAME(&owncnamelen); - if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } + if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME, owncname, owncnamelen)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } - uint32_t ssrcs[1]; + uint32_t ssrcs[1]; - ssrcs[0] = ssrc; + ssrcs[0] = ssrc; - if ((status = rtcpcomppack->AddBYEPacket(ssrcs,1,(const uint8_t *)reason,reasonlength)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) - return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; - return status; - } + if ((status = rtcpcomppack->AddBYEPacket(ssrcs, 1, (const uint8_t *) reason, reasonlength)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } - if ((status = rtcpcomppack->EndBuild()) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + if ((status = rtcpcomppack->EndBuild()) < 0) + { + delete rtcpcomppack; + return status; + } - *pack = rtcpcomppack; - return 0; + *pack = rtcpcomppack; + return 0; } } // end namespace diff --git a/qrtplib/rtcppacketbuilder.h b/qrtplib/rtcppacketbuilder.h index 0d0c83704..96cf1ce00 100644 --- a/qrtplib/rtcppacketbuilder.h +++ b/qrtplib/rtcppacketbuilder.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcppacketbuilder.h @@ -43,7 +43,6 @@ #include "rtperrors.h" #include "rtcpsdesinfo.h" #include "rtptimeutilities.h" -#include "rtpmemoryobject.h" namespace qrtplib { @@ -60,167 +59,301 @@ class RTCPCompoundPacketBuilder; * an RTPSources instance to automatically generate the next compound packet which should be sent. It also * provides functions to determine when SDES items other than the CNAME item should be sent. */ -class JRTPLIB_IMPORTEXPORT RTCPPacketBuilder : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTCPPacketBuilder { public: - /** Creates an RTCPPacketBuilder instance. - * Creates an instance which will use the source table \c sources and the RTP packet builder - * \c rtppackbuilder to determine the information for the next RTCP compound packet. Optionally, - * the memory manager \c mgr can be installed. - */ - RTCPPacketBuilder(RTPSources &sources,RTPPacketBuilder &rtppackbuilder, RTPMemoryManager *mgr = 0); - ~RTCPPacketBuilder(); + /** Creates an RTCPPacketBuilder instance. + * Creates an instance which will use the source table \c sources and the RTP packet builder + * \c rtppackbuilder to determine the information for the next RTCP compound packet. Optionally, + * the memory manager \c mgr can be installed. + */ + RTCPPacketBuilder(RTPSources &sources, RTPPacketBuilder &rtppackbuilder); + ~RTCPPacketBuilder(); - /** Initializes the builder. - * Initializes the builder to use the maximum allowed packet size \c maxpacksize, timestamp unit - * \c timestampunit and the SDES CNAME item specified by \c cname with length \c cnamelen. - * The timestamp unit is defined as a time interval divided by the timestamp interval corresponding to - * that interval: for 8000 Hz audio this would be 1/8000. - */ - int Init(size_t maxpacksize,double timestampunit,const void *cname,size_t cnamelen); + /** Initializes the builder. + * Initializes the builder to use the maximum allowed packet size \c maxpacksize, timestamp unit + * \c timestampunit and the SDES CNAME item specified by \c cname with length \c cnamelen. + * The timestamp unit is defined as a time interval divided by the timestamp interval corresponding to + * that interval: for 8000 Hz audio this would be 1/8000. + */ + int Init(size_t maxpacksize, double timestampunit, const void *cname, size_t cnamelen); - /** Cleans up the builder. */ - void Destroy(); + /** Cleans up the builder. */ + void Destroy(); - /** Sets the timestamp unit to be used to \c tsunit. - * Sets the timestamp unit to be used to \c tsunit. The timestamp unit is defined as a time interval - * divided by the timestamp interval corresponding to that interval: for 8000 Hz audio this would - * be 1/8000. - */ - int SetTimestampUnit(double tsunit) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; if (tsunit < 0) return ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT; timestampunit = tsunit; return 0; } + /** Sets the timestamp unit to be used to \c tsunit. + * Sets the timestamp unit to be used to \c tsunit. The timestamp unit is defined as a time interval + * divided by the timestamp interval corresponding to that interval: for 8000 Hz audio this would + * be 1/8000. + */ + int SetTimestampUnit(double tsunit) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + if (tsunit < 0) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT; + timestampunit = tsunit; + return 0; + } - /** Sets the maximum size allowed size of an RTCP compound packet to \c maxpacksize. */ - int SetMaximumPacketSize(size_t maxpacksize) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; if (maxpacksize < RTP_MINPACKETSIZE) return ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE; maxpacketsize = maxpacksize; return 0; } + /** Sets the maximum size allowed size of an RTCP compound packet to \c maxpacksize. */ + int SetMaximumPacketSize(size_t maxpacksize) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + if (maxpacksize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE; + maxpacketsize = maxpacksize; + return 0; + } - /** This function allows you to inform RTCP packet builder about the delay between sampling the first - * sample of a packet and sending the packet. - * This function allows you to inform RTCP packet builder about the delay between sampling the first - * sample of a packet and sending the packet. This delay is taken into account when calculating the - * relation between RTP timestamp and wallclock time, used for inter-media synchronization. - */ - int SetPreTransmissionDelay(const RTPTime &delay) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; transmissiondelay = delay; return 0; } + /** This function allows you to inform RTCP packet builder about the delay between sampling the first + * sample of a packet and sending the packet. + * This function allows you to inform RTCP packet builder about the delay between sampling the first + * sample of a packet and sending the packet. This delay is taken into account when calculating the + * relation between RTP timestamp and wallclock time, used for inter-media synchronization. + */ + int SetPreTransmissionDelay(const RTPTime &delay) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + transmissiondelay = delay; + return 0; + } - /** Builds the next RTCP compound packet which should be sent and stores it in \c pack. */ - int BuildNextPacket(RTCPCompoundPacket **pack); + /** Builds the next RTCP compound packet which should be sent and stores it in \c pack. */ + int BuildNextPacket(RTCPCompoundPacket **pack); - /** Builds a BYE packet with reason for leaving specified by \c reason and length \c reasonlength. - * Builds a BYE packet with reason for leaving specified by \c reason and length \c reasonlength. If - * \c useSRifpossible is set to \c true, the RTCP compound packet will start with a sender report if - * allowed. Otherwise, a receiver report is used. - */ - int BuildBYEPacket(RTCPCompoundPacket **pack,const void *reason,size_t reasonlength,bool useSRifpossible = true); + /** Builds a BYE packet with reason for leaving specified by \c reason and length \c reasonlength. + * Builds a BYE packet with reason for leaving specified by \c reason and length \c reasonlength. If + * \c useSRifpossible is set to \c true, the RTCP compound packet will start with a sender report if + * allowed. Otherwise, a receiver report is used. + */ + int BuildBYEPacket(RTCPCompoundPacket **pack, const void *reason, size_t reasonlength, bool useSRifpossible = true); - /** Sets the RTCP interval for the SDES name item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES name item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetNameInterval(int count) { if (!init) return; interval_name = count; } + /** Sets the RTCP interval for the SDES name item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES name item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNameInterval(int count) + { + if (!init) + return; + interval_name = count; + } - /** Sets the RTCP interval for the SDES e-mail item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES e-mail item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetEMailInterval(int count) { if (!init) return; interval_email = count; } + /** Sets the RTCP interval for the SDES e-mail item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES e-mail item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetEMailInterval(int count) + { + if (!init) + return; + interval_email = count; + } - /** Sets the RTCP interval for the SDES location item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES location item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetLocationInterval(int count) { if (!init) return; interval_location = count; } + /** Sets the RTCP interval for the SDES location item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES location item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetLocationInterval(int count) + { + if (!init) + return; + interval_location = count; + } - /** Sets the RTCP interval for the SDES phone item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES phone item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetPhoneInterval(int count) { if (!init) return; interval_phone = count; } + /** Sets the RTCP interval for the SDES phone item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES phone item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetPhoneInterval(int count) + { + if (!init) + return; + interval_phone = count; + } - /** Sets the RTCP interval for the SDES tool item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES tool item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetToolInterval(int count) { if (!init) return; interval_tool = count; } + /** Sets the RTCP interval for the SDES tool item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES tool item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetToolInterval(int count) + { + if (!init) + return; + interval_tool = count; + } - /** Sets the RTCP interval for the SDES note item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES note item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetNoteInterval(int count) { if (!init) return; interval_note = count; } + /** Sets the RTCP interval for the SDES note item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES note item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNoteInterval(int count) + { + if (!init) + return; + interval_note = count; + } - /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ - int SetLocalName(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetName((const uint8_t *)s,len); } + /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ + int SetLocalName(const void *s, size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetName((const uint8_t *) s, len); + } - /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ - int SetLocalEMail(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetEMail((const uint8_t *)s,len); } + /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ + int SetLocalEMail(const void *s, size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetEMail((const uint8_t *) s, len); + } - /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ - int SetLocalLocation(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetLocation((const uint8_t *)s,len); } + /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ + int SetLocalLocation(const void *s, size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetLocation((const uint8_t *) s, len); + } - /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ - int SetLocalPhone(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetPhone((const uint8_t *)s,len); } + /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ + int SetLocalPhone(const void *s, size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetPhone((const uint8_t *) s, len); + } - /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ - int SetLocalTool(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetTool((const uint8_t *)s,len); } + /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ + int SetLocalTool(const void *s, size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetTool((const uint8_t *) s, len); + } - /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ - int SetLocalNote(const void *s,size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; return ownsdesinfo.SetNote((const uint8_t *)s,len); } + /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ + int SetLocalNote(const void *s, size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetNote((const uint8_t *) s, len); + } - /** Returns the own CNAME item with length \c len */ - uint8_t *GetLocalCNAME(size_t *len) const { if (!init) return 0; return ownsdesinfo.GetCNAME(len); } + /** Returns the own CNAME item with length \c len */ + uint8_t *GetLocalCNAME(size_t *len) const + { + if (!init) + return 0; + return ownsdesinfo.GetCNAME(len); + } private: - void ClearAllSourceFlags(); - int FillInReportBlocks(RTCPCompoundPacketBuilder *pack,const RTPTime &curtime,int maxcount,bool *full,int *added,int *skipped,bool *atendoflist); - int FillInSDES(RTCPCompoundPacketBuilder *pack,bool *full,bool *processedall,int *added); - void ClearAllSDESFlags(); + void ClearAllSourceFlags(); + int FillInReportBlocks(RTCPCompoundPacketBuilder *pack, const RTPTime &curtime, int maxcount, bool *full, int *added, int *skipped, bool *atendoflist); + int FillInSDES(RTCPCompoundPacketBuilder *pack, bool *full, bool *processedall, int *added); + void ClearAllSDESFlags(); - RTPSources &sources; - RTPPacketBuilder &rtppacketbuilder; + RTPSources &sources; + RTPPacketBuilder &rtppacketbuilder; - bool init; - size_t maxpacketsize; - double timestampunit; - bool firstpacket; - RTPTime prevbuildtime,transmissiondelay; + bool init; + size_t maxpacketsize; + double timestampunit; + bool firstpacket; + RTPTime prevbuildtime, transmissiondelay; - class RTCPSDESInfoInternal : public RTCPSDESInfo - { - public: - RTCPSDESInfoInternal(RTPMemoryManager *mgr) : RTCPSDESInfo(mgr) { ClearFlags(); } - void ClearFlags() { pname = false; pemail = false; plocation = false; pphone = false; ptool = false; pnote = false; } - bool ProcessedName() const { return pname; } - bool ProcessedEMail() const { return pemail; } - bool ProcessedLocation() const { return plocation; } - bool ProcessedPhone() const { return pphone; } - bool ProcessedTool() const { return ptool; } - bool ProcessedNote() const { return pnote; } - void SetProcessedName(bool v) { pname = v; } - void SetProcessedEMail(bool v) { pemail = v; } - void SetProcessedLocation(bool v) { plocation = v; } - void SetProcessedPhone(bool v) { pphone = v; } - void SetProcessedTool(bool v) { ptool = v; } - void SetProcessedNote(bool v) { pnote = v; } - private: - bool pname,pemail,plocation,pphone,ptool,pnote; - }; + class RTCPSDESInfoInternal: public RTCPSDESInfo + { + public: + RTCPSDESInfoInternal() + { + ClearFlags(); + } + void ClearFlags() + { + pname = false; + pemail = false; + plocation = false; + pphone = false; + ptool = false; + pnote = false; + } + bool ProcessedName() const + { + return pname; + } + bool ProcessedEMail() const + { + return pemail; + } + bool ProcessedLocation() const + { + return plocation; + } + bool ProcessedPhone() const + { + return pphone; + } + bool ProcessedTool() const + { + return ptool; + } + bool ProcessedNote() const + { + return pnote; + } + void SetProcessedName(bool v) + { + pname = v; + } + void SetProcessedEMail(bool v) + { + pemail = v; + } + void SetProcessedLocation(bool v) + { + plocation = v; + } + void SetProcessedPhone(bool v) + { + pphone = v; + } + void SetProcessedTool(bool v) + { + ptool = v; + } + void SetProcessedNote(bool v) + { + pnote = v; + } + private: + bool pname, pemail, plocation, pphone, ptool, pnote; + }; - RTCPSDESInfoInternal ownsdesinfo; - int interval_name,interval_email,interval_location; - int interval_phone,interval_tool,interval_note; - bool doname,doemail,doloc,dophone,dotool,donote; - bool processingsdes; + RTCPSDESInfoInternal ownsdesinfo; + int interval_name, interval_email, interval_location; + int interval_phone, interval_tool, interval_note; + bool doname, doemail, doloc, dophone, dotool, donote; + bool processingsdes; - int sdesbuildcount; + int sdesbuildcount; }; } // end namespace diff --git a/qrtplib/rtcprrpacket.cpp b/qrtplib/rtcprrpacket.cpp index d6055bf80..9368127ac 100644 --- a/qrtplib/rtcprrpacket.cpp +++ b/qrtplib/rtcprrpacket.cpp @@ -1,67 +1,67 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcprrpacket.h" namespace qrtplib { -RTCPRRPacket::RTCPRRPacket(uint8_t *data,size_t datalength) - : RTCPPacket(RR,data,datalength) +RTCPRRPacket::RTCPRRPacket(uint8_t *data, size_t datalength) : + RTCPPacket(RR, data, datalength) { - knownformat = false; + knownformat = false; - RTCPCommonHeader *hdr; - size_t len = datalength; - size_t expectedlength; + RTCPCommonHeader *hdr; + size_t len = datalength; + size_t expectedlength; - hdr = (RTCPCommonHeader *)data; - if (hdr->padding) - { - uint8_t padcount = data[datalength-1]; - if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) - return; - if (((size_t)padcount) >= len) - return; - len -= (size_t)padcount; - } + hdr = (RTCPCommonHeader *) data; + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t) padcount) >= len) + return; + len -= (size_t) padcount; + } - expectedlength = sizeof(RTCPCommonHeader)+sizeof(uint32_t); - expectedlength += sizeof(RTCPReceiverReport)*((int)hdr->count); + expectedlength = sizeof(RTCPCommonHeader) + sizeof(uint32_t); + expectedlength += sizeof(RTCPReceiverReport) * ((int) hdr->count); - if (expectedlength != len) - return; + if (expectedlength != len) + return; - knownformat = true; + knownformat = true; } } // end namespace diff --git a/qrtplib/rtcprrpacket.h b/qrtplib/rtcprrpacket.h index 9e380460e..200e5b27c 100644 --- a/qrtplib/rtcprrpacket.h +++ b/qrtplib/rtcprrpacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcprrpacket.h @@ -49,152 +49,153 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP receiver report packet. */ -class JRTPLIB_IMPORTEXPORT RTCPRRPacket : public RTCPPacket +class JRTPLIB_IMPORTEXPORT RTCPRRPacket: public RTCPPacket { public: - /** Creates an instance based on the data in \c data with length \c datalen. - * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer - * is referenced inside the class (no copy of the data is made) one must make sure that the memory it points - * to is valid as long as the class instance exists. - */ - RTCPRRPacket(uint8_t *data,size_t datalen); - ~RTCPRRPacket() { } + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it points + * to is valid as long as the class instance exists. + */ + RTCPRRPacket(uint8_t *data, size_t datalen); + ~RTCPRRPacket() + { + } - /** Returns the SSRC of the participant who sent this packet. */ - uint32_t GetSenderSSRC() const; + /** Returns the SSRC of the participant who sent this packet. */ + uint32_t GetSenderSSRC() const; - /** Returns the number of reception report blocks present in this packet. */ - int GetReceptionReportCount() const; + /** Returns the number of reception report blocks present in this packet. */ + int GetReceptionReportCount() const; - /** Returns the SSRC of the reception report block described by \c index which may have a value - * from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetSSRC(int index) const; + /** Returns the SSRC of the reception report block described by \c index which may have a value + * from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetSSRC(int index) const; - /** Returns the `fraction lost' field of the reception report described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint8_t GetFractionLost(int index) const; + /** Returns the `fraction lost' field of the reception report described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint8_t GetFractionLost(int index) const; - /** Returns the number of lost packets in the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - int32_t GetLostPacketCount(int index) const; + /** Returns the number of lost packets in the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + int32_t GetLostPacketCount(int index) const; - /** Returns the extended highest sequence number of the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetExtendedHighestSequenceNumber(int index) const; + /** Returns the extended highest sequence number of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetExtendedHighestSequenceNumber(int index) const; - /** Returns the jitter field of the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetJitter(int index) const; + /** Returns the jitter field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetJitter(int index) const; - /** Returns the LSR field of the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetLSR(int index) const; - - /** Returns the DLSR field of the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetDLSR(int index) const; + /** Returns the LSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetLSR(int index) const; + /** Returns the DLSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetDLSR(int index) const; private: - RTCPReceiverReport *GotoReport(int index) const; + RTCPReceiverReport *GotoReport(int index) const; - RTPEndian m_endian; + RTPEndian m_endian; }; inline uint32_t RTCPRRPacket::GetSenderSSRC() const { - if (!knownformat) - return 0; + if (!knownformat) + return 0; - uint32_t *ssrcptr = (uint32_t *)(data+sizeof(RTCPCommonHeader)); - return m_endian.qToHost(*ssrcptr); + uint32_t *ssrcptr = (uint32_t *) (data + sizeof(RTCPCommonHeader)); + return m_endian.qToHost(*ssrcptr); } inline int RTCPRRPacket::GetReceptionReportCount() const { - if (!knownformat) - return 0; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; - return ((int)hdr->count); + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return ((int) hdr->count); } inline RTCPReceiverReport *RTCPRRPacket::GotoReport(int index) const { - RTCPReceiverReport *r = (RTCPReceiverReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)+index*sizeof(RTCPReceiverReport)); - return r; + RTCPReceiverReport *r = (RTCPReceiverReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) + index * sizeof(RTCPReceiverReport)); + return r; } inline uint32_t RTCPRRPacket::GetSSRC(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->ssrc); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->ssrc); } inline uint8_t RTCPRRPacket::GetFractionLost(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return r->fractionlost; + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return r->fractionlost; } inline int32_t RTCPRRPacket::GetLostPacketCount(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - uint32_t count = ((uint32_t)r->packetslost[2])|(((uint32_t)r->packetslost[1])<<8)|(((uint32_t)r->packetslost[0])<<16); - if ((count&0x00800000) != 0) // test for negative number - count |= 0xFF000000; - int32_t *count2 = (int32_t *)(&count); - return (*count2); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + uint32_t count = ((uint32_t) r->packetslost[2]) | (((uint32_t) r->packetslost[1]) << 8) | (((uint32_t) r->packetslost[0]) << 16); + if ((count & 0x00800000) != 0) // test for negative number + count |= 0xFF000000; + int32_t *count2 = (int32_t *) (&count); + return (*count2); } inline uint32_t RTCPRRPacket::GetExtendedHighestSequenceNumber(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->exthighseqnr); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->exthighseqnr); } inline uint32_t RTCPRRPacket::GetJitter(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->jitter); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->jitter); } inline uint32_t RTCPRRPacket::GetLSR(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->lsr); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->lsr); } inline uint32_t RTCPRRPacket::GetDLSR(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->dlsr); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->dlsr); } } // end namespace diff --git a/qrtplib/rtcpscheduler.cpp b/qrtplib/rtcpscheduler.cpp index b48180f5c..99a2589f1 100644 --- a/qrtplib/rtcpscheduler.cpp +++ b/qrtplib/rtcpscheduler.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcpscheduler.h" #include "rtpsources.h" @@ -43,13 +43,14 @@ namespace qrtplib { -RTCPSchedulerParams::RTCPSchedulerParams() : mininterval(RTCP_DEFAULTMININTERVAL) +RTCPSchedulerParams::RTCPSchedulerParams() : + mininterval(RTCP_DEFAULTMININTERVAL) { - bandwidth = 1000; // TODO What is a good value here? - senderfraction = RTCP_DEFAULTSENDERFRACTION; - usehalfatstartup = RTCP_DEFAULTHALFATSTARTUP; - immediatebye = RTCP_DEFAULTIMMEDIATEBYE; - timeinit.Dummy(); + bandwidth = 1000; // TODO What is a good value here? + senderfraction = RTCP_DEFAULTSENDERFRACTION; + usehalfatstartup = RTCP_DEFAULTHALFATSTARTUP; + immediatebye = RTCP_DEFAULTIMMEDIATEBYE; + timeinit.Dummy(); } RTCPSchedulerParams::~RTCPSchedulerParams() @@ -58,36 +59,37 @@ RTCPSchedulerParams::~RTCPSchedulerParams() int RTCPSchedulerParams::SetRTCPBandwidth(double bw) { - if (bw < 0.0) - return ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH; - bandwidth = bw; - return 0; + if (bw < 0.0) + return ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH; + bandwidth = bw; + return 0; } int RTCPSchedulerParams::SetSenderBandwidthFraction(double fraction) { - if (fraction < 0.0 || fraction > 1.0) - return ERR_RTP_SCHEDPARAMS_BADFRACTION; - senderfraction = fraction; - return 0; + if (fraction < 0.0 || fraction > 1.0) + return ERR_RTP_SCHEDPARAMS_BADFRACTION; + senderfraction = fraction; + return 0; } int RTCPSchedulerParams::SetMinimumTransmissionInterval(const RTPTime &t) { - double t2 = t.GetDouble(); + double t2 = t.GetDouble(); - if (t2 < RTCPSCHED_MININTERVAL) - return ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL; + if (t2 < RTCPSCHED_MININTERVAL) + return ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL; - mininterval = t; - return 0; + mininterval = t; + return 0; } -RTCPScheduler::RTCPScheduler(RTPSources &s, RTPRandom &r) : sources(s),nextrtcptime(0,0),prevrtcptime(0,0),rtprand(r) +RTCPScheduler::RTCPScheduler(RTPSources &s, RTPRandom &r) : + sources(s), nextrtcptime(0, 0), prevrtcptime(0, 0), rtprand(r) { - Reset(); + Reset(); - //std::cout << (void *)(&rtprand) << std::endl; + //std::cout << (void *)(&rtprand) << std::endl; } RTCPScheduler::~RTCPScheduler() @@ -96,323 +98,323 @@ RTCPScheduler::~RTCPScheduler() void RTCPScheduler::Reset() { - headeroverhead = 0; // user has to set this to an appropriate value - hassentrtcp = false; - firstcall = true; - avgrtcppacksize = 1000; // TODO: what is a good value for this? - byescheduled = false; - sendbyenow = false; + headeroverhead = 0; // user has to set this to an appropriate value + hassentrtcp = false; + firstcall = true; + avgrtcppacksize = 1000; // TODO: what is a good value for this? + byescheduled = false; + sendbyenow = false; } void RTCPScheduler::AnalyseIncoming(RTCPCompoundPacket &rtcpcomppack) { - bool isbye = false; - RTCPPacket *p; + bool isbye = false; + RTCPPacket *p; - rtcpcomppack.GotoFirstPacket(); - while (!isbye && ((p = rtcpcomppack.GetNextPacket()) != 0)) - { - if (p->GetPacketType() == RTCPPacket::BYE) - isbye = true; - } + rtcpcomppack.GotoFirstPacket(); + while (!isbye && ((p = rtcpcomppack.GetNextPacket()) != 0)) + { + if (p->GetPacketType() == RTCPPacket::BYE) + isbye = true; + } - if (!isbye) - { - size_t packsize = headeroverhead+rtcpcomppack.GetCompoundPacketLength(); - avgrtcppacksize = (size_t)((1.0/16.0)*((double)packsize)+(15.0/16.0)*((double)avgrtcppacksize)); - } - else - { - if (byescheduled) - { - size_t packsize = headeroverhead+rtcpcomppack.GetCompoundPacketLength(); - avgbyepacketsize = (size_t)((1.0/16.0)*((double)packsize)+(15.0/16.0)*((double)avgbyepacketsize)); - byemembers++; - } - } + if (!isbye) + { + size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgrtcppacksize = (size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgrtcppacksize)); + } + else + { + if (byescheduled) + { + size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgbyepacketsize = (size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgbyepacketsize)); + byemembers++; + } + } } void RTCPScheduler::AnalyseOutgoing(RTCPCompoundPacket &rtcpcomppack) { - bool isbye = false; - RTCPPacket *p; + bool isbye = false; + RTCPPacket *p; - rtcpcomppack.GotoFirstPacket(); - while (!isbye && ((p = rtcpcomppack.GetNextPacket()) != 0)) - { - if (p->GetPacketType() == RTCPPacket::BYE) - isbye = true; - } + rtcpcomppack.GotoFirstPacket(); + while (!isbye && ((p = rtcpcomppack.GetNextPacket()) != 0)) + { + if (p->GetPacketType() == RTCPPacket::BYE) + isbye = true; + } - if (!isbye) - { - size_t packsize = headeroverhead+rtcpcomppack.GetCompoundPacketLength(); - avgrtcppacksize = (size_t)((1.0/16.0)*((double)packsize)+(15.0/16.0)*((double)avgrtcppacksize)); - } + if (!isbye) + { + size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgrtcppacksize = (size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgrtcppacksize)); + } - hassentrtcp = true; + hassentrtcp = true; } RTPTime RTCPScheduler::GetTransmissionDelay() { - if (firstcall) - { - firstcall = false; - prevrtcptime = RTPTime::CurrentTime(); - pmembers = sources.GetActiveMemberCount(); - CalculateNextRTCPTime(); - } + if (firstcall) + { + firstcall = false; + prevrtcptime = RTPTime::CurrentTime(); + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + } - RTPTime curtime = RTPTime::CurrentTime(); + RTPTime curtime = RTPTime::CurrentTime(); - if (curtime > nextrtcptime) // packet should be sent - return RTPTime(0,0); + if (curtime > nextrtcptime) // packet should be sent + return RTPTime(0, 0); - RTPTime diff = nextrtcptime; - diff -= curtime; + RTPTime diff = nextrtcptime; + diff -= curtime; - return diff; + return diff; } bool RTCPScheduler::IsTime() { - if (firstcall) - { - firstcall = false; - prevrtcptime = RTPTime::CurrentTime(); - pmembers = sources.GetActiveMemberCount(); - CalculateNextRTCPTime(); - return false; - } + if (firstcall) + { + firstcall = false; + prevrtcptime = RTPTime::CurrentTime(); + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + return false; + } - RTPTime currenttime = RTPTime::CurrentTime(); + RTPTime currenttime = RTPTime::CurrentTime(); // // TODO: for debugging // double diff = nextrtcptime.GetDouble() - currenttime.GetDouble(); // // std::cout << "Delay till next RTCP interval: " << diff << std::endl; - if (currenttime < nextrtcptime) // timer has not yet expired - return false; + if (currenttime < nextrtcptime) // timer has not yet expired + return false; - RTPTime checktime(0,0); + RTPTime checktime(0, 0); - if (!byescheduled) - { - bool aresender = false; - RTPSourceData *srcdat; + if (!byescheduled) + { + bool aresender = false; + RTPSourceData *srcdat; - if ((srcdat = sources.GetOwnSourceInfo()) != 0) - aresender = srcdat->IsSender(); + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + aresender = srcdat->IsSender(); - checktime = CalculateTransmissionInterval(aresender); - } - else - checktime = CalculateBYETransmissionInterval(); + checktime = CalculateTransmissionInterval(aresender); + } + else + checktime = CalculateBYETransmissionInterval(); // std::cout << "Calculated checktime: " << checktime.GetDouble() << std::endl; - checktime += prevrtcptime; + checktime += prevrtcptime; - if (checktime <= currenttime) // Okay - { - byescheduled = false; - prevrtcptime = currenttime; - pmembers = sources.GetActiveMemberCount(); - CalculateNextRTCPTime(); - return true; - } + if (checktime <= currenttime) // Okay + { + byescheduled = false; + prevrtcptime = currenttime; + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + return true; + } // std::cout << "New delay: " << nextrtcptime.GetDouble() - currenttime.GetDouble() << std::endl; - nextrtcptime = checktime; - pmembers = sources.GetActiveMemberCount(); + nextrtcptime = checktime; + pmembers = sources.GetActiveMemberCount(); - return false; + return false; } void RTCPScheduler::CalculateNextRTCPTime() { - bool aresender = false; - RTPSourceData *srcdat; + bool aresender = false; + RTPSourceData *srcdat; - if ((srcdat = sources.GetOwnSourceInfo()) != 0) - aresender = srcdat->IsSender(); + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + aresender = srcdat->IsSender(); - nextrtcptime = RTPTime::CurrentTime(); - nextrtcptime += CalculateTransmissionInterval(aresender); + nextrtcptime = RTPTime::CurrentTime(); + nextrtcptime += CalculateTransmissionInterval(aresender); } RTPTime RTCPScheduler::CalculateDeterministicInterval(bool sender /* = false */) { - int numsenders = sources.GetSenderCount(); - int numtotal = sources.GetActiveMemberCount(); + int numsenders = sources.GetSenderCount(); + int numtotal = sources.GetActiveMemberCount(); // std::cout << "CalculateDeterministicInterval" << std::endl; // std::cout << " numsenders: " << numsenders << std::endl; // std::cout << " numtotal: " << numtotal << std::endl; - // Try to avoid division by zero: - if (numtotal == 0) - numtotal++; + // Try to avoid division by zero: + if (numtotal == 0) + numtotal++; - double sfraction = ((double)numsenders)/((double)numtotal); - double C,n; + double sfraction = ((double) numsenders) / ((double) numtotal); + double C, n; - if (sfraction <= schedparams.GetSenderBandwidthFraction()) - { - if (sender) - { - C = ((double)avgrtcppacksize)/(schedparams.GetSenderBandwidthFraction()*schedparams.GetRTCPBandwidth()); - n = (double)numsenders; - } - else - { - C = ((double)avgrtcppacksize)/((1.0-schedparams.GetSenderBandwidthFraction())*schedparams.GetRTCPBandwidth()); - n = (double)(numtotal-numsenders); - } - } - else - { - C = ((double)avgrtcppacksize)/schedparams.GetRTCPBandwidth(); - n = (double)numtotal; - } + if (sfraction <= schedparams.GetSenderBandwidthFraction()) + { + if (sender) + { + C = ((double) avgrtcppacksize) / (schedparams.GetSenderBandwidthFraction() * schedparams.GetRTCPBandwidth()); + n = (double) numsenders; + } + else + { + C = ((double) avgrtcppacksize) / ((1.0 - schedparams.GetSenderBandwidthFraction()) * schedparams.GetRTCPBandwidth()); + n = (double) (numtotal - numsenders); + } + } + else + { + C = ((double) avgrtcppacksize) / schedparams.GetRTCPBandwidth(); + n = (double) numtotal; + } - RTPTime Tmin = schedparams.GetMinimumTransmissionInterval(); - double tmin = Tmin.GetDouble(); + RTPTime Tmin = schedparams.GetMinimumTransmissionInterval(); + double tmin = Tmin.GetDouble(); - if (!hassentrtcp && schedparams.GetUseHalfAtStartup()) - tmin /= 2.0; + if (!hassentrtcp && schedparams.GetUseHalfAtStartup()) + tmin /= 2.0; - double ntimesC = n*C; - double Td = (tmin>ntimesC)?tmin:ntimesC; + double ntimesC = n * C; + double Td = (tmin > ntimesC) ? tmin : ntimesC; - // TODO: for debugging + // TODO: for debugging // std::cout << " Td: " << Td << std::endl; - return RTPTime(Td); + return RTPTime(Td); } RTPTime RTCPScheduler::CalculateTransmissionInterval(bool sender) { - RTPTime Td = CalculateDeterministicInterval(sender); - double td,mul,T; + RTPTime Td = CalculateDeterministicInterval(sender); + double td, mul, T; // std::cout << "CalculateTransmissionInterval" << std::endl; - td = Td.GetDouble(); - mul = rtprand.GetRandomDouble()+0.5; // gives random value between 0.5 and 1.5 - T = (td*mul)/1.21828; // see RFC 3550 p 30 + td = Td.GetDouble(); + mul = rtprand.GetRandomDouble() + 0.5; // gives random value between 0.5 and 1.5 + T = (td * mul) / 1.21828; // see RFC 3550 p 30 // std::cout << " Td: " << td << std::endl; // std::cout << " mul: " << mul << std::endl; // std::cout << " T: " << T << std::endl; - return RTPTime(T); + return RTPTime(T); } void RTCPScheduler::PerformReverseReconsideration() { - if (firstcall) - return; + if (firstcall) + return; - double diff1,diff2; - int members = sources.GetActiveMemberCount(); + double diff1, diff2; + int members = sources.GetActiveMemberCount(); - RTPTime tc = RTPTime::CurrentTime(); - RTPTime tn_min_tc = nextrtcptime; + RTPTime tc = RTPTime::CurrentTime(); + RTPTime tn_min_tc = nextrtcptime; - if (tn_min_tc > tc) - tn_min_tc -= tc; - else - tn_min_tc = RTPTime(0,0); + if (tn_min_tc > tc) + tn_min_tc -= tc; + else + tn_min_tc = RTPTime(0, 0); // std::cout << "+tn_min_tc0 " << nextrtcptime.GetDouble()-tc.GetDouble() << std::endl; // std::cout << "-tn_min_tc0 " << -nextrtcptime.GetDouble()+tc.GetDouble() << std::endl; // std::cout << "tn_min_tc " << tn_min_tc.GetDouble() << std::endl; - RTPTime tc_min_tp = tc; + RTPTime tc_min_tp = tc; - if (tc_min_tp > prevrtcptime) - tc_min_tp -= prevrtcptime; - else - tc_min_tp = 0; + if (tc_min_tp > prevrtcptime) + tc_min_tp -= prevrtcptime; + else + tc_min_tp = 0; - if (pmembers == 0) // avoid division by zero - pmembers++; + if (pmembers == 0) // avoid division by zero + pmembers++; - diff1 = (((double)members)/((double)pmembers))*tn_min_tc.GetDouble(); - diff2 = (((double)members)/((double)pmembers))*tc_min_tp.GetDouble(); + diff1 = (((double) members) / ((double) pmembers)) * tn_min_tc.GetDouble(); + diff2 = (((double) members) / ((double) pmembers)) * tc_min_tp.GetDouble(); - nextrtcptime = tc; - prevrtcptime = tc; - nextrtcptime += RTPTime(diff1); - prevrtcptime -= RTPTime(diff2); + nextrtcptime = tc; + prevrtcptime = tc; + nextrtcptime += RTPTime(diff1); + prevrtcptime -= RTPTime(diff2); - pmembers = members; + pmembers = members; } void RTCPScheduler::ScheduleBYEPacket(size_t packetsize) { - if (byescheduled) - return; + if (byescheduled) + return; - if (firstcall) - { - firstcall = false; - pmembers = sources.GetActiveMemberCount(); - } + if (firstcall) + { + firstcall = false; + pmembers = sources.GetActiveMemberCount(); + } - byescheduled = true; - avgbyepacketsize = packetsize+headeroverhead; + byescheduled = true; + avgbyepacketsize = packetsize + headeroverhead; - // For now, we will always use the BYE backoff algorithm as described in rfc 3550 p 33 + // For now, we will always use the BYE backoff algorithm as described in rfc 3550 p 33 - byemembers = 1; - pbyemembers = 1; + byemembers = 1; + pbyemembers = 1; - if (schedparams.GetRequestImmediateBYE() && sources.GetActiveMemberCount() < 50) // p 34 (top) - sendbyenow = true; - else - sendbyenow = false; + if (schedparams.GetRequestImmediateBYE() && sources.GetActiveMemberCount() < 50) // p 34 (top) + sendbyenow = true; + else + sendbyenow = false; - prevrtcptime = RTPTime::CurrentTime(); - nextrtcptime = prevrtcptime; - nextrtcptime += CalculateBYETransmissionInterval(); + prevrtcptime = RTPTime::CurrentTime(); + nextrtcptime = prevrtcptime; + nextrtcptime += CalculateBYETransmissionInterval(); } void RTCPScheduler::ActiveMemberDecrease() { - if (sources.GetActiveMemberCount() < pmembers) - PerformReverseReconsideration(); + if (sources.GetActiveMemberCount() < pmembers) + PerformReverseReconsideration(); } RTPTime RTCPScheduler::CalculateBYETransmissionInterval() { - if (!byescheduled) - return RTPTime(0,0); + if (!byescheduled) + return RTPTime(0, 0); - if (sendbyenow) - return RTPTime(0,0); + if (sendbyenow) + return RTPTime(0, 0); - double C,n; + double C, n; - C = ((double)avgbyepacketsize)/((1.0-schedparams.GetSenderBandwidthFraction())*schedparams.GetRTCPBandwidth()); - n = (double)byemembers; + C = ((double) avgbyepacketsize) / ((1.0 - schedparams.GetSenderBandwidthFraction()) * schedparams.GetRTCPBandwidth()); + n = (double) byemembers; - RTPTime Tmin = schedparams.GetMinimumTransmissionInterval(); - double tmin = Tmin.GetDouble(); + RTPTime Tmin = schedparams.GetMinimumTransmissionInterval(); + double tmin = Tmin.GetDouble(); - if (schedparams.GetUseHalfAtStartup()) - tmin /= 2.0; + if (schedparams.GetUseHalfAtStartup()) + tmin /= 2.0; - double ntimesC = n*C; - double Td = (tmin>ntimesC)?tmin:ntimesC; + double ntimesC = n * C; + double Td = (tmin > ntimesC) ? tmin : ntimesC; - double mul = rtprand.GetRandomDouble()+0.5; // gives random value between 0.5 and 1.5 - double T = (Td*mul)/1.21828; // see RFC 3550 p 30 + double mul = rtprand.GetRandomDouble() + 0.5; // gives random value between 0.5 and 1.5 + double T = (Td * mul) / 1.21828; // see RFC 3550 p 30 - return RTPTime(T); + return RTPTime(T); } } // end namespace diff --git a/qrtplib/rtcpscheduler.h b/qrtplib/rtcpscheduler.h index 14513c894..47c1d515a 100644 --- a/qrtplib/rtcpscheduler.h +++ b/qrtplib/rtcpscheduler.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpscheduler.h @@ -53,134 +53,167 @@ class RTPSources; class JRTPLIB_IMPORTEXPORT RTCPSchedulerParams { public: - RTCPSchedulerParams(); - ~RTCPSchedulerParams(); + RTCPSchedulerParams(); + ~RTCPSchedulerParams(); - /** Sets the RTCP bandwidth to be used to \c bw (in bytes per second). */ - int SetRTCPBandwidth(double bw); + /** Sets the RTCP bandwidth to be used to \c bw (in bytes per second). */ + int SetRTCPBandwidth(double bw); - /** Returns the used RTCP bandwidth in bytes per second (default is 1000). */ - double GetRTCPBandwidth() const { return bandwidth; } + /** Returns the used RTCP bandwidth in bytes per second (default is 1000). */ + double GetRTCPBandwidth() const + { + return bandwidth; + } - /** Sets the fraction of the RTCP bandwidth reserved for senders to \c fraction. */ - int SetSenderBandwidthFraction(double fraction); + /** Sets the fraction of the RTCP bandwidth reserved for senders to \c fraction. */ + int SetSenderBandwidthFraction(double fraction); - /** Returns the fraction of the RTCP bandwidth reserved for senders (default is 25%). */ - double GetSenderBandwidthFraction() const { return senderfraction; } + /** Returns the fraction of the RTCP bandwidth reserved for senders (default is 25%). */ + double GetSenderBandwidthFraction() const + { + return senderfraction; + } - /** Sets the minimum (deterministic) interval between RTCP compound packets to \c t. */ - int SetMinimumTransmissionInterval(const RTPTime &t); + /** Sets the minimum (deterministic) interval between RTCP compound packets to \c t. */ + int SetMinimumTransmissionInterval(const RTPTime &t); - /** Returns the minimum RTCP transmission interval (default is 5 seconds). */ - RTPTime GetMinimumTransmissionInterval() const { return mininterval; } + /** Returns the minimum RTCP transmission interval (default is 5 seconds). */ + RTPTime GetMinimumTransmissionInterval() const + { + return mininterval; + } - /** If \c usehalf is \c true, only use half the minimum interval before sending the first RTCP compound packet. */ - void SetUseHalfAtStartup(bool usehalf) { usehalfatstartup = usehalf; } + /** If \c usehalf is \c true, only use half the minimum interval before sending the first RTCP compound packet. */ + void SetUseHalfAtStartup(bool usehalf) + { + usehalfatstartup = usehalf; + } - /** Returns \c true if only half the minimum interval should be used before sending the first RTCP compound packet - * (defualt is \c true). - */ - bool GetUseHalfAtStartup() const { return usehalfatstartup; } + /** Returns \c true if only half the minimum interval should be used before sending the first RTCP compound packet + * (defualt is \c true). + */ + bool GetUseHalfAtStartup() const + { + return usehalfatstartup; + } - /** If \c v is \c true, the scheduler will schedule a BYE packet to be sent immediately if allowed. */ - void SetRequestImmediateBYE(bool v) { immediatebye = v; } + /** If \c v is \c true, the scheduler will schedule a BYE packet to be sent immediately if allowed. */ + void SetRequestImmediateBYE(bool v) + { + immediatebye = v; + } - /** Returns if the scheduler will schedule a BYE packet to be sent immediately if allowed - * (default is \c true). - */ - bool GetRequestImmediateBYE() const { return immediatebye; } + /** Returns if the scheduler will schedule a BYE packet to be sent immediately if allowed + * (default is \c true). + */ + bool GetRequestImmediateBYE() const + { + return immediatebye; + } private: - double bandwidth; - double senderfraction; - RTPTime mininterval; - bool usehalfatstartup; - bool immediatebye; + double bandwidth; + double senderfraction; + RTPTime mininterval; + bool usehalfatstartup; + bool immediatebye; }; /** This class determines when RTCP compound packets should be sent. */ class JRTPLIB_IMPORTEXPORT RTCPScheduler { public: - /** Creates an instance which will use the source table RTPSources to determine when RTCP compound - * packets should be scheduled. - * Creates an instance which will use the source table RTPSources to determine when RTCP compound - * packets should be scheduled. Note that for correct operation the \c sources instance should have information - * about the own SSRC (added by RTPSources::CreateOwnSSRC). You must also supply a random number - * generator \c rtprand which will be used for adding randomness to the RTCP intervals. - */ - RTCPScheduler(RTPSources &sources, RTPRandom &rtprand); - ~RTCPScheduler(); + /** Creates an instance which will use the source table RTPSources to determine when RTCP compound + * packets should be scheduled. + * Creates an instance which will use the source table RTPSources to determine when RTCP compound + * packets should be scheduled. Note that for correct operation the \c sources instance should have information + * about the own SSRC (added by RTPSources::CreateOwnSSRC). You must also supply a random number + * generator \c rtprand which will be used for adding randomness to the RTCP intervals. + */ + RTCPScheduler(RTPSources &sources, RTPRandom &rtprand); + ~RTCPScheduler(); - /** Resets the scheduler. */ - void Reset(); + /** Resets the scheduler. */ + void Reset(); - /** Sets the scheduler parameters to be used to \c params. */ - void SetParameters(const RTCPSchedulerParams ¶ms) { schedparams = params; } + /** Sets the scheduler parameters to be used to \c params. */ + void SetParameters(const RTCPSchedulerParams ¶ms) + { + schedparams = params; + } - /** Returns the currently used scheduler parameters. */ - RTCPSchedulerParams GetParameters() const { return schedparams; } + /** Returns the currently used scheduler parameters. */ + RTCPSchedulerParams GetParameters() const + { + return schedparams; + } - /** Sets the header overhead from underlying protocols (for example UDP and IP) to \c numbytes. */ - void SetHeaderOverhead(size_t numbytes) { headeroverhead = numbytes; } + /** Sets the header overhead from underlying protocols (for example UDP and IP) to \c numbytes. */ + void SetHeaderOverhead(size_t numbytes) + { + headeroverhead = numbytes; + } - /** Returns the currently used header overhead. */ - size_t GetHeaderOverhead() const { return headeroverhead; } + /** Returns the currently used header overhead. */ + size_t GetHeaderOverhead() const + { + return headeroverhead; + } - /** For each incoming RTCP compound packet, this function has to be called for the scheduler to work correctly. */ - void AnalyseIncoming(RTCPCompoundPacket &rtcpcomppack); + /** For each incoming RTCP compound packet, this function has to be called for the scheduler to work correctly. */ + void AnalyseIncoming(RTCPCompoundPacket &rtcpcomppack); - /** For each outgoing RTCP compound packet, this function has to be called for the scheduler to work correctly. */ - void AnalyseOutgoing(RTCPCompoundPacket &rtcpcomppack); + /** For each outgoing RTCP compound packet, this function has to be called for the scheduler to work correctly. */ + void AnalyseOutgoing(RTCPCompoundPacket &rtcpcomppack); - /** This function has to be called each time a member times out or sends a BYE packet. */ - void ActiveMemberDecrease(); + /** This function has to be called each time a member times out or sends a BYE packet. */ + void ActiveMemberDecrease(); - /** Asks the scheduler to schedule an RTCP compound packet containing a BYE packetl; the compound packet - * has size \c packetsize. - */ - void ScheduleBYEPacket(size_t packetsize); + /** Asks the scheduler to schedule an RTCP compound packet containing a BYE packetl; the compound packet + * has size \c packetsize. + */ + void ScheduleBYEPacket(size_t packetsize); - /** Returns the delay after which an RTCP compound will possibly have to be sent. - * Returns the delay after which an RTCP compound will possibly have to be sent. The IsTime member function - * should be called afterwards to make sure that it actually is time to send an RTCP compound packet. - */ - RTPTime GetTransmissionDelay(); + /** Returns the delay after which an RTCP compound will possibly have to be sent. + * Returns the delay after which an RTCP compound will possibly have to be sent. The IsTime member function + * should be called afterwards to make sure that it actually is time to send an RTCP compound packet. + */ + RTPTime GetTransmissionDelay(); - /** This function returns \c true if it's time to send an RTCP compound packet and \c false otherwise. - * This function returns \c true if it's time to send an RTCP compound packet and \c false otherwise. - * If the function returns \c true, it will also have calculated the next time at which a packet should - * be sent, so if it is called again right away, it will return \c false. - */ - bool IsTime(); + /** This function returns \c true if it's time to send an RTCP compound packet and \c false otherwise. + * This function returns \c true if it's time to send an RTCP compound packet and \c false otherwise. + * If the function returns \c true, it will also have calculated the next time at which a packet should + * be sent, so if it is called again right away, it will return \c false. + */ + bool IsTime(); - /** Calculates the deterministic interval at this time. - * Calculates the deterministic interval at this time. This is used - in combination with a certain multiplier - - * to time out members, senders etc. - */ - RTPTime CalculateDeterministicInterval(bool sender = false); + /** Calculates the deterministic interval at this time. + * Calculates the deterministic interval at this time. This is used - in combination with a certain multiplier - + * to time out members, senders etc. + */ + RTPTime CalculateDeterministicInterval(bool sender = false); private: - void CalculateNextRTCPTime(); - void PerformReverseReconsideration(); - RTPTime CalculateBYETransmissionInterval(); - RTPTime CalculateTransmissionInterval(bool sender); + void CalculateNextRTCPTime(); + void PerformReverseReconsideration(); + RTPTime CalculateBYETransmissionInterval(); + RTPTime CalculateTransmissionInterval(bool sender); - RTPSources &sources; - RTCPSchedulerParams schedparams; - size_t headeroverhead; - size_t avgrtcppacksize; - bool hassentrtcp; - bool firstcall; - RTPTime nextrtcptime; - RTPTime prevrtcptime; - int pmembers; + RTPSources &sources; + RTCPSchedulerParams schedparams; + size_t headeroverhead; + size_t avgrtcppacksize; + bool hassentrtcp; + bool firstcall; + RTPTime nextrtcptime; + RTPTime prevrtcptime; + int pmembers; - // for BYE packet scheduling - bool byescheduled; - int byemembers,pbyemembers; - size_t avgbyepacketsize; - bool sendbyenow; + // for BYE packet scheduling + bool byescheduled; + int byemembers, pbyemembers; + size_t avgbyepacketsize; + bool sendbyenow; - RTPRandom &rtprand; + RTPRandom &rtprand; }; } // end namespace diff --git a/qrtplib/rtcpsdesinfo.cpp b/qrtplib/rtcpsdesinfo.cpp index 40840b95c..1ed1895d1 100644 --- a/qrtplib/rtcpsdesinfo.cpp +++ b/qrtplib/rtcpsdesinfo.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcpsdesinfo.h" @@ -38,141 +38,141 @@ namespace qrtplib void RTCPSDESInfo::Clear() { #ifdef RTP_SUPPORT_SDESPRIV - std::list::const_iterator it; + std::list::const_iterator it; - for (it = privitems.begin() ; it != privitems.end() ; ++it) - RTPDelete(*it,GetMemoryManager()); - privitems.clear(); + for (it = privitems.begin(); it != privitems.end(); ++it) + delete *it; + privitems.clear(); #endif // RTP_SUPPORT_SDESPRIV } #ifdef RTP_SUPPORT_SDESPRIV -int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix,size_t prefixlen,const uint8_t *value,size_t valuelen) +int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix, size_t prefixlen, const uint8_t *value, size_t valuelen) { - std::list::const_iterator it; - bool found; + std::list::const_iterator it; + bool found; - found = false; - it = privitems.begin(); - while (!found && it != privitems.end()) - { - uint8_t *p; - size_t l; + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + size_t l; - p = (*it)->GetPrefix(&l); - if (l == prefixlen) - { - if (l <= 0) - found = true; - else if (memcmp(prefix,p,l) == 0) - found = true; - else - ++it; - } - else - ++it; - } + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix, p, l) == 0) + found = true; + else + ++it; + } + else + ++it; + } - SDESPrivateItem *item; + SDESPrivateItem *item; - if (found) // replace the value for this entry - item = *it; - else // no entry for this prefix found... add it - { - if (privitems.size() >= RTP_MAXPRIVITEMS) // too many items present, just ignore it - return ERR_RTP_SDES_MAXPRIVITEMS; + if (found) // replace the value for this entry + item = *it; + else // no entry for this prefix found... add it + { + if (privitems.size() >= RTP_MAXPRIVITEMS) // too many items present, just ignore it + return ERR_RTP_SDES_MAXPRIVITEMS; - int status; + int status; - item = new SDESPrivateItem(GetMemoryManager()); - if (item == 0) - return ERR_RTP_OUTOFMEM; - if ((status = item->SetPrefix(prefix,prefixlen)) < 0) - { - RTPDelete(item,GetMemoryManager()); - return status; - } - privitems.push_front(item); - } - return item->SetInfo(value,valuelen); + item = new SDESPrivateItem(); + if (item == 0) + return ERR_RTP_OUTOFMEM; + if ((status = item->SetPrefix(prefix, prefixlen)) < 0) + { + delete item; + return status; + } + privitems.push_front(item); + } + return item->SetInfo(value, valuelen); } -int RTCPSDESInfo::DeletePrivatePrefix(const uint8_t *prefix,size_t prefixlen) +int RTCPSDESInfo::DeletePrivatePrefix(const uint8_t *prefix, size_t prefixlen) { - std::list::iterator it; - bool found; + std::list::iterator it; + bool found; - found = false; - it = privitems.begin(); - while (!found && it != privitems.end()) - { - uint8_t *p; - size_t l; + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + size_t l; - p = (*it)->GetPrefix(&l); - if (l == prefixlen) - { - if (l <= 0) - found = true; - else if (memcmp(prefix,p,l) == 0) - found = true; - else - ++it; - } - else - ++it; - } - if (!found) - return ERR_RTP_SDES_PREFIXNOTFOUND; + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix, p, l) == 0) + found = true; + else + ++it; + } + else + ++it; + } + if (!found) + return ERR_RTP_SDES_PREFIXNOTFOUND; - RTPDelete(*it,GetMemoryManager()); - privitems.erase(it); - return 0; + delete *it; + privitems.erase(it); + return 0; } void RTCPSDESInfo::GotoFirstPrivateValue() { - curitem = privitems.begin(); + curitem = privitems.begin(); } -bool RTCPSDESInfo::GetNextPrivateValue(uint8_t **prefix,size_t *prefixlen,uint8_t **value,size_t *valuelen) +bool RTCPSDESInfo::GetNextPrivateValue(uint8_t **prefix, size_t *prefixlen, uint8_t **value, size_t *valuelen) { - if (curitem == privitems.end()) - return false; - *prefix = (*curitem)->GetPrefix(prefixlen); - *value = (*curitem)->GetInfo(valuelen); - ++curitem; - return true; + if (curitem == privitems.end()) + return false; + *prefix = (*curitem)->GetPrefix(prefixlen); + *value = (*curitem)->GetInfo(valuelen); + ++curitem; + return true; } -bool RTCPSDESInfo::GetPrivateValue(const uint8_t *prefix,size_t prefixlen,uint8_t **value,size_t *valuelen) const +bool RTCPSDESInfo::GetPrivateValue(const uint8_t *prefix, size_t prefixlen, uint8_t **value, size_t *valuelen) const { - std::list::const_iterator it; - bool found; + std::list::const_iterator it; + bool found; - found = false; - it = privitems.begin(); - while (!found && it != privitems.end()) - { - uint8_t *p; - size_t l; + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + size_t l; - p = (*it)->GetPrefix(&l); - if (l == prefixlen) - { - if (l <= 0) - found = true; - else if (memcmp(prefix,p,l) == 0) - found = true; - else - ++it; - } - else - ++it; - } - if (found) - *value = (*it)->GetInfo(valuelen); - return found; + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix, p, l) == 0) + found = true; + else + ++it; + } + else + ++it; + } + if (found) + *value = (*it)->GetInfo(valuelen); + return found; } #endif // RTP_SUPPORT_SDESPRIV diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h index 894776828..609ba215b 100644 --- a/qrtplib/rtcpsdesinfo.h +++ b/qrtplib/rtcpsdesinfo.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpsdesinfo.h @@ -42,7 +42,6 @@ #include "rtperrors.h" #include "rtpdefines.h" #include "rtptypes.h" -#include "rtpmemoryobject.h" #include #include @@ -50,167 +49,232 @@ namespace qrtplib { /** The class RTCPSDESInfo is a container for RTCP SDES information. */ -class JRTPLIB_IMPORTEXPORT RTCPSDESInfo : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTCPSDESInfo { public: - /** Constructs an instance, optionally installing a memory manager. */ - RTCPSDESInfo(RTPMemoryManager *mgr = 0) : RTPMemoryObject(mgr) { for (int i = 0 ; i < RTCP_SDES_NUMITEMS_NONPRIVATE ; i++) nonprivateitems[i].SetMemoryManager(mgr); } - virtual ~RTCPSDESInfo() { Clear(); } + /** Constructs an instance, optionally installing a memory manager. */ + RTCPSDESInfo() + { + } + virtual ~RTCPSDESInfo() + { + Clear(); + } - /** Clears all SDES information. */ - void Clear(); + /** Clears all SDES information. */ + void Clear(); - /** Sets the SDES CNAME item to \c s with length \c l. */ - int SetCNAME(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_CNAME-1,s,l); } + /** Sets the SDES CNAME item to \c s with length \c l. */ + int SetCNAME(const uint8_t *s, size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_CNAME - 1, s, l); + } - /** Sets the SDES name item to \c s with length \c l. */ - int SetName(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_NAME-1,s,l); } + /** Sets the SDES name item to \c s with length \c l. */ + int SetName(const uint8_t *s, size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_NAME - 1, s, l); + } - /** Sets the SDES e-mail item to \c s with length \c l. */ - int SetEMail(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_EMAIL-1,s,l); } + /** Sets the SDES e-mail item to \c s with length \c l. */ + int SetEMail(const uint8_t *s, size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_EMAIL - 1, s, l); + } - /** Sets the SDES phone item to \c s with length \c l. */ - int SetPhone(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_PHONE-1,s,l); } + /** Sets the SDES phone item to \c s with length \c l. */ + int SetPhone(const uint8_t *s, size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_PHONE - 1, s, l); + } - /** Sets the SDES location item to \c s with length \c l. */ - int SetLocation(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_LOCATION-1,s,l); } + /** Sets the SDES location item to \c s with length \c l. */ + int SetLocation(const uint8_t *s, size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_LOCATION - 1, s, l); + } - /** Sets the SDES tool item to \c s with length \c l. */ - int SetTool(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_TOOL-1,s,l); } + /** Sets the SDES tool item to \c s with length \c l. */ + int SetTool(const uint8_t *s, size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_TOOL - 1, s, l); + } - /** Sets the SDES note item to \c s with length \c l. */ - int SetNote(const uint8_t *s,size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_NOTE-1,s,l); } + /** Sets the SDES note item to \c s with length \c l. */ + int SetNote(const uint8_t *s, size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_NOTE - 1, s, l); + } #ifdef RTP_SUPPORT_SDESPRIV - /** Sets the entry for the prefix string specified by \c prefix with length \c prefixlen to contain - * the value string specified by \c value with length \c valuelen (if the maximum allowed - * number of prefixes was reached, the error code \c ERR_RTP_SDES_MAXPRIVITEMS is returned. - */ - int SetPrivateValue(const uint8_t *prefix,size_t prefixlen,const uint8_t *value,size_t valuelen); + /** Sets the entry for the prefix string specified by \c prefix with length \c prefixlen to contain + * the value string specified by \c value with length \c valuelen (if the maximum allowed + * number of prefixes was reached, the error code \c ERR_RTP_SDES_MAXPRIVITEMS is returned. + */ + int SetPrivateValue(const uint8_t *prefix, size_t prefixlen, const uint8_t *value, size_t valuelen); - /** Deletes the entry for the prefix specified by \c s with length \c len. */ - int DeletePrivatePrefix(const uint8_t *s,size_t len); + /** Deletes the entry for the prefix specified by \c s with length \c len. */ + int DeletePrivatePrefix(const uint8_t *s, size_t len); #endif // RTP_SUPPORT_SDESPRIV - /** Returns the SDES CNAME item and stores its length in \c len. */ - uint8_t *GetCNAME(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_CNAME-1,len); } + /** Returns the SDES CNAME item and stores its length in \c len. */ + uint8_t *GetCNAME(size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_CNAME - 1, len); + } - /** Returns the SDES name item and stores its length in \c len. */ - uint8_t *GetName(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_NAME-1,len); } + /** Returns the SDES name item and stores its length in \c len. */ + uint8_t *GetName(size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_NAME - 1, len); + } - /** Returns the SDES e-mail item and stores its length in \c len. */ - uint8_t *GetEMail(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_EMAIL-1,len); } + /** Returns the SDES e-mail item and stores its length in \c len. */ + uint8_t *GetEMail(size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_EMAIL - 1, len); + } - /** Returns the SDES phone item and stores its length in \c len. */ - uint8_t *GetPhone(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_PHONE-1,len); } + /** Returns the SDES phone item and stores its length in \c len. */ + uint8_t *GetPhone(size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_PHONE - 1, len); + } - /** Returns the SDES location item and stores its length in \c len. */ - uint8_t *GetLocation(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_LOCATION-1,len); } + /** Returns the SDES location item and stores its length in \c len. */ + uint8_t *GetLocation(size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_LOCATION - 1, len); + } - /** Returns the SDES tool item and stores its length in \c len. */ - uint8_t *GetTool(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_TOOL-1,len); } + /** Returns the SDES tool item and stores its length in \c len. */ + uint8_t *GetTool(size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_TOOL - 1, len); + } - /** Returns the SDES note item and stores its length in \c len. */ - uint8_t *GetNote(size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_NOTE-1,len); } + /** Returns the SDES note item and stores its length in \c len. */ + uint8_t *GetNote(size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_NOTE - 1, len); + } #ifdef RTP_SUPPORT_SDESPRIV - /** Starts the iteration over the stored SDES private item prefixes and their associated values. */ - void GotoFirstPrivateValue(); + /** Starts the iteration over the stored SDES private item prefixes and their associated values. */ + void GotoFirstPrivateValue(); - /** Returns SDES priv item information. - * If available, returns \c true and stores the next SDES - * private item prefix in \c prefix and its length in - * \c prefixlen. The associated value and its length are - * then stored in \c value and \c valuelen. Otherwise, - * it returns \c false. + /** Returns SDES priv item information. + * If available, returns \c true and stores the next SDES + * private item prefix in \c prefix and its length in + * \c prefixlen. The associated value and its length are + * then stored in \c value and \c valuelen. Otherwise, + * it returns \c false. */ - bool GetNextPrivateValue(uint8_t **prefix,size_t *prefixlen,uint8_t **value,size_t *valuelen); + bool GetNextPrivateValue(uint8_t **prefix, size_t *prefixlen, uint8_t **value, size_t *valuelen); - /** Returns SDES priv item information. - * Looks for the entry which corresponds to the SDES private - * item prefix \c prefix with length \c prefixlen. If found, - * the function returns \c true and stores the associated - * value and its length in \c value and \c valuelen - * respectively. - */ - bool GetPrivateValue(const uint8_t *prefix,size_t prefixlen,uint8_t **value,size_t *valuelen) const; + /** Returns SDES priv item information. + * Looks for the entry which corresponds to the SDES private + * item prefix \c prefix with length \c prefixlen. If found, + * the function returns \c true and stores the associated + * value and its length in \c value and \c valuelen + * respectively. + */ + bool GetPrivateValue(const uint8_t *prefix, size_t prefixlen, uint8_t **value, size_t *valuelen) const; #endif // RTP_SUPPORT_SDESPRIV private: - int SetNonPrivateItem(int itemno,const uint8_t *s,size_t l) { if (l > RTCP_SDES_MAXITEMLENGTH) return ERR_RTP_SDES_LENGTHTOOBIG; return nonprivateitems[itemno].SetInfo(s,l); } - uint8_t *GetNonPrivateItem(int itemno,size_t *len) const { return nonprivateitems[itemno].GetInfo(len); } + int SetNonPrivateItem(int itemno, const uint8_t *s, size_t l) + { + if (l > RTCP_SDES_MAXITEMLENGTH) + return ERR_RTP_SDES_LENGTHTOOBIG; + return nonprivateitems[itemno].SetInfo(s, l); + } + uint8_t *GetNonPrivateItem(int itemno, size_t *len) const + { + return nonprivateitems[itemno].GetInfo(len); + } - class SDESItem : public RTPMemoryObject - { - public: - SDESItem(RTPMemoryManager *mgr = 0) : RTPMemoryObject(mgr) - { - str = 0; - length = 0; - } - void SetMemoryManager(RTPMemoryManager *mgr) - { - RTPMemoryObject::SetMemoryManager(mgr); - } - ~SDESItem() - { - if (str) - RTPDeleteByteArray(str,GetMemoryManager()); - } - uint8_t *GetInfo(size_t *len) const { *len = length; return str; } - int SetInfo(const uint8_t *s,size_t len) { return SetString(&str,&length,s,len); } - protected: - int SetString(uint8_t **dest,size_t *destlen,const uint8_t *s,size_t len) - { - if (len <= 0) - { - if (*dest) - RTPDeleteByteArray((*dest),GetMemoryManager()); - *dest = 0; - *destlen = 0; - } - else - { - len = (len>RTCP_SDES_MAXITEMLENGTH)?RTCP_SDES_MAXITEMLENGTH:len; - uint8_t *str2 = new uint8_t[len]; - if (str2 == 0) - return ERR_RTP_OUTOFMEM; - memcpy(str2,s,len); - *destlen = len; - if (*dest) - RTPDeleteByteArray((*dest),GetMemoryManager()); - *dest = str2; - } - return 0; - } - private: - uint8_t *str; - size_t length; - }; + class SDESItem + { + public: + SDESItem() + { + str = 0; + length = 0; + } + ~SDESItem() + { + if (str) + delete[] str; + } + uint8_t *GetInfo(size_t *len) const + { + *len = length; + return str; + } + int SetInfo(const uint8_t *s, size_t len) + { + return SetString(&str, &length, s, len); + } + protected: + int SetString(uint8_t **dest, size_t *destlen, const uint8_t *s, size_t len) + { + if (len <= 0) + { + if (*dest) + delete[] (*dest); + *dest = 0; + *destlen = 0; + } + else + { + len = (len > RTCP_SDES_MAXITEMLENGTH) ? RTCP_SDES_MAXITEMLENGTH : len; + uint8_t *str2 = new uint8_t[len]; + if (str2 == 0) + return ERR_RTP_OUTOFMEM; + memcpy(str2, s, len); + *destlen = len; + if (*dest) + delete[] (*dest); + *dest = str2; + } + return 0; + } + private: + uint8_t *str; + size_t length; + }; - SDESItem nonprivateitems[RTCP_SDES_NUMITEMS_NONPRIVATE]; + SDESItem nonprivateitems[RTCP_SDES_NUMITEMS_NONPRIVATE]; #ifdef RTP_SUPPORT_SDESPRIV - class SDESPrivateItem : public SDESItem - { - public: - SDESPrivateItem(RTPMemoryManager *mgr) : SDESItem(mgr) - { - prefixlen = 0; - prefix = 0; - } - ~SDESPrivateItem() - { - if (prefix) - RTPDeleteByteArray(prefix,GetMemoryManager()); - } - uint8_t *GetPrefix(size_t *len) const { *len = prefixlen; return prefix; } - int SetPrefix(const uint8_t *s,size_t len) { return SetString(&prefix,&prefixlen,s,len); } - private: - uint8_t *prefix; - size_t prefixlen; - }; + class SDESPrivateItem: public SDESItem + { + public: + SDESPrivateItem() + { + prefixlen = 0; + prefix = 0; + } + ~SDESPrivateItem() + { + if (prefix) + delete[] prefix; + } + uint8_t *GetPrefix(size_t *len) const + { + *len = prefixlen; + return prefix; + } + int SetPrefix(const uint8_t *s, size_t len) + { + return SetString(&prefix, &prefixlen, s, len); + } + private: + uint8_t *prefix; + size_t prefixlen; + }; - std::list privitems; - std::list::const_iterator curitem; + std::list privitems; + std::list::const_iterator curitem; #endif // RTP_SUPPORT_SDESPRIV }; diff --git a/qrtplib/rtcpsdespacket.cpp b/qrtplib/rtcpsdespacket.cpp index 6d1876a79..03663b18a 100644 --- a/qrtplib/rtcpsdespacket.cpp +++ b/qrtplib/rtcpsdespacket.cpp @@ -1,141 +1,140 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcpsdespacket.h" namespace qrtplib { -RTCPSDESPacket::RTCPSDESPacket(uint8_t *data,size_t datalength) - : RTCPPacket(SDES,data,datalength) +RTCPSDESPacket::RTCPSDESPacket(uint8_t *data, size_t datalength) : + RTCPPacket(SDES, data, datalength) { - knownformat = false; - currentchunk = 0; - itemoffset = 0; - curchunknum = 0; + knownformat = false; + currentchunk = 0; + itemoffset = 0; + curchunknum = 0; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; - size_t len = datalength; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + size_t len = datalength; - if (hdr->padding) - { - uint8_t padcount = data[datalength-1]; - if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) - return; - if (((size_t)padcount) >= len) - return; - len -= (size_t)padcount; - } + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t) padcount) >= len) + return; + len -= (size_t) padcount; + } - if (hdr->count == 0) - { - if (len != sizeof(RTCPCommonHeader)) - return; - } - else - { - int ssrccount = (int)(hdr->count); - uint8_t *chunk; - int chunkoffset; + if (hdr->count == 0) + { + if (len != sizeof(RTCPCommonHeader)) + return; + } + else + { + int ssrccount = (int) (hdr->count); + uint8_t *chunk; + int chunkoffset; - if (len < sizeof(RTCPCommonHeader)) - return; + if (len < sizeof(RTCPCommonHeader)) + return; - len -= sizeof(RTCPCommonHeader); - chunk = data+sizeof(RTCPCommonHeader); + len -= sizeof(RTCPCommonHeader); + chunk = data + sizeof(RTCPCommonHeader); - while ((ssrccount > 0) && (len > 0)) - { - if (len < (sizeof(uint32_t)*2)) // chunk must contain at least a SSRC identifier - return; // and a (possibly empty) item + while ((ssrccount > 0) && (len > 0)) + { + if (len < (sizeof(uint32_t) * 2)) // chunk must contain at least a SSRC identifier + return; // and a (possibly empty) item - len -= sizeof(uint32_t); - chunkoffset = sizeof(uint32_t); + len -= sizeof(uint32_t); + chunkoffset = sizeof(uint32_t); - bool done = false; - while (!done) - { - if (len < 1) // at least a zero byte (end of item list) should be there - return; + bool done = false; + while (!done) + { + if (len < 1) // at least a zero byte (end of item list) should be there + return; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(chunk+chunkoffset); - if (sdeshdr->sdesid == 0) // end of item list - { - len--; - chunkoffset++; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (chunk + chunkoffset); + if (sdeshdr->sdesid == 0) // end of item list + { + len--; + chunkoffset++; - size_t r = (chunkoffset&0x03); - if (r != 0) - { - size_t addoffset = 4-r; + size_t r = (chunkoffset & 0x03); + if (r != 0) + { + size_t addoffset = 4 - r; - if (addoffset > len) - return; - len -= addoffset; - chunkoffset += addoffset; - } - done = true; - } - else - { - if (len < sizeof(RTCPSDESHeader)) - return; + if (addoffset > len) + return; + len -= addoffset; + chunkoffset += addoffset; + } + done = true; + } + else + { + if (len < sizeof(RTCPSDESHeader)) + return; - len -= sizeof(RTCPSDESHeader); - chunkoffset += sizeof(RTCPSDESHeader); + len -= sizeof(RTCPSDESHeader); + chunkoffset += sizeof(RTCPSDESHeader); - size_t itemlen = (size_t)(sdeshdr->length); - if (itemlen > len) - return; + size_t itemlen = (size_t)(sdeshdr->length); + if (itemlen > len) + return; - len -= itemlen; - chunkoffset += itemlen; - } - } + len -= itemlen; + chunkoffset += itemlen; + } + } - ssrccount--; - chunk += chunkoffset; - } + ssrccount--; + chunk += chunkoffset; + } - // check for remaining bytes - if (len > 0) - return; - if (ssrccount > 0) - return; - } + // check for remaining bytes + if (len > 0) + return; + if (ssrccount > 0) + return; + } - knownformat = true; + knownformat = true; } - } // end namespace diff --git a/qrtplib/rtcpsdespacket.h b/qrtplib/rtcpsdespacket.h index 874648fee..35c01a396 100644 --- a/qrtplib/rtcpsdespacket.h +++ b/qrtplib/rtcpsdespacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpsdespacket.h @@ -47,331 +47,333 @@ namespace qrtplib { - class RTCPCompoundPacket; +class RTCPCompoundPacket; /** Describes an RTCP source description packet. */ -class JRTPLIB_IMPORTEXPORT RTCPSDESPacket : public RTCPPacket +class JRTPLIB_IMPORTEXPORT RTCPSDESPacket: public RTCPPacket { public: - /** Identifies the type of an SDES item. */ - enum ItemType - { - None, /**< Used when the iteration over the items has finished. */ - CNAME, /**< Used for a CNAME (canonical name) item. */ - NAME, /**< Used for a NAME item. */ - EMAIL, /**< Used for an EMAIL item. */ - PHONE, /**< Used for a PHONE item. */ - LOC, /**< Used for a LOC (location) item. */ - TOOL, /**< Used for a TOOL item. */ - NOTE, /**< Used for a NOTE item. */ - PRIV, /**< Used for a PRIV item. */ - Unknown /**< Used when there is an item present, but the type is not recognized. */ - }; + /** Identifies the type of an SDES item. */ + enum ItemType + { + None, /**< Used when the iteration over the items has finished. */ + CNAME, /**< Used for a CNAME (canonical name) item. */ + NAME, /**< Used for a NAME item. */ + EMAIL, /**< Used for an EMAIL item. */ + PHONE, /**< Used for a PHONE item. */ + LOC, /**< Used for a LOC (location) item. */ + TOOL, /**< Used for a TOOL item. */ + NOTE, /**< Used for a NOTE item. */ + PRIV, /**< Used for a PRIV item. */ + Unknown /**< Used when there is an item present, but the type is not recognized. */ + }; - /** Creates an instance based on the data in \c data with length \c datalen. - * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer - * is referenced inside the class (no copy of the data is made) one must make sure that the memory it - * points to is valid as long as the class instance exists. - */ - RTCPSDESPacket(uint8_t *data,size_t datalen); - ~RTCPSDESPacket() { } + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPSDESPacket(uint8_t *data, size_t datalen); + ~RTCPSDESPacket() + { + } - /** Returns the number of SDES chunks in the SDES packet. - * Returns the number of SDES chunks in the SDES packet. Each chunk has its own SSRC identifier. - */ - int GetChunkCount() const; + /** Returns the number of SDES chunks in the SDES packet. + * Returns the number of SDES chunks in the SDES packet. Each chunk has its own SSRC identifier. + */ + int GetChunkCount() const; - /** Starts the iteration over the chunks. - * Starts the iteration. If no SDES chunks are present, the function returns \c false. Otherwise, - * it returns \c true and sets the current chunk to be the first chunk. - */ - bool GotoFirstChunk(); + /** Starts the iteration over the chunks. + * Starts the iteration. If no SDES chunks are present, the function returns \c false. Otherwise, + * it returns \c true and sets the current chunk to be the first chunk. + */ + bool GotoFirstChunk(); - /** Sets the current chunk to the next available chunk. - * Sets the current chunk to the next available chunk. If no next chunk is present, this function returns - * \c false, otherwise it returns \c true. - */ - bool GotoNextChunk(); + /** Sets the current chunk to the next available chunk. + * Sets the current chunk to the next available chunk. If no next chunk is present, this function returns + * \c false, otherwise it returns \c true. + */ + bool GotoNextChunk(); - /** Returns the SSRC identifier of the current chunk. */ - uint32_t GetChunkSSRC() const; + /** Returns the SSRC identifier of the current chunk. */ + uint32_t GetChunkSSRC() const; - /** Starts the iteration over the SDES items in the current chunk. - * Starts the iteration over the SDES items in the current chunk. If no SDES items are - * present, the function returns \c false. Otherwise, the function sets the current item - * to be the first one and returns \c true. - */ - bool GotoFirstItem(); + /** Starts the iteration over the SDES items in the current chunk. + * Starts the iteration over the SDES items in the current chunk. If no SDES items are + * present, the function returns \c false. Otherwise, the function sets the current item + * to be the first one and returns \c true. + */ + bool GotoFirstItem(); - /** Advances the iteration to the next item in the current chunk. - * If there's another item in the chunk, the current item is set to be the next one and the function - * returns \c true. Otherwise, the function returns \c false. - */ - bool GotoNextItem(); + /** Advances the iteration to the next item in the current chunk. + * If there's another item in the chunk, the current item is set to be the next one and the function + * returns \c true. Otherwise, the function returns \c false. + */ + bool GotoNextItem(); - /** Returns the SDES item type of the current item in the current chunk. */ - ItemType GetItemType() const; + /** Returns the SDES item type of the current item in the current chunk. */ + ItemType GetItemType() const; - /** Returns the item length of the current item in the current chunk. */ - size_t GetItemLength() const; + /** Returns the item length of the current item in the current chunk. */ + size_t GetItemLength() const; - /** Returns the item data of the current item in the current chunk. */ - uint8_t *GetItemData(); + /** Returns the item data of the current item in the current chunk. */ + uint8_t *GetItemData(); #ifdef RTP_SUPPORT_SDESPRIV - /** If the current item is an SDES PRIV item, this function returns the length of the - * prefix string of the private item. - */ - size_t GetPRIVPrefixLength() const; + /** If the current item is an SDES PRIV item, this function returns the length of the + * prefix string of the private item. + */ + size_t GetPRIVPrefixLength() const; - /** If the current item is an SDES PRIV item, this function returns actual data of the - * prefix string. - */ - uint8_t *GetPRIVPrefixData(); + /** If the current item is an SDES PRIV item, this function returns actual data of the + * prefix string. + */ + uint8_t *GetPRIVPrefixData(); - /** If the current item is an SDES PRIV item, this function returns the length of the - * value string of the private item. - */ - size_t GetPRIVValueLength() const; + /** If the current item is an SDES PRIV item, this function returns the length of the + * value string of the private item. + */ + size_t GetPRIVValueLength() const; - /** If the current item is an SDES PRIV item, this function returns actual value data of the - * private item. - */ - uint8_t *GetPRIVValueData(); + /** If the current item is an SDES PRIV item, this function returns actual value data of the + * private item. + */ + uint8_t *GetPRIVValueData(); #endif // RTP_SUPPORT_SDESPRIV private: - RTPEndian m_endian; - uint8_t *currentchunk; - int curchunknum; - size_t itemoffset; + RTPEndian m_endian; + uint8_t *currentchunk; + int curchunknum; + size_t itemoffset; }; inline int RTCPSDESPacket::GetChunkCount() const { - if (!knownformat) - return 0; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; - return ((int)hdr->count); + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return ((int) hdr->count); } inline bool RTCPSDESPacket::GotoFirstChunk() { - if (GetChunkCount() == 0) - { - currentchunk = 0; - return false; - } - currentchunk = data+sizeof(RTCPCommonHeader); - curchunknum = 1; - itemoffset = sizeof(uint32_t); - return true; + if (GetChunkCount() == 0) + { + currentchunk = 0; + return false; + } + currentchunk = data + sizeof(RTCPCommonHeader); + curchunknum = 1; + itemoffset = sizeof(uint32_t); + return true; } inline bool RTCPSDESPacket::GotoNextChunk() { - if (!knownformat) - return false; - if (currentchunk == 0) - return false; - if (curchunknum == GetChunkCount()) - return false; + if (!knownformat) + return false; + if (currentchunk == 0) + return false; + if (curchunknum == GetChunkCount()) + return false; - size_t offset = sizeof(uint32_t); - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+sizeof(uint32_t)); + size_t offset = sizeof(uint32_t); + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + sizeof(uint32_t)); - while (sdeshdr->sdesid != 0) - { - offset += sizeof(RTCPSDESHeader); - offset += (size_t)(sdeshdr->length); - sdeshdr = (RTCPSDESHeader *)(currentchunk+offset); - } - offset++; // for the zero byte - if ((offset&0x03) != 0) - offset += (4-(offset&0x03)); - currentchunk += offset; - curchunknum++; - itemoffset = sizeof(uint32_t); - return true; + while (sdeshdr->sdesid != 0) + { + offset += sizeof(RTCPSDESHeader); + offset += (size_t)(sdeshdr->length); + sdeshdr = (RTCPSDESHeader *) (currentchunk + offset); + } + offset++; // for the zero byte + if ((offset & 0x03) != 0) + offset += (4 - (offset & 0x03)); + currentchunk += offset; + curchunknum++; + itemoffset = sizeof(uint32_t); + return true; } inline uint32_t RTCPSDESPacket::GetChunkSSRC() const { - if (!knownformat) - return 0; - if (currentchunk == 0) - return 0; - uint32_t *ssrc = (uint32_t *)currentchunk; - return m_endian.qToHost(*ssrc); + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + uint32_t *ssrc = (uint32_t *) currentchunk; + return m_endian.qToHost(*ssrc); } inline bool RTCPSDESPacket::GotoFirstItem() { - if (!knownformat) - return false; - if (currentchunk == 0) - return false; - itemoffset = sizeof(uint32_t); - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - if (sdeshdr->sdesid == 0) - return false; - return true; + if (!knownformat) + return false; + if (currentchunk == 0) + return false; + itemoffset = sizeof(uint32_t); + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid == 0) + return false; + return true; } inline bool RTCPSDESPacket::GotoNextItem() { - if (!knownformat) - return false; - if (currentchunk == 0) - return false; + if (!knownformat) + return false; + if (currentchunk == 0) + return false; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - if (sdeshdr->sdesid == 0) - return false; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid == 0) + return false; - size_t offset = itemoffset; - offset += sizeof(RTCPSDESHeader); - offset += (size_t)(sdeshdr->length); - sdeshdr = (RTCPSDESHeader *)(currentchunk+offset); - if (sdeshdr->sdesid == 0) - return false; - itemoffset = offset; - return true; + size_t offset = itemoffset; + offset += sizeof(RTCPSDESHeader); + offset += (size_t)(sdeshdr->length); + sdeshdr = (RTCPSDESHeader *) (currentchunk + offset); + if (sdeshdr->sdesid == 0) + return false; + itemoffset = offset; + return true; } inline RTCPSDESPacket::ItemType RTCPSDESPacket::GetItemType() const { - if (!knownformat) - return None; - if (currentchunk == 0) - return None; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - switch (sdeshdr->sdesid) - { - case 0: - return None; - case RTCP_SDES_ID_CNAME: - return CNAME; - case RTCP_SDES_ID_NAME: - return NAME; - case RTCP_SDES_ID_EMAIL: - return EMAIL; - case RTCP_SDES_ID_PHONE: - return PHONE; - case RTCP_SDES_ID_LOCATION: - return LOC; - case RTCP_SDES_ID_TOOL: - return TOOL; - case RTCP_SDES_ID_NOTE: - return NOTE; - case RTCP_SDES_ID_PRIVATE: - return PRIV; - default: - return Unknown; - } - return Unknown; + if (!knownformat) + return None; + if (currentchunk == 0) + return None; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + switch (sdeshdr->sdesid) + { + case 0: + return None; + case RTCP_SDES_ID_CNAME: + return CNAME; + case RTCP_SDES_ID_NAME: + return NAME; + case RTCP_SDES_ID_EMAIL: + return EMAIL; + case RTCP_SDES_ID_PHONE: + return PHONE; + case RTCP_SDES_ID_LOCATION: + return LOC; + case RTCP_SDES_ID_TOOL: + return TOOL; + case RTCP_SDES_ID_NOTE: + return NOTE; + case RTCP_SDES_ID_PRIVATE: + return PRIV; + default: + return Unknown; + } + return Unknown; } inline size_t RTCPSDESPacket::GetItemLength() const { - if (!knownformat) - return None; - if (currentchunk == 0) - return None; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - if (sdeshdr->sdesid == 0) - return 0; - return (size_t)(sdeshdr->length); + if (!knownformat) + return None; + if (currentchunk == 0) + return None; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid == 0) + return 0; + return (size_t)(sdeshdr->length); } inline uint8_t *RTCPSDESPacket::GetItemData() { - if (!knownformat) - return 0; - if (currentchunk == 0) - return 0; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - if (sdeshdr->sdesid == 0) - return 0; - return (currentchunk+itemoffset+sizeof(RTCPSDESHeader)); + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid == 0) + return 0; + return (currentchunk + itemoffset + sizeof(RTCPSDESHeader)); } #ifdef RTP_SUPPORT_SDESPRIV inline size_t RTCPSDESPacket::GetPRIVPrefixLength() const { - if (!knownformat) - return 0; - if (currentchunk == 0) - return 0; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) - return 0; - if (sdeshdr->length == 0) - return 0; - uint8_t *preflen = currentchunk+itemoffset+sizeof(RTCPSDESHeader); - size_t prefixlength = (size_t)(*preflen); - if (prefixlength > (size_t)((sdeshdr->length)-1)) - return 0; - return prefixlength; + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); + size_t prefixlength = (size_t)(*preflen); + if (prefixlength > (size_t)((sdeshdr->length) - 1)) + return 0; + return prefixlength; } inline uint8_t *RTCPSDESPacket::GetPRIVPrefixData() { - if (!knownformat) - return 0; - if (currentchunk == 0) - return 0; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) - return 0; - if (sdeshdr->length == 0) - return 0; - uint8_t *preflen = currentchunk+itemoffset+sizeof(RTCPSDESHeader); - size_t prefixlength = (size_t)(*preflen); - if (prefixlength > (size_t)((sdeshdr->length)-1)) - return 0; - if (prefixlength == 0) - return 0; - return (currentchunk+itemoffset+sizeof(RTCPSDESHeader)+1); + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); + size_t prefixlength = (size_t)(*preflen); + if (prefixlength > (size_t)((sdeshdr->length) - 1)) + return 0; + if (prefixlength == 0) + return 0; + return (currentchunk + itemoffset + sizeof(RTCPSDESHeader) + 1); } inline size_t RTCPSDESPacket::GetPRIVValueLength() const { - if (!knownformat) - return 0; - if (currentchunk == 0) - return 0; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) - return 0; - if (sdeshdr->length == 0) - return 0; - uint8_t *preflen = currentchunk+itemoffset+sizeof(RTCPSDESHeader); - size_t prefixlength = (size_t)(*preflen); - if (prefixlength > (size_t)((sdeshdr->length)-1)) - return 0; - return ((size_t)(sdeshdr->length))-prefixlength-1; + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); + size_t prefixlength = (size_t)(*preflen); + if (prefixlength > (size_t)((sdeshdr->length) - 1)) + return 0; + return ((size_t)(sdeshdr->length)) - prefixlength - 1; } inline uint8_t *RTCPSDESPacket::GetPRIVValueData() { - if (!knownformat) - return 0; - if (currentchunk == 0) - return 0; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset); - if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) - return 0; - if (sdeshdr->length == 0) - return 0; - uint8_t *preflen = currentchunk+itemoffset+sizeof(RTCPSDESHeader); - size_t prefixlength = (size_t)(*preflen); - if (prefixlength > (size_t)((sdeshdr->length)-1)) - return 0; - size_t valuelen = ((size_t)(sdeshdr->length))-prefixlength-1; - if (valuelen == 0) - return 0; - return (currentchunk+itemoffset+sizeof(RTCPSDESHeader)+1+prefixlength); + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); + size_t prefixlength = (size_t)(*preflen); + if (prefixlength > (size_t)((sdeshdr->length) - 1)) + return 0; + size_t valuelen = ((size_t)(sdeshdr->length)) - prefixlength - 1; + if (valuelen == 0) + return 0; + return (currentchunk + itemoffset + sizeof(RTCPSDESHeader) + 1 + prefixlength); } #endif // RTP_SUPPORT_SDESPRIV diff --git a/qrtplib/rtcpsrpacket.cpp b/qrtplib/rtcpsrpacket.cpp index 1d7418163..805699018 100644 --- a/qrtplib/rtcpsrpacket.cpp +++ b/qrtplib/rtcpsrpacket.cpp @@ -1,69 +1,68 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtcpsrpacket.h" namespace qrtplib { -RTCPSRPacket::RTCPSRPacket(uint8_t *data,size_t datalength) - : RTCPPacket(SR,data,datalength) +RTCPSRPacket::RTCPSRPacket(uint8_t *data, size_t datalength) : + RTCPPacket(SR, data, datalength) { - knownformat = false; + knownformat = false; - RTCPCommonHeader *hdr; - size_t len = datalength; - size_t expectedlength; + RTCPCommonHeader *hdr; + size_t len = datalength; + size_t expectedlength; - hdr = (RTCPCommonHeader *)data; - if (hdr->padding) - { - uint8_t padcount = data[datalength-1]; - if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) - return; - if (((size_t)padcount) >= len) - return; - len -= (size_t)padcount; - } + hdr = (RTCPCommonHeader *) data; + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((size_t) padcount) >= len) + return; + len -= (size_t) padcount; + } - expectedlength = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+sizeof(RTCPSenderReport); - expectedlength += sizeof(RTCPReceiverReport)*((int)hdr->count); + expectedlength = sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport); + expectedlength += sizeof(RTCPReceiverReport) * ((int) hdr->count); - if (expectedlength != len) - return; + if (expectedlength != len) + return; - knownformat = true; + knownformat = true; } - } // end namespace diff --git a/qrtplib/rtcpsrpacket.h b/qrtplib/rtcpsrpacket.h index 020b689f1..200851eab 100644 --- a/qrtplib/rtcpsrpacket.h +++ b/qrtplib/rtcpsrpacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpsrpacket.h @@ -50,197 +50,199 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP sender report packet. */ -class JRTPLIB_IMPORTEXPORT RTCPSRPacket : public RTCPPacket +class JRTPLIB_IMPORTEXPORT RTCPSRPacket: public RTCPPacket { public: - /** Creates an instance based on the data in \c data with length \c datalen. - * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer - * is referenced inside the class (no copy of the data is made) one must make sure that the memory it - * points to is valid as long as the class instance exists. - */ - RTCPSRPacket(uint8_t *data,size_t datalength); - ~RTCPSRPacket() { } + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPSRPacket(uint8_t *data, size_t datalength); + ~RTCPSRPacket() + { + } - /** Returns the SSRC of the participant who sent this packet. */ - uint32_t GetSenderSSRC() const; + /** Returns the SSRC of the participant who sent this packet. */ + uint32_t GetSenderSSRC() const; - /** Returns the NTP timestamp contained in the sender report. */ - RTPNTPTime GetNTPTimestamp() const; + /** Returns the NTP timestamp contained in the sender report. */ + RTPNTPTime GetNTPTimestamp() const; - /** Returns the RTP timestamp contained in the sender report. */ - uint32_t GetRTPTimestamp() const; + /** Returns the RTP timestamp contained in the sender report. */ + uint32_t GetRTPTimestamp() const; - /** Returns the sender's packet count contained in the sender report. */ - uint32_t GetSenderPacketCount() const; + /** Returns the sender's packet count contained in the sender report. */ + uint32_t GetSenderPacketCount() const; - /** Returns the sender's octet count contained in the sender report. */ - uint32_t GetSenderOctetCount() const; + /** Returns the sender's octet count contained in the sender report. */ + uint32_t GetSenderOctetCount() const; - /** Returns the number of reception report blocks present in this packet. */ - int GetReceptionReportCount() const; + /** Returns the number of reception report blocks present in this packet. */ + int GetReceptionReportCount() const; - /** Returns the SSRC of the reception report block described by \c index which may have a value - * from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetSSRC(int index) const; + /** Returns the SSRC of the reception report block described by \c index which may have a value + * from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetSSRC(int index) const; - /** Returns the `fraction lost' field of the reception report described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint8_t GetFractionLost(int index) const; + /** Returns the `fraction lost' field of the reception report described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint8_t GetFractionLost(int index) const; - /** Returns the number of lost packets in the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - int32_t GetLostPacketCount(int index) const; + /** Returns the number of lost packets in the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + int32_t GetLostPacketCount(int index) const; - /** Returns the extended highest sequence number of the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetExtendedHighestSequenceNumber(int index) const; + /** Returns the extended highest sequence number of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetExtendedHighestSequenceNumber(int index) const; - /** Returns the jitter field of the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetJitter(int index) const; + /** Returns the jitter field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetJitter(int index) const; - /** Returns the LSR field of the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetLSR(int index) const; + /** Returns the LSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetLSR(int index) const; - /** Returns the DLSR field of the reception report block described by \c index which may have - * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is - * valid). - */ - uint32_t GetDLSR(int index) const; + /** Returns the DLSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetDLSR(int index) const; private: - RTCPReceiverReport *GotoReport(int index) const; + RTCPReceiverReport *GotoReport(int index) const; - RTPEndian m_endian; + RTPEndian m_endian; }; inline uint32_t RTCPSRPacket::GetSenderSSRC() const { - if (!knownformat) - return 0; + if (!knownformat) + return 0; - uint32_t *ssrcptr = (uint32_t *)(data+sizeof(RTCPCommonHeader)); - return m_endian.qToHost(*ssrcptr); + uint32_t *ssrcptr = (uint32_t *) (data + sizeof(RTCPCommonHeader)); + return m_endian.qToHost(*ssrcptr); } inline RTPNTPTime RTCPSRPacket::GetNTPTimestamp() const { - if (!knownformat) - return RTPNTPTime(0,0); + if (!knownformat) + return RTPNTPTime(0, 0); - RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); - return RTPNTPTime(m_endian.qToHost(sr->ntptime_msw),m_endian.qToHost(sr->ntptime_lsw)); + RTCPSenderReport *sr = (RTCPSenderReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); + return RTPNTPTime(m_endian.qToHost(sr->ntptime_msw), m_endian.qToHost(sr->ntptime_lsw)); } inline uint32_t RTCPSRPacket::GetRTPTimestamp() const { - if (!knownformat) - return 0; - RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); - return m_endian.qToHost(sr->rtptimestamp); + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); + return m_endian.qToHost(sr->rtptimestamp); } inline uint32_t RTCPSRPacket::GetSenderPacketCount() const { - if (!knownformat) - return 0; - RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); - return m_endian.qToHost(sr->packetcount); + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); + return m_endian.qToHost(sr->packetcount); } inline uint32_t RTCPSRPacket::GetSenderOctetCount() const { - if (!knownformat) - return 0; - RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)); - return m_endian.qToHost(sr->octetcount); + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); + return m_endian.qToHost(sr->octetcount); } inline int RTCPSRPacket::GetReceptionReportCount() const { - if (!knownformat) - return 0; - RTCPCommonHeader *hdr = (RTCPCommonHeader *)data; - return ((int)hdr->count); + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return ((int) hdr->count); } inline RTCPReceiverReport *RTCPSRPacket::GotoReport(int index) const { - RTCPReceiverReport *r = (RTCPReceiverReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)+sizeof(RTCPSenderReport)+index*sizeof(RTCPReceiverReport)); - return r; + RTCPReceiverReport *r = (RTCPReceiverReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport) + index * sizeof(RTCPReceiverReport)); + return r; } inline uint32_t RTCPSRPacket::GetSSRC(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->ssrc); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->ssrc); } inline uint8_t RTCPSRPacket::GetFractionLost(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return r->fractionlost; + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return r->fractionlost; } inline int32_t RTCPSRPacket::GetLostPacketCount(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - uint32_t count = ((uint32_t)r->packetslost[2])|(((uint32_t)r->packetslost[1])<<8)|(((uint32_t)r->packetslost[0])<<16); - if ((count&0x00800000) != 0) // test for negative number - count |= 0xFF000000; - int32_t *count2 = (int32_t *)(&count); - return (*count2); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + uint32_t count = ((uint32_t) r->packetslost[2]) | (((uint32_t) r->packetslost[1]) << 8) | (((uint32_t) r->packetslost[0]) << 16); + if ((count & 0x00800000) != 0) // test for negative number + count |= 0xFF000000; + int32_t *count2 = (int32_t *) (&count); + return (*count2); } inline uint32_t RTCPSRPacket::GetExtendedHighestSequenceNumber(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->exthighseqnr); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->exthighseqnr); } inline uint32_t RTCPSRPacket::GetJitter(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->jitter); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->jitter); } inline uint32_t RTCPSRPacket::GetLSR(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->lsr); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->lsr); } inline uint32_t RTCPSRPacket::GetDLSR(int index) const { - if (!knownformat) - return 0; - RTCPReceiverReport *r = GotoReport(index); - return m_endian.qToHost(r->dlsr); + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->dlsr); } } // end namespace diff --git a/qrtplib/rtcpunknownpacket.h b/qrtplib/rtcpunknownpacket.h index 79786539c..229c45a00 100644 --- a/qrtplib/rtcpunknownpacket.h +++ b/qrtplib/rtcpunknownpacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtcpunknownpacket.h @@ -51,20 +51,23 @@ class RTCPCompoundPacket; * the ones it inherited. Note that since an unknown packet type doesn't have any format to check * against, the IsKnownFormat function will trivially return \c true. */ -class JRTPLIB_IMPORTEXPORT RTCPUnknownPacket : public RTCPPacket +class JRTPLIB_IMPORTEXPORT RTCPUnknownPacket: public RTCPPacket { public: - /** Creates an instance based on the data in \c data with length \c datalen. - * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer - * is referenced inside the class (no copy of the data is made) one must make sure that the memory it - * points to is valid as long as the class instance exists. - */ - RTCPUnknownPacket(uint8_t *data,size_t datalen) : RTCPPacket(Unknown,data,datalen) - { - // Since we don't expect a format, we'll trivially put knownformat = true - knownformat = true; - } - ~RTCPUnknownPacket() { } + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPUnknownPacket(uint8_t *data, size_t datalen) : + RTCPPacket(Unknown, data, datalen) + { + // Since we don't expect a format, we'll trivially put knownformat = true + knownformat = true; + } + ~RTCPUnknownPacket() + { + } }; } // end namespace diff --git a/qrtplib/rtpabortdescriptors.cpp b/qrtplib/rtpabortdescriptors.cpp index 2aec6d4a8..8753154bc 100644 --- a/qrtplib/rtpabortdescriptors.cpp +++ b/qrtplib/rtpabortdescriptors.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpabortdescriptors.h" #include "rtpsocketutilinternal.h" @@ -40,187 +40,187 @@ namespace qrtplib RTPAbortDescriptors::RTPAbortDescriptors() { - m_descriptors[0] = RTPSOCKERR; - m_descriptors[1] = RTPSOCKERR; - m_init = false; + m_descriptors[0] = RTPSOCKERR; + m_descriptors[1] = RTPSOCKERR; + m_init = false; } RTPAbortDescriptors::~RTPAbortDescriptors() { - Destroy(); + Destroy(); } #ifdef RTP_SOCKETTYPE_WINSOCK int RTPAbortDescriptors::Init() { - if (m_init) - return ERR_RTP_ABORTDESC_ALREADYINIT; + if (m_init) + return ERR_RTP_ABORTDESC_ALREADYINIT; - SOCKET listensock; - int size; - struct sockaddr_in addr; + SOCKET listensock; + int size; + struct sockaddr_in addr; - listensock = socket(PF_INET,SOCK_STREAM,0); - if (listensock == RTPSOCKERR) - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + listensock = socket(PF_INET,SOCK_STREAM,0); + if (listensock == RTPSOCKERR) + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - memset(&addr,0,sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - if (bind(listensock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(listensock); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + if (bind(listensock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(listensock); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } - memset(&addr,0,sizeof(struct sockaddr_in)); - size = sizeof(struct sockaddr_in); - if (getsockname(listensock,(struct sockaddr*)&addr,&size) != 0) - { - RTPCLOSE(listensock); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } + memset(&addr,0,sizeof(struct sockaddr_in)); + size = sizeof(struct sockaddr_in); + if (getsockname(listensock,(struct sockaddr*)&addr,&size) != 0) + { + RTPCLOSE(listensock); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } - unsigned short connectport = ntohs(addr.sin_port); + unsigned short connectport = ntohs(addr.sin_port); - m_descriptors[0] = socket(PF_INET,SOCK_STREAM,0); - if (m_descriptors[0] == RTPSOCKERR) - { - RTPCLOSE(listensock); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } + m_descriptors[0] = socket(PF_INET,SOCK_STREAM,0); + if (m_descriptors[0] == RTPSOCKERR) + { + RTPCLOSE(listensock); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } - memset(&addr,0,sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - if (bind(m_descriptors[0],(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(listensock); - RTPCLOSE(m_descriptors[0]); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + if (bind(m_descriptors[0],(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(listensock); + RTPCLOSE(m_descriptors[0]); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } - if (listen(listensock,1) != 0) - { - RTPCLOSE(listensock); - RTPCLOSE(m_descriptors[0]); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } + if (listen(listensock,1) != 0) + { + RTPCLOSE(listensock); + RTPCLOSE(m_descriptors[0]); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } - memset(&addr,0,sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr("127.0.0.1"); - addr.sin_port = htons(connectport); + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(connectport); - if (connect(m_descriptors[0],(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(listensock); - RTPCLOSE(m_descriptors[0]); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } + if (connect(m_descriptors[0],(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(listensock); + RTPCLOSE(m_descriptors[0]); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } - memset(&addr,0,sizeof(struct sockaddr_in)); - size = sizeof(struct sockaddr_in); - m_descriptors[1] = accept(listensock,(struct sockaddr *)&addr,&size); - if (m_descriptors[1] == RTPSOCKERR) - { - RTPCLOSE(listensock); - RTPCLOSE(m_descriptors[0]); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } + memset(&addr,0,sizeof(struct sockaddr_in)); + size = sizeof(struct sockaddr_in); + m_descriptors[1] = accept(listensock,(struct sockaddr *)&addr,&size); + if (m_descriptors[1] == RTPSOCKERR) + { + RTPCLOSE(listensock); + RTPCLOSE(m_descriptors[0]); + return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; + } - // okay, got the connection, close the listening socket + // okay, got the connection, close the listening socket - RTPCLOSE(listensock); + RTPCLOSE(listensock); - m_init = true; - return 0; + m_init = true; + return 0; } void RTPAbortDescriptors::Destroy() { - if (!m_init) - return; + if (!m_init) + return; - RTPCLOSE(m_descriptors[0]); - RTPCLOSE(m_descriptors[1]); - m_descriptors[0] = RTPSOCKERR; - m_descriptors[1] = RTPSOCKERR; + RTPCLOSE(m_descriptors[0]); + RTPCLOSE(m_descriptors[1]); + m_descriptors[0] = RTPSOCKERR; + m_descriptors[1] = RTPSOCKERR; - m_init = false; + m_init = false; } int RTPAbortDescriptors::SendAbortSignal() { - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; - send(m_descriptors[1],"*",1,0); - return 0; + send(m_descriptors[1],"*",1,0); + return 0; } int RTPAbortDescriptors::ReadSignallingByte() { - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; - char buf[1]; + char buf[1]; - recv(m_descriptors[0],buf,1,0); - return 0; + recv(m_descriptors[0],buf,1,0); + return 0; } #else // unix-style int RTPAbortDescriptors::Init() { - if (m_init) - return ERR_RTP_ABORTDESC_ALREADYINIT; + if (m_init) + return ERR_RTP_ABORTDESC_ALREADYINIT; - if (pipe(m_descriptors) < 0) - return ERR_RTP_ABORTDESC_CANTCREATEPIPE; + if (pipe(m_descriptors) < 0) + return ERR_RTP_ABORTDESC_CANTCREATEPIPE; - m_init = true; - return 0; + m_init = true; + return 0; } void RTPAbortDescriptors::Destroy() { - if (!m_init) - return; + if (!m_init) + return; - close(m_descriptors[0]); - close(m_descriptors[1]); - m_descriptors[0] = RTPSOCKERR; - m_descriptors[1] = RTPSOCKERR; + close(m_descriptors[0]); + close(m_descriptors[1]); + m_descriptors[0] = RTPSOCKERR; + m_descriptors[1] = RTPSOCKERR; - m_init = false; + m_init = false; } int RTPAbortDescriptors::SendAbortSignal() { - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; - if (write(m_descriptors[1],"*",1)) - { - // To get rid of __wur related compiler warnings - } + if (write(m_descriptors[1], "*", 1)) + { + // To get rid of __wur related compiler warnings + } - return 0; + return 0; } int RTPAbortDescriptors::ReadSignallingByte() { - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; - unsigned char buf[1]; + unsigned char buf[1]; - if (read(m_descriptors[0],buf,1)) - { - // To get rid of __wur related compiler warnings - } - return 0; + if (read(m_descriptors[0], buf, 1)) + { + // To get rid of __wur related compiler warnings + } + return 0; } #endif // RTP_SOCKETTYPE_WINSOCK @@ -228,31 +228,31 @@ int RTPAbortDescriptors::ReadSignallingByte() // Keep calling 'ReadSignallingByte' until there's no byte left int RTPAbortDescriptors::ClearAbortSignal() { - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; + if (!m_init) + return ERR_RTP_ABORTDESC_NOTINIT; - bool done = false; - while (!done) - { - int8_t isset = 0; + bool done = false; + while (!done) + { + int8_t isset = 0; - // Not used: struct timeval tv = { 0, 0 }; + // Not used: struct timeval tv = { 0, 0 }; - int status = RTPSelect(&m_descriptors[0], &isset, 1, RTPTime(0)); - if (status < 0) - return status; + int status = RTPSelect(&m_descriptors[0], &isset, 1, RTPTime(0)); + if (status < 0) + return status; - if (!isset) - done = true; - else - { - int status = ReadSignallingByte(); - if (status < 0) - return status; - } - } + if (!isset) + done = true; + else + { + int status = ReadSignallingByte(); + if (status < 0) + return status; + } + } - return 0; + return 0; } } // end namespace diff --git a/qrtplib/rtpabortdescriptors.h b/qrtplib/rtpabortdescriptors.h index 52b9fb23a..333154832 100644 --- a/qrtplib/rtpabortdescriptors.h +++ b/qrtplib/rtpabortdescriptors.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpabortdescriptors.h @@ -64,38 +64,44 @@ namespace qrtplib class JRTPLIB_IMPORTEXPORT RTPAbortDescriptors { public: - RTPAbortDescriptors(); - ~RTPAbortDescriptors(); + RTPAbortDescriptors(); + ~RTPAbortDescriptors(); - /** Initializes this instance. */ - int Init(); + /** Initializes this instance. */ + int Init(); - /** Returns the socket descriptor that can be included in a call to - * 'select' (for example).*/ - SocketType GetAbortSocket() const { return m_descriptors[0]; } + /** Returns the socket descriptor that can be included in a call to + * 'select' (for example).*/ + SocketType GetAbortSocket() const + { + return m_descriptors[0]; + } - /** Returns a flag indicating if this instance was initialized. */ - bool IsInitialized() const { return m_init; } + /** Returns a flag indicating if this instance was initialized. */ + bool IsInitialized() const + { + return m_init; + } - /** De-initializes this instance. */ - void Destroy(); + /** De-initializes this instance. */ + void Destroy(); - /** Send a signal to the socket that's returned by RTPAbortDescriptors::GetAbortSocket, - * causing the 'select' call to detect that data is available, making the call - * end. */ - int SendAbortSignal(); + /** Send a signal to the socket that's returned by RTPAbortDescriptors::GetAbortSocket, + * causing the 'select' call to detect that data is available, making the call + * end. */ + int SendAbortSignal(); - /** For each RTPAbortDescriptors::SendAbortSignal function that's called, a call - * to this function can be made to clear the state again. */ - int ReadSignallingByte(); + /** For each RTPAbortDescriptors::SendAbortSignal function that's called, a call + * to this function can be made to clear the state again. */ + int ReadSignallingByte(); - /** Similar to ReadSignallingByte::ReadSignallingByte, this function clears the signalling - * state, but this also works independently from the amount of times that - * RTPAbortDescriptors::SendAbortSignal was called. */ - int ClearAbortSignal(); + /** Similar to ReadSignallingByte::ReadSignallingByte, this function clears the signalling + * state, but this also works independently from the amount of times that + * RTPAbortDescriptors::SendAbortSignal was called. */ + int ClearAbortSignal(); private: - SocketType m_descriptors[2]; - bool m_init; + SocketType m_descriptors[2]; + bool m_init; }; } // end namespace diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h index 9b6698a4e..421dacc14 100644 --- a/qrtplib/rtpaddress.h +++ b/qrtplib/rtpaddress.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpaddress.h @@ -50,45 +50,52 @@ class RTPMemoryManager; class JRTPLIB_IMPORTEXPORT RTPAddress { public: - /** Identifies the actual implementation being used. */ - enum AddressType - { - IPv4Address, /**< Used by the UDP over IPv4 transmitter. */ - IPv6Address, /**< Used by the UDP over IPv6 transmitter. */ - ByteAddress, /**< A very general type of address, consisting of a port number and a number of bytes representing the host address. */ - UserDefinedAddress, /**< Can be useful for a user-defined transmitter. */ - TCPAddress /**< Used by the TCP transmitter. */ - }; + /** Identifies the actual implementation being used. */ + enum AddressType + { + IPv4Address, /**< Used by the UDP over IPv4 transmitter. */ + IPv6Address, /**< Used by the UDP over IPv6 transmitter. */ + ByteAddress, /**< A very general type of address, consisting of a port number and a number of bytes representing the host address. */ + UserDefinedAddress, /**< Can be useful for a user-defined transmitter. */ + TCPAddress /**< Used by the TCP transmitter. */ + }; - /** Returns the type of address the actual implementation represents. */ - AddressType GetAddressType() const { return addresstype; } + /** Returns the type of address the actual implementation represents. */ + AddressType GetAddressType() const + { + return addresstype; + } - /** Creates a copy of the RTPAddress instance. - * Creates a copy of the RTPAddress instance. If \c mgr is not NULL, the - * corresponding memory manager will be used to allocate the memory for the address - * copy. - */ - virtual RTPAddress *CreateCopy(RTPMemoryManager *mgr) const = 0; + /** Creates a copy of the RTPAddress instance. + * Creates a copy of the RTPAddress instance. If \c mgr is not NULL, the + * corresponding memory manager will be used to allocate the memory for the address + * copy. + */ + virtual RTPAddress *CreateCopy() const = 0; - /** Checks if the address \c addr is the same address as the one this instance represents. - * Checks if the address \c addr is the same address as the one this instance represents. - * Implementations must be able to handle a NULL argument. - */ - virtual bool IsSameAddress(const RTPAddress *addr) const = 0; + /** Checks if the address \c addr is the same address as the one this instance represents. + * Checks if the address \c addr is the same address as the one this instance represents. + * Implementations must be able to handle a NULL argument. + */ + virtual bool IsSameAddress(const RTPAddress *addr) const = 0; - /** Checks if the address \c addr represents the same host as this instance. - * Checks if the address \c addr represents the same host as this instance. Implementations - * must be able to handle a NULL argument. - */ - virtual bool IsFromSameHost(const RTPAddress *addr) const = 0; + /** Checks if the address \c addr represents the same host as this instance. + * Checks if the address \c addr represents the same host as this instance. Implementations + * must be able to handle a NULL argument. + */ + virtual bool IsFromSameHost(const RTPAddress *addr) const = 0; - - virtual ~RTPAddress() { } + virtual ~RTPAddress() + { + } protected: - // only allow subclasses to be created - RTPAddress(const AddressType t) : addresstype(t) { } + // only allow subclasses to be created + RTPAddress(const AddressType t) : + addresstype(t) + { + } private: - const AddressType addresstype; + const AddressType addresstype; }; } // end namespace diff --git a/qrtplib/rtpbyteaddress.cpp b/qrtplib/rtpbyteaddress.cpp index ac3a88cb7..12819017a 100644 --- a/qrtplib/rtpbyteaddress.cpp +++ b/qrtplib/rtpbyteaddress.cpp @@ -1,81 +1,79 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpbyteaddress.h" -#include "rtpmemorymanager.h" namespace qrtplib { bool RTPByteAddress::IsSameAddress(const RTPAddress *addr) const { - if (addr == 0) - return false; - if (addr->GetAddressType() != ByteAddress) - return false; + if (addr == 0) + return false; + if (addr->GetAddressType() != ByteAddress) + return false; - const RTPByteAddress *addr2 = (const RTPByteAddress *)addr; + const RTPByteAddress *addr2 = (const RTPByteAddress *) addr; - if (addr2->addresslength != addresslength) - return false; - if (addresslength == 0 || (memcmp(hostaddress, addr2->hostaddress, addresslength) == 0)) - { - if (port == addr2->port) - return true; - } - return false; + if (addr2->addresslength != addresslength) + return false; + if (addresslength == 0 || (memcmp(hostaddress, addr2->hostaddress, addresslength) == 0)) + { + if (port == addr2->port) + return true; + } + return false; } bool RTPByteAddress::IsFromSameHost(const RTPAddress *addr) const { - if (addr == 0) - return false; - if (addr->GetAddressType() != ByteAddress) - return false; + if (addr == 0) + return false; + if (addr->GetAddressType() != ByteAddress) + return false; - const RTPByteAddress *addr2 = (const RTPByteAddress *)addr; + const RTPByteAddress *addr2 = (const RTPByteAddress *) addr; - if (addr2->addresslength != addresslength) - return false; - if (addresslength == 0 || (memcmp(hostaddress, addr2->hostaddress, addresslength) == 0)) - return true; - return false; + if (addr2->addresslength != addresslength) + return false; + if (addresslength == 0 || (memcmp(hostaddress, addr2->hostaddress, addresslength) == 0)) + return true; + return false; } -RTPAddress *RTPByteAddress::CreateCopy(RTPMemoryManager *mgr) const +RTPAddress *RTPByteAddress::CreateCopy() const { - JRTPLIB_UNUSED(mgr); // possibly unused - RTPByteAddress *a = new RTPByteAddress(hostaddress, addresslength, port); - return a; + RTPByteAddress *a = new RTPByteAddress(hostaddress, addresslength, port); + return a; } } // end namespace diff --git a/qrtplib/rtpbyteaddress.h b/qrtplib/rtpbyteaddress.h index b5f5711ad..60f347e86 100644 --- a/qrtplib/rtpbyteaddress.h +++ b/qrtplib/rtpbyteaddress.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpbyteaddress.h @@ -48,41 +48,65 @@ namespace qrtplib { -class RTPMemoryManager; - /** A very general kind of address consisting of a port number and a number of bytes describing the host address. * A very general kind of address, consisting of a port number and a number of bytes describing the host address. */ -class JRTPLIB_IMPORTEXPORT RTPByteAddress : public RTPAddress +class JRTPLIB_IMPORTEXPORT RTPByteAddress: public RTPAddress { public: - /** Creates an instance of the class using \c addrlen bytes of \c hostaddress as host identification, - * and using \c port as the port number. */ - RTPByteAddress(const uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH], size_t addrlen, uint16_t port = 0) : RTPAddress(ByteAddress) { if (addrlen > RTPBYTEADDRESS_MAXLENGTH) addrlen = RTPBYTEADDRESS_MAXLENGTH; memcpy(RTPByteAddress::hostaddress, hostaddress, addrlen); RTPByteAddress::addresslength = addrlen; RTPByteAddress::port = port; } + /** Creates an instance of the class using \c addrlen bytes of \c hostaddress as host identification, + * and using \c port as the port number. */ + RTPByteAddress(const uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH], size_t addrlen, uint16_t port = 0) : + RTPAddress(ByteAddress) + { + if (addrlen > RTPBYTEADDRESS_MAXLENGTH) + addrlen = RTPBYTEADDRESS_MAXLENGTH; + memcpy(RTPByteAddress::hostaddress, hostaddress, addrlen); + RTPByteAddress::addresslength = addrlen; + RTPByteAddress::port = port; + } - /** Sets the host address to the first \c addrlen bytes of \c hostaddress. */ - void SetHostAddress(const uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH], size_t addrlen) { if (addrlen > RTPBYTEADDRESS_MAXLENGTH) addrlen = RTPBYTEADDRESS_MAXLENGTH; memcpy(RTPByteAddress::hostaddress, hostaddress, addrlen); RTPByteAddress::addresslength = addrlen; } + /** Sets the host address to the first \c addrlen bytes of \c hostaddress. */ + void SetHostAddress(const uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH], size_t addrlen) + { + if (addrlen > RTPBYTEADDRESS_MAXLENGTH) + addrlen = RTPBYTEADDRESS_MAXLENGTH; + memcpy(RTPByteAddress::hostaddress, hostaddress, addrlen); + RTPByteAddress::addresslength = addrlen; + } - /** Sets the port number to \c port. */ - void SetPort(uint16_t port) { RTPByteAddress::port = port; } + /** Sets the port number to \c port. */ + void SetPort(uint16_t port) + { + RTPByteAddress::port = port; + } - /** Returns a pointer to the stored host address. */ - const uint8_t *GetHostAddress() const { return hostaddress; } + /** Returns a pointer to the stored host address. */ + const uint8_t *GetHostAddress() const + { + return hostaddress; + } - /** Returns the length in bytes of the stored host address. */ - size_t GetHostAddressLength() const { return addresslength; } + /** Returns the length in bytes of the stored host address. */ + size_t GetHostAddressLength() const + { + return addresslength; + } - /** Returns the port number stored in this instance. */ - uint16_t GetPort() const { return port; } + /** Returns the port number stored in this instance. */ + uint16_t GetPort() const + { + return port; + } - RTPAddress *CreateCopy(RTPMemoryManager *mgr) const; - bool IsSameAddress(const RTPAddress *addr) const; - bool IsFromSameHost(const RTPAddress *addr) const; + RTPAddress *CreateCopy() const; + bool IsSameAddress(const RTPAddress *addr) const; + bool IsFromSameHost(const RTPAddress *addr) const; private: - uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH]; - size_t addresslength; - uint16_t port; + uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH]; + size_t addresslength; + uint16_t port; }; } // end namespace diff --git a/qrtplib/rtpcollisionlist.cpp b/qrtplib/rtpcollisionlist.cpp index 4aa156032..10fa99436 100644 --- a/qrtplib/rtpcollisionlist.cpp +++ b/qrtplib/rtpcollisionlist.cpp @@ -1,112 +1,111 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpcollisionlist.h" #include "rtperrors.h" -#include "rtpmemorymanager.h" namespace qrtplib { -RTPCollisionList::RTPCollisionList(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) +RTPCollisionList::RTPCollisionList() { - timeinit.Dummy(); + timeinit.Dummy(); } void RTPCollisionList::Clear() { - std::list::iterator it; + std::list::iterator it; - for (it = addresslist.begin() ; it != addresslist.end() ; it++) - RTPDelete((*it).addr,GetMemoryManager()); - addresslist.clear(); + for (it = addresslist.begin(); it != addresslist.end(); it++) + delete (*it).addr; + addresslist.clear(); } -int RTPCollisionList::UpdateAddress(const RTPAddress *addr,const RTPTime &receivetime,bool *created) +int RTPCollisionList::UpdateAddress(const RTPAddress *addr, const RTPTime &receivetime, bool *created) { - if (addr == 0) - return ERR_RTP_COLLISIONLIST_BADADDRESS; + if (addr == 0) + return ERR_RTP_COLLISIONLIST_BADADDRESS; - std::list::iterator it; + std::list::iterator it; - for (it = addresslist.begin() ; it != addresslist.end() ; it++) - { - if (((*it).addr)->IsSameAddress(addr)) - { - (*it).recvtime = receivetime; - *created = false; - return 0; - } - } + for (it = addresslist.begin(); it != addresslist.end(); it++) + { + if (((*it).addr)->IsSameAddress(addr)) + { + (*it).recvtime = receivetime; + *created = false; + return 0; + } + } - RTPAddress *newaddr = addr->CreateCopy(GetMemoryManager()); - if (newaddr == 0) - return ERR_RTP_OUTOFMEM; + RTPAddress *newaddr = addr->CreateCopy(); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; - addresslist.push_back(AddressAndTime(newaddr,receivetime)); - *created = true; - return 0; + addresslist.push_back(AddressAndTime(newaddr, receivetime)); + *created = true; + return 0; } bool RTPCollisionList::HasAddress(const RTPAddress *addr) const { - std::list::const_iterator it; + std::list::const_iterator it; - for (it = addresslist.begin() ; it != addresslist.end() ; it++) - { - if (((*it).addr)->IsSameAddress(addr)) - return true; - } + for (it = addresslist.begin(); it != addresslist.end(); it++) + { + if (((*it).addr)->IsSameAddress(addr)) + return true; + } - return false; + return false; } -void RTPCollisionList::Timeout(const RTPTime ¤ttime,const RTPTime &timeoutdelay) +void RTPCollisionList::Timeout(const RTPTime ¤ttime, const RTPTime &timeoutdelay) { - std::list::iterator it; - RTPTime checktime = currenttime; - checktime -= timeoutdelay; + std::list::iterator it; + RTPTime checktime = currenttime; + checktime -= timeoutdelay; - it = addresslist.begin(); - while(it != addresslist.end()) - { - if ((*it).recvtime < checktime) // timeout - { - RTPDelete((*it).addr,GetMemoryManager()); - it = addresslist.erase(it); - } - else - it++; - } + it = addresslist.begin(); + while (it != addresslist.end()) + { + if ((*it).recvtime < checktime) // timeout + { + delete (*it).addr; + it = addresslist.erase(it); + } + else + it++; + } } } // end namespace diff --git a/qrtplib/rtpcollisionlist.h b/qrtplib/rtpcollisionlist.h index b0ff4ea4f..2c59434f1 100644 --- a/qrtplib/rtpcollisionlist.h +++ b/qrtplib/rtpcollisionlist.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpcollisionlist.h @@ -41,7 +41,6 @@ #include "rtpconfig.h" #include "rtpaddress.h" #include "rtptimeutilities.h" -#include "rtpmemoryobject.h" #include namespace qrtplib @@ -50,41 +49,47 @@ namespace qrtplib class RTPAddress; /** This class represents a list of addresses from which SSRC collisions were detected. */ -class JRTPLIB_IMPORTEXPORT RTPCollisionList : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTPCollisionList { public: - /** Constructs an instance, optionally installing a memory manager. */ - RTPCollisionList(RTPMemoryManager *mgr = 0); - ~RTPCollisionList() { Clear(); } + /** Constructs an instance, optionally installing a memory manager. */ + RTPCollisionList(); + ~RTPCollisionList() + { + Clear(); + } - /** Clears the list of addresses. */ - void Clear(); + /** Clears the list of addresses. */ + void Clear(); - /** Updates the entry for address \c addr to indicate that a collision was detected at time \c receivetime. - * Updates the entry for address \c addr to indicate that a collision was detected at time \c receivetime. - * If the entry did not exist yet, the flag \c created is set to \c true, otherwise it is set to \c false. - */ - int UpdateAddress(const RTPAddress *addr,const RTPTime &receivetime,bool *created); + /** Updates the entry for address \c addr to indicate that a collision was detected at time \c receivetime. + * Updates the entry for address \c addr to indicate that a collision was detected at time \c receivetime. + * If the entry did not exist yet, the flag \c created is set to \c true, otherwise it is set to \c false. + */ + int UpdateAddress(const RTPAddress *addr, const RTPTime &receivetime, bool *created); - /** Returns \c true} if the address \c addr appears in the list. */ - bool HasAddress(const RTPAddress *addr) const; + /** Returns \c true} if the address \c addr appears in the list. */ + bool HasAddress(const RTPAddress *addr) const; - /** Assuming that the current time is given by \c currenttime, this function times out entries which - * haven't been updated in the previous time interval specified by \c timeoutdelay. - */ - void Timeout(const RTPTime ¤ttime,const RTPTime &timeoutdelay); + /** Assuming that the current time is given by \c currenttime, this function times out entries which + * haven't been updated in the previous time interval specified by \c timeoutdelay. + */ + void Timeout(const RTPTime ¤ttime, const RTPTime &timeoutdelay); private: - class AddressAndTime - { - public: - AddressAndTime(RTPAddress *a,const RTPTime &t) : addr(a),recvtime(t) { } + class AddressAndTime + { + public: + AddressAndTime(RTPAddress *a, const RTPTime &t) : + addr(a), recvtime(t) + { + } - RTPAddress *addr; - RTPTime recvtime; - }; + RTPAddress *addr; + RTPTime recvtime; + }; - std::list addresslist; + std::list addresslist; }; } // end namespace diff --git a/qrtplib/rtpconfig.h b/qrtplib/rtpconfig.h index 67e602b2b..ab3b8b368 100644 --- a/qrtplib/rtpconfig.h +++ b/qrtplib/rtpconfig.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #ifndef RTPCONFIG_UNIX_H @@ -44,9 +44,9 @@ #define JRTPLIB_IMPORT #define JRTPLIB_EXPORT #ifdef JRTPLIB_COMPILING - #define JRTPLIB_IMPORTEXPORT JRTPLIB_EXPORT +#define JRTPLIB_IMPORTEXPORT JRTPLIB_EXPORT #else - #define JRTPLIB_IMPORTEXPORT JRTPLIB_IMPORT +#define JRTPLIB_IMPORTEXPORT JRTPLIB_IMPORT #endif // JRTPLIB_COMPILING // Don't have diff --git a/qrtplib/rtpdefines.h b/qrtplib/rtpdefines.h index a18c05688..6406fa3e3 100644 --- a/qrtplib/rtpdefines.h +++ b/qrtplib/rtpdefines.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #ifndef RTPDEFINES_H diff --git a/qrtplib/rtpendian.h b/qrtplib/rtpendian.h index 9c03534d4..6a009da79 100644 --- a/qrtplib/rtpendian.h +++ b/qrtplib/rtpendian.h @@ -24,7 +24,8 @@ public: } template - T qToHost(const T& x) const { + T qToHost(const T& x) const + { return m_isLittleEndian ? qToLittleEndian(x) : qToBigEndian(x); } @@ -34,6 +35,4 @@ private: } - - #endif /* QRTPLIB_RTPENDIAN_H_ */ diff --git a/qrtplib/rtperrors.cpp b/qrtplib/rtperrors.cpp index 54c989aa9..1baa7c872 100644 --- a/qrtplib/rtperrors.cpp +++ b/qrtplib/rtperrors.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtperrors.h" #include "rtpdefines.h" @@ -40,232 +40,231 @@ namespace qrtplib struct RTPErrorInfo { - int code; - const char *description; + int code; + const char *description; }; -static RTPErrorInfo ErrorDescriptions[]= +static RTPErrorInfo ErrorDescriptions[] = { - { ERR_RTP_OUTOFMEM,"Out of memory" }, - { ERR_RTP_NOTHREADSUPPORT, "No JThread support was compiled in"}, - { ERR_RTP_COLLISIONLIST_BADADDRESS, "Passed invalid address (null) to collision list"}, - { ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS, "Element already exists in hash table"}, - { ERR_RTP_HASHTABLE_ELEMENTNOTFOUND, "Element not found in hash table"}, - { ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index"}, - { ERR_RTP_HASHTABLE_NOCURRENTELEMENT, "No current element selected in hash table"}, - { ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index"}, - { ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS, "Key value already exists in key hash table"}, - { ERR_RTP_KEYHASHTABLE_KEYNOTFOUND, "Key value not found in key hash table"}, - { ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT, "No current element selected in key hash table"}, - { ERR_RTP_PACKBUILD_ALREADYINIT, "RTP packet builder is already initialized"}, - { ERR_RTP_PACKBUILD_CSRCALREADYINLIST, "The specified CSRC is already in the RTP packet builder's CSRC list"}, - { ERR_RTP_PACKBUILD_CSRCLISTFULL, "The RTP packet builder's CSRC list already contains 15 entries"}, - { ERR_RTP_PACKBUILD_CSRCNOTINLIST, "The specified CSRC was not found in the RTP packet builder's CSRC list"}, - { ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET, "The RTP packet builder's default mark flag is not set"}, - { ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET, "The RTP packet builder's default payload type is not set"}, - { ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET, "The RTP packet builder's default timestamp increment is not set"}, - { ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE, "The specified maximum packet size for the RTP packet builder is invalid"}, - { ERR_RTP_PACKBUILD_NOTINIT, "The RTP packet builder is not initialized"}, - { ERR_RTP_PACKET_BADPAYLOADTYPE, "Invalid payload type"}, - { ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE, "Tried to create an RTP packet which whould exceed the specified maximum packet size"}, - { ERR_RTP_PACKET_EXTERNALBUFFERNULL, "Illegal value (null) passed as external buffer for the RTP packet"}, - { ERR_RTP_PACKET_ILLEGALBUFFERSIZE, "Illegal buffer size specified for the RTP packet"}, - { ERR_RTP_PACKET_INVALIDPACKET, "Invalid RTP packet format"}, - { ERR_RTP_PACKET_TOOMANYCSRCS, "More than 15 CSRCs specified for the RTP packet"}, - { ERR_RTP_POLLTHREAD_ALREADYRUNNING, "Poll thread is already running"}, - { ERR_RTP_POLLTHREAD_CANTINITMUTEX, "Can't initialize a mutex for the poll thread"}, - { ERR_RTP_POLLTHREAD_CANTSTARTTHREAD, "Can't start the poll thread"}, - { ERR_RTP_RTCPCOMPOUND_INVALIDPACKET, "Invalid RTCP compound packet format"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING, "Already building this RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT, "This RTCP compound packet is already built"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT, "There's already a SR or RR in this RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG, "The specified APP data length for the RTCP compound packet is too big"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL, "The specified buffer size for the RTCP comound packet is too small"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH, "The APP data length must be a multiple of four"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE, "The APP packet subtype must be smaller than 32"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE, "Invalid SDES item type specified for the RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL, "The specified maximum packet size for the RTCP compound packet is too small"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE, "Tried to add an SDES item to the RTCP compound packet when no SSRC was present"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT, "An RTCP compound packet must contain a SR or RR"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING, "The RTCP compound packet builder is not initialized"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT, "Adding this data would exceed the specified maximum RTCP compound packet size"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED, "Tried to add a report block to the RTCP compound packet when no SR or RR was started"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS, "Only 31 SSRCs will fit into a BYE packet for the RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG, "The total data for the SDES PRIV item exceeds the maximum size (255 bytes) of an SDES item"}, - { ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT, "The RTCP packet builder is already initialized"}, - { ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE, "The specified maximum packet size for the RTCP packet builder is too small"}, - { ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT, "Speficied an illegal timestamp unit for the the RTCP packet builder"}, - { ERR_RTP_RTCPPACKETBUILDER_NOTINIT, "The RTCP packet builder was not initialized"}, - { ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON, "The RTCP compound packet filled sooner than expected"}, - { ERR_RTP_SCHEDPARAMS_BADFRACTION, "Illegal sender bandwidth fraction specified"}, - { ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL, "The minimum RTCP interval specified for the scheduler is too small"}, - { ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH, "Invalid RTCP bandwidth specified for the RTCP scheduler"}, - { ERR_RTP_SDES_LENGTHTOOBIG, "Specified size for the SDES item exceeds 255 bytes"}, - { ERR_RTP_SDES_PREFIXNOTFOUND, "The specified SDES PRIV prefix was not found"}, - { ERR_RTP_SESSION_ALREADYCREATED, "The session is already created"}, - { ERR_RTP_SESSION_CANTGETLOGINNAME, "Can't retrieve login name"}, - { ERR_RTP_SESSION_CANTINITMUTEX, "A mutex for the RTP session couldn't be initialized"}, - { ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL, "The maximum packet size specified for the RTP session is too small"}, - { ERR_RTP_SESSION_NOTCREATED, "The RTP session was not created"}, - { ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL, "The requested transmission protocol for the RTP session is not supported"}, - { ERR_RTP_SESSION_USINGPOLLTHREAD, "This function is not available when using the RTP poll thread feature"}, - { ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL, "A user-defined transmitter was requested but the supplied transmitter component is NULL"}, - { ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC, "Only one source can be marked as own SSRC in the source table"}, - { ERR_RTP_SOURCES_DONTHAVEOWNSSRC, "No source was marked as own SSRC in the source table"}, - { ERR_RTP_SOURCES_ILLEGALSDESTYPE, "Illegal SDES type specified for processing into the source table"}, - { ERR_RTP_SOURCES_SSRCEXISTS, "Can't create own SSRC because this SSRC identifier is already in the source table"}, - { ERR_RTP_UDPV4TRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_UDPV4TRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_UDPV4TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data"}, - { ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed"}, - { ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed"}, - { ERR_RTP_UDPV4TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket"}, - { ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_UDPV4TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_UDPV4TRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_UDPV4TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_UDPV4TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_UDPV4TRANS_NOTWAITING, "The transmitter is not waiting for incoming data"}, - { ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_UDPV6TRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_UDPV6TRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_UDPV6TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data"}, - { ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed"}, - { ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed"}, - { ERR_RTP_UDPV6TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket"}, - { ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_UDPV6TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_UDPV6TRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_UDPV6TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_UDPV6TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_UDPV6TRANS_NOTWAITING, "The transmitter is not waiting for incoming data"}, - { ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL,"The hostname is larger than the specified buffer size"}, - { ERR_RTP_SDES_MAXPRIVITEMS,"The maximum number of SDES private item prefixes was reached"}, - { ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE,"An invalid probation type was specified"}, - { ERR_RTP_FAKETRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_FAKETRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_FAKETRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_FAKETRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_FAKETRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_FAKETRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_FAKETRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_FAKETRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_FAKETRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED, "The WaitForIncomingData is not implemented in the Gst transmitter"}, - { ERR_RTP_RTPRANDOMURANDOM_CANTOPEN, "Unable to open /dev/urandom for reading"}, - { ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN, "The device /dev/urandom was already opened"}, - { ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED, "The rand_s call is not supported on this platform"}, - { ERR_RTP_EXTERNALTRANS_ALREADYCREATED, "The external transmission component was already created"}, - { ERR_RTP_EXTERNALTRANS_ALREADYINIT, "The external transmission component was already initialized"}, - { ERR_RTP_EXTERNALTRANS_ALREADYWAITING, "The external transmission component is already waiting for incoming data"}, - { ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE, "The external transmission component only supports accepting all incoming packets"}, - { ERR_RTP_EXTERNALTRANS_CANTINITMUTEX, "The external transmitter was unable to initialize a required mutex"}, - { ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS, "Only parameters of type RTPExternalTransmissionParams can be passed to the external transmission component"}, - { ERR_RTP_EXTERNALTRANS_NOACCEPTLIST, "The external transmitter does not have an accept list"}, - { ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED, "The external transmitter does not have a destination list"}, - { ERR_RTP_EXTERNALTRANS_NOIGNORELIST, "The external transmitter does not have an ignore list"}, - { ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT, "The external transmitter does not support the multicast functions"}, - { ERR_RTP_EXTERNALTRANS_NOSENDER, "No sender has been set for this external transmitter"}, - { ERR_RTP_EXTERNALTRANS_NOTCREATED, "The external transmitter has not been created yet"}, - { ERR_RTP_EXTERNALTRANS_NOTINIT, "The external transmitter has not been initialized yet"}, - { ERR_RTP_EXTERNALTRANS_NOTWAITING, "The external transmitter is not currently waiting for incoming data"}, - { ERR_RTP_EXTERNALTRANS_SENDERROR, "The external transmitter was unable to actually send the data"}, - { ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG, "The specified data size exceeds the maximum amount that has been set"}, - { ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT, "Unable to obtain the existing socket info using 'getsockname'"}, - { ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET, "The existing socket specified does not appear to be an IPv4 socket"}, - { ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET, "The existing socket that was specified does not have its port set yet"}, - { ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE, "Can't get the socket type of the specified existing socket"}, - { ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE, "The specified existing socket is not an UDP socket"}, - { ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET, "Can't get a valid socket when trying to choose a port automatically"}, - { ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET, "Can't seem to get RTP/RTCP ports automatically, too many attempts"}, - { ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED, "Flag to change data was requested, but OnChangeRTPOrRTCPData was not reimplemented"}, - { ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED, "The initialization function was already called"}, - { ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT, "Unable to initialize libsrtp context"}, - { ERR_RTP_SECURESESSION_CANTINITMUTEX, "Unable to initialize a mutex" }, - { ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED, "The libsrtp context initilization function must be called before it can be used"}, - { ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT, "There's not enough RTP or RTCP data to encrypt"}, - { ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA, "Unable to encrypt RTP data"}, - { ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA, "Unable to encrypt RTCP data"}, - { ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT, "There's not enough RTP or RTCP data to decrypt"}, - { ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA, "Unable to decrypt RTP data"}, - { ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA, "Unable to decrypt RTCP data"}, - { ERR_RTP_ABORTDESC_ALREADYINIT, "The RTPAbortDescriptors instance is already initialized" }, - { ERR_RTP_ABORTDESC_NOTINIT, "The RTPAbortDescriptors instance is not yet initialized" }, - { ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS, "Unable to create two connected TCP sockets for the abort descriptors" }, - { ERR_RTP_ABORTDESC_CANTCREATEPIPE, "Unable to create a pipe for the abort descriptors" }, - { ERR_RTP_SESSION_THREADSAFETYCONFLICT, "For the background poll thread to be used, thread safety must also be set" }, - { ERR_RTP_SELECT_ERRORINSELECT, "Error in the call to 'select'" }, - { ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE, "A socket descriptor value is too large for a call to 'select' (exceeds FD_SETSIZE)" }, - { ERR_RTP_SELECT_ERRORINPOLL, "Error in the call to 'poll' or 'WSAPoll'" }, - { ERR_RTP_TCPTRANS_NOTINIT, "The TCP transmitter is not yet initialized" }, - { ERR_RTP_TCPTRANS_ALREADYINIT, "The TCP transmitter is already initialized" }, - { ERR_RTP_TCPTRANS_NOTCREATED, "The TCP transmitter is not yet created" }, - { ERR_RTP_TCPTRANS_ALREADYCREATED, "The TCP transmitter is already created" }, - { ERR_RTP_TCPTRANS_ILLEGALPARAMETERS, "The parameters for the TCP transmitter are invalid" }, - { ERR_RTP_TCPTRANS_CANTINITMUTEX, "Unable to initialize a mutex during the initialization of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_ALREADYWAITING, "The TCP transmitter is already waiting for data" }, - { ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE, "The address specified is not a valid address for the TCP transmitter" }, - { ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED, "No socket was specified in the address used for the TCP transmitter" }, - { ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT, "The TCP transmitter does not support multicasting" }, - { ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED, "The TCP transmitter does not support receive modes other than 'accept all'" }, - { ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size for the TCP transmitter is limited to 64KB" }, - { ERR_RTP_TCPTRANS_NOTWAITING, "The TCP transmitter is not waiting for data" }, - { ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS, "The specified destination address (socket) was already added to the destination list of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS, "The specified destination address (socket) was not found in the list of destinations of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_ERRORINSEND, "An error occurred in the TCP transmitter while sending a packet" }, - { ERR_RTP_TCPTRANS_ERRORINRECV, "An error occurred in the TCP transmitter while receiving a packet" }, - { 0,0 } -}; +{ ERR_RTP_OUTOFMEM, "Out of memory" }, +{ ERR_RTP_NOTHREADSUPPORT, "No JThread support was compiled in" }, +{ ERR_RTP_COLLISIONLIST_BADADDRESS, "Passed invalid address (null) to collision list" }, +{ ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS, "Element already exists in hash table" }, +{ ERR_RTP_HASHTABLE_ELEMENTNOTFOUND, "Element not found in hash table" }, +{ ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index" }, +{ ERR_RTP_HASHTABLE_NOCURRENTELEMENT, "No current element selected in hash table" }, +{ ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index" }, +{ ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS, "Key value already exists in key hash table" }, +{ ERR_RTP_KEYHASHTABLE_KEYNOTFOUND, "Key value not found in key hash table" }, +{ ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT, "No current element selected in key hash table" }, +{ ERR_RTP_PACKBUILD_ALREADYINIT, "RTP packet builder is already initialized" }, +{ ERR_RTP_PACKBUILD_CSRCALREADYINLIST, "The specified CSRC is already in the RTP packet builder's CSRC list" }, +{ ERR_RTP_PACKBUILD_CSRCLISTFULL, "The RTP packet builder's CSRC list already contains 15 entries" }, +{ ERR_RTP_PACKBUILD_CSRCNOTINLIST, "The specified CSRC was not found in the RTP packet builder's CSRC list" }, +{ ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET, "The RTP packet builder's default mark flag is not set" }, +{ ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET, "The RTP packet builder's default payload type is not set" }, +{ ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET, "The RTP packet builder's default timestamp increment is not set" }, +{ ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE, "The specified maximum packet size for the RTP packet builder is invalid" }, +{ ERR_RTP_PACKBUILD_NOTINIT, "The RTP packet builder is not initialized" }, +{ ERR_RTP_PACKET_BADPAYLOADTYPE, "Invalid payload type" }, +{ ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE, "Tried to create an RTP packet which whould exceed the specified maximum packet size" }, +{ ERR_RTP_PACKET_EXTERNALBUFFERNULL, "Illegal value (null) passed as external buffer for the RTP packet" }, +{ ERR_RTP_PACKET_ILLEGALBUFFERSIZE, "Illegal buffer size specified for the RTP packet" }, +{ ERR_RTP_PACKET_INVALIDPACKET, "Invalid RTP packet format" }, +{ ERR_RTP_PACKET_TOOMANYCSRCS, "More than 15 CSRCs specified for the RTP packet" }, +{ ERR_RTP_POLLTHREAD_ALREADYRUNNING, "Poll thread is already running" }, +{ ERR_RTP_POLLTHREAD_CANTINITMUTEX, "Can't initialize a mutex for the poll thread" }, +{ ERR_RTP_POLLTHREAD_CANTSTARTTHREAD, "Can't start the poll thread" }, +{ ERR_RTP_RTCPCOMPOUND_INVALIDPACKET, "Invalid RTCP compound packet format" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING, "Already building this RTCP compound packet" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT, "This RTCP compound packet is already built" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT, "There's already a SR or RR in this RTCP compound packet" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG, "The specified APP data length for the RTCP compound packet is too big" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL, "The specified buffer size for the RTCP comound packet is too small" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH, "The APP data length must be a multiple of four" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE, "The APP packet subtype must be smaller than 32" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE, "Invalid SDES item type specified for the RTCP compound packet" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL, "The specified maximum packet size for the RTCP compound packet is too small" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE, "Tried to add an SDES item to the RTCP compound packet when no SSRC was present" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT, "An RTCP compound packet must contain a SR or RR" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING, "The RTCP compound packet builder is not initialized" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT, "Adding this data would exceed the specified maximum RTCP compound packet size" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED, "Tried to add a report block to the RTCP compound packet when no SR or RR was started" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS, "Only 31 SSRCs will fit into a BYE packet for the RTCP compound packet" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG, "The total data for the SDES PRIV item exceeds the maximum size (255 bytes) of an SDES item" }, +{ ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT, "The RTCP packet builder is already initialized" }, +{ ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE, "The specified maximum packet size for the RTCP packet builder is too small" }, +{ ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT, "Speficied an illegal timestamp unit for the the RTCP packet builder" }, +{ ERR_RTP_RTCPPACKETBUILDER_NOTINIT, "The RTCP packet builder was not initialized" }, +{ ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON, "The RTCP compound packet filled sooner than expected" }, +{ ERR_RTP_SCHEDPARAMS_BADFRACTION, "Illegal sender bandwidth fraction specified" }, +{ ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL, "The minimum RTCP interval specified for the scheduler is too small" }, +{ ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH, "Invalid RTCP bandwidth specified for the RTCP scheduler" }, +{ ERR_RTP_SDES_LENGTHTOOBIG, "Specified size for the SDES item exceeds 255 bytes" }, +{ ERR_RTP_SDES_PREFIXNOTFOUND, "The specified SDES PRIV prefix was not found" }, +{ ERR_RTP_SESSION_ALREADYCREATED, "The session is already created" }, +{ ERR_RTP_SESSION_CANTGETLOGINNAME, "Can't retrieve login name" }, +{ ERR_RTP_SESSION_CANTINITMUTEX, "A mutex for the RTP session couldn't be initialized" }, +{ ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL, "The maximum packet size specified for the RTP session is too small" }, +{ ERR_RTP_SESSION_NOTCREATED, "The RTP session was not created" }, +{ ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL, "The requested transmission protocol for the RTP session is not supported" }, +{ ERR_RTP_SESSION_USINGPOLLTHREAD, "This function is not available when using the RTP poll thread feature" }, +{ ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL, "A user-defined transmitter was requested but the supplied transmitter component is NULL" }, +{ ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC, "Only one source can be marked as own SSRC in the source table" }, +{ ERR_RTP_SOURCES_DONTHAVEOWNSSRC, "No source was marked as own SSRC in the source table" }, +{ ERR_RTP_SOURCES_ILLEGALSDESTYPE, "Illegal SDES type specified for processing into the source table" }, +{ ERR_RTP_SOURCES_SSRCEXISTS, "Can't create own SSRC because this SSRC identifier is already in the source table" }, +{ ERR_RTP_UDPV4TRANS_ALREADYCREATED, "The transmitter was already created" }, +{ ERR_RTP_UDPV4TRANS_ALREADYINIT, "The transmitter was already initialize" }, +{ ERR_RTP_UDPV4TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data" }, +{ ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed" }, +{ ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed" }, +{ ERR_RTP_UDPV4TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter" }, +{ ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket" }, +{ ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group" }, +{ ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode" }, +{ ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter" }, +{ ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter" }, +{ ERR_RTP_UDPV4TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty" }, +{ ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT, "Multicast support is not available" }, +{ ERR_RTP_UDPV4TRANS_NOSUCHENTRY, "Specified entry could not be found" }, +{ ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address" }, +{ ERR_RTP_UDPV4TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called" }, +{ ERR_RTP_UDPV4TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called" }, +{ ERR_RTP_UDPV4TRANS_NOTWAITING, "The transmitter is not waiting for incoming data" }, +{ ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN, "The specified port base is not an even number" }, +{ ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter" }, +{ ERR_RTP_UDPV6TRANS_ALREADYCREATED, "The transmitter was already created" }, +{ ERR_RTP_UDPV6TRANS_ALREADYINIT, "The transmitter was already initialize" }, +{ ERR_RTP_UDPV6TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data" }, +{ ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed" }, +{ ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed" }, +{ ERR_RTP_UDPV6TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket" }, +{ ERR_RTP_UDPV6TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter" }, +{ ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket" }, +{ ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket" }, +{ ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket" }, +{ ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket" }, +{ ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group" }, +{ ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode" }, +{ ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter" }, +{ ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter" }, +{ ERR_RTP_UDPV6TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty" }, +{ ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT, "Multicast support is not available" }, +{ ERR_RTP_UDPV6TRANS_NOSUCHENTRY, "Specified entry could not be found" }, +{ ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address" }, +{ ERR_RTP_UDPV6TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called" }, +{ ERR_RTP_UDPV6TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called" }, +{ ERR_RTP_UDPV6TRANS_NOTWAITING, "The transmitter is not waiting for incoming data" }, +{ ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN, "The specified port base is not an even number" }, +{ ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter" }, +{ ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL, "The hostname is larger than the specified buffer size" }, +{ ERR_RTP_SDES_MAXPRIVITEMS, "The maximum number of SDES private item prefixes was reached" }, +{ ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE, "An invalid probation type was specified" }, +{ ERR_RTP_FAKETRANS_ALREADYCREATED, "The transmitter was already created" }, +{ ERR_RTP_FAKETRANS_ALREADYINIT, "The transmitter was already initialize" }, +{ ERR_RTP_FAKETRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter" }, +{ ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group" }, +{ ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode" }, +{ ERR_RTP_FAKETRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter" }, +{ ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter" }, +{ ERR_RTP_FAKETRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty" }, +{ ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT, "Multicast support is not available" }, +{ ERR_RTP_FAKETRANS_NOSUCHENTRY, "Specified entry could not be found" }, +{ ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address" }, +{ ERR_RTP_FAKETRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called" }, +{ ERR_RTP_FAKETRANS_NOTINIT, "The 'Init' call for this transmitter has not been called" }, +{ ERR_RTP_FAKETRANS_PORTBASENOTEVEN, "The specified port base is not an even number" }, +{ ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter" }, +{ ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED, "The WaitForIncomingData is not implemented in the Gst transmitter" }, +{ ERR_RTP_RTPRANDOMURANDOM_CANTOPEN, "Unable to open /dev/urandom for reading" }, +{ ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN, "The device /dev/urandom was already opened" }, +{ ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED, "The rand_s call is not supported on this platform" }, +{ ERR_RTP_EXTERNALTRANS_ALREADYCREATED, "The external transmission component was already created" }, +{ ERR_RTP_EXTERNALTRANS_ALREADYINIT, "The external transmission component was already initialized" }, +{ ERR_RTP_EXTERNALTRANS_ALREADYWAITING, "The external transmission component is already waiting for incoming data" }, +{ ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE, "The external transmission component only supports accepting all incoming packets" }, +{ ERR_RTP_EXTERNALTRANS_CANTINITMUTEX, "The external transmitter was unable to initialize a required mutex" }, +{ ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS, "Only parameters of type RTPExternalTransmissionParams can be passed to the external transmission component" }, +{ ERR_RTP_EXTERNALTRANS_NOACCEPTLIST, "The external transmitter does not have an accept list" }, +{ ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED, "The external transmitter does not have a destination list" }, +{ ERR_RTP_EXTERNALTRANS_NOIGNORELIST, "The external transmitter does not have an ignore list" }, +{ ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT, "The external transmitter does not support the multicast functions" }, +{ ERR_RTP_EXTERNALTRANS_NOSENDER, "No sender has been set for this external transmitter" }, +{ ERR_RTP_EXTERNALTRANS_NOTCREATED, "The external transmitter has not been created yet" }, +{ ERR_RTP_EXTERNALTRANS_NOTINIT, "The external transmitter has not been initialized yet" }, +{ ERR_RTP_EXTERNALTRANS_NOTWAITING, "The external transmitter is not currently waiting for incoming data" }, +{ ERR_RTP_EXTERNALTRANS_SENDERROR, "The external transmitter was unable to actually send the data" }, +{ ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG, "The specified data size exceeds the maximum amount that has been set" }, +{ ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT, "Unable to obtain the existing socket info using 'getsockname'" }, +{ ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET, "The existing socket specified does not appear to be an IPv4 socket" }, +{ ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET, "The existing socket that was specified does not have its port set yet" }, +{ ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE, "Can't get the socket type of the specified existing socket" }, +{ ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE, "The specified existing socket is not an UDP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET, "Can't get a valid socket when trying to choose a port automatically" }, +{ ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET, "Can't seem to get RTP/RTCP ports automatically, too many attempts" }, +{ ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED, "Flag to change data was requested, but OnChangeRTPOrRTCPData was not reimplemented" }, +{ ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED, "The initialization function was already called" }, +{ ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT, "Unable to initialize libsrtp context" }, +{ ERR_RTP_SECURESESSION_CANTINITMUTEX, "Unable to initialize a mutex" }, +{ ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED, "The libsrtp context initilization function must be called before it can be used" }, +{ ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT, "There's not enough RTP or RTCP data to encrypt" }, +{ ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA, "Unable to encrypt RTP data" }, +{ ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA, "Unable to encrypt RTCP data" }, +{ ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT, "There's not enough RTP or RTCP data to decrypt" }, +{ ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA, "Unable to decrypt RTP data" }, +{ ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA, "Unable to decrypt RTCP data" }, +{ ERR_RTP_ABORTDESC_ALREADYINIT, "The RTPAbortDescriptors instance is already initialized" }, +{ ERR_RTP_ABORTDESC_NOTINIT, "The RTPAbortDescriptors instance is not yet initialized" }, +{ ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS, "Unable to create two connected TCP sockets for the abort descriptors" }, +{ ERR_RTP_ABORTDESC_CANTCREATEPIPE, "Unable to create a pipe for the abort descriptors" }, +{ ERR_RTP_SESSION_THREADSAFETYCONFLICT, "For the background poll thread to be used, thread safety must also be set" }, +{ ERR_RTP_SELECT_ERRORINSELECT, "Error in the call to 'select'" }, +{ ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE, "A socket descriptor value is too large for a call to 'select' (exceeds FD_SETSIZE)" }, +{ ERR_RTP_SELECT_ERRORINPOLL, "Error in the call to 'poll' or 'WSAPoll'" }, +{ ERR_RTP_TCPTRANS_NOTINIT, "The TCP transmitter is not yet initialized" }, +{ ERR_RTP_TCPTRANS_ALREADYINIT, "The TCP transmitter is already initialized" }, +{ ERR_RTP_TCPTRANS_NOTCREATED, "The TCP transmitter is not yet created" }, +{ ERR_RTP_TCPTRANS_ALREADYCREATED, "The TCP transmitter is already created" }, +{ ERR_RTP_TCPTRANS_ILLEGALPARAMETERS, "The parameters for the TCP transmitter are invalid" }, +{ ERR_RTP_TCPTRANS_CANTINITMUTEX, "Unable to initialize a mutex during the initialization of the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_ALREADYWAITING, "The TCP transmitter is already waiting for data" }, +{ ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE, "The address specified is not a valid address for the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED, "No socket was specified in the address used for the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT, "The TCP transmitter does not support multicasting" }, +{ ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED, "The TCP transmitter does not support receive modes other than 'accept all'" }, +{ ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size for the TCP transmitter is limited to 64KB" }, +{ ERR_RTP_TCPTRANS_NOTWAITING, "The TCP transmitter is not waiting for data" }, +{ ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS, "The specified destination address (socket) was already added to the destination list of the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS, "The specified destination address (socket) was not found in the list of destinations of the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_ERRORINSEND, "An error occurred in the TCP transmitter while sending a packet" }, +{ ERR_RTP_TCPTRANS_ERRORINRECV, "An error occurred in the TCP transmitter while receiving a packet" }, +{ 0, 0 } }; std::string RTPGetErrorString(int errcode) { - int i; + int i; - if (errcode >= 0) - return std::string("No error"); + if (errcode >= 0) + return std::string("No error"); - i = 0; - while (ErrorDescriptions[i].code != 0) - { - if (ErrorDescriptions[i].code == errcode) - return std::string(ErrorDescriptions[i].description); - i++; - } + i = 0; + while (ErrorDescriptions[i].code != 0) + { + if (ErrorDescriptions[i].code == errcode) + return std::string(ErrorDescriptions[i].description); + i++; + } - char str[16]; + char str[16]; - RTP_SNPRINTF(str,16,"(%d)",errcode); + RTP_SNPRINTF(str, 16, "(%d)", errcode); - return std::string("Unknown error code") + std::string(str); + return std::string("Unknown error code") + std::string(str); } } // end namespace diff --git a/qrtplib/rtperrors.h b/qrtplib/rtperrors.h index b454dce4e..03b236cad 100644 --- a/qrtplib/rtperrors.h +++ b/qrtplib/rtperrors.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtperrors.h diff --git a/qrtplib/rtpexternaltransmitter.cpp b/qrtplib/rtpexternaltransmitter.cpp index 740b533e5..bfb578915 100644 --- a/qrtplib/rtpexternaltransmitter.cpp +++ b/qrtplib/rtpexternaltransmitter.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpexternaltransmitter.h" #include "rtprawpacket.h" @@ -50,363 +50,364 @@ namespace qrtplib { -RTPExternalTransmitter::RTPExternalTransmitter(RTPMemoryManager *mgr) : RTPTransmitter(mgr), packetinjector((RTPExternalTransmitter *)this) +RTPExternalTransmitter::RTPExternalTransmitter() : + packetinjector((RTPExternalTransmitter *) this) { - created = false; - init = false; + created = false; + init = false; } RTPExternalTransmitter::~RTPExternalTransmitter() { - Destroy(); + Destroy(); } int RTPExternalTransmitter::Init(bool tsafe) { - if (init) - return ERR_RTP_EXTERNALTRANS_ALREADYINIT; + if (init) + return ERR_RTP_EXTERNALTRANS_ALREADYINIT; - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; - init = true; - return 0; + init = true; + return 0; } -int RTPExternalTransmitter::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) +int RTPExternalTransmitter::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) { - const RTPExternalTransmissionParams *params; - int status; + const RTPExternalTransmissionParams *params; + int status; - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; - MAINMUTEX_LOCK + MAINMUTEX_LOCK - if (created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_ALREADYCREATED; - } + if (created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_ALREADYCREATED; + } - // Obtain transmission parameters + // Obtain transmission parameters - if (transparams == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; - } - if (transparams->GetTransmissionProtocol() != RTPTransmitter::ExternalProto) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; - } + if (transparams == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; + } + if (transparams->GetTransmissionProtocol() != RTPTransmitter::ExternalProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; + } - params = (const RTPExternalTransmissionParams *)transparams; + params = (const RTPExternalTransmissionParams *) transparams; - if ((status = m_abortDesc.Init()) < 0) - { - MAINMUTEX_UNLOCK - return status; - } - m_abortCount = 0; + if ((status = m_abortDesc.Init()) < 0) + { + MAINMUTEX_UNLOCK + return status; + } + m_abortCount = 0; - maxpacksize = maximumpacketsize; - sender = params->GetSender(); - headersize = params->GetAdditionalHeaderSize(); + maxpacksize = maximumpacketsize; + sender = params->GetSender(); + headersize = params->GetAdditionalHeaderSize(); - localhostname = 0; - localhostnamelength = 0; + localhostname = 0; + localhostnamelength = 0; - waitingfordata = false; - created = true; - MAINMUTEX_UNLOCK - return 0; + waitingfordata = false; + created = true; + MAINMUTEX_UNLOCK + return 0; } void RTPExternalTransmitter::Destroy() { - if (!init) - return; + if (!init) + return; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK; - return; - } + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK; + return; + } - if (localhostname) - { - RTPDeleteByteArray(localhostname,GetMemoryManager()); - localhostname = 0; - localhostnamelength = 0; - } + if (localhostname) + { + delete[] localhostname; + localhostname = 0; + localhostnamelength = 0; + } - FlushPackets(); - created = false; + FlushPackets(); + created = false; - if (waitingfordata) - { - m_abortDesc.SendAbortSignal(); - m_abortCount++; - m_abortDesc.Destroy(); - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK - } - else - m_abortDesc.Destroy(); + if (waitingfordata) + { + m_abortDesc.SendAbortSignal(); + m_abortCount++; + m_abortDesc.Destroy(); + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK +} +else + m_abortDesc.Destroy(); - MAINMUTEX_UNLOCK +MAINMUTEX_UNLOCK } RTPTransmissionInfo *RTPExternalTransmitter::GetTransmissionInfo() { - if (!init) - return 0; +if (!init) +return 0; - MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = new RTPExternalTransmissionInfo(&packetinjector); - MAINMUTEX_UNLOCK - return tinf; +MAINMUTEX_LOCK +RTPTransmissionInfo *tinf = new RTPExternalTransmissionInfo(&packetinjector); +MAINMUTEX_UNLOCK +return tinf; } void RTPExternalTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) { - if (!init) - return; +if (!init) +return; - RTPDelete(i, GetMemoryManager()); +delete i; } -int RTPExternalTransmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) +int RTPExternalTransmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) { - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; +if (!init) +return ERR_RTP_EXTERNALTRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOTCREATED; +} - if (localhostname == 0) - { - // We'll just use 'gethostname' for simplicity +if (localhostname == 0) +{ +// We'll just use 'gethostname' for simplicity - char name[1024]; +char name[1024]; - if (gethostname(name,1023) != 0) - strcpy(name, "localhost"); // failsafe - else - name[1023] = 0; // ensure null-termination +if (gethostname(name, 1023) != 0) + strcpy(name, "localhost"); // failsafe +else + name[1023] = 0; // ensure null-termination - localhostnamelength = strlen(name); - localhostname = new uint8_t [localhostnamelength+1]; +localhostnamelength = strlen(name); +localhostname = new uint8_t[localhostnamelength + 1]; - memcpy(localhostname, name, localhostnamelength); - localhostname[localhostnamelength] = 0; - } +memcpy(localhostname, name, localhostnamelength); +localhostname[localhostnamelength] = 0; +} - if ((*bufferlength) < localhostnamelength) - { - *bufferlength = localhostnamelength; // tell the application the required size of the buffer - MAINMUTEX_UNLOCK - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } +if ((*bufferlength) < localhostnamelength) +{ +*bufferlength = localhostnamelength; // tell the application the required size of the buffer +MAINMUTEX_UNLOCK +return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; +} - memcpy(buffer,localhostname,localhostnamelength); - *bufferlength = localhostnamelength; +memcpy(buffer, localhostname, localhostnamelength); +*bufferlength = localhostnamelength; - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; } bool RTPExternalTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) { - MAINMUTEX_LOCK - bool value = false; - if (sender) - value = sender->ComesFromThisSender(addr); - MAINMUTEX_UNLOCK - return value; +MAINMUTEX_LOCK +bool value = false; +if (sender) +value = sender->ComesFromThisSender(addr); +MAINMUTEX_UNLOCK +return value; } int RTPExternalTransmitter::Poll() { - return 0; +return 0; } -int RTPExternalTransmitter::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +int RTPExternalTransmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) { - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; +if (!init) +return ERR_RTP_EXTERNALTRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (waitingfordata) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_ALREADYWAITING; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOTCREATED; +} +if (waitingfordata) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_ALREADYWAITING; +} - waitingfordata = true; +waitingfordata = true; - if (!rawpacketlist.empty()) - { - if (dataavailable != 0) - *dataavailable = true; - waitingfordata = false; - MAINMUTEX_UNLOCK - return 0; - } +if (!rawpacketlist.empty()) +{ +if (dataavailable != 0) + *dataavailable = true; +waitingfordata = false; +MAINMUTEX_UNLOCK +return 0; +} - WAITMUTEX_LOCK - MAINMUTEX_UNLOCK +WAITMUTEX_LOCK +MAINMUTEX_UNLOCK - int8_t isset = 0; - SocketType abortSock = m_abortDesc.GetAbortSocket(); - int status = RTPSelect(&abortSock, &isset, 1, delay); - if (status < 0) - { - MAINMUTEX_LOCK - waitingfordata = false; - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return status; - } +int8_t isset = 0; +SocketType abortSock = m_abortDesc.GetAbortSocket(); +int status = RTPSelect(&abortSock, &isset, 1, delay); +if (status < 0) +{ +MAINMUTEX_LOCK +waitingfordata = false; +MAINMUTEX_UNLOCK +WAITMUTEX_UNLOCK +return status; +} - MAINMUTEX_LOCK - waitingfordata = false; - if (!created) // destroy called - { - MAINMUTEX_UNLOCK; - WAITMUTEX_UNLOCK - return 0; - } +MAINMUTEX_LOCK +waitingfordata = false; +if (!created) // destroy called +{ +MAINMUTEX_UNLOCK; +WAITMUTEX_UNLOCK +return 0; +} - // if aborted, read from abort buffer - if (isset) - { - m_abortDesc.ClearAbortSignal(); - m_abortCount = 0; - } + // if aborted, read from abort buffer +if (isset) +{ +m_abortDesc.ClearAbortSignal(); +m_abortCount = 0; +} - if (dataavailable != 0) - { - if (rawpacketlist.empty()) - *dataavailable = false; - else - *dataavailable = true; - } +if (dataavailable != 0) +{ +if (rawpacketlist.empty()) + *dataavailable = false; +else + *dataavailable = true; +} - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +WAITMUTEX_UNLOCK +return 0; } int RTPExternalTransmitter::AbortWait() { - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; +if (!init) +return ERR_RTP_EXTERNALTRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (!waitingfordata) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOTWAITING; - } - - m_abortDesc.SendAbortSignal(); - m_abortCount++; - - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOTCREATED; +} +if (!waitingfordata) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOTWAITING; } -int RTPExternalTransmitter::SendRTPData(const void *data,size_t len) -{ - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; +m_abortDesc.SendAbortSignal(); +m_abortCount++; - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; - } - - if (!sender) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOSENDER; - } - - MAINMUTEX_UNLOCK - - if (!sender->SendRTP(data, len)) - return ERR_RTP_EXTERNALTRANS_SENDERROR; - - return 0; +MAINMUTEX_UNLOCK +return 0; } -int RTPExternalTransmitter::SendRTCPData(const void *data,size_t len) +int RTPExternalTransmitter::SendRTPData(const void *data, size_t len) { - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; +if (!init) +return ERR_RTP_EXTERNALTRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOTCREATED; +} +if (len > maxpacksize) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; +} - if (!sender) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOSENDER; - } - MAINMUTEX_UNLOCK +if (!sender) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOSENDER; +} - if (!sender->SendRTCP(data, len)) - return ERR_RTP_EXTERNALTRANS_SENDERROR; +MAINMUTEX_UNLOCK - return 0; +if (!sender->SendRTP(data, len)) +return ERR_RTP_EXTERNALTRANS_SENDERROR; + +return 0; +} + +int RTPExternalTransmitter::SendRTCPData(const void *data, size_t len) +{ +if (!init) +return ERR_RTP_EXTERNALTRANS_NOTINIT; + +MAINMUTEX_LOCK + +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOTCREATED; +} +if (len > maxpacksize) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; +} + +if (!sender) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOSENDER; +} +MAINMUTEX_UNLOCK + +if (!sender->SendRTCP(data, len)) +return ERR_RTP_EXTERNALTRANS_SENDERROR; + +return 0; } int RTPExternalTransmitter::AddDestination(const RTPAddress &) { - return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; +return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; } int RTPExternalTransmitter::DeleteDestination(const RTPAddress &) { - return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; +return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; } void RTPExternalTransmitter::ClearDestinations() @@ -415,17 +416,17 @@ void RTPExternalTransmitter::ClearDestinations() bool RTPExternalTransmitter::SupportsMulticasting() { - return false; +return false; } int RTPExternalTransmitter::JoinMulticastGroup(const RTPAddress &) { - return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; +return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; } int RTPExternalTransmitter::LeaveMulticastGroup(const RTPAddress &) { - return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; +return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; } void RTPExternalTransmitter::LeaveAllMulticastGroups() @@ -434,32 +435,32 @@ void RTPExternalTransmitter::LeaveAllMulticastGroups() int RTPExternalTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) { - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; +if (!init) +return ERR_RTP_EXTERNALTRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (m != RTPTransmitter::AcceptAll) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE; - } - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOTCREATED; +} +if (m != RTPTransmitter::AcceptAll) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE; +} +MAINMUTEX_UNLOCK +return 0; } int RTPExternalTransmitter::AddToIgnoreList(const RTPAddress &) { - return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; +return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; } int RTPExternalTransmitter::DeleteFromIgnoreList(const RTPAddress &) { - return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; +return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; } void RTPExternalTransmitter::ClearIgnoreList() @@ -468,12 +469,12 @@ void RTPExternalTransmitter::ClearIgnoreList() int RTPExternalTransmitter::AddToAcceptList(const RTPAddress &) { - return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; +return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; } int RTPExternalTransmitter::DeleteFromAcceptList(const RTPAddress &) { - return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; +return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; } void RTPExternalTransmitter::ClearAcceptList() @@ -482,228 +483,228 @@ void RTPExternalTransmitter::ClearAcceptList() int RTPExternalTransmitter::SetMaximumPacketSize(size_t s) { - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; +if (!init) +return ERR_RTP_EXTERNALTRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - maxpacksize = s; - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_EXTERNALTRANS_NOTCREATED; +} +maxpacksize = s; +MAINMUTEX_UNLOCK +return 0; } bool RTPExternalTransmitter::NewDataAvailable() { - if (!init) - return false; +if (!init) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - bool v; +bool v; - if (!created) - v = false; - else - { - if (rawpacketlist.empty()) - v = false; - else - v = true; - } +if (!created) +v = false; +else +{ +if (rawpacketlist.empty()) + v = false; +else + v = true; +} - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } RTPRawPacket *RTPExternalTransmitter::GetNextPacket() { - if (!init) - return 0; +if (!init) +return 0; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - RTPRawPacket *p; +RTPRawPacket *p; - if (!created) - { - MAINMUTEX_UNLOCK - return 0; - } - if (rawpacketlist.empty()) - { - MAINMUTEX_UNLOCK - return 0; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return 0; +} +if (rawpacketlist.empty()) +{ +MAINMUTEX_UNLOCK +return 0; +} - p = *(rawpacketlist.begin()); - rawpacketlist.pop_front(); +p = *(rawpacketlist.begin()); +rawpacketlist.pop_front(); - MAINMUTEX_UNLOCK - return p; +MAINMUTEX_UNLOCK +return p; } // Here the private functions start... void RTPExternalTransmitter::FlushPackets() { - std::list::const_iterator it; +std::list::const_iterator it; - for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) - RTPDelete(*it,GetMemoryManager()); - rawpacketlist.clear(); +for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) +delete *it; +rawpacketlist.clear(); } void RTPExternalTransmitter::InjectRTP(const void *data, size_t len, const RTPAddress &a) { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return; - } +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return; +} - RTPAddress *addr = a.CreateCopy(GetMemoryManager()); - if (addr == 0) - return; +RTPAddress *addr = a.CreateCopy(); +if (addr == 0) +return; - uint8_t *datacopy; +uint8_t *datacopy; - datacopy = new uint8_t[len]; - if (datacopy == 0) - { - RTPDelete(addr,GetMemoryManager()); - return; - } - memcpy(datacopy, data, len); +datacopy = new uint8_t[len]; +if (datacopy == 0) +{ +delete addr; +return; +} +memcpy(datacopy, data, len); - RTPTime curtime = RTPTime::CurrentTime(); - RTPRawPacket *pack; +RTPTime curtime = RTPTime::CurrentTime(); +RTPRawPacket *pack; - pack = new RTPRawPacket(datacopy,len,addr,curtime,true,GetMemoryManager()); - if (pack == 0) - { - RTPDelete(addr,GetMemoryManager()); - RTPDeleteByteArray(localhostname,GetMemoryManager()); - return; - } - rawpacketlist.push_back(pack); +pack = new RTPRawPacket(datacopy, len, addr, curtime, true); +if (pack == 0) +{ +delete addr; +delete[] localhostname; +return; +} +rawpacketlist.push_back(pack); - if (m_abortCount == 0) - { - m_abortDesc.SendAbortSignal(); - m_abortCount++; - } +if (m_abortCount == 0) +{ +m_abortDesc.SendAbortSignal(); +m_abortCount++; +} - MAINMUTEX_UNLOCK +MAINMUTEX_UNLOCK } void RTPExternalTransmitter::InjectRTCP(const void *data, size_t len, const RTPAddress &a) { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return; - } +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return; +} - RTPAddress *addr = a.CreateCopy(GetMemoryManager()); - if (addr == 0) - return; +RTPAddress *addr = a.CreateCopy(); +if (addr == 0) +return; - uint8_t *datacopy; +uint8_t *datacopy; - datacopy = new uint8_t[len]; - if (datacopy == 0) - { - RTPDelete(addr,GetMemoryManager()); - return; - } - memcpy(datacopy, data, len); +datacopy = new uint8_t[len]; +if (datacopy == 0) +{ +delete addr; +return; +} +memcpy(datacopy, data, len); - RTPTime curtime = RTPTime::CurrentTime(); - RTPRawPacket *pack; +RTPTime curtime = RTPTime::CurrentTime(); +RTPRawPacket *pack; - pack = new RTPRawPacket(datacopy,len,addr,curtime,false,GetMemoryManager()); - if (pack == 0) - { - RTPDelete(addr,GetMemoryManager()); - RTPDeleteByteArray(localhostname,GetMemoryManager()); - return; - } - rawpacketlist.push_back(pack); +pack = new RTPRawPacket(datacopy, len, addr, curtime, false); +if (pack == 0) +{ +delete addr; +delete[] localhostname; +return; +} +rawpacketlist.push_back(pack); - if (m_abortCount == 0) - { - m_abortDesc.SendAbortSignal(); - m_abortCount++; - } +if (m_abortCount == 0) +{ +m_abortDesc.SendAbortSignal(); +m_abortCount++; +} - MAINMUTEX_UNLOCK +MAINMUTEX_UNLOCK } void RTPExternalTransmitter::InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a) { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return; - } +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return; +} - RTPAddress *addr = a.CreateCopy(GetMemoryManager()); - if (addr == 0) - return; +RTPAddress *addr = a.CreateCopy(); +if (addr == 0) +return; - uint8_t *datacopy; - bool rtp = true; +uint8_t *datacopy; +bool rtp = true; - if (len >= 2) - { - const uint8_t *pData = (const uint8_t *)data; - if (pData[1] >= 200 && pData[1] <= 204) - rtp = false; - } +if (len >= 2) +{ +const uint8_t *pData = (const uint8_t *) data; +if (pData[1] >= 200 && pData[1] <= 204) +rtp = false; +} - datacopy = new uint8_t[len]; - if (datacopy == 0) - { - RTPDelete(addr,GetMemoryManager()); - return; - } - memcpy(datacopy, data, len); +datacopy = new uint8_t[len]; +if (datacopy == 0) +{ +delete addr; +return; +} +memcpy(datacopy, data, len); - RTPTime curtime = RTPTime::CurrentTime(); - RTPRawPacket *pack; +RTPTime curtime = RTPTime::CurrentTime(); +RTPRawPacket *pack; - pack = new RTPRawPacket(datacopy,len,addr,curtime,rtp,GetMemoryManager()); - if (pack == 0) - { - RTPDelete(addr,GetMemoryManager()); - RTPDeleteByteArray(localhostname,GetMemoryManager()); - return; - } - rawpacketlist.push_back(pack); +pack = new RTPRawPacket(datacopy, len, addr, curtime, rtp); +if (pack == 0) +{ +delete addr; +delete[] localhostname; +return; +} +rawpacketlist.push_back(pack); - if (m_abortCount == 0) - { - m_abortDesc.SendAbortSignal(); - m_abortCount++; - } +if (m_abortCount == 0) +{ +m_abortDesc.SendAbortSignal(); +m_abortCount++; +} - MAINMUTEX_UNLOCK +MAINMUTEX_UNLOCK } diff --git a/qrtplib/rtpexternaltransmitter.h b/qrtplib/rtpexternaltransmitter.h index cad0ff9b3..8cdefd2ca 100644 --- a/qrtplib/rtpexternaltransmitter.h +++ b/qrtplib/rtpexternaltransmitter.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpexternaltransmitter.h @@ -59,17 +59,21 @@ class RTPExternalTransmitter; class JRTPLIB_IMPORTEXPORT RTPExternalSender { public: - RTPExternalSender() { } - virtual ~RTPExternalSender() { } + RTPExternalSender() + { + } + virtual ~RTPExternalSender() + { + } - /** This member function will be called when RTP data needs to be transmitted. */ - virtual bool SendRTP(const void *data, size_t len) = 0; + /** This member function will be called when RTP data needs to be transmitted. */ + virtual bool SendRTP(const void *data, size_t len) = 0; - /** This member function will be called when an RTCP packet needs to be transmitted. */ - virtual bool SendRTCP(const void *data, size_t len) = 0; + /** This member function will be called when an RTCP packet needs to be transmitted. */ + virtual bool SendRTCP(const void *data, size_t len) = 0; - /** Used to identify if an RTPAddress instance originated from this sender (to be able to detect own packets). */ - virtual bool ComesFromThisSender(const RTPAddress *a) = 0; + /** Used to identify if an RTPAddress instance originated from this sender (to be able to detect own packets). */ + virtual bool ComesFromThisSender(const RTPAddress *a) = 0; }; /** Interface to inject incoming RTP and RTCP packets into the library. @@ -82,47 +86,70 @@ public: class JRTPLIB_IMPORTEXPORT RTPExternalPacketInjecter { public: - RTPExternalPacketInjecter(RTPExternalTransmitter *trans) { transmitter = trans; } - ~RTPExternalPacketInjecter() { } + RTPExternalPacketInjecter(RTPExternalTransmitter *trans) + { + transmitter = trans; + } + ~RTPExternalPacketInjecter() + { + } - /** This function can be called to insert an RTP packet into the transmission component. */ - void InjectRTP(const void *data, size_t len, const RTPAddress &a); + /** This function can be called to insert an RTP packet into the transmission component. */ + void InjectRTP(const void *data, size_t len, const RTPAddress &a); - /** This function can be called to insert an RTCP packet into the transmission component. */ - void InjectRTCP(const void *data, size_t len, const RTPAddress &a); + /** This function can be called to insert an RTCP packet into the transmission component. */ + void InjectRTCP(const void *data, size_t len, const RTPAddress &a); - /** Use this function to inject an RTP or RTCP packet and the transmitter will try to figure out which type of packet it is. */ - void InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a); + /** Use this function to inject an RTP or RTCP packet and the transmitter will try to figure out which type of packet it is. */ + void InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a); private: - RTPExternalTransmitter *transmitter; + RTPExternalTransmitter *transmitter; }; /** Parameters to initialize a transmitter of type RTPExternalTransmitter. */ -class JRTPLIB_IMPORTEXPORT RTPExternalTransmissionParams : public RTPTransmissionParams +class JRTPLIB_IMPORTEXPORT RTPExternalTransmissionParams: public RTPTransmissionParams { public: - /** Using this constructor you can specify which RTPExternalSender object you'll be using - * and how much the additional header overhead for each packet will be. */ - RTPExternalTransmissionParams(RTPExternalSender *s, int headeroverhead):RTPTransmissionParams(RTPTransmitter::ExternalProto) { sender = s; headersize = headeroverhead; } + /** Using this constructor you can specify which RTPExternalSender object you'll be using + * and how much the additional header overhead for each packet will be. */ + RTPExternalTransmissionParams(RTPExternalSender *s, int headeroverhead) : + RTPTransmissionParams(RTPTransmitter::ExternalProto) + { + sender = s; + headersize = headeroverhead; + } - RTPExternalSender *GetSender() const { return sender; } - int GetAdditionalHeaderSize() const { return headersize; } + RTPExternalSender *GetSender() const + { + return sender; + } + int GetAdditionalHeaderSize() const + { + return headersize; + } private: - RTPExternalSender *sender; - int headersize; + RTPExternalSender *sender; + int headersize; }; /** Additional information about the external transmission component. */ -class JRTPLIB_IMPORTEXPORT RTPExternalTransmissionInfo : public RTPTransmissionInfo +class JRTPLIB_IMPORTEXPORT RTPExternalTransmissionInfo: public RTPTransmissionInfo { public: - RTPExternalTransmissionInfo(RTPExternalPacketInjecter *p) : RTPTransmissionInfo(RTPTransmitter::ExternalProto) { packetinjector = p; } + RTPExternalTransmissionInfo(RTPExternalPacketInjecter *p) : + RTPTransmissionInfo(RTPTransmitter::ExternalProto) + { + packetinjector = p; + } - /** Tells you which RTPExternalPacketInjecter you need to use to pass RTP or RTCP - * data on to the transmission component. */ - RTPExternalPacketInjecter *GetPacketInjector() const { return packetinjector; } + /** Tells you which RTPExternalPacketInjecter you need to use to pass RTP or RTCP + * data on to the transmission component. */ + RTPExternalPacketInjecter *GetPacketInjector() const + { + return packetinjector; + } private: - RTPExternalPacketInjecter *packetinjector; + RTPExternalPacketInjecter *packetinjector; }; /** A transmission component which will use user specified functions to transmit the data and @@ -133,91 +160,93 @@ private: * sending the data. Obtain the RTPExternalTransmissionInfo object associated with this * transmitter to obtain the functions needed to pass RTP/RTCP packets on to the transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPExternalTransmitter : public RTPTransmitter +class JRTPLIB_IMPORTEXPORT RTPExternalTransmitter: public RTPTransmitter { public: - RTPExternalTransmitter(RTPMemoryManager *mgr); - ~RTPExternalTransmitter(); + RTPExternalTransmitter(); + ~RTPExternalTransmitter(); - int Init(bool treadsafe); - int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + int Init(bool treadsafe); + int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() { return headersize; } + int GetLocalHostName(uint8_t *buffer, size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() + { + return headersize; + } - int Poll(); - int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); - int AbortWait(); + int Poll(); + int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); + int AbortWait(); - int SendRTPData(const void *data,size_t len); - int SendRTCPData(const void *data,size_t len); + int SendRTPData(const void *data, size_t len); + int SendRTCPData(const void *data, size_t len); - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); - void InjectRTP(const void *data, size_t len, const RTPAddress &a); - void InjectRTCP(const void *data, size_t len, const RTPAddress &a); - void InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a); + void InjectRTP(const void *data, size_t len, const RTPAddress &a); + void InjectRTCP(const void *data, size_t len, const RTPAddress &a); + void InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a); private: - void FlushPackets(); + void FlushPackets(); - bool init; - bool created; - bool waitingfordata; - RTPExternalSender *sender; - RTPExternalPacketInjecter packetinjector; + bool init; + bool created; + bool waitingfordata; + RTPExternalSender *sender; + RTPExternalPacketInjecter packetinjector; - std::list rawpacketlist; + std::list rawpacketlist; - uint8_t *localhostname; - size_t localhostnamelength; + uint8_t *localhostname; + size_t localhostnamelength; - size_t maxpacksize; - int headersize; + size_t maxpacksize; + int headersize; - RTPAbortDescriptors m_abortDesc; - int m_abortCount; + RTPAbortDescriptors m_abortDesc; + int m_abortCount; }; inline void RTPExternalPacketInjecter::InjectRTP(const void *data, size_t len, const RTPAddress &a) { - transmitter->InjectRTP(data, len, a); + transmitter->InjectRTP(data, len, a); } inline void RTPExternalPacketInjecter::InjectRTCP(const void *data, size_t len, const RTPAddress &a) { - transmitter->InjectRTCP(data, len, a); + transmitter->InjectRTCP(data, len, a); } inline void RTPExternalPacketInjecter::InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a) { - transmitter->InjectRTPorRTCP(data, len, a); + transmitter->InjectRTPorRTCP(data, len, a); } } // end namespace #endif // RTPTCPSOCKETTRANSMITTER_H - diff --git a/qrtplib/rtphashtable.h b/qrtplib/rtphashtable.h index d95b0be00..9c98a089c 100644 --- a/qrtplib/rtphashtable.h +++ b/qrtplib/rtphashtable.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #ifndef RTPHASHTABLE_H @@ -39,267 +39,292 @@ */ #include "rtperrors.h" -#include "rtpmemoryobject.h" namespace qrtplib { //template -template -class RTPHashTable : public RTPMemoryObject +template +class RTPHashTable { public: - RTPHashTable(RTPMemoryManager *mgr = 0, int memtype = RTPMEM_TYPE_OTHER); - ~RTPHashTable() { Clear(); } + RTPHashTable(); + ~RTPHashTable() + { + Clear(); + } - void GotoFirstElement() { curhashelem = firsthashelem; } - void GotoLastElement() { curhashelem = lasthashelem; } - bool HasCurrentElement() { return (curhashelem == 0)?false:true; } - int DeleteCurrentElement(); - Element &GetCurrentElement() { return curhashelem->GetElement(); } - int GotoElement(const Element &e); - bool HasElement(const Element &e); - void GotoNextElement(); - void GotoPreviousElement(); - void Clear(); + void GotoFirstElement() + { + curhashelem = firsthashelem; + } + void GotoLastElement() + { + curhashelem = lasthashelem; + } + bool HasCurrentElement() + { + return (curhashelem == 0) ? false : true; + } + int DeleteCurrentElement(); + Element &GetCurrentElement() + { + return curhashelem->GetElement(); + } + int GotoElement(const Element &e); + bool HasElement(const Element &e); + void GotoNextElement(); + void GotoPreviousElement(); + void Clear(); - int AddElement(const Element &elem); - int DeleteElement(const Element &elem); + int AddElement(const Element &elem); + int DeleteElement(const Element &elem); private: - class HashElement - { - public: - HashElement(const Element &e,int index):element(e) { hashprev = 0; hashnext = 0; listnext = 0; listprev = 0; hashindex = index; } - int GetHashIndex() { return hashindex; } - Element &GetElement() { return element; } + class HashElement + { + public: + HashElement(const Element &e, int index) : + element(e) + { + hashprev = 0; + hashnext = 0; + listnext = 0; + listprev = 0; + hashindex = index; + } + int GetHashIndex() + { + return hashindex; + } + Element &GetElement() + { + return element; + } - private: - int hashindex; - Element element; - public: - HashElement *hashprev,*hashnext; - HashElement *listprev,*listnext; - }; + private: + int hashindex; + Element element; + public: + HashElement *hashprev, *hashnext; + HashElement *listprev, *listnext; + }; - HashElement *table[hashsize]; - HashElement *firsthashelem,*lasthashelem; - HashElement *curhashelem; + HashElement *table[hashsize]; + HashElement *firsthashelem, *lasthashelem; + HashElement *curhashelem; #ifdef RTP_SUPPORT_MEMORYMANAGEMENT - int memorytype; + int memorytype; #endif // RTP_SUPPORT_MEMORYMANAGEMENT }; -template -inline RTPHashTable::RTPHashTable(RTPMemoryManager *mgr,int memtype) : RTPMemoryObject(mgr) +template +inline RTPHashTable::RTPHashTable() { - JRTPLIB_UNUSED(memtype); // possibly unused - - for (int i = 0 ; i < hashsize ; i++) - table[i] = 0; - firsthashelem = 0; - lasthashelem = 0; -#ifdef RTP_SUPPORT_MEMORYMANAGEMENT - memorytype = memtype; -#endif // RTP_SUPPORT_MEMORYMANAGEMENT + for (int i = 0; i < hashsize; i++) + table[i] = 0; + firsthashelem = 0; + lasthashelem = 0; } -template -inline int RTPHashTable::DeleteCurrentElement() +template +inline int RTPHashTable::DeleteCurrentElement() { - if (curhashelem) - { - HashElement *tmp1,*tmp2; - int index; + if (curhashelem) + { + HashElement *tmp1, *tmp2; + int index; - // First, relink elements in current hash bucket + // First, relink elements in current hash bucket - index = curhashelem->GetHashIndex(); - tmp1 = curhashelem->hashprev; - tmp2 = curhashelem->hashnext; - if (tmp1 == 0) // no previous element in hash bucket - { - table[index] = tmp2; - if (tmp2 != 0) - tmp2->hashprev = 0; - } - else // there is a previous element in the hash bucket - { - tmp1->hashnext = tmp2; - if (tmp2 != 0) - tmp2->hashprev = tmp1; - } + index = curhashelem->GetHashIndex(); + tmp1 = curhashelem->hashprev; + tmp2 = curhashelem->hashnext; + if (tmp1 == 0) // no previous element in hash bucket + { + table[index] = tmp2; + if (tmp2 != 0) + tmp2->hashprev = 0; + } + else // there is a previous element in the hash bucket + { + tmp1->hashnext = tmp2; + if (tmp2 != 0) + tmp2->hashprev = tmp1; + } - // Relink elements in list + // Relink elements in list - tmp1 = curhashelem->listprev; - tmp2 = curhashelem->listnext; - if (tmp1 == 0) // curhashelem is first in list - { - firsthashelem = tmp2; - if (tmp2 != 0) - tmp2->listprev = 0; - else // curhashelem is also last in list - lasthashelem = 0; - } - else - { - tmp1->listnext = tmp2; - if (tmp2 != 0) - tmp2->listprev = tmp1; - else // curhashelem is last in list - lasthashelem = tmp1; - } + tmp1 = curhashelem->listprev; + tmp2 = curhashelem->listnext; + if (tmp1 == 0) // curhashelem is first in list + { + firsthashelem = tmp2; + if (tmp2 != 0) + tmp2->listprev = 0; + else + // curhashelem is also last in list + lasthashelem = 0; + } + else + { + tmp1->listnext = tmp2; + if (tmp2 != 0) + tmp2->listprev = tmp1; + else + // curhashelem is last in list + lasthashelem = tmp1; + } - // finally, with everything being relinked, we can delete curhashelem - RTPDelete(curhashelem,GetMemoryManager()); - curhashelem = tmp2; // Set to next element in the list - } - else - return ERR_RTP_HASHTABLE_NOCURRENTELEMENT; - return 0; + // finally, with everything being relinked, we can delete curhashelem + delete curhashelem; + curhashelem = tmp2; // Set to next element in the list + } + else + return ERR_RTP_HASHTABLE_NOCURRENTELEMENT; + return 0; } -template -inline int RTPHashTable::GotoElement(const Element &e) +template +inline int RTPHashTable::GotoElement(const Element &e) { - int index; - bool found; + int index; + bool found; - index = GetIndex::GetIndex(e); - if (index >= hashsize) - return ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + index = GetIndex::GetIndex(e); + if (index >= hashsize) + return ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; - curhashelem = table[index]; - found = false; - while(!found && curhashelem != 0) - { - if (curhashelem->GetElement() == e) - found = true; - else - curhashelem = curhashelem->hashnext; - } - if (!found) - return ERR_RTP_HASHTABLE_ELEMENTNOTFOUND; - return 0; + curhashelem = table[index]; + found = false; + while (!found && curhashelem != 0) + { + if (curhashelem->GetElement() == e) + found = true; + else + curhashelem = curhashelem->hashnext; + } + if (!found) + return ERR_RTP_HASHTABLE_ELEMENTNOTFOUND; + return 0; } -template -inline bool RTPHashTable::HasElement(const Element &e) +template +inline bool RTPHashTable::HasElement(const Element &e) { - int index; - bool found; - HashElement *tmp; + int index; + bool found; + HashElement *tmp; - index = GetIndex::GetIndex(e); - if (index >= hashsize) - return false; + index = GetIndex::GetIndex(e); + if (index >= hashsize) + return false; - tmp = table[index]; - found = false; - while(!found && tmp != 0) - { - if (tmp->GetElement() == e) - found = true; - else - tmp = tmp->hashnext; - } - return found; + tmp = table[index]; + found = false; + while (!found && tmp != 0) + { + if (tmp->GetElement() == e) + found = true; + else + tmp = tmp->hashnext; + } + return found; } -template -inline void RTPHashTable::GotoNextElement() +template +inline void RTPHashTable::GotoNextElement() { - if (curhashelem) - curhashelem = curhashelem->listnext; + if (curhashelem) + curhashelem = curhashelem->listnext; } -template -inline void RTPHashTable::GotoPreviousElement() +template +inline void RTPHashTable::GotoPreviousElement() { - if (curhashelem) - curhashelem = curhashelem->listprev; + if (curhashelem) + curhashelem = curhashelem->listprev; } -template -inline void RTPHashTable::Clear() +template +inline void RTPHashTable::Clear() { - HashElement *tmp1,*tmp2; + HashElement *tmp1, *tmp2; - for (int i = 0 ; i < hashsize ; i++) - table[i] = 0; + for (int i = 0; i < hashsize; i++) + table[i] = 0; - tmp1 = firsthashelem; - while (tmp1 != 0) - { - tmp2 = tmp1->listnext; - RTPDelete(tmp1,GetMemoryManager()); - tmp1 = tmp2; - } - firsthashelem = 0; - lasthashelem = 0; + tmp1 = firsthashelem; + while (tmp1 != 0) + { + tmp2 = tmp1->listnext; + delete tmp1; + tmp1 = tmp2; + } + firsthashelem = 0; + lasthashelem = 0; } -template -inline int RTPHashTable::AddElement(const Element &elem) +template +inline int RTPHashTable::AddElement(const Element &elem) { - int index; - bool found; - HashElement *e,*newelem; + int index; + bool found; + HashElement *e, *newelem; - index = GetIndex::GetIndex(elem); - if (index >= hashsize) - return ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + index = GetIndex::GetIndex(elem); + if (index >= hashsize) + return ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; - e = table[index]; - found = false; - while(!found && e != 0) - { - if (e->GetElement() == elem) - found = true; - else - e = e->hashnext; - } - if (found) - return ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS; + e = table[index]; + found = false; + while (!found && e != 0) + { + if (e->GetElement() == elem) + found = true; + else + e = e->hashnext; + } + if (found) + return ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS; - // Okay, the key doesn't exist, so we can add the new element in the hash table + // Okay, the key doesn't exist, so we can add the new element in the hash table - newelem = new HashElement(elem,index); - if (newelem == 0) - return ERR_RTP_OUTOFMEM; + newelem = new HashElement(elem, index); + if (newelem == 0) + return ERR_RTP_OUTOFMEM; - e = table[index]; - table[index] = newelem; - newelem->hashnext = e; - if (e != 0) - e->hashprev = newelem; + e = table[index]; + table[index] = newelem; + newelem->hashnext = e; + if (e != 0) + e->hashprev = newelem; - // Now, we still got to add it to the linked list + // Now, we still got to add it to the linked list - if (firsthashelem == 0) - { - firsthashelem = newelem; - lasthashelem = newelem; - } - else // there already are some elements in the list - { - lasthashelem->listnext = newelem; - newelem->listprev = lasthashelem; - lasthashelem = newelem; - } - return 0; + if (firsthashelem == 0) + { + firsthashelem = newelem; + lasthashelem = newelem; + } + else // there already are some elements in the list + { + lasthashelem->listnext = newelem; + newelem->listprev = lasthashelem; + lasthashelem = newelem; + } + return 0; } -template -inline int RTPHashTable::DeleteElement(const Element &elem) +template +inline int RTPHashTable::DeleteElement(const Element &elem) { - int status; + int status; - status = GotoElement(elem); - if (status < 0) - return status; - return DeleteCurrentElement(); + status = GotoElement(elem); + if (status < 0) + return status; + return DeleteCurrentElement(); } } // end namespace diff --git a/qrtplib/rtpinternalsourcedata.cpp b/qrtplib/rtpinternalsourcedata.cpp index bd731ee47..7854d09f1 100644 --- a/qrtplib/rtpinternalsourcedata.cpp +++ b/qrtplib/rtpinternalsourcedata.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpinternalsourcedata.h" #include "rtppacket.h" @@ -39,11 +39,12 @@ namespace qrtplib { -RTPInternalSourceData::RTPInternalSourceData(uint32_t ssrc,RTPSources::ProbationType probtype,RTPMemoryManager *mgr):RTPSourceData(ssrc,mgr) +RTPInternalSourceData::RTPInternalSourceData(uint32_t ssrc, RTPSources::ProbationType probtype) : + RTPSourceData(ssrc) { - JRTPLIB_UNUSED(probtype); // possibly unused + JRTPLIB_UNUSED(probtype); // possibly unused #ifdef RTP_SUPPORT_PROBATION - probationtype = probtype; + probationtype = probtype; #endif // RTP_SUPPORT_PROBATION } @@ -52,242 +53,242 @@ RTPInternalSourceData::~RTPInternalSourceData() } // The following function should delete rtppack if necessary -int RTPInternalSourceData::ProcessRTPPacket(RTPPacket *rtppack,const RTPTime &receivetime,bool *stored,RTPSources *sources) +int RTPInternalSourceData::ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, bool *stored, RTPSources *sources) { - bool accept,onprobation,applyprobation; - double tsunit; + bool accept, onprobation, applyprobation; + double tsunit; - *stored = false; + *stored = false; - if (timestampunit < 0) - tsunit = INF_GetEstimatedTimestampUnit(); - else - tsunit = timestampunit; + if (timestampunit < 0) + tsunit = INF_GetEstimatedTimestampUnit(); + else + tsunit = timestampunit; #ifdef RTP_SUPPORT_PROBATION - if (validated) // If the source is our own process, we can already be validated. No - applyprobation = false; // probation should be applied in that case. - else - { - if (probationtype == RTPSources::NoProbation) - applyprobation = false; - else - applyprobation = true; - } + if (validated) // If the source is our own process, we can already be validated. No + applyprobation = false; // probation should be applied in that case. + else + { + if (probationtype == RTPSources::NoProbation) + applyprobation = false; + else + applyprobation = true; + } #else - applyprobation = false; + applyprobation = false; #endif // RTP_SUPPORT_PROBATION - stats.ProcessPacket(rtppack,receivetime,tsunit,ownssrc,&accept,applyprobation,&onprobation); + stats.ProcessPacket(rtppack, receivetime, tsunit, ownssrc, &accept, applyprobation, &onprobation); #ifdef RTP_SUPPORT_PROBATION - switch (probationtype) - { - case RTPSources::ProbationStore: - if (!(onprobation || accept)) - return 0; - if (accept) - validated = true; - break; - case RTPSources::ProbationDiscard: - case RTPSources::NoProbation: - if (!accept) - return 0; - validated = true; - break; - default: - return ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE; - } + switch (probationtype) + { + case RTPSources::ProbationStore: + if (!(onprobation || accept)) + return 0; + if (accept) + validated = true; + break; + case RTPSources::ProbationDiscard: + case RTPSources::NoProbation: + if (!accept) + return 0; + validated = true; + break; + default: + return ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE; + } #else - if (!accept) - return 0; - validated = true; + if (!accept) + return 0; + validated = true; #endif // RTP_SUPPORT_PROBATION; - if (validated && !ownssrc) // for own ssrc these variables depend on the outgoing packets, not on the incoming - issender = true; + if (validated && !ownssrc) // for own ssrc these variables depend on the outgoing packets, not on the incoming + issender = true; - bool isonprobation = !validated; - bool ispackethandled = false; + bool isonprobation = !validated; + bool ispackethandled = false; - sources->OnValidatedRTPPacket(this, rtppack, isonprobation, &ispackethandled); - if (ispackethandled) // Packet is already handled in the callback, no need to store it in the list - { - // Set 'stored' to true to avoid the packet being deallocated - *stored = true; - return 0; - } + sources->OnValidatedRTPPacket(this, rtppack, isonprobation, &ispackethandled); + if (ispackethandled) // Packet is already handled in the callback, no need to store it in the list + { + // Set 'stored' to true to avoid the packet being deallocated + *stored = true; + return 0; + } - // Now, we can place the packet in the queue + // Now, we can place the packet in the queue - if (packetlist.empty()) - { - *stored = true; - packetlist.push_back(rtppack); - return 0; - } + if (packetlist.empty()) + { + *stored = true; + packetlist.push_back(rtppack); + return 0; + } - if (!validated) // still on probation - { - // Make sure that we don't buffer too much packets to avoid wasting memory - // on a bad source. Delete the packet in the queue with the lowest sequence - // number. - if (packetlist.size() == RTPINTERNALSOURCEDATA_MAXPROBATIONPACKETS) - { - RTPPacket *p = *(packetlist.begin()); - packetlist.pop_front(); - RTPDelete(p,GetMemoryManager()); - } - } + if (!validated) // still on probation + { + // Make sure that we don't buffer too much packets to avoid wasting memory + // on a bad source. Delete the packet in the queue with the lowest sequence + // number. + if (packetlist.size() == RTPINTERNALSOURCEDATA_MAXPROBATIONPACKETS) + { + RTPPacket *p = *(packetlist.begin()); + packetlist.pop_front(); + delete p; + } + } - // find the right position to insert the packet + // find the right position to insert the packet - std::list::iterator it,start; - bool done = false; - uint32_t newseqnr = rtppack->GetExtendedSequenceNumber(); + std::list::iterator it, start; + bool done = false; + uint32_t newseqnr = rtppack->GetExtendedSequenceNumber(); - it = packetlist.end(); - --it; - start = packetlist.begin(); + it = packetlist.end(); + --it; + start = packetlist.begin(); - while (!done) - { - RTPPacket *p; - uint32_t seqnr; + while (!done) + { + RTPPacket *p; + uint32_t seqnr; - p = *it; - seqnr = p->GetExtendedSequenceNumber(); - if (seqnr > newseqnr) - { - if (it != start) - --it; - else // we're at the start of the list - { - *stored = true; - done = true; - packetlist.push_front(rtppack); - } - } - else if (seqnr < newseqnr) // insert after this packet - { - ++it; - packetlist.insert(it,rtppack); - done = true; - *stored = true; - } - else // they're equal !! Drop packet - { - done = true; - } - } + p = *it; + seqnr = p->GetExtendedSequenceNumber(); + if (seqnr > newseqnr) + { + if (it != start) + --it; + else // we're at the start of the list + { + *stored = true; + done = true; + packetlist.push_front(rtppack); + } + } + else if (seqnr < newseqnr) // insert after this packet + { + ++it; + packetlist.insert(it, rtppack); + done = true; + *stored = true; + } + else // they're equal !! Drop packet + { + done = true; + } + } - return 0; + return 0; } -int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid,const uint8_t *data,size_t itemlen,const RTPTime &receivetime,bool *cnamecollis) +int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, size_t itemlen, const RTPTime &receivetime, bool *cnamecollis) { - *cnamecollis = false; + *cnamecollis = false; - stats.SetLastMessageTime(receivetime); + stats.SetLastMessageTime(receivetime); - switch(sdesid) - { - case RTCP_SDES_ID_CNAME: - { - size_t curlen; - uint8_t *oldcname; + switch (sdesid) + { + case RTCP_SDES_ID_CNAME: + { + size_t curlen; + uint8_t *oldcname; - // NOTE: we're going to make sure that the CNAME is only set once. - oldcname = SDESinf.GetCNAME(&curlen); - if (curlen == 0) - { - // if CNAME is set, the source is validated - SDESinf.SetCNAME(data,itemlen); - validated = true; - } - else // check if this CNAME is equal to the one that is already present - { - if (curlen != itemlen) - *cnamecollis = true; - else - { - if (memcmp(data,oldcname,itemlen) != 0) - *cnamecollis = true; - } - } - } - break; - case RTCP_SDES_ID_NAME: - { - size_t oldlen; + // NOTE: we're going to make sure that the CNAME is only set once. + oldcname = SDESinf.GetCNAME(&curlen); + if (curlen == 0) + { + // if CNAME is set, the source is validated + SDESinf.SetCNAME(data, itemlen); + validated = true; + } + else // check if this CNAME is equal to the one that is already present + { + if (curlen != itemlen) + *cnamecollis = true; + else + { + if (memcmp(data, oldcname, itemlen) != 0) + *cnamecollis = true; + } + } + } + break; + case RTCP_SDES_ID_NAME: + { + size_t oldlen; - SDESinf.GetName(&oldlen); - if (oldlen == 0) // Name not set - return SDESinf.SetName(data,itemlen); - } - break; - case RTCP_SDES_ID_EMAIL: - { - size_t oldlen; + SDESinf.GetName(&oldlen); + if (oldlen == 0) // Name not set + return SDESinf.SetName(data, itemlen); + } + break; + case RTCP_SDES_ID_EMAIL: + { + size_t oldlen; - SDESinf.GetEMail(&oldlen); - if (oldlen == 0) - return SDESinf.SetEMail(data,itemlen); - } - break; - case RTCP_SDES_ID_PHONE: - return SDESinf.SetPhone(data,itemlen); - case RTCP_SDES_ID_LOCATION: - return SDESinf.SetLocation(data,itemlen); - case RTCP_SDES_ID_TOOL: - { - size_t oldlen; + SDESinf.GetEMail(&oldlen); + if (oldlen == 0) + return SDESinf.SetEMail(data, itemlen); + } + break; + case RTCP_SDES_ID_PHONE: + return SDESinf.SetPhone(data, itemlen); + case RTCP_SDES_ID_LOCATION: + return SDESinf.SetLocation(data, itemlen); + case RTCP_SDES_ID_TOOL: + { + size_t oldlen; - SDESinf.GetTool(&oldlen); - if (oldlen == 0) - return SDESinf.SetTool(data,itemlen); - } - break; - case RTCP_SDES_ID_NOTE: - stats.SetLastNoteTime(receivetime); - return SDESinf.SetNote(data,itemlen); - } - return 0; + SDESinf.GetTool(&oldlen); + if (oldlen == 0) + return SDESinf.SetTool(data, itemlen); + } + break; + case RTCP_SDES_ID_NOTE: + stats.SetLastNoteTime(receivetime); + return SDESinf.SetNote(data, itemlen); + } + return 0; } #ifdef RTP_SUPPORT_SDESPRIV -int RTPInternalSourceData::ProcessPrivateSDESItem(const uint8_t *prefix,size_t prefixlen,const uint8_t *value,size_t valuelen,const RTPTime &receivetime) +int RTPInternalSourceData::ProcessPrivateSDESItem(const uint8_t *prefix, size_t prefixlen, const uint8_t *value, size_t valuelen, const RTPTime &receivetime) { - int status; + int status; - stats.SetLastMessageTime(receivetime); - status = SDESinf.SetPrivateValue(prefix,prefixlen,value,valuelen); - if (status == ERR_RTP_SDES_MAXPRIVITEMS) - return 0; // don't stop processing just because the number of items is full - return status; + stats.SetLastMessageTime(receivetime); + status = SDESinf.SetPrivateValue(prefix, prefixlen, value, valuelen); + if (status == ERR_RTP_SDES_MAXPRIVITEMS) + return 0; // don't stop processing just because the number of items is full + return status; } #endif // RTP_SUPPORT_SDESPRIV -int RTPInternalSourceData::ProcessBYEPacket(const uint8_t *reason,size_t reasonlen,const RTPTime &receivetime) +int RTPInternalSourceData::ProcessBYEPacket(const uint8_t *reason, size_t reasonlen, const RTPTime &receivetime) { - if (byereason) - { - RTPDeleteByteArray(byereason,GetMemoryManager()); - byereason = 0; - byereasonlen = 0; - } + if (byereason) + { + delete[] byereason; + byereason = 0; + byereasonlen = 0; + } - byetime = receivetime; - byereason = new uint8_t[reasonlen]; - if (byereason == 0) - return ERR_RTP_OUTOFMEM; - memcpy(byereason,reason,reasonlen); - byereasonlen = reasonlen; - receivedbye = true; - stats.SetLastMessageTime(receivetime); - return 0; + byetime = receivetime; + byereason = new uint8_t[reasonlen]; + if (byereason == 0) + return ERR_RTP_OUTOFMEM; + memcpy(byereason, reason, reasonlen); + byereasonlen = reasonlen; + receivedbye = true; + stats.SetLastMessageTime(receivetime); + return 0; } } // end namespace diff --git a/qrtplib/rtpinternalsourcedata.h b/qrtplib/rtpinternalsourcedata.h index e5ef60732..a9c3bc229 100644 --- a/qrtplib/rtpinternalsourcedata.h +++ b/qrtplib/rtpinternalsourcedata.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpinternalsourcedata.h @@ -47,86 +47,118 @@ namespace qrtplib { -class JRTPLIB_IMPORTEXPORT RTPInternalSourceData : public RTPSourceData +class JRTPLIB_IMPORTEXPORT RTPInternalSourceData: public RTPSourceData { public: - RTPInternalSourceData(uint32_t ssrc, RTPSources::ProbationType probtype, RTPMemoryManager *mgr = 0); - ~RTPInternalSourceData(); + RTPInternalSourceData(uint32_t ssrc, RTPSources::ProbationType probtype); + ~RTPInternalSourceData(); - int ProcessRTPPacket(RTPPacket *rtppack,const RTPTime &receivetime,bool *stored, RTPSources *sources); - void ProcessSenderInfo(const RTPNTPTime &ntptime,uint32_t rtptime,uint32_t packetcount, - uint32_t octetcount,const RTPTime &receivetime) { SRprevinf = SRinf; SRinf.Set(ntptime,rtptime,packetcount,octetcount,receivetime); stats.SetLastMessageTime(receivetime); } - void ProcessReportBlock(uint8_t fractionlost,int32_t lostpackets,uint32_t exthighseqnr, - uint32_t jitter,uint32_t lsr,uint32_t dlsr, - const RTPTime &receivetime) { RRprevinf = RRinf; RRinf.Set(fractionlost,lostpackets,exthighseqnr,jitter,lsr,dlsr,receivetime); stats.SetLastMessageTime(receivetime); } - void UpdateMessageTime(const RTPTime &receivetime) { stats.SetLastMessageTime(receivetime); } - int ProcessSDESItem(uint8_t sdesid,const uint8_t *data,size_t itemlen,const RTPTime &receivetime,bool *cnamecollis); + int ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, bool *stored, RTPSources *sources); + void ProcessSenderInfo(const RTPNTPTime &ntptime, uint32_t rtptime, uint32_t packetcount, uint32_t octetcount, const RTPTime &receivetime) + { + SRprevinf = SRinf; + SRinf.Set(ntptime, rtptime, packetcount, octetcount, receivetime); + stats.SetLastMessageTime(receivetime); + } + void ProcessReportBlock(uint8_t fractionlost, int32_t lostpackets, uint32_t exthighseqnr, uint32_t jitter, uint32_t lsr, uint32_t dlsr, const RTPTime &receivetime) + { + RRprevinf = RRinf; + RRinf.Set(fractionlost, lostpackets, exthighseqnr, jitter, lsr, dlsr, receivetime); + stats.SetLastMessageTime(receivetime); + } + void UpdateMessageTime(const RTPTime &receivetime) + { + stats.SetLastMessageTime(receivetime); + } + int ProcessSDESItem(uint8_t sdesid, const uint8_t *data, size_t itemlen, const RTPTime &receivetime, bool *cnamecollis); #ifdef RTP_SUPPORT_SDESPRIV - int ProcessPrivateSDESItem(const uint8_t *prefix,size_t prefixlen,const uint8_t *value,size_t valuelen,const RTPTime &receivetime); + int ProcessPrivateSDESItem(const uint8_t *prefix, size_t prefixlen, const uint8_t *value, size_t valuelen, const RTPTime &receivetime); #endif // RTP_SUPPORT_SDESPRIV - int ProcessBYEPacket(const uint8_t *reason,size_t reasonlen,const RTPTime &receivetime); + int ProcessBYEPacket(const uint8_t *reason, size_t reasonlen, const RTPTime &receivetime); - int SetRTPDataAddress(const RTPAddress *a); - int SetRTCPDataAddress(const RTPAddress *a); + int SetRTPDataAddress(const RTPAddress *a); + int SetRTCPDataAddress(const RTPAddress *a); - void ClearSenderFlag() { issender = false; } - void SentRTPPacket() { if (!ownssrc) return; RTPTime t = RTPTime::CurrentTime(); issender = true; stats.SetLastRTPPacketTime(t); stats.SetLastMessageTime(t); } - void SetOwnSSRC() { ownssrc = true; validated = true; } - void SetCSRC() { validated = true; iscsrc = true; } - void ClearNote() { SDESinf.SetNote(0,0); } + void ClearSenderFlag() + { + issender = false; + } + void SentRTPPacket() + { + if (!ownssrc) + return; + RTPTime t = RTPTime::CurrentTime(); + issender = true; + stats.SetLastRTPPacketTime(t); + stats.SetLastMessageTime(t); + } + void SetOwnSSRC() + { + ownssrc = true; + validated = true; + } + void SetCSRC() + { + validated = true; + iscsrc = true; + } + void ClearNote() + { + SDESinf.SetNote(0, 0); + } #ifdef RTP_SUPPORT_PROBATION private: - RTPSources::ProbationType probationtype; + RTPSources::ProbationType probationtype; #endif // RTP_SUPPORT_PROBATION }; inline int RTPInternalSourceData::SetRTPDataAddress(const RTPAddress *a) { - if (a == 0) - { - if (rtpaddr) - { - RTPDelete(rtpaddr,GetMemoryManager()); - rtpaddr = 0; - } - } - else - { - RTPAddress *newaddr = a->CreateCopy(GetMemoryManager()); - if (newaddr == 0) - return ERR_RTP_OUTOFMEM; + if (a == 0) + { + if (rtpaddr) + { + delete rtpaddr; + rtpaddr = 0; + } + } + else + { + RTPAddress *newaddr = a->CreateCopy(); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; - if (rtpaddr && a != rtpaddr) - RTPDelete(rtpaddr,GetMemoryManager()); - rtpaddr = newaddr; - } - isrtpaddrset = true; - return 0; + if (rtpaddr && a != rtpaddr) + delete rtpaddr; + rtpaddr = newaddr; + } + isrtpaddrset = true; + return 0; } inline int RTPInternalSourceData::SetRTCPDataAddress(const RTPAddress *a) { - if (a == 0) - { - if (rtcpaddr) - { - RTPDelete(rtcpaddr,GetMemoryManager()); - rtcpaddr = 0; - } - } - else - { - RTPAddress *newaddr = a->CreateCopy(GetMemoryManager()); - if (newaddr == 0) - return ERR_RTP_OUTOFMEM; + if (a == 0) + { + if (rtcpaddr) + { + delete rtcpaddr; + rtcpaddr = 0; + } + } + else + { + RTPAddress *newaddr = a->CreateCopy(); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; - if (rtcpaddr && a != rtcpaddr) - RTPDelete(rtcpaddr,GetMemoryManager()); - rtcpaddr = newaddr; - } - isrtcpaddrset = true; - return 0; + if (rtcpaddr && a != rtcpaddr) + delete rtcpaddr; + rtcpaddr = newaddr; + } + isrtcpaddrset = true; + return 0; } } // end namespace diff --git a/qrtplib/rtpinternalutils.h b/qrtplib/rtpinternalutils.h index dcfde5307..9ab51ff29 100644 --- a/qrtplib/rtpinternalutils.h +++ b/qrtplib/rtpinternalutils.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2011 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2011 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #ifndef RTPINTERNALUTILS_H @@ -37,22 +37,22 @@ #include "rtpconfig.h" #if defined(RTP_HAVE_SNPRINTF_S) - #include - #include - #define RTP_SNPRINTF _snprintf_s +#include +#include +#define RTP_SNPRINTF _snprintf_s #elif defined(RTP_HAVE_SNPRINTF) - #include - #include - #define RTP_SNPRINTF _snprintf +#include +#include +#define RTP_SNPRINTF _snprintf #else - #include - #define RTP_SNPRINTF snprintf +#include +#define RTP_SNPRINTF snprintf #endif #ifdef RTP_HAVE_STRNCPY_S - #define RTP_STRNCPY(dest, src, len) strncpy_s((dest), (len), (src), _TRUNCATE) +#define RTP_STRNCPY(dest, src, len) strncpy_s((dest), (len), (src), _TRUNCATE) #else - #define RTP_STRNCPY(dest, src, len) strncpy((dest), (src), (len)) +#define RTP_STRNCPY(dest, src, len) strncpy((dest), (src), (len)) #endif // RTP_HAVE_STRNCPY_S #endif // RTPINTERNALUTILS_H diff --git a/qrtplib/rtpipv4address.cpp b/qrtplib/rtpipv4address.cpp index 4d64309e3..6a204259e 100644 --- a/qrtplib/rtpipv4address.cpp +++ b/qrtplib/rtpipv4address.cpp @@ -1,74 +1,71 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpipv4address.h" -#include "rtpmemorymanager.h" namespace qrtplib { bool RTPIPv4Address::IsSameAddress(const RTPAddress *addr) const { - if (addr == 0) - return false; - if (addr->GetAddressType() != IPv4Address) - return false; + if (addr == 0) + return false; + if (addr->GetAddressType() != IPv4Address) + return false; - const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; - if (addr2->GetIP() == ip && addr2->GetPort() == port) - return true; - return false; + const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; + if (addr2->GetIP() == ip && addr2->GetPort() == port) + return true; + return false; } bool RTPIPv4Address::IsFromSameHost(const RTPAddress *addr) const { - if (addr == 0) - return false; - if (addr->GetAddressType() != IPv4Address) - return false; + if (addr == 0) + return false; + if (addr->GetAddressType() != IPv4Address) + return false; - const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; - if (addr2->GetIP() == ip) - return true; - return false; + const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; + if (addr2->GetIP() == ip) + return true; + return false; } -RTPAddress *RTPIPv4Address::CreateCopy(RTPMemoryManager *mgr) const +RTPAddress *RTPIPv4Address::CreateCopy() const { - JRTPLIB_UNUSED(mgr); // possibly unused - RTPIPv4Address *a = new RTPIPv4Address(ip,port); - return a; + RTPIPv4Address *a = new RTPIPv4Address(ip, port); + return a; } - } // end namespace diff --git a/qrtplib/rtpipv4address.h b/qrtplib/rtpipv4address.h index 864fdcfbc..6dd94af4d 100644 --- a/qrtplib/rtpipv4address.h +++ b/qrtplib/rtpipv4address.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpipv4address.h @@ -45,102 +45,127 @@ namespace qrtplib { -class RTPMemoryManager; - /** Represents an IPv4 IP address and port. * This class is used by the UDP over IPv4 transmission component. * When an RTPIPv4Address is used in one of the multicast functions of the transmitter, the port * number is ignored. When an instance is used in one of the accept or ignore functions of the * transmitter, a zero port number represents all ports for the specified IP address. */ -class JRTPLIB_IMPORTEXPORT RTPIPv4Address : public RTPAddress +class JRTPLIB_IMPORTEXPORT RTPIPv4Address: public RTPAddress { public: - /** Creates an instance with IP address \c ip and port number \c port (both - * are interpreted in host byte order), and possibly sets the RTCP multiplex flag - * (see RTPIPv4Address::UseRTCPMultiplexingOnTransmission). */ - RTPIPv4Address(uint32_t ip = 0, uint16_t port = 0,bool rtcpmux = false):RTPAddress(IPv4Address) - { - RTPIPv4Address::ip = ip; - RTPIPv4Address::port = port; - if (rtcpmux) - rtcpsendport = port; - else - rtcpsendport = port+1; - } + /** Creates an instance with IP address \c ip and port number \c port (both + * are interpreted in host byte order), and possibly sets the RTCP multiplex flag + * (see RTPIPv4Address::UseRTCPMultiplexingOnTransmission). */ + RTPIPv4Address(uint32_t ip = 0, uint16_t port = 0, bool rtcpmux = false) : + RTPAddress(IPv4Address) + { + RTPIPv4Address::ip = ip; + RTPIPv4Address::port = port; + if (rtcpmux) + rtcpsendport = port; + else + rtcpsendport = port + 1; + } - /** Creates an instance with IP address \c ip and port number \c port (both - * are interpreted in host byte order), and sets a specific port to - * send RTCP packets to (see RTPIPv4Address::GetRTCPSendPort). */ - RTPIPv4Address(uint32_t ip, uint16_t port, uint16_t rtcpsendport):RTPAddress(IPv4Address) - { - RTPIPv4Address::ip = ip; - RTPIPv4Address::port = port; - RTPIPv4Address::rtcpsendport = rtcpsendport; - } + /** Creates an instance with IP address \c ip and port number \c port (both + * are interpreted in host byte order), and sets a specific port to + * send RTCP packets to (see RTPIPv4Address::GetRTCPSendPort). */ + RTPIPv4Address(uint32_t ip, uint16_t port, uint16_t rtcpsendport) : + RTPAddress(IPv4Address) + { + RTPIPv4Address::ip = ip; + RTPIPv4Address::port = port; + RTPIPv4Address::rtcpsendport = rtcpsendport; + } - /** Creates an instance with IP address \c ip and port number \c port (\c port is - * interpreted in host byte order) and possibly sets the RTCP multiplex flag - * (see RTPIPv4Address::UseRTCPMultiplexingOnTransmission). */ - RTPIPv4Address(const uint8_t ip[4],uint16_t port = 0,bool rtcpmux = false):RTPAddress(IPv4Address) - { - RTPIPv4Address::ip = (uint32_t)ip[3]; - RTPIPv4Address::ip |= (((uint32_t)ip[2])<<8); - RTPIPv4Address::ip |= (((uint32_t)ip[1])<<16); - RTPIPv4Address::ip |= (((uint32_t)ip[0])<<24); + /** Creates an instance with IP address \c ip and port number \c port (\c port is + * interpreted in host byte order) and possibly sets the RTCP multiplex flag + * (see RTPIPv4Address::UseRTCPMultiplexingOnTransmission). */ + RTPIPv4Address(const uint8_t ip[4], uint16_t port = 0, bool rtcpmux = false) : + RTPAddress(IPv4Address) + { + RTPIPv4Address::ip = (uint32_t) ip[3]; + RTPIPv4Address::ip |= (((uint32_t) ip[2]) << 8); + RTPIPv4Address::ip |= (((uint32_t) ip[1]) << 16); + RTPIPv4Address::ip |= (((uint32_t) ip[0]) << 24); - RTPIPv4Address::port = port; - if (rtcpmux) - rtcpsendport = port; - else - rtcpsendport = port+1; - } + RTPIPv4Address::port = port; + if (rtcpmux) + rtcpsendport = port; + else + rtcpsendport = port + 1; + } - /** Creates an instance with IP address \c ip and port number \c port (both - * are interpreted in host byte order), and sets a specific port to - * send RTCP packets to (see RTPIPv4Address::GetRTCPSendPort). */ - RTPIPv4Address(const uint8_t ip[4],uint16_t port,uint16_t rtcpsendport):RTPAddress(IPv4Address) - { - RTPIPv4Address::ip = (uint32_t)ip[3]; - RTPIPv4Address::ip |= (((uint32_t)ip[2])<<8); - RTPIPv4Address::ip |= (((uint32_t)ip[1])<<16); - RTPIPv4Address::ip |= (((uint32_t)ip[0])<<24); + /** Creates an instance with IP address \c ip and port number \c port (both + * are interpreted in host byte order), and sets a specific port to + * send RTCP packets to (see RTPIPv4Address::GetRTCPSendPort). */ + RTPIPv4Address(const uint8_t ip[4], uint16_t port, uint16_t rtcpsendport) : + RTPAddress(IPv4Address) + { + RTPIPv4Address::ip = (uint32_t) ip[3]; + RTPIPv4Address::ip |= (((uint32_t) ip[2]) << 8); + RTPIPv4Address::ip |= (((uint32_t) ip[1]) << 16); + RTPIPv4Address::ip |= (((uint32_t) ip[0]) << 24); - RTPIPv4Address::port = port; - RTPIPv4Address::rtcpsendport = rtcpsendport; - } + RTPIPv4Address::port = port; + RTPIPv4Address::rtcpsendport = rtcpsendport; + } - ~RTPIPv4Address() { } + ~RTPIPv4Address() + { + } - /** Sets the IP address for this instance to \c ip which is assumed to be in host byte order. */ - void SetIP(uint32_t ip) { RTPIPv4Address::ip = ip; } + /** Sets the IP address for this instance to \c ip which is assumed to be in host byte order. */ + void SetIP(uint32_t ip) + { + RTPIPv4Address::ip = ip; + } - /** Sets the IP address of this instance to \c ip. */ - void SetIP(const uint8_t ip[4]) { RTPIPv4Address::ip = (uint32_t)ip[3]; RTPIPv4Address::ip |= (((uint32_t)ip[2])<<8); RTPIPv4Address::ip |= (((uint32_t)ip[1])<<16); RTPIPv4Address::ip |= (((uint32_t)ip[0])<<24); } + /** Sets the IP address of this instance to \c ip. */ + void SetIP(const uint8_t ip[4]) + { + RTPIPv4Address::ip = (uint32_t) ip[3]; + RTPIPv4Address::ip |= (((uint32_t) ip[2]) << 8); + RTPIPv4Address::ip |= (((uint32_t) ip[1]) << 16); + RTPIPv4Address::ip |= (((uint32_t) ip[0]) << 24); + } - /** Sets the port number for this instance to \c port which is interpreted in host byte order. */ - void SetPort(uint16_t port) { RTPIPv4Address::port = port; } + /** Sets the port number for this instance to \c port which is interpreted in host byte order. */ + void SetPort(uint16_t port) + { + RTPIPv4Address::port = port; + } - /** Returns the IP address contained in this instance in host byte order. */ - uint32_t GetIP() const { return ip; } + /** Returns the IP address contained in this instance in host byte order. */ + uint32_t GetIP() const + { + return ip; + } - /** Returns the port number of this instance in host byte order. */ - uint16_t GetPort() const { return port; } + /** Returns the port number of this instance in host byte order. */ + uint16_t GetPort() const + { + return port; + } - /** For outgoing packets, this indicates to which port RTCP packets will be sent (can, - * be the same port as the RTP packets in case RTCP multiplexing is used). */ - uint16_t GetRTCPSendPort() const { return rtcpsendport; } + /** For outgoing packets, this indicates to which port RTCP packets will be sent (can, + * be the same port as the RTP packets in case RTCP multiplexing is used). */ + uint16_t GetRTCPSendPort() const + { + return rtcpsendport; + } - RTPAddress *CreateCopy(RTPMemoryManager *mgr) const; + RTPAddress *CreateCopy() const; - // Note that these functions are only used for received packets, and for those - // the rtcpsendport variable is not important and should be ignored. - bool IsSameAddress(const RTPAddress *addr) const; - bool IsFromSameHost(const RTPAddress *addr) const; + // Note that these functions are only used for received packets, and for those + // the rtcpsendport variable is not important and should be ignored. + bool IsSameAddress(const RTPAddress *addr) const; + bool IsFromSameHost(const RTPAddress *addr) const; private: - uint32_t ip; - uint16_t port; - uint16_t rtcpsendport; + uint32_t ip; + uint16_t port; + uint16_t rtcpsendport; }; } // end namespace diff --git a/qrtplib/rtpipv4destination.cpp b/qrtplib/rtpipv4destination.cpp index acfd786a1..ad5047baf 100644 --- a/qrtplib/rtpipv4destination.cpp +++ b/qrtplib/rtpipv4destination.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2011 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2011 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpipv4destination.h" #include "rtpinternalutils.h" @@ -38,14 +38,13 @@ namespace qrtplib std::string RTPIPv4Destination::GetDestinationString() const { - char str[24]; - uint32_t ip = GetIP(); - uint16_t portbase = ntohs(GetRTPPort_NBO()); + char str[24]; + uint32_t ip = GetIP(); + uint16_t portbase = ntohs(GetRTPPort_NBO()); - RTP_SNPRINTF(str,24,"%d.%d.%d.%d:%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF),(int)(portbase)); - return std::string(str); + RTP_SNPRINTF(str, 24, "%d.%d.%d.%d:%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF), (int) (portbase)); + return std::string(str); } } // end namespace - diff --git a/qrtplib/rtpipv4destination.h b/qrtplib/rtpipv4destination.h index 457735ca8..746a7d017 100644 --- a/qrtplib/rtpipv4destination.h +++ b/qrtplib/rtpipv4destination.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpipv4destination.h @@ -42,9 +42,9 @@ #include "rtptypes.h" #include "rtpipv4address.h" #ifndef RTP_SOCKETTYPE_WINSOCK - #include - #include - #include +#include +#include +#include #endif // RTP_SOCKETTYPE_WINSOCK #include #include @@ -55,61 +55,79 @@ namespace qrtplib class JRTPLIB_IMPORTEXPORT RTPIPv4Destination { public: - RTPIPv4Destination() - { - ip = 0; - memset(&rtpaddr,0,sizeof(struct sockaddr_in)); - memset(&rtcpaddr,0,sizeof(struct sockaddr_in)); - } + RTPIPv4Destination() + { + ip = 0; + memset(&rtpaddr, 0, sizeof(struct sockaddr_in)); + memset(&rtcpaddr, 0, sizeof(struct sockaddr_in)); + } - RTPIPv4Destination(uint32_t ip,uint16_t rtpport,uint16_t rtcpport) - { - memset(&rtpaddr,0,sizeof(struct sockaddr_in)); - memset(&rtcpaddr,0,sizeof(struct sockaddr_in)); + RTPIPv4Destination(uint32_t ip, uint16_t rtpport, uint16_t rtcpport) + { + memset(&rtpaddr, 0, sizeof(struct sockaddr_in)); + memset(&rtcpaddr, 0, sizeof(struct sockaddr_in)); - rtpaddr.sin_family = AF_INET; - rtpaddr.sin_port = htons(rtpport); - rtpaddr.sin_addr.s_addr = htonl(ip); + rtpaddr.sin_family = AF_INET; + rtpaddr.sin_port = htons(rtpport); + rtpaddr.sin_addr.s_addr = htonl(ip); - rtcpaddr.sin_family = AF_INET; - rtcpaddr.sin_port = htons(rtcpport); - rtcpaddr.sin_addr.s_addr = htonl(ip); + rtcpaddr.sin_family = AF_INET; + rtcpaddr.sin_port = htons(rtcpport); + rtcpaddr.sin_addr.s_addr = htonl(ip); - RTPIPv4Destination::ip = ip; - } + RTPIPv4Destination::ip = ip; + } - bool operator==(const RTPIPv4Destination &src) const - { - if (rtpaddr.sin_addr.s_addr == src.rtpaddr.sin_addr.s_addr && rtpaddr.sin_port == src.rtpaddr.sin_port) - return true; - return false; - } - uint32_t GetIP() const { return ip; } - // nbo = network byte order - uint32_t GetIP_NBO() const { return rtpaddr.sin_addr.s_addr; } - uint16_t GetRTPPort_NBO() const { return rtpaddr.sin_port; } - uint16_t GetRTCPPort_NBO() const { return rtcpaddr.sin_port; } - const struct sockaddr_in *GetRTPSockAddr() const { return &rtpaddr; } - const struct sockaddr_in *GetRTCPSockAddr() const { return &rtcpaddr; } - std::string GetDestinationString() const; + bool operator==(const RTPIPv4Destination &src) const + { + if (rtpaddr.sin_addr.s_addr == src.rtpaddr.sin_addr.s_addr && rtpaddr.sin_port == src.rtpaddr.sin_port) + return true; + return false; + } + uint32_t GetIP() const + { + return ip; + } + // nbo = network byte order + uint32_t GetIP_NBO() const + { + return rtpaddr.sin_addr.s_addr; + } + uint16_t GetRTPPort_NBO() const + { + return rtpaddr.sin_port; + } + uint16_t GetRTCPPort_NBO() const + { + return rtcpaddr.sin_port; + } + const struct sockaddr_in *GetRTPSockAddr() const + { + return &rtpaddr; + } + const struct sockaddr_in *GetRTCPSockAddr() const + { + return &rtcpaddr; + } + std::string GetDestinationString() const; - static bool AddressToDestination(const RTPAddress &addr, RTPIPv4Destination &dest) - { - if (addr.GetAddressType() != RTPAddress::IPv4Address) - return false; + static bool AddressToDestination(const RTPAddress &addr, RTPIPv4Destination &dest) + { + if (addr.GetAddressType() != RTPAddress::IPv4Address) + return false; - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - uint16_t rtpport = address.GetPort(); - uint16_t rtcpport = address.GetRTCPSendPort(); + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + uint16_t rtpport = address.GetPort(); + uint16_t rtcpport = address.GetRTCPSendPort(); - dest = RTPIPv4Destination(address.GetIP(),rtpport,rtcpport); - return true; - } + dest = RTPIPv4Destination(address.GetIP(), rtpport, rtcpport); + return true; + } private: - uint32_t ip; - struct sockaddr_in rtpaddr; - struct sockaddr_in rtcpaddr; + uint32_t ip; + struct sockaddr_in rtpaddr; + struct sockaddr_in rtcpaddr; }; } // end namespace diff --git a/qrtplib/rtpkeyhashtable.h b/qrtplib/rtpkeyhashtable.h index 5e51ad876..ef0386b16 100644 --- a/qrtplib/rtpkeyhashtable.h +++ b/qrtplib/rtpkeyhashtable.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpkeyhashtable.h @@ -40,269 +40,297 @@ #include "rtpconfig.h" #include "rtperrors.h" -#include "rtpmemoryobject.h" namespace qrtplib { -template -class RTPKeyHashTable : public RTPMemoryObject +template +class RTPKeyHashTable { public: - RTPKeyHashTable(RTPMemoryManager *mgr = 0,int memtype = RTPMEM_TYPE_OTHER); - ~RTPKeyHashTable() { Clear(); } + RTPKeyHashTable(); + ~RTPKeyHashTable() + { + Clear(); + } - void GotoFirstElement() { curhashelem = firsthashelem; } - void GotoLastElement() { curhashelem = lasthashelem; } - bool HasCurrentElement() { return (curhashelem == 0)?false:true; } - int DeleteCurrentElement(); - Element &GetCurrentElement() { return curhashelem->GetElement(); } - Key &GetCurrentKey() { return curhashelem->GetKey(); } - int GotoElement(const Key &k); - bool HasElement(const Key &k); - void GotoNextElement(); - void GotoPreviousElement(); - void Clear(); + void GotoFirstElement() + { + curhashelem = firsthashelem; + } + void GotoLastElement() + { + curhashelem = lasthashelem; + } + bool HasCurrentElement() + { + return (curhashelem == 0) ? false : true; + } + int DeleteCurrentElement(); + Element &GetCurrentElement() + { + return curhashelem->GetElement(); + } + Key &GetCurrentKey() + { + return curhashelem->GetKey(); + } + int GotoElement(const Key &k); + bool HasElement(const Key &k); + void GotoNextElement(); + void GotoPreviousElement(); + void Clear(); - int AddElement(const Key &k,const Element &elem); - int DeleteElement(const Key &k); + int AddElement(const Key &k, const Element &elem); + int DeleteElement(const Key &k); private: - class HashElement - { - public: - HashElement(const Key &k,const Element &e,int index):key(k),element(e) { hashprev = 0; hashnext = 0; listnext = 0; listprev = 0; hashindex = index; } - int GetHashIndex() { return hashindex; } - Key &GetKey() { return key; } - Element &GetElement() { return element; } + class HashElement + { + public: + HashElement(const Key &k, const Element &e, int index) : + key(k), element(e) + { + hashprev = 0; + hashnext = 0; + listnext = 0; + listprev = 0; + hashindex = index; + } + int GetHashIndex() + { + return hashindex; + } + Key &GetKey() + { + return key; + } + Element &GetElement() + { + return element; + } - private: - int hashindex; - Key key; - Element element; - public: - HashElement *hashprev,*hashnext; - HashElement *listprev,*listnext; - }; + private: + int hashindex; + Key key; + Element element; + public: + HashElement *hashprev, *hashnext; + HashElement *listprev, *listnext; + }; - HashElement *table[hashsize]; - HashElement *firsthashelem,*lasthashelem; - HashElement *curhashelem; -#ifdef RTP_SUPPORT_MEMORYMANAGEMENT - int memorytype; -#endif // RTP_SUPPORT_MEMORYMANAGEMENT + HashElement *table[hashsize]; + HashElement *firsthashelem, *lasthashelem; + HashElement *curhashelem; }; -template -inline RTPKeyHashTable::RTPKeyHashTable(RTPMemoryManager *mgr,int memtype) : RTPMemoryObject(mgr) +template +inline RTPKeyHashTable::RTPKeyHashTable() { - JRTPLIB_UNUSED(memtype); // possibly unused - - for (int i = 0 ; i < hashsize ; i++) - table[i] = 0; - firsthashelem = 0; - lasthashelem = 0; -#ifdef RTP_SUPPORT_MEMORYMANAGEMENT - memorytype = memtype; -#endif // RTP_SUPPORT_MEMORYMANAGEMENT + for (int i = 0; i < hashsize; i++) + table[i] = 0; + firsthashelem = 0; + lasthashelem = 0; } -template -inline int RTPKeyHashTable::DeleteCurrentElement() +template +inline int RTPKeyHashTable::DeleteCurrentElement() { - if (curhashelem) - { - HashElement *tmp1,*tmp2; - int index; + if (curhashelem) + { + HashElement *tmp1, *tmp2; + int index; - // First, relink elements in current hash bucket + // First, relink elements in current hash bucket - index = curhashelem->GetHashIndex(); - tmp1 = curhashelem->hashprev; - tmp2 = curhashelem->hashnext; - if (tmp1 == 0) // no previous element in hash bucket - { - table[index] = tmp2; - if (tmp2 != 0) - tmp2->hashprev = 0; - } - else // there is a previous element in the hash bucket - { - tmp1->hashnext = tmp2; - if (tmp2 != 0) - tmp2->hashprev = tmp1; - } + index = curhashelem->GetHashIndex(); + tmp1 = curhashelem->hashprev; + tmp2 = curhashelem->hashnext; + if (tmp1 == 0) // no previous element in hash bucket + { + table[index] = tmp2; + if (tmp2 != 0) + tmp2->hashprev = 0; + } + else // there is a previous element in the hash bucket + { + tmp1->hashnext = tmp2; + if (tmp2 != 0) + tmp2->hashprev = tmp1; + } - // Relink elements in list + // Relink elements in list - tmp1 = curhashelem->listprev; - tmp2 = curhashelem->listnext; - if (tmp1 == 0) // curhashelem is first in list - { - firsthashelem = tmp2; - if (tmp2 != 0) - tmp2->listprev = 0; - else // curhashelem is also last in list - lasthashelem = 0; - } - else - { - tmp1->listnext = tmp2; - if (tmp2 != 0) - tmp2->listprev = tmp1; - else // curhashelem is last in list - lasthashelem = tmp1; - } + tmp1 = curhashelem->listprev; + tmp2 = curhashelem->listnext; + if (tmp1 == 0) // curhashelem is first in list + { + firsthashelem = tmp2; + if (tmp2 != 0) + tmp2->listprev = 0; + else + // curhashelem is also last in list + lasthashelem = 0; + } + else + { + tmp1->listnext = tmp2; + if (tmp2 != 0) + tmp2->listprev = tmp1; + else + // curhashelem is last in list + lasthashelem = tmp1; + } - // finally, with everything being relinked, we can delete curhashelem - RTPDelete(curhashelem,GetMemoryManager()); - curhashelem = tmp2; // Set to next element in list - } - else - return ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT; - return 0; + // finally, with everything being relinked, we can delete curhashelem + delete curhashelem; + curhashelem = tmp2; // Set to next element in list + } + else + return ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT; + return 0; } -template -inline int RTPKeyHashTable::GotoElement(const Key &k) +template +inline int RTPKeyHashTable::GotoElement(const Key &k) { - int index; - bool found; + int index; + bool found; - index = GetIndex::GetIndex(k); - if (index >= hashsize) - return ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; - curhashelem = table[index]; - found = false; - while(!found && curhashelem != 0) - { - if (curhashelem->GetKey() == k) - found = true; - else - curhashelem = curhashelem->hashnext; - } - if (!found) - return ERR_RTP_KEYHASHTABLE_KEYNOTFOUND; - return 0; + curhashelem = table[index]; + found = false; + while (!found && curhashelem != 0) + { + if (curhashelem->GetKey() == k) + found = true; + else + curhashelem = curhashelem->hashnext; + } + if (!found) + return ERR_RTP_KEYHASHTABLE_KEYNOTFOUND; + return 0; } -template -inline bool RTPKeyHashTable::HasElement(const Key &k) +template +inline bool RTPKeyHashTable::HasElement(const Key &k) { - int index; - bool found; - HashElement *tmp; + int index; + bool found; + HashElement *tmp; - index = GetIndex::GetIndex(k); - if (index >= hashsize) - return false; + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return false; - tmp = table[index]; - found = false; - while(!found && tmp != 0) - { - if (tmp->GetKey() == k) - found = true; - else - tmp = tmp->hashnext; - } - return found; + tmp = table[index]; + found = false; + while (!found && tmp != 0) + { + if (tmp->GetKey() == k) + found = true; + else + tmp = tmp->hashnext; + } + return found; } -template -inline void RTPKeyHashTable::GotoNextElement() +template +inline void RTPKeyHashTable::GotoNextElement() { - if (curhashelem) - curhashelem = curhashelem->listnext; + if (curhashelem) + curhashelem = curhashelem->listnext; } -template -inline void RTPKeyHashTable::GotoPreviousElement() +template +inline void RTPKeyHashTable::GotoPreviousElement() { - if (curhashelem) - curhashelem = curhashelem->listprev; + if (curhashelem) + curhashelem = curhashelem->listprev; } -template -inline void RTPKeyHashTable::Clear() +template +inline void RTPKeyHashTable::Clear() { - HashElement *tmp1,*tmp2; + HashElement *tmp1, *tmp2; - for (int i = 0 ; i < hashsize ; i++) - table[i] = 0; + for (int i = 0; i < hashsize; i++) + table[i] = 0; - tmp1 = firsthashelem; - while (tmp1 != 0) - { - tmp2 = tmp1->listnext; - RTPDelete(tmp1,GetMemoryManager()); - tmp1 = tmp2; - } - firsthashelem = 0; - lasthashelem = 0; + tmp1 = firsthashelem; + while (tmp1 != 0) + { + tmp2 = tmp1->listnext; + delete tmp1; + tmp1 = tmp2; + } + firsthashelem = 0; + lasthashelem = 0; } -template -inline int RTPKeyHashTable::AddElement(const Key &k,const Element &elem) +template +inline int RTPKeyHashTable::AddElement(const Key &k, const Element &elem) { - int index; - bool found; - HashElement *e,*newelem; + int index; + bool found; + HashElement *e, *newelem; - index = GetIndex::GetIndex(k); - if (index >= hashsize) - return ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; - e = table[index]; - found = false; - while(!found && e != 0) - { - if (e->GetKey() == k) - found = true; - else - e = e->hashnext; - } - if (found) - return ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS; + e = table[index]; + found = false; + while (!found && e != 0) + { + if (e->GetKey() == k) + found = true; + else + e = e->hashnext; + } + if (found) + return ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS; - // Okay, the key doesn't exist, so we can add the new element in the hash table + // Okay, the key doesn't exist, so we can add the new element in the hash table - newelem = new HashElement(k,elem,index); - if (newelem == 0) - return ERR_RTP_OUTOFMEM; + newelem = new HashElement(k, elem, index); + if (newelem == 0) + return ERR_RTP_OUTOFMEM; - e = table[index]; - table[index] = newelem; - newelem->hashnext = e; - if (e != 0) - e->hashprev = newelem; + e = table[index]; + table[index] = newelem; + newelem->hashnext = e; + if (e != 0) + e->hashprev = newelem; - // Now, we still got to add it to the linked list + // Now, we still got to add it to the linked list - if (firsthashelem == 0) - { - firsthashelem = newelem; - lasthashelem = newelem; - } - else // there already are some elements in the list - { - lasthashelem->listnext = newelem; - newelem->listprev = lasthashelem; - lasthashelem = newelem; - } - return 0; + if (firsthashelem == 0) + { + firsthashelem = newelem; + lasthashelem = newelem; + } + else // there already are some elements in the list + { + lasthashelem->listnext = newelem; + newelem->listprev = lasthashelem; + lasthashelem = newelem; + } + return 0; } -template -inline int RTPKeyHashTable::DeleteElement(const Key &k) +template +inline int RTPKeyHashTable::DeleteElement(const Key &k) { - int status; + int status; - status = GotoElement(k); - if (status < 0) - return status; - return DeleteCurrentElement(); + status = GotoElement(k); + if (status < 0) + return status; + return DeleteCurrentElement(); } } // end namespace diff --git a/qrtplib/rtplibraryversion.cpp b/qrtplib/rtplibraryversion.cpp index f282c8df4..447cef4b7 100644 --- a/qrtplib/rtplibraryversion.cpp +++ b/qrtplib/rtplibraryversion.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtplibraryversion.h" #include "rtpdefines.h" @@ -41,16 +41,16 @@ namespace qrtplib RTPLibraryVersion RTPLibraryVersion::GetVersion() { - return RTPLibraryVersion(JRTPLIB_VERSION_MAJOR, JRTPLIB_VERSION_MINOR, JRTPLIB_VERSION_DEBUG); + return RTPLibraryVersion(JRTPLIB_VERSION_MAJOR, JRTPLIB_VERSION_MINOR, JRTPLIB_VERSION_DEBUG); } std::string RTPLibraryVersion::GetVersionString() const { - char str[16]; + char str[16]; - RTP_SNPRINTF(str,16,"%d.%d.%d",majornr,minornr,debugnr); + RTP_SNPRINTF(str, 16, "%d.%d.%d", majornr, minornr, debugnr); - return std::string(str); + return std::string(str); } } // end namespace diff --git a/qrtplib/rtplibraryversion.h b/qrtplib/rtplibraryversion.h index d26d6273a..6c7a2706d 100644 --- a/qrtplib/rtplibraryversion.h +++ b/qrtplib/rtplibraryversion.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtplibraryversion.h @@ -51,24 +51,38 @@ namespace qrtplib class JRTPLIB_IMPORTEXPORT RTPLibraryVersion { public: - /** Returns an instance of RTPLibraryVersion describing the version of the library. */ - static RTPLibraryVersion GetVersion(); + /** Returns an instance of RTPLibraryVersion describing the version of the library. */ + static RTPLibraryVersion GetVersion(); private: - RTPLibraryVersion(int major,int minor,int debug) { majornr = major; minornr = minor; debugnr = debug; } + RTPLibraryVersion(int major, int minor, int debug) + { + majornr = major; + minornr = minor; + debugnr = debug; + } public: - /** Returns the major version number. */ - int GetMajorNumber() const { return majornr; } + /** Returns the major version number. */ + int GetMajorNumber() const + { + return majornr; + } - /** Returns the minor version number. */ - int GetMinorNumber() const { return minornr; } + /** Returns the minor version number. */ + int GetMinorNumber() const + { + return minornr; + } - /** Returns the debug version number. */ - int GetDebugNumber() const { return debugnr; } + /** Returns the debug version number. */ + int GetDebugNumber() const + { + return debugnr; + } - /** Returns a string describing the library version. */ - std::string GetVersionString() const; + /** Returns a string describing the library version. */ + std::string GetVersionString() const; private: - int debugnr,minornr,majornr; + int debugnr, minornr, majornr; }; } // end namespace diff --git a/qrtplib/rtplibraryversioninternal.h b/qrtplib/rtplibraryversioninternal.h index 03e3560f4..76eab4dc3 100644 --- a/qrtplib/rtplibraryversioninternal.h +++ b/qrtplib/rtplibraryversioninternal.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtplibraryversioninternal.h diff --git a/qrtplib/rtpmemorymanager.h b/qrtplib/rtpmemorymanager.h deleted file mode 100644 index 4484a74fa..000000000 --- a/qrtplib/rtpmemorymanager.h +++ /dev/null @@ -1,245 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtpmemorymanager.h - */ - -#ifndef RTPMEMORYMANAGER_H - -#define RTPMEMORYMANAGER_H - -#include "rtpconfig.h" -#include "rtptypes.h" - -/** Used to indicate a general kind of memory block. */ -#define RTPMEM_TYPE_OTHER 0 - -/** Buffer to store an incoming RTP packet. */ -#define RTPMEM_TYPE_BUFFER_RECEIVEDRTPPACKET 1 - -/** Buffer to store an incoming RTCP packet. */ -#define RTPMEM_TYPE_BUFFER_RECEIVEDRTCPPACKET 2 - -/** Buffer to store an RTCP APP packet. */ -#define RTPMEM_TYPE_BUFFER_RTCPAPPPACKET 3 - -/** Buffer to store an RTCP BYE packet. */ -#define RTPMEM_TYPE_BUFFER_RTCPBYEPACKET 4 - -/** Buffer to store a BYE reason. */ -#define RTPMEM_TYPE_BUFFER_RTCPBYEREASON 5 - -/** Buffer to store an RTCP compound packet. */ -#define RTPMEM_TYPE_BUFFER_RTCPCOMPOUNDPACKET 6 - -/** Buffer to store an SDES block. */ -#define RTPMEM_TYPE_BUFFER_RTCPSDESBLOCK 7 - -/** Buffer to store an RTP packet. */ -#define RTPMEM_TYPE_BUFFER_RTPPACKET 8 - -/** Buffer used by an RTPPacketBuilder instance. */ -#define RTPMEM_TYPE_BUFFER_RTPPACKETBUILDERBUFFER 9 - -/** Buffer to store an SDES item. */ -#define RTPMEM_TYPE_BUFFER_SDESITEM 10 - -/** Hash element used in the accept/ignore table. */ -#define RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT 11 - -/** Buffer to store a PortInfo instance, used by the UDP over IPv4 and IPv6 transmitters. */ -#define RTPMEM_TYPE_CLASS_ACCEPTIGNOREPORTINFO 12 - -/** Buffer to store a HashElement instance for the destination hash table. */ -#define RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT 13 - -/** Buffer to store a HashElement instance for the multicast hash table. */ -#define RTPMEM_TYPE_CLASS_MULTICASTHASHELEMENT 14 - -/** Buffer to store an instance of RTCPAPPPacket. */ -#define RTPMEM_TYPE_CLASS_RTCPAPPPACKET 15 - -/** Buffer to store an instance of RTCPBYEPacket. */ -#define RTPMEM_TYPE_CLASS_RTCPBYEPACKET 16 - -/** Buffer to store an instance of RTCPCompoundPacketBuilder. */ -#define RTPMEM_TYPE_CLASS_RTCPCOMPOUNDPACKETBUILDER 17 - -/** Buffer to store an RTCPReceiverReport instance. */ -#define RTPMEM_TYPE_CLASS_RTCPRECEIVERREPORT 18 - -/** Buffer to store an instance of RTCPRRPacket. */ -#define RTPMEM_TYPE_CLASS_RTCPRRPACKET 19 - -/** Buffer to store an instance of RTCPSDESPacket. */ -#define RTPMEM_TYPE_CLASS_RTCPSDESPACKET 20 - -/** Buffer to store an instance of RTCPSRPacket. */ -#define RTPMEM_TYPE_CLASS_RTCPSRPACKET 21 - -/** Buffer to store an instance of RTCPUnknownPacket. */ -#define RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET 22 - -/** Buffer to store an instance of an RTPAddress derived class. */ -#define RTPMEM_TYPE_CLASS_RTPADDRESS 23 - -/** Buffer to store an instance of RTPInternalSourceData. */ -#define RTPMEM_TYPE_CLASS_RTPINTERNALSOURCEDATA 24 - -/** Buffer to store an RTPPacket instance. */ -#define RTPMEM_TYPE_CLASS_RTPPACKET 25 - -/** Buffer to store an RTPPollThread instance. */ -#define RTPMEM_TYPE_CLASS_RTPPOLLTHREAD 26 - -/** Buffer to store an RTPRawPacket instance. */ -#define RTPMEM_TYPE_CLASS_RTPRAWPACKET 27 - -/** Buffer to store an RTPTransmissionInfo derived class. */ -#define RTPMEM_TYPE_CLASS_RTPTRANSMISSIONINFO 28 - -/** Buffer to store an RTPTransmitter derived class. */ -#define RTPMEM_TYPE_CLASS_RTPTRANSMITTER 29 - -/** Buffer to store an SDESPrivateItem instance. */ -#define RTPMEM_TYPE_CLASS_SDESPRIVATEITEM 30 - -/** Buffer to store an SDESSource instance. */ -#define RTPMEM_TYPE_CLASS_SDESSOURCE 31 - -/** Buffer to store a HashElement instance for the source table. */ -#define RTPMEM_TYPE_CLASS_SOURCETABLEHASHELEMENT 32 - -/** Buffer that's used when encrypting a packet. */ -#define RTPMEM_TYPE_BUFFER_SRTPDATA 33 - -namespace qrtplib -{ - -/** A memory manager. */ -class JRTPLIB_IMPORTEXPORT RTPMemoryManager -{ -public: - RTPMemoryManager() { } - virtual ~RTPMemoryManager() { } - - /** Called to allocate \c numbytes of memory. - * Called to allocate \c numbytes of memory. The \c memtype parameter - * indicates what the purpose of the memory block is. Relevant values - * can be found in rtpmemorymanager.h . Note that the types starting with - * \c RTPMEM_TYPE_CLASS indicate fixed size buffers and that types starting - * with \c RTPMEM_TYPE_BUFFER indicate variable size buffers. - */ - virtual void *AllocateBuffer(size_t numbytes, int memtype) = 0; - - /** Frees the previously allocated memory block \c buffer */ - virtual void FreeBuffer(void *buffer) = 0; -}; - -} // end namespace - -#ifdef RTP_SUPPORT_MEMORYMANAGEMENT - -#include - -inline void *operator new(size_t numbytes, qrtplib::RTPMemoryManager *mgr, int memtype) -{ - if (mgr == 0) - return operator new(numbytes); - return mgr->AllocateBuffer(numbytes,memtype); -} - -inline void operator delete(void *buffer, qrtplib::RTPMemoryManager *mgr, int memtype) -{ - JRTPLIB_UNUSED(memtype); - if (mgr == 0) - operator delete(buffer); - else - mgr->FreeBuffer(buffer); -} - -#ifdef RTP_HAVE_ARRAYALLOC -inline void *operator new[](size_t numbytes, qrtplib::RTPMemoryManager *mgr, int memtype) -{ - if (mgr == 0) - return operator new[](numbytes); - return mgr->AllocateBuffer(numbytes,memtype); -} - -inline void operator delete[](void *buffer, qrtplib::RTPMemoryManager *mgr, int memtype) -{ - JRTPLIB_UNUSED(memtype); - if (mgr == 0) - operator delete[](buffer); - else - mgr->FreeBuffer(buffer); -} -#endif // RTP_HAVE_ARRAYALLOC - -namespace qrtplib -{ - -inline void RTPDeleteByteArray(uint8_t *buf, RTPMemoryManager *mgr) -{ - if (mgr == 0) - delete [] buf; - else - mgr->FreeBuffer(buf); -} - -template -inline void RTPDelete(ClassName *obj, RTPMemoryManager *mgr) -{ - if (mgr == 0) - delete obj; - else - { - obj->~ClassName(); - mgr->FreeBuffer(obj); - } -} - -} // end namespace - -#define RTPNew(a,b) new(a,b) - -#else - -//#define RTPNew(a,b) new -#define RTPDelete(a,b) delete a -#define RTPDeleteByteArray(a,b) delete [] a; - -#endif // RTP_SUPPORT_MEMORYMANAGEMENT - -#endif // RTPMEMORYMANAGER_H - diff --git a/qrtplib/rtpmemoryobject.h b/qrtplib/rtpmemoryobject.h deleted file mode 100644 index 1053639d1..000000000 --- a/qrtplib/rtpmemoryobject.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtpmemoryobject.h - */ - -#ifndef RTPMEMORYOBJECT_H - -#define RTPMEMORYOBJECT_H - -#include "rtpconfig.h" -#include "rtpmemorymanager.h" - -namespace qrtplib -{ - -class JRTPLIB_IMPORTEXPORT RTPMemoryObject -{ -protected: -#ifdef RTP_SUPPORT_MEMORYMANAGEMENT - RTPMemoryObject(RTPMemoryManager *memmgr) : mgr(memmgr) { } -#else - RTPMemoryObject(RTPMemoryManager *memmgr) { JRTPLIB_UNUSED(memmgr); } -#endif // RTP_SUPPORT_MEMORYMANAGEMENT - virtual ~RTPMemoryObject() { } - -#ifdef RTP_SUPPORT_MEMORYMANAGEMENT - RTPMemoryManager *GetMemoryManager() const { return mgr; } - void SetMemoryManager(RTPMemoryManager *m) { mgr = m; } -#else - RTPMemoryManager *GetMemoryManager() const { return 0; } - void SetMemoryManager(RTPMemoryManager *m) { JRTPLIB_UNUSED(m); } -#endif // RTP_SUPPORT_MEMORYMANAGEMENT - -#ifdef RTP_SUPPORT_MEMORYMANAGEMENT -private: - RTPMemoryManager *mgr; -#endif // RTP_SUPPORT_MEMORYMANAGEMENT -}; - -} // end namespace - -#endif // RTPMEMORYOBJECT_H - diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp index e71444c47..c6d39368a 100644 --- a/qrtplib/rtppacket.cpp +++ b/qrtplib/rtppacket.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtppacket.h" #include "rtpstructs.h" @@ -42,272 +42,269 @@ namespace qrtplib void RTPPacket::Clear() { - hasextension = false; - hasmarker = false; - numcsrcs = 0; - payloadtype = 0; - extseqnr = 0; - timestamp = 0; - ssrc = 0; - packet = 0; - payload = 0; - packetlength = 0; - payloadlength = 0; - extid = 0; - extension = 0; - extensionlength = 0; - error = 0; - externalbuffer = false; + hasextension = false; + hasmarker = false; + numcsrcs = 0; + payloadtype = 0; + extseqnr = 0; + timestamp = 0; + ssrc = 0; + packet = 0; + payload = 0; + packetlength = 0; + payloadlength = 0; + extid = 0; + extension = 0; + extensionlength = 0; + error = 0; + externalbuffer = false; } -RTPPacket::RTPPacket(RTPRawPacket &rawpack,RTPMemoryManager *mgr) : RTPMemoryObject(mgr),receivetime(rawpack.GetReceiveTime()) +RTPPacket::RTPPacket(RTPRawPacket &rawpack) : + receivetime(rawpack.GetReceiveTime()) { - Clear(); - error = ParseRawPacket(rawpack); + Clear(); + error = ParseRawPacket(rawpack); } -RTPPacket::RTPPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, - uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, - bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, - size_t maxpacksize, RTPMemoryManager *mgr) : RTPMemoryObject(mgr),receivetime(0,0) +RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, size_t maxpacksize) : + receivetime(0, 0) { - Clear(); - error = BuildPacket(payloadtype,payloaddata,payloadlen,seqnr,timestamp,ssrc,gotmarker,numcsrcs, - csrcs,gotextension,extensionid,extensionlen_numwords,extensiondata,0,maxpacksize); + Clear(); + error = BuildPacket(payloadtype, payloaddata, payloadlen, seqnr, timestamp, ssrc, gotmarker, numcsrcs, csrcs, gotextension, extensionid, extensionlen_numwords, extensiondata, + 0, maxpacksize); } -RTPPacket::RTPPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, - uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, - bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, - void *buffer,size_t buffersize, RTPMemoryManager *mgr) : RTPMemoryObject(mgr),receivetime(0,0) +RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, size_t buffersize) : + receivetime(0, 0) { - Clear(); - if (buffer == 0) - error = ERR_RTP_PACKET_EXTERNALBUFFERNULL; - else if (buffersize <= 0) - error = ERR_RTP_PACKET_ILLEGALBUFFERSIZE; - else - error = BuildPacket(payloadtype,payloaddata,payloadlen,seqnr,timestamp,ssrc,gotmarker,numcsrcs, - csrcs,gotextension,extensionid,extensionlen_numwords,extensiondata,buffer,buffersize); + Clear(); + if (buffer == 0) + error = ERR_RTP_PACKET_EXTERNALBUFFERNULL; + else if (buffersize <= 0) + error = ERR_RTP_PACKET_ILLEGALBUFFERSIZE; + else + error = BuildPacket(payloadtype, payloaddata, payloadlen, seqnr, timestamp, ssrc, gotmarker, numcsrcs, csrcs, gotextension, extensionid, extensionlen_numwords, + extensiondata, buffer, buffersize); } int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) { - uint8_t *packetbytes; - size_t packetlen; - uint8_t payloadtype; - RTPHeader *rtpheader; - bool marker; - int csrccount; - bool hasextension; - int payloadoffset,payloadlength; - int numpadbytes; - RTPExtensionHeader *rtpextheader; + uint8_t *packetbytes; + size_t packetlen; + uint8_t payloadtype; + RTPHeader *rtpheader; + bool marker; + int csrccount; + bool hasextension; + int payloadoffset, payloadlength; + int numpadbytes; + RTPExtensionHeader *rtpextheader; - if (!rawpack.IsRTP()) // If we didn't receive it on the RTP port, we'll ignore it - return ERR_RTP_PACKET_INVALIDPACKET; + if (!rawpack.IsRTP()) // If we didn't receive it on the RTP port, we'll ignore it + return ERR_RTP_PACKET_INVALIDPACKET; - // The length should be at least the size of the RTP header - packetlen = rawpack.GetDataLength(); - if (packetlen < sizeof(RTPHeader)) - return ERR_RTP_PACKET_INVALIDPACKET; + // The length should be at least the size of the RTP header + packetlen = rawpack.GetDataLength(); + if (packetlen < sizeof(RTPHeader)) + return ERR_RTP_PACKET_INVALIDPACKET; - packetbytes = (uint8_t *)rawpack.GetData(); - rtpheader = (RTPHeader *)packetbytes; + packetbytes = (uint8_t *) rawpack.GetData(); + rtpheader = (RTPHeader *) packetbytes; - // The version number should be correct - if (rtpheader->version != RTP_VERSION) - return ERR_RTP_PACKET_INVALIDPACKET; + // The version number should be correct + if (rtpheader->version != RTP_VERSION) + return ERR_RTP_PACKET_INVALIDPACKET; - // We'll check if this is possibly a RTCP packet. For this to be possible - // the marker bit and payload type combined should be either an SR or RR - // identifier - marker = (rtpheader->marker == 0)?false:true; - payloadtype = rtpheader->payloadtype; - if (marker) - { - if (payloadtype == (RTP_RTCPTYPE_SR & 127)) // don't check high bit (this was the marker!!) - return ERR_RTP_PACKET_INVALIDPACKET; - if (payloadtype == (RTP_RTCPTYPE_RR & 127)) - return ERR_RTP_PACKET_INVALIDPACKET; - } + // We'll check if this is possibly a RTCP packet. For this to be possible + // the marker bit and payload type combined should be either an SR or RR + // identifier + marker = (rtpheader->marker == 0) ? false : true; + payloadtype = rtpheader->payloadtype; + if (marker) + { + if (payloadtype == (RTP_RTCPTYPE_SR & 127)) // don't check high bit (this was the marker!!) + return ERR_RTP_PACKET_INVALIDPACKET; + if (payloadtype == (RTP_RTCPTYPE_RR & 127)) + return ERR_RTP_PACKET_INVALIDPACKET; + } - csrccount = rtpheader->csrccount; - payloadoffset = sizeof(RTPHeader)+(int)(csrccount*sizeof(uint32_t)); + csrccount = rtpheader->csrccount; + payloadoffset = sizeof(RTPHeader) + (int) (csrccount * sizeof(uint32_t)); - if (rtpheader->padding) // adjust payload length to take padding into account - { - numpadbytes = (int)packetbytes[packetlen-1]; // last byte contains number of padding bytes - if (numpadbytes <= 0) - return ERR_RTP_PACKET_INVALIDPACKET; - } - else - numpadbytes = 0; + if (rtpheader->padding) // adjust payload length to take padding into account + { + numpadbytes = (int) packetbytes[packetlen - 1]; // last byte contains number of padding bytes + if (numpadbytes <= 0) + return ERR_RTP_PACKET_INVALIDPACKET; + } + else + numpadbytes = 0; - hasextension = (rtpheader->extension == 0)?false:true; - if (hasextension) // got header extension - { - rtpextheader = (RTPExtensionHeader *)(packetbytes+payloadoffset); - payloadoffset += sizeof(RTPExtensionHeader); + hasextension = (rtpheader->extension == 0) ? false : true; + if (hasextension) // got header extension + { + rtpextheader = (RTPExtensionHeader *) (packetbytes + payloadoffset); + payloadoffset += sizeof(RTPExtensionHeader); - uint16_t exthdrlen = m_endian.qToHost(rtpextheader->length); - payloadoffset += ((int)exthdrlen)*sizeof(uint32_t); - } - else - { - rtpextheader = 0; - } + uint16_t exthdrlen = m_endian.qToHost(rtpextheader->length); + payloadoffset += ((int) exthdrlen) * sizeof(uint32_t); + } + else + { + rtpextheader = 0; + } - payloadlength = packetlen-numpadbytes-payloadoffset; - if (payloadlength < 0) - return ERR_RTP_PACKET_INVALIDPACKET; + payloadlength = packetlen - numpadbytes - payloadoffset; + if (payloadlength < 0) + return ERR_RTP_PACKET_INVALIDPACKET; - // Now, we've got a valid packet, so we can create a new instance of RTPPacket - // and fill in the members + // Now, we've got a valid packet, so we can create a new instance of RTPPacket + // and fill in the members - RTPPacket::hasextension = hasextension; - if (hasextension) - { - RTPPacket::extid = m_endian.qToHost(rtpextheader->extid); - RTPPacket::extensionlength = ((int)m_endian.qToHost(rtpextheader->length))*sizeof(uint32_t); - RTPPacket::extension = ((uint8_t *)rtpextheader)+sizeof(RTPExtensionHeader); - } + RTPPacket::hasextension = hasextension; + if (hasextension) + { + RTPPacket::extid = m_endian.qToHost(rtpextheader->extid); + RTPPacket::extensionlength = ((int) m_endian.qToHost(rtpextheader->length)) * sizeof(uint32_t); + RTPPacket::extension = ((uint8_t *) rtpextheader) + sizeof(RTPExtensionHeader); + } - RTPPacket::hasmarker = marker; - RTPPacket::numcsrcs = csrccount; - RTPPacket::payloadtype = payloadtype; + RTPPacket::hasmarker = marker; + RTPPacket::numcsrcs = csrccount; + RTPPacket::payloadtype = payloadtype; - // Note: we don't fill in the EXTENDED sequence number here, since we - // don't have information about the source here. We just fill in the low - // 16 bits - RTPPacket::extseqnr = (uint32_t)m_endian.qToHost(rtpheader->sequencenumber); + // Note: we don't fill in the EXTENDED sequence number here, since we + // don't have information about the source here. We just fill in the low + // 16 bits + RTPPacket::extseqnr = (uint32_t) m_endian.qToHost(rtpheader->sequencenumber); - RTPPacket::timestamp = m_endian.qToHost(rtpheader->timestamp); - RTPPacket::ssrc = m_endian.qToHost(rtpheader->ssrc); - RTPPacket::packet = packetbytes; - RTPPacket::payload = packetbytes+payloadoffset; - RTPPacket::packetlength = packetlen; - RTPPacket::payloadlength = payloadlength; + RTPPacket::timestamp = m_endian.qToHost(rtpheader->timestamp); + RTPPacket::ssrc = m_endian.qToHost(rtpheader->ssrc); + RTPPacket::packet = packetbytes; + RTPPacket::payload = packetbytes + payloadoffset; + RTPPacket::packetlength = packetlen; + RTPPacket::payloadlength = payloadlength; - // We'll zero the data of the raw packet, since we're using it here now! - rawpack.ZeroData(); + // We'll zero the data of the raw packet, since we're using it here now! + rawpack.ZeroData(); - return 0; + return 0; } uint32_t RTPPacket::GetCSRC(int num) const { - if (num >= numcsrcs) - return 0; + if (num >= numcsrcs) + return 0; - uint8_t *csrcpos; - uint32_t *csrcval_nbo; - uint32_t csrcval_hbo; + uint8_t *csrcpos; + uint32_t *csrcval_nbo; + uint32_t csrcval_hbo; - csrcpos = packet+sizeof(RTPHeader)+num*sizeof(uint32_t); - csrcval_nbo = (uint32_t *)csrcpos; - csrcval_hbo = m_endian.qToHost(*csrcval_nbo); - return csrcval_hbo; + csrcpos = packet + sizeof(RTPHeader) + num * sizeof(uint32_t); + csrcval_nbo = (uint32_t *) csrcpos; + csrcval_hbo = m_endian.qToHost(*csrcval_nbo); + return csrcval_hbo; } -int RTPPacket::BuildPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, - uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, - bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, - void *buffer,size_t maxsize) +int RTPPacket::BuildPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, size_t maxsize) { - if (numcsrcs > RTP_MAXCSRCS) - return ERR_RTP_PACKET_TOOMANYCSRCS; + if (numcsrcs > RTP_MAXCSRCS) + return ERR_RTP_PACKET_TOOMANYCSRCS; - if (payloadtype > 127) // high bit should not be used - return ERR_RTP_PACKET_BADPAYLOADTYPE; - if (payloadtype == 72 || payloadtype == 73) // could cause confusion with rtcp types - return ERR_RTP_PACKET_BADPAYLOADTYPE; + if (payloadtype > 127) // high bit should not be used + return ERR_RTP_PACKET_BADPAYLOADTYPE; + if (payloadtype == 72 || payloadtype == 73) // could cause confusion with rtcp types + return ERR_RTP_PACKET_BADPAYLOADTYPE; - packetlength = sizeof(RTPHeader); - packetlength += sizeof(uint32_t)*((size_t)numcsrcs); - if (gotextension) - { - packetlength += sizeof(RTPExtensionHeader); - packetlength += sizeof(uint32_t)*((size_t)extensionlen_numwords); - } - packetlength += payloadlen; + packetlength = sizeof(RTPHeader); + packetlength += sizeof(uint32_t) * ((size_t) numcsrcs); + if (gotextension) + { + packetlength += sizeof(RTPExtensionHeader); + packetlength += sizeof(uint32_t) * ((size_t) extensionlen_numwords); + } + packetlength += payloadlen; - if (maxsize > 0 && packetlength > maxsize) - { - packetlength = 0; - return ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE; - } + if (maxsize > 0 && packetlength > maxsize) + { + packetlength = 0; + return ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE; + } - // Ok, now we'll just fill in... + // Ok, now we'll just fill in... - RTPHeader *rtphdr; + RTPHeader *rtphdr; - if (buffer == 0) - { - packet = new uint8_t [packetlength]; - if (packet == 0) - { - packetlength = 0; - return ERR_RTP_OUTOFMEM; - } - externalbuffer = false; - } - else - { - packet = (uint8_t *)buffer; - externalbuffer = true; - } + if (buffer == 0) + { + packet = new uint8_t[packetlength]; + if (packet == 0) + { + packetlength = 0; + return ERR_RTP_OUTOFMEM; + } + externalbuffer = false; + } + else + { + packet = (uint8_t *) buffer; + externalbuffer = true; + } - RTPPacket::hasmarker = gotmarker; - RTPPacket::hasextension = gotextension; - RTPPacket::numcsrcs = numcsrcs; - RTPPacket::payloadtype = payloadtype; - RTPPacket::extseqnr = (uint32_t)seqnr; - RTPPacket::timestamp = timestamp; - RTPPacket::ssrc = ssrc; - RTPPacket::payloadlength = payloadlen; - RTPPacket::extid = extensionid; - RTPPacket::extensionlength = ((size_t)extensionlen_numwords)*sizeof(uint32_t); + RTPPacket::hasmarker = gotmarker; + RTPPacket::hasextension = gotextension; + RTPPacket::numcsrcs = numcsrcs; + RTPPacket::payloadtype = payloadtype; + RTPPacket::extseqnr = (uint32_t) seqnr; + RTPPacket::timestamp = timestamp; + RTPPacket::ssrc = ssrc; + RTPPacket::payloadlength = payloadlen; + RTPPacket::extid = extensionid; + RTPPacket::extensionlength = ((size_t) extensionlen_numwords) * sizeof(uint32_t); - rtphdr = (RTPHeader *)packet; - rtphdr->version = RTP_VERSION; - rtphdr->padding = 0; - if (gotmarker) - rtphdr->marker = 1; - else - rtphdr->marker = 0; - if (gotextension) - rtphdr->extension = 1; - else - rtphdr->extension = 0; - rtphdr->csrccount = numcsrcs; - rtphdr->payloadtype = payloadtype&127; // make sure high bit isn't set - rtphdr->sequencenumber = qToBigEndian(seqnr); - rtphdr->timestamp = qToBigEndian(timestamp); - rtphdr->ssrc = qToBigEndian(ssrc); + rtphdr = (RTPHeader *) packet; + rtphdr->version = RTP_VERSION; + rtphdr->padding = 0; + if (gotmarker) + rtphdr->marker = 1; + else + rtphdr->marker = 0; + if (gotextension) + rtphdr->extension = 1; + else + rtphdr->extension = 0; + rtphdr->csrccount = numcsrcs; + rtphdr->payloadtype = payloadtype & 127; // make sure high bit isn't set + rtphdr->sequencenumber = qToBigEndian(seqnr); + rtphdr->timestamp = qToBigEndian(timestamp); + rtphdr->ssrc = qToBigEndian(ssrc); - uint32_t *curcsrc; - int i; + uint32_t *curcsrc; + int i; - curcsrc = (uint32_t *)(packet+sizeof(RTPHeader)); - for (i = 0 ; i < numcsrcs ; i++,curcsrc++) - *curcsrc = qToBigEndian(csrcs[i]); + curcsrc = (uint32_t *) (packet + sizeof(RTPHeader)); + for (i = 0; i < numcsrcs; i++, curcsrc++) + *curcsrc = qToBigEndian(csrcs[i]); - payload = packet+sizeof(RTPHeader)+((size_t)numcsrcs)*sizeof(uint32_t); - if (gotextension) - { - RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *)payload; + payload = packet + sizeof(RTPHeader) + ((size_t) numcsrcs) * sizeof(uint32_t); + if (gotextension) + { + RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *) payload; - rtpexthdr->extid = qToBigEndian(extensionid); - rtpexthdr->length = qToBigEndian((uint16_t)extensionlen_numwords); + rtpexthdr->extid = qToBigEndian(extensionid); + rtpexthdr->length = qToBigEndian((uint16_t) extensionlen_numwords); - payload += sizeof(RTPExtensionHeader); - memcpy(payload,extensiondata,RTPPacket::extensionlength); + payload += sizeof(RTPExtensionHeader); + memcpy(payload, extensiondata, RTPPacket::extensionlength); - payload += RTPPacket::extensionlength; - } - memcpy(payload,payloaddata,payloadlen); - return 0; + payload += RTPPacket::extensionlength; + } + memcpy(payload, payloaddata, payloadlen); + return 0; } } // end namespace diff --git a/qrtplib/rtppacket.h b/qrtplib/rtppacket.h index fa4dea018..36c340c19 100644 --- a/qrtplib/rtppacket.h +++ b/qrtplib/rtppacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtppacket.h @@ -41,7 +41,6 @@ #include "rtpconfig.h" #include "rtptypes.h" #include "rtptimeutilities.h" -#include "rtpmemoryobject.h" #include "rtpendian.h" namespace qrtplib @@ -54,126 +53,178 @@ class RTPRawPacket; * The class can also be used to create a new RTP packet according to the parameters specified by * the user. */ -class JRTPLIB_IMPORTEXPORT RTPPacket : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTPPacket { public: - /** Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. - * Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. - * If successful, the data is moved from the raw packet to the RTPPacket instance. - */ - RTPPacket(RTPRawPacket &rawpack,RTPMemoryManager *mgr = 0); + /** Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. + * Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. + * If successful, the data is moved from the raw packet to the RTPPacket instance. + */ + RTPPacket(RTPRawPacket &rawpack); - /** Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. - * Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. - * If \c maxpacksize is not equal to zero, an error is generated if the total packet size would exceed - * \c maxpacksize. The arguments of the constructor are self-explanatory. Note that the size of a header - * extension is specified in a number of 32-bit words. A memory manager can be installed. - */ - RTPPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, - uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, - bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, - size_t maxpacksize, RTPMemoryManager *mgr = 0); + /** Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. + * Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. + * If \c maxpacksize is not equal to zero, an error is generated if the total packet size would exceed + * \c maxpacksize. The arguments of the constructor are self-explanatory. Note that the size of a header + * extension is specified in a number of 32-bit words. A memory manager can be installed. + */ + RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, size_t maxpacksize); - /** This constructor is similar to the other constructor, but here data is stored in an external buffer - * \c buffer with size \c buffersize. */ - RTPPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, - uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, - bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, - void *buffer,size_t buffersize,RTPMemoryManager *mgr = 0); + /** This constructor is similar to the other constructor, but here data is stored in an external buffer + * \c buffer with size \c buffersize. */ + RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, size_t buffersize); - virtual ~RTPPacket() { if (packet && !externalbuffer) RTPDeleteByteArray(packet,GetMemoryManager()); } + virtual ~RTPPacket() + { + if (packet && !externalbuffer) + delete[] packet; + } - /** If an error occurred in one of the constructors, this function returns the error code. */ - int GetCreationError() const { return error; } + /** If an error occurred in one of the constructors, this function returns the error code. */ + int GetCreationError() const + { + return error; + } - /** Returns \c true if the RTP packet has a header extension and \c false otherwise. */ - bool HasExtension() const { return hasextension; } + /** Returns \c true if the RTP packet has a header extension and \c false otherwise. */ + bool HasExtension() const + { + return hasextension; + } - /** Returns \c true if the marker bit was set and \c false otherwise. */ - bool HasMarker() const { return hasmarker; } + /** Returns \c true if the marker bit was set and \c false otherwise. */ + bool HasMarker() const + { + return hasmarker; + } - /** Returns the number of CSRCs contained in this packet. */ - int GetCSRCCount() const { return numcsrcs; } + /** Returns the number of CSRCs contained in this packet. */ + int GetCSRCCount() const + { + return numcsrcs; + } - /** Returns a specific CSRC identifier. - * Returns a specific CSRC identifier. The parameter \c num can go from 0 to GetCSRCCount()-1. - */ - uint32_t GetCSRC(int num) const; + /** Returns a specific CSRC identifier. + * Returns a specific CSRC identifier. The parameter \c num can go from 0 to GetCSRCCount()-1. + */ + uint32_t GetCSRC(int num) const; - /** Returns the payload type of the packet. */ - uint8_t GetPayloadType() const { return payloadtype; } + /** Returns the payload type of the packet. */ + uint8_t GetPayloadType() const + { + return payloadtype; + } - /** Returns the extended sequence number of the packet. - * Returns the extended sequence number of the packet. When the packet is just received, - * only the low $16$ bits will be set. The high 16 bits can be filled in later. - */ - uint32_t GetExtendedSequenceNumber() const { return extseqnr; } + /** Returns the extended sequence number of the packet. + * Returns the extended sequence number of the packet. When the packet is just received, + * only the low $16$ bits will be set. The high 16 bits can be filled in later. + */ + uint32_t GetExtendedSequenceNumber() const + { + return extseqnr; + } - /** Returns the sequence number of this packet. */ - uint16_t GetSequenceNumber() const { return (uint16_t)(extseqnr&0x0000FFFF); } + /** Returns the sequence number of this packet. */ + uint16_t GetSequenceNumber() const + { + return (uint16_t) (extseqnr & 0x0000FFFF); + } - /** Sets the extended sequence number of this packet to \c seq. */ - void SetExtendedSequenceNumber(uint32_t seq) { extseqnr = seq; } + /** Sets the extended sequence number of this packet to \c seq. */ + void SetExtendedSequenceNumber(uint32_t seq) + { + extseqnr = seq; + } - /** Returns the timestamp of this packet. */ - uint32_t GetTimestamp() const { return timestamp; } + /** Returns the timestamp of this packet. */ + uint32_t GetTimestamp() const + { + return timestamp; + } - /** Returns the SSRC identifier stored in this packet. */ - uint32_t GetSSRC() const { return ssrc; } + /** Returns the SSRC identifier stored in this packet. */ + uint32_t GetSSRC() const + { + return ssrc; + } - /** Returns a pointer to the data of the entire packet. */ - uint8_t *GetPacketData() const { return packet; } + /** Returns a pointer to the data of the entire packet. */ + uint8_t *GetPacketData() const + { + return packet; + } - /** Returns a pointer to the actual payload data. */ - uint8_t *GetPayloadData() const { return payload; } + /** Returns a pointer to the actual payload data. */ + uint8_t *GetPayloadData() const + { + return payload; + } - /** Returns the length of the entire packet. */ - size_t GetPacketLength() const { return packetlength; } + /** Returns the length of the entire packet. */ + size_t GetPacketLength() const + { + return packetlength; + } - /** Returns the payload length. */ - size_t GetPayloadLength() const { return payloadlength; } + /** Returns the payload length. */ + size_t GetPayloadLength() const + { + return payloadlength; + } - /** If a header extension is present, this function returns the extension identifier. */ - uint16_t GetExtensionID() const { return extid; } + /** If a header extension is present, this function returns the extension identifier. */ + uint16_t GetExtensionID() const + { + return extid; + } - /** Returns the length of the header extension data. */ - uint8_t *GetExtensionData() const { return extension; } + /** Returns the length of the header extension data. */ + uint8_t *GetExtensionData() const + { + return extension; + } - /** Returns the length of the header extension data. */ - size_t GetExtensionLength() const { return extensionlength; } + /** Returns the length of the header extension data. */ + size_t GetExtensionLength() const + { + return extensionlength; + } - /** Returns the time at which this packet was received. - * When an RTPPacket instance is created from an RTPRawPacket instance, the raw packet's - * reception time is stored in the RTPPacket instance. This function then retrieves that - * time. - */ - RTPTime GetReceiveTime() const { return receivetime; } + /** Returns the time at which this packet was received. + * When an RTPPacket instance is created from an RTPRawPacket instance, the raw packet's + * reception time is stored in the RTPPacket instance. This function then retrieves that + * time. + */ + RTPTime GetReceiveTime() const + { + return receivetime; + } private: - void Clear(); - int ParseRawPacket(RTPRawPacket &rawpack); - int BuildPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr, - uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs, - bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata, - void *buffer,size_t maxsize); + void Clear(); + int ParseRawPacket(RTPRawPacket &rawpack); + int BuildPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, size_t maxsize); - RTPEndian m_endian; - int error; + RTPEndian m_endian; + int error; - bool hasextension,hasmarker; - int numcsrcs; + bool hasextension, hasmarker; + int numcsrcs; - uint8_t payloadtype; - uint32_t extseqnr,timestamp,ssrc; - uint8_t *packet,*payload; - size_t packetlength,payloadlength; + uint8_t payloadtype; + uint32_t extseqnr, timestamp, ssrc; + uint8_t *packet, *payload; + size_t packetlength, payloadlength; - uint16_t extid; - uint8_t *extension; - size_t extensionlength; + uint16_t extid; + uint8_t *extension; + size_t extensionlength; - bool externalbuffer; + bool externalbuffer; - RTPTime receivetime; + RTPTime receivetime; }; } // end namespace diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib/rtppacketbuilder.cpp index e3bdc3185..5df8c1e17 100644 --- a/qrtplib/rtppacketbuilder.cpp +++ b/qrtplib/rtppacketbuilder.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtppacketbuilder.h" #include "rtperrors.h" @@ -40,227 +40,222 @@ namespace qrtplib { -RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r,RTPMemoryManager *mgr) : RTPMemoryObject(mgr),rtprnd(r),lastwallclocktime(0,0) +RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r) : + rtprnd(r), lastwallclocktime(0, 0) { - init = false; - timeinit.Dummy(); + init = false; + timeinit.Dummy(); - //std::cout << (void *)(&rtprnd) << std::endl; + //std::cout << (void *)(&rtprnd) << std::endl; } RTPPacketBuilder::~RTPPacketBuilder() { - Destroy(); + Destroy(); } int RTPPacketBuilder::Init(size_t max) { - if (init) - return ERR_RTP_PACKBUILD_ALREADYINIT; - if (max <= 0) - return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; + if (init) + return ERR_RTP_PACKBUILD_ALREADYINIT; + if (max <= 0) + return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; - maxpacksize = max; - buffer = new uint8_t [max]; - if (buffer == 0) - return ERR_RTP_OUTOFMEM; - packetlength = 0; + maxpacksize = max; + buffer = new uint8_t[max]; + if (buffer == 0) + return ERR_RTP_OUTOFMEM; + packetlength = 0; - CreateNewSSRC(); + CreateNewSSRC(); - deftsset = false; - defptset = false; - defmarkset = false; + deftsset = false; + defptset = false; + defmarkset = false; - numcsrcs = 0; + numcsrcs = 0; - init = true; - return 0; + init = true; + return 0; } void RTPPacketBuilder::Destroy() { - if (!init) - return; - RTPDeleteByteArray(buffer,GetMemoryManager()); - init = false; + if (!init) + return; + delete[] buffer; + init = false; } int RTPPacketBuilder::SetMaximumPacketSize(size_t max) { - uint8_t *newbuf; + uint8_t *newbuf; - if (max <= 0) - return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; - newbuf = new uint8_t[max]; - if (newbuf == 0) - return ERR_RTP_OUTOFMEM; + if (max <= 0) + return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; + newbuf = new uint8_t[max]; + if (newbuf == 0) + return ERR_RTP_OUTOFMEM; - RTPDeleteByteArray(buffer,GetMemoryManager()); - buffer = newbuf; - maxpacksize = max; - return 0; + delete[] buffer; + buffer = newbuf; + maxpacksize = max; + return 0; } int RTPPacketBuilder::AddCSRC(uint32_t csrc) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (numcsrcs >= RTP_MAXCSRCS) - return ERR_RTP_PACKBUILD_CSRCLISTFULL; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (numcsrcs >= RTP_MAXCSRCS) + return ERR_RTP_PACKBUILD_CSRCLISTFULL; - int i; + int i; - for (i = 0 ; i < numcsrcs ; i++) - { - if (csrcs[i] == csrc) - return ERR_RTP_PACKBUILD_CSRCALREADYINLIST; - } - csrcs[numcsrcs] = csrc; - numcsrcs++; - return 0; + for (i = 0; i < numcsrcs; i++) + { + if (csrcs[i] == csrc) + return ERR_RTP_PACKBUILD_CSRCALREADYINLIST; + } + csrcs[numcsrcs] = csrc; + numcsrcs++; + return 0; } int RTPPacketBuilder::DeleteCSRC(uint32_t csrc) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; - int i = 0; - bool found = false; + int i = 0; + bool found = false; - while (!found && i < numcsrcs) - { - if (csrcs[i] == csrc) - found = true; - else - i++; - } + while (!found && i < numcsrcs) + { + if (csrcs[i] == csrc) + found = true; + else + i++; + } - if (!found) - return ERR_RTP_PACKBUILD_CSRCNOTINLIST; + if (!found) + return ERR_RTP_PACKBUILD_CSRCNOTINLIST; - // move the last csrc in the place of the deleted one - numcsrcs--; - if (numcsrcs > 0 && numcsrcs != i) - csrcs[i] = csrcs[numcsrcs]; - return 0; + // move the last csrc in the place of the deleted one + numcsrcs--; + if (numcsrcs > 0 && numcsrcs != i) + csrcs[i] = csrcs[numcsrcs]; + return 0; } void RTPPacketBuilder::ClearCSRCList() { - if (!init) - return; - numcsrcs = 0; + if (!init) + return; + numcsrcs = 0; } uint32_t RTPPacketBuilder::CreateNewSSRC() { - ssrc = rtprnd.GetRandom32(); - timestamp = rtprnd.GetRandom32(); - seqnr = rtprnd.GetRandom16(); + ssrc = rtprnd.GetRandom32(); + timestamp = rtprnd.GetRandom32(); + seqnr = rtprnd.GetRandom16(); - // p 38: the count SHOULD be reset if the sender changes its SSRC identifier - numpayloadbytes = 0; - numpackets = 0; - return ssrc; + // p 38: the count SHOULD be reset if the sender changes its SSRC identifier + numpayloadbytes = 0; + numpackets = 0; + return ssrc; } uint32_t RTPPacketBuilder::CreateNewSSRC(RTPSources &sources) { - bool found; + bool found; - do - { - ssrc = rtprnd.GetRandom32(); - found = sources.GotEntry(ssrc); - } while (found); + do + { + ssrc = rtprnd.GetRandom32(); + found = sources.GotEntry(ssrc); + } while (found); - timestamp = rtprnd.GetRandom32(); - seqnr = rtprnd.GetRandom16(); + timestamp = rtprnd.GetRandom32(); + seqnr = rtprnd.GetRandom16(); - // p 38: the count SHOULD be reset if the sender changes its SSRC identifier - numpayloadbytes = 0; - numpackets = 0; - return ssrc; + // p 38: the count SHOULD be reset if the sender changes its SSRC identifier + numpayloadbytes = 0; + numpackets = 0; + return ssrc; } -int RTPPacketBuilder::BuildPacket(const void *data,size_t len) +int RTPPacketBuilder::BuildPacket(const void *data, size_t len) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!defptset) - return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; - if (!defmarkset) - return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,false); + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!defptset) + return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; + if (!defmarkset) + return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + return PrivateBuildPacket(data, len, defaultpayloadtype, defaultmark, defaulttimestampinc, false); } -int RTPPacketBuilder::BuildPacket(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc) +int RTPPacketBuilder::BuildPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - return PrivateBuildPacket(data,len,pt,mark,timestampinc,false); + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + return PrivateBuildPacket(data, len, pt, mark, timestampinc, false); } -int RTPPacketBuilder::BuildPacketEx(const void *data,size_t len, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +int RTPPacketBuilder::BuildPacketEx(const void *data, size_t len, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!defptset) - return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; - if (!defmarkset) - return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,true,hdrextID,hdrextdata,numhdrextwords); + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!defptset) + return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; + if (!defmarkset) + return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + return PrivateBuildPacket(data, len, defaultpayloadtype, defaultmark, defaulttimestampinc, true, hdrextID, hdrextdata, numhdrextwords); } -int RTPPacketBuilder::BuildPacketEx(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +int RTPPacketBuilder::BuildPacketEx(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - return PrivateBuildPacket(data,len,pt,mark,timestampinc,true,hdrextID,hdrextdata,numhdrextwords); + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + return PrivateBuildPacket(data, len, pt, mark, timestampinc, true, hdrextID, hdrextdata, numhdrextwords); } -int RTPPacketBuilder::PrivateBuildPacket(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc,bool gotextension, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +int RTPPacketBuilder::PrivateBuildPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID, const void *hdrextdata, + size_t numhdrextwords) { - RTPPacket p(pt,data,len,seqnr,timestamp,ssrc,mark,numcsrcs,csrcs,gotextension,hdrextID, - (uint16_t)numhdrextwords,hdrextdata,buffer,maxpacksize,GetMemoryManager()); - int status = p.GetCreationError(); + RTPPacket p(pt, data, len, seqnr, timestamp, ssrc, mark, numcsrcs, csrcs, gotextension, hdrextID, (uint16_t) numhdrextwords, hdrextdata, buffer, maxpacksize); + int status = p.GetCreationError(); - if (status < 0) - return status; - packetlength = p.GetPacketLength(); + if (status < 0) + return status; + packetlength = p.GetPacketLength(); - if (numpackets == 0) // first packet - { - lastwallclocktime = RTPTime::CurrentTime(); - lastrtptimestamp = timestamp; - prevrtptimestamp = timestamp; - } - else if (timestamp != prevrtptimestamp) - { - lastwallclocktime = RTPTime::CurrentTime(); - lastrtptimestamp = timestamp; - prevrtptimestamp = timestamp; - } + if (numpackets == 0) // first packet + { + lastwallclocktime = RTPTime::CurrentTime(); + lastrtptimestamp = timestamp; + prevrtptimestamp = timestamp; + } + else if (timestamp != prevrtptimestamp) + { + lastwallclocktime = RTPTime::CurrentTime(); + lastrtptimestamp = timestamp; + prevrtptimestamp = timestamp; + } - numpayloadbytes += (uint32_t)p.GetPayloadLength(); - numpackets++; - timestamp += timestampinc; - seqnr++; + numpayloadbytes += (uint32_t) p.GetPayloadLength(); + numpackets++; + timestamp += timestampinc; + seqnr++; - return 0; + return 0; } } // end namespace diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib/rtppacketbuilder.h index c9498d5a6..1a853ed5f 100644 --- a/qrtplib/rtppacketbuilder.h +++ b/qrtplib/rtppacketbuilder.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtppacketbuilder.h @@ -44,7 +44,6 @@ #include "rtprandom.h" #include "rtptimeutilities.h" #include "rtptypes.h" -#include "rtpmemoryobject.h" namespace qrtplib { @@ -54,218 +53,261 @@ class RTPSources; /** This class can be used to build RTP packets and is a bit more high-level than the RTPPacket * class: it generates an SSRC identifier, keeps track of timestamp and sequence number etc. */ -class JRTPLIB_IMPORTEXPORT RTPPacketBuilder : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTPPacketBuilder { public: - /** Constructs an instance which will use \c rtprand for generating random numbers - * (used to initialize the SSRC value and sequence number), optionally installing a memory manager. - **/ - RTPPacketBuilder(RTPRandom &rtprand, RTPMemoryManager *mgr = 0); - ~RTPPacketBuilder(); + /** Constructs an instance which will use \c rtprand for generating random numbers + * (used to initialize the SSRC value and sequence number), optionally installing a memory manager. + **/ + RTPPacketBuilder(RTPRandom &rtprand); + ~RTPPacketBuilder(); - /** Initializes the builder to only allow packets with a size below \c maxpacksize. */ - int Init(size_t maxpacksize); + /** Initializes the builder to only allow packets with a size below \c maxpacksize. */ + int Init(size_t maxpacksize); - /** Cleans up the builder. */ - void Destroy(); + /** Cleans up the builder. */ + void Destroy(); - /** Returns the number of packets which have been created with the current SSRC identifier. */ - uint32_t GetPacketCount() { if (!init) return 0; return numpackets; } + /** Returns the number of packets which have been created with the current SSRC identifier. */ + uint32_t GetPacketCount() + { + if (!init) + return 0; + return numpackets; + } - /** Returns the number of payload octets which have been generated with this SSRC identifier. */ - uint32_t GetPayloadOctetCount() { if (!init) return 0; return numpayloadbytes; } + /** Returns the number of payload octets which have been generated with this SSRC identifier. */ + uint32_t GetPayloadOctetCount() + { + if (!init) + return 0; + return numpayloadbytes; + } - /** Sets the maximum allowed packet size to \c maxpacksize. */ - int SetMaximumPacketSize(size_t maxpacksize); + /** Sets the maximum allowed packet size to \c maxpacksize. */ + int SetMaximumPacketSize(size_t maxpacksize); - /** Adds a CSRC to the CSRC list which will be stored in the RTP packets. */ - int AddCSRC(uint32_t csrc); + /** Adds a CSRC to the CSRC list which will be stored in the RTP packets. */ + int AddCSRC(uint32_t csrc); - /** Deletes a CSRC from the list which will be stored in the RTP packets. */ - int DeleteCSRC(uint32_t csrc); + /** Deletes a CSRC from the list which will be stored in the RTP packets. */ + int DeleteCSRC(uint32_t csrc); - /** Clears the CSRC list. */ - void ClearCSRCList(); + /** Clears the CSRC list. */ + void ClearCSRCList(); - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type, marker - * and timestamp increment used will be those that have been set using the \c SetDefault - * functions below. - */ - int BuildPacket(const void *data,size_t len); + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type, marker + * and timestamp increment used will be those that have been set using the \c SetDefault + * functions below. + */ + int BuildPacket(const void *data, size_t len); - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type will be - * set to \c pt, the marker bit to \c mark and after building this packet, the timestamp will - * be incremented with \c timestamp. - */ - int BuildPacket(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc); + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type will be + * set to \c pt, the marker bit to \c mark and after building this packet, the timestamp will + * be incremented with \c timestamp. + */ + int BuildPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc); - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type, marker - * and timestamp increment used will be those that have been set using the \c SetDefault - * functions below. This packet will also contain an RTP header extension with identifier - * \c hdrextID and data \c hdrextdata. The length of the header extension data is given by - * \c numhdrextwords which expresses the length in a number of 32-bit words. - */ - int BuildPacketEx(const void *data,size_t len, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords); + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type, marker + * and timestamp increment used will be those that have been set using the \c SetDefault + * functions below. This packet will also contain an RTP header extension with identifier + * \c hdrextID and data \c hdrextdata. The length of the header extension data is given by + * \c numhdrextwords which expresses the length in a number of 32-bit words. + */ + int BuildPacketEx(const void *data, size_t len, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords); - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type will be set - * to \c pt, the marker bit to \c mark and after building this packet, the timestamp will - * be incremented with \c timestamp. This packet will also contain an RTP header extension - * with identifier \c hdrextID and data \c hdrextdata. The length of the header extension - * data is given by \c numhdrextwords which expresses the length in a number of 32-bit words. - */ - int BuildPacketEx(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords); + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type will be set + * to \c pt, the marker bit to \c mark and after building this packet, the timestamp will + * be incremented with \c timestamp. This packet will also contain an RTP header extension + * with identifier \c hdrextID and data \c hdrextdata. The length of the header extension + * data is given by \c numhdrextwords which expresses the length in a number of 32-bit words. + */ + int BuildPacketEx(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords); - /** Returns a pointer to the last built RTP packet data. */ - uint8_t *GetPacket() { if (!init) return 0; return buffer; } + /** Returns a pointer to the last built RTP packet data. */ + uint8_t *GetPacket() + { + if (!init) + return 0; + return buffer; + } - /** Returns the size of the last built RTP packet. */ - size_t GetPacketLength() { if (!init) return 0; return packetlength; } + /** Returns the size of the last built RTP packet. */ + size_t GetPacketLength() + { + if (!init) + return 0; + return packetlength; + } - /** Sets the default payload type to \c pt. */ - int SetDefaultPayloadType(uint8_t pt); + /** Sets the default payload type to \c pt. */ + int SetDefaultPayloadType(uint8_t pt); - /** Sets the default marker bit to \c m. */ - int SetDefaultMark(bool m); + /** Sets the default marker bit to \c m. */ + int SetDefaultMark(bool m); - /** Sets the default timestamp increment to \c timestampinc. */ - int SetDefaultTimestampIncrement(uint32_t timestampinc); + /** Sets the default timestamp increment to \c timestampinc. */ + int SetDefaultTimestampIncrement(uint32_t timestampinc); - /** This function increments the timestamp with the amount given by \c inc. - * This function increments the timestamp with the amount given by \c inc. This can be useful - * if, for example, a packet was not sent because it contained only silence. Then, this function - * should be called to increment the timestamp with the appropriate amount so that the next packets - * will still be played at the correct time at other hosts. - */ - int IncrementTimestamp(uint32_t inc); + /** This function increments the timestamp with the amount given by \c inc. + * This function increments the timestamp with the amount given by \c inc. This can be useful + * if, for example, a packet was not sent because it contained only silence. Then, this function + * should be called to increment the timestamp with the appropriate amount so that the next packets + * will still be played at the correct time at other hosts. + */ + int IncrementTimestamp(uint32_t inc); - /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement - * member function. - * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement - * member function. This can be useful if, for example, a packet was not sent because it contained only silence. - * Then, this function should be called to increment the timestamp with the appropriate amount so that the next - * packets will still be played at the correct time at other hosts. - */ - int IncrementTimestampDefault(); + /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. + * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. This can be useful if, for example, a packet was not sent because it contained only silence. + * Then, this function should be called to increment the timestamp with the appropriate amount so that the next + * packets will still be played at the correct time at other hosts. + */ + int IncrementTimestampDefault(); - /** Creates a new SSRC to be used in generated packets. - * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and - * sequence number offsets. - */ - uint32_t CreateNewSSRC(); + /** Creates a new SSRC to be used in generated packets. + * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and + * sequence number offsets. + */ + uint32_t CreateNewSSRC(); - /** Creates a new SSRC to be used in generated packets. - * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and - * sequence number offsets. The source table \c sources is used to make sure that the chosen SSRC - * isn't used by another participant yet. - */ - uint32_t CreateNewSSRC(RTPSources &sources); + /** Creates a new SSRC to be used in generated packets. + * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and + * sequence number offsets. The source table \c sources is used to make sure that the chosen SSRC + * isn't used by another participant yet. + */ + uint32_t CreateNewSSRC(RTPSources &sources); - /** Returns the current SSRC identifier. */ - uint32_t GetSSRC() const { if (!init) return 0; return ssrc; } + /** Returns the current SSRC identifier. */ + uint32_t GetSSRC() const + { + if (!init) + return 0; + return ssrc; + } - /** Returns the current RTP timestamp. */ - uint32_t GetTimestamp() const { if (!init) return 0; return timestamp; } + /** Returns the current RTP timestamp. */ + uint32_t GetTimestamp() const + { + if (!init) + return 0; + return timestamp; + } - /** Returns the current sequence number. */ - uint16_t GetSequenceNumber() const { if (!init) return 0; return seqnr; } + /** Returns the current sequence number. */ + uint16_t GetSequenceNumber() const + { + if (!init) + return 0; + return seqnr; + } - /** Returns the time at which a packet was generated. - * Returns the time at which a packet was generated. This is not necessarily the time at which - * the last RTP packet was generated: if the timestamp increment was zero, the time is not updated. - */ - RTPTime GetPacketTime() const { if (!init) return RTPTime(0,0); return lastwallclocktime; } + /** Returns the time at which a packet was generated. + * Returns the time at which a packet was generated. This is not necessarily the time at which + * the last RTP packet was generated: if the timestamp increment was zero, the time is not updated. + */ + RTPTime GetPacketTime() const + { + if (!init) + return RTPTime(0, 0); + return lastwallclocktime; + } - /** Returns the RTP timestamp which corresponds to the time returned by the previous function. */ - uint32_t GetPacketTimestamp() const { if (!init) return 0; return lastrtptimestamp; } + /** Returns the RTP timestamp which corresponds to the time returned by the previous function. */ + uint32_t GetPacketTimestamp() const + { + if (!init) + return 0; + return lastrtptimestamp; + } - /** Sets a specific SSRC to be used. - * Sets a specific SSRC to be used. Does not create a new timestamp offset or sequence number - * offset. Does not reset the packet count or byte count. Think twice before using this! - */ - void AdjustSSRC(uint32_t s) { ssrc = s; } + /** Sets a specific SSRC to be used. + * Sets a specific SSRC to be used. Does not create a new timestamp offset or sequence number + * offset. Does not reset the packet count or byte count. Think twice before using this! + */ + void AdjustSSRC(uint32_t s) + { + ssrc = s; + } private: - int PrivateBuildPacket(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc,bool gotextension, - uint16_t hdrextID = 0,const void *hdrextdata = 0,size_t numhdrextwords = 0); + int PrivateBuildPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID = 0, const void *hdrextdata = 0, + size_t numhdrextwords = 0); - RTPRandom &rtprnd; - size_t maxpacksize; - uint8_t *buffer; - size_t packetlength; + RTPRandom &rtprnd; + size_t maxpacksize; + uint8_t *buffer; + size_t packetlength; - uint32_t numpayloadbytes; - uint32_t numpackets; - bool init; + uint32_t numpayloadbytes; + uint32_t numpackets; + bool init; - uint32_t ssrc; - uint32_t timestamp; - uint16_t seqnr; + uint32_t ssrc; + uint32_t timestamp; + uint16_t seqnr; - uint32_t defaulttimestampinc; - uint8_t defaultpayloadtype; - bool defaultmark; + uint32_t defaulttimestampinc; + uint8_t defaultpayloadtype; + bool defaultmark; - bool deftsset,defptset,defmarkset; + bool deftsset, defptset, defmarkset; - uint32_t csrcs[RTP_MAXCSRCS]; - int numcsrcs; + uint32_t csrcs[RTP_MAXCSRCS]; + int numcsrcs; - RTPTime lastwallclocktime; - uint32_t lastrtptimestamp; - uint32_t prevrtptimestamp; + RTPTime lastwallclocktime; + uint32_t lastrtptimestamp; + uint32_t prevrtptimestamp; }; inline int RTPPacketBuilder::SetDefaultPayloadType(uint8_t pt) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - defptset = true; - defaultpayloadtype = pt; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + defptset = true; + defaultpayloadtype = pt; + return 0; } inline int RTPPacketBuilder::SetDefaultMark(bool m) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - defmarkset = true; - defaultmark = m; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + defmarkset = true; + defaultmark = m; + return 0; } inline int RTPPacketBuilder::SetDefaultTimestampIncrement(uint32_t timestampinc) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - deftsset = true; - defaulttimestampinc = timestampinc; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + deftsset = true; + defaulttimestampinc = timestampinc; + return 0; } inline int RTPPacketBuilder::IncrementTimestamp(uint32_t inc) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - timestamp += inc; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + timestamp += inc; + return 0; } inline int RTPPacketBuilder::IncrementTimestampDefault() { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - timestamp += defaulttimestampinc; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + timestamp += defaulttimestampinc; + return 0; } } // end namespace diff --git a/qrtplib/rtprandom.cpp b/qrtplib/rtprandom.cpp index 6bdcfa206..035c6d2b9 100644 --- a/qrtplib/rtprandom.cpp +++ b/qrtplib/rtprandom.cpp @@ -1,37 +1,37 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #if defined(WIN32) && !defined(_WIN32_WCE) - #define _CRT_RAND_S +#define _CRT_RAND_S #endif // WIN32 || _WIN32_WCE #include "rtprandom.h" @@ -40,15 +40,15 @@ #include "rtprandomrand48.h" #include #ifndef WIN32 - #include +#include #else - #ifndef _WIN32_WCE - #include - #else - #include - #include - #endif // _WIN32_WINCE - #include +#ifndef _WIN32_WCE +#include +#else +#include +#include +#endif // _WIN32_WINCE +#include #endif // WIN32 namespace qrtplib @@ -56,49 +56,49 @@ namespace qrtplib uint32_t RTPRandom::PickSeed() { - uint32_t x; + uint32_t x; #if defined(WIN32) || defined(_WIN32_WINCE) #ifndef _WIN32_WCE - x = (uint32_t)_getpid(); - x += (uint32_t)time(0); - x += (uint32_t)clock(); + x = (uint32_t)_getpid(); + x += (uint32_t)time(0); + x += (uint32_t)clock(); #else - x = (uint32_t)GetCurrentProcessId(); + x = (uint32_t)GetCurrentProcessId(); - FILETIME ft; - SYSTEMTIME st; + FILETIME ft; + SYSTEMTIME st; - GetSystemTime(&st); - SystemTimeToFileTime(&st,&ft); + GetSystemTime(&st); + SystemTimeToFileTime(&st,&ft); - x += ft.dwLowDateTime; + x += ft.dwLowDateTime; #endif // _WIN32_WCE - x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); + x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); #else - x = (uint32_t)getpid(); - x += (uint32_t)time(0); - x += (uint32_t)clock(); - x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); + x = (uint32_t) getpid(); + x += (uint32_t) time(0); + x += (uint32_t) clock(); + x ^= (uint32_t) ((uint8_t *) this - (uint8_t *) 0); #endif - return x; + return x; } RTPRandom *RTPRandom::CreateDefaultRandomNumberGenerator() { #ifdef RTP_HAVE_RAND_S - RTPRandomRandS *r = new RTPRandomRandS(); + RTPRandomRandS *r = new RTPRandomRandS(); #else - RTPRandomURandom *r = new RTPRandomURandom(); + RTPRandomURandom *r = new RTPRandomURandom(); #endif // RTP_HAVE_RAND_S - RTPRandom *rRet = r; + RTPRandom *rRet = r; - if (r->Init() < 0) // fall back to rand48 - { - delete r; - rRet = new RTPRandomRand48(); - } + if (r->Init() < 0) // fall back to rand48 + { + delete r; + rRet = new RTPRandomRand48(); + } - return rRet; + return rRet; } } // end namespace diff --git a/qrtplib/rtprandom.h b/qrtplib/rtprandom.h index c3778a97c..b3c6c479c 100644 --- a/qrtplib/rtprandom.h +++ b/qrtplib/rtprandom.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtprandom.h @@ -51,26 +51,30 @@ namespace qrtplib class JRTPLIB_IMPORTEXPORT RTPRandom { public: - RTPRandom() { } - virtual ~RTPRandom() { } + RTPRandom() + { + } + virtual ~RTPRandom() + { + } - /** Returns a random eight bit value. */ - virtual uint8_t GetRandom8() = 0; + /** Returns a random eight bit value. */ + virtual uint8_t GetRandom8() = 0; - /** Returns a random sixteen bit value. */ - virtual uint16_t GetRandom16() = 0; + /** Returns a random sixteen bit value. */ + virtual uint16_t GetRandom16() = 0; - /** Returns a random thirty-two bit value. */ - virtual uint32_t GetRandom32() = 0; + /** Returns a random thirty-two bit value. */ + virtual uint32_t GetRandom32() = 0; - /** Returns a random number between $0.0$ and $1.0$. */ - virtual double GetRandomDouble() = 0; + /** Returns a random number between $0.0$ and $1.0$. */ + virtual double GetRandomDouble() = 0; - /** Can be used by subclasses to generate a seed for a random number generator. */ - uint32_t PickSeed(); + /** Can be used by subclasses to generate a seed for a random number generator. */ + uint32_t PickSeed(); - /** Allocate a default random number generator based on your platform. */ - static RTPRandom *CreateDefaultRandomNumberGenerator(); + /** Allocate a default random number generator based on your platform. */ + static RTPRandom *CreateDefaultRandomNumberGenerator(); }; } // end namespace diff --git a/qrtplib/rtprandomrand48.cpp b/qrtplib/rtprandomrand48.cpp index 43795718e..73f8a9e87 100644 --- a/qrtplib/rtprandomrand48.cpp +++ b/qrtplib/rtprandomrand48.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtprandomrand48.h" @@ -37,12 +37,12 @@ namespace qrtplib RTPRandomRand48::RTPRandomRand48() { - SetSeed(PickSeed()); + SetSeed(PickSeed()); } RTPRandomRand48::RTPRandomRand48(uint32_t seed) { - SetSeed(seed); + SetSeed(seed); } RTPRandomRand48::~RTPRandomRand48() @@ -53,57 +53,57 @@ void RTPRandomRand48::SetSeed(uint32_t seed) { #ifdef RTP_HAVE_VSUINT64SUFFIX - state = ((uint64_t)seed) << 16 | 0x330Eui64; + state = ((uint64_t)seed) << 16 | 0x330Eui64; #else - state = ((uint64_t)seed) << 16 | 0x330EULL; + state = ((uint64_t) seed) << 16 | 0x330EULL; #endif // RTP_HAVE_VSUINT64SUFFIX } uint8_t RTPRandomRand48::GetRandom8() { - uint32_t x = ((GetRandom32() >> 24)&0xff); + uint32_t x = ((GetRandom32() >> 24) & 0xff); - return (uint8_t)x; + return (uint8_t) x; } uint16_t RTPRandomRand48::GetRandom16() { - uint32_t x = ((GetRandom32() >> 16)&0xffff); + uint32_t x = ((GetRandom32() >> 16) & 0xffff); - return (uint16_t)x; + return (uint16_t) x; } uint32_t RTPRandomRand48::GetRandom32() { #ifdef RTP_HAVE_VSUINT64SUFFIX - state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; + state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; - uint32_t x = (uint32_t)((state>>16)&0xffffffffui64); + uint32_t x = (uint32_t)((state>>16)&0xffffffffui64); #else - state = ((0x5DEECE66DULL*state) + 0xBULL)&0x0000ffffffffffffULL; + state = ((0x5DEECE66DULL * state) + 0xBULL) & 0x0000ffffffffffffULL; - uint32_t x = (uint32_t)((state>>16)&0xffffffffULL); + uint32_t x = (uint32_t) ((state >> 16) & 0xffffffffULL); #endif // RTP_HAVE_VSUINT64SUFFIX - return x; + return x; } double RTPRandomRand48::GetRandomDouble() { #ifdef RTP_HAVE_VSUINT64SUFFIX - state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; + state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; - int64_t x = (int64_t)state; + int64_t x = (int64_t)state; #else - state = ((0x5DEECE66DULL*state) + 0xBULL)&0x0000ffffffffffffULL; + state = ((0x5DEECE66DULL * state) + 0xBULL) & 0x0000ffffffffffffULL; - int64_t x = (int64_t)state; + int64_t x = (int64_t) state; #endif // RTP_HAVE_VSUINT64SUFFIX - double y = 3.552713678800500929355621337890625e-15 * (double)x; - return y; + double y = 3.552713678800500929355621337890625e-15 * (double) x; + return y; } } // end namespace diff --git a/qrtplib/rtprandomrand48.h b/qrtplib/rtprandomrand48.h index 36e9a83bc..9656f4d83 100644 --- a/qrtplib/rtprandomrand48.h +++ b/qrtplib/rtprandomrand48.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtprandomrand48.h @@ -46,21 +46,21 @@ namespace qrtplib { /** A random number generator using the algorithm of the rand48 set of functions. */ -class JRTPLIB_IMPORTEXPORT RTPRandomRand48 : public RTPRandom +class JRTPLIB_IMPORTEXPORT RTPRandomRand48: public RTPRandom { public: - RTPRandomRand48(); - RTPRandomRand48(uint32_t seed); - ~RTPRandomRand48(); + RTPRandomRand48(); + RTPRandomRand48(uint32_t seed); + ~RTPRandomRand48(); - uint8_t GetRandom8(); - uint16_t GetRandom16(); - uint32_t GetRandom32(); - double GetRandomDouble(); + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); private: - void SetSeed(uint32_t seed); + void SetSeed(uint32_t seed); - uint64_t state; + uint64_t state; }; } // end namespace diff --git a/qrtplib/rtprandomrands.cpp b/qrtplib/rtprandomrands.cpp index 8d937ed66..b81749dc5 100644 --- a/qrtplib/rtprandomrands.cpp +++ b/qrtplib/rtprandomrands.cpp @@ -1,38 +1,38 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpconfig.h" #ifdef RTP_HAVE_RAND_S - #define _CRT_RAND_S +#define _CRT_RAND_S #endif // RTP_HAVE_RAND_S #include "rtprandomrands.h" @@ -52,7 +52,7 @@ namespace qrtplib RTPRandomRandS::RTPRandomRandS() { - initialized = false; + initialized = false; } RTPRandomRandS::~RTPRandomRandS() @@ -61,34 +61,34 @@ RTPRandomRandS::~RTPRandomRandS() int RTPRandomRandS::Init() { - return ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED; + return ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED; } uint8_t RTPRandomRandS::GetRandom8() { - return 0; + return 0; } uint16_t RTPRandomRandS::GetRandom16() { - return 0; + return 0; } uint32_t RTPRandomRandS::GetRandom32() { - return 0; + return 0; } double RTPRandomRandS::GetRandomDouble() { - return 0; + return 0; } #else RTPRandomRandS::RTPRandomRandS() { - initialized = false; + initialized = false; } RTPRandomRandS::~RTPRandomRandS() @@ -97,100 +97,101 @@ RTPRandomRandS::~RTPRandomRandS() int RTPRandomRandS::Init() { - if (initialized) // doesn't matter then - return 0; + if (initialized) // doesn't matter then + return 0; - HMODULE hAdvApi32 = LoadLibrary(TEXT("ADVAPI32.DLL")); - if(hAdvApi32 != NULL) - { - if(NULL != GetProcAddress( hAdvApi32, "SystemFunction036" )) // RtlGenRandom - { - initialized = true; - } - FreeLibrary(hAdvApi32); - hAdvApi32 = NULL; - } + HMODULE hAdvApi32 = LoadLibrary(TEXT("ADVAPI32.DLL")); + if(hAdvApi32 != NULL) + { + if(NULL != GetProcAddress( hAdvApi32, "SystemFunction036" )) // RtlGenRandom + { + initialized = true; + } + FreeLibrary(hAdvApi32); + hAdvApi32 = NULL; + } - if (!initialized) - return ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED; - return 0; + if (!initialized) + return ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED; + return 0; } // rand_s gives a number between 0 and UINT_MAX. We'll assume that UINT_MAX is at least 8 bits uint8_t RTPRandomRandS::GetRandom8() { - if (!initialized) - return 0; + if (!initialized) + return 0; - unsigned int r; + unsigned int r; - rand_s(&r); + rand_s(&r); - return (uint8_t)(r&0xff); + return (uint8_t)(r&0xff); } uint16_t RTPRandomRandS::GetRandom16() { - if (!initialized) - return 0; + if (!initialized) + return 0; - unsigned int r; - int shift = 0; - uint16_t x = 0; + unsigned int r; + int shift = 0; + uint16_t x = 0; - for (int i = 0 ; i < 2 ; i++, shift += 8) - { - rand_s(&r); + for (int i = 0; i < 2; i++, shift += 8) + { + rand_s(&r); - x ^= ((uint16_t)r << shift); - } + x ^= ((uint16_t)r << shift); + } - return x; + return x; } uint32_t RTPRandomRandS::GetRandom32() { - if (!initialized) - return 0; + if (!initialized) + return 0; - unsigned int r; - int shift = 0; - uint32_t x = 0; + unsigned int r; + int shift = 0; + uint32_t x = 0; - for (int i = 0 ; i < 4 ; i++, shift += 8) - { - rand_s(&r); + for (int i = 0; i < 4; i++, shift += 8) + { + rand_s(&r); - x ^= ((uint32_t)r << shift); - } + x ^= ((uint32_t)r << shift); + } - return x; + return x; } double RTPRandomRandS::GetRandomDouble() { - if (!initialized) - return 0; + if (!initialized) + return 0; - unsigned int r; - int shift = 0; - uint64_t x = 0; + unsigned int r; + int shift = 0; + uint64_t x = 0; - for (int i = 0 ; i < 8 ; i++, shift += 8) - { - rand_s(&r); + for (int i = 0; i < 8; i++, shift += 8) + { + rand_s(&r); - x ^= ((uint64_t)r << shift); - } + x ^= ((uint64_t)r << shift); + } - x &= 0x7fffffffffffffffULL; + x &= 0x7fffffffffffffffULL; - int64_t x2 = (int64_t)x; - return RTPRANDOM_2POWMIN63*(double)x2; + int64_t x2 = (int64_t)x; + return RTPRANDOM_2POWMIN63*(double)x2; } #endif // RTP_HAVE_RAND_S -} // end namespace +} + // end namespace diff --git a/qrtplib/rtprandomrands.h b/qrtplib/rtprandomrands.h index 58b66e892..e4711bea9 100644 --- a/qrtplib/rtprandomrands.h +++ b/qrtplib/rtprandomrands.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtprandomrands.h @@ -47,21 +47,21 @@ namespace qrtplib /** A random number generator which tries to use the \c rand_s function on the * Win32 platform. */ -class JRTPLIB_IMPORTEXPORT RTPRandomRandS : public RTPRandom +class JRTPLIB_IMPORTEXPORT RTPRandomRandS: public RTPRandom { public: - RTPRandomRandS(); - ~RTPRandomRandS(); + RTPRandomRandS(); + ~RTPRandomRandS(); - /** Initialize the random number generator. */ - int Init(); + /** Initialize the random number generator. */ + int Init(); - uint8_t GetRandom8(); - uint16_t GetRandom16(); - uint32_t GetRandom32(); - double GetRandomDouble(); + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); private: - bool initialized; + bool initialized; }; } // end namespace diff --git a/qrtplib/rtprandomurandom.cpp b/qrtplib/rtprandomurandom.cpp index b7b45003a..dc4512f1a 100644 --- a/qrtplib/rtprandomurandom.cpp +++ b/qrtplib/rtprandomurandom.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtprandomurandom.h" #include "rtperrors.h" @@ -38,90 +38,94 @@ namespace qrtplib RTPRandomURandom::RTPRandomURandom() { - device = 0; + device = 0; } RTPRandomURandom::~RTPRandomURandom() { - if (device) - fclose(device); + if (device) + fclose(device); } int RTPRandomURandom::Init() { - if (device) - return ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN; + if (device) + return ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN; - device = fopen("/dev/urandom","rb"); - if (device == 0) - return ERR_RTP_RTPRANDOMURANDOM_CANTOPEN; + device = fopen("/dev/urandom", "rb"); + if (device == 0) + return ERR_RTP_RTPRANDOMURANDOM_CANTOPEN; - return 0; + return 0; } uint8_t RTPRandomURandom::GetRandom8() { - if (!device) - return 0; + if (!device) + return 0; - uint8_t value; + uint8_t value; - if (fread(&value, sizeof(uint8_t), 1, device) != sizeof(uint8_t)) { - return 0; - } + if (fread(&value, sizeof(uint8_t), 1, device) != sizeof(uint8_t)) + { + return 0; + } - return value; + return value; } uint16_t RTPRandomURandom::GetRandom16() { - if (!device) - return 0; + if (!device) + return 0; - uint16_t value; + uint16_t value; - if (fread(&value, sizeof(uint16_t), 1, device) != sizeof(uint16_t)) { - return 0; - } + if (fread(&value, sizeof(uint16_t), 1, device) != sizeof(uint16_t)) + { + return 0; + } - return value; + return value; } uint32_t RTPRandomURandom::GetRandom32() { - if (!device) - return 0; + if (!device) + return 0; - uint32_t value; + uint32_t value; - if (fread(&value, sizeof(uint32_t), 1, device) != sizeof(uint32_t)) { - return 0; - } + if (fread(&value, sizeof(uint32_t), 1, device) != sizeof(uint32_t)) + { + return 0; + } - return value; + return value; } double RTPRandomURandom::GetRandomDouble() { - if (!device) - return 0; + if (!device) + return 0; - uint64_t value; + uint64_t value; - if (fread(&value, sizeof(uint64_t), 1, device) != sizeof(uint64_t)) { - return 0; - } + if (fread(&value, sizeof(uint64_t), 1, device) != sizeof(uint64_t)) + { + return 0; + } #ifdef RTP_HAVE_VSUINT64SUFFIX - value &= 0x7fffffffffffffffui64; + value &= 0x7fffffffffffffffui64; #else - value &= 0x7fffffffffffffffULL; + value &= 0x7fffffffffffffffULL; #endif // RTP_HAVE_VSUINT64SUFFIX - int64_t value2 = (int64_t)value; - double x = RTPRANDOM_2POWMIN63*(double)value2; + int64_t value2 = (int64_t) value; + double x = RTPRANDOM_2POWMIN63 * (double) value2; - return x; + return x; } diff --git a/qrtplib/rtprandomurandom.h b/qrtplib/rtprandomurandom.h index 550492c38..0129755c4 100644 --- a/qrtplib/rtprandomurandom.h +++ b/qrtplib/rtprandomurandom.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtprandomurandom.h @@ -46,21 +46,21 @@ namespace qrtplib { /** A random number generator which uses bytes delivered by the /dev/urandom device. */ -class JRTPLIB_IMPORTEXPORT RTPRandomURandom : public RTPRandom +class JRTPLIB_IMPORTEXPORT RTPRandomURandom: public RTPRandom { public: - RTPRandomURandom(); - ~RTPRandomURandom(); + RTPRandomURandom(); + ~RTPRandomURandom(); - /** Initialize the random number generator. */ - int Init(); + /** Initialize the random number generator. */ + int Init(); - uint8_t GetRandom8(); - uint16_t GetRandom16(); - uint32_t GetRandom32(); - double GetRandomDouble(); + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); private: - FILE *device; + FILE *device; }; } // end namespace diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h index b63292a25..a8de7ccc8 100644 --- a/qrtplib/rtprawpacket.h +++ b/qrtplib/rtprawpacket.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtprawpacket.h @@ -42,116 +42,135 @@ #include "rtptimeutilities.h" #include "rtpaddress.h" #include "rtptypes.h" -#include "rtpmemoryobject.h" namespace qrtplib { /** This class is used by the transmission component to store the incoming RTP and RTCP data in. */ -class JRTPLIB_IMPORTEXPORT RTPRawPacket : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTPRawPacket { public: - /** Creates an instance which stores data from \c data with length \c datalen. - * Creates an instance which stores data from \c data with length \c datalen. Only the pointer - * to the data is stored, no actual copy is made! The address from which this packet originated - * is set to \c address and the time at which the packet was received is set to \c recvtime. - * The flag which indicates whether this data is RTP or RTCP data is set to \c rtp. A memory - * manager can be installed as well. - */ - RTPRawPacket(uint8_t *data,size_t datalen,RTPAddress *address,RTPTime &recvtime,bool rtp,RTPMemoryManager *mgr = 0); - ~RTPRawPacket(); + /** Creates an instance which stores data from \c data with length \c datalen. + * Creates an instance which stores data from \c data with length \c datalen. Only the pointer + * to the data is stored, no actual copy is made! The address from which this packet originated + * is set to \c address and the time at which the packet was received is set to \c recvtime. + * The flag which indicates whether this data is RTP or RTCP data is set to \c rtp. A memory + * manager can be installed as well. + */ + RTPRawPacket(uint8_t *data, size_t datalen, RTPAddress *address, RTPTime &recvtime, bool rtp); + ~RTPRawPacket(); - /** Returns the pointer to the data which is contained in this packet. */ - uint8_t *GetData() { return packetdata; } + /** Returns the pointer to the data which is contained in this packet. */ + uint8_t *GetData() + { + return packetdata; + } - /** Returns the length of the packet described by this instance. */ - size_t GetDataLength() const { return packetdatalength; } + /** Returns the length of the packet described by this instance. */ + size_t GetDataLength() const + { + return packetdatalength; + } - /** Returns the time at which this packet was received. */ - RTPTime GetReceiveTime() const { return receivetime; } + /** Returns the time at which this packet was received. */ + RTPTime GetReceiveTime() const + { + return receivetime; + } - /** Returns the address stored in this packet. */ - const RTPAddress *GetSenderAddress() const { return senderaddress; } + /** Returns the address stored in this packet. */ + const RTPAddress *GetSenderAddress() const + { + return senderaddress; + } - /** Returns \c true if this data is RTP data, \c false if it is RTCP data. */ - bool IsRTP() const { return isrtp; } + /** Returns \c true if this data is RTP data, \c false if it is RTCP data. */ + bool IsRTP() const + { + return isrtp; + } - /** Sets the pointer to the data stored in this packet to zero. - * Sets the pointer to the data stored in this packet to zero. This will prevent - * a \c delete call for the actual data when the destructor of RTPRawPacket is called. - * This function is used by the RTPPacket and RTCPCompoundPacket classes to obtain - * the packet data (without having to copy it) and to make sure the data isn't deleted - * when the destructor of RTPRawPacket is called. - */ - void ZeroData() { packetdata = 0; packetdatalength = 0; } + /** Sets the pointer to the data stored in this packet to zero. + * Sets the pointer to the data stored in this packet to zero. This will prevent + * a \c delete call for the actual data when the destructor of RTPRawPacket is called. + * This function is used by the RTPPacket and RTCPCompoundPacket classes to obtain + * the packet data (without having to copy it) and to make sure the data isn't deleted + * when the destructor of RTPRawPacket is called. + */ + void ZeroData() + { + packetdata = 0; + packetdatalength = 0; + } - /** Allocates a number of bytes for RTP or RTCP data using the memory manager that - * was used for this raw packet instance, can be useful if the RTPRawPacket::SetData - * function will be used. */ - uint8_t *AllocateBytes(bool isrtp, int recvlen) const; + /** Allocates a number of bytes for RTP or RTCP data using the memory manager that + * was used for this raw packet instance, can be useful if the RTPRawPacket::SetData + * function will be used. */ + uint8_t *AllocateBytes(bool isrtp, int recvlen) const; - /** Deallocates the previously stored data and replaces it with the data that's - * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ - void SetData(uint8_t *data, size_t datalen); + /** Deallocates the previously stored data and replaces it with the data that's + * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ + void SetData(uint8_t *data, size_t datalen); - /** Deallocates the currently stored RTPAddress instance and replaces it - * with the one that's specified (you probably don't need this function). */ - void SetSenderAddress(RTPAddress *address); + /** Deallocates the currently stored RTPAddress instance and replaces it + * with the one that's specified (you probably don't need this function). */ + void SetSenderAddress(RTPAddress *address); private: - void DeleteData(); + void DeleteData(); - uint8_t *packetdata; - size_t packetdatalength; - RTPTime receivetime; - RTPAddress *senderaddress; - bool isrtp; + uint8_t *packetdata; + size_t packetdatalength; + RTPTime receivetime; + RTPAddress *senderaddress; + bool isrtp; }; -inline RTPRawPacket::RTPRawPacket(uint8_t *data,size_t datalen,RTPAddress *address,RTPTime &recvtime,bool rtp,RTPMemoryManager *mgr):RTPMemoryObject(mgr),receivetime(recvtime) +inline RTPRawPacket::RTPRawPacket(uint8_t *data, size_t datalen, RTPAddress *address, RTPTime &recvtime, bool rtp) : + receivetime(recvtime) { - packetdata = data; - packetdatalength = datalen; - senderaddress = address; - isrtp = rtp; + packetdata = data; + packetdatalength = datalen; + senderaddress = address; + isrtp = rtp; } inline RTPRawPacket::~RTPRawPacket() { - DeleteData(); + DeleteData(); } inline void RTPRawPacket::DeleteData() { - if (packetdata) - RTPDeleteByteArray(packetdata,GetMemoryManager()); - if (senderaddress) - RTPDelete(senderaddress,GetMemoryManager()); + if (packetdata) + delete[] packetdata; + if (senderaddress) + delete senderaddress; - packetdata = 0; - senderaddress = 0; + packetdata = 0; + senderaddress = 0; } inline uint8_t *RTPRawPacket::AllocateBytes(bool isrtp, int recvlen) const { - JRTPLIB_UNUSED(isrtp); // possibly unused - return new uint8_t[recvlen]; + JRTPLIB_UNUSED(isrtp); // possibly unused + return new uint8_t[recvlen]; } inline void RTPRawPacket::SetData(uint8_t *data, size_t datalen) { - if (packetdata) - RTPDeleteByteArray(packetdata,GetMemoryManager()); + if (packetdata) + delete[] packetdata; - packetdata = data; - packetdatalength = datalen; + packetdata = data; + packetdatalength = datalen; } inline void RTPRawPacket::SetSenderAddress(RTPAddress *address) { - if (senderaddress) - RTPDelete(senderaddress, GetMemoryManager()); + if (senderaddress) + delete senderaddress; - senderaddress = address; + senderaddress = address; } } // end namespace diff --git a/qrtplib/rtpselect.h b/qrtplib/rtpselect.h index cdba3928d..b525a87b0 100644 --- a/qrtplib/rtpselect.h +++ b/qrtplib/rtpselect.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpselect.h @@ -59,52 +59,52 @@ namespace qrtplib inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsocks, RTPTime timeout) { - using namespace std; + using namespace std; - vector fds(numsocks); + vector fds(numsocks); - for (size_t i = 0 ; i < numsocks ; i++) - { - fds[i].fd = sockets[i]; - fds[i].events = POLLIN; - fds[i].revents = 0; - readflags[i] = 0; - } + for (size_t i = 0; i < numsocks; i++) + { + fds[i].fd = sockets[i]; + fds[i].events = POLLIN; + fds[i].revents = 0; + readflags[i] = 0; + } - int timeoutmsec = -1; - if (timeout.GetDouble() >= 0) - { - double dtimeoutmsec = timeout.GetDouble()*1000.0; - if (dtimeoutmsec > (numeric_limits::max)()) // parentheses to prevent windows 'max' macro expansion - dtimeoutmsec = (numeric_limits::max)(); + int timeoutmsec = -1; + if (timeout.GetDouble() >= 0) + { + double dtimeoutmsec = timeout.GetDouble() * 1000.0; + if (dtimeoutmsec > (numeric_limits::max)()) // parentheses to prevent windows 'max' macro expansion + dtimeoutmsec = (numeric_limits::max)(); - timeoutmsec = (int)dtimeoutmsec; - } + timeoutmsec = (int) dtimeoutmsec; + } #ifdef RTP_HAVE_WSAPOLL - int status = WSAPoll(&(fds[0]), (ULONG)numsocks, timeoutmsec); - if (status < 0) - return ERR_RTP_SELECT_ERRORINPOLL; + int status = WSAPoll(&(fds[0]), (ULONG)numsocks, timeoutmsec); + if (status < 0) + return ERR_RTP_SELECT_ERRORINPOLL; #else - int status = poll(&(fds[0]), numsocks, timeoutmsec); - if (status < 0) - { - // We're just going to ignore an EINTR - if (errno == EINTR) - return 0; - return ERR_RTP_SELECT_ERRORINPOLL; - } + int status = poll(&(fds[0]), numsocks, timeoutmsec); + if (status < 0) + { + // We're just going to ignore an EINTR + if (errno == EINTR) + return 0; + return ERR_RTP_SELECT_ERRORINPOLL; + } #endif // RTP_HAVE_WSAPOLL - if (status > 0) - { - for (size_t i = 0 ; i < numsocks ; i++) - { - if (fds[i].revents) - readflags[i] = 1; - } - } - return status; + if (status > 0) + { + for (size_t i = 0; i < numsocks; i++) + { + if (fds[i].revents) + readflags[i] = 1; + } + } + return status; } } // end namespace @@ -121,67 +121,67 @@ inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsoc namespace qrtplib { -/** Wrapper function around 'select', 'poll' or 'WSAPoll', depending on the - * availability on your platform. - * - * Wrapper function around 'select', 'poll' or 'WSAPoll', depending on the - * availability on your platform. The function will check the specified - * `sockets` for incoming data and sets the flags in `readflags` if so. - * A maximum time `timeout` will be waited for data to arrive, which is - * indefinitely if set to a negative value. The function returns the number - * of sockets that have data incoming. - */ -inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsocks, RTPTime timeout) -{ - struct timeval tv; - struct timeval *pTv = 0; + /** Wrapper function around 'select', 'poll' or 'WSAPoll', depending on the + * availability on your platform. + * + * Wrapper function around 'select', 'poll' or 'WSAPoll', depending on the + * availability on your platform. The function will check the specified + * `sockets` for incoming data and sets the flags in `readflags` if so. + * A maximum time `timeout` will be waited for data to arrive, which is + * indefinitely if set to a negative value. The function returns the number + * of sockets that have data incoming. + */ + inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsocks, RTPTime timeout) + { + struct timeval tv; + struct timeval *pTv = 0; - if (timeout.GetDouble() >= 0) - { - tv.tv_sec = (long)timeout.GetSeconds(); - tv.tv_usec = timeout.GetMicroSeconds(); - pTv = &tv; - } + if (timeout.GetDouble() >= 0) + { + tv.tv_sec = (long)timeout.GetSeconds(); + tv.tv_usec = timeout.GetMicroSeconds(); + pTv = &tv; + } - fd_set fdset; - FD_ZERO(&fdset); - for (size_t i = 0 ; i < numsocks ; i++) - { + fd_set fdset; + FD_ZERO(&fdset); + for (size_t i = 0; i < numsocks; i++) + { #ifndef RTP_SOCKETTYPE_WINSOCK - const int setsize = FD_SETSIZE; - // On windows it seems that comparing the socket value to FD_SETSIZE does - // not make sense - if (sockets[i] >= setsize) - return ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE; + const int setsize = FD_SETSIZE; + // On windows it seems that comparing the socket value to FD_SETSIZE does + // not make sense + if (sockets[i] >= setsize) + return ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE; #endif // RTP_SOCKETTYPE_WINSOCK - FD_SET(sockets[i], &fdset); - readflags[i] = 0; - } + FD_SET(sockets[i], &fdset); + readflags[i] = 0; + } - int status = select(FD_SETSIZE, &fdset, 0, 0, pTv); + int status = select(FD_SETSIZE, &fdset, 0, 0, pTv); #ifdef RTP_SOCKETTYPE_WINSOCK - if (status < 0) - return ERR_RTP_SELECT_ERRORINSELECT; + if (status < 0) + return ERR_RTP_SELECT_ERRORINSELECT; #else - if (status < 0) - { - // We're just going to ignore an EINTR - if (errno == EINTR) - return 0; - return ERR_RTP_SELECT_ERRORINSELECT; - } + if (status < 0) + { + // We're just going to ignore an EINTR + if (errno == EINTR) + return 0; + return ERR_RTP_SELECT_ERRORINSELECT; + } #endif // RTP_HAVE_WSAPOLL - if (status > 0) // some descriptors were set, check them - { - for (size_t i = 0 ; i < numsocks ; i++) - { - if (FD_ISSET(sockets[i], &fdset)) - readflags[i] = 1; - } - } - return status; -} + if (status > 0) // some descriptors were set, check them + { + for (size_t i = 0; i < numsocks; i++) + { + if (FD_ISSET(sockets[i], &fdset)) + readflags[i] = 1; + } + } + return status; + } } // end namespace diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index fded610e1..e6385e314 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpsession.h" #include "rtperrors.h" @@ -40,22 +40,20 @@ #include "rtprawpacket.h" #include "rtppacket.h" #include "rtptimeutilities.h" -#include "rtpmemorymanager.h" #include "rtprandomrand48.h" #include "rtprandomrands.h" #include "rtprandomurandom.h" #ifdef RTP_SUPPORT_SENDAPP - #include "rtcpcompoundpacket.h" +#include "rtcpcompoundpacket.h" #endif // RTP_SUPPORT_SENDAPP #include "rtpinternalutils.h" #ifndef WIN32 - #include - #include +#include +#include #else - #include +#include #endif // WIN32 - #define SOURCES_LOCK #define SOURCES_UNLOCK #define BUILDER_LOCK @@ -68,598 +66,591 @@ namespace qrtplib { -RTPSession::RTPSession(RTPRandom *r,RTPMemoryManager *mgr) - : RTPMemoryObject(mgr),rtprnd(GetRandomNumberGenerator(r)),sources(*this,mgr),packetbuilder(*rtprnd,mgr),rtcpsched(sources,*rtprnd), - rtcpbuilder(sources,packetbuilder,mgr),collisionlist(mgr) +RTPSession::RTPSession(RTPRandom *r) : + rtprnd(GetRandomNumberGenerator(r)), sources(*this), packetbuilder(*rtprnd), rtcpsched(sources, *rtprnd), rtcpbuilder(sources, packetbuilder) { - // We're not going to set these flags in Create, so that the constructor of a derived class - // can already change them - m_changeIncomingData = false; - m_changeOutgoingData = false; + // We're not going to set these flags in Create, so that the constructor of a derived class + // can already change them + m_changeIncomingData = false; + m_changeOutgoingData = false; - created = false; - timeinit.Dummy(); + created = false; + timeinit.Dummy(); - //std::cout << (void *)(rtprnd) << std::endl; + //std::cout << (void *)(rtprnd) << std::endl; } RTPSession::~RTPSession() { - Destroy(); + Destroy(); - if (deletertprnd) - delete rtprnd; + if (deletertprnd) + delete rtprnd; } -int RTPSession::Create(const RTPSessionParams &sessparams,const RTPTransmissionParams *transparams /* = 0 */, - RTPTransmitter::TransmissionProtocol protocol) +int RTPSession::Create(const RTPSessionParams &sessparams, const RTPTransmissionParams *transparams /* = 0 */, RTPTransmitter::TransmissionProtocol protocol) { - int status; + int status; - if (created) - return ERR_RTP_SESSION_ALREADYCREATED; + if (created) + return ERR_RTP_SESSION_ALREADYCREATED; - usingpollthread = sessparams.IsUsingPollThread(); - needthreadsafety = sessparams.NeedThreadSafety(); - if (usingpollthread && !needthreadsafety) - return ERR_RTP_SESSION_THREADSAFETYCONFLICT; + usingpollthread = sessparams.IsUsingPollThread(); + needthreadsafety = sessparams.NeedThreadSafety(); + if (usingpollthread && !needthreadsafety) + return ERR_RTP_SESSION_THREADSAFETYCONFLICT; - useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); - sentpackets = false; + useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); + sentpackets = false; - // Check max packet size + // Check max packet size - if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) - return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; + if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) + return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; - // Initialize the transmission component + // Initialize the transmission component - rtptrans = 0; - switch(protocol) - { - case RTPTransmitter::IPv4UDPProto: - rtptrans = new RTPUDPv4Transmitter(GetMemoryManager()); - break; - case RTPTransmitter::ExternalProto: - rtptrans = new RTPExternalTransmitter(GetMemoryManager()); - break; - case RTPTransmitter::UserDefinedProto: - rtptrans = NewUserDefinedTransmitter(); - if (rtptrans == 0) - return ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL; - break; - case RTPTransmitter::TCPProto: - rtptrans = new RTPTCPTransmitter(GetMemoryManager()); - break; - default: - return ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL; - } + rtptrans = 0; + switch (protocol) + { + case RTPTransmitter::IPv4UDPProto: + rtptrans = new RTPUDPv4Transmitter(); + break; + case RTPTransmitter::ExternalProto: + rtptrans = new RTPExternalTransmitter(); + break; + case RTPTransmitter::UserDefinedProto: + rtptrans = NewUserDefinedTransmitter(); + if (rtptrans == 0) + return ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL; + break; + case RTPTransmitter::TCPProto: + rtptrans = new RTPTCPTransmitter(); + break; + default: + return ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL; + } - if (rtptrans == 0) - return ERR_RTP_OUTOFMEM; - if ((status = rtptrans->Init(needthreadsafety)) < 0) - { - RTPDelete(rtptrans,GetMemoryManager()); - return status; - } - if ((status = rtptrans->Create(maxpacksize,transparams)) < 0) - { - RTPDelete(rtptrans,GetMemoryManager()); - return status; - } + if (rtptrans == 0) + return ERR_RTP_OUTOFMEM; + if ((status = rtptrans->Init(needthreadsafety)) < 0) + { + delete rtptrans; + return status; + } + if ((status = rtptrans->Create(maxpacksize, transparams)) < 0) + { + delete rtptrans; + return status; + } - deletetransmitter = true; - return InternalCreate(sessparams); + deletetransmitter = true; + return InternalCreate(sessparams); } -int RTPSession::Create(const RTPSessionParams &sessparams,RTPTransmitter *transmitter) +int RTPSession::Create(const RTPSessionParams &sessparams, RTPTransmitter *transmitter) { - int status; + int status; - if (created) - return ERR_RTP_SESSION_ALREADYCREATED; + if (created) + return ERR_RTP_SESSION_ALREADYCREATED; - usingpollthread = sessparams.IsUsingPollThread(); - needthreadsafety = sessparams.NeedThreadSafety(); - if (usingpollthread && !needthreadsafety) - return ERR_RTP_SESSION_THREADSAFETYCONFLICT; + usingpollthread = sessparams.IsUsingPollThread(); + needthreadsafety = sessparams.NeedThreadSafety(); + if (usingpollthread && !needthreadsafety) + return ERR_RTP_SESSION_THREADSAFETYCONFLICT; - useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); - sentpackets = false; + useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); + sentpackets = false; - // Check max packet size + // Check max packet size - if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) - return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; + if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) + return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; - rtptrans = transmitter; + rtptrans = transmitter; - if ((status = rtptrans->SetMaximumPacketSize(maxpacksize)) < 0) - return status; + if ((status = rtptrans->SetMaximumPacketSize(maxpacksize)) < 0) + return status; - deletetransmitter = false; - return InternalCreate(sessparams); + deletetransmitter = false; + return InternalCreate(sessparams); } int RTPSession::InternalCreate(const RTPSessionParams &sessparams) { - int status; + int status; - // Initialize packet builder + // Initialize packet builder - if ((status = packetbuilder.Init(maxpacksize)) < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - return status; - } + if ((status = packetbuilder.Init(maxpacksize)) < 0) + { + if (deletetransmitter) + delete rtptrans; + return status; + } - if (sessparams.GetUsePredefinedSSRC()) - packetbuilder.AdjustSSRC(sessparams.GetPredefinedSSRC()); + if (sessparams.GetUsePredefinedSSRC()) + packetbuilder.AdjustSSRC(sessparams.GetPredefinedSSRC()); #ifdef RTP_SUPPORT_PROBATION - // Set probation type - sources.SetProbationType(sessparams.GetProbationType()); + // Set probation type + sources.SetProbationType(sessparams.GetProbationType()); #endif // RTP_SUPPORT_PROBATION - // Add our own ssrc to the source table + // Add our own ssrc to the source table - if ((status = sources.CreateOwnSSRC(packetbuilder.GetSSRC())) < 0) - { - packetbuilder.Destroy(); - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - return status; - } + if ((status = sources.CreateOwnSSRC(packetbuilder.GetSSRC())) < 0) + { + packetbuilder.Destroy(); + if (deletetransmitter) + delete rtptrans; + return status; + } - // Set the initial receive mode + // Set the initial receive mode - if ((status = rtptrans->SetReceiveMode(sessparams.GetReceiveMode())) < 0) - { - packetbuilder.Destroy(); - sources.Clear(); - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - return status; - } + if ((status = rtptrans->SetReceiveMode(sessparams.GetReceiveMode())) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + delete rtptrans; + return status; + } - // Init the RTCP packet builder + // Init the RTCP packet builder - double timestampunit = sessparams.GetOwnTimestampUnit(); - uint8_t buf[1024]; - size_t buflen = 1024; - std::string forcedcname = sessparams.GetCNAME(); + double timestampunit = sessparams.GetOwnTimestampUnit(); + uint8_t buf[1024]; + size_t buflen = 1024; + std::string forcedcname = sessparams.GetCNAME(); - if (forcedcname.length() == 0) - { - if ((status = CreateCNAME(buf,&buflen,sessparams.GetResolveLocalHostname())) < 0) - { - packetbuilder.Destroy(); - sources.Clear(); - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - return status; - } - } - else - { - RTP_STRNCPY((char *)buf, forcedcname.c_str(), buflen); - buf[buflen-1] = 0; - buflen = strlen((char *)buf); - } + if (forcedcname.length() == 0) + { + if ((status = CreateCNAME(buf, &buflen, sessparams.GetResolveLocalHostname())) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + delete rtptrans; + return status; + } + } + else + { + RTP_STRNCPY((char * )buf, forcedcname.c_str(), buflen); + buf[buflen - 1] = 0; + buflen = strlen((char *) buf); + } - if ((status = rtcpbuilder.Init(maxpacksize,timestampunit,buf,buflen)) < 0) - { - packetbuilder.Destroy(); - sources.Clear(); - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - return status; - } + if ((status = rtcpbuilder.Init(maxpacksize, timestampunit, buf, buflen)) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + delete rtptrans; + return status; + } - // Set scheduler parameters + // Set scheduler parameters - rtcpsched.Reset(); - rtcpsched.SetHeaderOverhead(rtptrans->GetHeaderOverhead()); + rtcpsched.Reset(); + rtcpsched.SetHeaderOverhead(rtptrans->GetHeaderOverhead()); - RTCPSchedulerParams schedparams; + RTCPSchedulerParams schedparams; - sessionbandwidth = sessparams.GetSessionBandwidth(); - controlfragment = sessparams.GetControlTrafficFraction(); + sessionbandwidth = sessparams.GetSessionBandwidth(); + controlfragment = sessparams.GetControlTrafficFraction(); - if ((status = schedparams.SetRTCPBandwidth(sessionbandwidth*controlfragment)) < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return status; - } - if ((status = schedparams.SetSenderBandwidthFraction(sessparams.GetSenderControlBandwidthFraction())) < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return status; - } - if ((status = schedparams.SetMinimumTransmissionInterval(sessparams.GetMinimumRTCPTransmissionInterval())) < 0) - { - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - sources.Clear(); - rtcpbuilder.Destroy(); - return status; - } - schedparams.SetUseHalfAtStartup(sessparams.GetUseHalfRTCPIntervalAtStartup()); - schedparams.SetRequestImmediateBYE(sessparams.GetRequestImmediateBYE()); + if ((status = schedparams.SetRTCPBandwidth(sessionbandwidth * controlfragment)) < 0) + { + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + if ((status = schedparams.SetSenderBandwidthFraction(sessparams.GetSenderControlBandwidthFraction())) < 0) + { + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + if ((status = schedparams.SetMinimumTransmissionInterval(sessparams.GetMinimumRTCPTransmissionInterval())) < 0) + { + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + schedparams.SetUseHalfAtStartup(sessparams.GetUseHalfRTCPIntervalAtStartup()); + schedparams.SetRequestImmediateBYE(sessparams.GetRequestImmediateBYE()); - rtcpsched.SetParameters(schedparams); + rtcpsched.SetParameters(schedparams); - // copy other parameters + // copy other parameters - acceptownpackets = sessparams.AcceptOwnPackets(); - membermultiplier = sessparams.GetSourceTimeoutMultiplier(); - sendermultiplier = sessparams.GetSenderTimeoutMultiplier(); - byemultiplier = sessparams.GetBYETimeoutMultiplier(); - collisionmultiplier = sessparams.GetCollisionTimeoutMultiplier(); - notemultiplier = sessparams.GetNoteTimeoutMultiplier(); + acceptownpackets = sessparams.AcceptOwnPackets(); + membermultiplier = sessparams.GetSourceTimeoutMultiplier(); + sendermultiplier = sessparams.GetSenderTimeoutMultiplier(); + byemultiplier = sessparams.GetBYETimeoutMultiplier(); + collisionmultiplier = sessparams.GetCollisionTimeoutMultiplier(); + notemultiplier = sessparams.GetNoteTimeoutMultiplier(); - // Do thread stuff if necessary + // Do thread stuff if necessary - created = true; - return 0; + created = true; + return 0; } - void RTPSession::Destroy() { - if (!created) - return; + if (!created) + return; - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - rtcpbuilder.Destroy(); - rtcpsched.Reset(); - collisionlist.Clear(); - sources.Clear(); + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + rtcpbuilder.Destroy(); + rtcpsched.Reset(); + collisionlist.Clear(); + sources.Clear(); - std::list::const_iterator it; + std::list::const_iterator it; - for (it = byepackets.begin() ; it != byepackets.end() ; it++) - RTPDelete(*it,GetMemoryManager()); - byepackets.clear(); + for (it = byepackets.begin(); it != byepackets.end(); it++) + delete *it; + byepackets.clear(); - created = false; + created = false; } -void RTPSession::BYEDestroy(const RTPTime &maxwaittime,const void *reason,size_t reasonlength) +void RTPSession::BYEDestroy(const RTPTime &maxwaittime, const void *reason, size_t reasonlength) { - if (!created) - return; + if (!created) + return; - // first, stop the thread so we have full control over all components + // first, stop the thread so we have full control over all components - RTPTime stoptime = RTPTime::CurrentTime(); - stoptime += maxwaittime; + RTPTime stoptime = RTPTime::CurrentTime(); + stoptime += maxwaittime; - // add bye packet to the list if we've sent data + // add bye packet to the list if we've sent data - RTCPCompoundPacket *pack; + RTCPCompoundPacket *pack; - if (sentpackets) - { - int status; + if (sentpackets) + { + int status; - reasonlength = (reasonlength>RTCP_BYE_MAXREASONLENGTH)?RTCP_BYE_MAXREASONLENGTH:reasonlength; - status = rtcpbuilder.BuildBYEPacket(&pack,reason,reasonlength,useSR_BYEifpossible); - if (status >= 0) - { - byepackets.push_back(pack); + reasonlength = (reasonlength > RTCP_BYE_MAXREASONLENGTH) ? RTCP_BYE_MAXREASONLENGTH : reasonlength; + status = rtcpbuilder.BuildBYEPacket(&pack, reason, reasonlength, useSR_BYEifpossible); + if (status >= 0) + { + byepackets.push_back(pack); - if (byepackets.size() == 1) - rtcpsched.ScheduleBYEPacket(pack->GetCompoundPacketLength()); - } - } + if (byepackets.size() == 1) + rtcpsched.ScheduleBYEPacket(pack->GetCompoundPacketLength()); + } + } - if (!byepackets.empty()) - { - bool done = false; + if (!byepackets.empty()) + { + bool done = false; - while (!done) - { - RTPTime curtime = RTPTime::CurrentTime(); + while (!done) + { + RTPTime curtime = RTPTime::CurrentTime(); - if (curtime >= stoptime) - done = true; + if (curtime >= stoptime) + done = true; - if (rtcpsched.IsTime()) - { - pack = *(byepackets.begin()); - byepackets.pop_front(); + if (rtcpsched.IsTime()) + { + pack = *(byepackets.begin()); + byepackets.pop_front(); - SendRTCPData(pack->GetCompoundPacketData(),pack->GetCompoundPacketLength()); + SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength()); - OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering - RTPDelete(pack,GetMemoryManager()); - if (!byepackets.empty()) // more bye packets to send, schedule them - rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); - else - done = true; - } - if (!done) - RTPTime::Wait(RTPTime(0,100000)); - } - } + delete pack; + if (!byepackets.empty()) // more bye packets to send, schedule them + rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); + else + done = true; + } + if (!done) + RTPTime::Wait(RTPTime(0, 100000)); + } + } - if (deletetransmitter) - RTPDelete(rtptrans,GetMemoryManager()); - packetbuilder.Destroy(); - rtcpbuilder.Destroy(); - rtcpsched.Reset(); - collisionlist.Clear(); - sources.Clear(); + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + rtcpbuilder.Destroy(); + rtcpsched.Reset(); + collisionlist.Clear(); + sources.Clear(); - // clear rest of bye packets - std::list::const_iterator it; + // clear rest of bye packets + std::list::const_iterator it; - for (it = byepackets.begin() ; it != byepackets.end() ; it++) - RTPDelete(*it,GetMemoryManager()); - byepackets.clear(); + for (it = byepackets.begin(); it != byepackets.end(); it++) + delete *it; + byepackets.clear(); - created = false; + created = false; } bool RTPSession::IsActive() { - return created; + return created; } uint32_t RTPSession::GetLocalSSRC() { - if (!created) - return 0; + if (!created) + return 0; - uint32_t ssrc; + uint32_t ssrc; - BUILDER_LOCK - ssrc = packetbuilder.GetSSRC(); - BUILDER_UNLOCK - return ssrc; + BUILDER_LOCK + ssrc = packetbuilder.GetSSRC(); + BUILDER_UNLOCK + return ssrc; } int RTPSession::AddDestination(const RTPAddress &addr) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->AddDestination(addr); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddDestination(addr); } int RTPSession::DeleteDestination(const RTPAddress &addr) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->DeleteDestination(addr); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteDestination(addr); } void RTPSession::ClearDestinations() { - if (!created) - return; - rtptrans->ClearDestinations(); + if (!created) + return; + rtptrans->ClearDestinations(); } bool RTPSession::SupportsMulticasting() { - if (!created) - return false; - return rtptrans->SupportsMulticasting(); + if (!created) + return false; + return rtptrans->SupportsMulticasting(); } int RTPSession::JoinMulticastGroup(const RTPAddress &addr) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->JoinMulticastGroup(addr); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->JoinMulticastGroup(addr); } int RTPSession::LeaveMulticastGroup(const RTPAddress &addr) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->LeaveMulticastGroup(addr); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->LeaveMulticastGroup(addr); } void RTPSession::LeaveAllMulticastGroups() { - if (!created) - return; - rtptrans->LeaveAllMulticastGroups(); + if (!created) + return; + rtptrans->LeaveAllMulticastGroups(); } -int RTPSession::SendPacket(const void *data,size_t len) +int RTPSession::SendPacket(const void *data, size_t len) { - int status; + int status; - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK - if ((status = packetbuilder.BuildPacket(data,len)) < 0) - { - BUILDER_UNLOCK - return status; - } - if ((status = SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) - { - BUILDER_UNLOCK - return status; - } - BUILDER_UNLOCK + BUILDER_LOCK + if ((status = packetbuilder.BuildPacket(data, len)) < 0) + { + BUILDER_UNLOCK + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK - SOURCES_LOCK - sources.SentRTPPacket(); - SOURCES_UNLOCK - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK - return 0; + SOURCES_LOCK + sources.SentRTPPacket(); + SOURCES_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + return 0; } -int RTPSession::SendPacket(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc) +int RTPSession::SendPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc) { - int status; + int status; - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK - if ((status = packetbuilder.BuildPacket(data,len,pt,mark,timestampinc)) < 0) - { - BUILDER_UNLOCK - return status; - } - if ((status = SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) - { - BUILDER_UNLOCK - return status; - } - BUILDER_UNLOCK + BUILDER_LOCK + if ((status = packetbuilder.BuildPacket(data, len, pt, mark, timestampinc)) < 0) + { + BUILDER_UNLOCK + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK - SOURCES_LOCK - sources.SentRTPPacket(); - SOURCES_UNLOCK - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK - return 0; + SOURCES_LOCK + sources.SentRTPPacket(); + SOURCES_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + return 0; } -int RTPSession::SendPacketEx(const void *data,size_t len, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +int RTPSession::SendPacketEx(const void *data, size_t len, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords) { - int status; + int status; - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK - if ((status = packetbuilder.BuildPacketEx(data,len,hdrextID,hdrextdata,numhdrextwords)) < 0) - { - BUILDER_UNLOCK - return status; - } - if ((status = SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) - { - BUILDER_UNLOCK - return status; - } - BUILDER_UNLOCK + BUILDER_LOCK + if ((status = packetbuilder.BuildPacketEx(data, len, hdrextID, hdrextdata, numhdrextwords)) < 0) + { + BUILDER_UNLOCK + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK - SOURCES_LOCK - sources.SentRTPPacket(); - SOURCES_UNLOCK - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK - return 0; + SOURCES_LOCK + sources.SentRTPPacket(); + SOURCES_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + return 0; } -int RTPSession::SendPacketEx(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) +int RTPSession::SendPacketEx(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords) { - int status; + int status; - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK - if ((status = packetbuilder.BuildPacketEx(data,len,pt,mark,timestampinc,hdrextID,hdrextdata,numhdrextwords)) < 0) - { - BUILDER_UNLOCK - return status; - } - if ((status = SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) - { - BUILDER_UNLOCK - return status; - } - BUILDER_UNLOCK + BUILDER_LOCK + if ((status = packetbuilder.BuildPacketEx(data, len, pt, mark, timestampinc, hdrextID, hdrextdata, numhdrextwords)) < 0) + { + BUILDER_UNLOCK + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK - SOURCES_LOCK - sources.SentRTPPacket(); - SOURCES_UNLOCK - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK - return 0; + SOURCES_LOCK + sources.SentRTPPacket(); + SOURCES_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK + return 0; } #ifdef RTP_SUPPORT_SENDAPP int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, size_t appdatalen) { - int status; + int status; - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK - uint32_t ssrc = packetbuilder.GetSSRC(); - BUILDER_UNLOCK + BUILDER_LOCK + uint32_t ssrc = packetbuilder.GetSSRC(); + BUILDER_UNLOCK - RTCPCompoundPacketBuilder pb(GetMemoryManager()); + RTCPCompoundPacketBuilder pb; - status = pb.InitBuild(maxpacksize); + status = pb.InitBuild(maxpacksize); - if(status < 0) - return status; + if (status < 0) + return status; - //first packet in an rtcp compound packet should always be SR or RR - if((status = pb.StartReceiverReport(ssrc)) < 0) - return status; + //first packet in an rtcp compound packet should always be SR or RR + if ((status = pb.StartReceiverReport(ssrc)) < 0) + return status; - //add SDES packet with CNAME item - if ((status = pb.AddSDESSource(ssrc)) < 0) - return status; + //add SDES packet with CNAME item + if ((status = pb.AddSDESSource(ssrc)) < 0) + return status; - BUILDER_LOCK - size_t owncnamelen = 0; - uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); + BUILDER_LOCK + size_t owncnamelen = 0; + uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); - if ((status = pb.AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) - { - BUILDER_UNLOCK - return status; - } - BUILDER_UNLOCK + if ((status = pb.AddSDESNormalItem(RTCPSDESPacket::CNAME, owncname, owncnamelen)) < 0) + { + BUILDER_UNLOCK + return status; + } + BUILDER_UNLOCK - //add our application specific packet - if((status = pb.AddAPPPacket(subtype, ssrc, name, appdata, appdatalen)) < 0) - return status; + //add our application specific packet + if ((status = pb.AddAPPPacket(subtype, ssrc, name, appdata, appdatalen)) < 0) + return status; - if((status = pb.EndBuild()) < 0) - return status; + if ((status = pb.EndBuild()) < 0) + return status; - //send packet - status = SendRTCPData(pb.GetCompoundPacketData(),pb.GetCompoundPacketLength()); - if(status < 0) - return status; + //send packet + status = SendRTCPData(pb.GetCompoundPacketData(), pb.GetCompoundPacketLength()); + if (status < 0) + return status; - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK - return pb.GetCompoundPacketLength(); + return pb.GetCompoundPacketLength(); } #endif // RTP_SUPPORT_SENDAPP @@ -668,340 +659,340 @@ int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const int RTPSession::SendUnknownPacket(bool sr, uint8_t payload_type, uint8_t subtype, const void *data, size_t len) { - int status; + int status; - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK - uint32_t ssrc = packetbuilder.GetSSRC(); - BUILDER_UNLOCK + BUILDER_LOCK + uint32_t ssrc = packetbuilder.GetSSRC(); + BUILDER_UNLOCK - RTCPCompoundPacketBuilder* rtcpcomppack = new RTCPCompoundPacketBuilder(GetMemoryManager()); - if (rtcpcomppack == 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } + RTCPCompoundPacketBuilder* rtcpcomppack = new RTCPCompoundPacketBuilder(GetMemoryManager()); + if (rtcpcomppack == 0) + { + delete rtcpcomppack; + return ERR_RTP_OUTOFMEM; + } - status = rtcpcomppack->InitBuild(maxpacksize); - if(status < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + status = rtcpcomppack->InitBuild(maxpacksize); + if(status < 0) + { + delete rtcpcomppack; + return status; + } - if (sr) - { - // setup for the rtcp - RTPTime rtppacktime = packetbuilder.GetPacketTime(); - uint32_t rtppacktimestamp = packetbuilder.GetPacketTimestamp(); - uint32_t packcount = packetbuilder.GetPacketCount(); - uint32_t octetcount = packetbuilder.GetPayloadOctetCount(); - RTPTime curtime = RTPTime::CurrentTime(); - RTPTime diff = curtime; - diff -= rtppacktime; - diff += 1; // add transmission delay or RTPTime(0,0); + if (sr) + { + // setup for the rtcp + RTPTime rtppacktime = packetbuilder.GetPacketTime(); + uint32_t rtppacktimestamp = packetbuilder.GetPacketTimestamp(); + uint32_t packcount = packetbuilder.GetPacketCount(); + uint32_t octetcount = packetbuilder.GetPayloadOctetCount(); + RTPTime curtime = RTPTime::CurrentTime(); + RTPTime diff = curtime; + diff -= rtppacktime; + diff += 1;// add transmission delay or RTPTime(0,0); - double timestampunit = 90000; + double timestampunit = 90000; - uint32_t tsdiff = (uint32_t)((diff.GetDouble()/timestampunit)+0.5); - uint32_t rtptimestamp = rtppacktimestamp+tsdiff; - RTPNTPTime ntptimestamp = curtime.GetNTPTime(); + uint32_t tsdiff = (uint32_t)((diff.GetDouble()/timestampunit)+0.5); + uint32_t rtptimestamp = rtppacktimestamp+tsdiff; + RTPNTPTime ntptimestamp = curtime.GetNTPTime(); - //first packet in an rtcp compound packet should always be SR or RR - if((status = rtcpcomppack->StartSenderReport(ssrc,ntptimestamp,rtptimestamp,packcount,octetcount)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } - } - else - { - //first packet in an rtcp compound packet should always be SR or RR - if((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + //first packet in an rtcp compound packet should always be SR or RR + if((status = rtcpcomppack->StartSenderReport(ssrc,ntptimestamp,rtptimestamp,packcount,octetcount)) < 0) + { + delete rtcpcomppack; + return status; + } + } + else + { + //first packet in an rtcp compound packet should always be SR or RR + if((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) + { + delete rtcpcomppack; + return status; + } - } + } - //add SDES packet with CNAME item - if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + //add SDES packet with CNAME item + if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) + { + delete rtcpcomppack; + return status; + } - BUILDER_LOCK - size_t owncnamelen = 0; - uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); + BUILDER_LOCK + size_t owncnamelen = 0; + uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) - { - BUILDER_UNLOCK - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } - BUILDER_UNLOCK + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) + { + BUILDER_UNLOCK + delete rtcpcomppack; + return status; + } + BUILDER_UNLOCK - //add our packet - if((status = rtcpcomppack->AddUnknownPacket(payload_type, subtype, ssrc, data, len)) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + //add our packet + if((status = rtcpcomppack->AddUnknownPacket(payload_type, subtype, ssrc, data, len)) < 0) + { + delete rtcpcomppack; + return status; + } - if((status = rtcpcomppack->EndBuild()) < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + if((status = rtcpcomppack->EndBuild()) < 0) + { + delete rtcpcomppack; + return status; + } - //send packet - status = SendRTCPData(rtcpcomppack->GetCompoundPacketData(), rtcpcomppack->GetCompoundPacketLength()); - if(status < 0) - { - RTPDelete(rtcpcomppack,GetMemoryManager()); - return status; - } + //send packet + status = SendRTCPData(rtcpcomppack->GetCompoundPacketData(), rtcpcomppack->GetCompoundPacketLength()); + if(status < 0) + { + delete rtcpcomppack; + return status; + } - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK + PACKSENT_LOCK + sentpackets = true; + PACKSENT_UNLOCK - OnSendRTCPCompoundPacket(rtcpcomppack); // we'll place this after the actual send to avoid tampering + OnSendRTCPCompoundPacket(rtcpcomppack); // we'll place this after the actual send to avoid tampering - int retlen = rtcpcomppack->GetCompoundPacketLength(); + int retlen = rtcpcomppack->GetCompoundPacketLength(); - RTPDelete(rtcpcomppack,GetMemoryManager()); - return retlen; + delete rtcpcomppack; + return retlen; } #endif // RTP_SUPPORT_RTCPUNKNOWN int RTPSession::SendRawData(const void *data, size_t len, bool usertpchannel) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; + int status; - if (usertpchannel) - status = rtptrans->SendRTPData(data, len); - else - status = rtptrans->SendRTCPData(data, len); - return status; + if (usertpchannel) + status = rtptrans->SendRTPData(data, len); + else + status = rtptrans->SendRTCPData(data, len); + return status; } int RTPSession::SetDefaultPayloadType(uint8_t pt) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; + int status; - BUILDER_LOCK - status = packetbuilder.SetDefaultPayloadType(pt); - BUILDER_UNLOCK - return status; + BUILDER_LOCK + status = packetbuilder.SetDefaultPayloadType(pt); + BUILDER_UNLOCK + return status; } int RTPSession::SetDefaultMark(bool m) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; + int status; - BUILDER_LOCK - status = packetbuilder.SetDefaultMark(m); - BUILDER_UNLOCK - return status; + BUILDER_LOCK + status = packetbuilder.SetDefaultMark(m); + BUILDER_UNLOCK + return status; } int RTPSession::SetDefaultTimestampIncrement(uint32_t timestampinc) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; + int status; - BUILDER_LOCK - status = packetbuilder.SetDefaultTimestampIncrement(timestampinc); - BUILDER_UNLOCK - return status; + BUILDER_LOCK + status = packetbuilder.SetDefaultTimestampIncrement(timestampinc); + BUILDER_UNLOCK + return status; } int RTPSession::IncrementTimestamp(uint32_t inc) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; + int status; - BUILDER_LOCK - status = packetbuilder.IncrementTimestamp(inc); - BUILDER_UNLOCK - return status; + BUILDER_LOCK + status = packetbuilder.IncrementTimestamp(inc); + BUILDER_UNLOCK + return status; } int RTPSession::IncrementTimestampDefault() { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; + int status; - BUILDER_LOCK - status = packetbuilder.IncrementTimestampDefault(); - BUILDER_UNLOCK - return status; + BUILDER_LOCK + status = packetbuilder.IncrementTimestampDefault(); + BUILDER_UNLOCK + return status; } int RTPSession::SetPreTransmissionDelay(const RTPTime &delay) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; + int status; - BUILDER_LOCK - status = rtcpbuilder.SetPreTransmissionDelay(delay); - BUILDER_UNLOCK - return status; + BUILDER_LOCK + status = rtcpbuilder.SetPreTransmissionDelay(delay); + BUILDER_UNLOCK + return status; } RTPTransmissionInfo *RTPSession::GetTransmissionInfo() { - if (!created) - return 0; - return rtptrans->GetTransmissionInfo(); + if (!created) + return 0; + return rtptrans->GetTransmissionInfo(); } void RTPSession::DeleteTransmissionInfo(RTPTransmissionInfo *inf) { - if (!created) - return; - rtptrans->DeleteTransmissionInfo(inf); + if (!created) + return; + rtptrans->DeleteTransmissionInfo(inf); } int RTPSession::Poll() { - int status; + int status; - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - if (usingpollthread) - return ERR_RTP_SESSION_USINGPOLLTHREAD; - if ((status = rtptrans->Poll()) < 0) - return status; - return ProcessPolledData(); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + if (usingpollthread) + return ERR_RTP_SESSION_USINGPOLLTHREAD; + if ((status = rtptrans->Poll()) < 0) + return status; + return ProcessPolledData(); } -int RTPSession::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +int RTPSession::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - if (usingpollthread) - return ERR_RTP_SESSION_USINGPOLLTHREAD; - return rtptrans->WaitForIncomingData(delay,dataavailable); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + if (usingpollthread) + return ERR_RTP_SESSION_USINGPOLLTHREAD; + return rtptrans->WaitForIncomingData(delay, dataavailable); } int RTPSession::AbortWait() { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - if (usingpollthread) - return ERR_RTP_SESSION_USINGPOLLTHREAD; - return rtptrans->AbortWait(); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + if (usingpollthread) + return ERR_RTP_SESSION_USINGPOLLTHREAD; + return rtptrans->AbortWait(); } RTPTime RTPSession::GetRTCPDelay() { - if (!created) - return RTPTime(0,0); - if (usingpollthread) - return RTPTime(0,0); + if (!created) + return RTPTime(0, 0); + if (usingpollthread) + return RTPTime(0, 0); - SOURCES_LOCK - SCHED_LOCK - RTPTime t = rtcpsched.GetTransmissionDelay(); - SCHED_UNLOCK - SOURCES_UNLOCK - return t; + SOURCES_LOCK + SCHED_LOCK + RTPTime t = rtcpsched.GetTransmissionDelay(); + SCHED_UNLOCK + SOURCES_UNLOCK + return t; } int RTPSession::BeginDataAccess() { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - SOURCES_LOCK - return 0; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + SOURCES_LOCK + return 0; } bool RTPSession::GotoFirstSource() { - if (!created) - return false; - return sources.GotoFirstSource(); + if (!created) + return false; + return sources.GotoFirstSource(); } bool RTPSession::GotoNextSource() { - if (!created) - return false; - return sources.GotoNextSource(); + if (!created) + return false; + return sources.GotoNextSource(); } bool RTPSession::GotoPreviousSource() { - if (!created) - return false; - return sources.GotoPreviousSource(); + if (!created) + return false; + return sources.GotoPreviousSource(); } bool RTPSession::GotoFirstSourceWithData() { - if (!created) - return false; - return sources.GotoFirstSourceWithData(); + if (!created) + return false; + return sources.GotoFirstSourceWithData(); } bool RTPSession::GotoNextSourceWithData() { - if (!created) - return false; - return sources.GotoNextSourceWithData(); + if (!created) + return false; + return sources.GotoNextSourceWithData(); } bool RTPSession::GotoPreviousSourceWithData() { - if (!created) - return false; - return sources.GotoPreviousSourceWithData(); + if (!created) + return false; + return sources.GotoPreviousSourceWithData(); } RTPSourceData *RTPSession::GetCurrentSourceInfo() { - if (!created) - return 0; - return sources.GetCurrentSourceInfo(); + if (!created) + return 0; + return sources.GetCurrentSourceInfo(); } RTPSourceData *RTPSession::GetSourceInfo(uint32_t ssrc) { - if (!created) - return 0; - return sources.GetSourceInfo(ssrc); + if (!created) + return 0; + return sources.GetSourceInfo(ssrc); } RTPPacket *RTPSession::GetNextPacket() { - if (!created) - return 0; - return sources.GetNextPacket(); + if (!created) + return 0; + return sources.GetNextPacket(); } uint16_t RTPSession::GetNextSequenceNumber() const @@ -1011,586 +1002,586 @@ uint16_t RTPSession::GetNextSequenceNumber() const void RTPSession::DeletePacket(RTPPacket *p) { - RTPDelete(p,GetMemoryManager()); + delete p; } int RTPSession::EndDataAccess() { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - SOURCES_UNLOCK - return 0; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + SOURCES_UNLOCK + return 0; } int RTPSession::SetReceiveMode(RTPTransmitter::ReceiveMode m) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->SetReceiveMode(m); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->SetReceiveMode(m); } int RTPSession::AddToIgnoreList(const RTPAddress &addr) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->AddToIgnoreList(addr); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddToIgnoreList(addr); } int RTPSession::DeleteFromIgnoreList(const RTPAddress &addr) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->DeleteFromIgnoreList(addr); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteFromIgnoreList(addr); } void RTPSession::ClearIgnoreList() { - if (!created) - return; - rtptrans->ClearIgnoreList(); + if (!created) + return; + rtptrans->ClearIgnoreList(); } int RTPSession::AddToAcceptList(const RTPAddress &addr) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->AddToAcceptList(addr); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddToAcceptList(addr); } int RTPSession::DeleteFromAcceptList(const RTPAddress &addr) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - return rtptrans->DeleteFromAcceptList(addr); + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteFromAcceptList(addr); } void RTPSession::ClearAcceptList() { - if (!created) - return; - rtptrans->ClearAcceptList(); + if (!created) + return; + rtptrans->ClearAcceptList(); } int RTPSession::SetMaximumPacketSize(size_t s) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - if (s < RTP_MINPACKETSIZE) - return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; + if (s < RTP_MINPACKETSIZE) + return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; - int status; + int status; - if ((status = rtptrans->SetMaximumPacketSize(s)) < 0) - return status; + if ((status = rtptrans->SetMaximumPacketSize(s)) < 0) + return status; - BUILDER_LOCK - if ((status = packetbuilder.SetMaximumPacketSize(s)) < 0) - { - BUILDER_UNLOCK - // restore previous max packet size - rtptrans->SetMaximumPacketSize(maxpacksize); - return status; - } - if ((status = rtcpbuilder.SetMaximumPacketSize(s)) < 0) - { - // restore previous max packet size - packetbuilder.SetMaximumPacketSize(maxpacksize); - BUILDER_UNLOCK - rtptrans->SetMaximumPacketSize(maxpacksize); - return status; - } - BUILDER_UNLOCK - maxpacksize = s; - return 0; + BUILDER_LOCK + if ((status = packetbuilder.SetMaximumPacketSize(s)) < 0) + { + BUILDER_UNLOCK + // restore previous max packet size + rtptrans->SetMaximumPacketSize(maxpacksize); + return status; + } + if ((status = rtcpbuilder.SetMaximumPacketSize(s)) < 0) + { + // restore previous max packet size + packetbuilder.SetMaximumPacketSize(maxpacksize); + BUILDER_UNLOCK + rtptrans->SetMaximumPacketSize(maxpacksize); + return status; + } + BUILDER_UNLOCK + maxpacksize = s; + return 0; } int RTPSession::SetSessionBandwidth(double bw) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; - SCHED_LOCK - RTCPSchedulerParams p = rtcpsched.GetParameters(); - status = p.SetRTCPBandwidth(bw*controlfragment); - if (status >= 0) - { - rtcpsched.SetParameters(p); - sessionbandwidth = bw; - } - SCHED_UNLOCK - return status; + int status; + SCHED_LOCK + RTCPSchedulerParams p = rtcpsched.GetParameters(); + status = p.SetRTCPBandwidth(bw * controlfragment); + if (status >= 0) + { + rtcpsched.SetParameters(p); + sessionbandwidth = bw; + } + SCHED_UNLOCK + return status; } int RTPSession::SetTimestampUnit(double u) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; - int status; + int status; - BUILDER_LOCK - status = rtcpbuilder.SetTimestampUnit(u); - BUILDER_UNLOCK - return status; + BUILDER_LOCK + status = rtcpbuilder.SetTimestampUnit(u); + BUILDER_UNLOCK + return status; } void RTPSession::SetNameInterval(int count) { - if (!created) - return; - BUILDER_LOCK - rtcpbuilder.SetNameInterval(count); - BUILDER_UNLOCK + if (!created) + return; + BUILDER_LOCK + rtcpbuilder.SetNameInterval(count); +BUILDER_UNLOCK } void RTPSession::SetEMailInterval(int count) { - if (!created) - return; - BUILDER_LOCK - rtcpbuilder.SetEMailInterval(count); - BUILDER_UNLOCK +if (!created) + return; +BUILDER_LOCK +rtcpbuilder.SetEMailInterval(count); +BUILDER_UNLOCK } void RTPSession::SetLocationInterval(int count) { - if (!created) - return; - BUILDER_LOCK - rtcpbuilder.SetLocationInterval(count); - BUILDER_UNLOCK +if (!created) +return; +BUILDER_LOCK +rtcpbuilder.SetLocationInterval(count); +BUILDER_UNLOCK } void RTPSession::SetPhoneInterval(int count) { - if (!created) - return; - BUILDER_LOCK - rtcpbuilder.SetPhoneInterval(count); - BUILDER_UNLOCK +if (!created) +return; +BUILDER_LOCK +rtcpbuilder.SetPhoneInterval(count); +BUILDER_UNLOCK } void RTPSession::SetToolInterval(int count) { - if (!created) - return; - BUILDER_LOCK - rtcpbuilder.SetToolInterval(count); - BUILDER_UNLOCK +if (!created) +return; +BUILDER_LOCK +rtcpbuilder.SetToolInterval(count); +BUILDER_UNLOCK } void RTPSession::SetNoteInterval(int count) { - if (!created) - return; - BUILDER_LOCK - rtcpbuilder.SetNoteInterval(count); - BUILDER_UNLOCK +if (!created) +return; +BUILDER_LOCK +rtcpbuilder.SetNoteInterval(count); +BUILDER_UNLOCK } -int RTPSession::SetLocalName(const void *s,size_t len) +int RTPSession::SetLocalName(const void *s, size_t len) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; +if (!created) +return ERR_RTP_SESSION_NOTCREATED; - int status; - BUILDER_LOCK - status = rtcpbuilder.SetLocalName(s,len); - BUILDER_UNLOCK - return status; +int status; +BUILDER_LOCK +status = rtcpbuilder.SetLocalName(s, len); +BUILDER_UNLOCK +return status; } -int RTPSession::SetLocalEMail(const void *s,size_t len) +int RTPSession::SetLocalEMail(const void *s, size_t len) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; +if (!created) +return ERR_RTP_SESSION_NOTCREATED; - int status; - BUILDER_LOCK - status = rtcpbuilder.SetLocalEMail(s,len); - BUILDER_UNLOCK - return status; +int status; +BUILDER_LOCK +status = rtcpbuilder.SetLocalEMail(s, len); +BUILDER_UNLOCK +return status; } -int RTPSession::SetLocalLocation(const void *s,size_t len) +int RTPSession::SetLocalLocation(const void *s, size_t len) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; +if (!created) +return ERR_RTP_SESSION_NOTCREATED; - int status; - BUILDER_LOCK - status = rtcpbuilder.SetLocalLocation(s,len); - BUILDER_UNLOCK - return status; +int status; +BUILDER_LOCK +status = rtcpbuilder.SetLocalLocation(s, len); +BUILDER_UNLOCK +return status; } -int RTPSession::SetLocalPhone(const void *s,size_t len) +int RTPSession::SetLocalPhone(const void *s, size_t len) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; +if (!created) +return ERR_RTP_SESSION_NOTCREATED; - int status; - BUILDER_LOCK - status = rtcpbuilder.SetLocalPhone(s,len); - BUILDER_UNLOCK - return status; +int status; +BUILDER_LOCK +status = rtcpbuilder.SetLocalPhone(s, len); +BUILDER_UNLOCK +return status; } -int RTPSession::SetLocalTool(const void *s,size_t len) +int RTPSession::SetLocalTool(const void *s, size_t len) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; +if (!created) +return ERR_RTP_SESSION_NOTCREATED; - int status; - BUILDER_LOCK - status = rtcpbuilder.SetLocalTool(s,len); - BUILDER_UNLOCK - return status; +int status; +BUILDER_LOCK +status = rtcpbuilder.SetLocalTool(s, len); +BUILDER_UNLOCK +return status; } -int RTPSession::SetLocalNote(const void *s,size_t len) +int RTPSession::SetLocalNote(const void *s, size_t len) { - if (!created) - return ERR_RTP_SESSION_NOTCREATED; +if (!created) +return ERR_RTP_SESSION_NOTCREATED; - int status; - BUILDER_LOCK - status = rtcpbuilder.SetLocalNote(s,len); - BUILDER_UNLOCK - return status; +int status; +BUILDER_LOCK +status = rtcpbuilder.SetLocalNote(s, len); +BUILDER_UNLOCK +return status; } int RTPSession::ProcessPolledData() { - RTPRawPacket *rawpack; - int status; +RTPRawPacket *rawpack; +int status; - SOURCES_LOCK - while ((rawpack = rtptrans->GetNextPacket()) != 0) - { - if (m_changeIncomingData) - { - // Provide a way to change incoming data, for decryption for example - if (!OnChangeIncomingData(rawpack)) - { - RTPDelete(rawpack,GetMemoryManager()); - continue; - } - } +SOURCES_LOCK +while ((rawpack = rtptrans->GetNextPacket()) != 0) +{ +if (m_changeIncomingData) +{ + // Provide a way to change incoming data, for decryption for example +if (!OnChangeIncomingData(rawpack)) +{ +delete rawpack; +continue; +} +} - sources.ClearOwnCollisionFlag(); +sources.ClearOwnCollisionFlag(); - // since our sources instance also uses the scheduler (analysis of incoming packets) - // we'll lock it - SCHED_LOCK - if ((status = sources.ProcessRawPacket(rawpack,rtptrans,acceptownpackets)) < 0) - { - SCHED_UNLOCK - SOURCES_UNLOCK - RTPDelete(rawpack,GetMemoryManager()); - return status; - } - SCHED_UNLOCK + // since our sources instance also uses the scheduler (analysis of incoming packets) + // we'll lock it +SCHED_LOCK +if ((status = sources.ProcessRawPacket(rawpack, rtptrans, acceptownpackets)) < 0) +{ +SCHED_UNLOCK +SOURCES_UNLOCK +delete rawpack; +return status; +} +SCHED_UNLOCK - if (sources.DetectedOwnCollision()) // collision handling! - { - bool created; +if (sources.DetectedOwnCollision()) // collision handling! +{ +bool created; - if ((status = collisionlist.UpdateAddress(rawpack->GetSenderAddress(),rawpack->GetReceiveTime(),&created)) < 0) - { - SOURCES_UNLOCK - RTPDelete(rawpack,GetMemoryManager()); - return status; - } +if ((status = collisionlist.UpdateAddress(rawpack->GetSenderAddress(), rawpack->GetReceiveTime(), &created)) < 0) +{ +SOURCES_UNLOCK +delete rawpack; +return status; +} - if (created) // first time we've encountered this address, send bye packet and - { // change our own SSRC - PACKSENT_LOCK - bool hassentpackets = sentpackets; - PACKSENT_UNLOCK +if (created) // first time we've encountered this address, send bye packet and +{ // change our own SSRC +PACKSENT_LOCK +bool hassentpackets = sentpackets; +PACKSENT_UNLOCK - if (hassentpackets) - { - // Only send BYE packet if we've actually sent data using this - // SSRC +if (hassentpackets) +{ + // Only send BYE packet if we've actually sent data using this + // SSRC - RTCPCompoundPacket *rtcpcomppack; +RTCPCompoundPacket *rtcpcomppack; - BUILDER_LOCK - if ((status = rtcpbuilder.BuildBYEPacket(&rtcpcomppack,0,0,useSR_BYEifpossible)) < 0) - { - BUILDER_UNLOCK - SOURCES_UNLOCK - RTPDelete(rawpack,GetMemoryManager()); - return status; - } - BUILDER_UNLOCK +BUILDER_LOCK +if ((status = rtcpbuilder.BuildBYEPacket(&rtcpcomppack, 0, 0, useSR_BYEifpossible)) < 0) +{ +BUILDER_UNLOCK +SOURCES_UNLOCK +delete rawpack; +return status; +} +BUILDER_UNLOCK - byepackets.push_back(rtcpcomppack); - if (byepackets.size() == 1) // was the first packet, schedule a BYE packet (otherwise there's already one scheduled) - { - SCHED_LOCK - rtcpsched.ScheduleBYEPacket(rtcpcomppack->GetCompoundPacketLength()); - SCHED_UNLOCK - } - } - // bye packet is built and scheduled, now change our SSRC - // and reset the packet count in the transmitter +byepackets.push_back(rtcpcomppack); +if (byepackets.size() == 1) // was the first packet, schedule a BYE packet (otherwise there's already one scheduled) +{ +SCHED_LOCK +rtcpsched.ScheduleBYEPacket(rtcpcomppack->GetCompoundPacketLength()); +SCHED_UNLOCK +} +} + // bye packet is built and scheduled, now change our SSRC + // and reset the packet count in the transmitter - BUILDER_LOCK - uint32_t newssrc = packetbuilder.CreateNewSSRC(sources); - BUILDER_UNLOCK +BUILDER_LOCK +uint32_t newssrc = packetbuilder.CreateNewSSRC(sources); +BUILDER_UNLOCK - PACKSENT_LOCK - sentpackets = false; - PACKSENT_UNLOCK +PACKSENT_LOCK +sentpackets = false; +PACKSENT_UNLOCK // remove old entry in source table and add new one - if ((status = sources.DeleteOwnSSRC()) < 0) - { - SOURCES_UNLOCK - RTPDelete(rawpack,GetMemoryManager()); - return status; - } - if ((status = sources.CreateOwnSSRC(newssrc)) < 0) - { - SOURCES_UNLOCK - RTPDelete(rawpack,GetMemoryManager()); - return status; - } - } - } - RTPDelete(rawpack,GetMemoryManager()); - } - - SCHED_LOCK - RTPTime d = rtcpsched.CalculateDeterministicInterval(false); - SCHED_UNLOCK - - RTPTime t = RTPTime::CurrentTime(); - double Td = d.GetDouble(); - RTPTime sendertimeout = RTPTime(Td*sendermultiplier); - RTPTime generaltimeout = RTPTime(Td*membermultiplier); - RTPTime byetimeout = RTPTime(Td*byemultiplier); - RTPTime colltimeout = RTPTime(Td*collisionmultiplier); - RTPTime notetimeout = RTPTime(Td*notemultiplier); - - sources.MultipleTimeouts(t,sendertimeout,byetimeout,generaltimeout,notetimeout); - collisionlist.Timeout(t,colltimeout); - - // We'll check if it's time for RTCP stuff - - SCHED_LOCK - bool istime = rtcpsched.IsTime(); - SCHED_UNLOCK - - if (istime) - { - RTCPCompoundPacket *pack; - - // we'll check if there's a bye packet to send, or just a normal packet - - if (byepackets.empty()) - { - BUILDER_LOCK - if ((status = rtcpbuilder.BuildNextPacket(&pack)) < 0) - { - BUILDER_UNLOCK - SOURCES_UNLOCK - return status; - } - BUILDER_UNLOCK - if ((status = SendRTCPData(pack->GetCompoundPacketData(),pack->GetCompoundPacketLength())) < 0) - { - SOURCES_UNLOCK - RTPDelete(pack,GetMemoryManager()); - return status; - } - - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK - - OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering - } - else - { - pack = *(byepackets.begin()); - byepackets.pop_front(); - - if ((status = SendRTCPData(pack->GetCompoundPacketData(),pack->GetCompoundPacketLength())) < 0) - { - SOURCES_UNLOCK - RTPDelete(pack,GetMemoryManager()); - return status; - } - - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK - - OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering - - if (!byepackets.empty()) // more bye packets to send, schedule them - { - SCHED_LOCK - rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); - SCHED_UNLOCK - } - } - - SCHED_LOCK - rtcpsched.AnalyseOutgoing(*pack); - SCHED_UNLOCK - - RTPDelete(pack,GetMemoryManager()); - } - SOURCES_UNLOCK - return 0; +if ((status = sources.DeleteOwnSSRC()) < 0) +{ +SOURCES_UNLOCK +delete rawpack; +return status; +} +if ((status = sources.CreateOwnSSRC(newssrc)) < 0) +{ +SOURCES_UNLOCK +delete rawpack; +return status; +} +} +} +delete rawpack; } -int RTPSession::CreateCNAME(uint8_t *buffer,size_t *bufferlength,bool resolve) +SCHED_LOCK +RTPTime d = rtcpsched.CalculateDeterministicInterval(false); +SCHED_UNLOCK + +RTPTime t = RTPTime::CurrentTime(); +double Td = d.GetDouble(); +RTPTime sendertimeout = RTPTime(Td * sendermultiplier); +RTPTime generaltimeout = RTPTime(Td * membermultiplier); +RTPTime byetimeout = RTPTime(Td * byemultiplier); +RTPTime colltimeout = RTPTime(Td * collisionmultiplier); +RTPTime notetimeout = RTPTime(Td * notemultiplier); + +sources.MultipleTimeouts(t, sendertimeout, byetimeout, generaltimeout, notetimeout); +collisionlist.Timeout(t, colltimeout); + + // We'll check if it's time for RTCP stuff + +SCHED_LOCK +bool istime = rtcpsched.IsTime(); +SCHED_UNLOCK + +if (istime) +{ +RTCPCompoundPacket *pack; + + // we'll check if there's a bye packet to send, or just a normal packet + +if (byepackets.empty()) +{ +BUILDER_LOCK +if ((status = rtcpbuilder.BuildNextPacket(&pack)) < 0) +{ +BUILDER_UNLOCK +SOURCES_UNLOCK +return status; +} +BUILDER_UNLOCK +if ((status = SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength())) < 0) +{ +SOURCES_UNLOCK +delete pack; +return status; +} + +PACKSENT_LOCK +sentpackets = true; +PACKSENT_UNLOCK + +OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering +} +else +{ +pack = *(byepackets.begin()); +byepackets.pop_front(); + +if ((status = SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength())) < 0) +{ +SOURCES_UNLOCK +delete pack; +return status; +} + +PACKSENT_LOCK +sentpackets = true; +PACKSENT_UNLOCK + +OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + +if (!byepackets.empty()) // more bye packets to send, schedule them +{ +SCHED_LOCK +rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); +SCHED_UNLOCK +} +} + +SCHED_LOCK +rtcpsched.AnalyseOutgoing(*pack); +SCHED_UNLOCK + +delete pack; +} +SOURCES_UNLOCK +return 0; +} + +int RTPSession::CreateCNAME(uint8_t *buffer, size_t *bufferlength, bool resolve) { #ifndef WIN32 - bool gotlogin = true; +bool gotlogin = true; #ifdef RTP_SUPPORT_GETLOGINR - buffer[0] = 0; - if (getlogin_r((char *)buffer,*bufferlength) != 0) - gotlogin = false; - else - { - if (buffer[0] == 0) - gotlogin = false; - } +buffer[0] = 0; +if (getlogin_r((char *) buffer, *bufferlength) != 0) +gotlogin = false; +else +{ +if (buffer[0] == 0) +gotlogin = false; +} - if (!gotlogin) // try regular getlogin - { - char *loginname = getlogin(); - if (loginname == 0) - gotlogin = false; - else - strncpy((char *)buffer,loginname,*bufferlength); - } +if (!gotlogin) // try regular getlogin +{ +char *loginname = getlogin(); +if (loginname == 0) +gotlogin = false; +else +strncpy((char *) buffer, loginname, *bufferlength); +} #else - char *loginname = getlogin(); - if (loginname == 0) - gotlogin = false; - else - strncpy((char *)buffer,loginname,*bufferlength); +char *loginname = getlogin(); +if (loginname == 0) +gotlogin = false; +else +strncpy((char *)buffer,loginname,*bufferlength); #endif // RTP_SUPPORT_GETLOGINR - if (!gotlogin) - { - char *logname = getenv("LOGNAME"); - if (logname == 0) - return ERR_RTP_SESSION_CANTGETLOGINNAME; - strncpy((char *)buffer,logname,*bufferlength); - } +if (!gotlogin) +{ +char *logname = getenv("LOGNAME"); +if (logname == 0) +return ERR_RTP_SESSION_CANTGETLOGINNAME; +strncpy((char *) buffer, logname, *bufferlength); +} #else // Win32 version #ifndef _WIN32_WCE - DWORD len = *bufferlength; - if (!GetUserName((LPTSTR)buffer,&len)) - RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); +DWORD len = *bufferlength; +if (!GetUserName((LPTSTR)buffer,&len)) +RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); #else - RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); +RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); #endif // _WIN32_WCE #endif // WIN32 - buffer[*bufferlength-1] = 0; +buffer[*bufferlength - 1] = 0; - size_t offset = strlen((const char *)buffer); - if (offset < (*bufferlength-1)) - buffer[offset] = (uint8_t)'@'; - offset++; +size_t offset = strlen((const char *) buffer); +if (offset < (*bufferlength - 1)) +buffer[offset] = (uint8_t) '@'; +offset++; - size_t buflen2 = *bufferlength-offset; - int status; +size_t buflen2 = *bufferlength - offset; +int status; - if (resolve) - { - if ((status = rtptrans->GetLocalHostName(buffer+offset,&buflen2)) < 0) - return status; - *bufferlength = buflen2+offset; - } - else - { - char hostname[1024]; +if (resolve) +{ +if ((status = rtptrans->GetLocalHostName(buffer + offset, &buflen2)) < 0) +return status; +*bufferlength = buflen2 + offset; +} +else +{ +char hostname[1024]; - RTP_STRNCPY(hostname,"localhost",1024); // just in case gethostname fails +RTP_STRNCPY(hostname, "localhost", 1024); // just in case gethostname fails - gethostname(hostname,1024); - RTP_STRNCPY((char *)(buffer+offset),hostname,buflen2); +gethostname(hostname, 1024); +RTP_STRNCPY((char * )(buffer + offset), hostname, buflen2); - *bufferlength = offset+strlen(hostname); - } - if (*bufferlength > RTCP_SDES_MAXITEMLENGTH) - *bufferlength = RTCP_SDES_MAXITEMLENGTH; - return 0; +*bufferlength = offset + strlen(hostname); +} +if (*bufferlength > RTCP_SDES_MAXITEMLENGTH) +*bufferlength = RTCP_SDES_MAXITEMLENGTH; +return 0; } RTPRandom *RTPSession::GetRandomNumberGenerator(RTPRandom *r) { - RTPRandom *rnew = 0; +RTPRandom *rnew = 0; - if (r == 0) - { - rnew = RTPRandom::CreateDefaultRandomNumberGenerator(); - deletertprnd = true; - } - else - { - rnew = r; - deletertprnd = false; - } +if (r == 0) +{ +rnew = RTPRandom::CreateDefaultRandomNumberGenerator(); +deletertprnd = true; +} +else +{ +rnew = r; +deletertprnd = false; +} - return rnew; +return rnew; } int RTPSession::SendRTPData(const void *data, size_t len) { - if (!m_changeOutgoingData) - return rtptrans->SendRTPData(data, len); +if (!m_changeOutgoingData) +return rtptrans->SendRTPData(data, len); - void *pSendData = 0; - size_t sendLen = 0; - int status = 0; +void *pSendData = 0; +size_t sendLen = 0; +int status = 0; - status = OnChangeRTPOrRTCPData(data, len, true, &pSendData, &sendLen); - if (status < 0) - return status; +status = OnChangeRTPOrRTCPData(data, len, true, &pSendData, &sendLen); +if (status < 0) +return status; - if (pSendData) - { - status = rtptrans->SendRTPData(pSendData, sendLen); - OnSentRTPOrRTCPData(pSendData, sendLen, true); - } +if (pSendData) +{ +status = rtptrans->SendRTPData(pSendData, sendLen); +OnSentRTPOrRTCPData(pSendData, sendLen, true); +} - return status; +return status; } int RTPSession::SendRTCPData(const void *data, size_t len) { - if (!m_changeOutgoingData) - return rtptrans->SendRTCPData(data, len); +if (!m_changeOutgoingData) +return rtptrans->SendRTCPData(data, len); - void *pSendData = 0; - size_t sendLen = 0; - int status = 0; +void *pSendData = 0; +size_t sendLen = 0; +int status = 0; - status = OnChangeRTPOrRTCPData(data, len, false, &pSendData, &sendLen); - if (status < 0) - return status; +status = OnChangeRTPOrRTCPData(data, len, false, &pSendData, &sendLen); +if (status < 0) +return status; - if (pSendData) - { - status = rtptrans->SendRTCPData(pSendData, sendLen); - OnSentRTPOrRTCPData(pSendData, sendLen, false); - } +if (pSendData) +{ +status = rtptrans->SendRTCPData(pSendData, sendLen); +OnSentRTPOrRTCPData(pSendData, sendLen, false); +} - return status; +return status; } } // end namespace diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index 7766003e0..918ac2b00 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpsession.h @@ -48,7 +48,6 @@ #include "rtcppacketbuilder.h" #include "rtptimeutilities.h" #include "rtcpcompoundpacketbuilder.h" -#include "rtpmemoryobject.h" #include namespace qrtplib @@ -73,577 +72,619 @@ class RTCPAPPPacket; * \note The RTPSession class is not meant to be thread safe. The user should use some kind of locking * mechanism to prevent different threads from using the same RTPSession instance. */ -class JRTPLIB_IMPORTEXPORT RTPSession : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTPSession { public: - /** Constructs an RTPSession instance, optionally using a specific instance of a random - * number generator, and optionally installing a memory manager. - * Constructs an RTPSession instance, optionally using a specific instance of a random - * number generator, and optionally installing a memory manager. If no random number generator - * is specified, the RTPSession object will try to use either a RTPRandomURandom or - * RTPRandomRandS instance. If neither is available on the current platform, a RTPRandomRand48 - * instance will be used instead. By specifying a random number generator yourself, it is - * possible to use the same generator in several RTPSession instances. - */ - RTPSession(RTPRandom *rnd = 0, RTPMemoryManager *mgr = 0); - virtual ~RTPSession(); + /** Constructs an RTPSession instance, optionally using a specific instance of a random + * number generator, and optionally installing a memory manager. + * Constructs an RTPSession instance, optionally using a specific instance of a random + * number generator, and optionally installing a memory manager. If no random number generator + * is specified, the RTPSession object will try to use either a RTPRandomURandom or + * RTPRandomRandS instance. If neither is available on the current platform, a RTPRandomRand48 + * instance will be used instead. By specifying a random number generator yourself, it is + * possible to use the same generator in several RTPSession instances. + */ + RTPSession(RTPRandom *rnd = 0); + virtual ~RTPSession(); - /** Creates an RTP session. - * This function creates an RTP session with parameters \c sessparams, which will use a transmitter - * corresponding to \c proto. Parameters for this transmitter can be specified as well. If \c - * proto is of type RTPTransmitter::UserDefinedProto, the NewUserDefinedTransmitter function must - * be implemented. - */ - int Create(const RTPSessionParams &sessparams,const RTPTransmissionParams *transparams = 0, RTPTransmitter::TransmissionProtocol proto = RTPTransmitter::IPv4UDPProto); + /** Creates an RTP session. + * This function creates an RTP session with parameters \c sessparams, which will use a transmitter + * corresponding to \c proto. Parameters for this transmitter can be specified as well. If \c + * proto is of type RTPTransmitter::UserDefinedProto, the NewUserDefinedTransmitter function must + * be implemented. + */ + int Create(const RTPSessionParams &sessparams, const RTPTransmissionParams *transparams = 0, RTPTransmitter::TransmissionProtocol proto = RTPTransmitter::IPv4UDPProto); - /** Creates an RTP session using \c transmitter as transmission component. - * This function creates an RTP session with parameters \c sessparams, which will use the - * transmission component \c transmitter. Initialization and destruction of the transmitter - * will not be done by the RTPSession class if this Create function is used. This function - * can be useful if you which to reuse the transmission component in another RTPSession - * instance, once the original RTPSession isn't using the transmitter anymore. - */ - int Create(const RTPSessionParams &sessparams,RTPTransmitter *transmitter); + /** Creates an RTP session using \c transmitter as transmission component. + * This function creates an RTP session with parameters \c sessparams, which will use the + * transmission component \c transmitter. Initialization and destruction of the transmitter + * will not be done by the RTPSession class if this Create function is used. This function + * can be useful if you which to reuse the transmission component in another RTPSession + * instance, once the original RTPSession isn't using the transmitter anymore. + */ + int Create(const RTPSessionParams &sessparams, RTPTransmitter *transmitter); - /** Leaves the session without sending a BYE packet. */ - void Destroy(); + /** Leaves the session without sending a BYE packet. */ + void Destroy(); - /** Sends a BYE packet and leaves the session. - * Sends a BYE packet and leaves the session. At most a time \c maxwaittime will be waited to - * send the BYE packet. If this time expires, the session will be left without sending a BYE packet. - * The BYE packet will contain as reason for leaving \c reason with length \c reasonlength. - */ - void BYEDestroy(const RTPTime &maxwaittime,const void *reason,size_t reasonlength); + /** Sends a BYE packet and leaves the session. + * Sends a BYE packet and leaves the session. At most a time \c maxwaittime will be waited to + * send the BYE packet. If this time expires, the session will be left without sending a BYE packet. + * The BYE packet will contain as reason for leaving \c reason with length \c reasonlength. + */ + void BYEDestroy(const RTPTime &maxwaittime, const void *reason, size_t reasonlength); - /** Returns whether the session has been created or not. */ - bool IsActive(); + /** Returns whether the session has been created or not. */ + bool IsActive(); - /** Returns our own SSRC. */ - uint32_t GetLocalSSRC(); + /** Returns our own SSRC. */ + uint32_t GetLocalSSRC(); - /** Adds \c addr to the list of destinations. */ - int AddDestination(const RTPAddress &addr); + /** Adds \c addr to the list of destinations. */ + int AddDestination(const RTPAddress &addr); - /** Deletes \c addr from the list of destinations. */ - int DeleteDestination(const RTPAddress &addr); + /** Deletes \c addr from the list of destinations. */ + int DeleteDestination(const RTPAddress &addr); - /** Clears the list of destinations. */ - void ClearDestinations(); + /** Clears the list of destinations. */ + void ClearDestinations(); - /** Returns \c true if multicasting is supported. */ - bool SupportsMulticasting(); + /** Returns \c true if multicasting is supported. */ + bool SupportsMulticasting(); - /** Joins the multicast group specified by \c addr. */ - int JoinMulticastGroup(const RTPAddress &addr); + /** Joins the multicast group specified by \c addr. */ + int JoinMulticastGroup(const RTPAddress &addr); - /** Leaves the multicast group specified by \c addr. */ - int LeaveMulticastGroup(const RTPAddress &addr); + /** Leaves the multicast group specified by \c addr. */ + int LeaveMulticastGroup(const RTPAddress &addr); - /** Leaves all multicast groups. */ - void LeaveAllMulticastGroups(); + /** Leaves all multicast groups. */ + void LeaveAllMulticastGroups(); - /** Sends the RTP packet with payload \c data which has length \c len. - * Sends the RTP packet with payload \c data which has length \c len. - * The used payload type, marker and timestamp increment will be those that have been set - * using the \c SetDefault member functions. - */ - int SendPacket(const void *data,size_t len); + /** Sends the RTP packet with payload \c data which has length \c len. + * Sends the RTP packet with payload \c data which has length \c len. + * The used payload type, marker and timestamp increment will be those that have been set + * using the \c SetDefault member functions. + */ + int SendPacket(const void *data, size_t len); - /** Sends the RTP packet with payload \c data which has length \c len. - * It will use payload type \c pt, marker \c mark and after the packet has been built, the - * timestamp will be incremented by \c timestampinc. - */ - int SendPacket(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc); + /** Sends the RTP packet with payload \c data which has length \c len. + * It will use payload type \c pt, marker \c mark and after the packet has been built, the + * timestamp will be incremented by \c timestampinc. + */ + int SendPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc); - /** Sends the RTP packet with payload \c data which has length \c len. - * The packet will contain a header extension with identifier \c hdrextID and containing data - * \c hdrextdata. The length of this data is given by \c numhdrextwords and is specified in a - * number of 32-bit words. The used payload type, marker and timestamp increment will be those that - * have been set using the \c SetDefault member functions. - */ - int SendPacketEx(const void *data,size_t len, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords); + /** Sends the RTP packet with payload \c data which has length \c len. + * The packet will contain a header extension with identifier \c hdrextID and containing data + * \c hdrextdata. The length of this data is given by \c numhdrextwords and is specified in a + * number of 32-bit words. The used payload type, marker and timestamp increment will be those that + * have been set using the \c SetDefault member functions. + */ + int SendPacketEx(const void *data, size_t len, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords); - /** Sends the RTP packet with payload \c data which has length \c len. - * It will use payload type \c pt, marker \c mark and after the packet has been built, the - * timestamp will be incremented by \c timestampinc. The packet will contain a header - * extension with identifier \c hdrextID and containing data \c hdrextdata. The length - * of this data is given by \c numhdrextwords and is specified in a number of 32-bit words. - */ - int SendPacketEx(const void *data,size_t len, - uint8_t pt,bool mark,uint32_t timestampinc, - uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords); + /** Sends the RTP packet with payload \c data which has length \c len. + * It will use payload type \c pt, marker \c mark and after the packet has been built, the + * timestamp will be incremented by \c timestampinc. The packet will contain a header + * extension with identifier \c hdrextID and containing data \c hdrextdata. The length + * of this data is given by \c numhdrextwords and is specified in a number of 32-bit words. + */ + int SendPacketEx(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords); #ifdef RTP_SUPPORT_SENDAPP - /** If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet - * containing an RTCP APP packet and sends it immediately. - * If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet - * containing an RTCP APP packet and sends it immediately. If successful, the function returns the number - * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP - * specification, so use with care. - */ - int SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, size_t appdatalen); + /** If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet + * containing an RTCP APP packet and sends it immediately. + * If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet + * containing an RTCP APP packet and sends it immediately. If successful, the function returns the number + * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP + * specification, so use with care. + */ + int SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, size_t appdatalen); #endif // RTP_SUPPORT_SENDAPP #ifdef RTP_SUPPORT_RTCPUNKNOWN - /** Tries to send an Unknown packet immediately. - * Tries to send an Unknown packet immediately. If successful, the function returns the number - * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP - * specification, so use with care. Can send message along with a receiver report or a sender report - */ - int SendUnknownPacket(bool sr, uint8_t payload_type, uint8_t subtype, const void *data, size_t len); + /** Tries to send an Unknown packet immediately. + * Tries to send an Unknown packet immediately. If successful, the function returns the number + * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP + * specification, so use with care. Can send message along with a receiver report or a sender report + */ + int SendUnknownPacket(bool sr, uint8_t payload_type, uint8_t subtype, const void *data, size_t len); #endif // RTP_SUPPORT_RTCPUNKNOWN - /** With this function raw data can be sent directly over the RTP or - * RTCP channel (if they are different); the data is **not** passed through the - * RTPSession::OnChangeRTPOrRTCPData function. */ - int SendRawData(const void *data, size_t len, bool usertpchannel); + /** With this function raw data can be sent directly over the RTP or + * RTCP channel (if they are different); the data is **not** passed through the + * RTPSession::OnChangeRTPOrRTCPData function. */ + int SendRawData(const void *data, size_t len, bool usertpchannel); - /** Sets the default payload type for RTP packets to \c pt. */ - int SetDefaultPayloadType(uint8_t pt); + /** Sets the default payload type for RTP packets to \c pt. */ + int SetDefaultPayloadType(uint8_t pt); - /** Sets the default marker for RTP packets to \c m. */ - int SetDefaultMark(bool m); + /** Sets the default marker for RTP packets to \c m. */ + int SetDefaultMark(bool m); - /** Sets the default value to increment the timestamp with to \c timestampinc. */ - int SetDefaultTimestampIncrement(uint32_t timestampinc); + /** Sets the default value to increment the timestamp with to \c timestampinc. */ + int SetDefaultTimestampIncrement(uint32_t timestampinc); - /** This function increments the timestamp with the amount given by \c inc. - * This function increments the timestamp with the amount given by \c inc. This can be useful - * if, for example, a packet was not sent because it contained only silence. Then, this function - * should be called to increment the timestamp with the appropriate amount so that the next packets - * will still be played at the correct time at other hosts. - */ - int IncrementTimestamp(uint32_t inc); + /** This function increments the timestamp with the amount given by \c inc. + * This function increments the timestamp with the amount given by \c inc. This can be useful + * if, for example, a packet was not sent because it contained only silence. Then, this function + * should be called to increment the timestamp with the appropriate amount so that the next packets + * will still be played at the correct time at other hosts. + */ + int IncrementTimestamp(uint32_t inc); - /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement - * member function. - * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement - * member function. This can be useful if, for example, a packet was not sent because it contained only silence. - * Then, this function should be called to increment the timestamp with the appropriate amount so that the next - * packets will still be played at the correct time at other hosts. - */ - int IncrementTimestampDefault(); + /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. + * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. This can be useful if, for example, a packet was not sent because it contained only silence. + * Then, this function should be called to increment the timestamp with the appropriate amount so that the next + * packets will still be played at the correct time at other hosts. + */ + int IncrementTimestampDefault(); - /** This function allows you to inform the library about the delay between sampling the first - * sample of a packet and sending the packet. - * This function allows you to inform the library about the delay between sampling the first - * sample of a packet and sending the packet. This delay is taken into account when calculating the - * relation between RTP timestamp and wallclock time, used for inter-media synchronization. - */ - int SetPreTransmissionDelay(const RTPTime &delay); + /** This function allows you to inform the library about the delay between sampling the first + * sample of a packet and sending the packet. + * This function allows you to inform the library about the delay between sampling the first + * sample of a packet and sending the packet. This delay is taken into account when calculating the + * relation between RTP timestamp and wallclock time, used for inter-media synchronization. + */ + int SetPreTransmissionDelay(const RTPTime &delay); - /** This function returns an instance of a subclass of RTPTransmissionInfo which will give some - * additional information about the transmitter (a list of local IP addresses for example). - * This function returns an instance of a subclass of RTPTransmissionInfo which will give some - * additional information about the transmitter (a list of local IP addresses for example). The user - * has to free the returned instance when it is no longer needed, preferably using the DeleteTransmissionInfo - * function. - */ - RTPTransmissionInfo *GetTransmissionInfo(); + /** This function returns an instance of a subclass of RTPTransmissionInfo which will give some + * additional information about the transmitter (a list of local IP addresses for example). + * This function returns an instance of a subclass of RTPTransmissionInfo which will give some + * additional information about the transmitter (a list of local IP addresses for example). The user + * has to free the returned instance when it is no longer needed, preferably using the DeleteTransmissionInfo + * function. + */ + RTPTransmissionInfo *GetTransmissionInfo(); - /** Frees the memory used by the transmission information \c inf. */ - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + /** Frees the memory used by the transmission information \c inf. */ + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - /** If you're not using the poll thread, this function must be called regularly to process incoming data - * and to send RTCP data when necessary. - */ - int Poll(); + /** If you're not using the poll thread, this function must be called regularly to process incoming data + * and to send RTCP data when necessary. + */ + int Poll(); - /** Waits at most a time \c delay until incoming data has been detected. - * Waits at most a time \c delay until incoming data has been detected. Only works when you're not - * using the poll thread. If \c dataavailable is not \c NULL, it should be set to \c true if data - * was actually read and to \c false otherwise. - */ - int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); + /** Waits at most a time \c delay until incoming data has been detected. + * Waits at most a time \c delay until incoming data has been detected. Only works when you're not + * using the poll thread. If \c dataavailable is not \c NULL, it should be set to \c true if data + * was actually read and to \c false otherwise. + */ + int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); - /** If the previous function has been called, this one aborts the waiting (only works when you're not - * using the poll thread). - */ - int AbortWait(); + /** If the previous function has been called, this one aborts the waiting (only works when you're not + * using the poll thread). + */ + int AbortWait(); - /** Returns the time interval after which an RTCP compound packet may have to be sent (only works when - * you're not using the poll thread. - */ - RTPTime GetRTCPDelay(); + /** Returns the time interval after which an RTCP compound packet may have to be sent (only works when + * you're not using the poll thread. + */ + RTPTime GetRTCPDelay(); - /** The following member functions (till EndDataAccess}) need to be accessed between a call - * to BeginDataAccess and EndDataAccess. - * The BeginDataAccess function makes sure that the poll thread won't access the source table - * at the same time that you're using it. When the EndDataAccess is called, the lock on the - * source table is freed again. - */ - int BeginDataAccess(); + /** The following member functions (till EndDataAccess}) need to be accessed between a call + * to BeginDataAccess and EndDataAccess. + * The BeginDataAccess function makes sure that the poll thread won't access the source table + * at the same time that you're using it. When the EndDataAccess is called, the lock on the + * source table is freed again. + */ + int BeginDataAccess(); - /** Starts the iteration over the participants by going to the first member in the table. - * Starts the iteration over the participants by going to the first member in the table. - * If a member was found, the function returns \c true, otherwise it returns \c false. - */ - bool GotoFirstSource(); + /** Starts the iteration over the participants by going to the first member in the table. + * Starts the iteration over the participants by going to the first member in the table. + * If a member was found, the function returns \c true, otherwise it returns \c false. + */ + bool GotoFirstSource(); - /** Sets the current source to be the next source in the table. - * Sets the current source to be the next source in the table. If we're already at the last - * source, the function returns \c false, otherwise it returns \c true. - */ - bool GotoNextSource(); + /** Sets the current source to be the next source in the table. + * Sets the current source to be the next source in the table. If we're already at the last + * source, the function returns \c false, otherwise it returns \c true. + */ + bool GotoNextSource(); - /** Sets the current source to be the previous source in the table. - * Sets the current source to be the previous source in the table. If we're at the first source, - * the function returns \c false, otherwise it returns \c true. - */ - bool GotoPreviousSource(); + /** Sets the current source to be the previous source in the table. + * Sets the current source to be the previous source in the table. If we're at the first source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoPreviousSource(); - /** Sets the current source to be the first source in the table which has RTPPacket instances - * that we haven't extracted yet. - * Sets the current source to be the first source in the table which has RTPPacket instances - * that we haven't extracted yet. If no such member was found, the function returns \c false, - * otherwise it returns \c true. - */ - bool GotoFirstSourceWithData(); + /** Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoFirstSourceWithData(); - /** Sets the current source to be the next source in the table which has RTPPacket instances - * that we haven't extracted yet. - * Sets the current source to be the next source in the table which has RTPPacket instances - * that we haven't extracted yet. If no such member was found, the function returns \c false, - * otherwise it returns \c true. - */ - bool GotoNextSourceWithData(); + /** Sets the current source to be the next source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the next source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoNextSourceWithData(); - /** Sets the current source to be the previous source in the table which has RTPPacket - * instances that we haven't extracted yet. - * Sets the current source to be the previous source in the table which has RTPPacket - * instances that we haven't extracted yet. If no such member was found, the function returns \c false, - * otherwise it returns \c true. - */ - bool GotoPreviousSourceWithData(); + /** Sets the current source to be the previous source in the table which has RTPPacket + * instances that we haven't extracted yet. + * Sets the current source to be the previous source in the table which has RTPPacket + * instances that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoPreviousSourceWithData(); - /** Returns the \c RTPSourceData instance for the currently selected participant. */ - RTPSourceData *GetCurrentSourceInfo(); + /** Returns the \c RTPSourceData instance for the currently selected participant. */ + RTPSourceData *GetCurrentSourceInfo(); - /** Returns the \c RTPSourceData instance for the participant identified by \c ssrc, - * or NULL if no such entry exists. - */ - RTPSourceData *GetSourceInfo(uint32_t ssrc); + /** Returns the \c RTPSourceData instance for the participant identified by \c ssrc, + * or NULL if no such entry exists. + */ + RTPSourceData *GetSourceInfo(uint32_t ssrc); - /** Extracts the next packet from the received packets queue of the current participant, - * or NULL if no more packets are available. - * Extracts the next packet from the received packets queue of the current participant, - * or NULL if no more packets are available. When the packet is no longer needed, its - * memory should be freed using the DeletePacket member function. - */ - RTPPacket *GetNextPacket(); + /** Extracts the next packet from the received packets queue of the current participant, + * or NULL if no more packets are available. + * Extracts the next packet from the received packets queue of the current participant, + * or NULL if no more packets are available. When the packet is no longer needed, its + * memory should be freed using the DeletePacket member function. + */ + RTPPacket *GetNextPacket(); /** Returns the Sequence Number that will be used in the next SendPacket function call. */ uint16_t GetNextSequenceNumber() const; - /** Frees the memory used by \c p. */ - void DeletePacket(RTPPacket *p); + /** Frees the memory used by \c p. */ + void DeletePacket(RTPPacket *p); - /** See BeginDataAccess. */ - int EndDataAccess(); + /** See BeginDataAccess. */ + int EndDataAccess(); - /** Sets the receive mode to \c m. - * Sets the receive mode to \c m. Note that when the receive mode is changed, the list of - * addresses to be ignored ot accepted will be cleared. - */ - int SetReceiveMode(RTPTransmitter::ReceiveMode m); + /** Sets the receive mode to \c m. + * Sets the receive mode to \c m. Note that when the receive mode is changed, the list of + * addresses to be ignored ot accepted will be cleared. + */ + int SetReceiveMode(RTPTransmitter::ReceiveMode m); - /** Adds \c addr to the list of addresses to ignore. */ - int AddToIgnoreList(const RTPAddress &addr); + /** Adds \c addr to the list of addresses to ignore. */ + int AddToIgnoreList(const RTPAddress &addr); - /** Deletes \c addr from the list of addresses to ignore. */ - int DeleteFromIgnoreList(const RTPAddress &addr); + /** Deletes \c addr from the list of addresses to ignore. */ + int DeleteFromIgnoreList(const RTPAddress &addr); - /** Clears the list of addresses to ignore. */ - void ClearIgnoreList(); + /** Clears the list of addresses to ignore. */ + void ClearIgnoreList(); - /** Adds \c addr to the list of addresses to accept. */ - int AddToAcceptList(const RTPAddress &addr); + /** Adds \c addr to the list of addresses to accept. */ + int AddToAcceptList(const RTPAddress &addr); - /** Deletes \c addr from the list of addresses to accept. */ - int DeleteFromAcceptList(const RTPAddress &addr); + /** Deletes \c addr from the list of addresses to accept. */ + int DeleteFromAcceptList(const RTPAddress &addr); - /** Clears the list of addresses to accept. */ - void ClearAcceptList(); + /** Clears the list of addresses to accept. */ + void ClearAcceptList(); - /** Sets the maximum allowed packet size to \c s. */ - int SetMaximumPacketSize(size_t s); + /** Sets the maximum allowed packet size to \c s. */ + int SetMaximumPacketSize(size_t s); - /** Sets the session bandwidth to \c bw, which is specified in bytes per second. */ - int SetSessionBandwidth(double bw); + /** Sets the session bandwidth to \c bw, which is specified in bytes per second. */ + int SetSessionBandwidth(double bw); - /** Sets the timestamp unit for our own data. - * Sets the timestamp unit for our own data. The timestamp unit is defined as a time interval in - * seconds divided by the corresponding timestamp interval. For example, for 8000 Hz audio, the - * timestamp unit would typically be 1/8000. Since this value is initially set to an illegal value, - * the user must set this to an allowed value to be able to create a session. - */ - int SetTimestampUnit(double u); + /** Sets the timestamp unit for our own data. + * Sets the timestamp unit for our own data. The timestamp unit is defined as a time interval in + * seconds divided by the corresponding timestamp interval. For example, for 8000 Hz audio, the + * timestamp unit would typically be 1/8000. Since this value is initially set to an illegal value, + * the user must set this to an allowed value to be able to create a session. + */ + int SetTimestampUnit(double u); - /** Sets the RTCP interval for the SDES name item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES name item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetNameInterval(int count); + /** Sets the RTCP interval for the SDES name item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES name item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNameInterval(int count); - /** Sets the RTCP interval for the SDES e-mail item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES e-mail item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetEMailInterval(int count); + /** Sets the RTCP interval for the SDES e-mail item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES e-mail item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetEMailInterval(int count); - /** Sets the RTCP interval for the SDES location item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES location item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetLocationInterval(int count); + /** Sets the RTCP interval for the SDES location item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES location item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetLocationInterval(int count); - /** Sets the RTCP interval for the SDES phone item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES phone item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetPhoneInterval(int count); + /** Sets the RTCP interval for the SDES phone item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES phone item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetPhoneInterval(int count); - /** Sets the RTCP interval for the SDES tool item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES tool item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetToolInterval(int count); + /** Sets the RTCP interval for the SDES tool item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES tool item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetToolInterval(int count); - /** Sets the RTCP interval for the SDES note item. - * After all possible sources in the source table have been processed, the class will check if other - * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count - * is positive, an SDES note item will be added after the sources in the source table have been - * processed \c count times. - */ - void SetNoteInterval(int count); + /** Sets the RTCP interval for the SDES note item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES note item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNoteInterval(int count); - /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ - int SetLocalName(const void *s,size_t len); + /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ + int SetLocalName(const void *s, size_t len); - /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ - int SetLocalEMail(const void *s,size_t len); + /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ + int SetLocalEMail(const void *s, size_t len); - /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ - int SetLocalLocation(const void *s,size_t len); + /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ + int SetLocalLocation(const void *s, size_t len); - /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ - int SetLocalPhone(const void *s,size_t len); + /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ + int SetLocalPhone(const void *s, size_t len); - /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ - int SetLocalTool(const void *s,size_t len); + /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ + int SetLocalTool(const void *s, size_t len); - /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ - int SetLocalNote(const void *s,size_t len); + /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ + int SetLocalNote(const void *s, size_t len); protected: - /** Allocate a user defined transmitter. - * In case you specified in the Create function that you want to use a - * user defined transmitter, you should override this function. The RTPTransmitter - * instance returned by this function will then be used to send and receive RTP and - * RTCP packets. Note that when the session is destroyed, this RTPTransmitter - * instance will be destroyed as well. - */ - virtual RTPTransmitter *NewUserDefinedTransmitter(); + /** Allocate a user defined transmitter. + * In case you specified in the Create function that you want to use a + * user defined transmitter, you should override this function. The RTPTransmitter + * instance returned by this function will then be used to send and receive RTP and + * RTCP packets. Note that when the session is destroyed, this RTPTransmitter + * instance will be destroyed as well. + */ + virtual RTPTransmitter *NewUserDefinedTransmitter(); - /** Is called when an incoming RTP packet is about to be processed. - * Is called when an incoming RTP packet is about to be processed. This is _not_ - * a good function to process an RTP packet in, in case you want to avoid iterating - * over the sources using the GotoFirst/GotoNext functions. In that case, the - * RTPSession::OnValidatedRTPPacket function should be used. - */ - virtual void OnRTPPacket(RTPPacket *pack,const RTPTime &receivetime, const RTPAddress *senderaddress); + /** Is called when an incoming RTP packet is about to be processed. + * Is called when an incoming RTP packet is about to be processed. This is _not_ + * a good function to process an RTP packet in, in case you want to avoid iterating + * over the sources using the GotoFirst/GotoNext functions. In that case, the + * RTPSession::OnValidatedRTPPacket function should be used. + */ + virtual void OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an incoming RTCP packet is about to be processed. */ - virtual void OnRTCPCompoundPacket(RTCPCompoundPacket *pack,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Is called when an incoming RTCP packet is about to be processed. */ + virtual void OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an SSRC collision was detected. - * Is called when an SSRC collision was detected. The instance \c srcdat is the one present in - * the table, the address \c senderaddress is the one that collided with one of the addresses - * and \c isrtp indicates against which address of \c srcdat the check failed. - */ - virtual void OnSSRCCollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp); + /** Is called when an SSRC collision was detected. + * Is called when an SSRC collision was detected. The instance \c srcdat is the one present in + * the table, the address \c senderaddress is the one that collided with one of the addresses + * and \c isrtp indicates against which address of \c srcdat the check failed. + */ + virtual void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); - /** Is called when another CNAME was received than the one already present for source \c srcdat. */ - virtual void OnCNAMECollision(RTPSourceData *srcdat,const RTPAddress *senderaddress, - const uint8_t *cname,size_t cnamelength); + /** Is called when another CNAME was received than the one already present for source \c srcdat. */ + virtual void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, size_t cnamelength); - /** Is called when a new entry \c srcdat is added to the source table. */ - virtual void OnNewSource(RTPSourceData *srcdat); + /** Is called when a new entry \c srcdat is added to the source table. */ + virtual void OnNewSource(RTPSourceData *srcdat); - /** Is called when the entry \c srcdat is about to be deleted from the source table. */ - virtual void OnRemoveSource(RTPSourceData *srcdat); + /** Is called when the entry \c srcdat is about to be deleted from the source table. */ + virtual void OnRemoveSource(RTPSourceData *srcdat); - /** Is called when participant \c srcdat is timed out. */ - virtual void OnTimeout(RTPSourceData *srcdat); + /** Is called when participant \c srcdat is timed out. */ + virtual void OnTimeout(RTPSourceData *srcdat); - /** Is called when participant \c srcdat is timed after having sent a BYE packet. */ - virtual void OnBYETimeout(RTPSourceData *srcdat); + /** Is called when participant \c srcdat is timed after having sent a BYE packet. */ + virtual void OnBYETimeout(RTPSourceData *srcdat); - /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime - * from address \c senderaddress. - */ - virtual void OnAPPPacket(RTCPAPPPacket *apppacket,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime + * from address \c senderaddress. + */ + virtual void OnAPPPacket(RTCPAPPPacket *apppacket, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an unknown RTCP packet type was detected. */ - virtual void OnUnknownPacketType(RTCPPacket *rtcppack,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Is called when an unknown RTCP packet type was detected. */ + virtual void OnUnknownPacketType(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an unknown packet format for a known packet type was detected. */ - virtual void OnUnknownPacketFormat(RTCPPacket *rtcppack,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Is called when an unknown packet format for a known packet type was detected. */ + virtual void OnUnknownPacketFormat(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when the SDES NOTE item for source \c srcdat has been timed out. */ - virtual void OnNoteTimeout(RTPSourceData *srcdat); + /** Is called when the SDES NOTE item for source \c srcdat has been timed out. */ + virtual void OnNoteTimeout(RTPSourceData *srcdat); - /** Is called when an RTCP sender report has been processed for this source. */ - virtual void OnRTCPSenderReport(RTPSourceData *srcdat); + /** Is called when an RTCP sender report has been processed for this source. */ + virtual void OnRTCPSenderReport(RTPSourceData *srcdat); - /** Is called when an RTCP receiver report has been processed for this source. */ - virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); + /** Is called when an RTCP receiver report has been processed for this source. */ + virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); - /** Is called when a specific SDES item was received for this source. */ - virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, - const void *itemdata, size_t itemlength); + /** Is called when a specific SDES item was received for this source. */ + virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, size_t itemlength); #ifdef RTP_SUPPORT_SDESPRIV - /** Is called when a specific SDES item of 'private' type was received for this source. */ - virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, - const void *valuedata, size_t valuelen); + /** Is called when a specific SDES item of 'private' type was received for this source. */ + virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, const void *valuedata, size_t valuelen); #endif // RTP_SUPPORT_SDESPRIV - /** Is called when a BYE packet has been processed for source \c srcdat. */ - virtual void OnBYEPacket(RTPSourceData *srcdat); + /** Is called when a BYE packet has been processed for source \c srcdat. */ + virtual void OnBYEPacket(RTPSourceData *srcdat); - /** Is called when an RTCP compound packet has just been sent (useful to inspect outgoing RTCP data). */ - virtual void OnSendRTCPCompoundPacket(RTCPCompoundPacket *pack); + /** Is called when an RTCP compound packet has just been sent (useful to inspect outgoing RTCP data). */ + virtual void OnSendRTCPCompoundPacket(RTCPCompoundPacket *pack); - /** If this is set to true, outgoing data will be passed through RTPSession::OnChangeRTPOrRTCPData - * and RTPSession::OnSentRTPOrRTCPData, allowing you to modify the data (e.g. to encrypt it). */ - void SetChangeOutgoingData(bool change) { m_changeOutgoingData = change; } + /** If this is set to true, outgoing data will be passed through RTPSession::OnChangeRTPOrRTCPData + * and RTPSession::OnSentRTPOrRTCPData, allowing you to modify the data (e.g. to encrypt it). */ + void SetChangeOutgoingData(bool change) + { + m_changeOutgoingData = change; + } - /** If this is set to true, incoming data will be passed through RTPSession::OnChangeIncomingData, - * allowing you to modify the data (e.g. to decrypt it). */ - void SetChangeIncomingData(bool change) { m_changeIncomingData = change; } + /** If this is set to true, incoming data will be passed through RTPSession::OnChangeIncomingData, + * allowing you to modify the data (e.g. to decrypt it). */ + void SetChangeIncomingData(bool change) + { + m_changeIncomingData = change; + } - /** If RTPSession::SetChangeOutgoingData was sent to true, overriding this you can change the - * data packet that will actually be sent, for example adding encryption. - * If RTPSession::SetChangeOutgoingData was sent to true, overriding this you can change the - * data packet that will actually be sent, for example adding encryption. - * Note that no memory management will be performed on the `senddata` pointer you fill in, - * so if it needs to be deleted at some point you need to take care of this in some way - * yourself, a good way may be to do this in RTPSession::OnSentRTPOrRTCPData. If `senddata` is - * set to 0, no packet will be sent out. This also provides a way to turn off sending RTCP - * packets if desired. */ - virtual int OnChangeRTPOrRTCPData(const void *origdata, size_t origlen, bool isrtp, void **senddata, size_t *sendlen); + /** If RTPSession::SetChangeOutgoingData was sent to true, overriding this you can change the + * data packet that will actually be sent, for example adding encryption. + * If RTPSession::SetChangeOutgoingData was sent to true, overriding this you can change the + * data packet that will actually be sent, for example adding encryption. + * Note that no memory management will be performed on the `senddata` pointer you fill in, + * so if it needs to be deleted at some point you need to take care of this in some way + * yourself, a good way may be to do this in RTPSession::OnSentRTPOrRTCPData. If `senddata` is + * set to 0, no packet will be sent out. This also provides a way to turn off sending RTCP + * packets if desired. */ + virtual int OnChangeRTPOrRTCPData(const void *origdata, size_t origlen, bool isrtp, void **senddata, size_t *sendlen); - /** This function is called when an RTP or RTCP packet was sent, it can be helpful - * when data was allocated in RTPSession::OnChangeRTPOrRTCPData to deallocate it - * here. */ - virtual void OnSentRTPOrRTCPData(void *senddata, size_t sendlen, bool isrtp); + /** This function is called when an RTP or RTCP packet was sent, it can be helpful + * when data was allocated in RTPSession::OnChangeRTPOrRTCPData to deallocate it + * here. */ + virtual void OnSentRTPOrRTCPData(void *senddata, size_t sendlen, bool isrtp); - /** By overriding this function, the raw incoming data can be inspected - * and modified (e.g. for encryption). - * By overriding this function, the raw incoming data can be inspected - * and modified (e.g. for encryption). If the function returns `false`, - * the packet is discarded. - */ - virtual bool OnChangeIncomingData(RTPRawPacket *rawpack); + /** By overriding this function, the raw incoming data can be inspected + * and modified (e.g. for encryption). + * By overriding this function, the raw incoming data can be inspected + * and modified (e.g. for encryption). If the function returns `false`, + * the packet is discarded. + */ + virtual bool OnChangeIncomingData(RTPRawPacket *rawpack); - /** Allows you to use an RTP packet from the specified source directly. - * Allows you to use an RTP packet from the specified source directly. If - * `ispackethandled` is set to `true`, the packet will no longer be stored in this - * source's packet list. Note that if you do set this flag, you'll need to - * deallocate the packet yourself at an appropriate time. - * - * The difference with RTPSession::OnRTPPacket is that that - * function will always process the RTP packet further and is therefore not - * really suited to actually do something with the data. - */ - virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); + /** Allows you to use an RTP packet from the specified source directly. + * Allows you to use an RTP packet from the specified source directly. If + * `ispackethandled` is set to `true`, the packet will no longer be stored in this + * source's packet list. Note that if you do set this flag, you'll need to + * deallocate the packet yourself at an appropriate time. + * + * The difference with RTPSession::OnRTPPacket is that that + * function will always process the RTP packet further and is therefore not + * really suited to actually do something with the data. + */ + virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); private: - int InternalCreate(const RTPSessionParams &sessparams); - int CreateCNAME(uint8_t *buffer,size_t *bufferlength,bool resolve); - int ProcessPolledData(); - int ProcessRTCPCompoundPacket(RTCPCompoundPacket &rtcpcomppack,RTPRawPacket *pack); - RTPRandom *GetRandomNumberGenerator(RTPRandom *r); - int SendRTPData(const void *data, size_t len); - int SendRTCPData(const void *data, size_t len); + int InternalCreate(const RTPSessionParams &sessparams); + int CreateCNAME(uint8_t *buffer, size_t *bufferlength, bool resolve); + int ProcessPolledData(); + int ProcessRTCPCompoundPacket(RTCPCompoundPacket &rtcpcomppack, RTPRawPacket *pack); + RTPRandom *GetRandomNumberGenerator(RTPRandom *r); + int SendRTPData(const void *data, size_t len); + int SendRTCPData(const void *data, size_t len); - RTPRandom *rtprnd; - bool deletertprnd; + RTPRandom *rtprnd; + bool deletertprnd; - RTPTransmitter *rtptrans; - bool created; - bool deletetransmitter; - bool usingpollthread, needthreadsafety; - bool acceptownpackets; - bool useSR_BYEifpossible; - size_t maxpacksize; - double sessionbandwidth; - double controlfragment; - double sendermultiplier; - double byemultiplier; - double membermultiplier; - double collisionmultiplier; - double notemultiplier; - bool sentpackets; + RTPTransmitter *rtptrans; + bool created; + bool deletetransmitter; + bool usingpollthread, needthreadsafety; + bool acceptownpackets; + bool useSR_BYEifpossible; + size_t maxpacksize; + double sessionbandwidth; + double controlfragment; + double sendermultiplier; + double byemultiplier; + double membermultiplier; + double collisionmultiplier; + double notemultiplier; + bool sentpackets; - bool m_changeIncomingData, m_changeOutgoingData; + bool m_changeIncomingData, m_changeOutgoingData; - RTPSessionSources sources; - RTPPacketBuilder packetbuilder; - RTCPScheduler rtcpsched; - RTCPPacketBuilder rtcpbuilder; - RTPCollisionList collisionlist; + RTPSessionSources sources; + RTPPacketBuilder packetbuilder; + RTCPScheduler rtcpsched; + RTCPPacketBuilder rtcpbuilder; + RTPCollisionList collisionlist; - std::list byepackets; + std::list byepackets; - friend class RTPSessionSources; - friend class RTCPSessionPacketBuilder; + friend class RTPSessionSources; + friend class RTCPSessionPacketBuilder; }; -inline RTPTransmitter *RTPSession::NewUserDefinedTransmitter() { return 0; } -inline void RTPSession::OnRTPPacket(RTPPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSession::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSession::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool ) { } -inline void RTPSession::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, size_t ) { } -inline void RTPSession::OnNewSource(RTPSourceData *) { } -inline void RTPSession::OnRemoveSource(RTPSourceData *) { } -inline void RTPSession::OnTimeout(RTPSourceData *) { } -inline void RTPSession::OnBYETimeout(RTPSourceData *) { } -inline void RTPSession::OnAPPPacket(RTCPAPPPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSession::OnUnknownPacketType(RTCPPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSession::OnUnknownPacketFormat(RTCPPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSession::OnNoteTimeout(RTPSourceData *) { } -inline void RTPSession::OnRTCPSenderReport(RTPSourceData *) { } -inline void RTPSession::OnRTCPReceiverReport(RTPSourceData *) { } -inline void RTPSession::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, size_t) { } +inline RTPTransmitter *RTPSession::NewUserDefinedTransmitter() +{ + return 0; +} +inline void RTPSession::OnRTPPacket(RTPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool) +{ +} +inline void RTPSession::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, size_t) +{ +} +inline void RTPSession::OnNewSource(RTPSourceData *) +{ +} +inline void RTPSession::OnRemoveSource(RTPSourceData *) +{ +} +inline void RTPSession::OnTimeout(RTPSourceData *) +{ +} +inline void RTPSession::OnBYETimeout(RTPSourceData *) +{ +} +inline void RTPSession::OnAPPPacket(RTCPAPPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnUnknownPacketType(RTCPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnUnknownPacketFormat(RTCPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnNoteTimeout(RTPSourceData *) +{ +} +inline void RTPSession::OnRTCPSenderReport(RTPSourceData *) +{ +} +inline void RTPSession::OnRTCPReceiverReport(RTPSourceData *) +{ +} +inline void RTPSession::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, size_t) +{ +} #ifdef RTP_SUPPORT_SDESPRIV -inline void RTPSession::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, size_t, const void *, size_t) { } +inline void RTPSession::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, size_t, const void *, size_t) +{ +} #endif // RTP_SUPPORT_SDESPRIV -inline void RTPSession::OnBYEPacket(RTPSourceData *) { } -inline void RTPSession::OnSendRTCPCompoundPacket(RTCPCompoundPacket *) { } - -inline int RTPSession::OnChangeRTPOrRTCPData(const void *, size_t, bool, void **, size_t *) { - return ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED; +inline void RTPSession::OnBYEPacket(RTPSourceData *) +{ +} +inline void RTPSession::OnSendRTCPCompoundPacket(RTCPCompoundPacket *) +{ +} + +inline int RTPSession::OnChangeRTPOrRTCPData(const void *, size_t, bool, void **, size_t *) +{ + return ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED; +} +inline void RTPSession::OnSentRTPOrRTCPData(void *, size_t, bool) +{ +} +inline bool RTPSession::OnChangeIncomingData(RTPRawPacket *) +{ + return true; +} +inline void RTPSession::OnValidatedRTPPacket(RTPSourceData *, RTPPacket *, bool, bool *) +{ } -inline void RTPSession::OnSentRTPOrRTCPData(void *, size_t, bool) { } -inline bool RTPSession::OnChangeIncomingData(RTPRawPacket *) { return true; } -inline void RTPSession::OnValidatedRTPPacket(RTPSourceData *, RTPPacket *, bool, bool *) { } } // end namespace diff --git a/qrtplib/rtpsessionparams.cpp b/qrtplib/rtpsessionparams.cpp index d8cb21da3..407e20ed6 100644 --- a/qrtplib/rtpsessionparams.cpp +++ b/qrtplib/rtpsessionparams.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpconfig.h" #include "rtpsessionparams.h" @@ -38,47 +38,48 @@ namespace qrtplib { -RTPSessionParams::RTPSessionParams() : mininterval(0,0) +RTPSessionParams::RTPSessionParams() : + mininterval(0, 0) { - usepollthread = false; - m_needThreadSafety = false; - maxpacksize = RTP_DEFAULTPACKETSIZE; - receivemode = RTPTransmitter::AcceptAll; - acceptown = false; - owntsunit = -1; // The user will have to set it to the correct value himself - resolvehostname = false; + usepollthread = false; + m_needThreadSafety = false; + maxpacksize = RTP_DEFAULTPACKETSIZE; + receivemode = RTPTransmitter::AcceptAll; + acceptown = false; + owntsunit = -1; // The user will have to set it to the correct value himself + resolvehostname = false; #ifdef RTP_SUPPORT_PROBATION - probationtype = RTPSources::ProbationStore; + probationtype = RTPSources::ProbationStore; #endif // RTP_SUPPORT_PROBATION - mininterval = RTPTime(RTCP_DEFAULTMININTERVAL); - sessionbandwidth = RTP_DEFAULTSESSIONBANDWIDTH; - controlfrac = RTCP_DEFAULTBANDWIDTHFRACTION; - senderfrac = RTCP_DEFAULTSENDERFRACTION; - usehalfatstartup = RTCP_DEFAULTHALFATSTARTUP; - immediatebye = RTCP_DEFAULTIMMEDIATEBYE; - SR_BYE = RTCP_DEFAULTSRBYE; + mininterval = RTPTime(RTCP_DEFAULTMININTERVAL); + sessionbandwidth = RTP_DEFAULTSESSIONBANDWIDTH; + controlfrac = RTCP_DEFAULTBANDWIDTHFRACTION; + senderfrac = RTCP_DEFAULTSENDERFRACTION; + usehalfatstartup = RTCP_DEFAULTHALFATSTARTUP; + immediatebye = RTCP_DEFAULTIMMEDIATEBYE; + SR_BYE = RTCP_DEFAULTSRBYE; - sendermultiplier = RTP_SENDERTIMEOUTMULTIPLIER; - generaltimeoutmultiplier = RTP_MEMBERTIMEOUTMULTIPLIER; - byetimeoutmultiplier = RTP_BYETIMEOUTMULTIPLIER; - collisionmultiplier = RTP_COLLISIONTIMEOUTMULTIPLIER; - notemultiplier = RTP_NOTETTIMEOUTMULTIPLIER; + sendermultiplier = RTP_SENDERTIMEOUTMULTIPLIER; + generaltimeoutmultiplier = RTP_MEMBERTIMEOUTMULTIPLIER; + byetimeoutmultiplier = RTP_BYETIMEOUTMULTIPLIER; + collisionmultiplier = RTP_COLLISIONTIMEOUTMULTIPLIER; + notemultiplier = RTP_NOTETTIMEOUTMULTIPLIER; - usepredefinedssrc = false; - predefinedssrc = 0; + usepredefinedssrc = false; + predefinedssrc = 0; } int RTPSessionParams::SetUsePollThread(bool usethread) { - JRTPLIB_UNUSED(usethread); - return ERR_RTP_NOTHREADSUPPORT; + JRTPLIB_UNUSED(usethread); + return ERR_RTP_NOTHREADSUPPORT; } int RTPSessionParams::SetNeedThreadSafety(bool s) { - JRTPLIB_UNUSED(s); - return ERR_RTP_NOTHREADSUPPORT; + JRTPLIB_UNUSED(s); + return ERR_RTP_NOTHREADSUPPORT; } } // end namespace diff --git a/qrtplib/rtpsessionparams.h b/qrtplib/rtpsessionparams.h index c8efac478..3a455e29f 100644 --- a/qrtplib/rtpsessionparams.h +++ b/qrtplib/rtpsessionparams.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpsessionparams.h @@ -54,200 +54,332 @@ namespace qrtplib class JRTPLIB_IMPORTEXPORT RTPSessionParams { public: - RTPSessionParams(); + RTPSessionParams(); - /** If \c usethread is \c true, the session will use a poll thread to automatically process incoming - * data and to send RTCP packets when necessary. - */ - int SetUsePollThread(bool usethread); + /** If \c usethread is \c true, the session will use a poll thread to automatically process incoming + * data and to send RTCP packets when necessary. + */ + int SetUsePollThread(bool usethread); - /** if `s` is `true`, the session will use mutexes in case multiple threads - * are at work. */ - int SetNeedThreadSafety(bool s); + /** if `s` is `true`, the session will use mutexes in case multiple threads + * are at work. */ + int SetNeedThreadSafety(bool s); - /** Returns whether the session should use a poll thread or not (default is \c true). */ - bool IsUsingPollThread() const { return usepollthread; } + /** Returns whether the session should use a poll thread or not (default is \c true). */ + bool IsUsingPollThread() const + { + return usepollthread; + } - /** Sets the maximum allowed packet size for the session. */ - void SetMaximumPacketSize(size_t max) { maxpacksize = max; } + /** Sets the maximum allowed packet size for the session. */ + void SetMaximumPacketSize(size_t max) + { + maxpacksize = max; + } - /** Returns the maximum allowed packet size (default is 1400 bytes). */ - size_t GetMaximumPacketSize() const { return maxpacksize; } + /** Returns the maximum allowed packet size (default is 1400 bytes). */ + size_t GetMaximumPacketSize() const + { + return maxpacksize; + } - /** If the argument is \c true, the session should accept its own packets and store - * them accordingly in the source table. - */ - void SetAcceptOwnPackets(bool accept) { acceptown = accept; } + /** If the argument is \c true, the session should accept its own packets and store + * them accordingly in the source table. + */ + void SetAcceptOwnPackets(bool accept) + { + acceptown = accept; + } - /** Returns \c true if the session should accept its own packets (default is \c false). */ - bool AcceptOwnPackets() const { return acceptown; } + /** Returns \c true if the session should accept its own packets (default is \c false). */ + bool AcceptOwnPackets() const + { + return acceptown; + } - /** Sets the receive mode to be used by the session. */ - void SetReceiveMode(RTPTransmitter::ReceiveMode recvmode) { receivemode = recvmode; } + /** Sets the receive mode to be used by the session. */ + void SetReceiveMode(RTPTransmitter::ReceiveMode recvmode) + { + receivemode = recvmode; + } - /** Sets the receive mode to be used by the session (default is: accept all packets). */ - RTPTransmitter::ReceiveMode GetReceiveMode() const { return receivemode; } + /** Sets the receive mode to be used by the session (default is: accept all packets). */ + RTPTransmitter::ReceiveMode GetReceiveMode() const + { + return receivemode; + } - /** Sets the timestamp unit for our own data. - * Sets the timestamp unit for our own data. The timestamp unit is defined as a time interval in - * seconds divided by the corresponding timestamp interval. For example, for 8000 Hz audio, the - * timestamp unit would typically be 1/8000. Since this value is initially set to an illegal value, - * the user must set this to an allowed value to be able to create a session. - */ - void SetOwnTimestampUnit(double tsunit) { owntsunit = tsunit; } + /** Sets the timestamp unit for our own data. + * Sets the timestamp unit for our own data. The timestamp unit is defined as a time interval in + * seconds divided by the corresponding timestamp interval. For example, for 8000 Hz audio, the + * timestamp unit would typically be 1/8000. Since this value is initially set to an illegal value, + * the user must set this to an allowed value to be able to create a session. + */ + void SetOwnTimestampUnit(double tsunit) + { + owntsunit = tsunit; + } - /** Returns the currently set timestamp unit. */ - double GetOwnTimestampUnit() const { return owntsunit; } + /** Returns the currently set timestamp unit. */ + double GetOwnTimestampUnit() const + { + return owntsunit; + } - /** Sets a flag indicating if a DNS lookup should be done to determine our hostname (to construct a CNAME item). - * If \c v is set to \c true, the session will ask the transmitter to find a host name based upon the IP - * addresses in its list of local IP addresses. If set to \c false, a call to \c gethostname or something - * similar will be used to find the local hostname. Note that the first method might take some time. - */ - void SetResolveLocalHostname(bool v) { resolvehostname = v; } + /** Sets a flag indicating if a DNS lookup should be done to determine our hostname (to construct a CNAME item). + * If \c v is set to \c true, the session will ask the transmitter to find a host name based upon the IP + * addresses in its list of local IP addresses. If set to \c false, a call to \c gethostname or something + * similar will be used to find the local hostname. Note that the first method might take some time. + */ + void SetResolveLocalHostname(bool v) + { + resolvehostname = v; + } - /** Returns whether the local hostname should be determined from the transmitter's list of local IP addresses - * or not (default is \c false). - */ - bool GetResolveLocalHostname() const { return resolvehostname; } + /** Returns whether the local hostname should be determined from the transmitter's list of local IP addresses + * or not (default is \c false). + */ + bool GetResolveLocalHostname() const + { + return resolvehostname; + } #ifdef RTP_SUPPORT_PROBATION - /** If probation support is enabled, this function sets the probation type to be used. */ - void SetProbationType(RTPSources::ProbationType probtype) { probationtype = probtype; } + /** If probation support is enabled, this function sets the probation type to be used. */ + void SetProbationType(RTPSources::ProbationType probtype) + { + probationtype = probtype; + } - /** Returns the probation type which will be used (default is RTPSources::ProbationStore). */ - RTPSources::ProbationType GetProbationType() const { return probationtype; } + /** Returns the probation type which will be used (default is RTPSources::ProbationStore). */ + RTPSources::ProbationType GetProbationType() const + { + return probationtype; + } #endif // RTP_SUPPORT_PROBATION - /** Sets the session bandwidth in bytes per second. */ - void SetSessionBandwidth(double sessbw) { sessionbandwidth = sessbw; } + /** Sets the session bandwidth in bytes per second. */ + void SetSessionBandwidth(double sessbw) + { + sessionbandwidth = sessbw; + } - /** Returns the session bandwidth in bytes per second (default is 10000 bytes per second). */ - double GetSessionBandwidth() const { return sessionbandwidth; } + /** Returns the session bandwidth in bytes per second (default is 10000 bytes per second). */ + double GetSessionBandwidth() const + { + return sessionbandwidth; + } - /** Sets the fraction of the session bandwidth to be used for control traffic. */ - void SetControlTrafficFraction(double frac) { controlfrac = frac; } + /** Sets the fraction of the session bandwidth to be used for control traffic. */ + void SetControlTrafficFraction(double frac) + { + controlfrac = frac; + } - /** Returns the fraction of the session bandwidth that will be used for control traffic (default is 5%). */ - double GetControlTrafficFraction() const { return controlfrac; } + /** Returns the fraction of the session bandwidth that will be used for control traffic (default is 5%). */ + double GetControlTrafficFraction() const + { + return controlfrac; + } - /** Sets the minimum fraction of the control traffic that will be used by senders. */ - void SetSenderControlBandwidthFraction(double frac) { senderfrac = frac; } + /** Sets the minimum fraction of the control traffic that will be used by senders. */ + void SetSenderControlBandwidthFraction(double frac) + { + senderfrac = frac; + } - /** Returns the minimum fraction of the control traffic that will be used by senders (default is 25%). */ - double GetSenderControlBandwidthFraction() const { return senderfrac; } + /** Returns the minimum fraction of the control traffic that will be used by senders (default is 25%). */ + double GetSenderControlBandwidthFraction() const + { + return senderfrac; + } - /** Set the minimal time interval between sending RTCP packets. */ - void SetMinimumRTCPTransmissionInterval(const RTPTime &t) { mininterval = t; } + /** Set the minimal time interval between sending RTCP packets. */ + void SetMinimumRTCPTransmissionInterval(const RTPTime &t) + { + mininterval = t; + } - /** Returns the minimal time interval between sending RTCP packets (default is 5 seconds). */ - RTPTime GetMinimumRTCPTransmissionInterval() const { return mininterval; } + /** Returns the minimal time interval between sending RTCP packets (default is 5 seconds). */ + RTPTime GetMinimumRTCPTransmissionInterval() const + { + return mininterval; + } - /** If \c usehalf is set to \c true, the session will only wait half of the calculated RTCP - * interval before sending its first RTCP packet. - */ - void SetUseHalfRTCPIntervalAtStartup(bool usehalf) { usehalfatstartup = usehalf; } + /** If \c usehalf is set to \c true, the session will only wait half of the calculated RTCP + * interval before sending its first RTCP packet. + */ + void SetUseHalfRTCPIntervalAtStartup(bool usehalf) + { + usehalfatstartup = usehalf; + } - /** Returns whether the session will only wait half of the calculated RTCP interval before sending its - * first RTCP packet or not (default is \c true). - */ - bool GetUseHalfRTCPIntervalAtStartup() const { return usehalfatstartup; } + /** Returns whether the session will only wait half of the calculated RTCP interval before sending its + * first RTCP packet or not (default is \c true). + */ + bool GetUseHalfRTCPIntervalAtStartup() const + { + return usehalfatstartup; + } - /** If \c v is \c true, the session will send a BYE packet immediately if this is allowed. */ - void SetRequestImmediateBYE(bool v) { immediatebye = v; } + /** If \c v is \c true, the session will send a BYE packet immediately if this is allowed. */ + void SetRequestImmediateBYE(bool v) + { + immediatebye = v; + } - /** Returns whether the session should send a BYE packet immediately (if allowed) or not (default is \c true). */ - bool GetRequestImmediateBYE() const { return immediatebye; } + /** Returns whether the session should send a BYE packet immediately (if allowed) or not (default is \c true). */ + bool GetRequestImmediateBYE() const + { + return immediatebye; + } - /** When sending a BYE packet, this indicates whether it will be part of an RTCP compound packet - * that begins with a sender report (if allowed) or a receiver report. - */ - void SetSenderReportForBYE(bool v) { SR_BYE = v; } + /** When sending a BYE packet, this indicates whether it will be part of an RTCP compound packet + * that begins with a sender report (if allowed) or a receiver report. + */ + void SetSenderReportForBYE(bool v) + { + SR_BYE = v; + } - /** Returns \c true if a BYE packet will be sent in an RTCP compound packet which starts with a - * sender report; if a receiver report will be used, the function returns \c false (default is \c true). - */ - bool GetSenderReportForBYE() const { return SR_BYE; } + /** Returns \c true if a BYE packet will be sent in an RTCP compound packet which starts with a + * sender report; if a receiver report will be used, the function returns \c false (default is \c true). + */ + bool GetSenderReportForBYE() const + { + return SR_BYE; + } - /** Sets the multiplier to be used when timing out senders. */ - void SetSenderTimeoutMultiplier(double m) { sendermultiplier = m; } + /** Sets the multiplier to be used when timing out senders. */ + void SetSenderTimeoutMultiplier(double m) + { + sendermultiplier = m; + } - /** Returns the multiplier to be used when timing out senders (default is 2). */ - double GetSenderTimeoutMultiplier() const { return sendermultiplier; } + /** Returns the multiplier to be used when timing out senders (default is 2). */ + double GetSenderTimeoutMultiplier() const + { + return sendermultiplier; + } - /** Sets the multiplier to be used when timing out members. */ - void SetSourceTimeoutMultiplier(double m) { generaltimeoutmultiplier = m; } + /** Sets the multiplier to be used when timing out members. */ + void SetSourceTimeoutMultiplier(double m) + { + generaltimeoutmultiplier = m; + } - /** Returns the multiplier to be used when timing out members (default is 5). */ - double GetSourceTimeoutMultiplier() const { return generaltimeoutmultiplier; } + /** Returns the multiplier to be used when timing out members (default is 5). */ + double GetSourceTimeoutMultiplier() const + { + return generaltimeoutmultiplier; + } - /** Sets the multiplier to be used when timing out a member after it has sent a BYE packet. */ - void SetBYETimeoutMultiplier(double m) { byetimeoutmultiplier = m; } + /** Sets the multiplier to be used when timing out a member after it has sent a BYE packet. */ + void SetBYETimeoutMultiplier(double m) + { + byetimeoutmultiplier = m; + } - /** Returns the multiplier to be used when timing out a member after it has sent a BYE packet (default is 1). */ - double GetBYETimeoutMultiplier() const { return byetimeoutmultiplier; } + /** Returns the multiplier to be used when timing out a member after it has sent a BYE packet (default is 1). */ + double GetBYETimeoutMultiplier() const + { + return byetimeoutmultiplier; + } - /** Sets the multiplier to be used when timing out entries in the collision table. */ - void SetCollisionTimeoutMultiplier(double m) { collisionmultiplier = m; } + /** Sets the multiplier to be used when timing out entries in the collision table. */ + void SetCollisionTimeoutMultiplier(double m) + { + collisionmultiplier = m; + } - /** Returns the multiplier to be used when timing out entries in the collision table (default is 10). */ - double GetCollisionTimeoutMultiplier() const { return collisionmultiplier; } + /** Returns the multiplier to be used when timing out entries in the collision table (default is 10). */ + double GetCollisionTimeoutMultiplier() const + { + return collisionmultiplier; + } - /** Sets the multiplier to be used when timing out SDES NOTE information. */ - void SetNoteTimeoutMultiplier(double m) { notemultiplier = m; } + /** Sets the multiplier to be used when timing out SDES NOTE information. */ + void SetNoteTimeoutMultiplier(double m) + { + notemultiplier = m; + } - /** Returns the multiplier to be used when timing out SDES NOTE information (default is 25). */ - double GetNoteTimeoutMultiplier() const { return notemultiplier; } + /** Returns the multiplier to be used when timing out SDES NOTE information (default is 25). */ + double GetNoteTimeoutMultiplier() const + { + return notemultiplier; + } - /** Sets a flag which indicates if a predefined SSRC identifier should be used. */ - void SetUsePredefinedSSRC(bool f) { usepredefinedssrc = f; } + /** Sets a flag which indicates if a predefined SSRC identifier should be used. */ + void SetUsePredefinedSSRC(bool f) + { + usepredefinedssrc = f; + } - /** Returns a flag indicating if a predefined SSRC should be used. */ - bool GetUsePredefinedSSRC() const { return usepredefinedssrc; } + /** Returns a flag indicating if a predefined SSRC should be used. */ + bool GetUsePredefinedSSRC() const + { + return usepredefinedssrc; + } - /** Sets the SSRC which will be used if RTPSessionParams::GetUsePredefinedSSRC returns true. */ - void SetPredefinedSSRC(uint32_t ssrc) { predefinedssrc = ssrc; } + /** Sets the SSRC which will be used if RTPSessionParams::GetUsePredefinedSSRC returns true. */ + void SetPredefinedSSRC(uint32_t ssrc) + { + predefinedssrc = ssrc; + } - /** Returns the SSRC which will be used if RTPSessionParams::GetUsePredefinedSSRC returns true. */ - uint32_t GetPredefinedSSRC() const { return predefinedssrc; } + /** Returns the SSRC which will be used if RTPSessionParams::GetUsePredefinedSSRC returns true. */ + uint32_t GetPredefinedSSRC() const + { + return predefinedssrc; + } - /** Forces this string to be used as the CNAME identifier. */ - void SetCNAME(const std::string &s) { cname = s; } + /** Forces this string to be used as the CNAME identifier. */ + void SetCNAME(const std::string &s) + { + cname = s; + } - /** Returns the currently set CNAME, is blank when this will be generated automatically (the default). */ - std::string GetCNAME() const { return cname; } + /** Returns the currently set CNAME, is blank when this will be generated automatically (the default). */ + std::string GetCNAME() const + { + return cname; + } - /** Returns `true` if thread safety was requested using RTPSessionParams::SetNeedThreadSafety. */ - bool NeedThreadSafety() const { return m_needThreadSafety; } + /** Returns `true` if thread safety was requested using RTPSessionParams::SetNeedThreadSafety. */ + bool NeedThreadSafety() const + { + return m_needThreadSafety; + } private: - bool acceptown; - bool usepollthread; - size_t maxpacksize; - double owntsunit; - RTPTransmitter::ReceiveMode receivemode; - bool resolvehostname; + bool acceptown; + bool usepollthread; + size_t maxpacksize; + double owntsunit; + RTPTransmitter::ReceiveMode receivemode; + bool resolvehostname; #ifdef RTP_SUPPORT_PROBATION - RTPSources::ProbationType probationtype; + RTPSources::ProbationType probationtype; #endif // RTP_SUPPORT_PROBATION - double sessionbandwidth; - double controlfrac; - double senderfrac; - RTPTime mininterval; - bool usehalfatstartup; - bool immediatebye; - bool SR_BYE; + double sessionbandwidth; + double controlfrac; + double senderfrac; + RTPTime mininterval; + bool usehalfatstartup; + bool immediatebye; + bool SR_BYE; - double sendermultiplier; - double generaltimeoutmultiplier; - double byetimeoutmultiplier; - double collisionmultiplier; - double notemultiplier; + double sendermultiplier; + double generaltimeoutmultiplier; + double byetimeoutmultiplier; + double collisionmultiplier; + double notemultiplier; - bool usepredefinedssrc; - uint32_t predefinedssrc; + bool usepredefinedssrc; + uint32_t predefinedssrc; - std::string cname; - bool m_needThreadSafety; + std::string cname; + bool m_needThreadSafety; }; } // end namespace diff --git a/qrtplib/rtpsessionsources.cpp b/qrtplib/rtpsessionsources.cpp index 0bdfa0c59..c1525fada 100644 --- a/qrtplib/rtpsessionsources.cpp +++ b/qrtplib/rtpsessionsources.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpsessionsources.h" #include "rtpsession.h" @@ -37,103 +37,101 @@ namespace qrtplib { -void RTPSessionSources::OnRTPPacket(RTPPacket *pack,const RTPTime &receivetime,const RTPAddress *senderaddress) +void RTPSessionSources::OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress) { - rtpsession.OnRTPPacket(pack,receivetime,senderaddress); + rtpsession.OnRTPPacket(pack, receivetime, senderaddress); } -void RTPSessionSources::OnRTCPCompoundPacket(RTCPCompoundPacket *pack,const RTPTime &receivetime,const RTPAddress *senderaddress) +void RTPSessionSources::OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress) { - if (senderaddress != 0) // don't analyse own RTCP packets again (they're already analysed on their way out) - rtpsession.rtcpsched.AnalyseIncoming(*pack); - rtpsession.OnRTCPCompoundPacket(pack,receivetime,senderaddress); + if (senderaddress != 0) // don't analyse own RTCP packets again (they're already analysed on their way out) + rtpsession.rtcpsched.AnalyseIncoming(*pack); + rtpsession.OnRTCPCompoundPacket(pack, receivetime, senderaddress); } -void RTPSessionSources::OnSSRCCollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp) +void RTPSessionSources::OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp) { - if (srcdat->IsOwnSSRC()) - owncollision = true; - rtpsession.OnSSRCCollision(srcdat,senderaddress,isrtp); + if (srcdat->IsOwnSSRC()) + owncollision = true; + rtpsession.OnSSRCCollision(srcdat, senderaddress, isrtp); } -void RTPSessionSources::OnCNAMECollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,const uint8_t *cname,size_t cnamelength) +void RTPSessionSources::OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, size_t cnamelength) { - rtpsession.OnCNAMECollision(srcdat,senderaddress,cname,cnamelength); + rtpsession.OnCNAMECollision(srcdat, senderaddress, cname, cnamelength); } void RTPSessionSources::OnNewSource(RTPSourceData *srcdat) { - rtpsession.OnNewSource(srcdat); + rtpsession.OnNewSource(srcdat); } void RTPSessionSources::OnRemoveSource(RTPSourceData *srcdat) { - rtpsession.OnRemoveSource(srcdat); + rtpsession.OnRemoveSource(srcdat); } void RTPSessionSources::OnTimeout(RTPSourceData *srcdat) { - rtpsession.rtcpsched.ActiveMemberDecrease(); - rtpsession.OnTimeout(srcdat); + rtpsession.rtcpsched.ActiveMemberDecrease(); + rtpsession.OnTimeout(srcdat); } void RTPSessionSources::OnBYETimeout(RTPSourceData *srcdat) { - rtpsession.OnBYETimeout(srcdat); + rtpsession.OnBYETimeout(srcdat); } void RTPSessionSources::OnBYEPacket(RTPSourceData *srcdat) { - rtpsession.rtcpsched.ActiveMemberDecrease(); - rtpsession.OnBYEPacket(srcdat); + rtpsession.rtcpsched.ActiveMemberDecrease(); + rtpsession.OnBYEPacket(srcdat); } -void RTPSessionSources::OnAPPPacket(RTCPAPPPacket *apppacket,const RTPTime &receivetime,const RTPAddress *senderaddress) +void RTPSessionSources::OnAPPPacket(RTCPAPPPacket *apppacket, const RTPTime &receivetime, const RTPAddress *senderaddress) { - rtpsession.OnAPPPacket(apppacket,receivetime,senderaddress); + rtpsession.OnAPPPacket(apppacket, receivetime, senderaddress); } -void RTPSessionSources::OnUnknownPacketType(RTCPPacket *rtcppack,const RTPTime &receivetime, const RTPAddress *senderaddress) +void RTPSessionSources::OnUnknownPacketType(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress) { - rtpsession.OnUnknownPacketType(rtcppack,receivetime,senderaddress); + rtpsession.OnUnknownPacketType(rtcppack, receivetime, senderaddress); } -void RTPSessionSources::OnUnknownPacketFormat(RTCPPacket *rtcppack,const RTPTime &receivetime,const RTPAddress *senderaddress) +void RTPSessionSources::OnUnknownPacketFormat(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress) { - rtpsession.OnUnknownPacketFormat(rtcppack,receivetime,senderaddress); + rtpsession.OnUnknownPacketFormat(rtcppack, receivetime, senderaddress); } void RTPSessionSources::OnNoteTimeout(RTPSourceData *srcdat) { - rtpsession.OnNoteTimeout(srcdat); + rtpsession.OnNoteTimeout(srcdat); } void RTPSessionSources::OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled) { - rtpsession.OnValidatedRTPPacket(srcdat, rtppack, isonprobation, ispackethandled); + rtpsession.OnValidatedRTPPacket(srcdat, rtppack, isonprobation, ispackethandled); } void RTPSessionSources::OnRTCPSenderReport(RTPSourceData *srcdat) { - rtpsession.OnRTCPSenderReport(srcdat); + rtpsession.OnRTCPSenderReport(srcdat); } void RTPSessionSources::OnRTCPReceiverReport(RTPSourceData *srcdat) { - rtpsession.OnRTCPReceiverReport(srcdat); + rtpsession.OnRTCPReceiverReport(srcdat); } -void RTPSessionSources::OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, - const void *itemdata, size_t itemlength) +void RTPSessionSources::OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, size_t itemlength) { - rtpsession.OnRTCPSDESItem(srcdat, t, itemdata, itemlength); + rtpsession.OnRTCPSDESItem(srcdat, t, itemdata, itemlength); } #ifdef RTP_SUPPORT_SDESPRIV -void RTPSessionSources::OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, - const void *valuedata, size_t valuelen) +void RTPSessionSources::OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, const void *valuedata, size_t valuelen) { - rtpsession.OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); + rtpsession.OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); } #endif // RTP_SUPPORT_SDESPRIV diff --git a/qrtplib/rtpsessionsources.h b/qrtplib/rtpsessionsources.h index dddc035ee..d17c7b193 100644 --- a/qrtplib/rtpsessionsources.h +++ b/qrtplib/rtpsessionsources.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpsessionsources.h @@ -46,46 +46,49 @@ namespace qrtplib class RTPSession; -class JRTPLIB_IMPORTEXPORT RTPSessionSources : public RTPSources +class JRTPLIB_IMPORTEXPORT RTPSessionSources: public RTPSources { public: - RTPSessionSources(RTPSession &sess,RTPMemoryManager *mgr) : RTPSources(RTPSources::ProbationStore,mgr),rtpsession(sess) - { owncollision = false; } - ~RTPSessionSources() { } - void ClearOwnCollisionFlag() { owncollision = false; } - bool DetectedOwnCollision() const { return owncollision; } + RTPSessionSources(RTPSession &sess) : + RTPSources(RTPSources::ProbationStore), rtpsession(sess) + { + owncollision = false; + } + ~RTPSessionSources() + { + } + void ClearOwnCollisionFlag() + { + owncollision = false; + } + bool DetectedOwnCollision() const + { + return owncollision; + } private: - void OnRTPPacket(RTPPacket *pack,const RTPTime &receivetime, - const RTPAddress *senderaddress); - void OnRTCPCompoundPacket(RTCPCompoundPacket *pack,const RTPTime &receivetime, - const RTPAddress *senderaddress); - void OnSSRCCollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp); - void OnCNAMECollision(RTPSourceData *srcdat,const RTPAddress *senderaddress, - const uint8_t *cname,size_t cnamelength); - void OnNewSource(RTPSourceData *srcdat); - void OnRemoveSource(RTPSourceData *srcdat); - void OnTimeout(RTPSourceData *srcdat); - void OnBYETimeout(RTPSourceData *srcdat); - void OnBYEPacket(RTPSourceData *srcdat); - void OnAPPPacket(RTCPAPPPacket *apppacket,const RTPTime &receivetime, - const RTPAddress *senderaddress); - void OnUnknownPacketType(RTCPPacket *rtcppack,const RTPTime &receivetime, - const RTPAddress *senderaddress); - void OnUnknownPacketFormat(RTCPPacket *rtcppack,const RTPTime &receivetime, - const RTPAddress *senderaddress); - void OnNoteTimeout(RTPSourceData *srcdat); - void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); - void OnRTCPSenderReport(RTPSourceData *srcdat); - void OnRTCPReceiverReport(RTPSourceData *srcdat); - void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, - const void *itemdata, size_t itemlength); + void OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); + void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, size_t cnamelength); + void OnNewSource(RTPSourceData *srcdat); + void OnRemoveSource(RTPSourceData *srcdat); + void OnTimeout(RTPSourceData *srcdat); + void OnBYETimeout(RTPSourceData *srcdat); + void OnBYEPacket(RTPSourceData *srcdat); + void OnAPPPacket(RTCPAPPPacket *apppacket, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnUnknownPacketType(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnUnknownPacketFormat(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnNoteTimeout(RTPSourceData *srcdat); + void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); + void OnRTCPSenderReport(RTPSourceData *srcdat); + void OnRTCPReceiverReport(RTPSourceData *srcdat); + void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, size_t itemlength); #ifdef RTP_SUPPORT_SDESPRIV - void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, - const void *valuedata, size_t valuelen); + void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, const void *valuedata, size_t valuelen); #endif // RTP_SUPPORT_SDESPRIV - RTPSession &rtpsession; - bool owncollision; + RTPSession &rtpsession; + bool owncollision; }; } // end namespace diff --git a/qrtplib/rtpsocketutil.h b/qrtplib/rtpsocketutil.h index cee901089..ea648afc5 100644 --- a/qrtplib/rtpsocketutil.h +++ b/qrtplib/rtpsocketutil.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpsocketutil.h @@ -56,6 +56,7 @@ typedef SOCKET SocketType; #endif // RTP_SOCKETTYPE_WINSOCK -} // end namespace +} + // end namespace #endif // RTPSOCKETUTIL_H diff --git a/qrtplib/rtpsocketutilinternal.h b/qrtplib/rtpsocketutilinternal.h index a2473efbf..3dd63c5ca 100644 --- a/qrtplib/rtpsocketutilinternal.h +++ b/qrtplib/rtpsocketutilinternal.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpsocketutilinternal.h @@ -39,40 +39,40 @@ #define RTPSOCKETUTILINTERNAL_H #ifdef RTP_SOCKETTYPE_WINSOCK - #define RTPSOCKERR INVALID_SOCKET - #define RTPCLOSE(x) closesocket(x) - #define RTPSOCKLENTYPE int - #define RTPIOCTL ioctlsocket +#define RTPSOCKERR INVALID_SOCKET +#define RTPCLOSE(x) closesocket(x) +#define RTPSOCKLENTYPE int +#define RTPIOCTL ioctlsocket #else // not Win32 - #include - #include - #include - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include +#include +#include +#include - #ifdef RTP_HAVE_SYS_FILIO - #include - #endif // RTP_HAVE_SYS_FILIO - #ifdef RTP_HAVE_SYS_SOCKIO - #include - #endif // RTP_HAVE_SYS_SOCKIO - #ifdef RTP_SUPPORT_IFADDRS - #include - #endif // RTP_SUPPORT_IFADDRS +#ifdef RTP_HAVE_SYS_FILIO +#include +#endif // RTP_HAVE_SYS_FILIO +#ifdef RTP_HAVE_SYS_SOCKIO +#include +#endif // RTP_HAVE_SYS_SOCKIO +#ifdef RTP_SUPPORT_IFADDRS +#include +#endif // RTP_SUPPORT_IFADDRS - #define RTPSOCKERR -1 - #define RTPCLOSE(x) close(x) +#define RTPSOCKERR -1 +#define RTPCLOSE(x) close(x) - #ifdef RTP_SOCKLENTYPE_UINT - #define RTPSOCKLENTYPE unsigned int - #else - #define RTPSOCKLENTYPE int - #endif // RTP_SOCKLENTYPE_UINT +#ifdef RTP_SOCKLENTYPE_UINT +#define RTPSOCKLENTYPE unsigned int +#else +#define RTPSOCKLENTYPE int +#endif // RTP_SOCKLENTYPE_UINT - #define RTPIOCTL ioctl +#define RTPIOCTL ioctl #endif // RTP_SOCKETTYPE_WINSOCK #endif // RTPSOCKETUTILINTERNAL_H diff --git a/qrtplib/rtpsourcedata.cpp b/qrtplib/rtpsourcedata.cpp index d02befacc..46a798e62 100644 --- a/qrtplib/rtpsourcedata.cpp +++ b/qrtplib/rtpsourcedata.cpp @@ -1,40 +1,38 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpsourcedata.h" #include "rtpdefines.h" #include "rtpaddress.h" -#include "rtpmemorymanager.h" - #define ACCEPTPACKETCODE \ *accept = true; \ @@ -66,256 +64,255 @@ namespace qrtplib { -void RTPSourceStats::ProcessPacket(RTPPacket *pack,const RTPTime &receivetime,double tsunit, - bool ownpacket,bool *accept,bool applyprobation,bool *onprobation) +void RTPSourceStats::ProcessPacket(RTPPacket *pack, const RTPTime &receivetime, double tsunit, bool ownpacket, bool *accept, bool applyprobation, bool *onprobation) { - JRTPLIB_UNUSED(applyprobation); // possibly unused + JRTPLIB_UNUSED(applyprobation); // possibly unused - // Note that the sequence number in the RTP packet is still just the - // 16 bit number contained in the RTP header + // Note that the sequence number in the RTP packet is still just the + // 16 bit number contained in the RTP header - *onprobation = false; + *onprobation = false; - if (!sentdata) // no valid packets received yet - { + if (!sentdata) // no valid packets received yet + { #ifdef RTP_SUPPORT_PROBATION - if (applyprobation) - { - bool acceptpack = false; + if (applyprobation) + { + bool acceptpack = false; - if (probation) - { - uint16_t pseq; - uint32_t pseq2; + if (probation) + { + uint16_t pseq; + uint32_t pseq2; - pseq = prevseqnr; - pseq++; - pseq2 = (uint32_t)pseq; - if (pseq2 == pack->GetExtendedSequenceNumber()) // ok, its the next expected packet - { - prevseqnr = (uint16_t)pack->GetExtendedSequenceNumber(); - probation--; - if (probation == 0) // probation over - acceptpack = true; - else - *onprobation = true; - } - else // not next packet - { - probation = RTP_PROBATIONCOUNT; - prevseqnr = (uint16_t)pack->GetExtendedSequenceNumber(); - *onprobation = true; - } - } - else // first packet received with this SSRC ID, start probation - { - probation = RTP_PROBATIONCOUNT; - prevseqnr = (uint16_t)pack->GetExtendedSequenceNumber(); - *onprobation = true; - } + pseq = prevseqnr; + pseq++; + pseq2 = (uint32_t) pseq; + if (pseq2 == pack->GetExtendedSequenceNumber()) // ok, its the next expected packet + { + prevseqnr = (uint16_t) pack->GetExtendedSequenceNumber(); + probation--; + if (probation == 0) // probation over + acceptpack = true; + else + *onprobation = true; + } + else // not next packet + { + probation = RTP_PROBATIONCOUNT; + prevseqnr = (uint16_t) pack->GetExtendedSequenceNumber(); + *onprobation = true; + } + } + else // first packet received with this SSRC ID, start probation + { + probation = RTP_PROBATIONCOUNT; + prevseqnr = (uint16_t) pack->GetExtendedSequenceNumber(); + *onprobation = true; + } - if (acceptpack) - { - ACCEPTPACKETCODE - } - else - { - *accept = false; - lastmsgtime = receivetime; - } - } - else // No probation - { - ACCEPTPACKETCODE - } + if (acceptpack) + { + ACCEPTPACKETCODE + } + else + { + *accept = false; + lastmsgtime = receivetime; + } + } + else // No probation + { + ACCEPTPACKETCODE + } #else // No compiled-in probation support - ACCEPTPACKETCODE + ACCEPTPACKETCODE #endif // RTP_SUPPORT_PROBATION - } - else // already got packets - { - uint16_t maxseq16; - uint32_t extseqnr; + } + else // already got packets + { + uint16_t maxseq16; + uint32_t extseqnr; - // Adjust max extended sequence number and set extende seq nr of packet + // Adjust max extended sequence number and set extende seq nr of packet - *accept = true; - packetsreceived++; - numnewpackets++; + *accept = true; + packetsreceived++; + numnewpackets++; - maxseq16 = (uint16_t)(exthighseqnr&0x0000FFFF); - if (pack->GetExtendedSequenceNumber() >= maxseq16) - { - extseqnr = numcycles+pack->GetExtendedSequenceNumber(); - exthighseqnr = extseqnr; - } - else - { - uint16_t dif1,dif2; + maxseq16 = (uint16_t) (exthighseqnr & 0x0000FFFF); + if (pack->GetExtendedSequenceNumber() >= maxseq16) + { + extseqnr = numcycles + pack->GetExtendedSequenceNumber(); + exthighseqnr = extseqnr; + } + else + { + uint16_t dif1, dif2; - dif1 = ((uint16_t)pack->GetExtendedSequenceNumber()); - dif1 -= maxseq16; - dif2 = maxseq16; - dif2 -= ((uint16_t)pack->GetExtendedSequenceNumber()); - if (dif1 < dif2) - { - numcycles += 0x00010000; - extseqnr = numcycles+pack->GetExtendedSequenceNumber(); - exthighseqnr = extseqnr; - } - else - extseqnr = numcycles+pack->GetExtendedSequenceNumber(); - } + dif1 = ((uint16_t) pack->GetExtendedSequenceNumber()); + dif1 -= maxseq16; + dif2 = maxseq16; + dif2 -= ((uint16_t) pack->GetExtendedSequenceNumber()); + if (dif1 < dif2) + { + numcycles += 0x00010000; + extseqnr = numcycles + pack->GetExtendedSequenceNumber(); + exthighseqnr = extseqnr; + } + else + extseqnr = numcycles + pack->GetExtendedSequenceNumber(); + } - pack->SetExtendedSequenceNumber(extseqnr); + pack->SetExtendedSequenceNumber(extseqnr); - // Calculate jitter + // Calculate jitter - if (tsunit > 0) - { + if (tsunit > 0) + { #if 0 - RTPTime curtime = receivetime; - double diffts1,diffts2,diff; + RTPTime curtime = receivetime; + double diffts1,diffts2,diff; - curtime -= prevpacktime; - diffts1 = curtime.GetDouble()/tsunit; - diffts2 = (double)pack->GetTimestamp() - (double)prevtimestamp; - diff = diffts1 - diffts2; - if (diff < 0) - diff = -diff; - diff -= djitter; - diff /= 16.0; - djitter += diff; - jitter = (uint32_t)djitter; + curtime -= prevpacktime; + diffts1 = curtime.GetDouble()/tsunit; + diffts2 = (double)pack->GetTimestamp() - (double)prevtimestamp; + diff = diffts1 - diffts2; + if (diff < 0) + diff = -diff; + diff -= djitter; + diff /= 16.0; + djitter += diff; + jitter = (uint32_t)djitter; #else -RTPTime curtime = receivetime; -double diffts1,diffts2,diff; -uint32_t curts = pack->GetTimestamp(); + RTPTime curtime = receivetime; + double diffts1, diffts2, diff; + uint32_t curts = pack->GetTimestamp(); -curtime -= prevpacktime; -diffts1 = curtime.GetDouble()/tsunit; + curtime -= prevpacktime; + diffts1 = curtime.GetDouble() / tsunit; -if (curts > prevtimestamp) -{ - uint32_t unsigneddiff = curts - prevtimestamp; + if (curts > prevtimestamp) + { + uint32_t unsigneddiff = curts - prevtimestamp; - if (unsigneddiff < 0x10000000) // okay, curts realy is larger than prevtimestamp - diffts2 = (double)unsigneddiff; - else - { - // wraparound occurred and curts is actually smaller than prevtimestamp + if (unsigneddiff < 0x10000000) // okay, curts realy is larger than prevtimestamp + diffts2 = (double) unsigneddiff; + else + { + // wraparound occurred and curts is actually smaller than prevtimestamp - unsigneddiff = -unsigneddiff; // to get the actual difference (in absolute value) - diffts2 = -((double)unsigneddiff); - } -} -else if (curts < prevtimestamp) -{ - uint32_t unsigneddiff = prevtimestamp - curts; + unsigneddiff = -unsigneddiff; // to get the actual difference (in absolute value) + diffts2 = -((double) unsigneddiff); + } + } + else if (curts < prevtimestamp) + { + uint32_t unsigneddiff = prevtimestamp - curts; - if (unsigneddiff < 0x10000000) // okay, curts really is smaller than prevtimestamp - diffts2 = -((double)unsigneddiff); // negative since we actually need curts-prevtimestamp - else - { - // wraparound occurred and curts is actually larger than prevtimestamp + if (unsigneddiff < 0x10000000) // okay, curts really is smaller than prevtimestamp + diffts2 = -((double) unsigneddiff); // negative since we actually need curts-prevtimestamp + else + { + // wraparound occurred and curts is actually larger than prevtimestamp - unsigneddiff = -unsigneddiff; // to get the actual difference (in absolute value) - diffts2 = (double)unsigneddiff; - } -} -else - diffts2 = 0; + unsigneddiff = -unsigneddiff; // to get the actual difference (in absolute value) + diffts2 = (double) unsigneddiff; + } + } + else + diffts2 = 0; -diff = diffts1 - diffts2; -if (diff < 0) - diff = -diff; -diff -= djitter; -diff /= 16.0; -djitter += diff; -jitter = (uint32_t)djitter; + diff = diffts1 - diffts2; + if (diff < 0) + diff = -diff; + diff -= djitter; + diff /= 16.0; + djitter += diff; + jitter = (uint32_t) djitter; #endif - } - else - { - djitter = 0; - jitter = 0; - } + } + else + { + djitter = 0; + jitter = 0; + } - prevpacktime = receivetime; - prevtimestamp = pack->GetTimestamp(); - lastmsgtime = prevpacktime; - if (!ownpacket) // for own packet, this value is set on an outgoing packet - lastrtptime = prevpacktime; - } + prevpacktime = receivetime; + prevtimestamp = pack->GetTimestamp(); + lastmsgtime = prevpacktime; + if (!ownpacket) // for own packet, this value is set on an outgoing packet + lastrtptime = prevpacktime; + } } -RTPSourceData::RTPSourceData(uint32_t s, RTPMemoryManager *mgr) : RTPMemoryObject(mgr),SDESinf(mgr),byetime(0,0) +RTPSourceData::RTPSourceData(uint32_t s) : + byetime(0, 0) { - ssrc = s; - issender = false; - iscsrc = false; - timestampunit = -1; - receivedbye = false; - byereason = 0; - byereasonlen = 0; - rtpaddr = 0; - rtcpaddr = 0; - ownssrc = false; - validated = false; - processedinrtcp = false; - isrtpaddrset = false; - isrtcpaddrset = false; + ssrc = s; + issender = false; + iscsrc = false; + timestampunit = -1; + receivedbye = false; + byereason = 0; + byereasonlen = 0; + rtpaddr = 0; + rtcpaddr = 0; + ownssrc = false; + validated = false; + processedinrtcp = false; + isrtpaddrset = false; + isrtcpaddrset = false; } RTPSourceData::~RTPSourceData() { - FlushPackets(); - if (byereason) - RTPDeleteByteArray(byereason,GetMemoryManager()); - if (rtpaddr) - RTPDelete(rtpaddr,GetMemoryManager()); - if (rtcpaddr) - RTPDelete(rtcpaddr,GetMemoryManager()); + FlushPackets(); + if (byereason) + delete[] byereason; + if (rtpaddr) + delete rtpaddr; + if (rtcpaddr) + delete rtcpaddr; } double RTPSourceData::INF_GetEstimatedTimestampUnit() const { - if (!SRprevinf.HasInfo()) - return -1.0; + if (!SRprevinf.HasInfo()) + return -1.0; - RTPTime t1 = RTPTime(SRinf.GetNTPTimestamp()); - RTPTime t2 = RTPTime(SRprevinf.GetNTPTimestamp()); - if (t1.IsZero() || t2.IsZero()) // one of the times couldn't be calculated - return -1.0; + RTPTime t1 = RTPTime(SRinf.GetNTPTimestamp()); + RTPTime t2 = RTPTime(SRprevinf.GetNTPTimestamp()); + if (t1.IsZero() || t2.IsZero()) // one of the times couldn't be calculated + return -1.0; - if (t1 <= t2) - return -1.0; + if (t1 <= t2) + return -1.0; - t1 -= t2; // get the time difference + t1 -= t2; // get the time difference - uint32_t tsdiff = SRinf.GetRTPTimestamp()-SRprevinf.GetRTPTimestamp(); + uint32_t tsdiff = SRinf.GetRTPTimestamp() - SRprevinf.GetRTPTimestamp(); - return (t1.GetDouble()/((double)tsdiff)); + return (t1.GetDouble() / ((double) tsdiff)); } RTPTime RTPSourceData::INF_GetRoundtripTime() const { - if (!RRinf.HasInfo()) - return RTPTime(0,0); - if (RRinf.GetDelaySinceLastSR() == 0 && RRinf.GetLastSRTimestamp() == 0) - return RTPTime(0,0); + if (!RRinf.HasInfo()) + return RTPTime(0, 0); + if (RRinf.GetDelaySinceLastSR() == 0 && RRinf.GetLastSRTimestamp() == 0) + return RTPTime(0, 0); - RTPNTPTime recvtime = RRinf.GetReceiveTime().GetNTPTime(); - uint32_t rtt = ((recvtime.GetMSW()&0xFFFF)<<16)|((recvtime.GetLSW()>>16)&0xFFFF); - rtt -= RRinf.GetLastSRTimestamp(); - rtt -= RRinf.GetDelaySinceLastSR(); + RTPNTPTime recvtime = RRinf.GetReceiveTime().GetNTPTime(); + uint32_t rtt = ((recvtime.GetMSW() & 0xFFFF) << 16) | ((recvtime.GetLSW() >> 16) & 0xFFFF); + rtt -= RRinf.GetLastSRTimestamp(); + rtt -= RRinf.GetDelaySinceLastSR(); - double drtt = (((double)rtt)/65536.0); - return RTPTime(drtt); + double drtt = (((double) rtt) / 65536.0); + return RTPTime(drtt); } } // end namespace - diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h index ede7ed7c7..bcd91a83f 100644 --- a/qrtplib/rtpsourcedata.h +++ b/qrtplib/rtpsourcedata.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpsourcedata.h @@ -44,7 +44,6 @@ #include "rtcpsdesinfo.h" #include "rtptypes.h" #include "rtpsources.h" -#include "rtpmemoryobject.h" #include namespace qrtplib @@ -55,419 +54,746 @@ class RTPAddress; class JRTPLIB_IMPORTEXPORT RTCPSenderReportInfo { public: - RTCPSenderReportInfo():ntptimestamp(0,0),receivetime(0,0) { hasinfo = false; rtptimestamp = 0; packetcount = 0; bytecount = 0; } - void Set(const RTPNTPTime &ntptime,uint32_t rtptime,uint32_t pcount, - uint32_t bcount,const RTPTime &rcvtime) { ntptimestamp = ntptime; rtptimestamp = rtptime; packetcount = pcount; bytecount = bcount; receivetime = rcvtime; hasinfo = true; } + RTCPSenderReportInfo() : + ntptimestamp(0, 0), receivetime(0, 0) + { + hasinfo = false; + rtptimestamp = 0; + packetcount = 0; + bytecount = 0; + } + void Set(const RTPNTPTime &ntptime, uint32_t rtptime, uint32_t pcount, uint32_t bcount, const RTPTime &rcvtime) + { + ntptimestamp = ntptime; + rtptimestamp = rtptime; + packetcount = pcount; + bytecount = bcount; + receivetime = rcvtime; + hasinfo = true; + } - bool HasInfo() const { return hasinfo; } - RTPNTPTime GetNTPTimestamp() const { return ntptimestamp; } - uint32_t GetRTPTimestamp() const { return rtptimestamp; } - uint32_t GetPacketCount() const { return packetcount; } - uint32_t GetByteCount() const { return bytecount; } - RTPTime GetReceiveTime() const { return receivetime; } + bool HasInfo() const + { + return hasinfo; + } + RTPNTPTime GetNTPTimestamp() const + { + return ntptimestamp; + } + uint32_t GetRTPTimestamp() const + { + return rtptimestamp; + } + uint32_t GetPacketCount() const + { + return packetcount; + } + uint32_t GetByteCount() const + { + return bytecount; + } + RTPTime GetReceiveTime() const + { + return receivetime; + } private: - bool hasinfo; - RTPNTPTime ntptimestamp; - uint32_t rtptimestamp; - uint32_t packetcount; - uint32_t bytecount; - RTPTime receivetime; + bool hasinfo; + RTPNTPTime ntptimestamp; + uint32_t rtptimestamp; + uint32_t packetcount; + uint32_t bytecount; + RTPTime receivetime; }; class JRTPLIB_IMPORTEXPORT RTCPReceiverReportInfo { public: - RTCPReceiverReportInfo():receivetime(0,0) { hasinfo = false; fractionlost = 0; packetslost = 0; exthighseqnr = 0; jitter = 0; lsr = 0; dlsr = 0; } - void Set(uint8_t fraclost,int32_t plost,uint32_t exthigh, - uint32_t jit,uint32_t l,uint32_t dl,const RTPTime &rcvtime) { fractionlost = ((double)fraclost)/256.0; packetslost = plost; exthighseqnr = exthigh; jitter = jit; lsr = l; dlsr = dl; receivetime = rcvtime; hasinfo = true; } + RTCPReceiverReportInfo() : + receivetime(0, 0) + { + hasinfo = false; + fractionlost = 0; + packetslost = 0; + exthighseqnr = 0; + jitter = 0; + lsr = 0; + dlsr = 0; + } + void Set(uint8_t fraclost, int32_t plost, uint32_t exthigh, uint32_t jit, uint32_t l, uint32_t dl, const RTPTime &rcvtime) + { + fractionlost = ((double) fraclost) / 256.0; + packetslost = plost; + exthighseqnr = exthigh; + jitter = jit; + lsr = l; + dlsr = dl; + receivetime = rcvtime; + hasinfo = true; + } - bool HasInfo() const { return hasinfo; } - double GetFractionLost() const { return fractionlost; } - int32_t GetPacketsLost() const { return packetslost; } - uint32_t GetExtendedHighestSequenceNumber() const { return exthighseqnr; } - uint32_t GetJitter() const { return jitter; } - uint32_t GetLastSRTimestamp() const { return lsr; } - uint32_t GetDelaySinceLastSR() const { return dlsr; } - RTPTime GetReceiveTime() const { return receivetime; } + bool HasInfo() const + { + return hasinfo; + } + double GetFractionLost() const + { + return fractionlost; + } + int32_t GetPacketsLost() const + { + return packetslost; + } + uint32_t GetExtendedHighestSequenceNumber() const + { + return exthighseqnr; + } + uint32_t GetJitter() const + { + return jitter; + } + uint32_t GetLastSRTimestamp() const + { + return lsr; + } + uint32_t GetDelaySinceLastSR() const + { + return dlsr; + } + RTPTime GetReceiveTime() const + { + return receivetime; + } private: - bool hasinfo; - double fractionlost; - int32_t packetslost; - uint32_t exthighseqnr; - uint32_t jitter; - uint32_t lsr; - uint32_t dlsr; - RTPTime receivetime; + bool hasinfo; + double fractionlost; + int32_t packetslost; + uint32_t exthighseqnr; + uint32_t jitter; + uint32_t lsr; + uint32_t dlsr; + RTPTime receivetime; }; class JRTPLIB_IMPORTEXPORT RTPSourceStats { public: - RTPSourceStats(); - void ProcessPacket(RTPPacket *pack,const RTPTime &receivetime,double tsunit,bool ownpacket,bool *accept,bool applyprobation,bool *onprobation); + RTPSourceStats(); + void ProcessPacket(RTPPacket *pack, const RTPTime &receivetime, double tsunit, bool ownpacket, bool *accept, bool applyprobation, bool *onprobation); - bool HasSentData() const { return sentdata; } - uint32_t GetNumPacketsReceived() const { return packetsreceived; } - uint32_t GetBaseSequenceNumber() const { return baseseqnr; } - uint32_t GetExtendedHighestSequenceNumber() const { return exthighseqnr; } - uint32_t GetJitter() const { return jitter; } + bool HasSentData() const + { + return sentdata; + } + uint32_t GetNumPacketsReceived() const + { + return packetsreceived; + } + uint32_t GetBaseSequenceNumber() const + { + return baseseqnr; + } + uint32_t GetExtendedHighestSequenceNumber() const + { + return exthighseqnr; + } + uint32_t GetJitter() const + { + return jitter; + } - int32_t GetNumPacketsReceivedInInterval() const { return numnewpackets; } - uint32_t GetSavedExtendedSequenceNumber() const { return savedextseqnr; } - void StartNewInterval() { numnewpackets = 0; savedextseqnr = exthighseqnr; } + int32_t GetNumPacketsReceivedInInterval() const + { + return numnewpackets; + } + uint32_t GetSavedExtendedSequenceNumber() const + { + return savedextseqnr; + } + void StartNewInterval() + { + numnewpackets = 0; + savedextseqnr = exthighseqnr; + } - void SetLastMessageTime(const RTPTime &t) { lastmsgtime = t; } - RTPTime GetLastMessageTime() const { return lastmsgtime; } - void SetLastRTPPacketTime(const RTPTime &t) { lastrtptime = t; } - RTPTime GetLastRTPPacketTime() const { return lastrtptime; } + void SetLastMessageTime(const RTPTime &t) + { + lastmsgtime = t; + } + RTPTime GetLastMessageTime() const + { + return lastmsgtime; + } + void SetLastRTPPacketTime(const RTPTime &t) + { + lastrtptime = t; + } + RTPTime GetLastRTPPacketTime() const + { + return lastrtptime; + } - void SetLastNoteTime(const RTPTime &t) { lastnotetime = t; } - RTPTime GetLastNoteTime() const { return lastnotetime; } + void SetLastNoteTime(const RTPTime &t) + { + lastnotetime = t; + } + RTPTime GetLastNoteTime() const + { + return lastnotetime; + } private: - bool sentdata; - uint32_t packetsreceived; - uint32_t numcycles; // shifted left 16 bits - uint32_t baseseqnr; - uint32_t exthighseqnr,prevexthighseqnr; - uint32_t jitter,prevtimestamp; - double djitter; - RTPTime prevpacktime; - RTPTime lastmsgtime; - RTPTime lastrtptime; - RTPTime lastnotetime; - uint32_t numnewpackets; - uint32_t savedextseqnr; + bool sentdata; + uint32_t packetsreceived; + uint32_t numcycles; // shifted left 16 bits + uint32_t baseseqnr; + uint32_t exthighseqnr, prevexthighseqnr; + uint32_t jitter, prevtimestamp; + double djitter; + RTPTime prevpacktime; + RTPTime lastmsgtime; + RTPTime lastrtptime; + RTPTime lastnotetime; + uint32_t numnewpackets; + uint32_t savedextseqnr; #ifdef RTP_SUPPORT_PROBATION - uint16_t prevseqnr; - int probation; + uint16_t prevseqnr; + int probation; #endif // RTP_SUPPORT_PROBATION }; -inline RTPSourceStats::RTPSourceStats():prevpacktime(0,0),lastmsgtime(0,0),lastrtptime(0,0),lastnotetime(0,0) +inline RTPSourceStats::RTPSourceStats() : + prevpacktime(0, 0), lastmsgtime(0, 0), lastrtptime(0, 0), lastnotetime(0, 0) { - sentdata = false; - packetsreceived = 0; - baseseqnr = 0; - exthighseqnr = 0; - prevexthighseqnr = 0; - jitter = 0; - numcycles = 0; - numnewpackets = 0; - prevtimestamp = 0; - djitter = 0; - savedextseqnr = 0; + sentdata = false; + packetsreceived = 0; + baseseqnr = 0; + exthighseqnr = 0; + prevexthighseqnr = 0; + jitter = 0; + numcycles = 0; + numnewpackets = 0; + prevtimestamp = 0; + djitter = 0; + savedextseqnr = 0; #ifdef RTP_SUPPORT_PROBATION - probation = 0; - prevseqnr = 0; + probation = 0; + prevseqnr = 0; #endif // RTP_SUPPORT_PROBATION } /** Describes an entry in the RTPSources source table. */ -class JRTPLIB_IMPORTEXPORT RTPSourceData : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTPSourceData { protected: - RTPSourceData(uint32_t ssrc, RTPMemoryManager *mgr = 0); - virtual ~RTPSourceData(); + RTPSourceData(uint32_t ssrc); + virtual ~RTPSourceData(); public: - /** Extracts the first packet of this participants RTP packet queue. */ - RTPPacket *GetNextPacket(); - - /** Clears the participant's RTP packet list. */ - void FlushPackets(); - - /** Returns \c true if there are RTP packets which can be extracted. */ - bool HasData() const { if (!validated) return false; return packetlist.empty()?false:true; } - - /** Returns the SSRC identifier for this member. */ - uint32_t GetSSRC() const { return ssrc; } - - /** Returns \c true if the participant was added using the RTPSources member function CreateOwnSSRC and - * returns \c false otherwise. - */ - bool IsOwnSSRC() const { return ownssrc; } - - /** Returns \c true if the source identifier is actually a CSRC from an RTP packet. */ - bool IsCSRC() const { return iscsrc; } - - /** Returns \c true if this member is marked as a sender and \c false if not. */ - bool IsSender() const { return issender; } - - /** Returns \c true if the participant is validated, which is the case if a number of - * consecutive RTP packets have been received or if a CNAME item has been received for - * this participant. - */ - bool IsValidated() const { return validated; } - - /** Returns \c true if the source was validated and had not yet sent a BYE packet. */ - bool IsActive() const { if (!validated) return false; if (receivedbye) return false; return true; } - - /** This function is used by the RTCPPacketBuilder class to mark whether this participant's - * information has been processed in a report block or not. - */ - void SetProcessedInRTCP(bool v) { processedinrtcp = v; } - - /** This function is used by the RTCPPacketBuilder class and returns whether this participant - * has been processed in a report block or not. - */ - bool IsProcessedInRTCP() const { return processedinrtcp; } - - /** Returns \c true if the address from which this participant's RTP packets originate has - * already been set. - */ - bool IsRTPAddressSet() const { return isrtpaddrset; } - - /** Returns \c true if the address from which this participant's RTCP packets originate has - * already been set. - */ - bool IsRTCPAddressSet() const { return isrtcpaddrset; } - - /** Returns the address from which this participant's RTP packets originate. - * Returns the address from which this participant's RTP packets originate. If the address has - * been set and the returned value is NULL, this indicates that it originated from the local - * participant. - */ - const RTPAddress *GetRTPDataAddress() const { return rtpaddr; } - - /** Returns the address from which this participant's RTCP packets originate. - * Returns the address from which this participant's RTCP packets originate. If the address has - * been set and the returned value is NULL, this indicates that it originated from the local - * participant. - */ - const RTPAddress *GetRTCPDataAddress() const { return rtcpaddr; } - - /** Returns \c true if we received a BYE message for this participant and \c false otherwise. */ - bool ReceivedBYE() const { return receivedbye; } - - /** Returns the reason for leaving contained in the BYE packet of this participant. - * Returns the reason for leaving contained in the BYE packet of this participant. The length of - * the reason is stored in \c len. - */ - uint8_t *GetBYEReason(size_t *len) const { *len = byereasonlen; return byereason; } - - /** Returns the time at which the BYE packet was received. */ - RTPTime GetBYETime() const { return byetime; } - - /** Sets the value for the timestamp unit to be used in jitter calculations for data received from this participant. - * Sets the value for the timestamp unit to be used in jitter calculations for data received from this participant. - * If not set, the library uses an approximation for the timestamp unit which is calculated from two consecutive - * RTCP sender reports. The timestamp unit is defined as a time interval divided by the corresponding timestamp - * interval. For 8000 Hz audio this would be 1/8000. For video, often a timestamp unit of 1/90000 is used. - */ - void SetTimestampUnit(double tsu) { timestampunit = tsu; } - - /** Returns the timestamp unit used for this participant. */ - double GetTimestampUnit() const { return timestampunit; } - - /** Returns \c true if an RTCP sender report has been received from this participant. */ - bool SR_HasInfo() const { return SRinf.HasInfo(); } - - /** Returns the NTP timestamp contained in the last sender report. */ - RTPNTPTime SR_GetNTPTimestamp() const { return SRinf.GetNTPTimestamp(); } - - /** Returns the RTP timestamp contained in the last sender report. */ - uint32_t SR_GetRTPTimestamp() const { return SRinf.GetRTPTimestamp(); } - - /** Returns the packet count contained in the last sender report. */ - uint32_t SR_GetPacketCount() const { return SRinf.GetPacketCount(); } - - /** Returns the octet count contained in the last sender report. */ - uint32_t SR_GetByteCount() const { return SRinf.GetByteCount(); } - - /** Returns the time at which the last sender report was received. */ - RTPTime SR_GetReceiveTime() const { return SRinf.GetReceiveTime(); } - - /** Returns \c true if more than one RTCP sender report has been received. */ - bool SR_Prev_HasInfo() const { return SRprevinf.HasInfo(); } - - /** Returns the NTP timestamp contained in the second to last sender report. */ - RTPNTPTime SR_Prev_GetNTPTimestamp() const { return SRprevinf.GetNTPTimestamp(); } - - /** Returns the RTP timestamp contained in the second to last sender report. */ - uint32_t SR_Prev_GetRTPTimestamp() const { return SRprevinf.GetRTPTimestamp(); } - - /** Returns the packet count contained in the second to last sender report. */ - uint32_t SR_Prev_GetPacketCount() const { return SRprevinf.GetPacketCount(); } - - /** Returns the octet count contained in the second to last sender report. */ - uint32_t SR_Prev_GetByteCount() const { return SRprevinf.GetByteCount(); } - - /** Returns the time at which the second to last sender report was received. */ - RTPTime SR_Prev_GetReceiveTime() const { return SRprevinf.GetReceiveTime(); } - - /** Returns \c true if this participant sent a receiver report with information about the reception of our data. */ - bool RR_HasInfo() const { return RRinf.HasInfo(); } - - /** Returns the fraction lost value from the last report. */ - double RR_GetFractionLost() const { return RRinf.GetFractionLost(); } - - /** Returns the number of lost packets contained in the last report. */ - int32_t RR_GetPacketsLost() const { return RRinf.GetPacketsLost(); } - - /** Returns the extended highest sequence number contained in the last report. */ - uint32_t RR_GetExtendedHighestSequenceNumber() const { return RRinf.GetExtendedHighestSequenceNumber(); } - - /** Returns the jitter value from the last report. */ - uint32_t RR_GetJitter() const { return RRinf.GetJitter(); } - - /** Returns the LSR value from the last report. */ - uint32_t RR_GetLastSRTimestamp() const { return RRinf.GetLastSRTimestamp(); } - - /** Returns the DLSR value from the last report. */ - uint32_t RR_GetDelaySinceLastSR() const { return RRinf.GetDelaySinceLastSR(); } - - /** Returns the time at which the last report was received. */ - RTPTime RR_GetReceiveTime() const { return RRinf.GetReceiveTime(); } - - /** Returns \c true if this participant sent more than one receiver report with information - * about the reception of our data. - */ - bool RR_Prev_HasInfo() const { return RRprevinf.HasInfo(); } - - /** Returns the fraction lost value from the second to last report. */ - double RR_Prev_GetFractionLost() const { return RRprevinf.GetFractionLost(); } - - /** Returns the number of lost packets contained in the second to last report. */ - int32_t RR_Prev_GetPacketsLost() const { return RRprevinf.GetPacketsLost(); } - - /** Returns the extended highest sequence number contained in the second to last report. */ - uint32_t RR_Prev_GetExtendedHighestSequenceNumber() const { return RRprevinf.GetExtendedHighestSequenceNumber(); } - - /** Returns the jitter value from the second to last report. */ - uint32_t RR_Prev_GetJitter() const { return RRprevinf.GetJitter(); } - - /** Returns the LSR value from the second to last report. */ - uint32_t RR_Prev_GetLastSRTimestamp() const { return RRprevinf.GetLastSRTimestamp(); } - - /** Returns the DLSR value from the second to last report. */ - uint32_t RR_Prev_GetDelaySinceLastSR() const { return RRprevinf.GetDelaySinceLastSR(); } - - /** Returns the time at which the second to last report was received. */ - RTPTime RR_Prev_GetReceiveTime() const { return RRprevinf.GetReceiveTime(); } - - /** Returns \c true if validated RTP packets have been received from this participant. */ - bool INF_HasSentData() const { return stats.HasSentData(); } - - /** Returns the total number of received packets from this participant. */ - int32_t INF_GetNumPacketsReceived() const { return stats.GetNumPacketsReceived(); } - - /** Returns the base sequence number of this participant. */ - uint32_t INF_GetBaseSequenceNumber() const { return stats.GetBaseSequenceNumber(); } - - /** Returns the extended highest sequence number received from this participant. */ - uint32_t INF_GetExtendedHighestSequenceNumber() const { return stats.GetExtendedHighestSequenceNumber(); } - - /** Returns the current jitter value for this participant. */ - uint32_t INF_GetJitter() const { return stats.GetJitter(); } - - /** Returns the time at which something was last heard from this member. */ - RTPTime INF_GetLastMessageTime() const { return stats.GetLastMessageTime(); } - - /** Returns the time at which the last RTP packet was received. */ - RTPTime INF_GetLastRTPPacketTime() const { return stats.GetLastRTPPacketTime(); } - - /** Returns the estimated timestamp unit, calculated from two consecutive sender reports. */ - double INF_GetEstimatedTimestampUnit() const; - - /** Returns the number of packets received since a new interval was started with INF_StartNewInterval. */ - uint32_t INF_GetNumPacketsReceivedInInterval() const { return stats.GetNumPacketsReceivedInInterval(); } - - /** Returns the extended sequence number which was stored by the INF_StartNewInterval call. */ - uint32_t INF_GetSavedExtendedSequenceNumber() const { return stats.GetSavedExtendedSequenceNumber(); } - - /** Starts a new interval to count received packets in; this also stores the current extended highest sequence - * number to be able to calculate the packet loss during the interval. - */ - void INF_StartNewInterval() { stats.StartNewInterval(); } - - /** Estimates the round trip time by using the LSR and DLSR info from the last receiver report. */ - RTPTime INF_GetRoundtripTime() const; - - /** Returns the time at which the last SDES NOTE item was received. */ - RTPTime INF_GetLastSDESNoteTime() const { return stats.GetLastNoteTime(); } - - /** Returns a pointer to the SDES CNAME item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetCNAME(size_t *len) const { return SDESinf.GetCNAME(len); } - - /** Returns a pointer to the SDES name item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetName(size_t *len) const { return SDESinf.GetName(len); } - - /** Returns a pointer to the SDES e-mail item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetEMail(size_t *len) const { return SDESinf.GetEMail(len); } - - /** Returns a pointer to the SDES phone item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetPhone(size_t *len) const { return SDESinf.GetPhone(len); } - - /** Returns a pointer to the SDES location item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetLocation(size_t *len) const { return SDESinf.GetLocation(len); } - - /** Returns a pointer to the SDES tool item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetTool(size_t *len) const { return SDESinf.GetTool(len); } - - /** Returns a pointer to the SDES note item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetNote(size_t *len) const { return SDESinf.GetNote(len); } + /** Extracts the first packet of this participants RTP packet queue. */ + RTPPacket *GetNextPacket(); + + /** Clears the participant's RTP packet list. */ + void FlushPackets(); + + /** Returns \c true if there are RTP packets which can be extracted. */ + bool HasData() const + { + if (!validated) + return false; + return packetlist.empty() ? false : true; + } + + /** Returns the SSRC identifier for this member. */ + uint32_t GetSSRC() const + { + return ssrc; + } + + /** Returns \c true if the participant was added using the RTPSources member function CreateOwnSSRC and + * returns \c false otherwise. + */ + bool IsOwnSSRC() const + { + return ownssrc; + } + + /** Returns \c true if the source identifier is actually a CSRC from an RTP packet. */ + bool IsCSRC() const + { + return iscsrc; + } + + /** Returns \c true if this member is marked as a sender and \c false if not. */ + bool IsSender() const + { + return issender; + } + + /** Returns \c true if the participant is validated, which is the case if a number of + * consecutive RTP packets have been received or if a CNAME item has been received for + * this participant. + */ + bool IsValidated() const + { + return validated; + } + + /** Returns \c true if the source was validated and had not yet sent a BYE packet. */ + bool IsActive() const + { + if (!validated) + return false; + if (receivedbye) + return false; + return true; + } + + /** This function is used by the RTCPPacketBuilder class to mark whether this participant's + * information has been processed in a report block or not. + */ + void SetProcessedInRTCP(bool v) + { + processedinrtcp = v; + } + + /** This function is used by the RTCPPacketBuilder class and returns whether this participant + * has been processed in a report block or not. + */ + bool IsProcessedInRTCP() const + { + return processedinrtcp; + } + + /** Returns \c true if the address from which this participant's RTP packets originate has + * already been set. + */ + bool IsRTPAddressSet() const + { + return isrtpaddrset; + } + + /** Returns \c true if the address from which this participant's RTCP packets originate has + * already been set. + */ + bool IsRTCPAddressSet() const + { + return isrtcpaddrset; + } + + /** Returns the address from which this participant's RTP packets originate. + * Returns the address from which this participant's RTP packets originate. If the address has + * been set and the returned value is NULL, this indicates that it originated from the local + * participant. + */ + const RTPAddress *GetRTPDataAddress() const + { + return rtpaddr; + } + + /** Returns the address from which this participant's RTCP packets originate. + * Returns the address from which this participant's RTCP packets originate. If the address has + * been set and the returned value is NULL, this indicates that it originated from the local + * participant. + */ + const RTPAddress *GetRTCPDataAddress() const + { + return rtcpaddr; + } + + /** Returns \c true if we received a BYE message for this participant and \c false otherwise. */ + bool ReceivedBYE() const + { + return receivedbye; + } + + /** Returns the reason for leaving contained in the BYE packet of this participant. + * Returns the reason for leaving contained in the BYE packet of this participant. The length of + * the reason is stored in \c len. + */ + uint8_t *GetBYEReason(size_t *len) const + { + *len = byereasonlen; + return byereason; + } + + /** Returns the time at which the BYE packet was received. */ + RTPTime GetBYETime() const + { + return byetime; + } + + /** Sets the value for the timestamp unit to be used in jitter calculations for data received from this participant. + * Sets the value for the timestamp unit to be used in jitter calculations for data received from this participant. + * If not set, the library uses an approximation for the timestamp unit which is calculated from two consecutive + * RTCP sender reports. The timestamp unit is defined as a time interval divided by the corresponding timestamp + * interval. For 8000 Hz audio this would be 1/8000. For video, often a timestamp unit of 1/90000 is used. + */ + void SetTimestampUnit(double tsu) + { + timestampunit = tsu; + } + + /** Returns the timestamp unit used for this participant. */ + double GetTimestampUnit() const + { + return timestampunit; + } + + /** Returns \c true if an RTCP sender report has been received from this participant. */ + bool SR_HasInfo() const + { + return SRinf.HasInfo(); + } + + /** Returns the NTP timestamp contained in the last sender report. */ + RTPNTPTime SR_GetNTPTimestamp() const + { + return SRinf.GetNTPTimestamp(); + } + + /** Returns the RTP timestamp contained in the last sender report. */ + uint32_t SR_GetRTPTimestamp() const + { + return SRinf.GetRTPTimestamp(); + } + + /** Returns the packet count contained in the last sender report. */ + uint32_t SR_GetPacketCount() const + { + return SRinf.GetPacketCount(); + } + + /** Returns the octet count contained in the last sender report. */ + uint32_t SR_GetByteCount() const + { + return SRinf.GetByteCount(); + } + + /** Returns the time at which the last sender report was received. */ + RTPTime SR_GetReceiveTime() const + { + return SRinf.GetReceiveTime(); + } + + /** Returns \c true if more than one RTCP sender report has been received. */ + bool SR_Prev_HasInfo() const + { + return SRprevinf.HasInfo(); + } + + /** Returns the NTP timestamp contained in the second to last sender report. */ + RTPNTPTime SR_Prev_GetNTPTimestamp() const + { + return SRprevinf.GetNTPTimestamp(); + } + + /** Returns the RTP timestamp contained in the second to last sender report. */ + uint32_t SR_Prev_GetRTPTimestamp() const + { + return SRprevinf.GetRTPTimestamp(); + } + + /** Returns the packet count contained in the second to last sender report. */ + uint32_t SR_Prev_GetPacketCount() const + { + return SRprevinf.GetPacketCount(); + } + + /** Returns the octet count contained in the second to last sender report. */ + uint32_t SR_Prev_GetByteCount() const + { + return SRprevinf.GetByteCount(); + } + + /** Returns the time at which the second to last sender report was received. */ + RTPTime SR_Prev_GetReceiveTime() const + { + return SRprevinf.GetReceiveTime(); + } + + /** Returns \c true if this participant sent a receiver report with information about the reception of our data. */ + bool RR_HasInfo() const + { + return RRinf.HasInfo(); + } + + /** Returns the fraction lost value from the last report. */ + double RR_GetFractionLost() const + { + return RRinf.GetFractionLost(); + } + + /** Returns the number of lost packets contained in the last report. */ + int32_t RR_GetPacketsLost() const + { + return RRinf.GetPacketsLost(); + } + + /** Returns the extended highest sequence number contained in the last report. */ + uint32_t RR_GetExtendedHighestSequenceNumber() const + { + return RRinf.GetExtendedHighestSequenceNumber(); + } + + /** Returns the jitter value from the last report. */ + uint32_t RR_GetJitter() const + { + return RRinf.GetJitter(); + } + + /** Returns the LSR value from the last report. */ + uint32_t RR_GetLastSRTimestamp() const + { + return RRinf.GetLastSRTimestamp(); + } + + /** Returns the DLSR value from the last report. */ + uint32_t RR_GetDelaySinceLastSR() const + { + return RRinf.GetDelaySinceLastSR(); + } + + /** Returns the time at which the last report was received. */ + RTPTime RR_GetReceiveTime() const + { + return RRinf.GetReceiveTime(); + } + + /** Returns \c true if this participant sent more than one receiver report with information + * about the reception of our data. + */ + bool RR_Prev_HasInfo() const + { + return RRprevinf.HasInfo(); + } + + /** Returns the fraction lost value from the second to last report. */ + double RR_Prev_GetFractionLost() const + { + return RRprevinf.GetFractionLost(); + } + + /** Returns the number of lost packets contained in the second to last report. */ + int32_t RR_Prev_GetPacketsLost() const + { + return RRprevinf.GetPacketsLost(); + } + + /** Returns the extended highest sequence number contained in the second to last report. */ + uint32_t RR_Prev_GetExtendedHighestSequenceNumber() const + { + return RRprevinf.GetExtendedHighestSequenceNumber(); + } + + /** Returns the jitter value from the second to last report. */ + uint32_t RR_Prev_GetJitter() const + { + return RRprevinf.GetJitter(); + } + + /** Returns the LSR value from the second to last report. */ + uint32_t RR_Prev_GetLastSRTimestamp() const + { + return RRprevinf.GetLastSRTimestamp(); + } + + /** Returns the DLSR value from the second to last report. */ + uint32_t RR_Prev_GetDelaySinceLastSR() const + { + return RRprevinf.GetDelaySinceLastSR(); + } + + /** Returns the time at which the second to last report was received. */ + RTPTime RR_Prev_GetReceiveTime() const + { + return RRprevinf.GetReceiveTime(); + } + + /** Returns \c true if validated RTP packets have been received from this participant. */ + bool INF_HasSentData() const + { + return stats.HasSentData(); + } + + /** Returns the total number of received packets from this participant. */ + int32_t INF_GetNumPacketsReceived() const + { + return stats.GetNumPacketsReceived(); + } + + /** Returns the base sequence number of this participant. */ + uint32_t INF_GetBaseSequenceNumber() const + { + return stats.GetBaseSequenceNumber(); + } + + /** Returns the extended highest sequence number received from this participant. */ + uint32_t INF_GetExtendedHighestSequenceNumber() const + { + return stats.GetExtendedHighestSequenceNumber(); + } + + /** Returns the current jitter value for this participant. */ + uint32_t INF_GetJitter() const + { + return stats.GetJitter(); + } + + /** Returns the time at which something was last heard from this member. */ + RTPTime INF_GetLastMessageTime() const + { + return stats.GetLastMessageTime(); + } + + /** Returns the time at which the last RTP packet was received. */ + RTPTime INF_GetLastRTPPacketTime() const + { + return stats.GetLastRTPPacketTime(); + } + + /** Returns the estimated timestamp unit, calculated from two consecutive sender reports. */ + double INF_GetEstimatedTimestampUnit() const; + + /** Returns the number of packets received since a new interval was started with INF_StartNewInterval. */ + uint32_t INF_GetNumPacketsReceivedInInterval() const + { + return stats.GetNumPacketsReceivedInInterval(); + } + + /** Returns the extended sequence number which was stored by the INF_StartNewInterval call. */ + uint32_t INF_GetSavedExtendedSequenceNumber() const + { + return stats.GetSavedExtendedSequenceNumber(); + } + + /** Starts a new interval to count received packets in; this also stores the current extended highest sequence + * number to be able to calculate the packet loss during the interval. + */ + void INF_StartNewInterval() + { + stats.StartNewInterval(); + } + + /** Estimates the round trip time by using the LSR and DLSR info from the last receiver report. */ + RTPTime INF_GetRoundtripTime() const; + + /** Returns the time at which the last SDES NOTE item was received. */ + RTPTime INF_GetLastSDESNoteTime() const + { + return stats.GetLastNoteTime(); + } + + /** Returns a pointer to the SDES CNAME item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetCNAME(size_t *len) const + { + return SDESinf.GetCNAME(len); + } + + /** Returns a pointer to the SDES name item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetName(size_t *len) const + { + return SDESinf.GetName(len); + } + + /** Returns a pointer to the SDES e-mail item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetEMail(size_t *len) const + { + return SDESinf.GetEMail(len); + } + + /** Returns a pointer to the SDES phone item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetPhone(size_t *len) const + { + return SDESinf.GetPhone(len); + } + + /** Returns a pointer to the SDES location item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetLocation(size_t *len) const + { + return SDESinf.GetLocation(len); + } + + /** Returns a pointer to the SDES tool item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetTool(size_t *len) const + { + return SDESinf.GetTool(len); + } + + /** Returns a pointer to the SDES note item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetNote(size_t *len) const + { + return SDESinf.GetNote(len); + } #ifdef RTP_SUPPORT_SDESPRIV - /** Starts the iteration over the stored SDES private item prefixes and their associated values. */ - void SDES_GotoFirstPrivateValue() { SDESinf.GotoFirstPrivateValue(); } + /** Starts the iteration over the stored SDES private item prefixes and their associated values. */ + void SDES_GotoFirstPrivateValue() + { + SDESinf.GotoFirstPrivateValue(); + } - /** If available, returns \c true and stores the next SDES private item prefix in \c prefix and its length in - * \c prefixlen; the associated value and its length are then stored in \c value and \c valuelen. - */ - bool SDES_GetNextPrivateValue(uint8_t **prefix,size_t *prefixlen,uint8_t **value,size_t *valuelen) { return SDESinf.GetNextPrivateValue(prefix,prefixlen,value,valuelen); } + /** If available, returns \c true and stores the next SDES private item prefix in \c prefix and its length in + * \c prefixlen; the associated value and its length are then stored in \c value and \c valuelen. + */ + bool SDES_GetNextPrivateValue(uint8_t **prefix, size_t *prefixlen, uint8_t **value, size_t *valuelen) + { + return SDESinf.GetNextPrivateValue(prefix, prefixlen, value, valuelen); + } - /** Looks for the entry which corresponds to the SDES private item prefix \c prefix with length - * \c prefixlen; if found, the function returns \c true and stores the associated value and - * its length in \c value and \c valuelen respectively. - */ - bool SDES_GetPrivateValue(uint8_t *prefix,size_t prefixlen,uint8_t **value,size_t *valuelen) const { return SDESinf.GetPrivateValue(prefix,prefixlen,value,valuelen); } + /** Looks for the entry which corresponds to the SDES private item prefix \c prefix with length + * \c prefixlen; if found, the function returns \c true and stores the associated value and + * its length in \c value and \c valuelen respectively. + */ + bool SDES_GetPrivateValue(uint8_t *prefix, size_t prefixlen, uint8_t **value, size_t *valuelen) const + { + return SDESinf.GetPrivateValue(prefix, prefixlen, value, valuelen); + } #endif // RTP_SUPPORT_SDESPRIV protected: - std::list packetlist; + std::list packetlist; - uint32_t ssrc; - bool ownssrc; - bool iscsrc; - double timestampunit; - bool receivedbye; - bool validated; - bool processedinrtcp; - bool issender; + uint32_t ssrc; + bool ownssrc; + bool iscsrc; + double timestampunit; + bool receivedbye; + bool validated; + bool processedinrtcp; + bool issender; - RTCPSenderReportInfo SRinf,SRprevinf; - RTCPReceiverReportInfo RRinf,RRprevinf; - RTPSourceStats stats; - RTCPSDESInfo SDESinf; + RTCPSenderReportInfo SRinf, SRprevinf; + RTCPReceiverReportInfo RRinf, RRprevinf; + RTPSourceStats stats; + RTCPSDESInfo SDESinf; - bool isrtpaddrset,isrtcpaddrset; - RTPAddress *rtpaddr,*rtcpaddr; + bool isrtpaddrset, isrtcpaddrset; + RTPAddress *rtpaddr, *rtcpaddr; - RTPTime byetime; - uint8_t *byereason; - size_t byereasonlen; + RTPTime byetime; + uint8_t *byereason; + size_t byereasonlen; }; inline RTPPacket *RTPSourceData::GetNextPacket() { - if (!validated) - return 0; + if (!validated) + return 0; - RTPPacket *p; + RTPPacket *p; - if (packetlist.empty()) - return 0; - p = *(packetlist.begin()); - packetlist.pop_front(); - return p; + if (packetlist.empty()) + return 0; + p = *(packetlist.begin()); + packetlist.pop_front(); + return p; } inline void RTPSourceData::FlushPackets() { - std::list::const_iterator it; + std::list::const_iterator it; - for (it = packetlist.begin() ; it != packetlist.end() ; ++it) - RTPDelete(*it,GetMemoryManager()); - packetlist.clear(); + for (it = packetlist.begin(); it != packetlist.end(); ++it) + delete *it; + packetlist.clear(); } } // end namespace diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp index 89dab14a2..f5ea3c504 100644 --- a/qrtplib/rtpsources.cpp +++ b/qrtplib/rtpsources.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpsources.h" #include "rtperrors.h" @@ -48,1229 +48,1219 @@ namespace qrtplib { -RTPSources::RTPSources(ProbationType probtype,RTPMemoryManager *mgr) : RTPMemoryObject(mgr),sourcelist(mgr,RTPMEM_TYPE_CLASS_SOURCETABLEHASHELEMENT) +RTPSources::RTPSources(ProbationType probtype) { - JRTPLIB_UNUSED(probtype); // possibly unused + JRTPLIB_UNUSED(probtype); // possibly unused - totalcount = 0; - sendercount = 0; - activecount = 0; - owndata = 0; + totalcount = 0; + sendercount = 0; + activecount = 0; + owndata = 0; #ifdef RTP_SUPPORT_PROBATION - probationtype = probtype; + probationtype = probtype; #endif // RTP_SUPPORT_PROBATION } RTPSources::~RTPSources() { - Clear(); + Clear(); } void RTPSources::Clear() { - ClearSourceList(); + ClearSourceList(); } void RTPSources::ClearSourceList() { - sourcelist.GotoFirstElement(); - while (sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *sourcedata; + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *sourcedata; - sourcedata = sourcelist.GetCurrentElement(); - RTPDelete(sourcedata,GetMemoryManager()); - sourcelist.GotoNextElement(); - } - sourcelist.Clear(); - owndata = 0; - totalcount = 0; - sendercount = 0; - activecount = 0; + sourcedata = sourcelist.GetCurrentElement(); + delete sourcedata; + sourcelist.GotoNextElement(); + } + sourcelist.Clear(); + owndata = 0; + totalcount = 0; + sendercount = 0; + activecount = 0; } int RTPSources::CreateOwnSSRC(uint32_t ssrc) { - if (owndata != 0) - return ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC; - if (GotEntry(ssrc)) - return ERR_RTP_SOURCES_SSRCEXISTS; + if (owndata != 0) + return ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC; + if (GotEntry(ssrc)) + return ERR_RTP_SOURCES_SSRCEXISTS; - int status; - bool created; + int status; + bool created; - status = ObtainSourceDataInstance(ssrc,&owndata,&created); - if (status < 0) - { - owndata = 0; // just to make sure - return status; - } - owndata->SetOwnSSRC(); - owndata->SetRTPDataAddress(0); - owndata->SetRTCPDataAddress(0); + status = ObtainSourceDataInstance(ssrc, &owndata, &created); + if (status < 0) + { + owndata = 0; // just to make sure + return status; + } + owndata->SetOwnSSRC(); + owndata->SetRTPDataAddress(0); + owndata->SetRTCPDataAddress(0); - // we've created a validated ssrc, so we should increase activecount - activecount++; + // we've created a validated ssrc, so we should increase activecount + activecount++; - OnNewSource(owndata); - return 0; + OnNewSource(owndata); + return 0; } int RTPSources::DeleteOwnSSRC() { - if (owndata == 0) - return ERR_RTP_SOURCES_DONTHAVEOWNSSRC; + if (owndata == 0) + return ERR_RTP_SOURCES_DONTHAVEOWNSSRC; - uint32_t ssrc = owndata->GetSSRC(); + uint32_t ssrc = owndata->GetSSRC(); - sourcelist.GotoElement(ssrc); - sourcelist.DeleteCurrentElement(); + sourcelist.GotoElement(ssrc); + sourcelist.DeleteCurrentElement(); - totalcount--; - if (owndata->IsSender()) - sendercount--; - if (owndata->IsActive()) - activecount--; + totalcount--; + if (owndata->IsSender()) + sendercount--; + if (owndata->IsActive()) + activecount--; - OnRemoveSource(owndata); + OnRemoveSource(owndata); - RTPDelete(owndata,GetMemoryManager()); - owndata = 0; - return 0; + delete owndata; + owndata = 0; + return 0; } void RTPSources::SentRTPPacket() { - if (owndata == 0) - return; + if (owndata == 0) + return; - bool prevsender = owndata->IsSender(); + bool prevsender = owndata->IsSender(); - owndata->SentRTPPacket(); - if (!prevsender && owndata->IsSender()) - sendercount++; + owndata->SentRTPPacket(); + if (!prevsender && owndata->IsSender()) + sendercount++; } -int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *rtptrans,bool acceptownpackets) +int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans, bool acceptownpackets) { - RTPTransmitter *transmitters[1]; - int num; + RTPTransmitter *transmitters[1]; + int num; - transmitters[0] = rtptrans; - if (rtptrans == 0) - num = 0; - else - num = 1; - return ProcessRawPacket(rawpack,transmitters,num,acceptownpackets); + transmitters[0] = rtptrans; + if (rtptrans == 0) + num = 0; + else + num = 1; + return ProcessRawPacket(rawpack, transmitters, num, acceptownpackets); } -int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *rtptrans[],int numtrans,bool acceptownpackets) +int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans[], int numtrans, bool acceptownpackets) { - int status; + int status; - if (rawpack->IsRTP()) // RTP packet - { - RTPPacket *rtppack; + if (rawpack->IsRTP()) // RTP packet + { + RTPPacket *rtppack; - // First, we'll see if the packet can be parsed - rtppack = new RTPPacket(*rawpack,GetMemoryManager()); - if (rtppack == 0) - return ERR_RTP_OUTOFMEM; - if ((status = rtppack->GetCreationError()) < 0) - { - if (status == ERR_RTP_PACKET_INVALIDPACKET) - { - RTPDelete(rtppack,GetMemoryManager()); - rtppack = 0; - } - else - { - RTPDelete(rtppack,GetMemoryManager()); - return status; - } - } + // First, we'll see if the packet can be parsed + rtppack = new RTPPacket(*rawpack); + if (rtppack == 0) + return ERR_RTP_OUTOFMEM; + if ((status = rtppack->GetCreationError()) < 0) + { + if (status == ERR_RTP_PACKET_INVALIDPACKET) + { + delete rtppack; + rtppack = 0; + } + else + { + delete rtppack; + return status; + } + } - // Check if the packet was valid - if (rtppack != 0) - { - bool stored = false; - bool ownpacket = false; - int i; - const RTPAddress *senderaddress = rawpack->GetSenderAddress(); + // Check if the packet was valid + if (rtppack != 0) + { + bool stored = false; + bool ownpacket = false; + int i; + const RTPAddress *senderaddress = rawpack->GetSenderAddress(); - for (i = 0 ; !ownpacket && i < numtrans ; i++) - { - if (rtptrans[i]->ComesFromThisTransmitter(senderaddress)) - ownpacket = true; - } + for (i = 0; !ownpacket && i < numtrans; i++) + { + if (rtptrans[i]->ComesFromThisTransmitter(senderaddress)) + ownpacket = true; + } - // Check if the packet is our own. - if (ownpacket) - { - // Now it depends on the user's preference - // what to do with this packet: - if (acceptownpackets) - { - // sender addres for own packets has to be NULL! - if ((status = ProcessRTPPacket(rtppack,rawpack->GetReceiveTime(),0,&stored)) < 0) - { - if (!stored) - RTPDelete(rtppack,GetMemoryManager()); - return status; - } - } - } - else - { - if ((status = ProcessRTPPacket(rtppack,rawpack->GetReceiveTime(),senderaddress,&stored)) < 0) - { - if (!stored) - RTPDelete(rtppack,GetMemoryManager()); - return status; - } - } - if (!stored) - RTPDelete(rtppack,GetMemoryManager()); - } - } - else // RTCP packet - { - RTCPCompoundPacket rtcpcomppack(*rawpack,GetMemoryManager()); - bool valid = false; + // Check if the packet is our own. + if (ownpacket) + { + // Now it depends on the user's preference + // what to do with this packet: + if (acceptownpackets) + { + // sender addres for own packets has to be NULL! + if ((status = ProcessRTPPacket(rtppack, rawpack->GetReceiveTime(), 0, &stored)) < 0) + { + if (!stored) + delete rtppack; + return status; + } + } + } + else + { + if ((status = ProcessRTPPacket(rtppack, rawpack->GetReceiveTime(), senderaddress, &stored)) < 0) + { + if (!stored) + delete rtppack; + return status; + } + } + if (!stored) + delete rtppack; + } + } + else // RTCP packet + { + RTCPCompoundPacket rtcpcomppack(*rawpack); + bool valid = false; - if ((status = rtcpcomppack.GetCreationError()) < 0) - { - if (status != ERR_RTP_RTCPCOMPOUND_INVALIDPACKET) - return status; - } - else - valid = true; + if ((status = rtcpcomppack.GetCreationError()) < 0) + { + if (status != ERR_RTP_RTCPCOMPOUND_INVALIDPACKET) + return status; + } + else + valid = true; + if (valid) + { + bool ownpacket = false; + int i; + const RTPAddress *senderaddress = rawpack->GetSenderAddress(); - if (valid) - { - bool ownpacket = false; - int i; - const RTPAddress *senderaddress = rawpack->GetSenderAddress(); + for (i = 0; !ownpacket && i < numtrans; i++) + { + if (rtptrans[i]->ComesFromThisTransmitter(senderaddress)) + ownpacket = true; + } - for (i = 0 ; !ownpacket && i < numtrans ; i++) - { - if (rtptrans[i]->ComesFromThisTransmitter(senderaddress)) - ownpacket = true; - } + // First check if it's a packet of this session. + if (ownpacket) + { + if (acceptownpackets) + { + // sender address for own packets has to be NULL + status = ProcessRTCPCompoundPacket(&rtcpcomppack, rawpack->GetReceiveTime(), 0); + if (status < 0) + return status; + } + } + else // not our own packet + { + status = ProcessRTCPCompoundPacket(&rtcpcomppack, rawpack->GetReceiveTime(), rawpack->GetSenderAddress()); + if (status < 0) + return status; + } + } + } - // First check if it's a packet of this session. - if (ownpacket) - { - if (acceptownpackets) - { - // sender address for own packets has to be NULL - status = ProcessRTCPCompoundPacket(&rtcpcomppack,rawpack->GetReceiveTime(),0); - if (status < 0) - return status; - } - } - else // not our own packet - { - status = ProcessRTCPCompoundPacket(&rtcpcomppack,rawpack->GetReceiveTime(),rawpack->GetSenderAddress()); - if (status < 0) - return status; - } - } - } - - return 0; + return 0; } -int RTPSources::ProcessRTPPacket(RTPPacket *rtppack,const RTPTime &receivetime,const RTPAddress *senderaddress,bool *stored) +int RTPSources::ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, const RTPAddress *senderaddress, bool *stored) { - uint32_t ssrc; - RTPInternalSourceData *srcdat; - int status; - bool created; + uint32_t ssrc; + RTPInternalSourceData *srcdat; + int status; + bool created; - OnRTPPacket(rtppack,receivetime,senderaddress); + OnRTPPacket(rtppack, receivetime, senderaddress); - *stored = false; + *stored = false; - ssrc = rtppack->GetSSRC(); - if ((status = ObtainSourceDataInstance(ssrc,&srcdat,&created)) < 0) - return status; + ssrc = rtppack->GetSSRC(); + if ((status = ObtainSourceDataInstance(ssrc, &srcdat, &created)) < 0) + return status; - if (created) - { - if ((status = srcdat->SetRTPDataAddress(senderaddress)) < 0) - return status; - } - else // got a previously existing source - { - if (CheckCollision(srcdat,senderaddress,true)) - return 0; // ignore packet on collision - } + if (created) + { + if ((status = srcdat->SetRTPDataAddress(senderaddress)) < 0) + return status; + } + else // got a previously existing source + { + if (CheckCollision(srcdat, senderaddress, true)) + return 0; // ignore packet on collision + } - bool prevsender = srcdat->IsSender(); - bool prevactive = srcdat->IsActive(); + bool prevsender = srcdat->IsSender(); + bool prevactive = srcdat->IsActive(); - uint32_t CSRCs[RTP_MAXCSRCS]; - int numCSRCs = rtppack->GetCSRCCount(); - if (numCSRCs > RTP_MAXCSRCS) // shouldn't happen, but better to check than go out of bounds - numCSRCs = RTP_MAXCSRCS; + uint32_t CSRCs[RTP_MAXCSRCS]; + int numCSRCs = rtppack->GetCSRCCount(); + if (numCSRCs > RTP_MAXCSRCS) // shouldn't happen, but better to check than go out of bounds + numCSRCs = RTP_MAXCSRCS; - for (int i = 0 ; i < numCSRCs ; i++) - CSRCs[i] = rtppack->GetCSRC(i); + for (int i = 0; i < numCSRCs; i++) + CSRCs[i] = rtppack->GetCSRC(i); - // The packet comes from a valid source, we can process it further now - // The following function should delete rtppack itself if something goes - // wrong - if ((status = srcdat->ProcessRTPPacket(rtppack,receivetime,stored,this)) < 0) - return status; + // The packet comes from a valid source, we can process it further now + // The following function should delete rtppack itself if something goes + // wrong + if ((status = srcdat->ProcessRTPPacket(rtppack, receivetime, stored, this)) < 0) + return status; - // NOTE: we cannot use 'rtppack' anymore since it may have been deleted in - // OnValidatedRTPPacket + // NOTE: we cannot use 'rtppack' anymore since it may have been deleted in + // OnValidatedRTPPacket - if (!prevsender && srcdat->IsSender()) - sendercount++; - if (!prevactive && srcdat->IsActive()) - activecount++; + if (!prevsender && srcdat->IsSender()) + sendercount++; + if (!prevactive && srcdat->IsActive()) + activecount++; - if (created) - OnNewSource(srcdat); + if (created) + OnNewSource(srcdat); - if (srcdat->IsValidated()) // process the CSRCs - { - RTPInternalSourceData *csrcdat; - bool createdcsrc; + if (srcdat->IsValidated()) // process the CSRCs + { + RTPInternalSourceData *csrcdat; + bool createdcsrc; - int num = numCSRCs; - int i; + int num = numCSRCs; + int i; - for (i = 0 ; i < num ; i++) - { - if ((status = ObtainSourceDataInstance(CSRCs[i],&csrcdat,&createdcsrc)) < 0) - return status; - if (createdcsrc) - { - csrcdat->SetCSRC(); - if (csrcdat->IsActive()) - activecount++; - OnNewSource(csrcdat); - } - else // already found an entry, possibly because of RTCP data - { - if (!CheckCollision(csrcdat,senderaddress,true)) - csrcdat->SetCSRC(); - } - } - } + for (i = 0; i < num; i++) + { + if ((status = ObtainSourceDataInstance(CSRCs[i], &csrcdat, &createdcsrc)) < 0) + return status; + if (createdcsrc) + { + csrcdat->SetCSRC(); + if (csrcdat->IsActive()) + activecount++; + OnNewSource(csrcdat); + } + else // already found an entry, possibly because of RTCP data + { + if (!CheckCollision(csrcdat, senderaddress, true)) + csrcdat->SetCSRC(); + } + } + } - return 0; + return 0; } -int RTPSources::ProcessRTCPCompoundPacket(RTCPCompoundPacket *rtcpcomppack,const RTPTime &receivetime,const RTPAddress *senderaddress) +int RTPSources::ProcessRTCPCompoundPacket(RTCPCompoundPacket *rtcpcomppack, const RTPTime &receivetime, const RTPAddress *senderaddress) { - RTCPPacket *rtcppack; - int status; - bool gotownssrc = ((owndata == 0)?false:true); - uint32_t ownssrc = ((owndata != 0)?owndata->GetSSRC():0); + RTCPPacket *rtcppack; + int status; + bool gotownssrc = ((owndata == 0) ? false : true); + uint32_t ownssrc = ((owndata != 0) ? owndata->GetSSRC() : 0); - OnRTCPCompoundPacket(rtcpcomppack,receivetime,senderaddress); + OnRTCPCompoundPacket(rtcpcomppack, receivetime, senderaddress); - rtcpcomppack->GotoFirstPacket(); - while ((rtcppack = rtcpcomppack->GetNextPacket()) != 0) - { - if (rtcppack->IsKnownFormat()) - { - switch (rtcppack->GetPacketType()) - { - case RTCPPacket::SR: - { - RTCPSRPacket *p = (RTCPSRPacket *)rtcppack; - uint32_t senderssrc = p->GetSenderSSRC(); + rtcpcomppack->GotoFirstPacket(); + while ((rtcppack = rtcpcomppack->GetNextPacket()) != 0) + { + if (rtcppack->IsKnownFormat()) + { + switch (rtcppack->GetPacketType()) + { + case RTCPPacket::SR: + { + RTCPSRPacket *p = (RTCPSRPacket *) rtcppack; + uint32_t senderssrc = p->GetSenderSSRC(); - status = ProcessRTCPSenderInfo(senderssrc,p->GetNTPTimestamp(),p->GetRTPTimestamp(), - p->GetSenderPacketCount(),p->GetSenderOctetCount(), - receivetime,senderaddress); - if (status < 0) - return status; + status = ProcessRTCPSenderInfo(senderssrc, p->GetNTPTimestamp(), p->GetRTPTimestamp(), p->GetSenderPacketCount(), p->GetSenderOctetCount(), receivetime, + senderaddress); + if (status < 0) + return status; - bool gotinfo = false; - if (gotownssrc) - { - int i; - int num = p->GetReceptionReportCount(); - for (i = 0 ; i < num ; i++) - { - if (p->GetSSRC(i) == ownssrc) // data is meant for us - { - gotinfo = true; - status = ProcessRTCPReportBlock(senderssrc,p->GetFractionLost(i),p->GetLostPacketCount(i), - p->GetExtendedHighestSequenceNumber(i),p->GetJitter(i),p->GetLSR(i), - p->GetDLSR(i),receivetime,senderaddress); - if (status < 0) - return status; - } - } - } - if (!gotinfo) - { - status = UpdateReceiveTime(senderssrc,receivetime,senderaddress); - if (status < 0) - return status; - } - } - break; - case RTCPPacket::RR: - { - RTCPRRPacket *p = (RTCPRRPacket *)rtcppack; - uint32_t senderssrc = p->GetSenderSSRC(); + bool gotinfo = false; + if (gotownssrc) + { + int i; + int num = p->GetReceptionReportCount(); + for (i = 0; i < num; i++) + { + if (p->GetSSRC(i) == ownssrc) // data is meant for us + { + gotinfo = true; + status = ProcessRTCPReportBlock(senderssrc, p->GetFractionLost(i), p->GetLostPacketCount(i), p->GetExtendedHighestSequenceNumber(i), p->GetJitter(i), + p->GetLSR(i), p->GetDLSR(i), receivetime, senderaddress); + if (status < 0) + return status; + } + } + } + if (!gotinfo) + { + status = UpdateReceiveTime(senderssrc, receivetime, senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::RR: + { + RTCPRRPacket *p = (RTCPRRPacket *) rtcppack; + uint32_t senderssrc = p->GetSenderSSRC(); - bool gotinfo = false; + bool gotinfo = false; - if (gotownssrc) - { - int i; - int num = p->GetReceptionReportCount(); - for (i = 0 ; i < num ; i++) - { - if (p->GetSSRC(i) == ownssrc) - { - gotinfo = true; - status = ProcessRTCPReportBlock(senderssrc,p->GetFractionLost(i),p->GetLostPacketCount(i), - p->GetExtendedHighestSequenceNumber(i),p->GetJitter(i),p->GetLSR(i), - p->GetDLSR(i),receivetime,senderaddress); - if (status < 0) - return status; - } - } - } - if (!gotinfo) - { - status = UpdateReceiveTime(senderssrc,receivetime,senderaddress); - if (status < 0) - return status; - } - } - break; - case RTCPPacket::SDES: - { - RTCPSDESPacket *p = (RTCPSDESPacket *)rtcppack; + if (gotownssrc) + { + int i; + int num = p->GetReceptionReportCount(); + for (i = 0; i < num; i++) + { + if (p->GetSSRC(i) == ownssrc) + { + gotinfo = true; + status = ProcessRTCPReportBlock(senderssrc, p->GetFractionLost(i), p->GetLostPacketCount(i), p->GetExtendedHighestSequenceNumber(i), p->GetJitter(i), + p->GetLSR(i), p->GetDLSR(i), receivetime, senderaddress); + if (status < 0) + return status; + } + } + } + if (!gotinfo) + { + status = UpdateReceiveTime(senderssrc, receivetime, senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::SDES: + { + RTCPSDESPacket *p = (RTCPSDESPacket *) rtcppack; - if (p->GotoFirstChunk()) - { - do - { - uint32_t sdesssrc = p->GetChunkSSRC(); - bool updated = false; - if (p->GotoFirstItem()) - { - do - { - RTCPSDESPacket::ItemType t; + if (p->GotoFirstChunk()) + { + do + { + uint32_t sdesssrc = p->GetChunkSSRC(); + bool updated = false; + if (p->GotoFirstItem()) + { + do + { + RTCPSDESPacket::ItemType t; - if ((t = p->GetItemType()) != RTCPSDESPacket::PRIV) - { - updated = true; - status = ProcessSDESNormalItem(sdesssrc,t,p->GetItemLength(),p->GetItemData(),receivetime,senderaddress); - if (status < 0) - return status; - } + if ((t = p->GetItemType()) != RTCPSDESPacket::PRIV) + { + updated = true; + status = ProcessSDESNormalItem(sdesssrc, t, p->GetItemLength(), p->GetItemData(), receivetime, senderaddress); + if (status < 0) + return status; + } #ifdef RTP_SUPPORT_SDESPRIV - else - { - updated = true; - status = ProcessSDESPrivateItem(sdesssrc,p->GetPRIVPrefixLength(),p->GetPRIVPrefixData(),p->GetPRIVValueLength(), - p->GetPRIVValueData(),receivetime,senderaddress); - if (status < 0) - return status; - } + else + { + updated = true; + status = ProcessSDESPrivateItem(sdesssrc, p->GetPRIVPrefixLength(), p->GetPRIVPrefixData(), p->GetPRIVValueLength(), p->GetPRIVValueData(), + receivetime, senderaddress); + if (status < 0) + return status; + } #endif // RTP_SUPPORT_SDESPRIV - } while (p->GotoNextItem()); - } - if (!updated) - { - status = UpdateReceiveTime(sdesssrc,receivetime,senderaddress); - if (status < 0) - return status; - } - } while (p->GotoNextChunk()); - } - } - break; - case RTCPPacket::BYE: - { - RTCPBYEPacket *p = (RTCPBYEPacket *)rtcppack; - int i; - int num = p->GetSSRCCount(); + } while (p->GotoNextItem()); + } + if (!updated) + { + status = UpdateReceiveTime(sdesssrc, receivetime, senderaddress); + if (status < 0) + return status; + } + } while (p->GotoNextChunk()); + } + } + break; + case RTCPPacket::BYE: + { + RTCPBYEPacket *p = (RTCPBYEPacket *) rtcppack; + int i; + int num = p->GetSSRCCount(); - for (i = 0 ; i < num ; i++) - { - uint32_t byessrc = p->GetSSRC(i); - status = ProcessBYE(byessrc,p->GetReasonLength(),p->GetReasonData(),receivetime,senderaddress); - if (status < 0) - return status; - } - } - break; - case RTCPPacket::APP: - { - RTCPAPPPacket *p = (RTCPAPPPacket *)rtcppack; + for (i = 0; i < num; i++) + { + uint32_t byessrc = p->GetSSRC(i); + status = ProcessBYE(byessrc, p->GetReasonLength(), p->GetReasonData(), receivetime, senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::APP: + { + RTCPAPPPacket *p = (RTCPAPPPacket *) rtcppack; - OnAPPPacket(p,receivetime,senderaddress); - } - break; - case RTCPPacket::Unknown: - default: - { - OnUnknownPacketType(rtcppack,receivetime,senderaddress); - } - break; - } - } - else - { - OnUnknownPacketFormat(rtcppack,receivetime,senderaddress); - } - } + OnAPPPacket(p, receivetime, senderaddress); + } + break; + case RTCPPacket::Unknown: + default: + { + OnUnknownPacketType(rtcppack, receivetime, senderaddress); + } + break; + } + } + else + { + OnUnknownPacketFormat(rtcppack, receivetime, senderaddress); + } + } - return 0; + return 0; } bool RTPSources::GotoFirstSource() { - sourcelist.GotoFirstElement(); - if (sourcelist.HasCurrentElement()) - return true; - return false; + sourcelist.GotoFirstElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; } bool RTPSources::GotoNextSource() { - sourcelist.GotoNextElement(); - if (sourcelist.HasCurrentElement()) - return true; - return false; + sourcelist.GotoNextElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; } bool RTPSources::GotoPreviousSource() { - sourcelist.GotoPreviousElement(); - if (sourcelist.HasCurrentElement()) - return true; - return false; + sourcelist.GotoPreviousElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; } bool RTPSources::GotoFirstSourceWithData() { - bool found = false; + bool found = false; - sourcelist.GotoFirstElement(); - while (!found && sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *srcdat; + sourcelist.GotoFirstElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; - srcdat = sourcelist.GetCurrentElement(); - if (srcdat->HasData()) - found = true; - else - sourcelist.GotoNextElement(); - } + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoNextElement(); + } - return found; + return found; } bool RTPSources::GotoNextSourceWithData() { - bool found = false; + bool found = false; - sourcelist.GotoNextElement(); - while (!found && sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *srcdat; + sourcelist.GotoNextElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; - srcdat = sourcelist.GetCurrentElement(); - if (srcdat->HasData()) - found = true; - else - sourcelist.GotoNextElement(); - } + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoNextElement(); + } - return found; + return found; } bool RTPSources::GotoPreviousSourceWithData() { - bool found = false; + bool found = false; - sourcelist.GotoPreviousElement(); - while (!found && sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *srcdat; + sourcelist.GotoPreviousElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; - srcdat = sourcelist.GetCurrentElement(); - if (srcdat->HasData()) - found = true; - else - sourcelist.GotoPreviousElement(); - } + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoPreviousElement(); + } - return found; + return found; } RTPSourceData *RTPSources::GetCurrentSourceInfo() { - if (!sourcelist.HasCurrentElement()) - return 0; - return sourcelist.GetCurrentElement(); + if (!sourcelist.HasCurrentElement()) + return 0; + return sourcelist.GetCurrentElement(); } RTPSourceData *RTPSources::GetSourceInfo(uint32_t ssrc) { - if (sourcelist.GotoElement(ssrc) < 0) - return 0; - if (!sourcelist.HasCurrentElement()) - return 0; - return sourcelist.GetCurrentElement(); + if (sourcelist.GotoElement(ssrc) < 0) + return 0; + if (!sourcelist.HasCurrentElement()) + return 0; + return sourcelist.GetCurrentElement(); } bool RTPSources::GotEntry(uint32_t ssrc) { - return sourcelist.HasElement(ssrc); + return sourcelist.HasElement(ssrc); } RTPPacket *RTPSources::GetNextPacket() { - if (!sourcelist.HasCurrentElement()) - return 0; + if (!sourcelist.HasCurrentElement()) + return 0; - RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); - RTPPacket *pack = srcdat->GetNextPacket(); - return pack; + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + RTPPacket *pack = srcdat->GetNextPacket(); + return pack; } -int RTPSources::ProcessRTCPSenderInfo(uint32_t ssrc,const RTPNTPTime &ntptime,uint32_t rtptime, - uint32_t packetcount,uint32_t octetcount,const RTPTime &receivetime, - const RTPAddress *senderaddress) +int RTPSources::ProcessRTCPSenderInfo(uint32_t ssrc, const RTPNTPTime &ntptime, uint32_t rtptime, uint32_t packetcount, uint32_t octetcount, const RTPTime &receivetime, + const RTPAddress *senderaddress) { - RTPInternalSourceData *srcdat; - bool created; - int status; + RTPInternalSourceData *srcdat; + bool created; + int status; - status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); - if (status < 0) - return status; - if (srcdat == 0) - return 0; + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; - srcdat->ProcessSenderInfo(ntptime,rtptime,packetcount,octetcount,receivetime); + srcdat->ProcessSenderInfo(ntptime, rtptime, packetcount, octetcount, receivetime); - // Call the callback - if (created) - OnNewSource(srcdat); + // Call the callback + if (created) + OnNewSource(srcdat); - OnRTCPSenderReport(srcdat); + OnRTCPSenderReport(srcdat); - return 0; + return 0; } -int RTPSources::ProcessRTCPReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t lostpackets, - uint32_t exthighseqnr,uint32_t jitter,uint32_t lsr, - uint32_t dlsr,const RTPTime &receivetime,const RTPAddress *senderaddress) +int RTPSources::ProcessRTCPReportBlock(uint32_t ssrc, uint8_t fractionlost, int32_t lostpackets, uint32_t exthighseqnr, uint32_t jitter, uint32_t lsr, uint32_t dlsr, + const RTPTime &receivetime, const RTPAddress *senderaddress) { - RTPInternalSourceData *srcdat; - bool created; - int status; + RTPInternalSourceData *srcdat; + bool created; + int status; - status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); - if (status < 0) - return status; - if (srcdat == 0) - return 0; + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; - srcdat->ProcessReportBlock(fractionlost,lostpackets,exthighseqnr,jitter,lsr,dlsr,receivetime); + srcdat->ProcessReportBlock(fractionlost, lostpackets, exthighseqnr, jitter, lsr, dlsr, receivetime); - // Call the callback - if (created) - OnNewSource(srcdat); + // Call the callback + if (created) + OnNewSource(srcdat); - OnRTCPReceiverReport(srcdat); + OnRTCPReceiverReport(srcdat); - return 0; + return 0; } -int RTPSources::ProcessSDESNormalItem(uint32_t ssrc,RTCPSDESPacket::ItemType t,size_t itemlength, - const void *itemdata,const RTPTime &receivetime,const RTPAddress *senderaddress) +int RTPSources::ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, size_t itemlength, const void *itemdata, const RTPTime &receivetime, + const RTPAddress *senderaddress) { - RTPInternalSourceData *srcdat; - bool created,cnamecollis; - int status; - uint8_t sdesid; - bool prevactive; + RTPInternalSourceData *srcdat; + bool created, cnamecollis; + int status; + uint8_t sdesid; + bool prevactive; - switch(t) - { - case RTCPSDESPacket::CNAME: - sdesid = RTCP_SDES_ID_CNAME; - break; - case RTCPSDESPacket::NAME: - sdesid = RTCP_SDES_ID_NAME; - break; - case RTCPSDESPacket::EMAIL: - sdesid = RTCP_SDES_ID_EMAIL; - break; - case RTCPSDESPacket::PHONE: - sdesid = RTCP_SDES_ID_PHONE; - break; - case RTCPSDESPacket::LOC: - sdesid = RTCP_SDES_ID_LOCATION; - break; - case RTCPSDESPacket::TOOL: - sdesid = RTCP_SDES_ID_TOOL; - break; - case RTCPSDESPacket::NOTE: - sdesid = RTCP_SDES_ID_NOTE; - break; - default: - return ERR_RTP_SOURCES_ILLEGALSDESTYPE; - } + switch (t) + { + case RTCPSDESPacket::CNAME: + sdesid = RTCP_SDES_ID_CNAME; + break; + case RTCPSDESPacket::NAME: + sdesid = RTCP_SDES_ID_NAME; + break; + case RTCPSDESPacket::EMAIL: + sdesid = RTCP_SDES_ID_EMAIL; + break; + case RTCPSDESPacket::PHONE: + sdesid = RTCP_SDES_ID_PHONE; + break; + case RTCPSDESPacket::LOC: + sdesid = RTCP_SDES_ID_LOCATION; + break; + case RTCPSDESPacket::TOOL: + sdesid = RTCP_SDES_ID_TOOL; + break; + case RTCPSDESPacket::NOTE: + sdesid = RTCP_SDES_ID_NOTE; + break; + default: + return ERR_RTP_SOURCES_ILLEGALSDESTYPE; + } - status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); - if (status < 0) - return status; - if (srcdat == 0) - return 0; + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; - prevactive = srcdat->IsActive(); - status = srcdat->ProcessSDESItem(sdesid,(const uint8_t *)itemdata,itemlength,receivetime,&cnamecollis); - if (!prevactive && srcdat->IsActive()) - activecount++; + prevactive = srcdat->IsActive(); + status = srcdat->ProcessSDESItem(sdesid, (const uint8_t *) itemdata, itemlength, receivetime, &cnamecollis); + if (!prevactive && srcdat->IsActive()) + activecount++; - // Call the callback - if (created) - OnNewSource(srcdat); - if (cnamecollis) - OnCNAMECollision(srcdat,senderaddress,(const uint8_t *)itemdata,itemlength); + // Call the callback + if (created) + OnNewSource(srcdat); + if (cnamecollis) + OnCNAMECollision(srcdat, senderaddress, (const uint8_t *) itemdata, itemlength); - if (status >= 0) - OnRTCPSDESItem(srcdat, t, itemdata, itemlength); + if (status >= 0) + OnRTCPSDESItem(srcdat, t, itemdata, itemlength); - return status; + return status; } #ifdef RTP_SUPPORT_SDESPRIV -int RTPSources::ProcessSDESPrivateItem(uint32_t ssrc,size_t prefixlen,const void *prefixdata, - size_t valuelen,const void *valuedata,const RTPTime &receivetime, - const RTPAddress *senderaddress) +int RTPSources::ProcessSDESPrivateItem(uint32_t ssrc, size_t prefixlen, const void *prefixdata, size_t valuelen, const void *valuedata, const RTPTime &receivetime, + const RTPAddress *senderaddress) { - RTPInternalSourceData *srcdat; - bool created; - int status; + RTPInternalSourceData *srcdat; + bool created; + int status; - status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); - if (status < 0) - return status; - if (srcdat == 0) - return 0; + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; - status = srcdat->ProcessPrivateSDESItem((const uint8_t *)prefixdata,prefixlen,(const uint8_t *)valuedata,valuelen,receivetime); - // Call the callback - if (created) - OnNewSource(srcdat); + status = srcdat->ProcessPrivateSDESItem((const uint8_t *) prefixdata, prefixlen, (const uint8_t *) valuedata, valuelen, receivetime); + // Call the callback + if (created) + OnNewSource(srcdat); - if (status >= 0) - OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); + if (status >= 0) + OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); - return status; + return status; } #endif //RTP_SUPPORT_SDESPRIV -int RTPSources::ProcessBYE(uint32_t ssrc,size_t reasonlength,const void *reasondata, - const RTPTime &receivetime,const RTPAddress *senderaddress) +int RTPSources::ProcessBYE(uint32_t ssrc, size_t reasonlength, const void *reasondata, const RTPTime &receivetime, const RTPAddress *senderaddress) { - RTPInternalSourceData *srcdat; - bool created; - int status; - bool prevactive; + RTPInternalSourceData *srcdat; + bool created; + int status; + bool prevactive; - status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); - if (status < 0) - return status; - if (srcdat == 0) - return 0; + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; - // we'll ignore BYE packets for our own ssrc - if (srcdat == owndata) - return 0; + // we'll ignore BYE packets for our own ssrc + if (srcdat == owndata) + return 0; - prevactive = srcdat->IsActive(); - srcdat->ProcessBYEPacket((const uint8_t *)reasondata,reasonlength,receivetime); - if (prevactive && !srcdat->IsActive()) - activecount--; + prevactive = srcdat->IsActive(); + srcdat->ProcessBYEPacket((const uint8_t *) reasondata, reasonlength, receivetime); + if (prevactive && !srcdat->IsActive()) + activecount--; - // Call the callback - if (created) - OnNewSource(srcdat); - OnBYEPacket(srcdat); - return 0; + // Call the callback + if (created) + OnNewSource(srcdat); + OnBYEPacket(srcdat); + return 0; } -int RTPSources::ObtainSourceDataInstance(uint32_t ssrc,RTPInternalSourceData **srcdat,bool *created) +int RTPSources::ObtainSourceDataInstance(uint32_t ssrc, RTPInternalSourceData **srcdat, bool *created) { - RTPInternalSourceData *srcdat2; - int status; + RTPInternalSourceData *srcdat2; + int status; - if (sourcelist.GotoElement(ssrc) < 0) // No entry for this source - { + if (sourcelist.GotoElement(ssrc) < 0) // No entry for this source + { #ifdef RTP_SUPPORT_PROBATION - srcdat2 = new RTPInternalSourceData(ssrc,probationtype,GetMemoryManager()); + srcdat2 = new RTPInternalSourceData(ssrc, probationtype); #else - srcdat2 = new RTPInternalSourceData(ssrc,RTPSources::NoProbation,GetMemoryManager()); + srcdat2 = new RTPInternalSourceData(ssrc,RTPSources::NoProbation); #endif // RTP_SUPPORT_PROBATION - if (srcdat2 == 0) - return ERR_RTP_OUTOFMEM; - if ((status = sourcelist.AddElement(ssrc,srcdat2)) < 0) - { - RTPDelete(srcdat2,GetMemoryManager()); - return status; - } - *srcdat = srcdat2; - *created = true; - totalcount++; - } - else - { - *srcdat = sourcelist.GetCurrentElement(); - *created = false; - } - return 0; + if (srcdat2 == 0) + return ERR_RTP_OUTOFMEM; + if ((status = sourcelist.AddElement(ssrc, srcdat2)) < 0) + { + delete srcdat2; + return status; + } + *srcdat = srcdat2; + *created = true; + totalcount++; + } + else + { + *srcdat = sourcelist.GetCurrentElement(); + *created = false; + } + return 0; } - -int RTPSources::GetRTCPSourceData(uint32_t ssrc,const RTPAddress *senderaddress, - RTPInternalSourceData **srcdat2,bool *newsource) +int RTPSources::GetRTCPSourceData(uint32_t ssrc, const RTPAddress *senderaddress, RTPInternalSourceData **srcdat2, bool *newsource) { - int status; - bool created; - RTPInternalSourceData *srcdat; + int status; + bool created; + RTPInternalSourceData *srcdat; - *srcdat2 = 0; + *srcdat2 = 0; - if ((status = ObtainSourceDataInstance(ssrc,&srcdat,&created)) < 0) - return status; + if ((status = ObtainSourceDataInstance(ssrc, &srcdat, &created)) < 0) + return status; - if (created) - { - if ((status = srcdat->SetRTCPDataAddress(senderaddress)) < 0) - return status; - } - else // got a previously existing source - { - if (CheckCollision(srcdat,senderaddress,false)) - return 0; // ignore packet on collision - } + if (created) + { + if ((status = srcdat->SetRTCPDataAddress(senderaddress)) < 0) + return status; + } + else // got a previously existing source + { + if (CheckCollision(srcdat, senderaddress, false)) + return 0; // ignore packet on collision + } - *srcdat2 = srcdat; - *newsource = created; + *srcdat2 = srcdat; + *newsource = created; - return 0; + return 0; } -int RTPSources::UpdateReceiveTime(uint32_t ssrc,const RTPTime &receivetime,const RTPAddress *senderaddress) +int RTPSources::UpdateReceiveTime(uint32_t ssrc, const RTPTime &receivetime, const RTPAddress *senderaddress) { - RTPInternalSourceData *srcdat; - bool created; - int status; + RTPInternalSourceData *srcdat; + bool created; + int status; - status = GetRTCPSourceData(ssrc,senderaddress,&srcdat,&created); - if (status < 0) - return status; - if (srcdat == 0) - return 0; + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; - // We got valid SSRC info - srcdat->UpdateMessageTime(receivetime); + // We got valid SSRC info + srcdat->UpdateMessageTime(receivetime); - // Call the callback - if (created) - OnNewSource(srcdat); + // Call the callback + if (created) + OnNewSource(srcdat); - return 0; + return 0; } -void RTPSources::Timeout(const RTPTime &curtime,const RTPTime &timeoutdelay) +void RTPSources::Timeout(const RTPTime &curtime, const RTPTime &timeoutdelay) { - int newtotalcount = 0; - int newsendercount = 0; - int newactivecount = 0; - RTPTime checktime = curtime; - checktime -= timeoutdelay; + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; - sourcelist.GotoFirstElement(); - while (sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); - RTPTime lastmsgtime = srcdat->INF_GetLastMessageTime(); + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + RTPTime lastmsgtime = srcdat->INF_GetLastMessageTime(); - // we don't want to time out ourselves - if ((srcdat != owndata) && (lastmsgtime < checktime)) // timeout - { + // we don't want to time out ourselves + if ((srcdat != owndata) && (lastmsgtime < checktime)) // timeout + { - totalcount--; - if (srcdat->IsSender()) - sendercount--; - if (srcdat->IsActive()) - activecount--; + totalcount--; + if (srcdat->IsSender()) + sendercount--; + if (srcdat->IsActive()) + activecount--; - sourcelist.DeleteCurrentElement(); + sourcelist.DeleteCurrentElement(); - OnTimeout(srcdat); - OnRemoveSource(srcdat); - RTPDelete(srcdat,GetMemoryManager()); - } - else - { - newtotalcount++; - if (srcdat->IsSender()) - newsendercount++; - if (srcdat->IsActive()) - newactivecount++; - sourcelist.GotoNextElement(); - } - } + OnTimeout(srcdat); + OnRemoveSource(srcdat); + delete srcdat; + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } - totalcount = newtotalcount; // just to play it safe - sendercount = newsendercount; - activecount = newactivecount; + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; } -void RTPSources::SenderTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay) +void RTPSources::SenderTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay) { - int newtotalcount = 0; - int newsendercount = 0; - int newactivecount = 0; - RTPTime checktime = curtime; - checktime -= timeoutdelay; + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; - sourcelist.GotoFirstElement(); - while (sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); - newtotalcount++; - if (srcdat->IsActive()) - newactivecount++; + newtotalcount++; + if (srcdat->IsActive()) + newactivecount++; - if (srcdat->IsSender()) - { - RTPTime lastrtppacktime = srcdat->INF_GetLastRTPPacketTime(); + if (srcdat->IsSender()) + { + RTPTime lastrtppacktime = srcdat->INF_GetLastRTPPacketTime(); - if (lastrtppacktime < checktime) // timeout - { - srcdat->ClearSenderFlag(); - sendercount--; - } - else - newsendercount++; - } - sourcelist.GotoNextElement(); - } + if (lastrtppacktime < checktime) // timeout + { + srcdat->ClearSenderFlag(); + sendercount--; + } + else + newsendercount++; + } + sourcelist.GotoNextElement(); + } - totalcount = newtotalcount; // just to play it safe - sendercount = newsendercount; - activecount = newactivecount; + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; } -void RTPSources::BYETimeout(const RTPTime &curtime,const RTPTime &timeoutdelay) +void RTPSources::BYETimeout(const RTPTime &curtime, const RTPTime &timeoutdelay) { - int newtotalcount = 0; - int newsendercount = 0; - int newactivecount = 0; - RTPTime checktime = curtime; - checktime -= timeoutdelay; + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; - sourcelist.GotoFirstElement(); - while (sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); - if (srcdat->ReceivedBYE()) - { - RTPTime byetime = srcdat->GetBYETime(); + if (srcdat->ReceivedBYE()) + { + RTPTime byetime = srcdat->GetBYETime(); - if ((srcdat != owndata) && (checktime > byetime)) - { - totalcount--; - if (srcdat->IsSender()) - sendercount--; - if (srcdat->IsActive()) - activecount--; - sourcelist.DeleteCurrentElement(); - OnBYETimeout(srcdat); - OnRemoveSource(srcdat); - RTPDelete(srcdat,GetMemoryManager()); - } - else - { - newtotalcount++; - if (srcdat->IsSender()) - newsendercount++; - if (srcdat->IsActive()) - newactivecount++; - sourcelist.GotoNextElement(); - } - } - else - { - newtotalcount++; - if (srcdat->IsSender()) - newsendercount++; - if (srcdat->IsActive()) - newactivecount++; - sourcelist.GotoNextElement(); - } - } + if ((srcdat != owndata) && (checktime > byetime)) + { + totalcount--; + if (srcdat->IsSender()) + sendercount--; + if (srcdat->IsActive()) + activecount--; + sourcelist.DeleteCurrentElement(); + OnBYETimeout(srcdat); + OnRemoveSource(srcdat); + delete srcdat; + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } - totalcount = newtotalcount; // just to play it safe - sendercount = newsendercount; - activecount = newactivecount; + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; } -void RTPSources::NoteTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay) +void RTPSources::NoteTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay) { - int newtotalcount = 0; - int newsendercount = 0; - int newactivecount = 0; - RTPTime checktime = curtime; - checktime -= timeoutdelay; + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; - sourcelist.GotoFirstElement(); - while (sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); - size_t notelen; + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + size_t notelen; - srcdat->SDES_GetNote(¬elen); - if (notelen != 0) // Note has been set - { - RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); + srcdat->SDES_GetNote(¬elen); + if (notelen != 0) // Note has been set + { + RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); - if (checktime > notetime) - { - srcdat->ClearNote(); - OnNoteTimeout(srcdat); - } - } + if (checktime > notetime) + { + srcdat->ClearNote(); + OnNoteTimeout(srcdat); + } + } - newtotalcount++; - if (srcdat->IsSender()) - newsendercount++; - if (srcdat->IsActive()) - newactivecount++; - sourcelist.GotoNextElement(); - } + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } - totalcount = newtotalcount; // just to play it safe - sendercount = newsendercount; - activecount = newactivecount; + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; } -void RTPSources::MultipleTimeouts(const RTPTime &curtime,const RTPTime &sendertimeout,const RTPTime &byetimeout,const RTPTime &generaltimeout,const RTPTime ¬etimeout) +void RTPSources::MultipleTimeouts(const RTPTime &curtime, const RTPTime &sendertimeout, const RTPTime &byetimeout, const RTPTime &generaltimeout, const RTPTime ¬etimeout) { - int newtotalcount = 0; - int newsendercount = 0; - int newactivecount = 0; - RTPTime senderchecktime = curtime; - RTPTime byechecktime = curtime; - RTPTime generaltchecktime = curtime; - RTPTime notechecktime = curtime; - senderchecktime -= sendertimeout; - byechecktime -= byetimeout; - generaltchecktime -= generaltimeout; - notechecktime -= notetimeout; + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime senderchecktime = curtime; + RTPTime byechecktime = curtime; + RTPTime generaltchecktime = curtime; + RTPTime notechecktime = curtime; + senderchecktime -= sendertimeout; + byechecktime -= byetimeout; + generaltchecktime -= generaltimeout; + notechecktime -= notetimeout; - sourcelist.GotoFirstElement(); - while (sourcelist.HasCurrentElement()) - { - RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); - bool deleted,issender,isactive; - bool byetimeout,normaltimeout,notetimeout; + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + bool deleted, issender, isactive; + bool byetimeout, normaltimeout, notetimeout; - size_t notelen; + size_t notelen; - issender = srcdat->IsSender(); - isactive = srcdat->IsActive(); - deleted = false; - byetimeout = false; - normaltimeout = false; - notetimeout = false; + issender = srcdat->IsSender(); + isactive = srcdat->IsActive(); + deleted = false; + byetimeout = false; + normaltimeout = false; + notetimeout = false; - srcdat->SDES_GetNote(¬elen); - if (notelen != 0) // Note has been set - { - RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); + srcdat->SDES_GetNote(¬elen); + if (notelen != 0) // Note has been set + { + RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); - if (notechecktime > notetime) - { - notetimeout = true; - srcdat->ClearNote(); - } - } + if (notechecktime > notetime) + { + notetimeout = true; + srcdat->ClearNote(); + } + } - if (srcdat->ReceivedBYE()) - { - RTPTime byetime = srcdat->GetBYETime(); + if (srcdat->ReceivedBYE()) + { + RTPTime byetime = srcdat->GetBYETime(); - if ((srcdat != owndata) && (byechecktime > byetime)) - { - sourcelist.DeleteCurrentElement(); - deleted = true; - byetimeout = true; - } - } + if ((srcdat != owndata) && (byechecktime > byetime)) + { + sourcelist.DeleteCurrentElement(); + deleted = true; + byetimeout = true; + } + } - if (!deleted) - { - RTPTime lastmsgtime = srcdat->INF_GetLastMessageTime(); + if (!deleted) + { + RTPTime lastmsgtime = srcdat->INF_GetLastMessageTime(); - if ((srcdat != owndata) && (lastmsgtime < generaltchecktime)) - { - sourcelist.DeleteCurrentElement(); - deleted = true; - normaltimeout = true; - } - } + if ((srcdat != owndata) && (lastmsgtime < generaltchecktime)) + { + sourcelist.DeleteCurrentElement(); + deleted = true; + normaltimeout = true; + } + } - if (!deleted) - { - newtotalcount++; + if (!deleted) + { + newtotalcount++; - if (issender) - { - RTPTime lastrtppacktime = srcdat->INF_GetLastRTPPacketTime(); + if (issender) + { + RTPTime lastrtppacktime = srcdat->INF_GetLastRTPPacketTime(); - if (lastrtppacktime < senderchecktime) - { - srcdat->ClearSenderFlag(); - sendercount--; - } - else - newsendercount++; - } + if (lastrtppacktime < senderchecktime) + { + srcdat->ClearSenderFlag(); + sendercount--; + } + else + newsendercount++; + } - if (isactive) - newactivecount++; + if (isactive) + newactivecount++; - if (notetimeout) - OnNoteTimeout(srcdat); + if (notetimeout) + OnNoteTimeout(srcdat); - sourcelist.GotoNextElement(); - } - else // deleted entry - { - if (issender) - sendercount--; - if (isactive) - activecount--; - totalcount--; + sourcelist.GotoNextElement(); + } + else // deleted entry + { + if (issender) + sendercount--; + if (isactive) + activecount--; + totalcount--; - if (byetimeout) - OnBYETimeout(srcdat); - if (normaltimeout) - OnTimeout(srcdat); - OnRemoveSource(srcdat); - RTPDelete(srcdat,GetMemoryManager()); - } - } + if (byetimeout) + OnBYETimeout(srcdat); + if (normaltimeout) + OnTimeout(srcdat); + OnRemoveSource(srcdat); + delete srcdat; + } + } - totalcount = newtotalcount; // just to play it safe - sendercount = newsendercount; - activecount = newactivecount; + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; } -bool RTPSources::CheckCollision(RTPInternalSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp) +bool RTPSources::CheckCollision(RTPInternalSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp) { - bool isset,otherisset; - const RTPAddress *addr,*otheraddr; + bool isset, otherisset; + const RTPAddress *addr, *otheraddr; - if (isrtp) - { - isset = srcdat->IsRTPAddressSet(); - addr = srcdat->GetRTPDataAddress(); - otherisset = srcdat->IsRTCPAddressSet(); - otheraddr = srcdat->GetRTCPDataAddress(); - } - else - { - isset = srcdat->IsRTCPAddressSet(); - addr = srcdat->GetRTCPDataAddress(); - otherisset = srcdat->IsRTPAddressSet(); - otheraddr = srcdat->GetRTPDataAddress(); - } + if (isrtp) + { + isset = srcdat->IsRTPAddressSet(); + addr = srcdat->GetRTPDataAddress(); + otherisset = srcdat->IsRTCPAddressSet(); + otheraddr = srcdat->GetRTCPDataAddress(); + } + else + { + isset = srcdat->IsRTCPAddressSet(); + addr = srcdat->GetRTCPDataAddress(); + otherisset = srcdat->IsRTPAddressSet(); + otheraddr = srcdat->GetRTPDataAddress(); + } - if (!isset) - { - if (otherisset) // got other address, can check if it comes from same host - { - if (otheraddr == 0) // other came from our own session - { - if (senderaddress != 0) - { - OnSSRCCollision(srcdat,senderaddress,isrtp); - return true; - } + if (!isset) + { + if (otherisset) // got other address, can check if it comes from same host + { + if (otheraddr == 0) // other came from our own session + { + if (senderaddress != 0) + { + OnSSRCCollision(srcdat, senderaddress, isrtp); + return true; + } - // Ok, store it + // Ok, store it - if (isrtp) - srcdat->SetRTPDataAddress(senderaddress); - else - srcdat->SetRTCPDataAddress(senderaddress); - } - else - { - if (!otheraddr->IsFromSameHost(senderaddress)) - { - OnSSRCCollision(srcdat,senderaddress,isrtp); - return true; - } + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + else + { + if (!otheraddr->IsFromSameHost(senderaddress)) + { + OnSSRCCollision(srcdat, senderaddress, isrtp); + return true; + } - // Ok, comes from same host, store the address + // Ok, comes from same host, store the address - if (isrtp) - srcdat->SetRTPDataAddress(senderaddress); - else - srcdat->SetRTCPDataAddress(senderaddress); - } - } - else // no other address, store this one - { - if (isrtp) - srcdat->SetRTPDataAddress(senderaddress); - else - srcdat->SetRTCPDataAddress(senderaddress); - } - } - else // already got an address - { - if (addr == 0) - { - if (senderaddress != 0) - { - OnSSRCCollision(srcdat,senderaddress,isrtp); - return true; - } - } - else - { - if (!addr->IsSameAddress(senderaddress)) - { - OnSSRCCollision(srcdat,senderaddress,isrtp); - return true; - } - } - } + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + } + else // no other address, store this one + { + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + } + else // already got an address + { + if (addr == 0) + { + if (senderaddress != 0) + { + OnSSRCCollision(srcdat, senderaddress, isrtp); + return true; + } + } + else + { + if (!addr->IsSameAddress(senderaddress)) + { + OnSSRCCollision(srcdat, senderaddress, isrtp); + return true; + } + } + } - return false; + return false; } } // end namespace diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h index c3927ba36..aa3590ec5 100644 --- a/qrtplib/rtpsources.h +++ b/qrtplib/rtpsources.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpsources.h @@ -42,7 +42,6 @@ #include "rtpkeyhashtable.h" #include "rtcpsdespacket.h" #include "rtptypes.h" -#include "rtpmemoryobject.h" #define RTPSOURCES_HASHSIZE 8317 @@ -52,7 +51,10 @@ namespace qrtplib class JRTPLIB_IMPORTEXPORT RTPSources_GetHashIndex { public: - static int GetIndex(const uint32_t &ssrc) { return ssrc%RTPSOURCES_HASHSIZE; } + static int GetIndex(const uint32_t &ssrc) + { + return ssrc % RTPSOURCES_HASHSIZE; + } }; class RTPNTPTime; @@ -71,334 +73,369 @@ class RTPSourceData; * is used to identify packets from our own session. The class also provides some overridable functions * which can be used to catch certain events (new SSRC, SSRC collision, ...). */ -class JRTPLIB_IMPORTEXPORT RTPSources : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTPSources { public: - /** Type of probation to use for new sources. */ - enum ProbationType - { - NoProbation, /**< Don't use the probation algorithm; accept RTP packets immediately. */ - ProbationDiscard, /**< Discard incoming RTP packets originating from a source that's on probation. */ - ProbationStore /**< Store incoming RTP packet from a source that's on probation for later retrieval. */ - }; + /** Type of probation to use for new sources. */ + enum ProbationType + { + NoProbation, /**< Don't use the probation algorithm; accept RTP packets immediately. */ + ProbationDiscard, /**< Discard incoming RTP packets originating from a source that's on probation. */ + ProbationStore /**< Store incoming RTP packet from a source that's on probation for later retrieval. */ + }; - /** In the constructor you can select the probation type you'd like to use and also a memory manager. */ - RTPSources(ProbationType = ProbationStore,RTPMemoryManager *mgr = 0); - virtual ~RTPSources(); + /** In the constructor you can select the probation type you'd like to use and also a memory manager. */ + RTPSources(ProbationType = ProbationStore); + virtual ~RTPSources(); - /** Clears the source table. */ - void Clear(); + /** Clears the source table. */ + void Clear(); #ifdef RTP_SUPPORT_PROBATION - /** Changes the current probation type. */ - void SetProbationType(ProbationType probtype) { probationtype = probtype; } + /** Changes the current probation type. */ + void SetProbationType(ProbationType probtype) + { + probationtype = probtype; + } #endif // RTP_SUPPORT_PROBATION - /** Creates an entry for our own SSRC identifier. */ - int CreateOwnSSRC(uint32_t ssrc); + /** Creates an entry for our own SSRC identifier. */ + int CreateOwnSSRC(uint32_t ssrc); - /** Deletes the entry for our own SSRC identifier. */ - int DeleteOwnSSRC(); + /** Deletes the entry for our own SSRC identifier. */ + int DeleteOwnSSRC(); - /** This function should be called if our own session has sent an RTP packet. - * This function should be called if our own session has sent an RTP packet. - * For our own SSRC entry, the sender flag is updated based upon outgoing packets instead of incoming packets. - */ - void SentRTPPacket(); + /** This function should be called if our own session has sent an RTP packet. + * This function should be called if our own session has sent an RTP packet. + * For our own SSRC entry, the sender flag is updated based upon outgoing packets instead of incoming packets. + */ + void SentRTPPacket(); - /** Processes a raw packet \c rawpack. - * Processes a raw packet \c rawpack. The instance \c trans will be used to check if this - * packet is one of our own packets. The flag \c acceptownpackets indicates whether own packets should be - * accepted or ignored. - */ - int ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *trans,bool acceptownpackets); + /** Processes a raw packet \c rawpack. + * Processes a raw packet \c rawpack. The instance \c trans will be used to check if this + * packet is one of our own packets. The flag \c acceptownpackets indicates whether own packets should be + * accepted or ignored. + */ + int ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *trans, bool acceptownpackets); - /** Processes a raw packet \c rawpack. - * Processes a raw packet \c rawpack. Every transmitter in the array \c trans of length \c numtrans - * is used to check if the packet is from our own session. The flag \c acceptownpackets indicates - * whether own packets should be accepted or ignored. - */ - int ProcessRawPacket(RTPRawPacket *rawpack,RTPTransmitter *trans[],int numtrans,bool acceptownpackets); + /** Processes a raw packet \c rawpack. + * Processes a raw packet \c rawpack. Every transmitter in the array \c trans of length \c numtrans + * is used to check if the packet is from our own session. The flag \c acceptownpackets indicates + * whether own packets should be accepted or ignored. + */ + int ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *trans[], int numtrans, bool acceptownpackets); - /** Processes an RTPPacket instance \c rtppack which was received at time \c receivetime and - * which originated from \c senderaddres. - * Processes an RTPPacket instance \c rtppack which was received at time \c receivetime and - * which originated from \c senderaddres. The \c senderaddress parameter must be NULL if - * the packet was sent by the local participant. The flag \c stored indicates whether the packet - * was stored in the table or not. If so, the \c rtppack instance may not be deleted. - */ - int ProcessRTPPacket(RTPPacket *rtppack,const RTPTime &receivetime,const RTPAddress *senderaddress,bool *stored); + /** Processes an RTPPacket instance \c rtppack which was received at time \c receivetime and + * which originated from \c senderaddres. + * Processes an RTPPacket instance \c rtppack which was received at time \c receivetime and + * which originated from \c senderaddres. The \c senderaddress parameter must be NULL if + * the packet was sent by the local participant. The flag \c stored indicates whether the packet + * was stored in the table or not. If so, the \c rtppack instance may not be deleted. + */ + int ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, const RTPAddress *senderaddress, bool *stored); - /** Processes the RTCP compound packet \c rtcpcomppack which was received at time \c receivetime from \c senderaddress. - * Processes the RTCP compound packet \c rtcpcomppack which was received at time \c receivetime from \c senderaddress. - * The \c senderaddress parameter must be NULL if the packet was sent by the local participant. - */ - int ProcessRTCPCompoundPacket(RTCPCompoundPacket *rtcpcomppack,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Processes the RTCP compound packet \c rtcpcomppack which was received at time \c receivetime from \c senderaddress. + * Processes the RTCP compound packet \c rtcpcomppack which was received at time \c receivetime from \c senderaddress. + * The \c senderaddress parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessRTCPCompoundPacket(RTCPCompoundPacket *rtcpcomppack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Process the sender information of SSRC \c ssrc into the source table. - * Process the sender information of SSRC \c ssrc into the source table. The information was received - * at time \c receivetime from address \c senderaddress. The \c senderaddress} parameter must be NULL - * if the packet was sent by the local participant. - */ - int ProcessRTCPSenderInfo(uint32_t ssrc,const RTPNTPTime &ntptime,uint32_t rtptime, - uint32_t packetcount,uint32_t octetcount,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Process the sender information of SSRC \c ssrc into the source table. + * Process the sender information of SSRC \c ssrc into the source table. The information was received + * at time \c receivetime from address \c senderaddress. The \c senderaddress} parameter must be NULL + * if the packet was sent by the local participant. + */ + int ProcessRTCPSenderInfo(uint32_t ssrc, const RTPNTPTime &ntptime, uint32_t rtptime, uint32_t packetcount, uint32_t octetcount, const RTPTime &receivetime, + const RTPAddress *senderaddress); /** Processes the report block information which was sent by participant \c ssrc into the source table. - * Processes the report block information which was sent by participant \c ssrc into the source table. - * The information was received at time \c receivetime from address \c senderaddress The \c senderaddress - * parameter must be NULL if the packet was sent by the local participant. - */ - int ProcessRTCPReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t lostpackets, - uint32_t exthighseqnr,uint32_t jitter,uint32_t lsr, - uint32_t dlsr,const RTPTime &receivetime,const RTPAddress *senderaddress); + * Processes the report block information which was sent by participant \c ssrc into the source table. + * The information was received at time \c receivetime from address \c senderaddress The \c senderaddress + * parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessRTCPReportBlock(uint32_t ssrc, uint8_t fractionlost, int32_t lostpackets, uint32_t exthighseqnr, uint32_t jitter, uint32_t lsr, uint32_t dlsr, + const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Processes the non-private SDES item from source \c ssrc into the source table. - * Processes the non-private SDES item from source \c ssrc into the source table. The information was - * received at time \c receivetime from address \c senderaddress. The \c senderaddress parameter must - * be NULL if the packet was sent by the local participant. - */ - int ProcessSDESNormalItem(uint32_t ssrc,RTCPSDESPacket::ItemType t,size_t itemlength, - const void *itemdata,const RTPTime &receivetime,const RTPAddress *senderaddress); + /** Processes the non-private SDES item from source \c ssrc into the source table. + * Processes the non-private SDES item from source \c ssrc into the source table. The information was + * received at time \c receivetime from address \c senderaddress. The \c senderaddress parameter must + * be NULL if the packet was sent by the local participant. + */ + int ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, size_t itemlength, const void *itemdata, const RTPTime &receivetime, const RTPAddress *senderaddress); #ifdef RTP_SUPPORT_SDESPRIV - /** Processes the SDES private item from source \c ssrc into the source table. - * Processes the SDES private item from source \c ssrc into the source table. The information was - * received at time \c receivetime from address \c senderaddress. The \c senderaddress - * parameter must be NULL if the packet was sent by the local participant. - */ - int ProcessSDESPrivateItem(uint32_t ssrc,size_t prefixlen,const void *prefixdata, - size_t valuelen,const void *valuedata,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Processes the SDES private item from source \c ssrc into the source table. + * Processes the SDES private item from source \c ssrc into the source table. The information was + * received at time \c receivetime from address \c senderaddress. The \c senderaddress + * parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessSDESPrivateItem(uint32_t ssrc, size_t prefixlen, const void *prefixdata, size_t valuelen, const void *valuedata, const RTPTime &receivetime, + const RTPAddress *senderaddress); #endif //RTP_SUPPORT_SDESPRIV - /** Processes the BYE message for SSRC \c ssrc. - * Processes the BYE message for SSRC \c ssrc. The information was received at time \c receivetime from - * address \c senderaddress. The \c senderaddress parameter must be NULL if the packet was sent by the - * local participant. - */ - int ProcessBYE(uint32_t ssrc,size_t reasonlength,const void *reasondata,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Processes the BYE message for SSRC \c ssrc. + * Processes the BYE message for SSRC \c ssrc. The information was received at time \c receivetime from + * address \c senderaddress. The \c senderaddress parameter must be NULL if the packet was sent by the + * local participant. + */ + int ProcessBYE(uint32_t ssrc, size_t reasonlength, const void *reasondata, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** If we heard from source \c ssrc, but no actual data was added to the source table (for example, if - * no report block was meant for us), this function can e used to indicate that something was received from - * this source. - * If we heard from source \c ssrc, but no actual data was added to the source table (for example, if - * no report block was meant for us), this function can e used to indicate that something was received from - * this source. This will prevent a premature timeout for this participant. The message was received at time - * \c receivetime from address \c senderaddress. The \c senderaddress parameter must be NULL if the - * packet was sent by the local participant. - */ - int UpdateReceiveTime(uint32_t ssrc,const RTPTime &receivetime,const RTPAddress *senderaddress); + /** If we heard from source \c ssrc, but no actual data was added to the source table (for example, if + * no report block was meant for us), this function can e used to indicate that something was received from + * this source. + * If we heard from source \c ssrc, but no actual data was added to the source table (for example, if + * no report block was meant for us), this function can e used to indicate that something was received from + * this source. This will prevent a premature timeout for this participant. The message was received at time + * \c receivetime from address \c senderaddress. The \c senderaddress parameter must be NULL if the + * packet was sent by the local participant. + */ + int UpdateReceiveTime(uint32_t ssrc, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Starts the iteration over the participants by going to the first member in the table. - * Starts the iteration over the participants by going to the first member in the table. - * If a member was found, the function returns \c true, otherwise it returns \c false. - */ - bool GotoFirstSource(); + /** Starts the iteration over the participants by going to the first member in the table. + * Starts the iteration over the participants by going to the first member in the table. + * If a member was found, the function returns \c true, otherwise it returns \c false. + */ + bool GotoFirstSource(); - /** Sets the current source to be the next source in the table. - * Sets the current source to be the next source in the table. If we're already at the last source, - * the function returns \c false, otherwise it returns \c true. - */ - bool GotoNextSource(); + /** Sets the current source to be the next source in the table. + * Sets the current source to be the next source in the table. If we're already at the last source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoNextSource(); - /** Sets the current source to be the previous source in the table. - * Sets the current source to be the previous source in the table. If we're at the first source, - * the function returns \c false, otherwise it returns \c true. - */ - bool GotoPreviousSource(); + /** Sets the current source to be the previous source in the table. + * Sets the current source to be the previous source in the table. If we're at the first source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoPreviousSource(); - /** Sets the current source to be the first source in the table which has RTPPacket instances - * that we haven't extracted yet. - * Sets the current source to be the first source in the table which has RTPPacket instances - * that we haven't extracted yet. If no such member was found, the function returns \c false, - * otherwise it returns \c true. - */ - bool GotoFirstSourceWithData(); + /** Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoFirstSourceWithData(); - /** Sets the current source to be the next source in the table which has RTPPacket instances that - * we haven't extracted yet. - * Sets the current source to be the next source in the table which has RTPPacket instances that - * we haven't extracted yet. If no such member was found, the function returns \c false, - * otherwise it returns \c true. - */ - bool GotoNextSourceWithData(); + /** Sets the current source to be the next source in the table which has RTPPacket instances that + * we haven't extracted yet. + * Sets the current source to be the next source in the table which has RTPPacket instances that + * we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoNextSourceWithData(); - /** Sets the current source to be the previous source in the table which has RTPPacket instances - * that we haven't extracted yet. - * Sets the current source to be the previous source in the table which has RTPPacket instances - * that we haven't extracted yet. If no such member was found, the function returns \c false, - * otherwise it returns \c true. - */ - bool GotoPreviousSourceWithData(); + /** Sets the current source to be the previous source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the previous source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoPreviousSourceWithData(); - /** Returns the RTPSourceData instance for the currently selected participant. */ - RTPSourceData *GetCurrentSourceInfo(); + /** Returns the RTPSourceData instance for the currently selected participant. */ + RTPSourceData *GetCurrentSourceInfo(); - /** Returns the RTPSourceData instance for the participant identified by \c ssrc, or - * NULL if no such entry exists. - */ - RTPSourceData *GetSourceInfo(uint32_t ssrc); + /** Returns the RTPSourceData instance for the participant identified by \c ssrc, or + * NULL if no such entry exists. + */ + RTPSourceData *GetSourceInfo(uint32_t ssrc); - /** Extracts the next packet from the received packets queue of the current participant. */ - RTPPacket *GetNextPacket(); + /** Extracts the next packet from the received packets queue of the current participant. */ + RTPPacket *GetNextPacket(); - /** Returns \c true if an entry for participant \c ssrc exists and \c false otherwise. */ - bool GotEntry(uint32_t ssrc); + /** Returns \c true if an entry for participant \c ssrc exists and \c false otherwise. */ + bool GotEntry(uint32_t ssrc); - /** If present, it returns the RTPSourceData instance of the entry which was created by CreateOwnSSRC. */ - RTPSourceData *GetOwnSourceInfo() { return (RTPSourceData *)owndata; } + /** If present, it returns the RTPSourceData instance of the entry which was created by CreateOwnSSRC. */ + RTPSourceData *GetOwnSourceInfo() + { + return (RTPSourceData *) owndata; + } - /** Assuming that the current time is \c curtime, time out the members from whom we haven't heard - * during the previous time interval \c timeoutdelay. - */ - void Timeout(const RTPTime &curtime,const RTPTime &timeoutdelay); + /** Assuming that the current time is \c curtime, time out the members from whom we haven't heard + * during the previous time interval \c timeoutdelay. + */ + void Timeout(const RTPTime &curtime, const RTPTime &timeoutdelay); - /** Assuming that the current time is \c curtime, remove the sender flag for senders from whom we haven't - * received any RTP packets during the previous time interval \c timeoutdelay. - */ - void SenderTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay); + /** Assuming that the current time is \c curtime, remove the sender flag for senders from whom we haven't + * received any RTP packets during the previous time interval \c timeoutdelay. + */ + void SenderTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay); - /** Assuming that the current time is \c curtime, remove the members who sent a BYE packet more than - * the time interval \c timeoutdelay ago. - */ - void BYETimeout(const RTPTime &curtime,const RTPTime &timeoutdelay); + /** Assuming that the current time is \c curtime, remove the members who sent a BYE packet more than + * the time interval \c timeoutdelay ago. + */ + void BYETimeout(const RTPTime &curtime, const RTPTime &timeoutdelay); - /** Assuming that the current time is \c curtime, clear the SDES NOTE items which haven't been updated - * during the previous time interval \c timeoutdelay. - */ - void NoteTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay); + /** Assuming that the current time is \c curtime, clear the SDES NOTE items which haven't been updated + * during the previous time interval \c timeoutdelay. + */ + void NoteTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay); - /** Combines the functions SenderTimeout, BYETimeout, Timeout and NoteTimeout. - * Combines the functions SenderTimeout, BYETimeout, Timeout and NoteTimeout. This is more efficient - * than calling all four functions since only one iteration is needed in this function. - */ - void MultipleTimeouts(const RTPTime &curtime,const RTPTime &sendertimeout, - const RTPTime &byetimeout,const RTPTime &generaltimeout, - const RTPTime ¬etimeout); + /** Combines the functions SenderTimeout, BYETimeout, Timeout and NoteTimeout. + * Combines the functions SenderTimeout, BYETimeout, Timeout and NoteTimeout. This is more efficient + * than calling all four functions since only one iteration is needed in this function. + */ + void MultipleTimeouts(const RTPTime &curtime, const RTPTime &sendertimeout, const RTPTime &byetimeout, const RTPTime &generaltimeout, const RTPTime ¬etimeout); - /** Returns the number of participants which are marked as a sender. */ - int GetSenderCount() const { return sendercount; } + /** Returns the number of participants which are marked as a sender. */ + int GetSenderCount() const + { + return sendercount; + } - /** Returns the total number of entries in the source table. */ - int GetTotalCount() const { return totalcount; } + /** Returns the total number of entries in the source table. */ + int GetTotalCount() const + { + return totalcount; + } - /** Returns the number of members which have been validated and which haven't sent a BYE packet yet. */ - int GetActiveMemberCount() const { return activecount; } + /** Returns the number of members which have been validated and which haven't sent a BYE packet yet. */ + int GetActiveMemberCount() const + { + return activecount; + } protected: - /** Is called when an RTP packet is about to be processed. */ - virtual void OnRTPPacket(RTPPacket *pack,const RTPTime &receivetime, const RTPAddress *senderaddress); + /** Is called when an RTP packet is about to be processed. */ + virtual void OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an RTCP compound packet is about to be processed. */ - virtual void OnRTCPCompoundPacket(RTCPCompoundPacket *pack,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Is called when an RTCP compound packet is about to be processed. */ + virtual void OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an SSRC collision was detected. - * Is called when an SSRC collision was detected. The instance \c srcdat is the one present in - * the table, the address \c senderaddress is the one that collided with one of the addresses - * and \c isrtp indicates against which address of \c srcdat the check failed. - */ - virtual void OnSSRCCollision(RTPSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp); + /** Is called when an SSRC collision was detected. + * Is called when an SSRC collision was detected. The instance \c srcdat is the one present in + * the table, the address \c senderaddress is the one that collided with one of the addresses + * and \c isrtp indicates against which address of \c srcdat the check failed. + */ + virtual void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); - /** Is called when another CNAME was received than the one already present for source \c srcdat. */ - virtual void OnCNAMECollision(RTPSourceData *srcdat,const RTPAddress *senderaddress, - const uint8_t *cname,size_t cnamelength); + /** Is called when another CNAME was received than the one already present for source \c srcdat. */ + virtual void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, size_t cnamelength); - /** Is called when a new entry \c srcdat is added to the source table. */ - virtual void OnNewSource(RTPSourceData *srcdat); + /** Is called when a new entry \c srcdat is added to the source table. */ + virtual void OnNewSource(RTPSourceData *srcdat); - /** Is called when the entry \c srcdat is about to be deleted from the source table. */ - virtual void OnRemoveSource(RTPSourceData *srcdat); + /** Is called when the entry \c srcdat is about to be deleted from the source table. */ + virtual void OnRemoveSource(RTPSourceData *srcdat); - /** Is called when participant \c srcdat is timed out. */ - virtual void OnTimeout(RTPSourceData *srcdat); + /** Is called when participant \c srcdat is timed out. */ + virtual void OnTimeout(RTPSourceData *srcdat); - /** Is called when participant \c srcdat is timed after having sent a BYE packet. */ - virtual void OnBYETimeout(RTPSourceData *srcdat); + /** Is called when participant \c srcdat is timed after having sent a BYE packet. */ + virtual void OnBYETimeout(RTPSourceData *srcdat); - /** Is called when a BYE packet has been processed for source \c srcdat. */ - virtual void OnBYEPacket(RTPSourceData *srcdat); + /** Is called when a BYE packet has been processed for source \c srcdat. */ + virtual void OnBYEPacket(RTPSourceData *srcdat); - /** Is called when an RTCP sender report has been processed for this source. */ - virtual void OnRTCPSenderReport(RTPSourceData *srcdat); + /** Is called when an RTCP sender report has been processed for this source. */ + virtual void OnRTCPSenderReport(RTPSourceData *srcdat); - /** Is called when an RTCP receiver report has been processed for this source. */ - virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); + /** Is called when an RTCP receiver report has been processed for this source. */ + virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); - /** Is called when a specific SDES item was received for this source. */ - virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, - const void *itemdata, size_t itemlength); + /** Is called when a specific SDES item was received for this source. */ + virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, size_t itemlength); #ifdef RTP_SUPPORT_SDESPRIV - /** Is called when a specific SDES item of 'private' type was received for this source. */ - virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, - const void *valuedata, size_t valuelen); + /** Is called when a specific SDES item of 'private' type was received for this source. */ + virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, const void *valuedata, size_t valuelen); #endif // RTP_SUPPORT_SDESPRIV + /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime + * from address \c senderaddress. + */ + virtual void OnAPPPacket(RTCPAPPPacket *apppacket, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime - * from address \c senderaddress. - */ - virtual void OnAPPPacket(RTCPAPPPacket *apppacket,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Is called when an unknown RTCP packet type was detected. */ + virtual void OnUnknownPacketType(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an unknown RTCP packet type was detected. */ - virtual void OnUnknownPacketType(RTCPPacket *rtcppack,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Is called when an unknown packet format for a known packet type was detected. */ + virtual void OnUnknownPacketFormat(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); - /** Is called when an unknown packet format for a known packet type was detected. */ - virtual void OnUnknownPacketFormat(RTCPPacket *rtcppack,const RTPTime &receivetime, - const RTPAddress *senderaddress); + /** Is called when the SDES NOTE item for source \c srcdat has been timed out. */ + virtual void OnNoteTimeout(RTPSourceData *srcdat); - /** Is called when the SDES NOTE item for source \c srcdat has been timed out. */ - virtual void OnNoteTimeout(RTPSourceData *srcdat); - - /** Allows you to use an RTP packet from the specified source directly. - * Allows you to use an RTP packet from the specified source directly. If - * `ispackethandled` is set to `true`, the packet will no longer be stored in this - * source's packet list. */ - virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); + /** Allows you to use an RTP packet from the specified source directly. + * Allows you to use an RTP packet from the specified source directly. If + * `ispackethandled` is set to `true`, the packet will no longer be stored in this + * source's packet list. */ + virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); private: - void ClearSourceList(); - int ObtainSourceDataInstance(uint32_t ssrc,RTPInternalSourceData **srcdat,bool *created); - int GetRTCPSourceData(uint32_t ssrc,const RTPAddress *senderaddress,RTPInternalSourceData **srcdat,bool *newsource); - bool CheckCollision(RTPInternalSourceData *srcdat,const RTPAddress *senderaddress,bool isrtp); + void ClearSourceList(); + int ObtainSourceDataInstance(uint32_t ssrc, RTPInternalSourceData **srcdat, bool *created); + int GetRTCPSourceData(uint32_t ssrc, const RTPAddress *senderaddress, RTPInternalSourceData **srcdat, bool *newsource); + bool CheckCollision(RTPInternalSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); - RTPKeyHashTable sourcelist; + RTPKeyHashTable sourcelist; - int sendercount; - int totalcount; - int activecount; + int sendercount; + int totalcount; + int activecount; #ifdef RTP_SUPPORT_PROBATION - ProbationType probationtype; + ProbationType probationtype; #endif // RTP_SUPPORT_PROBATION - RTPInternalSourceData *owndata; + RTPInternalSourceData *owndata; - friend class RTPInternalSourceData; + friend class RTPInternalSourceData; }; // Inlining the default implementations to avoid unused-parameter errors. -inline void RTPSources::OnRTPPacket(RTPPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSources::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSources::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool) { } -inline void RTPSources::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, size_t) { } -inline void RTPSources::OnNewSource(RTPSourceData *) { } -inline void RTPSources::OnRemoveSource(RTPSourceData *) { } -inline void RTPSources::OnTimeout(RTPSourceData *) { } -inline void RTPSources::OnBYETimeout(RTPSourceData *) { } -inline void RTPSources::OnBYEPacket(RTPSourceData *) { } -inline void RTPSources::OnRTCPSenderReport(RTPSourceData *) { } -inline void RTPSources::OnRTCPReceiverReport(RTPSourceData *) { } -inline void RTPSources::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, size_t) { } +inline void RTPSources::OnRTPPacket(RTPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool) +{ +} +inline void RTPSources::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, size_t) +{ +} +inline void RTPSources::OnNewSource(RTPSourceData *) +{ +} +inline void RTPSources::OnRemoveSource(RTPSourceData *) +{ +} +inline void RTPSources::OnTimeout(RTPSourceData *) +{ +} +inline void RTPSources::OnBYETimeout(RTPSourceData *) +{ +} +inline void RTPSources::OnBYEPacket(RTPSourceData *) +{ +} +inline void RTPSources::OnRTCPSenderReport(RTPSourceData *) +{ +} +inline void RTPSources::OnRTCPReceiverReport(RTPSourceData *) +{ +} +inline void RTPSources::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, size_t) +{ +} #ifdef RTP_SUPPORT_SDESPRIV -inline void RTPSources::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, size_t, const void *, size_t) { } +inline void RTPSources::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, size_t, const void *, size_t) +{ +} #endif // RTP_SUPPORT_SDESPRIV -inline void RTPSources::OnAPPPacket(RTCPAPPPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSources::OnUnknownPacketType(RTCPPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSources::OnUnknownPacketFormat(RTCPPacket *, const RTPTime &, const RTPAddress *) { } -inline void RTPSources::OnNoteTimeout(RTPSourceData *) { } -inline void RTPSources::OnValidatedRTPPacket(RTPSourceData *, RTPPacket *, bool, bool *) { } +inline void RTPSources::OnAPPPacket(RTCPAPPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnUnknownPacketType(RTCPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnUnknownPacketFormat(RTCPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnNoteTimeout(RTPSourceData *) +{ +} +inline void RTPSources::OnValidatedRTPPacket(RTPSourceData *, RTPPacket *, bool, bool *) +{ +} } // end namespace diff --git a/qrtplib/rtpstructs.h b/qrtplib/rtpstructs.h index e22a9f2ed..152b08819 100644 --- a/qrtplib/rtpstructs.h +++ b/qrtplib/rtpstructs.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpstructs.h @@ -47,79 +47,79 @@ namespace qrtplib struct RTPHeader { #ifdef RTP_BIG_ENDIAN - uint8_t version:2; - uint8_t padding:1; - uint8_t extension:1; - uint8_t csrccount:4; + uint8_t version:2; + uint8_t padding:1; + uint8_t extension:1; + uint8_t csrccount:4; - uint8_t marker:1; - uint8_t payloadtype:7; + uint8_t marker:1; + uint8_t payloadtype:7; #else // little endian - uint8_t csrccount:4; - uint8_t extension:1; - uint8_t padding:1; - uint8_t version:2; + uint8_t csrccount :4; + uint8_t extension :1; + uint8_t padding :1; + uint8_t version :2; - uint8_t payloadtype:7; - uint8_t marker:1; + uint8_t payloadtype :7; + uint8_t marker :1; #endif // RTP_BIG_ENDIAN - uint16_t sequencenumber; - uint32_t timestamp; - uint32_t ssrc; + uint16_t sequencenumber; + uint32_t timestamp; + uint32_t ssrc; }; struct RTPExtensionHeader { - uint16_t extid; - uint16_t length; + uint16_t extid; + uint16_t length; }; struct RTPSourceIdentifier { - uint32_t ssrc; + uint32_t ssrc; }; struct RTCPCommonHeader { #ifdef RTP_BIG_ENDIAN - uint8_t version:2; - uint8_t padding:1; - uint8_t count:5; + uint8_t version:2; + uint8_t padding:1; + uint8_t count:5; #else // little endian - uint8_t count:5; - uint8_t padding:1; - uint8_t version:2; + uint8_t count :5; + uint8_t padding :1; + uint8_t version :2; #endif // RTP_BIG_ENDIAN - uint8_t packettype; - uint16_t length; + uint8_t packettype; + uint16_t length; }; struct RTCPSenderReport { - uint32_t ntptime_msw; - uint32_t ntptime_lsw; - uint32_t rtptimestamp; - uint32_t packetcount; - uint32_t octetcount; + uint32_t ntptime_msw; + uint32_t ntptime_lsw; + uint32_t rtptimestamp; + uint32_t packetcount; + uint32_t octetcount; }; struct RTCPReceiverReport { - uint32_t ssrc; // Identifies about which SSRC's data this report is... - uint8_t fractionlost; - uint8_t packetslost[3]; - uint32_t exthighseqnr; - uint32_t jitter; - uint32_t lsr; - uint32_t dlsr; + uint32_t ssrc; // Identifies about which SSRC's data this report is... + uint8_t fractionlost; + uint8_t packetslost[3]; + uint32_t exthighseqnr; + uint32_t jitter; + uint32_t lsr; + uint32_t dlsr; }; struct RTCPSDESHeader { - uint8_t sdesid; - uint8_t length; + uint8_t sdesid; + uint8_t length; }; } // end namespace diff --git a/qrtplib/rtptcpaddress.cpp b/qrtplib/rtptcpaddress.cpp index 980ba9661..7ea9ead54 100644 --- a/qrtplib/rtptcpaddress.cpp +++ b/qrtplib/rtptcpaddress.cpp @@ -1,67 +1,65 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtptcpaddress.h" -#include "rtpmemorymanager.h" namespace qrtplib { bool RTPTCPAddress::IsSameAddress(const RTPAddress *addr) const { - if (addr == 0) - return false; - if (addr->GetAddressType() != TCPAddress) - return false; + if (addr == 0) + return false; + if (addr->GetAddressType() != TCPAddress) + return false; - const RTPTCPAddress *a = static_cast(addr); + const RTPTCPAddress *a = static_cast(addr); - // We're using a socket to identify connections - if (a->m_socket == m_socket) - return true; + // We're using a socket to identify connections + if (a->m_socket == m_socket) + return true; - return false; + return false; } bool RTPTCPAddress::IsFromSameHost(const RTPAddress *addr) const { - return IsSameAddress(addr); + return IsSameAddress(addr); } -RTPAddress *RTPTCPAddress::CreateCopy(RTPMemoryManager *mgr) const +RTPAddress *RTPTCPAddress::CreateCopy() const { - JRTPLIB_UNUSED(mgr); // possibly unused - RTPTCPAddress *a = new RTPTCPAddress(m_socket); - return a; + RTPTCPAddress *a = new RTPTCPAddress(m_socket); + return a; } } // end namespace diff --git a/qrtplib/rtptcpaddress.h b/qrtplib/rtptcpaddress.h index b2921a0a3..45806c9b9 100644 --- a/qrtplib/rtptcpaddress.h +++ b/qrtplib/rtptcpaddress.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtptcpaddress.h @@ -45,37 +45,40 @@ namespace qrtplib { - -class RTPMemoryManager; - /** Represents a TCP 'address' and port. * This class is used by the TCP transmission component, to specify which sockets * should be used to send/receive data, and to know on which socket incoming data * was received. */ -class JRTPLIB_IMPORTEXPORT RTPTCPAddress : public RTPAddress +class JRTPLIB_IMPORTEXPORT RTPTCPAddress: public RTPAddress { public: - /** Creates an instance with which you can use a specific socket - * in the TCP transmitter (must be connected). */ - RTPTCPAddress(SocketType sock):RTPAddress(TCPAddress) - { - m_socket = sock; - } + /** Creates an instance with which you can use a specific socket + * in the TCP transmitter (must be connected). */ + RTPTCPAddress(SocketType sock) : + RTPAddress(TCPAddress) + { + m_socket = sock; + } - ~RTPTCPAddress() { } + ~RTPTCPAddress() + { + } - /** Returns the socket that was specified in the constructor. */ - SocketType GetSocket() const { return m_socket; } + /** Returns the socket that was specified in the constructor. */ + SocketType GetSocket() const + { + return m_socket; + } - RTPAddress *CreateCopy(RTPMemoryManager *mgr) const; + RTPAddress *CreateCopy() const; - // Note that these functions are only used for received packets - bool IsSameAddress(const RTPAddress *addr) const; - bool IsFromSameHost(const RTPAddress *addr) const; + // Note that these functions are only used for received packets + bool IsSameAddress(const RTPAddress *addr) const; + bool IsFromSameHost(const RTPAddress *addr) const; private: - SocketType m_socket; + SocketType m_socket; }; } // end namespace diff --git a/qrtplib/rtptcptransmitter.cpp b/qrtplib/rtptcptransmitter.cpp index df441c27e..36796f599 100644 --- a/qrtplib/rtptcptransmitter.cpp +++ b/qrtplib/rtptcptransmitter.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtptcptransmitter.h" #include "rtprawpacket.h" @@ -47,7 +47,7 @@ using namespace std; -#define RTPTCPTRANS_MAXPACKSIZE 65535 +#define RTPTCPTRANS_MAXPACKSIZE 65535 #define MAINMUTEX_LOCK #define MAINMUTEX_UNLOCK @@ -57,489 +57,487 @@ using namespace std; namespace qrtplib { -RTPTCPTransmitter::RTPTCPTransmitter(RTPMemoryManager *mgr) : RTPTransmitter(mgr) +RTPTCPTransmitter::RTPTCPTransmitter() { - m_created = false; - m_init = false; + m_created = false; + m_init = false; } RTPTCPTransmitter::~RTPTCPTransmitter() { - Destroy(); + Destroy(); } int RTPTCPTransmitter::Init(bool tsafe) { - if (m_init) - return ERR_RTP_TCPTRANS_ALREADYINIT; + if (m_init) + return ERR_RTP_TCPTRANS_ALREADYINIT; - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; - m_maxPackSize = RTPTCPTRANS_MAXPACKSIZE; - m_init = true; - return 0; + m_maxPackSize = RTPTCPTRANS_MAXPACKSIZE; + m_init = true; + return 0; } int RTPTCPTransmitter::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) { - JRTPLIB_UNUSED(maximumpacketsize); - const RTPTCPTransmissionParams *params,defaultparams; - int status; + JRTPLIB_UNUSED(maximumpacketsize); + const RTPTCPTransmissionParams *params, defaultparams; + int status; - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK + MAINMUTEX_LOCK - if (m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_ALREADYCREATED; - } + if (m_created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_ALREADYCREATED; + } - // Obtain transmission parameters + // Obtain transmission parameters - if (transparams == 0) - params = &defaultparams; - else - { - if (transparams->GetTransmissionProtocol() != RTPTransmitter::TCPProto) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_ILLEGALPARAMETERS; - } - params = static_cast(transparams); - } + if (transparams == 0) + params = &defaultparams; + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::TCPProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_TCPTRANS_ILLEGALPARAMETERS; + } + params = static_cast(transparams); + } - if (!params->GetCreatedAbortDescriptors()) - { - if ((status = m_abortDesc.Init()) < 0) - { - MAINMUTEX_UNLOCK - return status; - } - m_pAbortDesc = &m_abortDesc; - } - else - { - m_pAbortDesc = params->GetCreatedAbortDescriptors(); - if (!m_pAbortDesc->IsInitialized()) - { - MAINMUTEX_UNLOCK - return ERR_RTP_ABORTDESC_NOTINIT; - } - } + if (!params->GetCreatedAbortDescriptors()) + { + if ((status = m_abortDesc.Init()) < 0) + { + MAINMUTEX_UNLOCK + return status; + } + m_pAbortDesc = &m_abortDesc; + } + else + { + m_pAbortDesc = params->GetCreatedAbortDescriptors(); + if (!m_pAbortDesc->IsInitialized()) + { + MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; + } + } - m_waitingForData = false; - m_created = true; - MAINMUTEX_UNLOCK - return 0; + m_waitingForData = false; + m_created = true; + MAINMUTEX_UNLOCK + return 0; } void RTPTCPTransmitter::Destroy() { - if (!m_init) - return; + if (!m_init) + return; - MAINMUTEX_LOCK - if (!m_created) - { - MAINMUTEX_UNLOCK; - return; - } + MAINMUTEX_LOCK + if (!m_created) + { + MAINMUTEX_UNLOCK; + return; + } - ClearDestSockets(); - FlushPackets(); - m_created = false; + ClearDestSockets(); + FlushPackets(); + m_created = false; - if (m_waitingForData) - { - m_pAbortDesc->SendAbortSignal(); - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK - } - else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized + if (m_waitingForData) + { + m_pAbortDesc->SendAbortSignal(); + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK// to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK +} +else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK +MAINMUTEX_UNLOCK } RTPTransmissionInfo *RTPTCPTransmitter::GetTransmissionInfo() { - if (!m_init) - return 0; +if (!m_init) +return 0; - MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = new RTPTCPTransmissionInfo(); - MAINMUTEX_UNLOCK - return tinf; +MAINMUTEX_LOCK +RTPTransmissionInfo *tinf = new RTPTCPTransmissionInfo(); +MAINMUTEX_UNLOCK +return tinf; } void RTPTCPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) { - if (!m_init) - return; +if (!m_init) +return; - RTPDelete(i, GetMemoryManager()); +delete i; } -int RTPTCPTransmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) +int RTPTCPTransmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) { - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; +if (!m_init) +return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK - if (!m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTCREATED; - } +MAINMUTEX_LOCK +if (!m_created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTCREATED; +} - if (m_localHostname.size() == 0) - { - // - // TODO - // TODO - // TODO - // TODO - // - m_localHostname.resize(9); - memcpy(&m_localHostname[0], "localhost", m_localHostname.size()); - } +if (m_localHostname.size() == 0) +{ +// +// TODO +// TODO +// TODO +// TODO +// +m_localHostname.resize(9); +memcpy(&m_localHostname[0], "localhost", m_localHostname.size()); +} - if ((*bufferlength) < m_localHostname.size()) - { - *bufferlength = m_localHostname.size(); // tell the application the required size of the buffer - MAINMUTEX_UNLOCK - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } +if ((*bufferlength) < m_localHostname.size()) +{ +*bufferlength = m_localHostname.size(); // tell the application the required size of the buffer +MAINMUTEX_UNLOCK +return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; +} - memcpy(buffer,&m_localHostname[0], m_localHostname.size()); - *bufferlength = m_localHostname.size(); +memcpy(buffer, &m_localHostname[0], m_localHostname.size()); +*bufferlength = m_localHostname.size(); - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; } bool RTPTCPTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) { - if (!m_init) - return false; +if (!m_init) +return false; - if (addr == 0) - return false; +if (addr == 0) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!m_created) - return false; +if (!m_created) +return false; - if (addr->GetAddressType() != RTPAddress::TCPAddress) - return false; +if (addr->GetAddressType() != RTPAddress::TCPAddress) +return false; - const RTPTCPAddress *pAddr = static_cast(addr); - bool v = false; +bool v = false; - JRTPLIB_UNUSED(pAddr); - // TODO: for now, we're assuming that we can't just send to the same transmitter + // TODO: for now, we're assuming that we can't just send to the same transmitter - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } int RTPTCPTransmitter::Poll() { - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; +if (!m_init) +return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK - if (!m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTCREATED; - } - - std::map::iterator it = m_destSockets.begin(); - std::map::iterator end = m_destSockets.end(); - int status = 0; - - vector errSockets; - - while (it != end) - { - SocketType sock = it->first; - status = PollSocket(sock, it->second); - if (status < 0) - { - // Stop immediately on out of memory - if (status == ERR_RTP_OUTOFMEM) - break; - else - { - errSockets.push_back(sock); - // Don't let this count as an error (due to a closed connection for example), - // otherwise the poll thread (if used) will stop because of this. Since there - // may be more than one connection, that's not desirable in general. - status = 0; - } - } - ++it; - } - MAINMUTEX_UNLOCK - - for (size_t i = 0 ; i < errSockets.size() ; i++) - OnReceiveError(errSockets[i]); - - return status; +MAINMUTEX_LOCK +if (!m_created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTCREATED; } -int RTPTCPTransmitter::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +std::map::iterator it = m_destSockets.begin(); +std::map::iterator end = m_destSockets.end(); +int status = 0; + +vector errSockets; + +while (it != end) { - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; +SocketType sock = it->first; +status = PollSocket(sock, it->second); +if (status < 0) +{ + // Stop immediately on out of memory + if (status == ERR_RTP_OUTOFMEM) + break; + else + { + errSockets.push_back(sock); + // Don't let this count as an error (due to a closed connection for example), + // otherwise the poll thread (if used) will stop because of this. Since there + // may be more than one connection, that's not desirable in general. + status = 0; + } +} +++it; +} +MAINMUTEX_UNLOCK - MAINMUTEX_LOCK +for (size_t i = 0; i < errSockets.size(); i++) +OnReceiveError(errSockets[i]); - if (!m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTCREATED; - } - if (m_waitingForData) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_ALREADYWAITING; - } +return status; +} - m_tmpSocks.resize(m_destSockets.size()+1); - m_tmpFlags.resize(m_tmpSocks.size()); - SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); +int RTPTCPTransmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) +{ +if (!m_init) +return ERR_RTP_TCPTRANS_NOTINIT; - std::map::iterator it = m_destSockets.begin(); - std::map::iterator end = m_destSockets.end(); - int idx = 0; +MAINMUTEX_LOCK - while (it != end) - { - m_tmpSocks[idx] = it->first; - m_tmpFlags[idx] = 0; - ++it; - idx++; - } - m_tmpSocks[idx] = abortSocket; - m_tmpFlags[idx] = 0; - int idxAbort = idx; +if (!m_created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTCREATED; +} +if (m_waitingForData) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_ALREADYWAITING; +} - m_waitingForData = true; +m_tmpSocks.resize(m_destSockets.size() + 1); +m_tmpFlags.resize(m_tmpSocks.size()); +SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); - WAITMUTEX_LOCK - MAINMUTEX_UNLOCK +std::map::iterator it = m_destSockets.begin(); +std::map::iterator end = m_destSockets.end(); +int idx = 0; - //cout << "Waiting for " << delay.GetDouble() << " seconds for data on " << m_tmpSocks.size() << " sockets" << endl; - int status = RTPSelect(&m_tmpSocks[0], &m_tmpFlags[0], m_tmpSocks.size(), delay); - if (status < 0) - { - MAINMUTEX_LOCK - m_waitingForData = false; - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return status; - } +while (it != end) +{ +m_tmpSocks[idx] = it->first; +m_tmpFlags[idx] = 0; +++it; +idx++; +} +m_tmpSocks[idx] = abortSocket; +m_tmpFlags[idx] = 0; +int idxAbort = idx; - MAINMUTEX_LOCK - m_waitingForData = false; - if (!m_created) // destroy called - { - MAINMUTEX_UNLOCK; - WAITMUTEX_UNLOCK - return 0; - } +m_waitingForData = true; - // if aborted, read from abort buffer - if (m_tmpFlags[idxAbort]) - m_pAbortDesc->ReadSignallingByte(); +WAITMUTEX_LOCK +MAINMUTEX_UNLOCK - if (dataavailable != 0) - { - bool avail = false; + //cout << "Waiting for " << delay.GetDouble() << " seconds for data on " << m_tmpSocks.size() << " sockets" << endl; +int status = RTPSelect(&m_tmpSocks[0], &m_tmpFlags[0], m_tmpSocks.size(), delay); +if (status < 0) +{ +MAINMUTEX_LOCK +m_waitingForData = false; +MAINMUTEX_UNLOCK +WAITMUTEX_UNLOCK +return status; +} - for (size_t i = 0 ; i < m_tmpFlags.size() ; i++) - { - if (m_tmpFlags[i]) - { - avail = true; - //cout << "Data available!" << endl; - break; - } - } +MAINMUTEX_LOCK +m_waitingForData = false; +if (!m_created) // destroy called +{ +MAINMUTEX_UNLOCK; +WAITMUTEX_UNLOCK +return 0; +} - if (avail) - *dataavailable = true; - else - *dataavailable = false; - } + // if aborted, read from abort buffer +if (m_tmpFlags[idxAbort]) +m_pAbortDesc->ReadSignallingByte(); - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return 0; +if (dataavailable != 0) +{ +bool avail = false; + +for (size_t i = 0; i < m_tmpFlags.size(); i++) +{ + if (m_tmpFlags[i]) + { + avail = true; + //cout << "Data available!" << endl; + break; + } +} + +if (avail) + *dataavailable = true; +else + *dataavailable = false; +} + +MAINMUTEX_UNLOCK +WAITMUTEX_UNLOCK +return 0; } int RTPTCPTransmitter::AbortWait() { - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; +if (!m_init) +return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK - if (!m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTCREATED; - } - if (!m_waitingForData) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTWAITING; - } - - m_pAbortDesc->SendAbortSignal(); - - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!m_created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTCREATED; +} +if (!m_waitingForData) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTWAITING; } -int RTPTCPTransmitter::SendRTPData(const void *data,size_t len) -{ - return SendRTPRTCPData(data, len); +m_pAbortDesc->SendAbortSignal(); + +MAINMUTEX_UNLOCK +return 0; } -int RTPTCPTransmitter::SendRTCPData(const void *data,size_t len) +int RTPTCPTransmitter::SendRTPData(const void *data, size_t len) { - return SendRTPRTCPData(data, len); +return SendRTPRTCPData(data, len); +} + +int RTPTCPTransmitter::SendRTCPData(const void *data, size_t len) +{ +return SendRTPRTCPData(data, len); } int RTPTCPTransmitter::AddDestination(const RTPAddress &addr) { - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; +if (!m_init) +return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTCREATED; - } +if (!m_created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTCREATED; +} - if (addr.GetAddressType() != RTPAddress::TCPAddress) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; - } +if (addr.GetAddressType() != RTPAddress::TCPAddress) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; +} - const RTPTCPAddress &a = static_cast(addr); - SocketType s = a.GetSocket(); - if (s == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; - } +const RTPTCPAddress &a = static_cast(addr); +SocketType s = a.GetSocket(); +if (s == 0) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; +} - int status = ValidateSocket(s); - if (status != 0) - { - MAINMUTEX_UNLOCK - return status; - } +int status = ValidateSocket(s); +if (status != 0) +{ +MAINMUTEX_UNLOCK +return status; +} - std::map::iterator it = m_destSockets.find(s); - if (it != m_destSockets.end()) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS; - } - m_destSockets[s] = SocketData(); +std::map::iterator it = m_destSockets.find(s); +if (it != m_destSockets.end()) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS; +} +m_destSockets[s] = SocketData(); - // Because the sockets are also used for incoming data, we'll abort a wait - // that may be in progress, otherwise it could take a few seconds until the - // new socket is monitored for incoming data - m_pAbortDesc->SendAbortSignal(); + // Because the sockets are also used for incoming data, we'll abort a wait + // that may be in progress, otherwise it could take a few seconds until the + // new socket is monitored for incoming data +m_pAbortDesc->SendAbortSignal(); - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; } int RTPTCPTransmitter::DeleteDestination(const RTPAddress &addr) { - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; +if (!m_init) +return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTCREATED; - } +if (!m_created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTCREATED; +} - if (addr.GetAddressType() != RTPAddress::TCPAddress) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; - } +if (addr.GetAddressType() != RTPAddress::TCPAddress) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; +} - const RTPTCPAddress &a = static_cast(addr); - SocketType s = a.GetSocket(); - if (s == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; - } +const RTPTCPAddress &a = static_cast(addr); +SocketType s = a.GetSocket(); +if (s == 0) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; +} - std::map::iterator it = m_destSockets.find(s); - if (it == m_destSockets.end()) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS; - } +std::map::iterator it = m_destSockets.find(s); +if (it == m_destSockets.end()) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS; +} - // Clean up possibly allocated memory - uint8_t *pBuf = it->second.ExtractDataBuffer(); - if (pBuf) - RTPDeleteByteArray(pBuf, GetMemoryManager()); + // Clean up possibly allocated memory +uint8_t *pBuf = it->second.ExtractDataBuffer(); +if (pBuf) +delete[] pBuf; - m_destSockets.erase(it); +m_destSockets.erase(it); - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; } void RTPTCPTransmitter::ClearDestinations() { - if (!m_init) - return; +if (!m_init) +return; - MAINMUTEX_LOCK - if (m_created) - ClearDestSockets(); - MAINMUTEX_UNLOCK +MAINMUTEX_LOCK +if (m_created) +ClearDestSockets(); +MAINMUTEX_UNLOCK } bool RTPTCPTransmitter::SupportsMulticasting() { - return false; +return false; } int RTPTCPTransmitter::JoinMulticastGroup(const RTPAddress &) { - return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; +return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; } int RTPTCPTransmitter::LeaveMulticastGroup(const RTPAddress &) { - return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; +return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; } void RTPTCPTransmitter::LeaveAllMulticastGroups() @@ -548,19 +546,19 @@ void RTPTCPTransmitter::LeaveAllMulticastGroups() int RTPTCPTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) { - if (m != RTPTransmitter::AcceptAll) - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; - return 0; +if (m != RTPTransmitter::AcceptAll) +return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +return 0; } int RTPTCPTransmitter::AddToIgnoreList(const RTPAddress &) { - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; } int RTPTCPTransmitter::DeleteFromIgnoreList(const RTPAddress &) { - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; } void RTPTCPTransmitter::ClearIgnoreList() @@ -569,12 +567,12 @@ void RTPTCPTransmitter::ClearIgnoreList() int RTPTCPTransmitter::AddToAcceptList(const RTPAddress &) { - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; } int RTPTCPTransmitter::DeleteFromAcceptList(const RTPAddress &) { - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; +return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; } void RTPTCPTransmitter::ClearAcceptList() @@ -583,320 +581,318 @@ void RTPTCPTransmitter::ClearAcceptList() int RTPTCPTransmitter::SetMaximumPacketSize(size_t s) { - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; +if (!m_init) +return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK - if (!m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTCREATED; - } - if (s > RTPTCPTRANS_MAXPACKSIZE) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; - } - m_maxPackSize = s; - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!m_created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTCREATED; +} +if (s > RTPTCPTRANS_MAXPACKSIZE) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; +} +m_maxPackSize = s; +MAINMUTEX_UNLOCK +return 0; } bool RTPTCPTransmitter::NewDataAvailable() { - if (!m_init) - return false; +if (!m_init) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - bool v; +bool v; - if (!m_created) - v = false; - else - { - if (m_rawpacketlist.empty()) - v = false; - else - v = true; - } +if (!m_created) +v = false; +else +{ +if (m_rawpacketlist.empty()) +v = false; +else +v = true; +} - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } RTPRawPacket *RTPTCPTransmitter::GetNextPacket() { - if (!m_init) - return 0; +if (!m_init) +return 0; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - RTPRawPacket *p; +RTPRawPacket *p; - if (!m_created) - { - MAINMUTEX_UNLOCK - return 0; - } - if (m_rawpacketlist.empty()) - { - MAINMUTEX_UNLOCK - return 0; - } +if (!m_created) +{ +MAINMUTEX_UNLOCK +return 0; +} +if (m_rawpacketlist.empty()) +{ +MAINMUTEX_UNLOCK +return 0; +} - p = *(m_rawpacketlist.begin()); - m_rawpacketlist.pop_front(); +p = *(m_rawpacketlist.begin()); +m_rawpacketlist.pop_front(); - MAINMUTEX_UNLOCK - return p; +MAINMUTEX_UNLOCK +return p; } // Here the private functions start... void RTPTCPTransmitter::FlushPackets() { - std::list::const_iterator it; +std::list::const_iterator it; - for (it = m_rawpacketlist.begin() ; it != m_rawpacketlist.end() ; ++it) - RTPDelete(*it,GetMemoryManager()); - m_rawpacketlist.clear(); +for (it = m_rawpacketlist.begin(); it != m_rawpacketlist.end(); ++it) +delete *it; +m_rawpacketlist.clear(); } int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata) { #ifdef RTP_SOCKETTYPE_WINSOCK - unsigned long len; +unsigned long len; #else - size_t len; +size_t len; #endif // RTP_SOCKETTYPE_WINSOCK - bool dataavailable; +bool dataavailable; - do - { - len = 0; - RTPIOCTL(sock, FIONREAD, &len); +do +{ +len = 0; +RTPIOCTL(sock, FIONREAD, &len); - if (len <= 0) - dataavailable = false; - else - dataavailable = true; +if (len <= 0) +dataavailable = false; +else +dataavailable = true; - if (dataavailable) - { - RTPTime curtime = RTPTime::CurrentTime(); - int relevantLen = RTPTCPTRANS_MAXPACKSIZE+2; +if (dataavailable) +{ +RTPTime curtime = RTPTime::CurrentTime(); +int relevantLen = RTPTCPTRANS_MAXPACKSIZE + 2; - if ((int)len < relevantLen) - relevantLen = (int)len; +if ((int) len < relevantLen) + relevantLen = (int) len; - bool complete = false; - int status = sdata.ProcessAvailableBytes(sock, relevantLen, complete, GetMemoryManager()); - if (status < 0) - return status; +bool complete = false; +int status = sdata.ProcessAvailableBytes(sock, relevantLen, complete); +if (status < 0) + return status; - if (complete) - { - uint8_t *pBuf = sdata.ExtractDataBuffer(); - if (pBuf) - { - int dataLength = sdata.m_dataLength; - sdata.Reset(); +if (complete) +{ + uint8_t *pBuf = sdata.ExtractDataBuffer(); + if (pBuf) + { + int dataLength = sdata.m_dataLength; + sdata.Reset(); - RTPTCPAddress *pAddr = new RTPTCPAddress(sock); - if (pAddr == 0) - return ERR_RTP_OUTOFMEM; + RTPTCPAddress *pAddr = new RTPTCPAddress(sock); + if (pAddr == 0) + return ERR_RTP_OUTOFMEM; - bool isrtp = true; - if (dataLength > (int)sizeof(RTCPCommonHeader)) - { - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *)pBuf; - uint8_t packettype = rtcpheader->packettype; + bool isrtp = true; + if (dataLength > (int) sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) pBuf; + uint8_t packettype = rtcpheader->packettype; - if (packettype >= 200 && packettype <= 204) - isrtp = false; - } + if (packettype >= 200 && packettype <= 204) + isrtp = false; + } - RTPRawPacket *pPack = new RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp, GetMemoryManager()); - if (pPack == 0) - { - RTPDelete(pAddr,GetMemoryManager()); - RTPDeleteByteArray(pBuf,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - m_rawpacketlist.push_back(pPack); - } - } - } - } while (dataavailable); + RTPRawPacket *pPack = new RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp); + if (pPack == 0) + { + delete pAddr; + delete[] pBuf; + return ERR_RTP_OUTOFMEM; + } + m_rawpacketlist.push_back(pPack); + } +} +} +} while (dataavailable); - return 0; +return 0; } int RTPTCPTransmitter::SendRTPRTCPData(const void *data, size_t len) { - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; +if (!m_init) +return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!m_created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_NOTCREATED; - } - if (len > RTPTCPTRANS_MAXPACKSIZE) - { - MAINMUTEX_UNLOCK - return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; - } +if (!m_created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_NOTCREATED; +} +if (len > RTPTCPTRANS_MAXPACKSIZE) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; +} - std::map::iterator it = m_destSockets.begin(); - std::map::iterator end = m_destSockets.end(); +std::map::iterator it = m_destSockets.begin(); +std::map::iterator end = m_destSockets.end(); - vector errSockets; - int flags = 0; +vector errSockets; +int flags = 0; #ifdef RTP_HAVE_MSG_NOSIGNAL - flags = MSG_NOSIGNAL; +flags = MSG_NOSIGNAL; #endif // RTP_HAVE_MSG_NOSIGNAL - while (it != end) - { - uint8_t lengthBytes[2] = { (uint8_t)((len >> 8)&0xff), (uint8_t)(len&0xff) }; - SocketType sock = it->first; +while (it != end) +{ +uint8_t lengthBytes[2] = +{ (uint8_t) ((len >> 8) & 0xff), (uint8_t) (len & 0xff) }; +SocketType sock = it->first; - if (send(sock,(const char *)lengthBytes,2,flags) < 0 || - send(sock,(const char *)data,len,flags) < 0) - errSockets.push_back(sock); - ++it; - } +if (send(sock, (const char *) lengthBytes, 2, flags) < 0 || send(sock, (const char *) data, len, flags) < 0) +errSockets.push_back(sock); +++it; +} - MAINMUTEX_UNLOCK +MAINMUTEX_UNLOCK - if (errSockets.size() != 0) - { - for (size_t i = 0 ; i < errSockets.size() ; i++) - OnSendError(errSockets[i]); - } +if (errSockets.size() != 0) +{ +for (size_t i = 0; i < errSockets.size(); i++) +OnSendError(errSockets[i]); +} - // Don't return an error code to avoid the poll thread exiting - // due to one closed connection for example + // Don't return an error code to avoid the poll thread exiting + // due to one closed connection for example - return 0; +return 0; } int RTPTCPTransmitter::ValidateSocket(SocketType) { - // TODO: should we even do a check (for a TCP socket)? - return 0; + // TODO: should we even do a check (for a TCP socket)? +return 0; } void RTPTCPTransmitter::ClearDestSockets() { - std::map::iterator it = m_destSockets.begin(); - std::map::iterator end = m_destSockets.end(); +std::map::iterator it = m_destSockets.begin(); +std::map::iterator end = m_destSockets.end(); - while (it != end) - { - uint8_t *pBuf = it->second.ExtractDataBuffer(); - if (pBuf) - RTPDeleteByteArray(pBuf, GetMemoryManager()); +while (it != end) +{ +uint8_t *pBuf = it->second.ExtractDataBuffer(); +if (pBuf) +delete[] pBuf; - ++it; - } - m_destSockets.clear(); +++it; +} +m_destSockets.clear(); } RTPTCPTransmitter::SocketData::SocketData() { - Reset(); +Reset(); } void RTPTCPTransmitter::SocketData::Reset() { - m_lengthBufferOffset = 0; - m_dataLength = 0; - m_dataBufferOffset = 0; - m_pDataBuffer = 0; +m_lengthBufferOffset = 0; +m_dataLength = 0; +m_dataBufferOffset = 0; +m_pDataBuffer = 0; } RTPTCPTransmitter::SocketData::~SocketData() { - assert(m_pDataBuffer == 0); // Should be deleted externally to avoid storing a memory manager in the class +assert(m_pDataBuffer == 0); // Should be deleted externally to avoid storing a memory manager in the class } -int RTPTCPTransmitter::SocketData::ProcessAvailableBytes(SocketType sock, int availLen, bool &complete, RTPMemoryManager *pMgr) +int RTPTCPTransmitter::SocketData::ProcessAvailableBytes(SocketType sock, int availLen, bool &complete) { - JRTPLIB_UNUSED(pMgr); // possibly unused - const int numLengthBuffer = 2; - if (m_lengthBufferOffset < numLengthBuffer) // first we need to get the length - { - assert(m_pDataBuffer == 0); - int num = numLengthBuffer-m_lengthBufferOffset; - if (num > availLen) - num = availLen; +const int numLengthBuffer = 2; +if (m_lengthBufferOffset < numLengthBuffer) // first we need to get the length +{ +assert(m_pDataBuffer == 0); +int num = numLengthBuffer - m_lengthBufferOffset; +if (num > availLen) +num = availLen; - int r = 0; - if (num > 0) - { - r = (int)recv(sock, (char *)(m_lengthBuffer+m_lengthBufferOffset), num, 0); - if (r < 0) - return ERR_RTP_TCPTRANS_ERRORINRECV; - } +int r = 0; +if (num > 0) +{ +r = (int) recv(sock, (char *) (m_lengthBuffer + m_lengthBufferOffset), num, 0); +if (r < 0) + return ERR_RTP_TCPTRANS_ERRORINRECV; +} - m_lengthBufferOffset += r; - availLen -= r; +m_lengthBufferOffset += r; +availLen -= r; - assert(m_lengthBufferOffset <= numLengthBuffer); - if (m_lengthBufferOffset == numLengthBuffer) // we can constuct a length - { - int l = 0; - for (int i = numLengthBuffer-1, shift = 0 ; i >= 0 ; i--, shift += 8) - l |= ((int)m_lengthBuffer[i]) << shift; +assert(m_lengthBufferOffset <= numLengthBuffer); +if (m_lengthBufferOffset == numLengthBuffer) // we can constuct a length +{ +int l = 0; +for (int i = numLengthBuffer - 1, shift = 0; i >= 0; i--, shift += 8) + l |= ((int) m_lengthBuffer[i]) << shift; - m_dataLength = l; - m_dataBufferOffset = 0; +m_dataLength = l; +m_dataBufferOffset = 0; - //cout << "Expecting " << m_dataLength << " bytes" << endl; +//cout << "Expecting " << m_dataLength << " bytes" << endl; - // avoid allocation of length 0 - if (l == 0) - l = 1; +// avoid allocation of length 0 +if (l == 0) + l = 1; - // We don't yet know if it's an RTP or RTCP packet, so we'll stick to RTP - m_pDataBuffer = new uint8_t[l]; - if (m_pDataBuffer == 0) - return ERR_RTP_OUTOFMEM; - } - } +// We don't yet know if it's an RTP or RTCP packet, so we'll stick to RTP +m_pDataBuffer = new uint8_t[l]; +if (m_pDataBuffer == 0) + return ERR_RTP_OUTOFMEM; +} +} - if (m_lengthBufferOffset == numLengthBuffer && m_pDataBuffer) // the last one is to make sure we didn't run out of memory - { - if (m_dataBufferOffset < m_dataLength) - { - int num = m_dataLength-m_dataBufferOffset; - if (num > availLen) - num = availLen; +if (m_lengthBufferOffset == numLengthBuffer && m_pDataBuffer) // the last one is to make sure we didn't run out of memory +{ +if (m_dataBufferOffset < m_dataLength) +{ +int num = m_dataLength - m_dataBufferOffset; +if (num > availLen) + num = availLen; - int r = 0; - if (num > 0) - { - r = (int)recv(sock, (char *)(m_pDataBuffer+m_dataBufferOffset), num, 0); - if (r < 0) - return ERR_RTP_TCPTRANS_ERRORINRECV; - } +int r = 0; +if (num > 0) +{ + r = (int) recv(sock, (char *) (m_pDataBuffer + m_dataBufferOffset), num, 0); + if (r < 0) + return ERR_RTP_TCPTRANS_ERRORINRECV; +} - m_dataBufferOffset += r; - availLen -= r; - } +m_dataBufferOffset += r; +availLen -= r; +} - if (m_dataBufferOffset == m_dataLength) - complete = true; - } - return 0; +if (m_dataBufferOffset == m_dataLength) +complete = true; +} +return 0; } } // end namespace - diff --git a/qrtplib/rtptcptransmitter.h b/qrtplib/rtptcptransmitter.h index 092ab37d2..9dd2ff744 100644 --- a/qrtplib/rtptcptransmitter.h +++ b/qrtplib/rtptcptransmitter.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtptcptransmitter.h @@ -50,35 +50,47 @@ namespace qrtplib { /** Parameters for the TCP transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPTCPTransmissionParams : public RTPTransmissionParams +class JRTPLIB_IMPORTEXPORT RTPTCPTransmissionParams: public RTPTransmissionParams { public: - RTPTCPTransmissionParams(); + RTPTCPTransmissionParams(); - /** If non null, the specified abort descriptors will be used to cancel - * the function that's waiting for packets to arrive; set to null (the default) - * to let the transmitter create its own instance. */ - void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) { m_pAbortDesc = desc; } + /** If non null, the specified abort descriptors will be used to cancel + * the function that's waiting for packets to arrive; set to null (the default) + * to let the transmitter create its own instance. */ + void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) + { + m_pAbortDesc = desc; + } - /** If non-null, this RTPAbortDescriptors instance will be used internally, - * which can be useful when creating your own poll thread for multiple - * sessions. */ - RTPAbortDescriptors *GetCreatedAbortDescriptors() const { return m_pAbortDesc; } + /** If non-null, this RTPAbortDescriptors instance will be used internally, + * which can be useful when creating your own poll thread for multiple + * sessions. */ + RTPAbortDescriptors *GetCreatedAbortDescriptors() const + { + return m_pAbortDesc; + } private: - RTPAbortDescriptors *m_pAbortDesc; + RTPAbortDescriptors *m_pAbortDesc; }; -inline RTPTCPTransmissionParams::RTPTCPTransmissionParams() : RTPTransmissionParams(RTPTransmitter::TCPProto) +inline RTPTCPTransmissionParams::RTPTCPTransmissionParams() : + RTPTransmissionParams(RTPTransmitter::TCPProto) { - m_pAbortDesc = 0; + m_pAbortDesc = 0; } /** Additional information about the TCP transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPTCPTransmissionInfo : public RTPTransmissionInfo +class JRTPLIB_IMPORTEXPORT RTPTCPTransmissionInfo: public RTPTransmissionInfo { public: - RTPTCPTransmissionInfo() : RTPTransmissionInfo(RTPTransmitter::TCPProto) { } - ~RTPTCPTransmissionInfo() { } + RTPTCPTransmissionInfo() : + RTPTransmissionInfo(RTPTransmitter::TCPProto) + { + } + ~RTPTCPTransmissionInfo() + { + } }; // TODO: this is for IPv4, and will only be valid if one rtp packet is in one tcp frame @@ -108,94 +120,106 @@ public: class JRTPLIB_IMPORTEXPORT RTPTCPTransmitter : public RTPTransmitter { public: - RTPTCPTransmitter(RTPMemoryManager *mgr); - ~RTPTCPTransmitter(); + RTPTCPTransmitter(); + ~RTPTCPTransmitter(); - int Init(bool treadsafe); - int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + int Init(bool treadsafe); + int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() { return RTPTCPTRANS_HEADERSIZE; } + int GetLocalHostName(uint8_t *buffer, size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() + { + return RTPTCPTRANS_HEADERSIZE; + } - int Poll(); - int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); - int AbortWait(); + int Poll(); + int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); + int AbortWait(); - int SendRTPData(const void *data,size_t len); - int SendRTCPData(const void *data,size_t len); + int SendRTPData(const void *data, size_t len); + int SendRTCPData(const void *data, size_t len); - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); protected: - /** By overriding this function you can be notified of an error when sending over a socket. */ - virtual void OnSendError(SocketType sock); - /** By overriding this function you can be notified of an error when receiving from a socket. */ - virtual void OnReceiveError(SocketType sock); + /** By overriding this function you can be notified of an error when sending over a socket. */ + virtual void OnSendError(SocketType sock); + /** By overriding this function you can be notified of an error when receiving from a socket. */ + virtual void OnReceiveError(SocketType sock); private: - class SocketData - { - public: - SocketData(); - ~SocketData(); - void Reset(); + class SocketData + { + public: + SocketData(); + ~SocketData(); + void Reset(); - uint8_t m_lengthBuffer[2]; - int m_lengthBufferOffset; - int m_dataLength; - int m_dataBufferOffset; - uint8_t *m_pDataBuffer; + uint8_t m_lengthBuffer[2]; + int m_lengthBufferOffset; + int m_dataLength; + int m_dataBufferOffset; + uint8_t *m_pDataBuffer; - uint8_t *ExtractDataBuffer() { uint8_t *pTmp = m_pDataBuffer; m_pDataBuffer = 0; return pTmp; } - int ProcessAvailableBytes(SocketType sock, int availLen, bool &complete, RTPMemoryManager *pMgr); - }; + uint8_t *ExtractDataBuffer() + { + uint8_t *pTmp = m_pDataBuffer; + m_pDataBuffer = 0; + return pTmp; + } + int ProcessAvailableBytes(SocketType sock, int availLen, bool &complete); + }; - int SendRTPRTCPData(const void *data,size_t len); - void FlushPackets(); - int PollSocket(SocketType sock, SocketData &sdata); - void ClearDestSockets(); - int ValidateSocket(SocketType s); + int SendRTPRTCPData(const void *data, size_t len); + void FlushPackets(); + int PollSocket(SocketType sock, SocketData &sdata); + void ClearDestSockets(); + int ValidateSocket(SocketType s); - bool m_init; - bool m_created; - bool m_waitingForData; + bool m_init; + bool m_created; + bool m_waitingForData; - std::map m_destSockets; - std::vector m_tmpSocks; - std::vector m_tmpFlags; - std::vector m_localHostname; - size_t m_maxPackSize; + std::map m_destSockets; + std::vector m_tmpSocks; + std::vector m_tmpFlags; + std::vector m_localHostname; + size_t m_maxPackSize; - std::list m_rawpacketlist; + std::list m_rawpacketlist; - RTPAbortDescriptors m_abortDesc; - RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified + RTPAbortDescriptors m_abortDesc; + RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified }; -inline void RTPTCPTransmitter::OnSendError(SocketType) { } -inline void RTPTCPTransmitter::OnReceiveError(SocketType) { } +inline void RTPTCPTransmitter::OnSendError(SocketType) +{ +} +inline void RTPTCPTransmitter::OnReceiveError(SocketType) +{ +} } // end namespace diff --git a/qrtplib/rtptimeutilities.cpp b/qrtplib/rtptimeutilities.cpp index fabd89222..d664ae30c 100644 --- a/qrtplib/rtptimeutilities.cpp +++ b/qrtplib/rtptimeutilities.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpconfig.h" #include "rtptimeutilities.h" @@ -38,9 +38,9 @@ namespace qrtplib RTPTimeInitializerObject::RTPTimeInitializerObject() { - RTPTime curtime = RTPTime::CurrentTime(); - JRTPLIB_UNUSED(curtime); - dummy = -1; + RTPTime curtime = RTPTime::CurrentTime(); + JRTPLIB_UNUSED(curtime); + dummy = -1; } RTPTimeInitializerObject timeinit; diff --git a/qrtplib/rtptimeutilities.h b/qrtplib/rtptimeutilities.h index e180fc63b..c4cb8faea 100644 --- a/qrtplib/rtptimeutilities.h +++ b/qrtplib/rtptimeutilities.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtptimeutilities.h @@ -41,9 +41,9 @@ #include "rtpconfig.h" #include "rtptypes.h" #ifndef RTP_HAVE_QUERYPERFORMANCECOUNTER - #include - #include - #include +#include +#include +#include #endif // RTP_HAVE_QUERYPERFORMANCECOUNTER #define RTP_NTPTIMEOFFSET 2208988800UL @@ -66,16 +66,26 @@ namespace qrtplib class JRTPLIB_IMPORTEXPORT RTPNTPTime { public: - /** This constructor creates and instance with MSW \c m and LSW \c l. */ - RTPNTPTime(uint32_t m,uint32_t l) { msw = m ; lsw = l; } + /** This constructor creates and instance with MSW \c m and LSW \c l. */ + RTPNTPTime(uint32_t m, uint32_t l) + { + msw = m; + lsw = l; + } - /** Returns the most significant word. */ - uint32_t GetMSW() const { return msw; } + /** Returns the most significant word. */ + uint32_t GetMSW() const + { + return msw; + } - /** Returns the least significant word. */ - uint32_t GetLSW() const { return lsw; } + /** Returns the least significant word. */ + uint32_t GetLSW() const + { + return lsw; + } private: - uint32_t msw,lsw; + uint32_t msw, lsw; }; /** This class is used to specify wallclock time, delay intervals etc. @@ -85,176 +95,183 @@ private: class JRTPLIB_IMPORTEXPORT RTPTime { public: - /** Returns an RTPTime instance representing the current wallclock time. - * Returns an RTPTime instance representing the current wallclock time. This is expressed - * as a number of seconds since 00:00:00 UTC, January 1, 1970. - */ - static RTPTime CurrentTime(); + /** Returns an RTPTime instance representing the current wallclock time. + * Returns an RTPTime instance representing the current wallclock time. This is expressed + * as a number of seconds since 00:00:00 UTC, January 1, 1970. + */ + static RTPTime CurrentTime(); - /** This function waits the amount of time specified in \c delay. */ - static void Wait(const RTPTime &delay); + /** This function waits the amount of time specified in \c delay. */ + static void Wait(const RTPTime &delay); - /** Creates an RTPTime instance representing \c t, which is expressed in units of seconds. */ - RTPTime(double t); + /** Creates an RTPTime instance representing \c t, which is expressed in units of seconds. */ + RTPTime(double t); - /** Creates an instance that corresponds to \c ntptime. - * Creates an instance that corresponds to \c ntptime. If - * the conversion cannot be made, both the seconds and the - * microseconds are set to zero. - */ - RTPTime(RTPNTPTime ntptime); + /** Creates an instance that corresponds to \c ntptime. + * Creates an instance that corresponds to \c ntptime. If + * the conversion cannot be made, both the seconds and the + * microseconds are set to zero. + */ + RTPTime(RTPNTPTime ntptime); - /** Creates an instance corresponding to \c seconds and \c microseconds. */ - RTPTime(int64_t seconds, uint32_t microseconds); + /** Creates an instance corresponding to \c seconds and \c microseconds. */ + RTPTime(int64_t seconds, uint32_t microseconds); - /** Returns the number of seconds stored in this instance. */ - int64_t GetSeconds() const; + /** Returns the number of seconds stored in this instance. */ + int64_t GetSeconds() const; - /** Returns the number of microseconds stored in this instance. */ - uint32_t GetMicroSeconds() const; + /** Returns the number of microseconds stored in this instance. */ + uint32_t GetMicroSeconds() const; - /** Returns the time stored in this instance, expressed in units of seconds. */ - double GetDouble() const { return m_t; } + /** Returns the time stored in this instance, expressed in units of seconds. */ + double GetDouble() const + { + return m_t; + } - /** Returns the NTP time corresponding to the time stored in this instance. */ - RTPNTPTime GetNTPTime() const; + /** Returns the NTP time corresponding to the time stored in this instance. */ + RTPNTPTime GetNTPTime() const; - RTPTime &operator-=(const RTPTime &t); - RTPTime &operator+=(const RTPTime &t); - bool operator<(const RTPTime &t) const; - bool operator>(const RTPTime &t) const; - bool operator<=(const RTPTime &t) const; - bool operator>=(const RTPTime &t) const; + RTPTime &operator-=(const RTPTime &t); + RTPTime &operator+=(const RTPTime &t); + bool operator<(const RTPTime &t) const; + bool operator>(const RTPTime &t) const; + bool operator<=(const RTPTime &t) const; + bool operator>=(const RTPTime &t) const; - bool IsZero() const { return m_t == 0; } + bool IsZero() const + { + return m_t == 0; + } private: #ifdef RTP_HAVE_QUERYPERFORMANCECOUNTER - static inline uint64_t CalculateMicroseconds(uint64_t performancecount,uint64_t performancefrequency); + static inline uint64_t CalculateMicroseconds(uint64_t performancecount,uint64_t performancefrequency); #endif // RTP_HAVE_QUERYPERFORMANCECOUNTER - double m_t; + double m_t; }; inline RTPTime::RTPTime(double t) { - m_t = t; + m_t = t; } inline RTPTime::RTPTime(int64_t seconds, uint32_t microseconds) { - if (seconds >= 0) - { - m_t = (double)seconds + 1e-6*(double)microseconds; - } - else - { - int64_t possec = -seconds; + if (seconds >= 0) + { + m_t = (double) seconds + 1e-6 * (double) microseconds; + } + else + { + int64_t possec = -seconds; - m_t = (double)possec + 1e-6*(double)microseconds; - m_t = -m_t; - } + m_t = (double) possec + 1e-6 * (double) microseconds; + m_t = -m_t; + } } inline RTPTime::RTPTime(RTPNTPTime ntptime) { - if (ntptime.GetMSW() < RTP_NTPTIMEOFFSET) - { - m_t = 0; - } - else - { - uint32_t sec = ntptime.GetMSW() - RTP_NTPTIMEOFFSET; + if (ntptime.GetMSW() < RTP_NTPTIMEOFFSET) + { + m_t = 0; + } + else + { + uint32_t sec = ntptime.GetMSW() - RTP_NTPTIMEOFFSET; - double x = (double)ntptime.GetLSW(); - x /= (65536.0*65536.0); - x *= 1000000.0; - uint32_t microsec = (uint32_t)x; + double x = (double) ntptime.GetLSW(); + x /= (65536.0 * 65536.0); + x *= 1000000.0; + uint32_t microsec = (uint32_t) x; - m_t = (double)sec + 1e-6*(double)microsec; - } + m_t = (double) sec + 1e-6 * (double) microsec; + } } inline int64_t RTPTime::GetSeconds() const { - return (int64_t)m_t; + return (int64_t) m_t; } inline uint32_t RTPTime::GetMicroSeconds() const { - uint32_t microsec; + uint32_t microsec; - if (m_t >= 0) - { - int64_t sec = (int64_t)m_t; - microsec = (uint32_t)(1e6*(m_t - (double)sec) + 0.5); - } - else // m_t < 0 - { - int64_t sec = (int64_t)(-m_t); - microsec = (uint32_t)(1e6*((-m_t) - (double)sec) + 0.5); - } + if (m_t >= 0) + { + int64_t sec = (int64_t) m_t; + microsec = (uint32_t) (1e6 * (m_t - (double) sec) + 0.5); + } + else // m_t < 0 + { + int64_t sec = (int64_t) (-m_t); + microsec = (uint32_t) (1e6 * ((-m_t) - (double) sec) + 0.5); + } - if (microsec >= 1000000) - return 999999; - // Unsigned, it can never be less than 0 - // if (microsec < 0) - // return 0; - return microsec; + if (microsec >= 1000000) + return 999999; + // Unsigned, it can never be less than 0 + // if (microsec < 0) + // return 0; + return microsec; } #ifdef RTP_HAVE_QUERYPERFORMANCECOUNTER inline uint64_t RTPTime::CalculateMicroseconds(uint64_t performancecount,uint64_t performancefrequency) { - uint64_t f = performancefrequency; - uint64_t a = performancecount; - uint64_t b = a/f; - uint64_t c = a%f; // a = b*f+c => (a*1000000)/f = b*1000000+(c*1000000)/f + uint64_t f = performancefrequency; + uint64_t a = performancecount; + uint64_t b = a/f; + uint64_t c = a%f; // a = b*f+c => (a*1000000)/f = b*1000000+(c*1000000)/f - return b*C1000000+(c*C1000000)/f; + return b*C1000000+(c*C1000000)/f; } inline RTPTime RTPTime::CurrentTime() { - static int inited = 0; - static uint64_t microseconds, initmicroseconds; - static LARGE_INTEGER performancefrequency; + static int inited = 0; + static uint64_t microseconds, initmicroseconds; + static LARGE_INTEGER performancefrequency; - uint64_t emulate_microseconds, microdiff; - SYSTEMTIME systemtime; - FILETIME filetime; + uint64_t emulate_microseconds, microdiff; + SYSTEMTIME systemtime; + FILETIME filetime; - LARGE_INTEGER performancecount; + LARGE_INTEGER performancecount; - QueryPerformanceCounter(&performancecount); + QueryPerformanceCounter(&performancecount); - if(!inited){ - inited = 1; - QueryPerformanceFrequency(&performancefrequency); - GetSystemTime(&systemtime); - SystemTimeToFileTime(&systemtime,&filetime); - microseconds = ( ((uint64_t)(filetime.dwHighDateTime) << 32) + (uint64_t)(filetime.dwLowDateTime) ) / (uint64_t)10; - microseconds-= CEPOCH; // EPOCH - initmicroseconds = CalculateMicroseconds(performancecount.QuadPart, performancefrequency.QuadPart); - } + if(!inited) + { + inited = 1; + QueryPerformanceFrequency(&performancefrequency); + GetSystemTime(&systemtime); + SystemTimeToFileTime(&systemtime,&filetime); + microseconds = ( ((uint64_t)(filetime.dwHighDateTime) << 32) + (uint64_t)(filetime.dwLowDateTime) ) / (uint64_t)10; + microseconds-= CEPOCH; // EPOCH + initmicroseconds = CalculateMicroseconds(performancecount.QuadPart, performancefrequency.QuadPart); + } - emulate_microseconds = CalculateMicroseconds(performancecount.QuadPart, performancefrequency.QuadPart); + emulate_microseconds = CalculateMicroseconds(performancecount.QuadPart, performancefrequency.QuadPart); - microdiff = emulate_microseconds - initmicroseconds; + microdiff = emulate_microseconds - initmicroseconds; - double t = 1e-6*(double)(microseconds + microdiff); - return RTPTime(t); + double t = 1e-6*(double)(microseconds + microdiff); + return RTPTime(t); } inline void RTPTime::Wait(const RTPTime &delay) { - if (delay.m_t <= 0) - return; + if (delay.m_t <= 0) + return; - uint64_t sec = (uint64_t)delay.m_t; - uint32_t microsec = (uint32_t)(1e6*(delay.m_t-(double)sec)); - DWORD t = ((DWORD)sec)*1000+(((DWORD)microsec)/1000); - Sleep(t); + uint64_t sec = (uint64_t)delay.m_t; + uint32_t microsec = (uint32_t)(1e6*(delay.m_t-(double)sec)); + DWORD t = ((DWORD)sec)*1000+(((DWORD)microsec)/1000); + Sleep(t); } #else // unix style @@ -262,132 +279,134 @@ inline void RTPTime::Wait(const RTPTime &delay) #ifdef RTP_HAVE_CLOCK_GETTIME inline double RTPTime_timespecToDouble(struct timespec &ts) { - return (double)ts.tv_sec + 1e-9*(double)ts.tv_nsec; + return (double) ts.tv_sec + 1e-9 * (double) ts.tv_nsec; } inline RTPTime RTPTime::CurrentTime() { - static bool s_initialized = false; - static double s_startOffet = 0; + static bool s_initialized = false; + static double s_startOffet = 0; - if (!s_initialized) - { - s_initialized = true; + if (!s_initialized) + { + s_initialized = true; - // Get the corresponding times in system time and monotonic time - struct timespec tpSys, tpMono; + // Get the corresponding times in system time and monotonic time + struct timespec tpSys, tpMono; - clock_gettime(CLOCK_REALTIME, &tpSys); - clock_gettime(CLOCK_MONOTONIC, &tpMono); + clock_gettime(CLOCK_REALTIME, &tpSys); + clock_gettime(CLOCK_MONOTONIC, &tpMono); - double tSys = RTPTime_timespecToDouble(tpSys); - double tMono = RTPTime_timespecToDouble(tpMono); + double tSys = RTPTime_timespecToDouble(tpSys); + double tMono = RTPTime_timespecToDouble(tpMono); - s_startOffet = tSys - tMono; - return tSys; - } + s_startOffet = tSys - tMono; + return tSys; + } - struct timespec tpMono; - clock_gettime(CLOCK_MONOTONIC, &tpMono); + struct timespec tpMono; + clock_gettime(CLOCK_MONOTONIC, &tpMono); - double tMono0 = RTPTime_timespecToDouble(tpMono); - return tMono0 + s_startOffet; + double tMono0 = RTPTime_timespecToDouble(tpMono); + return tMono0 + s_startOffet; } #else // gettimeofday fallback inline RTPTime RTPTime::CurrentTime() { - struct timeval tv; + struct timeval tv; - gettimeofday(&tv,0); - return RTPTime((uint64_t)tv.tv_sec,(uint32_t)tv.tv_usec); + gettimeofday(&tv,0); + return RTPTime((uint64_t)tv.tv_sec,(uint32_t)tv.tv_usec); } #endif // RTP_HAVE_CLOCK_GETTIME inline void RTPTime::Wait(const RTPTime &delay) { - if (delay.m_t <= 0) - return; + if (delay.m_t <= 0) + return; - uint64_t sec = (uint64_t)delay.m_t; - uint64_t nanosec = (uint32_t)(1e9*(delay.m_t-(double)sec)); + uint64_t sec = (uint64_t) delay.m_t; + uint64_t nanosec = (uint32_t) (1e9 * (delay.m_t - (double) sec)); - struct timespec req,rem; - int ret; + struct timespec req, rem; + int ret; - req.tv_sec = (time_t)sec; - req.tv_nsec = ((long)nanosec); - do - { - ret = nanosleep(&req,&rem); - req = rem; - } while (ret == -1 && errno == EINTR); + req.tv_sec = (time_t) sec; + req.tv_nsec = ((long) nanosec); + do + { + ret = nanosleep(&req, &rem); + req = rem; + } while (ret == -1 && errno == EINTR); } #endif // RTP_HAVE_QUERYPERFORMANCECOUNTER inline RTPTime &RTPTime::operator-=(const RTPTime &t) { - m_t -= t.m_t; - return *this; + m_t -= t.m_t; + return *this; } inline RTPTime &RTPTime::operator+=(const RTPTime &t) { - m_t += t.m_t; - return *this; + m_t += t.m_t; + return *this; } inline RTPNTPTime RTPTime::GetNTPTime() const { - uint32_t sec = (uint32_t)m_t; - uint32_t microsec = (uint32_t)((m_t - (double)sec)*1e6); + uint32_t sec = (uint32_t) m_t; + uint32_t microsec = (uint32_t) ((m_t - (double) sec) * 1e6); - uint32_t msw = sec+RTP_NTPTIMEOFFSET; - uint32_t lsw; - double x; + uint32_t msw = sec + RTP_NTPTIMEOFFSET; + uint32_t lsw; + double x; - x = microsec/1000000.0; - x *= (65536.0*65536.0); - lsw = (uint32_t)x; + x = microsec / 1000000.0; + x *= (65536.0 * 65536.0); + lsw = (uint32_t) x; - return RTPNTPTime(msw,lsw); + return RTPNTPTime(msw, lsw); } inline bool RTPTime::operator<(const RTPTime &t) const { - return m_t < t.m_t; + return m_t < t.m_t; } inline bool RTPTime::operator>(const RTPTime &t) const { - return m_t > t.m_t; + return m_t > t.m_t; } inline bool RTPTime::operator<=(const RTPTime &t) const { - return m_t <= t.m_t; + return m_t <= t.m_t; } inline bool RTPTime::operator>=(const RTPTime &t) const { - return m_t >= t.m_t; + return m_t >= t.m_t; } class JRTPLIB_IMPORTEXPORT RTPTimeInitializerObject { public: - RTPTimeInitializerObject(); - void Dummy() { dummy++; } + RTPTimeInitializerObject(); + void Dummy() + { + dummy++; + } private: - int dummy; + int dummy; }; extern RTPTimeInitializerObject timeinit; } // end namespace - #endif // RTPTIMEUTILITIES_H diff --git a/qrtplib/rtptransmitter.h b/qrtplib/rtptransmitter.h index bb6463a2c..1a83edf02 100644 --- a/qrtplib/rtptransmitter.h +++ b/qrtplib/rtptransmitter.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtptransmitter.h @@ -40,7 +40,6 @@ #include "rtpconfig.h" #include "rtptypes.h" -#include "rtpmemoryobject.h" #include "rtptimeutilities.h" namespace qrtplib @@ -59,162 +58,167 @@ class RTPTransmissionInfo; * an UDP over IPv4 transmitter, an UDP over IPv6 transmitter and a transmitter * which can be used to use an external transmission mechanism. */ -class JRTPLIB_IMPORTEXPORT RTPTransmitter : public RTPMemoryObject +class JRTPLIB_IMPORTEXPORT RTPTransmitter { public: - /** Used to identify a specific transmitter. - * If UserDefinedProto is used in the RTPSession::Create function, the RTPSession - * virtual member function NewUserDefinedTransmitter will be called to create - * a transmission component. - */ - enum TransmissionProtocol - { - IPv4UDPProto, /**< Specifies the internal UDP over IPv4 transmitter. */ - IPv6UDPProto, /**< Specifies the internal UDP over IPv6 transmitter. */ - TCPProto, /**< Specifies the internal TCP transmitter. */ - ExternalProto, /**< Specifies the transmitter which can send packets using an external mechanism, and which can have received packets injected into it - see RTPExternalTransmitter for additional information. */ - UserDefinedProto /**< Specifies a user defined, external transmitter. */ - }; + /** Used to identify a specific transmitter. + * If UserDefinedProto is used in the RTPSession::Create function, the RTPSession + * virtual member function NewUserDefinedTransmitter will be called to create + * a transmission component. + */ + enum TransmissionProtocol + { + IPv4UDPProto, /**< Specifies the internal UDP over IPv4 transmitter. */ + IPv6UDPProto, /**< Specifies the internal UDP over IPv6 transmitter. */ + TCPProto, /**< Specifies the internal TCP transmitter. */ + ExternalProto, /**< Specifies the transmitter which can send packets using an external mechanism, and which can have received packets injected into it - see RTPExternalTransmitter for additional information. */ + UserDefinedProto /**< Specifies a user defined, external transmitter. */ + }; - /** Three kind of receive modes can be specified. */ - enum ReceiveMode - { - AcceptAll, /**< All incoming data is accepted, no matter where it originated from. */ - AcceptSome, /**< Only data coming from specific sources will be accepted. */ - IgnoreSome /**< All incoming data is accepted, except for data coming from a specific set of sources. */ - }; + /** Three kind of receive modes can be specified. */ + enum ReceiveMode + { + AcceptAll, /**< All incoming data is accepted, no matter where it originated from. */ + AcceptSome, /**< Only data coming from specific sources will be accepted. */ + IgnoreSome /**< All incoming data is accepted, except for data coming from a specific set of sources. */ + }; protected: - /** Constructor in which you can specify a memory manager to use. */ - RTPTransmitter(RTPMemoryManager *mgr) : RTPMemoryObject(mgr) { timeinit.Dummy(); } + /** Constructor in which you can specify a memory manager to use. */ + RTPTransmitter() + { + timeinit.Dummy(); + } public: - virtual ~RTPTransmitter() { } + virtual ~RTPTransmitter() + { + } - /** This function must be called before the transmission component can be used. - * This function must be called before the transmission component can be used. Depending on - * the value of \c threadsafe, the component will be created for thread-safe usage or not. - */ - virtual int Init(bool threadsafe) = 0; + /** This function must be called before the transmission component can be used. + * This function must be called before the transmission component can be used. Depending on + * the value of \c threadsafe, the component will be created for thread-safe usage or not. + */ + virtual int Init(bool threadsafe) = 0; - /** Prepares the component to be used. - * Prepares the component to be used. The parameter \c maxpacksize specifies the maximum size - * a packet can have: if the packet is larger it will not be transmitted. The \c transparams - * parameter specifies a pointer to an RTPTransmissionParams instance. This is also an abstract - * class and each actual component will define its own parameters by inheriting a class - * from RTPTransmissionParams. If \c transparams is NULL, the default transmission parameters - * for the component will be used. - */ - virtual int Create(size_t maxpacksize,const RTPTransmissionParams *transparams) = 0; + /** Prepares the component to be used. + * Prepares the component to be used. The parameter \c maxpacksize specifies the maximum size + * a packet can have: if the packet is larger it will not be transmitted. The \c transparams + * parameter specifies a pointer to an RTPTransmissionParams instance. This is also an abstract + * class and each actual component will define its own parameters by inheriting a class + * from RTPTransmissionParams. If \c transparams is NULL, the default transmission parameters + * for the component will be used. + */ + virtual int Create(size_t maxpacksize, const RTPTransmissionParams *transparams) = 0; - /** By calling this function, buffers are cleared and the component cannot be used anymore. - * By calling this function, buffers are cleared and the component cannot be used anymore. - * Only when the Create function is called again can the component be used again. */ - virtual void Destroy() = 0; + /** By calling this function, buffers are cleared and the component cannot be used anymore. + * By calling this function, buffers are cleared and the component cannot be used anymore. + * Only when the Create function is called again can the component be used again. */ + virtual void Destroy() = 0; - /** Returns additional information about the transmitter. - * This function returns an instance of a subclass of RTPTransmissionInfo which will give - * some additional information about the transmitter (a list of local IP addresses for example). - * Currently, either an instance of RTPUDPv4TransmissionInfo or RTPUDPv6TransmissionInfo is - * returned, depending on the type of the transmitter. The user has to deallocate the returned - * instance when it is no longer needed, which can be done using RTPTransmitter::DeleteTransmissionInfo. - */ - virtual RTPTransmissionInfo *GetTransmissionInfo() = 0; + /** Returns additional information about the transmitter. + * This function returns an instance of a subclass of RTPTransmissionInfo which will give + * some additional information about the transmitter (a list of local IP addresses for example). + * Currently, either an instance of RTPUDPv4TransmissionInfo or RTPUDPv6TransmissionInfo is + * returned, depending on the type of the transmitter. The user has to deallocate the returned + * instance when it is no longer needed, which can be done using RTPTransmitter::DeleteTransmissionInfo. + */ + virtual RTPTransmissionInfo *GetTransmissionInfo() = 0; - /** Deallocates the information returned by RTPTransmitter::GetTransmissionInfo . - * Deallocates the information returned by RTPTransmitter::GetTransmissionInfo . - */ - virtual void DeleteTransmissionInfo(RTPTransmissionInfo *inf) = 0; + /** Deallocates the information returned by RTPTransmitter::GetTransmissionInfo . + * Deallocates the information returned by RTPTransmitter::GetTransmissionInfo . + */ + virtual void DeleteTransmissionInfo(RTPTransmissionInfo *inf) = 0; - /** Looks up the local host name. - * Looks up the local host name based upon internal information about the local host's - * addresses. This function might take some time since a DNS query might be done. \c bufferlength - * should initially contain the number of bytes that may be stored in \c buffer. If the function - * succeeds, \c bufferlength is set to the number of bytes stored in \c buffer. Note that the data - * in \c buffer is not NULL-terminated. If the function fails because the buffer isn't large enough, - * it returns \c ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL and stores the number of bytes needed in - * \c bufferlength. - */ - virtual int GetLocalHostName(uint8_t *buffer,size_t *bufferlength) = 0; + /** Looks up the local host name. + * Looks up the local host name based upon internal information about the local host's + * addresses. This function might take some time since a DNS query might be done. \c bufferlength + * should initially contain the number of bytes that may be stored in \c buffer. If the function + * succeeds, \c bufferlength is set to the number of bytes stored in \c buffer. Note that the data + * in \c buffer is not NULL-terminated. If the function fails because the buffer isn't large enough, + * it returns \c ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL and stores the number of bytes needed in + * \c bufferlength. + */ + virtual int GetLocalHostName(uint8_t *buffer, size_t *bufferlength) = 0; - /** Returns \c true if the address specified by \c addr is one of the addresses of the transmitter. */ - virtual bool ComesFromThisTransmitter(const RTPAddress *addr) = 0; + /** Returns \c true if the address specified by \c addr is one of the addresses of the transmitter. */ + virtual bool ComesFromThisTransmitter(const RTPAddress *addr) = 0; - /** Returns the amount of bytes that will be added to the RTP packet by the underlying layers (excluding - * the link layer). */ - virtual size_t GetHeaderOverhead() = 0; + /** Returns the amount of bytes that will be added to the RTP packet by the underlying layers (excluding + * the link layer). */ + virtual size_t GetHeaderOverhead() = 0; - /** Checks for incoming data and stores it. */ - virtual int Poll() = 0; + /** Checks for incoming data and stores it. */ + virtual int Poll() = 0; - /** Waits until incoming data is detected. - * Waits at most a time \c delay until incoming data has been detected. If \c dataavailable is not NULL, - * it should be set to \c true if data was actually read and to \c false otherwise. - */ - virtual int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0) = 0; + /** Waits until incoming data is detected. + * Waits at most a time \c delay until incoming data has been detected. If \c dataavailable is not NULL, + * it should be set to \c true if data was actually read and to \c false otherwise. + */ + virtual int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0) = 0; - /** If the previous function has been called, this one aborts the waiting. */ - virtual int AbortWait() = 0; + /** If the previous function has been called, this one aborts the waiting. */ + virtual int AbortWait() = 0; - /** Send a packet with length \c len containing \c data to all RTP addresses of the current destination list. */ - virtual int SendRTPData(const void *data,size_t len) = 0; + /** Send a packet with length \c len containing \c data to all RTP addresses of the current destination list. */ + virtual int SendRTPData(const void *data, size_t len) = 0; - /** Send a packet with length \c len containing \c data to all RTCP addresses of the current destination list. */ - virtual int SendRTCPData(const void *data,size_t len) = 0; + /** Send a packet with length \c len containing \c data to all RTCP addresses of the current destination list. */ + virtual int SendRTCPData(const void *data, size_t len) = 0; - /** Adds the address specified by \c addr to the list of destinations. */ - virtual int AddDestination(const RTPAddress &addr) = 0; + /** Adds the address specified by \c addr to the list of destinations. */ + virtual int AddDestination(const RTPAddress &addr) = 0; - /** Deletes the address specified by \c addr from the list of destinations. */ - virtual int DeleteDestination(const RTPAddress &addr) = 0; + /** Deletes the address specified by \c addr from the list of destinations. */ + virtual int DeleteDestination(const RTPAddress &addr) = 0; - /** Clears the list of destinations. */ - virtual void ClearDestinations() = 0; + /** Clears the list of destinations. */ + virtual void ClearDestinations() = 0; - /** Returns \c true if the transmission component supports multicasting. */ - virtual bool SupportsMulticasting() = 0; + /** Returns \c true if the transmission component supports multicasting. */ + virtual bool SupportsMulticasting() = 0; - /** Joins the multicast group specified by \c addr. */ - virtual int JoinMulticastGroup(const RTPAddress &addr) = 0; + /** Joins the multicast group specified by \c addr. */ + virtual int JoinMulticastGroup(const RTPAddress &addr) = 0; - /** Leaves the multicast group specified by \c addr. */ - virtual int LeaveMulticastGroup(const RTPAddress &addr) = 0; + /** Leaves the multicast group specified by \c addr. */ + virtual int LeaveMulticastGroup(const RTPAddress &addr) = 0; - /** Leaves all the multicast groups that have been joined. */ - virtual void LeaveAllMulticastGroups() = 0; + /** Leaves all the multicast groups that have been joined. */ + virtual void LeaveAllMulticastGroups() = 0; - /** Sets the receive mode. - * Sets the receive mode to \c m, which is one of the following: RTPTransmitter::AcceptAll, - * RTPTransmitter::AcceptSome or RTPTransmitter::IgnoreSome. Note that if the receive - * mode is changed, all information about the addresses to ignore to accept is lost. - */ - virtual int SetReceiveMode(RTPTransmitter::ReceiveMode m) = 0; + /** Sets the receive mode. + * Sets the receive mode to \c m, which is one of the following: RTPTransmitter::AcceptAll, + * RTPTransmitter::AcceptSome or RTPTransmitter::IgnoreSome. Note that if the receive + * mode is changed, all information about the addresses to ignore to accept is lost. + */ + virtual int SetReceiveMode(RTPTransmitter::ReceiveMode m) = 0; - /** Adds \c addr to the list of addresses to ignore. */ - virtual int AddToIgnoreList(const RTPAddress &addr) = 0; + /** Adds \c addr to the list of addresses to ignore. */ + virtual int AddToIgnoreList(const RTPAddress &addr) = 0; - /** Deletes \c addr from the list of addresses to accept. */ - virtual int DeleteFromIgnoreList(const RTPAddress &addr)= 0; + /** Deletes \c addr from the list of addresses to accept. */ + virtual int DeleteFromIgnoreList(const RTPAddress &addr)= 0; - /** Clears the list of addresses to ignore. */ - virtual void ClearIgnoreList() = 0; + /** Clears the list of addresses to ignore. */ + virtual void ClearIgnoreList() = 0; - /** Adds \c addr to the list of addresses to accept. */ - virtual int AddToAcceptList(const RTPAddress &addr) = 0; + /** Adds \c addr to the list of addresses to accept. */ + virtual int AddToAcceptList(const RTPAddress &addr) = 0; - /** Deletes \c addr from the list of addresses to accept. */ - virtual int DeleteFromAcceptList(const RTPAddress &addr) = 0; + /** Deletes \c addr from the list of addresses to accept. */ + virtual int DeleteFromAcceptList(const RTPAddress &addr) = 0; - /** Clears the list of addresses to accept. */ - virtual void ClearAcceptList() = 0; + /** Clears the list of addresses to accept. */ + virtual void ClearAcceptList() = 0; - /** Sets the maximum packet size which the transmitter should allow to \c s. */ - virtual int SetMaximumPacketSize(size_t s) = 0; + /** Sets the maximum packet size which the transmitter should allow to \c s. */ + virtual int SetMaximumPacketSize(size_t s) = 0; - /** Returns \c true if packets can be obtained using the GetNextPacket member function. */ - virtual bool NewDataAvailable() = 0; + /** Returns \c true if packets can be obtained using the GetNextPacket member function. */ + virtual bool NewDataAvailable() = 0; - /** Returns the raw data of a received RTP packet (received during the Poll function) - * in an RTPRawPacket instance. */ - virtual RTPRawPacket *GetNextPacket() = 0; + /** Returns the raw data of a received RTP packet (received during the Poll function) + * in an RTPRawPacket instance. */ + virtual RTPRawPacket *GetNextPacket() = 0; }; /** Base class for transmission parameters. @@ -226,14 +230,22 @@ public: class JRTPLIB_IMPORTEXPORT RTPTransmissionParams { protected: - RTPTransmissionParams(RTPTransmitter::TransmissionProtocol p) { protocol = p; } + RTPTransmissionParams(RTPTransmitter::TransmissionProtocol p) + { + protocol = p; + } public: - virtual ~RTPTransmissionParams() { } + virtual ~RTPTransmissionParams() + { + } - /** Returns the transmitter type for which these parameters are valid. */ - RTPTransmitter::TransmissionProtocol GetTransmissionProtocol() const { return protocol; } + /** Returns the transmitter type for which these parameters are valid. */ + RTPTransmitter::TransmissionProtocol GetTransmissionProtocol() const + { + return protocol; + } private: - RTPTransmitter::TransmissionProtocol protocol; + RTPTransmitter::TransmissionProtocol protocol; }; /** Base class for additional information about the transmitter. @@ -245,13 +257,21 @@ private: class JRTPLIB_IMPORTEXPORT RTPTransmissionInfo { protected: - RTPTransmissionInfo(RTPTransmitter::TransmissionProtocol p) { protocol = p; } + RTPTransmissionInfo(RTPTransmitter::TransmissionProtocol p) + { + protocol = p; + } public: - virtual ~RTPTransmissionInfo() { } - /** Returns the transmitter type for which these parameters are valid. */ - RTPTransmitter::TransmissionProtocol GetTransmissionProtocol() const { return protocol; } + virtual ~RTPTransmissionInfo() + { + } + /** Returns the transmitter type for which these parameters are valid. */ + RTPTransmitter::TransmissionProtocol GetTransmissionProtocol() const + { + return protocol; + } private: - RTPTransmitter::TransmissionProtocol protocol; + RTPTransmitter::TransmissionProtocol protocol; }; } // end namespace diff --git a/qrtplib/rtptypes.h b/qrtplib/rtptypes.h index 161238853..aa93f8026 100644 --- a/qrtplib/rtptypes.h +++ b/qrtplib/rtptypes.h @@ -1,5 +1,4 @@ #include "rtpconfig.h" - #include #include diff --git a/qrtplib/rtptypes_win.h b/qrtplib/rtptypes_win.h index 350e061a2..3dbae45c0 100644 --- a/qrtplib/rtptypes_win.h +++ b/qrtplib/rtptypes_win.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #ifndef RTPTYPES_WIN_H diff --git a/qrtplib/rtpudpv4transmitter.cpp b/qrtplib/rtpudpv4transmitter.cpp index 2a2f28c23..9e21a79e5 100644 --- a/qrtplib/rtpudpv4transmitter.cpp +++ b/qrtplib/rtpudpv4transmitter.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpudpv4transmitter.h" #include "rtprawpacket.h" @@ -73,1074 +73,1068 @@ using namespace std; } \ } while(0) - namespace qrtplib { -RTPUDPv4Transmitter::RTPUDPv4Transmitter(RTPMemoryManager *mgr) : RTPTransmitter(mgr),destinations(mgr,RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT), -#ifdef RTP_SUPPORT_IPV4MULTICAST - multicastgroups(mgr,RTPMEM_TYPE_CLASS_MULTICASTHASHELEMENT), -#endif // RTP_SUPPORT_IPV4MULTICAST - acceptignoreinfo(mgr,RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT) +RTPUDPv4Transmitter::RTPUDPv4Transmitter() { - created = false; - init = false; + created = false; + init = false; } RTPUDPv4Transmitter::~RTPUDPv4Transmitter() { - Destroy(); + Destroy(); } int RTPUDPv4Transmitter::Init(bool tsafe) { - if (init) - return ERR_RTP_UDPV4TRANS_ALREADYINIT; + if (init) + return ERR_RTP_UDPV4TRANS_ALREADYINIT; - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; - init = true; - return 0; + init = true; + return 0; } int RTPUDPv4Transmitter::GetIPv4SocketPort(SocketType s, uint16_t *pPort) { - assert(pPort != 0); + assert(pPort != 0); - struct sockaddr_in addr; - memset(&addr, 0, sizeof(struct sockaddr_in)); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(struct sockaddr_in)); - RTPSOCKLENTYPE size = sizeof(struct sockaddr_in); - if (getsockname(s,(struct sockaddr*)&addr,&size) != 0) - return ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT; + RTPSOCKLENTYPE size = sizeof(struct sockaddr_in); + if (getsockname(s, (struct sockaddr*) &addr, &size) != 0) + return ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT; - if (addr.sin_family != AF_INET) - return ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET; + if (addr.sin_family != AF_INET) + return ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET; - uint16_t port = ntohs(addr.sin_port); - if (port == 0) - return ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET; + uint16_t port = ntohs(addr.sin_port); + if (port == 0) + return ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET; - int type = 0; - RTPSOCKLENTYPE length = sizeof(type); + int type = 0; + RTPSOCKLENTYPE length = sizeof(type); - if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*)&type, &length) != 0) - return ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE; + if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*) &type, &length) != 0) + return ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE; - if (type != SOCK_DGRAM) - return ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE; + if (type != SOCK_DGRAM) + return ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE; - *pPort = port; - return 0; + *pPort = port; + return 0; } -int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, - SocketType *pRtpSock, SocketType *pRtcpSock, - uint16_t *pRtpPort, uint16_t *pRtcpPort) +int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, SocketType *pRtpSock, SocketType *pRtcpSock, uint16_t *pRtpPort, uint16_t *pRtcpPort) { - const int maxAttempts = 1024; - int attempts = 0; - vector toClose; + const int maxAttempts = 1024; + int attempts = 0; + vector toClose; - while (attempts++ < maxAttempts) - { - SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); - if (sock == RTPSOCKERR) - { - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } + while (attempts++ < maxAttempts) + { + SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock == RTPSOCKERR) + { + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } - // First we get an automatically chosen port + // First we get an automatically chosen port - struct sockaddr_in addr; - memset(&addr,0,sizeof(struct sockaddr_in)); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = 0; - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(sock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(sock); - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; - } + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(sock); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; + } - uint16_t basePort = 0; - int status = GetIPv4SocketPort(sock, &basePort); - if (status < 0) - { - RTPCLOSE(sock); - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); - return status; - } + uint16_t basePort = 0; + int status = GetIPv4SocketPort(sock, &basePort); + if (status < 0) + { + RTPCLOSE(sock); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); + return status; + } - if (rtcpMux) // only need one socket - { - if (basePort%2 == 0 || allowOdd) - { - *pRtpSock = sock; - *pRtcpSock = sock; - *pRtpPort = basePort; - *pRtcpPort = basePort; - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); + if (rtcpMux) // only need one socket + { + if (basePort % 2 == 0 || allowOdd) + { + *pRtpSock = sock; + *pRtcpSock = sock; + *pRtpPort = basePort; + *pRtcpPort = basePort; + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); - return 0; - } - else - toClose.push_back(sock); - } - else - { - SocketType sock2 = socket(PF_INET, SOCK_DGRAM, 0); - if (sock2 == RTPSOCKERR) - { - RTPCLOSE(sock); - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } + return 0; + } + else + toClose.push_back(sock); + } + else + { + SocketType sock2 = socket(PF_INET, SOCK_DGRAM, 0); + if (sock2 == RTPSOCKERR) + { + RTPCLOSE(sock); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } - // Try the next port or the previous port - uint16_t secondPort = basePort; - bool possiblyValid = false; + // Try the next port or the previous port + uint16_t secondPort = basePort; + bool possiblyValid = false; - if (basePort%2 == 0) - { - secondPort++; - possiblyValid = true; - } - else if (basePort > 1) // avoid landing on port 0 - { - secondPort--; - possiblyValid = true; - } + if (basePort % 2 == 0) + { + secondPort++; + possiblyValid = true; + } + else if (basePort > 1) // avoid landing on port 0 + { + secondPort--; + possiblyValid = true; + } - if (possiblyValid) - { - memset(&addr,0,sizeof(struct sockaddr_in)); + if (possiblyValid) + { + memset(&addr, 0, sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(secondPort); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(sock2,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) == 0) - { - // In this case, we have two consecutive port numbers, the lower of - // which is even + addr.sin_family = AF_INET; + addr.sin_port = htons(secondPort); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(sock2, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == 0) + { + // In this case, we have two consecutive port numbers, the lower of + // which is even - if (basePort < secondPort) - { - *pRtpSock = sock; - *pRtcpSock = sock2; - *pRtpPort = basePort; - *pRtcpPort = secondPort; - } - else - { - *pRtpSock = sock2; - *pRtcpSock = sock; - *pRtpPort = secondPort; - *pRtcpPort = basePort; - } + if (basePort < secondPort) + { + *pRtpSock = sock; + *pRtcpSock = sock2; + *pRtpPort = basePort; + *pRtcpPort = secondPort; + } + else + { + *pRtpSock = sock2; + *pRtcpSock = sock; + *pRtpPort = secondPort; + *pRtcpPort = basePort; + } - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); - return 0; - } - } + return 0; + } + } - toClose.push_back(sock); - toClose.push_back(sock2); - } - } + toClose.push_back(sock); + toClose.push_back(sock2); + } + } - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; + return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; } -int RTPUDPv4Transmitter::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) +int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) { - const RTPUDPv4TransmissionParams *params,defaultparams; - struct sockaddr_in addr; - RTPSOCKLENTYPE size; - int status; + const RTPUDPv4TransmissionParams *params, defaultparams; + struct sockaddr_in addr; + RTPSOCKLENTYPE size; + int status; - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK + MAINMUTEX_LOCK - if (created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_ALREADYCREATED; - } + if (created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ALREADYCREATED; + } - // Obtain transmission parameters + // Obtain transmission parameters - if (transparams == 0) - params = &defaultparams; - else - { - if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; - } - params = (const RTPUDPv4TransmissionParams *)transparams; - } + if (transparams == 0) + params = &defaultparams; + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; + } + params = (const RTPUDPv4TransmissionParams *) transparams; + } - if (params->GetUseExistingSockets(rtpsock, rtcpsock)) - { - closesocketswhendone = false; + if (params->GetUseExistingSockets(rtpsock, rtcpsock)) + { + closesocketswhendone = false; - // Determine the port numbers - int status = GetIPv4SocketPort(rtpsock, &m_rtpPort); - if (status < 0) - { - MAINMUTEX_UNLOCK - return status; - } - status = GetIPv4SocketPort(rtcpsock, &m_rtcpPort); - if (status < 0) - { - MAINMUTEX_UNLOCK - return status; - } - } - else - { - closesocketswhendone = true; + // Determine the port numbers + int status = GetIPv4SocketPort(rtpsock, &m_rtpPort); + if (status < 0) + { + MAINMUTEX_UNLOCK + return status; + } + status = GetIPv4SocketPort(rtcpsock, &m_rtcpPort); + if (status < 0) + { + MAINMUTEX_UNLOCK + return status; + } + } + else + { + closesocketswhendone = true; - if (params->GetPortbase() == 0) - { - int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), - &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); - if (status < 0) - { - MAINMUTEX_UNLOCK - return status; - } - } - else - { - // Check if portbase is even (if necessary) - if (!params->GetAllowOddPortbase() && params->GetPortbase()%2 != 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; - } + if (params->GetPortbase() == 0) + { + int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); + if (status < 0) + { + MAINMUTEX_UNLOCK + return status; + } + } + else + { + // Check if portbase is even (if necessary) + if (!params->GetAllowOddPortbase() && params->GetPortbase() % 2 != 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; + } - // create sockets + // create sockets - rtpsock = socket(PF_INET,SOCK_DGRAM,0); - if (rtpsock == RTPSOCKERR) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } + rtpsock = socket(PF_INET, SOCK_DGRAM, 0); + if (rtpsock == RTPSOCKERR) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } - // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket - if (params->GetRTCPMultiplexing()) - rtcpsock = rtpsock; - else - { - rtcpsock = socket(PF_INET,SOCK_DGRAM,0); - if (rtcpsock == RTPSOCKERR) - { - RTPCLOSE(rtpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - } + // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket + if (params->GetRTCPMultiplexing()) + rtcpsock = rtpsock; + else + { + rtcpsock = socket(PF_INET, SOCK_DGRAM, 0); + if (rtcpsock == RTPSOCKERR) + { + RTPCLOSE(rtpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + } - // bind sockets + // bind sockets - uint32_t bindIP = params->GetBindIP(); + uint32_t bindIP = params->GetBindIP(); - m_rtpPort = params->GetPortbase(); + m_rtpPort = params->GetPortbase(); - memset(&addr,0,sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(params->GetPortbase()); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(rtpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; - } + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(params->GetPortbase()); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(rtpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; + } - if (rtpsock != rtcpsock) // no need to bind same socket twice when multiplexing - { - uint16_t rtpport = params->GetPortbase(); - uint16_t rtcpport = params->GetForcedRTCPPort(); + if (rtpsock != rtcpsock) // no need to bind same socket twice when multiplexing + { + uint16_t rtpport = params->GetPortbase(); + uint16_t rtcpport = params->GetForcedRTCPPort(); - if (rtcpport == 0) - { - rtcpport = rtpport; - if (rtcpport < 0xFFFF) - rtcpport++; - } + if (rtcpport == 0) + { + rtcpport = rtpport; + if (rtcpport < 0xFFFF) + rtcpport++; + } - memset(&addr,0,sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(rtcpport); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(rtcpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; - } + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(rtcpport); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(rtcpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; + } - m_rtcpPort = rtcpport; - } - else - m_rtcpPort = m_rtpPort; - } + m_rtcpPort = rtcpport; + } + else + m_rtcpPort = m_rtpPort; + } - // set socket buffer sizes + // set socket buffer sizes - size = params->GetRTPReceiveBuffer(); - if (setsockopt(rtpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; - } - size = params->GetRTPSendBuffer(); - if (setsockopt(rtpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; - } + size = params->GetRTPReceiveBuffer(); + if (setsockopt(rtpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; + } + size = params->GetRTPSendBuffer(); + if (setsockopt(rtpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; + } - if (rtpsock != rtcpsock) // no need to set RTCP flags when multiplexing - { - size = params->GetRTCPReceiveBuffer(); - if (setsockopt(rtcpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; - } - size = params->GetRTCPSendBuffer(); - if (setsockopt(rtcpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; - } - } - } + if (rtpsock != rtcpsock) // no need to set RTCP flags when multiplexing + { + size = params->GetRTCPReceiveBuffer(); + if (setsockopt(rtcpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; + } + size = params->GetRTCPSendBuffer(); + if (setsockopt(rtcpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; + } + } + } - // Try to obtain local IP addresses + // Try to obtain local IP addresses - localIPs = params->GetLocalIPList(); - if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them - { - int status; + localIPs = params->GetLocalIPList(); + if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them + { + int status; - if ((status = CreateLocalIPList()) < 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return status; - } - } + if ((status = CreateLocalIPList()) < 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return status; + } + } #ifdef RTP_SUPPORT_IPV4MULTICAST - if (SetMulticastTTL(params->GetMulticastTTL())) - supportsmulticasting = true; - else - supportsmulticasting = false; + if (SetMulticastTTL(params->GetMulticastTTL())) + supportsmulticasting = true; + else + supportsmulticasting = false; #else // no multicast support enabled - supportsmulticasting = false; + supportsmulticasting = false; #endif // RTP_SUPPORT_IPV4MULTICAST - if (maximumpacketsize > RTPUDPV4TRANS_MAXPACKSIZE) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } + if (maximumpacketsize > RTPUDPV4TRANS_MAXPACKSIZE) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } - if (!params->GetCreatedAbortDescriptors()) - { - if ((status = m_abortDesc.Init()) < 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return status; - } - m_pAbortDesc = &m_abortDesc; - } - else - { - m_pAbortDesc = params->GetCreatedAbortDescriptors(); - if (!m_pAbortDesc->IsInitialized()) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_ABORTDESC_NOTINIT; - } - } + if (!params->GetCreatedAbortDescriptors()) + { + if ((status = m_abortDesc.Init()) < 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return status; + } + m_pAbortDesc = &m_abortDesc; + } + else + { + m_pAbortDesc = params->GetCreatedAbortDescriptors(); + if (!m_pAbortDesc->IsInitialized()) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; + } + } - maxpacksize = maximumpacketsize; - multicastTTL = params->GetMulticastTTL(); - mcastifaceIP = params->GetMulticastInterfaceIP(); - receivemode = RTPTransmitter::AcceptAll; + maxpacksize = maximumpacketsize; + multicastTTL = params->GetMulticastTTL(); + mcastifaceIP = params->GetMulticastInterfaceIP(); + receivemode = RTPTransmitter::AcceptAll; - localhostname = 0; - localhostnamelength = 0; + localhostname = 0; + localhostnamelength = 0; - waitingfordata = false; - created = true; - MAINMUTEX_UNLOCK - return 0; + waitingfordata = false; + created = true; + MAINMUTEX_UNLOCK + return 0; } void RTPUDPv4Transmitter::Destroy() { - if (!init) - return; + if (!init) + return; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK; - return; - } + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK; + return; + } - if (localhostname) - { - RTPDeleteByteArray(localhostname,GetMemoryManager()); - localhostname = 0; - localhostnamelength = 0; - } + if (localhostname) + { + delete[] localhostname; + localhostname = 0; + localhostnamelength = 0; + } - CLOSESOCKETS; - destinations.Clear(); + CLOSESOCKETS; + destinations.Clear(); #ifdef RTP_SUPPORT_IPV4MULTICAST - multicastgroups.Clear(); + multicastgroups.Clear(); #endif // RTP_SUPPORT_IPV4MULTICAST - FlushPackets(); - ClearAcceptIgnoreInfo(); - localIPs.clear(); - created = false; + FlushPackets(); + ClearAcceptIgnoreInfo(); + localIPs.clear(); + created = false; - if (waitingfordata) - { - m_pAbortDesc->SendAbortSignal(); - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK - } - else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized + if (waitingfordata) + { + m_pAbortDesc->SendAbortSignal(); + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK// to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK +} +else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK +MAINMUTEX_UNLOCK } RTPTransmissionInfo *RTPUDPv4Transmitter::GetTransmissionInfo() { - if (!init) - return 0; +if (!init) +return 0; - MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionInfo(localIPs,rtpsock,rtcpsock,m_rtpPort,m_rtcpPort); - MAINMUTEX_UNLOCK - return tinf; +MAINMUTEX_LOCK +RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionInfo(localIPs, rtpsock, rtcpsock, m_rtpPort, m_rtcpPort); +MAINMUTEX_UNLOCK +return tinf; } void RTPUDPv4Transmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) { - if (!init) - return; +if (!init) +return; - RTPDelete(i, GetMemoryManager()); +delete i; } -int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) +int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} - if (localhostname == 0) - { - if (localIPs.empty()) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOLOCALIPS; - } +if (localhostname == 0) +{ +if (localIPs.empty()) +{ + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOLOCALIPS; +} - std::list::const_iterator it; - std::list hostnames; +std::list::const_iterator it; +std::list hostnames; - for (it = localIPs.begin() ; it != localIPs.end() ; it++) - { - bool founddouble = false; - bool foundentry = true; +for (it = localIPs.begin(); it != localIPs.end(); it++) +{ + bool founddouble = false; + bool foundentry = true; - while (!founddouble && foundentry) - { - struct hostent *he; - uint8_t addr[4]; - uint32_t ip = (*it); + while (!founddouble && foundentry) + { + struct hostent *he; + uint8_t addr[4]; + uint32_t ip = (*it); - addr[0] = (uint8_t)((ip>>24)&0xFF); - addr[1] = (uint8_t)((ip>>16)&0xFF); - addr[2] = (uint8_t)((ip>>8)&0xFF); - addr[3] = (uint8_t)(ip&0xFF); - he = gethostbyaddr((char *)addr,4,AF_INET); - if (he != 0) - { - std::string hname = std::string(he->h_name); - std::list::const_iterator it; + addr[0] = (uint8_t) ((ip >> 24) & 0xFF); + addr[1] = (uint8_t) ((ip >> 16) & 0xFF); + addr[2] = (uint8_t) ((ip >> 8) & 0xFF); + addr[3] = (uint8_t) (ip & 0xFF); + he = gethostbyaddr((char *) addr, 4, AF_INET); + if (he != 0) + { + std::string hname = std::string(he->h_name); + std::list::const_iterator it; - for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) - if ((*it) == hname) - founddouble = true; + for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) + if ((*it) == hname) + founddouble = true; - if (!founddouble) - hostnames.push_back(hname); + if (!founddouble) + hostnames.push_back(hname); - int i = 0; - while (!founddouble && he->h_aliases[i] != 0) - { - std::string hname = std::string(he->h_aliases[i]); + int i = 0; + while (!founddouble && he->h_aliases[i] != 0) + { + std::string hname = std::string(he->h_aliases[i]); - for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) - if ((*it) == hname) - founddouble = true; + for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) + if ((*it) == hname) + founddouble = true; - if (!founddouble) - { - hostnames.push_back(hname); - i++; - } - } - } - else - foundentry = false; - } - } + if (!founddouble) + { + hostnames.push_back(hname); + i++; + } + } + } + else + foundentry = false; + } +} - bool found = false; +bool found = false; - if (!hostnames.empty()) // try to select the most appropriate hostname - { - std::list::const_iterator it; +if (!hostnames.empty()) // try to select the most appropriate hostname +{ + std::list::const_iterator it; - hostnames.sort(); - for (it = hostnames.begin() ; !found && it != hostnames.end() ; it++) - { - if ((*it).find('.') != std::string::npos) - { - found = true; - localhostnamelength = (*it).length(); - localhostname = new uint8_t [localhostnamelength+1]; - if (localhostname == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname,(*it).c_str(),localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - } + hostnames.sort(); + for (it = hostnames.begin(); !found && it != hostnames.end(); it++) + { + if ((*it).find('.') != std::string::npos) + { + found = true; + localhostnamelength = (*it).length(); + localhostname = new uint8_t[localhostnamelength + 1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname, (*it).c_str(), localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } +} - if (!found) // use an IP address - { - uint32_t ip; - int len; - char str[16]; +if (!found) // use an IP address +{ + uint32_t ip; + int len; + char str[16]; - it = localIPs.begin(); - ip = (*it); + it = localIPs.begin(); + ip = (*it); - RTP_SNPRINTF(str,16,"%d.%d.%d.%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF)); - len = strlen(str); + RTP_SNPRINTF(str, 16, "%d.%d.%d.%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF)); + len = strlen(str); - localhostnamelength = len; - localhostname = new uint8_t [localhostnamelength + 1]; - if (localhostname == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname,str,localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } + localhostnamelength = len; + localhostname = new uint8_t[localhostnamelength + 1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname, str, localhostnamelength); + localhostname[localhostnamelength] = 0; +} +} - if ((*bufferlength) < localhostnamelength) - { - *bufferlength = localhostnamelength; // tell the application the required size of the buffer - MAINMUTEX_UNLOCK - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } +if ((*bufferlength) < localhostnamelength) +{ +*bufferlength = localhostnamelength; // tell the application the required size of the buffer +MAINMUTEX_UNLOCK +return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; +} - memcpy(buffer,localhostname,localhostnamelength); - *bufferlength = localhostnamelength; +memcpy(buffer, localhostname, localhostnamelength); +*bufferlength = localhostnamelength; - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; } bool RTPUDPv4Transmitter::ComesFromThisTransmitter(const RTPAddress *addr) { - if (!init) - return false; +if (!init) +return false; - if (addr == 0) - return false; +if (addr == 0) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - bool v; +bool v; - if (created && addr->GetAddressType() == RTPAddress::IPv4Address) - { - const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; - bool found = false; - std::list::const_iterator it; +if (created && addr->GetAddressType() == RTPAddress::IPv4Address) +{ +const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; +bool found = false; +std::list::const_iterator it; - it = localIPs.begin(); - while (!found && it != localIPs.end()) - { - if (addr2->GetIP() == *it) - found = true; - else - ++it; - } +it = localIPs.begin(); +while (!found && it != localIPs.end()) +{ + if (addr2->GetIP() == *it) + found = true; + else + ++it; +} - if (!found) - v = false; - else - { - if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port - v = true; - else - v = false; - } - } - else - v = false; +if (!found) + v = false; +else +{ + if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port + v = true; + else + v = false; +} +} +else +v = false; - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } int RTPUDPv4Transmitter::Poll() { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - int status; +int status; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - status = PollSocket(true); // poll RTP socket - if (rtpsock != rtcpsock) // no need to poll twice when multiplexing - { - if (status >= 0) - status = PollSocket(false); // poll RTCP socket - } - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +status = PollSocket(true); // poll RTP socket +if (rtpsock != rtcpsock) // no need to poll twice when multiplexing +{ +if (status >= 0) + status = PollSocket(false); // poll RTCP socket +} +MAINMUTEX_UNLOCK +return status; } -int RTPUDPv4Transmitter::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +int RTPUDPv4Transmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (waitingfordata) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_ALREADYWAITING; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (waitingfordata) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_ALREADYWAITING; +} - SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); +SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); - SocketType socks[3] = { rtpsock, rtcpsock, abortSocket }; - int8_t readflags[3] = { 0, 0, 0 }; - const int idxRTP = 0; - const int idxRTCP = 1; - const int idxAbort = 2; +SocketType socks[3] = +{ rtpsock, rtcpsock, abortSocket }; +int8_t readflags[3] = +{ 0, 0, 0 }; +const int idxRTP = 0; +const int idxRTCP = 1; +const int idxAbort = 2; - waitingfordata = true; +waitingfordata = true; - WAITMUTEX_LOCK - MAINMUTEX_UNLOCK +WAITMUTEX_LOCK +MAINMUTEX_UNLOCK - int status = RTPSelect(socks, readflags, 3, delay); - if (status < 0) - { - MAINMUTEX_LOCK - waitingfordata = false; - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return status; - } +int status = RTPSelect(socks, readflags, 3, delay); +if (status < 0) +{ +MAINMUTEX_LOCK +waitingfordata = false; +MAINMUTEX_UNLOCK +WAITMUTEX_UNLOCK +return status; +} - MAINMUTEX_LOCK - waitingfordata = false; - if (!created) // destroy called - { - MAINMUTEX_UNLOCK; - WAITMUTEX_UNLOCK - return 0; - } +MAINMUTEX_LOCK +waitingfordata = false; +if (!created) // destroy called +{ +MAINMUTEX_UNLOCK; +WAITMUTEX_UNLOCK +return 0; +} - // if aborted, read from abort buffer - if (readflags[idxAbort]) - m_pAbortDesc->ReadSignallingByte(); + // if aborted, read from abort buffer +if (readflags[idxAbort]) +m_pAbortDesc->ReadSignallingByte(); - if (dataavailable != 0) - { - if (readflags[idxRTP] || readflags[idxRTCP]) - *dataavailable = true; - else - *dataavailable = false; - } +if (dataavailable != 0) +{ +if (readflags[idxRTP] || readflags[idxRTCP]) + *dataavailable = true; +else + *dataavailable = false; +} - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +WAITMUTEX_UNLOCK +return 0; } int RTPUDPv4Transmitter::AbortWait() { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (!waitingfordata) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTWAITING; - } - - m_pAbortDesc->SendAbortSignal(); - - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (!waitingfordata) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTWAITING; } -int RTPUDPv4Transmitter::SendRTPData(const void *data,size_t len) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +m_pAbortDesc->SendAbortSignal(); - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTPSockAddr(),sizeof(struct sockaddr_in)); - destinations.GotoNextElement(); - } - - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; } -int RTPUDPv4Transmitter::SendRTCPData(const void *data,size_t len) +int RTPUDPv4Transmitter::SendRTPData(const void *data, size_t len) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (len > maxpacksize) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; +} - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtcpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTCPSockAddr(),sizeof(struct sockaddr_in)); - destinations.GotoNextElement(); - } +destinations.GotoFirstElement(); +while (destinations.HasCurrentElement()) +{ +sendto(rtpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTPSockAddr(), sizeof(struct sockaddr_in)); +destinations.GotoNextElement(); +} - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; +} + +int RTPUDPv4Transmitter::SendRTCPData(const void *data, size_t len) +{ +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; + +MAINMUTEX_LOCK + +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (len > maxpacksize) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; +} + +destinations.GotoFirstElement(); +while (destinations.HasCurrentElement()) +{ +sendto(rtcpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTCPSockAddr(), sizeof(struct sockaddr_in)); +destinations.GotoNextElement(); +} + +MAINMUTEX_UNLOCK +return 0; } int RTPUDPv4Transmitter::AddDestination(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } +RTPIPv4Destination dest; +if (!RTPIPv4Destination::AddressToDestination(addr, dest)) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} - int status = destinations.AddElement(dest); +int status = destinations.AddElement(dest); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } int RTPUDPv4Transmitter::DeleteDestination(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +RTPIPv4Destination dest; +if (!RTPIPv4Destination::AddressToDestination(addr, dest)) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} - int status = destinations.DeleteElement(dest); +int status = destinations.DeleteElement(dest); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } void RTPUDPv4Transmitter::ClearDestinations() { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (created) - destinations.Clear(); - MAINMUTEX_UNLOCK +MAINMUTEX_LOCK +if (created) +destinations.Clear(); +MAINMUTEX_UNLOCK } bool RTPUDPv4Transmitter::SupportsMulticasting() { - if (!init) - return false; +if (!init) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - bool v; +bool v; - if (!created) - v = false; - else - v = supportsmulticasting; +if (!created) +v = false; +else +v = supportsmulticasting; - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } #ifdef RTP_SUPPORT_IPV4MULTICAST int RTPUDPv4Transmitter::JoinMulticastGroup(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - uint32_t mcastIP = address.GetIP(); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +uint32_t mcastIP = address.GetIP(); - if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; - } +if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; +} - status = multicastgroups.AddElement(mcastIP); - if (status >= 0) - { - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock,IP_ADD_MEMBERSHIP,mcastIP,status); - if (status != 0) - { - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; - } +status = multicastgroups.AddElement(mcastIP); +if (status >= 0) +{ +RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_ADD_MEMBERSHIP, mcastIP, status); +if (status != 0) +{ +multicastgroups.DeleteElement(mcastIP); +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; +} - if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing - { - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock,IP_ADD_MEMBERSHIP,mcastIP,status); - if (status != 0) - { - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; - } - } - } - MAINMUTEX_UNLOCK - return status; +if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing +{ +RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_ADD_MEMBERSHIP, mcastIP, status); +if (status != 0) +{ + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; +} +} +} +MAINMUTEX_UNLOCK +return status; } int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - uint32_t mcastIP = address.GetIP(); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +uint32_t mcastIP = address.GetIP(); - if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; - } +if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; +} - status = multicastgroups.DeleteElement(mcastIP); - if (status >= 0) - { - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); +status = multicastgroups.DeleteElement(mcastIP); +if (status >= 0) +{ +RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); +if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing +RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - status = 0; - } +status = 0; +} - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } void RTPUDPv4Transmitter::LeaveAllMulticastGroups() { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (created) - { - multicastgroups.GotoFirstElement(); - while (multicastgroups.HasCurrentElement()) - { - uint32_t mcastIP; - int status = 0; +MAINMUTEX_LOCK +if (created) +{ +multicastgroups.GotoFirstElement(); +while (multicastgroups.HasCurrentElement()) +{ +uint32_t mcastIP; +int status = 0; - mcastIP = multicastgroups.GetCurrentElement(); +mcastIP = multicastgroups.GetCurrentElement(); - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - JRTPLIB_UNUSED(status); +RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); +if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); +JRTPLIB_UNUSED(status); - multicastgroups.GotoNextElement(); - } - multicastgroups.Clear(); - } - MAINMUTEX_UNLOCK +multicastgroups.GotoNextElement(); +} +multicastgroups.Clear(); +} +MAINMUTEX_UNLOCK } #else // no multicast support int RTPUDPv4Transmitter::JoinMulticastGroup(const RTPAddress &addr) { - return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; +return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; } int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) { - return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; +return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; } void RTPUDPv4Transmitter::LeaveAllMulticastGroups() @@ -1151,243 +1145,243 @@ void RTPUDPv4Transmitter::LeaveAllMulticastGroups() int RTPUDPv4Transmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (m != receivemode) - { - receivemode = m; - acceptignoreinfo.Clear(); - } - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (m != receivemode) +{ +receivemode = m; +acceptignoreinfo.Clear(); +} +MAINMUTEX_UNLOCK +return 0; } int RTPUDPv4Transmitter::AddToIgnoreList(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} +if (receivemode != RTPTransmitter::IgnoreSome) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } int RTPUDPv4Transmitter::DeleteFromIgnoreList(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} +if (receivemode != RTPTransmitter::IgnoreSome) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } void RTPUDPv4Transmitter::ClearIgnoreList() { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (created && receivemode == RTPTransmitter::IgnoreSome) - ClearAcceptIgnoreInfo(); - MAINMUTEX_UNLOCK +MAINMUTEX_LOCK +if (created && receivemode == RTPTransmitter::IgnoreSome) +ClearAcceptIgnoreInfo(); +MAINMUTEX_UNLOCK } int RTPUDPv4Transmitter::AddToAcceptList(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} +if (receivemode != RTPTransmitter::AcceptSome) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } int RTPUDPv4Transmitter::DeleteFromAcceptList(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} +if (receivemode != RTPTransmitter::AcceptSome) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } void RTPUDPv4Transmitter::ClearAcceptList() { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (created && receivemode == RTPTransmitter::AcceptSome) - ClearAcceptIgnoreInfo(); - MAINMUTEX_UNLOCK +MAINMUTEX_LOCK +if (created && receivemode == RTPTransmitter::AcceptSome) +ClearAcceptIgnoreInfo(); +MAINMUTEX_UNLOCK } int RTPUDPv4Transmitter::SetMaximumPacketSize(size_t s) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (s > RTPUDPV4TRANS_MAXPACKSIZE) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - maxpacksize = s; - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (s > RTPUDPV4TRANS_MAXPACKSIZE) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; +} +maxpacksize = s; +MAINMUTEX_UNLOCK +return 0; } bool RTPUDPv4Transmitter::NewDataAvailable() { - if (!init) - return false; +if (!init) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - bool v; +bool v; - if (!created) - v = false; - else - { - if (rawpacketlist.empty()) - v = false; - else - v = true; - } +if (!created) +v = false; +else +{ +if (rawpacketlist.empty()) +v = false; +else +v = true; +} - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } RTPRawPacket *RTPUDPv4Transmitter::GetNextPacket() { - if (!init) - return 0; +if (!init) +return 0; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - RTPRawPacket *p; +RTPRawPacket *p; - if (!created) - { - MAINMUTEX_UNLOCK - return 0; - } - if (rawpacketlist.empty()) - { - MAINMUTEX_UNLOCK - return 0; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return 0; +} +if (rawpacketlist.empty()) +{ +MAINMUTEX_UNLOCK +return 0; +} - p = *(rawpacketlist.begin()); - rawpacketlist.pop_front(); +p = *(rawpacketlist.begin()); +rawpacketlist.pop_front(); - MAINMUTEX_UNLOCK - return p; +MAINMUTEX_UNLOCK +return p; } // Here the private functions start... @@ -1395,372 +1389,372 @@ RTPRawPacket *RTPUDPv4Transmitter::GetNextPacket() #ifdef RTP_SUPPORT_IPV4MULTICAST bool RTPUDPv4Transmitter::SetMulticastTTL(uint8_t ttl) { - int ttl2,status; +int ttl2, status; - ttl2 = (int)ttl; - status = setsockopt(rtpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); - if (status != 0) - return false; +ttl2 = (int) ttl; +status = setsockopt(rtpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); +if (status != 0) +return false; - if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing - { - status = setsockopt(rtcpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); - if (status != 0) - return false; - } - return true; +if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing +{ +status = setsockopt(rtcpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); +if (status != 0) +return false; +} +return true; } #endif // RTP_SUPPORT_IPV4MULTICAST void RTPUDPv4Transmitter::FlushPackets() { - std::list::const_iterator it; +std::list::const_iterator it; - for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) - RTPDelete(*it,GetMemoryManager()); - rawpacketlist.clear(); +for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) +delete *it; +rawpacketlist.clear(); } int RTPUDPv4Transmitter::PollSocket(bool rtp) { - RTPSOCKLENTYPE fromlen; - int recvlen; - char packetbuffer[RTPUDPV4TRANS_MAXPACKSIZE]; +RTPSOCKLENTYPE fromlen; +int recvlen; +char packetbuffer[RTPUDPV4TRANS_MAXPACKSIZE]; #ifdef RTP_SOCKETTYPE_WINSOCK - SOCKET sock; - unsigned long len; +SOCKET sock; +unsigned long len; #else - size_t len; - int sock; +size_t len; +int sock; #endif // RTP_SOCKETTYPE_WINSOCK - struct sockaddr_in srcaddr; - bool dataavailable; +struct sockaddr_in srcaddr; +bool dataavailable; - if (rtp) - sock = rtpsock; - else - sock = rtcpsock; +if (rtp) +sock = rtpsock; +else +sock = rtcpsock; - do - { - len = 0; - RTPIOCTL(sock,FIONREAD,&len); +do +{ +len = 0; +RTPIOCTL(sock, FIONREAD, &len); - if (len <= 0) // make sure a packet of length zero is not queued - { - // An alternative workaround would be to just use non-blocking sockets. - // However, since the user does have access to the sockets and I do not - // know how this would affect anyone else's code, I chose to do it using - // an extra select call in case ioctl says the length is zero. +if (len <= 0) // make sure a packet of length zero is not queued +{ + // An alternative workaround would be to just use non-blocking sockets. + // However, since the user does have access to the sockets and I do not + // know how this would affect anyone else's code, I chose to do it using + // an extra select call in case ioctl says the length is zero. - int8_t isset = 0; - int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); - if (status < 0) - return status; +int8_t isset = 0; +int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); +if (status < 0) +return status; - if (isset) - dataavailable = true; - else - dataavailable = false; - } - else - dataavailable = true; +if (isset) +dataavailable = true; +else +dataavailable = false; +} +else +dataavailable = true; - if (dataavailable) - { - RTPTime curtime = RTPTime::CurrentTime(); - fromlen = sizeof(struct sockaddr_in); - recvlen = recvfrom(sock,packetbuffer,RTPUDPV4TRANS_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); - if (recvlen > 0) - { - bool acceptdata; +if (dataavailable) +{ +RTPTime curtime = RTPTime::CurrentTime(); +fromlen = sizeof(struct sockaddr_in); +recvlen = recvfrom(sock, packetbuffer, RTPUDPV4TRANS_MAXPACKSIZE, 0, (struct sockaddr *) &srcaddr, &fromlen); +if (recvlen > 0) +{ +bool acceptdata; - // got data, process it - if (receivemode == RTPTransmitter::AcceptAll) - acceptdata = true; - else - acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); + // got data, process it +if (receivemode == RTPTransmitter::AcceptAll) +acceptdata = true; +else +acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); - if (acceptdata) - { - RTPRawPacket *pack; - RTPIPv4Address *addr; - uint8_t *datacopy; +if (acceptdata) +{ +RTPRawPacket *pack; +RTPIPv4Address *addr; +uint8_t *datacopy; - addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); - if (addr == 0) - return ERR_RTP_OUTOFMEM; - datacopy = new uint8_t[recvlen]; - if (datacopy == 0) - { - RTPDelete(addr,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - memcpy(datacopy,packetbuffer,recvlen); +addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); +if (addr == 0) +return ERR_RTP_OUTOFMEM; +datacopy = new uint8_t[recvlen]; +if (datacopy == 0) +{ +delete addr; +return ERR_RTP_OUTOFMEM; +} +memcpy(datacopy, packetbuffer, recvlen); - bool isrtp = rtp; - if (rtpsock == rtcpsock) // check payload type when multiplexing - { - isrtp = true; +bool isrtp = rtp; +if (rtpsock == rtcpsock) // check payload type when multiplexing +{ +isrtp = true; - if ((size_t)recvlen > sizeof(RTCPCommonHeader)) - { - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *)datacopy; - uint8_t packettype = rtcpheader->packettype; +if ((size_t) recvlen > sizeof(RTCPCommonHeader)) +{ + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; + uint8_t packettype = rtcpheader->packettype; - if (packettype >= 200 && packettype <= 204) - isrtp = false; - } - } - - pack = new RTPRawPacket(datacopy,recvlen,addr,curtime,isrtp,GetMemoryManager()); - if (pack == 0) - { - RTPDelete(addr,GetMemoryManager()); - RTPDeleteByteArray(datacopy,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - rawpacketlist.push_back(pack); - } - } - } - } while (dataavailable); - - return 0; + if (packettype >= 200 && packettype <= 204) + isrtp = false; +} } -int RTPUDPv4Transmitter::ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port) +pack = new RTPRawPacket(datacopy, recvlen, addr, curtime, isrtp); +if (pack == 0) { - acceptignoreinfo.GotoElement(ip); - if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists - { - PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); +delete addr; +delete[] datacopy; +return ERR_RTP_OUTOFMEM; +} +rawpacketlist.push_back(pack); +} +} +} +} while (dataavailable); - if (port == 0) // select all ports - { - portinf->all = true; - portinf->portlist.clear(); - } - else if (!portinf->all) - { - std::list::const_iterator it,begin,end; +return 0; +} - begin = portinf->portlist.begin(); - end = portinf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == port) // already in list - return 0; - } - portinf->portlist.push_front(port); - } - } - else // got to create an entry for this IP address - { - PortInfo *portinf; - int status; +int RTPUDPv4Transmitter::ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port) +{ +acceptignoreinfo.GotoElement(ip); +if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists +{ +PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); - portinf = new PortInfo(); - if (port == 0) // select all ports - portinf->all = true; - else - portinf->portlist.push_front(port); +if (port == 0) // select all ports +{ +portinf->all = true; +portinf->portlist.clear(); +} +else if (!portinf->all) +{ +std::list::const_iterator it, begin, end; - status = acceptignoreinfo.AddElement(ip,portinf); - if (status < 0) - { - RTPDelete(portinf,GetMemoryManager()); - return status; - } - } +begin = portinf->portlist.begin(); +end = portinf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == port) // already in list +return 0; +} +portinf->portlist.push_front(port); +} +} +else // got to create an entry for this IP address +{ +PortInfo *portinf; +int status; - return 0; +portinf = new PortInfo(); +if (port == 0) // select all ports +portinf->all = true; +else +portinf->portlist.push_front(port); + +status = acceptignoreinfo.AddElement(ip, portinf); +if (status < 0) +{ +delete portinf; +return status; +} +} + +return 0; } void RTPUDPv4Transmitter::ClearAcceptIgnoreInfo() { - acceptignoreinfo.GotoFirstElement(); - while (acceptignoreinfo.HasCurrentElement()) - { - PortInfo *inf; +acceptignoreinfo.GotoFirstElement(); +while (acceptignoreinfo.HasCurrentElement()) +{ +PortInfo *inf; - inf = acceptignoreinfo.GetCurrentElement(); - RTPDelete(inf,GetMemoryManager()); - acceptignoreinfo.GotoNextElement(); - } - acceptignoreinfo.Clear(); +inf = acceptignoreinfo.GetCurrentElement(); +delete inf; +acceptignoreinfo.GotoNextElement(); +} +acceptignoreinfo.Clear(); } -int RTPUDPv4Transmitter::ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port) +int RTPUDPv4Transmitter::ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port) { - acceptignoreinfo.GotoElement(ip); - if (!acceptignoreinfo.HasCurrentElement()) - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; +acceptignoreinfo.GotoElement(ip); +if (!acceptignoreinfo.HasCurrentElement()) +return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - PortInfo *inf; +PortInfo *inf; - inf = acceptignoreinfo.GetCurrentElement(); - if (port == 0) // delete all entries - { - inf->all = false; - inf->portlist.clear(); - } - else // a specific port was selected - { - if (inf->all) // currently, all ports are selected. Add the one to remove to the list - { - // we have to check if the list doesn't contain the port already - std::list::const_iterator it,begin,end; +inf = acceptignoreinfo.GetCurrentElement(); +if (port == 0) // delete all entries +{ +inf->all = false; +inf->portlist.clear(); +} +else // a specific port was selected +{ +if (inf->all) // currently, all ports are selected. Add the one to remove to the list +{ + // we have to check if the list doesn't contain the port already +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == port) // already in list: this means we already deleted the entry - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - } - inf->portlist.push_front(port); - } - else // check if we can find the port in the list - { - std::list::iterator it,begin,end; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == port) // already in list: this means we already deleted the entry +return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; +} +inf->portlist.push_front(port); +} +else // check if we can find the port in the list +{ +std::list::iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; ++it) - { - if (*it == port) // found it! - { - inf->portlist.erase(it); - return 0; - } - } - // didn't find it - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - } - } - return 0; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; ++it) +{ +if (*it == port) // found it! +{ +inf->portlist.erase(it); +return 0; +} +} + // didn't find it +return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; +} +} +return 0; } -bool RTPUDPv4Transmitter::ShouldAcceptData(uint32_t srcip,uint16_t srcport) +bool RTPUDPv4Transmitter::ShouldAcceptData(uint32_t srcip, uint16_t srcport) { - if (receivemode == RTPTransmitter::AcceptSome) - { - PortInfo *inf; +if (receivemode == RTPTransmitter::AcceptSome) +{ +PortInfo *inf; - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return false; +acceptignoreinfo.GotoElement(srcip); +if (!acceptignoreinfo.HasCurrentElement()) +return false; - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // only accept the ones in the list - { - std::list::const_iterator it,begin,end; +inf = acceptignoreinfo.GetCurrentElement(); +if (!inf->all) // only accept the ones in the list +{ +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return true; - } - return false; - } - else // accept all, except the ones in the list - { - std::list::const_iterator it,begin,end; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == srcport) +return true; +} +return false; +} +else // accept all, except the ones in the list +{ +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return false; - } - return true; - } - } - else // IgnoreSome - { - PortInfo *inf; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == srcport) +return false; +} +return true; +} +} +else // IgnoreSome +{ +PortInfo *inf; - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return true; +acceptignoreinfo.GotoElement(srcip); +if (!acceptignoreinfo.HasCurrentElement()) +return true; - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // ignore the ports in the list - { - std::list::const_iterator it,begin,end; +inf = acceptignoreinfo.GetCurrentElement(); +if (!inf->all) // ignore the ports in the list +{ +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return false; - } - return true; - } - else // ignore all, except the ones in the list - { - std::list::const_iterator it,begin,end; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == srcport) +return false; +} +return true; +} +else // ignore all, except the ones in the list +{ +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return true; - } - return false; - } - } - return true; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == srcport) +return true; +} +return false; +} +} +return true; } int RTPUDPv4Transmitter::CreateLocalIPList() { - // first try to obtain the list from the network interface info + // first try to obtain the list from the network interface info - if (!GetLocalIPList_Interfaces()) - { - // If this fails, we'll have to depend on DNS info - GetLocalIPList_DNS(); - } - AddLoopbackAddress(); - return 0; +if (!GetLocalIPList_Interfaces()) +{ + // If this fails, we'll have to depend on DNS info +GetLocalIPList_DNS(); +} +AddLoopbackAddress(); +return 0; } #ifdef RTP_SOCKETTYPE_WINSOCK bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() { - unsigned char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; - DWORD outputsize; - DWORD numaddresses,i; - SOCKET_ADDRESS_LIST *addrlist; +unsigned char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; +DWORD outputsize; +DWORD numaddresses,i; +SOCKET_ADDRESS_LIST *addrlist; - if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANS_IFREQBUFSIZE,&outputsize,NULL,NULL)) - return false; +if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANS_IFREQBUFSIZE,&outputsize,NULL,NULL)) +return false; - addrlist = (SOCKET_ADDRESS_LIST *)buffer; - numaddresses = addrlist->iAddressCount; - for (i = 0 ; i < numaddresses ; i++) - { - SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); - if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address - { - struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; +addrlist = (SOCKET_ADDRESS_LIST *)buffer; +numaddresses = addrlist->iAddressCount; +for (i = 0; i < numaddresses; i++) +{ +SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); +if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address +{ +struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; - localIPs.push_back(ntohl(addr->sin_addr.s_addr)); - } - } +localIPs.push_back(ntohl(addr->sin_addr.s_addr)); +} +} - if (localIPs.empty()) - return false; +if (localIPs.empty()) +return false; - return true; +return true; } #else // use either getifaddrs or ioctl @@ -1769,92 +1763,92 @@ bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() { - struct ifaddrs *addrs,*tmp; +struct ifaddrs *addrs, *tmp; - getifaddrs(&addrs); - tmp = addrs; +getifaddrs(&addrs); +tmp = addrs; - while (tmp != 0) - { - if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) - { - struct sockaddr_in *inaddr = (struct sockaddr_in *)tmp->ifa_addr; - localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); - } - tmp = tmp->ifa_next; - } +while (tmp != 0) +{ +if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) +{ +struct sockaddr_in *inaddr = (struct sockaddr_in *) tmp->ifa_addr; +localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); +} +tmp = tmp->ifa_next; +} - freeifaddrs(addrs); +freeifaddrs(addrs); - if (localIPs.empty()) - return false; - return true; +if (localIPs.empty()) +return false; +return true; } #else // user ioctl bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() { - int status; - char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; - struct ifconf ifc; - struct ifreq *ifr; - struct sockaddr *sa; - char *startptr,*endptr; - int remlen; +int status; +char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; +struct ifconf ifc; +struct ifreq *ifr; +struct sockaddr *sa; +char *startptr,*endptr; +int remlen; - ifc.ifc_len = RTPUDPV4TRANS_IFREQBUFSIZE; - ifc.ifc_buf = buffer; - status = ioctl(rtpsock,SIOCGIFCONF,&ifc); - if (status < 0) - return false; +ifc.ifc_len = RTPUDPV4TRANS_IFREQBUFSIZE; +ifc.ifc_buf = buffer; +status = ioctl(rtpsock,SIOCGIFCONF,&ifc); +if (status < 0) +return false; - startptr = (char *)ifc.ifc_req; - endptr = startptr + ifc.ifc_len; - remlen = ifc.ifc_len; - while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) - { - ifr = (struct ifreq *)startptr; - sa = &(ifr->ifr_addr); +startptr = (char *)ifc.ifc_req; +endptr = startptr + ifc.ifc_len; +remlen = ifc.ifc_len; +while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) +{ +ifr = (struct ifreq *)startptr; +sa = &(ifr->ifr_addr); #ifdef RTP_HAVE_SOCKADDR_LEN - if (sa->sa_len <= sizeof(struct sockaddr)) - { - if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; +if (sa->sa_len <= sizeof(struct sockaddr)) +{ +if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) +{ +uint32_t ip; +struct sockaddr_in *addr = (struct sockaddr_in *)sa; - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); - } - else - { - int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); +ip = ntohl(addr->sin_addr.s_addr); +localIPs.push_back(ip); +} +remlen -= sizeof(struct ifreq); +startptr += sizeof(struct ifreq); +} +else +{ +int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); - remlen -= l; - startptr += l; - } +remlen -= l; +startptr += l; +} #else // don't have sa_len in struct sockaddr - if (sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; +if (sa->sa_family == PF_INET) +{ +uint32_t ip; +struct sockaddr_in *addr = (struct sockaddr_in *)sa; - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); +ip = ntohl(addr->sin_addr.s_addr); +localIPs.push_back(ip); +} +remlen -= sizeof(struct ifreq); +startptr += sizeof(struct ifreq); #endif // RTP_HAVE_SOCKADDR_LEN - } +} - if (localIPs.empty()) - return false; - return true; +if (localIPs.empty()) +return false; +return true; } #endif // RTP_SUPPORT_IFADDRS @@ -1863,49 +1857,49 @@ bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() void RTPUDPv4Transmitter::GetLocalIPList_DNS() { - struct hostent *he; - char name[1024]; - bool done; - int i,j; +struct hostent *he; +char name[1024]; +bool done; +int i, j; - gethostname(name,1023); - name[1023] = 0; - he = gethostbyname(name); - if (he == 0) - return; +gethostname(name, 1023); +name[1023] = 0; +he = gethostbyname(name); +if (he == 0) +return; - i = 0; - done = false; - while (!done) - { - if (he->h_addr_list[i] == NULL) - done = true; - else - { - uint32_t ip = 0; +i = 0; +done = false; +while (!done) +{ +if (he->h_addr_list[i] == NULL) +done = true; +else +{ +uint32_t ip = 0; - for (j = 0 ; j < 4 ; j++) - ip |= ((uint32_t)((unsigned char)he->h_addr_list[i][j])<<((3-j)*8)); - localIPs.push_back(ip); - i++; - } - } +for (j = 0; j < 4; j++) +ip |= ((uint32_t) ((unsigned char) he->h_addr_list[i][j]) << ((3 - j) * 8)); +localIPs.push_back(ip); +i++; +} +} } void RTPUDPv4Transmitter::AddLoopbackAddress() { - uint32_t loopbackaddr = (((uint32_t)127)<<24)|((uint32_t)1); - std::list::const_iterator it; - bool found = false; +uint32_t loopbackaddr = (((uint32_t) 127) << 24) | ((uint32_t) 1); +std::list::const_iterator it; +bool found = false; - for (it = localIPs.begin() ; !found && it != localIPs.end() ; it++) - { - if (*it == loopbackaddr) - found = true; - } +for (it = localIPs.begin(); !found && it != localIPs.end(); it++) +{ +if (*it == loopbackaddr) +found = true; +} - if (!found) - localIPs.push_back(loopbackaddr); +if (!found) +localIPs.push_back(loopbackaddr); } } // end namespace diff --git a/qrtplib/rtpudpv4transmitter.h b/qrtplib/rtpudpv4transmitter.h index cc7b0a542..48b9e5175 100644 --- a/qrtplib/rtpudpv4transmitter.h +++ b/qrtplib/rtpudpv4transmitter.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpudpv4transmitter.h @@ -59,186 +59,309 @@ namespace qrtplib { /** Parameters for the UDP over IPv4 transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionParams : public RTPTransmissionParams +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionParams: public RTPTransmissionParams { public: - RTPUDPv4TransmissionParams(); + RTPUDPv4TransmissionParams(); - /** Sets the IP address which is used to bind the sockets to \c ip. */ - void SetBindIP(uint32_t ip) { bindIP = ip; } + /** Sets the IP address which is used to bind the sockets to \c ip. */ + void SetBindIP(uint32_t ip) + { + bindIP = ip; + } - /** Sets the multicast interface IP address. */ - void SetMulticastInterfaceIP(uint32_t ip) { mcastifaceIP = ip; } + /** Sets the multicast interface IP address. */ + void SetMulticastInterfaceIP(uint32_t ip) + { + mcastifaceIP = ip; + } - /** Sets the RTP portbase to \c pbase, which has to be an even number - * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; - * a port number of zero will cause a port to be chosen automatically. */ - void SetPortbase(uint16_t pbase) { portbase = pbase; } + /** Sets the RTP portbase to \c pbase, which has to be an even number + * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; + * a port number of zero will cause a port to be chosen automatically. */ + void SetPortbase(uint16_t pbase) + { + portbase = pbase; + } - /** Sets the multicast TTL to be used to \c mcastTTL. */ - void SetMulticastTTL(uint8_t mcastTTL) { multicastTTL = mcastTTL; } + /** Sets the multicast TTL to be used to \c mcastTTL. */ + void SetMulticastTTL(uint8_t mcastTTL) + { + multicastTTL = mcastTTL; + } - /** Passes a list of IP addresses which will be used as the local IP addresses. */ - void SetLocalIPList(std::list &iplist) { localIPs = iplist; } + /** Passes a list of IP addresses which will be used as the local IP addresses. */ + void SetLocalIPList(std::list &iplist) + { + localIPs = iplist; + } - /** Clears the list of local IP addresses. - * Clears the list of local IP addresses. An empty list will make the transmission - * component itself determine the local IP addresses. - */ - void ClearLocalIPList() { localIPs.clear(); } + /** Clears the list of local IP addresses. + * Clears the list of local IP addresses. An empty list will make the transmission + * component itself determine the local IP addresses. + */ + void ClearLocalIPList() + { + localIPs.clear(); + } - /** Returns the IP address which will be used to bind the sockets. */ - uint32_t GetBindIP() const { return bindIP; } + /** Returns the IP address which will be used to bind the sockets. */ + uint32_t GetBindIP() const + { + return bindIP; + } - /** Returns the multicast interface IP address. */ - uint32_t GetMulticastInterfaceIP() const { return mcastifaceIP; } + /** Returns the multicast interface IP address. */ + uint32_t GetMulticastInterfaceIP() const + { + return mcastifaceIP; + } - /** Returns the RTP portbase which will be used (default is 5000). */ - uint16_t GetPortbase() const { return portbase; } + /** Returns the RTP portbase which will be used (default is 5000). */ + uint16_t GetPortbase() const + { + return portbase; + } - /** Returns the multicast TTL which will be used (default is 1). */ - uint8_t GetMulticastTTL() const { return multicastTTL; } + /** Returns the multicast TTL which will be used (default is 1). */ + uint8_t GetMulticastTTL() const + { + return multicastTTL; + } - /** Returns the list of local IP addresses. */ - const std::list &GetLocalIPList() const { return localIPs; } + /** Returns the list of local IP addresses. */ + const std::list &GetLocalIPList() const + { + return localIPs; + } - /** Sets the RTP socket's send buffer size. */ - void SetRTPSendBuffer(int s) { rtpsendbuf = s; } + /** Sets the RTP socket's send buffer size. */ + void SetRTPSendBuffer(int s) + { + rtpsendbuf = s; + } - /** Sets the RTP socket's receive buffer size. */ - void SetRTPReceiveBuffer(int s) { rtprecvbuf = s; } + /** Sets the RTP socket's receive buffer size. */ + void SetRTPReceiveBuffer(int s) + { + rtprecvbuf = s; + } - /** Sets the RTCP socket's send buffer size. */ - void SetRTCPSendBuffer(int s) { rtcpsendbuf = s; } + /** Sets the RTCP socket's send buffer size. */ + void SetRTCPSendBuffer(int s) + { + rtcpsendbuf = s; + } - /** Sets the RTCP socket's receive buffer size. */ - void SetRTCPReceiveBuffer(int s) { rtcprecvbuf = s; } + /** Sets the RTCP socket's receive buffer size. */ + void SetRTCPReceiveBuffer(int s) + { + rtcprecvbuf = s; + } - /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ - void SetRTCPMultiplexing(bool f) { rtcpmux = f; } + /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ + void SetRTCPMultiplexing(bool f) + { + rtcpmux = f; + } - /** Can be used to allow the RTP port base to be any number, not just even numbers. */ - void SetAllowOddPortbase(bool f) { allowoddportbase = f; } + /** Can be used to allow the RTP port base to be any number, not just even numbers. */ + void SetAllowOddPortbase(bool f) + { + allowoddportbase = f; + } - /** Force the RTCP socket to use a specific port, not necessarily one more than - * the RTP port (set this to zero to disable). */ - void SetForcedRTCPPort(uint16_t rtcpport) { forcedrtcpport = rtcpport; } + /** Force the RTCP socket to use a specific port, not necessarily one more than + * the RTP port (set this to zero to disable). */ + void SetForcedRTCPPort(uint16_t rtcpport) + { + forcedrtcpport = rtcpport; + } - /** Use sockets that have already been created, no checks on port numbers - * will be done, and no buffer sizes will be set; you'll need to close - * the sockets yourself when done, it will **not** be done automatically. */ - void SetUseExistingSockets(SocketType rtpsocket, SocketType rtcpsocket) { rtpsock = rtpsocket; rtcpsock = rtcpsocket; useexistingsockets = true; } + /** Use sockets that have already been created, no checks on port numbers + * will be done, and no buffer sizes will be set; you'll need to close + * the sockets yourself when done, it will **not** be done automatically. */ + void SetUseExistingSockets(SocketType rtpsocket, SocketType rtcpsocket) + { + rtpsock = rtpsocket; + rtcpsock = rtcpsocket; + useexistingsockets = true; + } - /** If non null, the specified abort descriptors will be used to cancel - * the function that's waiting for packets to arrive; set to null (the default - * to let the transmitter create its own instance. */ - void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) { m_pAbortDesc = desc; } + /** If non null, the specified abort descriptors will be used to cancel + * the function that's waiting for packets to arrive; set to null (the default + * to let the transmitter create its own instance. */ + void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) + { + m_pAbortDesc = desc; + } - /** Returns the RTP socket's send buffer size. */ - int GetRTPSendBuffer() const { return rtpsendbuf; } + /** Returns the RTP socket's send buffer size. */ + int GetRTPSendBuffer() const + { + return rtpsendbuf; + } - /** Returns the RTP socket's receive buffer size. */ - int GetRTPReceiveBuffer() const { return rtprecvbuf; } + /** Returns the RTP socket's receive buffer size. */ + int GetRTPReceiveBuffer() const + { + return rtprecvbuf; + } - /** Returns the RTCP socket's send buffer size. */ - int GetRTCPSendBuffer() const { return rtcpsendbuf; } + /** Returns the RTCP socket's send buffer size. */ + int GetRTCPSendBuffer() const + { + return rtcpsendbuf; + } - /** Returns the RTCP socket's receive buffer size. */ - int GetRTCPReceiveBuffer() const { return rtcprecvbuf; } + /** Returns the RTCP socket's receive buffer size. */ + int GetRTCPReceiveBuffer() const + { + return rtcprecvbuf; + } - /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ - bool GetRTCPMultiplexing() const { return rtcpmux; } + /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ + bool GetRTCPMultiplexing() const + { + return rtcpmux; + } - /** If true, any RTP portbase will be allowed, not just even numbers. */ - bool GetAllowOddPortbase() const { return allowoddportbase; } + /** If true, any RTP portbase will be allowed, not just even numbers. */ + bool GetAllowOddPortbase() const + { + return allowoddportbase; + } - /** If non-zero, the specified port will be used to receive RTCP traffic. */ - uint16_t GetForcedRTCPPort() const { return forcedrtcpport; } + /** If non-zero, the specified port will be used to receive RTCP traffic. */ + uint16_t GetForcedRTCPPort() const + { + return forcedrtcpport; + } - /** Returns true and fills in sockets if existing sockets were set - * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ - bool GetUseExistingSockets(SocketType &rtpsocket, SocketType &rtcpsocket) const { if (!useexistingsockets) return false; rtpsocket = rtpsock; rtcpsocket = rtcpsock; return true; } + /** Returns true and fills in sockets if existing sockets were set + * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ + bool GetUseExistingSockets(SocketType &rtpsocket, SocketType &rtcpsocket) const + { + if (!useexistingsockets) + return false; + rtpsocket = rtpsock; + rtcpsocket = rtcpsock; + return true; + } - /** If non-null, this RTPAbortDescriptors instance will be used internally, - * which can be useful when creating your own poll thread for multiple - * sessions. */ - RTPAbortDescriptors *GetCreatedAbortDescriptors() const { return m_pAbortDesc; } + /** If non-null, this RTPAbortDescriptors instance will be used internally, + * which can be useful when creating your own poll thread for multiple + * sessions. */ + RTPAbortDescriptors *GetCreatedAbortDescriptors() const + { + return m_pAbortDesc; + } private: - uint16_t portbase; - uint32_t bindIP, mcastifaceIP; - std::list localIPs; - uint8_t multicastTTL; - int rtpsendbuf, rtprecvbuf; - int rtcpsendbuf, rtcprecvbuf; - bool rtcpmux; - bool allowoddportbase; - uint16_t forcedrtcpport; + uint16_t portbase; + uint32_t bindIP, mcastifaceIP; + std::list localIPs; + uint8_t multicastTTL; + int rtpsendbuf, rtprecvbuf; + int rtcpsendbuf, rtcprecvbuf; + bool rtcpmux; + bool allowoddportbase; + uint16_t forcedrtcpport; - SocketType rtpsock, rtcpsock; - bool useexistingsockets; + SocketType rtpsock, rtcpsock; + bool useexistingsockets; - RTPAbortDescriptors *m_pAbortDesc; + RTPAbortDescriptors *m_pAbortDesc; }; -inline RTPUDPv4TransmissionParams::RTPUDPv4TransmissionParams() : RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) +inline RTPUDPv4TransmissionParams::RTPUDPv4TransmissionParams() : + RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) { - portbase = RTPUDPV4TRANS_DEFAULTPORTBASE; - bindIP = 0; - multicastTTL = 1; - mcastifaceIP = 0; - rtpsendbuf = RTPUDPV4TRANS_RTPTRANSMITBUFFER; - rtprecvbuf = RTPUDPV4TRANS_RTPRECEIVEBUFFER; - rtcpsendbuf = RTPUDPV4TRANS_RTCPTRANSMITBUFFER; - rtcprecvbuf = RTPUDPV4TRANS_RTCPRECEIVEBUFFER; - rtcpmux = false; - allowoddportbase = false; - forcedrtcpport = 0; - useexistingsockets = false; - rtpsock = 0; - rtcpsock = 0; - m_pAbortDesc = 0; + portbase = RTPUDPV4TRANS_DEFAULTPORTBASE; + bindIP = 0; + multicastTTL = 1; + mcastifaceIP = 0; + rtpsendbuf = RTPUDPV4TRANS_RTPTRANSMITBUFFER; + rtprecvbuf = RTPUDPV4TRANS_RTPRECEIVEBUFFER; + rtcpsendbuf = RTPUDPV4TRANS_RTCPTRANSMITBUFFER; + rtcprecvbuf = RTPUDPV4TRANS_RTCPRECEIVEBUFFER; + rtcpmux = false; + allowoddportbase = false; + forcedrtcpport = 0; + useexistingsockets = false; + rtpsock = 0; + rtcpsock = 0; + m_pAbortDesc = 0; } /** Additional information about the UDP over IPv4 transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionInfo : public RTPTransmissionInfo +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionInfo: public RTPTransmissionInfo { public: - RTPUDPv4TransmissionInfo(std::list iplist,SocketType rtpsock,SocketType rtcpsock, - uint16_t rtpport, uint16_t rtcpport) : RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) - { localIPlist = iplist; rtpsocket = rtpsock; rtcpsocket = rtcpsock; m_rtpPort = rtpport; m_rtcpPort = rtcpport; } + RTPUDPv4TransmissionInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : + RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) + { + localIPlist = iplist; + rtpsocket = rtpsock; + rtcpsocket = rtcpsock; + m_rtpPort = rtpport; + m_rtcpPort = rtcpport; + } - ~RTPUDPv4TransmissionInfo() { } + ~RTPUDPv4TransmissionInfo() + { + } - /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ - std::list GetLocalIPList() const { return localIPlist; } + /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ + std::list GetLocalIPList() const + { + return localIPlist; + } - /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ - SocketType GetRTPSocket() const { return rtpsocket; } + /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ + SocketType GetRTPSocket() const + { + return rtpsocket; + } - /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ - SocketType GetRTCPSocket() const { return rtcpsocket; } + /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ + SocketType GetRTCPSocket() const + { + return rtcpsocket; + } - /** Returns the port number that the RTP socket receives packets on. */ - uint16_t GetRTPPort() const { return m_rtpPort; } + /** Returns the port number that the RTP socket receives packets on. */ + uint16_t GetRTPPort() const + { + return m_rtpPort; + } - /** Returns the port number that the RTCP socket receives packets on. */ - uint16_t GetRTCPPort() const { return m_rtcpPort; } + /** Returns the port number that the RTCP socket receives packets on. */ + uint16_t GetRTCPPort() const + { + return m_rtcpPort; + } private: - std::list localIPlist; - SocketType rtpsocket,rtcpsocket; - uint16_t m_rtpPort, m_rtcpPort; + std::list localIPlist; + SocketType rtpsocket, rtcpsocket; + uint16_t m_rtpPort, m_rtcpPort; }; class JRTPLIB_IMPORTEXPORT RTPUDPv4Trans_GetHashIndex_IPv4Dest { public: - static int GetIndex(const RTPIPv4Destination &d) { return d.GetIP()%RTPUDPV4TRANS_HASHSIZE; } + static int GetIndex(const RTPIPv4Destination &d) + { + return d.GetIP() % RTPUDPV4TRANS_HASHSIZE; + } }; class JRTPLIB_IMPORTEXPORT RTPUDPv4Trans_GetHashIndex_uint32_t { public: - static int GetIndex(const uint32_t &k) { return k%RTPUDPV4TRANS_HASHSIZE; } + static int GetIndex(const uint32_t &k) + { + return k % RTPUDPV4TRANS_HASHSIZE; + } }; #define RTPUDPV4TRANS_HEADERSIZE (20+8) @@ -250,106 +373,110 @@ public: * argument require an argument of RTPIPv4Address. The GetTransmissionInfo member function * returns an instance of type RTPUDPv4TransmissionInfo. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4Transmitter : public RTPTransmitter +class JRTPLIB_IMPORTEXPORT RTPUDPv4Transmitter: public RTPTransmitter { public: - RTPUDPv4Transmitter(RTPMemoryManager *mgr); - ~RTPUDPv4Transmitter(); + RTPUDPv4Transmitter(); + ~RTPUDPv4Transmitter(); - int Init(bool treadsafe); - int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + int Init(bool treadsafe); + int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() { return RTPUDPV4TRANS_HEADERSIZE; } + int GetLocalHostName(uint8_t *buffer, size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() + { + return RTPUDPV4TRANS_HEADERSIZE; + } - int Poll(); - int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); - int AbortWait(); + int Poll(); + int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); + int AbortWait(); - int SendRTPData(const void *data,size_t len); - int SendRTCPData(const void *data,size_t len); + int SendRTPData(const void *data, size_t len); + int SendRTCPData(const void *data, size_t len); - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); private: - int CreateLocalIPList(); - bool GetLocalIPList_Interfaces(); - void GetLocalIPList_DNS(); - void AddLoopbackAddress(); - void FlushPackets(); - int PollSocket(bool rtp); - int ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port); - int ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port); + int CreateLocalIPList(); + bool GetLocalIPList_Interfaces(); + void GetLocalIPList_DNS(); + void AddLoopbackAddress(); + void FlushPackets(); + int PollSocket(bool rtp); + int ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port); + int ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port); #ifdef RTP_SUPPORT_IPV4MULTICAST - bool SetMulticastTTL(uint8_t ttl); + bool SetMulticastTTL(uint8_t ttl); #endif // RTP_SUPPORT_IPV4MULTICAST - bool ShouldAcceptData(uint32_t srcip,uint16_t srcport); - void ClearAcceptIgnoreInfo(); + bool ShouldAcceptData(uint32_t srcip, uint16_t srcport); + void ClearAcceptIgnoreInfo(); - int GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, - SocketType *pRtpSock, SocketType *pRtcpSock, - uint16_t *pRtpPort, uint16_t *pRtcpPort); - static int GetIPv4SocketPort(SocketType s, uint16_t *pPort); + int GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, SocketType *pRtpSock, SocketType *pRtcpSock, uint16_t *pRtpPort, uint16_t *pRtcpPort); + static int GetIPv4SocketPort(SocketType s, uint16_t *pPort); - bool init; - bool created; - bool waitingfordata; - SocketType rtpsock,rtcpsock; - uint32_t mcastifaceIP; - std::list localIPs; - uint16_t m_rtpPort, m_rtcpPort; - uint8_t multicastTTL; - RTPTransmitter::ReceiveMode receivemode; + bool init; + bool created; + bool waitingfordata; + SocketType rtpsock, rtcpsock; + uint32_t mcastifaceIP; + std::list localIPs; + uint16_t m_rtpPort, m_rtcpPort; + uint8_t multicastTTL; + RTPTransmitter::ReceiveMode receivemode; - uint8_t *localhostname; - size_t localhostnamelength; + uint8_t *localhostname; + size_t localhostnamelength; - RTPHashTable destinations; + RTPHashTable destinations; #ifdef RTP_SUPPORT_IPV4MULTICAST - RTPHashTable multicastgroups; + RTPHashTable multicastgroups; #endif // RTP_SUPPORT_IPV4MULTICAST - std::list rawpacketlist; + std::list rawpacketlist; - bool supportsmulticasting; - size_t maxpacksize; + bool supportsmulticasting; + size_t maxpacksize; - class PortInfo - { - public: - PortInfo() { all = false; } + class PortInfo + { + public: + PortInfo() + { + all = false; + } - bool all; - std::list portlist; - }; + bool all; + std::list portlist; + }; - RTPKeyHashTable acceptignoreinfo; + RTPKeyHashTable acceptignoreinfo; - bool closesocketswhendone; - RTPAbortDescriptors m_abortDesc; - RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified + bool closesocketswhendone; + RTPAbortDescriptors m_abortDesc; + RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified }; diff --git a/qrtplib/rtpudpv4transmitternobind.cpp b/qrtplib/rtpudpv4transmitternobind.cpp index 538469bec..9d9c1cbac 100644 --- a/qrtplib/rtpudpv4transmitternobind.cpp +++ b/qrtplib/rtpudpv4transmitternobind.cpp @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtpudpv4transmitternobind.h" #include "rtprawpacket.h" @@ -73,403 +73,380 @@ using namespace std; } \ } while(0) - namespace qrtplib { -RTPUDPv4TransmitterNoBind::RTPUDPv4TransmitterNoBind(RTPMemoryManager *mgr) : - RTPTransmitter(mgr), - init(false), - created(false), - waitingfordata(false), - rtpsock(-1), - rtcpsock(-1), - mcastifaceIP(0), - m_rtpPort(0), - m_rtcpPort(0), - multicastTTL(0), - receivemode(AcceptAll), - localhostname(0), - localhostnamelength(0), - destinations(mgr,RTPMEM_TYPE_CLASS_DESTINATIONLISTHASHELEMENT), -#ifdef RTP_SUPPORT_IPV4MULTICAST - multicastgroups(mgr,RTPMEM_TYPE_CLASS_MULTICASTHASHELEMENT), -#endif // RTP_SUPPORT_IPV4MULTICAST - supportsmulticasting(false), - maxpacksize(0), - acceptignoreinfo(mgr,RTPMEM_TYPE_CLASS_ACCEPTIGNOREHASHELEMENT), - closesocketswhendone(false), - m_pAbortDesc(0) +RTPUDPv4TransmitterNoBind::RTPUDPv4TransmitterNoBind() : + init(false), created(false), waitingfordata(false), rtpsock(-1), rtcpsock(-1), mcastifaceIP(0), m_rtpPort(0), m_rtcpPort(0), multicastTTL(0), receivemode( + AcceptAll), localhostname(0), localhostnamelength(0), + supportsmulticasting(false), maxpacksize(0), closesocketswhendone(false), m_pAbortDesc(0) { } RTPUDPv4TransmitterNoBind::~RTPUDPv4TransmitterNoBind() { - Destroy(); + Destroy(); } int RTPUDPv4TransmitterNoBind::Init(bool tsafe) { - if (init) - return ERR_RTP_UDPV4TRANS_ALREADYINIT; + if (init) + return ERR_RTP_UDPV4TRANS_ALREADYINIT; - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; + if (tsafe) + return ERR_RTP_NOTHREADSUPPORT; - init = true; - return 0; + init = true; + return 0; } int RTPUDPv4TransmitterNoBind::GetIPv4SocketPort(SocketType s, uint16_t *pPort) { - assert(pPort != 0); + assert(pPort != 0); - struct sockaddr_in addr; - memset(&addr, 0, sizeof(struct sockaddr_in)); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(struct sockaddr_in)); - RTPSOCKLENTYPE size = sizeof(struct sockaddr_in); - if (getsockname(s,(struct sockaddr*)&addr,&size) != 0) - return ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT; + RTPSOCKLENTYPE size = sizeof(struct sockaddr_in); + if (getsockname(s, (struct sockaddr*) &addr, &size) != 0) + return ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT; - if (addr.sin_family != AF_INET) - return ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET; + if (addr.sin_family != AF_INET) + return ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET; - uint16_t port = ntohs(addr.sin_port); - if (port == 0) - return ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET; + uint16_t port = ntohs(addr.sin_port); + if (port == 0) + return ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET; - int type = 0; - RTPSOCKLENTYPE length = sizeof(type); + int type = 0; + RTPSOCKLENTYPE length = sizeof(type); - if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*)&type, &length) != 0) - return ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE; + if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*) &type, &length) != 0) + return ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE; - if (type != SOCK_DGRAM) - return ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE; + if (type != SOCK_DGRAM) + return ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE; - *pPort = port; - return 0; + *pPort = port; + return 0; } -int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, - SocketType *pRtpSock, SocketType *pRtcpSock, - uint16_t *pRtpPort, uint16_t *pRtcpPort) +int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, SocketType *pRtpSock, SocketType *pRtcpSock, uint16_t *pRtpPort, uint16_t *pRtcpPort) { - const int maxAttempts = 1024; - int attempts = 0; - vector toClose; + const int maxAttempts = 1024; + int attempts = 0; + vector toClose; - while (attempts++ < maxAttempts) - { - SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); - if (sock == RTPSOCKERR) - { - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } + while (attempts++ < maxAttempts) + { + SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock == RTPSOCKERR) + { + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } - // First we get an automatically chosen port + // First we get an automatically chosen port - struct sockaddr_in addr; - memset(&addr,0,sizeof(struct sockaddr_in)); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = 0; - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(sock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(sock); - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; - } + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) + { + RTPCLOSE(sock); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; + } - uint16_t basePort = 0; - int status = GetIPv4SocketPort(sock, &basePort); - if (status < 0) - { - RTPCLOSE(sock); - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); - return status; - } + uint16_t basePort = 0; + int status = GetIPv4SocketPort(sock, &basePort); + if (status < 0) + { + RTPCLOSE(sock); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); + return status; + } - if (rtcpMux) // only need one socket - { - if (basePort%2 == 0 || allowOdd) - { - *pRtpSock = sock; - *pRtcpSock = sock; - *pRtpPort = basePort; - *pRtcpPort = basePort; - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); + if (rtcpMux) // only need one socket + { + if (basePort % 2 == 0 || allowOdd) + { + *pRtpSock = sock; + *pRtcpSock = sock; + *pRtpPort = basePort; + *pRtcpPort = basePort; + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); - return 0; - } - else - toClose.push_back(sock); - } - else - { - SocketType sock2 = socket(PF_INET, SOCK_DGRAM, 0); - if (sock2 == RTPSOCKERR) - { - RTPCLOSE(sock); - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } + return 0; + } + else + toClose.push_back(sock); + } + else + { + SocketType sock2 = socket(PF_INET, SOCK_DGRAM, 0); + if (sock2 == RTPSOCKERR) + { + RTPCLOSE(sock); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } - // Try the next port or the previous port - uint16_t secondPort = basePort; - bool possiblyValid = false; + // Try the next port or the previous port + uint16_t secondPort = basePort; + bool possiblyValid = false; - if (basePort%2 == 0) - { - secondPort++; - possiblyValid = true; - } - else if (basePort > 1) // avoid landing on port 0 - { - secondPort--; - possiblyValid = true; - } + if (basePort % 2 == 0) + { + secondPort++; + possiblyValid = true; + } + else if (basePort > 1) // avoid landing on port 0 + { + secondPort--; + possiblyValid = true; + } - if (possiblyValid) - { - memset(&addr,0,sizeof(struct sockaddr_in)); + if (possiblyValid) + { + memset(&addr, 0, sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(secondPort); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(sock2,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) == 0) - { - // In this case, we have two consecutive port numbers, the lower of - // which is even + addr.sin_family = AF_INET; + addr.sin_port = htons(secondPort); + addr.sin_addr.s_addr = htonl(bindIP); + if (bind(sock2, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == 0) + { + // In this case, we have two consecutive port numbers, the lower of + // which is even - if (basePort < secondPort) - { - *pRtpSock = sock; - *pRtcpSock = sock2; - *pRtpPort = basePort; - *pRtcpPort = secondPort; - } - else - { - *pRtpSock = sock2; - *pRtcpSock = sock; - *pRtpPort = secondPort; - *pRtcpPort = basePort; - } + if (basePort < secondPort) + { + *pRtpSock = sock; + *pRtcpSock = sock2; + *pRtpPort = basePort; + *pRtcpPort = secondPort; + } + else + { + *pRtpSock = sock2; + *pRtcpSock = sock; + *pRtpPort = secondPort; + *pRtcpPort = basePort; + } - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); - return 0; - } - } + return 0; + } + } - toClose.push_back(sock); - toClose.push_back(sock2); - } - } + toClose.push_back(sock); + toClose.push_back(sock2); + } + } - for (size_t i = 0 ; i < toClose.size() ; i++) - RTPCLOSE(toClose[i]); + for (size_t i = 0; i < toClose.size(); i++) + RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; + return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; } -int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize,const RTPTransmissionParams *transparams) +int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) { - const RTPUDPv4TransmissionNoBindParams *params,defaultparams; + const RTPUDPv4TransmissionNoBindParams *params, defaultparams; // struct sockaddr_in addr; - RTPSOCKLENTYPE size; - int status; + RTPSOCKLENTYPE size; + int status; - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK + MAINMUTEX_LOCK - if (created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_ALREADYCREATED; - } + if (created) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ALREADYCREATED; + } - // Obtain transmission parameters + // Obtain transmission parameters - if (transparams == 0) - params = &defaultparams; - else - { - if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; - } - params = (const RTPUDPv4TransmissionNoBindParams *)transparams; - } + if (transparams == 0) + params = &defaultparams; + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; + } + params = (const RTPUDPv4TransmissionNoBindParams *) transparams; + } - if (params->GetUseExistingSockets(rtpsock, rtcpsock)) - { - closesocketswhendone = false; + if (params->GetUseExistingSockets(rtpsock, rtcpsock)) + { + closesocketswhendone = false; - // Determine the port numbers. They are set to 0 if the sockets are not bound. - GetIPv4SocketPort(rtpsock, &m_rtpPort); - GetIPv4SocketPort(rtcpsock, &m_rtcpPort); - } - else - { - closesocketswhendone = true; + // Determine the port numbers. They are set to 0 if the sockets are not bound. + GetIPv4SocketPort(rtpsock, &m_rtpPort); + GetIPv4SocketPort(rtcpsock, &m_rtcpPort); + } + else + { + closesocketswhendone = true; - if (params->GetPortbase() == 0) - { - int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), - &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); - if (status < 0) - { - MAINMUTEX_UNLOCK - return status; - } - } - else - { - // Check if portbase is even (if necessary) - if (!params->GetAllowOddPortbase() && params->GetPortbase()%2 != 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; - } + if (params->GetPortbase() == 0) + { + int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); + if (status < 0) + { + MAINMUTEX_UNLOCK + return status; + } + } + else + { + // Check if portbase is even (if necessary) + if (!params->GetAllowOddPortbase() && params->GetPortbase() % 2 != 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; + } - // create sockets + // create sockets - rtpsock = socket(PF_INET,SOCK_DGRAM,0); - if (rtpsock == RTPSOCKERR) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } + rtpsock = socket(PF_INET, SOCK_DGRAM, 0); + if (rtpsock == RTPSOCKERR) + { + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } - // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket - if (params->GetRTCPMultiplexing()) - rtcpsock = rtpsock; - else - { - rtcpsock = socket(PF_INET,SOCK_DGRAM,0); - if (rtcpsock == RTPSOCKERR) - { - RTPCLOSE(rtpsock); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - } + // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket + if (params->GetRTCPMultiplexing()) + rtcpsock = rtpsock; + else + { + rtcpsock = socket(PF_INET, SOCK_DGRAM, 0); + if (rtcpsock == RTPSOCKERR) + { + RTPCLOSE(rtpsock); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; + } + } - } + } - // set socket buffer sizes + // set socket buffer sizes - size = params->GetRTPReceiveBuffer(); - if (setsockopt(rtpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; - } - size = params->GetRTPSendBuffer(); - if (setsockopt(rtpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; - } + size = params->GetRTPReceiveBuffer(); + if (setsockopt(rtpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; + } + size = params->GetRTPSendBuffer(); + if (setsockopt(rtpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; + } - if (rtpsock != rtcpsock) // no need to set RTCP flags when multiplexing - { - size = params->GetRTCPReceiveBuffer(); - if (setsockopt(rtcpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; - } - size = params->GetRTCPSendBuffer(); - if (setsockopt(rtcpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; - } - } - } + if (rtpsock != rtcpsock) // no need to set RTCP flags when multiplexing + { + size = params->GetRTCPReceiveBuffer(); + if (setsockopt(rtcpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; + } + size = params->GetRTCPSendBuffer(); + if (setsockopt(rtcpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; + } + } + } - // Try to obtain local IP addresses + // Try to obtain local IP addresses - localIPs = params->GetLocalIPList(); - if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them - { - int status; + localIPs = params->GetLocalIPList(); + if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them + { + int status; - if ((status = CreateLocalIPList()) < 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return status; - } - } + if ((status = CreateLocalIPList()) < 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return status; + } + } #ifdef RTP_SUPPORT_IPV4MULTICAST - if (SetMulticastTTL(params->GetMulticastTTL())) - supportsmulticasting = true; - else - supportsmulticasting = false; + if (SetMulticastTTL(params->GetMulticastTTL())) + supportsmulticasting = true; + else + supportsmulticasting = false; #else // no multicast support enabled - supportsmulticasting = false; + supportsmulticasting = false; #endif // RTP_SUPPORT_IPV4MULTICAST - if (maximumpacketsize > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } + if (maximumpacketsize > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } - if (!params->GetCreatedAbortDescriptors()) - { - if ((status = m_abortDesc.Init()) < 0) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return status; - } - m_pAbortDesc = &m_abortDesc; - } - else - { - m_pAbortDesc = params->GetCreatedAbortDescriptors(); - if (!m_pAbortDesc->IsInitialized()) - { - CLOSESOCKETS; - MAINMUTEX_UNLOCK - return ERR_RTP_ABORTDESC_NOTINIT; - } - } + if (!params->GetCreatedAbortDescriptors()) + { + if ((status = m_abortDesc.Init()) < 0) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return status; + } + m_pAbortDesc = &m_abortDesc; + } + else + { + m_pAbortDesc = params->GetCreatedAbortDescriptors(); + if (!m_pAbortDesc->IsInitialized()) + { + CLOSESOCKETS; + MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; + } + } - maxpacksize = maximumpacketsize; - multicastTTL = params->GetMulticastTTL(); - mcastifaceIP = params->GetMulticastInterfaceIP(); - receivemode = RTPTransmitter::AcceptAll; + maxpacksize = maximumpacketsize; + multicastTTL = params->GetMulticastTTL(); + mcastifaceIP = params->GetMulticastInterfaceIP(); + receivemode = RTPTransmitter::AcceptAll; - localhostname = 0; - localhostnamelength = 0; + localhostname = 0; + localhostnamelength = 0; - waitingfordata = false; - created = true; - MAINMUTEX_UNLOCK - return 0; + waitingfordata = false; + created = true; + MAINMUTEX_UNLOCK + return 0; } int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transparams) @@ -480,17 +457,17 @@ int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transpar return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; } - const RTPUDPv4TransmissionNoBindParams *params = (const RTPUDPv4TransmissionNoBindParams *)transparams; + const RTPUDPv4TransmissionNoBindParams *params = (const RTPUDPv4TransmissionNoBindParams *) transparams; uint32_t bindIP = params->GetBindIP(); m_rtpPort = params->GetPortbase(); struct sockaddr_in addr; - memset(&addr,0,sizeof(struct sockaddr_in)); + memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(params->GetPortbase()); addr.sin_addr.s_addr = htonl(bindIP); - if (bind(rtpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + if (bind(rtpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) { CLOSESOCKETS; MAINMUTEX_UNLOCK @@ -509,11 +486,11 @@ int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transpar rtcpport++; } - memset(&addr,0,sizeof(struct sockaddr_in)); + memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(rtcpport); addr.sin_addr.s_addr = htonl(bindIP); - if (bind(rtcpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) + if (bind(rtcpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) { CLOSESOCKETS; MAINMUTEX_UNLOCK @@ -530,635 +507,637 @@ int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transpar void RTPUDPv4TransmitterNoBind::Destroy() { - if (!init) - return; + if (!init) + return; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK; - return; - } + MAINMUTEX_LOCK + if (!created) + { + MAINMUTEX_UNLOCK; + return; + } - if (localhostname) - { - RTPDeleteByteArray(localhostname,GetMemoryManager()); - localhostname = 0; - localhostnamelength = 0; - } + if (localhostname) + { + delete[] localhostname; + localhostname = 0; + localhostnamelength = 0; + } - CLOSESOCKETS; - destinations.Clear(); + CLOSESOCKETS; + destinations.Clear(); #ifdef RTP_SUPPORT_IPV4MULTICAST - multicastgroups.Clear(); + multicastgroups.Clear(); #endif // RTP_SUPPORT_IPV4MULTICAST - FlushPackets(); - ClearAcceptIgnoreInfo(); - localIPs.clear(); - created = false; + FlushPackets(); + ClearAcceptIgnoreInfo(); + localIPs.clear(); + created = false; - if (waitingfordata) - { - m_pAbortDesc->SendAbortSignal(); - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK - } - else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized + if (waitingfordata) + { + m_pAbortDesc->SendAbortSignal(); + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + MAINMUTEX_UNLOCK + WAITMUTEX_LOCK// to make sure that the WaitForIncomingData function ended + WAITMUTEX_UNLOCK +} +else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK +MAINMUTEX_UNLOCK } RTPTransmissionInfo *RTPUDPv4TransmitterNoBind::GetTransmissionInfo() { - if (!init) - return 0; +if (!init) +return 0; - MAINMUTEX_LOCK - RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionNoBindInfo(localIPs,rtpsock,rtcpsock,m_rtpPort,m_rtcpPort); - MAINMUTEX_UNLOCK - return tinf; +MAINMUTEX_LOCK +RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionNoBindInfo(localIPs, rtpsock, rtcpsock, m_rtpPort, m_rtcpPort); +MAINMUTEX_UNLOCK +return tinf; } void RTPUDPv4TransmitterNoBind::DeleteTransmissionInfo(RTPTransmissionInfo *i) { - if (!init) - return; +if (!init) +return; - RTPDelete(i, GetMemoryManager()); +delete i; } int RTPUDPv4TransmitterNoBind::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} - if (localhostname == 0) - { - if (localIPs.empty()) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOLOCALIPS; - } +if (localhostname == 0) +{ +if (localIPs.empty()) +{ + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_NOLOCALIPS; +} - std::list::const_iterator it; - std::list hostnames; +std::list::const_iterator it; +std::list hostnames; - for (it = localIPs.begin() ; it != localIPs.end() ; it++) - { - bool founddouble = false; - bool foundentry = true; +for (it = localIPs.begin(); it != localIPs.end(); it++) +{ + bool founddouble = false; + bool foundentry = true; - while (!founddouble && foundentry) - { - struct hostent *he; - uint8_t addr[4]; - uint32_t ip = (*it); + while (!founddouble && foundentry) + { + struct hostent *he; + uint8_t addr[4]; + uint32_t ip = (*it); - addr[0] = (uint8_t)((ip>>24)&0xFF); - addr[1] = (uint8_t)((ip>>16)&0xFF); - addr[2] = (uint8_t)((ip>>8)&0xFF); - addr[3] = (uint8_t)(ip&0xFF); - he = gethostbyaddr((char *)addr,4,AF_INET); - if (he != 0) - { - std::string hname = std::string(he->h_name); - std::list::const_iterator it; + addr[0] = (uint8_t) ((ip >> 24) & 0xFF); + addr[1] = (uint8_t) ((ip >> 16) & 0xFF); + addr[2] = (uint8_t) ((ip >> 8) & 0xFF); + addr[3] = (uint8_t) (ip & 0xFF); + he = gethostbyaddr((char *) addr, 4, AF_INET); + if (he != 0) + { + std::string hname = std::string(he->h_name); + std::list::const_iterator it; - for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) - if ((*it) == hname) - founddouble = true; + for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) + if ((*it) == hname) + founddouble = true; - if (!founddouble) - hostnames.push_back(hname); + if (!founddouble) + hostnames.push_back(hname); - int i = 0; - while (!founddouble && he->h_aliases[i] != 0) - { - std::string hname = std::string(he->h_aliases[i]); + int i = 0; + while (!founddouble && he->h_aliases[i] != 0) + { + std::string hname = std::string(he->h_aliases[i]); - for (it = hostnames.begin() ; !founddouble && it != hostnames.end() ; it++) - if ((*it) == hname) - founddouble = true; + for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) + if ((*it) == hname) + founddouble = true; - if (!founddouble) - { - hostnames.push_back(hname); - i++; - } - } - } - else - foundentry = false; - } - } + if (!founddouble) + { + hostnames.push_back(hname); + i++; + } + } + } + else + foundentry = false; + } +} - bool found = false; +bool found = false; - if (!hostnames.empty()) // try to select the most appropriate hostname - { - std::list::const_iterator it; +if (!hostnames.empty()) // try to select the most appropriate hostname +{ + std::list::const_iterator it; - hostnames.sort(); - for (it = hostnames.begin() ; !found && it != hostnames.end() ; it++) - { - if ((*it).find('.') != std::string::npos) - { - found = true; - localhostnamelength = (*it).length(); - localhostname = new uint8_t [localhostnamelength+1]; - if (localhostname == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname,(*it).c_str(),localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - } + hostnames.sort(); + for (it = hostnames.begin(); !found && it != hostnames.end(); it++) + { + if ((*it).find('.') != std::string::npos) + { + found = true; + localhostnamelength = (*it).length(); + localhostname = new uint8_t[localhostnamelength + 1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname, (*it).c_str(), localhostnamelength); + localhostname[localhostnamelength] = 0; + } + } +} - if (!found) // use an IP address - { - uint32_t ip; - int len; - char str[16]; +if (!found) // use an IP address +{ + uint32_t ip; + int len; + char str[16]; - it = localIPs.begin(); - ip = (*it); + it = localIPs.begin(); + ip = (*it); - RTP_SNPRINTF(str,16,"%d.%d.%d.%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF)); - len = strlen(str); + RTP_SNPRINTF(str, 16, "%d.%d.%d.%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF)); + len = strlen(str); - localhostnamelength = len; - localhostname = new uint8_t [localhostnamelength + 1]; - if (localhostname == 0) - { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname,str,localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } + localhostnamelength = len; + localhostname = new uint8_t[localhostnamelength + 1]; + if (localhostname == 0) + { + MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname, str, localhostnamelength); + localhostname[localhostnamelength] = 0; +} +} - if ((*bufferlength) < localhostnamelength) - { - *bufferlength = localhostnamelength; // tell the application the required size of the buffer - MAINMUTEX_UNLOCK - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } +if ((*bufferlength) < localhostnamelength) +{ +*bufferlength = localhostnamelength; // tell the application the required size of the buffer +MAINMUTEX_UNLOCK +return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; +} - memcpy(buffer,localhostname,localhostnamelength); - *bufferlength = localhostnamelength; +memcpy(buffer, localhostname, localhostnamelength); +*bufferlength = localhostnamelength; - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; } bool RTPUDPv4TransmitterNoBind::ComesFromThisTransmitter(const RTPAddress *addr) { - if (!init) - return false; +if (!init) +return false; - if (addr == 0) - return false; +if (addr == 0) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - bool v; +bool v; - if (created && addr->GetAddressType() == RTPAddress::IPv4Address) - { - const RTPIPv4Address *addr2 = (const RTPIPv4Address *)addr; - bool found = false; - std::list::const_iterator it; +if (created && addr->GetAddressType() == RTPAddress::IPv4Address) +{ +const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; +bool found = false; +std::list::const_iterator it; - it = localIPs.begin(); - while (!found && it != localIPs.end()) - { - if (addr2->GetIP() == *it) - found = true; - else - ++it; - } +it = localIPs.begin(); +while (!found && it != localIPs.end()) +{ + if (addr2->GetIP() == *it) + found = true; + else + ++it; +} - if (!found) - v = false; - else - { - if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port - v = true; - else - v = false; - } - } - else - v = false; +if (!found) + v = false; +else +{ + if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port + v = true; + else + v = false; +} +} +else +v = false; - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } int RTPUDPv4TransmitterNoBind::Poll() { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - int status; +int status; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - status = PollSocket(true); // poll RTP socket - if (rtpsock != rtcpsock) // no need to poll twice when multiplexing - { - if (status >= 0) - status = PollSocket(false); // poll RTCP socket - } - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +status = PollSocket(true); // poll RTP socket +if (rtpsock != rtcpsock) // no need to poll twice when multiplexing +{ +if (status >= 0) + status = PollSocket(false); // poll RTCP socket +} +MAINMUTEX_UNLOCK +return status; } -int RTPUDPv4TransmitterNoBind::WaitForIncomingData(const RTPTime &delay,bool *dataavailable) +int RTPUDPv4TransmitterNoBind::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (waitingfordata) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_ALREADYWAITING; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (waitingfordata) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_ALREADYWAITING; +} - SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); +SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); - SocketType socks[3] = { rtpsock, rtcpsock, abortSocket }; - int8_t readflags[3] = { 0, 0, 0 }; - const int idxRTP = 0; - const int idxRTCP = 1; - const int idxAbort = 2; +SocketType socks[3] = +{ rtpsock, rtcpsock, abortSocket }; +int8_t readflags[3] = +{ 0, 0, 0 }; +const int idxRTP = 0; +const int idxRTCP = 1; +const int idxAbort = 2; - waitingfordata = true; +waitingfordata = true; - WAITMUTEX_LOCK - MAINMUTEX_UNLOCK +WAITMUTEX_LOCK +MAINMUTEX_UNLOCK - int status = RTPSelect(socks, readflags, 3, delay); - if (status < 0) - { - MAINMUTEX_LOCK - waitingfordata = false; - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return status; - } +int status = RTPSelect(socks, readflags, 3, delay); +if (status < 0) +{ +MAINMUTEX_LOCK +waitingfordata = false; +MAINMUTEX_UNLOCK +WAITMUTEX_UNLOCK +return status; +} - MAINMUTEX_LOCK - waitingfordata = false; - if (!created) // destroy called - { - MAINMUTEX_UNLOCK; - WAITMUTEX_UNLOCK - return 0; - } +MAINMUTEX_LOCK +waitingfordata = false; +if (!created) // destroy called +{ +MAINMUTEX_UNLOCK; +WAITMUTEX_UNLOCK +return 0; +} - // if aborted, read from abort buffer - if (readflags[idxAbort]) - m_pAbortDesc->ReadSignallingByte(); + // if aborted, read from abort buffer +if (readflags[idxAbort]) +m_pAbortDesc->ReadSignallingByte(); - if (dataavailable != 0) - { - if (readflags[idxRTP] || readflags[idxRTCP]) - *dataavailable = true; - else - *dataavailable = false; - } +if (dataavailable != 0) +{ +if (readflags[idxRTP] || readflags[idxRTCP]) + *dataavailable = true; +else + *dataavailable = false; +} - MAINMUTEX_UNLOCK - WAITMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +WAITMUTEX_UNLOCK +return 0; } int RTPUDPv4TransmitterNoBind::AbortWait() { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (!waitingfordata) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTWAITING; - } - - m_pAbortDesc->SendAbortSignal(); - - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (!waitingfordata) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTWAITING; } -int RTPUDPv4TransmitterNoBind::SendRTPData(const void *data,size_t len) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +m_pAbortDesc->SendAbortSignal(); - MAINMUTEX_LOCK - - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTPSockAddr(),sizeof(struct sockaddr_in)); - destinations.GotoNextElement(); - } - - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; } -int RTPUDPv4TransmitterNoBind::SendRTCPData(const void *data,size_t len) +int RTPUDPv4TransmitterNoBind::SendRTPData(const void *data, size_t len) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (len > maxpacksize) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; +} - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtcpsock,(const char *)data,len,0,(const struct sockaddr *)destinations.GetCurrentElement().GetRTCPSockAddr(),sizeof(struct sockaddr_in)); - destinations.GotoNextElement(); - } +destinations.GotoFirstElement(); +while (destinations.HasCurrentElement()) +{ +sendto(rtpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTPSockAddr(), sizeof(struct sockaddr_in)); +destinations.GotoNextElement(); +} - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_UNLOCK +return 0; +} + +int RTPUDPv4TransmitterNoBind::SendRTCPData(const void *data, size_t len) +{ +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; + +MAINMUTEX_LOCK + +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (len > maxpacksize) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; +} + +destinations.GotoFirstElement(); +while (destinations.HasCurrentElement()) +{ +sendto(rtcpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTCPSockAddr(), sizeof(struct sockaddr_in)); +destinations.GotoNextElement(); +} + +MAINMUTEX_UNLOCK +return 0; } int RTPUDPv4TransmitterNoBind::AddDestination(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } +RTPIPv4Destination dest; +if (!RTPIPv4Destination::AddressToDestination(addr, dest)) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} - int status = destinations.AddElement(dest); +int status = destinations.AddElement(dest); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } int RTPUDPv4TransmitterNoBind::DeleteDestination(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +RTPIPv4Destination dest; +if (!RTPIPv4Destination::AddressToDestination(addr, dest)) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} - int status = destinations.DeleteElement(dest); +int status = destinations.DeleteElement(dest); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } void RTPUDPv4TransmitterNoBind::ClearDestinations() { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (created) - destinations.Clear(); - MAINMUTEX_UNLOCK +MAINMUTEX_LOCK +if (created) +destinations.Clear(); +MAINMUTEX_UNLOCK } bool RTPUDPv4TransmitterNoBind::SupportsMulticasting() { - if (!init) - return false; +if (!init) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - bool v; +bool v; - if (!created) - v = false; - else - v = supportsmulticasting; +if (!created) +v = false; +else +v = supportsmulticasting; - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } #ifdef RTP_SUPPORT_IPV4MULTICAST int RTPUDPv4TransmitterNoBind::JoinMulticastGroup(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - uint32_t mcastIP = address.GetIP(); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +uint32_t mcastIP = address.GetIP(); - if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; - } +if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; +} - status = multicastgroups.AddElement(mcastIP); - if (status >= 0) - { - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock,IP_ADD_MEMBERSHIP,mcastIP,status); - if (status != 0) - { - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; - } +status = multicastgroups.AddElement(mcastIP); +if (status >= 0) +{ +RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_ADD_MEMBERSHIP, mcastIP, status); +if (status != 0) +{ +multicastgroups.DeleteElement(mcastIP); +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; +} - if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing - { - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock,IP_ADD_MEMBERSHIP,mcastIP,status); - if (status != 0) - { - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; - } - } - } - MAINMUTEX_UNLOCK - return status; +if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing +{ +RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_ADD_MEMBERSHIP, mcastIP, status); +if (status != 0) +{ + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + multicastgroups.DeleteElement(mcastIP); + MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; +} +} +} +MAINMUTEX_UNLOCK +return status; } int RTPUDPv4TransmitterNoBind::LeaveMulticastGroup(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - uint32_t mcastIP = address.GetIP(); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +uint32_t mcastIP = address.GetIP(); - if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; - } +if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; +} - status = multicastgroups.DeleteElement(mcastIP); - if (status >= 0) - { - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); +status = multicastgroups.DeleteElement(mcastIP); +if (status >= 0) +{ +RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); +if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing +RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - status = 0; - } +status = 0; +} - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (created) - { - multicastgroups.GotoFirstElement(); - while (multicastgroups.HasCurrentElement()) - { - uint32_t mcastIP; - int status = 0; +MAINMUTEX_LOCK +if (created) +{ +multicastgroups.GotoFirstElement(); +while (multicastgroups.HasCurrentElement()) +{ +uint32_t mcastIP; +int status = 0; - mcastIP = multicastgroups.GetCurrentElement(); +mcastIP = multicastgroups.GetCurrentElement(); - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock,IP_DROP_MEMBERSHIP,mcastIP,status); - JRTPLIB_UNUSED(status); +RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); +if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); +JRTPLIB_UNUSED(status); - multicastgroups.GotoNextElement(); - } - multicastgroups.Clear(); - } - MAINMUTEX_UNLOCK +multicastgroups.GotoNextElement(); +} +multicastgroups.Clear(); +} +MAINMUTEX_UNLOCK } #else // no multicast support int RTPUDPv4TransmitterNoBind::JoinMulticastGroup(const RTPAddress &addr) { - return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; +return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; } int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) { - return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; +return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; } void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() @@ -1169,243 +1148,243 @@ void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() int RTPUDPv4TransmitterNoBind::SetReceiveMode(RTPTransmitter::ReceiveMode m) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (m != receivemode) - { - receivemode = m; - acceptignoreinfo.Clear(); - } - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (m != receivemode) +{ +receivemode = m; +acceptignoreinfo.Clear(); +} +MAINMUTEX_UNLOCK +return 0; } int RTPUDPv4TransmitterNoBind::AddToIgnoreList(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} +if (receivemode != RTPTransmitter::IgnoreSome) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } int RTPUDPv4TransmitterNoBind::DeleteFromIgnoreList(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} +if (receivemode != RTPTransmitter::IgnoreSome) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } void RTPUDPv4TransmitterNoBind::ClearIgnoreList() { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (created && receivemode == RTPTransmitter::IgnoreSome) - ClearAcceptIgnoreInfo(); - MAINMUTEX_UNLOCK +MAINMUTEX_LOCK +if (created && receivemode == RTPTransmitter::IgnoreSome) +ClearAcceptIgnoreInfo(); +MAINMUTEX_UNLOCK } int RTPUDPv4TransmitterNoBind::AddToAcceptList(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} +if (receivemode != RTPTransmitter::AcceptSome) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(),address.GetPort()); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } int RTPUDPv4TransmitterNoBind::DeleteFromAcceptList(const RTPAddress &addr) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - int status; +int status; - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (addr.GetAddressType() != RTPAddress::IPv4Address) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; +} +if (receivemode != RTPTransmitter::AcceptSome) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; +} - const RTPIPv4Address &address = (const RTPIPv4Address &)addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(),address.GetPort()); +const RTPIPv4Address &address = (const RTPIPv4Address &) addr; +status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - MAINMUTEX_UNLOCK - return status; +MAINMUTEX_UNLOCK +return status; } void RTPUDPv4TransmitterNoBind::ClearAcceptList() { - if (!init) - return; +if (!init) +return; - MAINMUTEX_LOCK - if (created && receivemode == RTPTransmitter::AcceptSome) - ClearAcceptIgnoreInfo(); - MAINMUTEX_UNLOCK +MAINMUTEX_LOCK +if (created && receivemode == RTPTransmitter::AcceptSome) +ClearAcceptIgnoreInfo(); +MAINMUTEX_UNLOCK } int RTPUDPv4TransmitterNoBind::SetMaximumPacketSize(size_t s) { - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; +if (!init) +return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (!created) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (s > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) - { - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - maxpacksize = s; - MAINMUTEX_UNLOCK - return 0; +MAINMUTEX_LOCK +if (!created) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_NOTCREATED; +} +if (s > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) +{ +MAINMUTEX_UNLOCK +return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; +} +maxpacksize = s; +MAINMUTEX_UNLOCK +return 0; } bool RTPUDPv4TransmitterNoBind::NewDataAvailable() { - if (!init) - return false; +if (!init) +return false; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - bool v; +bool v; - if (!created) - v = false; - else - { - if (rawpacketlist.empty()) - v = false; - else - v = true; - } +if (!created) +v = false; +else +{ +if (rawpacketlist.empty()) +v = false; +else +v = true; +} - MAINMUTEX_UNLOCK - return v; +MAINMUTEX_UNLOCK +return v; } RTPRawPacket *RTPUDPv4TransmitterNoBind::GetNextPacket() { - if (!init) - return 0; +if (!init) +return 0; - MAINMUTEX_LOCK +MAINMUTEX_LOCK - RTPRawPacket *p; +RTPRawPacket *p; - if (!created) - { - MAINMUTEX_UNLOCK - return 0; - } - if (rawpacketlist.empty()) - { - MAINMUTEX_UNLOCK - return 0; - } +if (!created) +{ +MAINMUTEX_UNLOCK +return 0; +} +if (rawpacketlist.empty()) +{ +MAINMUTEX_UNLOCK +return 0; +} - p = *(rawpacketlist.begin()); - rawpacketlist.pop_front(); +p = *(rawpacketlist.begin()); +rawpacketlist.pop_front(); - MAINMUTEX_UNLOCK - return p; +MAINMUTEX_UNLOCK +return p; } // Here the private functions start... @@ -1413,372 +1392,372 @@ RTPRawPacket *RTPUDPv4TransmitterNoBind::GetNextPacket() #ifdef RTP_SUPPORT_IPV4MULTICAST bool RTPUDPv4TransmitterNoBind::SetMulticastTTL(uint8_t ttl) { - int ttl2,status; +int ttl2, status; - ttl2 = (int)ttl; - status = setsockopt(rtpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); - if (status != 0) - return false; +ttl2 = (int) ttl; +status = setsockopt(rtpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); +if (status != 0) +return false; - if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing - { - status = setsockopt(rtcpsock,IPPROTO_IP,IP_MULTICAST_TTL,(const char *)&ttl2,sizeof(int)); - if (status != 0) - return false; - } - return true; +if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing +{ +status = setsockopt(rtcpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); +if (status != 0) +return false; +} +return true; } #endif // RTP_SUPPORT_IPV4MULTICAST void RTPUDPv4TransmitterNoBind::FlushPackets() { - std::list::const_iterator it; +std::list::const_iterator it; - for (it = rawpacketlist.begin() ; it != rawpacketlist.end() ; ++it) - RTPDelete(*it,GetMemoryManager()); - rawpacketlist.clear(); +for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) +delete *it; +rawpacketlist.clear(); } int RTPUDPv4TransmitterNoBind::PollSocket(bool rtp) { - RTPSOCKLENTYPE fromlen; - int recvlen; - char packetbuffer[RTPUDPV4TRANSNOBIND_MAXPACKSIZE]; +RTPSOCKLENTYPE fromlen; +int recvlen; +char packetbuffer[RTPUDPV4TRANSNOBIND_MAXPACKSIZE]; #ifdef RTP_SOCKETTYPE_WINSOCK - SOCKET sock; - unsigned long len; +SOCKET sock; +unsigned long len; #else - size_t len; - int sock; +size_t len; +int sock; #endif // RTP_SOCKETTYPE_WINSOCK - struct sockaddr_in srcaddr; - bool dataavailable; +struct sockaddr_in srcaddr; +bool dataavailable; - if (rtp) - sock = rtpsock; - else - sock = rtcpsock; +if (rtp) +sock = rtpsock; +else +sock = rtcpsock; - do - { - len = 0; - RTPIOCTL(sock,FIONREAD,&len); +do +{ +len = 0; +RTPIOCTL(sock, FIONREAD, &len); - if (len <= 0) // make sure a packet of length zero is not queued - { - // An alternative workaround would be to just use non-blocking sockets. - // However, since the user does have access to the sockets and I do not - // know how this would affect anyone else's code, I chose to do it using - // an extra select call in case ioctl says the length is zero. +if (len <= 0) // make sure a packet of length zero is not queued +{ + // An alternative workaround would be to just use non-blocking sockets. + // However, since the user does have access to the sockets and I do not + // know how this would affect anyone else's code, I chose to do it using + // an extra select call in case ioctl says the length is zero. - int8_t isset = 0; - int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); - if (status < 0) - return status; +int8_t isset = 0; +int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); +if (status < 0) +return status; - if (isset) - dataavailable = true; - else - dataavailable = false; - } - else - dataavailable = true; +if (isset) +dataavailable = true; +else +dataavailable = false; +} +else +dataavailable = true; - if (dataavailable) - { - RTPTime curtime = RTPTime::CurrentTime(); - fromlen = sizeof(struct sockaddr_in); - recvlen = recvfrom(sock,packetbuffer,RTPUDPV4TRANSNOBIND_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); - if (recvlen > 0) - { - bool acceptdata; +if (dataavailable) +{ +RTPTime curtime = RTPTime::CurrentTime(); +fromlen = sizeof(struct sockaddr_in); +recvlen = recvfrom(sock, packetbuffer, RTPUDPV4TRANSNOBIND_MAXPACKSIZE, 0, (struct sockaddr *) &srcaddr, &fromlen); +if (recvlen > 0) +{ +bool acceptdata; - // got data, process it - if (receivemode == RTPTransmitter::AcceptAll) - acceptdata = true; - else - acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); + // got data, process it +if (receivemode == RTPTransmitter::AcceptAll) +acceptdata = true; +else +acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); - if (acceptdata) - { - RTPRawPacket *pack; - RTPIPv4Address *addr; - uint8_t *datacopy; +if (acceptdata) +{ +RTPRawPacket *pack; +RTPIPv4Address *addr; +uint8_t *datacopy; - addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr),ntohs(srcaddr.sin_port)); - if (addr == 0) - return ERR_RTP_OUTOFMEM; - datacopy = new uint8_t[recvlen]; - if (datacopy == 0) - { - RTPDelete(addr,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - memcpy(datacopy,packetbuffer,recvlen); +addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); +if (addr == 0) +return ERR_RTP_OUTOFMEM; +datacopy = new uint8_t[recvlen]; +if (datacopy == 0) +{ +delete addr; +return ERR_RTP_OUTOFMEM; +} +memcpy(datacopy, packetbuffer, recvlen); - bool isrtp = rtp; - if (rtpsock == rtcpsock) // check payload type when multiplexing - { - isrtp = true; +bool isrtp = rtp; +if (rtpsock == rtcpsock) // check payload type when multiplexing +{ +isrtp = true; - if ((size_t)recvlen > sizeof(RTCPCommonHeader)) - { - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *)datacopy; - uint8_t packettype = rtcpheader->packettype; +if ((size_t) recvlen > sizeof(RTCPCommonHeader)) +{ + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; + uint8_t packettype = rtcpheader->packettype; - if (packettype >= 200 && packettype <= 204) - isrtp = false; - } - } - - pack = new RTPRawPacket(datacopy,recvlen,addr,curtime,isrtp,GetMemoryManager()); - if (pack == 0) - { - RTPDelete(addr,GetMemoryManager()); - RTPDeleteByteArray(datacopy,GetMemoryManager()); - return ERR_RTP_OUTOFMEM; - } - rawpacketlist.push_back(pack); - } - } - } - } while (dataavailable); - - return 0; + if (packettype >= 200 && packettype <= 204) + isrtp = false; +} } -int RTPUDPv4TransmitterNoBind::ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port) +pack = new RTPRawPacket(datacopy, recvlen, addr, curtime, isrtp); +if (pack == 0) { - acceptignoreinfo.GotoElement(ip); - if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists - { - PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); +delete addr; +delete[] datacopy; +return ERR_RTP_OUTOFMEM; +} +rawpacketlist.push_back(pack); +} +} +} +} while (dataavailable); - if (port == 0) // select all ports - { - portinf->all = true; - portinf->portlist.clear(); - } - else if (!portinf->all) - { - std::list::const_iterator it,begin,end; +return 0; +} - begin = portinf->portlist.begin(); - end = portinf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == port) // already in list - return 0; - } - portinf->portlist.push_front(port); - } - } - else // got to create an entry for this IP address - { - PortInfo *portinf; - int status; +int RTPUDPv4TransmitterNoBind::ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port) +{ +acceptignoreinfo.GotoElement(ip); +if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists +{ +PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); - portinf = new PortInfo(); - if (port == 0) // select all ports - portinf->all = true; - else - portinf->portlist.push_front(port); +if (port == 0) // select all ports +{ +portinf->all = true; +portinf->portlist.clear(); +} +else if (!portinf->all) +{ +std::list::const_iterator it, begin, end; - status = acceptignoreinfo.AddElement(ip,portinf); - if (status < 0) - { - RTPDelete(portinf,GetMemoryManager()); - return status; - } - } +begin = portinf->portlist.begin(); +end = portinf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == port) // already in list +return 0; +} +portinf->portlist.push_front(port); +} +} +else // got to create an entry for this IP address +{ +PortInfo *portinf; +int status; - return 0; +portinf = new PortInfo(); +if (port == 0) // select all ports +portinf->all = true; +else +portinf->portlist.push_front(port); + +status = acceptignoreinfo.AddElement(ip, portinf); +if (status < 0) +{ +delete portinf; +return status; +} +} + +return 0; } void RTPUDPv4TransmitterNoBind::ClearAcceptIgnoreInfo() { - acceptignoreinfo.GotoFirstElement(); - while (acceptignoreinfo.HasCurrentElement()) - { - PortInfo *inf; +acceptignoreinfo.GotoFirstElement(); +while (acceptignoreinfo.HasCurrentElement()) +{ +PortInfo *inf; - inf = acceptignoreinfo.GetCurrentElement(); - RTPDelete(inf,GetMemoryManager()); - acceptignoreinfo.GotoNextElement(); - } - acceptignoreinfo.Clear(); +inf = acceptignoreinfo.GetCurrentElement(); +delete inf; +acceptignoreinfo.GotoNextElement(); +} +acceptignoreinfo.Clear(); } -int RTPUDPv4TransmitterNoBind::ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port) +int RTPUDPv4TransmitterNoBind::ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port) { - acceptignoreinfo.GotoElement(ip); - if (!acceptignoreinfo.HasCurrentElement()) - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; +acceptignoreinfo.GotoElement(ip); +if (!acceptignoreinfo.HasCurrentElement()) +return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - PortInfo *inf; +PortInfo *inf; - inf = acceptignoreinfo.GetCurrentElement(); - if (port == 0) // delete all entries - { - inf->all = false; - inf->portlist.clear(); - } - else // a specific port was selected - { - if (inf->all) // currently, all ports are selected. Add the one to remove to the list - { - // we have to check if the list doesn't contain the port already - std::list::const_iterator it,begin,end; +inf = acceptignoreinfo.GetCurrentElement(); +if (port == 0) // delete all entries +{ +inf->all = false; +inf->portlist.clear(); +} +else // a specific port was selected +{ +if (inf->all) // currently, all ports are selected. Add the one to remove to the list +{ + // we have to check if the list doesn't contain the port already +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == port) // already in list: this means we already deleted the entry - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - } - inf->portlist.push_front(port); - } - else // check if we can find the port in the list - { - std::list::iterator it,begin,end; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == port) // already in list: this means we already deleted the entry +return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; +} +inf->portlist.push_front(port); +} +else // check if we can find the port in the list +{ +std::list::iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; ++it) - { - if (*it == port) // found it! - { - inf->portlist.erase(it); - return 0; - } - } - // didn't find it - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - } - } - return 0; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; ++it) +{ +if (*it == port) // found it! +{ +inf->portlist.erase(it); +return 0; +} +} + // didn't find it +return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; +} +} +return 0; } -bool RTPUDPv4TransmitterNoBind::ShouldAcceptData(uint32_t srcip,uint16_t srcport) +bool RTPUDPv4TransmitterNoBind::ShouldAcceptData(uint32_t srcip, uint16_t srcport) { - if (receivemode == RTPTransmitter::AcceptSome) - { - PortInfo *inf; +if (receivemode == RTPTransmitter::AcceptSome) +{ +PortInfo *inf; - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return false; +acceptignoreinfo.GotoElement(srcip); +if (!acceptignoreinfo.HasCurrentElement()) +return false; - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // only accept the ones in the list - { - std::list::const_iterator it,begin,end; +inf = acceptignoreinfo.GetCurrentElement(); +if (!inf->all) // only accept the ones in the list +{ +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return true; - } - return false; - } - else // accept all, except the ones in the list - { - std::list::const_iterator it,begin,end; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == srcport) +return true; +} +return false; +} +else // accept all, except the ones in the list +{ +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return false; - } - return true; - } - } - else // IgnoreSome - { - PortInfo *inf; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == srcport) +return false; +} +return true; +} +} +else // IgnoreSome +{ +PortInfo *inf; - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return true; +acceptignoreinfo.GotoElement(srcip); +if (!acceptignoreinfo.HasCurrentElement()) +return true; - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // ignore the ports in the list - { - std::list::const_iterator it,begin,end; +inf = acceptignoreinfo.GetCurrentElement(); +if (!inf->all) // ignore the ports in the list +{ +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return false; - } - return true; - } - else // ignore all, except the ones in the list - { - std::list::const_iterator it,begin,end; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == srcport) +return false; +} +return true; +} +else // ignore all, except the ones in the list +{ +std::list::const_iterator it, begin, end; - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin ; it != end ; it++) - { - if (*it == srcport) - return true; - } - return false; - } - } - return true; +begin = inf->portlist.begin(); +end = inf->portlist.end(); +for (it = begin; it != end; it++) +{ +if (*it == srcport) +return true; +} +return false; +} +} +return true; } int RTPUDPv4TransmitterNoBind::CreateLocalIPList() { - // first try to obtain the list from the network interface info + // first try to obtain the list from the network interface info - if (!GetLocalIPList_Interfaces()) - { - // If this fails, we'll have to depend on DNS info - GetLocalIPList_DNS(); - } - AddLoopbackAddress(); - return 0; +if (!GetLocalIPList_Interfaces()) +{ + // If this fails, we'll have to depend on DNS info +GetLocalIPList_DNS(); +} +AddLoopbackAddress(); +return 0; } #ifdef RTP_SOCKETTYPE_WINSOCK bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() { - unsigned char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; - DWORD outputsize; - DWORD numaddresses,i; - SOCKET_ADDRESS_LIST *addrlist; +unsigned char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; +DWORD outputsize; +DWORD numaddresses,i; +SOCKET_ADDRESS_LIST *addrlist; - if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANSNOBIND_IFREQBUFSIZE,&outputsize,NULL,NULL)) - return false; +if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANSNOBIND_IFREQBUFSIZE,&outputsize,NULL,NULL)) +return false; - addrlist = (SOCKET_ADDRESS_LIST *)buffer; - numaddresses = addrlist->iAddressCount; - for (i = 0 ; i < numaddresses ; i++) - { - SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); - if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address - { - struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; +addrlist = (SOCKET_ADDRESS_LIST *)buffer; +numaddresses = addrlist->iAddressCount; +for (i = 0; i < numaddresses; i++) +{ +SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); +if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address +{ +struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; - localIPs.push_back(ntohl(addr->sin_addr.s_addr)); - } - } +localIPs.push_back(ntohl(addr->sin_addr.s_addr)); +} +} - if (localIPs.empty()) - return false; +if (localIPs.empty()) +return false; - return true; +return true; } #else // use either getifaddrs or ioctl @@ -1787,92 +1766,92 @@ bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() { - struct ifaddrs *addrs,*tmp; +struct ifaddrs *addrs, *tmp; - getifaddrs(&addrs); - tmp = addrs; +getifaddrs(&addrs); +tmp = addrs; - while (tmp != 0) - { - if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) - { - struct sockaddr_in *inaddr = (struct sockaddr_in *)tmp->ifa_addr; - localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); - } - tmp = tmp->ifa_next; - } +while (tmp != 0) +{ +if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) +{ +struct sockaddr_in *inaddr = (struct sockaddr_in *) tmp->ifa_addr; +localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); +} +tmp = tmp->ifa_next; +} - freeifaddrs(addrs); +freeifaddrs(addrs); - if (localIPs.empty()) - return false; - return true; +if (localIPs.empty()) +return false; +return true; } #else // user ioctl bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() { - int status; - char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; - struct ifconf ifc; - struct ifreq *ifr; - struct sockaddr *sa; - char *startptr,*endptr; - int remlen; +int status; +char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; +struct ifconf ifc; +struct ifreq *ifr; +struct sockaddr *sa; +char *startptr,*endptr; +int remlen; - ifc.ifc_len = RTPUDPV4TRANSNOBIND_IFREQBUFSIZE; - ifc.ifc_buf = buffer; - status = ioctl(rtpsock,SIOCGIFCONF,&ifc); - if (status < 0) - return false; +ifc.ifc_len = RTPUDPV4TRANSNOBIND_IFREQBUFSIZE; +ifc.ifc_buf = buffer; +status = ioctl(rtpsock,SIOCGIFCONF,&ifc); +if (status < 0) +return false; - startptr = (char *)ifc.ifc_req; - endptr = startptr + ifc.ifc_len; - remlen = ifc.ifc_len; - while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) - { - ifr = (struct ifreq *)startptr; - sa = &(ifr->ifr_addr); +startptr = (char *)ifc.ifc_req; +endptr = startptr + ifc.ifc_len; +remlen = ifc.ifc_len; +while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) +{ +ifr = (struct ifreq *)startptr; +sa = &(ifr->ifr_addr); #ifdef RTP_HAVE_SOCKADDR_LEN - if (sa->sa_len <= sizeof(struct sockaddr)) - { - if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; +if (sa->sa_len <= sizeof(struct sockaddr)) +{ +if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) +{ +uint32_t ip; +struct sockaddr_in *addr = (struct sockaddr_in *)sa; - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); - } - else - { - int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); +ip = ntohl(addr->sin_addr.s_addr); +localIPs.push_back(ip); +} +remlen -= sizeof(struct ifreq); +startptr += sizeof(struct ifreq); +} +else +{ +int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); - remlen -= l; - startptr += l; - } +remlen -= l; +startptr += l; +} #else // don't have sa_len in struct sockaddr - if (sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; +if (sa->sa_family == PF_INET) +{ +uint32_t ip; +struct sockaddr_in *addr = (struct sockaddr_in *)sa; - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); +ip = ntohl(addr->sin_addr.s_addr); +localIPs.push_back(ip); +} +remlen -= sizeof(struct ifreq); +startptr += sizeof(struct ifreq); #endif // RTP_HAVE_SOCKADDR_LEN - } +} - if (localIPs.empty()) - return false; - return true; +if (localIPs.empty()) +return false; +return true; } #endif // RTP_SUPPORT_IFADDRS @@ -1881,49 +1860,49 @@ bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() void RTPUDPv4TransmitterNoBind::GetLocalIPList_DNS() { - struct hostent *he; - char name[1024]; - bool done; - int i,j; +struct hostent *he; +char name[1024]; +bool done; +int i, j; - gethostname(name,1023); - name[1023] = 0; - he = gethostbyname(name); - if (he == 0) - return; +gethostname(name, 1023); +name[1023] = 0; +he = gethostbyname(name); +if (he == 0) +return; - i = 0; - done = false; - while (!done) - { - if (he->h_addr_list[i] == NULL) - done = true; - else - { - uint32_t ip = 0; +i = 0; +done = false; +while (!done) +{ +if (he->h_addr_list[i] == NULL) +done = true; +else +{ +uint32_t ip = 0; - for (j = 0 ; j < 4 ; j++) - ip |= ((uint32_t)((unsigned char)he->h_addr_list[i][j])<<((3-j)*8)); - localIPs.push_back(ip); - i++; - } - } +for (j = 0; j < 4; j++) +ip |= ((uint32_t) ((unsigned char) he->h_addr_list[i][j]) << ((3 - j) * 8)); +localIPs.push_back(ip); +i++; +} +} } void RTPUDPv4TransmitterNoBind::AddLoopbackAddress() { - uint32_t loopbackaddr = (((uint32_t)127)<<24)|((uint32_t)1); - std::list::const_iterator it; - bool found = false; +uint32_t loopbackaddr = (((uint32_t) 127) << 24) | ((uint32_t) 1); +std::list::const_iterator it; +bool found = false; - for (it = localIPs.begin() ; !found && it != localIPs.end() ; it++) - { - if (*it == loopbackaddr) - found = true; - } +for (it = localIPs.begin(); !found && it != localIPs.end(); it++) +{ +if (*it == loopbackaddr) +found = true; +} - if (!found) - localIPs.push_back(loopbackaddr); +if (!found) +localIPs.push_back(loopbackaddr); } } // end namespace diff --git a/qrtplib/rtpudpv4transmitternobind.h b/qrtplib/rtpudpv4transmitternobind.h index 1c3d425d6..28ae47986 100644 --- a/qrtplib/rtpudpv4transmitternobind.h +++ b/qrtplib/rtpudpv4transmitternobind.h @@ -1,34 +1,34 @@ /* - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtpudpv4transmitternobind.h @@ -59,186 +59,309 @@ namespace qrtplib { /** Parameters for the UDP over IPv4 transmitter that does not automatically bind sockets */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionNoBindParams : public RTPTransmissionParams +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionNoBindParams: public RTPTransmissionParams { public: RTPUDPv4TransmissionNoBindParams(); - /** Sets the IP address which is used to bind the sockets to \c ip. */ - void SetBindIP(uint32_t ip) { bindIP = ip; } + /** Sets the IP address which is used to bind the sockets to \c ip. */ + void SetBindIP(uint32_t ip) + { + bindIP = ip; + } - /** Sets the multicast interface IP address. */ - void SetMulticastInterfaceIP(uint32_t ip) { mcastifaceIP = ip; } + /** Sets the multicast interface IP address. */ + void SetMulticastInterfaceIP(uint32_t ip) + { + mcastifaceIP = ip; + } - /** Sets the RTP portbase to \c pbase, which has to be an even number - * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; - * a port number of zero will cause a port to be chosen automatically. */ - void SetPortbase(uint16_t pbase) { portbase = pbase; } + /** Sets the RTP portbase to \c pbase, which has to be an even number + * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; + * a port number of zero will cause a port to be chosen automatically. */ + void SetPortbase(uint16_t pbase) + { + portbase = pbase; + } - /** Sets the multicast TTL to be used to \c mcastTTL. */ - void SetMulticastTTL(uint8_t mcastTTL) { multicastTTL = mcastTTL; } + /** Sets the multicast TTL to be used to \c mcastTTL. */ + void SetMulticastTTL(uint8_t mcastTTL) + { + multicastTTL = mcastTTL; + } - /** Passes a list of IP addresses which will be used as the local IP addresses. */ - void SetLocalIPList(std::list &iplist) { localIPs = iplist; } + /** Passes a list of IP addresses which will be used as the local IP addresses. */ + void SetLocalIPList(std::list &iplist) + { + localIPs = iplist; + } - /** Clears the list of local IP addresses. - * Clears the list of local IP addresses. An empty list will make the transmission - * component itself determine the local IP addresses. - */ - void ClearLocalIPList() { localIPs.clear(); } + /** Clears the list of local IP addresses. + * Clears the list of local IP addresses. An empty list will make the transmission + * component itself determine the local IP addresses. + */ + void ClearLocalIPList() + { + localIPs.clear(); + } - /** Returns the IP address which will be used to bind the sockets. */ - uint32_t GetBindIP() const { return bindIP; } + /** Returns the IP address which will be used to bind the sockets. */ + uint32_t GetBindIP() const + { + return bindIP; + } - /** Returns the multicast interface IP address. */ - uint32_t GetMulticastInterfaceIP() const { return mcastifaceIP; } + /** Returns the multicast interface IP address. */ + uint32_t GetMulticastInterfaceIP() const + { + return mcastifaceIP; + } - /** Returns the RTP portbase which will be used (default is 5000). */ - uint16_t GetPortbase() const { return portbase; } + /** Returns the RTP portbase which will be used (default is 5000). */ + uint16_t GetPortbase() const + { + return portbase; + } - /** Returns the multicast TTL which will be used (default is 1). */ - uint8_t GetMulticastTTL() const { return multicastTTL; } + /** Returns the multicast TTL which will be used (default is 1). */ + uint8_t GetMulticastTTL() const + { + return multicastTTL; + } - /** Returns the list of local IP addresses. */ - const std::list &GetLocalIPList() const { return localIPs; } + /** Returns the list of local IP addresses. */ + const std::list &GetLocalIPList() const + { + return localIPs; + } - /** Sets the RTP socket's send buffer size. */ - void SetRTPSendBuffer(int s) { rtpsendbuf = s; } + /** Sets the RTP socket's send buffer size. */ + void SetRTPSendBuffer(int s) + { + rtpsendbuf = s; + } - /** Sets the RTP socket's receive buffer size. */ - void SetRTPReceiveBuffer(int s) { rtprecvbuf = s; } + /** Sets the RTP socket's receive buffer size. */ + void SetRTPReceiveBuffer(int s) + { + rtprecvbuf = s; + } - /** Sets the RTCP socket's send buffer size. */ - void SetRTCPSendBuffer(int s) { rtcpsendbuf = s; } + /** Sets the RTCP socket's send buffer size. */ + void SetRTCPSendBuffer(int s) + { + rtcpsendbuf = s; + } - /** Sets the RTCP socket's receive buffer size. */ - void SetRTCPReceiveBuffer(int s) { rtcprecvbuf = s; } + /** Sets the RTCP socket's receive buffer size. */ + void SetRTCPReceiveBuffer(int s) + { + rtcprecvbuf = s; + } - /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ - void SetRTCPMultiplexing(bool f) { rtcpmux = f; } + /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ + void SetRTCPMultiplexing(bool f) + { + rtcpmux = f; + } - /** Can be used to allow the RTP port base to be any number, not just even numbers. */ - void SetAllowOddPortbase(bool f) { allowoddportbase = f; } + /** Can be used to allow the RTP port base to be any number, not just even numbers. */ + void SetAllowOddPortbase(bool f) + { + allowoddportbase = f; + } - /** Force the RTCP socket to use a specific port, not necessarily one more than - * the RTP port (set this to zero to disable). */ - void SetForcedRTCPPort(uint16_t rtcpport) { forcedrtcpport = rtcpport; } + /** Force the RTCP socket to use a specific port, not necessarily one more than + * the RTP port (set this to zero to disable). */ + void SetForcedRTCPPort(uint16_t rtcpport) + { + forcedrtcpport = rtcpport; + } - /** Use sockets that have already been created, no checks on port numbers - * will be done, and no buffer sizes will be set; you'll need to close - * the sockets yourself when done, it will **not** be done automatically. */ - void SetUseExistingSockets(SocketType rtpsocket, SocketType rtcpsocket) { rtpsock = rtpsocket; rtcpsock = rtcpsocket; useexistingsockets = true; } + /** Use sockets that have already been created, no checks on port numbers + * will be done, and no buffer sizes will be set; you'll need to close + * the sockets yourself when done, it will **not** be done automatically. */ + void SetUseExistingSockets(SocketType rtpsocket, SocketType rtcpsocket) + { + rtpsock = rtpsocket; + rtcpsock = rtcpsocket; + useexistingsockets = true; + } - /** If non null, the specified abort descriptors will be used to cancel - * the function that's waiting for packets to arrive; set to null (the default - * to let the transmitter create its own instance. */ - void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) { m_pAbortDesc = desc; } + /** If non null, the specified abort descriptors will be used to cancel + * the function that's waiting for packets to arrive; set to null (the default + * to let the transmitter create its own instance. */ + void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) + { + m_pAbortDesc = desc; + } - /** Returns the RTP socket's send buffer size. */ - int GetRTPSendBuffer() const { return rtpsendbuf; } + /** Returns the RTP socket's send buffer size. */ + int GetRTPSendBuffer() const + { + return rtpsendbuf; + } - /** Returns the RTP socket's receive buffer size. */ - int GetRTPReceiveBuffer() const { return rtprecvbuf; } + /** Returns the RTP socket's receive buffer size. */ + int GetRTPReceiveBuffer() const + { + return rtprecvbuf; + } - /** Returns the RTCP socket's send buffer size. */ - int GetRTCPSendBuffer() const { return rtcpsendbuf; } + /** Returns the RTCP socket's send buffer size. */ + int GetRTCPSendBuffer() const + { + return rtcpsendbuf; + } - /** Returns the RTCP socket's receive buffer size. */ - int GetRTCPReceiveBuffer() const { return rtcprecvbuf; } + /** Returns the RTCP socket's receive buffer size. */ + int GetRTCPReceiveBuffer() const + { + return rtcprecvbuf; + } - /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ - bool GetRTCPMultiplexing() const { return rtcpmux; } + /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ + bool GetRTCPMultiplexing() const + { + return rtcpmux; + } - /** If true, any RTP portbase will be allowed, not just even numbers. */ - bool GetAllowOddPortbase() const { return allowoddportbase; } + /** If true, any RTP portbase will be allowed, not just even numbers. */ + bool GetAllowOddPortbase() const + { + return allowoddportbase; + } - /** If non-zero, the specified port will be used to receive RTCP traffic. */ - uint16_t GetForcedRTCPPort() const { return forcedrtcpport; } + /** If non-zero, the specified port will be used to receive RTCP traffic. */ + uint16_t GetForcedRTCPPort() const + { + return forcedrtcpport; + } - /** Returns true and fills in sockets if existing sockets were set - * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ - bool GetUseExistingSockets(SocketType &rtpsocket, SocketType &rtcpsocket) const { if (!useexistingsockets) return false; rtpsocket = rtpsock; rtcpsocket = rtcpsock; return true; } + /** Returns true and fills in sockets if existing sockets were set + * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ + bool GetUseExistingSockets(SocketType &rtpsocket, SocketType &rtcpsocket) const + { + if (!useexistingsockets) + return false; + rtpsocket = rtpsock; + rtcpsocket = rtcpsock; + return true; + } - /** If non-null, this RTPAbortDescriptors instance will be used internally, - * which can be useful when creating your own poll thread for multiple - * sessions. */ - RTPAbortDescriptors *GetCreatedAbortDescriptors() const { return m_pAbortDesc; } + /** If non-null, this RTPAbortDescriptors instance will be used internally, + * which can be useful when creating your own poll thread for multiple + * sessions. */ + RTPAbortDescriptors *GetCreatedAbortDescriptors() const + { + return m_pAbortDesc; + } private: - uint16_t portbase; - uint32_t bindIP, mcastifaceIP; - std::list localIPs; - uint8_t multicastTTL; - int rtpsendbuf, rtprecvbuf; - int rtcpsendbuf, rtcprecvbuf; - bool rtcpmux; - bool allowoddportbase; - uint16_t forcedrtcpport; + uint16_t portbase; + uint32_t bindIP, mcastifaceIP; + std::list localIPs; + uint8_t multicastTTL; + int rtpsendbuf, rtprecvbuf; + int rtcpsendbuf, rtcprecvbuf; + bool rtcpmux; + bool allowoddportbase; + uint16_t forcedrtcpport; - SocketType rtpsock, rtcpsock; - bool useexistingsockets; + SocketType rtpsock, rtcpsock; + bool useexistingsockets; - RTPAbortDescriptors *m_pAbortDesc; + RTPAbortDescriptors *m_pAbortDesc; }; -inline RTPUDPv4TransmissionNoBindParams::RTPUDPv4TransmissionNoBindParams() : RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) +inline RTPUDPv4TransmissionNoBindParams::RTPUDPv4TransmissionNoBindParams() : + RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) { - portbase = RTPUDPV4TRANSNOBIND_DEFAULTPORTBASE; - bindIP = 0; - multicastTTL = 1; - mcastifaceIP = 0; - rtpsendbuf = RTPUDPV4TRANSNOBIND_RTPTRANSMITBUFFER; - rtprecvbuf = RTPUDPV4TRANSNOBIND_RTPRECEIVEBUFFER; - rtcpsendbuf = RTPUDPV4TRANSNOBIND_RTCPTRANSMITBUFFER; - rtcprecvbuf = RTPUDPV4TRANSNOBIND_RTCPRECEIVEBUFFER; - rtcpmux = false; - allowoddportbase = false; - forcedrtcpport = 0; - useexistingsockets = false; - rtpsock = 0; - rtcpsock = 0; - m_pAbortDesc = 0; + portbase = RTPUDPV4TRANSNOBIND_DEFAULTPORTBASE; + bindIP = 0; + multicastTTL = 1; + mcastifaceIP = 0; + rtpsendbuf = RTPUDPV4TRANSNOBIND_RTPTRANSMITBUFFER; + rtprecvbuf = RTPUDPV4TRANSNOBIND_RTPRECEIVEBUFFER; + rtcpsendbuf = RTPUDPV4TRANSNOBIND_RTCPTRANSMITBUFFER; + rtcprecvbuf = RTPUDPV4TRANSNOBIND_RTCPRECEIVEBUFFER; + rtcpmux = false; + allowoddportbase = false; + forcedrtcpport = 0; + useexistingsockets = false; + rtpsock = 0; + rtcpsock = 0; + m_pAbortDesc = 0; } /** Additional information about the UDP over IPv4 transmitter that does not automatically bind sockets. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionNoBindInfo : public RTPTransmissionInfo +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionNoBindInfo: public RTPTransmissionInfo { public: - RTPUDPv4TransmissionNoBindInfo(std::list iplist,SocketType rtpsock,SocketType rtcpsock, - uint16_t rtpport, uint16_t rtcpport) : RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) - { localIPlist = iplist; rtpsocket = rtpsock; rtcpsocket = rtcpsock; m_rtpPort = rtpport; m_rtcpPort = rtcpport; } + RTPUDPv4TransmissionNoBindInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : + RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) + { + localIPlist = iplist; + rtpsocket = rtpsock; + rtcpsocket = rtcpsock; + m_rtpPort = rtpport; + m_rtcpPort = rtcpport; + } - ~RTPUDPv4TransmissionNoBindInfo() { } + ~RTPUDPv4TransmissionNoBindInfo() + { + } - /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ - std::list GetLocalIPList() const { return localIPlist; } + /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ + std::list GetLocalIPList() const + { + return localIPlist; + } - /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ - SocketType GetRTPSocket() const { return rtpsocket; } + /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ + SocketType GetRTPSocket() const + { + return rtpsocket; + } - /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ - SocketType GetRTCPSocket() const { return rtcpsocket; } + /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ + SocketType GetRTCPSocket() const + { + return rtcpsocket; + } - /** Returns the port number that the RTP socket receives packets on. */ - uint16_t GetRTPPort() const { return m_rtpPort; } + /** Returns the port number that the RTP socket receives packets on. */ + uint16_t GetRTPPort() const + { + return m_rtpPort; + } - /** Returns the port number that the RTCP socket receives packets on. */ - uint16_t GetRTCPPort() const { return m_rtcpPort; } + /** Returns the port number that the RTCP socket receives packets on. */ + uint16_t GetRTCPPort() const + { + return m_rtcpPort; + } private: - std::list localIPlist; - SocketType rtpsocket,rtcpsocket; - uint16_t m_rtpPort, m_rtcpPort; + std::list localIPlist; + SocketType rtpsocket, rtcpsocket; + uint16_t m_rtpPort, m_rtcpPort; }; class JRTPLIB_IMPORTEXPORT RTPUDPv4TransNoBind_GetHashIndex_IPv4Dest { public: - static int GetIndex(const RTPIPv4Destination &d) { return d.GetIP()%RTPUDPV4TRANSNOBIND_HASHSIZE; } + static int GetIndex(const RTPIPv4Destination &d) + { + return d.GetIP() % RTPUDPV4TRANSNOBIND_HASHSIZE; + } }; class JRTPLIB_IMPORTEXPORT RTPUDPv4TransNoBind_GetHashIndex_uint32_t { public: - static int GetIndex(const uint32_t &k) { return k%RTPUDPV4TRANSNOBIND_HASHSIZE; } + static int GetIndex(const uint32_t &k) + { + return k % RTPUDPV4TRANSNOBIND_HASHSIZE; + } }; #define RTPUDPV4TRANSNOBIND_HEADERSIZE (20+8) @@ -252,108 +375,112 @@ public: * This flavor of a RTPUDPv4Transmitter class does not automatically bind sockets. Use the * BindSockets method to do so. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmitterNoBind : public RTPTransmitter +class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmitterNoBind: public RTPTransmitter { public: - RTPUDPv4TransmitterNoBind(RTPMemoryManager *mgr); - ~RTPUDPv4TransmitterNoBind(); + RTPUDPv4TransmitterNoBind(); + ~RTPUDPv4TransmitterNoBind(); - int Init(bool treadsafe); - int Create(size_t maxpacksize,const RTPTransmissionParams *transparams); - /** Bind the RTP and RTCP sockets to ports defined in the transmission parameters */ - int BindSockets(const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + int Init(bool treadsafe); + int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + /** Bind the RTP and RTCP sockets to ports defined in the transmission parameters */ + int BindSockets(const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer,size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() { return RTPUDPV4TRANSNOBIND_HEADERSIZE; } + int GetLocalHostName(uint8_t *buffer, size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + size_t GetHeaderOverhead() + { + return RTPUDPV4TRANSNOBIND_HEADERSIZE; + } - int Poll(); - int WaitForIncomingData(const RTPTime &delay,bool *dataavailable = 0); - int AbortWait(); + int Poll(); + int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); + int AbortWait(); - int SendRTPData(const void *data,size_t len); - int SendRTCPData(const void *data,size_t len); + int SendRTPData(const void *data, size_t len); + int SendRTCPData(const void *data, size_t len); - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(size_t s); - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); private: - int CreateLocalIPList(); - bool GetLocalIPList_Interfaces(); - void GetLocalIPList_DNS(); - void AddLoopbackAddress(); - void FlushPackets(); - int PollSocket(bool rtp); - int ProcessAddAcceptIgnoreEntry(uint32_t ip,uint16_t port); - int ProcessDeleteAcceptIgnoreEntry(uint32_t ip,uint16_t port); + int CreateLocalIPList(); + bool GetLocalIPList_Interfaces(); + void GetLocalIPList_DNS(); + void AddLoopbackAddress(); + void FlushPackets(); + int PollSocket(bool rtp); + int ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port); + int ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port); #ifdef RTP_SUPPORT_IPV4MULTICAST - bool SetMulticastTTL(uint8_t ttl); + bool SetMulticastTTL(uint8_t ttl); #endif // RTP_SUPPORT_IPV4MULTICAST - bool ShouldAcceptData(uint32_t srcip,uint16_t srcport); - void ClearAcceptIgnoreInfo(); + bool ShouldAcceptData(uint32_t srcip, uint16_t srcport); + void ClearAcceptIgnoreInfo(); - int GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, - SocketType *pRtpSock, SocketType *pRtcpSock, - uint16_t *pRtpPort, uint16_t *pRtcpPort); - static int GetIPv4SocketPort(SocketType s, uint16_t *pPort); + int GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, SocketType *pRtpSock, SocketType *pRtcpSock, uint16_t *pRtpPort, uint16_t *pRtcpPort); + static int GetIPv4SocketPort(SocketType s, uint16_t *pPort); - bool init; - bool created; - bool waitingfordata; - SocketType rtpsock,rtcpsock; - uint32_t mcastifaceIP; - std::list localIPs; - uint16_t m_rtpPort, m_rtcpPort; - uint8_t multicastTTL; - RTPTransmitter::ReceiveMode receivemode; + bool init; + bool created; + bool waitingfordata; + SocketType rtpsock, rtcpsock; + uint32_t mcastifaceIP; + std::list localIPs; + uint16_t m_rtpPort, m_rtcpPort; + uint8_t multicastTTL; + RTPTransmitter::ReceiveMode receivemode; - uint8_t *localhostname; - size_t localhostnamelength; + uint8_t *localhostname; + size_t localhostnamelength; - RTPHashTable destinations; + RTPHashTable destinations; #ifdef RTP_SUPPORT_IPV4MULTICAST - RTPHashTable multicastgroups; + RTPHashTable multicastgroups; #endif // RTP_SUPPORT_IPV4MULTICAST - std::list rawpacketlist; + std::list rawpacketlist; - bool supportsmulticasting; - size_t maxpacksize; + bool supportsmulticasting; + size_t maxpacksize; - class PortInfo - { - public: - PortInfo() { all = false; } + class PortInfo + { + public: + PortInfo() + { + all = false; + } - bool all; - std::list portlist; - }; + bool all; + std::list portlist; + }; - RTPKeyHashTable acceptignoreinfo; + RTPKeyHashTable acceptignoreinfo; - bool closesocketswhendone; - RTPAbortDescriptors m_abortDesc; - RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified + bool closesocketswhendone; + RTPAbortDescriptors m_abortDesc; + RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified }; From fb4d07c275362d4f7b6eda5a3700c7fe4d49b567 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 23:13:12 +0100 Subject: [PATCH 024/956] qrtplib: removed unknown packet support --- qrtplib/rtcpcompoundpacketbuilder.cpp | 133 -------------------------- qrtplib/rtcpcompoundpacketbuilder.h | 11 --- qrtplib/rtpsession.cpp | 117 ---------------------- qrtplib/rtpsession.h | 9 -- 4 files changed, 270 deletions(-) diff --git a/qrtplib/rtcpcompoundpacketbuilder.cpp b/qrtplib/rtcpcompoundpacketbuilder.cpp index 13434293f..b571254ac 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.cpp +++ b/qrtplib/rtcpcompoundpacketbuilder.cpp @@ -36,9 +36,6 @@ #include "rtcpsdespacket.h" #include "rtcpbyepacket.h" #include "rtcpapppacket.h" -#ifdef RTP_SUPPORT_RTCPUNKNOWN -#include "rtcpunknownpacket.h" -#endif // RTP_SUPPORT_RTCPUNKNOWN #include namespace qrtplib @@ -48,9 +45,6 @@ RTCPCompoundPacketBuilder::RTCPCompoundPacketBuilder() { byesize = 0; appsize = 0; -#ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownsize = 0; -#endif // RTP_SUPPORT_RTCPUNKNOWN maximumpacketsize = 0; buffer = 0; external = false; @@ -80,24 +74,10 @@ void RTCPCompoundPacketBuilder::ClearBuildBuffers() if ((*it).packetdata) delete[] (*it).packetdata; } -#ifdef RTP_SUPPORT_RTCPUNKNOWN - for (it = unknownpackets.begin(); it != unknownpackets.end(); it++) - { - if ((*it).packetdata) - delete[] (*it).packetdata; - } -#endif // RTP_SUPPORT_RTCPUNKNOWN - byepackets.clear(); apppackets.clear(); -#ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownpackets.clear(); -#endif // RTP_SUPPORT_RTCPUNKNOWN byesize = 0; appsize = 0; -#ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownsize = 0; -#endif // RTP_SUPPORT_RTCPUNKNOWN } int RTCPCompoundPacketBuilder::InitBuild(size_t maxpacketsize) @@ -115,9 +95,6 @@ int RTCPCompoundPacketBuilder::InitBuild(size_t maxpacketsize) external = false; byesize = 0; appsize = 0; -#ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownsize = 0; -#endif // RTP_SUPPORT_RTCPUNKNOWN arebuilding = true; return 0; @@ -138,9 +115,6 @@ int RTCPCompoundPacketBuilder::InitBuild(void *externalbuffer, size_t buffersize external = true; byesize = 0; appsize = 0; -#ifdef RTP_SUPPORT_RTCPUNKNOWN - unknownsize = 0; -#endif // RTP_SUPPORT_RTCPUNKNOWN arebuilding = true; return 0; @@ -154,11 +128,7 @@ int RTCPCompoundPacketBuilder::StartSenderReport(uint32_t senderssrc, const RTPN if (report.headerlength != 0) return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; -#ifndef RTP_SUPPORT_RTCPUNKNOWN size_t totalsize = byesize + appsize + sdes.NeededBytes(); -#else - size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN size_t sizeleft = maximumpacketsize - totalsize; size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport); @@ -190,11 +160,7 @@ int RTCPCompoundPacketBuilder::StartReceiverReport(uint32_t senderssrc) if (report.headerlength != 0) return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; -#ifndef RTP_SUPPORT_RTCPUNKNOWN size_t totalsize = byesize + appsize + sdes.NeededBytes(); -#else - size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN size_t sizeleft = maximumpacketsize - totalsize; size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t); @@ -219,11 +185,7 @@ int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc, uint8_t fractionlos if (report.headerlength == 0) return ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED; -#ifndef RTP_SUPPORT_RTCPUNKNOWN size_t totalothersize = byesize + appsize + sdes.NeededBytes(); -#else - size_t totalothersize = byesize+appsize+unknownsize+sdes.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN size_t reportsizewithextrablock = report.NeededBytesWithExtraReportBlock(); if ((totalothersize + reportsizewithextrablock) > maximumpacketsize) @@ -256,11 +218,7 @@ int RTCPCompoundPacketBuilder::AddSDESSource(uint32_t ssrc) if (!arebuilding) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; -#ifndef RTP_SUPPORT_RTCPUNKNOWN size_t totalotherbytes = byesize + appsize + report.NeededBytes(); -#else - size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN size_t sdessizewithextrasource = sdes.NeededBytesWithExtraSource(); if ((totalotherbytes + sdessizewithextrasource) > maximumpacketsize) @@ -309,11 +267,7 @@ int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t, con return ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE; } -#ifndef RTP_SUPPORT_RTCPUNKNOWN size_t totalotherbytes = byesize + appsize + report.NeededBytes(); -#else - size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); if ((sdessizewithextraitem + totalotherbytes) > maximumpacketsize) @@ -350,11 +304,7 @@ int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata, uint8_ if (itemlength > 255) return ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG; -#ifndef RTP_SUPPORT_RTCPUNKNOWN size_t totalotherbytes = byesize + appsize + report.NeededBytes(); -#else - size_t totalotherbytes = byesize+appsize+unknownsize+report.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); if ((sdessizewithextraitem + totalotherbytes) > maximumpacketsize) @@ -408,11 +358,7 @@ int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, c } } -#ifndef RTP_SUPPORT_RTCPUNKNOWN size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); -#else - size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN if ((totalotherbytes + packsize) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; @@ -471,11 +417,7 @@ int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype, uint32_t ssrc, cons return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2 + appdatalen; -#ifndef RTP_SUPPORT_RTCPUNKNOWN size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); -#else - size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN if ((totalotherbytes + packsize) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; @@ -512,50 +454,6 @@ int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype, uint32_t ssrc, cons return 0; } -#ifdef RTP_SUPPORT_RTCPUNKNOWN - -int RTCPCompoundPacketBuilder::AddUnknownPacket(uint8_t payload_type, uint8_t subtype, uint32_t ssrc, const void *data, size_t len) -{ - if (!arebuilding) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - - size_t datawords = len/4; - - if ((datawords+2) > 65535) - return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; - - size_t packsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+len; - size_t totalotherbytes = appsize+unknownsize+byesize+sdes.NeededBytes()+report.NeededBytes(); - - if ((totalotherbytes + packsize) > maximumpacketsize) - return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; - - uint8_t *buf = new uint8_t[packsize]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; - - RTCPCommonHeader *hdr = (RTCPCommonHeader *)buf; - - hdr->version = 2; - hdr->padding = 0; - hdr->count = subtype; - hdr->length = qToBigEndian((uint16_t)(datawords+1)); - hdr->packettype = payload_type; - - uint32_t *source = (uint32_t *)(buf+sizeof(RTCPCommonHeader)); - *source = qToBigEndian(ssrc); - - if (len > 0) - memcpy((buf+sizeof(RTCPCommonHeader)+sizeof(uint32_t)),data,len); - - unknownpackets.push_back(Buffer(buf,packsize)); - unknownsize += packsize; - - return 0; -} - -#endif // RTP_SUPPORT_RTCPUNKNOWN - int RTCPCompoundPacketBuilder::EndBuild() { if (!arebuilding) @@ -566,11 +464,7 @@ int RTCPCompoundPacketBuilder::EndBuild() uint8_t *buf; size_t len; -#ifndef RTP_SUPPORT_RTCPUNKNOWN len = appsize + byesize + report.NeededBytes() + sdes.NeededBytes(); -#else - len = appsize+unknownsize+byesize+report.NeededBytes()+sdes.NeededBytes(); -#endif // RTP_SUPPORT_RTCPUNKNOWN if (!external) { @@ -744,33 +638,6 @@ int RTCPCompoundPacketBuilder::EndBuild() } } -#ifdef RTP_SUPPORT_RTCPUNKNOWN - - // adding the unknown data - - { - std::list::const_iterator it; - - for (it = unknownpackets.begin(); it != unknownpackets.end(); it++) - { - memcpy(curbuf,(*it).packetdata,(*it).packetlength); - - p = new RTCPUnknownPacket(curbuf,(*it).packetlength); - if (p == 0) - { - if (!external) - delete[] buf; - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } - rtcppacklist.push_back(p); - - curbuf += (*it).packetlength; - } - } - -#endif // RTP_SUPPORT_RTCPUNKNOWN - // adding bye packets { diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index ae0859dee..b2be64658 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -128,12 +128,6 @@ public: */ int EndBuild(); -#ifdef RTP_SUPPORT_RTCPUNKNOWN - /** Adds the RTCP packet specified by the arguments to the compound packet. - * Adds the RTCP packet specified by the arguments to the compound packet. - */ - int AddUnknownPacket(uint8_t payload_type, uint8_t subtype, uint32_t ssrc, const void *data, size_t len); -#endif // RTP_SUPPORT_RTCPUNKNOWN private: class Buffer { @@ -403,11 +397,6 @@ private: std::list apppackets; size_t appsize; -#ifdef RTP_SUPPORT_RTCPUNKNOWN - std::list unknownpackets; - size_t unknownsize; -#endif // RTP_SUPPORT_RTCPUNKNOWN - void ClearBuildBuffers(); }; diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index e6385e314..485c1db2c 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -655,123 +655,6 @@ int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const #endif // RTP_SUPPORT_SENDAPP -#ifdef RTP_SUPPORT_RTCPUNKNOWN - -int RTPSession::SendUnknownPacket(bool sr, uint8_t payload_type, uint8_t subtype, const void *data, size_t len) -{ - int status; - - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - - BUILDER_LOCK - uint32_t ssrc = packetbuilder.GetSSRC(); - BUILDER_UNLOCK - - RTCPCompoundPacketBuilder* rtcpcomppack = new RTCPCompoundPacketBuilder(GetMemoryManager()); - if (rtcpcomppack == 0) - { - delete rtcpcomppack; - return ERR_RTP_OUTOFMEM; - } - - status = rtcpcomppack->InitBuild(maxpacksize); - if(status < 0) - { - delete rtcpcomppack; - return status; - } - - if (sr) - { - // setup for the rtcp - RTPTime rtppacktime = packetbuilder.GetPacketTime(); - uint32_t rtppacktimestamp = packetbuilder.GetPacketTimestamp(); - uint32_t packcount = packetbuilder.GetPacketCount(); - uint32_t octetcount = packetbuilder.GetPayloadOctetCount(); - RTPTime curtime = RTPTime::CurrentTime(); - RTPTime diff = curtime; - diff -= rtppacktime; - diff += 1;// add transmission delay or RTPTime(0,0); - - double timestampunit = 90000; - - uint32_t tsdiff = (uint32_t)((diff.GetDouble()/timestampunit)+0.5); - uint32_t rtptimestamp = rtppacktimestamp+tsdiff; - RTPNTPTime ntptimestamp = curtime.GetNTPTime(); - - //first packet in an rtcp compound packet should always be SR or RR - if((status = rtcpcomppack->StartSenderReport(ssrc,ntptimestamp,rtptimestamp,packcount,octetcount)) < 0) - { - delete rtcpcomppack; - return status; - } - } - else - { - //first packet in an rtcp compound packet should always be SR or RR - if((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) - { - delete rtcpcomppack; - return status; - } - - } - - //add SDES packet with CNAME item - if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) - { - delete rtcpcomppack; - return status; - } - - BUILDER_LOCK - size_t owncnamelen = 0; - uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); - - if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME,owncname,owncnamelen)) < 0) - { - BUILDER_UNLOCK - delete rtcpcomppack; - return status; - } - BUILDER_UNLOCK - - //add our packet - if((status = rtcpcomppack->AddUnknownPacket(payload_type, subtype, ssrc, data, len)) < 0) - { - delete rtcpcomppack; - return status; - } - - if((status = rtcpcomppack->EndBuild()) < 0) - { - delete rtcpcomppack; - return status; - } - - //send packet - status = SendRTCPData(rtcpcomppack->GetCompoundPacketData(), rtcpcomppack->GetCompoundPacketLength()); - if(status < 0) - { - delete rtcpcomppack; - return status; - } - - PACKSENT_LOCK - sentpackets = true; - PACKSENT_UNLOCK - - OnSendRTCPCompoundPacket(rtcpcomppack); // we'll place this after the actual send to avoid tampering - - int retlen = rtcpcomppack->GetCompoundPacketLength(); - - delete rtcpcomppack; - return retlen; -} - -#endif // RTP_SUPPORT_RTCPUNKNOWN - int RTPSession::SendRawData(const void *data, size_t len, bool usertpchannel) { if (!created) diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index 918ac2b00..cc12084b5 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -180,15 +180,6 @@ public: int SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, size_t appdatalen); #endif // RTP_SUPPORT_SENDAPP -#ifdef RTP_SUPPORT_RTCPUNKNOWN - /** Tries to send an Unknown packet immediately. - * Tries to send an Unknown packet immediately. If successful, the function returns the number - * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP - * specification, so use with care. Can send message along with a receiver report or a sender report - */ - int SendUnknownPacket(bool sr, uint8_t payload_type, uint8_t subtype, const void *data, size_t len); -#endif // RTP_SUPPORT_RTCPUNKNOWN - /** With this function raw data can be sent directly over the RTP or * RTCP channel (if they are different); the data is **not** passed through the * RTPSession::OnChangeRTPOrRTCPData function. */ From b8c50eb79838e98b81db354c70d3864c2d7f0cb0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 23:26:03 +0100 Subject: [PATCH 025/956] qrtplib: removed JRTPLIB_IMPORTEXPORT --- qrtplib/rtcpapppacket.h | 2 +- qrtplib/rtcpbyepacket.h | 2 +- qrtplib/rtcpcompoundpacket.h | 2 +- qrtplib/rtcpcompoundpacketbuilder.h | 2 +- qrtplib/rtcppacket.h | 2 +- qrtplib/rtcppacketbuilder.h | 2 +- qrtplib/rtcprrpacket.h | 2 +- qrtplib/rtcpscheduler.h | 4 ++-- qrtplib/rtcpsdesinfo.h | 2 +- qrtplib/rtcpsdespacket.h | 2 +- qrtplib/rtcpsrpacket.h | 2 +- qrtplib/rtcpunknownpacket.h | 2 +- qrtplib/rtpabortdescriptors.h | 2 +- qrtplib/rtpaddress.h | 2 +- qrtplib/rtpbyteaddress.h | 2 +- qrtplib/rtpcollisionlist.h | 2 +- qrtplib/rtpconfig.h | 8 -------- qrtplib/rtperrors.h | 2 +- qrtplib/rtpexternaltransmitter.h | 10 +++++----- qrtplib/rtpinternalsourcedata.h | 2 +- qrtplib/rtpipv4address.h | 2 +- qrtplib/rtpipv4destination.h | 2 +- qrtplib/rtplibraryversion.h | 2 +- qrtplib/rtppacket.h | 2 +- qrtplib/rtppacketbuilder.h | 2 +- qrtplib/rtprandom.h | 2 +- qrtplib/rtprandomrand48.h | 2 +- qrtplib/rtprandomrands.h | 2 +- qrtplib/rtprandomurandom.h | 2 +- qrtplib/rtprawpacket.h | 2 +- qrtplib/rtpsession.h | 2 +- qrtplib/rtpsessionparams.h | 2 +- qrtplib/rtpsessionsources.h | 2 +- qrtplib/rtpsourcedata.h | 8 ++++---- qrtplib/rtpsources.h | 4 ++-- qrtplib/rtptcpaddress.h | 2 +- qrtplib/rtptcptransmitter.h | 6 +++--- qrtplib/rtptimeutilities.h | 6 +++--- qrtplib/rtptransmitter.h | 6 +++--- qrtplib/rtpudpv4transmitter.h | 10 +++++----- qrtplib/rtpudpv4transmitternobind.h | 10 +++++----- 41 files changed, 63 insertions(+), 71 deletions(-) diff --git a/qrtplib/rtcpapppacket.h b/qrtplib/rtcpapppacket.h index c6fe0bbd6..b2cd5579a 100644 --- a/qrtplib/rtcpapppacket.h +++ b/qrtplib/rtcpapppacket.h @@ -49,7 +49,7 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP APP packet. */ -class JRTPLIB_IMPORTEXPORT RTCPAPPPacket: public RTCPPacket +class RTCPAPPPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtcpbyepacket.h b/qrtplib/rtcpbyepacket.h index a6022175f..6619af851 100644 --- a/qrtplib/rtcpbyepacket.h +++ b/qrtplib/rtcpbyepacket.h @@ -49,7 +49,7 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP BYE packet. */ -class JRTPLIB_IMPORTEXPORT RTCPBYEPacket: public RTCPPacket +class RTCPBYEPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h index 70b66bfc7..c5f59f5b7 100644 --- a/qrtplib/rtcpcompoundpacket.h +++ b/qrtplib/rtcpcompoundpacket.h @@ -50,7 +50,7 @@ class RTPRawPacket; class RTCPPacket; /** Represents an RTCP compound packet. */ -class JRTPLIB_IMPORTEXPORT RTCPCompoundPacket +class RTCPCompoundPacket { public: /** Creates an RTCPCompoundPacket instance from the data in \c rawpack, installing a memory manager if specified. */ diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index b2be64658..7eceadb6f 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -57,7 +57,7 @@ class RTPMemoryManager; * been built successfully. The member functions described below return \c ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT * if the action would cause the maximum allowed size to be exceeded. */ -class JRTPLIB_IMPORTEXPORT RTCPCompoundPacketBuilder: public RTCPCompoundPacket +class RTCPCompoundPacketBuilder: public RTCPCompoundPacket { public: /** Constructs an RTCPCompoundPacketBuilder instance, optionally installing a memory manager. */ diff --git a/qrtplib/rtcppacket.h b/qrtplib/rtcppacket.h index a50938724..e5dcb129e 100644 --- a/qrtplib/rtcppacket.h +++ b/qrtplib/rtcppacket.h @@ -47,7 +47,7 @@ namespace qrtplib class RTCPCompoundPacket; /** Base class for specific types of RTCP packets. */ -class JRTPLIB_IMPORTEXPORT RTCPPacket +class RTCPPacket { public: /** Identifies the specific kind of RTCP packet. */ diff --git a/qrtplib/rtcppacketbuilder.h b/qrtplib/rtcppacketbuilder.h index 96cf1ce00..37908ef00 100644 --- a/qrtplib/rtcppacketbuilder.h +++ b/qrtplib/rtcppacketbuilder.h @@ -59,7 +59,7 @@ class RTCPCompoundPacketBuilder; * an RTPSources instance to automatically generate the next compound packet which should be sent. It also * provides functions to determine when SDES items other than the CNAME item should be sent. */ -class JRTPLIB_IMPORTEXPORT RTCPPacketBuilder +class RTCPPacketBuilder { public: /** Creates an RTCPPacketBuilder instance. diff --git a/qrtplib/rtcprrpacket.h b/qrtplib/rtcprrpacket.h index 200e5b27c..d6b4b1011 100644 --- a/qrtplib/rtcprrpacket.h +++ b/qrtplib/rtcprrpacket.h @@ -49,7 +49,7 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP receiver report packet. */ -class JRTPLIB_IMPORTEXPORT RTCPRRPacket: public RTCPPacket +class RTCPRRPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtcpscheduler.h b/qrtplib/rtcpscheduler.h index 47c1d515a..0d0961f91 100644 --- a/qrtplib/rtcpscheduler.h +++ b/qrtplib/rtcpscheduler.h @@ -50,7 +50,7 @@ class RTPPacket; class RTPSources; /** Describes parameters used by the RTCPScheduler class. */ -class JRTPLIB_IMPORTEXPORT RTCPSchedulerParams +class RTCPSchedulerParams { public: RTCPSchedulerParams(); @@ -119,7 +119,7 @@ private: }; /** This class determines when RTCP compound packets should be sent. */ -class JRTPLIB_IMPORTEXPORT RTCPScheduler +class RTCPScheduler { public: /** Creates an instance which will use the source table RTPSources to determine when RTCP compound diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h index 609ba215b..ab2e61568 100644 --- a/qrtplib/rtcpsdesinfo.h +++ b/qrtplib/rtcpsdesinfo.h @@ -49,7 +49,7 @@ namespace qrtplib { /** The class RTCPSDESInfo is a container for RTCP SDES information. */ -class JRTPLIB_IMPORTEXPORT RTCPSDESInfo +class RTCPSDESInfo { public: /** Constructs an instance, optionally installing a memory manager. */ diff --git a/qrtplib/rtcpsdespacket.h b/qrtplib/rtcpsdespacket.h index 35c01a396..0cdf5639b 100644 --- a/qrtplib/rtcpsdespacket.h +++ b/qrtplib/rtcpsdespacket.h @@ -50,7 +50,7 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP source description packet. */ -class JRTPLIB_IMPORTEXPORT RTCPSDESPacket: public RTCPPacket +class RTCPSDESPacket: public RTCPPacket { public: /** Identifies the type of an SDES item. */ diff --git a/qrtplib/rtcpsrpacket.h b/qrtplib/rtcpsrpacket.h index 200851eab..bbd968d73 100644 --- a/qrtplib/rtcpsrpacket.h +++ b/qrtplib/rtcpsrpacket.h @@ -50,7 +50,7 @@ namespace qrtplib class RTCPCompoundPacket; /** Describes an RTCP sender report packet. */ -class JRTPLIB_IMPORTEXPORT RTCPSRPacket: public RTCPPacket +class RTCPSRPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtcpunknownpacket.h b/qrtplib/rtcpunknownpacket.h index 229c45a00..6575cbd18 100644 --- a/qrtplib/rtcpunknownpacket.h +++ b/qrtplib/rtcpunknownpacket.h @@ -51,7 +51,7 @@ class RTCPCompoundPacket; * the ones it inherited. Note that since an unknown packet type doesn't have any format to check * against, the IsKnownFormat function will trivially return \c true. */ -class JRTPLIB_IMPORTEXPORT RTCPUnknownPacket: public RTCPPacket +class RTCPUnknownPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtpabortdescriptors.h b/qrtplib/rtpabortdescriptors.h index 333154832..2ccdc1919 100644 --- a/qrtplib/rtpabortdescriptors.h +++ b/qrtplib/rtpabortdescriptors.h @@ -61,7 +61,7 @@ namespace qrtplib * uses a single poll thread for several RTPSession and RTPTransmitter instances. * This idea is further illustrated in `example8.cpp`. */ -class JRTPLIB_IMPORTEXPORT RTPAbortDescriptors +class RTPAbortDescriptors { public: RTPAbortDescriptors(); diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h index 421dacc14..9ea607aa9 100644 --- a/qrtplib/rtpaddress.h +++ b/qrtplib/rtpaddress.h @@ -47,7 +47,7 @@ namespace qrtplib class RTPMemoryManager; /** This class is an abstract class which is used to specify destinations, multicast groups etc. */ -class JRTPLIB_IMPORTEXPORT RTPAddress +class RTPAddress { public: /** Identifies the actual implementation being used. */ diff --git a/qrtplib/rtpbyteaddress.h b/qrtplib/rtpbyteaddress.h index 60f347e86..4e40f4356 100644 --- a/qrtplib/rtpbyteaddress.h +++ b/qrtplib/rtpbyteaddress.h @@ -51,7 +51,7 @@ namespace qrtplib /** A very general kind of address consisting of a port number and a number of bytes describing the host address. * A very general kind of address, consisting of a port number and a number of bytes describing the host address. */ -class JRTPLIB_IMPORTEXPORT RTPByteAddress: public RTPAddress +class RTPByteAddress: public RTPAddress { public: /** Creates an instance of the class using \c addrlen bytes of \c hostaddress as host identification, diff --git a/qrtplib/rtpcollisionlist.h b/qrtplib/rtpcollisionlist.h index 2c59434f1..60c63a051 100644 --- a/qrtplib/rtpcollisionlist.h +++ b/qrtplib/rtpcollisionlist.h @@ -49,7 +49,7 @@ namespace qrtplib class RTPAddress; /** This class represents a list of addresses from which SSRC collisions were detected. */ -class JRTPLIB_IMPORTEXPORT RTPCollisionList +class RTPCollisionList { public: /** Constructs an instance, optionally installing a memory manager. */ diff --git a/qrtplib/rtpconfig.h b/qrtplib/rtpconfig.h index ab3b8b368..a37c58702 100644 --- a/qrtplib/rtpconfig.h +++ b/qrtplib/rtpconfig.h @@ -41,14 +41,6 @@ #define JRTPLIB_UNUSED(x) (void)(x) #endif // JRTPLIB_UNUSED -#define JRTPLIB_IMPORT -#define JRTPLIB_EXPORT -#ifdef JRTPLIB_COMPILING -#define JRTPLIB_IMPORTEXPORT JRTPLIB_EXPORT -#else -#define JRTPLIB_IMPORTEXPORT JRTPLIB_IMPORT -#endif // JRTPLIB_COMPILING - // Don't have // Don't have diff --git a/qrtplib/rtperrors.h b/qrtplib/rtperrors.h index 03b236cad..ec12c7955 100644 --- a/qrtplib/rtperrors.h +++ b/qrtplib/rtperrors.h @@ -45,7 +45,7 @@ namespace qrtplib { /** Returns a string describing the error code \c errcode. */ -std::string JRTPLIB_IMPORTEXPORT RTPGetErrorString(int errcode); +std::string RTPGetErrorString(int errcode); } // end namespace diff --git a/qrtplib/rtpexternaltransmitter.h b/qrtplib/rtpexternaltransmitter.h index 8cdefd2ca..c43c122d7 100644 --- a/qrtplib/rtpexternaltransmitter.h +++ b/qrtplib/rtpexternaltransmitter.h @@ -56,7 +56,7 @@ class RTPExternalTransmitter; * so that the transmitter will call the \c SendRTP, \c SendRTCP and \c ComesFromThisSender * methods of this instance when needed. */ -class JRTPLIB_IMPORTEXPORT RTPExternalSender +class RTPExternalSender { public: RTPExternalSender() @@ -83,7 +83,7 @@ public: * be using, you can obtain the associated RTPExternalPacketInjecter instance. By calling it's * member functions, you can then inject RTP or RTCP data into the library for further processing. */ -class JRTPLIB_IMPORTEXPORT RTPExternalPacketInjecter +class RTPExternalPacketInjecter { public: RTPExternalPacketInjecter(RTPExternalTransmitter *trans) @@ -107,7 +107,7 @@ private: }; /** Parameters to initialize a transmitter of type RTPExternalTransmitter. */ -class JRTPLIB_IMPORTEXPORT RTPExternalTransmissionParams: public RTPTransmissionParams +class RTPExternalTransmissionParams: public RTPTransmissionParams { public: /** Using this constructor you can specify which RTPExternalSender object you'll be using @@ -133,7 +133,7 @@ private: }; /** Additional information about the external transmission component. */ -class JRTPLIB_IMPORTEXPORT RTPExternalTransmissionInfo: public RTPTransmissionInfo +class RTPExternalTransmissionInfo: public RTPTransmissionInfo { public: RTPExternalTransmissionInfo(RTPExternalPacketInjecter *p) : @@ -160,7 +160,7 @@ private: * sending the data. Obtain the RTPExternalTransmissionInfo object associated with this * transmitter to obtain the functions needed to pass RTP/RTCP packets on to the transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPExternalTransmitter: public RTPTransmitter +class RTPExternalTransmitter: public RTPTransmitter { public: RTPExternalTransmitter(); diff --git a/qrtplib/rtpinternalsourcedata.h b/qrtplib/rtpinternalsourcedata.h index a9c3bc229..f4ae5af49 100644 --- a/qrtplib/rtpinternalsourcedata.h +++ b/qrtplib/rtpinternalsourcedata.h @@ -47,7 +47,7 @@ namespace qrtplib { -class JRTPLIB_IMPORTEXPORT RTPInternalSourceData: public RTPSourceData +class RTPInternalSourceData: public RTPSourceData { public: RTPInternalSourceData(uint32_t ssrc, RTPSources::ProbationType probtype); diff --git a/qrtplib/rtpipv4address.h b/qrtplib/rtpipv4address.h index 6dd94af4d..5d0b86639 100644 --- a/qrtplib/rtpipv4address.h +++ b/qrtplib/rtpipv4address.h @@ -51,7 +51,7 @@ namespace qrtplib * number is ignored. When an instance is used in one of the accept or ignore functions of the * transmitter, a zero port number represents all ports for the specified IP address. */ -class JRTPLIB_IMPORTEXPORT RTPIPv4Address: public RTPAddress +class RTPIPv4Address: public RTPAddress { public: /** Creates an instance with IP address \c ip and port number \c port (both diff --git a/qrtplib/rtpipv4destination.h b/qrtplib/rtpipv4destination.h index 746a7d017..e4a5ed073 100644 --- a/qrtplib/rtpipv4destination.h +++ b/qrtplib/rtpipv4destination.h @@ -52,7 +52,7 @@ namespace qrtplib { -class JRTPLIB_IMPORTEXPORT RTPIPv4Destination +class RTPIPv4Destination { public: RTPIPv4Destination() diff --git a/qrtplib/rtplibraryversion.h b/qrtplib/rtplibraryversion.h index 6c7a2706d..eb4e2ad39 100644 --- a/qrtplib/rtplibraryversion.h +++ b/qrtplib/rtplibraryversion.h @@ -48,7 +48,7 @@ namespace qrtplib /** * Used to provide information about the version of the library. */ -class JRTPLIB_IMPORTEXPORT RTPLibraryVersion +class RTPLibraryVersion { public: /** Returns an instance of RTPLibraryVersion describing the version of the library. */ diff --git a/qrtplib/rtppacket.h b/qrtplib/rtppacket.h index 36c340c19..ccb93b704 100644 --- a/qrtplib/rtppacket.h +++ b/qrtplib/rtppacket.h @@ -53,7 +53,7 @@ class RTPRawPacket; * The class can also be used to create a new RTP packet according to the parameters specified by * the user. */ -class JRTPLIB_IMPORTEXPORT RTPPacket +class RTPPacket { public: /** Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib/rtppacketbuilder.h index 1a853ed5f..d7b489e0b 100644 --- a/qrtplib/rtppacketbuilder.h +++ b/qrtplib/rtppacketbuilder.h @@ -53,7 +53,7 @@ class RTPSources; /** This class can be used to build RTP packets and is a bit more high-level than the RTPPacket * class: it generates an SSRC identifier, keeps track of timestamp and sequence number etc. */ -class JRTPLIB_IMPORTEXPORT RTPPacketBuilder +class RTPPacketBuilder { public: /** Constructs an instance which will use \c rtprand for generating random numbers diff --git a/qrtplib/rtprandom.h b/qrtplib/rtprandom.h index b3c6c479c..e7239f9a2 100644 --- a/qrtplib/rtprandom.h +++ b/qrtplib/rtprandom.h @@ -48,7 +48,7 @@ namespace qrtplib { /** Interface for generating random numbers. */ -class JRTPLIB_IMPORTEXPORT RTPRandom +class RTPRandom { public: RTPRandom() diff --git a/qrtplib/rtprandomrand48.h b/qrtplib/rtprandomrand48.h index 9656f4d83..7974f07fe 100644 --- a/qrtplib/rtprandomrand48.h +++ b/qrtplib/rtprandomrand48.h @@ -46,7 +46,7 @@ namespace qrtplib { /** A random number generator using the algorithm of the rand48 set of functions. */ -class JRTPLIB_IMPORTEXPORT RTPRandomRand48: public RTPRandom +class RTPRandomRand48: public RTPRandom { public: RTPRandomRand48(); diff --git a/qrtplib/rtprandomrands.h b/qrtplib/rtprandomrands.h index e4711bea9..d8c47e504 100644 --- a/qrtplib/rtprandomrands.h +++ b/qrtplib/rtprandomrands.h @@ -47,7 +47,7 @@ namespace qrtplib /** A random number generator which tries to use the \c rand_s function on the * Win32 platform. */ -class JRTPLIB_IMPORTEXPORT RTPRandomRandS: public RTPRandom +class RTPRandomRandS: public RTPRandom { public: RTPRandomRandS(); diff --git a/qrtplib/rtprandomurandom.h b/qrtplib/rtprandomurandom.h index 0129755c4..0754d322c 100644 --- a/qrtplib/rtprandomurandom.h +++ b/qrtplib/rtprandomurandom.h @@ -46,7 +46,7 @@ namespace qrtplib { /** A random number generator which uses bytes delivered by the /dev/urandom device. */ -class JRTPLIB_IMPORTEXPORT RTPRandomURandom: public RTPRandom +class RTPRandomURandom: public RTPRandom { public: RTPRandomURandom(); diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h index a8de7ccc8..c70835c57 100644 --- a/qrtplib/rtprawpacket.h +++ b/qrtplib/rtprawpacket.h @@ -47,7 +47,7 @@ namespace qrtplib { /** This class is used by the transmission component to store the incoming RTP and RTCP data in. */ -class JRTPLIB_IMPORTEXPORT RTPRawPacket +class RTPRawPacket { public: /** Creates an instance which stores data from \c data with length \c datalen. diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index cc12084b5..555c3d82e 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -72,7 +72,7 @@ class RTCPAPPPacket; * \note The RTPSession class is not meant to be thread safe. The user should use some kind of locking * mechanism to prevent different threads from using the same RTPSession instance. */ -class JRTPLIB_IMPORTEXPORT RTPSession +class RTPSession { public: /** Constructs an RTPSession instance, optionally using a specific instance of a random diff --git a/qrtplib/rtpsessionparams.h b/qrtplib/rtpsessionparams.h index 3a455e29f..198c225b0 100644 --- a/qrtplib/rtpsessionparams.h +++ b/qrtplib/rtpsessionparams.h @@ -51,7 +51,7 @@ namespace qrtplib * Describes the parameters for to be used by an RTPSession instance. Note that the own timestamp * unit must be set to a valid number, otherwise the session can't be created. */ -class JRTPLIB_IMPORTEXPORT RTPSessionParams +class RTPSessionParams { public: RTPSessionParams(); diff --git a/qrtplib/rtpsessionsources.h b/qrtplib/rtpsessionsources.h index d17c7b193..753fccbd4 100644 --- a/qrtplib/rtpsessionsources.h +++ b/qrtplib/rtpsessionsources.h @@ -46,7 +46,7 @@ namespace qrtplib class RTPSession; -class JRTPLIB_IMPORTEXPORT RTPSessionSources: public RTPSources +class RTPSessionSources: public RTPSources { public: RTPSessionSources(RTPSession &sess) : diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h index bcd91a83f..0c4075c8c 100644 --- a/qrtplib/rtpsourcedata.h +++ b/qrtplib/rtpsourcedata.h @@ -51,7 +51,7 @@ namespace qrtplib class RTPAddress; -class JRTPLIB_IMPORTEXPORT RTCPSenderReportInfo +class RTCPSenderReportInfo { public: RTCPSenderReportInfo() : @@ -105,7 +105,7 @@ private: RTPTime receivetime; }; -class JRTPLIB_IMPORTEXPORT RTCPReceiverReportInfo +class RTCPReceiverReportInfo { public: RTCPReceiverReportInfo() : @@ -174,7 +174,7 @@ private: RTPTime receivetime; }; -class JRTPLIB_IMPORTEXPORT RTPSourceStats +class RTPSourceStats { public: RTPSourceStats(); @@ -281,7 +281,7 @@ inline RTPSourceStats::RTPSourceStats() : } /** Describes an entry in the RTPSources source table. */ -class JRTPLIB_IMPORTEXPORT RTPSourceData +class RTPSourceData { protected: RTPSourceData(uint32_t ssrc); diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h index aa3590ec5..61d477506 100644 --- a/qrtplib/rtpsources.h +++ b/qrtplib/rtpsources.h @@ -48,7 +48,7 @@ namespace qrtplib { -class JRTPLIB_IMPORTEXPORT RTPSources_GetHashIndex +class RTPSources_GetHashIndex { public: static int GetIndex(const uint32_t &ssrc) @@ -73,7 +73,7 @@ class RTPSourceData; * is used to identify packets from our own session. The class also provides some overridable functions * which can be used to catch certain events (new SSRC, SSRC collision, ...). */ -class JRTPLIB_IMPORTEXPORT RTPSources +class RTPSources { public: /** Type of probation to use for new sources. */ diff --git a/qrtplib/rtptcpaddress.h b/qrtplib/rtptcpaddress.h index 45806c9b9..83b33acd1 100644 --- a/qrtplib/rtptcpaddress.h +++ b/qrtplib/rtptcpaddress.h @@ -50,7 +50,7 @@ namespace qrtplib * should be used to send/receive data, and to know on which socket incoming data * was received. */ -class JRTPLIB_IMPORTEXPORT RTPTCPAddress: public RTPAddress +class RTPTCPAddress: public RTPAddress { public: /** Creates an instance with which you can use a specific socket diff --git a/qrtplib/rtptcptransmitter.h b/qrtplib/rtptcptransmitter.h index 9dd2ff744..fa8515360 100644 --- a/qrtplib/rtptcptransmitter.h +++ b/qrtplib/rtptcptransmitter.h @@ -50,7 +50,7 @@ namespace qrtplib { /** Parameters for the TCP transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPTCPTransmissionParams: public RTPTransmissionParams +class RTPTCPTransmissionParams: public RTPTransmissionParams { public: RTPTCPTransmissionParams(); @@ -81,7 +81,7 @@ inline RTPTCPTransmissionParams::RTPTCPTransmissionParams() : } /** Additional information about the TCP transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPTCPTransmissionInfo: public RTPTransmissionInfo +class RTPTCPTransmissionInfo: public RTPTransmissionInfo { public: RTPTCPTransmissionInfo() : @@ -117,7 +117,7 @@ public: * To get notified of an error when sending over or receiving from a socket, override the * RTPTCPTransmitter::OnSendError and RTPTCPTransmitter::OnReceiveError member functions. */ -class JRTPLIB_IMPORTEXPORT RTPTCPTransmitter : public RTPTransmitter +class RTPTCPTransmitter : public RTPTransmitter { public: RTPTCPTransmitter(); diff --git a/qrtplib/rtptimeutilities.h b/qrtplib/rtptimeutilities.h index c4cb8faea..0e9a4d28a 100644 --- a/qrtplib/rtptimeutilities.h +++ b/qrtplib/rtptimeutilities.h @@ -63,7 +63,7 @@ namespace qrtplib * This is a simple wrapper for the most significant word (MSW) and least * significant word (LSW) of an NTP timestamp. */ -class JRTPLIB_IMPORTEXPORT RTPNTPTime +class RTPNTPTime { public: /** This constructor creates and instance with MSW \c m and LSW \c l. */ @@ -92,7 +92,7 @@ private: * This class is used to specify wallclock time, delay intervals etc. * It stores a number of seconds and a number of microseconds. */ -class JRTPLIB_IMPORTEXPORT RTPTime +class RTPTime { public: /** Returns an RTPTime instance representing the current wallclock time. @@ -392,7 +392,7 @@ inline bool RTPTime::operator>=(const RTPTime &t) const return m_t >= t.m_t; } -class JRTPLIB_IMPORTEXPORT RTPTimeInitializerObject +class RTPTimeInitializerObject { public: RTPTimeInitializerObject(); diff --git a/qrtplib/rtptransmitter.h b/qrtplib/rtptransmitter.h index 1a83edf02..8cfad498d 100644 --- a/qrtplib/rtptransmitter.h +++ b/qrtplib/rtptransmitter.h @@ -58,7 +58,7 @@ class RTPTransmissionInfo; * an UDP over IPv4 transmitter, an UDP over IPv6 transmitter and a transmitter * which can be used to use an external transmission mechanism. */ -class JRTPLIB_IMPORTEXPORT RTPTransmitter +class RTPTransmitter { public: /** Used to identify a specific transmitter. @@ -227,7 +227,7 @@ public: * GetTransmissionProtocol function which identifies the component type for which * these parameters are valid. */ -class JRTPLIB_IMPORTEXPORT RTPTransmissionParams +class RTPTransmissionParams { protected: RTPTransmissionParams(RTPTransmitter::TransmissionProtocol p) @@ -254,7 +254,7 @@ private: * GetTransmissionProtocol function which identifies the component type for which * these parameters are valid. */ -class JRTPLIB_IMPORTEXPORT RTPTransmissionInfo +class RTPTransmissionInfo { protected: RTPTransmissionInfo(RTPTransmitter::TransmissionProtocol p) diff --git a/qrtplib/rtpudpv4transmitter.h b/qrtplib/rtpudpv4transmitter.h index 48b9e5175..bd2aa9597 100644 --- a/qrtplib/rtpudpv4transmitter.h +++ b/qrtplib/rtpudpv4transmitter.h @@ -59,7 +59,7 @@ namespace qrtplib { /** Parameters for the UDP over IPv4 transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionParams: public RTPTransmissionParams +class RTPUDPv4TransmissionParams: public RTPTransmissionParams { public: RTPUDPv4TransmissionParams(); @@ -294,7 +294,7 @@ inline RTPUDPv4TransmissionParams::RTPUDPv4TransmissionParams() : } /** Additional information about the UDP over IPv4 transmitter. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionInfo: public RTPTransmissionInfo +class RTPUDPv4TransmissionInfo: public RTPTransmissionInfo { public: RTPUDPv4TransmissionInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : @@ -346,7 +346,7 @@ private: uint16_t m_rtpPort, m_rtcpPort; }; -class JRTPLIB_IMPORTEXPORT RTPUDPv4Trans_GetHashIndex_IPv4Dest +class RTPUDPv4Trans_GetHashIndex_IPv4Dest { public: static int GetIndex(const RTPIPv4Destination &d) @@ -355,7 +355,7 @@ public: } }; -class JRTPLIB_IMPORTEXPORT RTPUDPv4Trans_GetHashIndex_uint32_t +class RTPUDPv4Trans_GetHashIndex_uint32_t { public: static int GetIndex(const uint32_t &k) @@ -373,7 +373,7 @@ public: * argument require an argument of RTPIPv4Address. The GetTransmissionInfo member function * returns an instance of type RTPUDPv4TransmissionInfo. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4Transmitter: public RTPTransmitter +class RTPUDPv4Transmitter: public RTPTransmitter { public: RTPUDPv4Transmitter(); diff --git a/qrtplib/rtpudpv4transmitternobind.h b/qrtplib/rtpudpv4transmitternobind.h index 28ae47986..6b0da05d1 100644 --- a/qrtplib/rtpudpv4transmitternobind.h +++ b/qrtplib/rtpudpv4transmitternobind.h @@ -59,7 +59,7 @@ namespace qrtplib { /** Parameters for the UDP over IPv4 transmitter that does not automatically bind sockets */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionNoBindParams: public RTPTransmissionParams +class RTPUDPv4TransmissionNoBindParams: public RTPTransmissionParams { public: RTPUDPv4TransmissionNoBindParams(); @@ -294,7 +294,7 @@ inline RTPUDPv4TransmissionNoBindParams::RTPUDPv4TransmissionNoBindParams() : } /** Additional information about the UDP over IPv4 transmitter that does not automatically bind sockets. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmissionNoBindInfo: public RTPTransmissionInfo +class RTPUDPv4TransmissionNoBindInfo: public RTPTransmissionInfo { public: RTPUDPv4TransmissionNoBindInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : @@ -346,7 +346,7 @@ private: uint16_t m_rtpPort, m_rtcpPort; }; -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransNoBind_GetHashIndex_IPv4Dest +class RTPUDPv4TransNoBind_GetHashIndex_IPv4Dest { public: static int GetIndex(const RTPIPv4Destination &d) @@ -355,7 +355,7 @@ public: } }; -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransNoBind_GetHashIndex_uint32_t +class RTPUDPv4TransNoBind_GetHashIndex_uint32_t { public: static int GetIndex(const uint32_t &k) @@ -375,7 +375,7 @@ public: * This flavor of a RTPUDPv4Transmitter class does not automatically bind sockets. Use the * BindSockets method to do so. */ -class JRTPLIB_IMPORTEXPORT RTPUDPv4TransmitterNoBind: public RTPTransmitter +class RTPUDPv4TransmitterNoBind: public RTPTransmitter { public: RTPUDPv4TransmitterNoBind(); From 92bc10efe618f4053a3b28b54d633e5dec8ee9f9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 23:36:24 +0100 Subject: [PATCH 026/956] qrtplib: removed probation support --- qrtplib/rtpconfig.h | 2 +- qrtplib/rtpinternalsourcedata.cpp | 40 ++--------------------- qrtplib/rtpinternalsourcedata.h | 6 +--- qrtplib/rtpsession.cpp | 7 ---- qrtplib/rtpsessionparams.cpp | 3 -- qrtplib/rtpsessionparams.h | 16 --------- qrtplib/rtpsourcedata.cpp | 54 ------------------------------- qrtplib/rtpsourcedata.h | 8 ----- qrtplib/rtpsources.cpp | 9 +----- qrtplib/rtpsources.h | 11 ------- 10 files changed, 5 insertions(+), 151 deletions(-) diff --git a/qrtplib/rtpconfig.h b/qrtplib/rtpconfig.h index a37c58702..f0195e60a 100644 --- a/qrtplib/rtpconfig.h +++ b/qrtplib/rtpconfig.h @@ -57,7 +57,7 @@ #define RTP_SUPPORT_SDESPRIV -#define RTP_SUPPORT_PROBATION +// No #define RTP_SUPPORT_PROBATION #define RTP_SUPPORT_GETLOGINR diff --git a/qrtplib/rtpinternalsourcedata.cpp b/qrtplib/rtpinternalsourcedata.cpp index 7854d09f1..075b6bcb1 100644 --- a/qrtplib/rtpinternalsourcedata.cpp +++ b/qrtplib/rtpinternalsourcedata.cpp @@ -39,13 +39,9 @@ namespace qrtplib { -RTPInternalSourceData::RTPInternalSourceData(uint32_t ssrc, RTPSources::ProbationType probtype) : +RTPInternalSourceData::RTPInternalSourceData(uint32_t ssrc) : RTPSourceData(ssrc) { - JRTPLIB_UNUSED(probtype); // possibly unused -#ifdef RTP_SUPPORT_PROBATION - probationtype = probtype; -#endif // RTP_SUPPORT_PROBATION } RTPInternalSourceData::~RTPInternalSourceData() @@ -65,45 +61,13 @@ int RTPInternalSourceData::ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &r else tsunit = timestampunit; -#ifdef RTP_SUPPORT_PROBATION - if (validated) // If the source is our own process, we can already be validated. No - applyprobation = false; // probation should be applied in that case. - else - { - if (probationtype == RTPSources::NoProbation) - applyprobation = false; - else - applyprobation = true; - } -#else applyprobation = false; -#endif // RTP_SUPPORT_PROBATION stats.ProcessPacket(rtppack, receivetime, tsunit, ownssrc, &accept, applyprobation, &onprobation); -#ifdef RTP_SUPPORT_PROBATION - switch (probationtype) - { - case RTPSources::ProbationStore: - if (!(onprobation || accept)) - return 0; - if (accept) - validated = true; - break; - case RTPSources::ProbationDiscard: - case RTPSources::NoProbation: - if (!accept) - return 0; - validated = true; - break; - default: - return ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE; - } -#else if (!accept) - return 0; + return 0; validated = true; -#endif // RTP_SUPPORT_PROBATION; if (validated && !ownssrc) // for own ssrc these variables depend on the outgoing packets, not on the incoming issender = true; diff --git a/qrtplib/rtpinternalsourcedata.h b/qrtplib/rtpinternalsourcedata.h index f4ae5af49..9702d4fc7 100644 --- a/qrtplib/rtpinternalsourcedata.h +++ b/qrtplib/rtpinternalsourcedata.h @@ -50,7 +50,7 @@ namespace qrtplib class RTPInternalSourceData: public RTPSourceData { public: - RTPInternalSourceData(uint32_t ssrc, RTPSources::ProbationType probtype); + RTPInternalSourceData(uint32_t ssrc); ~RTPInternalSourceData(); int ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, bool *stored, RTPSources *sources); @@ -107,10 +107,6 @@ public: SDESinf.SetNote(0, 0); } -#ifdef RTP_SUPPORT_PROBATION -private: - RTPSources::ProbationType probationtype; -#endif // RTP_SUPPORT_PROBATION }; inline int RTPInternalSourceData::SetRTPDataAddress(const RTPAddress *a) diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index 485c1db2c..7f4522331 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -193,13 +193,6 @@ int RTPSession::InternalCreate(const RTPSessionParams &sessparams) if (sessparams.GetUsePredefinedSSRC()) packetbuilder.AdjustSSRC(sessparams.GetPredefinedSSRC()); -#ifdef RTP_SUPPORT_PROBATION - - // Set probation type - sources.SetProbationType(sessparams.GetProbationType()); - -#endif // RTP_SUPPORT_PROBATION - // Add our own ssrc to the source table if ((status = sources.CreateOwnSSRC(packetbuilder.GetSSRC())) < 0) diff --git a/qrtplib/rtpsessionparams.cpp b/qrtplib/rtpsessionparams.cpp index 407e20ed6..78f2bfea5 100644 --- a/qrtplib/rtpsessionparams.cpp +++ b/qrtplib/rtpsessionparams.cpp @@ -48,9 +48,6 @@ RTPSessionParams::RTPSessionParams() : acceptown = false; owntsunit = -1; // The user will have to set it to the correct value himself resolvehostname = false; -#ifdef RTP_SUPPORT_PROBATION - probationtype = RTPSources::ProbationStore; -#endif // RTP_SUPPORT_PROBATION mininterval = RTPTime(RTCP_DEFAULTMININTERVAL); sessionbandwidth = RTP_DEFAULTSESSIONBANDWIDTH; diff --git a/qrtplib/rtpsessionparams.h b/qrtplib/rtpsessionparams.h index 198c225b0..e4208dc96 100644 --- a/qrtplib/rtpsessionparams.h +++ b/qrtplib/rtpsessionparams.h @@ -143,19 +143,6 @@ public: { return resolvehostname; } -#ifdef RTP_SUPPORT_PROBATION - /** If probation support is enabled, this function sets the probation type to be used. */ - void SetProbationType(RTPSources::ProbationType probtype) - { - probationtype = probtype; - } - - /** Returns the probation type which will be used (default is RTPSources::ProbationStore). */ - RTPSources::ProbationType GetProbationType() const - { - return probationtype; - } -#endif // RTP_SUPPORT_PROBATION /** Sets the session bandwidth in bytes per second. */ void SetSessionBandwidth(double sessbw) @@ -357,9 +344,6 @@ private: double owntsunit; RTPTransmitter::ReceiveMode receivemode; bool resolvehostname; -#ifdef RTP_SUPPORT_PROBATION - RTPSources::ProbationType probationtype; -#endif // RTP_SUPPORT_PROBATION double sessionbandwidth; double controlfrac; diff --git a/qrtplib/rtpsourcedata.cpp b/qrtplib/rtpsourcedata.cpp index 46a798e62..185b7d8b1 100644 --- a/qrtplib/rtpsourcedata.cpp +++ b/qrtplib/rtpsourcedata.cpp @@ -75,61 +75,7 @@ void RTPSourceStats::ProcessPacket(RTPPacket *pack, const RTPTime &receivetime, if (!sentdata) // no valid packets received yet { -#ifdef RTP_SUPPORT_PROBATION - if (applyprobation) - { - bool acceptpack = false; - - if (probation) - { - uint16_t pseq; - uint32_t pseq2; - - pseq = prevseqnr; - pseq++; - pseq2 = (uint32_t) pseq; - if (pseq2 == pack->GetExtendedSequenceNumber()) // ok, its the next expected packet - { - prevseqnr = (uint16_t) pack->GetExtendedSequenceNumber(); - probation--; - if (probation == 0) // probation over - acceptpack = true; - else - *onprobation = true; - } - else // not next packet - { - probation = RTP_PROBATIONCOUNT; - prevseqnr = (uint16_t) pack->GetExtendedSequenceNumber(); - *onprobation = true; - } - } - else // first packet received with this SSRC ID, start probation - { - probation = RTP_PROBATIONCOUNT; - prevseqnr = (uint16_t) pack->GetExtendedSequenceNumber(); - *onprobation = true; - } - - if (acceptpack) - { - ACCEPTPACKETCODE - } - else - { - *accept = false; - lastmsgtime = receivetime; - } - } - else // No probation - { - ACCEPTPACKETCODE - } -#else // No compiled-in probation support - ACCEPTPACKETCODE - -#endif // RTP_SUPPORT_PROBATION } else // already got packets { diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h index 0c4075c8c..a5861f921 100644 --- a/qrtplib/rtpsourcedata.h +++ b/qrtplib/rtpsourcedata.h @@ -254,10 +254,6 @@ private: RTPTime lastnotetime; uint32_t numnewpackets; uint32_t savedextseqnr; -#ifdef RTP_SUPPORT_PROBATION - uint16_t prevseqnr; - int probation; -#endif // RTP_SUPPORT_PROBATION }; inline RTPSourceStats::RTPSourceStats() : @@ -274,10 +270,6 @@ inline RTPSourceStats::RTPSourceStats() : prevtimestamp = 0; djitter = 0; savedextseqnr = 0; -#ifdef RTP_SUPPORT_PROBATION - probation = 0; - prevseqnr = 0; -#endif // RTP_SUPPORT_PROBATION } /** Describes an entry in the RTPSources source table. */ diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp index f5ea3c504..d4ebea9d8 100644 --- a/qrtplib/rtpsources.cpp +++ b/qrtplib/rtpsources.cpp @@ -56,9 +56,6 @@ RTPSources::RTPSources(ProbationType probtype) sendercount = 0; activecount = 0; owndata = 0; -#ifdef RTP_SUPPORT_PROBATION - probationtype = probtype; -#endif // RTP_SUPPORT_PROBATION } RTPSources::~RTPSources() @@ -812,11 +809,7 @@ int RTPSources::ObtainSourceDataInstance(uint32_t ssrc, RTPInternalSourceData ** if (sourcelist.GotoElement(ssrc) < 0) // No entry for this source { -#ifdef RTP_SUPPORT_PROBATION - srcdat2 = new RTPInternalSourceData(ssrc, probationtype); -#else - srcdat2 = new RTPInternalSourceData(ssrc,RTPSources::NoProbation); -#endif // RTP_SUPPORT_PROBATION + srcdat2 = new RTPInternalSourceData(ssrc); if (srcdat2 == 0) return ERR_RTP_OUTOFMEM; if ((status = sourcelist.AddElement(ssrc, srcdat2)) < 0) diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h index 61d477506..62c789812 100644 --- a/qrtplib/rtpsources.h +++ b/qrtplib/rtpsources.h @@ -90,13 +90,6 @@ public: /** Clears the source table. */ void Clear(); -#ifdef RTP_SUPPORT_PROBATION - /** Changes the current probation type. */ - void SetProbationType(ProbationType probtype) - { - probationtype = probtype; - } -#endif // RTP_SUPPORT_PROBATION /** Creates an entry for our own SSRC identifier. */ int CreateOwnSSRC(uint32_t ssrc); @@ -370,10 +363,6 @@ private: int totalcount; int activecount; -#ifdef RTP_SUPPORT_PROBATION - ProbationType probationtype; -#endif // RTP_SUPPORT_PROBATION - RTPInternalSourceData *owndata; friend class RTPInternalSourceData; From ac5489ae37933acff75b83fe6e747df989612945 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 27 Feb 2018 23:54:23 +0100 Subject: [PATCH 027/956] qrtplib: removed JRTPLIB_UNUSED --- qrtplib.old/CMakeLists.txt | 44 --- qrtplib.old/rtpdefines.h | 78 ----- qrtplib.old/rtperrors.cpp | 277 ---------------- qrtplib.old/rtperrors.h | 253 --------------- qrtplib.old/rtpinternalutils.h | 61 ---- qrtplib.old/rtppacket.cpp | 441 -------------------------- qrtplib.old/rtppacket.h | 287 ----------------- qrtplib.old/rtppacketbuilder.cpp | 303 ------------------ qrtplib.old/rtppacketbuilder.h | 275 ---------------- qrtplib.old/rtprandom.cpp | 79 ----- qrtplib.old/rtprandom.h | 82 ----- qrtplib.old/rtprandomrand48.cpp | 89 ------ qrtplib.old/rtprandomrand48.h | 71 ----- qrtplib.old/rtprandomurandom.cpp | 132 -------- qrtplib.old/rtprandomurandom.h | 70 ---- qrtplib.old/rtprawpacket.h | 180 ----------- qrtplib.old/rtpstructs.h | 130 -------- qrtplib.old/rtptimeutilities.cpp | 46 --- qrtplib.old/rtptimeutilities.h | 323 ------------------- qrtplib/rtpconfig.h | 7 - qrtplib/rtpinternalsourcedata.cpp | 6 +- qrtplib/rtprawpacket.h | 5 +- qrtplib/rtpsessionparams.cpp | 6 +- qrtplib/rtpsessionsources.h | 2 +- qrtplib/rtpsourcedata.cpp | 11 +- qrtplib/rtpsourcedata.h | 7 +- qrtplib/rtpsources.cpp | 4 +- qrtplib/rtpsources.h | 2 +- qrtplib/rtptcptransmitter.cpp | 3 +- qrtplib/rtptimeutilities.cpp | 2 - qrtplib/rtpudpv4transmitter.cpp | 3 +- qrtplib/rtpudpv4transmitternobind.cpp | 3 +- 32 files changed, 24 insertions(+), 3258 deletions(-) delete mode 100644 qrtplib.old/CMakeLists.txt delete mode 100644 qrtplib.old/rtpdefines.h delete mode 100644 qrtplib.old/rtperrors.cpp delete mode 100644 qrtplib.old/rtperrors.h delete mode 100644 qrtplib.old/rtpinternalutils.h delete mode 100644 qrtplib.old/rtppacket.cpp delete mode 100644 qrtplib.old/rtppacket.h delete mode 100644 qrtplib.old/rtppacketbuilder.cpp delete mode 100644 qrtplib.old/rtppacketbuilder.h delete mode 100644 qrtplib.old/rtprandom.cpp delete mode 100644 qrtplib.old/rtprandom.h delete mode 100644 qrtplib.old/rtprandomrand48.cpp delete mode 100644 qrtplib.old/rtprandomrand48.h delete mode 100644 qrtplib.old/rtprandomurandom.cpp delete mode 100644 qrtplib.old/rtprandomurandom.h delete mode 100644 qrtplib.old/rtprawpacket.h delete mode 100644 qrtplib.old/rtpstructs.h delete mode 100644 qrtplib.old/rtptimeutilities.cpp delete mode 100644 qrtplib.old/rtptimeutilities.h diff --git a/qrtplib.old/CMakeLists.txt b/qrtplib.old/CMakeLists.txt deleted file mode 100644 index 2de5f94ac..000000000 --- a/qrtplib.old/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -project(qrtplib) - -set(qrtplib_SOURCES - rtperrors.cpp - rtppacket.cpp - rtprandom.cpp - rtprandomrand48.cpp - rtprandomurandom.cpp - rtptimeutilities.cpp -) - -set(qrtplib_HEADERS - rtperrors.h - rtpdefines.h - rtpinternalutils.h - rtppacket.h - rtprandom.h - rtprandomrand48.h - rtprandomurandom.h - rtprawpacket.h - rtpstructs.h - rtptimeutilities.h -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} -) - -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_SHARED) - -add_library(qrtplib SHARED - ${qrtplib_SOURCES} - ${qrtplib_HEADERS_MOC} -) - -target_link_libraries(qrtplib - ${QT_LIBRARIES} -) - -qt5_use_modules(qrtplib Core Network) - -install(TARGETS qrtplib DESTINATION lib) diff --git a/qrtplib.old/rtpdefines.h b/qrtplib.old/rtpdefines.h deleted file mode 100644 index c1d39af42..000000000 --- a/qrtplib.old/rtpdefines.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#ifndef RTPDEFINES_H - -#define RTPDEFINES_H - -#define RTP_VERSION 2 -#define RTP_MAXCSRCS 15 -#define RTP_MINPACKETSIZE 600 -#define RTP_DEFAULTPACKETSIZE 1400 -#define RTP_PROBATIONCOUNT 2 -#define RTP_MAXPRIVITEMS 256 -#define RTP_SENDERTIMEOUTMULTIPLIER 2 -#define RTP_BYETIMEOUTMULTIPLIER 1 -#define RTP_MEMBERTIMEOUTMULTIPLIER 5 -#define RTP_COLLISIONTIMEOUTMULTIPLIER 10 -#define RTP_NOTETTIMEOUTMULTIPLIER 25 -#define RTP_DEFAULTSESSIONBANDWIDTH 10000.0 - -#define RTP_RTCPTYPE_SR 200 -#define RTP_RTCPTYPE_RR 201 -#define RTP_RTCPTYPE_SDES 202 -#define RTP_RTCPTYPE_BYE 203 -#define RTP_RTCPTYPE_APP 204 - -#define RTCP_SDES_ID_CNAME 1 -#define RTCP_SDES_ID_NAME 2 -#define RTCP_SDES_ID_EMAIL 3 -#define RTCP_SDES_ID_PHONE 4 -#define RTCP_SDES_ID_LOCATION 5 -#define RTCP_SDES_ID_TOOL 6 -#define RTCP_SDES_ID_NOTE 7 -#define RTCP_SDES_ID_PRIVATE 8 -#define RTCP_SDES_NUMITEMS_NONPRIVATE 7 -#define RTCP_SDES_MAXITEMLENGTH 255 - -#define RTCP_BYE_MAXREASONLENGTH 255 -#define RTCP_DEFAULTMININTERVAL 5.0 -#define RTCP_DEFAULTBANDWIDTHFRACTION 0.05 -#define RTCP_DEFAULTSENDERFRACTION 0.25 -#define RTCP_DEFAULTHALFATSTARTUP true -#define RTCP_DEFAULTIMMEDIATEBYE true -#define RTCP_DEFAULTSRBYE true - -#endif // RTPDEFINES_H - diff --git a/qrtplib.old/rtperrors.cpp b/qrtplib.old/rtperrors.cpp deleted file mode 100644 index 4723a288c..000000000 --- a/qrtplib.old/rtperrors.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "../qrtplib.old/rtperrors.h" - -#include -#include "../qrtplib.old/rtpdefines.h" -#include "../qrtplib.old/rtpinternalutils.h" - -//#include "rtpdebug.h" - -namespace qrtplib -{ - -struct RTPErrorInfo -{ - int code; - const char *description; -}; - -static RTPErrorInfo ErrorDescriptions[]= -{ - { ERR_RTP_OUTOFMEM,"Out of memory" }, - { ERR_RTP_NOTHREADSUPPORT, "No JThread support was compiled in"}, - { ERR_RTP_COLLISIONLIST_BADADDRESS, "Passed invalid address (null) to collision list"}, - { ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS, "Element already exists in hash table"}, - { ERR_RTP_HASHTABLE_ELEMENTNOTFOUND, "Element not found in hash table"}, - { ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index"}, - { ERR_RTP_HASHTABLE_NOCURRENTELEMENT, "No current element selected in hash table"}, - { ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index"}, - { ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS, "Key value already exists in key hash table"}, - { ERR_RTP_KEYHASHTABLE_KEYNOTFOUND, "Key value not found in key hash table"}, - { ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT, "No current element selected in key hash table"}, - { ERR_RTP_PACKBUILD_ALREADYINIT, "RTP packet builder is already initialized"}, - { ERR_RTP_PACKBUILD_CSRCALREADYINLIST, "The specified CSRC is already in the RTP packet builder's CSRC list"}, - { ERR_RTP_PACKBUILD_CSRCLISTFULL, "The RTP packet builder's CSRC list already contains 15 entries"}, - { ERR_RTP_PACKBUILD_CSRCNOTINLIST, "The specified CSRC was not found in the RTP packet builder's CSRC list"}, - { ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET, "The RTP packet builder's default mark flag is not set"}, - { ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET, "The RTP packet builder's default payload type is not set"}, - { ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET, "The RTP packet builder's default timestamp increment is not set"}, - { ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE, "The specified maximum packet size for the RTP packet builder is invalid"}, - { ERR_RTP_PACKBUILD_NOTINIT, "The RTP packet builder is not initialized"}, - { ERR_RTP_PACKET_BADPAYLOADTYPE, "Invalid payload type"}, - { ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE, "Tried to create an RTP packet which whould exceed the specified maximum packet size"}, - { ERR_RTP_PACKET_EXTERNALBUFFERNULL, "Illegal value (null) passed as external buffer for the RTP packet"}, - { ERR_RTP_PACKET_ILLEGALBUFFERSIZE, "Illegal buffer size specified for the RTP packet"}, - { ERR_RTP_PACKET_INVALIDPACKET, "Invalid RTP packet format"}, - { ERR_RTP_PACKET_TOOMANYCSRCS, "More than 15 CSRCs specified for the RTP packet"}, - { ERR_RTP_POLLTHREAD_ALREADYRUNNING, "Poll thread is already running"}, - { ERR_RTP_POLLTHREAD_CANTINITMUTEX, "Can't initialize a mutex for the poll thread"}, - { ERR_RTP_POLLTHREAD_CANTSTARTTHREAD, "Can't start the poll thread"}, - { ERR_RTP_RTCPCOMPOUND_INVALIDPACKET, "Invalid RTCP compound packet format"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING, "Already building this RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT, "This RTCP compound packet is already built"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT, "There's already a SR or RR in this RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG, "The specified APP data length for the RTCP compound packet is too big"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL, "The specified buffer size for the RTCP comound packet is too small"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH, "The APP data length must be a multiple of four"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE, "The APP packet subtype must be smaller than 32"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE, "Invalid SDES item type specified for the RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL, "The specified maximum packet size for the RTCP compound packet is too small"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE, "Tried to add an SDES item to the RTCP compound packet when no SSRC was present"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT, "An RTCP compound packet must contain a SR or RR"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING, "The RTCP compound packet builder is not initialized"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT, "Adding this data would exceed the specified maximum RTCP compound packet size"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED, "Tried to add a report block to the RTCP compound packet when no SR or RR was started"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS, "Only 31 SSRCs will fit into a BYE packet for the RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG, "The total data for the SDES PRIV item exceeds the maximum size (255 bytes) of an SDES item"}, - { ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT, "The RTCP packet builder is already initialized"}, - { ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE, "The specified maximum packet size for the RTCP packet builder is too small"}, - { ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT, "Speficied an illegal timestamp unit for the the RTCP packet builder"}, - { ERR_RTP_RTCPPACKETBUILDER_NOTINIT, "The RTCP packet builder was not initialized"}, - { ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON, "The RTCP compound packet filled sooner than expected"}, - { ERR_RTP_SCHEDPARAMS_BADFRACTION, "Illegal sender bandwidth fraction specified"}, - { ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL, "The minimum RTCP interval specified for the scheduler is too small"}, - { ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH, "Invalid RTCP bandwidth specified for the RTCP scheduler"}, - { ERR_RTP_SDES_LENGTHTOOBIG, "Specified size for the SDES item exceeds 255 bytes"}, - { ERR_RTP_SDES_PREFIXNOTFOUND, "The specified SDES PRIV prefix was not found"}, - { ERR_RTP_SESSION_ALREADYCREATED, "The session is already created"}, - { ERR_RTP_SESSION_CANTGETLOGINNAME, "Can't retrieve login name"}, - { ERR_RTP_SESSION_CANTINITMUTEX, "A mutex for the RTP session couldn't be initialized"}, - { ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL, "The maximum packet size specified for the RTP session is too small"}, - { ERR_RTP_SESSION_NOTCREATED, "The RTP session was not created"}, - { ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL, "The requested transmission protocol for the RTP session is not supported"}, - { ERR_RTP_SESSION_USINGPOLLTHREAD, "This function is not available when using the RTP poll thread feature"}, - { ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL, "A user-defined transmitter was requested but the supplied transmitter component is NULL"}, - { ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC, "Only one source can be marked as own SSRC in the source table"}, - { ERR_RTP_SOURCES_DONTHAVEOWNSSRC, "No source was marked as own SSRC in the source table"}, - { ERR_RTP_SOURCES_ILLEGALSDESTYPE, "Illegal SDES type specified for processing into the source table"}, - { ERR_RTP_SOURCES_SSRCEXISTS, "Can't create own SSRC because this SSRC identifier is already in the source table"}, - { ERR_RTP_UDPV4TRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_UDPV4TRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_UDPV4TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data"}, - { ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed"}, - { ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed"}, - { ERR_RTP_UDPV4TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket"}, - { ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_UDPV4TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_UDPV4TRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_UDPV4TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_UDPV4TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_UDPV4TRANS_NOTWAITING, "The transmitter is not waiting for incoming data"}, - { ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_UDPV6TRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_UDPV6TRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_UDPV6TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data"}, - { ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed"}, - { ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed"}, - { ERR_RTP_UDPV6TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket"}, - { ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_UDPV6TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_UDPV6TRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_UDPV6TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_UDPV6TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_UDPV6TRANS_NOTWAITING, "The transmitter is not waiting for incoming data"}, - { ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL,"The hostname is larger than the specified buffer size"}, - { ERR_RTP_SDES_MAXPRIVITEMS,"The maximum number of SDES private item prefixes was reached"}, - { ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE,"An invalid probation type was specified"}, - { ERR_RTP_FAKETRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_FAKETRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_FAKETRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_FAKETRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_FAKETRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_FAKETRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_FAKETRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_FAKETRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_FAKETRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED, "The WaitForIncomingData is not implemented in the Gst transmitter"}, - { ERR_RTP_RTPRANDOMURANDOM_CANTOPEN, "Unable to open /dev/urandom for reading"}, - { ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN, "The device /dev/urandom was already opened"}, - { ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED, "The rand_s call is not supported on this platform"}, - { ERR_RTP_EXTERNALTRANS_ALREADYCREATED, "The external transmission component was already created"}, - { ERR_RTP_EXTERNALTRANS_ALREADYINIT, "The external transmission component was already initialized"}, - { ERR_RTP_EXTERNALTRANS_ALREADYWAITING, "The external transmission component is already waiting for incoming data"}, - { ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE, "The external transmission component only supports accepting all incoming packets"}, - { ERR_RTP_EXTERNALTRANS_CANTINITMUTEX, "The external transmitter was unable to initialize a required mutex"}, - { ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS, "Only parameters of type RTPExternalTransmissionParams can be passed to the external transmission component"}, - { ERR_RTP_EXTERNALTRANS_NOACCEPTLIST, "The external transmitter does not have an accept list"}, - { ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED, "The external transmitter does not have a destination list"}, - { ERR_RTP_EXTERNALTRANS_NOIGNORELIST, "The external transmitter does not have an ignore list"}, - { ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT, "The external transmitter does not support the multicast functions"}, - { ERR_RTP_EXTERNALTRANS_NOSENDER, "No sender has been set for this external transmitter"}, - { ERR_RTP_EXTERNALTRANS_NOTCREATED, "The external transmitter has not been created yet"}, - { ERR_RTP_EXTERNALTRANS_NOTINIT, "The external transmitter has not been initialized yet"}, - { ERR_RTP_EXTERNALTRANS_NOTWAITING, "The external transmitter is not currently waiting for incoming data"}, - { ERR_RTP_EXTERNALTRANS_SENDERROR, "The external transmitter was unable to actually send the data"}, - { ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG, "The specified data size exceeds the maximum amount that has been set"}, - { ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT, "Unable to obtain the existing socket info using 'getsockname'"}, - { ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET, "The existing socket specified does not appear to be an IPv4 socket"}, - { ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET, "The existing socket that was specified does not have its port set yet"}, - { ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE, "Can't get the socket type of the specified existing socket"}, - { ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE, "The specified existing socket is not an UDP socket"}, - { ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET, "Can't get a valid socket when trying to choose a port automatically"}, - { ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET, "Can't seem to get RTP/RTCP ports automatically, too many attempts"}, - { ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED, "Flag to change data was requested, but OnChangeRTPOrRTCPData was not reimplemented"}, - { ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED, "The initialization function was already called"}, - { ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT, "Unable to initialize libsrtp context"}, - { ERR_RTP_SECURESESSION_CANTINITMUTEX, "Unable to initialize a mutex" }, - { ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED, "The libsrtp context initilization function must be called before it can be used"}, - { ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT, "There's not enough RTP or RTCP data to encrypt"}, - { ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA, "Unable to encrypt RTP data"}, - { ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA, "Unable to encrypt RTCP data"}, - { ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT, "There's not enough RTP or RTCP data to decrypt"}, - { ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA, "Unable to decrypt RTP data"}, - { ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA, "Unable to decrypt RTCP data"}, - { ERR_RTP_ABORTDESC_ALREADYINIT, "The RTPAbortDescriptors instance is already initialized" }, - { ERR_RTP_ABORTDESC_NOTINIT, "The RTPAbortDescriptors instance is not yet initialized" }, - { ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS, "Unable to create two connected TCP sockets for the abort descriptors" }, - { ERR_RTP_ABORTDESC_CANTCREATEPIPE, "Unable to create a pipe for the abort descriptors" }, - { ERR_RTP_SESSION_THREADSAFETYCONFLICT, "For the background poll thread to be used, thread safety must also be set" }, - { ERR_RTP_SELECT_ERRORINSELECT, "Error in the call to 'select'" }, - { ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE, "A socket descriptor value is too large for a call to 'select' (exceeds FD_SETSIZE)" }, - { ERR_RTP_SELECT_ERRORINPOLL, "Error in the call to 'poll' or 'WSAPoll'" }, - { ERR_RTP_TCPTRANS_NOTINIT, "The TCP transmitter is not yet initialized" }, - { ERR_RTP_TCPTRANS_ALREADYINIT, "The TCP transmitter is already initialized" }, - { ERR_RTP_TCPTRANS_NOTCREATED, "The TCP transmitter is not yet created" }, - { ERR_RTP_TCPTRANS_ALREADYCREATED, "The TCP transmitter is already created" }, - { ERR_RTP_TCPTRANS_ILLEGALPARAMETERS, "The parameters for the TCP transmitter are invalid" }, - { ERR_RTP_TCPTRANS_CANTINITMUTEX, "Unable to initialize a mutex during the initialization of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_ALREADYWAITING, "The TCP transmitter is already waiting for data" }, - { ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE, "The address specified is not a valid address for the TCP transmitter" }, - { ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED, "No socket was specified in the address used for the TCP transmitter" }, - { ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT, "The TCP transmitter does not support multicasting" }, - { ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED, "The TCP transmitter does not support receive modes other than 'accept all'" }, - { ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size for the TCP transmitter is limited to 64KB" }, - { ERR_RTP_TCPTRANS_NOTWAITING, "The TCP transmitter is not waiting for data" }, - { ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS, "The specified destination address (socket) was already added to the destination list of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS, "The specified destination address (socket) was not found in the list of destinations of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_ERRORINSEND, "An error occurred in the TCP transmitter while sending a packet" }, - { ERR_RTP_TCPTRANS_ERRORINRECV, "An error occurred in the TCP transmitter while receiving a packet" }, - { 0,0 } -}; - -std::string RTPGetErrorString(int errcode) -{ - int i; - - if (errcode >= 0) - return std::string("No error"); - - i = 0; - while (ErrorDescriptions[i].code != 0) - { - if (ErrorDescriptions[i].code == errcode) - return std::string(ErrorDescriptions[i].description); - i++; - } - - char str[16]; - - RTP_SNPRINTF(str,16,"(%d)",errcode); - - return std::string("Unknown error code") + std::string(str); -} - -} // end namespace - diff --git a/qrtplib.old/rtperrors.h b/qrtplib.old/rtperrors.h deleted file mode 100644 index 3b9763ee2..000000000 --- a/qrtplib.old/rtperrors.h +++ /dev/null @@ -1,253 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtperrors.h - */ - -#ifndef RTPERRORS_H - -#define RTPERRORS_H - -//#include "rtpconfig.h" -#include - -namespace qrtplib -{ - -/** Returns a string describing the error code \c errcode. */ -std::string RTPGetErrorString(int errcode); - -} // end namespace - -#define ERR_RTP_OUTOFMEM -1 -#define ERR_RTP_NOTHREADSUPPORT -2 -#define ERR_RTP_COLLISIONLIST_BADADDRESS -3 -#define ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS -4 -#define ERR_RTP_HASHTABLE_ELEMENTNOTFOUND -5 -#define ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX -6 -#define ERR_RTP_HASHTABLE_NOCURRENTELEMENT -7 -#define ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX -8 -#define ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS -9 -#define ERR_RTP_KEYHASHTABLE_KEYNOTFOUND -10 -#define ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT -11 -#define ERR_RTP_PACKBUILD_ALREADYINIT -12 -#define ERR_RTP_PACKBUILD_CSRCALREADYINLIST -13 -#define ERR_RTP_PACKBUILD_CSRCLISTFULL -14 -#define ERR_RTP_PACKBUILD_CSRCNOTINLIST -15 -#define ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET -16 -#define ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET -17 -#define ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET -18 -#define ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE -19 -#define ERR_RTP_PACKBUILD_NOTINIT -20 -#define ERR_RTP_PACKET_BADPAYLOADTYPE -21 -#define ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE -22 -#define ERR_RTP_PACKET_EXTERNALBUFFERNULL -23 -#define ERR_RTP_PACKET_ILLEGALBUFFERSIZE -24 -#define ERR_RTP_PACKET_INVALIDPACKET -25 -#define ERR_RTP_PACKET_TOOMANYCSRCS -26 -#define ERR_RTP_POLLTHREAD_ALREADYRUNNING -27 -#define ERR_RTP_POLLTHREAD_CANTINITMUTEX -28 -#define ERR_RTP_POLLTHREAD_CANTSTARTTHREAD -29 -#define ERR_RTP_RTCPCOMPOUND_INVALIDPACKET -30 -#define ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING -31 -#define ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT -32 -#define ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT -33 -#define ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG -34 -#define ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL -35 -#define ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH -36 -#define ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE -37 -#define ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE -38 -#define ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL -39 -#define ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE -40 -#define ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT -41 -#define ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING -42 -#define ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT -43 -#define ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED -44 -#define ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS -45 -#define ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG -46 -#define ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT -47 -#define ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE -48 -#define ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT -49 -#define ERR_RTP_RTCPPACKETBUILDER_NOTINIT -50 -#define ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON -51 -#define ERR_RTP_SCHEDPARAMS_BADFRACTION -52 -#define ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL -53 -#define ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH -54 -#define ERR_RTP_SDES_LENGTHTOOBIG -55 -#define ERR_RTP_SDES_MAXPRIVITEMS -56 -#define ERR_RTP_SDES_PREFIXNOTFOUND -57 -#define ERR_RTP_SESSION_ALREADYCREATED -58 -#define ERR_RTP_SESSION_CANTGETLOGINNAME -59 -#define ERR_RTP_SESSION_CANTINITMUTEX -60 -#define ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL -61 -#define ERR_RTP_SESSION_NOTCREATED -62 -#define ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL -63 -#define ERR_RTP_SESSION_USINGPOLLTHREAD -64 -#define ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC -65 -#define ERR_RTP_SOURCES_DONTHAVEOWNSSRC -66 -#define ERR_RTP_SOURCES_ILLEGALSDESTYPE -67 -#define ERR_RTP_SOURCES_SSRCEXISTS -68 -#define ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL -69 -#define ERR_RTP_UDPV4TRANS_ALREADYCREATED -70 -#define ERR_RTP_UDPV4TRANS_ALREADYINIT -71 -#define ERR_RTP_UDPV4TRANS_ALREADYWAITING -72 -#define ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET -73 -#define ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET -74 -#define ERR_RTP_UDPV4TRANS_CANTCREATESOCKET -75 -#define ERR_RTP_UDPV4TRANS_CANTINITMUTEX -76 -#define ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF -77 -#define ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF -78 -#define ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF -79 -#define ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF -80 -#define ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP -81 -#define ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE -82 -#define ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS -83 -#define ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE -84 -#define ERR_RTP_UDPV4TRANS_NOLOCALIPS -85 -#define ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT -86 -#define ERR_RTP_UDPV4TRANS_NOSUCHENTRY -87 -#define ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS -88 -#define ERR_RTP_UDPV4TRANS_NOTCREATED -89 -#define ERR_RTP_UDPV4TRANS_NOTINIT -90 -#define ERR_RTP_UDPV4TRANS_NOTWAITING -91 -#define ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN -92 -#define ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG -93 -#define ERR_RTP_UDPV6TRANS_ALREADYCREATED -94 -#define ERR_RTP_UDPV6TRANS_ALREADYINIT -95 -#define ERR_RTP_UDPV6TRANS_ALREADYWAITING -96 -#define ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET -97 -#define ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET -98 -#define ERR_RTP_UDPV6TRANS_CANTCREATESOCKET -99 -#define ERR_RTP_UDPV6TRANS_CANTINITMUTEX -100 -#define ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF -101 -#define ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF -102 -#define ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF -103 -#define ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF -104 -#define ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP -105 -#define ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE -106 -#define ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS -107 -#define ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE -108 -#define ERR_RTP_UDPV6TRANS_NOLOCALIPS -109 -#define ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT -110 -#define ERR_RTP_UDPV6TRANS_NOSUCHENTRY -111 -#define ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS -112 -#define ERR_RTP_UDPV6TRANS_NOTCREATED -113 -#define ERR_RTP_UDPV6TRANS_NOTINIT -114 -#define ERR_RTP_UDPV6TRANS_NOTWAITING -115 -#define ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN -116 -#define ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG -117 -#define ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE -118 -#define ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL -119 -#define ERR_RTP_FAKETRANS_ALREADYCREATED -120 -#define ERR_RTP_FAKETRANS_ALREADYINIT -121 -#define ERR_RTP_FAKETRANS_CANTINITMUTEX -122 -#define ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP -123 -#define ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE -124 -#define ERR_RTP_FAKETRANS_ILLEGALPARAMETERS -125 -#define ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE -126 -#define ERR_RTP_FAKETRANS_NOLOCALIPS -127 -#define ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT -128 -#define ERR_RTP_FAKETRANS_NOSUCHENTRY -129 -#define ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS -130 -#define ERR_RTP_FAKETRANS_NOTCREATED -131 -#define ERR_RTP_FAKETRANS_NOTINIT -132 -#define ERR_RTP_FAKETRANS_PORTBASENOTEVEN -133 -#define ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG -134 -#define ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED -135 -#define ERR_RTP_RTPRANDOMURANDOM_CANTOPEN -136 -#define ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN -137 -#define ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED -138 -#define ERR_RTP_EXTERNALTRANS_ALREADYCREATED -139 -#define ERR_RTP_EXTERNALTRANS_ALREADYINIT -140 -#define ERR_RTP_EXTERNALTRANS_ALREADYWAITING -141 -#define ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE -142 -#define ERR_RTP_EXTERNALTRANS_CANTINITMUTEX -143 -#define ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS -144 -#define ERR_RTP_EXTERNALTRANS_NOACCEPTLIST -145 -#define ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED -146 -#define ERR_RTP_EXTERNALTRANS_NOIGNORELIST -147 -#define ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT -148 -#define ERR_RTP_EXTERNALTRANS_NOSENDER -149 -#define ERR_RTP_EXTERNALTRANS_NOTCREATED -150 -#define ERR_RTP_EXTERNALTRANS_NOTINIT -151 -#define ERR_RTP_EXTERNALTRANS_NOTWAITING -152 -#define ERR_RTP_EXTERNALTRANS_SENDERROR -153 -#define ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG -154 -#define ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT -155 -#define ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET -156 -#define ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET -157 -#define ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE -158 -#define ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE -159 -#define ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET -160 -#define ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET -161 -#define ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED -162 -#define ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED -163 -#define ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT -164 -#define ERR_RTP_SECURESESSION_CANTINITMUTEX -165 -#define ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED -166 -#define ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT -167 -#define ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA -168 -#define ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA -169 -#define ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT -170 -#define ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA -171 -#define ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA -172 -#define ERR_RTP_ABORTDESC_ALREADYINIT -173 -#define ERR_RTP_ABORTDESC_NOTINIT -174 -#define ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS -175 -#define ERR_RTP_ABORTDESC_CANTCREATEPIPE -176 -#define ERR_RTP_SESSION_THREADSAFETYCONFLICT -177 -#define ERR_RTP_SELECT_ERRORINSELECT -178 -#define ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE -179 -#define ERR_RTP_SELECT_ERRORINPOLL -180 -#define ERR_RTP_TCPTRANS_NOTINIT -181 -#define ERR_RTP_TCPTRANS_ALREADYINIT -182 -#define ERR_RTP_TCPTRANS_ALREADYCREATED -183 -#define ERR_RTP_TCPTRANS_ILLEGALPARAMETERS -184 -#define ERR_RTP_TCPTRANS_CANTINITMUTEX -185 -#define ERR_RTP_TCPTRANS_ALREADYWAITING -186 -#define ERR_RTP_TCPTRANS_NOTCREATED -187 -#define ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE -188 -#define ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED -189 -#define ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT -190 -#define ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED -191 -#define ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG -192 -#define ERR_RTP_TCPTRANS_NOTWAITING -193 -#define ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS -194 -#define ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS -195 -#define ERR_RTP_TCPTRANS_ERRORINSEND -196 -#define ERR_RTP_TCPTRANS_ERRORINRECV -197 - -#endif // RTPERRORS_H - diff --git a/qrtplib.old/rtpinternalutils.h b/qrtplib.old/rtpinternalutils.h deleted file mode 100644 index 503401bf2..000000000 --- a/qrtplib.old/rtpinternalutils.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2011 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#ifndef RTPINTERNALUTILS_H - -#define RTPINTERNALUTILS_H - -//#include "rtpconfig.h" - -#if defined(RTP_HAVE_SNPRINTF_S) - #include - #include - #define RTP_SNPRINTF _snprintf_s -#elif defined(RTP_HAVE_SNPRINTF) - #include - #include - #define RTP_SNPRINTF _snprintf -#else - #include - #define RTP_SNPRINTF snprintf -#endif - -#ifdef RTP_HAVE_STRNCPY_S - #define RTP_STRNCPY(dest, src, len) strncpy_s((dest), (len), (src), _TRUNCATE) -#else - #define RTP_STRNCPY(dest, src, len) strncpy((dest), (src), (len)) -#endif // RTP_HAVE_STRNCPY_S - -#endif // RTPINTERNALUTILS_H - diff --git a/qrtplib.old/rtppacket.cpp b/qrtplib.old/rtppacket.cpp deleted file mode 100644 index 15a28b8fe..000000000 --- a/qrtplib.old/rtppacket.cpp +++ /dev/null @@ -1,441 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "rtppacket.h" -#include "rtpstructs.h" -#include "rtpdefines.h" -#include "rtperrors.h" -#include "rtprawpacket.h" -#include - -#ifdef RTPDEBUG - #include -#endif // RTPDEBUG - -//#include "rtpdebug.h" - -namespace qrtplib -{ - -void RTPPacket::Clear() -{ - hasextension = false; - hasmarker = false; - numcsrcs = 0; - payloadtype = 0; - extseqnr = 0; - timestamp = 0; - ssrc = 0; - packet = 0; - payload = 0; - packetlength = 0; - payloadlength = 0; - extid = 0; - extension = 0; - extensionlength = 0; - error = 0; - externalbuffer = false; - - uint32_t endianTest32 = 1; - uint8_t *ptr = (uint8_t*) &endianTest32; - m_littleEndian = (*ptr == 1); -} - -RTPPacket::RTPPacket(RTPRawPacket &rawpack) : receivetime(rawpack.GetReceiveTime()) -{ - Clear(); - error = ParseRawPacket(rawpack); -} - -RTPPacket::RTPPacket( - uint8_t payloadtype, - const void *payloaddata, - std::size_t payloadlen, - uint16_t seqnr, - uint32_t timestamp, - uint32_t ssrc, - bool gotmarker, - uint8_t numcsrcs, - const uint32_t *csrcs, - bool gotextension, - uint16_t extensionid, - uint16_t extensionlen_numwords, - const void *extensiondata, - std::size_t maxpacksize) : receivetime(0,0) -{ - Clear(); - - error = BuildPacket( - payloadtype, - payloaddata, - payloadlen, - seqnr, - timestamp, - ssrc, - gotmarker, - numcsrcs, - csrcs, - gotextension, - extensionid, - extensionlen_numwords, - extensiondata, - 0, - maxpacksize); -} - -RTPPacket::RTPPacket( - uint8_t payloadtype, - const void *payloaddata, - std::size_t payloadlen, - uint16_t seqnr, - uint32_t timestamp, - uint32_t ssrc, - bool gotmarker, - uint8_t numcsrcs, - const uint32_t *csrcs, - bool gotextension, - uint16_t extensionid, - uint16_t extensionlen_numwords, - const void *extensiondata, - void *buffer, - std::size_t buffersize) : receivetime(0,0) -{ - Clear(); - - if (buffer == 0) { - error = ERR_RTP_PACKET_EXTERNALBUFFERNULL; - } else if (buffersize <= 0) { - error = ERR_RTP_PACKET_ILLEGALBUFFERSIZE; - } else { - error = BuildPacket( - payloadtype, - payloaddata, - payloadlen, - seqnr, - timestamp, - ssrc, - gotmarker, - numcsrcs, - csrcs, - gotextension, - extensionid, - extensionlen_numwords, - extensiondata, - buffer, - buffersize); - } -} - -int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) -{ - uint8_t *packetbytes; - std::size_t packetlen; - uint8_t payloadtype; - RTPHeader *rtpheader; - bool marker; - int csrccount; - bool hasextension; - int payloadoffset,payloadlength; - int numpadbytes; - RTPExtensionHeader *rtpextheader; - - if (!rawpack.IsRTP()) { // If we didn't receive it on the RTP port, we'll ignore it - return ERR_RTP_PACKET_INVALIDPACKET; - } - - // The length should be at least the size of the RTP header - packetlen = rawpack.GetDataLength(); - - if (packetlen < sizeof(RTPHeader)) { - return ERR_RTP_PACKET_INVALIDPACKET; - } - - packetbytes = (uint8_t *)rawpack.GetData(); - rtpheader = (RTPHeader *)packetbytes; - - // The version number should be correct - if (rtpheader->version != RTP_VERSION) { - return ERR_RTP_PACKET_INVALIDPACKET; - } - - // We'll check if this is possibly a RTCP packet. For this to be possible - // the marker bit and payload type combined should be either an SR or RR - // identifier - marker = (rtpheader->marker == 0)?false:true; - payloadtype = rtpheader->payloadtype; - - if (marker) - { - if (payloadtype == (RTP_RTCPTYPE_SR & 127)) { // don't check high bit (this was the marker!!) - return ERR_RTP_PACKET_INVALIDPACKET; - } - if (payloadtype == (RTP_RTCPTYPE_RR & 127)) { - return ERR_RTP_PACKET_INVALIDPACKET; - } - } - - csrccount = rtpheader->csrccount; - payloadoffset = sizeof(RTPHeader)+(int)(csrccount*sizeof(uint32_t)); - - if (rtpheader->padding) // adjust payload length to take padding into account - { - numpadbytes = (int)packetbytes[packetlen-1]; // last byte contains number of padding bytes - if (numpadbytes <= 0) { - return ERR_RTP_PACKET_INVALIDPACKET; - } - } - else - { - numpadbytes = 0; - } - - hasextension = (rtpheader->extension != 0); - - if (hasextension) // got header extension - { - rtpextheader = (RTPExtensionHeader *)(packetbytes+payloadoffset); - payloadoffset += sizeof(RTPExtensionHeader); - - uint16_t exthdrlen = qToHost(rtpextheader->length); // ntohs(rtpextheader->length); - payloadoffset += ((int)exthdrlen)*sizeof(uint32_t); - } - else - { - rtpextheader = 0; - } - - payloadlength = packetlen-numpadbytes-payloadoffset; - - if (payloadlength < 0) { - return ERR_RTP_PACKET_INVALIDPACKET; - } - - // Now, we've got a valid packet, so we can create a new instance of RTPPacket - // and fill in the members - - RTPPacket::hasextension = hasextension; - - if (hasextension) - { - RTPPacket::extid = qToHost(rtpextheader->extid); // ntohs(rtpextheader->extid); - RTPPacket::extensionlength = ((int)qToHost(rtpextheader->length))*sizeof(uint32_t); // ((int)ntohs(rtpextheader->length))*sizeof(uint32_t); - RTPPacket::extension = ((uint8_t *)rtpextheader)+sizeof(RTPExtensionHeader); - } - - RTPPacket::hasmarker = marker; - RTPPacket::numcsrcs = csrccount; - RTPPacket::payloadtype = payloadtype; - - // Note: we don't fill in the EXTENDED sequence number here, since we - // don't have information about the source here. We just fill in the low - // 16 bits - RTPPacket::extseqnr = (uint32_t) qToHost(rtpheader->sequencenumber); // ntohs(rtpheader->sequencenumber); - - RTPPacket::timestamp = qToHost(rtpheader->timestamp); // ntohl(rtpheader->timestamp); - RTPPacket::ssrc = qToHost(rtpheader->ssrc); // ntohl(rtpheader->ssrc); - RTPPacket::packet = packetbytes; - RTPPacket::payload = packetbytes+payloadoffset; - RTPPacket::packetlength = packetlen; - RTPPacket::payloadlength = payloadlength; - - // We'll zero the data of the raw packet, since we're using it here now! - rawpack.ZeroData(); - - return 0; -} - -uint32_t RTPPacket::GetCSRC(int num) const -{ - if (num >= numcsrcs) { - return 0; - } - - uint8_t *csrcpos; - uint32_t *csrcval_nbo; - uint32_t csrcval_hbo; - - csrcpos = packet+sizeof(RTPHeader)+num*sizeof(uint32_t); - csrcval_nbo = (uint32_t *)csrcpos; - csrcval_hbo = qToHost(*csrcval_nbo); // ntohl(*csrcval_nbo); - return csrcval_hbo; -} - -int RTPPacket::BuildPacket( - uint8_t payloadtype, - const void *payloaddata, - std::size_t payloadlen, - uint16_t seqnr, - uint32_t timestamp, - uint32_t ssrc, - bool gotmarker, - uint8_t numcsrcs, - const uint32_t *csrcs, - bool gotextension, - uint16_t extensionid, - uint16_t extensionlen_numwords, - const void *extensiondata, - void *buffer, - std::size_t maxsize) -{ - if (numcsrcs > RTP_MAXCSRCS) { - return ERR_RTP_PACKET_TOOMANYCSRCS; - } - - if (payloadtype > 127) { // high bit should not be used - return ERR_RTP_PACKET_BADPAYLOADTYPE; - } - if (payloadtype == 72 || payloadtype == 73) { // could cause confusion with rtcp types - return ERR_RTP_PACKET_BADPAYLOADTYPE; - } - - packetlength = sizeof(RTPHeader); - packetlength += sizeof(uint32_t)*((std::size_t)numcsrcs); - - if (gotextension) - { - packetlength += sizeof(RTPExtensionHeader); - packetlength += sizeof(uint32_t)*((size_t)extensionlen_numwords); - } - - packetlength += payloadlen; - - if (maxsize > 0 && packetlength > maxsize) - { - packetlength = 0; - return ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE; - } - - // Ok, now we'll just fill in... - - RTPHeader *rtphdr; - - if (buffer == 0) - { - packet = new uint8_t[packetlength]; - if (packet == 0) - { - packetlength = 0; - return ERR_RTP_OUTOFMEM; - } - externalbuffer = false; - } - else - { - packet = (uint8_t *)buffer; - externalbuffer = true; - } - - RTPPacket::hasmarker = gotmarker; - RTPPacket::hasextension = gotextension; - RTPPacket::numcsrcs = numcsrcs; - RTPPacket::payloadtype = payloadtype; - RTPPacket::extseqnr = (uint32_t)seqnr; - RTPPacket::timestamp = timestamp; - RTPPacket::ssrc = ssrc; - RTPPacket::payloadlength = payloadlen; - RTPPacket::extid = extensionid; - RTPPacket::extensionlength = ((std::size_t)extensionlen_numwords)*sizeof(uint32_t); - - rtphdr = (RTPHeader *)packet; - rtphdr->version = RTP_VERSION; - rtphdr->padding = 0; - if (gotmarker) - rtphdr->marker = 1; - else - rtphdr->marker = 0; - if (gotextension) - rtphdr->extension = 1; - else - rtphdr->extension = 0; - rtphdr->csrccount = numcsrcs; - rtphdr->payloadtype = payloadtype&127; // make sure high bit isn't set - rtphdr->sequencenumber = qToBigEndian(seqnr); // htons(seqnr); - rtphdr->timestamp = qToBigEndian(timestamp); // htonl(timestamp); - rtphdr->ssrc = qToBigEndian(ssrc); // htonl(ssrc); - - uint32_t *curcsrc; - int i; - curcsrc = (uint32_t *)(packet+sizeof(RTPHeader)); - - for (i = 0 ; i < numcsrcs ; i++,curcsrc++) { - *curcsrc = qToBigEndian(csrcs[i]); // htonl(csrcs[i]); - } - - payload = packet+sizeof(RTPHeader)+((std::size_t)numcsrcs)*sizeof(uint32_t); - - if (gotextension) - { - RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *)payload; - - rtpexthdr->extid = qToBigEndian(extensionid); // htons(extensionid); - rtpexthdr->length = qToBigEndian((uint16_t)extensionlen_numwords); // htons((uint16_t)extensionlen_numwords); - - payload += sizeof(RTPExtensionHeader); - memcpy(payload,extensiondata,RTPPacket::extensionlength); - - payload += RTPPacket::extensionlength; - } - - memcpy(payload,payloaddata,payloadlen); - return 0; -} - -#ifdef RTPDEBUG -void RTPPacket::Dump() -{ - int i; - - printf("Payload type: %d\n",(int)GetPayloadType()); - printf("Extended sequence number: 0x%08x\n",GetExtendedSequenceNumber()); - printf("Timestamp: 0x%08x\n",GetTimestamp()); - printf("SSRC: 0x%08x\n",GetSSRC()); - printf("Marker: %s\n",HasMarker()?"yes":"no"); - printf("CSRC count: %d\n",GetCSRCCount()); - for (i = 0 ; i < GetCSRCCount() ; i++) - printf(" CSRC[%02d]: 0x%08x\n",i,GetCSRC(i)); - printf("Payload: %s\n",GetPayloadData()); - printf("Payload length: %d\n",GetPayloadLength()); - printf("Packet length: %d\n",GetPacketLength()); - printf("Extension: %s\n",HasExtension()?"yes":"no"); - if (HasExtension()) - { - printf(" Extension ID: 0x%04x\n",GetExtensionID()); - printf(" Extension data: %s\n",GetExtensionData()); - printf(" Extension length: %d\n",GetExtensionLength()); - } -} -#endif // RTPDEBUG - -} // end namespace - diff --git a/qrtplib.old/rtppacket.h b/qrtplib.old/rtppacket.h deleted file mode 100644 index 6f4f817a4..000000000 --- a/qrtplib.old/rtppacket.h +++ /dev/null @@ -1,287 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtppacket.h - */ - -#ifndef RTPPACKET_H - -#define RTPPACKET_H - -//#include "rtpconfig.h" -//#include "rtptypes.h" -#include -#include -#include "../qrtplib.old/rtptimeutilities.h" - -namespace qrtplib -{ - -class RTPRawPacket; - -/** Represents an RTP Packet. - * The RTPPacket class can be used to parse a RTPRawPacket instance if it represents RTP data. - * The class can also be used to create a new RTP packet according to the parameters specified by - * the user. - */ -class RTPPacket -{ -public: - /** Creates an RTPPacket instance based upon the data in \c rawpack. - * Creates an RTPPacket instance based upon the data in \c rawpack. - * If successful, the data is moved from the raw packet to the RTPPacket instance. - */ - RTPPacket(RTPRawPacket &rawpack); - - /** Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. - * Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. - * If \c maxpacksize is not equal to zero, an error is generated if the total packet size would exceed - * \c maxpacksize. The arguments of the constructor are self-explanatory. Note that the size of a header - * extension is specified in a number of 32-bit words. - */ - RTPPacket( - uint8_t payloadtype, - const void *payloaddata, - std::size_t payloadlen, - uint16_t seqnr, - uint32_t timestamp, - uint32_t ssrc, - bool gotmarker, - uint8_t numcsrcs, - const uint32_t *csrcs, - bool gotextension, - uint16_t extensionid, - uint16_t extensionlen_numwords, - const void *extensiondata, - std::size_t maxpacksize); - - /** This constructor is similar to the other constructor, but here data is stored in an external buffer - * \c buffer with size \c buffersize. */ - RTPPacket(uint8_t payloadtype, - const void *payloaddata, - std::size_t payloadlen, - uint16_t seqnr, - uint32_t timestamp, - uint32_t ssrc, - bool gotmarker, - uint8_t numcsrcs, - const uint32_t *csrcs, - bool gotextension, - uint16_t extensionid, - uint16_t extensionlen_numwords, - const void *extensiondata, - void *buffer, - std::size_t buffersize); - - virtual ~RTPPacket() - { - if (packet && !externalbuffer){ - delete[] packet; - //RTPDeleteByteArray(packet); - } - } - - /** If an error occurred in one of the constructors, this function returns the error code. */ - int GetCreationError() const - { - return error; - } - - /** Returns \c true if the RTP packet has a header extension and \c false otherwise. */ - bool HasExtension() const - { - return hasextension; - } - - /** Returns \c true if the marker bit was set and \c false otherwise. */ - bool HasMarker() const - { - return hasmarker; - } - - /** Returns the number of CSRCs contained in this packet. */ - int GetCSRCCount() const - { - return numcsrcs; - } - - /** Returns a specific CSRC identifier. - * Returns a specific CSRC identifier. The parameter \c num can go from 0 to GetCSRCCount()-1. - */ - uint32_t GetCSRC(int num) const; - - /** Returns the payload type of the packet. */ - uint8_t GetPayloadType() const - { - return payloadtype; - } - - /** Returns the extended sequence number of the packet. - * Returns the extended sequence number of the packet. When the packet is just received, - * only the low $16$ bits will be set. The high 16 bits can be filled in later. - */ - uint32_t GetExtendedSequenceNumber() const - { - return extseqnr; - } - - /** Returns the sequence number of this packet. */ - uint16_t GetSequenceNumber() const - { - return (uint16_t)(extseqnr&0x0000FFFF); - } - - /** Sets the extended sequence number of this packet to \c seq. */ - void SetExtendedSequenceNumber(uint32_t seq) - { - extseqnr = seq; - } - - /** Returns the timestamp of this packet. */ - uint32_t GetTimestamp() const - { - return timestamp; - } - - /** Returns the SSRC identifier stored in this packet. */ - uint32_t GetSSRC() const - { - return ssrc; - } - - /** Returns a pointer to the data of the entire packet. */ - uint8_t *GetPacketData() const - { - return packet; - } - - /** Returns a pointer to the actual payload data. */ - uint8_t *GetPayloadData() const - { - return payload; - } - - /** Returns the length of the entire packet. */ - std::size_t GetPacketLength() const - { - return packetlength; - } - - /** Returns the payload length. */ - std::size_t GetPayloadLength() const - { - return payloadlength; - } - - /** If a header extension is present, this function returns the extension identifier. */ - uint16_t GetExtensionID() const - { - return extid; - } - - /** Returns the length of the header extension data. */ - uint8_t *GetExtensionData() const - { - return extension; - } - - /** Returns the length of the header extension data. */ - std::size_t GetExtensionLength() const - { - return extensionlength; - } -#ifdef RTPDEBUG - void Dump(); -#endif // RTPDEBUG - - /** Returns the time at which this packet was received. - * When an RTPPacket instance is created from an RTPRawPacket instance, the raw packet's - * reception time is stored in the RTPPacket instance. This function then retrieves that - * time. - */ - RTPTime GetReceiveTime() const - { - return receivetime; - } - -private: - void Clear(); - int ParseRawPacket(RTPRawPacket &rawpack); - int BuildPacket( - uint8_t payloadtype, - const void *payloaddata, - std::size_t payloadlen, - uint16_t seqnr, - uint32_t timestamp, - uint32_t ssrc, - bool gotmarker, - uint8_t numcsrcs, - const uint32_t *csrcs, - bool gotextension, - uint16_t extensionid, - uint16_t extensionlen_numwords, - const void *extensiondata, - void *buffer, - std::size_t maxsize); - - template - T qToHost(const T& x) const { - return m_littleEndian ? qToLittleEndian(x) : qToBigEndian(x); - } - - int error; - bool m_littleEndian; - - bool hasextension,hasmarker; - int numcsrcs; - - uint8_t payloadtype; - uint32_t extseqnr,timestamp,ssrc; - uint8_t *packet,*payload; - std::size_t packetlength,payloadlength; - - uint16_t extid; - uint8_t *extension; - std::size_t extensionlength; - - bool externalbuffer; - - RTPTime receivetime; -}; - -} // end namespace - -#endif // RTPPACKET_H - diff --git a/qrtplib.old/rtppacketbuilder.cpp b/qrtplib.old/rtppacketbuilder.cpp deleted file mode 100644 index 3a281b42e..000000000 --- a/qrtplib.old/rtppacketbuilder.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "../qrtplib.old/rtppacketbuilder.h" - -#include -#include - -#include "../qrtplib.old/rtperrors.h" -#include "../qrtplib.old/rtppacket.h" -#include "../qrtplib.old/rtpsources.h" -//#include "rtpdebug.h" - -namespace qrtplib -{ - -RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r) : rtprnd(r), lastwallclocktime(0,0) -{ - init = false; - deftsset = false; - defaultpayloadtype = 0; - lastrtptimestamp = 0; - ssrc = 0; - numcsrcs = 0; - defmarkset = false; - defaultmark = false; - defaulttimestampinc = 0; - timestamp = 0; - buffer = 0; - numpackets = 0; - seqnr = 0; - numpayloadbytes = 0; - prevrtptimestamp = 0; - defptset = false; -} - -RTPPacketBuilder::~RTPPacketBuilder() -{ - Destroy(); -} - -int RTPPacketBuilder::Init(std::size_t max) -{ - if (init) { - return ERR_RTP_PACKBUILD_ALREADYINIT; - } - - if (max <= 0) { - return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; - } - - maxpacksize = max; - buffer = new uint8_t[max]; - if (buffer == 0) { - return ERR_RTP_OUTOFMEM; - } - packetlength = 0; - - CreateNewSSRC(); - - deftsset = false; - defptset = false; - defmarkset = false; - - numcsrcs = 0; - - init = true; - return 0; -} - -void RTPPacketBuilder::Destroy() -{ - if (!init) { - return; - } - delete[] buffer; - init = false; -} - -int RTPPacketBuilder::SetMaximumPacketSize(std::size_t max) -{ - uint8_t *newbuf; - - if (max <= 0) { - return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; - } - newbuf = new uint8_t[max]; - if (newbuf == 0) { - return ERR_RTP_OUTOFMEM; - } - - delete[] buffer; - buffer = newbuf; - maxpacksize = max; - return 0; -} - -int RTPPacketBuilder::AddCSRC(uint32_t csrc) -{ - if (!init) { - return ERR_RTP_PACKBUILD_NOTINIT; - } - if (numcsrcs >= RTP_MAXCSRCS) { - return ERR_RTP_PACKBUILD_CSRCLISTFULL; - } - - int i; - - for (i = 0; i < numcsrcs; i++) { - if (csrcs[i] == csrc) { - return ERR_RTP_PACKBUILD_CSRCALREADYINLIST; - } - } - csrcs[numcsrcs] = csrc; - numcsrcs++; - return 0; -} - -int RTPPacketBuilder::DeleteCSRC(uint32_t csrc) -{ - if (!init) { - return ERR_RTP_PACKBUILD_NOTINIT; - } - - int i = 0; - bool found = false; - - while (!found && i < numcsrcs) - { - if (csrcs[i] == csrc) { - found = true; - } else { - i++; - } - } - - if (!found) { - return ERR_RTP_PACKBUILD_CSRCNOTINLIST; - } - - // move the last csrc in the place of the deleted one - numcsrcs--; - if (numcsrcs > 0 && numcsrcs != i) { - csrcs[i] = csrcs[numcsrcs]; - } - - return 0; -} - -void RTPPacketBuilder::ClearCSRCList() -{ - if (!init) { - return; - } - numcsrcs = 0; -} - -uint32_t RTPPacketBuilder::CreateNewSSRC() -{ - ssrc = rtprnd.GetRandom32(); - timestamp = rtprnd.GetRandom32(); - seqnr = rtprnd.GetRandom16(); - - // p 38: the count SHOULD be reset if the sender changes its SSRC identifier - numpayloadbytes = 0; - numpackets = 0; - return ssrc; -} - -uint32_t RTPPacketBuilder::CreateNewSSRC(RTPSources &sources) -{ - bool found; - - do - { - ssrc = rtprnd.GetRandom32(); - found = sources.GotEntry(ssrc); - } while (found); - - timestamp = rtprnd.GetRandom32(); - seqnr = rtprnd.GetRandom16(); - - // p 38: the count SHOULD be reset if the sender changes its SSRC identifier - numpayloadbytes = 0; - numpackets = 0; - return ssrc; -} - -int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len) -{ - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!defptset) - return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; - if (!defmarkset) - return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,false); -} - -int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len, - uint8_t pt,bool mark,uint32_t timestampinc) -{ - if (!init) { - return ERR_RTP_PACKBUILD_NOTINIT; - } - - return PrivateBuildPacket(data,len,pt,mark,timestampinc,false); -} - -int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) -{ - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!defptset) - return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; - if (!defmarkset) - return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,true,hdrextID,hdrextdata,numhdrextwords); -} - -int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) -{ - if (!init) { - return ERR_RTP_PACKBUILD_NOTINIT; - } - return PrivateBuildPacket(data,len,pt,mark,timestampinc,true,hdrextID,hdrextdata,numhdrextwords); - -} - -int RTPPacketBuilder::PrivateBuildPacket(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) -{ - RTPPacket p(pt,data,len,seqnr,timestamp,ssrc,mark,numcsrcs,csrcs,gotextension,hdrextID, - (uint16_t)numhdrextwords,hdrextdata,buffer,maxpacksize,GetMemoryManager()); - int status = p.GetCreationError(); - - if (status < 0) { - return status - }; - packetlength = p.GetPacketLength(); - - if (numpackets == 0) // first packet - { - lastwallclocktime = RTPTime::CurrentTime(); - lastrtptimestamp = timestamp; - prevrtptimestamp = timestamp; - } - else if (timestamp != prevrtptimestamp) - { - lastwallclocktime = RTPTime::CurrentTime(); - lastrtptimestamp = timestamp; - prevrtptimestamp = timestamp; - } - - numpayloadbytes += (uint32_t)p.GetPayloadLength(); - numpackets++; - timestamp += timestampinc; - seqnr++; - - return 0; -} - -} // end namespace - diff --git a/qrtplib.old/rtppacketbuilder.h b/qrtplib.old/rtppacketbuilder.h deleted file mode 100644 index b421b33f1..000000000 --- a/qrtplib.old/rtppacketbuilder.h +++ /dev/null @@ -1,275 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtppacketbuilder.h - */ - -#ifndef RTPPACKETBUILDER_H - -#define RTPPACKETBUILDER_H - -//#include "rtpconfig.h" -#include "../qrtplib.old/rtpdefines.h" -#include "../qrtplib.old/rtperrors.h" -#include "../qrtplib.old/rtprandom.h" -#include "../qrtplib.old/rtptimeutilities.h" -//#include "rtptypes.h" - -namespace qrtplib -{ - -class RTPSources; - -/** This class can be used to build RTP packets and is a bit more high-level than the RTPPacket - * class: it generates an SSRC identifier, keeps track of timestamp and sequence number etc. - */ -class RTPPacketBuilder -{ -public: - /** Constructs an instance which will use \c rtprand for generating random numbers - * (used to initialize the SSRC value and sequence number), optionally installing a memory manager. - **/ - RTPPacketBuilder(RTPRandom &rtprand); - ~RTPPacketBuilder(); - - /** Initializes the builder to only allow packets with a size below \c maxpacksize. */ - int Init(std::size_t maxpacksize); - - /** Cleans up the builder. */ - void Destroy(); - - /** Returns the number of packets which have been created with the current SSRC identifier. */ - uint32_t GetPacketCount() { if (!init) return 0; return numpackets; } - - /** Returns the number of payload octets which have been generated with this SSRC identifier. */ - uint32_t GetPayloadOctetCount() { if (!init) return 0; return numpayloadbytes; } - - /** Sets the maximum allowed packet size to \c maxpacksize. */ - int SetMaximumPacketSize(std::size_t maxpacksize); - - /** Adds a CSRC to the CSRC list which will be stored in the RTP packets. */ - int AddCSRC(uint32_t csrc); - - /** Deletes a CSRC from the list which will be stored in the RTP packets. */ - int DeleteCSRC(uint32_t csrc); - - /** Clears the CSRC list. */ - void ClearCSRCList(); - - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type, marker - * and timestamp increment used will be those that have been set using the \c SetDefault - * functions below. - */ - int BuildPacket(const void *data, std::size_t len); - - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type will be - * set to \c pt, the marker bit to \c mark and after building this packet, the timestamp will - * be incremented with \c timestamp. - */ - int BuildPacket(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc); - - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type, marker - * and timestamp increment used will be those that have been set using the \c SetDefault - * functions below. This packet will also contain an RTP header extension with identifier - * \c hdrextID and data \c hdrextdata. The length of the header extension data is given by - * \c numhdrextwords which expresses the length in a number of 32-bit words. - */ - int BuildPacketEx(const void *data, std::size_t len, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); - - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type will be set - * to \c pt, the marker bit to \c mark and after building this packet, the timestamp will - * be incremented with \c timestamp. This packet will also contain an RTP header extension - * with identifier \c hdrextID and data \c hdrextdata. The length of the header extension - * data is given by \c numhdrextwords which expresses the length in a number of 32-bit words. - */ - int BuildPacketEx(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); - - /** Returns a pointer to the last built RTP packet data. */ - uint8_t *GetPacket() { if (!init) return 0; return buffer; } - - /** Returns the size of the last built RTP packet. */ - size_t GetPacketLength() { if (!init) return 0; return packetlength; } - - /** Sets the default payload type to \c pt. */ - int SetDefaultPayloadType(uint8_t pt); - - /** Sets the default marker bit to \c m. */ - int SetDefaultMark(bool m); - - /** Sets the default timestamp increment to \c timestampinc. */ - int SetDefaultTimestampIncrement(uint32_t timestampinc); - - /** This function increments the timestamp with the amount given by \c inc. - * This function increments the timestamp with the amount given by \c inc. This can be useful - * if, for example, a packet was not sent because it contained only silence. Then, this function - * should be called to increment the timestamp with the appropriate amount so that the next packets - * will still be played at the correct time at other hosts. - */ - int IncrementTimestamp(uint32_t inc); - - /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement - * member function. - * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement - * member function. This can be useful if, for example, a packet was not sent because it contained only silence. - * Then, this function should be called to increment the timestamp with the appropriate amount so that the next - * packets will still be played at the correct time at other hosts. - */ - int IncrementTimestampDefault(); - - /** Creates a new SSRC to be used in generated packets. - * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and - * sequence number offsets. - */ - uint32_t CreateNewSSRC(); - - /** Creates a new SSRC to be used in generated packets. - * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and - * sequence number offsets. The source table \c sources is used to make sure that the chosen SSRC - * isn't used by another participant yet. - */ - uint32_t CreateNewSSRC(RTPSources &sources); - - /** Returns the current SSRC identifier. */ - uint32_t GetSSRC() const { if (!init) return 0; return ssrc; } - - /** Returns the current RTP timestamp. */ - uint32_t GetTimestamp() const { if (!init) return 0; return timestamp; } - - /** Returns the current sequence number. */ - uint16_t GetSequenceNumber() const { if (!init) return 0; return seqnr; } - - /** Returns the time at which a packet was generated. - * Returns the time at which a packet was generated. This is not necessarily the time at which - * the last RTP packet was generated: if the timestamp increment was zero, the time is not updated. - */ - RTPTime GetPacketTime() const { if (!init) return RTPTime(0,0); return lastwallclocktime; } - - /** Returns the RTP timestamp which corresponds to the time returned by the previous function. */ - uint32_t GetPacketTimestamp() const { if (!init) return 0; return lastrtptimestamp; } - - /** Sets a specific SSRC to be used. - * Sets a specific SSRC to be used. Does not create a new timestamp offset or sequence number - * offset. Does not reset the packet count or byte count. Think twice before using this! - */ - void AdjustSSRC(uint32_t s) { ssrc = s; } -private: - int PrivateBuildPacket(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, - uint16_t hdrextID = 0, const void *hdrextdata = 0, std::size_t numhdrextwords = 0); - - RTPRandom &rtprnd; - std::size_t maxpacksize; - uint8_t *buffer; - std::size_t packetlength; - - uint32_t numpayloadbytes; - uint32_t numpackets; - bool init; - - uint32_t ssrc; - uint32_t timestamp; - uint16_t seqnr; - - uint32_t defaulttimestampinc; - uint8_t defaultpayloadtype; - bool defaultmark; - - bool deftsset,defptset,defmarkset; - - uint32_t csrcs[RTP_MAXCSRCS]; - int numcsrcs; - - RTPTime lastwallclocktime; - uint32_t lastrtptimestamp; - uint32_t prevrtptimestamp; -}; - -inline int RTPPacketBuilder::SetDefaultPayloadType(uint8_t pt) -{ - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - defptset = true; - defaultpayloadtype = pt; - return 0; -} - -inline int RTPPacketBuilder::SetDefaultMark(bool m) -{ - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - defmarkset = true; - defaultmark = m; - return 0; -} - -inline int RTPPacketBuilder::SetDefaultTimestampIncrement(uint32_t timestampinc) -{ - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - deftsset = true; - defaulttimestampinc = timestampinc; - return 0; -} - -inline int RTPPacketBuilder::IncrementTimestamp(uint32_t inc) -{ - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - timestamp += inc; - return 0; -} - -inline int RTPPacketBuilder::IncrementTimestampDefault() -{ - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - timestamp += defaulttimestampinc; - return 0; -} - -} // end namespace - -#endif // RTPPACKETBUILDER_H - diff --git a/qrtplib.old/rtprandom.cpp b/qrtplib.old/rtprandom.cpp deleted file mode 100644 index 6a6aaa207..000000000 --- a/qrtplib.old/rtprandom.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "../qrtplib.old/rtprandom.h" - -#include -#include - -#include -#include "../qrtplib.old/rtprandomrand48.h" -#include "../qrtplib.old/rtprandomurandom.h" - -//#include "rtpdebug.h" - -namespace qrtplib -{ - -uint32_t RTPRandom::PickSeed() -{ - uint32_t x; - x = (uint32_t) getpid(); - QDateTime currentDateTime = QDateTime::currentDateTime(); - x += currentDateTime.toTime_t(); -#if defined(WIN32) - x += QDateTime::currentMSecsSinceEpoch() % 1000; -#else - x += (uint32_t)clock(); -#endif - x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); - return x; -} - -RTPRandom *RTPRandom::CreateDefaultRandomNumberGenerator() -{ - RTPRandomURandom *r = new RTPRandomURandom(); - RTPRandom *rRet = r; - - if (r->Init() < 0) // fall back to rand48 - { - delete r; - rRet = new RTPRandomRand48(); - } - - return rRet; -} - -} // end namespace - diff --git a/qrtplib.old/rtprandom.h b/qrtplib.old/rtprandom.h deleted file mode 100644 index e8316366b..000000000 --- a/qrtplib.old/rtprandom.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtprandom.h - */ - -#ifndef RTPRANDOM_H - -#define RTPRANDOM_H - -//#include "rtpconfig.h" -//#include "rtptypes.h" -#include -#include - -#define RTPRANDOM_2POWMIN63 1.08420217248550443400745280086994171142578125e-19 - -namespace qrtplib -{ - -/** Interface for generating random numbers. */ -class RTPRandom -{ -public: - RTPRandom() { } - virtual ~RTPRandom() { } - - /** Returns a random eight bit value. */ - virtual uint8_t GetRandom8() = 0; - - /** Returns a random sixteen bit value. */ - virtual uint16_t GetRandom16() = 0; - - /** Returns a random thirty-two bit value. */ - virtual uint32_t GetRandom32() = 0; - - /** Returns a random number between $0.0$ and $1.0$. */ - virtual double GetRandomDouble() = 0; - - /** Can be used by subclasses to generate a seed for a random number generator. */ - uint32_t PickSeed(); - - /** Allocate a default random number generator based on your platform. */ - static RTPRandom *CreateDefaultRandomNumberGenerator(); -}; - -} // end namespace - -#endif // RTPRANDOM_H - diff --git a/qrtplib.old/rtprandomrand48.cpp b/qrtplib.old/rtprandomrand48.cpp deleted file mode 100644 index 58c79e415..000000000 --- a/qrtplib.old/rtprandomrand48.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "../qrtplib.old/rtprandomrand48.h" - -namespace qrtplib -{ - -RTPRandomRand48::RTPRandomRand48() -{ - SetSeed(PickSeed()); -} - -RTPRandomRand48::RTPRandomRand48(uint32_t seed) -{ - SetSeed(seed); -} - -RTPRandomRand48::~RTPRandomRand48() -{ -} - -void RTPRandomRand48::SetSeed(uint32_t seed) -{ - state = ((uint64_t)seed) << 16 | 0x330EULL; -} - -uint8_t RTPRandomRand48::GetRandom8() -{ - uint32_t x = ((GetRandom32() >> 24)&0xff); - - return (uint8_t)x; -} - -uint16_t RTPRandomRand48::GetRandom16() -{ - uint32_t x = ((GetRandom32() >> 16)&0xffff); - - return (uint16_t)x; -} - -uint32_t RTPRandomRand48::GetRandom32() -{ - state = ((0x5DEECE66DULL*state) + 0xBULL)&0x0000ffffffffffffULL; - uint32_t x = (uint32_t)((state>>16)&0xffffffffULL); - return x; -} - -double RTPRandomRand48::GetRandomDouble() -{ - state = ((0x5DEECE66DULL*state) + 0xBULL)&0x0000ffffffffffffULL; - int64_t x = (int64_t)state; - double y = 3.552713678800500929355621337890625e-15 * (double)x; - return y; -} - -} // end namespace - diff --git a/qrtplib.old/rtprandomrand48.h b/qrtplib.old/rtprandomrand48.h deleted file mode 100644 index dcc85c2a5..000000000 --- a/qrtplib.old/rtprandomrand48.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtprandomrand48.h - */ - -#ifndef RTPRANDOMRAND48_H - -#define RTPRANDOMRAND48_H - -//#include "rtpconfig.h" -#include -#include -#include "../qrtplib.old/rtprandom.h" - -namespace qrtplib -{ - -/** A random number generator using the algorithm of the rand48 set of functions. */ -class RTPRandomRand48 : public RTPRandom -{ -public: - RTPRandomRand48(); - RTPRandomRand48(uint32_t seed); - ~RTPRandomRand48(); - - uint8_t GetRandom8(); - uint16_t GetRandom16(); - uint32_t GetRandom32(); - double GetRandomDouble(); -private: - void SetSeed(uint32_t seed); - uint64_t state; -}; - -} // end namespace - -#endif // RTPRANDOMRAND48_H - diff --git a/qrtplib.old/rtprandomurandom.cpp b/qrtplib.old/rtprandomurandom.cpp deleted file mode 100644 index fc909dbd6..000000000 --- a/qrtplib.old/rtprandomurandom.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -#include "../qrtplib.old/rtprandomurandom.h" - -#include "../qrtplib.old/rtperrors.h" - -//#include "rtpdebug.h" - -namespace qrtplib -{ - -RTPRandomURandom::RTPRandomURandom() -{ - device = 0; -} - -RTPRandomURandom::~RTPRandomURandom() -{ - if (device) - fclose(device); -} - -int RTPRandomURandom::Init() -{ - if (device) - return ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN; - - device = fopen("/dev/urandom","rb"); - if (device == 0) - return ERR_RTP_RTPRANDOMURANDOM_CANTOPEN; - - return 0; -} - -uint8_t RTPRandomURandom::GetRandom8() -{ - if (!device) - return 0; - - uint8_t value; - - if (fread(&value, sizeof(uint8_t), 1, device) == sizeof(uint8_t)) { - return value; - } else { - return 0; - } -} - -uint16_t RTPRandomURandom::GetRandom16() -{ - if (!device) - return 0; - - uint16_t value; - - if (fread(&value, sizeof(uint16_t), 1, device) == sizeof(uint16_t)) { - return value; - } else { - return 0; - } -} - -uint32_t RTPRandomURandom::GetRandom32() -{ - if (!device) - return 0; - - uint32_t value; - - if (fread(&value, sizeof(uint32_t), 1, device) == sizeof(uint32_t)) { - return value; - } else { - return 0; - } - - return value; -} - -double RTPRandomURandom::GetRandomDouble() -{ - if (!device) - return 0; - - uint64_t value; - - if (fread(&value, sizeof(uint64_t), 1, device) == sizeof(uint64_t)) - { - value &= 0x7fffffffffffffffULL; - int64_t value2 = (int64_t)value; - double x = RTPRANDOM_2POWMIN63*(double)value2; - return x; - } - else - { - return 0; - } -} - -} // end namespace - diff --git a/qrtplib.old/rtprandomurandom.h b/qrtplib.old/rtprandomurandom.h deleted file mode 100644 index 272cc95c2..000000000 --- a/qrtplib.old/rtprandomurandom.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtprandomurandom.h - */ - -#ifndef RTPRANDOMURANDOM_H - -#define RTPRANDOMURANDOM_H - -//#include "rtpconfig.h" -#include -#include "../qrtplib.old/rtprandom.h" - -namespace qrtplib -{ - -/** A random number generator which uses bytes delivered by the /dev/urandom device. */ -class RTPRandomURandom : public RTPRandom -{ -public: - RTPRandomURandom(); - ~RTPRandomURandom(); - - /** Initialize the random number generator. */ - int Init(); - - uint8_t GetRandom8(); - uint16_t GetRandom16(); - uint32_t GetRandom32(); - double GetRandomDouble(); -private: - FILE *device; -}; - -} // end namespace - -#endif // RTPRANDOMURANDOM_H diff --git a/qrtplib.old/rtprawpacket.h b/qrtplib.old/rtprawpacket.h deleted file mode 100644 index 0a6ce8bd4..000000000 --- a/qrtplib.old/rtprawpacket.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtprawpacket.h - */ - -#ifndef RTPRAWPACKET_H - -#define RTPRAWPACKET_H - -//#include "rtpconfig.h" -#include -#include -#include "../qrtplib.old/rtptimeutilities.h" - -namespace qrtplib -{ - -/** This class is used by the transmission component to store the incoming RTP and RTCP data in. */ -class RTPRawPacket -{ -public: - /** Creates an instance which stores data from \c data with length \c datalen. - * Creates an instance which stores data from \c data with length \c datalen. Only the pointer - * to the data is stored, no actual copy is made! The address from which this packet originated - * is set to \c address and the time at which the packet was received is set to \c recvtime. - * The flag which indicates whether this data is RTP or RTCP data is set to \c rtp. A memory - * manager can be installed as well. - */ - RTPRawPacket(uint8_t *data, std::size_t datalen, QHostAddress *address, RTPTime &recvtime, bool rtp); - ~RTPRawPacket(); - - /** Returns the pointer to the data which is contained in this packet. */ - uint8_t *GetData() - { - return packetdata; - } - - /** Returns the length of the packet described by this instance. */ - std::size_t GetDataLength() const - { - return packetdatalength; - } - - /** Returns the time at which this packet was received. */ - RTPTime GetReceiveTime() const - { - return receivetime; - } - - /** Returns the address stored in this packet. */ - const QHostAddress *GetSenderAddress() const - { - return senderaddress; - } - - /** Returns \c true if this data is RTP data, \c false if it is RTCP data. */ - bool IsRTP() const - { - return isrtp; - } - - /** Sets the pointer to the data stored in this packet to zero. - * Sets the pointer to the data stored in this packet to zero. This will prevent - * a \c delete call for the actual data when the destructor of RTPRawPacket is called. - * This function is used by the RTPPacket and RTCPCompoundPacket classes to obtain - * the packet data (without having to copy it) and to make sure the data isn't deleted - * when the destructor of RTPRawPacket is called. - */ - void ZeroData() - { - packetdata = 0; - packetdatalength = 0; - } - - /** Allocates a number of bytes for RTP or RTCP data using the memory manager that - * was used for this raw packet instance, can be useful if the RTPRawPacket::SetData - * function will be used. */ - uint8_t *AllocateBytes(bool isrtp, int recvlen) const; - - /** Deallocates the previously stored data and replaces it with the data that's - * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ - void SetData(uint8_t *data, std::size_t datalen); - - /** Deallocates the currently stored RTPAddress instance and replaces it - * with the one that's specified (you probably don't need this function). */ - void SetSenderAddress(QHostAddress *address); -private: - void DeleteData(); - - uint8_t *packetdata; - std::size_t packetdatalength; - RTPTime receivetime; - QHostAddress *senderaddress; - bool isrtp; -}; - -inline RTPRawPacket::RTPRawPacket( - uint8_t *data, - std::size_t datalen, - QHostAddress *address, - RTPTime &recvtime, - bool rtp): receivetime(recvtime) -{ - packetdata = data; - packetdatalength = datalen; - senderaddress = address; - isrtp = rtp; -} - -inline RTPRawPacket::~RTPRawPacket() -{ - DeleteData(); -} - -inline void RTPRawPacket::DeleteData() -{ - if (packetdata) { - delete[] packetdata; - } - - packetdata = 0; -} - -inline uint8_t *RTPRawPacket::AllocateBytes(bool isrtp __attribute__((unused)), int recvlen) const -{ - return new uint8_t[recvlen]; -} - -inline void RTPRawPacket::SetData(uint8_t *data, std::size_t datalen) -{ - if (packetdata) { - delete[] packetdata; - } - - packetdata = data; - packetdatalength = datalen; -} - -inline void RTPRawPacket::SetSenderAddress(QHostAddress *address) -{ - senderaddress = address; -} - -} // end namespace - -#endif // RTPRAWPACKET_H - diff --git a/qrtplib.old/rtpstructs.h b/qrtplib.old/rtpstructs.h deleted file mode 100644 index 859674fe7..000000000 --- a/qrtplib.old/rtpstructs.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtpstructs.h - */ - -#ifndef RTPSTRUCTS_H - -#define RTPSTRUCTS_H - -//#include "rtpconfig.h" -//#include "rtptypes.h" - -namespace qrtplib -{ - -struct RTPHeader -{ -#ifdef RTP_BIG_ENDIAN - uint8_t version:2; - uint8_t padding:1; - uint8_t extension:1; - uint8_t csrccount:4; - - uint8_t marker:1; - uint8_t payloadtype:7; -#else // little endian - uint8_t csrccount:4; - uint8_t extension:1; - uint8_t padding:1; - uint8_t version:2; - - uint8_t payloadtype:7; - uint8_t marker:1; -#endif // RTP_BIG_ENDIAN - - uint16_t sequencenumber; - uint32_t timestamp; - uint32_t ssrc; -}; - -struct RTPExtensionHeader -{ - uint16_t extid; - uint16_t length; -}; - -struct RTPSourceIdentifier -{ - uint32_t ssrc; -}; - -struct RTCPCommonHeader -{ -#ifdef RTP_BIG_ENDIAN - uint8_t version:2; - uint8_t padding:1; - uint8_t count:5; -#else // little endian - uint8_t count:5; - uint8_t padding:1; - uint8_t version:2; -#endif // RTP_BIG_ENDIAN - - uint8_t packettype; - uint16_t length; -}; - -struct RTCPSenderReport -{ - uint32_t ntptime_msw; - uint32_t ntptime_lsw; - uint32_t rtptimestamp; - uint32_t packetcount; - uint32_t octetcount; -}; - -struct RTCPReceiverReport -{ - uint32_t ssrc; // Identifies about which SSRC's data this report is... - uint8_t fractionlost; - uint8_t packetslost[3]; - uint32_t exthighseqnr; - uint32_t jitter; - uint32_t lsr; - uint32_t dlsr; -}; - -struct RTCPSDESHeader -{ - uint8_t sdesid; - uint8_t length; -}; - -} // end namespace - -#endif // RTPSTRUCTS - diff --git a/qrtplib.old/rtptimeutilities.cpp b/qrtplib.old/rtptimeutilities.cpp deleted file mode 100644 index d3df02cc7..000000000 --- a/qrtplib.old/rtptimeutilities.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -//#include "rtpconfig.h" -#include "../qrtplib.old/rtptimeutilities.h" - -namespace qrtplib -{ - -RTPTimeInitializerObject::RTPTimeInitializerObject() -{ - dummy = -1; -} - -} // end namespace diff --git a/qrtplib.old/rtptimeutilities.h b/qrtplib.old/rtptimeutilities.h deleted file mode 100644 index ffdd93fb7..000000000 --- a/qrtplib.old/rtptimeutilities.h +++ /dev/null @@ -1,323 +0,0 @@ -/* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - -*/ - -/** - * \file rtptimeutilities.h - */ - -#ifndef RTPTIMEUTILITIES_H - -#define RTPTIMEUTILITIES_H - -//#include "rtpconfig.h" -//#include "rtptypes.h" -#include -#include -#include -#include - -#define RTP_NTPTIMEOFFSET 2208988800UL -#define C1000000 1000000ULL -#define CEPOCH 11644473600000000ULL - -namespace qrtplib -{ - -/** - * This is a simple wrapper for the most significant word (MSW) and least - * significant word (LSW) of an NTP timestamp. - */ -class RTPNTPTime -{ -public: - /** This constructor creates and instance with MSW \c m and LSW \c l. */ - RTPNTPTime(uint32_t m, uint32_t l) { msw = m ; lsw = l; } - - /** Returns the most significant word. */ - uint32_t GetMSW() const { return msw; } - - /** Returns the least significant word. */ - uint32_t GetLSW() const { return lsw; } -private: - uint32_t msw,lsw; -}; - -/** This class is used to specify wallclock time, delay intervals etc. - * This class is used to specify wallclock time, delay intervals etc. - * It stores a number of seconds and a number of microseconds. - */ -class RTPTime -{ -public: - /** Returns an RTPTime instance representing the current wallclock time. - * Returns an RTPTime instance representing the current wallclock time. This is expressed - * as a number of seconds since 00:00:00 UTC, January 1, 1970. - */ - static RTPTime CurrentTime(); - - /** This function waits the amount of time specified in \c delay. */ - static void Wait(const RTPTime &delay); - - /** Creates an RTPTime instance representing \c t, which is expressed in units of seconds. */ - RTPTime(double t); - - /** Creates an instance that corresponds to \c ntptime. - * Creates an instance that corresponds to \c ntptime. If - * the conversion cannot be made, both the seconds and the - * microseconds are set to zero. - */ - RTPTime(RTPNTPTime ntptime); - - /** Creates an instance corresponding to \c seconds and \c microseconds. */ - RTPTime(int64_t seconds, uint32_t microseconds); - - /** Returns the number of seconds stored in this instance. */ - int64_t GetSeconds() const; - - /** Returns the number of microseconds stored in this instance. */ - uint32_t GetMicroSeconds() const; - - /** Returns the time stored in this instance, expressed in units of seconds. */ - double GetDouble() const { return m_t; } - - /** Returns the NTP time corresponding to the time stored in this instance. */ - RTPNTPTime GetNTPTime() const; - - RTPTime &operator-=(const RTPTime &t); - RTPTime &operator+=(const RTPTime &t); - bool operator<(const RTPTime &t) const; - bool operator>(const RTPTime &t) const; - bool operator<=(const RTPTime &t) const; - bool operator>=(const RTPTime &t) const; - - bool IsZero() const { return m_t == 0; } -private: - double m_t; -}; - -inline RTPTime::RTPTime(double t) -{ - m_t = t; -} - -inline RTPTime::RTPTime(int64_t seconds, uint32_t microseconds) -{ - if (seconds >= 0) - { - m_t = (double)seconds + 1e-6*(double)microseconds; - } - else - { - int64_t possec = -seconds; - - m_t = (double)possec + 1e-6*(double)microseconds; - m_t = -m_t; - } -} - -inline RTPTime::RTPTime(RTPNTPTime ntptime) -{ - if (ntptime.GetMSW() < RTP_NTPTIMEOFFSET) - { - m_t = 0; - } - else - { - uint32_t sec = ntptime.GetMSW() - RTP_NTPTIMEOFFSET; - - double x = (double)ntptime.GetLSW(); - x /= (65536.0*65536.0); - x *= 1000000.0; - uint32_t microsec = (uint32_t)x; - - m_t = (double)sec + 1e-6*(double)microsec; - } -} - -inline int64_t RTPTime::GetSeconds() const -{ - return (int64_t)m_t; -} - -inline uint32_t RTPTime::GetMicroSeconds() const -{ - uint32_t microsec; - - if (m_t >= 0) - { - int64_t sec = (int64_t)m_t; - microsec = (uint32_t)(1e6*(m_t - (double)sec) + 0.5); - } - else // m_t < 0 - { - int64_t sec = (int64_t)(-m_t); - microsec = (uint32_t)(1e6*((-m_t) - (double)sec) + 0.5); - } - - if (microsec >= 1000000) - return 999999; - // Unsigned, it can never be less than 0 - // if (microsec < 0) - // return 0; - return microsec; -} - -#ifdef RTP_HAVE_CLOCK_GETTIME -inline double RTPTime_timespecToDouble(struct timespec &ts) -{ - return (double)ts.tv_sec + 1e-9*(double)ts.tv_nsec; -} - -inline RTPTime RTPTime::CurrentTime() -{ - static bool s_initialized = false; - static double s_startOffet = 0; - - if (!s_initialized) - { - s_initialized = true; - - // Get the corresponding times in system time and monotonic time - struct timespec tpSys, tpMono; - - clock_gettime(CLOCK_REALTIME, &tpSys); - clock_gettime(CLOCK_MONOTONIC, &tpMono); - - double tSys = RTPTime_timespecToDouble(tpSys); - double tMono = RTPTime_timespecToDouble(tpMono); - - s_startOffet = tSys - tMono; - return tSys; - } - - struct timespec tpMono; - clock_gettime(CLOCK_MONOTONIC, &tpMono); - - double tMono0 = RTPTime_timespecToDouble(tpMono); - return tMono0 + s_startOffet; -} - -#else // gettimeofday fallback - -inline RTPTime RTPTime::CurrentTime() -{ - struct timeval tv; - - gettimeofday(&tv,0); - return RTPTime((uint64_t)tv.tv_sec,(uint32_t)tv.tv_usec); -} -#endif // RTP_HAVE_CLOCK_GETTIME - -inline void RTPTime::Wait(const RTPTime &delay) -{ - if (delay.m_t <= 0) - return; - - uint64_t sec = (uint64_t)delay.m_t; - uint64_t nanosec = (uint32_t)(1e9*(delay.m_t-(double)sec)); - - struct timespec req,rem; - int ret; - - req.tv_sec = (time_t)sec; - req.tv_nsec = ((long)nanosec); - do - { - ret = nanosleep(&req,&rem); - req = rem; - } while (ret == -1 && errno == EINTR); -} - -inline RTPTime &RTPTime::operator-=(const RTPTime &t) -{ - m_t -= t.m_t; - return *this; -} - -inline RTPTime &RTPTime::operator+=(const RTPTime &t) -{ - m_t += t.m_t; - return *this; -} - -inline RTPNTPTime RTPTime::GetNTPTime() const -{ - uint32_t sec = (uint32_t)m_t; - uint32_t microsec = (uint32_t)((m_t - (double)sec)*1e6); - - uint32_t msw = sec+RTP_NTPTIMEOFFSET; - uint32_t lsw; - double x; - - x = microsec/1000000.0; - x *= (65536.0*65536.0); - lsw = (uint32_t)x; - - return RTPNTPTime(msw,lsw); -} - -inline bool RTPTime::operator<(const RTPTime &t) const -{ - return m_t < t.m_t; -} - -inline bool RTPTime::operator>(const RTPTime &t) const -{ - return m_t > t.m_t; -} - -inline bool RTPTime::operator<=(const RTPTime &t) const -{ - return m_t <= t.m_t; -} - -inline bool RTPTime::operator>=(const RTPTime &t) const -{ - return m_t >= t.m_t; -} - -class RTPTimeInitializerObject -{ -public: - RTPTimeInitializerObject(); - void Dummy() { dummy++; } -private: - int dummy; -}; - -} // end namespace - - -#endif // RTPTIMEUTILITIES_H - diff --git a/qrtplib/rtpconfig.h b/qrtplib/rtpconfig.h index f0195e60a..faf4e6e2f 100644 --- a/qrtplib/rtpconfig.h +++ b/qrtplib/rtpconfig.h @@ -34,13 +34,6 @@ #define RTPCONFIG_UNIX_H -#ifndef JRTPLIB_UNUSED -/** - * Provide a macro to use for marking method parameters as unused. - */ -#define JRTPLIB_UNUSED(x) (void)(x) -#endif // JRTPLIB_UNUSED - // Don't have // Don't have diff --git a/qrtplib/rtpinternalsourcedata.cpp b/qrtplib/rtpinternalsourcedata.cpp index 075b6bcb1..e13cffa5f 100644 --- a/qrtplib/rtpinternalsourcedata.cpp +++ b/qrtplib/rtpinternalsourcedata.cpp @@ -51,7 +51,7 @@ RTPInternalSourceData::~RTPInternalSourceData() // The following function should delete rtppack if necessary int RTPInternalSourceData::ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, bool *stored, RTPSources *sources) { - bool accept, onprobation, applyprobation; + bool accept; double tsunit; *stored = false; @@ -61,9 +61,7 @@ int RTPInternalSourceData::ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &r else tsunit = timestampunit; - applyprobation = false; - - stats.ProcessPacket(rtppack, receivetime, tsunit, ownssrc, &accept, applyprobation, &onprobation); + stats.ProcessPacket(rtppack, receivetime, tsunit, ownssrc, &accept); if (!accept) return 0; diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h index c70835c57..3db022298 100644 --- a/qrtplib/rtprawpacket.h +++ b/qrtplib/rtprawpacket.h @@ -106,7 +106,7 @@ public: /** Allocates a number of bytes for RTP or RTCP data using the memory manager that * was used for this raw packet instance, can be useful if the RTPRawPacket::SetData * function will be used. */ - uint8_t *AllocateBytes(bool isrtp, int recvlen) const; + uint8_t *AllocateBytes(int recvlen) const; /** Deallocates the previously stored data and replaces it with the data that's * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ @@ -150,9 +150,8 @@ inline void RTPRawPacket::DeleteData() senderaddress = 0; } -inline uint8_t *RTPRawPacket::AllocateBytes(bool isrtp, int recvlen) const +inline uint8_t *RTPRawPacket::AllocateBytes(int recvlen) const { - JRTPLIB_UNUSED(isrtp); // possibly unused return new uint8_t[recvlen]; } diff --git a/qrtplib/rtpsessionparams.cpp b/qrtplib/rtpsessionparams.cpp index 78f2bfea5..40b611899 100644 --- a/qrtplib/rtpsessionparams.cpp +++ b/qrtplib/rtpsessionparams.cpp @@ -67,15 +67,13 @@ RTPSessionParams::RTPSessionParams() : predefinedssrc = 0; } -int RTPSessionParams::SetUsePollThread(bool usethread) +int RTPSessionParams::SetUsePollThread(bool usethread __attribute__((unused))) { - JRTPLIB_UNUSED(usethread); return ERR_RTP_NOTHREADSUPPORT; } -int RTPSessionParams::SetNeedThreadSafety(bool s) +int RTPSessionParams::SetNeedThreadSafety(bool __attribute__((unused))) { - JRTPLIB_UNUSED(s); return ERR_RTP_NOTHREADSUPPORT; } diff --git a/qrtplib/rtpsessionsources.h b/qrtplib/rtpsessionsources.h index 753fccbd4..2163e4486 100644 --- a/qrtplib/rtpsessionsources.h +++ b/qrtplib/rtpsessionsources.h @@ -50,7 +50,7 @@ class RTPSessionSources: public RTPSources { public: RTPSessionSources(RTPSession &sess) : - RTPSources(RTPSources::ProbationStore), rtpsession(sess) + rtpsession(sess) { owncollision = false; } diff --git a/qrtplib/rtpsourcedata.cpp b/qrtplib/rtpsourcedata.cpp index 185b7d8b1..398bda7fe 100644 --- a/qrtplib/rtpsourcedata.cpp +++ b/qrtplib/rtpsourcedata.cpp @@ -64,15 +64,16 @@ namespace qrtplib { -void RTPSourceStats::ProcessPacket(RTPPacket *pack, const RTPTime &receivetime, double tsunit, bool ownpacket, bool *accept, bool applyprobation, bool *onprobation) +void RTPSourceStats::ProcessPacket( + RTPPacket *pack, + const RTPTime &receivetime, + double tsunit, + bool ownpacket, + bool *accept) { - JRTPLIB_UNUSED(applyprobation); // possibly unused - // Note that the sequence number in the RTP packet is still just the // 16 bit number contained in the RTP header - *onprobation = false; - if (!sentdata) // no valid packets received yet { ACCEPTPACKETCODE diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h index a5861f921..b87efc095 100644 --- a/qrtplib/rtpsourcedata.h +++ b/qrtplib/rtpsourcedata.h @@ -178,7 +178,12 @@ class RTPSourceStats { public: RTPSourceStats(); - void ProcessPacket(RTPPacket *pack, const RTPTime &receivetime, double tsunit, bool ownpacket, bool *accept, bool applyprobation, bool *onprobation); + void ProcessPacket( + RTPPacket *pack, + const RTPTime &receivetime, + double tsunit, + bool ownpacket, + bool *accept); bool HasSentData() const { diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp index d4ebea9d8..f44e96a9c 100644 --- a/qrtplib/rtpsources.cpp +++ b/qrtplib/rtpsources.cpp @@ -48,10 +48,8 @@ namespace qrtplib { -RTPSources::RTPSources(ProbationType probtype) +RTPSources::RTPSources() { - JRTPLIB_UNUSED(probtype); // possibly unused - totalcount = 0; sendercount = 0; activecount = 0; diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h index 62c789812..e169277b9 100644 --- a/qrtplib/rtpsources.h +++ b/qrtplib/rtpsources.h @@ -85,7 +85,7 @@ public: }; /** In the constructor you can select the probation type you'd like to use and also a memory manager. */ - RTPSources(ProbationType = ProbationStore); + RTPSources(); virtual ~RTPSources(); /** Clears the source table. */ diff --git a/qrtplib/rtptcptransmitter.cpp b/qrtplib/rtptcptransmitter.cpp index 36796f599..428cdb7ef 100644 --- a/qrtplib/rtptcptransmitter.cpp +++ b/qrtplib/rtptcptransmitter.cpp @@ -81,9 +81,8 @@ int RTPTCPTransmitter::Init(bool tsafe) return 0; } -int RTPTCPTransmitter::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) +int RTPTCPTransmitter::Create(size_t maximumpacketsize __attribute__((unused)), const RTPTransmissionParams *transparams) { - JRTPLIB_UNUSED(maximumpacketsize); const RTPTCPTransmissionParams *params, defaultparams; int status; diff --git a/qrtplib/rtptimeutilities.cpp b/qrtplib/rtptimeutilities.cpp index d664ae30c..c5900efc2 100644 --- a/qrtplib/rtptimeutilities.cpp +++ b/qrtplib/rtptimeutilities.cpp @@ -38,8 +38,6 @@ namespace qrtplib RTPTimeInitializerObject::RTPTimeInitializerObject() { - RTPTime curtime = RTPTime::CurrentTime(); - JRTPLIB_UNUSED(curtime); dummy = -1; } diff --git a/qrtplib/rtpudpv4transmitter.cpp b/qrtplib/rtpudpv4transmitter.cpp index 9e21a79e5..c687f8f93 100644 --- a/qrtplib/rtpudpv4transmitter.cpp +++ b/qrtplib/rtpudpv4transmitter.cpp @@ -1109,14 +1109,13 @@ multicastgroups.GotoFirstElement(); while (multicastgroups.HasCurrentElement()) { uint32_t mcastIP; -int status = 0; +int status __attribute__((unused)) = 0; mcastIP = multicastgroups.GetCurrentElement(); RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); -JRTPLIB_UNUSED(status); multicastgroups.GotoNextElement(); } diff --git a/qrtplib/rtpudpv4transmitternobind.cpp b/qrtplib/rtpudpv4transmitternobind.cpp index 9d9c1cbac..d10149b88 100644 --- a/qrtplib/rtpudpv4transmitternobind.cpp +++ b/qrtplib/rtpudpv4transmitternobind.cpp @@ -1112,14 +1112,13 @@ multicastgroups.GotoFirstElement(); while (multicastgroups.HasCurrentElement()) { uint32_t mcastIP; -int status = 0; +int status __attribute__((unused)) = 0; mcastIP = multicastgroups.GetCurrentElement(); RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); -JRTPLIB_UNUSED(status); multicastgroups.GotoNextElement(); } From 83cf128dc041cf2c54ae14845520953d033cdf0c Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 28 Feb 2018 00:00:17 +0100 Subject: [PATCH 028/956] qrtplib: removed rtpbyteaddress --- qrtplib/CMakeLists.txt | 2 - qrtplib/rtcpcompoundpacketbuilder.h | 2 - qrtplib/rtpaddress.h | 2 - qrtplib/rtpbyteaddress.cpp | 79 ------------------- qrtplib/rtpbyteaddress.h | 115 ---------------------------- 5 files changed, 200 deletions(-) delete mode 100644 qrtplib/rtpbyteaddress.cpp delete mode 100644 qrtplib/rtpbyteaddress.h diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 482bb077e..594272c2d 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -44,7 +44,6 @@ set (qrtplib_HEADERS rtptypes.h rtpudpv4transmitter.h rtpudpv4transmitternobind.h - rtpbyteaddress.h rtpexternaltransmitter.h rtpsocketutil.h rtpabortdescriptors.h @@ -84,7 +83,6 @@ set(qrtplib_SOURCES rtptimeutilities.cpp rtpudpv4transmitter.cpp rtpudpv4transmitternobind.cpp - rtpbyteaddress.cpp rtpexternaltransmitter.cpp rtpabortdescriptors.cpp rtptcpaddress.cpp diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index 7eceadb6f..7430d653e 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -49,8 +49,6 @@ namespace qrtplib { -class RTPMemoryManager; - /** This class can be used to construct an RTCP compound packet. * The RTCPCompoundPacketBuilder class can be used to construct an RTCP compound packet. It inherits the member * functions of RTCPCompoundPacket which can be used to access the information in the compound packet once it has diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h index 9ea607aa9..e9d9d9f9d 100644 --- a/qrtplib/rtpaddress.h +++ b/qrtplib/rtpaddress.h @@ -44,8 +44,6 @@ namespace qrtplib { -class RTPMemoryManager; - /** This class is an abstract class which is used to specify destinations, multicast groups etc. */ class RTPAddress { diff --git a/qrtplib/rtpbyteaddress.cpp b/qrtplib/rtpbyteaddress.cpp deleted file mode 100644 index 12819017a..000000000 --- a/qrtplib/rtpbyteaddress.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtpbyteaddress.h" - -namespace qrtplib -{ - -bool RTPByteAddress::IsSameAddress(const RTPAddress *addr) const -{ - if (addr == 0) - return false; - if (addr->GetAddressType() != ByteAddress) - return false; - - const RTPByteAddress *addr2 = (const RTPByteAddress *) addr; - - if (addr2->addresslength != addresslength) - return false; - if (addresslength == 0 || (memcmp(hostaddress, addr2->hostaddress, addresslength) == 0)) - { - if (port == addr2->port) - return true; - } - return false; -} - -bool RTPByteAddress::IsFromSameHost(const RTPAddress *addr) const -{ - if (addr == 0) - return false; - if (addr->GetAddressType() != ByteAddress) - return false; - - const RTPByteAddress *addr2 = (const RTPByteAddress *) addr; - - if (addr2->addresslength != addresslength) - return false; - if (addresslength == 0 || (memcmp(hostaddress, addr2->hostaddress, addresslength) == 0)) - return true; - return false; -} - -RTPAddress *RTPByteAddress::CreateCopy() const -{ - RTPByteAddress *a = new RTPByteAddress(hostaddress, addresslength, port); - return a; -} - -} // end namespace diff --git a/qrtplib/rtpbyteaddress.h b/qrtplib/rtpbyteaddress.h deleted file mode 100644 index 4e40f4356..000000000 --- a/qrtplib/rtpbyteaddress.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpbyteaddress.h - */ - -#ifndef RTPBYTEADDRESS_H - -#define RTPBYTEADDRESS_H - -#include "rtpconfig.h" -#include "rtpaddress.h" -#include "rtptypes.h" -#include - -#define RTPBYTEADDRESS_MAXLENGTH 128 - -namespace qrtplib -{ - -/** A very general kind of address consisting of a port number and a number of bytes describing the host address. - * A very general kind of address, consisting of a port number and a number of bytes describing the host address. - */ -class RTPByteAddress: public RTPAddress -{ -public: - /** Creates an instance of the class using \c addrlen bytes of \c hostaddress as host identification, - * and using \c port as the port number. */ - RTPByteAddress(const uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH], size_t addrlen, uint16_t port = 0) : - RTPAddress(ByteAddress) - { - if (addrlen > RTPBYTEADDRESS_MAXLENGTH) - addrlen = RTPBYTEADDRESS_MAXLENGTH; - memcpy(RTPByteAddress::hostaddress, hostaddress, addrlen); - RTPByteAddress::addresslength = addrlen; - RTPByteAddress::port = port; - } - - /** Sets the host address to the first \c addrlen bytes of \c hostaddress. */ - void SetHostAddress(const uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH], size_t addrlen) - { - if (addrlen > RTPBYTEADDRESS_MAXLENGTH) - addrlen = RTPBYTEADDRESS_MAXLENGTH; - memcpy(RTPByteAddress::hostaddress, hostaddress, addrlen); - RTPByteAddress::addresslength = addrlen; - } - - /** Sets the port number to \c port. */ - void SetPort(uint16_t port) - { - RTPByteAddress::port = port; - } - - /** Returns a pointer to the stored host address. */ - const uint8_t *GetHostAddress() const - { - return hostaddress; - } - - /** Returns the length in bytes of the stored host address. */ - size_t GetHostAddressLength() const - { - return addresslength; - } - - /** Returns the port number stored in this instance. */ - uint16_t GetPort() const - { - return port; - } - - RTPAddress *CreateCopy() const; - bool IsSameAddress(const RTPAddress *addr) const; - bool IsFromSameHost(const RTPAddress *addr) const; - -private: - uint8_t hostaddress[RTPBYTEADDRESS_MAXLENGTH]; - size_t addresslength; - uint16_t port; -}; - -} // end namespace - -#endif // RTPBYTEADDRESS_H - From 493b37c37c3340d9d4683b66002834ad6e969cbf Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 28 Feb 2018 00:19:19 +0100 Subject: [PATCH 029/956] qrtplib: removed mutex macros --- qrtplib/rtpexternaltransmitter.cpp | 676 ++++----- qrtplib/rtptcptransmitter.cpp | 950 ++++++------ qrtplib/rtpudpv4transmitter.cpp | 1955 ++++++++++++------------ qrtplib/rtpudpv4transmitternobind.cpp | 1958 ++++++++++++------------- 4 files changed, 2626 insertions(+), 2913 deletions(-) diff --git a/qrtplib/rtpexternaltransmitter.cpp b/qrtplib/rtpexternaltransmitter.cpp index bfb578915..1c909f4ad 100644 --- a/qrtplib/rtpexternaltransmitter.cpp +++ b/qrtplib/rtpexternaltransmitter.cpp @@ -42,11 +42,6 @@ #include -#define MAINMUTEX_LOCK -#define MAINMUTEX_UNLOCK -#define WAITMUTEX_LOCK -#define WAITMUTEX_UNLOCK - namespace qrtplib { @@ -82,11 +77,8 @@ int RTPExternalTransmitter::Create(size_t maximumpacketsize, const RTPTransmissi if (!init) return ERR_RTP_EXTERNALTRANS_NOTINIT; - MAINMUTEX_LOCK - if (created) { - MAINMUTEX_UNLOCK return ERR_RTP_EXTERNALTRANS_ALREADYCREATED; } @@ -94,12 +86,10 @@ int RTPExternalTransmitter::Create(size_t maximumpacketsize, const RTPTransmissi if (transparams == 0) { - MAINMUTEX_UNLOCK return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; } if (transparams->GetTransmissionProtocol() != RTPTransmitter::ExternalProto) { - MAINMUTEX_UNLOCK return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; } @@ -107,7 +97,6 @@ int RTPExternalTransmitter::Create(size_t maximumpacketsize, const RTPTransmissi if ((status = m_abortDesc.Init()) < 0) { - MAINMUTEX_UNLOCK return status; } m_abortCount = 0; @@ -121,7 +110,6 @@ int RTPExternalTransmitter::Create(size_t maximumpacketsize, const RTPTransmissi waitingfordata = false; created = true; - MAINMUTEX_UNLOCK return 0; } @@ -130,10 +118,8 @@ void RTPExternalTransmitter::Destroy() if (!init) return; - MAINMUTEX_LOCK if (!created) { - MAINMUTEX_UNLOCK; return; } @@ -152,262 +138,223 @@ void RTPExternalTransmitter::Destroy() m_abortDesc.SendAbortSignal(); m_abortCount++; m_abortDesc.Destroy(); - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK // to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK -} -else - m_abortDesc.Destroy(); + // to make sure that the WaitForIncomingData function ended + + } + else + m_abortDesc.Destroy(); -MAINMUTEX_UNLOCK } RTPTransmissionInfo *RTPExternalTransmitter::GetTransmissionInfo() { -if (!init) -return 0; + if (!init) + return 0; -MAINMUTEX_LOCK -RTPTransmissionInfo *tinf = new RTPExternalTransmissionInfo(&packetinjector); -MAINMUTEX_UNLOCK -return tinf; + RTPTransmissionInfo *tinf = new RTPExternalTransmissionInfo(&packetinjector); + return tinf; } void RTPExternalTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) { -if (!init) -return; + if (!init) + return; -delete i; + delete i; } int RTPExternalTransmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) { -if (!init) -return ERR_RTP_EXTERNALTRANS_NOTINIT; + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOTCREATED; -} + if (!created) + { + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } -if (localhostname == 0) -{ + if (localhostname == 0) + { // We'll just use 'gethostname' for simplicity -char name[1024]; + char name[1024]; -if (gethostname(name, 1023) != 0) - strcpy(name, "localhost"); // failsafe -else - name[1023] = 0; // ensure null-termination + if (gethostname(name, 1023) != 0) + strcpy(name, "localhost"); // failsafe + else + name[1023] = 0; // ensure null-termination -localhostnamelength = strlen(name); -localhostname = new uint8_t[localhostnamelength + 1]; + localhostnamelength = strlen(name); + localhostname = new uint8_t[localhostnamelength + 1]; -memcpy(localhostname, name, localhostnamelength); -localhostname[localhostnamelength] = 0; -} + memcpy(localhostname, name, localhostnamelength); + localhostname[localhostnamelength] = 0; + } -if ((*bufferlength) < localhostnamelength) -{ -*bufferlength = localhostnamelength; // tell the application the required size of the buffer -MAINMUTEX_UNLOCK -return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; -} + if ((*bufferlength) < localhostnamelength) + { + *bufferlength = localhostnamelength; // tell the application the required size of the buffer + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; + } -memcpy(buffer, localhostname, localhostnamelength); -*bufferlength = localhostnamelength; + memcpy(buffer, localhostname, localhostnamelength); + *bufferlength = localhostnamelength; -MAINMUTEX_UNLOCK -return 0; + return 0; } bool RTPExternalTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) { -MAINMUTEX_LOCK -bool value = false; -if (sender) -value = sender->ComesFromThisSender(addr); -MAINMUTEX_UNLOCK -return value; + + bool value = false; + if (sender) + value = sender->ComesFromThisSender(addr); + return value; } int RTPExternalTransmitter::Poll() { -return 0; + return 0; } int RTPExternalTransmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) { -if (!init) -return ERR_RTP_EXTERNALTRANS_NOTINIT; + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (waitingfordata) + { + return ERR_RTP_EXTERNALTRANS_ALREADYWAITING; + } -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOTCREATED; -} -if (waitingfordata) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_ALREADYWAITING; -} + waitingfordata = true; -waitingfordata = true; + if (!rawpacketlist.empty()) + { + if (dataavailable != 0) + *dataavailable = true; + waitingfordata = false; + return 0; + } -if (!rawpacketlist.empty()) -{ -if (dataavailable != 0) - *dataavailable = true; -waitingfordata = false; -MAINMUTEX_UNLOCK -return 0; -} + int8_t isset = 0; + SocketType abortSock = m_abortDesc.GetAbortSocket(); + int status = RTPSelect(&abortSock, &isset, 1, delay); + if (status < 0) + { -WAITMUTEX_LOCK -MAINMUTEX_UNLOCK + waitingfordata = false; -int8_t isset = 0; -SocketType abortSock = m_abortDesc.GetAbortSocket(); -int status = RTPSelect(&abortSock, &isset, 1, delay); -if (status < 0) -{ -MAINMUTEX_LOCK -waitingfordata = false; -MAINMUTEX_UNLOCK -WAITMUTEX_UNLOCK -return status; -} + return status; + } -MAINMUTEX_LOCK -waitingfordata = false; -if (!created) // destroy called -{ -MAINMUTEX_UNLOCK; -WAITMUTEX_UNLOCK -return 0; -} + waitingfordata = false; + if (!created) // destroy called + { - // if aborted, read from abort buffer -if (isset) -{ -m_abortDesc.ClearAbortSignal(); -m_abortCount = 0; -} + return 0; + } -if (dataavailable != 0) -{ -if (rawpacketlist.empty()) - *dataavailable = false; -else - *dataavailable = true; -} + // if aborted, read from abort buffer + if (isset) + { + m_abortDesc.ClearAbortSignal(); + m_abortCount = 0; + } -MAINMUTEX_UNLOCK -WAITMUTEX_UNLOCK -return 0; + if (dataavailable != 0) + { + if (rawpacketlist.empty()) + *dataavailable = false; + else + *dataavailable = true; + } + + return 0; } int RTPExternalTransmitter::AbortWait() { -if (!init) -return ERR_RTP_EXTERNALTRANS_NOTINIT; + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOTCREATED; -} -if (!waitingfordata) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOTWAITING; -} + if (!created) + { + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (!waitingfordata) + { + return ERR_RTP_EXTERNALTRANS_NOTWAITING; + } -m_abortDesc.SendAbortSignal(); -m_abortCount++; + m_abortDesc.SendAbortSignal(); + m_abortCount++; -MAINMUTEX_UNLOCK -return 0; + return 0; } int RTPExternalTransmitter::SendRTPData(const void *data, size_t len) { -if (!init) -return ERR_RTP_EXTERNALTRANS_NOTINIT; + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (len > maxpacksize) + { + return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; + } -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOTCREATED; -} -if (len > maxpacksize) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; -} + if (!sender) + { + return ERR_RTP_EXTERNALTRANS_NOSENDER; + } -if (!sender) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOSENDER; -} + if (!sender->SendRTP(data, len)) + return ERR_RTP_EXTERNALTRANS_SENDERROR; -MAINMUTEX_UNLOCK - -if (!sender->SendRTP(data, len)) -return ERR_RTP_EXTERNALTRANS_SENDERROR; - -return 0; + return 0; } int RTPExternalTransmitter::SendRTCPData(const void *data, size_t len) { -if (!init) -return ERR_RTP_EXTERNALTRANS_NOTINIT; + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (len > maxpacksize) + { + return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; + } -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOTCREATED; -} -if (len > maxpacksize) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; -} + if (!sender) + { + return ERR_RTP_EXTERNALTRANS_NOSENDER; + } -if (!sender) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOSENDER; -} -MAINMUTEX_UNLOCK + if (!sender->SendRTCP(data, len)) + return ERR_RTP_EXTERNALTRANS_SENDERROR; -if (!sender->SendRTCP(data, len)) -return ERR_RTP_EXTERNALTRANS_SENDERROR; - -return 0; + return 0; } int RTPExternalTransmitter::AddDestination(const RTPAddress &) { -return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; + return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; } int RTPExternalTransmitter::DeleteDestination(const RTPAddress &) { -return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; + return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; } void RTPExternalTransmitter::ClearDestinations() @@ -416,17 +363,17 @@ void RTPExternalTransmitter::ClearDestinations() bool RTPExternalTransmitter::SupportsMulticasting() { -return false; + return false; } int RTPExternalTransmitter::JoinMulticastGroup(const RTPAddress &) { -return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; + return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; } int RTPExternalTransmitter::LeaveMulticastGroup(const RTPAddress &) { -return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; + return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; } void RTPExternalTransmitter::LeaveAllMulticastGroups() @@ -435,32 +382,28 @@ void RTPExternalTransmitter::LeaveAllMulticastGroups() int RTPExternalTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) { -if (!init) -return ERR_RTP_EXTERNALTRANS_NOTINIT; + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOTCREATED; -} -if (m != RTPTransmitter::AcceptAll) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE; -} -MAINMUTEX_UNLOCK -return 0; + if (!created) + { + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + if (m != RTPTransmitter::AcceptAll) + { + return ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE; + } + return 0; } int RTPExternalTransmitter::AddToIgnoreList(const RTPAddress &) { -return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; + return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; } int RTPExternalTransmitter::DeleteFromIgnoreList(const RTPAddress &) { -return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; + return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; } void RTPExternalTransmitter::ClearIgnoreList() @@ -469,12 +412,12 @@ void RTPExternalTransmitter::ClearIgnoreList() int RTPExternalTransmitter::AddToAcceptList(const RTPAddress &) { -return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; + return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; } int RTPExternalTransmitter::DeleteFromAcceptList(const RTPAddress &) { -return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; + return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; } void RTPExternalTransmitter::ClearAcceptList() @@ -483,228 +426,207 @@ void RTPExternalTransmitter::ClearAcceptList() int RTPExternalTransmitter::SetMaximumPacketSize(size_t s) { -if (!init) -return ERR_RTP_EXTERNALTRANS_NOTINIT; + if (!init) + return ERR_RTP_EXTERNALTRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_EXTERNALTRANS_NOTCREATED; -} -maxpacksize = s; -MAINMUTEX_UNLOCK -return 0; + if (!created) + { + return ERR_RTP_EXTERNALTRANS_NOTCREATED; + } + maxpacksize = s; + return 0; } bool RTPExternalTransmitter::NewDataAvailable() { -if (!init) -return false; + if (!init) + return false; -MAINMUTEX_LOCK + bool v; -bool v; + if (!created) + v = false; + else + { + if (rawpacketlist.empty()) + v = false; + else + v = true; + } -if (!created) -v = false; -else -{ -if (rawpacketlist.empty()) - v = false; -else - v = true; -} - -MAINMUTEX_UNLOCK -return v; + return v; } RTPRawPacket *RTPExternalTransmitter::GetNextPacket() { -if (!init) -return 0; + if (!init) + return 0; -MAINMUTEX_LOCK + RTPRawPacket *p; -RTPRawPacket *p; + if (!created) + { + return 0; + } + if (rawpacketlist.empty()) + { + return 0; + } -if (!created) -{ -MAINMUTEX_UNLOCK -return 0; -} -if (rawpacketlist.empty()) -{ -MAINMUTEX_UNLOCK -return 0; -} + p = *(rawpacketlist.begin()); + rawpacketlist.pop_front(); -p = *(rawpacketlist.begin()); -rawpacketlist.pop_front(); - -MAINMUTEX_UNLOCK -return p; + return p; } // Here the private functions start... void RTPExternalTransmitter::FlushPackets() { -std::list::const_iterator it; + std::list::const_iterator it; -for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) -delete *it; -rawpacketlist.clear(); + for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) + delete *it; + rawpacketlist.clear(); } void RTPExternalTransmitter::InjectRTP(const void *data, size_t len, const RTPAddress &a) { -if (!init) -return; + if (!init) + return; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return; -} + if (!created) + { + return; + } -RTPAddress *addr = a.CreateCopy(); -if (addr == 0) -return; + RTPAddress *addr = a.CreateCopy(); + if (addr == 0) + return; -uint8_t *datacopy; + uint8_t *datacopy; -datacopy = new uint8_t[len]; -if (datacopy == 0) -{ -delete addr; -return; -} -memcpy(datacopy, data, len); + datacopy = new uint8_t[len]; + if (datacopy == 0) + { + delete addr; + return; + } + memcpy(datacopy, data, len); -RTPTime curtime = RTPTime::CurrentTime(); -RTPRawPacket *pack; + RTPTime curtime = RTPTime::CurrentTime(); + RTPRawPacket *pack; -pack = new RTPRawPacket(datacopy, len, addr, curtime, true); -if (pack == 0) -{ -delete addr; -delete[] localhostname; -return; -} -rawpacketlist.push_back(pack); + pack = new RTPRawPacket(datacopy, len, addr, curtime, true); + if (pack == 0) + { + delete addr; + delete[] localhostname; + return; + } + rawpacketlist.push_back(pack); -if (m_abortCount == 0) -{ -m_abortDesc.SendAbortSignal(); -m_abortCount++; -} + if (m_abortCount == 0) + { + m_abortDesc.SendAbortSignal(); + m_abortCount++; + } -MAINMUTEX_UNLOCK } void RTPExternalTransmitter::InjectRTCP(const void *data, size_t len, const RTPAddress &a) { -if (!init) -return; + if (!init) + return; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return; -} + if (!created) + { + return; + } -RTPAddress *addr = a.CreateCopy(); -if (addr == 0) -return; + RTPAddress *addr = a.CreateCopy(); + if (addr == 0) + return; -uint8_t *datacopy; + uint8_t *datacopy; -datacopy = new uint8_t[len]; -if (datacopy == 0) -{ -delete addr; -return; -} -memcpy(datacopy, data, len); + datacopy = new uint8_t[len]; + if (datacopy == 0) + { + delete addr; + return; + } + memcpy(datacopy, data, len); -RTPTime curtime = RTPTime::CurrentTime(); -RTPRawPacket *pack; + RTPTime curtime = RTPTime::CurrentTime(); + RTPRawPacket *pack; -pack = new RTPRawPacket(datacopy, len, addr, curtime, false); -if (pack == 0) -{ -delete addr; -delete[] localhostname; -return; -} -rawpacketlist.push_back(pack); + pack = new RTPRawPacket(datacopy, len, addr, curtime, false); + if (pack == 0) + { + delete addr; + delete[] localhostname; + return; + } + rawpacketlist.push_back(pack); -if (m_abortCount == 0) -{ -m_abortDesc.SendAbortSignal(); -m_abortCount++; -} + if (m_abortCount == 0) + { + m_abortDesc.SendAbortSignal(); + m_abortCount++; + } -MAINMUTEX_UNLOCK } void RTPExternalTransmitter::InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a) { -if (!init) -return; + if (!init) + return; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return; -} + if (!created) + { + return; + } -RTPAddress *addr = a.CreateCopy(); -if (addr == 0) -return; + RTPAddress *addr = a.CreateCopy(); + if (addr == 0) + return; -uint8_t *datacopy; -bool rtp = true; + uint8_t *datacopy; + bool rtp = true; -if (len >= 2) -{ -const uint8_t *pData = (const uint8_t *) data; -if (pData[1] >= 200 && pData[1] <= 204) -rtp = false; -} + if (len >= 2) + { + const uint8_t *pData = (const uint8_t *) data; + if (pData[1] >= 200 && pData[1] <= 204) + rtp = false; + } -datacopy = new uint8_t[len]; -if (datacopy == 0) -{ -delete addr; -return; -} -memcpy(datacopy, data, len); + datacopy = new uint8_t[len]; + if (datacopy == 0) + { + delete addr; + return; + } + memcpy(datacopy, data, len); -RTPTime curtime = RTPTime::CurrentTime(); -RTPRawPacket *pack; + RTPTime curtime = RTPTime::CurrentTime(); + RTPRawPacket *pack; -pack = new RTPRawPacket(datacopy, len, addr, curtime, rtp); -if (pack == 0) -{ -delete addr; -delete[] localhostname; -return; -} -rawpacketlist.push_back(pack); + pack = new RTPRawPacket(datacopy, len, addr, curtime, rtp); + if (pack == 0) + { + delete addr; + delete[] localhostname; + return; + } + rawpacketlist.push_back(pack); -if (m_abortCount == 0) -{ -m_abortDesc.SendAbortSignal(); -m_abortCount++; -} - -MAINMUTEX_UNLOCK + if (m_abortCount == 0) + { + m_abortDesc.SendAbortSignal(); + m_abortCount++; + } } diff --git a/qrtplib/rtptcptransmitter.cpp b/qrtplib/rtptcptransmitter.cpp index 428cdb7ef..ce8ce0dde 100644 --- a/qrtplib/rtptcptransmitter.cpp +++ b/qrtplib/rtptcptransmitter.cpp @@ -49,11 +49,6 @@ using namespace std; #define RTPTCPTRANS_MAXPACKSIZE 65535 -#define MAINMUTEX_LOCK -#define MAINMUTEX_UNLOCK -#define WAITMUTEX_LOCK -#define WAITMUTEX_UNLOCK - namespace qrtplib { @@ -89,11 +84,8 @@ int RTPTCPTransmitter::Create(size_t maximumpacketsize __attribute__((unused)), if (!m_init) return ERR_RTP_TCPTRANS_NOTINIT; - MAINMUTEX_LOCK - if (m_created) { - MAINMUTEX_UNLOCK return ERR_RTP_TCPTRANS_ALREADYCREATED; } @@ -105,7 +97,6 @@ int RTPTCPTransmitter::Create(size_t maximumpacketsize __attribute__((unused)), { if (transparams->GetTransmissionProtocol() != RTPTransmitter::TCPProto) { - MAINMUTEX_UNLOCK return ERR_RTP_TCPTRANS_ILLEGALPARAMETERS; } params = static_cast(transparams); @@ -115,7 +106,6 @@ int RTPTCPTransmitter::Create(size_t maximumpacketsize __attribute__((unused)), { if ((status = m_abortDesc.Init()) < 0) { - MAINMUTEX_UNLOCK return status; } m_pAbortDesc = &m_abortDesc; @@ -125,14 +115,12 @@ int RTPTCPTransmitter::Create(size_t maximumpacketsize __attribute__((unused)), m_pAbortDesc = params->GetCreatedAbortDescriptors(); if (!m_pAbortDesc->IsInitialized()) { - MAINMUTEX_UNLOCK return ERR_RTP_ABORTDESC_NOTINIT; } } m_waitingForData = false; m_created = true; - MAINMUTEX_UNLOCK return 0; } @@ -141,10 +129,8 @@ void RTPTCPTransmitter::Destroy() if (!m_init) return; - MAINMUTEX_LOCK if (!m_created) { - MAINMUTEX_UNLOCK; return; } @@ -156,387 +142,346 @@ void RTPTCPTransmitter::Destroy() { m_pAbortDesc->SendAbortSignal(); m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK// to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK -} -else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized + // to make sure that the WaitForIncomingData function ended + + } + else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized -MAINMUTEX_UNLOCK } RTPTransmissionInfo *RTPTCPTransmitter::GetTransmissionInfo() { -if (!m_init) -return 0; + if (!m_init) + return 0; -MAINMUTEX_LOCK -RTPTransmissionInfo *tinf = new RTPTCPTransmissionInfo(); -MAINMUTEX_UNLOCK -return tinf; + RTPTransmissionInfo *tinf = new RTPTCPTransmissionInfo(); + return tinf; } void RTPTCPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) { -if (!m_init) -return; + if (!m_init) + return; -delete i; + delete i; } int RTPTCPTransmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) { -if (!m_init) -return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; -MAINMUTEX_LOCK -if (!m_created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTCREATED; -} + if (!m_created) + { + return ERR_RTP_TCPTRANS_NOTCREATED; + } -if (m_localHostname.size() == 0) -{ + if (m_localHostname.size() == 0) + { // // TODO // TODO // TODO // TODO // -m_localHostname.resize(9); -memcpy(&m_localHostname[0], "localhost", m_localHostname.size()); -} + m_localHostname.resize(9); + memcpy(&m_localHostname[0], "localhost", m_localHostname.size()); + } -if ((*bufferlength) < m_localHostname.size()) -{ -*bufferlength = m_localHostname.size(); // tell the application the required size of the buffer -MAINMUTEX_UNLOCK -return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; -} + if ((*bufferlength) < m_localHostname.size()) + { + *bufferlength = m_localHostname.size(); // tell the application the required size of the buffer + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; + } -memcpy(buffer, &m_localHostname[0], m_localHostname.size()); -*bufferlength = m_localHostname.size(); + memcpy(buffer, &m_localHostname[0], m_localHostname.size()); + *bufferlength = m_localHostname.size(); -MAINMUTEX_UNLOCK -return 0; + return 0; } bool RTPTCPTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) { -if (!m_init) -return false; + if (!m_init) + return false; -if (addr == 0) -return false; + if (addr == 0) + return false; -MAINMUTEX_LOCK + if (!m_created) + return false; -if (!m_created) -return false; + if (addr->GetAddressType() != RTPAddress::TCPAddress) + return false; -if (addr->GetAddressType() != RTPAddress::TCPAddress) -return false; + bool v = false; -bool v = false; + // TODO: for now, we're assuming that we can't just send to the same transmitter - // TODO: for now, we're assuming that we can't just send to the same transmitter - -MAINMUTEX_UNLOCK -return v; + return v; } int RTPTCPTransmitter::Poll() { -if (!m_init) -return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; -MAINMUTEX_LOCK -if (!m_created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTCREATED; -} - -std::map::iterator it = m_destSockets.begin(); -std::map::iterator end = m_destSockets.end(); -int status = 0; - -vector errSockets; - -while (it != end) -{ -SocketType sock = it->first; -status = PollSocket(sock, it->second); -if (status < 0) -{ - // Stop immediately on out of memory - if (status == ERR_RTP_OUTOFMEM) - break; - else + if (!m_created) { - errSockets.push_back(sock); - // Don't let this count as an error (due to a closed connection for example), - // otherwise the poll thread (if used) will stop because of this. Since there - // may be more than one connection, that's not desirable in general. - status = 0; + return ERR_RTP_TCPTRANS_NOTCREATED; } -} -++it; -} -MAINMUTEX_UNLOCK -for (size_t i = 0; i < errSockets.size(); i++) -OnReceiveError(errSockets[i]); + std::map::iterator it = m_destSockets.begin(); + std::map::iterator end = m_destSockets.end(); + int status = 0; -return status; + vector errSockets; + + while (it != end) + { + SocketType sock = it->first; + status = PollSocket(sock, it->second); + if (status < 0) + { + // Stop immediately on out of memory + if (status == ERR_RTP_OUTOFMEM) + break; + else + { + errSockets.push_back(sock); + // Don't let this count as an error (due to a closed connection for example), + // otherwise the poll thread (if used) will stop because of this. Since there + // may be more than one connection, that's not desirable in general. + status = 0; + } + } + ++it; + } + + for (size_t i = 0; i < errSockets.size(); i++) + OnReceiveError(errSockets[i]); + + return status; } int RTPTCPTransmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) { -if (!m_init) -return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; -MAINMUTEX_LOCK - -if (!m_created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTCREATED; -} -if (m_waitingForData) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_ALREADYWAITING; -} - -m_tmpSocks.resize(m_destSockets.size() + 1); -m_tmpFlags.resize(m_tmpSocks.size()); -SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); - -std::map::iterator it = m_destSockets.begin(); -std::map::iterator end = m_destSockets.end(); -int idx = 0; - -while (it != end) -{ -m_tmpSocks[idx] = it->first; -m_tmpFlags[idx] = 0; -++it; -idx++; -} -m_tmpSocks[idx] = abortSocket; -m_tmpFlags[idx] = 0; -int idxAbort = idx; - -m_waitingForData = true; - -WAITMUTEX_LOCK -MAINMUTEX_UNLOCK - - //cout << "Waiting for " << delay.GetDouble() << " seconds for data on " << m_tmpSocks.size() << " sockets" << endl; -int status = RTPSelect(&m_tmpSocks[0], &m_tmpFlags[0], m_tmpSocks.size(), delay); -if (status < 0) -{ -MAINMUTEX_LOCK -m_waitingForData = false; -MAINMUTEX_UNLOCK -WAITMUTEX_UNLOCK -return status; -} - -MAINMUTEX_LOCK -m_waitingForData = false; -if (!m_created) // destroy called -{ -MAINMUTEX_UNLOCK; -WAITMUTEX_UNLOCK -return 0; -} - - // if aborted, read from abort buffer -if (m_tmpFlags[idxAbort]) -m_pAbortDesc->ReadSignallingByte(); - -if (dataavailable != 0) -{ -bool avail = false; - -for (size_t i = 0; i < m_tmpFlags.size(); i++) -{ - if (m_tmpFlags[i]) + if (!m_created) { - avail = true; - //cout << "Data available!" << endl; - break; + return ERR_RTP_TCPTRANS_NOTCREATED; + } + if (m_waitingForData) + { + return ERR_RTP_TCPTRANS_ALREADYWAITING; } -} -if (avail) - *dataavailable = true; -else - *dataavailable = false; -} + m_tmpSocks.resize(m_destSockets.size() + 1); + m_tmpFlags.resize(m_tmpSocks.size()); + SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); -MAINMUTEX_UNLOCK -WAITMUTEX_UNLOCK -return 0; + std::map::iterator it = m_destSockets.begin(); + std::map::iterator end = m_destSockets.end(); + int idx = 0; + + while (it != end) + { + m_tmpSocks[idx] = it->first; + m_tmpFlags[idx] = 0; + ++it; + idx++; + } + m_tmpSocks[idx] = abortSocket; + m_tmpFlags[idx] = 0; + int idxAbort = idx; + + m_waitingForData = true; + + //cout << "Waiting for " << delay.GetDouble() << " seconds for data on " << m_tmpSocks.size() << " sockets" << endl; + int status = RTPSelect(&m_tmpSocks[0], &m_tmpFlags[0], m_tmpSocks.size(), delay); + if (status < 0) + { + m_waitingForData = false; + + return status; + } + + m_waitingForData = false; + if (!m_created) // destroy called + { + + return 0; + } + + // if aborted, read from abort buffer + if (m_tmpFlags[idxAbort]) + m_pAbortDesc->ReadSignallingByte(); + + if (dataavailable != 0) + { + bool avail = false; + + for (size_t i = 0; i < m_tmpFlags.size(); i++) + { + if (m_tmpFlags[i]) + { + avail = true; + //cout << "Data available!" << endl; + break; + } + } + + if (avail) + *dataavailable = true; + else + *dataavailable = false; + } + + return 0; } int RTPTCPTransmitter::AbortWait() { -if (!m_init) -return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; -MAINMUTEX_LOCK -if (!m_created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTCREATED; -} -if (!m_waitingForData) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTWAITING; -} + if (!m_created) + { + return ERR_RTP_TCPTRANS_NOTCREATED; + } + if (!m_waitingForData) + { + return ERR_RTP_TCPTRANS_NOTWAITING; + } -m_pAbortDesc->SendAbortSignal(); + m_pAbortDesc->SendAbortSignal(); -MAINMUTEX_UNLOCK -return 0; + return 0; } int RTPTCPTransmitter::SendRTPData(const void *data, size_t len) { -return SendRTPRTCPData(data, len); + return SendRTPRTCPData(data, len); } int RTPTCPTransmitter::SendRTCPData(const void *data, size_t len) { -return SendRTPRTCPData(data, len); + return SendRTPRTCPData(data, len); } int RTPTCPTransmitter::AddDestination(const RTPAddress &addr) { -if (!m_init) -return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; -MAINMUTEX_LOCK + if (!m_created) + { + return ERR_RTP_TCPTRANS_NOTCREATED; + } -if (!m_created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTCREATED; -} + if (addr.GetAddressType() != RTPAddress::TCPAddress) + { + return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; + } -if (addr.GetAddressType() != RTPAddress::TCPAddress) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; -} + const RTPTCPAddress &a = static_cast(addr); + SocketType s = a.GetSocket(); + if (s == 0) + { + return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; + } -const RTPTCPAddress &a = static_cast(addr); -SocketType s = a.GetSocket(); -if (s == 0) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; -} + int status = ValidateSocket(s); + if (status != 0) + { -int status = ValidateSocket(s); -if (status != 0) -{ -MAINMUTEX_UNLOCK -return status; -} + return status; + } -std::map::iterator it = m_destSockets.find(s); -if (it != m_destSockets.end()) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS; -} -m_destSockets[s] = SocketData(); + std::map::iterator it = m_destSockets.find(s); + if (it != m_destSockets.end()) + { - // Because the sockets are also used for incoming data, we'll abort a wait - // that may be in progress, otherwise it could take a few seconds until the - // new socket is monitored for incoming data -m_pAbortDesc->SendAbortSignal(); + return ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS; + } + m_destSockets[s] = SocketData(); -MAINMUTEX_UNLOCK -return 0; + // Because the sockets are also used for incoming data, we'll abort a wait + // that may be in progress, otherwise it could take a few seconds until the + // new socket is monitored for incoming data + m_pAbortDesc->SendAbortSignal(); + + return 0; } int RTPTCPTransmitter::DeleteDestination(const RTPAddress &addr) { -if (!m_init) -return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; -MAINMUTEX_LOCK + if (!m_created) + { -if (!m_created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTCREATED; -} + return ERR_RTP_TCPTRANS_NOTCREATED; + } -if (addr.GetAddressType() != RTPAddress::TCPAddress) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; -} + if (addr.GetAddressType() != RTPAddress::TCPAddress) + { -const RTPTCPAddress &a = static_cast(addr); -SocketType s = a.GetSocket(); -if (s == 0) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; -} + return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; + } -std::map::iterator it = m_destSockets.find(s); -if (it == m_destSockets.end()) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS; -} + const RTPTCPAddress &a = static_cast(addr); + SocketType s = a.GetSocket(); + if (s == 0) + { - // Clean up possibly allocated memory -uint8_t *pBuf = it->second.ExtractDataBuffer(); -if (pBuf) -delete[] pBuf; + return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; + } -m_destSockets.erase(it); + std::map::iterator it = m_destSockets.find(s); + if (it == m_destSockets.end()) + { -MAINMUTEX_UNLOCK -return 0; + return ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS; + } + + // Clean up possibly allocated memory + uint8_t *pBuf = it->second.ExtractDataBuffer(); + if (pBuf) + delete[] pBuf; + + m_destSockets.erase(it); + + return 0; } void RTPTCPTransmitter::ClearDestinations() { -if (!m_init) -return; + if (!m_init) + return; + + if (m_created) + ClearDestSockets(); -MAINMUTEX_LOCK -if (m_created) -ClearDestSockets(); -MAINMUTEX_UNLOCK } bool RTPTCPTransmitter::SupportsMulticasting() { -return false; + return false; } int RTPTCPTransmitter::JoinMulticastGroup(const RTPAddress &) { -return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; + return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; } int RTPTCPTransmitter::LeaveMulticastGroup(const RTPAddress &) { -return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; + return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; } void RTPTCPTransmitter::LeaveAllMulticastGroups() @@ -545,19 +490,19 @@ void RTPTCPTransmitter::LeaveAllMulticastGroups() int RTPTCPTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) { -if (m != RTPTransmitter::AcceptAll) -return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; -return 0; + if (m != RTPTransmitter::AcceptAll) + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; + return 0; } int RTPTCPTransmitter::AddToIgnoreList(const RTPAddress &) { -return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; } int RTPTCPTransmitter::DeleteFromIgnoreList(const RTPAddress &) { -return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; } void RTPTCPTransmitter::ClearIgnoreList() @@ -566,12 +511,12 @@ void RTPTCPTransmitter::ClearIgnoreList() int RTPTCPTransmitter::AddToAcceptList(const RTPAddress &) { -return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; } int RTPTCPTransmitter::DeleteFromAcceptList(const RTPAddress &) { -return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; + return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; } void RTPTCPTransmitter::ClearAcceptList() @@ -580,317 +525,306 @@ void RTPTCPTransmitter::ClearAcceptList() int RTPTCPTransmitter::SetMaximumPacketSize(size_t s) { -if (!m_init) -return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; -MAINMUTEX_LOCK -if (!m_created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTCREATED; -} -if (s > RTPTCPTRANS_MAXPACKSIZE) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; -} -m_maxPackSize = s; -MAINMUTEX_UNLOCK -return 0; + if (!m_created) + { + + return ERR_RTP_TCPTRANS_NOTCREATED; + } + if (s > RTPTCPTRANS_MAXPACKSIZE) + { + + return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; + } + m_maxPackSize = s; + + return 0; } bool RTPTCPTransmitter::NewDataAvailable() { -if (!m_init) -return false; + if (!m_init) + return false; -MAINMUTEX_LOCK + bool v; -bool v; + if (!m_created) + v = false; + else + { + if (m_rawpacketlist.empty()) + v = false; + else + v = true; + } -if (!m_created) -v = false; -else -{ -if (m_rawpacketlist.empty()) -v = false; -else -v = true; -} - -MAINMUTEX_UNLOCK -return v; + return v; } RTPRawPacket *RTPTCPTransmitter::GetNextPacket() { -if (!m_init) -return 0; + if (!m_init) + return 0; -MAINMUTEX_LOCK + RTPRawPacket *p; -RTPRawPacket *p; + if (!m_created) + { -if (!m_created) -{ -MAINMUTEX_UNLOCK -return 0; -} -if (m_rawpacketlist.empty()) -{ -MAINMUTEX_UNLOCK -return 0; -} + return 0; + } + if (m_rawpacketlist.empty()) + { -p = *(m_rawpacketlist.begin()); -m_rawpacketlist.pop_front(); + return 0; + } -MAINMUTEX_UNLOCK -return p; + p = *(m_rawpacketlist.begin()); + m_rawpacketlist.pop_front(); + + return p; } // Here the private functions start... void RTPTCPTransmitter::FlushPackets() { -std::list::const_iterator it; + std::list::const_iterator it; -for (it = m_rawpacketlist.begin(); it != m_rawpacketlist.end(); ++it) -delete *it; -m_rawpacketlist.clear(); + for (it = m_rawpacketlist.begin(); it != m_rawpacketlist.end(); ++it) + delete *it; + m_rawpacketlist.clear(); } int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata) { #ifdef RTP_SOCKETTYPE_WINSOCK -unsigned long len; + unsigned long len; #else -size_t len; + size_t len; #endif // RTP_SOCKETTYPE_WINSOCK -bool dataavailable; + bool dataavailable; -do -{ -len = 0; -RTPIOCTL(sock, FIONREAD, &len); - -if (len <= 0) -dataavailable = false; -else -dataavailable = true; - -if (dataavailable) -{ -RTPTime curtime = RTPTime::CurrentTime(); -int relevantLen = RTPTCPTRANS_MAXPACKSIZE + 2; - -if ((int) len < relevantLen) - relevantLen = (int) len; - -bool complete = false; -int status = sdata.ProcessAvailableBytes(sock, relevantLen, complete); -if (status < 0) - return status; - -if (complete) -{ - uint8_t *pBuf = sdata.ExtractDataBuffer(); - if (pBuf) + do { - int dataLength = sdata.m_dataLength; - sdata.Reset(); + len = 0; + RTPIOCTL(sock, FIONREAD, &len); - RTPTCPAddress *pAddr = new RTPTCPAddress(sock); - if (pAddr == 0) - return ERR_RTP_OUTOFMEM; + if (len <= 0) + dataavailable = false; + else + dataavailable = true; - bool isrtp = true; - if (dataLength > (int) sizeof(RTCPCommonHeader)) + if (dataavailable) { - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) pBuf; - uint8_t packettype = rtcpheader->packettype; + RTPTime curtime = RTPTime::CurrentTime(); + int relevantLen = RTPTCPTRANS_MAXPACKSIZE + 2; - if (packettype >= 200 && packettype <= 204) - isrtp = false; + if ((int) len < relevantLen) + relevantLen = (int) len; + + bool complete = false; + int status = sdata.ProcessAvailableBytes(sock, relevantLen, complete); + if (status < 0) + return status; + + if (complete) + { + uint8_t *pBuf = sdata.ExtractDataBuffer(); + if (pBuf) + { + int dataLength = sdata.m_dataLength; + sdata.Reset(); + + RTPTCPAddress *pAddr = new RTPTCPAddress(sock); + if (pAddr == 0) + return ERR_RTP_OUTOFMEM; + + bool isrtp = true; + if (dataLength > (int) sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) pBuf; + uint8_t packettype = rtcpheader->packettype; + + if (packettype >= 200 && packettype <= 204) + isrtp = false; + } + + RTPRawPacket *pPack = new RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp); + if (pPack == 0) + { + delete pAddr; + delete[] pBuf; + return ERR_RTP_OUTOFMEM; + } + m_rawpacketlist.push_back(pPack); + } + } } + } while (dataavailable); - RTPRawPacket *pPack = new RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp); - if (pPack == 0) - { - delete pAddr; - delete[] pBuf; - return ERR_RTP_OUTOFMEM; - } - m_rawpacketlist.push_back(pPack); - } -} -} -} while (dataavailable); - -return 0; + return 0; } int RTPTCPTransmitter::SendRTPRTCPData(const void *data, size_t len) { -if (!m_init) -return ERR_RTP_TCPTRANS_NOTINIT; + if (!m_init) + return ERR_RTP_TCPTRANS_NOTINIT; -MAINMUTEX_LOCK + if (!m_created) + { -if (!m_created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_NOTCREATED; -} -if (len > RTPTCPTRANS_MAXPACKSIZE) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; -} + return ERR_RTP_TCPTRANS_NOTCREATED; + } + if (len > RTPTCPTRANS_MAXPACKSIZE) + { -std::map::iterator it = m_destSockets.begin(); -std::map::iterator end = m_destSockets.end(); + return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; + } -vector errSockets; -int flags = 0; + std::map::iterator it = m_destSockets.begin(); + std::map::iterator end = m_destSockets.end(); + + vector errSockets; + int flags = 0; #ifdef RTP_HAVE_MSG_NOSIGNAL -flags = MSG_NOSIGNAL; + flags = MSG_NOSIGNAL; #endif // RTP_HAVE_MSG_NOSIGNAL -while (it != end) -{ -uint8_t lengthBytes[2] = -{ (uint8_t) ((len >> 8) & 0xff), (uint8_t) (len & 0xff) }; -SocketType sock = it->first; + while (it != end) + { + uint8_t lengthBytes[2] = + { (uint8_t) ((len >> 8) & 0xff), (uint8_t) (len & 0xff) }; + SocketType sock = it->first; -if (send(sock, (const char *) lengthBytes, 2, flags) < 0 || send(sock, (const char *) data, len, flags) < 0) -errSockets.push_back(sock); -++it; -} + if (send(sock, (const char *) lengthBytes, 2, flags) < 0 || send(sock, (const char *) data, len, flags) < 0) + errSockets.push_back(sock); + ++it; + } -MAINMUTEX_UNLOCK + if (errSockets.size() != 0) + { + for (size_t i = 0; i < errSockets.size(); i++) + OnSendError(errSockets[i]); + } -if (errSockets.size() != 0) -{ -for (size_t i = 0; i < errSockets.size(); i++) -OnSendError(errSockets[i]); -} + // Don't return an error code to avoid the poll thread exiting + // due to one closed connection for example - // Don't return an error code to avoid the poll thread exiting - // due to one closed connection for example - -return 0; + return 0; } int RTPTCPTransmitter::ValidateSocket(SocketType) { - // TODO: should we even do a check (for a TCP socket)? -return 0; + // TODO: should we even do a check (for a TCP socket)? + return 0; } void RTPTCPTransmitter::ClearDestSockets() { -std::map::iterator it = m_destSockets.begin(); -std::map::iterator end = m_destSockets.end(); + std::map::iterator it = m_destSockets.begin(); + std::map::iterator end = m_destSockets.end(); -while (it != end) -{ -uint8_t *pBuf = it->second.ExtractDataBuffer(); -if (pBuf) -delete[] pBuf; + while (it != end) + { + uint8_t *pBuf = it->second.ExtractDataBuffer(); + if (pBuf) + delete[] pBuf; -++it; -} -m_destSockets.clear(); + ++it; + } + m_destSockets.clear(); } RTPTCPTransmitter::SocketData::SocketData() { -Reset(); + Reset(); } void RTPTCPTransmitter::SocketData::Reset() { -m_lengthBufferOffset = 0; -m_dataLength = 0; -m_dataBufferOffset = 0; -m_pDataBuffer = 0; + m_lengthBufferOffset = 0; + m_dataLength = 0; + m_dataBufferOffset = 0; + m_pDataBuffer = 0; } RTPTCPTransmitter::SocketData::~SocketData() { -assert(m_pDataBuffer == 0); // Should be deleted externally to avoid storing a memory manager in the class + assert(m_pDataBuffer == 0); // Should be deleted externally to avoid storing a memory manager in the class } int RTPTCPTransmitter::SocketData::ProcessAvailableBytes(SocketType sock, int availLen, bool &complete) { -const int numLengthBuffer = 2; -if (m_lengthBufferOffset < numLengthBuffer) // first we need to get the length -{ -assert(m_pDataBuffer == 0); -int num = numLengthBuffer - m_lengthBufferOffset; -if (num > availLen) -num = availLen; + const int numLengthBuffer = 2; + if (m_lengthBufferOffset < numLengthBuffer) // first we need to get the length + { + assert(m_pDataBuffer == 0); + int num = numLengthBuffer - m_lengthBufferOffset; + if (num > availLen) + num = availLen; -int r = 0; -if (num > 0) -{ -r = (int) recv(sock, (char *) (m_lengthBuffer + m_lengthBufferOffset), num, 0); -if (r < 0) - return ERR_RTP_TCPTRANS_ERRORINRECV; -} + int r = 0; + if (num > 0) + { + r = (int) recv(sock, (char *) (m_lengthBuffer + m_lengthBufferOffset), num, 0); + if (r < 0) + return ERR_RTP_TCPTRANS_ERRORINRECV; + } -m_lengthBufferOffset += r; -availLen -= r; + m_lengthBufferOffset += r; + availLen -= r; -assert(m_lengthBufferOffset <= numLengthBuffer); -if (m_lengthBufferOffset == numLengthBuffer) // we can constuct a length -{ -int l = 0; -for (int i = numLengthBuffer - 1, shift = 0; i >= 0; i--, shift += 8) - l |= ((int) m_lengthBuffer[i]) << shift; + assert(m_lengthBufferOffset <= numLengthBuffer); + if (m_lengthBufferOffset == numLengthBuffer) // we can constuct a length + { + int l = 0; + for (int i = numLengthBuffer - 1, shift = 0; i >= 0; i--, shift += 8) + l |= ((int) m_lengthBuffer[i]) << shift; -m_dataLength = l; -m_dataBufferOffset = 0; + m_dataLength = l; + m_dataBufferOffset = 0; //cout << "Expecting " << m_dataLength << " bytes" << endl; // avoid allocation of length 0 -if (l == 0) - l = 1; + if (l == 0) + l = 1; // We don't yet know if it's an RTP or RTCP packet, so we'll stick to RTP -m_pDataBuffer = new uint8_t[l]; -if (m_pDataBuffer == 0) - return ERR_RTP_OUTOFMEM; -} -} + m_pDataBuffer = new uint8_t[l]; + if (m_pDataBuffer == 0) + return ERR_RTP_OUTOFMEM; + } + } -if (m_lengthBufferOffset == numLengthBuffer && m_pDataBuffer) // the last one is to make sure we didn't run out of memory -{ -if (m_dataBufferOffset < m_dataLength) -{ -int num = m_dataLength - m_dataBufferOffset; -if (num > availLen) - num = availLen; + if (m_lengthBufferOffset == numLengthBuffer && m_pDataBuffer) // the last one is to make sure we didn't run out of memory + { + if (m_dataBufferOffset < m_dataLength) + { + int num = m_dataLength - m_dataBufferOffset; + if (num > availLen) + num = availLen; -int r = 0; -if (num > 0) -{ - r = (int) recv(sock, (char *) (m_pDataBuffer + m_dataBufferOffset), num, 0); - if (r < 0) - return ERR_RTP_TCPTRANS_ERRORINRECV; -} + int r = 0; + if (num > 0) + { + r = (int) recv(sock, (char *) (m_pDataBuffer + m_dataBufferOffset), num, 0); + if (r < 0) + return ERR_RTP_TCPTRANS_ERRORINRECV; + } -m_dataBufferOffset += r; -availLen -= r; -} + m_dataBufferOffset += r; + availLen -= r; + } -if (m_dataBufferOffset == m_dataLength) -complete = true; -} -return 0; + if (m_dataBufferOffset == m_dataLength) + complete = true; + } + return 0; } } // end namespace diff --git a/qrtplib/rtpudpv4transmitter.cpp b/qrtplib/rtpudpv4transmitter.cpp index c687f8f93..47cc89aac 100644 --- a/qrtplib/rtpudpv4transmitter.cpp +++ b/qrtplib/rtpudpv4transmitter.cpp @@ -59,10 +59,6 @@ using namespace std; mreq.imr_interface.s_addr = htonl(mcastifaceIP);\ status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ } -#define MAINMUTEX_LOCK -#define MAINMUTEX_UNLOCK -#define WAITMUTEX_LOCK -#define WAITMUTEX_UNLOCK #define CLOSESOCKETS do { \ if (closesocketswhendone) \ @@ -269,11 +265,9 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (created) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ALREADYCREATED; } @@ -285,7 +279,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP { if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; } params = (const RTPUDPv4TransmissionParams *) transparams; @@ -299,13 +293,13 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP int status = GetIPv4SocketPort(rtpsock, &m_rtpPort); if (status < 0) { - MAINMUTEX_UNLOCK + return status; } status = GetIPv4SocketPort(rtcpsock, &m_rtcpPort); if (status < 0) { - MAINMUTEX_UNLOCK + return status; } } @@ -318,7 +312,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); if (status < 0) { - MAINMUTEX_UNLOCK + return status; } } @@ -327,7 +321,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP // Check if portbase is even (if necessary) if (!params->GetAllowOddPortbase() && params->GetPortbase() % 2 != 0) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; } @@ -336,7 +330,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP rtpsock = socket(PF_INET, SOCK_DGRAM, 0); if (rtpsock == RTPSOCKERR) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; } @@ -349,7 +343,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if (rtcpsock == RTPSOCKERR) { RTPCLOSE(rtpsock); - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; } } @@ -367,7 +361,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if (bind(rtpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; } @@ -390,7 +384,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if (bind(rtcpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; } @@ -406,14 +400,14 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if (setsockopt(rtpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; } size = params->GetRTPSendBuffer(); if (setsockopt(rtpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; } @@ -423,14 +417,14 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if (setsockopt(rtcpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; } size = params->GetRTCPSendBuffer(); if (setsockopt(rtcpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; } } @@ -446,7 +440,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if ((status = CreateLocalIPList()) < 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return status; } } @@ -463,7 +457,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if (maximumpacketsize > RTPUDPV4TRANS_MAXPACKSIZE) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; } @@ -472,7 +466,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if ((status = m_abortDesc.Init()) < 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return status; } m_pAbortDesc = &m_abortDesc; @@ -483,7 +477,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP if (!m_pAbortDesc->IsInitialized()) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; } } @@ -498,7 +492,7 @@ int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionP waitingfordata = false; created = true; - MAINMUTEX_UNLOCK + return 0; } @@ -507,10 +501,9 @@ void RTPUDPv4Transmitter::Destroy() if (!init) return; - MAINMUTEX_LOCK if (!created) { - MAINMUTEX_UNLOCK; + ; return; } @@ -535,605 +528,563 @@ void RTPUDPv4Transmitter::Destroy() { m_pAbortDesc->SendAbortSignal(); m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK// to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK -} -else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized -MAINMUTEX_UNLOCK + // to make sure that the WaitForIncomingData function ended + + } + else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + } RTPTransmissionInfo *RTPUDPv4Transmitter::GetTransmissionInfo() { -if (!init) -return 0; + if (!init) + return 0; -MAINMUTEX_LOCK -RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionInfo(localIPs, rtpsock, rtcpsock, m_rtpPort, m_rtcpPort); -MAINMUTEX_UNLOCK -return tinf; + RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionInfo(localIPs, rtpsock, rtcpsock, m_rtpPort, m_rtcpPort); + + return tinf; } void RTPUDPv4Transmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) { -if (!init) -return; + if (!init) + return; -delete i; + delete i; } int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} - -if (localhostname == 0) -{ -if (localIPs.empty()) -{ - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOLOCALIPS; -} - -std::list::const_iterator it; -std::list hostnames; - -for (it = localIPs.begin(); it != localIPs.end(); it++) -{ - bool founddouble = false; - bool foundentry = true; - - while (!founddouble && foundentry) + if (!created) { - struct hostent *he; - uint8_t addr[4]; - uint32_t ip = (*it); - addr[0] = (uint8_t) ((ip >> 24) & 0xFF); - addr[1] = (uint8_t) ((ip >> 16) & 0xFF); - addr[2] = (uint8_t) ((ip >> 8) & 0xFF); - addr[3] = (uint8_t) (ip & 0xFF); - he = gethostbyaddr((char *) addr, 4, AF_INET); - if (he != 0) + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (localhostname == 0) + { + if (localIPs.empty()) + { + + return ERR_RTP_UDPV4TRANS_NOLOCALIPS; + } + + std::list::const_iterator it; + std::list hostnames; + + for (it = localIPs.begin(); it != localIPs.end(); it++) + { + bool founddouble = false; + bool foundentry = true; + + while (!founddouble && foundentry) + { + struct hostent *he; + uint8_t addr[4]; + uint32_t ip = (*it); + + addr[0] = (uint8_t) ((ip >> 24) & 0xFF); + addr[1] = (uint8_t) ((ip >> 16) & 0xFF); + addr[2] = (uint8_t) ((ip >> 8) & 0xFF); + addr[3] = (uint8_t) (ip & 0xFF); + he = gethostbyaddr((char *) addr, 4, AF_INET); + if (he != 0) + { + std::string hname = std::string(he->h_name); + std::list::const_iterator it; + + for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + hostnames.push_back(hname); + + int i = 0; + while (!founddouble && he->h_aliases[i] != 0) + { + std::string hname = std::string(he->h_aliases[i]); + + for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + { + hostnames.push_back(hname); + i++; + } + } + } + else + foundentry = false; + } + } + + bool found = false; + + if (!hostnames.empty()) // try to select the most appropriate hostname { - std::string hname = std::string(he->h_name); std::list::const_iterator it; - for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) - hostnames.push_back(hname); - - int i = 0; - while (!founddouble && he->h_aliases[i] != 0) + hostnames.sort(); + for (it = hostnames.begin(); !found && it != hostnames.end(); it++) { - std::string hname = std::string(he->h_aliases[i]); - - for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) + if ((*it).find('.') != std::string::npos) { - hostnames.push_back(hname); - i++; + found = true; + localhostnamelength = (*it).length(); + localhostname = new uint8_t[localhostnamelength + 1]; + if (localhostname == 0) + { + + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname, (*it).c_str(), localhostnamelength); + localhostname[localhostnamelength] = 0; } } } - else - foundentry = false; - } -} -bool found = false; - -if (!hostnames.empty()) // try to select the most appropriate hostname -{ - std::list::const_iterator it; - - hostnames.sort(); - for (it = hostnames.begin(); !found && it != hostnames.end(); it++) - { - if ((*it).find('.') != std::string::npos) + if (!found) // use an IP address { - found = true; - localhostnamelength = (*it).length(); + uint32_t ip; + int len; + char str[16]; + + it = localIPs.begin(); + ip = (*it); + + RTP_SNPRINTF(str, 16, "%d.%d.%d.%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF)); + len = strlen(str); + + localhostnamelength = len; localhostname = new uint8_t[localhostnamelength + 1]; if (localhostname == 0) { - MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; } - memcpy(localhostname, (*it).c_str(), localhostnamelength); + memcpy(localhostname, str, localhostnamelength); localhostname[localhostnamelength] = 0; } } -} -if (!found) // use an IP address -{ - uint32_t ip; - int len; - char str[16]; - - it = localIPs.begin(); - ip = (*it); - - RTP_SNPRINTF(str, 16, "%d.%d.%d.%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF)); - len = strlen(str); - - localhostnamelength = len; - localhostname = new uint8_t[localhostnamelength + 1]; - if (localhostname == 0) + if ((*bufferlength) < localhostnamelength) { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; + *bufferlength = localhostnamelength; // tell the application the required size of the buffer + + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; } - memcpy(localhostname, str, localhostnamelength); - localhostname[localhostnamelength] = 0; -} -} -if ((*bufferlength) < localhostnamelength) -{ -*bufferlength = localhostnamelength; // tell the application the required size of the buffer -MAINMUTEX_UNLOCK -return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; -} + memcpy(buffer, localhostname, localhostnamelength); + *bufferlength = localhostnamelength; -memcpy(buffer, localhostname, localhostnamelength); -*bufferlength = localhostnamelength; - -MAINMUTEX_UNLOCK -return 0; + return 0; } bool RTPUDPv4Transmitter::ComesFromThisTransmitter(const RTPAddress *addr) { -if (!init) -return false; + if (!init) + return false; -if (addr == 0) -return false; + if (addr == 0) + return false; -MAINMUTEX_LOCK + bool v; -bool v; + if (created && addr->GetAddressType() == RTPAddress::IPv4Address) + { + const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; + bool found = false; + std::list::const_iterator it; -if (created && addr->GetAddressType() == RTPAddress::IPv4Address) -{ -const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; -bool found = false; -std::list::const_iterator it; + it = localIPs.begin(); + while (!found && it != localIPs.end()) + { + if (addr2->GetIP() == *it) + found = true; + else + ++it; + } -it = localIPs.begin(); -while (!found && it != localIPs.end()) -{ - if (addr2->GetIP() == *it) - found = true; - else - ++it; -} - -if (!found) - v = false; -else -{ - if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port - v = true; + if (!found) + v = false; + else + { + if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port + v = true; + else + v = false; + } + } else v = false; -} -} -else -v = false; -MAINMUTEX_UNLOCK -return v; + return v; } int RTPUDPv4Transmitter::Poll() { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -int status; + int status; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -status = PollSocket(true); // poll RTP socket -if (rtpsock != rtcpsock) // no need to poll twice when multiplexing -{ -if (status >= 0) - status = PollSocket(false); // poll RTCP socket -} -MAINMUTEX_UNLOCK -return status; + if (!created) + { + + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + status = PollSocket(true); // poll RTP socket + if (rtpsock != rtcpsock) // no need to poll twice when multiplexing + { + if (status >= 0) + status = PollSocket(false); // poll RTCP socket + } + + return status; } int RTPUDPv4Transmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (waitingfordata) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_ALREADYWAITING; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (waitingfordata) + { -SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); + return ERR_RTP_UDPV4TRANS_ALREADYWAITING; + } -SocketType socks[3] = -{ rtpsock, rtcpsock, abortSocket }; -int8_t readflags[3] = -{ 0, 0, 0 }; -const int idxRTP = 0; -const int idxRTCP = 1; -const int idxAbort = 2; + SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); -waitingfordata = true; + SocketType socks[3] = + { rtpsock, rtcpsock, abortSocket }; + int8_t readflags[3] = + { 0, 0, 0 }; + const int idxRTP = 0; + const int idxRTCP = 1; + const int idxAbort = 2; -WAITMUTEX_LOCK -MAINMUTEX_UNLOCK + waitingfordata = true; -int status = RTPSelect(socks, readflags, 3, delay); -if (status < 0) -{ -MAINMUTEX_LOCK -waitingfordata = false; -MAINMUTEX_UNLOCK -WAITMUTEX_UNLOCK -return status; -} + int status = RTPSelect(socks, readflags, 3, delay); + if (status < 0) + { + waitingfordata = false; -MAINMUTEX_LOCK -waitingfordata = false; -if (!created) // destroy called -{ -MAINMUTEX_UNLOCK; -WAITMUTEX_UNLOCK -return 0; -} + return status; + } - // if aborted, read from abort buffer -if (readflags[idxAbort]) -m_pAbortDesc->ReadSignallingByte(); + waitingfordata = false; + if (!created) // destroy called + { + ; -if (dataavailable != 0) -{ -if (readflags[idxRTP] || readflags[idxRTCP]) - *dataavailable = true; -else - *dataavailable = false; -} + return 0; + } -MAINMUTEX_UNLOCK -WAITMUTEX_UNLOCK -return 0; + // if aborted, read from abort buffer + if (readflags[idxAbort]) + m_pAbortDesc->ReadSignallingByte(); + + if (dataavailable != 0) + { + if (readflags[idxRTP] || readflags[idxRTCP]) + *dataavailable = true; + else + *dataavailable = false; + } + + return 0; } int RTPUDPv4Transmitter::AbortWait() { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (!waitingfordata) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTWAITING; -} + if (!created) + { -m_pAbortDesc->SendAbortSignal(); + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (!waitingfordata) + { -MAINMUTEX_UNLOCK -return 0; + return ERR_RTP_UDPV4TRANS_NOTWAITING; + } + + m_pAbortDesc->SendAbortSignal(); + + return 0; } int RTPUDPv4Transmitter::SendRTPData(const void *data, size_t len) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (len > maxpacksize) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (len > maxpacksize) + { -destinations.GotoFirstElement(); -while (destinations.HasCurrentElement()) -{ -sendto(rtpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTPSockAddr(), sizeof(struct sockaddr_in)); -destinations.GotoNextElement(); -} + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } -MAINMUTEX_UNLOCK -return 0; + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTPSockAddr(), sizeof(struct sockaddr_in)); + destinations.GotoNextElement(); + } + + return 0; } int RTPUDPv4Transmitter::SendRTCPData(const void *data, size_t len) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (len > maxpacksize) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (len > maxpacksize) + { -destinations.GotoFirstElement(); -while (destinations.HasCurrentElement()) -{ -sendto(rtcpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTCPSockAddr(), sizeof(struct sockaddr_in)); -destinations.GotoNextElement(); -} + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } -MAINMUTEX_UNLOCK -return 0; + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtcpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTCPSockAddr(), sizeof(struct sockaddr_in)); + destinations.GotoNextElement(); + } + + return 0; } int RTPUDPv4Transmitter::AddDestination(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } -RTPIPv4Destination dest; -if (!RTPIPv4Destination::AddressToDestination(addr, dest)) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { -int status = destinations.AddElement(dest); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } -MAINMUTEX_UNLOCK -return status; + int status = destinations.AddElement(dest); + + return status; } int RTPUDPv4Transmitter::DeleteDestination(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -RTPIPv4Destination dest; -if (!RTPIPv4Destination::AddressToDestination(addr, dest)) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { -int status = destinations.DeleteElement(dest); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } -MAINMUTEX_UNLOCK -return status; + int status = destinations.DeleteElement(dest); + + return status; } void RTPUDPv4Transmitter::ClearDestinations() { -if (!init) -return; + if (!init) + return; + + if (created) + destinations.Clear(); -MAINMUTEX_LOCK -if (created) -destinations.Clear(); -MAINMUTEX_UNLOCK } bool RTPUDPv4Transmitter::SupportsMulticasting() { -if (!init) -return false; + if (!init) + return false; -MAINMUTEX_LOCK + bool v; -bool v; + if (!created) + v = false; + else + v = supportsmulticasting; -if (!created) -v = false; -else -v = supportsmulticasting; - -MAINMUTEX_UNLOCK -return v; + return v; } #ifdef RTP_SUPPORT_IPV4MULTICAST int RTPUDPv4Transmitter::JoinMulticastGroup(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -uint32_t mcastIP = address.GetIP(); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } -if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; -} + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + uint32_t mcastIP = address.GetIP(); -status = multicastgroups.AddElement(mcastIP); -if (status >= 0) -{ -RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_ADD_MEMBERSHIP, mcastIP, status); -if (status != 0) -{ -multicastgroups.DeleteElement(mcastIP); -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; -} + if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) + { -if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing -{ -RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_ADD_MEMBERSHIP, mcastIP, status); -if (status != 0) -{ - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; -} -} -} -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.AddElement(mcastIP); + if (status >= 0) + { + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_ADD_MEMBERSHIP, mcastIP, status); + if (status != 0) + { + multicastgroups.DeleteElement(mcastIP); + + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + + if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing + { + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_ADD_MEMBERSHIP, mcastIP, status); + if (status != 0) + { + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + multicastgroups.DeleteElement(mcastIP); + + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + } + } + + return status; } int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -uint32_t mcastIP = address.GetIP(); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } -if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; -} + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + uint32_t mcastIP = address.GetIP(); -status = multicastgroups.DeleteElement(mcastIP); -if (status >= 0) -{ -RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); -if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing -RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) + { -status = 0; -} + return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; + } -MAINMUTEX_UNLOCK -return status; + status = multicastgroups.DeleteElement(mcastIP); + if (status >= 0) + { + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + + status = 0; + } + + return status; } void RTPUDPv4Transmitter::LeaveAllMulticastGroups() { -if (!init) -return; + if (!init) + return; -MAINMUTEX_LOCK -if (created) -{ -multicastgroups.GotoFirstElement(); -while (multicastgroups.HasCurrentElement()) -{ -uint32_t mcastIP; -int status __attribute__((unused)) = 0; + if (created) + { + multicastgroups.GotoFirstElement(); + while (multicastgroups.HasCurrentElement()) + { + uint32_t mcastIP; + int status __attribute__((unused)) = 0; -mcastIP = multicastgroups.GetCurrentElement(); + mcastIP = multicastgroups.GetCurrentElement(); -RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); -if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + + multicastgroups.GotoNextElement(); + } + multicastgroups.Clear(); + } -multicastgroups.GotoNextElement(); -} -multicastgroups.Clear(); -} -MAINMUTEX_UNLOCK } #else // no multicast support int RTPUDPv4Transmitter::JoinMulticastGroup(const RTPAddress &addr) { -return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; + return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; } int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) { -return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; + return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; } void RTPUDPv4Transmitter::LeaveAllMulticastGroups() @@ -1144,243 +1095,221 @@ void RTPUDPv4Transmitter::LeaveAllMulticastGroups() int RTPUDPv4Transmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (m != receivemode) -{ -receivemode = m; -acceptignoreinfo.Clear(); -} -MAINMUTEX_UNLOCK -return 0; + if (!created) + { + + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (m != receivemode) + { + receivemode = m; + acceptignoreinfo.Clear(); + } + + return 0; } int RTPUDPv4Transmitter::AddToIgnoreList(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} -if (receivemode != RTPTransmitter::IgnoreSome) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + + return status; } int RTPUDPv4Transmitter::DeleteFromIgnoreList(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} -if (receivemode != RTPTransmitter::IgnoreSome) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + + return status; } void RTPUDPv4Transmitter::ClearIgnoreList() { -if (!init) -return; + if (!init) + return; + + if (created && receivemode == RTPTransmitter::IgnoreSome) + ClearAcceptIgnoreInfo(); -MAINMUTEX_LOCK -if (created && receivemode == RTPTransmitter::IgnoreSome) -ClearAcceptIgnoreInfo(); -MAINMUTEX_UNLOCK } int RTPUDPv4Transmitter::AddToAcceptList(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} -if (receivemode != RTPTransmitter::AcceptSome) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + + return status; } int RTPUDPv4Transmitter::DeleteFromAcceptList(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} -if (receivemode != RTPTransmitter::AcceptSome) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + + return status; } void RTPUDPv4Transmitter::ClearAcceptList() { -if (!init) -return; + if (!init) + return; + + if (created && receivemode == RTPTransmitter::AcceptSome) + ClearAcceptIgnoreInfo(); -MAINMUTEX_LOCK -if (created && receivemode == RTPTransmitter::AcceptSome) -ClearAcceptIgnoreInfo(); -MAINMUTEX_UNLOCK } int RTPUDPv4Transmitter::SetMaximumPacketSize(size_t s) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (s > RTPUDPV4TRANS_MAXPACKSIZE) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; -} -maxpacksize = s; -MAINMUTEX_UNLOCK -return 0; + if (!created) + { + + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (s > RTPUDPV4TRANS_MAXPACKSIZE) + { + + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + maxpacksize = s; + + return 0; } bool RTPUDPv4Transmitter::NewDataAvailable() { -if (!init) -return false; + if (!init) + return false; -MAINMUTEX_LOCK + bool v; -bool v; + if (!created) + v = false; + else + { + if (rawpacketlist.empty()) + v = false; + else + v = true; + } -if (!created) -v = false; -else -{ -if (rawpacketlist.empty()) -v = false; -else -v = true; -} - -MAINMUTEX_UNLOCK -return v; + return v; } RTPRawPacket *RTPUDPv4Transmitter::GetNextPacket() { -if (!init) -return 0; + if (!init) + return 0; -MAINMUTEX_LOCK + RTPRawPacket *p; -RTPRawPacket *p; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return 0; -} -if (rawpacketlist.empty()) -{ -MAINMUTEX_UNLOCK -return 0; -} + return 0; + } + if (rawpacketlist.empty()) + { -p = *(rawpacketlist.begin()); -rawpacketlist.pop_front(); + return 0; + } -MAINMUTEX_UNLOCK -return p; + p = *(rawpacketlist.begin()); + rawpacketlist.pop_front(); + + return p; } // Here the private functions start... @@ -1388,372 +1317,372 @@ return p; #ifdef RTP_SUPPORT_IPV4MULTICAST bool RTPUDPv4Transmitter::SetMulticastTTL(uint8_t ttl) { -int ttl2, status; + int ttl2, status; -ttl2 = (int) ttl; -status = setsockopt(rtpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); -if (status != 0) -return false; + ttl2 = (int) ttl; + status = setsockopt(rtpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); + if (status != 0) + return false; -if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing -{ -status = setsockopt(rtcpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); -if (status != 0) -return false; -} -return true; + if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing + { + status = setsockopt(rtcpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); + if (status != 0) + return false; + } + return true; } #endif // RTP_SUPPORT_IPV4MULTICAST void RTPUDPv4Transmitter::FlushPackets() { -std::list::const_iterator it; + std::list::const_iterator it; -for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) -delete *it; -rawpacketlist.clear(); + for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) + delete *it; + rawpacketlist.clear(); } int RTPUDPv4Transmitter::PollSocket(bool rtp) { -RTPSOCKLENTYPE fromlen; -int recvlen; -char packetbuffer[RTPUDPV4TRANS_MAXPACKSIZE]; + RTPSOCKLENTYPE fromlen; + int recvlen; + char packetbuffer[RTPUDPV4TRANS_MAXPACKSIZE]; #ifdef RTP_SOCKETTYPE_WINSOCK -SOCKET sock; -unsigned long len; + SOCKET sock; + unsigned long len; #else -size_t len; -int sock; + size_t len; + int sock; #endif // RTP_SOCKETTYPE_WINSOCK -struct sockaddr_in srcaddr; -bool dataavailable; + struct sockaddr_in srcaddr; + bool dataavailable; -if (rtp) -sock = rtpsock; -else -sock = rtcpsock; + if (rtp) + sock = rtpsock; + else + sock = rtcpsock; -do -{ -len = 0; -RTPIOCTL(sock, FIONREAD, &len); + do + { + len = 0; + RTPIOCTL(sock, FIONREAD, &len); -if (len <= 0) // make sure a packet of length zero is not queued -{ - // An alternative workaround would be to just use non-blocking sockets. - // However, since the user does have access to the sockets and I do not - // know how this would affect anyone else's code, I chose to do it using - // an extra select call in case ioctl says the length is zero. + if (len <= 0) // make sure a packet of length zero is not queued + { + // An alternative workaround would be to just use non-blocking sockets. + // However, since the user does have access to the sockets and I do not + // know how this would affect anyone else's code, I chose to do it using + // an extra select call in case ioctl says the length is zero. -int8_t isset = 0; -int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); -if (status < 0) -return status; + int8_t isset = 0; + int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); + if (status < 0) + return status; -if (isset) -dataavailable = true; -else -dataavailable = false; -} -else -dataavailable = true; + if (isset) + dataavailable = true; + else + dataavailable = false; + } + else + dataavailable = true; -if (dataavailable) -{ -RTPTime curtime = RTPTime::CurrentTime(); -fromlen = sizeof(struct sockaddr_in); -recvlen = recvfrom(sock, packetbuffer, RTPUDPV4TRANS_MAXPACKSIZE, 0, (struct sockaddr *) &srcaddr, &fromlen); -if (recvlen > 0) -{ -bool acceptdata; + if (dataavailable) + { + RTPTime curtime = RTPTime::CurrentTime(); + fromlen = sizeof(struct sockaddr_in); + recvlen = recvfrom(sock, packetbuffer, RTPUDPV4TRANS_MAXPACKSIZE, 0, (struct sockaddr *) &srcaddr, &fromlen); + if (recvlen > 0) + { + bool acceptdata; - // got data, process it -if (receivemode == RTPTransmitter::AcceptAll) -acceptdata = true; -else -acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); + // got data, process it + if (receivemode == RTPTransmitter::AcceptAll) + acceptdata = true; + else + acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); -if (acceptdata) -{ -RTPRawPacket *pack; -RTPIPv4Address *addr; -uint8_t *datacopy; + if (acceptdata) + { + RTPRawPacket *pack; + RTPIPv4Address *addr; + uint8_t *datacopy; -addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); -if (addr == 0) -return ERR_RTP_OUTOFMEM; -datacopy = new uint8_t[recvlen]; -if (datacopy == 0) -{ -delete addr; -return ERR_RTP_OUTOFMEM; -} -memcpy(datacopy, packetbuffer, recvlen); + addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); + if (addr == 0) + return ERR_RTP_OUTOFMEM; + datacopy = new uint8_t[recvlen]; + if (datacopy == 0) + { + delete addr; + return ERR_RTP_OUTOFMEM; + } + memcpy(datacopy, packetbuffer, recvlen); -bool isrtp = rtp; -if (rtpsock == rtcpsock) // check payload type when multiplexing -{ -isrtp = true; + bool isrtp = rtp; + if (rtpsock == rtcpsock) // check payload type when multiplexing + { + isrtp = true; -if ((size_t) recvlen > sizeof(RTCPCommonHeader)) -{ - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; - uint8_t packettype = rtcpheader->packettype; + if ((size_t) recvlen > sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; + uint8_t packettype = rtcpheader->packettype; - if (packettype >= 200 && packettype <= 204) - isrtp = false; -} -} + if (packettype >= 200 && packettype <= 204) + isrtp = false; + } + } -pack = new RTPRawPacket(datacopy, recvlen, addr, curtime, isrtp); -if (pack == 0) -{ -delete addr; -delete[] datacopy; -return ERR_RTP_OUTOFMEM; -} -rawpacketlist.push_back(pack); -} -} -} -} while (dataavailable); + pack = new RTPRawPacket(datacopy, recvlen, addr, curtime, isrtp); + if (pack == 0) + { + delete addr; + delete[] datacopy; + return ERR_RTP_OUTOFMEM; + } + rawpacketlist.push_back(pack); + } + } + } + } while (dataavailable); -return 0; + return 0; } int RTPUDPv4Transmitter::ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port) { -acceptignoreinfo.GotoElement(ip); -if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists -{ -PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); + acceptignoreinfo.GotoElement(ip); + if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists + { + PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); -if (port == 0) // select all ports -{ -portinf->all = true; -portinf->portlist.clear(); -} -else if (!portinf->all) -{ -std::list::const_iterator it, begin, end; + if (port == 0) // select all ports + { + portinf->all = true; + portinf->portlist.clear(); + } + else if (!portinf->all) + { + std::list::const_iterator it, begin, end; -begin = portinf->portlist.begin(); -end = portinf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == port) // already in list -return 0; -} -portinf->portlist.push_front(port); -} -} -else // got to create an entry for this IP address -{ -PortInfo *portinf; -int status; + begin = portinf->portlist.begin(); + end = portinf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == port) // already in list + return 0; + } + portinf->portlist.push_front(port); + } + } + else // got to create an entry for this IP address + { + PortInfo *portinf; + int status; -portinf = new PortInfo(); -if (port == 0) // select all ports -portinf->all = true; -else -portinf->portlist.push_front(port); + portinf = new PortInfo(); + if (port == 0) // select all ports + portinf->all = true; + else + portinf->portlist.push_front(port); -status = acceptignoreinfo.AddElement(ip, portinf); -if (status < 0) -{ -delete portinf; -return status; -} -} + status = acceptignoreinfo.AddElement(ip, portinf); + if (status < 0) + { + delete portinf; + return status; + } + } -return 0; + return 0; } void RTPUDPv4Transmitter::ClearAcceptIgnoreInfo() { -acceptignoreinfo.GotoFirstElement(); -while (acceptignoreinfo.HasCurrentElement()) -{ -PortInfo *inf; + acceptignoreinfo.GotoFirstElement(); + while (acceptignoreinfo.HasCurrentElement()) + { + PortInfo *inf; -inf = acceptignoreinfo.GetCurrentElement(); -delete inf; -acceptignoreinfo.GotoNextElement(); -} -acceptignoreinfo.Clear(); + inf = acceptignoreinfo.GetCurrentElement(); + delete inf; + acceptignoreinfo.GotoNextElement(); + } + acceptignoreinfo.Clear(); } int RTPUDPv4Transmitter::ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port) { -acceptignoreinfo.GotoElement(ip); -if (!acceptignoreinfo.HasCurrentElement()) -return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + acceptignoreinfo.GotoElement(ip); + if (!acceptignoreinfo.HasCurrentElement()) + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; -PortInfo *inf; + PortInfo *inf; -inf = acceptignoreinfo.GetCurrentElement(); -if (port == 0) // delete all entries -{ -inf->all = false; -inf->portlist.clear(); -} -else // a specific port was selected -{ -if (inf->all) // currently, all ports are selected. Add the one to remove to the list -{ - // we have to check if the list doesn't contain the port already -std::list::const_iterator it, begin, end; + inf = acceptignoreinfo.GetCurrentElement(); + if (port == 0) // delete all entries + { + inf->all = false; + inf->portlist.clear(); + } + else // a specific port was selected + { + if (inf->all) // currently, all ports are selected. Add the one to remove to the list + { + // we have to check if the list doesn't contain the port already + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == port) // already in list: this means we already deleted the entry -return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; -} -inf->portlist.push_front(port); -} -else // check if we can find the port in the list -{ -std::list::iterator it, begin, end; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == port) // already in list: this means we already deleted the entry + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + } + inf->portlist.push_front(port); + } + else // check if we can find the port in the list + { + std::list::iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; ++it) -{ -if (*it == port) // found it! -{ -inf->portlist.erase(it); -return 0; -} -} - // didn't find it -return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; -} -} -return 0; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; ++it) + { + if (*it == port) // found it! + { + inf->portlist.erase(it); + return 0; + } + } + // didn't find it + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + } + } + return 0; } bool RTPUDPv4Transmitter::ShouldAcceptData(uint32_t srcip, uint16_t srcport) { -if (receivemode == RTPTransmitter::AcceptSome) -{ -PortInfo *inf; + if (receivemode == RTPTransmitter::AcceptSome) + { + PortInfo *inf; -acceptignoreinfo.GotoElement(srcip); -if (!acceptignoreinfo.HasCurrentElement()) -return false; + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return false; -inf = acceptignoreinfo.GetCurrentElement(); -if (!inf->all) // only accept the ones in the list -{ -std::list::const_iterator it, begin, end; + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // only accept the ones in the list + { + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == srcport) -return true; -} -return false; -} -else // accept all, except the ones in the list -{ -std::list::const_iterator it, begin, end; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == srcport) + return true; + } + return false; + } + else // accept all, except the ones in the list + { + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == srcport) -return false; -} -return true; -} -} -else // IgnoreSome -{ -PortInfo *inf; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == srcport) + return false; + } + return true; + } + } + else // IgnoreSome + { + PortInfo *inf; -acceptignoreinfo.GotoElement(srcip); -if (!acceptignoreinfo.HasCurrentElement()) -return true; + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return true; -inf = acceptignoreinfo.GetCurrentElement(); -if (!inf->all) // ignore the ports in the list -{ -std::list::const_iterator it, begin, end; + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // ignore the ports in the list + { + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == srcport) -return false; -} -return true; -} -else // ignore all, except the ones in the list -{ -std::list::const_iterator it, begin, end; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == srcport) + return false; + } + return true; + } + else // ignore all, except the ones in the list + { + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == srcport) -return true; -} -return false; -} -} -return true; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == srcport) + return true; + } + return false; + } + } + return true; } int RTPUDPv4Transmitter::CreateLocalIPList() { - // first try to obtain the list from the network interface info + // first try to obtain the list from the network interface info -if (!GetLocalIPList_Interfaces()) -{ - // If this fails, we'll have to depend on DNS info -GetLocalIPList_DNS(); -} -AddLoopbackAddress(); -return 0; + if (!GetLocalIPList_Interfaces()) + { + // If this fails, we'll have to depend on DNS info + GetLocalIPList_DNS(); + } + AddLoopbackAddress(); + return 0; } #ifdef RTP_SOCKETTYPE_WINSOCK bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() { -unsigned char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; -DWORD outputsize; -DWORD numaddresses,i; -SOCKET_ADDRESS_LIST *addrlist; + unsigned char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; + DWORD outputsize; + DWORD numaddresses,i; + SOCKET_ADDRESS_LIST *addrlist; -if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANS_IFREQBUFSIZE,&outputsize,NULL,NULL)) -return false; + if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANS_IFREQBUFSIZE,&outputsize,NULL,NULL)) + return false; -addrlist = (SOCKET_ADDRESS_LIST *)buffer; -numaddresses = addrlist->iAddressCount; -for (i = 0; i < numaddresses; i++) -{ -SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); -if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address -{ -struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; + addrlist = (SOCKET_ADDRESS_LIST *)buffer; + numaddresses = addrlist->iAddressCount; + for (i = 0; i < numaddresses; i++) + { + SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); + if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address + { + struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; -localIPs.push_back(ntohl(addr->sin_addr.s_addr)); -} -} + localIPs.push_back(ntohl(addr->sin_addr.s_addr)); + } + } -if (localIPs.empty()) -return false; + if (localIPs.empty()) + return false; -return true; + return true; } #else // use either getifaddrs or ioctl @@ -1762,92 +1691,92 @@ return true; bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() { -struct ifaddrs *addrs, *tmp; + struct ifaddrs *addrs, *tmp; -getifaddrs(&addrs); -tmp = addrs; + getifaddrs(&addrs); + tmp = addrs; -while (tmp != 0) -{ -if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) -{ -struct sockaddr_in *inaddr = (struct sockaddr_in *) tmp->ifa_addr; -localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); -} -tmp = tmp->ifa_next; -} + while (tmp != 0) + { + if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) + { + struct sockaddr_in *inaddr = (struct sockaddr_in *) tmp->ifa_addr; + localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); + } + tmp = tmp->ifa_next; + } -freeifaddrs(addrs); + freeifaddrs(addrs); -if (localIPs.empty()) -return false; -return true; + if (localIPs.empty()) + return false; + return true; } #else // user ioctl bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() { -int status; -char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; -struct ifconf ifc; -struct ifreq *ifr; -struct sockaddr *sa; -char *startptr,*endptr; -int remlen; + int status; + char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; + struct ifconf ifc; + struct ifreq *ifr; + struct sockaddr *sa; + char *startptr,*endptr; + int remlen; -ifc.ifc_len = RTPUDPV4TRANS_IFREQBUFSIZE; -ifc.ifc_buf = buffer; -status = ioctl(rtpsock,SIOCGIFCONF,&ifc); -if (status < 0) -return false; + ifc.ifc_len = RTPUDPV4TRANS_IFREQBUFSIZE; + ifc.ifc_buf = buffer; + status = ioctl(rtpsock,SIOCGIFCONF,&ifc); + if (status < 0) + return false; -startptr = (char *)ifc.ifc_req; -endptr = startptr + ifc.ifc_len; -remlen = ifc.ifc_len; -while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) -{ -ifr = (struct ifreq *)startptr; -sa = &(ifr->ifr_addr); + startptr = (char *)ifc.ifc_req; + endptr = startptr + ifc.ifc_len; + remlen = ifc.ifc_len; + while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) + { + ifr = (struct ifreq *)startptr; + sa = &(ifr->ifr_addr); #ifdef RTP_HAVE_SOCKADDR_LEN -if (sa->sa_len <= sizeof(struct sockaddr)) -{ -if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) -{ -uint32_t ip; -struct sockaddr_in *addr = (struct sockaddr_in *)sa; + if (sa->sa_len <= sizeof(struct sockaddr)) + { + if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; -ip = ntohl(addr->sin_addr.s_addr); -localIPs.push_back(ip); -} -remlen -= sizeof(struct ifreq); -startptr += sizeof(struct ifreq); -} -else -{ -int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); + } + else + { + int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); -remlen -= l; -startptr += l; -} + remlen -= l; + startptr += l; + } #else // don't have sa_len in struct sockaddr -if (sa->sa_family == PF_INET) -{ -uint32_t ip; -struct sockaddr_in *addr = (struct sockaddr_in *)sa; + if (sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; -ip = ntohl(addr->sin_addr.s_addr); -localIPs.push_back(ip); -} -remlen -= sizeof(struct ifreq); -startptr += sizeof(struct ifreq); + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); #endif // RTP_HAVE_SOCKADDR_LEN -} + } -if (localIPs.empty()) -return false; -return true; + if (localIPs.empty()) + return false; + return true; } #endif // RTP_SUPPORT_IFADDRS @@ -1856,49 +1785,49 @@ return true; void RTPUDPv4Transmitter::GetLocalIPList_DNS() { -struct hostent *he; -char name[1024]; -bool done; -int i, j; + struct hostent *he; + char name[1024]; + bool done; + int i, j; -gethostname(name, 1023); -name[1023] = 0; -he = gethostbyname(name); -if (he == 0) -return; + gethostname(name, 1023); + name[1023] = 0; + he = gethostbyname(name); + if (he == 0) + return; -i = 0; -done = false; -while (!done) -{ -if (he->h_addr_list[i] == NULL) -done = true; -else -{ -uint32_t ip = 0; + i = 0; + done = false; + while (!done) + { + if (he->h_addr_list[i] == NULL) + done = true; + else + { + uint32_t ip = 0; -for (j = 0; j < 4; j++) -ip |= ((uint32_t) ((unsigned char) he->h_addr_list[i][j]) << ((3 - j) * 8)); -localIPs.push_back(ip); -i++; -} -} + for (j = 0; j < 4; j++) + ip |= ((uint32_t) ((unsigned char) he->h_addr_list[i][j]) << ((3 - j) * 8)); + localIPs.push_back(ip); + i++; + } + } } void RTPUDPv4Transmitter::AddLoopbackAddress() { -uint32_t loopbackaddr = (((uint32_t) 127) << 24) | ((uint32_t) 1); -std::list::const_iterator it; -bool found = false; + uint32_t loopbackaddr = (((uint32_t) 127) << 24) | ((uint32_t) 1); + std::list::const_iterator it; + bool found = false; -for (it = localIPs.begin(); !found && it != localIPs.end(); it++) -{ -if (*it == loopbackaddr) -found = true; -} + for (it = localIPs.begin(); !found && it != localIPs.end(); it++) + { + if (*it == loopbackaddr) + found = true; + } -if (!found) -localIPs.push_back(loopbackaddr); + if (!found) + localIPs.push_back(loopbackaddr); } } // end namespace diff --git a/qrtplib/rtpudpv4transmitternobind.cpp b/qrtplib/rtpudpv4transmitternobind.cpp index d10149b88..ad4f79d86 100644 --- a/qrtplib/rtpudpv4transmitternobind.cpp +++ b/qrtplib/rtpudpv4transmitternobind.cpp @@ -59,10 +59,6 @@ using namespace std; mreq.imr_interface.s_addr = htonl(mcastifaceIP);\ status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ } -#define MAINMUTEX_LOCK -#define MAINMUTEX_UNLOCK -#define WAITMUTEX_LOCK -#define WAITMUTEX_UNLOCK #define CLOSESOCKETS do { \ if (closesocketswhendone) \ @@ -77,9 +73,8 @@ namespace qrtplib { RTPUDPv4TransmitterNoBind::RTPUDPv4TransmitterNoBind() : - init(false), created(false), waitingfordata(false), rtpsock(-1), rtcpsock(-1), mcastifaceIP(0), m_rtpPort(0), m_rtcpPort(0), multicastTTL(0), receivemode( - AcceptAll), localhostname(0), localhostnamelength(0), - supportsmulticasting(false), maxpacksize(0), closesocketswhendone(false), m_pAbortDesc(0) + init(false), created(false), waitingfordata(false), rtpsock(-1), rtcpsock(-1), mcastifaceIP(0), m_rtpPort(0), m_rtcpPort(0), multicastTTL(0), receivemode(AcceptAll), localhostname( + 0), localhostnamelength(0), supportsmulticasting(false), maxpacksize(0), closesocketswhendone(false), m_pAbortDesc(0) { } @@ -270,11 +265,9 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; - MAINMUTEX_LOCK - if (created) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ALREADYCREATED; } @@ -286,7 +279,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi { if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; } params = (const RTPUDPv4TransmissionNoBindParams *) transparams; @@ -309,7 +302,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); if (status < 0) { - MAINMUTEX_UNLOCK + return status; } } @@ -318,7 +311,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi // Check if portbase is even (if necessary) if (!params->GetAllowOddPortbase() && params->GetPortbase() % 2 != 0) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; } @@ -327,7 +320,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi rtpsock = socket(PF_INET, SOCK_DGRAM, 0); if (rtpsock == RTPSOCKERR) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; } @@ -340,7 +333,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi if (rtcpsock == RTPSOCKERR) { RTPCLOSE(rtpsock); - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; } } @@ -353,14 +346,14 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi if (setsockopt(rtpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; } size = params->GetRTPSendBuffer(); if (setsockopt(rtpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; } @@ -370,14 +363,14 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi if (setsockopt(rtcpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; } size = params->GetRTCPSendBuffer(); if (setsockopt(rtcpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; } } @@ -393,7 +386,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi if ((status = CreateLocalIPList()) < 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return status; } } @@ -410,7 +403,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi if (maximumpacketsize > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; } @@ -419,7 +412,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi if ((status = m_abortDesc.Init()) < 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return status; } m_pAbortDesc = &m_abortDesc; @@ -430,7 +423,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi if (!m_pAbortDesc->IsInitialized()) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_ABORTDESC_NOTINIT; } } @@ -445,7 +438,7 @@ int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmi waitingfordata = false; created = true; - MAINMUTEX_UNLOCK + return 0; } @@ -453,7 +446,7 @@ int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transpar { if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) { - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; } @@ -470,7 +463,7 @@ int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transpar if (bind(rtpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; } @@ -493,7 +486,7 @@ int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transpar if (bind(rtcpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) { CLOSESOCKETS; - MAINMUTEX_UNLOCK + return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; } @@ -510,10 +503,9 @@ void RTPUDPv4TransmitterNoBind::Destroy() if (!init) return; - MAINMUTEX_LOCK if (!created) { - MAINMUTEX_UNLOCK; + ; return; } @@ -538,605 +530,563 @@ void RTPUDPv4TransmitterNoBind::Destroy() { m_pAbortDesc->SendAbortSignal(); m_abortDesc.Destroy(); // Doesn't do anything if not initialized - MAINMUTEX_UNLOCK - WAITMUTEX_LOCK// to make sure that the WaitForIncomingData function ended - WAITMUTEX_UNLOCK -} -else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized -MAINMUTEX_UNLOCK + // to make sure that the WaitForIncomingData function ended + + } + else + m_abortDesc.Destroy(); // Doesn't do anything if not initialized + } RTPTransmissionInfo *RTPUDPv4TransmitterNoBind::GetTransmissionInfo() { -if (!init) -return 0; + if (!init) + return 0; -MAINMUTEX_LOCK -RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionNoBindInfo(localIPs, rtpsock, rtcpsock, m_rtpPort, m_rtcpPort); -MAINMUTEX_UNLOCK -return tinf; + RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionNoBindInfo(localIPs, rtpsock, rtcpsock, m_rtpPort, m_rtcpPort); + + return tinf; } void RTPUDPv4TransmitterNoBind::DeleteTransmissionInfo(RTPTransmissionInfo *i) { -if (!init) -return; + if (!init) + return; -delete i; + delete i; } int RTPUDPv4TransmitterNoBind::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} - -if (localhostname == 0) -{ -if (localIPs.empty()) -{ - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_NOLOCALIPS; -} - -std::list::const_iterator it; -std::list hostnames; - -for (it = localIPs.begin(); it != localIPs.end(); it++) -{ - bool founddouble = false; - bool foundentry = true; - - while (!founddouble && foundentry) + if (!created) { - struct hostent *he; - uint8_t addr[4]; - uint32_t ip = (*it); - addr[0] = (uint8_t) ((ip >> 24) & 0xFF); - addr[1] = (uint8_t) ((ip >> 16) & 0xFF); - addr[2] = (uint8_t) ((ip >> 8) & 0xFF); - addr[3] = (uint8_t) (ip & 0xFF); - he = gethostbyaddr((char *) addr, 4, AF_INET); - if (he != 0) + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (localhostname == 0) + { + if (localIPs.empty()) + { + + return ERR_RTP_UDPV4TRANS_NOLOCALIPS; + } + + std::list::const_iterator it; + std::list hostnames; + + for (it = localIPs.begin(); it != localIPs.end(); it++) + { + bool founddouble = false; + bool foundentry = true; + + while (!founddouble && foundentry) + { + struct hostent *he; + uint8_t addr[4]; + uint32_t ip = (*it); + + addr[0] = (uint8_t) ((ip >> 24) & 0xFF); + addr[1] = (uint8_t) ((ip >> 16) & 0xFF); + addr[2] = (uint8_t) ((ip >> 8) & 0xFF); + addr[3] = (uint8_t) (ip & 0xFF); + he = gethostbyaddr((char *) addr, 4, AF_INET); + if (he != 0) + { + std::string hname = std::string(he->h_name); + std::list::const_iterator it; + + for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + hostnames.push_back(hname); + + int i = 0; + while (!founddouble && he->h_aliases[i] != 0) + { + std::string hname = std::string(he->h_aliases[i]); + + for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) + if ((*it) == hname) + founddouble = true; + + if (!founddouble) + { + hostnames.push_back(hname); + i++; + } + } + } + else + foundentry = false; + } + } + + bool found = false; + + if (!hostnames.empty()) // try to select the most appropriate hostname { - std::string hname = std::string(he->h_name); std::list::const_iterator it; - for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) - hostnames.push_back(hname); - - int i = 0; - while (!founddouble && he->h_aliases[i] != 0) + hostnames.sort(); + for (it = hostnames.begin(); !found && it != hostnames.end(); it++) { - std::string hname = std::string(he->h_aliases[i]); - - for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) + if ((*it).find('.') != std::string::npos) { - hostnames.push_back(hname); - i++; + found = true; + localhostnamelength = (*it).length(); + localhostname = new uint8_t[localhostnamelength + 1]; + if (localhostname == 0) + { + + return ERR_RTP_OUTOFMEM; + } + memcpy(localhostname, (*it).c_str(), localhostnamelength); + localhostname[localhostnamelength] = 0; } } } - else - foundentry = false; - } -} -bool found = false; - -if (!hostnames.empty()) // try to select the most appropriate hostname -{ - std::list::const_iterator it; - - hostnames.sort(); - for (it = hostnames.begin(); !found && it != hostnames.end(); it++) - { - if ((*it).find('.') != std::string::npos) + if (!found) // use an IP address { - found = true; - localhostnamelength = (*it).length(); + uint32_t ip; + int len; + char str[16]; + + it = localIPs.begin(); + ip = (*it); + + RTP_SNPRINTF(str, 16, "%d.%d.%d.%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF)); + len = strlen(str); + + localhostnamelength = len; localhostname = new uint8_t[localhostnamelength + 1]; if (localhostname == 0) { - MAINMUTEX_UNLOCK + return ERR_RTP_OUTOFMEM; } - memcpy(localhostname, (*it).c_str(), localhostnamelength); + memcpy(localhostname, str, localhostnamelength); localhostname[localhostnamelength] = 0; } } -} -if (!found) // use an IP address -{ - uint32_t ip; - int len; - char str[16]; - - it = localIPs.begin(); - ip = (*it); - - RTP_SNPRINTF(str, 16, "%d.%d.%d.%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF)); - len = strlen(str); - - localhostnamelength = len; - localhostname = new uint8_t[localhostnamelength + 1]; - if (localhostname == 0) + if ((*bufferlength) < localhostnamelength) { - MAINMUTEX_UNLOCK - return ERR_RTP_OUTOFMEM; + *bufferlength = localhostnamelength; // tell the application the required size of the buffer + + return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; } - memcpy(localhostname, str, localhostnamelength); - localhostname[localhostnamelength] = 0; -} -} -if ((*bufferlength) < localhostnamelength) -{ -*bufferlength = localhostnamelength; // tell the application the required size of the buffer -MAINMUTEX_UNLOCK -return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; -} + memcpy(buffer, localhostname, localhostnamelength); + *bufferlength = localhostnamelength; -memcpy(buffer, localhostname, localhostnamelength); -*bufferlength = localhostnamelength; - -MAINMUTEX_UNLOCK -return 0; + return 0; } bool RTPUDPv4TransmitterNoBind::ComesFromThisTransmitter(const RTPAddress *addr) { -if (!init) -return false; + if (!init) + return false; -if (addr == 0) -return false; + if (addr == 0) + return false; -MAINMUTEX_LOCK + bool v; -bool v; + if (created && addr->GetAddressType() == RTPAddress::IPv4Address) + { + const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; + bool found = false; + std::list::const_iterator it; -if (created && addr->GetAddressType() == RTPAddress::IPv4Address) -{ -const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; -bool found = false; -std::list::const_iterator it; + it = localIPs.begin(); + while (!found && it != localIPs.end()) + { + if (addr2->GetIP() == *it) + found = true; + else + ++it; + } -it = localIPs.begin(); -while (!found && it != localIPs.end()) -{ - if (addr2->GetIP() == *it) - found = true; - else - ++it; -} - -if (!found) - v = false; -else -{ - if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port - v = true; + if (!found) + v = false; + else + { + if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port + v = true; + else + v = false; + } + } else v = false; -} -} -else -v = false; -MAINMUTEX_UNLOCK -return v; + return v; } int RTPUDPv4TransmitterNoBind::Poll() { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -int status; + int status; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -status = PollSocket(true); // poll RTP socket -if (rtpsock != rtcpsock) // no need to poll twice when multiplexing -{ -if (status >= 0) - status = PollSocket(false); // poll RTCP socket -} -MAINMUTEX_UNLOCK -return status; + if (!created) + { + + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + status = PollSocket(true); // poll RTP socket + if (rtpsock != rtcpsock) // no need to poll twice when multiplexing + { + if (status >= 0) + status = PollSocket(false); // poll RTCP socket + } + + return status; } int RTPUDPv4TransmitterNoBind::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (waitingfordata) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_ALREADYWAITING; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (waitingfordata) + { -SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); + return ERR_RTP_UDPV4TRANS_ALREADYWAITING; + } -SocketType socks[3] = -{ rtpsock, rtcpsock, abortSocket }; -int8_t readflags[3] = -{ 0, 0, 0 }; -const int idxRTP = 0; -const int idxRTCP = 1; -const int idxAbort = 2; + SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); -waitingfordata = true; + SocketType socks[3] = + { rtpsock, rtcpsock, abortSocket }; + int8_t readflags[3] = + { 0, 0, 0 }; + const int idxRTP = 0; + const int idxRTCP = 1; + const int idxAbort = 2; -WAITMUTEX_LOCK -MAINMUTEX_UNLOCK + waitingfordata = true; -int status = RTPSelect(socks, readflags, 3, delay); -if (status < 0) -{ -MAINMUTEX_LOCK -waitingfordata = false; -MAINMUTEX_UNLOCK -WAITMUTEX_UNLOCK -return status; -} + int status = RTPSelect(socks, readflags, 3, delay); + if (status < 0) + { + waitingfordata = false; -MAINMUTEX_LOCK -waitingfordata = false; -if (!created) // destroy called -{ -MAINMUTEX_UNLOCK; -WAITMUTEX_UNLOCK -return 0; -} + return status; + } - // if aborted, read from abort buffer -if (readflags[idxAbort]) -m_pAbortDesc->ReadSignallingByte(); + waitingfordata = false; + if (!created) // destroy called + { + ; -if (dataavailable != 0) -{ -if (readflags[idxRTP] || readflags[idxRTCP]) - *dataavailable = true; -else - *dataavailable = false; -} + return 0; + } -MAINMUTEX_UNLOCK -WAITMUTEX_UNLOCK -return 0; + // if aborted, read from abort buffer + if (readflags[idxAbort]) + m_pAbortDesc->ReadSignallingByte(); + + if (dataavailable != 0) + { + if (readflags[idxRTP] || readflags[idxRTCP]) + *dataavailable = true; + else + *dataavailable = false; + } + + return 0; } int RTPUDPv4TransmitterNoBind::AbortWait() { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (!waitingfordata) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTWAITING; -} + if (!created) + { -m_pAbortDesc->SendAbortSignal(); + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (!waitingfordata) + { -MAINMUTEX_UNLOCK -return 0; + return ERR_RTP_UDPV4TRANS_NOTWAITING; + } + + m_pAbortDesc->SendAbortSignal(); + + return 0; } int RTPUDPv4TransmitterNoBind::SendRTPData(const void *data, size_t len) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (len > maxpacksize) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (len > maxpacksize) + { -destinations.GotoFirstElement(); -while (destinations.HasCurrentElement()) -{ -sendto(rtpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTPSockAddr(), sizeof(struct sockaddr_in)); -destinations.GotoNextElement(); -} + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } -MAINMUTEX_UNLOCK -return 0; + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTPSockAddr(), sizeof(struct sockaddr_in)); + destinations.GotoNextElement(); + } + + return 0; } int RTPUDPv4TransmitterNoBind::SendRTCPData(const void *data, size_t len) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (len > maxpacksize) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (len > maxpacksize) + { -destinations.GotoFirstElement(); -while (destinations.HasCurrentElement()) -{ -sendto(rtcpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTCPSockAddr(), sizeof(struct sockaddr_in)); -destinations.GotoNextElement(); -} + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } -MAINMUTEX_UNLOCK -return 0; + destinations.GotoFirstElement(); + while (destinations.HasCurrentElement()) + { + sendto(rtcpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTCPSockAddr(), sizeof(struct sockaddr_in)); + destinations.GotoNextElement(); + } + + return 0; } int RTPUDPv4TransmitterNoBind::AddDestination(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } -RTPIPv4Destination dest; -if (!RTPIPv4Destination::AddressToDestination(addr, dest)) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { -int status = destinations.AddElement(dest); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } -MAINMUTEX_UNLOCK -return status; + int status = destinations.AddElement(dest); + + return status; } int RTPUDPv4TransmitterNoBind::DeleteDestination(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -RTPIPv4Destination dest; -if (!RTPIPv4Destination::AddressToDestination(addr, dest)) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + RTPIPv4Destination dest; + if (!RTPIPv4Destination::AddressToDestination(addr, dest)) + { -int status = destinations.DeleteElement(dest); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } -MAINMUTEX_UNLOCK -return status; + int status = destinations.DeleteElement(dest); + + return status; } void RTPUDPv4TransmitterNoBind::ClearDestinations() { -if (!init) -return; + if (!init) + return; + + if (created) + destinations.Clear(); -MAINMUTEX_LOCK -if (created) -destinations.Clear(); -MAINMUTEX_UNLOCK } bool RTPUDPv4TransmitterNoBind::SupportsMulticasting() { -if (!init) -return false; + if (!init) + return false; -MAINMUTEX_LOCK + bool v; -bool v; + if (!created) + v = false; + else + v = supportsmulticasting; -if (!created) -v = false; -else -v = supportsmulticasting; - -MAINMUTEX_UNLOCK -return v; + return v; } #ifdef RTP_SUPPORT_IPV4MULTICAST int RTPUDPv4TransmitterNoBind::JoinMulticastGroup(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -uint32_t mcastIP = address.GetIP(); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } -if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; -} + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + uint32_t mcastIP = address.GetIP(); -status = multicastgroups.AddElement(mcastIP); -if (status >= 0) -{ -RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_ADD_MEMBERSHIP, mcastIP, status); -if (status != 0) -{ -multicastgroups.DeleteElement(mcastIP); -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; -} + if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) + { -if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing -{ -RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_ADD_MEMBERSHIP, mcastIP, status); -if (status != 0) -{ - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - multicastgroups.DeleteElement(mcastIP); - MAINMUTEX_UNLOCK - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; -} -} -} -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; + } + + status = multicastgroups.AddElement(mcastIP); + if (status >= 0) + { + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_ADD_MEMBERSHIP, mcastIP, status); + if (status != 0) + { + multicastgroups.DeleteElement(mcastIP); + + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + + if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing + { + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_ADD_MEMBERSHIP, mcastIP, status); + if (status != 0) + { + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + multicastgroups.DeleteElement(mcastIP); + + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + } + } + + return status; } int RTPUDPv4TransmitterNoBind::LeaveMulticastGroup(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -uint32_t mcastIP = address.GetIP(); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } -if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; -} + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + uint32_t mcastIP = address.GetIP(); -status = multicastgroups.DeleteElement(mcastIP); -if (status >= 0) -{ -RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); -if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing -RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) + { -status = 0; -} + return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; + } -MAINMUTEX_UNLOCK -return status; + status = multicastgroups.DeleteElement(mcastIP); + if (status >= 0) + { + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + + status = 0; + } + + return status; } void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() { -if (!init) -return; + if (!init) + return; -MAINMUTEX_LOCK -if (created) -{ -multicastgroups.GotoFirstElement(); -while (multicastgroups.HasCurrentElement()) -{ -uint32_t mcastIP; -int status __attribute__((unused)) = 0; + if (created) + { + multicastgroups.GotoFirstElement(); + while (multicastgroups.HasCurrentElement()) + { + uint32_t mcastIP; + int status __attribute__((unused)) = 0; -mcastIP = multicastgroups.GetCurrentElement(); + mcastIP = multicastgroups.GetCurrentElement(); -RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); -if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing + RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); + + multicastgroups.GotoNextElement(); + } + multicastgroups.Clear(); + } -multicastgroups.GotoNextElement(); -} -multicastgroups.Clear(); -} -MAINMUTEX_UNLOCK } #else // no multicast support int RTPUDPv4TransmitterNoBind::JoinMulticastGroup(const RTPAddress &addr) { -return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; + return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; } int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) { -return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; + return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; } void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() @@ -1147,243 +1097,221 @@ void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() int RTPUDPv4TransmitterNoBind::SetReceiveMode(RTPTransmitter::ReceiveMode m) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (m != receivemode) -{ -receivemode = m; -acceptignoreinfo.Clear(); -} -MAINMUTEX_UNLOCK -return 0; + if (!created) + { + + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (m != receivemode) + { + receivemode = m; + acceptignoreinfo.Clear(); + } + + return 0; } int RTPUDPv4TransmitterNoBind::AddToIgnoreList(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} -if (receivemode != RTPTransmitter::IgnoreSome) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + + return status; } int RTPUDPv4TransmitterNoBind::DeleteFromIgnoreList(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} -if (receivemode != RTPTransmitter::IgnoreSome) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::IgnoreSome) + { -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + + return status; } void RTPUDPv4TransmitterNoBind::ClearIgnoreList() { -if (!init) -return; + if (!init) + return; + + if (created && receivemode == RTPTransmitter::IgnoreSome) + ClearAcceptIgnoreInfo(); -MAINMUTEX_LOCK -if (created && receivemode == RTPTransmitter::IgnoreSome) -ClearAcceptIgnoreInfo(); -MAINMUTEX_UNLOCK } int RTPUDPv4TransmitterNoBind::AddToAcceptList(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} -if (receivemode != RTPTransmitter::AcceptSome) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + + return status; } int RTPUDPv4TransmitterNoBind::DeleteFromAcceptList(const RTPAddress &addr) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK + int status; -int status; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (addr.GetAddressType() != RTPAddress::IPv4Address) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; -} -if (receivemode != RTPTransmitter::AcceptSome) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; -} + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (addr.GetAddressType() != RTPAddress::IPv4Address) + { -const RTPIPv4Address &address = (const RTPIPv4Address &) addr; -status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; + } + if (receivemode != RTPTransmitter::AcceptSome) + { -MAINMUTEX_UNLOCK -return status; + return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; + } + + const RTPIPv4Address &address = (const RTPIPv4Address &) addr; + status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); + + return status; } void RTPUDPv4TransmitterNoBind::ClearAcceptList() { -if (!init) -return; + if (!init) + return; + + if (created && receivemode == RTPTransmitter::AcceptSome) + ClearAcceptIgnoreInfo(); -MAINMUTEX_LOCK -if (created && receivemode == RTPTransmitter::AcceptSome) -ClearAcceptIgnoreInfo(); -MAINMUTEX_UNLOCK } int RTPUDPv4TransmitterNoBind::SetMaximumPacketSize(size_t s) { -if (!init) -return ERR_RTP_UDPV4TRANS_NOTINIT; + if (!init) + return ERR_RTP_UDPV4TRANS_NOTINIT; -MAINMUTEX_LOCK -if (!created) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_NOTCREATED; -} -if (s > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) -{ -MAINMUTEX_UNLOCK -return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; -} -maxpacksize = s; -MAINMUTEX_UNLOCK -return 0; + if (!created) + { + + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + if (s > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) + { + + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + maxpacksize = s; + + return 0; } bool RTPUDPv4TransmitterNoBind::NewDataAvailable() { -if (!init) -return false; + if (!init) + return false; -MAINMUTEX_LOCK + bool v; -bool v; + if (!created) + v = false; + else + { + if (rawpacketlist.empty()) + v = false; + else + v = true; + } -if (!created) -v = false; -else -{ -if (rawpacketlist.empty()) -v = false; -else -v = true; -} - -MAINMUTEX_UNLOCK -return v; + return v; } RTPRawPacket *RTPUDPv4TransmitterNoBind::GetNextPacket() { -if (!init) -return 0; + if (!init) + return 0; -MAINMUTEX_LOCK + RTPRawPacket *p; -RTPRawPacket *p; + if (!created) + { -if (!created) -{ -MAINMUTEX_UNLOCK -return 0; -} -if (rawpacketlist.empty()) -{ -MAINMUTEX_UNLOCK -return 0; -} + return 0; + } + if (rawpacketlist.empty()) + { -p = *(rawpacketlist.begin()); -rawpacketlist.pop_front(); + return 0; + } -MAINMUTEX_UNLOCK -return p; + p = *(rawpacketlist.begin()); + rawpacketlist.pop_front(); + + return p; } // Here the private functions start... @@ -1391,372 +1319,372 @@ return p; #ifdef RTP_SUPPORT_IPV4MULTICAST bool RTPUDPv4TransmitterNoBind::SetMulticastTTL(uint8_t ttl) { -int ttl2, status; + int ttl2, status; -ttl2 = (int) ttl; -status = setsockopt(rtpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); -if (status != 0) -return false; + ttl2 = (int) ttl; + status = setsockopt(rtpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); + if (status != 0) + return false; -if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing -{ -status = setsockopt(rtcpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); -if (status != 0) -return false; -} -return true; + if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing + { + status = setsockopt(rtcpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); + if (status != 0) + return false; + } + return true; } #endif // RTP_SUPPORT_IPV4MULTICAST void RTPUDPv4TransmitterNoBind::FlushPackets() { -std::list::const_iterator it; + std::list::const_iterator it; -for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) -delete *it; -rawpacketlist.clear(); + for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) + delete *it; + rawpacketlist.clear(); } int RTPUDPv4TransmitterNoBind::PollSocket(bool rtp) { -RTPSOCKLENTYPE fromlen; -int recvlen; -char packetbuffer[RTPUDPV4TRANSNOBIND_MAXPACKSIZE]; + RTPSOCKLENTYPE fromlen; + int recvlen; + char packetbuffer[RTPUDPV4TRANSNOBIND_MAXPACKSIZE]; #ifdef RTP_SOCKETTYPE_WINSOCK -SOCKET sock; -unsigned long len; + SOCKET sock; + unsigned long len; #else -size_t len; -int sock; + size_t len; + int sock; #endif // RTP_SOCKETTYPE_WINSOCK -struct sockaddr_in srcaddr; -bool dataavailable; + struct sockaddr_in srcaddr; + bool dataavailable; -if (rtp) -sock = rtpsock; -else -sock = rtcpsock; + if (rtp) + sock = rtpsock; + else + sock = rtcpsock; -do -{ -len = 0; -RTPIOCTL(sock, FIONREAD, &len); + do + { + len = 0; + RTPIOCTL(sock, FIONREAD, &len); -if (len <= 0) // make sure a packet of length zero is not queued -{ - // An alternative workaround would be to just use non-blocking sockets. - // However, since the user does have access to the sockets and I do not - // know how this would affect anyone else's code, I chose to do it using - // an extra select call in case ioctl says the length is zero. + if (len <= 0) // make sure a packet of length zero is not queued + { + // An alternative workaround would be to just use non-blocking sockets. + // However, since the user does have access to the sockets and I do not + // know how this would affect anyone else's code, I chose to do it using + // an extra select call in case ioctl says the length is zero. -int8_t isset = 0; -int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); -if (status < 0) -return status; + int8_t isset = 0; + int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); + if (status < 0) + return status; -if (isset) -dataavailable = true; -else -dataavailable = false; -} -else -dataavailable = true; + if (isset) + dataavailable = true; + else + dataavailable = false; + } + else + dataavailable = true; -if (dataavailable) -{ -RTPTime curtime = RTPTime::CurrentTime(); -fromlen = sizeof(struct sockaddr_in); -recvlen = recvfrom(sock, packetbuffer, RTPUDPV4TRANSNOBIND_MAXPACKSIZE, 0, (struct sockaddr *) &srcaddr, &fromlen); -if (recvlen > 0) -{ -bool acceptdata; + if (dataavailable) + { + RTPTime curtime = RTPTime::CurrentTime(); + fromlen = sizeof(struct sockaddr_in); + recvlen = recvfrom(sock, packetbuffer, RTPUDPV4TRANSNOBIND_MAXPACKSIZE, 0, (struct sockaddr *) &srcaddr, &fromlen); + if (recvlen > 0) + { + bool acceptdata; - // got data, process it -if (receivemode == RTPTransmitter::AcceptAll) -acceptdata = true; -else -acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); + // got data, process it + if (receivemode == RTPTransmitter::AcceptAll) + acceptdata = true; + else + acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); -if (acceptdata) -{ -RTPRawPacket *pack; -RTPIPv4Address *addr; -uint8_t *datacopy; + if (acceptdata) + { + RTPRawPacket *pack; + RTPIPv4Address *addr; + uint8_t *datacopy; -addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); -if (addr == 0) -return ERR_RTP_OUTOFMEM; -datacopy = new uint8_t[recvlen]; -if (datacopy == 0) -{ -delete addr; -return ERR_RTP_OUTOFMEM; -} -memcpy(datacopy, packetbuffer, recvlen); + addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); + if (addr == 0) + return ERR_RTP_OUTOFMEM; + datacopy = new uint8_t[recvlen]; + if (datacopy == 0) + { + delete addr; + return ERR_RTP_OUTOFMEM; + } + memcpy(datacopy, packetbuffer, recvlen); -bool isrtp = rtp; -if (rtpsock == rtcpsock) // check payload type when multiplexing -{ -isrtp = true; + bool isrtp = rtp; + if (rtpsock == rtcpsock) // check payload type when multiplexing + { + isrtp = true; -if ((size_t) recvlen > sizeof(RTCPCommonHeader)) -{ - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; - uint8_t packettype = rtcpheader->packettype; + if ((size_t) recvlen > sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; + uint8_t packettype = rtcpheader->packettype; - if (packettype >= 200 && packettype <= 204) - isrtp = false; -} -} + if (packettype >= 200 && packettype <= 204) + isrtp = false; + } + } -pack = new RTPRawPacket(datacopy, recvlen, addr, curtime, isrtp); -if (pack == 0) -{ -delete addr; -delete[] datacopy; -return ERR_RTP_OUTOFMEM; -} -rawpacketlist.push_back(pack); -} -} -} -} while (dataavailable); + pack = new RTPRawPacket(datacopy, recvlen, addr, curtime, isrtp); + if (pack == 0) + { + delete addr; + delete[] datacopy; + return ERR_RTP_OUTOFMEM; + } + rawpacketlist.push_back(pack); + } + } + } + } while (dataavailable); -return 0; + return 0; } int RTPUDPv4TransmitterNoBind::ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port) { -acceptignoreinfo.GotoElement(ip); -if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists -{ -PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); + acceptignoreinfo.GotoElement(ip); + if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists + { + PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); -if (port == 0) // select all ports -{ -portinf->all = true; -portinf->portlist.clear(); -} -else if (!portinf->all) -{ -std::list::const_iterator it, begin, end; + if (port == 0) // select all ports + { + portinf->all = true; + portinf->portlist.clear(); + } + else if (!portinf->all) + { + std::list::const_iterator it, begin, end; -begin = portinf->portlist.begin(); -end = portinf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == port) // already in list -return 0; -} -portinf->portlist.push_front(port); -} -} -else // got to create an entry for this IP address -{ -PortInfo *portinf; -int status; + begin = portinf->portlist.begin(); + end = portinf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == port) // already in list + return 0; + } + portinf->portlist.push_front(port); + } + } + else // got to create an entry for this IP address + { + PortInfo *portinf; + int status; -portinf = new PortInfo(); -if (port == 0) // select all ports -portinf->all = true; -else -portinf->portlist.push_front(port); + portinf = new PortInfo(); + if (port == 0) // select all ports + portinf->all = true; + else + portinf->portlist.push_front(port); -status = acceptignoreinfo.AddElement(ip, portinf); -if (status < 0) -{ -delete portinf; -return status; -} -} + status = acceptignoreinfo.AddElement(ip, portinf); + if (status < 0) + { + delete portinf; + return status; + } + } -return 0; + return 0; } void RTPUDPv4TransmitterNoBind::ClearAcceptIgnoreInfo() { -acceptignoreinfo.GotoFirstElement(); -while (acceptignoreinfo.HasCurrentElement()) -{ -PortInfo *inf; + acceptignoreinfo.GotoFirstElement(); + while (acceptignoreinfo.HasCurrentElement()) + { + PortInfo *inf; -inf = acceptignoreinfo.GetCurrentElement(); -delete inf; -acceptignoreinfo.GotoNextElement(); -} -acceptignoreinfo.Clear(); + inf = acceptignoreinfo.GetCurrentElement(); + delete inf; + acceptignoreinfo.GotoNextElement(); + } + acceptignoreinfo.Clear(); } int RTPUDPv4TransmitterNoBind::ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port) { -acceptignoreinfo.GotoElement(ip); -if (!acceptignoreinfo.HasCurrentElement()) -return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + acceptignoreinfo.GotoElement(ip); + if (!acceptignoreinfo.HasCurrentElement()) + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; -PortInfo *inf; + PortInfo *inf; -inf = acceptignoreinfo.GetCurrentElement(); -if (port == 0) // delete all entries -{ -inf->all = false; -inf->portlist.clear(); -} -else // a specific port was selected -{ -if (inf->all) // currently, all ports are selected. Add the one to remove to the list -{ - // we have to check if the list doesn't contain the port already -std::list::const_iterator it, begin, end; + inf = acceptignoreinfo.GetCurrentElement(); + if (port == 0) // delete all entries + { + inf->all = false; + inf->portlist.clear(); + } + else // a specific port was selected + { + if (inf->all) // currently, all ports are selected. Add the one to remove to the list + { + // we have to check if the list doesn't contain the port already + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == port) // already in list: this means we already deleted the entry -return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; -} -inf->portlist.push_front(port); -} -else // check if we can find the port in the list -{ -std::list::iterator it, begin, end; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == port) // already in list: this means we already deleted the entry + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + } + inf->portlist.push_front(port); + } + else // check if we can find the port in the list + { + std::list::iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; ++it) -{ -if (*it == port) // found it! -{ -inf->portlist.erase(it); -return 0; -} -} - // didn't find it -return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; -} -} -return 0; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; ++it) + { + if (*it == port) // found it! + { + inf->portlist.erase(it); + return 0; + } + } + // didn't find it + return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; + } + } + return 0; } bool RTPUDPv4TransmitterNoBind::ShouldAcceptData(uint32_t srcip, uint16_t srcport) { -if (receivemode == RTPTransmitter::AcceptSome) -{ -PortInfo *inf; + if (receivemode == RTPTransmitter::AcceptSome) + { + PortInfo *inf; -acceptignoreinfo.GotoElement(srcip); -if (!acceptignoreinfo.HasCurrentElement()) -return false; + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return false; -inf = acceptignoreinfo.GetCurrentElement(); -if (!inf->all) // only accept the ones in the list -{ -std::list::const_iterator it, begin, end; + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // only accept the ones in the list + { + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == srcport) -return true; -} -return false; -} -else // accept all, except the ones in the list -{ -std::list::const_iterator it, begin, end; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == srcport) + return true; + } + return false; + } + else // accept all, except the ones in the list + { + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == srcport) -return false; -} -return true; -} -} -else // IgnoreSome -{ -PortInfo *inf; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == srcport) + return false; + } + return true; + } + } + else // IgnoreSome + { + PortInfo *inf; -acceptignoreinfo.GotoElement(srcip); -if (!acceptignoreinfo.HasCurrentElement()) -return true; + acceptignoreinfo.GotoElement(srcip); + if (!acceptignoreinfo.HasCurrentElement()) + return true; -inf = acceptignoreinfo.GetCurrentElement(); -if (!inf->all) // ignore the ports in the list -{ -std::list::const_iterator it, begin, end; + inf = acceptignoreinfo.GetCurrentElement(); + if (!inf->all) // ignore the ports in the list + { + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == srcport) -return false; -} -return true; -} -else // ignore all, except the ones in the list -{ -std::list::const_iterator it, begin, end; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == srcport) + return false; + } + return true; + } + else // ignore all, except the ones in the list + { + std::list::const_iterator it, begin, end; -begin = inf->portlist.begin(); -end = inf->portlist.end(); -for (it = begin; it != end; it++) -{ -if (*it == srcport) -return true; -} -return false; -} -} -return true; + begin = inf->portlist.begin(); + end = inf->portlist.end(); + for (it = begin; it != end; it++) + { + if (*it == srcport) + return true; + } + return false; + } + } + return true; } int RTPUDPv4TransmitterNoBind::CreateLocalIPList() { - // first try to obtain the list from the network interface info + // first try to obtain the list from the network interface info -if (!GetLocalIPList_Interfaces()) -{ - // If this fails, we'll have to depend on DNS info -GetLocalIPList_DNS(); -} -AddLoopbackAddress(); -return 0; + if (!GetLocalIPList_Interfaces()) + { + // If this fails, we'll have to depend on DNS info + GetLocalIPList_DNS(); + } + AddLoopbackAddress(); + return 0; } #ifdef RTP_SOCKETTYPE_WINSOCK bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() { -unsigned char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; -DWORD outputsize; -DWORD numaddresses,i; -SOCKET_ADDRESS_LIST *addrlist; + unsigned char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; + DWORD outputsize; + DWORD numaddresses,i; + SOCKET_ADDRESS_LIST *addrlist; -if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANSNOBIND_IFREQBUFSIZE,&outputsize,NULL,NULL)) -return false; + if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANSNOBIND_IFREQBUFSIZE,&outputsize,NULL,NULL)) + return false; -addrlist = (SOCKET_ADDRESS_LIST *)buffer; -numaddresses = addrlist->iAddressCount; -for (i = 0; i < numaddresses; i++) -{ -SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); -if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address -{ -struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; + addrlist = (SOCKET_ADDRESS_LIST *)buffer; + numaddresses = addrlist->iAddressCount; + for (i = 0; i < numaddresses; i++) + { + SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); + if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address + { + struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; -localIPs.push_back(ntohl(addr->sin_addr.s_addr)); -} -} + localIPs.push_back(ntohl(addr->sin_addr.s_addr)); + } + } -if (localIPs.empty()) -return false; + if (localIPs.empty()) + return false; -return true; + return true; } #else // use either getifaddrs or ioctl @@ -1765,92 +1693,92 @@ return true; bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() { -struct ifaddrs *addrs, *tmp; + struct ifaddrs *addrs, *tmp; -getifaddrs(&addrs); -tmp = addrs; + getifaddrs(&addrs); + tmp = addrs; -while (tmp != 0) -{ -if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) -{ -struct sockaddr_in *inaddr = (struct sockaddr_in *) tmp->ifa_addr; -localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); -} -tmp = tmp->ifa_next; -} + while (tmp != 0) + { + if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) + { + struct sockaddr_in *inaddr = (struct sockaddr_in *) tmp->ifa_addr; + localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); + } + tmp = tmp->ifa_next; + } -freeifaddrs(addrs); + freeifaddrs(addrs); -if (localIPs.empty()) -return false; -return true; + if (localIPs.empty()) + return false; + return true; } #else // user ioctl bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() { -int status; -char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; -struct ifconf ifc; -struct ifreq *ifr; -struct sockaddr *sa; -char *startptr,*endptr; -int remlen; + int status; + char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; + struct ifconf ifc; + struct ifreq *ifr; + struct sockaddr *sa; + char *startptr,*endptr; + int remlen; -ifc.ifc_len = RTPUDPV4TRANSNOBIND_IFREQBUFSIZE; -ifc.ifc_buf = buffer; -status = ioctl(rtpsock,SIOCGIFCONF,&ifc); -if (status < 0) -return false; + ifc.ifc_len = RTPUDPV4TRANSNOBIND_IFREQBUFSIZE; + ifc.ifc_buf = buffer; + status = ioctl(rtpsock,SIOCGIFCONF,&ifc); + if (status < 0) + return false; -startptr = (char *)ifc.ifc_req; -endptr = startptr + ifc.ifc_len; -remlen = ifc.ifc_len; -while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) -{ -ifr = (struct ifreq *)startptr; -sa = &(ifr->ifr_addr); + startptr = (char *)ifc.ifc_req; + endptr = startptr + ifc.ifc_len; + remlen = ifc.ifc_len; + while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) + { + ifr = (struct ifreq *)startptr; + sa = &(ifr->ifr_addr); #ifdef RTP_HAVE_SOCKADDR_LEN -if (sa->sa_len <= sizeof(struct sockaddr)) -{ -if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) -{ -uint32_t ip; -struct sockaddr_in *addr = (struct sockaddr_in *)sa; + if (sa->sa_len <= sizeof(struct sockaddr)) + { + if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; -ip = ntohl(addr->sin_addr.s_addr); -localIPs.push_back(ip); -} -remlen -= sizeof(struct ifreq); -startptr += sizeof(struct ifreq); -} -else -{ -int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); + } + else + { + int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); -remlen -= l; -startptr += l; -} + remlen -= l; + startptr += l; + } #else // don't have sa_len in struct sockaddr -if (sa->sa_family == PF_INET) -{ -uint32_t ip; -struct sockaddr_in *addr = (struct sockaddr_in *)sa; + if (sa->sa_family == PF_INET) + { + uint32_t ip; + struct sockaddr_in *addr = (struct sockaddr_in *)sa; -ip = ntohl(addr->sin_addr.s_addr); -localIPs.push_back(ip); -} -remlen -= sizeof(struct ifreq); -startptr += sizeof(struct ifreq); + ip = ntohl(addr->sin_addr.s_addr); + localIPs.push_back(ip); + } + remlen -= sizeof(struct ifreq); + startptr += sizeof(struct ifreq); #endif // RTP_HAVE_SOCKADDR_LEN -} + } -if (localIPs.empty()) -return false; -return true; + if (localIPs.empty()) + return false; + return true; } #endif // RTP_SUPPORT_IFADDRS @@ -1859,49 +1787,49 @@ return true; void RTPUDPv4TransmitterNoBind::GetLocalIPList_DNS() { -struct hostent *he; -char name[1024]; -bool done; -int i, j; + struct hostent *he; + char name[1024]; + bool done; + int i, j; -gethostname(name, 1023); -name[1023] = 0; -he = gethostbyname(name); -if (he == 0) -return; + gethostname(name, 1023); + name[1023] = 0; + he = gethostbyname(name); + if (he == 0) + return; -i = 0; -done = false; -while (!done) -{ -if (he->h_addr_list[i] == NULL) -done = true; -else -{ -uint32_t ip = 0; + i = 0; + done = false; + while (!done) + { + if (he->h_addr_list[i] == NULL) + done = true; + else + { + uint32_t ip = 0; -for (j = 0; j < 4; j++) -ip |= ((uint32_t) ((unsigned char) he->h_addr_list[i][j]) << ((3 - j) * 8)); -localIPs.push_back(ip); -i++; -} -} + for (j = 0; j < 4; j++) + ip |= ((uint32_t) ((unsigned char) he->h_addr_list[i][j]) << ((3 - j) * 8)); + localIPs.push_back(ip); + i++; + } + } } void RTPUDPv4TransmitterNoBind::AddLoopbackAddress() { -uint32_t loopbackaddr = (((uint32_t) 127) << 24) | ((uint32_t) 1); -std::list::const_iterator it; -bool found = false; + uint32_t loopbackaddr = (((uint32_t) 127) << 24) | ((uint32_t) 1); + std::list::const_iterator it; + bool found = false; -for (it = localIPs.begin(); !found && it != localIPs.end(); it++) -{ -if (*it == loopbackaddr) -found = true; -} + for (it = localIPs.begin(); !found && it != localIPs.end(); it++) + { + if (*it == loopbackaddr) + found = true; + } -if (!found) -localIPs.push_back(loopbackaddr); + if (!found) + localIPs.push_back(loopbackaddr); } } // end namespace From dbbdc851c664de9140c64f56eae13d1b9c52514a Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 28 Feb 2018 01:09:57 +0100 Subject: [PATCH 030/956] qrtplib: removed transmitters and addresses implementations --- qrtplib/CMakeLists.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 594272c2d..b9e1f9a39 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -21,8 +21,8 @@ set (qrtplib_HEADERS rtperrors.h rtphashtable.h rtpinternalsourcedata.h - rtpipv4address.h - rtpipv4destination.h +# rtpipv4address.h +# rtpipv4destination.h rtpkeyhashtable.h rtplibraryversion.h rtppacket.h @@ -42,14 +42,14 @@ set (qrtplib_HEADERS rtptransmitter.h rtptypes_win.h rtptypes.h - rtpudpv4transmitter.h - rtpudpv4transmitternobind.h - rtpexternaltransmitter.h +# rtpudpv4transmitter.h +# rtpudpv4transmitternobind.h +# rtpexternaltransmitter.h rtpsocketutil.h rtpabortdescriptors.h rtpselect.h - rtptcpaddress.h - rtptcptransmitter.h +# rtptcpaddress.h +# rtptcptransmitter.h ) set(qrtplib_SOURCES @@ -66,8 +66,8 @@ set(qrtplib_SOURCES rtpcollisionlist.cpp rtperrors.cpp rtpinternalsourcedata.cpp - rtpipv4address.cpp - rtpipv4destination.cpp +# rtpipv4address.cpp +# rtpipv4destination.cpp rtplibraryversion.cpp rtppacket.cpp rtppacketbuilder.cpp @@ -81,12 +81,12 @@ set(qrtplib_SOURCES rtpsourcedata.cpp rtpsources.cpp rtptimeutilities.cpp - rtpudpv4transmitter.cpp - rtpudpv4transmitternobind.cpp - rtpexternaltransmitter.cpp +# rtpudpv4transmitter.cpp +# rtpudpv4transmitternobind.cpp +# rtpexternaltransmitter.cpp rtpabortdescriptors.cpp - rtptcpaddress.cpp - rtptcptransmitter.cpp +# rtptcpaddress.cpp +# rtptcptransmitter.cpp ) include_directories( From a0c7381047e408c76c1921f34ba46a10901137c3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 28 Feb 2018 01:37:47 +0100 Subject: [PATCH 031/956] qrtplib: implemented Qt style RTP address object --- qrtplib/CMakeLists.txt | 1 + qrtplib/rtpaddress.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++ qrtplib/rtpaddress.h | 68 ++++++++++++++++++++++++----------- qrtplib/rtpsession.cpp | 36 ++++++++++--------- 4 files changed, 147 insertions(+), 38 deletions(-) create mode 100644 qrtplib/rtpaddress.cpp diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index b9e1f9a39..38e1502f2 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -63,6 +63,7 @@ set(qrtplib_SOURCES rtcpsdesinfo.cpp rtcpsdespacket.cpp rtcpsrpacket.cpp + rtpaddress.cpp rtpcollisionlist.cpp rtperrors.cpp rtpinternalsourcedata.cpp diff --git a/qrtplib/rtpaddress.cpp b/qrtplib/rtpaddress.cpp new file mode 100644 index 000000000..4fe56a96b --- /dev/null +++ b/qrtplib/rtpaddress.cpp @@ -0,0 +1,80 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpaddress.h" + +namespace qrtplib +{ + +RTPAddress *RTPAddress::CreateCopy() const +{ + RTPAddress *a = new RTPAddress(); + a->address = address; + a->port = port; + a->rtcpsendport = rtcpsendport; + return a; +} + +bool RTPAddress::IsSameAddress(const RTPAddress *addr) const +{ + if (addr == 0) { + return false; + } + + if (addr->address.protocol() != address.protocol()) { + return false; + } + + if (addr->address == address) + { + return addr->port == port; + } + else + { + return false; + } +} + +bool RTPAddress::IsFromSameHost(const RTPAddress *addr) const +{ + if (addr == 0) { + return false; + } + + if (addr->address.protocol() != address.protocol()) { + return false; + } + + return addr->address == address; +} + +} // namespace diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h index e9d9d9f9d..68f07bd80 100644 --- a/qrtplib/rtpaddress.h +++ b/qrtplib/rtpaddress.h @@ -40,6 +40,8 @@ #include "rtpconfig.h" #include +#include +#include namespace qrtplib { @@ -48,20 +50,10 @@ namespace qrtplib class RTPAddress { public: - /** Identifies the actual implementation being used. */ - enum AddressType - { - IPv4Address, /**< Used by the UDP over IPv4 transmitter. */ - IPv6Address, /**< Used by the UDP over IPv6 transmitter. */ - ByteAddress, /**< A very general type of address, consisting of a port number and a number of bytes representing the host address. */ - UserDefinedAddress, /**< Can be useful for a user-defined transmitter. */ - TCPAddress /**< Used by the TCP transmitter. */ - }; - /** Returns the type of address the actual implementation represents. */ - AddressType GetAddressType() const + QAbstractSocket::NetworkLayerProtocol GetAddressType() const { - return addresstype; + return address.protocol(); } /** Creates a copy of the RTPAddress instance. @@ -69,31 +61,65 @@ public: * corresponding memory manager will be used to allocate the memory for the address * copy. */ - virtual RTPAddress *CreateCopy() const = 0; + RTPAddress *CreateCopy() const; /** Checks if the address \c addr is the same address as the one this instance represents. * Checks if the address \c addr is the same address as the one this instance represents. * Implementations must be able to handle a NULL argument. + * + * Note that this function is only used for received packets, and for those + * the rtcpsendport variable is not important and should be ignored. */ - virtual bool IsSameAddress(const RTPAddress *addr) const = 0; + bool IsSameAddress(const RTPAddress *addr) const; /** Checks if the address \c addr represents the same host as this instance. * Checks if the address \c addr represents the same host as this instance. Implementations * must be able to handle a NULL argument. + * + * Note that this function is only used for received packets. */ - virtual bool IsFromSameHost(const RTPAddress *addr) const = 0; + bool IsFromSameHost(const RTPAddress *addr) const; - virtual ~RTPAddress() + /** Get host address */ + const QHostAddress& getAddress() const { + return address; } -protected: - // only allow subclasses to be created - RTPAddress(const AddressType t) : - addresstype(t) + + /** Set host address */ + void setAddress(const QHostAddress& address) { + this->address = address; } + + /** Get RTP port */ + uint16_t getPort() const + { + return port; + } + + /** Set RTP port */ + void setPort(uint16_t port) + { + this->port = port; + } + + /** Get RTCP port */ + uint16_t getRtcpsendport() const + { + return rtcpsendport; + } + + /** Set RTCP port */ + void setRtcpsendport(uint16_t rtcpsendport) + { + this->rtcpsendport = rtcpsendport; + } + private: - const AddressType addresstype; + QHostAddress address; + uint16_t port; + uint16_t rtcpsendport; }; } // end namespace diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index 7f4522331..e0bc3d222 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -32,9 +32,10 @@ #include "rtpsession.h" #include "rtperrors.h" -#include "rtpudpv4transmitter.h" -#include "rtptcptransmitter.h" -#include "rtpexternaltransmitter.h" +// TODO: this is for Create with transmitter creation. See if we keep it. +//#include "rtpudpv4transmitter.h" +//#include "rtptcptransmitter.h" +//#include "rtpexternaltransmitter.h" #include "rtpsessionparams.h" #include "rtpdefines.h" #include "rtprawpacket.h" @@ -113,20 +114,21 @@ int RTPSession::Create(const RTPSessionParams &sessparams, const RTPTransmission rtptrans = 0; switch (protocol) { - case RTPTransmitter::IPv4UDPProto: - rtptrans = new RTPUDPv4Transmitter(); - break; - case RTPTransmitter::ExternalProto: - rtptrans = new RTPExternalTransmitter(); - break; - case RTPTransmitter::UserDefinedProto: - rtptrans = NewUserDefinedTransmitter(); - if (rtptrans == 0) - return ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL; - break; - case RTPTransmitter::TCPProto: - rtptrans = new RTPTCPTransmitter(); - break; + // TODO: see if we keep this Create method or use the one with the transmitter specified +// case RTPTransmitter::IPv4UDPProto: +// rtptrans = new RTPUDPv4Transmitter(); +// break; +// case RTPTransmitter::ExternalProto: +// rtptrans = new RTPExternalTransmitter(); +// break; +// case RTPTransmitter::UserDefinedProto: +// rtptrans = NewUserDefinedTransmitter(); +// if (rtptrans == 0) +// return ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL; +// break; +// case RTPTransmitter::TCPProto: +// rtptrans = new RTPTCPTransmitter(); +// break; default: return ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL; } From 9bcabb708d6c71ff2cb776122336833ce3f70425 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 28 Feb 2018 08:37:03 +0100 Subject: [PATCH 032/956] DATV demod: use cmake finder module for FFmpeg --- cmake/Modules/FindFFmpeg.cmake | 151 +++++++++++++++++++++ plugins/channelrx/CMakeLists.txt | 7 +- plugins/channelrx/demoddatv/CMakeLists.txt | 9 +- 3 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 cmake/Modules/FindFFmpeg.cmake diff --git a/cmake/Modules/FindFFmpeg.cmake b/cmake/Modules/FindFFmpeg.cmake new file mode 100644 index 000000000..f97cfbd58 --- /dev/null +++ b/cmake/Modules/FindFFmpeg.cmake @@ -0,0 +1,151 @@ +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionally set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVFILTER +# - AVUTIL +# - POSTPROC +# - SWSCALE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if (NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL) +endif () + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component ) + if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else () + # message(STATUS " - ${_component} not found.") + endif () +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif () + endif (NOT WIN32) + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + ${PC_LIB${_component}_INCLUDEDIR} + ${PC_LIB${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${PC_LIB${_component}_LIBDIR} + ${PC_LIB${_component}_LIBRARY_DIRS} + ) + + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT FFMPEG_LIBRARIES) + + # Check for all possible component. + find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) + find_component(AVFORMAT libavformat avformat libavformat/avformat.h) + find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(AVFILTER libavfilter avfilter libavfilter/avfilter.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) + + # Check if the required components were found and add their stuff to the FFMPEG_* vars. + foreach (_component ${FFmpeg_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + endif () + + # cache the vars. + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + + mark_as_advanced(FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + FFMPEG_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach (_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) \ No newline at end of file diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 86d6bc75e..776378a47 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -11,12 +11,17 @@ add_subdirectory(demodwfm) add_subdirectory(chanalyzer) add_subdirectory(chanalyzerng) add_subdirectory(demodatv) -add_subdirectory(demoddatv) if(LIBDSDCC_FOUND AND LIBMBE_FOUND) add_subdirectory(demoddsd) endif(LIBDSDCC_FOUND AND LIBMBE_FOUND) +find_package(FFmpeg) +if(FFMPEG_FOUND) + add_subdirectory(demoddatv) +endif() + if (BUILD_DEBIAN) add_subdirectory(demoddsd) + add_subdirectory(demoddatv) endif (BUILD_DEBIAN) diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt index 5b4f5115a..cb123e52f 100644 --- a/plugins/channelrx/demoddatv/CMakeLists.txt +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -27,6 +27,9 @@ set(datv_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${AVCODEC_INCLUDE_DIRS} + ${AVFORMAT_INCLUDE_DIRS} + ${SWSCALE_INCLUDE_DIRS} ) #include(${QT_USE_FILE}) @@ -47,9 +50,9 @@ target_link_libraries(demoddatv ${QT_LIBRARIES} sdrbase sdrgui - avcodec - avformat - swscale + ${AVCODEC_LIBRARIES} + ${AVFORMAT_LIBRARIES} + ${SWSCALE_LIBRARIES} ) qt5_use_modules(demoddatv Core Widgets Multimedia MultimediaWidgets) From f678c049f9f2dd8ca1424af71e214fa256927771 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 28 Feb 2018 13:31:44 +0100 Subject: [PATCH 033/956] qrtplib: replaced size_t by std::size_t --- qrtplib/rtcpapppacket.cpp | 8 +-- qrtplib/rtcpapppacket.h | 8 +-- qrtplib/rtcpbyepacket.cpp | 12 ++-- qrtplib/rtcpbyepacket.h | 10 +-- qrtplib/rtcpcompoundpacket.cpp | 12 ++-- qrtplib/rtcpcompoundpacket.h | 8 +-- qrtplib/rtcpcompoundpacketbuilder.cpp | 92 +++++++++++++-------------- qrtplib/rtcpcompoundpacketbuilder.h | 60 ++++++++--------- qrtplib/rtcppacket.h | 7 +- qrtplib/rtcppacketbuilder.cpp | 10 +-- qrtplib/rtcppacketbuilder.h | 22 +++---- qrtplib/rtcprrpacket.cpp | 10 +-- qrtplib/rtcprrpacket.h | 2 +- qrtplib/rtcpscheduler.cpp | 14 ++-- qrtplib/rtcpscheduler.h | 13 ++-- qrtplib/rtcpsdesinfo.cpp | 14 ++-- qrtplib/rtcpsdesinfo.h | 54 ++++++++-------- qrtplib/rtcpsdespacket.cpp | 14 ++-- qrtplib/rtcpsdespacket.h | 46 +++++++------- qrtplib/rtcpsrpacket.cpp | 10 +-- qrtplib/rtcpsrpacket.h | 2 +- qrtplib/rtcpunknownpacket.h | 2 +- qrtplib/rtpexternaltransmitter.cpp | 16 ++--- qrtplib/rtpexternaltransmitter.h | 38 +++++------ qrtplib/rtpinternalsourcedata.cpp | 14 ++-- qrtplib/rtpinternalsourcedata.h | 6 +- qrtplib/rtppacket.cpp | 22 +++---- qrtplib/rtppacket.h | 22 +++---- qrtplib/rtppacketbuilder.cpp | 16 ++--- qrtplib/rtppacketbuilder.h | 22 +++---- qrtplib/rtprawpacket.h | 12 ++-- qrtplib/rtpselect.h | 12 ++-- qrtplib/rtpsession.cpp | 46 +++++++------- qrtplib/rtpsession.h | 56 ++++++++-------- qrtplib/rtpsessionparams.h | 6 +- qrtplib/rtpsessionsources.cpp | 6 +- qrtplib/rtpsessionsources.h | 6 +- qrtplib/rtpsourcedata.h | 22 +++---- qrtplib/rtpsources.cpp | 10 +-- qrtplib/rtpsources.h | 18 +++--- qrtplib/rtptcptransmitter.cpp | 20 +++--- qrtplib/rtptcptransmitter.h | 16 ++--- qrtplib/rtptransmitter.h | 13 ++-- qrtplib/rtpudpv4transmitter.cpp | 28 ++++---- qrtplib/rtpudpv4transmitter.h | 16 ++--- qrtplib/rtpudpv4transmitternobind.cpp | 28 ++++---- qrtplib/rtpudpv4transmitternobind.h | 16 ++--- 47 files changed, 460 insertions(+), 457 deletions(-) diff --git a/qrtplib/rtcpapppacket.cpp b/qrtplib/rtcpapppacket.cpp index 7aeb9abd3..07737797e 100644 --- a/qrtplib/rtcpapppacket.cpp +++ b/qrtplib/rtcpapppacket.cpp @@ -35,13 +35,13 @@ namespace qrtplib { -RTCPAPPPacket::RTCPAPPPacket(uint8_t *data, size_t datalength) : +RTCPAPPPacket::RTCPAPPPacket(uint8_t *data, std::size_t datalength) : RTCPPacket(APP, data, datalength) { knownformat = false; RTCPCommonHeader *hdr; - size_t len = datalength; + std::size_t len = datalength; hdr = (RTCPCommonHeader *) data; if (hdr->padding) @@ -49,9 +49,9 @@ RTCPAPPPacket::RTCPAPPPacket(uint8_t *data, size_t datalength) : uint8_t padcount = data[datalength - 1]; if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) return; - if (((size_t) padcount) >= len) + if (((std::size_t) padcount) >= len) return; - len -= (size_t) padcount; + len -= (std::size_t) padcount; } if (len < (sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2)) diff --git a/qrtplib/rtcpapppacket.h b/qrtplib/rtcpapppacket.h index b2cd5579a..ab4839c18 100644 --- a/qrtplib/rtcpapppacket.h +++ b/qrtplib/rtcpapppacket.h @@ -57,7 +57,7 @@ public: * is referenced inside the class (no copy of the data is made) one must make sure that the memory it * points to is valid as long as the class instance exists. */ - RTCPAPPPacket(uint8_t *data, size_t datalen); + RTCPAPPPacket(uint8_t *data, std::size_t datalen); ~RTCPAPPPacket() { } @@ -77,10 +77,10 @@ public: uint8_t *GetAPPData(); /** Returns the length of the actual data. */ - size_t GetAPPDataLength() const; + std::size_t GetAPPDataLength() const; private: RTPEndian m_endian; - size_t appdatalen; + std::size_t appdatalen; }; inline uint8_t RTCPAPPPacket::GetSubType() const @@ -117,7 +117,7 @@ inline uint8_t *RTCPAPPPacket::GetAPPData() return (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2); } -inline size_t RTCPAPPPacket::GetAPPDataLength() const +inline std::size_t RTCPAPPPacket::GetAPPDataLength() const { if (!knownformat) return 0; diff --git a/qrtplib/rtcpbyepacket.cpp b/qrtplib/rtcpbyepacket.cpp index 443f47797..23d1f5eec 100644 --- a/qrtplib/rtcpbyepacket.cpp +++ b/qrtplib/rtcpbyepacket.cpp @@ -35,14 +35,14 @@ namespace qrtplib { -RTCPBYEPacket::RTCPBYEPacket(uint8_t *data, size_t datalength) : +RTCPBYEPacket::RTCPBYEPacket(uint8_t *data, std::size_t datalength) : RTCPPacket(BYE, data, datalength) { knownformat = false; reasonoffset = 0; RTCPCommonHeader *hdr; - size_t len = datalength; + std::size_t len = datalength; hdr = (RTCPCommonHeader *) data; if (hdr->padding) @@ -50,18 +50,18 @@ RTCPBYEPacket::RTCPBYEPacket(uint8_t *data, size_t datalength) : uint8_t padcount = data[datalength - 1]; if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) return; - if (((size_t) padcount) >= len) + if (((std::size_t) padcount) >= len) return; - len -= (size_t) padcount; + len -= (std::size_t) padcount; } - size_t ssrclen = ((size_t)(hdr->count)) * sizeof(uint32_t) + sizeof(RTCPCommonHeader); + std::size_t ssrclen = ((std::size_t)(hdr->count)) * sizeof(uint32_t) + sizeof(RTCPCommonHeader); if (ssrclen > len) return; if (ssrclen < len) // there's probably a reason for leaving { uint8_t *reasonlength = (data + ssrclen); - size_t reaslen = (size_t)(*reasonlength); + std::size_t reaslen = (std::size_t)(*reasonlength); if (reaslen > (len - ssrclen - 1)) return; reasonoffset = ssrclen; diff --git a/qrtplib/rtcpbyepacket.h b/qrtplib/rtcpbyepacket.h index 6619af851..993d40df4 100644 --- a/qrtplib/rtcpbyepacket.h +++ b/qrtplib/rtcpbyepacket.h @@ -57,7 +57,7 @@ public: * is referenced inside the class (no copy of the data is made) one must make sure that the memory it * points to is valid as long as the class instance exists. */ - RTCPBYEPacket(uint8_t *data, size_t datalen); + RTCPBYEPacket(uint8_t *data, std::size_t datalen); ~RTCPBYEPacket() { } @@ -74,14 +74,14 @@ public: bool HasReasonForLeaving() const; /** Returns the length of the string which describes why the source(s) left. */ - size_t GetReasonLength() const; + std::size_t GetReasonLength() const; /** Returns the actual reason for leaving data. */ uint8_t *GetReasonData(); private: RTPEndian m_endian; - size_t reasonoffset; + std::size_t reasonoffset; }; inline int RTCPBYEPacket::GetSSRCCount() const @@ -110,14 +110,14 @@ inline bool RTCPBYEPacket::HasReasonForLeaving() const return true; } -inline size_t RTCPBYEPacket::GetReasonLength() const +inline std::size_t RTCPBYEPacket::GetReasonLength() const { if (!knownformat) return 0; if (reasonoffset == 0) return 0; uint8_t *reasonlen = (data + reasonoffset); - return (size_t)(*reasonlen); + return (std::size_t)(*reasonlen); } inline uint8_t *RTCPBYEPacket::GetReasonData() diff --git a/qrtplib/rtcpcompoundpacket.cpp b/qrtplib/rtcpcompoundpacket.cpp index 4a0e1d228..959cd978d 100644 --- a/qrtplib/rtcpcompoundpacket.cpp +++ b/qrtplib/rtcpcompoundpacket.cpp @@ -58,7 +58,7 @@ RTCPCompoundPacket::RTCPCompoundPacket(RTPRawPacket &rawpack) } uint8_t *data = rawpack.GetData(); - size_t datalen = rawpack.GetDataLength(); + std::size_t datalen = rawpack.GetDataLength(); error = ParseData(data, datalen); if (error < 0) @@ -73,7 +73,7 @@ RTCPCompoundPacket::RTCPCompoundPacket(RTPRawPacket &rawpack) rtcppackit = rtcppacklist.begin(); } -RTCPCompoundPacket::RTCPCompoundPacket(uint8_t *packet, size_t packetlen, bool deletedata) +RTCPCompoundPacket::RTCPCompoundPacket(uint8_t *packet, std::size_t packetlen, bool deletedata) { compoundpacket = 0; compoundpacketlength = 0; @@ -97,7 +97,7 @@ RTCPCompoundPacket::RTCPCompoundPacket() deletepacket = true; } -int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen) +int RTCPCompoundPacket::ParseData(uint8_t *data, std::size_t datalen) { bool first; @@ -109,7 +109,7 @@ int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen) do { RTCPCommonHeader *rtcphdr; - size_t length; + std::size_t length; rtcphdr = (RTCPCommonHeader *) data; if (rtcphdr->version != RTP_VERSION) // check version @@ -129,7 +129,7 @@ int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen) } } - length = (size_t) m_endian.qToHost(rtcphdr->length); + length = (std::size_t) m_endian.qToHost(rtcphdr->length); length++; length *= sizeof(uint32_t); @@ -182,7 +182,7 @@ int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen) datalen -= length; data += length; - } while (datalen >= (size_t) sizeof(RTCPCommonHeader)); + } while (datalen >= (std::size_t) sizeof(RTCPCommonHeader)); if (datalen != 0) // some remaining bytes { diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h index c5f59f5b7..e84b6aa70 100644 --- a/qrtplib/rtcpcompoundpacket.h +++ b/qrtplib/rtcpcompoundpacket.h @@ -61,7 +61,7 @@ public: * flag specifies if the data in \c packet should be deleted when the compound packet is destroyed. If * specified, a memory manager will be installed. */ - RTCPCompoundPacket(uint8_t *packet, size_t len, bool deletedata = true); + RTCPCompoundPacket(uint8_t *packet, std::size_t len, bool deletedata = true); protected: RTCPCompoundPacket(); // this is for the compoundpacket builder public: @@ -83,7 +83,7 @@ public: } /** Returns the size of the entire RTCP compound packet. */ - size_t GetCompoundPacketLength() + std::size_t GetCompoundPacketLength() { return compoundpacketlength; } @@ -109,13 +109,13 @@ public: protected: void ClearPacketList(); - int ParseData(uint8_t *packet, size_t len); + int ParseData(uint8_t *packet, std::size_t len); RTPEndian m_endian; int error; uint8_t *compoundpacket; - size_t compoundpacketlength; + std::size_t compoundpacketlength; bool deletepacket; std::list rtcppacklist; diff --git a/qrtplib/rtcpcompoundpacketbuilder.cpp b/qrtplib/rtcpcompoundpacketbuilder.cpp index b571254ac..7aeba2abc 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.cpp +++ b/qrtplib/rtcpcompoundpacketbuilder.cpp @@ -80,7 +80,7 @@ void RTCPCompoundPacketBuilder::ClearBuildBuffers() appsize = 0; } -int RTCPCompoundPacketBuilder::InitBuild(size_t maxpacketsize) +int RTCPCompoundPacketBuilder::InitBuild(std::size_t maxpacketsize) { if (arebuilding) return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; @@ -100,7 +100,7 @@ int RTCPCompoundPacketBuilder::InitBuild(size_t maxpacketsize) return 0; } -int RTCPCompoundPacketBuilder::InitBuild(void *externalbuffer, size_t buffersize) +int RTCPCompoundPacketBuilder::InitBuild(void *externalbuffer, std::size_t buffersize) { if (arebuilding) return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; @@ -128,9 +128,9 @@ int RTCPCompoundPacketBuilder::StartSenderReport(uint32_t senderssrc, const RTPN if (report.headerlength != 0) return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; - size_t totalsize = byesize + appsize + sdes.NeededBytes(); - size_t sizeleft = maximumpacketsize - totalsize; - size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport); + std::size_t totalsize = byesize + appsize + sdes.NeededBytes(); + std::size_t sizeleft = maximumpacketsize - totalsize; + std::size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport); if (neededsize > sizeleft) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; @@ -160,9 +160,9 @@ int RTCPCompoundPacketBuilder::StartReceiverReport(uint32_t senderssrc) if (report.headerlength != 0) return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; - size_t totalsize = byesize + appsize + sdes.NeededBytes(); - size_t sizeleft = maximumpacketsize - totalsize; - size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t); + std::size_t totalsize = byesize + appsize + sdes.NeededBytes(); + std::size_t sizeleft = maximumpacketsize - totalsize; + std::size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t); if (neededsize > sizeleft) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; @@ -185,8 +185,8 @@ int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc, uint8_t fractionlos if (report.headerlength == 0) return ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED; - size_t totalothersize = byesize + appsize + sdes.NeededBytes(); - size_t reportsizewithextrablock = report.NeededBytesWithExtraReportBlock(); + std::size_t totalothersize = byesize + appsize + sdes.NeededBytes(); + std::size_t reportsizewithextrablock = report.NeededBytesWithExtraReportBlock(); if ((totalothersize + reportsizewithextrablock) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; @@ -218,8 +218,8 @@ int RTCPCompoundPacketBuilder::AddSDESSource(uint32_t ssrc) if (!arebuilding) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; - size_t totalotherbytes = byesize + appsize + report.NeededBytes(); - size_t sdessizewithextrasource = sdes.NeededBytesWithExtraSource(); + std::size_t totalotherbytes = byesize + appsize + report.NeededBytes(); + std::size_t sdessizewithextrasource = sdes.NeededBytesWithExtraSource(); if ((totalotherbytes + sdessizewithextrasource) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; @@ -267,26 +267,26 @@ int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t, con return ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE; } - size_t totalotherbytes = byesize + appsize + report.NeededBytes(); - size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); + std::size_t totalotherbytes = byesize + appsize + report.NeededBytes(); + std::size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); if ((sdessizewithextraitem + totalotherbytes) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; uint8_t *buf; - size_t len; + std::size_t len; - buf = new uint8_t[sizeof(RTCPSDESHeader) + (size_t) itemlength]; + buf = new uint8_t[sizeof(RTCPSDESHeader) + (std::size_t) itemlength]; if (buf == 0) return ERR_RTP_OUTOFMEM; - len = sizeof(RTCPSDESHeader) + (size_t) itemlength; + len = sizeof(RTCPSDESHeader) + (std::size_t) itemlength; RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (buf); sdeshdr->sdesid = itemid; sdeshdr->length = itemlength; if (itemlength != 0) - memcpy((buf + sizeof(RTCPSDESHeader)), itemdata, (size_t) itemlength); + memcpy((buf + sizeof(RTCPSDESHeader)), itemdata, (std::size_t) itemlength); sdes.AddItem(buf, len); return 0; @@ -300,23 +300,23 @@ int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata, uint8_ if (sdes.sdessources.empty()) return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; - size_t itemlength = ((size_t) prefixlength) + 1 + ((size_t) valuelength); + std::size_t itemlength = ((std::size_t) prefixlength) + 1 + ((std::size_t) valuelength); if (itemlength > 255) return ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG; - size_t totalotherbytes = byesize + appsize + report.NeededBytes(); - size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); + std::size_t totalotherbytes = byesize + appsize + report.NeededBytes(); + std::size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); if ((sdessizewithextraitem + totalotherbytes) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; uint8_t *buf; - size_t len; + std::size_t len; buf = new uint8_t[sizeof(RTCPSDESHeader) + itemlength]; if (buf == 0) return ERR_RTP_OUTOFMEM; - len = sizeof(RTCPSDESHeader) + (size_t) itemlength; + len = sizeof(RTCPSDESHeader) + (std::size_t) itemlength; RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (buf); @@ -325,9 +325,9 @@ int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata, uint8_ buf[sizeof(RTCPSDESHeader)] = prefixlength; if (prefixlength != 0) - memcpy((buf + sizeof(RTCPSDESHeader) + 1), prefixdata, (size_t) prefixlength); + memcpy((buf + sizeof(RTCPSDESHeader) + 1), prefixdata, (std::size_t) prefixlength); if (valuelength != 0) - memcpy((buf + sizeof(RTCPSDESHeader) + 1 + (size_t) prefixlength), valuedata, (size_t) valuelength); + memcpy((buf + sizeof(RTCPSDESHeader) + 1 + (std::size_t) prefixlength), valuedata, (std::size_t) valuelength); sdes.AddItem(buf, len); return 0; @@ -342,15 +342,15 @@ int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, c if (numssrcs > 31) return ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS; - size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * ((size_t) numssrcs); - size_t zerobytes = 0; + std::size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * ((std::size_t) numssrcs); + std::size_t zerobytes = 0; if (reasonlength > 0) { packsize += 1; // 1 byte for the length; - packsize += (size_t) reasonlength; + packsize += (std::size_t) reasonlength; - size_t r = (packsize & 0x03); + std::size_t r = (packsize & 0x03); if (r != 0) { zerobytes = 4 - r; @@ -358,13 +358,13 @@ int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, c } } - size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); + std::size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); if ((totalotherbytes + packsize) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; uint8_t *buf; - size_t numwords; + std::size_t numwords; buf = new uint8_t[packsize]; if (buf == 0) @@ -388,11 +388,11 @@ int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, c if (reasonlength != 0) { - size_t offset = sizeof(RTCPCommonHeader) + ((size_t) numssrcs) * sizeof(uint32_t); + std::size_t offset = sizeof(RTCPCommonHeader) + ((std::size_t) numssrcs) * sizeof(uint32_t); buf[offset] = reasonlength; - memcpy((buf + offset + 1), reasondata, (size_t) reasonlength); - for (size_t i = 0; i < zerobytes; i++) + memcpy((buf + offset + 1), reasondata, (std::size_t) reasonlength); + for (std::size_t i = 0; i < zerobytes; i++) buf[packsize - 1 - i] = 0; } @@ -402,7 +402,7 @@ int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, c return 0; } -int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype, uint32_t ssrc, const uint8_t name[4], const void *appdata, size_t appdatalen) +int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype, uint32_t ssrc, const uint8_t name[4], const void *appdata, std::size_t appdatalen) { if (!arebuilding) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; @@ -411,13 +411,13 @@ int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype, uint32_t ssrc, cons if ((appdatalen % 4) != 0) return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH; - size_t appdatawords = appdatalen / 4; + std::size_t appdatawords = appdatalen / 4; if ((appdatawords + 2) > 65535) return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; - size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2 + appdatalen; - size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); + std::size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2 + appdatalen; + std::size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); if ((totalotherbytes + packsize) > maximumpacketsize) return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; @@ -462,7 +462,7 @@ int RTCPCompoundPacketBuilder::EndBuild() return ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT; uint8_t *buf; - size_t len; + std::size_t len; len = appsize + byesize + report.NeededBytes() + sdes.NeededBytes(); @@ -487,7 +487,7 @@ int RTCPCompoundPacketBuilder::EndBuild() do { RTCPCommonHeader *hdr = (RTCPCommonHeader *) curbuf; - size_t offset; + std::size_t offset; hdr->version = 2; hdr->padding = 0; @@ -516,7 +516,7 @@ int RTCPCompoundPacketBuilder::EndBuild() it++; } - size_t numwords = offset / sizeof(uint32_t); + std::size_t numwords = offset / sizeof(uint32_t); hdr->length = qToBigEndian((uint16_t) (numwords - 1)); hdr->count = count; @@ -551,7 +551,7 @@ int RTCPCompoundPacketBuilder::EndBuild() do { RTCPCommonHeader *hdr = (RTCPCommonHeader *) curbuf; - size_t offset = sizeof(RTCPCommonHeader); + std::size_t offset = sizeof(RTCPCommonHeader); hdr->version = 2; hdr->padding = 0; @@ -579,11 +579,11 @@ int RTCPCompoundPacketBuilder::EndBuild() curbuf[offset] = 0; // end of item list; offset++; - size_t r = offset & 0x03; + std::size_t r = offset & 0x03; if (r != 0) // align to 32 bit boundary { - size_t num = 4 - r; - size_t i; + std::size_t num = 4 - r; + std::size_t i; for (i = 0; i < num; i++) curbuf[offset + i] = 0; @@ -594,7 +594,7 @@ int RTCPCompoundPacketBuilder::EndBuild() sourcecount++; } - size_t numwords = offset / 4; + std::size_t numwords = offset / 4; hdr->count = sourcecount; hdr->length = qToBigEndian((uint16_t) (numwords - 1)); diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index 7430d653e..4a1fccfd9 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -66,13 +66,13 @@ public: * Starts building an RTCP compound packet with maximum size \c maxpacketsize. New memory will be allocated * to store the packet. */ - int InitBuild(size_t maxpacketsize); + int InitBuild(std::size_t maxpacketsize); /** Starts building a RTCP compound packet. * Starts building a RTCP compound packet. Data will be stored in \c externalbuffer which * can contain \c buffersize bytes. */ - int InitBuild(void *externalbuffer, size_t buffersize); + int InitBuild(void *externalbuffer, std::size_t buffersize); /** Adds a sender report to the compound packet. * Tells the packet builder that the packet should start with a sender report which will contain @@ -118,7 +118,7 @@ public: * Adds the APP packet specified by the arguments to the compound packet. Note that \c appdatalen has to be * a multiple of four. */ - int AddAPPPacket(uint8_t subtype, uint32_t ssrc, const uint8_t name[4], const void *appdata, size_t appdatalen); + int AddAPPPacket(uint8_t subtype, uint32_t ssrc, const uint8_t name[4], const void *appdata, std::size_t appdatalen); /** Finishes building the compound packet. * Finishes building the compound packet. If successful, the RTCPCompoundPacket member functions @@ -134,13 +134,13 @@ private: packetdata(0), packetlength(0) { } - Buffer(uint8_t *data, size_t len) : + Buffer(uint8_t *data, std::size_t len) : packetdata(data), packetlength(len) { } uint8_t *packetdata; - size_t packetlength; + std::size_t packetlength; }; class Report @@ -170,9 +170,9 @@ private: headerlength = 0; } - size_t NeededBytes() + std::size_t NeededBytes() { - size_t x, n, d, r; + std::size_t x, n, d, r; n = reportblocks.size(); if (n == 0) { @@ -194,9 +194,9 @@ private: return x; } - size_t NeededBytesWithExtraReportBlock() + std::size_t NeededBytesWithExtraReportBlock() { - size_t x, n, d, r; + std::size_t x, n, d, r; n = reportblocks.size() + 1; // +1 for the extra block x = n * sizeof(RTCPReceiverReport); d = n / 31; // max 31 reportblocks per report @@ -213,7 +213,7 @@ private: uint8_t *headerdata; uint32_t headerdata32[(sizeof(uint32_t) + sizeof(RTCPSenderReport)) / sizeof(uint32_t)]; // either for ssrc and sender info or just ssrc - size_t headerlength; + std::size_t headerlength; std::list reportblocks; }; @@ -235,9 +235,9 @@ private: items.clear(); } - size_t NeededBytes() + std::size_t NeededBytes() { - size_t x, r; + std::size_t x, r; x = totalitemsize + 1; // +1 for the 0 byte which terminates the item list r = x % sizeof(uint32_t); if (r != 0) @@ -246,10 +246,10 @@ private: return x; } - size_t NeededBytesWithExtraItem(uint8_t itemdatalength) + std::size_t NeededBytesWithExtraItem(uint8_t itemdatalength) { - size_t x, r; - x = totalitemsize + sizeof(RTCPSDESHeader) + (size_t) itemdatalength + 1; + std::size_t x, r; + x = totalitemsize + sizeof(RTCPSDESHeader) + (std::size_t) itemdatalength + 1; r = x % sizeof(uint32_t); if (r != 0) x += (sizeof(uint32_t) - r); // make sure it ends on a 32 bit boundary @@ -257,7 +257,7 @@ private: return x; } - void AddItem(uint8_t *buf, size_t len) + void AddItem(uint8_t *buf, std::size_t len) { Buffer b(buf, len); totalitemsize += len; @@ -267,7 +267,7 @@ private: uint32_t ssrc; std::list items; private: - size_t totalitemsize; + std::size_t totalitemsize; }; class SDES @@ -302,7 +302,7 @@ private: return 0; } - int AddItem(uint8_t *buf, size_t len) + int AddItem(uint8_t *buf, std::size_t len) { if (sdessources.empty()) return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; @@ -310,11 +310,11 @@ private: return 0; } - size_t NeededBytes() + std::size_t NeededBytes() { std::list::const_iterator it; - size_t x = 0; - size_t n, d, r; + std::size_t x = 0; + std::size_t n, d, r; if (sdessources.empty()) return 0; @@ -330,11 +330,11 @@ private: return x; } - size_t NeededBytesWithExtraItem(uint8_t itemdatalength) + std::size_t NeededBytesWithExtraItem(uint8_t itemdatalength) { std::list::const_iterator it; - size_t x = 0; - size_t n, d, r; + std::size_t x = 0; + std::size_t n, d, r; if (sdessources.empty()) return 0; @@ -351,11 +351,11 @@ private: return x; } - size_t NeededBytesWithExtraSource() + std::size_t NeededBytesWithExtraSource() { std::list::const_iterator it; - size_t x = 0; - size_t n, d, r; + std::size_t x = 0; + std::size_t n, d, r; if (sdessources.empty()) return 0; @@ -381,7 +381,7 @@ private: }; RTPEndian m_endian; - size_t maximumpacketsize; + std::size_t maximumpacketsize; uint8_t *buffer; bool external; bool arebuilding; @@ -390,10 +390,10 @@ private: SDES sdes; std::list byepackets; - size_t byesize; + std::size_t byesize; std::list apppackets; - size_t appsize; + std::size_t appsize; void ClearBuildBuffers(); }; diff --git a/qrtplib/rtcppacket.h b/qrtplib/rtcppacket.h index e5dcb129e..84092e340 100644 --- a/qrtplib/rtcppacket.h +++ b/qrtplib/rtcppacket.h @@ -40,6 +40,7 @@ #include "rtpconfig.h" #include "rtptypes.h" +#include namespace qrtplib { @@ -61,7 +62,7 @@ public: Unknown /**< The type of RTCP packet was not recognized. */ }; protected: - RTCPPacket(PacketType t, uint8_t *d, size_t dlen) : + RTCPPacket(PacketType t, uint8_t *d, std::size_t dlen) : data(d), datalen(dlen), packettype(t) { knownformat = false; @@ -90,14 +91,14 @@ public: } /** Returns the length of this RTCP packet. */ - size_t GetPacketLength() const + std::size_t GetPacketLength() const { return datalen; } protected: uint8_t *data; - size_t datalen; + std::size_t datalen; bool knownformat; private: const PacketType packettype; diff --git a/qrtplib/rtcppacketbuilder.cpp b/qrtplib/rtcppacketbuilder.cpp index 8ebe26dcf..c4b2ff543 100644 --- a/qrtplib/rtcppacketbuilder.cpp +++ b/qrtplib/rtcppacketbuilder.cpp @@ -52,7 +52,7 @@ RTCPPacketBuilder::~RTCPPacketBuilder() Destroy(); } -int RTCPPacketBuilder::Init(size_t maxpacksize, double tsunit, const void *cname, size_t cnamelen) +int RTCPPacketBuilder::Init(std::size_t maxpacksize, double tsunit, const void *cname, std::size_t cnamelen) { if (init) return ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT; @@ -163,7 +163,7 @@ int RTCPPacketBuilder::BuildNextPacket(RTCPCompoundPacket **pack) } uint8_t *owncname; - size_t owncnamelen; + std::size_t owncnamelen; owncname = ownsdesinfo.GetCNAME(&owncnamelen); @@ -506,7 +506,7 @@ int RTCPPacketBuilder::FillInSDES(RTCPCompoundPacketBuilder *rtcpcomppack, bool { int status; uint8_t *data; - size_t datalen; + std::size_t datalen; *full = false; *processedall = false; @@ -626,7 +626,7 @@ void RTCPPacketBuilder::ClearAllSDESFlags() ownsdesinfo.ClearFlags(); } -int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack, const void *reason, size_t reasonlength, bool useSRifpossible) +int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack, const void *reason, std::size_t reasonlength, bool useSRifpossible) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; @@ -697,7 +697,7 @@ int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack, const void *rea } uint8_t *owncname; - size_t owncnamelen; + std::size_t owncnamelen; owncname = ownsdesinfo.GetCNAME(&owncnamelen); diff --git a/qrtplib/rtcppacketbuilder.h b/qrtplib/rtcppacketbuilder.h index 37908ef00..cfd612fc2 100644 --- a/qrtplib/rtcppacketbuilder.h +++ b/qrtplib/rtcppacketbuilder.h @@ -76,7 +76,7 @@ public: * The timestamp unit is defined as a time interval divided by the timestamp interval corresponding to * that interval: for 8000 Hz audio this would be 1/8000. */ - int Init(size_t maxpacksize, double timestampunit, const void *cname, size_t cnamelen); + int Init(std::size_t maxpacksize, double timestampunit, const void *cname, std::size_t cnamelen); /** Cleans up the builder. */ void Destroy(); @@ -97,7 +97,7 @@ public: } /** Sets the maximum size allowed size of an RTCP compound packet to \c maxpacksize. */ - int SetMaximumPacketSize(size_t maxpacksize) + int SetMaximumPacketSize(std::size_t maxpacksize) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; @@ -129,7 +129,7 @@ public: * \c useSRifpossible is set to \c true, the RTCP compound packet will start with a sender report if * allowed. Otherwise, a receiver report is used. */ - int BuildBYEPacket(RTCPCompoundPacket **pack, const void *reason, size_t reasonlength, bool useSRifpossible = true); + int BuildBYEPacket(RTCPCompoundPacket **pack, const void *reason, std::size_t reasonlength, bool useSRifpossible = true); /** Sets the RTCP interval for the SDES name item. * After all possible sources in the source table have been processed, the class will check if other @@ -210,7 +210,7 @@ public: } /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ - int SetLocalName(const void *s, size_t len) + int SetLocalName(const void *s, std::size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; @@ -218,7 +218,7 @@ public: } /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ - int SetLocalEMail(const void *s, size_t len) + int SetLocalEMail(const void *s, std::size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; @@ -226,7 +226,7 @@ public: } /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ - int SetLocalLocation(const void *s, size_t len) + int SetLocalLocation(const void *s, std::size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; @@ -234,7 +234,7 @@ public: } /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ - int SetLocalPhone(const void *s, size_t len) + int SetLocalPhone(const void *s, std::size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; @@ -242,7 +242,7 @@ public: } /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ - int SetLocalTool(const void *s, size_t len) + int SetLocalTool(const void *s, std::size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; @@ -250,7 +250,7 @@ public: } /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ - int SetLocalNote(const void *s, size_t len) + int SetLocalNote(const void *s, std::size_t len) { if (!init) return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; @@ -258,7 +258,7 @@ public: } /** Returns the own CNAME item with length \c len */ - uint8_t *GetLocalCNAME(size_t *len) const + uint8_t *GetLocalCNAME(std::size_t *len) const { if (!init) return 0; @@ -274,7 +274,7 @@ private: RTPPacketBuilder &rtppacketbuilder; bool init; - size_t maxpacketsize; + std::size_t maxpacketsize; double timestampunit; bool firstpacket; RTPTime prevbuildtime, transmissiondelay; diff --git a/qrtplib/rtcprrpacket.cpp b/qrtplib/rtcprrpacket.cpp index 9368127ac..5c7ccf673 100644 --- a/qrtplib/rtcprrpacket.cpp +++ b/qrtplib/rtcprrpacket.cpp @@ -35,14 +35,14 @@ namespace qrtplib { -RTCPRRPacket::RTCPRRPacket(uint8_t *data, size_t datalength) : +RTCPRRPacket::RTCPRRPacket(uint8_t *data, std::size_t datalength) : RTCPPacket(RR, data, datalength) { knownformat = false; RTCPCommonHeader *hdr; - size_t len = datalength; - size_t expectedlength; + std::size_t len = datalength; + std::size_t expectedlength; hdr = (RTCPCommonHeader *) data; if (hdr->padding) @@ -50,9 +50,9 @@ RTCPRRPacket::RTCPRRPacket(uint8_t *data, size_t datalength) : uint8_t padcount = data[datalength - 1]; if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) return; - if (((size_t) padcount) >= len) + if (((std::size_t) padcount) >= len) return; - len -= (size_t) padcount; + len -= (std::size_t) padcount; } expectedlength = sizeof(RTCPCommonHeader) + sizeof(uint32_t); diff --git a/qrtplib/rtcprrpacket.h b/qrtplib/rtcprrpacket.h index d6b4b1011..66d158bb1 100644 --- a/qrtplib/rtcprrpacket.h +++ b/qrtplib/rtcprrpacket.h @@ -57,7 +57,7 @@ public: * is referenced inside the class (no copy of the data is made) one must make sure that the memory it points * to is valid as long as the class instance exists. */ - RTCPRRPacket(uint8_t *data, size_t datalen); + RTCPRRPacket(uint8_t *data, std::size_t datalen); ~RTCPRRPacket() { } diff --git a/qrtplib/rtcpscheduler.cpp b/qrtplib/rtcpscheduler.cpp index 99a2589f1..31f7a8bb1 100644 --- a/qrtplib/rtcpscheduler.cpp +++ b/qrtplib/rtcpscheduler.cpp @@ -120,15 +120,15 @@ void RTCPScheduler::AnalyseIncoming(RTCPCompoundPacket &rtcpcomppack) if (!isbye) { - size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); - avgrtcppacksize = (size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgrtcppacksize)); + std::size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgrtcppacksize = (std::size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgrtcppacksize)); } else { if (byescheduled) { - size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); - avgbyepacketsize = (size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgbyepacketsize)); + std::size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgbyepacketsize = (std::size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgbyepacketsize)); byemembers++; } } @@ -148,8 +148,8 @@ void RTCPScheduler::AnalyseOutgoing(RTCPCompoundPacket &rtcpcomppack) if (!isbye) { - size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); - avgrtcppacksize = (size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgrtcppacksize)); + std::size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgrtcppacksize = (std::size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgrtcppacksize)); } hassentrtcp = true; @@ -354,7 +354,7 @@ void RTCPScheduler::PerformReverseReconsideration() pmembers = members; } -void RTCPScheduler::ScheduleBYEPacket(size_t packetsize) +void RTCPScheduler::ScheduleBYEPacket(std::size_t packetsize) { if (byescheduled) return; diff --git a/qrtplib/rtcpscheduler.h b/qrtplib/rtcpscheduler.h index 0d0961f91..f7bc84a47 100644 --- a/qrtplib/rtcpscheduler.h +++ b/qrtplib/rtcpscheduler.h @@ -41,6 +41,7 @@ #include "rtpconfig.h" #include "rtptimeutilities.h" #include "rtprandom.h" +#include namespace qrtplib { @@ -148,13 +149,13 @@ public: } /** Sets the header overhead from underlying protocols (for example UDP and IP) to \c numbytes. */ - void SetHeaderOverhead(size_t numbytes) + void SetHeaderOverhead(std::size_t numbytes) { headeroverhead = numbytes; } /** Returns the currently used header overhead. */ - size_t GetHeaderOverhead() const + std::size_t GetHeaderOverhead() const { return headeroverhead; } @@ -171,7 +172,7 @@ public: /** Asks the scheduler to schedule an RTCP compound packet containing a BYE packetl; the compound packet * has size \c packetsize. */ - void ScheduleBYEPacket(size_t packetsize); + void ScheduleBYEPacket(std::size_t packetsize); /** Returns the delay after which an RTCP compound will possibly have to be sent. * Returns the delay after which an RTCP compound will possibly have to be sent. The IsTime member function @@ -199,8 +200,8 @@ private: RTPSources &sources; RTCPSchedulerParams schedparams; - size_t headeroverhead; - size_t avgrtcppacksize; + std::size_t headeroverhead; + std::size_t avgrtcppacksize; bool hassentrtcp; bool firstcall; RTPTime nextrtcptime; @@ -210,7 +211,7 @@ private: // for BYE packet scheduling bool byescheduled; int byemembers, pbyemembers; - size_t avgbyepacketsize; + std::size_t avgbyepacketsize; bool sendbyenow; RTPRandom &rtprand; diff --git a/qrtplib/rtcpsdesinfo.cpp b/qrtplib/rtcpsdesinfo.cpp index 1ed1895d1..735065061 100644 --- a/qrtplib/rtcpsdesinfo.cpp +++ b/qrtplib/rtcpsdesinfo.cpp @@ -47,7 +47,7 @@ void RTCPSDESInfo::Clear() } #ifdef RTP_SUPPORT_SDESPRIV -int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix, size_t prefixlen, const uint8_t *value, size_t valuelen) +int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, const uint8_t *value, std::size_t valuelen) { std::list::const_iterator it; bool found; @@ -57,7 +57,7 @@ int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix, size_t prefixlen, const while (!found && it != privitems.end()) { uint8_t *p; - size_t l; + std::size_t l; p = (*it)->GetPrefix(&l); if (l == prefixlen) @@ -97,7 +97,7 @@ int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix, size_t prefixlen, const return item->SetInfo(value, valuelen); } -int RTCPSDESInfo::DeletePrivatePrefix(const uint8_t *prefix, size_t prefixlen) +int RTCPSDESInfo::DeletePrivatePrefix(const uint8_t *prefix, std::size_t prefixlen) { std::list::iterator it; bool found; @@ -107,7 +107,7 @@ int RTCPSDESInfo::DeletePrivatePrefix(const uint8_t *prefix, size_t prefixlen) while (!found && it != privitems.end()) { uint8_t *p; - size_t l; + std::size_t l; p = (*it)->GetPrefix(&l); if (l == prefixlen) @@ -135,7 +135,7 @@ void RTCPSDESInfo::GotoFirstPrivateValue() curitem = privitems.begin(); } -bool RTCPSDESInfo::GetNextPrivateValue(uint8_t **prefix, size_t *prefixlen, uint8_t **value, size_t *valuelen) +bool RTCPSDESInfo::GetNextPrivateValue(uint8_t **prefix, std::size_t *prefixlen, uint8_t **value, std::size_t *valuelen) { if (curitem == privitems.end()) return false; @@ -145,7 +145,7 @@ bool RTCPSDESInfo::GetNextPrivateValue(uint8_t **prefix, size_t *prefixlen, uint return true; } -bool RTCPSDESInfo::GetPrivateValue(const uint8_t *prefix, size_t prefixlen, uint8_t **value, size_t *valuelen) const +bool RTCPSDESInfo::GetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, uint8_t **value, std::size_t *valuelen) const { std::list::const_iterator it; bool found; @@ -155,7 +155,7 @@ bool RTCPSDESInfo::GetPrivateValue(const uint8_t *prefix, size_t prefixlen, uint while (!found && it != privitems.end()) { uint8_t *p; - size_t l; + std::size_t l; p = (*it)->GetPrefix(&l); if (l == prefixlen) diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h index ab2e61568..34e169cc0 100644 --- a/qrtplib/rtcpsdesinfo.h +++ b/qrtplib/rtcpsdesinfo.h @@ -65,43 +65,43 @@ public: void Clear(); /** Sets the SDES CNAME item to \c s with length \c l. */ - int SetCNAME(const uint8_t *s, size_t l) + int SetCNAME(const uint8_t *s, std::size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_CNAME - 1, s, l); } /** Sets the SDES name item to \c s with length \c l. */ - int SetName(const uint8_t *s, size_t l) + int SetName(const uint8_t *s, std::size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_NAME - 1, s, l); } /** Sets the SDES e-mail item to \c s with length \c l. */ - int SetEMail(const uint8_t *s, size_t l) + int SetEMail(const uint8_t *s, std::size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_EMAIL - 1, s, l); } /** Sets the SDES phone item to \c s with length \c l. */ - int SetPhone(const uint8_t *s, size_t l) + int SetPhone(const uint8_t *s, std::size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_PHONE - 1, s, l); } /** Sets the SDES location item to \c s with length \c l. */ - int SetLocation(const uint8_t *s, size_t l) + int SetLocation(const uint8_t *s, std::size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_LOCATION - 1, s, l); } /** Sets the SDES tool item to \c s with length \c l. */ - int SetTool(const uint8_t *s, size_t l) + int SetTool(const uint8_t *s, std::size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_TOOL - 1, s, l); } /** Sets the SDES note item to \c s with length \c l. */ - int SetNote(const uint8_t *s, size_t l) + int SetNote(const uint8_t *s, std::size_t l) { return SetNonPrivateItem(RTCP_SDES_ID_NOTE - 1, s, l); } @@ -111,50 +111,50 @@ public: * the value string specified by \c value with length \c valuelen (if the maximum allowed * number of prefixes was reached, the error code \c ERR_RTP_SDES_MAXPRIVITEMS is returned. */ - int SetPrivateValue(const uint8_t *prefix, size_t prefixlen, const uint8_t *value, size_t valuelen); + int SetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, const uint8_t *value, std::size_t valuelen); /** Deletes the entry for the prefix specified by \c s with length \c len. */ - int DeletePrivatePrefix(const uint8_t *s, size_t len); + int DeletePrivatePrefix(const uint8_t *s, std::size_t len); #endif // RTP_SUPPORT_SDESPRIV /** Returns the SDES CNAME item and stores its length in \c len. */ - uint8_t *GetCNAME(size_t *len) const + uint8_t *GetCNAME(std::size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_CNAME - 1, len); } /** Returns the SDES name item and stores its length in \c len. */ - uint8_t *GetName(size_t *len) const + uint8_t *GetName(std::size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_NAME - 1, len); } /** Returns the SDES e-mail item and stores its length in \c len. */ - uint8_t *GetEMail(size_t *len) const + uint8_t *GetEMail(std::size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_EMAIL - 1, len); } /** Returns the SDES phone item and stores its length in \c len. */ - uint8_t *GetPhone(size_t *len) const + uint8_t *GetPhone(std::size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_PHONE - 1, len); } /** Returns the SDES location item and stores its length in \c len. */ - uint8_t *GetLocation(size_t *len) const + uint8_t *GetLocation(std::size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_LOCATION - 1, len); } /** Returns the SDES tool item and stores its length in \c len. */ - uint8_t *GetTool(size_t *len) const + uint8_t *GetTool(std::size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_TOOL - 1, len); } /** Returns the SDES note item and stores its length in \c len. */ - uint8_t *GetNote(size_t *len) const + uint8_t *GetNote(std::size_t *len) const { return GetNonPrivateItem(RTCP_SDES_ID_NOTE - 1, len); } @@ -169,7 +169,7 @@ public: * then stored in \c value and \c valuelen. Otherwise, * it returns \c false. */ - bool GetNextPrivateValue(uint8_t **prefix, size_t *prefixlen, uint8_t **value, size_t *valuelen); + bool GetNextPrivateValue(uint8_t **prefix, std::size_t *prefixlen, uint8_t **value, std::size_t *valuelen); /** Returns SDES priv item information. * Looks for the entry which corresponds to the SDES private @@ -178,16 +178,16 @@ public: * value and its length in \c value and \c valuelen * respectively. */ - bool GetPrivateValue(const uint8_t *prefix, size_t prefixlen, uint8_t **value, size_t *valuelen) const; + bool GetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, uint8_t **value, std::size_t *valuelen) const; #endif // RTP_SUPPORT_SDESPRIV private: - int SetNonPrivateItem(int itemno, const uint8_t *s, size_t l) + int SetNonPrivateItem(int itemno, const uint8_t *s, std::size_t l) { if (l > RTCP_SDES_MAXITEMLENGTH) return ERR_RTP_SDES_LENGTHTOOBIG; return nonprivateitems[itemno].SetInfo(s, l); } - uint8_t *GetNonPrivateItem(int itemno, size_t *len) const + uint8_t *GetNonPrivateItem(int itemno, std::size_t *len) const { return nonprivateitems[itemno].GetInfo(len); } @@ -205,17 +205,17 @@ private: if (str) delete[] str; } - uint8_t *GetInfo(size_t *len) const + uint8_t *GetInfo(std::size_t *len) const { *len = length; return str; } - int SetInfo(const uint8_t *s, size_t len) + int SetInfo(const uint8_t *s, std::size_t len) { return SetString(&str, &length, s, len); } protected: - int SetString(uint8_t **dest, size_t *destlen, const uint8_t *s, size_t len) + int SetString(uint8_t **dest, std::size_t *destlen, const uint8_t *s, std::size_t len) { if (len <= 0) { @@ -240,7 +240,7 @@ private: } private: uint8_t *str; - size_t length; + std::size_t length; }; SDESItem nonprivateitems[RTCP_SDES_NUMITEMS_NONPRIVATE]; @@ -259,18 +259,18 @@ private: if (prefix) delete[] prefix; } - uint8_t *GetPrefix(size_t *len) const + uint8_t *GetPrefix(std::size_t *len) const { *len = prefixlen; return prefix; } - int SetPrefix(const uint8_t *s, size_t len) + int SetPrefix(const uint8_t *s, std::size_t len) { return SetString(&prefix, &prefixlen, s, len); } private: uint8_t *prefix; - size_t prefixlen; + std::size_t prefixlen; }; std::list privitems; diff --git a/qrtplib/rtcpsdespacket.cpp b/qrtplib/rtcpsdespacket.cpp index 03663b18a..5c40d7f10 100644 --- a/qrtplib/rtcpsdespacket.cpp +++ b/qrtplib/rtcpsdespacket.cpp @@ -35,7 +35,7 @@ namespace qrtplib { -RTCPSDESPacket::RTCPSDESPacket(uint8_t *data, size_t datalength) : +RTCPSDESPacket::RTCPSDESPacket(uint8_t *data, std::size_t datalength) : RTCPPacket(SDES, data, datalength) { knownformat = false; @@ -44,16 +44,16 @@ RTCPSDESPacket::RTCPSDESPacket(uint8_t *data, size_t datalength) : curchunknum = 0; RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; - size_t len = datalength; + std::size_t len = datalength; if (hdr->padding) { uint8_t padcount = data[datalength - 1]; if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) return; - if (((size_t) padcount) >= len) + if (((std::size_t) padcount) >= len) return; - len -= (size_t) padcount; + len -= (std::size_t) padcount; } if (hdr->count == 0) @@ -93,10 +93,10 @@ RTCPSDESPacket::RTCPSDESPacket(uint8_t *data, size_t datalength) : len--; chunkoffset++; - size_t r = (chunkoffset & 0x03); + std::size_t r = (chunkoffset & 0x03); if (r != 0) { - size_t addoffset = 4 - r; + std::size_t addoffset = 4 - r; if (addoffset > len) return; @@ -113,7 +113,7 @@ RTCPSDESPacket::RTCPSDESPacket(uint8_t *data, size_t datalength) : len -= sizeof(RTCPSDESHeader); chunkoffset += sizeof(RTCPSDESHeader); - size_t itemlen = (size_t)(sdeshdr->length); + std::size_t itemlen = (std::size_t)(sdeshdr->length); if (itemlen > len) return; diff --git a/qrtplib/rtcpsdespacket.h b/qrtplib/rtcpsdespacket.h index 0cdf5639b..72cdb5b3c 100644 --- a/qrtplib/rtcpsdespacket.h +++ b/qrtplib/rtcpsdespacket.h @@ -73,7 +73,7 @@ public: * is referenced inside the class (no copy of the data is made) one must make sure that the memory it * points to is valid as long as the class instance exists. */ - RTCPSDESPacket(uint8_t *data, size_t datalen); + RTCPSDESPacket(uint8_t *data, std::size_t datalen); ~RTCPSDESPacket() { } @@ -115,7 +115,7 @@ public: ItemType GetItemType() const; /** Returns the item length of the current item in the current chunk. */ - size_t GetItemLength() const; + std::size_t GetItemLength() const; /** Returns the item data of the current item in the current chunk. */ uint8_t *GetItemData(); @@ -124,7 +124,7 @@ public: /** If the current item is an SDES PRIV item, this function returns the length of the * prefix string of the private item. */ - size_t GetPRIVPrefixLength() const; + std::size_t GetPRIVPrefixLength() const; /** If the current item is an SDES PRIV item, this function returns actual data of the * prefix string. @@ -134,7 +134,7 @@ public: /** If the current item is an SDES PRIV item, this function returns the length of the * value string of the private item. */ - size_t GetPRIVValueLength() const; + std::size_t GetPRIVValueLength() const; /** If the current item is an SDES PRIV item, this function returns actual value data of the * private item. @@ -146,7 +146,7 @@ private: RTPEndian m_endian; uint8_t *currentchunk; int curchunknum; - size_t itemoffset; + std::size_t itemoffset; }; inline int RTCPSDESPacket::GetChunkCount() const @@ -179,13 +179,13 @@ inline bool RTCPSDESPacket::GotoNextChunk() if (curchunknum == GetChunkCount()) return false; - size_t offset = sizeof(uint32_t); + std::size_t offset = sizeof(uint32_t); RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + sizeof(uint32_t)); while (sdeshdr->sdesid != 0) { offset += sizeof(RTCPSDESHeader); - offset += (size_t)(sdeshdr->length); + offset += (std::size_t)(sdeshdr->length); sdeshdr = (RTCPSDESHeader *) (currentchunk + offset); } offset++; // for the zero byte @@ -231,9 +231,9 @@ inline bool RTCPSDESPacket::GotoNextItem() if (sdeshdr->sdesid == 0) return false; - size_t offset = itemoffset; + std::size_t offset = itemoffset; offset += sizeof(RTCPSDESHeader); - offset += (size_t)(sdeshdr->length); + offset += (std::size_t)(sdeshdr->length); sdeshdr = (RTCPSDESHeader *) (currentchunk + offset); if (sdeshdr->sdesid == 0) return false; @@ -274,7 +274,7 @@ inline RTCPSDESPacket::ItemType RTCPSDESPacket::GetItemType() const return Unknown; } -inline size_t RTCPSDESPacket::GetItemLength() const +inline std::size_t RTCPSDESPacket::GetItemLength() const { if (!knownformat) return None; @@ -283,7 +283,7 @@ inline size_t RTCPSDESPacket::GetItemLength() const RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); if (sdeshdr->sdesid == 0) return 0; - return (size_t)(sdeshdr->length); + return (std::size_t)(sdeshdr->length); } inline uint8_t *RTCPSDESPacket::GetItemData() @@ -299,7 +299,7 @@ inline uint8_t *RTCPSDESPacket::GetItemData() } #ifdef RTP_SUPPORT_SDESPRIV -inline size_t RTCPSDESPacket::GetPRIVPrefixLength() const +inline std::size_t RTCPSDESPacket::GetPRIVPrefixLength() const { if (!knownformat) return 0; @@ -311,8 +311,8 @@ inline size_t RTCPSDESPacket::GetPRIVPrefixLength() const if (sdeshdr->length == 0) return 0; uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); - size_t prefixlength = (size_t)(*preflen); - if (prefixlength > (size_t)((sdeshdr->length) - 1)) + std::size_t prefixlength = (std::size_t)(*preflen); + if (prefixlength > (std::size_t)((sdeshdr->length) - 1)) return 0; return prefixlength; } @@ -329,15 +329,15 @@ inline uint8_t *RTCPSDESPacket::GetPRIVPrefixData() if (sdeshdr->length == 0) return 0; uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); - size_t prefixlength = (size_t)(*preflen); - if (prefixlength > (size_t)((sdeshdr->length) - 1)) + std::size_t prefixlength = (std::size_t)(*preflen); + if (prefixlength > (std::size_t)((sdeshdr->length) - 1)) return 0; if (prefixlength == 0) return 0; return (currentchunk + itemoffset + sizeof(RTCPSDESHeader) + 1); } -inline size_t RTCPSDESPacket::GetPRIVValueLength() const +inline std::size_t RTCPSDESPacket::GetPRIVValueLength() const { if (!knownformat) return 0; @@ -349,10 +349,10 @@ inline size_t RTCPSDESPacket::GetPRIVValueLength() const if (sdeshdr->length == 0) return 0; uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); - size_t prefixlength = (size_t)(*preflen); - if (prefixlength > (size_t)((sdeshdr->length) - 1)) + std::size_t prefixlength = (std::size_t)(*preflen); + if (prefixlength > (std::size_t)((sdeshdr->length) - 1)) return 0; - return ((size_t)(sdeshdr->length)) - prefixlength - 1; + return ((std::size_t)(sdeshdr->length)) - prefixlength - 1; } inline uint8_t *RTCPSDESPacket::GetPRIVValueData() @@ -367,10 +367,10 @@ inline uint8_t *RTCPSDESPacket::GetPRIVValueData() if (sdeshdr->length == 0) return 0; uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); - size_t prefixlength = (size_t)(*preflen); - if (prefixlength > (size_t)((sdeshdr->length) - 1)) + std::size_t prefixlength = (std::size_t)(*preflen); + if (prefixlength > (std::size_t)((sdeshdr->length) - 1)) return 0; - size_t valuelen = ((size_t)(sdeshdr->length)) - prefixlength - 1; + std::size_t valuelen = ((std::size_t)(sdeshdr->length)) - prefixlength - 1; if (valuelen == 0) return 0; return (currentchunk + itemoffset + sizeof(RTCPSDESHeader) + 1 + prefixlength); diff --git a/qrtplib/rtcpsrpacket.cpp b/qrtplib/rtcpsrpacket.cpp index 805699018..119bb102a 100644 --- a/qrtplib/rtcpsrpacket.cpp +++ b/qrtplib/rtcpsrpacket.cpp @@ -35,14 +35,14 @@ namespace qrtplib { -RTCPSRPacket::RTCPSRPacket(uint8_t *data, size_t datalength) : +RTCPSRPacket::RTCPSRPacket(uint8_t *data, std::size_t datalength) : RTCPPacket(SR, data, datalength) { knownformat = false; RTCPCommonHeader *hdr; - size_t len = datalength; - size_t expectedlength; + std::size_t len = datalength; + std::size_t expectedlength; hdr = (RTCPCommonHeader *) data; if (hdr->padding) @@ -50,9 +50,9 @@ RTCPSRPacket::RTCPSRPacket(uint8_t *data, size_t datalength) : uint8_t padcount = data[datalength - 1]; if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) return; - if (((size_t) padcount) >= len) + if (((std::size_t) padcount) >= len) return; - len -= (size_t) padcount; + len -= (std::size_t) padcount; } expectedlength = sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport); diff --git a/qrtplib/rtcpsrpacket.h b/qrtplib/rtcpsrpacket.h index bbd968d73..cb6c6e29d 100644 --- a/qrtplib/rtcpsrpacket.h +++ b/qrtplib/rtcpsrpacket.h @@ -58,7 +58,7 @@ public: * is referenced inside the class (no copy of the data is made) one must make sure that the memory it * points to is valid as long as the class instance exists. */ - RTCPSRPacket(uint8_t *data, size_t datalength); + RTCPSRPacket(uint8_t *data, std::size_t datalength); ~RTCPSRPacket() { } diff --git a/qrtplib/rtcpunknownpacket.h b/qrtplib/rtcpunknownpacket.h index 6575cbd18..8bb3e3e4a 100644 --- a/qrtplib/rtcpunknownpacket.h +++ b/qrtplib/rtcpunknownpacket.h @@ -59,7 +59,7 @@ public: * is referenced inside the class (no copy of the data is made) one must make sure that the memory it * points to is valid as long as the class instance exists. */ - RTCPUnknownPacket(uint8_t *data, size_t datalen) : + RTCPUnknownPacket(uint8_t *data, std::size_t datalen) : RTCPPacket(Unknown, data, datalen) { // Since we don't expect a format, we'll trivially put knownformat = true diff --git a/qrtplib/rtpexternaltransmitter.cpp b/qrtplib/rtpexternaltransmitter.cpp index 1c909f4ad..83c6cec08 100644 --- a/qrtplib/rtpexternaltransmitter.cpp +++ b/qrtplib/rtpexternaltransmitter.cpp @@ -69,7 +69,7 @@ int RTPExternalTransmitter::Init(bool tsafe) return 0; } -int RTPExternalTransmitter::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) +int RTPExternalTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) { const RTPExternalTransmissionParams *params; int status; @@ -163,7 +163,7 @@ void RTPExternalTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) delete i; } -int RTPExternalTransmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) +int RTPExternalTransmitter::GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) { if (!init) return ERR_RTP_EXTERNALTRANS_NOTINIT; @@ -297,7 +297,7 @@ int RTPExternalTransmitter::AbortWait() return 0; } -int RTPExternalTransmitter::SendRTPData(const void *data, size_t len) +int RTPExternalTransmitter::SendRTPData(const void *data, std::size_t len) { if (!init) return ERR_RTP_EXTERNALTRANS_NOTINIT; @@ -322,7 +322,7 @@ int RTPExternalTransmitter::SendRTPData(const void *data, size_t len) return 0; } -int RTPExternalTransmitter::SendRTCPData(const void *data, size_t len) +int RTPExternalTransmitter::SendRTCPData(const void *data, std::size_t len) { if (!init) return ERR_RTP_EXTERNALTRANS_NOTINIT; @@ -424,7 +424,7 @@ void RTPExternalTransmitter::ClearAcceptList() { } -int RTPExternalTransmitter::SetMaximumPacketSize(size_t s) +int RTPExternalTransmitter::SetMaximumPacketSize(std::size_t s) { if (!init) return ERR_RTP_EXTERNALTRANS_NOTINIT; @@ -490,7 +490,7 @@ void RTPExternalTransmitter::FlushPackets() rawpacketlist.clear(); } -void RTPExternalTransmitter::InjectRTP(const void *data, size_t len, const RTPAddress &a) +void RTPExternalTransmitter::InjectRTP(const void *data, std::size_t len, const RTPAddress &a) { if (!init) return; @@ -534,7 +534,7 @@ void RTPExternalTransmitter::InjectRTP(const void *data, size_t len, const RTPAd } -void RTPExternalTransmitter::InjectRTCP(const void *data, size_t len, const RTPAddress &a) +void RTPExternalTransmitter::InjectRTCP(const void *data, std::size_t len, const RTPAddress &a) { if (!init) return; @@ -578,7 +578,7 @@ void RTPExternalTransmitter::InjectRTCP(const void *data, size_t len, const RTPA } -void RTPExternalTransmitter::InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a) +void RTPExternalTransmitter::InjectRTPorRTCP(const void *data, std::size_t len, const RTPAddress &a) { if (!init) return; diff --git a/qrtplib/rtpexternaltransmitter.h b/qrtplib/rtpexternaltransmitter.h index c43c122d7..9cfc82fdc 100644 --- a/qrtplib/rtpexternaltransmitter.h +++ b/qrtplib/rtpexternaltransmitter.h @@ -67,10 +67,10 @@ public: } /** This member function will be called when RTP data needs to be transmitted. */ - virtual bool SendRTP(const void *data, size_t len) = 0; + virtual bool SendRTP(const void *data, std::size_t len) = 0; /** This member function will be called when an RTCP packet needs to be transmitted. */ - virtual bool SendRTCP(const void *data, size_t len) = 0; + virtual bool SendRTCP(const void *data, std::size_t len) = 0; /** Used to identify if an RTPAddress instance originated from this sender (to be able to detect own packets). */ virtual bool ComesFromThisSender(const RTPAddress *a) = 0; @@ -95,13 +95,13 @@ public: } /** This function can be called to insert an RTP packet into the transmission component. */ - void InjectRTP(const void *data, size_t len, const RTPAddress &a); + void InjectRTP(const void *data, std::size_t len, const RTPAddress &a); /** This function can be called to insert an RTCP packet into the transmission component. */ - void InjectRTCP(const void *data, size_t len, const RTPAddress &a); + void InjectRTCP(const void *data, std::size_t len, const RTPAddress &a); /** Use this function to inject an RTP or RTCP packet and the transmitter will try to figure out which type of packet it is. */ - void InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a); + void InjectRTPorRTCP(const void *data, std::size_t len, const RTPAddress &a); private: RTPExternalTransmitter *transmitter; }; @@ -167,14 +167,14 @@ public: ~RTPExternalTransmitter(); int Init(bool treadsafe); - int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); void Destroy(); RTPTransmissionInfo *GetTransmissionInfo(); void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer, size_t *bufferlength); + int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() + std::size_t GetHeaderOverhead() { return headersize; } @@ -183,8 +183,8 @@ public: int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); int AbortWait(); - int SendRTPData(const void *data, size_t len); - int SendRTCPData(const void *data, size_t len); + int SendRTPData(const void *data, std::size_t len); + int SendRTCPData(const void *data, std::size_t len); int AddDestination(const RTPAddress &addr); int DeleteDestination(const RTPAddress &addr); @@ -202,14 +202,14 @@ public: int AddToAcceptList(const RTPAddress &addr); int DeleteFromAcceptList(const RTPAddress &addr); void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); + int SetMaximumPacketSize(std::size_t s); bool NewDataAvailable(); RTPRawPacket *GetNextPacket(); - void InjectRTP(const void *data, size_t len, const RTPAddress &a); - void InjectRTCP(const void *data, size_t len, const RTPAddress &a); - void InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a); + void InjectRTP(const void *data, std::size_t len, const RTPAddress &a); + void InjectRTCP(const void *data, std::size_t len, const RTPAddress &a); + void InjectRTPorRTCP(const void *data, std::size_t len, const RTPAddress &a); private: void FlushPackets(); @@ -222,26 +222,26 @@ private: std::list rawpacketlist; uint8_t *localhostname; - size_t localhostnamelength; + std::size_t localhostnamelength; - size_t maxpacksize; + std::size_t maxpacksize; int headersize; RTPAbortDescriptors m_abortDesc; int m_abortCount; }; -inline void RTPExternalPacketInjecter::InjectRTP(const void *data, size_t len, const RTPAddress &a) +inline void RTPExternalPacketInjecter::InjectRTP(const void *data, std::size_t len, const RTPAddress &a) { transmitter->InjectRTP(data, len, a); } -inline void RTPExternalPacketInjecter::InjectRTCP(const void *data, size_t len, const RTPAddress &a) +inline void RTPExternalPacketInjecter::InjectRTCP(const void *data, std::size_t len, const RTPAddress &a) { transmitter->InjectRTCP(data, len, a); } -inline void RTPExternalPacketInjecter::InjectRTPorRTCP(const void *data, size_t len, const RTPAddress &a) +inline void RTPExternalPacketInjecter::InjectRTPorRTCP(const void *data, std::size_t len, const RTPAddress &a) { transmitter->InjectRTPorRTCP(data, len, a); } diff --git a/qrtplib/rtpinternalsourcedata.cpp b/qrtplib/rtpinternalsourcedata.cpp index e13cffa5f..4995dd97f 100644 --- a/qrtplib/rtpinternalsourcedata.cpp +++ b/qrtplib/rtpinternalsourcedata.cpp @@ -147,7 +147,7 @@ int RTPInternalSourceData::ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &r return 0; } -int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, size_t itemlen, const RTPTime &receivetime, bool *cnamecollis) +int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, std::size_t itemlen, const RTPTime &receivetime, bool *cnamecollis) { *cnamecollis = false; @@ -157,7 +157,7 @@ int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, { case RTCP_SDES_ID_CNAME: { - size_t curlen; + std::size_t curlen; uint8_t *oldcname; // NOTE: we're going to make sure that the CNAME is only set once. @@ -182,7 +182,7 @@ int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, break; case RTCP_SDES_ID_NAME: { - size_t oldlen; + std::size_t oldlen; SDESinf.GetName(&oldlen); if (oldlen == 0) // Name not set @@ -191,7 +191,7 @@ int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, break; case RTCP_SDES_ID_EMAIL: { - size_t oldlen; + std::size_t oldlen; SDESinf.GetEMail(&oldlen); if (oldlen == 0) @@ -204,7 +204,7 @@ int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, return SDESinf.SetLocation(data, itemlen); case RTCP_SDES_ID_TOOL: { - size_t oldlen; + std::size_t oldlen; SDESinf.GetTool(&oldlen); if (oldlen == 0) @@ -220,7 +220,7 @@ int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, #ifdef RTP_SUPPORT_SDESPRIV -int RTPInternalSourceData::ProcessPrivateSDESItem(const uint8_t *prefix, size_t prefixlen, const uint8_t *value, size_t valuelen, const RTPTime &receivetime) +int RTPInternalSourceData::ProcessPrivateSDESItem(const uint8_t *prefix, std::size_t prefixlen, const uint8_t *value, std::size_t valuelen, const RTPTime &receivetime) { int status; @@ -233,7 +233,7 @@ int RTPInternalSourceData::ProcessPrivateSDESItem(const uint8_t *prefix, size_t #endif // RTP_SUPPORT_SDESPRIV -int RTPInternalSourceData::ProcessBYEPacket(const uint8_t *reason, size_t reasonlen, const RTPTime &receivetime) +int RTPInternalSourceData::ProcessBYEPacket(const uint8_t *reason, std::size_t reasonlen, const RTPTime &receivetime) { if (byereason) { diff --git a/qrtplib/rtpinternalsourcedata.h b/qrtplib/rtpinternalsourcedata.h index 9702d4fc7..120e903d6 100644 --- a/qrtplib/rtpinternalsourcedata.h +++ b/qrtplib/rtpinternalsourcedata.h @@ -70,11 +70,11 @@ public: { stats.SetLastMessageTime(receivetime); } - int ProcessSDESItem(uint8_t sdesid, const uint8_t *data, size_t itemlen, const RTPTime &receivetime, bool *cnamecollis); + int ProcessSDESItem(uint8_t sdesid, const uint8_t *data, std::size_t itemlen, const RTPTime &receivetime, bool *cnamecollis); #ifdef RTP_SUPPORT_SDESPRIV - int ProcessPrivateSDESItem(const uint8_t *prefix, size_t prefixlen, const uint8_t *value, size_t valuelen, const RTPTime &receivetime); + int ProcessPrivateSDESItem(const uint8_t *prefix, std::size_t prefixlen, const uint8_t *value, std::size_t valuelen, const RTPTime &receivetime); #endif // RTP_SUPPORT_SDESPRIV - int ProcessBYEPacket(const uint8_t *reason, size_t reasonlen, const RTPTime &receivetime); + int ProcessBYEPacket(const uint8_t *reason, std::size_t reasonlen, const RTPTime &receivetime); int SetRTPDataAddress(const RTPAddress *a); int SetRTCPDataAddress(const RTPAddress *a); diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp index c6d39368a..08419ef8e 100644 --- a/qrtplib/rtppacket.cpp +++ b/qrtplib/rtppacket.cpp @@ -67,8 +67,8 @@ RTPPacket::RTPPacket(RTPRawPacket &rawpack) : error = ParseRawPacket(rawpack); } -RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, size_t maxpacksize) : +RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, std::size_t maxpacksize) : receivetime(0, 0) { Clear(); @@ -76,8 +76,8 @@ RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloa 0, maxpacksize); } -RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, size_t buffersize) : +RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, std::size_t buffersize) : receivetime(0, 0) { Clear(); @@ -93,7 +93,7 @@ RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloa int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) { uint8_t *packetbytes; - size_t packetlen; + std::size_t packetlen; uint8_t payloadtype; RTPHeader *rtpheader; bool marker; @@ -209,8 +209,8 @@ uint32_t RTPPacket::GetCSRC(int num) const return csrcval_hbo; } -int RTPPacket::BuildPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, size_t maxsize) +int RTPPacket::BuildPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, std::size_t maxsize) { if (numcsrcs > RTP_MAXCSRCS) return ERR_RTP_PACKET_TOOMANYCSRCS; @@ -221,11 +221,11 @@ int RTPPacket::BuildPacket(uint8_t payloadtype, const void *payloaddata, size_t return ERR_RTP_PACKET_BADPAYLOADTYPE; packetlength = sizeof(RTPHeader); - packetlength += sizeof(uint32_t) * ((size_t) numcsrcs); + packetlength += sizeof(uint32_t) * ((std::size_t) numcsrcs); if (gotextension) { packetlength += sizeof(RTPExtensionHeader); - packetlength += sizeof(uint32_t) * ((size_t) extensionlen_numwords); + packetlength += sizeof(uint32_t) * ((std::size_t) extensionlen_numwords); } packetlength += payloadlen; @@ -264,7 +264,7 @@ int RTPPacket::BuildPacket(uint8_t payloadtype, const void *payloaddata, size_t RTPPacket::ssrc = ssrc; RTPPacket::payloadlength = payloadlen; RTPPacket::extid = extensionid; - RTPPacket::extensionlength = ((size_t) extensionlen_numwords) * sizeof(uint32_t); + RTPPacket::extensionlength = ((std::size_t) extensionlen_numwords) * sizeof(uint32_t); rtphdr = (RTPHeader *) packet; rtphdr->version = RTP_VERSION; @@ -290,7 +290,7 @@ int RTPPacket::BuildPacket(uint8_t payloadtype, const void *payloaddata, size_t for (i = 0; i < numcsrcs; i++, curcsrc++) *curcsrc = qToBigEndian(csrcs[i]); - payload = packet + sizeof(RTPHeader) + ((size_t) numcsrcs) * sizeof(uint32_t); + payload = packet + sizeof(RTPHeader) + ((std::size_t) numcsrcs) * sizeof(uint32_t); if (gotextension) { RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *) payload; diff --git a/qrtplib/rtppacket.h b/qrtplib/rtppacket.h index ccb93b704..8e8797d3f 100644 --- a/qrtplib/rtppacket.h +++ b/qrtplib/rtppacket.h @@ -68,13 +68,13 @@ public: * \c maxpacksize. The arguments of the constructor are self-explanatory. Note that the size of a header * extension is specified in a number of 32-bit words. A memory manager can be installed. */ - RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, size_t maxpacksize); + RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, std::size_t maxpacksize); /** This constructor is similar to the other constructor, but here data is stored in an external buffer * \c buffer with size \c buffersize. */ - RTPPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, size_t buffersize); + RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, std::size_t buffersize); virtual ~RTPPacket() { @@ -163,13 +163,13 @@ public: } /** Returns the length of the entire packet. */ - size_t GetPacketLength() const + std::size_t GetPacketLength() const { return packetlength; } /** Returns the payload length. */ - size_t GetPayloadLength() const + std::size_t GetPayloadLength() const { return payloadlength; } @@ -187,7 +187,7 @@ public: } /** Returns the length of the header extension data. */ - size_t GetExtensionLength() const + std::size_t GetExtensionLength() const { return extensionlength; } @@ -204,8 +204,8 @@ public: private: void Clear(); int ParseRawPacket(RTPRawPacket &rawpack); - int BuildPacket(uint8_t payloadtype, const void *payloaddata, size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, size_t maxsize); + int BuildPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, std::size_t maxsize); RTPEndian m_endian; int error; @@ -216,11 +216,11 @@ private: uint8_t payloadtype; uint32_t extseqnr, timestamp, ssrc; uint8_t *packet, *payload; - size_t packetlength, payloadlength; + std::size_t packetlength, payloadlength; uint16_t extid; uint8_t *extension; - size_t extensionlength; + std::size_t extensionlength; bool externalbuffer; diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib/rtppacketbuilder.cpp index 5df8c1e17..10f97e4f4 100644 --- a/qrtplib/rtppacketbuilder.cpp +++ b/qrtplib/rtppacketbuilder.cpp @@ -54,7 +54,7 @@ RTPPacketBuilder::~RTPPacketBuilder() Destroy(); } -int RTPPacketBuilder::Init(size_t max) +int RTPPacketBuilder::Init(std::size_t max) { if (init) return ERR_RTP_PACKBUILD_ALREADYINIT; @@ -87,7 +87,7 @@ void RTPPacketBuilder::Destroy() init = false; } -int RTPPacketBuilder::SetMaximumPacketSize(size_t max) +int RTPPacketBuilder::SetMaximumPacketSize(std::size_t max) { uint8_t *newbuf; @@ -186,7 +186,7 @@ uint32_t RTPPacketBuilder::CreateNewSSRC(RTPSources &sources) return ssrc; } -int RTPPacketBuilder::BuildPacket(const void *data, size_t len) +int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; @@ -199,14 +199,14 @@ int RTPPacketBuilder::BuildPacket(const void *data, size_t len) return PrivateBuildPacket(data, len, defaultpayloadtype, defaultmark, defaulttimestampinc, false); } -int RTPPacketBuilder::BuildPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc) +int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; return PrivateBuildPacket(data, len, pt, mark, timestampinc, false); } -int RTPPacketBuilder::BuildPacketEx(const void *data, size_t len, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords) +int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; @@ -219,7 +219,7 @@ int RTPPacketBuilder::BuildPacketEx(const void *data, size_t len, uint16_t hdrex return PrivateBuildPacket(data, len, defaultpayloadtype, defaultmark, defaulttimestampinc, true, hdrextID, hdrextdata, numhdrextwords); } -int RTPPacketBuilder::BuildPacketEx(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords) +int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; @@ -227,8 +227,8 @@ int RTPPacketBuilder::BuildPacketEx(const void *data, size_t len, uint8_t pt, bo } -int RTPPacketBuilder::PrivateBuildPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID, const void *hdrextdata, - size_t numhdrextwords) +int RTPPacketBuilder::PrivateBuildPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID, const void *hdrextdata, + std::size_t numhdrextwords) { RTPPacket p(pt, data, len, seqnr, timestamp, ssrc, mark, numcsrcs, csrcs, gotextension, hdrextID, (uint16_t) numhdrextwords, hdrextdata, buffer, maxpacksize); int status = p.GetCreationError(); diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib/rtppacketbuilder.h index d7b489e0b..7bda2c4a9 100644 --- a/qrtplib/rtppacketbuilder.h +++ b/qrtplib/rtppacketbuilder.h @@ -63,7 +63,7 @@ public: ~RTPPacketBuilder(); /** Initializes the builder to only allow packets with a size below \c maxpacksize. */ - int Init(size_t maxpacksize); + int Init(std::size_t maxpacksize); /** Cleans up the builder. */ void Destroy(); @@ -85,7 +85,7 @@ public: } /** Sets the maximum allowed packet size to \c maxpacksize. */ - int SetMaximumPacketSize(size_t maxpacksize); + int SetMaximumPacketSize(std::size_t maxpacksize); /** Adds a CSRC to the CSRC list which will be stored in the RTP packets. */ int AddCSRC(uint32_t csrc); @@ -101,14 +101,14 @@ public: * and timestamp increment used will be those that have been set using the \c SetDefault * functions below. */ - int BuildPacket(const void *data, size_t len); + int BuildPacket(const void *data, std::size_t len); /** Builds a packet with payload \c data and payload length \c len. * Builds a packet with payload \c data and payload length \c len. The payload type will be * set to \c pt, the marker bit to \c mark and after building this packet, the timestamp will * be incremented with \c timestamp. */ - int BuildPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc); + int BuildPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc); /** Builds a packet with payload \c data and payload length \c len. * Builds a packet with payload \c data and payload length \c len. The payload type, marker @@ -117,7 +117,7 @@ public: * \c hdrextID and data \c hdrextdata. The length of the header extension data is given by * \c numhdrextwords which expresses the length in a number of 32-bit words. */ - int BuildPacketEx(const void *data, size_t len, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords); + int BuildPacketEx(const void *data, std::size_t len, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); /** Builds a packet with payload \c data and payload length \c len. * Builds a packet with payload \c data and payload length \c len. The payload type will be set @@ -126,7 +126,7 @@ public: * with identifier \c hdrextID and data \c hdrextdata. The length of the header extension * data is given by \c numhdrextwords which expresses the length in a number of 32-bit words. */ - int BuildPacketEx(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords); + int BuildPacketEx(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); /** Returns a pointer to the last built RTP packet data. */ uint8_t *GetPacket() @@ -137,7 +137,7 @@ public: } /** Returns the size of the last built RTP packet. */ - size_t GetPacketLength() + std::size_t GetPacketLength() { if (!init) return 0; @@ -235,13 +235,13 @@ public: ssrc = s; } private: - int PrivateBuildPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID = 0, const void *hdrextdata = 0, - size_t numhdrextwords = 0); + int PrivateBuildPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID = 0, const void *hdrextdata = 0, + std::size_t numhdrextwords = 0); RTPRandom &rtprnd; - size_t maxpacksize; + std::size_t maxpacksize; uint8_t *buffer; - size_t packetlength; + std::size_t packetlength; uint32_t numpayloadbytes; uint32_t numpackets; diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h index 3db022298..05ab2ed7c 100644 --- a/qrtplib/rtprawpacket.h +++ b/qrtplib/rtprawpacket.h @@ -57,7 +57,7 @@ public: * The flag which indicates whether this data is RTP or RTCP data is set to \c rtp. A memory * manager can be installed as well. */ - RTPRawPacket(uint8_t *data, size_t datalen, RTPAddress *address, RTPTime &recvtime, bool rtp); + RTPRawPacket(uint8_t *data, std::size_t datalen, RTPAddress *address, RTPTime &recvtime, bool rtp); ~RTPRawPacket(); /** Returns the pointer to the data which is contained in this packet. */ @@ -67,7 +67,7 @@ public: } /** Returns the length of the packet described by this instance. */ - size_t GetDataLength() const + std::size_t GetDataLength() const { return packetdatalength; } @@ -110,7 +110,7 @@ public: /** Deallocates the previously stored data and replaces it with the data that's * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ - void SetData(uint8_t *data, size_t datalen); + void SetData(uint8_t *data, std::size_t datalen); /** Deallocates the currently stored RTPAddress instance and replaces it * with the one that's specified (you probably don't need this function). */ @@ -119,13 +119,13 @@ private: void DeleteData(); uint8_t *packetdata; - size_t packetdatalength; + std::size_t packetdatalength; RTPTime receivetime; RTPAddress *senderaddress; bool isrtp; }; -inline RTPRawPacket::RTPRawPacket(uint8_t *data, size_t datalen, RTPAddress *address, RTPTime &recvtime, bool rtp) : +inline RTPRawPacket::RTPRawPacket(uint8_t *data, std::size_t datalen, RTPAddress *address, RTPTime &recvtime, bool rtp) : receivetime(recvtime) { packetdata = data; @@ -155,7 +155,7 @@ inline uint8_t *RTPRawPacket::AllocateBytes(int recvlen) const return new uint8_t[recvlen]; } -inline void RTPRawPacket::SetData(uint8_t *data, size_t datalen) +inline void RTPRawPacket::SetData(uint8_t *data, std::size_t datalen) { if (packetdata) delete[] packetdata; diff --git a/qrtplib/rtpselect.h b/qrtplib/rtpselect.h index b525a87b0..679cef0cf 100644 --- a/qrtplib/rtpselect.h +++ b/qrtplib/rtpselect.h @@ -57,13 +57,13 @@ namespace qrtplib { -inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsocks, RTPTime timeout) +inline int RTPSelect(const SocketType *sockets, int8_t *readflags, std::size_t numsocks, RTPTime timeout) { using namespace std; vector fds(numsocks); - for (size_t i = 0; i < numsocks; i++) + for (std::size_t i = 0; i < numsocks; i++) { fds[i].fd = sockets[i]; fds[i].events = POLLIN; @@ -98,7 +98,7 @@ inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsoc if (status > 0) { - for (size_t i = 0; i < numsocks; i++) + for (std::size_t i = 0; i < numsocks; i++) { if (fds[i].revents) readflags[i] = 1; @@ -131,7 +131,7 @@ namespace qrtplib * indefinitely if set to a negative value. The function returns the number * of sockets that have data incoming. */ - inline int RTPSelect(const SocketType *sockets, int8_t *readflags, size_t numsocks, RTPTime timeout) + inline int RTPSelect(const SocketType *sockets, int8_t *readflags, std::size_t numsocks, RTPTime timeout) { struct timeval tv; struct timeval *pTv = 0; @@ -145,7 +145,7 @@ namespace qrtplib fd_set fdset; FD_ZERO(&fdset); - for (size_t i = 0; i < numsocks; i++) + for (std::size_t i = 0; i < numsocks; i++) { #ifndef RTP_SOCKETTYPE_WINSOCK const int setsize = FD_SETSIZE; @@ -174,7 +174,7 @@ namespace qrtplib if (status > 0) // some descriptors were set, check them { - for (size_t i = 0; i < numsocks; i++) + for (std::size_t i = 0; i < numsocks; i++) { if (FD_ISSET(sockets[i], &fdset)) readflags[i] = 1; diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index e0bc3d222..2d92c502a 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -220,7 +220,7 @@ int RTPSession::InternalCreate(const RTPSessionParams &sessparams) double timestampunit = sessparams.GetOwnTimestampUnit(); uint8_t buf[1024]; - size_t buflen = 1024; + std::size_t buflen = 1024; std::string forcedcname = sessparams.GetCNAME(); if (forcedcname.length() == 0) @@ -329,7 +329,7 @@ void RTPSession::Destroy() created = false; } -void RTPSession::BYEDestroy(const RTPTime &maxwaittime, const void *reason, size_t reasonlength) +void RTPSession::BYEDestroy(const RTPTime &maxwaittime, const void *reason, std::size_t reasonlength) { if (!created) return; @@ -474,7 +474,7 @@ void RTPSession::LeaveAllMulticastGroups() rtptrans->LeaveAllMulticastGroups(); } -int RTPSession::SendPacket(const void *data, size_t len) +int RTPSession::SendPacket(const void *data, std::size_t len) { int status; @@ -503,7 +503,7 @@ int RTPSession::SendPacket(const void *data, size_t len) return 0; } -int RTPSession::SendPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc) +int RTPSession::SendPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc) { int status; @@ -532,7 +532,7 @@ int RTPSession::SendPacket(const void *data, size_t len, uint8_t pt, bool mark, return 0; } -int RTPSession::SendPacketEx(const void *data, size_t len, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords) +int RTPSession::SendPacketEx(const void *data, std::size_t len, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) { int status; @@ -561,7 +561,7 @@ int RTPSession::SendPacketEx(const void *data, size_t len, uint16_t hdrextID, co return 0; } -int RTPSession::SendPacketEx(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords) +int RTPSession::SendPacketEx(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) { int status; @@ -592,7 +592,7 @@ int RTPSession::SendPacketEx(const void *data, size_t len, uint8_t pt, bool mark #ifdef RTP_SUPPORT_SENDAPP -int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, size_t appdatalen) +int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, std::size_t appdatalen) { int status; @@ -619,7 +619,7 @@ int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const return status; BUILDER_LOCK - size_t owncnamelen = 0; + std::size_t owncnamelen = 0; uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); if ((status = pb.AddSDESNormalItem(RTCPSDESPacket::CNAME, owncname, owncnamelen)) < 0) @@ -650,7 +650,7 @@ int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const #endif // RTP_SUPPORT_SENDAPP -int RTPSession::SendRawData(const void *data, size_t len, bool usertpchannel) +int RTPSession::SendRawData(const void *data, std::size_t len, bool usertpchannel) { if (!created) return ERR_RTP_SESSION_NOTCREATED; @@ -940,7 +940,7 @@ void RTPSession::ClearAcceptList() rtptrans->ClearAcceptList(); } -int RTPSession::SetMaximumPacketSize(size_t s) +int RTPSession::SetMaximumPacketSize(std::size_t s) { if (!created) return ERR_RTP_SESSION_NOTCREATED; @@ -1059,7 +1059,7 @@ rtcpbuilder.SetNoteInterval(count); BUILDER_UNLOCK } -int RTPSession::SetLocalName(const void *s, size_t len) +int RTPSession::SetLocalName(const void *s, std::size_t len) { if (!created) return ERR_RTP_SESSION_NOTCREATED; @@ -1071,7 +1071,7 @@ BUILDER_UNLOCK return status; } -int RTPSession::SetLocalEMail(const void *s, size_t len) +int RTPSession::SetLocalEMail(const void *s, std::size_t len) { if (!created) return ERR_RTP_SESSION_NOTCREATED; @@ -1083,7 +1083,7 @@ BUILDER_UNLOCK return status; } -int RTPSession::SetLocalLocation(const void *s, size_t len) +int RTPSession::SetLocalLocation(const void *s, std::size_t len) { if (!created) return ERR_RTP_SESSION_NOTCREATED; @@ -1095,7 +1095,7 @@ BUILDER_UNLOCK return status; } -int RTPSession::SetLocalPhone(const void *s, size_t len) +int RTPSession::SetLocalPhone(const void *s, std::size_t len) { if (!created) return ERR_RTP_SESSION_NOTCREATED; @@ -1107,7 +1107,7 @@ BUILDER_UNLOCK return status; } -int RTPSession::SetLocalTool(const void *s, size_t len) +int RTPSession::SetLocalTool(const void *s, std::size_t len) { if (!created) return ERR_RTP_SESSION_NOTCREATED; @@ -1119,7 +1119,7 @@ BUILDER_UNLOCK return status; } -int RTPSession::SetLocalNote(const void *s, size_t len) +int RTPSession::SetLocalNote(const void *s, std::size_t len) { if (!created) return ERR_RTP_SESSION_NOTCREATED; @@ -1321,7 +1321,7 @@ SOURCES_UNLOCK return 0; } -int RTPSession::CreateCNAME(uint8_t *buffer, size_t *bufferlength, bool resolve) +int RTPSession::CreateCNAME(uint8_t *buffer, std::size_t *bufferlength, bool resolve) { #ifndef WIN32 bool gotlogin = true; @@ -1370,12 +1370,12 @@ RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); #endif // WIN32 buffer[*bufferlength - 1] = 0; -size_t offset = strlen((const char *) buffer); +std::size_t offset = strlen((const char *) buffer); if (offset < (*bufferlength - 1)) buffer[offset] = (uint8_t) '@'; offset++; -size_t buflen2 = *bufferlength - offset; +std::size_t buflen2 = *bufferlength - offset; int status; if (resolve) @@ -1418,13 +1418,13 @@ deletertprnd = false; return rnew; } -int RTPSession::SendRTPData(const void *data, size_t len) +int RTPSession::SendRTPData(const void *data, std::size_t len) { if (!m_changeOutgoingData) return rtptrans->SendRTPData(data, len); void *pSendData = 0; -size_t sendLen = 0; +std::size_t sendLen = 0; int status = 0; status = OnChangeRTPOrRTCPData(data, len, true, &pSendData, &sendLen); @@ -1440,13 +1440,13 @@ OnSentRTPOrRTCPData(pSendData, sendLen, true); return status; } -int RTPSession::SendRTCPData(const void *data, size_t len) +int RTPSession::SendRTCPData(const void *data, std::size_t len) { if (!m_changeOutgoingData) return rtptrans->SendRTCPData(data, len); void *pSendData = 0; -size_t sendLen = 0; +std::size_t sendLen = 0; int status = 0; status = OnChangeRTPOrRTCPData(data, len, false, &pSendData, &sendLen); diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index 555c3d82e..960c6aa5a 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -112,7 +112,7 @@ public: * send the BYE packet. If this time expires, the session will be left without sending a BYE packet. * The BYE packet will contain as reason for leaving \c reason with length \c reasonlength. */ - void BYEDestroy(const RTPTime &maxwaittime, const void *reason, size_t reasonlength); + void BYEDestroy(const RTPTime &maxwaittime, const void *reason, std::size_t reasonlength); /** Returns whether the session has been created or not. */ bool IsActive(); @@ -146,13 +146,13 @@ public: * The used payload type, marker and timestamp increment will be those that have been set * using the \c SetDefault member functions. */ - int SendPacket(const void *data, size_t len); + int SendPacket(const void *data, std::size_t len); /** Sends the RTP packet with payload \c data which has length \c len. * It will use payload type \c pt, marker \c mark and after the packet has been built, the * timestamp will be incremented by \c timestampinc. */ - int SendPacket(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc); + int SendPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc); /** Sends the RTP packet with payload \c data which has length \c len. * The packet will contain a header extension with identifier \c hdrextID and containing data @@ -160,7 +160,7 @@ public: * number of 32-bit words. The used payload type, marker and timestamp increment will be those that * have been set using the \c SetDefault member functions. */ - int SendPacketEx(const void *data, size_t len, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords); + int SendPacketEx(const void *data, std::size_t len, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); /** Sends the RTP packet with payload \c data which has length \c len. * It will use payload type \c pt, marker \c mark and after the packet has been built, the @@ -168,7 +168,7 @@ public: * extension with identifier \c hdrextID and containing data \c hdrextdata. The length * of this data is given by \c numhdrextwords and is specified in a number of 32-bit words. */ - int SendPacketEx(const void *data, size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, size_t numhdrextwords); + int SendPacketEx(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); #ifdef RTP_SUPPORT_SENDAPP /** If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet * containing an RTCP APP packet and sends it immediately. @@ -177,13 +177,13 @@ public: * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP * specification, so use with care. */ - int SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, size_t appdatalen); + int SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, std::size_t appdatalen); #endif // RTP_SUPPORT_SENDAPP /** With this function raw data can be sent directly over the RTP or * RTCP channel (if they are different); the data is **not** passed through the * RTPSession::OnChangeRTPOrRTCPData function. */ - int SendRawData(const void *data, size_t len, bool usertpchannel); + int SendRawData(const void *data, std::size_t len, bool usertpchannel); /** Sets the default payload type for RTP packets to \c pt. */ int SetDefaultPayloadType(uint8_t pt); @@ -353,7 +353,7 @@ public: void ClearAcceptList(); /** Sets the maximum allowed packet size to \c s. */ - int SetMaximumPacketSize(size_t s); + int SetMaximumPacketSize(std::size_t s); /** Sets the session bandwidth to \c bw, which is specified in bytes per second. */ int SetSessionBandwidth(double bw); @@ -415,22 +415,22 @@ public: void SetNoteInterval(int count); /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ - int SetLocalName(const void *s, size_t len); + int SetLocalName(const void *s, std::size_t len); /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ - int SetLocalEMail(const void *s, size_t len); + int SetLocalEMail(const void *s, std::size_t len); /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ - int SetLocalLocation(const void *s, size_t len); + int SetLocalLocation(const void *s, std::size_t len); /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ - int SetLocalPhone(const void *s, size_t len); + int SetLocalPhone(const void *s, std::size_t len); /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ - int SetLocalTool(const void *s, size_t len); + int SetLocalTool(const void *s, std::size_t len); /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ - int SetLocalNote(const void *s, size_t len); + int SetLocalNote(const void *s, std::size_t len); protected: /** Allocate a user defined transmitter. @@ -461,7 +461,7 @@ protected: virtual void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); /** Is called when another CNAME was received than the one already present for source \c srcdat. */ - virtual void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, size_t cnamelength); + virtual void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, std::size_t cnamelength); /** Is called when a new entry \c srcdat is added to the source table. */ virtual void OnNewSource(RTPSourceData *srcdat); @@ -496,10 +496,10 @@ protected: virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); /** Is called when a specific SDES item was received for this source. */ - virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, size_t itemlength); + virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, std::size_t itemlength); #ifdef RTP_SUPPORT_SDESPRIV /** Is called when a specific SDES item of 'private' type was received for this source. */ - virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, const void *valuedata, size_t valuelen); + virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, std::size_t prefixlen, const void *valuedata, std::size_t valuelen); #endif // RTP_SUPPORT_SDESPRIV /** Is called when a BYE packet has been processed for source \c srcdat. */ @@ -531,12 +531,12 @@ protected: * yourself, a good way may be to do this in RTPSession::OnSentRTPOrRTCPData. If `senddata` is * set to 0, no packet will be sent out. This also provides a way to turn off sending RTCP * packets if desired. */ - virtual int OnChangeRTPOrRTCPData(const void *origdata, size_t origlen, bool isrtp, void **senddata, size_t *sendlen); + virtual int OnChangeRTPOrRTCPData(const void *origdata, std::size_t origlen, bool isrtp, void **senddata, std::size_t *sendlen); /** This function is called when an RTP or RTCP packet was sent, it can be helpful * when data was allocated in RTPSession::OnChangeRTPOrRTCPData to deallocate it * here. */ - virtual void OnSentRTPOrRTCPData(void *senddata, size_t sendlen, bool isrtp); + virtual void OnSentRTPOrRTCPData(void *senddata, std::size_t sendlen, bool isrtp); /** By overriding this function, the raw incoming data can be inspected * and modified (e.g. for encryption). @@ -559,12 +559,12 @@ protected: virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); private: int InternalCreate(const RTPSessionParams &sessparams); - int CreateCNAME(uint8_t *buffer, size_t *bufferlength, bool resolve); + int CreateCNAME(uint8_t *buffer, std::size_t *bufferlength, bool resolve); int ProcessPolledData(); int ProcessRTCPCompoundPacket(RTCPCompoundPacket &rtcpcomppack, RTPRawPacket *pack); RTPRandom *GetRandomNumberGenerator(RTPRandom *r); - int SendRTPData(const void *data, size_t len); - int SendRTCPData(const void *data, size_t len); + int SendRTPData(const void *data, std::size_t len); + int SendRTCPData(const void *data, std::size_t len); RTPRandom *rtprnd; bool deletertprnd; @@ -575,7 +575,7 @@ private: bool usingpollthread, needthreadsafety; bool acceptownpackets; bool useSR_BYEifpossible; - size_t maxpacksize; + std::size_t maxpacksize; double sessionbandwidth; double controlfragment; double sendermultiplier; @@ -612,7 +612,7 @@ inline void RTPSession::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime inline void RTPSession::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool) { } -inline void RTPSession::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, size_t) +inline void RTPSession::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, std::size_t) { } inline void RTPSession::OnNewSource(RTPSourceData *) @@ -645,12 +645,12 @@ inline void RTPSession::OnRTCPSenderReport(RTPSourceData *) inline void RTPSession::OnRTCPReceiverReport(RTPSourceData *) { } -inline void RTPSession::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, size_t) +inline void RTPSession::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, std::size_t) { } #ifdef RTP_SUPPORT_SDESPRIV -inline void RTPSession::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, size_t, const void *, size_t) +inline void RTPSession::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, std::size_t, const void *, std::size_t) { } #endif // RTP_SUPPORT_SDESPRIV @@ -662,11 +662,11 @@ inline void RTPSession::OnSendRTCPCompoundPacket(RTCPCompoundPacket *) { } -inline int RTPSession::OnChangeRTPOrRTCPData(const void *, size_t, bool, void **, size_t *) +inline int RTPSession::OnChangeRTPOrRTCPData(const void *, std::size_t, bool, void **, std::size_t *) { return ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED; } -inline void RTPSession::OnSentRTPOrRTCPData(void *, size_t, bool) +inline void RTPSession::OnSentRTPOrRTCPData(void *, std::size_t, bool) { } inline bool RTPSession::OnChangeIncomingData(RTPRawPacket *) diff --git a/qrtplib/rtpsessionparams.h b/qrtplib/rtpsessionparams.h index e4208dc96..b75517c42 100644 --- a/qrtplib/rtpsessionparams.h +++ b/qrtplib/rtpsessionparams.h @@ -72,13 +72,13 @@ public: } /** Sets the maximum allowed packet size for the session. */ - void SetMaximumPacketSize(size_t max) + void SetMaximumPacketSize(std::size_t max) { maxpacksize = max; } /** Returns the maximum allowed packet size (default is 1400 bytes). */ - size_t GetMaximumPacketSize() const + std::size_t GetMaximumPacketSize() const { return maxpacksize; } @@ -340,7 +340,7 @@ public: private: bool acceptown; bool usepollthread; - size_t maxpacksize; + std::size_t maxpacksize; double owntsunit; RTPTransmitter::ReceiveMode receivemode; bool resolvehostname; diff --git a/qrtplib/rtpsessionsources.cpp b/qrtplib/rtpsessionsources.cpp index c1525fada..045fa1cee 100644 --- a/qrtplib/rtpsessionsources.cpp +++ b/qrtplib/rtpsessionsources.cpp @@ -56,7 +56,7 @@ void RTPSessionSources::OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress rtpsession.OnSSRCCollision(srcdat, senderaddress, isrtp); } -void RTPSessionSources::OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, size_t cnamelength) +void RTPSessionSources::OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, std::size_t cnamelength) { rtpsession.OnCNAMECollision(srcdat, senderaddress, cname, cnamelength); } @@ -123,13 +123,13 @@ void RTPSessionSources::OnRTCPReceiverReport(RTPSourceData *srcdat) rtpsession.OnRTCPReceiverReport(srcdat); } -void RTPSessionSources::OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, size_t itemlength) +void RTPSessionSources::OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, std::size_t itemlength) { rtpsession.OnRTCPSDESItem(srcdat, t, itemdata, itemlength); } #ifdef RTP_SUPPORT_SDESPRIV -void RTPSessionSources::OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, const void *valuedata, size_t valuelen) +void RTPSessionSources::OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, std::size_t prefixlen, const void *valuedata, std::size_t valuelen) { rtpsession.OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); } diff --git a/qrtplib/rtpsessionsources.h b/qrtplib/rtpsessionsources.h index 2163e4486..1649c0a90 100644 --- a/qrtplib/rtpsessionsources.h +++ b/qrtplib/rtpsessionsources.h @@ -69,7 +69,7 @@ private: void OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); void OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); - void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, size_t cnamelength); + void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, std::size_t cnamelength); void OnNewSource(RTPSourceData *srcdat); void OnRemoveSource(RTPSourceData *srcdat); void OnTimeout(RTPSourceData *srcdat); @@ -82,9 +82,9 @@ private: void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); void OnRTCPSenderReport(RTPSourceData *srcdat); void OnRTCPReceiverReport(RTPSourceData *srcdat); - void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, size_t itemlength); + void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, std::size_t itemlength); #ifdef RTP_SUPPORT_SDESPRIV - void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, const void *valuedata, size_t valuelen); + void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, std::size_t prefixlen, const void *valuedata, std::size_t valuelen); #endif // RTP_SUPPORT_SDESPRIV RTPSession &rtpsession; diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h index b87efc095..a2d83cf13 100644 --- a/qrtplib/rtpsourcedata.h +++ b/qrtplib/rtpsourcedata.h @@ -405,7 +405,7 @@ public: * Returns the reason for leaving contained in the BYE packet of this participant. The length of * the reason is stored in \c len. */ - uint8_t *GetBYEReason(size_t *len) const + uint8_t *GetBYEReason(std::size_t *len) const { *len = byereasonlen; return byereason; @@ -679,43 +679,43 @@ public: } /** Returns a pointer to the SDES CNAME item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetCNAME(size_t *len) const + uint8_t *SDES_GetCNAME(std::size_t *len) const { return SDESinf.GetCNAME(len); } /** Returns a pointer to the SDES name item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetName(size_t *len) const + uint8_t *SDES_GetName(std::size_t *len) const { return SDESinf.GetName(len); } /** Returns a pointer to the SDES e-mail item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetEMail(size_t *len) const + uint8_t *SDES_GetEMail(std::size_t *len) const { return SDESinf.GetEMail(len); } /** Returns a pointer to the SDES phone item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetPhone(size_t *len) const + uint8_t *SDES_GetPhone(std::size_t *len) const { return SDESinf.GetPhone(len); } /** Returns a pointer to the SDES location item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetLocation(size_t *len) const + uint8_t *SDES_GetLocation(std::size_t *len) const { return SDESinf.GetLocation(len); } /** Returns a pointer to the SDES tool item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetTool(size_t *len) const + uint8_t *SDES_GetTool(std::size_t *len) const { return SDESinf.GetTool(len); } /** Returns a pointer to the SDES note item of this participant and stores its length in \c len. */ - uint8_t *SDES_GetNote(size_t *len) const + uint8_t *SDES_GetNote(std::size_t *len) const { return SDESinf.GetNote(len); } @@ -730,7 +730,7 @@ public: /** If available, returns \c true and stores the next SDES private item prefix in \c prefix and its length in * \c prefixlen; the associated value and its length are then stored in \c value and \c valuelen. */ - bool SDES_GetNextPrivateValue(uint8_t **prefix, size_t *prefixlen, uint8_t **value, size_t *valuelen) + bool SDES_GetNextPrivateValue(uint8_t **prefix, std::size_t *prefixlen, uint8_t **value, std::size_t *valuelen) { return SDESinf.GetNextPrivateValue(prefix, prefixlen, value, valuelen); } @@ -739,7 +739,7 @@ public: * \c prefixlen; if found, the function returns \c true and stores the associated value and * its length in \c value and \c valuelen respectively. */ - bool SDES_GetPrivateValue(uint8_t *prefix, size_t prefixlen, uint8_t **value, size_t *valuelen) const + bool SDES_GetPrivateValue(uint8_t *prefix, std::size_t prefixlen, uint8_t **value, std::size_t *valuelen) const { return SDESinf.GetPrivateValue(prefix, prefixlen, value, valuelen); } @@ -767,7 +767,7 @@ protected: RTPTime byetime; uint8_t *byereason; - size_t byereasonlen; + std::size_t byereasonlen; }; inline RTPPacket *RTPSourceData::GetNextPacket() diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp index f44e96a9c..f72c19b3a 100644 --- a/qrtplib/rtpsources.cpp +++ b/qrtplib/rtpsources.cpp @@ -686,7 +686,7 @@ int RTPSources::ProcessRTCPReportBlock(uint32_t ssrc, uint8_t fractionlost, int3 return 0; } -int RTPSources::ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, size_t itemlength, const void *itemdata, const RTPTime &receivetime, +int RTPSources::ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, std::size_t itemlength, const void *itemdata, const RTPTime &receivetime, const RTPAddress *senderaddress) { RTPInternalSourceData *srcdat; @@ -746,7 +746,7 @@ int RTPSources::ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, } #ifdef RTP_SUPPORT_SDESPRIV -int RTPSources::ProcessSDESPrivateItem(uint32_t ssrc, size_t prefixlen, const void *prefixdata, size_t valuelen, const void *valuedata, const RTPTime &receivetime, +int RTPSources::ProcessSDESPrivateItem(uint32_t ssrc, std::size_t prefixlen, const void *prefixdata, std::size_t valuelen, const void *valuedata, const RTPTime &receivetime, const RTPAddress *senderaddress) { RTPInternalSourceData *srcdat; @@ -771,7 +771,7 @@ int RTPSources::ProcessSDESPrivateItem(uint32_t ssrc, size_t prefixlen, const vo } #endif //RTP_SUPPORT_SDESPRIV -int RTPSources::ProcessBYE(uint32_t ssrc, size_t reasonlength, const void *reasondata, const RTPTime &receivetime, const RTPAddress *senderaddress) +int RTPSources::ProcessBYE(uint32_t ssrc, std::size_t reasonlength, const void *reasondata, const RTPTime &receivetime, const RTPAddress *senderaddress) { RTPInternalSourceData *srcdat; bool created; @@ -1027,7 +1027,7 @@ void RTPSources::NoteTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay while (sourcelist.HasCurrentElement()) { RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); - size_t notelen; + std::size_t notelen; srcdat->SDES_GetNote(¬elen); if (notelen != 0) // Note has been set @@ -1076,7 +1076,7 @@ void RTPSources::MultipleTimeouts(const RTPTime &curtime, const RTPTime &sendert bool deleted, issender, isactive; bool byetimeout, normaltimeout, notetimeout; - size_t notelen; + std::size_t notelen; issender = srcdat->IsSender(); isactive = srcdat->IsActive(); diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h index e169277b9..f549e71af 100644 --- a/qrtplib/rtpsources.h +++ b/qrtplib/rtpsources.h @@ -153,14 +153,14 @@ public: * received at time \c receivetime from address \c senderaddress. The \c senderaddress parameter must * be NULL if the packet was sent by the local participant. */ - int ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, size_t itemlength, const void *itemdata, const RTPTime &receivetime, const RTPAddress *senderaddress); + int ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, std::size_t itemlength, const void *itemdata, const RTPTime &receivetime, const RTPAddress *senderaddress); #ifdef RTP_SUPPORT_SDESPRIV /** Processes the SDES private item from source \c ssrc into the source table. * Processes the SDES private item from source \c ssrc into the source table. The information was * received at time \c receivetime from address \c senderaddress. The \c senderaddress * parameter must be NULL if the packet was sent by the local participant. */ - int ProcessSDESPrivateItem(uint32_t ssrc, size_t prefixlen, const void *prefixdata, size_t valuelen, const void *valuedata, const RTPTime &receivetime, + int ProcessSDESPrivateItem(uint32_t ssrc, std::size_t prefixlen, const void *prefixdata, std::size_t valuelen, const void *valuedata, const RTPTime &receivetime, const RTPAddress *senderaddress); #endif //RTP_SUPPORT_SDESPRIV /** Processes the BYE message for SSRC \c ssrc. @@ -168,7 +168,7 @@ public: * address \c senderaddress. The \c senderaddress parameter must be NULL if the packet was sent by the * local participant. */ - int ProcessBYE(uint32_t ssrc, size_t reasonlength, const void *reasondata, const RTPTime &receivetime, const RTPAddress *senderaddress); + int ProcessBYE(uint32_t ssrc, std::size_t reasonlength, const void *reasondata, const RTPTime &receivetime, const RTPAddress *senderaddress); /** If we heard from source \c ssrc, but no actual data was added to the source table (for example, if * no report block was meant for us), this function can e used to indicate that something was received from @@ -302,7 +302,7 @@ protected: virtual void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); /** Is called when another CNAME was received than the one already present for source \c srcdat. */ - virtual void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, size_t cnamelength); + virtual void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, std::size_t cnamelength); /** Is called when a new entry \c srcdat is added to the source table. */ virtual void OnNewSource(RTPSourceData *srcdat); @@ -326,10 +326,10 @@ protected: virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); /** Is called when a specific SDES item was received for this source. */ - virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, size_t itemlength); + virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, std::size_t itemlength); #ifdef RTP_SUPPORT_SDESPRIV /** Is called when a specific SDES item of 'private' type was received for this source. */ - virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, size_t prefixlen, const void *valuedata, size_t valuelen); + virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, std::size_t prefixlen, const void *valuedata, std::size_t valuelen); #endif // RTP_SUPPORT_SDESPRIV /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime @@ -378,7 +378,7 @@ inline void RTPSources::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime inline void RTPSources::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool) { } -inline void RTPSources::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, size_t) +inline void RTPSources::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, std::size_t) { } inline void RTPSources::OnNewSource(RTPSourceData *) @@ -402,11 +402,11 @@ inline void RTPSources::OnRTCPSenderReport(RTPSourceData *) inline void RTPSources::OnRTCPReceiverReport(RTPSourceData *) { } -inline void RTPSources::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, size_t) +inline void RTPSources::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, std::size_t) { } #ifdef RTP_SUPPORT_SDESPRIV -inline void RTPSources::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, size_t, const void *, size_t) +inline void RTPSources::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, std::size_t, const void *, std::size_t) { } #endif // RTP_SUPPORT_SDESPRIV diff --git a/qrtplib/rtptcptransmitter.cpp b/qrtplib/rtptcptransmitter.cpp index ce8ce0dde..411c6f56a 100644 --- a/qrtplib/rtptcptransmitter.cpp +++ b/qrtplib/rtptcptransmitter.cpp @@ -76,7 +76,7 @@ int RTPTCPTransmitter::Init(bool tsafe) return 0; } -int RTPTCPTransmitter::Create(size_t maximumpacketsize __attribute__((unused)), const RTPTransmissionParams *transparams) +int RTPTCPTransmitter::Create(std::size_t maximumpacketsize __attribute__((unused)), const RTPTransmissionParams *transparams) { const RTPTCPTransmissionParams *params, defaultparams; int status; @@ -167,7 +167,7 @@ void RTPTCPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) delete i; } -int RTPTCPTransmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) +int RTPTCPTransmitter::GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) { if (!m_init) return ERR_RTP_TCPTRANS_NOTINIT; @@ -259,7 +259,7 @@ int RTPTCPTransmitter::Poll() ++it; } - for (size_t i = 0; i < errSockets.size(); i++) + for (std::size_t i = 0; i < errSockets.size(); i++) OnReceiveError(errSockets[i]); return status; @@ -324,7 +324,7 @@ int RTPTCPTransmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavail { bool avail = false; - for (size_t i = 0; i < m_tmpFlags.size(); i++) + for (std::size_t i = 0; i < m_tmpFlags.size(); i++) { if (m_tmpFlags[i]) { @@ -362,12 +362,12 @@ int RTPTCPTransmitter::AbortWait() return 0; } -int RTPTCPTransmitter::SendRTPData(const void *data, size_t len) +int RTPTCPTransmitter::SendRTPData(const void *data, std::size_t len) { return SendRTPRTCPData(data, len); } -int RTPTCPTransmitter::SendRTCPData(const void *data, size_t len) +int RTPTCPTransmitter::SendRTCPData(const void *data, std::size_t len) { return SendRTPRTCPData(data, len); } @@ -523,7 +523,7 @@ void RTPTCPTransmitter::ClearAcceptList() { } -int RTPTCPTransmitter::SetMaximumPacketSize(size_t s) +int RTPTCPTransmitter::SetMaximumPacketSize(std::size_t s) { if (!m_init) return ERR_RTP_TCPTRANS_NOTINIT; @@ -603,7 +603,7 @@ int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata) #ifdef RTP_SOCKETTYPE_WINSOCK unsigned long len; #else - size_t len; + std::size_t len; #endif // RTP_SOCKETTYPE_WINSOCK bool dataavailable; @@ -668,7 +668,7 @@ int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata) return 0; } -int RTPTCPTransmitter::SendRTPRTCPData(const void *data, size_t len) +int RTPTCPTransmitter::SendRTPRTCPData(const void *data, std::size_t len) { if (!m_init) return ERR_RTP_TCPTRANS_NOTINIT; @@ -706,7 +706,7 @@ int RTPTCPTransmitter::SendRTPRTCPData(const void *data, size_t len) if (errSockets.size() != 0) { - for (size_t i = 0; i < errSockets.size(); i++) + for (std::size_t i = 0; i < errSockets.size(); i++) OnSendError(errSockets[i]); } diff --git a/qrtplib/rtptcptransmitter.h b/qrtplib/rtptcptransmitter.h index fa8515360..205e3c019 100644 --- a/qrtplib/rtptcptransmitter.h +++ b/qrtplib/rtptcptransmitter.h @@ -124,14 +124,14 @@ public: ~RTPTCPTransmitter(); int Init(bool treadsafe); - int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); void Destroy(); RTPTransmissionInfo *GetTransmissionInfo(); void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer, size_t *bufferlength); + int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() + std::size_t GetHeaderOverhead() { return RTPTCPTRANS_HEADERSIZE; } @@ -140,8 +140,8 @@ public: int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); int AbortWait(); - int SendRTPData(const void *data, size_t len); - int SendRTCPData(const void *data, size_t len); + int SendRTPData(const void *data, std::size_t len); + int SendRTCPData(const void *data, std::size_t len); int AddDestination(const RTPAddress &addr); int DeleteDestination(const RTPAddress &addr); @@ -159,7 +159,7 @@ public: int AddToAcceptList(const RTPAddress &addr); int DeleteFromAcceptList(const RTPAddress &addr); void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); + int SetMaximumPacketSize(std::size_t s); bool NewDataAvailable(); RTPRawPacket *GetNextPacket(); @@ -191,7 +191,7 @@ private: int ProcessAvailableBytes(SocketType sock, int availLen, bool &complete); }; - int SendRTPRTCPData(const void *data, size_t len); + int SendRTPRTCPData(const void *data, std::size_t len); void FlushPackets(); int PollSocket(SocketType sock, SocketData &sdata); void ClearDestSockets(); @@ -205,7 +205,7 @@ private: std::vector m_tmpSocks; std::vector m_tmpFlags; std::vector m_localHostname; - size_t m_maxPackSize; + std::size_t m_maxPackSize; std::list m_rawpacketlist; diff --git a/qrtplib/rtptransmitter.h b/qrtplib/rtptransmitter.h index 8cfad498d..a337fb30f 100644 --- a/qrtplib/rtptransmitter.h +++ b/qrtplib/rtptransmitter.h @@ -41,6 +41,7 @@ #include "rtpconfig.h" #include "rtptypes.h" #include "rtptimeutilities.h" +#include namespace qrtplib { @@ -107,7 +108,7 @@ public: * from RTPTransmissionParams. If \c transparams is NULL, the default transmission parameters * for the component will be used. */ - virtual int Create(size_t maxpacksize, const RTPTransmissionParams *transparams) = 0; + virtual int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams) = 0; /** By calling this function, buffers are cleared and the component cannot be used anymore. * By calling this function, buffers are cleared and the component cannot be used anymore. @@ -137,14 +138,14 @@ public: * it returns \c ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL and stores the number of bytes needed in * \c bufferlength. */ - virtual int GetLocalHostName(uint8_t *buffer, size_t *bufferlength) = 0; + virtual int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) = 0; /** Returns \c true if the address specified by \c addr is one of the addresses of the transmitter. */ virtual bool ComesFromThisTransmitter(const RTPAddress *addr) = 0; /** Returns the amount of bytes that will be added to the RTP packet by the underlying layers (excluding * the link layer). */ - virtual size_t GetHeaderOverhead() = 0; + virtual std::size_t GetHeaderOverhead() = 0; /** Checks for incoming data and stores it. */ virtual int Poll() = 0; @@ -159,10 +160,10 @@ public: virtual int AbortWait() = 0; /** Send a packet with length \c len containing \c data to all RTP addresses of the current destination list. */ - virtual int SendRTPData(const void *data, size_t len) = 0; + virtual int SendRTPData(const void *data, std::size_t len) = 0; /** Send a packet with length \c len containing \c data to all RTCP addresses of the current destination list. */ - virtual int SendRTCPData(const void *data, size_t len) = 0; + virtual int SendRTCPData(const void *data, std::size_t len) = 0; /** Adds the address specified by \c addr to the list of destinations. */ virtual int AddDestination(const RTPAddress &addr) = 0; @@ -211,7 +212,7 @@ public: virtual void ClearAcceptList() = 0; /** Sets the maximum packet size which the transmitter should allow to \c s. */ - virtual int SetMaximumPacketSize(size_t s) = 0; + virtual int SetMaximumPacketSize(std::size_t s) = 0; /** Returns \c true if packets can be obtained using the GetNextPacket member function. */ virtual bool NewDataAvailable() = 0; diff --git a/qrtplib/rtpudpv4transmitter.cpp b/qrtplib/rtpudpv4transmitter.cpp index 47cc89aac..12f165281 100644 --- a/qrtplib/rtpudpv4transmitter.cpp +++ b/qrtplib/rtpudpv4transmitter.cpp @@ -137,7 +137,7 @@ int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtc SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); if (sock == RTPSOCKERR) { - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; } @@ -153,7 +153,7 @@ int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtc if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) { RTPCLOSE(sock); - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; } @@ -163,7 +163,7 @@ int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtc if (status < 0) { RTPCLOSE(sock); - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return status; } @@ -176,7 +176,7 @@ int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtc *pRtcpSock = sock; *pRtpPort = basePort; *pRtcpPort = basePort; - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return 0; @@ -190,7 +190,7 @@ int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtc if (sock2 == RTPSOCKERR) { RTPCLOSE(sock); - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; } @@ -237,7 +237,7 @@ int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtc *pRtcpPort = basePort; } - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return 0; @@ -249,13 +249,13 @@ int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtc } } - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; } -int RTPUDPv4Transmitter::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) +int RTPUDPv4Transmitter::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) { const RTPUDPv4TransmissionParams *params, defaultparams; struct sockaddr_in addr; @@ -555,7 +555,7 @@ void RTPUDPv4Transmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) delete i; } -int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) +int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) { if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; @@ -831,7 +831,7 @@ int RTPUDPv4Transmitter::AbortWait() return 0; } -int RTPUDPv4Transmitter::SendRTPData(const void *data, size_t len) +int RTPUDPv4Transmitter::SendRTPData(const void *data, std::size_t len) { if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; @@ -857,7 +857,7 @@ int RTPUDPv4Transmitter::SendRTPData(const void *data, size_t len) return 0; } -int RTPUDPv4Transmitter::SendRTCPData(const void *data, size_t len) +int RTPUDPv4Transmitter::SendRTCPData(const void *data, std::size_t len) { if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; @@ -1248,7 +1248,7 @@ void RTPUDPv4Transmitter::ClearAcceptList() } -int RTPUDPv4Transmitter::SetMaximumPacketSize(size_t s) +int RTPUDPv4Transmitter::SetMaximumPacketSize(std::size_t s) { if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; @@ -1352,7 +1352,7 @@ int RTPUDPv4Transmitter::PollSocket(bool rtp) SOCKET sock; unsigned long len; #else - size_t len; + std::size_t len; int sock; #endif // RTP_SOCKETTYPE_WINSOCK struct sockaddr_in srcaddr; @@ -1425,7 +1425,7 @@ int RTPUDPv4Transmitter::PollSocket(bool rtp) { isrtp = true; - if ((size_t) recvlen > sizeof(RTCPCommonHeader)) + if ((std::size_t) recvlen > sizeof(RTCPCommonHeader)) { RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; uint8_t packettype = rtcpheader->packettype; diff --git a/qrtplib/rtpudpv4transmitter.h b/qrtplib/rtpudpv4transmitter.h index bd2aa9597..109a4c093 100644 --- a/qrtplib/rtpudpv4transmitter.h +++ b/qrtplib/rtpudpv4transmitter.h @@ -380,14 +380,14 @@ public: ~RTPUDPv4Transmitter(); int Init(bool treadsafe); - int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); void Destroy(); RTPTransmissionInfo *GetTransmissionInfo(); void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer, size_t *bufferlength); + int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() + std::size_t GetHeaderOverhead() { return RTPUDPV4TRANS_HEADERSIZE; } @@ -396,8 +396,8 @@ public: int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); int AbortWait(); - int SendRTPData(const void *data, size_t len); - int SendRTCPData(const void *data, size_t len); + int SendRTPData(const void *data, std::size_t len); + int SendRTCPData(const void *data, std::size_t len); int AddDestination(const RTPAddress &addr); int DeleteDestination(const RTPAddress &addr); @@ -415,7 +415,7 @@ public: int AddToAcceptList(const RTPAddress &addr); int DeleteFromAcceptList(const RTPAddress &addr); void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); + int SetMaximumPacketSize(std::size_t s); bool NewDataAvailable(); RTPRawPacket *GetNextPacket(); @@ -449,7 +449,7 @@ private: RTPTransmitter::ReceiveMode receivemode; uint8_t *localhostname; - size_t localhostnamelength; + std::size_t localhostnamelength; RTPHashTable destinations; #ifdef RTP_SUPPORT_IPV4MULTICAST @@ -458,7 +458,7 @@ private: std::list rawpacketlist; bool supportsmulticasting; - size_t maxpacksize; + std::size_t maxpacksize; class PortInfo { diff --git a/qrtplib/rtpudpv4transmitternobind.cpp b/qrtplib/rtpudpv4transmitternobind.cpp index ad4f79d86..e51ba491d 100644 --- a/qrtplib/rtpudpv4transmitternobind.cpp +++ b/qrtplib/rtpudpv4transmitternobind.cpp @@ -137,7 +137,7 @@ int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bo SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); if (sock == RTPSOCKERR) { - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; } @@ -153,7 +153,7 @@ int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bo if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) { RTPCLOSE(sock); - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; } @@ -163,7 +163,7 @@ int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bo if (status < 0) { RTPCLOSE(sock); - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return status; } @@ -176,7 +176,7 @@ int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bo *pRtcpSock = sock; *pRtpPort = basePort; *pRtcpPort = basePort; - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return 0; @@ -190,7 +190,7 @@ int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bo if (sock2 == RTPSOCKERR) { RTPCLOSE(sock); - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; } @@ -237,7 +237,7 @@ int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bo *pRtcpPort = basePort; } - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return 0; @@ -249,13 +249,13 @@ int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bo } } - for (size_t i = 0; i < toClose.size(); i++) + for (std::size_t i = 0; i < toClose.size(); i++) RTPCLOSE(toClose[i]); return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; } -int RTPUDPv4TransmitterNoBind::Create(size_t maximumpacketsize, const RTPTransmissionParams *transparams) +int RTPUDPv4TransmitterNoBind::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) { const RTPUDPv4TransmissionNoBindParams *params, defaultparams; // struct sockaddr_in addr; @@ -557,7 +557,7 @@ void RTPUDPv4TransmitterNoBind::DeleteTransmissionInfo(RTPTransmissionInfo *i) delete i; } -int RTPUDPv4TransmitterNoBind::GetLocalHostName(uint8_t *buffer, size_t *bufferlength) +int RTPUDPv4TransmitterNoBind::GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) { if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; @@ -833,7 +833,7 @@ int RTPUDPv4TransmitterNoBind::AbortWait() return 0; } -int RTPUDPv4TransmitterNoBind::SendRTPData(const void *data, size_t len) +int RTPUDPv4TransmitterNoBind::SendRTPData(const void *data, std::size_t len) { if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; @@ -859,7 +859,7 @@ int RTPUDPv4TransmitterNoBind::SendRTPData(const void *data, size_t len) return 0; } -int RTPUDPv4TransmitterNoBind::SendRTCPData(const void *data, size_t len) +int RTPUDPv4TransmitterNoBind::SendRTCPData(const void *data, std::size_t len) { if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; @@ -1250,7 +1250,7 @@ void RTPUDPv4TransmitterNoBind::ClearAcceptList() } -int RTPUDPv4TransmitterNoBind::SetMaximumPacketSize(size_t s) +int RTPUDPv4TransmitterNoBind::SetMaximumPacketSize(std::size_t s) { if (!init) return ERR_RTP_UDPV4TRANS_NOTINIT; @@ -1354,7 +1354,7 @@ int RTPUDPv4TransmitterNoBind::PollSocket(bool rtp) SOCKET sock; unsigned long len; #else - size_t len; + std::size_t len; int sock; #endif // RTP_SOCKETTYPE_WINSOCK struct sockaddr_in srcaddr; @@ -1427,7 +1427,7 @@ int RTPUDPv4TransmitterNoBind::PollSocket(bool rtp) { isrtp = true; - if ((size_t) recvlen > sizeof(RTCPCommonHeader)) + if ((std::size_t) recvlen > sizeof(RTCPCommonHeader)) { RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; uint8_t packettype = rtcpheader->packettype; diff --git a/qrtplib/rtpudpv4transmitternobind.h b/qrtplib/rtpudpv4transmitternobind.h index 6b0da05d1..a1d3f4201 100644 --- a/qrtplib/rtpudpv4transmitternobind.h +++ b/qrtplib/rtpudpv4transmitternobind.h @@ -382,16 +382,16 @@ public: ~RTPUDPv4TransmitterNoBind(); int Init(bool treadsafe); - int Create(size_t maxpacksize, const RTPTransmissionParams *transparams); + int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); /** Bind the RTP and RTCP sockets to ports defined in the transmission parameters */ int BindSockets(const RTPTransmissionParams *transparams); void Destroy(); RTPTransmissionInfo *GetTransmissionInfo(); void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer, size_t *bufferlength); + int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); bool ComesFromThisTransmitter(const RTPAddress *addr); - size_t GetHeaderOverhead() + std::size_t GetHeaderOverhead() { return RTPUDPV4TRANSNOBIND_HEADERSIZE; } @@ -400,8 +400,8 @@ public: int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); int AbortWait(); - int SendRTPData(const void *data, size_t len); - int SendRTCPData(const void *data, size_t len); + int SendRTPData(const void *data, std::size_t len); + int SendRTCPData(const void *data, std::size_t len); int AddDestination(const RTPAddress &addr); int DeleteDestination(const RTPAddress &addr); @@ -419,7 +419,7 @@ public: int AddToAcceptList(const RTPAddress &addr); int DeleteFromAcceptList(const RTPAddress &addr); void ClearAcceptList(); - int SetMaximumPacketSize(size_t s); + int SetMaximumPacketSize(std::size_t s); bool NewDataAvailable(); RTPRawPacket *GetNextPacket(); @@ -453,7 +453,7 @@ private: RTPTransmitter::ReceiveMode receivemode; uint8_t *localhostname; - size_t localhostnamelength; + std::size_t localhostnamelength; RTPHashTable destinations; #ifdef RTP_SUPPORT_IPV4MULTICAST @@ -462,7 +462,7 @@ private: std::list rawpacketlist; bool supportsmulticasting; - size_t maxpacksize; + std::size_t maxpacksize; class PortInfo { From cd7962508d6b494792ddc7296b686429a904d395 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 28 Feb 2018 14:20:53 +0100 Subject: [PATCH 034/956] qrtplib: generic UDP transmitter (1) --- qrtplib/rtpudptransmitter.h | 398 ++++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 qrtplib/rtpudptransmitter.h diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h new file mode 100644 index 000000000..5ad8c04e5 --- /dev/null +++ b/qrtplib/rtpudptransmitter.h @@ -0,0 +1,398 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#ifndef QRTPLIB_RTPUDPTRANSMITTER_H_ +#define QRTPLIB_RTPUDPTRANSMITTER_H_ + +#include "rtptransmitter.h" +#include +#include +#include + +#define RTPUDPV4TRANS_HASHSIZE 8317 +#define RTPUDPV4TRANS_DEFAULTPORTBASE 5000 +#define RTPUDPV4TRANS_RTPRECEIVEBUFFER 32768 +#define RTPUDPV4TRANS_RTCPRECEIVEBUFFER 32768 +#define RTPUDPV4TRANS_RTPTRANSMITBUFFER 32768 +#define RTPUDPV4TRANS_RTCPTRANSMITBUFFER 32768 + +class QUdpSocket; + +namespace qrtplib +{ + +/** Parameters for the UDP transmitter. */ +class RTPUDPTransmissionParams: public RTPTransmissionParams +{ +public: + RTPUDPTransmissionParams(); + + /** Sets the IP address which is used to bind the sockets to \c bindAddress. */ + void SetBindIP(const QHostAddress& bindAddress) { + m_bindAddress = bindAddress; + } + + /** Sets the multicast interface IP address. */ + void SetMulticastInterfaceIP(const QHostAddress& mcastGroupAddress) { + m_mcastGroupAddress = mcastGroupAddress; + } + + /** Sets the RTP portbase to \c pbase, which has to be an even number + * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; + * a port number of zero will cause a port to be chosen automatically. */ + void SetPortbase(uint16_t pbase) + { + m_portbase = pbase; + } + + /** Passes a list of IP addresses which will be used as the local IP addresses. */ + void SetLocalIPList(const std::list& iplist) + { + m_localIPs = iplist; + } + + /** Clears the list of local IP addresses. + * Clears the list of local IP addresses. An empty list will make the transmission + * component itself determine the local IP addresses. + */ + void ClearLocalIPList() + { + m_localIPs.clear(); + } + + /** Returns the IP address which will be used to bind the sockets. */ + QHostAddress GetBindIP() const + { + return m_bindAddress; + } + + /** Returns the multicast interface IP address. */ + QHostAddress GetMulticastInterfaceIP() const + { + return m_mcastGroupAddress; + } + + /** Returns the RTP portbase which will be used (default is 5000). */ + uint16_t GetPortbase() const + { + return m_portbase; + } + + /** Returns the list of local IP addresses. */ + const std::list &GetLocalIPList() const + { + return m_localIPs; + } + + /** Sets the RTP socket's send buffer size. */ + void SetRTPSendBufferSize(int s) + { + m_rtpsendbufsz = s; + } + + /** Sets the RTP socket's receive buffer size. */ + void SetRTPReceiveBufferSize(int s) + { + m_rtprecvbufsz = s; + } + + /** Sets the RTCP socket's send buffer size. */ + void SetRTCPSendBufferSize(int s) + { + m_rtcpsendbufsz = s; + } + + /** Sets the RTCP socket's receive buffer size. */ + void SetRTCPReceiveBufferSize(int s) + { + m_rtcprecvbufsz = s; + } + + /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ + void SetRTCPMultiplexing(bool f) + { + m_rtcpmux = f; + } + + /** Can be used to allow the RTP port base to be any number, not just even numbers. */ + void SetAllowOddPortbase(bool f) + { + m_allowoddportbase = f; + } + + /** Force the RTCP socket to use a specific port, not necessarily one more than + * the RTP port (set this to zero to disable). */ + void SetForcedRTCPPort(uint16_t rtcpport) + { + m_forcedrtcpport = rtcpport; + } + + /** Use sockets that have already been created, no checks on port numbers + * will be done, and no buffer sizes will be set; you'll need to close + * the sockets yourself when done, it will **not** be done automatically. */ + void SetUseExistingSockets(QUdpSocket *rtpsocket, QUdpSocket *rtcpsocket) + { + m_rtpsock = rtpsocket; + m_rtcpsock = rtcpsocket; + m_useexistingsockets = true; + } + + /** Returns the RTP socket's send buffer size. */ + int GetRTPSendBufferSize() const + { + return m_rtpsendbufsz; + } + + /** Returns the RTP socket's receive buffer size. */ + int GetRTPReceiveBufferSize() const + { + return m_rtprecvbufsz; + } + + /** Returns the RTCP socket's send buffer size. */ + int GetRTCPSendBufferSize() const + { + return m_rtcpsendbufsz; + } + + /** Returns the RTCP socket's receive buffer size. */ + int GetRTCPReceiveBufferSize() const + { + return m_rtcprecvbufsz; + } + + /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ + bool GetRTCPMultiplexing() const + { + return m_rtcpmux; + } + + /** If true, any RTP portbase will be allowed, not just even numbers. */ + bool GetAllowOddPortbase() const + { + return m_allowoddportbase; + } + + /** If non-zero, the specified port will be used to receive RTCP traffic. */ + uint16_t GetForcedRTCPPort() const + { + return m_forcedrtcpport; + } + + /** Returns true and fills in sockets if existing sockets were set + * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ + bool GetUseExistingSockets(QUdpSocket **rtpsocket, QUdpSocket **rtcpsocket) const + { + if (!m_useexistingsockets) { + return false; + } + + *rtpsocket = m_rtpsock; + *rtcpsocket = m_rtcpsock; + return true; + } + +private: + QHostAddress m_bindAddress; + QHostAddress m_mcastGroupAddress; + uint16_t m_portbase; + std::list m_localIPs; + int m_rtpsendbufsz, m_rtprecvbufsz; + int m_rtcpsendbufsz, m_rtcprecvbufsz; + bool m_rtcpmux; + bool m_allowoddportbase; + uint16_t m_forcedrtcpport; + + QUdpSocket *m_rtpsock, *m_rtcpsock; + bool m_useexistingsockets; +}; + +inline RTPUDPTransmissionParams::RTPUDPTransmissionParams() : + RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) +{ + m_portbase = RTPUDPV4TRANS_DEFAULTPORTBASE; + m_rtpsendbufsz = RTPUDPV4TRANS_RTPTRANSMITBUFFER; + m_rtprecvbufsz = RTPUDPV4TRANS_RTPRECEIVEBUFFER; + m_rtcpsendbufsz = RTPUDPV4TRANS_RTCPTRANSMITBUFFER; + m_rtcprecvbufsz = RTPUDPV4TRANS_RTCPRECEIVEBUFFER; + m_rtcpmux = false; + m_allowoddportbase = false; + m_forcedrtcpport = 0; + m_useexistingsockets = false; + m_rtpsock = 0; + m_rtcpsock = 0; +} + +/** Additional information about the UDP over IPv4 transmitter. */ +class RTPUDPTransmissionInfo: public RTPTransmissionInfo +{ +public: + RTPUDPTransmissionInfo(const std::list& iplist, QUdpSocket *rtpsock, QUdpSocket *rtcpsock, uint16_t rtpport, uint16_t rtcpport) : + RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) + { + m_localIPlist = iplist; + m_rtpsocket = rtpsock; + m_rtcpsocket = rtcpsock; + m_rtpPort = rtpport; + m_rtcpPort = rtcpport; + } + + ~RTPUDPTransmissionInfo() + { + } + + /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ + std::list GetLocalIPList() const + { + return m_localIPlist; + } + + /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ + QUdpSocket *GetRTPSocket() const + { + return m_rtpsocket; + } + + /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ + QUdpSocket *GetRTCPSocket() const + { + return m_rtcpsocket; + } + + /** Returns the port number that the RTP socket receives packets on. */ + uint16_t GetRTPPort() const + { + return m_rtpPort; + } + + /** Returns the port number that the RTCP socket receives packets on. */ + uint16_t GetRTCPPort() const + { + return m_rtcpPort; + } +private: + std::list m_localIPlist; + QUdpSocket *m_rtpsocket, *m_rtcpsocket; + uint16_t m_rtpPort, m_rtcpPort; +}; + +#define RTPUDPTRANS_HEADERSIZE (20+8) + +/** An UDP transmission component. + * This class inherits the RTPTransmitter interface and implements a transmission component + * which uses UDP to send and receive RTP and RTCP data. The component's parameters + * are described by the class RTPUDPTransmissionParams. The GetTransmissionInfo member function + * returns an instance of type RTPUDPTransmissionInfo. + */ +class RTPUDPTransmitter: public RTPTransmitter +{ +public: + RTPUDPTransmitter(); + ~RTPUDPTransmitter(); + + int Init(); + int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); + void Destroy(); + RTPTransmissionInfo *GetTransmissionInfo(); + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); + bool ComesFromThisTransmitter(const RTPAddress *addr); + std::size_t GetHeaderOverhead() + { + return RTPUDPTRANS_HEADERSIZE; + } + + int Poll(); + int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); + int AbortWait(); + + int SendRTPData(const void *data, std::size_t len); + int SendRTCPData(const void *data, std::size_t len); + + int AddDestination(const RTPAddress &addr); + int DeleteDestination(const RTPAddress &addr); + void ClearDestinations(); + + bool SupportsMulticasting(); + int JoinMulticastGroup(const RTPAddress &addr); + int LeaveMulticastGroup(const RTPAddress &addr); + void LeaveAllMulticastGroups(); + + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + int AddToIgnoreList(const RTPAddress &addr); + int DeleteFromIgnoreList(const RTPAddress &addr); + void ClearIgnoreList(); + int AddToAcceptList(const RTPAddress &addr); + int DeleteFromAcceptList(const RTPAddress &addr); + void ClearAcceptList(); + int SetMaximumPacketSize(std::size_t s); + + bool NewDataAvailable(); + RTPRawPacket *GetNextPacket(); + +private: + int CreateLocalIPList(); + bool GetLocalIPList_Interfaces(); + void GetLocalIPList_DNS(); + void AddLoopbackAddress(); + void FlushPackets(); + int ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port); + int ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port); + bool ShouldAcceptData(uint32_t srcip, uint16_t srcport); + void ClearAcceptIgnoreInfo(); + + bool m_init; + bool m_created; + bool m_waitingfordata; + QUdpSocket *m_rtpsock, *m_rtcpsock; + QHostAddress m_mcastifaceIP; + std::list m_localIPs; + uint16_t m_rtpPort, m_rtcpPort; + uint8_t m_multicastTTL; + RTPTransmitter::ReceiveMode m_receivemode; + + uint8_t *m_localhostname; + std::size_t m_localhostnamelength; + + std::list m_rawpacketlist; + + bool m_supportsmulticasting; + std::size_t m_maxpacksize; + + bool m_closesocketswhendone; +}; + + +} // namespace + +#endif /* QRTPLIB_RTPUDPTRANSMITTER_H_ */ From 8f4a006bcac11058dc6bd4438f50a18ce07857cf Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 01:08:57 +0100 Subject: [PATCH 035/956] qrtplib: generic UDP transmitter (2) --- qrtplib/rtpudptransmitter.h | 8 +++----- qrtplib/rtpudpv4transmitternobind.h | 10 +++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h index 5ad8c04e5..3cdf8401d 100644 --- a/qrtplib/rtpudptransmitter.h +++ b/qrtplib/rtpudptransmitter.h @@ -325,7 +325,6 @@ public: RTPTransmissionInfo *GetTransmissionInfo(); void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); bool ComesFromThisTransmitter(const RTPAddress *addr); std::size_t GetHeaderOverhead() { @@ -357,8 +356,8 @@ public: void ClearAcceptList(); int SetMaximumPacketSize(std::size_t s); - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); + bool NewDataAvailable(); // TODO: emit signal instead + RTPRawPacket *GetNextPacket(); // TODO: use a queue private: int CreateLocalIPList(); @@ -376,9 +375,8 @@ private: bool m_waitingfordata; QUdpSocket *m_rtpsock, *m_rtcpsock; QHostAddress m_mcastifaceIP; - std::list m_localIPs; + QHostAddress m_localIP; uint16_t m_rtpPort, m_rtcpPort; - uint8_t m_multicastTTL; RTPTransmitter::ReceiveMode m_receivemode; uint8_t *m_localhostname; diff --git a/qrtplib/rtpudpv4transmitternobind.h b/qrtplib/rtpudpv4transmitternobind.h index a1d3f4201..2ab5c238e 100644 --- a/qrtplib/rtpudpv4transmitternobind.h +++ b/qrtplib/rtpudpv4transmitternobind.h @@ -297,10 +297,10 @@ inline RTPUDPv4TransmissionNoBindParams::RTPUDPv4TransmissionNoBindParams() : class RTPUDPv4TransmissionNoBindInfo: public RTPTransmissionInfo { public: - RTPUDPv4TransmissionNoBindInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : + RTPUDPv4TransmissionNoBindInfo(const QHostAddress& ip, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) { - localIPlist = iplist; + localIP = ip; rtpsocket = rtpsock; rtcpsocket = rtcpsock; m_rtpPort = rtpport; @@ -312,9 +312,9 @@ public: } /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ - std::list GetLocalIPList() const + QHostAddress GetLocalIP() const { - return localIPlist; + return localIP; } /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ @@ -341,7 +341,7 @@ public: return m_rtcpPort; } private: - std::list localIPlist; + QHostAddress localIP; SocketType rtpsocket, rtcpsocket; uint16_t m_rtpPort, m_rtcpPort; }; From 13d3ec949d42152c900ccdac2355551b7a1ba020 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 01:40:04 +0100 Subject: [PATCH 036/956] qrtplib: added missing file --- qrtplib/rtpudptransmitter.cpp | 235 ++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 qrtplib/rtpudptransmitter.cpp diff --git a/qrtplib/rtpudptransmitter.cpp b/qrtplib/rtpudptransmitter.cpp new file mode 100644 index 000000000..ee889b80a --- /dev/null +++ b/qrtplib/rtpudptransmitter.cpp @@ -0,0 +1,235 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpudptransmitter.h" +#include "rtperrors.h" + +#define RTPUDPTRANS_MAXPACKSIZE 65535 + +namespace qrtplib +{ + +RTPUDPTransmitter::RTPUDPTransmitter() +{ + m_created = false; + m_init = false; + m_rtcpsock = 0; + m_rtpsock = 0; + m_waitingfordata = false; + m_closesocketswhendone = false; + m_rtcpPort = 0; + m_rtpPort = 0; + m_supportsmulticasting = false; + m_receivemode = RTPTransmitter::AcceptAll; + m_localhostname = 0; +} + +RTPUDPTransmitter::~RTPUDPTransmitter() +{ + Destroy(); +} + +int RTPUDPTransmitter::Init() +{ + if (m_init) { + return ERR_RTP_UDPV4TRANS_ALREADYINIT; + } + + m_init = true; + return 0; +} + +int RTPUDPTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) +{ + const RTPUDPTransmissionParams *params, defaultparams; + struct sockaddr_in addr; + qint64 size; + int status; + + if (maximumpacketsize > RTPUDPTRANS_MAXPACKSIZE) { + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (m_created) { + return ERR_RTP_UDPV4TRANS_ALREADYCREATED; + } + + // Obtain transmission parameters + + if (transparams == 0) { + params = &defaultparams; + } + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) + { + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; + } + params = (const RTPUDPv4TransmissionParams *) transparams; + } + + // Determine the port numbers + + m_localIP = params->GetBindIP(); + + if (params->GetAllowOddPortbase()) + { + m_rtpPort = params->GetPortbase(); + m_rtcpPort = params->GetForcedRTCPPort(); + } + else + { + if (params->GetPortbase() % 2 == 0) + { + m_rtpPort = params->GetPortbase(); + m_rtcpPort = m_rtpPort + 1; + } + else + { + return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; + } + } + + if (params->GetUseExistingSockets(&m_rtpsock, &m_rtcpsock)) + { + m_closesocketswhendone = false; + } + else + { + m_rtpsock = new QUdpSocket(); + + // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket + if (params->GetRTCPMultiplexing()) + { + m_rtcpsock = m_rtpsock; + m_rtcpPort = m_rtpPort; + } else { + m_rtcpsock = new QUdpSocket(); + } + + m_closesocketswhendone = true; + + // set socket buffer sizes + + size = params->GetRTPReceiveBufferSize(); + m_rtpsock->setReadBufferSize(size); + + if (m_rtpsock != m_rtcpsock) + { + size = params->GetRTCPReceiveBufferSize(); + m_rtcpsock->setReadBufferSize(size); + } + } + + m_maxpacksize = maximumpacketsize; + m_mcastifaceIP = params->GetMulticastInterfaceIP(); + m_receivemode = RTPTransmitter::AcceptAll; + + m_localhostname = 0; + m_localhostnamelength = 0; + + m_waitingfordata = false; + m_created = true; + + return 0; +} + +void RTPUDPTransmitter::Destroy() +{ + if (!m_init) { + return; + } + + if (!m_created) + { + return; + } + + if (m_localhostname) + { + delete[] m_localhostname; + m_localhostname = 0; + m_localhostnamelength = 0; + } + + FlushPackets(); + ClearAcceptIgnoreInfo(); + + if (m_closesocketswhendone) + { + if (m_rtpsock != m_rtcpsock) { + delete m_rtcpsock; + } + + delete m_rtpsock; + } + + m_created = false; +} + +RTPTransmissionInfo *RTPUDPTransmitter::GetTransmissionInfo() +{ + if (!m_init) { + return 0; + } + + RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionNoBindInfo( + m_localIP, m_rtpsock, m_rtcpsock, m_rtpPort, m_rtcpPort); + + return tinf; +} + +void RTPUDPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *inf) +{ + if (!m_init) { + return; + } + + delete inf; +} + +bool RTPUDPTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) +{ + if (addr->getAddress() != m_localIP) { + return false; + } + + return (addr->getPort() == m_rtpPort) && (addr->getRtcpsendport() == m_rtcpPort); +} + +} // namespace + + From 0cc99dad130450e1e3bed81b3ccec9af516e63ae Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 01:40:29 +0100 Subject: [PATCH 037/956] GLSpectrum: suppress VLAs --- sdrgui/gui/glspectrum.cpp | 24 +++++++++--- sdrgui/gui/glspectrum.h | 77 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 642ba9329..44e80184f 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -812,7 +812,8 @@ void GLSpectrum::paintGL() m_maxHold[i] = (j * m_powerRange) / 99.0 + m_referenceLevel; } { - GLfloat q3[2*m_fftSize]; + //GLfloat q3[2*m_fftSize]; + GLfloat *q3 = m_shaderArrays.m_q3FFT; Real bottom = -m_powerRange; for(int i = 0; i < m_fftSize; i++) { @@ -835,7 +836,8 @@ void GLSpectrum::paintGL() { { Real bottom = -m_powerRange; - GLfloat q3[2*m_fftSize]; + //GLfloat q3[2*m_fftSize]; + GLfloat *q3 = m_shaderArrays.m_q3FFT; for(int i = 0; i < m_fftSize; i++) { Real v = (*m_currentSpectrum)[i] - m_referenceLevel; @@ -860,7 +862,8 @@ void GLSpectrum::paintGL() tickList = &m_timeScale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_shaderArrays.m_q3TickTime; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -887,7 +890,8 @@ void GLSpectrum::paintGL() tickList = &m_frequencyScale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_shaderArrays.m_q3TickFrequency; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -920,7 +924,8 @@ void GLSpectrum::paintGL() tickList = &m_powerScale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_shaderArrays.m_q3TickPower; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) @@ -947,7 +952,8 @@ void GLSpectrum::paintGL() tickList = &m_frequencyScale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_shaderArrays.m_q3TickFrequency; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) @@ -1572,6 +1578,8 @@ void GLSpectrum::applyChanges() memset(m_histogram, 0x00, 100 * m_fftSize); m_histogramHoldoff = new quint8[100 * m_fftSize]; memset(m_histogramHoldoff, 0x07, 100 * m_fftSize); + + m_shaderArrays.allocFFT(2*m_fftSize); } if(fftSizeChanged || windowSizeChanged) @@ -1579,6 +1587,10 @@ void GLSpectrum::applyChanges() m_waterfallTextureHeight = waterfallHeight; m_waterfallTexturePos = 0; } + + m_shaderArrays.allocTickTime(4*m_timeScale.getTickList().count()); + m_shaderArrays.allocTickFrequency(4*m_frequencyScale.getTickList().count()); + m_shaderArrays.allocTickPower(4*m_powerScale.getTickList().count()); } void GLSpectrum::mouseMoveEvent(QMouseEvent* event) diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index 0dbae94a5..dcc4d8c65 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -72,6 +72,82 @@ public: void connectTimer(const QTimer& timer); private: + struct ShaderArrays + { + GLfloat *m_q3TickTime; + GLfloat *m_q3TickFrequency; + GLfloat *m_q3TickPower; + GLfloat *m_q3FFT; + + uint32_t m_q3TickTimeSize; + uint32_t m_q3TickTimeFrequencySize; + uint32_t m_q3TickPowerSize; + uint32_t m_q3FFTSize; + + ShaderArrays() : + m_q3TickTime(0), + m_q3TickFrequency(0), + m_q3TickPower(0), + m_q3FFT(0), + m_q3TickTimeSize(0), + m_q3TickTimeFrequencySize(0), + m_q3TickPowerSize(0), + m_q3FFTSize(0) + {} + + ~ShaderArrays() + { + if (m_q3TickTime) { delete[] m_q3TickTime; } + if (m_q3TickFrequency) { delete[] m_q3TickFrequency; } + if (m_q3TickPower) { delete[] m_q3TickPower; } + if (m_q3FFT) { delete[] m_q3FFT; } + } + + void allocTickTime(uint32_t size) + { + if (size <= m_q3TickTimeSize) { + return; + } + + if (m_q3TickTime) { delete[] m_q3TickTime; } + m_q3TickTime = new GLfloat[size]; + m_q3TickTimeSize = size; + } + + void allocTickFrequency(uint32_t size) + { + if (size <= m_q3TickTimeFrequencySize) { + return; + } + + if (m_q3TickFrequency) { delete[] m_q3TickFrequency; } + m_q3TickFrequency = new GLfloat[size]; + m_q3TickTimeFrequencySize = size; + } + + void allocTickPower(uint32_t size) + { + if (size <= m_q3TickPowerSize) { + return; + } + + if (m_q3TickPower) { delete[] m_q3TickPower; } + m_q3TickPower = new GLfloat[size]; + m_q3TickPowerSize = size; + } + + void allocFFT(uint32_t size) + { + if (size <= m_q3FFTSize) { + return; + } + + if (m_q3FFT) { delete[] m_q3FFT; } + m_q3FFT = new GLfloat[size]; + m_q3FFTSize = size; + } + }; + struct ChannelMarkerState { ChannelMarker* m_channelMarker; QMatrix4x4 m_glMatrixWaterfall; @@ -164,6 +240,7 @@ private: GLShaderTextured m_glShaderHistogram; int m_matrixLoc; int m_colorLoc; + ShaderArrays m_shaderArrays; static const int m_waterfallBufferHeight = 256; From c9fd26f661a79402a2ef05ccc3a25888f815595d Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 02:19:52 +0100 Subject: [PATCH 038/956] GLSpectrum: suppress VLAs templatized version --- sdrbase/util/incrementalarray.h | 59 ++++++++++++++++++++++++ sdrgui/gui/glspectrum.cpp | 20 ++++---- sdrgui/gui/glspectrum.h | 82 ++------------------------------- 3 files changed, 74 insertions(+), 87 deletions(-) create mode 100644 sdrbase/util/incrementalarray.h diff --git a/sdrbase/util/incrementalarray.h b/sdrbase/util/incrementalarray.h new file mode 100644 index 000000000..ffe142a9e --- /dev/null +++ b/sdrbase/util/incrementalarray.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 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 // +// // +// 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 SDRBASE_UTIL_INCREMENTALARRAY_H_ +#define SDRBASE_UTIL_INCREMENTALARRAY_H_ + +#include + +template +class IncrementalArray +{ +public: + T *m_array; + + IncrementalArray(); + ~IncrementalArray(); + + void allocate(uint32_t size); + +private: + uint32_t m_size; +}; + +template +IncrementalArray::IncrementalArray() : + m_array(0), + m_size(0) +{ +} + +template +IncrementalArray::~IncrementalArray() +{ + if (m_array) { delete[] m_array; } +} + +template +void IncrementalArray::allocate(uint32_t size) +{ + if (size <= m_size) { return; } + if (m_array) { delete[] m_array; } + m_array = new T[size]; + m_size = size; +} + +#endif /* SDRBASE_UTIL_INCREMENTALARRAY_H_ */ diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 44e80184f..9a511628f 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -813,7 +813,7 @@ void GLSpectrum::paintGL() } { //GLfloat q3[2*m_fftSize]; - GLfloat *q3 = m_shaderArrays.m_q3FFT; + GLfloat *q3 = m_q3FFT.m_array; Real bottom = -m_powerRange; for(int i = 0; i < m_fftSize; i++) { @@ -837,7 +837,7 @@ void GLSpectrum::paintGL() { Real bottom = -m_powerRange; //GLfloat q3[2*m_fftSize]; - GLfloat *q3 = m_shaderArrays.m_q3FFT; + GLfloat *q3 = m_q3FFT.m_array; for(int i = 0; i < m_fftSize; i++) { Real v = (*m_currentSpectrum)[i] - m_referenceLevel; @@ -863,7 +863,7 @@ void GLSpectrum::paintGL() { //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_shaderArrays.m_q3TickTime; + GLfloat *q3 = m_q3TickTime.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -891,7 +891,7 @@ void GLSpectrum::paintGL() { //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_shaderArrays.m_q3TickFrequency; + GLfloat *q3 = m_q3TickFrequency.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -925,7 +925,7 @@ void GLSpectrum::paintGL() { //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_shaderArrays.m_q3TickPower; + GLfloat *q3 = m_q3TickPower.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) @@ -953,7 +953,7 @@ void GLSpectrum::paintGL() { //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_shaderArrays.m_q3TickFrequency; + GLfloat *q3 = m_q3TickFrequency.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) @@ -1579,7 +1579,7 @@ void GLSpectrum::applyChanges() m_histogramHoldoff = new quint8[100 * m_fftSize]; memset(m_histogramHoldoff, 0x07, 100 * m_fftSize); - m_shaderArrays.allocFFT(2*m_fftSize); + m_q3FFT.allocate(2*m_fftSize); } if(fftSizeChanged || windowSizeChanged) @@ -1588,9 +1588,9 @@ void GLSpectrum::applyChanges() m_waterfallTexturePos = 0; } - m_shaderArrays.allocTickTime(4*m_timeScale.getTickList().count()); - m_shaderArrays.allocTickFrequency(4*m_frequencyScale.getTickList().count()); - m_shaderArrays.allocTickPower(4*m_powerScale.getTickList().count()); + m_q3TickTime.allocate(4*m_timeScale.getTickList().count()); + m_q3TickFrequency.allocate(4*m_frequencyScale.getTickList().count()); + m_q3TickPower.allocate(4*m_powerScale.getTickList().count()); } void GLSpectrum::mouseMoveEvent(QMouseEvent* event) diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index dcc4d8c65..f00da8249 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -33,6 +33,7 @@ #include "gui/glshadertextured.h" #include "dsp/channelmarker.h" #include "util/export.h" +#include "util/incrementalarray.h" class QOpenGLShaderProgram; @@ -72,82 +73,6 @@ public: void connectTimer(const QTimer& timer); private: - struct ShaderArrays - { - GLfloat *m_q3TickTime; - GLfloat *m_q3TickFrequency; - GLfloat *m_q3TickPower; - GLfloat *m_q3FFT; - - uint32_t m_q3TickTimeSize; - uint32_t m_q3TickTimeFrequencySize; - uint32_t m_q3TickPowerSize; - uint32_t m_q3FFTSize; - - ShaderArrays() : - m_q3TickTime(0), - m_q3TickFrequency(0), - m_q3TickPower(0), - m_q3FFT(0), - m_q3TickTimeSize(0), - m_q3TickTimeFrequencySize(0), - m_q3TickPowerSize(0), - m_q3FFTSize(0) - {} - - ~ShaderArrays() - { - if (m_q3TickTime) { delete[] m_q3TickTime; } - if (m_q3TickFrequency) { delete[] m_q3TickFrequency; } - if (m_q3TickPower) { delete[] m_q3TickPower; } - if (m_q3FFT) { delete[] m_q3FFT; } - } - - void allocTickTime(uint32_t size) - { - if (size <= m_q3TickTimeSize) { - return; - } - - if (m_q3TickTime) { delete[] m_q3TickTime; } - m_q3TickTime = new GLfloat[size]; - m_q3TickTimeSize = size; - } - - void allocTickFrequency(uint32_t size) - { - if (size <= m_q3TickTimeFrequencySize) { - return; - } - - if (m_q3TickFrequency) { delete[] m_q3TickFrequency; } - m_q3TickFrequency = new GLfloat[size]; - m_q3TickTimeFrequencySize = size; - } - - void allocTickPower(uint32_t size) - { - if (size <= m_q3TickPowerSize) { - return; - } - - if (m_q3TickPower) { delete[] m_q3TickPower; } - m_q3TickPower = new GLfloat[size]; - m_q3TickPowerSize = size; - } - - void allocFFT(uint32_t size) - { - if (size <= m_q3FFTSize) { - return; - } - - if (m_q3FFT) { delete[] m_q3FFT; } - m_q3FFT = new GLfloat[size]; - m_q3FFTSize = size; - } - }; - struct ChannelMarkerState { ChannelMarker* m_channelMarker; QMatrix4x4 m_glMatrixWaterfall; @@ -240,7 +165,10 @@ private: GLShaderTextured m_glShaderHistogram; int m_matrixLoc; int m_colorLoc; - ShaderArrays m_shaderArrays; + IncrementalArray m_q3TickTime; + IncrementalArray m_q3TickFrequency; + IncrementalArray m_q3TickPower; + IncrementalArray m_q3FFT; static const int m_waterfallBufferHeight = 256; From 9c882a59aac2483232605b2816f4443efe3c1df2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 02:33:18 +0100 Subject: [PATCH 039/956] GLScopeNG: suppress VLAs --- sdrgui/gui/glscopeng.cpp | 43 ++++++++++++++++++++++++++++++---------- sdrgui/gui/glscopeng.h | 7 +++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index 642b68a07..3210eff9b 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -213,7 +213,8 @@ void GLScopeNG::paintGL() { tickList = &m_y1Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY1.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -243,7 +244,8 @@ void GLScopeNG::paintGL() { tickList = &m_x1Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX1.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -386,7 +388,8 @@ void GLScopeNG::paintGL() { tickList = &m_y2Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY2.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -416,7 +419,8 @@ void GLScopeNG::paintGL() { tickList = &m_x2Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -566,7 +570,8 @@ void GLScopeNG::paintGL() // Horizontal Y1 tickList = &m_y1Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY1.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -594,7 +599,8 @@ void GLScopeNG::paintGL() // Vertical X1 tickList = &m_x1Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX1.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -655,7 +661,8 @@ void GLScopeNG::paintGL() // Horizontal Y2 tickList = &m_y2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -782,7 +789,8 @@ void GLScopeNG::paintGL() // Horizontal Y2 tickList = &m_y2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -805,7 +813,8 @@ void GLScopeNG::paintGL() // Vertical X2 tickList = &m_x2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -871,7 +880,8 @@ void GLScopeNG::paintGL() if(end - start < 2) start--; - GLfloat q3[2*(end - start)]; + //GLfloat q3[2*(end - start)]; + GLfloat *q3 = m_q3Polar.m_array; const float *trace0 = (*m_traces)[0]; memcpy(q3, &(trace0[2*start+1]), (2*(end - start) - 1)*sizeof(float)); // copy X values @@ -1018,6 +1028,19 @@ void GLScopeNG::applyConfig() { setPolarDisplays(); } + + m_q3TickY1.allocate(4*m_y1Scale.getTickList().count()); + m_q3TickY2.allocate(4*m_y2Scale.getTickList().count()); + m_q3TickX1.allocate(4*m_x1Scale.getTickList().count()); + m_q3TickX2.allocate(4*m_x1Scale.getTickList().count()); + + int start = (m_timeOfsProMill/1000.0) * m_traceSize; + int end = std::min(start + m_traceSize/m_timeBase, m_traceSize); + + if(end - start < 2) + start--; + + m_q3Polar.allocate(2*(end - start)); } void GLScopeNG::setUniqueDisplays() diff --git a/sdrgui/gui/glscopeng.h b/sdrgui/gui/glscopeng.h index c49d07c93..e28816e51 100644 --- a/sdrgui/gui/glscopeng.h +++ b/sdrgui/gui/glscopeng.h @@ -31,6 +31,7 @@ #include "gui/glshadertextured.h" #include "util/export.h" #include "util/bitfieldindex.h" +#include "util/incrementalarray.h" class QPainter; @@ -131,6 +132,12 @@ private: GLShaderTextured m_glShaderBottom2Scale; GLShaderTextured m_glShaderPowerOverlay; + IncrementalArray m_q3Polar; + IncrementalArray m_q3TickY1; + IncrementalArray m_q3TickY2; + IncrementalArray m_q3TickX1; + IncrementalArray m_q3TickX2; + static const int m_topMargin = 5; static const int m_botMargin = 20; static const int m_leftMargin = 35; From e5ad63e9b38308a14fc42586a33bf95c4990359a Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 03:22:30 +0100 Subject: [PATCH 040/956] GLScopeMulti: suppress VLAs --- sdrgui/gui/glscopemulti.cpp | 43 ++++++++++++++++++++++++++++--------- sdrgui/gui/glscopemulti.h | 7 ++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/sdrgui/gui/glscopemulti.cpp b/sdrgui/gui/glscopemulti.cpp index e78a288a4..c963f63b2 100644 --- a/sdrgui/gui/glscopemulti.cpp +++ b/sdrgui/gui/glscopemulti.cpp @@ -213,7 +213,8 @@ void GLScopeMulti::paintGL() { tickList = &m_y1Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY1.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -243,7 +244,8 @@ void GLScopeMulti::paintGL() { tickList = &m_x1Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX1.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -386,7 +388,8 @@ void GLScopeMulti::paintGL() { tickList = &m_y2Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY2.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -416,7 +419,8 @@ void GLScopeMulti::paintGL() { tickList = &m_x2Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -566,7 +570,8 @@ void GLScopeMulti::paintGL() // Horizontal Y1 tickList = &m_y1Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY1.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -594,7 +599,8 @@ void GLScopeMulti::paintGL() // Vertical X1 tickList = &m_x1Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX1.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -655,7 +661,8 @@ void GLScopeMulti::paintGL() // Horizontal Y2 tickList = &m_y2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -782,7 +789,8 @@ void GLScopeMulti::paintGL() // Horizontal Y2 tickList = &m_y2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -805,7 +813,8 @@ void GLScopeMulti::paintGL() // Vertical X2 tickList = &m_x2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -871,7 +880,8 @@ void GLScopeMulti::paintGL() if(end - start < 2) start--; - GLfloat q3[2*(end - start)]; + //GLfloat q3[2*(end - start)]; + GLfloat *q3 = m_q3Polar.m_array; const float *trace0 = (*m_traces)[0]; memcpy(q3, &(trace0[2*start+1]), (2*(end - start) - 1)*sizeof(float)); // copy X values @@ -1018,6 +1028,19 @@ void GLScopeMulti::applyConfig() { setPolarDisplays(); } + + m_q3TickY1.allocate(4*m_y1Scale.getTickList().count()); + m_q3TickY2.allocate(4*m_y2Scale.getTickList().count()); + m_q3TickX1.allocate(4*m_x1Scale.getTickList().count()); + m_q3TickX2.allocate(4*m_x1Scale.getTickList().count()); + + int start = (m_timeOfsProMill/1000.0) * m_traceSize; + int end = std::min(start + m_traceSize/m_timeBase, m_traceSize); + + if(end - start < 2) + start--; + + m_q3Polar.allocate(2*(end - start)); } void GLScopeMulti::setUniqueDisplays() diff --git a/sdrgui/gui/glscopemulti.h b/sdrgui/gui/glscopemulti.h index 577008586..5466522a1 100644 --- a/sdrgui/gui/glscopemulti.h +++ b/sdrgui/gui/glscopemulti.h @@ -31,6 +31,7 @@ #include "gui/glshadertextured.h" #include "util/export.h" #include "util/bitfieldindex.h" +#include "util/incrementalarray.h" class QPainter; @@ -131,6 +132,12 @@ private: GLShaderTextured m_glShaderBottom2Scale; GLShaderTextured m_glShaderPowerOverlay; + IncrementalArray m_q3Polar; + IncrementalArray m_q3TickY1; + IncrementalArray m_q3TickY2; + IncrementalArray m_q3TickX1; + IncrementalArray m_q3TickX2; + static const int m_topMargin = 5; static const int m_botMargin = 20; static const int m_leftMargin = 35; From 5c055ac2ad0b30a7e240025afa698a7850d05e53 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 03:22:47 +0100 Subject: [PATCH 041/956] GLScope: suppress VLAs --- sdrgui/gui/glscope.cpp | 53 +++++++++++++++++++++++++++++++++--------- sdrgui/gui/glscope.h | 8 +++++++ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/sdrgui/gui/glscope.cpp b/sdrgui/gui/glscope.cpp index 29db0558a..cda180827 100644 --- a/sdrgui/gui/glscope.cpp +++ b/sdrgui/gui/glscope.cpp @@ -287,8 +287,9 @@ void GLScope::paintGL() if(!m_mutex.tryLock(2)) return; - if(m_configChanged) + if(m_configChanged) { applyConfig(); + } handleMode(); @@ -327,7 +328,8 @@ void GLScope::paintGL() tickList = &m_y1Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY1.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) @@ -357,7 +359,8 @@ void GLScope::paintGL() // Vertical X1 tickList = &m_x1Scale.getTickList(); - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX1.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -451,6 +454,7 @@ void GLScope::paintGL() if(m_displayTrace->size() > 0) { { + int start = (m_timeOfsProMill/1000.0) * m_displayTrace->size(); int end = std::min(start + m_displayTrace->size()/m_timeBase, m_displayTrace->size()); if(end - start < 2) @@ -458,7 +462,9 @@ void GLScope::paintGL() float posLimit = 1.0 / m_amp1; float negLimit = -1.0 / m_amp1; - GLfloat q3[2*(end -start)]; + //GLfloat q3[2*(end -start)]; + m_q3Trace.allocate(2*(end - start)); + GLfloat *q3 = m_q3Trace.m_array; for (int i = start; i < end; i++) { @@ -525,6 +531,7 @@ void GLScope::paintGL() if (m_displayTrace->size() > 0) { { + int start = (m_timeOfsProMill/1000.0) * m_displayTrace->size(); int end = std::min(start + m_displayTrace->size()/m_timeBase, m_displayTrace->size()); @@ -532,10 +539,13 @@ void GLScope::paintGL() start--; } + float posLimit = 1.0 / m_amp2; float negLimit = -1.0 / m_amp2; - GLfloat q3[2*(end - start)]; + //GLfloat q3[2*(end - start)]; + m_q3Trace.allocate(2*(end - start)); + GLfloat *q3 = m_q3Trace.m_array; for(int i = start; i < end; i++) { @@ -573,7 +583,8 @@ void GLScope::paintGL() // Horizontal Y2 tickList = &m_y2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -637,7 +648,8 @@ void GLScope::paintGL() // Horizontal Y2 tickList = &m_y2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickY2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -660,7 +672,8 @@ void GLScope::paintGL() // Vertical X2 tickList = &m_x2Scale.getTickList(); { - GLfloat q3[4*tickList->count()]; + //GLfloat q3[4*tickList->count()]; + GLfloat *q3 = m_q3TickX2.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; @@ -750,14 +763,19 @@ void GLScope::paintGL() { if (m_mode == ModeIQPolar) { + int start = (m_timeOfsProMill/1000.0) * m_displayTrace->size(); int end = std::min(start + m_displayTrace->size()/m_timeBase, m_displayTrace->size()); if (end - start < 2) { start--; } - { - GLfloat q3[2*(end - start)]; + + + { + //GLfloat q3[2*(end - start)]; + m_q3Trace.allocate(2*(end - start)); + GLfloat *q3 = m_q3Trace.m_array; for(int i = start; i < end; i++) { @@ -802,10 +820,13 @@ void GLScope::paintGL() start--; } + float posLimit = 1.0 / m_amp2; float negLimit = -1.0 / m_amp2; - GLfloat q3[2*(end - start)]; + //GLfloat q3[2*(end - start)]; + m_q3Trace.allocate(2*(end - start)); + GLfloat *q3 = m_q3Trace.m_array; for(int i = start; i < end; i++) { float v = (*m_displayTrace)[i].imag(); @@ -2122,6 +2143,16 @@ void GLScope::applyConfig() } // X2 scale } // Secondary display only + + m_q3TickY1.allocate(4*m_y1Scale.getTickList().count()); + m_q3TickY2.allocate(4*m_y2Scale.getTickList().count()); + m_q3TickX1.allocate(4*m_x1Scale.getTickList().count()); + m_q3TickX2.allocate(4*m_x1Scale.getTickList().count()); +} + +void GLScope::applyTraceConfig(uint32_t size) +{ + m_q3Trace.allocate(2*size); } void GLScope::tick() diff --git a/sdrgui/gui/glscope.h b/sdrgui/gui/glscope.h index 9966e5ff3..8319583b6 100644 --- a/sdrgui/gui/glscope.h +++ b/sdrgui/gui/glscope.h @@ -34,6 +34,7 @@ #include "gui/glshadertextured.h" #include "util/export.h" #include "util/bitfieldindex.h" +#include "util/incrementalarray.h" class ScopeVis; class QPainter; @@ -167,6 +168,12 @@ private: GLShaderTextured m_glShaderBottom2Scale; GLShaderTextured m_glShaderPowerOverlay; + IncrementalArray m_q3Trace; + IncrementalArray m_q3TickY1; + IncrementalArray m_q3TickY2; + IncrementalArray m_q3TickX1; + IncrementalArray m_q3TickX2; + void initializeGL(); void resizeGL(int width, int height); void paintGL(); @@ -175,6 +182,7 @@ private: void handleMode(); void applyConfig(); + void applyTraceConfig(uint32_t size); void drawPowerOverlay(); protected slots: From d6cc7ef23ddd9b50945dbf8ff492b4482db80216 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 04:15:46 +0100 Subject: [PATCH 042/956] DATV Demod: removed VLAs in leansdr. Activated VLA warning at compile time --- CMakeLists.txt | 2 +- plugins/channelrx/demoddatv/leansdr/dvb.h | 6 +- .../demoddatv/leansdr/incrementalarray.h | 64 +++++++++++++++++++ plugins/channelrx/demoddatv/leansdr/sdr.h | 21 ++++-- 4 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 plugins/channelrx/demoddatv/leansdr/incrementalarray.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 96d1b840a..3c1fd2f52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,7 +204,7 @@ if (SANITIZE_ADDRESS) set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -fsanitize=address") endif() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fmax-errors=10 -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -fmax-errors=10 -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") ############################################################################## # base libraries diff --git a/plugins/channelrx/demoddatv/leansdr/dvb.h b/plugins/channelrx/demoddatv/leansdr/dvb.h index 4f4957445..3a08afe06 100644 --- a/plugins/channelrx/demoddatv/leansdr/dvb.h +++ b/plugins/channelrx/demoddatv/leansdr/dvb.h @@ -7,6 +7,7 @@ #include "leansdr/convolutional.h" #include "leansdr/sdr.h" #include "leansdr/rs.h" +#include "leansdr/incrementalarray.h" namespace leansdr { @@ -1459,6 +1460,7 @@ private: dvb_dec_interface *dec; TCS *map; // [nsymbols] }*syncs; // [nsyncs] + IncrementalArray m_totaldiscr; int current_sync; static const int chunk_size = 128; int resync_phase; @@ -1512,6 +1514,7 @@ public: // polarity inversion. We could reduce nsyncs. syncs = new sync[nsyncs]; + m_totaldiscr.allocate(nsyncs); for (int s = 0; s < nsyncs; ++s) { @@ -1636,7 +1639,8 @@ public: while ((long) in.readable() >= nshifts * chunk_size + (nshifts - 1) && ((long) out.writable() * 8) >= fec->bits_in * chunk_size) { - TPM totaldiscr[nsyncs]; + //TPM totaldiscr[nsyncs]; + TPM *totaldiscr = m_totaldiscr.m_array; for (int s = 0; s < nsyncs; ++s) totaldiscr[s] = 0; diff --git a/plugins/channelrx/demoddatv/leansdr/incrementalarray.h b/plugins/channelrx/demoddatv/leansdr/incrementalarray.h new file mode 100644 index 000000000..ac2c33db5 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/incrementalarray.h @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 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 // +// // +// 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 SDRBASE_UTIL_INCREMENTALARRAY_H_ +#define SDRBASE_UTIL_INCREMENTALARRAY_H_ + +#include + +namespace leansdr +{ + +template +class IncrementalArray +{ +public: + T *m_array; + + IncrementalArray(); + ~IncrementalArray(); + + void allocate(uint32_t size); + +private: + uint32_t m_size; +}; + +template +IncrementalArray::IncrementalArray() : + m_array(0), + m_size(0) +{ +} + +template +IncrementalArray::~IncrementalArray() +{ + if (m_array) { delete[] m_array; } +} + +template +void IncrementalArray::allocate(uint32_t size) +{ + if (size <= m_size) { return; } + if (m_array) { delete[] m_array; } + m_array = new T[size]; + m_size = size; +} + +} // namespace + +#endif /* SDRBASE_UTIL_INCREMENTALARRAY_H_ */ diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h index ebd4d9c23..94b35db94 100644 --- a/plugins/channelrx/demoddatv/leansdr/sdr.h +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -3,6 +3,7 @@ #include "leansdr/math.h" #include "leansdr/dsp.h" +#include "leansdr/incrementalarray.h" namespace leansdr { @@ -50,6 +51,8 @@ struct auto_notch: runnable __slots[s].i = -1; __slots[s].expj = new complex [fft.n]; } + m_data.allocate(fft.n); + m_amp.allocate(fft.n); } void run() @@ -71,7 +74,8 @@ struct auto_notch: runnable void detect() { complex *pin = in.rd(); - complex data[fft.n]; + //complex data[fft.n]; + complex *data = m_data.m_array; float m0 = 0, m2 = 0; for (unsigned int i = 0; i < fft.n; ++i) @@ -95,7 +99,8 @@ struct auto_notch: runnable } fft.inplace(data, true); - float amp[fft.n]; + //float amp[fft.n]; + float *amp = m_amp.m_array; for (unsigned int i = 0; i < fft.n; ++i) { amp[i] = hypotf(data[i].re, data[i].im); @@ -179,6 +184,8 @@ private: int phase; float gain; T agc_rms_setpoint; + IncrementalArray > m_data; + IncrementalArray m_amp; }; // SIGNAL STRENGTH ESTIMATOR @@ -1537,12 +1544,16 @@ struct cnr_fft: runnable if (bandwidth > 0.25) { fail("cnr_fft::cnr_fft", "CNR estimator requires Fsampling > 4x Fsignal"); } + m_data.allocate(fft.n); + m_power.allocate(fft.n); } float bandwidth; float *freq_tap, tap_multiplier; int decimation; float kavg; + IncrementalArray > m_data; + IncrementalArray m_power; void run() { @@ -1564,10 +1575,12 @@ private: { float center_freq = freq_tap ? *freq_tap * tap_multiplier : 0; int icf = floor(center_freq * fft.n + 0.5); - complex data[fft.n]; + //complex data[fft.n]; + complex *data = m_data.m_array; memcpy(data, in.rd(), fft.n * sizeof(data[0])); fft.inplace(data, true); - T power[fft.n]; + //T power[fft.n]; + T *power = m_power.m_array; for (unsigned int i = 0; i < fft.n; ++i) power[i] = data[i].re * data[i].re + data[i].im * data[i].im; if (!avgpower) From ed63cb51c2c76cd96e655cfd144df6e6f0fa9d9e Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 04:24:54 +0100 Subject: [PATCH 043/956] GLScope: fixed over VLA replacement --- sdrgui/gui/glscope.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/gui/glscope.cpp b/sdrgui/gui/glscope.cpp index cda180827..32ade47b5 100644 --- a/sdrgui/gui/glscope.cpp +++ b/sdrgui/gui/glscope.cpp @@ -2147,7 +2147,7 @@ void GLScope::applyConfig() m_q3TickY1.allocate(4*m_y1Scale.getTickList().count()); m_q3TickY2.allocate(4*m_y2Scale.getTickList().count()); m_q3TickX1.allocate(4*m_x1Scale.getTickList().count()); - m_q3TickX2.allocate(4*m_x1Scale.getTickList().count()); + m_q3TickX2.allocate(4*m_x2Scale.getTickList().count()); } void GLScope::applyTraceConfig(uint32_t size) From 8ed4abcd2eb405ad38de2d62260cdb9dd599e844 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 04:30:12 +0100 Subject: [PATCH 044/956] GLScopeNG, GLScopeMulti: fixed over VLA replacement --- sdrgui/gui/glscopemulti.cpp | 2 +- sdrgui/gui/glscopeng.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdrgui/gui/glscopemulti.cpp b/sdrgui/gui/glscopemulti.cpp index c963f63b2..d3d9549b4 100644 --- a/sdrgui/gui/glscopemulti.cpp +++ b/sdrgui/gui/glscopemulti.cpp @@ -1032,7 +1032,7 @@ void GLScopeMulti::applyConfig() m_q3TickY1.allocate(4*m_y1Scale.getTickList().count()); m_q3TickY2.allocate(4*m_y2Scale.getTickList().count()); m_q3TickX1.allocate(4*m_x1Scale.getTickList().count()); - m_q3TickX2.allocate(4*m_x1Scale.getTickList().count()); + m_q3TickX2.allocate(4*m_x2Scale.getTickList().count()); int start = (m_timeOfsProMill/1000.0) * m_traceSize; int end = std::min(start + m_traceSize/m_timeBase, m_traceSize); diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index 3210eff9b..30f32bac0 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -1032,7 +1032,7 @@ void GLScopeNG::applyConfig() m_q3TickY1.allocate(4*m_y1Scale.getTickList().count()); m_q3TickY2.allocate(4*m_y2Scale.getTickList().count()); m_q3TickX1.allocate(4*m_x1Scale.getTickList().count()); - m_q3TickX2.allocate(4*m_x1Scale.getTickList().count()); + m_q3TickX2.allocate(4*m_x2Scale.getTickList().count()); int start = (m_timeOfsProMill/1000.0) * m_traceSize; int end = std::min(start + m_traceSize/m_timeBase, m_traceSize); From 34bdfbf49552a47b20fcf85726d9b5127ffc29b4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Mar 2018 09:14:37 +0100 Subject: [PATCH 045/956] class vs struct mismatch fixes --- httpserver/httpdocrootsettings.h | 3 ++- httpserver/httplistenersettings.h | 3 ++- plugins/channelrx/demodatv/atvdemodsettings.h | 2 +- sdrbase/dsp/cwkeyersettings.h | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/httpserver/httpdocrootsettings.h b/httpserver/httpdocrootsettings.h index ebcc73bff..1bb3a7d15 100644 --- a/httpserver/httpdocrootsettings.h +++ b/httpserver/httpdocrootsettings.h @@ -12,8 +12,9 @@ namespace qtwebapp { -struct HttpDocrootSettings +class HttpDocrootSettings { +public: QString path; QString encoding; int maxAge; diff --git a/httpserver/httplistenersettings.h b/httpserver/httplistenersettings.h index 378da1c66..018ee8303 100644 --- a/httpserver/httplistenersettings.h +++ b/httpserver/httplistenersettings.h @@ -10,8 +10,9 @@ namespace qtwebapp { -struct HttpListenerSettings +class HttpListenerSettings { +public: QString host; int port; int minThreads; diff --git a/plugins/channelrx/demodatv/atvdemodsettings.h b/plugins/channelrx/demodatv/atvdemodsettings.h index f72f8e9d7..fb4c39fc1 100644 --- a/plugins/channelrx/demodatv/atvdemodsettings.h +++ b/plugins/channelrx/demodatv/atvdemodsettings.h @@ -21,7 +21,7 @@ #include #include -struct Serializable; +class Serializable; struct ATVDemodSettings { diff --git a/sdrbase/dsp/cwkeyersettings.h b/sdrbase/dsp/cwkeyersettings.h index 49fcbfd95..ec3c5c489 100644 --- a/sdrbase/dsp/cwkeyersettings.h +++ b/sdrbase/dsp/cwkeyersettings.h @@ -21,8 +21,9 @@ #include #include -struct CWKeyerSettings +class CWKeyerSettings { +public: typedef enum { CWNone, From 2b1d5f083863ae9cccbbfa830a80115bee6621aa Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 3 Mar 2018 10:06:47 +0100 Subject: [PATCH 046/956] Fixed -Woverloaded-virtual warnings --- CMakeLists.txt | 2 +- plugins/channelrx/demodnfm/nfmdemodgui.cpp | 1 - sdrbase/dsp/nullsink.cpp | 2 +- sdrbase/dsp/nullsink.h | 2 +- sdrgui/webapi/webapiadaptergui.cpp | 2 +- sdrgui/webapi/webapiadaptergui.h | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c1fd2f52..11122c5f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,7 +204,7 @@ if (SANITIZE_ADDRESS) set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -fsanitize=address") endif() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -fmax-errors=10 -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -Woverloaded-virtual -fmax-errors=10 -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") ############################################################################## # base libraries diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.cpp b/plugins/channelrx/demodnfm/nfmdemodgui.cpp index a2d27eed7..1f65583a1 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodgui.cpp @@ -7,7 +7,6 @@ #include #include "ui_nfmdemodgui.h" -#include "dsp/nullsink.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" diff --git a/sdrbase/dsp/nullsink.cpp b/sdrbase/dsp/nullsink.cpp index 3537d4ff9..592735116 100644 --- a/sdrbase/dsp/nullsink.cpp +++ b/sdrbase/dsp/nullsink.cpp @@ -16,7 +16,7 @@ bool NullSink::init(const Message& message __attribute__((unused))) return false; } -void NullSink::feed(SampleVector::const_iterator begin __attribute__((unused)), SampleVector::const_iterator end __attribute__((unused)), bool positiveOnly __attribute__((unused))) +void NullSink::feed(const SampleVector::const_iterator& begin __attribute__((unused)), const SampleVector::const_iterator& end __attribute__((unused)), bool positiveOnly __attribute__((unused))) { } diff --git a/sdrbase/dsp/nullsink.h b/sdrbase/dsp/nullsink.h index aef7240eb..f9c75447c 100644 --- a/sdrbase/dsp/nullsink.h +++ b/sdrbase/dsp/nullsink.h @@ -13,7 +13,7 @@ public: virtual ~NullSink(); virtual bool init(const Message& cmd); - virtual void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool positiveOnly); + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); virtual void stop(); virtual bool handleMessage(const Message& message); diff --git a/sdrgui/webapi/webapiadaptergui.cpp b/sdrgui/webapi/webapiadaptergui.cpp index a06a5d424..0869cb82c 100644 --- a/sdrgui/webapi/webapiadaptergui.cpp +++ b/sdrgui/webapi/webapiadaptergui.cpp @@ -103,7 +103,7 @@ int WebAPIAdapterGUI::instanceSummary( } int WebAPIAdapterGUI::instanceDelete( - SWGSDRangel::SWGInstanceSummaryResponse& response __attribute__((unused)), + SWGSDRangel::SWGSuccessResponse& response __attribute__((unused)), SWGSDRangel::SWGErrorResponse& error) { *error.getMessage() = QString("Not supported in GUI instance"); diff --git a/sdrgui/webapi/webapiadaptergui.h b/sdrgui/webapi/webapiadaptergui.h index 18a91ae5b..5b5891d91 100644 --- a/sdrgui/webapi/webapiadaptergui.h +++ b/sdrgui/webapi/webapiadaptergui.h @@ -36,7 +36,7 @@ public: SWGSDRangel::SWGErrorResponse& error); virtual int instanceDelete( - SWGSDRangel::SWGInstanceSummaryResponse& response, + SWGSDRangel::SWGSuccessResponse& response, SWGSDRangel::SWGErrorResponse& error); virtual int instanceDevices( From c22d146376923f758148affe200d8b89ef813b08 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 3 Mar 2018 20:23:38 +0100 Subject: [PATCH 047/956] Adapt to MSVC linker --- devices/bladerf/devicebladerf.h | 4 +- devices/bladerf/devicebladerfshared.h | 3 +- devices/bladerf/devicebladerfvalues.h | 3 +- devices/hackrf/devicehackrf.h | 4 +- devices/hackrf/devicehackrfshared.h | 3 +- devices/hackrf/devicehackrfvalues.h | 4 +- devices/limesdr/devicelimesdr.h | 4 +- devices/limesdr/devicelimesdrparam.h | 4 +- devices/limesdr/devicelimesdrshared.h | 3 +- devices/perseus/deviceperseus.h | 4 +- devices/perseus/deviceperseusscan.h | 3 +- devices/plutosdr/deviceplutosdr.h | 4 +- devices/plutosdr/deviceplutosdrbox.h | 4 +- devices/plutosdr/deviceplutosdrparams.h | 4 +- devices/plutosdr/deviceplutosdrscan.h | 4 +- devices/plutosdr/deviceplutosdrshared.h | 3 +- httpserver/CMakeLists.txt | 1 + httpserver/httpconnectionhandler.h | 4 +- httpserver/httpconnectionhandlerpool.h | 4 +- httpserver/httpcookie.h | 4 +- httpserver/httplistener.h | 4 +- httpserver/httprequest.h | 4 +- httpserver/httprequesthandler.h | 4 +- httpserver/httpresponse.h | 4 +- httpserver/httpsession.h | 6 +- httpserver/httpsessionstore.h | 4 +- httpserver/staticfilecontroller.h | 4 +- logging/CMakeLists.txt | 1 + logging/dualfilelogger.h | 4 +- logging/filelogger.h | 4 +- logging/logger.h | 4 +- logging/loggerwithfile.h | 4 +- logging/logmessage.h | 4 +- qrtplib/CMakeLists.txt | 1 + qrtplib/rtcpapppacket.h | 4 +- qrtplib/rtcpbyepacket.h | 4 +- qrtplib/rtcpcompoundpacket.h | 4 +- qrtplib/rtcpcompoundpacketbuilder.h | 4 +- qrtplib/rtcppacketbuilder.h | 4 +- qrtplib/rtcprrpacket.h | 4 +- qrtplib/rtcpscheduler.h | 4 +- qrtplib/rtcpsdesinfo.h | 4 +- qrtplib/rtcpsdespacket.h | 4 +- qrtplib/rtcpsrpacket.h | 4 +- qrtplib/rtpabortdescriptors.h | 4 +- qrtplib/rtpaddress.h | 4 +- qrtplib/rtpcollisionlist.h | 4 +- qrtplib/rtpexternaltransmitter.h | 4 +- qrtplib/rtpinternalsourcedata.h | 4 +- qrtplib/rtpipv4address.h | 4 +- qrtplib/rtpipv4destination.h | 4 +- qrtplib/rtplibraryversion.h | 4 +- qrtplib/rtppacketbuilder.h | 4 +- qrtplib/rtprandom.h | 4 +- qrtplib/rtprandomrand48.h | 4 +- qrtplib/rtprandomrands.h | 4 +- qrtplib/rtprandomurandom.h | 4 +- qrtplib/rtpsession.h | 4 +- qrtplib/rtpsessionparams.h | 4 +- qrtplib/rtpsessionsources.h | 4 +- qrtplib/rtpsourcedata.h | 4 +- qrtplib/rtpsources.h | 6 +- qrtplib/rtptcpaddress.h | 4 +- qrtplib/rtptcptransmitter.h | 4 +- qrtplib/rtptimeutilities.h | 10 ++- qrtplib/rtpudptransmitter.h | 8 +- qrtplib/rtpudpv4transmitter.h | 8 +- qrtplib/rtpudpv4transmitternobind.h | 8 +- sdrbase/audio/audiodeviceinfo.h | 2 +- sdrbase/audio/audiofifo.h | 2 +- sdrbase/audio/audioinput.h | 2 +- sdrbase/audio/audionetsink.h | 2 +- sdrbase/audio/audiooutput.h | 2 +- sdrbase/channel/channelsinkapi.h | 2 +- sdrbase/channel/channelsourceapi.h | 2 +- sdrbase/commands/command.h | 4 +- sdrbase/device/deviceenumerator.h | 3 +- sdrbase/device/devicesinkapi.h | 2 +- sdrbase/device/devicesourceapi.h | 2 +- sdrbase/dsp/afsquelch.h | 3 +- sdrbase/dsp/agc.h | 5 +- sdrbase/dsp/basebandsamplesink.h | 2 +- sdrbase/dsp/basebandsamplesource.h | 2 +- sdrbase/dsp/channelmarker.h | 2 +- sdrbase/dsp/ctcssdetector.h | 3 +- sdrbase/dsp/cwkeyer.h | 4 +- sdrbase/dsp/cwkeyersettings.h | 4 +- sdrbase/dsp/decimatorsf.h | 3 +- sdrbase/dsp/devicesamplesink.h | 2 +- sdrbase/dsp/devicesamplesource.h | 2 +- sdrbase/dsp/downchannelizer.h | 2 +- sdrbase/dsp/dspcommands.h | 58 ++++++------- sdrbase/dsp/dspdevicesinkengine.h | 2 +- sdrbase/dsp/dspdevicesourceengine.h | 2 +- sdrbase/dsp/dspengine.h | 2 +- sdrbase/dsp/dvserialengine.h | 4 +- sdrbase/dsp/dvserialworker.h | 3 +- sdrbase/dsp/fftengine.h | 2 +- sdrbase/dsp/fftfilt.h | 5 +- sdrbase/dsp/fftwengine.h | 3 +- sdrbase/dsp/fftwindow.h | 2 +- sdrbase/dsp/filerecord.h | 2 +- sdrbase/dsp/filtermbe.h | 3 +- sdrbase/dsp/filterrc.h | 3 +- sdrbase/dsp/hbfiltertraits.h | 17 ++-- sdrbase/dsp/interpolator.h | 2 +- sdrbase/dsp/kissengine.h | 3 +- sdrbase/dsp/nco.h | 2 +- sdrbase/dsp/ncof.h | 2 +- sdrbase/dsp/nullsink.h | 2 +- sdrbase/dsp/phaselock.h | 3 +- sdrbase/dsp/recursivefilters.h | 4 +- sdrbase/dsp/samplesinkfifo.h | 2 +- sdrbase/dsp/samplesinkfifodoublebuffered.h | 2 +- sdrbase/dsp/samplesourcefifo.h | 2 +- sdrbase/dsp/threadedbasebandsamplesink.h | 4 +- sdrbase/dsp/threadedbasebandsamplesource.h | 2 +- sdrbase/dsp/upchannelizer.h | 2 +- sdrbase/dsp/wfir.h | 4 +- sdrbase/mainparser.h | 4 +- sdrbase/plugin/pluginapi.h | 2 +- sdrbase/plugin/plugininstancegui.h | 2 +- sdrbase/plugin/plugininterface.h | 6 +- sdrbase/plugin/pluginmanager.h | 2 +- sdrbase/settings/mainsettings.h | 3 +- sdrbase/settings/preferences.h | 4 +- sdrbase/settings/preset.h | 4 +- sdrbase/util/CRC64.h | 4 +- sdrbase/util/db.h | 3 +- sdrbase/util/export.h | 98 ++++++++++++++++++---- sdrbase/util/fixedtraits.h | 10 ++- sdrbase/util/message.h | 2 +- sdrbase/util/messagequeue.h | 2 +- sdrbase/util/prettyprint.h | 4 +- sdrbase/util/rtpsink.h | 4 +- sdrbase/util/samplesourceserializer.h | 3 +- sdrbase/util/simpleserializer.h | 2 +- sdrbase/util/spinlock.h | 4 +- sdrbase/util/syncmessenger.h | 2 +- sdrbase/util/uid.h | 4 +- sdrbase/webapi/webapiadapterinterface.h | 4 +- sdrbase/webapi/webapirequestmapper.h | 4 +- sdrbase/webapi/webapiserver.h | 4 +- sdrgui/device/deviceuiset.h | 4 +- sdrgui/dsp/scopevis.h | 2 +- sdrgui/dsp/scopevismulti.h | 2 +- sdrgui/dsp/scopevisng.h | 2 +- sdrgui/dsp/spectrumscopecombovis.h | 2 +- sdrgui/dsp/spectrumscopengcombovis.h | 2 +- sdrgui/dsp/spectrumvis.h | 2 +- sdrgui/gui/aboutdialog.h | 4 +- sdrgui/gui/addpresetdialog.h | 4 +- sdrgui/gui/audiodialog.h | 4 +- sdrgui/gui/basicchannelsettingsdialog.h | 4 +- sdrgui/gui/buttonswitch.h | 4 +- sdrgui/gui/channelwindow.h | 4 +- sdrgui/gui/clickablelabel.h | 4 +- sdrgui/gui/colormapper.h | 2 +- sdrgui/gui/commanditem.h | 4 +- sdrgui/gui/commandkeyreceiver.h | 4 +- sdrgui/gui/commandoutputdialog.h | 2 + sdrgui/gui/cwkeyergui.h | 2 +- sdrgui/gui/editcommanddialog.h | 4 +- sdrgui/gui/externalclockbutton.h | 4 +- sdrgui/gui/externalclockdialog.h | 4 +- sdrgui/gui/glscope.h | 2 +- sdrgui/gui/glscopegui.h | 2 +- sdrgui/gui/glscopemulti.h | 2 +- sdrgui/gui/glscopemultigui.h | 2 +- sdrgui/gui/glscopeng.h | 2 +- sdrgui/gui/glscopenggui.h | 2 +- sdrgui/gui/glshadersimple.h | 4 +- sdrgui/gui/glshadertextured.h | 4 +- sdrgui/gui/glspectrum.h | 2 +- sdrgui/gui/glspectrumgui.h | 2 +- sdrgui/gui/indicator.h | 2 +- sdrgui/gui/levelmeter.h | 3 +- sdrgui/gui/loggingdialog.h | 3 +- sdrgui/gui/mypositiondialog.h | 3 +- sdrgui/gui/pluginsdialog.h | 3 +- sdrgui/gui/presetitem.h | 4 +- sdrgui/gui/rollupwidget.h | 2 +- sdrgui/gui/samplingdevicecontrol.h | 2 +- sdrgui/gui/samplingdevicedialog.h | 4 +- sdrgui/gui/scaleengine.h | 2 +- sdrgui/gui/tickedslider.h | 4 +- sdrgui/gui/transverterbutton.h | 4 +- sdrgui/gui/transverterdialog.h | 4 +- sdrgui/gui/valuedial.h | 2 +- sdrgui/gui/valuedialz.h | 2 +- sdrgui/mainwindow.h | 2 +- sdrgui/webapi/webapiadaptergui.h | 3 +- 192 files changed, 547 insertions(+), 259 deletions(-) diff --git a/devices/bladerf/devicebladerf.h b/devices/bladerf/devicebladerf.h index 5ef31f3b8..73233a79b 100644 --- a/devices/bladerf/devicebladerf.h +++ b/devices/bladerf/devicebladerf.h @@ -19,7 +19,9 @@ #include -class DeviceBladeRF +#include "util/export.h" + +class DEVICES_API DeviceBladeRF { public: static bool open_bladerf(struct bladerf **dev, const char *serial); diff --git a/devices/bladerf/devicebladerfshared.h b/devices/bladerf/devicebladerfshared.h index 4ad706744..996d7deca 100644 --- a/devices/bladerf/devicebladerfshared.h +++ b/devices/bladerf/devicebladerfshared.h @@ -18,8 +18,9 @@ #define DEVICES_BLADERF_DEVICEHACKRFSHARED_H_ #include "util/message.h" +#include "util/export.h" -class DeviceBladeRFShared +class DEVICES_API DeviceBladeRFShared { public: static const float m_sampleFifoLengthInSeconds; diff --git a/devices/bladerf/devicebladerfvalues.h b/devices/bladerf/devicebladerfvalues.h index 40efc736c..23f57706e 100644 --- a/devices/bladerf/devicebladerfvalues.h +++ b/devices/bladerf/devicebladerfvalues.h @@ -17,8 +17,9 @@ #ifndef DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ #define DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ +#include "util/export.h" -class DeviceBladeRFBandwidths { +class DEVICES_API DeviceBladeRFBandwidths { public: static unsigned int getBandwidth(unsigned int bandwidth_index); static unsigned int getBandwidthIndex(unsigned int bandwidth); diff --git a/devices/hackrf/devicehackrf.h b/devices/hackrf/devicehackrf.h index 483f6999f..72e78c048 100644 --- a/devices/hackrf/devicehackrf.h +++ b/devices/hackrf/devicehackrf.h @@ -19,7 +19,9 @@ #include "libhackrf/hackrf.h" -class DeviceHackRF +#include "util/export.h" + +class DEVICES_API DeviceHackRF { public: static DeviceHackRF& instance(); diff --git a/devices/hackrf/devicehackrfshared.h b/devices/hackrf/devicehackrfshared.h index f9106a8d3..cefa5c32a 100644 --- a/devices/hackrf/devicehackrfshared.h +++ b/devices/hackrf/devicehackrfshared.h @@ -18,8 +18,9 @@ #define DEVICES_HACKRF_DEVICEHACKRFSHARED_H_ #include "util/message.h" +#include "util/export.h" -class DeviceHackRFShared +class DEVICES_API DeviceHackRFShared { public: class MsgConfigureFrequencyDelta : public Message diff --git a/devices/hackrf/devicehackrfvalues.h b/devices/hackrf/devicehackrfvalues.h index 3059473c8..d322e4d06 100644 --- a/devices/hackrf/devicehackrfvalues.h +++ b/devices/hackrf/devicehackrfvalues.h @@ -17,7 +17,9 @@ #ifndef DEVICES_HACKRF_DEVICEHACKRFVALUES_H_ #define DEVICES_HACKRF_DEVICEHACKRFVALUES_H_ -class HackRFBandwidths { +#include "util/export.h" + +class DEVICES_API HackRFBandwidths { public: static unsigned int getBandwidth(unsigned int bandwidth_index); static unsigned int getBandwidthIndex(unsigned int bandwidth); diff --git a/devices/limesdr/devicelimesdr.h b/devices/limesdr/devicelimesdr.h index 01421277f..f409b31ed 100644 --- a/devices/limesdr/devicelimesdr.h +++ b/devices/limesdr/devicelimesdr.h @@ -19,7 +19,9 @@ #include "lime/LimeSuite.h" -class DeviceLimeSDR +#include "util/export.h" + +class DEVICES_API DeviceLimeSDR { public: enum PathRxRFE diff --git a/devices/limesdr/devicelimesdrparam.h b/devices/limesdr/devicelimesdrparam.h index 1bc6cdd78..e34251d1b 100644 --- a/devices/limesdr/devicelimesdrparam.h +++ b/devices/limesdr/devicelimesdrparam.h @@ -19,6 +19,8 @@ #include "lime/LimeSuite.h" +#include "util/export.h" + /** * This structure refers to one physical device shared among parties (logical devices represented by * the DeviceSinkAPI or DeviceSourceAPI). @@ -26,7 +28,7 @@ * There is only one copy that is constructed by the first participant and destroyed by the last. * A participant knows it is the first or last by checking the lists of buddies (Rx + Tx). */ -struct DeviceLimeSDRParams +struct DEVICES_API DeviceLimeSDRParams { lms_device_t *m_dev; //!< device handle uint32_t m_nbRxChannels; //!< number of Rx channels (normally 2, we'll see if we really use it...) diff --git a/devices/limesdr/devicelimesdrshared.h b/devices/limesdr/devicelimesdrshared.h index d23894399..6d90a63b5 100644 --- a/devices/limesdr/devicelimesdrshared.h +++ b/devices/limesdr/devicelimesdrshared.h @@ -20,11 +20,12 @@ #include #include "devicelimesdrparam.h" #include "util/message.h" +#include "util/export.h" /** * Structure shared by a buddy with other buddies */ -class DeviceLimeSDRShared +class DEVICES_API DeviceLimeSDRShared { public: class MsgReportBuddyChange : public Message { diff --git a/devices/perseus/deviceperseus.h b/devices/perseus/deviceperseus.h index b4eb8d8f0..3305fdf54 100644 --- a/devices/perseus/deviceperseus.h +++ b/devices/perseus/deviceperseus.h @@ -19,7 +19,9 @@ #include "deviceperseusscan.h" -class DevicePerseus +#include "util/export.h" + +class DEVICES_API DevicePerseus { public: static DevicePerseus& instance(); diff --git a/devices/perseus/deviceperseusscan.h b/devices/perseus/deviceperseusscan.h index 8eb85677a..4c4e1b3d5 100644 --- a/devices/perseus/deviceperseusscan.h +++ b/devices/perseus/deviceperseusscan.h @@ -23,8 +23,9 @@ #include #include +#include "util/export.h" -class DevicePerseusScan +class DEVICES_API DevicePerseusScan { public: struct DeviceScan diff --git a/devices/plutosdr/deviceplutosdr.h b/devices/plutosdr/deviceplutosdr.h index 60795f755..dbae13e0d 100644 --- a/devices/plutosdr/deviceplutosdr.h +++ b/devices/plutosdr/deviceplutosdr.h @@ -22,7 +22,9 @@ #include "deviceplutosdrscan.h" #include "deviceplutosdrbox.h" -class DevicePlutoSDR +#include "util/export.h" + +class DEVICES_API DevicePlutoSDR { public: static DevicePlutoSDR& instance(); diff --git a/devices/plutosdr/deviceplutosdrbox.h b/devices/plutosdr/deviceplutosdrbox.h index 0e2edb2f9..4f6ae948e 100644 --- a/devices/plutosdr/deviceplutosdrbox.h +++ b/devices/plutosdr/deviceplutosdrbox.h @@ -22,7 +22,9 @@ #include #include "deviceplutosdrscan.h" -class DevicePlutoSDRBox +#include "util/export.h" + +class DEVICES_API DevicePlutoSDRBox { public: typedef enum diff --git a/devices/plutosdr/deviceplutosdrparams.h b/devices/plutosdr/deviceplutosdrparams.h index 946106159..e318d3f72 100644 --- a/devices/plutosdr/deviceplutosdrparams.h +++ b/devices/plutosdr/deviceplutosdrparams.h @@ -19,7 +19,9 @@ #include -class DevicePlutoSDRBox; +#include "util/export.h" + +class DEVICES_API DevicePlutoSDRBox; /** * This structure refers to one physical device shared among parties (logical devices represented by diff --git a/devices/plutosdr/deviceplutosdrscan.h b/devices/plutosdr/deviceplutosdrscan.h index 1cb7c607a..1ad1d0d41 100644 --- a/devices/plutosdr/deviceplutosdrscan.h +++ b/devices/plutosdr/deviceplutosdrscan.h @@ -21,7 +21,9 @@ #include #include -class DevicePlutoSDRScan +#include "util/export.h" + +class DEVICES_API DevicePlutoSDRScan { public: struct DeviceScan diff --git a/devices/plutosdr/deviceplutosdrshared.h b/devices/plutosdr/deviceplutosdrshared.h index 211da350c..5ae24df29 100644 --- a/devices/plutosdr/deviceplutosdrshared.h +++ b/devices/plutosdr/deviceplutosdrshared.h @@ -20,13 +20,14 @@ #include #include "util/message.h" +#include "util/export.h" class DevicePlutoSDRParams; /** * Structure shared by a buddy with other buddies */ -class DevicePlutoSDRShared +class DEVICES_API DevicePlutoSDRShared { public: /** diff --git a/httpserver/CMakeLists.txt b/httpserver/CMakeLists.txt index 31d74f277..089ae7d9e 100644 --- a/httpserver/CMakeLists.txt +++ b/httpserver/CMakeLists.txt @@ -33,6 +33,7 @@ set(httpserver_HEADERS include_directories( . + ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/httpserver/httpconnectionhandler.h b/httpserver/httpconnectionhandler.h index b93c73432..e205b6585 100644 --- a/httpserver/httpconnectionhandler.h +++ b/httpserver/httpconnectionhandler.h @@ -18,6 +18,8 @@ #include "httprequesthandler.h" #include "httplistenersettings.h" +#include "util/export.h" + namespace qtwebapp { /** Alias type definition, for compatibility to different Qt versions */ @@ -47,7 +49,7 @@ namespace qtwebapp { The readTimeout value defines the maximum time to wait for a complete HTTP request. @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize. */ -class DECLSPEC HttpConnectionHandler : public QThread { +class HTTPSERVER_API HttpConnectionHandler : public QThread { Q_OBJECT Q_DISABLE_COPY(HttpConnectionHandler) diff --git a/httpserver/httpconnectionhandlerpool.h b/httpserver/httpconnectionhandlerpool.h index 3122a6d20..f264d650b 100644 --- a/httpserver/httpconnectionhandlerpool.h +++ b/httpserver/httpconnectionhandlerpool.h @@ -9,6 +9,8 @@ #include "httpconnectionhandler.h" #include "httplistenersettings.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -46,7 +48,7 @@ namespace qtwebapp { @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize */ -class DECLSPEC HttpConnectionHandlerPool : public QObject { +class HTTPSERVER_API HttpConnectionHandlerPool : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpConnectionHandlerPool) public: diff --git a/httpserver/httpcookie.h b/httpserver/httpcookie.h index 2075554d5..58c84a66b 100644 --- a/httpserver/httpcookie.h +++ b/httpserver/httpcookie.h @@ -10,6 +10,8 @@ #include #include "httpglobal.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -18,7 +20,7 @@ namespace qtwebapp { 2109. */ -class DECLSPEC HttpCookie +class HTTPSERVER_API HttpCookie { public: diff --git a/httpserver/httplistener.h b/httpserver/httplistener.h index aa3168de8..d7b5c0282 100644 --- a/httpserver/httplistener.h +++ b/httpserver/httplistener.h @@ -15,6 +15,8 @@ #include "httprequesthandler.h" #include "httplistenersettings.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -42,7 +44,7 @@ namespace qtwebapp { @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize */ -class DECLSPEC HttpListener : public QTcpServer { +class HTTPSERVER_API HttpListener : public QTcpServer { Q_OBJECT Q_DISABLE_COPY(HttpListener) public: diff --git a/httpserver/httprequest.h b/httpserver/httprequest.h index 79cd2bbee..acca03652 100644 --- a/httpserver/httprequest.h +++ b/httpserver/httprequest.h @@ -17,6 +17,8 @@ #include "httpglobal.h" #include "httplistenersettings.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -36,7 +38,7 @@ namespace qtwebapp { The body is always a little larger than the file itself. */ -class DECLSPEC HttpRequest { +class HTTPSERVER_API HttpRequest { Q_DISABLE_COPY(HttpRequest) friend class HttpSessionStore; diff --git a/httpserver/httprequesthandler.h b/httpserver/httprequesthandler.h index 862baf89c..c2aa08736 100644 --- a/httpserver/httprequesthandler.h +++ b/httpserver/httprequesthandler.h @@ -10,6 +10,8 @@ #include "httprequest.h" #include "httpresponse.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -24,7 +26,7 @@ namespace qtwebapp { @see StaticFileController which delivers static local files. */ -class DECLSPEC HttpRequestHandler : public QObject { +class HTTPSERVER_API HttpRequestHandler : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpRequestHandler) public: diff --git a/httpserver/httpresponse.h b/httpserver/httpresponse.h index 044484b79..4270a37cb 100644 --- a/httpserver/httpresponse.h +++ b/httpserver/httpresponse.h @@ -12,6 +12,8 @@ #include "httpglobal.h" #include "httpcookie.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -33,7 +35,7 @@ namespace qtwebapp { before calling write(). Web Browsers use that information to display a progress bar. */ -class DECLSPEC HttpResponse { +class HTTPSERVER_API HttpResponse { Q_DISABLE_COPY(HttpResponse) public: diff --git a/httpserver/httpsession.h b/httpserver/httpsession.h index dabe982c2..ed7e673cc 100644 --- a/httpserver/httpsession.h +++ b/httpserver/httpsession.h @@ -11,6 +11,8 @@ #include #include "httpglobal.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -20,7 +22,7 @@ namespace qtwebapp { @see HttpSessionStore should be used to create and get instances of this class. */ -class DECLSPEC HttpSession { +class HTTPSERVER_API HttpSession { public: @@ -53,7 +55,7 @@ public: QByteArray getId() const; /** - Null sessions cannot store data. All calls to set() and remove() + Null sessions cannot store data. All calls to set() and remove() do not have any effect.This method is thread safe. */ bool isNull() const; diff --git a/httpserver/httpsessionstore.h b/httpserver/httpsessionstore.h index 395ff8530..301bc329d 100644 --- a/httpserver/httpsessionstore.h +++ b/httpserver/httpsessionstore.h @@ -16,6 +16,8 @@ #include "httprequest.h" #include "httpsessionssettings.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -33,7 +35,7 @@ namespace qtwebapp { */ -class DECLSPEC HttpSessionStore : public QObject { +class HTTPSERVER_API HttpSessionStore : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpSessionStore) public: diff --git a/httpserver/staticfilecontroller.h b/httpserver/staticfilecontroller.h index f369181e3..2ff346e85 100644 --- a/httpserver/staticfilecontroller.h +++ b/httpserver/staticfilecontroller.h @@ -13,6 +13,8 @@ #include "httpresponse.h" #include "httprequesthandler.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -44,7 +46,7 @@ namespace qtwebapp { class HttpDocrootSettings; -class DECLSPEC StaticFileController : public HttpRequestHandler { +class HTTPSERVER_API StaticFileController : public HttpRequestHandler { Q_OBJECT Q_DISABLE_COPY(StaticFileController) public: diff --git a/logging/CMakeLists.txt b/logging/CMakeLists.txt index 4c615fae8..c3e3d18f5 100644 --- a/logging/CMakeLists.txt +++ b/logging/CMakeLists.txt @@ -19,6 +19,7 @@ set(httpserver_HEADERS include_directories( . + ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/logging/dualfilelogger.h b/logging/dualfilelogger.h index 6fb16c568..6599d6542 100644 --- a/logging/dualfilelogger.h +++ b/logging/dualfilelogger.h @@ -13,6 +13,8 @@ #include "logger.h" #include "filelogger.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -21,7 +23,7 @@ namespace qtwebapp { @see FileLogger for a description of the two underlying loggers. */ -class DECLSPEC DualFileLogger : public Logger { +class LOGGING_API DualFileLogger : public Logger { Q_OBJECT Q_DISABLE_COPY(DualFileLogger) public: diff --git a/logging/filelogger.h b/logging/filelogger.h index 7e23a303d..62eb04cc0 100644 --- a/logging/filelogger.h +++ b/logging/filelogger.h @@ -15,6 +15,8 @@ #include "logger.h" #include "fileloggersettings.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -49,7 +51,7 @@ namespace qtwebapp { @see Logger for a descrition of the buffer. */ -class DECLSPEC FileLogger : public Logger { +class LOGGING_API FileLogger : public Logger { Q_OBJECT Q_DISABLE_COPY(FileLogger) public: diff --git a/logging/logger.h b/logging/logger.h index 17353333a..ba7de727b 100644 --- a/logging/logger.h +++ b/logging/logger.h @@ -15,6 +15,8 @@ #include "logglobal.h" #include "logmessage.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -47,7 +49,7 @@ namespace qtwebapp { because logging to the console is less useful. */ -class DECLSPEC Logger : public QObject { +class LOGGING_API Logger : public QObject { Q_OBJECT Q_DISABLE_COPY(Logger) public: diff --git a/logging/loggerwithfile.h b/logging/loggerwithfile.h index 526e28df7..defa586cd 100644 --- a/logging/loggerwithfile.h +++ b/logging/loggerwithfile.h @@ -12,6 +12,8 @@ #include "logger.h" #include "filelogger.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -20,7 +22,7 @@ namespace qtwebapp { @see Logger for a description of the console loger. */ -class DECLSPEC LoggerWithFile : public Logger { +class LOGGING_API LoggerWithFile : public Logger { Q_OBJECT Q_DISABLE_COPY(LoggerWithFile) diff --git a/logging/logmessage.h b/logging/logmessage.h index c84c83b4d..3552f6be0 100644 --- a/logging/logmessage.h +++ b/logging/logmessage.h @@ -11,6 +11,8 @@ #include #include "logglobal.h" +#include "util/export.h" + namespace qtwebapp { /** @@ -33,7 +35,7 @@ namespace qtwebapp { - {line} Line number where the message was generated */ -class DECLSPEC LogMessage +class LOGGING_API LogMessage { Q_DISABLE_COPY(LogMessage) public: diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 38e1502f2..0ddab281d 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -92,6 +92,7 @@ set(qrtplib_SOURCES include_directories( . + ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/qrtplib/rtcpapppacket.h b/qrtplib/rtcpapppacket.h index ab4839c18..d8f36efa0 100644 --- a/qrtplib/rtcpapppacket.h +++ b/qrtplib/rtcpapppacket.h @@ -43,13 +43,15 @@ #include "rtpstructs.h" #include "rtpendian.h" +#include "util/export.h" + namespace qrtplib { class RTCPCompoundPacket; /** Describes an RTCP APP packet. */ -class RTCPAPPPacket: public RTCPPacket +class QRTPLIB_API RTCPAPPPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtcpbyepacket.h b/qrtplib/rtcpbyepacket.h index 993d40df4..f6f9493e0 100644 --- a/qrtplib/rtcpbyepacket.h +++ b/qrtplib/rtcpbyepacket.h @@ -43,13 +43,15 @@ #include "rtpstructs.h" #include "rtpendian.h" +#include "util/export.h" + namespace qrtplib { class RTCPCompoundPacket; /** Describes an RTCP BYE packet. */ -class RTCPBYEPacket: public RTCPPacket +class QRTPLIB_API RTCPBYEPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h index e84b6aa70..446504266 100644 --- a/qrtplib/rtcpcompoundpacket.h +++ b/qrtplib/rtcpcompoundpacket.h @@ -43,6 +43,8 @@ #include "rtpendian.h" #include +#include "util/export.h" + namespace qrtplib { @@ -50,7 +52,7 @@ class RTPRawPacket; class RTCPPacket; /** Represents an RTCP compound packet. */ -class RTCPCompoundPacket +class QRTPLIB_API RTCPCompoundPacket { public: /** Creates an RTCPCompoundPacket instance from the data in \c rawpack, installing a memory manager if specified. */ diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index 4a1fccfd9..834599ad5 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -46,6 +46,8 @@ #include "rtpendian.h" #include +#include "util/export.h" + namespace qrtplib { @@ -55,7 +57,7 @@ namespace qrtplib * been built successfully. The member functions described below return \c ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT * if the action would cause the maximum allowed size to be exceeded. */ -class RTCPCompoundPacketBuilder: public RTCPCompoundPacket +class QRTPLIB_API RTCPCompoundPacketBuilder: public RTCPCompoundPacket { public: /** Constructs an RTCPCompoundPacketBuilder instance, optionally installing a memory manager. */ diff --git a/qrtplib/rtcppacketbuilder.h b/qrtplib/rtcppacketbuilder.h index cfd612fc2..c19f5a725 100644 --- a/qrtplib/rtcppacketbuilder.h +++ b/qrtplib/rtcppacketbuilder.h @@ -44,6 +44,8 @@ #include "rtcpsdesinfo.h" #include "rtptimeutilities.h" +#include "util/export.h" + namespace qrtplib { @@ -59,7 +61,7 @@ class RTCPCompoundPacketBuilder; * an RTPSources instance to automatically generate the next compound packet which should be sent. It also * provides functions to determine when SDES items other than the CNAME item should be sent. */ -class RTCPPacketBuilder +class QRTPLIB_API RTCPPacketBuilder { public: /** Creates an RTCPPacketBuilder instance. diff --git a/qrtplib/rtcprrpacket.h b/qrtplib/rtcprrpacket.h index 66d158bb1..2ca047ffb 100644 --- a/qrtplib/rtcprrpacket.h +++ b/qrtplib/rtcprrpacket.h @@ -43,13 +43,15 @@ #include "rtpstructs.h" #include "rtpendian.h" +#include "util/export.h" + namespace qrtplib { class RTCPCompoundPacket; /** Describes an RTCP receiver report packet. */ -class RTCPRRPacket: public RTCPPacket +class QRTPLIB_API RTCPRRPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtcpscheduler.h b/qrtplib/rtcpscheduler.h index f7bc84a47..a9272ed34 100644 --- a/qrtplib/rtcpscheduler.h +++ b/qrtplib/rtcpscheduler.h @@ -43,6 +43,8 @@ #include "rtprandom.h" #include +#include "util/export.h" + namespace qrtplib { @@ -51,7 +53,7 @@ class RTPPacket; class RTPSources; /** Describes parameters used by the RTCPScheduler class. */ -class RTCPSchedulerParams +class QRTPLIB_API RTCPSchedulerParams { public: RTCPSchedulerParams(); diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h index 34e169cc0..65cfaa0b7 100644 --- a/qrtplib/rtcpsdesinfo.h +++ b/qrtplib/rtcpsdesinfo.h @@ -45,11 +45,13 @@ #include #include +#include "util/export.h" + namespace qrtplib { /** The class RTCPSDESInfo is a container for RTCP SDES information. */ -class RTCPSDESInfo +class QRTPLIB_API RTCPSDESInfo { public: /** Constructs an instance, optionally installing a memory manager. */ diff --git a/qrtplib/rtcpsdespacket.h b/qrtplib/rtcpsdespacket.h index 72cdb5b3c..eaa7c6744 100644 --- a/qrtplib/rtcpsdespacket.h +++ b/qrtplib/rtcpsdespacket.h @@ -44,13 +44,15 @@ #include "rtpdefines.h" #include "rtpendian.h" +#include "util/export.h" + namespace qrtplib { class RTCPCompoundPacket; /** Describes an RTCP source description packet. */ -class RTCPSDESPacket: public RTCPPacket +class QRTPLIB_API RTCPSDESPacket: public RTCPPacket { public: /** Identifies the type of an SDES item. */ diff --git a/qrtplib/rtcpsrpacket.h b/qrtplib/rtcpsrpacket.h index cb6c6e29d..20131fc5e 100644 --- a/qrtplib/rtcpsrpacket.h +++ b/qrtplib/rtcpsrpacket.h @@ -44,13 +44,15 @@ #include "rtpstructs.h" #include "rtpendian.h" +#include "util/export.h" + namespace qrtplib { class RTCPCompoundPacket; /** Describes an RTCP sender report packet. */ -class RTCPSRPacket: public RTCPPacket +class QRTPLIB_API RTCPSRPacket: public RTCPPacket { public: /** Creates an instance based on the data in \c data with length \c datalen. diff --git a/qrtplib/rtpabortdescriptors.h b/qrtplib/rtpabortdescriptors.h index 2ccdc1919..cab7f68b6 100644 --- a/qrtplib/rtpabortdescriptors.h +++ b/qrtplib/rtpabortdescriptors.h @@ -41,6 +41,8 @@ #include "rtpconfig.h" #include "rtpsocketutil.h" +#include "util/export.h" + namespace qrtplib { @@ -61,7 +63,7 @@ namespace qrtplib * uses a single poll thread for several RTPSession and RTPTransmitter instances. * This idea is further illustrated in `example8.cpp`. */ -class RTPAbortDescriptors +class QRTPLIB_API RTPAbortDescriptors { public: RTPAbortDescriptors(); diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h index 68f07bd80..68324b3fe 100644 --- a/qrtplib/rtpaddress.h +++ b/qrtplib/rtpaddress.h @@ -43,11 +43,13 @@ #include #include +#include "util/export.h" + namespace qrtplib { /** This class is an abstract class which is used to specify destinations, multicast groups etc. */ -class RTPAddress +class QRTPLIB_API RTPAddress { public: /** Returns the type of address the actual implementation represents. */ diff --git a/qrtplib/rtpcollisionlist.h b/qrtplib/rtpcollisionlist.h index 60c63a051..e8c5e2616 100644 --- a/qrtplib/rtpcollisionlist.h +++ b/qrtplib/rtpcollisionlist.h @@ -43,13 +43,15 @@ #include "rtptimeutilities.h" #include +#include "util/export.h" + namespace qrtplib { class RTPAddress; /** This class represents a list of addresses from which SSRC collisions were detected. */ -class RTPCollisionList +class QRTPLIB_API RTPCollisionList { public: /** Constructs an instance, optionally installing a memory manager. */ diff --git a/qrtplib/rtpexternaltransmitter.h b/qrtplib/rtpexternaltransmitter.h index 9cfc82fdc..d937d68e4 100644 --- a/qrtplib/rtpexternaltransmitter.h +++ b/qrtplib/rtpexternaltransmitter.h @@ -43,6 +43,8 @@ #include "rtpabortdescriptors.h" #include +#include "util/export.h" + namespace qrtplib { @@ -56,7 +58,7 @@ class RTPExternalTransmitter; * so that the transmitter will call the \c SendRTP, \c SendRTCP and \c ComesFromThisSender * methods of this instance when needed. */ -class RTPExternalSender +class QRTPLIB_API RTPExternalSender { public: RTPExternalSender() diff --git a/qrtplib/rtpinternalsourcedata.h b/qrtplib/rtpinternalsourcedata.h index 120e903d6..f36f8eba9 100644 --- a/qrtplib/rtpinternalsourcedata.h +++ b/qrtplib/rtpinternalsourcedata.h @@ -44,10 +44,12 @@ #include "rtptimeutilities.h" #include "rtpsources.h" +#include "util/export.h" + namespace qrtplib { -class RTPInternalSourceData: public RTPSourceData +class QRTPLIB_API RTPInternalSourceData: public RTPSourceData { public: RTPInternalSourceData(uint32_t ssrc); diff --git a/qrtplib/rtpipv4address.h b/qrtplib/rtpipv4address.h index 5d0b86639..0253d9be3 100644 --- a/qrtplib/rtpipv4address.h +++ b/qrtplib/rtpipv4address.h @@ -42,6 +42,8 @@ #include "rtpaddress.h" #include "rtptypes.h" +#include "util/export.h" + namespace qrtplib { @@ -51,7 +53,7 @@ namespace qrtplib * number is ignored. When an instance is used in one of the accept or ignore functions of the * transmitter, a zero port number represents all ports for the specified IP address. */ -class RTPIPv4Address: public RTPAddress +class QRTPLIB_API RTPIPv4Address: public RTPAddress { public: /** Creates an instance with IP address \c ip and port number \c port (both diff --git a/qrtplib/rtpipv4destination.h b/qrtplib/rtpipv4destination.h index e4a5ed073..32491bdcc 100644 --- a/qrtplib/rtpipv4destination.h +++ b/qrtplib/rtpipv4destination.h @@ -49,10 +49,12 @@ #include #include +#include "util/export.h" + namespace qrtplib { -class RTPIPv4Destination +class QRTPLIB_API RTPIPv4Destination { public: RTPIPv4Destination() diff --git a/qrtplib/rtplibraryversion.h b/qrtplib/rtplibraryversion.h index eb4e2ad39..8f46255f6 100644 --- a/qrtplib/rtplibraryversion.h +++ b/qrtplib/rtplibraryversion.h @@ -42,13 +42,15 @@ #include #include +#include "util/export.h" + namespace qrtplib { /** * Used to provide information about the version of the library. */ -class RTPLibraryVersion +class QRTPLIB_API RTPLibraryVersion { public: /** Returns an instance of RTPLibraryVersion describing the version of the library. */ diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib/rtppacketbuilder.h index 7bda2c4a9..6fe1cd689 100644 --- a/qrtplib/rtppacketbuilder.h +++ b/qrtplib/rtppacketbuilder.h @@ -45,6 +45,8 @@ #include "rtptimeutilities.h" #include "rtptypes.h" +#include "util/export.h" + namespace qrtplib { @@ -53,7 +55,7 @@ class RTPSources; /** This class can be used to build RTP packets and is a bit more high-level than the RTPPacket * class: it generates an SSRC identifier, keeps track of timestamp and sequence number etc. */ -class RTPPacketBuilder +class QRTPLIB_API RTPPacketBuilder { public: /** Constructs an instance which will use \c rtprand for generating random numbers diff --git a/qrtplib/rtprandom.h b/qrtplib/rtprandom.h index e7239f9a2..747f03cd3 100644 --- a/qrtplib/rtprandom.h +++ b/qrtplib/rtprandom.h @@ -42,13 +42,15 @@ #include "rtptypes.h" #include +#include "util/export.h" + #define RTPRANDOM_2POWMIN63 1.08420217248550443400745280086994171142578125e-19 namespace qrtplib { /** Interface for generating random numbers. */ -class RTPRandom +class QRTPLIB_API RTPRandom { public: RTPRandom() diff --git a/qrtplib/rtprandomrand48.h b/qrtplib/rtprandomrand48.h index 7974f07fe..6795c86b3 100644 --- a/qrtplib/rtprandomrand48.h +++ b/qrtplib/rtprandomrand48.h @@ -42,11 +42,13 @@ #include "rtprandom.h" #include +#include "util/export.h" + namespace qrtplib { /** A random number generator using the algorithm of the rand48 set of functions. */ -class RTPRandomRand48: public RTPRandom +class QRTPLIB_API RTPRandomRand48: public RTPRandom { public: RTPRandomRand48(); diff --git a/qrtplib/rtprandomrands.h b/qrtplib/rtprandomrands.h index d8c47e504..58f692a6c 100644 --- a/qrtplib/rtprandomrands.h +++ b/qrtplib/rtprandomrands.h @@ -41,13 +41,15 @@ #include "rtpconfig.h" #include "rtprandom.h" +#include "util/export.h" + namespace qrtplib { /** A random number generator which tries to use the \c rand_s function on the * Win32 platform. */ -class RTPRandomRandS: public RTPRandom +class QRTPLIB_API RTPRandomRandS: public RTPRandom { public: RTPRandomRandS(); diff --git a/qrtplib/rtprandomurandom.h b/qrtplib/rtprandomurandom.h index 0754d322c..4d5cf339c 100644 --- a/qrtplib/rtprandomurandom.h +++ b/qrtplib/rtprandomurandom.h @@ -42,11 +42,13 @@ #include "rtprandom.h" #include +#include "util/export.h" + namespace qrtplib { /** A random number generator which uses bytes delivered by the /dev/urandom device. */ -class RTPRandomURandom: public RTPRandom +class QRTPLIB_API RTPRandomURandom: public RTPRandom { public: RTPRandomURandom(); diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index 960c6aa5a..cf98baf65 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -50,6 +50,8 @@ #include "rtcpcompoundpacketbuilder.h" #include +#include "util/export.h" + namespace qrtplib { @@ -72,7 +74,7 @@ class RTCPAPPPacket; * \note The RTPSession class is not meant to be thread safe. The user should use some kind of locking * mechanism to prevent different threads from using the same RTPSession instance. */ -class RTPSession +class QRTPLIB_API RTPSession { public: /** Constructs an RTPSession instance, optionally using a specific instance of a random diff --git a/qrtplib/rtpsessionparams.h b/qrtplib/rtpsessionparams.h index b75517c42..9cf6cf790 100644 --- a/qrtplib/rtpsessionparams.h +++ b/qrtplib/rtpsessionparams.h @@ -44,6 +44,8 @@ #include "rtptimeutilities.h" #include "rtpsources.h" +#include "util/export.h" + namespace qrtplib { @@ -51,7 +53,7 @@ namespace qrtplib * Describes the parameters for to be used by an RTPSession instance. Note that the own timestamp * unit must be set to a valid number, otherwise the session can't be created. */ -class RTPSessionParams +class QRTPLIB_API RTPSessionParams { public: RTPSessionParams(); diff --git a/qrtplib/rtpsessionsources.h b/qrtplib/rtpsessionsources.h index 1649c0a90..92d6b727d 100644 --- a/qrtplib/rtpsessionsources.h +++ b/qrtplib/rtpsessionsources.h @@ -41,12 +41,14 @@ #include "rtpconfig.h" #include "rtpsources.h" +#include "util/export.h" + namespace qrtplib { class RTPSession; -class RTPSessionSources: public RTPSources +class QRTPLIB_API RTPSessionSources: public RTPSources { public: RTPSessionSources(RTPSession &sess) : diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h index a2d83cf13..8958cb393 100644 --- a/qrtplib/rtpsourcedata.h +++ b/qrtplib/rtpsourcedata.h @@ -46,12 +46,14 @@ #include "rtpsources.h" #include +#include "util/export.h" + namespace qrtplib { class RTPAddress; -class RTCPSenderReportInfo +class QRTPLIB_API RTCPSenderReportInfo { public: RTCPSenderReportInfo() : diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h index f549e71af..7ddff9bca 100644 --- a/qrtplib/rtpsources.h +++ b/qrtplib/rtpsources.h @@ -43,12 +43,14 @@ #include "rtcpsdespacket.h" #include "rtptypes.h" +#include "util/export.h" + #define RTPSOURCES_HASHSIZE 8317 namespace qrtplib { -class RTPSources_GetHashIndex +class QRTPLIB_API RTPSources_GetHashIndex { public: static int GetIndex(const uint32_t &ssrc) @@ -73,7 +75,7 @@ class RTPSourceData; * is used to identify packets from our own session. The class also provides some overridable functions * which can be used to catch certain events (new SSRC, SSRC collision, ...). */ -class RTPSources +class QRTPLIB_API RTPSources { public: /** Type of probation to use for new sources. */ diff --git a/qrtplib/rtptcpaddress.h b/qrtplib/rtptcpaddress.h index 83b33acd1..ba3a50bd1 100644 --- a/qrtplib/rtptcpaddress.h +++ b/qrtplib/rtptcpaddress.h @@ -43,6 +43,8 @@ #include "rtptypes.h" #include "rtpsocketutil.h" +#include "util/export.h" + namespace qrtplib { /** Represents a TCP 'address' and port. @@ -50,7 +52,7 @@ namespace qrtplib * should be used to send/receive data, and to know on which socket incoming data * was received. */ -class RTPTCPAddress: public RTPAddress +class QRTPLIB_API RTPTCPAddress: public RTPAddress { public: /** Creates an instance with which you can use a specific socket diff --git a/qrtplib/rtptcptransmitter.h b/qrtplib/rtptcptransmitter.h index 205e3c019..dc964c304 100644 --- a/qrtplib/rtptcptransmitter.h +++ b/qrtplib/rtptcptransmitter.h @@ -46,11 +46,13 @@ #include #include +#include "util/export.h" + namespace qrtplib { /** Parameters for the TCP transmitter. */ -class RTPTCPTransmissionParams: public RTPTransmissionParams +class QRTPLIB_API RTPTCPTransmissionParams: public RTPTransmissionParams { public: RTPTCPTransmissionParams(); diff --git a/qrtplib/rtptimeutilities.h b/qrtplib/rtptimeutilities.h index 0e9a4d28a..be885a0c0 100644 --- a/qrtplib/rtptimeutilities.h +++ b/qrtplib/rtptimeutilities.h @@ -46,6 +46,8 @@ #include #endif // RTP_HAVE_QUERYPERFORMANCECOUNTER +#include "util/export.h" + #define RTP_NTPTIMEOFFSET 2208988800UL #ifdef RTP_HAVE_VSUINT64SUFFIX @@ -63,7 +65,7 @@ namespace qrtplib * This is a simple wrapper for the most significant word (MSW) and least * significant word (LSW) of an NTP timestamp. */ -class RTPNTPTime +class QRTPLIB_API RTPNTPTime { public: /** This constructor creates and instance with MSW \c m and LSW \c l. */ @@ -92,7 +94,7 @@ private: * This class is used to specify wallclock time, delay intervals etc. * It stores a number of seconds and a number of microseconds. */ -class RTPTime +class QRTPLIB_API RTPTime { public: /** Returns an RTPTime instance representing the current wallclock time. @@ -392,7 +394,7 @@ inline bool RTPTime::operator>=(const RTPTime &t) const return m_t >= t.m_t; } -class RTPTimeInitializerObject +class QRTPLIB_API RTPTimeInitializerObject { public: RTPTimeInitializerObject(); @@ -404,7 +406,7 @@ private: int dummy; }; -extern RTPTimeInitializerObject timeinit; +extern QRTPLIB_API RTPTimeInitializerObject timeinit; } // end namespace diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h index 3cdf8401d..ae298de9d 100644 --- a/qrtplib/rtpudptransmitter.h +++ b/qrtplib/rtpudptransmitter.h @@ -38,6 +38,8 @@ #include #include +#include "util/export.h" + #define RTPUDPV4TRANS_HASHSIZE 8317 #define RTPUDPV4TRANS_DEFAULTPORTBASE 5000 #define RTPUDPV4TRANS_RTPRECEIVEBUFFER 32768 @@ -51,7 +53,7 @@ namespace qrtplib { /** Parameters for the UDP transmitter. */ -class RTPUDPTransmissionParams: public RTPTransmissionParams +class QRTPLIB_API RTPUDPTransmissionParams: public RTPTransmissionParams { public: RTPUDPTransmissionParams(); @@ -253,7 +255,7 @@ inline RTPUDPTransmissionParams::RTPUDPTransmissionParams() : } /** Additional information about the UDP over IPv4 transmitter. */ -class RTPUDPTransmissionInfo: public RTPTransmissionInfo +class QRTPLIB_API RTPUDPTransmissionInfo: public RTPTransmissionInfo { public: RTPUDPTransmissionInfo(const std::list& iplist, QUdpSocket *rtpsock, QUdpSocket *rtcpsock, uint16_t rtpport, uint16_t rtcpport) : @@ -313,7 +315,7 @@ private: * are described by the class RTPUDPTransmissionParams. The GetTransmissionInfo member function * returns an instance of type RTPUDPTransmissionInfo. */ -class RTPUDPTransmitter: public RTPTransmitter +class QRTPLIB_API RTPUDPTransmitter: public RTPTransmitter { public: RTPUDPTransmitter(); diff --git a/qrtplib/rtpudpv4transmitter.h b/qrtplib/rtpudpv4transmitter.h index 109a4c093..aa9abac8b 100644 --- a/qrtplib/rtpudpv4transmitter.h +++ b/qrtplib/rtpudpv4transmitter.h @@ -47,6 +47,8 @@ #include "rtpabortdescriptors.h" #include +#include "util/export.h" + #define RTPUDPV4TRANS_HASHSIZE 8317 #define RTPUDPV4TRANS_DEFAULTPORTBASE 5000 @@ -59,7 +61,7 @@ namespace qrtplib { /** Parameters for the UDP over IPv4 transmitter. */ -class RTPUDPv4TransmissionParams: public RTPTransmissionParams +class QRTPLIB_API RTPUDPv4TransmissionParams: public RTPTransmissionParams { public: RTPUDPv4TransmissionParams(); @@ -294,7 +296,7 @@ inline RTPUDPv4TransmissionParams::RTPUDPv4TransmissionParams() : } /** Additional information about the UDP over IPv4 transmitter. */ -class RTPUDPv4TransmissionInfo: public RTPTransmissionInfo +class QRTPLIB_API RTPUDPv4TransmissionInfo: public RTPTransmissionInfo { public: RTPUDPv4TransmissionInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : @@ -373,7 +375,7 @@ public: * argument require an argument of RTPIPv4Address. The GetTransmissionInfo member function * returns an instance of type RTPUDPv4TransmissionInfo. */ -class RTPUDPv4Transmitter: public RTPTransmitter +class QRTPLIB_API RTPUDPv4Transmitter: public RTPTransmitter { public: RTPUDPv4Transmitter(); diff --git a/qrtplib/rtpudpv4transmitternobind.h b/qrtplib/rtpudpv4transmitternobind.h index 2ab5c238e..5cef0a739 100644 --- a/qrtplib/rtpudpv4transmitternobind.h +++ b/qrtplib/rtpudpv4transmitternobind.h @@ -47,6 +47,8 @@ #include "rtpabortdescriptors.h" #include +#include "util/export.h" + #define RTPUDPV4TRANSNOBIND_HASHSIZE 8317 #define RTPUDPV4TRANSNOBIND_DEFAULTPORTBASE 5000 @@ -59,7 +61,7 @@ namespace qrtplib { /** Parameters for the UDP over IPv4 transmitter that does not automatically bind sockets */ -class RTPUDPv4TransmissionNoBindParams: public RTPTransmissionParams +class QRTPLIB_API RTPUDPv4TransmissionNoBindParams: public RTPTransmissionParams { public: RTPUDPv4TransmissionNoBindParams(); @@ -294,7 +296,7 @@ inline RTPUDPv4TransmissionNoBindParams::RTPUDPv4TransmissionNoBindParams() : } /** Additional information about the UDP over IPv4 transmitter that does not automatically bind sockets. */ -class RTPUDPv4TransmissionNoBindInfo: public RTPTransmissionInfo +class QRTPLIB_API RTPUDPv4TransmissionNoBindInfo: public RTPTransmissionInfo { public: RTPUDPv4TransmissionNoBindInfo(const QHostAddress& ip, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : @@ -375,7 +377,7 @@ public: * This flavor of a RTPUDPv4Transmitter class does not automatically bind sockets. Use the * BindSockets method to do so. */ -class RTPUDPv4TransmitterNoBind: public RTPTransmitter +class QRTPLIB_API RTPUDPv4TransmitterNoBind: public RTPTransmitter { public: RTPUDPv4TransmitterNoBind(); diff --git a/sdrbase/audio/audiodeviceinfo.h b/sdrbase/audio/audiodeviceinfo.h index 38bce5596..02938b674 100644 --- a/sdrbase/audio/audiodeviceinfo.h +++ b/sdrbase/audio/audiodeviceinfo.h @@ -24,7 +24,7 @@ #include "util/export.h" -class SDRANGEL_API AudioDeviceInfo { +class SDRBASE_API AudioDeviceInfo { public: AudioDeviceInfo(); diff --git a/sdrbase/audio/audiofifo.h b/sdrbase/audio/audiofifo.h index 71859ef53..0fc452299 100644 --- a/sdrbase/audio/audiofifo.h +++ b/sdrbase/audio/audiofifo.h @@ -26,7 +26,7 @@ #include "util/export.h" #include "util/udpsink.h" -class SDRANGEL_API AudioFifo : public QObject { +class SDRBASE_API AudioFifo : public QObject { Q_OBJECT public: AudioFifo(); diff --git a/sdrbase/audio/audioinput.h b/sdrbase/audio/audioinput.h index 645143a0f..6e3cc56b5 100644 --- a/sdrbase/audio/audioinput.h +++ b/sdrbase/audio/audioinput.h @@ -29,7 +29,7 @@ class AudioFifo; class AudioOutputPipe; -class SDRANGEL_API AudioInput : public QIODevice { +class SDRBASE_API AudioInput : public QIODevice { public: AudioInput(); virtual ~AudioInput(); diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index 08dc27618..750aaf404 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -25,7 +25,7 @@ template class UDPSink; class RTPSink; -class SDRANGEL_API AudioNetSink { +class SDRBASE_API AudioNetSink { public: typedef enum { diff --git a/sdrbase/audio/audiooutput.h b/sdrbase/audio/audiooutput.h index a6d5b6ced..4fd1dade4 100644 --- a/sdrbase/audio/audiooutput.h +++ b/sdrbase/audio/audiooutput.h @@ -29,7 +29,7 @@ class QAudioOutput; class AudioFifo; class AudioOutputPipe; -class SDRANGEL_API AudioOutput : QIODevice { +class SDRBASE_API AudioOutput : QIODevice { public: AudioOutput(); virtual ~AudioOutput(); diff --git a/sdrbase/channel/channelsinkapi.h b/sdrbase/channel/channelsinkapi.h index dba578bf9..a2c2f9b1b 100644 --- a/sdrbase/channel/channelsinkapi.h +++ b/sdrbase/channel/channelsinkapi.h @@ -30,7 +30,7 @@ namespace SWGSDRangel class SWGChannelSettings; } -class SDRANGEL_API ChannelSinkAPI { +class SDRBASE_API ChannelSinkAPI { public: ChannelSinkAPI(const QString& name); virtual ~ChannelSinkAPI() {} diff --git a/sdrbase/channel/channelsourceapi.h b/sdrbase/channel/channelsourceapi.h index 192ee239f..18734b8f1 100644 --- a/sdrbase/channel/channelsourceapi.h +++ b/sdrbase/channel/channelsourceapi.h @@ -29,7 +29,7 @@ namespace SWGSDRangel class SWGChannelSettings; } -class SDRANGEL_API ChannelSourceAPI { +class SDRBASE_API ChannelSourceAPI { public: ChannelSourceAPI(const QString& name); virtual ~ChannelSourceAPI() {} diff --git a/sdrbase/commands/command.h b/sdrbase/commands/command.h index 172ac0d31..491f89df6 100644 --- a/sdrbase/commands/command.h +++ b/sdrbase/commands/command.h @@ -25,7 +25,9 @@ #include #include -class Command : public QObject +#include "util/export.h" + +class SDRBASE_API Command : public QObject { Q_OBJECT public: diff --git a/sdrbase/device/deviceenumerator.h b/sdrbase/device/deviceenumerator.h index 92938d5ac..b599dcb61 100644 --- a/sdrbase/device/deviceenumerator.h +++ b/sdrbase/device/deviceenumerator.h @@ -20,10 +20,11 @@ #include #include "plugin/plugininterface.h" +#include "util/export.h" class PluginManager; -class DeviceEnumerator +class SDRBASE_API DeviceEnumerator { public: DeviceEnumerator(); diff --git a/sdrbase/device/devicesinkapi.h b/sdrbase/device/devicesinkapi.h index 7566aeef7..19434b824 100644 --- a/sdrbase/device/devicesinkapi.h +++ b/sdrbase/device/devicesinkapi.h @@ -33,7 +33,7 @@ class Preset; class DeviceSourceAPI; class ChannelSourceAPI; -class SDRANGEL_API DeviceSinkAPI : public QObject { +class SDRBASE_API DeviceSinkAPI : public QObject { Q_OBJECT public: diff --git a/sdrbase/device/devicesourceapi.h b/sdrbase/device/devicesourceapi.h index 828506a9e..f8dd69ea3 100644 --- a/sdrbase/device/devicesourceapi.h +++ b/sdrbase/device/devicesourceapi.h @@ -35,7 +35,7 @@ class Preset; class DeviceSinkAPI; class ChannelSinkAPI; -class SDRANGEL_API DeviceSourceAPI : public QObject { +class SDRBASE_API DeviceSourceAPI : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/afsquelch.h b/sdrbase/dsp/afsquelch.h index d3e66447c..72bca12c4 100644 --- a/sdrbase/dsp/afsquelch.h +++ b/sdrbase/dsp/afsquelch.h @@ -19,11 +19,12 @@ #include "dsp/dsptypes.h" #include "dsp/movingaverage.h" +#include "util/export.h" /** AFSquelch: AF squelch class based on the Modified Goertzel * algorithm. */ -class AFSquelch { +class SDRBASE_API AFSquelch { public: // allows user defined tone pair AFSquelch(unsigned int nbTones, diff --git a/sdrbase/dsp/agc.h b/sdrbase/dsp/agc.h index 1c107bd88..2f7a3d71d 100644 --- a/sdrbase/dsp/agc.h +++ b/sdrbase/dsp/agc.h @@ -10,8 +10,9 @@ #include "movingaverage.h" #include "util/movingaverage.h" +#include "util/export.h" -class AGC +class SDRBASE_API AGC { public: AGC(int historySize, double R); @@ -32,7 +33,7 @@ protected: }; -class MagAGC : public AGC +class SDRBASE_API MagAGC : public AGC { public: MagAGC(int historySize, double R, double threshold); diff --git a/sdrbase/dsp/basebandsamplesink.h b/sdrbase/dsp/basebandsamplesink.h index 9897d280f..462d0ec04 100644 --- a/sdrbase/dsp/basebandsamplesink.h +++ b/sdrbase/dsp/basebandsamplesink.h @@ -25,7 +25,7 @@ class Message; -class SDRANGEL_API BasebandSampleSink : public QObject { +class SDRBASE_API BasebandSampleSink : public QObject { Q_OBJECT public: BasebandSampleSink(); diff --git a/sdrbase/dsp/basebandsamplesource.h b/sdrbase/dsp/basebandsamplesource.h index 4daac5840..a5b2e595c 100644 --- a/sdrbase/dsp/basebandsamplesource.h +++ b/sdrbase/dsp/basebandsamplesource.h @@ -26,7 +26,7 @@ class Message; -class SDRANGEL_API BasebandSampleSource : public QObject { +class SDRBASE_API BasebandSampleSource : public QObject { Q_OBJECT public: BasebandSampleSource(); diff --git a/sdrbase/dsp/channelmarker.h b/sdrbase/dsp/channelmarker.h index 30befb315..87bcb45e1 100644 --- a/sdrbase/dsp/channelmarker.h +++ b/sdrbase/dsp/channelmarker.h @@ -8,7 +8,7 @@ #include "settings/serializable.h" #include "util/export.h" -class SDRANGEL_API ChannelMarker : public QObject, public Serializable { +class SDRBASE_API ChannelMarker : public QObject, public Serializable { Q_OBJECT public: diff --git a/sdrbase/dsp/ctcssdetector.h b/sdrbase/dsp/ctcssdetector.h index e3799c07c..6f2fada0e 100644 --- a/sdrbase/dsp/ctcssdetector.h +++ b/sdrbase/dsp/ctcssdetector.h @@ -10,12 +10,13 @@ #define INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ #include "dsp/dsptypes.h" +#include "util/export.h" /** CTCSSDetector: Continuous Tone Coded Squelch System * tone detector class based on the Modified Goertzel * algorithm. */ -class CTCSSDetector { +class SDRBASE_API CTCSSDetector { public: // Constructors and Destructor CTCSSDetector(); diff --git a/sdrbase/dsp/cwkeyer.h b/sdrbase/dsp/cwkeyer.h index 445da213b..35e2399ab 100644 --- a/sdrbase/dsp/cwkeyer.h +++ b/sdrbase/dsp/cwkeyer.h @@ -28,7 +28,7 @@ /** * Ancillary class to smooth out CW transitions with a sine shape */ -class CWSmoother +class SDRBASE_API CWSmoother { public: CWSmoother(); @@ -46,7 +46,7 @@ private: float *m_fadeOutSamples; }; -class SDRANGEL_API CWKeyer : public QObject { +class SDRBASE_API CWKeyer : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/cwkeyersettings.h b/sdrbase/dsp/cwkeyersettings.h index ec3c5c489..d4a4d9d4a 100644 --- a/sdrbase/dsp/cwkeyersettings.h +++ b/sdrbase/dsp/cwkeyersettings.h @@ -21,7 +21,9 @@ #include #include -class CWKeyerSettings +#include "util/export.h" + +class SDRBASE_API CWKeyerSettings { public: typedef enum diff --git a/sdrbase/dsp/decimatorsf.h b/sdrbase/dsp/decimatorsf.h index 3c631ee1e..751f39815 100644 --- a/sdrbase/dsp/decimatorsf.h +++ b/sdrbase/dsp/decimatorsf.h @@ -18,10 +18,11 @@ #define SDRBASE_DSP_DECIMATORSF_H_ #include "dsp/inthalfbandfilterdbf.h" +#include "util/export.h" #define DECIMATORSF_HB_FILTER_ORDER 64 -class DecimatorsF +class SDRBASE_API DecimatorsF { public: void decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); diff --git a/sdrbase/dsp/devicesamplesink.h b/sdrbase/dsp/devicesamplesink.h index e49b441f3..5171f06ef 100644 --- a/sdrbase/dsp/devicesamplesink.h +++ b/sdrbase/dsp/devicesamplesink.h @@ -31,7 +31,7 @@ namespace SWGSDRangel class SWGDeviceState; } -class SDRANGEL_API DeviceSampleSink : public QObject { +class SDRBASE_API DeviceSampleSink : public QObject { Q_OBJECT public: DeviceSampleSink(); diff --git a/sdrbase/dsp/devicesamplesource.h b/sdrbase/dsp/devicesamplesource.h index dbc570b20..909c74a4b 100644 --- a/sdrbase/dsp/devicesamplesource.h +++ b/sdrbase/dsp/devicesamplesource.h @@ -32,7 +32,7 @@ namespace SWGSDRangel class SWGDeviceState; } -class SDRANGEL_API DeviceSampleSource : public QObject { +class SDRBASE_API DeviceSampleSource : public QObject { Q_OBJECT public: DeviceSampleSource(); diff --git a/sdrbase/dsp/downchannelizer.h b/sdrbase/dsp/downchannelizer.h index f95678c08..d41b94677 100644 --- a/sdrbase/dsp/downchannelizer.h +++ b/sdrbase/dsp/downchannelizer.h @@ -37,7 +37,7 @@ class MessageQueue; -class SDRANGEL_API DownChannelizer : public BasebandSampleSink { +class SDRBASE_API DownChannelizer : public BasebandSampleSink { Q_OBJECT public: class SDRANGEL_API MsgChannelizerNotification : public Message { diff --git a/sdrbase/dsp/dspcommands.h b/sdrbase/dsp/dspcommands.h index 7ba24a242..49199098b 100644 --- a/sdrbase/dsp/dspcommands.h +++ b/sdrbase/dsp/dspcommands.h @@ -31,31 +31,31 @@ class BasebandSampleSource; class ThreadedBasebandSampleSource; class AudioFifo; -class SDRANGEL_API DSPAcquisitionInit : public Message { +class SDRBASE_API DSPAcquisitionInit : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPAcquisitionStart : public Message { +class SDRBASE_API DSPAcquisitionStart : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPAcquisitionStop : public Message { +class SDRBASE_API DSPAcquisitionStop : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPGenerationInit : public Message { +class SDRBASE_API DSPGenerationInit : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPGenerationStart : public Message { +class SDRBASE_API DSPGenerationStart : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPGenerationStop : public Message { +class SDRBASE_API DSPGenerationStop : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPGetSourceDeviceDescription : public Message { +class SDRBASE_API DSPGetSourceDeviceDescription : public Message { MESSAGE_CLASS_DECLARATION public: @@ -66,7 +66,7 @@ private: QString m_deviceDescription; }; -class SDRANGEL_API DSPGetSinkDeviceDescription : public Message { +class SDRBASE_API DSPGetSinkDeviceDescription : public Message { MESSAGE_CLASS_DECLARATION public: @@ -77,7 +77,7 @@ private: QString m_deviceDescription; }; -class SDRANGEL_API DSPGetErrorMessage : public Message { +class SDRBASE_API DSPGetErrorMessage : public Message { MESSAGE_CLASS_DECLARATION public: @@ -88,7 +88,7 @@ private: QString m_errorMessage; }; -class SDRANGEL_API DSPSetSource : public Message { +class SDRBASE_API DSPSetSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -100,7 +100,7 @@ private: DeviceSampleSource* m_sampleSource; }; -class SDRANGEL_API DSPSetSink : public Message { +class SDRBASE_API DSPSetSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -112,7 +112,7 @@ private: DeviceSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPAddBasebandSampleSink : public Message { +class SDRBASE_API DSPAddBasebandSampleSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -124,7 +124,7 @@ private: BasebandSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPAddSpectrumSink : public Message { +class SDRBASE_API DSPAddSpectrumSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -136,7 +136,7 @@ private: BasebandSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPAddBasebandSampleSource : public Message { +class SDRBASE_API DSPAddBasebandSampleSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -148,7 +148,7 @@ private: BasebandSampleSource* m_sampleSource; }; -class SDRANGEL_API DSPRemoveBasebandSampleSink : public Message { +class SDRBASE_API DSPRemoveBasebandSampleSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -160,7 +160,7 @@ private: BasebandSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPRemoveSpectrumSink : public Message { +class SDRBASE_API DSPRemoveSpectrumSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -172,7 +172,7 @@ private: BasebandSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPRemoveBasebandSampleSource : public Message { +class SDRBASE_API DSPRemoveBasebandSampleSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -184,7 +184,7 @@ private: BasebandSampleSource* m_sampleSource; }; -class SDRANGEL_API DSPAddThreadedBasebandSampleSink : public Message { +class SDRBASE_API DSPAddThreadedBasebandSampleSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -196,7 +196,7 @@ private: ThreadedBasebandSampleSink* m_threadedSampleSink; }; -class SDRANGEL_API DSPAddThreadedBasebandSampleSource : public Message { +class SDRBASE_API DSPAddThreadedBasebandSampleSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -208,7 +208,7 @@ private: ThreadedBasebandSampleSource* m_threadedSampleSource; }; -class SDRANGEL_API DSPRemoveThreadedBasebandSampleSink : public Message { +class SDRBASE_API DSPRemoveThreadedBasebandSampleSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -220,7 +220,7 @@ private: ThreadedBasebandSampleSink* m_threadedSampleSink; }; -class SDRANGEL_API DSPRemoveThreadedBasebandSampleSource : public Message { +class SDRBASE_API DSPRemoveThreadedBasebandSampleSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -232,7 +232,7 @@ private: ThreadedBasebandSampleSource* m_threadedSampleSource; }; -class SDRANGEL_API DSPAddAudioSink : public Message { +class SDRBASE_API DSPAddAudioSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -244,7 +244,7 @@ private: AudioFifo* m_audioFifo; }; -class SDRANGEL_API DSPRemoveAudioSink : public Message { +class SDRBASE_API DSPRemoveAudioSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -256,7 +256,7 @@ private: AudioFifo* m_audioFifo; }; -class SDRANGEL_API DSPConfigureSpectrumVis : public Message { +class SDRBASE_API DSPConfigureSpectrumVis : public Message { MESSAGE_CLASS_DECLARATION public: @@ -277,7 +277,7 @@ private: FFTWindow::Function m_window; }; -class SDRANGEL_API DSPConfigureCorrection : public Message { +class SDRBASE_API DSPConfigureCorrection : public Message { MESSAGE_CLASS_DECLARATION public: @@ -296,7 +296,7 @@ private: }; -class SDRANGEL_API DSPEngineReport : public Message { +class SDRBASE_API DSPEngineReport : public Message { MESSAGE_CLASS_DECLARATION public: @@ -314,7 +314,7 @@ private: quint64 m_centerFrequency; }; -class SDRANGEL_API DSPConfigureScopeVis : public Message { +class SDRBASE_API DSPConfigureScopeVis : public Message { MESSAGE_CLASS_DECLARATION public: @@ -335,7 +335,7 @@ private: Real m_triggerLevelLow; }; -class SDRANGEL_API DSPSignalNotification : public Message { +class SDRBASE_API DSPSignalNotification : public Message { MESSAGE_CLASS_DECLARATION public: @@ -353,7 +353,7 @@ private: qint64 m_centerFrequency; }; -class SDRANGEL_API DSPConfigureChannelizer : public Message { +class SDRBASE_API DSPConfigureChannelizer : public Message { MESSAGE_CLASS_DECLARATION public: diff --git a/sdrbase/dsp/dspdevicesinkengine.h b/sdrbase/dsp/dspdevicesinkengine.h index db4553dda..f148df672 100644 --- a/sdrbase/dsp/dspdevicesinkengine.h +++ b/sdrbase/dsp/dspdevicesinkengine.h @@ -36,7 +36,7 @@ class BasebandSampleSource; class ThreadedBasebandSampleSource; class BasebandSampleSink; -class SDRANGEL_API DSPDeviceSinkEngine : public QThread { +class SDRBASE_API DSPDeviceSinkEngine : public QThread { Q_OBJECT public: diff --git a/sdrbase/dsp/dspdevicesourceengine.h b/sdrbase/dsp/dspdevicesourceengine.h index 923c0715e..a71a3ebbc 100644 --- a/sdrbase/dsp/dspdevicesourceengine.h +++ b/sdrbase/dsp/dspdevicesourceengine.h @@ -33,7 +33,7 @@ class DeviceSampleSource; class BasebandSampleSink; class ThreadedBasebandSampleSink; -class SDRANGEL_API DSPDeviceSourceEngine : public QThread { +class SDRBASE_API DSPDeviceSourceEngine : public QThread { Q_OBJECT public: diff --git a/sdrbase/dsp/dspengine.h b/sdrbase/dsp/dspengine.h index 7f8bac5b1..43fe1bf28 100644 --- a/sdrbase/dsp/dspengine.h +++ b/sdrbase/dsp/dspengine.h @@ -32,7 +32,7 @@ class DSPDeviceSourceEngine; class DSPDeviceSinkEngine; -class SDRANGEL_API DSPEngine : public QObject { +class SDRBASE_API DSPEngine : public QObject { Q_OBJECT public: DSPEngine(); diff --git a/sdrbase/dsp/dvserialengine.h b/sdrbase/dsp/dvserialengine.h index f2248f90e..b5c50af09 100644 --- a/sdrbase/dsp/dvserialengine.h +++ b/sdrbase/dsp/dvserialengine.h @@ -24,11 +24,13 @@ #include #include +#include "util/export.h" + class QThread; class DVSerialWorker; class AudioFifo; -class DVSerialEngine : public QObject +class SDRBASE_API DVSerialEngine : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/dvserialworker.h b/sdrbase/dsp/dvserialworker.h index 52af0acdb..72dac23ad 100644 --- a/sdrbase/dsp/dvserialworker.h +++ b/sdrbase/dsp/dvserialworker.h @@ -29,12 +29,13 @@ #include "util/message.h" #include "util/syncmessenger.h" #include "util/messagequeue.h" +#include "util/export.h" #include "dsp/filtermbe.h" #include "dsp/dsptypes.h" class AudioFifo; -class DVSerialWorker : public QObject { +class SDRBASE_API DVSerialWorker : public QObject { Q_OBJECT public: class MsgTest : public Message diff --git a/sdrbase/dsp/fftengine.h b/sdrbase/dsp/fftengine.h index 05665241a..abae57959 100644 --- a/sdrbase/dsp/fftengine.h +++ b/sdrbase/dsp/fftengine.h @@ -4,7 +4,7 @@ #include "dsp/dsptypes.h" #include "util/export.h" -class SDRANGEL_API FFTEngine { +class SDRBASE_API FFTEngine { public: virtual ~FFTEngine(); diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index 6318cf2d5..2feabd9ce 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -7,13 +7,14 @@ #include #include "gfft.h" +#include "util/export.h" #undef M_PI #define M_PI 3.14159265358979323846 //---------------------------------------------------------------------- -class fftfilt { +class SDRBASE_API fftfilt { enum {NONE, BLACKMAN, HAMMING, HANNING}; public: @@ -66,7 +67,7 @@ protected: /* Sliding FFT filter from Fldigi */ -class sfft { +class SDRBASE_API sfft { #define K1 0.99999 public: typedef std::complex cmplx; diff --git a/sdrbase/dsp/fftwengine.h b/sdrbase/dsp/fftwengine.h index 9224ba4ad..6000a7a7a 100644 --- a/sdrbase/dsp/fftwengine.h +++ b/sdrbase/dsp/fftwengine.h @@ -5,8 +5,9 @@ #include #include #include "dsp/fftengine.h" +#include "util/export.h" -class FFTWEngine : public FFTEngine { +class SDRBASE_API FFTWEngine : public FFTEngine { public: FFTWEngine(); ~FFTWEngine(); diff --git a/sdrbase/dsp/fftwindow.h b/sdrbase/dsp/fftwindow.h index cca317810..cad15d052 100644 --- a/sdrbase/dsp/fftwindow.h +++ b/sdrbase/dsp/fftwindow.h @@ -27,7 +27,7 @@ #undef M_PI #define M_PI 3.14159265358979323846 -class SDRANGEL_API FFTWindow { +class SDRBASE_API FFTWindow { public: enum Function { Bartlett, diff --git a/sdrbase/dsp/filerecord.h b/sdrbase/dsp/filerecord.h index dac0be078..ccb531a02 100644 --- a/sdrbase/dsp/filerecord.h +++ b/sdrbase/dsp/filerecord.h @@ -11,7 +11,7 @@ class Message; -class SDRANGEL_API FileRecord : public BasebandSampleSink { +class SDRBASE_API FileRecord : public BasebandSampleSink { public: struct Header diff --git a/sdrbase/dsp/filtermbe.h b/sdrbase/dsp/filtermbe.h index f3d6788fb..9163e8523 100644 --- a/sdrbase/dsp/filtermbe.h +++ b/sdrbase/dsp/filtermbe.h @@ -58,8 +58,9 @@ */ #include "iirfilter.h" +#include "util/export.h" -class MBEAudioInterpolatorFilter +class SDRBASE_API MBEAudioInterpolatorFilter { public: MBEAudioInterpolatorFilter(); diff --git a/sdrbase/dsp/filterrc.h b/sdrbase/dsp/filterrc.h index 7f1af7c89..09da1cd64 100644 --- a/sdrbase/dsp/filterrc.h +++ b/sdrbase/dsp/filterrc.h @@ -19,9 +19,10 @@ #define INCLUDE_DSP_FILTERRC_H_ #include "dsp/dsptypes.h" +#include "util/export.h" /** First order low-pass IIR filter for real-valued signals. */ -class LowPassFilterRC +class SDRBASE_API LowPassFilterRC { public: diff --git a/sdrbase/dsp/hbfiltertraits.h b/sdrbase/dsp/hbfiltertraits.h index f72e46765..8d1848944 100644 --- a/sdrbase/dsp/hbfiltertraits.h +++ b/sdrbase/dsp/hbfiltertraits.h @@ -19,6 +19,7 @@ #define SDRBASE_DSP_HBFILTERTRAITS_H_ #include +#include "util/export.h" // uses Q1.14 format internally, input and output are S16 @@ -32,7 +33,7 @@ struct HBFIRFilterTraits }; template<> -struct HBFIRFilterTraits<16> +struct SDRBASE_API HBFIRFilterTraits<16> { static const int32_t hbOrder = 16; static const int32_t hbShift = 12; @@ -42,7 +43,7 @@ struct HBFIRFilterTraits<16> }; template<> -struct HBFIRFilterTraits<32> +struct SDRBASE_API HBFIRFilterTraits<32> { static const int32_t hbOrder = 32; static const int32_t hbShift = 12; @@ -52,7 +53,7 @@ struct HBFIRFilterTraits<32> }; template<> -struct HBFIRFilterTraits<48> +struct SDRBASE_API HBFIRFilterTraits<48> { static const int32_t hbOrder = 48; static const int32_t hbShift = 12; @@ -62,7 +63,7 @@ struct HBFIRFilterTraits<48> }; template<> -struct HBFIRFilterTraits<64> +struct SDRBASE_API HBFIRFilterTraits<64> { static const int32_t hbOrder = 64; static const int32_t hbShift = 12; @@ -72,7 +73,7 @@ struct HBFIRFilterTraits<64> }; template<> -struct HBFIRFilterTraits<80> +struct SDRBASE_API HBFIRFilterTraits<80> { static const int32_t hbOrder = 80; static const int32_t hbShift = 14; @@ -82,7 +83,7 @@ struct HBFIRFilterTraits<80> }; template<> -struct HBFIRFilterTraits<96> +struct SDRBASE_API HBFIRFilterTraits<96> { static const int32_t hbOrder = 96; static const int32_t hbShift = 16; @@ -92,7 +93,7 @@ struct HBFIRFilterTraits<96> }; template<> -struct HBFIRFilterTraits<112> +struct SDRBASE_API HBFIRFilterTraits<112> { static const int32_t hbOrder = 112; static const int32_t hbShift = 18; @@ -102,7 +103,7 @@ struct HBFIRFilterTraits<112> }; template<> -struct HBFIRFilterTraits<128> +struct SDRBASE_API HBFIRFilterTraits<128> { static const int32_t hbOrder = 128; static const int32_t hbShift = 20; diff --git a/sdrbase/dsp/interpolator.h b/sdrbase/dsp/interpolator.h index b504987af..11a979e48 100644 --- a/sdrbase/dsp/interpolator.h +++ b/sdrbase/dsp/interpolator.h @@ -11,7 +11,7 @@ #include #endif -class SDRANGEL_API Interpolator { +class SDRBASE_API Interpolator { public: Interpolator(); ~Interpolator(); diff --git a/sdrbase/dsp/kissengine.h b/sdrbase/dsp/kissengine.h index ad8e53c32..83d029745 100644 --- a/sdrbase/dsp/kissengine.h +++ b/sdrbase/dsp/kissengine.h @@ -3,8 +3,9 @@ #include "dsp/fftengine.h" #include "dsp/kissfft.h" +#include "util/export.h" -class KissEngine : public FFTEngine { +class SDRBASE_API KissEngine : public FFTEngine { public: void configure(int n, bool inverse); void transform(); diff --git a/sdrbase/dsp/nco.h b/sdrbase/dsp/nco.h index 58739fcbc..735a7f70a 100644 --- a/sdrbase/dsp/nco.h +++ b/sdrbase/dsp/nco.h @@ -21,7 +21,7 @@ #include "dsp/dsptypes.h" #include "util/export.h" -class SDRANGEL_API NCO { +class SDRBASE_API NCO { private: enum { TableSize = (1 << 12), diff --git a/sdrbase/dsp/ncof.h b/sdrbase/dsp/ncof.h index 57fa4258f..d07064e56 100644 --- a/sdrbase/dsp/ncof.h +++ b/sdrbase/dsp/ncof.h @@ -20,7 +20,7 @@ #include "dsp/dsptypes.h" #include "util/export.h" -class SDRANGEL_API NCOF { +class SDRBASE_API NCOF { private: enum { TableSize = (1 << 12), diff --git a/sdrbase/dsp/nullsink.h b/sdrbase/dsp/nullsink.h index f9c75447c..0bdbe76a2 100644 --- a/sdrbase/dsp/nullsink.h +++ b/sdrbase/dsp/nullsink.h @@ -6,7 +6,7 @@ class Message; -class SDRANGEL_API NullSink : public BasebandSampleSink { +class SDRBASE_API NullSink : public BasebandSampleSink { public: NullSink(); diff --git a/sdrbase/dsp/phaselock.h b/sdrbase/dsp/phaselock.h index 8547a4f82..47e0ba692 100644 --- a/sdrbase/dsp/phaselock.h +++ b/sdrbase/dsp/phaselock.h @@ -17,9 +17,10 @@ #include #include "dsp/dsptypes.h" +#include "util/export.h" /** Phase-locked loop mainly for broadcadt FM stereo pilot. */ -class PhaseLock +class SDRBASE_API PhaseLock { public: diff --git a/sdrbase/dsp/recursivefilters.h b/sdrbase/dsp/recursivefilters.h index 26f6577f8..68f4b0448 100644 --- a/sdrbase/dsp/recursivefilters.h +++ b/sdrbase/dsp/recursivefilters.h @@ -17,11 +17,13 @@ #ifndef SDRBASE_DSP_RECURSIVEFILTERS_H_ #define SDRBASE_DSP_RECURSIVEFILTERS_H_ +#include "util/export.h" + /** * \Brief: This is a second order bandpass filter using recursive method. r is in range ]0..1[ the higher the steeper the filter. * inspired by:http://www.ece.umd.edu/~tretter/commlab/c6713slides/FSKSlides.pdf */ -class SecondOrderRecursiveFilter +class SDRBASE_API SecondOrderRecursiveFilter { public: SecondOrderRecursiveFilter(float samplingFrequency, float centerFrequency, float r); diff --git a/sdrbase/dsp/samplesinkfifo.h b/sdrbase/dsp/samplesinkfifo.h index 35fcc027c..cdead7be5 100644 --- a/sdrbase/dsp/samplesinkfifo.h +++ b/sdrbase/dsp/samplesinkfifo.h @@ -24,7 +24,7 @@ #include "dsp/dsptypes.h" #include "util/export.h" -class SDRANGEL_API SampleSinkFifo : public QObject { +class SDRBASE_API SampleSinkFifo : public QObject { Q_OBJECT private: diff --git a/sdrbase/dsp/samplesinkfifodoublebuffered.h b/sdrbase/dsp/samplesinkfifodoublebuffered.h index 74befb05d..d7421e512 100644 --- a/sdrbase/dsp/samplesinkfifodoublebuffered.h +++ b/sdrbase/dsp/samplesinkfifodoublebuffered.h @@ -24,7 +24,7 @@ #include "util/export.h" #include "dsp/dsptypes.h" -class SDRANGEL_API SampleSinkFifoDoubleBuffered : public QObject { +class SDRBASE_API SampleSinkFifoDoubleBuffered : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/samplesourcefifo.h b/sdrbase/dsp/samplesourcefifo.h index f42f9c6b5..0f4f2e108 100644 --- a/sdrbase/dsp/samplesourcefifo.h +++ b/sdrbase/dsp/samplesourcefifo.h @@ -24,7 +24,7 @@ #include "util/export.h" #include "dsp/dsptypes.h" -class SDRANGEL_API SampleSourceFifo : public QObject { +class SDRBASE_API SampleSourceFifo : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/threadedbasebandsamplesink.h b/sdrbase/dsp/threadedbasebandsamplesink.h index 93368c6bb..9c9a9fe1a 100644 --- a/sdrbase/dsp/threadedbasebandsamplesink.h +++ b/sdrbase/dsp/threadedbasebandsamplesink.h @@ -32,7 +32,7 @@ class QThread; * Because Qt is a piece of shit this class cannot be a nested protected class of ThreadedSampleSink * So let's make everything public */ -class ThreadedBasebandSampleSinkFifo : public QObject { +class SDRBASE_API ThreadedBasebandSampleSinkFifo : public QObject { Q_OBJECT public: @@ -50,7 +50,7 @@ public slots: /** * This class is a wrapper for SampleSink that runs the SampleSink object in its own thread */ -class SDRANGEL_API ThreadedBasebandSampleSink : public QObject { +class SDRBASE_API ThreadedBasebandSampleSink : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/threadedbasebandsamplesource.h b/sdrbase/dsp/threadedbasebandsamplesource.h index defb1cfb5..e22673c97 100644 --- a/sdrbase/dsp/threadedbasebandsamplesource.h +++ b/sdrbase/dsp/threadedbasebandsamplesource.h @@ -30,7 +30,7 @@ class QThread; /** * This class is a wrapper for BasebandSampleSource that runs the BasebandSampleSource object in its own thread */ -class SDRANGEL_API ThreadedBasebandSampleSource : public QObject { +class SDRBASE_API ThreadedBasebandSampleSource : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/upchannelizer.h b/sdrbase/dsp/upchannelizer.h index 3b15f7a96..644155699 100644 --- a/sdrbase/dsp/upchannelizer.h +++ b/sdrbase/dsp/upchannelizer.h @@ -33,7 +33,7 @@ class MessageQueue; -class SDRANGEL_API UpChannelizer : public BasebandSampleSource { +class SDRBASE_API UpChannelizer : public BasebandSampleSource { Q_OBJECT public: class SDRANGEL_API MsgChannelizerNotification : public Message { diff --git a/sdrbase/dsp/wfir.h b/sdrbase/dsp/wfir.h index 981f80a24..d01a46893 100644 --- a/sdrbase/dsp/wfir.h +++ b/sdrbase/dsp/wfir.h @@ -53,7 +53,9 @@ #ifndef _WFIR_H_ #define _WFIR_H_ -class WFIR +#include "util/export.h" + +class SDRBASE_API WFIR { public: enum TPassTypeName diff --git a/sdrbase/mainparser.h b/sdrbase/mainparser.h index 3d0c19765..0112d3079 100644 --- a/sdrbase/mainparser.h +++ b/sdrbase/mainparser.h @@ -21,7 +21,9 @@ #include #include -class MainParser +#include "util/export.h" + +class SDRBASE_API MainParser { public: MainParser(); diff --git a/sdrbase/plugin/pluginapi.h b/sdrbase/plugin/pluginapi.h index ed7a5ad59..6e72ca989 100644 --- a/sdrbase/plugin/pluginapi.h +++ b/sdrbase/plugin/pluginapi.h @@ -13,7 +13,7 @@ class PluginManager; class MessageQueue; class PluginInstanceGUI; -class SDRANGEL_API PluginAPI : public QObject { +class SDRBASE_API PluginAPI : public QObject { Q_OBJECT public: diff --git a/sdrbase/plugin/plugininstancegui.h b/sdrbase/plugin/plugininstancegui.h index 8871c1ec4..af8ccc59f 100644 --- a/sdrbase/plugin/plugininstancegui.h +++ b/sdrbase/plugin/plugininstancegui.h @@ -10,7 +10,7 @@ class Message; class MessageQueue; -class SDRANGEL_API PluginInstanceGUI { +class SDRBASE_API PluginInstanceGUI { public: PluginInstanceGUI() { }; virtual ~PluginInstanceGUI() { }; diff --git a/sdrbase/plugin/plugininterface.h b/sdrbase/plugin/plugininterface.h index fdcaccda6..57dec2139 100644 --- a/sdrbase/plugin/plugininterface.h +++ b/sdrbase/plugin/plugininterface.h @@ -4,7 +4,9 @@ #include #include -struct PluginDescriptor { +#include "util/export.h" + +struct SDRBASE_API PluginDescriptor { // general plugin description const QString displayedName; const QString version; @@ -27,7 +29,7 @@ class BasebandSampleSource; class ChannelSinkAPI; class ChannelSourceAPI; -class PluginInterface { +class SDRBASE_API PluginInterface { public: struct SamplingDevice { diff --git a/sdrbase/plugin/pluginmanager.h b/sdrbase/plugin/pluginmanager.h index 442a21719..e202f5856 100644 --- a/sdrbase/plugin/pluginmanager.h +++ b/sdrbase/plugin/pluginmanager.h @@ -19,7 +19,7 @@ class MessageQueue; class DeviceSourceAPI; class DeviceSinkAPI; -class SDRANGEL_API PluginManager : public QObject { +class SDRBASE_API PluginManager : public QObject { Q_OBJECT public: diff --git a/sdrbase/settings/mainsettings.h b/sdrbase/settings/mainsettings.h index d200b8865..8734d81f5 100644 --- a/sdrbase/settings/mainsettings.h +++ b/sdrbase/settings/mainsettings.h @@ -5,10 +5,11 @@ #include "preferences.h" #include "preset.h" #include "audio/audiodeviceinfo.h" +#include "util/export.h" class Command; -class MainSettings { +class SDRBASE_API MainSettings { public: MainSettings(); ~MainSettings(); diff --git a/sdrbase/settings/preferences.h b/sdrbase/settings/preferences.h index c58723447..ef7461ddb 100644 --- a/sdrbase/settings/preferences.h +++ b/sdrbase/settings/preferences.h @@ -3,7 +3,9 @@ #include -class Preferences { +#include "util/export.h" + +class SDRBASE_API Preferences { public: Preferences(); diff --git a/sdrbase/settings/preset.h b/sdrbase/settings/preset.h index a2f07fb77..390fd63da 100644 --- a/sdrbase/settings/preset.h +++ b/sdrbase/settings/preset.h @@ -5,7 +5,9 @@ #include #include -class Preset { +#include "util/export.h" + +class SDRBASE_API Preset { public: struct ChannelConfig { QString m_channelIdURI; //!< Channel type ID in URI form diff --git a/sdrbase/util/CRC64.h b/sdrbase/util/CRC64.h index bede1df55..26924252a 100644 --- a/sdrbase/util/CRC64.h +++ b/sdrbase/util/CRC64.h @@ -19,7 +19,9 @@ #include -class CRC64 +#include "util/export.h" + +class SDRBASE_API CRC64 { public: CRC64(); diff --git a/sdrbase/util/db.h b/sdrbase/util/db.h index 5e6741cac..fb08bcc41 100644 --- a/sdrbase/util/db.h +++ b/sdrbase/util/db.h @@ -18,8 +18,9 @@ #define INCLUDE_UTIL_DB_H_ #include "dsp/dsptypes.h" +#include "util/export.h" -class CalcDb +class SDRBASE_API CalcDb { public: static double dbPower(double magsq, double floor = 1e-12); diff --git a/sdrbase/util/export.h b/sdrbase/util/export.h index 7c246d648..6332b9bf7 100644 --- a/sdrbase/util/export.h +++ b/sdrbase/util/export.h @@ -17,29 +17,91 @@ #ifndef __SDRANGEL_EXPORT_H #define __SDRANGEL_EXPORT_H -#if defined __GNUC__ -# if __GNUC__ >= 4 -# define __SDR_EXPORT __attribute__((visibility("default"))) -# define __SDR_IMPORT __attribute__((visibility("default"))) -# else -# define __SDR_EXPORT -# define __SDR_IMPORT -# endif -#elif _MSC_VER -# define __SDR_EXPORT __declspec(dllexport) -# define __SDR_IMPORT __declspec(dllimport) +#if defined (__GNUC__) && (__GNUC__ >= 4) +# define __SDR_EXPORT __attribute__((visibility("default"))) +# define __SDR_IMPORT __attribute__((visibility("default"))) + +#elif defined (_MSC_VER) +# define __SDR_EXPORT __declspec(dllexport) +# define __SDR_IMPORT __declspec(dllimport) + #else # define __SDR_EXPORT # define __SDR_IMPORT #endif -#ifndef sdrangel_STATIC -# ifdef sdrangel_EXPORTS -# define SDRANGEL_API __SDR_EXPORT -# else -# define SDRANGEL_API __SDR_IMPORT -# endif +/* The 'SDRBASE_API' controls the import/export of 'sdrbase' symbols and classes. + */ +#if !defined(sdrangel_STATIC) +# if defined sdrangel_EXPORTS || defined sdrbase_EXPORTS +# define SDRBASE_API __SDR_EXPORT +# else +# define SDRBASE_API __SDR_IMPORT +# endif #else -#define SDRANGEL_API +# define SDRBASE_API #endif + +#define SDRANGEL_API SDRBASE_API /* to be compatible with current situation TODO: remove */ + +/* the 'SDRGUI_API' controls the import/export of 'sdrgui' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef sdrgui_EXPORTS +# define SDRGUI_API __SDR_EXPORT +# else +# define SDRGUI_API __SDR_IMPORT +# endif +#else +# define SDRGUI_API +#endif + +/* the 'DEVICES_API' controls the import/export of 'devices' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef devices_EXPORTS +# define DEVICES_API __SDR_EXPORT +# else +# define DEVICES_API __SDR_IMPORT +# endif +#else +# define DEVICES_API +#endif + +/* the 'HTTPSERVER_API' controls the import/export of 'httpserver' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef httpserver_EXPORTS +# define HTTPSERVER_API __SDR_EXPORT +# else +# define HTTPSERVER_API __SDR_IMPORT +# endif +#else +# define HTTPSERVER_API +#endif + +/* the 'LOGGING_API' controls the import/export of 'logging' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef logging_EXPORTS +# define LOGGING_API __SDR_EXPORT +# else +# define LOGGING_API __SDR_IMPORT +# endif +#else +# define LOGGING_API +#endif + +/* the 'QRTPLIB_API' controls the import/export of 'qrtplib' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef qrtplib_EXPORTS +# define QRTPLIB_API __SDR_EXPORT +# else +# define QRTPLIB_API __SDR_IMPORT +# endif +#else +# define QRTPLIB_API +#endif + #endif /* __SDRANGEL_EXPORT_H */ diff --git a/sdrbase/util/fixedtraits.h b/sdrbase/util/fixedtraits.h index 7b7167031..85c1327d5 100644 --- a/sdrbase/util/fixedtraits.h +++ b/sdrbase/util/fixedtraits.h @@ -20,13 +20,15 @@ #include +#include "util/export.h" + template class FixedTraits { }; template<> -struct FixedTraits<28> +struct SDRBASE_API FixedTraits<28> { static const uint32_t fixed_resolution_shift = 28; //!< 1.0 representation. 28 is the highest power of two that can represent 9.99999... safely on 64 bits internally static const int64_t fixed_resolution = 1LL << fixed_resolution_shift; @@ -42,7 +44,7 @@ struct FixedTraits<28> }; template<> -struct FixedTraits<16> +struct SDRBASE_API FixedTraits<16> { static const uint32_t fixed_resolution_shift = 16; static const int64_t fixed_resolution = 1LL << fixed_resolution_shift; @@ -58,7 +60,7 @@ struct FixedTraits<16> }; template<> -struct FixedTraits<23> +struct SDRBASE_API FixedTraits<23> { static const uint32_t fixed_resolution_shift = 23; static const int64_t fixed_resolution = 1LL << fixed_resolution_shift; @@ -74,7 +76,7 @@ struct FixedTraits<23> }; template<> -struct FixedTraits<24> +struct SDRBASE_API FixedTraits<24> { static const uint32_t fixed_resolution_shift = 24; static const int64_t fixed_resolution = 1LL << fixed_resolution_shift; diff --git a/sdrbase/util/message.h b/sdrbase/util/message.h index 758368a68..bbdeca69a 100644 --- a/sdrbase/util/message.h +++ b/sdrbase/util/message.h @@ -21,7 +21,7 @@ #include #include "util/export.h" -class SDRANGEL_API Message { +class SDRBASE_API Message { public: Message(); virtual ~Message(); diff --git a/sdrbase/util/messagequeue.h b/sdrbase/util/messagequeue.h index 1e718978b..1301a13e6 100644 --- a/sdrbase/util/messagequeue.h +++ b/sdrbase/util/messagequeue.h @@ -25,7 +25,7 @@ class Message; -class SDRANGEL_API MessageQueue : public QObject { +class SDRBASE_API MessageQueue : public QObject { Q_OBJECT public: diff --git a/sdrbase/util/prettyprint.h b/sdrbase/util/prettyprint.h index f53839532..74002f86f 100644 --- a/sdrbase/util/prettyprint.h +++ b/sdrbase/util/prettyprint.h @@ -19,7 +19,9 @@ #include -class EscapeColors +#include "util/export.h" + +class SDRBASE_API EscapeColors { public: static const QString red; diff --git a/sdrbase/util/rtpsink.h b/sdrbase/util/rtpsink.h index 8daac0f74..28753f3c4 100644 --- a/sdrbase/util/rtpsink.h +++ b/sdrbase/util/rtpsink.h @@ -31,7 +31,9 @@ #include "rtperrors.h" #include "rtplibraryversion.h" -class RTPSinkMemoryManager : public jrtplib::RTPMemoryManager +#include "util/export.h" + +class SDRBASE_API RTPSinkMemoryManager : public jrtplib::RTPMemoryManager { public: RTPSinkMemoryManager() diff --git a/sdrbase/util/samplesourceserializer.h b/sdrbase/util/samplesourceserializer.h index c1dad5af2..c303b7f38 100644 --- a/sdrbase/util/samplesourceserializer.h +++ b/sdrbase/util/samplesourceserializer.h @@ -18,8 +18,9 @@ #define INCLUDE_UTIL_SAMPLESOURCESERIALIZER_H_ #include "util/simpleserializer.h" +#include "util/export.h" -class SampleSourceSerializer +class SDRBASE_API SampleSourceSerializer { public: struct Data diff --git a/sdrbase/util/simpleserializer.h b/sdrbase/util/simpleserializer.h index cc76c2006..2d02d2e04 100644 --- a/sdrbase/util/simpleserializer.h +++ b/sdrbase/util/simpleserializer.h @@ -6,7 +6,7 @@ #include "dsp/dsptypes.h" #include "util/export.h" -class SDRANGEL_API SimpleSerializer { +class SDRBASE_API SimpleSerializer { public: SimpleSerializer(quint32 version); diff --git a/sdrbase/util/spinlock.h b/sdrbase/util/spinlock.h index 78af0b61a..969a511ac 100644 --- a/sdrbase/util/spinlock.h +++ b/sdrbase/util/spinlock.h @@ -3,7 +3,9 @@ #include -class Spinlock { +#include "util/export.h" + +class SDRBASE_API Spinlock { public: void lock() { diff --git a/sdrbase/util/syncmessenger.h b/sdrbase/util/syncmessenger.h index d9c59ddba..0c6ed44f8 100644 --- a/sdrbase/util/syncmessenger.h +++ b/sdrbase/util/syncmessenger.h @@ -29,7 +29,7 @@ class Message; /** * This class is responsible of managing the synchronous processing of a message across threads */ -class SDRANGEL_API SyncMessenger : public QObject { +class SDRBASE_API SyncMessenger : public QObject { Q_OBJECT public: diff --git a/sdrbase/util/uid.h b/sdrbase/util/uid.h index eb7b82e7f..d612e7e51 100644 --- a/sdrbase/util/uid.h +++ b/sdrbase/util/uid.h @@ -21,7 +21,9 @@ #include -class UidCalculator +#include "util/export.h" + +class SDRBASE_API UidCalculator { public: /** diff --git a/sdrbase/webapi/webapiadapterinterface.h b/sdrbase/webapi/webapiadapterinterface.h index 914a2abac..ac8f559c3 100644 --- a/sdrbase/webapi/webapiadapterinterface.h +++ b/sdrbase/webapi/webapiadapterinterface.h @@ -24,6 +24,8 @@ #include "SWGErrorResponse.h" +#include "util/export.h" + namespace SWGSDRangel { class SWGInstanceSummaryResponse; @@ -48,7 +50,7 @@ namespace SWGSDRangel class SWGSuccessResponse; } -class WebAPIAdapterInterface +class SDRBASE_API WebAPIAdapterInterface { public: virtual ~WebAPIAdapterInterface() {} diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index 33d981158..bb95aa1fc 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -27,13 +27,15 @@ #include "staticfilecontroller.h" #include "webapiadapterinterface.h" +#include "util/export.h" + namespace SWGSDRangel { class SWGPresetTransfer; class SWGPresetIdentifier; } -class WebAPIRequestMapper : public qtwebapp::HttpRequestHandler { +class SDRBASE_API WebAPIRequestMapper : public qtwebapp::HttpRequestHandler { Q_OBJECT public: WebAPIRequestMapper(QObject* parent=0); diff --git a/sdrbase/webapi/webapiserver.h b/sdrbase/webapi/webapiserver.h index 53b9ae3af..1ae21efc0 100644 --- a/sdrbase/webapi/webapiserver.h +++ b/sdrbase/webapi/webapiserver.h @@ -19,6 +19,8 @@ #ifndef SDRBASE_WEBAPI_WEBAPISERVER_H_ #define SDRBASE_WEBAPI_WEBAPISERVER_H_ +#include "util/export.h" + namespace qtwebapp { class HttpListener; @@ -27,7 +29,7 @@ namespace qtwebapp class WebAPIRequestMapper; -class WebAPIServer +class SDRBASE_API WebAPIServer { public: WebAPIServer(const QString& host, uint16_t port, WebAPIRequestMapper *requestMapper); diff --git a/sdrgui/device/deviceuiset.h b/sdrgui/device/deviceuiset.h index 79c9bae3d..d38118795 100644 --- a/sdrgui/device/deviceuiset.h +++ b/sdrgui/device/deviceuiset.h @@ -20,6 +20,8 @@ #include #include +#include "util/export.h" + class SpectrumVis; class GLSpectrum; class GLSpectrumGUI; @@ -32,7 +34,7 @@ class DeviceSinkAPI; class ChannelMarker; class PluginAPI; -class DeviceUISet +class SDRGUI_API DeviceUISet { public: SpectrumVis *m_spectrumVis; diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index e5e896c28..2520a4684 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -9,7 +9,7 @@ class GLScope; class MessageQueue; -class SDRANGEL_API ScopeVis : public BasebandSampleSink { +class SDRGUI_API ScopeVis : public BasebandSampleSink { public: enum TriggerChannel { TriggerFreeRun, diff --git a/sdrgui/dsp/scopevismulti.h b/sdrgui/dsp/scopevismulti.h index 15bc5d8e9..28c42c43f 100644 --- a/sdrgui/dsp/scopevismulti.h +++ b/sdrgui/dsp/scopevismulti.h @@ -37,7 +37,7 @@ class GLScopeMulti; -class SDRANGEL_API ScopeVisMulti : public QObject { +class SDRGUI_API ScopeVisMulti : public QObject { Q_OBJECT public: enum ProjectionType diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index c31435fc8..0cbbb64b9 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -38,7 +38,7 @@ class GLScopeNG; -class SDRANGEL_API ScopeVisNG : public BasebandSampleSink { +class SDRGUI_API ScopeVisNG : public BasebandSampleSink { public: enum ProjectionType diff --git a/sdrgui/dsp/spectrumscopecombovis.h b/sdrgui/dsp/spectrumscopecombovis.h index bd0f84e8f..c9b5ababc 100644 --- a/sdrgui/dsp/spectrumscopecombovis.h +++ b/sdrgui/dsp/spectrumscopecombovis.h @@ -8,7 +8,7 @@ class Message; -class SDRANGEL_API SpectrumScopeComboVis : public BasebandSampleSink { +class SDRGUI_API SpectrumScopeComboVis : public BasebandSampleSink { public: SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVis* scopeVis); diff --git a/sdrgui/dsp/spectrumscopengcombovis.h b/sdrgui/dsp/spectrumscopengcombovis.h index 3369cf02b..822db1f25 100644 --- a/sdrgui/dsp/spectrumscopengcombovis.h +++ b/sdrgui/dsp/spectrumscopengcombovis.h @@ -8,7 +8,7 @@ class Message; -class SDRANGEL_API SpectrumScopeNGComboVis : public BasebandSampleSink { +class SDRGUI_API SpectrumScopeNGComboVis : public BasebandSampleSink { public: SpectrumScopeNGComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis); diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index 3f3f58ff4..7e237c8f6 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -11,7 +11,7 @@ class GLSpectrum; class MessageQueue; -class SDRANGEL_API SpectrumVis : public BasebandSampleSink { +class SDRGUI_API SpectrumVis : public BasebandSampleSink { public: class SDRANGEL_API MsgConfigureSpectrumVis : public Message { diff --git a/sdrgui/gui/aboutdialog.h b/sdrgui/gui/aboutdialog.h index 52190fd2c..204eda624 100644 --- a/sdrgui/gui/aboutdialog.h +++ b/sdrgui/gui/aboutdialog.h @@ -3,11 +3,13 @@ #include +#include "util/export.h" + namespace Ui { class AboutDialog; } -class AboutDialog : public QDialog { +class SDRGUI_API AboutDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/addpresetdialog.h b/sdrgui/gui/addpresetdialog.h index dea9f7bd1..3f7abdebf 100644 --- a/sdrgui/gui/addpresetdialog.h +++ b/sdrgui/gui/addpresetdialog.h @@ -3,11 +3,13 @@ #include +#include "util/export.h" + namespace Ui { class AddPresetDialog; } -class AddPresetDialog : public QDialog { +class SDRGUI_API AddPresetDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/audiodialog.h b/sdrgui/gui/audiodialog.h index 5807cf688..1a2b54756 100644 --- a/sdrgui/gui/audiodialog.h +++ b/sdrgui/gui/audiodialog.h @@ -3,13 +3,15 @@ #include +#include "util/export.h" + class AudioDeviceInfo; namespace Ui { class AudioDialog; } -class AudioDialog : public QDialog { +class SDRGUI_API AudioDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/basicchannelsettingsdialog.h b/sdrgui/gui/basicchannelsettingsdialog.h index eb599ad9a..2185980f4 100644 --- a/sdrgui/gui/basicchannelsettingsdialog.h +++ b/sdrgui/gui/basicchannelsettingsdialog.h @@ -3,13 +3,15 @@ #include +#include "util/export.h" + namespace Ui { class BasicChannelSettingsDialog; } class ChannelMarker; -class BasicChannelSettingsDialog : public QDialog +class SDRGUI_API BasicChannelSettingsDialog : public QDialog { Q_OBJECT diff --git a/sdrgui/gui/buttonswitch.h b/sdrgui/gui/buttonswitch.h index d5c5d7398..d116b3e72 100644 --- a/sdrgui/gui/buttonswitch.h +++ b/sdrgui/gui/buttonswitch.h @@ -3,7 +3,9 @@ #include -class ButtonSwitch : public QToolButton { +#include "util/export.h" + +class SDRGUI_API ButtonSwitch : public QToolButton { Q_OBJECT public: diff --git a/sdrgui/gui/channelwindow.h b/sdrgui/gui/channelwindow.h index 6bc87e3e5..20c1521d0 100644 --- a/sdrgui/gui/channelwindow.h +++ b/sdrgui/gui/channelwindow.h @@ -3,11 +3,13 @@ #include +#include "util/export.h" + class QBoxLayout; class QSpacerItem; class RollupWidget; -class ChannelWindow : public QScrollArea { +class SDRGUI_API ChannelWindow : public QScrollArea { Q_OBJECT public: diff --git a/sdrgui/gui/clickablelabel.h b/sdrgui/gui/clickablelabel.h index 93ae151a5..41769c499 100644 --- a/sdrgui/gui/clickablelabel.h +++ b/sdrgui/gui/clickablelabel.h @@ -21,7 +21,9 @@ #include #include -class ClickableLabel : public QLabel +#include "util/export.h" + +class SDRGUI_API ClickableLabel : public QLabel { Q_OBJECT public: diff --git a/sdrgui/gui/colormapper.h b/sdrgui/gui/colormapper.h index 5937f387d..50cb93241 100644 --- a/sdrgui/gui/colormapper.h +++ b/sdrgui/gui/colormapper.h @@ -12,7 +12,7 @@ #include #include "util/export.h" -class SDRANGEL_API ColorMapper +class SDRGUI_API ColorMapper { public: enum Theme { diff --git a/sdrgui/gui/commanditem.h b/sdrgui/gui/commanditem.h index 1ab492858..acf17398e 100644 --- a/sdrgui/gui/commanditem.h +++ b/sdrgui/gui/commanditem.h @@ -16,7 +16,9 @@ #include -class CommandItem : public QTreeWidgetItem { +#include "util/export.h" + +class SDRGUI_API CommandItem : public QTreeWidgetItem { public: CommandItem(QTreeWidgetItem* parent, const QStringList& strings, const QString& description, int type); bool operator<(const QTreeWidgetItem& other) const; diff --git a/sdrgui/gui/commandkeyreceiver.h b/sdrgui/gui/commandkeyreceiver.h index 5dce1d45f..9b5ed3be6 100644 --- a/sdrgui/gui/commandkeyreceiver.h +++ b/sdrgui/gui/commandkeyreceiver.h @@ -19,9 +19,11 @@ #include +#include "util/export.h" + class QKeyEvent; -class CommandKeyReceiver : public QObject +class SDRGUI_API CommandKeyReceiver : public QObject { Q_OBJECT public: diff --git a/sdrgui/gui/commandoutputdialog.h b/sdrgui/gui/commandoutputdialog.h index aace09a10..dc6f5e406 100644 --- a/sdrgui/gui/commandoutputdialog.h +++ b/sdrgui/gui/commandoutputdialog.h @@ -20,6 +20,8 @@ #include #include +#include "util/export.h" + namespace Ui { class CommandOutputDialog; } diff --git a/sdrgui/gui/cwkeyergui.h b/sdrgui/gui/cwkeyergui.h index 32fa03048..d55d6982f 100644 --- a/sdrgui/gui/cwkeyergui.h +++ b/sdrgui/gui/cwkeyergui.h @@ -31,7 +31,7 @@ class MessageQueue; class CWKeyer; class CWKeyerSettings; -class SDRANGEL_API CWKeyerGUI : public QWidget, public Serializable { +class SDRGUI_API CWKeyerGUI : public QWidget, public Serializable { Q_OBJECT public: diff --git a/sdrgui/gui/editcommanddialog.h b/sdrgui/gui/editcommanddialog.h index 6861194fd..4f957c1a3 100644 --- a/sdrgui/gui/editcommanddialog.h +++ b/sdrgui/gui/editcommanddialog.h @@ -20,6 +20,8 @@ #include #include +#include "util/export.h" + namespace Ui { class EditCommandDialog; } @@ -27,7 +29,7 @@ namespace Ui { class Command; class CommandKeyReceiver; -class EditCommandDialog : public QDialog { +class SDRGUI_API EditCommandDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/externalclockbutton.h b/sdrgui/gui/externalclockbutton.h index b18d1bc40..98598b906 100644 --- a/sdrgui/gui/externalclockbutton.h +++ b/sdrgui/gui/externalclockbutton.h @@ -23,7 +23,9 @@ #include -class ExternalClockButton : public QPushButton { +#include "util/export.h" + +class SDRGUI_API ExternalClockButton : public QPushButton { Q_OBJECT public: diff --git a/sdrgui/gui/externalclockdialog.h b/sdrgui/gui/externalclockdialog.h index 448bc4de0..f77d77387 100644 --- a/sdrgui/gui/externalclockdialog.h +++ b/sdrgui/gui/externalclockdialog.h @@ -23,11 +23,13 @@ #include +#include "util/export.h" + namespace Ui { class ExternalClockDialog; } -class ExternalClockDialog : public QDialog { +class SDRGUI_API ExternalClockDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/glscope.h b/sdrgui/gui/glscope.h index 8319583b6..558705823 100644 --- a/sdrgui/gui/glscope.h +++ b/sdrgui/gui/glscope.h @@ -39,7 +39,7 @@ class ScopeVis; class QPainter; -class SDRANGEL_API GLScope: public QGLWidget { +class SDRGUI_API GLScope: public QGLWidget { Q_OBJECT public: diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h index 38866cbd5..982f0bd3c 100644 --- a/sdrgui/gui/glscopegui.h +++ b/sdrgui/gui/glscopegui.h @@ -15,7 +15,7 @@ namespace Ui { class MessageQueue; class GLScope; -class SDRANGEL_API GLScopeGUI : public QWidget, public Serializable { +class SDRGUI_API GLScopeGUI : public QWidget, public Serializable { Q_OBJECT public: diff --git a/sdrgui/gui/glscopemulti.h b/sdrgui/gui/glscopemulti.h index 5466522a1..5a3cac400 100644 --- a/sdrgui/gui/glscopemulti.h +++ b/sdrgui/gui/glscopemulti.h @@ -35,7 +35,7 @@ class QPainter; -class SDRANGEL_API GLScopeMulti: public QGLWidget { +class SDRGUI_API GLScopeMulti: public QGLWidget { Q_OBJECT public: diff --git a/sdrgui/gui/glscopemultigui.h b/sdrgui/gui/glscopemultigui.h index 0c04df3dc..cdae7726f 100644 --- a/sdrgui/gui/glscopemultigui.h +++ b/sdrgui/gui/glscopemultigui.h @@ -33,7 +33,7 @@ namespace Ui { class MessageQueue; class GLScopeMulti; -class SDRANGEL_API GLScopeMultiGUI : public QWidget { +class SDRGUI_API GLScopeMultiGUI : public QWidget { Q_OBJECT public: diff --git a/sdrgui/gui/glscopeng.h b/sdrgui/gui/glscopeng.h index e28816e51..317ddb522 100644 --- a/sdrgui/gui/glscopeng.h +++ b/sdrgui/gui/glscopeng.h @@ -35,7 +35,7 @@ class QPainter; -class SDRANGEL_API GLScopeNG: public QGLWidget { +class SDRGUI_API GLScopeNG: public QGLWidget { Q_OBJECT public: diff --git a/sdrgui/gui/glscopenggui.h b/sdrgui/gui/glscopenggui.h index 8b3c67891..f82d9f90b 100644 --- a/sdrgui/gui/glscopenggui.h +++ b/sdrgui/gui/glscopenggui.h @@ -34,7 +34,7 @@ namespace Ui { class MessageQueue; class GLScopeNG; -class SDRANGEL_API GLScopeNGGUI : public QWidget, public Serializable { +class SDRGUI_API GLScopeNGGUI : public QWidget, public Serializable { Q_OBJECT public: diff --git a/sdrgui/gui/glshadersimple.h b/sdrgui/gui/glshadersimple.h index 633f621f8..929d17f03 100644 --- a/sdrgui/gui/glshadersimple.h +++ b/sdrgui/gui/glshadersimple.h @@ -21,11 +21,13 @@ #include #include +#include "util/export.h" + class QOpenGLShaderProgram; class QMatrix4x4; class QVector4D; -class GLShaderSimple +class SDRGUI_API GLShaderSimple { public: GLShaderSimple(); diff --git a/sdrgui/gui/glshadertextured.h b/sdrgui/gui/glshadertextured.h index a20daacc5..59d411fdd 100644 --- a/sdrgui/gui/glshadertextured.h +++ b/sdrgui/gui/glshadertextured.h @@ -25,11 +25,13 @@ #include #include +#include "util/export.h" + class QOpenGLShaderProgram; class QMatrix4x4; class QImage; -class GLShaderTextured +class SDRGUI_API GLShaderTextured { public: GLShaderTextured(); diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index f00da8249..57c7a7456 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -37,7 +37,7 @@ class QOpenGLShaderProgram; -class SDRANGEL_API GLSpectrum : public QGLWidget { +class SDRGUI_API GLSpectrum : public QGLWidget { Q_OBJECT public: diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 4f29d9da9..3ee2cf481 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -14,7 +14,7 @@ class MessageQueue; class SpectrumVis; class GLSpectrum; -class SDRANGEL_API GLSpectrumGUI : public QWidget, public Serializable { +class SDRGUI_API GLSpectrumGUI : public QWidget, public Serializable { Q_OBJECT public: diff --git a/sdrgui/gui/indicator.h b/sdrgui/gui/indicator.h index 66e505810..ee9aa75f9 100644 --- a/sdrgui/gui/indicator.h +++ b/sdrgui/gui/indicator.h @@ -21,7 +21,7 @@ #include #include "util/export.h" -class SDRANGEL_API Indicator : public QWidget { +class SDRGUI_API Indicator : public QWidget { private: Q_OBJECT; diff --git a/sdrgui/gui/levelmeter.h b/sdrgui/gui/levelmeter.h index 07a52d1f1..e7f6084ca 100644 --- a/sdrgui/gui/levelmeter.h +++ b/sdrgui/gui/levelmeter.h @@ -52,13 +52,14 @@ #include "dsp/dsptypes.h" #include "gui/scaleengine.h" +#include "util/export.h" /** * Widget which displays a vertical audio level meter, indicating the * RMS and peak levels of the window of audio samples most recently analyzed * by the Engine. */ -class LevelMeter : public QWidget +class SDRGUI_API LevelMeter : public QWidget { Q_OBJECT diff --git a/sdrgui/gui/loggingdialog.h b/sdrgui/gui/loggingdialog.h index 043399284..e5e016f2b 100644 --- a/sdrgui/gui/loggingdialog.h +++ b/sdrgui/gui/loggingdialog.h @@ -20,12 +20,13 @@ #include #include "settings/mainsettings.h" +#include "util/export.h" namespace Ui { class LoggingDialog; } -class LoggingDialog : public QDialog { +class SDRGUI_API LoggingDialog : public QDialog { Q_OBJECT public: explicit LoggingDialog(MainSettings& mainSettings, QWidget* parent = 0); diff --git a/sdrgui/gui/mypositiondialog.h b/sdrgui/gui/mypositiondialog.h index 89bf087ac..c4f834f76 100644 --- a/sdrgui/gui/mypositiondialog.h +++ b/sdrgui/gui/mypositiondialog.h @@ -23,12 +23,13 @@ #include #include "settings/mainsettings.h" +#include "util/export.h" namespace Ui { class MyPositionDialog; } -class MyPositionDialog : public QDialog { +class SDRGUI_API MyPositionDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/pluginsdialog.h b/sdrgui/gui/pluginsdialog.h index d0f7305a7..591d51127 100644 --- a/sdrgui/gui/pluginsdialog.h +++ b/sdrgui/gui/pluginsdialog.h @@ -3,12 +3,13 @@ #include #include "plugin/pluginmanager.h" +#include "util/export.h" namespace Ui { class PluginsDialog; } -class PluginsDialog : public QDialog { +class SDRGUI_API PluginsDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/presetitem.h b/sdrgui/gui/presetitem.h index fa455435b..8735b071f 100644 --- a/sdrgui/gui/presetitem.h +++ b/sdrgui/gui/presetitem.h @@ -17,7 +17,9 @@ #include -class PresetItem : public QTreeWidgetItem { +#include "util/export.h" + +class SDRGUI_API PresetItem : public QTreeWidgetItem { public: PresetItem(QTreeWidgetItem* parent, const QStringList& strings, quint64 frequency, int type); bool operator<(const QTreeWidgetItem& other) const; diff --git a/sdrgui/gui/rollupwidget.h b/sdrgui/gui/rollupwidget.h index 4a6514a53..d5f746b08 100644 --- a/sdrgui/gui/rollupwidget.h +++ b/sdrgui/gui/rollupwidget.h @@ -4,7 +4,7 @@ #include #include "util/export.h" -class SDRANGEL_API RollupWidget : public QWidget { +class SDRGUI_API RollupWidget : public QWidget { Q_OBJECT public: diff --git a/sdrgui/gui/samplingdevicecontrol.h b/sdrgui/gui/samplingdevicecontrol.h index 462a84a68..260ad9cc1 100644 --- a/sdrgui/gui/samplingdevicecontrol.h +++ b/sdrgui/gui/samplingdevicecontrol.h @@ -33,7 +33,7 @@ class PluginManager; class DeviceSourceAPI; class DeviceSinkAPI; -class SDRANGEL_API SamplingDeviceControl : public QWidget { +class SDRGUI_API SamplingDeviceControl : public QWidget { Q_OBJECT public: diff --git a/sdrgui/gui/samplingdevicedialog.h b/sdrgui/gui/samplingdevicedialog.h index 6e771fa33..059a1ac51 100644 --- a/sdrgui/gui/samplingdevicedialog.h +++ b/sdrgui/gui/samplingdevicedialog.h @@ -24,11 +24,13 @@ #include #include +#include "util/export.h" + namespace Ui { class SamplingDeviceDialog; } -class SamplingDeviceDialog : public QDialog { +class SDRGUI_API SamplingDeviceDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/scaleengine.h b/sdrgui/gui/scaleengine.h index 3746dbe05..41874d1f4 100644 --- a/sdrgui/gui/scaleengine.h +++ b/sdrgui/gui/scaleengine.h @@ -24,7 +24,7 @@ #include "physicalunit.h" #include "util/export.h" -class SDRANGEL_API ScaleEngine { +class SDRGUI_API ScaleEngine { public: struct Tick { float pos; diff --git a/sdrgui/gui/tickedslider.h b/sdrgui/gui/tickedslider.h index 050881223..67b33b2d5 100644 --- a/sdrgui/gui/tickedslider.h +++ b/sdrgui/gui/tickedslider.h @@ -22,7 +22,9 @@ #include #include -class TickedSlider : public QSlider +#include "util/export.h" + +class SDRGUI_API TickedSlider : public QSlider { public: TickedSlider(QWidget* parent = 0); diff --git a/sdrgui/gui/transverterbutton.h b/sdrgui/gui/transverterbutton.h index b526fb02c..c4b19a9fc 100644 --- a/sdrgui/gui/transverterbutton.h +++ b/sdrgui/gui/transverterbutton.h @@ -23,7 +23,9 @@ #include -class TransverterButton : public QPushButton { +#include "util/export.h" + +class SDRGUI_API TransverterButton : public QPushButton { Q_OBJECT public: diff --git a/sdrgui/gui/transverterdialog.h b/sdrgui/gui/transverterdialog.h index 581617482..892f0c058 100644 --- a/sdrgui/gui/transverterdialog.h +++ b/sdrgui/gui/transverterdialog.h @@ -23,11 +23,13 @@ #include +#include "util/export.h" + namespace Ui { class TransverterDialog; } -class TransverterDialog : public QDialog { +class SDRGUI_API TransverterDialog : public QDialog { Q_OBJECT public: diff --git a/sdrgui/gui/valuedial.h b/sdrgui/gui/valuedial.h index a0bf7b104..6c9e020bf 100644 --- a/sdrgui/gui/valuedial.h +++ b/sdrgui/gui/valuedial.h @@ -20,7 +20,7 @@ #include "gui/colormapper.h" #include "util/export.h" -class SDRANGEL_API ValueDial : public QWidget { +class SDRGUI_API ValueDial : public QWidget { Q_OBJECT public: diff --git a/sdrgui/gui/valuedialz.h b/sdrgui/gui/valuedialz.h index 0be7cf95b..c313d550f 100644 --- a/sdrgui/gui/valuedialz.h +++ b/sdrgui/gui/valuedialz.h @@ -23,7 +23,7 @@ #include "gui/colormapper.h" #include "util/export.h" -class SDRANGEL_API ValueDialZ : public QWidget { +class SDRGUI_API ValueDialZ : public QWidget { Q_OBJECT public: diff --git a/sdrgui/mainwindow.h b/sdrgui/mainwindow.h index f423ea50b..b4785d5c7 100644 --- a/sdrgui/mainwindow.h +++ b/sdrgui/mainwindow.h @@ -66,7 +66,7 @@ namespace Ui { class MainWindow; } -class SDRANGEL_API MainWindow : public QMainWindow { +class SDRGUI_API MainWindow : public QMainWindow { Q_OBJECT public: diff --git a/sdrgui/webapi/webapiadaptergui.h b/sdrgui/webapi/webapiadaptergui.h index 5b5891d91..b82124fff 100644 --- a/sdrgui/webapi/webapiadaptergui.h +++ b/sdrgui/webapi/webapiadaptergui.h @@ -22,10 +22,11 @@ #include #include "webapi/webapiadapterinterface.h" +#include "util/export.h" class MainWindow; -class WebAPIAdapterGUI: public WebAPIAdapterInterface +class SDRGUI_API WebAPIAdapterGUI: public WebAPIAdapterInterface { public: WebAPIAdapterGUI(MainWindow& mainWindow); From 980192548d90ebd0aaa1d3f695091abfd6889fac Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 3 Mar 2018 21:19:59 +0100 Subject: [PATCH 048/956] Adapt to MSVC linker: removed SDRANGEL_API --- plugins/channelrx/demodatv/atvscreen.h | 2 +- plugins/channelrx/demoddatv/datvscreen.h | 2 +- sdrbase/dsp/downchannelizer.h | 2 +- sdrbase/dsp/inthalfbandfilter.h | 2 +- sdrbase/dsp/inthalfbandfilterdb.h | 2 +- sdrbase/dsp/inthalfbandfilterdbf.h | 2 +- sdrbase/dsp/inthalfbandfiltereo1.h | 2 +- sdrbase/dsp/upchannelizer.h | 2 +- sdrbase/util/export.h | 2 -- sdrbase/util/simpleserializer.h | 2 +- sdrgui/dsp/spectrumvis.h | 2 +- sdrsrv/maincore.h | 2 +- 12 files changed, 11 insertions(+), 13 deletions(-) diff --git a/plugins/channelrx/demodatv/atvscreen.h b/plugins/channelrx/demodatv/atvscreen.h index 37068aaa0..f2cf82c88 100644 --- a/plugins/channelrx/demodatv/atvscreen.h +++ b/plugins/channelrx/demodatv/atvscreen.h @@ -39,7 +39,7 @@ class QPainter; -class SDRANGEL_API ATVScreen: public QGLWidget, public ATVScreenInterface +class ATVScreen: public QGLWidget, public ATVScreenInterface { Q_OBJECT diff --git a/plugins/channelrx/demoddatv/datvscreen.h b/plugins/channelrx/demoddatv/datvscreen.h index 7dc6b34e0..3f9042b52 100644 --- a/plugins/channelrx/demoddatv/datvscreen.h +++ b/plugins/channelrx/demoddatv/datvscreen.h @@ -35,7 +35,7 @@ class QPainter; -class SDRANGEL_API DATVScreen: public QGLWidget +class DATVScreen: public QGLWidget { Q_OBJECT diff --git a/sdrbase/dsp/downchannelizer.h b/sdrbase/dsp/downchannelizer.h index d41b94677..b1dad6012 100644 --- a/sdrbase/dsp/downchannelizer.h +++ b/sdrbase/dsp/downchannelizer.h @@ -40,7 +40,7 @@ class MessageQueue; class SDRBASE_API DownChannelizer : public BasebandSampleSink { Q_OBJECT public: - class SDRANGEL_API MsgChannelizerNotification : public Message { + class MsgChannelizerNotification : public Message { MESSAGE_CLASS_DECLARATION public: diff --git a/sdrbase/dsp/inthalfbandfilter.h b/sdrbase/dsp/inthalfbandfilter.h index 2a0eec627..6b482d2b2 100644 --- a/sdrbase/dsp/inthalfbandfilter.h +++ b/sdrbase/dsp/inthalfbandfilter.h @@ -24,7 +24,7 @@ #include "util/export.h" template -class SDRANGEL_API IntHalfbandFilter { +class SDRBASE_API IntHalfbandFilter { public: IntHalfbandFilter() : m_ptr(0), diff --git a/sdrbase/dsp/inthalfbandfilterdb.h b/sdrbase/dsp/inthalfbandfilterdb.h index e65dfa59c..f3bb13f53 100644 --- a/sdrbase/dsp/inthalfbandfilterdb.h +++ b/sdrbase/dsp/inthalfbandfilterdb.h @@ -27,7 +27,7 @@ #include "util/export.h" template -class SDRANGEL_API IntHalfbandFilterDB { +class SDRBASE_API IntHalfbandFilterDB { public: IntHalfbandFilterDB(); diff --git a/sdrbase/dsp/inthalfbandfilterdbf.h b/sdrbase/dsp/inthalfbandfilterdbf.h index 01b281f50..9b40edc40 100644 --- a/sdrbase/dsp/inthalfbandfilterdbf.h +++ b/sdrbase/dsp/inthalfbandfilterdbf.h @@ -27,7 +27,7 @@ #include "util/export.h" template -class SDRANGEL_API IntHalfbandFilterDBF { +class SDRBASE_API IntHalfbandFilterDBF { public: IntHalfbandFilterDBF(); diff --git a/sdrbase/dsp/inthalfbandfiltereo1.h b/sdrbase/dsp/inthalfbandfiltereo1.h index b69c1da3a..4d627bed6 100644 --- a/sdrbase/dsp/inthalfbandfiltereo1.h +++ b/sdrbase/dsp/inthalfbandfiltereo1.h @@ -30,7 +30,7 @@ #include "util/export.h" template -class SDRANGEL_API IntHalfbandFilterEO1 { +class SDRBASE_API IntHalfbandFilterEO1 { public: IntHalfbandFilterEO1(); diff --git a/sdrbase/dsp/upchannelizer.h b/sdrbase/dsp/upchannelizer.h index 644155699..5d16b2c00 100644 --- a/sdrbase/dsp/upchannelizer.h +++ b/sdrbase/dsp/upchannelizer.h @@ -36,7 +36,7 @@ class MessageQueue; class SDRBASE_API UpChannelizer : public BasebandSampleSource { Q_OBJECT public: - class SDRANGEL_API MsgChannelizerNotification : public Message { + class MsgChannelizerNotification : public Message { MESSAGE_CLASS_DECLARATION public: diff --git a/sdrbase/util/export.h b/sdrbase/util/export.h index 6332b9bf7..34e3c2830 100644 --- a/sdrbase/util/export.h +++ b/sdrbase/util/export.h @@ -42,8 +42,6 @@ # define SDRBASE_API #endif -#define SDRANGEL_API SDRBASE_API /* to be compatible with current situation TODO: remove */ - /* the 'SDRGUI_API' controls the import/export of 'sdrgui' symbols */ #if !defined(sdrangel_STATIC) diff --git a/sdrbase/util/simpleserializer.h b/sdrbase/util/simpleserializer.h index 2d02d2e04..24a026856 100644 --- a/sdrbase/util/simpleserializer.h +++ b/sdrbase/util/simpleserializer.h @@ -48,7 +48,7 @@ protected: bool writeTag(Type type, quint32 id, quint32 length); }; -class SDRANGEL_API SimpleDeserializer { +class SDRBASE_API SimpleDeserializer { public: SimpleDeserializer(const QByteArray& data); diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index 7e237c8f6..92abd42df 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -14,7 +14,7 @@ class MessageQueue; class SDRGUI_API SpectrumVis : public BasebandSampleSink { public: - class SDRANGEL_API MsgConfigureSpectrumVis : public Message { + class MsgConfigureSpectrumVis : public Message { MESSAGE_CLASS_DECLARATION public: diff --git a/sdrsrv/maincore.h b/sdrsrv/maincore.h index daf5dce34..34841682e 100644 --- a/sdrsrv/maincore.h +++ b/sdrsrv/maincore.h @@ -47,7 +47,7 @@ namespace qtwebapp { class LoggerWithFile; } -class SDRANGEL_API MainCore : public QObject { +class MainCore : public QObject { Q_OBJECT public: From 98fc93a26b3458a5a33d445469e019650c76f13e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Mar 2018 01:47:32 +0100 Subject: [PATCH 049/956] Make main CMakeLists.txt detect Clang compiler correctly --- CMakeLists.txt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11122c5f3..ca4c02edf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,7 @@ if (${ARCHITECTURE} MATCHES "x86_64|AMD64|x86") EXECUTE_PROCESS( COMMAND grep flags /proc/cpuinfo OUTPUT_VARIABLE CPU_FLAGS ) # if (${CPU_FLAGS} MATCHES "avx2") # set(HAS_AVX2 ON CACHE BOOL "Architecture has AVX2 SIMD enabled") -# if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) +# if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) # set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mavx2" ) # set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mavx2" ) # message(STATUS "Use AVX2 SIMD instructions") @@ -117,7 +117,7 @@ if (${ARCHITECTURE} MATCHES "x86_64|AMD64|x86") # endif() if (${CPU_FLAGS} MATCHES "sse4_1") set(HAS_SSE4_1 ON CACHE BOOL "Architecture has SSE 4.1 SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -msse4.1" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -msse4.1" ) message(STATUS "Use SSE 4.1 SIMD instructions") @@ -134,7 +134,7 @@ if (${ARCHITECTURE} MATCHES "x86_64|AMD64|x86") endif() if (${CPU_FLAGS} MATCHES "ssse3") set(HAS_SSSE3 ON CACHE BOOL "Architecture has SSSE3 SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mssse3" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mssse3" ) message(STATUS "Use SSSE3 SIMD instructions") @@ -151,7 +151,7 @@ if (${ARCHITECTURE} MATCHES "x86_64|AMD64|x86") endif() if (${CPU_FLAGS} MATCHES "sse2") set(HAS_SSE2 ON CACHE BOOL "Architecture has SSE2 SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -msse2" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -msse2" ) message(STATUS "Use SSE2 SIMD instructions") @@ -170,7 +170,7 @@ elseif (${ARCHITECTURE} MATCHES "armv7l") EXECUTE_PROCESS( COMMAND grep Features /proc/cpuinfo OUTPUT_VARIABLE CPU_FLAGS ) if (${CPU_FLAGS} MATCHES "neon") set(HAS_NEON ON CACHE BOOL "Architecture has NEON SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon" ) message(STATUS "Use NEON SIMD instructions") @@ -181,7 +181,7 @@ elseif (${ARCHITECTURE} MATCHES "armv7l") endif() elseif (${ARCHITECTURE} MATCHES "aarch64") set(HAS_NEON ON CACHE BOOL FORCE "Architecture has NEON SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) message(STATUS "Aarch64 always has NEON SIMD instructions") add_definitions(-DUSE_NEON) endif() @@ -204,7 +204,11 @@ if (SANITIZE_ADDRESS) set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -fsanitize=address") endif() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -Woverloaded-virtual -fmax-errors=10 -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -Woverloaded-virtual -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=10") +endif() ############################################################################## # base libraries From 28e9df5fe8e9382270aaece18324a6d6ab5c5fca Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Mar 2018 01:47:51 +0100 Subject: [PATCH 050/956] Clang clean compile --- devices/plutosdr/deviceplutosdrparams.h | 2 +- plugins/channelrx/demodbfm/CMakeLists.txt | 4 +++- plugins/channelrx/demoddatv/leansdr/generic.h | 2 +- plugins/channelrx/demoddatv/leansdr/sdr.h | 1 - plugins/channelrx/demoddsd/dsddemod.cpp | 1 - plugins/channelrx/demoddsd/dsddemod.h | 2 -- plugins/channelrx/demodnfm/nfmdemod.cpp | 2 -- plugins/channelrx/demodnfm/nfmdemod.h | 8 -------- plugins/channelrx/demodwfm/wfmdemod.h | 1 - plugins/channelrx/tcpsrc/tcpsrcsettings.h | 2 +- plugins/channelrx/udpsrc/udpsrcsettings.h | 2 +- plugins/channeltx/modatv/atvmodsettings.h | 2 +- plugins/channeltx/modnfm/nfmmod.cpp | 10 +++++----- plugins/channeltx/modnfm/nfmmod.h | 11 +---------- plugins/channeltx/modnfm/nfmmodgui.h | 2 +- plugins/channeltx/modssb/ssbmodsettings.h | 2 +- plugins/channeltx/udpsink/udpsinkgui.cpp | 2 +- plugins/channeltx/udpsink/udpsinksettings.h | 2 +- .../sdrdaemonsource/sdrdaemonsourcebuffer.cpp | 3 +-- .../sdrdaemonsource/sdrdaemonsourcebuffer.h | 3 +-- .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 4 +--- .../sdrdaemonsource/sdrdaemonsourceinput.h | 2 -- .../sdrdaemonsource/sdrdaemonsourceudphandler.cpp | 1 - sdrbase/resources/webapi/doc/html2/index.html | 2 +- sdrbase/util/bitfieldindex.h | 14 ++++++++++++-- sdrgui/dsp/scopevisng.cpp | 2 -- sdrgui/dsp/scopevisng.h | 2 -- swagger/sdrangel/code/html2/index.html | 2 +- swagger/sdrangel/code/qt5/client/SWGAudioDevice.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGAudioDevices.h | 8 ++++---- .../code/qt5/client/SWGAudioDevicesSelect.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGCWKeyerSettings.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGChannel.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGChannelListItem.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGChannelSettings.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGDVSeralDevices.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGDVSerialDevice.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGDeviceListItem.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGDeviceSet.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGDeviceSetList.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGDeviceSettings.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGDeviceState.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGErrorResponse.h | 8 ++++---- .../code/qt5/client/SWGFileSourceSettings.h | 8 ++++---- .../code/qt5/client/SWGHackRFInputSettings.h | 8 ++++---- .../code/qt5/client/SWGHackRFOutputSettings.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGHelpers.cpp | 2 +- .../code/qt5/client/SWGInstanceChannelsResponse.h | 8 ++++---- .../code/qt5/client/SWGInstanceDevicesResponse.h | 8 ++++---- .../code/qt5/client/SWGInstanceSummaryResponse.h | 8 ++++---- .../code/qt5/client/SWGLimeSdrInputSettings.h | 8 ++++---- .../code/qt5/client/SWGLimeSdrOutputSettings.h | 8 ++++---- .../code/qt5/client/SWGLocationInformation.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGNFMDemodSettings.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGNFMModSettings.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGPresetExport.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGPresetGroup.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGPresetIdentifier.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGPresetImport.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGPresetItem.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGPresetTransfer.h | 8 ++++---- swagger/sdrangel/code/qt5/client/SWGPresets.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGRtlSdrSettings.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGSamplingDevice.h | 8 ++++---- .../sdrangel/code/qt5/client/SWGSuccessResponse.h | 8 ++++---- 66 files changed, 184 insertions(+), 207 deletions(-) diff --git a/devices/plutosdr/deviceplutosdrparams.h b/devices/plutosdr/deviceplutosdrparams.h index e318d3f72..69f83de66 100644 --- a/devices/plutosdr/deviceplutosdrparams.h +++ b/devices/plutosdr/deviceplutosdrparams.h @@ -30,7 +30,7 @@ class DEVICES_API DevicePlutoSDRBox; * There is only one copy that is constructed by the first participant and destroyed by the last. * A participant knows it is the first or last by checking the lists of buddies (Rx + Tx). */ -struct DevicePlutoSDRParams +class DevicePlutoSDRParams { public: DevicePlutoSDRParams(); diff --git a/plugins/channelrx/demodbfm/CMakeLists.txt b/plugins/channelrx/demodbfm/CMakeLists.txt index 8eae42d0b..090cce81d 100644 --- a/plugins/channelrx/demodbfm/CMakeLists.txt +++ b/plugins/channelrx/demodbfm/CMakeLists.txt @@ -22,7 +22,9 @@ set(bfm_HEADERS rdstmc.h ) -set_source_files_properties(rdstmc.cpp PROPERTIES COMPILE_FLAGS -fno-var-tracking-assignments) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set_source_files_properties(rdstmc.cpp PROPERTIES COMPILE_FLAGS -fno-var-tracking-assignments) +endif() set(bfm_FORMS bfmdemodgui.ui diff --git a/plugins/channelrx/demoddatv/leansdr/generic.h b/plugins/channelrx/demoddatv/leansdr/generic.h index eb44ac05b..47577328e 100644 --- a/plugins/channelrx/demoddatv/leansdr/generic.h +++ b/plugins/channelrx/demoddatv/leansdr/generic.h @@ -329,7 +329,7 @@ private: template struct serializer: runnable { - serializer(scheduler *sch, pipebuf &_in, pipebuf &_out) : + serializer(scheduler *sch __attribute__((unused)), pipebuf &_in, pipebuf &_out) : nin(max((size_t) 1, sizeof(Tin) / sizeof(Tout))), nout(max((size_t) 1, sizeof(Tout) / sizeof(Tin))), in(_in), out(_out, nout) { if (nin * sizeof(Tin) != nout * sizeof(Tout)) diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h index 94b35db94..6d4da2b57 100644 --- a/plugins/channelrx/demoddatv/leansdr/sdr.h +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -325,7 +325,6 @@ private: float gain = estimated ? out_rms / sqrtf(estimated) : 0; pin = in.rd(); complex *pout = out.wr(); - float bwcomp = 1 - bw; for (; pin < pend; ++pin, ++pout) { pout->re = pin->re * gain; diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index d668cc743..34b3e86e1 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -50,7 +50,6 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : m_squelchGate(0), m_squelchLevel(1e-4), m_squelchOpen(false), - m_fmExcursion(24), m_audioFifo1(48000), m_audioFifo2(48000), m_scope(0), diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index 2554f4f2a..5c47cb5fe 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -181,8 +181,6 @@ private: double m_magsqPeak; int m_magsqCount; - Real m_fmExcursion; - SampleVector m_scopeSampleBuffer; AudioVector m_audioBuffer; uint m_audioBufferFill; diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 0a097bee0..f3fb247a4 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -55,7 +55,6 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_sampleCount(0), m_squelchCount(0), m_squelchGate(2), - m_audioMute(false), m_squelchLevel(-990), m_squelchOpen(false), m_afSquelchOpen(false), @@ -64,7 +63,6 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_magsqPeak(0.0f), m_magsqCount(0), m_afSquelch(2, afSqTones), - m_fmExcursion(2400), m_audioFifo(48000), m_settingsMutex(QMutex::Recursive) { diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index 7fe8d2569..eea637641 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -191,7 +191,6 @@ private: int m_sampleCount; int m_squelchCount; int m_squelchGate; - bool m_audioMute; Real m_squelchLevel; bool m_squelchOpen; @@ -201,16 +200,9 @@ private: double m_magsqPeak; int m_magsqCount; - Real m_lastArgument; - //Complex m_m1Sample; - //Complex m_m2Sample; MovingAverageUtil m_movingAverage; AFSquelch m_afSquelch; Real m_agcLevel; // AGC will aim to this level - Real m_agcFloor; // AGC will not go below this level - - Real m_fmExcursion; - //Real m_fmScaling; AudioVector m_audioBuffer; uint m_audioBufferFill; diff --git a/plugins/channelrx/demodwfm/wfmdemod.h b/plugins/channelrx/demodwfm/wfmdemod.h index 7cb60b43b..507b2a8f6 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.h +++ b/plugins/channelrx/demodwfm/wfmdemod.h @@ -149,7 +149,6 @@ private: double m_magsqPeak; int m_magsqCount; - Real m_lastArgument; MovingAverageUtil m_movingAverage; Real m_fmExcursion; diff --git a/plugins/channelrx/tcpsrc/tcpsrcsettings.h b/plugins/channelrx/tcpsrc/tcpsrcsettings.h index 372a032f0..9c1aee574 100644 --- a/plugins/channelrx/tcpsrc/tcpsrcsettings.h +++ b/plugins/channelrx/tcpsrc/tcpsrcsettings.h @@ -21,7 +21,7 @@ #include #include -struct Serializable; +class Serializable; struct TCPSrcSettings { diff --git a/plugins/channelrx/udpsrc/udpsrcsettings.h b/plugins/channelrx/udpsrc/udpsrcsettings.h index 45ee20a14..7286d5fcf 100644 --- a/plugins/channelrx/udpsrc/udpsrcsettings.h +++ b/plugins/channelrx/udpsrc/udpsrcsettings.h @@ -21,7 +21,7 @@ #include #include -struct Serializable; +class Serializable; struct UDPSrcSettings { diff --git a/plugins/channeltx/modatv/atvmodsettings.h b/plugins/channeltx/modatv/atvmodsettings.h index 2293a8ea5..c54e1d4eb 100644 --- a/plugins/channeltx/modatv/atvmodsettings.h +++ b/plugins/channeltx/modatv/atvmodsettings.h @@ -21,7 +21,7 @@ #include #include -struct Serializable; +class Serializable; struct ATVModSettings { diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp index 4a3c9d825..347385c82 100644 --- a/plugins/channeltx/modnfm/nfmmod.cpp +++ b/plugins/channeltx/modnfm/nfmmod.cpp @@ -179,10 +179,10 @@ void NFMMod::pullAF(Real& sample) { switch (m_settings.m_modAFInput) { - case NFMModInputTone: + case NFMModSettings::NFMModInputTone: sample = m_toneNco.next(); break; - case NFMModInputFile: + case NFMModSettings::NFMModInputFile: // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw if (m_ifstream.is_open()) @@ -211,10 +211,10 @@ void NFMMod::pullAF(Real& sample) sample = 0.0f; } break; - case NFMModInputAudio: + case NFMModSettings::NFMModInputAudio: sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor; break; - case NFMModInputCWTone: + case NFMModSettings::NFMModInputCWTone: Real fadeFactor; if (m_cwKeyer.getSample()) @@ -235,7 +235,7 @@ void NFMMod::pullAF(Real& sample) } } break; - case NFMModInputNone: + case NFMModSettings::NFMModInputNone: default: sample = 0.0f; break; diff --git a/plugins/channeltx/modnfm/nfmmod.h b/plugins/channeltx/modnfm/nfmmod.h index 1a7c84039..9276b10b1 100644 --- a/plugins/channeltx/modnfm/nfmmod.h +++ b/plugins/channeltx/modnfm/nfmmod.h @@ -91,15 +91,6 @@ public: { } }; - typedef enum - { - NFMModInputNone, - NFMModInputTone, - NFMModInputFile, - NFMModInputAudio, - NFMModInputCWTone - } NFMModInputAF; - class MsgConfigureFileSourceName : public Message { MESSAGE_CLASS_DECLARATION @@ -296,7 +287,7 @@ private: quint32 m_recordLength; //!< record length in seconds computed from file size int m_sampleRate; - NFMModInputAF m_afInput; + NFMModSettings::NFMModInputAF m_afInput; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; diff --git a/plugins/channeltx/modnfm/nfmmodgui.h b/plugins/channeltx/modnfm/nfmmodgui.h index d07a7b1a0..8d144480d 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.h +++ b/plugins/channeltx/modnfm/nfmmodgui.h @@ -72,7 +72,7 @@ private: int m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; - NFMMod::NFMModInputAF m_modAFInput; + NFMModSettings::NFMModInputAF m_modAFInput; MessageQueue m_inputMessageQueue; explicit NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); diff --git a/plugins/channeltx/modssb/ssbmodsettings.h b/plugins/channeltx/modssb/ssbmodsettings.h index 516d283e0..2cab6cb18 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.h +++ b/plugins/channeltx/modssb/ssbmodsettings.h @@ -21,7 +21,7 @@ #include #include -struct Serializable; +class Serializable; struct SSBModSettings { diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index f6e01794c..f4741295b 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -255,7 +255,7 @@ void UDPSinkGUI::on_deltaFrequency_changed(qint64 value) void UDPSinkGUI::on_sampleFormat_currentIndexChanged(int index) { - if ((index == (int) UDPSinkSettings::FormatNFM)) { + if (index == (int) UDPSinkSettings::FormatNFM) { ui->fmDeviation->setEnabled(true); } else { ui->fmDeviation->setEnabled(false); diff --git a/plugins/channeltx/udpsink/udpsinksettings.h b/plugins/channeltx/udpsink/udpsinksettings.h index ae44f6b9a..e060752a0 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.h +++ b/plugins/channeltx/udpsink/udpsinksettings.h @@ -21,7 +21,7 @@ #include #include -struct Serializable; +class Serializable; struct UDPSinkSettings { diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp index 594434cf2..a4e721c96 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp @@ -28,7 +28,7 @@ const int SDRdaemonSourceBuffer::m_sampleSize = 2; const int SDRdaemonSourceBuffer::m_iqSampleSize = 2 * m_sampleSize; -SDRdaemonSourceBuffer::SDRdaemonSourceBuffer(uint32_t throttlems) : +SDRdaemonSourceBuffer::SDRdaemonSourceBuffer() : m_decoderIndexHead(nbDecoderSlots/2), m_frameHead(0), m_curNbBlocks(0), @@ -39,7 +39,6 @@ SDRdaemonSourceBuffer::SDRdaemonSourceBuffer(uint32_t throttlems) : m_maxNbRecovery(0), m_framesDecoded(true), m_readIndex(0), - m_throttlemsNominal(throttlems), m_readBuffer(0), m_readSize(0), m_bufferLenSec(0.0f), diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h index 5613ecac0..c051061e9 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h @@ -83,7 +83,7 @@ public: }; #pragma pack(pop) - SDRdaemonSourceBuffer(uint32_t throttlems); + SDRdaemonSourceBuffer(); ~SDRdaemonSourceBuffer(); // R/W operations @@ -207,7 +207,6 @@ private: uint32_t m_tvOut_usec; //!< Estimated returned samples timestamp (microseconds) int m_readNbBytes; //!< Nominal number of bytes per read (50ms) - uint32_t m_throttlemsNominal; //!< Initial throttle in ms uint8_t* m_readBuffer; //!< Read buffer to hold samples when looping back to beginning of raw buffer int m_readSize; //!< Read buffer size diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 5013a3f38..622ccfa3e 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -52,9 +52,7 @@ SDRdaemonSourceInput::SDRdaemonSourceInput(DeviceSourceAPI *deviceAPI) : m_settings(), m_SDRdaemonUDPHandler(0), m_deviceDescription(), - m_startingTimeStamp(0), - m_autoFollowRate(false), - m_autoCorrBuffer(false) + m_startingTimeStamp(0) { m_sender = nn_socket(AF_SP, NN_PAIR); assert(m_sender != -1); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h index 8e640428c..0de1e372b 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h @@ -297,8 +297,6 @@ private: int m_sender; QString m_deviceDescription; std::time_t m_startingTimeStamp; - bool m_autoFollowRate; - bool m_autoCorrBuffer; FileRecord *m_fileSink; //!< File sink to record device I/Q output void applySettings(const SDRdaemonSourceSettings& settings, bool force = false); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index 5ae6f0778..5110e75ea 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -32,7 +32,6 @@ SDRdaemonSourceUDPHandler::SDRdaemonSourceUDPHandler(SampleSinkFifo *sampleFifo, m_masterTimerConnected(false), m_running(false), m_rateDivider(1000/SDRDAEMONSOURCE_THROTTLE_MS), - m_sdrDaemonBuffer(m_rateDivider), m_dataSocket(0), m_dataAddress(QHostAddress::LocalHost), m_remoteAddress(QHostAddress::LocalHost), diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 0c50ca5ef..faedb146f 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -16925,7 +16925,7 @@ except ApiException as e:
- Generated 2018-02-20T20:02:08.315+01:00 + Generated 2018-03-03T23:35:13.013+01:00
diff --git a/sdrbase/util/bitfieldindex.h b/sdrbase/util/bitfieldindex.h index e1c8d93a3..0a4b00ac1 100644 --- a/sdrbase/util/bitfieldindex.h +++ b/sdrbase/util/bitfieldindex.h @@ -45,9 +45,19 @@ struct BitfieldIndex }; template -BitfieldIndex operator+(const BitfieldIndex &a, const BitfieldIndex &b) { BitfieldIndex x; x.v = x.a + x.b; return x; } +BitfieldIndex operator+(const BitfieldIndex &a, const BitfieldIndex &b) +{ + BitfieldIndex x; + x.v = a.v + b.v; + return x; +} template -BitfieldIndex operator-(const BitfieldIndex &a, const BitfieldIndex &b) { BitfieldIndex x; x.v = x.a - x.b; return x; } +BitfieldIndex operator-(const BitfieldIndex &a, const BitfieldIndex &b) +{ + BitfieldIndex x; + x.v = a.v - b.v; + return x; +} #endif // _UTIL_BITFIELDINDEX_H_ diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index 19195f4d6..f44485055 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -51,8 +51,6 @@ ScopeVisNG::ScopeVisNG(GLScopeNG* glScope) : m_timeBase(1), m_timeOfsProMill(0), m_traceStart(true), - m_traceFill(0), - m_zTraceIndex(-1), m_sampleRate(0), m_traceDiscreteMemory(m_nbTraceMemories), m_freeRun(true), diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index 0cbbb64b9..1c723a9ad 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -1033,8 +1033,6 @@ private: uint32_t m_timeBase; //!< Trace display time divisor uint32_t m_timeOfsProMill; //!< Start trace shift in 1/1000 trace size bool m_traceStart; //!< Trace is at start point - int m_traceFill; //!< Count of samples accumulated into trace - int m_zTraceIndex; //!< Index of the trace used for Z input (luminance or false colors) SampleVector::const_iterator m_triggerPoint; //!< Trigger start location in the samples vector int m_sampleRate; TraceBackDiscreteMemory m_traceDiscreteMemory; //!< Complex trace memory for triggered states TODO: vectorize when more than on input is allowed diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 0c50ca5ef..faedb146f 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -16925,7 +16925,7 @@ except ApiException as e:
- Generated 2018-02-20T20:02:08.315+01:00 + Generated 2018-03-03T23:35:13.013+01:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h index 7bcc3d58d..26d1f18f3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGAudioDevice* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAudioDevice* fromJson(QString &jsonString) override; QString* getName(); void setName(QString* name); diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h index 985c3b2ff..ef6adb324 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h @@ -37,10 +37,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGAudioDevices* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAudioDevices* fromJson(QString &jsonString) override; float getInputVolume(); void setInputVolume(float input_volume); diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h index c7698ddab..72badf455 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h @@ -35,10 +35,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGAudioDevicesSelect* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAudioDevicesSelect* fromJson(QString &jsonString) override; float getInputVolume(); void setInputVolume(float input_volume); diff --git a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h index a73fb536d..4c5550b96 100644 --- a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGCWKeyerSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGCWKeyerSettings* fromJson(QString &jsonString) override; qint32 getSampleRate(); void setSampleRate(qint32 sample_rate); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannel.h b/swagger/sdrangel/code/qt5/client/SWGChannel.h index 48ab8a6dd..315b4b958 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannel.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannel.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGChannel* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGChannel* fromJson(QString &jsonString) override; qint32 getIndex(); void setIndex(qint32 index); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h index d3069e8c6..71da54406 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGChannelListItem* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGChannelListItem* fromJson(QString &jsonString) override; QString* getName(); void setName(QString* name); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 6f2132964..d1c9efec9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -38,10 +38,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGChannelSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGChannelSettings* fromJson(QString &jsonString) override; QString* getChannelType(); void setChannelType(QString* channel_type); diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h index 295be16ee..06415c4cf 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h @@ -37,10 +37,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGDVSeralDevices* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDVSeralDevices* fromJson(QString &jsonString) override; qint32 getNbDevices(); void setNbDevices(qint32 nb_devices); diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h index 8adcf632b..a761de4a4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGDVSerialDevice* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDVSerialDevice* fromJson(QString &jsonString) override; QString* getDeviceName(); void setDeviceName(QString* device_name); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h index b6fd960d0..51a5e56d2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGDeviceListItem* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDeviceListItem* fromJson(QString &jsonString) override; QString* getDisplayedName(); void setDisplayedName(QString* displayed_name); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h index fb6bcb931..f12f08ca9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h @@ -38,10 +38,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGDeviceSet* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDeviceSet* fromJson(QString &jsonString) override; SWGSamplingDevice* getSamplingDevice(); void setSamplingDevice(SWGSamplingDevice* sampling_device); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h index 6c0980081..6f8f518d0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h @@ -37,10 +37,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGDeviceSetList* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDeviceSetList* fromJson(QString &jsonString) override; qint32 getDevicesetcount(); void setDevicesetcount(qint32 devicesetcount); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index a690c7ba8..1f926232b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -42,10 +42,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGDeviceSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDeviceSettings* fromJson(QString &jsonString) override; QString* getDeviceHwType(); void setDeviceHwType(QString* device_hw_type); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceState.h b/swagger/sdrangel/code/qt5/client/SWGDeviceState.h index 5741bea1b..9978df7f5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceState.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceState.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGDeviceState* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDeviceState* fromJson(QString &jsonString) override; QString* getState(); void setState(QString* state); diff --git a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h index 3b90da05a..22e1db85b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGErrorResponse* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGErrorResponse* fromJson(QString &jsonString) override; QString* getMessage(); void setMessage(QString* message); diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h index 9f83483a0..248bc61e9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGFileSourceSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGFileSourceSettings* fromJson(QString &jsonString) override; QString* getFileName(); void setFileName(QString* file_name); diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h index 5a7bed901..c2be99dec 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h @@ -35,10 +35,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGHackRFInputSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGHackRFInputSettings* fromJson(QString &jsonString) override; qint64 getCenterFrequency(); void setCenterFrequency(qint64 center_frequency); diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h index eba2f71c9..2511f50f7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h @@ -35,10 +35,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGHackRFOutputSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGHackRFOutputSettings* fromJson(QString &jsonString) override; qint64 getCenterFrequency(); void setCenterFrequency(qint64 center_frequency); diff --git a/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp b/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp index d7041acca..1c2d92e2f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp @@ -447,7 +447,7 @@ toJsonArray(QList* value, QJsonObject* output, QString innerName, QString if(innerType.startsWith("SWG")){ for(void* obj : *value) { SWGObject *SWGobject = reinterpret_cast(obj); - if(SWGobject != nullptr) + if(SWGobject != nullptr) { QJsonObject* o = SWGobject->asJsonObject(); outputarray.append(*o); diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h index 6da513791..ca26f9fa8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h @@ -37,10 +37,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGInstanceChannelsResponse* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGInstanceChannelsResponse* fromJson(QString &jsonString) override; qint32 getChannelcount(); void setChannelcount(qint32 channelcount); diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h index dcbbdaebc..85f4622cf 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h @@ -37,10 +37,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGInstanceDevicesResponse* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGInstanceDevicesResponse* fromJson(QString &jsonString) override; qint32 getDevicecount(); void setDevicecount(qint32 devicecount); diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h index f588775b0..9421a56ad 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h @@ -38,10 +38,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGInstanceSummaryResponse* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGInstanceSummaryResponse* fromJson(QString &jsonString) override; QString* getVersion(); void setVersion(QString* version); diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h index f78262bd1..ef2e7e036 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h @@ -35,10 +35,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGLimeSdrInputSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGLimeSdrInputSettings* fromJson(QString &jsonString) override; qint64 getCenterFrequency(); void setCenterFrequency(qint64 center_frequency); diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h index e9c552dfd..713090459 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h @@ -35,10 +35,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGLimeSdrOutputSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGLimeSdrOutputSettings* fromJson(QString &jsonString) override; qint64 getCenterFrequency(); void setCenterFrequency(qint64 center_frequency); diff --git a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h index d9456c236..45f21c6c3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h +++ b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h @@ -35,10 +35,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGLocationInformation* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGLocationInformation* fromJson(QString &jsonString) override; float getLatitude(); void setLatitude(float latitude); diff --git a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h index 3219d498a..7a9ca9cbc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h +++ b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGLoggingInfo* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGLoggingInfo* fromJson(QString &jsonString) override; QString* getConsoleLevel(); void setConsoleLevel(QString* console_level); diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h index 879cd04db..ba483569e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGNFMDemodSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGNFMDemodSettings* fromJson(QString &jsonString) override; qint64 getInputFrequencyOffset(); void setInputFrequencyOffset(qint64 input_frequency_offset); diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h index f51fb8057..26adc9fc7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h @@ -37,10 +37,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGNFMModSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGNFMModSettings* fromJson(QString &jsonString) override; qint64 getInputFrequencyOffset(); void setInputFrequencyOffset(qint64 input_frequency_offset); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetExport.h b/swagger/sdrangel/code/qt5/client/SWGPresetExport.h index cc836b90a..60e393c87 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetExport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetExport.h @@ -37,10 +37,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGPresetExport* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPresetExport* fromJson(QString &jsonString) override; QString* getFilePath(); void setFilePath(QString* file_path); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h index 609592077..22e678ef0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h @@ -38,10 +38,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGPresetGroup* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPresetGroup* fromJson(QString &jsonString) override; QString* getGroupName(); void setGroupName(QString* group_name); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h index 32043025c..891190190 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGPresetIdentifier* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPresetIdentifier* fromJson(QString &jsonString) override; QString* getGroupName(); void setGroupName(QString* group_name); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetImport.h b/swagger/sdrangel/code/qt5/client/SWGPresetImport.h index 692532764..d228b8ae9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetImport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetImport.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGPresetImport* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPresetImport* fromJson(QString &jsonString) override; QString* getGroupName(); void setGroupName(QString* group_name); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetItem.h b/swagger/sdrangel/code/qt5/client/SWGPresetItem.h index 860fb29d4..4242fce2b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetItem.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGPresetItem* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPresetItem* fromJson(QString &jsonString) override; qint64 getCenterFrequency(); void setCenterFrequency(qint64 center_frequency); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h index e66bb6207..8a81e0f79 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGPresetTransfer* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPresetTransfer* fromJson(QString &jsonString) override; qint32 getDeviceSetIndex(); void setDeviceSetIndex(qint32 device_set_index); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresets.h b/swagger/sdrangel/code/qt5/client/SWGPresets.h index c8c0268da..4b138082f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresets.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresets.h @@ -37,10 +37,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGPresets* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPresets* fromJson(QString &jsonString) override; qint32 getNbGroups(); void setNbGroups(qint32 nb_groups); diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h index 8f41d2a00..9cd27d8a3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h @@ -35,10 +35,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGRtlSdrSettings* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRtlSdrSettings* fromJson(QString &jsonString) override; qint32 getDevSampleRate(); void setDevSampleRate(qint32 dev_sample_rate); diff --git a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h index e76d032bc..04eaa2400 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGSamplingDevice* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSamplingDevice* fromJson(QString &jsonString) override; qint32 getIndex(); void setIndex(qint32 index); diff --git a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h index 760e09765..7ea7ec7ab 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h @@ -36,10 +36,10 @@ public: void init(); void cleanup(); - QString asJson (); - QJsonObject* asJsonObject(); - void fromJsonObject(QJsonObject &json); - SWGSuccessResponse* fromJson(QString &jsonString); + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSuccessResponse* fromJson(QString &jsonString) override; QString* getMessage(); void setMessage(QString* message); From c062c99c2a6853902898a96c685a6faf61194666 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Mar 2018 02:35:15 +0100 Subject: [PATCH 051/956] DATV demod: tried to fix segfault on avcodec_close() --- plugins/channelrx/demoddatv/datvideorender.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvideorender.cpp b/plugins/channelrx/demoddatv/datvideorender.cpp index efe47e4dc..c1e9b9525 100644 --- a/plugins/channelrx/demoddatv/datvideorender.cpp +++ b/plugins/channelrx/demoddatv/datvideorender.cpp @@ -561,8 +561,9 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) //Only once execution m_blnRunning=true; - avformat_close_input(&m_objFormatCtx); - m_objFormatCtx=NULL; + // maybe done in the avcodec_close +// avformat_close_input(&m_objFormatCtx); +// m_objFormatCtx=NULL; if(m_objDecoderCtx) { From 503c6093b9ac2d7492c93c7c0dec7f2af82973c5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Mar 2018 04:00:02 +0100 Subject: [PATCH 052/956] DATV demod: removed using namespace leansdr --- plugins/channelrx/demoddatv/datvdemod.cpp | 102 ++++++++-------- plugins/channelrx/demoddatv/datvdemod.h | 118 +++++++++---------- plugins/channelrx/demoddatv/datvdemodgui.cpp | 40 +++---- 3 files changed, 129 insertions(+), 131 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 8f7a28059..d67be7c15 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -182,7 +182,7 @@ void DATVDemod::configure(MessageQueue* objMessageQueue, int intCenterFrequency, dvb_version enmStandard, DATVModulation enmModulation, - code_rate enmFEC, + leansdr::code_rate enmFEC, int intSymbolRate, int intNotchFilters, bool blnAllowDrift, @@ -202,7 +202,7 @@ void DATVDemod::InitDATVParameters(int intMsps, int intCenterFrequency, dvb_version enmStandard, DATVModulation enmModulation, - code_rate enmFEC, + leansdr::code_rate enmFEC, int intSampleRate, int intSymbolRate, int intNotchFilters, @@ -472,43 +472,43 @@ void DATVDemod::InitDATVFramework() switch(m_objRunning.enmModulation) { case BPSK: - m_objCfg.constellation = cstln_lut<256>::BPSK; + m_objCfg.constellation = leansdr::cstln_lut<256>::BPSK; break; case QPSK: - m_objCfg.constellation = cstln_lut<256>::QPSK; + m_objCfg.constellation = leansdr::cstln_lut<256>::QPSK; break; case PSK8: - m_objCfg.constellation = cstln_lut<256>::PSK8; + m_objCfg.constellation = leansdr::cstln_lut<256>::PSK8; break; case APSK16: - m_objCfg.constellation = cstln_lut<256>::APSK16; + m_objCfg.constellation = leansdr::cstln_lut<256>::APSK16; break; case APSK32: - m_objCfg.constellation = cstln_lut<256>::APSK32; + m_objCfg.constellation = leansdr::cstln_lut<256>::APSK32; break; case APSK64E: - m_objCfg.constellation = cstln_lut<256>::APSK64E; + m_objCfg.constellation = leansdr::cstln_lut<256>::APSK64E; break; case QAM16: - m_objCfg.constellation = cstln_lut<256>::QAM16; + m_objCfg.constellation = leansdr::cstln_lut<256>::QAM16; break; case QAM64: - m_objCfg.constellation = cstln_lut<256>::QAM64; + m_objCfg.constellation = leansdr::cstln_lut<256>::QAM64; break; case QAM256: - m_objCfg.constellation = cstln_lut<256>::QAM256; + m_objCfg.constellation = leansdr::cstln_lut<256>::QAM256; break; default: - m_objCfg.constellation = cstln_lut<256>::BPSK; + m_objCfg.constellation = leansdr::cstln_lut<256>::BPSK; break; } @@ -550,19 +550,19 @@ void DATVDemod::InitDATVFramework() m_lngExpectedReadIQ = BUF_BASEBAND; - m_objScheduler = new scheduler(); + m_objScheduler = new leansdr::scheduler(); //*************** - p_rawiq = new pipebuf(m_objScheduler, "rawiq", BUF_BASEBAND); - p_rawiq_writer = new pipewriter(*p_rawiq); + p_rawiq = new leansdr::pipebuf(m_objScheduler, "rawiq", BUF_BASEBAND); + p_rawiq_writer = new leansdr::pipewriter(*p_rawiq); p_preprocessed = p_rawiq; // NOTCH FILTER if ( m_objCfg.anf>0 ) { - p_autonotched = new pipebuf(m_objScheduler, "autonotched", BUF_BASEBAND); - r_auto_notch = new auto_notch(m_objScheduler, *p_preprocessed, *p_autonotched, m_objCfg.anf, 0); + p_autonotched = new leansdr::pipebuf(m_objScheduler, "autonotched", BUF_BASEBAND); + r_auto_notch = new leansdr::auto_notch(m_objScheduler, *p_preprocessed, *p_autonotched, m_objCfg.anf, 0); p_preprocessed = p_autonotched; } @@ -573,11 +573,11 @@ void DATVDemod::InitDATVFramework() // CNR ESTIMATION - p_cnr = new pipebuf(m_objScheduler, "cnr", BUF_SLOW); + p_cnr = new leansdr::pipebuf(m_objScheduler, "cnr", BUF_SLOW); if ( m_objCfg.cnr==true ) { - r_cnr = new cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm/m_objCfg.Fs); + r_cnr = new leansdr::cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm/m_objCfg.Fs); r_cnr->decimation = decimation(m_objCfg.Fs, 1); // 1 Hz } @@ -598,20 +598,20 @@ void DATVDemod::InitDATVFramework() // Generic constellation receiver - p_symbols = new pipebuf(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS); - p_freq = new pipebuf (m_objScheduler, "freq", BUF_SLOW); - p_ss = new pipebuf (m_objScheduler, "SS", BUF_SLOW); - p_mer = new pipebuf (m_objScheduler, "MER", BUF_SLOW); - p_sampled = new pipebuf (m_objScheduler, "PSK symbols", BUF_BASEBAND); + p_symbols = new leansdr::pipebuf(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS); + p_freq = new leansdr::pipebuf (m_objScheduler, "freq", BUF_SLOW); + p_ss = new leansdr::pipebuf (m_objScheduler, "SS", BUF_SLOW); + p_mer = new leansdr::pipebuf (m_objScheduler, "MER", BUF_SLOW); + p_sampled = new leansdr::pipebuf (m_objScheduler, "PSK symbols", BUF_BASEBAND); switch ( m_objCfg.sampler ) { case SAMP_NEAREST: - sampler = new nearest_sampler(); + sampler = new leansdr::nearest_sampler(); break; case SAMP_LINEAR: - sampler = new linear_sampler(); + sampler = new leansdr::linear_sampler(); break; case SAMP_RRC: @@ -621,15 +621,15 @@ void DATVDemod::InitDATVFramework() if ( m_objCfg.rrc_steps == 0 ) { // At least 64 discrete sampling points between symbols - m_objCfg.rrc_steps = max(1, (int)(64*m_objCfg.Fm / m_objCfg.Fs)); + m_objCfg.rrc_steps = std::max(1, (int)(64*m_objCfg.Fm / m_objCfg.Fs)); } float Frrc = m_objCfg.Fs * m_objCfg.rrc_steps; // Sample freq of the RRC filter float transition = (m_objCfg.Fm/2) * m_objCfg.rolloff; int order = m_objCfg.rrc_rej * Frrc / (22*transition); - ncoeffs_sampler = filtergen::root_raised_cosine(order, m_objCfg.Fm/Frrc, m_objCfg.rolloff, &coeffs_sampler); + ncoeffs_sampler = leansdr::filtergen::root_raised_cosine(order, m_objCfg.Fm/Frrc, m_objCfg.rolloff, &coeffs_sampler); - sampler = new fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); + sampler = new leansdr::fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); break; } @@ -638,11 +638,11 @@ void DATVDemod::InitDATVFramework() return; } - m_objDemodulator = new cstln_receiver(m_objScheduler, sampler, *p_preprocessed, *p_symbols, p_freq, p_ss, p_mer, p_sampled); + m_objDemodulator = new leansdr::cstln_receiver(m_objScheduler, sampler, *p_preprocessed, *p_symbols, p_freq, p_ss, p_mer, p_sampled); if ( m_objCfg.standard == DVB_S ) { - if ( m_objCfg.constellation != cstln_lut<256>::QPSK && m_objCfg.constellation != cstln_lut<256>::BPSK ) + if ( m_objCfg.constellation != leansdr::cstln_lut<256>::QPSK && m_objCfg.constellation != leansdr::cstln_lut<256>::BPSK ) { fprintf(stderr, "Warning: non-standard constellation for DVB-S\n"); } @@ -696,13 +696,13 @@ void DATVDemod::InitDATVFramework() m_objRegisteredDATVScreen->resizeDATVScreen(256,256); - r_scope_symbols = new datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredDATVScreen); + r_scope_symbols = new leansdr::datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredDATVScreen); r_scope_symbols->decimation = 1; r_scope_symbols->cstln = &m_objDemodulator->cstln; // DECONVOLUTION AND SYNCHRONIZATION - p_bytes = new pipebuf(m_objScheduler, "bytes", BUF_BYTES); + p_bytes = new leansdr::pipebuf(m_objScheduler, "bytes", BUF_BYTES); r_deconv = NULL; @@ -710,13 +710,13 @@ void DATVDemod::InitDATVFramework() if ( m_objCfg.viterbi ) { - if ( m_objCfg.fec==FEC23 && (m_objDemodulator->cstln->nsymbols==4 || m_objDemodulator->cstln->nsymbols==64) ) + if ( m_objCfg.fec == leansdr::FEC23 && (m_objDemodulator->cstln->nsymbols == 4 || m_objDemodulator->cstln->nsymbols == 64) ) { - m_objCfg.fec = FEC46; + m_objCfg.fec = leansdr::FEC46; } //To uncomment -> Linking Problem : undefined symbol: _ZN7leansdr21viterbi_dec_interfaceIhhiiE6updateEPiS2_ - r = new viterbi_sync(m_objScheduler, (*p_symbols), (*p_bytes), m_objDemodulator->cstln, m_objCfg.fec); + r = new leansdr::viterbi_sync(m_objScheduler, (*p_symbols), (*p_bytes), m_objDemodulator->cstln, m_objCfg.fec); if ( m_objCfg.fastlock ) { @@ -731,25 +731,25 @@ void DATVDemod::InitDATVFramework() //******* -> if ( m_objCfg.hdlc ) - p_mpegbytes = new pipebuf (m_objScheduler, "mpegbytes", BUF_MPEGBYTES); - p_lock = new pipebuf (m_objScheduler, "lock", BUF_SLOW); - p_locktime = new pipebuf (m_objScheduler, "locktime", BUF_PACKETS); + p_mpegbytes = new leansdr::pipebuf (m_objScheduler, "mpegbytes", BUF_MPEGBYTES); + p_lock = new leansdr::pipebuf (m_objScheduler, "lock", BUF_SLOW); + p_locktime = new leansdr::pipebuf (m_objScheduler, "locktime", BUF_PACKETS); - r_sync_mpeg = new mpeg_sync(m_objScheduler, *p_bytes, *p_mpegbytes, r_deconv, p_lock, p_locktime); + r_sync_mpeg = new leansdr::mpeg_sync(m_objScheduler, *p_bytes, *p_mpegbytes, r_deconv, p_lock, p_locktime); r_sync_mpeg->fastlock = m_objCfg.fastlock; // DEINTERLEAVING - p_rspackets = new pipebuf< rspacket >(m_objScheduler, "RS-enc packets", BUF_PACKETS); - r_deinter = new deinterleaver(m_objScheduler, *p_mpegbytes, *p_rspackets); + p_rspackets = new leansdr::pipebuf< leansdr::rspacket >(m_objScheduler, "RS-enc packets", BUF_PACKETS); + r_deinter = new leansdr::deinterleaver(m_objScheduler, *p_mpegbytes, *p_rspackets); // REED-SOLOMON - p_vbitcount = new pipebuf(m_objScheduler, "Bits processed", BUF_PACKETS); - p_verrcount = new pipebuf(m_objScheduler, "Bits corrected", BUF_PACKETS); - p_rtspackets = new pipebuf(m_objScheduler, "rand TS packets", BUF_PACKETS); - r_rsdec = new rs_decoder (m_objScheduler, *p_rspackets, *p_rtspackets, p_vbitcount, p_verrcount); + p_vbitcount = new leansdr::pipebuf(m_objScheduler, "Bits processed", BUF_PACKETS); + p_verrcount = new leansdr::pipebuf(m_objScheduler, "Bits corrected", BUF_PACKETS); + p_rtspackets = new leansdr::pipebuf(m_objScheduler, "rand TS packets", BUF_PACKETS); + r_rsdec = new leansdr::rs_decoder (m_objScheduler, *p_rspackets, *p_rtspackets, p_vbitcount, p_verrcount); // BER ESTIMATION @@ -768,12 +768,12 @@ void DATVDemod::InitDATVFramework() // DERANDOMIZATION - p_tspackets = new pipebuf(m_objScheduler, "TS packets", BUF_PACKETS); - r_derand = new derandomizer(m_objScheduler, *p_rtspackets, *p_tspackets); + p_tspackets = new leansdr::pipebuf(m_objScheduler, "TS packets", BUF_PACKETS); + r_derand = new leansdr::derandomizer(m_objScheduler, *p_rtspackets, *p_tspackets); // OUTPUT - r_videoplayer = new datvvideoplayer(m_objScheduler, *p_tspackets,m_objVideoStream); + r_videoplayer = new leansdr::datvvideoplayer(m_objScheduler, *p_tspackets,m_objVideoStream); m_blnDVBInitialized=true; } @@ -783,7 +783,7 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect qint16 * ptrBufferToRelease=NULL; float fltI; float fltQ; - cf32 objIQ; + leansdr::cf32 objIQ; //Complex objC; fftfilt::cmplx *objRF; int intRFOut; @@ -866,7 +866,7 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect m_lngReadIQ=0; delete p_rawiq_writer; - p_rawiq_writer = new pipewriter(*p_rawiq); + p_rawiq_writer = new leansdr::pipewriter(*p_rawiq); } } diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index be7906145..815407ef3 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -66,13 +66,11 @@ class DownChannelizer; #include "datvideostream.h" #include "datvideorender.h" -using namespace leansdr; - enum DATVModulation { BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 }; enum dvb_version { DVB_S, DVB_S2 }; enum dvb_sampler { SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC }; -inline int decimation(float Fin, float Fout) { int d = Fin / Fout; return max(d, 1); } +inline int decimation(float Fin, float Fout) { int d = Fin / Fout; return std::max(d, 1); } struct config { @@ -86,8 +84,8 @@ struct config bool cnr; // Measure CNR unsigned int decim; // Decimation, 0=auto float Fm; // QPSK symbol rate (Hz) - cstln_lut<256>::predef constellation; - code_rate fec; + leansdr::cstln_lut<256>::predef constellation; + leansdr::code_rate fec; float Ftune; // Bias frequency for the QPSK demodulator (Hz) bool allow_drift; bool fastlock; @@ -108,12 +106,12 @@ struct config buf_factor(4), Fs(2.4e6), Fderot(0), - anf(1), + anf(0), cnr(false), decim(0), Fm(2e6), - constellation(cstln_lut<256>::QPSK), - fec(FEC12), + constellation(leansdr::cstln_lut<256>::QPSK), + fec(leansdr::FEC12), Ftune(0), allow_drift(false), fastlock(true), @@ -139,7 +137,7 @@ struct DATVConfig int intCenterFrequency; dvb_version enmStandard; DATVModulation enmModulation; - code_rate enmFEC; + leansdr::code_rate enmFEC; int intSampleRate; int intSymbolRate; int intNotchFilters; @@ -157,7 +155,7 @@ struct DATVConfig intCenterFrequency(0), enmStandard(DVB_S), enmModulation(BPSK), - enmFEC(FEC12), + enmFEC(leansdr::FEC12), intSampleRate(1024000), intSymbolRate(250000), intNotchFilters(1), @@ -196,7 +194,7 @@ public: int intCenterFrequency, dvb_version enmStandard, DATVModulation enmModulation, - code_rate enmFEC, + leansdr::code_rate enmFEC, int intSymbolRate, int intNotchFilters, bool blnAllowDrift, @@ -223,7 +221,7 @@ public: int intCenterFrequency, dvb_version enmStandard, DATVModulation enmModulation, - code_rate enmFEC, + leansdr::code_rate enmFEC, int intSampleRate, int intSymbolRate, int intNotchFilters, @@ -278,7 +276,7 @@ private: int intCenterFrequency, dvb_version enmStandard, DATVModulation enmModulation, - code_rate enmFEC, + leansdr::code_rate enmFEC, int intSymbolRate, int intNotchFilters, bool blnAllowDrift, @@ -300,7 +298,7 @@ private: int intCenterFrequency, dvb_version enmStandard, DATVModulation enmModulation, - code_rate enmFEC, + leansdr::code_rate enmFEC, int intSymbolRate, int intNotchFilters, bool blnAllowDrift, @@ -343,7 +341,7 @@ private: //************** LEANDBV Scheduler *************** - scheduler * m_objScheduler; + leansdr::scheduler * m_objScheduler; struct config m_objCfg; bool m_blnDVBInitialized; @@ -352,90 +350,90 @@ private: //LeanSDR Pipe Buffer // INPUT - pipebuf *p_rawiq; - pipewriter *p_rawiq_writer; - pipebuf *p_preprocessed; + leansdr::pipebuf *p_rawiq; + leansdr::pipewriter *p_rawiq_writer; + leansdr::pipebuf *p_preprocessed; // NOTCH FILTER - auto_notch *r_auto_notch; - pipebuf *p_autonotched; + leansdr::auto_notch *r_auto_notch; + leansdr::pipebuf *p_autonotched; // FREQUENCY CORRECTION : DEROTATOR - pipebuf *p_derot; - rotator *r_derot; + leansdr::pipebuf *p_derot; + leansdr::rotator *r_derot; // CNR ESTIMATION - pipebuf *p_cnr; - cnr_fft *r_cnr; + leansdr::pipebuf *p_cnr; + leansdr::cnr_fft *r_cnr; //FILTERING - fir_filter *r_resample; - pipebuf *p_resampled; + leansdr::fir_filter *r_resample; + leansdr::pipebuf *p_resampled; float *coeffs; int ncoeffs; // OUTPUT PREPROCESSED DATA - sampler_interface *sampler; + leansdr::sampler_interface *sampler; float *coeffs_sampler; int ncoeffs_sampler; - pipebuf *p_symbols; - pipebuf *p_freq; - pipebuf *p_ss; - pipebuf *p_mer; - pipebuf *p_sampled; + leansdr::pipebuf *p_symbols; + leansdr::pipebuf *p_freq; + leansdr::pipebuf *p_ss; + leansdr::pipebuf *p_mer; + leansdr::pipebuf *p_sampled; //DECIMATION - pipebuf *p_decimated; - decimator *p_decim; + leansdr::pipebuf *p_decimated; + leansdr::decimator *p_decim; //PROCESSED DATA MONITORING - file_writer *r_ppout; + leansdr::file_writer *r_ppout; //GENERIC CONSTELLATION RECEIVER - cstln_receiver *m_objDemodulator; + leansdr::cstln_receiver *m_objDemodulator; // DECONVOLUTION AND SYNCHRONIZATION - pipebuf *p_bytes; - deconvol_sync_simple *r_deconv; - viterbi_sync *r; - pipebuf *p_descrambled; - pipebuf *p_frames; + leansdr::pipebuf *p_bytes; + leansdr::deconvol_sync_simple *r_deconv; + leansdr::viterbi_sync *r; + leansdr::pipebuf *p_descrambled; + leansdr::pipebuf *p_frames; - etr192_descrambler * r_etr192_descrambler; - hdlc_sync *r_sync; + leansdr::etr192_descrambler * r_etr192_descrambler; + leansdr::hdlc_sync *r_sync; - pipebuf *p_mpegbytes; - pipebuf *p_lock; - pipebuf *p_locktime; - mpeg_sync *r_sync_mpeg; + leansdr::pipebuf *p_mpegbytes; + leansdr::pipebuf *p_lock; + leansdr::pipebuf *p_locktime; + leansdr::mpeg_sync *r_sync_mpeg; // DEINTERLEAVING - pipebuf< rspacket > *p_rspackets; - deinterleaver *r_deinter; + leansdr::pipebuf > *p_rspackets; + leansdr::deinterleaver *r_deinter; // REED-SOLOMON - pipebuf *p_vbitcount; - pipebuf *p_verrcount; - pipebuf *p_rtspackets; - rs_decoder *r_rsdec; + leansdr::pipebuf *p_vbitcount; + leansdr::pipebuf *p_verrcount; + leansdr::pipebuf *p_rtspackets; + leansdr::rs_decoder *r_rsdec; // BER ESTIMATION - pipebuf *p_vber; - rate_estimator *r_vber; + leansdr::pipebuf *p_vber; + leansdr::rate_estimator *r_vber; // DERANDOMIZATION - pipebuf *p_tspackets; - derandomizer *r_derand; + leansdr::pipebuf *p_tspackets; + leansdr::derandomizer *r_derand; //OUTPUT - file_writer *r_stdout; - datvvideoplayer *r_videoplayer; + leansdr::file_writer *r_stdout; + leansdr::datvvideoplayer *r_videoplayer; //CONSTELLATION - datvconstellation *r_scope_symbols; + leansdr::datvconstellation *r_scope_symbols; DeviceSourceAPI* m_deviceAPI; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index aa23c85e9..7fab97e52 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -87,7 +87,7 @@ void DATVDemodGUI::resetToDefaults() ui->cmbFilter->setCurrentIndex(0); displayRRCParameters(false); - ui->spiNotchFilters->setValue(1); + ui->spiNotchFilters->setValue(0); ui->prgSynchro->setValue(0); ui->lblStatus->setText(""); @@ -191,7 +191,7 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) d.readS32(11, &tmp, 0); ui->cmbStandard->setCurrentIndex(tmp); - d.readS32(12, &tmp, 1); + d.readS32(12, &tmp, 0); ui->spiNotchFilters->setValue(tmp); d.readS32(13, &tmp, 1024000); @@ -320,7 +320,7 @@ void DATVDemodGUI::applySettings() DATVModulation enmSelectedModulation; dvb_version enmVersion; - code_rate enmFEC; + leansdr::code_rate enmFEC; dvb_sampler enmSampler; if (m_blnDoApplySettings) @@ -403,41 +403,41 @@ void DATVDemodGUI::applySettings() strFEC = ui->cmbFEC->currentText(); - if(strFEC=="1/2") + if(strFEC == "1/2") { - enmFEC=FEC12; + enmFEC = leansdr::FEC12; } - else if(strFEC=="2/3") + else if(strFEC == "2/3") { - enmFEC=FEC23; + enmFEC = leansdr::FEC23; } - else if(strFEC=="3/4") + else if(strFEC == "3/4") { - enmFEC=FEC34; + enmFEC = leansdr::FEC34; } - else if(strFEC=="5/6") + else if(strFEC == "5/6") { - enmFEC=FEC56; + enmFEC = leansdr::FEC56; } - else if(strFEC=="7/8") + else if(strFEC == "7/8") { - enmFEC=FEC78; + enmFEC = leansdr::FEC78; } - else if(strFEC=="4/5") + else if(strFEC == "4/5") { - enmFEC=FEC45; + enmFEC = leansdr::FEC45; } - else if(strFEC=="8/9") + else if(strFEC == "8/9") { - enmFEC=FEC89; + enmFEC = leansdr::FEC89; } - else if(strFEC=="9/10") + else if(strFEC == "9/10") { - enmFEC=FEC910; + enmFEC = leansdr::FEC910; } else { - enmFEC=FEC12; + enmFEC = leansdr::FEC12; } if (ui->cmbFilter->currentIndex()==0) From afe3fbabd3e0ef5e475025a3d2f9eaab880a77f9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Mar 2018 01:17:19 +0100 Subject: [PATCH 053/956] qrtplib: new RTPUDPTransmitter --- qrtplib/CMakeLists.txt | 2 + qrtplib/rtcpcompoundpacket.cpp | 21 +- qrtplib/rtcpcompoundpacket.h | 9 +- qrtplib/rtpaddress.cpp | 5 + qrtplib/rtpaddress.h | 3 + qrtplib/rtpinternalutils.h | 10 +- qrtplib/rtppacket.cpp | 96 ++++- qrtplib/rtprawpacket.h | 59 +-- qrtplib/rtpsession.cpp | 760 +++++++++++++-------------------- qrtplib/rtpsession.h | 22 +- qrtplib/rtpsources.cpp | 8 +- qrtplib/rtptransmitter.h | 43 +- qrtplib/rtpudptransmitter.cpp | 373 ++++++++++++++-- qrtplib/rtpudptransmitter.h | 158 +++---- 14 files changed, 835 insertions(+), 734 deletions(-) diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 0ddab281d..979fd508a 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -42,6 +42,7 @@ set (qrtplib_HEADERS rtptransmitter.h rtptypes_win.h rtptypes.h + rtpudpransmitter.h # rtpudpv4transmitter.h # rtpudpv4transmitternobind.h # rtpexternaltransmitter.h @@ -82,6 +83,7 @@ set(qrtplib_SOURCES rtpsourcedata.cpp rtpsources.cpp rtptimeutilities.cpp + rtpudptransmitter.cpp # rtpudpv4transmitter.cpp # rtpudpv4transmitternobind.cpp # rtpexternaltransmitter.cpp diff --git a/qrtplib/rtcpcompoundpacket.cpp b/qrtplib/rtcpcompoundpacket.cpp index 959cd978d..23d8ea179 100644 --- a/qrtplib/rtcpcompoundpacket.cpp +++ b/qrtplib/rtcpcompoundpacket.cpp @@ -61,30 +61,30 @@ RTCPCompoundPacket::RTCPCompoundPacket(RTPRawPacket &rawpack) std::size_t datalen = rawpack.GetDataLength(); error = ParseData(data, datalen); - if (error < 0) + + if (error < 0) { return; + } compoundpacket = rawpack.GetData(); compoundpacketlength = rawpack.GetDataLength(); - deletepacket = true; - - rawpack.ZeroData(); rtcppackit = rtcppacklist.begin(); } -RTCPCompoundPacket::RTCPCompoundPacket(uint8_t *packet, std::size_t packetlen, bool deletedata) +RTCPCompoundPacket::RTCPCompoundPacket(uint8_t *packet, std::size_t packetlen) { compoundpacket = 0; compoundpacketlength = 0; error = ParseData(packet, packetlen); - if (error < 0) + + if (error < 0) { return; + } compoundpacket = packet; compoundpacketlength = packetlen; - deletepacket = deletedata; rtcppackit = rtcppacklist.begin(); } @@ -94,7 +94,6 @@ RTCPCompoundPacket::RTCPCompoundPacket() compoundpacket = 0; compoundpacketlength = 0; error = 0; - deletepacket = true; } int RTCPCompoundPacket::ParseData(uint8_t *data, std::size_t datalen) @@ -195,16 +194,16 @@ int RTCPCompoundPacket::ParseData(uint8_t *data, std::size_t datalen) RTCPCompoundPacket::~RTCPCompoundPacket() { ClearPacketList(); - if (compoundpacket && deletepacket) - delete[] compoundpacket; } void RTCPCompoundPacket::ClearPacketList() { std::list::const_iterator it; - for (it = rtcppacklist.begin(); it != rtcppacklist.end(); it++) + for (it = rtcppacklist.begin(); it != rtcppacklist.end(); it++) { delete *it; + } + rtcppacklist.clear(); rtcppackit = rtcppacklist.begin(); } diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h index 446504266..c15aaacd1 100644 --- a/qrtplib/rtcpcompoundpacket.h +++ b/qrtplib/rtcpcompoundpacket.h @@ -58,12 +58,8 @@ public: /** Creates an RTCPCompoundPacket instance from the data in \c rawpack, installing a memory manager if specified. */ RTCPCompoundPacket(RTPRawPacket &rawpack); - /** Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. - * Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. The \c deletedata - * flag specifies if the data in \c packet should be deleted when the compound packet is destroyed. If - * specified, a memory manager will be installed. - */ - RTCPCompoundPacket(uint8_t *packet, std::size_t len, bool deletedata = true); + /** Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. */ + RTCPCompoundPacket(uint8_t *packet, std::size_t len); protected: RTCPCompoundPacket(); // this is for the compoundpacket builder public: @@ -118,7 +114,6 @@ protected: uint8_t *compoundpacket; std::size_t compoundpacketlength; - bool deletepacket; std::list rtcppacklist; std::list::const_iterator rtcppackit; diff --git a/qrtplib/rtpaddress.cpp b/qrtplib/rtpaddress.cpp index 4fe56a96b..28b5979f4 100644 --- a/qrtplib/rtpaddress.cpp +++ b/qrtplib/rtpaddress.cpp @@ -77,4 +77,9 @@ bool RTPAddress::IsFromSameHost(const RTPAddress *addr) const return addr->address == address; } +bool RTPAddress::operator==(const RTPAddress& otherAddress) +{ + return IsSameAddress(&otherAddress); +} + } // namespace diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h index 68324b3fe..e5832f6ce 100644 --- a/qrtplib/rtpaddress.h +++ b/qrtplib/rtpaddress.h @@ -82,6 +82,9 @@ public: */ bool IsFromSameHost(const RTPAddress *addr) const; + /** Equality */ + bool operator==(const RTPAddress& otherAddress); + /** Get host address */ const QHostAddress& getAddress() const { diff --git a/qrtplib/rtpinternalutils.h b/qrtplib/rtpinternalutils.h index 9ab51ff29..bbe0e34b7 100644 --- a/qrtplib/rtpinternalutils.h +++ b/qrtplib/rtpinternalutils.h @@ -7,7 +7,7 @@ This library was developed at the Expertise Centre for Digital Media (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for + (http://www.uhasselt.be). The library is based upon work done for my thesis at the School for Knowledge Technology (Belgium/The Netherlands). Permission is hereby granted, free of charge, to any person obtaining a @@ -47,13 +47,7 @@ #else #include #define RTP_SNPRINTF snprintf -#endif - -#ifdef RTP_HAVE_STRNCPY_S -#define RTP_STRNCPY(dest, src, len) strncpy_s((dest), (len), (src), _TRUNCATE) -#else -#define RTP_STRNCPY(dest, src, len) strncpy((dest), (src), (len)) -#endif // RTP_HAVE_STRNCPY_S +#endif #endif // RTPINTERNALUTILS_H diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp index 08419ef8e..b8aa717ed 100644 --- a/qrtplib/rtppacket.cpp +++ b/qrtplib/rtppacket.cpp @@ -67,18 +67,59 @@ RTPPacket::RTPPacket(RTPRawPacket &rawpack) : error = ParseRawPacket(rawpack); } -RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, std::size_t maxpacksize) : - receivetime(0, 0) +RTPPacket::RTPPacket( + uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + std::size_t maxpacksize) : + receivetime(0, 0) { Clear(); - error = BuildPacket(payloadtype, payloaddata, payloadlen, seqnr, timestamp, ssrc, gotmarker, numcsrcs, csrcs, gotextension, extensionid, extensionlen_numwords, extensiondata, - 0, maxpacksize); + error = BuildPacket( + payloadtype, + payloaddata, + payloadlen, + seqnr, + timestamp, + ssrc, + gotmarker, + numcsrcs, + csrcs, + gotextension, + extensionid, + extensionlen_numwords, + extensiondata, + 0, + maxpacksize); } -RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, std::size_t buffersize) : - receivetime(0, 0) +RTPPacket::RTPPacket( + uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + std::size_t buffersize) : + receivetime(0, 0) { Clear(); if (buffer == 0) @@ -86,8 +127,22 @@ RTPPacket::RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t p else if (buffersize <= 0) error = ERR_RTP_PACKET_ILLEGALBUFFERSIZE; else - error = BuildPacket(payloadtype, payloaddata, payloadlen, seqnr, timestamp, ssrc, gotmarker, numcsrcs, csrcs, gotextension, extensionid, extensionlen_numwords, - extensiondata, buffer, buffersize); + error = BuildPacket( + payloadtype, + payloaddata, + payloadlen, + seqnr, + timestamp, + ssrc, + gotmarker, + numcsrcs, + csrcs, + gotextension, + extensionid, + extensionlen_numwords, + extensiondata, + buffer, + buffersize); } int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) @@ -188,9 +243,6 @@ int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) RTPPacket::packetlength = packetlen; RTPPacket::payloadlength = payloadlength; - // We'll zero the data of the raw packet, since we're using it here now! - rawpack.ZeroData(); - return 0; } @@ -209,8 +261,22 @@ uint32_t RTPPacket::GetCSRC(int num) const return csrcval_hbo; } -int RTPPacket::BuildPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, std::size_t maxsize) +int RTPPacket::BuildPacket( + uint8_t payloadtype, + const void *payloaddata, + std::size_t payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + std::size_t maxsize) { if (numcsrcs > RTP_MAXCSRCS) return ERR_RTP_PACKET_TOOMANYCSRCS; diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h index 05ab2ed7c..df00af0a6 100644 --- a/qrtplib/rtprawpacket.h +++ b/qrtplib/rtprawpacket.h @@ -57,7 +57,7 @@ public: * The flag which indicates whether this data is RTP or RTCP data is set to \c rtp. A memory * manager can be installed as well. */ - RTPRawPacket(uint8_t *data, std::size_t datalen, RTPAddress *address, RTPTime &recvtime, bool rtp); + RTPRawPacket(const uint8_t *data, std::size_t datalen, const RTPAddress& address, RTPTime &recvtime, bool rtp); ~RTPRawPacket(); /** Returns the pointer to the data which is contained in this packet. */ @@ -79,7 +79,7 @@ public: } /** Returns the address stored in this packet. */ - const RTPAddress *GetSenderAddress() const + const RTPAddress& GetSenderAddress() const { return senderaddress; } @@ -90,45 +90,28 @@ public: return isrtp; } - /** Sets the pointer to the data stored in this packet to zero. - * Sets the pointer to the data stored in this packet to zero. This will prevent - * a \c delete call for the actual data when the destructor of RTPRawPacket is called. - * This function is used by the RTPPacket and RTCPCompoundPacket classes to obtain - * the packet data (without having to copy it) and to make sure the data isn't deleted - * when the destructor of RTPRawPacket is called. - */ - void ZeroData() - { - packetdata = 0; - packetdatalength = 0; - } - - /** Allocates a number of bytes for RTP or RTCP data using the memory manager that - * was used for this raw packet instance, can be useful if the RTPRawPacket::SetData - * function will be used. */ - uint8_t *AllocateBytes(int recvlen) const; - /** Deallocates the previously stored data and replaces it with the data that's * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ - void SetData(uint8_t *data, std::size_t datalen); + void SetData(const uint8_t *data, std::size_t datalen); /** Deallocates the currently stored RTPAddress instance and replaces it * with the one that's specified (you probably don't need this function). */ - void SetSenderAddress(RTPAddress *address); + void SetSenderAddress(const RTPAddress& address); private: void DeleteData(); uint8_t *packetdata; std::size_t packetdatalength; RTPTime receivetime; - RTPAddress *senderaddress; + RTPAddress senderaddress; bool isrtp; }; -inline RTPRawPacket::RTPRawPacket(uint8_t *data, std::size_t datalen, RTPAddress *address, RTPTime &recvtime, bool rtp) : +inline RTPRawPacket::RTPRawPacket(const uint8_t *data, std::size_t datalen, const RTPAddress& address, RTPTime &recvtime, bool rtp) : receivetime(recvtime) { - packetdata = data; + packetdata = new uint8_t[datalen]; + memcpy(packetdata, data, datalen); packetdatalength = datalen; senderaddress = address; isrtp = rtp; @@ -142,33 +125,25 @@ inline RTPRawPacket::~RTPRawPacket() inline void RTPRawPacket::DeleteData() { if (packetdata) + { delete[] packetdata; - if (senderaddress) - delete senderaddress; - - packetdata = 0; - senderaddress = 0; + packetdata = 0; + } } -inline uint8_t *RTPRawPacket::AllocateBytes(int recvlen) const +inline void RTPRawPacket::SetData(const uint8_t *data, std::size_t datalen) { - return new uint8_t[recvlen]; -} - -inline void RTPRawPacket::SetData(uint8_t *data, std::size_t datalen) -{ - if (packetdata) + if (packetdata) { delete[] packetdata; + } - packetdata = data; + packetdata = new uint8_t[datalen]; + memcpy(packetdata, data, datalen); packetdatalength = datalen; } -inline void RTPRawPacket::SetSenderAddress(RTPAddress *address) +inline void RTPRawPacket::SetSenderAddress(const RTPAddress& address) { - if (senderaddress) - delete senderaddress; - senderaddress = address; } diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index 2d92c502a..c04c7b95b 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -55,20 +55,16 @@ #include #endif // WIN32 -#define SOURCES_LOCK -#define SOURCES_UNLOCK -#define BUILDER_LOCK -#define BUILDER_UNLOCK -#define SCHED_LOCK -#define SCHED_UNLOCK -#define PACKSENT_LOCK -#define PACKSENT_UNLOCK namespace qrtplib { RTPSession::RTPSession(RTPRandom *r) : - rtprnd(GetRandomNumberGenerator(r)), sources(*this), packetbuilder(*rtprnd), rtcpsched(sources, *rtprnd), rtcpbuilder(sources, packetbuilder) + rtprnd(GetRandomNumberGenerator(r)), + sources(*this), + packetbuilder(*rtprnd), + rtcpsched(sources, *rtprnd), + rtcpbuilder(sources, packetbuilder) { // We're not going to set these flags in Create, so that the constructor of a derived class // can already change them @@ -97,9 +93,6 @@ int RTPSession::Create(const RTPSessionParams &sessparams, const RTPTransmission return ERR_RTP_SESSION_ALREADYCREATED; usingpollthread = sessparams.IsUsingPollThread(); - needthreadsafety = sessparams.NeedThreadSafety(); - if (usingpollthread && !needthreadsafety) - return ERR_RTP_SESSION_THREADSAFETYCONFLICT; useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); sentpackets = false; @@ -135,7 +128,7 @@ int RTPSession::Create(const RTPSessionParams &sessparams, const RTPTransmission if (rtptrans == 0) return ERR_RTP_OUTOFMEM; - if ((status = rtptrans->Init(needthreadsafety)) < 0) + if ((status = rtptrans->Init()) < 0) { delete rtptrans; return status; @@ -158,9 +151,6 @@ int RTPSession::Create(const RTPSessionParams &sessparams, RTPTransmitter *trans return ERR_RTP_SESSION_ALREADYCREATED; usingpollthread = sessparams.IsUsingPollThread(); - needthreadsafety = sessparams.NeedThreadSafety(); - if (usingpollthread && !needthreadsafety) - return ERR_RTP_SESSION_THREADSAFETYCONFLICT; useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); sentpackets = false; @@ -236,7 +226,7 @@ int RTPSession::InternalCreate(const RTPSessionParams &sessparams) } else { - RTP_STRNCPY((char * )buf, forcedcname.c_str(), buflen); + strncpy((char * )buf, forcedcname.c_str(), buflen); buf[buflen - 1] = 0; buflen = strlen((char *) buf); } @@ -419,9 +409,7 @@ uint32_t RTPSession::GetLocalSSRC() uint32_t ssrc; - BUILDER_LOCK ssrc = packetbuilder.GetSSRC(); - BUILDER_UNLOCK return ssrc; } @@ -467,13 +455,6 @@ int RTPSession::LeaveMulticastGroup(const RTPAddress &addr) return rtptrans->LeaveMulticastGroup(addr); } -void RTPSession::LeaveAllMulticastGroups() -{ - if (!created) - return; - rtptrans->LeaveAllMulticastGroups(); -} - int RTPSession::SendPacket(const void *data, std::size_t len) { int status; @@ -481,25 +462,17 @@ int RTPSession::SendPacket(const void *data, std::size_t len) if (!created) return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK if ((status = packetbuilder.BuildPacket(data, len)) < 0) { - BUILDER_UNLOCK return status; } if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) { - BUILDER_UNLOCK return status; } - BUILDER_UNLOCK - SOURCES_LOCK sources.SentRTPPacket(); - SOURCES_UNLOCK - PACKSENT_LOCK sentpackets = true; - PACKSENT_UNLOCK return 0; } @@ -510,25 +483,17 @@ int RTPSession::SendPacket(const void *data, std::size_t len, uint8_t pt, bool m if (!created) return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK if ((status = packetbuilder.BuildPacket(data, len, pt, mark, timestampinc)) < 0) { - BUILDER_UNLOCK return status; } if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) { - BUILDER_UNLOCK return status; } - BUILDER_UNLOCK - SOURCES_LOCK sources.SentRTPPacket(); - SOURCES_UNLOCK - PACKSENT_LOCK sentpackets = true; - PACKSENT_UNLOCK return 0; } @@ -539,25 +504,17 @@ int RTPSession::SendPacketEx(const void *data, std::size_t len, uint16_t hdrextI if (!created) return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK if ((status = packetbuilder.BuildPacketEx(data, len, hdrextID, hdrextdata, numhdrextwords)) < 0) { - BUILDER_UNLOCK return status; } if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) { - BUILDER_UNLOCK return status; } - BUILDER_UNLOCK - SOURCES_LOCK sources.SentRTPPacket(); - SOURCES_UNLOCK - PACKSENT_LOCK sentpackets = true; - PACKSENT_UNLOCK return 0; } @@ -568,25 +525,17 @@ int RTPSession::SendPacketEx(const void *data, std::size_t len, uint8_t pt, bool if (!created) return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK if ((status = packetbuilder.BuildPacketEx(data, len, pt, mark, timestampinc, hdrextID, hdrextdata, numhdrextwords)) < 0) { - BUILDER_UNLOCK return status; } if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) { - BUILDER_UNLOCK return status; } - BUILDER_UNLOCK - SOURCES_LOCK sources.SentRTPPacket(); - SOURCES_UNLOCK - PACKSENT_LOCK sentpackets = true; - PACKSENT_UNLOCK return 0; } @@ -599,9 +548,7 @@ int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const if (!created) return ERR_RTP_SESSION_NOTCREATED; - BUILDER_LOCK uint32_t ssrc = packetbuilder.GetSSRC(); - BUILDER_UNLOCK RTCPCompoundPacketBuilder pb; @@ -618,16 +565,13 @@ int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const if ((status = pb.AddSDESSource(ssrc)) < 0) return status; - BUILDER_LOCK std::size_t owncnamelen = 0; uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); if ((status = pb.AddSDESNormalItem(RTCPSDESPacket::CNAME, owncname, owncnamelen)) < 0) { - BUILDER_UNLOCK return status; } - BUILDER_UNLOCK //add our application specific packet if ((status = pb.AddAPPPacket(subtype, ssrc, name, appdata, appdatalen)) < 0) @@ -641,9 +585,7 @@ int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const if (status < 0) return status; - PACKSENT_LOCK sentpackets = true; - PACKSENT_UNLOCK return pb.GetCompoundPacketLength(); } @@ -671,9 +613,7 @@ int RTPSession::SetDefaultPayloadType(uint8_t pt) int status; - BUILDER_LOCK status = packetbuilder.SetDefaultPayloadType(pt); - BUILDER_UNLOCK return status; } @@ -684,9 +624,7 @@ int RTPSession::SetDefaultMark(bool m) int status; - BUILDER_LOCK status = packetbuilder.SetDefaultMark(m); - BUILDER_UNLOCK return status; } @@ -697,9 +635,7 @@ int RTPSession::SetDefaultTimestampIncrement(uint32_t timestampinc) int status; - BUILDER_LOCK status = packetbuilder.SetDefaultTimestampIncrement(timestampinc); - BUILDER_UNLOCK return status; } @@ -710,9 +646,7 @@ int RTPSession::IncrementTimestamp(uint32_t inc) int status; - BUILDER_LOCK status = packetbuilder.IncrementTimestamp(inc); - BUILDER_UNLOCK return status; } @@ -723,9 +657,7 @@ int RTPSession::IncrementTimestampDefault() int status; - BUILDER_LOCK status = packetbuilder.IncrementTimestampDefault(); - BUILDER_UNLOCK return status; } @@ -736,9 +668,7 @@ int RTPSession::SetPreTransmissionDelay(const RTPTime &delay) int status; - BUILDER_LOCK status = rtcpbuilder.SetPreTransmissionDelay(delay); - BUILDER_UNLOCK return status; } @@ -756,37 +686,6 @@ void RTPSession::DeleteTransmissionInfo(RTPTransmissionInfo *inf) rtptrans->DeleteTransmissionInfo(inf); } -int RTPSession::Poll() -{ - int status; - - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - if (usingpollthread) - return ERR_RTP_SESSION_USINGPOLLTHREAD; - if ((status = rtptrans->Poll()) < 0) - return status; - return ProcessPolledData(); -} - -int RTPSession::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) -{ - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - if (usingpollthread) - return ERR_RTP_SESSION_USINGPOLLTHREAD; - return rtptrans->WaitForIncomingData(delay, dataavailable); -} - -int RTPSession::AbortWait() -{ - if (!created) - return ERR_RTP_SESSION_NOTCREATED; - if (usingpollthread) - return ERR_RTP_SESSION_USINGPOLLTHREAD; - return rtptrans->AbortWait(); -} - RTPTime RTPSession::GetRTCPDelay() { if (!created) @@ -794,11 +693,7 @@ RTPTime RTPSession::GetRTCPDelay() if (usingpollthread) return RTPTime(0, 0); - SOURCES_LOCK - SCHED_LOCK RTPTime t = rtcpsched.GetTransmissionDelay(); - SCHED_UNLOCK - SOURCES_UNLOCK return t; } @@ -806,7 +701,6 @@ int RTPSession::BeginDataAccess() { if (!created) return ERR_RTP_SESSION_NOTCREATED; - SOURCES_LOCK return 0; } @@ -887,7 +781,6 @@ int RTPSession::EndDataAccess() { if (!created) return ERR_RTP_SESSION_NOTCREATED; - SOURCES_UNLOCK return 0; } @@ -953,10 +846,8 @@ int RTPSession::SetMaximumPacketSize(std::size_t s) if ((status = rtptrans->SetMaximumPacketSize(s)) < 0) return status; - BUILDER_LOCK if ((status = packetbuilder.SetMaximumPacketSize(s)) < 0) { - BUILDER_UNLOCK // restore previous max packet size rtptrans->SetMaximumPacketSize(maxpacksize); return status; @@ -965,11 +856,9 @@ int RTPSession::SetMaximumPacketSize(std::size_t s) { // restore previous max packet size packetbuilder.SetMaximumPacketSize(maxpacksize); - BUILDER_UNLOCK rtptrans->SetMaximumPacketSize(maxpacksize); return status; } - BUILDER_UNLOCK maxpacksize = s; return 0; } @@ -980,7 +869,6 @@ int RTPSession::SetSessionBandwidth(double bw) return ERR_RTP_SESSION_NOTCREATED; int status; - SCHED_LOCK RTCPSchedulerParams p = rtcpsched.GetParameters(); status = p.SetRTCPBandwidth(bw * controlfragment); if (status >= 0) @@ -988,7 +876,6 @@ int RTPSession::SetSessionBandwidth(double bw) rtcpsched.SetParameters(p); sessionbandwidth = bw; } - SCHED_UNLOCK return status; } @@ -999,9 +886,7 @@ int RTPSession::SetTimestampUnit(double u) int status; - BUILDER_LOCK status = rtcpbuilder.SetTimestampUnit(u); - BUILDER_UNLOCK return status; } @@ -1009,457 +894,384 @@ void RTPSession::SetNameInterval(int count) { if (!created) return; - BUILDER_LOCK rtcpbuilder.SetNameInterval(count); -BUILDER_UNLOCK } void RTPSession::SetEMailInterval(int count) { -if (!created) - return; -BUILDER_LOCK -rtcpbuilder.SetEMailInterval(count); -BUILDER_UNLOCK + if (!created) + return; + rtcpbuilder.SetEMailInterval(count); } void RTPSession::SetLocationInterval(int count) { -if (!created) -return; -BUILDER_LOCK -rtcpbuilder.SetLocationInterval(count); -BUILDER_UNLOCK + if (!created) + return; + rtcpbuilder.SetLocationInterval(count); } void RTPSession::SetPhoneInterval(int count) { -if (!created) -return; -BUILDER_LOCK -rtcpbuilder.SetPhoneInterval(count); -BUILDER_UNLOCK + if (!created) + return; + rtcpbuilder.SetPhoneInterval(count); } void RTPSession::SetToolInterval(int count) { -if (!created) -return; -BUILDER_LOCK -rtcpbuilder.SetToolInterval(count); -BUILDER_UNLOCK + if (!created) + return; + rtcpbuilder.SetToolInterval(count); } void RTPSession::SetNoteInterval(int count) { -if (!created) -return; -BUILDER_LOCK -rtcpbuilder.SetNoteInterval(count); -BUILDER_UNLOCK + if (!created) + return; + rtcpbuilder.SetNoteInterval(count); } int RTPSession::SetLocalName(const void *s, std::size_t len) { -if (!created) -return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; -int status; -BUILDER_LOCK -status = rtcpbuilder.SetLocalName(s, len); -BUILDER_UNLOCK -return status; + int status; + status = rtcpbuilder.SetLocalName(s, len); + return status; } int RTPSession::SetLocalEMail(const void *s, std::size_t len) { -if (!created) -return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; -int status; -BUILDER_LOCK -status = rtcpbuilder.SetLocalEMail(s, len); -BUILDER_UNLOCK -return status; + int status; + status = rtcpbuilder.SetLocalEMail(s, len); + return status; } int RTPSession::SetLocalLocation(const void *s, std::size_t len) { -if (!created) -return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; -int status; -BUILDER_LOCK -status = rtcpbuilder.SetLocalLocation(s, len); -BUILDER_UNLOCK -return status; + int status; + status = rtcpbuilder.SetLocalLocation(s, len); + return status; } int RTPSession::SetLocalPhone(const void *s, std::size_t len) { -if (!created) -return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; -int status; -BUILDER_LOCK -status = rtcpbuilder.SetLocalPhone(s, len); -BUILDER_UNLOCK -return status; + int status; + status = rtcpbuilder.SetLocalPhone(s, len); + return status; } int RTPSession::SetLocalTool(const void *s, std::size_t len) { -if (!created) -return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; -int status; -BUILDER_LOCK -status = rtcpbuilder.SetLocalTool(s, len); -BUILDER_UNLOCK -return status; + int status; + status = rtcpbuilder.SetLocalTool(s, len); + return status; } int RTPSession::SetLocalNote(const void *s, std::size_t len) { -if (!created) -return ERR_RTP_SESSION_NOTCREATED; + if (!created) + return ERR_RTP_SESSION_NOTCREATED; -int status; -BUILDER_LOCK -status = rtcpbuilder.SetLocalNote(s, len); -BUILDER_UNLOCK -return status; + int status; + status = rtcpbuilder.SetLocalNote(s, len); + return status; } int RTPSession::ProcessPolledData() { -RTPRawPacket *rawpack; -int status; + RTPRawPacket *rawpack; + int status; -SOURCES_LOCK -while ((rawpack = rtptrans->GetNextPacket()) != 0) -{ -if (m_changeIncomingData) -{ - // Provide a way to change incoming data, for decryption for example -if (!OnChangeIncomingData(rawpack)) -{ -delete rawpack; -continue; -} + while ((rawpack = rtptrans->GetNextPacket()) != 0) + { + if (m_changeIncomingData) + { + // Provide a way to change incoming data, for decryption for example + if (!OnChangeIncomingData(rawpack)) + { + delete rawpack; + continue; + } + } + + sources.ClearOwnCollisionFlag(); + + // since our sources instance also uses the scheduler (analysis of incoming packets) + // we'll lock it + if ((status = sources.ProcessRawPacket(rawpack, rtptrans, acceptownpackets)) < 0) + { + delete rawpack; + return status; + } + + if (sources.DetectedOwnCollision()) // collision handling! + { + bool created; + + if ((status = collisionlist.UpdateAddress(&rawpack->GetSenderAddress(), rawpack->GetReceiveTime(), &created)) < 0) + { + delete rawpack; + return status; + } + + if (created) // first time we've encountered this address, send bye packet and + { // change our own SSRC + bool hassentpackets = sentpackets; + + if (hassentpackets) + { + // Only send BYE packet if we've actually sent data using this + // SSRC + + RTCPCompoundPacket *rtcpcomppack; + + if ((status = rtcpbuilder.BuildBYEPacket(&rtcpcomppack, 0, 0, useSR_BYEifpossible)) < 0) + { + delete rawpack; + return status; + } + + byepackets.push_back(rtcpcomppack); + if (byepackets.size() == 1) // was the first packet, schedule a BYE packet (otherwise there's already one scheduled) + { + rtcpsched.ScheduleBYEPacket(rtcpcomppack->GetCompoundPacketLength()); + } + } + // bye packet is built and scheduled, now change our SSRC + // and reset the packet count in the transmitter + + uint32_t newssrc = packetbuilder.CreateNewSSRC(sources); + + sentpackets = false; + + // remove old entry in source table and add new one + + if ((status = sources.DeleteOwnSSRC()) < 0) + { + delete rawpack; + return status; + } + if ((status = sources.CreateOwnSSRC(newssrc)) < 0) + { + delete rawpack; + return status; + } + } + } + delete rawpack; + } + + RTPTime d = rtcpsched.CalculateDeterministicInterval(false); + + RTPTime t = RTPTime::CurrentTime(); + double Td = d.GetDouble(); + RTPTime sendertimeout = RTPTime(Td * sendermultiplier); + RTPTime generaltimeout = RTPTime(Td * membermultiplier); + RTPTime byetimeout = RTPTime(Td * byemultiplier); + RTPTime colltimeout = RTPTime(Td * collisionmultiplier); + RTPTime notetimeout = RTPTime(Td * notemultiplier); + + sources.MultipleTimeouts(t, sendertimeout, byetimeout, generaltimeout, notetimeout); + collisionlist.Timeout(t, colltimeout); + + // We'll check if it's time for RTCP stuff + + bool istime = rtcpsched.IsTime(); + + if (istime) + { + RTCPCompoundPacket *pack; + + // we'll check if there's a bye packet to send, or just a normal packet + + if (byepackets.empty()) + { + if ((status = rtcpbuilder.BuildNextPacket(&pack)) < 0) + { + return status; + } + if ((status = SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength())) < 0) + { + delete pack; + return status; + } + + sentpackets = true; + + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + } + else + { + pack = *(byepackets.begin()); + byepackets.pop_front(); + + if ((status = SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength())) < 0) + { + delete pack; + return status; + } + + sentpackets = true; + + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + + if (!byepackets.empty()) // more bye packets to send, schedule them + { + rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); + } + } + + rtcpsched.AnalyseOutgoing(*pack); + + delete pack; + } + return 0; } -sources.ClearOwnCollisionFlag(); - - // since our sources instance also uses the scheduler (analysis of incoming packets) - // we'll lock it -SCHED_LOCK -if ((status = sources.ProcessRawPacket(rawpack, rtptrans, acceptownpackets)) < 0) -{ -SCHED_UNLOCK -SOURCES_UNLOCK -delete rawpack; -return status; -} -SCHED_UNLOCK - -if (sources.DetectedOwnCollision()) // collision handling! -{ -bool created; - -if ((status = collisionlist.UpdateAddress(rawpack->GetSenderAddress(), rawpack->GetReceiveTime(), &created)) < 0) -{ -SOURCES_UNLOCK -delete rawpack; -return status; -} - -if (created) // first time we've encountered this address, send bye packet and -{ // change our own SSRC -PACKSENT_LOCK -bool hassentpackets = sentpackets; -PACKSENT_UNLOCK - -if (hassentpackets) -{ - // Only send BYE packet if we've actually sent data using this - // SSRC - -RTCPCompoundPacket *rtcpcomppack; - -BUILDER_LOCK -if ((status = rtcpbuilder.BuildBYEPacket(&rtcpcomppack, 0, 0, useSR_BYEifpossible)) < 0) -{ -BUILDER_UNLOCK -SOURCES_UNLOCK -delete rawpack; -return status; -} -BUILDER_UNLOCK - -byepackets.push_back(rtcpcomppack); -if (byepackets.size() == 1) // was the first packet, schedule a BYE packet (otherwise there's already one scheduled) -{ -SCHED_LOCK -rtcpsched.ScheduleBYEPacket(rtcpcomppack->GetCompoundPacketLength()); -SCHED_UNLOCK -} -} - // bye packet is built and scheduled, now change our SSRC - // and reset the packet count in the transmitter - -BUILDER_LOCK -uint32_t newssrc = packetbuilder.CreateNewSSRC(sources); -BUILDER_UNLOCK - -PACKSENT_LOCK -sentpackets = false; -PACKSENT_UNLOCK - - // remove old entry in source table and add new one - -if ((status = sources.DeleteOwnSSRC()) < 0) -{ -SOURCES_UNLOCK -delete rawpack; -return status; -} -if ((status = sources.CreateOwnSSRC(newssrc)) < 0) -{ -SOURCES_UNLOCK -delete rawpack; -return status; -} -} -} -delete rawpack; -} - -SCHED_LOCK -RTPTime d = rtcpsched.CalculateDeterministicInterval(false); -SCHED_UNLOCK - -RTPTime t = RTPTime::CurrentTime(); -double Td = d.GetDouble(); -RTPTime sendertimeout = RTPTime(Td * sendermultiplier); -RTPTime generaltimeout = RTPTime(Td * membermultiplier); -RTPTime byetimeout = RTPTime(Td * byemultiplier); -RTPTime colltimeout = RTPTime(Td * collisionmultiplier); -RTPTime notetimeout = RTPTime(Td * notemultiplier); - -sources.MultipleTimeouts(t, sendertimeout, byetimeout, generaltimeout, notetimeout); -collisionlist.Timeout(t, colltimeout); - - // We'll check if it's time for RTCP stuff - -SCHED_LOCK -bool istime = rtcpsched.IsTime(); -SCHED_UNLOCK - -if (istime) -{ -RTCPCompoundPacket *pack; - - // we'll check if there's a bye packet to send, or just a normal packet - -if (byepackets.empty()) -{ -BUILDER_LOCK -if ((status = rtcpbuilder.BuildNextPacket(&pack)) < 0) -{ -BUILDER_UNLOCK -SOURCES_UNLOCK -return status; -} -BUILDER_UNLOCK -if ((status = SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength())) < 0) -{ -SOURCES_UNLOCK -delete pack; -return status; -} - -PACKSENT_LOCK -sentpackets = true; -PACKSENT_UNLOCK - -OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering -} -else -{ -pack = *(byepackets.begin()); -byepackets.pop_front(); - -if ((status = SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength())) < 0) -{ -SOURCES_UNLOCK -delete pack; -return status; -} - -PACKSENT_LOCK -sentpackets = true; -PACKSENT_UNLOCK - -OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering - -if (!byepackets.empty()) // more bye packets to send, schedule them -{ -SCHED_LOCK -rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); -SCHED_UNLOCK -} -} - -SCHED_LOCK -rtcpsched.AnalyseOutgoing(*pack); -SCHED_UNLOCK - -delete pack; -} -SOURCES_UNLOCK -return 0; -} - -int RTPSession::CreateCNAME(uint8_t *buffer, std::size_t *bufferlength, bool resolve) +int RTPSession::CreateCNAME(uint8_t *buffer, std::size_t *bufferlength, bool resolve __attribute__((unused))) { #ifndef WIN32 -bool gotlogin = true; + bool gotlogin = true; #ifdef RTP_SUPPORT_GETLOGINR -buffer[0] = 0; -if (getlogin_r((char *) buffer, *bufferlength) != 0) -gotlogin = false; -else -{ -if (buffer[0] == 0) -gotlogin = false; -} + buffer[0] = 0; + if (getlogin_r((char *) buffer, *bufferlength) != 0) + gotlogin = false; + else + { + if (buffer[0] == 0) + gotlogin = false; + } -if (!gotlogin) // try regular getlogin -{ -char *loginname = getlogin(); -if (loginname == 0) -gotlogin = false; -else -strncpy((char *) buffer, loginname, *bufferlength); -} + if (!gotlogin) // try regular getlogin + { + char *loginname = getlogin(); + if (loginname == 0) + gotlogin = false; + else + strncpy((char *) buffer, loginname, *bufferlength); + } #else -char *loginname = getlogin(); -if (loginname == 0) -gotlogin = false; -else -strncpy((char *)buffer,loginname,*bufferlength); + char *loginname = getlogin(); + if (loginname == 0) + gotlogin = false; + else + strncpy((char *)buffer,loginname,*bufferlength); #endif // RTP_SUPPORT_GETLOGINR -if (!gotlogin) -{ -char *logname = getenv("LOGNAME"); -if (logname == 0) -return ERR_RTP_SESSION_CANTGETLOGINNAME; -strncpy((char *) buffer, logname, *bufferlength); -} + if (!gotlogin) + { + char *logname = getenv("LOGNAME"); + if (logname == 0) + return ERR_RTP_SESSION_CANTGETLOGINNAME; + strncpy((char *) buffer, logname, *bufferlength); + } #else // Win32 version #ifndef _WIN32_WCE -DWORD len = *bufferlength; -if (!GetUserName((LPTSTR)buffer,&len)) -RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); + DWORD len = *bufferlength; + if (!GetUserName((LPTSTR)buffer,&len)) + strncpy((char *)buffer,"unknown",*bufferlength); #else -RTP_STRNCPY((char *)buffer,"unknown",*bufferlength); + strncpy((char *)buffer,"unknown",*bufferlength); #endif // _WIN32_WCE #endif // WIN32 -buffer[*bufferlength - 1] = 0; + buffer[*bufferlength - 1] = 0; -std::size_t offset = strlen((const char *) buffer); -if (offset < (*bufferlength - 1)) -buffer[offset] = (uint8_t) '@'; -offset++; + std::size_t offset = strlen((const char *) buffer); + if (offset < (*bufferlength - 1)) + buffer[offset] = (uint8_t) '@'; + offset++; -std::size_t buflen2 = *bufferlength - offset; -int status; + std::size_t buflen2 = *bufferlength - offset; -if (resolve) -{ -if ((status = rtptrans->GetLocalHostName(buffer + offset, &buflen2)) < 0) -return status; -*bufferlength = buflen2 + offset; -} -else -{ -char hostname[1024]; + char hostname[1024]; -RTP_STRNCPY(hostname, "localhost", 1024); // just in case gethostname fails + strncpy(hostname, "localhost", 1024); // just in case gethostname fails -gethostname(hostname, 1024); -RTP_STRNCPY((char * )(buffer + offset), hostname, buflen2); + gethostname(hostname, 1024); + strncpy((char * )(buffer + offset), hostname, buflen2); -*bufferlength = offset + strlen(hostname); -} -if (*bufferlength > RTCP_SDES_MAXITEMLENGTH) -*bufferlength = RTCP_SDES_MAXITEMLENGTH; -return 0; + *bufferlength = offset + strlen(hostname); + if (*bufferlength > RTCP_SDES_MAXITEMLENGTH) + *bufferlength = RTCP_SDES_MAXITEMLENGTH; + return 0; } RTPRandom *RTPSession::GetRandomNumberGenerator(RTPRandom *r) { -RTPRandom *rnew = 0; + RTPRandom *rnew = 0; -if (r == 0) -{ -rnew = RTPRandom::CreateDefaultRandomNumberGenerator(); -deletertprnd = true; -} -else -{ -rnew = r; -deletertprnd = false; -} + if (r == 0) + { + rnew = RTPRandom::CreateDefaultRandomNumberGenerator(); + deletertprnd = true; + } + else + { + rnew = r; + deletertprnd = false; + } -return rnew; + return rnew; } int RTPSession::SendRTPData(const void *data, std::size_t len) { -if (!m_changeOutgoingData) -return rtptrans->SendRTPData(data, len); + if (!m_changeOutgoingData) + return rtptrans->SendRTPData(data, len); -void *pSendData = 0; -std::size_t sendLen = 0; -int status = 0; + void *pSendData = 0; + std::size_t sendLen = 0; + int status = 0; -status = OnChangeRTPOrRTCPData(data, len, true, &pSendData, &sendLen); -if (status < 0) -return status; + status = OnChangeRTPOrRTCPData(data, len, true, &pSendData, &sendLen); + if (status < 0) + return status; -if (pSendData) -{ -status = rtptrans->SendRTPData(pSendData, sendLen); -OnSentRTPOrRTCPData(pSendData, sendLen, true); -} + if (pSendData) + { + status = rtptrans->SendRTPData(pSendData, sendLen); + OnSentRTPOrRTCPData(pSendData, sendLen, true); + } -return status; + return status; } int RTPSession::SendRTCPData(const void *data, std::size_t len) { -if (!m_changeOutgoingData) -return rtptrans->SendRTCPData(data, len); + if (!m_changeOutgoingData) + return rtptrans->SendRTCPData(data, len); -void *pSendData = 0; -std::size_t sendLen = 0; -int status = 0; + void *pSendData = 0; + std::size_t sendLen = 0; + int status = 0; -status = OnChangeRTPOrRTCPData(data, len, false, &pSendData, &sendLen); -if (status < 0) -return status; + status = OnChangeRTPOrRTCPData(data, len, false, &pSendData, &sendLen); + if (status < 0) + return status; -if (pSendData) -{ -status = rtptrans->SendRTCPData(pSendData, sendLen); -OnSentRTPOrRTCPData(pSendData, sendLen, false); -} + if (pSendData) + { + status = rtptrans->SendRTCPData(pSendData, sendLen); + OnSentRTPOrRTCPData(pSendData, sendLen, false); + } -return status; + return status; } } // end namespace diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index cf98baf65..2ad288a44 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -140,9 +140,6 @@ public: /** Leaves the multicast group specified by \c addr. */ int LeaveMulticastGroup(const RTPAddress &addr); - /** Leaves all multicast groups. */ - void LeaveAllMulticastGroups(); - /** Sends the RTP packet with payload \c data which has length \c len. * Sends the RTP packet with payload \c data which has length \c len. * The used payload type, marker and timestamp increment will be those that have been set @@ -233,23 +230,6 @@ public: /** Frees the memory used by the transmission information \c inf. */ void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - /** If you're not using the poll thread, this function must be called regularly to process incoming data - * and to send RTCP data when necessary. - */ - int Poll(); - - /** Waits at most a time \c delay until incoming data has been detected. - * Waits at most a time \c delay until incoming data has been detected. Only works when you're not - * using the poll thread. If \c dataavailable is not \c NULL, it should be set to \c true if data - * was actually read and to \c false otherwise. - */ - int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); - - /** If the previous function has been called, this one aborts the waiting (only works when you're not - * using the poll thread). - */ - int AbortWait(); - /** Returns the time interval after which an RTCP compound packet may have to be sent (only works when * you're not using the poll thread. */ @@ -574,7 +554,7 @@ private: RTPTransmitter *rtptrans; bool created; bool deletetransmitter; - bool usingpollthread, needthreadsafety; + bool usingpollthread; bool acceptownpackets; bool useSR_BYEifpossible; std::size_t maxpacksize; diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp index f72c19b3a..62bbc725f 100644 --- a/qrtplib/rtpsources.cpp +++ b/qrtplib/rtpsources.cpp @@ -191,7 +191,7 @@ int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans bool stored = false; bool ownpacket = false; int i; - const RTPAddress *senderaddress = rawpack->GetSenderAddress(); + const RTPAddress& senderaddress = rawpack->GetSenderAddress(); for (i = 0; !ownpacket && i < numtrans; i++) { @@ -217,7 +217,7 @@ int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans } else { - if ((status = ProcessRTPPacket(rtppack, rawpack->GetReceiveTime(), senderaddress, &stored)) < 0) + if ((status = ProcessRTPPacket(rtppack, rawpack->GetReceiveTime(), &senderaddress, &stored)) < 0) { if (!stored) delete rtppack; @@ -245,7 +245,7 @@ int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans { bool ownpacket = false; int i; - const RTPAddress *senderaddress = rawpack->GetSenderAddress(); + const RTPAddress& senderaddress = rawpack->GetSenderAddress(); for (i = 0; !ownpacket && i < numtrans; i++) { @@ -266,7 +266,7 @@ int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans } else // not our own packet { - status = ProcessRTCPCompoundPacket(&rtcpcomppack, rawpack->GetReceiveTime(), rawpack->GetSenderAddress()); + status = ProcessRTCPCompoundPacket(&rtcpcomppack, rawpack->GetReceiveTime(), &rawpack->GetSenderAddress()); if (status < 0) return status; } diff --git a/qrtplib/rtptransmitter.h b/qrtplib/rtptransmitter.h index a337fb30f..595e1bd7b 100644 --- a/qrtplib/rtptransmitter.h +++ b/qrtplib/rtptransmitter.h @@ -94,11 +94,8 @@ public: { } - /** This function must be called before the transmission component can be used. - * This function must be called before the transmission component can be used. Depending on - * the value of \c threadsafe, the component will be created for thread-safe usage or not. - */ - virtual int Init(bool threadsafe) = 0; + /** This function must be called before the transmission component can be used. */ + virtual int Init() = 0; /** Prepares the component to be used. * Prepares the component to be used. The parameter \c maxpacksize specifies the maximum size @@ -110,6 +107,9 @@ public: */ virtual int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams) = 0; + /** Bind the RTP and RTCP sockets to ports that were set at creation time */ + virtual int BindSockets() = 0; + /** By calling this function, buffers are cleared and the component cannot be used anymore. * By calling this function, buffers are cleared and the component cannot be used anymore. * Only when the Create function is called again can the component be used again. */ @@ -129,36 +129,13 @@ public: */ virtual void DeleteTransmissionInfo(RTPTransmissionInfo *inf) = 0; - /** Looks up the local host name. - * Looks up the local host name based upon internal information about the local host's - * addresses. This function might take some time since a DNS query might be done. \c bufferlength - * should initially contain the number of bytes that may be stored in \c buffer. If the function - * succeeds, \c bufferlength is set to the number of bytes stored in \c buffer. Note that the data - * in \c buffer is not NULL-terminated. If the function fails because the buffer isn't large enough, - * it returns \c ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL and stores the number of bytes needed in - * \c bufferlength. - */ - virtual int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) = 0; - /** Returns \c true if the address specified by \c addr is one of the addresses of the transmitter. */ - virtual bool ComesFromThisTransmitter(const RTPAddress *addr) = 0; + virtual bool ComesFromThisTransmitter(const RTPAddress& addr) = 0; /** Returns the amount of bytes that will be added to the RTP packet by the underlying layers (excluding * the link layer). */ virtual std::size_t GetHeaderOverhead() = 0; - /** Checks for incoming data and stores it. */ - virtual int Poll() = 0; - - /** Waits until incoming data is detected. - * Waits at most a time \c delay until incoming data has been detected. If \c dataavailable is not NULL, - * it should be set to \c true if data was actually read and to \c false otherwise. - */ - virtual int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0) = 0; - - /** If the previous function has been called, this one aborts the waiting. */ - virtual int AbortWait() = 0; - /** Send a packet with length \c len containing \c data to all RTP addresses of the current destination list. */ virtual int SendRTPData(const void *data, std::size_t len) = 0; @@ -183,9 +160,6 @@ public: /** Leaves the multicast group specified by \c addr. */ virtual int LeaveMulticastGroup(const RTPAddress &addr) = 0; - /** Leaves all the multicast groups that have been joined. */ - virtual void LeaveAllMulticastGroups() = 0; - /** Sets the receive mode. * Sets the receive mode to \c m, which is one of the following: RTPTransmitter::AcceptAll, * RTPTransmitter::AcceptSome or RTPTransmitter::IgnoreSome. Note that if the receive @@ -214,10 +188,7 @@ public: /** Sets the maximum packet size which the transmitter should allow to \c s. */ virtual int SetMaximumPacketSize(std::size_t s) = 0; - /** Returns \c true if packets can be obtained using the GetNextPacket member function. */ - virtual bool NewDataAvailable() = 0; - - /** Returns the raw data of a received RTP packet (received during the Poll function) + /** Returns the raw data of a received RTP packet * in an RTPRawPacket instance. */ virtual RTPRawPacket *GetNextPacket() = 0; }; diff --git a/qrtplib/rtpudptransmitter.cpp b/qrtplib/rtpudptransmitter.cpp index ee889b80a..e7650afc6 100644 --- a/qrtplib/rtpudptransmitter.cpp +++ b/qrtplib/rtpudptransmitter.cpp @@ -32,13 +32,17 @@ #include "rtpudptransmitter.h" #include "rtperrors.h" +#include "rtpaddress.h" +#include "rtpstructs.h" +#include "rtprawpacket.h" -#define RTPUDPTRANS_MAXPACKSIZE 65535 +#include namespace qrtplib { -RTPUDPTransmitter::RTPUDPTransmitter() +RTPUDPTransmitter::RTPUDPTransmitter() : + m_rawPacketQueueLock(QMutex::Recursive) { m_created = false; m_init = false; @@ -48,9 +52,7 @@ RTPUDPTransmitter::RTPUDPTransmitter() m_closesocketswhendone = false; m_rtcpPort = 0; m_rtpPort = 0; - m_supportsmulticasting = false; m_receivemode = RTPTransmitter::AcceptAll; - m_localhostname = 0; } RTPUDPTransmitter::~RTPUDPTransmitter() @@ -71,11 +73,9 @@ int RTPUDPTransmitter::Init() int RTPUDPTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) { const RTPUDPTransmissionParams *params, defaultparams; - struct sockaddr_in addr; qint64 size; - int status; - if (maximumpacketsize > RTPUDPTRANS_MAXPACKSIZE) { + if (maximumpacketsize > m_absoluteMaxPackSize) { return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; } @@ -98,7 +98,7 @@ int RTPUDPTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissi { return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; } - params = (const RTPUDPv4TransmissionParams *) transparams; + params = (const RTPUDPTransmissionParams *) transparams; } // Determine the port numbers @@ -155,18 +155,35 @@ int RTPUDPTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissi } m_maxpacksize = maximumpacketsize; - m_mcastifaceIP = params->GetMulticastInterfaceIP(); + m_multicastInterface = params->GetMulticastInterface(); m_receivemode = RTPTransmitter::AcceptAll; - m_localhostname = 0; - m_localhostnamelength = 0; - m_waitingfordata = false; m_created = true; return 0; } +int RTPUDPTransmitter::BindSockets() +{ + if (!m_rtpsock->bind(m_localIP, m_rtpPort)) { + return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; + } + + connect(m_rtpsock, SIGNAL(readyRead()), this, SLOT(readRTPPendingDatagrams())); + + if (m_rtpsock != m_rtcpsock) + { + if (!m_rtcpsock->bind(m_localIP, m_rtcpPort)) { + return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; + } + + connect(m_rtcpsock, SIGNAL(readyRead()), this, SLOT(readRTCPPendingDatagrams())); + } + + return 0; +} + void RTPUDPTransmitter::Destroy() { if (!m_init) { @@ -178,16 +195,6 @@ void RTPUDPTransmitter::Destroy() return; } - if (m_localhostname) - { - delete[] m_localhostname; - m_localhostname = 0; - m_localhostnamelength = 0; - } - - FlushPackets(); - ClearAcceptIgnoreInfo(); - if (m_closesocketswhendone) { if (m_rtpsock != m_rtcpsock) { @@ -206,8 +213,7 @@ RTPTransmissionInfo *RTPUDPTransmitter::GetTransmissionInfo() return 0; } - RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionNoBindInfo( - m_localIP, m_rtpsock, m_rtcpsock, m_rtpPort, m_rtcpPort); + RTPTransmissionInfo *tinf = new RTPUDPTransmissionInfo(m_localIP, m_rtpsock, m_rtcpsock, m_rtpPort, m_rtcpPort); return tinf; } @@ -221,13 +227,326 @@ void RTPUDPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *inf) delete inf; } -bool RTPUDPTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) +bool RTPUDPTransmitter::ComesFromThisTransmitter(const RTPAddress& addr) { - if (addr->getAddress() != m_localIP) { + if (addr.getAddress() != m_localIP) { return false; } - return (addr->getPort() == m_rtpPort) && (addr->getRtcpsendport() == m_rtcpPort); + return (addr.getPort() == m_rtpPort) && (addr.getRtcpsendport() == m_rtcpPort); +} + +int RTPUDPTransmitter::SendRTPData(const void *data, std::size_t len) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (len > m_maxpacksize) + { + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + std::list::const_iterator it = m_destinations.begin(); + + for (; it != m_destinations.end(); ++it) + { + m_rtpsock->writeDatagram((const char*) data, (qint64) len, it->getAddress(), it->getPort()); + } + + return 0; +} + +int RTPUDPTransmitter::SendRTCPData(const void *data, std::size_t len) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (len > m_maxpacksize) { + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + std::list::const_iterator it = m_destinations.begin(); + + for (; it != m_destinations.end(); ++it) + { + m_rtcpsock->writeDatagram((const char*) data, (qint64) len, it->getAddress(), it->getRtcpsendport()); + } + + return 0; +} + +int RTPUDPTransmitter::AddDestination(const RTPAddress &addr) +{ + m_destinations.push_back(addr); + return 0; +} + +int RTPUDPTransmitter::DeleteDestination(const RTPAddress &addr) +{ + m_destinations.remove(addr); + return 0; +} + +void RTPUDPTransmitter::ClearDestinations() +{ + m_destinations.clear(); +} + +bool RTPUDPTransmitter::SupportsMulticasting() +{ + QNetworkInterface::InterfaceFlags flags = m_multicastInterface.flags(); + QAbstractSocket::SocketState rtpSocketState = m_rtpsock->state(); + QAbstractSocket::SocketState rtcpSocketState = m_rtcpsock->state(); + return m_multicastInterface.isValid() + && (rtpSocketState & QAbstractSocket::BoundState) + && (rtcpSocketState & QAbstractSocket::BoundState) + && (flags & QNetworkInterface::CanMulticast) + && (flags & QNetworkInterface::IsRunning) + && !(flags & QNetworkInterface::IsLoopBack); +} + +int RTPUDPTransmitter::JoinMulticastGroup(const RTPAddress &addr) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (!SupportsMulticasting()) { + return ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT; + } + + if (m_rtpsock->joinMulticastGroup(addr.getAddress(), m_multicastInterface)) + { + if (m_rtpsock != m_rtcpsock) + { + if (!m_rtcpsock->joinMulticastGroup(addr.getAddress(), m_multicastInterface)) { + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + } + } + else + { + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + + return 0; +} + +int RTPUDPTransmitter::LeaveMulticastGroup(const RTPAddress &addr) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (!SupportsMulticasting()) { + return ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT; + } + + m_rtpsock->leaveMulticastGroup(addr.getAddress()); + + if (m_rtpsock != m_rtcpsock) + { + m_rtcpsock->leaveMulticastGroup(addr.getAddress()); + } + + return 0; +} + +int RTPUDPTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (m != m_receivemode) { + m_receivemode = m; + } + + return 0; +} + +int RTPUDPTransmitter::AddToIgnoreList(const RTPAddress &addr) +{ + m_ignoreList.push_back(addr); + return 0; +} + +int RTPUDPTransmitter::DeleteFromIgnoreList(const RTPAddress &addr) +{ + m_ignoreList.remove(addr); + return 0; +} + +void RTPUDPTransmitter::ClearIgnoreList() +{ + m_ignoreList.clear(); +} + +int RTPUDPTransmitter::AddToAcceptList(const RTPAddress &addr) +{ + m_acceptList.push_back(addr); + return 0; +} + +int RTPUDPTransmitter::DeleteFromAcceptList(const RTPAddress &addr) +{ + m_acceptList.remove(addr); + return 0; +} + +void RTPUDPTransmitter::ClearAcceptList() +{ + m_acceptList.clear(); +} + +int RTPUDPTransmitter::SetMaximumPacketSize(std::size_t s) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (s > m_absoluteMaxPackSize) { + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + m_maxpacksize = s; + return 0; +} + +RTPRawPacket *RTPUDPTransmitter::GetNextPacket() +{ + QMutexLocker locker(&m_rawPacketQueueLock); + + if (m_rawPacketQueue.isEmpty()) { + return 0; + } else { + return m_rawPacketQueue.takeFirst(); + } +} + +void RTPUDPTransmitter::readRTPPendingDatagrams() +{ + while (m_rtpsock->hasPendingDatagrams()) + { + RTPTime curtime = RTPTime::CurrentTime(); + QHostAddress remoteAddress; + quint16 remotePort; + qint64 pendingDataSize = m_rtpsock->pendingDatagramSize(); + qint64 bytesRead = m_rtpsock->readDatagram(m_rtpBuffer, pendingDataSize, &remoteAddress, &remotePort); + qDebug("RTPUDPTransmitter::readRTPPendingDatagrams: %lld bytes read from %s:%d", + bytesRead, + qPrintable(remoteAddress.toString()), + remotePort); + + RTPAddress rtpAddress; + rtpAddress.setAddress(remoteAddress); + rtpAddress.setPort(remotePort); + + if (ShouldAcceptData(rtpAddress)) + { + bool isrtp = true; + + if (m_rtpsock == m_rtcpsock) // check payload type when multiplexing + { + if ((std::size_t) bytesRead > sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) m_rtpBuffer; + uint8_t packettype = rtcpheader->packettype; + + if (packettype >= 200 && packettype <= 204) { + isrtp = false; + } + } + } + + RTPRawPacket *pack = new RTPRawPacket((uint8_t *) m_rtpBuffer, bytesRead, rtpAddress, curtime, isrtp); + + m_rawPacketQueueLock.lock(); + m_rawPacketQueue.append(pack); + m_rawPacketQueueLock.unlock(); + + emit NewDataAvailable(); + } + } +} + +void RTPUDPTransmitter::readRTCPPendingDatagrams() +{ + while (m_rtcpsock->hasPendingDatagrams()) + { + RTPTime curtime = RTPTime::CurrentTime(); + QHostAddress remoteAddress; + quint16 remotePort; + qint64 pendingDataSize = m_rtcpsock->pendingDatagramSize(); + qint64 bytesRead = m_rtcpsock->readDatagram(m_rtcpBuffer, pendingDataSize, &remoteAddress, &remotePort); + qDebug("RTPUDPTransmitter::readRTCPPendingDatagrams: %lld bytes read from %s:%d", + bytesRead, + qPrintable(remoteAddress.toString()), + remotePort); + + RTPAddress rtpAddress; + rtpAddress.setAddress(remoteAddress); + rtpAddress.setPort(remotePort); + + if (ShouldAcceptData(rtpAddress)) + { + RTPRawPacket *pack = new RTPRawPacket((uint8_t *) m_rtcpBuffer, bytesRead, rtpAddress, curtime, false); + + m_rawPacketQueueLock.lock(); + m_rawPacketQueue.append(pack); + m_rawPacketQueueLock.unlock(); + + emit NewDataAvailable(); + } + } +} + +bool RTPUDPTransmitter::ShouldAcceptData(const RTPAddress& rtpAddress) +{ + if (m_receivemode == RTPTransmitter::AcceptAll) + { + return true; + } + else if (m_receivemode == RTPTransmitter::AcceptSome) + { + std::list::iterator findIt = std::find(m_acceptList.begin(), m_acceptList.end(), rtpAddress); + return findIt != m_acceptList.end(); + } + else if (m_receivemode == RTPTransmitter::IgnoreSome) + { + std::list::iterator findIt = std::find(m_ignoreList.begin(), m_ignoreList.end(), rtpAddress); + return findIt == m_ignoreList.end(); + } + else + { + return false; + } } } // namespace diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h index ae298de9d..48fbbf8b3 100644 --- a/qrtplib/rtpudptransmitter.h +++ b/qrtplib/rtpudptransmitter.h @@ -34,12 +34,17 @@ #define QRTPLIB_RTPUDPTRANSMITTER_H_ #include "rtptransmitter.h" +#include "util/export.h" + +#include #include +#include +#include +#include + #include #include -#include "util/export.h" - #define RTPUDPV4TRANS_HASHSIZE 8317 #define RTPUDPV4TRANS_DEFAULTPORTBASE 5000 #define RTPUDPV4TRANS_RTPRECEIVEBUFFER 32768 @@ -64,8 +69,8 @@ public: } /** Sets the multicast interface IP address. */ - void SetMulticastInterfaceIP(const QHostAddress& mcastGroupAddress) { - m_mcastGroupAddress = mcastGroupAddress; + void SetMulticastInterface(const QNetworkInterface& mcastInterface) { + m_mcastInterface = mcastInterface; } /** Sets the RTP portbase to \c pbase, which has to be an even number @@ -76,21 +81,6 @@ public: m_portbase = pbase; } - /** Passes a list of IP addresses which will be used as the local IP addresses. */ - void SetLocalIPList(const std::list& iplist) - { - m_localIPs = iplist; - } - - /** Clears the list of local IP addresses. - * Clears the list of local IP addresses. An empty list will make the transmission - * component itself determine the local IP addresses. - */ - void ClearLocalIPList() - { - m_localIPs.clear(); - } - /** Returns the IP address which will be used to bind the sockets. */ QHostAddress GetBindIP() const { @@ -98,9 +88,9 @@ public: } /** Returns the multicast interface IP address. */ - QHostAddress GetMulticastInterfaceIP() const + QNetworkInterface GetMulticastInterface() const { - return m_mcastGroupAddress; + return m_mcastInterface; } /** Returns the RTP portbase which will be used (default is 5000). */ @@ -109,12 +99,6 @@ public: return m_portbase; } - /** Returns the list of local IP addresses. */ - const std::list &GetLocalIPList() const - { - return m_localIPs; - } - /** Sets the RTP socket's send buffer size. */ void SetRTPSendBufferSize(int s) { @@ -225,9 +209,8 @@ public: private: QHostAddress m_bindAddress; - QHostAddress m_mcastGroupAddress; + QNetworkInterface m_mcastInterface; uint16_t m_portbase; - std::list m_localIPs; int m_rtpsendbufsz, m_rtprecvbufsz; int m_rtcpsendbufsz, m_rtcprecvbufsz; bool m_rtcpmux; @@ -258,10 +241,15 @@ inline RTPUDPTransmissionParams::RTPUDPTransmissionParams() : class QRTPLIB_API RTPUDPTransmissionInfo: public RTPTransmissionInfo { public: - RTPUDPTransmissionInfo(const std::list& iplist, QUdpSocket *rtpsock, QUdpSocket *rtcpsock, uint16_t rtpport, uint16_t rtcpport) : - RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) + RTPUDPTransmissionInfo( + QHostAddress localIP, + QUdpSocket *rtpsock, + QUdpSocket *rtcpsock, + uint16_t rtpport, + uint16_t rtcpport) : + RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) { - m_localIPlist = iplist; + m_localIP = localIP; m_rtpsocket = rtpsock; m_rtcpsocket = rtcpsock; m_rtpPort = rtpport; @@ -272,12 +260,6 @@ public: { } - /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ - std::list GetLocalIPList() const - { - return m_localIPlist; - } - /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ QUdpSocket *GetRTPSocket() const { @@ -302,7 +284,7 @@ public: return m_rtcpPort; } private: - std::list m_localIPlist; + QHostAddress m_localIP; QUdpSocket *m_rtpsocket, *m_rtcpsocket; uint16_t m_rtpPort, m_rtcpPort; }; @@ -315,81 +297,79 @@ private: * are described by the class RTPUDPTransmissionParams. The GetTransmissionInfo member function * returns an instance of type RTPUDPTransmissionInfo. */ -class QRTPLIB_API RTPUDPTransmitter: public RTPTransmitter +class QRTPLIB_API RTPUDPTransmitter: public QObject, public RTPTransmitter { + Q_OBJECT public: RTPUDPTransmitter(); - ~RTPUDPTransmitter(); + virtual ~RTPUDPTransmitter(); - int Init(); - int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + virtual int Init(); + virtual int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); + virtual int BindSockets(); + virtual void Destroy(); + virtual RTPTransmissionInfo *GetTransmissionInfo(); + virtual void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - bool ComesFromThisTransmitter(const RTPAddress *addr); - std::size_t GetHeaderOverhead() + virtual bool ComesFromThisTransmitter(const RTPAddress& addr); + virtual std::size_t GetHeaderOverhead() { return RTPUDPTRANS_HEADERSIZE; } - int Poll(); - int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); - int AbortWait(); + virtual int SendRTPData(const void *data, std::size_t len); + virtual int SendRTCPData(const void *data, std::size_t len); - int SendRTPData(const void *data, std::size_t len); - int SendRTCPData(const void *data, std::size_t len); + virtual int AddDestination(const RTPAddress &addr); + virtual int DeleteDestination(const RTPAddress &addr); + virtual void ClearDestinations(); - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); + virtual bool SupportsMulticasting(); + virtual int JoinMulticastGroup(const RTPAddress &addr); + virtual int LeaveMulticastGroup(const RTPAddress &addr); - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); + virtual int SetReceiveMode(RTPTransmitter::ReceiveMode m); + virtual int AddToIgnoreList(const RTPAddress &addr); + virtual int DeleteFromIgnoreList(const RTPAddress &addr); + virtual void ClearIgnoreList(); + virtual int AddToAcceptList(const RTPAddress &addr); + virtual int DeleteFromAcceptList(const RTPAddress &addr); + virtual void ClearAcceptList(); + virtual int SetMaximumPacketSize(std::size_t s); - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(std::size_t s); - - bool NewDataAvailable(); // TODO: emit signal instead - RTPRawPacket *GetNextPacket(); // TODO: use a queue + virtual RTPRawPacket *GetNextPacket(); private: - int CreateLocalIPList(); - bool GetLocalIPList_Interfaces(); - void GetLocalIPList_DNS(); - void AddLoopbackAddress(); - void FlushPackets(); - int ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port); - int ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port); - bool ShouldAcceptData(uint32_t srcip, uint16_t srcport); - void ClearAcceptIgnoreInfo(); - bool m_init; bool m_created; bool m_waitingfordata; QUdpSocket *m_rtpsock, *m_rtcpsock; - QHostAddress m_mcastifaceIP; - QHostAddress m_localIP; + QHostAddress m_localIP; //!< from parameters bind IP + QNetworkInterface m_multicastInterface; //!< from parameters multicast interface uint16_t m_rtpPort, m_rtcpPort; RTPTransmitter::ReceiveMode m_receivemode; - uint8_t *m_localhostname; - std::size_t m_localhostnamelength; - - std::list m_rawpacketlist; - - bool m_supportsmulticasting; std::size_t m_maxpacksize; + static const std::size_t m_absoluteMaxPackSize = 65535; + char m_rtpBuffer[m_absoluteMaxPackSize]; + char m_rtcpBuffer[m_absoluteMaxPackSize]; + + std::list m_destinations; + std::list m_acceptList; + std::list m_ignoreList; + QQueue m_rawPacketQueue; + QMutex m_rawPacketQueueLock; bool m_closesocketswhendone; + + bool ShouldAcceptData(const RTPAddress& address); + +private slots: + void readRTPPendingDatagrams(); + void readRTCPPendingDatagrams(); + +signals: + void NewDataAvailable(); }; From e86e2f25a219ee283e7e50a2d1287252e6191210 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Mar 2018 01:43:22 +0100 Subject: [PATCH 054/956] qrtplib: simplify random functions --- qrtplib/rtprandom.cpp | 45 ++++++++---------------------------- qrtplib/rtprandomrand48.cpp | 20 ---------------- qrtplib/rtprandomurandom.cpp | 6 ----- 3 files changed, 10 insertions(+), 61 deletions(-) diff --git a/qrtplib/rtprandom.cpp b/qrtplib/rtprandom.cpp index 035c6d2b9..5fbb5562f 100644 --- a/qrtplib/rtprandom.cpp +++ b/qrtplib/rtprandom.cpp @@ -38,18 +38,11 @@ #include "rtprandomrands.h" #include "rtprandomurandom.h" #include "rtprandomrand48.h" + #include -#ifndef WIN32 #include -#else -#ifndef _WIN32_WCE -#include -#else -#include -#include -#endif // _WIN32_WINCE -#include -#endif // WIN32 + +#include namespace qrtplib { @@ -57,39 +50,21 @@ namespace qrtplib uint32_t RTPRandom::PickSeed() { uint32_t x; -#if defined(WIN32) || defined(_WIN32_WINCE) -#ifndef _WIN32_WCE - x = (uint32_t)_getpid(); - x += (uint32_t)time(0); - x += (uint32_t)clock(); -#else - x = (uint32_t)GetCurrentProcessId(); - - FILETIME ft; - SYSTEMTIME st; - - GetSystemTime(&st); - SystemTimeToFileTime(&st,&ft); - - x += ft.dwLowDateTime; -#endif // _WIN32_WCE - x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); -#else x = (uint32_t) getpid(); - x += (uint32_t) time(0); - x += (uint32_t) clock(); - x ^= (uint32_t) ((uint8_t *) this - (uint8_t *) 0); + QDateTime currentDateTime = QDateTime::currentDateTime(); + x += currentDateTime.toTime_t(); +#if defined(WIN32) + x += QDateTime::currentMSecsSinceEpoch() % 1000; +#else + x += (uint32_t)clock(); #endif + x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); return x; } RTPRandom *RTPRandom::CreateDefaultRandomNumberGenerator() { -#ifdef RTP_HAVE_RAND_S - RTPRandomRandS *r = new RTPRandomRandS(); -#else RTPRandomURandom *r = new RTPRandomURandom(); -#endif // RTP_HAVE_RAND_S RTPRandom *rRet = r; if (r->Init() < 0) // fall back to rand48 diff --git a/qrtplib/rtprandomrand48.cpp b/qrtplib/rtprandomrand48.cpp index 73f8a9e87..c80ccf6aa 100644 --- a/qrtplib/rtprandomrand48.cpp +++ b/qrtplib/rtprandomrand48.cpp @@ -51,12 +51,7 @@ RTPRandomRand48::~RTPRandomRand48() void RTPRandomRand48::SetSeed(uint32_t seed) { - -#ifdef RTP_HAVE_VSUINT64SUFFIX - state = ((uint64_t)seed) << 16 | 0x330Eui64; -#else state = ((uint64_t) seed) << 16 | 0x330EULL; -#endif // RTP_HAVE_VSUINT64SUFFIX } uint8_t RTPRandomRand48::GetRandom8() @@ -75,16 +70,8 @@ uint16_t RTPRandomRand48::GetRandom16() uint32_t RTPRandomRand48::GetRandom32() { - -#ifdef RTP_HAVE_VSUINT64SUFFIX - state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; - - uint32_t x = (uint32_t)((state>>16)&0xffffffffui64); -#else state = ((0x5DEECE66DULL * state) + 0xBULL) & 0x0000ffffffffffffULL; - uint32_t x = (uint32_t) ((state >> 16) & 0xffffffffULL); -#endif // RTP_HAVE_VSUINT64SUFFIX return x; } @@ -92,15 +79,8 @@ uint32_t RTPRandomRand48::GetRandom32() double RTPRandomRand48::GetRandomDouble() { -#ifdef RTP_HAVE_VSUINT64SUFFIX - state = ((0x5DEECE66Dui64*state) + 0xBui64)&0x0000ffffffffffffui64; - - int64_t x = (int64_t)state; -#else state = ((0x5DEECE66DULL * state) + 0xBULL) & 0x0000ffffffffffffULL; - int64_t x = (int64_t) state; -#endif // RTP_HAVE_VSUINT64SUFFIX double y = 3.552713678800500929355621337890625e-15 * (double) x; return y; diff --git a/qrtplib/rtprandomurandom.cpp b/qrtplib/rtprandomurandom.cpp index dc4512f1a..b96b5c6cf 100644 --- a/qrtplib/rtprandomurandom.cpp +++ b/qrtplib/rtprandomurandom.cpp @@ -116,17 +116,11 @@ double RTPRandomURandom::GetRandomDouble() return 0; } -#ifdef RTP_HAVE_VSUINT64SUFFIX - value &= 0x7fffffffffffffffui64; -#else value &= 0x7fffffffffffffffULL; -#endif // RTP_HAVE_VSUINT64SUFFIX - int64_t value2 = (int64_t) value; double x = RTPRANDOM_2POWMIN63 * (double) value2; return x; - } } // end namespace From 3c4d40ac733e253cc82b5bf7a89408e1cc871225 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Mar 2018 16:40:53 +0100 Subject: [PATCH 055/956] qrtplib: Windows build --- httpserver/httpserver.pro | 2 + qrtplib/CMakeLists.txt | 174 +++++++++++++++++++------------------- qrtplib/qrtplib.pro | 96 +++++++++++++++++++++ qrtplib/rtpsession.cpp | 63 ++------------ sdrangel.windows.pro | 1 + 5 files changed, 194 insertions(+), 142 deletions(-) create mode 100644 qrtplib/qrtplib.pro diff --git a/httpserver/httpserver.pro b/httpserver/httpserver.pro index 33e3b89a4..908950ade 100644 --- a/httpserver/httpserver.pro +++ b/httpserver/httpserver.pro @@ -10,6 +10,8 @@ TEMPLATE = lib TARGET = httpserver INCLUDEPATH += $$PWD +INCLUDEPATH += ../sdrbase + QMAKE_CXXFLAGS += -std=c++11 CONFIG(Release):build_subdir = release diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 979fd508a..75ba994c0 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -1,100 +1,100 @@ project(qrtplib) set (qrtplib_HEADERS - rtcpapppacket.h - rtcpbyepacket.h - rtcpcompoundpacket.h - rtcpcompoundpacketbuilder.h - rtcppacket.h - rtcppacketbuilder.h - rtcprrpacket.h - rtcpscheduler.h - rtcpsdesinfo.h - rtcpsdespacket.h - rtcpsrpacket.h - rtcpunknownpacket.h - rtpaddress.h - rtpcollisionlist.h - rtpconfig.h - rtpdefines.h - rtpendian.h - rtperrors.h - rtphashtable.h - rtpinternalsourcedata.h -# rtpipv4address.h -# rtpipv4destination.h - rtpkeyhashtable.h - rtplibraryversion.h - rtppacket.h - rtppacketbuilder.h - rtprandom.h - rtprandomrand48.h - rtprandomrands.h - rtprandomurandom.h - rtprawpacket.h - rtpsession.h - rtpsessionparams.h - rtpsessionsources.h - rtpsourcedata.h - rtpsources.h - rtpstructs.h - rtptimeutilities.h - rtptransmitter.h - rtptypes_win.h - rtptypes.h - rtpudpransmitter.h -# rtpudpv4transmitter.h + rtcpapppacket.h + rtcpbyepacket.h + rtcpcompoundpacket.h + rtcpcompoundpacketbuilder.h + rtcppacket.h + rtcppacketbuilder.h + rtcprrpacket.h + rtcpscheduler.h + rtcpsdesinfo.h + rtcpsdespacket.h + rtcpsrpacket.h + rtcpunknownpacket.h + rtpaddress.h + rtpcollisionlist.h + rtpconfig.h + rtpdefines.h + rtpendian.h + rtperrors.h + rtphashtable.h + rtpinternalsourcedata.h +# rtpipv4address.h +# rtpipv4destination.h + rtpkeyhashtable.h + rtplibraryversion.h + rtppacket.h + rtppacketbuilder.h + rtprandom.h + rtprandomrand48.h + rtprandomrands.h + rtprandomurandom.h + rtprawpacket.h + rtpsession.h + rtpsessionparams.h + rtpsessionsources.h + rtpsourcedata.h + rtpsources.h + rtpstructs.h + rtptimeutilities.h + rtptransmitter.h + rtptypes_win.h + rtptypes.h + rtpudptransmitter.h +# rtpudpv4transmitter.h # rtpudpv4transmitternobind.h -# rtpexternaltransmitter.h - rtpsocketutil.h - rtpabortdescriptors.h - rtpselect.h -# rtptcpaddress.h -# rtptcptransmitter.h - ) +# rtpexternaltransmitter.h + rtpsocketutil.h + rtpabortdescriptors.h + rtpselect.h +# rtptcpaddress.h +# rtptcptransmitter.h + ) set(qrtplib_SOURCES - rtcpapppacket.cpp - rtcpbyepacket.cpp - rtcpcompoundpacket.cpp - rtcpcompoundpacketbuilder.cpp - rtcppacketbuilder.cpp - rtcprrpacket.cpp - rtcpscheduler.cpp - rtcpsdesinfo.cpp - rtcpsdespacket.cpp - rtcpsrpacket.cpp - rtpaddress.cpp - rtpcollisionlist.cpp - rtperrors.cpp - rtpinternalsourcedata.cpp -# rtpipv4address.cpp -# rtpipv4destination.cpp - rtplibraryversion.cpp - rtppacket.cpp - rtppacketbuilder.cpp - rtprandom.cpp - rtprandomrand48.cpp - rtprandomrands.cpp - rtprandomurandom.cpp - rtpsession.cpp - rtpsessionparams.cpp - rtpsessionsources.cpp - rtpsourcedata.cpp - rtpsources.cpp - rtptimeutilities.cpp - rtpudptransmitter.cpp -# rtpudpv4transmitter.cpp + rtcpapppacket.cpp + rtcpbyepacket.cpp + rtcpcompoundpacket.cpp + rtcpcompoundpacketbuilder.cpp + rtcppacketbuilder.cpp + rtcprrpacket.cpp + rtcpscheduler.cpp + rtcpsdesinfo.cpp + rtcpsdespacket.cpp + rtcpsrpacket.cpp + rtpaddress.cpp + rtpcollisionlist.cpp + rtperrors.cpp + rtpinternalsourcedata.cpp +# rtpipv4address.cpp +# rtpipv4destination.cpp + rtplibraryversion.cpp + rtppacket.cpp + rtppacketbuilder.cpp + rtprandom.cpp + rtprandomrand48.cpp + rtprandomrands.cpp + rtprandomurandom.cpp + rtpsession.cpp + rtpsessionparams.cpp + rtpsessionsources.cpp + rtpsourcedata.cpp + rtpsources.cpp + rtptimeutilities.cpp + rtpudptransmitter.cpp +# rtpudpv4transmitter.cpp # rtpudpv4transmitternobind.cpp -# rtpexternaltransmitter.cpp - rtpabortdescriptors.cpp -# rtptcpaddress.cpp -# rtptcptransmitter.cpp - ) +# rtpexternaltransmitter.cpp + rtpabortdescriptors.cpp +# rtptcpaddress.cpp +# rtptcptransmitter.cpp + ) include_directories( . - ${CMAKE_SOURCE_DIR}/sdrbase + ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/qrtplib/qrtplib.pro b/qrtplib/qrtplib.pro new file mode 100644 index 000000000..4e8140f44 --- /dev/null +++ b/qrtplib/qrtplib.pro @@ -0,0 +1,96 @@ +#-------------------------------------------------------- +# +# Pro file for Windows builds with Qt Creator +# +#-------------------------------------------------------- + +QT += core network + +TEMPLATE = lib +TARGET = qrtplib + +INCLUDEPATH += $$PWD +INCLUDEPATH += ../sdrbase + +QMAKE_CXXFLAGS += -std=c++11 + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +# Enable very detailed debug messages when compiling the debug version +CONFIG(debug, debug|release) { + DEFINES += SUPERVERBOSE +} + +HEADERS += $$PWD/rtcpapppacket.h \ + $$PWD/rtcpbyepacket.h \ + $$PWD/rtcpcompoundpacket.h \ + $$PWD/rtcpcompoundpacketbuilder.h \ + $$PWD/rtcppacket.h \ + $$PWD/rtcppacketbuilder.h \ + $$PWD/rtcprrpacket.h \ + $$PWD/rtcpscheduler.h \ + $$PWD/rtcpsdesinfo.h \ + $$PWD/rtcpsdespacket.h \ + $$PWD/rtcpsrpacket.h \ + $$PWD/rtcpunknownpacket.h \ + $$PWD/rtpaddress.h \ + $$PWD/rtpcollisionlist.h \ + $$PWD/rtpconfig.h \ + $$PWD/rtpdefines.h \ + $$PWD/rtpendian.h \ + $$PWD/rtperrors.h \ + $$PWD/rtphashtable.h \ + $$PWD/rtpinternalsourcedata.h \ + $$PWD/rtpkeyhashtable.h \ + $$PWD/rtplibraryversion.h \ + $$PWD/rtppacket.h \ + $$PWD/rtppacketbuilder.h \ + $$PWD/rtprandom.h \ + $$PWD/rtprandomrand48.h \ + $$PWD/rtprandomrands.h \ + $$PWD/rtprandomurandom.h \ + $$PWD/rtprawpacket.h \ + $$PWD/rtpsession.h \ + $$PWD/rtpsessionparams.h \ + $$PWD/rtpsessionsources.h \ + $$PWD/rtpsourcedata.h \ + $$PWD/rtpsources.h \ + $$PWD/rtpstructs.h \ + $$PWD/rtptimeutilities.h \ + $$PWD/rtptransmitter.h \ + $$PWD/rtptypes_win.h \ + $$PWD/rtptypes.h \ + $$PWD/rtpudptransmitter.h \ + $$PWD/rtpsocketutil.h \ + $$PWD/rtpselect.h + + +SOURCES += $$PWD/rtcpapppacket.cpp \ + $$PWD/rtcpbyepacket.cpp \ + $$PWD/rtcpcompoundpacket.cpp \ + $$PWD/rtcpcompoundpacketbuilder.cpp \ + $$PWD/rtcppacketbuilder.cpp \ + $$PWD/rtcprrpacket.cpp \ + $$PWD/rtcpscheduler.cpp \ + $$PWD/rtcpsdesinfo.cpp \ + $$PWD/rtcpsdespacket.cpp \ + $$PWD/rtcpsrpacket.cpp \ + $$PWD/rtpaddress.cpp \ + $$PWD/rtpcollisionlist.cpp \ + $$PWD/rtperrors.cpp \ + $$PWD/rtpinternalsourcedata.cpp \ + $$PWD/rtplibraryversion.cpp \ + $$PWD/rtppacket.cpp \ + $$PWD/rtppacketbuilder.cpp \ + $$PWD/rtprandom.cpp \ + $$PWD/rtprandomrand48.cpp \ + $$PWD/rtprandomrands.cpp \ + $$PWD/rtprandomurandom.cpp \ + $$PWD/rtpsession.cpp \ + $$PWD/rtpsessionparams.cpp \ + $$PWD/rtpsessionsources.cpp \ + $$PWD/rtpsourcedata.cpp \ + $$PWD/rtpsources.cpp \ + $$PWD/rtptimeutilities.cpp \ + $$PWD/rtpudptransmitter.cpp diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index c04c7b95b..94dedda1d 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -48,12 +48,11 @@ #include "rtcpcompoundpacket.h" #endif // RTP_SUPPORT_SENDAPP #include "rtpinternalutils.h" -#ifndef WIN32 + #include #include -#else -#include -#endif // WIN32 + +#include namespace qrtplib @@ -1145,51 +1144,6 @@ int RTPSession::ProcessPolledData() int RTPSession::CreateCNAME(uint8_t *buffer, std::size_t *bufferlength, bool resolve __attribute__((unused))) { -#ifndef WIN32 - bool gotlogin = true; -#ifdef RTP_SUPPORT_GETLOGINR - buffer[0] = 0; - if (getlogin_r((char *) buffer, *bufferlength) != 0) - gotlogin = false; - else - { - if (buffer[0] == 0) - gotlogin = false; - } - - if (!gotlogin) // try regular getlogin - { - char *loginname = getlogin(); - if (loginname == 0) - gotlogin = false; - else - strncpy((char *) buffer, loginname, *bufferlength); - } -#else - char *loginname = getlogin(); - if (loginname == 0) - gotlogin = false; - else - strncpy((char *)buffer,loginname,*bufferlength); -#endif // RTP_SUPPORT_GETLOGINR - if (!gotlogin) - { - char *logname = getenv("LOGNAME"); - if (logname == 0) - return ERR_RTP_SESSION_CANTGETLOGINNAME; - strncpy((char *) buffer, logname, *bufferlength); - } -#else // Win32 version - -#ifndef _WIN32_WCE - DWORD len = *bufferlength; - if (!GetUserName((LPTSTR)buffer,&len)) - strncpy((char *)buffer,"unknown",*bufferlength); -#else - strncpy((char *)buffer,"unknown",*bufferlength); -#endif // _WIN32_WCE - -#endif // WIN32 buffer[*bufferlength - 1] = 0; std::size_t offset = strlen((const char *) buffer); @@ -1199,16 +1153,15 @@ int RTPSession::CreateCNAME(uint8_t *buffer, std::size_t *bufferlength, bool res std::size_t buflen2 = *bufferlength - offset; - char hostname[1024]; + QString hostnameStr = QHostInfo::localHostName(); + int hostnameSize = hostnameStr.size(); - strncpy(hostname, "localhost", 1024); // just in case gethostname fails + strncpy((char * )(buffer + offset), hostnameStr.toStdString().c_str(), buflen2); + *bufferlength = offset + hostnameSize; - gethostname(hostname, 1024); - strncpy((char * )(buffer + offset), hostname, buflen2); - - *bufferlength = offset + strlen(hostname); if (*bufferlength > RTCP_SDES_MAXITEMLENGTH) *bufferlength = RTCP_SDES_MAXITEMLENGTH; + return 0; } diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index f68e51083..d6645e29c 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -11,6 +11,7 @@ CONFIG += ordered SUBDIRS = serialdv SUBDIRS += httpserver SUBDIRS += logging +SUBDIRS += qrtplib SUBDIRS += swagger SUBDIRS += sdrbase SUBDIRS += sdrgui From 914b8a4d97f69cd6aee12040987d1ff2740656fb Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Mar 2018 17:32:14 +0100 Subject: [PATCH 056/956] Windows build fixes --- liblimesuite/liblimesuite.pro | 4 ++-- logging/logging.pro | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/liblimesuite/liblimesuite.pro b/liblimesuite/liblimesuite.pro index 32cdee284..e119a1a0a 100644 --- a/liblimesuite/liblimesuite.pro +++ b/liblimesuite/liblimesuite.pro @@ -17,8 +17,8 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" CONFIG(MINGW32):INCLUDEPATH += "..\libsqlite3\src" CONFIG(MINGW64):INCLUDEPATH += "..\libsqlite3\src" diff --git a/logging/logging.pro b/logging/logging.pro index 92b2b9d90..7aae18fd7 100644 --- a/logging/logging.pro +++ b/logging/logging.pro @@ -10,6 +10,8 @@ TEMPLATE = lib TARGET = logging INCLUDEPATH += $$PWD +INCLUDEPATH += ../sdrbase + QMAKE_CXXFLAGS += -std=c++11 CONFIG(Release):build_subdir = release From 915b865e6a3793f17b1e8ab968f12185692ec77c Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Mar 2018 21:27:06 +0100 Subject: [PATCH 057/956] qrtplib: file cleanup --- qrtplib/CMakeLists.txt | 20 - qrtplib/qrtplib.pro | 4 - qrtplib/rtpabortdescriptors.cpp | 258 ---- qrtplib/rtpabortdescriptors.h | 111 -- qrtplib/rtpexternaltransmitter.cpp | 634 --------- qrtplib/rtpexternaltransmitter.h | 254 ---- qrtplib/rtphashtable.h | 333 ----- qrtplib/rtpipv4address.cpp | 71 - qrtplib/rtpipv4address.h | 176 --- qrtplib/rtpipv4destination.cpp | 50 - qrtplib/rtpipv4destination.h | 138 -- qrtplib/rtplibraryversion.cpp | 57 - qrtplib/rtplibraryversion.h | 93 -- qrtplib/rtpselect.h | 190 --- qrtplib/rtpsession.h | 1 - qrtplib/rtptcpaddress.cpp | 66 - qrtplib/rtptcpaddress.h | 89 -- qrtplib/rtptcptransmitter.cpp | 831 ----------- qrtplib/rtptcptransmitter.h | 229 --- qrtplib/rtpudpv4transmitter.cpp | 1834 ------------------------ qrtplib/rtpudpv4transmitter.h | 488 ------- qrtplib/rtpudpv4transmitternobind.cpp | 1836 ------------------------- qrtplib/rtpudpv4transmitternobind.h | 492 ------- 23 files changed, 8255 deletions(-) delete mode 100644 qrtplib/rtpabortdescriptors.cpp delete mode 100644 qrtplib/rtpabortdescriptors.h delete mode 100644 qrtplib/rtpexternaltransmitter.cpp delete mode 100644 qrtplib/rtpexternaltransmitter.h delete mode 100644 qrtplib/rtphashtable.h delete mode 100644 qrtplib/rtpipv4address.cpp delete mode 100644 qrtplib/rtpipv4address.h delete mode 100644 qrtplib/rtpipv4destination.cpp delete mode 100644 qrtplib/rtpipv4destination.h delete mode 100644 qrtplib/rtplibraryversion.cpp delete mode 100644 qrtplib/rtplibraryversion.h delete mode 100644 qrtplib/rtpselect.h delete mode 100644 qrtplib/rtptcpaddress.cpp delete mode 100644 qrtplib/rtptcpaddress.h delete mode 100644 qrtplib/rtptcptransmitter.cpp delete mode 100644 qrtplib/rtptcptransmitter.h delete mode 100644 qrtplib/rtpudpv4transmitter.cpp delete mode 100644 qrtplib/rtpudpv4transmitter.h delete mode 100644 qrtplib/rtpudpv4transmitternobind.cpp delete mode 100644 qrtplib/rtpudpv4transmitternobind.h diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 75ba994c0..a5472b153 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -19,12 +19,8 @@ set (qrtplib_HEADERS rtpdefines.h rtpendian.h rtperrors.h - rtphashtable.h rtpinternalsourcedata.h -# rtpipv4address.h -# rtpipv4destination.h rtpkeyhashtable.h - rtplibraryversion.h rtppacket.h rtppacketbuilder.h rtprandom.h @@ -43,14 +39,7 @@ set (qrtplib_HEADERS rtptypes_win.h rtptypes.h rtpudptransmitter.h -# rtpudpv4transmitter.h -# rtpudpv4transmitternobind.h -# rtpexternaltransmitter.h rtpsocketutil.h - rtpabortdescriptors.h - rtpselect.h -# rtptcpaddress.h -# rtptcptransmitter.h ) set(qrtplib_SOURCES @@ -68,9 +57,6 @@ set(qrtplib_SOURCES rtpcollisionlist.cpp rtperrors.cpp rtpinternalsourcedata.cpp -# rtpipv4address.cpp -# rtpipv4destination.cpp - rtplibraryversion.cpp rtppacket.cpp rtppacketbuilder.cpp rtprandom.cpp @@ -84,12 +70,6 @@ set(qrtplib_SOURCES rtpsources.cpp rtptimeutilities.cpp rtpudptransmitter.cpp -# rtpudpv4transmitter.cpp -# rtpudpv4transmitternobind.cpp -# rtpexternaltransmitter.cpp - rtpabortdescriptors.cpp -# rtptcpaddress.cpp -# rtptcptransmitter.cpp ) include_directories( diff --git a/qrtplib/qrtplib.pro b/qrtplib/qrtplib.pro index 4e8140f44..50dc5d777 100644 --- a/qrtplib/qrtplib.pro +++ b/qrtplib/qrtplib.pro @@ -40,10 +40,7 @@ HEADERS += $$PWD/rtcpapppacket.h \ $$PWD/rtpdefines.h \ $$PWD/rtpendian.h \ $$PWD/rtperrors.h \ - $$PWD/rtphashtable.h \ $$PWD/rtpinternalsourcedata.h \ - $$PWD/rtpkeyhashtable.h \ - $$PWD/rtplibraryversion.h \ $$PWD/rtppacket.h \ $$PWD/rtppacketbuilder.h \ $$PWD/rtprandom.h \ @@ -80,7 +77,6 @@ SOURCES += $$PWD/rtcpapppacket.cpp \ $$PWD/rtpcollisionlist.cpp \ $$PWD/rtperrors.cpp \ $$PWD/rtpinternalsourcedata.cpp \ - $$PWD/rtplibraryversion.cpp \ $$PWD/rtppacket.cpp \ $$PWD/rtppacketbuilder.cpp \ $$PWD/rtprandom.cpp \ diff --git a/qrtplib/rtpabortdescriptors.cpp b/qrtplib/rtpabortdescriptors.cpp deleted file mode 100644 index 8753154bc..000000000 --- a/qrtplib/rtpabortdescriptors.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtpabortdescriptors.h" -#include "rtpsocketutilinternal.h" -#include "rtperrors.h" -#include "rtpselect.h" - -namespace qrtplib -{ - -RTPAbortDescriptors::RTPAbortDescriptors() -{ - m_descriptors[0] = RTPSOCKERR; - m_descriptors[1] = RTPSOCKERR; - m_init = false; -} - -RTPAbortDescriptors::~RTPAbortDescriptors() -{ - Destroy(); -} - -#ifdef RTP_SOCKETTYPE_WINSOCK - -int RTPAbortDescriptors::Init() -{ - if (m_init) - return ERR_RTP_ABORTDESC_ALREADYINIT; - - SOCKET listensock; - int size; - struct sockaddr_in addr; - - listensock = socket(PF_INET,SOCK_STREAM,0); - if (listensock == RTPSOCKERR) - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - - memset(&addr,0,sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - if (bind(listensock,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(listensock); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } - - memset(&addr,0,sizeof(struct sockaddr_in)); - size = sizeof(struct sockaddr_in); - if (getsockname(listensock,(struct sockaddr*)&addr,&size) != 0) - { - RTPCLOSE(listensock); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } - - unsigned short connectport = ntohs(addr.sin_port); - - m_descriptors[0] = socket(PF_INET,SOCK_STREAM,0); - if (m_descriptors[0] == RTPSOCKERR) - { - RTPCLOSE(listensock); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } - - memset(&addr,0,sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - if (bind(m_descriptors[0],(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(listensock); - RTPCLOSE(m_descriptors[0]); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } - - if (listen(listensock,1) != 0) - { - RTPCLOSE(listensock); - RTPCLOSE(m_descriptors[0]); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } - - memset(&addr,0,sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr("127.0.0.1"); - addr.sin_port = htons(connectport); - - if (connect(m_descriptors[0],(struct sockaddr *)&addr,sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(listensock); - RTPCLOSE(m_descriptors[0]); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } - - memset(&addr,0,sizeof(struct sockaddr_in)); - size = sizeof(struct sockaddr_in); - m_descriptors[1] = accept(listensock,(struct sockaddr *)&addr,&size); - if (m_descriptors[1] == RTPSOCKERR) - { - RTPCLOSE(listensock); - RTPCLOSE(m_descriptors[0]); - return ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS; - } - - // okay, got the connection, close the listening socket - - RTPCLOSE(listensock); - - m_init = true; - return 0; -} - -void RTPAbortDescriptors::Destroy() -{ - if (!m_init) - return; - - RTPCLOSE(m_descriptors[0]); - RTPCLOSE(m_descriptors[1]); - m_descriptors[0] = RTPSOCKERR; - m_descriptors[1] = RTPSOCKERR; - - m_init = false; -} - -int RTPAbortDescriptors::SendAbortSignal() -{ - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; - - send(m_descriptors[1],"*",1,0); - return 0; -} - -int RTPAbortDescriptors::ReadSignallingByte() -{ - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; - - char buf[1]; - - recv(m_descriptors[0],buf,1,0); - return 0; -} - -#else // unix-style - -int RTPAbortDescriptors::Init() -{ - if (m_init) - return ERR_RTP_ABORTDESC_ALREADYINIT; - - if (pipe(m_descriptors) < 0) - return ERR_RTP_ABORTDESC_CANTCREATEPIPE; - - m_init = true; - return 0; -} - -void RTPAbortDescriptors::Destroy() -{ - if (!m_init) - return; - - close(m_descriptors[0]); - close(m_descriptors[1]); - m_descriptors[0] = RTPSOCKERR; - m_descriptors[1] = RTPSOCKERR; - - m_init = false; -} - -int RTPAbortDescriptors::SendAbortSignal() -{ - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; - - if (write(m_descriptors[1], "*", 1)) - { - // To get rid of __wur related compiler warnings - } - - return 0; -} - -int RTPAbortDescriptors::ReadSignallingByte() -{ - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; - - unsigned char buf[1]; - - if (read(m_descriptors[0], buf, 1)) - { - // To get rid of __wur related compiler warnings - } - return 0; -} - -#endif // RTP_SOCKETTYPE_WINSOCK - -// Keep calling 'ReadSignallingByte' until there's no byte left -int RTPAbortDescriptors::ClearAbortSignal() -{ - if (!m_init) - return ERR_RTP_ABORTDESC_NOTINIT; - - bool done = false; - while (!done) - { - int8_t isset = 0; - - // Not used: struct timeval tv = { 0, 0 }; - - int status = RTPSelect(&m_descriptors[0], &isset, 1, RTPTime(0)); - if (status < 0) - return status; - - if (!isset) - done = true; - else - { - int status = ReadSignallingByte(); - if (status < 0) - return status; - } - } - - return 0; -} - -} // end namespace diff --git a/qrtplib/rtpabortdescriptors.h b/qrtplib/rtpabortdescriptors.h deleted file mode 100644 index cab7f68b6..000000000 --- a/qrtplib/rtpabortdescriptors.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpabortdescriptors.h - */ - -#ifndef RTPABORTDESCRIPTORS_H - -#define RTPABORTDESCRIPTORS_H - -#include "rtpconfig.h" -#include "rtpsocketutil.h" - -#include "util/export.h" - -namespace qrtplib -{ - -/** - * Helper class for several RTPTransmitter instances, to be able to cancel a - * call to 'select', 'poll' or 'WSAPoll'. - * - * This is a helper class for several RTPTransmitter instances. Typically a - * call to 'select' (or 'poll' or 'WSAPoll', depending on the platform) is used - * to wait for incoming data for a certain time. To be able to cancel this wait - * from another thread, this class provides a socket descriptor that's compatible - * with e.g. the 'select' call, and to which data can be sent using - * RTPAbortDescriptors::SendAbortSignal. If the descriptor is included in the - * 'select' call, the function will detect incoming data and the function stops - * waiting for incoming data. - * - * The class can be useful in case you'd like to create an implementation which - * uses a single poll thread for several RTPSession and RTPTransmitter instances. - * This idea is further illustrated in `example8.cpp`. - */ -class QRTPLIB_API RTPAbortDescriptors -{ -public: - RTPAbortDescriptors(); - ~RTPAbortDescriptors(); - - /** Initializes this instance. */ - int Init(); - - /** Returns the socket descriptor that can be included in a call to - * 'select' (for example).*/ - SocketType GetAbortSocket() const - { - return m_descriptors[0]; - } - - /** Returns a flag indicating if this instance was initialized. */ - bool IsInitialized() const - { - return m_init; - } - - /** De-initializes this instance. */ - void Destroy(); - - /** Send a signal to the socket that's returned by RTPAbortDescriptors::GetAbortSocket, - * causing the 'select' call to detect that data is available, making the call - * end. */ - int SendAbortSignal(); - - /** For each RTPAbortDescriptors::SendAbortSignal function that's called, a call - * to this function can be made to clear the state again. */ - int ReadSignallingByte(); - - /** Similar to ReadSignallingByte::ReadSignallingByte, this function clears the signalling - * state, but this also works independently from the amount of times that - * RTPAbortDescriptors::SendAbortSignal was called. */ - int ClearAbortSignal(); -private: - SocketType m_descriptors[2]; - bool m_init; -}; - -} // end namespace - -#endif // RTPABORTDESCRIPTORS_H diff --git a/qrtplib/rtpexternaltransmitter.cpp b/qrtplib/rtpexternaltransmitter.cpp deleted file mode 100644 index 83c6cec08..000000000 --- a/qrtplib/rtpexternaltransmitter.cpp +++ /dev/null @@ -1,634 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtpexternaltransmitter.h" -#include "rtprawpacket.h" -#include "rtptimeutilities.h" -#include "rtpdefines.h" -#include "rtperrors.h" -#include "rtpsocketutilinternal.h" -#include "rtpselect.h" -#include -#include - -#include - -namespace qrtplib -{ - -RTPExternalTransmitter::RTPExternalTransmitter() : - packetinjector((RTPExternalTransmitter *) this) -{ - created = false; - init = false; -} - -RTPExternalTransmitter::~RTPExternalTransmitter() -{ - Destroy(); -} - -int RTPExternalTransmitter::Init(bool tsafe) -{ - if (init) - return ERR_RTP_EXTERNALTRANS_ALREADYINIT; - - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; - - init = true; - return 0; -} - -int RTPExternalTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) -{ - const RTPExternalTransmissionParams *params; - int status; - - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; - - if (created) - { - return ERR_RTP_EXTERNALTRANS_ALREADYCREATED; - } - - // Obtain transmission parameters - - if (transparams == 0) - { - return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; - } - if (transparams->GetTransmissionProtocol() != RTPTransmitter::ExternalProto) - { - return ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS; - } - - params = (const RTPExternalTransmissionParams *) transparams; - - if ((status = m_abortDesc.Init()) < 0) - { - return status; - } - m_abortCount = 0; - - maxpacksize = maximumpacketsize; - sender = params->GetSender(); - headersize = params->GetAdditionalHeaderSize(); - - localhostname = 0; - localhostnamelength = 0; - - waitingfordata = false; - created = true; - return 0; -} - -void RTPExternalTransmitter::Destroy() -{ - if (!init) - return; - - if (!created) - { - return; - } - - if (localhostname) - { - delete[] localhostname; - localhostname = 0; - localhostnamelength = 0; - } - - FlushPackets(); - created = false; - - if (waitingfordata) - { - m_abortDesc.SendAbortSignal(); - m_abortCount++; - m_abortDesc.Destroy(); - // to make sure that the WaitForIncomingData function ended - - } - else - m_abortDesc.Destroy(); - -} - -RTPTransmissionInfo *RTPExternalTransmitter::GetTransmissionInfo() -{ - if (!init) - return 0; - - RTPTransmissionInfo *tinf = new RTPExternalTransmissionInfo(&packetinjector); - return tinf; -} - -void RTPExternalTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) -{ - if (!init) - return; - - delete i; -} - -int RTPExternalTransmitter::GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) -{ - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; - - if (!created) - { - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - - if (localhostname == 0) - { -// We'll just use 'gethostname' for simplicity - - char name[1024]; - - if (gethostname(name, 1023) != 0) - strcpy(name, "localhost"); // failsafe - else - name[1023] = 0; // ensure null-termination - - localhostnamelength = strlen(name); - localhostname = new uint8_t[localhostnamelength + 1]; - - memcpy(localhostname, name, localhostnamelength); - localhostname[localhostnamelength] = 0; - } - - if ((*bufferlength) < localhostnamelength) - { - *bufferlength = localhostnamelength; // tell the application the required size of the buffer - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } - - memcpy(buffer, localhostname, localhostnamelength); - *bufferlength = localhostnamelength; - - return 0; -} - -bool RTPExternalTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) -{ - - bool value = false; - if (sender) - value = sender->ComesFromThisSender(addr); - return value; -} - -int RTPExternalTransmitter::Poll() -{ - return 0; -} - -int RTPExternalTransmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) -{ - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; - - if (!created) - { - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (waitingfordata) - { - return ERR_RTP_EXTERNALTRANS_ALREADYWAITING; - } - - waitingfordata = true; - - if (!rawpacketlist.empty()) - { - if (dataavailable != 0) - *dataavailable = true; - waitingfordata = false; - return 0; - } - - int8_t isset = 0; - SocketType abortSock = m_abortDesc.GetAbortSocket(); - int status = RTPSelect(&abortSock, &isset, 1, delay); - if (status < 0) - { - - waitingfordata = false; - - return status; - } - - waitingfordata = false; - if (!created) // destroy called - { - - return 0; - } - - // if aborted, read from abort buffer - if (isset) - { - m_abortDesc.ClearAbortSignal(); - m_abortCount = 0; - } - - if (dataavailable != 0) - { - if (rawpacketlist.empty()) - *dataavailable = false; - else - *dataavailable = true; - } - - return 0; -} - -int RTPExternalTransmitter::AbortWait() -{ - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; - - if (!created) - { - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (!waitingfordata) - { - return ERR_RTP_EXTERNALTRANS_NOTWAITING; - } - - m_abortDesc.SendAbortSignal(); - m_abortCount++; - - return 0; -} - -int RTPExternalTransmitter::SendRTPData(const void *data, std::size_t len) -{ - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; - - if (!created) - { - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (len > maxpacksize) - { - return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; - } - - if (!sender) - { - return ERR_RTP_EXTERNALTRANS_NOSENDER; - } - - if (!sender->SendRTP(data, len)) - return ERR_RTP_EXTERNALTRANS_SENDERROR; - - return 0; -} - -int RTPExternalTransmitter::SendRTCPData(const void *data, std::size_t len) -{ - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; - - if (!created) - { - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (len > maxpacksize) - { - return ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG; - } - - if (!sender) - { - return ERR_RTP_EXTERNALTRANS_NOSENDER; - } - - if (!sender->SendRTCP(data, len)) - return ERR_RTP_EXTERNALTRANS_SENDERROR; - - return 0; -} - -int RTPExternalTransmitter::AddDestination(const RTPAddress &) -{ - return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; -} - -int RTPExternalTransmitter::DeleteDestination(const RTPAddress &) -{ - return ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED; -} - -void RTPExternalTransmitter::ClearDestinations() -{ -} - -bool RTPExternalTransmitter::SupportsMulticasting() -{ - return false; -} - -int RTPExternalTransmitter::JoinMulticastGroup(const RTPAddress &) -{ - return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; -} - -int RTPExternalTransmitter::LeaveMulticastGroup(const RTPAddress &) -{ - return ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT; -} - -void RTPExternalTransmitter::LeaveAllMulticastGroups() -{ -} - -int RTPExternalTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) -{ - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; - - if (!created) - { - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - if (m != RTPTransmitter::AcceptAll) - { - return ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE; - } - return 0; -} - -int RTPExternalTransmitter::AddToIgnoreList(const RTPAddress &) -{ - return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; -} - -int RTPExternalTransmitter::DeleteFromIgnoreList(const RTPAddress &) -{ - return ERR_RTP_EXTERNALTRANS_NOIGNORELIST; -} - -void RTPExternalTransmitter::ClearIgnoreList() -{ -} - -int RTPExternalTransmitter::AddToAcceptList(const RTPAddress &) -{ - return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; -} - -int RTPExternalTransmitter::DeleteFromAcceptList(const RTPAddress &) -{ - return ERR_RTP_EXTERNALTRANS_NOACCEPTLIST; -} - -void RTPExternalTransmitter::ClearAcceptList() -{ -} - -int RTPExternalTransmitter::SetMaximumPacketSize(std::size_t s) -{ - if (!init) - return ERR_RTP_EXTERNALTRANS_NOTINIT; - - if (!created) - { - return ERR_RTP_EXTERNALTRANS_NOTCREATED; - } - maxpacksize = s; - return 0; -} - -bool RTPExternalTransmitter::NewDataAvailable() -{ - if (!init) - return false; - - bool v; - - if (!created) - v = false; - else - { - if (rawpacketlist.empty()) - v = false; - else - v = true; - } - - return v; -} - -RTPRawPacket *RTPExternalTransmitter::GetNextPacket() -{ - if (!init) - return 0; - - RTPRawPacket *p; - - if (!created) - { - return 0; - } - if (rawpacketlist.empty()) - { - return 0; - } - - p = *(rawpacketlist.begin()); - rawpacketlist.pop_front(); - - return p; -} - -// Here the private functions start... - -void RTPExternalTransmitter::FlushPackets() -{ - std::list::const_iterator it; - - for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) - delete *it; - rawpacketlist.clear(); -} - -void RTPExternalTransmitter::InjectRTP(const void *data, std::size_t len, const RTPAddress &a) -{ - if (!init) - return; - - if (!created) - { - return; - } - - RTPAddress *addr = a.CreateCopy(); - if (addr == 0) - return; - - uint8_t *datacopy; - - datacopy = new uint8_t[len]; - if (datacopy == 0) - { - delete addr; - return; - } - memcpy(datacopy, data, len); - - RTPTime curtime = RTPTime::CurrentTime(); - RTPRawPacket *pack; - - pack = new RTPRawPacket(datacopy, len, addr, curtime, true); - if (pack == 0) - { - delete addr; - delete[] localhostname; - return; - } - rawpacketlist.push_back(pack); - - if (m_abortCount == 0) - { - m_abortDesc.SendAbortSignal(); - m_abortCount++; - } - -} - -void RTPExternalTransmitter::InjectRTCP(const void *data, std::size_t len, const RTPAddress &a) -{ - if (!init) - return; - - if (!created) - { - return; - } - - RTPAddress *addr = a.CreateCopy(); - if (addr == 0) - return; - - uint8_t *datacopy; - - datacopy = new uint8_t[len]; - if (datacopy == 0) - { - delete addr; - return; - } - memcpy(datacopy, data, len); - - RTPTime curtime = RTPTime::CurrentTime(); - RTPRawPacket *pack; - - pack = new RTPRawPacket(datacopy, len, addr, curtime, false); - if (pack == 0) - { - delete addr; - delete[] localhostname; - return; - } - rawpacketlist.push_back(pack); - - if (m_abortCount == 0) - { - m_abortDesc.SendAbortSignal(); - m_abortCount++; - } - -} - -void RTPExternalTransmitter::InjectRTPorRTCP(const void *data, std::size_t len, const RTPAddress &a) -{ - if (!init) - return; - - if (!created) - { - return; - } - - RTPAddress *addr = a.CreateCopy(); - if (addr == 0) - return; - - uint8_t *datacopy; - bool rtp = true; - - if (len >= 2) - { - const uint8_t *pData = (const uint8_t *) data; - if (pData[1] >= 200 && pData[1] <= 204) - rtp = false; - } - - datacopy = new uint8_t[len]; - if (datacopy == 0) - { - delete addr; - return; - } - memcpy(datacopy, data, len); - - RTPTime curtime = RTPTime::CurrentTime(); - RTPRawPacket *pack; - - pack = new RTPRawPacket(datacopy, len, addr, curtime, rtp); - if (pack == 0) - { - delete addr; - delete[] localhostname; - return; - } - rawpacketlist.push_back(pack); - - if (m_abortCount == 0) - { - m_abortDesc.SendAbortSignal(); - m_abortCount++; - } - -} - -} // end namespace - diff --git a/qrtplib/rtpexternaltransmitter.h b/qrtplib/rtpexternaltransmitter.h deleted file mode 100644 index d937d68e4..000000000 --- a/qrtplib/rtpexternaltransmitter.h +++ /dev/null @@ -1,254 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpexternaltransmitter.h - */ - -#ifndef RTPEXTERNALTRANSMITTER_H - -#define RTPEXTERNALTRANSMITTER_H - -#include "rtpconfig.h" -#include "rtptransmitter.h" -#include "rtpabortdescriptors.h" -#include - -#include "util/export.h" - -namespace qrtplib -{ - -class RTPExternalTransmitter; - -/** Base class to specify a mechanism to transmit RTP packets outside of this library. - * Base class to specify a mechanism to transmit RTP packets outside of this library. When - * you want to use your own mechanism to transmit RTP packets, you need to specify that - * you'll be using the external transmission component, and derive a class from this base - * class. An instance should then be specified in the RTPExternalTransmissionParams object, - * so that the transmitter will call the \c SendRTP, \c SendRTCP and \c ComesFromThisSender - * methods of this instance when needed. - */ -class QRTPLIB_API RTPExternalSender -{ -public: - RTPExternalSender() - { - } - virtual ~RTPExternalSender() - { - } - - /** This member function will be called when RTP data needs to be transmitted. */ - virtual bool SendRTP(const void *data, std::size_t len) = 0; - - /** This member function will be called when an RTCP packet needs to be transmitted. */ - virtual bool SendRTCP(const void *data, std::size_t len) = 0; - - /** Used to identify if an RTPAddress instance originated from this sender (to be able to detect own packets). */ - virtual bool ComesFromThisSender(const RTPAddress *a) = 0; -}; - -/** Interface to inject incoming RTP and RTCP packets into the library. - * Interface to inject incoming RTP and RTCP packets into the library. When you have your own - * mechanism to receive incoming RTP/RTCP data, you'll need to pass these packets to the library. - * By first retrieving the RTPExternalTransmissionInfo instance for the external transmitter you'll - * be using, you can obtain the associated RTPExternalPacketInjecter instance. By calling it's - * member functions, you can then inject RTP or RTCP data into the library for further processing. - */ -class RTPExternalPacketInjecter -{ -public: - RTPExternalPacketInjecter(RTPExternalTransmitter *trans) - { - transmitter = trans; - } - ~RTPExternalPacketInjecter() - { - } - - /** This function can be called to insert an RTP packet into the transmission component. */ - void InjectRTP(const void *data, std::size_t len, const RTPAddress &a); - - /** This function can be called to insert an RTCP packet into the transmission component. */ - void InjectRTCP(const void *data, std::size_t len, const RTPAddress &a); - - /** Use this function to inject an RTP or RTCP packet and the transmitter will try to figure out which type of packet it is. */ - void InjectRTPorRTCP(const void *data, std::size_t len, const RTPAddress &a); -private: - RTPExternalTransmitter *transmitter; -}; - -/** Parameters to initialize a transmitter of type RTPExternalTransmitter. */ -class RTPExternalTransmissionParams: public RTPTransmissionParams -{ -public: - /** Using this constructor you can specify which RTPExternalSender object you'll be using - * and how much the additional header overhead for each packet will be. */ - RTPExternalTransmissionParams(RTPExternalSender *s, int headeroverhead) : - RTPTransmissionParams(RTPTransmitter::ExternalProto) - { - sender = s; - headersize = headeroverhead; - } - - RTPExternalSender *GetSender() const - { - return sender; - } - int GetAdditionalHeaderSize() const - { - return headersize; - } -private: - RTPExternalSender *sender; - int headersize; -}; - -/** Additional information about the external transmission component. */ -class RTPExternalTransmissionInfo: public RTPTransmissionInfo -{ -public: - RTPExternalTransmissionInfo(RTPExternalPacketInjecter *p) : - RTPTransmissionInfo(RTPTransmitter::ExternalProto) - { - packetinjector = p; - } - - /** Tells you which RTPExternalPacketInjecter you need to use to pass RTP or RTCP - * data on to the transmission component. */ - RTPExternalPacketInjecter *GetPacketInjector() const - { - return packetinjector; - } -private: - RTPExternalPacketInjecter *packetinjector; -}; - -/** A transmission component which will use user specified functions to transmit the data and - * which will expose functions to inject received RTP or RTCP data into this component. - * A transmission component which will use user specified functions to transmit the data and - * which will expose functions to inject received RTP or RTCP data into this component. Use - * a class derived from RTPExternalSender to specify the functions which need to be used for - * sending the data. Obtain the RTPExternalTransmissionInfo object associated with this - * transmitter to obtain the functions needed to pass RTP/RTCP packets on to the transmitter. - */ -class RTPExternalTransmitter: public RTPTransmitter -{ -public: - RTPExternalTransmitter(); - ~RTPExternalTransmitter(); - - int Init(bool treadsafe); - int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - - int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - std::size_t GetHeaderOverhead() - { - return headersize; - } - - int Poll(); - int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); - int AbortWait(); - - int SendRTPData(const void *data, std::size_t len); - int SendRTCPData(const void *data, std::size_t len); - - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); - - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); - - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(std::size_t s); - - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); - - void InjectRTP(const void *data, std::size_t len, const RTPAddress &a); - void InjectRTCP(const void *data, std::size_t len, const RTPAddress &a); - void InjectRTPorRTCP(const void *data, std::size_t len, const RTPAddress &a); -private: - void FlushPackets(); - - bool init; - bool created; - bool waitingfordata; - RTPExternalSender *sender; - RTPExternalPacketInjecter packetinjector; - - std::list rawpacketlist; - - uint8_t *localhostname; - std::size_t localhostnamelength; - - std::size_t maxpacksize; - int headersize; - - RTPAbortDescriptors m_abortDesc; - int m_abortCount; -}; - -inline void RTPExternalPacketInjecter::InjectRTP(const void *data, std::size_t len, const RTPAddress &a) -{ - transmitter->InjectRTP(data, len, a); -} - -inline void RTPExternalPacketInjecter::InjectRTCP(const void *data, std::size_t len, const RTPAddress &a) -{ - transmitter->InjectRTCP(data, len, a); -} - -inline void RTPExternalPacketInjecter::InjectRTPorRTCP(const void *data, std::size_t len, const RTPAddress &a) -{ - transmitter->InjectRTPorRTCP(data, len, a); -} - -} // end namespace - -#endif // RTPTCPSOCKETTRANSMITTER_H - diff --git a/qrtplib/rtphashtable.h b/qrtplib/rtphashtable.h deleted file mode 100644 index 9c98a089c..000000000 --- a/qrtplib/rtphashtable.h +++ /dev/null @@ -1,333 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#ifndef RTPHASHTABLE_H - -#define RTPHASHTABLE_H - -/** - * \file rtphashtable.h - */ - -#include "rtperrors.h" - -namespace qrtplib -{ - -//template -template -class RTPHashTable -{ -public: - RTPHashTable(); - ~RTPHashTable() - { - Clear(); - } - - void GotoFirstElement() - { - curhashelem = firsthashelem; - } - void GotoLastElement() - { - curhashelem = lasthashelem; - } - bool HasCurrentElement() - { - return (curhashelem == 0) ? false : true; - } - int DeleteCurrentElement(); - Element &GetCurrentElement() - { - return curhashelem->GetElement(); - } - int GotoElement(const Element &e); - bool HasElement(const Element &e); - void GotoNextElement(); - void GotoPreviousElement(); - void Clear(); - - int AddElement(const Element &elem); - int DeleteElement(const Element &elem); - -private: - class HashElement - { - public: - HashElement(const Element &e, int index) : - element(e) - { - hashprev = 0; - hashnext = 0; - listnext = 0; - listprev = 0; - hashindex = index; - } - int GetHashIndex() - { - return hashindex; - } - Element &GetElement() - { - return element; - } - - private: - int hashindex; - Element element; - public: - HashElement *hashprev, *hashnext; - HashElement *listprev, *listnext; - }; - - HashElement *table[hashsize]; - HashElement *firsthashelem, *lasthashelem; - HashElement *curhashelem; -#ifdef RTP_SUPPORT_MEMORYMANAGEMENT - int memorytype; -#endif // RTP_SUPPORT_MEMORYMANAGEMENT -}; - -template -inline RTPHashTable::RTPHashTable() -{ - for (int i = 0; i < hashsize; i++) - table[i] = 0; - firsthashelem = 0; - lasthashelem = 0; -} - -template -inline int RTPHashTable::DeleteCurrentElement() -{ - if (curhashelem) - { - HashElement *tmp1, *tmp2; - int index; - - // First, relink elements in current hash bucket - - index = curhashelem->GetHashIndex(); - tmp1 = curhashelem->hashprev; - tmp2 = curhashelem->hashnext; - if (tmp1 == 0) // no previous element in hash bucket - { - table[index] = tmp2; - if (tmp2 != 0) - tmp2->hashprev = 0; - } - else // there is a previous element in the hash bucket - { - tmp1->hashnext = tmp2; - if (tmp2 != 0) - tmp2->hashprev = tmp1; - } - - // Relink elements in list - - tmp1 = curhashelem->listprev; - tmp2 = curhashelem->listnext; - if (tmp1 == 0) // curhashelem is first in list - { - firsthashelem = tmp2; - if (tmp2 != 0) - tmp2->listprev = 0; - else - // curhashelem is also last in list - lasthashelem = 0; - } - else - { - tmp1->listnext = tmp2; - if (tmp2 != 0) - tmp2->listprev = tmp1; - else - // curhashelem is last in list - lasthashelem = tmp1; - } - - // finally, with everything being relinked, we can delete curhashelem - delete curhashelem; - curhashelem = tmp2; // Set to next element in the list - } - else - return ERR_RTP_HASHTABLE_NOCURRENTELEMENT; - return 0; -} - -template -inline int RTPHashTable::GotoElement(const Element &e) -{ - int index; - bool found; - - index = GetIndex::GetIndex(e); - if (index >= hashsize) - return ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; - - curhashelem = table[index]; - found = false; - while (!found && curhashelem != 0) - { - if (curhashelem->GetElement() == e) - found = true; - else - curhashelem = curhashelem->hashnext; - } - if (!found) - return ERR_RTP_HASHTABLE_ELEMENTNOTFOUND; - return 0; -} - -template -inline bool RTPHashTable::HasElement(const Element &e) -{ - int index; - bool found; - HashElement *tmp; - - index = GetIndex::GetIndex(e); - if (index >= hashsize) - return false; - - tmp = table[index]; - found = false; - while (!found && tmp != 0) - { - if (tmp->GetElement() == e) - found = true; - else - tmp = tmp->hashnext; - } - return found; -} - -template -inline void RTPHashTable::GotoNextElement() -{ - if (curhashelem) - curhashelem = curhashelem->listnext; -} - -template -inline void RTPHashTable::GotoPreviousElement() -{ - if (curhashelem) - curhashelem = curhashelem->listprev; -} - -template -inline void RTPHashTable::Clear() -{ - HashElement *tmp1, *tmp2; - - for (int i = 0; i < hashsize; i++) - table[i] = 0; - - tmp1 = firsthashelem; - while (tmp1 != 0) - { - tmp2 = tmp1->listnext; - delete tmp1; - tmp1 = tmp2; - } - firsthashelem = 0; - lasthashelem = 0; -} - -template -inline int RTPHashTable::AddElement(const Element &elem) -{ - int index; - bool found; - HashElement *e, *newelem; - - index = GetIndex::GetIndex(elem); - if (index >= hashsize) - return ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; - - e = table[index]; - found = false; - while (!found && e != 0) - { - if (e->GetElement() == elem) - found = true; - else - e = e->hashnext; - } - if (found) - return ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS; - - // Okay, the key doesn't exist, so we can add the new element in the hash table - - newelem = new HashElement(elem, index); - if (newelem == 0) - return ERR_RTP_OUTOFMEM; - - e = table[index]; - table[index] = newelem; - newelem->hashnext = e; - if (e != 0) - e->hashprev = newelem; - - // Now, we still got to add it to the linked list - - if (firsthashelem == 0) - { - firsthashelem = newelem; - lasthashelem = newelem; - } - else // there already are some elements in the list - { - lasthashelem->listnext = newelem; - newelem->listprev = lasthashelem; - lasthashelem = newelem; - } - return 0; -} - -template -inline int RTPHashTable::DeleteElement(const Element &elem) -{ - int status; - - status = GotoElement(elem); - if (status < 0) - return status; - return DeleteCurrentElement(); -} - -} // end namespace - -#endif // RTPHASHTABLE_H - diff --git a/qrtplib/rtpipv4address.cpp b/qrtplib/rtpipv4address.cpp deleted file mode 100644 index 6a204259e..000000000 --- a/qrtplib/rtpipv4address.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtpipv4address.h" - -namespace qrtplib -{ - -bool RTPIPv4Address::IsSameAddress(const RTPAddress *addr) const -{ - if (addr == 0) - return false; - if (addr->GetAddressType() != IPv4Address) - return false; - - const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; - if (addr2->GetIP() == ip && addr2->GetPort() == port) - return true; - return false; -} - -bool RTPIPv4Address::IsFromSameHost(const RTPAddress *addr) const -{ - if (addr == 0) - return false; - if (addr->GetAddressType() != IPv4Address) - return false; - - const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; - if (addr2->GetIP() == ip) - return true; - return false; -} - -RTPAddress *RTPIPv4Address::CreateCopy() const -{ - RTPIPv4Address *a = new RTPIPv4Address(ip, port); - return a; -} - -} // end namespace - diff --git a/qrtplib/rtpipv4address.h b/qrtplib/rtpipv4address.h deleted file mode 100644 index 0253d9be3..000000000 --- a/qrtplib/rtpipv4address.h +++ /dev/null @@ -1,176 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpipv4address.h - */ - -#ifndef RTPIPV4ADDRESS_H - -#define RTPIPV4ADDRESS_H - -#include "rtpconfig.h" -#include "rtpaddress.h" -#include "rtptypes.h" - -#include "util/export.h" - -namespace qrtplib -{ - -/** Represents an IPv4 IP address and port. - * This class is used by the UDP over IPv4 transmission component. - * When an RTPIPv4Address is used in one of the multicast functions of the transmitter, the port - * number is ignored. When an instance is used in one of the accept or ignore functions of the - * transmitter, a zero port number represents all ports for the specified IP address. - */ -class QRTPLIB_API RTPIPv4Address: public RTPAddress -{ -public: - /** Creates an instance with IP address \c ip and port number \c port (both - * are interpreted in host byte order), and possibly sets the RTCP multiplex flag - * (see RTPIPv4Address::UseRTCPMultiplexingOnTransmission). */ - RTPIPv4Address(uint32_t ip = 0, uint16_t port = 0, bool rtcpmux = false) : - RTPAddress(IPv4Address) - { - RTPIPv4Address::ip = ip; - RTPIPv4Address::port = port; - if (rtcpmux) - rtcpsendport = port; - else - rtcpsendport = port + 1; - } - - /** Creates an instance with IP address \c ip and port number \c port (both - * are interpreted in host byte order), and sets a specific port to - * send RTCP packets to (see RTPIPv4Address::GetRTCPSendPort). */ - RTPIPv4Address(uint32_t ip, uint16_t port, uint16_t rtcpsendport) : - RTPAddress(IPv4Address) - { - RTPIPv4Address::ip = ip; - RTPIPv4Address::port = port; - RTPIPv4Address::rtcpsendport = rtcpsendport; - } - - /** Creates an instance with IP address \c ip and port number \c port (\c port is - * interpreted in host byte order) and possibly sets the RTCP multiplex flag - * (see RTPIPv4Address::UseRTCPMultiplexingOnTransmission). */ - RTPIPv4Address(const uint8_t ip[4], uint16_t port = 0, bool rtcpmux = false) : - RTPAddress(IPv4Address) - { - RTPIPv4Address::ip = (uint32_t) ip[3]; - RTPIPv4Address::ip |= (((uint32_t) ip[2]) << 8); - RTPIPv4Address::ip |= (((uint32_t) ip[1]) << 16); - RTPIPv4Address::ip |= (((uint32_t) ip[0]) << 24); - - RTPIPv4Address::port = port; - if (rtcpmux) - rtcpsendport = port; - else - rtcpsendport = port + 1; - } - - /** Creates an instance with IP address \c ip and port number \c port (both - * are interpreted in host byte order), and sets a specific port to - * send RTCP packets to (see RTPIPv4Address::GetRTCPSendPort). */ - RTPIPv4Address(const uint8_t ip[4], uint16_t port, uint16_t rtcpsendport) : - RTPAddress(IPv4Address) - { - RTPIPv4Address::ip = (uint32_t) ip[3]; - RTPIPv4Address::ip |= (((uint32_t) ip[2]) << 8); - RTPIPv4Address::ip |= (((uint32_t) ip[1]) << 16); - RTPIPv4Address::ip |= (((uint32_t) ip[0]) << 24); - - RTPIPv4Address::port = port; - RTPIPv4Address::rtcpsendport = rtcpsendport; - } - - ~RTPIPv4Address() - { - } - - /** Sets the IP address for this instance to \c ip which is assumed to be in host byte order. */ - void SetIP(uint32_t ip) - { - RTPIPv4Address::ip = ip; - } - - /** Sets the IP address of this instance to \c ip. */ - void SetIP(const uint8_t ip[4]) - { - RTPIPv4Address::ip = (uint32_t) ip[3]; - RTPIPv4Address::ip |= (((uint32_t) ip[2]) << 8); - RTPIPv4Address::ip |= (((uint32_t) ip[1]) << 16); - RTPIPv4Address::ip |= (((uint32_t) ip[0]) << 24); - } - - /** Sets the port number for this instance to \c port which is interpreted in host byte order. */ - void SetPort(uint16_t port) - { - RTPIPv4Address::port = port; - } - - /** Returns the IP address contained in this instance in host byte order. */ - uint32_t GetIP() const - { - return ip; - } - - /** Returns the port number of this instance in host byte order. */ - uint16_t GetPort() const - { - return port; - } - - /** For outgoing packets, this indicates to which port RTCP packets will be sent (can, - * be the same port as the RTP packets in case RTCP multiplexing is used). */ - uint16_t GetRTCPSendPort() const - { - return rtcpsendport; - } - - RTPAddress *CreateCopy() const; - - // Note that these functions are only used for received packets, and for those - // the rtcpsendport variable is not important and should be ignored. - bool IsSameAddress(const RTPAddress *addr) const; - bool IsFromSameHost(const RTPAddress *addr) const; -private: - uint32_t ip; - uint16_t port; - uint16_t rtcpsendport; -}; - -} // end namespace - -#endif // RTPIPV4ADDRESS_H - diff --git a/qrtplib/rtpipv4destination.cpp b/qrtplib/rtpipv4destination.cpp deleted file mode 100644 index ad5047baf..000000000 --- a/qrtplib/rtpipv4destination.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2011 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtpipv4destination.h" -#include "rtpinternalutils.h" - -namespace qrtplib -{ - -std::string RTPIPv4Destination::GetDestinationString() const -{ - char str[24]; - uint32_t ip = GetIP(); - uint16_t portbase = ntohs(GetRTPPort_NBO()); - - RTP_SNPRINTF(str, 24, "%d.%d.%d.%d:%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF), (int) (portbase)); - return std::string(str); -} - -} // end namespace - diff --git a/qrtplib/rtpipv4destination.h b/qrtplib/rtpipv4destination.h deleted file mode 100644 index 32491bdcc..000000000 --- a/qrtplib/rtpipv4destination.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpipv4destination.h - */ - -#ifndef RTPIPV4DESTINATION_H - -#define RTPIPV4DESTINATION_H - -#include "rtpconfig.h" -#include "rtptypes.h" -#include "rtpipv4address.h" -#ifndef RTP_SOCKETTYPE_WINSOCK -#include -#include -#include -#endif // RTP_SOCKETTYPE_WINSOCK -#include -#include - -#include "util/export.h" - -namespace qrtplib -{ - -class QRTPLIB_API RTPIPv4Destination -{ -public: - RTPIPv4Destination() - { - ip = 0; - memset(&rtpaddr, 0, sizeof(struct sockaddr_in)); - memset(&rtcpaddr, 0, sizeof(struct sockaddr_in)); - } - - RTPIPv4Destination(uint32_t ip, uint16_t rtpport, uint16_t rtcpport) - { - memset(&rtpaddr, 0, sizeof(struct sockaddr_in)); - memset(&rtcpaddr, 0, sizeof(struct sockaddr_in)); - - rtpaddr.sin_family = AF_INET; - rtpaddr.sin_port = htons(rtpport); - rtpaddr.sin_addr.s_addr = htonl(ip); - - rtcpaddr.sin_family = AF_INET; - rtcpaddr.sin_port = htons(rtcpport); - rtcpaddr.sin_addr.s_addr = htonl(ip); - - RTPIPv4Destination::ip = ip; - } - - bool operator==(const RTPIPv4Destination &src) const - { - if (rtpaddr.sin_addr.s_addr == src.rtpaddr.sin_addr.s_addr && rtpaddr.sin_port == src.rtpaddr.sin_port) - return true; - return false; - } - uint32_t GetIP() const - { - return ip; - } - // nbo = network byte order - uint32_t GetIP_NBO() const - { - return rtpaddr.sin_addr.s_addr; - } - uint16_t GetRTPPort_NBO() const - { - return rtpaddr.sin_port; - } - uint16_t GetRTCPPort_NBO() const - { - return rtcpaddr.sin_port; - } - const struct sockaddr_in *GetRTPSockAddr() const - { - return &rtpaddr; - } - const struct sockaddr_in *GetRTCPSockAddr() const - { - return &rtcpaddr; - } - std::string GetDestinationString() const; - - static bool AddressToDestination(const RTPAddress &addr, RTPIPv4Destination &dest) - { - if (addr.GetAddressType() != RTPAddress::IPv4Address) - return false; - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - uint16_t rtpport = address.GetPort(); - uint16_t rtcpport = address.GetRTCPSendPort(); - - dest = RTPIPv4Destination(address.GetIP(), rtpport, rtcpport); - return true; - } - -private: - uint32_t ip; - struct sockaddr_in rtpaddr; - struct sockaddr_in rtcpaddr; -}; - -} // end namespace - -#endif // RTPIPV4DESTINATION_H - diff --git a/qrtplib/rtplibraryversion.cpp b/qrtplib/rtplibraryversion.cpp deleted file mode 100644 index 447cef4b7..000000000 --- a/qrtplib/rtplibraryversion.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtplibraryversion.h" -#include "rtpdefines.h" -#include "rtplibraryversioninternal.h" -#include "rtpinternalutils.h" -#include - -namespace qrtplib -{ - -RTPLibraryVersion RTPLibraryVersion::GetVersion() -{ - return RTPLibraryVersion(JRTPLIB_VERSION_MAJOR, JRTPLIB_VERSION_MINOR, JRTPLIB_VERSION_DEBUG); -} - -std::string RTPLibraryVersion::GetVersionString() const -{ - char str[16]; - - RTP_SNPRINTF(str, 16, "%d.%d.%d", majornr, minornr, debugnr); - - return std::string(str); -} - -} // end namespace - diff --git a/qrtplib/rtplibraryversion.h b/qrtplib/rtplibraryversion.h deleted file mode 100644 index 8f46255f6..000000000 --- a/qrtplib/rtplibraryversion.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtplibraryversion.h - */ - -#ifndef RTPLIBRARYVERSION_H - -#define RTPLIBRARYVERSION_H - -#include "rtpconfig.h" -#include -#include - -#include "util/export.h" - -namespace qrtplib -{ - -/** - * Used to provide information about the version of the library. - */ -class QRTPLIB_API RTPLibraryVersion -{ -public: - /** Returns an instance of RTPLibraryVersion describing the version of the library. */ - static RTPLibraryVersion GetVersion(); -private: - RTPLibraryVersion(int major, int minor, int debug) - { - majornr = major; - minornr = minor; - debugnr = debug; - } -public: - /** Returns the major version number. */ - int GetMajorNumber() const - { - return majornr; - } - - /** Returns the minor version number. */ - int GetMinorNumber() const - { - return minornr; - } - - /** Returns the debug version number. */ - int GetDebugNumber() const - { - return debugnr; - } - - /** Returns a string describing the library version. */ - std::string GetVersionString() const; -private: - int debugnr, minornr, majornr; -}; - -} // end namespace - -#endif // RTPLIBRARYVERSION_H - diff --git a/qrtplib/rtpselect.h b/qrtplib/rtpselect.h deleted file mode 100644 index 679cef0cf..000000000 --- a/qrtplib/rtpselect.h +++ /dev/null @@ -1,190 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpselect.h - */ - -#ifndef RTPSELECT_H - -#define RTPSELECT_H - -#include "rtpconfig.h" -#include "rtptypes.h" -#include "rtperrors.h" -#include "rtptimeutilities.h" -#include "rtpsocketutil.h" - -#if defined(RTP_HAVE_WSAPOLL) || defined(RTP_HAVE_POLL) - -#ifndef RTP_HAVE_WSAPOLL -#include -#include -#endif // !RTP_HAVE_WSAPOLL - -#include -#include - -namespace qrtplib -{ - -inline int RTPSelect(const SocketType *sockets, int8_t *readflags, std::size_t numsocks, RTPTime timeout) -{ - using namespace std; - - vector fds(numsocks); - - for (std::size_t i = 0; i < numsocks; i++) - { - fds[i].fd = sockets[i]; - fds[i].events = POLLIN; - fds[i].revents = 0; - readflags[i] = 0; - } - - int timeoutmsec = -1; - if (timeout.GetDouble() >= 0) - { - double dtimeoutmsec = timeout.GetDouble() * 1000.0; - if (dtimeoutmsec > (numeric_limits::max)()) // parentheses to prevent windows 'max' macro expansion - dtimeoutmsec = (numeric_limits::max)(); - - timeoutmsec = (int) dtimeoutmsec; - } - -#ifdef RTP_HAVE_WSAPOLL - int status = WSAPoll(&(fds[0]), (ULONG)numsocks, timeoutmsec); - if (status < 0) - return ERR_RTP_SELECT_ERRORINPOLL; -#else - int status = poll(&(fds[0]), numsocks, timeoutmsec); - if (status < 0) - { - // We're just going to ignore an EINTR - if (errno == EINTR) - return 0; - return ERR_RTP_SELECT_ERRORINPOLL; - } -#endif // RTP_HAVE_WSAPOLL - - if (status > 0) - { - for (std::size_t i = 0; i < numsocks; i++) - { - if (fds[i].revents) - readflags[i] = 1; - } - } - return status; -} - -} // end namespace - -#else - -#ifndef RTP_SOCKETTYPE_WINSOCK -#include -#include -#include -#include -#endif // !RTP_SOCKETTYPE_WINSOCK - -namespace qrtplib -{ - - /** Wrapper function around 'select', 'poll' or 'WSAPoll', depending on the - * availability on your platform. - * - * Wrapper function around 'select', 'poll' or 'WSAPoll', depending on the - * availability on your platform. The function will check the specified - * `sockets` for incoming data and sets the flags in `readflags` if so. - * A maximum time `timeout` will be waited for data to arrive, which is - * indefinitely if set to a negative value. The function returns the number - * of sockets that have data incoming. - */ - inline int RTPSelect(const SocketType *sockets, int8_t *readflags, std::size_t numsocks, RTPTime timeout) - { - struct timeval tv; - struct timeval *pTv = 0; - - if (timeout.GetDouble() >= 0) - { - tv.tv_sec = (long)timeout.GetSeconds(); - tv.tv_usec = timeout.GetMicroSeconds(); - pTv = &tv; - } - - fd_set fdset; - FD_ZERO(&fdset); - for (std::size_t i = 0; i < numsocks; i++) - { -#ifndef RTP_SOCKETTYPE_WINSOCK - const int setsize = FD_SETSIZE; - // On windows it seems that comparing the socket value to FD_SETSIZE does - // not make sense - if (sockets[i] >= setsize) - return ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE; -#endif // RTP_SOCKETTYPE_WINSOCK - FD_SET(sockets[i], &fdset); - readflags[i] = 0; - } - - int status = select(FD_SETSIZE, &fdset, 0, 0, pTv); -#ifdef RTP_SOCKETTYPE_WINSOCK - if (status < 0) - return ERR_RTP_SELECT_ERRORINSELECT; -#else - if (status < 0) - { - // We're just going to ignore an EINTR - if (errno == EINTR) - return 0; - return ERR_RTP_SELECT_ERRORINSELECT; - } -#endif // RTP_HAVE_WSAPOLL - - if (status > 0) // some descriptors were set, check them - { - for (std::size_t i = 0; i < numsocks; i++) - { - if (FD_ISSET(sockets[i], &fdset)) - readflags[i] = 1; - } - } - return status; - } - -} // end namespace - -#endif // RTP_HAVE_POLL || RTP_HAVE_WSAPOLL - -#endif // RTPSELECT_H diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index 2ad288a44..e81994c7a 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -39,7 +39,6 @@ #define RTPSESSION_H #include "rtpconfig.h" -#include "rtplibraryversion.h" #include "rtppacketbuilder.h" #include "rtpsessionsources.h" #include "rtptransmitter.h" diff --git a/qrtplib/rtptcpaddress.cpp b/qrtplib/rtptcpaddress.cpp deleted file mode 100644 index 7ea9ead54..000000000 --- a/qrtplib/rtptcpaddress.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtptcpaddress.h" - -namespace qrtplib -{ - -bool RTPTCPAddress::IsSameAddress(const RTPAddress *addr) const -{ - if (addr == 0) - return false; - if (addr->GetAddressType() != TCPAddress) - return false; - - const RTPTCPAddress *a = static_cast(addr); - - // We're using a socket to identify connections - if (a->m_socket == m_socket) - return true; - - return false; -} - -bool RTPTCPAddress::IsFromSameHost(const RTPAddress *addr) const -{ - return IsSameAddress(addr); -} - -RTPAddress *RTPTCPAddress::CreateCopy() const -{ - RTPTCPAddress *a = new RTPTCPAddress(m_socket); - return a; -} - -} // end namespace - diff --git a/qrtplib/rtptcpaddress.h b/qrtplib/rtptcpaddress.h deleted file mode 100644 index ba3a50bd1..000000000 --- a/qrtplib/rtptcpaddress.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtptcpaddress.h - */ - -#ifndef RTPTCPADDRESS_H - -#define RTPTCPADDRESS_H - -#include "rtpconfig.h" -#include "rtpaddress.h" -#include "rtptypes.h" -#include "rtpsocketutil.h" - -#include "util/export.h" - -namespace qrtplib -{ -/** Represents a TCP 'address' and port. - * This class is used by the TCP transmission component, to specify which sockets - * should be used to send/receive data, and to know on which socket incoming data - * was received. - */ -class QRTPLIB_API RTPTCPAddress: public RTPAddress -{ -public: - /** Creates an instance with which you can use a specific socket - * in the TCP transmitter (must be connected). */ - RTPTCPAddress(SocketType sock) : - RTPAddress(TCPAddress) - { - m_socket = sock; - } - - ~RTPTCPAddress() - { - } - - /** Returns the socket that was specified in the constructor. */ - SocketType GetSocket() const - { - return m_socket; - } - - RTPAddress *CreateCopy() const; - - // Note that these functions are only used for received packets - bool IsSameAddress(const RTPAddress *addr) const; - bool IsFromSameHost(const RTPAddress *addr) const; - -private: - SocketType m_socket; -}; - -} // end namespace - -#endif // RTPTCPADDRESS_H - diff --git a/qrtplib/rtptcptransmitter.cpp b/qrtplib/rtptcptransmitter.cpp deleted file mode 100644 index 411c6f56a..000000000 --- a/qrtplib/rtptcptransmitter.cpp +++ /dev/null @@ -1,831 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtptcptransmitter.h" -#include "rtprawpacket.h" -#include "rtptcpaddress.h" -#include "rtptimeutilities.h" -#include "rtpdefines.h" -#include "rtpstructs.h" -#include "rtpsocketutilinternal.h" -#include "rtpinternalutils.h" -#include "rtpselect.h" -#include -#include -#include - -#include - -using namespace std; - -#define RTPTCPTRANS_MAXPACKSIZE 65535 - -namespace qrtplib -{ - -RTPTCPTransmitter::RTPTCPTransmitter() -{ - m_created = false; - m_init = false; -} - -RTPTCPTransmitter::~RTPTCPTransmitter() -{ - Destroy(); -} - -int RTPTCPTransmitter::Init(bool tsafe) -{ - if (m_init) - return ERR_RTP_TCPTRANS_ALREADYINIT; - - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; - - m_maxPackSize = RTPTCPTRANS_MAXPACKSIZE; - m_init = true; - return 0; -} - -int RTPTCPTransmitter::Create(std::size_t maximumpacketsize __attribute__((unused)), const RTPTransmissionParams *transparams) -{ - const RTPTCPTransmissionParams *params, defaultparams; - int status; - - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (m_created) - { - return ERR_RTP_TCPTRANS_ALREADYCREATED; - } - - // Obtain transmission parameters - - if (transparams == 0) - params = &defaultparams; - else - { - if (transparams->GetTransmissionProtocol() != RTPTransmitter::TCPProto) - { - return ERR_RTP_TCPTRANS_ILLEGALPARAMETERS; - } - params = static_cast(transparams); - } - - if (!params->GetCreatedAbortDescriptors()) - { - if ((status = m_abortDesc.Init()) < 0) - { - return status; - } - m_pAbortDesc = &m_abortDesc; - } - else - { - m_pAbortDesc = params->GetCreatedAbortDescriptors(); - if (!m_pAbortDesc->IsInitialized()) - { - return ERR_RTP_ABORTDESC_NOTINIT; - } - } - - m_waitingForData = false; - m_created = true; - return 0; -} - -void RTPTCPTransmitter::Destroy() -{ - if (!m_init) - return; - - if (!m_created) - { - return; - } - - ClearDestSockets(); - FlushPackets(); - m_created = false; - - if (m_waitingForData) - { - m_pAbortDesc->SendAbortSignal(); - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - // to make sure that the WaitForIncomingData function ended - - } - else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - -} - -RTPTransmissionInfo *RTPTCPTransmitter::GetTransmissionInfo() -{ - if (!m_init) - return 0; - - RTPTransmissionInfo *tinf = new RTPTCPTransmissionInfo(); - return tinf; -} - -void RTPTCPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) -{ - if (!m_init) - return; - - delete i; -} - -int RTPTCPTransmitter::GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) -{ - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (!m_created) - { - return ERR_RTP_TCPTRANS_NOTCREATED; - } - - if (m_localHostname.size() == 0) - { -// -// TODO -// TODO -// TODO -// TODO -// - m_localHostname.resize(9); - memcpy(&m_localHostname[0], "localhost", m_localHostname.size()); - } - - if ((*bufferlength) < m_localHostname.size()) - { - *bufferlength = m_localHostname.size(); // tell the application the required size of the buffer - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } - - memcpy(buffer, &m_localHostname[0], m_localHostname.size()); - *bufferlength = m_localHostname.size(); - - return 0; -} - -bool RTPTCPTransmitter::ComesFromThisTransmitter(const RTPAddress *addr) -{ - if (!m_init) - return false; - - if (addr == 0) - return false; - - if (!m_created) - return false; - - if (addr->GetAddressType() != RTPAddress::TCPAddress) - return false; - - bool v = false; - - // TODO: for now, we're assuming that we can't just send to the same transmitter - - return v; -} - -int RTPTCPTransmitter::Poll() -{ - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (!m_created) - { - return ERR_RTP_TCPTRANS_NOTCREATED; - } - - std::map::iterator it = m_destSockets.begin(); - std::map::iterator end = m_destSockets.end(); - int status = 0; - - vector errSockets; - - while (it != end) - { - SocketType sock = it->first; - status = PollSocket(sock, it->second); - if (status < 0) - { - // Stop immediately on out of memory - if (status == ERR_RTP_OUTOFMEM) - break; - else - { - errSockets.push_back(sock); - // Don't let this count as an error (due to a closed connection for example), - // otherwise the poll thread (if used) will stop because of this. Since there - // may be more than one connection, that's not desirable in general. - status = 0; - } - } - ++it; - } - - for (std::size_t i = 0; i < errSockets.size(); i++) - OnReceiveError(errSockets[i]); - - return status; -} - -int RTPTCPTransmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) -{ - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (!m_created) - { - return ERR_RTP_TCPTRANS_NOTCREATED; - } - if (m_waitingForData) - { - return ERR_RTP_TCPTRANS_ALREADYWAITING; - } - - m_tmpSocks.resize(m_destSockets.size() + 1); - m_tmpFlags.resize(m_tmpSocks.size()); - SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); - - std::map::iterator it = m_destSockets.begin(); - std::map::iterator end = m_destSockets.end(); - int idx = 0; - - while (it != end) - { - m_tmpSocks[idx] = it->first; - m_tmpFlags[idx] = 0; - ++it; - idx++; - } - m_tmpSocks[idx] = abortSocket; - m_tmpFlags[idx] = 0; - int idxAbort = idx; - - m_waitingForData = true; - - //cout << "Waiting for " << delay.GetDouble() << " seconds for data on " << m_tmpSocks.size() << " sockets" << endl; - int status = RTPSelect(&m_tmpSocks[0], &m_tmpFlags[0], m_tmpSocks.size(), delay); - if (status < 0) - { - m_waitingForData = false; - - return status; - } - - m_waitingForData = false; - if (!m_created) // destroy called - { - - return 0; - } - - // if aborted, read from abort buffer - if (m_tmpFlags[idxAbort]) - m_pAbortDesc->ReadSignallingByte(); - - if (dataavailable != 0) - { - bool avail = false; - - for (std::size_t i = 0; i < m_tmpFlags.size(); i++) - { - if (m_tmpFlags[i]) - { - avail = true; - //cout << "Data available!" << endl; - break; - } - } - - if (avail) - *dataavailable = true; - else - *dataavailable = false; - } - - return 0; -} - -int RTPTCPTransmitter::AbortWait() -{ - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (!m_created) - { - return ERR_RTP_TCPTRANS_NOTCREATED; - } - if (!m_waitingForData) - { - return ERR_RTP_TCPTRANS_NOTWAITING; - } - - m_pAbortDesc->SendAbortSignal(); - - return 0; -} - -int RTPTCPTransmitter::SendRTPData(const void *data, std::size_t len) -{ - return SendRTPRTCPData(data, len); -} - -int RTPTCPTransmitter::SendRTCPData(const void *data, std::size_t len) -{ - return SendRTPRTCPData(data, len); -} - -int RTPTCPTransmitter::AddDestination(const RTPAddress &addr) -{ - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (!m_created) - { - return ERR_RTP_TCPTRANS_NOTCREATED; - } - - if (addr.GetAddressType() != RTPAddress::TCPAddress) - { - return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; - } - - const RTPTCPAddress &a = static_cast(addr); - SocketType s = a.GetSocket(); - if (s == 0) - { - return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; - } - - int status = ValidateSocket(s); - if (status != 0) - { - - return status; - } - - std::map::iterator it = m_destSockets.find(s); - if (it != m_destSockets.end()) - { - - return ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS; - } - m_destSockets[s] = SocketData(); - - // Because the sockets are also used for incoming data, we'll abort a wait - // that may be in progress, otherwise it could take a few seconds until the - // new socket is monitored for incoming data - m_pAbortDesc->SendAbortSignal(); - - return 0; -} - -int RTPTCPTransmitter::DeleteDestination(const RTPAddress &addr) -{ - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (!m_created) - { - - return ERR_RTP_TCPTRANS_NOTCREATED; - } - - if (addr.GetAddressType() != RTPAddress::TCPAddress) - { - - return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE; - } - - const RTPTCPAddress &a = static_cast(addr); - SocketType s = a.GetSocket(); - if (s == 0) - { - - return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED; - } - - std::map::iterator it = m_destSockets.find(s); - if (it == m_destSockets.end()) - { - - return ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS; - } - - // Clean up possibly allocated memory - uint8_t *pBuf = it->second.ExtractDataBuffer(); - if (pBuf) - delete[] pBuf; - - m_destSockets.erase(it); - - return 0; -} - -void RTPTCPTransmitter::ClearDestinations() -{ - if (!m_init) - return; - - if (m_created) - ClearDestSockets(); - -} - -bool RTPTCPTransmitter::SupportsMulticasting() -{ - return false; -} - -int RTPTCPTransmitter::JoinMulticastGroup(const RTPAddress &) -{ - return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; -} - -int RTPTCPTransmitter::LeaveMulticastGroup(const RTPAddress &) -{ - return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT; -} - -void RTPTCPTransmitter::LeaveAllMulticastGroups() -{ -} - -int RTPTCPTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) -{ - if (m != RTPTransmitter::AcceptAll) - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; - return 0; -} - -int RTPTCPTransmitter::AddToIgnoreList(const RTPAddress &) -{ - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; -} - -int RTPTCPTransmitter::DeleteFromIgnoreList(const RTPAddress &) -{ - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; -} - -void RTPTCPTransmitter::ClearIgnoreList() -{ -} - -int RTPTCPTransmitter::AddToAcceptList(const RTPAddress &) -{ - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; -} - -int RTPTCPTransmitter::DeleteFromAcceptList(const RTPAddress &) -{ - return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED; -} - -void RTPTCPTransmitter::ClearAcceptList() -{ -} - -int RTPTCPTransmitter::SetMaximumPacketSize(std::size_t s) -{ - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (!m_created) - { - - return ERR_RTP_TCPTRANS_NOTCREATED; - } - if (s > RTPTCPTRANS_MAXPACKSIZE) - { - - return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; - } - m_maxPackSize = s; - - return 0; -} - -bool RTPTCPTransmitter::NewDataAvailable() -{ - if (!m_init) - return false; - - bool v; - - if (!m_created) - v = false; - else - { - if (m_rawpacketlist.empty()) - v = false; - else - v = true; - } - - return v; -} - -RTPRawPacket *RTPTCPTransmitter::GetNextPacket() -{ - if (!m_init) - return 0; - - RTPRawPacket *p; - - if (!m_created) - { - - return 0; - } - if (m_rawpacketlist.empty()) - { - - return 0; - } - - p = *(m_rawpacketlist.begin()); - m_rawpacketlist.pop_front(); - - return p; -} - -// Here the private functions start... - -void RTPTCPTransmitter::FlushPackets() -{ - std::list::const_iterator it; - - for (it = m_rawpacketlist.begin(); it != m_rawpacketlist.end(); ++it) - delete *it; - m_rawpacketlist.clear(); -} - -int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata) -{ -#ifdef RTP_SOCKETTYPE_WINSOCK - unsigned long len; -#else - std::size_t len; -#endif // RTP_SOCKETTYPE_WINSOCK - bool dataavailable; - - do - { - len = 0; - RTPIOCTL(sock, FIONREAD, &len); - - if (len <= 0) - dataavailable = false; - else - dataavailable = true; - - if (dataavailable) - { - RTPTime curtime = RTPTime::CurrentTime(); - int relevantLen = RTPTCPTRANS_MAXPACKSIZE + 2; - - if ((int) len < relevantLen) - relevantLen = (int) len; - - bool complete = false; - int status = sdata.ProcessAvailableBytes(sock, relevantLen, complete); - if (status < 0) - return status; - - if (complete) - { - uint8_t *pBuf = sdata.ExtractDataBuffer(); - if (pBuf) - { - int dataLength = sdata.m_dataLength; - sdata.Reset(); - - RTPTCPAddress *pAddr = new RTPTCPAddress(sock); - if (pAddr == 0) - return ERR_RTP_OUTOFMEM; - - bool isrtp = true; - if (dataLength > (int) sizeof(RTCPCommonHeader)) - { - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) pBuf; - uint8_t packettype = rtcpheader->packettype; - - if (packettype >= 200 && packettype <= 204) - isrtp = false; - } - - RTPRawPacket *pPack = new RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp); - if (pPack == 0) - { - delete pAddr; - delete[] pBuf; - return ERR_RTP_OUTOFMEM; - } - m_rawpacketlist.push_back(pPack); - } - } - } - } while (dataavailable); - - return 0; -} - -int RTPTCPTransmitter::SendRTPRTCPData(const void *data, std::size_t len) -{ - if (!m_init) - return ERR_RTP_TCPTRANS_NOTINIT; - - if (!m_created) - { - - return ERR_RTP_TCPTRANS_NOTCREATED; - } - if (len > RTPTCPTRANS_MAXPACKSIZE) - { - - return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG; - } - - std::map::iterator it = m_destSockets.begin(); - std::map::iterator end = m_destSockets.end(); - - vector errSockets; - int flags = 0; -#ifdef RTP_HAVE_MSG_NOSIGNAL - flags = MSG_NOSIGNAL; -#endif // RTP_HAVE_MSG_NOSIGNAL - - while (it != end) - { - uint8_t lengthBytes[2] = - { (uint8_t) ((len >> 8) & 0xff), (uint8_t) (len & 0xff) }; - SocketType sock = it->first; - - if (send(sock, (const char *) lengthBytes, 2, flags) < 0 || send(sock, (const char *) data, len, flags) < 0) - errSockets.push_back(sock); - ++it; - } - - if (errSockets.size() != 0) - { - for (std::size_t i = 0; i < errSockets.size(); i++) - OnSendError(errSockets[i]); - } - - // Don't return an error code to avoid the poll thread exiting - // due to one closed connection for example - - return 0; -} - -int RTPTCPTransmitter::ValidateSocket(SocketType) -{ - // TODO: should we even do a check (for a TCP socket)? - return 0; -} - -void RTPTCPTransmitter::ClearDestSockets() -{ - std::map::iterator it = m_destSockets.begin(); - std::map::iterator end = m_destSockets.end(); - - while (it != end) - { - uint8_t *pBuf = it->second.ExtractDataBuffer(); - if (pBuf) - delete[] pBuf; - - ++it; - } - m_destSockets.clear(); -} - -RTPTCPTransmitter::SocketData::SocketData() -{ - Reset(); -} - -void RTPTCPTransmitter::SocketData::Reset() -{ - m_lengthBufferOffset = 0; - m_dataLength = 0; - m_dataBufferOffset = 0; - m_pDataBuffer = 0; -} - -RTPTCPTransmitter::SocketData::~SocketData() -{ - assert(m_pDataBuffer == 0); // Should be deleted externally to avoid storing a memory manager in the class -} - -int RTPTCPTransmitter::SocketData::ProcessAvailableBytes(SocketType sock, int availLen, bool &complete) -{ - - const int numLengthBuffer = 2; - if (m_lengthBufferOffset < numLengthBuffer) // first we need to get the length - { - assert(m_pDataBuffer == 0); - int num = numLengthBuffer - m_lengthBufferOffset; - if (num > availLen) - num = availLen; - - int r = 0; - if (num > 0) - { - r = (int) recv(sock, (char *) (m_lengthBuffer + m_lengthBufferOffset), num, 0); - if (r < 0) - return ERR_RTP_TCPTRANS_ERRORINRECV; - } - - m_lengthBufferOffset += r; - availLen -= r; - - assert(m_lengthBufferOffset <= numLengthBuffer); - if (m_lengthBufferOffset == numLengthBuffer) // we can constuct a length - { - int l = 0; - for (int i = numLengthBuffer - 1, shift = 0; i >= 0; i--, shift += 8) - l |= ((int) m_lengthBuffer[i]) << shift; - - m_dataLength = l; - m_dataBufferOffset = 0; - -//cout << "Expecting " << m_dataLength << " bytes" << endl; - -// avoid allocation of length 0 - if (l == 0) - l = 1; - -// We don't yet know if it's an RTP or RTCP packet, so we'll stick to RTP - m_pDataBuffer = new uint8_t[l]; - if (m_pDataBuffer == 0) - return ERR_RTP_OUTOFMEM; - } - } - - if (m_lengthBufferOffset == numLengthBuffer && m_pDataBuffer) // the last one is to make sure we didn't run out of memory - { - if (m_dataBufferOffset < m_dataLength) - { - int num = m_dataLength - m_dataBufferOffset; - if (num > availLen) - num = availLen; - - int r = 0; - if (num > 0) - { - r = (int) recv(sock, (char *) (m_pDataBuffer + m_dataBufferOffset), num, 0); - if (r < 0) - return ERR_RTP_TCPTRANS_ERRORINRECV; - } - - m_dataBufferOffset += r; - availLen -= r; - } - - if (m_dataBufferOffset == m_dataLength) - complete = true; - } - return 0; -} - -} // end namespace - diff --git a/qrtplib/rtptcptransmitter.h b/qrtplib/rtptcptransmitter.h deleted file mode 100644 index dc964c304..000000000 --- a/qrtplib/rtptcptransmitter.h +++ /dev/null @@ -1,229 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtptcptransmitter.h - */ - -#ifndef RTPTCPTRANSMITTER_H - -#define RTPTCPTRANSMITTER_H - -#include "rtpconfig.h" -#include "rtptransmitter.h" -#include "rtpsocketutil.h" -#include "rtpabortdescriptors.h" -#include -#include -#include - -#include "util/export.h" - -namespace qrtplib -{ - -/** Parameters for the TCP transmitter. */ -class QRTPLIB_API RTPTCPTransmissionParams: public RTPTransmissionParams -{ -public: - RTPTCPTransmissionParams(); - - /** If non null, the specified abort descriptors will be used to cancel - * the function that's waiting for packets to arrive; set to null (the default) - * to let the transmitter create its own instance. */ - void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) - { - m_pAbortDesc = desc; - } - - /** If non-null, this RTPAbortDescriptors instance will be used internally, - * which can be useful when creating your own poll thread for multiple - * sessions. */ - RTPAbortDescriptors *GetCreatedAbortDescriptors() const - { - return m_pAbortDesc; - } -private: - RTPAbortDescriptors *m_pAbortDesc; -}; - -inline RTPTCPTransmissionParams::RTPTCPTransmissionParams() : - RTPTransmissionParams(RTPTransmitter::TCPProto) -{ - m_pAbortDesc = 0; -} - -/** Additional information about the TCP transmitter. */ -class RTPTCPTransmissionInfo: public RTPTransmissionInfo -{ -public: - RTPTCPTransmissionInfo() : - RTPTransmissionInfo(RTPTransmitter::TCPProto) - { - } - ~RTPTCPTransmissionInfo() - { - } -}; - -// TODO: this is for IPv4, and will only be valid if one rtp packet is in one tcp frame -#define RTPTCPTRANS_HEADERSIZE (20+20+2) // 20 IP, 20 TCP, 2 for framing (RFC 4571) - -/** A TCP transmission component. - * - * This class inherits the RTPTransmitter interface and implements a transmission component - * which uses TCP to send and receive RTP and RTCP data. The component's parameters - * are described by the class RTPTCPTransmissionParams. The functions which have an RTPAddress - * argument require an argument of RTPTCPAddress. The RTPTransmitter::GetTransmissionInfo member function - * returns an instance of type RTPTCPTransmissionInfo. - * - * After this transmission component was created, no data will actually be sent or received - * yet. You can specify over which TCP connections (which must be established first) data - * should be transmitted by using the RTPTransmitter::AddDestination member function. This - * takes an argument of type RTPTCPAddress, with which relevant the socket descriptor can - * be passed to the transmitter. - * - * These sockets will also be used to check for incoming RTP or RTCP data. The RTPTCPAddress - * instance that's associated with a received packet, will contain the socket descriptor - * on which the data was received. This descriptor can be obtained using RTPTCPAddress::GetSocket. - * - * To get notified of an error when sending over or receiving from a socket, override the - * RTPTCPTransmitter::OnSendError and RTPTCPTransmitter::OnReceiveError member functions. - */ -class RTPTCPTransmitter : public RTPTransmitter -{ -public: - RTPTCPTransmitter(); - ~RTPTCPTransmitter(); - - int Init(bool treadsafe); - int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - - int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - std::size_t GetHeaderOverhead() - { - return RTPTCPTRANS_HEADERSIZE; - } - - int Poll(); - int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); - int AbortWait(); - - int SendRTPData(const void *data, std::size_t len); - int SendRTCPData(const void *data, std::size_t len); - - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); - - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); - - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(std::size_t s); - - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); -protected: - /** By overriding this function you can be notified of an error when sending over a socket. */ - virtual void OnSendError(SocketType sock); - /** By overriding this function you can be notified of an error when receiving from a socket. */ - virtual void OnReceiveError(SocketType sock); -private: - class SocketData - { - public: - SocketData(); - ~SocketData(); - void Reset(); - - uint8_t m_lengthBuffer[2]; - int m_lengthBufferOffset; - int m_dataLength; - int m_dataBufferOffset; - uint8_t *m_pDataBuffer; - - uint8_t *ExtractDataBuffer() - { - uint8_t *pTmp = m_pDataBuffer; - m_pDataBuffer = 0; - return pTmp; - } - int ProcessAvailableBytes(SocketType sock, int availLen, bool &complete); - }; - - int SendRTPRTCPData(const void *data, std::size_t len); - void FlushPackets(); - int PollSocket(SocketType sock, SocketData &sdata); - void ClearDestSockets(); - int ValidateSocket(SocketType s); - - bool m_init; - bool m_created; - bool m_waitingForData; - - std::map m_destSockets; - std::vector m_tmpSocks; - std::vector m_tmpFlags; - std::vector m_localHostname; - std::size_t m_maxPackSize; - - std::list m_rawpacketlist; - - RTPAbortDescriptors m_abortDesc; - RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified - -}; - -inline void RTPTCPTransmitter::OnSendError(SocketType) -{ -} -inline void RTPTCPTransmitter::OnReceiveError(SocketType) -{ -} - -} // end namespace - -#endif // RTPTCPTRANSMITTER_H - diff --git a/qrtplib/rtpudpv4transmitter.cpp b/qrtplib/rtpudpv4transmitter.cpp deleted file mode 100644 index 12f165281..000000000 --- a/qrtplib/rtpudpv4transmitter.cpp +++ /dev/null @@ -1,1834 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtpudpv4transmitter.h" -#include "rtprawpacket.h" -#include "rtpipv4address.h" -#include "rtptimeutilities.h" -#include "rtpdefines.h" -#include "rtpstructs.h" -#include "rtpsocketutilinternal.h" -#include "rtpinternalutils.h" -#include "rtpselect.h" -#include -#include -#include - -#include - -using namespace std; - -#define RTPUDPV4TRANS_MAXPACKSIZE 65535 -#define RTPUDPV4TRANS_IFREQBUFSIZE 8192 - -#define RTPUDPV4TRANS_IS_MCASTADDR(x) (((x)&0xF0000000) == 0xE0000000) - -#define RTPUDPV4TRANS_MCASTMEMBERSHIP(socket,type,mcastip,status) {\ - struct ip_mreq mreq;\ - \ - mreq.imr_multiaddr.s_addr = htonl(mcastip);\ - mreq.imr_interface.s_addr = htonl(mcastifaceIP);\ - status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ - } - -#define CLOSESOCKETS do { \ - if (closesocketswhendone) \ - {\ - if (rtpsock != rtcpsock) \ - RTPCLOSE(rtcpsock); \ - RTPCLOSE(rtpsock); \ - } \ -} while(0) - -namespace qrtplib -{ - -RTPUDPv4Transmitter::RTPUDPv4Transmitter() -{ - created = false; - init = false; -} - -RTPUDPv4Transmitter::~RTPUDPv4Transmitter() -{ - Destroy(); -} - -int RTPUDPv4Transmitter::Init(bool tsafe) -{ - if (init) - return ERR_RTP_UDPV4TRANS_ALREADYINIT; - - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; - - init = true; - return 0; -} - -int RTPUDPv4Transmitter::GetIPv4SocketPort(SocketType s, uint16_t *pPort) -{ - assert(pPort != 0); - - struct sockaddr_in addr; - memset(&addr, 0, sizeof(struct sockaddr_in)); - - RTPSOCKLENTYPE size = sizeof(struct sockaddr_in); - if (getsockname(s, (struct sockaddr*) &addr, &size) != 0) - return ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT; - - if (addr.sin_family != AF_INET) - return ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET; - - uint16_t port = ntohs(addr.sin_port); - if (port == 0) - return ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET; - - int type = 0; - RTPSOCKLENTYPE length = sizeof(type); - - if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*) &type, &length) != 0) - return ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE; - - if (type != SOCK_DGRAM) - return ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE; - - *pPort = port; - return 0; -} - -int RTPUDPv4Transmitter::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, SocketType *pRtpSock, SocketType *pRtcpSock, uint16_t *pRtpPort, uint16_t *pRtcpPort) -{ - const int maxAttempts = 1024; - int attempts = 0; - vector toClose; - - while (attempts++ < maxAttempts) - { - SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); - if (sock == RTPSOCKERR) - { - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - - // First we get an automatically chosen port - - struct sockaddr_in addr; - memset(&addr, 0, sizeof(struct sockaddr_in)); - - addr.sin_family = AF_INET; - addr.sin_port = 0; - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(sock); - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; - } - - uint16_t basePort = 0; - int status = GetIPv4SocketPort(sock, &basePort); - if (status < 0) - { - RTPCLOSE(sock); - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - return status; - } - - if (rtcpMux) // only need one socket - { - if (basePort % 2 == 0 || allowOdd) - { - *pRtpSock = sock; - *pRtcpSock = sock; - *pRtpPort = basePort; - *pRtcpPort = basePort; - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - - return 0; - } - else - toClose.push_back(sock); - } - else - { - SocketType sock2 = socket(PF_INET, SOCK_DGRAM, 0); - if (sock2 == RTPSOCKERR) - { - RTPCLOSE(sock); - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - - // Try the next port or the previous port - uint16_t secondPort = basePort; - bool possiblyValid = false; - - if (basePort % 2 == 0) - { - secondPort++; - possiblyValid = true; - } - else if (basePort > 1) // avoid landing on port 0 - { - secondPort--; - possiblyValid = true; - } - - if (possiblyValid) - { - memset(&addr, 0, sizeof(struct sockaddr_in)); - - addr.sin_family = AF_INET; - addr.sin_port = htons(secondPort); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(sock2, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == 0) - { - // In this case, we have two consecutive port numbers, the lower of - // which is even - - if (basePort < secondPort) - { - *pRtpSock = sock; - *pRtcpSock = sock2; - *pRtpPort = basePort; - *pRtcpPort = secondPort; - } - else - { - *pRtpSock = sock2; - *pRtcpSock = sock; - *pRtpPort = secondPort; - *pRtcpPort = basePort; - } - - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - - return 0; - } - } - - toClose.push_back(sock); - toClose.push_back(sock2); - } - } - - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - - return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; -} - -int RTPUDPv4Transmitter::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) -{ - const RTPUDPv4TransmissionParams *params, defaultparams; - struct sockaddr_in addr; - RTPSOCKLENTYPE size; - int status; - - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (created) - { - - return ERR_RTP_UDPV4TRANS_ALREADYCREATED; - } - - // Obtain transmission parameters - - if (transparams == 0) - params = &defaultparams; - else - { - if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) - { - - return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; - } - params = (const RTPUDPv4TransmissionParams *) transparams; - } - - if (params->GetUseExistingSockets(rtpsock, rtcpsock)) - { - closesocketswhendone = false; - - // Determine the port numbers - int status = GetIPv4SocketPort(rtpsock, &m_rtpPort); - if (status < 0) - { - - return status; - } - status = GetIPv4SocketPort(rtcpsock, &m_rtcpPort); - if (status < 0) - { - - return status; - } - } - else - { - closesocketswhendone = true; - - if (params->GetPortbase() == 0) - { - int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); - if (status < 0) - { - - return status; - } - } - else - { - // Check if portbase is even (if necessary) - if (!params->GetAllowOddPortbase() && params->GetPortbase() % 2 != 0) - { - - return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; - } - - // create sockets - - rtpsock = socket(PF_INET, SOCK_DGRAM, 0); - if (rtpsock == RTPSOCKERR) - { - - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - - // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket - if (params->GetRTCPMultiplexing()) - rtcpsock = rtpsock; - else - { - rtcpsock = socket(PF_INET, SOCK_DGRAM, 0); - if (rtcpsock == RTPSOCKERR) - { - RTPCLOSE(rtpsock); - - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - } - - // bind sockets - - uint32_t bindIP = params->GetBindIP(); - - m_rtpPort = params->GetPortbase(); - - memset(&addr, 0, sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(params->GetPortbase()); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(rtpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; - } - - if (rtpsock != rtcpsock) // no need to bind same socket twice when multiplexing - { - uint16_t rtpport = params->GetPortbase(); - uint16_t rtcpport = params->GetForcedRTCPPort(); - - if (rtcpport == 0) - { - rtcpport = rtpport; - if (rtcpport < 0xFFFF) - rtcpport++; - } - - memset(&addr, 0, sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(rtcpport); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(rtcpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; - } - - m_rtcpPort = rtcpport; - } - else - m_rtcpPort = m_rtpPort; - } - - // set socket buffer sizes - - size = params->GetRTPReceiveBuffer(); - if (setsockopt(rtpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; - } - size = params->GetRTPSendBuffer(); - if (setsockopt(rtpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; - } - - if (rtpsock != rtcpsock) // no need to set RTCP flags when multiplexing - { - size = params->GetRTCPReceiveBuffer(); - if (setsockopt(rtcpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; - } - size = params->GetRTCPSendBuffer(); - if (setsockopt(rtcpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; - } - } - } - - // Try to obtain local IP addresses - - localIPs = params->GetLocalIPList(); - if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them - { - int status; - - if ((status = CreateLocalIPList()) < 0) - { - CLOSESOCKETS; - - return status; - } - } - -#ifdef RTP_SUPPORT_IPV4MULTICAST - if (SetMulticastTTL(params->GetMulticastTTL())) - supportsmulticasting = true; - else - supportsmulticasting = false; -#else // no multicast support enabled - supportsmulticasting = false; -#endif // RTP_SUPPORT_IPV4MULTICAST - - if (maximumpacketsize > RTPUDPV4TRANS_MAXPACKSIZE) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - - if (!params->GetCreatedAbortDescriptors()) - { - if ((status = m_abortDesc.Init()) < 0) - { - CLOSESOCKETS; - - return status; - } - m_pAbortDesc = &m_abortDesc; - } - else - { - m_pAbortDesc = params->GetCreatedAbortDescriptors(); - if (!m_pAbortDesc->IsInitialized()) - { - CLOSESOCKETS; - - return ERR_RTP_ABORTDESC_NOTINIT; - } - } - - maxpacksize = maximumpacketsize; - multicastTTL = params->GetMulticastTTL(); - mcastifaceIP = params->GetMulticastInterfaceIP(); - receivemode = RTPTransmitter::AcceptAll; - - localhostname = 0; - localhostnamelength = 0; - - waitingfordata = false; - created = true; - - return 0; -} - -void RTPUDPv4Transmitter::Destroy() -{ - if (!init) - return; - - if (!created) - { - ; - return; - } - - if (localhostname) - { - delete[] localhostname; - localhostname = 0; - localhostnamelength = 0; - } - - CLOSESOCKETS; - destinations.Clear(); -#ifdef RTP_SUPPORT_IPV4MULTICAST - multicastgroups.Clear(); -#endif // RTP_SUPPORT_IPV4MULTICAST - FlushPackets(); - ClearAcceptIgnoreInfo(); - localIPs.clear(); - created = false; - - if (waitingfordata) - { - m_pAbortDesc->SendAbortSignal(); - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - - // to make sure that the WaitForIncomingData function ended - - } - else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - -} - -RTPTransmissionInfo *RTPUDPv4Transmitter::GetTransmissionInfo() -{ - if (!init) - return 0; - - RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionInfo(localIPs, rtpsock, rtcpsock, m_rtpPort, m_rtcpPort); - - return tinf; -} - -void RTPUDPv4Transmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i) -{ - if (!init) - return; - - delete i; -} - -int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - - if (localhostname == 0) - { - if (localIPs.empty()) - { - - return ERR_RTP_UDPV4TRANS_NOLOCALIPS; - } - - std::list::const_iterator it; - std::list hostnames; - - for (it = localIPs.begin(); it != localIPs.end(); it++) - { - bool founddouble = false; - bool foundentry = true; - - while (!founddouble && foundentry) - { - struct hostent *he; - uint8_t addr[4]; - uint32_t ip = (*it); - - addr[0] = (uint8_t) ((ip >> 24) & 0xFF); - addr[1] = (uint8_t) ((ip >> 16) & 0xFF); - addr[2] = (uint8_t) ((ip >> 8) & 0xFF); - addr[3] = (uint8_t) (ip & 0xFF); - he = gethostbyaddr((char *) addr, 4, AF_INET); - if (he != 0) - { - std::string hname = std::string(he->h_name); - std::list::const_iterator it; - - for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) - hostnames.push_back(hname); - - int i = 0; - while (!founddouble && he->h_aliases[i] != 0) - { - std::string hname = std::string(he->h_aliases[i]); - - for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) - { - hostnames.push_back(hname); - i++; - } - } - } - else - foundentry = false; - } - } - - bool found = false; - - if (!hostnames.empty()) // try to select the most appropriate hostname - { - std::list::const_iterator it; - - hostnames.sort(); - for (it = hostnames.begin(); !found && it != hostnames.end(); it++) - { - if ((*it).find('.') != std::string::npos) - { - found = true; - localhostnamelength = (*it).length(); - localhostname = new uint8_t[localhostnamelength + 1]; - if (localhostname == 0) - { - - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname, (*it).c_str(), localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - } - - if (!found) // use an IP address - { - uint32_t ip; - int len; - char str[16]; - - it = localIPs.begin(); - ip = (*it); - - RTP_SNPRINTF(str, 16, "%d.%d.%d.%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF)); - len = strlen(str); - - localhostnamelength = len; - localhostname = new uint8_t[localhostnamelength + 1]; - if (localhostname == 0) - { - - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname, str, localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - - if ((*bufferlength) < localhostnamelength) - { - *bufferlength = localhostnamelength; // tell the application the required size of the buffer - - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } - - memcpy(buffer, localhostname, localhostnamelength); - *bufferlength = localhostnamelength; - - return 0; -} - -bool RTPUDPv4Transmitter::ComesFromThisTransmitter(const RTPAddress *addr) -{ - if (!init) - return false; - - if (addr == 0) - return false; - - bool v; - - if (created && addr->GetAddressType() == RTPAddress::IPv4Address) - { - const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; - bool found = false; - std::list::const_iterator it; - - it = localIPs.begin(); - while (!found && it != localIPs.end()) - { - if (addr2->GetIP() == *it) - found = true; - else - ++it; - } - - if (!found) - v = false; - else - { - if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port - v = true; - else - v = false; - } - } - else - v = false; - - return v; -} - -int RTPUDPv4Transmitter::Poll() -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - status = PollSocket(true); // poll RTP socket - if (rtpsock != rtcpsock) // no need to poll twice when multiplexing - { - if (status >= 0) - status = PollSocket(false); // poll RTCP socket - } - - return status; -} - -int RTPUDPv4Transmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (waitingfordata) - { - - return ERR_RTP_UDPV4TRANS_ALREADYWAITING; - } - - SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); - - SocketType socks[3] = - { rtpsock, rtcpsock, abortSocket }; - int8_t readflags[3] = - { 0, 0, 0 }; - const int idxRTP = 0; - const int idxRTCP = 1; - const int idxAbort = 2; - - waitingfordata = true; - - int status = RTPSelect(socks, readflags, 3, delay); - if (status < 0) - { - waitingfordata = false; - - return status; - } - - waitingfordata = false; - if (!created) // destroy called - { - ; - - return 0; - } - - // if aborted, read from abort buffer - if (readflags[idxAbort]) - m_pAbortDesc->ReadSignallingByte(); - - if (dataavailable != 0) - { - if (readflags[idxRTP] || readflags[idxRTCP]) - *dataavailable = true; - else - *dataavailable = false; - } - - return 0; -} - -int RTPUDPv4Transmitter::AbortWait() -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (!waitingfordata) - { - - return ERR_RTP_UDPV4TRANS_NOTWAITING; - } - - m_pAbortDesc->SendAbortSignal(); - - return 0; -} - -int RTPUDPv4Transmitter::SendRTPData(const void *data, std::size_t len) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTPSockAddr(), sizeof(struct sockaddr_in)); - destinations.GotoNextElement(); - } - - return 0; -} - -int RTPUDPv4Transmitter::SendRTCPData(const void *data, std::size_t len) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtcpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTCPSockAddr(), sizeof(struct sockaddr_in)); - destinations.GotoNextElement(); - } - - return 0; -} - -int RTPUDPv4Transmitter::AddDestination(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - - int status = destinations.AddElement(dest); - - return status; -} - -int RTPUDPv4Transmitter::DeleteDestination(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - - int status = destinations.DeleteElement(dest); - - return status; -} - -void RTPUDPv4Transmitter::ClearDestinations() -{ - if (!init) - return; - - if (created) - destinations.Clear(); - -} - -bool RTPUDPv4Transmitter::SupportsMulticasting() -{ - if (!init) - return false; - - bool v; - - if (!created) - v = false; - else - v = supportsmulticasting; - - return v; -} - -#ifdef RTP_SUPPORT_IPV4MULTICAST - -int RTPUDPv4Transmitter::JoinMulticastGroup(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - uint32_t mcastIP = address.GetIP(); - - if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) - { - - return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; - } - - status = multicastgroups.AddElement(mcastIP); - if (status >= 0) - { - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_ADD_MEMBERSHIP, mcastIP, status); - if (status != 0) - { - multicastgroups.DeleteElement(mcastIP); - - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; - } - - if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing - { - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_ADD_MEMBERSHIP, mcastIP, status); - if (status != 0) - { - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - multicastgroups.DeleteElement(mcastIP); - - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; - } - } - } - - return status; -} - -int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - uint32_t mcastIP = address.GetIP(); - - if (!RTPUDPV4TRANS_IS_MCASTADDR(mcastIP)) - { - - return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; - } - - status = multicastgroups.DeleteElement(mcastIP); - if (status >= 0) - { - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - - status = 0; - } - - return status; -} - -void RTPUDPv4Transmitter::LeaveAllMulticastGroups() -{ - if (!init) - return; - - if (created) - { - multicastgroups.GotoFirstElement(); - while (multicastgroups.HasCurrentElement()) - { - uint32_t mcastIP; - int status __attribute__((unused)) = 0; - - mcastIP = multicastgroups.GetCurrentElement(); - - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANS_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - - multicastgroups.GotoNextElement(); - } - multicastgroups.Clear(); - } - -} - -#else // no multicast support - -int RTPUDPv4Transmitter::JoinMulticastGroup(const RTPAddress &addr) -{ - return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; -} - -int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) -{ - return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; -} - -void RTPUDPv4Transmitter::LeaveAllMulticastGroups() -{ -} - -#endif // RTP_SUPPORT_IPV4MULTICAST - -int RTPUDPv4Transmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (m != receivemode) - { - receivemode = m; - acceptignoreinfo.Clear(); - } - - return 0; -} - -int RTPUDPv4Transmitter::AddToIgnoreList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - - return status; -} - -int RTPUDPv4Transmitter::DeleteFromIgnoreList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - - return status; -} - -void RTPUDPv4Transmitter::ClearIgnoreList() -{ - if (!init) - return; - - if (created && receivemode == RTPTransmitter::IgnoreSome) - ClearAcceptIgnoreInfo(); - -} - -int RTPUDPv4Transmitter::AddToAcceptList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - - return status; -} - -int RTPUDPv4Transmitter::DeleteFromAcceptList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - - return status; -} - -void RTPUDPv4Transmitter::ClearAcceptList() -{ - if (!init) - return; - - if (created && receivemode == RTPTransmitter::AcceptSome) - ClearAcceptIgnoreInfo(); - -} - -int RTPUDPv4Transmitter::SetMaximumPacketSize(std::size_t s) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (s > RTPUDPV4TRANS_MAXPACKSIZE) - { - - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - maxpacksize = s; - - return 0; -} - -bool RTPUDPv4Transmitter::NewDataAvailable() -{ - if (!init) - return false; - - bool v; - - if (!created) - v = false; - else - { - if (rawpacketlist.empty()) - v = false; - else - v = true; - } - - return v; -} - -RTPRawPacket *RTPUDPv4Transmitter::GetNextPacket() -{ - if (!init) - return 0; - - RTPRawPacket *p; - - if (!created) - { - - return 0; - } - if (rawpacketlist.empty()) - { - - return 0; - } - - p = *(rawpacketlist.begin()); - rawpacketlist.pop_front(); - - return p; -} - -// Here the private functions start... - -#ifdef RTP_SUPPORT_IPV4MULTICAST -bool RTPUDPv4Transmitter::SetMulticastTTL(uint8_t ttl) -{ - int ttl2, status; - - ttl2 = (int) ttl; - status = setsockopt(rtpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); - if (status != 0) - return false; - - if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing - { - status = setsockopt(rtcpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); - if (status != 0) - return false; - } - return true; -} -#endif // RTP_SUPPORT_IPV4MULTICAST - -void RTPUDPv4Transmitter::FlushPackets() -{ - std::list::const_iterator it; - - for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) - delete *it; - rawpacketlist.clear(); -} - -int RTPUDPv4Transmitter::PollSocket(bool rtp) -{ - RTPSOCKLENTYPE fromlen; - int recvlen; - char packetbuffer[RTPUDPV4TRANS_MAXPACKSIZE]; -#ifdef RTP_SOCKETTYPE_WINSOCK - SOCKET sock; - unsigned long len; -#else - std::size_t len; - int sock; -#endif // RTP_SOCKETTYPE_WINSOCK - struct sockaddr_in srcaddr; - bool dataavailable; - - if (rtp) - sock = rtpsock; - else - sock = rtcpsock; - - do - { - len = 0; - RTPIOCTL(sock, FIONREAD, &len); - - if (len <= 0) // make sure a packet of length zero is not queued - { - // An alternative workaround would be to just use non-blocking sockets. - // However, since the user does have access to the sockets and I do not - // know how this would affect anyone else's code, I chose to do it using - // an extra select call in case ioctl says the length is zero. - - int8_t isset = 0; - int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); - if (status < 0) - return status; - - if (isset) - dataavailable = true; - else - dataavailable = false; - } - else - dataavailable = true; - - if (dataavailable) - { - RTPTime curtime = RTPTime::CurrentTime(); - fromlen = sizeof(struct sockaddr_in); - recvlen = recvfrom(sock, packetbuffer, RTPUDPV4TRANS_MAXPACKSIZE, 0, (struct sockaddr *) &srcaddr, &fromlen); - if (recvlen > 0) - { - bool acceptdata; - - // got data, process it - if (receivemode == RTPTransmitter::AcceptAll) - acceptdata = true; - else - acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); - - if (acceptdata) - { - RTPRawPacket *pack; - RTPIPv4Address *addr; - uint8_t *datacopy; - - addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); - if (addr == 0) - return ERR_RTP_OUTOFMEM; - datacopy = new uint8_t[recvlen]; - if (datacopy == 0) - { - delete addr; - return ERR_RTP_OUTOFMEM; - } - memcpy(datacopy, packetbuffer, recvlen); - - bool isrtp = rtp; - if (rtpsock == rtcpsock) // check payload type when multiplexing - { - isrtp = true; - - if ((std::size_t) recvlen > sizeof(RTCPCommonHeader)) - { - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; - uint8_t packettype = rtcpheader->packettype; - - if (packettype >= 200 && packettype <= 204) - isrtp = false; - } - } - - pack = new RTPRawPacket(datacopy, recvlen, addr, curtime, isrtp); - if (pack == 0) - { - delete addr; - delete[] datacopy; - return ERR_RTP_OUTOFMEM; - } - rawpacketlist.push_back(pack); - } - } - } - } while (dataavailable); - - return 0; -} - -int RTPUDPv4Transmitter::ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port) -{ - acceptignoreinfo.GotoElement(ip); - if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists - { - PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); - - if (port == 0) // select all ports - { - portinf->all = true; - portinf->portlist.clear(); - } - else if (!portinf->all) - { - std::list::const_iterator it, begin, end; - - begin = portinf->portlist.begin(); - end = portinf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == port) // already in list - return 0; - } - portinf->portlist.push_front(port); - } - } - else // got to create an entry for this IP address - { - PortInfo *portinf; - int status; - - portinf = new PortInfo(); - if (port == 0) // select all ports - portinf->all = true; - else - portinf->portlist.push_front(port); - - status = acceptignoreinfo.AddElement(ip, portinf); - if (status < 0) - { - delete portinf; - return status; - } - } - - return 0; -} - -void RTPUDPv4Transmitter::ClearAcceptIgnoreInfo() -{ - acceptignoreinfo.GotoFirstElement(); - while (acceptignoreinfo.HasCurrentElement()) - { - PortInfo *inf; - - inf = acceptignoreinfo.GetCurrentElement(); - delete inf; - acceptignoreinfo.GotoNextElement(); - } - acceptignoreinfo.Clear(); -} - -int RTPUDPv4Transmitter::ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port) -{ - acceptignoreinfo.GotoElement(ip); - if (!acceptignoreinfo.HasCurrentElement()) - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - - PortInfo *inf; - - inf = acceptignoreinfo.GetCurrentElement(); - if (port == 0) // delete all entries - { - inf->all = false; - inf->portlist.clear(); - } - else // a specific port was selected - { - if (inf->all) // currently, all ports are selected. Add the one to remove to the list - { - // we have to check if the list doesn't contain the port already - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == port) // already in list: this means we already deleted the entry - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - } - inf->portlist.push_front(port); - } - else // check if we can find the port in the list - { - std::list::iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; ++it) - { - if (*it == port) // found it! - { - inf->portlist.erase(it); - return 0; - } - } - // didn't find it - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - } - } - return 0; -} - -bool RTPUDPv4Transmitter::ShouldAcceptData(uint32_t srcip, uint16_t srcport) -{ - if (receivemode == RTPTransmitter::AcceptSome) - { - PortInfo *inf; - - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return false; - - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // only accept the ones in the list - { - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == srcport) - return true; - } - return false; - } - else // accept all, except the ones in the list - { - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == srcport) - return false; - } - return true; - } - } - else // IgnoreSome - { - PortInfo *inf; - - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return true; - - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // ignore the ports in the list - { - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == srcport) - return false; - } - return true; - } - else // ignore all, except the ones in the list - { - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == srcport) - return true; - } - return false; - } - } - return true; -} - -int RTPUDPv4Transmitter::CreateLocalIPList() -{ - // first try to obtain the list from the network interface info - - if (!GetLocalIPList_Interfaces()) - { - // If this fails, we'll have to depend on DNS info - GetLocalIPList_DNS(); - } - AddLoopbackAddress(); - return 0; -} - -#ifdef RTP_SOCKETTYPE_WINSOCK - -bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() -{ - unsigned char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; - DWORD outputsize; - DWORD numaddresses,i; - SOCKET_ADDRESS_LIST *addrlist; - - if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANS_IFREQBUFSIZE,&outputsize,NULL,NULL)) - return false; - - addrlist = (SOCKET_ADDRESS_LIST *)buffer; - numaddresses = addrlist->iAddressCount; - for (i = 0; i < numaddresses; i++) - { - SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); - if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address - { - struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; - - localIPs.push_back(ntohl(addr->sin_addr.s_addr)); - } - } - - if (localIPs.empty()) - return false; - - return true; -} - -#else // use either getifaddrs or ioctl - -#ifdef RTP_SUPPORT_IFADDRS - -bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() -{ - struct ifaddrs *addrs, *tmp; - - getifaddrs(&addrs); - tmp = addrs; - - while (tmp != 0) - { - if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) - { - struct sockaddr_in *inaddr = (struct sockaddr_in *) tmp->ifa_addr; - localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); - } - tmp = tmp->ifa_next; - } - - freeifaddrs(addrs); - - if (localIPs.empty()) - return false; - return true; -} - -#else // user ioctl - -bool RTPUDPv4Transmitter::GetLocalIPList_Interfaces() -{ - int status; - char buffer[RTPUDPV4TRANS_IFREQBUFSIZE]; - struct ifconf ifc; - struct ifreq *ifr; - struct sockaddr *sa; - char *startptr,*endptr; - int remlen; - - ifc.ifc_len = RTPUDPV4TRANS_IFREQBUFSIZE; - ifc.ifc_buf = buffer; - status = ioctl(rtpsock,SIOCGIFCONF,&ifc); - if (status < 0) - return false; - - startptr = (char *)ifc.ifc_req; - endptr = startptr + ifc.ifc_len; - remlen = ifc.ifc_len; - while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) - { - ifr = (struct ifreq *)startptr; - sa = &(ifr->ifr_addr); -#ifdef RTP_HAVE_SOCKADDR_LEN - if (sa->sa_len <= sizeof(struct sockaddr)) - { - if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; - - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); - } - else - { - int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); - - remlen -= l; - startptr += l; - } -#else // don't have sa_len in struct sockaddr - if (sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; - - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); - -#endif // RTP_HAVE_SOCKADDR_LEN - } - - if (localIPs.empty()) - return false; - return true; -} - -#endif // RTP_SUPPORT_IFADDRS - -#endif // RTP_SOCKETTYPE_WINSOCK - -void RTPUDPv4Transmitter::GetLocalIPList_DNS() -{ - struct hostent *he; - char name[1024]; - bool done; - int i, j; - - gethostname(name, 1023); - name[1023] = 0; - he = gethostbyname(name); - if (he == 0) - return; - - i = 0; - done = false; - while (!done) - { - if (he->h_addr_list[i] == NULL) - done = true; - else - { - uint32_t ip = 0; - - for (j = 0; j < 4; j++) - ip |= ((uint32_t) ((unsigned char) he->h_addr_list[i][j]) << ((3 - j) * 8)); - localIPs.push_back(ip); - i++; - } - } -} - -void RTPUDPv4Transmitter::AddLoopbackAddress() -{ - uint32_t loopbackaddr = (((uint32_t) 127) << 24) | ((uint32_t) 1); - std::list::const_iterator it; - bool found = false; - - for (it = localIPs.begin(); !found && it != localIPs.end(); it++) - { - if (*it == loopbackaddr) - found = true; - } - - if (!found) - localIPs.push_back(loopbackaddr); -} - -} // end namespace - diff --git a/qrtplib/rtpudpv4transmitter.h b/qrtplib/rtpudpv4transmitter.h deleted file mode 100644 index aa9abac8b..000000000 --- a/qrtplib/rtpudpv4transmitter.h +++ /dev/null @@ -1,488 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpudpv4transmitter.h - */ - -#ifndef RTPUDPV4TRANSMITTER_H - -#define RTPUDPV4TRANSMITTER_H - -#include "rtpconfig.h" -#include "rtptransmitter.h" -#include "rtpipv4destination.h" -#include "rtphashtable.h" -#include "rtpkeyhashtable.h" -#include "rtpsocketutil.h" -#include "rtpabortdescriptors.h" -#include - -#include "util/export.h" - -#define RTPUDPV4TRANS_HASHSIZE 8317 -#define RTPUDPV4TRANS_DEFAULTPORTBASE 5000 - -#define RTPUDPV4TRANS_RTPRECEIVEBUFFER 32768 -#define RTPUDPV4TRANS_RTCPRECEIVEBUFFER 32768 -#define RTPUDPV4TRANS_RTPTRANSMITBUFFER 32768 -#define RTPUDPV4TRANS_RTCPTRANSMITBUFFER 32768 - -namespace qrtplib -{ - -/** Parameters for the UDP over IPv4 transmitter. */ -class QRTPLIB_API RTPUDPv4TransmissionParams: public RTPTransmissionParams -{ -public: - RTPUDPv4TransmissionParams(); - - /** Sets the IP address which is used to bind the sockets to \c ip. */ - void SetBindIP(uint32_t ip) - { - bindIP = ip; - } - - /** Sets the multicast interface IP address. */ - void SetMulticastInterfaceIP(uint32_t ip) - { - mcastifaceIP = ip; - } - - /** Sets the RTP portbase to \c pbase, which has to be an even number - * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; - * a port number of zero will cause a port to be chosen automatically. */ - void SetPortbase(uint16_t pbase) - { - portbase = pbase; - } - - /** Sets the multicast TTL to be used to \c mcastTTL. */ - void SetMulticastTTL(uint8_t mcastTTL) - { - multicastTTL = mcastTTL; - } - - /** Passes a list of IP addresses which will be used as the local IP addresses. */ - void SetLocalIPList(std::list &iplist) - { - localIPs = iplist; - } - - /** Clears the list of local IP addresses. - * Clears the list of local IP addresses. An empty list will make the transmission - * component itself determine the local IP addresses. - */ - void ClearLocalIPList() - { - localIPs.clear(); - } - - /** Returns the IP address which will be used to bind the sockets. */ - uint32_t GetBindIP() const - { - return bindIP; - } - - /** Returns the multicast interface IP address. */ - uint32_t GetMulticastInterfaceIP() const - { - return mcastifaceIP; - } - - /** Returns the RTP portbase which will be used (default is 5000). */ - uint16_t GetPortbase() const - { - return portbase; - } - - /** Returns the multicast TTL which will be used (default is 1). */ - uint8_t GetMulticastTTL() const - { - return multicastTTL; - } - - /** Returns the list of local IP addresses. */ - const std::list &GetLocalIPList() const - { - return localIPs; - } - - /** Sets the RTP socket's send buffer size. */ - void SetRTPSendBuffer(int s) - { - rtpsendbuf = s; - } - - /** Sets the RTP socket's receive buffer size. */ - void SetRTPReceiveBuffer(int s) - { - rtprecvbuf = s; - } - - /** Sets the RTCP socket's send buffer size. */ - void SetRTCPSendBuffer(int s) - { - rtcpsendbuf = s; - } - - /** Sets the RTCP socket's receive buffer size. */ - void SetRTCPReceiveBuffer(int s) - { - rtcprecvbuf = s; - } - - /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ - void SetRTCPMultiplexing(bool f) - { - rtcpmux = f; - } - - /** Can be used to allow the RTP port base to be any number, not just even numbers. */ - void SetAllowOddPortbase(bool f) - { - allowoddportbase = f; - } - - /** Force the RTCP socket to use a specific port, not necessarily one more than - * the RTP port (set this to zero to disable). */ - void SetForcedRTCPPort(uint16_t rtcpport) - { - forcedrtcpport = rtcpport; - } - - /** Use sockets that have already been created, no checks on port numbers - * will be done, and no buffer sizes will be set; you'll need to close - * the sockets yourself when done, it will **not** be done automatically. */ - void SetUseExistingSockets(SocketType rtpsocket, SocketType rtcpsocket) - { - rtpsock = rtpsocket; - rtcpsock = rtcpsocket; - useexistingsockets = true; - } - - /** If non null, the specified abort descriptors will be used to cancel - * the function that's waiting for packets to arrive; set to null (the default - * to let the transmitter create its own instance. */ - void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) - { - m_pAbortDesc = desc; - } - - /** Returns the RTP socket's send buffer size. */ - int GetRTPSendBuffer() const - { - return rtpsendbuf; - } - - /** Returns the RTP socket's receive buffer size. */ - int GetRTPReceiveBuffer() const - { - return rtprecvbuf; - } - - /** Returns the RTCP socket's send buffer size. */ - int GetRTCPSendBuffer() const - { - return rtcpsendbuf; - } - - /** Returns the RTCP socket's receive buffer size. */ - int GetRTCPReceiveBuffer() const - { - return rtcprecvbuf; - } - - /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ - bool GetRTCPMultiplexing() const - { - return rtcpmux; - } - - /** If true, any RTP portbase will be allowed, not just even numbers. */ - bool GetAllowOddPortbase() const - { - return allowoddportbase; - } - - /** If non-zero, the specified port will be used to receive RTCP traffic. */ - uint16_t GetForcedRTCPPort() const - { - return forcedrtcpport; - } - - /** Returns true and fills in sockets if existing sockets were set - * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ - bool GetUseExistingSockets(SocketType &rtpsocket, SocketType &rtcpsocket) const - { - if (!useexistingsockets) - return false; - rtpsocket = rtpsock; - rtcpsocket = rtcpsock; - return true; - } - - /** If non-null, this RTPAbortDescriptors instance will be used internally, - * which can be useful when creating your own poll thread for multiple - * sessions. */ - RTPAbortDescriptors *GetCreatedAbortDescriptors() const - { - return m_pAbortDesc; - } -private: - uint16_t portbase; - uint32_t bindIP, mcastifaceIP; - std::list localIPs; - uint8_t multicastTTL; - int rtpsendbuf, rtprecvbuf; - int rtcpsendbuf, rtcprecvbuf; - bool rtcpmux; - bool allowoddportbase; - uint16_t forcedrtcpport; - - SocketType rtpsock, rtcpsock; - bool useexistingsockets; - - RTPAbortDescriptors *m_pAbortDesc; -}; - -inline RTPUDPv4TransmissionParams::RTPUDPv4TransmissionParams() : - RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) -{ - portbase = RTPUDPV4TRANS_DEFAULTPORTBASE; - bindIP = 0; - multicastTTL = 1; - mcastifaceIP = 0; - rtpsendbuf = RTPUDPV4TRANS_RTPTRANSMITBUFFER; - rtprecvbuf = RTPUDPV4TRANS_RTPRECEIVEBUFFER; - rtcpsendbuf = RTPUDPV4TRANS_RTCPTRANSMITBUFFER; - rtcprecvbuf = RTPUDPV4TRANS_RTCPRECEIVEBUFFER; - rtcpmux = false; - allowoddportbase = false; - forcedrtcpport = 0; - useexistingsockets = false; - rtpsock = 0; - rtcpsock = 0; - m_pAbortDesc = 0; -} - -/** Additional information about the UDP over IPv4 transmitter. */ -class QRTPLIB_API RTPUDPv4TransmissionInfo: public RTPTransmissionInfo -{ -public: - RTPUDPv4TransmissionInfo(std::list iplist, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : - RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) - { - localIPlist = iplist; - rtpsocket = rtpsock; - rtcpsocket = rtcpsock; - m_rtpPort = rtpport; - m_rtcpPort = rtcpport; - } - - ~RTPUDPv4TransmissionInfo() - { - } - - /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ - std::list GetLocalIPList() const - { - return localIPlist; - } - - /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ - SocketType GetRTPSocket() const - { - return rtpsocket; - } - - /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ - SocketType GetRTCPSocket() const - { - return rtcpsocket; - } - - /** Returns the port number that the RTP socket receives packets on. */ - uint16_t GetRTPPort() const - { - return m_rtpPort; - } - - /** Returns the port number that the RTCP socket receives packets on. */ - uint16_t GetRTCPPort() const - { - return m_rtcpPort; - } -private: - std::list localIPlist; - SocketType rtpsocket, rtcpsocket; - uint16_t m_rtpPort, m_rtcpPort; -}; - -class RTPUDPv4Trans_GetHashIndex_IPv4Dest -{ -public: - static int GetIndex(const RTPIPv4Destination &d) - { - return d.GetIP() % RTPUDPV4TRANS_HASHSIZE; - } -}; - -class RTPUDPv4Trans_GetHashIndex_uint32_t -{ -public: - static int GetIndex(const uint32_t &k) - { - return k % RTPUDPV4TRANS_HASHSIZE; - } -}; - -#define RTPUDPV4TRANS_HEADERSIZE (20+8) - -/** An UDP over IPv4 transmission component. - * This class inherits the RTPTransmitter interface and implements a transmission component - * which uses UDP over IPv4 to send and receive RTP and RTCP data. The component's parameters - * are described by the class RTPUDPv4TransmissionParams. The functions which have an RTPAddress - * argument require an argument of RTPIPv4Address. The GetTransmissionInfo member function - * returns an instance of type RTPUDPv4TransmissionInfo. - */ -class QRTPLIB_API RTPUDPv4Transmitter: public RTPTransmitter -{ -public: - RTPUDPv4Transmitter(); - ~RTPUDPv4Transmitter(); - - int Init(bool treadsafe); - int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - - int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - std::size_t GetHeaderOverhead() - { - return RTPUDPV4TRANS_HEADERSIZE; - } - - int Poll(); - int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); - int AbortWait(); - - int SendRTPData(const void *data, std::size_t len); - int SendRTCPData(const void *data, std::size_t len); - - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); - - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); - - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(std::size_t s); - - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); - -private: - int CreateLocalIPList(); - bool GetLocalIPList_Interfaces(); - void GetLocalIPList_DNS(); - void AddLoopbackAddress(); - void FlushPackets(); - int PollSocket(bool rtp); - int ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port); - int ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port); -#ifdef RTP_SUPPORT_IPV4MULTICAST - bool SetMulticastTTL(uint8_t ttl); -#endif // RTP_SUPPORT_IPV4MULTICAST - bool ShouldAcceptData(uint32_t srcip, uint16_t srcport); - void ClearAcceptIgnoreInfo(); - - int GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, SocketType *pRtpSock, SocketType *pRtcpSock, uint16_t *pRtpPort, uint16_t *pRtcpPort); - static int GetIPv4SocketPort(SocketType s, uint16_t *pPort); - - bool init; - bool created; - bool waitingfordata; - SocketType rtpsock, rtcpsock; - uint32_t mcastifaceIP; - std::list localIPs; - uint16_t m_rtpPort, m_rtcpPort; - uint8_t multicastTTL; - RTPTransmitter::ReceiveMode receivemode; - - uint8_t *localhostname; - std::size_t localhostnamelength; - - RTPHashTable destinations; -#ifdef RTP_SUPPORT_IPV4MULTICAST - RTPHashTable multicastgroups; -#endif // RTP_SUPPORT_IPV4MULTICAST - std::list rawpacketlist; - - bool supportsmulticasting; - std::size_t maxpacksize; - - class PortInfo - { - public: - PortInfo() - { - all = false; - } - - bool all; - std::list portlist; - }; - - RTPKeyHashTable acceptignoreinfo; - - bool closesocketswhendone; - RTPAbortDescriptors m_abortDesc; - RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified - -}; - -} // end namespace - -#endif // RTPUDPV4TRANSMITTER_H - diff --git a/qrtplib/rtpudpv4transmitternobind.cpp b/qrtplib/rtpudpv4transmitternobind.cpp deleted file mode 100644 index e51ba491d..000000000 --- a/qrtplib/rtpudpv4transmitternobind.cpp +++ /dev/null @@ -1,1836 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -#include "rtpudpv4transmitternobind.h" -#include "rtprawpacket.h" -#include "rtpipv4address.h" -#include "rtptimeutilities.h" -#include "rtpdefines.h" -#include "rtpstructs.h" -#include "rtpsocketutilinternal.h" -#include "rtpinternalutils.h" -#include "rtpselect.h" -#include -#include -#include - -#include - -using namespace std; - -#define RTPUDPV4TRANSNOBIND_MAXPACKSIZE 65535 -#define RTPUDPV4TRANSNOBIND_IFREQBUFSIZE 8192 - -#define RTPUDPV4TRANSNOBIND_IS_MCASTADDR(x) (((x)&0xF0000000) == 0xE0000000) - -#define RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(socket,type,mcastip,status) {\ - struct ip_mreq mreq;\ - \ - mreq.imr_multiaddr.s_addr = htonl(mcastip);\ - mreq.imr_interface.s_addr = htonl(mcastifaceIP);\ - status = setsockopt(socket,IPPROTO_IP,type,(const char *)&mreq,sizeof(struct ip_mreq));\ - } - -#define CLOSESOCKETS do { \ - if (closesocketswhendone) \ - {\ - if (rtpsock != rtcpsock) \ - RTPCLOSE(rtcpsock); \ - RTPCLOSE(rtpsock); \ - } \ -} while(0) - -namespace qrtplib -{ - -RTPUDPv4TransmitterNoBind::RTPUDPv4TransmitterNoBind() : - init(false), created(false), waitingfordata(false), rtpsock(-1), rtcpsock(-1), mcastifaceIP(0), m_rtpPort(0), m_rtcpPort(0), multicastTTL(0), receivemode(AcceptAll), localhostname( - 0), localhostnamelength(0), supportsmulticasting(false), maxpacksize(0), closesocketswhendone(false), m_pAbortDesc(0) -{ -} - -RTPUDPv4TransmitterNoBind::~RTPUDPv4TransmitterNoBind() -{ - Destroy(); -} - -int RTPUDPv4TransmitterNoBind::Init(bool tsafe) -{ - if (init) - return ERR_RTP_UDPV4TRANS_ALREADYINIT; - - if (tsafe) - return ERR_RTP_NOTHREADSUPPORT; - - init = true; - return 0; -} - -int RTPUDPv4TransmitterNoBind::GetIPv4SocketPort(SocketType s, uint16_t *pPort) -{ - assert(pPort != 0); - - struct sockaddr_in addr; - memset(&addr, 0, sizeof(struct sockaddr_in)); - - RTPSOCKLENTYPE size = sizeof(struct sockaddr_in); - if (getsockname(s, (struct sockaddr*) &addr, &size) != 0) - return ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT; - - if (addr.sin_family != AF_INET) - return ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET; - - uint16_t port = ntohs(addr.sin_port); - if (port == 0) - return ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET; - - int type = 0; - RTPSOCKLENTYPE length = sizeof(type); - - if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*) &type, &length) != 0) - return ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE; - - if (type != SOCK_DGRAM) - return ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE; - - *pPort = port; - return 0; -} - -int RTPUDPv4TransmitterNoBind::GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, SocketType *pRtpSock, SocketType *pRtcpSock, uint16_t *pRtpPort, uint16_t *pRtcpPort) -{ - const int maxAttempts = 1024; - int attempts = 0; - vector toClose; - - while (attempts++ < maxAttempts) - { - SocketType sock = socket(PF_INET, SOCK_DGRAM, 0); - if (sock == RTPSOCKERR) - { - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - - // First we get an automatically chosen port - - struct sockaddr_in addr; - memset(&addr, 0, sizeof(struct sockaddr_in)); - - addr.sin_family = AF_INET; - addr.sin_port = 0; - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) - { - RTPCLOSE(sock); - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET; - } - - uint16_t basePort = 0; - int status = GetIPv4SocketPort(sock, &basePort); - if (status < 0) - { - RTPCLOSE(sock); - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - return status; - } - - if (rtcpMux) // only need one socket - { - if (basePort % 2 == 0 || allowOdd) - { - *pRtpSock = sock; - *pRtcpSock = sock; - *pRtpPort = basePort; - *pRtcpPort = basePort; - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - - return 0; - } - else - toClose.push_back(sock); - } - else - { - SocketType sock2 = socket(PF_INET, SOCK_DGRAM, 0); - if (sock2 == RTPSOCKERR) - { - RTPCLOSE(sock); - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - - // Try the next port or the previous port - uint16_t secondPort = basePort; - bool possiblyValid = false; - - if (basePort % 2 == 0) - { - secondPort++; - possiblyValid = true; - } - else if (basePort > 1) // avoid landing on port 0 - { - secondPort--; - possiblyValid = true; - } - - if (possiblyValid) - { - memset(&addr, 0, sizeof(struct sockaddr_in)); - - addr.sin_family = AF_INET; - addr.sin_port = htons(secondPort); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(sock2, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == 0) - { - // In this case, we have two consecutive port numbers, the lower of - // which is even - - if (basePort < secondPort) - { - *pRtpSock = sock; - *pRtcpSock = sock2; - *pRtpPort = basePort; - *pRtcpPort = secondPort; - } - else - { - *pRtpSock = sock2; - *pRtcpSock = sock; - *pRtpPort = secondPort; - *pRtcpPort = basePort; - } - - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - - return 0; - } - } - - toClose.push_back(sock); - toClose.push_back(sock2); - } - } - - for (std::size_t i = 0; i < toClose.size(); i++) - RTPCLOSE(toClose[i]); - - return ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET; -} - -int RTPUDPv4TransmitterNoBind::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) -{ - const RTPUDPv4TransmissionNoBindParams *params, defaultparams; -// struct sockaddr_in addr; - RTPSOCKLENTYPE size; - int status; - - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (created) - { - - return ERR_RTP_UDPV4TRANS_ALREADYCREATED; - } - - // Obtain transmission parameters - - if (transparams == 0) - params = &defaultparams; - else - { - if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) - { - - return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; - } - params = (const RTPUDPv4TransmissionNoBindParams *) transparams; - } - - if (params->GetUseExistingSockets(rtpsock, rtcpsock)) - { - closesocketswhendone = false; - - // Determine the port numbers. They are set to 0 if the sockets are not bound. - GetIPv4SocketPort(rtpsock, &m_rtpPort); - GetIPv4SocketPort(rtcpsock, &m_rtcpPort); - } - else - { - closesocketswhendone = true; - - if (params->GetPortbase() == 0) - { - int status = GetAutoSockets(params->GetBindIP(), params->GetAllowOddPortbase(), params->GetRTCPMultiplexing(), &rtpsock, &rtcpsock, &m_rtpPort, &m_rtcpPort); - if (status < 0) - { - - return status; - } - } - else - { - // Check if portbase is even (if necessary) - if (!params->GetAllowOddPortbase() && params->GetPortbase() % 2 != 0) - { - - return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; - } - - // create sockets - - rtpsock = socket(PF_INET, SOCK_DGRAM, 0); - if (rtpsock == RTPSOCKERR) - { - - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - - // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket - if (params->GetRTCPMultiplexing()) - rtcpsock = rtpsock; - else - { - rtcpsock = socket(PF_INET, SOCK_DGRAM, 0); - if (rtcpsock == RTPSOCKERR) - { - RTPCLOSE(rtpsock); - - return ERR_RTP_UDPV4TRANS_CANTCREATESOCKET; - } - } - - } - - // set socket buffer sizes - - size = params->GetRTPReceiveBuffer(); - if (setsockopt(rtpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF; - } - size = params->GetRTPSendBuffer(); - if (setsockopt(rtpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF; - } - - if (rtpsock != rtcpsock) // no need to set RTCP flags when multiplexing - { - size = params->GetRTCPReceiveBuffer(); - if (setsockopt(rtcpsock, SOL_SOCKET, SO_RCVBUF, (const char *) &size, sizeof(int)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF; - } - size = params->GetRTCPSendBuffer(); - if (setsockopt(rtcpsock, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(int)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF; - } - } - } - - // Try to obtain local IP addresses - - localIPs = params->GetLocalIPList(); - if (localIPs.empty()) // User did not provide list of local IP addresses, calculate them - { - int status; - - if ((status = CreateLocalIPList()) < 0) - { - CLOSESOCKETS; - - return status; - } - } - -#ifdef RTP_SUPPORT_IPV4MULTICAST - if (SetMulticastTTL(params->GetMulticastTTL())) - supportsmulticasting = true; - else - supportsmulticasting = false; -#else // no multicast support enabled - supportsmulticasting = false; -#endif // RTP_SUPPORT_IPV4MULTICAST - - if (maximumpacketsize > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - - if (!params->GetCreatedAbortDescriptors()) - { - if ((status = m_abortDesc.Init()) < 0) - { - CLOSESOCKETS; - - return status; - } - m_pAbortDesc = &m_abortDesc; - } - else - { - m_pAbortDesc = params->GetCreatedAbortDescriptors(); - if (!m_pAbortDesc->IsInitialized()) - { - CLOSESOCKETS; - - return ERR_RTP_ABORTDESC_NOTINIT; - } - } - - maxpacksize = maximumpacketsize; - multicastTTL = params->GetMulticastTTL(); - mcastifaceIP = params->GetMulticastInterfaceIP(); - receivemode = RTPTransmitter::AcceptAll; - - localhostname = 0; - localhostnamelength = 0; - - waitingfordata = false; - created = true; - - return 0; -} - -int RTPUDPv4TransmitterNoBind::BindSockets(const RTPTransmissionParams *transparams) -{ - if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) - { - - return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; - } - - const RTPUDPv4TransmissionNoBindParams *params = (const RTPUDPv4TransmissionNoBindParams *) transparams; - - uint32_t bindIP = params->GetBindIP(); - m_rtpPort = params->GetPortbase(); - struct sockaddr_in addr; - - memset(&addr, 0, sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(params->GetPortbase()); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(rtpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; - } - - if (rtpsock != rtcpsock) // no need to bind same socket twice when multiplexing - { - uint16_t rtpport = params->GetPortbase(); - uint16_t rtcpport = params->GetForcedRTCPPort(); - - if (rtcpport == 0) - { - rtcpport = rtpport; - if (rtcpport < 0xFFFF) - rtcpport++; - } - - memset(&addr, 0, sizeof(struct sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(rtcpport); - addr.sin_addr.s_addr = htonl(bindIP); - if (bind(rtcpsock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) - { - CLOSESOCKETS; - - return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; - } - - m_rtcpPort = rtcpport; - } - else - m_rtcpPort = m_rtpPort; - - return 0; -} - -void RTPUDPv4TransmitterNoBind::Destroy() -{ - if (!init) - return; - - if (!created) - { - ; - return; - } - - if (localhostname) - { - delete[] localhostname; - localhostname = 0; - localhostnamelength = 0; - } - - CLOSESOCKETS; - destinations.Clear(); -#ifdef RTP_SUPPORT_IPV4MULTICAST - multicastgroups.Clear(); -#endif // RTP_SUPPORT_IPV4MULTICAST - FlushPackets(); - ClearAcceptIgnoreInfo(); - localIPs.clear(); - created = false; - - if (waitingfordata) - { - m_pAbortDesc->SendAbortSignal(); - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - - // to make sure that the WaitForIncomingData function ended - - } - else - m_abortDesc.Destroy(); // Doesn't do anything if not initialized - -} - -RTPTransmissionInfo *RTPUDPv4TransmitterNoBind::GetTransmissionInfo() -{ - if (!init) - return 0; - - RTPTransmissionInfo *tinf = new RTPUDPv4TransmissionNoBindInfo(localIPs, rtpsock, rtcpsock, m_rtpPort, m_rtcpPort); - - return tinf; -} - -void RTPUDPv4TransmitterNoBind::DeleteTransmissionInfo(RTPTransmissionInfo *i) -{ - if (!init) - return; - - delete i; -} - -int RTPUDPv4TransmitterNoBind::GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - - if (localhostname == 0) - { - if (localIPs.empty()) - { - - return ERR_RTP_UDPV4TRANS_NOLOCALIPS; - } - - std::list::const_iterator it; - std::list hostnames; - - for (it = localIPs.begin(); it != localIPs.end(); it++) - { - bool founddouble = false; - bool foundentry = true; - - while (!founddouble && foundentry) - { - struct hostent *he; - uint8_t addr[4]; - uint32_t ip = (*it); - - addr[0] = (uint8_t) ((ip >> 24) & 0xFF); - addr[1] = (uint8_t) ((ip >> 16) & 0xFF); - addr[2] = (uint8_t) ((ip >> 8) & 0xFF); - addr[3] = (uint8_t) (ip & 0xFF); - he = gethostbyaddr((char *) addr, 4, AF_INET); - if (he != 0) - { - std::string hname = std::string(he->h_name); - std::list::const_iterator it; - - for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) - hostnames.push_back(hname); - - int i = 0; - while (!founddouble && he->h_aliases[i] != 0) - { - std::string hname = std::string(he->h_aliases[i]); - - for (it = hostnames.begin(); !founddouble && it != hostnames.end(); it++) - if ((*it) == hname) - founddouble = true; - - if (!founddouble) - { - hostnames.push_back(hname); - i++; - } - } - } - else - foundentry = false; - } - } - - bool found = false; - - if (!hostnames.empty()) // try to select the most appropriate hostname - { - std::list::const_iterator it; - - hostnames.sort(); - for (it = hostnames.begin(); !found && it != hostnames.end(); it++) - { - if ((*it).find('.') != std::string::npos) - { - found = true; - localhostnamelength = (*it).length(); - localhostname = new uint8_t[localhostnamelength + 1]; - if (localhostname == 0) - { - - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname, (*it).c_str(), localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - } - - if (!found) // use an IP address - { - uint32_t ip; - int len; - char str[16]; - - it = localIPs.begin(); - ip = (*it); - - RTP_SNPRINTF(str, 16, "%d.%d.%d.%d", (int) ((ip >> 24) & 0xFF), (int) ((ip >> 16) & 0xFF), (int) ((ip >> 8) & 0xFF), (int) (ip & 0xFF)); - len = strlen(str); - - localhostnamelength = len; - localhostname = new uint8_t[localhostnamelength + 1]; - if (localhostname == 0) - { - - return ERR_RTP_OUTOFMEM; - } - memcpy(localhostname, str, localhostnamelength); - localhostname[localhostnamelength] = 0; - } - } - - if ((*bufferlength) < localhostnamelength) - { - *bufferlength = localhostnamelength; // tell the application the required size of the buffer - - return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL; - } - - memcpy(buffer, localhostname, localhostnamelength); - *bufferlength = localhostnamelength; - - return 0; -} - -bool RTPUDPv4TransmitterNoBind::ComesFromThisTransmitter(const RTPAddress *addr) -{ - if (!init) - return false; - - if (addr == 0) - return false; - - bool v; - - if (created && addr->GetAddressType() == RTPAddress::IPv4Address) - { - const RTPIPv4Address *addr2 = (const RTPIPv4Address *) addr; - bool found = false; - std::list::const_iterator it; - - it = localIPs.begin(); - while (!found && it != localIPs.end()) - { - if (addr2->GetIP() == *it) - found = true; - else - ++it; - } - - if (!found) - v = false; - else - { - if (addr2->GetPort() == m_rtpPort || addr2->GetPort() == m_rtcpPort) // check for RTP port and RTCP port - v = true; - else - v = false; - } - } - else - v = false; - - return v; -} - -int RTPUDPv4TransmitterNoBind::Poll() -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - status = PollSocket(true); // poll RTP socket - if (rtpsock != rtcpsock) // no need to poll twice when multiplexing - { - if (status >= 0) - status = PollSocket(false); // poll RTCP socket - } - - return status; -} - -int RTPUDPv4TransmitterNoBind::WaitForIncomingData(const RTPTime &delay, bool *dataavailable) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (waitingfordata) - { - - return ERR_RTP_UDPV4TRANS_ALREADYWAITING; - } - - SocketType abortSocket = m_pAbortDesc->GetAbortSocket(); - - SocketType socks[3] = - { rtpsock, rtcpsock, abortSocket }; - int8_t readflags[3] = - { 0, 0, 0 }; - const int idxRTP = 0; - const int idxRTCP = 1; - const int idxAbort = 2; - - waitingfordata = true; - - int status = RTPSelect(socks, readflags, 3, delay); - if (status < 0) - { - waitingfordata = false; - - return status; - } - - waitingfordata = false; - if (!created) // destroy called - { - ; - - return 0; - } - - // if aborted, read from abort buffer - if (readflags[idxAbort]) - m_pAbortDesc->ReadSignallingByte(); - - if (dataavailable != 0) - { - if (readflags[idxRTP] || readflags[idxRTCP]) - *dataavailable = true; - else - *dataavailable = false; - } - - return 0; -} - -int RTPUDPv4TransmitterNoBind::AbortWait() -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (!waitingfordata) - { - - return ERR_RTP_UDPV4TRANS_NOTWAITING; - } - - m_pAbortDesc->SendAbortSignal(); - - return 0; -} - -int RTPUDPv4TransmitterNoBind::SendRTPData(const void *data, std::size_t len) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTPSockAddr(), sizeof(struct sockaddr_in)); - destinations.GotoNextElement(); - } - - return 0; -} - -int RTPUDPv4TransmitterNoBind::SendRTCPData(const void *data, std::size_t len) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (len > maxpacksize) - { - - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - - destinations.GotoFirstElement(); - while (destinations.HasCurrentElement()) - { - sendto(rtcpsock, (const char *) data, len, 0, (const struct sockaddr *) destinations.GetCurrentElement().GetRTCPSockAddr(), sizeof(struct sockaddr_in)); - destinations.GotoNextElement(); - } - - return 0; -} - -int RTPUDPv4TransmitterNoBind::AddDestination(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - - int status = destinations.AddElement(dest); - - return status; -} - -int RTPUDPv4TransmitterNoBind::DeleteDestination(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - RTPIPv4Destination dest; - if (!RTPIPv4Destination::AddressToDestination(addr, dest)) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - - int status = destinations.DeleteElement(dest); - - return status; -} - -void RTPUDPv4TransmitterNoBind::ClearDestinations() -{ - if (!init) - return; - - if (created) - destinations.Clear(); - -} - -bool RTPUDPv4TransmitterNoBind::SupportsMulticasting() -{ - if (!init) - return false; - - bool v; - - if (!created) - v = false; - else - v = supportsmulticasting; - - return v; -} - -#ifdef RTP_SUPPORT_IPV4MULTICAST - -int RTPUDPv4TransmitterNoBind::JoinMulticastGroup(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - uint32_t mcastIP = address.GetIP(); - - if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) - { - - return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; - } - - status = multicastgroups.AddElement(mcastIP); - if (status >= 0) - { - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_ADD_MEMBERSHIP, mcastIP, status); - if (status != 0) - { - multicastgroups.DeleteElement(mcastIP); - - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; - } - - if (rtpsock != rtcpsock) // no need to join multicast group twice when multiplexing - { - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_ADD_MEMBERSHIP, mcastIP, status); - if (status != 0) - { - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - multicastgroups.DeleteElement(mcastIP); - - return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; - } - } - } - - return status; -} - -int RTPUDPv4TransmitterNoBind::LeaveMulticastGroup(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - uint32_t mcastIP = address.GetIP(); - - if (!RTPUDPV4TRANSNOBIND_IS_MCASTADDR(mcastIP)) - { - - return ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS; - } - - status = multicastgroups.DeleteElement(mcastIP); - if (status >= 0) - { - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - - status = 0; - } - - return status; -} - -void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() -{ - if (!init) - return; - - if (created) - { - multicastgroups.GotoFirstElement(); - while (multicastgroups.HasCurrentElement()) - { - uint32_t mcastIP; - int status __attribute__((unused)) = 0; - - mcastIP = multicastgroups.GetCurrentElement(); - - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - if (rtpsock != rtcpsock) // no need to leave multicast group twice when multiplexing - RTPUDPV4TRANSNOBIND_MCASTMEMBERSHIP(rtcpsock, IP_DROP_MEMBERSHIP, mcastIP, status); - - multicastgroups.GotoNextElement(); - } - multicastgroups.Clear(); - } - -} - -#else // no multicast support - -int RTPUDPv4TransmitterNoBind::JoinMulticastGroup(const RTPAddress &addr) -{ - return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; -} - -int RTPUDPv4Transmitter::LeaveMulticastGroup(const RTPAddress &addr) -{ - return ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT; -} - -void RTPUDPv4TransmitterNoBind::LeaveAllMulticastGroups() -{ -} - -#endif // RTP_SUPPORT_IPV4MULTICAST - -int RTPUDPv4TransmitterNoBind::SetReceiveMode(RTPTransmitter::ReceiveMode m) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (m != receivemode) - { - receivemode = m; - acceptignoreinfo.Clear(); - } - - return 0; -} - -int RTPUDPv4TransmitterNoBind::AddToIgnoreList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - - return status; -} - -int RTPUDPv4TransmitterNoBind::DeleteFromIgnoreList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::IgnoreSome) - { - - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - - return status; -} - -void RTPUDPv4TransmitterNoBind::ClearIgnoreList() -{ - if (!init) - return; - - if (created && receivemode == RTPTransmitter::IgnoreSome) - ClearAcceptIgnoreInfo(); - -} - -int RTPUDPv4TransmitterNoBind::AddToAcceptList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - status = ProcessAddAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - - return status; -} - -int RTPUDPv4TransmitterNoBind::DeleteFromAcceptList(const RTPAddress &addr) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - int status; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (addr.GetAddressType() != RTPAddress::IPv4Address) - { - - return ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE; - } - if (receivemode != RTPTransmitter::AcceptSome) - { - - return ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE; - } - - const RTPIPv4Address &address = (const RTPIPv4Address &) addr; - status = ProcessDeleteAcceptIgnoreEntry(address.GetIP(), address.GetPort()); - - return status; -} - -void RTPUDPv4TransmitterNoBind::ClearAcceptList() -{ - if (!init) - return; - - if (created && receivemode == RTPTransmitter::AcceptSome) - ClearAcceptIgnoreInfo(); - -} - -int RTPUDPv4TransmitterNoBind::SetMaximumPacketSize(std::size_t s) -{ - if (!init) - return ERR_RTP_UDPV4TRANS_NOTINIT; - - if (!created) - { - - return ERR_RTP_UDPV4TRANS_NOTCREATED; - } - if (s > RTPUDPV4TRANSNOBIND_MAXPACKSIZE) - { - - return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; - } - maxpacksize = s; - - return 0; -} - -bool RTPUDPv4TransmitterNoBind::NewDataAvailable() -{ - if (!init) - return false; - - bool v; - - if (!created) - v = false; - else - { - if (rawpacketlist.empty()) - v = false; - else - v = true; - } - - return v; -} - -RTPRawPacket *RTPUDPv4TransmitterNoBind::GetNextPacket() -{ - if (!init) - return 0; - - RTPRawPacket *p; - - if (!created) - { - - return 0; - } - if (rawpacketlist.empty()) - { - - return 0; - } - - p = *(rawpacketlist.begin()); - rawpacketlist.pop_front(); - - return p; -} - -// Here the private functions start... - -#ifdef RTP_SUPPORT_IPV4MULTICAST -bool RTPUDPv4TransmitterNoBind::SetMulticastTTL(uint8_t ttl) -{ - int ttl2, status; - - ttl2 = (int) ttl; - status = setsockopt(rtpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); - if (status != 0) - return false; - - if (rtpsock != rtcpsock) // no need to set TTL twice when multiplexing - { - status = setsockopt(rtcpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl2, sizeof(int)); - if (status != 0) - return false; - } - return true; -} -#endif // RTP_SUPPORT_IPV4MULTICAST - -void RTPUDPv4TransmitterNoBind::FlushPackets() -{ - std::list::const_iterator it; - - for (it = rawpacketlist.begin(); it != rawpacketlist.end(); ++it) - delete *it; - rawpacketlist.clear(); -} - -int RTPUDPv4TransmitterNoBind::PollSocket(bool rtp) -{ - RTPSOCKLENTYPE fromlen; - int recvlen; - char packetbuffer[RTPUDPV4TRANSNOBIND_MAXPACKSIZE]; -#ifdef RTP_SOCKETTYPE_WINSOCK - SOCKET sock; - unsigned long len; -#else - std::size_t len; - int sock; -#endif // RTP_SOCKETTYPE_WINSOCK - struct sockaddr_in srcaddr; - bool dataavailable; - - if (rtp) - sock = rtpsock; - else - sock = rtcpsock; - - do - { - len = 0; - RTPIOCTL(sock, FIONREAD, &len); - - if (len <= 0) // make sure a packet of length zero is not queued - { - // An alternative workaround would be to just use non-blocking sockets. - // However, since the user does have access to the sockets and I do not - // know how this would affect anyone else's code, I chose to do it using - // an extra select call in case ioctl says the length is zero. - - int8_t isset = 0; - int status = RTPSelect(&sock, &isset, 1, RTPTime(0)); - if (status < 0) - return status; - - if (isset) - dataavailable = true; - else - dataavailable = false; - } - else - dataavailable = true; - - if (dataavailable) - { - RTPTime curtime = RTPTime::CurrentTime(); - fromlen = sizeof(struct sockaddr_in); - recvlen = recvfrom(sock, packetbuffer, RTPUDPV4TRANSNOBIND_MAXPACKSIZE, 0, (struct sockaddr *) &srcaddr, &fromlen); - if (recvlen > 0) - { - bool acceptdata; - - // got data, process it - if (receivemode == RTPTransmitter::AcceptAll) - acceptdata = true; - else - acceptdata = ShouldAcceptData(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); - - if (acceptdata) - { - RTPRawPacket *pack; - RTPIPv4Address *addr; - uint8_t *datacopy; - - addr = new RTPIPv4Address(ntohl(srcaddr.sin_addr.s_addr), ntohs(srcaddr.sin_port)); - if (addr == 0) - return ERR_RTP_OUTOFMEM; - datacopy = new uint8_t[recvlen]; - if (datacopy == 0) - { - delete addr; - return ERR_RTP_OUTOFMEM; - } - memcpy(datacopy, packetbuffer, recvlen); - - bool isrtp = rtp; - if (rtpsock == rtcpsock) // check payload type when multiplexing - { - isrtp = true; - - if ((std::size_t) recvlen > sizeof(RTCPCommonHeader)) - { - RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) datacopy; - uint8_t packettype = rtcpheader->packettype; - - if (packettype >= 200 && packettype <= 204) - isrtp = false; - } - } - - pack = new RTPRawPacket(datacopy, recvlen, addr, curtime, isrtp); - if (pack == 0) - { - delete addr; - delete[] datacopy; - return ERR_RTP_OUTOFMEM; - } - rawpacketlist.push_back(pack); - } - } - } - } while (dataavailable); - - return 0; -} - -int RTPUDPv4TransmitterNoBind::ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port) -{ - acceptignoreinfo.GotoElement(ip); - if (acceptignoreinfo.HasCurrentElement()) // An entry for this IP address already exists - { - PortInfo *portinf = acceptignoreinfo.GetCurrentElement(); - - if (port == 0) // select all ports - { - portinf->all = true; - portinf->portlist.clear(); - } - else if (!portinf->all) - { - std::list::const_iterator it, begin, end; - - begin = portinf->portlist.begin(); - end = portinf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == port) // already in list - return 0; - } - portinf->portlist.push_front(port); - } - } - else // got to create an entry for this IP address - { - PortInfo *portinf; - int status; - - portinf = new PortInfo(); - if (port == 0) // select all ports - portinf->all = true; - else - portinf->portlist.push_front(port); - - status = acceptignoreinfo.AddElement(ip, portinf); - if (status < 0) - { - delete portinf; - return status; - } - } - - return 0; -} - -void RTPUDPv4TransmitterNoBind::ClearAcceptIgnoreInfo() -{ - acceptignoreinfo.GotoFirstElement(); - while (acceptignoreinfo.HasCurrentElement()) - { - PortInfo *inf; - - inf = acceptignoreinfo.GetCurrentElement(); - delete inf; - acceptignoreinfo.GotoNextElement(); - } - acceptignoreinfo.Clear(); -} - -int RTPUDPv4TransmitterNoBind::ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port) -{ - acceptignoreinfo.GotoElement(ip); - if (!acceptignoreinfo.HasCurrentElement()) - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - - PortInfo *inf; - - inf = acceptignoreinfo.GetCurrentElement(); - if (port == 0) // delete all entries - { - inf->all = false; - inf->portlist.clear(); - } - else // a specific port was selected - { - if (inf->all) // currently, all ports are selected. Add the one to remove to the list - { - // we have to check if the list doesn't contain the port already - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == port) // already in list: this means we already deleted the entry - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - } - inf->portlist.push_front(port); - } - else // check if we can find the port in the list - { - std::list::iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; ++it) - { - if (*it == port) // found it! - { - inf->portlist.erase(it); - return 0; - } - } - // didn't find it - return ERR_RTP_UDPV4TRANS_NOSUCHENTRY; - } - } - return 0; -} - -bool RTPUDPv4TransmitterNoBind::ShouldAcceptData(uint32_t srcip, uint16_t srcport) -{ - if (receivemode == RTPTransmitter::AcceptSome) - { - PortInfo *inf; - - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return false; - - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // only accept the ones in the list - { - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == srcport) - return true; - } - return false; - } - else // accept all, except the ones in the list - { - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == srcport) - return false; - } - return true; - } - } - else // IgnoreSome - { - PortInfo *inf; - - acceptignoreinfo.GotoElement(srcip); - if (!acceptignoreinfo.HasCurrentElement()) - return true; - - inf = acceptignoreinfo.GetCurrentElement(); - if (!inf->all) // ignore the ports in the list - { - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == srcport) - return false; - } - return true; - } - else // ignore all, except the ones in the list - { - std::list::const_iterator it, begin, end; - - begin = inf->portlist.begin(); - end = inf->portlist.end(); - for (it = begin; it != end; it++) - { - if (*it == srcport) - return true; - } - return false; - } - } - return true; -} - -int RTPUDPv4TransmitterNoBind::CreateLocalIPList() -{ - // first try to obtain the list from the network interface info - - if (!GetLocalIPList_Interfaces()) - { - // If this fails, we'll have to depend on DNS info - GetLocalIPList_DNS(); - } - AddLoopbackAddress(); - return 0; -} - -#ifdef RTP_SOCKETTYPE_WINSOCK - -bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() -{ - unsigned char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; - DWORD outputsize; - DWORD numaddresses,i; - SOCKET_ADDRESS_LIST *addrlist; - - if (WSAIoctl(rtpsock,SIO_ADDRESS_LIST_QUERY,NULL,0,&buffer,RTPUDPV4TRANSNOBIND_IFREQBUFSIZE,&outputsize,NULL,NULL)) - return false; - - addrlist = (SOCKET_ADDRESS_LIST *)buffer; - numaddresses = addrlist->iAddressCount; - for (i = 0; i < numaddresses; i++) - { - SOCKET_ADDRESS *sockaddr = &(addrlist->Address[i]); - if (sockaddr->iSockaddrLength == sizeof(struct sockaddr_in)) // IPv4 address - { - struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr->lpSockaddr; - - localIPs.push_back(ntohl(addr->sin_addr.s_addr)); - } - } - - if (localIPs.empty()) - return false; - - return true; -} - -#else // use either getifaddrs or ioctl - -#ifdef RTP_SUPPORT_IFADDRS - -bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() -{ - struct ifaddrs *addrs, *tmp; - - getifaddrs(&addrs); - tmp = addrs; - - while (tmp != 0) - { - if (tmp->ifa_addr != 0 && tmp->ifa_addr->sa_family == AF_INET) - { - struct sockaddr_in *inaddr = (struct sockaddr_in *) tmp->ifa_addr; - localIPs.push_back(ntohl(inaddr->sin_addr.s_addr)); - } - tmp = tmp->ifa_next; - } - - freeifaddrs(addrs); - - if (localIPs.empty()) - return false; - return true; -} - -#else // user ioctl - -bool RTPUDPv4TransmitterNoBind::GetLocalIPList_Interfaces() -{ - int status; - char buffer[RTPUDPV4TRANSNOBIND_IFREQBUFSIZE]; - struct ifconf ifc; - struct ifreq *ifr; - struct sockaddr *sa; - char *startptr,*endptr; - int remlen; - - ifc.ifc_len = RTPUDPV4TRANSNOBIND_IFREQBUFSIZE; - ifc.ifc_buf = buffer; - status = ioctl(rtpsock,SIOCGIFCONF,&ifc); - if (status < 0) - return false; - - startptr = (char *)ifc.ifc_req; - endptr = startptr + ifc.ifc_len; - remlen = ifc.ifc_len; - while((startptr < endptr) && remlen >= (int)sizeof(struct ifreq)) - { - ifr = (struct ifreq *)startptr; - sa = &(ifr->ifr_addr); -#ifdef RTP_HAVE_SOCKADDR_LEN - if (sa->sa_len <= sizeof(struct sockaddr)) - { - if (sa->sa_len == sizeof(struct sockaddr_in) && sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; - - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); - } - else - { - int l = sa->sa_len-sizeof(struct sockaddr)+sizeof(struct ifreq); - - remlen -= l; - startptr += l; - } -#else // don't have sa_len in struct sockaddr - if (sa->sa_family == PF_INET) - { - uint32_t ip; - struct sockaddr_in *addr = (struct sockaddr_in *)sa; - - ip = ntohl(addr->sin_addr.s_addr); - localIPs.push_back(ip); - } - remlen -= sizeof(struct ifreq); - startptr += sizeof(struct ifreq); - -#endif // RTP_HAVE_SOCKADDR_LEN - } - - if (localIPs.empty()) - return false; - return true; -} - -#endif // RTP_SUPPORT_IFADDRS - -#endif // RTP_SOCKETTYPE_WINSOCK - -void RTPUDPv4TransmitterNoBind::GetLocalIPList_DNS() -{ - struct hostent *he; - char name[1024]; - bool done; - int i, j; - - gethostname(name, 1023); - name[1023] = 0; - he = gethostbyname(name); - if (he == 0) - return; - - i = 0; - done = false; - while (!done) - { - if (he->h_addr_list[i] == NULL) - done = true; - else - { - uint32_t ip = 0; - - for (j = 0; j < 4; j++) - ip |= ((uint32_t) ((unsigned char) he->h_addr_list[i][j]) << ((3 - j) * 8)); - localIPs.push_back(ip); - i++; - } - } -} - -void RTPUDPv4TransmitterNoBind::AddLoopbackAddress() -{ - uint32_t loopbackaddr = (((uint32_t) 127) << 24) | ((uint32_t) 1); - std::list::const_iterator it; - bool found = false; - - for (it = localIPs.begin(); !found && it != localIPs.end(); it++) - { - if (*it == loopbackaddr) - found = true; - } - - if (!found) - localIPs.push_back(loopbackaddr); -} - -} // end namespace - diff --git a/qrtplib/rtpudpv4transmitternobind.h b/qrtplib/rtpudpv4transmitternobind.h deleted file mode 100644 index 5cef0a739..000000000 --- a/qrtplib/rtpudpv4transmitternobind.h +++ /dev/null @@ -1,492 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpudpv4transmitternobind.h - */ - -#ifndef RTPUDPV4TRANSMITTERNOBIND_H - -#define RTPUDPV4TRANSMITTERNOBIND_H - -#include "rtpconfig.h" -#include "rtptransmitter.h" -#include "rtpipv4destination.h" -#include "rtphashtable.h" -#include "rtpkeyhashtable.h" -#include "rtpsocketutil.h" -#include "rtpabortdescriptors.h" -#include - -#include "util/export.h" - -#define RTPUDPV4TRANSNOBIND_HASHSIZE 8317 -#define RTPUDPV4TRANSNOBIND_DEFAULTPORTBASE 5000 - -#define RTPUDPV4TRANSNOBIND_RTPRECEIVEBUFFER 32768 -#define RTPUDPV4TRANSNOBIND_RTCPRECEIVEBUFFER 32768 -#define RTPUDPV4TRANSNOBIND_RTPTRANSMITBUFFER 32768 -#define RTPUDPV4TRANSNOBIND_RTCPTRANSMITBUFFER 32768 - -namespace qrtplib -{ - -/** Parameters for the UDP over IPv4 transmitter that does not automatically bind sockets */ -class QRTPLIB_API RTPUDPv4TransmissionNoBindParams: public RTPTransmissionParams -{ -public: - RTPUDPv4TransmissionNoBindParams(); - - /** Sets the IP address which is used to bind the sockets to \c ip. */ - void SetBindIP(uint32_t ip) - { - bindIP = ip; - } - - /** Sets the multicast interface IP address. */ - void SetMulticastInterfaceIP(uint32_t ip) - { - mcastifaceIP = ip; - } - - /** Sets the RTP portbase to \c pbase, which has to be an even number - * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; - * a port number of zero will cause a port to be chosen automatically. */ - void SetPortbase(uint16_t pbase) - { - portbase = pbase; - } - - /** Sets the multicast TTL to be used to \c mcastTTL. */ - void SetMulticastTTL(uint8_t mcastTTL) - { - multicastTTL = mcastTTL; - } - - /** Passes a list of IP addresses which will be used as the local IP addresses. */ - void SetLocalIPList(std::list &iplist) - { - localIPs = iplist; - } - - /** Clears the list of local IP addresses. - * Clears the list of local IP addresses. An empty list will make the transmission - * component itself determine the local IP addresses. - */ - void ClearLocalIPList() - { - localIPs.clear(); - } - - /** Returns the IP address which will be used to bind the sockets. */ - uint32_t GetBindIP() const - { - return bindIP; - } - - /** Returns the multicast interface IP address. */ - uint32_t GetMulticastInterfaceIP() const - { - return mcastifaceIP; - } - - /** Returns the RTP portbase which will be used (default is 5000). */ - uint16_t GetPortbase() const - { - return portbase; - } - - /** Returns the multicast TTL which will be used (default is 1). */ - uint8_t GetMulticastTTL() const - { - return multicastTTL; - } - - /** Returns the list of local IP addresses. */ - const std::list &GetLocalIPList() const - { - return localIPs; - } - - /** Sets the RTP socket's send buffer size. */ - void SetRTPSendBuffer(int s) - { - rtpsendbuf = s; - } - - /** Sets the RTP socket's receive buffer size. */ - void SetRTPReceiveBuffer(int s) - { - rtprecvbuf = s; - } - - /** Sets the RTCP socket's send buffer size. */ - void SetRTCPSendBuffer(int s) - { - rtcpsendbuf = s; - } - - /** Sets the RTCP socket's receive buffer size. */ - void SetRTCPReceiveBuffer(int s) - { - rtcprecvbuf = s; - } - - /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ - void SetRTCPMultiplexing(bool f) - { - rtcpmux = f; - } - - /** Can be used to allow the RTP port base to be any number, not just even numbers. */ - void SetAllowOddPortbase(bool f) - { - allowoddportbase = f; - } - - /** Force the RTCP socket to use a specific port, not necessarily one more than - * the RTP port (set this to zero to disable). */ - void SetForcedRTCPPort(uint16_t rtcpport) - { - forcedrtcpport = rtcpport; - } - - /** Use sockets that have already been created, no checks on port numbers - * will be done, and no buffer sizes will be set; you'll need to close - * the sockets yourself when done, it will **not** be done automatically. */ - void SetUseExistingSockets(SocketType rtpsocket, SocketType rtcpsocket) - { - rtpsock = rtpsocket; - rtcpsock = rtcpsocket; - useexistingsockets = true; - } - - /** If non null, the specified abort descriptors will be used to cancel - * the function that's waiting for packets to arrive; set to null (the default - * to let the transmitter create its own instance. */ - void SetCreatedAbortDescriptors(RTPAbortDescriptors *desc) - { - m_pAbortDesc = desc; - } - - /** Returns the RTP socket's send buffer size. */ - int GetRTPSendBuffer() const - { - return rtpsendbuf; - } - - /** Returns the RTP socket's receive buffer size. */ - int GetRTPReceiveBuffer() const - { - return rtprecvbuf; - } - - /** Returns the RTCP socket's send buffer size. */ - int GetRTCPSendBuffer() const - { - return rtcpsendbuf; - } - - /** Returns the RTCP socket's receive buffer size. */ - int GetRTCPReceiveBuffer() const - { - return rtcprecvbuf; - } - - /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ - bool GetRTCPMultiplexing() const - { - return rtcpmux; - } - - /** If true, any RTP portbase will be allowed, not just even numbers. */ - bool GetAllowOddPortbase() const - { - return allowoddportbase; - } - - /** If non-zero, the specified port will be used to receive RTCP traffic. */ - uint16_t GetForcedRTCPPort() const - { - return forcedrtcpport; - } - - /** Returns true and fills in sockets if existing sockets were set - * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ - bool GetUseExistingSockets(SocketType &rtpsocket, SocketType &rtcpsocket) const - { - if (!useexistingsockets) - return false; - rtpsocket = rtpsock; - rtcpsocket = rtcpsock; - return true; - } - - /** If non-null, this RTPAbortDescriptors instance will be used internally, - * which can be useful when creating your own poll thread for multiple - * sessions. */ - RTPAbortDescriptors *GetCreatedAbortDescriptors() const - { - return m_pAbortDesc; - } -private: - uint16_t portbase; - uint32_t bindIP, mcastifaceIP; - std::list localIPs; - uint8_t multicastTTL; - int rtpsendbuf, rtprecvbuf; - int rtcpsendbuf, rtcprecvbuf; - bool rtcpmux; - bool allowoddportbase; - uint16_t forcedrtcpport; - - SocketType rtpsock, rtcpsock; - bool useexistingsockets; - - RTPAbortDescriptors *m_pAbortDesc; -}; - -inline RTPUDPv4TransmissionNoBindParams::RTPUDPv4TransmissionNoBindParams() : - RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) -{ - portbase = RTPUDPV4TRANSNOBIND_DEFAULTPORTBASE; - bindIP = 0; - multicastTTL = 1; - mcastifaceIP = 0; - rtpsendbuf = RTPUDPV4TRANSNOBIND_RTPTRANSMITBUFFER; - rtprecvbuf = RTPUDPV4TRANSNOBIND_RTPRECEIVEBUFFER; - rtcpsendbuf = RTPUDPV4TRANSNOBIND_RTCPTRANSMITBUFFER; - rtcprecvbuf = RTPUDPV4TRANSNOBIND_RTCPRECEIVEBUFFER; - rtcpmux = false; - allowoddportbase = false; - forcedrtcpport = 0; - useexistingsockets = false; - rtpsock = 0; - rtcpsock = 0; - m_pAbortDesc = 0; -} - -/** Additional information about the UDP over IPv4 transmitter that does not automatically bind sockets. */ -class QRTPLIB_API RTPUDPv4TransmissionNoBindInfo: public RTPTransmissionInfo -{ -public: - RTPUDPv4TransmissionNoBindInfo(const QHostAddress& ip, SocketType rtpsock, SocketType rtcpsock, uint16_t rtpport, uint16_t rtcpport) : - RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) - { - localIP = ip; - rtpsocket = rtpsock; - rtcpsocket = rtcpsock; - m_rtpPort = rtpport; - m_rtcpPort = rtcpport; - } - - ~RTPUDPv4TransmissionNoBindInfo() - { - } - - /** Returns the list of IPv4 addresses the transmitter considers to be the local IP addresses. */ - QHostAddress GetLocalIP() const - { - return localIP; - } - - /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ - SocketType GetRTPSocket() const - { - return rtpsocket; - } - - /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ - SocketType GetRTCPSocket() const - { - return rtcpsocket; - } - - /** Returns the port number that the RTP socket receives packets on. */ - uint16_t GetRTPPort() const - { - return m_rtpPort; - } - - /** Returns the port number that the RTCP socket receives packets on. */ - uint16_t GetRTCPPort() const - { - return m_rtcpPort; - } -private: - QHostAddress localIP; - SocketType rtpsocket, rtcpsocket; - uint16_t m_rtpPort, m_rtcpPort; -}; - -class RTPUDPv4TransNoBind_GetHashIndex_IPv4Dest -{ -public: - static int GetIndex(const RTPIPv4Destination &d) - { - return d.GetIP() % RTPUDPV4TRANSNOBIND_HASHSIZE; - } -}; - -class RTPUDPv4TransNoBind_GetHashIndex_uint32_t -{ -public: - static int GetIndex(const uint32_t &k) - { - return k % RTPUDPV4TRANSNOBIND_HASHSIZE; - } -}; - -#define RTPUDPV4TRANSNOBIND_HEADERSIZE (20+8) - -/** An UDP over IPv4 transmission component. - * This class inherits the RTPTransmitter interface and implements a transmission component - * which uses UDP over IPv4 to send and receive RTP and RTCP data. The component's parameters - * are described by the class RTPUDPv4TransmissionNoBindParams. The functions which have an RTPAddress - * argument require an argument of RTPIPv4Address. The GetTransmissionInfo member function - * returns an instance of type RTPUDPv4TransmissionNoBindInfo. - * This flavor of a RTPUDPv4Transmitter class does not automatically bind sockets. Use the - * BindSockets method to do so. - */ -class QRTPLIB_API RTPUDPv4TransmitterNoBind: public RTPTransmitter -{ -public: - RTPUDPv4TransmitterNoBind(); - ~RTPUDPv4TransmitterNoBind(); - - int Init(bool treadsafe); - int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); - /** Bind the RTP and RTCP sockets to ports defined in the transmission parameters */ - int BindSockets(const RTPTransmissionParams *transparams); - void Destroy(); - RTPTransmissionInfo *GetTransmissionInfo(); - void DeleteTransmissionInfo(RTPTransmissionInfo *inf); - - int GetLocalHostName(uint8_t *buffer, std::size_t *bufferlength); - bool ComesFromThisTransmitter(const RTPAddress *addr); - std::size_t GetHeaderOverhead() - { - return RTPUDPV4TRANSNOBIND_HEADERSIZE; - } - - int Poll(); - int WaitForIncomingData(const RTPTime &delay, bool *dataavailable = 0); - int AbortWait(); - - int SendRTPData(const void *data, std::size_t len); - int SendRTCPData(const void *data, std::size_t len); - - int AddDestination(const RTPAddress &addr); - int DeleteDestination(const RTPAddress &addr); - void ClearDestinations(); - - bool SupportsMulticasting(); - int JoinMulticastGroup(const RTPAddress &addr); - int LeaveMulticastGroup(const RTPAddress &addr); - void LeaveAllMulticastGroups(); - - int SetReceiveMode(RTPTransmitter::ReceiveMode m); - int AddToIgnoreList(const RTPAddress &addr); - int DeleteFromIgnoreList(const RTPAddress &addr); - void ClearIgnoreList(); - int AddToAcceptList(const RTPAddress &addr); - int DeleteFromAcceptList(const RTPAddress &addr); - void ClearAcceptList(); - int SetMaximumPacketSize(std::size_t s); - - bool NewDataAvailable(); - RTPRawPacket *GetNextPacket(); - -private: - int CreateLocalIPList(); - bool GetLocalIPList_Interfaces(); - void GetLocalIPList_DNS(); - void AddLoopbackAddress(); - void FlushPackets(); - int PollSocket(bool rtp); - int ProcessAddAcceptIgnoreEntry(uint32_t ip, uint16_t port); - int ProcessDeleteAcceptIgnoreEntry(uint32_t ip, uint16_t port); -#ifdef RTP_SUPPORT_IPV4MULTICAST - bool SetMulticastTTL(uint8_t ttl); -#endif // RTP_SUPPORT_IPV4MULTICAST - bool ShouldAcceptData(uint32_t srcip, uint16_t srcport); - void ClearAcceptIgnoreInfo(); - - int GetAutoSockets(uint32_t bindIP, bool allowOdd, bool rtcpMux, SocketType *pRtpSock, SocketType *pRtcpSock, uint16_t *pRtpPort, uint16_t *pRtcpPort); - static int GetIPv4SocketPort(SocketType s, uint16_t *pPort); - - bool init; - bool created; - bool waitingfordata; - SocketType rtpsock, rtcpsock; - uint32_t mcastifaceIP; - std::list localIPs; - uint16_t m_rtpPort, m_rtcpPort; - uint8_t multicastTTL; - RTPTransmitter::ReceiveMode receivemode; - - uint8_t *localhostname; - std::size_t localhostnamelength; - - RTPHashTable destinations; -#ifdef RTP_SUPPORT_IPV4MULTICAST - RTPHashTable multicastgroups; -#endif // RTP_SUPPORT_IPV4MULTICAST - std::list rawpacketlist; - - bool supportsmulticasting; - std::size_t maxpacksize; - - class PortInfo - { - public: - PortInfo() - { - all = false; - } - - bool all; - std::list portlist; - }; - - RTPKeyHashTable acceptignoreinfo; - - bool closesocketswhendone; - RTPAbortDescriptors m_abortDesc; - RTPAbortDescriptors *m_pAbortDesc; // in case an external one was specified - -}; - -} // end namespace - -#endif // RTPUDPV4TRANSMITTERNOBIND_H - From 9f9eaa7a882934d24eaca930ce4e9304bb2e891b Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Mar 2018 22:09:00 +0100 Subject: [PATCH 058/956] qrtplib: RTP address constructor with host address and port --- qrtplib/rtpaddress.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h index e5832f6ce..21b2caf3f 100644 --- a/qrtplib/rtpaddress.h +++ b/qrtplib/rtpaddress.h @@ -52,6 +52,17 @@ namespace qrtplib class QRTPLIB_API RTPAddress { public: + /** Default constructor. Address and port set via setters */ + RTPAddress() : port(0), rtcpsendport(0) + {} + + /** Constructor with address and port */ + RTPAddress(const QHostAddress& address, uint16_t port) : + address(address), + port(port), + rtcpsendport(0) + {} + /** Returns the type of address the actual implementation represents. */ QAbstractSocket::NetworkLayerProtocol GetAddressType() const { From 9dacbb6d83af4a95891fad3e69272b96666ee4f7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 6 Mar 2018 02:23:47 +0100 Subject: [PATCH 059/956] qrtplib: NFM demod implementation. Also solve issue Cannot create children for a parent that is in a different thread on UDP sink used for copy audio to UDP --- plugins/channelrx/demodnfm/nfmdemod.cpp | 10 +++- qrtplib/rtpudptransmitter.cpp | 55 +++++++---------- qrtplib/rtpudptransmitter.h | 27 --------- sdrbase/CMakeLists.txt | 21 ++----- sdrbase/audio/audionetsink.cpp | 44 ++++---------- sdrbase/audio/audionetsink.h | 3 + sdrbase/dsp/basebandsamplesink.cpp | 5 +- sdrbase/dsp/basebandsamplesink.h | 22 +++++++ sdrbase/dsp/downchannelizer.cpp | 4 ++ sdrbase/dsp/threadedbasebandsamplesink.cpp | 4 ++ sdrbase/dsp/threadedbasebandsamplesink.h | 1 + sdrbase/util/rtpsink.cpp | 68 +++++++++------------- sdrbase/util/rtpsink.h | 49 +++------------- sdrbase/util/udpsink.h | 5 ++ 14 files changed, 123 insertions(+), 195 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index f3fb247a4..7509cabdf 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -77,7 +77,7 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_afSquelch.setCoefficients(24, 600, 48000.0, 200, 0); // 0.5ms test period, 300ms average span, 48kS/s SR, 100ms attack, no decay DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_audioNetSink = new AudioNetSink(this); + m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); m_channelizer = new DownChannelizer(this); @@ -374,6 +374,14 @@ bool NFMDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("NFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + m_audioNetSink->moveToThread(const_cast(thread)); // use the thread for udp sinks + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; diff --git a/qrtplib/rtpudptransmitter.cpp b/qrtplib/rtpudptransmitter.cpp index e7650afc6..f4fef5a76 100644 --- a/qrtplib/rtpudptransmitter.cpp +++ b/qrtplib/rtpudptransmitter.cpp @@ -49,7 +49,6 @@ RTPUDPTransmitter::RTPUDPTransmitter() : m_rtcpsock = 0; m_rtpsock = 0; m_waitingfordata = false; - m_closesocketswhendone = false; m_rtcpPort = 0; m_rtpPort = 0; m_receivemode = RTPTransmitter::AcceptAll; @@ -123,35 +122,26 @@ int RTPUDPTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissi } } - if (params->GetUseExistingSockets(&m_rtpsock, &m_rtcpsock)) + m_rtpsock = new QUdpSocket(); + + // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket + if (params->GetRTCPMultiplexing()) { - m_closesocketswhendone = false; + m_rtcpsock = m_rtpsock; + m_rtcpPort = m_rtpPort; + } else { + m_rtcpsock = new QUdpSocket(); } - else + + // set socket buffer sizes + + size = params->GetRTPReceiveBufferSize(); + m_rtpsock->setReadBufferSize(size); + + if (m_rtpsock != m_rtcpsock) { - m_rtpsock = new QUdpSocket(); - - // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket - if (params->GetRTCPMultiplexing()) - { - m_rtcpsock = m_rtpsock; - m_rtcpPort = m_rtpPort; - } else { - m_rtcpsock = new QUdpSocket(); - } - - m_closesocketswhendone = true; - - // set socket buffer sizes - - size = params->GetRTPReceiveBufferSize(); - m_rtpsock->setReadBufferSize(size); - - if (m_rtpsock != m_rtcpsock) - { - size = params->GetRTCPReceiveBufferSize(); - m_rtcpsock->setReadBufferSize(size); - } + size = params->GetRTCPReceiveBufferSize(); + m_rtcpsock->setReadBufferSize(size); } m_maxpacksize = maximumpacketsize; @@ -195,15 +185,12 @@ void RTPUDPTransmitter::Destroy() return; } - if (m_closesocketswhendone) - { - if (m_rtpsock != m_rtcpsock) { - delete m_rtcpsock; - } - - delete m_rtpsock; + if (m_rtpsock != m_rtcpsock) { + delete m_rtcpsock; } + delete m_rtpsock; + m_created = false; } diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h index 48fbbf8b3..7b1719d61 100644 --- a/qrtplib/rtpudptransmitter.h +++ b/qrtplib/rtpudptransmitter.h @@ -142,16 +142,6 @@ public: m_forcedrtcpport = rtcpport; } - /** Use sockets that have already been created, no checks on port numbers - * will be done, and no buffer sizes will be set; you'll need to close - * the sockets yourself when done, it will **not** be done automatically. */ - void SetUseExistingSockets(QUdpSocket *rtpsocket, QUdpSocket *rtcpsocket) - { - m_rtpsock = rtpsocket; - m_rtcpsock = rtcpsocket; - m_useexistingsockets = true; - } - /** Returns the RTP socket's send buffer size. */ int GetRTPSendBufferSize() const { @@ -194,19 +184,6 @@ public: return m_forcedrtcpport; } - /** Returns true and fills in sockets if existing sockets were set - * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ - bool GetUseExistingSockets(QUdpSocket **rtpsocket, QUdpSocket **rtcpsocket) const - { - if (!m_useexistingsockets) { - return false; - } - - *rtpsocket = m_rtpsock; - *rtcpsocket = m_rtcpsock; - return true; - } - private: QHostAddress m_bindAddress; QNetworkInterface m_mcastInterface; @@ -218,7 +195,6 @@ private: uint16_t m_forcedrtcpport; QUdpSocket *m_rtpsock, *m_rtcpsock; - bool m_useexistingsockets; }; inline RTPUDPTransmissionParams::RTPUDPTransmissionParams() : @@ -232,7 +208,6 @@ inline RTPUDPTransmissionParams::RTPUDPTransmissionParams() : m_rtcpmux = false; m_allowoddportbase = false; m_forcedrtcpport = 0; - m_useexistingsockets = false; m_rtpsock = 0; m_rtcpsock = 0; } @@ -360,8 +335,6 @@ private: QQueue m_rawPacketQueue; QMutex m_rawPacketQueueLock; - bool m_closesocketswhendone; - bool ShouldAcceptData(const RTPAddress& address); private slots: diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 185fc1f0e..ff2597f76 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -65,6 +65,7 @@ set(sdrbase_SOURCES util/message.cpp util/messagequeue.cpp util/prettyprint.cpp + util/rtpsink.cpp util/syncmessenger.cpp util/samplesourceserializer.cpp util/simpleserializer.cpp @@ -173,6 +174,7 @@ set(sdrbase_HEADERS util/messagequeue.h util/movingaverage.h util/prettyprint.h + util/rtpsink.h util/syncmessenger.h util/samplesourceserializer.h util/simpleserializer.h @@ -215,19 +217,6 @@ else(FFTW3F_FOUND) add_definitions(-DUSE_KISSFFT) endif(FFTW3F_FOUND) -if (JRTPLIB_FOUND) - set(sdrbase_HEADERS - ${sdrbase_HEADERS} - util/rtpsink.h - ) - set(sdrbase_SOURCES - ${sdrbase_SOURCES} - util/rtpsink.cpp - ) - add_definitions(-DHAS_JRTPLIB) - include_directories(${JRTPLIB_INCLUDE_DIR}) -endif(JRTPLIB_FOUND) - if (LIBSERIALDV_FOUND) set(sdrbase_SOURCES ${sdrbase_SOURCES} @@ -270,12 +259,14 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} . ${CMAKE_SOURCE_DIR}/httpserver + ${CMAKE_SOURCE_DIR}/qrtplib ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) target_link_libraries(sdrbase ${QT_LIBRARIES} httpserver + qrtplib swagger ) @@ -283,10 +274,6 @@ if(FFTW3F_FOUND) target_link_libraries(sdrbase ${FFTW3F_LIBRARIES}) endif(FFTW3F_FOUND) -if (JRTPLIB_FOUND) - target_link_libraries(sdrbase ${JRTPLIB_LIBRARIES}) -endif(JRTPLIB_FOUND) - if(LIBSERIALDV_FOUND) target_link_libraries(sdrbase ${LIBSERIALDV_LIBRARY}) endif(LIBSERIALDV_FOUND) diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index 50e4a2437..6444410e7 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -17,9 +17,7 @@ #include "audionetsink.h" #include "util/udpsink.h" -#ifdef HAS_JRTPLIB #include "util/rtpsink.h" -#endif const int AudioNetSink::m_udpBlockSize = 512; @@ -35,9 +33,7 @@ AudioNetSink::AudioNetSink(QObject *parent, bool stereo) : m_udpBufferAudioMono = new UDPSink(parent, m_udpBlockSize); } -#ifdef HAS_JRTPLIB m_rtpBufferAudio = new RTPSink("127.0.0.1", 9999, stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono); -#endif } AudioNetSink::~AudioNetSink() @@ -49,20 +45,14 @@ AudioNetSink::~AudioNetSink() if (m_udpBufferAudioStereo) { delete m_udpBufferAudioStereo; } -#ifdef HAS_JRTPLIB if (m_rtpBufferAudio) { delete m_rtpBufferAudio; } -#endif } bool AudioNetSink::isRTPCapable() const { -#ifdef HAS_JRTPLIB return m_rtpBufferAudio->isValid(); -#else - return false; -#endif } bool AudioNetSink::selectType(SinkType type) @@ -74,13 +64,8 @@ bool AudioNetSink::selectType(SinkType type) } else if (type == SinkRTP) { -#ifdef HAS_JRTPLIB m_type = SinkRTP; return true; -#else - m_type = SinkUDP; - return false; -#endif } else { @@ -96,39 +81,24 @@ void AudioNetSink::setDestination(const QString& address, uint16_t port) if (m_udpBufferAudioStereo) { m_udpBufferAudioStereo->setDestination(address, port); } - -#ifdef HAS_JRTPLIB if (m_rtpBufferAudio) { m_rtpBufferAudio->setDestination(address, port); } -#endif } -#ifdef HAS_JRTPLIB void AudioNetSink::addDestination(const QString& address, uint16_t port) { if (m_rtpBufferAudio) { m_rtpBufferAudio->addDestination(address, port); } } -#else -void AudioNetSink::addDestination(const QString& address __attribute__((unused)), uint16_t port __attribute__((unused))) -{ -} -#endif -#ifdef HAS_JRTPLIB void AudioNetSink::deleteDestination(const QString& address, uint16_t port) { if (m_rtpBufferAudio) { m_rtpBufferAudio->deleteDestination(address, port); } } -#else -void AudioNetSink::deleteDestination(const QString& address __attribute__((unused)), uint16_t port __attribute__((unused))) -{ -} -#endif void AudioNetSink::write(qint16 sample) { @@ -139,9 +109,7 @@ void AudioNetSink::write(qint16 sample) if (m_type == SinkUDP) { m_udpBufferAudioMono->write(sample); } else if (m_type == SinkRTP) { -#ifdef HAS_JRTPLIB m_rtpBufferAudio->write((uint8_t *) &sample); -#endif } } @@ -154,10 +122,18 @@ void AudioNetSink::write(const AudioSample& sample) if (m_type == SinkUDP) { m_udpBufferAudioStereo->write(sample); } else if (m_type == SinkRTP) { -#ifdef HAS_JRTPLIB m_rtpBufferAudio->write((uint8_t *) &sample); -#endif } } +void AudioNetSink::moveToThread(QThread *thread) +{ + if (m_udpBufferAudioMono) { + m_udpBufferAudioMono->moveToThread(thread); + } + + if (m_udpBufferAudioStereo) { + m_udpBufferAudioMono->moveToThread(thread); + } +} diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index 750aaf404..a1781de8c 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -24,6 +24,7 @@ template class UDPSink; class RTPSink; +class QThread; class SDRBASE_API AudioNetSink { public: @@ -46,6 +47,8 @@ public: bool isRTPCapable() const; bool selectType(SinkType type); + void moveToThread(QThread *thread); + static const int m_udpBlockSize; protected: diff --git a/sdrbase/dsp/basebandsamplesink.cpp b/sdrbase/dsp/basebandsamplesink.cpp index 7887f768d..81c9f53a9 100644 --- a/sdrbase/dsp/basebandsamplesink.cpp +++ b/sdrbase/dsp/basebandsamplesink.cpp @@ -1,5 +1,6 @@ -#include -#include "util/message.h" +#include "basebandsamplesink.h" + +MESSAGE_CLASS_DEFINITION(BasebandSampleSink::MsgThreadedSink, Message) BasebandSampleSink::BasebandSampleSink() : m_guiMessageQueue(0) diff --git a/sdrbase/dsp/basebandsamplesink.h b/sdrbase/dsp/basebandsamplesink.h index 462d0ec04..f70e93230 100644 --- a/sdrbase/dsp/basebandsamplesink.h +++ b/sdrbase/dsp/basebandsamplesink.h @@ -22,12 +22,34 @@ #include "dsp/dsptypes.h" #include "util/export.h" #include "util/messagequeue.h" +#include "util/message.h" class Message; class SDRBASE_API BasebandSampleSink : public QObject { Q_OBJECT public: + /** Used to notify on which thread the sample sink is now running (with ThreadedSampleSink) */ + class MsgThreadedSink : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QThread* getThread() const { return m_thread; } + + static MsgThreadedSink* create(const QThread* thread) + { + return new MsgThreadedSink(thread); + } + + private: + const QThread *m_thread; + + MsgThreadedSink(const QThread *thread) : + Message(), + m_thread(thread) + { } + }; + BasebandSampleSink(); virtual ~BasebandSampleSink(); diff --git a/sdrbase/dsp/downchannelizer.cpp b/sdrbase/dsp/downchannelizer.cpp index cfbec834d..3b2772db0 100644 --- a/sdrbase/dsp/downchannelizer.cpp +++ b/sdrbase/dsp/downchannelizer.cpp @@ -144,6 +144,10 @@ bool DownChannelizer::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + return m_sampleSink->handleMessage(cmd); // this message is passed to the demod + } else { return false; diff --git a/sdrbase/dsp/threadedbasebandsamplesink.cpp b/sdrbase/dsp/threadedbasebandsamplesink.cpp index 751e2ce19..0ab77303a 100644 --- a/sdrbase/dsp/threadedbasebandsamplesink.cpp +++ b/sdrbase/dsp/threadedbasebandsamplesink.cpp @@ -76,6 +76,10 @@ ThreadedBasebandSampleSink::ThreadedBasebandSampleSink(BasebandSampleSink* sampl //moveToThread(m_thread); // FIXME: Fixed? the intermediate FIFO should be handled within the sink. Define a new type of sink that is compatible with threading m_basebandSampleSink->moveToThread(m_thread); m_threadedBasebandSampleSinkFifo->moveToThread(m_thread); + BasebandSampleSink::MsgThreadedSink *msg = BasebandSampleSink::MsgThreadedSink::create(m_thread); // inform of the new thread + if (!m_basebandSampleSink->handleMessage(*msg)) { + delete msg; + } //m_sampleFifo.moveToThread(m_thread); //connect(&m_sampleFifo, SIGNAL(dataReady()), this, SLOT(handleData())); //m_sampleFifo.setSize(262144); diff --git a/sdrbase/dsp/threadedbasebandsamplesink.h b/sdrbase/dsp/threadedbasebandsamplesink.h index 9c9a9fe1a..727d03903 100644 --- a/sdrbase/dsp/threadedbasebandsamplesink.h +++ b/sdrbase/dsp/threadedbasebandsamplesink.h @@ -66,6 +66,7 @@ public: void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool positiveOnly); //!< Feed sink with samples QString getSampleSinkObjectName() const; + const QThread *getThread() const { return m_thread; } protected: diff --git a/sdrbase/util/rtpsink.cpp b/sdrbase/util/rtpsink.cpp index e794117c5..76cba88d8 100644 --- a/sdrbase/util/rtpsink.cpp +++ b/sdrbase/util/rtpsink.cpp @@ -28,35 +28,28 @@ RTPSink::RTPSink(const QString& address, uint16_t port, PayloadType payloadType) m_sampleBufferIndex(0), m_byteBuffer(0), m_destport(port), - m_rtpTransmitter(&m_rtpMemoryManager), m_mutex(QMutex::Recursive) { - // Here we use JRTPLIB in a bit funny way since we do not want the socket to bind because we are only sending - // data to a remote party and we don't want to waste a port on the local machine for each possible connection that may not be used. - // Therefore we create a socket and assign it through the SetUseExistingSockets method of the RTPUDPv4TransmissionParams object - // By doing this the socket is left unbound but sending RTP packets with the library is still possible. Other functions may - // not work but we don't care - m_rtpSessionParams.SetOwnTimestampUnit(1.0 / (double) m_sampleRate); m_rtpTransmissionParams.SetRTCPMultiplexing(true); // do not allocate another socket for RTCP - int status = m_rtpTransmitter.Init(false); + int status = m_rtpTransmitter.Init(); if (status < 0) { - qCritical("RTPSink::RTPSink: cannot initialize transmitter: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::RTPSink: cannot initialize transmitter: %s", qrtplib::RTPGetErrorString(status).c_str()); m_valid = false; } else { - qDebug("RTPSink::RTPSink: initialized transmitter: %s", jrtplib::RTPGetErrorString(status).c_str()); + qDebug("RTPSink::RTPSink: initialized transmitter: %s", qrtplib::RTPGetErrorString(status).c_str()); } m_rtpTransmitter.Create(m_rtpSessionParams.GetMaximumPacketSize(), &m_rtpTransmissionParams); - qDebug("RTPSink::RTPSink: created transmitter: %s", jrtplib::RTPGetErrorString(status).c_str()); + qDebug("RTPSink::RTPSink: created transmitter: %s", qrtplib::RTPGetErrorString(status).c_str()); status = m_rtpSession.Create(m_rtpSessionParams, &m_rtpTransmitter); if (status < 0) { - qCritical("RTPSink::RTPSink: cannot create session: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::RTPSink: cannot create session: %s", qrtplib::RTPGetErrorString(status).c_str()); m_valid = false; } else { - qDebug("RTPSink::RTPSink: created session: %s", jrtplib::RTPGetErrorString(status).c_str()); + qDebug("RTPSink::RTPSink: created session: %s", qrtplib::RTPGetErrorString(status).c_str()); } setPayloadType(payloadType); @@ -66,14 +59,12 @@ RTPSink::RTPSink(const QString& address, uint16_t port, PayloadType payloadType) uint8_t *ptr = (uint8_t*) &endianTest32; m_endianReverse = (*ptr == 1); - m_destip = inet_addr(address.toStdString().c_str()); - m_destip = ntohl(m_destip); - + m_destip.setAddress(address); } RTPSink::~RTPSink() { - jrtplib::RTPTime delay = jrtplib::RTPTime(10.0); + qrtplib::RTPTime delay = qrtplib::RTPTime(10.0); m_rtpSession.BYEDestroy(delay, "Time's up", 9); if (m_byteBuffer) { @@ -116,79 +107,76 @@ void RTPSink::setPayloadType(PayloadType payloadType) int status = m_rtpSession.SetTimestampUnit(1.0 / (double) m_sampleRate); if (status < 0) { - qCritical("RTPSink::setPayloadType: cannot set timestamp unit: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::setPayloadType: cannot set timestamp unit: %s", qrtplib::RTPGetErrorString(status).c_str()); } else { qDebug("RTPSink::setPayloadType: timestamp unit set to %f: %s", 1.0 / (double) m_sampleRate, - jrtplib::RTPGetErrorString(status).c_str()); + qrtplib::RTPGetErrorString(status).c_str()); } status = m_rtpSession.SetDefaultMark(false); if (status < 0) { - qCritical("RTPSink::setPayloadType: cannot set default mark: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::setPayloadType: cannot set default mark: %s", qrtplib::RTPGetErrorString(status).c_str()); } else { - qDebug("RTPSink::setPayloadType: set default mark to false: %s", jrtplib::RTPGetErrorString(status).c_str()); + qDebug("RTPSink::setPayloadType: set default mark to false: %s", qrtplib::RTPGetErrorString(status).c_str()); } status = m_rtpSession.SetDefaultTimestampIncrement(m_packetSamples); if (status < 0) { - qCritical("RTPSink::setPayloadType: cannot set default timestamp increment: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::setPayloadType: cannot set default timestamp increment: %s", qrtplib::RTPGetErrorString(status).c_str()); } else { - qDebug("RTPSink::setPayloadType: set default timestamp increment to %d: %s", m_packetSamples, jrtplib::RTPGetErrorString(status).c_str()); + qDebug("RTPSink::setPayloadType: set default timestamp increment to %d: %s", m_packetSamples, qrtplib::RTPGetErrorString(status).c_str()); } status = m_rtpSession.SetMaximumPacketSize(m_bufferSize+40); if (status < 0) { - qCritical("RTPSink::setPayloadType: cannot set maximum packet size: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::setPayloadType: cannot set maximum packet size: %s", qrtplib::RTPGetErrorString(status).c_str()); } else { - qDebug("RTPSink::setPayloadType: set maximum packet size to %d bytes: %s", m_bufferSize+40, jrtplib::RTPGetErrorString(status).c_str()); + qDebug("RTPSink::setPayloadType: set maximum packet size to %d bytes: %s", m_bufferSize+40, qrtplib::RTPGetErrorString(status).c_str()); } } void RTPSink::setDestination(const QString& address, uint16_t port) { m_rtpSession.ClearDestinations(); - m_rtpSession.DeleteDestination(jrtplib::RTPIPv4Address(m_destip, m_destport)); - m_destip = inet_addr(address.toStdString().c_str()); - m_destip = ntohl(m_destip); + m_rtpSession.DeleteDestination(qrtplib::RTPAddress(m_destip, m_destport)); + m_destip.setAddress(address); m_destport = port; - int status = m_rtpSession.AddDestination(jrtplib::RTPIPv4Address(m_destip, m_destport)); + int status = m_rtpSession.AddDestination(qrtplib::RTPAddress(m_destip, m_destport)); if (status < 0) { - qCritical("RTPSink::setDestination: cannot set destination address: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::setDestination: cannot set destination address: %s", qrtplib::RTPGetErrorString(status).c_str()); } } void RTPSink::deleteDestination(const QString& address, uint16_t port) { - uint32_t destip = inet_addr(address.toStdString().c_str()); - destip = ntohl(m_destip); + QHostAddress destip(address); - int status = m_rtpSession.DeleteDestination(jrtplib::RTPIPv4Address(destip, port)); + int status = m_rtpSession.DeleteDestination(qrtplib::RTPAddress(destip, port)); if (status < 0) { - qCritical("RTPSink::deleteDestination: cannot delete destination address: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::deleteDestination: cannot delete destination address: %s", qrtplib::RTPGetErrorString(status).c_str()); } } void RTPSink::addDestination(const QString& address, uint16_t port) { - uint32_t destip = inet_addr(address.toStdString().c_str()); - destip = ntohl(m_destip); + QHostAddress destip(address); - int status = m_rtpSession.AddDestination(jrtplib::RTPIPv4Address(destip, port)); + int status = m_rtpSession.AddDestination(qrtplib::RTPAddress(destip, port)); if (status < 0) { - qCritical("RTPSink::addDestination: cannot add destination address: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::addDestination: cannot add destination address: %s", qrtplib::RTPGetErrorString(status).c_str()); } else { qDebug("RTPSink::addDestination: destination address set to %s:%d: %s", address.toStdString().c_str(), port, - jrtplib::RTPGetErrorString(status).c_str()); + qrtplib::RTPGetErrorString(status).c_str()); } } @@ -210,7 +198,7 @@ void RTPSink::write(const uint8_t *sampleByte) int status = m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize); if (status < 0) { - qCritical("RTPSink::write: cannot write packet: %s", jrtplib::RTPGetErrorString(status).c_str()); + qCritical("RTPSink::write: cannot write packet: %s", qrtplib::RTPGetErrorString(status).c_str()); } writeNetBuf(&m_byteBuffer[0], sampleByte, elemLength(m_payloadType), m_sampleBytes, m_endianReverse); diff --git a/sdrbase/util/rtpsink.h b/sdrbase/util/rtpsink.h index 28753f3c4..9b56c19be 100644 --- a/sdrbase/util/rtpsink.h +++ b/sdrbase/util/rtpsink.h @@ -21,48 +21,18 @@ #include #include #include +#include #include -// jrtplib includes +// qrtplib includes #include "rtpsession.h" -#include "rtpudpv4transmitternobind.h" -#include "rtpipv4address.h" +#include "rtpudptransmitter.h" +#include "rtpaddress.h" #include "rtpsessionparams.h" #include "rtperrors.h" -#include "rtplibraryversion.h" #include "util/export.h" -class SDRBASE_API RTPSinkMemoryManager : public jrtplib::RTPMemoryManager -{ -public: - RTPSinkMemoryManager() - { - alloccount = 0; - freecount = 0; - } - ~RTPSinkMemoryManager() - { - qDebug() << "RTPSinkMemoryManager::~RTPSinkMemoryManager: alloc: " << alloccount << " free: " << freecount; - } - void *AllocateBuffer(size_t numbytes, int memtype) - { - void *buf = malloc(numbytes); - qDebug() << "RTPSinkMemoryManager::AllocateBuffer: Allocated " << numbytes << " bytes at location " << buf << " (memtype = " << memtype << ")"; - alloccount++; - return buf; - } - - void FreeBuffer(void *p) - { - qDebug() << "RTPSinkMemoryManager::FreeBuffer: Freeing block " << p; - freecount++; - free(p); - } -private: - int alloccount,freecount; -}; - class RTPSink { public: @@ -98,13 +68,12 @@ protected: int m_bufferSize; int m_sampleBufferIndex; uint8_t *m_byteBuffer; - uint32_t m_destip; + QHostAddress m_destip; uint16_t m_destport; - jrtplib::RTPSession m_rtpSession; - jrtplib::RTPSessionParams m_rtpSessionParams; - jrtplib::RTPUDPv4TransmissionNoBindParams m_rtpTransmissionParams; - jrtplib::RTPUDPv4TransmitterNoBind m_rtpTransmitter; - RTPSinkMemoryManager m_rtpMemoryManager; + qrtplib::RTPSession m_rtpSession; + qrtplib::RTPSessionParams m_rtpSessionParams; + qrtplib::RTPUDPTransmissionParams m_rtpTransmissionParams; + qrtplib::RTPUDPTransmitter m_rtpTransmitter; bool m_endianReverse; QMutex m_mutex; }; diff --git a/sdrbase/util/udpsink.h b/sdrbase/util/udpsink.h index c271b172e..17ed66a2b 100644 --- a/sdrbase/util/udpsink.h +++ b/sdrbase/util/udpsink.h @@ -71,6 +71,11 @@ public: delete m_socket; } + void moveToThread(QThread *thread) + { + m_socket->moveToThread(thread); + } + void setAddress(QString& address) { m_address.setAddress(address); } void setPort(unsigned int port) { m_port = port; } From 60568de298986ba0d527eb040c942c65d47de298 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 6 Mar 2018 08:40:46 +0100 Subject: [PATCH 060/956] Always delete BasebandSampleSink::MsgThreadedSink on return of processing --- sdrbase/dsp/threadedbasebandsamplesink.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdrbase/dsp/threadedbasebandsamplesink.cpp b/sdrbase/dsp/threadedbasebandsamplesink.cpp index 0ab77303a..71ded353e 100644 --- a/sdrbase/dsp/threadedbasebandsamplesink.cpp +++ b/sdrbase/dsp/threadedbasebandsamplesink.cpp @@ -77,9 +77,8 @@ ThreadedBasebandSampleSink::ThreadedBasebandSampleSink(BasebandSampleSink* sampl m_basebandSampleSink->moveToThread(m_thread); m_threadedBasebandSampleSinkFifo->moveToThread(m_thread); BasebandSampleSink::MsgThreadedSink *msg = BasebandSampleSink::MsgThreadedSink::create(m_thread); // inform of the new thread - if (!m_basebandSampleSink->handleMessage(*msg)) { - delete msg; - } + m_basebandSampleSink->handleMessage(*msg); + delete msg; //m_sampleFifo.moveToThread(m_thread); //connect(&m_sampleFifo, SIGNAL(dataReady()), this, SLOT(handleData())); //m_sampleFifo.setSize(262144); From 0c861d63e2cb31c0efc203907afa769a1ce21c07 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 6 Mar 2018 23:16:50 +0100 Subject: [PATCH 061/956] qrtplib: use it for the audio net sink --- qrtplib/rtpudptransmitter.cpp | 11 +++++++++++ qrtplib/rtpudptransmitter.h | 2 ++ sdrbase/audio/audionetsink.cpp | 4 ++++ sdrbase/util/rtpsink.h | 4 ++++ 4 files changed, 21 insertions(+) diff --git a/qrtplib/rtpudptransmitter.cpp b/qrtplib/rtpudptransmitter.cpp index f4fef5a76..cc2ee3a3e 100644 --- a/qrtplib/rtpudptransmitter.cpp +++ b/qrtplib/rtpudptransmitter.cpp @@ -174,6 +174,17 @@ int RTPUDPTransmitter::BindSockets() return 0; } +void RTPUDPTransmitter::moveToThread(QThread *thread) +{ + if (m_rtpsock) { + m_rtpsock->moveToThread(thread); + } + + if (m_rtpsock != m_rtcpsock) { + m_rtcpsock->moveToThread(thread); + } +} + void RTPUDPTransmitter::Destroy() { if (!m_init) { diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h index 7b1719d61..add4561ee 100644 --- a/qrtplib/rtpudptransmitter.h +++ b/qrtplib/rtpudptransmitter.h @@ -282,6 +282,7 @@ public: virtual int Init(); virtual int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); virtual int BindSockets(); + void moveToThread(QThread *thread); virtual void Destroy(); virtual RTPTransmissionInfo *GetTransmissionInfo(); virtual void DeleteTransmissionInfo(RTPTransmissionInfo *inf); @@ -314,6 +315,7 @@ public: virtual RTPRawPacket *GetNextPacket(); + private: bool m_init; bool m_created; diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index 6444410e7..c8775662c 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -135,5 +135,9 @@ void AudioNetSink::moveToThread(QThread *thread) if (m_udpBufferAudioStereo) { m_udpBufferAudioMono->moveToThread(thread); } + + if (m_rtpBufferAudio) { + m_rtpBufferAudio->moveToThread(thread); + } } diff --git a/sdrbase/util/rtpsink.h b/sdrbase/util/rtpsink.h index 9b56c19be..051316d65 100644 --- a/sdrbase/util/rtpsink.h +++ b/sdrbase/util/rtpsink.h @@ -55,6 +55,10 @@ public: void write(const uint8_t *sampleByte); void write(const uint8_t *sampleByte, int nbSamples); + void moveToThread(QThread *thread) { + m_rtpTransmitter.moveToThread(thread); + } + protected: /** Reverse endianess in destination buffer */ static void writeNetBuf(uint8_t *dest, const uint8_t *src, unsigned int elemLen, unsigned int bytesLen, bool endianReverse); From 5b8a5efd3f3914e451ae19cfb08876b3494fff8d Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Mar 2018 00:28:50 +0100 Subject: [PATCH 062/956] NFM demod: replace RTP button switch by a checkbox --- plugins/channelrx/demodnfm/nfmdemodgui.ui | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.ui b/plugins/channelrx/demodnfm/nfmdemodgui.ui index c798abb3f..6c7d73d4e 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.ui +++ b/plugins/channelrx/demodnfm/nfmdemodgui.ui @@ -612,16 +612,13 @@
- + - Use RTP protocol for sending audio via UDP + Use RTP protocol for audio copy to UDP R - - true - From 1200e090127f89e2b4d6400f3702a0c94764753c Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Mar 2018 00:55:08 +0100 Subject: [PATCH 063/956] AM demod: implement RTP over UDP for audio copy --- plugins/channelrx/demodam/amdemod.cpp | 34 ++++++++++++++++--- plugins/channelrx/demodam/amdemod.h | 13 +++++-- plugins/channelrx/demodam/amdemodgui.cpp | 14 ++++++++ plugins/channelrx/demodam/amdemodgui.h | 1 + plugins/channelrx/demodam/amdemodgui.ui | 10 ++++++ plugins/channelrx/demodam/amdemodsettings.cpp | 3 ++ plugins/channelrx/demodam/amdemodsettings.h | 1 + 7 files changed, 69 insertions(+), 7 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index a73cfcc69..2e075ad84 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -58,7 +58,8 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_magsq = 0.0; DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); + m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically + m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); @@ -72,13 +73,18 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : AMDemod::~AMDemod() { DSPEngine::instance()->removeAudioSink(&m_audioFifo); - delete m_udpBufferAudio; + delete m_audioNetSink; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; } +bool AMDemod::isAudioNetSinkRTPCapable() const +{ + return m_audioNetSink && m_audioNetSink->isRTPCapable(); +} + void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { Complex ci; @@ -226,6 +232,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) << " m_audioMute: " << settings.m_audioMute << " m_bandpassEnable: " << settings.m_bandpassEnable << " m_copyAudioToUDP: " << settings.m_copyAudioToUDP + << " m_copyAudioUseRTP: " << settings.m_copyAudioUseRTP << " m_udpAddress: " << settings.m_udpAddress << " m_udpPort: " << settings.m_udpPort << " force: " << force; @@ -250,8 +257,27 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) if ((m_settings.m_udpAddress != settings.m_udpAddress) || (m_settings.m_udpPort != settings.m_udpPort) || force) { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); + m_audioNetSink->setDestination(settings.m_udpAddress, settings.m_udpPort); + } + + if ((settings.m_copyAudioUseRTP != m_settings.m_copyAudioUseRTP) || force) + { + if (settings.m_copyAudioUseRTP) + { + if (m_audioNetSink->selectType(AudioNetSink::SinkRTP)) { + qDebug("NFMDemod::applySettings: set audio sink to RTP mode"); + } else { + qWarning("NFMDemod::applySettings: RTP support for audio sink not available. Fall back too UDP"); + } + } + else + { + if (m_audioNetSink->selectType(AudioNetSink::SinkUDP)) { + qDebug("NFMDemod::applySettings: set audio sink to UDP mode"); + } else { + qWarning("NFMDemod::applySettings: failed to set audio sink to UDP mode"); + } + } } m_settings = settings; diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index 3dccaeec1..8d20b7456 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -30,6 +30,7 @@ #include "audio/audiofifo.h" #include "util/message.h" #include "amdemodsettings.h" +#include "audio/audionetsink.h" class DeviceSourceAPI; class DownChannelizer; @@ -113,6 +114,8 @@ public: m_magsqCount = 0; } + bool isAudioNetSinkRTPCapable() const; + static const QString m_channelIdURI; static const QString m_channelId; @@ -151,7 +154,7 @@ private: AudioVector m_audioBuffer; uint32_t m_audioBufferFill; AudioFifo m_audioFifo; - UDPSink *m_udpBufferAudio; + AudioNetSink *m_audioNetSink; static const int m_udpBlockSize; @@ -207,14 +210,18 @@ private: Real attack = (m_squelchCount - 0.05f * m_settings.m_audioSampleRate) / (0.05f * m_settings.m_audioSampleRate); sample = demod * attack * 2048 * m_settings.m_volume; - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(demod * attack * SDR_RX_SCALEF); + if (m_settings.m_copyAudioToUDP) { + m_audioNetSink->write(demod * attack * 32768.0f); + } m_squelchOpen = true; } else { sample = 0; - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(0); + if (m_settings.m_copyAudioToUDP) { + m_audioNetSink->write(0); + } m_squelchOpen = false; } diff --git a/plugins/channelrx/demodam/amdemodgui.cpp b/plugins/channelrx/demodam/amdemodgui.cpp index 6302d7a26..bbd28f62c 100644 --- a/plugins/channelrx/demodam/amdemodgui.cpp +++ b/plugins/channelrx/demodam/amdemodgui.cpp @@ -153,6 +153,12 @@ void AMDemodGUI::on_copyAudioToUDP_toggled(bool checked) applySettings(); } +void AMDemodGUI::on_useRTP_toggled(bool checked) +{ + m_settings.m_copyAudioUseRTP = checked; + applySettings(); +} + void AMDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { /* @@ -221,6 +227,10 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); + if (!m_amDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->hide(); + } + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); @@ -285,6 +295,10 @@ void AMDemodGUI::displaySettings() ui->bandpassEnable->setChecked(m_settings.m_bandpassEnable); ui->copyAudioToUDP->setChecked(m_settings.m_copyAudioToUDP); + if (m_amDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->setChecked(m_settings.m_copyAudioUseRTP); + } + blockApplySettings(false); } diff --git a/plugins/channelrx/demodam/amdemodgui.h b/plugins/channelrx/demodam/amdemodgui.h index f853cc220..1cb7886cc 100644 --- a/plugins/channelrx/demodam/amdemodgui.h +++ b/plugins/channelrx/demodam/amdemodgui.h @@ -72,6 +72,7 @@ private slots: void on_squelch_valueChanged(int value); void on_audioMute_toggled(bool checked); void on_copyAudioToUDP_toggled(bool copy); + void on_useRTP_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void tick(); diff --git a/plugins/channelrx/demodam/amdemodgui.ui b/plugins/channelrx/demodam/amdemodgui.ui index 01b80ae3f..deb8182dc 100644 --- a/plugins/channelrx/demodam/amdemodgui.ui +++ b/plugins/channelrx/demodam/amdemodgui.ui @@ -199,6 +199,16 @@ + + + + Use RTP protocol for audio copy to UDP + + + R + + + diff --git a/plugins/channelrx/demodam/amdemodsettings.cpp b/plugins/channelrx/demodam/amdemodsettings.cpp index 2af7ceaab..a523e49d8 100644 --- a/plugins/channelrx/demodam/amdemodsettings.cpp +++ b/plugins/channelrx/demodam/amdemodsettings.cpp @@ -37,6 +37,7 @@ void AMDemodSettings::resetToDefaults() m_audioMute = false; m_bandpassEnable = false; m_copyAudioToUDP = false; + m_copyAudioUseRTP = false; m_udpAddress = "127.0.0.1"; m_udpPort = 9999; m_rgbColor = QColor(255, 255, 0).rgb(); @@ -58,6 +59,7 @@ QByteArray AMDemodSettings::serialize() const s.writeU32(7, m_rgbColor); s.writeBool(8, m_bandpassEnable); s.writeString(9, m_title); + s.writeBool(10, m_copyAudioUseRTP); return s.final(); } @@ -93,6 +95,7 @@ bool AMDemodSettings::deserialize(const QByteArray& data) d.readU32(7, &m_rgbColor); d.readBool(8, &m_bandpassEnable, false); d.readString(9, &m_title, "AM Demodulator"); + d.readBool(10, &m_copyAudioUseRTP, false); return true; } diff --git a/plugins/channelrx/demodam/amdemodsettings.h b/plugins/channelrx/demodam/amdemodsettings.h index c469246f6..4c908a144 100644 --- a/plugins/channelrx/demodam/amdemodsettings.h +++ b/plugins/channelrx/demodam/amdemodsettings.h @@ -31,6 +31,7 @@ struct AMDemodSettings bool m_audioMute; bool m_bandpassEnable; bool m_copyAudioToUDP; + bool m_copyAudioUseRTP; QString m_udpAddress; quint16 m_udpPort; quint32 m_rgbColor; From 26995aae1ca5e86702b410c2682eae8da9ed60fa Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Mar 2018 01:15:59 +0100 Subject: [PATCH 064/956] SSB demod: implement RTP over UDP for audio copy --- plugins/channelrx/demodam/amdemod.cpp | 8 ++++ plugins/channelrx/demodam/amdemod.h | 2 +- plugins/channelrx/demodssb/ssbdemod.cpp | 48 ++++++++++++++++--- plugins/channelrx/demodssb/ssbdemod.h | 5 +- plugins/channelrx/demodssb/ssbdemodgui.cpp | 13 +++++ plugins/channelrx/demodssb/ssbdemodgui.h | 1 + plugins/channelrx/demodssb/ssbdemodgui.ui | 16 +++++-- .../channelrx/demodssb/ssbdemodsettings.cpp | 3 ++ plugins/channelrx/demodssb/ssbdemodsettings.h | 1 + sdrbase/audio/audionetsink.h | 4 +- 10 files changed, 88 insertions(+), 13 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index 2e075ad84..7b9d2f7e0 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -187,6 +187,14 @@ bool AMDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("AMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + m_audioNetSink->moveToThread(const_cast(thread)); // use the thread for udp sinks + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index 8d20b7456..5fec9042f 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -28,9 +28,9 @@ #include "dsp/agc.h" #include "dsp/bandpass.h" #include "audio/audiofifo.h" +#include "audio/audionetsink.h" #include "util/message.h" #include "amdemodsettings.h" -#include "audio/audionetsink.h" class DeviceSourceAPI; class DownChannelizer; diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 84d92bd8a..5e90d4252 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -22,6 +22,7 @@ #include #include "audio/audiooutput.h" +#include "audio/audionetsink.h" #include "dsp/dspengine.h" #include "dsp/downchannelizer.h" #include "dsp/threadedbasebandsamplesink.h" @@ -85,7 +86,8 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : DSBFilter = new fftfilt((2.0f * m_Bandwidth) / m_audioSampleRate, 2 * ssbFftLen); DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); + m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically + m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); @@ -101,13 +103,17 @@ SSBDemod::~SSBDemod() if (SSBFilter) delete SSBFilter; if (DSBFilter) delete DSBFilter; DSPEngine::instance()->removeAudioSink(&m_audioFifo); + delete m_audioNetSink; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; +} - delete m_udpBufferAudio; +bool SSBDemod::isAudioNetSinkRTPCapable() const +{ + return m_audioNetSink && m_audioNetSink->isRTPCapable(); } void SSBDemod::configure(MessageQueue* messageQueue, @@ -219,7 +225,7 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_audioBuffer[m_audioBufferFill].r = 0; m_audioBuffer[m_audioBufferFill].l = 0; - if (m_settings.m_copyAudioToUDP) { m_udpBufferAudio->write(0); } + if (m_settings.m_copyAudioToUDP) { m_audioNetSink->write(0); } } else { @@ -236,7 +242,7 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_audioBuffer[m_audioBufferFill].l = (qint16)(sideband[i].imag() * m_volume * agcVal); } - if (m_settings.m_copyAudioToUDP) { m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill].r + m_audioBuffer[m_audioBufferFill].l); } + if (m_settings.m_copyAudioToUDP) { m_audioNetSink->write(m_audioBuffer[m_audioBufferFill].r + m_audioBuffer[m_audioBufferFill].l); } } else { @@ -245,7 +251,7 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; - if (m_settings.m_copyAudioToUDP) { m_udpBufferAudio->write(sample); } + if (m_settings.m_copyAudioToUDP) { m_audioNetSink->write(sample); } } } @@ -325,6 +331,14 @@ bool SSBDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("SSBDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + m_audioNetSink->moveToThread(const_cast(thread)); // use the thread for udp sinks + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -380,6 +394,7 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) << " m_dsb: " << settings.m_dsb << " m_audioMute: " << settings.m_audioMute << " m_copyAudioToUDP: " << settings.m_copyAudioToUDP + << " m_copyAudioUseRTP: " << settings.m_copyAudioUseRTP << " m_agcActive: " << settings.m_agc << " m_agcClamping: " << settings.m_agcClamping << " m_agcTimeLog2: " << settings.m_agcTimeLog2 @@ -476,8 +491,27 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) if ((m_settings.m_udpAddress != settings.m_udpAddress) || (m_settings.m_udpPort != settings.m_udpPort) || force) { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); + m_audioNetSink->setDestination(settings.m_udpAddress, settings.m_udpPort); + } + + if ((settings.m_copyAudioUseRTP != m_settings.m_copyAudioUseRTP) || force) + { + if (settings.m_copyAudioUseRTP) + { + if (m_audioNetSink->selectType(AudioNetSink::SinkRTP)) { + qDebug("NFMDemod::applySettings: set audio sink to RTP mode"); + } else { + qWarning("NFMDemod::applySettings: RTP support for audio sink not available. Fall back too UDP"); + } + } + else + { + if (m_audioNetSink->selectType(AudioNetSink::SinkUDP)) { + qDebug("NFMDemod::applySettings: set audio sink to UDP mode"); + } else { + qWarning("NFMDemod::applySettings: failed to set audio sink to UDP mode"); + } + } } m_spanLog2 = settings.m_spanLog2; diff --git a/plugins/channelrx/demodssb/ssbdemod.h b/plugins/channelrx/demodssb/ssbdemod.h index a8fa5e0d9..c71698e01 100644 --- a/plugins/channelrx/demodssb/ssbdemod.h +++ b/plugins/channelrx/demodssb/ssbdemod.h @@ -38,6 +38,7 @@ class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; +class AudioNetSink; class SSBDemod : public BasebandSampleSink, public ChannelSinkAPI { public: @@ -133,6 +134,8 @@ public: m_magsqCount = 0; } + bool isAudioNetSinkRTPCapable() const; + static const QString m_channelIdURI; static const QString m_channelId; @@ -274,7 +277,7 @@ private: uint m_audioBufferFill; AudioFifo m_audioFifo; quint32 m_audioSampleRate; - UDPSink *m_udpBufferAudio; + AudioNetSink *m_audioNetSink; static const int m_udpBlockSize; QMutex m_settingsMutex; diff --git a/plugins/channelrx/demodssb/ssbdemodgui.cpp b/plugins/channelrx/demodssb/ssbdemodgui.cpp index 2e07bc7c1..eb9c63e0e 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.cpp +++ b/plugins/channelrx/demodssb/ssbdemodgui.cpp @@ -203,6 +203,12 @@ void SSBDemodGUI::on_copyAudioToUDP_toggled(bool checked) applySettings(); } +void SSBDemodGUI::on_useRTP_toggled(bool checked) +{ + m_settings.m_copyAudioUseRTP = checked; + applySettings(); +} + void SSBDemodGUI::onMenuDialogCalled(const QPoint &p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); @@ -282,6 +288,9 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); + if (!m_ssbDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->hide(); + } connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); @@ -514,6 +523,10 @@ void SSBDemodGUI::displaySettings() ui->agcThresholdGateText->setText(s); ui->copyAudioToUDP->setChecked(m_settings.m_copyAudioToUDP); + if (m_ssbDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->setChecked(m_settings.m_copyAudioUseRTP); + } + blockApplySettings(false); } diff --git a/plugins/channelrx/demodssb/ssbdemodgui.h b/plugins/channelrx/demodssb/ssbdemodgui.h index 79c15f4fa..52b2a9cd5 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.h +++ b/plugins/channelrx/demodssb/ssbdemodgui.h @@ -96,6 +96,7 @@ private slots: void on_spanLog2_valueChanged(int value); void on_flipSidebands_clicked(bool checked); void on_copyAudioToUDP_toggled(bool copy); + void on_useRTP_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void tick(); diff --git a/plugins/channelrx/demodssb/ssbdemodgui.ui b/plugins/channelrx/demodssb/ssbdemodgui.ui index df8594703..4fd0a4800 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.ui +++ b/plugins/channelrx/demodssb/ssbdemodgui.ui @@ -6,7 +6,7 @@ 0 0 - 400 + 413 190 @@ -36,13 +36,13 @@ 0 0 - 398 + 410 171 - 398 + 410 0 @@ -873,6 +873,16 @@ + + + + Use RTP protocol for audio copy to UDP + + + R + + + diff --git a/plugins/channelrx/demodssb/ssbdemodsettings.cpp b/plugins/channelrx/demodssb/ssbdemodsettings.cpp index b1f805e6b..2cebdd11e 100644 --- a/plugins/channelrx/demodssb/ssbdemodsettings.cpp +++ b/plugins/channelrx/demodssb/ssbdemodsettings.cpp @@ -45,6 +45,7 @@ void SSBDemodSettings::resetToDefaults() m_agc = false; m_agcClamping = false; m_copyAudioToUDP = false; + m_copyAudioUseRTP = false; m_agcPowerThreshold = -40; m_agcThresholdGate = 4; m_agcTimeLog2 = 7; @@ -83,6 +84,7 @@ QByteArray SSBDemodSettings::serialize() const s.writeS32(14, m_agcThresholdGate); s.writeBool(15, m_agcClamping); s.writeString(16, m_title); + s.writeBool(17, m_copyAudioUseRTP); return s.final(); } @@ -127,6 +129,7 @@ bool SSBDemodSettings::deserialize(const QByteArray& data) d.readS32(14, &m_agcThresholdGate, 4); d.readBool(15, &m_agcClamping, false); d.readString(16, &m_title, "SSB Demodulator"); + d.readBool(17, &m_copyAudioUseRTP, false); return true; } diff --git a/plugins/channelrx/demodssb/ssbdemodsettings.h b/plugins/channelrx/demodssb/ssbdemodsettings.h index fda0e1fad..a13232797 100644 --- a/plugins/channelrx/demodssb/ssbdemodsettings.h +++ b/plugins/channelrx/demodssb/ssbdemodsettings.h @@ -34,6 +34,7 @@ struct SSBDemodSettings bool m_dsb; bool m_audioMute; bool m_copyAudioToUDP; + bool m_copyAudioUseRTP; bool m_agc; bool m_agcClamping; int m_agcTimeLog2; diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index a1781de8c..33456a50e 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -18,10 +18,12 @@ #ifndef SDRBASE_AUDIO_AUDIONETSINK_H_ #define SDRBASE_AUDIO_AUDIONETSINK_H_ -#include #include "dsp/dsptypes.h" #include "util/export.h" +#include +#include + template class UDPSink; class RTPSink; class QThread; From 3dbf59cd1d73deca0d97d7834fa2a947a9a7075b Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Mar 2018 01:39:02 +0100 Subject: [PATCH 065/956] DSD demod: implement RTP over UDP for audio copy (part 1) --- plugins/channelrx/demoddsd/dsddemod.h | 2 ++ plugins/channelrx/demoddsd/dsddemodgui.cpp | 10 ++++++++++ plugins/channelrx/demoddsd/dsddemodgui.h | 1 + plugins/channelrx/demoddsd/dsddemodgui.ui | 10 ++++++++++ plugins/channelrx/demoddsd/dsddemodsettings.cpp | 3 +++ plugins/channelrx/demoddsd/dsddemodsettings.h | 1 + 6 files changed, 27 insertions(+) diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index 5c47cb5fe..d4b91e568 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -125,6 +125,8 @@ public: m_magsqCount = 0; } + bool isAudioNetSinkRTPCapable() const { return false; } + static const QString m_channelIdURI; static const QString m_channelId; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 7471d988f..066b470e1 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -219,6 +219,12 @@ void DSDDemodGUI::on_udpOutput_toggled(bool checked) applySettings(); } +void DSDDemodGUI::on_useRTP_toggled(bool checked) +{ + m_settings.m_copyAudioUseRTP = checked; + applySettings(); +} + void DSDDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { /* @@ -385,6 +391,10 @@ void DSDDemodGUI::displaySettings() ui->udpOutput->setChecked(m_settings.m_copyAudioToUDP); ui->symbolPLLLock->setChecked(m_settings.m_pllLock); + if (m_dsdDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->setChecked(m_settings.m_copyAudioUseRTP); + } + ui->baudRate->setCurrentIndex(DSDDemodBaudRates::getRateIndex(m_settings.m_baudRate)); blockApplySettings(false); diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index 40fa8e18d..c79648b44 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -128,6 +128,7 @@ private slots: void on_audioMute_toggled(bool checked); void on_symbolPLLLock_toggled(bool checked); void on_udpOutput_toggled(bool checked); + void on_useRTP_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void tick(); diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index 81dae5996..c05e60e6f 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -867,6 +867,16 @@ + + + + Use RTP protocol for audio copy to UDP + + + R + + + diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index c7404ee77..bd0d23c91 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -47,6 +47,7 @@ void DSDDemodSettings::resetToDefaults() m_tdmaStereo = false; m_pllLock = true; m_copyAudioToUDP = false; + m_copyAudioUseRTP = false; m_udpAddress = "127.0.0.1"; m_udpPort = 9999; m_rgbColor = QColor(0, 255, 255).rgb(); @@ -83,6 +84,7 @@ QByteArray DSDDemodSettings::serialize() const s.writeString(18, m_title); s.writeBool(19, m_highPassFilter); + s.writeBool(20, m_copyAudioUseRTP); return s.final(); } @@ -136,6 +138,7 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) d.readBool(16, &m_tdmaStereo, false); d.readString(18, &m_title, "DSD Demodulator"); d.readBool(19, &m_highPassFilter, false); + d.readBool(20, &m_copyAudioUseRTP, false); return true; } diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.h b/plugins/channelrx/demoddsd/dsddemodsettings.h index f60a4d6c2..58cb08c65 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.h +++ b/plugins/channelrx/demoddsd/dsddemodsettings.h @@ -40,6 +40,7 @@ struct DSDDemodSettings bool m_tdmaStereo; bool m_pllLock; bool m_copyAudioToUDP; + bool m_copyAudioUseRTP; QString m_udpAddress; quint16 m_udpPort; quint32 m_rgbColor; From 9648779002a13e6b78a617295c0fce71634faea5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Mar 2018 02:17:08 +0100 Subject: [PATCH 066/956] qrtplib: removed useless header --- qrtplib/rtpsocketutilinternal.h | 79 --------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 qrtplib/rtpsocketutilinternal.h diff --git a/qrtplib/rtpsocketutilinternal.h b/qrtplib/rtpsocketutilinternal.h deleted file mode 100644 index 3dd63c5ca..000000000 --- a/qrtplib/rtpsocketutilinternal.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs - - Contact: jori.liesenborgs@gmail.com - - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - - */ - -/** - * \file rtpsocketutilinternal.h - */ - -#ifndef RTPSOCKETUTILINTERNAL_H - -#define RTPSOCKETUTILINTERNAL_H - -#ifdef RTP_SOCKETTYPE_WINSOCK -#define RTPSOCKERR INVALID_SOCKET -#define RTPCLOSE(x) closesocket(x) -#define RTPSOCKLENTYPE int -#define RTPIOCTL ioctlsocket -#else // not Win32 -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef RTP_HAVE_SYS_FILIO -#include -#endif // RTP_HAVE_SYS_FILIO -#ifdef RTP_HAVE_SYS_SOCKIO -#include -#endif // RTP_HAVE_SYS_SOCKIO -#ifdef RTP_SUPPORT_IFADDRS -#include -#endif // RTP_SUPPORT_IFADDRS - -#define RTPSOCKERR -1 -#define RTPCLOSE(x) close(x) - -#ifdef RTP_SOCKLENTYPE_UINT -#define RTPSOCKLENTYPE unsigned int -#else -#define RTPSOCKLENTYPE int -#endif // RTP_SOCKLENTYPE_UINT - -#define RTPIOCTL ioctl -#endif // RTP_SOCKETTYPE_WINSOCK - -#endif // RTPSOCKETUTILINTERNAL_H - From bc37dbfd24f0bb618b9d1baa559c2cf94ed3db0a Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Mar 2018 13:40:16 +0100 Subject: [PATCH 067/956] Simplify AudioNetSink by removing the stereo UDP socket/buffer --- sdrbase/audio/audionetsink.cpp | 36 +--------------------------------- sdrbase/audio/audionetsink.h | 2 -- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index c8775662c..8960ff80e 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -24,15 +24,9 @@ const int AudioNetSink::m_udpBlockSize = 512; AudioNetSink::AudioNetSink(QObject *parent, bool stereo) : m_type(SinkUDP), m_udpBufferAudioMono(0), - m_udpBufferAudioStereo(0), m_rtpBufferAudio(0) { - if (stereo) { - m_udpBufferAudioStereo = new UDPSink(parent, m_udpBlockSize); - } else { - m_udpBufferAudioMono = new UDPSink(parent, m_udpBlockSize); - } - + m_udpBufferAudioMono = new UDPSink(parent, m_udpBlockSize); m_rtpBufferAudio = new RTPSink("127.0.0.1", 9999, stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono); } @@ -41,10 +35,6 @@ AudioNetSink::~AudioNetSink() if (m_udpBufferAudioMono) { delete m_udpBufferAudioMono; } - - if (m_udpBufferAudioStereo) { - delete m_udpBufferAudioStereo; - } if (m_rtpBufferAudio) { delete m_rtpBufferAudio; } @@ -78,9 +68,6 @@ void AudioNetSink::setDestination(const QString& address, uint16_t port) if (m_udpBufferAudioMono) { m_udpBufferAudioMono->setDestination(address, port); } - if (m_udpBufferAudioStereo) { - m_udpBufferAudioStereo->setDestination(address, port); - } if (m_rtpBufferAudio) { m_rtpBufferAudio->setDestination(address, port); } @@ -102,10 +89,6 @@ void AudioNetSink::deleteDestination(const QString& address, uint16_t port) void AudioNetSink::write(qint16 sample) { - if (m_udpBufferAudioMono == 0) { - return; - } - if (m_type == SinkUDP) { m_udpBufferAudioMono->write(sample); } else if (m_type == SinkRTP) { @@ -113,29 +96,12 @@ void AudioNetSink::write(qint16 sample) } } -void AudioNetSink::write(const AudioSample& sample) -{ - if (m_udpBufferAudioStereo == 0) { - return; - } - - if (m_type == SinkUDP) { - m_udpBufferAudioStereo->write(sample); - } else if (m_type == SinkRTP) { - m_rtpBufferAudio->write((uint8_t *) &sample); - } -} - void AudioNetSink::moveToThread(QThread *thread) { if (m_udpBufferAudioMono) { m_udpBufferAudioMono->moveToThread(thread); } - if (m_udpBufferAudioStereo) { - m_udpBufferAudioMono->moveToThread(thread); - } - if (m_rtpBufferAudio) { m_rtpBufferAudio->moveToThread(thread); } diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index 33456a50e..bd2c1f4f7 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -44,7 +44,6 @@ public: void deleteDestination(const QString& address, uint16_t port); void write(qint16 sample); - void write(const AudioSample& sample); bool isRTPCapable() const; bool selectType(SinkType type); @@ -56,7 +55,6 @@ public: protected: SinkType m_type; UDPSink *m_udpBufferAudioMono; - UDPSink *m_udpBufferAudioStereo; RTPSink *m_rtpBufferAudio; }; From f310eb4dad34de087b6a3bda487e295343b7482a Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Mar 2018 20:18:06 +0100 Subject: [PATCH 068/956] AudioNetSink: suspend RTP sink --- sdrbase/audio/audionetsink.cpp | 10 +++++----- sdrbase/audio/audionetsink.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index 8960ff80e..e2f982b5c 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -27,7 +27,7 @@ AudioNetSink::AudioNetSink(QObject *parent, bool stereo) : m_rtpBufferAudio(0) { m_udpBufferAudioMono = new UDPSink(parent, m_udpBlockSize); - m_rtpBufferAudio = new RTPSink("127.0.0.1", 9999, stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono); + //m_rtpBufferAudio = new RTPSink("127.0.0.1", 9999, stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono); } AudioNetSink::~AudioNetSink() @@ -40,10 +40,10 @@ AudioNetSink::~AudioNetSink() } } -bool AudioNetSink::isRTPCapable() const -{ - return m_rtpBufferAudio->isValid(); -} +//bool AudioNetSink::isRTPCapable() const +//{ +// return m_rtpBufferAudio->isValid(); +//} bool AudioNetSink::selectType(SinkType type) { diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index bd2c1f4f7..9a8c9ca2b 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -45,7 +45,7 @@ public: void write(qint16 sample); - bool isRTPCapable() const; + bool isRTPCapable() const { return false; } bool selectType(SinkType type); void moveToThread(QThread *thread); From 5b0f62c3e2f72d567034b1de5646892c3c738ed3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Mar 2018 00:16:24 +0100 Subject: [PATCH 069/956] qrtplib: copy Audio to UDP/RTP: use a single UDP socket for UDP and RTP --- qrtplib/rtpudptransmitter.cpp | 48 ++++++++++++++------------- qrtplib/rtpudptransmitter.h | 27 ++++++++++++++++ sdrbase/audio/audionetsink.cpp | 59 ++++++++++++++++++++-------------- sdrbase/audio/audionetsink.h | 13 +++++--- sdrbase/util/rtpsink.cpp | 11 +++---- sdrbase/util/rtpsink.h | 8 ++--- 6 files changed, 103 insertions(+), 63 deletions(-) diff --git a/qrtplib/rtpudptransmitter.cpp b/qrtplib/rtpudptransmitter.cpp index cc2ee3a3e..957f62ec2 100644 --- a/qrtplib/rtpudptransmitter.cpp +++ b/qrtplib/rtpudptransmitter.cpp @@ -48,6 +48,7 @@ RTPUDPTransmitter::RTPUDPTransmitter() : m_init = false; m_rtcpsock = 0; m_rtpsock = 0; + m_deletesocketswhendone = false; m_waitingfordata = false; m_rtcpPort = 0; m_rtpPort = 0; @@ -122,15 +123,24 @@ int RTPUDPTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissi } } - m_rtpsock = new QUdpSocket(); - - // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket - if (params->GetRTCPMultiplexing()) + if (params->GetUseExistingSockets(&m_rtpsock, &m_rtcpsock)) { - m_rtcpsock = m_rtpsock; - m_rtcpPort = m_rtpPort; - } else { - m_rtcpsock = new QUdpSocket(); + m_deletesocketswhendone = false; + } + else + { + m_deletesocketswhendone = true; + + m_rtpsock = new QUdpSocket(); + + // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket + if (params->GetRTCPMultiplexing()) + { + m_rtcpsock = m_rtpsock; + m_rtcpPort = m_rtpPort; + } else { + m_rtcpsock = new QUdpSocket(); + } } // set socket buffer sizes @@ -174,17 +184,6 @@ int RTPUDPTransmitter::BindSockets() return 0; } -void RTPUDPTransmitter::moveToThread(QThread *thread) -{ - if (m_rtpsock) { - m_rtpsock->moveToThread(thread); - } - - if (m_rtpsock != m_rtcpsock) { - m_rtcpsock->moveToThread(thread); - } -} - void RTPUDPTransmitter::Destroy() { if (!m_init) { @@ -196,11 +195,14 @@ void RTPUDPTransmitter::Destroy() return; } - if (m_rtpsock != m_rtcpsock) { - delete m_rtcpsock; - } + if (m_deletesocketswhendone) + { + if (m_rtpsock != m_rtcpsock) { + delete m_rtcpsock; + } - delete m_rtpsock; + delete m_rtpsock; + } m_created = false; } diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h index add4561ee..223810a58 100644 --- a/qrtplib/rtpudptransmitter.h +++ b/qrtplib/rtpudptransmitter.h @@ -142,6 +142,16 @@ public: m_forcedrtcpport = rtcpport; } + /** Use sockets that have already been created, no checks on port numbers + * will be done, and no buffer sizes will be set; you'll need to close + * the sockets yourself when done, it will **not** be done automatically. */ + void SetUseExistingSockets(QUdpSocket *rtpsocket, QUdpSocket *rtcpsocket) + { + m_rtpsock = rtpsocket; + m_rtcpsock = rtcpsocket; + m_useexistingsockets = true; + } + /** Returns the RTP socket's send buffer size. */ int GetRTPSendBufferSize() const { @@ -184,6 +194,20 @@ public: return m_forcedrtcpport; } + /** Returns true and fills in sockets if existing sockets were set + * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ + bool GetUseExistingSockets(QUdpSocket **rtpsocket, QUdpSocket **rtcpsocket) const + { + if (!m_useexistingsockets) { + return false; + } + + *rtpsocket = m_rtpsock; + *rtcpsocket = m_rtcpsock; + + return true; + } + private: QHostAddress m_bindAddress; QNetworkInterface m_mcastInterface; @@ -195,6 +219,7 @@ private: uint16_t m_forcedrtcpport; QUdpSocket *m_rtpsock, *m_rtcpsock; + bool m_useexistingsockets; }; inline RTPUDPTransmissionParams::RTPUDPTransmissionParams() : @@ -210,6 +235,7 @@ inline RTPUDPTransmissionParams::RTPUDPTransmissionParams() : m_forcedrtcpport = 0; m_rtpsock = 0; m_rtcpsock = 0; + m_useexistingsockets = false; } /** Additional information about the UDP over IPv4 transmitter. */ @@ -321,6 +347,7 @@ private: bool m_created; bool m_waitingfordata; QUdpSocket *m_rtpsock, *m_rtcpsock; + bool m_deletesocketswhendone; QHostAddress m_localIP; //!< from parameters bind IP QNetworkInterface m_multicastInterface; //!< from parameters multicast interface uint16_t m_rtpPort, m_rtcpPort; diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index e2f982b5c..85ee786ac 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -16,34 +16,36 @@ /////////////////////////////////////////////////////////////////////////////////// #include "audionetsink.h" -#include "util/udpsink.h" #include "util/rtpsink.h" +#include +#include + const int AudioNetSink::m_udpBlockSize = 512; AudioNetSink::AudioNetSink(QObject *parent, bool stereo) : m_type(SinkUDP), - m_udpBufferAudioMono(0), - m_rtpBufferAudio(0) + m_rtpBufferAudio(0), + m_bufferIndex(0), + m_port(9998) { - m_udpBufferAudioMono = new UDPSink(parent, m_udpBlockSize); - //m_rtpBufferAudio = new RTPSink("127.0.0.1", 9999, stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono); + m_udpSocket = new QUdpSocket(parent); + m_rtpBufferAudio = new RTPSink(m_udpSocket, stereo); } AudioNetSink::~AudioNetSink() { - if (m_udpBufferAudioMono) { - delete m_udpBufferAudioMono; - } if (m_rtpBufferAudio) { delete m_rtpBufferAudio; } + + m_udpSocket->deleteLater(); // this thread is not the owner thread (was moved) } -//bool AudioNetSink::isRTPCapable() const -//{ -// return m_rtpBufferAudio->isValid(); -//} +bool AudioNetSink::isRTPCapable() const +{ + return m_rtpBufferAudio->isValid(); +} bool AudioNetSink::selectType(SinkType type) { @@ -65,9 +67,9 @@ bool AudioNetSink::selectType(SinkType type) void AudioNetSink::setDestination(const QString& address, uint16_t port) { - if (m_udpBufferAudioMono) { - m_udpBufferAudioMono->setDestination(address, port); - } + m_address.setAddress(const_cast(address)); + m_port = port; + if (m_rtpBufferAudio) { m_rtpBufferAudio->setDestination(address, port); } @@ -89,21 +91,28 @@ void AudioNetSink::deleteDestination(const QString& address, uint16_t port) void AudioNetSink::write(qint16 sample) { - if (m_type == SinkUDP) { - m_udpBufferAudioMono->write(sample); - } else if (m_type == SinkRTP) { + if (m_type == SinkUDP) + { + if (m_bufferIndex >= m_udpBlockSize) + { + m_udpSocket->writeDatagram((const char*)m_data, (qint64 ) m_udpBlockSize, m_address, m_port); + m_bufferIndex = 0; + } + else + { + qint16 *p = (qint16*) &m_data[m_bufferIndex]; + *p = sample; + m_bufferIndex += sizeof(qint16); + } + } + else if (m_type == SinkRTP) + { m_rtpBufferAudio->write((uint8_t *) &sample); } } void AudioNetSink::moveToThread(QThread *thread) { - if (m_udpBufferAudioMono) { - m_udpBufferAudioMono->moveToThread(thread); - } - - if (m_rtpBufferAudio) { - m_rtpBufferAudio->moveToThread(thread); - } + m_udpSocket->moveToThread(thread); } diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index 9a8c9ca2b..e0e08dfc7 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -21,10 +21,11 @@ #include "dsp/dsptypes.h" #include "util/export.h" -#include #include +#include +#include -template class UDPSink; +class QUdpSocket; class RTPSink; class QThread; @@ -45,7 +46,7 @@ public: void write(qint16 sample); - bool isRTPCapable() const { return false; } + bool isRTPCapable() const; bool selectType(SinkType type); void moveToThread(QThread *thread); @@ -54,8 +55,12 @@ public: protected: SinkType m_type; - UDPSink *m_udpBufferAudioMono; + QUdpSocket *m_udpSocket; RTPSink *m_rtpBufferAudio; + char m_data[65536]; + unsigned int m_bufferIndex; + QHostAddress m_address; + unsigned int m_port; }; diff --git a/sdrbase/util/rtpsink.cpp b/sdrbase/util/rtpsink.cpp index 76cba88d8..67232eb4e 100644 --- a/sdrbase/util/rtpsink.cpp +++ b/sdrbase/util/rtpsink.cpp @@ -19,19 +19,20 @@ #include "dsp/dsptypes.h" #include -RTPSink::RTPSink(const QString& address, uint16_t port, PayloadType payloadType) : - m_payloadType(payloadType), +RTPSink::RTPSink(QUdpSocket *udpSocket, bool stereo) : + m_payloadType(stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono), m_sampleRate(48000), m_sampleBytes(0), m_packetSamples(0), m_bufferSize(0), m_sampleBufferIndex(0), m_byteBuffer(0), - m_destport(port), + m_destport(9998), m_mutex(QMutex::Recursive) { m_rtpSessionParams.SetOwnTimestampUnit(1.0 / (double) m_sampleRate); m_rtpTransmissionParams.SetRTCPMultiplexing(true); // do not allocate another socket for RTCP + m_rtpTransmissionParams.SetUseExistingSockets(udpSocket, udpSocket); int status = m_rtpTransmitter.Init(); if (status < 0) { @@ -52,14 +53,12 @@ RTPSink::RTPSink(const QString& address, uint16_t port, PayloadType payloadType) qDebug("RTPSink::RTPSink: created session: %s", qrtplib::RTPGetErrorString(status).c_str()); } - setPayloadType(payloadType); + setPayloadType(m_payloadType); m_valid = true; uint32_t endianTest32 = 1; uint8_t *ptr = (uint8_t*) &endianTest32; m_endianReverse = (*ptr == 1); - - m_destip.setAddress(address); } RTPSink::~RTPSink() diff --git a/sdrbase/util/rtpsink.h b/sdrbase/util/rtpsink.h index 051316d65..0c4666a88 100644 --- a/sdrbase/util/rtpsink.h +++ b/sdrbase/util/rtpsink.h @@ -33,6 +33,8 @@ #include "util/export.h" +class QUdpSocket; + class RTPSink { public: @@ -42,7 +44,7 @@ public: PayloadL16Stereo, } PayloadType; - RTPSink(const QString& address, uint16_t port, PayloadType payloadType = PayloadL16Mono); + RTPSink(QUdpSocket *udpSocket, bool stereo); ~RTPSink(); bool isValid() const { return m_valid; } @@ -55,10 +57,6 @@ public: void write(const uint8_t *sampleByte); void write(const uint8_t *sampleByte, int nbSamples); - void moveToThread(QThread *thread) { - m_rtpTransmitter.moveToThread(thread); - } - protected: /** Reverse endianess in destination buffer */ static void writeNetBuf(uint8_t *dest, const uint8_t *src, unsigned int elemLen, unsigned int bytesLen, bool endianReverse); From d34ff61032ffd6646848407ba64ca56a261797ff Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Mar 2018 08:59:17 +0100 Subject: [PATCH 070/956] DSD demod: implement RTP over UDP for audio copy (part 2) --- plugins/channelrx/demoddsd/dsddemod.cpp | 48 +++++++++++++++++++++---- plugins/channelrx/demoddsd/dsddemod.h | 3 +- plugins/channelrx/demodwfm/wfmdemod.h | 1 + sdrbase/audio/audiofifo.cpp | 12 ++++--- sdrbase/audio/audiofifo.h | 10 ++++-- sdrbase/audio/audionetsink.cpp | 35 ++++++++++++++++++ sdrbase/audio/audionetsink.h | 2 ++ 7 files changed, 97 insertions(+), 14 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 34b3e86e1..4c0e57874 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -22,6 +22,7 @@ #include #include "audio/audiooutput.h" +#include "audio/audionetsink.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" @@ -74,9 +75,14 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : DSPEngine::instance()->addAudioSink(&m_audioFifo1); DSPEngine::instance()->addAudioSink(&m_audioFifo2); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); - m_audioFifo1.setUDPSink(m_udpBufferAudio); - m_audioFifo2.setUDPSink(m_udpBufferAudio); +// m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); +// m_audioFifo1.setUDPSink(m_udpBufferAudio); +// m_audioFifo2.setUDPSink(m_udpBufferAudio); + m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically + m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); + m_audioNetSink->setStereo(true); + m_audioFifo1.setAudioNetSink(m_audioNetSink); + m_audioFifo2.setAudioNetSink(m_audioNetSink); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); @@ -92,7 +98,8 @@ DSDDemod::~DSDDemod() delete[] m_sampleBuffer; DSPEngine::instance()->removeAudioSink(&m_audioFifo1); DSPEngine::instance()->removeAudioSink(&m_audioFifo2); - delete m_udpBufferAudio; +// delete m_udpBufferAudio; + delete m_audioNetSink; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); @@ -363,6 +370,14 @@ bool DSDDemod::handleMessage(const Message& cmd) m_dsdDecoder.setMyPoint(cfg.getMyLatitude(), cfg.getMyLongitude()); return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("DSDDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + m_audioNetSink->moveToThread(const_cast(thread)); // use the thread for udp sinks + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -477,8 +492,9 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) if ((settings.m_udpAddress != m_settings.m_udpAddress) || (settings.m_udpPort != m_settings.m_udpPort) || force) { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); +// m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); +// m_udpBufferAudio->setPort(settings.m_udpPort); + m_audioNetSink->setDestination(settings.m_udpAddress, settings.m_udpPort); } if ((settings.m_copyAudioToUDP != m_settings.m_copyAudioToUDP) @@ -489,6 +505,26 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) m_audioFifo2.setCopyToUDP(settings.m_slot2On && !settings.m_slot1On && settings.m_copyAudioToUDP); } + if ((settings.m_copyAudioUseRTP != m_settings.m_copyAudioUseRTP) || force) + { + if (settings.m_copyAudioUseRTP) + { + if (m_audioNetSink->selectType(AudioNetSink::SinkRTP)) { + qDebug("DSDDemod::applySettings: set audio sink to RTP mode"); + } else { + qWarning("DSDDemod::applySettings: RTP support for audio sink not available. Fall back too UDP"); + } + } + else + { + if (m_audioNetSink->selectType(AudioNetSink::SinkUDP)) { + qDebug("DSDDemod::applySettings: set audio sink to UDP mode"); + } else { + qWarning("DSDDemod::applySettings: failed to set audio sink to UDP mode"); + } + } + } + if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) { m_dsdDecoder.useHPMbelib(settings.m_highPassFilter); diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index d4b91e568..046bab152 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -199,7 +199,8 @@ private: QMutex m_settingsMutex; PhaseDiscriminators m_phaseDiscri; - UDPSink *m_udpBufferAudio; + //UDPSink *m_udpBufferAudio; + AudioNetSink *m_audioNetSink; static const int m_udpBlockSize; diff --git a/plugins/channelrx/demodwfm/wfmdemod.h b/plugins/channelrx/demodwfm/wfmdemod.h index 507b2a8f6..4cd82576c 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.h +++ b/plugins/channelrx/demodwfm/wfmdemod.h @@ -31,6 +31,7 @@ #include "dsp/phasediscri.h" #include "audio/audiofifo.h" #include "util/message.h" +#include "util/udpsink.h" #include "wfmdemodsettings.h" diff --git a/sdrbase/audio/audiofifo.cpp b/sdrbase/audio/audiofifo.cpp index 78bdf4ea0..1a412d454 100644 --- a/sdrbase/audio/audiofifo.cpp +++ b/sdrbase/audio/audiofifo.cpp @@ -19,13 +19,15 @@ #include #include "dsp/dsptypes.h" #include "audio/audiofifo.h" +#include "audio/audionetsink.h" #define MIN(x, y) ((x) < (y) ? (x) : (y)) AudioFifo::AudioFifo() : m_fifo(0), m_sampleSize(sizeof(AudioSample)), - m_udpSink(0), + //m_udpSink(0), + m_audioNetSink(0), m_copyToUDP(false) { m_size = 0; @@ -37,7 +39,8 @@ AudioFifo::AudioFifo() : AudioFifo::AudioFifo(uint32_t numSamples) : m_fifo(0), m_sampleSize(sizeof(AudioSample)), - m_udpSink(0), + //m_udpSink(0), + m_audioNetSink(0), m_copyToUDP(false) { QMutexLocker mutexLocker(&m_mutex); @@ -75,9 +78,10 @@ uint AudioFifo::write(const quint8* data, uint32_t numSamples, int timeout_ms) uint32_t remaining; uint32_t copyLen; - if (m_copyToUDP && m_udpSink) + if (m_copyToUDP && m_audioNetSink) { - m_udpSink->write((AudioSample *) data, numSamples); + //m_udpSink->write((AudioSample *) data, numSamples); + m_audioNetSink->write((AudioSample *) data, numSamples); } if(m_fifo == 0) diff --git a/sdrbase/audio/audiofifo.h b/sdrbase/audio/audiofifo.h index 0fc452299..fb2760270 100644 --- a/sdrbase/audio/audiofifo.h +++ b/sdrbase/audio/audiofifo.h @@ -24,7 +24,9 @@ #include "dsp/dsptypes.h" #include "util/export.h" -#include "util/udpsink.h" +//#include "util/udpsink.h" + +class AudioNetSink; class SDRBASE_API AudioFifo : public QObject { Q_OBJECT @@ -47,7 +49,8 @@ public: inline bool isFull() const { return m_fill == m_size; } inline uint32_t size() const { return m_size; } - void setUDPSink(UDPSink *udpSink) { m_udpSink = udpSink; } + //void setUDPSink(UDPSink *udpSink) { m_udpSink = udpSink; } + void setAudioNetSink(AudioNetSink *audioNetSink) { m_audioNetSink = audioNetSink; } void setCopyToUDP(bool copyToUDP) { m_copyToUDP = copyToUDP; } private: @@ -67,7 +70,8 @@ private: QWaitCondition m_writeWaitCondition; QWaitCondition m_readWaitCondition; - UDPSink *m_udpSink; + //UDPSink *m_udpSink; + AudioNetSink *m_audioNetSink; bool m_copyToUDP; bool create(uint32_t numSamples); diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index 85ee786ac..e128cd631 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -89,6 +89,11 @@ void AudioNetSink::deleteDestination(const QString& address, uint16_t port) } } +void AudioNetSink::setStereo(bool stereo) +{ + m_rtpBufferAudio->setPayloadType(stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Stereo); +} + void AudioNetSink::write(qint16 sample) { if (m_type == SinkUDP) @@ -111,6 +116,36 @@ void AudioNetSink::write(qint16 sample) } } +void AudioNetSink::write(AudioSample* samples, uint32_t numSamples) +{ + if (m_type == SinkUDP) + { + int samplesIndex = 0; + + if (m_bufferIndex + numSamples*sizeof(AudioSample) >= m_udpBlockSize) // fill remainder of buffer and send it + { + memcpy(&m_data[m_bufferIndex], &samples[samplesIndex], m_udpBlockSize - m_bufferIndex); // fill remainder of buffer + m_udpSocket->writeDatagram((const char*)m_data, (qint64 ) m_udpBlockSize, m_address, m_port); + m_bufferIndex = 0; + samplesIndex += (m_udpBlockSize - m_bufferIndex) / sizeof(AudioSample); + numSamples -= (m_udpBlockSize - m_bufferIndex) / sizeof(AudioSample); + } + + while (numSamples > m_udpBlockSize/sizeof(AudioSample)) // send directly from input without buffering + { + m_udpSocket->writeDatagram((const char*)&samples[samplesIndex], (qint64 ) m_udpBlockSize, m_address, m_port); + samplesIndex += m_udpBlockSize/sizeof(AudioSample); + numSamples -= m_udpBlockSize/sizeof(AudioSample); + } + + memcpy(&m_data[m_bufferIndex], &samples[samplesIndex], numSamples*sizeof(AudioSample)); + } + else if (m_type == SinkRTP) + { + m_rtpBufferAudio->write((uint8_t *) samples, numSamples*sizeof(AudioSample)); + } +} + void AudioNetSink::moveToThread(QThread *thread) { m_udpSocket->moveToThread(thread); diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index e0e08dfc7..23cfe93a3 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -43,8 +43,10 @@ public: void setDestination(const QString& address, uint16_t port); void addDestination(const QString& address, uint16_t port); void deleteDestination(const QString& address, uint16_t port); + void setStereo(bool stereo); void write(qint16 sample); + void write(AudioSample* samples, uint32_t numSamples); bool isRTPCapable() const; bool selectType(SinkType type); From f3d9f6191e38765b6f24f7bf497201d9cfd77cf1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Mar 2018 17:08:36 +0100 Subject: [PATCH 071/956] Windows build: fixes to compile RTP support --- CMakeLists.txt | 1 - sdrbase/sdrbase.pro | 4 ++++ windows.install.bat | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca4c02edf..d83455f09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,6 @@ find_package(OpenGL REQUIRED) find_package(PkgConfig) find_package(Boost REQUIRED) find_package(FFTW3F) -find_package(JRTPLib) if (NOT BUILD_DEBIAN) find_package(LibDSDcc) diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index 9aeeb90a1..6b3262d63 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -11,6 +11,7 @@ TEMPLATE = lib TARGET = sdrbase INCLUDEPATH += $$PWD INCLUDEPATH += ../httpserver +INCLUDEPATH += ../qrtplib INCLUDEPATH += ../swagger/sdrangel/code/qt5/client DEFINES += USE_KISSFFT=1 @@ -104,6 +105,7 @@ SOURCES += audio/audiodeviceinfo.cpp\ util/message.cpp\ util/messagequeue.cpp\ util/prettyprint.cpp\ + util/rtpsink.cpp\ util/syncmessenger.cpp\ util/samplesourceserializer.cpp\ util/simpleserializer.cpp\ @@ -194,6 +196,7 @@ HEADERS += audio/audiodeviceinfo.h\ util/message.h\ util/messagequeue.h\ util/prettyprint.h\ + util/rtpsink.h\ util/syncmessenger.h\ util/samplesourceserializer.h\ util/simpleserializer.h\ @@ -205,6 +208,7 @@ HEADERS += audio/audiodeviceinfo.h\ !macx:LIBS += -L../serialdv/$${build_subdir} -lserialdv LIBS += -L../httpserver/$${build_subdir} -lhttpserver +LIBS += -L../qrtplib/$${build_subdir} -lqrtplib LIBS += -L../swagger/$${build_subdir} -lswagger RCC_BINARY_SOURCES += resources/res.qrc diff --git a/windows.install.bat b/windows.install.bat index d2719e14a..a1774d709 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -13,6 +13,7 @@ copy mbelib\%1\mbelib.dll %2 copy dsdcc\%1\dsdcc.dll %2 copy serialdv\%1\serialdv.dll %2 copy httpserver\%1\httpserver.dll %2 +copy qrtplib\%1\qrtplib.dll %2 copy swagger\%1\swagger.dll %2 copy logging\%1\logging.dll %2 copy libhackrf\%1\libhackrf.dll %2 From 5fa4454b5a73b86b01bf943356949e2b743414e5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Mar 2018 18:53:02 +0100 Subject: [PATCH 072/956] Added .vscode folder to .gitignore --- .gitignore | 41 ++++++++++++----------- plugins/channelrx/demoddsd/dsddecoder.cpp | 2 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 6615edfb7..6aa50abf1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,21 @@ -CMakeLists.txt.user* -build* -qtbuild/* -sdriq/* -presets/* -LOCAL/* -sdrangelove.supp -.cproject -.project -.pydevproject -.settings/ -*.cs -*.pro.user -.idea/* -debian/sdrangel/* -debian/sdrangel.substvars -debian/files -debian/sdrangel.debhelper.log -debian/debhelper-build-stamp -obj-x86_64-linux-gnu/* +CMakeLists.txt.user* +build* +qtbuild/* +sdriq/* +presets/* +LOCAL/* +sdrangelove.supp +.cproject +.project +.pydevproject +.vscode/ +.settings/ +*.cs +*.pro.user +.idea/* +debian/sdrangel/* +debian/sdrangel.substvars +debian/files +debian/sdrangel.debhelper.log +debian/debhelper-build-stamp +obj-x86_64-linux-gnu/* diff --git a/plugins/channelrx/demoddsd/dsddecoder.cpp b/plugins/channelrx/demoddsd/dsddecoder.cpp index c8cb55afa..9a355c3de 100644 --- a/plugins/channelrx/demoddsd/dsddecoder.cpp +++ b/plugins/channelrx/demoddsd/dsddecoder.cpp @@ -15,7 +15,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "../../channelrx/demoddsd/dsddecoder.h" +#include "dsddecoder.h" #include #include "audio/audiofifo.h" From 46f80e2a15a8f0d6cbe9b4b5a19d4a6650ad95d5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 9 Mar 2018 06:54:45 +0100 Subject: [PATCH 073/956] DSD demod: implement RTP over UDP for audio copy final --- plugins/channelrx/demoddsd/dsddemodgui.cpp | 4 ++++ sdrbase/audio/audiofifo.cpp | 17 +++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 066b470e1..ec6966bd4 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -308,6 +308,10 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); + if (!m_dsdDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->hide(); + } + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); diff --git a/sdrbase/audio/audiofifo.cpp b/sdrbase/audio/audiofifo.cpp index 1a412d454..efd24a9e6 100644 --- a/sdrbase/audio/audiofifo.cpp +++ b/sdrbase/audio/audiofifo.cpp @@ -78,12 +78,6 @@ uint AudioFifo::write(const quint8* data, uint32_t numSamples, int timeout_ms) uint32_t remaining; uint32_t copyLen; - if (m_copyToUDP && m_audioNetSink) - { - //m_udpSink->write((AudioSample *) data, numSamples); - m_audioNetSink->write((AudioSample *) data, numSamples); - } - if(m_fifo == 0) { return 0; @@ -223,6 +217,17 @@ uint AudioFifo::read(quint8* data, uint32_t numSamples, int timeout_ms) copyLen = MIN(remaining, m_fill); copyLen = MIN(copyLen, m_size - m_head); memcpy(data, m_fifo + (m_head * m_sampleSize), copyLen * m_sampleSize); + + if (m_copyToUDP && m_audioNetSink) + { + for (quint8 *p = data; p < data + copyLen* m_sampleSize;) + { + AudioSample *a = (AudioSample *) p; + m_audioNetSink->write((a->l + a->r)/2); + p += m_sampleSize; + } + } + m_head += copyLen; m_head %= m_size; m_fill -= copyLen; From 4f9a49cf02d9076dd9cd0fd3285035969995b18b Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 9 Mar 2018 13:48:14 +0100 Subject: [PATCH 074/956] Avoid useless sdrbase dependency by moving the exports header in its own folder --- CMakeLists.txt | 1 + {sdrbase => exports}/util/export.h | 0 httpserver/CMakeLists.txt | 2 +- logging/CMakeLists.txt | 2 +- pluginssrv/samplesink/filesink/CMakeLists.txt | 1 + pluginssrv/samplesink/hackrfoutput/CMakeLists.txt | 2 ++ pluginssrv/samplesink/limesdroutput/CMakeLists.txt | 2 ++ pluginssrv/samplesource/filesource/CMakeLists.txt | 1 + pluginssrv/samplesource/hackrfinput/CMakeLists.txt | 2 ++ pluginssrv/samplesource/limesdrinput/CMakeLists.txt | 4 +++- pluginssrv/samplesource/rtlsdr/CMakeLists.txt | 2 ++ qrtplib/CMakeLists.txt | 2 +- sdrbase/CMakeLists.txt | 3 ++- sdrgui/CMakeLists.txt | 1 + sdrsrv/CMakeLists.txt | 1 + 15 files changed, 21 insertions(+), 5 deletions(-) rename {sdrbase => exports}/util/export.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d83455f09..05203f365 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,6 +221,7 @@ add_subdirectory(swagger) include_directories( ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_SOURCE_DIR}/sdrgui ${CMAKE_SOURCE_DIR}/logging diff --git a/sdrbase/util/export.h b/exports/util/export.h similarity index 100% rename from sdrbase/util/export.h rename to exports/util/export.h diff --git a/httpserver/CMakeLists.txt b/httpserver/CMakeLists.txt index 089ae7d9e..72721e406 100644 --- a/httpserver/CMakeLists.txt +++ b/httpserver/CMakeLists.txt @@ -33,7 +33,7 @@ set(httpserver_HEADERS include_directories( . - ${CMAKE_SOURCE_DIR}/sdrbase + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/logging/CMakeLists.txt b/logging/CMakeLists.txt index c3e3d18f5..b26835edb 100644 --- a/logging/CMakeLists.txt +++ b/logging/CMakeLists.txt @@ -19,7 +19,7 @@ set(httpserver_HEADERS include_directories( . - ${CMAKE_SOURCE_DIR}/sdrbase + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/pluginssrv/samplesink/filesink/CMakeLists.txt b/pluginssrv/samplesink/filesink/CMakeLists.txt index cfaf7ddfb..b224a7b0b 100644 --- a/pluginssrv/samplesink/filesink/CMakeLists.txt +++ b/pluginssrv/samplesink/filesink/CMakeLists.txt @@ -20,6 +20,7 @@ set(filesink_HEADERS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) diff --git a/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt b/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt index e96887267..ab42a142b 100644 --- a/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt +++ b/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt @@ -21,6 +21,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIBHACKRFSRC} @@ -30,6 +31,7 @@ else (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIBHACKRF_INCLUDE_DIR} diff --git a/pluginssrv/samplesink/limesdroutput/CMakeLists.txt b/pluginssrv/samplesink/limesdroutput/CMakeLists.txt index 1fdde5e2a..e8c5f9113 100644 --- a/pluginssrv/samplesink/limesdroutput/CMakeLists.txt +++ b/pluginssrv/samplesink/limesdroutput/CMakeLists.txt @@ -21,6 +21,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIBLIMESUITESRC}/src @@ -37,6 +38,7 @@ else (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIMESUITE_INCLUDE_DIR} diff --git a/pluginssrv/samplesource/filesource/CMakeLists.txt b/pluginssrv/samplesource/filesource/CMakeLists.txt index 72c4f4250..0f6e72d72 100644 --- a/pluginssrv/samplesource/filesource/CMakeLists.txt +++ b/pluginssrv/samplesource/filesource/CMakeLists.txt @@ -19,6 +19,7 @@ set(filesource_HEADERS include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) diff --git a/pluginssrv/samplesource/hackrfinput/CMakeLists.txt b/pluginssrv/samplesource/hackrfinput/CMakeLists.txt index d1c67c8c5..66f9d5673 100644 --- a/pluginssrv/samplesource/hackrfinput/CMakeLists.txt +++ b/pluginssrv/samplesource/hackrfinput/CMakeLists.txt @@ -20,6 +20,7 @@ set(hackrfinput_HEADERS if (BUILD_DEBIAN) include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices @@ -29,6 +30,7 @@ include_directories( else (BUILD_DEBIAN) include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices diff --git a/pluginssrv/samplesource/limesdrinput/CMakeLists.txt b/pluginssrv/samplesource/limesdrinput/CMakeLists.txt index b24dc0d4c..b92ca4b8e 100644 --- a/pluginssrv/samplesource/limesdrinput/CMakeLists.txt +++ b/pluginssrv/samplesource/limesdrinput/CMakeLists.txt @@ -21,6 +21,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIBLIMESUITESRC}/src @@ -36,7 +37,8 @@ include_directories( else (BUILD_DEBIAN) include_directories( . - ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIMESUITE_INCLUDE_DIR} diff --git a/pluginssrv/samplesource/rtlsdr/CMakeLists.txt b/pluginssrv/samplesource/rtlsdr/CMakeLists.txt index 13462859b..8e15b3095 100644 --- a/pluginssrv/samplesource/rtlsdr/CMakeLists.txt +++ b/pluginssrv/samplesource/rtlsdr/CMakeLists.txt @@ -21,6 +21,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBRTLSDRSRC}/include ${LIBRTLSDRSRC}/src @@ -29,6 +30,7 @@ else (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBRTLSDR_INCLUDE_DIR} ) diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index a5472b153..7bd992492 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -74,7 +74,7 @@ set(qrtplib_SOURCES include_directories( . - ${CMAKE_SOURCE_DIR}/sdrbase + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index ff2597f76..30699a609 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -168,7 +168,7 @@ set(sdrbase_HEADERS util/CRC64.h util/db.h util/doublebuffer.h - util/export.h + #util/export.h util/fixedtraits.h util/message.h util/messagequeue.h @@ -258,6 +258,7 @@ add_library(sdrbase SHARED include_directories( ${CMAKE_CURRENT_BINARY_DIR} . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/httpserver ${CMAKE_SOURCE_DIR}/qrtplib ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index f8a5874a1..940a75881 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -160,6 +160,7 @@ add_library(sdrgui SHARED include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_SOURCE_DIR}/logging ${CMAKE_SOURCE_DIR}/httpserver diff --git a/sdrsrv/CMakeLists.txt b/sdrsrv/CMakeLists.txt index 1dec81abf..db0a2f090 100644 --- a/sdrsrv/CMakeLists.txt +++ b/sdrsrv/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(sdrsrv SHARED include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_SOURCE_DIR}/logging ${CMAKE_SOURCE_DIR}/httpserver From e8e2176529882db5d06265a12179efe9eee206f2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 9 Mar 2018 14:07:39 +0100 Subject: [PATCH 075/956] Removed sdrangel_EXPORTS --- exports/util/export.h | 2 +- sdrbase/CMakeLists.txt | 2 +- sdrgui/CMakeLists.txt | 2 +- sdrsrv/CMakeLists.txt | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/exports/util/export.h b/exports/util/export.h index 34e3c2830..dea1f929c 100644 --- a/exports/util/export.h +++ b/exports/util/export.h @@ -33,7 +33,7 @@ /* The 'SDRBASE_API' controls the import/export of 'sdrbase' symbols and classes. */ #if !defined(sdrangel_STATIC) -# if defined sdrangel_EXPORTS || defined sdrbase_EXPORTS +# if defined sdrbase_EXPORTS # define SDRBASE_API __SDR_EXPORT # else # define SDRBASE_API __SDR_IMPORT diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 30699a609..9217c8647 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -283,7 +283,7 @@ if (BUILD_DEBIAN) target_link_libraries(sdrbase serialdv) endif (BUILD_DEBIAN) -set_target_properties(sdrbase PROPERTIES DEFINE_SYMBOL "sdrangel_EXPORTS") +set_target_properties(sdrbase PROPERTIES DEFINE_SYMBOL "sdrbase_EXPORTS") target_compile_features(sdrbase PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 qt5_use_modules(sdrbase Core Multimedia) diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 940a75881..32a4aa078 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -176,7 +176,7 @@ target_link_libraries(sdrgui logging ) -set_target_properties(sdrgui PROPERTIES DEFINE_SYMBOL "sdrangel_EXPORTS") +set_target_properties(sdrgui PROPERTIES DEFINE_SYMBOL "sdrgui_EXPORTS") target_compile_features(sdrgui PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 qt5_use_modules(sdrgui Core Widgets OpenGL Multimedia) diff --git a/sdrsrv/CMakeLists.txt b/sdrsrv/CMakeLists.txt index db0a2f090..d06292637 100644 --- a/sdrsrv/CMakeLists.txt +++ b/sdrsrv/CMakeLists.txt @@ -42,7 +42,6 @@ target_link_libraries(sdrsrv logging ) -set_target_properties(sdrsrv PROPERTIES DEFINE_SYMBOL "sdrangel_EXPORTS") target_compile_features(sdrsrv PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 qt5_use_modules(sdrsrv Core Multimedia) From b183a66d297a91561a4709f9e8ac8158d5cceef9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 10 Mar 2018 05:49:18 +0100 Subject: [PATCH 076/956] RTP: corrections for stereo --- sdrbase/audio/audionetsink.cpp | 2 +- sdrbase/util/rtpsink.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index e128cd631..80b8fbe0c 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -142,7 +142,7 @@ void AudioNetSink::write(AudioSample* samples, uint32_t numSamples) } else if (m_type == SinkRTP) { - m_rtpBufferAudio->write((uint8_t *) samples, numSamples*sizeof(AudioSample)); + m_rtpBufferAudio->write((uint8_t *) samples, numSamples*2); // 2 x 16 bit sample } } diff --git a/sdrbase/util/rtpsink.cpp b/sdrbase/util/rtpsink.cpp index 67232eb4e..b3cbf5871 100644 --- a/sdrbase/util/rtpsink.cpp +++ b/sdrbase/util/rtpsink.cpp @@ -73,6 +73,7 @@ RTPSink::~RTPSink() void RTPSink::setPayloadType(PayloadType payloadType) { + uint32_t timestampinc; QMutexLocker locker(&m_mutex); qDebug("RTPSink::setPayloadType: %d", payloadType); @@ -83,12 +84,14 @@ void RTPSink::setPayloadType(PayloadType payloadType) m_sampleRate = 48000; m_sampleBytes = sizeof(AudioSample); m_rtpSession.SetDefaultPayloadType(96); + timestampinc = m_sampleRate / 100; break; case PayloadL16Mono: default: m_sampleRate = 48000; m_sampleBytes = sizeof(int16_t); m_rtpSession.SetDefaultPayloadType(96); + timestampinc = m_sampleRate / 50; break; } @@ -121,12 +124,12 @@ void RTPSink::setPayloadType(PayloadType payloadType) qDebug("RTPSink::setPayloadType: set default mark to false: %s", qrtplib::RTPGetErrorString(status).c_str()); } - status = m_rtpSession.SetDefaultTimestampIncrement(m_packetSamples); + status = m_rtpSession.SetDefaultTimestampIncrement(timestampinc); if (status < 0) { qCritical("RTPSink::setPayloadType: cannot set default timestamp increment: %s", qrtplib::RTPGetErrorString(status).c_str()); } else { - qDebug("RTPSink::setPayloadType: set default timestamp increment to %d: %s", m_packetSamples, qrtplib::RTPGetErrorString(status).c_str()); + qDebug("RTPSink::setPayloadType: set default timestamp increment to %d: %s", timestampinc, qrtplib::RTPGetErrorString(status).c_str()); } status = m_rtpSession.SetMaximumPacketSize(m_bufferSize+40); From 7bf925203971a0743a38c79bf0b43c0b36ff99e4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 10 Mar 2018 06:13:15 +0100 Subject: [PATCH 077/956] WFM demod: implement RTP over UDP for audio copy --- plugins/channelrx/demodwfm/wfmdemod.cpp | 45 ++++++++++++++++--- plugins/channelrx/demodwfm/wfmdemod.h | 5 ++- plugins/channelrx/demodwfm/wfmdemodgui.cpp | 14 ++++++ plugins/channelrx/demodwfm/wfmdemodgui.h | 1 + plugins/channelrx/demodwfm/wfmdemodgui.ui | 10 +++++ .../channelrx/demodwfm/wfmdemodsettings.cpp | 1 + plugins/channelrx/demodwfm/wfmdemodsettings.h | 1 + 7 files changed, 71 insertions(+), 6 deletions(-) diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index 66d2dd2f5..8aad0c527 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -25,6 +25,7 @@ #include "dsp/threadedbasebandsamplesink.h" #include "device/devicesourceapi.h" #include "audio/audiooutput.h" +#include "audio/audionetsink.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" @@ -59,7 +60,8 @@ WFMDemod::WFMDemod(DeviceSourceAPI* deviceAPI) : m_audioBufferFill = 0; DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); + m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically + m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); @@ -78,13 +80,17 @@ WFMDemod::~WFMDemod() } DSPEngine::instance()->removeAudioSink(&m_audioFifo); + delete m_audioNetSink; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; +} - delete m_udpBufferAudio; +bool WFMDemod::isAudioNetSinkRTPCapable() const +{ + return m_audioNetSink && m_audioNetSink->isRTPCapable(); } void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) @@ -148,7 +154,9 @@ void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; - if (m_settings.m_copyAudioToUDP) { m_udpBufferAudio->write(sample); } + if (m_settings.m_copyAudioToUDP) { + m_audioNetSink->write(sample); + } ++m_audioBufferFill; @@ -232,6 +240,14 @@ bool WFMDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("WFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + m_audioNetSink->moveToThread(const_cast(thread)); // use the thread for udp sinks + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -315,8 +331,27 @@ void WFMDemod::applySettings(const WFMDemodSettings& settings, bool force) if ((m_settings.m_udpAddress != settings.m_udpAddress) || (m_settings.m_udpPort != settings.m_udpPort) || force) { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); + m_audioNetSink->setDestination(settings.m_udpAddress, settings.m_udpPort); + } + + if ((settings.m_copyAudioUseRTP != m_settings.m_copyAudioUseRTP) || force) + { + if (settings.m_copyAudioUseRTP) + { + if (m_audioNetSink->selectType(AudioNetSink::SinkRTP)) { + qDebug("WFMDemod::applySettings: set audio sink to RTP mode"); + } else { + qWarning("WFMDemod::applySettings: RTP support for audio sink not available. Fall back too UDP"); + } + } + else + { + if (m_audioNetSink->selectType(AudioNetSink::SinkUDP)) { + qDebug("WFMDemod::applySettings: set audio sink to UDP mode"); + } else { + qWarning("WFMDemod::applySettings: failed to set audio sink to UDP mode"); + } + } } m_settings = settings; diff --git a/plugins/channelrx/demodwfm/wfmdemod.h b/plugins/channelrx/demodwfm/wfmdemod.h index 4cd82576c..e87dc3904 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.h +++ b/plugins/channelrx/demodwfm/wfmdemod.h @@ -40,6 +40,7 @@ class ThreadedBasebandSampleSink; class DownChannelizer; class DeviceSourceAPI; +class AudioNetSink; class WFMDemod : public BasebandSampleSink, public ChannelSinkAPI { public: @@ -119,6 +120,8 @@ public: m_magsqCount = 0; } + bool isAudioNetSinkRTPCapable() const; + static const QString m_channelIdURI; static const QString m_channelId; @@ -155,7 +158,7 @@ private: AudioVector m_audioBuffer; uint m_audioBufferFill; - UDPSink *m_udpBufferAudio; + AudioNetSink *m_audioNetSink; AudioFifo m_audioFifo; SampleVector m_sampleBuffer; diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.cpp b/plugins/channelrx/demodwfm/wfmdemodgui.cpp index 3280f8420..15c476568 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.cpp +++ b/plugins/channelrx/demodwfm/wfmdemodgui.cpp @@ -137,6 +137,12 @@ void WFMDemodGUI::on_copyAudioToUDP_toggled(bool checked) applySettings(); } +void WFMDemodGUI::on_useRTP_toggled(bool checked) +{ + m_settings.m_copyAudioUseRTP = checked; + applySettings(); +} + void WFMDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { } @@ -207,6 +213,10 @@ WFMDemodGUI::WFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); + if (!m_wfmDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->hide(); + } + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); @@ -269,6 +279,10 @@ void WFMDemodGUI::displaySettings() ui->squelch->setValue(m_settings.m_squelch); ui->squelchText->setText(QString("%1 dB").arg(m_settings.m_squelch)); + if (m_wfmDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->setChecked(m_settings.m_copyAudioUseRTP); + } + blockApplySettings(false); } diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.h b/plugins/channelrx/demodwfm/wfmdemodgui.h index a9b85ba32..9ac64abde 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.h +++ b/plugins/channelrx/demodwfm/wfmdemodgui.h @@ -81,6 +81,7 @@ private slots: void on_squelch_valueChanged(int value); void on_audioMute_toggled(bool checked); void on_copyAudioToUDP_toggled(bool copy); + void on_useRTP_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void tick(); diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.ui b/plugins/channelrx/demodwfm/wfmdemodgui.ui index ab6ee3888..ca63b9d3b 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.ui +++ b/plugins/channelrx/demodwfm/wfmdemodgui.ui @@ -384,6 +384,16 @@ + + + + Kiki koko kuku cacaboudin + + + R + + + diff --git a/plugins/channelrx/demodwfm/wfmdemodsettings.cpp b/plugins/channelrx/demodwfm/wfmdemodsettings.cpp index 104c293ad..c2fbb12e7 100644 --- a/plugins/channelrx/demodwfm/wfmdemodsettings.cpp +++ b/plugins/channelrx/demodwfm/wfmdemodsettings.cpp @@ -44,6 +44,7 @@ void WFMDemodSettings::resetToDefaults() m_audioMute = false; m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); m_copyAudioToUDP = false; + m_copyAudioUseRTP = false; m_udpAddress = "127.0.0.1"; m_udpPort = 9999; m_rgbColor = QColor(0, 0, 255).rgb(); diff --git a/plugins/channelrx/demodwfm/wfmdemodsettings.h b/plugins/channelrx/demodwfm/wfmdemodsettings.h index 9c643d12b..9688d8984 100644 --- a/plugins/channelrx/demodwfm/wfmdemodsettings.h +++ b/plugins/channelrx/demodwfm/wfmdemodsettings.h @@ -32,6 +32,7 @@ struct WFMDemodSettings bool m_audioMute; quint32 m_audioSampleRate; bool m_copyAudioToUDP; + bool m_copyAudioUseRTP; QString m_udpAddress; quint16 m_udpPort; quint32 m_rgbColor; From dc9cb0463faa04b022f991a6f0ec76057a59491c Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 10 Mar 2018 07:03:11 +0100 Subject: [PATCH 078/956] BFM demod: implement RTP over UDP for audio copy --- plugins/channelrx/demodbfm/bfmdemod.cpp | 50 ++++++++++++++++--- plugins/channelrx/demodbfm/bfmdemod.h | 5 +- plugins/channelrx/demodbfm/bfmdemodgui.cpp | 14 ++++++ plugins/channelrx/demodbfm/bfmdemodgui.h | 1 + plugins/channelrx/demodbfm/bfmdemodgui.ui | 10 ++++ .../channelrx/demodbfm/bfmdemodsettings.cpp | 1 + plugins/channelrx/demodbfm/bfmdemodsettings.h | 1 + plugins/channelrx/demodwfm/wfmdemod.h | 1 - plugins/channelrx/demodwfm/wfmdemodgui.ui | 2 +- 9 files changed, 73 insertions(+), 12 deletions(-) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index 7ce345792..8bf7c5b7a 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -21,6 +21,7 @@ #include #include "audio/audiooutput.h" +#include "audio/audionetsink.h" #include "dsp/dspengine.h" #include "dsp/downchannelizer.h" #include "dsp/threadedbasebandsamplesink.h" @@ -84,7 +85,9 @@ BFMDemod::BFMDemod(DeviceSourceAPI *deviceAPI) : m_audioBufferFill = 0; DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); + m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically + m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); + m_audioNetSink->setStereo(true); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); @@ -103,7 +106,7 @@ BFMDemod::~BFMDemod() } DSPEngine::instance()->removeAudioSink(&m_audioFifo); - delete m_udpBufferAudio; + delete m_audioNetSink; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); @@ -235,8 +238,10 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume); m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume); - if (m_settings.m_copyAudioToUDP) { - m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); + if (m_settings.m_copyAudioToUDP) + { + m_audioNetSink->write(m_audioBuffer[m_audioBufferFill].l); + m_audioNetSink->write(m_audioBuffer[m_audioBufferFill].r); } } else @@ -247,8 +252,10 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; - if (m_settings.m_copyAudioToUDP) { - m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); + if (m_settings.m_copyAudioToUDP) + { + m_audioNetSink->write(m_audioBuffer[m_audioBufferFill].l); + m_audioNetSink->write(m_audioBuffer[m_audioBufferFill].r); } } @@ -347,6 +354,14 @@ bool BFMDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("BFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + m_audioNetSink->moveToThread(const_cast(thread)); // use the thread for udp sinks + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -484,8 +499,27 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) if ((settings.m_udpAddress != m_settings.m_udpAddress) || (settings.m_udpPort != m_settings.m_udpPort) || force) { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); + m_audioNetSink->setDestination(settings.m_udpAddress, settings.m_udpPort); + } + + if ((settings.m_copyAudioUseRTP != m_settings.m_copyAudioUseRTP) || force) + { + if (settings.m_copyAudioUseRTP) + { + if (m_audioNetSink->selectType(AudioNetSink::SinkRTP)) { + qDebug("WFMDemod::applySettings: set audio sink to RTP mode"); + } else { + qWarning("WFMDemod::applySettings: RTP support for audio sink not available. Fall back too UDP"); + } + } + else + { + if (m_audioNetSink->selectType(AudioNetSink::SinkUDP)) { + qDebug("WFMDemod::applySettings: set audio sink to UDP mode"); + } else { + qWarning("WFMDemod::applySettings: failed to set audio sink to UDP mode"); + } + } } m_settings = settings; diff --git a/plugins/channelrx/demodbfm/bfmdemod.h b/plugins/channelrx/demodbfm/bfmdemod.h index cf0a7ee33..41a8bffb7 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.h +++ b/plugins/channelrx/demodbfm/bfmdemod.h @@ -33,7 +33,6 @@ #include "dsp/phasediscri.h" #include "audio/audiofifo.h" #include "util/message.h" -#include "util/udpsink.h" #include "rdsparser.h" #include "rdsdecoder.h" @@ -43,6 +42,7 @@ class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; +class AudioNetSink; class BFMDemod : public BasebandSampleSink, public ChannelSinkAPI { public: @@ -152,6 +152,7 @@ public: m_magsqCount = 0; } + bool isAudioNetSinkRTPCapable() const { return false; } RDSParser& getRDSParser() { return m_rdsParser; } static const QString m_channelIdURI; @@ -221,7 +222,7 @@ private: static const int default_excursion = 750000; // +/- 75 kHz PhaseDiscriminators m_phaseDiscri; - UDPSink *m_udpBufferAudio; + AudioNetSink *m_audioNetSink; static const int m_udpBlockSize; diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index ff1ebcd0b..f33253382 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -207,6 +207,12 @@ void BFMDemodGUI::on_copyAudioToUDP_toggled(bool copy) applySettings(); } +void BFMDemodGUI::on_useRTP_toggled(bool checked) +{ + m_settings.m_copyAudioUseRTP = checked; + applySettings(); +} + void BFMDemodGUI::on_showPilot_clicked() { m_settings.m_showPilot = ui->showPilot->isChecked(); @@ -375,6 +381,10 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); + if (!m_bfmDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->hide(); + } + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); @@ -456,6 +466,10 @@ void BFMDemodGUI::displaySettings() ui->rds->setChecked(m_settings.m_rdsActive); ui->copyAudioToUDP->setChecked(m_settings.m_copyAudioToUDP); + if (m_bfmDemod->isAudioNetSinkRTPCapable()) { + ui->useRTP->setChecked(m_settings.m_copyAudioUseRTP); + } + blockApplySettings(false); } diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.h b/plugins/channelrx/demodbfm/bfmdemodgui.h index a008bb22b..629a50ee0 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.h +++ b/plugins/channelrx/demodbfm/bfmdemodgui.h @@ -109,6 +109,7 @@ private slots: void on_showPilot_clicked(); void on_rds_clicked(); void on_copyAudioToUDP_toggled(bool copy); + void on_useRTP_toggled(bool checked); void on_g14ProgServiceNames_currentIndexChanged(int index); void on_clearData_clicked(bool checked); void on_g00AltFrequenciesBox_activated(int index); diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.ui b/plugins/channelrx/demodbfm/bfmdemodgui.ui index 7894e4185..d76e8c2b3 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.ui +++ b/plugins/channelrx/demodbfm/bfmdemodgui.ui @@ -321,6 +321,16 @@ + + + + Use RTP protocol for audio copy to UDP + + + R + + + diff --git a/plugins/channelrx/demodbfm/bfmdemodsettings.cpp b/plugins/channelrx/demodbfm/bfmdemodsettings.cpp index 1c6d1cd13..c7c273d5e 100644 --- a/plugins/channelrx/demodbfm/bfmdemodsettings.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodsettings.cpp @@ -47,6 +47,7 @@ void BFMDemodSettings::resetToDefaults() m_showPilot = false; m_rdsActive = false; m_copyAudioToUDP = false; + m_copyAudioUseRTP = false; m_udpAddress = "127.0.0.1"; m_udpPort = 9999; m_rgbColor = QColor(80, 120, 228).rgb(); diff --git a/plugins/channelrx/demodbfm/bfmdemodsettings.h b/plugins/channelrx/demodbfm/bfmdemodsettings.h index b79f6e114..38e1678f9 100644 --- a/plugins/channelrx/demodbfm/bfmdemodsettings.h +++ b/plugins/channelrx/demodbfm/bfmdemodsettings.h @@ -34,6 +34,7 @@ struct BFMDemodSettings bool m_showPilot; bool m_rdsActive; bool m_copyAudioToUDP; + bool m_copyAudioUseRTP; QString m_udpAddress; quint16 m_udpPort; quint32 m_rgbColor; diff --git a/plugins/channelrx/demodwfm/wfmdemod.h b/plugins/channelrx/demodwfm/wfmdemod.h index e87dc3904..4a17378b1 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.h +++ b/plugins/channelrx/demodwfm/wfmdemod.h @@ -31,7 +31,6 @@ #include "dsp/phasediscri.h" #include "audio/audiofifo.h" #include "util/message.h" -#include "util/udpsink.h" #include "wfmdemodsettings.h" diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.ui b/plugins/channelrx/demodwfm/wfmdemodgui.ui index ca63b9d3b..05ffd6164 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.ui +++ b/plugins/channelrx/demodwfm/wfmdemodgui.ui @@ -387,7 +387,7 @@ - Kiki koko kuku cacaboudin + Use RTP protocol for audio copy to UDP R From 82ec3891c53cdfcabe680d477c9090ad07597a41 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 10 Mar 2018 10:43:22 +0100 Subject: [PATCH 079/956] DATV demod: GUI changes (1) --- plugins/channelrx/demodatv/atvdemodgui.ui | 2 +- plugins/channelrx/demoddatv/datvdemodgui.cpp | 6 + plugins/channelrx/demoddatv/datvdemodgui.ui | 1623 ++++++++++-------- 3 files changed, 908 insertions(+), 723 deletions(-) diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index d48aa0577..17ac80b09 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -1011,7 +1011,7 @@ QTabWidget::West - 0 + 1 diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index 7fab97e52..1c68bf252 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -277,6 +277,12 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); m_objTimer.start(); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + + ui->rfBandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); + ui->rfBandwidth->setValueRange(7, 0, 9999999); m_objChannelMarker.blockSignals(true); m_objChannelMarker.setColor(Qt::magenta); diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index a7e2b3181..5fa342fa9 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -6,26 +6,20 @@ 0 0 - 512 - 640 + 530 + 442 - + 0 0 - 512 - 640 - - - - - 512 - 640 + 530 + 442 @@ -40,741 +34,914 @@ DATV Demodulator - + - 10 + 0 0 - 496 - 250 + 521 + 41 - - - 496 - 250 - + + RF Settings - - - 496 - 250 - - - - DATV Settings - - - - - 0 - 20 - 222 - 222 - - - - - QLayout::SetMinimumSize - - - - - - 0 - 0 - - - - - 220 - 220 - - - - - 220 - 220 - - - - Signal constellation - - - - - - - - - - - - 230 - 20 - 261 - 221 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 10 - 10 - 81 - 21 - - - - DVB Standard - - - - DVB-S - - - - - - - 10 - 40 - 80 - 21 - - - - Modulation scheme - - - - BPSK - - - - - QPSK - - - - - 8PSK - - - - - 16APSK - - - - - 32APSK - - - - - 64APSKe - - - - - 16QAM - - - - - 64QAM - - - - - 256QAM - - - - - - - 10 - 70 - 80 - 21 - - - - FEC ratio - - - - 1/2 - - - - - 2/3 - - - - - 3/4 - - - - - 5/6 - - - - - 7/8 - - - - - - - 10 - 100 - 101 - 20 - - - - Fast signal decode - - - FAST LOCK - - - - - - 140 - 120 - 81 - 20 - - - - Viterbi algorithm (CPU intensive) - - - VITERBI - - - - - - 10 - 120 - 111 - 20 - - - - Constellation hardening - - - HARD METRIC - - - - - - 100 - 40 - 61 - 21 - - - - Symbols/s - - - - - - 100 - 10 - 71 - 21 - - - - Bandwidth - - - - - - 140 - 100 - 111 - 20 - - - - Small frequency drift compensation - - - ALLOW DRIFT - - - - - - 170 - 70 - 81 - 23 - - - - Number of stray peaks to suppress - - - 32 - - - - - - 100 - 70 - 71 - 21 - - - - Notch filter - - - - - - 70 - 200 - 181 - 20 - - - - Video buffer fill - - - 0 - - - - - - 10 - 180 - 111 - 16 - - - - Total number of bytes decoded - - - - - - - - - - 230 - 120 - 21 - 22 - - - - R - - - - - - 170 - 40 - 81 - 23 - - - - Symbol rate - - - 1 - - - 1024000000 - - - 1000 - - - - - - 170 - 10 - 81 - 23 - - - - RF filter bandwidth - - - 1000 - - - 1024000000 - - - 1000 - - - - - - 130 - 180 - 121 - 16 - - - - Stream speed - - - - - - - - - - 10 - 200 - 61 - 15 - - - - Buffer: - - - - - - 10 - 150 - 91 - 22 - - - - Filter - - - - FIR LINEAR - - - - - FIR NEAREST - - - - - FIR RRC - - - - - - - 140 - 150 - 41 - 23 - - - - RRC filter roll off factor - - - 1 - - - 99 - - - 35 - - - - - - 106 - 150 - 28 - 23 - - - - R.off - - - - - - 180 - 150 - 28 - 23 - - - - Exc - - - - - - 210 - 150 - 41 - 23 - - - - Filter excursion (dB) - - - 1 - - - 99 - - - 10 - - - + + + + + + + 10 + + + 10 + + + + + dF + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + DejaVu Sans Mono + 12 + false + + + + PointingHandCursor + + + Channel center frequency shift + + + + + + + Hz + + + + + + + + + 10 + + + 10 + + + + + BW + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + DejaVu Sans Mono + 12 + false + + + + PointingHandCursor + + + RF bandwidth + + + + + + + Hz + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + - + - 10 - 250 - 496 - 385 + 0 + 40 + 526 + 400 - 496 - 385 + 526 + 400 - - - 496 - 385 - + + DATV - - VIDEO Stream + + QTabWidget::West - - - - 0 - 300 - 281 - 81 - - - - Stream information - - - true - - - false - - - - - - 400 - 350 - 91 - 27 - - - - Full screen video (click in the image to return) - - - Full Screen - - - - - - 400 - 300 - 91 - 27 - - - - Start/Stop video streaming - - - Video - - - - - - 0 - 20 - 488 - 272 - - - - - - - - 0 - 0 - + + 0 + + + + DATV + + + + + 0 + 0 + 496 + 250 + + + + + 496 + 250 + + + + + 496 + 250 + + + + DATV Settings + + + + + 0 + 20 + 222 + 222 + + + + + QLayout::SetMinimumSize - - - 480 - 270 - - - - - 355 - 270 - + + + + + 0 + 0 + + + + + 220 + 220 + + + + + 220 + 220 + + + + Signal constellation + + + + + + + + + + + + 230 + 20 + 261 + 221 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 10 + 10 + 81 + 21 + - Video + DVB Standard - - + + + DVB-S + + + + + + + 10 + 40 + 80 + 21 + + + + Modulation scheme + + + + BPSK + + + + + QPSK + + + + + 8PSK + + + + + 16APSK + + + + + 32APSK + + + + + 64APSKe + + + + + 16QAM + + + + + 64QAM + + + + + 256QAM + + + + + + + 10 + 70 + 80 + 21 + + + + FEC ratio + + + + 1/2 + + + + + 2/3 + + + + + 3/4 + + + + + 5/6 + + + + + 7/8 + + + + + + + 10 + 100 + 101 + 20 + + + + Fast signal decode + + + FAST LOCK - - + + + + 140 + 120 + 81 + 20 + + + + Viterbi algorithm (CPU intensive) + + + VITERBI + + + + + + 10 + 120 + 111 + 20 + + + + Constellation hardening + + + HARD METRIC + + + + + + 100 + 40 + 61 + 21 + + + + Symbols/s + + + + + + 100 + 10 + 71 + 21 + + + + Bandwidth + + + + + + 140 + 100 + 111 + 20 + + + + Small frequency drift compensation + + + ALLOW DRIFT + + + + + + 170 + 70 + 81 + 23 + + + + Number of stray peaks to suppress + + + 32 + + + + + + 100 + 70 + 71 + 21 + + + + Notch filter + + + + + + 70 + 200 + 181 + 20 + + + + Video buffer fill + + + 0 + + + + + + 10 + 180 + 111 + 16 + + + + Total number of bytes decoded + + + - + + + + + + 230 + 120 + 21 + 22 + + + + R + + + + + + 170 + 40 + 81 + 23 + + + + Symbol rate + + + 1 + + + 1024000000 + + + 1000 + + + + + + 170 + 10 + 81 + 23 + + + + RF filter bandwidth + + + 1000 + + + 1024000000 + + + 1000 + + + + + + 130 + 180 + 121 + 16 + + + + Stream speed + + + - + + + + + + 10 + 200 + 61 + 15 + + + + Buffer: + + + + + + 10 + 150 + 91 + 22 + + + + Filter + + + + FIR LINEAR + + + + + FIR NEAREST + + + + + FIR RRC + + + + + + + 140 + 150 + 41 + 23 + + + + RRC filter roll off factor + + + 1 + + + 99 + + + 35 + + + + + + 106 + 150 + 28 + 23 + + + + R.off + + + + + + 180 + 150 + 28 + 23 + + + + Exc + + + + + + 210 + 150 + 41 + 23 + + + + Filter excursion (dB) + + + 1 + + + 99 + + + 10 + + + + - - - false - - - - 300 - 320 - 85 - 20 - - - - Transport stream detected - - - Transport - - - true - - - - - false - - - - 300 - 340 - 85 - 20 - - - - Video data detected - - - Video - - - true - - - - - false - - - - 300 - 360 - 85 - 20 - - - - Video being decoded - - - Decoding - - - true - - - - - false - - - - 300 - 300 - 85 - 20 - - - - Data being received - - - Data - - - true - + + + Video + + + + + 0 + 0 + 496 + 385 + + + + + 496 + 385 + + + + + 496 + 385 + + + + VIDEO Stream + + + + + 0 + 300 + 281 + 81 + + + + Stream information + + + true + + + false + + + + + + 400 + 350 + 91 + 27 + + + + Full screen video (click in the image to return) + + + Full Screen + + + + + + 400 + 300 + 91 + 27 + + + + Start/Stop video streaming + + + Video + + + + + + 0 + 20 + 488 + 272 + + + + + + + + 0 + 0 + + + + + 480 + 270 + + + + + 355 + 270 + + + + Video + + + + + + + + + + + false + + + + 300 + 320 + 85 + 20 + + + + Transport stream detected + + + Transport + + + true + + + + + false + + + + 300 + 340 + 85 + 20 + + + + Video data detected + + + Video + + + true + + + + + false + + + + 300 + 360 + 85 + 20 + + + + Video being decoded + + + Decoding + + + true + + + + + false + + + + 300 + 300 + 85 + 20 + + + + Data being received + + + Data + + + true + + + @@ -796,6 +963,18 @@
datvideorender.h
1 + + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
From d1fafce4b62396fabb251b13d87992808f0f9be0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 10 Mar 2018 19:30:55 +0100 Subject: [PATCH 080/956] DATV demod: GUI changes (2) --- plugins/channelrx/demoddatv/datvdemod.cpp | 44 ++++++++++++------ plugins/channelrx/demoddatv/datvdemodgui.cpp | 49 ++++++++++++-------- plugins/channelrx/demoddatv/datvdemodgui.h | 14 +----- plugins/channelrx/demoddatv/datvdemodgui.ui | 13 +----- 4 files changed, 63 insertions(+), 57 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index d67be7c15..38590273d 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -101,7 +101,7 @@ DATVDemod::~DATVDemod() void DATVDemod::channelSampleRateChanged() { qDebug() << "DATVDemod::channelSampleRateChanged:" - << " intMsps: " << m_channelizer->getInputSampleRate(); + << " sample rate: " << m_channelizer->getInputSampleRate(); if(m_objRunning.intMsps!=m_channelizer->getInputSampleRate()) { @@ -444,18 +444,18 @@ void DATVDemod::InitDATVFramework() CleanUpDATVFramework(false); qDebug() << "DATVDemod::InitDATVParameters:" - << " - Msps: " << m_objRunning.intMsps - << " - Sample Rate: " << m_objRunning.intSampleRate - << " - Symbol Rate: " << m_objRunning.intSymbolRate - << " - Modulation: " << m_objRunning.enmModulation - << " - Notch Filters: " << m_objRunning.intNotchFilters - << " - Allow Drift: " << m_objRunning.blnAllowDrift - << " - Fast Lock: " << m_objRunning.blnFastLock - << " - Filter: " << m_objRunning.enmFilter - << " - HARD METRIC: " << m_objRunning.blnHardMetric - << " - RollOff: " << m_objRunning.fltRollOff - << " - Viterbi: " << m_objRunning.blnViterbi - << " - Excursion: " << m_objRunning.intExcursion; + << " Msps: " << m_objRunning.intMsps + << " Sample Rate: " << m_objRunning.intSampleRate + << " Symbol Rate: " << m_objRunning.intSymbolRate + << " Modulation: " << m_objRunning.enmModulation + << " Notch Filters: " << m_objRunning.intNotchFilters + << " Allow Drift: " << m_objRunning.blnAllowDrift + << " Fast Lock: " << m_objRunning.blnFastLock + << " Filter: " << m_objRunning.enmFilter + << " HARD METRIC: " << m_objRunning.blnHardMetric + << " RollOff: " << m_objRunning.fltRollOff + << " Viterbi: " << m_objRunning.blnViterbi + << " Excursion: " << m_objRunning.intExcursion; m_objCfg.standard = m_objRunning.enmStandard; @@ -935,8 +935,22 @@ bool DATVDemod::handleMessage(const Message& cmd) m_objRunning.intCenterFrequency = objCfg.m_objMsgConfig.intCenterFrequency; m_objRunning.intExcursion = objCfg.m_objMsgConfig.intExcursion; - qDebug() << "ATVDemod::handleMessage: MsgConfigureDATVDemod: sampleRate: " << m_objRunning.intMsps - << " sampleRate: " << m_objRunning.intSampleRate; + qDebug() << "ATVDemod::handleMessage: MsgConfigureDATVDemod:" + << " blnAllowDrift: " << objCfg.m_objMsgConfig.blnAllowDrift + << " intRFBandwidth: " << objCfg.m_objMsgConfig.intRFBandwidth + << " intCenterFrequency: " << objCfg.m_objMsgConfig.intCenterFrequency + << " blnFastLock: " << objCfg.m_objMsgConfig.blnFastLock + << " enmFilter: " << objCfg.m_objMsgConfig.enmFilter + << " fltRollOff: " << objCfg.m_objMsgConfig.fltRollOff + << " blnViterbi: " << objCfg.m_objMsgConfig.blnViterbi + << " enmFEC: " << objCfg.m_objMsgConfig.enmFEC + << " enmModulation: " << objCfg.m_objMsgConfig.enmModulation + << " enmStandard: " << objCfg.m_objMsgConfig.enmStandard + << " intNotchFilters: " << objCfg.m_objMsgConfig.intNotchFilters + << " intSymbolRate: " << objCfg.m_objMsgConfig.intSymbolRate + << " intRFBandwidth: " << objCfg.m_objMsgConfig.intRFBandwidth + << " intCenterFrequency: " << objCfg.m_objMsgConfig.intCenterFrequency + << " intExcursion: " << objCfg.m_objMsgConfig.intExcursion; ApplySettings(); } diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index 1c68bf252..9b31b27d1 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -92,7 +92,7 @@ void DATVDemodGUI::resetToDefaults() ui->lblStatus->setText(""); - ui->spiBandwidth->setValue(512000); + ui->rfBandwidth->setValue(512000); ui->spiSymbolRate->setValue(250000); ui->spiRollOff->setValue(35); ui->spiExcursion->setValue(10); @@ -122,7 +122,7 @@ QByteArray DATVDemodGUI::serialize() const s.writeS32(11, ui->cmbStandard->currentIndex()); s.writeS32(12, ui->spiNotchFilters->value()); - s.writeS32(13, ui->spiBandwidth->value()); + s.writeS64(13, ui->rfBandwidth->getValue()); s.writeS32(14, ui->spiSymbolRate->value()); s.writeS32(15, ui->spiExcursion->value()); @@ -143,6 +143,7 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) { QByteArray bytetmp; uint32_t u32tmp; + qint64 i64tmp; int tmp; bool booltmp; @@ -194,8 +195,8 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) d.readS32(12, &tmp, 0); ui->spiNotchFilters->setValue(tmp); - d.readS32(13, &tmp, 1024000); - ui->spiBandwidth->setValue(tmp); + d.readS64(13, &i64tmp, 5120000); + ui->rfBandwidth->setValue(i64tmp); d.readS32(14, &tmp, 250000); ui->spiSymbolRate->setValue(tmp); @@ -224,6 +225,8 @@ bool DATVDemodGUI::handleMessage(const Message& objMessage __attribute__((unused void DATVDemodGUI::channelMarkerChangedByCursor() { + ui->deltaFrequency->setValue(m_objChannelMarker.getCenterFrequency()); + if(m_intCenterFrequency!=m_objChannelMarker.getCenterFrequency()) { m_intCenterFrequency=m_objChannelMarker.getCenterFrequency(); @@ -282,7 +285,7 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); ui->rfBandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); - ui->rfBandwidth->setValueRange(7, 0, 9999999); + ui->rfBandwidth->setValueRange(true, 7, 0, 9999999); m_objChannelMarker.blockSignals(true); m_objChannelMarker.setColor(Qt::magenta); @@ -331,12 +334,13 @@ void DATVDemodGUI::applySettings() if (m_blnDoApplySettings) { + //Bandwidth and center frequency + m_objChannelMarker.setCenterFrequency(ui->deltaFrequency->getValueNew()); + m_objChannelMarker.setBandwidth(ui->rfBandwidth->getValueNew()); + DATVDemod::MsgConfigureChannelizer *msgChan = DATVDemod::MsgConfigureChannelizer::create(m_objChannelMarker.getCenterFrequency()); m_objDATVDemod->getInputMessageQueue()->push(msgChan); - //Bandwidth and center frequency - m_objChannelMarker.setBandwidth(ui->spiBandwidth->value()); - setTitleColor(m_objChannelMarker.getColor()); strStandard = ui->cmbStandard->currentText(); @@ -478,10 +482,8 @@ void DATVDemodGUI::applySettings() ui->spiExcursion->value()); qDebug() << "DATVDemodGUI::applySettings:" - << " .inputSampleRate: " << 0 /*m_objChannelizer->getInputSampleRate()*/ - << " m_objDATVDemod.sampleRate: " << m_objDATVDemod->GetSampleRate(); - - + << " m_objDATVDemod->getCenterFrequency: " << m_objDATVDemod->getCenterFrequency() + << " m_objDATVDemod->GetSampleRate: " << m_objDATVDemod->GetSampleRate(); } } @@ -715,6 +717,15 @@ void DATVDemodGUI::on_spiBandwidth_valueChanged(int arg1 __attribute__((unused)) applySettings(); } +void DATVDemodGUI::on_deltaFrequency_changed(qint64 value __attribute__((unused))) +{ + applySettings(); +} + +void DATVDemodGUI::on_rfBandwidth_changed(qint64 value __attribute__((unused))) +{ + applySettings(); +} void DATVDemodGUI::on_chkFastlock_clicked() { @@ -730,13 +741,13 @@ void DATVDemodGUI::on_StreamMetaDataChanged(DataTSMetaData2 *objMetaData) if(objMetaData->OK_TransportStream==true) { - strMetaData.sprintf("PID: %d - Width: %d - Height: %d\r\n%s%s\r\nCodec: %s\r\n",objMetaData->PID - ,objMetaData->Width - ,objMetaData->Height - ,objMetaData->Program.toStdString().c_str() - ,objMetaData->Stream.toStdString().c_str() - ,objMetaData->CodecDescription.toStdString().c_str()); - + strMetaData.sprintf("PID: %d - Width: %d - Height: %d\r\n%s%s\r\nCodec: %s\r\n", + objMetaData->PID, + objMetaData->Width, + objMetaData->Height, + objMetaData->Program.toStdString().c_str(), + objMetaData->Stream.toStdString().c_str(), + objMetaData->CodecDescription.toStdString().c_str()); } ui->textEdit->setText(strMetaData); diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h index 406197b43..da26eca9e 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.h +++ b/plugins/channelrx/demoddatv/datvdemodgui.h @@ -81,32 +81,22 @@ private slots: void on_cmbFEC_currentIndexChanged(const QString &arg1); void on_chkViterbi_clicked(); void on_chkHardMetric_clicked(); - void on_pushButton_2_clicked(); - void on_spiSymbolRate_valueChanged(int arg1); - void on_spiNotchFilters_valueChanged(int arg1); - void on_chkAllowDrift_clicked(); - void on_pushButton_3_clicked(); - void on_pushButton_4_clicked(); - void on_mouseEvent(QMouseEvent* obj); void on_StreamDataAvailable(int *intPackets, int *intBytes, int *intPercent, qint64 *intTotalReceived); void on_StreamMetaDataChanged(DataTSMetaData2 *objMetaData); - void on_spiBandwidth_valueChanged(int arg1); - void on_chkFastlock_clicked(); - void on_cmbFilter_currentIndexChanged(int index); - void on_spiRollOff_valueChanged(int arg1); - void on_spiExcursion_valueChanged(int arg1); + void on_deltaFrequency_changed(qint64 value); + void on_rfBandwidth_changed(qint64 value); private: Ui::DATVDemodGUI* ui; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index 5fa342fa9..6bd511c08 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -118,7 +118,7 @@
- + 0 @@ -138,11 +138,8 @@ false - - PointingHandCursor - - RF bandwidth + RF Bandwidth @@ -969,12 +966,6 @@
gui/valuedialz.h
1 - - ValueDial - QWidget -
gui/valuedial.h
- 1 -
From 93d0d28addbe5c5feabedbea73da1e23eca18696 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 10 Mar 2018 20:15:20 +0100 Subject: [PATCH 081/956] DATV demod: GUI changes (3) --- plugins/channelrx/demoddatv/datvdemodgui.ui | 41 ++------------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index 6bd511c08..61503bfe8 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -454,19 +454,6 @@ Symbols/s - - - - 100 - 10 - 71 - 21 - - - - Bandwidth - - @@ -541,7 +528,7 @@ Total number of bytes decoded - - + Data: 0 @@ -579,28 +566,6 @@ 1000 - - - - 170 - 10 - 81 - 23 - - - - RF filter bandwidth - - - 1000 - - - 1024000000 - - - 1000 - - @@ -614,7 +579,7 @@ Stream speed - - + Speed: 0b/s @@ -668,7 +633,7 @@ - RRC filter roll off factor + RRC filter roll off factor (%) 1 From b2e446afccdaa2a66ce3199822afd65536b7efd9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 10 Mar 2018 20:32:46 +0100 Subject: [PATCH 082/956] Windows build: new location for export header --- app/app.pro | 1 + devices/devices.pro | 1 + httpserver/httpserver.pro | 2 +- logging/logging.pro | 2 +- plugins/channelrx/chanalyzer/chanalyzer.pro | 1 + plugins/channelrx/chanalyzerng/chanalyzerng.pro | 1 + plugins/channelrx/demodam/demodam.pro | 1 + plugins/channelrx/demodatv/demodatv.pro | 1 + plugins/channelrx/demodbfm/demodbfm.pro | 1 + plugins/channelrx/demoddsd/demoddsd.pro | 1 + plugins/channelrx/demodlora/demodlora.pro | 1 + plugins/channelrx/demodnfm/demodnfm.pro | 1 + plugins/channelrx/demodssb/demodssb.pro | 1 + plugins/channelrx/demodwfm/demodwfm.pro | 1 + plugins/channelrx/tcpsrc/tcpsrc.pro | 1 + plugins/channelrx/udpsrc/udpsrc.pro | 1 + plugins/channeltx/modam/modam.pro | 1 + plugins/channeltx/modatv/modatv.pro | 1 + plugins/channeltx/modnfm/modnfm.pro | 1 + plugins/channeltx/modssb/modssb.pro | 1 + plugins/channeltx/modwfm/modwfm.pro | 1 + plugins/channeltx/udpsink/udpsink.pro | 1 + plugins/samplesink/bladerfoutput/bladerfoutput.pro | 1 + plugins/samplesink/filesink/filesink.pro | 1 + plugins/samplesink/hackrfoutput/hackrfoutput.pro | 1 + plugins/samplesink/limesdroutput/limesdroutput.pro | 1 + plugins/samplesink/plutosdroutput/plutosdroutput.pro | 1 + plugins/samplesource/airspy/airspy.pro | 1 + plugins/samplesource/airspyhf/airspyhf.pro | 1 + plugins/samplesource/bladerfinput/bladerfinput.pro | 1 + plugins/samplesource/filesource/filesource.pro | 1 + plugins/samplesource/hackrfinput/hackrfinput.pro | 1 + plugins/samplesource/limesdrinput/limesdrinput.pro | 1 + plugins/samplesource/plutosdrinput/plutosdrinput.pro | 1 + plugins/samplesource/rtlsdr/rtlsdr.pro | 1 + plugins/samplesource/testsource/testsource.pro | 1 + qrtplib/qrtplib.pro | 5 ++--- sdrangel.windows.pro | 3 +-- sdrbase/sdrbase.pro | 1 + sdrgui/sdrgui.pro | 1 + 40 files changed, 41 insertions(+), 7 deletions(-) diff --git a/app/app.pro b/app/app.pro index fd561a06a..b7a1eb67b 100644 --- a/app/app.pro +++ b/app/app.pro @@ -10,6 +10,7 @@ QMAKE_CXXFLAGS += -std=c++11 TEMPLATE = app TARGET = sdrangel +INCLUDEPATH += $$PWD/../exports INCLUDEPATH += $$PWD/../sdrbase INCLUDEPATH += $$PWD/../sdrgui INCLUDEPATH += $$PWD/../logging diff --git a/devices/devices.pro b/devices/devices.pro index c942ba234..a489c71e8 100644 --- a/devices/devices.pro +++ b/devices/devices.pro @@ -29,6 +29,7 @@ CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" INCLUDEPATH += $$PWD +INCLUDEPATH += ../exports INCLUDEPATH += ../sdrbase INCLUDEPATH += $$LIBBLADERFSRC INCLUDEPATH += $$LIBHACKRFSRC diff --git a/httpserver/httpserver.pro b/httpserver/httpserver.pro index 908950ade..9dd999c07 100644 --- a/httpserver/httpserver.pro +++ b/httpserver/httpserver.pro @@ -10,7 +10,7 @@ TEMPLATE = lib TARGET = httpserver INCLUDEPATH += $$PWD -INCLUDEPATH += ../sdrbase +INCLUDEPATH += ../exports QMAKE_CXXFLAGS += -std=c++11 diff --git a/logging/logging.pro b/logging/logging.pro index 7aae18fd7..670800044 100644 --- a/logging/logging.pro +++ b/logging/logging.pro @@ -10,7 +10,7 @@ TEMPLATE = lib TARGET = logging INCLUDEPATH += $$PWD -INCLUDEPATH += ../sdrbase +INCLUDEPATH += ../exports QMAKE_CXXFLAGS += -std=c++11 diff --git a/plugins/channelrx/chanalyzer/chanalyzer.pro b/plugins/channelrx/chanalyzer/chanalyzer.pro index 67a4d5588..e46850563 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.pro +++ b/plugins/channelrx/chanalyzer/chanalyzer.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.pro b/plugins/channelrx/chanalyzerng/chanalyzerng.pro index 19a0d8987..07cddc9d2 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.pro +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/demodam/demodam.pro b/plugins/channelrx/demodam/demodam.pro index 1ab4a6d8b..c63c76dfc 100644 --- a/plugins/channelrx/demodam/demodam.pro +++ b/plugins/channelrx/demodam/demodam.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/demodatv/demodatv.pro b/plugins/channelrx/demodatv/demodatv.pro index 5fc8131bf..7ed97aae2 100644 --- a/plugins/channelrx/demodatv/demodatv.pro +++ b/plugins/channelrx/demodatv/demodatv.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/demodbfm/demodbfm.pro b/plugins/channelrx/demodbfm/demodbfm.pro index 4a8cd67df..f0a75c3c3 100644 --- a/plugins/channelrx/demodbfm/demodbfm.pro +++ b/plugins/channelrx/demodbfm/demodbfm.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/demoddsd/demoddsd.pro b/plugins/channelrx/demoddsd/demoddsd.pro index ff90fb57a..07b8069ed 100644 --- a/plugins/channelrx/demoddsd/demoddsd.pro +++ b/plugins/channelrx/demoddsd/demoddsd.pro @@ -30,6 +30,7 @@ CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += $$LIBDSDCCSRC diff --git a/plugins/channelrx/demodlora/demodlora.pro b/plugins/channelrx/demodlora/demodlora.pro index 29438cf8f..9f96323bc 100644 --- a/plugins/channelrx/demodlora/demodlora.pro +++ b/plugins/channelrx/demodlora/demodlora.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/demodnfm/demodnfm.pro b/plugins/channelrx/demodnfm/demodnfm.pro index f3f274eb8..347e194f8 100644 --- a/plugins/channelrx/demodnfm/demodnfm.pro +++ b/plugins/channelrx/demodnfm/demodnfm.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/channelrx/demodssb/demodssb.pro b/plugins/channelrx/demodssb/demodssb.pro index 11e37b289..c9612f7d6 100644 --- a/plugins/channelrx/demodssb/demodssb.pro +++ b/plugins/channelrx/demodssb/demodssb.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/demodwfm/demodwfm.pro b/plugins/channelrx/demodwfm/demodwfm.pro index c705ff3cb..4952543b4 100644 --- a/plugins/channelrx/demodwfm/demodwfm.pro +++ b/plugins/channelrx/demodwfm/demodwfm.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/tcpsrc/tcpsrc.pro b/plugins/channelrx/tcpsrc/tcpsrc.pro index 0b50cace3..dd4e04b60 100644 --- a/plugins/channelrx/tcpsrc/tcpsrc.pro +++ b/plugins/channelrx/tcpsrc/tcpsrc.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/udpsrc/udpsrc.pro b/plugins/channelrx/udpsrc/udpsrc.pro index 87118283f..f78274edd 100644 --- a/plugins/channelrx/udpsrc/udpsrc.pro +++ b/plugins/channelrx/udpsrc/udpsrc.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channeltx/modam/modam.pro b/plugins/channeltx/modam/modam.pro index 239a16ddf..529b82ede 100644 --- a/plugins/channeltx/modam/modam.pro +++ b/plugins/channeltx/modam/modam.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channeltx/modatv/modatv.pro b/plugins/channeltx/modatv/modatv.pro index 464796e74..04adb5fe7 100644 --- a/plugins/channeltx/modatv/modatv.pro +++ b/plugins/channeltx/modatv/modatv.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channeltx/modnfm/modnfm.pro b/plugins/channeltx/modnfm/modnfm.pro index 073b23c34..ec9622875 100644 --- a/plugins/channeltx/modnfm/modnfm.pro +++ b/plugins/channeltx/modnfm/modnfm.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/channeltx/modssb/modssb.pro b/plugins/channeltx/modssb/modssb.pro index 5bc58f2c4..b1378fc83 100644 --- a/plugins/channeltx/modssb/modssb.pro +++ b/plugins/channeltx/modssb/modssb.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channeltx/modwfm/modwfm.pro b/plugins/channeltx/modwfm/modwfm.pro index abe2358bd..a2ade1bc1 100644 --- a/plugins/channeltx/modwfm/modwfm.pro +++ b/plugins/channeltx/modwfm/modwfm.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channeltx/udpsink/udpsink.pro b/plugins/channeltx/udpsink/udpsink.pro index 4904bbc8c..dce7d049b 100644 --- a/plugins/channeltx/udpsink/udpsink.pro +++ b/plugins/channeltx/udpsink/udpsink.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.pro b/plugins/samplesink/bladerfoutput/bladerfoutput.pro index ba62f7337..b670583c8 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.pro +++ b/plugins/samplesink/bladerfoutput/bladerfoutput.pro @@ -20,6 +20,7 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesink/filesink/filesink.pro b/plugins/samplesink/filesink/filesink.pro index 18e83f81d..370183c48 100644 --- a/plugins/samplesink/filesink/filesink.pro +++ b/plugins/samplesink/filesink/filesink.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesink/hackrfoutput/hackrfoutput.pro b/plugins/samplesink/hackrfoutput/hackrfoutput.pro index c619666ac..05c256d4d 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutput.pro +++ b/plugins/samplesink/hackrfoutput/hackrfoutput.pro @@ -20,6 +20,7 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host" CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesink/limesdroutput/limesdroutput.pro b/plugins/samplesink/limesdroutput/limesdroutput.pro index 0ef177a0e..51efed10b 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.pro +++ b/plugins/samplesink/limesdroutput/limesdroutput.pro @@ -21,6 +21,7 @@ CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.pro b/plugins/samplesink/plutosdroutput/plutosdroutput.pro index 96dd9551e..f788ba60d 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.pro +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.pro @@ -21,6 +21,7 @@ CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/airspy/airspy.pro b/plugins/samplesource/airspy/airspy.pro index 3348db739..9f3083501 100644 --- a/plugins/samplesource/airspy/airspy.pro +++ b/plugins/samplesource/airspy/airspy.pro @@ -14,6 +14,7 @@ TARGET = inputairspy CONFIG(MINGW32):LIBAIRSPYSRC = "D:\softs\libairspy" CONFIG(MINGW64):LIBAIRSPYSRC = "D:\softs\libairspy" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/airspyhf/airspyhf.pro b/plugins/samplesource/airspyhf/airspyhf.pro index c31ae2e71..70ec7bdea 100644 --- a/plugins/samplesource/airspyhf/airspyhf.pro +++ b/plugins/samplesource/airspyhf/airspyhf.pro @@ -14,6 +14,7 @@ TARGET = inputairspyhf CONFIG(MINGW32):LIBAIRSPYHFSRC = "D:\softs\airspyhf" CONFIG(MINGW64):LIBAIRSPYHFSRC = "D:\softs\airspyhf" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/bladerfinput/bladerfinput.pro b/plugins/samplesource/bladerfinput/bladerfinput.pro index 9275a3542..42543f9fd 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.pro +++ b/plugins/samplesource/bladerfinput/bladerfinput.pro @@ -20,6 +20,7 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/filesource/filesource.pro b/plugins/samplesource/filesource/filesource.pro index a22dc7d9d..7a85b75b6 100644 --- a/plugins/samplesource/filesource/filesource.pro +++ b/plugins/samplesource/filesource/filesource.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/hackrfinput/hackrfinput.pro b/plugins/samplesource/hackrfinput/hackrfinput.pro index a6f151a9c..89317600b 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.pro +++ b/plugins/samplesource/hackrfinput/hackrfinput.pro @@ -20,6 +20,7 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host" CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/limesdrinput/limesdrinput.pro b/plugins/samplesource/limesdrinput/limesdrinput.pro index 901fc788b..c1fca9b8f 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.pro +++ b/plugins/samplesource/limesdrinput/limesdrinput.pro @@ -23,6 +23,7 @@ CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.pro b/plugins/samplesource/plutosdrinput/plutosdrinput.pro index dab6ad546..906ea2ea5 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.pro +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.pro @@ -21,6 +21,7 @@ CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/rtlsdr/rtlsdr.pro b/plugins/samplesource/rtlsdr/rtlsdr.pro index a2e403e3d..8d89e19a7 100644 --- a/plugins/samplesource/rtlsdr/rtlsdr.pro +++ b/plugins/samplesource/rtlsdr/rtlsdr.pro @@ -20,6 +20,7 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBRTLSDRSRC = "D:\softs\librtlsdr" CONFIG(MINGW64):LIBRTLSDRSRC = "D:\softs\librtlsdr" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/testsource/testsource.pro b/plugins/samplesource/testsource/testsource.pro index 547baba25..04b7389dc 100644 --- a/plugins/samplesource/testsource/testsource.pro +++ b/plugins/samplesource/testsource/testsource.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/qrtplib/qrtplib.pro b/qrtplib/qrtplib.pro index 50dc5d777..bfe30b4a9 100644 --- a/qrtplib/qrtplib.pro +++ b/qrtplib/qrtplib.pro @@ -10,7 +10,7 @@ TEMPLATE = lib TARGET = qrtplib INCLUDEPATH += $$PWD -INCLUDEPATH += ../sdrbase +INCLUDEPATH += ../exports QMAKE_CXXFLAGS += -std=c++11 @@ -59,8 +59,7 @@ HEADERS += $$PWD/rtcpapppacket.h \ $$PWD/rtptypes_win.h \ $$PWD/rtptypes.h \ $$PWD/rtpudptransmitter.h \ - $$PWD/rtpsocketutil.h \ - $$PWD/rtpselect.h + $$PWD/rtpsocketutil.h SOURCES += $$PWD/rtcpapppacket.cpp \ diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index d6645e29c..8bf10973c 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -30,7 +30,7 @@ SUBDIRS += librtlsdr SUBDIRS += devices SUBDIRS += mbelib SUBDIRS += dsdcc -CONFIG(MINGW64)SUBDIRS += cm256cc +SUBDIRS += cm256cc SUBDIRS += plugins/samplesource/airspy SUBDIRS += plugins/samplesource/airspyhf SUBDIRS += plugins/samplesource/bladerfinput @@ -59,7 +59,6 @@ SUBDIRS += plugins/channelrx/demodwfm SUBDIRS += plugins/channelrx/tcpsrc SUBDIRS += plugins/channelrx/udpsrc SUBDIRS += plugins/channeltx/modam -CONFIG(MINGW64)SUBDIRS += plugins/channeltx/modatv SUBDIRS += plugins/channeltx/modnfm SUBDIRS += plugins/channeltx/modssb SUBDIRS += plugins/channeltx/modwfm diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index 6b3262d63..a2d0c640b 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -10,6 +10,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = lib TARGET = sdrbase INCLUDEPATH += $$PWD +INCLUDEPATH += ../exports INCLUDEPATH += ../httpserver INCLUDEPATH += ../qrtplib INCLUDEPATH += ../swagger/sdrangel/code/qt5/client diff --git a/sdrgui/sdrgui.pro b/sdrgui/sdrgui.pro index c7e6e5d5d..194d7b396 100644 --- a/sdrgui/sdrgui.pro +++ b/sdrgui/sdrgui.pro @@ -10,6 +10,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = lib TARGET = sdrgui INCLUDEPATH += $$PWD +INCLUDEPATH += ../exports INCLUDEPATH += ../sdrbase INCLUDEPATH += ../logging INCLUDEPATH += ../httpserver From 04864623989feb1fe725f5bed9490441607abd35 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 10 Mar 2018 23:01:03 +0100 Subject: [PATCH 083/956] DATV demod: GUI changes (4) --- plugins/channelrx/demodatv/atvdemodgui.ui | 2 +- plugins/channelrx/demoddatv/datvdemod.cpp | 3 +++ plugins/channelrx/demoddatv/datvdemod.h | 3 +++ plugins/channelrx/demoddatv/datvdemodgui.cpp | 7 +++++++ plugins/channelrx/demoddatv/datvdemodgui.h | 2 ++ plugins/channelrx/demoddatv/datvdemodgui.ui | 13 +++++++++++++ 6 files changed, 29 insertions(+), 1 deletion(-) diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index 17ac80b09..0f4b2d106 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -265,7 +265,7 @@ - Channel power + Channel power (dB) -100.0 dB diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 38590273d..fb52bb273 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -787,6 +787,7 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect //Complex objC; fftfilt::cmplx *objRF; int intRFOut; + double magSq; //********** Bis repetita : Let's rock and roll buddy ! ********** @@ -849,6 +850,8 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect { objIQ.re = objRF->real(); objIQ.im = objRF->imag(); + magSq = objIQ.re*objIQ.re + objIQ.im*objIQ.im; + m_objMagSqAverage(magSq); objRF ++; diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 815407ef3..6718894f7 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -61,6 +61,7 @@ class DownChannelizer; #include "dsp/agc.h" #include "audio/audiofifo.h" #include "util/message.h" +#include "util/movingaverage.h" #include #include "datvideostream.h" @@ -236,6 +237,7 @@ public: void CleanUpDATVFramework(bool blnRelease); int GetSampleRate(); void InitDATVFramework(); + double getMagSq() const { return m_objMagSqAverage; } //!< Beware this is scaled to 2^30 static const QString m_channelIdURI; static const QString m_channelId; @@ -458,6 +460,7 @@ private: //QElapsedTimer m_objTimer; DATVConfig m_objRunning; + MovingAverageUtil m_objMagSqAverage; QMutex m_objSettingsMutex; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index 9b31b27d1..123ed4ad5 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -503,6 +503,13 @@ void DATVDemodGUI::enterEvent(QEvent*) void DATVDemodGUI::tick() { + if (m_objDATVDemod) + { + m_objMagSqAverage(m_objDATVDemod->getMagSq()); + double magSqDB = CalcDb::dbPower(m_objMagSqAverage / (SDR_RX_SCALED*SDR_RX_SCALED)); + ui->channePowerText->setText(tr("%1 dB").arg(magSqDB, 0, 'f', 1)); + } + if((m_intLastDecodedData-m_intPreviousDecodedData)>=0) { m_intLastSpeed = 8*(m_intLastDecodedData-m_intPreviousDecodedData); diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h index da26eca9e..d16db6c2e 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.h +++ b/plugins/channelrx/demoddatv/datvdemodgui.h @@ -120,6 +120,8 @@ private: bool m_blnDoApplySettings; bool m_blnButtonPlayClicked; + MovingAverageUtil m_objMagSqAverage; + explicit DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* objParent = 0); virtual ~DATVDemodGUI(); diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index 61503bfe8..3393e12e9 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -165,6 +165,19 @@
+ + + + Channel power (dB) + + + -100.0 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + From 107b1c9ae60ca502a27a13ec7491c64a39937580 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 00:16:39 +0100 Subject: [PATCH 084/956] DATV demod: prevent segfault when DATV constellation screen is not registered --- plugins/channelrx/demoddatv/datvdemod.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index fb52bb273..bf04aa6e6 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -694,11 +694,14 @@ void DATVDemod::InitDATVFramework() //constellation - m_objRegisteredDATVScreen->resizeDATVScreen(256,256); + if (m_objRegisteredDATVScreen) + { + m_objRegisteredDATVScreen->resizeDATVScreen(256,256); - r_scope_symbols = new leansdr::datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredDATVScreen); - r_scope_symbols->decimation = 1; - r_scope_symbols->cstln = &m_objDemodulator->cstln; + r_scope_symbols = new leansdr::datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredDATVScreen); + r_scope_symbols->decimation = 1; + r_scope_symbols->cstln = &m_objDemodulator->cstln; + } // DECONVOLUTION AND SYNCHRONIZATION From 30dd4f4c4bb4d89c099ee5d86a24adffb3fcecf9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 00:17:22 +0100 Subject: [PATCH 085/956] DATV demod: fixed delta frequency dial setting at deserialization --- plugins/channelrx/demoddatv/datvdemodgui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index 123ed4ad5..c619e7917 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -152,6 +152,7 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) d.readS32(1, &tmp, 0); m_objChannelMarker.setCenterFrequency(tmp); + ui->deltaFrequency->setValue(tmp); if (d.readU32(2, &u32tmp)) { From 1b8ad814e58fce978d38262986c75d47d1ea3a72 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 01:00:27 +0100 Subject: [PATCH 086/956] DATV demod: header includes cleanup --- plugins/channelrx/demoddatv/datvdemod.h | 18 +++++++----------- plugins/channelrx/demoddatv/datvdemodgui.h | 4 +--- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 6718894f7..3c60368ef 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -47,14 +47,11 @@ class DownChannelizer; #include "datvvideoplayer.h" #include "channel/channelsinkapi.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "dsp/basebandsamplesink.h" +#include "dsp/devicesamplesource.h" +#include "dsp/dspcommands.h" +#include "dsp/downchannelizer.h" +#include "dsp/fftfilt.h" #include "dsp/nco.h" #include "dsp/interpolator.h" #include "dsp/movingaverage.h" @@ -62,11 +59,12 @@ class DownChannelizer; #include "audio/audiofifo.h" #include "util/message.h" #include "util/movingaverage.h" -#include #include "datvideostream.h" #include "datvideorender.h" +#include + enum DATVModulation { BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 }; enum dvb_version { DVB_S, DVB_S2 }; enum dvb_sampler { SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC }; @@ -457,8 +455,6 @@ private: DATVModulation m_enmModulation; - //QElapsedTimer m_objTimer; - DATVConfig m_objRunning; MovingAverageUtil m_objMagSqAverage; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h index d16db6c2e..e6fad2764 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.h +++ b/plugins/channelrx/demoddatv/datvdemodgui.h @@ -20,14 +20,12 @@ #define INCLUDE_DATVDEMODGUI_H #include "gui/rollupwidget.h" -#include +#include "plugin/plugininstancegui.h" #include "dsp/channelmarker.h" #include "dsp/movingaverage.h" #include "datvdemod.h" -#include -#include #include From 8312e5ad58f7bb0daeb0eb0ca9974c101f33da14 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 01:48:43 +0100 Subject: [PATCH 087/956] LimeSDR: Temporary fix for rubbish values returned by recent version of Lime Sute --- devices/limesdr/devicelimesdrparam.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/devices/limesdr/devicelimesdrparam.cpp b/devices/limesdr/devicelimesdrparam.cpp index 954eee9fa..015253136 100644 --- a/devices/limesdr/devicelimesdrparam.cpp +++ b/devices/limesdr/devicelimesdrparam.cpp @@ -93,6 +93,18 @@ bool DeviceLimeSDRParams::open(lms_info_str_t deviceStr) return false; } + // FIXME: Temporary fix for rubbish values returned by recent version of Lime Sute + m_lpfRangeRx.step = 1.0; + m_lpfRangeTx.step = 1.0; + m_srRangeRx.step = 1.0; + m_srRangeTx.step = 1.0; + m_loRangeRx.max = 3800000000.0; + m_loRangeRx.min = 30000000.0; + m_loRangeRx.step = 1.0; + m_loRangeTx.max = 3800000000.0; + m_loRangeTx.min = 30000000.0; + m_loRangeTx.step = 1.0; + return true; } From d0599a2ec0fd5879d41e4cfc2dd8399de2c24f75 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 01:48:59 +0100 Subject: [PATCH 088/956] Removed LimeSDR support from all builds --- plugins/samplesink/CMakeLists.txt | 2 +- plugins/samplesource/CMakeLists.txt | 2 +- sdrangel.windows.pro | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index 179ba540e..73a0974b1 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -31,7 +31,7 @@ endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) if (BUILD_DEBIAN) add_subdirectory(bladerfoutput) add_subdirectory(hackrfoutput) - add_subdirectory(limesdroutput) +# add_subdirectory(limesdroutput) if (LIBNANOMSG_FOUND) add_subdirectory(sdrdaemonsink) endif (LIBNANOMSG_FOUND) diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index 3bfb5c675..f5d393dde 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -87,7 +87,7 @@ if (BUILD_DEBIAN) add_subdirectory(rtlsdr) add_subdirectory(bladerfinput) add_subdirectory(sdrplay) - add_subdirectory(limesdrinput) +# add_subdirectory(limesdrinput) add_subdirectory(perseus) add_subdirectory(plutosdrinput) endif (BUILD_DEBIAN) diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index 8bf10973c..5afd9d2ed 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -36,7 +36,7 @@ SUBDIRS += plugins/samplesource/airspyhf SUBDIRS += plugins/samplesource/bladerfinput SUBDIRS += plugins/samplesource/filesource SUBDIRS += plugins/samplesource/hackrfinput -SUBDIRS += plugins/samplesource/limesdrinput +#SUBDIRS += plugins/samplesource/limesdrinput SUBDIRS += plugins/samplesource/plutosdrinput CONFIG(MINGW64)SUBDIRS += plugins/samplesource/sdrdaemonsource SUBDIRS += plugins/samplesource/rtlsdr @@ -44,7 +44,7 @@ SUBDIRS += plugins/samplesource/testsource SUBDIRS += plugins/samplesink/filesink SUBDIRS += plugins/samplesink/bladerfoutput SUBDIRS += plugins/samplesink/hackrfoutput -SUBDIRS += plugins/samplesink/limesdroutput +#SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput SUBDIRS += plugins/channelrx/chanalyzer SUBDIRS += plugins/channelrx/chanalyzerng From 2b24970eea5f972589783b7bc7f5d9e69162db7d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 02:00:41 +0100 Subject: [PATCH 089/956] Updated main readme with recent LimeSDR support discontinuation --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index c14375423..b6e0b859d 100644 --- a/Readme.md +++ b/Readme.md @@ -117,6 +117,8 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3.

LimeSDR

+

⚠Source code only. Recent versions of Lime Suite library are crap so official support cannot be maintained. You can still compile the plugins from source and see for yourself

+ [LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next).

⚠ The plugins should work normally when running as single instances. Support of many Rx and/or Tx instances running concurrently is considered experimental. At least you should always have one of the streams running.

From b5b3b4d699159471b7a605835aa30a78048d3076 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 02:26:09 +0100 Subject: [PATCH 090/956] LimeSDR: updated plugins version --- Readme.md | 4 ++-- plugins/samplesink/limesdroutput/limesdroutputplugin.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinputplugin.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index b6e0b859d..e2f7c5fb7 100644 --- a/Readme.md +++ b/Readme.md @@ -41,7 +41,7 @@ From version 3 transmission or signal generation is supported for BladeRF, HackR - [BladeRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerfoutput) limited support in Windows - [HackRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/hackrfoutput) - - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) + - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) Source only - [PlutoSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/plutosdroutput) - [File output or file sink plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/filesink) - [Remote device via Network with SDRdaemon](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) Linux only @@ -117,7 +117,7 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3.

LimeSDR

-

⚠Source code only. Recent versions of Lime Suite library are crap so official support cannot be maintained. You can still compile the plugins from source and see for yourself

+

⚠ Source code only. Recent versions of Lime Suite library are crap so official support cannot be maintained. You can still compile the plugins from source and see for yourself

[LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next). diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp index a53c37602..d16fa3623 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp @@ -34,7 +34,7 @@ const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = { QString("LimeSDR Output"), - QString("3.10.1"), + QString("3.13.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 21c043c48..3ab77f042 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("3.10.1"), + QString("3.13.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From e8fee43100b6eca1e413f447c0413beea917df03 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 09:59:12 +0100 Subject: [PATCH 091/956] Updated Debian changelog --- debian/changelog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 0245f38c1..b91448549 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ sdrangel (3.13.0-1) unstable; urgency=medium * DATV (Digital Amateur TV) demodulator. + * Option to use RTP protocol for UDP audio for AM, NFM, SSB, WFM. - -- Edouard Griffiths, F4EXB Sun, 25 Feb 2018 12:14:18 +0100 + -- Edouard Griffiths, F4EXB Sun, 11 Mar 2018 06:14:18 +0100 sdrangel (3.12.0-1) unstable; urgency=medium From 8e6438908bfb8db87594c5e3b34fc715ff71638e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 12:22:00 +0100 Subject: [PATCH 092/956] Unify TV screen (1) --- plugins/channelrx/demodatv/glshaderarray.cpp | 4 ++-- plugins/channelrx/demoddatv/glshaderarray.cpp | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/demodatv/glshaderarray.cpp b/plugins/channelrx/demodatv/glshaderarray.cpp index 58802c781..f54041dc9 100644 --- a/plugins/channelrx/demodatv/glshaderarray.cpp +++ b/plugins/channelrx/demodatv/glshaderarray.cpp @@ -126,7 +126,7 @@ void GLShaderArray::InitializeGL(int intCols, int intRows) QRgb * GLShaderArray::GetRowBuffer(int intRow) { - if (m_blnInitialized == false) + if (!m_blnInitialized) { return 0; } @@ -168,7 +168,7 @@ void GLShaderArray::RenderPixels(unsigned char *chrData) QRgb *ptrLine; int intVal; - if (m_blnInitialized == false) + if (!m_blnInitialized) { return; } diff --git a/plugins/channelrx/demoddatv/glshaderarray.cpp b/plugins/channelrx/demoddatv/glshaderarray.cpp index 985b16397..6e09c57bb 100644 --- a/plugins/channelrx/demoddatv/glshaderarray.cpp +++ b/plugins/channelrx/demoddatv/glshaderarray.cpp @@ -223,7 +223,6 @@ void GLShaderArray::RenderPixels(unsigned char *chrData) m_objTexture->release(); m_objProgram->release(); - } void GLShaderArray::ResetPixels() @@ -295,7 +294,6 @@ bool GLShaderArray::SetDataColor(int intCol, QRgb objColor) } } - return blnRslt; } From 096b61f6e31b028bcfb03fb6574808f0b997e2f9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 12:23:06 +0100 Subject: [PATCH 093/956] DATV plugin: updated dependencies --- Readme.md | 4 ++-- debian/control | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index e2f7c5fb7..6559dfa3d 100644 --- a/Readme.md +++ b/Readme.md @@ -340,7 +340,7 @@ Install cmake version 3:

With newer versions just do:

- - `sudo apt-get install cmake g++ pkg-config libfftw3-dev libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev libusb-1.0 librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libnanomsg-dev libopencv-dev libsqlite3-dev libxml2-dev bison flex` + - `sudo apt-get install cmake g++ pkg-config libfftw3-dev libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev libusb-1.0 librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libnanomsg-dev libopencv-dev libsqlite3-dev libxml2-dev bison flex ffmpeg libavcodec-dev libavformat-dev` - `mkdir build && cd build && cmake ../ && make` `librtlsdr-dev` is in the `universe` repo. (utopic 14.10 amd64.) @@ -357,7 +357,7 @@ Debian 7 "wheezy" uses Qt4. Qt5 is available from the "wheezy-backports" repo, b For Debian Jessie or Stretch: -`sudo apt-get install cmake g++ pkg-config libfftw3-dev libusb-1.0-0-dev libusb-dev qt5-default qtbase5-dev qtchooser libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libopencv-dev libsqlite3-dev libxml2-dev bison flex` +`sudo apt-get install cmake g++ pkg-config libfftw3-dev libusb-1.0-0-dev libusb-dev qt5-default qtbase5-dev qtchooser libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libopencv-dev libsqlite3-dev libxml2-dev bison flex ffmpeg libavcodec-dev libavformat-dev` `mkdir build && cd build && cmake ../ && make` diff --git a/debian/control b/debian/control index 5ae42f3a4..f9d3e2960 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://github.com/f4exb/sdrangel Package: sdrangel Architecture: any -Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libnanomsg0|libnanomsg4, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, libsqlite3-dev, pulseaudio, libxml2, ${shlibs:Depends}, ${misc:Depends} +Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libnanomsg0|libnanomsg4, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, libsqlite3-dev, pulseaudio, libxml2, ffmpeg, libavcodec-dev, libavformat-dev, ${shlibs:Depends}, ${misc:Depends} Description: SDR/Analyzer/Generator front-end for various hardware SDR/Analyzer/Generator front-end for Airspy, BladeRF, HackRF, RTL-SDR, FunCube, LimeSDR, PlutoSDR. Also File source and sink for I/Q samples, network I/Q sources with SDRDaemon. From 5c6bdb5c3da1edea53f7ad05ebb52b8caeaa96b1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 12:24:55 +0100 Subject: [PATCH 094/956] Debian build. Corrected ffmpeg dependency --- plugins/channelrx/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 776378a47..110fb1dd3 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -23,5 +23,4 @@ endif() if (BUILD_DEBIAN) add_subdirectory(demoddsd) - add_subdirectory(demoddatv) endif (BUILD_DEBIAN) From 7d63bf48e0062c1b70174cde3ccb6d43a08f030b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 12:25:18 +0100 Subject: [PATCH 095/956] DATV demod: updated documentation --- doc/img/DATVDemod_plugin.png | Bin 0 -> 43218 bytes doc/img/DATVDemod_plugin.xcf | Bin 0 -> 149836 bytes doc/img/DATVDemod_pluginDATV.png | Bin 0 -> 33520 bytes doc/img/DATVDemod_pluginDATV.xcf | Bin 0 -> 103382 bytes doc/img/DATVDemod_pluginDATV2.png | Bin 0 -> 23947 bytes doc/img/DATVDemod_pluginDATV2.xcf | Bin 0 -> 87539 bytes doc/img/DATVDemod_pluginRF.png | Bin 0 -> 6360 bytes doc/img/DATVDemod_pluginRF.xcf | Bin 0 -> 35711 bytes doc/img/DATVDemod_pluginVideo.png | Bin 0 -> 93176 bytes doc/img/DATVDemod_pluginVideo.xcf | Bin 0 -> 247272 bytes plugins/channelrx/demoddatv/readme.md | 150 ++++++++++++++++++-------- 11 files changed, 105 insertions(+), 45 deletions(-) create mode 100644 doc/img/DATVDemod_plugin.png create mode 100644 doc/img/DATVDemod_plugin.xcf create mode 100644 doc/img/DATVDemod_pluginDATV.png create mode 100644 doc/img/DATVDemod_pluginDATV.xcf create mode 100644 doc/img/DATVDemod_pluginDATV2.png create mode 100644 doc/img/DATVDemod_pluginDATV2.xcf create mode 100644 doc/img/DATVDemod_pluginRF.png create mode 100644 doc/img/DATVDemod_pluginRF.xcf create mode 100644 doc/img/DATVDemod_pluginVideo.png create mode 100644 doc/img/DATVDemod_pluginVideo.xcf diff --git a/doc/img/DATVDemod_plugin.png b/doc/img/DATVDemod_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..cd048bfb27e8d0c401a9ae676f05727f92406c73 GIT binary patch literal 43218 zcmeFZg;$o_x;IQnNQg8Df`Wh`B`swjrGygFDIrLAhlR9AH%du&NrQx>G)Q-M_j}!* zeZFzVcgFi4ylaoO*4`@5bKi4b^SXXDx9|a8=ALuSe73VVSgh>eHnuNndBo1oh$|}4xKpJQ>~}s z*(A7y-|w>A6Mu}6ihV?vf9n>*H&%aCGzGD%S8jj!^5lt2bo60xbO0sMt?(~%$0yOj zdK)5TaSx0aqHWuUQ5m1YUnvdwDx{y`ue(#e$iL7M{xARI|NZ{|S|2HC+w8kSGn<=v zDvGd_ehe%unNQYyB({)+Wua*CD;2w-!ECmFw#b)gwy`TeaF3Oaj_&w`zAZ4+I>@c2 z@8c2)7DkoEgR#!l#E!IGUV+jTwQH%oRU2mx!=!Irych1j%p83|boyd&RCR1W(#O1t z^7O?*UUKPnT2)ZluhUN_*(YWg$9SWgqz&O+eMcJwsuu-q@0aHMY<2r`buF3ZZ;c(F za#tJUyfpFHs$HZ!91>WwzoHJ0mwUU9Jye-jWm~1JY}+h9~{uY5A__@m%c*F;YiEbem@sWZWl_iO}(RvQhg5tO240&K>mDJ#R7 z3ryEga##azUByO^K+{22sXd7GcJ)?&6f<8D|0w-!_8L7A8{Qm+{cbK(=H3m<_g^wolln2m$#(|6b4hK zE`A7!s)%<_qa3Cwczn{3lozJ`@|IE3mR58R?tpiCW* zdEJ*LySLQyK0VPx@X~518HV`qFkN8fa=I(x?c|Am9UFfx{Xk*Ebd^C6Q z>fPS#2R@wKslt?q^$^RExNNNHGGD@I?K#jc@Xb&g zyesz^nc%&Si}&?L3JnLp9QG^9&r@x@%2s{(Bm2dKKqfOu-Q(x^E+|Kd^A&ng1fdq_ z&8QcT9(T3lp93wMg$+e`ZqhU-nj-*Iz*ULnO@D~_8xge zt6RX(F)#X5gZ5~Eh>sto8@*PKW<;i-W0&I31X0I&znS<`mLkLU2tBT7r=@Q`9kfCY z4t&$O-}?;HFziu(SR7Z1EWFG$tV^u3?jXNT(*NW_RAyE0DkCm#g-B17BxTlX(Xg=_ zPUyQ8UW=T;M)<9~mvrr=X{rrV4DAQT>S^-w15w)C{h2?|Ze}4_;blo;x_wqwx%E5jI`CLftI9 zLTYr?Rl8!LHfnXV-|b>OOt_aTlwuMj{c(p?9sg|Rv5d}vqn+fsg>xv&Yn%y^+aiLHXo}#J-V~dQ+nrlc|bQQ zkL~jcmXzZ;oQ4W~R2nq2`@xKZrP=i9o0PjFxil&wLv;#_J`(FspWcZaTO1o18Brq` zV%MpuYS^rl*veJD7ZfGwL(}56F?2(hf5D$^?LI9Te!lQV{*}^iA5~kPOo=DqE)Z5z zS?6C#XROPL>XwcDaNOV@7gE{H8Js_Y&gOOMm%oX1Klq1i;gz9lQ3UPmr7}90Rc6Pd z_`4NW6*o~_)GsWq>{T|qWa`scR~j524yatwX>jw<3JQ<-x$@T8T)X(gN~dWq9i7d& zRhfPhPHbZdIwszWx_fyu2hF7J6PujD+BeZBn%!zty2J$$mxlOonS5C5`NOH;Y97AR|$SyQ$BNLp{E4n%)LujkAgFic(|UZ zkW9DcI{o7H9j6X*m9-YP)b)t0wqSG#^>-60nx}13n`}9o@*VWa+jH*tfAwqB9Sc%x z5>Z4kXIS-qaxAY|9a z9^5gs$Jsa^k5+M|Np2RAU++G@*C8LSJZ!s_S)(2v!L70L8#hsNSCrq{b=GQZ`G*w~ z%!J3~A}bPYF(iLa%2F545{Aysy01EtDu2Xvx^TSd<1@+cQ`H#NEgu_sSv5F(SS9%3 z^JgjX>6$^e_Q7`^7i9-y#up|pU@oeT>N>e4s+R#GA(5iuoOH=T%djF>6CP8amP00N z84mwD{!J@$3EnD{M@v`b58}7_vyc(%Tx=5Ybk#^2$7w$BcM)#C$KI#i_0h86KY#w% zlz9>}$<2*b*s^Mtzo=FM1oVfFbgRvf%X0h^Uy0Fn%B}IJ1^%R+WvdnK;Hx=~S!3l@?cj9A^0(-6!aCNkjTDWP zZ8rOasN`vPez;03;C94`Lh$g6lR#we`s7@cn1oe)5A z|J@B<)9Ykp&HWjQz5xLSXGhyd$H({GPwcq3xz#Fc7`jDIS#xu9v$d;Z!oo-roEIo# z#ftY=23}7$d}LM4_rjueyDl#;596c$E5mb?t*tHHh7VVZ-wp*mk7O^fo|X7%zuNrg zo0zq|z2VF+U-*q{uCA_h^z_ctAIY;v*cffldwEq2<<_$T)-zY+ zAD;8jdY;{H{DePS)Ga#ejYX6H;3*Y3dF0~t^#NgFVH65MySt*IqO3YKaZ^*e?3(3u zS$Q>n$;tHYrw6H~1DR*1lb4JlA~(G}&QD&ZJ|jIjSbq*uXCDgf^hVgFV_ciG<=GRxOLpNKX&dx<>j07F|H=N z?5`ZNd(O3n{Ay~t?{VhDXED~=5zW1Qc!)K%-9l>&j%*D*iShHT`%#1ru zmSASX2`e%iDSVszux#c-PJh3Wf}-N~?rzbimk%F4T;1#FkXh)8Z-+*Zp;IfW<8jQO zr>6&hUR(sWE`^$9TdX*(I`#CfAH(yAVQQO>j*en&H!J!qmAo`q>|%Jq7pvLIS!D2t zX?l&H1cZcO;cq55&0Gzlet=fj7JiSC%K#hc4GsWo6IPj@-YWmFIYl^(`LmeW<=CVz?Y>^amDNd%@Q{#tKhk?gEe|j1H^wV*HQ5;$ zFyMV_PFC*0c1S&YMtA4VhiGnNw5=9e&#n1L9WMC6mHrH|XU}k44>t@~2Xj!MGk)2g zZP{(6vgCX_$O^|u%)sCdf@K*PK1$NM7r!{Y98l3gz7opUxEgql&oz|(T&>uv#Eetx z>$31!t3!Flqow96H(@vD!*1%b{KkkmXuz6mIGv1G`&4r&Ty-csT+-?Jv$pmjxpuI= zwvD-jh}lI(q4V3f+?HB5SA}7vn+>>nYCSKXT{b*C-|H(j9cXiDRfXLtq#WSyjiLPP z^_Sz6n3Poc_CaR>SM`_kll`pXz;f+s7o`k^kFXst^Rz`UuTi(gJ+SSsgvDb?;=*<6 zlDl3(v$me(z|q>;>;E!lY;5dksP;k}T9ve%+`_}lV}e+I%U4Y>b?#1u$1zpDx8&WO zl;8JC^yq^>9z^@Fi&$Rxn;$g#mv#3&NQ`f)bQ@FeBPJx2m>@S+`PTy6~Jl&I&!7tMuIKFNuGjGA{Pz+>#} za&zdBx}cqzAx#sNQ1wxJ&B@S>Yc#??$o|c?F%^!V4I87fmu%|f#uR_a6?&%@1M~H{ ze=*j55uH*O50pg;cnqT|o%Y>|_Lir{ZRP>D{k4XY7oB~=$nQOEQ>$yQD4#hU@O;RB zc-gTyGf(}Kln`LV_sWdLsqt}=zhyj^H{-fFw2CcF5*DYVt2tp8>BQ|7CAV<6o}|AN zM*gr3(bkxjo|PphMUpn{u4UVTNB`>U1io_q@`|O8@COJh=FumT5?&0Tdraala?Hnj zTo9R^GzxboCAjDzk702PYc}CHv=cwy;7`8ium0~#9QxP`%N>^j7mp1ATjwBZ1W9~# z<|Ful6n$o>{W`v_i(Q7=4nN)Q1QAnv1(uV;O?d-@6qYCMFZ&apK4Y4)H+k(Lbs<=m z*?pYSpyIC8ZPxG_pJ&@XKf+RN5_jzuZ=;thsiZ~Tssa<^lxOCUdI9l8yEb1acbnw+D+;L$8wL6LUl{E z@SLO5drj;9BN9UDV+-D~{vy$8j9rqcww$b2U0o&D)}N=lGo;6G#w0bFYYPaCgqm~O zF!O~zZ2T(KonNHpQDdG$F{(*+su3}&IaPRV1jp&S7nAtu3v1Q}ro!aptF8IWw1lzJ zPtDGhZU4?Lz42@;G%T4YWYO3@RPh@nr|&W*vc6a-%}XI69u>>gv^}*Lmhda+G%Tn! ztmwpGTb)`$xj{mXA#>2ZlLSQ&@DO=A10M~yX zG)xBztMUI-s@g%>_alJLOMkwh15BI$#5)aDFaV^wyF(G>x{|;GOsRvpn~a(NXRL+D-u*;gCqC zP2>Gda@9%(zwTO*`hrktczN-$|GfP7u<(Y_a%=j-NzdASLNYRkPI?`fLWW&&5B4_3 zV~2-Tfu?7|+>N_-Q*djos9Paf0$vu^_SE5h*5Pa8S+T0sQU)LMYb0c|6rM=4nSb37cZ`( zqM{z1oTy9Qa9ID_1UzebC{M>*Vkh@lqBU^xsjYmTN+Z6PZGp+~$jJ1@4AL|fXM4A@ z7Ty(wie*$iCO57&R4y%Cv$S3ts|ba&-;<@nLF0L5{XIEZtb?}cyZ*(jHt0}KeTMWwT%AfB2S>_p=cb~ggs;vmI;6KzMkBf&U zJU;lRac9~mtv7Fe@OEHaxsvaB=@IJ{$(^Y(T(dNNrpAtVSf}n>^3d@0$JG$^TVAh47gZ zE~{+F@$fj?mXrvh{td7zWU~r5W=cxRuyABtTtH=|Yl3qu@7qto!34PE?7x716A?84 z@ZIewbJ;h2^5ltXt%t}!rt&P1XRk#vcKXlUnRutoiA7p3KGOQ1_L5{q>5sWu5wF zwrcB^YUZ{t+g^oTD5<*_gC>D;w$(@gbgN~-tc*K{{^b@c?! z#p&q~rak1S5#79LevXIvIUt~eTc_6J{Ij25LuY5;VuEW>UhPE;ydufZPL?TUqOPtk zMEka$Z63j^mXG1_g{3%;VfsS0CpN#kxRIuIbJX^#hkM!Vq~$GW-v*;2ZjV%4kzU;sr;;jZzn3-HgpS1ydd7g?$5NqOyW?r&-1;@70QIp=gr z;ousbYDgdlH_dM(@tI4QI~<6ic&2;zR%0HXlF`t}rOU;eJ<%*N9S{IjB*FRh>(|={ z2c^zkqodIKhutFMV`AD?2C^vm%qVJVYWVVA8h#fm=HIGAC-@Q*^GsEh0+tYXjlKtCR*s1_i2nF?&;l(|D5Y4ga^S{7Gac$dSqv3H&SzdWOR0F%FEv$7Z66v z(9n>J$6F&KM#*3rLnuK$dV8#E)VlEBs0FN&q};dHoS(a&>L^cMSUhb>2@QEExW4KZ zb}Z`1D8siqtk+;LbMikLTUa4!>ceqX5tqFr=&w#rPJhy5sXpOTHJ6%?X7_>6Ar9yO zrEZ}!wqiC&XsI_9*TTZ0YO4VkzRc^#j~~MABB0MmOG^tMO}$3}waEv@;H_@A2em?L z%gevPmTC^ar}Ej~Kdbl{$QAz<)bcD;Fif!>ndeeccY9^YWMPf)=UPWR4N&m(%IRppi&(Jhcx6?r@3+C z*`r4&o)^dIvUOBEZ(ev4bZd``HZ%}Rmw%OBJT=W1ZB>}u2r9Nvi=Qkb{qAm`D_)60 zJBTGt=<)+)ebs=DIb)=_CQC53^zwY#!FV@QGeDYtfa`0l)OxC1%?B`!)~S9R4*};!eWHpaRBP z*8B!=}dfowt9%?Tjyavv`Bx4uoM#WhLre^|^9Tdn(yscda2@Xs z1423a!(QjJY#TID9!O#x+ivRFZW@cLIB3A590fC1Hc>Qwxi4ME)&+Ws*+n%VMYs3r z9stuCA6{$+yQMQQFp&P-yf-DC!5J-c>>+lxyJt9YB)S(fNn`6t-+Amha)2F2z2>xY zfr+;^=B4;jy6RwE`@&DULT`lnVj`D9mhnmB?5>uhbRNPfKb?oq>^B*{rL(drp8f4Z z#ktjk8RsW8Q*wB?qhfITZ)9Ss`%QMIQ}uFT>i6|hm)b)Tv$g-71=yz;uDyNVdfBad zN}$%_$bZbgwJeb2^?$#V#;d2O^3M#ea4_BvjQUl)8quD27Oc^5Z2kNQe<)f<%~ePU z?S_i+Yz=N)&`+Dbe?(#DVr8-Cc^b#X(Q|=^7YYFf_Qv*nm^xE?Bk#&M=<{`tnaNlm zDi`x91emk?pR=`_v%gRaeHkxCvXwH2rv12$wB)R$jW17y^pGW}L&NiXozJIsm)Mfp z<4F4#LoB;B!)K+L<&}b5mevf*@}zE_;_!nnc7$G5Bmo(_P&W*YtL~`IJ6lZ4Q#sWT zG)y%+oe3e1tSFObSo!ZiE+dJr^vaNTCJEteDy6r`)j*7qrg`x2vu2o$I9py}cyjg+ zj!d(wVZ50SebEv~>T~Qq0|$Dg_EJYlZJ&t{ql)Wme7vkfsa6O)Z&ll?pu+F)Sxp>o zTL-Zd;IjxHDis#`z1mpWxfswY&xsJ?&3skg*c8&&!@MR;rjqm@Mu8B%A@cLLKdm&x z98Hgu>Ki*l`aDzfZRM9s^U?x^m;U?}nsnyo4m>+c+#Ry~$ApF_3&$4FB6$sx+k3@P z<#E{V-Sf|8RU~XPL_gr0KhJUoa_QSzt`yV1^UHuIB)nV5%EPeT6hWpln|UN<94AA2 z-(8fM=#mvodjAlIX6%g)>4Zq9q|R$eLI<&8XBjX$KQ0YHX8W3@nuYD-b|YivDd}Tl zETzw(_yDXcwp>VdzrN*O(c9g`>(bDObzWT=Qy8ry9(Be95^t=xpf>!qD0CAwg-`L-ep+PJDtioHT?bSVhEC4N}a?& zb&{837LEf>%HX-T;3{J6;6&b5c;`I}dcSGiY2CU=i2~?J%yCLAN_Bk?{yhY#%*SfK zgPP3cJ<}zHBxhnGBGm$Y3=|+a0wCH)$uNS}E+r*(>a6S|r&q&y6UpfJ=X}@mI zj5tLhK8o;8D}8^a^1aFZIAQ0nC;O{KhO1*8HJdf3TRS`Yz*kJe;dS^QE5!5N z88FH5Qc1Y8snJd);xfCj;W2&;n1kZMj`+$4A9T)xfkzQ+v5jD2Y;A9U1M~T8Yo|S~ zT;x6fq+6eQ7wEc{E|>4;<_xH-y7Iy8K)H5PwN6h+2u1X03LTa`O30BNMIt$*r8zLU zU4%yF!al)X?cm_RZP&vyO+HaBUZj4-`rpIcQ5yDA(Ie>54yx?{H;3~^;80x48=gYg z+y=I3_QPGw_uh!5V%SdB984>}*cERwQkZI=01VS=ZTSAHJZ;&34Aa=qFeEx!8s@%4 zGGTJhn{!97FF^mL=j6N&Bt;1M;M5B+Ix@?U^b6X=zD&GeIJi%5<+{7Nj6gT$ z*X+trOg~!9s|EU)FFJ%UHM8M5nD@}eZvxE0_?t#A8DzCRBMwVrL`>#+dF}{%(@*!$ zpW1W*bsO9x!;2324V2p=%%%DT}K zi*do#ABnNCRNmg+U{uKEyo9nL*}~Cp4xkhe5SVkGsIT{??x^<;R}FzlAL{7`P=E&~9yHWn*d)0}2&xz~<7}`Lw#IC`)zGzJ@t;SqRxF&sPQzn^c-{%G+yr(_$HDsO z1Mm$IfCkPU5YMmC(I#xWJ(9FNWtNj@7{Z*iX*aoz@FpfE5VywE7mCBiAFB!@t#^E7 z+TkNRm-$~FKzzFAr!I&?1LIy=Ru+OX@4)Q%2nC9|&{%|QHw#N!S_gfd2W#%53nZkbViz=&MqvLyI0T1k?z1U!mD$bpqH)R#@8 zRMWNw$f%CvgS_ek^_p%h3=BdN5`$e$xugzx7Z*XTN(W_J5>SqJNVeYT4V*Gmc3f+n(Nr`YO6zU|{}$Bv$mS2^lxo(UZWEEnMw6_Joq$uBBW+(aDs z^n(G*M^*4-tre{e1vp2%AJfz7TWLEkuU(|6`X6i`)ZZSdcVRGpj5ZOt@LOd3_itZ- zpZnL4Rn7Eh8ZXh9m1r5EyzWdvH*6J=3hS+U92)RuIC_bQ)ciu-ygbyf$=g&oi~QxvMXP&5n;>GeC9FsG~i0Amzqg~ z5p_832=Sja6y4qw>EY2>ECn6sc~X6SebqvPt0>SZ!65D}d8-oZ1)WY3dgbxKsO7HN z^H4@v#LtWpbr*!9_yySC%gc+X`j!D=YCV9XhU3Jq+xqEVcg9IK4DqUcy0uO zUNkc^GhFhPoSa=#7kUwoZ)`dJZX)!Lfbs*MaIu|#r@;7_c4 z`_)&XCrk92nwr^VZYBenq|i&&#w+=|OL;8DqIMU%HPV|}T8iM0t=eH>VPWFmcnaY4 zhe9gO?c28z0ef%HZo+x-W?Woc6xhBfwdV&^H-#KP5%~iO7pR=w&^x1H%YlGlv;@

C-Fl-cvzB8~K=3V3Tfx24H=(r3YK9XzKjf_xw!Ln}820yep-ZA9j;!*|U9`=WIu!iQLM_hhBCknLM3^)hS zLU>I3u{k(6pi6uN_TL&p|KZao^>zq5AcnD?3 zakXCshyQD6DC*MRl6z3LcW&QCJ0wW{I^J-3502FX0b}!D&DP`O4*-QpCzu^Hy9q8}xZy&~GtmU;| z^8sbj^HStJ7Qw=9_d}-p_d{T?h%X*O^Ben%n&*G~@+$9}KVE-J-r9nQ2!)Br(bjf$ z2KDbS77*tP4i$)<*J)@x8t0punyi=p+(tu32e2~FE4jI2d$MN&0^Xb9{M(e2ly{hz zc7HP^LXQkFo;b7E(~Q$WfpX$P^n93rH+EoMkV$1)~ zxB=dWij|$+ua*{{@^S$bKs*rS;83UrTly2|wSdSiMX>Tf1s=5B5_DLD4VRRXk-2mK zeveB5jH}OMeyE#dhl3iHAs`?@5ewMNNfmgeJ9I?dM=Bh`5nYK7yFqPh28e~SJ3W%4 zQ3hY~T5$n(b+(CIXAYpx-4{Pbs$KaZuCTB*-8i@vp7`t6QFh6M~uK4YyyZ(JpmIyqs*d&MzD(PoXmW? z`ut!NWQ{sOKO}ng>cW)!-T6K(##D7b!I487|5I^s<o`0QPGxPTqo|lcZq>-vM9Ze9Z~!82ql(;j(#P1p*HqOoPZ*UF~rU zw*TBK1D+emKu=Ex=`YDkg|MQc!WvRsL=}rM@W?O*QdJ{Acd7Df~L`$K4G=)SE6 zs(fxc_zwXAc!3kXV|2NPLY;I9$2{hLNjNVLTwc8*13ezciBt`8hj~Kh$Z7le)uG9c zbVY=Y|95h*sQM3nJHj6FnlWGvES+95srXKS0_Ndbj&c6Na1Da*I0C<Q^A%GVD<05VWCwY2s%dNFjlqNSccp9LxS*RNl9=X~!@jk`c^ zR|A0?!ZqoS-(w;Isolm{G>B)&3?<{x&Q4-d@HR9sxPJY*7bMlJt*tY^pzZAJd>8j8 zgG2Ht{5F!T5^_CYope9EiA%*-|2Cdr+jEwL`$&G)Jz#Y4% zrzf0;AFpz5WMpJO>a5`H5IdM}sS&poq?i2T0G27=k{8~-(k;A;S5Qy@5Bs9TR5`)@ zKn*}+3rxVw(T(OYv9wW_6Ic(LY}Q&@+?Oz zg$T0>1iC)(X8|E05jF`4*Zzz~%X-=g?S0SG>^f@atgkIV1r@OixckMMOdX>mJ;B0YS@dZ8!u7-RK7) z&Pmbv%FUYB@JXO`gbzc99xgSff;N~887nyHAE0>N!=n*{3kF$5h~H(%{y~}nK+OE0 z3(L{czz4PqQ%g&&|H&IZQB_r~@y-EPMELS-*KNCr9I+%E9Uaw6Oj9SD$U^9{!ZvAO zdX!$+Gz~zw2xgFdKi5+h?>NG$F#?q29w}huu&!sDo|m(s@}l1W*TCru113AQveE`? zVZt^$Gt&Uy_uR@Vzq7Egs7P8~-Wc?710y2^$iuk}0XY=|iFyRm1Tacr4iN)(F*7r( z*`358Agm}+ZX*;{QBi@Ju^S(ekP!6SpjWw=E*vUu>G<CO<=&6b z`q1F@vDme%q(I?Mm@K>=Rymy!TTGIDZ$0qR~SOm_snp4EI?xZ4mAxO%`x%cEr+(C`_dK_T|?`Qc=w z%b!u;>9C#cwtX{jT0LNHjg*u$m7g=@8OaU~(HS&*pb-rQ zT+l16;aF6FI3(b*cNeJBBEX(9W?0K#F!3R8k`75w#1H_Z-(hp2^XzyR<^bvxeDNX# zYUki2QwurZaVCZ$vmAnLo`|u!VxMoftN`|=FNhf^aKu=(ekuiuLF03a7I7`D3HOuo z1{j3`$1sRS#FTjIr^EU)$d3s)ZB6ld_WgasLj845dg*^tx2d+z@h~B zH+afTP6a$K+_YVm8CEC%iFXR@TC3M{03(6upp^A05O~;qvtdpETLKcSDe$07M$Td7@rsAqx+HjgQZ8`?fdivo$o60yxC6Cr!2hATV>YxA~B&M!@W3fBY7|0LEHC;v%u=>|%MP&e504#A#n73R18=k9G zsm~StIXD=p-XT0Qh^-*O321fG8ygnYeh$DP$_%0u>WKe+z6$DQxQaHj)iF%g`O~v1 z=Ut#a+TM}2z%A2cq7`yiWI+;wyqjztF@|u6-=i{VWRhm+^1NfB3;zo1&CJZ~L9ey3wQU?2;O*wn`FEABm5q1@Vuy5E1~B1~*&=FSY&bhVuWZ`xXk(+W z3QrTFq3}@z2_E~@>*gm2YY`oqw^ido4tCe!-~8J&YU4B51^fDILV`9+yCs~Ppdi7I z|0Q=>+`w|hg2d3f>S__t4k2Vrq?QY=9=zAl=_xTWaWdJRe}j=<5hgWIN}e}=l73mV zQ4$l2IXRW92Zh};77(MqkwJ%uXKfFm+q}obx3aNe3F+Yr{hhMAk)o+kPE44g$+Bmz z0J5Extu6iS+mG{#kr{pC=d(voS#tj8)bskUsn?UI`d=&bbNbq?Tenc4xxn~WVjcpm z<9`mdaIe-@sovh+)~+rXnLNb;f-^9^p#v&WB3iTa|9_N$D*OM9vX^Mn?4qJIy@^9w zFmE8luJ(h25?C#GUN~@Ac<*39Oi|pjc?P z$RV}+$sX!05s`Qx+xfcnXmW|7fw0@?Sl3d($A;<+0WAGZ%qNQ5IH)8WY8ogFS{+ag z(C^{G!wWK2)fen5+4(Vi=54m!B9b6%fQl0*4Te@`<7daRS9Y3PM2l z4ckbdQg^ge0B1#W8R9@IV<(@^l}g4A3X*}WJiIWXnQou$b{9b^1`kuLVmuuzq>x&Up}(TqP|AFa#MZD>nPkRm%eUQEnT&Wh;V8`5?Sgt)RbL~HrbrVb5ogdYV9FrD?z(mdM@Qp>wGlY}MUX>D0H{ZF{@su(8g9jSb#ll9a=s4? zDR2fZZpnf);It5J>@*w5k0?eUd&je+dOg9O7GB&Hz9i!4@s-O=q7(RaTh69;p<*{~ z(Y0pr3X{@f24R~or_+EG;QB#3#Cv{05dd^tfUzNyD0&kFfS-2D`rMu{{#9}{VTthJ zz6Em8076k_=T==E9divIanC@mau$t40wzH6T7jFYRXJHhN6`R^T2)nrAWmpaK%a{& zCq-d`_<_^|X=>s?D*n>0O|Xt3F8)KiS`hqtT`;uIb`viR;JP0;3$akWAT{fdv1_)z z!@ynY|9#-z426a47&JXD*fD5~Ieo}?!{v@Tz_*~U^8rcfa*PF7hD7@jmCc`=!)R@| zV9*5Oo3Mi-YolcZNMdamiwwGg_Ksf4aWDehhaI5aa=( zG@>@Z^%E2hjyyCrR}cZg^YMqOKne-u!p%PrwIEvc`L7%Vzr5@2{(dTS5gezF=QheC z_Yi9U)DS=5%{DMr#>U4fLA8N^cy9jWkLLIe*yLqFpt69yiUU+Q0fzZ37_ zjmx(`@#Pa=-%w%1G#*ogYV4v)fgib(x4)(mgU;-G21(dY2=>eY3(o7@jgF3nXw@HZ zqX3aO#_qsMA!h>wnN{~0Zv($1@PFMF9KrzfIxa%?n!~Sq9L(S*dgDm)n5UeH8$<@&Xk_H%l24wX1q<(BL*NCu zSp*U`6tyNDxjd}G+4vAWos_inH$5?1&&{DcTF6o`y&cR(oq`8pgn%NX^Qf+^<(lR6XzHXISk2R3)Wg)xGv9@0Aah)eDSErRmC z{uQ7{aKC07ysI6!+6ufTm&ha-w$=z_4VTxw*xJ9H_If1Ms$F<#J&taJPopN*$pv zTzx_;NjbSL?sYI*pmD2Rj%O$`)tqh!!D~DP=@l_wAaH<$(%=idp-AuBuS^sNQT=xo zKxv^nQGRox+B_nBmudtqXR-8!l;czJSpfqP?$!af2?V+7rxIJh0i0KJ%D@2C?f>!e z4D@#dJ@;p;5qp6))C>B;YG4}t{4)eayE!=tKt%VBkWehRKBoOY>Y*Dw1!v0jV67mC zXaR657H}qzwvpuq!XXVX%mn-t#7u>6HZV4}1z+)Pc~wlkZr$W0Er{>%01upZHVOB~ ze2noi(P2+YChYiyh2yfbzdBF4La#*P&B)FI1|9q}P~~h3*JkwNIRph`06v2_ld!o5 zzTt~93oSTv2>*uCEv-150C6n)_N7aEE;Ixh8X9U*x8XFiE|=$pv=NKA?mX`tagoqf zjr1Ji{=%n0ce38rpTE$}mf$_xniXiV6xb|tYuClJb{XdFPr}r8B9wFb z)>e{)L0W`8`nh+Gd!DF!}Pof@riUK`# zwitm}q>PmnC%g@EqYZA)*e-NXLc7JO1HTe{xB2%3qPlS3?N@Vi#K;AQ*3U3_5%q4@ zXR@+}u!amjgW$Dm&JQ$DGF9@r5dRPP8*Wv>Wj!WlW?wk$gg0->R=e!OW<5=y)i6$XNwZJ=hn1O^2=>+sJOM(v%Zjla4&X zs%I-8@Pm9c`PT{cZ<%E@(aiIE>LbF3!rhC}mI95=A0&Us*`M?Tr|~Fn z{;Yqqc>U9#xCZxA{99g&&Iysu(S!j>?Doq6$HL!RoT*hbl`EPk#YJpC&CSgTFWNtN z@BpDBBLUjLe!qVCVl{qhO&KVG@G+1{0R4J*79Z~}+D_G>ik@wW0Z3`Ls|8{r4`UXt zBZHGtt2AT}8a>j8S8un(y{rOy37o49qU#T1QB;-=vo#mTD$2{JmY1d7+=PMRIk~!O za@s-*Kb5$aP_@ z!_wkG86YRnN&KLIz(#pjP;j4&j7-5_TTbp8G_U6Nc7G_pzHBvaFm@Ce9iYPIfgU1L zmyL}L6lu&yz{Ng&`h??*kB^^<^>y)({308nUTO92~*Gc@gY|+(rbE8r-B@E4o{8Ry4a&sjT6@q^w`B;`Gt^8r-3O@QFfl zn=98k(0z?Q_^ZhB(6?*xOTS)~63GcvbeFpAqzcZF6w6TP)+t0h8?T<+VQZ$qO^%#u z27{p!o^T16JA9WUW2m0Fc_)DC*0#2W$;r!b{saI7)ZoiW&ww~84G44~<^tdCyGP~hOKwC=~V$h}`F|=W(s0T@DFbPfeNC8*h9RjvZE7aY4tfTOTp-Y+2`-W3%Oz>#VG_3Hx|_7LP59>24eBN z_M4O`usRw?Mki$}z=9<%&#wp{vW zulqoH*vs6kk;>A9Iorank?1*D0rp+&?Ux@}+T;&ZuRmuMTuSoBTn_6NFY}zjvar=4 z(!KE}DOmLPoyhWQ>&BxzX$2qIG-nd6lRu6Zex#GTX&=Bz7xK%-xczIk0{Fk5;>WXm zun}+y@`{Q!Euz!IZ5;#z2h)fs9LIk8_z?p}b2Hqvl3Be8UK(f*fMBNM&n8w@RzT|s z0&*H5U1k9DxIkZ9Qql(|&FtLV2N_0s7|%dM3QY%CnB{>mL<`!pyn6LYv)l>^E0DP{$$D7@R_H#yvv^Dr7vq? zTggq&d6kccHgg;#jyWcsOl}Q4Y1SX;?NaDXQbNwnPJ=c{t~d#AKg+Roo(q3&eP~%S zCDQ#nl<9YOH+Y@eCMZ8M|7K_3=jTs{YXLBPYKtaE>*~CqfWv*uT`ASn)S!o*raHrQ zDw2kM;chrg(5o#9P8%84)D8?Gxt7Pr2yPf@S88;r$?p9B$-N*;I# zpbdgBHw)f*Z_*^}E|Os4;Z@eS6s?VUa(h+Phfv~Zrk&sIJ^Hn!Br%Z{YabyzV*!-? zA510eT8n~{TY*Xw*MwMJN*kKL4SF!}BhAgaDcD3NnXDwL9*^rK4a*-d%s1bEKYOL% zjpNqplPcZ%p8#kgZX+|_3z&v*d~k{ilrYFO^P6ux%Gke_MBsk( zj)Sv>8XXUt?z39QcRJGL=QBz=wa!9Xs4xoHKO9ISR%P?a5Z>!p1!`( z7ifW}hmAtJ<$b4IqnC)Scu9;ljq{^&_IGys=k{)r)XNh5dp#;6lOB3^!!BKH8c+8C zl+m%V$?7p@@Eby61V%_8Y>%%{;dQ6pw>{s*S=EcBWQv{8m1_K7si;IkXh@&q!jen* z@?80B<;S;XJR{Ce_Z3`>1#JW0b{H^H4L<_0;_Gu7XV%x0$rR#uznwL3C`22wx-J;R z1Q_uY-Bpd!2#Px+x^}MQSsKotD>|zE>%=&gTpPz>=`n#rfj_fMJ-NeGX}YO z?^oXJ+uOINt~S}pvRTcb33wac;d#}Ph4TXGGV#J)I>X{Y9OvvjKQU!-~15fKq!H9?0;yFQ|`rN6hg z_vN?2v-eXNsQ?>75)yL1;$Q7njKRuA-e;_W{#6VS+U5WV@BuLY;r|?njE9#O3GLZb z!~s>zD%O&-#__uT0?Z&b@jfdiJND2 z9d5MxbdN2S;jS)$YgD@?Bd2%bkuN&6?j3snPAaKedS}EF(~=%@I4b2GhSE{w^Wy)9 zy*CZ(aedo{ey1_-@<#X=O?MuJbz2<2;W2*!TVTu_n${G>Uh@M8tMTf}g*@ z_~kAC9MPvX_g}w$-R2|<qJP0lMTIBw_cOMjdvN9L>vNJYL zD|%zqt_*k2cAaoWg+auVD4A=bdnQc+SpvBlUKi{x8F|G`&A98Chw7O53se3`3EcF? zvb{QIvhPcI#_8@5JM++F;o|m17A^X1?Vt0-%lO{JCx7|#Q%?7q&&=m^bD!aQE$&T= ze8=-cqXoXC_eMo~H)VuaM})~Ru3lCDNO+cJ72kBn8Wk0l>N6KS&i#ch-l{rW#T|c+ zJo~u0jHg99+ACAsHm(mD;n4RMRPL=SJ=zK9UleR(i?iS3s$|YA{2}6_(yOBeMwzF# z1%+Jx{!tOWCRUDRJ)bNN`*L|$icEZ26fQS6vRSpdB8_L}@>*k&`$V)Hl@9Xx;M>H1zSpS3GUN4&ylCmW z>by)PykY|(Dapw~k|lpoUwMN16z1$DAt8~f>?@1w@YIPz_2-iIdVdhJeAvkK!nAP> z|DZ=PKi8zOYkJSc(ps0e1qnaB@#14qm4DLT+3Y!@=%2<})ZDI9?G#tU)MXiME6O|N z?lPzH?ndUyRp*;K-dKe*uA|?i79-4ks^(#nApE4L*IhGt#cS}9Gjox#jP{e0Yva?` zwI3QyJrhn$XWS3cQ*K~2p$vMsuJ@|yJT%)@z;I}~Au{{BlbCrkn+Y$|nu~Sj>u|Xe znWLr+*k7oMEUsHbfKHTnfiu;Z$^>HWSk{&AO`s`Inn4-?8>NSE{_emaCq;p2wj}K!{2@ z(t4@MWJliGq|z=oT~>YGwE|4~xmB;%Vt+9CTqkA~gyn`yLx{g1H*69GbIa@3>F84= z-PYI^(dEd^;WQjmyHT&~bWp(eigoltOn&3@9IqZelfTbAa7;O6`^S+{g=9Ua)4?1R z+{*8!*GW}TVvXZnQ0eFMOlf#*oo)rUt(Cc|Y8Dx5y~avfL*4&^XW}e1&bJ#VYWz)f*6+B$thn0o{>2{|!Y)qO_RM}`rwX2as zRO-m?*Y6{@&om|Mk&p3>;tS9Gs64{1x5-+0ly9GQFM1S5sB7nTKgv1blFF>bSgiPz zmo`q5{`Y{%h1Xs=zncTH8`7%DM;B2m@nblQ&Jvmm9{_OFVU>|tAkAp+>awuTcyUeM z{-VKqC8b`2EyA>?bAH>7Y^kStu5w*&bAqOI_j%d2IyOroX)5uXK27{#x4A-Ay}T1$ z?%llQ_}w-K>Cm-8>>?9)C10QT986a`D=wGTJ}9g;ku0Cdu+ckC?E`aCY1CSwlDu%i z#$ErXQu^dQ(=Nu<0Wm+?X8MQf2yuAA}tW5X1v1RqDo}6WE zCJo7B9x0D*I%b&-v1T6G)!3_-6Z|GRR%@LI%dr*?*VoUpJ8fGw+u1x%_y1C`c2D7* z#E{LboSa5jCE)N4?1FW5brdcxt~>Ez>bx{VyMGn!+(|0W`Kfn$c6L0htO*WtVPRpV zh32^T`PqY`LVgIn_0VJ8<02E8$0!)G2J1z)wNQ4GRc@q|ja%MT`A;PyVVmdDP~4e4 zk*};@r!X#k$;x5>uhx$-Y6b##(w_GD=!vX}i#HU*rW6p`7hzbKu$RSv-YAa4zKNHX z+305F$+ERV-wW*Sv?lNssQt0-F!LC@AoPs_UVB`-Q%Py*+Jc&kOG|#pYO2nB-UMh_ zb2N!+1Gh4hxOGj-pM#)(^Y&^Ev&H1)@d1WCf_BP3gn&m5bL+qXtEo{=@{ZDxh+kG~ z(|EDZRq1(Jr|sbBk%Ld186DZ%)XPtYIZHTVo3<+FU(RPqTEiQ(S|V5BaZ&ZQh*3t@ zM9pg@Tdp4My5!fUE5Ayl>EVSsYT0%t9H=@PSy6VWp6s7av3z?Dv@jdM)$+|QV(gE1rz+W27_hCy+0%!H_k6u*JpnfDtD3yVbn zmlI^0Xfl{zzZ3-&0FF!o$>OZ5Owy1A8-9>PWd{=l1``olb7Si+hx2``K}8aRX-+?H zUvj^jR?j#tt;cfws|Zuaqk*fmTLj|Ud|Jd`aLl@!pDFjsdNU|>)^68(3Ifo8lUIB~ z)v)HeVAmw~9qS zwu!6m*a64j)xM)1LDa?$ERGyTUg^~O39@GTd~w>jN5Wm~87$6K%O&42^Pk+P{P1jk zf`DO%T*||!UllpFTaF6E1_`B?>>CcOr?mn3nwzfIRz`u&dLUd>2J_qe7*Fa^koi=t8IJdrd%hpjXt)@)=dfBEFVD}F^q zjx^(IeXM*&S336I?FSYKYl22Jnt&3ZC)R>0WWUhQLk4c@)RYgG`P1EA_eP`kWsW&r z7yPr*m5QN|iKm&_`1T9Qs;ejHf2a3t3_A41Z0&`Bu2#dN=4l90sIEePopyYSyOnA`Elu?_g7jSjpo|bN0>r) zwHs`@%OtXf=51(>fPd2xua%lSq*(l0V}a|ThbkMFl|_OP@V zX7m5re@AO_C)$}|%A~1`kAs@{Ij{>9ge?F_$frU-DxYmi5$%U=kh)~#<(EHF-8yQH zKilQcLLVv#BhSyb8g6bPfZLqFog^?aL^~pX<1ru(Y3^KNsBHw=V)(gv{_7jd zN&yB`ZRj4bva^Hpu>tl?BUpv+U*Dtv{JXK#e|f0;{=J(?ec^JpeBCbc&x2@Keccz58Ze<~7VKva|0=6L&2bMG^)Rf;JeY9WoBb*24Bp=aQMt*-)fb| zfg$zqIO#1i+RQh7n3|dHR$B}-Q_-lG>|I=R;hkWp*sJHK&A(-av)16~qXEf#0_|}| zWhz{g`jW?by2GLgvMz4FE1qQRYHL0`9(P$$~hV*e6^c35#iK- z`h~(X@nFW|^?Yc|+lz2lys6EsdF*n*ablOA9hUh6rtsm|*1V zFo4cxB=Is^*|!0od1iXrH0>QgHu&{|F7|*sQ(~s5@%#1X@B&;#)W%9RfB8PE2woR|n|6&rEUJhWiQqc% zE1+Y5;%T}j1m$If!MSc%74&QrkdwZfx-nQoWxl>h%3;9 zmXr&gpMI|e%EulMU9F$^J#0{AU|_(ZY{gSA{+_BOq%S024nRoZfcODy0$AM)+`Dn4 zraV1;3wRd%(0f7T5Sx`%Hm!mkZdK{VOtc|r1G+R_X6{Rs3cFOS6WR0pIlqX=<;F`J zYQN8Qr^5ya|Wrv8g)SE zy{}$bro9_MOLo`UFR4}1sAye4zv)4rlh{fE87AVCl+d z{%_gr6URz=LpF`h4YF zMc zT+t*eFYhr_drj~ zZth0mr8)d3@}|`t&CEZt!d9v!c>oQyXyu(y?aK0Qbb!p6o`E#DOG!yAqF6KU=h8YADXH~xa&ncG zm2K_qi{0A#wzgYwB!|&QvUn|gz?x~*RITpCo=qaXF#!{K#fd|gRL!JE^81g3Zn6<9 zuchB_p{i}jzoqx;$(4Z_aWiIW#-^)dtL@dn{EI+816kn#eNg#6zzZdQ=u&w2ES|y9 z**Odw4n(F#z_Rd`nwR?CPT1dfo!Sp>IGY(88aM+lPLI33|Fj+>ct&Y|88`;=&P|Jdmrzw<>S6XVIq+h_xTi?FT0`o@nChPTU*TNGLSW%A6<&=!K(;ZZBj>FLk07}*0GwYmk4t9>G#lI`8=!hXV4wN%BdJ;;21y-) zNI$F~5DIWS6}vJO6Nv5$oq!iFUce0}zhPI+U66(&p^=gXdlg{aB3738z<>&35Azp- zNdVfz)$$Bg^3a!-o28=7!RD5hfGPDp9h2%NK^B)S+oLIT1)KN&Vy~y z3{rakn>Ud>qnvOjnr$#u0-?6gcu*7c-{@PnPCcv$Mcsr-Bzb5a9gltkHJ#Ql8!M~R z5UH^&4$r*`MZOGtv+&ZTk{G(v&DVyjWflF!#EZ*J*;S`i`K#Ub$4)Io2Qx|}RJ+J! z$XAM9fYnU;i}S)S3)pJ*jCZAl-n)0N z;fC^+M;`(%p%<{vO%X&DSDd5rKof{!tj)zM-Eef}Z7K-dCT*#iXv%048wP>YN z)?ME>_cYhQV$J!|Yg=B3$gj^Hepe)&dIzw;g<#7>p!2e5icPLqj%{8OEbK#7J<%5% zo;m)VVa~5T#ddVtt12PJ1GHN&+-ED=SvI4k__N=tMK-m*!Ob};JDVFpl%0bEYz6^F zvsDCtHZn4|GBo5BIUY>6TBF1$!H$2M~W5KBND$E^wy zpF}zPH7YcyudsNfiAPOucQ|?!7AcJ2tkXM^-kpib29zs1)*LoH?x_0=bJ#ehuTiCJ6#Kya`13%uIE?>1yuIL z-_ydkKHxSNe0HO(>?)}4!G-$|4Ig!LV`5_qyLxp`nurJ-XL|8J@`zQEhFqgAM2hS19h}Bk-l`3T0EZZuv)n&LqJp0<<;C9y`Qgg zP#@>yz1aBZD$~&4&=WWFgcbRr_`@1y06Kqv$>c-+j(o{CP5y>_sX0u*HS*=fI@Z6x zpOzcktZ{?p-i+UBYFx?~%Om_>)*>tYFT@zam2RbvYH_g@U@czSxG87Q)! z!`$55e%E`th7JGx{Q5^x)xRS3N+&*jBHkT`4jf@dqyC( z@JmQU;g%L1t*EFdK>iJu6ZJ#ey^wGtI^~0h50yeu|44IF6KEQxvDa?hBK@uD@86NP z>y}1=GXcF?Gh~@*#=7`NP;U$)FQb=Vgnj`6L_u^HkUc=Y-h_MnazuoEPp-zq=g$=& z;8MDtK84753%Gcj;ZZAfE?~MEiG>5?!zg;7%{(xjfF#Td(kfY%a|9Vf{2^xl6pIqm zVCYPUuBfOINfn$3ZkYxmN#jsBxs`&%6(5cT!V+vEIBZ)iL{Uf*BOm}ZPaOhnTX(nR zuK=C7&1^3M;fPkcWP>As#ZKfv-PSZPkcJkakwTT_9jA7dy_vi6PRog(r+(Vm*$7ny zuE0s!8>OY~U=;P&2C6+THqkqDC=2nCFQs#!`iwY{Navz^hFh2zPEXPv+~R%a45|H) z+mS9S{;yjV~+82ZOG^ATS2g#2lQjuu4)nMm);@ za15!f+tH)@?l|Z`j7tcnXp9R>NEml~92?WYZI0dHh)4-=PkiCe93ym^h~4d{=4NL* zx^Auhd*fGD?*fJ1gF|)8};@2Yz;$S5% z-M_hHGwskMVT0r96Aj*2UU6}2Ff|?Euk$0npz$k#-!)t_NWJjwo7C6Ht5*fVSpu8r z1G@6T!Sx3h_aY{u3$IaqXA>d48XFtmzIU$(rwVFHwl@yemCnr|FFHz2f+o<`=OE{$yX(o9J zWuNIFdN#3XlxYyr7gZwKdE!KyaqWio`oxJ7NIVNGajpzje*gXro3i;nCA?H&6<(q@ z#%H3}WBxmi4yp3^e`hADJlJ+QBh4iQAAFF+>lZd1L1h&`A^Q3?-q&7;r6?ePjiHMP z@}1F0I{XFzED2Ju?u1KAs_c;wbC-8Aii!{WlLb(+pfCsTR7g;;NLx<3J8QQd(i{W; zS16ppe_bmq=2;#Z5&|L(O{|UkJMCedNh;Jo=3C%c{!)^H=Q;{67XZ}4e0+H?R8Jf` zW(Y(9Ogj*&sQ;zGK`cXfQUJF5o1Kh@f0L6GT_hoe+gH;FDwFD0)BlLg>i&4x2q(| zR0v3Ez+DUbPFyjODnp>QhMv9wBr8r%&Z61wnEHcA{0M6vvd(SpZ6y8wTg8tY>)9DF zZExSX6IX5U?&U-AjXq~Sy~x9%f<%lO>6vDk&Ap9%hFYLML`ss^3{zB;&BrbBNJOsLaj6gz z?ZAnF3So#tHQ>wyURp1db0^<2!{ z!+<~44OK>D;pWUOsvduPNL}UsTfVxwnrx(e31mpXe!#Xh+OfP>ON;u@p+kU8W@cv_ zaJK-FtCc1^0bnh4i1xPnLo(W%%W(Zr08}`GZbgBJ=7Dj;PPb#nuq7Ly(@AVGUBssU z_U##1KGF&bt_ahqsj0;55F&0o&xNleP~BO)Cvr_V?}*^w6hMCgTL+^nMfYKe3KvZA zu|pv9*aiOCnVp>t0uKCv+5kO8W*Nuj*>A`O8Xn1w=LH!w0f0Vg=+_YEB7hj@P6A0wFVVfG7;xCm|gVbN(2x`iH#zyHgZ0mPBS zl9TnnwLAi*#V+#H*nNC3@vx)2TMr>@3j_O)8A^HoMq5jvViV`FO13eV1GoQwEV2e^p{+WbEnft#4@1WViTF*0^#H zK!vHcY?|>==fwC}@FL-f1A@pldHDmEmH(4RE~Ow>hUDe)wQE8%Gq=>9ziVr|j6fC{ ztNYJAJp;*GY(j$hby)y{&QEl2;xmh3zg#IV|Hc`yTg1w(uB+?I)oR}`l7)tN8iVD7 zKwKo|2wg9uFdng<({z&&g`Q-oLkv$wjn;cIDSFyR1#+DEahE}Wl#;{?1psJKB~q$B z$Xg(i**FU*^uHx=B?XB66x7yUUtjM;9;4yuDNDf~c0t6XjE;|E59)*LO-S>yvUmHt z_ge|^@&M7L>la{i)$`DkO4B5$V?2TyIsR+z-$H5JQ2NU{iZ#Zs39B zw+^g*oCs3M>BC3D)1*V_?N6S3hA|J?pb6ullTWsVfv$8J%5)?Tgei&Z2aXR`@5j}g z+L#bpORVl}1Ld1YhOn@(zB=$lW@Kcgf{59)dSnl` z?Zve;AuGveZnBo06T1kI7xD;&7dF7(g}#h=NF$spykmAH&-uSW-tuLW0)Wv-iJubao=z8%>RQDxwJJ`<*}X z3OatIHG-&oV4vZjwp$$n4je>s#~~~wq@}fkJWC9200&NeefSuj!m??M$ZbXUn3%*M z6?>1U=)K;^;d%Zd{hX?u2`+IYv4EyU%DVRXEW<)eF>qNK1e*Flmz;)8H6QIL$P#KfStzU2Sk@5pTwAuu2Bp(DTk&|7LpI zpR&S_;KmJc)1DI-{`@+F454%BH@e~b9-lvhTi$MA0FDOTK!-P07e`(Rnor8i+)8wg z85tRg=q1a+A_vg_xuMq{;QgREpaIGpw)N(WR&a^&V90=HpRKKFumm?6KbE9cXj$ar zH_{AcEQCpuD3-X6P>WG2(kl$XbY;>X7k!CEDGOFz2&E0^EksTaKfOX_)HO~}QB$z0 z6%ZR)ax45-E_ZkrKYDgIV12a}Ckc1uBDP56iKiH~lije+8wU|qb4A+k0&%PKtt>B- z#t>UZcTdml7p$B^n_+PR2+SGddq^N0AHVkE{JfCrndxDTqwZPYn4%%Dn%?FUfFYE! z-x<0MAeKZ$w9>tLsPkv1lPnGkafShZtbYlo7O+Z8ug+%in^l+CL-~9H`)hoWGG@jN zDY*B?qIGIG_4e)CweVB8xnRGh{(=qy5`J^vjgnnZ$>xI}PG-*hn=QgkJLu}w zb@XFs4lu zVk`~OG1l!`f5uOgrSNIV*s=auUtc^VnEWZ7z}iMpArZqdyphBSFWk8!MZeJnxIVp% z>hBiY0e5)Dlwk$Fe5q^r6lX?67>D9QB`UFLXLIaI$`ffOhpM)ox_0fGS+yH-wql zKvGS-#E|wHcE(%(jRzYbS{nJQ00|;F;T1ZL|B(|4raxR(<+Yin37OxxpY`0T;Nd^x zI+8wU_+^;A#$BFRoe}wxpCET4^QrbiPj~I*-@{&bkqBe*G{ekNnAZTOGrhlR>dYr* zl1wfg-!-hhYOV6HMrp^re707_z1+UH&e*zUf^&LJHt9%6bA$+Ax8Blb{JS;5mf1|m zBST~xRxvQjNSt~2$wKeys}1WsQX9PsGMMmb5QbNoOrMuaLU`rV$I^RV?dnV5k_?xad<-e9M=A2!b0PgALU2kk9fx=`DB zuP4i9+lv+SG$H3`QQx3vp@obMRQkZL=W#7PL^lsPEc^a7${N-zhHmvSQQn&>s+z`R z+ecOBsrs9xgrxKOUzX2mu2*#1$5f;ip8Bf1cb?kEEE(JQmo)D)0}uoxsWxhK~ zdfd#~J<(@xbsY~~x50;yTxZ|R=6D)vvV19Hti^SS)kYz*TfaXcED$3kK{3` zX^xz{@GX)@OU>jzsb8`jFE!ZXetVz(4Sk)hr+tnpnke_`+3m_F={yNRV2hS&vMEmw z%c(l9VwG-KbI5x!%r0>@##v&%s+Cs$NEjQ~{B-JvJ|!PN_}4Z0oY~Uv%Z{oC`rl>$ zL^FUQl-iS2ZjsO8k}5hX*Kk?P!gunH-!xyE)BD_iUvqAkwvgd_)h~BkSu@=B96uVk zai3G1&Q=eBSalla=CcgvN3IGcYqP9XXVS#@q)*brxL|VB`@?@MI_v48o&$H12@fDfQV&=girXorcJ!#L> zH9B2I@?PYv_h_2AD2&IiNY&Z6?|Y%JXd(C@W?2=f&1~iRQ|1PO>24HV>5yF?E`~Ay zm|7s5_rIIK^ck4V$4o;SPMApGqRc8T?>^21QBhqaXOn~e2sU#zv-$X>$G)DWIWY~^+32B z^d1*k$%?2q)w7D2v762xe?kl4B8Z-nl?xfQlX|FRQoEc!ndjJga&nZ_RqE@^qsq7^s83<5J&4SY+xhX51Rw*IXdxoCp{uLx_A?eb!FpcqUh8&sDLwC z=FYbM4?QyQ_KprvclNUvM9D}?tD}`kQ^-b6Y>knXgJ>DoqcF*(n3XsKp?VtZaJ5|YpJ}h{Di1pNSY0+2(n8I| zUKE8(N0^^K6x;!3PEK)cbRKB3hik!RR_xgK@-{6+I1@+hz@00T#>GNA7cZIwh4gUQ zU%V5v8a&p(Sz*snFqJ>(yTeVX&%u;KxvR@+_;Ni03G~{l&FHW@!+vBp64Z*4{NI{yzwv9qhn$i3uOz zp)j<)fI6(~1Z_&{p_%O?-VirURuK9)WWzL;{3km@CfO#qq@#sf}Q_A6|$K>zs`*eT5PJV<7OG4X)`&sW9zg3Ap-NoHu3hL?RN4uIu!`JV3 zb?I<;p|HGxqQz(saGwfCb7ds3A;L%>a0C)8&B4cl@$vD}a&oM1->s?-yqN057t3D1 zhE`gg<=nN@yO=Fuzc!rxZYY=8&7EA{{oMfvCMARp@>7f7Y!l|8+iKNr=-r!Cy*_6) zvHFEG6+@d4=OOQ2_0S80S`6~h?Job}0!aSqOPN~O>-;5CkNc}ztALPwl+4L0`v}AX zhtPGPcDpJ$M#+d4nkJ+-u|y_sayniFLPH-t?^8G3Rcq1htSsQtQ?$9>?Qr#8$8Z0D zfG0kWi&Fl%t=BUd2<+4T^SnVr?6{Q{*C$F(LN;z3BO5fB%}htacQ~oeDYnSO>pRFM zKwE+6Vz6tAqr@4RfJOye5Py~Ej=3J}z2kGejy0P}mz|oUjn1!*CHYA3YH_Q2{4YZh zwK=)!35QErPM_Dg7s_Qp7~$Ph&!iRbe)D9_&RnW*RgPCmy@w9BbT3IXW%;a)6J|=( zBG97)&+m;x#|!oZ{xS~}1CQA9p&@VZmp}K9{2B1mbn8*)viuWneCy9T6JNtnVQJSl zTf@%V^j_KODczE)wbfzE^Fn`SZRt>sv6t77jBvPQj%@XxXw;SOOL=nBk;_%lX!mP$ zgzaY7*h=a=lPgjlf6t{u@Oq_{bK9sh>1bv{v(D9@X+Nx-c4bLqOH1t09eZcvk^X$^ zFvV1wuW`%qZ4rg?_e%A3Ai5ICXYVjgzH9M*j_r`IwrL%}p(|x&Wty*%i~B50^MUK; z3`zr#LQ)^8t}J;`(0ziB>JgM1gOASL>OY^Am4#7PJa7#W|Cx#;dKkzE0hizd0=hjY zbTx^t!*#)6x1rleCd#TUO^2GPfQ`+IUfKC~U|>P0L*^u*7NE~5y6uNrnb0;sYy@jj zcUREg=lM{vDSNgqpJ|UR=vnP;}j;97S`Z>FAm-s2aziAz6RufQ5y&t?l5xIbuFc&?3O1 z#7q)Wtx14722*oBF(cD&%28ecOm;;J+J27fmja?D0Om_?_wL)bum3Ah(R9>QNk~*`_O{cAo`gKgsbIT^De8FSGl#Y9AQoxHh~+fheH2RsEi@ET86y{9F2TxaL*lV37L&aV$t2JlI2O<{Vs z!+of6g?o{G!>e2k>gxx}I*3^m7KyWAaK$F6ZZOQ-$3)bfE#SL%Zq{utZds zaymX`_{7phY`bDC`!FIylR4Qy$n>tw4^64j2TRG>7UL;P5lewN^q+a@3Q6)ANwRID7I+?nflo_?%YvmU;3K+OHUQord1Ev@eF;FDtTzcFfQX@1_}=N&dq1x%!B-KHwA5 zGXBRsVXEqZZ~la$=i0-7F@Q;s84)lh8cv-L%nv#S9w!=Pt{7oWlu)FwG$1JgA(l^& z0RZzS%5E&)+BYpyK<(h3G?n-J{8*CZ-UT1x=K;2}=L?NU;1K>@RS8bRlM;PlQiX9X zVh6a+yj_`AYhpFoe%^y4sS4FHvB5M{Y^riZFwqR5itE`8}IKo<_mkz z_)+)dDBEGaB#ZOCksB6*XjnuP?$g=bji*}5?5v7%zdNZdY^I-gd0Rm9t%cozA1#+U z6lO)0hFEIA-l#aD(SH9%u=;RGk;|oJxVgx{=@}h@2}FMI^2dNRs!jT5cZ{s14eiQM zh_UKrC4Okyt=gu&O14*4N-$L7N7E`|z+jIQ z61txJJ{d5Zkb_a-JekvXa2uq-fie8P9vCjpz)#+NdT1y+{at0tv+15!VPHTNef0J4Atn0Edosx9$T1F>co;jHafxfFJEpWrkieke(@ZkJx4PlbI zi`*+Ol}M|NzkcZ->^D31VSVmu$^J%eEZ-`&T{&}UEjt2Qx{1I8dy$4EwQ=j+w*2k> zigi@nh!rXFK2k zVQ+EG2kbq=c0!uHu&OE*3;P@_dd;y{6VmM zL}?X`&qck<0`Ycp$I*21Y+qM)ENa^oIv=7AO16Rwx1LdIr=z#(#{}cqwZ*&jwaqKqT@>CBc4*i`qJJ~c0r0=_EKK{ z6#<=9yI*qO6jr+SW~TfNq%t{y%ZgxR0&k(wz;G4ktFivPk9_>z_?2otra1n66fd+D~cRrm8rNqRadu-ow zgPq~q_ewwSYeR67c6yMGMUt-EQz?(qa}CxL{n?Tdnr z9JpGrza(5_>}Cab_t(Fv@gID(XGLYIW^O$+!Xo(ZGZ~ZKB=mm|o_fr7c3@?BY9)Jz z<~#VvabNgyCt>I%@i&AWAIu#|Gu-|a0?38I^PkH=<>HzPAW|qh_0o+I6vO6QR%i+r zwmQzw`q;J~du$WgYI>nPRnF56m@ZCKIo!z0~z@MZ%Mz&J+*Y?9x zOIg3GVz|Cw2d6emKw#_<8wa?YCq6H|}eaIxCYg&}F~gWSU07li$e7 zbMRH!j?X=LjRwWm4^DCc=T@8a2K)7W`tfpm9QXj1Og`emGTSbUL9RXZJ%P<}D?Buz z8fd@o^0__gwtn*Il)cW|_!Udqan)zyCQryr8KZXBSnYNyUk1@aIvY`bdo(fgGARwu@HMh;`sp7;Npp%fl6tgg;tf9lwi0!rw52pP zp1Cu-M;V?BA1hz~_@t7IC4ctfOcIIJJz3wnT;#>A+jq6}#zj+<&mx65(+1z>wG@5+Bav%MkaQy|^B+sXI*8ib2Hm#TZlN;(4?Pc2-zrO*HM;#_jU zR=c?~{iTBx7K71%Z>}`_YnaxY=%h1E>#!&i`)s{I9m<;x%gpEBIt_F9#0Lw{Y}+3G zMD?)1R$r!fIJm_=S9X1vFtU3x9NBXQuO!lX>#qO_y{R*nG3OEAjI3?MTD@1XM*I58 zr9)45vtV>EZ%nj%r)^`g-xm3|d(X~NHk{^qW@*8(rN#B5%BrF5cB|Lx^QBvihbA;paNW*$8|C8N zzhk9!CG1Idv&1K}6aho|c%H5!AvB`b7E0bk3-l?>7=X$Xhvp%;4JN)*`&P1ly+aQS zhT*JAF4=`sDMr)Y##3|4qI>2q1a!+L-?=Ebln{6~`5un;Op{!gsCv8afsfL{JGw`d zpINos4U<%F*NELd9CBOG|6>TESd20-Q2!y}^!P^Gn)ZySJSV!mN#hL}AA8ayDXPQN zXDfb}gqnSf+x)uoKOOU-XihhO??U5(S_x~Qo5EN6y;GJkplvo_^@@ul6$ zQBWp^oDxvt*0&uc7K2E>qLuEY2~eA?IDfX%^WW=N z6`_@PRxu@K_A9q6JMZKn)L14 zFogA8X!;8Q{aCIqr=@X6Dds#UyD^(zMhATF~O^cI7e_`)~-pjf>Mn~!5 zC5^04j=Rtuiho8WRsa0xnTd_IN;}6y=CkH~vYvi1PnQZ=gR_IbXH+(D?JNCO=BG58 z!1KDoI^LrC;gti$Pv6t+N*2IpF$~t-;mIUmAUiE!5brcxYH>`HRccg|PBXsnd0m!- z_W=uf`cD;oU9E5diKzgTGIiWW;HJi9H_S|OrDYiu~uf0M`B>_V@X z7*c`h&)e7;$x&%bac-+>E#c3;W^_knoq2MkM| zkN3HJ8of0o;+tcFw~FeHx6#S~lLu_W-GQWh2#ZcpMz5b+JFJX?=u zw!y%aKg92y(4xEps&sUSWi0X61fv$h9A|jZ)r*Gx9vE)nJT;16qIXCY>=ZP#bn(G( zi{+zNjbRNH7!c9315Jtoyp++8IX)FBZ%#gv{C)trFfzUb(3EBPX&^azP@dIQK zD9L_6SVTgw4lfHAj6HakXbbl~I(G^)mD>L`fCNzKfX*SYsttm(!nc~ay+Z-NPXM=w zIWGZjmm}I&mF@$xi99R77f=SB5Sb>jK_Xm32IAlKkz)-F*?h#D4G!c*^3!l7ECrPW zB_Tst-+Sio&vTX&$!_fGG6t--2Wy5|WA1KS#rI#=#?GsPA&!WM2$`~EZ`1)-@~5W) z<~WfqTVX^3q1?4b$Ioeyy@08E9Vy$H@0e8(gXuYKcyWyn5qPy8>S8ANE;(AT*>-J8?4Bz}E`@0<(i zv`al%aG>BUwoq?SuH!m4SdSS5Mr77979URNHNQUH@WZ55!o&cOKuy@Npwc|VI#ES3U|ft5yjGFQ*y9|vW~sv750qf4SeS_)vuj0~ zLoCw3*Cq~|5Gbl0OGnwANXP*Q9J(>wdq721oT`a`zNi9k5wg`mdi{GG3(ViVn%OKV zJ@8K^^V|<(vM(7zj`8FTcnBfv9bq4 z+Y}V$Q(wKqXc){KkANbSxY?tvt@-ND(nmF6a1ue14Jeqw2H=7wMA)|az-plr39q<3QQWh5*bNk2%x`#OBO1At0tb9UxBc(DwqOc+*zxVtS>6WkrxzOsAqP6 z^SXcmf1vPtv)umEKV1q=?v(wVBcm(ck=`c^HzF&LkVd#X0jrCTu*eujua#1I2?1wkdH zUnNIfy0f2xz6vQlBbLRvE{F;-?7KRM3>Cs~APjsXvsEa7TsC9` zVDEx7hB-D`q}Q((z0`fl*}}pt!WDdO*ya{u58BUJf_02f1_Kt3Y6%2MaB$3jt4CkS z#}{Cae!uv9Y-}tJM-IEP3PSvc7*&E$L=VX>p{~HW>C}$BF!yqX-Y;)|S1WN0$Gk;- zh(S1Hoz|@UdQ}^}WvY>91Y`#?+5rC_L*bMwufRqx%hJw238Y%{M`uqYOC9O})gk)4 ziWBanqh9r^H=APK7eRxGB`n5&T;L8^u!q?ZO&y3>K!+*2#^di|ICc;!T=0IjVn>ka z6wekM4?po!gbyD%u4I&#d(vz-q=6wRbX>)-Fy?(x4Re+)vV@UDnxrXyaMyBEzJ;lQN*2li?ygXV1mO#ld|& z9A}qUG6H@+_X5uQ%tVqSVm&qpsz=Yzb1>q_d>1k;$~Z&*Yt{T4T+oi+A}&aP+>Q~H zSIBLSvlxD&!V;HX&nwVn5X$%}ue#zJe3utLHU_*W>JK(C3tsqYBCc|IO&#*TP@bRU zy%$rKcoEq^UoNkiH6+V~VK`l2mSTEU0nQIzPnc>Ud?ne^6+q_$s~7rltrQ&k84xq} z+1eT-K)XFE)8eVZYi<2g2b=3$T)cU2#1Gz=*tbu3MMarujr8f^I##Jg#qY`m1qCFF zL0~&tIz+o2NYw7BngpajrU*`OP}aa6CQf@=&xzYOIDeRdyN1loz#f5&TM?=;;*LN@ z2%$X3VrgV4ozZ;%6W)fN@NFh0gD}I#jPnAVY0zM9g9{DX;+8ArH*P&qa*r8jeN2Z9 z+paP{sFkDYKY_`%#3>!m1FhjD86oB_|B5PcBUi*a>9TPjug)ET>Pv5{w)DZLiU z^1ksBnI}p$oshFMTJpF8BvpOH?8f^eyVEenM*8d*E8?)e1+I?OOE9Mvf(s@FHYlC@ z)`eW{8Q=scCXP*fT$PL0fvI(u+uGWC`q~`-+%24H5%h_FcJ$JLH#j|aV0`J)m0Az) zY)U^s3s@|;px*&n4f}Bwnv{sPAsn~IRu^mX3fDDJ+&&Xw| z>Ts=qv8s#%!UMJ$FA5tp94qjKkP$ay#eI-15}`j{l><_&;_uudQc^Bh_`E5MDW+(q z=;-S9Auf}t3xFwz>?m-t(@yivXD;6h!0s?9qvvoF%6(#GZg0f>27a}$F$Ze?TX$r) z*SIl)EbcL0YFfrMl1ZOoqxtjSCp56(L?t9fF=N0H zdYENb@9IF`)rQSfM!r+(e-$kuss3f;{ zOn(vS9K+XpVqD!O=(Uk?jG*CuJ(6ipU>PD?fgAtw(A4j?l?@^Fyu`d=V$Z374dS1Y zmuh9D=RFSl&N`zKI4sukc7J8O0IZu~L*0<<^!fLDF&l$;@0QF@>$j33=ht4m+;$Ap z;aZbqgM^||{zjgu&1EOd4#LpMrtm<$%-=Mm{i-_aUktiyx zT>pFiD^j|R8#kI>i#j+yaj7gfcbGHB<|#4;ueN_`r&YXaYB{(*rh&|7ey=INL}qY- z_*{Xhaum#LXh7Uq9N}5V***vG3TcpS&tz&ju0xM)Bhv! z;~qHJVukJBoxhVts-qr2Qksu5hEPD6eo*`8DlufO#NHhA zpMOhP0vjvetjJIq8=Xji5)(I)mvk!d_d(>wjkxYjpdSMjkc?pCuTow|EUZhH7BpZm>=iZ*NoRB9ZZCvw{5XzA1R7fZsOiHsn&DD0X6*JTo#vH^1 z!a>NFhY`!JaqwH{;Q&FUO*BSmlhNFgvF>Y zW=TCn!V2o-hog_a=BzAztoix+IDE2Wz{?jh$?hWQRy9;_KZrGnh;@7_*preE23hXE zwsehwgYaow(bC=%rpWK=(2gaN5tu{KWJ!>n zfFTOF8ZSpjllwSN&=d9U^4$g+=V1C{xHcbgTZBAOklc%o0ek^<0Zu!x2wGW+mWG#!@w@EzfMAGk49r;U<{% zoZM|uQBb>vBdlYjNm5VtZJgRdRHGDW5cYV)&-@dd_7G;Bv zmDv5T2o@0&LxIRe22$cIQ=}n!B^#qV;3q85$#~XDh3c4|q`DLd`*Z3s9ZfWma^Nk;xGJAZ^K$3n7;YWUgF0PoPeP)d4NaUsAuE#n za-1E=1O9W?2D(3_5(H_enQmYpbL^1g%mD7VxW^JHCEyAnfIpNvR5E}CsKBx(vjjjt zcwPU3<0A>ado?^fWKgOXAVnZ(#UOe^&`yt)6qS*oekLkEHlq1TA+w&5u*pKqN-7%U z9+O8tMR~2w7BDDw!wDJmn|mspc@0Dcgt4KT#lkuL;tV5#Tj9vX3t-a~u3Sf8qthtT zwMaaeivJ-1`*J-HWvTOcYS4^A1mAi&LFcv>uR)O=77{DQ56gKJO?297U2YLq2$2`r zh9WyHV63O8z_wmwJ$^|(6z z1=0RbKI^UL z0PN6a@7<%JL4~&rDeS&$Q4HFG21=Z4n>V912`plluo)%>Ue^@ob96` zGymI4{2!m?|NhZT{=M`5{iOIKxfA|fP;8q2{ztN$|2*M;mizzzCI5YW{@*Xx->Z&7 z?Em2<{Ih%hFE8PLzoP&BPX7Ke|KpE;r!)Uw|0Ui(FMF5-yYl_%)1^2V$+z|q8g!K$8@$~OTIc?kMwW8>4M u;u)kFoHY#;pDSBX%B7K$Q7&bE|Ie87BEOK2VQnu15O})!xvXx`kQ;LGp6c7+lR0J!CfRGqSxs+>r`+sk?+}_<@DE|HmdiVLv?#`Q;-6?P1d-G=Y zdt=5;oYFi$cXad7<0gzEglO|2jD`#R-2y?Qfx~SOg8Dp92nP7w4#5vXFoZkBYs_~C zf zdTKvyOip(5hz=3-r*hFrOlk{7b5x2->CL@gVA1^PQ?hf%O&Zg@lk_Yg&Q>FewDvP6 zjhoSY+@##Gqo!l|$)iV4A2ma+AeQzd2^80&*bm}QvyzYiBOwn$hY##W$Ou28?Ky@f zY9AaZX1WW`(5KjU#IJdu=5QifJispi0*(O>AxON&eD%a@I1wBR%!5B1E9p8!aa~_= z-9T~O2(IzI`26J%0wGA{mE+%3q+`8s{IQ%c{xe49&uBj(d-kYlK%#9hwLx0*me7=z z&}*Y7Pl8BF_N3`jto`(Hb4E4q6e$g~kcU!*o}4l+f7ApVgzQN(#$`_!mp#4tY&s|t zvL}t13H;W)<)}&RXVM$yWKSQ}K5yK#QMoh5O`g|Q`{*bFSb{y0V*o#&Sn=bxz zv>zZM4J&k~?in#rr0JGnyg*|nomSmObTs(G+4lOXRcX-wx>f!pV3p%_@lvx&Lu^Z4 zTjh*viT%}8tGpyb$yd~GVqLUy)vDKsjy^!MN{4rO-8H&Oj%o-8*)^-A>Ts{WDyHdH zX>fkx75IjS}LsM$tMqZ*CLbFTh0dOz7m{-SZ9S$44vCx7}{9U|bPvNbF zwmm#wNNy#zLgg77WiuUnSPMmj6hCa@d_|Ntv%#bM?=CcGYJI-ag#^uQkk4;a(ENCN z?TT9z1Zeip*@`&c+Zxnmw-Q_2` zHXpmiv4?L0(nwYzD2+l4vqnt=8ITpx85#S}5?c5y{HX~o6l&rxP!wo#l_4J3288ke z2&oW8LYN?AmhuGc1G#t{!Z`?4Aa9{eJrIcXT0`gxp)Z6~2zd}@KzJI$QV44yY=N*3 z!f^=aAXI@|_9LW03WNd(Z$mgqNJAZjju3`HcoxEDLT-ibyb}cXouLr!hY$^65Cq6` z=XeN@LRd&hQ;;C{tb*_!ggp?BLHGqiIU)B#Q|@gHp*bO~;2z-&ga;v{K>+n1J`=*f zAp9G`n-I1VazEUoDg zEFgtE95D?=gD>y3^#h7lEf|>~uAHF@h^m`8bQanKFSMT=pgsJ}9E3LM3LU*kCuoYJ z(0Q^JQX5*5R=%?!wg2pX=tSSV&Ns1||6iCVK?Hi>rZo!w7X}UXfX{Bg+5DevFWe~4 z6YZA2a;gG)-9c7qZ)!FEM@dxHH_CH;Mv2cT zxiQvdd`5}rwEn9+6@T?}eMX7TDDfF3|Kw#G$tdy}B|f9XXO#S-JrsYnF(N(RXO#Gi z5}#4xGfE2K>x;j9TfT2ki4L~O`u3FgmT&%9dq^Z>L^|K>Mv3dUIR!f09d)yASyg_I zQ=b>w;@f=s=Nk;d=R&TZ5z%jpa7d`;4uLa%#e#9+nhqCU%U`kh@(N$`Tx&nv%$AAr z!yCLV(8b@pHiq(x8@$HG_%?g|sjiexMEoAe{WBr@?NEWP4=JbzBIrg6K}n=Q5dhz! zCxkBeJu>`WgYYuFBE$ga!Qyo|oWsOx_=Z^l?h)4<$$!@O@wdaXuwH=>;QMp>LmFNq z4ZiKB*VhE<5qIN&ddL7U5gj63(PaMDpdS7ELs#KX8cyiK{=)>4MC%Wq=M&-t!U!a` zUmT9JFxomEt~nmyD#U9C4_Fm=9Y8Erz{lXN1Yt8E2ACFzkAX3NQu~|3arvynaS=v| z#xaQ??1dP2@OA=a1UwBt;;`}nMPUUv2OSSF2gU@<3&4sfbvSB?me^_?juJTQ5Hdr6 z;;`c#MT&59nt+tsXz0XuIs#WTKuh)V~1%B8ny4p;eFVseTR?i!6qDmMhUlq)8|gg=Wug7REkoBrgS=j_$zf{eFP9& zVCm%`XrExX!g(0ak5I<<^89`|?oTZ3_=BJE4?t6e58h=El-YS+6&Q}Kg=!A^_X9+cY7XH+Pn!k{Q z$AK2%S_1TdHj+Wj2pJBb9K(NsPzu59nrHBKkl_GQG5C83#Sq{giGZ|}sE5!NLKK7q z2$_T!0hGd+2jO`LJ0JjXMKTE2T!$hj0!;6(K|YAlwe2HH5AZ`a(#BkOyG~gr^}a zg|HUF76|(w9EWfYLKPvy{2<&8p*4iA5c)z$g^&kf285>}EQPQZ!WIboARLEq4nh?n z5BWj39YSjeT_N;^kP0CW!VCycLs$x7Ercx)_CYuf;T(i2=xaX+w?k+Rp(}*G5KPS~%$-r(`Yb{~OuD}wF_ ztOxw9gXrmowmW(k)+_8nU{+lZMqfOkpxsj#0CYj*N~`w1^Uc{G0l?;a&^5vrH0&*1 zAEDPM79dS_k2uqPd9SI@))6NV22RWW=x3!jLwNwqAkgk3&|Wz}y+M@zy#urxRVTFF z2Nv@8|L(aH!FLh22zU=lvnhlxXh5*c6-)@F$q{k{%b=0?ztOpZ_IB0kRMpeRfSZIW z1AL@#Ek7RPvNCX{(Q$$1*6Pd+CcFK?H9Fa9WciQ9`9i~hggBkxUw;loqV^DMJNp*(g5y#*F zKe;Uct2m+xK-IfLh=*W+AUfuyJR|!OGGzrJbB7c1FB>8A2NCjoB`n?Kz!DC)n^t{A z$bX(EVN$|vMJxax{6g#3P#kcxgp0*^)&xpFKk~UtJ=a?=KR~ukf5QC~KJu;avzn6OHXzqSc)z+JNOmTfYs_ zHqIy7P(9JM#&92mP7q=sBtS@mkPTrX(ca&VXuE)SziT)IBZNmGyb0k;2o|DcS`lsZ zR}g-NPz<3O`cDI)0R*^rPq=qa>fbFOPwMTR*B<1fc&oni(Rq)q3|JZXO3u? zhggJ=aGjU({e-9O#i#AVrrqOBp1b=7V_CX@>cM=gU5RkYWAd23mxO&uwOXH3>EK|RXhU9?--;R1WgWLED8G$GvAi{qWPuf z(kPX}mc=zZU$a;!%rFbTp>+>otTW4)T}9At%X#SG0_#?u$f>_X>;eI>MbfE*))BV_@XA2m>+@84tBmL217;^`~dxS9@ z>D4l%WlgWpUM*W13?ivdcu06npU^(x;e%#~q<;5@+;8s}+V6ht{Zhe%jv*cG384uc zwH+l=V%Lza_QcS{uG+2=DXDu%cY9K3Qg>~4iDZlniL@I-jgi_&iIf}_5@k;gO^(t= zNu<>1kZ5~qXlk@J8cCsyE%DpRMGSNkN$nZZ6O(#sdfNXDX?HO8&#QVuXLGC90n>9N zEhZ$UCM`5ACguem(r;&MgH2yPh8aG3mS)I^4T-JE2+fF%E89ymR4v>3)Bza7+KS)O z=`)7(*7fEJxZZxfYlegl={<&jhUTj|GpSE)0>kVS?voj(i{lEpIKQ}>%+SoZr}=S^ z>o%r3`*I|+XNjC`SiCNtdxDGii?<959TvZYA4!GXeIutAMHN}~_sz^~)%4z`P4)JX z8TSBmpjkh?IU!3A;|q8AdD4+s^hHg{(oaC-RDHUHkYl`~;?NZMrw?M;Q31>2eMGu4Yt3C_`bNud3J+%?YWzD!tu*eh;6trPsP>}z> z*8QHkY7!w>7R9BNhq5CfOHBuR6d`jCE!YO@f!w-zK&tIlU7wJC zr2$aX-k5KWxyDxnx|h|Gnv<|v_5Gxdc98nCEL8vGNJ7>ug?mXQoABLKO747?MB1B> zZ)ZA7=I;h<-HZ<9tQQhbscxOC>9?hjRQvlYiPs(#r3>OLG^u>jh`Y zVP~y(LrL1Tt)^5?A{s1+!%~UE!t<4%PxT{PUVv(vlYfU*M*2W|aK*>4u8X6|m40M^ z+fhb%UeI)lNeAE*xD8n%aLQuf6gv{4@zkbibW?}ED5i+h7g60)Q1^5R?GhdyJ5(ffN8Qssv^&*3 zLarXDdwPWSpt?sO^+MePdW`BGffR?jCoVLO>K=jA2XzmqHmZ9BQatLO_|SN&dj!&h zsCym^eUR#&A{X8BAnG1SLf!KyRM12B^h4bPnldIPe?O#q>Yf3pdj^CKh>iPbCC%WW zdlGts?wPO`br0y*-oy4!rTN@+&p^~Y6ZWF+85lY+Zp!}Qkjqo|3`X5EX)o%Y!J&iW z=j_j*y64V@!6XW_!=1tRdK%gd?-LE}V0S~CxEb0Imknd99%7Ppef^j6{VaIDa;2e- zH&xjEph*Nnn+S$Bi2>hg&h#CUD!dP>kxM2x`iDrdELT}{BdDN{)n){!q+P7 z$aM-kTJCy;9Z?87WEFv7v0K^CCR22 zPo7)MFmXF7fBN@Hrq@U1l_z7FwvfQT$PB17Fz@e)8GQOA^V}!V-G@g*LTNJBgJHgy z!Bh`}dE`lE{x`$A!DOCGHo;q8cHr??zAwYfILSo6dbV^`cPJpF)EszRDxqcUDQENL z6euG-WNb>ezNcrpr1aTY`O`~ZF;AYYE_tIH!!TUY$4n$+NzP=F&pnD)5B+Mou%bIN zg^6tun_HmW!^9HngGu?5?`x@PiR!Rra7&XoyOYT7@p47_- zu-YjgDo~hU2CJO{qHPx~C&u^G3Ny`MX~Jp!x00;s=^n49>1$_yQJWc~iLv7(G&sg2 z&P64%d%R4M9veHT=KWYrtR1JP!LcTBmMW3m<5i0E-kRRr{tIxl?4Gj?-DP zwetv>1Y3$boFknoVD9$P^Q01X@K1FquOroWn}p(1KY*E>?Yksm`A+j)*{Mxr?G!@t z-Xo-SIT>^k(tgT+yFPy_Y^`^_!@o|PJp&uPu?7p5LpJNS@uU>?B$FeG8^|?)?WCij z{H0ceyiR*yQve=(4a(Ch6p|-TCXs!M8fe>gSJpvy{KOp?vF4iY^uD(!yXn?Xjx$ooh?;j$h(c53{n-AqXesOHm z8d|==maq2Cg7WtrD|?d+_`2di7uc=w35jhQTlDvp#y#SOXS7CXj1wJ@#^QWOBD=@S z73tZ-2i}j;80SMEjm2q^NOg-=D^df6`4R|YoHn_hs4&K<69{9^6BWidivnTnd7{D? zCsZJeJx^2^~_5XPP-5XM_SSvMSoF;2Ze7z;Bn>!V6C z2xGT+HH|+rX3cIR3S*p=fiM;)XA;>xUZzNojqSgCF$!axuYoWYr)?71Jzk|qN1^-5 z78J%fhXY|OPUU2JT?ErBaRcJ!?|U#V4ySe?jKvwAk_r<(mFWCWv!2|~#>eAa4}`He z06~38hmBX7U8s`$(JzlOzkL}g1Exws(yu+I* z+VCQ^jwsqmXx+m*>3O2BCK~$iYI>gNtBE#0yqcaT`f8&253i=@iN2aR6M$FK^F&`w zoD{&T>3O2BCe9Dw)$}~UtC|1!w88jl;zR*nO<}%Z9i}A1tLYZ6rt$3L$#eVTtBG?6 zcs0eTghY0amnqU?W8>$J##a+(7w~F|lMIRM9^-Dd!f>$UiWxq|t6 zOnxlmH2IHTwaE`)dk$|X<*4# z7jVC)fhAjA!2O;EmTYwa_j?*xvegCLe@zyaY;ifR!NPL4xE$ADV!4}Kj_WnC1mnzn zJvJ7WaoSkNuiD0fG9vdARcQ|Y;p!sIufaBPw^e|juhldWq`9Y6BAY6}&z@F^Y^nf1 zds-#3sRI1$X_d&P3h=Y1RU(@zz|WpmiEOF>KYLmwvZ(_6d`)JFY^FG_!7On%QykY| zm$=(0j_b8c1dGOeJ%$OEaT+Gaui7wyG9o|oLGK1`4E%4_?9Y^q7c8O;QZQ8EHJ39#a*^v?O%h(@iy8{#m94Qv*AuYQG%HEI9I=-RksjTmT?h*V(;8TTicQz!|1UsJ+`OE2zu;9kDckUt8|QE9In;&;2G#_ zsr3R+xpq#ZGr6wLiE?JEqjRE#TvydOyk>LImPT6xKM4G=@iyId&-dvl1Aj;lD@eUy z`l>Ogdi?v2H#}&ZJ*Ok&um~Q0CIm|K3D<;M2F*}$^hb`%e|%0J1A4$B`2A^qdFPjG z)zf`on)~tb@*XhRsHZ!^G#xQf-VG+(^mK2SrYk1O`@&?Cp6(LUbjL(_hnQ^9)BR$a zNKBOXipd5&-8rU-!bEx3m~79}J!G0_nh3L0*<7a^$Yi&^W&9U4RmU=Mp-b3U=0Kss zoqino1NM_?V$3h_lKbApt3Szhe9^*9VxTy>mMt6?+=6I`3<%)n#qvfxYZ0Nq8^U?;e3AYfZkPtPk$vyp{-Ej63L9iZD&fi zd~lozefLlBGrzN!uIdw~iK`NP`Qt$*Jw*!*i^ol9N}qgiya}!N=lSB-roH7@7O#o7 z3Rb-!!*0F(-rMTm>M6n`K8yk`Gbap;8#HM&orA)p7(FU@>YXz%Zt&zO9gq((-ZFvm zRSfcB|MAl&rL{*#N=#@BI#Te|Eql`RDGzl;KE!Bvf0{oI7e>J*M%RhrB=BKhTq5lo z+7~@1ij%;H18}hvJagzcQJe%m9E8iJgF*+P-$Zc|_;4^Tpn`daZWF~x;KM{*N`=ji z=rvKC1U@w2qN*X(fKC&|N#H{R@?p`5^ZQ4m+XRx)ZGtCFPs`|Czy7i(9r;ioxbR^T z^5MLam#6POn+})w#-P^(PcdUJ?ETl1Z{{H%()c_DA0`7I{>$snse3;U^cW%k!$cG<%i;j|%&=ho(;Av>dG~`3P%q?;9VQO65+>%jo z1LIyThRGqkMBp1S;b~6Ezp)@3Zya(lCgjI6oDLs7cqauLZD-8Kj@aR$gYKi>V zDMrPy_dryy-n}Bx5pNOvd^EqjP%qoZ)T1Zxqr7Ax1C@2<}qbQUbj8)qri_F?FiZ#xGQkCc8{l`#sztc z!Q0Zimlb-&ioB$#-PYdK7wD@6y^Uq=hczHr$Utf7dGfzMs1DDZf5)IYJahhCgX-|i z`F9Vh!!zd}8B~X7&Oa)s4$qu_w5#fNUwW5SxqCLcA}{N1ch8%PE_bSU#frSFzug^g zURLN8EAmc7?y>TI6-)HzT(r7}lJ~wq@1oc}^!x>{-a0|mhu+%i7EeV_`B>KPRI#^I z*Y^r?xuEZ{n55{bh7VWO`%Y@Lp!cy1^07kQyDERm#|nM#s{JV+D-^z~`lo!X(D<(U zpYpLn<+~aHl#dlU-_-)3e5_FVt|kERv5V>lK6Z)ArhqG1)3X}0$+}-u@^ak87I2yf zWr8h$*<}515|q6h7wrLea+Nci(-5E*fH%uPNmpb%=VBFj7zu|3qd+B-i(%klCtMKh z0+mdHso+I6D~tuo(`XFPRX}$an$l)~EqhWn#VVj=EV!-rse9h4ooj^1(_Z~-l&8J= z*(gtY^|Mi)_UdP&JnhxbMtRz+pN;agS3eu@w2S%RzT7996YkmQDxhpXxckvu>W_5?Q+Q~V@jfg4Vvl99lT zq*2K#;BskHG6T5LG%D$RZVZh|ik=%wqmpLl#$nVUZ>&?OaUqGL8|U2BH21KpkEJQY z(Oq)xI+>f`k&jNKJ@hX((V0={+qsP?mN&sERPH2aZiR~nGRr&P6nb`wklD@Cb2}sG zSHD}@G`^*gnue9ej!vQ7RV)V{Qs_9GEW7DYO;TGNIYCQu;4+1Ql%~PLi2F$sUvZeF zsjfK8(&Sc*O4C_xggA17E)$r*A%?hT?ZpaXQA%u2AN9fi-UZ0 z>Zwp90)fkOW^@}ldFrW9GK$=;BPUNe6`BP&8S9`vazyp*bI~W#2&yU+t|=Ws+&AK; zF*tB>(zj303S0|u3-<+ygW!t&woxGX*A+ zw`(W_wUETo?HKNYnCsx|a_mGH8@l(xUFve+jCC7By3ax(IE7^j4*a!JMz=APw^k^G zD)@D!+^%CN@25~mQgHuzA4A^%v!DZw4h9|y{5OYs^-dGEZ3ywwjx$y}2 zJi$8stmWW9CP+-+7Hz{1t-ImJ4vFSDeYPA`Bz*{9mztM+$uI*QEM`dirh5NIyq#rH zdC7?dScu!Y9zBbfroh^Qx%=tmk>f2i!|*Z5`IqNohFz~=hBJ?1hT)Zw4D-oaQFPqd zs7*r;!Er-ZnW*-+^PRa-D<7_J$XgGtiO}l-4mjYbb@}|G5qiD>Z?&vnxLVKac&-}p z2h63_Jpb%LJ@AUXn*S)*FW3ALUj?84BgIzw5su#K^V^XkleD?E?K}`PxfM#dg@qI)_DC z8L!|SCk+N$WFC>`;DJdb{(&#Ig*ysVk%eR`+!HxQ;34NdolGRsus#wKN%gUB0up7S z_h>F?*e0Q2u$3Jj+KAZU)U;7kdz&JC(Je&+6BSV=`d=TBiHax_{jcwg>~oFxv=_NB z5yoUD!ida7LPTUDjL1w>giJ)N!42<}CD6SgO+*RQqM^G43igmdL`4Dt&0PXflB)!w zBv%O}krWb0DkyUgzk-rnB@mE2BoL*6v~)@!T2Lv0fPfN+=&1xEdU6X9n1@j2`ClKA zc?e~m|Mi`bd9LxEcH+W37?YU?BQo;{5s`T?A~O%sBVq=89ry0r43qz}2t!eWONE1z zlIC+snk3Tz;X9uf_z{rxR;DVtGLK<;Ru31DEv&e7`91H?n_iBM#r4Xk`LQ&gJ^x}) zFn_Gu)(IJFU~M#J^wg=Zb5k`_O>m80GL7P&rkNcGOkpdF62UT3qjk~TV_dXfbPc$C zqGQTFg!$67ZN<#;a8>2j46FZe+n1T-Q1 zrn#De2RW*aFNy?{(SlGY%rS%n`JiF~5V$panEDK$7KtA-SBa=WgYwTK3{cr%uHrF< z=@A{hV@-5))Y~5d&ECCaCX~TyyW%zo+iz+zxg3UBc6{{#G*>@*12WZ)tc_)u3FSSU z#T4H$V=*mLa~cp)&J+#h60lZK3@UpX_TGH3foVXW<%-oH7CO9&$>#bqreGmI%k2o#kN7t0^--n)v!yc`rE~k%)WI{9rLU* zfC{4bx5rc+Vj9sJ!F=T!v47}rMsx_fJK|T7HfRl&ud34wh7-tOyG;c^ zsQK?f0XoQeV=VAua?bpV2MiiRS+2uWWH6+prS1D~T3Yf4dw^D*bm>|5LN+M$0;JVU z=4KfTYkt`Hg+XWF_Pz_G@)_m~gQ1`@MZFBv;p{4d;rz*$wp&R0F1H;M~bic;nifr;mwfEJg*|zV4#n*r&k|?C(E_e8ZFbdRTZxrO$)lR zho8}${@j=LuNI{z={$HR&}hwFU0j`FGiNvES8nYKw5$>UmnuQo1n9v7R(SzBj zig4>SsG`|oN^ev}y+eCbRU~9q!6J!4RRqggR22!iqEQur!l^6$oU-as>l@`NNCU%RFM)K=&Dqa5*6sm zs_1&4fB=(SgLwMJh$EsEWig*RG1>g4e2woCQ5qk(j2gio`@URU{^z7&3 zPt(sNe$_Xua1g+YsHh^?pftVEztA)yHWqg%r5D0KH$gs6RRoAAgA9hO0?^y02V-Jz z2h^a#G@98>6?H{blw5$S2-jD8&UqMep~mTqswk-dRS_=BMh!uP6-?-aswg%QRS~Y{ zf-1@sQ#zt5g73$vDiSiwo6T@mB#<6JRrEmU15_0Wq;{x^Kvz&zB#^>U6@`a}Q&l98 z?WZ>S5QSt=%%Yu zMM})3E32aG0crxoFjrMXJXH}?+qLT)eFd*vaI9H$&H>pZlfhZ15_hbFy|~=I3vd)SJ(@*lolLKbVA&TP z-M{8NaQ@LRDzwq|!Ve~b?!rCZ=*yE*-Q#77^wqPD)}}{kgsmakDEoST0Vp}#34l&N zDb+n*u1LRVJuw#G%tZKL`={IxP=?~iA=ahJR86FMyjy_aIMj5rM^=8&%}wY@!7$;6 z>Z9A=GmJk`W!hqrvdmdxm`< z%(TzTtQR*J4axtxSbOeugJFF|^`vp9t3IK9hxIGB<`^daP;+$0+lDQdtIt1Y=w{#! zEH^|NEcsc6g4#T|vM#w?_3N{S6hl?vj?5?a50)@!PKZt#B^7)&Dp8bC^Y+CH?i!F$ zqSppxl1v6Kj!N7y4`tEHEhwYVeG?pG7QHw!y)J@fWps4k-Or(nLbndcs0m9(po|io zJW{HAyiAe)RNm@6i72Dc{{u2=-u`JQqeO3zl82RZyNlMh(pwgfdEi#Hyx{S6v1PMAuk_ zBCUT`0?H`#X@QIqU0VXtHCClalVntpPe%P6WmMKyd~kLGPcVB|Qq`j< zqjC!4gn2y3C~-QEGD#-G^j;P@{ zMAuk_BCT_5SCmoc>;M@h`aA@pYphCS;mOm!E8wTqG0_VSzp6Q&>x}QJW_Ygiy;yHkJU?PEJ7)(Jp;CQ1 zAZ1ddUf1NnLMIQ`J2wz!6^?749Jo&w9M?WOaGx!R(}C-o90;6!rRf3X>?=(VC}(@l z3*^ZHaJJ{XK%OiBXM4^IgH*I9zd*`5;viqz{eDJ|6Gyk3h^utgo$Zc@6Nn2u|=Dcx;M(TKc0lTzU9D;brPv#(@S zQqK0YCS?;7INQ^jlubFiR3$Q_EBjpPmf`&5#tdKlFjFoKX6_c@ zx2lUDXQC}H?qL*=To%ZbTsD?z2KRX?GA5rJ&M;qIUd+JfXws)UR>-o{R-!3!?1L|e zEU+)mdip8YJ!g59AIS)KUjp`*fdV+e_inIf5+J4E2=G&I>`#vXcm>x299A!S4fsol z9af;-rRIvnHjD68S7@^WD$9GD;qg*~ytNtNxuh(0wpjr45?a!mwnS)847~=tC}<}@ zP{FY~JqiF}0$3P)^(D7iRf2IMD%BO*40o35>b=d3Gn2PAi(g&QzdWpKoCSz30j^7E z34D?UErjpX;7D7AAGpDF7(EKGU+{e#Q1o~d$61xPt+AK;aLE!ZlERY4<(1$#v#`qY zJ|H4-2TI^rEfAO!FaC}nZsDe9VeLd^iRB4!1(d(a_m}oVGjCn~m==Of&#>Vc=l&jR zD`WXWpqIbqfGq&RS_?CaUjU^{092)1G;v*0G2@a+@2XAUBj67G21kpqhC*>?l40I& zb}p8P|C;Esl=3pSX2u3sTG2#VgryT&5dUw{bxB0FtWEfDjFCN-{bu{^#VFiRfN=p; zM&4+xx?ZB`hPYbt&TCaGTEMlDw_?j?Gu@c2>5Mm!H)YFKGu@)CiNHj8d$w#e)6LqN z_LwMd)ON)=5m0#KE!!Y|!Tk#OQuL$=$C8MgW|h`OL>v|?7)&ku7y1=C<{&x{##%8@cqwl70gDu@ad} z0U>H+$z#Z+TaUw0go`jEJVai}Y~<1xi~GjJV0>hW%%uk4(w{a*NB8@64LYpwLtt=N z&0aqi99CwG#s2apa_L9MkxP*f^Aqhaw2{rvlq{dT>ud@-st_y#996HDJb{iXj72Rj zUOwqHhy$0>_%pQNVC2#TfOL@y7Zh|0PFsr8z&&L`JdVhyqT?5iFQ8nCae+$*;>H(P zpP=5T)9cY2g)#6(2}B%l83qivbjU>(oly#K5P?hkA!15AdVD8)(O(mJurVsHx$7~#mCij%;lh^uCe95Q}p2D+kv!vIwc`L1RB zOuw0q$;hP`YZ*y9uPbsXJ0^KrI{Ki%VF*4bE1uf)(DmS^rX!bPv>EHdrK0cdO zn2KCFqOiBLol54?uF<${N^+dKx>e2SHjQ~~6msdrN03XA&~py$ug=J&LkcB_shT_W zp@J+H-K)T*g8P&fM5n2%TU54qtI&xW88!5gf=4oug$17~GN8OWOVwp6h*EjGma4~8 zU{ZPimTb*Zzp22a@=h+l)cCNsr^6oA*Kr?xtmu$&WgH~Wt1Y5I8 zu2IonYDJ%D2l!Vn2MSsNXssRXJ3TUKL=P87sTG}}E!yhZa=nm;ErM^f9*-Ok1bJ&4#7B1-k5*P_+*HySW01shS6XSyP91&A)R{9o{wnh@d*WYyRznBs>#! z3^W(3z|}_Z54WjqnGg*HSFf649|heOp>x|naiv`qlKbCt8wJc=&bgQe9@fHn!8lOK zgcgEHwh+8n3XU;vAYb_J?#FdGmB_mR11cLtFhrqRKz3} zS2Z=x)wNy=Qe3bsR5Gb8&#IUkYS|o4E5l>8_9~WjEwZ`c?nHAnKipFltGKe+;qF6I zGeew4ieeR4bzN4nIb?&x%c`yoOCyUb)@xM_6UyR>Wm{FtgtE9-e;Z|Sul_d5;$Ho2 zl*PUJ+W;+1a;>TO+knMY7i?A6bQP<(qH*HtRJ+oeuEgT1hDx!#*;C<_5J9EfJT$j6 z?mIcnbV+Z5(+MMcYVKeJr-O$fqQuLRTsC(CW)?2&xmy;9yCwO6l>VyQ7>@4NbYGVf z5WO7oK1>jwl06|}e@UhRH;SSUNxLUS+%QSqb2&6s((ecq1t3V0LKo4eBn>WNQ%NdY z1hQ)Bs)$9jD>wDafp4)5a|g`GH+b<67R%Fbg?<%*qdZ5)O{bb^T!CJt9348@R;Wf{ z_N~OkQj89rWVIe=pqhWzZ9{`1$9i)HWZPprK<)J@XP#~DU5X4UCN> z!$sUKNgQ+V7YES4(ta5c^Gn(wgT7HZHiAs$?xRs@(Sie?DIFUZG%-m`idbVUT^AVy zY%&-f#jX?>ojdVP;+vx?*Y0L)AM*Tbhy$?rOFSyjt+=T_;{evx5bOJ&&F8L{sl#k}2rnaw0s)AS9Otmj_p&M-BT z7-sr8%b`I4%VkOb`rFSw1Y|6K)eXpCIo}|4Q|0O#q;9HXT8khfv8#UQ4!~yBh=2r; zBSwCZ6f;KlSzQqcF$pWF`l~#^J-QLlo1hMV5m;4NqcA(e>+>4s*}~uqd$Fx~H^haF zR>NgTMF=8sNHOLdQj7uol{lo=h4nL!1^800%-WiwV z=K-++upLD|**<$JMFqA)YqTGDE6b>%Fe27eLt=7TMzak0wpt_iRe@0}rKThePHdK_ znhGBsrVVEAVOs(~~oE~(X({*?{^6GSCddhE3)X_DZs2twjnG&LC- zV;leOU) zs?KO0zT)TVFMlx7!E>t=dY7V4plDJycb|K$w zcwYOwW-(_~9V+IE zj`&<)Nk?>O+qkW*Q&^rv0|2iGCUr#P5w-*uVu#esghmP00lkz|uI-3~#y2xDAi_TV9%^D!bzob{1do6v!O8eL$U9WAu#GC5X;g23>evJ%SjK;FBq3{&`VIXPYA0QOwKRb( zP4rfy+O6{weoaXD&U>47&cO`74prU(fqv+h@~Rq2OjFfRa+bqysZyDFo<8?hGNdL@*g zq@e)l$wNa)SWuFNqEK3H8VWJFJTw#~dT1yL-{r2MZZ@L2hLT$0O+z`;y=f>#4s{LX z%-}^sIWwqhC@Ec4LrIA)8cHI1(NI#dR}H1epst}58PqkD4A!AChZ56NHI$sDuA$^) zRSl(*>eNt*L=_FCNLA5Lid1(Eb@fRe8p>JJi-r<2sB0*>w5o=3rmATuXR4Zp5>vfs zC^1!CLy3uM8cIx3)ll*i)isou>|I028N6sHXS%wEa;AIHP>Kxd8cNQfuA#(aR}J-d zBh~`&lbVJC{3j0$B|$_<8j2!pxoIeb>hjP~l<1+MD5{schPv5^>KaOFg*Of5O!uat z6gkv2lrw`D4du+BuA!uKRShL2x@ahg=tVwrCWZWH?JmxN_D%bfwau)%Zl4qc%)v00UNsA?M?4mJn zKa1ZiLh_nmrmo>6fGE15;T~no*C_&T^dydWU<7OPSVH~`0(jc}o~ z)0;NIgckpSUUmFJCKB$BRwS5kZ5xpe#NMGWoYW(h%$XB&?(RkIr-KtnYTKm{;@}bIJA);zt~}kp~EOdkeU< zhsWw*+^wDb-fi5Pv?E*u3sh6qvhk!@4>F@($n(3QZZ%`by*)Eq%nzCPw)X-5`?fTS z__H@s05&-dlEizZAVVn402gn1QdO9e@{o0A%9hX;Ar~#UE?FQLyX_P zVKi=rC1;L%#RwC~*#NS)Egk-a!dV2C>$uZP;CS)es-&dzi$~dWl9D#>N=lwmIxne| z=L;aoKCSqzB>3YB9I8N;3mYXC(j?*kLnI}wJ+}4>_#+z*iotQu$4Nm++?M^y5_$Ol z2lzJ>_)iLet(R*rErPRo)2`&yX_sCFKt8hsvlnQPpOedKS@S9U4~18ZaA`kQoyuxh z^Cq5ml)h(Vb*y@2B{sShwt+ zP`WOqp^_an$@YAHIPhcnhX;@kQ{KIl{Bp(0sVVR$CblgnFRxB+q~t}7k^7ew{zs$u zt#6H+t!1^pJ#I8U|5Z)dN?^$v(^z98B^PRv?6de`z*puE4kT^fH*yH9*j6Xcv(Euu z+)|vE)KJNNkU7n?40z3wc4;LtYfw_n>6h_h`)@f(4V+n#?W+D=U6lFo;omY1n|1=9 zm9O~M@bn_v42&i1JUhZrU&(Jewz}w)o&PSEWjE~v1}j@|qN?`Pb2P>pKKW%B+tA5r z0g3hnWup^YD;X|uFgJhE;MP~qc9M4S2}GYj^a(^kzdT%ruJ#E;pFpg`YV=u%J`2%j zA^I%DzoCT~+qOX4n{67#b&g_Nu=)wM@i|dZY;(5h5c}wy&TtW$!95&?=XCL$#*L%# zRBo)xIfWa8@rKEcmv{T^b}Way3U!ZWK})C)u{_+O-AtItuYy zk~AL$ir1llq8`TfWVu{=_e3s-o(FT;%5&2}_Mzz!aI2t1hsmEHTAFr)C`6%01qy(|%Dq=O({gc6okseYeI3vDW0@_Z1F>3FhU^@3HJlN7)zc z3z9b$Z%Td2WNhHpG4a&-#j(o+k zXC5C@lg7SykY&eI!!sPENsZ*LY`~U1Q^B&Y?^?H$Rc0~hX%7dnCAsS!P96Q_VU`_P zRs5Js9#&8LADTaON3r$t?ku=@)fc4(mrRLzIykqo-1c|a5Vk2DqFY#dPSqGW+L*P! zRr?lOpVbQk24f)%SPNE~qxd*54Bk8+2l_bBCj-OSf2<4)?!a)z?7@RaLx91fQtMJ#w>|LHDaG=iL$2L$+*!hBx;HS5~a?bMWlX{K*WY%f( zCoH??zu#7WmBX^5zpOe}#IkVfhv5%bcCf7B@`&`km8PFEz%vVv_tSrA=-y_~#fS7= zYsZAIPiFU2PBm`+hBf@MA^DM-X;|>U8;0cREIaK-cIBZ|!_*X^V4(i={XajZf9jxK zA2EjAvz28t99dJUjV!xu6PDWd5%tEtwT^x1yU7L^e0Z3@z83xh_jCvShF77cw#n?C zH$eN?DyBYp7S6?Q?8k!H_}42e`@;-2W&MTA?<5J2gQE9!8S`=r{nqE9XekthC#JhR z5j?$yx%RB#Wd`*9j=eBnC>f-;zE&hW@kc0DG1<83IGc24UGl8z>G-^P!`VsJhZgT* z*+<73hMj#zD)r548}v_o2Caia(3xL@i*M|)>hn-FD6|>=fTf7tyO3pHxKMNH9fMFv zr>|`Fcx2n0>PdR0CcE`pY!lX=R6dk#gpJqfEzNh9nSXp7o1XQHZXqk{3#jvctz1|Tvq}~wg z=$sBxEO>}*e3%%?&}C?L`eir?>DqML2YdE>0B0-AMA=Rt8chpIO{?$G>w6xyd}qWs z>a$LIee3_$T%4nC2v11Uq&eQ+qut|p8?JtY87m7?dQ3^5&L@iWq2Iq0mR}JrC^I0x zao_>gl%j8-w}qvqz66uk?^dLy!sM29bkpx#q}Pu1?rPu#7MbKo9Zg-o~^!nB7 zp@<_aH8`~lGnWk4pUc(jWBArnPC%8c?=KVdNlnS{?tELl{*hxedrELh8BCPfpU3LU z(m`?X9UuK1v#(h#Xsw!`$M)fy>Gk7&qS=#!lgnTheYJNt1{;5bDK^VN&5NMHQ1jXr zdi{@3bJS8C)I0?mEYv(HIEiM4l(83kb^HcS8?1mx9w+a^>9J(HBQe3##@ z+J7e{;r%;fr!Ty0S_6X+bHwt!FbJTpI>R8mS5qtu0xZTl*6q~pbgaYWS(w%fqYs+b zwt45y&3J}czCcXWC2F?&CE8%RFAxR?54ORaUyRfnY#)}5D+Vmy;P8U)qfMwvefQms6(=_Du0G%; z51pwK)>{U1(&3t)FIE(IZR6-` z!x_>h*b*lt51-^WNflfPl7|*gUsdc^>=>k8C0zof1gpU+=@M)QcRn&OAy7ThGGS}g z#kE;5uBwnsX2ZNA_Alof)EgRP9Fm@;2m@mRJe?cEO zK~vPFq#|8aN=oUfQc_CvA|;*4-le3J$GenNxLJNDNspBaEAOs|TU8q@T zI%t2X_Fw%6KldC79fq(qMdV&$y6stZ=B;EMX*OU$U_VVi(}vj>4F^fsfB~do2gqdW zPkKE>?j~m4oek;}5{kG+pEo=+e__9-n!8B;nR@}Ih)XYo7W{7d(BFSY(@^`%&pXB}3zzjYq}29#;#MeK z*beT*b;tvXW@Gi3yp0=4_`}&3M2WQ;0%#bGMn|mAa;*Z70^$odGn&K@u2(P#B3yI>(wcB1gYkAF5Pph#s1edL3XvWc zh?LlR0A=MEKq3hj@HXOIPt64aSJWSQ>-|=cr9I)A1mmrPdJ=!a<#oX8205e;aV&>t zIoczz6yb)BAO(SM!8PD*E$oLFTk4?$@t7z%Ds+K<1`S=l3Y$BysemA4$qc$92y1vLB09;B(JDM;(jNqth4 zIFIZMA@Nl?`x^rgojqvgJjheiqupdA5R`J4df!G`@$+KuApiMra!&r3%%+c`xCSwl za3^UzEvElnPrvljjt>@ASG>Ih5*#rgP0iE8${%|BE-;-kf6kT zB!}Dz0>pB+_ERz~4+M*&5vVins1|Nl?WZ9ppgRHre}tHI&z*pf2F-CR-`*|^n;r@ zR^Zh%QlP0%94PAwK-O(1+~gd*fqaZ2wNEcx2t%@PE(p8Y_Ei_%Wc1Nny} zg0y=YG|Y8z%(GfIj?p5)gJZN>HIC6WPWAKCO?<@3F{bI^8VxgU+V<^x6b$qHtKJx< z-ZFKDnTkW{V;JuYV;|SqA6aQVg=w=biTwI&d=Hw?sAU;|5Jo=-FRVYyZHxu(@n_8| z!-f&kwjJ=RKdT|;vCoYkx9h$Fn!koDXfz=A+hw=)7NTcZEs0&zfP7A))r;+Sl;{G< zW<7#YIxYh9`?HqjZ{Biw%rg22OICQss#RI^+D^vgqY+c@VbJ#etSw>B9c!T!=O0bu z=7vpm%?T)Ytc5jj=k4eGA-64i5=a|Fm9u`)h4k42V{Zl{=e9e^2b2oNh50djo0k&O zI{~DHKUC0BLc#3dVxV}z-HL{e;}cqI3GyujpbqZ>k>St6Reyv&^bV&dmBF)PD7>Us zmsMHpYOn3&-PCIgD;zEY^nrA^Go8^ds52pJg5b`|hhg ze$il9{@Xa?LR%i73do>%xqxxTZQBhSl&cJvvfyjdcS_A?=CjS5AZI~|+?1aaTPZQl zf(CO_pBUWg>M_p#AQ!md@WFSk9oh!Bb#!3Eglan4HVJM5yR79Xe3;6$2!-!nfau3M zPVrjaGLvP~s*k~UEM@^XVgnC6Pz=qdn1+tFP{#7~_DH=Ri*Tow!q{H`5QXPVTsF&! z$P&iX)R%uqO-)gh^3$7JAO5_#RO|&PT=V6-KfD1j4*)#@8hY9HY=Ha>KfQh2w*^wg zxKl5{olMz|Nh{C7Gbs8542QEsHGGTIJg-ma_HXb@D%zNmQ?-?q-?8i)XV@$bzTy>8 zDBzo8XR#V6#(uk!{dR?3E0n4{^-6a32$ns&nq|M-3JZyfBWqy_>7je z3m>Lo?a8KR;F&d>4?4jt6b*yNY4!Gm;mw6o&hZY$n@G`)$Q&Jos2l6JMV*|~3Z4qL zxZ&U*eUeIecepU0q>`nGPg3c9lIlhSs`W`KpQQ5HRIcU`8e;!sNd@q36zDDYj{v=y zPt^`**?Fe`l7-v(2H4jU{+9Wa>3!D(iuwkyM5o@Y9f>8_$4^zCoWed=TUETB&DvY; z0s4k{7k>N!=FP2r%J|adbQT`L8ot{$Lp^6|iqYwvn}Kq&osw%?^0{#M%^AgnpwHWZCykr`SC6 zlg8y`>FhfnVFf?T5P{#s9K&m&xrM(}e>W9iZBA8v!e;HS6j9#*v5exqInSO5Z{kSl z-em2Wd>)wkmf<@ptUo^sl`l_vlMCEGsT2O><1d*_Q$Gj4gc=@BrzPv@G|rk$IMbDVd!zb-z!%s9<40UlC| z4`;XiHd3hIIGE#|Ps%udaBSwHn#C-;`?tcOSwbTWJI)S=hB`8N9bfw*J7Q<0?bJMI zNLkVw=WV}#EOZ9N1253Ng52%nb044k_`Ea1BVGZx(ekDyXXt{HUR}rN-=HR%(9C{U`n1V*RcknWPqMqiRD01 zzxV^o8XcMNME7#s{?(A%S~g~kBOQ{Vs?K8A5#Z{xW~-I^;nF$HIa^siAmD;q_9}|y zVl}zXR=~|T_d-@|n)6{7oQ+F!fXlX^bh=^4JcCCm(@rm?*mV`v6WIl)YD#y@rTp}k zX3bw)*Ss0vDpDjrKfR*}yvdH`5Jim+FxcUH8LYGq3da3!5t*ZcrhlUr8NBI8zEMyN?SZ_W7?lQ~!b zP~KIr%(&saBBvi)J(C;3{`$IGR%)g>?5;UK{DNiIoN>!ZZ8q1N>e#9jQ*Bw9iJ$6H z0nvctR+K633(o8L)p_7uwtl^trG9#wF_TAIuEuH0IhzKYbC|+cJzHVKg#d1kt4-(Z zTjyxNmFPq+(7Z2?TMO26RDE(mAy0gA!6z4dashCGefENjo#B%UKDqFZlnWgIb97|A zk=FDsfTkK26~;Zlz~5f<`G0_0wISRp#cOpzQ?((QDv%|#RI`YdN^=4lstwUlX#}uN zZHRSBBYZ_SD$pa9H9jvA}{Q3{xtXdzP5X9(!%bWp=}BLmZ>!61IFk z1)EU06Oh>HGfb70|3#yoKBj6i`{to1_p^nEo-D;@(|VOGFym8v;mYJEIo#`xN zWapSRu4IRlZ~Rm)b7`O0me1aCt63wqCe>CBZsk+u%OU#(+GAU3k3rL?)eduQdSkZs z+nOYn-ONvb+$I{v^Oh-*cJ+uGSWS7;J%HT1}@w>@2apM8d`(USaczqyw>r z|6hAo0vA=a|Ib`d7Fo;Tf))%&ljp_E$^E%oa1r#1c|Qlyw+pxikOoxpxKz((3)6J@a|t^PPK^-&yXt=XbVy z&i4|}=@UW8CAhZ(5s^XxW}uzj!Ny}udlU>~{)`9Abr@KSY6DhwBabOYfHcUE*6zv2 zp7rrM36>?pJkoaloLPgy|Z_@qmASTncelTQQGx(FVHe(qT(+c?N$7;K}60ZkV zoNf)8BO}+1kj`$1*wTX&84{-_GmGmXBe=w@O+hdwwDw?RWF7nDfyv0yk!!~|E!TI3 zo3#Mcmlu^WqRfJhJxh z4nQ;fYW6UL_+p=Kz)^v@#pSWj`sD8+_@IVLfIo8Yzd4Pn> z>$r{GZ0*{%r%sag&0(}8pX^AwkRmr1aH>63Ww8+wS3X8j?GcM;O9^S}#o?X{aZ{v4 z9wBds5VsZ}=d2o3y}*O~&~q(m)ugaV+$_*xUZPJzd{i(YZ;c3^xgtI9y>4lV50X)# zggn=tbV^4m6<+Tg??&T&m><> zgg_1Wt5Vu-Bm*dE0V1~6AYwDTe7yA$67ab{?zDErOgER%z?9=I$7|!dc?>2kkrcV& z)f_Zzj0GC_~$7_vUg_CWKYdG3f%h?tME5`3G2Y_9AaAuFyGdwn@MWoyT##l@_`W}29Rk3ANI)K8rTY+hPIu+h}r4jL?+mNF=RPw9VY`;LWr8lJ{{F);(}CWYb4_ESMTPh#UBc>QmjR3pFOxLK-Ook!6aoh^hau3a(rE+9!IwvlC4>Xh>|$`6hD`)# zSFMj*zJDuEFQy1Bz{Bd{jXYa_Aft)dE3o5zB$Su~5+nP93hw~1p2Ot}BJ}OVl=$K9 zP~w%~$(u-oWh0o2GaqQG>EQZQR1oliCYtu;BqoM8<^z2$jAS$|l$(YBV6%|w`Pt^9aj&n_J$#JNUH)|F+1!r`f-O6M0A88g`dj3wcFkSK6&BA><2y2FKI?BC%@UixJ}JqFSc-<_LEd%jbLqF5aUV~{CZcg#*01=s2=9*?gjB-n+3qZQ<>oO@H(7)f3Ey4h{iJoF~TL4(OeMZ82Xt0XZU zDoRs#T2@!jUa5lbm4FbUeoaH3yzs885Bl9_Yp*4~wK*~IGW@K^&qlS6Co>aU zeeVY%)*xc+)r@#1Co<1%gO{y7IEu7fFnS%j{ps+mP2h=N=0hW=E2itmJT{0_O`o@9 zIuBXvboACj`vl@at_2Jlk!((WV&p?4>H3`G+uE)qC=vL2bw?=)0gLd|t}i??+&^>P z_dUpiEy8%vzoNtLg6@%#dY8^jXsvM3zS!E8=(bPO4I~7eHD(1{MFw0?H#Qg@`5e*| zIA5^U3h|G!3g^*_RUg0Xb}TnS^FY)VV@Ov*S|=k)VZ_7CwObTiEdyR?#~yADT#PXR zWLc1gao+ay-hc4mWI~29=N`V~k^6!${5A9b{vy$M&H!k~5@Kcazyj!jy@_==GlCE} z!^IcV`_Rg>#;OVwPS!MVhCezEz@Rde$S&c%h}R^CcAx%8W>Cb#)ks_Ek;tLDF zvg``39GYsOi9)nlh`%Fnrmq+nZKG!lSpFI1uqD_1al6s_fIW@t}9Nr=rEui&H(E*c@X-m39KH7n#p#5q>E#tvujL#Mf_9qiW2h){rnm2!0ioCf+ z@uQb$v^3k<(-Hkn(O^v$Nqwz78*uxg6+PF|Q4H_f4T6VKj0AV!p%zV!LSt8JTPdPp zteI{aE^-@VdBJV0XmXa6&9|RKS+351>FR8!j&&SicaUz{50W$LR7T_}WL?3r1j`w{ z29}D8He1Ef@svsjbl`$rkKo?CNf<@l`Syy7>r_eSz;z^@(D|J}_LJ^TZ?Fjr!y63Ev&PmAs)+QCV4Yh0?ks?>2?a_(S}yG8G{?OvPBaBi;FRV!rcv@TlXrI)qwwjXdd&ivtCZ8n}_ zT|mb6EEH~tQN&bh<3J;@_egZyhgUVO8idriXvzv-(!|-SvNW3gdo|hDV%RY%vGxi< zQzN-Zq3|XAR!_7gNA5Ws89B!`KGG#pSgyk_LPk!sy;Z1*+`lIhE|K*ir>Hr#P?(F~ z>It@K>OF_mH{U5w7(tJl3p_oky$fA z%2hGV9#*-;hOo|0bP^45!FglG@hd8Nyupc8THQLOP|=SaOMGUNV$y1ikfp z6A-D8X%F?5b105hUEWGp#6_&F&{AYVLG{l)rJP-zM#tMyoI|BF3?+QE14j`}S7xz5 zi2T6o)We>H5Mzt8YmwX;h#UDEb~bWnKyTLiH*%9Z1GzJhI|I2h5RdLleNV$?)G?co zX9n`jAaTlAd1fHb41PVUkY@(sy#6mVGYF%gdv>LkOxq!}>ZWwt=U@PFu?cT5CKg?Z zGeZj|;zBDkOUt7uJy^`bFk-)-BOF{T&MYlam(R?OO5VMIg#!}{ z3s>~XYN{!JhlNXG(-m-}fWVqr92_lMbQ&y7S(Pjh$XLWci@u~eN4GQ#RDnQsG%crh zW7cK?Spo-5r-4RwvINjI&j9vjIPQxuU_Lu3n)?;Sqs$^$2~ z6)V+ju_S7>P2EIoef6;GVe2bwNd-hkNVu{(Mjc(fLcRYBNU>ED;Z=20wKeZBci5W8 z)=wm{q%yTCM<1nHAS9@;FcP!>xXMjs-gD^C9#s>S02-t=6bm0;SjrS-HMT9{dWW_& z!aYK9z%4@Z#u8IRys#u*5pT*X#G(dcMB1Wkp%jv&0SE?{NTtoO#M8c1{}H8UwlSxV+2gHQ0JrjTO+x82lA9{4W*nSqj%B~`?rbqGR!kca znXJsRGRw*=E3@1)%RRH)GuQRVe}SHvu~#?d5@(Fp|G>D=xP-m#W8-a031!sRrqk@L zUn44Q9bwzse@9zUXpzQ)U0k8YXh2ghp6~{QS(m?C2vY5-YlC6=a*7{YLvb~ z3KO$?P)o#*0&d8(TW2cSg3_$De{i&3;I*@&!2HRv&s>Lqg?(KVlZ4zepIo?zq27d} z<+?41;fC}r0GfHFY8PT=uY_EwIlg$Izy|r_ixI{oeP~ocFXmd)>71$>nJ>y@1rJ&gMLAP*M8I5h{KXpr&l< z=6rlLplT^zW0{^mOU0m;=)~+@az%xr$Q4ztsB(+ipq-Xm)cd}JqaVDJwZ7VGi~?G_+L8qCDXK|l_d5AvV@&-<8OU+8!mw0aJENPw zu9KUFZ7{~VZ86svWwbiS_LL|Whjk&=8MD-;V$(}){Vo_iqm#A?udsaK``bU^BYt+l zDOi?)pkvd^HAXcZweOh35gfhaqS9H=>a@oNf>_Selx9eIoMKCGNKVEc7GwRM7A(}@ zUbdWr+!R&pyH%<>IqX=eb=txr3Yy32pBKx{aBf~*CfUc+1?Md*SifNe#P#m>MQ66K z!#pNYUvUM8nG?f(#Z??;P8j1WuHZB@Tlz)CKIttj>44?kEr znQHDI@h^86gbe00;6oDwC(Dl$u=zB;9$^vHW>^+UT5`^OG7RV znWOkuqcNE~*I6y((vVBz&tejGbj!EDjyXm@GDoqJv8UW!48bNW3`d944phqbZMJI1 z)WvK>LTc5R@b=+SO3Xe+qm6DKE!o)$b%65THp^_b|2a0>+fvp}>cZl?%~reiu$Y~- z$6r*K$UPM>3ZH&f}H4*fmHxF`&kw?6ECZ~C3nx&&sP8b(b)cNU%9Teyg-PgUt?_T_V* z{qGKO1E34FrepVp^+JPyjn44@0c%@oc>Zej>`4XHo9O-0eHxK=?b%e^v9TbN(sy^$ z_70DAs+c`LF^{qoN{_Cm%_z<>`AWXuqd@5_K}k&sl)krv{%pN|>7&}JlBZO8y7G^r zXzuAlMH|_^N3VMMsQ1rNhdixBoX6Kw`p!3T+M~N@u5}?*G7=2CJa%&`Y(H1c|8;etY zSt0j?L-HaXr8Y~LMZelWCs#QM=3!Gt{9+181+_Tc-$fLo`SLnSQ-8def7mI2>g}tB zqAm6SbvO72p4AXgLp!%&a!u~x#AG4YWK=-8Cd+NI+$PgI*Cv-QJ@xZ+@sQ@nG-5{S zi|lx(s{A6gJK<=;;R86hS&^D7IUQ0(&bq#d_NUd-$xfTK^nDf0;6ka;KR75P2*ZwE z9sTMrK7t44bKvzxwunb(cDw$KFr5o zUu-T?6q&B((}L?Tn9JtBw;LY9#iiIWYE)mI!H$k#$I%{)0!^6}H1{%mO&4;eSF+8j z!lxVA@8+W{$aNOPFjXiktF)Cd`T%iB#gtdZPEc5jBg_16mj|tAx;&wX-aIS0g-$cd$7cG<8U0!{rsT*GEO~BICMi_ zu!z#^i#4T(l{D+p`2!cELo!XN9+{<@Q&q;xn=n>oqd6SruKUeU%%ovKVSX9p2fpl) z&yC-!cz(ziXP@KwcBex5-hA2p=Qjk+ujs~q5ZCE)4}Re)z7JnEy0#0?Uzo|Cd5Awg zyze)i;3mxcj#u#dUF-SnPj@Q{+Bt#m%L~uu!>w}Ln|!~2=1+~>*K5yb$$T%nr~pxp zH*`2N;PttImwNC$`P$Kj0G|J0CUai)0%GP5jb_gGpMR*KxRDl>z4u*{1#| zZ0f%>9jhIdaVPdd-m;obK)Y(JT^2>tHzGpI+}daa`h^`{$gP=6k5r_pKKPa<>M*aK zr&1r@jY+T(X9nmNM%yBMco+@40AU5a_KH4@9ZdLo5?%ixrL%vmsrYmp9YBTrWvr>) z0zwkS%QQ4d2p$0GhyBbQ`%}t4%v*w`3l5kt+n;!035D|JfB}>S4Yuc_VJj(@_w9TL za(VyN>bJaOyl%^;yd&Cq|Ir^SjUG*VaLxtjal0*Jv`S@C&_Ej>?jEim6A@{*MY~7q zwd!!YP3f&Pi9&a&!7OIJkt)i2W}T~}TpGVu8Vytt#Z|A0x5;8x#heDJn8Q1(Vh(TD zz`NGKR56Edq>8tH*WjPGA5o4uXyBcGUJX`vw(YfCMe(g z-Mt!MukdO13LhV1uke9$Vx+!{>-HED0NQL&64z*&5h#8rbe>NqN=6=6I(k8!-Q{@ku^OW_Ato4YCY8 z!H4*USXR8j5A+#mUhzdA-sWwyoOq)TPk4F3LSFA^xZWYvoE)je%q&c^;R%zTnmRjm&Wt4H+Z1pGY=FDn z%)~tGW?V=+Co_^3<(YdkC$pE6+1ttN3p0z?@#h8uOOO4v0+`u_S;RkXh`-(7$g(Tp zh72X#-~53fAOslNK!zJyK)9hgz(lw(PXMxE!9XZ58i)l_fOH@m*a+kS2Y`H_0MG#@ z!VU8T{DEK~6c`P}0x3W`kPU1E@_++CK2QMY02ARJ^#uHZU?3D24a5Q|Kst~OYy|Rv z13*4d0O$Y{;f8wx{y;Dg3XBF~ffOJe$Obk7dB6c6A1DBHfQfJ;JOO_o7zhPM1F=8~ zkPc)68-YCF0FVz906M@#xW_yJe;^nL1x5q0Knjo!WCI(4Jm3J34-^18z=UVR>IcEg zD`(j5xZMZ&smGnqPvi2b&l}69oWI89^Luu={QfZSCo^;Z&)eN2 zNJc#{7EwF`^k;nu{$2-O6-|UFaUEkfcf&PEGS>}pd?P16J$ceI$*D6sg(svv#e5yZ z4aZO?18#q0r~{=C!?NKJ8X(6o#W8AO&e9+Ew^M}FW1At+6L1|RO zJ)T3j$JYQa0ULl#z*gYzz;<8<@IJ5y_z*Y<90862CxBDH8Q@#sd*A|a2`B=t0;K@* z@OU*)3s?v@3RxV5(bOn+zzb*&v;x`!?SM`|AkZD?1@r?301p9=0KRfrY>#U>T4Fvew{QqTO` ZGAs^R?&f90%0+5je*5Yk@h`Uk{|CG!1fT!_ literal 0 HcmV?d00001 diff --git a/doc/img/DATVDemod_pluginDATV.png b/doc/img/DATVDemod_pluginDATV.png new file mode 100644 index 0000000000000000000000000000000000000000..dfbe59ec1b086a1a1139127389f0eca096149b52 GIT binary patch literal 33520 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 literal 0 HcmV?d00001 diff --git a/doc/img/DATVDemod_pluginDATV.xcf b/doc/img/DATVDemod_pluginDATV.xcf new file mode 100644 index 0000000000000000000000000000000000000000..b013125aecd416262187b563a4e4b22878f39c2c GIT binary patch literal 103382 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 Cp>n_g literal 0 HcmV?d00001 diff --git a/doc/img/DATVDemod_pluginDATV2.png b/doc/img/DATVDemod_pluginDATV2.png new file mode 100644 index 0000000000000000000000000000000000000000..bfb75641a820cf4457a8a990e5a1b0c9ca66c41c GIT binary patch literal 23947 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

pex_3hx$y!NrE2k-RDo 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 A$p8QV literal 0 HcmV?d00001 diff --git a/doc/img/DATVDemod_pluginDATV2.xcf b/doc/img/DATVDemod_pluginDATV2.xcf new file mode 100644 index 0000000000000000000000000000000000000000..9606321f7160f27755a06bb07b83111fc6323758 GIT binary patch literal 87539 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 literal 0 HcmV?d00001 diff --git a/doc/img/DATVDemod_pluginRF.png b/doc/img/DATVDemod_pluginRF.png new file mode 100644 index 0000000000000000000000000000000000000000..9c93430ff3fe04055f2eb16a23d97e75cde243d3 GIT binary patch literal 6360 zcmcgwcUY54mk(E^is-7SpdgB%AWhm82&gneRXRaHqzJ?iq<0h*RGJ7PH54gANJ0xO z1e7YhCy)>j5D-Eqp@gs&o*JPVK!o_h`PT)op5$L+$rD@@%j)21*-Mm2R9*%Zijt-Z7UA&wxYeV!On7=%6 z4g@-X2cmJ;#E-m4^$#+cCbX_D>)m6Au(Au6Hi1fyo+Pkdv2cLjAe?e$dBb()Bs#mC zG{y;~P*{JFpTOa;;A-AlbC!}s=w}7qhgHw2D_Bb}+qn#X{%|zxraJG(&a|D@*GWS0 z@WeFfb^m&s^|0hlMEAOiKeElYJ2}}t&Ve0JlXOg69eCaGI|8&5+#t}4mBZ~f@B+jR%jUB^)V4kAD@y4eY?UcH}_3p z?wi>Gcr0_ZKl6E%_~Cj-MQY)n#%!1BTT?62R#*YmT^=J`$bKZprju>1(}ZV-+v)Z3 zJ+YfC1A>~4pXyIqzOP;<**tdLS}ZZf5fT~vjyaA%64E#uF)Y^TGn`J&EK}D*}UfR?Yc_~ zX9NGCTIDyyweYrT)mF?sl{u1jZk1{jkNc&G<6NMVg<_KUeF@bji$f#bN&U=BqK)^A zIo&+;LZ&|6K1Obiqu{zNdCRKh$f4cHUhaX;(I%E61ov1I)9*=t50l%tJ2!#D92|sT zOg^;!t8aAZQ=pjq>UJU=@R#c->%C6(Ek_GUff|% z;kkKSP&I8_1O#&K?M_#Il`Xj755Tc1=5>?#o8$cMkQ)%65XuXl(lvOy>TpT3_ve5U z@!*%sQ7jlo@gGL510% zA66cQcN$_14V|Dti+rGRRiRPYSI~t;x4a6A?_~%UcerwY9^y;d_O9;yFz|tObNTCA zA2URKGZj4M3)=hQ#<&0 zCnfEwYWYaie#*$q3^Dvo1mteHY^p4$|Ig_WJ9dQ7&6_tnQ7gQ>GxQoHx%cZaSR7Ps zkVr*ypB!^%43+yv=%?F|jQRO+k0p#~fp81Iq%ej)A}dHsC^X|z_zG_rcB8SOp|)_n z;$Opw#`gDiqV9DmD@T=xyGs<08u;PeEN}7x4|cv5ov*M3^C$5it~1o$Fv{LyK6QskOxxAXFrs0QQvP7E!&;Oj~U=UjL zTJcdRJ3?ZR5api-Bn{Xv)vlaEgHYwCmacMyE-31 z!pRTtokP!s-wU1CTekQ(qenvrid++`Fn)bP5MT7!Y!tnSj>cRT zLXK;MiMv+&k54vRU&?1cypz&O=0y_d{kr5*$4GJ|V%cY>Gg%h*v`0WcNjhufdl#hx z$B@x-7a&f{LeJtP1ILFv?yv_VMHJ~&8 z=<9etndczJ}4Pvr^jNi!#5tzEo^hQXT{ z85!Zb3ErDjU)Zl7aVd%jtqUS3Y7#l^)bJ)eBNMA#fNvqc!>8=r_!_rY7w&CR9A*oWWbjoersmxA!A z_!Y0MttF(T6#-f`x3=P`W)>D-v$M0EmVvX7Q>Wm)G;E-Jix%aZtbW^nR{qLu_jU1- zrwL3(Okb#KnRa;J)^h3Cf1j#AWB!}80{IK!S?%q4^}>kBrg!A}(0bMFU#B7c9a8m0 zbh;Pgcc1#3H*XkA^#@!8;qKZf=abv~tkW+L*g8`dm z4Gx;*=H<;V;g`*xKQHMY7|^%&efaR9j=sLBjScd6$Jxyo->6;8qcn$NsbpRbgudu zhDK93IYy4Ma;whWNUEW_k)CD*6N@$n%IU>(Lxz9KZ?34fer1Ok`R5w*9CYbrmfA2%$M=Ts}67T{;2MmQM% zA}3rBuhRh-PDy$lWvor|5e)UqRPAEkGgKjH3nHY5giSgzs_k8LkG>%JnOSI;YndYM zdnppCYhVx)LjNM@(wD6XEpv(%2wJ&I_3Qx)EK>lQ_>`DfL-pBSbJWzD!e4^h$`K ziWHd1e3R4pnVeWbvZctQ6ik?ugiqq; zt^;}cIFfcWU+(t$ES@@Bx3grtxkSaePuv?Ijv5<+bAdyosiENvFi^l!^{m{}?u%#7 zo;{>U&)M!zT}}hsildoRt+6x!J$$zu-ab=E{rK@?3Skz#be>NueN_@|1SDS5XT4h} z?47$cOzz-=AFBDdP}Ldc)g)gV#cNtFEjdT`ZtOWUdJX_N&X8>xc|jjuT$FzY-3Ocx zNIwl*k1@CLZ(iJkiy9(LD&45`Xhe|ua_zbSfeG+Qz+DLukvyBs_?)V$KhK^!N80C3 z%n1t%3)oCm@-F1$;sW%|-C3@;m8#pyp6SULpS(?r=6{ka@ciSD*lj)Lm`H=wrBkO) zrTqCkJMpIFWQB6jTz@W~ob!WMh?Zwdf>Gn62U0pZ?gtv3foj$39ikD@+H_*Ylt4z* z!n7%dT5-w@P4&=&RaPOlM8|wy9ak^?Y<}!l8|m|6iYdR?9=ttQ_p{eb=Y>j|cQl{8 zPMK3zf7mgu6h_^rJE)?f!3sFV1PU$8Qjc(L4Cf}}eX&5y$IcDp!+Mb5TgTNM9UaBQ z#QN!T1B~W#w!eS1#Y@;kZgj{`U2+*Bs|ljGjZU`?Mr}@my0AthAJ0vsZ(va{o79|m zV~tpLy5?E)i%3|obD7JYd_k8)p?sJpcsp=;tJJZBk6>tOYWgalPdR9-SSy-unzvWU zcd6*AcFZ6i0fD@$@tT|TvHC1&4Md{J)sl|(cKUZVLFeyb#{dfS+_|*fF*NX~nVdsG z_yj+&TYc2#azFNXr*RNc;7jEa=8gnUHz!T9ecszfg zV7xZ;j;>8d-$#-My=CT$2cOm=;~I9_+1M?MvPUBds!avF@vwiCLkV_U;LDxlI$L98 zNqr30mD)c{^t1YHZG}Zd=G#Ow1`R`*#(>{bYnF=4Pz?s>VFEUn zBp_rUgK?|dc8ca#Gytd^TJ9X@;pqtuu@F*Ivjt+J?E#Z?4Z^{}A)prJ&QPgPNYx+6 zHzXZGWNEbe#m086HS?G3+>&4oCRBN-cJ*nG)g^!g9s;m0w{MLJP{X@Vgqho(bUv@O z7~eQHWgBwa!D~ml?Zd5gUA`ieRp`$_P3!CE!C|3QaxtwOR_Phx&P@|U#g=hVkuNZ4 zZ!33oJWRvL96oINb)nJrsMmkRqg5O>SYJj)CSblGe-tTqPReF;x4U-L|Qz!P-Ia8<^+OD!`K7P@5e}eB;as2!4L{yOzPf;rr zAY5pu{(lp$hexSE;DXG^Gllfqo?6RA4OXeWX>T^B1P_K$U1)5x~|ty0d!;jvPSoXQFT z@4&Q{6M@I;O1>=V%oWtypDU+j%4Yr-wzhj$vk29QRsHimPY(@Sd*qxP%(cs}emrb@sQ$|m z)75bLQdOokJ{4(eE|w169FqZWEr!(i6&T?+Ge~cLVff-kO?&D}v2(X49aFpJv6gT* z2zo*;%n6Lk1H!LakLu2|S89TaZfO(-5I+Nt+aWK4159{SR$jDbj4W>OVuKOE^f3Bb zMo#^5TwT@mW}k$mDotkdRZPayUnz?FuPZ^a#~Y`_@l@?@TEsy-^(U&X1}lu+KZvEo z7FZ`~%4%&A7fJ};Uwbjl;=XfDBmE*U?0jGDtF-Ve0BL@s74?;WT};l^p_|qAr<2HU ztmN!<5MBFbh)YUNq{(T#S9a#Hb(B@tZaZv$7fRSKzIY-(FIU8Mqg+)f5OYteZq<;m z*~c*y%>MU{_%K?G_CtR{50v`7S95-amH)ckVAbpD+(Q|OoK^}fsqIrTWWW>zi78zX zsiS%MP*ijFueuU)k~dVFQwm4+=_=%&+4m5G9~3pqFJC04_y#4}sz1R!r@36ZJ+`|& zoj`A4st4Hb2`$gO1#+h^^-dMlmbmcvW|r8c+Hr5OBJ7ytwb9Gg1qm@{?(-i=?)jvf zcy-rSoEp>PM06FbSJY*ZU%pE}Lsfl*Quw8~6Z1c-K(YnvAF`|H5VY(6e1>6{DF>iq*SYG5>n`FUdVGJBSZA%;_!51UE^gz{J$w+!r1kJA9w9EP&oJSUh|B z0_JLSZu@X3K-y05!;8ajsUWLJu-jXc1J%Cw%VZmwhZ9O3*(Mj1k0{r(j-L4v*I46M zi*U>}MjS9qrv9z)0oBHBZ7HxL+>K35a|^{9BWqkCW|d5VbY(GxymZd)PX)HVsmJrP z#CUozNY}sXLF2YtHN1hNy$?&$$px9kdV9=ahx0EpY(+U2#H6tG(EwY~9PE-Q8{qC! z0P|>tc0Kd-grQJj;#V=@+JEFF|(5NmNAGM`+n!=Y@BV_KB(_VE&ptt@%6 zp3Qcr-|gess6F=Qg9_s5KS0DQFM1z49@dvJfv%S3ae z*)P}P&IqQncJ4OH>utQYJAM?j^`bEk7($BsFudNPozJ3%zA81$Svn{izj{Ok&HasE zXw_5*5cs}kDs=bsVM+QP%kp8R@Shso|59-Nr;h#qUhDq9Us}5F;7I(1nf@{7ZBf7$ OAc&@c2Ik)5u>S<>9*=wg literal 0 HcmV?d00001 diff --git a/doc/img/DATVDemod_pluginRF.xcf b/doc/img/DATVDemod_pluginRF.xcf new file mode 100644 index 0000000000000000000000000000000000000000..f28239b4999b8ae9f5e95a60f676bb2691b55b3a GIT binary patch literal 35711 zcmeHw3w%}8neRTFkl;zZw)Q2SMq_G?fFU7dlM_NXISFqP5|B4V3=kj{5(KmXA3646 z6{b2>Yp+!>)7Fl5e!aKtsO>m?Fa@R+VzsT+sn)AgdqpyXS`x_X?0x3{Uu*Au*5294 zA_dnnFt#9RQZrZjzw{^p&+)Yhe8#PTE(}MrL3gYKOpg;is zeHb*Z>TflTgwIDn8K84PAG7l0`zZM5vVJpeTd<*J+qTB$T_|h3cz*Sc#)e&u8*@L~ zv};T5-1)Wjyz)W8+O(}IYPY}wkhaYE?wO`A8gY;D<* zd$~voc5GhXkXtmlNPK)UQ>;r9&~&{|Q~P@E&>1#u-MPJCLsRqS+$r|6P`TT$(yA-C zuDNMfZd3DyEsZptH>vGoR z=HXTHFnXI>no+p2p?RlWD%jbyr!jX*#GY`;BWmS{Zf|OB+{(#lXx`P-u(hdSXYOt> zIa?c=H(!T!mz&qvTyUMZ;rfQ1jRhNehwOB4ErWhOetSGPXFhpdb} ztr*VWKe_9IPqJK?JYNe6%FxVyGs86dG4{b^hL)K$OhXHb{29~ed-3FenKVU3NK15C z*>PHbXTM#a3Fd@pbOo9KYN zFZ%Y2V|e(nqlZP`WSl=4Kyw?>?<(hq$@Q9BRA3r=Iu34X~^JT{A zGK4K_8gO?ah$%4b+(mAltS7*86!Ze96Vwj~1a6K4#*IU}aZ5mP5WYCawS&F~ItY3O z^a|)L3BMuKeP9o0FX-!_?|_~F9RIC)U`61AS8a{0we3$9G$Fxwg1k}b{2XY;v z4=9)_{2z%cP+PbY@$yb4)m*PX{DpivPlY=>GXC`0RQe-QuLAJCwdwL=STH}_LE zzo-@HHb55g8>KC9h40D*L6WgiRMT~@<@88poS-WgxrV_`KU_(bG;Orz%&VH~iG zK2qzqvfZhynKVQ#x@(x`Ya);CBHu4=itZwx&}dMgyG9ab3A&31$UM4>d{)xzVt3Un zx(m~$FquZn?k&CVrM^G@Zyiswm1?|(u>?J_%RNV~&dDk|l6>B&8>b~MN%pA4k0yVs z7H>=5;1uyXtIZeAamsk5)rsx`=Ye=|qmih?E6NAr!P}GOdc3CHfq@&6<|fRHUB>Hv z1h4B`9H~-W%tiPv0Sq4{K7Ig0=YsV)f3wV}O7;2s;KT@n@_rR?{xP?Jl?i7`Fy=4d@0Co_Qfa z`@(+!{WIto=ta0ta4)XP;hTBYF_5uqyBtjCIQfisX zycjO#9jRqKm!`aaR=l5hkCvf|6U?_|JqTCz*)E%RmQTeW=4D_0|5}DQnhl>0p4#Y2 ztZmCWkRBDsg75uAim^w-?`<1=Q*xwMrklo1Mv{ijSS?^C`jfJ9oE8w3 zqfN)cGNR38r?yR$+B+GmnZGy|IA(UR6CLP82RebY|JczZ*y&&;9xf_Jo8sYcQSqiS zMJKRra~4}5FnKR;{iPTm36vRPjO={>@8XlhILV4EF;;)MJgrRICRFnyHMpj55xiRh z&S&xCC*Wh{CH{Z$zsbC6a9y0hZt-EzGoU|!UU&24#x?B{7+IIx0Q!5-Z#6CN8=xP6 zeh&H#=#QXNaC%(=ss-%^!Qje=!Il3fP&e)ifVmu?bNQv963{HrQqV@wF3=Z1_kg|y zdX#-=JGTrQVG$P%|7|@+w&_3{g@*T}qYd>OJPS%vMm$1GDFaWn5-zlZ@c`=Ok{hjV z7Z3&t)Go(BLcT*ajvUpC29w8_dMA0alT)=6r8APIioRrbpp8RlGpwTX^rKsHJ&N7ar9-_K~6Ubqra}oPG*O&05GO+ zR{%yUIPYycDf(_m5I7-*#bZ1PDOC(7N1|Ex7viNZkxqvH64zA>VZJTvQ95kjlO?NS z6Z7l~VZipRzPC#VTi$Q;yW?!z7i%`OI#_EXuQa^=ab)P|hom0Fd|w%9V=G;|a-%cU z#?aYI<&8UY=hipuWLRR29d`C^DRgw#ll&Aq=m+*^!Xq;0bUH)RCOn1Ezw<%xir+dB zgzyx8Y@P{*rcJ$E(`MoNvLa1ewN=x$yrgNnCu!O(t2OP5i^<(K<{|w;?NZ&)X69w* zb#kx@0ovZ zuIibu>pzjVTNMk&jJOfYh;^+BuZn#k*%VtCd&JZ~sGn|lBcdO?%i&y89jrEPF{(4F zyViu)RNs}n`r(1C2a@Fc?my^y>q{2r8EsW8R#RVpyHOvge`{VW#`}IHdDR1_HtbL8 z=jq+$r#9;PjMHmn7y7jo(dh4!W0R-vi$?igUr26=2BOBpCU_IAug@qCl=uJWVaa?3 zLsYTp+G`&)t_@s!8hH*@WAY0XfePdBaWpUwLnR(~v&?$CrRBkL<3^)Aqr8hVQt{@` z%gg7U`JPw-OUi;}#vY?AqpXXQS9WLe8`nS8vs_$T90^8@g*ze{a-IW`zI$Hpe{M@e zT&$axJuOieuA7!Mjq~kUD>cR0#fh45O>tH+uP!LcE=nv2FDS|?VsUOkc0popcy2*f z0gIkh$fcU#Qq9We)zo?}U$-lMRbHNWd|6I*PFGpDEGK8__Ek5o&B;NnsnuUl-xfHi zr-Y%9!cZ74%*(5cO^>Zzaj9rLmBKJJJT*T*y1HTQf=N;sN+}Gb;ZjQ&rc)TEho@V@ zFq6Unv9g3AMq!ABW0o*fQ5eu?OBkvt4AtRkOBiNT7-olOdxT*&g#pzRh8rIHmxtH- z2*Vr-10=MfqV>qFo4@(9#XiC?kHRo7JTDr1;{IrK?$7S@6NdQ|hOLJv43O~3HAk+c zFg)<+U}0E5Vc2?z!hk7@Z9lR$HZOMjVIN_rp)fQbqA*~ttM?pPar@8LeeQ^lFf540 z<}X~h^U%V;!nfwcV!Utbk!A58ue#-kpD@geM!$1p?2*$qN27eNmLqE@47VNj6$XsW z>V}5T9c~CToL*E>!NFQ}q=mw8|D%J2VK#+f%OMH_XQbkZua}q4`q`Jox~QfwY(7L` z;N+EEf8_HUzj3fuT&tom%x<7CaGoIyyD?>}r7%=d7%Ib+mN0nMN(F_XB3xk!Lpg<^ zJX~%GLxjQ*2}dkp@T@{E)dZKS6o%A#p1N@L`h}J-Or$VO3{T9-sa`R6-Ta}1;UWsd zMd6F`@`{QIrpzq=q3bR^G5R8m+!*lbT*@X2mwdeT1Q$!my%^!hp%GUK_93*jC>lI6dDCWwF@oIdfLE%?ZqT zt2!3reb>YnuDh}3>bRdUlt!aF<74BeuZl+bUd!XP6o&QW9sJFJu~}GGx2~-&PS!VV z#H`0;39T*UpO}lw0#c6)`6uQQs3JnM3;8D|l{=y1g=|%?U#faz(w5DoSg8rBGaSL-lY1P`wrB89O>v^_bY1Ix>oF%o(P_#FtWOhAlTGdkN^p^YN_0-Jt zy7W!dr>`)V*{fzuc_RM!HsFp+7sb_dWpk;$GRKr99!c&6Jh@a>TuoOtm)NU+OeFDh zV0Tpe)v}&9XmeZ8YIfQ9y^?+&BXdx`8 zDVy^M(==ZI(=IE9sC*U`v!&?)m{u(ttJXGje2R;zt7+f>rd2z~sEiX)YeyN+L|B zDVwwHl?j->?Z^(ov}#Ndrqh(oYJ2qqkH3Dnjxa54u6}8Ap*l_3tRhU)B12O8gOL-PpJ{t`==rVR&}_DjqP!ZfWqz_edtmJ_CF=K-eu5;Kw(cT$LE z0h>E9rxK^}VA?M+FC$FTW(7?9CFTUqJ*`;4v|nNt5TYdS~Z&q z(`m}4PMD^>37BSLo705pG-b1tFipc6FzvFh3Daj$VHw4MY1QVoYHbtTr#P>=nx;2k zTD8EfTH7G^DMB&lgRJM-wzyS0Ofe=ca=lH@>*H3nf4NUyPt8oP%SI!t` zd({A@uiR8inC9ZK?RUa-nzC79uS~#nLwq@5S~c?t(`m|PvAz0%$De7NMVOY}fPU!> zKy{k3IfeKPa1tRF$--`g~V33MG4dX5=)q-cL6Z%mzY-&rs;10O#3C~ zM8Y&Z5P)gF#LQ2NJ1Io7fX$r*^AZv(PYZ06z6)UVFeURMVlHHo>m25bNNDUQaP-4uH4D*T9(BcLq#(c?8 zOQhz8ls7SIB||-tnp;%fBv3yFuf3Q!O#{!H7>i~5CB$j&gLo5TiDXzxoaQEqH!+r4 z3^PXV^|0HH#_M4$lg}NE*TYyL+m6!fVXTzR>GpVLWNr`HkbaM6M&|eMJaHILhqB#9 zw}@=WQ5u2gqgQ%9WLwTodPm0~4ts)@mXF%=VXu!RWux|d*z0$31fV=R&xb7|b;Rmn zJRe4#9IskJ_As6gTV`vB)!ZudCPt0C(E?&MHxIpuu|P7+C027g(VG}^CBtCIv2e&n z9%33zib08uU33%==j(7^v!QZio)4ppy`2H)$06)s#&rj+N8|o53gxSh*!^Kll?{h< ze;B2bVMzCfFHe^XXGr&l5tDZq(*0pi%aHC5qgvi!NcV>^+hQPIr*U_9uiI@$ zyi0C2+ zh%L24x>;YTeIHiIMlV9L;69C0?9C>AH*n<3`6=sj46^~NI!^C zEE$IMgBT?i1MxbI-@|*|ZaW%3h@s2pj>ZpSe^ZXq4`NFL9TU%t%n>5{Oi#!&BlCpF zhRzd*@qH*k3v`sEMrj1TkY4Evk!?9U>0KX#IP3{3AzqKp7h|Y5*TeWiY^fd67h+r?2YyIjhy*xeXkUntFYk~Rdx&aoCx}(D(Tk8d zoCCyt%?8VnxkHTc?5%~@^oWd|r5i8YLWfA}tNJDSnHQ4Do~KvqcRk6fm-)Z>vJSt2 z!gKP4&qt~rc}Z_L6Vd0qj?mCKNrz(qce(at;G}W-dHq8DBwFcfjcJiH4f^$uAH#pI zIh=gn7(EgFKl?1k=tIBO^(XJr|Nhqd@!w;D@$_4Pw~W)Tq8Ev2KYbKFLGy)VAo=H0 zjk^A|L;CZ}uEBqw7o2w@s?NAoaNhe|IXL<3@?G7v`leG=C#OZiR{bJ-6Q&9$^KQ&AO&;_Tt@p3Q^t7YAYaL&qT8-IEnkLQ2a5r??Jd#BTcP0 z@7bv5yWOgykrOEGcBE!bVn>|deYX=evyc~^@NBo|G_!;koxpy#*EF+~7o9MFx6d>) z!i!GOzuRA$S;mXb5dgQZG_zb>oH1sGJ1orcoAzDvt25ottg@rX?2`gFir$JbTB!A8bLj8Y@NV@rmK^)QM4;h9);Y_ywsiv@$-tF5d| zofzPLmJ%JdHrZO4sm6twN6_RXL;gLnULkp);Fjcd|CadA_NrJQ)~$F-MWZ#<&M5*X z8cdum_}k=(13SKL-di20?pC~{_LDrm&rqzFcm7K9`*?`)i%$Dd7Mpk_ysQlI>TyE8 z9DnF7+}PWnoU0x`Fdr7paKgYjU62}c;pyCxjB~gTAR@02hoWU&H_1%FzV0=GqFB^;*XfxK~BDyvP4}pYhFI-$5i;psZUtydYZB z(diEAavYys+yRJ8zxTC)*X}Ok7M2?FB^oX`o0-MD zfpaFp?bggJ;zj3#gxjT=S-^|K!hmyn!tK$_%x6o2Rlgm(WkFRxp=oYBBW~_-fJ(B6 zu_(jNQ@=c85wxSZ`WekNVP!DWMF8SdOWe?e?v^QKQB z%b+lVB$LYZ3t}}b+b8qcEayo!@$3qM6r0XRj`&z+y*?AplG&wj4k7#m_yoky0zll1+l- z5`g3%9*ss9{P7`zBxDNyw;m?ZV+RFDGH<>BQpZM&G<*7@8$+wMQ{Wr8Hj7MJ~`+d9|W_42yAJL~Ny5qDy@ zb$*K=`J?9vlCnauiC4nQ-q^VT@89#l3B<+9LFi?2Q|E4iox3^Qcb}M5QNgn0Gao~{F0BqA`G;6IuZqtT>1JUZrcwMthqc8fZZGJ|0szB$=N&}0QWriew0LlWEBq! zVAqEGK1w1%a#$Fs;6VZjCu$-=l3>*zS$WNMEAp^aFQJNvmsfpV#&u?k15}blj714S za?$1`JC^4FB;lUIK7Buv%clpY8`mx;NV1qCdCiAK)B3U~071WD(xk2`Qmf~0et z10bn{U`UX34gvurm4GP;lFqRpfTR*EB|*|TBm|ID0;MEKI!A^8k{CuiR!V|o2|>~o z66JUZ`)XQlx^ep)JA%qSStLPHhfMZb=ZGk*CiIK8G98nT)gm;zEO+8Mu6BJ1q1jS<*UF+gi>S-l&y=cb6N%mo+)Q}68UQL`w zKf11pSE`4W#AtMBCFN>plUM?dv#*y6qTd&@)b`_CR4R#W3os&=uLLC&{Bq7p~ zCtq)#6f5V<6d+QGYLXD?oS*_kDiKW*BAxS8fJh~pNkZhXAmE&}a(nJ1L=vj{?Z71q zsQL*^lX0ZZq?RdRO0tNt7*B|lC$a#MGL)&G$t4ql6O5Te&~7D`32jmk=^WZJsXe+5 zx1+3@-1eDwAGV-3YRTI0A3l~T=pV9V#SaAiwoJwBA3uGSA?G`|Pf^d7%*o9o`I-m*#T^d7(RGucxHy_b84vsl2@ zvx`>>x=vvETJE7~Zjtmg-9sfo|9B)TNGa$ajATV91^pwDtnj2XXkz;TeM6DFhpjBw zb4MPzOWu=KXK@%Ss+_%&XiLr4GRnQSyHYJ|d8G`rRSa{F^=_H#9_t-NjE`cnyAAlz zB%rNgv%4+$P$8yPu`S#tJm%D8wYzQjP$|})Vz#@D_)r9BtJv*sD?U^Pw53(%w%?r+ z&=yyrfevBEyQ>v8la{+`q7SmjyEKwDY?ZLw>N`@4f0Ro3!<%bw9lApStShwpP4t(GRl!g*`DDmK332XI%qOD?qiB$Zahx@TE+0@#1G!-oxMt62C%Tg44v|6LEa8$hD1 z-@z&CG!@X+@6?o43A9zLf446{psiy5yPW|7Z58|9?F|rUt2hAM?f`+diU+{$4+xb5 zZE=-o>+cY7S1WAhT%xUF>$B`~38Xv)UN& z=`0RNd5=Dw#px&uZ7pxYc{~hNpnJUu#&XtroC3ybR@#RK){25WsAOC%3eXR+Yh-K? z1^D=}OJr;k1$o@a*enWA$*>D#Y!L?M4H=0KBcFE@nSQ;N)+VDH$yHSXf4>vl*`I(v&r>G&BESN4d(Fl zPJ#YGm}YN^lOguf^_XTnQG=!Hwz~rLlXc6e;yEI$VrfIk=U66_wZ7%PrqxAh5LpFj z|5$G#%^hpyd#oE->orI?9`+n?DPiGytP_fkn`4k^VQfsXcowe5v``q`3)f>xKtlP< z!3)=8I#~A`ym0vv(lY23mVxpT>cUNJ`pL~fbvTrT>#+$ql=hH#7A~iwET!CA^P+Ci zapB6PCe?J~(_-$WI&Mw3RJ#qFjl-&tS~iaaq9B!O9$iC0TIAN^k=ijAm0S6hYtOCS z^1O=Qvi0b=#)V|_D6kCi#Ixl$igAf3NOjb>R21Y_9V1Y|CY4RbtD+!vOJkBKNL7-d zuDBvKM+U~qf~qL8cCy}`+U=nRXiQE`fwgWudLBdH?MadxgS0sY_S!6-bxWm5QQZ&( zZ!@~rt+H!F%?+{e0 zjvqn4G^Zg%wy(B#Pt~JOful!oERNI)h9BY&*!JG_w5~5O7lG~cvfrLQ^1pbCMF_U- zyW_3S=k~I>@z^5>8Z}OCN969Q(wDy$xu#Dt&BI;#)^1+X`o`^K`{@C){re4IJG1sw zMA!d?Y-5ri*9U%>OdcT1olkr2XI%RtqjbM5Zx{s}8+(5Pp5D8j()IbqA_fbuJqeb+ zAK4vyW6zS`0pQ>Cx$*6UA`XK|zo{d(g&C;R{OKTCD}OtY@< z{8QhP3o%kX%YXB`fBhkuFi58n68tlS1pjqF|2m-W>lI3|*N)>4PFL{{PCxw}{(ol& z|9|QL|9@!?{!g^OP>P+(#Ny;aCg1pTgnOO8-l(@}zXu(EFPo1EC-Fzo`D-%#4NZPN z2l{}>X<7^^|2R57xu763_@m=3_zyq7sqwF=XIC|BxMuT?mg}1Fn=kK=AIGt}$?n{G zn*S?Z#@|x^5`H!7_M1SrgYX)+|DBuXOZ&m|UC>WJ_`B<0!rxK<(n*k^X}6sV$^jLC zDnPZMwV-XFn?PR%?FW4q^b^oipqD@=K?a_6E+_|70IC4hg4Tkzu@9b~IZ3|fnWN1RdSo>6H5MnSb6ebKy9i1h*FI#KYQ_eyiO0mzV}K^+lQZx z+P4O@8MIUK**trn2M>NoZ|}>XS3$=?{{eav)CKAR4S+Bb`yi$JvOxH0hJELOJ`BPS zuF(;Py?0dw%gKl$BjO`MKtLdiiwVg?KtNeSKtSHW!vZ6wp2P9L1y)Z| zR0y~NUk`#Up1=jcN=(%j0s;y1{RIh;oQew!!r6&S3&U-HKtf>sm{Xy01OY(`Auc4K z=(K#&=A5blN&~;~9awxjX3i)z9v%y^W z#bRfNBf^(2EOYZ(5Jrdy@)P_oG;h*shL|58qoT@csM6L;j_41bUmHPlb8Eo2IM*?F ztmL1}ZJ9ba-F1RDMpY-25{&zs2H|70f;s5Ue(rxMgJly}@@xKFTmz4kWu>ZZ| ze+T~e>HmAj{|@~BV>*N!t36xf*bG{9%JT1v37Dw8k_lYZ>#h5t_Ax0e-lhf z)b>#AUFiy73}~`F*mfr7b~su5^K^203IklQ{aQv$E08y!S2eDT`0IWsZ?Y!=W{m8cls{tZMh{@^cZi7V4{_Vcd zA;TK8)yC)*i6fU>>G_3nR z-M3Fv8;k_FA3Q%?H$7jCJlr*Oj9u-G7@L{>p2`-c&O73g9k4b@5B={$(WJXwiSs=S z$yQr!{Y+rcM)^UXxN|cjTcNUO5>1XE9#iS@;^J}POENh-+nblDHLTq6I5*7p3u{hF z)MiQ;2OS#n?e+Xk3tbVF_XaIIJlyTRhh#s}^Z84El0e2^c1z;Z(^H<8n;8f@JG+a{ z52z4D!e^y5?)GYBkOqw^#aZRS<~M2{K1Kf1B8@s>Wx6E0PT z8&jWNwCh<@;KuVlJ4C8=Kc0ro5QS6*Us`%P@mcG&Oah%MRA(3-bK~PtNz=oAO4DwP zq@0BHPB?qD?Sahu?55}E``f_lBhu_^TmLOKt{V|I3NA>HDNSTX&i*9P0FZIS{DYP3&>6%l9}H$@FKOaK22P{huX6$1{`X z8`yK=ePw23`Nx*)Ouu$pEma$&+6>dMCQ8e49g_3CJxnNDYcvMC`m9ZC&?2rx2s^yT!YajdLB-`H(_vN?W>MqVnBu zHr`E5O?jU`Ii)K*pL1_Kpfs%7Nbc1cO?`$)PDwEpnQ(A)j3q}H8yo9+tq1nIy!nXM za6EMgr~LxPqG=DC?=o0+^Xav4pzik&MmBY~+DIy=Z4$7z{J_$}_e7;}J5~dKdH-05 zbkA33HjCAux`eqB z{_gz!cz@7Y?(70fnVpm4%RJ8E8~;_=W;>Y5=5|gw(v-y9+#D-ZIGef{zPQ5k<@jW^ zsis<5R#sLxx30b(WkuB1mN{Z)a&1lXnp>$%t*;nV;$??-d!6ydLqtUMe3jTic-iQ1 zzOMP;el%OuSU%$Udf@G-fJ^|PQmF%(sO3UaV>kvi-5I*^de;zde*M7Hr&o1# zu2;oj^aj|V6y+E8xc|p^oc`lTZt~d~&6in#gCipiET3Q<5M65^8U4eNyUky=l?kDL(z{032D!jaor*Q** zppM2Hc05%V&PPP%(hpGh3vVa=z&8=H0_xW!^}V-8LMzekAu+iJ~z z0r?R;ohSLQh3YBBmvNBs_K?wZQay4%-_Q;ky1r{WcaIb$aj)HXbg;L-tZ!%wm!xm< z13Wnw7njpT7hDtIlOY=4UZ0u(SK4$tukH+N)BU((JF^+dyxQyO#%p+_+wK>)x2Fv% zc&PW-G~xMb3sK(j>cV&ZcVcp7MN(NAXM+E&^@j2tPb{KRcM=~_RXJViLAbcOsE^Yt+jAI?8fHR7rmPhVfU)@ zK^hPiC@z)y!S3Tc*HY_O2@RFNp;RhW>2e3J4dg`fsfiaG$y z+C4r!?v3L>3*f>rlxB1z9T$iR0?_ALLJ#dHPiN!G$cyL|j%Eq(I zjlAR#E7I@%I%Ros)QUC*Ax_-A0758Lt23$aBgma97LP#-$Wp`tcCAnVOW4=r{2V6{ zQ}<23tc<*~B7Rb=Po1t1PqZnlqJZLvZ`^ zU9-*XZzen-UZ)>iu7leXQ!Lw^d%Zis?)dolNqp{HNI3Mr=TrZ>&Tob9xh`mo@O#`L zHXTwuZY3|ap`46J4_a;yRiVj(=^2R zyc;8S96mZRav)6B!mO%`&GK^ibDaL5h5f{&)K94lm&*1whSe~(#~|AwG>>F-17)M- zr7(c#b^i1`XI!yYB#QLBvXCx>qb@${;_)`w@KEpuHoRJkRk@aDU>nN*W+tn*e(|W> zbi7_+y?s(*Y;HdYb}l_s9}=$c{Hpzs=x8$p_v#;!{t@O8{sHL%tKwuYcB^n;h;P;&E0D_`tLePvL?tt}$dqzS6Hl=hjIkJu4rgJ(=H?E(q zSUH&_B@%bD!({e{TDDqCIwl}8rhB>&EC)|=1&aU7SLv(&=FH_lKFVDlzShDm7Xfo+ zR1j1bcE~^n4;SjiO{kd~L4E0P*@uHUa2e$J)(OW4SfZyrwIVNU$8cENDx_#i7fz_K z4Cfw}jN2)Kk?1peBv!I|@?rdU34H+ZD)nqm=OW z0Q+plUq0U`GdNom>+B><63M&R_v!6e)9q?UTTi()yr@j!ez~LFcjNidIHd1$RBXT} zfTm!Mu(w|#rvq3bLx|14g^Wsyy+8dnSb+tgpwo?;@V-Gq&IgNeG}%jtey zmC+b6~cy5#UbWt%ZIGWy#^%wn$M5Dkxx*Ja59mjpdK< z$C78M&?Ol5`oewN09Va#pD%W7m(gt|rz9JjeRMh&w#;H&fmpv6$w)sVhIoBUt>JVT zpXzyi)#iVra9p#;X>kZ=;PtLc04T%XudkOgbX@k)t6^|K)3XnDV4%zzsN?wZjoD66dmb)qVMaU0vlR(B#9Qt*r&%BuJ~1LzT&ul2Dz3+ zRuGJLH|uLk7tMhWK(Ex9;XuB7vWS#A2w^b{Y_V#i(@ z3V8i7@ax3bs$1;H#_K7!(S{WZ!F+wq_FpYSwoy`M2N>gn*xr-Y%4;0Eu!l} zyWveQGU9SKRu=ox0m7Ov@LNj5MPEsKqi9CtPLdlLYNxE{qry1HPMGe`{d4|H>urwm zn+&G*^POh;_H*TFh^F$;<7VnSc&0HHHklmzN5y5e$n2(#oDzu3*R&6L2a6d82`!dg zlnCGc!s1InBBdXI(m#9&Za`viV)u6wYhM&r@%zk)0p-%7@tvOkwWH%p#)Q7U?~W6> zt`ua?qcV5X<=guBxyMTX6GOXKD8JR`@V`01NNq{KaZuci9=&xU$4&blho1G8f}hH> z#x*Gb%6{q4cmY^?lVR=9$x=1;&@jN5?gkeCVztddTpPv_gL~zkaXrDG zVYOD{>e-1MM|3b8?hjBpEvGHvT7(PR%SNIx*OsSun3c>Ls@dW&H8&?LD~sy*tqIAO zLzZM%MgK3OEvzPQnrmAHKi}i`Us%?6OMjGoR0s#MT1o=P*V0B)*ezA~X|WKo5d8(* zhtxvl5f_fLnwGx5v7(QSaZ>JORU%z&5!=|3TAuM(I+$t?(a8{!lW@NDuD7LJz82P#g?*{YCFEwjl{6qxD&Ah zz`BEF(+XaltllRkF}+gv)33+bpFdgcF%Bx`!Q#2`$ib6of4}z@b#%m`N3MlF%M?n# zO;yGcCKs1iH9R{y+CJ#G$LlMk=6#UEvFvP@qP)CUi2Ox|%?VIvV{>zglrecaY=j;w zl}Y&+@|?TSYVMzuADZF&bHTTb+#juJSjFOAdwq&K_)W^PYJbYXhu#~};pa(MV9x7= z7150)V8-PO+1a1wmUw<6V}Pjfh7b;bhu8nEQo>)`(lmYIRB65bI9h`Q8}}kg z68ng6JIP!)v;zgnj$_@f7qRLg%I0^K4o++}6q3OQZi@iT!9VoKmovQJj_ z;7^)Igj{q84TW(lWope#`?(|bnu?TSWmdnShr0^pq3anlXGu*RoR=ES+(UD;^lC5{ zVachN#pH0+#H~oDsClpueY?A;#-I*kAOdDriKk?}zhODU7!6E^r;(0;QD3(N4hej$ z$U_?#+_sQf_gO2p03=2P_)<;)?Ck*EUQW6!4$ze-uFDF1Lo#*mK$qp z(yp%Dl4ngR22aLO|ut=S_W=ltn1S4O2{m4z&lvs7lAe6G=yY;4w2nl}_ zbeG+{1@V1$&|8EwgQF~W=?*vze>UEedabpgT+n)rTAlQ<$_;L~yK|7?K4p>N*n$Qq zr3W=WqEwy1@FtN zkubXqt1?rPU-w5R2$qw(*^5Bd)wJv{t0{JDLEY5)I(@e^E)KvA-|_pq5L9=5!1XzO zgzvXC7CJQF&B@Y9uUgBWlAhvPyJFZ^BK1&tV`AYRFE4_kiDg|Vdgyv6#J1SG>95R@JdZ&v?ceD?V^(Lm z^FFw>m-hA(S<9twQi%JU_jtQWDavOSLLR3W{^*>09)*V#Hjev;jq!M>51Wif?Guxd z4#R?go^adQAF~-dXd%EcNmy0%58%op$n2|~o^R)GY41{y^Yy;?Uu^u%1p{n5XvE=NUD$t=OIuO3R}8beQ+-cWeXJ>Q<*IqhPVKHN%!WkFt^KdR?_56|ta!t-&*>v5s*F{<>| zenM|D>~Z4yb%_WG?;=9}IOGs@ko}_y100=VYJubvp2@VmW~3XL#DXQEFqUvUdEwp> z+TIo49(ZKc?7^j9I6C;_4*+QNt~7F-HSRRtZ-+h{DR&Gj$nt`mJ?~DIH0vt`hy$_W<|8XFWi&(D3Mk7Ibvt{%81jZ;u*{l@f90o;+h%@5s;*8 zlNQ%W#5RXt%}zdC*QGR=Zyy$9?5CZ(Q3;@B)=`SbBvV|W1UzNw#nZp6Z)`*!IQNtW zh=i|BAV2ZFZ422RB%b5n^3udp0N8JI=r@y{!#ur9+Qh+E)UGcypuwcRy<@jYQ|`yk zEG!N+Oks4Hp@OnU)*CLGyv1q*B>u04?v3@#?*u21oT(N`OG>z?+wCAG{ zxcsw3WgdD99>;Q}-h2m;iSaMb0i?u7US=_edb1^K6Zo41W|9`E9C83(gCM7j9)S-e zSfOf{7x2Z%bS8{kN!MCz#i1TXkvTU#q?okohoU(Xv*nBSFPKx7hK z9L5-6gbfw{OqkMFhaf{l--8b-MnR-Rn2Pv`eEcovob2>KhUQfE_L(*uL%wjByp*eC zh!{EskSMlKH@A~b%Q<%^bdTrD%$947qpXm8AU43JA?%pkH{d{W>t>c#1BHnTRTq#8 zYJj0`MRCTd>L9irll$85D{OztsW11-h0x|9nR=buxK`b;W^okq(sp;2*+b$blqPf$ z;uji;dQ||oRB(PC8W10SZScar5{B9k<#c76rKrMVzHQ&(+94J%t4jK9mJ+-}q%jVj zS1E-#omKWg%q*%3PG3)dS*dU^+l%Js?QU6DT^ZIl3$pqA7=q1zm}|L18NwZk)qSIe zPbX58{CTu0nG^+{FfnyKtOP6Ao>9DN8qOT#$ucJ~(yZfKJ6@R5mrSRNn_WkF@uAjw za$X=lWaujviZNaV07A8^BEQq-)03%myG1AL@B5xL)mxp>b2!emuVuVv^(d2VCwLRD z$GI(M2OIM9Db>`~Iqw$r-(NF-TI!$$0Jl;HH_@9@!4Rp02e4NWOG}cm*yO{rGh#F| zmmnhr-!aJITs)|Sy_+5Ei)Gc=r1=6GA%r$-9;ae$ykPz06gA21vCXf6hh@^E$=OLS z-ry?x9X2<1A>GOTOwiAXGZZ$<^-=SN4Uw#*ijp-Jd|!ba(Hmua4w}#^SbfvbOueck zHKw@iBnFJ%K3k`ZA+U^9Wo{mJ@i8Gh17#Z%l38;0kN$sq=P42bLdH$3|24|=VW{1S zUi(<{(V7j>mS$BHkeGgJ(y+K^rTcVTJCE_{%d*Xi~JJ{d7Nb)q2Yjtg!3)`8*>`ojNv9ZvBfrsQgvP>j%JSA z9xJUajl9=*OaVvBar!40m&4VPr_0~(9|ITfe*CrLtj7&_K8oO8sw%PV;CYkbGQ^=G z`r;!AZ~!7bw|(Y}t+jIxN6i(BrD}in)-B}P`uc2pTD4;Le%MfUoPo>WpMCFY=Ki8F zR>P;jWBdS>4O~7p?9|Wa$=qq-VE(>f(XkJ*`ZdjWq{Scm+P`ID$YC?=%AE9&RyoOVs9zHtQ4u35ZI@O$LPl$f$~i42G(d2VM|fIkcPOu6 znRyHfg%LdtkyB8!9%FqA)P7!II5LUs9I<-2U6|o2Vl~H{%`~{R$+i4TVA1Hk_ccL* z4r@S4$yCf~^A&D!Zt9o9v(c&IxB+m?-2i=kdvA^bT?AdXN-TyvNli)Il=G|+=J}+6 zKc}K15y(k+E?_wuG`F-oTq(B;J?-B$3!TS_D?AJZ8#LUOfmmL?;}!egT7Z=-XGo32oCvPsv(pC4jqw-? z$s|r&N^8QIr6qA~ZSApCS?>iQTidVU$mZ{?cOjL<^d#)69?%wy6ydptmXgemm2D2H z0|yN>QcH=ykAYCx=O@=@RF?#PV~)GcaJJsv7F3?)eYyer-JA@^o-=IH%8?tH0uk!< zd-*B|NVF0%8;$~U5rFdmaa82iSffGr)C+eCe*>&qV<;`{4Uz_^X}q#hjlG1Xe zT(j-JXTMQEMATxuvB-DC_yNcz0|^@mEe%k{hMue?>O1x-CrCwT_~Ko)Fky-`fZb#J=s?3&vnK^C!(=j$VQdv zCSXAd?*0*y9hj4rSFLB&VCJ&2zjx*+u$GxQ@o6)PM+7Fc_WLZx!LDLGwAP`C824{UR z;UyRE?hhxJfo#E|P&Zxr&WPUP=vdW~Q^7*o?y+mMjQ&{|PKtpn0QWg)h*@3iidRTi z*?fe6o|#IP_`=tgNQn>3GeB(nQB%D>r%ZPI_QF=xMj$}LHex?OB;EDZnFS@F>Sc`u zW!a>*tjBoOM8cu48G8kbcFmIMo)=VNjbYNEea(S2PuuOrVbn3NV&{BWyu7u#XzdTj z#?CeqXPQm35ckN>iBfJO+TJmG4`sKlr@^@q9l^=kTHIVNDQu^PgNYaOT|kx9#MPwa zc-%SKnpw&uU1)wHBI_!%swz6qks+_8AjMdr0QrbYNgc7zZ!JyTX)hj!2CKyylK+3 zfu(TXS3`!$B2j|$(e_;~MWak8G9Nt{MbY+vMCjQW5s;!pY$CLt4bk5;$7ziR^xp-$ zgO+d_46Qm}+>I)5@-YmU!Yeb#?{xYj15bNY1e-DfTMc->+J z6{uHQxm+Qn>zZHxQ~=Z3i$;zn#F^TNXW&+PWqQNuTb++pq!~S$HxvR#+ehW3nR*>ou_fcTHtI!nkJ5hV%QVO;w`t4mM!_g7a!O1&PvtbpO;BlAFiOK zC4(;3d>tz5ID_&ISG!>CO_YJuTgMBTpL~|pW~8nLShOg?Lbg2+;M#!mwN4E&aMdPy z@*c%wPp{yU3yuYnJ(mtD)e}g+7C5m@HmE-TH1)5ej2Bc&w8+|Oh%Uk^Wf!6(>+;Fz znj%-JJ=ne{PCwXcKAhHabY%B$lxuKe?nfK2-uomxmFY<(E{`6diyLkdkGo9m?(PoP zcS{@@G>Y?d>E7{Kq+czDqyhz>CfK}8Kb_iC`>3Yr97e;Q`&-i=gcis1^7}iQJgee_ zL(^{viC;tqxbC(Va#Fb(ZsZLoJK!FF6_Kb}Nqj9S%ByHCBf5U>ry?}9B)I3BDUgbW zt9nN&{miyt9?K2HhO7#7BMXcE8;6{*Zy z`4zGO4Tp9lZ%@a6>cZ{?EI)2u`1D9F&O`>YG4e2yig0mPh@EoGUa`}MqfNLQlW8p{ zqOA1^vOwaHR|2Z!+4b8cmhe>6P$Gr>=dd6CnLRgu4h`(2vOh)#KoNBCS+=E1}C@nlK}RT4pp zKP{`gP&6DI9I^?+ZHQW>eyqd|#gq5wi*ze_*5rJhuQQQUuiG2LRS3o)iiG(J4+SKq zP-9H_+V@s$#yd<(rh=fH2%)|LiXQdspHFr3GC@usg0kT!ZtosE0_$gZ!Sv#ZmpxI` zA=7t+eMg3~o2lksh@PxMbEQ9EDt_$!(2;0Ejt>(oqrEpq6dS@}5U6t6&(Tp^nh*_F z^u9xP4HWx79!BG0)6T-@$a!zPv(*5Vem`Mf;6NFCnQ7u-sC_R)Q-&g)!QM}u$QGc3 zXS(9D;_!K7^t6HxjRJiFNU|D0RaQj2=r{!cIoR#f%LacT2CB| zxGaA4h*f(H(G?@4P)v|Ko!pz*8u$lC@BcG;KEV(JbH^!pf1a|oghq(WNSG%gMrCL1;W z79}aFEnwo3mQ}cN|H_b7JYDj*&Mu)Z$)I5K%usL*AmGC5Dh*M7Wl%!H^mNf&owkwh zy#Bq2VnYB%G!;A?b0DY~I6I4#x*u%!NAdCoZx(m)VUGgYSXco5?j)DG(N5eLM|4F^ zV$f=CX4ZRtZABQ!ApsxWg-S@s8dYI(e;|qQQ(A~ERy=X7_9rL)0{D^{FU-fS9d+PW zuQf-xsb-{;t)}DZ!o)RCwlC=r$fS%gLxmy3R+T5*5P<(qq1i!ZZ0dPF!~$R@}@?u((gM ztd{u(sz9%J=R(iC?DBN)Lt*?0^clg z({@;{xBfj{LB8G};}(Oxb~l|Wq@Pj1=QP3p948M%_3Tu#!G#faqOIQSsPkSKA%Y#&PrKE$2dI| z(Q`Yk^r9@OqvTf(6b|E)(#y;&B_6JW( z^}T*_+ngW1L5i}+2L_3Dgv*uriQn*y`NvTT6M#Y^_#XdR=T&hiR+0{XpZ(GaI9uH}EagtDq_jNt7BCUy6ECT%4eXqJF?lv^!uu4du-q@?Dw+c-R7mQeG z&amm?#&!~Q1Z_fVRFn`Z80X~l*kG)GPAYjhWDYh5gK%#)%^|;CRSbj}S)3;aqD{*S z7gkR}EGCD`w;ntg>EIT$nDnkjMGWMSu_E;J>-TvzXY_YVe|_akf7?MaVu1~o`A^2z zk1+rS344z|>Ql|H19mi@QP=zSyLiZ&pf4>#6Ds5{Id1y-;C{ zQB7WRc=3@Ms3DK$+Y7G2RJ-r7v!8tsd(OvTrugoaLq|&7PfEQ6%V;S<)D9N|pi?Ts z!4^<886J#vcy)3W!92ryDxkMisqd})(WDh15p=MKgK~6}^!s40-t0w-ZxpFZuyC%w zxV9ikSrWX1R-zY%i5x@}`)zMX%-=h93?riFD!(qjK_$1&9s@sAJUf|2zSLjrYc|)D z-icqY1xI7sh%yHP%_30Cl~bB8mOhBru34qBIo{&gLsO9cF`}=KybTOi&EB-3qMNXS zO=)GtHb6!(7N?7WtwaVHih(F*SrnC!B?La^d4AnXSXM+{C4QN#%lWZU4wbgzve=gQ zbu1FsAG!tq?>X^A@_l@>2{MvTr6(s{wltDI6yWvGEQ*4gMD^jmsBDN}cG#zY0@Qf| z^1#$>wL6;}&1XCH!zeDu_Yipud35u=eEAEm`|R1-iqDD!0~& zN;T_)iAuQOUB6KKBtpC0^JeUFzuA2VEl7`5yXl3KLF>JuBPq>SJAg3#7R^; zB)uVB%cS94Q-?Dr(s09PQjMGD@Tx~xrp$vb6jzICk*UfK5({-Gm?v3iq$aib3~Mt) zqp=q=W{A*pL29JIyh}wfqXa1c_ghq}Qk9;y9#Lyh}gr~Xx+xOX= zVM$V%X=EPtc#itwpRq6)dz(p@LM*fz1UiDq^VygcCI@q6iJQ12R7H*(_@xTf7AXBw zeC7JTXa&B8cb7D2x)9Mt@;*Wnu+`}!e4p<_%RYg}reWnUw%~-Uo5vT0V>KkB7)kle z8$gR*6fTqZcdp22?gh7@BKP-NwK>Yb;w-&@H#@x~iQq~!~yT--sP@IcQE3uN**G=TjCMPPC})lcPcgqiwH7%oiL#?!tbRMM50 z9y*(vk|5l2Xjo0!FVcZ7qME|{*IfZVhtq)9^h^ARg-O_h;ozyRJ-^Zc!sl>SJf>i7 zQl(Ou5S0|Nn>GROFZz<~sAXgkAv*G=#$`{Ftn|ytkKY2=3b_b)|CPo~Lkq}-{F^g6 zjWE$%UYiocg28oTK`d1$&X;3CNU*$DnRZK0(*JPJl z*yntWCm8PE?4^k+{3-sA(<{+KDc+~EZP|V?H0HMfNjlG?v&%a_2 zCCffFBA%McBweY|xBT;r>S)SHJYmKP+Et|iLa^Uu)R-Zlzb}@!hyCp_i?e2ghXM+l zEYLP74wK4YN0EFj3aSewzFCqbLs?YD8rL{Ztq|>SN;j5A9_P~VLv0A_9!1zFh$l#$ zLp47B(^E4R4H*5qf?Ee-TuCe8Tuj-*hN)ty)E8oO~7ULEuNINDHr(?PFX_t zTAJgRq2EhF4zmK}9D1#n|E-Kdo|l`60ZpP1KAlF-WS(nkUS_f@K~k!vh$-B}qtm^w z(9fkuZ0@Muf$X{Y7^@=uE6KU<#kQlWA-ktlHx%wh$D34;{f(e*YRFG|_6&{aT5_JV z4fY|_1t~vgc~$-(*R>jdm%;g09_<1@DQ|LZvF#&RrBVuym-3P0kvl|wdim-FE7J_K zuBVh&ipO6gAK_njLZV7I{<`H!lnsbhdI(UMfmEZ>=|ZDyF?a_BLvv4u#C)2(L*Oe% zQ`Gmc&c{zxV%rI{rcM-)`M-vXR0i}xQ?C_M>jr!r*4B`T6gBSB&Uj*l*s6*Bum~ z`OX@gwrI}C(H^zQ;O=lmm%&^Sc73}0w89hY)&Tof`yHQC_f;BVU@2rUFk)d> zvHoRk-d5_ma+@dzq|QkF{wTpstt<#dDQ-m=o@i}V1H0FV`@zG3HnAPdM0_9FviJ?C zegSk0+jG+j&lPb7;LWfKze z?LHd&6=+`2ks0}%=`7TvGh~nsRj~^f|FW{0p->_CPU}^^4a;5(p60z$bWG}#4eAs2(e~MGvnjG?%O3FXa>j91txGC zPcqEtI7EjIyM{@k7X|c-IMtnEaO`A*T6|K_4O71+haNc$ou`$RE5UABF11C)zw69ae&}kz3yH7IX&n5`bS5vC zHqZ&PCG+XK7Myg7%R|YKy3=WG+5P(6bRvqT(pRXpQDewOmTH*nBeWU_cWRFCy&{ zAvAGx3F6HRs2Rw=jelb(GCOQx*h)oxSN9_~}7ab&g93HTUn!#Gb- zPi&ogc+61gF$a6~0Ob+YH(@BJmLt_9m8OibijNxqcmVl|f58kH8>;Zvut4tL(N6h* zN4P03)PMo_Y>kfRmOR~A7n7l*HQCHIK$O_oZ+>6Ab*r||iEZ@<`pEl;Pz9t!jX$`v^Gm!;y*@mgT0+!Nmg%37oGOpY z#lL|vjO6-$JRy)MZlqi!DKcab0*EI&>eQ~&TBjKfnZCCTAvSa%VcgYo@8C)z5(7?@ zN+*=VVb|Z%vVvHsu2?H>#xd8R1S`ag?!n!`1JMZjbG8u|rCC?;b0(ik6H+PcUZ!{= zLvU77oG(tdU6IGXheD?x+bGqoeYh!zjY3VhIfv)pVz8vt^yiAbOW9a0tPHXUC3kS8 z(OQ0gCgHy_GE43~nq#n@a>lV97DocgYVzHI;wTc8ZYusqnuughU;B#kgRMZ4ty`)z zAExA?E*2g)fJIxOW#%FHU5XdDe10JmQ~Uas4Ag51Un5px{_{t;NcolbrZYRNg(-FW zYnEOsD?lvjNMo=r#3nqC^EyvC+D?`ITftr;^i?!FJ3%AdoxN5yJ*lV>PL)TWDY)_c zWb9YGI|R7dp|sj=*HLbCe=4>!Etls0B`Wk{)vw=h?D4kb!Y3#~lfWn?S1g;pwvHp_ zTpA4pF`}`+)ztJWj+PS{^PlltpiARVx^n>V+OOo@0Lp)UGhrdZ#PrUy{SGtVB#j-? zDWk|>_>sD40A&|5k5cyHOcH}#T0?N-@T?=6rriqu0FGXckL9?<BKFi!>$_EXFC_41i24=anIJ&|*atciTG)qj-q@mE zsGvgS0~azj4R$C{)_oJS*Jfwl#U^h`5XxF#Yp1pnT(Qhh4sPdCnWM#1i@>hi6Mu@T{bP`kP6}V?j^O(0feVWB8GY#>8Dx!Wp2T`RKF zQD5hdrv!(~s}%ga0MjPR5pE@*SK8QB%ZHGQU$k;v-68KL;azJjIFRCKESE^6xdp+@ z;|fy7k_AB_&NChS%i(+*MY?|lSLBqHdE+rhZ12Y6*Z}pi*MV@ao%3nxOO3{uZ+3kL zi}fwB78NPCYo}NUq8F1uS7`4F^b}CeQ8U5E{jY6&T&CV~Lq<|^M3-v4w63vSl5iki zK#&HMF)@lZz^MOG9HZc&Oi?u;d{Z-Da8mPkb@D=5YM&!JGJy$|9z1`wIV65rE4r`p z9YHbq*6xvw+!=;j3yA77kuth#K+=z{ zrobV4v6Y1QjxnIpC5iirtLAtf4fe@>*s8OAE^6Pk#t6d);fel(P}|rpH%4_A7GVK_ zCR0^YUKoiC4U}TeA;T?Mo?y=*DDc#3@m0<28lP&^IT%jGg_ApdDJX7&L$3=Tu-(@@JI)QIYs>s&ief z@!TDVswQeH3<*;M{n!mJ{Xnai^V+f2K%~dbmwNLR5&Fa)d(HNzwevG~=mi1Vbsy5q zhJq+;&jrDd*u+?4zX3NoSJ`(refndLI`w;bVq0`n7Z7g*gIj`KU zZ4&8G>ud($YZb?0x0I{b==Gv!u6}}isWOoN=j%`$@BDC zREkY|)mj4xQ?5L>x>!+}%=gU<5B77Uy=2@pPP5GTu`n(^M$CxyO9$C|PwuPQY{b}k zzBi<vUfa411+fUjduTzk9X#jwR!2>*SunMW_qo8bj%58cWHJc-j{#T9!lg8ES;HEb7$Wio-mM#9tMFf?|0g)8cQ68GcB^5JYY85mG7?h z?(Xi3g$@JO5$^QK;7$-@zQstyK|Ln)an+g9qAat6@83?jAVJ?Rpw?4M45Sq{@Kn18 z`;t_cJtm7zslWR^5m{wPg7hb2@s?tF+hGnPnBm`Y_gGSpJNTuny|m(e zx@jW)Kr{E@nPE?J-KzatMbh;5zy38^k=JiE;Qh-4#oMKG$1)JemFsDgSC*y5O9&cL z7)?${>N9S5lXA}GdP*&MGWch%z4SH?XFIAFWn0|ao30DdG+uf! zsvsIY#)=!;ee{SjU84o9`O-2{^B}8D&J@MDy0O)XLXa}sM?oJ_qvM<*@(7e|%Cf*d z!R)AA>)j&OVqD`WXW>%)I7N7bZi+iYPRW%>P`@?Q&$Z1<9Fi?d3;gZj(28xG^2P|z z2db(@PsVBJAHgY>50uB}ErD=aE?&ML1+b43$OJ{!u@MVWZ4lYloxU`zIS?FVc=AR4 zPHBI8IX?gxLw6FUtq;b}O=qp2>nL@S8M;@A^7Vt>QA}k1qt8oyZ7?#LGyV+SIjun* zE{v?WuXFx6qN~Xp2NcNx^x|0Pr{wZdiY`$25A`{{wuA}IGY<@8( z7Km`s=yM6)9b6O(ca!@OtMWo#6MCUY`{|j~=%wA=2}%2DD)Bxs!!0mARH@o+ipeEJ z(`j6PP0*2f7#Babui0QQD!ReH@4ep!DCPzMn?YO-bPmk>IBs@A!otBV0lDVjj?OU< z?Jm4n3}ZlFC=T;t3u>3QSsW3CimA5!Cv0qdiwOO*y;_m>)gh3+OA>NR;8G=bsOiZ2 zo)3`PG!f?H=J;8$fm9&hGCJ&N%qPK@ImnF3w~4fl!QW1uA=nx1*Bu$jfj~ zBC?gs#eBKeDK*GqM)l@?h3=cpv}7fz!yt^!3Zc$-OSuvhQ@(DaSr zb+m2QO&Z&_ZQE?L)5b{~+qP{qM&qPGW7{?x+qS>y{k&g}BmZa5-gC{l*151x5`N*t z_)v^eX-F_&FA(lkpTeD{le%u8svw!cRp#`oJWyjtKD^3YL!u$sc^ooOWC;ed$ST>ti3`s?Ge9=97n#!$&>026+|R7F zrgU_i!88y_!pneK4kXBI1H!Mb-#M83CEDQ*jDDZyn2N&B@;8#O+Cqiq`;&yI^H}$T zk+Rraw^eO1xY+VRTM4=I{!FMskqAS`@3wJdkdje?|Ic*uf8bIo53p~H#0Eg0VI5x-bEJ{cYK0jyIk z0*HGraa@TWX|%v&RaF!knyi-sV$yQ4WX@mh2v74fs>2))-T3N5EyfcJb=w9sJKy4_M&=;ra56 zdt_d)QlM=M*8%*-gEkw4>iHsFUdl2|ZW0;xt}>qrne)I5LrK=8^Ypj#+&>%Jw8Hqh z%;bz|=fhQ+vkW{@tY+532XLuPF=*i7V&N3*L$F5-72QSn z^~1A+yAb8;*?KJzh2^tsMngj@)vT|;jdpped@T1wq?-x87$A-o)pR4 z;kGad%)gC3L<9s@3KNB(aEE3AMt62CW!zXCA)blNO_I-_KhqQzrK6azG1k3}-v|I| zfa&3cF&Cxa2wSI5cKa)_p zAqcqc+B|jW$@G;?0skTk0FRDbAsV)h9heee`wh^|NJ;=~ZL>Nq%f^n+ucmfW&1@4aC&2?m2cLYW-I;ZVjmZW;358k zE9Xg*B<*hvd|dZNQi;EVD@JkonB|bn_nKe72NC~VYO&`2{BurmLsdJctp=>!JDzZ3j9tbrWncU^qY+|wx`Rj{X$ycDeiJejeEh3GSjkhuzeq} z3|Hi|nA)fhYi!$d$bLB>9{=)}%Di1+U=LewV&VMk_U?|dhS*%YG4JrNjqms|XV%#c z#6gZD)5goaE0X*7ocFTMu_5jM-IzHG&VRuZV3&X|K5@miGt1WJCWEnuorh;AntOd| zhL!)AaMOMF=84z4VcP7UDc>H*FL5T=P~88EU8AF%7k9DMHAPdV{%1cyo=KDeEs^TB zp?;Oku#kwB67X*X*5e85e0=u$oNZ0SocP1K9Y?(-Fmci&u2J_}FSdeIswAkBV3(2L zWNW&NyEprm5j}AI=WTw#!JcR~!Yx3fIF1DN0!}u35I}CYRs+YHK`cbT7t! zV?-${xK;0^@_YJ?E+Gz))E@Yk)Ng7-|Gk%^08Ehq7{Uh$F8~Q4swAp>GJ`(gQW?Cx zv#fv(@Jm$WGgnlDQQGTK^7&{D~#q<)z;U=k~;9M>($y5dnwqJ5cHx)F|>0TqK z9)d7<*>LqO=xn9&AL?_`Xqn)@5CBBS1MhDyu}>#}dlR_Q=JD1E)IocBXW^7luq-B{ zl;m$tiIhFNb9V(=72C4&Mj;y0$&l0x5jaZLhCl4^`+FSWXN0H?j8zdO{0c_=7!6*E zzWY}=;mrTiDg$2a4V@HmN-~nO+xIox^VK35YXLfu_1-w`P}VQgyHjF$v(;Nk1*@|P zfY>ZBFtEaYY1(F7^pFZ!AhM5)RR+P|)hOsUsMLsFF9d-aMh`%b7v2}1fCVaN6L8@h zOlAuLqPpuRft-)mgV@L!F?}2m-MFCk7*1e}c%~zS=+ubYiH0rpNpnN1!dahzN`;!r zoAoYY=+9r7y|{rrxorx!$ToR##JYaQE2yT5=X)tTVq(%g#!hWu|quJ<9D=3(s8@t~pu`+?F zPiqSn=ZQqaqvY&B{zBAy)AjhsgZtn4Y9Gpi@bu3B7J*5x(O`vJuNAG3!3Egch|}T+ zDD2s91P6FSK^D(_A+q(=rV;M&y)2Y4l{@5$9N2`PC1j?%s_pFAsMCv!*OD<{fanfW zq;DC{3fxWTe~}yPm>iD;q@?fx|FvWmOCp*-sMzLrJ&)sHGj6t@(U>ePUk?^=5x^Xy zj_Go~IvhjDAJZA!79BM1{E*eRgmZky*KdGYakskEQ@{x*pdj*$c~L+GYS7{LWJzYf z+UVSw#TZcB9jV>oVpy>%khw=ivMSuV7tN=%wy3@fe`HO(FF zD;Q<)g;xm$eA0K=bim<|!i`eM{=8U;a7w{WwIU~)oSX1(FpR;cDdagSNL;G7#Qx9H zwQer_00e*#y+j5r>^kt4*Q?(BH6gLdE{0)6Kt`TjR3}>{o>2U2lKKj4{d;6B?)V09 z;Cy11%*thsc?Q^|NjrnH{o98iNnpYKLmRt9Gq*7>e7%kKL__rL>9fEZ&21d5?GQP+>Ek? z+Pr~WnJ7exV}qvIh2*CHZYQKfv(A19zRjj%&I^HQjyIRB@5@T@+p6u`g6%ke;E7qU z)=GqK2C}>J1S=bxSmjV|ne3R|u?|zHgz}$xm`{@rVbKGyVLv5Hpw>PIs47~dBnVC) z&i3@+Ag&A`-I%#!46U$stzjKU>atGrN>D(IDG-L*6DX0Nu$TIT+@w~m$`*{150TFP zElr0xSl-nKJCURA33Ugj{d}T2y}@>Hr@?2uo!m zJx(TGj#Eqi;=(n=*A8>4&-5Z@iLXD9F1~L%i0DA8-r1c#&<>#=D^^7ua28)f=u+ zVE;GH*a!WaTHD^vY!4E1UMc1+e)3EWwXWyL_Bqi4+ao^wws^?K`=jn^Fp6wETpp_ zsK}9eixwC<1c&{oHrZJ?SiW3>p^~vP7b8RWhO?a;f0%<9OVE4>vzRU;GQ%0{-Oz57<9bG4W#;^y84UP|rWEm4Rwk z89DNR(ngzhD1~ulko$5e-?iF)l%;*`^Xmg~?B$xSkbQH`-9fZN_ir?H=FwWS1j_B7 zU1g-%qlEqs9NrROk`I|A8ycr2iainLf`) z!1Q_pxY!A>?d=x+@;1vKaF+N++_ zwzX7mm~^%h!n?=Ge$KMrG%WKnGHev#g3e6Mso2-FuS&w8i8}Ki1Q0U>_&go=y`TSv zOdP`|AeiX!d9iZ>aMQ$Q^lQuU2Fks>lCBn*`=(pKgLqyCz#^j8+CE}Gpo85I49P~! zRvpBxy804$k|6ZZia&2g8MY?BP8LH5dv#yu=7fHCmlwA6x)I&{Np7RC6&$QFp=X}4 zbPGavY^_n3FFNX3EB>TC&|FQ<6Pj6Yw;;8h*%Myi5r~OJZ}n`?&6*53giHMT~z%J^#5?U^lZ~co(y8y!8o|Y$lTvnT9GnR{(51hFn|LQt+U|AXEj0=dK!I@H@nW;^=lkT+l13y9q3dWbeQwRNN?b>~f`J49YV0YA$FUF2UoZp; zITW!U*-MRNi1tfOo4Xy&cp+7axm)2k9P# zcL3Fy4Ete_Vkt?K`7q;&!L_I`afcIb(t!*uihUE1xcE`lSRcN+{t zPfdcJLCdGmfrha#gBkE66HSpd3O+V_h1p20kRsF7&Wb0Z85~+}t32M0A__ z0wo4CoL<<#`)jk(YN}>aQ|bvONomNEC)K{X3<>^eY(IE(b42(NO%60kr!bea6Tmwi zDVM|GA4--Yg~4}pcy>{!QI{%}ZyiWuLTxktw}{0MAp-@x5M!@{Z9%KNUbdN|LOa=w z+JxHGn>`6??7tI;Z44cF(npgJkdPdJ>9-CmSnb}_QC0b&4Z85iMJbB$c3NOuN64UIzp>V>K1sq z6McKsYQIj)voICAg@E8Wh=ek*MbVh>W4ep%o_LA-4E%Re&z9C8j-}m%eub!9aCM0P zI~Wo{>QLrS&^B#s0s;+AAa$AYu~hg^8uV?=#ovOOr7^P`E0dRpb}tc`_i1Lv@g6h1 zDMFB)ske@&sNWFyxNc?qaeYh%4tiO%-(~yYExF0dJW*h-$)tHFb~J^bJXfSd~WT%U0{i@cEC{@_BvPx8e|N9gGO>!g^{Gi`%VQueShD^$~IyC4Yfdf&AU|ZN$hWX zbT*Gb2xEqPHhKX0aTzSU_q_4K!H+CO@}O);FUb$(0RN4sz~6w_R|&@~%0k<%px~kY zz~Sw?SIEZm?nUqYnJSj@iRY3I(uq3{sU2uL-3Ui{;c}Syl{}|q>j7t(Tl7Dw;U^uK8|K7S+A={H7Okeoe|Gg~+o2#RX_-%_=v3G$= z$kL#<2%CG0?fO4Hnr$o!ND*~V%!%AdeS3LO`m%6Tuv*ulX{o(C&Kp__XGxh){w;vy zhuMb)wYydX`Pnhg!_eCAU%_D}C$X zeoVpl38E~T#-Vm0MG7So`Gs19bjOk_XG53rDm>BZ=jj01FrI9E>Srz^o|2^FbM7v{ zCop2ozQ(WZch)tN)Mjm%XW$RSIBv+7S_)nsa!f|24TvqcKs)%=PCOY$eB6J!{Gq*5 za*KKhc1*BjxqR6N^(C`~tpe<4+~Co$cE-xrI0_n$w-H4uS$OLptKNm;f3d-b=jQ`B zfhlMoF4xH#QD;d!%~HVfHjBT>^X<8GXFg(~npz__;orEFtgk;EWo^!ZcO+)31yOnA zGP>trhF;Gon4rTX4NRDYAlM#Rc;k%jFH)@C@2Gey52F2kj7})I$@StgNmWa8#>SGf zp!iCtny1#Zum#&{;mDuK*T2c`jzujz4NggeHYhiTKjG8R>9HP!x6o>?B51^qPab1dYskMWfhj zQJ}#fUe#c-xygPmOyXGw@o&smspSU5S$u6A=_kshJ!)z`x{uz{;! zmiWV}Opq9ei$b~O7M3Xws1te9Loa#T5%26^ikaFk=zBvhOn*P!x5m~TDIHzBJS0cH zK82rT)B2N_)kTs}MP;o2X|<{A%qcb9Mmy(}!czh5l_{{#B1PX{W=M=p32dY3f+=sx zGNWbx!0(%0!m_8^cjl>T5uW}n$Y9MVvp8mmC~gU8?2MU813D$k82f|2j5di-)fu~V zn3@&)|2-#LsZ_SJTAKts^i>ner7>H3B28bx)r$-+M?}{}tq%VROEg{aUK>Iys|0H) z{$g-r&8&8!&3TFL+`Rp1iVEr_aX0&zE%$sz>bU9kEz~O05853ALSUJF-;kTD?D$yA z+&+RyJqKMKKO7%!)`-2sqxrc}f2Wq1l^h7X)}tZ8tqQT{f9o#1rl3QS(T}ZpJz<^O zkmi`0XQ*s#Ce@S4PYJYC5{~tJ5KKZ#eoCa7R3+KP7(LgCo#z{P5HO+dT9{o+{3>dm zCG3pJFOD;Rctpp}(E;~Ga5(529(}&KGep*97Zl71d7hq?A>{&Be+wm#a8{2giLC|| z8vQAUFR)Ghw|7JVwgVpRimtA%WdS{-u)tEqfnX5NJrOiv<}P1Xk|0^2_lN4n^QEYr z7D25RCG@e^6PDf+W?Lo6dQ;o~09?(9ajTWvCoyDkb7gXEq~nbrbt3-aZ10)gCy@WV z6?>6|hxDo5Qn>FZSoLw_^TvL%Z);=;+RfJYH_l4WUVdEM^`ZNVi-N7+IH;-*7GFQh zOD>u?LvzYTvW8!tc##In)b;s!wEIZZD|*Nn0es-I3g}HjfRwcL$K+tQGDflYa{#`Cpdu87P(>!XGV^?9Rx#Z!0zkGOTT1tFw zn_VF4+tkj^4k>au7b^{xu*#rRru245tLOh~0lxB$1#Au?rJr3DSu5YD3Q}8E&**;> zyV5}q@@SdBdfQ+ONT)jI(RH(opqQsLODIzpL7L zjjvPv%=UhvE!_LC@DY?PqC)fNwQuP=^qU>N?V#eq_~p^Y<}KE@e*kcs8={C#)`$iW zrXSl;YNiI6$)>~nDsIKk$KY?U`4_fO9go^4@Z6|DvzfGAN}c^ngGqHUU1oC9LUrT- zHa~2=L+BoHoLRdXRnlqCVb%TREYop8Q{Z)o+>w)u(qj<3#k0j-e|A7qSU^Z0H-WiA>=HW5fe?(LN#?pW9*oFT1<`D14o~Ssi*56*(JtROaK!Fm=u(W|o?3yMU-| z4=*mo(QnDGzTG>$a{{uP6g8;%g~q;reyHXK5aCh(A;ka-fZ~x>s?I3fGZrAU+0=Gj zgMt*%%KIdR2fciINeDyy{0!CP^_13DbpY%0dMMZ=niGGlCnYA*GyZWU{N%g-2Fc{} zCb{bH@_??UESuf+-3JIhyGDM$kBc?b_;WKOuRV<2f>cJE=aFTa;~tXj?_W^9C@}m7 zB>>O``23BUZr=WQKHCr~-^pINI(u_i2CMC((k_CqH5*^$p|Lakf9+q+PF9Z}6+Eiv;&t zSg6F7*&4XQe!mlR&{8dM6{50hte4-g#CPRY*$YSaQ>!Gfx~+x3U=UL$;((SDaH|ss zI*xEY{zv_@N^g=(^YVjs9e+0D+#nz#+5@Tfgq?i~OKQ>6^ym0_C75E`Z7l=vhS&9} z@t{86cDp3(NFlw!5IokTzHtirp{k1AYeWpno zKSq&ZEip?Zj<%0TLhep-#5?)(zB^Ih_1>VdlGr9MlnW)zb*bf!^j0ExWjD{PwMhy*=2cyQ*zcyJ@&_VK3U za8wxewd}^1$~#uq^L|E{h2?C+u}~oAX|!{9!{_ny!diz-j}v#e+ua^?NvJbNi};N~ z1LGyTYzohTa!2g^4f0Y-_8{TO!d1(S-sHeRc80~uwLsFrv@YP(Wy&vzQ?v<=01?FPdHQR+AaG3i>U=?I^!E zQ(A1i#F>W`rwCoT;F%NR2Tl(kY*&dk@km$t@+2ra5`fnApty z@BP|DYB!Fp%)_F{bbrZ!VQ^^}7$Zh!gYeB-dCXG=nLhIUfr^haU>OoIPVIi%Mx14tdxuenx6AFVNq#~MiB?@wIfD26|7q9 zb7m}czP|XSx3s(PeuZUa5G{&dPbh4FlWz`>vpjOPrPLaNsl8E5NJ+3kUZ$Q+v5=38+@1JOlp_0ZGRwj*9awD3q|eNhwq(wcqqGvdr3znwAs3#LZ$zIk zHK|RLL#&UZEL~2&ii|$?eChPTWa&55%4Q6}d%WPEDbB7b|Dvl6_>A?XDTUq%kWuy|=l}Ib6S*} zV;xGZT7MzmQw3Bo5Tbeqz@na-D`k?3heWj*^Kiu-3N_;nXV`6OPW?^5Cc5gT)0>c% zW}V@sz&kP!z|U>RoA(L~gG$E1)(E;S;rwleP^g1SDH!DJHX(1|p(7U?1!X=pGo?!R zD>GDul>MPI1IDx0JJe9hm^}7I-GfY>&mo2fOF=qd}A?b!{9;*Np zBr$pMTCkro%^#86uRXNVy9VXesqyhxy2)yrQTjfmdGHnRhY7_BW++fb)_GGwsmd0X zO8EBXb^ex-9&x)g7+v9X^FX{udpixojAC5FzMZ9aZEAzoKRO41d|Mi%_Ol5oUsbxz z<@G$iHk^s?I-9WVI$JEFZWuMAhPTgPb{M=jm12XMAWz*&vK*l?viPkL+Zvcwyg1lYbSgIl9Vq!u$lq2G{kz z!~s;E=BdPz5&FzG7c*|fc2=oQ8d5?xCftL>VeK2Q@k8(o-0gqMW9^Wg@a6ax1xdDH z`DqjL8W{%vgfN2Cw*Fu&a>Rw&KBj`!#>i>6)Q$J~k3`~+Jth16e3%_&&{h%!Te_D} zEQ!hFA(LL?^A|M)27O!OsH%moIed&!-L{>XvQL8@?Vi+XRPY*+D0;{+ zL}3Iy?$TDGJgbtYO=O6JC}aMt8mwJ*W}``d3FW;fkNl$* zOppj+cja$coAfKMZR6s~n>Eb+Ss^45y4RnUkLHC^&iz`)HQ2NDsU@bkHuL6vWr~zq z-gH;P#RehoKVB-wlXS3^(EE%;1Lto!% zBKY(|kGZGovN>NS71~TXvW;}i@~5@(>$|o5Ie#dCG$~^^?8)7al*!98sVX?d0Z|7z z+vo~w052uc7elag+I6msPzf8J8rR&^z3^+`QfS*o z1-m=GuP?8!3bBqSAD-+V9v_`oQ*X%E&x<+IrJa4w2fBk3y8P~TAA+x&)WZ9|zKbtU z!J|I$s%qK2^2k<3vZb-YZj%?RyCIJ|zVC+j>e5#c?qurL@hla05?-+=kC4)BmK_#UxX4Oc5YFvYXO$|4P`-y9S03>}~H>;Sda#H?7Whljja`lYgJVU4~29}I_ zP1m9#UXco7lCQw7FIA0M@(#t4v6{1DJRL%SmNqE>_u*=SH;<(!eQX3c#iiES##X_y zfyGYW!<)F)Y&xEcX{NoDCHSAObO2k~z+jOGtJAz1j7SbhSe`1;R4}%8^i?6fk07!G zK?pLkx$S*1jxd%}ys?YEry!pLa^f9pY@PA?6rDk~WE9ZB%A{`wfJev#`!zHUK6gIo zuU}O@T;5=}Mhsec!&j8LjL@EkWg)CZI`*`7JD|K+nUnwYXRdU;RMN z;I@H;)@xL9RpNfqi3y76N%%I(V}2%U1w^R4@jv7VNRq1mn7-~m{ac$+r8d}`ufedH znGY)U`$EtkES4=r-p4CV3j^rU`yaer&es)4Q}T<8p$`aN9?m~+P^U}+L8SFsj7Mc8 zB<1sV192zEax(UX0q6n0;$WtZJqSVWaZ>rBsaii;A|wX}1}+6-*FoVrC{>nbVUBt? zc%O(oUhSbqjh~-i-1S$u7MQ*(7J^7;_$thK(VNq&)?v7769$3yt!4hYSg$y7ZG}x} z2`Z^YLk~}ns)cQcAeAssgjT*{9DE!VsaC%i80jw^Ce?~>xQE0>EArZ!wH}Pxjn@0S zKZ|Q@A5bFIGe_*4QO*WnJ%7x>vj_^NMu1I2vLO%n*APS=LXNF4w^GM((@_JgNA}s{`S9c@!(uqYh?jtu$76NS6##zmjR=2m3X*_4 z-!mPjX-Pn;_^-bw%`|&C{a3WC#20FGVN!rSE0ZKxY7aFoQ_l|o2ePyzJO{NGf`$Tr ze-Y{|r=!8;%~g%l`dxQ`Yp_PCN4yl7APCqu2bRMI!vB`E}P)2o&L0Q7fWhl@ge!d^$CR%=c1?B5-!7PNp; zx5~Z+ApX=e*&DM>*twExle?>h1#OK(ZM+z7}#pH&~wB#F0FnXF7F0w(d`=LU8 z8{S*+I^K8lS${isy;0hDHud>H_Z`Ur8jBov7CXoO(lZ&ok;mGx9Eh8L8VKz^ z?#nOb8&#(q$SG>7?BAncI4`mr&%h;E5U4UFk6b9A^~WxTf&6n7cDLtM&dzl%z%uPs~!IFSnB5 z8xGJFk*9@RY~33BJ~3$Wvr)U&0r8($z}Dj?gO6cnfp2?=O+df_BKe78a3{cV!sL$l z4V8DJxWp?$E;p&mLYg2nmB(tFUanM4Q<(n8$CMsWI%WZg)f|T|_|4Do8m!l{6{h~j z2RnuBxJ@{#a$7C^*LViBz0HuBiK0-XD<$d_i^0I@iKsw+4UyJpcBIDEb;9i>Cr?u5 zfsm3hP(q=Tr9I6fU@|Tp_g{kFCMm($3Jm{sj_{kr?P`;Y%jbajs!(hINeK#u8cp;& zSUfupdad<^377SHpR3J_5VMZ~J@@;0&Q+=JJHms&^QQsPD2kpRJ5YUD+xXa-&Wzy5 zUl4Y%bnP)B-NE6-Xaj!rO?K|+%FrHNe1{O*p3xxMK)(D6c6rsdia~GMu2=l^*rt7v zqGzpLP+~#9H|~F1jH2a%W&$8~(i9Lns_HCM_|zpkSYVnreAF4W)4H6z&=ek?ALWAn z%&x4A{u62wT!z3i7qAJ%G$c#Pr=UTA2?`3R7=+aZktqyR--jIk9u=dWgxvs|X=wWVOs5I+Aj$E>d~H^0XZweB z1p*Q%ei#)nx|dw~6bKX8gs2@^f=bMKYVSB9DrTBW8V43DmblUBPAgbm3jcWq?Ygi` zc0g-fU2YmjO0N>H|4`|ze4a!g$}5A z48k2+$?YL*JX;!IY+UWhk5x<}fe-K_Q73@`|L3{bO*3y@yid>@Kb=bM8!R{{A`EtZ zSev|=6o~wT!HeM^CqsHAkq34#411!96H<}NPruo6;uGIIAmFndqI8+#^Ws_q%l9A7 zO7#)71K!3HN42BLjE4OYn1ltQmTM{pHGw!YKVH^J7Z|>G`+tV+QqQ7~`$P|+1`B?m z3J>SCjoKZtm;7NX`ID8okCr+9%C596gpGtSe%7s{Pb;zMI7p(fs8+^oyRDb`MPkLU z{cG4un!M?7cP~>{%J4Zm2|~vXP{uovp^wzHIAVv`1~Aed1UVf5W@x? z=$mMCbH+_lZyfa45n*BavxdaSqh24mGU>J?Dg1=%J#r*1ELdHO{(5Zeam)unsdd4H zhbY%?us!3#KKx2`NSAW?Io99*U4mmE5$5~V(vTw2Mr|;5$7`dLt{V}wD-i@E30XYE zFlJ&9X4VLGW3j|`0tkvMMK2wkUb&&W#Ca~{URk3)v-_M$@k}p@=+9Kj#lXqD2K)$} z2mIQj`%;ZPIwaXMobF?OuTyJWw#H1Y8*5FO$n;7704W=F72WtK4 z;i1`}$(WWrh9vMMQEd-TIM;Q=|4{8QHYc}5B1_rF)(9gn`6d!USFeSb&zTL$Z0yq! z)TIvL05uMe1no{RHs_6Ws4xck?yPT6D+K)g#L9$B88*1{>YR(BY&~V*rk9RG-O&w zRR-J88b2;<+BS>h4BEBF9!K~IqDy>q*Gdr<<5YK0%-NB*kM;Nc$6bU z5Pv1<;`JPIqb|gdqf08jRFTc_jQFgNS?}cM59qxQvQc&1KRLlv2bcK6z`;<9Y7{tX z7*;FRQHDfFpa<5ShU8cg9NUL|3y5dR`L>6qyVg!aeS~yf@@we*O&mj9-nUXBUuE#y zv0r{G$EW%*k!KlZk)I=BV&Puhxeo}Wj^V8v$O)djpn%o#YJn4tEDomV!#QKi1nxNr zsYj;}9crrEm72pk1YAAcp#m{QI}X>E3Z$I=I}=H?&kxUmLbe2OG%(1fI}JEI(<+xM zg$~3DjY{l^vv78xO_U*$(Ayi0EyJ0eoA_VXb3}R6h9xY8ZAOi!lBD(v&X>xp(W3`@ zc9aw5zhvC!>S_v-RMmijD%S8&q|cRyB-E9yk)NgX6;ScebXa9Axv$3lmko~6=d>xx}FD-2#TUn%IYkRoS&NM!;Wx!6djk!Zm{1m zWL>Pvw1!ZB{VD#}M`)C&_>X`X)eh;Uwg$-oon3bxg4O5W^85Ja!od8+x0PiMNTbN@ zur3dy9Wz%~blkFZ@fD z4l&J81T^?8d#ZXn%Cw5qFdZdkeP@Pnz|vSFs*IG`EfaYs5<&2!<9DJRWsg?SB@?QK zMh>aS6NO<$sjbFPL7^pUL=mr+FpCyQaZTk?sc=x;O=-M=gbvW|-BGfXAh|pmG8ph4 z7Tf?aDoXiTv`#`y)XdMt)NCr+7x6}lzygY5Vyn`LbB+O4dJp>3f&>HTz%xXffkp*JfbIQKM%5!3s%=YdcT6J(=wWUa3 zs1-4PG(5#MZ7DeKx2Qg*>Kgqa`Ko9beTD6idyKz{8bGZLOhIoVlp*I=3m)OnIH;fS zRTFs?{mU?C2PJMqElC4q?%?#!*H*Ms*-~^`jaoBWRuCGk{I2$~&lm`O_M4;C zUGAJ>RC!lKZw?yzQ-}msQj~DYz_yp@BINU5A05a0$-hVz6a_^$n5?-2gvCM8@lk^?4h`!!Wh4 zdAusUWI=XC&Yd@V{?3oMQzPg_vUq5S# zHuI7OX3EfzLANeU$?KH+XdKgSqQ?0G#Z~B?V$xWZ+tPtD2_;H` z)K%b`2H(ZTWEF?Z6xDxHW)n5JC4BInJeUN;L~5Bpfhz@57uVO53wbn&2JJSW%t0E4 zLM`rPdKJ}GJA7koFLQBO+^56Do&TdD3C$3wN<_7s!Yhz#ueaEb?!`cpLd#eJnny1$ zFC~5^B*Wa@7L4*mI^vT4rA^WyE393L4tc9`E?> z6SD!LQux^_fvxcuwGxPAW!_8~5LSXlK8LX~NB`f|J~|AkLyxd%vRlz9Ve2sqXG4hTo_dP!hS{v1LGTw=>>%z*;IIEQYlBL@@b}%GTD{Tv3m@ng=_et5oFe?UATwC%I7PO22-DddNIsNI?IY9RJDNwx zY(B|LtGYnbK^L((krODbfpEDFv~WHD`tW;y*>$XVvDlC$Qt?}GSbIxm0pVT{PgZ~K z)1R&e1OJ$xEIvwMYEp+Elj|AK!}e>g**On-M9#N}95yIz5+Dv_6#>b}BpOh|o;Z3B z0oZ-;eX^erf3==ej=7!^yMO(b-!hHJ0TbZ05ffEjIWjV`)9$tLz~9l?-`~IJ+geZZ zMbTr%uLzR6G3zb#J1MEe9KFCTP(jV?^NyHk z)pc4&PeF1pw&JFp00;l%+Q;qS)5z8ZdB=-$dV}~ZhbD)0dW$HXDmdmAOPCGuEA9E< z|JMSbMVl2L8mAa1F?~{CYyWeD7-#4u-J8t46~bt%ncC1aw$Q`^Q?gIi4j>utwZ@em zb|wNE{3%%imlGsC-sUhAXNmc$dOTE_$Mg3<0oQ%Z=WpS&36dGtrOe*JF#e}hudAP5 z+^(!5eIKqqBvhFLt1<;&$znTi!8>23&Q@Dq@QBnSZ#`&1K?HmrIMv?VOs=6OXKD%e z9ABR^c|8+mO($lwv`nh2`{LDO4C)HiN@vU{C`!eo0`M_CZBzKLTqYb?vo+96e$losi1f#<>Y3sg!HEV4h}Z=|>m&hPe%AL`C4 z^25^ohzl7RS!rE>$pt0JVJ*3FK4K~-wo6p)AWlb#LC=0gQpgB_IJgdN3uDAMFzz^H zEW_H0U$D2A-E}%X9X%RS@dPT;B+YoXAdN#7T;XO6mrr^HSKo4XfopW2uVf(0#M~M` z$uebvBx~h%YzDr%4FgZ#7NYT}DpCCd*fQ9d>s4M&yr+6yd?`_!QAO zlI#((t6++1lEfr)$QNXnBZWB_V+gT4H2|Y2e!+2VaPQ32}sgl ze8U8vu=y-pjM1keFqJS`;nE9HwVQ45^<#;3itQFSq<6;+(T7$%441ep-05Y4Q4(w# z;T4UL-}@@69KxrIy@V)6#byze1!Y$w({y5*2doU^iRLZFm`bN9QoE*R5My$9hZprU z{IDJASAy|+`YIw@z#&DQPaT6+2TT8Z-^CsE*j_~ z{VSRp6S?BVlaiJjR149N5-(w@LZLygGVv<}Mu`@a?D|OLli*_Krlm5`PYitX{lb&E zePp8(sJtbUz`-iDp1{lTx!&GH?$~jEkcBw=_MO&x5K54!j8Y-b0h?L8n#U?9XOF^HSAkt6OBPTacqkmd|9|1 zQ{#ZO(U9{_$QJ!9S%cK3M=4TIL_Pn9Pw0cNiHScPxWso7x`YBIQn+UK1A@vOkp;)^ zM9mE0pRz95FWzXeAlsVkl1ehj%KIgd4iMToa#zf2DSyvc*0Zt&kr>T4Pbe1^(j(98 zNpw`r$t5MqoP;>76W|3#Ve++CEe|YM-+Z-XRpbkTOp*W7RF7ASU!N*lV*!sXR#)Ru z61;TT{J^-A=76?qRb;*(H_4m( zCmsl#!ejqgz$*nxrAaILcl&V*oggH5yi13aP`gy;KyY1>X~TVzy$u*awFer@Y?=nP z?yY^dsF@V3F~L4LYH+NmZzN-Zjc{0I-4=BAVQ5oFT(UEIQiUMsH6iog=EqFVgtOMv z%ELAAw@ddwA-53c##WmVOc1Y2tD9hw{x)TXpaiUJctESxvh!HO2oP0th&(`{tg_MRkb1h_7+%Du80^;24zqge%R=4ww<{d8AcMI^hwLg*)> z!r_4rW52(%Y9ZOIHbYYqGBx}X`DXEDa{OxcVtG*ZK!yyGZq%e<0cl$}i$1WmM`Wv9 zKa;ZAE^X9cm^~SO`zuNd{1L6AcB8|~S>r(>-d%_XmAjjp+x2}2rOnAWPjsFcg|@Yz zOhP$@8dhw)Xwda=PNaU~dR@_9@ zlE$vE?+c0R>bnVY8Q~++BmaLvVL@cMBd|gS)%CLvR?}-Q9va!6CQ=mwTSP-<@ClnYD&}`t0tmuC6Nj z3V~+z7q-#y=XGKrUyQ1`t={*dSs07>jGuK?S*KO|B8S`^4`=3p;SOK{T`PQJPBZvJ z6QczC)OicrLnzBu4ZgD~{9m@SPHJ(;K_0hi8t?D#Z+{>dM(t8alPLxq&{xm;l`#lu?Hqx~I^K z6%x9k7*1#np$hgj9n*XVMk8hET^R_KI-X0w$~QhK7nCd-k2X#aRQUop=Ng4 zx6NJlyM9kpJ$;SIWxdzeHGnN3%VAL5E4ka^y~E6cfnDs;O?ncuO!zq&{HJTS>+UgjXJ>Q_VRx&gktK z6gFc0Md0w9U1)2`+M`m9mhB6o>)A@(yRe=Num$|rGbdL&0fz)1A*r#m_4T(iuZX|7 zxp{g<#?EYks0WsHvuVICQlxX+==DHsj(U~JS4Ythna$G3mT0-0AU=cUR1XPq@*O8-=Cwgnn-iaZbliM~_5;cftY=N47RO z2o9|5NG__)3zaT9^0@+z@t2+6{@dQmn`)OmQG|i>(@wwDf&^bj4G%FlFmXiXp*lUR z62Dp|6%iLptU!d2Oui^fn1)6zMb0(Zi~D1Kme^Ov37#0Tft;q*Q(Au%WrL}0YtBL- zgNP)KlV=~^smj*@6($>ddS)ga@Q<^~jA(5|#@1?(3)OXfyH&NxzWwY(NSNCRs2Kk0 zILh^EJt@grYU-+2LN=}t9^IJEfq34fH=dtQ`p<+NaPYQ3fMp}57lC=d+J4vF`Uo%_ z-Ir#}EUapz3ix3~9d#LFp})?;J@&c;qGx1uI{&epKnWWs_(cA&?I%d|6!(#Ea$~n zO151#>!MhW*Bxqi-)^wdH}j@{?fo*b{rY!%*82|%@|G)a@MliFmYAPZ*e4V|FoE@V zy{2&J(jxlO&oQinwBt9@A%oH$%0_Y6GMc~uh4KD;T@rspZjlNmsMRJ#q|6Dd$W&sB z5vt2B4bX#xpn(`SQ^y)z9ygz`61zNHx9{|SKa$S=_Z}VzM0@e`_&x4~Wn^XP zKHQi566SW$5e13#Z1;Q%3ulfk&!D;R`OTP3!n&4%Z~fH@2cO7&o9QMY?rjLt5Zi0I zm)4<;eF#n&X--2)M_r-n*q29}RxPVZLw6Pm>k`4do-K8oQIbYUD^-lH}hMOK05T zI$>BfcZ%}qL=!)Cce_W|_`XxP%z=|A$rQwde1$NXRk=Di$~{~UGazn^4fDM_my=Uq z)Fh*v5pez7Dwn5J-8!S9l`I9LA2<8A-U?!= zfF1{$1gsblv#u_kodlC}>&l$a`*+USRvluSME=LzoD*fLPqz=!SUtk&n}S0B;>&$q zu?7Wk%SHr!yJ}3vpIK;9#(w*Yh^&8SZ1U^e)nmqMt3kmeY|n(~Wu;x*CnaQ58VuTP z!x7p|;ELEX6Ag5ej%6n$C0a&LYylG$6$PXoR_|SVfdFQ6wp;b+RHeszxAz@1Wo(JC z3d98F04?q^><{D&n?c$ajMG`sj)nAU4Q|&nEgD?a)@sFUE@gw9u`y|vo|*CxNI-2# z?pDE$&@u%{gaWomtkm*?Cv2tvzt&zue zaMI|j6$}I#2lEzL*MlXdT9o5RkdMl!~kULG^1ESl% z_TE7cT^~esj!%aFSJC6(#faA}853v=+ej;HF`hNM@#NHZ#;I5>MAnk>HtCe>m%tCk zV^ZRXzE|7Ogz4DEoVNG>`B~KIQrsT=iKPC%EHKeq4jQ#^lN1dKgwVd}zb1D0jN#PV zGr+ZnoJbF^vtL5~S&Srxe5V{cx3p=cP@`OQo7u1&?Ex(|kH4>L**Cr=87b6R~QZmizftO+;G-!%SYo(Ih5Yj^vwhVJP$1GgBbS!s%aB>+ix< za5t32^w=i#(P{}F2Ffa0ND+`V`>G*Gbtu{aaA_8gGBgqDLJZ;QFndnrN3!Z6+W3YV z?u9O6xvZ!+|C*#dna0%7f=I|yGW%0yLrodb`L6Cu*|anHG_puq$3bg=mm+s<7lZ}F zqRgNg#&vGX){srl)II+?N*6_$%rSkyMZj!wi27!02h%O>IZ2a@9DlM+1jX{@hJq-C z8l|;M&QIHC2vufAHmzdi0wZ5vxMGA9rTz2K1LMxUkkTj)atb+xrCQgw)ZMQY|CN%g zoSd3yv?O9}jH7zj8B3X5v8;2HQ0JZG{nm|MBmgk3OsWW*kidzlj7+x>1oyYLUreij za8S)uid-!tfmWhD@{;vm{?vPdxIlw(lA&4c@_L{oOt=!Aw#J!WtK#BMq;9g2E_mOi zAezqL=jru z%;w3TjmBl>AQR-GwIc)uH;ks^{(Y>xDG8zs=nw;9o zCeI`zC@)hhx}7G1LM_hK2*IM8oZKd>9(^uL_`l!!YYJeIkl^#AST;q9ryfwyY>ntwY2sPT8p-Gc}`X9W)}2#X_Ue!E2cs{Y-4bB zXm)7S(TI2%eHCN^U6COJ(u;Lx0#4h_5Cbdb1BfCT4r!?*HtTX@eoQVg1AUTSz!-WM zhHm0a&6@2LBwN!D*RthlO|#yR$=*IX%E&SZYLcoeR7Y;h586OvxINHIJzHw@jvHH~ zzKklhB_ymNdJGQXWOFn@p*Co(9UnbGL~F%dhyUhk4lj(K|8i17*rq!XuLSoej>Or- zWlP2*LPf{;pv}}69emjdHM?4aq`zv#JUuej3&h0V2Ls`J>-BvR1D&TAcWm8F zFGx@8+a*YoRlU4#mBZBtcli{}DyB$YpSmcC+ERHsmB%k6Xn}w7wdIrcV+)+(fCY#*o8U33XB`3r*D~S6Rk?Cw?!)%t! zQsm7Xidw`C{oEFxLolCKLD3l#+914VJ$Jh1u#pr&<@>bXF$%u{SjtK2>V7v%0z;h7 z;ga{&%=c##EroJQ_}B?e1+DS+27TYH@4cc!7oZ^8}ip2^aWgUes9FHE(d z<=>$?qu!-sx)mfXrThY|7QAknq;{)3(kP7)m7!8C>9w%zSt`GLk}t?|rf%`!P>*)* z`DB9dh1)RJF$25dBIH#?+I-iOo$bU%TamP|9@&}r_34

A4gFU6Tlb`KyNoPXxy!e!U;BQ9Xkh0G{U6Y-0qh-c_txYFT~#WyQRxcX*-C zyYlPOaUfN@L|lgbTv^^Ln3BT5F1XfAqeTvX1#x9wiMvVGWsTbrFZM@=+A?QU7CLr@ z+wYg6e?m+_Mh*x^<`S;wY^yH$hu3> zEQ9`RSsKIwYiSa3B7{omLs^Itf+G#%FkR3zt(SzfA@zFbe^q5sSkPqP_BSya9Vt9B zIguC!Ch!~iZR2C-iXRFDSt{p9uL=hsT^MmabQr_Tbp-Ph#g=U;r(^S~>H2@SC}M0x z1gz`%T2q0rk&{!Ro8Tk*`>w}+t=ca2%-2z{`hJO$XF)28u6vf&Np`Jrwd0Ir-^^Lr z=c1+G8yHIpyMqXz2?YtuIAo#s-iDCjt30DV$I- zWig!4K}R{9Q0n68x?X7->BL~_NQr*T7zlL#e~s8aUt_q3Gg2ZOUN4(WeY#@6rPOF%(LC}PwQA&$OUmKFniKGE8YT{s1uoU*`zy$}s`p|Xez z%|beR2qp>(iUC0br{t)l_j{siRO7O6DRwT7>TxYDxFbeQ+c{9Sw_SF>r03=`Qi#A+ zW~o>eU7U@-K?^&?4$Q&KLMGtpoJCG5TlXDL-e3ijbQQ(HENIbf>Qw9Q=$8-@YVvg| zPV5C1B-4T%2JA9PxjM8)9M70iN4UxDimyu@NHQ_SB;Yj@ad8iC#d-Sr#Soo?NU~DcJ=nJVUQlnqzV^&EJUY#{jZ$Uy4Mo}|8Ox*q?^8LKHO4q zqh}+Mxt-+!0CZR>dc>TQlP(h|*?gLAVAo>UWM|6XS_vUYm9K6A) zj90&mkzwu5f&bxh%geooTAIJyXef+uV#~&BLV%*yuJuqJpX+h*8OaPBDw+J3huo|u z{TGwaWl{;N6~QJIVmmD&y93XC-o0jRarP!ILMrGNH_?&(P%(mR{LQ0Li`|VqIzeoc zSVII~PC<@AJySNg7!KwH5Hm1Y~k#2_x$KeOzSQ3H-lU1vwXXh3! zpU<7RXn-J3B)Vx@-=9z0-v8h|?s!z$t7P|}`Tsi1z9qtvO*t*2M2U(S+5s((yZeIa z5Z-aygFHHw*+igN%-&wNYKUuGV^(U^EWlfqJeAZ%BBSE+BE{a<4+~#}9g(ct)`lX7 zDW`c!>ayk(qg=mk7={^QqkqXUw6G57EFW@|!yloBHGkv_josFFj~XxK1S27iHcn0q z7LHFCx#9FKDgWO;#)9)Yeff$6@Cnx|)-nE~e|Ejx;#X001fYbBjEsbUE-hIysdbv; zz87?5v7YsD=`7K9n>v4Inl(g3C*#(^WMY+2&bQGRG39WKSQ&hiDS#$$_cAv6JM*5c zh*o?#Z;xe;#-XQMC-nLh^+mwLVoOD^JIvB14;0CEC&dkm%&K_Q_VSTsG|GX){ zdEtd_gyVE~g{lLhY-qA9LgjlHAeyt{X!G1=XUme0B@!6^v8*4KoH$Q~Rl8L0HDb3* zyxaB$EtMFYRpZ5qj*i~>`YLQ*YO&JtLOY$!BhOx4>vlwVcy!3^`UnGnZ>uNT$tw8# zF3Ak@&Yj*@X6-ph>%h9^{CBcqQ%|c*)i6beMRm=Bro&0P($$6^*q~xcOQxN6>zbdA zRuQWixaK9LmJA$i_Z8MC8nxc83D06VMe~xRtp}l5Cruq2?_@2Ge{+5C{Z6O_|BiZT z85o$GduEpAd1sD2lW*Fk>8KSiU$C%qhuBf+HCZtIZIx)4FZ!~4+|n^QHT}!3vGwVh zb6&^B>1}a2;SL>utpZU36j&)1+th2SID1(*}fcSX)qkU3-D)A1TlJ=(v$2V7|xW77wHzOzK0HC3o01VXy%8IkqhOn*< z*F6WbZ6G{^X;xY;X_FI+yVo~1@LM*V;Dz=2Vs|)!quKfI!9Q$yF_j5VZ2|Q*uqc3L z8n!0@S%WVa;2c|C4qH-MI-PNVv4}}cUBb1ztMNOt%d7PkWN&tKZ>v_RZndbB>JuqM z0!!m^1YtQRiG&GE4hcP%d7lRTp7z&FaUMtg9&rpG=SI@dX}Z|!AhD3Oe9w02^2Dg}F)aiOxeGJ2ue*QVYX;HYg4c&;^aU8w z0dZ}c><~9v!LPQE{?Fxb96qg~V2e)r>$aKCg;Swg$(ZQi$BbpAGN%xNp%U z2%-RcR2p9bKsXIN4luvOKmn4A&_v$xGrso?5CMbSBfyg`SEF69YI6Ey00fVtW8EYp z!azKm{E2JBemJlFX=RsAoQp9B6|bMCzUb=c z&~DD1yJi7}M+>hcyY=F_rw=a>!a|uPY|bqA09#zSaPeawf4$3w<4F z-?pdGeGj_4IyUTNtu)F^Ys554jsER4hC&7PHmVJSV6zI@vJLOH?U%~rb5N=0{FnXb zqWyV(Ex&&QRwd%}TD|x6>nlffkL7lIuI$Hp&ok%i*5wC$G&^2%N%uq7Rq;1R3voX0c zD9NCT|LPrD*8FZuqP*e07c=;mTJ`71p-7JD=hNzZYq~eZX4Cr=osiZqj=$T8=JX6G z&t3X5K6^NW-wHT5E1k9^JGh9MvEUQjK?V#BfC79BP!ko@vV-Islq5$x)Z zoEahu`|>lOxXy*bAe5!>B^t>_ma2=DKU+c;xO#NXw*`n6u!ei*rz{mhxU5gyz1U{6 zOKGYI1rww@*yg_Bh6V}C$@C41<(y>D&^pSM`Wbz`*<-MeMtFzR0bxy>n%cR#Vu9E} z8d21dGEtf&6@RjS&Y#SlxQtut$Cnlt7tdvK(!YFt?SM4uOg=X?Ls)4|uhpz1Ph85> z0S(X1p#el|;3uPo?f^!oeO+l}HJZEvi($0FEL&|#_FryCyv(K zJ~t4$EtkLFUoMXTe=Ephj?OIe2I-%_xE{8Uq&*-I_Jy-e-pV1{_`-kSt|ti}THRCB z|5vNswn9*ESYb<;>S?D+%4oNjjR(ls%bg#U)C>=j@fsG3L^}o-kRzC6PgF~zrV384HP7v4zcHbT z9IQhh#a|l-vP|nRD5o#%s)U7eE+Cw_HPV8)=#{( z5l?L4qn*o9KrAH&BU$tvklRqCNMTBeh@_1K{3YSeBIOMvhIJS>7C-cDf-FXXj9?8) zDBo!7=h(whNHf*UnunuNnS$VVygD>#RTJ*-F8LhdXtui(7=N#x5=o-tb;? zJf0=N!orf-HH#L8vQoY0r?)pAGg%(=hZslPFyIxN&7P|2o31Y8-QC%7G;@abgJl$4 z!h3KfIrtIzSkNzG8Z5kOlVFWdWKnTMg*enb!XW}xBn@{M zXF8X^gdXb**w*;ttd1>w)0&e20>=u zCsKB0aS|{{nZhbD?zA=;hGS}|B<;sQ?^=}v(&R{AD-CK+Qo>f)(6iS zAF`$VgwLBG=2?3*OB~JkSC#m5qvH3Szt)gxFIvGU#aCA)D9WdgJM%MWBaW%HXb=mh z=m$mCWay2gWZvY5AYyCUB)B?+QQf6bmU5IP9aY-hgV7C;Ru*Vt`nN=^Fzo?R>%UO9 zS@Lf%fLCLFF|?G{d4aB9VMFB!hrrYw8T)iZuW;|kGSn((AtZ}fm=_0^>i;Q66VQ5; zvyz@8F1Z#&9NHSjy7kA86d6ByTWaAr>N!#Ye1}Z&1!Witjt52zg*9Joe=N0+ID;GWW8yQG~ z6-oYHxfAs1ZdDV@ilg!g()&2>1i1}wER4_bajT?AE0mG=GDUwvoRc9ZMSsD}IBhgR zpVJEhQLJa55FR93JFiVEyQ!YRS!nC&e4~v%UZID11KD@w)ObJ1X1gB@n(NCGpTyBBMvwyx(8kZ`ss4ineHb=x?1jRdr+K(i^{%MEM z&1#vZQzen>Sze<3J))0OS!16BmDuky`YM&fUxK~aJ))+_KWZR3t*&3vQiM9>+sg3W z@44G^1h}N*vE3x$zQkb3gi2ANy7EQfY0x<`Rtgm+lS(H#S7=m`+d73iVX)~YZ~)7BNp?v)Dt3&W~F5f>LS@V^ex8$(iLrhS{l=kE5AEKuY{q;y9=3u&)S z#gtToB@(Q_BL-k(|EPRv9yn=?K}#&LYM{pcJ^?i$f%XSWMng&u(^RM_t$tl05N*S0 zVoHq;v%PDpBoP-m4qp6LE=V*CDdw07Yp{`1E$oM=k9>H2^6C?<%)}3w2JTQ=x%dc& znx)qq6fHk%em(=vD`xvNTycbXiuI3lPO+<=L)==#J$s8>ZLCYDYhwhml&@9mS|!&&|ghAINAZ?J9_fP^i;So-JkWSg2{PD zc>iDJ_S68%j_y9OZbs<9QFzM{`v_wHPLnZIf`|vF0-J)NLLo&OP)IFEr1T5+i|l?r z9u2L;oa#`}cD_p3#E|tsq)3Or$V(~Lg+alA&YhkNF@C1NFyLS7Bvp{0)ecIcZ-=0v zBo3I>Sp70xfBEdQadnvGez6Lk<*L729~(`O&Kba^C-ZRJ`qrb7gvmJKn1adZfK7pI z{G2&osu*XhNXZPOt<0&zvV7-)DULh8)onB>Re2vLBwRU?K|YSJIH2(gb8M%^z<}ng z(L>FQs!=5t>m3bDkCUo*O6?YYK0Zdl&VzH7LZz}Ej8sk%F^R2E;{yDA3bh~iz4jro z3g1scVqJ^MGLj?F<#77EC5?l{+HII43QCL}zC{mLBty20M!lJIl1|T7vHe=4v^=0M z!-`DqwSr!w5pMoos<|IFMi+b zhve+$I6fe=t_iEs++6lPwcg&p+8HpJso6eN9xE1+Sj!xJRDDo)z zUU10h2*Eg^#X4tPOk5;#ToKMD#KA_3c{dkqfrWf+^YInFkJNVcXN?@Uds~0uZ`0;a zU{MX33-^|pSf!I_h+Sf$oU?N*{JaA}ah<2?ZHH;~RdWZV_BBneKC4ni?Y*bFzcZ+E zcZaYIO+7q3Yy=l|(lWN_x^;&0+II40UevANTFOa(u}sVoK5vB>9&g+Xe-C)+_sJv@ zdD1EU@;`*w;2=OjzeuG0LU+!}Un8|wE7^Lw?IfAn*Ev@29!ZA^Wf}b%x1(CJj&=rr5g)L6UWOJouY`uIrZ?zJm;s~jd>Cko<p*9wehZJArUQ}yY2ZD@F16D;8De~AO1_VJU@Y3i=+VWn z&dbO!va*TLoUpK(VOgE?CQW(I zH*g&!DAevjO7*=)Vsztssdn4C6-cIM`zQ?BS4y0k*4P1Ij#-7UG7V|86a6ap;dSsi zqQH;M>%UP`9nac1$4DHJ;c_#8n&8>B280p$x|`18Thoj}w*`^|JE#vTYp8P2sk7WF zBvOmtX1{`EtB}u;G?JsGR%ny5eZyv`@PM;TXJ3+vAb1(Uwk<*eR%Zxy(4oMr51hPpdWo(S5 zV)0KeSehRp#AlIe{g;~nXu{vfA2otS%csn&`~&;@rfozt6vH^NguIyv$;6Y8nymHz zZ351*9RS=lGKzSO23q|pN*!Uimp6g`P$jZRQh;wY@cQ~%jORn3WinGIfCaIjGx7S$ zcYAvaDBweeVAy1B4|foHV(T{BjddSEEnAuf){lFP3H1-*lwfW=+`p{+ovU{CC3E=P zLWpjLL z*&y!IA0r|5$=HIcKBR{f1!p$l0s&3^hf9|ZfwsPXmliwp&i3on_I``+Y0GqnZRz|D z-u5FDJUslTLC0tnt6+SD9;85jkZuduR{#m{dDv1+B!9~9Xs#TWI+hu6Dvr{KEs<_L zK+h{$^$r;LgR@y1A%lQPl3t;fU?gN#AB#C_!@(>aIS*BdD2$?%ms!N3O?Cco!pQlZ z^NYV&S$vL{Yi(!eq@>_8s;kZVNX^??O`X?_!hYr1!pB8R_k`fbo1a1t~9K9Su zl@QpoW!@k~oBB`awwpk)`qwiSrp`yOO@p*#W$949qn62F4AJFxV-_;_L{^c+_Z{`` z4P5liubaEC`{H6oTJ%KEi^$hUL{GPxe{*$hxqsYY0A@^Vz~!(v7=eCtSE?gk>wJWP zytd8^-P3og=$G0pAg{@WA}S5OM~SQ9f3g(~8z+3kr~r5l^gO9zdw0%cbB!7L?*GKK zltW}`YWv|C@1gUp_~170b^asO-un&j74X@6&3~)nP_9~f9a#OpQ;+}AJ_Ym~yrd?x zh9o%cbK^ggX<3f|uveJ6xADuUos7Rw@aF)+=WYVNX-v31`Xvo1X{~HHk2Bf4y%cMb z&AMm}eJoyr>b&jgxSln4X5zCD*XI+mQE;Ks^D(Rye<=eh4B(3fJK( z5nmr*za9#Y$^W^a9{-Z*>ShePTufcyVO7#&o2fIdjbXlqB?*dCZKVUWE=A6f#;I>x z+o1K_++)R&*BAW;T81Ca-Np=T%(}UmU~>My_VmG(xeiSvKPkDWXEMUNWtQg=B5EX$ zELKq&wjr{M)=e{(%sz*(QJ2XGT)L$r$E-!F1zIfH96j0^o9@08gPwuW3?0r|S*1Wk zoiND7TDM$xit^m`u(oOX@NYQu9PPk)$D-vEbi@A*eRduzjVAOhcgDa+N7{A+iF;k} z=-1}ik{Ch6V$kSfq)_BK0dq({ZxZ!kg5i{qO4%QlT+t^p@BJjtbYf{$Goz<1VOf)f`I<}7?~fG{pR+%( z{FtjzsXlxPI@67Wsf0{Qa?W;?mFg1PI7K_vt6+$OK^drVR?*N@s)2T4<^nqosto!n z3SrB2?gHt7?y(3#0!UPqSRVGbU}dSFjYqAUWq_;@Z%S@)r1#|P2n@PxbvyHG{+&$C z7Y*kSI1&eG>_o=Gj_5>Y4JfIcp-G&y7_4TLKujb{iaK>_TOrzhF47p)aC##vVkI2> z+r^yMm!4-}kNltpMV(GgL1F&VB|?34)82zFPVhk=2#py>rxSW0ZJL4m{@v`EoG!OC zIL^s8gNTvoZOB32aQPd6Zy$s{dv1qGVv2 zjUj$yLq%}628BIx_amvdsf^fW5ti6A7KYOjFKA`LAL`y?eB#Ew}_$s=hHQj4^?TZ z;?*N^?zf41TRDZP{Gd;|p!hrX<-rd3>j)yJKaaSJKmULr@ zCk|J~@U7l1yiWTtd5?qJ&v-|U(zSjcz6PEr#RXimi)M1!&cp}eULCBJ%|O%k-})0? z>t;QNFblVMnH3D~?;Ce)vx35c-2PxJF{tD*nB3und`mGTD*PX|urU-%4u8)SQWdjB z?id(9=iq#?6j7CdU`DSFwfi2etcC8c?1JZml*g^$V+^GZN|Fjdfim)1Y2@9#_c=(> zbU!$j_B-X{z{Z-+{?B1dOgY(%EKkxvyj5=AK`$i)J{0HH(@c48XMn@}_ZkC4^X*W0 zz`Y&`PUnpuUFX!)zZ+*6eFGlDaHeT^u<=^1EcCqFbId|sCP}zGmNA^HL0l{;f#g4N%8LT0`>K)% z1*aSJ`25g4#h|&_7pu>k0AuAC!msLl26xJ3{d6d+=&(I-na`m-q9Zvc@9{7oI6XP2 zA@J~EC`L{b@Jr4O(K&)r?yuC(WEEik^70@U@oQ^m&v8e}6L$OcnG+a1O-@dtgc@ld z%F47^b*>MoRj$y3_IFYA4CH}5>Z&Gij_il; zUKgdS=T~lduWW{ohiOk~E&P*AZ@bVQC$mve& zr$x9OVE+s2mU#+!Rr8j~N4%3dl4CLF;~~-F1A6Ji1QtE(5IjAE6YaDBYp$iK7%ICE zalWxiOSRo}gGzZ6379nLV2r>%NT;8kZPvh;;2#z)0uY!1D60U$p`RpVeBmR9Eaf+S zhI%YtJ1!tA7yYdSzT=r{#n@Qg3MH% z>c~@)7-lUBXsv;6H|Oq6hyzWB3Q}1|0*?Ut;bM!K|)EmWQAHK z-}~m}ZLC|@=C)KEqS4L@m7xY}WJbxa(mEToJze9)wLjgelAM3DdGYu5>%RL@>T^R5I&;{ z{XL!(#uxYis04>j$M`tEdz7iwc=v5Wov^riQ9BFk+BgO~x)e4M$#20TGR!QkGBkCJ ztaNr%X1vfN~BvXHzTlNb`mWlt6N6i3*xdbDo1Qa&(pT`BL*3)(pZZ(I{*< zrpYYkB2YoCT>sYt)6KjiLTXstF(i{HqagF|H+-H-4_32|c3dGATM!XLsZ*D+vx8Ik z1kWy+iA+2U3+N13IIz%d?wQ^Ten-i`?@9<1d|NRTQZgE+J{(BT%pqa>VM3ypDcDF0jnjygC0Q#Bl}2Nw>YG=gBWo(`H!srFR0+91{&v(T%3K-+`6$RK zS}3bn5)cr|(GHQeEM{SIV$xj1aVlsKXjw`1f$>nnLN&`kBU6;jcRh&yX~TIt2j;{U zmhfnI^-^#GaEX`i*c|58RDgg3YfS9ae@ANL380h8hUlUsgWYl zh0UU6#TUgz#Hht^jZB&d7)xBttVAtM;WkeVKHV=iiTQHKB*s>Z=3g?*K4}neB)6)Q zRz{um{^#m;6GZgtzsc)bf3&vQjp`o#ZD$lrk^e8ol=*G8z>ZT_v*X)$_w}d*n6`x^ zMv}p{=WdfNyqC_a#{Xr^33zLU;dCbuP?sMQH)v5vi)5CNO9!IdWPhH(3FPpctnJ)@mO2x0?7CA}UAis}vNQ{2d-HhMXn>k5MQ>+U9FWEpdnJ84N@M z=9&CadgZh#DqM1r=9IV?MRA3XBjYemMH4O1soQbV6)avpc)VVZWNh+?f(#_37#Z^= zzuU*PW+%nVug%;l3QjrC^o*_^l3Mb_&oe++HL)LI8mp_2{eHoD8Y3!zOgS{<$x^~L z#%+bSwZ#`cdZBi>usBW)Ow@o`fKFXSMKF=PuZ9LdU;rpUNT=aq=tz+nXmK~)wx_z> zHpD?j2{BONF^aar;k{KWbyBG|73C#ciykNmNH75$bS-dK0SWA@ily8WX&*PY^i3k9 zbaf??=2d7gpm0zDGheWhWz0Y)WGXYIPdsX2Yi#p4?Ubho%VuxX!9IwdkhWM!hYW?9 zd|D?NJVdCYslkE(OO;Pgy-cZq)E<)~W*`i?!SBOq+3;dmnU+Eg;5NQdDOw}pbM*pj zktjS@ZI6ku^}9Q^FeHNAC0+Zyx`4;8C*(ROXFi7HeMFG zhO46?1Lt#{Je;1)0&rfVcrRMP{i!3rGqK|m5}IFbb-8yBg}@Jjr0>3lLkUrlL8LYG z|3s+iho@gwWWD6?)smpLl_0l`Fg;{a^|WtLZmm6=1=~@(BSd8xQ%u*9SfRm+3vl9q7JcBABIcBi)2fP*HD6t?fF5iVPqHi9w;Bk7s^nw!~lYGtFkAt}Vg z`@jwq&VT;I%YWa9Ui*ml0jywXs-+4=%UbqAS($CE+x?mpcoMW|=#h9$4&_DUdL=(1 zewb~*LXZ3B^)f<>65=~xx8(%OCd|kRM=5SJ#%03e4%Inx{eFBz+WIKrB(oUis=q}U zLEa8KT9k?|wiYQQH`r{(%`#v5EO_(&QU5iRBcQlVynT}qkfbu<>l~&?7EmPZ4&TF1 zguIa=j_M_qRX-BH&M?h>YdQP9?Yk??sF!}R?u!Bu9gV=s!;{#yj{mK$xwJ1waJ}7F zkE*47j@A_l{S3Cb!EM{9`)}I__umP>;PAQm5*7xTw6aNJ=uyM@-rPJ7hV74luP3^Pfi*r~mE`+_l!%uv09!YAc?0skMVKR?NKD#!lXcoR4hm+|@%N8~ zFE4F-)yw974>)J<*h8^zUJpC-GKe z=y;0rC+c@{6&UaGa_hMEdky{jyM1j!?_=Nh-v!$pXRhxvhHskQ$9_*Jpzr1F(wb#T z2`z9zfQOgF&${YMU!W%4a|0MY+#WaN@$G0osR5a*XlauLz8Sj6wIwTdU=fv-l@*`I zC7iQ!r)k1U7m*U0D}y7C%9-du@PMnmkt4kKRtOTQxHnD|Nh^K|3A-N&RE%n69`Lpq z?6rV`-1%UO-*zkiTB_)qEBJa-ifijlN1-4?Y7JMum}y$4QF%Sg*`3hyT-j3yP<>=$O_EzZTOuX@aT$xAKK@)Fk=$7RnvVU+@$3|zr~cEr zL|@Wbr*(8LI@k2dIQtw<&gU2EaMu#_VQ|S$D4#m&Z z5Ck33K_ZQlKn_b+H$=T~pMy{MsL1#-=c(9VKM^HJ`j+Cb%JlC&gZrCLYl9 zN5U3^fvxzQARRNfgR|vkiRNDbanH`k{P)Pv_%guF`8fn98hU!S-aUt$rPbZGxMu#u zVg6T;_f1mX89YZC<7Q1k0SO>Ev=0y4pEH8kvf3~}>RwkQi0JC>LWfeGB5evTIneMs zB3dt-kQhs|^0##&Qg1B(nZUZSMYfepC4umh)Oa2fn4!1E?w>T2t%l|8L9A1P{xsPr znAF_??}stgY#*mYSIKy(j3IfMs-zW_Pai&R+XMY)^hYy!GmuAt`&G%sIfAv+@Upl1 z@Ar!Q{pB-0e=-d}rn!TYA!2x%Jo%4wsNp?hjv!h-qb^2NyefpiVR}?LYzn0|{0bhR z4MOKw)FqeV{ni^S#x?d06^sUwHYRbH_A8@(CFB5>0iXpy|A&MRTP_h{yWi!7_w44+ z_jBwwikkE{Y$OY|B&HumW#BcNQ@Q7yWXGSdRY@C5>i0Ry_*T`o3^5yG>>+k2EsZ}d z{4CtqiV=tka9mruS!iKzNRy~@1=EYKQ~xD-p-Cz@aRUy;%F{r9rn$dRJ>1E}OXj(p zPA(g(WoB6XbDRnEFK1ORsy^iRH*3hL7^GuJfj9Bj9LJP3Nl33&wqK05t6>!~>6sI# zEE^bI;Zs|nax}qo%X%cFC2-pI$$VX{{=qQlD57M2<>q@smGQci#P_m$tC`(Nf|mVf z7su%4iNBS)m%nVa!3%bb)Zu4GKr@zFDaJSZSH0C09-b)Z489}hlOga%b3F38PpIZ> zKWsIdmNLmBpG7}cGa&1Z1W5~k>uviUoicf5Rl8mki;Y#_N&h?Qt4K2Y^P~><8`F?Q zG=!?A$L_eM)Z$`_Mx^k)XhMHqj-vA;@|;n7}`%`G;$8rBR1yo zk_h;~JHG?ZYmK_&axiHy4wuQ9yuGMAFT$o-QjRP#J8KG6Ew+fO1Q;@)X9_8LK zFWwFB!QW%5Fy>NCmDV`hO*G`QN`tuktJL1au=+Y;m!ZSL=H?J_@8u+s4|TN;@XBXK z8z@*z{4LcOe>Edd5CVZLeC3n3G>7*0`I&U3S>$F(yhZ%PuZ1JPd8x#)vivwF6p<_P zR5Pul5wmO2qx+)rvu1%vNMxsa>+rrUg-Vh|rf04shB&Y*4i?B2JJ(~4Y)IV*Zab%i zD=afvh5oINTRc(SMCvtMOEwycLU?DEXJQRJ`;Xrrrk0ZkY&qMr0fPs2ZdcB zz}9^jW_-L7f%{17M{=20F!h97lTYmIf&SKi^iol)rZ0~8HCdfqq*?*35h^qDA|4Ym z&^!2W0rlD}5(>LEmu1Dr`eC_CJ|yUvwEr@-CQKWi3;$HZ1O&25i>1P+fX34DS?Z(; zJCB}$SLrQttlls1Dn*b!#l0||IS8g!#HQw^;}Mky{A@^kkd9ukG#V8Wo@StYQKLE- z+yH+&ujEof>}aTUO9VWLFe=A>(m@d>XH|!sq&77ueVTLQP3#V+$(Umf0;t%)_2Ijy z2ZsxfC=B$$;eiO4qZ^FaPSQNx+s{%o>Uz+pnaayt?*EUbZw#-b?V62kCllMYZQHhu ziEY~x+xEnnaAMoGb@sf^`F{14eRY!FeQT{+wW|7jI*t(?UsJ~F)(7mg_i7Id8BT-s z-4Or(0-6GS<36j0$Ek9@(3!=bpwl?V#h~feY9McMPX8Q)8IcfFCP%1K1=hqAYy|D> z`fHxku}T&8gU)*EW36%24H8zqRCLwO{R-$w98L%GFE`X1Lf%i~9QSwf;&PLlhPh&F z7H}_}>~<-tcQjikJ|*{tr^f?a%NY@AsiYyxf(H3t($%&tG0HzERLd227lWlG`uZ6) z%k1cqj^F*12BmG}00Qc3Y8_0N!aJsGN(ImT+;ATdE4fI9F(OiGSW{EVWNk!qWy=1w z*dv*C3&T9G1J_%lCI-F{lwXk#E+BI-pZ#urNAvqjKWSXTe@J^Z{SIY8yxZ8{9&L*6 zf#-NunWT?FS`QAX720D>NNv)38i&0d-!n6j`30K9*gp?ESHTM5$X8b-w>I?qeQsN{ zqR*;FedY~)R8$KJ%=Ffd2B}RqmSx12a$ZxF&hNkQO(U=SaEyhUZ^p#Q$?2DqZy)G% zRI^~68$v^1m&cVc9d040xTs|PQbCHTkzb&AQJ^oGWXSx^8J{u6@8MT3@);fe?~*3g zO`#okLjlJA$4gE(Lpp2N!vlM`wu%uZ;dCc@H&A`QSH>A$p7Hq^9$}WiS^NgoAy&r1 zICL-PphM@RVkYt_Ryh>jnf0CE-!uWKP`cZ}GB|BzE#y%odz||2aOw~GzR!V_wf1@F z)ZI~v9dh3|BGqR>P1RLdnjKG<+6qv_cBxb)#PY_fB6`6lay11_FzftMJly@ueWo3c zLr6UC3I&H^5MBPg)VsAFQf46uJvn{#u@x-9>GIohZFEwdC?tE$-a&-9`9D;L*m0a4 z`H1ZF*c~tJUKTW*wf{SFa@Q@}&ZfApy{X4Gg)82zGW(IVx@f5c#i3-iQ2<(a7(+Ni z_6Y}Ep)OWb;k*t+-`l!ovoaB`*}VpXWIRxV0TuJ%9gas<DQKh*wqO5Kl(t#$YIBE9w-%D6)ywo~r2DG>r!0Vc2xvvDeV;5n6dSN$~hMLE2(X z%URW*>JNEsX^Em3`i0Z(JVznPd&q)0DrE#w5GB}7nYe;TR+bHJ$Ps%<8ibF$n zQmXQMT!AiWV1X-w+j)~V7-Yl7$d~1R~&=teQ;rm*FYNl$$^bx}w?^Z!ZBs=R&jLsl4vs!a}k5FpT2F|EL zkVqqk%aLf3o0G#_tZYkl46bUBg50}uhyB8a-5T;_l@%nrs~@vlPiayTOw(hiQ`fYf zfK2;EOu#&+M_KqACeH}X$pmvIr>A}|JT5Q%H=>*m-hK=cz%~#mR z0QSOznt{i-mWVhYB$z2a!Fv@&-3I5(?jFHODm@vJca*iGgTz)V5bN0XF4C5Y~N*>1& zbhrv^sc5g?NF9orxwA$IE}4CA#rCF)LN-4`3dF4=)#REH80mqrOUU$#2XT_AM}M@3 z9=aG7)jxo|lw6`0$~WFIr%wQ3$QQ=Yrf?L^kAiT#T#%&YGgtva)H{qekuws6OG0)G zipwzRJW->P?*C%Sx}OiK&|So0S}G6i2vrW%#mxs-hb#KUZf~HxJy=Yi%E_n{tn1Ht zBzA%V(5`jCpg-A$X)nQ_M0|o6zWPmQr!EOwc_L%O8!*BL$ssBZ&~qnrV&9#GOR*>G zR&u6l1<&Ui1%70J2H*EYc~$)v*sv%!<~Y*Dd&>ZLes6-t@UC4<%241V9u=+t0n zj}d#jx3G%~LuE~o1Mt4jWCI(Y?HROGrb7GRSL{LMV>lL-`3tTmT1Z6Kl^z>T68X)R zlYTXFEvrQRhQo}ubF(FUqR8iaiSJ$F+~F>#S-+SB(m4`jHe47Y*1Y@r7fn(Oy`Rt_ zW62mBVYP{|1KKhNVwwe!*ks0Nr{uFGYPF@uTEKIXXurfln4_`+G=rv!Idbcbf%VMZ zbDx3{@kg+@a{&Sf#Wf8fqUQIC(pn*MfK%ZgI8ke|H$u(vn#Y8M1uZVQSIymphGIh$ ziPGs5q|St*hy`KXS|484kfY$unQ+)ROU`O^D>bGNn8~hVg&qA>=@~K z_Glg9x+{2EW*9s3dgH9Ac1C@u&9cfAXx-OOK-Yo6%K#?hNPT^M08)^6k&+L-4kNjw zj+dJ}$`lm_`L)^4<>;78@0c!qmeIc_*gp~K`X`R5({~)*C+t;}ny1^DTQ`)e$@Xw@zC`Yt(5{ z3OCwQP*LwRv?A|#>rR}kkFvTQEh9YHOZl`dB;!!pfpo2b0Hd*K4@*Yc+q{qrl{t*% z)dDoL9S@7^$yS|isERX8S1g*=W<%Vp8bR8`g%dV{Fx$$e&3QCPCQKZHLgj>di=&l5 z&0-pLq?~+v>!e-73PA@2()?HKvg>2Tdy{miQ6uy3p}!9Fta7rtMc}E*dn!x{_xlSPCjHf*SQoY(ZkRR+ znGrMat|MnoJtuE5OhRQCG1*z~_V0V12vQ?YPpnhbS+Qc-o$=%D>R+$D+vl~4S*T_u z(ojNN=)XH7MBOv<`$Aw^Zzm6EOAn!#K`awVi3Ooz6F_QQxo5yAjj*Iq5|~_i4g0Y{ zIKuDWk4>{a;e|_0r@6wJ!Z@#tKe!wDMb@VY)6#x^%1t~2CDslmpKkY5ql$RJ<+x-F z6}qZzHT^FVUO}0Jh$_Vw@bk%}aXpls`|6`A!TRxIkR^>o;0r(N-oG-x8N;q>jFQxu_LOVFhCFpu1vpP4IUDKPvtoEF zQ;S`YNAD%kfXht1@A@>qb8F{1>!e$b z{ai*SdlY-S*axAJsV8u!i=qJN9@@=e%us5&A9%(rywnzX04S0`guMHm$n}d5_Y$Pe zC~|-;2n+?S9;i`L$&_ZOBhm@?{=SK@)7XR06{QY8`UkgB5l^RMKvF|0iPm?u+0X3mv)X|d1lLbzTl9v!uwwQ+E2{-78Vv0-iT1u zr^KlT4-;jH$`6T&>Q$*NK=)7}eU`xHHGoCh)Z9EYG{or*Fmlw{eo<$?(psF~Fox+L zda~0xBh);e*S01a1ZxAsoUyU*`4qx`pQ9Z#Y1@mRYO*1SiJu|P0MF}-Y8n#8GNzYO zb%E_xh^W#%rt`3J58!r8c-6`eg?(q*bw?oZLY<#yZ8iy7 z;M6_C0Oxki$juh_rm&=MS?%*DP);yo};c#?!8L`+Ps*?LKoQj7N6 zgA-`Z;lG?*3FN1pMt75WpKY^ic~m^>?2h0S2=YuzjcGyx2$8_pB2gKLLv~K4JdELa z-!E9&X>$k>(IrzILTM|kYs|k*6NPU+<{55ozhBlDKDd=7lQ_kt|BRhj8esJo8GLHH zxzzse^iW3^&e>n~|6E>v+%$LzHP~6-{&FwC!ZrbHl7PAurDt$$;m7Azk9Ws;+`8x4 zf{SJ*3h^;3LT;8+0h0PJMb6`h!U)DQ6bWP?w!RUCEx(X!{##}xT$C`q&P{m?k2IXH z0X`DiAf)rTC%#(x%*)C9v6^Jwx0V@Y)U5JNAyevHXPDT&DB?7nZSB81z3zn0=ZH2?z2-^?|y0L>_&c(5HCTk$R4Wg&7sN$nl5 z*f-Ab_`W;`KOLpI>!AopEPMx-lQb@Qe5Dxs>t9wuIR+xk6VbA^!t~L^lg*-Z`gP;$ zVD;hUpqLiv8F|l^Qv}B!(WuSG&xWY|+Z(<)4*vTL8CiZ=W16@qH8`64Mz?|i{YmY* zvC~jJ^FvOPt&w=V-yipYHBwug@F*#J^~N!R=F;#%cwg($6CI7o;QaORN-O$HCW#cw z1g6bO|EI`o_KZ{#Kp1nZ7xevgBcCrU);LHzp@TqdJRDDKW{X+MU6^xv1<3{r2~0nr_bAbK7FrFyMI$gTVt0;J?(g)Z}`hr zvcm0*-)TMx5|%}TT|^gu^!I-?j5>tsGV#T%T=W#&6R#DQM=pzgscM;2C=<9P&Lq;F zhMDFgK|xy9NXOv`D3Y9^a(*|LC!_99#2TgGYzfWL&|#D`#K7_&sDQd7`lcutoI z1vsD$aHphNb(O)7>^P9R)Y=AcY%MGAg$#9GpeW&Q4iSF>6&Y7cIu01h=DMgLH?cr+ z_N_bKtoR||H?6E0?7iZlK?ivm_KI$y{g{?VCw9YhDA)i{9CpNh=mwSw46=(YzfgK7 zT+JaIm9fF5lO{8`XBglV*gP3uKrK;?jr9ncQ`hJFNsMG(8NAC5&gid{V5M1rJ+;bsqa>9ugGLNpQ~V@^^7Ya(9nuQff1|m>f+oT zI=-|7kV++RUjwIfIXCWgD+i!)c8xbvL*WiNN$M??qyGIRUN$cTo7#~E0Emm5(hHO` zd1Tu6J`WA;DwQ8bEI-FJxbYj8ht%F(2f!{l>goGCv^(F4i4u`zqy{SPWpXk@`Hp?U zB(_5gu7j)_y=TA?uFioNl1GBY1ao@AS@~%xSYbg$rHmLUYi70WBD)u@*>3iGl}a2n zMpwIUAWe&$ej~92j{>9EXzAh@oep@nyH<#~2;FTwYg4)tRT!m85Hp8rh#tTnrqZWJAwvz^s-qmdyT*C?kwnv_a+>(CCrQ>UcH zv7wVfW7;TZqCIC7738ocN)?XC>+hW#*Qsqh1U{{}YejA=k%p~4s^vLF#*SXVCx<9- zUalG`7q6WBgQJ)JH&1%U7?SzwSfFhv8z1L*Wo!NPj+YILQH9tcckcZ#-O>~;P?;)m zOE|hpya-q*U4LfD=;vy$lRGhl4kb3Nn|yvME}x@76Q+gd75Lz!*YL1bc%)CK`wwmS z0#m-|qDlF)fH|uQ6X<%gL%MZwEialG$jncE$@W^-$WHsZ1>5P;QN7{P|8@*?cB373 zn-j3?I_J6t^sLf2xhje!Ky|u`I)^F|JcKmT7t9T5J%KpPk;33@0oO#p6*D>6%$O&j;6ypl3?Z$<)g(|bYlB_>U)ZZ zVIp(a_U7lxB=i3V9M4k}?Znu|_QP{DR{>iSOfWVWFn!%#-eShu-_~Nv#y57D?dQn- zDfHh{@TDn9q|)mFHEAzIkVC{;QR(sf83p3GlxKxaNhm$BrtovQh|_x^0N~6J6xT{T zpNY=)g|IANXKw?R;r3BLqx=n6e#0+lH#pwR^9DbtR3zsfE8LayW%(_w*7qKUIb5&uUyIKE(`_0#8mi_O+U$H z9*VHyNr}r}LzEz?=CiaxWrVqwq(u`tvP_O6jQjttZ06*9C6RH6R)MzqvZ%z1U#xg> zC^beb;as*g#Ikc+lEm)jeR4QG{FRGH+-yelNj#3@lRk#S0?iA}WwKmOU&xq-|5Z_N zT5{_>1>}wZ*^IdrgwSeos|mBt`}#ly(57|#gGBs9bSqh{fOSa`_ne#&@S9+ObMRvT zSzgXBRoge@^+~Ln846r^!EtUUWD1RAp2SRE`3L^tzyrATAY0a^xjIOZKfb>FRFbke z8BZF!8y#E{$IN}iKHzXUXGTO{(&jC(V#N{jZi!85ql^Nyhya>D>?B{il$X8#|JJGY zs~bJc1>HM=R7wer^SY!Rdl7yPztZcoCYIhneS+Pl8z2F06mk7NVciX1=|Co~MBIcd zB!?`fclL4zA0%-HTJz`h z9bPh{uFj6$^|!#wRzrT77EgAiV#6@PrNsIc6NYqBK$_KDD_u@Vz50Hxz?&2QqfXzS z)A_Q*FUGACtj(7zSM1~cYH`Iuki&F7ClV*remog2~lE^Veqh`4!eP?F!ErX_>YH)0iPm%5HZ9)O+F z92s=jsr`E+Nu6hL|HlRRhjpnDMd_GD>JaH`faowKeO|G#t8SClDUdTK8A9@=CrU9B zup5pL_To(~Z zH)A)tmCS7K=u+p;JWkf#IcIOhQ`^@F|HD_`&!YidJQ6Uizsu5=7qr2>M6Ma(PTjkH zkgH0+Wsur=kuIG?)hF%EU4~V_x+afm4*^=$vsyZb8a;n%EezfJ=2OPZYg+{*)E=8m z!Rohvv|Tuex&Jb0*C%05%PNcP%H4W_hE;za`?W}zRWYHai84!+h!?|PB|Z4jn=8Al zj9Fw+z+R?A0~d|TsM0pcs@zy!Jpe7ikx}$xvcOcBluX&|K+ILhU68#&*syqr=(kl! zaQnRY8a*lru#*Ka-A{T6a43qCibN`8uyCX(PFrNpAWmDeNbC|k40k~RNk zgafA>04oRp+7vvVRIAEVEw0tC3us*3ogYgZ_Z$FH@BbAzV`ooYTYpJc@HYlwp(QrX zfmtyG7M5!7P=U;loVlO2t^<7R>7rqE7H?o;6Ox7klVK>-2u^jY9z?8f_Ij7Z&efci zi&k0emC(?g9D~mj=wso})jHElwB3S!!=5zjEPx;e>Z#MVE|{~xZ&36nrBN_R3HM+i zxvzuu{*nqBlh~9-(%1GTr3fh0QQh+(9*G3YF^9ssmP}HwS-Fz6yJX6+#N^q&oa05J;oYGmQc@0dZBEOwtYBBA<(z3&7e;aK^xZ^LU%!$*M>5d+)g0gOj1Kd=#&#PfW&!g{Af?NK~ReDlE4=5w@CQ%NlpzOjm;x=DDz|ev3 zZAmfVDEy2lv>7(1`Gjqry};RA%$8xsCq$^Mo7dt-f|hEFCN_2*JI;Yu8Ul}0mM6(A zjBr6EU-u7BLrD&?JY;l&B!inC?!oadLGHqBEoM$P-b_)hfT8Jump5kTcy2luhia%e z1k`Q*Wr}qm2d05?ck(w$3 z9eH~>;LXCRS`Xw_ft7zAzK8uM<&qxy^KN-hz z>ew&6Q_Z8JBdDNX5gKa|w=UBFwlOv~_8+Kf{r-S)ee#l%VSl}Nh^6~(;Rd|PPTHA@ zMaekO7_TL4e0oFCM~D=` zqAlG7fC^yk@?(C{s%@cfo#zczIP(8tur2^)*uA6tWfL95JT6MJw~0;7s!ormVSvb9e$X)j=Y*CzB6Fj*t2+|af6^e#5#4bVu3o`2CcA4*E9`1+ z7WK6}X*{5dx4Otp-ohPodb(KM1GXgFYsCm$g9#!egbI?glYtUyS}o>ZIHB z4H@vvs6E&wXgVT<3PTM$`i20cnz(c234mpe4f@vB z1R&?10kl3ictj^ai%jo*?sdwDEZQ+c{zTp}9f^ZiZXab70FIRDWB7TmC#^NvubVj) z`HLxx5spc3a1gOCRp2!lAQExg39ZUsN`g8V6CdxgI}qBt&(M}fgL>X?W8>t6yicob zBQ7o;)zP{2$?5wBC;jJ4T>F0!nl3az_=#@=Ai-wiXR+RD_wOg&ZqLMZ`z||?a}8-G zZtkd*B!G~TF94MpVm*Xd(~j07;Xggfb{Iiq<=AvZO{dqR*8Zpt1BFCLu|mUSkahY< zGr{F@4mmwNW%Z+=P$ch-Q7&sFE@OIszCM1e^Z)D|)3N^zsKg!v)a_#fd?BB1FXm@z zr5(NL^+fjImL6&N5cwXEq-@97N)T7xsVP=@H~jl zCu2@WopQ3J8LO%`gJ!kLkz$M;$ZJS1V7@ zR~zhcNQKK*MpFQ-_to3vQK`a^{n2;^9dYpij`g$$aRmal^EM0KmmoJ=K zi$sc=*DvKN9GY0}H(WBqLlzy^|L6!VMsPQ=4kpNR5Ysn?m~D4RN@dgVBda^t`o?W~ zKh=A03%OtOw4ZXl%-u8GJj^TH-wYx;WOUQ9rE~f`+@#em*7@vKecW2`561iLfd0^T zzgytC>iw24?gNOuFbF(r{PUd#uvAdoIMi9}0bwXYKIfRf|6X6ug-155%tw*`Q(xLh z!&%9Hv-*MWHeD~qX7{u#|A_C}Yo;_hh5zeif5Z2AGka^9g+UHrOVpcb?(H-!E*t{Pl}d>m}ay!;}BM!8b(T=bvq3mf_8>e#md4zK2Br?%hM{g_F5j`=$SZ1;Rpn z?21K~2Y-^Y>5+GG)=`{-S=a4YhTvP(i>frkn_LIzU8aWRuKaE058yK~B=WNIh|TdT z({<$YrZ7f-b7dH0tUuiSaBHaznL^=cXV#hp$nO1fld$r0-#>V)pzvJh*={YloSB(I z?V56v($v;oLvaH2+zEtyxm5V-n&R`x*@R^>nr2ZAY*$87s1NC%Y8D9rh!I_CM~Z#yzEBA6UzebOKm-~WVL~mYqcBnFpN=wF1F??#^ zGoc=wnEydsMr1`#($(MM3}>06o3?Q$`iP_SwUQg#u`x7GA8Jl(8q1#*|BHUT7fo1h z^*h#8s}!ADH@>`4u8s-7*N^ADlQeL9?vI1^VwwTSlF6ag%a@4&ETA#K zEK8K6(L<$Dl_kk;-D*yJ7$Yd7GI%}w=0{iU+g6%f{3{46hC3GEF)Tx$=B%H!PYa*P z1*toN3Wr|RqVZ3^ zpE8*q&)w!Vw%gSz0T0kYgqy7?mNSFTyu~Ta+ICY;qym&!i2@5Fp)^V zTGHd*qonXKA_3rp-+l2i*4E44L0o>pc!C6B0^8uN_kc!Tukw0#{VG zl0G23HA>Q`VEnxV0WMeo7*BQ|RfKy{uJgYw7g}tpvI_1PH`o3-3-5U0p~I%tF1jGM z*^;8>+!ez#^}|kb4!Uj}0|m38p4D3}{_YTX55hJgir&3$`t_XDj=M?2z$^;d%j;~| zhWY7m2sE6mSR?}o8tGri^8+e%z4_n3s|N^{T=oEpVgD0U!Kwixc%R}$amqIUW3O6X4>E2hU3v1qy8vl$OM{^5Qn_&G zV_~Q0zh6si(_lgbNq{0H{*06Sp6O!{)pit2St3C?$h8>y-6{5@^5y&Ji(>P|AJP3{ zXN*ex164a(t1J^m!?IiV)TJ#b(KUTFP4mguWB93~ySOXJq+!c!(@b*E;Q3Oz!M#|b zBjWL}H5zHf4dX-4ThCr(blsoA@yA-Nj!AE{8^JJ5&*s0!J-Ec#^SAafUd(QD2)nTb z4IkuQKPMG@>vL;e&PSDq#z02q=Z6TU&xMH=BaFJHQN8UNOiXzA#J3pc00#*jFc%08 z8{YjZPLh~$K|wxZ2s4mT$3j-@tj2ueIQ#@xs!9LdMKhT~`85-7zT{H)R<;r{IS$@vgO1cQ*&ZSmHz#Z};U_(x4kz zU&l656>IXf=|iio14!rd8M@Z^kM}JhElRLh!C%VX=;Cw(C`m}@#nObCzXFAc zW&;L@0y*E)`VE<&lp7D3LI#mhNSrbX>3awTi;yz8B$tzsP?HU%WXfe|VWI~_m_vww zd}yK5*pujqNfJR~MFfQ=iP6%)G|=eLDSwkdzyV`~6AJwmLN=2wBA$x;W6*)@Mk)!1 zBLuXHflkEC^1G3d*vh38N-9Jn!;2tVA;}6P3Mz0Ilr#z~R%kzARi8|2H++Fo3k;&K zIlRLPJhFdu#|*6>hzKT_cB~DEGz|wDI1LEc62*u3myBq5hW+j3#)?h5t`xLhCkHi;JnTu5{CN@JlWiKs?9}ElTl!}JeMYokmeN&A zfgTw(n2hS^*W^(b%@a{s6-wmfQc2VyNg@AUYI0i$sdr99V;?kW=dW3OKX#37bm$Ah zT}prQL{ZTRd`V|!4ZoXuO&nHrGSlD#+Z!P(vONv*CVqa5=*G%Y-e?EEP5BG{gg0V}H5`swT9h_*l_$vvekb1&_lO zj`Mp!;j*(V@G}>oP;DY>f+2{-5cbbZ76~bepqOj#0wA#jw1+?DMVXb+9Rn*xN?cx` ziOBIO)}@u9WQ2mIrX=G}s%Sb-jmX4B*ix-C`lTFDA1JHnzT*aKd<#ex`)gw(?gE1^+$1RziPL)-oFr?fB9abhVozIU_xk($&#Gs@ zB`w`c842#}3a0M(J$s&Rtp;12X#GNF^xNU7XcImHF3c1h2@!p1(rt6sT#0&eM3Vvr z@gUrse&_uyYxe)py*qcgzrTcpWraCZvX&$^cv%C>i9T_Zr5Nkc=uwc-Z_O(iaR_gL zB*5TULi$$kr?4AZWFqrf3ZDLSi^kS`i3WfGx*AKr<^q+F`=Q-BG*%g~p2@1LJ`~ldHHmtJo)EKSsieDbzc-5^Rr9jX~MDRVsM8y}vBpM3#J{D++Sw3(UC6-JHkpqdY+$|;W z3T?l|N~IrpoKC${)ri)QQS`p@dLJL2bZU_gDH|Q&=ry)#b;1%^Pg%Ad0y4 zq=I!6;4HLiP(cL_ef1sSPJZqSxwzF=Bk40B{fMCKN_zLJY5jM+0A{Weuj4Xb^EzGc z;|FTygd_$>-9q_EQbTj-{mjnG7eRZh2n9$E1||6RTj?%Oynxh(EF;~GZU7lk;V{qE zD-c7EYuYvMyWWUsZr4?N{;U#Wnc)nRqFZ?^cMei>R-6Mb`=8MHbSMUCU0N$7#$m55 z$M4M?zg@U8a|2VJb>?t&;T>4NlSGbva6_kNZRq^hWQKVrBv6vP8M-!t{hbDgt%M!a z{1?{E*53u#DEKY6+-;j)L6LS@(M>mH25&!)QVOubu}eFScXHttM_=xm4E6)MBhxaD z9sF;Q%JeO7Z{zt!*SB8C?K>QD2m_8%_C5HY;Ma^IL|KNA>^g@?hX7v$-w(B|r&r$6 z@ChUYa|bs63kMRBVCMj@>(<6FRfv1y;sIs*Wn5*lO&iQQA`uUM>u&7X$TSEos-q%g z#^;FPwkEx!(gX#pZOaGs+VdU%Ac16o(TT;ibf-<=4Ghy9vq96I-yKTt-s^1jnA6i1 zvMq0Aa4VRNHm>et7@IsFp!?D2I(r~*(3q(32$yYTZiYU?V5xf6jDhWij@|FNGHGYS zQbMEg=WW?6{j|rlSPMkofHrdi%DgW)3njONVal8kVTlD8vb9v$pKbL-1Fo2A4JKgUbcSZJ(idOiCv4M zB@y#mLoZt0xDf2PFR&QCgq=9HQ(V5Ob$_?0{O??y_VX#o3NK((OJ3olw*0-=KGN6U zeLQ1VdJ>r)6rN8dm@B?6Lj}vU$~;dXqx@i~h+$(QO&9HL6OewNKmvEq&;Ppa247^6 z7)QAfz7*3pDd?hQi=dD!Q4A}Pi3!+sJ!P`5x86ZUzgtEJzxRD}j1sZwo%i%eHolWZ zeEgnhVtS`)2x|bWGsjeM!)LTl^~DnDKNd>zabt)=!|LqlDR2|(K1d1e}~R= z`I*OfnL~xe-;Kp@#CLDsmBsrdW;Psq-5iHya>B{LV$KfS^geHl_DEbEm0m(3GgE*i zNiKW7e{cM9pL47QbjZK2%U-l-Isi9aGhKZokac=seAl2{<3%-uvo`g68E0+$oB{oK zsN0xH;fzVzMwXN3j~z{D z%G1_o7Ey#~Uqpv~{dJ1Nv2C#5kvTf+A<_N4zn-aboOy(jhKvgwM;OhQoH3bd)$~kG zLc;ZjINDEDEJu^O4i{LF=8I$aPXE*w2Ty{E&;Siay?5!uAaj;R3rRG!1Hq-G`8oE3 zOd5NU+GU{Z$|G?6$h&s^NPxSGe9W>el$g0zLhjw5V}ge;7Aa-8vG!LpZBd(!*Wq|k zgtLtj87shxIC_dP~uQ{0+d&{`nEpJ5hRdThVLHvZ6(!KA#SWdD7O z*U-LahRpnp=(?x-!RwxAkMHE3i&suw?njH?^x|!?F6*G)FHaQHN)!RI;I~#tGcsRO zGrnnJS(D>Z2g?SdBm$2RS6_%8PJUNb_^%BnykE!1US~FCuepEsEx!MpzYO?qjed9> zdw%l77kLc$zb#-YlJN5&^hLMzzTl(H8z6#l7kkoDZT=6D%I z^vf&D_exy9?t=8++AsP(;p%1j)OvD?fNtGak&qU%wZr&rABnuv@8Ekt(9h?CQ&Qu3modK>9?=R$9g<}T%St)=lj#$_eZXb z&tdkA%Dc*oM*QZe+^E5*NAIm?>A(`QmSf+f@k$zIm=d_uAO9xo&l!^WxdSXnu?>?x z??wYJm(aCzmBtjXY+Cop)RQR63XS0LY+W_4qA_hXGQO#_y{WsEv5n6om_xfuixK_A z4A*VD(&N>+OKE-ZLpf8DE9yHXz}X?~<=xT`4Vg5_c%FnpC_PkTF>d=uHLKQ4a}g`< znI#d|;^~jh>zqSt#dTnC7l``{iDl-^_6K6!?nJ)T?L!%c*Y`{g&~+CSE+p5;OUcrO@`kzD8&+ z_95x`mv>7Zv8Z#@!+d5=MtQ^QMz5)epPUZwe*$K{250UkO_=tt)JG&FB7{fl?^b5) z(bCA$Gz}xYx+J6JkEp>e1F+7ZV+gT}Af}U6uoP%w*lB6$c2tT+pnyqU7OY@8vjV^{_*O0fCwEw;(WiNReOndhKauE?>X!&eDlue@A>=PmN2sA3XaGy4fZ z=A$w?T_Mn3MDQb3L+5^;g*Ih=rBW0~cFP6>GDzqp9XuHfDstu`W9Lv#=7yLUUHc!# zf*Q%bYio!5<9E%Oyj|L9Vw`J``zSm|CbW4}_wTV%QKJ|g4PCkf4ISG=^C;Q!+$1Xv=D41WixkU(<0!nY6ELvQ z6Lv->&+{|npc@2)?!d-$*K&7~_i>K*RVI&%z_WKREIroWgo%da;|S`Xa7|0Xy@0$T zKiEbEepH|%MB@I8HR5h(Hhgfkj{}oU-;xyGz7dA>FPc{Yh%D9^M~39KRs_Fhw3Z2x zlyvcHkwQY(a?sXAL5LVy*;If}^%%G1#|5Zo*ztV1oLySnTl7Jp%mTa<$@ZY6yAU$) z*_mW57usRQjlUoy4jVp^qoW-0m9@f#F6+T@u@Jg{6s2g%op2J{xUy-SQdp;TP))7v z>miarZFC9F6maB4GjB66bsiRS$T@=loC1cTXDd{T5ILfPtGiR3ILvPtyS%DD!HAH_ z`8GRq*pj?Sp<-o&!xAF*(?sp(kL_A!8?aT4c7sk?91ekMVL1T16tPkubaGSIR#vj% z*4!klJzg7R>y1BqAm7W(XjMGLW%{CWK%rYfrzMUkKZ?Q zuL&*lF{Z4$LEsuNiu;wCg{IkvIlknaDa|{vc2# zEJle<@WT~|qFJN&@!AFwCtW7)TiXj+3{;UeV(YNhj4!ng!77+p{Ggo?E_IHeX^mH+ zlTsyKAE{{^{?dOerde0NH0+{LU>yIY309{~Wk!uKjRXY-LbP>dKasr%px{T348zi@ zDXIt(p;$L(sH&5$M*T@k6E?G$p=>q2yo^@D3nlXc$BPl{@b3rOa3DI|GJjt8z{uC_fvWs*xt! zgcz9ZArg#+@Ze~`>*?FYm(^j?V<;Z~?%YPL*k%Q@LB^8XV&858(Wy)@_OIE??p2dw za>fS5_e_*$$J4)-6z|T=agC(br2KpEx5&eL524JYQIgfao?ktchTn}y@KPZ{e{hwK z>x34=)zWD=1g2&RrlQ|^;+29XslQSZZ?vR8iPqVgXV*v##cM@pZJ|V&hKHQDgZk^j zY-;g06uh=hjLV;-g2lAK%Sxmq8Lj(T`jO5RTXzkW-qkA^2csZ?f`misd;!vFvz80L=;b46jD8D~ z7Z`o8#M1Y^rb2k;iUsK6;MJl@0XBh85SuS<>HA3TP6+{_ z^L{q26xXnBU(AGC$Zn_M9!`c_CG*+Qa7n%jVh#b=)dP?u$Ry}Va1_+7jQCbcq}=M^ zO>Oj^0tk9)HL9-UB0Cv(EQZ!hYGTRjC<`7on(d9?bQvJ(CRqAf9P1?n!}X*4_OAB* z+VQf@rpH(JhkVN@VNb!jBDHhtg>s`18sIT;%qUth_IU18mOeRTRp6dWLpo`rE#33al}Mj0xraOd7Tcy-9s%V`aIGL5?4 zB2il{hjNb&SrCIN5k4ru7$WUm>gUoX!=hQ&OOh|3?FI4ZzZCIb52ixXd%m8wJN5=- z@d`qStF|`hldD=zS~p{~=tb#3d&EMh8n$RMWtvvx%pW#xazbbL7$zfkz7-Y8JG+4j&3i4TFd%1OfP}5hu(Q7=dmYkMDryz< z>jbweg*?`Cv1BP`?^ml#OwA0429P;nDmY>Lq(i&bia4oi!MJ0qrp#4r%?4nu+V`n3 zc{+4}IaJ%R)!GjR>-~~5SWP&SM|=C<2Qf{4Au;eert{uR!w!%!MCS{vN?o5NF8C6M zM8DJTM{&@fMB9*#;VfXF>!+=mnri>eh>Di(P>Nui4`M@fX0W-XfWqdWJ#iYwTLq(L zD_Dw^Bh_@!NIWlsffOzISoA;u=*T5h(Ivxff$+? zDnaPXJD^xrGwj&onhRyshdu{9ek|2rt{^y%iARIey;}Ye4-e6HCK7musUUTNjx?PD z<`!Ed@{JFiyhv_W=TewOz*@D2ci?S|Fsezv+27%7w`^|y67bhm6|-bDbejs+Wkfeq z66L=-DA;4=Z%;sTAiOZ>2;q1n_72bM{$OlmqOKs7mENtWnvHHhD7tSHj=Z5 zUSXhWvq*A@`({+XTHL6iec<5=w+*Qz@3K_InkF%q>2fjgTWV><$ ziGxTd^u-3{fHe>-^mW3lr)0ML+j5#nL%JYAm~?V1YNO$M?~gk}s>pa;RRxc#^iA-% z`RHPbv`>}OiA1Z`-|@HUiWFIMjZ3>HU^b1-*NLZ+R;leOU`%yWol~||bx&FP!?xoU zh0|}-@N}0dLobG^%i(PwHFbf#L)ynEzBsuKJ5Q`o5lb_hmn)QaEQj75c{ zH}3fxG}fkF?>*{Z`N$2UhC>-tsEb+~#U~z~z7`(rk6$?T>nQ=|7)91pGII=VFOq|q zKBJyEQEn=%mUpcJ$9x%A_u*$Z-v?DUNYY|(cMvYZSE~5DN9C;Ya>mxTU)TkWd0Z}! z2qR)WEqNM;M%z(8z!wg0CFn1`z7Njh54GEt)b;`MO&1}WEqP-N(uEwj}$<>e%eybc!Ie+EqD>*Tg%ZY&{?r;O3F+^ zhNocQ$Z9&?&LX#c($+3jo;Zq)M0CZ|SSXb4DudCtC;F_0_yC7ZUQUKhMZQ$(&Z>s8 z<_0e0@aj6Yr8-elK^%Q7l{!lIEKGC8g=TWdQ4;&G*?Dj&Hz7p1cd5JJUiaAMO6h0y^o|FLOCu}WR7 z((_f>baJqI&X6C*yyyott@JJpVio?Q7y7A7o#SDl>{+6%Y5?X|xl@Bncsdh$rf2y( zsO16D-5qQqy_MFp2>;faBaB|v&1Osw?{XhFL-!pV#-tCI`HwC7uMAln`J6F}{`z@d zyiZ1J1be?#BR|V_b`+r-<6em)xX$^(KV%kl=$7O!Ay(GGgg#ym* z2WPcr$#Pe2ZeH~MUcK;J#n*s~&}p2J6eI6brW+w*179Q&ALPF3UO_*4!gJi5oe3EU zrz+o?S_#2adTxWC2B+EuPx$WB6UM3j_8j|=6#wF0i@we0fO$amijKOa?<+Uy^jjAO z=za;D#jAk=Kr9-5&(;8f)^Ke#z8QudQ^itJGQF>$rY9j=Kl84vCpID^`~`todl10Z z$ElkHskj>mL&8j+4KLbC7o19~sWn?}YAUOFAe;^5a#-<%{Ly&H@&DL*?|3f5K7Le1 zJBqZB2HAT>p;QWK$jZ*%LS{xOBa*VqDw4`3dqrduWo2)&_vXCs=XuWSyw2~O|9XY* zxbN%weBSGGU#g{1G{zlf`+~`o1zg{lG#xyZ73W1F&(QAN%Bg&}KX$QHA+h-ORAo*_ zb*Nbo$)5^k=8~(2C?)rrF_v_aDDfLMobA1Ji1ONiPV8>FvQ4we>|PJ@jKt5*+pdN3 zye|uq(EXMjT@ogv7Ht{fSyAuLQWLy;cDn3ZMA_Spxr)zkUf=z2-Iq*>eFUHWqZg@~ z{I<`!T;4j29HABGJo#t;oV%zXa97GvmHdL8?jO=zGOMrCl)NYAvnj_<-gwHhkL?`$ zu@tTE#`{QF7Ncjk*IXqYc&=`6M@mGE`(AZRh^IysP<8GMkw3?oq z{+Rpe#Ee7S7gjRuGTpWDrp$3u&Rg#7O@X|enOcGIqgN6J|HdXrzx?WBDBsnX55oC4H-Ak=7?czk+Mpek{IrOMDsn@kYA8ZoZznep4pf2QP{Jywaeu3 zjn%gple(iF?OiEVV=Fcb6!Dk zkMhlJM`n2fJSsU~U*fdMY!#ypcRHhI;ab+5%_`^<_L6#^*7k(hfyInF0q*3~8Wn~q zTE*UPB~&%*EQ;lFoQbJ?Brp40U+SUTTfcU;g!oX-WL-dwrs8W$7Ag zz?WMJx2~F$Rj0HVkJU6Pb;(U-{KV5H|3kR7ZdrGX`VQ-dI1CgGwmQ#sB=K~cnfZh; z%iahz&A8l@t$pp<2dY%t0wI0@!)S}kH+WE9912=c=w)r-!V-# z%8)lKcSwWP&fR-ASNLb-R}Y2#U)}ph6w@rSqxvK?3Nv|(s=U&@pQ~P@IOLi&A#M}C zR*?7~qVTH!Sas4`<(4D+j_8Sq&dh8@4>G=UDI6_dE6V935^I9TOOKALH18AWdG+$B z;(Vr_#17szWfplkgY)N_p#=FGzG*ny|BO1q?1LcH+l$_2$$HPuG31S?mlvwN`Qr96 z&dgGPhV6c!@-Z2vHRkTToluEfA33y0<_xaqlpTC2CWi>Aj-n*;z zv`;@k`7=pGqIkf>f}P9$M~Bz=sIBH)WKXDyp9q_0OJW~er>)Ig>Km{RYRinSV6U89 zALLd1)F*7iWw1Xwk#DB?+PeL2Qi=Rd;bWzTceCzE$LqW?JCpvIay9MF-+-y1*UaQG zEF90-C@VV#Hj92QN{VweD4sgl!Q!=dA3>baO(gR7kpB0d&+VzuT>eAFWQm^;|11sr z&oAMhUPAxibGxhm_iDxJ z&r|Z%pXo2m9cztwt&*|$fSJ3eug|T`lr!lO2j1F^mp>_p<=kb06)w&gZd^Q>Nb&I6 zO^^S47`OIU%7WDY{e0Oz2ZM!}#xL}ejkyIC?ep3DMIk=;KR(9o=?qnRk8-Zt2pXz>Mf5CsCzSnwFyUU!)*F(b5AiD-4VgBD9!_U)d-ukQ2T%U~1~Ji_+=(C;$` zcbji}@ZbRn1OI?WX;}C;d8H%I(;(RcJHDRyDLpnhJ6L^~n)*#t6xUFu|IGY+X;V|s zlP6Es`kdVD^Dd{Qrl|MuVapwJc!%dr~#=})_|vhw0Z zURf)W;&v4VX-mxuiN>7&U9RRN8M*6wz0A$cLvNl-P#m*a5HS18Z`_iUxO|k6iD?e! z2d21eI5J4(>aWE`I(iG5{pH5t7i%induD76_V!P#X|)$t(a5an|4M30yr6^?zjaL1 zasK^vzmr$5U%${2_2dzFznwPMi}bqOSO1LGo~_9!I}i1bwr6p1sC?6^3FW~w^i z7dP$7f2&d8xUGylh;b_KVej_quV^1s`ZHdftKn;LPIxC^!fQ}ROMdcV#S1DXuNN;Y zvw4=Lx;2F*E5~|0>1{`19xqLfx28U`9StiAgXNNE2j1FPvRet=ur%6fE z=5Kw`<^q?T93EX`KP~I0*O6^`Gfc4dL0D82J-vc|@{32Q4WT;@lyHPy8>))EqK zKQ3~rd>hFf&Phuu%JX&ESpCpo_{)%geyV#b6Q?Wvct%bq(la(DV{H8Iz$^I!$M<2TxVDZ{wQexXTe4+VumSH1&o!Hy?h zUc2>cLW|S%szZe=PS%Sqb%$#s!k1(cD-F4%n`KDuIa17{^Pt?jdXb52D*yldDk!LE(G`f z@JzSp7IS;{%z|~}1%niENHRVwCLLnd4@QaDe67hW+FafDgqSkGB50poxfvzaS==|X z*lzJV*T$sGVL8T6ELtVQ_?u~)zlhDh>3%zsySS;^r(!Mkp>Nb9q zB69w@He@S@uiv^74B5`32B~KwlMBDSN z+MCeOikHXvei>&}6>i(HGti_gRhgaKnv$Bj46E@loA%OV=UWjQR^)DpgVuxClp2XI z)SMS96Jj;m7^~_cYLubU<+|mJHSq(>1;70D49QoeCRw$@agUH9+bS58#S zG;MP$);h(&@KNHlRc{HI?){-ZU+#@}g zq#6%gKeppfqH0K)&(RAPbpyFA`>tZaUPU+9ca$@KYFIq?>?0#KFd(8*ZC6hG@Qk2=*H4DSd zJ9B67sQPWc;+44e@7%GrIFUCLPPS_oaa%|rtv`*6W4iuIjbqapb2yQ%n+}6h9tUU| zlhiMW*p82!x@AFq#bf^mwCG3w{=7idC|vH_*Y$Wx^ykat{nKK;l0UE<@8>>Z!TunA zYkj%j&uOXCuIe#v32NLhc5NKGLV`xYJ5AS(j1Gsw$T177QJnmZ>RF0sNz;|;I?SoH zwRCrWM?Y)*W^~Wpp=)ywie<^`(^rTQ7n7DGMh1rGt*xyzD!d#N;^N}7v$G_IElG5! zwm<9ZDWv?T=jML2x94?a0+QrUm+bTYjuQcTS~Oh?;^=4D)~vabi)e&zz2G@@=8PMM zY9@2L$)C@c4Go#2oEML~yStlqWY5rx7hR~FJwi*8F8Q6Q`+J-Pn(=LJR0+vfv@iVp zD2O>8T*-pTOrVaR+NnML{X4#+&L&Ms>?PMnk#(AHxL(!Rgs*w6$Ymq@(I}-ajnGhf zz1Wpty(64zH?*`)o$&QAh;;BUh;lxH+YaP|cEnA^bSuBtTFid>6wWlq3UzaH^YZbD z0enGSWp0S5mlgM)zBFuo6EBz| zvy&{B{x$h5jJ2?>Jh^z$+efy2cvn?ztwO>c)*$aaROh(SYr-7A+DKv(dA)oozsVV4 zR&YB?EPs8r`eR(&4sN}w!@(S?ijw&P#(#F>VxzJ@L+$S>2*XKeBKg;2<#=4y?O#fs z^`xLbCngpt>3br`WG1(HVX1f*J%I-viO}uXxnHX-O^-M@;n;qUy=bAy;>8O@GvU<8 zzj5{I^D}{pNop%uYl#Z+M~aJ!X)oBmeED>EOxLHIwF{zQOr7fbn_*H8L|0i^S=`+n1$@Iok%7;x1&r|g`SaJW zUtf}x+$q`LWPI4>-=E%$7L9(KZFdTdda7{k6`ljNtxW7S4r(IS=)l0hK`N>rIH$#- zyuM-GujeN3O*Q+qvM^=AM22&+?{K)TxU91BvCcd@CPv1=-1eQMq+ktJR|>^1U%rf% z#BDdJhTgVTY(2}H%=9FqXp`u?GE`@7Na(L`#xUe-!eSc%D3;_#({i;TJ7N@$C z&qrjT^VGPcx78FnuX%ZSJ)Ac4@$tEr8KgAJ#LVo462$DP%6`N@ARyVHYcY8Hv*P!= zlgG*csyWqj@u_1>*_fH{u*);rKYq*>9b-@*`7t(Dy~U3Jwa?NEeSGGIW3U=$F@(gH zd{Werd99|so z8d!MxLQLrt2RHZY!RipU=Aw}i!|gkE(5**|OW{7hxqgzxT4PcCVwM}Wd@#zVwvJAa z85ZlnF!CCA&oW;6a5qMh;oQ0HWz}9dWZ<5;t(chD#l=<~G`f_R8Qd%^mAxJcCnaMV zitXyUj5-YY{Uah|y9=F#u?5{XaFY)mKKvZxp((z-AO7zw0IAx;hbhmv3!7^Pro6XH z`O}(L4A##1YPi~67*wwxY!WPJGX48a>IFL%{emo=9U{m3Pw?-KX{bQ+yIeev?8E~aapHdltF{Tx%c=RYQDStrZ{PO(tn}DrW$@@uijy}JRWi=oP1368K03|Je4{Pu z&6~@(trmvtL_w^N6s2CxUqE%nTB_`o;gq6eQ%cgE?)gvo`}OM%TK7+rMG1aq7ajq|~mc zEboVc_0;OF=!GCQ>h}fd&Obcm&tQp~Xi_(qn7ui9_2xM$^s}PLuqtuJuDyHrE>F2` z$zcBmh5hq=C^~9cxHg$*l`QI0);BG>eHmx>OnDEct+^8<5pWV?o>zb*UikXj{A2AK z9=G77M=j*@XtSO5FFYFBNSKwp$dHVP63X0`yMPm)|<~|Mbp&|U`hA5A0WJ?6I^={Mbt}0ArOL_4iS|`*fBz2v_}6-GaVQ2ky;u#-XVhr+R)`}n zEG$IbzV@Arl=Su6w+`2bjA$0meB1A-&((FjI@bF$>EvpH?V{8Jwr2xJHl&R1hu`2k zZ+(Zgtgkct_e!}!p=P;!KTUejT1^#pP3jKenIIAvrT5{U7qRp-ufICMst8hkhBM| z(t9iER$Sg_Ae%+O!V%dp!7@|93m5s<(2Jl4G^88c#_7LUp1M#dN`L;OJbbtmwXQYW z@+^AoN9iC|Fxd!;!W9pEl4!;FJ^S|cL()P~o5gw4#5lIg>L?S|bM;6=%yB;bnr;oj z1G{%?VOQ`Q{`%gVzAP*lPM_Jl!&`l4)$Y!M>6w-~HuhsW#kC6}NkMWeT|W!Sbu^A< z$dYZpOh&;aB_*X+==2u%ya8WGI{y2q?8k@BjMv4aIdw^I|1iFmuFQ@nXW}FKGjz^d zqB2YC>N1FLF6-n_(512Sfvgbh4w$2|y87PsX5IVe*w}o~zQ7Zbr&q_4buv_hZT=mG zwsOs%;T)Vx8jJj5EWhivwu1Q2#K6*;tP(3b(UJ*ULUVl>f<{R9L#<4}a=$-DgCCPm;Up zbcf5WD6!h>^v(m1G_9;=IUK&{Q}-oatbUuH)wl50cMBkFc1gs_3CmuV z@3Ji}cu_HD8*(T2TVT)MK+dxY@6ak=3|@PBY1+@S=!nlz_faMSIy#B~vM}SNwJTnqULO|U>czSuk^ds+jt2}JJap(XsOD%x z%&vxp1`>X=zkxhDKYyh>xB?K44=nFLBYExG%w*n_T%w8qTIX>wr|jRW(TvYMJklQg z*hv6TJg;DSaZw-7NRVFefJVq;)W`re)T>fg`-jH?g1SJiWieyA8TuV!+MW0&$O0aL zfrp9e21G;9cUt2DMSd}Z7q*O2a#r3G(1JWqI^H;Bk*;5RMqFI8k}fbPD3&2JQKLX4 z&vxPrKfk=NA5iVxA{S=@_en|JmDbA1&L&W@N~Y-x#dy7~&5e8FZFq0F-YZ9|pa7EBaW_q&eYG5xZME)P=?aEqzVUEtal5G-1C| zGHYuxyrgf`op8S3x?V$J+Xh1y31#M$l1v(7xx2#A`6p^C;%7!UXw0F^c)facj+fUT zW$>hZ&o;(B16=cOh7A%dt_#@Q)1bF3Wo7N(jIQ92CkK$(yI*$W-)6D{ zA7t^oZjwiznxISwryk%lh&Cwv`J$UR`1!THn0MjbeOuRaw$Yp1r$7$JwK;XQ#I0!&X>TTlIoTMHGF4p)-mPWrm zQr6Uz9W}JPvoiu%J`G(1_c~d)aF%icyYYm8@l`;ZYKE^9zdr1M_;*TFG$O0Z(FYhp zJ1RignkP!4cntcJ2ge8 zoE$7>@?lJR)ELXXPt&L`ffqDsgv<#|5*yV&Fz~9p{9zhF(>wkKdr9rK8{4e#82#5n zh~<@bK>3er5|j?*(b=h*X_~_}Jltv5J=j;~(=zBWV`ph?-H$5+1*rVPWa4`(?qmDG zHFw(CEzy`i&f932JTPM$La2S&mi_iq1xufml277g9zcHv73m)yE(av0vzju@P#N5n z@#xK@zoTDmn9wj@pC~urQL!~V&z?Q2rq(`vn%FFEk)n@9Oeyy7Pq>p$efaQs_SC~i zj`a2Reo$VO^gAhwFAc2|Xxvk;6&UHIWXx<6}|nE(qJur>miZfkQND` zA`#YdYTao1z|#)?8-I71i04gQ@p8;#u;gj3&w;p2aiHE%iQL~gLeS&(44~;-}?KP09w2icXf%4=!T$#dv|f+&%I;5 zlF`x8BkfsJvI{op{d{4y>tOGXMt(g79-e74P!eAPdm}Ca#qU!M<$Lx^^c0G-$8Nl5>_+BDh9A!^ex<_irQK|(3u3)Eb=E6 z6*o4gwl@6|64(KYd?aHCU&8xZA@US*1WCJx zgdBxMqvl@C)Kb#k9)|v_#eag>f9^*$vPTP(1PCSlSHk_3SG(}D0ieyYmKLS9=2)4d z&@|l?;|Eb6m^e9;UPko*5LDLILQLnpWI6gCmr$<=FONqERmr{wM*EaO#O4JcTRLFYvc>FRDe7-?n=~v+1-8PY>r;JaHZclaM1l%pcV30OXB?@BQg{c%s0_z(a zQnIpokcjnYCnq2(FflQCzJ7gB(4uR6UKG;nIT4X}1hTDUiqhgfM$|*}8LZ<7f*qrp z-%Hn*ot>KlIn{$?B;@=GhKDw^`A3r-$02cmKfmzu%Kk#!sM+mDq|ZMZIY^u%=_}Nw z)mQq0gk3#15HbKP#VhEpL19k_GX|m@FJw{3lb67?QMBb3x$t|LX6>*^jzEpBB0Wi% zsQ6xd5N6H+MFCuUwwda8=Gx>h_xY>?5mObNbGvW^vO}cHsu6MQv$pmb{1}RtqOtBg z5W!{rPl;Z6>}=IvN${W@ZKz>z7T7k^Ve(0zEQd--b?lgzr{^wzh7Z3-Kda{n!eM3U z*GNNaNlQy3QO+`_BaC3sImjze(Z2gL%DiTHfht7svR{SHk7IirMtQoVPqb7$x82j% zmnTca97@~iikF*TxoVy#wbp!IX}~i3)ur9T20fKn9dTCloGu(9YTN%(1loD>lLVB$|u)z#vwBh~b9iC>4EK(%!pEM?U6Vo&O#Qq&S z#rv@5``F1S=kF3A%D-=DudOb|f+>UQBN)L3raii5V39hInWxIU)^1s8LJZzhK{zDfL_1pxV% zq)Y|@#nt#Uaj2NRy;TtHhlYLvbKOtT+QwD5e1=ep0rFsvMvae~k_>J!7NwGrku9&R z^p1^HgDumt_W&A@2WQdv7H(X)c=%oAl@D+G`lvdF`)ZH7mkH-~q;YxQmIE26dH_{U z4c;OQ(rY(^hJ)|k2l;VhlnKTtupoLCK+~d47H&8jzHk{tO6pT+r>o$H~Z8f&~q{!c7qDFyBSzxvj@eZlF1LNZw|gXyjF$6CO^c5ruiI;#2L8oJ>>Rdbuw zfAPtIyX$f32_PFE0U!uaEP@v=Hx;xd9t^iKM5DI_9!eOwA3l5t7ju4x`zCSdGu649 zTHl{+Bg(R_fdL;lMOk^>@h~9^{Zg+(>>Bx@K+(cBmN}%Wn{iyhOHi(#v?CdYy+_v-FUpkJPM5suyXkM;tiM4>PYWnS{pVg*o;GjVa19i^_ z;A_!l1}&%Ik-DshKGs##)C@eM5PO)*#mE?|CKHy<^XcP9Zu@C1OUvDpB7f{Uz1kO_ z2^Df}E_EC|wNl|ca~T5OC6>WQLID8*_j-9H_0k>Y^x^W;YQ)}q1Et0K-yaswz55{I zGoY{BbS30*Imh_<$KO;qAZO?fRs}6DFTV~Ay|FLvA3Y&X;*D!EA#EuBT}e_-E%{(o zTH6?}lx(%bnADDI7sW|kFo~exlUI?|IyuQ%tvZl5UBaKD^5C-T`G-@7N$e~wSpvRv zM54hF#($o60`&v#V_*~w;v%1*qRru}v$=-cIuAM##aemUf+%~18;h#=K%lVb{ZQ>? zu3ja5yg0ti+ZtLJvIK%_vv7X9@Ibq=E-LIza7=F~$K!NAT;I?)5JgX7Y@N`-w-$jkss zbX8hd-+ne@&5$SdxGeYXBGkFR0_Gi|P`cmX4Vn{Gjanv+C5BM(`CtYMns)wv#PMgcH966x$_WlnAxTiINjg7%R=-zX#fB?UC`!oUv#Wup>srkmj} zZ>`w~PT}ISt7g9BDq8m^C?o-`9*^_H)&{cNJ6v=wNV*K)4j}^r6h-f{l;_Ya4jnyO zj;`8<#1HI0#UxK)7Q)UU{vXuTu<_#_w4u9V{r)-e``My;ni0L{fCg+nVw2eI-Cm2W zz$PFRLF5nvB_ooBhPICvwT9TPqS*6>$bF2+ujNkjDa_Q0~5`ve* ze&okR1!-fEGDFF&C6qdTLqax(`|$#WX7!!TdVWE{8@;!9HsB#-9>$W3>X1;Ge3SrU z+yk^U0JHEttaZZtj*D_pIpl;IYgLi=KO(FydFo4(F*J*MlqjP=pASJwv-$jC1at}( zAJJs+X8Bv2YfdYJA>?1S6EGH7Ghw$Mh-F}yT{1uT9ihDt`5o8lzD)ZWZ9o@?iHsK7 z;20PWveqW5CUxfg!JOhUZ z#rSWgS^PJwfHjWWLZ^u|WZwP!&JTFH?0<>6M0^vGszCiA83ETOEOa1N?CDQ8-}b!@ zJ}V}sVL1|{_m)VDWSA&jGrV@~TB>%LH*Yy27<|B5+OTt=ZChDe2c>KQQch1~brW6@ zgOoNHyL`OjL~i8g;o7iWhndm{35bqVc{CMQBZ7dk@C&g#N+C}j_UtDt&7Nlzte5xP zy?Ymq+0znJ_Raofv^K28-oMF&_;?xyW8s3Jn)|fP)7d4%JbzThiCtP1G z!7#c-x{LM)5h1jj_tfuIgxzK5BQBz=xJGcv^@5lUX$1Vdq!h z&Nis}YMk!-mJzZwu!?BG=})4%=s++blsi~*P?HEB#-CdbG`Gn(NF*}0x)3ftjj!Yt zEP@=I+6`HJ5FV)|D{B=M?f=2+*|QE@dhp=Gm7jA+UtVDE{p**(z%x|lqZDvf6DSAm zZd;j)+5d-pf95SuP)s}li<8_%ZBgPA8tKK^inHWzEFBBFip3MZDKs7@5=d87D^tM} zWdL&No@*I3Ha6O0fzoES?GgyA`Hj6+1q;U>8zJya>z(ioXMn4#SFavCeE8#XW?5Mo zOS^P}IRF9q(|w3CJ#zF7r2QQUhe>a80+G@_D9=^hi(8zj;owJppiT(qF*75~P_(zK z9e9LD6A@%m+$CS4mxWm3YwG!(#kiaB(w`+Kb7AY~f%&|o752x5SY4dJJXU!`CR>C- z<^Rd$8HI$hZsr9B23l1s#D4sk;7+6#C(xoS7_QMe$J@s3=2?_cAhZLHKeB!@QDwb2HE{y_9ySKI~cbZY&%{@h9E6GYyODeRfS~x|~*BUGmk|AfjLs{$*SrAKrDP-6aV!YbC;;Uf$ls zM!95}a^*!+ix`XX9)r5@UIf9eN=hOk#+YDE#BRf~=YnNvzImGKSd0X9M7!oBLu@r4 zefXoKgs9(~oSfj^0Z~!O%73B9l!A#QbOb(ga|`FR2+A}ED|?GB9QfE;EzcI&o^@_i zuB-7)eG?P1SSKXQXlo8*iCKnV9IQcmfm?|vTiIWwlb~hLjwt{WAYhN8r@Fr?CJ-=; zUxXm;K?(127^d_58;{(`S@e1evE?TjCM_a=XxIsHePC#aRX_zDaBs)dhe)Rmu;wt<$? zms)=Eo|}X;y9Io!Xz)nt)x4nVuz*K9^Ms5u-XV^4m$=-h+PqV$1&#tJX&2E1NXRcU zAn^qajrC9s;d|nfDlWoGIY>zf$$-P!xe#Fan|Y_o`B(dpe^1E|^2)L;OQtzBC`hi9 z+(NKUVU6x3N2xxJ!=lC2_GC*EM+M~;!;yn6EuqoOXe!->zPiUeu{gh zm7(%h&`c#~kKi`quOZl%BT0kMD8%uO)BAG&-&~kYiF*+O8K5<{)^`%HS6c5PO*mli zBqLFG`5u&z{x=(d9hlZ(nPK#Mr>m=LT%p?{E|JJ`4eO1E**MW zhTh(*`E_rtLv4rahBfpfR|1DuuwviIzuPD{18eRqAxZ*-5FLyV_z`a&-8lUV_BA|! ztNQw%Zn^uS9|6IZ;2YnKZ$kAMnI~ek-h|B#uh<^}!ke9_(8hDG{Xe$3=Ed-==XI`3sWO&a?i(wzq4cWP(8Y-uH2QOW zht#j=Yx`jQ-b5D~5RZPvcE9Z+j6}lX@Y^$6boUn?PX!@7}4zIA>*MCOvA9 z)+2Zy6rvYCK5TYTsi{0@F8Lda?Sn@+n?|`RSpO>jab_G$m9EPJ#ucZUk{Qb1bGnr8 zYuYF!3ikDwhLMPF2N$_qS|V<>Fvk? zTHYj`H}<`69aSmwMVVlRe$LV`l#`=`h!C=qvQL-~eixuu4J47TY64E{%a()-3h%(i z@sPT@I=1%5l$6&{H*dsjH*OCJ(zth5=$LvpTXC4Z2%VUdvO(Rw`@FiVIF9sj)*o7G zirSi*{X#-Qc!+f!>x_4}xN9U)jE){XihSGd!}UuOjj{U(cjb|%tO6@F41Im1Y)}Z( z4g4lkl5xa3B$eW_GBPw{d;9FV?HurrJoX=t{~C#9$aJ2O)YRg?Afz09p%?w!w8MyfrxmrUj2P zz~5j$q4ct=W#5ToSKI?3kBC9ymU0US3}U?zBA@i?KIXc!nJA^JOH+OjN0ACAfV@b1 zYE~8#TsrXAKp6(LX2|!q;f?r`*HXxJ zgvUzYAvlRIPl~<+dHI6jU`&Ct2B_hLmHEx0I}*YG zs-yp_jFwygi!pyptBZ4L0vsPV)f+b z=l?UER=kI+N?ay_%f5M2jJruhZ$=tpeJB%5J9D8!*uw#@sH5Wc^*_ z>Pob?y;DoD14KpPNN_RQ`@DKJghgwB6(sV*hiIpebTE~0s*4BHpAg=bKbAy`}iWFXRyNb*Qqt~`HE z8hguRbRsz1fY=eIPLW_?1Cb(5D2j?sV4xs!S6J6Xl7f(6^zz{4{zj}G!#DMi43G}l z;ceaT3>HWD$Z;xIm=|EG_CQq#ef##%i4)Q&MUbp|&=v_l4Kk1~p5`q~hCEg!R#^^Jjk2s1-N{fykyJ4PM}vk(t?iKmX3Ejj5R# z4?n*yB*m?@GraurtWT6xRmbFyAbNOKTs#WzCB6`Ih)5d_4kC=s!lcK+CnQu}T6&d< zXC?inTb8G;sc8*T$S^(_p=T(WgQ`vmLlD1nu}BvrEq|d|75$cIQ9$I=1L*)X;jJieNmoG#449H>05hgh*U7BIT8RP-CO;1m2L3clY{`|wGNp)QHABayr zw6!HF;b7*{d$?PHUG&VH!U8}Xutp?sjnEFt>*`)%ii277lAcoJfn(ex5QGSsL`_|t z&A9-?`E^*B>H{uiNaPP6KGZ_3z?1+|W^wzsAcgf04#raDBf~iZB9GwCDEzFm*I(&E zD-_A8g^7?Lf9x}}eLTBn7sB&CK;4wYZy%zhlrz8Vr5GQJ_!dHrs4_nR5_$Oe?oIv{ zdZ}k>`V!p;OsM7lj2+IIWOY4t?AS48Gx^-Oj57dkz)f?A2NUiiqrHr@^ch}WN~~gn zN=9TNw7<>UWbEC$cf9zfgU+Sp<>`RNd)qUy0E~yXmH>s|gvv@;ZJeU!g=qc?DkHIv z^6gVgKSE$kAA}!05xRTSQ*V(RFw1r%!N>sRna{f~axI1!l1>E z5?b*cwLELgMr0hx4{A!)xem1;km4hXegq0|8stkNT8YyQgiu63k@BMJm)F<(0WkAG zq%dB{RS5ieObhccAbqe6wxI;WV!P+@gb)!ynx6y&P;G5YxgtD4a5NOOZDn(oAolaY z9Pyu(YJdHD4OTIYK)QVPZNkO^e}ql1WoA~ZxjB23pzeE>=Dy0a#@Z!)JxPYXjrhd; zC4~b&U(rm0@$j{n#rVIG&;uX%5f} z{u91(*T}q0#=8ogiAhC5{K4=cIXQXgyLVO-e+9rlh};8i(Un9+0`9w@-^LZTaBy+? zDQP+fK~a}-8@v9!q+~lz_;uM>P#SfhQb??X!@}(AHE7HkVw6xyedhv<42Y7kCW2fQsZmkt<=^!GPKUa5wNU~JDelQy8gPvst zQAB#l85LYwz{_8w@SoG?*7kNbQDH{m=<$h(=<{0#6|Fw5{xgbo{-e+3Xnd zKrS5M%_cUs6L-+^(Idh=holhE^#FPiq)I@}0jSd@rKMnDq_J`9_|BMgNqICX2Mj`} zGL!S%5rxaY59}rU_*d6GWD^o*%PBOGL=1PO49R#olJYGzh9CmhwlTCwu|| zvCwa;22`J*wzJK>A0HnN%sI(eBrSu8B8iEK37UKyDN6d@vuDrl-@iX4D{C0dj3R#u z@#HUcb1?SmA5Rr1jauU`caxLrqiKp1_<}E%LV#J2wS)VHH8cuQ54}#SHRS;;;3Ac^ zCRnH85fN;(9*x|IXt%Pmx*;oD z*4lbZMn;D5Ntl?k$j_fYvGX2iYfB(cciwvF&kgjX{7CLTLDSi&%gy>5e| zfYr%TtO~T$?Yu;%p`k(CS_%pZj3hkTq_3)*c^rxoeOpoS2-aKK&z~MhhlowvN^kYB zfl_ui?fFH7f`Cf{+ofB?3hk!gZ!W&=nt%ucI!yksTAK#}li~ z*)?&b7JhAT{cqg9jWCzGL7l49`yk}{F{<+F)vKzb7*S zWZg%pfO+y6j3S81)0GhWc({&k&`}|_$kfynx91$@-8w#QhK3$P&g9dQ1L)@SjG!6nWI%mX#7b=;I{OC_x!6S!(1FW;zlOyl|xm z?O-q!rAoKhjSPZvrWy-e?}P#N=Fadk7Sd&Vs~bKMIH3hL&hWFo%Zd(1-QCotUrI`T6n#=6^}nvEy6XHUYkW!1xd3(z|fs0*3b> zRGUWd8&N`Qz8WjnJ2LW_)Vi*&ZqSOY=?_puP!}&cF48W<$U0y=5Ev;U^m>hE(2Eh@ zsS4S`Z>4_wHUN@Cfe3L7?27%zxSt?Mk^<+5Q!V>F-$k{|koaifJF@TiKVVu55*J%L z4XP|e7GbPgk!_8bsU$WPI=;wU0hZ970|)d_*pa-^HZpn<5kUtmtBry@sPP+oA1dg- z1MjP=>gx^WM;hS3Ej<0JhRtfGYQ@9NeJQ3P1tn9j>wQ6iDAHL-FdmYTsopF8jmSVi z01L9kOqm|Kn26{Zy~q6c(D4@w@wwp8s>GSOZmt-hKXn^>Q_26}pU-a+#G)!+jf0IMq)gGlpWD|Ox zgC@whb-yw^wWsGld#a!v(CY~1eV~wq6Z4|s;Wx3|yKPNSM70)&g&w$=QWrd48bUiz zAm{M*?Ch93W*Be4y1w_x$7!bQ zC=u5}hZOx?UXAjMtjllQv55X(o2wcs>YS10X|7wFv&HvY)4V-p=^5;43J-g5y&>Dz zxAFjRXTMMjHLPG_a@Oi$uMf{xFhcAiVd8{&W9i(@+jl3mu*n1#0+Jr@d_fIxA; zo(NAOUOJ1=8HU(zB9jgfQVNcBTYMvMYjdfH(B+U5qakP(wk<)C*o{FMj)KvFPXYqf zqr~A^K!`mJptL`l_zPUcM`$r2nl4|2l!mBMXZN-i?k?KeUOd;k8bIg!rs)$ta;eUp z++7JGAytt|sBuY3--tXX1P8*ZN-kVwg}5NHq^hEl2G^4yJy}^o$P+LK3M%Fl;f@li zLV9|7^RE0C$i$)T=z(_;Q#1H9fR@J)CBRqZ1%>ZN+Cb^6J`$|g+k}`ogR*d0iOl=& zPqL$J>BBp+?k8H??|DgcX>5{vikW}xRB`+7NkBkH>m(w@4nEA-5`vO~;V_KiS#7M^ zpm(E8Z6l^gtDIxGP}k)%hY=lu*^*<~zY}W_$*j4oqKyMretf-f2NKFngt-7$98D*z zHrE{@Hn^&BiLvmh=;&neBd+{ux4I2{W5Njr|C@t7LyVXUw>|&-3Dw>bZTE!wSJiFT zE=Kmq#y2%$QUDzElNviaJ1ZS|qSQ3h(0a7+Ag5^Lc8f9eOhZG1OMnUboj_m2Tr-qF z++BP=kF;8>He6{eJFFTatrxql12%!Q#RmWffD>pigtLNpT^x!iQlVf*z7Y`-X$3G( zF%QPf%zO$73dF!?DtB2J*p{qkPTSqvJ+jZP@B3%5b(;n%(GcC%Z^7$$(u2LdJtFP9 z4>R?db>#yc3XAv=Wd`;qF|CheI06jByvepp*ewvD{x~>EMgPVGLkgTI0IlhT1x5}I zU$h27q=j0fXhb|pgAX+CPSSYe*$ZFx9|-E34`N|uJ%g_a&o@{`cpEWSM%-ZNtgu{l zQDcEhN97M4lo$z#iqf2@I>DHbm^ik+#w#cYX!IOxKMdL8{|VF8j2$kD#GJ(gRFbNN zD(QLA-B7n6qy(~J-7trqa&J?Rho=}CA(2$I?#3XFhldAd*df6f zVICD-rm0O5IflT}0UU-Z((P>1=hSh2(bctlgQhTYw~N7hS*|@@k!0wTKYstdx2KC3 zk3Vn#lsT`1D;bI*BH6CWeNEAq`)K<$idA z@BonlV(S4q)PfU?tQnl=+DOsdQF6@SVbab52N*MFaWPU;As&I*Btiqgcp|)9#^SJp zO}bilW_qNtV)1TGk-D3B-U*B7Y20|s%pE*_T*}^lnJOaW@ZrPq{VHX*bC(j&At5>c z=_qECe&A)=BJuukBnBPZ=NBpz7z6}@@UKY8lWJsM^@89ZV#d?2$0-> z62rz8-)M$sJp4l7KX%$t_WXql5ZI!uVqdd1e!0hl>HbQD_Fp6>Cd%=v$I4k^fSs7b z1SgZ$){edn2cEhYxdFcSpXS`WSW_P2uD;pb=6hPuIu z&LtNtp15<7jEDpaba^BuasYZ3H#8gqMFR^vhCxo~o4b0@XkiAPK&lFrI!^xP7h-l1 zS!G2^c~w+5d1qye$>JE8}sHXrn4(ACOv?%qS3VbMn^G= z!+7@Wm6cu0wVj=Gc+r}g8eBR#x3kQgKjdu<(?NdF3LyCfx($$EHTEDiuuTgWLi){O z+BR(>aTCn`#de|sTanqXlbCOEo<9T#2FHVnmNwxcC)fee0f&R<)0#Z|yAXl9IhYBC zNt7j~cb|Ww?^ZR9C*E>*hvy`g>CDf^_XC7JQBid?CA6^X4YX8sM3fAd^*o! zY|0Mah$%}#zQGeSaA`%zhOonYfVn{R2`S6GQzS{FKpKStA>j|W{&>Ox-r(fm+%-JL zz;WIXlp4?YaLJ)}i}jR+@8+yW_Aw(v01rG#P|1)U&4J;099^ljxcDiU6>2I`-+uV} z<+db#`Xa&VSO**8Ei%Z&lp&D!`ube`phnF2T$M(BT^%^gPSn4IgamwF2GmnD1ZZ-o zCpxHGfbfK+il9eZo?SBURN{Cc^pD=bL5M1BDc`<<8;G9ZB?0p_W`Vf1gytOqH;d$H=sFdz?52GQr0Sw^tG%**Oj!v$i zk7F@D>3dK^nCeI)Juo+i1#t-d4Ttu!ZBsQPR}Y+*4Bk&h0xC5^YAx|ckC7@To+`iX zuUOZ=-56H>{^w61aB#srfv>2*s_473jWS$#@Kd!L**>I_c7oacM7@QwM~tnYCXBSE zo`sD9vG*VijSqGP06f%%R5W{HjtAy7VUl4To`$6btU}~g0Ts%@e{)>7T!I9`5LB;0 zHt!NLHiQ-*Vb}El&OCJ7|Es$zjfZ+)NUoC}YVC6~%POmZhXp zmtq>tb&f2P$db7zSsOW=v4)UTvNVbsNjIehp-W7Xr8M{ZKXvb$`{usBulV?kncx4n zJiq7pF3*g?-L@DeJ2V7A*|yfXECOfKv@*E(&=_;WQbA5e6=rZR>-{MK2Q*))Vg^LH6n~7e9c>oHlUS9)wrgKWyTRzF&GY8<$z(M zaCdrp@p;Wja%gC1|89HxZRqquh_C=I0)p-U0S=Qpx_((ij%GuVZ7j{nPU@V;q=(@=z8UZQe|Dxz@Ub#q>GeK`NJ);e=`Plhhgo*dlUo80@I z7L#~_v#FVx@vxx~;@4>++rI@`@_LxW3^BSZbqn~4J)!JcamptkEAq3zNTF4;B-T+pT zh&C9AirHyawZ}mkx1bIdTr_ARd?Eypt*)T~LjFWtl-(MyqeltE2a`fv*(g9IoZBj@ z6kh_*LF0pKJp)(s^4_gwBNFGJ>JZNILnjKK4V0}>R-^i5jT`gB9L>%f=lX_5UHN%7 z_vlL(rO)1CzCsenS+vVZNEq|{;eRb1Zy_#x3>ZavYuqXv|L~zjC={h_*~`R%K#Xn{ z3S%0g7nMULBL%vJutmZ`p{1jf>tgk+OR%pQG?z0dBxz}B_%Q08do$`^ChdQTV|{_g zi$r1#S?_V9iu;$()TKgEL*mU1~uzH$6>!AxMmuqjG?*y(jvrLz1rHV4W8OZA;_ zJBuMUD_=Jk{}_en3C(vD;wB3B(^=2Tz?-3#Cp?E>#2TQjG79@@f)WEMBq--Nls-g5 zFGItn^XJbeVBg>oRdBMtAkAEBPMihF>)X~1E&{$>qFe*d)d8>r@5n#xo&jM9!~;l; zQR#ibH;r?$3<$mfc_<7Bw6yUj*!RuG13Ml&jLh5_<5$$0F6fC+d@}?M&0XX%Amh=t zVQK_=&(hImNKqi;QMJEu{hc{mpJ+0ORD&Rqke(B6lvA}RIf_bCVCP zd|X}A+h(Df--1NPsVEFQE_;u$sa#%O9?S**^pRJrXcmbGC1C&d-4#3RO?jhx!ws_^ z8h+G|$k&j}bk%1}nSB8{+&-}$1m{}-B*-(y(P%~tF$eSF`CNleaHHV9p)zw0P;oQ4 zf%hWVFW=vUT>jp@_*HxU1_bYV z=;U9J0(M=ZFig~O3RJ0nV^4$h7-M=7iaAdnUm5NkC#odi=&L@>04z*AqxBs@DELur zsn=z`Muuaz?VqoJ$`;FHm-11?Hgcg`=@EWd4V7v3Pm(98Rp#I5~7D<6!J&p|qUiT2%Ir`LUxmgR5GDbm6XrIpVvEwQ3>gghc#iLo5Wal5a~C!Mb@b9h*UMkyXFicuS>VHl>_eqPQ61sEGUt ztu?o=1$P`W^2rTMQ2P4qU*9~k`8iVZ^vN693nztp7T|qCwQ!+`NSObqiz;>x?A8}H zwzsS;UmMsLw3k%pIQaK%%J@8gpQ%Mhxi1oF!{?I&C!N1GrXD!v;`zBi|A0c1-??jj z`DB~Abc-)vPizSA{}@pI;%U=0weHlCsv`PavI8my42b39TJP`h-azLn`xm3MSA7TQ zkGF@|Ooil8lfRFcyxTiys&|m{-n~WdbNlIqv5u*R8}lksl+JV3u?{6+JA8YHoFZ{HJIU%%?hnWLjrh*HrP(}cn@iFr)`VMg z0FQB^=%Ake^&9$WYA^HXy(EVYO>>b9o^iS11?!3YV{VIhjAu0N#iR^kX5(Gy!bLhk zb5$$WXV0$O=W^ouDty&U45eIDtN!!v*JenrqH+tG0F{Wny7N>1B?Nau>FKBY$NWke!*6uFZ z)THl|xkls_NI!Rv7p0h^{B4uaxL3K~R>IFfL-pbLrivO<;;a&n?lkr(M&F<~m0>{= ztK>@wsAq^>cDKWv5Ze9szWZ}9|G$4;eqS;-%yM}7zL1LLP{vBpt#(=#(LBTd1*l(R Ai~s-t literal 0 HcmV?d00001 diff --git a/doc/img/DATVDemod_pluginVideo.xcf b/doc/img/DATVDemod_pluginVideo.xcf new file mode 100644 index 0000000000000000000000000000000000000000..447c20bfd8e187a5cb429e34fbd82e5f4d9716da GIT binary patch literal 247272 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

k#UC#WoaNn>v+Jk*;cdF}lLB1GP# zQSI<`G=4z3HB}!{5^tUD);yg~h5vWvSQY6k$O$>shx`b4Yc|&9nGT#QjuRw@$ci6R z^YY&zfE)n(BJXUA#z!kMG{sNA6fwoqq)UoO6e$Kj3TD;2(liQ%>;=g zTh8<)bcoE*AzE>f5LM%dN#=JoI2xO24+2TN>hj_a;)aek;Jt7qUF3`jM06kj?puOGp)t>p!9h)Bj>#6Ai{i- z*P<5Oq%Gl!W-dmsGivqZ!g}89Q=TY6PT4mk{L*4{97!gR8vWW4_94?>CH9hyty z3I%-Dq7#kS9oK)_NXzkPg(Z77Vb|ZR&KM;ax%-Dp=L)GiRvLlA#UhAYo>u5@ynMFI{%sdLxmwQFY57aT96^U@j-lQRaz0(b{ zm%x^rn~w`%*_c5@FASLKamLLW)Uqfdb#&=C+UDAto8OS< zNX37M)QdIIK{8nMBGvW?se6jiFN{0zOB>8n`HrUz9Z!t+NQPMG@Z8V}MBnYTdmjV>9h_1AI- z(xD^QsE4?lYf+k3spV|3d|}6G{59NxG>uZj*<$!5F=~G`x8GLfRaHMmZ8Kt=B#>S{ zerbl$%UG|$QjARTOI7|VZvRoJU~<*t1Z(G#vh$z7)^yw)J$%=lr2kXM>M@x0?Ex;Bu`=eZpvx zR2%JbyVk_^K}F5a3kf6oU6R5i`@HP)vd`mQm+N`Co_~<*c}XF26Q6ZCQJwV+-rW7Dd@ucG9Kn)22Nv zJM~<(`E)Eho<1!g3l)MLHY}JVl7XQR=netF10x0oCi$8?q`L>|eRTDZ>edy{)b)3! zl)C!Fi?ubEXX|?Ce6;mcbGztmt+%!=tZLh+(er1~=L4Ny`;^wYcQ>!d&9Ajjbd1#c zXmb;?YAV)gzddyR=5)gsrRLOdt#*07<@^M#wtUN#LX!dN-d-B(-Y=;-x27mj%^=2zZrrxwwq>W+O89!OdKVeO4as5j) z+lp%o*0b#5Tx(gHiY<#{*-b}SKNc6;^RnZqLiRS<+hlJ;EhpD)4_w{m?WQBHWI3Z# zZ=CkJ`dGIoE}Fg>aF#nG5y`=El%+$U z-VlfASvJ%+)E1)BvTT@dm|d?4WfR#p#`~35q44rkC=@3E{mlbLKBZ9n6Vn&PbO5G| zkoKMen;N9OK%3ViqbH7?Hf6?S-imI@2RJ}0F-^1ii)l<0o!ru`-O_E`(rw++54)uw zL7Hw?`aOgM-5>ou4rp3QXZWA;NTHB$9mHEI0Q057AOQ(h0^2)I)FgX4-5td5DVhLLa+*K1&2T;$O47Hs!*o;fes)L z^aF!|0mOoMun?>QTfrfa39>*Tu%hhzKnD;A`hmf;ER!B>g5!6eZ(~vBspfE~xB06w4< zXbT<%kAo*bClCO-gC5{{5CmQV{lNh65AZKA6odd3(1GFL4KM~6!8kAp#DVD`5zGR! z!F;d?B!lH(CHNSu1slK?upR6Id%yu&mS_>_U3zDPmf+5L=r$gte8SyJ@8H|xpZu%u Gu>S(tGcF$h literal 0 HcmV?d00001 diff --git a/plugins/channelrx/demoddatv/readme.md b/plugins/channelrx/demoddatv/readme.md index fff354b9e..0fd2bc7b1 100644 --- a/plugins/channelrx/demoddatv/readme.md +++ b/plugins/channelrx/demoddatv/readme.md @@ -1,76 +1,136 @@

DATV demodulator plugin

-

Dependencies

+

Specific dependencies

- ffmpeg - libavcodec-dev - libavformat-dev +[LeanSDR](https://github.com/pabr/leansdr) framework from F4DAV is intensively used. It has been integrated in the source tree and modified to suit SDRangel specific needs. +

Introduction

-TBD... +This plugin can be used to view digital amateur analog television transmissions a.k.a DATV. The only supported standard for now is DVB-S in various modulations. The standard modulation is QPSK but experimental configurations with other PSK modulations (BPSK, 8PSK, QAMn) can be selected. -This plugin can be used to view amateur analog television transmissions a.k.a ATV. The video signal is in fact a 625 lines standard signal black and white or color (PAL, NTSC) but only the black and white levels (luminance) is retained. There is no provision to demodulate the audio subcarrier either. The modulation can be either AM or FM. - -The whole bandwidth available to the channel is used. That is it runs at the device sample rate possibly downsampled by a power of two in the source plugin. It expects an integer number of MS/s and acceptable results require a sample rate of at least 6 MS/s (Airspy Mini, Airspy, BladerRF, HackRF). +The whole bandwidth available to the channel is used. That is it runs at the device sample rate possibly downsampled by a power of two in the source plugin.

Interface

-![ATV Demodulator plugin GUI](../../../doc/img/ATVDemod_plugin.png) +![DATV Demodulator plugin GUI](../../../doc/img/DATVDemod_plugin.png) -

1: Image

+

A: RF settings

-This is where the TV image appears. +![DATV Demodulator plugin RF GUI](../../../doc/img/DATVDemod_pluginRF.png) -

2: Modulation

+

A.1: Channel frequency shift

- - FM1: this is Frequency Modulation with approximative demodulation algorithm not using atan2 - - FM2: this is Frequency Modulation with less approximative demodulation algorithm still not using atan2 - - AM: this is Amplitude Modulation +This is the shift of channel center frequency from RF passband center frequency + +

A.2: RF bandwidth

+ +Sets the bandwidth of the channel filter + +

A.3: Channel power

+ +Power of signal received in the channel (dB) + +

B: DATV section

+ +![DATV Demodulator plugin DATV GUI](../../../doc/img/DATVDemod_pluginDATV.png) + +

B.1: Symbol constellation

+ +This is the constellation of the PSK or QAM synchronized signal. When the demodulation parameters are set correctly (modulation type, symbol rate and filtering) and signal is strong enough to recover symbol synchronization the purple dots appear close to the white crosses. White crosses represent the ideal symbols positions in the I/Q plane. + +

B.2a: DATV signal settings

+ +![DATV Demodulator plugin DATV2 GUI](../../../doc/img/DATVDemod_pluginDATV2.png) + +
B.2a.1: DATV standard
+ +For now only the DVB-S standard is available + +
B.2a.2: Modulation type
+ + - BPSK: binary phase shift keying. Symbols are in π/4 and -3π/4 positions. + - QPSK: quadrature phase shift keying. Symbols are in π/4, 3π/4, -3π/4 and -π/4 positions. + - 8PSK: 8 phase shift keying a.k.a. π/4 QPSK. Symbols are in 0, π/4, π/2, 3π/4, π, -3π/4, -π/2 and -π/4 positions + - APSK16: amplitude and phase shift keying with 16 symbols + - APSK32: amplitude and phase shift keying with 32 symbols + - APSK64e: amplitude and phase shift keying with 64 symbols + - QAM16: quadrature amplitude modulation with 16 symbols + - QAM64: quadrature amplitude modulation with 64 symbols + - QAM256: quadrature amplitude modulation with 256 symbols -For FM choose the algorithm that best suits your conditions. +
B.2a.3: Symbol rate
-

3: Frames Per Second

+This controls the expected symbol rate -This combo lets you chose between a 25 FPS or 30 FPS standard. +
B.2a.4: FEC rate
-

4: Horizontal sync

+Choice between 1/2 , 2/3 , 3/4, 5/6 and 7/8. -Use this button to toggle horizontal synchronization processing. +
B.2a.5: Notch filter
-

5: Vertical sync

+LeanSDR feature: attempts to fix signal spectrum shape by eliminating peaks. This is the number of peaks to be tracked. It is safer to keep the 0 default (no notch). -Use this button to toggle vertical synchronization processing. +
B.2a.6: Fast lock
-

6: Half image

+Faster signal decode but may yield more errors at startup. -Use this button to disable (on) or enable interlacing of the two half images (off). +
B.2a.7: Allow drift
-

7: Reset defaults

+Small frequency drift compensation. -Use this push button to reset values to a standard setting: +
B.2a.8: Hard metric
- - FM1 modulation - - 25 FPS - - Horizontal and vertical syncs active - - Interlacing - - 100 mV sync level - - 310 mV black level - - 64 microsecond line length - - 3 microsecond sync length +Constellation hardening (LeanSDR feature). + +
B.2a.9: Viterbi
+ +Viterbi decoding. Be aware that this is CPU intensive. Should be limited to FEC 1/2 , 2/3 and 3/4 in practice. + +
B.2a.10: Reset to defaults
+ +Push this button when you are lost... + +
B.2a.11: Filter
+ + - FIR Linear + - FIR Nearest + - RRC (Root Raised Cosine): when selected additional controls for roll-off factor (%) and excursion (dB) are provided. + +
B.2a.12: Amount of data decoded
+ +Automatically adjusts unit (kB, MB, ...) + +
B.2a.13: Stream speed
+ +
B.2a.14: Buffer status
+ +Gauge that shows percentage of buffer queue length + +

B.2b: DATV video stream

+ +![DATV Demodulator plugin video GUI](../../../doc/img/DATVDemod_pluginVideo.png) + +
B.2b.1: Image thumbnail
+ +Use full screen button (5) to switch to full screen video + +
B.2b.2: Stream information
+ +
B.2b.3: Stream decoding status
+ +These non clickable checkboxes report the decoding status (checked when OK): + + - data: reception on going + - transport: transport stream detected + - video: video data detected + - decoding: video being decoded -

8: Synchronization level

+
B.2b.4: Play/pause video playback
+ +
B.2b.4: Full screen mode
-Use this slider to adjust the top level of the synchronization pulse on a 0 to 1V scale. The value in mV appears on the right of the slider. Nominal value: 100 mV. - -

9: Black level

- -Use this slider to adjust the black level of the video signal on a 0 to 1V scale. The value in mV appears on the right of the slider. Nominal value: 310 mV. - -

10: Line length

- -This is the line length in time units. The value in microseconds appears on the right of the slider. Nominal value: 64 microseconds. - -

10: Top length

- -This is the length in time units of a synchronization top. The value in microseconds appears on the right of the slider. Nominal value 3 microseconds. \ No newline at end of file +Click on this button to see video in full screen mode then click anywhere on the screen to exit full screen mode \ No newline at end of file From def91c40194526cf243665ba879346507aa07f3e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 13:00:33 +0100 Subject: [PATCH 096/956] Unify TV screen (2) --- plugins/channelrx/demodatv/CMakeLists.txt | 2 - plugins/channelrx/demodatv/atvscreen.cpp | 2 +- plugins/channelrx/demodatv/atvscreen.h | 4 +- plugins/channelrx/demodatv/demodatv.pro | 6 +- plugins/channelrx/demoddatv/CMakeLists.txt | 2 - plugins/channelrx/demoddatv/datvscreen.cpp | 2 +- plugins/channelrx/demoddatv/datvscreen.h | 4 +- plugins/channelrx/demoddatv/demoddatv.pro | 6 +- plugins/channelrx/demoddatv/glshaderarray.cpp | 299 ------------------ plugins/channelrx/demoddatv/glshaderarray.h | 76 ----- sdrgui/CMakeLists.txt | 2 + .../gui/glshadertvarray.cpp | 39 ++- .../gui/glshadertvarray.h | 15 +- sdrgui/sdrgui.pro | 2 + 14 files changed, 45 insertions(+), 416 deletions(-) delete mode 100644 plugins/channelrx/demoddatv/glshaderarray.cpp delete mode 100644 plugins/channelrx/demoddatv/glshaderarray.h rename plugins/channelrx/demodatv/glshaderarray.cpp => sdrgui/gui/glshadertvarray.cpp (87%) rename plugins/channelrx/demodatv/glshaderarray.h => sdrgui/gui/glshadertvarray.h (90%) diff --git a/plugins/channelrx/demodatv/CMakeLists.txt b/plugins/channelrx/demodatv/CMakeLists.txt index e92bc546e..83ac99570 100644 --- a/plugins/channelrx/demodatv/CMakeLists.txt +++ b/plugins/channelrx/demodatv/CMakeLists.txt @@ -6,7 +6,6 @@ set(atv_SOURCES atvdemodgui.cpp atvdemodplugin.cpp atvscreen.cpp - glshaderarray.cpp ) set(atv_HEADERS @@ -16,7 +15,6 @@ set(atv_HEADERS atvdemodplugin.h atvscreen.h atvscreeninterface.h - glshaderarray.h ) set(atv_FORMS diff --git a/plugins/channelrx/demodatv/atvscreen.cpp b/plugins/channelrx/demodatv/atvscreen.cpp index bba4aaf3d..359a623ef 100644 --- a/plugins/channelrx/demodatv/atvscreen.cpp +++ b/plugins/channelrx/demodatv/atvscreen.cpp @@ -28,7 +28,7 @@ #include ATVScreen::ATVScreen(QWidget* parent) : - QGLWidget(parent), ATVScreenInterface(), m_objMutex(QMutex::NonRecursive) + QGLWidget(parent), ATVScreenInterface(), m_objMutex(QMutex::NonRecursive), m_objGLShaderArray(false) { setAttribute(Qt::WA_OpaquePaintEvent); connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); diff --git a/plugins/channelrx/demodatv/atvscreen.h b/plugins/channelrx/demodatv/atvscreen.h index f2cf82c88..26c6a5bcd 100644 --- a/plugins/channelrx/demodatv/atvscreen.h +++ b/plugins/channelrx/demodatv/atvscreen.h @@ -28,7 +28,7 @@ #include #include #include "dsp/dsptypes.h" -#include "glshaderarray.h" +#include "gui/glshadertvarray.h" #include "gui/glshadertextured.h" #include "util/export.h" #include "util/bitfieldindex.h" @@ -69,7 +69,7 @@ private: bool m_blnDataChanged; bool m_blnConfigChanged; - GLShaderArray m_objGLShaderArray; + GLShaderTVArray m_objGLShaderArray; unsigned char *m_chrLastData; diff --git a/plugins/channelrx/demodatv/demodatv.pro b/plugins/channelrx/demodatv/demodatv.pro index 7ed97aae2..6d9f6d146 100644 --- a/plugins/channelrx/demodatv/demodatv.pro +++ b/plugins/channelrx/demodatv/demodatv.pro @@ -32,15 +32,13 @@ CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" SOURCES += atvdemod.cpp\ atvdemodgui.cpp\ atvdemodplugin.cpp\ - atvscreen.cpp\ - glshaderarray.cpp + atvscreen.cpp HEADERS += atvdemod.h\ atvdemodgui.h\ atvdemodplugin.h\ atvscreen.h\ - atvscreeninterface.h\ - glshaderarray.h + atvscreeninterface.h FORMS += atvdemodgui.ui diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt index cb123e52f..ca4229347 100644 --- a/plugins/channelrx/demoddatv/CMakeLists.txt +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -5,7 +5,6 @@ set(datv_SOURCES datvdemodgui.cpp datvdemodplugin.cpp datvscreen.cpp - glshaderarray.cpp datvideostream.cpp datvideorender.cpp ) @@ -15,7 +14,6 @@ set(datv_HEADERS datvdemodgui.h datvdemodplugin.h datvscreen.h - glshaderarray.h datvideostream.h datvideorender.h ) diff --git a/plugins/channelrx/demoddatv/datvscreen.cpp b/plugins/channelrx/demoddatv/datvscreen.cpp index 754539294..d7965f57c 100644 --- a/plugins/channelrx/demoddatv/datvscreen.cpp +++ b/plugins/channelrx/demoddatv/datvscreen.cpp @@ -28,7 +28,7 @@ #include DATVScreen::DATVScreen(QWidget* parent) : - QGLWidget(parent), m_objMutex(QMutex::NonRecursive) + QGLWidget(parent), m_objMutex(QMutex::NonRecursive), m_objGLShaderArray(true) { setAttribute(Qt::WA_OpaquePaintEvent); connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); diff --git a/plugins/channelrx/demoddatv/datvscreen.h b/plugins/channelrx/demoddatv/datvscreen.h index 3f9042b52..545703394 100644 --- a/plugins/channelrx/demoddatv/datvscreen.h +++ b/plugins/channelrx/demoddatv/datvscreen.h @@ -28,8 +28,8 @@ #include #include #include "dsp/dsptypes.h" -#include "glshaderarray.h" #include "gui/glshadertextured.h" +#include "gui/glshadertvarray.h" #include "util/export.h" #include "util/bitfieldindex.h" @@ -74,7 +74,7 @@ private: bool m_blnDataChanged; bool m_blnConfigChanged; - GLShaderArray m_objGLShaderArray; + GLShaderTVArray m_objGLShaderArray; void initializeGL(); void resizeGL(int width, int height); diff --git a/plugins/channelrx/demoddatv/demoddatv.pro b/plugins/channelrx/demoddatv/demoddatv.pro index a0b3cd352..b38c59fcd 100644 --- a/plugins/channelrx/demoddatv/demoddatv.pro +++ b/plugins/channelrx/demoddatv/demoddatv.pro @@ -31,15 +31,14 @@ CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" SOURCES += datvdemod.cpp\ datvdemodgui.cpp\ datvdemodplugin.cpp\ - datvscreen.cpp \ - glshaderarray.cpp \ + datvscreen.cpp \ datvideostream.cpp \ datvideorender.cpp HEADERS += datvdemod.h\ datvdemodgui.h\ datvdemodplugin.h\ - datvscreen.h \ + datvscreen.h \ leansdr/convolutional.h \ leansdr/dsp.h \ leansdr/dvb.h \ @@ -53,7 +52,6 @@ HEADERS += datvdemod.h\ leansdr/sdr.h \ leansdr/viterbi.h \ datvconstellation.h \ - glshaderarray.h \ datvvideoplayer.h \ datvideostream.h \ datvideorender.h diff --git a/plugins/channelrx/demoddatv/glshaderarray.cpp b/plugins/channelrx/demoddatv/glshaderarray.cpp deleted file mode 100644 index 6e09c57bb..000000000 --- a/plugins/channelrx/demoddatv/glshaderarray.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// 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 "glshaderarray.h" - -const QString GLShaderArray::m_strVertexShaderSourceArray = QString( - "uniform highp mat4 uMatrix;\n" - "attribute highp vec4 vertex;\n" - "attribute highp vec2 texCoord;\n" - "varying mediump vec2 texCoordVar;\n" - "void main() {\n" - " gl_Position = uMatrix * vertex;\n" - " texCoordVar = texCoord;\n" - "}\n"); - -const QString GLShaderArray::m_strFragmentShaderSourceColored = QString( - "uniform lowp sampler2D uTexture;\n" - "varying mediump vec2 texCoordVar;\n" - "void main() {\n" - " gl_FragColor = texture2D(uTexture, texCoordVar);\n" - "}\n"); - -GLShaderArray::GLShaderArray() -{ - m_objProgram = 0; - m_objImage = 0; - m_objTexture = 0; - m_intCols = 0; - m_intRows = 0; - m_blnInitialized = false; - m_objCurrentRow = 0; - - m_objTextureLoc = 0; - m_objColorLoc = 0; - m_objMatrixLoc = 0; -} - -GLShaderArray::~GLShaderArray() -{ - Cleanup(); -} - -void GLShaderArray::InitializeGL(int intCols, int intRows) -{ - QMatrix4x4 objQMatrix; - - m_blnInitialized = false; - - m_intCols = 0; - m_intRows = 0; - - m_objCurrentRow = 0; - - if (m_objProgram == 0) - { - m_objProgram = new QOpenGLShaderProgram(); - - if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, - m_strVertexShaderSourceArray)) - { - qDebug() << "GLShaderArray::initializeGL: error in vertex shader: " - << m_objProgram->log(); - } - - if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, - m_strFragmentShaderSourceColored)) - { - qDebug() - << "GLShaderArray::initializeGL: error in fragment shader: " - << m_objProgram->log(); - } - - m_objProgram->bindAttributeLocation("vertex", 0); - - if (!m_objProgram->link()) - { - qDebug() << "GLShaderArray::initializeGL: error linking shader: " - << m_objProgram->log(); - } - - m_objProgram->bind(); - m_objProgram->setUniformValue(m_objMatrixLoc, objQMatrix); - m_objProgram->setUniformValue(m_objTextureLoc, 0); - m_objProgram->release(); - } - - m_objMatrixLoc = m_objProgram->uniformLocation("uMatrix"); - m_objTextureLoc = m_objProgram->uniformLocation("uTexture"); - m_objColorLoc = m_objProgram->uniformLocation("uColour"); - - if (m_objTexture != 0) - { - delete m_objTexture; - m_objTexture = 0; - } - - //Image container - m_objImage = new QImage(intCols, intRows, QImage::Format_RGBA8888); - m_objImage->fill(QColor(0, 0, 0)); - - m_objTexture = new QOpenGLTexture(*m_objImage); - m_objTexture->setMinificationFilter(QOpenGLTexture::Linear); - m_objTexture->setMagnificationFilter(QOpenGLTexture::Linear); - m_objTexture->setWrapMode(QOpenGLTexture::ClampToEdge); - - m_intCols = intCols; - m_intRows = intRows; - - m_blnInitialized = true; - -} - -QRgb * GLShaderArray::GetRowBuffer(int intRow) -{ - if (!m_blnInitialized) - { - return 0; - } - - if (m_objImage == 0) - { - return 0; - } - - if (intRow > m_intRows) - { - return 0; - } - - return (QRgb *) m_objImage->scanLine(intRow); -} - -void GLShaderArray::RenderPixels(unsigned char *chrData) -{ - QOpenGLFunctions *ptrF; - int intI; - int intJ; - int intNbVertices = 6; - - QMatrix4x4 objQMatrix; - - GLfloat arrVertices[] = - // 2 3 - // 1 4 - //1 2 3 3 4 1 - { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; - - GLfloat arrTextureCoords[] = - // 1 4 - // 2 3 - //1 2 3 3 4 1 - { 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; - - QRgb *ptrLine; - - if (!m_blnInitialized) - { - return; - } - - if (m_objImage == 0) - { - return; - } - - if (chrData != 0) - { - for (intJ = 0; intJ < m_intRows; intJ++) - { - ptrLine = (QRgb *) m_objImage->scanLine(intJ); - - for (intI = 0; intI < m_intCols; intI++) - { - - *ptrLine = qRgb((int) (*(chrData+2)), (int) (*(chrData+1)), (int) (*chrData)); - ptrLine++; - - chrData+=3; - } - } - } - - //Affichage - ptrF = QOpenGLContext::currentContext()->functions(); - - m_objProgram->bind(); - - m_objProgram->setUniformValue(m_objMatrixLoc, objQMatrix); - m_objProgram->setUniformValue(m_objTextureLoc, 0); - - m_objTexture->bind(); - - ptrF->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_intCols, m_intRows, GL_RGBA, - GL_UNSIGNED_BYTE, m_objImage->bits()); - - ptrF->glEnableVertexAttribArray(0); // vertex - ptrF->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, arrVertices); - - ptrF->glEnableVertexAttribArray(1); // texture coordinates - ptrF->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, arrTextureCoords); - - ptrF->glDrawArrays(GL_TRIANGLES, 0, intNbVertices); - - //cleanup - ptrF->glDisableVertexAttribArray(0); - ptrF->glDisableVertexAttribArray(1); - - //*********************// - - m_objTexture->release(); - m_objProgram->release(); -} - -void GLShaderArray::ResetPixels() -{ - if (m_objImage != 0) - { - m_objImage->fill(0); - } -} - -void GLShaderArray::Cleanup() -{ - m_blnInitialized = false; - - m_intCols = 0; - m_intRows = 0; - - m_objCurrentRow = 0; - - if (m_objProgram) - { - delete m_objProgram; - m_objProgram = 0; - } - - if (m_objTexture != 0) - { - delete m_objTexture; - m_objTexture = 0; - } - - if (m_objImage != 0) - { - delete m_objImage; - m_objImage = 0; - } -} - -bool GLShaderArray::SelectRow(int intLine) -{ - bool blnRslt = false; - - if (m_blnInitialized) - { - if ((intLine < m_intRows) && (intLine >= 0)) - { - m_objCurrentRow = (QRgb *) m_objImage->scanLine(intLine); - blnRslt = true; - } - else - { - m_objCurrentRow = 0; - } - } - - return blnRslt; -} - -bool GLShaderArray::SetDataColor(int intCol, QRgb objColor) -{ - bool blnRslt = false; - - if (m_blnInitialized) - { - if ((intCol < m_intCols) && (intCol >= 0) && (m_objCurrentRow != 0)) - { - m_objCurrentRow[intCol] = objColor; - blnRslt = true; - } - } - - return blnRslt; -} - diff --git a/plugins/channelrx/demoddatv/glshaderarray.h b/plugins/channelrx/demoddatv/glshaderarray.h deleted file mode 100644 index 4c34969f3..000000000 --- a/plugins/channelrx/demoddatv/glshaderarray.h +++ /dev/null @@ -1,76 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// 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_GUI_GLSHADERARRAY_H_ -#define INCLUDE_GUI_GLSHADERARRAY_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class QOpenGLShaderProgram; -class QMatrix4x4; -class QVector4D; - -class GLShaderArray -{ -public: - GLShaderArray(); - ~GLShaderArray(); - - void InitializeGL(int intCols, int intRows); - void ResizeContainer(int intCols, int intRows); - void Cleanup(); - QRgb *GetRowBuffer(int intRow); - void RenderPixels(unsigned char *chrData); - void ResetPixels(); - - bool SelectRow(int intLine); - bool SetDataColor(int intCol,QRgb objColor); - - -protected: - - QOpenGLShaderProgram *m_objProgram; - int m_objMatrixLoc; - int m_objTextureLoc; - int m_objColorLoc; - static const QString m_strVertexShaderSourceArray; - static const QString m_strFragmentShaderSourceColored; - - QImage *m_objImage=NULL; - QOpenGLTexture *m_objTexture=NULL; - - int m_intCols; - int m_intRows; - - QRgb * m_objCurrentRow; - - bool m_blnInitialized; -}; - -#endif /* INCLUDE_GUI_GLSHADERARRAY_H_ */ diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 32a4aa078..cd9ead1fd 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -24,6 +24,7 @@ set(sdrgui_SOURCES gui/glscopemultigui.cpp gui/glshadersimple.cpp gui/glshadertextured.cpp + gui/glshadertvarray.cpp gui/glspectrum.cpp gui/glspectrumgui.cpp gui/indicator.cpp @@ -77,6 +78,7 @@ set(sdrgui_HEADERS gui/glscopenggui.h gui/glscopemultigui.h gui/glshadersimple.h + gui/glshadertvarray.h gui/glshadertextured.h gui/glspectrum.h gui/glspectrumgui.h diff --git a/plugins/channelrx/demodatv/glshaderarray.cpp b/sdrgui/gui/glshadertvarray.cpp similarity index 87% rename from plugins/channelrx/demodatv/glshaderarray.cpp rename to sdrgui/gui/glshadertvarray.cpp index f54041dc9..b0a45c190 100644 --- a/plugins/channelrx/demodatv/glshaderarray.cpp +++ b/sdrgui/gui/glshadertvarray.cpp @@ -15,9 +15,9 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "glshaderarray.h" +#include -const QString GLShaderArray::m_strVertexShaderSourceArray = QString( +const QString GLShaderTVArray::m_strVertexShaderSourceArray = QString( "uniform highp mat4 uMatrix;\n" "attribute highp vec4 vertex;\n" "attribute highp vec2 texCoord;\n" @@ -27,14 +27,14 @@ const QString GLShaderArray::m_strVertexShaderSourceArray = QString( " texCoordVar = texCoord;\n" "}\n"); -const QString GLShaderArray::m_strFragmentShaderSourceColored = QString( +const QString GLShaderTVArray::m_strFragmentShaderSourceColored = QString( "uniform lowp sampler2D uTexture;\n" "varying mediump vec2 texCoordVar;\n" "void main() {\n" " gl_FragColor = texture2D(uTexture, texCoordVar);\n" "}\n"); -GLShaderArray::GLShaderArray() +GLShaderTVArray::GLShaderTVArray(bool blnColor) : m_blnColor(blnColor) { m_objProgram = 0; m_objImage = 0; @@ -49,12 +49,12 @@ GLShaderArray::GLShaderArray() m_objMatrixLoc = 0; } -GLShaderArray::~GLShaderArray() +GLShaderTVArray::~GLShaderTVArray() { Cleanup(); } -void GLShaderArray::InitializeGL(int intCols, int intRows) +void GLShaderTVArray::InitializeGL(int intCols, int intRows) { QMatrix4x4 objQMatrix; @@ -124,7 +124,7 @@ void GLShaderArray::InitializeGL(int intCols, int intRows) } -QRgb * GLShaderArray::GetRowBuffer(int intRow) +QRgb * GLShaderTVArray::GetRowBuffer(int intRow) { if (!m_blnInitialized) { @@ -144,7 +144,7 @@ QRgb * GLShaderArray::GetRowBuffer(int intRow) return (QRgb *) m_objImage->scanLine(intRow); } -void GLShaderArray::RenderPixels(unsigned char *chrData) +void GLShaderTVArray::RenderPixels(unsigned char *chrData) { QOpenGLFunctions *ptrF; int intI; @@ -186,12 +186,19 @@ void GLShaderArray::RenderPixels(unsigned char *chrData) for (intI = 0; intI < m_intCols; intI++) { + if (m_blnColor) + { + *ptrLine = qRgb((int) (*(chrData+2)), (int) (*(chrData+1)), (int) (*chrData)); + chrData+=3; + } + else + { + intVal = (int) (*chrData); + *ptrLine = qRgb(intVal, intVal, intVal); + chrData++; + } - intVal = (int) (*chrData); - *ptrLine = qRgb(intVal, intVal, intVal); ptrLine++; - - chrData++; } } } @@ -227,7 +234,7 @@ void GLShaderArray::RenderPixels(unsigned char *chrData) m_objProgram->release(); } -void GLShaderArray::ResetPixels() +void GLShaderTVArray::ResetPixels() { if (m_objImage != 0) { @@ -235,7 +242,7 @@ void GLShaderArray::ResetPixels() } } -void GLShaderArray::Cleanup() +void GLShaderTVArray::Cleanup() { m_blnInitialized = false; @@ -263,7 +270,7 @@ void GLShaderArray::Cleanup() } } -bool GLShaderArray::SelectRow(int intLine) +bool GLShaderTVArray::SelectRow(int intLine) { bool blnRslt = false; @@ -283,7 +290,7 @@ bool GLShaderArray::SelectRow(int intLine) return blnRslt; } -bool GLShaderArray::SetDataColor(int intCol, QRgb objColor) +bool GLShaderTVArray::SetDataColor(int intCol, QRgb objColor) { bool blnRslt = false; diff --git a/plugins/channelrx/demodatv/glshaderarray.h b/sdrgui/gui/glshadertvarray.h similarity index 90% rename from plugins/channelrx/demodatv/glshaderarray.h rename to sdrgui/gui/glshadertvarray.h index 4c34969f3..b9804c547 100644 --- a/plugins/channelrx/demodatv/glshaderarray.h +++ b/sdrgui/gui/glshadertvarray.h @@ -15,8 +15,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_GUI_GLSHADERARRAY_H_ -#define INCLUDE_GUI_GLSHADERARRAY_H_ +#ifndef INCLUDE_GUI_GLTVSHADERARRAY_H_ +#define INCLUDE_GUI_GLTVSHADERARRAY_H_ #include #include @@ -36,11 +36,11 @@ class QOpenGLShaderProgram; class QMatrix4x4; class QVector4D; -class GLShaderArray +class GLShaderTVArray { public: - GLShaderArray(); - ~GLShaderArray(); + GLShaderTVArray(bool blnColor); + ~GLShaderTVArray(); void InitializeGL(int intCols, int intRows); void ResizeContainer(int intCols, int intRows); @@ -62,7 +62,7 @@ protected: static const QString m_strVertexShaderSourceArray; static const QString m_strFragmentShaderSourceColored; - QImage *m_objImage=NULL; + QImage *m_objImage=NULL; QOpenGLTexture *m_objTexture=NULL; int m_intCols; @@ -71,6 +71,7 @@ protected: QRgb * m_objCurrentRow; bool m_blnInitialized; + bool m_blnColor; }; -#endif /* INCLUDE_GUI_GLSHADERARRAY_H_ */ +#endif /* INCLUDE_GUI_GLTVSHADERARRAY_H_ */ diff --git a/sdrgui/sdrgui.pro b/sdrgui/sdrgui.pro index 194d7b396..2810484c8 100644 --- a/sdrgui/sdrgui.pro +++ b/sdrgui/sdrgui.pro @@ -64,6 +64,7 @@ SOURCES += mainwindow.cpp\ gui/glscopenggui.cpp\ gui/glshadersimple.cpp\ gui/glshadertextured.cpp\ + gui/glshadertvarray.cpp\ gui/glspectrum.cpp\ gui/glspectrumgui.cpp\ gui/indicator.cpp\ @@ -114,6 +115,7 @@ HEADERS += mainwindow.h\ gui/glscopenggui.h\ gui/glshadersimple.h\ gui/glshadertextured.h\ + gui/glshadertvarray.h\ gui/glspectrum.h\ gui/glspectrumgui.h\ gui/indicator.h\ From 707ce197cd4e27ed19f27b74ac6e959adfef2183 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 16:39:02 +0100 Subject: [PATCH 097/956] Unify TV screen (3) --- plugins/channelrx/demodatv/atvscreen.cpp | 8 ++++ plugins/channelrx/demodatv/atvscreen.h | 20 +++++----- plugins/channelrx/demoddatv/CMakeLists.txt | 2 - .../channelrx/demoddatv/datvconstellation.h | 6 +-- plugins/channelrx/demoddatv/datvdemod.cpp | 14 +++---- plugins/channelrx/demoddatv/datvdemod.h | 4 +- plugins/channelrx/demoddatv/datvdemodgui.cpp | 2 +- plugins/channelrx/demoddatv/datvdemodgui.ui | 13 +++--- .../channelrx/demoddatv/datvideorender.cpp | 4 +- plugins/channelrx/demoddatv/datvideorender.h | 4 +- plugins/channelrx/demoddatv/demoddatv.pro | 2 - sdrgui/CMakeLists.txt | 4 +- .../datvscreen.cpp => sdrgui/gui/tvscreen.cpp | 40 +++++++++---------- .../datvscreen.h => sdrgui/gui/tvscreen.h | 24 +++++------ 14 files changed, 78 insertions(+), 69 deletions(-) rename plugins/channelrx/demoddatv/datvscreen.cpp => sdrgui/gui/tvscreen.cpp (85%) rename plugins/channelrx/demoddatv/datvscreen.h => sdrgui/gui/tvscreen.h (86%) diff --git a/plugins/channelrx/demodatv/atvscreen.cpp b/plugins/channelrx/demodatv/atvscreen.cpp index 359a623ef..7e4b72c58 100644 --- a/plugins/channelrx/demodatv/atvscreen.cpp +++ b/plugins/channelrx/demodatv/atvscreen.cpp @@ -171,6 +171,14 @@ void ATVScreen::tick() } } +void ATVScreen::connectTimer(const QTimer& objTimer) +{ + qDebug() << "ATVScreen::connectTimer"; + disconnect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); + connect(&objTimer, SIGNAL(timeout()), this, SLOT(tick())); + m_objTimer.stop(); +} + void ATVScreen::cleanup() { if (m_blnGLContextInitialized) diff --git a/plugins/channelrx/demodatv/atvscreen.h b/plugins/channelrx/demodatv/atvscreen.h index 26c6a5bcd..7b64a3e36 100644 --- a/plugins/channelrx/demodatv/atvscreen.h +++ b/plugins/channelrx/demodatv/atvscreen.h @@ -28,8 +28,8 @@ #include #include #include "dsp/dsptypes.h" -#include "gui/glshadertvarray.h" #include "gui/glshadertextured.h" +#include "gui/glshadertvarray.h" #include "util/export.h" #include "util/bitfieldindex.h" @@ -50,9 +50,18 @@ public: virtual void resizeATVScreen(int intCols, int intRows); virtual void renderImage(unsigned char * objData); + QRgb* getRowBuffer(int intRow); + void resetImage(); + virtual bool selectRow(int intLine); virtual bool setDataColor(int intCol,int intRed, int intGreen, int intBlue); + void connectTimer(const QTimer& timer); + + //Valeurs par défaut + static const int ATV_COLS=192; + static const int ATV_ROWS=625; + signals: void traceSizeChanged(int); void sampleRateChanged(int); @@ -71,20 +80,13 @@ private: GLShaderTVArray m_objGLShaderArray; - unsigned char *m_chrLastData; - - //Valeurs par défaut - static const int ATV_COLS=192; - static const int ATV_ROWS=625; - void initializeGL(); void resizeGL(int width, int height); void paintGL(); void mousePressEvent(QMouseEvent*); - QRgb* getRowBuffer(int intRow); - void resetImage(); + unsigned char *m_chrLastData; protected slots: void cleanup(); diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt index ca4229347..f12dac3ff 100644 --- a/plugins/channelrx/demoddatv/CMakeLists.txt +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -4,7 +4,6 @@ set(datv_SOURCES datvdemod.cpp datvdemodgui.cpp datvdemodplugin.cpp - datvscreen.cpp datvideostream.cpp datvideorender.cpp ) @@ -13,7 +12,6 @@ set(datv_HEADERS datvdemod.h datvdemodgui.h datvdemodplugin.h - datvscreen.h datvideostream.h datvideorender.h ) diff --git a/plugins/channelrx/demoddatv/datvconstellation.h b/plugins/channelrx/demoddatv/datvconstellation.h index 2289eb010..9cd92ecf3 100644 --- a/plugins/channelrx/demoddatv/datvconstellation.h +++ b/plugins/channelrx/demoddatv/datvconstellation.h @@ -22,7 +22,7 @@ #include #include "leansdr/framework.h" -#include "datvscreen.h" +#include "gui/tvscreen.h" namespace leansdr { @@ -35,11 +35,11 @@ template struct datvconstellation: runnable unsigned long decimation; unsigned long pixels_per_frame; cstln_lut<256> **cstln; // Optional ptr to optional constellation - DATVScreen *m_objDATVScreen; + TVScreen *m_objDATVScreen; pipereader > in; unsigned long phase; - datvconstellation(scheduler *sch, pipebuf > &_in, T _xymin, T _xymax, const char *_name = NULL, DATVScreen * objDATVScreen = NULL) : + datvconstellation(scheduler *sch, pipebuf > &_in, T _xymin, T _xymax, const char *_name = 0, TVScreen *objDATVScreen = 0) : runnable(sch, _name ? _name : _in.name), xymin(_xymin), xymax(_xymax), diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index bf04aa6e6..25d78b408 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -39,8 +39,8 @@ DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_blnNeedConfigUpdate(false), m_deviceAPI(deviceAPI), - m_objRegisteredDATVScreen(NULL), - m_objRegisteredVideoRender(NULL), + m_objRegisteredTVScreen(0), + m_objRegisteredVideoRender(0), m_objVideoStream(NULL), m_objRenderThread(NULL), m_blnRenderingVideo(false), @@ -112,9 +112,9 @@ void DATVDemod::channelSampleRateChanged() } } -bool DATVDemod::SetDATVScreen(DATVScreen *objScreen) +bool DATVDemod::SetTVScreen(TVScreen *objScreen) { - m_objRegisteredDATVScreen = objScreen; + m_objRegisteredTVScreen = objScreen; return true; } @@ -694,11 +694,11 @@ void DATVDemod::InitDATVFramework() //constellation - if (m_objRegisteredDATVScreen) + if (m_objRegisteredTVScreen) { - m_objRegisteredDATVScreen->resizeDATVScreen(256,256); + m_objRegisteredTVScreen->resizeTVScreen(256,256); - r_scope_symbols = new leansdr::datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredDATVScreen); + r_scope_symbols = new leansdr::datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredTVScreen); r_scope_symbols->decimation = 1; r_scope_symbols->cstln = &m_objDemodulator->cstln; } diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 3c60368ef..dc2d36276 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -209,7 +209,7 @@ public: virtual void stop(); virtual bool handleMessage(const Message& cmd); - bool SetDATVScreen(DATVScreen *objScreen); + bool SetTVScreen(TVScreen *objScreen); DATVideostream * SetVideoRender(DATVideoRender *objScreen); bool PlayVideo(bool blnStartStop); @@ -441,7 +441,7 @@ private: DownChannelizer* m_channelizer; //*************** DATV PARAMETERS *************** - DATVScreen * m_objRegisteredDATVScreen; + TVScreen * m_objRegisteredTVScreen; DATVideoRender * m_objRegisteredVideoRender; DATVideostream * m_objVideoStream; DATVideoRenderThread * m_objRenderThread; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index c619e7917..0e124fd1a 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -266,7 +266,7 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba m_objDATVDemod = (DATVDemod*) rxChannel; m_objDATVDemod->setMessageQueueToGUI(getInputMessageQueue()); - m_objDATVDemod->SetDATVScreen(ui->screenTV); + m_objDATVDemod->SetTVScreen(ui->screenTV); connect(m_objDATVDemod->SetVideoRender(ui->screenTV_2),&DATVideostream::onDataPackets,this,&DATVDemodGUI::on_StreamDataAvailable); diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index 3393e12e9..fb0284f41 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -248,7 +248,7 @@ QLayout::SetMinimumSize - + 0 @@ -927,11 +927,6 @@
gui/rollupwidget.h
1 - - DATVScreen - QWidget -
datvscreen.h
-
DATVideoRender QWidget @@ -944,6 +939,12 @@
gui/valuedialz.h
1
+ + TVScreen + QWidget +
gui/tvscreen.h
+ 1 +
diff --git a/plugins/channelrx/demoddatv/datvideorender.cpp b/plugins/channelrx/demoddatv/datvideorender.cpp index c1e9b9525..32aa01f9e 100644 --- a/plugins/channelrx/demoddatv/datvideorender.cpp +++ b/plugins/channelrx/demoddatv/datvideorender.cpp @@ -18,7 +18,7 @@ #include "datvideorender.h" DATVideoRender::DATVideoRender(QWidget * parent): - DATVScreen(parent) + TVScreen(true, parent) { installEventFilter(this); m_blnIsFullScreen=false; @@ -488,7 +488,7 @@ bool DATVideoRender::RenderStream() //Rendering device setup - resizeDATVScreen(m_objFrame->width,m_objFrame->height); + resizeTVScreen(m_objFrame->width,m_objFrame->height); update(); resetImage(); diff --git a/plugins/channelrx/demoddatv/datvideorender.h b/plugins/channelrx/demoddatv/datvideorender.h index b3341b9a2..5603b6497 100644 --- a/plugins/channelrx/demoddatv/datvideorender.h +++ b/plugins/channelrx/demoddatv/datvideorender.h @@ -23,7 +23,7 @@ #include #include -#include "datvscreen.h" +#include "gui/tvscreen.h" #include "datvideostream.h" extern "C" @@ -85,7 +85,7 @@ struct DataTSMetaData2 } }; -class DATVideoRender : public DATVScreen +class DATVideoRender : public TVScreen { Q_OBJECT diff --git a/plugins/channelrx/demoddatv/demoddatv.pro b/plugins/channelrx/demoddatv/demoddatv.pro index b38c59fcd..c02e89ad5 100644 --- a/plugins/channelrx/demoddatv/demoddatv.pro +++ b/plugins/channelrx/demoddatv/demoddatv.pro @@ -31,14 +31,12 @@ CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" SOURCES += datvdemod.cpp\ datvdemodgui.cpp\ datvdemodplugin.cpp\ - datvscreen.cpp \ datvideostream.cpp \ datvideorender.cpp HEADERS += datvdemod.h\ datvdemodgui.h\ datvdemodplugin.h\ - datvscreen.h \ leansdr/convolutional.h \ leansdr/dsp.h \ leansdr/dvb.h \ diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index cd9ead1fd..a893a5e8a 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -41,6 +41,7 @@ set(sdrgui_SOURCES gui/tickedslider.cpp gui/transverterbutton.cpp gui/transverterdialog.cpp + gui/tvscreen.cpp gui/valuedial.cpp gui/valuedialz.cpp @@ -96,7 +97,8 @@ set(sdrgui_HEADERS gui/scaleengine.h gui/tickedslider.h gui/transverterbutton.h - gui/transverterdialog.h + gui/transverterdialog.h + gui/tvscreen.h gui/valuedial.h gui/valuedialz.h diff --git a/plugins/channelrx/demoddatv/datvscreen.cpp b/sdrgui/gui/tvscreen.cpp similarity index 85% rename from plugins/channelrx/demoddatv/datvscreen.cpp rename to sdrgui/gui/tvscreen.cpp index d7965f57c..c3c816e43 100644 --- a/plugins/channelrx/demoddatv/datvscreen.cpp +++ b/sdrgui/gui/tvscreen.cpp @@ -22,13 +22,13 @@ #include #include #include -#include "datvscreen.h" +#include "tvscreen.h" #include #include -DATVScreen::DATVScreen(QWidget* parent) : - QGLWidget(parent), m_objMutex(QMutex::NonRecursive), m_objGLShaderArray(true) +TVScreen::TVScreen(bool blnColor, QWidget* parent) : + QGLWidget(parent), m_objMutex(QMutex::NonRecursive), m_objGLShaderArray(blnColor) { setAttribute(Qt::WA_OpaquePaintEvent); connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); @@ -40,17 +40,17 @@ DATVScreen::DATVScreen(QWidget* parent) : m_blnGLContextInitialized = false; //Par défaut - m_intAskedCols = DATV_COLS; - m_intAskedRows = DATV_ROWS; + m_intAskedCols = TV_COLS; + m_intAskedRows = TV_ROWS; } -DATVScreen::~DATVScreen() +TVScreen::~TVScreen() { cleanup(); } -QRgb* DATVScreen::getRowBuffer(int intRow) +QRgb* TVScreen::getRowBuffer(int intRow) { if (!m_blnGLContextInitialized) { @@ -60,25 +60,25 @@ QRgb* DATVScreen::getRowBuffer(int intRow) return m_objGLShaderArray.GetRowBuffer(intRow); } -void DATVScreen::renderImage(unsigned char * objData) +void TVScreen::renderImage(unsigned char * objData) { m_chrLastData = objData; m_blnDataChanged = true; //update(); } -void DATVScreen::resetImage() +void TVScreen::resetImage() { m_objGLShaderArray.ResetPixels(); } -void DATVScreen::resizeDATVScreen(int intCols, int intRows) +void TVScreen::resizeTVScreen(int intCols, int intRows) { m_intAskedCols = intCols; m_intAskedRows = intRows; } -void DATVScreen::initializeGL() +void TVScreen::initializeGL() { m_objMutex.lock(); @@ -128,21 +128,21 @@ void DATVScreen::initializeGL() } connect(objGlCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, - &DATVScreen::cleanup); // TODO: when migrating to QOpenGLWidget + &TVScreen::cleanup); // TODO: when migrating to QOpenGLWidget m_blnGLContextInitialized = true; m_objMutex.unlock(); } -void DATVScreen::resizeGL(int intWidth, int intHeight) +void TVScreen::resizeGL(int intWidth, int intHeight) { QOpenGLFunctions *ptrF = QOpenGLContext::currentContext()->functions(); ptrF->glViewport(0, 0, intWidth, intHeight); m_blnConfigChanged = true; } -void DATVScreen::paintGL() +void TVScreen::paintGL() { if (!m_objMutex.tryLock(2)) return; @@ -161,18 +161,18 @@ void DATVScreen::paintGL() m_objMutex.unlock(); } -void DATVScreen::mousePressEvent(QMouseEvent* event __attribute__((unused))) +void TVScreen::mousePressEvent(QMouseEvent* event __attribute__((unused))) { } -void DATVScreen::tick() +void TVScreen::tick() { if (m_blnDataChanged) { update(); } } -void DATVScreen::connectTimer(const QTimer& objTimer) +void TVScreen::connectTimer(const QTimer& objTimer) { qDebug() << "DATVScreen::connectTimer"; disconnect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); @@ -180,7 +180,7 @@ void DATVScreen::connectTimer(const QTimer& objTimer) m_objTimer.stop(); } -void DATVScreen::cleanup() +void TVScreen::cleanup() { if (m_blnGLContextInitialized) { @@ -188,7 +188,7 @@ void DATVScreen::cleanup() } } -bool DATVScreen::selectRow(int intLine) +bool TVScreen::selectRow(int intLine) { if (m_blnGLContextInitialized) { @@ -200,7 +200,7 @@ bool DATVScreen::selectRow(int intLine) } } -bool DATVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue) +bool TVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue) { if (m_blnGLContextInitialized) { diff --git a/plugins/channelrx/demoddatv/datvscreen.h b/sdrgui/gui/tvscreen.h similarity index 86% rename from plugins/channelrx/demoddatv/datvscreen.h rename to sdrgui/gui/tvscreen.h index 545703394..93cfdd156 100644 --- a/plugins/channelrx/demoddatv/datvscreen.h +++ b/sdrgui/gui/tvscreen.h @@ -18,8 +18,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_DATVSCREEN_H -#define INCLUDE_DATVSCREEN_H +#ifndef INCLUDE_TVSCREEN_H +#define INCLUDE_TVSCREEN_H #include #include @@ -28,23 +28,23 @@ #include #include #include "dsp/dsptypes.h" -#include "gui/glshadertextured.h" -#include "gui/glshadertvarray.h" +#include "glshadertextured.h" +#include "glshadertvarray.h" #include "util/export.h" #include "util/bitfieldindex.h" class QPainter; -class DATVScreen: public QGLWidget +class TVScreen: public QGLWidget { Q_OBJECT public: - DATVScreen(QWidget* parent = NULL); - ~DATVScreen(); + TVScreen(bool blnColor, QWidget* parent = 0); + ~TVScreen(); - void resizeDATVScreen(int intCols, int intRows); + void resizeTVScreen(int intCols, int intRows); void renderImage(unsigned char * objData); QRgb* getRowBuffer(int intRow); void resetImage(); @@ -55,8 +55,8 @@ public: void connectTimer(const QTimer& timer); //Valeurs par défaut - static const int DATV_COLS=256; - static const int DATV_ROWS=256; + static const int TV_COLS=256; + static const int TV_ROWS=256; signals: void traceSizeChanged(int); @@ -82,11 +82,11 @@ private: void mousePressEvent(QMouseEvent*); - unsigned char *m_chrLastData; + unsigned char *m_chrLastData; protected slots: void cleanup(); void tick(); }; -#endif // INCLUDE_DATVSCREEN_H +#endif // INCLUDE_TVSCREEN_H From 6109be8eebe318cd0d597d19b4fa68331ca238e6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 17:11:02 +0100 Subject: [PATCH 098/956] Unify TV screen (4) --- plugins/channelrx/demodatv/CMakeLists.txt | 3 - plugins/channelrx/demodatv/atvdemod.cpp | 12 +- plugins/channelrx/demodatv/atvdemod.h | 24 +- plugins/channelrx/demodatv/atvdemodgui.cpp | 2 +- plugins/channelrx/demodatv/atvdemodgui.ui | 16 +- plugins/channelrx/demodatv/atvscreen.cpp | 209 ------------------ plugins/channelrx/demodatv/atvscreen.h | 96 -------- .../channelrx/demodatv/atvscreeninterface.h | 46 ---- plugins/channelrx/demodatv/demodatv.pro | 7 +- 9 files changed, 29 insertions(+), 386 deletions(-) delete mode 100644 plugins/channelrx/demodatv/atvscreen.cpp delete mode 100644 plugins/channelrx/demodatv/atvscreen.h delete mode 100644 plugins/channelrx/demodatv/atvscreeninterface.h diff --git a/plugins/channelrx/demodatv/CMakeLists.txt b/plugins/channelrx/demodatv/CMakeLists.txt index 83ac99570..5683fbbad 100644 --- a/plugins/channelrx/demodatv/CMakeLists.txt +++ b/plugins/channelrx/demodatv/CMakeLists.txt @@ -5,7 +5,6 @@ set(atv_SOURCES atvdemodsettings.cpp atvdemodgui.cpp atvdemodplugin.cpp - atvscreen.cpp ) set(atv_HEADERS @@ -13,8 +12,6 @@ set(atv_HEADERS atvdemodsettings.h atvdemodgui.h atvdemodplugin.h - atvscreen.h - atvscreeninterface.h ) set(atv_FORMS diff --git a/plugins/channelrx/demodatv/atvdemod.cpp b/plugins/channelrx/demodatv/atvdemod.cpp index 6855fd210..64b67cc71 100644 --- a/plugins/channelrx/demodatv/atvdemod.cpp +++ b/plugins/channelrx/demodatv/atvdemod.cpp @@ -42,7 +42,7 @@ ATVDemod::ATVDemod(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_scopeSink(0), - m_registeredATVScreen(0), + m_registeredTVScreen(0), m_intNumberSamplePerTop(0), m_intImageIndex(0), m_intSynchroPoints(0), @@ -103,9 +103,9 @@ ATVDemod::~ATVDemod() delete m_channelizer; } -void ATVDemod::setATVScreen(ATVScreenInterface *objScreen) +void ATVDemod::setTVScreen(TVScreen *objScreen) { - m_registeredATVScreen = objScreen; + m_registeredTVScreen = objScreen; } void ATVDemod::configure( @@ -607,10 +607,10 @@ void ATVDemod::applySettings() m_configPrivate.m_intNumberSamplePerLine = (int) (m_config.m_fltLineDuration * m_config.m_intSampleRate); m_intNumberSamplePerTop = (int) (m_config.m_fltTopDuration * m_config.m_intSampleRate); - if (m_registeredATVScreen) + if (m_registeredTVScreen) { - m_registeredATVScreen->setRenderImmediate(!(m_config.m_fltFramePerS > 25.0f)); - m_registeredATVScreen->resizeATVScreen( + //m_registeredTVScreen->setRenderImmediate(!(m_config.m_fltFramePerS > 25.0f)); + m_registeredTVScreen->resizeTVScreen( m_configPrivate.m_intNumberSamplePerLine - m_intNumberSamplePerLineSignals, m_intNumberOfLines - m_intNumberOfBlackLines); } diff --git a/plugins/channelrx/demodatv/atvdemod.h b/plugins/channelrx/demodatv/atvdemod.h index 22d348392..419c92623 100644 --- a/plugins/channelrx/demodatv/atvdemod.h +++ b/plugins/channelrx/demodatv/atvdemod.h @@ -37,7 +37,7 @@ #include "dsp/phasediscri.h" #include "audio/audiofifo.h" #include "util/message.h" -#include "atvscreeninterface.h" +#include "gui/tvscreen.h" class DeviceSourceAPI; class ThreadedBasebandSampleSink; @@ -231,7 +231,7 @@ public: virtual QByteArray serialize() const { return QByteArray(); } virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } - void setATVScreen(ATVScreenInterface *objScreen); + void setTVScreen(TVScreen *objScreen); int getSampleRate(); int getEffectiveSampleRate(); double getMagSq() const { return m_objMagSqAverage; } //!< Beware this is scaled to 2^30 @@ -413,7 +413,7 @@ private: SampleVector m_scopeSampleBuffer; //*************** ATV PARAMETERS *************** - ATVScreenInterface * m_registeredATVScreen; + TVScreen *m_registeredTVScreen; //int m_intNumberSamplePerLine; int m_intNumberSamplePerTop; @@ -497,7 +497,7 @@ private: inline void processHSkip(float& fltVal, int& intVal) { - m_registeredATVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop, intVal, intVal, intVal); + m_registeredTVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop, intVal, intVal, intVal); // Horizontal Synchro detection @@ -522,7 +522,7 @@ private: { //qDebug("VSync: %d %d %d", m_intColIndex, m_intSampleIndex, m_intLineIndex); m_intAvgColIndex = m_intColIndex; - m_registeredATVScreen->renderImage(0); + m_registeredTVScreen->renderImage(0); m_intImageIndex++; m_intLineIndex = 0; @@ -570,7 +570,7 @@ private: m_fltEffMax = -2000000.0f; } - m_registeredATVScreen->selectRow(m_intRowIndex); + m_registeredTVScreen->selectRow(m_intRowIndex); m_intLineIndex++; m_intRowIndex++; } @@ -658,7 +658,7 @@ private: if (m_intRowIndex < m_intNumberOfLines) { - m_registeredATVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); + m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); } m_intLineIndex++; @@ -667,7 +667,7 @@ private: // Filling pixels // +4 is to compensate shift due to hsync amortizing factor of 1/4 - m_registeredATVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop + 4, intVal, intVal, intVal); + m_registeredTVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop + 4, intVal, intVal, intVal); m_intColIndex++; // Vertical sync and image rendering @@ -686,7 +686,7 @@ private: if ((m_intLineIndex % 2 == 0) || !m_interleaved) // even => odd image { - m_registeredATVScreen->renderImage(0); + m_registeredTVScreen->renderImage(0); m_intRowIndex = 1; } else @@ -694,7 +694,7 @@ private: m_intRowIndex = 0; } - m_registeredATVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); + m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); m_intLineIndex = 0; m_intImageIndex++; } @@ -711,7 +711,7 @@ private: { if (m_intImageIndex % 2 == 1) // odd image { - m_registeredATVScreen->renderImage(0); + m_registeredTVScreen->renderImage(0); if (m_rfRunning.m_enmModulation == ATV_AM) { @@ -736,7 +736,7 @@ private: m_intRowIndex = 0; } - m_registeredATVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); + m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); m_intLineIndex = 0; m_intImageIndex++; } diff --git a/plugins/channelrx/demodatv/atvdemodgui.cpp b/plugins/channelrx/demodatv/atvdemodgui.cpp index c794eb420..c96727ec7 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.cpp +++ b/plugins/channelrx/demodatv/atvdemodgui.cpp @@ -285,7 +285,7 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base m_atvDemod = (ATVDemod*) rxChannel; //new ATVDemod(m_deviceUISet->m_deviceSourceAPI); m_atvDemod->setMessageQueueToGUI(getInputMessageQueue()); m_atvDemod->setScopeSink(m_scopeVis); - m_atvDemod->setATVScreen(ui->screenTV); + m_atvDemod->setTVScreen(ui->screenTV); ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index 0f4b2d106..84dd0db70 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -1011,7 +1011,7 @@ QTabWidget::West
- 1 + 0 @@ -1037,7 +1037,7 @@ QLayout::SetMinimumSize - + 0 @@ -1116,12 +1116,6 @@ QToolButton
gui/buttonswitch.h
- - ATVScreen - QWidget -
atvscreen.h
- 1 -
GLScopeNG QWidget @@ -1140,6 +1134,12 @@
gui/valuedialz.h
1
+ + TVScreen + QWidget +
gui/tvscreen.h
+ 1 +
diff --git a/plugins/channelrx/demodatv/atvscreen.cpp b/plugins/channelrx/demodatv/atvscreen.cpp deleted file mode 100644 index 7e4b72c58..000000000 --- a/plugins/channelrx/demodatv/atvscreen.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// OpenGL interface modernization. // -// // -// 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 -#include "atvscreen.h" - -#include -#include - -ATVScreen::ATVScreen(QWidget* parent) : - QGLWidget(parent), ATVScreenInterface(), m_objMutex(QMutex::NonRecursive), m_objGLShaderArray(false) -{ - setAttribute(Qt::WA_OpaquePaintEvent); - connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); - m_objTimer.start(40); // capped at 25 FPS - - m_chrLastData = NULL; - m_blnConfigChanged = false; - m_blnDataChanged = false; - m_blnGLContextInitialized = false; - - //Par défaut - m_intAskedCols = ATV_COLS; - m_intAskedRows = ATV_ROWS; -} - -ATVScreen::~ATVScreen() -{ - cleanup(); -} - -QRgb* ATVScreen::getRowBuffer(int intRow) -{ - if (m_blnGLContextInitialized == false) - { - return NULL; - } - - return m_objGLShaderArray.GetRowBuffer(intRow); -} - -void ATVScreen::renderImage(unsigned char * objData) -{ - m_chrLastData = objData; - m_blnDataChanged = true; - if (m_blnRenderImmediate) update(); -} - -void ATVScreen::resetImage() -{ - m_objGLShaderArray.ResetPixels(); -} - -void ATVScreen::resizeATVScreen(int intCols, int intRows) -{ - m_intAskedCols = intCols; - m_intAskedRows = intRows; -} - -void ATVScreen::initializeGL() -{ - m_objMutex.lock(); - - QOpenGLContext *objGlCurrentContext = QOpenGLContext::currentContext(); - - if (objGlCurrentContext) - { - if (QOpenGLContext::currentContext()->isValid()) - { - qDebug() << "ATVScreen::initializeGL: context:" - << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion() - << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion() - << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no"); - } - else - { - qDebug() << "ATVScreen::initializeGL: current context is invalid"; - } - } - else - { - qCritical() << "ATVScreen::initializeGL: no current context"; - return; - } - - QSurface *objSurface = objGlCurrentContext->surface(); - - if (objSurface == NULL) - { - qCritical() << "ATVScreen::initializeGL: no surface attached"; - return; - } - else - { - if (objSurface->surfaceType() != QSurface::OpenGLSurface) - { - qCritical() << "ATVScreen::initializeGL: surface is not an OpenGLSurface: " - << objSurface->surfaceType() - << " cannot use an OpenGL context"; - return; - } - else - { - qDebug() << "ATVScreen::initializeGL: OpenGL surface:" - << " class: " << (objSurface->surfaceClass() == QSurface::Window ? "Window" : "Offscreen"); - } - } - - connect(objGlCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, - &ATVScreen::cleanup); // TODO: when migrating to QOpenGLWidget - - m_blnGLContextInitialized = true; - - m_objMutex.unlock(); -} - -void ATVScreen::resizeGL(int intWidth, int intHeight) -{ - QOpenGLFunctions *ptrF = QOpenGLContext::currentContext()->functions(); - ptrF->glViewport(0, 0, intWidth, intHeight); - m_blnConfigChanged = true; -} - -void ATVScreen::paintGL() -{ - if (!m_objMutex.tryLock(2)) - return; - - m_blnDataChanged = false; - - if ((m_intAskedCols != 0) && (m_intAskedRows != 0)) - { - m_objGLShaderArray.InitializeGL(m_intAskedCols, m_intAskedRows); - m_intAskedCols = 0; - m_intAskedRows = 0; - } - - m_objGLShaderArray.RenderPixels(m_chrLastData); - - m_objMutex.unlock(); -} - -void ATVScreen::mousePressEvent(QMouseEvent* event __attribute__((unused))) -{ -} - -void ATVScreen::tick() -{ - if (m_blnDataChanged) { - update(); - } -} - -void ATVScreen::connectTimer(const QTimer& objTimer) -{ - qDebug() << "ATVScreen::connectTimer"; - disconnect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); - connect(&objTimer, SIGNAL(timeout()), this, SLOT(tick())); - m_objTimer.stop(); -} - -void ATVScreen::cleanup() -{ - if (m_blnGLContextInitialized) - { - m_objGLShaderArray.Cleanup(); - } -} - -bool ATVScreen::selectRow(int intLine) -{ - if (m_blnGLContextInitialized) - { - return m_objGLShaderArray.SelectRow(intLine); - } - - return false; -} - -bool ATVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue) -{ - if (m_blnGLContextInitialized) - { - return m_objGLShaderArray.SetDataColor(intCol, - qRgb(intRed, intGreen, intBlue)); - } - - return false; -} diff --git a/plugins/channelrx/demodatv/atvscreen.h b/plugins/channelrx/demodatv/atvscreen.h deleted file mode 100644 index 7b64a3e36..000000000 --- a/plugins/channelrx/demodatv/atvscreen.h +++ /dev/null @@ -1,96 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// OpenGL interface modernization. // -// See: http://doc.qt.io/qt-5/qopenglshaderprogram.html // -// // -// 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_ATVSCREEN_H -#define INCLUDE_ATVSCREEN_H - -#include -#include -#include -#include -#include -#include -#include "dsp/dsptypes.h" -#include "gui/glshadertextured.h" -#include "gui/glshadertvarray.h" -#include "util/export.h" -#include "util/bitfieldindex.h" - -#include "atvscreeninterface.h" - -class QPainter; - - - -class ATVScreen: public QGLWidget, public ATVScreenInterface -{ - Q_OBJECT - -public: - - ATVScreen(QWidget* parent = NULL); - virtual ~ATVScreen(); - - virtual void resizeATVScreen(int intCols, int intRows); - virtual void renderImage(unsigned char * objData); - QRgb* getRowBuffer(int intRow); - void resetImage(); - - virtual bool selectRow(int intLine); - virtual bool setDataColor(int intCol,int intRed, int intGreen, int intBlue); - - void connectTimer(const QTimer& timer); - - //Valeurs par défaut - static const int ATV_COLS=192; - static const int ATV_ROWS=625; - -signals: - void traceSizeChanged(int); - void sampleRateChanged(int); - -private: - bool m_blnGLContextInitialized; - int m_intAskedCols; - int m_intAskedRows; - - - // state - QTimer m_objTimer; - QMutex m_objMutex; - bool m_blnDataChanged; - bool m_blnConfigChanged; - - GLShaderTVArray m_objGLShaderArray; - - void initializeGL(); - void resizeGL(int width, int height); - void paintGL(); - - void mousePressEvent(QMouseEvent*); - - unsigned char *m_chrLastData; - -protected slots: - void cleanup(); - void tick(); -}; - -#endif // INCLUDE_ATVSCREEN_H diff --git a/plugins/channelrx/demodatv/atvscreeninterface.h b/plugins/channelrx/demodatv/atvscreeninterface.h deleted file mode 100644 index 3a16c59aa..000000000 --- a/plugins/channelrx/demodatv/atvscreeninterface.h +++ /dev/null @@ -1,46 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// OpenGL interface modernization. // -// See: http://doc.qt.io/qt-5/qopenglshaderprogram.html // -// // -// 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 PLUGINS_CHANNELRX_DEMODATV_ATVSCREENINTERFACE_H_ -#define PLUGINS_CHANNELRX_DEMODATV_ATVSCREENINTERFACE_H_ - -class ATVScreenInterface -{ -public: - ATVScreenInterface() : - m_blnRenderImmediate(false) - {} - - virtual ~ATVScreenInterface() {} - - virtual void resizeATVScreen(int intCols __attribute__((unused)), int intRows __attribute__((unused))) {} - virtual void renderImage(unsigned char * objData __attribute__((unused))) {} - virtual bool selectRow(int intLine __attribute__((unused))) { return false; } - virtual bool setDataColor(int intCol __attribute__((unused)), int intRed __attribute__((unused)), int intGreen __attribute__((unused)), int intBlue __attribute__((unused))) { return false; } - void setRenderImmediate(bool blnRenderImmediate) { m_blnRenderImmediate = blnRenderImmediate; } - -protected: - bool m_blnRenderImmediate; - -}; - - - -#endif /* PLUGINS_CHANNELRX_DEMODATV_ATVSCREENINTERFACE_H_ */ diff --git a/plugins/channelrx/demodatv/demodatv.pro b/plugins/channelrx/demodatv/demodatv.pro index 6d9f6d146..1b7811c02 100644 --- a/plugins/channelrx/demodatv/demodatv.pro +++ b/plugins/channelrx/demodatv/demodatv.pro @@ -31,14 +31,11 @@ CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" SOURCES += atvdemod.cpp\ atvdemodgui.cpp\ - atvdemodplugin.cpp\ - atvscreen.cpp + atvdemodplugin.cpp HEADERS += atvdemod.h\ atvdemodgui.h\ - atvdemodplugin.h\ - atvscreen.h\ - atvscreeninterface.h + atvdemodplugin.h FORMS += atvdemodgui.ui From 35138b0e9392c88cb15e9135c5255410cefdcaea Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 11 Mar 2018 19:43:40 +0100 Subject: [PATCH 099/956] Unify TV screen (5) --- plugins/channelrx/demodatv/atvdemodgui.cpp | 1 + plugins/channelrx/demoddatv/datvdemodgui.cpp | 1 + sdrgui/gui/glshadertvarray.h | 1 + sdrgui/gui/tvscreen.cpp | 5 +++++ sdrgui/gui/tvscreen.h | 1 + 5 files changed, 9 insertions(+) diff --git a/plugins/channelrx/demodatv/atvdemodgui.cpp b/plugins/channelrx/demodatv/atvdemodgui.cpp index c96727ec7..f4a31e7d2 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.cpp +++ b/plugins/channelrx/demodatv/atvdemodgui.cpp @@ -278,6 +278,7 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base m_inputSampleRate(48000) { ui->setupUi(this); + ui->screenTV->setColor(false); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index 0e124fd1a..21c22892e 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -260,6 +260,7 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba m_blnDoApplySettings(true) { ui->setupUi(this); + ui->screenTV->setColor(true); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); diff --git a/sdrgui/gui/glshadertvarray.h b/sdrgui/gui/glshadertvarray.h index b9804c547..d7b881140 100644 --- a/sdrgui/gui/glshadertvarray.h +++ b/sdrgui/gui/glshadertvarray.h @@ -42,6 +42,7 @@ public: GLShaderTVArray(bool blnColor); ~GLShaderTVArray(); + void setColor(bool blnColor) { m_blnColor = blnColor; } void InitializeGL(int intCols, int intRows); void ResizeContainer(int intCols, int intRows); void Cleanup(); diff --git a/sdrgui/gui/tvscreen.cpp b/sdrgui/gui/tvscreen.cpp index c3c816e43..729d1bacf 100644 --- a/sdrgui/gui/tvscreen.cpp +++ b/sdrgui/gui/tvscreen.cpp @@ -50,6 +50,11 @@ TVScreen::~TVScreen() cleanup(); } +void TVScreen::setColor(bool blnColor) +{ + m_objGLShaderArray.setColor(blnColor); +} + QRgb* TVScreen::getRowBuffer(int intRow) { if (!m_blnGLContextInitialized) diff --git a/sdrgui/gui/tvscreen.h b/sdrgui/gui/tvscreen.h index 93cfdd156..5676f23bc 100644 --- a/sdrgui/gui/tvscreen.h +++ b/sdrgui/gui/tvscreen.h @@ -44,6 +44,7 @@ public: TVScreen(bool blnColor, QWidget* parent = 0); ~TVScreen(); + void setColor(bool blnColor); void resizeTVScreen(int intCols, int intRows); void renderImage(unsigned char * objData); QRgb* getRowBuffer(int intRow); From de68eb360f0ef3c456f0bd16b420a005524bbc89 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 12 Mar 2018 05:07:51 +0100 Subject: [PATCH 100/956] DSD demod: GUI scope simplification (1) --- plugins/channelrx/demoddsd/dsddemod.cpp | 6 ++ plugins/channelrx/demoddsd/dsddemod.h | 2 + plugins/channelrx/demoddsd/dsddemodgui.cpp | 29 +++++- plugins/channelrx/demoddsd/dsddemodgui.h | 2 + plugins/channelrx/demoddsd/dsddemodgui.ui | 82 ++++++++------- sdrgui/CMakeLists.txt | 2 + sdrgui/dsp/scopevisxy.cpp | 111 +++++++++++++++++++++ sdrgui/dsp/scopevisxy.h | 64 ++++++++++++ sdrgui/gui/glshadertvarray.h | 1 + sdrgui/gui/tvscreen.cpp | 13 ++- sdrgui/gui/tvscreen.h | 3 + 11 files changed, 276 insertions(+), 39 deletions(-) create mode 100644 sdrgui/dsp/scopevisxy.cpp create mode 100644 sdrgui/dsp/scopevisxy.h diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 4c0e57874..d7f3dfc4e 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -54,6 +54,7 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : m_audioFifo1(48000), m_audioFifo2(48000), m_scope(0), + m_scopeXY(0), m_scopeEnabled(true), m_dsdDecoder(), m_settingsMutex(QMutex::Recursive) @@ -315,6 +316,11 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_scope->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth } + if ((m_scopeXY != 0) && (m_scopeEnabled)) + { + m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth + } + m_settingsMutex.unlock(); } diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index 046bab152..6a581ce2c 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -94,6 +94,7 @@ public: ~DSDDemod(); virtual void destroy() { delete this; } void setScopeSink(BasebandSampleSink* sampleSink) { m_scope = sampleSink; } + void setScopeXYSink(BasebandSampleSink* sampleSink) { m_scopeXY = sampleSink; } void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude); @@ -193,6 +194,7 @@ private: AudioFifo m_audioFifo1; AudioFifo m_audioFifo2; BasebandSampleSink* m_scope; + BasebandSampleSink* m_scopeXY; bool m_scopeEnabled; DSDDecoder m_dsdDecoder; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index ec6966bd4..4a9e52dd0 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -20,13 +20,11 @@ #include #include "device/deviceuiset.h" #include -#include -#include -#include #include "dsp/threadedbasebandsamplesink.h" #include "ui_dsddemodgui.h" #include "dsp/scopevis.h" +#include "dsp/scopevisxy.h" #include "gui/glscope.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" @@ -38,6 +36,12 @@ #include "dsddemodbaudrates.h" #include "dsddemod.h" +#include +#include +#include + +#include + DSDDemodGUI* DSDDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { DSDDemodGUI* gui = new DSDDemodGUI(pluginAPI, deviceUISet, rxChannel); @@ -270,14 +274,32 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_tickCount(0) { ui->setupUi(this); + ui->screenTV->setColor(true); + ui->screenTV->resizeTVScreen(200,200); + ui->screenTV->setRenderImmediate(true); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_scopeVis = new ScopeVis(SDR_RX_SCALEF, ui->glScope); + m_scopeVisXY = new ScopeVisXY(ui->screenTV); + m_scopeVisXY->setScale(2.0); + m_scopeVisXY->setPixelsPerFrame(4001); + m_scopeVisXY->setPlotRGB(qRgb(0, 220, 250)); + m_scopeVisXY->setGridRGB(qRgb(255, 255, 128)); + + for (float x = -0.84; x < 1.0; x += 0.56) + { + for (float y = -0.84; y < 1.0; y += 0.56) + { + m_scopeVisXY->addGraticulePoint(std::complex(x, y)); + } + } + m_dsdDemod = (DSDDemod*) rxChannel; //new DSDDemod(m_deviceUISet->m_deviceSourceAPI); m_dsdDemod->setScopeSink(m_scopeVis); + m_dsdDemod->setScopeXYSink(m_scopeVisXY); m_dsdDemod->setMessageQueueToGUI(getInputMessageQueue()); ui->glScope->setSampleRate(48000); @@ -329,6 +351,7 @@ DSDDemodGUI::~DSDDemodGUI() { m_deviceUISet->removeRxChannelInstance(this); delete m_dsdDemod; // TODO: check this: when the GUI closes it has to delete the demodulator + delete m_scopeVisXY; delete m_scopeVis; delete ui; } diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index c79648b44..a7269dfff 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -33,6 +33,7 @@ class PluginAPI; class DeviceUISet; class BasebandSampleSink; class ScopeVis; +class ScopeVisXY; class DSDDemod; namespace Ui { @@ -81,6 +82,7 @@ private: SignalFormat m_signalFormat; ScopeVis* m_scopeVis; + ScopeVisXY* m_scopeVisXY; DSDDemod* m_dsdDemod; bool m_enableCosineFiltering; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index c05e60e6f..244b5228d 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -62,16 +62,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -954,33 +945,50 @@ Discriminator Scope - - 2 - - - 2 - - - 2 - - + 2 - - - - 632 - 250 - - - - - Monospace - 8 - - - + + + + + + 0 + 0 + + + + + 432 + 200 + + + + + Monospace + 8 + + + + + + + + + 0 + 0 + + + + + 200 + 200 + + + + + @@ -1024,6 +1032,12 @@
gui/valuedialz.h
1 + + TVScreen + QWidget +
gui/tvscreen.h
+ 1 +
diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index a893a5e8a..149fff50a 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -48,6 +48,7 @@ set(sdrgui_SOURCES dsp/scopevis.cpp dsp/scopevisng.cpp dsp/scopevismulti.cpp + dsp/scopevisxy.cpp dsp/spectrumvis.cpp dsp/spectrumscopecombovis.cpp dsp/spectrumscopengcombovis.cpp @@ -105,6 +106,7 @@ set(sdrgui_HEADERS dsp/scopevis.h dsp/scopevisng.h dsp/scopevismulti.h + dsp/scopevisxy.h dsp/spectrumvis.h dsp/spectrumscopecombovis.h dsp/spectrumscopengcombovis.h diff --git a/sdrgui/dsp/scopevisxy.cpp b/sdrgui/dsp/scopevisxy.cpp new file mode 100644 index 000000000..2758417e5 --- /dev/null +++ b/sdrgui/dsp/scopevisxy.cpp @@ -0,0 +1,111 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 "scopevisxy.h" +#include "gui/tvscreen.h" +#include + +ScopeVisXY::ScopeVisXY(TVScreen *tvScreen) : + m_tvScreen(tvScreen), + m_scale(1.0), + m_cols(0), + m_rows(0), + m_pixelsPerFrame(480), + m_pixelCount(0), + m_plotRGB(qRgb(0, 255, 0)), + m_gridRGB(qRgb(255, 255 ,255)) +{ + setObjectName("ScopeVisXY"); +} + +ScopeVisXY::~ScopeVisXY() +{} + +void ScopeVisXY::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, + bool positiveOnly __attribute__((unused))) +{ + SampleVector::const_iterator begin(cbegin); + + while (begin < end) + { + float x = m_scale * (begin->m_real/SDR_RX_SCALEF); + float y = m_scale * (begin->m_imag/SDR_RX_SCALEF); + + int row = m_rows * ((1.0 - y) / 2.0); + int col = m_cols * ((1.0 + x) / 2.0); + + row = row < 0 ? 0 : row >= m_rows ? m_rows-1 : row; + col = col < 0 ? 0 : col >= m_cols ? m_cols-1 : col; + + m_tvScreen->selectRow(row); + m_tvScreen->setDataColor(col, qRed(m_plotRGB), qGreen(m_plotRGB), qBlue(m_plotRGB)); + m_pixelCount++; + + if (m_pixelCount == m_pixelsPerFrame) + { + drawGraticule(); + m_tvScreen->renderImage(0); + usleep(10000); + m_tvScreen->getSize(m_cols, m_rows); + m_tvScreen->resetImage(); + m_pixelCount = 0; + } + + ++begin; + } +} + +void ScopeVisXY::start() +{ +} + +void ScopeVisXY::stop() +{ +} + +bool ScopeVisXY::handleMessage(const Message& message __attribute__((unused))) +{ + return false; +} + +void ScopeVisXY::addGraticulePoint(const std::complex& z) { + m_graticule.push_back(z); +} + +void ScopeVisXY::clearGraticule() { + m_graticule.clear(); +} + +void ScopeVisXY::drawGraticule() +{ + std::vector >::const_iterator grIt = m_graticule.begin(); + + for (; grIt != m_graticule.end(); ++grIt) + { + int y = m_rows * ((1.0 - grIt->imag()) / 2.0); + int x = m_cols * ((1.0 + grIt->real()) / 2.0); + + for (int d = -4; d <= 4; ++d) + { + m_tvScreen->selectRow(y + d); + m_tvScreen->setDataColor(x, qRed(m_gridRGB), qGreen(m_gridRGB), qBlue(m_gridRGB)); + m_tvScreen->selectRow(y); + m_tvScreen->setDataColor(x + d, qRed(m_gridRGB), qGreen(m_gridRGB), qBlue(m_gridRGB)); + } + } +} + diff --git a/sdrgui/dsp/scopevisxy.h b/sdrgui/dsp/scopevisxy.h new file mode 100644 index 000000000..bea51e3c5 --- /dev/null +++ b/sdrgui/dsp/scopevisxy.h @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 SDRGUI_DSP_SCOPEVISXY_H_ +#define SDRGUI_DSP_SCOPEVISXY_H_ + +#include "dsp/basebandsamplesink.h" +#include "util/export.h" +#include "util/message.h" + +#include +#include +#include + +class TVScreen; + +class SDRGUI_API ScopeVisXY : public BasebandSampleSink { +public: + ScopeVisXY(TVScreen *tvScreen); + virtual ~ScopeVisXY(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& message); + + void setScale(float scale) { m_scale = scale; } + void setPixelsPerFrame(int pixelsPerFrame) { m_pixelsPerFrame = pixelsPerFrame; } + void setPlotRGB(const QRgb& plotRGB) { m_plotRGB = plotRGB; } + void setGridRGB(const QRgb& gridRGB) { m_gridRGB = gridRGB; } + + void addGraticulePoint(const std::complex& z); + void clearGraticule(); + +private: + void drawGraticule(); + + TVScreen *m_tvScreen; + float m_scale; + int m_cols; + int m_rows; + int m_pixelsPerFrame; + int m_pixelCount; + QRgb m_plotRGB; + QRgb m_gridRGB; + std::vector > m_graticule; +}; + + +#endif /* SDRGUI_DSP_SCOPEVISXY_H_ */ diff --git a/sdrgui/gui/glshadertvarray.h b/sdrgui/gui/glshadertvarray.h index d7b881140..408a62ef4 100644 --- a/sdrgui/gui/glshadertvarray.h +++ b/sdrgui/gui/glshadertvarray.h @@ -45,6 +45,7 @@ public: void setColor(bool blnColor) { m_blnColor = blnColor; } void InitializeGL(int intCols, int intRows); void ResizeContainer(int intCols, int intRows); + void getSize(int& intCols, int& intRows) const { intCols = m_intCols, intRows = m_intRows; } void Cleanup(); QRgb *GetRowBuffer(int intRow); void RenderPixels(unsigned char *chrData); diff --git a/sdrgui/gui/tvscreen.cpp b/sdrgui/gui/tvscreen.cpp index 729d1bacf..729421a06 100644 --- a/sdrgui/gui/tvscreen.cpp +++ b/sdrgui/gui/tvscreen.cpp @@ -38,6 +38,7 @@ TVScreen::TVScreen(bool blnColor, QWidget* parent) : m_blnConfigChanged = false; m_blnDataChanged = false; m_blnGLContextInitialized = false; + m_blnRenderImmediate = false; //Par défaut m_intAskedCols = TV_COLS; @@ -69,7 +70,10 @@ void TVScreen::renderImage(unsigned char * objData) { m_chrLastData = objData; m_blnDataChanged = true; - //update(); + + if (m_blnRenderImmediate) { + update(); + } } void TVScreen::resetImage() @@ -83,6 +87,11 @@ void TVScreen::resizeTVScreen(int intCols, int intRows) m_intAskedRows = intRows; } +void TVScreen::getSize(int& intCols, int& intRows) const +{ + m_objGLShaderArray.getSize(intCols, intRows); +} + void TVScreen::initializeGL() { m_objMutex.lock(); @@ -209,7 +218,7 @@ bool TVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue) { if (m_blnGLContextInitialized) { - return m_objGLShaderArray.SetDataColor(intCol, qRgb(intRed, intGreen, intBlue)); + return m_objGLShaderArray.SetDataColor(intCol, qRgb(intBlue, intGreen, intRed)); // FIXME: blue <> red inversion in shader } else { diff --git a/sdrgui/gui/tvscreen.h b/sdrgui/gui/tvscreen.h index 5676f23bc..da19c653d 100644 --- a/sdrgui/gui/tvscreen.h +++ b/sdrgui/gui/tvscreen.h @@ -46,12 +46,14 @@ public: void setColor(bool blnColor); void resizeTVScreen(int intCols, int intRows); + void getSize(int& intCols, int& intRows) const; void renderImage(unsigned char * objData); QRgb* getRowBuffer(int intRow); void resetImage(); bool selectRow(int intLine); bool setDataColor(int intCol,int intRed, int intGreen, int intBlue); + void setRenderImmediate(bool blnRenderImmediate) { m_blnRenderImmediate = blnRenderImmediate; } void connectTimer(const QTimer& timer); @@ -74,6 +76,7 @@ private: QMutex m_objMutex; bool m_blnDataChanged; bool m_blnConfigChanged; + bool m_blnRenderImmediate; GLShaderTVArray m_objGLShaderArray; From 1728f91d15b4daa88f1d01e5c0ec23cfc3fa76cf Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 12 Mar 2018 05:23:09 +0100 Subject: [PATCH 101/956] DSD demod: GUI scope simplification (2) --- plugins/channelrx/demoddsd/dsddemod.cpp | 7 -- plugins/channelrx/demoddsd/dsddemod.h | 2 - plugins/channelrx/demoddsd/dsddemodgui.cpp | 11 --- plugins/channelrx/demoddsd/dsddemodgui.h | 1 - plugins/channelrx/demoddsd/dsddemodgui.ui | 79 +++++-------------- plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- 6 files changed, 20 insertions(+), 82 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index d7f3dfc4e..e3eecc3e9 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -53,7 +53,6 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : m_squelchOpen(false), m_audioFifo1(48000), m_audioFifo2(48000), - m_scope(0), m_scopeXY(0), m_scopeEnabled(true), m_dsdDecoder(), @@ -310,12 +309,6 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto // } } - - if ((m_scope != 0) && (m_scopeEnabled)) - { - m_scope->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth - } - if ((m_scopeXY != 0) && (m_scopeEnabled)) { m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index 6a581ce2c..acf1f25ce 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -93,7 +93,6 @@ public: DSDDemod(DeviceSourceAPI *deviceAPI); ~DSDDemod(); virtual void destroy() { delete this; } - void setScopeSink(BasebandSampleSink* sampleSink) { m_scope = sampleSink; } void setScopeXYSink(BasebandSampleSink* sampleSink) { m_scopeXY = sampleSink; } void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude); @@ -193,7 +192,6 @@ private: AudioFifo m_audioFifo1; AudioFifo m_audioFifo2; - BasebandSampleSink* m_scope; BasebandSampleSink* m_scopeXY; bool m_scopeEnabled; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 4a9e52dd0..a509e3201 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -282,7 +282,6 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); - m_scopeVis = new ScopeVis(SDR_RX_SCALEF, ui->glScope); m_scopeVisXY = new ScopeVisXY(ui->screenTV); m_scopeVisXY->setScale(2.0); m_scopeVisXY->setPixelsPerFrame(4001); @@ -298,15 +297,9 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban } m_dsdDemod = (DSDDemod*) rxChannel; //new DSDDemod(m_deviceUISet->m_deviceSourceAPI); - m_dsdDemod->setScopeSink(m_scopeVis); m_dsdDemod->setScopeXYSink(m_scopeVisXY); m_dsdDemod->setMessageQueueToGUI(getInputMessageQueue()); - ui->glScope->setSampleRate(48000); - m_scopeVis->setSampleRate(48000); - - ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); - connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); @@ -337,10 +330,7 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); - ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); - m_settings.setChannelMarker(&m_channelMarker); - m_settings.setScopeGUI(ui->scopeGUI); updateMyPosition(); displaySettings(); @@ -352,7 +342,6 @@ DSDDemodGUI::~DSDDemodGUI() m_deviceUISet->removeRxChannelInstance(this); delete m_dsdDemod; // TODO: check this: when the GUI closes it has to delete the demodulator delete m_scopeVisXY; - delete m_scopeVis; delete ui; } diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index a7269dfff..ea2669e6f 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -81,7 +81,6 @@ private: char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text SignalFormat m_signalFormat; - ScopeVis* m_scopeVis; ScopeVisXY* m_scopeVisXY; DSDDemod* m_dsdDemod; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index 244b5228d..06ed22c57 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -7,7 +7,7 @@ 0 0 686 - 841 + 397
@@ -931,14 +931,14 @@ 10 180 - 636 - 651 + 200 + 200 - 636 - 0 + 200 + 200 @@ -949,49 +949,20 @@ 2 - - - - - - 0 - 0 - - - - - 432 - 200 - - - - - Monospace - 8 - - - - - - - - - 0 - 0 - - - - - 200 - 200 - - - - - - - - + + + + 0 + 0 + + + + + 200 + 200 + + +
@@ -1014,18 +985,6 @@
gui/levelmeter.h
1 - - GLScope - QWidget -
gui/glscope.h
- 1 -
- - GLScopeGUI - QWidget -
gui/glscopegui.h
- 1 -
ValueDialZ QWidget diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index a4c96b3fe..e88f2401f 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("3.12.0"), + QString("3.13.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 47036d8c3ac7c278e33355123d2b7801d6827717 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 12 Mar 2018 05:27:21 +0100 Subject: [PATCH 102/956] DSD demod: GUI scope simplification (3) --- plugins/channelrx/demoddsd/dsddemodgui.ui | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index 06ed22c57..cf4a3f097 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -7,7 +7,7 @@ 0 0 686 - 397 + 414 @@ -926,25 +926,25 @@
- + 10 180 - 200 - 200 + 218 + 218 - 200 - 200 + 210 + 210 Discriminator Scope - + 2 From e317a0bbea6c1bb0216f1517263d11232a0ec515 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 12 Mar 2018 09:18:21 +0100 Subject: [PATCH 103/956] DSD demod: GUI scope simplification (4) --- Readme.md | 2 +- plugins/channelrx/demoddsd/dsddemodgui.ui | 985 ++++++++++++---------- 2 files changed, 534 insertions(+), 453 deletions(-) diff --git a/Readme.md b/Readme.md index 6559dfa3d..014d269dc 100644 --- a/Readme.md +++ b/Readme.md @@ -117,7 +117,7 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3.

LimeSDR

-

⚠ Source code only. Recent versions of Lime Suite library are crap so official support cannot be maintained. You can still compile the plugins from source and see for yourself

+

⚠ Source code only. Due to instability of recent versions of Lime Suite the "official" support cannot be maintained. You can still compile the plugins from source and see for yourself

[LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next). diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index cf4a3f097..3c8758fb0 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -6,8 +6,8 @@ 0 0 - 686 - 414 + 610 + 392 @@ -18,7 +18,7 @@ - 686 + 610 0 @@ -39,7 +39,7 @@ 0 0 - 684 + 608 172 @@ -51,7 +51,7 @@ - 684 + 608 0 @@ -131,6 +131,48 @@
+ + + + RFBW + + + + + + + Bandwidth (kHz) before discriminator + + + 500 + + + 1 + + + 100 + + + Qt::Horizontal + + + + + + + + 35 + 0 + + + + 0.00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -144,210 +186,6 @@ - - - - - 0 - 0 - - - - - 35 - 0 - - - - - 16777215 - 16777215 - - - - Baud rate: 2.4k: NXDN48, dPMR 4.8k: DMR, D-Star, YSF, NXDN96 - - - - 2.4k - - - - - 4.8k - - - - - - - - - 110 - 0 - - - - - 16777215 - 25 - - - - - DejaVu Sans Mono - 9 - - - - Synchronized on this frame type - - - QFrame::Box - - - QFrame::Sunken - - - 2 - - - No Sync______ - - - - - - - true - - - Symbol PLL toggle (green: PLL locked) - - - ... - - - - :/unlocked.png - :/locked.png:/unlocked.png - - - true - - - true - - - - - - - - 25 - 0 - - - - Symbol synchronization rate (%) - - - 000 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 25 - 0 - - - - Zero crossing relative position in number of samples (<0 sampling point lags, >0 it leads) - - - -00 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Enable cosine filtering - - - - - - - :/dsb.png:/dsb.png - - - - - - - Toggle between transition constellation and symbol synchronization displays - - - - - - - :/constellation.png - :/slopep_icon.png:/constellation.png - - - true - - - - - - - - 25 - 0 - - - - Carrier relative position (%) when synchronized - - - -00 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 25 - 0 - - - - Carrier input level (%) when synchronized - - - 000 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -415,133 +253,7 @@ - - - - - RFBW - - - - - - - Bandwidth (kHz) before discriminator - - - 500 - - - 1 - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 00.0k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 10 - 0 - - - - - - - - - - - Gain - - - - - - - - 0 - 0 - - - - Gain after discriminator - - - 50 - - - 400 - - - 1 - - - 100 - - - Qt::Horizontal - - - - - - - - 35 - 0 - - - - 0.00 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - + @@ -596,119 +308,6 @@ - - - - - - - - FMd - - - - - - - Maximum frequency deviation (kHz) - - - 500 - - - 1 - - - 50 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 00.0k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - TDMA slot1 or FDMA unique slot voice on/off - - - - - - - :/slot1_off.png - :/slot1_on.png:/slot1_off.png - - - true - - - - - - - TDMA slot2 voice on/off - - - - - - - :/slot2_off.png - :/slot2_on.png:/slot2_off.png - - - true - - - - - - - Split TDMA channels on left (slot 1) and right (slot 2) audio stereo channels or merge as mono - - - - - - - :/mono.png - :/stereo.png:/mono.png - - - true - - - @@ -816,6 +415,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -931,13 +543,13 @@ 10 180 - 218 - 218 + 600 + 210 - 210 + 600 210 @@ -964,6 +576,475 @@ + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 10 + 10 + 59 + 20 + + + + + 0 + 0 + + + + + 35 + 0 + + + + + 16777215 + 16777215 + + + + Baud rate: 2.4k: NXDN48, dPMR 4.8k: DMR, D-Star, YSF, NXDN96 + + + + 2.4k + + + + + 4.8k + + + + + + + 80 + 10 + 110 + 25 + + + + + 110 + 0 + + + + + 16777215 + 25 + + + + + DejaVu Sans Mono + 9 + + + + Synchronized on this frame type + + + QFrame::Box + + + QFrame::Sunken + + + 2 + + + No Sync______ + + + + + true + + + + 200 + 10 + 23 + 22 + + + + Symbol PLL toggle (green: PLL locked) + + + ... + + + + :/unlocked.png + :/locked.png:/unlocked.png + + + true + + + true + + + + + + 10 + 40 + 25 + 28 + + + + + 25 + 0 + + + + Symbol synchronization rate (%) + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 40 + 40 + 25 + 28 + + + + + 25 + 0 + + + + Zero crossing relative position in number of samples (<0 sampling point lags, >0 it leads) + + + -00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 80 + 40 + 23 + 22 + + + + Enable cosine filtering + + + + + + + :/dsb.png:/dsb.png + + + + + + 110 + 40 + 25 + 28 + + + + + 25 + 0 + + + + Carrier relative position (%) when synchronized + + + -00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 150 + 40 + 25 + 28 + + + + + 25 + 0 + + + + Carrier input level (%) when synchronized + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 70 + 23 + 22 + + + + Toggle between transition constellation and symbol synchronization displays + + + + + + + :/constellation.png + :/slopep_icon.png:/constellation.png + + + true + + + + + + 50 + 105 + 115 + 16 + + + + Maximum frequency deviation (kHz) + + + 500 + + + 1 + + + 50 + + + Qt::Horizontal + + + + + + 10 + 100 + 25 + 29 + + + + FMd + + + + + + 180 + 100 + 40 + 29 + + + + + 40 + 0 + + + + 00.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 130 + 28 + 30 + + + + Gain + + + + + + 50 + 135 + 115 + 16 + + + + + 0 + 0 + + + + Gain after discriminator + + + 50 + + + 400 + + + 1 + + + 100 + + + Qt::Horizontal + + + + + + 180 + 130 + 40 + 30 + + + + + 40 + 0 + + + + 00.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 40 + 70 + 23 + 22 + + + + TDMA slot1 or FDMA unique slot voice on/off + + + + + + + :/slot1_off.png + :/slot1_on.png:/slot1_off.png + + + true + + + + + + 70 + 70 + 23 + 22 + + + + TDMA slot2 voice on/off + + + + + + + :/slot2_off.png + :/slot2_on.png:/slot2_off.png + + + true + + + + + + 100 + 70 + 23 + 22 + + + + Split TDMA channels on left (slot 1) and right (slot 2) audio stereo channels or merge as mono + + + + + + + :/mono.png + :/stereo.png:/mono.png + + + true + + + + From dc5f1397b6f2ec40b5dfea640a790e78247e9584 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 12 Mar 2018 20:39:16 +0100 Subject: [PATCH 104/956] DSD demod: GUI scope simplification (5) --- plugins/channelrx/demoddsd/dsddemod.cpp | 2 +- plugins/channelrx/demoddsd/dsddemodgui.cpp | 10 ++ plugins/channelrx/demoddsd/dsddemodgui.h | 1 + plugins/channelrx/demoddsd/dsddemodgui.ui | 134 +++++++++++++----- .../channelrx/demoddsd/dsddemodsettings.cpp | 4 + plugins/channelrx/demoddsd/dsddemodsettings.h | 1 + sdrgui/dsp/scopevisxy.cpp | 4 +- sdrgui/dsp/scopevisxy.h | 6 +- sdrgui/gui/glshadertvarray.cpp | 6 +- sdrgui/gui/glshadertvarray.h | 2 +- sdrgui/gui/tvscreen.cpp | 12 ++ sdrgui/gui/tvscreen.h | 3 +- 12 files changed, 144 insertions(+), 41 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index e3eecc3e9..cea17c719 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -206,7 +206,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_settings.m_syncOrConstellation) { - Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort); + Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort * 0.84); m_scopeSampleBuffer.push_back(s); } else diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index a509e3201..166e039a9 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -162,6 +162,13 @@ void DSDDemodGUI::on_syncOrConstellation_toggled(bool checked) applySettings(); } +void DSDDemodGUI::on_traceLength_valueChanged(int value) +{ + m_settings.m_traceLengthMutliplier = value; + ui->traceLengthText->setText(QString("%1").arg(m_settings.m_traceLengthMutliplier*50)); + m_scopeVisXY->setPixelsPerFrame(m_settings.m_traceLengthMutliplier*960); // 48000 / 50. Chunks of 50 ms. +} + void DSDDemodGUI::on_slot1On_toggled(bool checked) { m_settings.m_slot1On = checked; @@ -412,6 +419,9 @@ void DSDDemodGUI::displaySettings() } ui->baudRate->setCurrentIndex(DSDDemodBaudRates::getRateIndex(m_settings.m_baudRate)); + ui->traceLength->setValue(m_settings.m_traceLengthMutliplier); + ui->traceLengthText->setText(QString("%1").arg(m_settings.m_traceLengthMutliplier*50)); + m_scopeVisXY->setPixelsPerFrame(m_settings.m_traceLengthMutliplier*960); // 48000 / 50. Chunks of 50 ms. blockApplySettings(false); } diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index ea2669e6f..4a739ec6f 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -119,6 +119,7 @@ private slots: void on_baudRate_currentIndexChanged(int index); void on_enableCosineFiltering_toggled(bool enable); void on_syncOrConstellation_toggled(bool checked); + void on_traceLength_valueChanged(int value); void on_slot1On_toggled(bool checked); void on_slot2On_toggled(bool checked); void on_tdmaStereoSplit_toggled(bool checked); diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index 3c8758fb0..aa0e91f2b 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -62,7 +62,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -158,15 +167,15 @@ - + - 35 + 40 0 - 0.00 + 00.0k Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -554,10 +563,19 @@ - Discriminator Scope + Digital - + + 2 + + + 2 + + + 2 + + 2 @@ -846,7 +864,7 @@ 50 - 105 + 107 115 16 @@ -919,7 +937,7 @@ 50 - 135 + 137 115 16 @@ -949,32 +967,10 @@ Qt::Horizontal - - - - 180 - 130 - 40 - 30 - - - - - 40 - 0 - - - - 00.0k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 40 + 140 70 23 22 @@ -998,7 +994,7 @@ - 70 + 170 70 23 22 @@ -1022,7 +1018,7 @@ - 100 + 200 70 23 22 @@ -1043,6 +1039,78 @@ true + + + + 40 + 68 + 24 + 24 + + + + + 24 + 24 + + + + Display trace length (ms) + + + 1 + + + 30 + + + 1 + + + 5 + + + + + + 70 + 73 + 31 + 16 + + + + Display trace length (ms) + + + 0000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 180 + 130 + 40 + 29 + + + + + 35 + 0 + + + + 0.00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index bd0d23c91..335c38654 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -53,6 +53,7 @@ void DSDDemodSettings::resetToDefaults() m_rgbColor = QColor(0, 255, 255).rgb(); m_title = "DSD Demodulator"; m_highPassFilter = false; + m_traceLengthMutliplier = 5; // 250 ms } QByteArray DSDDemodSettings::serialize() const @@ -85,6 +86,7 @@ QByteArray DSDDemodSettings::serialize() const s.writeString(18, m_title); s.writeBool(19, m_highPassFilter); s.writeBool(20, m_copyAudioUseRTP); + s.writeS32(21, m_traceLengthMutliplier); return s.final(); } @@ -139,6 +141,8 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) d.readString(18, &m_title, "DSD Demodulator"); d.readBool(19, &m_highPassFilter, false); d.readBool(20, &m_copyAudioUseRTP, false); + d.readS32(21, &tmp, 5); + m_traceLengthMutliplier = tmp < 1 ? 1 : tmp > 30 ? 30 : tmp; return true; } diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.h b/plugins/channelrx/demoddsd/dsddemodsettings.h index 58cb08c65..26093c700 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.h +++ b/plugins/channelrx/demoddsd/dsddemodsettings.h @@ -46,6 +46,7 @@ struct DSDDemodSettings quint32 m_rgbColor; QString m_title; bool m_highPassFilter; + int m_traceLengthMutliplier; // x 50ms Serializable *m_channelMarker; Serializable *m_scopeGUI; diff --git a/sdrgui/dsp/scopevisxy.cpp b/sdrgui/dsp/scopevisxy.cpp index 2758417e5..e856f2608 100644 --- a/sdrgui/dsp/scopevisxy.cpp +++ b/sdrgui/dsp/scopevisxy.cpp @@ -52,14 +52,14 @@ void ScopeVisXY::feed(const SampleVector::const_iterator& cbegin, const SampleVe col = col < 0 ? 0 : col >= m_cols ? m_cols-1 : col; m_tvScreen->selectRow(row); - m_tvScreen->setDataColor(col, qRed(m_plotRGB), qGreen(m_plotRGB), qBlue(m_plotRGB)); + m_tvScreen->setDataColor(col, qRed(m_plotRGB), qGreen(m_plotRGB), qBlue(m_plotRGB), 128); // FIXME: alpha does not work m_pixelCount++; if (m_pixelCount == m_pixelsPerFrame) { drawGraticule(); m_tvScreen->renderImage(0); - usleep(10000); + usleep(50000); m_tvScreen->getSize(m_cols, m_rows); m_tvScreen->resetImage(); m_pixelCount = 0; diff --git a/sdrgui/dsp/scopevisxy.h b/sdrgui/dsp/scopevisxy.h index bea51e3c5..e9b22f001 100644 --- a/sdrgui/dsp/scopevisxy.h +++ b/sdrgui/dsp/scopevisxy.h @@ -39,7 +39,11 @@ public: virtual bool handleMessage(const Message& message); void setScale(float scale) { m_scale = scale; } - void setPixelsPerFrame(int pixelsPerFrame) { m_pixelsPerFrame = pixelsPerFrame; } + + void setPixelsPerFrame(int pixelsPerFrame) { + m_pixelsPerFrame = pixelsPerFrame; + m_pixelCount = 0; + } void setPlotRGB(const QRgb& plotRGB) { m_plotRGB = plotRGB; } void setGridRGB(const QRgb& gridRGB) { m_gridRGB = gridRGB; } diff --git a/sdrgui/gui/glshadertvarray.cpp b/sdrgui/gui/glshadertvarray.cpp index b0a45c190..22edc0ed5 100644 --- a/sdrgui/gui/glshadertvarray.cpp +++ b/sdrgui/gui/glshadertvarray.cpp @@ -45,7 +45,6 @@ GLShaderTVArray::GLShaderTVArray(bool blnColor) : m_blnColor(blnColor) m_objCurrentRow = 0; m_objTextureLoc = 0; - m_objColorLoc = 0; m_objMatrixLoc = 0; } @@ -100,7 +99,6 @@ void GLShaderTVArray::InitializeGL(int intCols, int intRows) m_objMatrixLoc = m_objProgram->uniformLocation("uMatrix"); m_objTextureLoc = m_objProgram->uniformLocation("uTexture"); - m_objColorLoc = m_objProgram->uniformLocation("uColour"); if (m_objTexture != 0) { @@ -113,6 +111,7 @@ void GLShaderTVArray::InitializeGL(int intCols, int intRows) m_objImage->fill(QColor(0, 0, 0)); m_objTexture = new QOpenGLTexture(*m_objImage); + m_objTexture->setFormat(QOpenGLTexture::RGBA8_UNorm); m_objTexture->setMinificationFilter(QOpenGLTexture::Linear); m_objTexture->setMagnificationFilter(QOpenGLTexture::Linear); m_objTexture->setWrapMode(QOpenGLTexture::ClampToEdge); @@ -210,6 +209,9 @@ void GLShaderTVArray::RenderPixels(unsigned char *chrData) m_objProgram->setUniformValue(m_objMatrixLoc, objQMatrix); m_objProgram->setUniformValue(m_objTextureLoc, 0); + ptrF->glClear(GL_COLOR_BUFFER_BIT); + ptrF->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + ptrF->glEnable(GL_BLEND); m_objTexture->bind(); diff --git a/sdrgui/gui/glshadertvarray.h b/sdrgui/gui/glshadertvarray.h index 408a62ef4..706401b53 100644 --- a/sdrgui/gui/glshadertvarray.h +++ b/sdrgui/gui/glshadertvarray.h @@ -60,7 +60,7 @@ protected: QOpenGLShaderProgram *m_objProgram; int m_objMatrixLoc; int m_objTextureLoc; - int m_objColorLoc; + //int m_objColorLoc; static const QString m_strVertexShaderSourceArray; static const QString m_strFragmentShaderSourceColored; diff --git a/sdrgui/gui/tvscreen.cpp b/sdrgui/gui/tvscreen.cpp index 729421a06..d00c17824 100644 --- a/sdrgui/gui/tvscreen.cpp +++ b/sdrgui/gui/tvscreen.cpp @@ -225,3 +225,15 @@ bool TVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue) return false; } } + +bool TVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue, int intAlpha) +{ + if (m_blnGLContextInitialized) + { + return m_objGLShaderArray.SetDataColor(intCol, qRgba(intBlue, intGreen, intRed, intAlpha)); // FIXME: blue <> red inversion in shader + } + else + { + return false; + } +} diff --git a/sdrgui/gui/tvscreen.h b/sdrgui/gui/tvscreen.h index da19c653d..1a3926990 100644 --- a/sdrgui/gui/tvscreen.h +++ b/sdrgui/gui/tvscreen.h @@ -52,7 +52,8 @@ public: void resetImage(); bool selectRow(int intLine); - bool setDataColor(int intCol,int intRed, int intGreen, int intBlue); + bool setDataColor(int intCol, int intRed, int intGreen, int intBlue); + bool setDataColor(int intCol, int intRed, int intGreen, int intBlue, int intAlpha); void setRenderImmediate(bool blnRenderImmediate) { m_blnRenderImmediate = blnRenderImmediate; } void connectTimer(const QTimer& timer); From c2544b528ef55c712a5e458f9cf53dec29f5eb14 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 13 Mar 2018 01:39:43 +0100 Subject: [PATCH 105/956] DSD demod: GUI scope simplification (6) --- plugins/channelrx/demoddsd/dsddemodgui.cpp | 23 +++ plugins/channelrx/demoddsd/dsddemodgui.h | 2 + plugins/channelrx/demoddsd/dsddemodgui.ui | 156 +++++++++++++----- .../channelrx/demoddsd/dsddemodsettings.cpp | 12 +- plugins/channelrx/demoddsd/dsddemodsettings.h | 2 + sdrgui/dsp/scopevisxy.cpp | 23 ++- sdrgui/dsp/scopevisxy.h | 11 +- sdrgui/gui/glshadertvarray.cpp | 25 ++- sdrgui/gui/glshadertvarray.h | 5 + sdrgui/gui/tvscreen.cpp | 5 + sdrgui/gui/tvscreen.h | 5 +- 11 files changed, 216 insertions(+), 53 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 166e039a9..7d658f146 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -169,6 +169,20 @@ void DSDDemodGUI::on_traceLength_valueChanged(int value) m_scopeVisXY->setPixelsPerFrame(m_settings.m_traceLengthMutliplier*960); // 48000 / 50. Chunks of 50 ms. } +void DSDDemodGUI::on_traceStroke_valueChanged(int value) +{ + m_settings.m_traceStroke = value; + ui->traceStrokeText->setText(QString("%1").arg(m_settings.m_traceStroke)); + m_scopeVisXY->setStroke(m_settings.m_traceStroke); +} + +void DSDDemodGUI::on_traceDecay_valueChanged(int value) +{ + m_settings.m_traceDecay = value; + ui->traceDecayText->setText(QString("%1").arg(m_settings.m_traceDecay)); + m_scopeVisXY->setDecay(m_settings.m_traceDecay); +} + void DSDDemodGUI::on_slot1On_toggled(bool checked) { m_settings.m_slot1On = checked; @@ -419,10 +433,19 @@ void DSDDemodGUI::displaySettings() } ui->baudRate->setCurrentIndex(DSDDemodBaudRates::getRateIndex(m_settings.m_baudRate)); + ui->traceLength->setValue(m_settings.m_traceLengthMutliplier); ui->traceLengthText->setText(QString("%1").arg(m_settings.m_traceLengthMutliplier*50)); m_scopeVisXY->setPixelsPerFrame(m_settings.m_traceLengthMutliplier*960); // 48000 / 50. Chunks of 50 ms. + ui->traceStroke->setValue(m_settings.m_traceStroke); + ui->traceStrokeText->setText(QString("%1").arg(m_settings.m_traceStroke)); + m_scopeVisXY->setStroke(m_settings.m_traceStroke); + + ui->traceDecay->setValue(m_settings.m_traceDecay); + ui->traceDecayText->setText(QString("%1").arg(m_settings.m_traceDecay)); + m_scopeVisXY->setDecay(m_settings.m_traceDecay); + blockApplySettings(false); } diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index 4a739ec6f..93b4f2db2 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -120,6 +120,8 @@ private slots: void on_enableCosineFiltering_toggled(bool enable); void on_syncOrConstellation_toggled(bool checked); void on_traceLength_valueChanged(int value); + void on_traceStroke_valueChanged(int value); + void on_traceDecay_valueChanged(int value); void on_slot1On_toggled(bool checked); void on_slot2On_toggled(bool checked); void on_tdmaStereoSplit_toggled(bool checked); diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index aa0e91f2b..a37a7064a 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -62,16 +62,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -566,16 +557,7 @@ Digital - - 2 - - - 2 - - - 2 - - + 2 @@ -692,7 +674,7 @@ - 200 + 230 10 23 22 @@ -769,8 +751,8 @@ - 80 - 40 + 200 + 10 23 22 @@ -789,7 +771,7 @@ - 110 + 80 40 25 28 @@ -814,7 +796,7 @@ - 150 + 110 40 25 28 @@ -865,7 +847,7 @@ 50 107 - 115 + 151 16 @@ -901,7 +883,7 @@ - 180 + 210 100 40 29 @@ -938,7 +920,7 @@ 50 137 - 115 + 151 16 @@ -970,8 +952,8 @@ - 140 - 70 + 170 + 40 23 22 @@ -994,8 +976,8 @@ - 170 - 70 + 200 + 40 23 22 @@ -1018,8 +1000,8 @@ - 200 - 70 + 230 + 40 23 22 @@ -1058,7 +1040,7 @@ Display trace length (ms) - 1 + 6 30 @@ -1067,7 +1049,7 @@ 1 - 5 + 6 @@ -1092,7 +1074,7 @@ - 180 + 210 130 40 29 @@ -1111,6 +1093,106 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + 110 + 70 + 24 + 24 + + + + + 24 + 24 + + + + Trace stroke [0..255] + + + 0 + + + 255 + + + 1 + + + 100 + + + + + + 130 + 73 + 31 + 16 + + + + Trace stroke value + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 70 + 24 + 24 + + + + + 24 + 24 + + + + Trace decay [0..255] + + + 0 + + + 255 + + + 1 + + + 200 + + + + + + 190 + 73 + 31 + 16 + + + + Trace decay value + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index 335c38654..523626644 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -53,7 +53,9 @@ void DSDDemodSettings::resetToDefaults() m_rgbColor = QColor(0, 255, 255).rgb(); m_title = "DSD Demodulator"; m_highPassFilter = false; - m_traceLengthMutliplier = 5; // 250 ms + m_traceLengthMutliplier = 6; // 300 ms + m_traceStroke = 100; + m_traceDecay = 200; } QByteArray DSDDemodSettings::serialize() const @@ -87,6 +89,8 @@ QByteArray DSDDemodSettings::serialize() const s.writeBool(19, m_highPassFilter); s.writeBool(20, m_copyAudioUseRTP); s.writeS32(21, m_traceLengthMutliplier); + s.writeS32(22, m_traceStroke); + s.writeS32(23, m_traceDecay); return s.final(); } @@ -142,7 +146,11 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) d.readBool(19, &m_highPassFilter, false); d.readBool(20, &m_copyAudioUseRTP, false); d.readS32(21, &tmp, 5); - m_traceLengthMutliplier = tmp < 1 ? 1 : tmp > 30 ? 30 : tmp; + m_traceLengthMutliplier = tmp < 2 ? 2 : tmp > 30 ? 30 : tmp; + d.readS32(22, &tmp, 100); + m_traceStroke = tmp < 0 ? 0 : tmp > 255 ? 255 : tmp; + d.readS32(23, &tmp, 200); + m_traceDecay = tmp < 0 ? 0 : tmp > 255 ? 255 : tmp; return true; } diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.h b/plugins/channelrx/demoddsd/dsddemodsettings.h index 26093c700..502858ce5 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.h +++ b/plugins/channelrx/demoddsd/dsddemodsettings.h @@ -47,6 +47,8 @@ struct DSDDemodSettings QString m_title; bool m_highPassFilter; int m_traceLengthMutliplier; // x 50ms + int m_traceStroke; // [0..255] + int m_traceDecay; // [0..255] Serializable *m_channelMarker; Serializable *m_scopeGUI; diff --git a/sdrgui/dsp/scopevisxy.cpp b/sdrgui/dsp/scopevisxy.cpp index e856f2608..1a237f32a 100644 --- a/sdrgui/dsp/scopevisxy.cpp +++ b/sdrgui/dsp/scopevisxy.cpp @@ -26,14 +26,26 @@ ScopeVisXY::ScopeVisXY(TVScreen *tvScreen) : m_rows(0), m_pixelsPerFrame(480), m_pixelCount(0), + m_alphaTrace(128), + m_alphaReset(128), m_plotRGB(qRgb(0, 255, 0)), m_gridRGB(qRgb(255, 255 ,255)) { setObjectName("ScopeVisXY"); + setPixelsPerFrame(m_pixelsPerFrame); + m_tvScreen->setAlphaBlend(true); } ScopeVisXY::~ScopeVisXY() -{} +{ +} + +void ScopeVisXY::setPixelsPerFrame(int pixelsPerFrame) +{ + m_pixelsPerFrame = pixelsPerFrame; + m_pixelCount = 0; + m_tvScreen->setAlphaReset(); +} void ScopeVisXY::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) @@ -52,20 +64,21 @@ void ScopeVisXY::feed(const SampleVector::const_iterator& cbegin, const SampleVe col = col < 0 ? 0 : col >= m_cols ? m_cols-1 : col; m_tvScreen->selectRow(row); - m_tvScreen->setDataColor(col, qRed(m_plotRGB), qGreen(m_plotRGB), qBlue(m_plotRGB), 128); // FIXME: alpha does not work + m_tvScreen->setDataColor(col, qRed(m_plotRGB), qGreen(m_plotRGB), qBlue(m_plotRGB), m_alphaTrace); + + ++begin; m_pixelCount++; if (m_pixelCount == m_pixelsPerFrame) { drawGraticule(); m_tvScreen->renderImage(0); - usleep(50000); + usleep(5000); m_tvScreen->getSize(m_cols, m_rows); - m_tvScreen->resetImage(); + m_tvScreen->resetImage(m_alphaReset); m_pixelCount = 0; } - ++begin; } } diff --git a/sdrgui/dsp/scopevisxy.h b/sdrgui/dsp/scopevisxy.h index e9b22f001..4eedf3860 100644 --- a/sdrgui/dsp/scopevisxy.h +++ b/sdrgui/dsp/scopevisxy.h @@ -39,11 +39,10 @@ public: virtual bool handleMessage(const Message& message); void setScale(float scale) { m_scale = scale; } - - void setPixelsPerFrame(int pixelsPerFrame) { - m_pixelsPerFrame = pixelsPerFrame; - m_pixelCount = 0; - } + void setStroke(int stroke) { m_alphaTrace = stroke; } + void setDecay(int decay) { m_alphaReset = 255 - decay; } + + void setPixelsPerFrame(int pixelsPerFrame); void setPlotRGB(const QRgb& plotRGB) { m_plotRGB = plotRGB; } void setGridRGB(const QRgb& gridRGB) { m_gridRGB = gridRGB; } @@ -59,6 +58,8 @@ private: int m_rows; int m_pixelsPerFrame; int m_pixelCount; + int m_alphaTrace; //!< this is the stroke value [0:255] + int m_alphaReset; //!< alpha channel of screen blanking (blackening) is 255 minus decay value [0:255] QRgb m_plotRGB; QRgb m_gridRGB; std::vector > m_graticule; diff --git a/sdrgui/gui/glshadertvarray.cpp b/sdrgui/gui/glshadertvarray.cpp index 22edc0ed5..0e8cd1a5b 100644 --- a/sdrgui/gui/glshadertvarray.cpp +++ b/sdrgui/gui/glshadertvarray.cpp @@ -36,6 +36,8 @@ const QString GLShaderTVArray::m_strFragmentShaderSourceColored = QString( GLShaderTVArray::GLShaderTVArray(bool blnColor) : m_blnColor(blnColor) { + m_blnAlphaBlend = false; + m_blnAlphaReset = false; m_objProgram = 0; m_objImage = 0; m_objTexture = 0; @@ -209,9 +211,18 @@ void GLShaderTVArray::RenderPixels(unsigned char *chrData) m_objProgram->setUniformValue(m_objMatrixLoc, objQMatrix); m_objProgram->setUniformValue(m_objTextureLoc, 0); - ptrF->glClear(GL_COLOR_BUFFER_BIT); - ptrF->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - ptrF->glEnable(GL_BLEND); + + if (m_blnAlphaReset) { + ptrF->glClear(GL_COLOR_BUFFER_BIT); + m_blnAlphaReset = false; + } + + if (m_blnAlphaBlend) { + ptrF->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + ptrF->glEnable(GL_BLEND); + } else { + ptrF->glDisable(GL_BLEND); + } m_objTexture->bind(); @@ -244,6 +255,14 @@ void GLShaderTVArray::ResetPixels() } } +void GLShaderTVArray::ResetPixels(int alpha) +{ + if (m_objImage != 0) + { + m_objImage->fill(qRgba(0, 0, 0, alpha)); + } +} + void GLShaderTVArray::Cleanup() { m_blnInitialized = false; diff --git a/sdrgui/gui/glshadertvarray.h b/sdrgui/gui/glshadertvarray.h index 706401b53..5c5044743 100644 --- a/sdrgui/gui/glshadertvarray.h +++ b/sdrgui/gui/glshadertvarray.h @@ -43,6 +43,8 @@ public: ~GLShaderTVArray(); void setColor(bool blnColor) { m_blnColor = blnColor; } + void setAlphaBlend(bool blnAlphaBlend) { m_blnAlphaBlend = blnAlphaBlend; } + void setAlphaReset() { m_blnAlphaReset = true; } void InitializeGL(int intCols, int intRows); void ResizeContainer(int intCols, int intRows); void getSize(int& intCols, int& intRows) const { intCols = m_intCols, intRows = m_intRows; } @@ -50,6 +52,7 @@ public: QRgb *GetRowBuffer(int intRow); void RenderPixels(unsigned char *chrData); void ResetPixels(); + void ResetPixels(int alpha); bool SelectRow(int intLine); bool SetDataColor(int intCol,QRgb objColor); @@ -74,6 +77,8 @@ protected: bool m_blnInitialized; bool m_blnColor; + bool m_blnAlphaBlend; + bool m_blnAlphaReset; }; #endif /* INCLUDE_GUI_GLTVSHADERARRAY_H_ */ diff --git a/sdrgui/gui/tvscreen.cpp b/sdrgui/gui/tvscreen.cpp index d00c17824..b41d471b0 100644 --- a/sdrgui/gui/tvscreen.cpp +++ b/sdrgui/gui/tvscreen.cpp @@ -81,6 +81,11 @@ void TVScreen::resetImage() m_objGLShaderArray.ResetPixels(); } +void TVScreen::resetImage(int alpha) +{ + m_objGLShaderArray.ResetPixels(alpha); +} + void TVScreen::resizeTVScreen(int intCols, int intRows) { m_intAskedCols = intCols; diff --git a/sdrgui/gui/tvscreen.h b/sdrgui/gui/tvscreen.h index 1a3926990..f100b9e66 100644 --- a/sdrgui/gui/tvscreen.h +++ b/sdrgui/gui/tvscreen.h @@ -42,7 +42,7 @@ class TVScreen: public QGLWidget public: TVScreen(bool blnColor, QWidget* parent = 0); - ~TVScreen(); + virtual ~TVScreen(); void setColor(bool blnColor); void resizeTVScreen(int intCols, int intRows); @@ -50,11 +50,14 @@ public: void renderImage(unsigned char * objData); QRgb* getRowBuffer(int intRow); void resetImage(); + void resetImage(int alpha); bool selectRow(int intLine); bool setDataColor(int intCol, int intRed, int intGreen, int intBlue); bool setDataColor(int intCol, int intRed, int intGreen, int intBlue, int intAlpha); void setRenderImmediate(bool blnRenderImmediate) { m_blnRenderImmediate = blnRenderImmediate; } + void setAlphaBlend(bool blnAlphaBlend) { m_objGLShaderArray.setAlphaBlend(blnAlphaBlend); } + void setAlphaReset() { m_objGLShaderArray.setAlphaReset(); } void connectTimer(const QTimer& timer); From d555333f8ed421b7f19c2fed1551b9f7bbf27300 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 13 Mar 2018 08:16:39 +0100 Subject: [PATCH 106/956] DSD demod: GUI scope simplification (7) --- plugins/channelrx/demoddsd/dsddemodsettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index 523626644..163289121 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -145,7 +145,7 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) d.readString(18, &m_title, "DSD Demodulator"); d.readBool(19, &m_highPassFilter, false); d.readBool(20, &m_copyAudioUseRTP, false); - d.readS32(21, &tmp, 5); + d.readS32(21, &tmp, 6); m_traceLengthMutliplier = tmp < 2 ? 2 : tmp > 30 ? 30 : tmp; d.readS32(22, &tmp, 100); m_traceStroke = tmp < 0 ? 0 : tmp > 255 ? 255 : tmp; From 9e907095843ccdbf3e7e0dc291e5b9fe719d550b Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 13 Mar 2018 19:56:40 +0100 Subject: [PATCH 107/956] Revert "LimeSDR: Temporary fix for rubbish values returned by recent version of Lime Sute" This reverts commit 8312e5ad58f7bb0daeb0eb0ca9974c101f33da14. --- devices/limesdr/devicelimesdrparam.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/devices/limesdr/devicelimesdrparam.cpp b/devices/limesdr/devicelimesdrparam.cpp index 015253136..954eee9fa 100644 --- a/devices/limesdr/devicelimesdrparam.cpp +++ b/devices/limesdr/devicelimesdrparam.cpp @@ -93,18 +93,6 @@ bool DeviceLimeSDRParams::open(lms_info_str_t deviceStr) return false; } - // FIXME: Temporary fix for rubbish values returned by recent version of Lime Sute - m_lpfRangeRx.step = 1.0; - m_lpfRangeTx.step = 1.0; - m_srRangeRx.step = 1.0; - m_srRangeTx.step = 1.0; - m_loRangeRx.max = 3800000000.0; - m_loRangeRx.min = 30000000.0; - m_loRangeRx.step = 1.0; - m_loRangeTx.max = 3800000000.0; - m_loRangeTx.min = 30000000.0; - m_loRangeTx.step = 1.0; - return true; } From 56178c65cfe3fbbc45c827e780e4b1f3a369f075 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 14 Mar 2018 05:16:07 +0100 Subject: [PATCH 108/956] DSD demod: updated documentation following GUI redesign --- doc/img/DSDdemod_plugin.old.xcf | Bin 453523 -> 0 bytes doc/img/DSDdemod_plugin.png | Bin 118309 -> 130329 bytes doc/img/DSDdemod_plugin.xcf | Bin 634086 -> 317822 bytes doc/img/DSDdemod_plugin_2fsk.png | Bin 0 -> 76865 bytes doc/img/DSDdemod_plugin_2fsk.xcf | Bin 0 -> 82875 bytes doc/img/DSDdemod_plugin_2fsk_sym.png | Bin 0 -> 61550 bytes doc/img/DSDdemod_plugin_2fsk_sym.xcf | Bin 0 -> 66216 bytes doc/img/DSDdemod_plugin_4fsk.png | Bin 0 -> 82587 bytes doc/img/DSDdemod_plugin_4fsk.xcf | Bin 0 -> 95764 bytes doc/img/DSDdemod_plugin_4fsk_sym.png | Bin 0 -> 71847 bytes doc/img/DSDdemod_plugin_4fsk_sym.xcf | Bin 0 -> 72671 bytes doc/img/DSDdemod_plugin_dmr_polar.png | Bin 41283 -> 0 bytes doc/img/DSDdemod_plugin_dstar_polar.png | Bin 23933 -> 0 bytes doc/img/DSDdemod_plugin_scope.png | Bin 65705 -> 0 bytes doc/img/DSDdemod_plugin_scope.xcf | Bin 318472 -> 0 bytes doc/img/DSDdemod_plugin_scope2.png | Bin 42479 -> 0 bytes doc/img/DSDdemod_plugin_scope2.xcf | Bin 257160 -> 0 bytes plugins/channelrx/demoddsd/readme.md | 364 +++++++++++------------- 18 files changed, 174 insertions(+), 190 deletions(-) delete mode 100644 doc/img/DSDdemod_plugin.old.xcf create mode 100644 doc/img/DSDdemod_plugin_2fsk.png create mode 100644 doc/img/DSDdemod_plugin_2fsk.xcf create mode 100644 doc/img/DSDdemod_plugin_2fsk_sym.png create mode 100644 doc/img/DSDdemod_plugin_2fsk_sym.xcf create mode 100644 doc/img/DSDdemod_plugin_4fsk.png create mode 100644 doc/img/DSDdemod_plugin_4fsk.xcf create mode 100644 doc/img/DSDdemod_plugin_4fsk_sym.png create mode 100644 doc/img/DSDdemod_plugin_4fsk_sym.xcf delete mode 100644 doc/img/DSDdemod_plugin_dmr_polar.png delete mode 100644 doc/img/DSDdemod_plugin_dstar_polar.png delete mode 100644 doc/img/DSDdemod_plugin_scope.png delete mode 100644 doc/img/DSDdemod_plugin_scope.xcf delete mode 100644 doc/img/DSDdemod_plugin_scope2.png delete mode 100644 doc/img/DSDdemod_plugin_scope2.xcf diff --git a/doc/img/DSDdemod_plugin.old.xcf b/doc/img/DSDdemod_plugin.old.xcf deleted file mode 100644 index d6eb120c9b073ad1f754ba848f2e13b50c9a37e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 453523 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 diff --git a/doc/img/DSDdemod_plugin.png b/doc/img/DSDdemod_plugin.png index e834a3e24ad8000c1aca4d1909b62ccbd4f53039..603de11bdf70294a61a583ea0a506870fd561df0 100644 GIT binary patch literal 130329 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�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%0DRj5gt*ac1uitz|GYb*Pxl$oL| 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 a(DT!vU~6$WkB)``q!MiN>VE*Jku7-u literal 118309 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�Ac80AjAcx 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 VzKaV>ipT39c5XM+F3_|I`#){9#?b%( diff --git a/doc/img/DSDdemod_plugin.xcf b/doc/img/DSDdemod_plugin.xcf index 7be41e72d451e588e4ec08f881d056d000df5796..ba0db41aaa7f6e4ad95a18e94dc03678fc5b8f90 100644 GIT binary patch literal 317822 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⋘2r=E2R1ta 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 bZWLk9+2__T{I8OD_X|@}IA2c%{-V3Eq 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¥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 V{~yB-w<>tc^2dx*{L@ePe*rG_q?rH! literal 634086 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%0DYepHkSD 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≻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 literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_2fsk.xcf b/doc/img/DSDdemod_plugin_2fsk.xcf new file mode 100644 index 0000000000000000000000000000000000000000..8f528c8e4f1cf987a26310d5d2d8ddf056016e62 GIT binary patch literal 82875 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 G+5ZDz@EHIA literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_2fsk_sym.png b/doc/img/DSDdemod_plugin_2fsk_sym.png new file mode 100644 index 0000000000000000000000000000000000000000..f38ee59b849181f88efae9b930400abc9de995e4 GIT binary patch literal 61550 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

gxc^kZ|d*_p#rv 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 literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_2fsk_sym.xcf b/doc/img/DSDdemod_plugin_2fsk_sym.xcf new file mode 100644 index 0000000000000000000000000000000000000000..0d9488995f4b920c0549a655bb8e9187c330d744 GIT binary patch literal 66216 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

kIweinXD0+>Rx2 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%0D(unG_7^hWV5RF>o&PU?~<64q61P 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 R7NU4|y%xd^)D*pt{ufKONpJuF literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_4fsk.png b/doc/img/DSDdemod_plugin_4fsk.png new file mode 100644 index 0000000000000000000000000000000000000000..57e80030684c366dbbfc814b6aea835812c858be GIT binary patch literal 82587 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 C;7sZO literal 34382 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
{j+%QE-qsHnbuj{z-beQO`%*@p2 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 MRUVYydm8wE0Zpq7T>t<8 diff --git a/doc/img/LimeSDRInput_plugin.xcf b/doc/img/LimeSDRInput_plugin.xcf index 8c97946e5e8cccf81360c316017a6db372af3bee..ae259f017ab59a0356117d70dde1cdf726fdce97 100644 GIT binary patch delta 12223 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 delta 8998 zcmeHNYgANMmcHi}&nilJLvf*iE1{4_gI5%aqNpkg5D=v(LBvNCV~pv}Lpyd>?6ek1 zJ7b$pC&m-!PI{6~GI7l$ne?Qcn7AgBhiP?sI?2i;voe|3GImU|B47jZD6ooqsM-78 zDixw@{>+-0A7QP}U3;H>Pn~o2xA)msc;}gfM-C($sv|!*=D0wz1eqiIp5e#~A9CcS z7dT$|5r;oIQcbtiroZOl%g&~ifS~|D0P&vQ-M8)7Tu~{XDkr^l z_@{rV_JKqmjN-WdH5?J{1NZI6yR>MaJ#WW7*tv8<#?SIqH&dbBo`80fBx>G6Rk)LP07bmx`S9s2S> z3+k&X4VB6&VO6Dmo(QbMVNaEzO7RGuD*ZeW@ZfMqwV_(+5IU;$^F*KnhrLzJD- z+KXDJ%|Jt_b*fsef%Xoh)8as~QL}W}4fcsHfj%)%U<`DjPq)M1nCKShhXVx*1Kns@ z=hQiwk2$00Xds8?4H}aI>(Jig(z#e4b49t%)6$@c<^>H&fgT*$;MTd>L(CoJK2M(; zOrwVe7p9DF(4KL%c{~T1hj`94(~~gvYA}W#3K~+T)F-k6x?J z%6`nOQPv5sKwlgxNzu-xqgq$ZR{pR$3eS-a4V4(R6Kr2@Sgy1SwCs|pJkXA-zDdAH zrLu{2R7O>5ach$dwC@s}bsIj@T4A8iqt=SriZTP;r+~HswYl^F3eoqJ)D*o%BM*|y zUnkUwqK$s8WQU1n%`xQot%5Ztr;{34wivYHOdgxZ$Ms3q7+Q`DEf>l~(Meacyre)m zx-CVPE)|wqEDm~*0ZW&nwF+3;HwKHULahc^dVzsfgIZmsfqI5PQ(5k+0zMC?7X@56 zR*wt>&Laco2yOjtVBnV7)MC9g;70dGo57|u3XL}XJP~MAhk=x{!|60?1W2h`nvj%D z0{z|ykW$T{W+Y{^K<^v@Qa0mLElA3{P5~+J2<6bXM~o?2ZnYvQ?>dE~Y!&F2BVv*k zfHtJ$J*QMk_J>UL-VQY3cUOp3j#=p5#(v2EVkW>D0fxbJI7q9WDIL9DA$CE6oAJ?0M#W=yYm}o{OngzOS9HMAO z7ZF(~3UueVSQHS^QdnXrQ3{2^68$_8D8%8#`5kR*`Vs(<7!~6E>~kyQ;)BSG@Wz1Trk7vq?j_L&QRrW z+}9V~=fBnAfTdXcP#@K$EVJ8JwzqHgwUc&#qusttSY}_@CtrvrVv4rV!i`AD=SCYf&qOy7}IeO4Cw2=;8{4 z?1{xlsl>9B8RUCFEecq0^uM7Da7;cHOHvm?HB~8wH1q=vH%{k;X=uVqYJxj&IxjV7 z!Z{<{jpss8P3iPE`84ig1}(oRQqz_6*el`t{fkTa^zn-WaDAHSp#d}f)j$S4Gf+U! z4rCl2ETo?YGRX(j0iPUjr{x8c>E6LS`q*G5eH~=@o+Fd=;-H!O2eS>6-9ooxe<*yD zK~oXTq??BdsCC9np9lH+kePlpw3sSGd2zGtc&h59WTxIrDfHyUSX!(spg+F!9{r<| zMm>MZqp2*-1VqCt!c!N%7y2Xz=MUcoCfdz1=!bt2=@Sfg6}}Jlj^)$R;Q^?>CR#RP zrnZp`x^<+0-VgGbkwUsBWWf3gTk8jHVkDEE4`o38jHjca7?8myGAr`tb~U=eS6J zGM@h0^W%v`&Wa)?`u>EOem;>wLlXrwWio@-Ocv7iNetK?_;hG8lb)E&lY5d$g1kDK zBtXjL9rh7GKQ_+BB6& zw@)R}ol|K#vV4|~jhp1`SeW+s)O+-f=`{MtbRK?GW3=A^JLkHkyttWScj`u?vZxS{CB1189%#1z&&e{7b16c8Z2k_5Ky+Sm*{ckeJ6 zX8zA4o%9(s2D8J77n6C-3uosgrS?A$s!k6LWj3TD|0 zvuLRrndKKPq=oHQosRr_H8?>y2U8(~6Xn0tllYX#KCZwwNp}yoNYN6#471DxlhUu> zX8^`Mqqj>j5-p#}f-FxI@OUDR2VwZ|%pe(@Jq;@j&{u{`PyrHSv~#8-Pz;nIgD)Hn ztOencfP%HF!MH(;&63h4w7~&~1#441D-_7p3VlSdR@GXjtPoa|=_7)zP^}SqA2BvsH8Yc0zUaz18#3nKtc99yoJTReS#1xwgSU9208S zjSYCt6|DikwxsK_ZkGOjR0v-&F}~}M?ON&uerdBcy!#6qi8SOl)iETIgPWI|XMVcv zuTQSB5^K0|zmaZG9kgiEpIk~-TWkzl)T7R8F2>dcH21ZAQl8E`+tJtl@1Ip-aK6{^;9 zWwEdrQ&JObv1*;y__H$ATBTG76_}EmU=^yhT3I42!M9XRuqCS1s$i!J87bwfl1ZxB z1}^}Wc#~;@Riaf+h$3d}APljZAwd6i!UUZ}Vmn)QjUeH?yvyNm@7<1!h0}I8AH1oK zQHYRBdv;cSCkkF2+b7|YgUCe&=cTO7qDVG$D+$@xT;?X_)i(-xZ8Awp$9cY;l=9ut zB>S*G0nQE`zMXZea1UGiT=hed)B=bhso%{t%hpiD@c-I}uwXBnKW8hu! z*Z#y@JjBS}=4I8@7Z2>IQe%N9JrC^LB_9I?-Zo<8w^E4F2TjkMmkYT1)GHwF2DE_e zDHTdFdo;mH)i_ru&^BWBXhI`(T$y4KESNo-U>4PP-dicl)|hEgL_x&t(S*k08M*}; zyE~>ABxy9CsgWF&c)_W!8YUkz;gsR*!A6gKAhD97MOlkjQ5jtM_&H%wyfjl%;oYz? zbK}egZ$$~TCYo;87%wSB9bJuCjY?NX5w_)bJ5jIos*{InXvKy}2 z8jKBT=-A`VcC#MMvBzz6$IFMd@Og4=8i_MZ_dnuxuQ_o!D8*D=y-SmyNxARvp^cI-{Mz z{+~I|{t|8_Tnic)7F#!Jy zgR9xj=gNvnUHkz@%a^A&t_15Cxcl%cyXvCrrq3Oyt7|_o7Lww?(rM4Ovrg?xI_(Me z*~S}~jdom~w>G<$c{NvWt+5u4-_qPxn^mi{HCyobQA+?bDV<&6&GITMN-%_*mNSPPc)u-2k zr6I0?$adm`llMm&RMG2rdoysm}6jWjPtz6C5=?QI>&*zc?eV|Kp93H86L^`2U>;F$p2~ zefAMRGJxY2a$GBjcva!JTjYN#Bn9+GNjv0r3$Zk1H){iA3rIU4iX+)O;Meqf0DlH} zV$L|pev~5>CphBzfFoV6b7V&^NA|pN;w=j~xwYQ6I_z8wQq(5%-2=bxe-UsTaB|K# z@jb?oKYxKEhc<9Tj^)U+Z=QJP5i(p*cyzDk$d!T=0I;f~;gy7KHbL0zH)!0^!CyO{f_7KNs{w>Fs z{)XdSzvcKY1IKTBjN^B$=JPvA9?fY2H^wv{ejczwgY|$*oV(t9oO-H!|C{! z0e=tp2f!PEHv#Veehc_#!25uY0RIN~3;^@-=K)^=z5xsYMgZf0DbASk1DXJ>fYksmU@c%h;8ws^+zK2(=z7Q_ MMqRu-Vbp!_UlEkAsQ>@~ diff --git a/doc/img/LimeSDRInput_plugin_2.png b/doc/img/LimeSDRInput_plugin_2.png index 40dc9bb81f4c628029b301edebb8f02a28a207e9..39b3927a0a350c8613f7c60d5ba3d609d1516b0f 100644 GIT binary patch literal 7176 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 literal 7135 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 diff --git a/doc/img/LimeSDROutput_plugin.png b/doc/img/LimeSDROutput_plugin.png index 95367eba14b1cfc6395c69bb4d4854cccdb47081..b51b192443d88e68970e1079340f398cc6bc94a8 100644 GIT binary patch literal 35323 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 L75vs5nee{>Yq%Tf diff --git a/plugins/samplesink/limesdroutput/readme.md b/plugins/samplesink/limesdroutput/readme.md index 2ee287df9..0c779dcfb 100644 --- a/plugins/samplesink/limesdroutput/readme.md +++ b/plugins/samplesink/limesdroutput/readme.md @@ -73,16 +73,42 @@ The button is lit when NCO is active and dark when inactive. Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an independent NCO in each Tx channel that can span the bandwidth sent to the DAC. This effectively allows non zero digital IF. -

6: Zero (reset) NCO frequency

- -Use this push button to reset the NCO frequency to 0 and thus center on the main passband of the DAC. - -

7: NCO frequency shift

+

6: NCO frequency shift

This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of transmission minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. ☞ In the LMS7002M TSP block the NCO sits after the interpolator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual DAC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware interpolation factor. For example with a 4 MS/s device to host sample rate (10) and a hadrware interpolation of 16 (8) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). +

7: Transverter mode open dialog

+ +This button opens a dialog to set the transverter mode frequency translation options: + +![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) + +Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. + +

7.1: Translating frequency

+ +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. + +The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. + +For example a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set at 7,130 kHz the PlutoSDR will be set to 127.130 MHz. + +If you use a down converter to receive the 6 cm band narrowband center frequency of 5670 MHz at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. + +For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to receive the 10368 MHz frequency at 432 MHz you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. + +The Hz precision allows a fine tuning of the transverter LO offset + +

7.2: Translating frequency enable/disable

+ +Use this toggle button to activate or deactivate the frequency translation + +

7.3: Confirmation buttons

+ +Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. +

7A: External clock control

Use this button to open a dialog that lets you choose the external clock frequency and enable or disable it. When disabled the internal 30.72 MHz VCTCXO is used. diff --git a/plugins/samplesource/limesdrinput/readme.md b/plugins/samplesource/limesdrinput/readme.md index 9a15d8859..05a332abb 100644 --- a/plugins/samplesource/limesdrinput/readme.md +++ b/plugins/samplesource/limesdrinput/readme.md @@ -75,24 +75,50 @@ The button is lit when NCO is active and dark when inactive. Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an independent NCO in each Rx channel that can span the bandwidth received by the ADC. This effectively allows non zero digital IF. -

2.2: Zero (reset) NCO frequency

- -USe this push button to reset the NCO frequency to 0 and thus center on the main passband of the ADC. - -

2.3: NCO frequency shift

+

2.2: NCO frequency shift

This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of reception minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. ☞ In the LMS7002M TSP block the NCO sits before the decimator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual ADC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware decimation factor. For example with a 4 MS/s device to host sample rate (5) and a hadrware decimation of 16 (3) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). -

2.4: DC component auto correction

+

2.3: DC component auto correction

Enables or disables the auto remove DC component -

2.5: I/Q balance auto correction

+

2.4: I/Q balance auto correction

Enables or disables the auto I/Q balance correction. The DC correction must be enabled for this to be effective. +

2.5: Transverter mode open dialog

+ +This button opens a dialog to set the transverter mode frequency translation options: + +![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) + +Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. + +
2.5.1: Translating frequency
+ +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. + +The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. + +For example a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set at 7,130 kHz the PlutoSDR will be set to 127.130 MHz. + +If you use a down converter to receive the 6 cm band narrowband center frequency of 5670 MHz at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. + +For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to receive the 10368 MHz frequency at 432 MHz you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. + +The Hz precision allows a fine tuning of the transverter LO offset + +
2.5.2: Translating frequency enable/disable
+ +Use this toggle button to activate or deactivate the frequency translation + +
2.5.3: Confirmation buttons
+ +Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. +

2.6: External clock control

Use this button to open a dialog that lets you choose the external clock frequency and enable or disable it. When disabled the internal 30.72 MHz VCTCXO is used. From aa3f12ec46aebe8744c16d5dffa83bf892aff854 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 17 Apr 2018 09:15:02 +0200 Subject: [PATCH 289/956] NFM demod: assymetrical fade in and fade out for the squelch --- plugins/channelrx/demodnfm/nfmdemod.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 3c2d82ab3..38e0eec9d 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -193,7 +193,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { if (m_squelchCount > 0) { - m_squelchCount--; + m_squelchCount -= 10; } } } @@ -203,7 +203,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { if (m_squelchCount > 0) { - m_squelchCount--; + m_squelchCount -= 10; } } else From 5c51297717b23de6eaca003e2bd538ce3bcd79f6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 18 Apr 2018 22:20:47 +0200 Subject: [PATCH 290/956] Moved FFT filter destruction at end of the mod or demod destructor --- plugins/channelrx/chanalyzer/chanalyzer.cpp | 4 ++-- .../channelrx/chanalyzerng/chanalyzerng.cpp | 4 ++-- plugins/channelrx/demodatv/atvdemod.cpp | 2 ++ plugins/channelrx/demodbfm/bfmdemod.cpp | 7 +------ plugins/channelrx/demoddatv/datvdemod.cpp | 1 + plugins/channelrx/demodssb/ssbdemod.cpp | 4 ++-- plugins/channelrx/demodwfm/wfmdemod.cpp | 6 +----- plugins/channelrx/tcpsrc/tcpsrc.cpp | 3 +-- plugins/channelrx/udpsrc/udpsrc.cpp | 2 +- plugins/channeltx/modatv/atvmod.cpp | 4 ++++ plugins/channeltx/modssb/ssbmod.cpp | 21 +++++-------------- plugins/channeltx/modwfm/wfmmod.cpp | 4 ++-- plugins/channeltx/udpsink/udpsink.cpp | 4 ++-- 13 files changed, 26 insertions(+), 40 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzer.cpp b/plugins/channelrx/chanalyzer/chanalyzer.cpp index c8f80498f..8af199fb5 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzer.cpp @@ -63,12 +63,12 @@ ChannelAnalyzer::ChannelAnalyzer(DeviceSourceAPI *deviceAPI) : ChannelAnalyzer::~ChannelAnalyzer() { - if (SSBFilter) delete SSBFilter; - if (DSBFilter) delete DSBFilter; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + if (SSBFilter) delete SSBFilter; + if (DSBFilter) delete DSBFilter; } void ChannelAnalyzer::configure(MessageQueue* messageQueue, diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 5e91572b8..fed5b3e2f 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -60,12 +60,12 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : ChannelAnalyzerNG::~ChannelAnalyzerNG() { - delete SSBFilter; - delete DSBFilter; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete SSBFilter; + delete DSBFilter; } void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, diff --git a/plugins/channelrx/demodatv/atvdemod.cpp b/plugins/channelrx/demodatv/atvdemod.cpp index 73ac7ef83..68e8feed5 100644 --- a/plugins/channelrx/demodatv/atvdemod.cpp +++ b/plugins/channelrx/demodatv/atvdemod.cpp @@ -101,6 +101,8 @@ ATVDemod::~ATVDemod() m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_DSBFilter; + delete m_DSBFilterBuffer; } void ATVDemod::setTVScreen(TVScreen *objScreen) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index 43c4eae61..c7c8050ef 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -78,7 +78,6 @@ BFMDemod::BFMDemod(DeviceSourceAPI *deviceAPI) : m_rfFilter = new fftfilt(-50000.0 / 384000.0, 50000.0 / 384000.0, filtFftLen); - m_deemphasisFilterX.configure(default_deemphasis * m_audioSampleRate * 1.0e-6); m_deemphasisFilterY.configure(default_deemphasis * m_audioSampleRate * 1.0e-6); m_phaseDiscri.setFMScaling(384000/m_fmExcursion); @@ -97,17 +96,13 @@ BFMDemod::BFMDemod(DeviceSourceAPI *deviceAPI) : BFMDemod::~BFMDemod() { - if (m_rfFilter) - { - delete m_rfFilter; - } - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_rfFilter; } void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 94a228380..2023e5c3f 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -91,6 +91,7 @@ DATVDemod::~DATVDemod() m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_objRFFilter; } bool DATVDemod::SetTVScreen(TVScreen *objScreen) diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 7f3a93dbf..80ca4d644 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -96,14 +96,14 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : SSBDemod::~SSBDemod() { - if (SSBFilter) delete SSBFilter; - if (DSBFilter) delete DSBFilter; DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete SSBFilter; + delete DSBFilter; } void SSBDemod::configure(MessageQueue* messageQueue, diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index 3493d1eca..e12606162 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -72,17 +72,13 @@ WFMDemod::WFMDemod(DeviceSourceAPI* deviceAPI) : WFMDemod::~WFMDemod() { - if (m_rfFilter) - { - delete m_rfFilter; - } - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_rfFilter; } void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) diff --git a/plugins/channelrx/tcpsrc/tcpsrc.cpp b/plugins/channelrx/tcpsrc/tcpsrc.cpp index 6b3d7a49a..5e19f537f 100644 --- a/plugins/channelrx/tcpsrc/tcpsrc.cpp +++ b/plugins/channelrx/tcpsrc/tcpsrc.cpp @@ -73,12 +73,11 @@ TCPSrc::TCPSrc(DeviceSourceAPI* deviceAPI) : TCPSrc::~TCPSrc() { - if (TCPFilter) delete TCPFilter; - m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete TCPFilter; } void TCPSrc::setSpectrum(MessageQueue* messageQueue, bool enabled) diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index fd3e1f482..dc2c1ab24 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -117,12 +117,12 @@ UDPSrc::~UDPSrc() delete m_udpBuffer16; delete m_udpBufferMono16; delete[] m_udpAudioBuf; - if (UDPFilter) delete UDPFilter; DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete UDPFilter; } void UDPSrc::setSpectrum(MessageQueue* messageQueue, bool enabled) diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index c98025b09..285802bb2 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -110,6 +110,10 @@ ATVMod::~ATVMod() m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_SSBFilter; + delete m_DSBFilter; + delete[] m_SSBFilterBuffer; + delete[] m_DSBFilterBuffer; } void ATVMod::pullAudio(int nbSamples __attribute__((unused))) diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index 8c341e4be..04daf762b 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -114,28 +114,17 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : SSBMod::~SSBMod() { - if (m_SSBFilter) { - delete m_SSBFilter; - } - - if (m_DSBFilter) { - delete m_DSBFilter; - } - - if (m_SSBFilterBuffer) { - delete m_SSBFilterBuffer; - } - - if (m_DSBFilterBuffer) { - delete m_DSBFilterBuffer; - } - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + + delete m_SSBFilter; + delete m_DSBFilter; + delete[] m_SSBFilterBuffer; + delete[] m_DSBFilterBuffer; } void SSBMod::pull(Sample& sample) diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index 09b4c5074..6aa022f28 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -98,13 +98,13 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : WFMMod::~WFMMod() { - delete m_rfFilter; - delete[] m_rfFilterBuffer; DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_rfFilter; + delete[] m_rfFilterBuffer; } void WFMMod::pull(Sample& sample) diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index 41bbe2542..93e7769c3 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -82,12 +82,12 @@ UDPSink::UDPSink(DeviceSinkAPI *deviceAPI) : UDPSink::~UDPSink() { - delete[] m_SSBFilterBuffer; - delete m_SSBFilter; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_SSBFilter; + delete[] m_SSBFilterBuffer; } void UDPSink::start() From f8251ecb507a810dd40e5b0496a6e1656411493f Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 19 Apr 2018 00:43:29 +0200 Subject: [PATCH 291/956] UDPSink: fixed wrong sample sizes based on I/Q actual sample size that can now be 16 or 32 bits --- plugins/channeltx/udpsink/udpsink.cpp | 6 +-- plugins/channeltx/udpsink/udpsink.h | 8 ++-- .../channeltx/udpsink/udpsinkudphandler.cpp | 40 +++++++++++++------ plugins/channeltx/udpsink/udpsinkudphandler.h | 7 ++-- swagger/sdrangel/examples/tx_test.py | 15 +++++-- 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index 93e7769c3..0acb9b186 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -175,7 +175,7 @@ void UDPSink::modulateSample() } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFM) { - FixReal t; + qint16 t; readMonoSample(t); m_inMovingAverage.feed((t*t)/1073741824.0); @@ -198,7 +198,7 @@ void UDPSink::modulateSample() } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAM) { - FixReal t; + qint16 t; readMonoSample(t); m_inMovingAverage.feed((t*t)/(SDR_TX_SCALED*SDR_TX_SCALED)); m_inMagsq = m_inMovingAverage.average(); @@ -219,7 +219,7 @@ void UDPSink::modulateSample() } else if ((m_settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB)) { - FixReal t; + qint16 t; Complex c, ci; fftfilt::cmplx *filtered; int n_out = 0; diff --git a/plugins/channeltx/udpsink/udpsink.h b/plugins/channeltx/udpsink/udpsink.h index e9166156b..f2b4bd741 100644 --- a/plugins/channeltx/udpsink/udpsink.h +++ b/plugins/channeltx/udpsink/udpsink.h @@ -300,14 +300,14 @@ private: } } - inline void readMonoSample(FixReal& t) + inline void readMonoSample(qint16& t) { - Sample s; if (m_settings.m_stereoInput) { - m_udpHandler.readSample(s); - t = ((s.m_real + s.m_imag) * m_settings.m_gainIn) / 2; + AudioSample a; + m_udpHandler.readSample(a); + t = ((a.l + a.r) * m_settings.m_gainIn) / 2; } else { diff --git a/plugins/channeltx/udpsink/udpsinkudphandler.cpp b/plugins/channeltx/udpsink/udpsinkudphandler.cpp index b37134d10..da3f647a4 100644 --- a/plugins/channeltx/udpsink/udpsinkudphandler.cpp +++ b/plugins/channeltx/udpsink/udpsinkudphandler.cpp @@ -33,7 +33,7 @@ UDPSinkUDPHandler::UDPSinkUDPHandler() : m_udpDumpIndex(0), m_nbUDPFrames(m_minNbUDPFrames), m_nbAllocatedUDPFrames(m_minNbUDPFrames), - m_writeIndex(0), + m_writeFrameIndex(0), m_readFrameIndex(m_minNbUDPFrames/2), m_readIndex(0), m_rwDelta(m_minNbUDPFrames/2), @@ -129,31 +129,45 @@ void UDPSinkUDPHandler::dataReadyRead() void UDPSinkUDPHandler::moveData(char *blk) { - memcpy(m_udpBuf[m_writeIndex], blk, m_udpBlockSize); + memcpy(m_udpBuf[m_writeFrameIndex], blk, m_udpBlockSize); - if (m_writeIndex < m_nbUDPFrames - 1) { - m_writeIndex++; + if (m_writeFrameIndex < m_nbUDPFrames - 1) { + m_writeFrameIndex++; } else { - m_writeIndex = 0; + m_writeFrameIndex = 0; } } -void UDPSinkUDPHandler::readSample(FixReal &t) +void UDPSinkUDPHandler::readSample(qint16 &t) { - if (m_readFrameIndex == m_writeIndex) // block until more writes + if (m_readFrameIndex == m_writeFrameIndex) // block until more writes { t = 0; } else { - memcpy(&t, &m_udpBuf[m_readFrameIndex][m_readIndex], sizeof(FixReal)); - advanceReadPointer((int) sizeof(FixReal)); + memcpy(&t, &m_udpBuf[m_readFrameIndex][m_readIndex], sizeof(qint16)); + advanceReadPointer((int) sizeof(qint16)); + } +} + +void UDPSinkUDPHandler::readSample(AudioSample &a) +{ + if (m_readFrameIndex == m_writeFrameIndex) // block until more writes + { + a.l = 0; + a.r = 0; + } + else + { + memcpy(&a, &m_udpBuf[m_readFrameIndex][m_readIndex], sizeof(AudioSample)); + advanceReadPointer((int) sizeof(AudioSample)); } } void UDPSinkUDPHandler::readSample(Sample &s) { - if (m_readFrameIndex == m_writeIndex) // block until more writes + if (m_readFrameIndex == m_writeFrameIndex) // block until more writes { s.m_real = 0; s.m_imag = 0; @@ -181,7 +195,7 @@ void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) } else { - m_rwDelta = m_writeIndex; // raw R/W delta estimate + m_rwDelta = m_writeFrameIndex; // raw R/W delta estimate int nbUDPFrames2 = m_nbUDPFrames/2; float d = (m_rwDelta - nbUDPFrames2)/(float) m_nbUDPFrames; //qDebug("UDPSinkUDPHandler::advanceReadPointer: w: %02d d: %f", m_writeIndex, d); @@ -233,7 +247,7 @@ void UDPSinkUDPHandler::applyUDPLink(const QString& address, quint16 port) void UDPSinkUDPHandler::resetReadIndex() { - m_readFrameIndex = (m_writeIndex + (m_nbUDPFrames/2)) % m_nbUDPFrames; + m_readFrameIndex = (m_writeFrameIndex + (m_nbUDPFrames/2)) % m_nbUDPFrames; m_rwDelta = m_nbUDPFrames/2; m_readIndex = 0; m_d = 0.0f; @@ -252,7 +266,7 @@ void UDPSinkUDPHandler::resizeBuffer(float sampleRate) } m_nbUDPFrames = 2*halfNbFrames; - m_writeIndex = 0; + m_writeFrameIndex = 0; resetReadIndex(); } diff --git a/plugins/channeltx/udpsink/udpsinkudphandler.h b/plugins/channeltx/udpsink/udpsinkudphandler.h index 52de91143..e75cca5b3 100644 --- a/plugins/channeltx/udpsink/udpsinkudphandler.h +++ b/plugins/channeltx/udpsink/udpsinkudphandler.h @@ -40,8 +40,9 @@ public: void resetReadIndex(); void resizeBuffer(float sampleRate); - void readSample(FixReal &t); - void readSample(Sample &s); + void readSample(qint16 &t); //!< audio mono + void readSample(AudioSample &a); //!< audio stereo + void readSample(Sample &s); //!< I/Q stream void setAutoRWBalance(bool autoRWBalance) { m_autoRWBalance = autoRWBalance; } void setFeedbackMessageQueue(MessageQueue *messageQueue) { m_feedbackMessageQueue = messageQueue; } @@ -104,7 +105,7 @@ private: int m_udpDumpIndex; int m_nbUDPFrames; int m_nbAllocatedUDPFrames; - int m_writeIndex; + int m_writeFrameIndex; int m_readFrameIndex; int m_readIndex; int m_rwDelta; diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index 4332402ee..e1720ade6 100644 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -26,6 +26,7 @@ def getInputOptions(): parser.add_option("-f", "--channel-freq", dest="channel_freq", help="channel center frequency (Hz)", metavar="FREQ", type="int") parser.add_option("-s", "--sample-rate", dest="sample_rate", help="host to device sample rate (S/s)", metavar="RATE", type="int") parser.add_option("-l", "--log2-interp", dest="log2_interp", help="log2 of interpolation factor", metavar="RATE", type="int") + parser.add_option("-L", "--log2-interp-hard", dest="log2_interp_hard", help="log2 of hardware interpolation factor", metavar="RATE", type="int") parser.add_option("-A", "--antenna-path", dest="antenna_path", help="antenna path number", metavar="NUMBER", type="int") parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="CREATE", action="store_true", default=False) parser.add_option("--ppm", dest="lo_ppm", help="LO correction in ppm", metavar="FILENAME", type="float", default=0) @@ -55,6 +56,9 @@ def getInputOptions(): if options.log2_interp == None: options.log2_interp = 4 + if options.log2_interp_hard == None: + options.log2_interp = 4 + if options.antenna_path == None: options.antenna_path = 1 @@ -121,7 +125,7 @@ def setupDevice(options): elif options.device_hwid == "LimeSDR": settings["limeSdrOutputSettings"]["antennaPath"] = options.antenna_path settings["limeSdrOutputSettings"]["devSampleRate"] = options.sample_rate - settings["limeSdrOutputSettings"]["log2HardInterp"] = 4 + settings["limeSdrOutputSettings"]["log2HardInterp"] = options.log2_interp_hard settings["limeSdrOutputSettings"]["log2SoftInterp"] = options.log2_interp settings["limeSdrOutputSettings"]["centerFrequency"] = options.device_freq*1000 + 500000 settings["limeSdrOutputSettings"]["ncoEnable"] = 1 @@ -129,6 +133,7 @@ def setupDevice(options): settings["limeSdrOutputSettings"]["lpfBW"] = 4050000 settings["limeSdrOutputSettings"]["lpfFIRBW"] = 100000 settings["limeSdrOutputSettings"]["lpfFIREnable"] = 1 + settings["limeSdrOutputSettings"]["gain"] = 17 elif options.device_hwid == "HackRF": settings['hackRFOutputSettings']['LOppmTenths'] = round(options.lo_ppm*10) settings['hackRFOutputSettings']['centerFrequency'] = options.device_freq*1000 @@ -208,13 +213,15 @@ def setupChannel(options): settings["UDPSinkSettings"]["title"] = "Test UDP Sink" settings["UDPSinkSettings"]["inputFrequencyOffset"] = options.channel_freq settings["UDPSinkSettings"]["rfBandwidth"] = 12500 - settings["UDPSinkSettings"]["fmDeviation"] = 5000 - settings["UDPSinkSettings"]["autoRWBalance"] = 0 + settings["UDPSinkSettings"]["fmDeviation"] = 6000 + settings["UDPSinkSettings"]["autoRWBalance"] = 1 settings["UDPSinkSettings"]["stereoInput"] = 0 settings["UDPSinkSettings"]["udpAddress"] = "127.0.0.1" settings["UDPSinkSettings"]["udpPort"] = 9998 - settings["UDPSinkSettings"]["inputSampleRate"] = 24000 + settings["UDPSinkSettings"]["inputSampleRate"] = 48000 settings["UDPSinkSettings"]["sampleFormat"] = 1 # FormatNFM + settings["UDPSinkSettings"]["gainIn"] = 2.5 + settings["UDPSinkSettings"]["gainOut"] = 2.8 elif options.channel_id == "WFMMod": settings["WFMModSettings"]["title"] = "Test WFM" settings["WFMModSettings"]["inputFrequencyOffset"] = options.channel_freq From da362823dce770a2ed0b2a0b89b7e07cf17e1c09 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 19 Apr 2018 00:52:01 +0200 Subject: [PATCH 292/956] UDPSink: use neutral denomination for I/Q sample size with sample size format --- plugins/channeltx/udpsink/readme.md | 2 +- plugins/channeltx/udpsink/udpsink.cpp | 2 +- plugins/channeltx/udpsink/udpsinkgui.cpp | 6 +++--- plugins/channeltx/udpsink/udpsinkgui.ui | 2 +- plugins/channeltx/udpsink/udpsinksettings.cpp | 2 +- plugins/channeltx/udpsink/udpsinksettings.h | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/channeltx/udpsink/readme.md b/plugins/channeltx/udpsink/readme.md index 8c81da302..d578d1329 100644 --- a/plugins/channeltx/udpsink/readme.md +++ b/plugins/channeltx/udpsink/readme.md @@ -42,7 +42,7 @@ Sample rate in samples per second of the signal that is recveived on UDP. The ac Combo box to specify the type of samples that are received and sent in the channel. - - `S16LE I/Q`: Raw I/Q samples on signed 16 bits integers with Little Endian layout. Use it with software that sends I/Q data as output like GNUradio with the `UDP source` block. The output is interleaved I and Q samples. It can also match the UDP source plugin with the same `S16LE I/Q` format and can be used for linear transposition. + - `SnLE I/Q`: Raw I/Q samples on signed 16 or 24 bits integers with Little Endian layout. Use it with software that sends I/Q data as output like GNUradio with the `UDP source` block. The output is interleaved I and Q samples. It can also match the UDP source plugin with the same `S16LE I/Q` format and compiled for the same sample I/Q size and can be used for linear transposition. - `S16LE NFM`: receives 16 bits signed integers on 1 (mono) or 2 (stereo) channels with Little Endian layout. It produces a mono signal with narrow bandwidth FM modulation. Stereo input channels are mixed before modulation. There is no DC block so it can be used with modulating signals where the DC component is important like digital signals. - `S16LE LSB`: Takes a 1 (mono) or 2 (stereo) channels AF signal and produces a LSB modulated signal. Stereo input channels are mixed before modulation. - `S16LE USB`: Takes a 1 (mono) or 2 (stereo) channels AF signal and produces a USB modulated signal. Stereo input channels are mixed before modulation. diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index 0acb9b186..890749b7e 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -149,7 +149,7 @@ void UDPSink::pull(Sample& sample) void UDPSink::modulateSample() { - if (m_settings.m_sampleFormat == UDPSinkSettings::FormatS16LE) // Linear I/Q transponding + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatSnLE) // Linear I/Q transponding { Sample s; diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index 02c370346..701fdf9a9 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -492,7 +492,7 @@ void UDPSinkGUI::setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampl { switch(sampleFormat) { - case UDPSinkSettings::FormatS16LE: + case UDPSinkSettings::FormatSnLE: ui->sampleFormat->setCurrentIndex(0); ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); @@ -532,7 +532,7 @@ void UDPSinkGUI::setSampleFormat(int index) switch(index) { case 0: - m_settings.m_sampleFormat = UDPSinkSettings::FormatS16LE; + m_settings.m_sampleFormat = UDPSinkSettings::FormatSnLE; ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); ui->stereoInput->setEnabled(false); @@ -558,7 +558,7 @@ void UDPSinkGUI::setSampleFormat(int index) ui->stereoInput->setEnabled(true); break; default: - m_settings.m_sampleFormat = UDPSinkSettings::FormatS16LE; + m_settings.m_sampleFormat = UDPSinkSettings::FormatSnLE; ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); ui->stereoInput->setEnabled(false); diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsink/udpsinkgui.ui index f7a3d3492..0a547742d 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsink/udpsinkgui.ui @@ -825,7 +825,7 @@ - S16LE I/Q + SnLE I/Q diff --git a/plugins/channeltx/udpsink/udpsinksettings.cpp b/plugins/channeltx/udpsink/udpsinksettings.cpp index df9f16af4..2c0ca7a56 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.cpp +++ b/plugins/channeltx/udpsink/udpsinksettings.cpp @@ -30,7 +30,7 @@ UDPSinkSettings::UDPSinkSettings() : void UDPSinkSettings::resetToDefaults() { - m_sampleFormat = FormatS16LE; + m_sampleFormat = FormatSnLE; m_inputSampleRate = 48000; m_inputFrequencyOffset = 0; m_rfBandwidth = 12500; diff --git a/plugins/channeltx/udpsink/udpsinksettings.h b/plugins/channeltx/udpsink/udpsinksettings.h index e060752a0..9442fe627 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.h +++ b/plugins/channeltx/udpsink/udpsinksettings.h @@ -26,7 +26,7 @@ class Serializable; struct UDPSinkSettings { enum SampleFormat { - FormatS16LE, + FormatSnLE, FormatNFM, FormatLSB, FormatUSB, From befc08f2e12faeb2c72ee2545e51e24cb97d177b Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 19 Apr 2018 13:34:22 +0200 Subject: [PATCH 293/956] UDP source: send audio samples always on 16 bits. Options to send raw I/Q in either 16 or 24 bits regardless of sample size at compile time --- plugins/channelrx/udpsrc/udpsrc.cpp | 8 +-- plugins/channelrx/udpsrc/udpsrc.h | 37 +++++-------- plugins/channelrx/udpsrc/udpsrcgui.cpp | 59 ++++++++++----------- plugins/channelrx/udpsrc/udpsrcgui.h | 1 - plugins/channelrx/udpsrc/udpsrcgui.ui | 24 +++------ plugins/channelrx/udpsrc/udpsrcplugin.cpp | 2 +- plugins/channelrx/udpsrc/udpsrcsettings.cpp | 16 ++---- plugins/channelrx/udpsrc/udpsrcsettings.h | 10 +--- plugins/channeltx/udpsink/udpsinkplugin.cpp | 2 +- 9 files changed, 54 insertions(+), 105 deletions(-) diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index dc2c1ab24..09370909b 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -61,7 +61,6 @@ UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : m_udpBuffer16 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); m_udpBufferMono16 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); m_udpBuffer24 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); - m_udpBufferMono24 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); m_audioSocket = new QUdpSocket(this); m_udpAudioBuf = new char[m_udpAudioPayloadSize]; @@ -113,7 +112,6 @@ UDPSrc::~UDPSrc() { delete m_audioSocket; delete m_udpBuffer24; - delete m_udpBufferMono24; delete m_udpBuffer16; delete m_udpBufferMono16; delete[] m_udpAudioBuf; @@ -153,7 +151,8 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: if ((m_settings.m_agc) && (m_settings.m_sampleFormat != UDPSrcSettings::FormatNFM) && (m_settings.m_sampleFormat != UDPSrcSettings::FormatNFMMono) && - (m_settings.m_sampleFormat != UDPSrcSettings::FormatIQ)) + (m_settings.m_sampleFormat != UDPSrcSettings::FormatIQ16) && + (m_settings.m_sampleFormat != UDPSrcSettings::FormatIQ24)) { agcFactor = m_agc.feedAndGetValue(ci); inMagSq = m_agc.getMagSq(); @@ -494,7 +493,6 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) << " m_squelchGate" << settings.m_squelchGate << " m_agc" << settings.m_agc << " m_sampleFormat: " << settings.m_sampleFormat - << " m_sampleSize: " << 16 + settings.m_sampleSize*8 << " m_outputSampleRate: " << settings.m_outputSampleRate << " m_rfBandwidth: " << settings.m_rfBandwidth << " m_fmDeviation: " << settings.m_fmDeviation @@ -582,7 +580,6 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_udpBuffer16->setAddress(const_cast(settings.m_udpAddress)); m_udpBufferMono16->setAddress(const_cast(settings.m_udpAddress)); m_udpBuffer24->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferMono24->setAddress(const_cast(settings.m_udpAddress)); } if ((settings.m_udpPort != m_settings.m_udpPort) || force) @@ -590,7 +587,6 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_udpBuffer16->setPort(settings.m_udpPort); m_udpBufferMono16->setPort(settings.m_udpPort); m_udpBuffer24->setPort(settings.m_udpPort); - m_udpBufferMono24->setPort(settings.m_udpPort); } if ((settings.m_audioPort != m_settings.m_audioPort) || force) diff --git a/plugins/channelrx/udpsrc/udpsrc.h b/plugins/channelrx/udpsrc/udpsrc.h index 9e9864d4e..8ef8f75dc 100644 --- a/plugins/channelrx/udpsrc/udpsrc.h +++ b/plugins/channelrx/udpsrc/udpsrc.h @@ -186,7 +186,6 @@ protected: UDPSink *m_udpBuffer16; UDPSink *m_udpBufferMono16; UDPSink *m_udpBuffer24; - UDPSink *m_udpBufferMono24; AudioVector m_audioBuffer; uint m_audioBufferFill; @@ -281,18 +280,22 @@ protected: { if (SDR_RX_SAMP_SZ == 16) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { + if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ16) { m_udpBuffer16->write(Sample16(real, imag)); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { + } else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ24) { m_udpBuffer24->write(Sample24(real<<8, imag<<8)); + } else { + m_udpBuffer16->write(Sample16(real, imag)); } } else if (SDR_RX_SAMP_SZ == 24) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { + if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ16) { m_udpBuffer16->write(Sample16(real>>8, imag>>8)); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { + } else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ24) { m_udpBuffer24->write(Sample24(real, imag)); + } else { + m_udpBuffer16->write(Sample16(real>>8, imag>>8)); } } } @@ -301,38 +304,22 @@ protected: { if (SDR_RX_SAMP_SZ == 16) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { - m_udpBufferMono16->write(sample); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { - m_udpBufferMono24->write(sample<<8); - } + m_udpBufferMono16->write(sample); } else if (SDR_RX_SAMP_SZ == 24) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { - m_udpBufferMono16->write(sample>>8); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { - m_udpBufferMono24->write(sample); - } + m_udpBufferMono16->write(sample>>8); } } void udpWriteNorm(Real real, Real imag) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { - m_udpBuffer16->write(Sample16(real*32768.0, imag*32768.0)); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { - m_udpBuffer24->write(Sample24(real*8388608.0, imag*8388608.0)); - } + m_udpBuffer16->write(Sample16(real*32768.0, imag*32768.0)); } void udpWriteNormMono(Real sample) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { - m_udpBufferMono16->write(sample*32768.0); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { - m_udpBufferMono24->write(sample*8388608.0); - } + m_udpBufferMono16->write(sample*32768.0); } }; diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsrc/udpsrcgui.cpp index 518b6cb17..045b13029 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsrc/udpsrcgui.cpp @@ -251,36 +251,39 @@ void UDPSrcGUI::setSampleFormatIndex(const UDPSrcSettings::SampleFormat& sampleF { switch(sampleFormat) { - case UDPSrcSettings::FormatIQ: + case UDPSrcSettings::FormatIQ16: ui->sampleFormat->setCurrentIndex(0); break; - case UDPSrcSettings::FormatNFM: + case UDPSrcSettings::FormatIQ24: ui->sampleFormat->setCurrentIndex(1); break; - case UDPSrcSettings::FormatNFMMono: + case UDPSrcSettings::FormatNFM: ui->sampleFormat->setCurrentIndex(2); break; - case UDPSrcSettings::FormatLSB: + case UDPSrcSettings::FormatNFMMono: ui->sampleFormat->setCurrentIndex(3); break; - case UDPSrcSettings::FormatUSB: + case UDPSrcSettings::FormatLSB: ui->sampleFormat->setCurrentIndex(4); break; - case UDPSrcSettings::FormatLSBMono: + case UDPSrcSettings::FormatUSB: ui->sampleFormat->setCurrentIndex(5); break; - case UDPSrcSettings::FormatUSBMono: + case UDPSrcSettings::FormatLSBMono: ui->sampleFormat->setCurrentIndex(6); break; - case UDPSrcSettings::FormatAMMono: + case UDPSrcSettings::FormatUSBMono: ui->sampleFormat->setCurrentIndex(7); break; - case UDPSrcSettings::FormatAMNoDCMono: + case UDPSrcSettings::FormatAMMono: ui->sampleFormat->setCurrentIndex(8); break; - case UDPSrcSettings::FormatAMBPFMono: + case UDPSrcSettings::FormatAMNoDCMono: ui->sampleFormat->setCurrentIndex(9); break; + case UDPSrcSettings::FormatAMBPFMono: + ui->sampleFormat->setCurrentIndex(10); + break; default: ui->sampleFormat->setCurrentIndex(0); break; @@ -292,47 +295,51 @@ void UDPSrcGUI::setSampleFormat(int index) switch(index) { case 0: - m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ; + m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ16; ui->fmDeviation->setEnabled(false); break; case 1: + m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ24; + ui->fmDeviation->setEnabled(false); + break; + case 2: m_settings.m_sampleFormat = UDPSrcSettings::FormatNFM; ui->fmDeviation->setEnabled(true); break; - case 2: + case 3: m_settings.m_sampleFormat = UDPSrcSettings::FormatNFMMono; ui->fmDeviation->setEnabled(true); break; - case 3: + case 4: m_settings.m_sampleFormat = UDPSrcSettings::FormatLSB; ui->fmDeviation->setEnabled(false); break; - case 4: + case 5: m_settings.m_sampleFormat = UDPSrcSettings::FormatUSB; ui->fmDeviation->setEnabled(false); break; - case 5: + case 6: m_settings.m_sampleFormat = UDPSrcSettings::FormatLSBMono; ui->fmDeviation->setEnabled(false); break; - case 6: + case 7: m_settings.m_sampleFormat = UDPSrcSettings::FormatUSBMono; ui->fmDeviation->setEnabled(false); break; - case 7: + case 8: m_settings.m_sampleFormat = UDPSrcSettings::FormatAMMono; ui->fmDeviation->setEnabled(false); break; - case 8: + case 9: m_settings.m_sampleFormat = UDPSrcSettings::FormatAMNoDCMono; ui->fmDeviation->setEnabled(false); break; - case 9: + case 10: m_settings.m_sampleFormat = UDPSrcSettings::FormatAMBPFMono; ui->fmDeviation->setEnabled(false); break; default: - m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ; + m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ16; ui->fmDeviation->setEnabled(false); break; } @@ -384,18 +391,6 @@ void UDPSrcGUI::on_sampleFormat_currentIndexChanged(int index) ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_sampleSize_currentIndexChanged(int index) -{ - if ((index < 0) || (index >= UDPSrcSettings::SizeNone)) { - return; - } - - m_settings.m_sampleSize = (UDPSrcSettings::SampleSize) index; - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - void UDPSrcGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; diff --git a/plugins/channelrx/udpsrc/udpsrcgui.h b/plugins/channelrx/udpsrc/udpsrcgui.h index 0ae8fec13..e6fef1ad2 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.h +++ b/plugins/channelrx/udpsrc/udpsrcgui.h @@ -94,7 +94,6 @@ private: private slots: void on_deltaFrequency_changed(qint64 value); void on_sampleFormat_currentIndexChanged(int index); - void on_sampleSize_currentIndexChanged(int index); void on_sampleRate_textEdited(const QString& arg1); void on_rfBandwidth_textEdited(const QString& arg1); void on_fmDeviation_textEdited(const QString& arg1); diff --git a/plugins/channelrx/udpsrc/udpsrcgui.ui b/plugins/channelrx/udpsrc/udpsrcgui.ui index 8b50c2c30..19ef8a26c 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.ui +++ b/plugins/channelrx/udpsrc/udpsrcgui.ui @@ -386,7 +386,12 @@ - I/Q + I/Q 16bits + + + + + I/Q 24bits @@ -436,23 +441,6 @@ - - - - Samples size - - - - 16 bits - - - - - 24 bits - - - - diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.cpp b/plugins/channelrx/udpsrc/udpsrcplugin.cpp index 032b7868b..0ad6887e7 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.cpp +++ b/plugins/channelrx/udpsrc/udpsrcplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor UDPSrcPlugin::m_pluginDescriptor = { QString("UDP Channel Source"), - QString("3.12.0"), + QString("3.14.3"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/udpsrc/udpsrcsettings.cpp b/plugins/channelrx/udpsrc/udpsrcsettings.cpp index eb28df536..31578c657 100644 --- a/plugins/channelrx/udpsrc/udpsrcsettings.cpp +++ b/plugins/channelrx/udpsrc/udpsrcsettings.cpp @@ -31,8 +31,7 @@ UDPSrcSettings::UDPSrcSettings() : void UDPSrcSettings::resetToDefaults() { m_outputSampleRate = 48000; - m_sampleFormat = FormatIQ; - m_sampleSize = Size16bits; + m_sampleFormat = FormatIQ16; m_inputFrequencyOffset = 0; m_rfBandwidth = 12500; m_fmDeviation = 2500; @@ -78,7 +77,6 @@ QByteArray UDPSrcSettings::serialize() const s.writeS32(17, m_squelchGate); s.writeBool(18, m_agc); s.writeString(19, m_title); - s.writeS32(20, (int) m_sampleFormat); return s.final(); @@ -108,12 +106,12 @@ bool UDPSrcSettings::deserialize(const QByteArray& data) d.readS32(2, &s32tmp, 0); m_inputFrequencyOffset = s32tmp; - d.readS32(3, &s32tmp, FormatIQ); + d.readS32(3, &s32tmp, FormatIQ16); if ((s32tmp >= 0) && (s32tmp < (int) FormatNone)) { m_sampleFormat = (SampleFormat) s32tmp; } else { - m_sampleFormat = FormatIQ; + m_sampleFormat = FormatIQ16; } d.readReal(4, &m_outputSampleRate, 48000.0); @@ -136,14 +134,6 @@ bool UDPSrcSettings::deserialize(const QByteArray& data) d.readBool(18, &m_agc, false); d.readString(19, &m_title, "UDP Sample Source"); - d.readS32(20, &s32tmp, Size16bits); - - if ((s32tmp >= 0) && (s32tmp < (int) SizeNone)) { - m_sampleSize = (SampleSize) s32tmp; - } else { - m_sampleSize = Size16bits; - } - return true; } else diff --git a/plugins/channelrx/udpsrc/udpsrcsettings.h b/plugins/channelrx/udpsrc/udpsrcsettings.h index 7286d5fcf..b268b47c4 100644 --- a/plugins/channelrx/udpsrc/udpsrcsettings.h +++ b/plugins/channelrx/udpsrc/udpsrcsettings.h @@ -26,7 +26,8 @@ class Serializable; struct UDPSrcSettings { enum SampleFormat { - FormatIQ, + FormatIQ16, + FormatIQ24, FormatNFM, FormatNFMMono, FormatLSB, @@ -39,15 +40,8 @@ struct UDPSrcSettings FormatNone }; - enum SampleSize { - Size16bits, - Size24bits, - SizeNone - }; - float m_outputSampleRate; SampleFormat m_sampleFormat; - SampleSize m_sampleSize; int64_t m_inputFrequencyOffset; float m_rfBandwidth; int m_fmDeviation; diff --git a/plugins/channeltx/udpsink/udpsinkplugin.cpp b/plugins/channeltx/udpsink/udpsinkplugin.cpp index 959feead3..e02545094 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.cpp +++ b/plugins/channeltx/udpsink/udpsinkplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { QString("UDP Channel Sink"), - QString("3.14.2"), + QString("3.14.3"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 15f7c4d80e7f6c95875d2aef42f2dea9654121c1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 19 Apr 2018 23:03:21 +0200 Subject: [PATCH 294/956] UDP source and sink: added dialogs to specify addresses and ports --- debian/changelog | 7 +- plugins/channelrx/udpsrc/udpsrcgui.cpp | 50 ++++++ plugins/channelrx/udpsrc/udpsrcgui.h | 3 + plugins/channelrx/udpsrc/udpsrcgui.ui | 147 ++++++++++++++---- plugins/channelrx/udpsrc/udpsrcsettings.cpp | 25 ++- plugins/channeltx/udpsink/udpsinkgui.cpp | 27 +++- plugins/channeltx/udpsink/udpsinkgui.h | 2 + plugins/channeltx/udpsink/udpsinkgui.ui | 69 +++++++- plugins/channeltx/udpsink/udpsinksettings.cpp | 4 +- sdrgui/gui/audiodialog.ui | 4 +- 10 files changed, 292 insertions(+), 46 deletions(-) diff --git a/debian/changelog b/debian/changelog index 483635497..d9df8abf2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,12 @@ sdrangel (3.14.3-1) unstable; urgency=medium * LimeSDR: compiled with LimeSuite commit 67dcef1 - * LimeSDR: implemented transverter dialog + * LimeSDR: implemented transverter dialog (issue #157) + * UDP source and sink: make sure audio samples are always on 16 bits + * UDP source and sink: dialog elements for address and port + * Reviewed FFT destruction in many channel sources and sinks (issue #159) - -- Edouard Griffiths, F4EXB Sat, 21 Apr 2018 18:14:18 +0200 + -- Edouard Griffiths, F4EXB Fri, 20 Apr 2018 20:14:18 +0200 sdrangel (3.14.2-1) unstable; urgency=medium diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsrc/udpsrcgui.cpp index 045b13029..b19b4bb83 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsrc/udpsrcgui.cpp @@ -219,11 +219,17 @@ void UDPSrcGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); + blockApplySettings(true); + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); ui->sampleRate->setText(QString("%1").arg(m_settings.m_outputSampleRate, 0)); setSampleFormatIndex(m_settings.m_sampleFormat); + ui->outputUDPAddress->setText(m_settings.m_udpAddress); + ui->outputUDPPort->setText(tr("%1").arg(m_settings.m_udpPort)); + ui->inputUDPAudioPort->setText(tr("%1").arg(m_settings.m_audioPort)); + ui->squelch->setValue(m_settings.m_squelchdB); ui->squelchText->setText(tr("%1").arg(ui->squelch->value()*1.0, 0, 'f', 0)); @@ -244,6 +250,11 @@ void UDPSrcGUI::displaySettings() ui->gain->setValue(m_settings.m_gain*10.0); ui->gainText->setText(tr("%1").arg(ui->gain->value()/10.0, 0, 'f', 1)); + ui->applyBtn->setEnabled(false); + ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); + + blockApplySettings(false); + ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); } @@ -391,6 +402,45 @@ void UDPSrcGUI::on_sampleFormat_currentIndexChanged(int index) ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } +void UDPSrcGUI::on_outputUDPAddress_editingFinished() +{ + m_settings.m_udpAddress = ui->outputUDPAddress->text(); + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + +void UDPSrcGUI::on_outputUDPPort_editingFinished() +{ + bool ok; + quint16 udpPort = ui->outputUDPPort->text().toInt(&ok); + + if((!ok) || (udpPort < 1024)) { + udpPort = 9998; + } + + m_settings.m_udpPort = udpPort; + ui->outputUDPPort->setText(tr("%1").arg(m_settings.m_udpPort)); + + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + +void UDPSrcGUI::on_inputUDPAudioPort_editingFinished() +{ + bool ok; + quint16 udpPort = ui->inputUDPAudioPort->text().toInt(&ok); + + if((!ok) || (udpPort < 1024)) { + udpPort = 9997; + } + + m_settings.m_audioPort = udpPort; + ui->inputUDPAudioPort->setText(tr("%1").arg(m_settings.m_audioPort)); + + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + void UDPSrcGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; diff --git a/plugins/channelrx/udpsrc/udpsrcgui.h b/plugins/channelrx/udpsrc/udpsrcgui.h index e6fef1ad2..12035dbb9 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.h +++ b/plugins/channelrx/udpsrc/udpsrcgui.h @@ -94,6 +94,9 @@ private: private slots: void on_deltaFrequency_changed(qint64 value); void on_sampleFormat_currentIndexChanged(int index); + void on_outputUDPAddress_editingFinished(); + void on_outputUDPPort_editingFinished(); + void on_inputUDPAudioPort_editingFinished(); void on_sampleRate_textEdited(const QString& arg1); void on_rfBandwidth_textEdited(const QString& arg1); void on_fmDeviation_textEdited(const QString& arg1); diff --git a/plugins/channelrx/udpsrc/udpsrcgui.ui b/plugins/channelrx/udpsrc/udpsrcgui.ui index 19ef8a26c..d920278bd 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.ui +++ b/plugins/channelrx/udpsrc/udpsrcgui.ui @@ -6,7 +6,7 @@ 0 0 - 354 + 383 355 @@ -39,13 +39,13 @@ 2 2 - 350 + 380 142 - 350 + 380 0 @@ -53,16 +53,7 @@ Settings - - 2 - - - 2 - - - 2 - - + 2 @@ -331,7 +322,7 @@ - + 30 @@ -339,23 +330,71 @@ - Addr + Ad - + - 170 + 120 0 - - UDP <address>:<data port>/<audio port> + + Destinationn UDP address + + + 000.000.000.000; - 00.000.000.000:0000/0000 + 127.0.0.1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + P + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Destination UDP port + + + 00000; + + + 9998 @@ -378,6 +417,18 @@ + + + 120 + 0 + + + + + 120 + 16777215 + + Samples format @@ -441,6 +492,51 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Au + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Audio input UDP port + + + 00000; + + + 9997 + + + @@ -721,16 +817,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 diff --git a/plugins/channelrx/udpsrc/udpsrcsettings.cpp b/plugins/channelrx/udpsrc/udpsrcsettings.cpp index 31578c657..6a1bdf856 100644 --- a/plugins/channelrx/udpsrc/udpsrcsettings.cpp +++ b/plugins/channelrx/udpsrc/udpsrcsettings.cpp @@ -45,8 +45,8 @@ void UDPSrcSettings::resetToDefaults() m_audioStereo = false; m_volume = 20; m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; - m_audioPort = 9998; + m_udpPort = 9998; + m_audioPort = 9997; m_rgbColor = QColor(225, 25, 99).rgb(); m_title = "UDP Sample Source"; } @@ -77,6 +77,9 @@ QByteArray UDPSrcSettings::serialize() const s.writeS32(17, m_squelchGate); s.writeBool(18, m_agc); s.writeString(19, m_title); + s.writeString(20, m_udpAddress); + s.writeU32(21, m_udpPort); + s.writeU32(22, m_audioPort); return s.final(); @@ -97,6 +100,7 @@ bool UDPSrcSettings::deserialize(const QByteArray& data) QByteArray bytetmp; QString strtmp; int32_t s32tmp; + quint32 u32tmp; if (m_channelMarker) { d.readBlob(6, &bytetmp); @@ -133,6 +137,23 @@ bool UDPSrcSettings::deserialize(const QByteArray& data) d.readS32(17, &m_squelchGate, 5); d.readBool(18, &m_agc, false); d.readString(19, &m_title, "UDP Sample Source"); + d.readString(20, &m_udpAddress, "127.0.0.1"); + + d.readU32(21, &u32tmp, 9998); + + if ((u32tmp > 1024) & (u32tmp < 65538)) { + m_udpPort = u32tmp; + } else { + m_udpPort = 9998; + } + + d.readU32(22, &u32tmp, 9997); + + if ((u32tmp > 1024) & (u32tmp < 65538)) { + m_audioPort = u32tmp; + } else { + m_audioPort = 9997; + } return true; } diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index 701fdf9a9..3bfa7e620 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -243,7 +243,8 @@ void UDPSinkGUI::displaySettings() ui->squelchGateText->setText(tr("%1").arg(roundf(m_settings.m_squelchGate * 1000.0), 0, 'f', 0)); ui->squelchGate->setValue(roundf(m_settings.m_squelchGate * 100.0)); - ui->addressText->setText(tr("%1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); + ui->localUDPAddress->setText(m_settings.m_udpAddress); + ui->localUDPPort->setText(tr("%1").arg(m_settings.m_udpPort)); ui->applyBtn->setEnabled(false); ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); @@ -285,6 +286,29 @@ void UDPSinkGUI::on_sampleFormat_currentIndexChanged(int index) ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } +void UDPSinkGUI::on_localUDPAddress_editingFinished() +{ + m_settings.m_udpAddress = ui->localUDPAddress->text(); + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + +void UDPSinkGUI::on_localUDPPort_editingFinished() +{ + bool ok; + quint16 udpPort = ui->localUDPPort->text().toInt(&ok); + + if((!ok) || (udpPort < 1024)) { + udpPort = 9998; + } + + m_settings.m_udpPort = udpPort; + ui->localUDPPort->setText(tr("%1").arg(m_settings.m_udpPort)); + + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + void UDPSinkGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; @@ -445,7 +469,6 @@ void UDPSinkGUI::onMenuDialogCalled(const QPoint &p) setWindowTitle(m_channelMarker.getTitle()); setTitleColor(m_settings.m_rgbColor); - ui->addressText->setText(tr("%1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); applySettings(); } diff --git a/plugins/channeltx/udpsink/udpsinkgui.h b/plugins/channeltx/udpsink/udpsinkgui.h index c70001189..f8190df50 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.h +++ b/plugins/channeltx/udpsink/udpsinkgui.h @@ -90,6 +90,8 @@ private slots: void handleSourceMessages(); void on_deltaFrequency_changed(qint64 value); void on_sampleFormat_currentIndexChanged(int index); + void on_localUDPAddress_editingFinished(); + void on_localUDPPort_editingFinished(); void on_sampleRate_textEdited(const QString& arg1); void on_rfBandwidth_textEdited(const QString& arg1); void on_fmDeviation_textEdited(const QString& arg1); diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsink/udpsinkgui.ui index 0a547742d..92a6d60b3 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsink/udpsinkgui.ui @@ -770,7 +770,39 @@ - + + + A + + + + + + + + 110 + 0 + + + + + 110 + 16777215 + + + + Local UDP address + + + 000.000.000.000; + + + 127.0.0.1 + + + + + 35 @@ -778,26 +810,51 @@ - Addr + : - + - 150 + 50 0 + + + 50 + 16777215 + + + + false + - Receiving UDP address and port + Local UDP port + + + 00000; - 000.000.000.000:00000 + 9998 + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/plugins/channeltx/udpsink/udpsinksettings.cpp b/plugins/channeltx/udpsink/udpsinksettings.cpp index 2c0ca7a56..7bcbdab13 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.cpp +++ b/plugins/channeltx/udpsink/udpsinksettings.cpp @@ -145,12 +145,12 @@ bool UDPSinkSettings::deserialize(const QByteArray& data) m_gainIn = s32tmp / 10.0; d.readString(18, &m_udpAddress, "127.0.0.1"); - d.readU32(19, &u32tmp, 10); + d.readU32(19, &u32tmp, 9998); if ((u32tmp > 1024) & (u32tmp < 65538)) { m_udpPort = u32tmp; } else { - m_udpPort = 9999; + m_udpPort = 9998; } d.readString(20, &m_title, "UDP Sample Sink"); diff --git a/sdrgui/gui/audiodialog.ui b/sdrgui/gui/audiodialog.ui index ce6312a16..d995c3b04 100644 --- a/sdrgui/gui/audiodialog.ui +++ b/sdrgui/gui/audiodialog.ui @@ -136,7 +136,7 @@ - UDP address + Destination UDP address 000.000.000.000; @@ -162,7 +162,7 @@ - UDP port + DestinationUDP port 00000; From 8c891a191a653d6891dcf319eec96c7d3a86d69c Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 20 Apr 2018 00:17:10 +0200 Subject: [PATCH 295/956] UDP source and sink: ensure proper click focus on line edits --- plugins/channelrx/udpsrc/udpsrcgui.ui | 9 +++++++++ plugins/channeltx/udpsink/udpsinkgui.ui | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/plugins/channelrx/udpsrc/udpsrcgui.ui b/plugins/channelrx/udpsrc/udpsrcgui.ui index d920278bd..e2da29a74 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.ui +++ b/plugins/channelrx/udpsrc/udpsrcgui.ui @@ -342,6 +342,9 @@ 0 + + Qt::ClickFocus + Destinationn UDP address @@ -387,6 +390,9 @@ 16777215 + + Qt::ClickFocus + Destination UDP port @@ -526,6 +532,9 @@ 16777215 + + Qt::ClickFocus + Audio input UDP port diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsink/udpsinkgui.ui index 92a6d60b3..cd9049d87 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsink/udpsinkgui.ui @@ -609,6 +609,9 @@ + + Qt::ClickFocus + Mute/Unmute channel (green when on air) @@ -790,6 +793,9 @@ 16777215 + + Qt::ClickFocus + Local UDP address @@ -828,6 +834,9 @@ 16777215 + + Qt::ClickFocus + false From 20fbac5621f2296fa3c830168ea67a8263e43580 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 20 Apr 2018 09:15:30 +0200 Subject: [PATCH 296/956] LimeSDR: updated documentation --- Readme.md | 8 +++----- debian/changelog | 2 +- plugins/samplesink/limesdroutput/readme.md | 4 +++- plugins/samplesource/limesdrinput/readme.md | 4 ++++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index 578c46a10..7c3c29c1a 100644 --- a/Readme.md +++ b/Readme.md @@ -41,7 +41,7 @@ From version 3 transmission or signal generation is supported for BladeRF, HackR - [BladeRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerfoutput) limited support in Windows - [HackRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/hackrfoutput) - - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) not working properly + - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) - [PlutoSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/plutosdroutput) - [File output or file sink plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/filesink) - [Remote device via Network with SDRdaemon](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) Linux only @@ -119,11 +119,9 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3. [LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next). -

⚠ The latest version of LimeSuite is used and must be considered experimental. For now only the Rx side is operational.

+

⚠ Version 18.04.1 of LimeSuite is used in the builds and corresponding gateware loaded in the LimeSDR should be is used (2.16 for LimeSDR-USB and 1.24 for LimeSDR-Mini). If you compile from source version 18.04.1 of LimeSuite must be used.

-

⚠ It seems LimeSDR mini has trouble working with host sample rates lower than 2.5 MS/s particularly in Tx mode.

- -You will need a minimal installation of LimeSuite. Presently commit 90c3991 or later should be used with its corresponding firmware (v4) and gateware (v2.14) installed in the LimeSDR device. LimeSDR Mini gateware v1.24 should be used. +

⚠ LimeSDR-Mini seems to have problems with Zadig driver therefore it is supported in Linux only.

- `sudo apt-get install libsqlite3-dev` - `git clone https://github.com/myriadrf/LimeSuite.git` diff --git a/debian/changelog b/debian/changelog index d9df8abf2..517edb9c3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ sdrangel (3.14.3-1) unstable; urgency=medium - * LimeSDR: compiled with LimeSuite commit 67dcef1 + * LimeSDR: compiled with LimeSuite release 18.04.1 * LimeSDR: implemented transverter dialog (issue #157) * UDP source and sink: make sure audio samples are always on 16 bits * UDP source and sink: dialog elements for address and port diff --git a/plugins/samplesink/limesdroutput/readme.md b/plugins/samplesink/limesdroutput/readme.md index 0c779dcfb..25c5791ef 100644 --- a/plugins/samplesink/limesdroutput/readme.md +++ b/plugins/samplesink/limesdroutput/readme.md @@ -4,7 +4,9 @@ This output sample sink plugin sends its samples to a [LimeSDR device](https://myriadrf.org/projects/limesdr/). -

⚠ The latest version of LimeSuite is used and must be considered experimental. Hence LimeSDR support in SDRangel is also experimental. Tx does not work properly now.

+

⚠ Version 18.04.1 of LimeSuite is used in the buildsand corresponding gateware loaded in the LimeSDR should be is used (2.16 for LimeSDR-USB and 1.24 for LimeSDR-Mini). If you compile from source version 18.04.1 of LimeSuite must be used.

+ +

⚠ LimeSDR-Mini seems to have problems with Zadig driver therefore it is supported in Linux only.

LimeSDR is a 2x2 MIMO device so it has two transmitting channels that can run concurrently. To activate the second channel when the first is already active just open a new sink tab in the main window (Devices -> Add sink device) and select the same LimeSDR device. diff --git a/plugins/samplesource/limesdrinput/readme.md b/plugins/samplesource/limesdrinput/readme.md index 05a332abb..b10565d3c 100644 --- a/plugins/samplesource/limesdrinput/readme.md +++ b/plugins/samplesource/limesdrinput/readme.md @@ -4,6 +4,10 @@ This input sample source plugin gets its samples from a [LimeSDR device](https://myriadrf.org/projects/limesdr/). +

⚠ Version 18.04.1 of LimeSuite is used in the builds and corresponding gateware loaded in the LimeSDR should be is used (2.16 for LimeSDR-USB and 1.24 for LimeSDR-Mini). If you compile from source version 18.04.1 of LimeSuite must be used.

+ +

⚠ LimeSDR-Mini seems to have problems with Zadig driver therefore it is supported in Linux only.

+ LimeSDR is a 2x2 MIMO device so it has two receiving channels that can run concurrently. To activate the second channel when the first is already active just open a new source tab in the main window (Devices -> Add source device) and select the same LimeSDR device.

Build

From 685fa7947e973aa72c3abc90eec998993a231032 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 20 Apr 2018 13:09:35 +0200 Subject: [PATCH 297/956] Debian build: update on LimeSuite build --- liblimesuite/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/liblimesuite/CMakeLists.txt b/liblimesuite/CMakeLists.txt index b29fdba47..3125d5dad 100644 --- a/liblimesuite/CMakeLists.txt +++ b/liblimesuite/CMakeLists.txt @@ -17,14 +17,12 @@ set(limesuite_SOURCES ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_BaseCalibrations.cpp ${LIBLIMESUITESRC}/src/lms7002m/goert.cpp ${LIBLIMESUITESRC}/src/lms7002m/mcu_dc_iq_calibration.cpp - ${LIBLIMESUITESRC}/src/lms7002m/CalibrationCache.cpp ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_filtersCalibration.cpp ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_gainCalibrations.cpp ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.cpp ${LIBLIMESUITESRC}/src/protocols/Streamer.cpp ${LIBLIMESUITESRC}/src/protocols/ConnectionImages.cpp ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.cpp - ${LIBLIMESUITESRC}/src/kissFFT/kiss_fft.c ${LIBLIMESUITESRC}/src/API/lms7_api.cpp ${LIBLIMESUITESRC}/src/API/lms7_device.cpp ${LIBLIMESUITESRC}/src/API/LmsGeneric.cpp @@ -60,7 +58,6 @@ set(limesuite_HEADERS ${LIBLIMESUITESRC}/src/lms7002m/*.h ${LIBLIMESUITESRC}/src/FPGA_common/*.h ${LIBLIMESUITESRC}/src/HPM7/*.h - ${LIBLIMESUITESRC}/src/kissFFT/*.h ) include_directories( From 145d0cad38e57ed2b65c0a6c7cff9f9c53906fd6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 20 Apr 2018 19:07:18 +0200 Subject: [PATCH 298/956] LimeSuite: Windows build fixes --- liblimesuite/liblimesuite.pro | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/liblimesuite/liblimesuite.pro b/liblimesuite/liblimesuite.pro index 3e0d140e0..a810c3336 100644 --- a/liblimesuite/liblimesuite.pro +++ b/liblimesuite/liblimesuite.pro @@ -48,14 +48,12 @@ SOURCES = $$LIBLIMESUITESRC/src/Logger.cpp\ $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_BaseCalibrations.cpp\ $$LIBLIMESUITESRC/src/lms7002m/goert.cpp\ $$LIBLIMESUITESRC/src/lms7002m/mcu_dc_iq_calibration.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/CalibrationCache.cpp\ $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_filtersCalibration.cpp\ $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_gainCalibrations.cpp\ $$LIBLIMESUITESRC/src/protocols/LMS64CProtocol.cpp\ $$LIBLIMESUITESRC/src/protocols/Streamer.cpp\ $$LIBLIMESUITESRC/src/protocols/ConnectionImages.cpp\ $$LIBLIMESUITESRC/src/Si5351C/Si5351C.cpp\ - $$LIBLIMESUITESRC/src/kissFFT/kiss_fft.c\ $$LIBLIMESUITESRC/src/API/lms7_api.cpp\ $$LIBLIMESUITESRC/src/API/lms7_device.cpp\ $$LIBLIMESUITESRC/src/API/LmsGeneric.cpp\ @@ -88,8 +86,7 @@ HEADERS = $$LIBLIMESUITESRC/src/API/*.h\ $$LIBLIMESUITESRC/src/Si5351C/*.h\ $$LIBLIMESUITESRC/src/lms7002m/*.h\ $$LIBLIMESUITESRC/src/FPGA_common/*.h\ - $$LIBLIMESUITESRC/src/HPM7/*.h\ - $$LIBLIMESUITESRC/src/kissFFT/*.h + $$LIBLIMESUITESRC/src/HPM7/*.h CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 From 12f5f4e30c270410e3ad1cd15198084d39483415 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 21 Apr 2018 09:23:01 +0200 Subject: [PATCH 299/956] NFM demod: buffered squelch --- app/main.cpp | 2 +- appsrv/main.cpp | 2 +- plugins/channelrx/demodnfm/nfmdemod.cpp | 43 +++++++----- plugins/channelrx/demodnfm/nfmdemod.h | 2 + plugins/channelrx/demodnfm/nfmplugin.cpp | 2 +- sdrbase/CMakeLists.txt | 2 +- sdrbase/util/doublebufferfifo.h | 84 ++++++++++++++++++++++++ 7 files changed, 116 insertions(+), 21 deletions(-) create mode 100644 sdrbase/util/doublebufferfifo.h diff --git a/app/main.cpp b/app/main.cpp index 14164fd21..a843b4f10 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.14.3"); + QCoreApplication::setApplicationVersion("3.14.4"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 608ede061..3c82592f2 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.14.3"); + QCoreApplication::setApplicationVersion("3.14.4"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 38e0eec9d..7aa4bebbe 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -57,7 +57,7 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_sampleCount(0), m_squelchCount(0), m_squelchGate(4800), - m_squelchDecay(480), + m_squelchDecay(4800), m_squelchLevel(-990), m_squelchOpen(false), m_afSquelchOpen(false), @@ -66,6 +66,7 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_magsqPeak(0.0f), m_magsqCount(0), m_afSquelch(), + m_squelchDelayLine(24000), m_audioFifo(48000), m_settingsMutex(QMutex::Recursive) { @@ -178,22 +179,29 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_settings.m_deltaSquelch) { - if (m_afSquelch.analyze(demod * m_discriCompensation)) { - m_afSquelchOpen = m_afSquelch.evaluate() ? m_squelchGate + m_squelchDecay : 0; + if (m_afSquelch.analyze(demod * m_discriCompensation)) + { + m_afSquelchOpen = m_afSquelch.evaluate(); // ? m_squelchGate + m_squelchDecay : 0; + + if (!m_afSquelchOpen) { + m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period + } } if (m_afSquelchOpen) { - if (m_squelchCount < m_squelchGate + m_squelchDecay) - { + m_squelchDelayLine.write(demod * m_discriCompensation); + + if (m_squelchCount < m_squelchGate + m_squelchDecay) { m_squelchCount++; } } else { - if (m_squelchCount > 0) - { - m_squelchCount -= 10; + m_squelchDelayLine.write(0); + + if (m_squelchCount > 0) { + m_squelchCount--; } } } @@ -201,15 +209,17 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { if ((Real) m_movingAverage < m_squelchLevel) { - if (m_squelchCount > 0) - { - m_squelchCount -= 10; + m_squelchDelayLine.write(0); + + if (m_squelchCount > 0) { + m_squelchCount--; } } else { - if (m_squelchCount < m_squelchGate + m_squelchDecay) - { + m_squelchDelayLine.write(demod * m_discriCompensation); + + if (m_squelchCount < m_squelchGate + m_squelchDecay) { m_squelchCount++; } } @@ -261,9 +271,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { - demod = m_bandpass.filter(demod * m_discriCompensation); - Real squelchFactor = StepFunctions::smootherstep((Real) (m_squelchCount - m_squelchGate) / (Real) m_squelchDecay); - sample = demod * m_settings.m_volume * squelchFactor; + sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume; } } else @@ -413,7 +421,7 @@ void NFMDemod::applyAudioSampleRate(int sampleRate) m_lowpass.create(301, sampleRate, 250.0); m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate - m_squelchDecay = (sampleRate / 100); // decay is fixed at 10ms + m_squelchDecay = m_squelchGate; m_squelchCount = 0; // reset squelch open counter m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution @@ -429,6 +437,7 @@ void NFMDemod::applyAudioSampleRate(int sampleRate) m_phaseDiscri.setFMScaling(sampleRate / static_cast(m_settings.m_fmDeviation)); m_audioFifo.setSize(sampleRate); + m_squelchDelayLine.resize(sampleRate/2); m_settingsMutex.unlock(); diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index 132b1ee25..cec6cb1aa 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -34,6 +34,7 @@ #include "audio/audiofifo.h" #include "util/message.h" #include "util/movingaverage.h" +#include "util/doublebufferfifo.h" #include "nfmdemodsettings.h" @@ -207,6 +208,7 @@ private: MovingAverageUtil m_movingAverage; AFSquelch m_afSquelch; Real m_agcLevel; // AGC will aim to this level + DoubleBufferFIFO m_squelchDelayLine; AudioVector m_audioBuffer; uint m_audioBufferFill; diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index 47feaed9f..690aa3b1c 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("3.14.2"), + QString("3.14.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index ec51b06f3..0a95fe91a 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -170,7 +170,7 @@ set(sdrbase_HEADERS util/CRC64.h util/db.h util/doublebuffer.h - #export.h + util/doublebufferfifo.h util/fixedtraits.h util/message.h util/messagequeue.h diff --git a/sdrbase/util/doublebufferfifo.h b/sdrbase/util/doublebufferfifo.h new file mode 100644 index 000000000..58c8b3929 --- /dev/null +++ b/sdrbase/util/doublebufferfifo.h @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 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 // +// // +// 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 SDRBASE_UTIL_DOUBLEBUFFERFIFO_H_ +#define SDRBASE_UTIL_DOUBLEBUFFERFIFO_H_ + + +template +class DoubleBufferFIFO +{ +public: + DoubleBufferFIFO(int size) : m_size(size), m_writeIndex(0), m_currentIndex(0) + { + m_data = new T[2*m_size]; + } + + ~DoubleBufferFIFO() + { + delete[] m_data; + } + + void resize(int size) + { + delete[] m_data; + m_size = size; + m_data = new T[2*m_size]; + m_writeIndex = 0; + m_currentIndex = 0; + } + + void write(const T& element) + { + m_data[m_writeIndex] = element; + m_data[m_writeIndex+m_size] = element; + m_currentIndex = m_writeIndex; + + if (m_writeIndex < m_size - 1) { + m_writeIndex++; + } else { + m_writeIndex = 0; + } + } + + T& readBack(int delay) + { + if (delay > m_size) { + delay = m_size; + } + + return m_data[m_currentIndex + m_size - delay]; + } + + void zeroBack(int delay) + { + if (delay > m_size) { + delay = m_size; + } + + for (int i = 0; i < delay; i++) { + m_data[m_currentIndex + m_size - i] = 0; + } + } + +private: + int m_size; + T *m_data; + int m_writeIndex; + int m_currentIndex; +}; + +#endif /* SDRBASE_UTIL_DOUBLEBUFFERFIFO_H_ */ From 84538f1acf76f4a0a38034884d5a2bec05b75570 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 21 Apr 2018 09:56:12 +0200 Subject: [PATCH 300/956] DSD demod: use buffered squelch to start decoding at the very beginning of the transmission regardless of squelch gate length --- plugins/channelrx/demoddsd/dsddemod.cpp | 10 +- plugins/channelrx/demoddsd/dsddemod.h | 419 ++++++++++++------------ 2 files changed, 218 insertions(+), 211 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 1cc5d941c..1dde3f556 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -50,6 +50,7 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : m_squelchGate(0), m_squelchLevel(1e-4), m_squelchOpen(false), + m_squelchDelayLine(24000), m_audioFifo1(48000), m_audioFifo2(48000), m_scopeXY(0), @@ -143,6 +144,8 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_movingAverage.asDouble() > m_squelchLevel) { + m_squelchDelayLine.write(demod); + if (m_squelchGate > 0) { if (m_squelchCount < m_squelchGate) { @@ -158,14 +161,17 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { + m_squelchDelayLine.write(0); m_squelchCount = 0; m_squelchOpen = false; } if (m_squelchOpen) { - sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples - sample = demod * SDR_RX_SCALEF; // scale to sample size + sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f; // DSD decoder takes int16 samples + sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size +// sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples +// sample = demod * SDR_RX_SCALEF; // scale to sample size } else { diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index bd98370ca..f7c71d21a 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -1,209 +1,210 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2016 F4EXB // -// written by Edouard Griffiths // -// // -// 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_DSDDEMOD_H -#define INCLUDE_DSDDEMOD_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/phasediscri.h" -#include "dsp/nco.h" -#include "dsp/interpolator.h" -#include "dsp/lowpass.h" -#include "dsp/bandpass.h" -#include "dsp/afsquelch.h" -#include "util/movingaverage.h" -#include "dsp/afsquelch.h" -#include "audio/audiofifo.h" -#include "util/message.h" -#include "util/udpsink.h" - -#include "dsddemodsettings.h" -#include "dsddecoder.h" - -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -class DSDDemod : public BasebandSampleSink, public ChannelSinkAPI { -public: - class MsgConfigureDSDDemod : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const DSDDemodSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureDSDDemod* create(const DSDDemodSettings& settings, bool force) - { - return new MsgConfigureDSDDemod(settings, force); - } - - private: - DSDDemodSettings m_settings; - bool m_force; - - MsgConfigureDSDDemod(const DSDDemodSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - DSDDemod(DeviceSourceAPI *deviceAPI); - ~DSDDemod(); - virtual void destroy() { delete this; } - void setScopeXYSink(BasebandSampleSink* sampleSink) { m_scopeXY = sampleSink; } - - void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude); - - 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 = m_settings.m_title; } - virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - double getMagSq() { return m_magsq; } - bool getSquelchOpen() const { return m_squelchOpen; } - - const DSDDecoder& getDecoder() const { return m_dsdDecoder; } - - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - class MsgConfigureMyPosition : public Message { - MESSAGE_CLASS_DECLARATION - - public: - float getMyLatitude() const { return m_myLatitude; } - float getMyLongitude() const { return m_myLongitude; } - - static MsgConfigureMyPosition* create(float myLatitude, float myLongitude) - { - return new MsgConfigureMyPosition(myLatitude, myLongitude); - } - - private: - float m_myLatitude; - float m_myLongitude; - - MsgConfigureMyPosition(float myLatitude, float myLongitude) : - m_myLatitude(myLatitude), - m_myLongitude(myLongitude) - {} - }; - - enum RateState { - RSInitialFill, - RSRunning - }; - - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; - DSDDemodSettings m_settings; - quint32 m_audioSampleRate; - - NCO m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - int m_sampleCount; - int m_squelchCount; - int m_squelchGate; - - double m_squelchLevel; - bool m_squelchOpen; - - MovingAverageUtil m_movingAverage; - double m_magsq; - double m_magsqSum; - double m_magsqPeak; - int m_magsqCount; - - SampleVector m_scopeSampleBuffer; - AudioVector m_audioBuffer; - uint m_audioBufferFill; - FixReal *m_sampleBuffer; //!< samples ring buffer - int m_sampleBufferIndex; - int m_scaleFromShort; - - AudioFifo m_audioFifo1; - AudioFifo m_audioFifo2; - BasebandSampleSink* m_scopeXY; - bool m_scopeEnabled; - - DSDDecoder m_dsdDecoder; - QMutex m_settingsMutex; - - PhaseDiscriminators m_phaseDiscri; - - static const int m_udpBlockSize; - - void applyAudioSampleRate(int sampleRate); - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const DSDDemodSettings& settings, bool force = false); -}; - -#endif // INCLUDE_DSDDEMOD_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 F4EXB // +// written by Edouard Griffiths // +// // +// 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_DSDDEMOD_H +#define INCLUDE_DSDDEMOD_H + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "dsp/phasediscri.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/lowpass.h" +#include "dsp/bandpass.h" +#include "dsp/afsquelch.h" +#include "util/movingaverage.h" +#include "dsp/afsquelch.h" +#include "audio/audiofifo.h" +#include "util/message.h" +#include "util/udpsink.h" +#include "util/doublebufferfifo.h" + +#include "dsddemodsettings.h" +#include "dsddecoder.h" + +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; + +class DSDDemod : public BasebandSampleSink, public ChannelSinkAPI { +public: + class MsgConfigureDSDDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DSDDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDSDDemod* create(const DSDDemodSettings& settings, bool force) + { + return new MsgConfigureDSDDemod(settings, force); + } + + private: + DSDDemodSettings m_settings; + bool m_force; + + MsgConfigureDSDDemod(const DSDDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + DSDDemod(DeviceSourceAPI *deviceAPI); + ~DSDDemod(); + virtual void destroy() { delete this; } + void setScopeXYSink(BasebandSampleSink* sampleSink) { m_scopeXY = sampleSink; } + + void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude); + + 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 = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + double getMagSq() { return m_magsq; } + bool getSquelchOpen() const { return m_squelchOpen; } + + const DSDDecoder& getDecoder() const { return m_dsdDecoder; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; + m_magsq = avg; + peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + class MsgConfigureMyPosition : public Message { + MESSAGE_CLASS_DECLARATION + + public: + float getMyLatitude() const { return m_myLatitude; } + float getMyLongitude() const { return m_myLongitude; } + + static MsgConfigureMyPosition* create(float myLatitude, float myLongitude) + { + return new MsgConfigureMyPosition(myLatitude, myLongitude); + } + + private: + float m_myLatitude; + float m_myLongitude; + + MsgConfigureMyPosition(float myLatitude, float myLongitude) : + m_myLatitude(myLatitude), + m_myLongitude(myLongitude) + {} + }; + + enum RateState { + RSInitialFill, + RSRunning + }; + + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + + int m_inputSampleRate; + int m_inputFrequencyOffset; + DSDDemodSettings m_settings; + quint32 m_audioSampleRate; + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + int m_sampleCount; + int m_squelchCount; + int m_squelchGate; + double m_squelchLevel; + bool m_squelchOpen; + DoubleBufferFIFO m_squelchDelayLine; + + MovingAverageUtil m_movingAverage; + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + + SampleVector m_scopeSampleBuffer; + AudioVector m_audioBuffer; + uint m_audioBufferFill; + FixReal *m_sampleBuffer; //!< samples ring buffer + int m_sampleBufferIndex; + int m_scaleFromShort; + + AudioFifo m_audioFifo1; + AudioFifo m_audioFifo2; + BasebandSampleSink* m_scopeXY; + bool m_scopeEnabled; + + DSDDecoder m_dsdDecoder; + QMutex m_settingsMutex; + + PhaseDiscriminators m_phaseDiscri; + + static const int m_udpBlockSize; + + void applyAudioSampleRate(int sampleRate); + void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); + void applySettings(const DSDDemodSettings& settings, bool force = false); +}; + +#endif // INCLUDE_DSDDEMOD_H From b4a77e08c8907137941e4b54b89d2783c3a8e845 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 21 Apr 2018 17:22:07 +0200 Subject: [PATCH 301/956] DSD demod: symmetrical attack and decay for squelch like NFM --- plugins/channelrx/demoddsd/dsddemod.cpp | 38 +++++++++++++------ plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 1dde3f556..481716fb2 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -144,15 +144,15 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_movingAverage.asDouble() > m_squelchLevel) { - m_squelchDelayLine.write(demod); - if (m_squelchGate > 0) { - if (m_squelchCount < m_squelchGate) { + + if (m_squelchCount < m_squelchGate*2) { m_squelchCount++; } - m_squelchOpen = m_squelchCount == m_squelchGate; + m_squelchDelayLine.write(demod); + m_squelchOpen = m_squelchCount > m_squelchGate; } else { @@ -161,17 +161,33 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { - m_squelchDelayLine.write(0); - m_squelchCount = 0; - m_squelchOpen = false; + if (m_squelchGate > 0) + { + if (m_squelchCount > 0) { + m_squelchCount--; + } + + m_squelchDelayLine.write(0); + m_squelchOpen = m_squelchCount > m_squelchGate; + } + else + { + m_squelchOpen = false; + } } if (m_squelchOpen) { - sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f; // DSD decoder takes int16 samples - sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size -// sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples -// sample = demod * SDR_RX_SCALEF; // scale to sample size + if (m_squelchGate > 0) + { + sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f; // DSD decoder takes int16 samples + sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size + } + else + { + sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples + sample = demod * SDR_RX_SCALEF; // scale to sample size + } } else { diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index beff73f71..c4b190b5e 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("3.14.1"), + QString("3.14.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From f6ea7b216e82bfb4fd5cbac9d614798490a49354 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Sat, 21 Apr 2018 14:14:14 -0700 Subject: [PATCH 302/956] Fix spelling errors present in UI files --- plugins/channelrx/chanalyzer/chanalyzergui.ui | 2 +- .../channelrx/chanalyzerng/chanalyzernggui.ui | 2 +- plugins/channelrx/demodam/amdemodgui.ui | 2 +- plugins/channelrx/demodatv/atvdemodgui.ui | 2 +- plugins/channelrx/demodbfm/bfmdemodgui.ui | 24 +++++++++---------- plugins/channelrx/demoddatv/datvdemodgui.ui | 2 +- plugins/channelrx/demodssb/ssbdemodgui.ui | 6 ++--- plugins/channelrx/udpsrc/udpsrcgui.ui | 2 +- plugins/channeltx/modatv/atvmodgui.ui | 2 +- plugins/channeltx/modssb/ssbmodgui.ui | 4 ++-- .../hackrfoutput/hackrfoutputgui.ui | 4 ++-- .../sdrdaemonsink/sdrdaemonsinkgui.ui | 2 +- .../hackrfinput/hackrfinputgui.ui | 4 ++-- plugins/samplesource/rtlsdr/rtlsdrgui.ui | 2 +- .../sdrdaemonsource/sdrdaemonsourcegui.ui | 2 +- .../samplesource/testsource/testsourcegui.ui | 2 +- sdrgui/gui/audiodialog.ui | 2 +- sdrgui/gui/glscopemultigui.ui | 4 ++-- sdrgui/gui/glscopenggui.ui | 4 ++-- 19 files changed, 37 insertions(+), 37 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index bec5d53ad..e05f3592b 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -266,7 +266,7 @@ - SSB/DSB togggle + SSB/DSB toggle SSB diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index 448ad44b1..7f04bd6ad 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -397,7 +397,7 @@ - SSB/DSB togggle + SSB/DSB toggle SSB diff --git a/plugins/channelrx/demodam/amdemodgui.ui b/plugins/channelrx/demodam/amdemodgui.ui index a21902430..ba656f815 100644 --- a/plugins/channelrx/demodam/amdemodgui.ui +++ b/plugins/channelrx/demodam/amdemodgui.ui @@ -227,7 +227,7 @@ - Toggle boxcar bandpass filter with 300 Hz low cuttof + Toggle boxcar bandpass filter with 300 Hz low cutoff diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index 84dd0db70..be545a93e 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -377,7 +377,7 @@ - Engage asymmertical bandpass FFT filter + Engage asymmetrical bandpass FFT filter diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.ui b/plugins/channelrx/demodbfm/bfmdemodgui.ui index 8eeaa78d0..36476f2a1 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.ui +++ b/plugins/channelrx/demodbfm/bfmdemodgui.ui @@ -617,7 +617,7 @@ - Lits if synchronized + Lights if synchronized Dec @@ -1091,7 +1091,7 @@ - Lits if PI is updated + Lights if PI is updated PI @@ -1223,7 +1223,7 @@ - Lits if group 1 is updated + Lights if group 1 is updated G01 @@ -1322,7 +1322,7 @@ - Lits if group 0 is updated + Lights if group 0 is updated G00 @@ -1446,7 +1446,7 @@ - Alternalte frequencies (MHz) + Alternate frequencies (MHz) @@ -1479,7 +1479,7 @@ - Lits if group 3 updated is updated + Lights if group 3 updated is updated G03 @@ -1525,7 +1525,7 @@ - Lits if group 9 is updated + Lights if group 9 is updated G09 @@ -1568,7 +1568,7 @@ - Lits if group 2 is updated + Lights if group 2 is updated G02 @@ -1602,7 +1602,7 @@ - Lits if group 4 is updated + Lights if group 4 is updated G04 @@ -1670,7 +1670,7 @@ - Lits if group 14 is updated + Lights if group 14 is updated G14 @@ -1719,7 +1719,7 @@ - Station altermate freqiuencies + Station alternate frequencies @@ -1736,7 +1736,7 @@ - Lits if group 8 is updated + Lights if group 8 is updated G08 diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index fb0284f41..605bcc1c8 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -349,7 +349,7 @@ - 64APSKe + 64APSK diff --git a/plugins/channelrx/demodssb/ssbdemodgui.ui b/plugins/channelrx/demodssb/ssbdemodgui.ui index d204799e1..7ecb9be9a 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.ui +++ b/plugins/channelrx/demodssb/ssbdemodgui.ui @@ -689,7 +689,7 @@ - Togle AGC + Toggle AGC AGC @@ -809,7 +809,7 @@ - Power threshiold gate (ms) + Power threshold gate (ms) 20 @@ -831,7 +831,7 @@ - Power threshiold gate (ms) + Power threshold gate (ms) 00 diff --git a/plugins/channelrx/udpsrc/udpsrcgui.ui b/plugins/channelrx/udpsrc/udpsrcgui.ui index e2da29a74..4365f9077 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.ui +++ b/plugins/channelrx/udpsrc/udpsrcgui.ui @@ -346,7 +346,7 @@ Qt::ClickFocus - Destinationn UDP address + Destination UDP address 000.000.000.000; diff --git a/plugins/channeltx/modatv/atvmodgui.ui b/plugins/channeltx/modatv/atvmodgui.ui index e311e0f1d..674a0ac3a 100644 --- a/plugins/channeltx/modatv/atvmodgui.ui +++ b/plugins/channeltx/modatv/atvmodgui.ui @@ -130,7 +130,7 @@ - Force decimaor usage + Force decimator usage diff --git a/plugins/channeltx/modssb/ssbmodgui.ui b/plugins/channeltx/modssb/ssbmodgui.ui index 8b51bf8a9..3bd0bd02f 100644 --- a/plugins/channeltx/modssb/ssbmodgui.ui +++ b/plugins/channeltx/modssb/ssbmodgui.ui @@ -711,7 +711,7 @@ - AGC volume order in faraction of maximum amplitude + AGC volume order in fraction of maximum amplitude 100 @@ -727,7 +727,7 @@ - AGC volume order in faraction of maximum amplitude + AGC volume order in fraction of maximum amplitude 0.00 diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui index 68e0763d9..4b8c889ee 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui +++ b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui @@ -182,7 +182,7 @@ - Local Oscullator ppm correction + Local Oscillator ppm correction -300 @@ -264,7 +264,7 @@ - RF bandpas filter + RF bandpass filter diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index aa388601f..676cebecc 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -485,7 +485,7 @@ - Number of uncrecoverable errors since event counts reset + Number of unrecoverable errors since event counts reset 000 diff --git a/plugins/samplesource/hackrfinput/hackrfinputgui.ui b/plugins/samplesource/hackrfinput/hackrfinputgui.ui index dc2d7d917..0fd2172f7 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputgui.ui +++ b/plugins/samplesource/hackrfinput/hackrfinputgui.ui @@ -193,7 +193,7 @@ - Local Oscullator ppm correction + Local Oscillator ppm correction -300 @@ -461,7 +461,7 @@ - RF bandpas filter + RF bandpass filter diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.ui b/plugins/samplesource/rtlsdr/rtlsdrgui.ui index 2b9775381..25431b5a5 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrgui.ui +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.ui @@ -260,7 +260,7 @@ - Relative postion of device center frequency + Relative position of device center frequency 2 diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui index 645408a19..65959b6e5 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui @@ -527,7 +527,7 @@ - Number of uncrecoverable errors since event counts reset + Number of unrecoverable errors since event counts reset 000 diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui index 13e13e02d..546f3a175 100644 --- a/plugins/samplesource/testsource/testsourcegui.ui +++ b/plugins/samplesource/testsource/testsourcegui.ui @@ -287,7 +287,7 @@ - Relative postion of generator center frequency + Relative position of generator center frequency 2 diff --git a/sdrgui/gui/audiodialog.ui b/sdrgui/gui/audiodialog.ui index d995c3b04..2a6a313fb 100644 --- a/sdrgui/gui/audiodialog.ui +++ b/sdrgui/gui/audiodialog.ui @@ -162,7 +162,7 @@ - DestinationUDP port + Destination UDP port 00000; diff --git a/sdrgui/gui/glscopemultigui.ui b/sdrgui/gui/glscopemultigui.ui index 253784e37..e4f029aee 100644 --- a/sdrgui/gui/glscopemultigui.ui +++ b/sdrgui/gui/glscopemultigui.ui @@ -1158,7 +1158,7 @@ kS/s - Sellect triggger in trigger chain + Select trigger in trigger chain 0 @@ -1532,7 +1532,7 @@ kS/s - Triger level + Trigger level L: diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index d09b03d22..e6560dddd 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -1115,7 +1115,7 @@ kS/s - Sellect triggger in trigger chain + Select trigger in trigger chain 0 @@ -1419,7 +1419,7 @@ kS/s - Triger level + Trigger level L: From 8cf3469c87aa8b4d6deca7cfac9a3375340d7259 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Sat, 21 Apr 2018 14:14:36 -0700 Subject: [PATCH 303/956] Fix spelling errors in documentation --- Readme.md | 22 +++++----- ReadmeDeveloper.md | 40 +++++++++---------- plugins/channelrx/chanalyzerng/readme.md | 38 +++++++++--------- plugins/channelrx/demodam/readme.md | 4 +- plugins/channelrx/demodatv/readme.md | 12 +++--- plugins/channelrx/demoddsd/readme.md | 24 +++++------ plugins/channelrx/demodnfm/readme.md | 6 +-- plugins/channelrx/demodssb/readme.md | 12 +++--- plugins/channelrx/demodwfm/readme.md | 4 +- plugins/channelrx/udpsrc/readme.md | 8 ++-- plugins/channeltx/modam/readme.md | 6 +-- plugins/channeltx/modatv/readme.md | 22 +++++----- plugins/channeltx/modnfm/readme.md | 6 +-- plugins/channeltx/modssb/readme.md | 16 ++++---- plugins/channeltx/modwfm/readme.md | 6 +-- plugins/channeltx/udpsink/readme.md | 14 +++---- plugins/samplesink/bladerfoutput/readme.md | 6 +-- plugins/samplesink/filesink/readme.md | 6 +-- plugins/samplesink/hackrfoutput/readme.md | 6 +-- plugins/samplesink/limesdroutput/readme.md | 26 ++++++------ plugins/samplesink/plutosdroutput/readme.md | 22 +++++----- plugins/samplesink/sdrdaemonsink/readme.md | 6 +-- plugins/samplesource/airspyhf/readme.md | 8 ++-- plugins/samplesource/bladerfinput/readme.md | 8 ++-- plugins/samplesource/hackrfinput/readme.md | 16 ++++---- plugins/samplesource/limesdrinput/readme.md | 32 +++++++-------- plugins/samplesource/perseus/readme.md | 14 +++---- plugins/samplesource/plutosdrinput/readme.md | 20 +++++----- plugins/samplesource/rtlsdr/readme.md | 12 +++--- .../samplesource/sdrdaemonsource/readme.md | 16 ++++---- plugins/samplesource/sdrplay/readme.md | 2 +- plugins/samplesource/testsource/readme.md | 4 +- sdrgui/audio.md | 4 +- sdrgui/readme.md | 34 ++++++++-------- swagger/sdrangel/examples/Readme.md | 2 +- 35 files changed, 242 insertions(+), 242 deletions(-) diff --git a/Readme.md b/Readme.md index 7c3c29c1a..e57fa8762 100644 --- a/Readme.md +++ b/Readme.md @@ -12,7 +12,7 @@ - master: the production branch - dev: the development branch -- legacy: the modified code from the parent application [hexameron rtl-sdrangelove](https://github.com/hexameron/rtl-sdrangelove) before a major redeisign of the code was carried out and sync was lost. +- legacy: the modified code from the parent application [hexameron rtl-sdrangelove](https://github.com/hexameron/rtl-sdrangelove) before a major redesign of the code was carried out and sync was lost.

Untested plugins

@@ -152,7 +152,7 @@ If you use your own location for libperseus-sdr install directory you need to sp [PlutoSDR](https://wiki.analog.com/university/tools/pluto) is supported with the libiio interface. This library should be installed in your system for proper build of the software and operation support. Add `libiio-dev` to the list of dependencies to install. Be aware that version 0.10 is needed and is not available yet in all distributions. You may have to compile it from source instead. -If you use your own location for libiio install directory you need to specify library and include locations. Example with `/opt/install/libiio` with the following defines on `cmake` command line: `-DLIBIIO_INCLUDE_DIR=/opt/install/libiio/include -DLIBIIO_LIBRARY=/opt/install/libiio/lib/libiio.so`. In openSuse the lib directory path would be: `-DLIBIIO_LIBRARY=/opt/install/libiio/lib64/libiio.so`. +If you use your own location for libiio install directory you need to specify library and include locations. Example with `/opt/install/libiio` with the following defines on `cmake` command line: `-DLIBIIO_INCLUDE_DIR=/opt/install/libiio/include -DLIBIIO_LIBRARY=/opt/install/libiio/lib/libiio.so`. In openSUSE the lib directory path would be: `-DLIBIIO_LIBRARY=/opt/install/libiio/lib64/libiio.so`.

RTL-SDR

@@ -182,7 +182,7 @@ These plugins do not use any hardware device connected to your system. The [File source plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/filesource) allows the playback of a recorded IQ file. Such a file is obtained using the recording feature. Click on the record button on the left of the main frequency dial to toggle recording. The file has a fixed name `test_.sdriq` created in the current directory where `` is the device tab index. -Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is alwasys available in the list of devices as `FileSource[0]` even if no physical device is connected. +Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `FileSource[0]` even if no physical device is connected. The `.sdriq` format produced are the 2x2 bytes I/Q samples with a header containing the center frequency of the baseband, the sample rate and the timestamp of the recording start. Note that this header length is a multiple of the sample size so the file can be read with a simple 2x2 bytes I/Q reader such as a GNU Radio file source block. It will just produce a short glitch at the beginning corresponding to the header data. @@ -200,11 +200,11 @@ The [Test source plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/s Linux only. -The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of the SDRdaemon receiver server `sdrdaemonrx`. See the [SDRdaemon](https://github.com/f4exb/sdrdaemon) project in this Github repository. You must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiveng center frequency. It also opens a TCP link to another port to send service messages such as setting parameters specific to the hadrware device connected to the server (default port is `9091`). The `libnanomsg` library is used to support this messaging. +The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of the SDRdaemon receiver server `sdrdaemonrx`. See the [SDRdaemon](https://github.com/f4exb/sdrdaemon) project in this Github repository. You must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiving center frequency. It also opens a TCP link to another port to send service messages such as setting parameters specific to the hardware device connected to the server (default port is `9091`). The `libnanomsg` library is used to support this messaging. The data blocks transmitted via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. -There is an automated skew rate compensation in place. During rate readjustemnt streaming can be suspended or signal glitches can occur for about one second. +There is an automated skew rate compensation in place. During rate readjustment streaming can be suspended or signal glitches can occur for about one second. This plugin will be built only if the libnanomsg and the [CM256cc library](https://github.com/f4exb/cm256cc) are installed in your system. libnanomsg is available as a dev package in most distributions For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. @@ -343,7 +343,7 @@ Install cmake version 3:

Mint

-Tested with Cinnamon 17.2. Since it is based on Ubuntu 14.04 LTS pleae follow instructions for this distribution (paragraph just above). +Tested with Cinnamon 17.2. Since it is based on Ubuntu 14.04 LTS please follow instructions for this distribution (paragraph just above).

Debian

@@ -359,7 +359,7 @@ For Debian Jessie or Stretch:

openSUSE

-This has been tested with the bleeding edge "Thumbleweed" distribution: +This has been tested with the bleeding edge "Tumbleweed" distribution: `sudo zypper install Mesa-libGL1 Mesa-libEGL-devel Mesa-libGL-devel Mesa-libGLESv1_CM-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel Mesa-libglapi-devel libOSMesa-devel` @@ -369,8 +369,8 @@ Then you should be all set to build the software with `cmake` and `make` as disc - Note1: if you are on Leap you will need a more recent g++ compiler so in place of `gcc-c++` use `gcc5-c++` or `gcc6-c++` then add the following in the cmake command: `-DCMAKE_C_COMPILER=/usr/bin/gcc-6 -DCMAKE_CXX_COMPILER=/usr/bin/g++-6` (for gcc 6) and then `-DCMAKE_INSTALL_PREFIX:PATH=...` for the custom install path (not `-DCMAKE_INSTALL_PREFIX=...`) - Note2: On Leap and aarch64 architectures you will need to build and install `libnanomsg` from [source](https://github.com/nanomsg/nanomsg) - - Note3 for udev rules: installed udev rules for BladeRF and HackRF are targetted at Debian or Ubuntu systems that have a plugdev group for USB hotplug devices. This is not the case in openSUSE. To make the udev rules file compatible just remove the `GROUP` parameter on all lines and change `MODE` parameter to `666`. - - Note4: A package has been created in OpenSUSE thanks to Martin, see: [sdrangel](https://build.opensuse.org/package/show/hardware:sdr/sdrangel). It is based on the latest release on master branch. + - Note3 for udev rules: installed udev rules for BladeRF and HackRF are targeted at Debian or Ubuntu systems that have a plugdev group for USB hotplug devices. This is not the case in openSUSE. To make the udev rules file compatible just remove the `GROUP` parameter on all lines and change `MODE` parameter to `666`. + - Note4: A package has been created in openSUSE thanks to Martin, see: [sdrangel](https://build.opensuse.org/package/show/hardware:sdr/sdrangel). It is based on the latest release on master branch.

Fedora

@@ -433,7 +433,7 @@ You can uninstall the software with `make uninstall` or `sudo make uninstall` fr

Changes from SDRangelove

-See the v1.0.1 first official relase [release notes](https://github.com/f4exb/sdrangel/releases/tag/v1.0.1) +See the v1.0.1 first official release [release notes](https://github.com/f4exb/sdrangel/releases/tag/v1.0.1)

To Do

@@ -449,4 +449,4 @@ Other ideas:

Developer's notes

-Please check the developper's specific [readme](./ReadmeDeveloper.md) +Please check the developer's specific [readme](./ReadmeDeveloper.md) diff --git a/ReadmeDeveloper.md b/ReadmeDeveloper.md index 27b15416b..9fd3e31ac 100644 --- a/ReadmeDeveloper.md +++ b/ReadmeDeveloper.md @@ -19,7 +19,7 @@ You can add `-Wno-dev` on the `cmake` command line to avoid warnings. ![SDRangel code map](./doc/img/SDRangelFlow.png) -The existing receiving flow is represented with green boxes. The futrue Tx flow with red boxes. Some classes and file paths in the Rx part were renamed to avoid collision with future Tx names in this case the old name appears below the present name in italics. +The existing receiving flow is represented with green boxes. The future Tx flow with red boxes. Some classes and file paths in the Rx part were renamed to avoid collision with future Tx names in this case the old name appears below the present name in italics.

Rx path

@@ -33,22 +33,22 @@ The existing receiving flow is represented with green boxes. The futrue Tx flow At present the following plugins are available: - - `AirspyXxx` classes in `plugins/samplesource/airspy`: Inteface with Airspy devices - - `BladeRFXxx` classes in `plugins/samplesource/bladerf`: Inteface with BladeRF devices - - `BladeRFXxx` classes in `plugins/samplesource/bladerf`: Inteface with BladeRF devices - - `FCDProXxx` classes in `plugins/samplesource/fcdpro`: Inteface with Funcube Pro devices - - `FCDProPlusXxx` classes in `plugins/samplesource/fcdproplus`: Inteface with Funcube Pro+ devices - - `HackRFXxx` classes in `plugins/samplesource/hackrf`: Inteface with HackRF devices - - `RTLSDRXxx` classes in `plugins/samplesource/rtlsdr`: Inteface with RTL-SDR devices - - `SDRDaemonXxx` classes in `plugins/samplesource/sdrdaemon`: Special inteface collecting I/Q samples from an UDP flow sent by a remote device using [SDRdaemon](https://github.com/f4exb/sdrdaemon). - - `SDRDaemonFECXxx` classes in `plugins/samplesource/sdrdaemonfec`: Special inteface collecting I/Q samples from an UDP flow sent by a remote device using [SDRdaemon](https://github.com/f4exb/sdrdaemon) with FEC protection of blocks. - - `FileSource` classes in `plugins/samplesource/filesource`: Special inteface reading I/Q samples from a file directly into the baseband skipping the downsampling block + - `AirspyXxx` classes in `plugins/samplesource/airspy`: Interface with Airspy devices + - `BladeRFXxx` classes in `plugins/samplesource/bladerf`: Interface with BladeRF devices + - `BladeRFXxx` classes in `plugins/samplesource/bladerf`: Interface with BladeRF devices + - `FCDProXxx` classes in `plugins/samplesource/fcdpro`: Interface with Funcube Pro devices + - `FCDProPlusXxx` classes in `plugins/samplesource/fcdproplus`: Interface with Funcube Pro+ devices + - `HackRFXxx` classes in `plugins/samplesource/hackrf`: Interface with HackRF devices + - `RTLSDRXxx` classes in `plugins/samplesource/rtlsdr`: Interface with RTL-SDR devices + - `SDRDaemonXxx` classes in `plugins/samplesource/sdrdaemon`: Special interface collecting I/Q samples from an UDP flow sent by a remote device using [SDRdaemon](https://github.com/f4exb/sdrdaemon). + - `SDRDaemonFECXxx` classes in `plugins/samplesource/sdrdaemonfec`: Special interface collecting I/Q samples from an UDP flow sent by a remote device using [SDRdaemon](https://github.com/f4exb/sdrdaemon) with FEC protection of blocks. + - `FileSource` classes in `plugins/samplesource/filesource`: Special interface reading I/Q samples from a file directly into the baseband skipping the downsampling block

Device sample sink plugins

At present the following plugins are available: - - `FileSink` classes in `plugins/samplesink/filesink`: Special inteface writing baseband I/Q samples to a file skipping the final upsampling block + - `FileSink` classes in `plugins/samplesink/filesink`: Special interface writing baseband I/Q samples to a file skipping the final upsampling block

Channel receiver (Rx) plugins

@@ -105,7 +105,7 @@ The `plugins` subdirectory contains the associated plugins used to manage device - `xxxanalyzerplugin.h/cpp` : Analyzer plugin manager - `xxxanalyzer.pro` : Qt .pro file for Windows/Android build - `xxxsrc` : Interface to the outside (e.g xxx = udp): - - `xxxsrc.h/cpp` : Inteface core + - `xxxsrc.h/cpp` : Interface core - `xxxsrcgui.h/cpp` : Interface GUI - `xxxsrcplugin/h/cpp` : Interface plugin manager - `xxxsrc.pro` : Qt .pro file for Windows/Android build @@ -131,7 +131,7 @@ The `plugins` subdirectory contains the associated plugins used to manage device - `xxxgeneratorplugin.h/cpp` : Generator plugin manager - `xxxgenerator.pro` : Qt .pro file for Windows/Android build - `xxxsink` : Interface to the outside (e.g xxx = udp): - - `xxxsink.h/cpp` : Inteface core + - `xxxsink.h/cpp` : Interface core - `xxxsinkgui.h/cpp` : Interface GUI - `xxxsinklugin/h/cpp` : Interface plugin manager - `xxxsink.pro` : Qt .pro file for Windows/Android build @@ -153,9 +153,9 @@ The lifecycle of the GUI is controlled from the "Sampling Device Control" device - Remove the current device API from the relevant buddies lists. Buddies list are effective only for physical devices with SISO or MIMO architecture (more on that later) - Create the new device API - Add the new device API to the relevant devices APIs buddies list - - creates tne new GUI and hence new device interface. This will always open the physical device unless the physical device has a SISO or MIMO architecture + - Creates the new GUI and hence new device interface. This will always open the physical device unless the physical device has a SISO or MIMO architecture -Here is the relevant par ot the code (source side) in the `MainWindow::on_sampleSource_confirmClicked` method: +Here is the relevant part of the code (source side) in the `MainWindow::on_sampleSource_confirmClicked` method: deviceUI->m_deviceSourceAPI->stopAcquisition(); deviceUI->m_deviceSourceAPI->setSampleSourcePluginInstanceUI(0); // deletes old UI and input object @@ -182,11 +182,11 @@ Note that the following would also work for multiple sample channels Rx or Tx on In SDRangel there is a complete receiver or transmitter per I/Q sample flow. These transmitters and receivers are visually represented by the Rn and Tn tabs in the main window. They are created and disposed in the way explained in the previous paragraph using the source or sink selection confirmation button. In fact it acts as if each receiver or transmitter was controlled independently. In single input or single output (none at the moment) devices this is a true independence but with SISO or MIMO devices this cannot be the case and although each receiver or transmitter looks like it was handled independently there are things in common that have to be taken into account. For example in all cases the device handle should be unique and opening and closing the device has to be done only once per physical device usage cycle. -This is where the "buddies list" come into play. Receivers exhibit a generic interface in the form of the DeviceSourceAPI class and transmitter have the DeviceSinkAPI with the same role. Through these APIs some information and control can flow between receivers and trasmitters. The point is that all receivers and/or transmitters pertaining to the same physical device must "know" each other in order to be able to exchange information or control each other. For this purpose the DeviceSourceAPI or DeviceSinkAPI maintain a list of DeviceSourceAPI siblings and a list of DeviceSinkAPI siblings called "buddies". Thus any multi flow Rx/Tx configuration can be handled. +This is where the "buddies list" come into play. Receivers exhibit a generic interface in the form of the DeviceSourceAPI class and transmitter have the DeviceSinkAPI with the same role. Through these APIs some information and control can flow between receivers and transmitters. The point is that all receivers and/or transmitters pertaining to the same physical device must "know" each other in order to be able to exchange information or control each other. For this purpose the DeviceSourceAPI or DeviceSinkAPI maintain a list of DeviceSourceAPI siblings and a list of DeviceSinkAPI siblings called "buddies". Thus any multi flow Rx/Tx configuration can be handled. -The exact behaviour of these coupled receivers and/or transmitters is dependent on the hardware therefore a generic pointer attached to the API can be used to convey any kind of class or structure taylored for the exact hardware use case. Through this structure the state of the receiver or transmitter can be exposed therefore there is one structure per receiver and transmitter in the device interface. This structure may contain pointers to common areas (dynamically allocated) related to the physical device. One such "area" is the device handle which is present in all cases. +The exact behaviour of these coupled receivers and/or transmitters is dependent on the hardware therefore a generic pointer attached to the API can be used to convey any kind of class or structure tailored for the exact hardware use case. Through this structure the state of the receiver or transmitter can be exposed therefore there is one structure per receiver and transmitter in the device interface. This structure may contain pointers to common areas (dynamically allocated) related to the physical device. One such "area" is the device handle which is present in all cases. -Normally the first buddy would create the common areas (through new) and the last would delete them (through delete) and the indovidual structure (superstructure) would be on the stack of each buddy. Thus by copying this superstructure a buddy would gain access to common areas from another (already present) buddy along with static information from the other buddy (such as which hadrware Rx or Tx channel it uses in a MIMO architecture). Exchange of dynamic information between buddies is done using message passing. +Normally the first buddy would create the common areas (through new) and the last would delete them (through delete) and the individual structure (superstructure) would be on the stack of each buddy. Thus by copying this superstructure a buddy would gain access to common areas from another (already present) buddy along with static information from the other buddy (such as which hardware Rx or Tx channel it uses in a MIMO architecture). Exchange of dynamic information between buddies is done using message passing. The degree of entanglement between the different coupled flows in a single hardware can be very different: @@ -194,7 +194,7 @@ The degree of entanglement between the different coupled flows in a single hardw - independent Rx and Tx sample rates - independent Rx and Tx center frequencies - independent Gain, bandwidth, ... - - only the device handle and indication of the presence of the XB200 accesory board is common + - only the device handle and indication of the presence of the XB200 accessory board is common - HackRF: this is a half duplex device. Rx and Tx might appear as tightly coupled but since you can use only one or the other then in fact you can control them differently as this is done in sequence. In fact only the common device handle has to be taken care of diff --git a/plugins/channelrx/chanalyzerng/readme.md b/plugins/channelrx/chanalyzerng/readme.md index ce2ac81cc..d4fc6ab17 100644 --- a/plugins/channelrx/chanalyzerng/readme.md +++ b/plugins/channelrx/chanalyzerng/readme.md @@ -35,15 +35,15 @@ Note: the spectrum view (Channel spectrum) is not presented here.

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Toggle the rational downsampler

-The input channel sample rate is given by the source device sample rate possibly downsampled by a power of two in the source device plugin. This input sample rate can be optionnaly downsampled to any value using a rational downsampler. This allows a precise control of the timings independently of the source plugin sample rate. Some devices are flexible on their sample rate some like the Airspy are not. +The input channel sample rate is given by the source device sample rate possibly downsampled by a power of two in the source device plugin. This input sample rate can be optionally downsampled to any value using a rational downsampler. This allows a precise control of the timings independently of the source plugin sample rate. Some devices are flexible on their sample rate some like the Airspy are not.

4: Rational downsampler output rate

-Use the wheels to adjust the sample rate that will be used in the rest of the signal processing in the channel. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 2000 S/s and the maximum value is the source plugin output sample rate. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate that will be used in the rest of the signal processing in the channel. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 2000 S/s and the maximum value is the source plugin output sample rate. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

5: Downsampler by a power of two

@@ -61,7 +61,7 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the In SSB mode this filter is a complex filter that can lowpass on either side of the center frequency. It is therefore labeled as "LP". For negative frequencies (LSB) the cut-off frequency is therefore negative. In fact setting a negative frequency in SSB mode automatically turns on the LSB mode processing and the spectrum is reversed. -In normal (DSB) mode this filter is a real filter that lowpass on both sides of the zero (center) frequency symmetrically. Therefore it acts as a badpass filter centered on the zero frequency and therefore it is labeled as "BP". The value displayed in (9) is the full bandwidth of the filter. +In normal (DSB) mode this filter is a real filter that lowpass on both sides of the zero (center) frequency symmetrically. Therefore it acts as a bandpass filter centered on the zero frequency and therefore it is labeled as "BP". The value displayed in (9) is the full bandwidth of the filter.

9. Lowpass filter cut-off frequency

@@ -77,11 +77,11 @@ When SSB is off the lowpass filter is actually a bandpass filter around the chan

11. Select highpass filter cut-off frequency

-In SSB mode this controls the cut-off frequency of the complex highpass filter which is the filter closest to the zero frequency. This cut-off frquency is always at least 0.1 kHz in absolute value below the lowpass filter cut-off frequency (8). +In SSB mode this controls the cut-off frequency of the complex highpass filter which is the filter closest to the zero frequency. This cut-off frequency is always at least 0.1 kHz in absolute value below the lowpass filter cut-off frequency (8). In normal (DSB) mode this filter is not active. -

12. Hghpass filter cut-off frequency

+

12. Highpass filter cut-off frequency

This is the cut-off frequency of the highpass filter in kHz. It is zero or negative in LSB mode. @@ -173,7 +173,7 @@ To construct a trace which represents real values the incoming complex signal mu - Imag: take the imaginary part - Mag: calculate magnitude in linear representation. This is just the module of the complex sample - MagDB: calculate power in log representation as 10*log10(x) or decibel (dB) representation. This is the squared module of the complex sample expressed in decibels - - Phi: instantaneous phase. This is the argument of the comlpex sample. + - Phi: instantaneous phase. This is the argument of the complex sample. - dPhi: instantaneous derivative of the phase. This is the difference of arguments between successive samples thus represents the instantaneous frequency. in the MagDB mode when the trace is selected (1) the display overlay on the top right of the trace shows 3 figures. From left to right: peak power in dB, average power in dB and peak to average difference in dB. @@ -188,7 +188,7 @@ This is for future use when more than one incoming complex signals can be applie This slider lets you adjust the amplitude scale. The full scale value appears on the left of the slider. The unit depends on the projection. -

7. Offset adjustement

+

7. Offset adjustment

This pair of sliders let you offset the trace vertically. The offset value from reference appears on the left of the slider. The reference is either: @@ -196,7 +196,7 @@ This pair of sliders let you offset the trace vertically. The offset value from - bottom zero value for MagLin projection - bottom -200 dB value for MagDB projection -The top slider is a coarse adjustement. Each step moves the trace by an amount that depends on the projection type: +The top slider is a coarse adjustment. Each step moves the trace by an amount that depends on the projection type: - Real, Imag: 0.01 - Mag: 0.005 @@ -214,13 +214,13 @@ The bottom slider is a fine adjustment. Each step moves the trace by an amount t This pair of sliders let you control the time offset of the trace from the global starting point. The time offset value appears on the left of the slider and the corresponding number of samples appears as a tooltip. -The top slider is a coarse adjustement. Each step moves trace by 100 samples +The top slider is a coarse adjustment. Each step moves trace by 100 samples The bottom slider is a fine adjustment. Each step moves trace by 1 sample

9. Trace display enable

-By default the trace display is enabled and this checkbox is checked. You can optionnally "mute" the trace by unchecking this checkbox. +By default the trace display is enabled and this checkbox is checked. You can optionally "mute" the trace by unchecking this checkbox.

10. Trace color

@@ -238,9 +238,9 @@ It is the complex signal that is memorized actually so when a trace in memory is

1. Select trigger

-This button lets you select which triger condition is affected by the controls. The trigger index appears on the left of the button. +This button lets you select which trigger condition is affected by the controls. The trigger index appears on the left of the button. -Up to 10 triggers (index 0..9) can be chained to give the final trigger top. The first trigger condition (index 0) is tested then if the trigger is raised the next trigger condition (index 1) is activated then when trigger is raised it passes control to the next trigger etc... until the last trigger is raised then the trace process starts. This established to point in time of the trigger. Optionnally the trace can start some time before this point (this is pre-trigger - see 11) +Up to 10 triggers (index 0..9) can be chained to give the final trigger top. The first trigger condition (index 0) is tested then if the trigger is raised the next trigger condition (index 1) is activated then when trigger is raised it passes control to the next trigger etc... until the last trigger is raised then the trace process starts. This established to point in time of the trigger. Optionally the trace can start some time before this point (this is pre-trigger - see 11)

2. Add/delete trigger

@@ -260,7 +260,7 @@ Real: take the real part Imag: take the imaginary part Mag: calculate magnitude in linear representation. This is just the module of the complex sample MagDB: calculate power in log representation as 10*log10(x) or decibel (dB) representation. This is the squared module of the complex sample expressed in decibels -Phi: instantaneous phase. This is the argument of the comlpex sample. +Phi: instantaneous phase. This is the argument of the complex sample. dPhi: instantaneous derivative of the phase. This is the difference of arguments between successive samples thus represents the instantaneous frequency.

5. Trigger repetition

@@ -283,7 +283,7 @@ This button selects both signal edges triggering. Trigger is raised if the signa This pair of sliders let you adjust the trigger level, The level appears on the left of the sliders. -The top slider is a coarse adjustement. Each step moves the trigger level by an amount that depends on the projection type: +The top slider is a coarse adjustment. Each step moves the trigger level by an amount that depends on the projection type: - Real, Imag: 0.01 - Mag: 0.005 @@ -301,17 +301,17 @@ The bottom slider is a fine adjustment. Each step moves the trigger level by an The actual trigger top can be moved forward by a number of samples. This pair of slider lets you adjust this delay. The delay in time units appears at the left of the sliders and the amount of samples as a tooltip -The top slider is a coarse adjustement. Each step moves the delay by a trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples +The top slider is a coarse adjustment. Each step moves the delay by a trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples

11. Pre-trigger delay

The trace can start an amount of time before the trigger top. This pair of sliders let you adjust this amount of time which is displayed at the left of the sliders. The corresponding number of samples appear as a tooltip. -The top slider is a coarse adjustement. Each step moves the delay by a hundreth of the trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples. +The top slider is a coarse adjustment. Each step moves the delay by a hundreth of the trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples.

12. Trigger line color

-This area shows the current trigger line color. When clicking on it a color chooser dialog appears that lets you change the color of the current trigger line color. This line appears when the selected trace projection matches the trigger projecion. +This area shows the current trigger line color. When clicking on it a color chooser dialog appears that lets you change the color of the current trigger line color. This line appears when the selected trace projection matches the trigger projection.

13. One-shot trigger

@@ -319,4 +319,4 @@ This button toggles a one shot trigger. When the (final) trigger is raised only

14. Freerun

-When active the triggers are disabled and traces are processed continuously. This is the default at plugin start time. \ No newline at end of file +When active the triggers are disabled and traces are processed continuously. This is the default at plugin start time. diff --git a/plugins/channelrx/demodam/readme.md b/plugins/channelrx/demodam/readme.md index fc09b4b69..ba7031d9d 100644 --- a/plugins/channelrx/demodam/readme.md +++ b/plugins/channelrx/demodam/readme.md @@ -10,7 +10,7 @@ This plugin can be used to listen to a narrowband amplitude modulated signal. "N

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Channel power

@@ -30,7 +30,7 @@ If you right click on it it will open a dialog to select the audio output device

6:Bandpass boxcar filter toggle

-Use this button to enable or disable the bandpass boxcar (sharp) filter with low cutoff at 300 Hz and high cutoff at half the RF bandwidth. This may help readibility of low signals on air traffic communications but degrades audio on comfortable AM broadcast transmissions. +Use this button to enable or disable the bandpass boxcar (sharp) filter with low cutoff at 300 Hz and high cutoff at half the RF bandwidth. This may help readability of low signals on air traffic communications but degrades audio on comfortable AM broadcast transmissions.

7: RF bandwidth

diff --git a/plugins/channelrx/demodatv/readme.md b/plugins/channelrx/demodatv/readme.md index 3f1370775..b858ba8b9 100644 --- a/plugins/channelrx/demodatv/readme.md +++ b/plugins/channelrx/demodatv/readme.md @@ -26,7 +26,7 @@ Each part is detailed next

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews.Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows.Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Rational downsampler toggle

@@ -42,7 +42,7 @@ N = S / (l × F) ☞ The number of points should be a bit larger than the number of lines up to 240 lines then it can be a bit smaller to give an acceptable image quality. -When the rational dowsampler is engaged (3) the resulting sample rate is calculated as the closest 10 S/s multiple to the source sample rate to fit an integer number of line points. +When the rational downsampler is engaged (3) the resulting sample rate is calculated as the closest 10 S/s multiple to the source sample rate to fit an integer number of line points. The example taken in the screenshot is from a 405 lines × 20 FPS video signal: @@ -66,9 +66,9 @@ When single sideband demodulation is selected (USB, LSB) the BFO is phased locke ⚠ this is experimental. -This allows adjstment of BFO frequency in 1 Hz steps from -2 to +2 kHz. You will have to look for the right value to lock to the carrier. See (6) for the lock indicator. This will work only at relatively low sample rates say below 1 MS/s. +This allows adjustment of BFO frequency in 1 Hz steps from -2 to +2 kHz. You will have to look for the right value to lock to the carrier. See (6) for the lock indicator. This will work only at relatively low sample rates say below 1 MS/s. -The BFO base frequency in Hz appears on the right. Actual frequency may change acoording to PLL locking to the carrier. +The BFO base frequency in Hz appears on the right. Actual frequency may change according to PLL locking to the carrier.

8: Channel power

@@ -138,13 +138,13 @@ This combo lets you set the TV standard type. This sets the number of lines per - PAL405: this is not the British standard. It just follows the same scheme as the two above but with only 7 black lines per half frame - ShI: this is an experimental mode that uses the least possible vertical sync lines as possible. That is one line for a long synchronization pulse and one line at a higher level (0.7) to reset the vertical sync condition. Thus only 2 lines are consumed for vertical sync and the rest is left to the image. In this mode the frames are interleaved - ShNI: this is the same as above but with non interleaved frames. - - HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pluse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines. + - HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pulse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines. When the standard chosen matches the standard of transmission the image should appear in full size and proper aspect ratio. ☞ Interleaved mode requires an odd number of lines because the system recognizes the even and odd frames depending on a odd or even number of lines respectively for the half images -☞ For non interlaved mode all standards are supposed to work for any number of lines. You may experiment with any and see if it fits your purpose. However it will be easier to obtain good or optimal results in general with the following recommendations: +☞ For non interleaved mode all standards are supposed to work for any number of lines. You may experiment with any and see if it fits your purpose. However it will be easier to obtain good or optimal results in general with the following recommendations: diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index 01d196659..86d7a1fa9 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -22,13 +22,13 @@ To enable this plugin at compile time you will need to have DSDcc installed in y You can use a serial device connected to your system that implements and exposes the packet interface of the AMBE3000 chip. This can be for example a ThumbDV USB dongle. In order to support DV serial devices in your system you will need two things: - Compile with [SerialDV](https://github.com/f4exb/serialDV) support Please refer to this project Readme.md to compile and install SerialDV. If you install it in a custom location say `/opt/install/serialdv` you will need to add these defines to the cmake command: `-DLIBSERIALDV_INCLUDE_DIR=/opt/install/serialdv/include/serialdv -DLIBSERIALDV_LIBRARY=/opt/install/serialdv/lib/libserialdv.so` - - Enable DV serial devices in your system by checking the option in the Preferences menu. YOu will need to enable the DV serial devices each time you start SDRangel. + - Enable DV serial devices in your system by checking the option in the Preferences menu. You will need to enable the DV serial devices each time you start SDRangel. Although such serial devices work with a serial interface at 400 kb in practice maybe for other reasons they are capable of handling only one conversation at a time. The software will allocate the device dynamically to a conversation with an inactivity timeout of 1 second so that conversations do not get interrupted constantly making the audio output too choppy. In practice you will have to have as many devices connected to your system as the number of conversations you would like to be handled in parallel. Note also that this is not supported in Windows because of trouble with COM port support (contributors welcome!). -Altermatively you can use software decoding with Mbelib. Possible copyright issues apart (see next) the audio quality with the DVSI AMBE chip is much better. +Alternatively you can use software decoding with Mbelib. Possible copyright issues apart (see next) the audio quality with the DVSI AMBE chip is much better. --- ⚠ Since kernel 4.4.52 the default for FTDI devices (that is in the ftdi_sio kernel module) is not to set it as low latency. This results in the ThumbDV dongle not working anymore because its response is too slow to sustain the normal AMBE packets flow. The solution is to force low latency by changing the variable for your device (ex: /dev/ttyUSB0) as follows: @@ -46,7 +46,7 @@ If you are not comfortable with this just do not install DSDcc and/or mbelib and - For Linux distributions: `plugins/channel/libdemoddsd.so` - For Windows distributions: `dsdcc.dll`, `mbelib.dll`, `plugins\channel\demoddsd.dll` -For software built fron source if you choose to have `mbelib` support you will need to have DSDcc compiled with `mbelib` support. You will also need to have defines for it on the cmake command. If you have mbelib installed in a custom location, say `/opt/install/mbelib` you will need to add these defines to the cmake command: `-DLIBMBE_INCLUDE_DIR=/opt/install/mbelib/include -DLIBMBE_LIBRARY=/opt/install/mbelib/lib/libmbe.so` +For software built from source if you choose to have `mbelib` support you will need to have DSDcc compiled with `mbelib` support. You will also need to have defines for it on the cmake command. If you have mbelib installed in a custom location, say `/opt/install/mbelib` you will need to add these defines to the cmake command: `-DLIBMBE_INCLUDE_DIR=/opt/install/mbelib/include -DLIBMBE_LIBRARY=/opt/install/mbelib/lib/libmbe.so`

Interface

@@ -56,7 +56,7 @@ For software built fron source if you choose to have `mbelib` support you will n

A.1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews.Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows.Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

A.2: Channel bandwidth before discriminator

@@ -124,7 +124,7 @@ If you right click on it it will open a dialog to select the audio output device

A.12: Format specific status display

-When the display is active the background turns from the surrounding gray color to dark green. It shows informatory or status messages that are particular to each format. +When the display is active the background turns from the surrounding gray color to dark green. It shows informational or status messages that are particular to each format.

A11.1: D-Star status display

@@ -195,7 +195,7 @@ This is either: - `IDL`: data idle block - `VLC`: voice Link Control data block - `TLC`: terminator with Link Control information data block - - `CSB`: CSBK (Control Signalling BlocK) data block + - `CSB`: CSBK (Control Signaling BlocK) data block - `MBH`: Multi Block Control block header data block - `MBC`: Multi Block Control block continuation data block - `DAH`: Data header block @@ -223,7 +223,7 @@ String is in the form: `02223297>G00000222` - `--`: undetermined - `HD`: Header of FS1 type - - `PY`: Payload frame of a sitll undetermined type + - `PY`: Payload frame of a still undetermined type - `VO`: Voice frame - `VD`: Voice and data frame - `D1`: Data without FEC frame @@ -277,7 +277,7 @@ This displays a summary of FICH (Frame Identification CHannel) block data. From - `W`: wide band mode (as in the example) - second character is the path type: - `I`: Internet path - - `L`: local path (as inthe example) + - `L`: local path (as in the example) - last three characters are the YSF squelch code (0..127) or dashes `---` if the YSF squelch is not active
A11.4.2: Origin and destination callsigns
@@ -303,7 +303,7 @@ This display shows the sampled points of the demodulated FM signal in a XY plane - X as the signal at time t and Y the signal at time t minus symbol time if "transitions constellation" is selected by button (B.13) - X as the signal and Y as the synchronization signal if "symbol synchronization" is selected by button (B.13) -The display shows 16 points as yellow crosses that can be used to tune the center frequency (A.1) and FM deviation (B.17) so that symbol recovery can be done with the best conditions. In the rest of the documentation they will be referenced with numbers fron 0 to 15 starting at the top left corner and going from left to right and top to bottom. +The display shows 16 points as yellow crosses that can be used to tune the center frequency (A.1) and FM deviation (B.17) so that symbol recovery can be done with the best conditions. In the rest of the documentation they will be referenced with numbers from 0 to 15 starting at the top left corner and going from left to right and top to bottom.
Transition constellation display
@@ -397,7 +397,7 @@ Normally you would always want to have a matched filter however on some strong D

B.5: Symbol PLL lock indicator

-Since dsdcc version 1.7.1 the synbol synchronization can be done with a PLL fed by a ringing filter (narrow passband) tuned at the symbol rate and itself fed with the squared magnitude of the discriminator signal. For signals strong enough to lock the PLL this works significantly better than with the ringing filter alone that was the only option in versions <= 1.6.0. Version 1.7.0 had the PLL enabled permanently. +Since dsdcc version 1.7.1 the symbol synchronization can be done with a PLL fed by a ringing filter (narrow passband) tuned at the symbol rate and itself fed with the squared magnitude of the discriminator signal. For signals strong enough to lock the PLL this works significantly better than with the ringing filter alone that was the only option in versions <= 1.6.0. Version 1.7.0 had the PLL enabled permanently. However with marginal signals the ringing filter alone and a few heuristics work better. This is why since DSDcc version 1.7.1 the PLL became optional. @@ -416,7 +416,7 @@ With the PLL engaged the figure should be 100% all the time in presence of a loc

B.7: Zero crossing shift

-This is the current (at display polling time) zero crosing shift. It should be the closest to 0 as possible. However some jitter is acceptable for good symbol synchronization: +This is the current (at display polling time) zero crossing shift. It should be the closest to 0 as possible. However some jitter is acceptable for good symbol synchronization: - `2400 S/s`: +/- 5 inclusive - `4800 S/s`: +/- 2 inclusive @@ -446,7 +446,7 @@ Toggle button to select slot #2 voice output. When on waves appear on the icon.
B.12 (3): TDMA stereo mode toggle
- When off the icon shows a single loudspeaker. It mixes slot #1 and slot #2 voice as a mono audio signal - - When on the icon shows a pair of loudspeakers. It sends slot #1 vocie to the left stereo audio channel and slot #2 to the right one + - When on the icon shows a pair of loudspeakers. It sends slot #1 voice to the left stereo audio channel and slot #2 to the right one For FDMA standards you may want to leave this as mono mode. diff --git a/plugins/channelrx/demodnfm/readme.md b/plugins/channelrx/demodnfm/readme.md index ae37300f0..9f1bdfb14 100644 --- a/plugins/channelrx/demodnfm/readme.md +++ b/plugins/channelrx/demodnfm/readme.md @@ -10,7 +10,7 @@ This plugin can be used to listen to a narrowband FM modulated signal. "Narrowba

1: Frequency shift from center frequency of reception value

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit.

2: Channel power

@@ -26,7 +26,7 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the This is the bandwidth in kHz of the channel signal before demodulation. It can be set in steps as 5, 6.25, 8.33, 10, 12.5, 15, 20, 25 and 40 kHz. The expected one side frequency deviation is 0.4 times the bandwidth. -☞ The demodulation is done at the channel sample rate which is guaranteed not to be lower than the requested audio sample rate but can possibly be equal to it. This means that for correct operaton in any case you must ensure that the sample rate of the audio device is not lower than the Nyquist rate required to process this channel bandwidth. +☞ The demodulation is done at the channel sample rate which is guaranteed not to be lower than the requested audio sample rate but can possibly be equal to it. This means that for correct operation in any case you must ensure that the sample rate of the audio device is not lower than the Nyquist rate required to process this channel bandwidth. ☞ The channel sample rate is always the baseband signal rate divided by an integer power of two so depending on the baseband sample rate obtained from the sampling device you could also guarantee a minimal channel bandwidth. For example with a 125 kS/s baseband sample rate and a 8 kS/s audio sample rate the channel sample rate cannot be lower than 125/8 = 15.625 kS/s (125/16 = 7.8125 kS/s is too small) which is still OK for 5 or 6.25 kHz channel bandwidths. @@ -80,4 +80,4 @@ This is the value of the tone squelch received when the CTCSS is activated. It d Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. -If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. \ No newline at end of file +If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. diff --git a/plugins/channelrx/demodssb/readme.md b/plugins/channelrx/demodssb/readme.md index 82a181092..c7ffc2e02 100644 --- a/plugins/channelrx/demodssb/readme.md +++ b/plugins/channelrx/demodssb/readme.md @@ -14,7 +14,7 @@ This plugin can be used to listen to a single sideband or double sidebands modul

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Channel power

@@ -58,7 +58,7 @@ The span value display is set as follows depending on the SSB or DSB mode: - In SSB mode: the span goes from zero to the upper (USB: positive frequencies) or lower (LSB: negative frequencies) limit and the absolute value of the limit is displayed. - In DSB mode: the span goes from the lower to the upper limit of same absolute value and ± the absolute value of the limit is displayed. -This is how the Span (8) and bandpass (9, 10) fitler controls look like in the 3 possible modes: +This is how the Span (8) and bandpass (9, 10) filter controls look like in the 3 possible modes: **DSB**: @@ -97,7 +97,7 @@ Values are expressed in kHz and step is 100 Hz. Values are expressed in kHz and step is 100 Hz. - - In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel signe side band bandpass filter. + - In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel single side band bandpass filter. - In DSB mode it is inactive and set to zero (double side band filter).

11: Volume and AGC

@@ -112,7 +112,7 @@ This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can Use this checkbox to toggle AGC on and off. -If you are into digging weak signals out of the noise you probably will not turn the AGC on. AGC is intended for medium and large signals and help accomodate the signal power variations from a station to another or due to QSB. +If you are into digging weak signals out of the noise you probably will not turn the AGC on. AGC is intended for medium and large signals and help accommodate the signal power variations from a station to another or due to QSB. This AGC is based on the calculated magnitude (square root of power of the filtered signal as I² + Q²) and will try to adjust audio volume as if a -20dB power signal was received. @@ -140,7 +140,7 @@ The signal power is calculated as the moving average over the AGC time constant Active only in AGC mode with squelch enabled. -To avoid unwanted squelch opening on short transient bursts only signals wilth power above threshold during this period in milliseconds will open the squelch.It can be varied from 0 to 20 ms in 1 ms steps. +To avoid unwanted squelch opening on short transient bursts only signals with power above threshold during this period in milliseconds will open the squelch.It can be varied from 0 to 20 ms in 1 ms steps. When the power threshold is close to the noise floor a few milliseconds help in preventing noise power wiggle to open the squelch. @@ -152,4 +152,4 @@ If you right click on it a dialog will open to select the audio output device. S

14: Spectrum display

-This is the spectrum display of the demodulated signal (SSB) or translated signal (DSB). Controls on the bottom of the panel are identical to the ones of the main spectrum display. \ No newline at end of file +This is the spectrum display of the demodulated signal (SSB) or translated signal (DSB). Controls on the bottom of the panel are identical to the ones of the main spectrum display. diff --git a/plugins/channelrx/demodwfm/readme.md b/plugins/channelrx/demodwfm/readme.md index d5ffe763a..fab4bef2d 100644 --- a/plugins/channelrx/demodwfm/readme.md +++ b/plugins/channelrx/demodwfm/readme.md @@ -10,7 +10,7 @@ This plugin can be used to listen to a wideband or narrowband FM modulated signa

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Channel power

@@ -42,4 +42,4 @@ This is the squelch threshold in dB. The average total power received in the sig Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. -If you right click on this button this will open a dialog to select the audio output device. \ No newline at end of file +If you right click on this button this will open a dialog to select the audio output device. diff --git a/plugins/channelrx/udpsrc/readme.md b/plugins/channelrx/udpsrc/readme.md index f26dc0f63..7604b0ea0 100644 --- a/plugins/channelrx/udpsrc/readme.md +++ b/plugins/channelrx/udpsrc/readme.md @@ -2,7 +2,7 @@

Introduction

-By "source" one should undetstand a source of samples for the outside of SDRangel application. An UDP connection is established from the plugin to the given address and port and samples are directed to it. +By "source" one should understand a source of samples for the outside of SDRangel application. An UDP connection is established from the plugin to the given address and port and samples are directed to it. The UDP block size or UDP payload size is fixed at 512 bytes. @@ -16,7 +16,7 @@ This plugin is available for Linux and Mac O/S only.

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Input channel power

@@ -41,13 +41,13 @@ Sample rate in samples per second of the signal that is sent over UDP. The actua Left: combo box to specify the type of samples that are sent over UDP: - `I/Q`: Raw I/Q samples. Use it with software that accepts I/Q data as input like GNUradio with the `UDP source` block. The output is interleaved I and Q samples - - `NFM`: AF of FM demodulated signal. Use it with software that takes the FM demodulated audio or the discriminator output of a radio as input. Make sure you specify the appropriate signal bandwidth (see 7) according to the AF bandwidth needs. The output is a repetition of NFM samples on real part and on imaginary part this facilitates integration wtih software expecting a stereo type of input with the same samples on L and R channels. With GNURadio just use a complex to real block. + - `NFM`: AF of FM demodulated signal. Use it with software that takes the FM demodulated audio or the discriminator output of a radio as input. Make sure you specify the appropriate signal bandwidth (see 7) according to the AF bandwidth needs. The output is a repetition of NFM samples on real part and on imaginary part this facilitates integration with software expecting a stereo type of input with the same samples on L and R channels. With GNURadio just use a complex to real block. - `NFM Mono`: This is the same as above but only one sample is output for one NFM sample. This can be used with software that accept a mono type of input like `dsd` or `multimon`. - `USB`: AF of USB demodulated signal. Use it with software that uses a SSB demodulated signal as input i.e. software that is based on the audio output of a SSB radio. The output is the I/Q binaural output of the demodulator. - `LSB`: AF of LSB demodulated signal. Use it with software that uses a SSB demodulated signal as input i.e. software that is based on the audio output of a SSB radio. The output is the I/Q binaural output of the demodulator. - `LSB Mono`: AF of the LSB part of a SSB demodulated signal as "mono" (I+Q)*0.7 samples that is one sample per demodulator output sample. This can be used with software that accepts mono type of input. - `USB Mono`: AF of the USB part of a SSB demodulated signal as "mono" (I+Q)*0.7 samples that is one sample per demodulator output sample. This can be used with software that accepts mono type of input. - - `AM Mono`: AF of the enveloppe demodulated signal i.e. channel magnitude or sqrt(I² + Q²) as "mono" samples that is one sample per demodulator output sample. This can be used with software that accepts mono type of input. + - `AM Mono`: AF of the envelope demodulated signal i.e. channel magnitude or sqrt(I² + Q²) as "mono" samples that is one sample per demodulator output sample. This can be used with software that accepts mono type of input. - `AM !DC Mono`: Same as above but with a DC block based on magnitude average over a 5 ms period - `AM BPF Mono`: Same as AM Mono but raw magnitude signal is passed through a bandpass filter with lower cutoff at 300 Hz and higher cutoff at RF bandwidth frequency diff --git a/plugins/channeltx/modam/readme.md b/plugins/channeltx/modam/readme.md index 17ba6987f..4978f07e0 100644 --- a/plugins/channeltx/modam/readme.md +++ b/plugins/channeltx/modam/readme.md @@ -10,7 +10,7 @@ This plugin can be used to generate a narrowband amplitude modulated signal. "Na

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Channel power

@@ -38,7 +38,7 @@ This is the volume of the audio signal from 0.0 (mute) to 2.0 (maximum). It can - bottom bar (brown): instantaneous peak value - tip vertical bar (bright red): peak hold value -You should aim at keepimg the peak value below 100% using the volume control +You should aim at keeping the peak value below 100% using the volume control

9: Input source control

@@ -133,4 +133,4 @@ This is the audio file play length in time units

17: Play file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 14.3) \ No newline at end of file +This slider can be used to randomly set the current position in the file when file play is in pause state (button 14.3) diff --git a/plugins/channeltx/modatv/readme.md b/plugins/channeltx/modatv/readme.md index 761a1ed5d..1e06e64b1 100644 --- a/plugins/channeltx/modatv/readme.md +++ b/plugins/channeltx/modatv/readme.md @@ -4,7 +4,7 @@ This plugin can be used to generate an analog TV signal mostly used in amateur radio. It is limited to black and white images as only the luminance (256 levels) is supported. -There is no sound either. You coud imagine using any of the plugins supporting audio to create a mixed signal. This is not working well however for various reasons. It is better to use two physical transmitters and two physical receivers. +There is no sound either. You could imagine using any of the plugins supporting audio to create a mixed signal. This is not working well however for various reasons. It is better to use two physical transmitters and two physical receivers. In practice 4 MS/s with about 300 points per line is the lowest sample rate that produces a standard image quality. Lower sample rates and line definition produce low quality images that may still be acceptable for experiments. The plugin offers to go as low as 32 lines and 8 FPS for NBTV experiments. NBTV stands for Narrow Band TeleVision see: [Wikipedia article](https://en.wikipedia.org/wiki/Narrow-bandwidth_television) and [NBTV.org](http://www.nbtv.org/) @@ -14,7 +14,7 @@ In practice 4 MS/s with about 300 points per line is the lowest sample rate that

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Sample rate data

@@ -58,7 +58,7 @@ The video signal can modulate the carrier in the following modes: - AM: Amplitude modulation. Modulation index is 90%. - FM: Frequency modulation. Excursion is a percentage of the bandwidth available given the channel sample rate. This percentage is controlled by button (2). e.g. at 25% for 4 MS/s sample rate this is 1 MHz (±0.5 MHz) - USB: SSB upper side band: video signal is transposed only in positive frequencies including DC component - - LSB: SSB lower side band: video signal is transposed only in megative frequencies excluding DC component + - LSB: SSB lower side band: video signal is transposed only in negative frequencies excluding DC component - VUSB: SSB upper sideband with vestigial lower sideband. The cutoff frequency of the lower sideband is controlled by slider (3) - VLSB: SSB lower sideband with vestigial upper sideband. The cutoff frequency of the upper sideband is controlled by slider (3) @@ -66,7 +66,7 @@ The video signal can modulate the carrier in the following modes: Use this button to control FM deviation in FM modulation mode. This is a percentage of total available channel bandwidth. e.g for the sample rate of 2997 kS/s of the screenshot and a percentage of 19% this yields a full deviation of 2997 × 0.19 = 569.43 kHz that is ±284.715 kHz -☞ You can adjust this value and see the result for yourseelf. A good starting point is half of the signal bandwidth. +☞ You can adjust this value and see the result for yourself. A good starting point is half of the signal bandwidth.

A.3: Opposite sideband FFT filter cutoff

@@ -90,13 +90,13 @@ The cutoff frequency in kHz is displayed on the right of the slider

A.5: Modulated signal level before filtering stages

-This button controls the scaling from the +1/-1 modulated signal level to the -32768/+32768 2 bytes samples. This is useful to control the saturation of the FFT or FIR filters. Looking at the output spectrum you can precisely control the limit above which distorsion appears. +This button controls the scaling from the +1/-1 modulated signal level to the -32768/+32768 2 bytes samples. This is useful to control the saturation of the FFT or FIR filters. Looking at the output spectrum you can precisely control the limit above which distortion appears.

A.6: Video signal level meter

This is the level meter fed with the video signal. Units are the percentage of the 0.0 to 1.0 modulating video signal. -

A.7: Nuber of lines

+

A.7: Number of lines

This controls the number of lines per full frame. Choice is between 640, 625, 525, 480, 405, 360, 343, 240, 180, 120, 90, 60 and 32 lines. @@ -108,18 +108,18 @@ This controls the number of full frames per second. Choice is between 30, 25, 20

A.9: TV Standard

-This controls the frame synchronization schem and number of black lines: +This controls the frame synchronization scheme and number of black lines: - PAL625: this is the PAL 625 lines standard with 25 FPS. Since only black and white (luminance) is supported this corresponds to any of the B,G,I or L PAL standards - PAL525: this is the PAL 525 lines standard with 30 FPS. This corresponds to the PAL M standard. - PAL405: this loosely corresponds to the British 405 lines system and is similar to PAL for synchronization. This mode has only 7 black lines. - ShI: this is an experimental mode that uses the least possible vertical sync lines as possible. That is one line for a long synchronization pulse and one line at a higher level (0.7) to reset the vertical sync condition. Thus only 2 lines are consumed for vertical sync and the rest is left to the image. In this mode the frames are interleaved - ShNI: this is the same as above but with non interleaved frames. - - HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pluse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines. + - HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pulse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines. ☞ Interleaved mode requires an odd number of lines because the system recognizes the even and odd frames depending on a odd or even number of lines respectively for the half images -☞ For non interlaved mode all standards are supposed to work for any number of lines. You may experiment with any and see if it fits your purpose. However it will be easier to obtain good or optimal results in general with the following recommendations: +☞ For non interleaved mode all standards are supposed to work for any number of lines. You may experiment with any and see if it fits your purpose. However it will be easier to obtain good or optimal results in general with the following recommendations:
@@ -234,7 +234,7 @@ This is the video file play length in time units

12. Video file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 16). When video plays the slider moves according to the current position. +This slider can be used to randomly set the current position in the file when file play is in pause state (button 16). When video plays the slider moves according to the current position.

23. Play/Pause camera

@@ -264,4 +264,4 @@ Use this button to switch between system camera FPS (off) and manual camera FPS

19. Manual camera FPS adjust

-Use this dial button to adjust camera FPS manually between 2 and 30 FPS in 0.1 FPS steps. The manual FPS value appears on the right of the button. \ No newline at end of file +Use this dial button to adjust camera FPS manually between 2 and 30 FPS in 0.1 FPS steps. The manual FPS value appears on the right of the button. diff --git a/plugins/channeltx/modnfm/readme.md b/plugins/channeltx/modnfm/readme.md index 83e1b470b..1ec04715a 100644 --- a/plugins/channeltx/modnfm/readme.md +++ b/plugins/channeltx/modnfm/readme.md @@ -10,7 +10,7 @@ This plugin can be used to generate a narrowband frequency modulated signal. "Na

1: Frequency shift from center frequency of transmission/h3> -Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Channel power

@@ -42,7 +42,7 @@ This is the volume of the audio signal from 0.0 (mute) to 2.0 (maximum). It can - bottom bar (brown): instantaneous peak value - tip vertical bar (bright red): peak hold value -You should aim at keepimg the peak value below 100% using the volume control +You should aim at keeping the peak value below 100% using the volume control

10: Input source control

@@ -145,4 +145,4 @@ This is the audio file play length in time units

21: Play file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 18.3) \ No newline at end of file +This slider can be used to randomly set the current position in the file when file play is in pause state (button 18.3) diff --git a/plugins/channeltx/modssb/readme.md b/plugins/channeltx/modssb/readme.md index ba4ada59e..bdced14a2 100644 --- a/plugins/channeltx/modssb/readme.md +++ b/plugins/channeltx/modssb/readme.md @@ -14,7 +14,7 @@ This plugin can be used to generate a single sideband or double sidebands modula

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Channel power

@@ -53,7 +53,7 @@ The span value display is set as follows depending on the SSB or DSB mode: - In SSB mode: the span goes from zero to the upper (USB: positive frequencies) or lower (LSB: negative frequencies) limit and the absolute value of the limit is displayed. - In DSB mode: the span goes from the lower to the upper limit of same absolute value and ± the absolute value of the limit is displayed. -This is how the Span (7) and bandpass (8, 9) fitler controls look like in the 3 possible modes: +This is how the Span (7) and bandpass (8, 9) filter controls look like in the 3 possible modes: **DSB**: @@ -92,7 +92,7 @@ Values are expressed in kHz and step is 100 Hz. Values are expressed in kHz and step is 100 Hz. - - In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel signe side band bandpass filter. + - In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel single side band bandpass filter. - In DSB mode it is inactive and set to zero (double side band filter).

10: Volume

@@ -105,7 +105,7 @@ This is the volume of the audio signal from 0.0 (mute) to 2.0 (maximum). It can - bottom bar (brown): instantaneous peak value - tip vertical bar (bright red): peak hold value -You should aim at keepimg the peak value below 100% using the volume control +You should aim at keeping the peak value below 100% using the volume control

12: Audio compression

@@ -125,7 +125,7 @@ Use this button to toggle audio compressor on and off. In "on" mode the button i

12.2: AGC magnitude order

-This is the ratio to maximum signal magnitude aimed by the AGC. The higher the stronger is the compression but the signal will have more chances to get clamped and therefore will get more severly distorted. +This is the ratio to maximum signal magnitude aimed by the AGC. The higher the stronger is the compression but the signal will have more chances to get clamped and therefore will get more severely distorted. The default value is 0.2 which is rather mild. For normal voice you should not exceed 0.4 however the criteria is rather subjective. It is flexible enough to be tuned between 0 and 1 in 0.01 increments. @@ -137,7 +137,7 @@ The default value is 200 ms which is relatively "soft". Most practically useful

12.4: Power threshold

-in order to avoid small signals due to background noise or power wiggle to enter the system and raise to normal voice level a power based squelch is in place. This control allows to select a threshold in dB above which a signal will open the squalch if it lasts longer than the squelch gate (2.5). Default is -40 dB. +in order to avoid small signals due to background noise or power wiggle to enter the system and raise to normal voice level a power based squelch is in place. This control allows to select a threshold in dB above which a signal will open the squelch if it lasts longer than the squelch gate (2.5). Default is -40 dB.

12.5: Squelch gate

@@ -240,8 +240,8 @@ This is the audio file play length in time units

21: Play file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 17.3) +This slider can be used to randomly set the current position in the file when file play is in pause state (button 17.3)

22: Channel spectrum display

-This is the channel spectrum display. Controls at the bottom of the panel are the same as with the central spectrum display. \ No newline at end of file +This is the channel spectrum display. Controls at the bottom of the panel are the same as with the central spectrum display. diff --git a/plugins/channeltx/modwfm/readme.md b/plugins/channeltx/modwfm/readme.md index 5dcf946af..5f540bbd7 100644 --- a/plugins/channeltx/modwfm/readme.md +++ b/plugins/channeltx/modwfm/readme.md @@ -10,7 +10,7 @@ This plugin can be used to generate a wideband frequency modulated signal. "Wide

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Channel power

@@ -42,7 +42,7 @@ This is the volume of the audio signal from 0.0 (mute) to 2.0 (maximum). It can - bottom bar (brown): instantaneous peak value - tip vertical bar (bright red): peak hold value -You should aim at keepimg the peak value below 100% using the volume control +You should aim at keeping the peak value below 100% using the volume control

10: Input source control

@@ -137,4 +137,4 @@ This is the audio file play length in time units

18: Play file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 15.3) \ No newline at end of file +This slider can be used to randomly set the current position in the file when file play is in pause state (button 15.3) diff --git a/plugins/channeltx/udpsink/readme.md b/plugins/channeltx/udpsink/readme.md index d578d1329..792a51540 100644 --- a/plugins/channeltx/udpsink/readme.md +++ b/plugins/channeltx/udpsink/readme.md @@ -2,7 +2,7 @@

Introduction

-By "sink" one should undetstand a sink of samples for the outside of SDRangel application. An external application establishes an UDP connection to the plugin at the given address and port and samples are directed to it. In fact it can also come frome SDRangel itself using the UDP source plugin +By "sink" one should understand a sink of samples for the outside of SDRangel application. An external application establishes an UDP connection to the plugin at the given address and port and samples are directed to it. In fact it can also come from SDRangel itself using the UDP source plugin The UDP block size or UDP payload size is optimized for 512 bytes but other sizes are acceptable. @@ -14,11 +14,11 @@ This plugin is available for Linux and Mac O/S only.

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Input channel power

-Total power in dB relative to a +/- 1.0 amplitude signal received fron UDP. +Total power in dB relative to a +/- 1.0 amplitude signal received from UDP.

3: Output channel power

@@ -26,7 +26,7 @@ Total power in dB relative to a +/- 1.0 amplitude signal sent in the channel.

4: Channel mute

-Use this button to switch off the RF on the channel. The background of the button lits in green when a signal can be sent. +Use this button to switch off the RF on the channel. The background of the button lights in green when a signal can be sent.

5: UDP address and port

@@ -36,7 +36,7 @@ The display is in the format `address:data port`

6: Input sample rate

-Sample rate in samples per second of the signal that is recveived on UDP. The actual byte rate depends on the type of sample which corresponds to a number of bytes per sample. +Sample rate in samples per second of the signal that is received on UDP. The actual byte rate depends on the type of sample which corresponds to a number of bytes per sample.

7: Type of samples

@@ -62,7 +62,7 @@ This is the maximum FM deviation in Hz for a +/- 1.0 amplitude modulating signal

11: AM percentage modulation

-this is the AM precentage modulation when a +/- 1.0 amplitude modulating signal is applied. herefore it is active only for `S16LE AM Mono` sample format. +this is the AM percentage modulation when a +/- 1.0 amplitude modulating signal is applied. Therefore it is active only for `S16LE AM Mono` sample format.

12: Apply (validation) button

@@ -86,7 +86,7 @@ The button sets the delay after which a signal constantly above the squelch thre

14: signal amplitude percentage of maximum

-The gain (15) should be adjusted so that the peak amplitude (small red vertical bar) never exceeds 100%. Above 100% the signal is clipper which results in distorsion. +The gain (15) should be adjusted so that the peak amplitude (small red vertical bar) never exceeds 100%. Above 100% the signal is clipper which results in distortion.

15: Input and output Gains

diff --git a/plugins/samplesink/bladerfoutput/readme.md b/plugins/samplesink/bladerfoutput/readme.md index 04b86a7c4..5b724dec5 100644 --- a/plugins/samplesink/bladerfoutput/readme.md +++ b/plugins/samplesink/bladerfoutput/readme.md @@ -22,7 +22,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Red square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

2: Baseband sample rate

@@ -72,7 +72,7 @@ This controls the optional XB-200 add-on when it is fitted to the BladeRF main b This is the BladeRF device DAC sample rate in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: Tx filter bandwidth

@@ -84,4 +84,4 @@ The VGA1 (relative) gain can be adjusted from -35 dB to -4 dB in 1 dB steps. The

9: Variable gain amplifier #2 gain

-The VGA2 gain can be adjusted from 0 dB to 25 dB in 1 dB steps. The VGA2 is inside the LMS6002D chip and is placed after the RF mixer. It can be considered as the PA (Power AMplifier). The maximum output power when both VGA1 and VGA2 are at their maximum is about 4 mW (6 dBm). \ No newline at end of file +The VGA2 gain can be adjusted from 0 dB to 25 dB in 1 dB steps. The VGA2 is inside the LMS6002D chip and is placed after the RF mixer. It can be considered as the PA (Power Amplifier). The maximum output power when both VGA1 and VGA2 are at their maximum is about 4 mW (6 dBm). diff --git a/plugins/samplesink/filesink/readme.md b/plugins/samplesink/filesink/readme.md index cede1d66b..d65fb0b60 100644 --- a/plugins/samplesink/filesink/readme.md +++ b/plugins/samplesink/filesink/readme.md @@ -26,7 +26,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Red square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured + - Magenta (or pink) square icon: an error occurred

2: File stream sample rate

@@ -52,8 +52,8 @@ The baseband stream is interpolated by this value before being written to file. This is the baseband sample rate before interpolation in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

8: Time counter

-This is the recording time count in HH:MM:SS.SSS \ No newline at end of file +This is the recording time count in HH:MM:SS.SSS diff --git a/plugins/samplesink/hackrfoutput/readme.md b/plugins/samplesink/hackrfoutput/readme.md index b769d7b69..6db204276 100644 --- a/plugins/samplesink/hackrfoutput/readme.md +++ b/plugins/samplesink/hackrfoutput/readme.md @@ -20,7 +20,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Red square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. If you have the Rx open in another tab and it is running then it will be stopped automatically before the Tx starts. In a similar manner the Tx will be stopped before the Rx is started from the Rx tab. @@ -75,7 +75,7 @@ According to HackRF documentation the output power when the PA is engaged and th This is the HackRF device DAC sample rate in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

9: Tx filter bandwidth

@@ -83,4 +83,4 @@ This is the Tx filter bandwidth in kHz. Possible values are: 1750, 2500, 3500, 5

10: Tx variable gain amplifier gain

-The Tx VGA gain can be adjusted from 0 dB to 47 dB in 1 dB steps. See (7) for an indication on maximum output power. \ No newline at end of file +The Tx VGA gain can be adjusted from 0 dB to 47 dB in 1 dB steps. See (7) for an indication on maximum output power. diff --git a/plugins/samplesink/limesdroutput/readme.md b/plugins/samplesink/limesdroutput/readme.md index 25c5791ef..2bbf9aba9 100644 --- a/plugins/samplesink/limesdroutput/readme.md +++ b/plugins/samplesink/limesdroutput/readme.md @@ -41,7 +41,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again.

2A: DAC sample rate

@@ -77,21 +77,21 @@ Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an ind

6: NCO frequency shift

-This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of transmission minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. +This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of transmission minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. -☞ In the LMS7002M TSP block the NCO sits after the interpolator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual DAC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware interpolation factor. For example with a 4 MS/s device to host sample rate (10) and a hadrware interpolation of 16 (8) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). +☞ In the LMS7002M TSP block the NCO sits after the interpolator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual DAC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware interpolation factor. For example with a 4 MS/s device to host sample rate (10) and a hardware interpolation of 16 (8) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz).

7: Transverter mode open dialog

This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

7.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. @@ -117,11 +117,11 @@ Use this button to open a dialog that lets you choose the external clock frequen ![LimeSDR input plugin gain GUI](../../../doc/img/LimeSDR_plugin_extclock.png) -

7A.1: Exrernal clock frequency

+

7A.1: External clock frequency

Can be varied from 5 to 300 MHz -Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. +Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor.

7A.2: Enable/disable external clock input @@ -149,13 +149,13 @@ The I/Q stream from the baseband is upsampled by a power of two by software insi This is the LMS7002M device to/from host stream sample rate in S/s. It is the same for the Rx and Tx systems. -Use the wheels to adjust the sample rate. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. +Use the wheels to adjust the sample rate. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. The LMS7002M uses the same clock for both the ADCs and DACs therefore this sample rate affects all of the 2x2 MIMO channels.

11: Tx hardware filter bandwidth

-This is the Tx hardware filter bandwidth in kHz in the LMS7002M device for the given channel. Boundaries are updated automatically but generally are from 5 to 130 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +This is the Tx hardware filter bandwidth in kHz in the LMS7002M device for the given channel. Boundaries are updated automatically but generally are from 5 to 130 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

12: TSP FIR filter toggle

@@ -163,11 +163,11 @@ The TSP in the LMS7002M chip has a FIR filter chain per channel. Use this button

13: TSP FIR filter bandwidth

-Use the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

14: Gain

-Use this slider to adjust the global gain of the Tx chain. LimeSuite software automatically set optimal values of the amplifiers to achive this global gain. This gain can be set between 0 and 70 dB in 1 dB steps. The value in dB appears at the right of the slider. +Use this slider to adjust the global gain of the Tx chain. LimeSuite software automatically set optimal values of the amplifiers to achieve this global gain. This gain can be set between 0 and 70 dB in 1 dB steps. The value in dB appears at the right of the slider.

15: Antenna selection

@@ -187,7 +187,7 @@ This label turns green when status can be obtained from the current stream. Usua

18: Stream global (all Tx) throughput in MB/s

-This is the stream throughput in MB/s and is usually about 3 times the sample rate for a single stream and 6 times for a dual Tx stream. This is due to the fact that 12 bits samples are used and although they are represented as 16 bit values only 12 bita travel on the USB link. +This is the stream throughput in MB/s and is usually about 3 times the sample rate for a single stream and 6 times for a dual Tx stream. This is due to the fact that 12 bits samples are used and although they are represented as 16 bit values only 12 bits travel on the USB link.

19: FIFO status

@@ -195,4 +195,4 @@ This is the fill percentage of the Tx FIFO in the LimeSuite interface. In normal

20: Board temperature

-This is the board temperature in degrees Celsius updated every ~5s. Before the first probe the display marks "00C" this is normal. \ No newline at end of file +This is the board temperature in degrees Celsius updated every ~5s. Before the first probe the display marks "00C" this is normal. diff --git a/plugins/samplesink/plutosdroutput/readme.md b/plugins/samplesink/plutosdroutput/readme.md index b8724eb6a..680d0b857 100644 --- a/plugins/samplesink/plutosdroutput/readme.md +++ b/plugins/samplesink/plutosdroutput/readme.md @@ -2,7 +2,7 @@

Introduction

-This output sample sink plugin sends its samples to a [PlutoSDR device](https://wiki.analog.com/university/tools/pluto). This is also known as the ADALM-Pluto. ADALM stands for Analog Devices Active Learning Module and is targetting students in electrical engineering and digital signal processing. Of course it can be used as a radio device like any other SDR. +This output sample sink plugin sends its samples to a [PlutoSDR device](https://wiki.analog.com/university/tools/pluto). This is also known as the ADALM-Pluto. ADALM stands for Analog Devices Active Learning Module and is targeting students in electrical engineering and digital signal processing. Of course it can be used as a radio device like any other SDR. As you can see from the Wiki this is becoming a fairly popular SDR hardware platform. It does have interesting features but the library documentation and examples are poor when not misleading. Therefore while this implementation does work it should still be considered experimental. @@ -45,7 +45,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again.

1.3: DAC sample rate

@@ -63,19 +63,19 @@ Use this slider to adjust LO correction in ppm. It can be varied from -20.0 to 2 This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

2a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for up converters and negative for down converters. -For example with a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set to 7,130 kHz the PuotSDR will be set to 127.130 MHz. +For example with a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set to 7,130 kHz the PlutoSDR will be set to 127.130 MHz. -If you use an up converter to transmit at the 6 cm band narrowband center frequency of 5670 MHz aith the PlutoSDR set at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. +If you use an up converter to transmit at the 6 cm band narrowband center frequency of 5670 MHz with the PlutoSDR set at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to transmit at the 10368 MHz frequency with 432 MHz for the PlutoSDR you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. @@ -101,7 +101,7 @@ The AD9363 has many port options however as only the A output is connected you s This is the AD9363 device to/from host stream sample rate in S/s. It is the same for the Rx and Tx systems. -Use the wheels to adjust the sample rate. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. +Use the wheels to adjust the sample rate. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. The minimum sample rate depends on the hardware FIR decimation factor (9) and is the following: @@ -113,7 +113,7 @@ The maximum sample rate is fixed and set to 20 MS/s

6: Tx analog filter bandwidth

-This is the Tx analog filter bandwidth in kHz in the AD9363 device. It can be varied from 625 kHz to 16 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +This is the Tx analog filter bandwidth in kHz in the AD9363 device. It can be varied from 625 kHz to 16 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: Hardware FIR filter toggle

@@ -123,11 +123,11 @@ The FIR filter settings are the same on Rx and Tx side therefore any change here

8: Hardware FIR filter bandwidth

-Use the wheels to adjust the bandwidth of the hardware FIR filter. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the bandwidth of the hardware FIR filter. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The filter limits are calculated as 0.05 and 0.9 times the FIR filter input frequency for the lower and higher limit respectively. The FIR filter input frequency is the baseband sample rate (5) multiplied by the FIR interpolation factor (9) -For bandwidths greater than 0.2 times the FIR filter input frequency the filter is calculated as a windowed FIR filter with a Blackman-Harris window. This has a high out of band rejection value at the expense of a slightly smoother roll off compared to other filters. The bandwidth value sets the -6 dB point approxomately. +For bandwidths greater than 0.2 times the FIR filter input frequency the filter is calculated as a windowed FIR filter with a Blackman-Harris window. This has a high out of band rejection value at the expense of a slightly smoother roll off compared to other filters. The bandwidth value sets the -6 dB point approximately. For bandwidths between 0.05 and 0.2 times the FIR filter input frequency the window used is a Hamming window giving a sharper transition. @@ -149,4 +149,4 @@ This is the indicative RSSI of the transmitter. It works only when the Rx is in

13: Board temperature

-This is the board temperature in degrees Celsius updated every ~5s. \ No newline at end of file +This is the board temperature in degrees Celsius updated every ~5s. diff --git a/plugins/samplesink/sdrdaemonsink/readme.md b/plugins/samplesink/sdrdaemonsink/readme.md index 9f1d0aea9..906cc2983 100644 --- a/plugins/samplesink/sdrdaemonsink/readme.md +++ b/plugins/samplesink/sdrdaemonsink/readme.md @@ -2,7 +2,7 @@

Introduction

-This output sample sink plugin sends its samples over tbe network to a SDRdaemon transmitter server using UDP connection. SDRdaemon refers to the SDRdaemon utility `sdrdaemontx`found in [this](https://github.com/f4exb/sdrdaemon) Github repostory. +This output sample sink plugin sends its samples over tbe network to a SDRdaemon transmitter server using UDP connection. SDRdaemon refers to the SDRdaemon utility `sdrdaemontx`found in [this](https://github.com/f4exb/sdrdaemon) Github repository. Forward Error Correction with a Cauchy MDS block erasure codec is used to prevent block loss. This can make the UDP transmission more robust particularly over WiFi links. @@ -64,7 +64,7 @@ This sets the number of FEC blocks per frame. A frame consists of 128 data block

6.2: Distant transmitter queue length

-This is the samples queue length reported from the distant transmitter. This is a numnber of vectors of 127 ✕ 127 ✕ _I_ samples where _I_ is the interpolation factor. This corresponds to a block of 127 ✕ 127 samples sent over the network. This numbers serves to thottle the sample generator so that the queue length is close to 8 vectors. +This is the samples queue length reported from the distant transmitter. This is a number of vectors of 127 ✕ 127 ✕ _I_ samples where _I_ is the interpolation factor. This corresponds to a block of 127 ✕ 127 samples sent over the network. This numbers serves to throttle the sample generator so that the queue length is close to 8 vectors.

6.3: Stream status

@@ -95,7 +95,7 @@ This counter counts the unrecoverable error conditions found (i.e. 6.4 between 1

6.8: events counters timer

-This hh:mm:ss time display shows the time since the reset evetnts counters button (4.6) was pushed. +This hh:mm:ss time display shows the time since the reset events counters button (4.6) was pushed.

7: Network parameters

diff --git a/plugins/samplesource/airspyhf/readme.md b/plugins/samplesource/airspyhf/readme.md index 9e6c96c0f..8a7a73b7e 100644 --- a/plugins/samplesource/airspyhf/readme.md +++ b/plugins/samplesource/airspyhf/readme.md @@ -35,7 +35,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -66,7 +66,7 @@ Use this combo box to select the HF or VHF range. This will set the limits of th This is the device to host sample rate in kilo samples per second (kS/s). -Although the combo box is there to present a choice of sample rates at present the AirspyHF+ deals only with 768 kS/s. However the support library has provision to get a list of sample rates from the device incase of future developments. +Although the combo box is there to present a choice of sample rates at present the AirspyHF+ deals only with 768 kS/s. However the support library has provision to get a list of sample rates from the device in case of future developments.

6: Decimation factor

@@ -76,13 +76,13 @@ The I/Q stream from the AirspyHF to host is downsampled by a power of two before This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

7a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. diff --git a/plugins/samplesource/bladerfinput/readme.md b/plugins/samplesource/bladerfinput/readme.md index c62f96997..94eea48b4 100644 --- a/plugins/samplesource/bladerfinput/readme.md +++ b/plugins/samplesource/bladerfinput/readme.md @@ -28,7 +28,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -62,11 +62,11 @@ This controls the optional XB-200 add-on when it is fitted to the BladeRF main b This is the BladeRF device ADC sample rate in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

5: Decimation factor

-The I/Q stream from the BladeRF ADC is doensampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. +The I/Q stream from the BladeRF ADC is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64.

6: Baseband center frequency position relative the the BladeRF Rx center frequency

@@ -94,4 +94,4 @@ The VGA1 gain can be adjusted from 5 dB to 30 dB in 1 dB steps. The VGA1 is insi

10: Variable gain amplifier #2 gain

-The VGA2 gain can be adjusted from 0 dB to 30 dB in 3 dB steps. The VGA2 is inside the LMS6002D chip and is placed between the baseband filter and the ADC. \ No newline at end of file +The VGA2 gain can be adjusted from 0 dB to 30 dB in 3 dB steps. The VGA2 is inside the LMS6002D chip and is placed between the baseband filter and the ADC. diff --git a/plugins/samplesource/hackrfinput/readme.md b/plugins/samplesource/hackrfinput/readme.md index 6c78948fa..02e220dc5 100644 --- a/plugins/samplesource/hackrfinput/readme.md +++ b/plugins/samplesource/hackrfinput/readme.md @@ -2,7 +2,7 @@

Introduction

-This intput sample source plugin gets its samples from a [HackRF device](https://greatscottgadgets.com/hackrf/). +This input sample source plugin gets its samples from a [HackRF device](https://greatscottgadgets.com/hackrf/).

Build

@@ -28,7 +28,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Red square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Red square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. If you have the Tx open in another tab and it is running then it will be stopped automatically before the Rx starts. In a similar manner the Rx will be stopped before the Tx is started from the Tx tab. @@ -73,7 +73,7 @@ Use this checkbox to toggle the extra low noise amplifier (LNA). This gives an a This is the HackRF device ADC sample rate in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: Rx filter bandwidth

@@ -84,11 +84,11 @@ This is the Rx filter bandwidth in kHz. Possible values are: 1750, 2500, 3500, 5 The device stream from the HackRF is decimated to obtain the baseband stream. Possible values are: - **1**: no decimation - - **2**: divide devcie stream sample rate by 2 - - **4**: divide devcie stream sample rate by 4 - - **8**: divide devcie stream sample rate by 8 - - **16**: divide devcie stream sample rate by 16 - - **32**: divide devcie stream sample rate by 32 + - **2**: divide device stream sample rate by 2 + - **4**: divide device stream sample rate by 4 + - **8**: divide device stream sample rate by 8 + - **16**: divide device stream sample rate by 16 + - **32**: divide device stream sample rate by 32

10: Internal LNA gain

diff --git a/plugins/samplesource/limesdrinput/readme.md b/plugins/samplesource/limesdrinput/readme.md index b10565d3c..758fbd190 100644 --- a/plugins/samplesource/limesdrinput/readme.md +++ b/plugins/samplesource/limesdrinput/readme.md @@ -49,7 +49,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again.

1.3: Record

@@ -81,9 +81,9 @@ Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an ind

2.2: NCO frequency shift

-This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of reception minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. +This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of reception minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. -☞ In the LMS7002M TSP block the NCO sits before the decimator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual ADC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware decimation factor. For example with a 4 MS/s device to host sample rate (5) and a hadrware decimation of 16 (3) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). +☞ In the LMS7002M TSP block the NCO sits before the decimator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual ADC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware decimation factor. For example with a 4 MS/s device to host sample rate (5) and a hardware decimation of 16 (3) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz).

2.3: DC component auto correction

@@ -97,13 +97,13 @@ Enables or disables the auto I/Q balance correction. The DC correction must be e This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.
2.5.1: Translating frequency
-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. @@ -129,11 +129,11 @@ Use this button to open a dialog that lets you choose the external clock frequen ![LimeSDR input plugin gain GUI](../../../doc/img/LimeSDR_plugin_extclock.png) -
2.6.1: Exrernal clock frequency
+
2.6.1: External clock frequency
Can be varied from 5 to 300 MHz -Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. +Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor.
2.6.2: Enable/disable external clock input
@@ -155,19 +155,19 @@ Thus the actual sample rate of the ADC is the stream sample rate (5) multiplied

4: Software decimation factor

-The I/Q stream from the LimeSDR is doensampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. +The I/Q stream from the LimeSDR is downsampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32.

5: Device to host stream sample rate

This is the LMS7002M device to/from host stream sample rate in S/s. It is the same for the Rx and Tx systems. -Use the wheels to adjust the sample rate. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. +Use the wheels to adjust the sample rate. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. The LMS7002M uses the same clock for both the ADCs and DACs therefore this sample rate affects all of the 2x2 MIMO channels.

6: Rx hardware filter bandwidth

-This is the Rx hardware filter bandwidth in kHz in the LMS7002M device for the given channel. Boundaries are updated automatically but generally are from 1.4 to 130 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +This is the Rx hardware filter bandwidth in kHz in the LMS7002M device for the given channel. Boundaries are updated automatically but generally are from 1.4 to 130 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: TSP FIR filter toggle

@@ -175,7 +175,7 @@ The TSP in the LMS7002M chip has a FIR filter chain per channel. Use this button

8: TSP FIR filter bandwidth

-USe the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

9: Gain settings

@@ -183,17 +183,17 @@ USe the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing

9.1: Gain mode

-Use this combo to select either the automatic gain (Aut) or the manual (Man) gain setting. Autonatic gain sets the global gain using a predefined table for LNA, TIA and PGA gain blocks. This global gain is set with button 9.2. When manual gain is engaged the LNA, TIA and PGA gains can be set independently with the 9.3, 9.4 and 9.5 buttons respectively. +Use this combo to select either the automatic gain (Aut) or the manual (Man) gain setting. Automatic gain sets the global gain using a predefined table for LNA, TIA and PGA gain blocks. This global gain is set with button 9.2. When manual gain is engaged the LNA, TIA and PGA gains can be set independently with the 9.3, 9.4 and 9.5 buttons respectively. Please refer to [LMS7002M documentation](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) for a precise description of LNA, TIA and PGA and their location in the Rx chain. To summarize these blocks are placed in this order from antenna to ADC.

9.2: Global automatic gain

-Use this button to adjust the global gain of the LNA, TIA and PGA. LimeSuite software automatically set optimal values of the amplifiers to achive this global gain. This gain can be set between 0 and 70 dB in 1 dB steps. The value in dB appears at the right of the button. +Use this button to adjust the global gain of the LNA, TIA and PGA. LimeSuite software automatically set optimal values of the amplifiers to achieve this global gain. This gain can be set between 0 and 70 dB in 1 dB steps. The value in dB appears at the right of the button.

9.3: LNA manual gain

-Use this button to adjust the gain of tha LNA when manual gain mode is set (9.1). Gain can be set between 1 and 30 dB in 1 dB steps. However the hardware has 3 dB steps for the lower gain values so increasing or decerasing by one step does not always produce a change. The value in dB appears at the right of the button. +Use this button to adjust the gain of tha LNA when manual gain mode is set (9.1). Gain can be set between 1 and 30 dB in 1 dB steps. However the hardware has 3 dB steps for the lower gain values so increasing or decreasing by one step does not always produce a change. The value in dB appears at the right of the button.

9.4: TIA manual gain

@@ -209,7 +209,7 @@ Use this combo box to select the antenna input: - **No**: None - **Lo**: Selects the low frequency input (700 to 900 MHz nominally) - - **Hi**: Selects the high frequncy input (2 to 2.6 GHz) + - **Hi**: Selects the high frequency input (2 to 2.6 GHz) - **Wo**: Selects the wideband input - **T1**: Selects loopback from TX #1 (experimental) - **T1**: Selects loopback from TX #2 (experimental) @@ -234,4 +234,4 @@ This is the fill percentage of the Rx FIFO in the LimeSuite interface. It should

16: Board temperature

-This is the board temperature in degrees Celsius updated every ~5s. Before the first probe the display marks "00C" this is normal. \ No newline at end of file +This is the board temperature in degrees Celsius updated every ~5s. Before the first probe the display marks "00C" this is normal. diff --git a/plugins/samplesource/perseus/readme.md b/plugins/samplesource/perseus/readme.md index 18a961ba7..1b24c42ab 100644 --- a/plugins/samplesource/perseus/readme.md +++ b/plugins/samplesource/perseus/readme.md @@ -39,7 +39,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -59,7 +59,7 @@ This resets the LO ppm correction (zero the value).

4: Device to hast sample rate

-This is the device to host sample rate in kilo samples per second (kS/s). The sample rate can be as low as 48 kS/s so there is no need for software decimation. Note that at 48 kS/s some slight rate mismatch can appear with the audio that has the same nominal rate. This may cause some occasinal audio samples drops however hardly noticeable. +This is the device to host sample rate in kilo samples per second (kS/s). The sample rate can be as low as 48 kS/s so there is no need for software decimation. Note that at 48 kS/s some slight rate mismatch can appear with the audio that has the same nominal rate. This may cause some occasional audio samples drops however hardly noticeable.

5: Wideband mode

@@ -67,19 +67,19 @@ Switch on this button to disable the preselection filters. The corresponding LED

6: Decimation factor

-The I/Q stream from the Perseus to host is downsampled by a power of two before being sent to the passband. This is normally not needed for most use cases as the Perseus can go as low as 48 kS/s which is the lower limit for audio channel plugins (AM, FM, SSB, Digital voice). So it can be left to `1` most of the time. A software decimation by 2 or 4 is still provided for easier anaysis of very narrowband or slow varying signals. Note that there is no dynamic gain with this decimation as the precision is already limited to 24 significant bits either for integer or floating point (float) processing. +The I/Q stream from the Perseus to host is downsampled by a power of two before being sent to the passband. This is normally not needed for most use cases as the Perseus can go as low as 48 kS/s which is the lower limit for audio channel plugins (AM, FM, SSB, Digital voice). So it can be left to `1` most of the time. A software decimation by 2 or 4 is still provided for easier analysis of very narrowband or slow varying signals. Note that there is no dynamic gain with this decimation as the precision is already limited to 24 significant bits either for integer or floating point (float) processing.

7: Transverter mode open dialog

This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

7a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. @@ -101,7 +101,7 @@ Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes.

8: Attenuators control

-Use this combo box to cotrol the attenuators inside the Perseus: +Use this combo box to control the attenuators inside the Perseus: - 0 dB: no attenuation - 10 dB: 10 dB attenuator engaged @@ -117,4 +117,4 @@ Use this button to turn on or off the Perseus ADC dithering

10: ADC preamplifier

Use this button to turn on or off the Perseus ADC preamplifier - \ No newline at end of file + diff --git a/plugins/samplesource/plutosdrinput/readme.md b/plugins/samplesource/plutosdrinput/readme.md index 409370b37..cc4507d74 100644 --- a/plugins/samplesource/plutosdrinput/readme.md +++ b/plugins/samplesource/plutosdrinput/readme.md @@ -2,7 +2,7 @@

Introduction

-This input sample source plugin gets its samples from a [PlutoSDR device](https://wiki.analog.com/university/tools/pluto). This is also known as the ADALM-Pluto. ADALM stands for Analog Devices Active Learning Module and is targetting students in electrical engineering and digital signal processing. Of course it can be used as a radio device like any other SDR. +This input sample source plugin gets its samples from a [PlutoSDR device](https://wiki.analog.com/university/tools/pluto). This is also known as the ADALM-Pluto. ADALM stands for Analog Devices Active Learning Module and is targeting students in electrical engineering and digital signal processing. Of course it can be used as a radio device like any other SDR. As you can see from the Wiki this is becoming a fairly popular SDR hardware platform. It does have interesting features but the library documentation and examples are poor when not misleading. Therefore while this implementation does work it should still be considered experimental. @@ -45,7 +45,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again.

1.3: Record

@@ -76,13 +76,13 @@ These buttons control the software DSP auto correction options: This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

4a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. @@ -104,7 +104,7 @@ Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes.

5: Software decimation factor

-The I/Q stream from the PlutoSDR is doensampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. +The I/Q stream from the PlutoSDR is downsampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64.

6: Decimated bandpass center frequency placement

@@ -120,7 +120,7 @@ The AD9363 has many port options however as only the A balanced input is connect This is the AD9363 device to/from host stream sample rate in S/s. It is the same for the Rx and Tx systems. -Use the wheels to adjust the sample rate. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. +Use the wheels to adjust the sample rate. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. The minimum sample rate depends on the hardware FIR decimation factor (12) and is the following: @@ -132,7 +132,7 @@ The maximum sample rate is fixed and set to 20 MS/s

9: Rx analog filter bandwidth

-This is the Rx analog filter bandwidth in kHz in the AD9363 device. It can be varied from 200 kHz to 14 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +This is the Rx analog filter bandwidth in kHz in the AD9363 device. It can be varied from 200 kHz to 14 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

10: Hardware FIR filter toggle

@@ -142,11 +142,11 @@ The FIR filter settings are the same on Rx and Tx side therefore any change here

11: Hardware FIR filter bandwidth

-Use the wheels to adjust the bandwidth of the hardware FIR filter. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the bandwidth of the hardware FIR filter. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The filter limits are calculated as 0.05 and 0.9 times the FIR filter input frequency for the lower and higher limit respectively. The FIR filter input frequency is the baseband sample rate (5) multiplied by the FIR interpolation factor (9) -For bandwidths greater than 0.2 times the FIR filter input frequency the filter is calculated as a windowed FIR filter with a Blackman-Harris window. This has a high out of band rejection value at the expense of a slightly smoother roll off compared to other filters. The bandwidth value sets the -6 dB point approxomately. +For bandwidths greater than 0.2 times the FIR filter input frequency the filter is calculated as a windowed FIR filter with a Blackman-Harris window. This has a high out of band rejection value at the expense of a slightly smoother roll off compared to other filters. The bandwidth value sets the -6 dB point approximately. For bandwidths between 0.05 and 0.2 times the FIR filter input frequency the window used is a Hamming window giving a sharper transition. @@ -183,4 +183,4 @@ This is the indicative RSSI of the receiver.

17: Board temperature

-This is the board temperature in degrees Celsius updated every ~5s. \ No newline at end of file +This is the board temperature in degrees Celsius updated every ~5s. diff --git a/plugins/samplesource/rtlsdr/readme.md b/plugins/samplesource/rtlsdr/readme.md index f74aa533d..cd0a60941 100644 --- a/plugins/samplesource/rtlsdr/readme.md +++ b/plugins/samplesource/rtlsdr/readme.md @@ -28,7 +28,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -61,13 +61,13 @@ Possible values are: This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

4a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. @@ -96,11 +96,11 @@ When button is off the sample rate can vary from 950 kS/s to 2400 kS/s This is the device sample rate in samples per second (S/s). -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: Decimation factor

-The I/Q stream from the RTLSDR ADC is doensampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. +The I/Q stream from the RTLSDR ADC is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64.

8: Direct sampling mode

@@ -114,4 +114,4 @@ This controls the tuner filter bandwidth and can be varied from 350 kHz to 8 MHz The slider sets RF gain in dB. The values are defined in the RTLSDR device and generally are: 0.0, 0.9, 1.4, 2.7, 3.7, 7.7, 8.7, 12.5, 14.4, 15.7, 16.6, 19.7, 20.7, 22.9, 25.4, 28.0, 29.7, 32.8, 33.8, 36.4, 37.2, 38.6, 40.2, 42.1, 43.4, 43.9, 44.5, 48.0, 49.6 -The AGC checkbox can be used to switch on or off the RTL2838 AGC. This is independent of the gain setting as this AGC acts after the gain block. \ No newline at end of file +The AGC checkbox can be used to switch on or off the RTL2838 AGC. This is independent of the gain setting as this AGC acts after the gain block. diff --git a/plugins/samplesource/sdrdaemonsource/readme.md b/plugins/samplesource/sdrdaemonsource/readme.md index 36d72f053..07cfd9415 100644 --- a/plugins/samplesource/sdrdaemonsource/readme.md +++ b/plugins/samplesource/sdrdaemonsource/readme.md @@ -2,7 +2,7 @@

Introduction

-This input sample source plugin gets its samples over tbe network from a SDRdaemon receiver server using UDP connection. SDRdaemon refers to the SDRdaemon utility `sdrdaemonrx`found in [this](https://github.com/f4exb/sdrdaemon) Github repostory. +This input sample source plugin gets its samples over tbe network from a SDRdaemon receiver server using UDP connection. SDRdaemon refers to the SDRdaemon utility `sdrdaemonrx`found in [this](https://github.com/f4exb/sdrdaemon) Github repository. Forward Error Correction with a Cauchy MDS block erasure codec is used to prevent block loss. This can make the UDP transmission more robust particularly over WiFi links. @@ -33,7 +33,7 @@ Device start / stop button.

1.3: Record

-Record I/Q stresm toggle button +Record I/Q stream toggle button

1.4: Stream sample rate

@@ -74,7 +74,7 @@ There are two gauges separated by a dot in the center. Ideally these gauges shou - The left gauge is the negative gauge. It is the value in percent of buffer size from the write pointer position to the read pointer position when this difference is less than half of a buffer distance. It means that the writes are leading or reads are lagging. - The right gauge is the positive gauge. It is the value in percent of buffer size of the difference from the read pointer position to the write pointer position when this difference is less than half of a buffer distance. It menas that the writes are lagging or reads are leading. -The system tries to compensate read / write unbalance however at start or when a large stream distruption has occured a delay of a few tens of seconds is necessary before read / write reaches equilibrium. +The system tries to compensate read / write unbalance however at start or when a large stream disruption has occurred a delay of a few tens of seconds is necessary before read / write reaches equilibrium.

4: Forward Error Correction setting and status

@@ -102,7 +102,7 @@ The color of the icon indicates stream status: This is the minimum total number of blocks per frame during the last polling period. If all blocks were received for all frames then this number is the nominal number of original blocks plus FEC blocks (Green lock icon). In our example this is 128+8 = 136. -If this number falls below 128 then some blocks are definitely lost and the lock lits in red. +If this number falls below 128 then some blocks are definitely lost and the lock lights in red.

4.5: Maximum number of FEC blocks used by frame

@@ -122,7 +122,7 @@ This counter counts the unrecoverable error conditions found (i.e. 4.4 between 1

4.9: events counters timer

-This hh:mm:ss time display shows the time since the reset evetnts counters button (4.6) was pushed. +This hh:mm:ss time display shows the time since the reset events counters button (4.6) was pushed.

5: Network parameters

@@ -148,7 +148,7 @@ When the return key is hit within the address (5.1), data port (5.2) or configur This is the center frequency sent to the distant device. This becomes reflected in the main frequency dial (1.1) only when it gets acknowledged by the distant server and this frequency is sent back in the frames meta data. -Use the wheels to adjust the frequency. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 0 Hz and the maximum value is 9.9 GHz. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 0 Hz and the maximum value is 9.9 GHz. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: Delay between UDP blocks transmission

@@ -167,7 +167,7 @@ Formula: ((127 ✕ 127 ✕ _d_) / _SR_) / (128 + _F_) This is the device sample rate sent to the distant device. It will be divided in the distant server by the decimation factor set with (9) to give the actual sample rate over the network. This becomes effective and displayed in (1.4) only when it gets acknowledged by the distant server and this sample rate is sent back in the frames meta data. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 32 kS/s and the maximum value is 9.9 MS/s. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 32 kS/s and the maximum value is 9.9 MS/s. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

9: Desired distant decimation factor

@@ -175,7 +175,7 @@ This is the decimation factor to be set in the distant server downsampler. The h

10: Center frequency position

-The center frequency in the passband wil be set either: +The center frequency in the passband will be set either: - below the local oscillator (NCO) or infradyne. Actually -1/4th the bandwidth. - above the local oscillator (NCO) or supradyne. Actually +1/4th the bandwidth. diff --git a/plugins/samplesource/sdrplay/readme.md b/plugins/samplesource/sdrplay/readme.md index cc2128d28..c955f62c1 100644 --- a/plugins/samplesource/sdrplay/readme.md +++ b/plugins/samplesource/sdrplay/readme.md @@ -6,7 +6,7 @@ This plugin supports input from SDRplay RSP1 devices. SDRplay is based on the MS No Windows support -Driver is too unstable in Windows randomly stopping the appication and causing BSOD. +Driver is too unstable in Windows randomly stopping the application and causing BSOD.

Build

diff --git a/plugins/samplesource/testsource/readme.md b/plugins/samplesource/testsource/readme.md index b357c6f16..912a86bd4 100644 --- a/plugins/samplesource/testsource/readme.md +++ b/plugins/samplesource/testsource/readme.md @@ -26,7 +26,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -50,7 +50,7 @@ This combo box control the local DSP auto correction options:

2.2: Decimation factor

-The I/Q stream from the generator is doensampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. This exercises the decimation chain. +The I/Q stream from the generator is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. This exercises the decimation chain. This exercises the decimation chain. diff --git a/sdrgui/audio.md b/sdrgui/audio.md index e95a63d2a..b17d21fb8 100644 --- a/sdrgui/audio.md +++ b/sdrgui/audio.md @@ -92,7 +92,7 @@ Use this button to keep only the visible devices in the devices registrations. T

1.14 Unregister device

-Use this button to remove the device from the devices registrations returning it to the unregistered state. Therefore when associated to an output stream or selected it will initially take default values and appear with the `D` induicator in the list. +Use this button to remove the device from the devices registrations returning it to the unregistered state. Therefore when associated to an output stream or selected it will initially take default values and appear with the `D` indicator in the list.

1.15 OK button

@@ -152,7 +152,7 @@ Use this button to keep only the visible devices in the devices registrations. T

2.9 Unregister device

-Use this button to remove the device from the devices registrations returning it to the unregistered state. Therefore when associated to an output stream or selected it will initially take default values and appear with the `D` induicator in the list. +Use this button to remove the device from the devices registrations returning it to the unregistered state. Therefore when associated to an output stream or selected it will initially take default values and appear with the `D` indicator in the list.

2.10 OK button

diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 9d6b5a68b..21da04252 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -4,7 +4,7 @@ Starting with version 2 SDRangel supports running several sampling devices simultaneously. Each concurrent device is associated to a slot with a set of tabbed windows in the UI. These tabs are marked R0, R1, R2... -The slots are arranged in a stacked fashion so that when a new device is added with the Acquisition -> Add device set menu a new slot is allocated in the last position and when a devcie is removed with the Acquisition -> Remove last device set menu the slot in the last position is deleted. Slot 0 (R0) receiver slot is created at initialization and cannot be deleted with the menu. The letter "R" in the tab names indicates that the slot is for a receiver (source) device while "T" designates a tramsmitter (sink) device. +The slots are arranged in a stacked fashion so that when a new device is added with the Acquisition -> Add device set menu a new slot is allocated in the last position and when a device is removed with the Acquisition -> Remove last device set menu the slot in the last position is deleted. Slot 0 (R0) receiver slot is created at initialization and cannot be deleted with the menu. The letter "R" in the tab names indicates that the slot is for a receiver (source) device while "T" designates a transmitter (sink) device. The tabbed windows are: @@ -16,11 +16,11 @@ The tabbed windows are: The combination of a sampling device and its associated channels is called a "device set". -![Main Window nulti device support](../doc/img/MainWindow_tabs.png) +![Main Window multi device support](../doc/img/MainWindow_tabs.png) The sampling devices tab (1) acts as a master and when one of its tabs is selected all other tabs are selected accordingly i.e. all R0s, all R1s, etc... in tabs (2), (3), (4) and (5) -In each slave tab group (2), (3), (4) and (5) an individual tab corresponding to one device can be selected without affecting the selection of the other tabs. This way you can sneak peek into another spectrum or channel goup without affecting the display of other tabbed windows. +In each slave tab group (2), (3), (4) and (5) an individual tab corresponding to one device can be selected without affecting the selection of the other tabs. This way you can sneak peek into another spectrum or channel group without affecting the display of other tabbed windows.

Interface details

@@ -171,7 +171,7 @@ This is the sampling rate in kS/s of the I/Q stream extracted from the device af

2.4. Center frequency

-This is the current center frequency in kHz with dot separated thousands (MHz, GHz). On devices for which frequency can be directly controlled (i.e. all except File Source and SDRdaemon) you can use the thumbwheels to set the frequency. Thumwheels move with the mouse wheel when hovering over a digit. +This is the current center frequency in kHz with dot separated thousands (MHz, GHz). On devices for which frequency can be directly controlled (i.e. all except File Source and SDRdaemon) you can use the thumbwheels to set the frequency. Thumbwheels move with the mouse wheel when hovering over a digit. When left clicking on a digit a cursor is set on it and you can also use the arrows to move the corresponding thumbwheel. @@ -214,7 +214,7 @@ Use the `Cancel` button to exit the dialog without any change

3.3. Reload currently selected device

-This button activates a close/open sequence to recycle the device. It may be useful when the device is not streaming anymore or in an attempt to clear possible errors. Make sure the streaming is stopeed first. +This button activates a close/open sequence to recycle the device. It may be useful when the device is not streaming anymore or in an attempt to clear possible errors. Make sure the streaming is stopped first.

3.4. Channel selector

@@ -230,7 +230,7 @@ These are the controls of the main spectrum display in (7). Please refer to the

5. Presets and commands

-The presets and commands tree view are by default stacked in tabbs. The following sections describe the presets section 5A) and commands (section 5B) views successively +The presets and commands tree view are by default stacked in tabs. The following sections describe the presets section 5A) and commands (section 5B) views successively

5A. Presets

@@ -309,7 +309,7 @@ This is a tree view of the saved commands. Commands describe the path to an exec Typically an "executable file" is a script (Python, shell, whatever...) or can be a compiled program (c, c++, java, whatever...) that interacts with SDRangel using its web REST API. When called from within SDRangel they can act as "macros" allowing to perform actions automatically. -Of course any binary that resides in your system can be used that way like `/bin/ls` or `/bin/date` although these two are of anedoctical interest... +Of course any binary that resides in your system can be used that way like `/bin/ls` or `/bin/date` although these two are of anecdotal interest... ![Main Window presets view](../doc/img/MainWindow_commands_view.png) @@ -373,10 +373,10 @@ You can edit the description using this text box. The description will appear in
5B.6.3.3. Executable file selection
-Clicking on this button wil open a file dialog to select the executable file that will be run with this command. The file selection dialog has predefined file pattern selections: +Clicking on this button will open a file dialog to select the executable file that will be run with this command. The file selection dialog has predefined file pattern selections: - `*` for All files - - `*.py` for Pyhon files + - `*.py` for Python files - `*.sh` or `*.bat` for shell or batch files - `*.bin` or `*.exe` for binary files @@ -420,7 +420,7 @@ Use the "Cancel" button to cancel the changes.
5B.6.4. Run command or groups of commands
-This will run the currently selected command. If the selection is a group it will run all commands of the group starting them in the displayed order. Please note that commands are run in independant processes and therefore all launched commands in the group will run concurrently. +This will run the currently selected command. If the selection is a group it will run all commands of the group starting them in the displayed order. Please note that commands are run in independent processes and therefore all launched commands in the group will run concurrently.
5B.6.5. View last command run details
@@ -430,9 +430,9 @@ This dialog will show the results of the last run including the output (merged s
5B.6.5.1. Process status
-When the process is not running the stop icon (■) is displayed. The backgroumd color indicate different states: +When the process is not running the stop icon (■) is displayed. The background color indicate different states: - - no color (same as backround): the process has never run during this session + - no color (same as background): the process has never run during this session - red: the process ended with error - green: the process ended successfully. This does not mean that there was no programmatic error. @@ -456,7 +456,7 @@ This is the process PID. It is 0 if the process has never run during this sessio
5B.6.5.6. Process kill
-Use this button to kill (send SIGKILL) the running process. It has no effect if the process is not runing. +Use this button to kill (send SIGKILL) the running process. It has no effect if the process is not running.
5B.6.5.7. Command line
@@ -492,7 +492,7 @@ By pushing the "Close" button the process output window is closed.
5B.6.6. Save commands
-This will save the commands immediately. The commands will be automatically saved when the applicaiton exits normally. +This will save the commands immediately. The commands will be automatically saved when the application exits normally.
5B.6.7. Delete commands or group of commands
@@ -504,9 +504,9 @@ Use this button to activate the keyboard binding. This requires that the focus i

6. Channels

-This area shows the control GUIs of the channels curently active for the device. When the preset is saved (as default at exit time or as a saved preset) the GUIs are ordered by increasing frequency. If presets share the same frequenccy they are ordered by their internal ID name. Thus new channel GUIs will appear ordered only when reloaded. +This area shows the control GUIs of the channels currently active for the device. When the preset is saved (as default at exit time or as a saved preset) the GUIs are ordered by increasing frequency. If presets share the same frequency they are ordered by their internal ID name. Thus new channel GUIs will appear ordered only when reloaded. -Details about the GUIs can be found in the channel plugins documentation which consits of a readme.md file in each of the channel plugins folder (done partially). +Details about the GUIs can be found in the channel plugins documentation which consists of a readme.md file in each of the channel plugins folder (done partially). With these channels: AM demod, BFM demod, DSD demod, NFM demod, UDP source, UDP sink some common basic settings can be set with a popup dialog. This dialog is opened by clicking on the small grey square on the top left of the channel window. The settings are as follows: @@ -541,7 +541,7 @@ Do not make any changes and exit dialog This shows the spectrum in the passband returned from the sampling device possibly after decimation. The actual sample rate is shown in the device control at the left of the frequency display (2.3) -The spectrum display is cotrolled by the display control (4). +The spectrum display is controlled by the display control (4).

8. Status

diff --git a/swagger/sdrangel/examples/Readme.md b/swagger/sdrangel/examples/Readme.md index 4d6521a15..4bf83a84d 100644 --- a/swagger/sdrangel/examples/Readme.md +++ b/swagger/sdrangel/examples/Readme.md @@ -1,6 +1,6 @@ ## Examples of client scripts ## -These are all Python scripts using python-requests so you have to install this package as a prerequiste either with your package manager or pip. They are designed for Python 2.7 but may work with Python 3 possibly with minimal changes. +These are all Python scripts using python-requests so you have to install this package as a prerequisite either with your package manager or pip. They are designed for Python 2.7 but may work with Python 3 possibly with minimal changes.

add_channel.py

From 7c67b7de7cee32c52e520aa540395daf9e9e9018 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Fri, 20 Apr 2018 17:49:46 -0700 Subject: [PATCH 304/956] qrtplib: clear buffer fed to RTPSession::CreateCNAME The RTPSession::CreateCNAME function checks to see if the buffer that it is provided already has any data in it, and appends to it if so. The RTPSession::InternalCreate function calls this function with an uninitialized buffer, which results in indeterminate behavior. To ensure that the CNAME is properly created, we clear the buffer before use. ==30323== Conditional jump or move depends on uninitialised value(s) ==30323== at 0x4C30109: __strlen_sse2 (vg_replace_strmem.c:460) ==30323== by 0x85647A4: qrtplib::RTPSession::CreateCNAME(unsigned char*, unsigned long*, bool) (rtpsession.cpp:1150) ==30323== by 0x8564B35: qrtplib::RTPSession::InternalCreate(qrtplib::RTPSessionParams const&) (rtpsession.cpp:218) ==30323== by 0x5499159: RTPSink::RTPSink(QUdpSocket*, int, bool) (rtpsink.cpp:48) ==30323== by 0x5420B6A: AudioNetSink::AudioNetSink(QObject*, int, bool) (audionetsink.cpp:42) ==30323== by 0x541F465: AudioOutput::start(int, int) (audiooutput.cpp:114) ==30323== by 0x5412763: AudioDeviceManager::startAudioOutput(int) (audiodevicemanager.cpp:361) ==30323== by 0x5412B0C: AudioDeviceManager::addAudioSink(AudioFifo*, MessageQueue*, int) (audiodevicemanager.cpp:229) ==30323== by 0x33F96DE7: BFMDemod::BFMDemod(DeviceSourceAPI*) (bfmdemod.cpp:56) ==30323== by 0x33FB03F2: non-virtual thunk to BFMPlugin::createRxChannelBS(DeviceSourceAPI*) (bfmplugin.cpp:62) ==30323== by 0x4F47F25: DeviceUISet::loadRxChannelSettings(Preset const*, PluginAPI*) (deviceuiset.cpp:199) ==30323== by 0x4EA51EA: MainWindow::loadPresetSettings(Preset const*, int) (mainwindow.cpp:575) ==30323== by 0x4EAC81B: MainWindow::MainWindow(qtwebapp::LoggerWithFile*, MainParser const&, QWidget*) (mainwindow.cpp:176) ==30323== by 0x10A49B: runQtApplication(int, char**, qtwebapp::LoggerWithFile*) (main.cpp:120) ==30323== by 0x109B38: main (main.cpp:131) --- qrtplib/rtpsession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index 643858cb8..a9d42a8d2 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -209,7 +209,7 @@ int RTPSession::InternalCreate(const RTPSessionParams &sessparams) // Init the RTCP packet builder double timestampunit = sessparams.GetOwnTimestampUnit(); - uint8_t buf[1024]; + uint8_t buf[1024] = {0}; std::size_t buflen = 1024; std::string forcedcname = sessparams.GetCNAME(); From bc4d7adce79035bd1c4e7c105f252582c20adfce Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Fri, 20 Apr 2018 21:52:57 -0700 Subject: [PATCH 305/956] FileSourceGui: Prevent potential integer overflow in updateWithStreamTime UBSan reports the following error when replaying an IQ stream: ./plugins/samplesource/filesource/filesourcegui.cpp:331:29: runtime error: signed integer overflow: 2704064 * 1000 cannot be represented in type 'int' By rearranging the calculation, we can be sure that the calculation never overflows. --- plugins/samplesource/filesource/filesourcegui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index d679ae889..c74a6270f 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -328,8 +328,8 @@ void FileSourceGui::updateWithStreamTime() int t_msec = 0; if (m_sampleRate > 0){ - t_msec = ((m_samplesCount * 1000) / m_sampleRate) % 1000; t_sec = m_samplesCount / m_sampleRate; + t_msec = (m_samplesCount - (t_sec * m_sampleRate)) * 1000 / m_sampleRate; } QTime t(0, 0, 0, 0); From 141997475ca5d927c1d48ca5a737b55526c85d15 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Sat, 21 Apr 2018 09:53:04 -0700 Subject: [PATCH 306/956] BFM demod: RDS demod: Initialize RDSDemod array elements The m_parms.tot_errs array is not initialized prior to its first use in the RDSDemod::biphase function. ASAN does not pick up on this directly, but instead reports it as follows (note that ASAN fills memory with 0xBE and -1094795586 is 0xBEBEBEBE): ./plugins/channelrx/demodbfm/rdsdemod.cpp:159:95: runtime error: signed integer overflow: -1094795586 + -1094795586 cannot be represented in type 'int' The m_parms.subcarr_bb array does not appear to be read prior to initialization, but we initialize it to zero anyway for the sake of good hygiene. --- plugins/channelrx/demodbfm/rdsdemod.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/channelrx/demodbfm/rdsdemod.cpp b/plugins/channelrx/demodbfm/rdsdemod.cpp index fd5094f75..c38cccdf8 100644 --- a/plugins/channelrx/demodbfm/rdsdemod.cpp +++ b/plugins/channelrx/demodbfm/rdsdemod.cpp @@ -36,6 +36,7 @@ RDSDemod::RDSDemod() m_srate = 250000; m_parms.subcarr_phi = 0; + memset(m_parms.subcarr_bb, 0, sizeof(m_parms.subcarr_bb)); m_parms.clock_offset = 0; m_parms.clock_phi = 0; m_parms.prev_clock_phi = 0; @@ -48,6 +49,7 @@ RDSDemod::RDSDemod() m_parms.prev_acc = 0; m_parms.counter = 0; m_parms.reading_frame = 0; + memset(m_parms.tot_errs, 0, sizeof(m_parms.tot_errs)); m_parms.dbit = 0; m_prev = 0.0f; memset(m_xv, 0, 6*sizeof(Real)); From a71ba63b495e87bb6071207d19eebf27fca7a95e Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Fri, 20 Apr 2018 17:16:13 -0700 Subject: [PATCH 307/956] MainWindow: call cleanup code on aboutToQuit signal Address the two largest leaks detected by valgrind when simply opening and closing sdrangel with the 'X' button rather than the 'Exit' menu item. ==11533== 18,016 (24 direct, 17,992 indirect) bytes in 1 blocks are definitely lost in loss record 6,991 of 7,005 ==11533== at 0x4C2D54F: operator new(unsigned long) (vg_replace_malloc.c:334) ==11533== by 0x54C82ED: allocate (new_allocator.h:111) ==11533== by 0x54C82ED: allocate (alloc_traits.h:436) ==11533== by 0x54C82ED: _M_get_node (stl_list.h:383) ==11533== by 0x54C82ED: _M_create_node (stl_list.h:572) ==11533== by 0x54C82ED: _M_insert (stl_list.h:1801) ==11533== by 0x54C82ED: push_back (stl_list.h:1118) ==11533== by 0x54C82ED: FFTWEngine::configure(int, bool) (fftwengine.cpp:35) ==11533== by 0x4F4590B: SpectrumVis::handleConfigure(int, int, FFTWindow::Function) (spectrumvis.cpp:206) ==11533== by 0x4F45B94: SpectrumVis::SpectrumVis(float, GLSpectrum*) (spectrumvis.cpp:29) ==11533== by 0x4F46349: DeviceUISet::DeviceUISet(int, bool, QTimer&) (deviceuiset.cpp:39) ==11533== by 0x4EA890E: MainWindow::addSourceDevice(int) (mainwindow.cpp:240) ==11533== by 0x4EAC7F3: MainWindow::MainWindow(qtwebapp::LoggerWithFile*, MainParser const&, QWidget*) (mainwindow.cpp:171) ==11533== by 0x10A3B3: runQtApplication(int, char**, qtwebapp::LoggerWithFile*) (main.cpp:120) ==11533== by 0x109A78: main (main.cpp:130) ==11533== ==11533== 54,096 (112 direct, 53,984 indirect) bytes in 1 blocks are definitely lost in loss record 6,999 of 7,005 ==11533== at 0x4C2D54F: operator new(unsigned long) (vg_replace_malloc.c:334) ==11533== by 0x69E9162: QObject::QObject(QObject*) (in /usr/lib/libQt5Core.so.5.10.1) ==11533== by 0x54834E6: BasebandSampleSink::BasebandSampleSink() (basebandsamplesink.cpp:6) ==11533== by 0x4F45A29: SpectrumVis::SpectrumVis(float, GLSpectrum*) (spectrumvis.cpp:26) ==11533== by 0x4F46349: DeviceUISet::DeviceUISet(int, bool, QTimer&) (deviceuiset.cpp:39) ==11533== by 0x4EA890E: MainWindow::addSourceDevice(int) (mainwindow.cpp:240) ==11533== by 0x4EAC7F3: MainWindow::MainWindow(qtwebapp::LoggerWithFile*, MainParser const&, QWidget*) (mainwindow.cpp:171) ==11533== by 0x10A3B3: runQtApplication(int, char**, qtwebapp::LoggerWithFile*) (main.cpp:120) ==11533== by 0x109A78: main (main.cpp:130) --- app/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/main.cpp b/app/main.cpp index 14164fd21..f60faa79a 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -119,6 +119,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo MainWindow w(logger, parser); w.show(); + QObject::connect(&a, SIGNAL(aboutToQuit()), &w, SLOT(on_action_Exit_triggered())); return a.exec(); } From 1eaae0de308e07c60c9ffd5a016020a9a9f0b6f5 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Fri, 20 Apr 2018 19:48:55 -0700 Subject: [PATCH 308/956] BFM demod: Delete the SpectrumVis when destroying BFMDemodGUI Take care of a pair of memory leaks that occur when the BFM demod GUI is closed. ==786== 19,952 (24 direct, 19,928 indirect) bytes in 1 blocks are definitely lost in loss record 7,065 of 7,081 ==786== at 0x4C2D54F: operator new(unsigned long) (vg_replace_malloc.c:334) ==786== by 0x54C82ED: allocate (new_allocator.h:111) ==786== by 0x54C82ED: allocate (alloc_traits.h:436) ==786== by 0x54C82ED: _M_get_node (stl_list.h:383) ==786== by 0x54C82ED: _M_create_node (stl_list.h:572) ==786== by 0x54C82ED: _M_insert (stl_list.h:1801) ==786== by 0x54C82ED: push_back (stl_list.h:1118) ==786== by 0x54C82ED: FFTWEngine::configure(int, bool) (fftwengine.cpp:35) ==786== by 0x4F4590B: SpectrumVis::handleConfigure(int, int, FFTWindow::Function) (spectrumvis.cpp:206) ==786== by 0x4F459EE: SpectrumVis::handleMessage(Message const&) (spectrumvis.cpp:170) ==786== by 0x548346C: BasebandSampleSink::handleInputMessages() (basebandsamplesink.cpp:21) ==786== by 0x69E1615: QMetaObject::activate(QObject*, int, int, void**) (in /usr/lib/libQt5Core.so.5.10.1) ==786== by 0x5497D4B: MessageQueue::push(Message*, bool) (messagequeue.cpp:52) ==786== by 0x33F9D61A: BFMDemodGUI::BFMDemodGUI(PluginAPI*, DeviceUISet*, BasebandSampleSink*, QWidget*) (bfmdemodgui.cpp:352) ==786== by 0x33F9D985: BFMDemodGUI::create(PluginAPI*, DeviceUISet*, BasebandSampleSink*) (bfmdemodgui.cpp:50) ==786== by 0x33FB035D: non-virtual thunk to BFMPlugin::createRxChannelGUI(DeviceUISet*, BasebandSampleSink*) (bfmplugin.cpp:57) ==786== by 0x4F47F19: DeviceUISet::loadRxChannelSettings(Preset const*, PluginAPI*) (deviceuiset.cpp:201) ==786== by 0x4EA51EA: MainWindow::loadPresetSettings(Preset const*, int) (mainwindow.cpp:575) ==786== by 0x4EAC81B: MainWindow::MainWindow(qtwebapp::LoggerWithFile*, MainParser const&, QWidget*) (mainwindow.cpp:176) ==786== by 0x10A49B: runQtApplication(int, char**, qtwebapp::LoggerWithFile*) (main.cpp:120) ==786== by 0x109B38: main (main.cpp:131) ==786== ==786== 54,096 (112 direct, 53,984 indirect) bytes in 1 blocks are definitely lost in loss record 7,075 of 7,081 ==786== at 0x4C2D54F: operator new(unsigned long) (vg_replace_malloc.c:334) ==786== by 0x69E9162: QObject::QObject(QObject*) (in /usr/lib/libQt5Core.so.5.10.1) ==786== by 0x5497BEE: MessageQueue::MessageQueue(QObject*) (messagequeue.cpp:26) ==786== by 0x54834FF: BasebandSampleSink::BasebandSampleSink() (basebandsamplesink.cpp:6) ==786== by 0x4F45A29: SpectrumVis::SpectrumVis(float, GLSpectrum*) (spectrumvis.cpp:26) ==786== by 0x33F9D53C: BFMDemodGUI::BFMDemodGUI(PluginAPI*, DeviceUISet*, BasebandSampleSink*, QWidget*) (bfmdemodgui.cpp:342) ==786== by 0x33F9D985: BFMDemodGUI::create(PluginAPI*, DeviceUISet*, BasebandSampleSink*) (bfmdemodgui.cpp:50) ==786== by 0x33FB035D: non-virtual thunk to BFMPlugin::createRxChannelGUI(DeviceUISet*, BasebandSampleSink*) (bfmplugin.cpp:57) ==786== by 0x4F47F19: DeviceUISet::loadRxChannelSettings(Preset const*, PluginAPI*) (deviceuiset.cpp:201) ==786== by 0x4EA51EA: MainWindow::loadPresetSettings(Preset const*, int) (mainwindow.cpp:575) ==786== by 0x4EAC81B: MainWindow::MainWindow(qtwebapp::LoggerWithFile*, MainParser const&, QWidget*) (mainwindow.cpp:176) ==786== by 0x10A49B: runQtApplication(int, char**, qtwebapp::LoggerWithFile*) (main.cpp:120) ==786== by 0x109B38: main (main.cpp:131) --- plugins/channelrx/demodbfm/bfmdemodgui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index 6707b01dd..92b73f77c 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -388,6 +388,7 @@ BFMDemodGUI::~BFMDemodGUI() { m_deviceUISet->removeRxChannelInstance(this); delete m_bfmDemod; // TODO: check this: when the GUI closes it has to delete the demodulator + delete m_spectrumVis; delete ui; } From 7e6267f41cb2fb395fc438dd1f96f33b5847d07b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 22 Apr 2018 09:37:34 +0200 Subject: [PATCH 309/956] SSB demod: use delay line to squeeze squelch tail --- plugins/channelrx/demodssb/ssbdemod.cpp | 21 +++++++++++++-------- plugins/channelrx/demodssb/ssbdemod.h | 2 ++ plugins/channelrx/demodssb/ssbplugin.cpp | 2 +- sdrbase/dsp/agc.cpp | 12 ++++++++++++ sdrbase/dsp/agc.h | 3 +++ 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 80ca4d644..a3320cffd 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -51,6 +51,7 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : m_agcNbSamples(12000), m_agcPowerThreshold(1e-2), m_agcThresholdGate(0), + m_squelchDelayLine(2*48000), m_audioActive(false), m_sampleSink(0), m_audioFifo(24000), @@ -207,8 +208,10 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_sum.imag(0.0); } - double agcVal = m_agcActive ? m_agc.feedAndGetValue(sideband[i]) : 10.0; // 10.0 for 3276.8, 1.0 for 327.68 - m_audioActive = agcVal != 0.0; + float agcVal = m_agcActive ? m_agc.feedAndGetValue(sideband[i]) : 10.0; // 10.0 for 3276.8, 1.0 for 327.68 + fftfilt::cmplx& delayedSample = m_squelchDelayLine.readBack(m_agc.getStepDownDelay()); + m_audioActive = delayedSample.real() != 0.0; + m_squelchDelayLine.write(sideband[i]*agcVal); if (m_audioMute) { @@ -217,23 +220,25 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { + fftfilt::cmplx z = delayedSample * m_agc.getStepDownValue(); + if (m_audioBinaual) { if (m_audioFlipChannels) { - m_audioBuffer[m_audioBufferFill].r = (qint16)(sideband[i].imag() * m_volume * agcVal); - m_audioBuffer[m_audioBufferFill].l = (qint16)(sideband[i].real() * m_volume * agcVal); + m_audioBuffer[m_audioBufferFill].r = (qint16)(z.imag() * m_volume); + m_audioBuffer[m_audioBufferFill].l = (qint16)(z.real() * m_volume); } else { - m_audioBuffer[m_audioBufferFill].r = (qint16)(sideband[i].real() * m_volume * agcVal); - m_audioBuffer[m_audioBufferFill].l = (qint16)(sideband[i].imag() * m_volume * agcVal); + m_audioBuffer[m_audioBufferFill].r = (qint16)(z.real() * m_volume); + m_audioBuffer[m_audioBufferFill].l = (qint16)(z.imag() * m_volume); } } else { - Real demod = (sideband[i].real() + sideband[i].imag()) * 0.7; - qint16 sample = (qint16)(demod * m_volume * agcVal); + Real demod = (z.real() + z.imag()) * 0.7; + qint16 sample = (qint16)(demod * m_volume); m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; } diff --git a/plugins/channelrx/demodssb/ssbdemod.h b/plugins/channelrx/demodssb/ssbdemod.h index 038d27bd1..27e721e18 100644 --- a/plugins/channelrx/demodssb/ssbdemod.h +++ b/plugins/channelrx/demodssb/ssbdemod.h @@ -29,6 +29,7 @@ #include "dsp/agc.h" #include "audio/audiofifo.h" #include "util/message.h" +#include "util/doublebufferfifo.h" #include "ssbdemodsettings.h" @@ -259,6 +260,7 @@ private: int m_agcNbSamples; //!< number of audio (48 kHz) samples for AGC averaging double m_agcPowerThreshold; //!< AGC power threshold (linear) int m_agcThresholdGate; //!< Gate length in number of samples befor threshold triggers + DoubleBufferFIFO m_squelchDelayLine; bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold) NCOF m_nco; diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp index 07bf4b1d4..af380205a 100644 --- a/plugins/channelrx/demodssb/ssbplugin.cpp +++ b/plugins/channelrx/demodssb/ssbplugin.cpp @@ -8,7 +8,7 @@ const PluginDescriptor SSBPlugin::m_pluginDescriptor = { QString("SSB Demodulator"), - QString("3.14.0"), + QString("3.14.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/dsp/agc.cpp b/sdrbase/dsp/agc.cpp index 94b77cd0b..82f2f59b7 100644 --- a/sdrbase/dsp/agc.cpp +++ b/sdrbase/dsp/agc.cpp @@ -176,3 +176,15 @@ double MagAGC::feedAndGetValue(const Complex& ci) return m_u0; } } + +float MagAGC::getStepDownValue() const +{ + if (m_count < m_stepDownDelay) + { + return 1.0f; + } + else + { + return StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta); + } +} diff --git a/sdrbase/dsp/agc.h b/sdrbase/dsp/agc.h index 3c0b464bd..75f3959c7 100644 --- a/sdrbase/dsp/agc.h +++ b/sdrbase/dsp/agc.h @@ -50,6 +50,9 @@ public: void setStepDownDelay(int stepDownDelay) { m_stepDownDelay = stepDownDelay; } void setClamping(bool clamping) { m_clamping = clamping; } void setClampMax(double clampMax) { m_clampMax = clampMax; } + int getStepDownDelay() const { return m_stepDownDelay; } + float getStepDownValue() const; + private: bool m_squared; //!< use squared magnitude (power) to compute AGC value double m_magsq; //!< current squared magnitude (power) From 06cd90e35498b41af5840a025434039c4b00b1a5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 22 Apr 2018 10:23:11 +0200 Subject: [PATCH 310/956] AM demod: use buffered delay squelch --- plugins/channelrx/demodam/amdemod.cpp | 2 ++ plugins/channelrx/demodam/amdemod.h | 32 +++++++++++++++++---- plugins/channelrx/demodam/amdemodplugin.cpp | 2 +- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index dd18cf468..14729bf50 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -49,6 +49,7 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_inputFrequencyOffset(0), m_running(false), m_squelchOpen(false), + m_squelchDelayLine(12000), m_magsqSum(0.0f), m_magsqPeak(0.0f), m_magsqCount(0), @@ -231,6 +232,7 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); m_audioFifo.setSize(sampleRate); + m_squelchDelayLine.resize(sampleRate/4); m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index cc8fcff88..2e730d732 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -29,6 +29,8 @@ #include "dsp/bandpass.h" #include "audio/audiofifo.h" #include "util/message.h" +#include "util/doublebufferfifo.h" + #include "amdemodsettings.h" class DeviceSourceAPI; @@ -155,6 +157,7 @@ private: Real m_squelchLevel; uint32_t m_squelchCount; bool m_squelchOpen; + DoubleBufferFIFO m_squelchDelayLine; double m_magsq; double m_magsqSum; double m_magsqPeak; @@ -194,26 +197,43 @@ private: m_magsqCount++; +// if (m_magsq >= m_squelchLevel) +// { +// if (m_squelchCount <= m_audioSampleRate / 10) +// { +// m_squelchCount++; +// } +// } +// else +// { +// if (m_squelchCount > 1) +// { +// m_squelchCount -= 2; +// } +// } + if (m_magsq >= m_squelchLevel) { - if (m_squelchCount <= m_audioSampleRate / 10) - { + if (m_squelchCount < m_audioSampleRate / 10) { m_squelchCount++; } + + m_squelchDelayLine.write(magsq); } else { - if (m_squelchCount > 1) - { - m_squelchCount -= 2; + if (m_squelchCount > 0) { + m_squelchCount--; } + + m_squelchDelayLine.write(0); } qint16 sample; if ((m_squelchCount >= m_audioSampleRate / 20) && !m_settings.m_audioMute) { - Real demod = sqrt(magsq); + Real demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate / 20)); m_volumeAGC.feed(demod); demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index 76149cd3c..4942287c9 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("3.14.2"), + QString("3.14.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 486468afe027e7efa2e4691ddc6cf7dec092580d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 22 Apr 2018 18:49:58 +0200 Subject: [PATCH 311/956] Updated Debian changelog and DSD demod doc --- debian/changelog | 9 +++++++++ plugins/channelrx/demoddsd/readme.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 517edb9c3..057c4dce7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +sdrangel (3.14.4-1) unstable; urgency=medium + + * AM demod: squelch buffer to open at start of valid squelch + * NFM demod: same as AM with squelch noise tail cut + * SSB demod: squelch buffer to cut squelch noise tail + * DSD demod: squelch buffer to open at start of valid squelch not loosing any samples + + -- Edouard Griffiths, F4EXB Sun, 22 Apr 2018 17:14:18 +0200 + sdrangel (3.14.3-1) unstable; urgency=medium * LimeSDR: compiled with LimeSuite release 18.04.1 diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index 86d7a1fa9..7beae3a15 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -110,7 +110,7 @@ The level corresponds to the channel power above which the squelch gate opens.

A.9: Squelch time gate

-Number of milliseconds following squelch gate opening after which the signal is actually fed to the decoder. 0 means no delay i.e. immediate feed. +Number of milliseconds following squelch gate opening after which the signal is declared open. There is a delay line for the samples so samples applied to the decoder actually start at the beginning of the gate period not loosing any samples. 0 means squelch is declared open with no delay.

A.10: High-pass filter for audio

From 114e70b595fcc6f7105bb5745b8069a03a826541 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 22 Apr 2018 22:58:11 +0200 Subject: [PATCH 312/956] Revert "MainWindow: call cleanup code on aboutToQuit signal" This reverts commit a71ba63b495e87bb6071207d19eebf27fca7a95e. --- app/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/main.cpp b/app/main.cpp index d6ac544fe..a843b4f10 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -119,7 +119,6 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo MainWindow w(logger, parser); w.show(); - QObject::connect(&a, SIGNAL(aboutToQuit()), &w, SLOT(on_action_Exit_triggered())); return a.exec(); } From 2efa7ab59409ca5283f66c07ee65ac1debe3bd4e Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 23 Apr 2018 01:04:47 +0200 Subject: [PATCH 313/956] DSD demod: allow use of audio rates that are integer multiples of 8k other than 48k (x2,3,4,5) --- app/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 +++ plugins/channelrx/demoddsd/dsddecoder.cpp | 5 +++ plugins/channelrx/demoddsd/dsddecoder.h | 1 + plugins/channelrx/demoddsd/dsddemod.cpp | 15 +++---- plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- sdrbase/dsp/dspengine.cpp | 6 +-- sdrbase/dsp/dspengine.h | 2 +- sdrbase/dsp/dvserialengine.cpp | 6 +-- sdrbase/dsp/dvserialengine.h | 2 +- sdrbase/dsp/dvserialworker.cpp | 39 +++++++++++++++++-- sdrbase/dsp/dvserialworker.h | 15 +++---- 13 files changed, 74 insertions(+), 29 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index a843b4f10..e405a11df 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.14.4"); + QCoreApplication::setApplicationVersion("3.14.5"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 3c82592f2..d0fbbdfac 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.14.4"); + QCoreApplication::setApplicationVersion("3.14.5"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 057c4dce7..d8a5a17ee 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (3.14.5-1) unstable; urgency=medium + + * DSD demod: allow audio rates integer multiples of 8k other than 48k + + -- Edouard Griffiths, F4EXB Sun, 29 Apr 2018 17:14:18 +0200 + sdrangel (3.14.4-1) unstable; urgency=medium * AM demod: squelch buffer to open at start of valid squelch diff --git a/plugins/channelrx/demoddsd/dsddecoder.cpp b/plugins/channelrx/demoddsd/dsddecoder.cpp index bc39c4b05..54c8d75e9 100644 --- a/plugins/channelrx/demoddsd/dsddecoder.cpp +++ b/plugins/channelrx/demoddsd/dsddecoder.cpp @@ -41,6 +41,11 @@ void DSDDecoder::set48k(bool to48k) m_decoder.setUpsampling(to48k ? 6 : 0); } +void DSDDecoder::setUpsampling(int upsampling) +{ + m_decoder.setUpsampling(upsampling); +} + void DSDDecoder::setBaudRate(int baudRate) { if (baudRate == 2400) diff --git a/plugins/channelrx/demoddsd/dsddecoder.h b/plugins/channelrx/demoddsd/dsddecoder.h index 0b283fa7f..b135a653c 100644 --- a/plugins/channelrx/demoddsd/dsddecoder.h +++ b/plugins/channelrx/demoddsd/dsddecoder.h @@ -72,6 +72,7 @@ public: void setSymbolPLLLock(bool pllLock) { m_decoder.setSymbolPLLLock(pllLock); } void useHPMbelib(bool useHP) { m_decoder.useHPMbelib(useHP); } void set48k(bool to48k); + void setUpsampling(int upsampling); private: DSDcc::DSDDecoder m_decoder; diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 481716fb2..d16142854 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -238,7 +238,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_settings.m_volume * 10.0, m_settings.m_tdmaStereo ? 1 : 3, // left or both channels m_settings.m_highPassFilter, - m_audioSampleRate != 8000, // upsample to 48k unless native 8k + m_audioSampleRate/8000, // upsample from native 8k &m_audioFifo1); } @@ -255,7 +255,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_settings.m_volume * 10.0, m_settings.m_tdmaStereo ? 2 : 3, // right or both channels m_settings.m_highPassFilter, - m_audioSampleRate != 8000, // upsample to 48k unless native 8k + m_audioSampleRate/8000, // upsample from native 8k &m_audioFifo2); } @@ -412,14 +412,15 @@ bool DSDDemod::handleMessage(const Message& cmd) void DSDDemod::applyAudioSampleRate(int sampleRate) { - qDebug("DSDDemod::applyAudioSampleRate: %d", sampleRate); + int upsampling = sampleRate / 8000; - if ((sampleRate != 48000) && (sampleRate != 8000)) { - qWarning("DSDDemod::applyAudioSampleRate: audio does not work properly with sample rates other than 48 or 8 kS/s"); + qDebug("DSDDemod::applyAudioSampleRate: audio rate: %d upsample by %d", sampleRate, upsampling); + + if (sampleRate % 8000 != 0) { + qDebug("DSDDemod::applyAudioSampleRate: audio will sound best with sample rates that are integer multiples of 8 kS/s"); } - m_dsdDecoder.set48k(sampleRate != 8000); - + m_dsdDecoder.setUpsampling(upsampling); m_audioSampleRate = sampleRate; } diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index c4b190b5e..8403767d0 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("3.14.4"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/dsp/dspengine.cpp b/sdrbase/dsp/dspengine.cpp index 6fc5505b2..afa9c4453 100644 --- a/sdrbase/dsp/dspengine.cpp +++ b/sdrbase/dsp/dspengine.cpp @@ -164,10 +164,10 @@ void DSPEngine::pushMbeFrame( int mbeVolumeIndex, unsigned char channels, bool useHP, - bool upSample48k, + int upsampling, AudioFifo *audioFifo) { - m_dvSerialEngine.pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upSample48k, audioFifo); + m_dvSerialEngine.pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upsampling, audioFifo); } #else void DSPEngine::pushMbeFrame( @@ -176,7 +176,7 @@ void DSPEngine::pushMbeFrame( int mbeVolumeIndex __attribute((unused)), unsigned char channels __attribute((unused)), bool useHP __attribute((unused)), - bool upSample48k __attribute((unused)), + int upsampling __attribute((unused)), AudioFifo *audioFifo __attribute((unused))) {} #endif diff --git a/sdrbase/dsp/dspengine.h b/sdrbase/dsp/dspengine.h index f99cd1346..f7438cf28 100644 --- a/sdrbase/dsp/dspengine.h +++ b/sdrbase/dsp/dspengine.h @@ -68,7 +68,7 @@ public: int mbeVolumeIndex, unsigned char channels, bool useHP, - bool upSample48k, + int upsampling, AudioFifo *audioFifo); const QTimer& getMasterTimer() const { return m_masterTimer; } diff --git a/sdrbase/dsp/dvserialengine.cpp b/sdrbase/dsp/dvserialengine.cpp index d19b09b07..ddb4370b9 100644 --- a/sdrbase/dsp/dvserialengine.cpp +++ b/sdrbase/dsp/dvserialengine.cpp @@ -253,7 +253,7 @@ void DVSerialEngine::pushMbeFrame( int mbeVolumeIndex, unsigned char channels, bool useLP, - bool upSample48k, + int upsampling, AudioFifo *audioFifo) { std::vector::iterator it = m_controllers.begin(); @@ -265,7 +265,7 @@ void DVSerialEngine::pushMbeFrame( { if (it->worker->hasFifo(audioFifo)) { - it->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upSample48k, audioFifo); + it->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo); done = true; } else if (it->worker->isAvailable()) @@ -283,7 +283,7 @@ void DVSerialEngine::pushMbeFrame( int wNum = itAvail - m_controllers.begin(); qDebug("DVSerialEngine::pushMbeFrame: push %p on empty queue %d", audioFifo, wNum); - itAvail->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upSample48k, audioFifo); + itAvail->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo); } else { diff --git a/sdrbase/dsp/dvserialengine.h b/sdrbase/dsp/dvserialengine.h index 24b50bea7..dfc3d1520 100644 --- a/sdrbase/dsp/dvserialengine.h +++ b/sdrbase/dsp/dvserialengine.h @@ -49,7 +49,7 @@ public: int mbeVolumeIndex, unsigned char channels, bool useHP, - bool upSample48k, + int upsampling, AudioFifo *audioFifo); private: diff --git a/sdrbase/dsp/dvserialworker.cpp b/sdrbase/dsp/dvserialworker.cpp index 9d5d1bc2c..0423f48b6 100644 --- a/sdrbase/dsp/dvserialworker.cpp +++ b/sdrbase/dsp/dvserialworker.cpp @@ -86,8 +86,11 @@ void DVSerialWorker::handleInputMessages() if (m_dvController.decode(m_dvAudioSamples, decodeMsg->getMbeFrame(), decodeMsg->getMbeRate(), dBVolume)) { - if (decodeMsg->getUpsample48k()) { - upsample6(m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); + int upsampling = decodeMsg->getUpsampling(); + upsampling = upsampling > 6 ? 6 : upsampling < 1 ? 1 : upsampling; + + if (upsampling > 1) { + upsample(upsampling, m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); } else { noUpsample(m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); } @@ -121,11 +124,11 @@ void DVSerialWorker::pushMbeFrame(const unsigned char *mbeFrame, int mbeVolumeIndex, unsigned char channels, bool useHP, - bool upSample48k, + int upsampling, AudioFifo *audioFifo) { m_audioFifo = audioFifo; - m_inputMessageQueue.push(MsgMbeDecode::create(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upSample48k, audioFifo)); + m_inputMessageQueue.push(MsgMbeDecode::create(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upsampling, audioFifo)); } bool DVSerialWorker::isAvailable() @@ -170,6 +173,34 @@ void DVSerialWorker::upsample6(short *in, int nbSamplesIn, unsigned char channel } } +void DVSerialWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels) +{ + for (int i = 0; i < nbSamplesIn; i++) + { + int cur = (int) in[i]; + int prev = (int) m_upsamplerLastValue; + qint16 upsample; + + for (int j = 1; j <= upsampling; j++) + { + upsample = m_upsampleFilter.run((qint16) ((cur*j + prev*(upsampling-j)) / upsampling)); + m_audioBuffer[m_audioBufferFill].l = channels & 1 ? upsample : 0; + m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? upsample : 0; + + if (m_audioBufferFill < m_audioBuffer.size() - 1) + { + ++m_audioBufferFill; + } + else + { + qDebug("DVSerialWorker::upsample6: audio buffer is full check its size"); + } + } + + m_upsamplerLastValue = in[i]; + } +} + void DVSerialWorker::noUpsample(short *in, int nbSamplesIn, unsigned char channels) { for (int i = 0; i < nbSamplesIn; i++) diff --git a/sdrbase/dsp/dvserialworker.h b/sdrbase/dsp/dvserialworker.h index c38b7dfeb..87f2d44ef 100644 --- a/sdrbase/dsp/dvserialworker.h +++ b/sdrbase/dsp/dvserialworker.h @@ -56,7 +56,7 @@ public: int getVolumeIndex() const { return m_volumeIndex; } unsigned char getChannels() const { return m_channels % 4; } bool getUseHP() const { return m_useHP; } - bool getUpsample48k() const { return m_upSample48k; } + int getUpsampling() const { return m_upsampling; } AudioFifo *getAudioFifo() { return m_audioFifo; } static MsgMbeDecode* create( @@ -65,10 +65,10 @@ public: int volumeIndex, unsigned char channels, bool useHP, - bool upSample48k, + int upsampling, AudioFifo *audioFifo) { - return new MsgMbeDecode(mbeFrame, (SerialDV::DVRate) mbeRateIndex, volumeIndex, channels, useHP, upSample48k, audioFifo); + return new MsgMbeDecode(mbeFrame, (SerialDV::DVRate) mbeRateIndex, volumeIndex, channels, useHP, upsampling, audioFifo); } private: @@ -77,7 +77,7 @@ public: int m_volumeIndex; unsigned char m_channels; bool m_useHP; - bool m_upSample48k; + int m_upsampling; AudioFifo *m_audioFifo; MsgMbeDecode(const unsigned char *mbeFrame, @@ -85,14 +85,14 @@ public: int volumeIndex, unsigned char channels, bool useHP, - bool upSample48k, + int upsampling, AudioFifo *audioFifo) : Message(), m_mbeRate(mbeRate), m_volumeIndex(volumeIndex), m_channels(channels), m_useHP(useHP), - m_upSample48k(upSample48k), + m_upsampling(upsampling), m_audioFifo(audioFifo) { memcpy((void *) m_mbeFrame, (const void *) mbeFrame, SerialDV::DVController::getNbMbeBytes(m_mbeRate)); @@ -107,7 +107,7 @@ public: int mbeVolumeIndex, unsigned char channels, bool useHP, - bool upSample48k, + int upsampling, AudioFifo *audioFifo); bool open(const std::string& serialDevice); @@ -136,6 +136,7 @@ public slots: private: //void upsample6(short *in, short *out, int nbSamplesIn); void upsample6(short *in, int nbSamplesIn, unsigned char channels); + void upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels); void noUpsample(short *in, int nbSamplesIn, unsigned char channels); SerialDV::DVController m_dvController; From 375db9ae211527c58702c27dfdf18460be477bf0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 23 Apr 2018 16:43:18 +0200 Subject: [PATCH 314/956] Added a benchmark framework --- CMakeLists.txt | 25 +++++++++ appbench/main.cpp | 110 +++++++++++++++++++++++++++++++++++++ sdrbench/CMakeLists.txt | 42 +++++++++++++++ sdrbench/mainbench.cpp | 46 ++++++++++++++++ sdrbench/mainbench.h | 49 +++++++++++++++++ sdrbench/parserbench.cpp | 113 +++++++++++++++++++++++++++++++++++++++ sdrbench/parserbench.h | 52 ++++++++++++++++++ 7 files changed, 437 insertions(+) create mode 100644 appbench/main.cpp create mode 100644 sdrbench/CMakeLists.txt create mode 100644 sdrbench/mainbench.cpp create mode 100644 sdrbench/mainbench.h create mode 100644 sdrbench/parserbench.cpp create mode 100644 sdrbench/parserbench.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 05203f365..b39c72ef7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,6 +214,7 @@ endif() add_subdirectory(sdrbase) add_subdirectory(sdrgui) add_subdirectory(sdrsrv) +add_subdirectory(sdrbench) add_subdirectory(httpserver) add_subdirectory(logging) add_subdirectory(qrtplib) @@ -293,6 +294,29 @@ target_link_libraries(sdrangelsrv qt5_use_modules(sdrangelsrv Multimedia) +############################################################################## +# main benchmark application + +set(sdrangelbench_SOURCES + appbench/main.cpp +) + +add_executable(sdrangelbench + ${sdrangelbench_SOURCES} +) + +target_include_directories(sdrangelbench + PUBLIC ${CMAKE_SOURCE_DIR}/sdrbench +) + +target_link_libraries(sdrangelbench + sdrbench + logging + ${QT_LIBRARIES} +) + +qt5_use_modules(sdrangelbench Multimedia) + ############################################################################## if (BUILD_DEBIAN) @@ -325,6 +349,7 @@ endif(LIBUSB_FOUND AND UNIX) #install targets install(TARGETS sdrangel DESTINATION bin) install(TARGETS sdrangelsrv DESTINATION bin) +install(TARGETS sdrangelbench DESTINATION bin) #install(TARGETS sdrbase DESTINATION lib) #install files and directories diff --git a/appbench/main.cpp b/appbench/main.cpp new file mode 100644 index 000000000..74adea9bc --- /dev/null +++ b/appbench/main.cpp @@ -0,0 +1,110 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// Swagger server adapter interface // +// // +// 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 +#include + +#include "loggerwithfile.h" +#include "mainbench.h" +#include "dsp/dsptypes.h" + +void handler(int sig) { + fprintf(stderr, "quit the application by signal(%d).\n", sig); + QCoreApplication::quit(); +} + +void catchUnixSignals(const std::vector& quitSignals) { + sigset_t blocking_mask; + sigemptyset(&blocking_mask); + + for (std::vector::const_iterator it = quitSignals.begin(); it != quitSignals.end(); ++it) { + sigaddset(&blocking_mask, *it); + } + + struct sigaction sa; + sa.sa_handler = handler; + sa.sa_mask = blocking_mask; + sa.sa_flags = 0; + + for (std::vector::const_iterator it = quitSignals.begin(); it != quitSignals.end(); ++it) { + sigaction(*it, &sa, 0); + } +} + +static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *logger) +{ + QCoreApplication a(argc, argv); + + QCoreApplication::setOrganizationName("f4exb"); + QCoreApplication::setApplicationName("SDRangelBench"); + QCoreApplication::setApplicationVersion("3.14.5"); + + int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; + std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); + catchUnixSignals(vsig); + + ParserBench parser; + parser.parse(a); + +#if QT_VERSION >= 0x050400 + qInfo("%s %s Qt %s %db %s %s DSP Rx:%db Tx:%db PID %lld", + qPrintable(QCoreApplication::applicationName()), + qPrintable(QCoreApplication::applicationVersion()), + qPrintable(QString(QT_VERSION_STR)), + QT_POINTER_SIZE*8, + qPrintable(QSysInfo::currentCpuArchitecture()), + qPrintable(QSysInfo::prettyProductName()), + SDR_RX_SAMP_SZ, + SDR_TX_SAMP_SZ, + QCoreApplication::applicationPid()); +#else + qInfo("%s %s Qt %s %db DSP Rx:%db Tx:%db PID %lld", + qPrintable(QCoreApplication::applicationName()), + qPrintable((QCoreApplication::>applicationVersion()), + qPrintable(QString(QT_VERSION_STR)), + QT_POINTER_SIZE*8, + SDR_RX_SAMP_SZ, + SDR_TX_SAMP_SZ, + QCoreApplication::applicationPid()); +#endif + + MainBench m(logger, parser, &a); + + // This will cause the application to exit when the main core is finished + QObject::connect(&m, SIGNAL(finished()), &a, SLOT(quit())); + // This will run the task from the application event loop + QTimer::singleShot(0, &m, SLOT(run())); + + return a.exec(); +} + +int main(int argc, char* argv[]) +{ + qtwebapp::LoggerWithFile *logger = new qtwebapp::LoggerWithFile(qApp); + logger->installMsgHandler(); + int res = runQtApplication(argc, argv, logger); + qWarning("SDRangel quit."); + return res; +} + + diff --git a/sdrbench/CMakeLists.txt b/sdrbench/CMakeLists.txt new file mode 100644 index 000000000..94975cdd3 --- /dev/null +++ b/sdrbench/CMakeLists.txt @@ -0,0 +1,42 @@ +project (sdrbench) + +set(sdrbench_SOURCES + mainbench.cpp + parserbench.cpp +) + +set(sdrbench_HEADERS + mainbench.h + parserbench.h +) + +set(sdrbench_SOURCES + ${sdrbench_SOURCES} + ${sdrbench_HEADERS} +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_SHARED) + +add_library(sdrbench SHARED + ${sdrbench_SOURCES} + ${sdrbench_HEADERS_MOC} +) + +include_directories( + . + ${CMAKE_SOURCE_DIR}/logging + ${CMAKE_CURRENT_BINARY_DIR} +) + +target_link_libraries(sdrbench + ${QT_LIBRARIES} + logging +) + +target_compile_features(sdrbench PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 + +qt5_use_modules(sdrbench Core Gui) + +install(TARGETS sdrbench DESTINATION lib) + diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp new file mode 100644 index 000000000..4da6db668 --- /dev/null +++ b/sdrbench/mainbench.cpp @@ -0,0 +1,46 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// Swagger server adapter interface // +// // +// 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 "mainbench.h" + +MainBench *MainBench::m_instance = 0; + +MainBench::MainBench(qtwebapp::LoggerWithFile *logger, const ParserBench& parser, QObject *parent) : + QObject(parent), + m_logger(logger), + m_parser(parser) +{ + qDebug() << "MainBench::MainBench: start"; + m_instance = this; + qDebug() << "MainBench::MainBench: end"; +} + +void MainBench::run() +{ + qDebug() << "MainBench::run: work in progress"; + qDebug() << "MainBench::run: parameters:" + << " test: " << m_parser.getTest() + << " nsamples: " << m_parser.getNbSamples() + << " repet: " << m_parser.getRepetition() + << " log2f: " << m_parser.getLog2Factor(); + emit finished(); +} + +MainBench::~MainBench() +{} \ No newline at end of file diff --git a/sdrbench/mainbench.h b/sdrbench/mainbench.h new file mode 100644 index 000000000..a94ec05b9 --- /dev/null +++ b/sdrbench/mainbench.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// Swagger server adapter interface // +// // +// 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 SDRBENCH_MAINBENCH_H_ +#define SDRBENCH_MAINBENCH_H_ + +#include + +#include "parserbench.h" + +namespace qtwebapp { + class LoggerWithFile; +} + +class MainBench: public QObject { + Q_OBJECT + +public: + explicit MainBench(qtwebapp::LoggerWithFile *logger, const ParserBench& parser, QObject *parent = 0); + ~MainBench(); + +public slots: + void run(); + +signals: + void finished(); + +private: + static MainBench *m_instance; + qtwebapp::LoggerWithFile *m_logger; + const ParserBench& m_parser; +}; + +#endif // SDRBENCH_MAINBENCH_H_ diff --git a/sdrbench/parserbench.cpp b/sdrbench/parserbench.cpp new file mode 100644 index 000000000..8f914a72e --- /dev/null +++ b/sdrbench/parserbench.cpp @@ -0,0 +1,113 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 "parserbench.h" + +ParserBench::ParserBench() : + m_testOption(QStringList() << "t" << "test", + "Test type.", + "test", + "decimate"), + m_nbSamplesOption(QStringList() << "n" << "nb-samples", + "Number of sample to deal with.", + "samples", + "1048576"), + m_repetitionOption(QStringList() << "r" << "repeat", + "Number of repetitions.", + "repetition", + "1"), + m_log2FactorOption(QStringList() << "l" << "log2-factor", + "Log2 factor for rate conversion.", + "log2", + "2") +{ + m_test = "decimate"; + m_nbSamples = 1048576; + m_repetition = 1; + m_log2Factor = 4; + + m_parser.setApplicationDescription("Software Defined Radio application benchmarks"); + m_parser.addHelpOption(); + m_parser.addVersionOption(); + + m_parser.addOption(m_testOption); + m_parser.addOption(m_nbSamplesOption); + m_parser.addOption(m_repetitionOption); + m_parser.addOption(m_log2FactorOption); +} + +ParserBench::~ParserBench() +{ } + +void ParserBench::parse(const QCoreApplication& app) +{ + m_parser.process(app); + + int pos; + bool ok; + + // test switch + + QString test = m_parser.value(m_testOption); + + QString testStr = "(decimate)"; + QRegExp ipRegex ("^" + testStr + "$"); + QRegExpValidator ipValidator(ipRegex); + + if (ipValidator.validate(test, pos) == QValidator::Acceptable) { + m_test = test; + } else { + qWarning() << "ParserBench::parse: test type invalid. Defaulting to " << m_test; + } + + // number of samples + + QString nbSamplesStr = m_parser.value(m_nbSamplesOption); + int nbSamples = nbSamplesStr.toInt(&ok); + + if (ok && (nbSamples > 1024) && (nbSamples < 1073741824)) { + m_nbSamples = nbSamples; + } else { + qWarning() << "ParserBench::parse: number of samples invalid. Defaulting to " << m_nbSamples; + } + + // repetition + + QString repetitionStr = m_parser.value(m_repetitionOption); + int repetition = repetitionStr.toInt(&ok); + + if (ok && (repetition >= 0)) { + m_repetition = repetition; + } else { + qWarning() << "ParserBench::parse: repetition invalid. Defaulting to " << m_repetition; + } + + // log2 factor + + QString log2FactorStr = m_parser.value(m_log2FactorOption); + int log2Factor = log2FactorStr.toInt(&ok); + + if (ok && (log2Factor >= 0) && (log2Factor <= 6)) { + m_log2Factor = log2Factor; + } else { + qWarning() << "ParserBench::parse: repetilog2 factortion invalid. Defaulting to " << m_log2Factor; + } +} diff --git a/sdrbench/parserbench.h b/sdrbench/parserbench.h new file mode 100644 index 000000000..a7b7a2660 --- /dev/null +++ b/sdrbench/parserbench.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 SDRBENCH_PARSERBENCH_H_ +#define SDRBENCH_PARSERBENCH_H_ + +#include +#include + +class ParserBench +{ +public: + ParserBench(); + ~ParserBench(); + + void parse(const QCoreApplication& app); + + const QString& getTest() const { return m_test; } + uint32_t getNbSamples() const { return m_nbSamples; } + uint32_t getRepetition() const { return m_repetition; } + uint32_t getLog2Factor() const { return m_log2Factor; } + +private: + QString m_test; + uint32_t m_nbSamples; + uint32_t m_repetition; + uint32_t m_log2Factor; + + QCommandLineParser m_parser; + QCommandLineOption m_testOption; + QCommandLineOption m_nbSamplesOption; + QCommandLineOption m_repetitionOption; + QCommandLineOption m_log2FactorOption; +}; + + + +#endif /* SDRBENCH_PARSERBENCH_H_ */ From 698f5bd172c442ddd6d1732fdc463d3c32f2f701 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 23 Apr 2018 18:24:45 +0200 Subject: [PATCH 315/956] Benchmarking: added actual decimator test --- sdrbench/CMakeLists.txt | 3 ++ sdrbench/mainbench.cpp | 66 +++++++++++++++++++++++++++++++++++++++-- sdrbench/mainbench.h | 12 ++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/sdrbench/CMakeLists.txt b/sdrbench/CMakeLists.txt index 94975cdd3..c51d83509 100644 --- a/sdrbench/CMakeLists.txt +++ b/sdrbench/CMakeLists.txt @@ -25,12 +25,15 @@ add_library(sdrbench SHARED include_directories( . + ${CMAKE_SOURCE_DIR}/exports + ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_SOURCE_DIR}/logging ${CMAKE_CURRENT_BINARY_DIR} ) target_link_libraries(sdrbench ${QT_LIBRARIES} + sdrbase logging ) diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index 4da6db668..a01938447 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -17,6 +17,8 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include + #include "mainbench.h" MainBench *MainBench::m_instance = 0; @@ -31,16 +33,74 @@ MainBench::MainBench(qtwebapp::LoggerWithFile *logger, const ParserBench& parser qDebug() << "MainBench::MainBench: end"; } +MainBench::~MainBench() +{} + void MainBench::run() { - qDebug() << "MainBench::run: work in progress"; + QElapsedTimer timer; + qint64 nsecs; + qDebug() << "MainBench::run: parameters:" << " test: " << m_parser.getTest() << " nsamples: " << m_parser.getNbSamples() << " repet: " << m_parser.getRepetition() << " log2f: " << m_parser.getLog2Factor(); + + qDebug() << "MainBench::run: create test data"; + + m_buf = new qint16[m_parser.getNbSamples()*2]; + m_convertBuffer.resize(m_parser.getNbSamples()/(1< +#include "dsp/decimators.h" #include "parserbench.h" namespace qtwebapp { @@ -41,9 +42,20 @@ signals: void finished(); private: + void decimate(const qint16 *buf, int len); + static MainBench *m_instance; qtwebapp::LoggerWithFile *m_logger; const ParserBench& m_parser; + +#ifdef SDR_RX_SAMPLE_24BIT + Decimators m_decimators; +#else + Decimators m_decimators; +#endif + + qint16 *m_buf; + SampleVector m_convertBuffer; }; #endif // SDRBENCH_MAINBENCH_H_ From b03e9c59cb9dc159555b2977ed6ea1cf7ba0ef50 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 25 Apr 2018 01:44:54 +0200 Subject: [PATCH 316/956] Benchmarking: implemented decimator float to int test --- .../samplesource/airspyhf/airspyhfthread.h | 4 +- sdrbase/CMakeLists.txt | 4 +- sdrbase/dsp/decimators.h | 1 + .../dsp/{decimatorsf.cpp => decimatorsfi.cpp} | 40 +++--- sdrbase/dsp/{decimatorsf.h => decimatorsfi.h} | 9 +- sdrbench/mainbench.cpp | 121 ++++++++++++++---- sdrbench/mainbench.h | 14 +- sdrbench/parserbench.cpp | 19 ++- sdrbench/parserbench.h | 11 +- 9 files changed, 158 insertions(+), 65 deletions(-) rename sdrbase/dsp/{decimatorsf.cpp => decimatorsfi.cpp} (95%) rename sdrbase/dsp/{decimatorsf.h => decimatorsfi.h} (94%) diff --git a/plugins/samplesource/airspyhf/airspyhfthread.h b/plugins/samplesource/airspyhf/airspyhfthread.h index 9b7ab98ba..05dd414ca 100644 --- a/plugins/samplesource/airspyhf/airspyhfthread.h +++ b/plugins/samplesource/airspyhf/airspyhfthread.h @@ -17,13 +17,13 @@ #ifndef INCLUDE_AIRSPYHFTHREAD_H #define INCLUDE_AIRSPYHFTHREAD_H +#include #include #include #include #include #include "dsp/samplesinkfifo.h" -#include "dsp/decimatorsf.h" #define AIRSPYHF_BLOCKSIZE (1<<17) @@ -53,7 +53,7 @@ private: unsigned int m_log2Decim; static AirspyHFThread *m_this; - DecimatorsF m_decimators; + DecimatorsFI m_decimators; void run(); void callback(const float* buf, qint32 len); diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 0a95fe91a..c69bced99 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -21,7 +21,7 @@ set(sdrbase_SOURCES dsp/ctcssdetector.cpp dsp/cwkeyer.cpp dsp/cwkeyersettings.cpp - dsp/decimatorsf.cpp + dsp/decimatorsfi.cpp dsp/dspcommands.cpp dsp/dspengine.cpp dsp/dspdevicesourceengine.cpp @@ -104,7 +104,7 @@ set(sdrbase_HEADERS dsp/cwkeyer.h dsp/cwkeyersettings.h dsp/decimators.h - dsp/decimatorsf.h + dsp/decimatorsfi.h dsp/decimatorsu.h dsp/interpolators.h dsp/dspcommands.h diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index a854edc75..c5affe6f0 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -282,6 +282,7 @@ struct TripleByteLE } __attribute__((__packed__)); +/** Decimators with integer input and integer output */ template class Decimators { diff --git a/sdrbase/dsp/decimatorsf.cpp b/sdrbase/dsp/decimatorsfi.cpp similarity index 95% rename from sdrbase/dsp/decimatorsf.cpp rename to sdrbase/dsp/decimatorsfi.cpp index dd637ce70..aeda9ad03 100644 --- a/sdrbase/dsp/decimatorsf.cpp +++ b/sdrbase/dsp/decimatorsfi.cpp @@ -14,9 +14,9 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "decimatorsf.h" +#include -void DecimatorsF::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { float xreal, yimag; @@ -30,7 +30,7 @@ void DecimatorsF::decimate1(SampleVector::iterator* it, const float* buf, qint32 } } -void DecimatorsF::decimate2_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate2_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double intbuf[2]; @@ -52,7 +52,7 @@ void DecimatorsF::decimate2_cen(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal, yimag; @@ -72,7 +72,7 @@ void DecimatorsF::decimate2_inf(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal, yimag; @@ -92,7 +92,7 @@ void DecimatorsF::decimate2_sup(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal, yimag; @@ -108,7 +108,7 @@ void DecimatorsF::decimate4_inf(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { // Sup (USB): // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 @@ -130,7 +130,7 @@ void DecimatorsF::decimate4_sup(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate8_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate8_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal[2], yimag[2]; @@ -152,7 +152,7 @@ void DecimatorsF::decimate8_inf(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate8_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate8_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal[2], yimag[2]; @@ -174,7 +174,7 @@ void DecimatorsF::decimate8_sup(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate16_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate16_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] @@ -201,7 +201,7 @@ void DecimatorsF::decimate16_inf(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate16_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate16_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] @@ -228,7 +228,7 @@ void DecimatorsF::decimate16_sup(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate32_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate32_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal[8], yimag[8]; @@ -258,7 +258,7 @@ void DecimatorsF::decimate32_inf(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate32_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate32_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal[8], yimag[8]; @@ -288,7 +288,7 @@ void DecimatorsF::decimate32_sup(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate64_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate64_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal[16], yimag[16]; @@ -327,7 +327,7 @@ void DecimatorsF::decimate64_inf(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double xreal[16], yimag[16]; @@ -366,7 +366,7 @@ void DecimatorsF::decimate64_sup(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate4_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate4_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double intbuf[4]; @@ -400,7 +400,7 @@ void DecimatorsF::decimate4_cen(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate8_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate8_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double intbuf[8]; @@ -459,7 +459,7 @@ void DecimatorsF::decimate8_cen(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate16_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate16_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double intbuf[16]; @@ -567,7 +567,7 @@ void DecimatorsF::decimate16_cen(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate32_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate32_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double intbuf[32]; @@ -772,7 +772,7 @@ void DecimatorsF::decimate32_cen(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { double intbuf[64]; diff --git a/sdrbase/dsp/decimatorsf.h b/sdrbase/dsp/decimatorsfi.h similarity index 94% rename from sdrbase/dsp/decimatorsf.h rename to sdrbase/dsp/decimatorsfi.h index ba419fb40..31f8fe50e 100644 --- a/sdrbase/dsp/decimatorsf.h +++ b/sdrbase/dsp/decimatorsfi.h @@ -14,15 +14,16 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef SDRBASE_DSP_DECIMATORSF_H_ -#define SDRBASE_DSP_DECIMATORSF_H_ +#ifndef SDRBASE_DSP_DECIMATORSFI_H_ +#define SDRBASE_DSP_DECIMATORSFI_H_ #include "dsp/inthalfbandfilterdbf.h" #include "export.h" #define DECIMATORSF_HB_FILTER_ORDER 64 -class SDRBASE_API DecimatorsF +/** Decimators with float input and integer output */ +class SDRBASE_API DecimatorsFI { public: void decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); @@ -55,4 +56,4 @@ public: -#endif /* SDRBASE_DSP_DECIMATORSF_H_ */ +#endif /* SDRBASE_DSP_DECIMATORSFI_H_ */ diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index a01938447..ea3ef4b7e 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -38,69 +38,140 @@ MainBench::~MainBench() void MainBench::run() { - QElapsedTimer timer; - qint64 nsecs; - - qDebug() << "MainBench::run: parameters:" - << " test: " << m_parser.getTest() + qDebug() << "MainBench::run: parameters:" + << " testStr: " << m_parser.getTestStr() + << " testType: " << (int) m_parser.getTestType() << " nsamples: " << m_parser.getNbSamples() << " repet: " << m_parser.getRepetition() << " log2f: " << m_parser.getLog2Factor(); - - qDebug() << "MainBench::run: create test data"; - m_buf = new qint16[m_parser.getNbSamples()*2]; + if (m_parser.getTestType() == ParserBench::TestDecimatorsII) { + testDecimateII(); + } else if (m_parser.getTestType() == ParserBench::TestDecimatorsFI) { + testDecimateFI(); + } + + emit finished(); +} + +void MainBench::testDecimateII() +{ + QElapsedTimer timer; + qint64 nsecs; + + qDebug() << "MainBench::testDecimateII: create test data"; + + qint16 *buf = new qint16[m_parser.getNbSamples()*2]; m_convertBuffer.resize(m_parser.getNbSamples()/(1< #include "dsp/decimators.h" +#include "dsp/decimatorsfi.h" #include "parserbench.h" namespace qtwebapp { @@ -42,19 +43,22 @@ signals: void finished(); private: - void decimate(const qint16 *buf, int len); + void testDecimateII(); + void testDecimateFI(); + void decimateII(const qint16 *buf, int len); + void decimateFI(const float *buf, int len); static MainBench *m_instance; qtwebapp::LoggerWithFile *m_logger; const ParserBench& m_parser; #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; + Decimators m_decimatorsII; #else - Decimators m_decimators; + Decimators m_decimatorsII; #endif - - qint16 *m_buf; + DecimatorsFI m_decimatorsFI; + SampleVector m_convertBuffer; }; diff --git a/sdrbench/parserbench.cpp b/sdrbench/parserbench.cpp index 8f914a72e..b1d7f741f 100644 --- a/sdrbench/parserbench.cpp +++ b/sdrbench/parserbench.cpp @@ -25,7 +25,7 @@ ParserBench::ParserBench() : m_testOption(QStringList() << "t" << "test", "Test type.", "test", - "decimate"), + "decimateii"), m_nbSamplesOption(QStringList() << "n" << "nb-samples", "Number of sample to deal with.", "samples", @@ -39,7 +39,7 @@ ParserBench::ParserBench() : "log2", "2") { - m_test = "decimate"; + m_testStr = "decimateii"; m_nbSamples = 1048576; m_repetition = 1; m_log2Factor = 4; @@ -68,14 +68,14 @@ void ParserBench::parse(const QCoreApplication& app) QString test = m_parser.value(m_testOption); - QString testStr = "(decimate)"; + QString testStr = "([a-z]+)"; QRegExp ipRegex ("^" + testStr + "$"); QRegExpValidator ipValidator(ipRegex); if (ipValidator.validate(test, pos) == QValidator::Acceptable) { - m_test = test; + m_testStr = test; } else { - qWarning() << "ParserBench::parse: test type invalid. Defaulting to " << m_test; + qWarning() << "ParserBench::parse: test string invalid. Defaulting to " << m_testStr; } // number of samples @@ -111,3 +111,12 @@ void ParserBench::parse(const QCoreApplication& app) qWarning() << "ParserBench::parse: repetilog2 factortion invalid. Defaulting to " << m_log2Factor; } } + +ParserBench::TestType ParserBench::getTestType() const +{ + if (m_testStr == "decimatefi") { + return TestDecimatorsFI; + } else { + return TestDecimatorsII; + } +} diff --git a/sdrbench/parserbench.h b/sdrbench/parserbench.h index a7b7a2660..2b67f23e1 100644 --- a/sdrbench/parserbench.h +++ b/sdrbench/parserbench.h @@ -24,18 +24,25 @@ class ParserBench { public: + typedef enum + { + TestDecimatorsII, + TestDecimatorsFI + } TestType; + ParserBench(); ~ParserBench(); void parse(const QCoreApplication& app); - const QString& getTest() const { return m_test; } + const QString& getTestStr() const { return m_testStr; } + TestType getTestType() const; uint32_t getNbSamples() const { return m_nbSamples; } uint32_t getRepetition() const { return m_repetition; } uint32_t getLog2Factor() const { return m_log2Factor; } private: - QString m_test; + QString m_testStr; uint32_t m_nbSamples; uint32_t m_repetition; uint32_t m_log2Factor; From 17ea5f29b315309d4e3dd2f24e6744255142045d Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 25 Apr 2018 18:01:01 +0200 Subject: [PATCH 317/956] Benchmarking: added float->float decimators and corresponding benchmark test --- CMakeLists.txt | 1 + sdrbase/CMakeLists.txt | 5 +- sdrbase/dsp/decimatorsff.cpp | 1173 +++++++++++++++++ sdrbase/dsp/decimatorsff.h | 59 + sdrbase/dsp/decimatorsfi.h | 16 +- sdrbase/dsp/dsptypes.h | 23 + sdrbase/dsp/inthalfbandfilterdbff.h | 711 ++++++++++ ...andfilterdbf.h => inthalfbandfilterdbfi.h} | 10 +- sdrbench/mainbench.cpp | 86 +- sdrbench/mainbench.h | 10 + sdrbench/parserbench.cpp | 2 + sdrbench/parserbench.h | 3 +- 12 files changed, 2074 insertions(+), 25 deletions(-) create mode 100644 sdrbase/dsp/decimatorsff.cpp create mode 100644 sdrbase/dsp/decimatorsff.h create mode 100644 sdrbase/dsp/inthalfbandfilterdbff.h rename sdrbase/dsp/{inthalfbandfilterdbf.h => inthalfbandfilterdbfi.h} (98%) diff --git a/CMakeLists.txt b/CMakeLists.txt index b39c72ef7..047c3caa1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -315,6 +315,7 @@ target_link_libraries(sdrangelbench ${QT_LIBRARIES} ) +target_compile_features(sdrangelbench PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 qt5_use_modules(sdrangelbench Multimedia) ############################################################################## diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index c69bced99..1ae66fa84 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -21,6 +21,7 @@ set(sdrbase_SOURCES dsp/ctcssdetector.cpp dsp/cwkeyer.cpp dsp/cwkeyersettings.cpp + dsp/decimatorsff.cpp dsp/decimatorsfi.cpp dsp/dspcommands.cpp dsp/dspengine.cpp @@ -104,6 +105,7 @@ set(sdrbase_HEADERS dsp/cwkeyer.h dsp/cwkeyersettings.h dsp/decimators.h + dsp/decimatorsff.h dsp/decimatorsfi.h dsp/decimatorsu.h dsp/interpolators.h @@ -125,7 +127,8 @@ set(sdrbase_HEADERS dsp/hbfiltertraits.h dsp/inthalfbandfilter.h dsp/inthalfbandfilterdb.h - dsp/inthalfbandfilterdbf.h + dsp/inthalfbandfilterdbff.h + dsp/inthalfbandfilterdbfi.h dsp/inthalfbandfiltereo1.h dsp/inthalfbandfiltereo1i.h dsp/inthalfbandfilterst.h diff --git a/sdrbase/dsp/decimatorsff.cpp b/sdrbase/dsp/decimatorsff.cpp new file mode 100644 index 000000000..0134865e8 --- /dev/null +++ b/sdrbase/dsp/decimatorsff.cpp @@ -0,0 +1,1173 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "decimatorsff.h" + +void DecimatorsFF::decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = buf[pos+0]; + yimag = buf[pos+1]; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +void DecimatorsFF::decimate2_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double intbuf[2]; + + for (int pos = 0; pos < nbIAndQ - 3; pos += 4) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + + (**it).setReal(intbuf[0]); + (**it).setImag(intbuf[1]); + + ++(*it); + } +} + +void DecimatorsFF::decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3]); + yimag = (buf[pos+1] + buf[pos+2]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+7] - buf[pos+4]); + yimag = (- buf[pos+5] - buf[pos+6]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +void DecimatorsFF::decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2]); + yimag = (- buf[pos+0] - buf[pos+3]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+6] - buf[pos+5]); + yimag = (buf[pos+4] + buf[pos+7]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +void DecimatorsFF::decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +void DecimatorsFF::decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Sup (USB): + // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 + // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + // Inf (LSB): + // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 + // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + double xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +void DecimatorsFF::decimate8_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + + xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(xreal[1]); + (**it).setImag(yimag[1]); + + ++(*it); + } +} + +void DecimatorsFF::decimate8_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + pos += 8; + + xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(xreal[1]); + (**it).setImag(yimag[1]); + + ++(*it); + } +} + +void DecimatorsFF::decimate16_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + double xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(xreal[3]); + (**it).setImag(yimag[3]); + + ++(*it); + } +} + +void DecimatorsFF::decimate16_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + double xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(xreal[3]); + (**it).setImag(yimag[3]); + + ++(*it); + } +} + +void DecimatorsFF::decimate32_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(xreal[7]); + (**it).setImag(yimag[7]); + + ++(*it); + } +} + +void DecimatorsFF::decimate32_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(xreal[7]); + (**it).setImag(yimag[7]); + + ++(*it); + } +} + +void DecimatorsFF::decimate64_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15]); + (**it).setImag(yimag[15]); + + ++(*it); + } +} + +void DecimatorsFF::decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15]); + (**it).setImag(yimag[15]); + + ++(*it); + } +} + +void DecimatorsFF::decimate4_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double intbuf[4]; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + + (**it).setReal(intbuf[2]); + (**it).setImag(intbuf[3]); + ++(*it); + } +} + +void DecimatorsFF::decimate8_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double intbuf[8]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 16) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6]); + (**it).setImag(intbuf[7]); + ++(*it); + } +} + +void DecimatorsFF::decimate16_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double intbuf[16]; + + for (int pos = 0; pos < nbIAndQ - 31; pos += 32) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14]); + (**it).setImag(intbuf[15]); + ++(*it); + } +} + +void DecimatorsFF::decimate32_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double intbuf[32]; + + for (int pos = 0; pos < nbIAndQ - 63; pos += 64) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30]); + (**it).setImag(intbuf[31]); + ++(*it); + } +} + +void DecimatorsFF::decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + double intbuf[64]; + + for (int pos = 0; pos < nbIAndQ - 127; pos += 128) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + intbuf[32] = buf[pos+66]; + intbuf[33] = buf[pos+67]; + intbuf[34] = buf[pos+70]; + intbuf[35] = buf[pos+71]; + intbuf[36] = buf[pos+74]; + intbuf[37] = buf[pos+75]; + intbuf[38] = buf[pos+78]; + intbuf[39] = buf[pos+79]; + intbuf[40] = buf[pos+82]; + intbuf[41] = buf[pos+83]; + intbuf[42] = buf[pos+86]; + intbuf[43] = buf[pos+87]; + intbuf[44] = buf[pos+90]; + intbuf[45] = buf[pos+91]; + intbuf[46] = buf[pos+94]; + intbuf[47] = buf[pos+95]; + intbuf[48] = buf[pos+98]; + intbuf[49] = buf[pos+99]; + intbuf[50] = buf[pos+102]; + intbuf[51] = buf[pos+103]; + intbuf[52] = buf[pos+106]; + intbuf[53] = buf[pos+107]; + intbuf[54] = buf[pos+110]; + intbuf[55] = buf[pos+111]; + intbuf[56] = buf[pos+114]; + intbuf[57] = buf[pos+115]; + intbuf[58] = buf[pos+118]; + intbuf[59] = buf[pos+119]; + intbuf[60] = buf[pos+122]; + intbuf[61] = buf[pos+123]; + intbuf[62] = buf[pos+126]; + intbuf[63] = buf[pos+127]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + buf[pos+64], + buf[pos+65], + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + buf[pos+68], + buf[pos+69], + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + buf[pos+72], + buf[pos+73], + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + buf[pos+76], + buf[pos+77], + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + buf[pos+80], + buf[pos+81], + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + buf[pos+84], + buf[pos+85], + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + buf[pos+88], + buf[pos+89], + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + buf[pos+92], + buf[pos+93], + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + buf[pos+96], + buf[pos+97], + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + buf[pos+100], + buf[pos+101], + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + buf[pos+104], + buf[pos+105], + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + buf[pos+108], + buf[pos+109], + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + buf[pos+112], + buf[pos+113], + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + buf[pos+116], + buf[pos+117], + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + buf[pos+120], + buf[pos+121], + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + buf[pos+124], + buf[pos+125], + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62]); + (**it).setImag(intbuf[63]); + ++(*it); + } +} + diff --git a/sdrbase/dsp/decimatorsff.h b/sdrbase/dsp/decimatorsff.h new file mode 100644 index 000000000..649a0fc6c --- /dev/null +++ b/sdrbase/dsp/decimatorsff.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 SDRBASE_DSP_DECIMATORSFF_H_ +#define SDRBASE_DSP_DECIMATORSFF_H_ + +#include "dsp/inthalfbandfilterdbff.h" +#include "export.h" + +#define DECIMATORSFF_HB_FILTER_ORDER 64 + +/** Decimators with float input and float output */ +class SDRBASE_API DecimatorsFF +{ +public: + void decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + + IntHalfbandFilterDBFF m_decimator2; // 1st stages + IntHalfbandFilterDBFF m_decimator4; // 2nd stages + IntHalfbandFilterDBFF m_decimator8; // 3rd stages + IntHalfbandFilterDBFF m_decimator16; // 4th stages + IntHalfbandFilterDBFF m_decimator32; // 5th stages + IntHalfbandFilterDBFF m_decimator64; // 6th stages +}; + + + +#endif /* SDRBASE_DSP_DECIMATORSFF_H_ */ diff --git a/sdrbase/dsp/decimatorsfi.h b/sdrbase/dsp/decimatorsfi.h index 31f8fe50e..283cb6e0a 100644 --- a/sdrbase/dsp/decimatorsfi.h +++ b/sdrbase/dsp/decimatorsfi.h @@ -17,10 +17,10 @@ #ifndef SDRBASE_DSP_DECIMATORSFI_H_ #define SDRBASE_DSP_DECIMATORSFI_H_ -#include "dsp/inthalfbandfilterdbf.h" +#include "dsp/inthalfbandfilterdbfi.h" #include "export.h" -#define DECIMATORSF_HB_FILTER_ORDER 64 +#define DECIMATORSFI_HB_FILTER_ORDER 64 /** Decimators with float input and integer output */ class SDRBASE_API DecimatorsFI @@ -46,12 +46,12 @@ public: void decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); void decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); - IntHalfbandFilterDBF m_decimator2; // 1st stages - IntHalfbandFilterDBF m_decimator4; // 2nd stages - IntHalfbandFilterDBF m_decimator8; // 3rd stages - IntHalfbandFilterDBF m_decimator16; // 4th stages - IntHalfbandFilterDBF m_decimator32; // 5th stages - IntHalfbandFilterDBF m_decimator64; // 6th stages + IntHalfbandFilterDBFI m_decimator2; // 1st stages + IntHalfbandFilterDBFI m_decimator4; // 2nd stages + IntHalfbandFilterDBFI m_decimator8; // 3rd stages + IntHalfbandFilterDBFI m_decimator16; // 4th stages + IntHalfbandFilterDBFI m_decimator32; // 5th stages + IntHalfbandFilterDBFI m_decimator64; // 6th stages }; diff --git a/sdrbase/dsp/dsptypes.h b/sdrbase/dsp/dsptypes.h index e3913a785..f39f9deb7 100644 --- a/sdrbase/dsp/dsptypes.h +++ b/sdrbase/dsp/dsptypes.h @@ -64,6 +64,28 @@ struct Sample FixReal m_imag; }; +struct FSample +{ + FSample() : m_real(0), m_imag(0) {} + FSample(Real real) : m_real(real), m_imag(0) {} + FSample(Real real, Real imag) : m_real(real), m_imag(imag) {} + FSample(const FSample& other) : m_real(other.m_real), m_imag(other.m_imag) {} + inline FSample& operator=(const FSample& other) { m_real = other.m_real; m_imag = other.m_imag; return *this; } + + inline FSample& operator+=(const FSample& other) { m_real += other.m_real; m_imag += other.m_imag; return *this; } + inline FSample& operator-=(const FSample& other) { m_real -= other.m_real; m_imag -= other.m_imag; return *this; } + inline FSample& operator/=(const Real& divisor) { m_real /= divisor; m_imag /= divisor; return *this; } + + inline void setReal(Real v) { m_real = v; } + inline void setImag(Real v) { m_imag = v; } + + inline Real real() const { return m_real; } + inline Real imag() const { return m_imag; } + + Real m_real; + Real m_imag; +}; + struct AudioSample { qint16 l; qint16 r; @@ -71,6 +93,7 @@ struct AudioSample { #pragma pack(pop) typedef std::vector SampleVector; +typedef std::vector FSampleVector; typedef std::vector AudioVector; #endif // INCLUDE_DSPTYPES_H diff --git a/sdrbase/dsp/inthalfbandfilterdbff.h b/sdrbase/dsp/inthalfbandfilterdbff.h new file mode 100644 index 000000000..2816f2696 --- /dev/null +++ b/sdrbase/dsp/inthalfbandfilterdbff.h @@ -0,0 +1,711 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Float half-band FIR based interpolator and decimator // +// This is the double buffer variant // +// // +// 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_INTHALFBANDFILTER_DBFF_H +#define INCLUDE_INTHALFBANDFILTER_DBFF_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterDBFF { +public: + IntHalfbandFilterDBFF(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(FSample* sample) + { + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(FSample* sampleIn, FSample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(FSample* sampleIn, FSample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(FSample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->imag(), (Real) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->real(), (Real) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) sample->imag(), (Real) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(FSample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) sample->imag(), (Real) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->real(), (Real) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->imag(), (Real) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + void myDecimate(const FSample* sample1, FSample* sample2) + { + storeSampleReal((Real) sample1->real(), (Real) sample1->imag()); + advancePointer(); + + storeSampleReal((Real) sample2->real(), (Real) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) + { + storeSampleAccu(x1, y1); + advancePointer(); + + storeSampleAccu(*x2, *y2); + doFIRAccu(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(FSample* sample1, FSample* sample2) + { + storeSampleReal((Real) sample1->real(), (Real) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSampleReal((Real) 0, (Real) 0); + doFIR(sample2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = *x1; + m_samplesDB[m_ptr][1] = *y1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + SampleType m_samplesDB[2*(HBFIRFilterTraits::hbOrder - 1)][2]; // double buffer technique + int m_ptr; + int m_size; + int m_state; + + void storeSampleReal(const Real& sampleI, const Real& sampleQ) + { + m_samplesDB[m_ptr][0] = sampleI; + m_samplesDB[m_ptr][1] = sampleQ; + m_samplesDB[m_ptr + m_size][0] = sampleI; + m_samplesDB[m_ptr + m_size][1] = sampleQ; + } + + void storeSampleAccu(AccuType x, AccuType y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(FSample* sample) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); + + sample->setReal(iAcc); + sample->setImag(qAcc); + } + + void doFIRAccu(AccuType *x, AccuType *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] / 2.0; + qAcc += m_samplesDB[b-1][1] / 2.0; + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(FSample* sample) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + sample->setReal(iAcc); + sample->setImag(qAcc); + } + + void doInterpolateFIR(Real *x, Real *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc; + *y = qAcc; + } +}; + +template +IntHalfbandFilterDBFF::IntHalfbandFilterDBFF() +{ + m_size = HBFIRFilterTraits::hbOrder - 1; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif // INCLUDE_INTHALFBANDFILTER_DBFF_H diff --git a/sdrbase/dsp/inthalfbandfilterdbf.h b/sdrbase/dsp/inthalfbandfilterdbfi.h similarity index 98% rename from sdrbase/dsp/inthalfbandfilterdbf.h rename to sdrbase/dsp/inthalfbandfilterdbfi.h index 5dc7bad7a..f83c4fd33 100644 --- a/sdrbase/dsp/inthalfbandfilterdbf.h +++ b/sdrbase/dsp/inthalfbandfilterdbfi.h @@ -18,8 +18,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_INTHALFBANDFILTER_DBF_H -#define INCLUDE_INTHALFBANDFILTER_DBF_H +#ifndef INCLUDE_INTHALFBANDFILTER_DBFI_H +#define INCLUDE_INTHALFBANDFILTER_DBFI_H #include #include "dsp/dsptypes.h" @@ -27,9 +27,9 @@ #include "export.h" template -class SDRBASE_API IntHalfbandFilterDBF { +class SDRBASE_API IntHalfbandFilterDBFI { public: - IntHalfbandFilterDBF(); + IntHalfbandFilterDBFI(); // downsample by 2, return center part of original spectrum bool workDecimateCenter(Sample* sample) @@ -694,7 +694,7 @@ protected: }; template -IntHalfbandFilterDBF::IntHalfbandFilterDBF() +IntHalfbandFilterDBFI::IntHalfbandFilterDBFI() { m_size = HBFIRFilterTraits::hbOrder - 1; diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index ea3ef4b7e..5477d1246 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -26,7 +26,8 @@ MainBench *MainBench::m_instance = 0; MainBench::MainBench(qtwebapp::LoggerWithFile *logger, const ParserBench& parser, QObject *parent) : QObject(parent), m_logger(logger), - m_parser(parser) + m_parser(parser), + m_uniform_distribution(-1.0, 1.0) { qDebug() << "MainBench::MainBench: start"; m_instance = this; @@ -49,6 +50,10 @@ void MainBench::run() testDecimateII(); } else if (m_parser.getTestType() == ParserBench::TestDecimatorsFI) { testDecimateFI(); + } else if (m_parser.getTestType() == ParserBench::TestDecimatorsFF) { + testDecimateFF(); + } else { + qDebug() << "MainBench::run: unknown test type: " << m_parser.getTestType(); } emit finished(); @@ -73,13 +78,9 @@ void MainBench::testDecimateII() } nsecs = timer.nsecsElapsed(); - QDebug debug = qDebug(); - debug.noquote(); - debug << tr("MainBench::testDecimateII: ran test in %L1 ns").arg(nsecs); - + printResults("MainBench::testDecimateII", nsecs); qDebug() << "MainBench::testDecimateII: cleanup test data"; - delete[] buf; } @@ -92,6 +93,8 @@ void MainBench::testDecimateFI() float *buf = new float[m_parser.getNbSamples()*2]; m_convertBuffer.resize(m_parser.getNbSamples()/(1< +#include +#include #include "dsp/decimators.h" #include "dsp/decimatorsfi.h" +#include "dsp/decimatorsff.h" #include "parserbench.h" namespace qtwebapp { @@ -45,12 +48,17 @@ signals: private: void testDecimateII(); void testDecimateFI(); + void testDecimateFF(); void decimateII(const qint16 *buf, int len); void decimateFI(const float *buf, int len); + void decimateFF(const float *buf, int len); + void printResults(const QString& prefix, qint64 nsecs); static MainBench *m_instance; qtwebapp::LoggerWithFile *m_logger; const ParserBench& m_parser; + std::mt19937 m_generator; + std::uniform_real_distribution m_uniform_distribution; #ifdef SDR_RX_SAMPLE_24BIT Decimators m_decimatorsII; @@ -58,8 +66,10 @@ private: Decimators m_decimatorsII; #endif DecimatorsFI m_decimatorsFI; + DecimatorsFF m_decimatorsFF; SampleVector m_convertBuffer; + FSampleVector m_convertBufferF; }; #endif // SDRBENCH_MAINBENCH_H_ diff --git a/sdrbench/parserbench.cpp b/sdrbench/parserbench.cpp index b1d7f741f..cd0a2fe0d 100644 --- a/sdrbench/parserbench.cpp +++ b/sdrbench/parserbench.cpp @@ -116,6 +116,8 @@ ParserBench::TestType ParserBench::getTestType() const { if (m_testStr == "decimatefi") { return TestDecimatorsFI; + } else if (m_testStr == "decimateff") { + return TestDecimatorsFF; } else { return TestDecimatorsII; } diff --git a/sdrbench/parserbench.h b/sdrbench/parserbench.h index 2b67f23e1..10a930a3a 100644 --- a/sdrbench/parserbench.h +++ b/sdrbench/parserbench.h @@ -27,7 +27,8 @@ public: typedef enum { TestDecimatorsII, - TestDecimatorsFI + TestDecimatorsFI, + TestDecimatorsFF } TestType; ParserBench(); From ddfcccab22059be2a82978aca1981a7eefbd6581 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 25 Apr 2018 18:06:47 +0200 Subject: [PATCH 318/956] Benchmarking: use info level message instead of debug to show results --- sdrbench/mainbench.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index 5477d1246..43590c8c4 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -237,7 +237,7 @@ void MainBench::decimateFF(const float *buf, int len) void MainBench::printResults(const QString& prefix, qint64 nsecs) { double ratekSs = (m_parser.getNbSamples()*m_parser.getRepetition() / (double) nsecs) * 1e6; - QDebug debug = qDebug(); - debug.noquote(); - debug << tr("%1: ran test in %L2 ns - sample rate: %3 kS/s").arg(prefix).arg(nsecs).arg(ratekSs); + QDebug info = qInfo(); + info.noquote(); + info << tr("%1: ran test in %L2 ns - sample rate: %3 kS/s").arg(prefix).arg(nsecs).arg(ratekSs); } From 2427c885f718d5b9780cc7cb0d7d09337017d875 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 26 Apr 2018 22:45:47 +0200 Subject: [PATCH 319/956] Benchmarking: added option to deactivate SIMD for decimators --- CMakeLists.txt | 8 ++++++++ sdrbase/dsp/decimators.h | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 047c3caa1..36a5bef26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ option(DEBUG_OUTPUT "Print debug messages" OFF) option(SANITIZE_ADDRESS "Activate memory address sanitization" OFF) option(HOST_RPI "Compiling on RPi" OFF) option(RX_SAMPLE_24BIT "Internal 24 bit Rx DSP" OFF) +option(NO_DSP_SIMD "Do not ese SIMD instructions for DSP even if available" OFF) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) @@ -203,6 +204,13 @@ if (SANITIZE_ADDRESS) set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -fsanitize=address") endif() +if (NO_DSP_SIMD) + message(STATUS "Not compiling with SIMD instructions for DSP even if available") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNO_DSP_SIMD") +else() + message(STATUS "Compiling with SIMD instructions for DSP if available") +endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -Woverloaded-virtual -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index c5affe6f0..567ea5852 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -18,7 +18,7 @@ #define INCLUDE_GPL_DSP_DECIMATORS_H_ #include "dsp/dsptypes.h" -#ifdef SDR_RX_SAMPLE_24BIT +#if defined(SDR_RX_SAMPLE_24BIT) || defined(NO_SIMD_DSP) #include "dsp/inthalfbandfilterdb.h" #else #ifdef USE_SSE4_1 @@ -331,7 +331,7 @@ public: void decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); private: -#ifdef SDR_RX_SAMPLE_24BIT +#if defined(SDR_RX_SAMPLE_24BIT) || defined(NO_SIMD_DSP) IntHalfbandFilterDB m_decimator2; // 1st stages IntHalfbandFilterDB m_decimator4; // 2nd stages IntHalfbandFilterDB m_decimator8; // 3rd stages From 45aa323cf6a9524cde8724724f003355f5b2da18 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 27 Apr 2018 06:41:48 +0200 Subject: [PATCH 320/956] SSB demod: make the filter sliders less bulky with only one scale on the high cutoff --- plugins/channelrx/demodssb/ssbdemodgui.ui | 164 +++++++++++----------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/plugins/channelrx/demodssb/ssbdemodgui.ui b/plugins/channelrx/demodssb/ssbdemodgui.ui index 7ecb9be9a..75522cec1 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.ui +++ b/plugins/channelrx/demodssb/ssbdemodgui.ui @@ -327,6 +327,88 @@ + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Low cut + + + + + + + + 16777215 + 16 + + + + Highpass filter cutoff frequency (SSB) + + + -60 + + + 60 + + + 1 + + + 3 + + + Qt::Horizontal + + + false + + + QSlider::NoTicks + + + 5 + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 0.3k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + @@ -546,88 +628,6 @@ - - - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - Low cut - - - - - - - - 16777215 - 16 - - - - Highpass filter cutoff frequency (SSB) - - - -60 - - - 60 - - - 1 - - - 3 - - - Qt::Horizontal - - - false - - - QSlider::TicksAbove - - - 5 - - - - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - 0.3k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - From 34ff36926e9594107392f202a3d8f19abaaf1d94 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 27 Apr 2018 06:59:36 +0200 Subject: [PATCH 321/956] SSB demod: fixed span slider aspect --- plugins/channelrx/demodssb/ssbdemodgui.cpp | 20 +++++------ plugins/channelrx/demodssb/ssbdemodgui.h | 2 +- plugins/channelrx/demodssb/ssbdemodgui.ui | 41 +++++++++++++++------- plugins/channelrx/demodssb/ssbplugin.cpp | 2 +- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/plugins/channelrx/demodssb/ssbdemodgui.cpp b/plugins/channelrx/demodssb/ssbdemodgui.cpp index 9a8ad9c3a..42a43237e 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.cpp +++ b/plugins/channelrx/demodssb/ssbdemodgui.cpp @@ -85,7 +85,7 @@ bool SSBDemodGUI::handleMessage(const Message& message) if (DSPConfigureAudio::match(message)) { qDebug("SSBDemodGUI::handleMessage: DSPConfigureAudio: %d", m_ssbDemod->getAudioSampleRate()); - applyBandwidths(); // will update spectrum details with new sample rate + applyBandwidths(5 - ui->spanLog2->value()); // will update spectrum details with new sample rate return true; } else @@ -136,7 +136,7 @@ void SSBDemodGUI::on_audioFlipChannels_toggled(bool flip) void SSBDemodGUI::on_dsb_toggled(bool dsb) { ui->flipSidebands->setEnabled(!dsb); - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBDemodGUI::on_deltaFrequency_changed(qint64 value) @@ -148,12 +148,12 @@ void SSBDemodGUI::on_deltaFrequency_changed(qint64 value) void SSBDemodGUI::on_BW_valueChanged(int value __attribute__((unused))) { - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBDemodGUI::on_lowCut_valueChanged(int value __attribute__((unused))) { - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBDemodGUI::on_volume_valueChanged(int value) @@ -207,11 +207,11 @@ void SSBDemodGUI::on_audioMute_toggled(bool checked) void SSBDemodGUI::on_spanLog2_valueChanged(int value) { - if ((value < 1) || (value > 5)) { + if ((value < 0) || (value > 4)) { return; } - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBDemodGUI::on_flipSidebands_clicked(bool checked __attribute__((unused))) @@ -311,7 +311,7 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::Off); displaySettings(); - applyBandwidths(true); // does applySettings(true) + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) } SSBDemodGUI::~SSBDemodGUI() @@ -342,10 +342,10 @@ void SSBDemodGUI::applySettings(bool force) } } -void SSBDemodGUI::applyBandwidths(bool force) +void SSBDemodGUI::applyBandwidths(int spanLog2, bool force) { bool dsb = ui->dsb->isChecked(); - int spanLog2 = ui->spanLog2->value(); + //int spanLog2 = ui->spanLog2->value(); m_spectrumRate = m_ssbDemod->getAudioSampleRate() / (1<BW->value(); int lw = ui->lowCut->value(); @@ -493,7 +493,7 @@ void SSBDemodGUI::displaySettings() ui->BW->blockSignals(true); ui->dsb->setChecked(m_settings.m_dsb); - ui->spanLog2->setValue(m_settings.m_spanLog2); + ui->spanLog2->setValue(5 - m_settings.m_spanLog2); ui->BW->setValue(m_settings.m_rfBandwidth / 100.0); QString s = QString::number(m_settings.m_rfBandwidth/1000.0, 'f', 1); diff --git a/plugins/channelrx/demodssb/ssbdemodgui.h b/plugins/channelrx/demodssb/ssbdemodgui.h index 24eca4639..20639178c 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.h +++ b/plugins/channelrx/demodssb/ssbdemodgui.h @@ -70,7 +70,7 @@ private: bool blockApplySettings(bool block); void applySettings(bool force = false); - void applyBandwidths(bool force = false); + void applyBandwidths(int spanLog2, bool force = false); void displaySettings(); void displayAGCPowerThreshold(int value); diff --git a/plugins/channelrx/demodssb/ssbdemodgui.ui b/plugins/channelrx/demodssb/ssbdemodgui.ui index 75522cec1..d75d76a66 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.ui +++ b/plugins/channelrx/demodssb/ssbdemodgui.ui @@ -274,11 +274,20 @@ - - 0 - + + + 50 + 0 + + + + + 50 + 16777215 + + Span @@ -290,33 +299,39 @@ Demod frequency span - 1 + 0 - 5 + 4 1 - 3 + 2 - 3 + 2 Qt::Horizontal - - true - - - true - + + + 50 + 0 + + + + + 50 + 16777215 + + 6.0k diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp index af380205a..2e04494ed 100644 --- a/plugins/channelrx/demodssb/ssbplugin.cpp +++ b/plugins/channelrx/demodssb/ssbplugin.cpp @@ -8,7 +8,7 @@ const PluginDescriptor SSBPlugin::m_pluginDescriptor = { QString("SSB Demodulator"), - QString("3.14.4"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 8d565f8187c8a5f6d8e55288cba18b8d8d653e0b Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 27 Apr 2018 07:09:37 +0200 Subject: [PATCH 322/956] SSB mod: fixed span slider aspect and bulky low/high cut slider group --- plugins/channeltx/modssb/ssbmodgui.cpp | 23 ++- plugins/channeltx/modssb/ssbmodgui.h | 2 +- plugins/channeltx/modssb/ssbmodgui.ui | 226 +++++++++++----------- plugins/channeltx/modssb/ssbmodplugin.cpp | 2 +- 4 files changed, 129 insertions(+), 124 deletions(-) diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index a61ae5bc5..1f45221bc 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -84,14 +84,14 @@ bool SSBModGUI::deserialize(const QByteArray& data) { qDebug("SSBModGUI::deserialize"); displaySettings(); - applyBandwidths(true); // does applySettings(true) + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) return true; } else { m_settings.resetToDefaults(); displaySettings(); - applyBandwidths(true); // does applySettings(true) + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) return false; } } @@ -115,7 +115,7 @@ bool SSBModGUI::handleMessage(const Message& message) else if (DSPConfigureAudio::match(message)) { qDebug("SSBModGUI::handleMessage: DSPConfigureAudio: %d", m_ssbMod->getAudioSampleRate()); - applyBandwidths(); // will update spectrum details with new sample rate + applyBandwidths(5 - ui->spanLog2->value()); // will update spectrum details with new sample rate return true; } else if (SSBMod::MsgConfigureSSBMod::match(message)) @@ -184,7 +184,7 @@ void SSBModGUI::on_flipSidebands_clicked(bool checked __attribute__((unused))) void SSBModGUI::on_dsb_toggled(bool dsb) { ui->flipSidebands->setEnabled(!dsb); - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBModGUI::on_audioBinaural_toggled(bool checked) @@ -201,21 +201,21 @@ void SSBModGUI::on_audioFlipChannels_toggled(bool checked) void SSBModGUI::on_spanLog2_valueChanged(int value) { - if ((value < 1) || (value > 5)) { + if ((value < 0) || (value > 4)) { return; } - applyBandwidths(); + applyBandwidths(5 - value); } void SSBModGUI::on_BW_valueChanged(int value __attribute__((unused))) { - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBModGUI::on_lowCut_valueChanged(int value __attribute__((unused))) { - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBModGUI::on_toneFrequency_valueChanged(int value) @@ -454,7 +454,7 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::Off); displaySettings(); - applyBandwidths(true); // does applySettings(true) + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) } SSBModGUI::~SSBModGUI() @@ -485,10 +485,9 @@ void SSBModGUI::applySettings(bool force) } } -void SSBModGUI::applyBandwidths(bool force) +void SSBModGUI::applyBandwidths(int spanLog2, bool force) { bool dsb = ui->dsb->isChecked(); - int spanLog2 = ui->spanLog2->value(); m_spectrumRate = m_ssbMod->getAudioSampleRate() / (1<BW->value(); int lw = ui->lowCut->value(); @@ -646,7 +645,7 @@ void SSBModGUI::displaySettings() ui->BW->blockSignals(true); ui->dsb->setChecked(m_settings.m_dsb); - ui->spanLog2->setValue(m_settings.m_spanLog2); + ui->spanLog2->setValue(5 - m_settings.m_spanLog2); ui->BW->setValue(roundf(m_settings.m_bandwidth/100.0)); s = QString::number(m_settings.m_bandwidth/1000.0, 'f', 1); diff --git a/plugins/channeltx/modssb/ssbmodgui.h b/plugins/channeltx/modssb/ssbmodgui.h index 58b5191e9..d972f2c2c 100644 --- a/plugins/channeltx/modssb/ssbmodgui.h +++ b/plugins/channeltx/modssb/ssbmodgui.h @@ -87,7 +87,7 @@ private: bool blockApplySettings(bool block); void applySettings(bool force = false); - void applyBandwidths(bool force = false); + void applyBandwidths(int spanLog2, bool force = false); void displaySettings(); void displayAGCPowerThreshold(); void updateWithStreamData(); diff --git a/plugins/channeltx/modssb/ssbmodgui.ui b/plugins/channeltx/modssb/ssbmodgui.ui index 3bd0bd02f..17d480f3e 100644 --- a/plugins/channeltx/modssb/ssbmodgui.ui +++ b/plugins/channeltx/modssb/ssbmodgui.ui @@ -53,16 +53,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -252,6 +243,18 @@ + + + 50 + 0 + + + + + 50 + 16777215 + + Span @@ -263,10 +266,92 @@ Spectrum display frequency span - 1 + 0 - 5 + 4 + + + 1 + + + 2 + + + 2 + + + Qt::Horizontal + + + false + + + false + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 6.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Low cut + + + + + + + + 16777215 + 16 + + + + Highpass filter cutoff frequency (SSB) + + + -60 + + + 60 1 @@ -274,24 +359,33 @@ 3 - - 3 - Qt::Horizontal - - true + + QSlider::NoTicks - - true + + 5 - + + + + 50 + 0 + + + + + 50 + 16777215 + + - 6.0k + 0.3k Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -522,85 +616,6 @@ - - - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - Low cut - - - - - - - - 16777215 - 16 - - - - Highpass filter cutoff frequency (SSB) - - - -60 - - - 60 - - - 1 - - - 3 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 5 - - - - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - 0.3k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - @@ -1224,16 +1239,7 @@ 2 - - 3 - - - 3 - - - 3 - - + 3 diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp index c19c2cc04..f4a5fd5cb 100644 --- a/plugins/channeltx/modssb/ssbmodplugin.cpp +++ b/plugins/channeltx/modssb/ssbmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor SSBModPlugin::m_pluginDescriptor = { QString("SSB Modulator"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 33e171bd9b9974bfdc28386e6fd78bb39683b601 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 27 Apr 2018 20:40:17 +0200 Subject: [PATCH 323/956] Moving average: fixed initialization --- sdrbase/dsp/movingaverage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrbase/dsp/movingaverage.h b/sdrbase/dsp/movingaverage.h index 0f932ab84..ef2ed5a37 100644 --- a/sdrbase/dsp/movingaverage.h +++ b/sdrbase/dsp/movingaverage.h @@ -8,7 +8,7 @@ template class MovingAverage { public: - MovingAverage(int historySize, Type initial) + MovingAverage(int historySize, Type initial) : m_index(0) { resize(historySize, initial); } From 27623709f073c3ac8ef69345fd997239919b42f4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 28 Apr 2018 03:04:34 +0200 Subject: [PATCH 324/956] Rewrite of decimator ifdefs --- sdrbase/dsp/decimators.h | 55 ++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index 567ea5852..0e2d3874c 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -18,15 +18,20 @@ #define INCLUDE_GPL_DSP_DECIMATORS_H_ #include "dsp/dsptypes.h" -#if defined(SDR_RX_SAMPLE_24BIT) || defined(NO_SIMD_DSP) + +#ifdef NO_DSP_SIMD #include "dsp/inthalfbandfilterdb.h" -#else +#else // NO_DSP_SIMD +#ifdef SDR_RX_SAMPLE_24BIT +#include "dsp/inthalfbandfilterdb.h" +#else // SDR_RX_SAMPLE_24BIT #ifdef USE_SSE4_1 #include "dsp/inthalfbandfiltereo1.h" -#else +#else // USE_SSE4_1 #include "dsp/inthalfbandfilterdb.h" -#endif -#endif +#endif // USE_SSE4_1 +#endif // SDR_RX_SAMPLE_24BIT +#endif // NO_DSP_SIMD #define DECIMATORS_HB_FILTER_ORDER 64 @@ -331,14 +336,31 @@ public: void decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); private: -#if defined(SDR_RX_SAMPLE_24BIT) || defined(NO_SIMD_DSP) +#ifdef NO_DSP_SIMD +#ifdef SDR_RX_SAMPLE_24BIT IntHalfbandFilterDB m_decimator2; // 1st stages IntHalfbandFilterDB m_decimator4; // 2nd stages IntHalfbandFilterDB m_decimator8; // 3rd stages IntHalfbandFilterDB m_decimator16; // 4th stages IntHalfbandFilterDB m_decimator32; // 5th stages IntHalfbandFilterDB m_decimator64; // 6th stages -#else +#else // SDR_RX_SAMPLE_24BIT + IntHalfbandFilterDB m_decimator2; // 1st stages + IntHalfbandFilterDB m_decimator4; // 2nd stages + IntHalfbandFilterDB m_decimator8; // 3rd stages + IntHalfbandFilterDB m_decimator16; // 4th stages + IntHalfbandFilterDB m_decimator32; // 5th stages + IntHalfbandFilterDB m_decimator64; // 6th stages +#endif // SDR_RX_SAMPLE_24BIT +#else // NO_DSP_SIMD +#ifdef SDR_RX_SAMPLE_24BIT + IntHalfbandFilterDB m_decimator2; // 1st stages + IntHalfbandFilterDB m_decimator4; // 2nd stages + IntHalfbandFilterDB m_decimator8; // 3rd stages + IntHalfbandFilterDB m_decimator16; // 4th stages + IntHalfbandFilterDB m_decimator32; // 5th stages + IntHalfbandFilterDB m_decimator64; // 6th stages +#else // SDR_RX_SAMPLE_24BIT #ifdef USE_SSE4_1 IntHalfbandFilterEO1 m_decimator2; // 1st stages IntHalfbandFilterEO1 m_decimator4; // 2nd stages @@ -346,15 +368,16 @@ private: IntHalfbandFilterEO1 m_decimator16; // 4th stages IntHalfbandFilterEO1 m_decimator32; // 5th stages IntHalfbandFilterEO1 m_decimator64; // 6th stages -#else - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages -#endif -#endif +#else // USE_SSE4_1 + IntHalfbandFilterDB m_decimator2; // 1st stages + IntHalfbandFilterDB m_decimator4; // 2nd stages + IntHalfbandFilterDB m_decimator8; // 3rd stages + IntHalfbandFilterDB m_decimator16; // 4th stages + IntHalfbandFilterDB m_decimator32; // 5th stages + IntHalfbandFilterDB m_decimator64; // 6th stages +#endif // USE_SSE4_1 +#endif // SDR_RX_SAMPLE_24BIT +#endif // NO_DSP_SIMD }; template From b23d1f6a63ad3abdb2ad77ac9c514ca60d0dd7a2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 28 Apr 2018 05:08:01 +0200 Subject: [PATCH 325/956] Optimization: always use the even/odd decimators --- CMakeLists.txt | 2 +- sdrbase/dsp/decimators.h | 49 +- sdrbase/dsp/inthalfbandfiltereo1.h | 4 +- sdrbase/dsp/inthalfbandfiltereo2.h | 853 +++++++++++++++++++++++++++++ 4 files changed, 863 insertions(+), 45 deletions(-) create mode 100644 sdrbase/dsp/inthalfbandfiltereo2.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 36a5bef26..f3b26d126 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ option(DEBUG_OUTPUT "Print debug messages" OFF) option(SANITIZE_ADDRESS "Activate memory address sanitization" OFF) option(HOST_RPI "Compiling on RPi" OFF) option(RX_SAMPLE_24BIT "Internal 24 bit Rx DSP" OFF) -option(NO_DSP_SIMD "Do not ese SIMD instructions for DSP even if available" OFF) +option(NO_DSP_SIMD "Do not use SIMD instructions for DSP even if available" OFF) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index 0e2d3874c..44680cbd8 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -19,19 +19,11 @@ #include "dsp/dsptypes.h" -#ifdef NO_DSP_SIMD -#include "dsp/inthalfbandfilterdb.h" -#else // NO_DSP_SIMD #ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfilterdb.h" +#include "dsp/inthalfbandfiltereo2.h" #else // SDR_RX_SAMPLE_24BIT -#ifdef USE_SSE4_1 #include "dsp/inthalfbandfiltereo1.h" -#else // USE_SSE4_1 -#include "dsp/inthalfbandfilterdb.h" -#endif // USE_SSE4_1 #endif // SDR_RX_SAMPLE_24BIT -#endif // NO_DSP_SIMD #define DECIMATORS_HB_FILTER_ORDER 64 @@ -336,48 +328,21 @@ public: void decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); private: -#ifdef NO_DSP_SIMD #ifdef SDR_RX_SAMPLE_24BIT - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages + IntHalfbandFilterEO2 m_decimator2; // 1st stages + IntHalfbandFilterEO2 m_decimator4; // 2nd stages + IntHalfbandFilterEO2 m_decimator8; // 3rd stages + IntHalfbandFilterEO2 m_decimator16; // 4th stages + IntHalfbandFilterEO2 m_decimator32; // 5th stages + IntHalfbandFilterEO2 m_decimator64; // 6th stages #else // SDR_RX_SAMPLE_24BIT - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages -#endif // SDR_RX_SAMPLE_24BIT -#else // NO_DSP_SIMD -#ifdef SDR_RX_SAMPLE_24BIT - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages -#else // SDR_RX_SAMPLE_24BIT -#ifdef USE_SSE4_1 IntHalfbandFilterEO1 m_decimator2; // 1st stages IntHalfbandFilterEO1 m_decimator4; // 2nd stages IntHalfbandFilterEO1 m_decimator8; // 3rd stages IntHalfbandFilterEO1 m_decimator16; // 4th stages IntHalfbandFilterEO1 m_decimator32; // 5th stages IntHalfbandFilterEO1 m_decimator64; // 6th stages -#else // USE_SSE4_1 - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages -#endif // USE_SSE4_1 #endif // SDR_RX_SAMPLE_24BIT -#endif // NO_DSP_SIMD }; template diff --git a/sdrbase/dsp/inthalfbandfiltereo1.h b/sdrbase/dsp/inthalfbandfiltereo1.h index e76f3a871..b2b8f1793 100644 --- a/sdrbase/dsp/inthalfbandfiltereo1.h +++ b/sdrbase/dsp/inthalfbandfiltereo1.h @@ -681,7 +681,7 @@ protected: int32_t iAcc = 0; int32_t qAcc = 0; -#ifdef USE_SSE4_1 +#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) IntHalfbandFilterEO1Intrisics::work( m_ptr, m_even, @@ -731,7 +731,7 @@ protected: int32_t iAcc = 0; int32_t qAcc = 0; -#ifdef USE_SSE4_1 +#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) IntHalfbandFilterEO1Intrisics::work( m_ptr, m_even, diff --git a/sdrbase/dsp/inthalfbandfiltereo2.h b/sdrbase/dsp/inthalfbandfiltereo2.h new file mode 100644 index 000000000..15619d1eb --- /dev/null +++ b/sdrbase/dsp/inthalfbandfiltereo2.h @@ -0,0 +1,853 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// 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 SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "dsp/inthalfbandfiltereo2i.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterEO2 { +public: + IntHalfbandFilterEO2(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + bool workDecimateCenter(int32_t *x, int32_t *y) + { + // insert sample into ring-buffer + storeSample32(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSample((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(qint64 x1, qint64 y1, qint64 *x2, qint64 *y2) + { + storeSample64(x1, y1); + advancePointer(); + + storeSample64(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSample((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(qint64 *x1, qint64 *y1, qint64 *x2, qint64 *y2) + { + storeSample64(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample64(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + qint64 m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + qint64 m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + int32_t m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique + + int m_ptr; + int m_size; + int m_state; + + void storeSample(const FixReal& sampleI, const FixReal& sampleQ) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = sampleI; + m_even[1][m_ptr/2] = sampleQ; + m_even[0][m_ptr/2 + m_size] = sampleI; + m_even[1][m_ptr/2 + m_size] = sampleQ; + } + else + { + m_odd[0][m_ptr/2] = sampleI; + m_odd[1][m_ptr/2] = sampleQ; + m_odd[0][m_ptr/2 + m_size] = sampleI; + m_odd[1][m_ptr/2 + m_size] = sampleQ; + } + } + + void storeSample32(int32_t x, int32_t y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void storeSample64(qint64 x, qint64 y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + +//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) +// IntHalfbandFilterEO2Intrisics::work( +// m_ptr, +// m_even, +// m_odd, +// iAcc, +// qAcc +// ); +//#else + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } +//#endif + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doFIR(qint64 *x, qint64 *y) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + +//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) +// IntHalfbandFilterEO2Intrisics::work( +// m_ptr, +// m_even, +// m_odd, +// iAcc, +// qAcc +// ); +//#else + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } +//#endif + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doInterpolateFIR(Sample* sample) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } +}; + +template +IntHalfbandFilterEO2::IntHalfbandFilterEO2() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0; + m_even[1][i] = 0; + m_odd[0][i] = 0; + m_odd[1][i] = 0; + m_samples[i][0] = 0; + m_samples[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ */ From 2252dcb06ada29a0bfd859ac986e9f898ce70fdd Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 29 Apr 2018 10:37:36 +0200 Subject: [PATCH 326/956] Do not use intrinsics at all for IntHalfbandFilterEO1 --- sdrbase/dsp/inthalfbandfiltereo1.h | 42 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/sdrbase/dsp/inthalfbandfiltereo1.h b/sdrbase/dsp/inthalfbandfiltereo1.h index b2b8f1793..7a1539afc 100644 --- a/sdrbase/dsp/inthalfbandfiltereo1.h +++ b/sdrbase/dsp/inthalfbandfiltereo1.h @@ -26,7 +26,7 @@ #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "dsp/inthalfbandfiltereo1i.h" +//#include "dsp/inthalfbandfiltereo1i.h" #include "export.h" template @@ -681,15 +681,15 @@ protected: int32_t iAcc = 0; int32_t qAcc = 0; -#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) - IntHalfbandFilterEO1Intrisics::work( - m_ptr, - m_even, - m_odd, - iAcc, - qAcc - ); -#else +//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) +// IntHalfbandFilterEO1Intrisics::work( +// m_ptr, +// m_even, +// m_odd, +// iAcc, +// qAcc +// ); +//#else int a = m_ptr/2 + m_size; // tip pointer int b = m_ptr/2 + 1; // tail pointer @@ -709,7 +709,7 @@ protected: a -= 1; b += 1; } -#endif +//#endif if ((m_ptr % 2) == 0) { @@ -731,15 +731,15 @@ protected: int32_t iAcc = 0; int32_t qAcc = 0; -#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) - IntHalfbandFilterEO1Intrisics::work( - m_ptr, - m_even, - m_odd, - iAcc, - qAcc - ); -#else +//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) +// IntHalfbandFilterEO1Intrisics::work( +// m_ptr, +// m_even, +// m_odd, +// iAcc, +// qAcc +// ); +//#else int a = m_ptr/2 + m_size; // tip pointer int b = m_ptr/2 + 1; // tail pointer @@ -759,7 +759,7 @@ protected: a -= 1; b += 1; } -#endif +//#endif if ((m_ptr % 2) == 0) { iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); From d735025c6c82d3aaa1af77a65ff4e2631cf5c14f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 29 Apr 2018 10:38:25 +0200 Subject: [PATCH 327/956] With global adoption of even/odd decimators the accu type must be 32 not 64 bits --- plugins/samplesource/airspy/airspythread.h | 2 +- .../bladerfinput/bladerfinputthread.h | 2 +- .../hackrfinput/hackrfinputthread.h | 2 +- .../limesdrinput/limesdrinputthread.h | 2 +- plugins/samplesource/perseus/perseusthread.h | 4 ++-- .../plutosdrinput/plutosdrinputthread.h | 2 +- plugins/samplesource/sdrplay/sdrplaythread.h | 2 +- .../testsource/testsourcethread.h | 6 +++--- sdrbase/dsp/inthalfbandfiltereo2.h | 19 +++++++++---------- sdrbench/CMakeLists.txt | 2 ++ sdrbench/mainbench.h | 2 +- 11 files changed, 23 insertions(+), 22 deletions(-) diff --git a/plugins/samplesource/airspy/airspythread.h b/plugins/samplesource/airspy/airspythread.h index b21c894d3..6abd2efd8 100644 --- a/plugins/samplesource/airspy/airspythread.h +++ b/plugins/samplesource/airspy/airspythread.h @@ -56,7 +56,7 @@ private: static AirspyThread *m_this; #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; + Decimators m_decimators; #else Decimators m_decimators; #endif diff --git a/plugins/samplesource/bladerfinput/bladerfinputthread.h b/plugins/samplesource/bladerfinput/bladerfinputthread.h index 8230c5c90..a6e0b29b0 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputthread.h +++ b/plugins/samplesource/bladerfinput/bladerfinputthread.h @@ -52,7 +52,7 @@ private: int m_fcPos; #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; + Decimators m_decimators; #else Decimators m_decimators; #endif diff --git a/plugins/samplesource/hackrfinput/hackrfinputthread.h b/plugins/samplesource/hackrfinput/hackrfinputthread.h index 32df49077..6cb81d1b4 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputthread.h +++ b/plugins/samplesource/hackrfinput/hackrfinputthread.h @@ -55,7 +55,7 @@ private: int m_fcPos; #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; + Decimators m_decimators; #else Decimators m_decimators; #endif diff --git a/plugins/samplesource/limesdrinput/limesdrinputthread.h b/plugins/samplesource/limesdrinput/limesdrinputthread.h index 096083ac8..f7b645b94 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputthread.h +++ b/plugins/samplesource/limesdrinput/limesdrinputthread.h @@ -56,7 +56,7 @@ private: unsigned int m_log2Decim; // soft decimation #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; + Decimators m_decimators; #else Decimators m_decimators; #endif diff --git a/plugins/samplesource/perseus/perseusthread.h b/plugins/samplesource/perseus/perseusthread.h index 46d75c649..0adfda1e8 100644 --- a/plugins/samplesource/perseus/perseusthread.h +++ b/plugins/samplesource/perseus/perseusthread.h @@ -52,8 +52,8 @@ private: unsigned int m_log2Decim; static PerseusThread *m_this; - Decimators, SDR_RX_SAMP_SZ, 24> m_decimators32; // for no decimation (accumulator is int32) - Decimators, SDR_RX_SAMP_SZ, 24> m_decimators64; // for actual decimation (accumulator is int64) + Decimators, SDR_RX_SAMP_SZ, 24> m_decimators32; // for no decimation (accumulator is int32) + Decimators, SDR_RX_SAMP_SZ, 24> m_decimators64; // for actual decimation (accumulator is int64) void run(); void callback(const uint8_t* buf, qint32 len); // inner call back diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputthread.h b/plugins/samplesource/plutosdrinput/plutosdrinputthread.h index e79630334..843175246 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputthread.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinputthread.h @@ -60,7 +60,7 @@ private: float m_phasor; #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; + Decimators m_decimators; #else Decimators m_decimators; #endif diff --git a/plugins/samplesource/sdrplay/sdrplaythread.h b/plugins/samplesource/sdrplay/sdrplaythread.h index 807db699c..1317ae57f 100644 --- a/plugins/samplesource/sdrplay/sdrplaythread.h +++ b/plugins/samplesource/sdrplay/sdrplaythread.h @@ -53,7 +53,7 @@ private: int m_fcPos; #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; + Decimators m_decimators; #else Decimators m_decimators; #endif diff --git a/plugins/samplesource/testsource/testsourcethread.h b/plugins/samplesource/testsource/testsourcethread.h index dbfd508a2..db9e4d3c8 100644 --- a/plugins/samplesource/testsource/testsourcethread.h +++ b/plugins/samplesource/testsource/testsourcethread.h @@ -100,9 +100,9 @@ private: QMutex m_mutex; #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators_8; - Decimators m_decimators_12; - Decimators m_decimators_16; + Decimators m_decimators_8; + Decimators m_decimators_12; + Decimators m_decimators_16; #else Decimators m_decimators_8; Decimators m_decimators_12; diff --git a/sdrbase/dsp/inthalfbandfiltereo2.h b/sdrbase/dsp/inthalfbandfiltereo2.h index 15619d1eb..72bf760d4 100644 --- a/sdrbase/dsp/inthalfbandfiltereo2.h +++ b/sdrbase/dsp/inthalfbandfiltereo2.h @@ -563,12 +563,12 @@ public: advancePointer(); } - void myDecimate(qint64 x1, qint64 y1, qint64 *x2, qint64 *y2) + void myDecimate(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2) { - storeSample64(x1, y1); + storeSample32(x1, y1); advancePointer(); - storeSample64(*x2, *y2); + storeSample32(*x2, *y2); doFIR(x2, y2); advancePointer(); } @@ -586,13 +586,13 @@ public: } /** Simple zero stuffing and filter */ - void myInterpolateZeroStuffing(qint64 *x1, qint64 *y1, qint64 *x2, qint64 *y2) + void myInterpolateZeroStuffing(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) { - storeSample64(*x1, *y1); + storeSample32(*x1, *y1); doFIR(x1, y1); advancePointer(); - storeSample64(0, 0); + storeSample32(0, 0); doFIR(x2, y2); advancePointer(); } @@ -622,8 +622,8 @@ public: } protected: - qint64 m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique - qint64 m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + qint64 m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique (qint32 to activate vectorization) + qint64 m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique int32_t m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique int m_ptr; @@ -705,7 +705,6 @@ protected: //#else int a = m_ptr/2 + m_size; // tip pointer int b = m_ptr/2 + 1; // tail pointer - for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) { if ((m_ptr % 2) == 0) @@ -739,7 +738,7 @@ protected: sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); } - void doFIR(qint64 *x, qint64 *y) + void doFIR(int32_t *x, int32_t *y) { qint64 iAcc = 0; qint64 qAcc = 0; diff --git a/sdrbench/CMakeLists.txt b/sdrbench/CMakeLists.txt index c51d83509..55c3b5449 100644 --- a/sdrbench/CMakeLists.txt +++ b/sdrbench/CMakeLists.txt @@ -1,5 +1,7 @@ project (sdrbench) +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopt-info-vec-optimized") + set(sdrbench_SOURCES mainbench.cpp parserbench.cpp diff --git a/sdrbench/mainbench.h b/sdrbench/mainbench.h index 67b78cf7c..4d5871bef 100644 --- a/sdrbench/mainbench.h +++ b/sdrbench/mainbench.h @@ -61,7 +61,7 @@ private: std::uniform_real_distribution m_uniform_distribution; #ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimatorsII; + Decimators m_decimatorsII; #else Decimators m_decimatorsII; #endif From 9c49be1313fb2130295d0dd939cefc0ccf0e250d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 29 Apr 2018 11:38:42 +0200 Subject: [PATCH 328/956] Removed intrinsics completely from IntHalfbandFilterEO2 --- sdrbase/dsp/inthalfbandfiltereo2.h | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/sdrbase/dsp/inthalfbandfiltereo2.h b/sdrbase/dsp/inthalfbandfiltereo2.h index 72bf760d4..be513419b 100644 --- a/sdrbase/dsp/inthalfbandfiltereo2.h +++ b/sdrbase/dsp/inthalfbandfiltereo2.h @@ -26,7 +26,6 @@ #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "dsp/inthalfbandfiltereo2i.h" #include "export.h" template @@ -622,7 +621,7 @@ public: } protected: - qint64 m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique (qint32 to activate vectorization) + qint64 m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique qint64 m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique int32_t m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique @@ -694,15 +693,6 @@ protected: qint64 iAcc = 0; qint64 qAcc = 0; -//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) -// IntHalfbandFilterEO2Intrisics::work( -// m_ptr, -// m_even, -// m_odd, -// iAcc, -// qAcc -// ); -//#else int a = m_ptr/2 + m_size; // tip pointer int b = m_ptr/2 + 1; // tail pointer for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) @@ -721,7 +711,6 @@ protected: a -= 1; b += 1; } -//#endif if ((m_ptr % 2) == 0) { @@ -743,15 +732,6 @@ protected: qint64 iAcc = 0; qint64 qAcc = 0; -//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) -// IntHalfbandFilterEO2Intrisics::work( -// m_ptr, -// m_even, -// m_odd, -// iAcc, -// qAcc -// ); -//#else int a = m_ptr/2 + m_size; // tip pointer int b = m_ptr/2 + 1; // tail pointer @@ -771,7 +751,7 @@ protected: a -= 1; b += 1; } -//#endif + if ((m_ptr % 2) == 0) { iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); From 1213ad2a71f833aedebb0cacb3b7e91c55b74551 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 29 Apr 2018 11:48:46 +0200 Subject: [PATCH 329/956] Simplified float halfband filters with unique class for floating point --- sdrbase/CMakeLists.txt | 3 +- sdrbase/dsp/decimatorsff.h | 14 +-- sdrbase/dsp/decimatorsfi.cpp | 2 +- sdrbase/dsp/decimatorsfi.h | 14 +-- sdrbase/dsp/inthalfbandfilterdbf.h | 146 +++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 sdrbase/dsp/inthalfbandfilterdbf.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 1ae66fa84..e42a8097b 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -127,8 +127,7 @@ set(sdrbase_HEADERS dsp/hbfiltertraits.h dsp/inthalfbandfilter.h dsp/inthalfbandfilterdb.h - dsp/inthalfbandfilterdbff.h - dsp/inthalfbandfilterdbfi.h + dsp/inthalfbandfilterdbf.h dsp/inthalfbandfiltereo1.h dsp/inthalfbandfiltereo1i.h dsp/inthalfbandfilterst.h diff --git a/sdrbase/dsp/decimatorsff.h b/sdrbase/dsp/decimatorsff.h index 649a0fc6c..92e9a9e96 100644 --- a/sdrbase/dsp/decimatorsff.h +++ b/sdrbase/dsp/decimatorsff.h @@ -17,7 +17,7 @@ #ifndef SDRBASE_DSP_DECIMATORSFF_H_ #define SDRBASE_DSP_DECIMATORSFF_H_ -#include "dsp/inthalfbandfilterdbff.h" +#include "dsp/inthalfbandfilterdbf.h" #include "export.h" #define DECIMATORSFF_HB_FILTER_ORDER 64 @@ -46,12 +46,12 @@ public: void decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); void decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); - IntHalfbandFilterDBFF m_decimator2; // 1st stages - IntHalfbandFilterDBFF m_decimator4; // 2nd stages - IntHalfbandFilterDBFF m_decimator8; // 3rd stages - IntHalfbandFilterDBFF m_decimator16; // 4th stages - IntHalfbandFilterDBFF m_decimator32; // 5th stages - IntHalfbandFilterDBFF m_decimator64; // 6th stages + IntHalfbandFilterDBF m_decimator2; // 1st stages + IntHalfbandFilterDBF m_decimator4; // 2nd stages + IntHalfbandFilterDBF m_decimator8; // 3rd stages + IntHalfbandFilterDBF m_decimator16; // 4th stages + IntHalfbandFilterDBF m_decimator32; // 5th stages + IntHalfbandFilterDBF m_decimator64; // 6th stages }; diff --git a/sdrbase/dsp/decimatorsfi.cpp b/sdrbase/dsp/decimatorsfi.cpp index aeda9ad03..95f2e47c3 100644 --- a/sdrbase/dsp/decimatorsfi.cpp +++ b/sdrbase/dsp/decimatorsfi.cpp @@ -14,7 +14,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include +#include "decimatorsfi.h" void DecimatorsFI::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { diff --git a/sdrbase/dsp/decimatorsfi.h b/sdrbase/dsp/decimatorsfi.h index 283cb6e0a..45f6ae2fe 100644 --- a/sdrbase/dsp/decimatorsfi.h +++ b/sdrbase/dsp/decimatorsfi.h @@ -17,7 +17,7 @@ #ifndef SDRBASE_DSP_DECIMATORSFI_H_ #define SDRBASE_DSP_DECIMATORSFI_H_ -#include "dsp/inthalfbandfilterdbfi.h" +#include "dsp/inthalfbandfilterdbf.h" #include "export.h" #define DECIMATORSFI_HB_FILTER_ORDER 64 @@ -46,12 +46,12 @@ public: void decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); void decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); - IntHalfbandFilterDBFI m_decimator2; // 1st stages - IntHalfbandFilterDBFI m_decimator4; // 2nd stages - IntHalfbandFilterDBFI m_decimator8; // 3rd stages - IntHalfbandFilterDBFI m_decimator16; // 4th stages - IntHalfbandFilterDBFI m_decimator32; // 5th stages - IntHalfbandFilterDBFI m_decimator64; // 6th stages + IntHalfbandFilterDBF m_decimator2; // 1st stages + IntHalfbandFilterDBF m_decimator4; // 2nd stages + IntHalfbandFilterDBF m_decimator8; // 3rd stages + IntHalfbandFilterDBF m_decimator16; // 4th stages + IntHalfbandFilterDBF m_decimator32; // 5th stages + IntHalfbandFilterDBF m_decimator64; // 6th stages }; diff --git a/sdrbase/dsp/inthalfbandfilterdbf.h b/sdrbase/dsp/inthalfbandfilterdbf.h new file mode 100644 index 000000000..938bf57d3 --- /dev/null +++ b/sdrbase/dsp/inthalfbandfilterdbf.h @@ -0,0 +1,146 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Float half-band FIR based interpolator and decimator // +// This is the double buffer variant // +// // +// 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_INTHALFBANDFILTER_DBF_H +#define INCLUDE_INTHALFBANDFILTER_DBF_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterDBF { +public: + IntHalfbandFilterDBF(); + + void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) + { + storeSample(x1, y1); + advancePointer(); + + storeSample(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = *x1; + m_samplesDB[m_ptr][1] = *y1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + SampleType m_samplesDB[2*(HBFIRFilterTraits::hbOrder - 1)][2]; // double buffer technique + int m_ptr; + int m_size; + int m_state; + + void storeSample(AccuType x, AccuType y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(AccuType *x, AccuType *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] / 2.0; + qAcc += m_samplesDB[b-1][1] / 2.0; + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc * SDR_RX_SCALED; + *y = qAcc * SDR_RX_SCALED; + } +}; + +template +IntHalfbandFilterDBF::IntHalfbandFilterDBF() +{ + m_size = HBFIRFilterTraits::hbOrder - 1; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif // INCLUDE_INTHALFBANDFILTER_DBF_H From 48cc6df8a769a2ae024ded5bd26504ddcfbc8e9a Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 29 Apr 2018 22:56:34 +0200 Subject: [PATCH 330/956] Floating point to integer decimator optimization using the even/odd algorithm --- sdrbase/CMakeLists.txt | 2 + sdrbase/dsp/decimatorsfi.cpp | 36 ++--- sdrbase/dsp/decimatorsfi.h | 14 +- sdrbase/dsp/inthalfbandfiltereof.h | 231 +++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 25 deletions(-) create mode 100644 sdrbase/dsp/inthalfbandfiltereof.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index e42a8097b..7647c6a1b 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -130,6 +130,8 @@ set(sdrbase_HEADERS dsp/inthalfbandfilterdbf.h dsp/inthalfbandfiltereo1.h dsp/inthalfbandfiltereo1i.h + dsp/inthalfbandfiltereo2.h + dsp/inthalfbandfiltereof.h dsp/inthalfbandfilterst.h dsp/inthalfbandfiltersti.h dsp/kissfft.h diff --git a/sdrbase/dsp/decimatorsfi.cpp b/sdrbase/dsp/decimatorsfi.cpp index 95f2e47c3..e4e2d8451 100644 --- a/sdrbase/dsp/decimatorsfi.cpp +++ b/sdrbase/dsp/decimatorsfi.cpp @@ -32,7 +32,7 @@ void DecimatorsFI::decimate1(SampleVector::iterator* it, const float* buf, qint3 void DecimatorsFI::decimate2_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[2]; + float intbuf[2]; for (int pos = 0; pos < nbIAndQ - 3; pos += 4) { @@ -54,7 +54,7 @@ void DecimatorsFI::decimate2_cen(SampleVector::iterator* it, const float* buf, q void DecimatorsFI::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -74,7 +74,7 @@ void DecimatorsFI::decimate2_inf(SampleVector::iterator* it, const float* buf, q void DecimatorsFI::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -94,7 +94,7 @@ void DecimatorsFI::decimate2_sup(SampleVector::iterator* it, const float* buf, q void DecimatorsFI::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -116,7 +116,7 @@ void DecimatorsFI::decimate4_sup(SampleVector::iterator* it, const float* buf, q // Inf (LSB): // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -132,7 +132,7 @@ void DecimatorsFI::decimate4_sup(SampleVector::iterator* it, const float* buf, q void DecimatorsFI::decimate8_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[2], yimag[2]; + float xreal[2], yimag[2]; for (int pos = 0; pos < nbIAndQ - 15; pos += 8) { @@ -154,7 +154,7 @@ void DecimatorsFI::decimate8_inf(SampleVector::iterator* it, const float* buf, q void DecimatorsFI::decimate8_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[2], yimag[2]; + float xreal[2], yimag[2]; for (int pos = 0; pos < nbIAndQ - 15; pos += 8) { @@ -178,7 +178,7 @@ void DecimatorsFI::decimate16_inf(SampleVector::iterator* it, const float* buf, { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - double xreal[4], yimag[4]; + float xreal[4], yimag[4]; for (int pos = 0; pos < nbIAndQ - 31; ) { @@ -205,7 +205,7 @@ void DecimatorsFI::decimate16_sup(SampleVector::iterator* it, const float* buf, { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - double xreal[4], yimag[4]; + float xreal[4], yimag[4]; for (int pos = 0; pos < nbIAndQ - 31; ) { @@ -230,7 +230,7 @@ void DecimatorsFI::decimate16_sup(SampleVector::iterator* it, const float* buf, void DecimatorsFI::decimate32_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[8], yimag[8]; + float xreal[8], yimag[8]; for (int pos = 0; pos < nbIAndQ - 63; ) { @@ -260,7 +260,7 @@ void DecimatorsFI::decimate32_inf(SampleVector::iterator* it, const float* buf, void DecimatorsFI::decimate32_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[8], yimag[8]; + float xreal[8], yimag[8]; for (int pos = 0; pos < nbIAndQ - 63; ) { @@ -290,7 +290,7 @@ void DecimatorsFI::decimate32_sup(SampleVector::iterator* it, const float* buf, void DecimatorsFI::decimate64_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[16], yimag[16]; + float xreal[16], yimag[16]; for (int pos = 0; pos < nbIAndQ - 127; ) { @@ -329,7 +329,7 @@ void DecimatorsFI::decimate64_inf(SampleVector::iterator* it, const float* buf, void DecimatorsFI::decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[16], yimag[16]; + float xreal[16], yimag[16]; for (int pos = 0; pos < nbIAndQ - 127; ) { @@ -368,7 +368,7 @@ void DecimatorsFI::decimate64_sup(SampleVector::iterator* it, const float* buf, void DecimatorsFI::decimate4_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[4]; + float intbuf[4]; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -402,7 +402,7 @@ void DecimatorsFI::decimate4_cen(SampleVector::iterator* it, const float* buf, q void DecimatorsFI::decimate8_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[8]; + float intbuf[8]; for (int pos = 0; pos < nbIAndQ - 15; pos += 16) { @@ -461,7 +461,7 @@ void DecimatorsFI::decimate8_cen(SampleVector::iterator* it, const float* buf, q void DecimatorsFI::decimate16_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[16]; + float intbuf[16]; for (int pos = 0; pos < nbIAndQ - 31; pos += 32) { @@ -569,7 +569,7 @@ void DecimatorsFI::decimate16_cen(SampleVector::iterator* it, const float* buf, void DecimatorsFI::decimate32_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[32]; + float intbuf[32]; for (int pos = 0; pos < nbIAndQ - 63; pos += 64) { @@ -774,7 +774,7 @@ void DecimatorsFI::decimate32_cen(SampleVector::iterator* it, const float* buf, void DecimatorsFI::decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[64]; + float intbuf[64]; for (int pos = 0; pos < nbIAndQ - 127; pos += 128) { diff --git a/sdrbase/dsp/decimatorsfi.h b/sdrbase/dsp/decimatorsfi.h index 45f6ae2fe..cfeb85a82 100644 --- a/sdrbase/dsp/decimatorsfi.h +++ b/sdrbase/dsp/decimatorsfi.h @@ -17,7 +17,7 @@ #ifndef SDRBASE_DSP_DECIMATORSFI_H_ #define SDRBASE_DSP_DECIMATORSFI_H_ -#include "dsp/inthalfbandfilterdbf.h" +#include "dsp/inthalfbandfiltereof.h" #include "export.h" #define DECIMATORSFI_HB_FILTER_ORDER 64 @@ -46,12 +46,12 @@ public: void decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); void decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); - IntHalfbandFilterDBF m_decimator2; // 1st stages - IntHalfbandFilterDBF m_decimator4; // 2nd stages - IntHalfbandFilterDBF m_decimator8; // 3rd stages - IntHalfbandFilterDBF m_decimator16; // 4th stages - IntHalfbandFilterDBF m_decimator32; // 5th stages - IntHalfbandFilterDBF m_decimator64; // 6th stages + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages }; diff --git a/sdrbase/dsp/inthalfbandfiltereof.h b/sdrbase/dsp/inthalfbandfiltereof.h new file mode 100644 index 000000000..1afa92fb4 --- /dev/null +++ b/sdrbase/dsp/inthalfbandfiltereof.h @@ -0,0 +1,231 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// 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 SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterEOF { +public: + IntHalfbandFilterEOF(); + + bool workDecimateCenter(float *x, float *y) + { + // insert sample into ring-buffer + storeSample(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + void myDecimate(float x1, float y1, float *x2, float *y2) + { + storeSample(x1, y1); + advancePointer(); + + storeSample(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(float *x1, float *y1, float *x2, float *y2) + { + storeSample(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(float *x1, float *y1, float *x2, float *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + double m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + double m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + double m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique + + int m_ptr; + int m_size; + int m_state; + + void storeSample(float x, float y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(float *x, float *y) + { + double iAcc = 0; + double qAcc = 0; + +//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) +// IntHalfbandFilterEO1Intrisics::work( +// m_ptr, +// m_even, +// m_odd, +// iAcc, +// qAcc +// ); +//#else + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + } + + a -= 1; + b += 1; + } +//#endif + if ((m_ptr % 2) == 0) + { + iAcc += m_odd[0][m_ptr/2 + m_size/2] * 0.5f; + qAcc += m_odd[1][m_ptr/2 + m_size/2] * 0.5f; + } + else + { + iAcc += m_even[0][m_ptr/2 + m_size/2 + 1] * 0.5f; + qAcc += m_even[1][m_ptr/2 + m_size/2 + 1] * 0.5f; + } + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(float *x, float *y) + { + qint32 iAcc = 0; + qint32 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc * SDR_RX_SCALED; + *y = qAcc * SDR_RX_SCALED; + } +}; + +template +IntHalfbandFilterEOF::IntHalfbandFilterEOF() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0.0f; + m_even[1][i] = 0.0f; + m_odd[0][i] = 0.0f; + m_odd[1][i] = 0.0f; + m_samples[i][0] = 0.0f; + m_samples[i][1] = 0.0f; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ */ From efa168ec7715e378ae6c3668cf8d5ee893540dbe Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 30 Apr 2018 11:08:08 +0200 Subject: [PATCH 331/956] Floating point to floating point decimator optimization using the even/odd algorithm --- sdrbase/dsp/decimatorsff.cpp | 36 ++++++++++++++++++------------------ sdrbase/dsp/decimatorsff.h | 14 +++++++------- sdrbench/mainbench.cpp | 27 +++++++++++++++------------ sdrbench/mainbench.h | 3 ++- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/sdrbase/dsp/decimatorsff.cpp b/sdrbase/dsp/decimatorsff.cpp index 0134865e8..dc7afbcb0 100644 --- a/sdrbase/dsp/decimatorsff.cpp +++ b/sdrbase/dsp/decimatorsff.cpp @@ -32,7 +32,7 @@ void DecimatorsFF::decimate1(FSampleVector::iterator* it, const float* buf, qint void DecimatorsFF::decimate2_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[2]; + float intbuf[2]; for (int pos = 0; pos < nbIAndQ - 3; pos += 4) { @@ -54,7 +54,7 @@ void DecimatorsFF::decimate2_cen(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -74,7 +74,7 @@ void DecimatorsFF::decimate2_inf(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -94,7 +94,7 @@ void DecimatorsFF::decimate2_sup(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -116,7 +116,7 @@ void DecimatorsFF::decimate4_sup(FSampleVector::iterator* it, const float* buf, // Inf (LSB): // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -132,7 +132,7 @@ void DecimatorsFF::decimate4_sup(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate8_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[2], yimag[2]; + float xreal[2], yimag[2]; for (int pos = 0; pos < nbIAndQ - 15; pos += 8) { @@ -154,7 +154,7 @@ void DecimatorsFF::decimate8_inf(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate8_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[2], yimag[2]; + float xreal[2], yimag[2]; for (int pos = 0; pos < nbIAndQ - 15; pos += 8) { @@ -178,7 +178,7 @@ void DecimatorsFF::decimate16_inf(FSampleVector::iterator* it, const float* buf, { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - double xreal[4], yimag[4]; + float xreal[4], yimag[4]; for (int pos = 0; pos < nbIAndQ - 31; ) { @@ -205,7 +205,7 @@ void DecimatorsFF::decimate16_sup(FSampleVector::iterator* it, const float* buf, { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - double xreal[4], yimag[4]; + float xreal[4], yimag[4]; for (int pos = 0; pos < nbIAndQ - 31; ) { @@ -230,7 +230,7 @@ void DecimatorsFF::decimate16_sup(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate32_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[8], yimag[8]; + float xreal[8], yimag[8]; for (int pos = 0; pos < nbIAndQ - 63; ) { @@ -260,7 +260,7 @@ void DecimatorsFF::decimate32_inf(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate32_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[8], yimag[8]; + float xreal[8], yimag[8]; for (int pos = 0; pos < nbIAndQ - 63; ) { @@ -290,7 +290,7 @@ void DecimatorsFF::decimate32_sup(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate64_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[16], yimag[16]; + float xreal[16], yimag[16]; for (int pos = 0; pos < nbIAndQ - 127; ) { @@ -329,7 +329,7 @@ void DecimatorsFF::decimate64_inf(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[16], yimag[16]; + float xreal[16], yimag[16]; for (int pos = 0; pos < nbIAndQ - 127; ) { @@ -368,7 +368,7 @@ void DecimatorsFF::decimate64_sup(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate4_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[4]; + float intbuf[4]; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -402,7 +402,7 @@ void DecimatorsFF::decimate4_cen(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate8_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[8]; + float intbuf[8]; for (int pos = 0; pos < nbIAndQ - 15; pos += 16) { @@ -461,7 +461,7 @@ void DecimatorsFF::decimate8_cen(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate16_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[16]; + float intbuf[16]; for (int pos = 0; pos < nbIAndQ - 31; pos += 32) { @@ -569,7 +569,7 @@ void DecimatorsFF::decimate16_cen(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate32_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[32]; + float intbuf[32]; for (int pos = 0; pos < nbIAndQ - 63; pos += 64) { @@ -774,7 +774,7 @@ void DecimatorsFF::decimate32_cen(FSampleVector::iterator* it, const float* buf, void DecimatorsFF::decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[64]; + float intbuf[64]; for (int pos = 0; pos < nbIAndQ - 127; pos += 128) { diff --git a/sdrbase/dsp/decimatorsff.h b/sdrbase/dsp/decimatorsff.h index 92e9a9e96..ca491ee58 100644 --- a/sdrbase/dsp/decimatorsff.h +++ b/sdrbase/dsp/decimatorsff.h @@ -17,7 +17,7 @@ #ifndef SDRBASE_DSP_DECIMATORSFF_H_ #define SDRBASE_DSP_DECIMATORSFF_H_ -#include "dsp/inthalfbandfilterdbf.h" +#include "dsp/inthalfbandfiltereof.h" #include "export.h" #define DECIMATORSFF_HB_FILTER_ORDER 64 @@ -46,12 +46,12 @@ public: void decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); void decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); - IntHalfbandFilterDBF m_decimator2; // 1st stages - IntHalfbandFilterDBF m_decimator4; // 2nd stages - IntHalfbandFilterDBF m_decimator8; // 3rd stages - IntHalfbandFilterDBF m_decimator16; // 4th stages - IntHalfbandFilterDBF m_decimator32; // 5th stages - IntHalfbandFilterDBF m_decimator64; // 6th stages + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages }; diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index 43590c8c4..82605a5eb 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -27,7 +27,8 @@ MainBench::MainBench(qtwebapp::LoggerWithFile *logger, const ParserBench& parser QObject(parent), m_logger(logger), m_parser(parser), - m_uniform_distribution(-1.0, 1.0) + m_uniform_distribution_f(-1.0, 1.0), + m_uniform_distribution_s16(-32768,32767) { qDebug() << "MainBench::MainBench: start"; m_instance = this; @@ -62,22 +63,24 @@ void MainBench::run() void MainBench::testDecimateII() { QElapsedTimer timer; - qint64 nsecs; + qint64 nsecs = 0; qDebug() << "MainBench::testDecimateII: create test data"; qint16 *buf = new qint16[m_parser.getNbSamples()*2]; m_convertBuffer.resize(m_parser.getNbSamples()/(1< m_uniform_distribution; + std::uniform_real_distribution m_uniform_distribution_f; + std::uniform_int_distribution m_uniform_distribution_s16; #ifdef SDR_RX_SAMPLE_24BIT Decimators m_decimatorsII; From a81e2f297a122d046392c04b508fa47611af6453 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 1 May 2018 19:49:47 +0200 Subject: [PATCH 332/956] Benchmarking: added int to float decimation --- sdrbase/CMakeLists.txt | 2 + sdrbase/dsp/decimatorsif.cpp | 22 + sdrbase/dsp/decimatorsif.h | 1249 ++++++++++++++++++++++++++++ sdrbase/dsp/hbfiltertraits.cpp | 2 +- sdrbase/dsp/hbfiltertraits.h | 2 +- sdrbase/dsp/inthalfbandfiltereof.h | 10 +- sdrbench/mainbench.cpp | 63 +- sdrbench/mainbench.h | 4 + sdrbench/parserbench.cpp | 2 + sdrbench/parserbench.h | 1 + 10 files changed, 1349 insertions(+), 8 deletions(-) create mode 100644 sdrbase/dsp/decimatorsif.cpp create mode 100644 sdrbase/dsp/decimatorsif.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 7647c6a1b..050c289aa 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -21,6 +21,7 @@ set(sdrbase_SOURCES dsp/ctcssdetector.cpp dsp/cwkeyer.cpp dsp/cwkeyersettings.cpp + dsp/decimatorsif.cpp dsp/decimatorsff.cpp dsp/decimatorsfi.cpp dsp/dspcommands.cpp @@ -105,6 +106,7 @@ set(sdrbase_HEADERS dsp/cwkeyer.h dsp/cwkeyersettings.h dsp/decimators.h + dsp/decimatorsif.h dsp/decimatorsff.h dsp/decimatorsfi.h dsp/decimatorsu.h diff --git a/sdrbase/dsp/decimatorsif.cpp b/sdrbase/dsp/decimatorsif.cpp new file mode 100644 index 000000000..302669d68 --- /dev/null +++ b/sdrbase/dsp/decimatorsif.cpp @@ -0,0 +1,22 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "decimatorsif.h" + +const float decimation_scale<8>::scaleIn = 1.0/128.0; +const float decimation_scale<12>::scaleIn = 1.0/2048.0; +const float decimation_scale<16>::scaleIn = 1.0/32768.0; + diff --git a/sdrbase/dsp/decimatorsif.h b/sdrbase/dsp/decimatorsif.h new file mode 100644 index 000000000..7cd72e1e4 --- /dev/null +++ b/sdrbase/dsp/decimatorsif.h @@ -0,0 +1,1249 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 SDRBASE_DSP_DECIMATORSIF_H_ +#define SDRBASE_DSP_DECIMATORSIF_H_ + +#include "dsp/dsptypes.h" +#include "dsp/inthalfbandfiltereof.h" + +#define DECIMATORS_IF_FILTER_ORDER 64 + +template +struct decimation_scale +{ + static const float scaleIn; +}; + +template +const float decimation_scale::scaleIn = 1.0; + +template<> +struct decimation_scale<8> +{ + static const float scaleIn; +}; + +template<> +struct decimation_scale<12> +{ + static const float scaleIn; +}; + +template<> +struct decimation_scale<16> +{ + static const float scaleIn; +}; + + +template +class DecimatorsIF { +public: + // interleaved I/Q input buffer + void decimate1(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages +}; + +template +void DecimatorsIF::decimate1(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = buf[pos+0] * decimation_scale::scaleIn; + yimag = buf[pos+1] * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template +void DecimatorsIF::decimate2_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3]) * decimation_scale::scaleIn; + yimag = (buf[pos+1] + buf[pos+2]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+7] - buf[pos+4]) * decimation_scale::scaleIn; + yimag = (- buf[pos+5] - buf[pos+6]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template +void DecimatorsIF::decimate2_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2]) * decimation_scale::scaleIn; + yimag = (- buf[pos+0] - buf[pos+3]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+6] - buf[pos+5]) * decimation_scale::scaleIn; + yimag = (buf[pos+4] + buf[pos+7]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template +void DecimatorsIF::decimate2_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[2]; + + for (int pos = 0; pos < nbIAndQ - 3; pos += 4) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + + (**it).setReal(intbuf[0] * decimation_scale::scaleIn); + (**it).setImag(intbuf[1] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) * decimation_scale::scaleIn; + yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) * decimation_scale::scaleIn; + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) * decimation_scale::scaleIn; + yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) * decimation_scale::scaleIn; + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[4]; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + + (**it).setReal(intbuf[2] * decimation_scale::scaleIn); + (**it).setImag(intbuf[3] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + + xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(xreal[1] * decimation_scale::scaleIn); + (**it).setImag(yimag[1] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + pos += 8; + + xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(xreal[1] * decimation_scale::scaleIn); + (**it).setImag(yimag[1] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[8]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 16) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6] * decimation_scale::scaleIn); + (**it).setImag(intbuf[7] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(xreal[3] * decimation_scale::scaleIn); + (**it).setImag(yimag[3] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(xreal[3] * decimation_scale::scaleIn); + (**it).setImag(yimag[3] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[16]; + + for (int pos = 0; pos < nbIAndQ - 31; pos += 32) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14] * decimation_scale::scaleIn); + (**it).setImag(intbuf[15] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(xreal[7] * decimation_scale::scaleIn); + (**it).setImag(yimag[7] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(xreal[7] * decimation_scale::scaleIn); + (**it).setImag(yimag[7] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[32]; + + for (int pos = 0; pos < nbIAndQ - 63; pos += 64) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30] * decimation_scale::scaleIn); + (**it).setImag(intbuf[31] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15] * decimation_scale::scaleIn); + (**it).setImag(yimag[15] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15] * decimation_scale::scaleIn); + (**it).setImag(yimag[15] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[64]; + + for (int pos = 0; pos < nbIAndQ - 127; pos += 128) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + intbuf[32] = buf[pos+66]; + intbuf[33] = buf[pos+67]; + intbuf[34] = buf[pos+70]; + intbuf[35] = buf[pos+71]; + intbuf[36] = buf[pos+74]; + intbuf[37] = buf[pos+75]; + intbuf[38] = buf[pos+78]; + intbuf[39] = buf[pos+79]; + intbuf[40] = buf[pos+82]; + intbuf[41] = buf[pos+83]; + intbuf[42] = buf[pos+86]; + intbuf[43] = buf[pos+87]; + intbuf[44] = buf[pos+90]; + intbuf[45] = buf[pos+91]; + intbuf[46] = buf[pos+94]; + intbuf[47] = buf[pos+95]; + intbuf[48] = buf[pos+98]; + intbuf[49] = buf[pos+99]; + intbuf[50] = buf[pos+102]; + intbuf[51] = buf[pos+103]; + intbuf[52] = buf[pos+106]; + intbuf[53] = buf[pos+107]; + intbuf[54] = buf[pos+110]; + intbuf[55] = buf[pos+111]; + intbuf[56] = buf[pos+114]; + intbuf[57] = buf[pos+115]; + intbuf[58] = buf[pos+118]; + intbuf[59] = buf[pos+119]; + intbuf[60] = buf[pos+122]; + intbuf[61] = buf[pos+123]; + intbuf[62] = buf[pos+126]; + intbuf[63] = buf[pos+127]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + buf[pos+64], + buf[pos+65], + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + buf[pos+68], + buf[pos+69], + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + buf[pos+72], + buf[pos+73], + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + buf[pos+76], + buf[pos+77], + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + buf[pos+80], + buf[pos+81], + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + buf[pos+84], + buf[pos+85], + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + buf[pos+88], + buf[pos+89], + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + buf[pos+92], + buf[pos+93], + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + buf[pos+96], + buf[pos+97], + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + buf[pos+100], + buf[pos+101], + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + buf[pos+104], + buf[pos+105], + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + buf[pos+108], + buf[pos+109], + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + buf[pos+112], + buf[pos+113], + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + buf[pos+116], + buf[pos+117], + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + buf[pos+120], + buf[pos+121], + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + buf[pos+124], + buf[pos+125], + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62] * decimation_scale::scaleIn); + (**it).setImag(intbuf[63] * decimation_scale::scaleIn); + ++(*it); + } +} + +#endif /* SDRBASE_DSP_DECIMATORSIF_H_ */ diff --git a/sdrbase/dsp/hbfiltertraits.cpp b/sdrbase/dsp/hbfiltertraits.cpp index 8f82c1e36..ef8da3aa1 100644 --- a/sdrbase/dsp/hbfiltertraits.cpp +++ b/sdrbase/dsp/hbfiltertraits.cpp @@ -170,7 +170,7 @@ const int32_t HBFIRFilterTraits<64>::hbCoeffs[16] = { // (qint32)( 0.317657589850154464805598308885237202048 * (1 << hbShift)), }; -const double HBFIRFilterTraits<64>::hbCoeffsF[16] = { +const float HBFIRFilterTraits<64>::hbCoeffsF[16] = { -0.0004653050334792540416659067936677729449, 0.0007120490624526883919470643391491648799, -0.0012303473710125558716887983479182366864, diff --git a/sdrbase/dsp/hbfiltertraits.h b/sdrbase/dsp/hbfiltertraits.h index 5d3136604..9232c9a73 100644 --- a/sdrbase/dsp/hbfiltertraits.h +++ b/sdrbase/dsp/hbfiltertraits.h @@ -69,7 +69,7 @@ struct SDRBASE_API HBFIRFilterTraits<64> static const int32_t hbShift = 12; static const int16_t hbMod[64+6]; static const int32_t hbCoeffs[16] __attribute__ ((aligned (32))); - static const double hbCoeffsF[16]; + static const float hbCoeffsF[16]; }; template<> diff --git a/sdrbase/dsp/inthalfbandfiltereof.h b/sdrbase/dsp/inthalfbandfiltereof.h index 1afa92fb4..09f188e0a 100644 --- a/sdrbase/dsp/inthalfbandfiltereof.h +++ b/sdrbase/dsp/inthalfbandfiltereof.h @@ -107,9 +107,9 @@ public: } protected: - double m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique - double m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique - double m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique + float m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + float m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + float m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique int m_ptr; int m_size; @@ -140,8 +140,8 @@ protected: void doFIR(float *x, float *y) { - double iAcc = 0; - double qAcc = 0; + float iAcc = 0; + float qAcc = 0; //#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) // IntHalfbandFilterEO1Intrisics::work( diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index 82605a5eb..8e26c5a51 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -28,7 +28,7 @@ MainBench::MainBench(qtwebapp::LoggerWithFile *logger, const ParserBench& parser m_logger(logger), m_parser(parser), m_uniform_distribution_f(-1.0, 1.0), - m_uniform_distribution_s16(-32768,32767) + m_uniform_distribution_s16(-2048, 2047) { qDebug() << "MainBench::MainBench: start"; m_instance = this; @@ -49,6 +49,8 @@ void MainBench::run() if (m_parser.getTestType() == ParserBench::TestDecimatorsII) { testDecimateII(); + } else if (m_parser.getTestType() == ParserBench::TestDecimatorsIF) { + testDecimateIF(); } else if (m_parser.getTestType() == ParserBench::TestDecimatorsFI) { testDecimateFI(); } else if (m_parser.getTestType() == ParserBench::TestDecimatorsFF) { @@ -87,6 +89,33 @@ void MainBench::testDecimateII() delete[] buf; } +void MainBench::testDecimateIF() +{ + QElapsedTimer timer; + qint64 nsecs = 0; + + qDebug() << "MainBench::testDecimateIF: create test data"; + + qint16 *buf = new qint16[m_parser.getNbSamples()*2]; + m_convertBufferF.resize(m_parser.getNbSamples()/(1< #include "dsp/decimators.h" +#include "dsp/decimatorsif.h" #include "dsp/decimatorsfi.h" #include "dsp/decimatorsff.h" #include "parserbench.h" @@ -47,9 +48,11 @@ signals: private: void testDecimateII(); + void testDecimateIF(); void testDecimateFI(); void testDecimateFF(); void decimateII(const qint16 *buf, int len); + void decimateIF(const qint16 *buf, int len); void decimateFI(const float *buf, int len); void decimateFF(const float *buf, int len); void printResults(const QString& prefix, qint64 nsecs); @@ -66,6 +69,7 @@ private: #else Decimators m_decimatorsII; #endif + DecimatorsIF m_decimatorsIF; DecimatorsFI m_decimatorsFI; DecimatorsFF m_decimatorsFF; diff --git a/sdrbench/parserbench.cpp b/sdrbench/parserbench.cpp index cd0a2fe0d..9c8444008 100644 --- a/sdrbench/parserbench.cpp +++ b/sdrbench/parserbench.cpp @@ -118,6 +118,8 @@ ParserBench::TestType ParserBench::getTestType() const return TestDecimatorsFI; } else if (m_testStr == "decimateff") { return TestDecimatorsFF; + }else if (m_testStr == "decimateif") { + return TestDecimatorsIF; } else { return TestDecimatorsII; } diff --git a/sdrbench/parserbench.h b/sdrbench/parserbench.h index 10a930a3a..473f54bc4 100644 --- a/sdrbench/parserbench.h +++ b/sdrbench/parserbench.h @@ -27,6 +27,7 @@ public: typedef enum { TestDecimatorsII, + TestDecimatorsIF, TestDecimatorsFI, TestDecimatorsFF } TestType; From 4924e3edbd59ea75e3554e93afbd28d84b6a7112 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 1 May 2018 22:02:30 +0200 Subject: [PATCH 333/956] Down channelizer optimization: use even/odd technique halfband filter --- sdrbase/dsp/downchannelizer.cpp | 64 +++++++++++---------------------- sdrbase/dsp/downchannelizer.h | 25 +++++-------- 2 files changed, 30 insertions(+), 59 deletions(-) diff --git a/sdrbase/dsp/downchannelizer.cpp b/sdrbase/dsp/downchannelizer.cpp index 56dc25807..713748af6 100644 --- a/sdrbase/dsp/downchannelizer.cpp +++ b/sdrbase/dsp/downchannelizer.cpp @@ -190,70 +190,48 @@ void DownChannelizer::applyConfiguration() #ifdef SDR_RX_SAMPLE_24BIT DownChannelizer::FilterStage::FilterStage(Mode mode) : - m_filter(new IntHalfbandFilterDB), + m_filter(new IntHalfbandFilterEO2), m_workFunction(0), m_mode(mode), - m_sse(false) + m_sse(true) { switch(mode) { case ModeCenter: - m_workFunction = &IntHalfbandFilterDB::workDecimateCenter; + m_workFunction = &IntHalfbandFilterEO2::workDecimateCenter; break; case ModeLowerHalf: - m_workFunction = &IntHalfbandFilterDB::workDecimateLowerHalf; + m_workFunction = &IntHalfbandFilterEO2::workDecimateLowerHalf; break; case ModeUpperHalf: - m_workFunction = &IntHalfbandFilterDB::workDecimateUpperHalf; + m_workFunction = &IntHalfbandFilterEO2::workDecimateUpperHalf; break; } } #else -#ifdef USE_SSE4_1 DownChannelizer::FilterStage::FilterStage(Mode mode) : - m_filter(new IntHalfbandFilterEO1), - m_workFunction(0), - m_mode(mode), - m_sse(true) + m_filter(new IntHalfbandFilterEO1), + m_workFunction(0), + m_mode(mode), + m_sse(true) { - switch(mode) { - case ModeCenter: - m_workFunction = &IntHalfbandFilterEO1::workDecimateCenter; - break; + switch(mode) { + case ModeCenter: + m_workFunction = &IntHalfbandFilterEO1::workDecimateCenter; + break; - case ModeLowerHalf: - m_workFunction = &IntHalfbandFilterEO1::workDecimateLowerHalf; - break; + case ModeLowerHalf: + m_workFunction = &IntHalfbandFilterEO1::workDecimateLowerHalf; + break; - case ModeUpperHalf: - m_workFunction = &IntHalfbandFilterEO1::workDecimateUpperHalf; - break; - } -} -#else -DownChannelizer::FilterStage::FilterStage(Mode mode) : - m_filter(new IntHalfbandFilterDB), - m_workFunction(0), - m_mode(mode), - m_sse(false) -{ - switch(mode) { - case ModeCenter: - m_workFunction = &IntHalfbandFilterDB::workDecimateCenter; - break; - - case ModeLowerHalf: - m_workFunction = &IntHalfbandFilterDB::workDecimateLowerHalf; - break; - - case ModeUpperHalf: - m_workFunction = &IntHalfbandFilterDB::workDecimateUpperHalf; - break; - } + case ModeUpperHalf: + m_workFunction = &IntHalfbandFilterEO1::workDecimateUpperHalf; + break; + } } #endif -#endif + DownChannelizer::FilterStage::~FilterStage() { delete m_filter; diff --git a/sdrbase/dsp/downchannelizer.h b/sdrbase/dsp/downchannelizer.h index afecca973..86c21db27 100644 --- a/sdrbase/dsp/downchannelizer.h +++ b/sdrbase/dsp/downchannelizer.h @@ -23,15 +23,12 @@ #include #include "export.h" #include "util/message.h" + #ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfilterdb.h" -#else -#ifdef USE_SSE4_1 +#include "dsp/inthalfbandfiltereo2.h" +#else // SDR_RX_SAMPLE_24BIT #include "dsp/inthalfbandfiltereo1.h" -#else -#include "dsp/inthalfbandfilterdb.h" -#endif -#endif +#endif // SDR_RX_SAMPLE_24BIT #define DOWNCHANNELIZER_HB_FILTER_ORDER 48 @@ -84,17 +81,13 @@ protected: }; #ifdef SDR_RX_SAMPLE_24BIT - typedef bool (IntHalfbandFilterDB::*WorkFunction)(Sample* s); - IntHalfbandFilterDB* m_filter; + typedef bool (IntHalfbandFilterEO2::*WorkFunction)(Sample* s); + IntHalfbandFilterEO2* m_filter; #else -#ifdef USE_SSE4_1 - typedef bool (IntHalfbandFilterEO1::*WorkFunction)(Sample* s); - IntHalfbandFilterEO1* m_filter; -#else - typedef bool (IntHalfbandFilterDB::*WorkFunction)(Sample* s); - IntHalfbandFilterDB* m_filter; -#endif + typedef bool (IntHalfbandFilterEO1::*WorkFunction)(Sample* s); + IntHalfbandFilterEO1* m_filter; #endif + WorkFunction m_workFunction; Mode m_mode; bool m_sse; From 19c32b43545190fcfb84fa495be83e9b71c9d78f Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 1 May 2018 23:57:12 +0200 Subject: [PATCH 334/956] Removed 24/16 bit differentiation on Decimator instantiation where possible --- plugins/samplesource/airspy/airspythread.h | 4 - .../bladerfinput/bladerfinputthread.h | 4 - .../hackrfinput/hackrfinputthread.h | 4 - .../limesdrinput/limesdrinputthread.h | 4 - .../plutosdrinput/plutosdrinputthread.h | 4 - plugins/samplesource/sdrplay/sdrplaythread.h | 4 - .../testsource/testsourcethread.h | 6 - sdrbase/dsp/decimators.h | 238 +++++++++--------- sdrbench/mainbench.h | 4 - 9 files changed, 119 insertions(+), 153 deletions(-) diff --git a/plugins/samplesource/airspy/airspythread.h b/plugins/samplesource/airspy/airspythread.h index 6abd2efd8..48511b18a 100644 --- a/plugins/samplesource/airspy/airspythread.h +++ b/plugins/samplesource/airspy/airspythread.h @@ -55,11 +55,7 @@ private: int m_fcPos; static AirspyThread *m_this; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/bladerfinput/bladerfinputthread.h b/plugins/samplesource/bladerfinput/bladerfinputthread.h index a6e0b29b0..8c2766166 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputthread.h +++ b/plugins/samplesource/bladerfinput/bladerfinputthread.h @@ -51,11 +51,7 @@ private: unsigned int m_log2Decim; int m_fcPos; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/hackrfinput/hackrfinputthread.h b/plugins/samplesource/hackrfinput/hackrfinputthread.h index 6cb81d1b4..cf93c4e92 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputthread.h +++ b/plugins/samplesource/hackrfinput/hackrfinputthread.h @@ -54,11 +54,7 @@ private: unsigned int m_log2Decim; int m_fcPos; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void callback(const qint8* buf, qint32 len); diff --git a/plugins/samplesource/limesdrinput/limesdrinputthread.h b/plugins/samplesource/limesdrinput/limesdrinputthread.h index f7b645b94..ddf671047 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputthread.h +++ b/plugins/samplesource/limesdrinput/limesdrinputthread.h @@ -55,11 +55,7 @@ private: unsigned int m_log2Decim; // soft decimation -#ifdef SDR_RX_SAMPLE_24BIT Decimators m_decimators; -#else - Decimators m_decimators; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputthread.h b/plugins/samplesource/plutosdrinput/plutosdrinputthread.h index 843175246..4bd285aee 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputthread.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinputthread.h @@ -59,11 +59,7 @@ private: int m_fcPos; float m_phasor; -#ifdef SDR_RX_SAMPLE_24BIT Decimators m_decimators; -#else - Decimators m_decimators; -#endif void run(); void convert(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/sdrplay/sdrplaythread.h b/plugins/samplesource/sdrplay/sdrplaythread.h index 1317ae57f..428cd00c4 100644 --- a/plugins/samplesource/sdrplay/sdrplaythread.h +++ b/plugins/samplesource/sdrplay/sdrplaythread.h @@ -52,11 +52,7 @@ private: unsigned int m_log2Decim; int m_fcPos; -#ifdef SDR_RX_SAMPLE_24BIT Decimators m_decimators; -#else - Decimators m_decimators; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/testsource/testsourcethread.h b/plugins/samplesource/testsource/testsourcethread.h index db9e4d3c8..db506d308 100644 --- a/plugins/samplesource/testsource/testsourcethread.h +++ b/plugins/samplesource/testsource/testsourcethread.h @@ -99,15 +99,9 @@ private: bool m_throttleToggle; QMutex m_mutex; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators_8; - Decimators m_decimators_12; - Decimators m_decimators_16; -#else Decimators m_decimators_8; Decimators m_decimators_12; Decimators m_decimators_16; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index 44680cbd8..9d413e948 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -280,7 +280,7 @@ struct TripleByteLE /** Decimators with integer input and integer output */ -template +template class Decimators { public: @@ -345,8 +345,8 @@ private: #endif // SDR_RX_SAMPLE_24BIT }; -template -void Decimators::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) { qint32 xreal, yimag; @@ -360,8 +360,8 @@ void Decimators::decimate1(SampleVector::iterat } } -template -void Decimators::decimate1(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate1(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { qint32 xreal, yimag; @@ -375,10 +375,10 @@ void Decimators::decimate1(SampleVector::iterat } } -template -void Decimators::decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -396,10 +396,10 @@ void Decimators::decimate2_u(SampleVector::iter } } -template -void Decimators::decimate2_u(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate2_u(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 3; pos += 4) { @@ -419,10 +419,10 @@ void Decimators::decimate2_u(SampleVector::iter } } -template -void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -440,10 +440,10 @@ void Decimators::decimate2_inf(SampleVector::it } } -template -void Decimators::decimate2_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate2_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 3; pos += 4) { @@ -463,10 +463,10 @@ void Decimators::decimate2_inf(SampleVector::it } } -template -void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -484,10 +484,10 @@ void Decimators::decimate2_sup(SampleVector::it } } -template -void Decimators::decimate2_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate2_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 3; pos += 4) { @@ -507,10 +507,10 @@ void Decimators::decimate2_sup(SampleVector::it } } -template -void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -524,10 +524,10 @@ void Decimators::decimate4_inf(SampleVector::it } } -template -void Decimators::decimate4_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate4_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 3; pos += 4) { @@ -541,8 +541,8 @@ void Decimators::decimate4_inf(SampleVector::it } } -template -void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) { // Sup (USB): // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 @@ -550,7 +550,7 @@ void Decimators::decimate4_sup(SampleVector::it // Inf (LSB): // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -564,10 +564,10 @@ void Decimators::decimate4_sup(SampleVector::it } } -template -void Decimators::decimate4_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate4_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 3; pos += 4) { @@ -581,10 +581,10 @@ void Decimators::decimate4_sup(SampleVector::it } } -template -void Decimators::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType xreal[2], yimag[2]; for (int pos = 0; pos < len - 15; pos += 8) { @@ -604,10 +604,10 @@ void Decimators::decimate8_inf(SampleVector::it } } -template -void Decimators::decimate8_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate8_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType xreal[2], yimag[2]; for (int pos = 0; pos < len - 7; pos += 4) { @@ -627,10 +627,10 @@ void Decimators::decimate8_inf(SampleVector::it } } -template -void Decimators::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType xreal[2], yimag[2]; for (int pos = 0; pos < len - 15; pos += 8) { @@ -650,10 +650,10 @@ void Decimators::decimate8_sup(SampleVector::it } } -template -void Decimators::decimate8_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate8_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType xreal[2], yimag[2]; for (int pos = 0; pos < len - 7; pos += 4) { @@ -673,12 +673,12 @@ void Decimators::decimate8_sup(SampleVector::it } } -template -void Decimators::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal[4], yimag[4]; + StorageType xreal[4], yimag[4]; for (int pos = 0; pos < len - 31; ) { @@ -701,12 +701,12 @@ void Decimators::decimate16_inf(SampleVector::i } } -template -void Decimators::decimate16_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate16_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal[4], yimag[4]; + StorageType xreal[4], yimag[4]; for (int pos = 0; pos < len - 15; ) { @@ -729,12 +729,12 @@ void Decimators::decimate16_inf(SampleVector::i } } -template -void Decimators::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - AccuType xreal[4], yimag[4]; + StorageType xreal[4], yimag[4]; for (int pos = 0; pos < len - 31; ) { @@ -757,12 +757,12 @@ void Decimators::decimate16_sup(SampleVector::i } } -template -void Decimators::decimate16_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate16_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - AccuType xreal[4], yimag[4]; + StorageType xreal[4], yimag[4]; for (int pos = 0; pos < len - 15; ) { @@ -785,10 +785,10 @@ void Decimators::decimate16_sup(SampleVector::i } } -template -void Decimators::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType xreal[8], yimag[8]; for (int pos = 0; pos < len - 63; ) { @@ -816,10 +816,10 @@ void Decimators::decimate32_inf(SampleVector::i } } -template -void Decimators::decimate32_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate32_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType xreal[8], yimag[8]; for (int pos = 0; pos < len - 31; ) { @@ -847,10 +847,10 @@ void Decimators::decimate32_inf(SampleVector::i } } -template -void Decimators::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType xreal[8], yimag[8]; for (int pos = 0; pos < len - 63; ) { @@ -878,10 +878,10 @@ void Decimators::decimate32_sup(SampleVector::i } } -template -void Decimators::decimate32_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate32_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType xreal[8], yimag[8]; for (int pos = 0; pos < len - 31; ) { @@ -909,10 +909,10 @@ void Decimators::decimate32_sup(SampleVector::i } } -template -void Decimators::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType xreal[16], yimag[16]; for (int pos = 0; pos < len - 127; ) { @@ -949,10 +949,10 @@ void Decimators::decimate64_inf(SampleVector::i } } -template -void Decimators::decimate64_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate64_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType xreal[16], yimag[16]; for (int pos = 0; pos < len - 63; ) { @@ -989,10 +989,10 @@ void Decimators::decimate64_inf(SampleVector::i } } -template -void Decimators::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType xreal[16], yimag[16]; for (int pos = 0; pos < len - 127; ) { @@ -1029,10 +1029,10 @@ void Decimators::decimate64_sup(SampleVector::i } } -template -void Decimators::decimate64_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate64_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType xreal[16], yimag[16]; for (int pos = 0; pos < len - 63; ) { @@ -1069,10 +1069,10 @@ void Decimators::decimate64_sup(SampleVector::i } } -template -void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[2]; + StorageType intbuf[2]; for (int pos = 0; pos < len - 3; pos += 4) { @@ -1092,10 +1092,10 @@ void Decimators::decimate2_cen(SampleVector::it } } -template -void Decimators::decimate2_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate2_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[2]; + StorageType intbuf[2]; for (int pos = 0; pos < len - 1; pos += 2) { @@ -1114,10 +1114,10 @@ void Decimators::decimate2_cen(SampleVector::it } } -template -void Decimators::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[4]; + StorageType intbuf[4]; for (int pos = 0; pos < len - 7; pos += 8) { @@ -1149,10 +1149,10 @@ void Decimators::decimate4_cen(SampleVector::it } } -template -void Decimators::decimate4_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate4_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[4]; + StorageType intbuf[4]; for (int pos = 0; pos < len - 3; pos += 4) { @@ -1184,10 +1184,10 @@ void Decimators::decimate4_cen(SampleVector::it } } -template -void Decimators::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[8]; + StorageType intbuf[8]; for (int pos = 0; pos < len - 15; pos += 16) { @@ -1244,10 +1244,10 @@ void Decimators::decimate8_cen(SampleVector::it } } -template -void Decimators::decimate8_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate8_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[8]; + StorageType intbuf[8]; for (int pos = 0; pos < len - 7; pos += 8) { @@ -1304,10 +1304,10 @@ void Decimators::decimate8_cen(SampleVector::it } } -template -void Decimators::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[16]; + StorageType intbuf[16]; for (int pos = 0; pos < len - 31; pos += 32) { @@ -1413,10 +1413,10 @@ void Decimators::decimate16_cen(SampleVector::i } } -template -void Decimators::decimate16_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate16_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[16]; + StorageType intbuf[16]; for (int pos = 0; pos < len - 15; pos += 16) { @@ -1522,10 +1522,10 @@ void Decimators::decimate16_cen(SampleVector::i } } -template -void Decimators::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[32]; + StorageType intbuf[32]; for (int pos = 0; pos < len - 63; pos += 64) { @@ -1728,10 +1728,10 @@ void Decimators::decimate32_cen(SampleVector::i } } -template -void Decimators::decimate32_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate32_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[32]; + StorageType intbuf[32]; for (int pos = 0; pos < len - 31; pos += 32) { @@ -1934,10 +1934,10 @@ void Decimators::decimate32_cen(SampleVector::i } } -template -void Decimators::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[64]; + StorageType intbuf[64]; for (int pos = 0; pos < len - 127; pos += 128) { @@ -2334,10 +2334,10 @@ void Decimators::decimate64_cen(SampleVector::i } } -template -void Decimators::decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[64]; + StorageType intbuf[64]; for (int pos = 0; pos < len - 63; pos += 64) { diff --git a/sdrbench/mainbench.h b/sdrbench/mainbench.h index 8ee61f386..beb17e5fd 100644 --- a/sdrbench/mainbench.h +++ b/sdrbench/mainbench.h @@ -64,11 +64,7 @@ private: std::uniform_real_distribution m_uniform_distribution_f; std::uniform_int_distribution m_uniform_distribution_s16; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimatorsII; -#else Decimators m_decimatorsII; -#endif DecimatorsIF m_decimatorsIF; DecimatorsFI m_decimatorsFI; DecimatorsFF m_decimatorsFF; From 058f3d5af80cf55fd7c5fd0b8226355325aca375 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 2 May 2018 00:24:50 +0200 Subject: [PATCH 335/956] RTLSDR: optimized decimator --- plugins/samplesource/rtlsdr/rtlsdrthread.h | 4 - sdrbase/dsp/decimatorsu.h | 144 ++++++++++----------- 2 files changed, 66 insertions(+), 82 deletions(-) diff --git a/plugins/samplesource/rtlsdr/rtlsdrthread.h b/plugins/samplesource/rtlsdr/rtlsdrthread.h index 97f3ed56d..081cba920 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrthread.h +++ b/plugins/samplesource/rtlsdr/rtlsdrthread.h @@ -52,11 +52,7 @@ private: unsigned int m_log2Decim; int m_fcPos; -#ifdef SDR_RX_SAMPLE_24BIT - DecimatorsU m_decimators; -#else DecimatorsU m_decimators; -#endif void run(); void callback(const quint8* buf, qint32 len); diff --git a/sdrbase/dsp/decimatorsu.h b/sdrbase/dsp/decimatorsu.h index 059e7dcf3..f10725384 100644 --- a/sdrbase/dsp/decimatorsu.h +++ b/sdrbase/dsp/decimatorsu.h @@ -24,15 +24,12 @@ #define INCLUDE_GPL_DSP_DECIMATORSU_H_ #include "dsp/dsptypes.h" + #ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfilterdb.h" -#else -#ifdef USE_SSE4_1 +#include "dsp/inthalfbandfiltereo2.h" +#else // SDR_RX_SAMPLE_24BIT #include "dsp/inthalfbandfiltereo1.h" -#else -#include "dsp/inthalfbandfilterdb.h" -#endif -#endif +#endif // SDR_RX_SAMPLE_24BIT #define DECIMATORS_HB_FILTER_ORDER 64 @@ -180,7 +177,7 @@ struct decimation_shifts<24, 8> static const uint post64 = 0; }; -template +template class DecimatorsU { public: @@ -207,33 +204,24 @@ public: private: #ifdef SDR_RX_SAMPLE_24BIT - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages + IntHalfbandFilterEO2 m_decimator2; // 1st stages + IntHalfbandFilterEO2 m_decimator4; // 2nd stages + IntHalfbandFilterEO2 m_decimator8; // 3rd stages + IntHalfbandFilterEO2 m_decimator16; // 4th stages + IntHalfbandFilterEO2 m_decimator32; // 5th stages + IntHalfbandFilterEO2 m_decimator64; // 6th stages #else -#ifdef USE_SSE4_1 IntHalfbandFilterEO1 m_decimator2; // 1st stages IntHalfbandFilterEO1 m_decimator4; // 2nd stages IntHalfbandFilterEO1 m_decimator8; // 3rd stages IntHalfbandFilterEO1 m_decimator16; // 4th stages IntHalfbandFilterEO1 m_decimator32; // 5th stages IntHalfbandFilterEO1 m_decimator64; // 6th stages -#else - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages -#endif #endif }; -template -void DecimatorsU::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) { qint32 xreal, yimag; @@ -247,8 +235,8 @@ void DecimatorsU::decimate1(SampleVector } } -template -void DecimatorsU::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) { qint32 xreal, yimag; @@ -268,10 +256,10 @@ void DecimatorsU::decimate2_inf(SampleVe } } -template -void DecimatorsU::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -289,10 +277,10 @@ void DecimatorsU::decimate2_sup(SampleVe } } -template -void DecimatorsU::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -306,8 +294,8 @@ void DecimatorsU::decimate4_inf(SampleVe } } -template -void DecimatorsU::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) { // Sup (USB): // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 @@ -315,7 +303,7 @@ void DecimatorsU::decimate4_sup(SampleVe // Inf (LSB): // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -329,10 +317,10 @@ void DecimatorsU::decimate4_sup(SampleVe } } -template -void DecimatorsU::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType xreal[2], yimag[2]; for (int pos = 0; pos < len - 15; pos += 8) { @@ -352,10 +340,10 @@ void DecimatorsU::decimate8_inf(SampleVe } } -template -void DecimatorsU::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType xreal[2], yimag[2]; for (int pos = 0; pos < len - 15; pos += 8) { @@ -375,12 +363,12 @@ void DecimatorsU::decimate8_sup(SampleVe } } -template -void DecimatorsU::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal[4], yimag[4]; + StorageType xreal[4], yimag[4]; for (int pos = 0; pos < len - 31; ) { @@ -403,12 +391,12 @@ void DecimatorsU::decimate16_inf(SampleV } } -template -void DecimatorsU::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - AccuType xreal[4], yimag[4]; + StorageType xreal[4], yimag[4]; for (int pos = 0; pos < len - 31; ) { @@ -431,10 +419,10 @@ void DecimatorsU::decimate16_sup(SampleV } } -template -void DecimatorsU::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType xreal[8], yimag[8]; for (int pos = 0; pos < len - 63; ) { @@ -462,10 +450,10 @@ void DecimatorsU::decimate32_inf(SampleV } } -template -void DecimatorsU::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType xreal[8], yimag[8]; for (int pos = 0; pos < len - 63; ) { @@ -493,10 +481,10 @@ void DecimatorsU::decimate32_sup(SampleV } } -template -void DecimatorsU::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType xreal[16], yimag[16]; for (int pos = 0; pos < len - 127; ) { @@ -533,10 +521,10 @@ void DecimatorsU::decimate64_inf(SampleV } } -template -void DecimatorsU::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType xreal[16], yimag[16]; for (int pos = 0; pos < len - 127; ) { @@ -573,10 +561,10 @@ void DecimatorsU::decimate64_sup(SampleV } } -template -void DecimatorsU::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[2]; + StorageType intbuf[2]; for (int pos = 0; pos < len - 3; pos += 4) { @@ -595,10 +583,10 @@ void DecimatorsU::decimate2_cen(SampleVe } } -template -void DecimatorsU::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[4]; + StorageType intbuf[4]; for (int pos = 0; pos < len - 7; pos += 8) { @@ -630,10 +618,10 @@ void DecimatorsU::decimate4_cen(SampleVe } } -template -void DecimatorsU::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[8]; + StorageType intbuf[8]; for (int pos = 0; pos < len - 15; pos += 16) { @@ -690,10 +678,10 @@ void DecimatorsU::decimate8_cen(SampleVe } } -template -void DecimatorsU::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[16]; + StorageType intbuf[16]; for (int pos = 0; pos < len - 31; pos += 32) { @@ -799,10 +787,10 @@ void DecimatorsU::decimate16_cen(SampleV } } -template -void DecimatorsU::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[32]; + StorageType intbuf[32]; for (int pos = 0; pos < len - 63; pos += 64) { @@ -1005,10 +993,10 @@ void DecimatorsU::decimate32_cen(SampleV } } -template -void DecimatorsU::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[64]; + StorageType intbuf[64]; for (int pos = 0; pos < len - 127; pos += 128) { From c7db2a3ca76fdc06be70e8325a6d66e6be05fec5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 2 May 2018 00:29:18 +0200 Subject: [PATCH 336/956] Bumped plugins version --- debian/changelog | 4 +++- plugins/samplesource/airspy/airspyplugin.cpp | 2 +- plugins/samplesource/bladerfinput/bladerfinputplugin.cpp | 2 +- plugins/samplesource/hackrfinput/hackrfinputplugin.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinputplugin.cpp | 2 +- plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp | 2 +- plugins/samplesource/rtlsdr/rtlsdrplugin.cpp | 2 +- plugins/samplesource/sdrplay/sdrplayplugin.cpp | 2 +- plugins/samplesource/testsource/testsourceplugin.cpp | 2 +- 9 files changed, 11 insertions(+), 9 deletions(-) diff --git a/debian/changelog b/debian/changelog index d8a5a17ee..78ac9df32 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,10 @@ sdrangel (3.14.5-1) unstable; urgency=medium * DSD demod: allow audio rates integer multiples of 8k other than 48k + * Added a benchmark program testing decimators + * Optimization of decimators using even/odd technique - -- Edouard Griffiths, F4EXB Sun, 29 Apr 2018 17:14:18 +0200 + -- Edouard Griffiths, F4EXB Fri, 04 May 2018 20:14:18 +0200 sdrangel (3.14.4-1) unstable; urgency=medium diff --git a/plugins/samplesource/airspy/airspyplugin.cpp b/plugins/samplesource/airspy/airspyplugin.cpp index 355d6df56..6d1ee85f7 100644 --- a/plugins/samplesource/airspy/airspyplugin.cpp +++ b/plugins/samplesource/airspy/airspyplugin.cpp @@ -29,7 +29,7 @@ const int AirspyPlugin::m_maxDevices = 32; const PluginDescriptor AirspyPlugin::m_pluginDescriptor = { QString("Airspy Input"), - QString("3.11.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index 256ff39b3..e93a56375 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { QString("BladeRF Input"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp b/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp index 19bdbb9db..dadc4629f 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp @@ -32,7 +32,7 @@ const PluginDescriptor HackRFInputPlugin::m_pluginDescriptor = { QString("HackRF Input"), - QString("3.11.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 28d7f5a6d..50765becd 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("3.14.3"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp index b8acbeb77..189591b5d 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp @@ -28,7 +28,7 @@ class DeviceSourceAPI; const PluginDescriptor PlutoSDRInputPlugin::m_pluginDescriptor = { QString("PlutoSDR Input"), - QString("3.11.1"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp index 9188cbd27..a952d8063 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp @@ -14,7 +14,7 @@ const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = { QString("RTL-SDR Input"), - QString("3.14.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/sdrplay/sdrplayplugin.cpp b/plugins/samplesource/sdrplay/sdrplayplugin.cpp index ee861911e..aebdd2072 100644 --- a/plugins/samplesource/sdrplay/sdrplayplugin.cpp +++ b/plugins/samplesource/sdrplay/sdrplayplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor SDRPlayPlugin::m_pluginDescriptor = { QString("SDRPlay RSP1 Input"), - QString("3.11.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/testsource/testsourceplugin.cpp b/plugins/samplesource/testsource/testsourceplugin.cpp index 618fb408d..6ad8bbea4 100644 --- a/plugins/samplesource/testsource/testsourceplugin.cpp +++ b/plugins/samplesource/testsource/testsourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor TestSourcePlugin::m_pluginDescriptor = { QString("Test Source input"), - QString("3.12.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 1db2da3b60ba2192e0012efaf1477ae42c27fe02 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 2 May 2018 01:22:34 +0200 Subject: [PATCH 337/956] NFM demod: fixed squelch --- plugins/channelrx/demodnfm/nfmdemod.cpp | 96 +++++++++++++----------- plugins/channelrx/demodnfm/nfmdemod.h | 1 - plugins/channelrx/demodnfm/nfmplugin.cpp | 2 +- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 7aa4bebbe..a75eec537 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -57,7 +57,6 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_sampleCount(0), m_squelchCount(0), m_squelchGate(4800), - m_squelchDecay(4800), m_squelchLevel(-990), m_squelchOpen(false), m_afSquelchOpen(false), @@ -192,7 +191,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { m_squelchDelayLine.write(demod * m_discriCompensation); - if (m_squelchCount < m_squelchGate + m_squelchDecay) { + if (m_squelchCount < 2*m_squelchGate) { m_squelchCount++; } } @@ -219,76 +218,84 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { m_squelchDelayLine.write(demod * m_discriCompensation); - if (m_squelchCount < m_squelchGate + m_squelchDecay) { + if (m_squelchCount < 2*m_squelchGate) { m_squelchCount++; } } } - m_squelchOpen = (m_squelchCount > m_squelchGate); - - if ((m_squelchOpen) && !m_settings.m_audioMute) + if (m_settings.m_audioMute) { - if (m_settings.m_ctcssOn) + sample = 0; + } + else + { + m_squelchOpen = (m_squelchCount > m_squelchGate); + + if (m_squelchOpen) { - Real ctcss_sample = m_lowpass.filter(demod * m_discriCompensation); - - if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k + if (m_settings.m_ctcssOn) { - if (m_ctcssDetector.analyze(&ctcss_sample)) - { - int maxToneIndex; + Real ctcss_sample = m_lowpass.filter(demod * m_discriCompensation); - if (m_ctcssDetector.getDetectedTone(maxToneIndex)) + if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k + { + if (m_ctcssDetector.analyze(&ctcss_sample)) { - if (maxToneIndex+1 != m_ctcssIndex) + int maxToneIndex; + + if (m_ctcssDetector.getDetectedTone(maxToneIndex)) { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]); - getMessageQueueToGUI()->push(msg); + if (maxToneIndex+1 != m_ctcssIndex) + { + if (getMessageQueueToGUI()) { + MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]); + getMessageQueueToGUI()->push(msg); + } + m_ctcssIndex = maxToneIndex+1; } - m_ctcssIndex = maxToneIndex+1; } - } - else - { - if (m_ctcssIndex != 0) + else { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); - getMessageQueueToGUI()->push(msg); + if (m_ctcssIndex != 0) + { + if (getMessageQueueToGUI()) { + MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); + getMessageQueueToGUI()->push(msg); + } + m_ctcssIndex = 0; } - m_ctcssIndex = 0; } } } } - } - if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex)) - { - sample = 0; + if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex)) + { + sample = 0; + } + else + { + sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume; + } } else { - sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume; - } - } - else - { - if (m_ctcssIndex != 0) - { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); - getMessageQueueToGUI()->push(msg); + if (m_ctcssIndex != 0) + { + if (getMessageQueueToGUI()) { + MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); + getMessageQueueToGUI()->push(msg); + } + + m_ctcssIndex = 0; } - m_ctcssIndex = 0; + sample = 0; } - - sample = 0; } + m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; ++m_audioBufferFill; @@ -421,7 +428,6 @@ void NFMDemod::applyAudioSampleRate(int sampleRate) m_lowpass.create(301, sampleRate, 250.0); m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate - m_squelchDecay = m_squelchGate; m_squelchCount = 0; // reset squelch open counter m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index cec6cb1aa..be42291c2 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -195,7 +195,6 @@ private: int m_sampleCount; int m_squelchCount; int m_squelchGate; - int m_squelchDecay; Real m_squelchLevel; bool m_squelchOpen; diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index 690aa3b1c..f47b5e63e 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("3.14.4"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 17aa15c4d02f979693bbcf9bfc230d73affaa92a Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 2 May 2018 14:00:03 +0200 Subject: [PATCH 338/956] Unified the even/odd integer halfband filters --- sdrbase/CMakeLists.txt | 7 +- sdrbase/dsp/decimators.h | 35 +- sdrbase/dsp/inthalfbandfiltereo.h | 815 +++++++++++++++++++++++++++++ sdrbase/dsp/inthalfbandfiltereo1.h | 6 +- 4 files changed, 837 insertions(+), 26 deletions(-) create mode 100644 sdrbase/dsp/inthalfbandfiltereo.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 050c289aa..9a55e6dd8 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -130,9 +130,10 @@ set(sdrbase_HEADERS dsp/inthalfbandfilter.h dsp/inthalfbandfilterdb.h dsp/inthalfbandfilterdbf.h - dsp/inthalfbandfiltereo1.h - dsp/inthalfbandfiltereo1i.h - dsp/inthalfbandfiltereo2.h + dsp/inthalfbandfiltereo.h + # dsp/inthalfbandfiltereo1.h + # dsp/inthalfbandfiltereo1i.h + # dsp/inthalfbandfiltereo2.h dsp/inthalfbandfiltereof.h dsp/inthalfbandfilterst.h dsp/inthalfbandfiltersti.h diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index 9d413e948..50fb6b97b 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -18,12 +18,7 @@ #define INCLUDE_GPL_DSP_DECIMATORS_H_ #include "dsp/dsptypes.h" - -#ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfiltereo2.h" -#else // SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfiltereo1.h" -#endif // SDR_RX_SAMPLE_24BIT +#include "dsp/inthalfbandfiltereo.h" #define DECIMATORS_HB_FILTER_ORDER 64 @@ -329,20 +324,20 @@ public: private: #ifdef SDR_RX_SAMPLE_24BIT - IntHalfbandFilterEO2 m_decimator2; // 1st stages - IntHalfbandFilterEO2 m_decimator4; // 2nd stages - IntHalfbandFilterEO2 m_decimator8; // 3rd stages - IntHalfbandFilterEO2 m_decimator16; // 4th stages - IntHalfbandFilterEO2 m_decimator32; // 5th stages - IntHalfbandFilterEO2 m_decimator64; // 6th stages -#else // SDR_RX_SAMPLE_24BIT - IntHalfbandFilterEO1 m_decimator2; // 1st stages - IntHalfbandFilterEO1 m_decimator4; // 2nd stages - IntHalfbandFilterEO1 m_decimator8; // 3rd stages - IntHalfbandFilterEO1 m_decimator16; // 4th stages - IntHalfbandFilterEO1 m_decimator32; // 5th stages - IntHalfbandFilterEO1 m_decimator64; // 6th stages -#endif // SDR_RX_SAMPLE_24BIT + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages +#else + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages +#endif }; template diff --git a/sdrbase/dsp/inthalfbandfiltereo.h b/sdrbase/dsp/inthalfbandfiltereo.h new file mode 100644 index 000000000..8d6af22ff --- /dev/null +++ b/sdrbase/dsp/inthalfbandfiltereo.h @@ -0,0 +1,815 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// 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 SDRBASE_DSP_INTHALFBANDFILTEREO_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterEO { +public: + IntHalfbandFilterEO(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + bool workDecimateCenter(int32_t *x, int32_t *y) + { + // insert sample into ring-buffer + storeSample32(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSample((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSample((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) + { + storeSample32(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample32(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + EOStorageType m_even[2][HBFIRFilterTraits::hbOrder]; + EOStorageType m_odd[2][HBFIRFilterTraits::hbOrder]; + int32_t m_samples[HBFIRFilterTraits::hbOrder][2]; + + int m_ptr; + int m_size; + int m_state; + + void storeSample(const FixReal& sampleI, const FixReal& sampleQ) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = sampleI; + m_even[1][m_ptr/2] = sampleQ; + m_even[0][m_ptr/2 + m_size] = sampleI; + m_even[1][m_ptr/2 + m_size] = sampleQ; + } + else + { + m_odd[0][m_ptr/2] = sampleI; + m_odd[1][m_ptr/2] = sampleQ; + m_odd[0][m_ptr/2 + m_size] = sampleI; + m_odd[1][m_ptr/2 + m_size] = sampleQ; + } + } + + void storeSample32(int32_t x, int32_t y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doFIR(int32_t *x, int32_t *y) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doInterpolateFIR(Sample* sample) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } +}; + +template +IntHalfbandFilterEO::IntHalfbandFilterEO() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0; + m_even[1][i] = 0; + m_odd[0][i] = 0; + m_odd[1][i] = 0; + m_samples[i][0] = 0; + m_samples[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO_H_ */ diff --git a/sdrbase/dsp/inthalfbandfiltereo1.h b/sdrbase/dsp/inthalfbandfiltereo1.h index 7a1539afc..21bc112de 100644 --- a/sdrbase/dsp/inthalfbandfiltereo1.h +++ b/sdrbase/dsp/inthalfbandfiltereo1.h @@ -19,8 +19,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO_H_ -#define SDRBASE_DSP_INTHALFBANDFILTEREO_H_ +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ #include #include @@ -837,4 +837,4 @@ IntHalfbandFilterEO1::IntHalfbandFilterEO1() m_state = 0; } -#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO_H_ */ +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ */ From 359af254f08f69ccdfcfeea04cf6f49a91f84e92 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 3 May 2018 00:28:27 +0200 Subject: [PATCH 339/956] SSB modulator: fixed issue #167: typo in remove Tx registration method --- plugins/channeltx/modssb/ssbmodgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index 1f45221bc..d5e966927 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -459,7 +459,7 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam SSBModGUI::~SSBModGUI() { - m_deviceUISet->removeRxChannelInstance(this); + m_deviceUISet->removeTxChannelInstance(this); delete m_ssbMod; // TODO: check this: when the GUI closes it has to delete the modulator delete m_spectrumVis; delete ui; From 3f1389dc81c9570f7e785d157cd1e779f5246de9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 05:28:34 +0200 Subject: [PATCH 340/956] Removed sqlite3 dependency --- debian/changelog | 1 + liblimesuite/CMakeLists.txt | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 78ac9df32..56aed61cb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,7 @@ sdrangel (3.14.5-1) unstable; urgency=medium * DSD demod: allow audio rates integer multiples of 8k other than 48k * Added a benchmark program testing decimators * Optimization of decimators using even/odd technique + * SSB mod: fixed channel unregistration -- Edouard Griffiths, F4EXB Fri, 04 May 2018 20:14:18 +0200 diff --git a/liblimesuite/CMakeLists.txt b/liblimesuite/CMakeLists.txt index 3125d5dad..70589b041 100644 --- a/liblimesuite/CMakeLists.txt +++ b/liblimesuite/CMakeLists.txt @@ -1,7 +1,6 @@ project(limesuite) find_package(LibUSB) -find_package(SQLite3) set(limesuite_SOURCES ${LIBLIMESUITESRC}/src/Logger.cpp @@ -89,7 +88,6 @@ add_library(limesuite SHARED target_link_libraries(limesuite ${LIBUSB_LIBRARIES} - ${SQLITE3_LIBRARIES} ) install(TARGETS limesuite DESTINATION lib) From b09d15436d803fe6314709ec30bbac33bed2098d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 05:54:04 +0200 Subject: [PATCH 341/956] Debian build: removed sqlite3 from dependencies --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index f9d3e2960..71563bd3a 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://github.com/f4exb/sdrangel Package: sdrangel Architecture: any -Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libnanomsg0|libnanomsg4, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, libsqlite3-dev, pulseaudio, libxml2, ffmpeg, libavcodec-dev, libavformat-dev, ${shlibs:Depends}, ${misc:Depends} +Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libnanomsg0|libnanomsg4, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, pulseaudio, libxml2, ffmpeg, libavcodec-dev, libavformat-dev, ${shlibs:Depends}, ${misc:Depends} Description: SDR/Analyzer/Generator front-end for various hardware SDR/Analyzer/Generator front-end for Airspy, BladeRF, HackRF, RTL-SDR, FunCube, LimeSDR, PlutoSDR. Also File source and sink for I/Q samples, network I/Q sources with SDRDaemon. From 1d10ef12a331c3f3d8c82fd05dafd576204e87e9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 11:51:30 +0200 Subject: [PATCH 342/956] MainWindow: removed specific font settings --- sdrgui/mainwindow.ui | 148 +++---------------------------------------- 1 file changed, 8 insertions(+), 140 deletions(-) diff --git a/sdrgui/mainwindow.ui b/sdrgui/mainwindow.ui index 59f7dc4e6..48b117453 100644 --- a/sdrgui/mainwindow.ui +++ b/sdrgui/mainwindow.ui @@ -10,12 +10,6 @@ 721 - - - Sans Serif - 9 - - SDRangel @@ -27,32 +21,12 @@ true - - - Sans Serif - - - - 0 - - - 0 - - - 0 - - + 0 - - - Sans Serif - 9 - - QTabWidget::East @@ -79,24 +53,12 @@ - - - Sans Serif - 9 - - &File - - - Sans Serif - 9 - - &DeviceSets @@ -105,24 +67,12 @@ - - - Sans Serif - 9 - - &View - - - Sans Serif - 9 - - &Help @@ -131,12 +81,6 @@ - - - Sans Serif - 9 - - &Window @@ -157,14 +101,7 @@ - - - - Sans Serif - 9 - - - + Sampling devices @@ -183,16 +120,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -226,16 +154,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -272,16 +191,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -306,16 +216,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -539,16 +440,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -576,16 +468,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -768,9 +651,6 @@ E&xit - - - Ctrl+Q @@ -804,9 +684,6 @@ &Fullscreen - - - F11 @@ -826,9 +703,6 @@ &About SDRangel... - - - @@ -842,17 +716,11 @@ Loaded &Plugins... - - - Add source device set - - - From 5ce49a387df8361a083af1acde7087dbafc539c2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 15:10:19 +0200 Subject: [PATCH 343/956] Changed font for upward compatibiilty (test) --- plugins/channelrx/demodam/amdemodgui.ui | 7 +++--- .../samplesource/filesource/filesourcegui.ui | 19 +++++---------- .../samplesource/testsource/testsourcegui.ui | 24 +++++++------------ sdrgui/mainwindow.ui | 5 ++++ 4 files changed, 22 insertions(+), 33 deletions(-) diff --git a/plugins/channelrx/demodam/amdemodgui.ui b/plugins/channelrx/demodam/amdemodgui.ui index ba656f815..12ab3108d 100644 --- a/plugins/channelrx/demodam/amdemodgui.ui +++ b/plugins/channelrx/demodam/amdemodgui.ui @@ -24,8 +24,7 @@ - Sans Serif - 9 + Liberation Sans @@ -98,7 +97,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -204,7 +203,7 @@ - Monospace + Liberation Mono 8 diff --git a/plugins/samplesource/filesource/filesourcegui.ui b/plugins/samplesource/filesource/filesourcegui.ui index 20ec4bc82..33da4847d 100644 --- a/plugins/samplesource/filesource/filesourcegui.ui +++ b/plugins/samplesource/filesource/filesourcegui.ui @@ -24,8 +24,10 @@ - Sans Serif - 9 + Liberation Sans + 50 + false + false @@ -35,16 +37,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -130,7 +123,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui index 546f3a175..d4052fd52 100644 --- a/plugins/samplesource/testsource/testsourcegui.ui +++ b/plugins/samplesource/testsource/testsourcegui.ui @@ -24,8 +24,10 @@ - Sans Serif - 9 + Liberation Sans + 50 + false + false @@ -35,16 +37,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -141,7 +134,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -412,7 +405,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -553,9 +546,8 @@ - DejaVu Sans Mono + Liberation Mono 12 - false diff --git a/sdrgui/mainwindow.ui b/sdrgui/mainwindow.ui index 48b117453..d3f9938df 100644 --- a/sdrgui/mainwindow.ui +++ b/sdrgui/mainwindow.ui @@ -10,6 +10,11 @@ 721 + + + Liberation Sans + + SDRangel From bec196953ee126173640669daa7b326159d7825a Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 15:21:49 +0200 Subject: [PATCH 344/956] Corrected default font size --- plugins/channelrx/demodam/amdemodgui.ui | 12 ++- .../samplesource/filesource/filesourcegui.ui | 12 ++- .../samplesource/testsource/testsourcegui.ui | 12 ++- sdrgui/mainwindow.ui | 78 +++++++++++++++++-- 4 files changed, 104 insertions(+), 10 deletions(-) diff --git a/plugins/channelrx/demodam/amdemodgui.ui b/plugins/channelrx/demodam/amdemodgui.ui index 12ab3108d..b2d4fc427 100644 --- a/plugins/channelrx/demodam/amdemodgui.ui +++ b/plugins/channelrx/demodam/amdemodgui.ui @@ -25,6 +25,7 @@ Liberation Sans + 9 @@ -58,7 +59,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 diff --git a/plugins/samplesource/filesource/filesourcegui.ui b/plugins/samplesource/filesource/filesourcegui.ui index 33da4847d..f6a01558a 100644 --- a/plugins/samplesource/filesource/filesourcegui.ui +++ b/plugins/samplesource/filesource/filesourcegui.ui @@ -25,6 +25,7 @@ Liberation Sans + 9 50 false false @@ -37,7 +38,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui index d4052fd52..31f2bbd58 100644 --- a/plugins/samplesource/testsource/testsourcegui.ui +++ b/plugins/samplesource/testsource/testsourcegui.ui @@ -25,6 +25,7 @@ Liberation Sans + 9 50 false false @@ -37,7 +38,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 diff --git a/sdrgui/mainwindow.ui b/sdrgui/mainwindow.ui index d3f9938df..2c39cf307 100644 --- a/sdrgui/mainwindow.ui +++ b/sdrgui/mainwindow.ui @@ -13,6 +13,7 @@ Liberation Sans + 9 @@ -27,7 +28,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -125,7 +135,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -159,7 +178,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -196,7 +224,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -221,7 +258,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -445,7 +491,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -473,7 +528,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 From a6792ebad7873126b43c0c1190da5f858db9a895 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 17:21:11 +0200 Subject: [PATCH 345/956] Windows build fixes --- sdrbase/sdrbase.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index 77571c589..399cc14c5 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -68,7 +68,7 @@ SOURCES += audio/audiodevicemanager.cpp\ dsp/ctcssdetector.cpp\ dsp/cwkeyer.cpp\ dsp/cwkeyersettings.cpp\ - dsp/decimatorsf.cpp\ + dsp/decimatorsfi.cpp\ dsp/dspcommands.cpp\ dsp/dspengine.cpp\ dsp/dspdevicesourceengine.cpp\ @@ -132,6 +132,7 @@ HEADERS += audio/audiodevicemanager.h\ device/devicesinkapi.h\ device/deviceenumerator.h\ dsp/afsquelch.h\ + dsp/decimatorsfi.h\ dsp/downchannelizer.h\ dsp/upchannelizer.h\ dsp/channelmarker.h\ @@ -139,7 +140,6 @@ HEADERS += audio/audiodevicemanager.h\ dsp/cwkeyersettings.h\ dsp/complex.h\ dsp/decimators.h\ - dsp/decimatorsf.h\ dsp/interpolators.h\ dsp/dspcommands.h\ dsp/dspengine.h\ From 03c4c41c82f7beec9610f720ed0bf875e791f0be Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 17:34:35 +0200 Subject: [PATCH 346/956] MainWindow: use Liberation font everywhere --- sdrgui/mainwindow.ui | 77 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/sdrgui/mainwindow.ui b/sdrgui/mainwindow.ui index 2c39cf307..9c08c869c 100644 --- a/sdrgui/mainwindow.ui +++ b/sdrgui/mainwindow.ui @@ -96,11 +96,23 @@ + + + Liberation Sans + 9 + + &Window + + + Liberation Sans + 9 + + &Preferences @@ -720,6 +732,12 @@ E&xit + + + Liberation Sans + 9 + + Ctrl+Q @@ -753,6 +771,12 @@ &Fullscreen + + + Liberation Sans + 9 + + F11 @@ -772,6 +796,12 @@ &About SDRangel... + + + Liberation Sans + 9 + + @@ -785,18 +815,33 @@ Loaded &Plugins... + + + Liberation Sans + 9 + + Add source device set + + + Liberation Sans + 9 + + Remove last device set - + + Liberation Sans + 9 + @@ -806,6 +851,12 @@ Audio devices setting + + + Liberation Sans + 9 + + @@ -817,6 +868,12 @@ Toggle AMBE DV serial device usage + + + Liberation Sans + 9 + + @@ -825,11 +882,23 @@ Set my geo position + + + Liberation Sans + 9 + + Add sink device set + + + Liberation Sans + 9 + + @@ -838,6 +907,12 @@ Message logging options + + + Liberation Sans + 9 + + presetDock channelDock From d150dd810a4c18897c931db22e6cf3c9be0e8c62 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 17:44:38 +0200 Subject: [PATCH 347/956] sdrgui: changed all fonts to Liberation --- sdrgui/gui/aboutdialog.ui | 2 +- sdrgui/gui/audiodialog.ui | 6 +-- sdrgui/gui/audioselectdialog.ui | 4 +- sdrgui/gui/basicchannelsettingsdialog.ui | 6 +++ sdrgui/gui/commandoutputdialog.ui | 2 +- sdrgui/gui/cwkeyergui.ui | 15 ++++++-- sdrgui/gui/editcommanddialog.ui | 4 +- sdrgui/gui/externalclockdialog.ui | 2 +- sdrgui/gui/glscopegui.ui | 15 ++++++-- sdrgui/gui/glscopemultigui.ui | 3 +- sdrgui/gui/glscopenggui.ui | 47 +++++++++++++++++++++--- sdrgui/gui/glspectrumgui.ui | 13 ++++++- sdrgui/gui/loggingdialog.ui | 2 +- sdrgui/gui/myposdialog.ui | 4 +- sdrgui/gui/pluginsdialog.ui | 2 +- sdrgui/gui/samplingdevicecontrol.ui | 2 +- sdrgui/gui/samplingdevicedialog.ui | 2 +- sdrgui/gui/scopewindow.ui | 28 +++++++++++++- sdrgui/gui/transverterdialog.ui | 2 +- 19 files changed, 128 insertions(+), 33 deletions(-) diff --git a/sdrgui/gui/aboutdialog.ui b/sdrgui/gui/aboutdialog.ui index 010036652..87ae06603 100644 --- a/sdrgui/gui/aboutdialog.ui +++ b/sdrgui/gui/aboutdialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/audiodialog.ui b/sdrgui/gui/audiodialog.ui index 2a6a313fb..4bc3625a8 100644 --- a/sdrgui/gui/audiodialog.ui +++ b/sdrgui/gui/audiodialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 @@ -139,7 +139,7 @@ Destination UDP address - 000.000.000.000; + 000.000.000.000 127.0.0.1 @@ -165,7 +165,7 @@ Destination UDP port - 00000; + 00000 9998 diff --git a/sdrgui/gui/audioselectdialog.ui b/sdrgui/gui/audioselectdialog.ui index 0b2a7b0d5..dc9575594 100644 --- a/sdrgui/gui/audioselectdialog.ui +++ b/sdrgui/gui/audioselectdialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 @@ -43,7 +43,7 @@ Rate - AlignRight|AlignVCenter + AlignTrailing|AlignVCenter diff --git a/sdrgui/gui/basicchannelsettingsdialog.ui b/sdrgui/gui/basicchannelsettingsdialog.ui index d2ccc0881..25af72d52 100644 --- a/sdrgui/gui/basicchannelsettingsdialog.ui +++ b/sdrgui/gui/basicchannelsettingsdialog.ui @@ -10,6 +10,12 @@ 131 + + + Liberation Sans + 9 + + Basic channel settings diff --git a/sdrgui/gui/commandoutputdialog.ui b/sdrgui/gui/commandoutputdialog.ui index 8ee6be6bb..590aa9f39 100644 --- a/sdrgui/gui/commandoutputdialog.ui +++ b/sdrgui/gui/commandoutputdialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/cwkeyergui.ui b/sdrgui/gui/cwkeyergui.ui index fe5899f94..cdd0867ce 100644 --- a/sdrgui/gui/cwkeyergui.ui +++ b/sdrgui/gui/cwkeyergui.ui @@ -12,15 +12,24 @@ - Sans Serif - 8 + Liberation Sans + 9 CW Keyer - + + 2 + + + 2 + + + 2 + + 2 diff --git a/sdrgui/gui/editcommanddialog.ui b/sdrgui/gui/editcommanddialog.ui index 6a052b533..9ced7090c 100644 --- a/sdrgui/gui/editcommanddialog.ui +++ b/sdrgui/gui/editcommanddialog.ui @@ -7,12 +7,12 @@ 0 0 324 - 239 + 251 - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/externalclockdialog.ui b/sdrgui/gui/externalclockdialog.ui index 9efa481d3..2e75e9086 100644 --- a/sdrgui/gui/externalclockdialog.ui +++ b/sdrgui/gui/externalclockdialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/glscopegui.ui b/sdrgui/gui/glscopegui.ui index 135b1e0f6..8b07d6067 100644 --- a/sdrgui/gui/glscopegui.ui +++ b/sdrgui/gui/glscopegui.ui @@ -7,7 +7,7 @@ 0 0 634 - 115 + 117 @@ -18,7 +18,7 @@ - Sans Serif + Liberation Sans 8 @@ -29,7 +29,16 @@ 3 - + + 0 + + + 0 + + + 0 + + 0 diff --git a/sdrgui/gui/glscopemultigui.ui b/sdrgui/gui/glscopemultigui.ui index e4f029aee..e9b51bb4a 100644 --- a/sdrgui/gui/glscopemultigui.ui +++ b/sdrgui/gui/glscopemultigui.ui @@ -7,7 +7,7 @@ 0 0 750 - 120 + 121 @@ -18,6 +18,7 @@ + Liberation Sans 8 diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index e6560dddd..e7a8afe70 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -7,7 +7,7 @@ 0 0 758 - 120 + 121 @@ -18,6 +18,7 @@ + Liberation Sans 8 @@ -646,7 +647,16 @@ kS/s 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -928,7 +938,16 @@ kS/s 2 - + + 0 + + + 0 + + + 0 + + 0 @@ -1253,7 +1272,16 @@ kS/s 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -1531,7 +1559,16 @@ kS/s 2 - + + 0 + + + 0 + + + 0 + + 0 diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index 87340e0ea..b46b17a66 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 8 @@ -20,7 +20,16 @@ Oscilloscope - + + 2 + + + 2 + + + 2 + + 2 diff --git a/sdrgui/gui/loggingdialog.ui b/sdrgui/gui/loggingdialog.ui index ec05378a9..29ff46d8d 100644 --- a/sdrgui/gui/loggingdialog.ui +++ b/sdrgui/gui/loggingdialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/myposdialog.ui b/sdrgui/gui/myposdialog.ui index d87ff3ad8..bc022184e 100644 --- a/sdrgui/gui/myposdialog.ui +++ b/sdrgui/gui/myposdialog.ui @@ -7,12 +7,12 @@ 0 0 324 - 127 + 147 - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/pluginsdialog.ui b/sdrgui/gui/pluginsdialog.ui index 1f87c6d1b..530491765 100644 --- a/sdrgui/gui/pluginsdialog.ui +++ b/sdrgui/gui/pluginsdialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/samplingdevicecontrol.ui b/sdrgui/gui/samplingdevicecontrol.ui index 3866aa90e..7eb8340b9 100644 --- a/sdrgui/gui/samplingdevicecontrol.ui +++ b/sdrgui/gui/samplingdevicecontrol.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/samplingdevicedialog.ui b/sdrgui/gui/samplingdevicedialog.ui index 7555e1677..b3aa657e0 100644 --- a/sdrgui/gui/samplingdevicedialog.ui +++ b/sdrgui/gui/samplingdevicedialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/sdrgui/gui/scopewindow.ui b/sdrgui/gui/scopewindow.ui index d521af357..c208dace0 100644 --- a/sdrgui/gui/scopewindow.ui +++ b/sdrgui/gui/scopewindow.ui @@ -10,6 +10,12 @@ 149 + + + Liberation Sans + 9 + + Oscilloscope @@ -17,7 +23,16 @@ 3 - + + 0 + + + 0 + + + 0 + + 0 @@ -41,7 +56,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 diff --git a/sdrgui/gui/transverterdialog.ui b/sdrgui/gui/transverterdialog.ui index c657b3994..1d9850edd 100644 --- a/sdrgui/gui/transverterdialog.ui +++ b/sdrgui/gui/transverterdialog.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 From c43ecc8afec01b543d6272720416b2c1a98f4e66 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 5 May 2018 18:12:05 +0200 Subject: [PATCH 348/956] Channel Rx plugins: use liberation font --- plugins/channelrx/chanalyzer/chanalyzergui.ui | 35 +++++++++++++++--- .../channelrx/chanalyzer/chanalyzerplugin.cpp | 2 +- .../channelrx/chanalyzerng/chanalyzernggui.ui | 10 +++--- .../chanalyzerng/chanalyzerngplugin.cpp | 2 +- plugins/channelrx/demodam/amdemodplugin.cpp | 2 +- plugins/channelrx/demodatv/atvdemodgui.ui | 10 ++++-- plugins/channelrx/demodatv/atvdemodplugin.cpp | 2 +- plugins/channelrx/demodbfm/bfmdemodgui.ui | 32 +++++++++++++---- plugins/channelrx/demodbfm/bfmplugin.cpp | 2 +- plugins/channelrx/demoddatv/datvdemodgui.ui | 8 ++--- .../channelrx/demoddatv/datvdemodplugin.cpp | 2 +- plugins/channelrx/demoddsd/dsddemodgui.ui | 32 +++++++++++++---- plugins/channelrx/demodlora/lorademodgui.ui | 2 +- plugins/channelrx/demodlora/loraplugin.cpp | 2 +- plugins/channelrx/demodnfm/nfmdemodgui.ui | 17 ++++++--- plugins/channelrx/demodssb/ssbdemodgui.ui | 30 ++++++++++++---- plugins/channelrx/demodwfm/wfmdemodgui.ui | 17 ++++++--- plugins/channelrx/demodwfm/wfmplugin.cpp | 2 +- plugins/channelrx/tcpsrc/tcpsrcgui.ui | 6 ++-- plugins/channelrx/tcpsrc/tcpsrcplugin.cpp | 2 +- plugins/channelrx/udpsrc/udpsrcgui.ui | 36 ++++++++++++++----- plugins/channelrx/udpsrc/udpsrcplugin.cpp | 2 +- 22 files changed, 189 insertions(+), 66 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index e05f3592b..2ea84a2bb 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -18,7 +18,7 @@ - Sans Serif + Liberation Sans 9 @@ -41,7 +41,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -397,7 +406,16 @@ 2 - + + 2 + + + 2 + + + 2 + + 2 @@ -443,7 +461,16 @@ 2 - + + 3 + + + 3 + + + 3 + + 3 diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index c728805a4..32f33cc41 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -8,7 +8,7 @@ const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("3.9.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index 7f04bd6ad..c6e712619 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -18,7 +18,7 @@ - Sans Serif + Liberation Sans 9 @@ -89,7 +89,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -209,7 +209,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -503,7 +503,7 @@ - Monospace + Liberation Mono 8 @@ -558,7 +558,7 @@ - Monospace + Liberation Mono 8 diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp index 4161511b4..076d9b967 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = { QString("Channel Analyzer NG"), - QString("3.9.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index 4942287c9..1cffd61c3 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("3.14.4"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index be545a93e..9163af836 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -86,7 +86,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -1095,6 +1095,12 @@ 312 + + + Liberation Mono + 8 + + diff --git a/plugins/channelrx/demodatv/atvdemodplugin.cpp b/plugins/channelrx/demodatv/atvdemodplugin.cpp index aba8cc1f6..2a3852120 100644 --- a/plugins/channelrx/demodatv/atvdemodplugin.cpp +++ b/plugins/channelrx/demodatv/atvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor ATVDemodPlugin::m_ptrPluginDescriptor = { QString("ATV Demodulator"), - QString("3.12.0"), + QString("3.14.5"), QString("(c) F4HKW for F4EXB / SDRAngel"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.ui b/plugins/channelrx/demodbfm/bfmdemodgui.ui index 36476f2a1..3592997c0 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.ui +++ b/plugins/channelrx/demodbfm/bfmdemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -62,7 +62,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -101,7 +110,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -290,7 +299,7 @@ - Monospace + Liberation Mono 8 @@ -509,15 +518,24 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 - Sans Serif - 9 + Liberation Mono + 8 diff --git a/plugins/channelrx/demodbfm/bfmplugin.cpp b/plugins/channelrx/demodbfm/bfmplugin.cpp index 89a086c56..d4f420623 100644 --- a/plugins/channelrx/demodbfm/bfmplugin.cpp +++ b/plugins/channelrx/demodbfm/bfmplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor BFMPlugin::m_pluginDescriptor = { QString("Broadcast FM Demodulator"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index 605bcc1c8..1135f36f0 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -80,7 +80,7 @@ - DejaVu Sans Mono + Liberation Mono 12 false @@ -133,7 +133,7 @@ - DejaVu Sans Mono + Liberation Mono 12 false @@ -204,7 +204,7 @@ QTabWidget::West - 0 + 1 diff --git a/plugins/channelrx/demoddatv/datvdemodplugin.cpp b/plugins/channelrx/demoddatv/datvdemodplugin.cpp index 94d3a849c..2ac38cf0d 100644 --- a/plugins/channelrx/demoddatv/datvdemodplugin.cpp +++ b/plugins/channelrx/demoddatv/datvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DATVDemodPlugin::m_ptrPluginDescriptor = { QString("DATV Demodulator"), - QString("3.14.0"), + QString("3.14.5"), QString("(c) F4HKW for SDRAngel using LeanSDR framework (c) F4DAV"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index e110fc2e4..f3a2fb54f 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -62,7 +62,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -104,7 +113,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -244,7 +253,7 @@ - Monospace + Liberation Mono 8 @@ -533,7 +542,7 @@ - DejaVu Sans Mono + Liberation Mono 9 @@ -590,7 +599,16 @@ Digital - + + 2 + + + 2 + + + 2 + + 2 @@ -681,7 +699,7 @@ - DejaVu Sans Mono + Liberation Mono 9 diff --git a/plugins/channelrx/demodlora/lorademodgui.ui b/plugins/channelrx/demodlora/lorademodgui.ui index ef9f95270..c17a2a6c6 100644 --- a/plugins/channelrx/demodlora/lorademodgui.ui +++ b/plugins/channelrx/demodlora/lorademodgui.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/plugins/channelrx/demodlora/loraplugin.cpp b/plugins/channelrx/demodlora/loraplugin.cpp index e889c995b..0a25300a4 100644 --- a/plugins/channelrx/demodlora/loraplugin.cpp +++ b/plugins/channelrx/demodlora/loraplugin.cpp @@ -7,7 +7,7 @@ const PluginDescriptor LoRaPlugin::m_pluginDescriptor = { QString("LoRa Demodulator"), - QString("3.12.0"), + QString("3.14.5"), QString("(c) 2015 John Greb"), QString("http://www.maintech.de"), true, diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.ui b/plugins/channelrx/demodnfm/nfmdemodgui.ui index 8a1a94f51..070458b1c 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.ui +++ b/plugins/channelrx/demodnfm/nfmdemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -53,7 +53,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -92,7 +101,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -181,7 +190,7 @@ - Monospace + Liberation Mono 8 diff --git a/plugins/channelrx/demodssb/ssbdemodgui.ui b/plugins/channelrx/demodssb/ssbdemodgui.ui index d75d76a66..a481293b3 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.ui +++ b/plugins/channelrx/demodssb/ssbdemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -53,7 +53,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -92,7 +101,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -264,7 +273,7 @@ - Monospace + Liberation Mono 8 @@ -907,7 +916,16 @@ 2 - + + 3 + + + 3 + + + 3 + + 3 @@ -920,7 +938,7 @@ - Monospace + Liberation Mono 8 diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.ui b/plugins/channelrx/demodwfm/wfmdemodgui.ui index 95347ec8a..3373fd7a7 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.ui +++ b/plugins/channelrx/demodwfm/wfmdemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -59,7 +59,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -98,7 +107,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -186,7 +195,7 @@ - Monospace + Liberation Mono 8 diff --git a/plugins/channelrx/demodwfm/wfmplugin.cpp b/plugins/channelrx/demodwfm/wfmplugin.cpp index 98a4c32cb..bc2fa2c04 100644 --- a/plugins/channelrx/demodwfm/wfmplugin.cpp +++ b/plugins/channelrx/demodwfm/wfmplugin.cpp @@ -8,7 +8,7 @@ const PluginDescriptor WFMPlugin::m_pluginDescriptor = { QString("WFM Demodulator"), - QString("3.14.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/tcpsrc/tcpsrcgui.ui b/plugins/channelrx/tcpsrc/tcpsrcgui.ui index 33df165f7..bb9965fa7 100644 --- a/plugins/channelrx/tcpsrc/tcpsrcgui.ui +++ b/plugins/channelrx/tcpsrc/tcpsrcgui.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 @@ -177,7 +177,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -375,7 +375,7 @@ - Sans Serif + Liberation Mono 9 diff --git a/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp b/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp index d7a0193c7..e3925ed20 100644 --- a/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp +++ b/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp @@ -8,7 +8,7 @@ const PluginDescriptor TCPSrcPlugin::m_pluginDescriptor = { QString("TCP Channel Source"), - QString("3.12.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/udpsrc/udpsrcgui.ui b/plugins/channelrx/udpsrc/udpsrcgui.ui index 4365f9077..b0d114948 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.ui +++ b/plugins/channelrx/udpsrc/udpsrcgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -53,7 +53,16 @@ Settings - + + 2 + + + 2 + + + 2 + + 2 @@ -139,7 +148,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -349,7 +358,7 @@ Destination UDP address - 000.000.000.000; + 000.000.000.000 127.0.0.1 @@ -397,7 +406,7 @@ Destination UDP port - 00000; + 00000 9998 @@ -539,7 +548,7 @@ Audio input UDP port - 00000; + 00000 9997 @@ -826,15 +835,24 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 - Sans Serif - 9 + Liberation Mono + 8 diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.cpp b/plugins/channelrx/udpsrc/udpsrcplugin.cpp index 0ad6887e7..c6e52b8f9 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.cpp +++ b/plugins/channelrx/udpsrc/udpsrcplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor UDPSrcPlugin::m_pluginDescriptor = { QString("UDP Channel Source"), - QString("3.14.3"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From bcc3476e9d600c797b12382e5af104198af44cef Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 00:30:43 +0200 Subject: [PATCH 349/956] Channel Tx plugins: use liberation font --- plugins/channeltx/modam/ammodgui.ui | 20 ++++++-- plugins/channeltx/modam/ammodplugin.cpp | 2 +- plugins/channeltx/modatv/atvmodgui.ui | 9 +++- plugins/channeltx/modatv/atvmodplugin.cpp | 2 +- plugins/channeltx/modnfm/nfmmodgui.ui | 9 +++- plugins/channeltx/modnfm/nfmmodplugin.cpp | 2 +- plugins/channeltx/modssb/ssbmodgui.ui | 33 +++++++++++-- plugins/channeltx/modwfm/wfmmodgui.ui | 9 +++- plugins/channeltx/modwfm/wfmmodplugin.cpp | 2 +- plugins/channeltx/udpsink/udpsinkgui.ui | 51 +++++++++++++++------ plugins/channeltx/udpsink/udpsinkplugin.cpp | 2 +- 11 files changed, 108 insertions(+), 33 deletions(-) diff --git a/plugins/channeltx/modam/ammodgui.ui b/plugins/channeltx/modam/ammodgui.ui index 4321def42..ba944b224 100644 --- a/plugins/channeltx/modam/ammodgui.ui +++ b/plugins/channeltx/modam/ammodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -56,7 +56,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -95,7 +104,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -349,6 +358,11 @@ 0 + + + Liberation Mono + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold diff --git a/plugins/channeltx/modam/ammodplugin.cpp b/plugins/channeltx/modam/ammodplugin.cpp index c50ef1d87..a6a629378 100644 --- a/plugins/channeltx/modam/ammodplugin.cpp +++ b/plugins/channeltx/modam/ammodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor AMModPlugin::m_pluginDescriptor = { QString("AM Modulator"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modatv/atvmodgui.ui b/plugins/channeltx/modatv/atvmodgui.ui index 674a0ac3a..c21663ff3 100644 --- a/plugins/channeltx/modatv/atvmodgui.ui +++ b/plugins/channeltx/modatv/atvmodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -98,7 +98,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -503,6 +503,11 @@ 32 + + + Liberation Mono + + video signal level in % of 0:1 range diff --git a/plugins/channeltx/modatv/atvmodplugin.cpp b/plugins/channeltx/modatv/atvmodplugin.cpp index d9e3e26dd..8a99fe362 100644 --- a/plugins/channeltx/modatv/atvmodplugin.cpp +++ b/plugins/channeltx/modatv/atvmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor ATVModPlugin::m_pluginDescriptor = { QString("ATV Modulator"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modnfm/nfmmodgui.ui b/plugins/channeltx/modnfm/nfmmodgui.ui index 3805fd725..ff364bfeb 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.ui +++ b/plugins/channeltx/modnfm/nfmmodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -104,7 +104,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -398,6 +398,11 @@ 0 + + + Liberation Mono + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp index e6c05ab9e..24694c679 100644 --- a/plugins/channeltx/modnfm/nfmmodplugin.cpp +++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor NFMModPlugin::m_pluginDescriptor = { QString("NFM Modulator"), - QString("3.14.1"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modssb/ssbmodgui.ui b/plugins/channeltx/modssb/ssbmodgui.ui index 17d480f3e..1974e70fb 100644 --- a/plugins/channeltx/modssb/ssbmodgui.ui +++ b/plugins/channeltx/modssb/ssbmodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -53,7 +53,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -92,7 +101,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -695,6 +704,11 @@ 0 + + + Liberation Mono + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold @@ -1239,7 +1253,16 @@ 2 - + + 3 + + + 3 + + + 3 + + 3 @@ -1252,7 +1275,7 @@ - Monospace + Liberation Mono 8 diff --git a/plugins/channeltx/modwfm/wfmmodgui.ui b/plugins/channeltx/modwfm/wfmmodgui.ui index 5291a1f21..464c5e07c 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.ui +++ b/plugins/channeltx/modwfm/wfmmodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -104,7 +104,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -398,6 +398,11 @@ 0 + + + Liberation Mono + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp index 6aca86f4e..9c723b505 100644 --- a/plugins/channeltx/modwfm/wfmmodplugin.cpp +++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor WFMModPlugin::m_pluginDescriptor = { QString("WFM Modulator"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsink/udpsinkgui.ui index cd9049d87..d346f0164 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsink/udpsinkgui.ui @@ -24,19 +24,19 @@ - Sans Serif + Liberation Sans 9 UDP Sample Sink + + -1 + UDP Sample Sink - - -1 - @@ -56,7 +56,16 @@ Settings - + + 2 + + + 2 + + + 2 + + 2 @@ -83,7 +92,7 @@ Input sample rate (S/s) - 0009999; + 0009999 48000 @@ -113,7 +122,7 @@ Signal bandwidth (Hz) - 0009999; + 0009999 32000 @@ -199,6 +208,11 @@ 0 + + + Liberation Mono + + Amplitude meter in % of maximum @@ -497,7 +511,7 @@ FM deviation in Hz - 00000; + 00000 2500 @@ -523,7 +537,7 @@ Percentage of AM modulation - 000; + 000 95 @@ -664,7 +678,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -800,7 +814,7 @@ Local UDP address - 000.000.000.000; + 000.000.000.000 127.0.0.1 @@ -844,7 +858,7 @@ Local UDP port - 00000; + 00000 9998 @@ -954,14 +968,23 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 - Sans Serif + Liberation Mono 9 diff --git a/plugins/channeltx/udpsink/udpsinkplugin.cpp b/plugins/channeltx/udpsink/udpsinkplugin.cpp index e02545094..c72836c08 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.cpp +++ b/plugins/channeltx/udpsink/udpsinkplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { QString("UDP Channel Sink"), - QString("3.14.3"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 8281058b2418e866f5a0994c759813c6b9500365 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 00:40:09 +0200 Subject: [PATCH 350/956] Sample sink plugins: use liberation font --- .../bladerfoutput/bladerfoutputgui.ui | 6 ++--- .../bladerfoutput/bladerfoutputplugin.cpp | 2 +- plugins/samplesink/filesink/filesinkgui.ui | 6 ++--- .../samplesink/filesink/filesinkplugin.cpp | 2 +- .../hackrfoutput/hackrfoutputgui.ui | 17 ++++++++++---- .../hackrfoutput/hackrfoutputplugin.cpp | 2 +- .../limesdroutput/limesdroutputgui.ui | 23 +++++++++++++------ .../limesdroutput/limesdroutputplugin.cpp | 2 +- .../plutosdroutput/plutosdroutputgui.ui | 10 ++++---- .../plutosdroutput/plutosdroutputplugin.cpp | 2 +- .../sdrdaemonsink/sdrdaemonsinkgui.ui | 6 ++--- .../sdrdaemonsink/sdrdaemonsinkplugin.cpp | 2 +- 12 files changed, 49 insertions(+), 31 deletions(-) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputgui.ui b/plugins/samplesink/bladerfoutput/bladerfoutputgui.ui index d6902578b..d8ce68767 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputgui.ui +++ b/plugins/samplesink/bladerfoutput/bladerfoutputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -127,7 +127,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -328,7 +328,7 @@ - Bitstream Vera Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp index cd861aea4..92dab83a8 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor BladerfOutputPlugin::m_pluginDescriptor = { QString("BladeRF Output"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/filesink/filesinkgui.ui b/plugins/samplesink/filesink/filesinkgui.ui index b500b42d4..415c9e3a9 100644 --- a/plugins/samplesink/filesink/filesinkgui.ui +++ b/plugins/samplesink/filesink/filesinkgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -130,7 +130,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -304,7 +304,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/filesink/filesinkplugin.cpp b/plugins/samplesink/filesink/filesinkplugin.cpp index 1944c6bed..468ad020a 100644 --- a/plugins/samplesink/filesink/filesinkplugin.cpp +++ b/plugins/samplesink/filesink/filesinkplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor FileSinkPlugin::m_pluginDescriptor = { QString("File sink output"), - QString("3.9.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui index 4b8c889ee..858dd7c71 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui +++ b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -35,7 +35,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -121,7 +130,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -311,7 +320,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp b/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp index a1140bf03..6314d7cb5 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor HackRFOutputPlugin::m_pluginDescriptor = { QString("HackRF Output"), - QString("3.14.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.ui b/plugins/samplesink/limesdroutput/limesdroutputgui.ui index b7bb79f87..19d8d00e4 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.ui +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -35,7 +35,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -118,7 +127,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -223,7 +232,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -458,7 +467,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -517,7 +526,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -575,7 +584,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp index 61a2c30a8..d10f5c0c1 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp @@ -34,7 +34,7 @@ const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = { QString("LimeSDR Output"), - QString("3.14.3"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui b/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui index a996fa364..f72004af0 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui +++ b/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -127,7 +127,7 @@ - DejaVu Sans Mono + Liberation Mono 20 50 false @@ -408,7 +408,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -465,7 +465,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -529,7 +529,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp b/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp index 188cf223c..66347e51a 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp @@ -28,7 +28,7 @@ class DeviceSourceAPI; const PluginDescriptor PlutoSDROutputPlugin::m_pluginDescriptor = { QString("PlutoSDR Output"), - QString("3.10.1"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 676cebecc..fc4484eb5 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -130,7 +130,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -202,7 +202,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp index da24162f8..c5e0cd345 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp @@ -26,7 +26,7 @@ const PluginDescriptor SDRdaemonSinkPlugin::m_pluginDescriptor = { QString("SDRdaemon sink output"), - QString("3.9.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From e0d3c77fc3ad9d8845afa1291dae31bf09ef225b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 00:55:42 +0200 Subject: [PATCH 351/956] Sample source plugins: use liberation font --- plugins/samplesource/airspy/airspygui.ui | 4 ++-- plugins/samplesource/airspyhf/airspyhfgui.ui | 15 +++++++++--- .../samplesource/airspyhf/airspyhfplugin.cpp | 2 +- .../bladerfinput/bladerfinputgui.ui | 19 +++++++++++---- plugins/samplesource/fcdpro/fcdprogui.ui | 4 ++-- .../samplesource/fcdproplus/fcdproplusgui.ui | 4 ++-- .../filesource/filesourceplugin.cpp | 2 +- .../hackrfinput/hackrfinputgui.ui | 6 ++--- .../limesdrinput/limesdrinputgui.ui | 23 +++++++++++++------ plugins/samplesource/perseus/perseusgui.ui | 4 ++-- .../samplesource/perseus/perseusplugin.cpp | 2 +- .../plutosdrinput/plutosdrinputgui.ui | 12 ++++------ plugins/samplesource/rtlsdr/rtlsdrgui.ui | 19 +++++++++++---- .../sdrdaemonsource/sdrdaemonsourcegui.ui | 8 +++---- .../sdrdaemonsource/sdrdaemonsourceplugin.cpp | 2 +- plugins/samplesource/sdrplay/sdrplaygui.ui | 4 ++-- 16 files changed, 82 insertions(+), 48 deletions(-) diff --git a/plugins/samplesource/airspy/airspygui.ui b/plugins/samplesource/airspy/airspygui.ui index 722d960e7..b767aee82 100644 --- a/plugins/samplesource/airspy/airspygui.ui +++ b/plugins/samplesource/airspy/airspygui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/airspyhf/airspyhfgui.ui b/plugins/samplesource/airspyhf/airspyhfgui.ui index d28e3066b..73334a081 100644 --- a/plugins/samplesource/airspyhf/airspyhfgui.ui +++ b/plugins/samplesource/airspyhf/airspyhfgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -35,7 +35,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -126,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/airspyhf/airspyhfplugin.cpp b/plugins/samplesource/airspyhf/airspyhfplugin.cpp index aa436df7c..2bdd3c46e 100644 --- a/plugins/samplesource/airspyhf/airspyhfplugin.cpp +++ b/plugins/samplesource/airspyhf/airspyhfplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor AirspyHFPlugin::m_pluginDescriptor = { QString("AirspyHF Input"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/bladerfinput/bladerfinputgui.ui b/plugins/samplesource/bladerfinput/bladerfinputgui.ui index 3d880f06f..1d53891d7 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputgui.ui +++ b/plugins/samplesource/bladerfinput/bladerfinputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -35,7 +35,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -127,7 +136,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -225,7 +234,7 @@ XB200 board mode - + None @@ -322,7 +331,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesource/fcdpro/fcdprogui.ui b/plugins/samplesource/fcdpro/fcdprogui.ui index d7a31d4c2..5e3d747d7 100644 --- a/plugins/samplesource/fcdpro/fcdprogui.ui +++ b/plugins/samplesource/fcdpro/fcdprogui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/fcdproplus/fcdproplusgui.ui b/plugins/samplesource/fcdproplus/fcdproplusgui.ui index 82431d645..983ee0050 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusgui.ui +++ b/plugins/samplesource/fcdproplus/fcdproplusgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/filesource/filesourceplugin.cpp b/plugins/samplesource/filesource/filesourceplugin.cpp index f4a962afd..10057f205 100644 --- a/plugins/samplesource/filesource/filesourceplugin.cpp +++ b/plugins/samplesource/filesource/filesourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor FileSourcePlugin::m_pluginDescriptor = { QString("File source input"), - QString("3.11.1"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/hackrfinput/hackrfinputgui.ui b/plugins/samplesource/hackrfinput/hackrfinputgui.ui index 0fd2172f7..7a075d010 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputgui.ui +++ b/plugins/samplesource/hackrfinput/hackrfinputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -132,7 +132,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -355,7 +355,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.ui b/plugins/samplesource/limesdrinput/limesdrinputgui.ui index 5d79b1103..a41f0c50e 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.ui +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -35,7 +35,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -133,7 +142,7 @@ - DejaVu Sans Mono + Liberation Mono 20 50 false @@ -246,7 +255,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -503,7 +512,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -564,7 +573,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -624,7 +633,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesource/perseus/perseusgui.ui b/plugins/samplesource/perseus/perseusgui.ui index c448f4904..b6d1fbd00 100644 --- a/plugins/samplesource/perseus/perseusgui.ui +++ b/plugins/samplesource/perseus/perseusgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/perseus/perseusplugin.cpp b/plugins/samplesource/perseus/perseusplugin.cpp index 1cfb595bb..241bd3171 100644 --- a/plugins/samplesource/perseus/perseusplugin.cpp +++ b/plugins/samplesource/perseus/perseusplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor PerseusPlugin::m_pluginDescriptor = { QString("Perseus Input"), - QString("3.12.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui b/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui index 853cbad14..4f89275e7 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -142,10 +142,8 @@ - DejaVu Sans Mono + Liberation Mono 20 - 50 - false @@ -533,7 +531,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -590,7 +588,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -654,7 +652,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.ui b/plugins/samplesource/rtlsdr/rtlsdrgui.ui index 25431b5a5..79b0dd127 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrgui.ui +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -35,7 +35,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 @@ -126,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -367,7 +376,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -507,7 +516,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui index 65959b6e5..d13313191 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui @@ -18,7 +18,7 @@ - Sans Serif + Liberation Sans 9 @@ -132,7 +132,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -752,7 +752,7 @@ - DejaVu Sans Mono + Liberation Mono 12 false @@ -870,7 +870,7 @@ - DejaVu Sans Mono + Liberation Mono 12 false diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp index 4c2794252..fa46eeb2d 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp @@ -26,7 +26,7 @@ const PluginDescriptor SDRdaemonSourcePlugin::m_pluginDescriptor = { QString("SDRdaemon source input"), - QString("3.14.2"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/sdrplay/sdrplaygui.ui b/plugins/samplesource/sdrplay/sdrplaygui.ui index 95d7ecb82..22ce537d1 100644 --- a/plugins/samplesource/sdrplay/sdrplaygui.ui +++ b/plugins/samplesource/sdrplay/sdrplaygui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -136,7 +136,7 @@ - DejaVu Sans Mono + Liberation Mono 20 From 6b1123009de3a76386fb742e3a728b1928a2552d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 01:17:04 +0200 Subject: [PATCH 352/956] FCD: bumped plugins version --- fcdlib/fcdtraits.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fcdlib/fcdtraits.cpp b/fcdlib/fcdtraits.cpp index 2ace765d2..01fd745eb 100644 --- a/fcdlib/fcdtraits.cpp +++ b/fcdlib/fcdtraits.cpp @@ -22,8 +22,8 @@ const char *fcd_traits::displayedName = "FunCube Dongle Pro+"; const char *fcd_traits::pluginDisplayedName = "FunCube Pro Input"; const char *fcd_traits::pluginDisplayedName = "FunCube Pro+ Input"; -const char *fcd_traits::pluginVersion = "3.9.0"; -const char *fcd_traits::pluginVersion = "3.9.0"; +const char *fcd_traits::pluginVersion = "3.14.5"; +const char *fcd_traits::pluginVersion = "3.14.5"; const int64_t fcd_traits::loLowLimitFreq = 64000000L; const int64_t fcd_traits::loLowLimitFreq = 150000L; From 56c0aaedcdaeddeb7208ba20b6c584051cfb12c5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 02:39:39 +0200 Subject: [PATCH 353/956] Mag AGC: corrected step calculation. Added method to combine step up and down smoothing --- plugins/channelrx/demodssb/ssbdemod.cpp | 6 +++--- plugins/channelrx/udpsrc/udpsrc.cpp | 2 +- plugins/channeltx/modssb/ssbmod.cpp | 7 +++++-- plugins/channeltx/modssb/ssbmod.h | 1 + sdrbase/dsp/agc.cpp | 20 +++++++++++++++----- sdrbase/dsp/agc.h | 3 ++- 6 files changed, 27 insertions(+), 12 deletions(-) diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index a3320cffd..38e5c51d7 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -220,7 +220,7 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { - fftfilt::cmplx z = delayedSample * m_agc.getStepDownValue(); + fftfilt::cmplx z = delayedSample * m_agc.getStepValue(); if (m_audioBinaual) { @@ -405,7 +405,7 @@ void SSBDemod::applyAudioSampleRate(int sampleRate) if (m_agcNbSamples != agcNbSamples) { - m_agc.resize(agcNbSamples, agcTarget); + m_agc.resize(agcNbSamples, std::min(sampleRate/20, agcNbSamples/2), agcTarget); m_agc.setStepDownDelay(agcNbSamples); m_agcNbSamples = agcNbSamples; } @@ -503,7 +503,7 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) if (m_agcNbSamples != agcNbSamples) { m_settingsMutex.lock(); - m_agc.resize(agcNbSamples, agcTarget); + m_agc.resize(agcNbSamples, std::min((int)m_audioSampleRate/20, agcNbSamples/2), agcTarget); m_agc.setStepDownDelay(agcNbSamples); m_agcNbSamples = agcNbSamples; m_settingsMutex.unlock(); diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index 09370909b..191700b1f 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -524,7 +524,7 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_squelchRelease = (settings.m_outputSampleRate * settings.m_squelchGate) / 100; initSquelch(m_squelchOpen); - m_agc.resize(settings.m_outputSampleRate * 0.2, m_agcTarget); // Fixed 200 ms + m_agc.resize(settings.m_outputSampleRate/5, settings.m_outputSampleRate/20, m_agcTarget); // Fixed 200 ms int stepDownDelay = (settings.m_outputSampleRate * (settings.m_squelchGate == 0 ? 1 : settings.m_squelchGate))/100; m_agc.setStepDownDelay(stepDownDelay); m_agc.setGate(settings.m_outputSampleRate * 0.05); diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index 04daf762b..b31332d19 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -68,7 +68,8 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : m_levelCalcCount(0), m_peakLevel(0.0f), m_levelSum(0.0f), - m_inAGC(9600, 0.2, 1e-4) + m_inAGC(9600, 0.2, 1e-4), + m_agcStepLength(2400) { setObjectName(m_channelId); @@ -690,6 +691,8 @@ void SSBMod::applyAudioSampleRate(int sampleRate) m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); m_cwKeyer.setSampleRate(sampleRate); + m_agcStepLength = std::min(sampleRate/20, m_settings.m_agcTime/2); // 50 ms or half the AGC length whichever is smaller + m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; @@ -796,7 +799,7 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) (settings.m_agcOrder != m_settings.m_agcOrder) || force) { m_settingsMutex.lock(); - m_inAGC.resize(settings.m_agcTime, settings.m_agcOrder); + m_inAGC.resize(settings.m_agcTime, m_agcStepLength, settings.m_agcOrder); m_settingsMutex.unlock(); } diff --git a/plugins/channeltx/modssb/ssbmod.h b/plugins/channeltx/modssb/ssbmod.h index 4276f3b30..72cad1980 100644 --- a/plugins/channeltx/modssb/ssbmod.h +++ b/plugins/channeltx/modssb/ssbmod.h @@ -309,6 +309,7 @@ private: CWKeyer m_cwKeyer; MagAGC m_inAGC; + int m_agcStepLength; static const int m_levelNbSamples; diff --git a/sdrbase/dsp/agc.cpp b/sdrbase/dsp/agc.cpp index 82f2f59b7..1a94b7457 100644 --- a/sdrbase/dsp/agc.cpp +++ b/sdrbase/dsp/agc.cpp @@ -10,8 +10,6 @@ #include "util/stepfunctions.h" -#define StepLengthMax 2400 // 50ms - AGC::AGC(int historySize, double R) : m_u0(1.0), m_R(R), @@ -49,7 +47,7 @@ MagAGC::MagAGC(int historySize, double R, double threshold) : m_threshold(threshold), m_thresholdEnable(true), m_gate(0), - m_stepLength(std::min(StepLengthMax, historySize/2)), + m_stepLength(std::min(2400, historySize/2)), // max 50 ms (at 48 kHz) m_stepDelta(1.0/m_stepLength), m_stepUpCounter(0), m_stepDownCounter(m_stepLength), @@ -63,10 +61,10 @@ MagAGC::MagAGC(int historySize, double R, double threshold) : MagAGC::~MagAGC() {} -void MagAGC::resize(int historySize, Real R) +void MagAGC::resize(int historySize, int stepLength, Real R) { m_R2 = R*R; - m_stepLength = std::min(StepLengthMax, historySize/2); + m_stepLength = stepLength; m_stepDelta = 1.0 / m_stepLength; m_stepUpCounter = 0; m_stepDownCounter = m_stepLength; @@ -188,3 +186,15 @@ float MagAGC::getStepDownValue() const return StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta); } } + +float MagAGC::getStepValue() const +{ + if (m_count < m_stepDownDelay) + { + return StepFunctions::smootherstep(m_stepUpCounter * m_stepDelta); // step up + } + else + { + return StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta); // step down + } +} diff --git a/sdrbase/dsp/agc.h b/sdrbase/dsp/agc.h index 75f3959c7..4e0e2939c 100644 --- a/sdrbase/dsp/agc.h +++ b/sdrbase/dsp/agc.h @@ -39,7 +39,7 @@ public: MagAGC(int historySize, double R, double threshold); virtual ~MagAGC(); void setSquared(bool squared) { m_squared = squared; } - void resize(int historySize, Real R); + void resize(int historySize, int stepLength, Real R); void setOrder(double R); virtual void feed(Complex& ci); double feedAndGetValue(const Complex& ci); @@ -52,6 +52,7 @@ public: void setClampMax(double clampMax) { m_clampMax = clampMax; } int getStepDownDelay() const { return m_stepDownDelay; } float getStepDownValue() const; + float getStepValue() const; private: bool m_squared; //!< use squared magnitude (power) to compute AGC value From b9a19577c782438a131c0c7a43ab096427990ffb Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 14:50:13 +0200 Subject: [PATCH 354/956] SSB demod: squelch: change ramp up/down constant to half of the averaging time --- plugins/channelrx/demodssb/ssbdemod.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 38e5c51d7..e75b562a4 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -405,7 +405,7 @@ void SSBDemod::applyAudioSampleRate(int sampleRate) if (m_agcNbSamples != agcNbSamples) { - m_agc.resize(agcNbSamples, std::min(sampleRate/20, agcNbSamples/2), agcTarget); + m_agc.resize(agcNbSamples, agcNbSamples/2, agcTarget); m_agc.setStepDownDelay(agcNbSamples); m_agcNbSamples = agcNbSamples; } @@ -503,7 +503,7 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) if (m_agcNbSamples != agcNbSamples) { m_settingsMutex.lock(); - m_agc.resize(agcNbSamples, std::min((int)m_audioSampleRate/20, agcNbSamples/2), agcTarget); + m_agc.resize(agcNbSamples, agcNbSamples/2, agcTarget); m_agc.setStepDownDelay(agcNbSamples); m_agcNbSamples = agcNbSamples; m_settingsMutex.unlock(); From 161d4a5e56bfaf6c5658da977800129f1ff42929 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 17:51:18 +0200 Subject: [PATCH 355/956] Set spectrum font to Liberation Sans --- sdrgui/device/deviceuiset.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdrgui/device/deviceuiset.cpp b/sdrgui/device/deviceuiset.cpp index 7c77bec2c..7999b2486 100644 --- a/sdrgui/device/deviceuiset.cpp +++ b/sdrgui/device/deviceuiset.cpp @@ -53,10 +53,9 @@ DeviceUISet::DeviceUISet(int tabIndex, bool rxElseTx, QTimer& timer) // m_spectrum needs to have its font to be set since it cannot be inherited from the main window QFont font; - font.setFamily(QStringLiteral("Sans Serif")); + font.setFamily(QStringLiteral("Liberation Sans")); font.setPointSize(9); m_spectrum->setFont(font); - } DeviceUISet::~DeviceUISet() From 8633adf3451be795f6c8bfa27c352ca0c8ac0b6b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 19:17:50 +0200 Subject: [PATCH 356/956] Sampling device dialog: set font --- sdrgui/gui/samplingdevicedialog.ui | 33 +++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/sdrgui/gui/samplingdevicedialog.ui b/sdrgui/gui/samplingdevicedialog.ui index b3aa657e0..2615d2885 100644 --- a/sdrgui/gui/samplingdevicedialog.ui +++ b/sdrgui/gui/samplingdevicedialog.ui @@ -2,6 +2,12 @@ SamplingDeviceDialog + + + Liberation Sans + 9 + + 0 @@ -10,12 +16,6 @@ 139 - - - Liberation Sans - 9 - - Select sampling device @@ -28,18 +28,37 @@ 70 + + + Liberation Sans + 9 + + Select from list - + + + + Liberation Sans + 9 + + + + + + Liberation Sans + 9 + + Qt::Horizontal From 1ac7ceae800029b2c4afba8268d565311cf170b4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 20:27:30 +0200 Subject: [PATCH 357/956] Added mention in the main readme for udev rules installation after Debian package installation --- Readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Readme.md b/Readme.md index e57fa8762..f3a7cacb2 100644 --- a/Readme.md +++ b/Readme.md @@ -289,9 +289,12 @@ Since apt-get v 1.1 installation is possible from a local file: - cd to where the archive has been downloaded - `sudo apt-get install ./sdrangel_vx.y.z-1_amd64.deb` where x.y.z is the version number + - `sudo apt-get -f install` this will install missing dependencies The software is installed in `/opt/sdrangel` you can start it from the command line with: - `/opt/sdrangel/bin/sdrangel` + +**⚠** The udev rules are not set by the package installation so you will have to set it manually in order to be able to access the various SDR hardware. The `udev-rules` folder contains the rules file and the `install.sh` script that you can run as sudo to install all rules files. You may also adapt the script to copy only the required files.

Windows distribution

From 97677075b189862de28c585bef16868ab93b1570 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 6 May 2018 22:56:24 +0200 Subject: [PATCH 358/956] AM demod: fixed delayed squelch --- debian/changelog | 3 +- plugins/channelrx/demodam/amdemod.cpp | 4 +-- plugins/channelrx/demodam/amdemod.h | 46 ++++++++------------------- 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/debian/changelog b/debian/changelog index 56aed61cb..dc80e9307 100644 --- a/debian/changelog +++ b/debian/changelog @@ -4,8 +4,9 @@ sdrangel (3.14.5-1) unstable; urgency=medium * Added a benchmark program testing decimators * Optimization of decimators using even/odd technique * SSB mod: fixed channel unregistration + * AM demod: fixed delayed squelch - -- Edouard Griffiths, F4EXB Fri, 04 May 2018 20:14:18 +0200 + -- Edouard Griffiths, F4EXB Sun, 06 May 2018 20:14:18 +0200 sdrangel (3.14.4-1) unstable; urgency=medium diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index 14729bf50..fafa7fb69 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -49,7 +49,7 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_inputFrequencyOffset(0), m_running(false), m_squelchOpen(false), - m_squelchDelayLine(12000), + m_squelchDelayLine(9600), m_magsqSum(0.0f), m_magsqPeak(0.0f), m_magsqCount(0), @@ -232,7 +232,7 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); m_audioFifo.setSize(sampleRate); - m_squelchDelayLine.resize(sampleRate/4); + m_squelchDelayLine.resize(sampleRate/5); m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index 2e730d732..9c32eb8ff 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -30,7 +30,6 @@ #include "audio/audiofifo.h" #include "util/message.h" #include "util/doublebufferfifo.h" - #include "amdemodsettings.h" class DeviceSourceAPI; @@ -183,8 +182,8 @@ private: void processOneSample(Complex &ci) { - Real re = ci.real() / SDR_RX_SCALED; - Real im = ci.imag() / SDR_RX_SCALED; + Real re = ci.real() / SDR_RX_SCALEF; + Real im = ci.imag() / SDR_RX_SCALEF; Real magsq = re*re + im*im; m_movingAverage(magsq); m_magsq = m_movingAverage.asDouble(); @@ -197,43 +196,28 @@ private: m_magsqCount++; -// if (m_magsq >= m_squelchLevel) -// { -// if (m_squelchCount <= m_audioSampleRate / 10) -// { -// m_squelchCount++; -// } -// } -// else -// { -// if (m_squelchCount > 1) -// { -// m_squelchCount -= 2; -// } -// } + m_squelchDelayLine.write(magsq); - if (m_magsq >= m_squelchLevel) - { - if (m_squelchCount < m_audioSampleRate / 10) { - m_squelchCount++; - } - - m_squelchDelayLine.write(magsq); - } - else + if (m_magsq < m_squelchLevel) { if (m_squelchCount > 0) { m_squelchCount--; } - - m_squelchDelayLine.write(0); + } + else + { + if (m_squelchCount < m_audioSampleRate / 10) { + m_squelchCount++; + } } qint16 sample; - if ((m_squelchCount >= m_audioSampleRate / 20) && !m_settings.m_audioMute) + m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); + + if (m_squelchOpen && !m_settings.m_audioMute) { - Real demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate / 20)); + Real demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); m_volumeAGC.feed(demod); demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); @@ -245,12 +229,10 @@ private: Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); sample = demod * attack * (m_audioSampleRate/24) * m_settings.m_volume; - m_squelchOpen = true; } else { sample = 0; - m_squelchOpen = false; } m_audioBuffer[m_audioBufferFill].l = sample; From c9e14d1f2dbfbd4220a56b4fc464e74fdeb8b80c Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 7 May 2018 01:04:34 +0200 Subject: [PATCH 359/956] Scale engine: add one more space to avoid horizontal scale figure collision --- sdrgui/gui/scaleengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/gui/scaleengine.cpp b/sdrgui/gui/scaleengine.cpp index 2683bafbc..99a5bc3c8 100644 --- a/sdrgui/gui/scaleengine.cpp +++ b/sdrgui/gui/scaleengine.cpp @@ -297,7 +297,7 @@ int ScaleEngine::calcTickTextSize() calcMajorTickUnits((m_rangeMax - m_rangeMin) / m_scale, &decimalPlaces); - return tickLen + decimalPlaces + 1; + return tickLen + decimalPlaces + 2; } void ScaleEngine::forceTwoTicks() From 606c83e484b0d7e5514a6d9c80df7cf156148717 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 7 May 2018 02:15:24 +0200 Subject: [PATCH 360/956] Scale engine: use +20% ratio (1.2) to ensure proper horizontal scale text spacing --- sdrgui/gui/scaleengine.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/sdrgui/gui/scaleengine.cpp b/sdrgui/gui/scaleengine.cpp index 99a5bc3c8..20eab2fe8 100644 --- a/sdrgui/gui/scaleengine.cpp +++ b/sdrgui/gui/scaleengine.cpp @@ -297,7 +297,7 @@ int ScaleEngine::calcTickTextSize() calcMajorTickUnits((m_rangeMax - m_rangeMin) / m_scale, &decimalPlaces); - return tickLen + decimalPlaces + 2; + return tickLen + decimalPlaces + 1; } void ScaleEngine::forceTwoTicks() @@ -358,11 +358,16 @@ void ScaleEngine::reCalc() if(m_orientation == Qt::Vertical) { maxNumMajorTicks = (int)(m_size / (fontMetrics.lineSpacing() * 1.3f)); - } else { - majorTickSize = (calcTickTextSize() + 2) * m_charSize; - if(majorTickSize != 0.0) - maxNumMajorTicks = (int)(m_size / majorTickSize); - else maxNumMajorTicks = 20; + } + else + { + majorTickSize = (calcTickTextSize() + 2) * m_charSize * 1.2f; + + if(majorTickSize != 0.0) { + maxNumMajorTicks = (int)(m_size / majorTickSize); + } else { + maxNumMajorTicks = 20; + } } m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces); From 4e389d77e1a1a38849fbc4bb86062a7b92f632cc Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 7 May 2018 03:14:18 +0200 Subject: [PATCH 361/956] Try to add fonts to GUI resources and add to font database from main window --- sdrgui/mainwindow.cpp | 8 ++++++++ sdrgui/resources/LiberationMono-Regular.ttf | Bin 0 -> 108492 bytes sdrgui/resources/LiberationSans-Regular.ttf | Bin 0 -> 139764 bytes sdrgui/resources/res.qrc | 2 ++ 4 files changed, 10 insertions(+) create mode 100644 sdrgui/resources/LiberationMono-Regular.ttf create mode 100644 sdrgui/resources/LiberationSans-Regular.ttf diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index dae1f235c..d41da2f39 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -101,6 +102,13 @@ MainWindow::MainWindow(qtwebapp::LoggerWithFile *logger, const MainParser& parse m_instance = this; m_settings.setAudioDeviceManager(m_dspEngine->getAudioDeviceManager()); + QFontDatabase::addApplicationFont(":/LiberationSans-Regular.ttf"); + QFontDatabase::addApplicationFont(":/LiberationMono-Regular.ttf"); + + QFont font("Liberation Sans"); + font.setPointSize(9); + qApp->setFont(font); + ui->setupUi(this); createStatusBar(); diff --git a/sdrgui/resources/LiberationMono-Regular.ttf b/sdrgui/resources/LiberationMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..690e112f33b2845a025b92fc7f1fec839ac24f51 GIT binary patch literal 108492 zcmdSBd0Z1`{y6?T&&=e8BtW)z+vrvK4?nxze!E>=b=&RkXIoph+xF9~629+e0@m$*_x1Vb_s=iM zOlF>$dFFZE_censLdXwREYdj~wUrG^*F_=xoEuuhOH1qy@*5Nl`{A%3Tk5KsHs{#l zt*}1|`?}I;bwzTEH5K+tV1G*0G)w9~bxX4l!bf4hZO+13z4fCx4ur%F2#G$Nvt&S5 z894K0ghGxWMDjX&=PvC3tpV=kn+)f0b7%GUqF^M2{uFS%YVLyNorcF7euI!c7U6kU z=XK0#CqEp5{%`^u&zJ`d@Y*7V+Vgm*%J0nhXx#2d)zx z8+#o3cz680@h{mMLQmdf-Ze{G$kR9qInglU;7S#;peBTvL?#F^u+OmX;!!w)xP6=z zx{UDtGqX@9@sn^w%t{I22+JNqGNaw<` z2OU7`&{lj0x1j;yI}=g-97cVx97b;l|Gz?R*uIS(fpzD^QiUqeHY9^{qv2bI_o065 zi`I?b#XF(j(`Ypsfu2Xuc61(=LG%S$kLC#Nr=j(0^c;E}j@Xd~&4iIYiasI!(6)du za8(wJDh>7z;wq#?@1a#NGA;C63bV+Gh-vF4wx{2?0m;wDhER26O9HHLfj`0`O9SI$*g15IO(@f-e$Sx9kh2C%&e5-LHP~`wVkH%31{sYbgF1R!PKAex~di*`~7;GO# zH<1F?Ww3O@%nt*7QJUKTdr!mv0vJ^^@MbhhLAh`|8jhZT`8@_lA3?_fV!g0`4qEm= z+ZRCV`_T+^5oR9<*mlDnykcSR=(y)TKTvL|fHUdHhEXeOMD=h@7o0a8w$h+&3p$UbFp6~`D@FvG`51{{ zRxiT{UWV~fdMZK=-~~6@2kkS)$5A(W2YA&1JVNO(9j@O2xT178h(cg4>F8sD>RtvO z_y+E$xxWBzfY+y@=f}T;?_InVZ^tco8#;+j!S^gW3ld=vK>r=q;3m8X-5$S--oYIx z3b{}sYK9|`Fjf{|+zR8BA|uL161-$`fvqmUQy0+lAaLPS6ajPGk3I+dP#Mw>%LwWx zk?7as-;qcXN!Q**7!QmIcauomjk~@3|7m;j>>vMOhQfz*PHZei1kc0s0P;#0gA%x= zA29k7jMxbC3WIMb&O=84q9AM0S{w=#(u->0I{;M3!SV<6J8VQh0PX$)v*;dH3q3Qi zrb{@?A3p3mut>5zA8QI0rZt0hO=BtME=dPQu79$=l>QvyvHMwlGgI@3F1yj>vV9 z|In#*8eOC=N@vm~>#VvQU7oH$SE5^`JED6|cU;fu{q=$RD7{Iaq@SsOOn)?rM2Vu5 zQGQXHsL&{FR9uuf${95)s>47GDuW)TMT`og${1h_HijFcjS0pyW1ew=aoD)lxY4-9 z_^9zI<8k9D>WE8{aqn-uRg*&%~RGOl_t)rcTp>y98(pf!;U-BmaQ}leb{x zAHm4i!pOHVPqT;}jU0~rTBp+a>w1ZvErdom+po_1Udkw{G6LaqHt-hi*N5>!Djq zZ)tD&-BR7+ZjqaJZ@z!?z|Ac;tv8cz#@>v%DZQz`8F^E4GvLPeH*VdyaijP~#*LWk zW!Ih8ZP)XzXJ5~_ZoQsx-FQ9ndeC+D+TX8zdF}ISf4%nSYrnttU)SEb_KRzmul?-W zxoa<9d-2*+*QQ=8zc%Gs;x+TN_-irZM+MqB3sC?6--ki7L2tK%M(6~KVIFXOH)z}i zXdzfRi%<`6Qy<7#%JYM030jJlq2*`@Jp_{LVYCu7_b^Dw)o2Y`3o>jyNb-$n6G(^6 zAQ88MZrKi+d34?w4Wi2e^~*WZI&{R6s&uA@Jq8zA*>fqeZ0eTx2! zK0|*&e?|Wdvf^_=_kM}KLU+*LL7V;qB=EQBJM=$bY5fzV$z600jUf*j#|UFg!1iK6 z+K8|iORyBnupBF}5B9}Mtio#Shy8H?$l^d8goAMi4h4-Fjw5g+)?yvj<0x#v(b$Mh zI0nb!I2@17H~}Z(By7RSI0Y;sD^A1dI0I*beUy!Ja4ycn`M3bvkcdzljS?W0Uk`pT zNK5Z$^8dl34{m#}WI!WuAoau`ZKa@@<)8z6kS|DG71&>X$R9MI2J}V{_yHj(6l8BW zXog6zdvr(-y2Sw6(TGea2F0Q{6pzecX(pm1u$7ZRpQj=#Nbq#9TQfnfW`mB-MR_P6 z6(AepQ6Xr@Vz6@UpbtyI&Mre!P&w$_O3*%4ptY(|4Vng0y$IhKQGeA|xfUYQ2y$Ezgsp=$9)G45;vp`j^0!0Bm zfj!zQaKb#I0ZKmz)Za~FfbP3UAyD>Sl85?8F)0B)86@c>i)7-h#0I=F1p4w>pvzUj zNef9X<_S+SfP)?)fuL1}fj1tb6hI_IN>oHm6vPL;LRO=TI199=frNr~*+m{B0ca`l zC4M9b_-O>VYBTVXmxs0p{DXjxDCazm{*KFW4e9|KpbERtGCU3JuL^{Cdt*ag?X;R| zSJl+Yit;IC&QgcHq`0V%w-w~)<>q8(WoD$OBwLaa6Jldb#%M#7UK<>sRw;dbQ=6ML8Jx~Ux^I{T4YM9-XmjhJ$?>n}xOMcpS#aoI&g9|D&Yzsg zd(Y(WpNUnvJd~H1ptBov?srQJx^uXxx&gMgmKYj!?%Tq4rLfJKgdHE)(d*#~ojrJ7 ziO!ANbauC6$-EJJTS;O9J}H+K8;Uz*i3#YWOb(lJ*mB1jdQalm0xWEhSbNS%f+Rll zwr<8~pVjVmRX5m6!u0ya#Dp@puK{|3L#SBj(k&`>i-j(AU2YgU+N?X7aA{=AITf1S zW>&Nt+GovZa5J;u+7ZS+GP1#~HoM~tCGNPPPlExD9qt4}iQR3cy_VP9zfbv(x5RGF zs50nAzC|z{!|g9%9uKt4nrIOjRo^0d&;ghl8F3hNj*+&JS?9)wXB%`X!^la6Vx-p& z5Js*BH-`3CH;1_$TN>S}ws|;bVzQ2!a<_kVbAy{09lCk5VEXX08T46UdNoWy0Eh#w z|9>Aw00;mFz*MiN6W@G}N3&taJzU-3-PfVnVW$vpF*mwN8$EJq@`$F69vPlIa{ux+ z17Nd!TEmE&HI}s->;U77XTH~OGaP?Nbvp6`*%AGZeUXT>9P&b4eiHnu(wV8mdp!=JL(b>+)lF>^|cLd zz67Ab&zc|$`^jVrTrsN+P}D^+S>52a7<%0Sh9WQBKm#3?y=z*7aEZ6o?hYt+L*QlN zD!0WB*8;KIM<{L4A<(`I)eYy7b^PYZG+o$fDlr>NpxGT*40L6(k2JJ*y0vX#?Z9cB zx`r^ln{NaJHyRo`8Y!UxgyL?(?cf8`a+BiPhVp5K^6I7rz^cAcI6@E5?pUM!C*2qt z!o1x8$+;y)iLQZ!F^zDd3L10{*f133!P+f0N?=g|z=c*yszrIa1{{Va&xT>R<8=0p zl8N)^{=akzJB+c|IoX?t?m|Ds&M-YNweU$yAaGbWabvh#LV@a}$LJx(sDr~2pwD93 z=7nW2rFmV0p~KK*2D=!@3cPp?um1#e`v^Lj)r)b0bQ^e3lxf4ZKeI+2ELE0NdQ* z<>!%;JWqLf9%b7RLs|QXVOm3;a5hMghr@>Gz5P%*F0U<0OaNh4bkcw~R-feY#%WCr z=T!*lHr6(rBA67n6*ZoWhGPxqb)YMRRzh28BRxe&cW6&Fup<%94?E98%tJWJ3Jt>k zoO2inZQhfh4Wl{dh__8Oc^-i_*4xGlZH@2&ECtU4f&{^C*R@mptZ1A!($+|690dYI z;D_DVPyo;|6r99Fq;Sg&9Yt=rp@_EIXuHkZE~4#XLy;Tos>Fn$5tZHWZEzxjxwT6f zb_{>(iq(GYh}Hhx5wE@DNY`%u@`*2B_>!6b)#k6rYhU3bU*WK?n!lR=6~lf}@&%E7 z?iknp^B|&tWw`hZ)zZuIsMz*In0#ue+~{bk|(hhOfD=vC3=ss>Z5qz0z}K)fJ}o&7L<` zy~$jB1OMD*()PZNb+0GCe(80l_x0h|iSmN>0_nsVTWIKzf6>;ZKuCF&78wNy!m;jcGx}YCT{m7 z_fWjp3+;*HCd;wp2{_69jP>Gc&6@r3w2*Fwfiw>4vSXcyx zFR#I_{cZbu_cM?0Hfi@dOxol<{2l`1IK4YC+(E|^zuQ;scr0vOtK4;B*Tr27zbhie z!S4zTgOx(*P(EsT)b{AAN56WMQ@)B7VDTt;9of0nq}?%XTzg{_Cy!$7sAZJ&jIJ6b zXuE2=ZaYK!)ol+Bcj&eyZzEM(TetRXWseeQ&Ws=w6RjV<-TB>mL z0~&gOwidw>ii7Jytig3+D^n)|gto!4R#?uD-^AimS`+jS8@$#(+%dDssh#0S(Kf?> z6a4$9`qgomsbf=_x^r0aa#)^L$>8(gDg>WO*YfA!FDTY=4$F8WbXemG(|%PwUQPJw z^ehKoZH#ri@4`1KaD^j6TkddbUFUEZ--M?C$je}0PFPA|IpM%J9A7!eumcCCYU)`7`a2X2g3tF$Rwm8+DjQfX0EDSMRLl{b{*O0f;vzEUzh2%@$4P$2j@=kVys z+G%EU`8n}8SP@cJvm0-88>i7VU)|&uZFHl$rsjr|7;kG_w{U|`UeKh^n)ih`jOc@Fkm+K(?(&1VFMZ`KClPTZ*%{^fN&66 z;R@IrFb@iAb3bkGhg0a)2yOsf_e1mwE*LQP<9^tJBmD#PhHxc@&=y<+=k~Yu3wMUG zz!1#lnf=0TF>D|>VW8hTkpAG-nGo@RhkFM-43T;bY7^EVe}4S1Ip8xbg@^{^H6C1h zrvG@({})@{TnG3wKLao45ct4nz#I2|HiDP_40y>8elCKK`~rCB7r>j?`rv_|Y@dgI zRtRmoz{~oNAIK>6KwRJm+;o#3s_2XE&USU*9}Az!1NqyQ}<&oFlI@D8Ci0{#wo{RpCw`ZEVMEeFW`rCGIT!L_=-Mz6wlQ25zAOBj zh{F+IMdxAjbtLOT_Wo>l_Lb~EW`C2T$cfD<$eEEd zoST~K%x%kElDj4MaPF&lqP)v_*Ydv3m*q$2=j6Yh|6%@@1)_q;g0uo>!Hj}|f{}v5 z1?LK`7W}E;2b;gk!RMn&=wPs39Tg|eXZ8iI9 zo~=1m^Lou^(^S)9r?pL6HtqY`jM{CrAJpEftEua*+fsMDp43Ox=hQztefIPf)7MVl zGW~diZ$n^1WP_>UO2gHLj~e3}QyX&{_ck7C{Gjn#Q$kZ(Q(n^tP1l-BW{?@-GcsmW z%$Pl6*^F&7jq9lz;J z?p!@LVD6cD@$){I_hpx)E3zxCYf4v5*MhE_UEg=_?mp1{Z1<(^EAxxyADREd0>1^3 z3o;h0Sg>}%7Yn{waBpGT!g-4#7yYfLp{KX!T5mzGqjzd=eeX!`&fa~!hx-(L0e#_p zGy0zI`+o7v#jp0q_pcv_95^r-J{Udt`jX@&^-C@RVaLOxE2CDvv?^egZdKN*w^sdexP5rR z@XF!c!v`J-ePqQWU#@0Xt5%y<&se>C_0`pXU4zyHuSr_tT+_B@*_zQc$Jbn0b8XF? zwd~sXwXU^8Ymct|U|sOK_I0P$eY!qveee2bH(c59=|8?mD&WyT_*NmhA4`eSD8%&w@P%_FUde_VRlh_Ac7HZSV7Yuk8KwamnNH zk57Gk@#BXcf9vtT?F-mfuy66cef!?n_stWLPdJ}g@x+lQ-g&~aKM9Py1N-0EfA7hh zC%d2A`{aiQL-Ln;QNCW2WKB#c5vIlBL`o7YSEF==k`D6e(s~^?i>w2ntZh6 zXw%W&qic`;>R9Ztna6e=`||mM7s6jScf9@hhcDK@c=@H0mv+DOn-j4oI#0ahj&^T! zf9U@HWYWpnlLIHuoc!+8)Kk|^r=8w+`n@yr5TW^T($3XEyiJUfPGV%qJ0)hrZ>OFV zao6%rF$6ZzNrpCawE2`+6mc&PjB(hus`YB4Uac=7I!`p->zT*Z{qRBw3wd@#s1_h^ zb0tE*alD2R?tZ*uYqsbgPD(+!cKJ ztvB#i=9M2Vy!|${_?b%PELaS5oH2X=vzrj9c(+A_kqC=8P%RLuwOOr}RBUN!x$T#2 zx$W&u=yA)o*qa{B*0`HYC0RB02w6oH5^M_#OUnAGK2y5g1{L zU_{^@nK0w0^p&$Re@R5Rh7FLi7=^IGT%a~m%!-2ij|XtaMM1}dLoJMrAxtO}oGju+ zE)k;&)&-NB!FPg5fG9Xv6u`*zYPCWkJ&t5pCSesPd?K(w`;5a!ZRDGkT4lG5FKPkZy%V?{8)3Vug0SKXbSkvz|XU zEUNcBR=cS7?OVlv$Ku9C4S0UTqK4m&@pjx@%oKXwT;Q2UagX;d!1)WjTb_9fJa6KB zfH8xL#{1co++~Pcm!K*f!vC@)zc!zAOvQ^zaYr8Rw&20|b@60L-1<1OBx-#W>F~wN zWt(JVxpb41EN3<`WJo)rB?&!=iS}}9jK~(P7K^omV5Si;TkI;k&faG4wR3bM88$}k zhwN^<*lw>@^vHQXopYNU%jG#eNXu&p(yFv~w2U^D?~k71*Kf7iq7xHiqK&*R#%bjJ zf}BQUw5VL&FF^{Gf>*c{Z3>S59a6XzcNCmLAsH28v6zzt3&hr<1|atV9&A>N*-SwW z;0a7<<-6Qnl9 zz}Jw(rKc5;OyCG;6DKh->PQX;M3Nx2FN4#PNQ}CGVOM4?KCyK8|NJBBd3j=GZ>0n% zlOZXy8!EE2{Ng8DhC6q3@&PZ&Vr&h$Ig95u6h_~3$J$#mN@wSVkt4~j?wrE;m1ex6 ze7H56d+I6i`R9xo%_VUx{%-=o53GoY$t;^?t65qV%dB3_Cb$MG^vPA(de&3#t2O6T zFCZBirItuv{3N4Kw~x)VRj28do;Wr#3rs>_%<*s7_qb}HgdoUk*dQ12^Yf@+{3G61 z>g&vkl`5QRbd_{&{G-#*5(xjoAr);^(3OZb1;VlAWD^0wQV%(X_0S(?RQT2LJJbv1 zb7a0wE#!P`m|-BL1Tl4t;DhNvV$apVu! zFhu!6TO_fj77+Zu9KXJUIr8YeLpgIR6BDP-%ggJUnvgiP`z=3%Rd2|M_VbTUkJ4GA zA@}jWoxzn!w3Uy{XE^&3U9!>v0z~q-z4xrx)?lGlGILrh7j6Ur(XDY99ZB zeTu7u+)@>IE#L6L{ZrRWCCjTfRg;c}!G?7WOxHA=ZN$2By`0Zz`vxw$E2$@5)U5fC1*Ab^Q2pBBItB)ZZ#Hl(*;xL~w^DJVz| zp|J;>%HL^A#v#ce$+PO6N?so1RJJAdCJrYu9WET_!kjBDk(DLe%j?QJ%9-+j6%%BPcG*YapgR+HoU{6pIR5A)&2xx7=El5~6 zKxd3EGl|vAB!(i_I)O|vaA5(?4D!X|NNi2b$kg~^%7$LV2L=@|4Dhcno+JV`Ij*8O zCosIUes1o<1Dz>}Q|A{b!!!!>^tFxlCH2WAiyxh;Ehw21A8!lT5js7^JS#RdSreS) zc`Z1Rk4Y#?k2D4GeRU~Q8lNhvo2lbx6~>h2r6@E8OK3={DLAKd#8tU+ZnZAFq^!bN zu&_GGP}0tqE~t0tGuqo)lBU14+`ReEGc&AW=?1x+EsM&E2@FXpjxQWAho$C}-urd? zt%0P+I?`s8#P~;=s7}QAJ5Z4y3VJr2_hEK}<|Tv$HWt+DgsPrUt>pNGMy1!)d!{oF z!F3uG%Ll4NchpKw%Mp$Xf;=z?U=pFv3v#xA(upm4uv-XGnO%Y)S1 z9k`21L_{jYP23Fb1Gl5&rQ+340N4clS|b@|Fuq~Kj7x1J4e{{}BW;&vY-orl0i$<6 z`-5;QblBt=0=J^#X1FHq^x9>e7l^;B_gRp z&iQb?;$iWqm=QyVXMv@}VjpVQSj;U}pk1J0t00a**T9xwIK7*F8MJw@s`Arpiy7MJ zIwiSvW(y^0V-Oc4mV-_)W^#;`9K@1D&u8bC?%u!jXP&<%V!2`?dt~m~vLBq7j3ejw zW$WkTI7$Z)fjGlXXAeLwyNox=eKAaimEz}l5m1DTAw=N=4PwqW8T27yUzBpE1@?QZ z#f)Gmp9w>QF<3)yr@keO?lVAw+Mw2_>tWz}4LR|srvV>*6dxsRo?3i-CqC|}-RY%4 z#`73?8Jj^OW%DsoNSPC)@>M&fBn0F?-NKKPJJu4655ZSV3F!Okl!u0#{k*p z7GP+9J;uy2EcLt%z3m=@I#rgU2P&ga`>RMiNmMfhm?sM#iWdBWQZzW7f<+j zS3E&b8W9&C7l{_}0j>ao0&qY;Vq_1Jsem-AVsTY$Yb=S4i!+4uE5(L>E(F;?Bf=ow zZRwlj*}e%;+R`^^yWjxgi$PRk(13mgAP^(5pZLBw^y)z)*e+~U<+?MSb5E@)FJF76 zyW`~AidSN%_BksCs$%1+7MGPSt}=TXJ||DV>G_`{rKL|}*_-cR#X+7w==tWIgCDF+ zOJDihgZq9noRu~Fo2Y*RM^bP>{W9APw!0r{63*$yT6M&;{wB z&NC^^K8auPZ+m!K(Uk2g+Crbjvvx3Jv88F+T~Yb9sdp=O2;&-sapeJSOlSi?V^FkC zM6z{wAPmApOG4L&lBJpr8nQHSLm=sr;rS}usKQ(*Hkl$8X(cuZ>5*KNz${R#-y(m1 z)E3hPYaBi8kK_S(g$OoM37)$TGI=_iu5We zaZCX-q0CsZUNf37`t&pFYGX=gNgzOKdd9a9h`M!$J$5M zP05>{9Tu6_n5(W>-;tBtGSWEnsnONLk2rv1F&YMXEQdMgqR;p_gW2n{$((G=XS=e= zvb2q9WI-A(Pisyi1D3TGB1&44v_6Sh8n+>iEHL6GBc9`j+kJ4I4_+?WBq7Vio5WtT z{~@xOkc`ljMK)clj_7n~kw5Q`kw5mQvNlf>>ip?GGx#1zxal%S>{V)w7}Cs91O-ceC#k;WLU*Y@aiGDw3V%_YrM4WrQ{$rs&` zVTxqpMX|A8rwi@xiN&$8s_1^b2(UbfS(R6G3HYRV^_m=g6wlUF>YG{26IlIMJW}jU zunD-u-~Y9JZ2fQkzwW>&)$czUQHH zL;3dAuPt$|oI5q>c=$urQoPNF;)!ryR^q@Z*Vs3JiHbuv38){XQa&5;&!7GgY&6($qi{$%w{~7D_U~LSYlz zURYgm!L{B!YfQ^*XI~xheC&DO^VY!wP(PT8qt{Vc56@!&4jq8QIPjRN(OG`wVC=eB zvP7|7L6(t?ge;S6l#rzn8zRW^s7+C1ncqe~((SO~>cWdp08?8ba(q!aUMjT2**dt9_fypINrhuKl~5X-Ube(nhg zY=7?n?yeU+04gU2&_3{Mo`-7Pr7t5~!?7H# z-cX59fCzCShebK;9XTA_%UcqOIi4>lqQ^B~n7q#pv_8@zLy$GP-{Q{jVdNCQha2xK$tP57d zQ0H?M17ZYk2EvaPa~~x;tLi<#jX!m2V4?SoU_N0QcJ1<@U0_E4D@;VV25u(61c?G5 zxQ;j$U_v=B?4C@U{L`3ib0}EsXY7dM)AR&nT zQH@7eVChVTXF)iMAZ#GMZx~G2&tU9n*c4+yRZW#G2A?1?1yfy>dB(WPg(W3DHL2or ziYY7Iy$epREE(JWCi6G@yqcoi+>&XT#hulKbR#{pHapT(+EJir+Or@(xn_YvI#+a( zDVc!0qu*x6S$->PiHPdD5brz~}Uf~=WV#+T6q1bS_D!Ro{JW493CqY*&r)`sE?!L8+}UGK|I z2KF)`?EXyL)Wy!ow3tAQMarbyvUpp4dQeb8VfE~+_yTjdl+z8&ktpS&h=ABaOGtXh zuC`^btt!gxIoz4s*;s8VHP7l zFp!VJRw;&Dhc6>$^sH=T?A=Y5vFG>r+p)j~K|vyTJ|YT7MJ0#FHPL9|A(30eiu__*!^7jkTE$FUs|cp#^)Ljt(Q7GF zxdEvj2s+jDKrdi2NGF-|j?v6_+`HKJfs51Z_8?Y0Z^Jh6EH;JM*qeQ|q9-I|6Z z&zF1l+_+V}*r~6!wybvD!?Lo6o2_`o+|lNE&Tgt&QJ>XOo$D*jn%+lZ=Qn%WwArq) z-`g6pLp@PJSs*Z}T<-%rXo+B-9^n(SW3VHlKB6;%=?sVH6V_>ywWLkkt0jlDP{O9; zBch$)bjjsz$WT&-wFAS-WM-OdA*eZ7!)sg`25D3pqH%=QhZ19JD7av(N~V(~%i3hU zGAvNK=fu{onPKaw%F!GQuNiovbL+bs3i-9a8tK^EQyFqpnN~I{uW?n4 zi5y$=>n+aYsht^xj&)sp$2m+6Jd5#(=FFDmMTJQXCGjO2eo0|O1X`i~vI6o`vv~U; zSx3l_Y6Rj%LBl~LASgPhJ&0Ka6@4U3X;#ixGHG(0Cd0W5214;UL?gN(0Rp8Ekw4H! zz$AUZRJ|JIK0#37Bt*=-L_s|zfgJqU4%go6TRhuv=7E1aTX!jI@$tdshd+3axQ}|q zUuxv+m4|;gzTwjH?7RQK@W>Jt#ylIwEJsazu9%kvK$IBCAeIQ2gXpsqp0KCEB1x-~ zvvI4q?Ht-3T{<`SIp{KIZC+#(VAgo6h2z0>U)j{Yr>8vl1=Hr9rj=EOW5<)H6{q-k{(x_+3(iQ4 z?0OaB$DadwZAxogZqHh@+`i$Lt3BU%{-zEJkz&`ONg4o205mY2&yzPvw8#S4Rce57 zbhRBG@B^#WN6a+!Ld^mg(T7+!3+8L9ON+UjYpOQNTDF`78(?K~g}Bkkq#q=!0!iN^R2Z z(t}dA3Nj_DVDo+H4XH>)qfrVUR?;f=W8|$Mv1u6gcB(fB3nPJqh8kOgEs)co!Au&A zp@fkR>t8XMp36Az-rG3Oa~X)@?mzbLXCXyGVd5VDg83N6qCw@abf^YZ>r{*!>=ix= z5bN(RK}~A4Bxso497$9VXZ3FlV0>FSsgxp{$|sv(E_-oHnHTU2nFPT>HciI*C^DIk ztsT2(zwnOXHCcR4S=_uX&)fK0e5C#9{(@7Nw0uim#P$(J3Iyxr)n5zn*~6y|$kxh; zLsl>Alrin%0r6Tf1MeM~4@H)w44%v**)kj;!&nE|BS;Y~madi(>ACTne43-Yt)W*vZOc9y4IOl{__e`QHBsFuo1=u7LS)$ixS-ER2HE z9hq>D7-mAOzh0iq zt2Lmy{|fX11SFo(E5Ve|4@uk-Rmbb+w#mu%c zZNe0QHafu$=>(arhNoVa^Yv5?le7rDBxHRENejWtgEj?`+#tN%f0IAS^~d!pY**o* zRp)LgzkMHTZI@EMDdB5 zN@+i~)3NsAvQ?Lc`I6NaSNso_ynSHr{{06Ye|i@=_M+#9lZ`V^WA3>ZF*#oEdB*cR zJfST1{L}L_e7}Hzxz~WYF(M~-~wsaalSVougRvCKK<;p{oxkIj7ek+Iw6DV@~r>GV9z_5n_Gz=fOn+35*` z3F{J=^jI3gWO7VHrV$g96Ezey62)|>m#a6anJH`&OB~vIEpdd`hZC+TKrTnk`c^&G z3)9l$0DXWyDW=&Kf>j|nB)T<{<>J8~khHbZ2VP(*;BN~eb~2S{rnOTbcOO6%o!^he z6+)7Q#v}#J`V)j%XK!taTi+4R2br2v>K>Wp2E(KGxQe z>GHX!{<;4@VRF(Yd>nA8hRVP_=TYeRozqHGsiKnDpahw-2?O6?qyxEv)GD8VwI+x;MA2hnjYfj`0g%(L^$i}+8=PA|=k zsa@TSN5A)6_1wY1)B5aMVjKH8XSdE8nO^YF+zRE`3(UJWJn^x`U@^jks(?@D0ZqWO zV*K*7e7rCbmq#>55Xe)uDeyqS+5$3=kNJF8K3SiKOY&;-$db(UnPeblZ46=c1Nyak zrdEzOiSdvaZzgy-!SPLTabPIZNQ|~n+O$ft9rSKm6RpD|+BAR8(iR;bAI*Xm=A|ys zb`z=?auI?;hIlmv!-p;2+~&V!BPd%_-kwm>(OxC(P2|LQi{}J?f5LyD?t4%q$IdHQ z_2&A&dG1D^S0vXC)j5~eB^P$DD@pB~S)Cu{TzR~G;OV}SfD3YSX?uxlK~Y3{^KyG; z@7%^B6JA`meO}JFmyDTBh0$R-O*ThEmOb6B!h7J_=t&qzV_j&Drk_CC-XZwj?IU z&9*S>g?YeAh@qt73SO<3C{QH{R?(GQfhs_Z{Phgo2&FcRN(J&HC0LCrQ-q@d&?p== zP-zn`SE7ojU}3G2p-O5Q0u|}#MNl|$^Nf%0#D-LcDnRhu;Xxk3RxCZ>7fV~!v?G6d zU6eEx-lhgsJOD0(*>DaXsZ&UCfD|MNu89hbq76=|IzTE_19Vf&DKk@1Kx!&Tu$q9- zfSLY&0rY2@%v^N17?^%)aC^S(bNf) zF#m9M9_Oo6iq&dAwOFb2<+`u__Gb$va*ktFDzUH9SG4fNLa@|)#Xu?|nL;%0XLyZ& zTCOe9QBmoLwB=g;J@9PWy`L4XNXfR^izD*$GK27TPhVhGZhl0uqoi{vgDIvw9SyiX z1~e;2SNIuHDJ0Oryn@=BILKf?0TK}-ERnNv5fMwVM8?UWG#6s}2#X=pfF)uHo=L<3 zgorsAu8~7vUp`aD1;{u#ROaYpgg|)?BSKIl#!{^kEDMLSG9L*q4|Bq+M&O|*EMfg* z@W_~r!Z`?pCD^qV8cL;Q41Hc$PpkzJpWLRAw+(cWT#`(z!TY8jK}(C2dIARFXClAA*orY5$sc=@{G6;6Xr991D8NC2cJuSnL?xnH5i>)K zbH(K_U2%vwPRvZ>a2i(vi3G%{I333*II%)96Lvx zWNhq7G6#AsgE_2$IY`kUZ`vfmT@pN9vQ$FiB-xTO3ByVPKzY$*2s2YG2@p#dF~!Ph zNG<>~Ug9I5B$fr|7ltOMrN~tf^$=6pO(d;U^awsKNFKlr4Ro9DbAkYQ7-k&gK)}1y zU_HC$?psXiSP=8Zz4w?|+u6|loA3UK!Wr*_y2qbEen5;Ye7NAme@(r8LVbVm!ocbN z7E(6fg5^SkJ5*MCa~g0DA+`d&?;{4!4&39JE*{+c&!>ck^DycMUC{*n0x9tkn-LyD zI2#`AgGxsgRLZR*ID>SOB~U?yLCU-W1A(*`C>C$Q8pu@82ee=fj1vyzCUfN8`=^-n z$&xiV-5184B8)qZ4-$XfMpYZ6w%`pn@Lb5Z9KsI_S->f17EB#}Sisos@$7(6Pv0zR z-TXa_7^8LgRmi>DA&)nQx2)qZ%>@xiRc%2~z}N+^TA~HwPzegbs9w=9gm6U?I{x)A z19;H+4zEN|C@y5yDAZWWLGbVh4N$@~sB+8l%ZB-YM@Z)+B~%+Q$33rjUfqj_evMb) zR~f^->&)hRgMb!zSKr<7X!gIx*TQXm&oBtR1_gxxeTX240ihiG;kC9l0e?H$lS~eG z2K?tx-gf|AEk=fE?u?;ts-gIL7w!gSo9Wgd4SC)3!7n~xPkt8hRRCN|ggUE@>{OJB z3ehvXZ6q2GMUF(0uKeZsoAMcBMtTPE*Fn@)1cH!6NE4;`Qie;^BmxqlE${MHgh5?R(~5 zc{_gW<7q?Hv9D+LG-fsSuuQK+5|&w=kye|lJHC7Vrm0%Y<`&P)2~)-tTh;vhspi*C zdXik#B3s))dLxUK$;}lt?Eu@;K>tNRpE`JR$Sl4{D8(tU)>_-GOu3=iFy8=J!|CCe z(`vMX+I3nMm9~NBqt7sl0vyPH*q`{DT%nN=@b=Lp!vjD&jy}PhCC~SU!t*wGr)eBO+GgG=H*u- zZLLK`-DR<{WnG1~HeOGnJfC_#tDha3nWSf><~(N%d)w0(ksTNs)YaUZF%di;#Qp%aVVHky}!rH=m!-m5yg^9wbYzz;J zbcTfmyYzv+E>0__CC`kUheoKBPyt;*LcmK`P$pwc66bx&_KaWk#d3+s?PybT4(a!gFlFB%^*d7oVCa#ZTY$k8$RirEGL+PAFc` zduU!ta@Wyi15dWaQ)-}cMyP87deA|>eU$HLkH!%AFNxyH_q8p>hV8przvNU`{ zI9cYq(U%PQjQEfeQcKziq$jmnc^R!&8uv!n+hmMPmS}>PlWA^3>Ik_^CQk1PRdFsA zt=J^PG-;ut>;k%q(g2|T$G8U#w*Lqu>g-LzViN0<&u2tn3>hM^K8e9ari#8PnJrUN z6$ey&_uhH^XIB{6BTu=Vpf_H5iO1!=sguLuk^^Cf3IKVVT}`@zK#E?23>&spX4FPhD3@#oC(d$r8*Z6O`oo8uWM zSlPiq_B9}5X1Ppwi8H+7!_&YfpgqDgN9DVGH1Fnu$YMdOGSLzPMDD-M5 z35``|x}vnILKkFFq%NcrB|~kGh#DiH7pPA!{D8sfvvKALt?@5>(1N{nwL%qCKK?Or z52-=Q8MG7|bOeaYpBAE#xlNnqZtN%^>4&hzQQ>pSdASf2o&0iU%# ztlHt{r{_qd18IjL;-OV*f?XU0@oA|)1qPLMf^zT@IYg5|4_GIPw1E;Pi|^Ei^Dm1S zCYZ13;-{@{vb?O%btUJ{txPz>z2g~5?MSjzq(>yo80{FdkXKxD^Me!X9H3!N5*f?UB$m5V;Q1GlEO5VbG_0dPOp#!VC} z#-!82Yl!nuR-xlyN=ldPuaB##C=3qFb5_M>&2q+hx=8tzE8z?Gw8o1SO6e9(Arpt^ zw-z(M5Qd21O_g4_$lU9c<8_3xx zImpxj$Wv{$Y{)=(vl28d0T9oE+J0(YS!vxSwWz?dqK~TmOU`DeXXn~8XYe0>>nX$U z;eX8b^lu~%&)&)n3;{4wcPHs*h z`v(3Upqh0HvISmY1=qKOzQ{(~&ZGG8tEZ((Cp;r~m2Z^#IHR&5VdInJyl8pfGHzko zEWH+@1^>rpVX7>xmQ|MR7FK1^S&}V`#gZGMa0<^!-SNRnADa*Pzm$CkU|iLe_Ph7J z>AjCe(u_u3qu!e(jcirRvPQDyE=xAH+yFP(U>mT&1zfOA3lNG8VSx}5Y!EFZ!5cyX zNeCn%*-eEk?Ed*f2xONeyTKa&-??u_l7*A(?=l)m_uaYm+;h+Q&UfClykp^ZfJv&g zv6RewIb?U)N-bizFkU{0QtTkup?0Wda=a6!sSgR}3X8#v{X=gl^2;N37evXdO{VoY zxXYYcm=UhZbqkNqT_h{GCA+etWZ6;|cNW%ll{PJ|@;y$a=JxWh^lffUtsFYgKlzZn zD6?l?nNCwsUFRl%&d9pb?utNSKZ-s;N5Lnm&rOZmt^vFX-s$kw)k1Mf4QG;ak6gba+tb1 zpRzN_ZnuxvWxG8m&`Ed%Pt0Nu#WXnI#gfxk^=-HR_p& zIfr&%zzPbQD}#;>$vF24DCK?j>dw^!h%!9`1IN% zzl|Dakc5*dbMsey?&{igOXm5oGZ}l9ChaNQ*<>Z9CAO~l>d&pp|Ej-nO?TtQ-ePEW zZQW}dQ+VdSw>~%MF6yY@p^}F5_;QV$Kff_zkaMc$1gaL4`(k(e;QDph^!^W@Dt zA6OHqzxrWyUXNlt0?K?5doUX9>?vMZyro!fD_;zT8Mz^iRRiXc?aAJieJEQoW&|?! zW*p5>oEjl?uC+O||+NYoJ_!F|cG&IIai>h#9J zM&4C%x|0KLI*3+<0CC6M(Y_`>E<75H0|w(+8Wx_?(!aj-yWe<)JM-snYHeH7l%6(c zq`h(D!eV8Nl6K5mcd%-;{!Xc(-T@^K6pW-Rq(7gEYs`3f5-KqVO&So`o zOrzUBmWe_{_->JICgOn6G}K~X=*HpaJ?kL$}UyIshi1@ScE_&JudLn5neI@ z*izjsvXb4n?F*Y$-@T*PdD>jvv$nqBimrmnWqUfKH(b&5_UnB|uk5xytSgzfxa3cT z3$JW#J+i%S3TGg-O!yU#EW_+1eymncdWQ+ah}pJIY8$b>5Y8ng)gh;+4QM! z+dBS38sm2Cmz&XMyL4xC?h=J{TbEk_jazBs1{=$f2LYFty)w^7@&T^;XnJP)XnLl6 zG~4Xsb`EFA`DhU24RF^WK+oC*ca2ST*F+2rP^%=mF(u#$3F9`bb5oYn*0L#TIRNR`8Uxg5Bl^1`V?UUQTtf% zV32nNmjrosuqw#Y$$c{4YrGLWkr<(uhEqQGiz%xlC4LNE&3FwUK zz@bxtd^X=dsdpPp2K;jy@BH5LPY)qhQ*Xj|x|{#`Caue0GV0A{dHq@TFRASrIbdoD zb%x@_n9qm&gCncbiwm;4-R$xBQg>%oK}m4M@RAfx+opP_GN2uYEuS0MfFA0TzQ@4f8>3Z?Vm%tk4bFFz z(g6p9R@+S!`GY1~G#XiFgyN6LO5kN_uU3+0MgU$3_KNYmWC8y#d@^h(tombwZ$^G3 z{&M{5-(;ubJHG?E`^f9@o$L|zwRjsZ;@0>|_D_?aO#TS{o{Mw-2>Knu{bnHOw>i7k zJ9jw`Ipyy3<>_3em02t|$-Iy^axQh5y9KLGt~4s~&2|F8$^ynK+5MaRd;CWrOzkjw z_(OWVBM1jR_U`jWM;A^>C!CBQM~#jsaB7a5{LD`)*kZAF`R(?g!)8ug>M99$MWh^c zxaH6*)|s=OEFk1LAiFWGAJ}*b!$PX!mIpIHO$#GWtk~18O8(C|v9;GO%xuVTl{xy3 zl{w>)^YE}`1btlg1w-Zw-&p&=)<#8lvn=nvv0N#gydkx(x7lE7>gh}8H zB8-ax=L8U5UR|zki4NfP0jT>uS{-iSTsxwL`#`V9o!dlV2^rr4M+M$?*f z=Op|&LsN4~?wrb`C&zOK|Kj9+dBNlo{^k)m_t=pOKNtRs_b@j(xRaH`*W}!7J8a{L zz*z3b7{d+xZnTyLd%T>Lmse&K{ZW&nX)!ai*|A>(2M^QanEqrl_aSUoK52dmF^=Z4 zlt1~UvZNaYLklAd9pT~z37M(hkm#(SElOsVYH=s3Ci^p4R>K}uaUr8L0{_S;-w58V zwAL#Z7M3o&qO$Ueg{5>rGs91cTO+Q5xr+)47q#ZcR{kWuv?#W3+lCtA|Fn5I+)LaQU&>!Z|G^6sHOgPYjG$X&C~6TG zP+W3=l*f(iQF2p}%LnuJ80-oESB%-ts6~)PZ7SGPaHK#6=Wq*F_Kw-ha+yZx`wfHB@Fnb8$$y&adt!x-- zE5^7KwXdpYD{AJKc#cK-wspkMG%u+Y<5;t_CH_#`)>x6cZm;A8%7~mNWk^HOfLF;? zYLrpsbIPNN)?Q|C!__ZdfsH&L&7to@;(JS`J)NcdzYN=t58%ltR+W@){IU(09KvK( z{;IIBg03<49|&L(k>QXAM+O!mxFPihOjKlBt!`#)XF*?YPmZUeveMVSuPQXBsV%?# zw(c93<+jeLORqR6=ULnVi_UJBmQTOGZ^!(lZv7q zuH1unc7h`V=pxD52XrS7+S96(X#0zno}71VJ2%NM#eZ4@*`_Uc#Q z3%mlpz#N2topqF+)xU98s<{5F>ka=|R(e)fc{c5hv!RN!Y4+>SroZ85XLC!>`hR*> zdE=~NKkNGCIVr88;;i$HbGjgXwVyTr@@#g+S<4$|v+!y^oA*=coE}hub8b(4{aNOi zFkAdt+1bAOvuVzAsTuY4W#!CaWpyj%jFGMpIY4d zf3L6i8J-LNpku}OPj_7P>A=SEKd%c}Ud&(sL)$0f*RNa1|9asQ&zk%i^N&`B^sl^P zKXuE$v5n&q!wWCCpRa4Nei3V#a`)W3ke`^Bh*yd>9f2 zmfY&fDVuf$7L!)QjcduS9VQ|zT^P$vxxlp31^~5h%-7#95-ms)z3#5hF-V)t; z{lA_#);Q8T$5&mAb@HI}8>Lj)32R2J^tcXd2wb_CKu;nae7la?@n2s!i!Uy zQh0}ZiJND-E8M)pzQoQm?G<+3VOnD1nWhR8@4$({Gj$bUTH(t%FXl~H+F@2WN5v(_ei!v0_|3-qHGo(FMi)$o%;*)&qKzq50^dfle= zIR%m2O`Aqh6-pHq;i(4oDOILbVIAKCKfYIbCc1UV!+LO2_Y`>QJ@SBy$x)>Lab>%z zT#(-c|B!7UOB{B{W`$|BUOlX=PJ_p5)jJJVS1FK|Zbj0sa#)hl28xI@@cDJ75wKgx zwh_BqvV#%8uJ<@)0zM}g6`_FOcyx#t!RI5WFDgK+q(VvF2ks8Xfp}4=m4;MYiCDZ8 zogjQRL>-_)OfIn2_>F9T{Klu_2iWx-@1ybc-+?jts_*c}P8>OP5j*(QkrVvE$-DU` zAs1codYp$}5pppn>X3C`La?#k06i4w1b9@@RzXc7B%&%1G3yj;jWhDmNBJYN@xq@! zy-PM-_!HG9oxZqODZ*LVE3ILg=+2$?19rY?3EMStXoSxjUNp?-tz5K{&s(u*#m*H6 zRw(nD7d7+Ep1q!<9y#Kk?_S}SBmMLHSM+c0S0XVMBV2x@i(OfNUH$QTIZ`kD;M|xU z$mW_}PcI)z&s^5s+OcS%wYx{#t*PzK?4e^Q>P+cs?a^drYI<6gB7e8E(7uo_TzExs zw^YQ67P&eQDzUrca0k&Mbfila{dpOnd*k3_H1$hx9HHBU52lSE$O{A@1G7a|Hxff1 zV(~-dlp-)RL2p2jp>$%?1`ziV^$lLdsV0D++Js?3h#e?8f6J7CdkJBWYXs9G0TXa6 zHp~24vRK$2(wEE|EV=&1ibYp7wjJBnkyblUQ{Gn_Y}oe5mZ8t@>T^A0u3dNqwnlGJ z{qVll)&m<`Ga46HM|$eRKW4wFUbeldVfma;`M{NPYKGeKG~W-ltc6XZDaWzAvp8Ck zVYQcZZ(dNmw7b^gsf_klcJEqLlp5XG8>;OruzQNzN^80+v+On$>MmZ?S#7hHL}O*g z?z`~4vi3?``NLK1c_nkpGHr-Phgq4kKIIkpI?X<@zRIH^`4DhQhlm13!%itMC?MeY zDZ~tEq^LKTh~8Eq$S?pA5(lK9$2bds>gQayjOTae2OLJ~unloJCGJ zw_N@#znqBTmcHdNW_&wj680)?3xz_w)YAe7a@6kbLVR(ks+od_n_sa2$QI!>)Y6;=0lJlDzSM$P|%PK%vLt6N-G&{7&U?{rnozhd5sLrV(_m)x{;-W6Tt z_8rEIk~w(|i>g!P(8>pYvgg{jPp?|_;M;rl{N%xvt9lPVJ-YtsgT1{6pI$%u^x?cfv{3p$FMpu?Ui$%KE~DWChxr4`?`TGI&)%0qyMEg7U_~Zx!WN+pDX~zg4rXX1sk8(u4xitWX`sIj=Xu}IF^l(Hw~NQ#{D znoUYDL1ewAncCl~`bv8nBRy+t6FdV5NBUJg7-7Zk7We3cPxiCnMVBGN)5vPjhL;pD zmz;>+sZqZC4>xQ4*WY<)KdU@(_r}3rogVIg;!9d)PQ6liSsq7{?GP)$%l#;Zfb-V zr=(=z#>U1?3yZHxEo;rqX)Q}jD{IZkZ7oYxn#l82+`F-H&PGx>H_pjzEl*1;r&8tV z>E*3rkAVsb+%B)c&S#T0M{8|nWtzBBi~bRBrQKoqa@4GHSC;BpbSHK4N!@APJGzfx z0TM)(&|97XqYntT5ffPPZLn<%M$6KG%5+1dmQ$RT*pp<=N?-9Z;ZO<{Tx%zwhBI29X%MG5N}%Ok$OnL^Nwpxm&JHSpAB>oyEj zb;^U+NF z-e#*c{6-DBE%UtZ^_ufDGehs^|JwY%3^K-d)Y~PAc~Y@eS_QmGlJ^qphoI)84q`cJ z1)fweCH+0_J>0=TUv=+&Czg(NXKQaUEF0`ylv4*Re|>sWMaKNjw#C|;brpRZ8rnB@ zWGz`y)l=q`Q^&^hqWy)IdF@>bifhshS6M>U`E%MkTFtmtriB~(N_wt#b4^Z78^+@j zEU%!J7$;CMKdzaH<*lx*^k~@a@4Xs-TRG?cDE&WZERo~n8f6abAbHZpsNZCVZMI&= zGCMT(PzKR5oDaPPnsOSxNcJKuivo`YP@-twqAq7NEekKiv4aawhRfzm4aCeE17gVN zK0|J4Qjf9kRA3PRl`71z5YRXfp{NI8B9L{q!$`hUIGw8>+c7Yn0fhZ?&!x7k@1D19 zphWnLwB^k$uB)z&He|G<#*b-UnY^xjY_POy#m~ZuPrxgJ73BHg^oi-Q!C3B@} zq?DgO`0An9p^@qe#Pab@<$M40N3HFD{wRN=c-i4)iw_M|?1g+|AG1gJFZi2IEpHIx zeNDWLLC=7NaeyNFej6>dxw_f{ivz$#SX^EHloX$rOk-Z$DsM(PJ=42xPl>{Yjo+AV z)!c<$S4v9~V@r#XB8G7q(~5-2T1dFuyy^s>Om0?$>1r!+(`N4lTQ~N&i&HAH3+gj% zOYwMQ0)vM$2NPBsJ$!oP1Z-y&B!k=3wFT$Eihite{a}xzBM>7%L3trQA!3YWf zy?P)Mf$951VERitOUeRxDOK4-re0yEArLl1(G2#|N)O9o221VyC52^6yXzc#e$Okn z@U?+R&~cSBt0Fv^Mw^^V{{~&|chKcrQdk;@mhDYBn!@Q;$ajSgh54?uLup)k@09zD z`@CCryR!oCZB=8$z^7pJy>ElM_H^idaB58fKGaY#VGW{7*k{fH5>#}vf|@4(u6*t7 zYllv)sVrM_``Qh63>DpV?T)eQGn)o0ckNf4OHXdBuitob>Ee?c8yhyB9K8GREq5)r zsyzc&8IF8Mw<-&!^s9*cf&TYQ`+-Ih3YPf;`|pjm53Gg{j1IkBXER7Ty&32V?Pb*~ z=vMYgRlO4Lpzvf>gP*DrD1zi0{@173K>X}kHgMs`6ZGzB)^}?`Yy|AQI2)Mc7^)Wk zhM+08;6K&nKwW1ObpgLdb$v{AJ;0uvoRIuc3&@>j*MNt`vnJ7wezfDki(&QIl<9V4 zM4d9Prgk*2Xaj42!6ngzsn5qa-y45Nxl*Zz+$sdKPR*!xxAw4B9#wWLhZVU!bubn7 zj6KK(@E$3BTl#o9D7T7T_o!zXI1SE6J=lK+LssO0!cAFwvW_G;xrFJL zu2$+6o4V(dP^e7Oa@AHfYk>mQsmlhpbd@j$WFT49_x^oSuBWyWDHq@VO5Z=`%LAJfHS4XD&s1Idsyzi(f!qYtIDtXmXL2?+X*&6h)WA3acgjn=1eKRQWMvTmD3H zMNQ&?A&-B$w3rucJk&o`p7r1dYI!d{KuYyow)_Lh`a#E(EKd!RGRiJnekfTU;V}|2 zcZ2XqrUuDeO7W>rPzUI`GS!EX#4}nJlLsX)*spn=3}0v{(WJz>#JXVU!|-44aX>+@mK8Tx<79)zpt0!9tNb>bY_SAnHtaJE>uumZrqE!8bf44U zKz>KB@#%efvqLwm$mU_$262d!ja*o`gKytKIyxgfD*izm6F;5uzX>8K=t0CJMYg%* zf0#u;D8&!Q&z*Rj{Rh7<{uJwszsUAZ{-RrPdpz1>NG*PUe4VEKp7_A$;-&W}Puu5q zwK?NkX*{;a+lWgEC4rhSSzUCBI{j&&b@uVhchLtX`T)n1Snp5G?88~=1Dnx@+2zN;LOk7v4d}yE zY0-yS<)_!X=!AqGT&_IzL2b{}`b~5K?C|On$@Po%NHUjro$R=ko`|m9;l0Vrd6{>_ z%iZ2=+&lwE0(^hU$rPTNQkc@1vL!`vIEeep$9%qU(6vHqg!fnVkT~_j!H}WPz*|7} ze_Bx3d}PoXqM>{UFjxApNp>YkpRgQ-zC}g~(#@gK;77t%DQ=870TJDrAc2BML_%bupC#Kd0lOBK{s04l>6IMq0i~M!|GcLb~tcpLAC&t;8;`wEX0%IUem93?B& z??EuL)dw;bII0Be2L*l=XRS0MLd0%m)?iM0u98tOtT*{RipJ{#la@;908}BISY)rq z*1(N~wgpI9D#&|y`t@bMy-hHI9KOHx zrNQ@ae`Lqxz)|LB#z)8alXF|+NH%~yC3qCX9)Z_Y$ivgBJPZh#duAea#wxx{`7vop zLLSlsO9JV$*6^(Qr}qd;r9GPwxNLb^^J;r$$U}SpveE|AE?x6z6Eqj)anXl4q7P%z z`b3)y_>A=iv!YM#a;&FmJ{vnE=@rqr0->@#uq$vVAa8c=bslvRrwurVxI-)hx~Qak zO}ZJRHr^4fXT$`klu4K1v{8>~adao5Ua-|?8zH9P)Q6Jr580PCe$%Ii!X09B(sSmN5u261MO%reG@<;tK|0(|&zY;R|{f3Yn zk-7kOI{yrEh0)h&0jxF-M|-5BP+ZBelO0VVS(62hHZv%156wUdZY^x^jhE= zu-vf+$hbkw#rTe#=I|J(P6JgEh$xT5wXzi8CxV_-l_AX&RQOrJAYx$sa`otao2tW=4VB^A zf#HE#mN$3zvZ@sSBesCkINaAb&{E&Ix}&nYp{%N&-e@|zl>i??EJ5x@T}nhC7OHmohJG!Jnh(2nOiNITv(F|WfTPp_@ZlppJr zgmz31l+JCRF8$fEtmh91JAay{l~#;;p^d@Dr`et9BWzUb+;M4@?-tVU)u!gtJwJeByQwgB!V968AAMxRRxg>^7eX z`4(^lz-yUC1yT!VOd^7l@yD2_sH41Rx&5fQu=}ds`c+-kPESqus)n{TEy4Ps-OZ2L z!d2NBmAO9Uaee;$?Xi`6vWl-?FmmI9j`nvwAl)X9g8*=!AY0Z^coNY2JV}EfB z+_JWIb(aYB^w%zrmJc>(7cIV~=W0G=Ikanf=_@M$snm++w3e6c;=7v!B6w zjAEZM6McrA#q6xij*t2Cgx#3P>v(RBYhA@uqqq3vTas2Vr9@w zqE;ZB&02$KDybOBeaNNju@4t&{uRivTIr?ejqNpqHS23+$_lW)T%KRDqGWT4JU@R$ z{^oqSP-#@Qfb$NaG2CU>8Wg8qZ+4oUVPB`bEtj3aQJrhg9U zl$*7CwMUT|)Zo%8I!7VEELbq$Y1IJyu$DE-zF<^Uuv(^xp2cbsHm*dlUo#`{}ZPP(E!c586b?z$6+q58lq4wu> z*UtT6@uCRR*Z%mC$Lg>B+QI9e-Cb8T_Eq+DxO01~e`k02)NTE{d$Q#pXQVDa-M;hc z)x`_5+?AmnSFXA%e*GufJ>NRo(|z#8ty`Wu+!MF1KC+^yc-1YdzVUR?ies2(5%Z=Y zd5k^c0DUy(nQkPxm}f(It1COX15j4PoQbmpHy7G3Ulsc$TkMyYCUWv;=|Z#1kF8ht z06nliJ8!!5XX_Sv7VVcDl$9dcmnlna7VQxC5GqUcN~JBADNAcnZ8Pa(LiVFDtSkAs zYUwH2KPJ5cKh-WQ03GfiS48J%ttO4$x(!}5+4Lo;Mc=0fdoSEodcDPL_)1g{nlO!3 z`bvUSrVq5br`bnf4FU23o>|bNZU0a>ss23eqar6&GA|t2f?@w9W*Ne8I3AyTNq%qg ztB=dyc>ekL!?)ia-~T*Pkjc{3*b75AlTxr7%B5dKv)i)=v)5dXj~1)>6JaVnsd0NxPVYt}y+?Om|Ata9FsU)%GOy*&lacN1?ON{W5jk z<022M4sDSaF5W+p7s+-dcZoPxW|toWgZbLqwuRO+S&YyavKak!B9gB@kyz2BbBS03(f(DU{bNwYsXZC^kS!Tmj_Fg5OD|G9 zCiHU`?k|YP+!H<$=IflS!@0!Cw{Rv9mDOI>kju2G-qhWxhf|ec=Lne?8!C)|GnI{( z@v?HMG&SEA%ksK4u@nR!6161q`9e^BhHVA&oz$5VFh`aM{!Mrzl3|(&78d!I*|CdgJb~atQbw*t^^m$^qA2&t7EoFBjg6BLE{L)%v48h>P*Ho zz)rjx<#C&XZKoGLZId|hh27UX+fJ{W=;f^U7c=zoPbTIibkZrkS*@RBo?8Dx z{6N@_gxy*SWX`fH&(izZ!Vgv1OM1MnvrpA{lQ!}Ai1Lo6eX8z$_8MfL={fGq$dFV6 zld8MN2NiG}CEBcG{cJm0VSQ2J7r!~NaG@-25?F77+g{vfReS81bTDzBjo?1JJ5ro5 z$|mmwg5yT4Ptw=4(%9(qe%YE^kWkrF`@LE>jf1Eg`^BBGp%tQTZCTRb`#0)No-1O5 z8H$qX;LN&5r|RyLg3{;3PN8-?B1M;N_xDj=w43#T3JO|gFgHKZvsFe#s?cLH5M;TBs2?J^xQA&>c9%~6fqe4zdXW{QRz56_ts^>>tl zEs){J^u>I#&lk<<1jAiMmpM%G8<1}m%f;l|`~?{XhA&STZa*S zc;~izhYL#9JxJq8>vgS=k@*Q3>4c1&^1u9P!g8s9M7=|ij1>1G*2`5HM)xgNeCZuZ zt(*3OTDK}6r|N!x!jW=`?`8J=Y@sq;w^Ckz$^A^No8*F8cOO>6=cn8K?-S;PmnI=k z)w)S8sO|1UyCF^GqQpf`j0I#V@J|Sx& zV(zH5s_!Bpca82TNkjb;oJK;X&nKDAdO%T6Iyy`)T)UtnP>?K*va{=62wA1pO)}C@ zTKf;`UYMx6gX->CbeXzo?$mboA^YYfGX1i3)7+`;?nB*}J6%UtqFQQid^zR#P1LF7 zcVjFd_cf8?L}khTYo%m8{PP#p{&%5`mhMaYPwOip#>!7=Hw8`>)PqP1?fuO|d4uu6faY?3f#dKa2ym zmGWJIW%5bm(f)0eJwj(%;Z`VTY74?I_nS5Ja?&iD%_%fk}m_a8WLaORZt@_VPI>`Tg%2hbAiiSMG1O!z;AuNe1sme}XyfM>qE zS-xe}jzls|*pb9N9}-I%B4N=@L$u%hf++r(+ADAJV;xK0vbhrdv;A138ae zEs46dtlt7T|2#5gPkG!`oX|;>178@*LBd0>_SaE4&U4+Wxv!d5($rX!+*8}Co>}zi zVytmu&1fs;L5ziny9N)H%0kAH3}hzbEcUU#$3r!PbWEij!ysX&uEqIr2zynA1!H@( zWEW-MUgO{HztJxnUCamt!jNGoBY%vn^R4ijSLjZApYrlkx-*DBC7p<{&0bgmQuzw# zp~|qsg)~V>tRy1E06!tjEfzYAZ!me1U0k3P6N(W8Qx^D61tpn?15OaxtwlbS2R9DK z|H$4bTXVu8QUf z#zmtgVBv1J9LkuVm1XQ0p}bu=vNB(%;~kRx7H~P#i4>5H$$^*+se$#94Pc-VMR3f9 zfoO&}-CilkL!dcNQL9b85j~-P$PhFd+=da&Oub05t^H?*;(W4ZIbEr6R4qcnrpFZr*d(%M0l03D=8bfTk;-@p7 z->07O8`SlkiuHYJre_p&Ka4XIxpuG~OQ@_kGljPE)P&ilOzSbfM>%zUV|UFg_w^a& zuwH+U$ykr|YC=AQ;!FAAZe?sAWB-sKPH@8smbm{NMjct#GF71YB41*T--RJ zMGk_gS>Y<59CQN%&|YhzM)Tp_mcw8yyGdlxD#8J8JLmx^IS4e2##wZ84!ij{c-dom zc?RWN623akI#*E&`jHO{C&@cJlW>rr)H7M=-XK&pQPCBg7;%4`{(san0DQNUqX_GO z`h#^~OlZA7p6&_Bufr$@UObe8`$%A+Ac`<(V{p5$IHWTllL%J^NvD@_qWDz-SW|WuVG?)9pwn6f$ z0zw!36&Q$ScBA?REQR;i*(#2c5_LiSkEK5uJ%-$~}@>FzxLS^GY%c z`JGUn>c?9BO=7LSJ26!^t-)td4!nVhdQtA*6Xo8Yn5vV?&1x@O09J%){FfGC8JFU} zE^V(C<)>RK<(Evi7InissobRLz}oKLL~Hx*ctrEKgQNWM-!E^BsHAE@S3B|hxF8IzFA1-bGVPf>6Tf>VEu)LRM%@l~S7u4-NQIIwXFBb}L}}(C ze;O#wRJtr+D^x%pL61o3u1tn$CjDen31kS4H;cy&%#rX60BxZRhI$XIYhK=3<~d*s z=9Gj=8nZ0ft;;Hbqo@>Q%qqE7`8V!Zdh-yx0XMyM`@)n+ZE&3@JJr^(<=$cTP*Qch zaHBFj&9yarHE~9Yal^SsJdxE>U$iol0&RAt9YEHlR8MM7>aNs7shYezU8tk1tk$LN zkaXkFU_OQyh}snxU@D)-+4DCAU1y7;cQouc(x>mm`$C@WtlM;ONsFmIf_T0}B9Wh>BGryVal-f?Jo<*`+|@jxT@inCUSt% zBB8aS0;*MsAEt;Q?ocyu`I1=bu!pGq9VPf3RugP}e2uR>aPjv|`(C`Rp^}%xcTma( z_WWd{x6$Ql^eo)9FfYGvjLXP+vTA}!4=>Q(6Yu(5y#Ai!Tv5WZ+_3+(lPA7^u!a1P zX&9#oto>Z60T`|)qC5A^JvNu`Z9dw}ch?=R<9i}UB79V)q(I22H&%JDQr=R!uk=`{ ztjY9ba!tS!;2;?GaC>@G>dMRW=jgi)hMX>YD{F0SFYoH}5BpF1pYkhyDHe^^G#14& z(o^l48n_m%u5m>E=v|khVQ{k0u+Uz@*C};5fqo%Cf%-=^dM5BMf?fkAXreWpLc8Q7 z#Y)lGTGYDGdeoHPHrn-Ef8@~0o@)n-pLqDCzT!juEBDP0O>XfQwdCa? zmvm}Lb5?$9NeWw#Qd^qAS(`qlGMOj9Ezb|FNHU_HTfIoYSn_7kqVfV2$)cV?kn8sf z{SPvm^gq}X)qRo%BfmW@q&TLuKRRRn6XiW(9Yfn8U&?az%phN1V@8&JN`t5K28 zHT8vy&rJlgr(65kwz37dA49XHroM3TFB6&h)2$U_A#$LJv4CbJ#v*w?iQ>G!DLQY~ z{Y3C`im_Ni{gcMYsEJzo8 zb_f~=<6Pt)x@1kv8V9xj%0hA+G>xs7jKc!7VVq)^@afPu;QOc*pZSe=8}WCFpQBay z`BL$7lu^dToS+=!Cg~`uPNPR#5QeO1FKNsAiyuQ~k`BJ~F*!`l7$_D$_O zTCH7+xJIPgG7nQKTd1rWi<;UPG6E8V~kB-VwBYoPO8 z;1qDQh`mjU_6rw3ohWaeg=Q9}GvsUXTT|=#(bPdRkWc_)waT99& z)E>3|DSd-c{ZeQK8fixRN#9WGr}9(v|8=6U^0Mupu0MwI4@xCZb0#8%{cg~ThS;_c zvxo3$3}84ZJJF|V1QX&90H_#e(Jz5jRX>;NA$&(6=}?64C`ak^}X6Q2qtcUVv%Hp9H@+<^%pJQM$a)7D<*C^#dgVOg+^<=hFI(iTZ6w+?IR- z_0Ph{B(*e<@CTbJT z^3jB>q(+Sz!S$MKk?4cq!W4ZTqCQJwIDScPll^$5(IU-kzgjohdqv%_Rag>!E~?vV zG+(xEvag7`S>L3XCB|qdp_A-tq%jtG^zhxUG#*iGTx}7irLqJ0alVct!?CAI)a47L0lYd3f5jhY+ zLl96ws%Pak7SOBau8 zmfQ5V=*;$g`*}Dg-Zz92f+P6_2cNG zoR(3fjqDoBKL|o`iig^dGQMoBCQai@(`07p^y&J{bePKq@RQYw>;`ErG9Kcms|`l5 z>KgM7x8*O+UzacE-xN8xEoU6$oR-3a;~2f_gCGJ*m*ljxbc2Q%tCXmoQpt0}w!HB? zo_BnIK^fVak(?Q#OKI%jpvcQh4DLdi(r9qGL6NY>raAK;3$l=5B z1|FvhJMzmC!WQxTc)>5>Z^XaS525PY-v-;x(1$mR?4Sw8V~10!x8JNefByXRJD>~c zgrrz5__$b>Owp&vm7R#tpakM-{3#QB5WZRW0C`zq5&PaZk50aEtrGQoh==)xBhgS4TQipKLXJeHGTv3+_|t>EtR&g znrQmbe73juXfI!{fMI@b&CwdUmROuH>yEByV%F!-tOxLebXT#f$t9Fju;w04F*vvz)4wIVHe_5@|c z`v>Xss#bPYS<;qOB$n5wqtlTQNTR2?!u|F1iH~QmFUWX^Xyr@dOoByNoJoHY=P*y9 zI~&tXq@>A~$l8SAWR{&1EV#QD}I-{(JdlYrwlc;;0dfv%J2>)DP zRBH4kON;YPV4OwWFQ|2|6X%Cqfx6dKR9mM`N3^34cT(J`?795z_vki)n~R70C2$PNtL~#m-5<@auP|!O(8u&6`fnuP>PzDl@O_}3qaZ~QI)#U|z4M^r7fv0r&xzE-mcRvDzI zM<6${YMzwtLUt%?E9kkfqduP=D<~2{~W6 z#;iTQ%Ww$7`=m&CIrM&l&rNW>kgpN33$%TZj#y9UU#jt9nZDd0f_*`S)0W{ z)|PT&zwIR85G0>$Stm+0eVVPBcQlGkQ>tkZ-|W|%*1W9IYogv1K`8~K#M{s+NW{F2 zkd7S$SQH}UGsGwf`*XrjBri*D>Ezcvvv;zlEw8IC9Ik`KOO^k5v-cwA2fDND!$|m~ zvS^x~_-!;%i@VEwv&p2zhB!!MJtp@F`7Vqgyxk-Sp?aW|kaB`Mh=i6%!KR8i*_YOt zziD%3S&i#ZN>yQMd2xC}T6~+?Hab|^*Ay{XiyJ%gB4rj6md-`2jqhs+Q`8@|8$*E* z57J0$Y#tl8(qWQ~?*a#Gsimncsr!*)Qj?l$JRq6M;1UREPTG%qP8w;|Q~>8m4k{s} z6JnV7%mqm`(rJ>Bo*?|xOqoX($9l7T=`JIt#$7zWv1oB@e%Kdu81zXS& z${NPC1}|3jF%$jDhF_VE(s1heaT75@;nX3@WIy>esqN}lTB79E42xHGDiL0Hrj3CNoz}ZCv3?1vF4= zxn;4DRTF=nUply3Zz5|I_SEgwJR}J0&(EWuqdpdG_%K zNTUD<67F6X-^6FWB79x>Ys6n6g2Wh3;i`Sr3CX$rN=u4w(6HOvldX z&JrEEq;X(6pSM~K8d)JaDA?+SqZ}83?T{bX1Idd3t_9F!G1uTafk6qD80w8cG|NxL zm%aQld;F`P-i-x7a4=2#P1f0E8gsiI?gtS~ci({!Gj6>j)ucv2uBB&W@a$a^$t19iE#! za(mri9dGA@oHvA6Ln@ndxQV(`-EyEI8VGkaG*p+Z$~UfZ=HqX*-)_es`!oDy{&Bxz zN16?PYcvwcTu~hbCvJ5MVdZUeWLI9EJu>VpaHbDqeUWXC(1W=35LjMi*uw%6m~LDn z1&RsDIFiu$gd;<+B@2FJO4Wii5Jcjh#B!6r$47e>7fwVS;^)m=9VdS4_7@L!<@R0E z9|(BMT(8B;dxmS=c?)(f=-t_y=dKy0TD)R;c{nzy6B4Tjz9L7_#V(@`q-7@^l z8C){}V!Hp`+UsvQHGk9N*F>Y&JiclEsavkEefNI+LasS_=9%GJe)Yg${L$p7lbz%) ztiK^`t237!CY_EA{uk_7&mvX1$X5QFiC~&T&@&5S25`W!jCZet|VqUIyy`OrPxZ%ksX-HcdO(Yq?~` z^XJ9$PH9hcK>>$ahkWn@PLnfn{pD=HVREcy+>Oj;TsFx}S+Ui*-+9t0BO#g-Ig3`X z6(-gLB2kYFP5zCt5KAizTx5v7{J7s5Pn=sm~?zEY&GJ0 z*lH5LC+vb2j3eZ?6FyO_e+Tk?hNbUD`Bhf_VnHYIhKKmh>rVeW*}Q$3Ri z5s=_;7B_iyA0%EAL?k6h{;c{zC7BXsTU`Ok|n75G&#rZB)H#+Wc| zTEetpT>{Fad=u-+E)7R(?J#-k*lLMr?Sb}W0~VcSwbkaf+ALO`Rp`Rb{nnGn+q(kR zPsmrhO|~Y_5)o8P7LOVEwCqzUo@iOaevwSv1oFU2<(sd^ziAJet!9hWoNkYwf1Ul$ zx8=iSYYNJWOUoi_iZAR%!_ej%(Z(Lp=CY|isOtn67p#+;@%s)GRpVc8j;=qidrSAO zPJUPSv5vpY-{kKgU1chXlAd(6_`I$9w=&fcf|W`hvc}NH;H9Dz6A3!9Fl70o{xqMUSwIf z8dut3=afOHX3$3gy`nT%%&3Z!3YZsDN`W({OopUb)7hw68HEw#4Jg@=CE!3LLFWOh z+Lb&o`4{>BT=X#v`&%2V8e@jaxh^0;U}Y}uH3 z{l!1QN7n~Q5rOyb&KITPi|;;zEGD3Pem6RRH#L;gB!gapE25HBXVY5?SJ-m=x>e;K z;)>%$8uzmPO})~mA6D7p^!oa`tcnqj-;-ND0*00{4b*ZR%kf!$pi^ZcECkqh66^o@0!^<85FMf{s`)|t0zWi#(+RIf_f=N0?H*60CXBgQjvpM0EICgJf&gB7dDnZ6q>iG zyYjxu?v3*@9xC7T@Qwy{>*AAN+sMwvdt%2%8|1PJZ#9e_i?L_peH*`aa&deo`h#AH zd^Yvc&!gAw@f`7}gyEMePop`+A})`5c6$zcmOl>j^pq|&4rZUX{O|fd_G^gZha+v-`Dhld@_v7H zBPmv;)h*S$nj}&6-_WWAj9OKyC;{*ADrqP~w`fIM5Kro~KhQZ?Qqp$w|Yf`i~#hIf$ zjQbVN9N6bJiMV3n+Y=;#d^W#cazBe_lD;X5vlGt<-<0DrzNs6br=8OL8G70cLQh+a zN8!8SbKng0&uP(5S?W;#Qz+Xi%HE`wg_Dx}JcNB{YG|@gdHhk9^82QUvIj3IOOD#U zDRiV7e=BrFQC8Iz7sCQh^}12k==Q0KVj{-w3ur@|&?|2j^$z}Vre66z!r)bh^psu+ zy-)NJ*d;Z7bMxMuqd9zU)zK=xx9n&cUmszKh)zoW$W7W*?LO@>t+Frk7{H8SL5d&t z`h02Kc4?$yTLnK=ai-#}igzoNiVBM61U4zt2_CFP0idQONRuOy0pgFoH@>NkaFe5oRWpI&Z)YAN5!xrN+ekGEWdq|(oKpnRO$bdxBIzTp zFj${i&8!wCL}oLkr+L&|kJ80g<2{a0s#pKAqs$*KKvct4HQQr!;XpdS`O{B}w_bO! z^}tV7DzFuEX&d(QAl8)&y4x3`0!53gi)^!wwK*3%*E!`Y%&g74*vtcDz#9bFNkv7d zQ`(j?p28QUT#F@2TmD1-g%3*_yv_*Mk0CJz4ROEd|993!78BWw5G!{DrX{FW%I-@ZeWQ;{VKl z&;Dibww+5-1N}R0U%BAGNNvsV0eJ!%H;aF#X6*3kwO2oPpsQ{F^ENmGWX48QCm5JHsx;Od5?zfG(%=PfsTT5WMCIYnQV= zxjrDt$jF-@8xx~Ztr{c9FrlWG6BN;&Orpp0@~hf+uAJxDl(GE(vG?ZjQI_Z4_;cUS zGyA?T$s{wGtdo_jBq8Ks$;Orh!xj<}+4n?15O9fr3n&VTg1CT7J!-L_M%2~{w$<8& zYI}NGPfu%Ud)m{t*6MrO*PbKH@P4oRnFO%*?fd!sexKi8KSJi2nateJyz3YpWJc$*Ws`e9fVMo;Ff`aLvfJ_H9S6soAh{;ne&+o3bnFL(2== zYNBO5TUr_hS`s?0FI4AOO+ZPVw?sBg`)-l5pTrBlil_hX)3O+A2oQr`#S zWQUsAK9Q}dV{1c{MP1&^cXMIuS;6JI6D)+fMb*{T4k_S7I4Etg7&2l90`WPD<<S7p8xoOI{8@dd1&^uH$qW9E0B{_kR&y z{Q_hI=qQK5FKTt)LzGh(eSYQix?Hv_Ld9)=udYQHv?qv%f%s6IgIy0~4M8T(nwiDB zf`dUmJ;?HdwL#wH8T9a}9+vN^_3)U~*6ADY4f&4wPWp7szD@)*9P=sf`hMxVh8X;vW&BAE4!ip_0)e{e7}0+{imLM|JG{d zU#o8Z@#ByG_+}`lOtZ(~?D3$!Zlje-vPKOR1{HQ~1$OOL2NkXW5WUkefTEL%Lv>F= zAhFly>T)|O^s2s#7=_$rra{6yp`mdwOgtz4C~y``^F9U0B?XMN3nb0&?CP1iDzT*D zfn|NWuE|fm$^OiGhgM83xj~&(*0-;pcisb8@sH3m5Yq?!{l5fFZo=!i*spQv$Lfug zM26oGxHG^vdv|*|SG`@{L9cwB=T;BT^VE1+JS#lPa@Q8u9+yl7A*VX!_5K_Ecl%|( z6ScmV%gwC2puk@IqxKF6QK+kx)8QW^3mBOrGg8o8x-{)bu%reIq{Z zAfQiPPMnV7uoQP9;(Bo=%k)YrYJy0T1L4>DE;tv+kV~tKLlj#|P#);9%=${wB3pyYP%9=LE9pW?4?nCSCOYMSUyzA5JM5$vj) zu;vBQI<;L$y&2iX*^MY-V)6U!tqx1Jg*z-KExgk*0MiWWWEaT=t;bMwj!SAmDeBT9 zjE9KFW9-Tf$Ng6LS!wGdI-dMy1W8TD?tsdnW>LVS)bG)BQOb@w{tU=eK@>n<;F@3H zdgSG3Lr?X{eWMm{#BGUM!d1DoZ6#hVSKq&U+J@}Z0^M(tbLXs`lKGV<_~wi671u{? zI{8+kHLJRI0qBVtj7t=9l$GKg=z(U~Q0ZlS2y>}t5VsJ;AzM1@{u_HeTvsrC zaI(6nDc6iTEI8EIJ$LF|;oPcerOn+Fg8JRYtdhoAOD3wFm01pZPW__kIjzk#V+Uh- zJcABK)any<91*{SI0rq&7-63LIAfjwU6sL(H)SZqoK7I7oKZZO&84MMxGiHKpXF!d z$E4O_&xnUZSGBi$JQdznDW=A{kY~dy#kxuy8Q>B&AzfmA$8C5ZCO8eLm zMfx5sOY0c(6oLXA=S?C3D|8mr0+)Y+BFBC^>N#h(6gON`9ZcPAaE3jvd$PQif{vzg ztF^eQHX#3td?LAIe9L4uE}6=|)4uD!e|dJstZ#hq=bO6p7MtNgy~U{9L-fa`N5Ln0 zlun$eWW5I~^ItU1p&WLl-9d>KN-F3k`7%60gSt z{>D@}EtB$iZL+iyr}Q*$etc=uvaTr+h4ax#Tb|pn>gC(#yjgP1miCtAYF4DJYeC7h z<H*!rv+v+mB4*XFXdH9Knd)yV0~G}AREj?7Q;S{WR?($$fvA{25E zCq!m_tzoO-c7tqalRzRXtk+v_16zs3O8r$;QdgUg4_D*w=bV^dB=3DnY!xft*URroJ~iT zwhS$r7QD`1-P9((SvqU^#_Ou5546^0y7DLWPS2S>xhlOevP$!!uCTiN$fm}7?!A5W zQ?cK;PBbOp_L@lgAopWbo5YHnMR+(90TQ+<{^tM?I#@fxUc$dRb ztR?2go`E2R6GodK5~us&4I*L@|Hi0=lnm?*k~s>H>zJsefXpQ6pa`Y5Y3D0DcYgK8 z=J^l(%gwy}VN<-g#FbfB9R9jDr!=%^?tyhphwq1umd`#Me*KPFGw%BCtp|RzZ|Xh7 zRg}cw&@FRj+&VOvC|~~Q#&>^EeeGQut&q(s6LXO#%~s3JIpLf{jvOQ8Oel2BdJ?hI zR!3{DLzfMBl};jsD!;(nmF>4ft8xf-EAY&Kwx$KB`WrPR^NQUb8>UFnWAcg1yF1xaT$^d;vch&N2E80=SklVB13DKr zH1s`NYEg@q+u3wakB6HLVFS--lfo<PUAI@qo!X-Giz88(VbJrm z);GQqbl!_d6v6JRvSej?6!V7u} zj6u>hmZkT(a+QZVcE7al_{JA*oLaH$p*23T!aaJpaQ5bz-M1_(e>Pa67S5c(rtq4> zKfSw6pIXBX>)Q9dyMN{D53X|G>6pFg-mZW7XCt3JcyxXJto`5KK6rFp{h|HI*KEYE z8emVlN9lq+>DOvqZIJ1mekWh<*y7mZkO{!m;+W@9TKp^g*ZXhtD~Lw**ZSpJKQsA5 zD7n(+bNYP9PI6vNpgu;v7P`4oVklHdjf12g@c4nY=YS- z5D2q2#P)rnA|TnUVgbxvN3Lg!XOBk~E{&O}nZxTPMBw5)l|2eGblCup!2A-{o6;xa zAFQIt4LgxeLfD-8C(<8qg}_Yjipkb0^@1nn!QCmB;dBHi6d~UM57qUK^UbLTUS~gf zqj5=9b|fd*9<7);eR|;&EdOhWcO8A>!MR%``M|BlC{)vvg3p61= z1#yy&eJ8HESg^wZC6Y8pD3KLDQX*tuR+irHut||Fr{7L=^-(1W&II7ooQve*+_Z`I z5~wT9*76`K(Jr!cgGOAM2pw=$@zGi9TVls5*FC%K_||9FRX!e_zJBKMO)NTd=VE?) z^2Qw}HrBAzI`#mo*?3|H-;>N(vMLMQlXAXvg+*}YY;0}jb zs;Ee0OC8P43154;x!K%l9x~rzzSpdGn9rEsMF}}IJEz@=qRr>cpO_W1d0I`onixzB zC*Dh(Pw1KxtTQo?7)l&VoJ{Bvs_Goh9?9m}Vcph-+Z%X8SDwGziBzyy_5MoeNpSYk z1f*vGdIN_4Q9T~WW}>Zl8eJ)PNZn=Anugj27bJmG>^BWj*u<*%E5VXUd3klYZeRJV ziP;NAdWuRqhg&Lp)H2kVFH?IeTZTJJih4#CWKW!3?sezZQqRXWz7-ynfC7 zy>pN3oaeaRJ^c7LR*bx}w>`gY>7+?Z+w$A@zB97o8;=jWZ+Fbwd1P+y{cGxL7w>4f zh$)5Ms`)LDD^|m}NNfIGi`jzTf?~FQI2!b)K`wA!AhMCZBY3Zlz9ak>t+h}79ML}ON#~xT;96ZYemPnXeGxPv z{4Nh_?UrhO7vzd%#Zy}nzA=oC6iH^OPmilo1#R z%04&JKcNaeoX1p!o^}-ohcJWUf(JN}Ns|%5k;OWa3bd-EO-w7mihV`KwwOy(zaw;d z$t&{Po2y(aB2!9BCUp(YeyZ(_)X(I))bqJ>=3Y}Yc*lamypD}tZ+>+Yp6QhvzrABa z)SXNa4k7i1+2^v9_w8MDzWeqi)nabWW3Cnx{+(Sb=4OjD7OzandsMxNiHy1A(ZWBb z<;?{~r$l;LjkiYTM^;AUJw{d|vz^hqqCA@IKnW9Q?!KMoyUgHBbiDOq)-0B?#5N!| zICw3XIrm9}jzn{U4N^g|f$Rzi zRRDm2;%L;B_1rwKc|~V~?^x#CEf25Uf9{Sc>YnfJTXAG)cIX*Lb;q@n=I)qVqGMm? z*KT^@hUSVnD{H5;-nK?v@hanwJ5I%#S)AuP6MdsE{OyjA&QH>meVO{4?R#$A-Y{N{I7)0d$)$RZ0D7#F!l` z>(c{8jDQ#^Tg~pu9?Vv<_1Sul)K@@j6NFGwP*CG39YiFMaWGpB3_3k!gL*ji;nET) zCyido*#J@nJA@I)gUsudvIbo?Bw7Tphh!))`?MgXZBkHA`lutIMi$k`;ql7= zS@Nx?z)mDqkr+8;acCjyXxgGExvJqBbjx5q5 zwaEgD8aMT-diI|Fyq;(4%MoZlgy?!5J=}|UdquCe>3WeS3ZNB!3JxlRjQr69+${`) zNcK(^GjXVcsI|kLfZU+p^e?xj4xHisGpPfq1q|Ss3yYO6efG7Kz*EtBeP8UmTs+n4 zT^&N-oX8!xU5%{>v9)fdw=;xDc-xIezsCD|RTn$pIu07d-g~L2RI33+#nD}^42!>a z7#kIc<>UHO=)G?MRZ=V#zj?qQ`>&`FNDE9@5#n!Tk{~%nllC>M1c($TM!E_Xk!)qH za;Rzh>Fvos%MT`t*wl0xtkl1-s;3@ddlwv7-2m8WYGv)p1B2T)rwWJDXXZGb6|c`@ zyuh=%^>j{Pyt>qCg)rDO-XXPRoqebMF1vineHL-3j$p4zb#x)F#pE!B47^X45xE-b zLbV4%TRKArLdW3+4Q1&24DJDk9F&X$I=2Ckf1?n-Dv_KkjZ38xZ)0VHz<8YJ0JQ7R zLxw>t5s>PWW$+tBs@i2kOp#6=zxd$sZAH^3mbs_#zNb_FI>B93F)_+B9!&m-9Z9X` zWmP+#{PBL4dN6hS?dOi|s2YgL*3f4IqFSkr@MrI$WT|3QbK#~@0W~n@nDX={ePE?`tM_)VoM3~^NWS2W z_-t~nQEYZX^OG!5*?&b=Kgj41+O;rLx`IMbDi4H$3}b@SW!m`OB*6vW{ofRh9V_&{Au=(>Zy$ev5vOUT)RT z*KfSF&cK_d^Z6`AKoWkUX5-Hv)5>KH6e$vY z7^&_?^u%)DWYQ!%c$JEUr>R&>_@yd?+U<$NDX@hE5QM>^Wg(9tC6)Nsei+gFT zM?P%Q*X$khO3!|`t!?xx1?{V+%-YgZ z?>QM8-1E$u^(Ssn3p-XfPrH6WRm+~U-pa)zowMtf&Me8DwxnUlrb9c{RCd*66;AJ+ z&@}g!1*O$XcFk^HF}v1buIyYtebM0!ld&#>Ut@23q(kZkOCNJgzd z0+IJU>v^lvY-MNhj8pz%n2~&>Q}Y(jJovewkdxC=)**vD?;A#XGh03PSz9z3PSd?E{eBr_LeiwPH=?tPX=hB~W z<{(Q0rQ@TV4xzluImj^=;;h45grr;4dE4E4-CR^HT#tf9-tiWGytgB&QQJ>CE84UMb`R8+5n>1DuDPZev>fAjMMnG7yRbLmO>g zM@e?oTy+nJhMF&TmT$r!)GV7KU#$B@811v#-F|GPw9^4z0;hla* zwVUn0Fj~XCY6jB1kk$O9SYf-k!gwsK=zeUjjEo57u;$jVn?c=i4wy7#JORkW;iP5B zEABr>qln3Rjy_*4D#qm}Q@0oUi>sQtR!$3*+bwZ(R!v25QK7eq|MaWMmr`Fjk^1j9 zZ=D^}>5eF}r*7Gk|Ko9X{MzELBc1?j{duhQ7+njjwV^i~rr~C3NV-MR z(Zjvc8IrC{a__K)2|uo!0`{5@jK@!D=PnT=`2ldSiepG#hqT%9oywOk;C%e-YiSxA z$6DjHs0SkC{(RA|tFX~!Dd<~u!idH5PHyBk3|Q2?!W`Pnl6WST$KsaW zv5cZ>Rvu142nr})W$1lZ`K9u)f^@G0(nl!aI>EIdp#q81A+Gfa(19R&>WC& z3EWG$)f>-el%14AfD4%}&^zfr9(xew-gjOeK7Z z+|j4P9Sqge)<;Tu7u0l=Ej|G4pHAT^xrOc7W-`{v2h7LKywf~@znERRJ_m2+ynHNN z19&&CX2tP<4s#1+b(={yI3}z^Bow_C0A)7UaBSkOskPKF_?IS0$I!Ldq^TD2|e`! zqJ8Y_<)@z7aQ#z{$PP&4y(FH)dUJ8=_Muea7Qz*+LBE%w-&WYMg0Kgdt?;t7klQQ) zOMyk!G>tyh#+Gao>^#4yU0fqM;;LqWo%?)9#?hx&!;yBV zXnNcZ4u0bCe1F8TY!C1iA;r+v?$giML5ab&{_(J)h5kXx>Av8p_L7t(P^0di2 zPH+2PU!MHwg(p(4w#@$0!z}RSF|{Lk_MOxJneyJ0`jnU_Ey-nF%8uQ+Tn()Vu(eL6 zx3V!E%E1mej%!5(NQa^@TF@d@69}2SSL;xo0hC*iy=m=98^D#S)8(E+Q3Cl3%SHax zO4FIYqcJ7FJFYNMznQcE@@yY$V+rY`dQ*#So{d)(vMmU8h8+xyF59=5pG+_nW-b-+ z&>{<^L^iLIEzvzu9xX@C5?I9$V$6p`iM})JmrOT=vza+N!i&OdgjLKLjYcb?gVAA_ z_@g7y_oC;c3M^%wR_A=@N~dgwhU=uTO|bYe+ZgmiB1Wv8fcwy%7ZtS0@~EvH@DSv# z7?(Sl8%St^zX*$@5K-&cf3WAw$G$Y_+Ky_+GvQf7M^^0n?jCi@9q0D1e0WQDit1Hwsx3x~GT{)+s`GyxZVV&n= zz7Au){8EmzPc5aO3_h5}5Lhyl$%cX~(&sv6K8e{Qu!tGSr244B(|dvQ0fn49R7PDm zc~I~VPM%z+wNF5egSo}+rz9Dms zMeD&eOFE{jDvX_rpvub;d&=PnOYgfD3HNJt4?X_)LzcH+eEpphYlk+hUAKP2dj9M` zr7pa*r04jD4u@w?{cvo7eUHo7b5OI zQ6XXIMbsm9Fmn!M2W|=Q=D<+kSm0yKT0ja9SjIy5v~x=XN5O|B)Dw-hendzylD4(& z(O-oKP|bCX35hcgc|9QhM620>#k+b68n?Z41N-%vKc()=%x>8^1TYoXy~n50<)Fi+i4%FSW83>pUyhYe77TggMZ59GeeZd=OR&NN^3nPoK>`pUo)= zP_?ehTM8VJYRmQF$B~#@N1~N&uKJwY}Ip zbEd_G;ODL#nD_3)3-X9f6zaS$&np?hu>yEx?gcfCfez$7DuA(CR4D33Vl$hDNv{x z)(-N>pRrxCDK^&ScLeqJE{o4*yxNA9#->%`xFk7+?B~vNlxl$l#TfJe4l*cg-g;`Z>>!JOjxt>W7llF_p2Wr zy4ZvLB-$$FLO@KOp7bum!Iuwmt-ZDEnDb@83?yW(VByAwG z+tRi%vT-#-Hoez)%qYxT6o^YEE` zhZ75itMPgjRM3c`kTm63JOh!U-W=Js`e|mts>F6hkl_`UEdY`<%T_b1l0{lIVb}^e zq8VN#3238dV*LFV4@GOrOM^#)rR6ozLodGHR>-Bp(Btu0O-rveC86Hi6KhW3Gv<%u|lr`6%jRlBPYYO}9|Lx3?) z3{M0U&{vRGAeVa*tsxJ7bTw_|_GScF+jWfuW9P9BHKz-)Tx7H2`i>l(ic?O4~PbhSH2rHDYt;r-gxWnbnZq|GanQ(V-cEW7hJSYg%vLHS_wrrcAkgL+kasYWvzs z4NnAr`sV6m8z)wG4VF$^-&0jH^c8P*^PpN%(3J2mS=>0M)Vu+e;wg7>tBuf!*^MQT|13t-wyh^~zcL4878U`mn;3@N2I01EL zvu}kcK!x+KT@nb#eK;`<9>;BZX4bn28e1g{O5pLZCeb8ps)7(p8UtkUv=Ir073@Q{ zN|t5D{`}Sn0wSgKXAu!}DlmKQ44=H>!rT0ir+!ks_u^r6OX#$skCY3H{TQ|+JwNj2 z%dvn`D9=5f`x4{%8OGCxF9xKi)tXI~T^23`!utB$6rIh7kOs1iPC~3zk%3Am%VvYkX^`#4O{QJ2$RV{B;?nFP?r)U>gTm#d z1Z+-US2=}h2kbtowIWKuU>2^nG$CyvDqsjf5Ktj)6X9{(+@x8AHFML?Ml0ua%}I=Y zf_(??V%Y58ZXAQNU!Pn@2hLz)-a(0>gT4GIN@>oLH0qOBvzwmpo zVyPJ7$kpa0*$s9oFH7wX7Zz7eUos_9;j-j=vMY=7^AaKcwtV8%)Ng)r-{L}}?p8&Y zSn|O8Eb_{Qw?%(fVy#x8zfokD2@^z;?=;?Jqce` z05#mQ(Gc7gyaR#2&a7z1NfnM&lwK0L@x{qKBq|i3vaIgg8KfHN@U@4f3L{u{13+ug0{sF46vD9xK zO8tz0#z#MU$Lw=E6rIEEHNW#Q|FM6P+T_33CchGDYN`(;OF-baL}zz*<>q#G&x-O} zF#}9`8RMt*vPey8uCA5CkH$=`$IPtR{IQu!@V|4jvAdiU6SK}IUqM+UM`_U{Bu+D- zl558sTf;7lj;3@(Dz)sp7v5$cr98L?*@`}oX&$@3K`K`>Su0Wv=vt5IS+o9QJ(my% z#`Q{<0hyv`Zxcb#JwXoQj@sRnpmwVZSZ8tgi~CUg&{U-h{`YIf?>kYPnXO{ocVXN! z&?kd5QO)Ld#h|l0z;cw`j3&h=8&Jni7*f*V?jOC~FCyK4hYEY-dP5&} z$?v4Lj;0`OK=_bPX#7It?-j8l@LfWFYej!<1C~Aj{ijsA>!s<%tb~XvfTRVygyltX zMP9?0xW*Ww3eT%G^tUjx*y4-_kpwEpOdHefw@?S-CZJizoGA>cYmO%d1WD98>M`!|OIaw5-~A@g+Pz0-9KYwb&r$iVSvy zcV4YLEogE%Y+@b9ji-!UMT$axz(~(!5$03?Sp@~7S{=mnYz!oO61Xs#$o7(1CWxqx zb(?)8q`jCXTiC|&HtPa|E;&&Za8bnk!i;`5MFJLFd`b|inxju7cmzm8%d zlXwx5den(#;t(!+@Kb7ad#lCaU=*fw#vwZ#8R!tIM)07T8Ecnd+0}RXe0JY>c&kuK zHO8hH6JcXXgYC67h-3C)KL@mSj<0-XU3^YgXWr;&{^FJEYwtUReYG*Cu_R=4c(6yY zzkUQE$Mx7g_OR&Z7*DfEgQ%U?K{myXLF2HPyt6PW>W7i*k%Cgrs;IG}Sr*Bz$jPqQ zfy9kuO+@u49dj7uZeqr1L*lp{QG0^9BkP+mrr^5rICCCnuH$1elN zzB!^lMq5XhebCM+%y`6pz^)vyAGe>fpSOQv*CEjOtU5Wr1c(Sr2T7H`G=8il?jXq$ zj?=OUFc|~bZzRi#(dZIg&K@&?W?{aO&daEpssFGorUyVqMZ$GzUDr%nX%Z0PlQtF_ zkMu*W9X9_s9aibIE;t_eE+c-Tj!R@C+QQ>Ej`n?d~Re_SB#5{hV0q&*v>xn@YKci z;p=XGgr9n9Y;1Ihj14Viqq2cyH~c=vhahe-z81AaEP%qiNb)J_SbIWNTPu}t8YmG1=TJ!%=Yj8J+br)3Z+j}+9ef=WQj0KkW&6bPcm z7)AC-dO-;l$<{Mw=po}5U`|(>{eV9#1J@Z30aPZ{ZacGg`4iW-ncgr?=;*JVGt{1^ zq;|2JmHhUhIa~KR&)VBJKe}{abpL#wb;G&C^Xr$~IL%tyU6V1P|Bm^)dv+}>TeH|c zed~hCnvwTu-$<>Pqa&E3Frt|5RhKP`vzGY0I7h5(OJrVzuP@0f^>DXDo(%zXjkjL}#Dx^TiU~m|z?&M&|5%l@HVHt}8 z-<;^u1LKPcfuSdz?1Bv18skOq7zlG*2qrODgI$Kfx zZX_uv`RmV>*TJ-4URtneFMytH(Cl`o!_t?T++unbdof`eat{c|fm_#P2_l-_LvP47+EPUzYmNtz2nkLYmDiNR;ZdFb7Mui9C;e? z2y8fHrVwPNI#_U2QG>X~%7TTI2KI6-;$!kjnImpTuJpC$=hy3aWouP!ZJks#QpKxu zHQl_nRx0T-3CDpQlZxb<$RXMl&Oo1FM@&i}St@x^anT>?!Rk3b)Q>G>|HwAbmWwvhwEtXVP@03b}TRH_|~I<&2-q9=9Yk1ou#=U(&-uw&fh;eu=LT* z9k#R1eOrkK>DbMwUAkQGpxXY93C1@}ZP!1seD9fUwLI&|_eW|f7i^twU%aMl;jW(D z^Y7@NkWtfJYn^uElKT0F&w)Ng{UOvf64|)%8c8eCHD3QG(IxAI#g!_Am~DEK040up zhGk-#C9W^-h(rnB>mqM8Tf5QIG#_4c_YBB4> zdM?f@kpS2h$ZWS1!un%5A7P>%K^X+gNW_~R?V#feO21d`IFHc1{1OQ!v_yAycGS`x zaRgA4CpsojjENKiLnD-CG3UT!6l<8l^brS1lTcp*o}u}zJy(hva^}~6P`&*Afp@=D z2-x=GDUp1aFE3sZ3t4N}J5`exObiWet>(jr9utzso2iz^-`i2H*WGH!AAEX*M{ru} zGHYg(ExDG)O|j6YFm5kKzC>*>TfJ%64^w$xFU&k7Hc#BtEX~(=;^^Gshc(m^?xfg+r3~H^xZH*<}IhQ#SKxt#+3G zF!l0LeoL)(oY!eeYN}R~q}X3mCJ20uV4NAwDu`Zy zlIW7Ch(2BpUE@Gz!GU}ta#2*Wzl!$&tZ|w7j2+ZpzAMrjKGvFIxm zkhtsRQ7CVz<4nwNh>eY$xgB}$TF>O#)UJrxU3y1k_=tkkv4;k zD=iRJ1Q?0z9=JDqW|R*-nM6c5yDxbj>wK!K$j-%dHD~@S#6f)JT%HK+z|9pd0iTmtAgs zyzf+R3(B^L+hNjgG!Bc#f$OfT(OSPE9ztuMcyh0x*)=dC3Z5*AQ%U8e+8xGuaxVIF zMGYp*&sVkiK^8iU_^T+mtkp1&L9`1o@1NBGaQf^Y`hNk_lRPc9^s zpZt7q@Z8`a=4`*bL~h1Dk)_1T0A*nVkZ0Qk4}g4BI?NtIdO^+}0p$tfpU6B7C0UL! zx%t`DUs;{*`^o$GM*JOZ-HW!Sh_AS!_@F5zxw& zO~R_@iJ^&S;kW;eyo7H|B2Eo$_NN|@9Sj>*YE%pVew*cJ6JBYo!XxZqfQLUNZh>k* z;80}76>ay6wo?y`w|x)#tBU^Csm{x7>$xBO9h(N}sC<||gq|QK0?l9DXA_Hg_*<#( zFw?W+edf{&@)9L1+BHZEUe@dU2==DtOqhbpbDWP?hNA#wJXDU7g)p`{VQd|nbSHYC zGq6X%A|b?y91%%mP$tf2r3nGlRkud z(TZFi_2pI4J)}QuI7%SE0}5O-V$H_q*Pu%;o#+(x2a@I3G>=Vn3`@z^i0MxL;EEL$ zGmn0y4k;Oo3;JEF&ia3)UyAgIjy-IYoPViIt62Y+Jxd*;UX9U&box@U;zu4S3w+=~ z^I78?y0enONlE^#$ddV{S@#VCsI0vCQ`|-~N&hYhTQrm7vaUuaSIK!k{=9v&J#~bw z4u3Pu*2urOFh_aO^^}KgOx@>siim&g9zJQRnk%`^dS%BO2J2a<40b2EC;b7pTpgDOFZRC-N!b1o^r9JZ0UH%T&c%Wk7@h3iyf4A%CEy8Sf}RsTzRenGy@opC;0~)$~x7{*!;~PfmK* zN)IUG^SveFGRpBd1$Uh;qdZSLL=ZN?Gh?*zWp#AIgw>u+qsv}iG`f1V z=ceGO>E)bJ7za7>7;QM+^UAiC!N~j{oyeYND}lJJ?%jwLD{zZ93Z8^AG<1kG_BZLEd(M z%2|HRxdq zGVJWZ@g{n%_{3Nfd0_{;i{HYwXH4I;pl3^Kc6RHQo&}qxXQbY*YcF=YOWJE|+Dkm1 zlJ->jKl}*m|A!v|`7rjWJe&VS@YhV$CO=3=C0BH$b&(}d;|`!jg!okP6Z?zKslM4k6^zz5jpQPr0uWv$XPZrdU651(w40Q*5 zCg$VMrB1OffMQwqezu{RZO%^JojMr7_cx;L>qOfXYFPfz_(!JyP$zvTp$67_AYvo% z!}`y)PWzBfg}gXrB1c^V9(|F`O`ST0#v`ccxFtKazZqLl`UrNn^AOs#{PGU$`l!Y1 zI3l6QqtzuFj$jK2FggZd(>-cckhX*-7dj`YqQrB?>>9)iX@aox$FC0`3V!SDZ}5<~ zK-PKXv)8}#ov|y)$d@kYPAEUrTipg!)RXiOz`>tk+m=(n3d&N!D_nLWKf5IMtr=cs z%q7OJOQaoTj zFTqR`(EocR;y`GNYH_!_EF#r+c|UnXV4rT@xqM0L5WD0pD~u>|Mo!$CSr&Dz-=6Bk zICHQ7vjirxOD%>#z|EBJ)9KMMQ?D7N1I$M2pw4D9DFe-@!2=h!Ndo%X1cb7%bO_QE z)_}`KJD5JeZ?F;+7fC{3NZ)^{L8j!5pQx+AO0$Q`6P?k4D32zhi3U_K7;@T&VWF^jkyONaFNOQ) zrJ)7_vo|zMs@|xHQWf^ij#5X7d$Xs=GvqktI0?_7((GXlkHb@)x0yoT4TkE?dS^ov z`6wVhzvRzvr|W6ajvi|aD=jJxlJgDfrx+P!c*7gwK`RjDuD_|gI4G<=<5ieNq5*tT zte%Kcppq=2{&EdwW9(Ga!r=80 zE^Bjt#avJ`C3n*>fzs%AVK@6t{zUEk;zJBM6fh;?#ft60S(5gDRnT zvarw`Y8N)L8*!GE440N>N<)n^JBNEKTGiIB){$1FwY_)t>>ls1$5S<&JKQtU!^?Y` zd$`)z)p(#$b~Lia-k#jtp58{qXdBK{QTaGCvwx~#$T+8UOIuZ@vtmn8X=zc>%(jk> zHvQ(QZTK;DhqgEuIK5tHXv$`j-U#;=OvMyTLz7P=Mv3R}5}ZI=8oZC=PoU+;($kS= zK@@-#pA`9&#J9w%&>=`t3fbZaM2||KWRF8$5fK@fk6et}GF{G2>j%PZJQTgwQ$uC= zs4Td*s#42i2yZcOE$rMnr|Iz@J^nP`IM_R5sb#&+iA2reoYIO=Wl=a|!qkG+g~koK zxF-^>sq&ZQhc15NFKW!4sLq@j>TPU$;DPC-8G*{Io*wzHv-Vs&F}X?C^7KXfHOsH* za%JRr4S9vRlVh%;s>%7qlZ!m_+pQ(>{CHk{9J_v!9Iu;E>P@ZKlzQaad+xb5H8RI= ztxi9;lTv=r{#pzh=rZiDNx<67TQh;x$!+oil<7<)tT3_FTdda1$yJQiZ_JEJ$T}U! z%<=m(DVa?s3v3QH25Y06V^uML#hYU+7z@VqWt;Pgi}MWn&8`YEJi9`)WiaP|XTQME zuLaCf<#H`#R)C*LHW7j&`<&sq0Gma4L+bD(o!uv0HP?V0V`n~ZASSO#)ff}Aw{%r6 zm^n3TR$}4c`pGxExU1zx1!qW#O3`9isv@$o8PhNnkoxBm&!wV4fZqkZs(4k zVs}=a=MJz%_w<`y+;AX(1iNwfJ%9M4J+mb1?nFao+m3<#_F`WmiaCV1dFe08Ib9cI z;{s`>)G2*aHO;7IkO(#TkmN+fn+tgnD6;oeFWxW|BCV=S4|6(a&F*y0CdTG)bTVl+ z>zv&=TWZW2HKVpBl+aPHx3Rlr!f2~h;jG{l^kzZfXyY9Hte~@kMJp=sW<}}g+E+VH ztIe&fwN*8QPCH(WpVndSNf;FK{}*BRrz7g4zw`@51-3dN6JqBepOTOU$A!UhKjau+ z8c(d3mH{;nTam4#7vE`guMn!I+a z*XndiR@K@i63ytWNaGxQ(do3|@=Dq>lZ)psA#G1NuBy!uZVq>bhr+iYQH}H}3ym5S z#UMPvPST9YNQ(S)_$7lxnt`Qpp~C$MHk2nyXo>)kCaN@4gY4;J3B$;gZXh`PxLgi2 z5P!RBj;qDHYeU`QskzaqIq|94p~j`{)$^KSl?%2^_2-t}$ymFpVGxv?>ho&ZPLqjGC*}E%7GaXd= z32a2lWbRCNrqMf^pWkdY^c!nVheEN&!3KBnX-AC3APt#hi)5q_C`yUdi~7n_5m#|?FW6!1jlT!NK4g5cePy2a$ zs9~^uO&7$0=c1#{2;PJ#DkdyAxr98>?I{!T$;bA;Nrg{XzzoLoiOCoa=XzalA)|1%yWA~1WTZ_gvF8^R*o*SNB^9uO9Ou!mwp)a%V$TQgBag)ht*QH9?e5E!!x3oA_%x_JV%ND)Cn8EW-6gt22np3xtlx)+Oc#LL1X% z1OeipClow(q{vkY1>v%<*@JKbfrWSRO}pN{y`^N<%B>^K*S&d9_rYRmTGq?25 z&S}p@D8~0ZjkEe|sus+c6wK*=^!r0gkKfckDbji04|k^?L1I#Gbkc&lME&wvC43)* zghYXy>s@*;+dnKi|%>E83A%5cn6glQ-U<q#-T$1gQ$ZN03u^(e-MI>KmcGPNZJHsD|mX;5Na z64UyI5sD689w+Vtq`C_{IEBFw0ONvuZx?dRL$U02^xw14#&Wa)&}ficK~ zFku)kkKtVM9LynTSF}&_gnJ=<DF5$pYRJ@whiI7-i8+ z`J^OeDjw$$Q05k?TDH&4`3l*{gTIVP#V`Wa8zr8Gkbo9uP%p;MO zhBil4cUdTOi~nB#G5;C=yZ&GLk;b*C8HE_~@}gb9#t`&J2u}z(seK0Nz6wP0x)G1Wzo`VD$9&^pDkzO{jY9Zb6`Mwy?c7NPo^W+%ZTD>}Tjb}; zR;^oGG4g{0vzyoLn>p#;1GlekUe*(8$obX}EuKu*{egmo4}3Fqe_}>mw5n(0mMs-E zi>Ku;d;A>9O;*UGXLYwCKGBVQzdS8CNDij?60|GHAE zD<{LvnI|*Hsa0zICx9@tK`KM^&2!0{&&dCss@Jdm z?*|Rm|N0Bs%)q@UKcNL#(riVD3fa!~G9?$hl~BkLNSnsRfPlLITKs%9N|8!kEsNN$HZ!2G06X9(%Z2h|K&hg|J& zM8PhN`dDL8ME(7`ID^!Tti*ip+SG4PGy8wCuiR>vgV!hL={2nLw#XCGmT;15X_E$`~#PSIh45=Nzy_44|!+ zNzb`ecTPJLE=M~O+CE=UZLAKwQ|Jup7JV!fAMqj-|(O-B(`Syjw0LS1v zk6cPA6^aqKg*r7Zf?hh60fk#nNH$9GA;V)8LWmJT4Lm3HHd=b2+LJkwfb9MKl6_Orjqj zrt0Ni>3##6fFG19k|a#BF2k3RmjNuO{>uTRs}#kk0=aq8^AY#+IMPsMNTYE9q19Zm zls4W+v6q)~K@n1x5TAaMqU4QLQwyAxi*B5eJ2T*&9Iu*E;Hd1qX=ct$zq?7duy9IG z$%WD-x6d!K81FYa^VFUS<%f7#Xfho#yND;RXI;vm{1@m9)1^_>lUHH#O0yE;u}C~l zBw$J|y<0C{`(632TGvX~gRZAt`lxs-CLXQy!B+a9O}sA1YOQm^tXTPcexnL}W+S0m z8tdZE50$VI)$MC{mPAWOD@_aJGGPUVq$bO-&EP__cjbqmqGic)U(3OcDeFq}r%aj{ zsoc{&`~FtNp!h1Qssb%LtFtFHO%JB8g;#&DsB`wrJo+5}v^eY!Sp6~IfxPmP5L`M@pt13_yb>f4`RX7h$^^bw6lwn%l#0$_bT(jIl8&FPX9 zW@Tn%^jEmWV&ya;96)u&WA_;~H=5JM~JOhSDWMGftV_GwB>AW^(DCM;ZrI zLO!OPL@i{ULJR`fgW~I|-X_~Y|8;K*{vc*i7~xR>#sxM3o)P#wGz=^AAKMo{0K ze%-?ViW)(J7(e*&n|CP1P9`3|C9c8ZJBq)>D#3U?HK>Tp1ZWE<-o>Zo-isMH`L>;7 zcO6BFlV=|K`;j3VZ852tQ@B(1Qhh0D4^L#R|Q1zA(U7E{w7#ATh?j+YiekT@3sR};|JAY24 zpQ~IZ=E{rVaUc6R`}xI(Wp`>@Y8&6l&ZgAlZS%OqHl?VT4Z|9bDhnW6=%hk5s2JFD zl&BUg_<0AAp&ug@9a-3jiqJUSfMgAryAWc^^!GCoI8r@)vL++xjY1=Z$#=~UI!p1<>lwjvGQd2`v6|x1cmcfL)!bu*OUej^OFbjW^J7t zom>&Aj29F~GW?-<8(khSLY?X%snimZYOxy8A$ncEkMaeOcN5I;F~@8xbnL(A*T zlmJ{PoFlv=bj7_t8oReB9iW}uwRB{QxHg`3Fn9Lf+*|+bfBpIk?|tc0Uab6-zmEOs zABD&$!QL&sz%B~uOqgOdoZ_!|F|914c@lk!GnF1P-TQsa7;a9Z#{JXtYml;3GdciHm6AWZV%%s2 zNQtpt(AWzPY90CX6B@qOhFDXB6hn1qM1Nn>GhIr#+(;!gqD=xVub4vRS2X!pK zwSHcpg~OUdWu`g-G+sJA%*wAWWGlbozdU#_bwn^N&ekR$l9!>M)K(UHdI_XMsB#0x zFfg_e1YyX-2Y-6-Vh-#y=$lwmY7-bjivcMjnGu_U>(T6}>7o(4gTndAWsXj2 zbS9P-27Z_F)70H)N^CCWz6%?5aoATxo9L$wD^sP~QC!VtzGP7@QKY^Jt-h>JTE;f4 z=&zP7fR1D_m)gEfKdzNGBy%wrT!%ZM-=BR|8KSO_(X^P4eAQ&Q#Bux^QraS2&=x5v z774R~b~W0r`QOET30z!7ws%$az1`5=>?9BZq1ho2Xu5$0LPE2XO$cN)8y#qxPJl+b z$?6z2Y8-XcjBC_!T;k}cGvk=WXxzqSbVj4TF*7=!OOi2;j?2^c#;5Z_fbU;*7uqB~ z=l$OMeqTd(om;o=sZ&*_PSveC=M-ifqGzLjpm)>Fp#D^W;e8dKp&N1`lH|H$^ok&; z0plcZipCOEq3{x~1@sof0Ivo`Rw$ef9^&n;)j1adAWI8O)(H3gkOky3A~lIkUtf%Aka=Qz$NjuKoZ8jmA9z#>i? zRjviSVj>kLd(a)&A9mBw=^J)~EOaU9qSFbSCsaCBo`5KeikJXq&Jz}18xf-%`+hd2 zA{U)Y#jcaA5RT5l-aUN-`Uee-jT(PGx^B1ZIQj6B0dc|uCnZVkC=o#iwqjX9^EO=M+46HmVJRwkLpz%0QoW6m*yL;TU zdu)`3z{b+urz;e>sEm(7F7g~u)6mwMu$afs%@#sdoW&So$mOy^8%Q{B@NTE|Z{RnH zGQ=SRvnU21n=H!Uw4a@hP?!X223#1S0E*}ktfvS$!6@j}Tu`b*l`88DiVP7llY7Xi zamPl>avZ8FCK7JOF#U?*JYyZ@vhOiz%*;+j5_BSTPDLt%aUl3rZs(p@6oD@?r=z=f zza_+p;GeNQk+I;Q)0vU68V{*sr3G^~E9W6IW}D;ZW+O$mgbNq$oC2ejU?q*0zW$O+ z7}T926V$pZbjPDz_-p_xMZ_YHM|^8UR=LV41_ou=;zn|DS!QsR7#+lL$cJa?A~8f@ zG9zS;UT>A|Xrp-|lJi&;1|0)*6Ud{{W1`j~MlF-$k5X$w_@Lf|gy7>47%f4FAT$+k zCeCXBAcy#53>8+PNg}muV$9@xQS3HjiV%ek32Tx7p^Wl@r zWvUFJ7?Q`{mQH}J(JqGf`A8Tx|KQjYfIM%3>qy^Jy*oCB)PAEf` zIwcXpTGX4Mo%hQa*pI|UnM`qLooIu?Z*WsV_Qi}w@=^AHQ5v(T?1q9UQcy37BC~Ot zAj(6rISY2<6{c_;lSk7rD!k9jCBLQ250trv$`ica_*4d?nObjS`qYSzmCm#3O-zqO zy@}b3I{PqGftke&M9e+K7$NG7FVapJW%%lYRfWlLDl&ZDH7UrndQ(IhpMVKbMnmcp zOiY(JzlF(lQExE@L!2Vj;CgLBw(@$58dJu72Jxm19m-h}Vzg7csnR(G)A8a6=`c2` za^3hf(h10jmv$R|52l0IJ7W$A59jWL7rVp^FPqTt6vk7Z-I|^BdDU1Pq(BW z<3$1EtHk@gpn>ym9PAVpx%rCJG!{DnL=G=`4_SoATE#Tm!r zb&`~-!Kls&;&UcVgo;XmV|$RKa<*tEwT(Ds8i%XxCEDuL6AGsbLBZu8R@+!mIwIs# z^nQMY=`S{I`MXO-T!wUH6zX9}QK6(AU&OC4Zx(6Cxh>5ZX^|p#GOfTHa{ze9IpVVu zM?i6e-(AFf- z1~-mW&udw}9jEBbs5tK`xmU#bOzEz+t7TvO%(>@ZBoB+YU_xQ-svUCBoLN2pT6sN> zo7Qc~n!VsUxjWzsc6SGT0ckT!adccEZ{T;M4bz>SyX1{ii|f~3DsSRZ=A@E_bvxzF zzV3~OsC2Ner>k$>s^uJ9vUPKnw*q?L0dX#O^mg|)ei>=g!axI+X1@Mwp@JSmcAwJjrQzhE7`JzSd+8u zjqOYN%$J3R`jO(m=3wBut9Jp~cg>ZTp>;+7Q9E!FbQQG^bd;d+1;+g!1+*l|`FWB< z`NfhY#~zmilu3?r2Br2m==bKRU-`cYo4zA`QdoR=n(%m7Wa#;Z!#*FqP5F-P>FuSr zDc{kF?~gyCBt89VZ!h!teB-^!SB>VFj+`@=KX9&mZ-C;wOa8N6}aLT-m|{(H&&vtzJv{s@*VUy#3(Z^$T_fL5@HTCk{`K$GZnnr?{O#~kZ_c9@#+=KCjV z*#DNn*?s>ZYH>%(Z_POG+FyPxzt20D^;mk_b-(&XK4z@wz2`8EM(RHx^_wP_@7_Zc zdlT}TnH?8BdP@F_Nc}-p=Z<}&@(1SX4R;=<@kk9{g8N^$%-;CZLo@-YU(a23>A`XN zuOjtFdCPY`HX(l$J$LZ7!|-M;liwrtD>1c!+YiyC1M({~&%fe{!x;Ky@(FHyTDWT0 zVLW(1{xrUR_~yej1vh?+8!sg^1aE=r)&cpY;Sqp7lRI7h z;;mzEe(?H7uYCH_7hk;e=_?<-{=u8a-ZDR*GJo5H|1SR@PL5^zg6$9eU4Cb3^QIpk zrZa%zIcnIK)Ux@8Lo^+vKmYtoue|=I{1%r0F-M<$;pJD~kdOXq-(!cLkslIq4?O(H z{zFg758QCe?LWO&Mr9)1-s^9?^^Uvby*qa9y81daG7&2qO$ z*VWV4KNOO?+B%o7S|=|Majgs6m#kPVx4LH6HniZpA&$>pR$1e2lUfum5@jyU-4@sl5aCch0t2akR5(WhU?@6^RkvJT#Yp@D!MnOK@g zlW8ih#p_d;41zOIO~U=xlIWb`>70@0l5t8N=S=?Jj^z8MQ?g+BaMqX0$N$&yTo#At z!LNC~dFS|Y*&3c!gOkpYmOuYp>AC#Xo(&JbD;|;+qBQ(}6tWtEHvIpz_uEO3{GU%H zq_u{R3uprdh42gk_iFd`T+gQK>@=ejXFYZfN>$k}Wcas6fUp5T8v(+j1ROp}bcr!R z`B6p*F;u8yg%Tf*Kpi)fjMdRYxsNfV5@X07F@`jedGPwPm@LKVE^CN~c!{43kPBd6 zyp{ZbTtY4t^3$Ec8}@xHzbWG0Xdk-fkA}2}m)-nl`JfP%a$_A%2&2j5 zACP+y5Ly{{KG_{{5KDHO4<3Euji+CI?qvf+VEn-r->&<=l3(Hk z#L2K1nP^=cR%#2eHb|p$oJPi_{&F?A@ygtVcheRyYIu^<^kD=5o7ORYQ+fIQEOb6s$v#+`yLlYm}uD}iRmhC%sg2Dr`dGof5E>#A~C_YN~DlxQ7$UfJG z!QpW}{YG5$iq%~`r{!pjFWsXu)SnU8QnsM2^BcLvIsd$lujOXM?T>#O(*F=mJs`)w z_0F*m4&#(0nM}cruk0Ux_T?dJbEnE*vHOj?%)aQB_^-Z-Z;AGqcNy(|)#%%b1ccj4U zpR*hbb!UBHHwP=-Grw6=unuCHS*vza=d92`Koz-E5v;RzS3)>4oG3`?6uHUJ#zmqTNFXr_n%XLZqx z_2NHFGXG}%__40>_muy&0E`>7yU!}pdUBk2R=e}O26T0eAJawxeHfUG(vumFsJLjR z3Lw|6XE|yh1v>r=5byvGAvqFaZD2DQk(NnIh=D}y!=^t(~lLaCGo z(ho*omrAjq-A8vAdJNm~T%tP?ixgd4Q3VSgN3a-kL|Gj%^o&dNG~Iz+O%|5kL<*lo zW?0t13J*P~JZgGU(nd1rsj&yg9*{hvmq`~YX&KV)QSpYS z#ZW8l;b=#zm=~D1zRpVdE(pv>oW$ag3`Xc|;!rjwKgeCq+J#z^3>UyARm+3Pkp`0~ z!uUuGc4#9WLBaUu3)jU-621@=<>)wZjWq^t&yNH7A|-Zh$GD6Bu7kcZ9zA|1T~E7k zg4(}~m9p}&SH_-Wb3x0!PX9gM-KtAh0oZY=LeOH04xlE)5Sa>Ov&jd$D=dtR6in5N>D&DpX7KHHqB`N+^X1JqYkEX|Ph=M^P2^3wU zp4_PfJZ(?pn3#?k!DCTkc-9>aHzK($3KzusRNf z-2s<}=T!)smuX)@bt{?hQyX5-q{lL8rh!D}(S$T){N8K$rzP*bx8{P^hGXA*P0L?b z)#0j>a7B9#dKOoAU%p_$<-FFFa8Vc;VFZRJ;YrUVnAZ;-q?f}R)d-0vnZyp)JEHGT zr_j76mUg%pegrOt)42dJ!Ze$qt7-y(`|yDAI!yP_KHON^1qGiZ9152QEq)W+$0otW zq^rWPHYX=%nbW% zMQJHtwgsC`CrtXabSre8X2BMedklESJ%4&pVbR8-?M0HmaBCs+=WWen({l@RH|B27 zl@c?vGkY>anG!$phV`axN@Izs*{MCLp;W1CqPTj(y^0hsc3X?x#cq;)80EE+6m%5Q z-V8<112E5RZeE6&)}sp=Tzp+Sx^zxy?Si%iY(eRQQd2sg#U+pe%6@H1ASLg@AXIa@ zfQ3SC^*%=d>Hhv_uIu=$AT4d?%(S!u zx=(|~`{Ik{bW~P$%qfbmPN-PqZEWaW1Ot#o3X0Wt{c)QDDTq0iFswxiIt^LD_`fIH z^QKM9vnQ9%sC8y!IBRF{wv)ozE{Bw2oqE7MBX^?6Hjz?eEpnug^rDg?7Mo&AVMQrL zDOOUDhWb93RgyKQr3Fp3S842kn^z@A1q4A$yZRWZ-|j(VkqT7kpru{g1OQEx*d z4p&a7o~+XEsK>vh6=*0#IKE4(u2QW6dA%``GI53Us5Aqs{0-!~$5xj3OSYC6vSGZx z4@Lzz4ev`BHOIBCwEC@Etp?cY)BT0{`BF82$6~pEl7v(fV0UynSH5+_h1C$0YRe)0 z_ot_(CggJ#hU+QH+!kpF8#$)f^g@a~1?~eGn>BpF8@n~i#*Nrd;(Ka>gQ2~gkOVP| zNi>v|T}jZs=c^j9SxPeH;TQ8;SbmxET=*596I^-X969YQF(`!}-<#i6(OR)C|32q^ zwwdzZ{wB|~0nA^A$gcdo&b^5I_+v!wy<^s*zS*<;7R}N^!`0vOgj2^ozOANj5e{hU zt5Ff~dZjg_*>JsdBdpmRWZA()8{B3mVTq3Jr>Uu?0sxOri%aL%Gfb%|7(Lm(Bto(g z9++y!4rehRIIR$XN8#H*Y-ousx1$bjY>MqIZnJ=Sx8}eDzH)>SJt~OL`_HThy5bX8 zS93wh;(Sdt8XfNe)9@$HL^v|yivx~(zM>Cq)PPcSZmp#5I5`VNbSK!S(P?sIY_vJXl5Q!n7%Y}qlQV@iM_#R}iap9FFX9Y2A>jVB=5usK z(bI%pKP{mdNpUzc2IlZW^}m>%Dctt*@|La1rSvb1E=Y1Ej!(?UN85d_r88Nwu85!xhCAqb^hTPl~K1GCAVHltAlX-CN5M~9?pA|=bZ5YATQ(D)xE1@`@I>bLVC^0>RFv_i;MEQ zyuM=IR34OVBg!_H+Y6QFR#%tUYwbJi279h~nk?IdQ)NTUwAfsCZhP*g0Ydj6<5zt+3 z&dTj7+J33qed+e05^KpM z=)URpJLVbY-EsR(b#;><-uY5Uvf<}aFT9Kwxswd;oQu;Uxif7tJ$AZ%x?y@mY80G@ zl{k+G-(^SE9C4()n{otg&dn~7w_>WJxZ>lsC~@2(S>bvdt(#Sl;cCqk-zdN?~Vymla#Z36kejPTt20T5>O^yin0wCC1*{0 z>Ig=)ta*V&i-(%hdva5zxwr2R4)6a#{k(aN`=Xw^6U!+1zr7tEAU<8%>YM)h%l3|W zs}}C;a#gI`NpGrI*6tZRxU;o!$78{vN4D2o^u&cv-tf%z^T5!QzB9g(eKBz--kfai zNR|xee};<*v|+6WRKpWAJ1yZ|zmUSW$`k{Ar|rdu##d(0?40rAoF}o*AU$IEK;92G zud*vq-Iz&PXOSc;Nkjn`PN#U6L<=O;1u;cEv%znG7UkX+oj11=a0EYx$=@_e?} zFvB^oxOg7!>}C1OuaX|gZR)Bjtj>&%&a5u1>T1g6^h&$f7vO)H6gmX*mrgjxb(nntt611_JP!59j}M~s;CQPygF{Ch|e6Fm#eVOw37|i>K1aBRu zlIJg3(m2yuGiP?hrN+Fv)pNFNEpA!X(HKiFWQ#nmrLDE@mZB>6+&OYddv(T;&$V(v zU2R>iLhmPRHv1g(I>}X!lbN8`%~?L5w|r`BD%>w@x6{lMSy|>;CsLE~XC^1&EGNuy zapi6Nh^1HvTYGZS)Tz<=>##nPnSL@~C_+S^l-r?nX94ddLV^nCa=c)?(O-z;Q(*4T zfWO&`7k$OQA9rrRUjl@KKQVi9ar0zx<|=xd{`RSqpHH%kIC7P;&+{7BR=QWu%`{#a zS!b`PpH@(jR*=)^t|*vQZ`@@nYFuisUS6Hn(A2TOkW$fEm6lVpFfXcRPEB1-Ns4)( zs=gd%;MR)vij;KM{F(JjQ&@Ztms2mY1%`6)lNqy-HSTygMWV;!ja`fz2^tuihE1j< zw@q@xrIuo*Nu-kza!g!$TuIzd2d5{vd`|myPJMJZuQbXN2X`mpzBSg zC|f!|l!wCIKrjaVd9gBJ;>9v_}cP($COJl;J53!gw%$-q3wo-Z9AWdsVIrgtSX#WJ7Wet zc)?q?=@+3WkR_+lj|?bFG&J)Q<>N-9l^nOg+p0Adma)Y8VHB@cY*2Csle|7TS^SG` zi&<$FmI2`iCc|K8=&nt_JhP$OJ-&^Zq~mLDyZLVK+I#oxS#kl-$3Wwl+h7F$$6@sm zD=d$bxp1V36J+5~^ka81fLY0KJ6FI5f+nY{?l_4!?tn_$-;CTud5U%Vtjr8d87J8i>KwM#msgj6l6@9no%%*IJG1*AtAFQHPxP-n3z?X;OLxf%b4BaIB#XV zA!T`OL2G$>dPQr&+!e`&1e_QFcX03k#H$aVF7liTW6yv#s4%pdX}1a+usnFT3QI`y zt_sT}8jfofJVwAS6*iG=h8h))Ac=+(DjZ3or9>6Bz$MHY6}FOM>1`E`A_4g$6^sMNRn#!T7@m7QquCWlGV~SH9m^GFPl_2np|jHj+LqaPQcwthN1J|BYl`7 z*+@R=#vI>995@}<4yLn_4(LSmAZ*3EY={&Annwnasu)lm>BpZf3`0;{d9m-}MeHW= zEKj)*Pt{{BxC75D0o+E;L#!X^Y`|*+=3#_ANI!sZ82Yd_P}hs!@6Mkz1ctT_`+a>O zTYh(e&0)8@Y#rVnTdOBjWSckGU2LoC@3)BrLEEr5=pEkV?J2e`oLAq}QMY7X+j%yB z(B`p)hCMyr0nhM8TcG#b&l8aCC+kH~c;WpZX%HkW5@7(h{p-EMo{&EE*LP09;`|d543%45h_(S23>7h5Q_4R*HPC#Q)Qlwy3v@I>}EVi|KLf*lU%`@0zTcR^xTW_zw+bbfw zy~8075(h$kX#EW%!~S58znimJ5P6@?OS~fwqbu{@Nj@by|1<=KhXM+p64V^8G2Syd zBlG0^Iw)A0bcVc}ytYN2P{Ylzyr(#CP@8W>IY+5MV>r;dr>9^VLyo?-XHB zC`RMju@B(D+ff--#48}9E3u1O4a<=l_|=^Y`w=%pKs`q9MvSM;m_fE;Je?1Vl7;Bc z=V4@RhdyHm`uP%QH7-CRdPaVQ=yi zXg2O8SK)NIo#Y1SEgH#>pyPZcwBX-|GV-0|LFhDoMZPBYK>zV~(2V~j2F7k2%GLuN zc`tdH{2rR}uR<5{HE2NgLTCOBXhr@C%=#s?C;vo_LPN3-+Vh`7pK=4(dZXy8gXAtS z`~~RKybq6{18jnT`GjmC+tAauLj&_da){gwo0yBp#pDw58P2760`DYds7qU+o)}HW z$T;*DW1;pD4~={RUHnAoT2h=&l>{BcX*30z`Ki#noB`J_Hkv{HLH?6w(kz-yb7(Hj zqxrOe&ZIctoz8+&#}aC%rLcy2i#l*fLm4fn71Tv5X%(%8i?13w2Wzjj)QwZ>KA?42 z>S>^jw23y;7TQYZ(fM!!wvaBu**;R#V|oLzupDPwCzC z9(pg`OYfsUqxaJX=+Ehc^db5%{RRCcTZJ(}VOem@hw0pP+~7 zlk_S2G!Dvpmi~r5N1vxJ&==`%=}Yuw`a7IK`wD#(C$7B)=R9xFf1!V%Z_=amkMvLU zE&4Y7GyJW;12--2(f8>wdYqo1f1w}Hlk`LSSNakCnEs7^LO-RS(a-6>!nMzr^xx>e z)4$WN=zq|!={NKr^grn-ItpE>F**)wdxFl&J+MjeY6FjKECR=jnVALtj-psJi-B{O zI2O+mSR$K(G|X16C^6v37DJ zxrr@i9jucrVN2OEwj63#E7|!tL1;Bw!`8x=P#5#C^{kuqFfW5859?!owgKl0^|Jvs z$O2eXxPT3_APcb(wh0G^gQm+s%H=ZeTaEo7m0l7IrJ!!){}@vpd*N*q!XB>@IdUyNBJ&_Okof z&)EI!0rqqDAbW^C%zj}S8T8xjb#^su6rt1Aq+HiK)X=Ghf~lck7_BvD*XG z#vUbTL_VI7sY%Tc?+83&W0R7hR|MuJ{b@7COBJW7NhQOpgvKTXqgMo$mPtY^zDd~J zq9ZW-bkN#@In#hgA=c^(yRfuQK5OZlgr(N?o?)pEKgM|>e}9kH=of)$o>~&WT9SE6 zN&HIH%~NUiD&47)I%r#wHXRPdl@C=d({2Zs8* zhNeNE!8_J`bF%6z!AaP9snVn)N|P>COEsdF zYN=AH5fR8shcQo+@%}LAi&`39rl^tdE7MZ7=|_~%vV4+4%jPh6Wf;755;mT%ly#d3 z%;)R8Yu=`Va(`gZ7c|##T9jlS9W>Q7i71oDD?&?Ku-_BxQ{ccPZ0!uIAZsw}LP!Tk zX`LF@D`7*G9KvgPFsjFo_pcyc!}yiCu79Y{Bfv<{U?Ak}_xe57rlFu8B#P*WrjUAn z9(uHhvbGKQdGA*)mWCxZw+(oGN}@PFl7A~NS>z?vdqW<%#e;5aQuC3{N0b5i$V>VF zNjxKYfoEvQgDx|$zQ@BBjj;1Z*a|-wSLDpv{f3UdfZXZ#4R{PoJR>HR3PXFJ-_U?> zd(dy?cr*%P)CAhK*`pWS>J2MF1PD;7gVQgd#1ZAb_Na)I#q-j7B$s`>FmjK#Kjbl~ zxTI~kVc<9Mj)5X^Wb#H)q<*DHgClH<-xN^FV;JrW81W`H;3<_wU$g`?AmDW*jiVL%NSb%~j%Ix+?Pp{u!YPUO_8dP4YLk=C9yj!Nn zmFrN24!LxwQirNEs6x-JqEyYlQNO3exzkYz`fpL#NoGVX-@OIvqMb zhfb$M$L!ECJ9NxW9kWx%?9?$k^}2BCc{uevoO&KkJrAdzhf~j^OwXfC&!bGwqfF1E zOwVJoUdr@5%Je+S^gPP+JSudmD)c8R^d~CxCo1$OTsl=Qofel)i%X{kE(%q?ap{;{ zI%b!S*`;Hy)G=4;<*n57sMPbQ)bps+^QhGGsMPbQ)bps+^MIdJm8vQ|k19QnDm{-X zJ&!6qk19QnDwo2WW{f30tnmg;#;mSK{*s!HQ>->?U>zRgOoTnrV)%_4|gu8E1r%^GA}sU+Mg0-l@0 zUMl9E2$j-6AlNIT#3Bhs7A)7T?~l4*1j7TLUJhdc#W>&}vP{6Y1U34Y7t) z3O1*hxnh$_%v2qASF_abANClBuORs;E23vz5Zcx1u=@}jlZ0`5=hD-t)88VAdtbi*w zdjgvW6=*#c7Zqq^sAtfsKF~9`KIjz%a@Z@Bu(Zk?7!LLEnSrO@>K{afpa%G2q{Vx| zh<}r(-#ggtmHPrCL2nf5sz2cKcYFFVu$p;dRCs@ANC(%4B08JFO89UH7Xa_eK-il!vl%)!(ax!y)xLgo##RV(kr#3~RTnyk~0{QKD<>SE7$= zSIT;%0V|ITYmF4=%V6ISkNm|ctR3SaqBQRxX0=ra8n$!pyEn}cD9C#=f<&UAD$a|b6FROk&&;M`H|TlO*2kMsM~-qh|ZEdG*W9&6k_V@J{uh@#cQ_{onij-fOL?>ZV^3b(U%f5JG(MhbPwD+|tQaL1!K$)c7&h22aS&$)%r=a6&bU@jhxoQQ?&S_sX%| zJD3p7vlFJ2Wu?8D^&7VD!}hMiDG5pc#XXpV!Qpx1{0lfp;$xV&-wk z3yrg9)C<~ucn|5=p3;aFUiqpg@V*1@!yE7FSUme)uR0t%fDpHXb7!~BKfm~mV+1a} zPl#^byp~xrzFfKFb3*h-2@%Tgnz6W5saF0KpMMaaH_V@L*X+=hzkEvI?-C)Z=GK<> z4zGskmkIoi{k6}ww#{zsdVTsYgakg1@A!Z)+=2HGO+U6CtW~G~gXmRQLCE>dVOwtH z&bq<-RCAPeyjKy*YVpG7lo`$fGF~;{taJXXnj??oYLkEIVOB#ALkTG*y+pt#H6(#l z5yI^e1_+1u9N!Op*g}Mzf(?fZa^>7#Ndxs!3DjM|Q+DzD2t8Ir7XN;0_{4Ej#uJGc zf2YEG&SJ1BGvGuWA$u=>ghY@gti*l>Lg|krn>~S$5Sl8d)AS=Um2W2^CO>5e$rH+n z9oCaZ?6U%s2Nyz&{83Vm{rmC08uRDav)I1~({4<2Fcn}*z*K^%5mOVUN=$Xb{rho@ z*=!s+ZzrWHJ$V@4#bY`syha)^9b{$xOL9<=O6IV8eD(z%A(!jhvHq~!m$lDeb#m@{ zt_Gj8;4^!$UQ})-BIXuM@mN0&$GMlmj7h`2MBMoHp{qETW`vK;eGR^Y<>l-i`-BXa zx4>&;BfK{B4z~ATxd-3B0ozz&eb=xY$Jv1GsrZ~8@7H2kDZDl`h`B!|0aGG9O2Vj? zJi~IF|1^B&C+xc(QyQiP2#=GsW4RsM;)Sn?p8i4t@qP-!8N27_4qfMLq>Hb|`5Y$E zn1V3b`Q4{gxlO>nakpT-1q;s0 zB_|IP8$CR93Ga7f!ZPyL;i2y^_rr7!Q;0B?@Hn666uCqHP@Ek4iSrryR*^LHoZ@3L zP9Y3Ehil|u@5OYGZ$uueAYk$DV0EXONm zl1|Jmieuy+;dfbXDOo*k4dvWDBp2t}jC0<|*P%?pI({eeAIc`a!`05MmwAWDKvzE( z{dl1~VRS^e`(3;xtR;o~X0nF+4DWB?v(YFYaZJHV%6J>m2`9;VMJAbob37-%i}573 zvpNPd;^v^@2V&vNusncr9%+Nk6_XO*V^DPxAC(UKe1LrpVIMYE9T|mv*_?SnlJkS| z^K*%sjj5bPg82`~c)1Vulb@4_&n>*kI2#}bny?#IA$5yr$m^P z-T3Y@Vo@Rt^1B8|6N3lgs6*ULEKj(-O znSYR$t69RaP_c0(+;^_x`&QhMmSdm%TM7FzzS=;~kzy4AKYVT!bdxgFq{^^Q1*S@b z=QQ%n0F*oHa2~Uxn%pVt8TG`5<*CXK;6Y&k{Al9P9^5}ivpp2os~+=A zx%>fni0!ld2Q-ZT00iO4&@TdkkA)+2vt0hS9NSr++X+5#^<_Crceb%H{@>K!`Rsox z;{qSKGtm$5evVxC0qSNSkfoRu^h0tNOKdHbKfoSL_uR@6Un=bLIHnVrqFKVe?W$Nf zu9^#fRhB`ng21Pkijn@uAaA7e1Ec|IknPoHv0lRxmP1&=bza0|3+8{v#B@h5Ov#vH zF~#Hhe~mP>4AUxFMVc|$DQf6)T957fhQ8wxh=JONzGdQPI0X~H65+qa8deHM%6S{} z7fCl!kPNaMR=~Y5M1$yS^bLB6Th8@xz1+jxJA5tQ&fgQdHuUcXZ-dSdY6vq#7_0`H zANJZ?B@6pX$`KVz6N!e}v0H{NeN7)HYsVd^lSFkM(+m_95zEH=y@ zHY04diJCMfqnVhg*~6?cYs~@XU~{-R&OFkbZk}iEHm@;nF!!48Ge2xTYCd6p*8IHr ztobeTdGjacZz9qoq=>ADx`O<(5;jas;m*F@32l$I_ z!{2A#XFhxz{x=c+j}iW~+wjkhXubgmKMV~GeM!jBlS31R5}a$DYn%&j?i;)?cz*Dm z!E=MV2YUwRf7$(|_Uf-!|L5v2SO0PKr>oyx{r2jYSHHM=_3Fi|`>(FJdiT{OSB+P- zSADK3uku$0ul)7OzAIaT=YjiI?&& zNtec6O1m`b(#T8kmm)6dF9lo@F8*}!`o$kFes}Ski=SS6_u{#WuU`Di#TPH0zIf{5 zlNa}2EW9}BV*W+T#kh;H7o&W(`P{4ACQ}nyeE)x4)bRh;$iw4uWG0zK>d9=NeAg93&|p~m@FY(WGPuj?k3C03gn-aWEELW){wPi z9a&E{kd35=Y(mR)GucA6l0I?|xtH8WwvqeEcJcsuknA8k$u6>+JVf@8y<{JGnCvHy zkVna59k+;b?@(wvqE|9;HcgcI?eewbMkbFcwCZCW`$!Fvu zxkNrEm&p}!m3%?IBwvxQ$v5P`$hYJ>@;&*1{79~mzme+O3)G+iZ}0(M(1H&9z#jr&1O!45 z1Vadff*uTDgfK8cIG7;dl}x}N0F*CU`rRN}@h5;x*bJcuXpB5I@t zZ{kCIiI(VyAMr4r%kWivW31lQ;#DpFpGl?LPB#K0n7!ph3NIbEif5J)< zNfNP?CNI98GDo7>zJgUhwQbVSbT2e=5;CvVru15;I2Wf3LQr14Ctw)i%9z)7P ztx%?{Cy=%nWgSPVI)QX`3MuPZq$;E-szZ8b+|WqWAy8p;|fud8!9FYZLO5i;sF`9wNv~jC*#F|Au_@ z0JT5?6au<%UJ|-=7^r)2N zL~DX2J}xRU!W?c2GwK7h-Ws)+r@Nb|QYr+Vqd?+vOu2OiM?{^2k1*No@$B9-11o0S zQBmhGU{&tF_Hh{4b2G5%UwTT|v*AxYC09@Bc2Ce4(n)%JoFT_#aGc9F8Tz5BxB|)RZAt#_Q+u(pYLyjYNVPj8DU3PpN9CvpcZyG<_Ek2GM zcXP+0JC+<#rq<&Sl>u^rM&+a(r$pt+UhCk@IWy`VMa304*+E8QWqe$o!^?ytVG|iI z59v^hcPQl{4NVRNooq53j~nRe?bnc*b+I1xrurGvDjeJle71+n>FHVT@Q!svo3b6z zU0($t9%nn^OxZb(ST<^Y$?f;#|M5z22xg7R(DM(1(=lE95$AD7&5Yq1g<10t!WwgN zrFwdDO@`c_x}F*RL)|k?292rbxQ9niYYt+V6je9?>z~~ed#TBl5 z1DP3gf=CIml@40RS_W>l=*n12_pO%OkJp)SHS?!b^f-8PUcD&?alC1UqkAUOQ!`r% zlg8ooOOVmjS;pKjG99_Gw{ggj>zE+l-@-N0%)d70-D)=Us7(1& zde};sh6f}Fd7gtX5|&1LdwwJar>;89t$q~icvqby z*Hz+$s}#_P1c`z@$57AKXIW)qPhBOWapH#bB!SC1yg^pAjeLdbDZdJ&WelwSVb3%s-i1_t5SAdo?DEBqY{ zzr#O@qVzxIM(O`MH%5OWH&$PJeZ_UEzFv5}_WIWA$F2+RUw;*<|MH7mz4{A~zR2~{ zU%is6KY!)&mFrhH>545S_eySn{?gdXWuK4b$}R&}c99#>t3S|xK;<9l&48fX^Dn~- z1L^vgiX!ySzZj(-It@krt^M8oT>sF3G}P~tl&gOx^O?eDTAo?)%-&~?J)>+rvG0WA z1gAa$eNThqX;426D)m#DPhEeC>vr@xsKYVfxZvOtj%6OB`<`$-K?k0=@B~dbnt7D& zJpu!VFC3{Twk;cTDHTP?XW#JM1TJ_fBlMWTel5uO_4Wl9@YrocJZVOuSxX}O8|>MFaw3Ul8ipE3b)!Y6TMEgY!1OpabJ9xA3%aq{R~ zsn{HqdtNFng1lUV-i~8Vz;rAZF6Um)rQNyUm!vE626dSxNnM5tNf|2Idc8VRU8`Q9 z=GE#1b)mXNy;Xf#J)~AE$*FvD%57lrCejP8J5*wS}uN=Y+PFYm#fDI1w6!t3> zS2+|L9HgwOy5cy%mddr8Hc~ccC>eN#km9j0Ts9YZuY0^qZl z+Oga42srR`8~}ehTtm0FWB+!1zCB<%(zWom@HV*{BXK&iME=FPKQ2BE^>d7=4*kgL z{`hrH{iDzS7bWHi!s{g3V8>9$zVq6ERv|$R`_AhO+Gprz!fO|5v;X-zjrzIk+J+kL z`v2Im8TIMSsMlj;`1VzYcIy(fE{~!X{x~6MQ`zvXbBEhML#yzAz2GXmfjad;?0GL* zmva$9LYLB?Fw$2-=F^WjX5DVYIqrofv}Os^kw>8#Esj;g!%auax#dqo_n_7L7+T5Q zf7A;rhyG4HZ$6HYZ^Uu7qn*9rk5-Je{FB{rdW8K+v`5&rQvSssrJZZ0rzjn~4{z>4 zEBzia13p1GH*@1iw%`qi!nrKYy31vy(RB`-k_t~Fj8KhvVIv{}Bfj!Y`IeUZE@B_L(PsO6(> zjBZWcl19@`jwv1UY5M0G9vR)43#HN0gVM|6_K)k&(q>K1`gr{O@jJ5vv#YYZvcJkP z)C6fl#e}vAJrkUEAA5{F%U)&gu=m>c+xzVo@}#_qywRZvyXR?u7U zXu-1u7Ye>D++BFG@a@8{3U3x^i!4R+i`|ODiqncqisu!tDN&V#m86xFlr)#DD%oCg zxa8%MPfLE9;x;90O3IXiDGgJWPuVtQcWGK_NojNGs?wdMCrV!}{j~INWs0)UvXrud zvWBwdW!uXRm%Ui_VcFlx73IsP@>2t+TBq8lPM_K_wP)(?sg9|ySM01fQ1NobrY8=$ti!Wjp7r6Zztt=1 z8|puw{poCHgI|NCA-7?ALvzEzhE)w)8qPNSHpe<=@tnRnzcyw!ZEyOvIiPuZ^Uk?P z=e|GBXI{a))|TllTUz^D2U=fm{dqy#g02Ot7WA~mwT)~`Z#&!ecH8^ye(j;{743EH zpSFL|{(VPp$F@#O=Z%G`MfHo8E!wu|=%QB_eX)38$-=IHt`keAEz>Src6aFAFD{Q+ zK~}VNA6(hHa@#7ORS&K%TK(agj5P<=#;yHv-SqWd>!a3Zt*=_&w!UZmz3YG6(7Iv6 z#?c$4jrNUo8ykBtGzO=rV?YfMhY1hj`AHJ@3^sZ`7Xt-{@s4NLw7gsZr#0jw_|tzLym{~A9C&~-*a%U$KLsS z-`$tK@12JyJ^cK`uRr|7{#E;TJ`(rH$VU!8a`ut8A3g9`@MHTP>woO*V;2u39aw%~ z;J}T?Lmy9hyx{SM$Cp3;;NvGAfAjG#9(NuLJeYoP`oZply$7E^__srbLq&%U99A5z zKfLeoZ%3pftDX#eGX2SdCwrcJ`N>b8{M#{~W6O?RaP%Gj^~9@BJ$N$r>ENeNp6WRD z#WUJxa-UiJ%>HLCJmc(->#yqH-~avTv}fI(J^bva&rN!+`MHbFFMNK>^RK^1Uc7Op z@yzZsKMzbA=o;8I@a9XZmkckZy)^BmE)Ncec9{fkuO)h-23v0moL8J_Db3- z9k1+v<=wL$XVcC$pMCV~yMLj7N&idhUk?1``&UC>Eq`_QtM9(1cx}>at6n?xn)CJC z*ZW@o@eTfs;5SCTG3kx^x0ao&I3I96;(YG;ndf`XKY0GY`IF~gK7Zl7^Fq;uRTobF zRqa@$2nFlu<8>^g?Kal1h9IWx6#{d^fqQwgEJf+bT$fQza7cOm=qiLH0~xV za1Y0E;i$BStEP0j@_>>mx2N)Xd?ku-$v?=h;K7}b5aA8+6Ok?y4~TSx7%ftT2p{E% zfDNCBuuz0(5wL2B2<0M(B0O?PgjFJxiuGcL$ZZs1rr0LZY_U|NVPcX+ zX-8RwUFV${9UBy z#7iPQB0h)DNby$t7%^Y07P$~HUZn7r_?h^F$n6!6iDyJENQ@Q7;3!K)L5<+A5VwkZ zvHrX$NMfBx)uLXc%2t3{^vDXkdCHN@gu!=eYG8UzO-=0rylUIpV%yr<*nLe6DqIT| z#KvMxtgC`mvR(@o4EJ#r<;DdG(`#aDt}U=7TuZcqiL-IZHXY2_7I5~}mtiya^KUEO zKm~)6sobly+qI^oQ8HRN=;naapj}l*TWL2HSVoKR&Orw#Ptgc?${b~9xnvj-Y^PQw zbSpvGqFABWs^Ah7nF^{93~0wF*nDi~YLXyfLEE{S1s7vuYvh6p9tIO_-lFYyF4?T> z=U#>9oH_8EOlKm-y_$q2=&RL}52bZgBcMtL*Fwz)X#E2?Urr&qaRN#3w`KaRJt*FM>1VwhJ}iCitP$6 zH?ur*T_!g&qCTP{f-_SX5d;BYqqqc7H;yw{!D`hchPH%6w~$0bB4t0;#G=I3L_x(R zGQtQk*;y`idi&WE`4&#n1lYOs7L75h{d@{Mlmfzr6jqd#~3ETKbdD_Z% z^HktKER=Pf)T2}wza-E-HH5^B!H+H5s7KK`k<5MEnTa}6o++J~{%5@KU(r`IeP zeE*uaPijJ&zYUi$(ON@>R|Ydu12tv~M2-sO{JkSBT=K{aItuv*Z9V+OwUR0?w z!AJ{@^v>Ww8%q7Xy*R4){tVN*up>wGdy_ zo^PDqxqFOoO( zv*LYWGsZytF|L8>?j z!3rg7<=Bs(zgDN~AG#rx`fL1k{yGwwa?*`OCE_%3hB%JINpaLwE{dy*YmK`ScOy;} zC!GvAG4Z5260S!MMbb#g&C?!Pcq*(v=Trdi$LV|$PpSLIoDzIV=Cz-%WoEKpNQhm~ zhB_tWN-;KNGh@!y=vF;jC{vgh z)`eo94EpDL#rw9Y9a&lJWr^1E&TPz+ejl@o+xgJ%m!-Dy#H6y0tnnRXiHYS4`QQ9v zGoxapI6wc`%;?C>xPaep_{C&I#g1b&QcOffTmYQ7`$$uKeB+VjYoDAG9X;pC!8e4J z9?=a)S1&(0*J5ctx?=UwhUlB-S*r^S#)8!|Yr6}>!V0_TvYD$`#i|*#t5^jZY#gx< z{V1de8!`5jM=C)pmx zmYI4@T(A%)qzlso-pwu0Eyj&=qk%Mra(cG3qeV}<+lassF(Wu@bVLrHl9lIg%tNnT z??jk5*@yGz(&CEzfr|tGw79r5f6h~#0>@Gyg>m^<(ZilnWY6?0^rW6!IoLcIX6Cfz z(3G6q9Li0!Mo*55@W~NXnZX#ob!zQ7(36gdj6%DEMc< zD1R?dhJr0AWt7efT#K*JY5n{&I1U-w3vST`f2gi6Ep$@NT~g^PSs+tT{1t_2%Gl9pytd(b0;P9iCOy(g>}p+XB46ubkxyzK(}(I_+; zMK^Ba?mywVb&Qj8L=z+sl9|bt=$IF+=MVlALu;*_=#IJ~{>YefA^o z_FwbX&4p+g-gQ^zazeKdfMC9HpO7jM;9$0^y$!KUBgf z<$BcFg{M$EtEsVFW1A+zg@s{QyiMN5WFyL1qmJ&q&shNn?}LN1&RGga?}ekz(t8=6 z`klYQN<5X|K?+V2e(1_G?%sB{2gr6MlMH^Mw_Vxo(dR)Wbk;aLxIT}49@K+1c|}Cq zJtR+!-9veh>~bdwgI}SbXPTicb`Vw7ShLKvrVL0nCBsTl8=_qrpIGtP!Q`p=S%z_I z#(m~mr)s!B3u!CTrcu%oDxsFjQ9_>E3+I6*ph8V>-@rX2p(fGln{3oo!_RPG-#!^< zsAa-(gqqQh z6Npp!!-8Psr=?zi0u*o$^~pdn845niKkb;jY4X~W4RcPc&d*E{?FHs8MFR# z#Kvya@72g-Ze+S-Fo@k~Nr|fYv=6x{0k#4K;1wK0B2`ip0XL*Tjh{Um9fS1(htYlw z-tnA5p@I%P)RB2DDbr?)O|aF(0@^(OwuG958V%C{*?Jkhg=90M0ciH`uBhxGA+zOSUeE#QRHE1awiX+folV4m!?NUXPcqi4AB_bYcg_8CI~jc2px0= ztqr1zAczc^FR7%67`vo`ttwE(bo_io@#RsPO%=201 z!@2nc`cNOw_5dYBxS&tXyoPL89(@gu4Q+X2hhtaI-j~{*|H7g z>D3wf(DceN-jmkNPD`5EH+@2P$GkbS8=6uXN^pG&kQPTEPR5XrrTe?^U?J_KYbota zTbo8ZZEJ0`GhuB4?etvhNxRHFW?Jn%*P8}LgH8ntE$b{4b>zHAh?t);BC&Qv%Lp1d zB65VAuVH>-BGRYcH^FzWFW2V_zGJ)H=U;aNMyDwwIsyYVn#ELNe0~lSf9F!W4I)7xny5jvtY^Dq5KwNI-fVCG&6D6gs$qu$ zHYi}N=6(%bsDS`KSm?LTZ@VAoCrBPTyHM>m*KH|kbt-7_SLi9U(km$Gk$J3WwO^rv z3dMfA%Fhqn6$(Ih$wU)20fjwd78JU&{7;CrFC&uuG?i$Td4KWSXDzaxP&4z3Qe_RVTKxKM((njI^0M^<;` z>Z0<;m|{!23dh}57^ev~Yw0gLoa4;NQs;iggOoH5^I$lhJ3A&>dv(y|*W*Vkl}$<+ zeO(+^O6w^tHPxFsOk8PLeON~rmmHcCO4lYq5|f(oXk@Jk1an|=U`}9XAnz9lx)!Y# z5t(4#Yo>i>FsF4Vgv{5t8BlX!;wk{em0L(mr@=5XpnZgHhmPvh%8|p{hpk3NAQBGx zm0ZY^IZdXUSf-C7Z@C!Fg||O;6UxkH+5B?h?BDj55o6j9w=C`N8k@7~xrO%Sa|-;A z1}`n1xVSVv@kD#w?v}C7gxi~Q6YGm@QTfeTZv5N(% zMLUgajkGglZ3yiQUK>n11J(x6F8>~X+U48hOS?RvOVy*IUCJIM{X-s?<;>2ZW%;x7 zY1O#qadhN-UCeyFI$U3f?4j4`-Eg7R8Tt&W&q&DFo5A&EK*p4AJ7X8OaP|1k)YQod zoq@{9ox|Dzn^r1{(P1enGX`2>INelcf87!fY?bA8X8A2K>T;0$$?`(6$^A)*@cq+0 zADG&CYT2@<7o{c?&Q2LqlW9zCJ#bh1p_UY5X3f~Kjg#U&56Y;|n=mso*e|W2sBF$C zZ&Pq~dwF3?4j!>-D=ulx4uOqT56sWVxa)z+$&05Zi@aCH)aEf0SI=lEL*lP3Q<0sA-Id1Ocn3&0T$$VFb>*Is#!}#t+ zsp(K8JmL=bdpzbr4|~H7Z|E?sG0_gg8Vn(FUHTsVPCeHZ)DyHbh+7)CDR5UHR~6M9 zMJs6&rJ4X_z!Vjj$@~Q0OkY~)d&ZX%UxTmJSMqiE3QFHJ_ZBy|g!zUL2x06OWQeeb z_>OF^4QL6V0RcixdzjLz-T$@_LlNekiaMN&1<|vsxyWSrHS~v|PALnmKiCj%YjqSi zIn?RURQtn9wk7?$g6A_~%?k^QS2fuEjs`EP&RbGurMkSDd86uf%^Q=s_*lyiKbLZt3B&q2i00IP+*f_sT1;aC6U}zj^=p^YZ%1b_tH{ftG8fY?(-IXqsr4ZQ}WV zGERJM8z-K-z0_!+?3N9bOlASNOcdK7!}h-zDc%UiABM^+=Xc+-RL;}g3@E^O`%d1@ zAI4M4&8LZgd!4tBpPh@sAHse%5&*UX+OWJ1Kta%WC_;ONm(BT1mt$RFmEG~|sH>XK zm^W@Z7VdVFOF_a#@cZJU#9+lI^XXB@ztI8ewqPylNB!#F^jSz%BV zDOwe12QVlUf|Y0PUXIVi1It?xXf@5GEp#tEPX)?wz%XF5F&rQ;w{b8kS;Rx=h=VG` z!IwLCKnu*@>70HJ_t#P+xk%)5GfLu(Qg&zf+Hl%wT5Ftk z3WA`3S|gA2i>m$Lt2N%To{z-L#H}kj!;}qq<76!Br9k(E(xqwK))y;czE0WrZr4kH;pSwt4pIonl3FlEhmlNmOL}mBz5x(^o#N18s85vN5ALR8n(s`cDKZ$0KoqNZUJs3F2USWL1;D<(EVUCVDu-BV+@ehTD>OKOf=tW-322 zDg%`B$1i+r#^}N%KMIO~oSM!FrFR!cO;~ffExj%;#YYjjyTjKfHX>tgVf>Vb|F(PG zm;IZjTgxB&`<|v#4=#=ym9+lA*_IcbpS;zSx$y8uWT@{A2tPAzaDP~4<>>NNQ{!uA z&GDS$yeGLy&cR)IO)8baA59QoLoLb>-*# zr%4?T4}8|X`EN(+jMmhE*3it6#`bq0_|PUG7f*KQE_~hj>#n5IbYof16H^y&$Y@wI zA-u>%pBT~+O3;2#5-s>h?=;YbbRFdyd>8tz^W~ag32X$eNxMY5QOmU}Rx9Xi1uPSK z1==h?7k-R@U)olp&c}6fYdM<2m2(XoSH**!hcYEhP~yi87`Guhg;|lTKz9OsqxeNZ z1BDnNP2fZUz7zf_U?|hmlMlr2LU6Yd34Dj&(omvN8kANgXTY$h(oe@RV&@bbe8c_1 zQSKOj2E}KQpTmz@{p$Ss{097P_z4Mq0JK)?>$}hsJOz#?J|q+U7BwzY487nexG#FQNa- z-#?mEN&2vaFnTNu!mQoF(N#pDQAi3-sql%aT@ehy!O=mrN-nxqfjDqkWeb@3KK&1? z0%cy@q||9$C}=NkvlQqtMqJ>EEe_V}{Z z6&B~;c5ic@fpMiBc0+OEw3S89-O!%5v@!`c&wZdaPFNXPy0U6aW7$}>TUym3n$FL2vJ|Mjww;{C2geACVM3N_@!X;iCy<9$!rRGg$+8hDP!h6GMb2u~y zGXksPASR?ags$_482@H}G{ki(+^lqz3e`n{b%FF);F&;b2n2N?1cqtY<`JXxv}-ic zhG-}>fyqQ_^*m8))YPh$)UE1n^?>?nvG+SGjR_MC$`*79Md9JyBkL49KHb!7|}# zZFa*({B0Z@(HwO6wX-b%lH06O` zT-8gthXP<}037v)ApcnZG5$;a`9m5A(!^@UXqIaDrRd?{UnM^LvE1lm7{ z#usTtdy7_NDyc!k9i2RPFoes0$<`mmqHJ8cXhE3W-2eMMgZ~ZhJ_5e4whWYPduOTh zzag#Vg`NU>-r@ZF(>21%;zQ1xC%3+}YV7aF?OX6q>wwG?PK4i`OnzF4j74PQ*sOHN z{ohj%@ekK%&_Lvr#NY|4+7;;E7eu}mII)(o^(~Ty8kwZo+Q=IRx+BpKiGa!JARmA8 zUM}h8JKO`p%H7WNolYIPMP2W{2j4A{rqhHD4V>}}uv4l~G5SbTde~JeG+F<^j@2Fb z+UtOy?Pa2JvjXf`h;#nLR~_4 z0;j7!hTldqjaQ9tG}hig=@BLb)<*kkY6Yvin+My4m4ioV>2E7!xL!cumLXgm>5_7}Dnvd43}cxK`vT$W@asbA zB6k)Z5tQT@3ux^V90ErKjUWj{^38y7Lr@unKFm1O;|HD^J(dOU2A;wLQY=4(rW|mL zJ2KfJaWq;P4KBcgO(^vlW7xc3IK%cQ879Nfk6a_dq$A5D^+pP-1X$_@^@;=Q~$?<$D}o9aBc77y_7?^fe6CWAj1{3ZV)|2lu(t5(ez zRKg$w&%!YnAYdc}xb@07Ly3ah4s(Oz33ntIBFB}7-NSM~40ShnG=iDq(L3HC{ zlqBay&)um#F`yq;lODVkzna3d&cm3XUglnQ<`%G;30q>)lL=dft_#|xCRQV zmG>!Wy%HY6?}q7I{6KjV;VKD~yTL9u2zLW;YgYY++Ok`pxQ|6h2*y0o72xivq<2t; z(hVrjt8h7Z#I6m!yZ*y_X%bT=M%!>ssWwzBm{H{7h+)>i%m_-EM3@r6mkgfkM;dwj z>fk|&|9S_%LPE))FAwg%`6VukYfgLu&PgDnr6>(=#rPHyr~yf|4vn*YXt}F+1nrh| z1dbaPh%#}?xMeGC)EzuSUl&&X9)uEacw7a=apP#5=dazBj)cndsmgFSxM@|A_#$GUB^&>{U&oqDEmR{9KZlLz8eb@KjhJoWv=-M0Yq05k%g+|oqX@yz77pjc zV^gW6!@`e}rhFw&E*xLx8b!cKvhMUb3)JI;Xg&jAq=o)&0X728p&LVhlBLMfYT+^& z5)2j^8Z?arOTknbtYGJU1|*FS;$9cR<%a|D4CjK8?EKMi!Wf*l%_W79d+@jwYOb!6 ziSqHqTSpYJ-qmoc5sxkopKoMM!;LIDi5-}K3kylenM@sK#}4F+`8SYyz0rzF8KqF- z*$zDOgIaJzq@RB%=on1{p4H0WMqwC3D96w@=cV@4tDW1{Ikzf2)oP`;2C(+<@32Uz z^-*)2MyFLl>p!@ow&pmS)s_@HBl4!i4XC5y6a16$6Xb+BkvB_)l{XW#nOS2rn)Iv; zE%#ShT-_nteaYF=#0_aL(j(TM;+E@1i2+N|^5GrMT@zxoyECHQ?tZ|XM!Pew&yAxjf;jSQ{L}S%RnpVw#<^rD0kaQsF26Pv6SMW5PydWqJ z-IUXaN~4lgoKl6u;VOm1+Py*K+$Aiy6BQE&P%;Dcg^y5Q zV6p6Z(!^>r1e!rKLx~Y|C~}NY8U(r^sP=~u{?OnJjUGU%CAAhXY;B0{j;5j2$K1}i zQG*+(-Sp^48Bwd|O||mrFy@Oxnqd3=pOiqXMLkf#-!hYB?RHolyh8yNKB_(Gy!qIZ z&flJ_n05k$LkB@PKJArpE1qAv{KXZS<5s+|bj_KiV{mtO{xs0|hkD!ayz`ewR$SOt zbZeh4-|@an*Ol!jFFa${DNQ*<;XZ%Zr8%OZZd@Q2!*PO#&cp1%p?0et(JhSt{I6A3 z4KJ2TQLEP`XbZKq+7((stv#2hR_vft0l#S2!3+ z3*8Xtm&9{-C~RdR9?K*-E6KUM21a0t2n1{q_GyNrBg$e|Fr$s0B)VLJsL{|E(LlLmDu z(4@m-G7wW0&3q9KJZLbkTdYz^8eg=YMphBMMlb0(rQTQL>t|o=yTSKCUv7*qjAU9y z0#+>=xqjpWBe}GZ;5$-CsBMXX${5Ix0YwbNgrgo=Cqjt`+4x-nlSu3U0+KGn65z$U0f2hF!n$!2$cIQ8x zpGKYbN}F|WL)n&w)XcU$bz>IYRhJu8+;_HZ^|RfR{h#+rE?+uj=GtOY=DaOM<5n)5 zGbt9_?X_<8DM(~9Tj)^D8_6J4^T$Y?7{2{xq{CuA0< zg!`zmv9wLr&n)O8I*IhGg!fUca(&tHPmz00ggmiQTq151`7!Jxju9gLMT8yV8zSB0 zy3Z4vME~hZpQK3}vZ^we0%}gag0IcvJ-TCB{;b3m0LNxC^_zh4sP; z{P3bstQDzM1pEkN&Ux{&NFA)qYPX8~4H4Mq8myjg6`@uHNd!InpNSn>@QaPh;&o9; z@bi!BB6LeNVxM?HKaE1+7hY#%& z`AiWEG8kx*^IlLzC93plRqj(dm^ZN-Pvx<#TkRigR~qmj8R3ylLEZpZ@cC`Dn_?j_ zKr?ypt0X+Z?z)&B#gr?T8YSP|(zTFfha0E6*cb0__jI)_Xk+HTeBe(GQea!)^81 z>vIf!g0d$^3K|Hy5OgJo4`NdbM^!~tRVW9PxUIrofu3jfBr_yArQoZ;Q5QKMwZ| z{^2EbXo(^XRL7{(P`^~E;al~uYN{TIY8^G80M}^1s;NVnKA^dw5j6kiMy6*_K{X&^ zsnw?r5j%jW6%u?vO*#B%N@Qfgv~CnA!@gs*ukf28^cmOwi}(1}_9;_~5&gyZN1Xiq z{8KPofZ8$V_3GeDZ@@ZrsE3!QhnGi)H>`OB^;|=Ir71en9Brzwa#xs*hWJvhLws5I zsZ!pOIOz0-rF0Xec@#Q4)_BldJfjfW6H4fldP17?rChTneJt3%a}@->cedt{3N}!Gq?3UNK&2 zUYzI!-+BEL&+&VjJ(E2-g(rOD`HLs@^7IP~jR{Q)<-}0Zgp1RQhr1!1$Q>w0K;*c=$H{*LWKL85)TeKXJOV6j@L+5)0Q(7@N4e`x3_&8wOZHE3z5)T7ml z)f?0gs{7Rn549u>jfk+T7g-;$eq_b7`?=O~E9Y;Gv{Ho?Mp@@r7g-;-K4X2&`d`-n zv8p1hptSm1=^L+M@BgxLi>(h@4_f=Je3KQTtfQ^CTq9T)_|^)aSmB`cbt~Org&r$Z zSR1W0j|~YbYk-yhmla;O!sD*HD68GN!7A){&DC#%JgR`bqpToWM_B2{)^Dx!Ei3G_ z9=1Mf8BKm*HG*i)?#V+DMpn-%_Ph41B;zG;PiD{QpxupVLn0)m!iooKDHa?w@@ zw1S5f8V9ZLqZKY$;cY8OLoZn2h!u8Q0moftg}GLkW`%q!q+21@3c*%zx57;;{9uKP zR(Q(_&)@1#Hd|qZ74EV^traF)A;Fqyr6E>OTLIs2U4FxPE8zIYtniQ(wz9r=TWPi2 zdyEz0tq^1dPb-Z6-3r&N@R=3PS>ZV=JYj`hR@iKXrSkCk)@m!o*&;YNTLcGZi{M<6 z!NK8=$ir{4!re0XH8S{Pt-v18TQT%wU18m8J!8FW9kMD2B0XDKisC*ro>Oy?lG<^le{5JcHgL?y`LfI6Te zt}{es&>6R=85EsS#$j~aMn-wQ=jaSL!}yySbzpqx@A)*%|9ftA7Lb|uexJ|3(N1;M zsav;vD)&Fzmywzigi?hX9 zQ>SXz;2y)xgFZ zn4D{avnIx75>mF=^#tyV@>QtbSx)e*LGpw^tz3Y@Y}=BMRzGWW_)$uGz0dd*~yE)^~_SD85)+#SRVr@tawenJlwpoShw=6OFvbQ=+c-*!S2C??p!e zmv6QnI7iq8WE+(^$vXcR?E-c{ z0p-kOH+GwTY{HXuo2pFBCdFCXoH)P5cwiYca!f16G>+UdxXyuUll;r_GjH z3-?&&Saw^!wkU|$edB8j`-Jv;EJs13ylP={ESo_uS6NsAsE=CyYEi5fb}tBDwY+cP z&sf+Y3p-$8H(A&Q3yj`f7Cw&5-W3)ekGElA=S7ppEhjDfu;qw_-(g`pENqj7EwQla z7ADMV*%sy zQ%e!_Cv!jD^6ucgnz_gmN+xC|@|E~i8bH485u>po*SZ{a5_+7Vp6 z-*Ozl(UyG{jt)dDi!FSDh54wtL<`5&Pg|I{dLLb{)3U*`-*U*JmRd9m$92)#acXRz zenPfof@L~rc36zHIB^F^Pg-PjK@k_H22^qJV;0tLVX?C+aLx{kdeU;n!e10m1ZVJ4 z>Wjk?g!xdeliTDivMe)L*2PKye29qwTAyGU!E-To8#1e2hT&l78~GWczy>j@U|D^G zL_2b0Ykz~kWkn2&jZY}ui&1t`gJ$@YvO1Lw$__=oO1?q9O_t}$tU#`nTV;8j=_V7OZt8@GgNCRXv65!l32vqF z9FGVgqe*daFO*;n+^P?AJ5)ssSFDP-X%RgRF!4x3!3!c3s=2ss6 z=7)#?J1hSUQDD3GDl;Fr<(tDKA?mv` zI*UCL>$?|WaqKL18vmgoau9j?ClL7$EA5v6^B&RpwF9xfSC+ewaPD&c^{;4n);7cX zZU1!(PPgy}WteA>x&NO(l~NFmh%EoVJd3|VXaupbN!_J#g$`|2b_1}W%Ft}!T?R@Y z{{+E$2Dc#>duAHC3rZY-}_8iYcGqiQw!bV;jQvicKsQM)|`v3|Vk_4Aks7_8QREKs}Rnu>?XG+Y32r@SuqRGBuJc9{;ClzUBoHSsR|5)*Z;Nq*JD zZZWZ1Q>$sSNiH&Ba$xtIF`YN@6DIbM=^4{2CV7WxKc;lk64N@9j1-_U6U#OMK_lM8 z1>yT9cHG47FcG!9!L)Y1&}oGfk|* zG{MAublpS~U-60QD-%Dax2B~Gwnx|Ig3xWs6@#cpO98UQ5!0(CPQ5#1lEpnuKz0&6 z#c*5rjJ~PGjcW?;H!(2;ajZwXwyr)LZ^|@b(Q0Bf=VBe}GqFx|otjcKQ^drxO%<{G zd(8BriPL@UH_2_(Vai4O)bx#bM7VRD8tcPi(;5>ZONDbHVp2?*Flqu_7zR6tHOko! zlJN{$Zfp+J<&-X=XeXo&d=j*mkTB>&5=#H*wD{H!3>_1D1Q}7R^9Wl4X}QQKUkGdf znwAkYD;&{C{h$jJ6`46Lj^%{to6+Aq#MZpY*6|1V=7F2}=>eaRYZpF_b}M_LpTbw3 zr6oHNFx$g>G1OGrI49B%UqIR+#Ds8l$`C$Y<;T(Pn{L9`TB$tE(H^=Rc<2;}8y46&b7p1_Jpr$9K^PHSf&02|hb7EIiW%)&%R zB-L3G%ixCirC1C^M8bUkIfJnV4vIm$Co*i1iG@4BQ7RIgt!!ycCzD1|z^yrU^}?n$V=YZXNhGmZwD4ub3*j zfI0ohc+Vma2xU5xCCp$n7|SWQCSDlhN4UF`jOe;850`V&A)bWKll6n&m{Rtm>)!sw z&(rG{H?>^Vnw#6Ys(IXE&C4?f-k7sAwWc&vF&B*u=P92@=ZvaO^0-&bjeZvW9k2D@j!y2$8-!kj3$#BvAewm2$_w=xq-`^G$cNE zmERCtn?ixPx?ziuH;8*ge5}BLP-0O&BH@T7q-oug7jJVMF^^ro|B9{8tS-xLoV|SA zn0en{uGx;-*L6=`uBGwJ1rMyPU$H7~{PhcKXWsqxwO2iI!;JFevRRuZ*ypY+UoLE+ zT$+h{JsabTE!wOZnaeWy+_IHr0M%|~Txg_b#iP*Fs2GfH<0j)Sqmt096-s;K&m!%! zvd7orH88_>m>?-$vIZk2r<4+ z%o-|E@bw2WoUA5Sig(SvzvWOFGHd&Z2ObAI@PXeAz5a<;ir4?-in%P2rSPh`ODsjf zLVz-TlaLo4TV%H6lvbv)HRAm*KYZ=_gG*xXUzb5Cc>n1zeqO1S%*|Sf5c*LoM{zQm za#O3>oYtawGuS@lxa9TZfD>HIRhoA!??j%QM`9)cVg~P9isa;|zKGjNZ(e9yBvTO3 z#fs<|T%2zv9DpOTk-%Q%k*ZIBYRtH~j z?7O*m>GUxQ`@b{xF0Wk}E-s$e8iY|a^`7^3)Xl#C-Rn~JJobZ(iCf7b#efwr@v-U~ z&;TFQtgrT`8Utt=rOD2cC^8uGQ@Z=_xyN1C)!}PO zbrr|wmuFbtlb`#xReo;!b#=?yiVTL`vg$4vn|H-_$iJDO&TpVNgrt8>qhSx z&54)!vU)ZnZdn|kJ7(n=o-0?$JU5;d_?bD`le{^3cd}y0aA$1F*p;D}gUwo5W~rTR zwV$$cdrOa@nc~2-4)``yb!-cQUM;CUpda#gTA0-0YuVS**P=vDwXkC?ti6TxwDh)c zM@z7UpJ+KpTg+78$gGZdosPyxlG_}qG_YI@CDP1+Ux3MnRss!VEEC!Q{Uzx+I2Wy@ z$p}wLCTmnN<8>;?O}H2Y8Ew{(U&n{#<$-HbKi z<#$4dxZ?iR>wjE=!KLuWXq=r2jE*d?YFgUp^=eBdR4t!c7M+#bvSe&(TSsQwwKJbA zXd9E&wCC-cZ#r@Nfxh`5ZV( z=_$>}JOIXWdP+T~J?A{C$&)I1tVvRQM0JYUryQ#f@Yd&Ln9JdG(etS>_po|B-5e}N z?p~TpkuDm`z0;Wt=k>3vIP<=Wk_r8vXJe3LaSE6qu9 z7@$ecBwv!2q!^QuQat-StU-Ui{6mlS?!Sdk;4$;P6!j7DaEHe$eDD`M>ON1ecmYZ~ zk+a?g&r_b4J@0swvmVyxIp#UxksCbipq70ewpSdILAM%*PtY~?dN}#=58^bm=Aql3 z-aa*a%EPGTK93yq(D`&u$fH%(gx7d_(7Qg5LY?3jJj|nI<%ZE;bO68TFkSVuNA-G` z+u@9u(*~d1=QI<68z6OJ$aAz6GaQg4!a@AT(?r?~9xI+|c@SZ^B$SYbR*o@b2gl^* zRqAPon5}mACsi#7XEvtWvTeDe(=GaApIg6jO0lBe!j;6L@g>TSh~M{s6=d*+Rr*Go zemzGbEZbxRW?+|jm-&EM5fOcq<*`|k$@;S2BEyPyUtGQoObX8>g>DwhJ}|!DC``t4*!7Y13wg z7u$Mly&_~+Jw?`9{WoHHoiJ2tv4f{==WJ#pU^y&`$)QT%MUvwU4T#Xh5L-)6Y^|>2 z1u4t|=0*{*xoDkBkG~)}rRNQe4AhiMqj&7+?`I#r7j0#~VSm3Wx>bGa+bg&&8XUM= z$Ot?$RxifWYOO+UJ|?jfc#?2+mIB^HF-fNprptj$)2AL&`9YPfQF~PGP`xVG5o?5` zPNPwzvJ3j*W9kX@v@1ag^7IcF;{>o9Q z5HXqa2>mWZ8WCg)FNH~)XL3TKJB=~;tlc2IY$TkJpoFFp< z-NELdYz%thnqRdsgUxNLvXSFYHQBxivDZT%g#H?mUktH(L+q9i>ke%W@p&QE8e&DE zx)A?5#6AtN4?^tC5c~Ns5d%pmLmVC1N*x$JYz?vdE>K^z3+z;gp_!+s zzxRhY2&oO;9%6MNmK|as{WLU8bU(GVH^e(btS!VEs9EL+c|%+$+7h}Y^jPS{kg{5w z?-l2-d@)3wk;OHrEAv7O?Wcvp3K}{QVtjFEU#K^9ETrJ-r6Cp! zF-c1ZF;iw`B;V;VU`|hP1Z~7sWzc%XL{9TKJ~Su>S!{6TrWHLtF*$z8-VsLx!HB~b z`@#kfh|7ou=}Vva@J%1uLBUaPyMiqIA=GdapqVsKJ*?fk~9 zXGs;Hz)miw)U;ipZP}YHr;LqsYE!S7l>N*g6@RjQWn)_Lj2k)zZj+~Fwv8_}s)5=% z9Inh*awUBS=HK2)(ejPaYGBw-5RN#?vtpUnI3iT46lJUWooa#9CB;DwpU3|NL>l_wJHwlPhL4 z2t8o<=2PpZu56xr)AYQ5b*uVKin*88TS^A*%`Rn18??I3zTG~)A$>?9Vqox3T8ud2OpE zG_9Ue5SZLu-?U~*As-bz6aCwSTi@HuOPgZQ*cqa; zaDS7fTxpE7UvqZXY_8$mg_{fcuIvNZ9GyO`C17T1_0I9~R^uEa-z6Ua6OFVq;9ZXG z8J+HEmYhyR37>N+CTHJR)-aZ>8M|*R_l{*3#v)?Qo=-sv_$D!qPz2 z1|8l3wuVtqgoWZa%=MRhAo&aKU9;iI5=;`Yh31pU0RVQ6Hg#FUntK=I|2(B`$;7cM z+e^sc(6+QLg=cO#abU*mPVOtM%ZScV4SC_ZLWFRYk4dd)4JJiye|uZ!1FLE>7ytNX zT6B)N8Xg9HeR2+bkttP4Khmz55nLAJbBk6M@mgQ2k5`$SkuHIvWt1PP7*w}PR%^xV zZq~A_Ta!;E^W>VI;uhqGSY%&~R>R+^VN#8+W?xNT4OSpE>{tzJuVFnky*1oX6RhDU zYR=IXGr7}TPHTR|;LIq~0Sv{O~Ik6+#q@`%O66I>N$8=l%Qbvbhuv|Tx_X-Oy}Ep$aQJ9No z=?PWk$*z67zNuo*rPg@TGs@dWC-eGEy^9OVyC1xIRqu-OoGX4T^aRY${CjwgUa21W z97&~sdr9)gc}Zc=wFLo(W|BPlh>G;Y#XF4-U4wz?G`32h923glxkMnwmYzwj@9V=K z#wR7AXiTKmoo|gV9aTT8I#sUEY#BEuIk~RBHnDzQU4~Kqk*d}#-Q6+p7U>SPu(ce) z8njyaxi*8cBlzm1tx0>6bV}=}v{5{}75S23jj%QNzJy-n<{5>oy+D}O8QiJPA)okMz``r^qlhoYOCK-{F-e89xQ0Y!yu$ zA{Pxnlt@b(rOHR@);!$3@a}b!;}0h9>lt%Jb6&~Rjm?cc%eD93c=o;Yhs>oDW|UmF zp=k1|#-h11+G_l)Xwo$uMHyQ6B=4*aXI^9J=!QaXf~&BpYx4g4Z@eY3ur|vvu`N)W zm*KQ}vV!AglUzlGnG5H5p&CHWFRWPkcGhjDWL6p3HQhFXmXmKml9Uf7f_U((Zuk#S<# zR2PLH;vEtM=Aa?L#m4-rIS>gIQwJw}zW&=ME}rlM^Y<;|zomDD6|(#_?$gQVY1P8c z2v=B@)GYB`sL8>{I3_wcK*G8mn;p9y0(F7k1xx;N`5O6V8KHCVJz@=gPOHa;h)8re zk$Jnt$rWd!{zZu$XPP<^`KzuLc*7&q|@zoYT z56R#6$G;i>VZ7-=JUbqLGM@LwvmK~3$`{A83Gvh85yHrn_{4br+Ua2e+Cc}2&@i|` zXB*Opnte#5?0#yA&7*dhzW;8z^hHhh2ZBi5G)V-Y*h3dOMurc&VwYsE-hu$JONDZ&aFQwimK*}p}v zIrevBLZZuHNC4W~S1*HGHFv{=1~`hwHMsdJVhmmwK&;7I;LSq$X)XKD$!tu)!~%X@ z!JdNq3gk*>lap_B5^SAZmC>BRs|3i=In515tW9rC=e2+WAodi@OhB2M89+T%wq_uw z(ywy-5)99rv}JEa0un+&U10=!W~3XLl$D>wW@j*3AgK+o#6Wg{8v=-vp;M0ps3E00cB(nx32>ZwG{9yCx&pfbGR`QXlNe4p6JYNT zw)#+j?H3KN3$RY2W)*=60iHQXd+%2P_DbO00Phd5!vVG~@_a!1x7b}hBZ#Sg2Lkeh!1MsG8oX{d zuKULTLyP*&$r}PY=x{S`M@CzQfIGm?(}VdW!1o6Z1^BK2!|l%uunBauD!|VS-sWLE z9=<{z@l!gEk(B#~NAd}NtK7!H5Q4ko*MA%7sf$VT(3fIA=p}2^q2a2Z7dtvQvT=0qGqT;Gk&J_}{j^LS z9MW{kFnJM%w;qJKb{#@4_5WbJT_jxJaDT%DN>eXR`RCD=Ohj7_#fT@AD+hl2gvn*H zn9UYbg6WwP(ceDvoY8KCm(FZ*8jknz z$NO^fa+-$ttC?h(M84(?5cEWyjPOBS? z5-hORTDf9P#1*WcSiiE$$E}PqFppT}9ad(r=31MrUDjPz+V?6b%q9^%nBpCb9<-We zW=7BwGWsBxdbACv{u&013&I^|ig3#+5%lO5F_|J7{aqWws`?oU>fCQTWRw3H@oQh% z*clspUxa}k5n-GLh`*V@$vj{?Y zq!>J}X2ht2i|i+26e)4%BIj*w(8%8{u;BhxYwh%BjV|2(Fr`j?a_JcM+4^v-qrP66+XCc zF)qCP9j)$8ncd0Ry-s#OVz)Ve;N)URT$iyYUN*P`1uAuTkL1!{8RnB!q0qu#{Kbre#U>^&jslrf4`p# z%1-|VKNp1A{t7?;*K{+fO(qE4)FxXlntY!+z(kt|r~^zi`W@myTJ12X(bYb>{r!Hv z*uTclC-|rPxzAtf=ZZhk&rkc$`T24GNk9MBbk(Q*EJns+jj>o$EY{F>j&sRR7mE0m zh`-0b&wtFX1pQ3%JN?{ASX>_#WFZ|qbjhqV zG;T&5CzC%uMHowKgTwQYh?EWmQ-ZiGO+8`|kTHoyh8PfpwGwj%RUy`LgyK0rw|&XA zQ!>Y38SiTUUb!ne?bsQM*PFuSf^(6qr)!+v~u;kDrU4LzP`Jxz}GogmZ z;$V|T^ScJ_lxqf7@)vi@xwq{8_IFhDfJvE%gZ&=-Ma9x$tx=|>egdujlZ*Z7`R80# zuiBt;EcZ149r>+a2IE%8aYtNx+^M*;aY_&ytK(#NLQ}p>l8OeSO<^}Dj}V|pAUg%f z^xG6hTtrUyN?|Rkq|l{`sydbgxNYGgB%Eyu2E#7S8kdgC%&3l(mq)5Ic5c79Vcz6X z6)gotE#(=L=QTuU6--(+fps^`u1V)xd3w$4dbaYmd$wJ^}L?wIJN%f4%g;Py<(28DsDO@Tba%4qK zRI^nXG{;q!~J7d)5c?-iZbg5iF z7e3@S)elwP3xppam65EsOlS!U=U~0~nE_}YByBEM*Wd>FR0S?UT)7wC!5H?BmX7>4 zRdXeUHPBG^0ru|&iYTze!EQleFD3pZ;ejG$kPy&Jsm>XGiAYcUgmaX*86Q1435RJC zCwj;d-OCJ9eFFTA&mE3VG2ZxJH|g^x@$$`rmzNxs^5Js?H>Z%cUk#2<4ER!W&pFJf znQ}}%0aLo!_hrz#+RGi@cJC?gS+5er#%izZ^(Ga68AwY6OOpr{Ir1@f1~(6smFe7^ z0OWWIJ*uvR;m1{xDB;Wjoq<*XGV2LMMXz- zM&`JsjXR?Man(w;tbBT-U|Vf^8S7ki<+giniyo*SV(*6N-4j+#D%hsq<9uGAMC3fl zC*7x|&Lw<)4=+HbhCHg&;tbm`tC!-gp*Nj)7B`Kx&Iv668&aAtZB3e-=7W(g;Qsuvzeq7-T$<^bsCXse^tek0PXfz%|J7q^8?jx6RIN z99xr7wxc6IGPx}$sjR#_xoJynCw7eBelw%Mgu}62?t>47k&tdMyUqN8Bj#7l@0-6eE1=di`<(~E zU6kg2*nA{Low9phnCc$hgot=#kn5EXog%D7JF@ZmaF4#JS*~6mR?LZJzDl%IzwAm} z_qh3_S*F(b4pCrns(Fc-Q$559X7nA`M{fQr=6B5;nd-gfKC?`f7I%m%_h?R3KRk)b zis#KT9Ydi+@It6g3sW{bSCIVPuOIC}CcCJ&h$@Z9;Ky8x$Q!!!8M8bcWf%{M>l1 zXdyAx8`hA456hNOGCTnP0c?{9Z*Ca)jaWs~^hI)j^l|VE;&Y&ch#k6Y5aK{cgJBAq z?_#G2@j%OAd?-YGu-qZ)=_Mqge2hhuJLFG%8>em)ptoGQ`NGHYf2+VUfaSPbdOA&R z=!*~bIm-K_xBE{bz| zO0l}=97ac_6Yxcdk8vZj5dislJoRw4&6dl22U3otJ=ZYp`uCR2+uYTE{U7F}#T~UT ztYvVTq`Wod)hWMA&wMQIy}4e~qmL&1@caK@?!DJK@4r9!+3E)S+*yc8!kjk-Gh75X z8f8Ernbw@ls(3T!N%1-HmGN@6lxH)fuJ(BHq`=jNth}r3iwKxcYBwx0tOgWn*=h;0 zh0|9l%Hpg+eVjg^!C~-G2+1+&G-A|=KQw!MSjx3_WI60!NC(+rZ?F%Gic@xz$*$!T zhV1}058Lel@A}k4qcO0-UAC!gSD9R90IWWW3LxQ*#fL6Tz^CK^tKyoR?crpN+=v(D&){w>0?Cjua~mmrMg$UW@5%%0Dj7WjIkMfqGzsW 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 OV+S^Jd+N1u<^LNTKjGm3 literal 0 HcmV?d00001 diff --git a/sdrgui/resources/res.qrc b/sdrgui/resources/res.qrc index 9f6b124e8..6b645b992 100644 --- a/sdrgui/resources/res.qrc +++ b/sdrgui/resources/res.qrc @@ -93,5 +93,7 @@ load.png keyboard.png kill.png + LiberationMono-Regular.ttf + LiberationSans-Regular.ttf From b32330780b25d0b41cbd2910ddccbf865492be88 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 7 May 2018 03:38:57 +0200 Subject: [PATCH 362/956] Level meters: fixed text position and make sure appropriate font is used --- plugins/channeltx/modam/ammodgui.ui | 12 ++------ plugins/channeltx/modatv/atvmodgui.ui | 12 ++------ plugins/channeltx/modnfm/nfmmodgui.ui | 12 ++------ plugins/channeltx/modssb/ssbmodgui.ui | 23 ++------------ plugins/channeltx/modwfm/wfmmodgui.ui | 12 ++------ plugins/channeltx/udpsink/udpsinkgui.ui | 41 ++++++++----------------- sdrgui/gui/levelmeter.cpp | 3 +- 7 files changed, 25 insertions(+), 90 deletions(-) diff --git a/plugins/channeltx/modam/ammodgui.ui b/plugins/channeltx/modam/ammodgui.ui index ba944b224..a6fb4f3a6 100644 --- a/plugins/channeltx/modam/ammodgui.ui +++ b/plugins/channeltx/modam/ammodgui.ui @@ -56,16 +56,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -361,6 +352,7 @@ Liberation Mono + 8 diff --git a/plugins/channeltx/modatv/atvmodgui.ui b/plugins/channeltx/modatv/atvmodgui.ui index c21663ff3..ce45e6cdc 100644 --- a/plugins/channeltx/modatv/atvmodgui.ui +++ b/plugins/channeltx/modatv/atvmodgui.ui @@ -50,16 +50,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -506,6 +497,7 @@ Liberation Mono + 8 diff --git a/plugins/channeltx/modnfm/nfmmodgui.ui b/plugins/channeltx/modnfm/nfmmodgui.ui index ff364bfeb..d6c935ba3 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.ui +++ b/plugins/channeltx/modnfm/nfmmodgui.ui @@ -56,16 +56,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -401,6 +392,7 @@ Liberation Mono + 8 diff --git a/plugins/channeltx/modssb/ssbmodgui.ui b/plugins/channeltx/modssb/ssbmodgui.ui index 1974e70fb..b5d2a6112 100644 --- a/plugins/channeltx/modssb/ssbmodgui.ui +++ b/plugins/channeltx/modssb/ssbmodgui.ui @@ -53,16 +53,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -707,6 +698,7 @@ Liberation Mono + 8 @@ -1253,16 +1245,7 @@ 2 - - 3 - - - 3 - - - 3 - - + 3 diff --git a/plugins/channeltx/modwfm/wfmmodgui.ui b/plugins/channeltx/modwfm/wfmmodgui.ui index 464c5e07c..e69ef7ee6 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.ui +++ b/plugins/channeltx/modwfm/wfmmodgui.ui @@ -56,16 +56,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -401,6 +392,7 @@ Liberation Mono + 8 diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsink/udpsinkgui.ui index d346f0164..0fa5a35b1 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsink/udpsinkgui.ui @@ -31,12 +31,12 @@ UDP Sample Sink - - -1 - UDP Sample Sink + + -1 + @@ -56,16 +56,7 @@ Settings - - 2 - - - 2 - - - 2 - - + 2 @@ -92,7 +83,7 @@ Input sample rate (S/s) - 0009999 + 0009999; 48000 @@ -122,7 +113,7 @@ Signal bandwidth (Hz) - 0009999 + 0009999; 32000 @@ -211,6 +202,7 @@ Liberation Mono + 8 @@ -511,7 +503,7 @@ FM deviation in Hz - 00000 + 00000; 2500 @@ -537,7 +529,7 @@ Percentage of AM modulation - 000 + 000; 95 @@ -814,7 +806,7 @@ Local UDP address - 000.000.000.000 + 000.000.000.000; 127.0.0.1 @@ -858,7 +850,7 @@ Local UDP port - 00000 + 00000; 9998 @@ -968,16 +960,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 diff --git a/sdrgui/gui/levelmeter.cpp b/sdrgui/gui/levelmeter.cpp index d5257aee4..595c874aa 100644 --- a/sdrgui/gui/levelmeter.cpp +++ b/sdrgui/gui/levelmeter.cpp @@ -180,6 +180,7 @@ void LevelMeterVU::resized() // 100% full height white line painter.setPen(Qt::white); + painter.setFont(font()); // painter.drawLine(0.75*bar.width(), 0, 0.75*bar.width(), bar.height()); m_scaleEngine.setSize(0.75*bar.width()); @@ -323,7 +324,7 @@ void LevelMeterSignalDB::resized() { if ((tick.textSize > 0) && (tick.textPos > 0)) { - painter.drawText(QPointF(tick.textPos - (tick.textSize/2) - 2, bar.height()/2 - 1), tick.text); + painter.drawText(QPointF(tick.textPos - (tick.textSize/2) - 4, bar.height()/2 - 1), tick.text); } painter.drawLine(shiftx(tick.pos, bar.width()), 0, shiftx(scaleTickList[i].pos,bar.width()), bar.height()); From c786e3f6f918e0918d931e9e2aa23022079d01d4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 7 May 2018 04:04:57 +0200 Subject: [PATCH 363/956] Level meter: make sure text and minor ticks do not collide --- sdrgui/gui/levelmeter.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdrgui/gui/levelmeter.cpp b/sdrgui/gui/levelmeter.cpp index 595c874aa..0aae57ea0 100644 --- a/sdrgui/gui/levelmeter.cpp +++ b/sdrgui/gui/levelmeter.cpp @@ -201,14 +201,14 @@ void LevelMeterVU::resized() { if ((tick.textSize > 0) && (tick.textPos > 0)) { - painter.drawText(QPointF(tick.textPos - (tick.textSize/2) - 2, bar.height()/2), tick.text); + painter.drawText(QPointF(tick.textPos - (tick.textSize/2) - 4, bar.height()/2 - 3), tick.text); } painter.drawLine(shiftx(tick.pos, bar.width()), 0, shiftx(scaleTickList[i].pos, bar.width()), bar.height()); } else { - painter.drawLine(tick.pos, bar.height()/4, scaleTickList[i].pos, bar.height()/2); + painter.drawLine(tick.pos, bar.height()/2 - 2, scaleTickList[i].pos, bar.height()/2); } } } @@ -324,14 +324,14 @@ void LevelMeterSignalDB::resized() { if ((tick.textSize > 0) && (tick.textPos > 0)) { - painter.drawText(QPointF(tick.textPos - (tick.textSize/2) - 4, bar.height()/2 - 1), tick.text); + painter.drawText(QPointF(tick.textPos - (tick.textSize/2) - 4, bar.height()/2 - 3), tick.text); } painter.drawLine(shiftx(tick.pos, bar.width()), 0, shiftx(scaleTickList[i].pos,bar.width()), bar.height()); } else { - painter.drawLine(tick.pos, bar.height()/4, scaleTickList[i].pos, bar.height()/2); + painter.drawLine(tick.pos, bar.height()/2 - 2, scaleTickList[i].pos, bar.height()/2); } } } From 8102d0ed080833eb50f79b5d3eeeba04d53e9976 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 7 May 2018 11:33:42 +0200 Subject: [PATCH 364/956] Scale engine: fixed tick text size calculation --- sdrgui/gui/scaleengine.cpp | 13 ++++++++----- sdrgui/gui/scaleengine.h | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/sdrgui/gui/scaleengine.cpp b/sdrgui/gui/scaleengine.cpp index 20eab2fe8..2e921ead7 100644 --- a/sdrgui/gui/scaleengine.cpp +++ b/sdrgui/gui/scaleengine.cpp @@ -281,7 +281,7 @@ double ScaleEngine::calcMajorTickUnits(double distance, int* retDecimalPlaces) return sign * base * pow(10.0, exponent); } -int ScaleEngine::calcTickTextSize() +int ScaleEngine::calcTickTextSize(double distance) { int tmp; int tickLen; @@ -295,7 +295,7 @@ int ScaleEngine::calcTickTextSize() if(tmp > tickLen) tickLen = tmp; - calcMajorTickUnits((m_rangeMax - m_rangeMin) / m_scale, &decimalPlaces); + calcMajorTickUnits(distance, &decimalPlaces); return tickLen + decimalPlaces + 1; } @@ -356,21 +356,24 @@ void ScaleEngine::reCalc() rangeMinScaled = m_rangeMin / m_scale; rangeMaxScaled = m_rangeMax / m_scale; - if(m_orientation == Qt::Vertical) { + if(m_orientation == Qt::Vertical) + { maxNumMajorTicks = (int)(m_size / (fontMetrics.lineSpacing() * 1.3f)); + m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces); } else { - majorTickSize = (calcTickTextSize() + 2) * m_charSize * 1.2f; + majorTickSize = (calcTickTextSize((rangeMaxScaled - rangeMinScaled) / 20) + 2) * m_charSize; if(majorTickSize != 0.0) { maxNumMajorTicks = (int)(m_size / majorTickSize); } else { maxNumMajorTicks = 20; } + + m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces); } - m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces); numMajorTicks = (int)((rangeMaxScaled - rangeMinScaled) / m_majorTickValueDistance); if(numMajorTicks == 0) { diff --git a/sdrgui/gui/scaleengine.h b/sdrgui/gui/scaleengine.h index cdfde9c3b..a66f6b093 100644 --- a/sdrgui/gui/scaleengine.h +++ b/sdrgui/gui/scaleengine.h @@ -62,7 +62,7 @@ private: void calcCharSize(); void calcScaleFactor(); double calcMajorTickUnits(double distance, int* retDecimalPlaces); - int calcTickTextSize(); + int calcTickTextSize(double distance); void forceTwoTicks(); void reCalc(); From 0c946d86e23e222185a7d13caed3116ea8628b78 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 8 May 2018 01:35:08 +0200 Subject: [PATCH 365/956] Use unified even/odd half band decimator --- sdrbase/dsp/decimatorsu.h | 31 +++++++++++++------------------ sdrbase/dsp/downchannelizer.cpp | 16 ++++++++-------- sdrbase/dsp/downchannelizer.h | 15 +++++---------- 3 files changed, 26 insertions(+), 36 deletions(-) diff --git a/sdrbase/dsp/decimatorsu.h b/sdrbase/dsp/decimatorsu.h index f10725384..60c122b11 100644 --- a/sdrbase/dsp/decimatorsu.h +++ b/sdrbase/dsp/decimatorsu.h @@ -24,12 +24,7 @@ #define INCLUDE_GPL_DSP_DECIMATORSU_H_ #include "dsp/dsptypes.h" - -#ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfiltereo2.h" -#else // SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfiltereo1.h" -#endif // SDR_RX_SAMPLE_24BIT +#include "dsp/inthalfbandfiltereo.h" #define DECIMATORS_HB_FILTER_ORDER 64 @@ -204,19 +199,19 @@ public: private: #ifdef SDR_RX_SAMPLE_24BIT - IntHalfbandFilterEO2 m_decimator2; // 1st stages - IntHalfbandFilterEO2 m_decimator4; // 2nd stages - IntHalfbandFilterEO2 m_decimator8; // 3rd stages - IntHalfbandFilterEO2 m_decimator16; // 4th stages - IntHalfbandFilterEO2 m_decimator32; // 5th stages - IntHalfbandFilterEO2 m_decimator64; // 6th stages + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages #else - IntHalfbandFilterEO1 m_decimator2; // 1st stages - IntHalfbandFilterEO1 m_decimator4; // 2nd stages - IntHalfbandFilterEO1 m_decimator8; // 3rd stages - IntHalfbandFilterEO1 m_decimator16; // 4th stages - IntHalfbandFilterEO1 m_decimator32; // 5th stages - IntHalfbandFilterEO1 m_decimator64; // 6th stages + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages #endif }; diff --git a/sdrbase/dsp/downchannelizer.cpp b/sdrbase/dsp/downchannelizer.cpp index 713748af6..b8fe5f3f3 100644 --- a/sdrbase/dsp/downchannelizer.cpp +++ b/sdrbase/dsp/downchannelizer.cpp @@ -190,43 +190,43 @@ void DownChannelizer::applyConfiguration() #ifdef SDR_RX_SAMPLE_24BIT DownChannelizer::FilterStage::FilterStage(Mode mode) : - m_filter(new IntHalfbandFilterEO2), + m_filter(new IntHalfbandFilterEO), m_workFunction(0), m_mode(mode), m_sse(true) { switch(mode) { case ModeCenter: - m_workFunction = &IntHalfbandFilterEO2::workDecimateCenter; + m_workFunction = &IntHalfbandFilterEO::workDecimateCenter; break; case ModeLowerHalf: - m_workFunction = &IntHalfbandFilterEO2::workDecimateLowerHalf; + m_workFunction = &IntHalfbandFilterEO::workDecimateLowerHalf; break; case ModeUpperHalf: - m_workFunction = &IntHalfbandFilterEO2::workDecimateUpperHalf; + m_workFunction = &IntHalfbandFilterEO::workDecimateUpperHalf; break; } } #else DownChannelizer::FilterStage::FilterStage(Mode mode) : - m_filter(new IntHalfbandFilterEO1), + m_filter(new IntHalfbandFilterEO), m_workFunction(0), m_mode(mode), m_sse(true) { switch(mode) { case ModeCenter: - m_workFunction = &IntHalfbandFilterEO1::workDecimateCenter; + m_workFunction = &IntHalfbandFilterEO::workDecimateCenter; break; case ModeLowerHalf: - m_workFunction = &IntHalfbandFilterEO1::workDecimateLowerHalf; + m_workFunction = &IntHalfbandFilterEO::workDecimateLowerHalf; break; case ModeUpperHalf: - m_workFunction = &IntHalfbandFilterEO1::workDecimateUpperHalf; + m_workFunction = &IntHalfbandFilterEO::workDecimateUpperHalf; break; } } diff --git a/sdrbase/dsp/downchannelizer.h b/sdrbase/dsp/downchannelizer.h index 86c21db27..3e5059154 100644 --- a/sdrbase/dsp/downchannelizer.h +++ b/sdrbase/dsp/downchannelizer.h @@ -23,12 +23,7 @@ #include #include "export.h" #include "util/message.h" - -#ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfiltereo2.h" -#else // SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfiltereo1.h" -#endif // SDR_RX_SAMPLE_24BIT +#include "dsp/inthalfbandfiltereo.h" #define DOWNCHANNELIZER_HB_FILTER_ORDER 48 @@ -81,11 +76,11 @@ protected: }; #ifdef SDR_RX_SAMPLE_24BIT - typedef bool (IntHalfbandFilterEO2::*WorkFunction)(Sample* s); - IntHalfbandFilterEO2* m_filter; + typedef bool (IntHalfbandFilterEO::*WorkFunction)(Sample* s); + IntHalfbandFilterEO* m_filter; #else - typedef bool (IntHalfbandFilterEO1::*WorkFunction)(Sample* s); - IntHalfbandFilterEO1* m_filter; + typedef bool (IntHalfbandFilterEO::*WorkFunction)(Sample* s); + IntHalfbandFilterEO* m_filter; #endif WorkFunction m_workFunction; From 4bb63bbf1bbeff28beac9b4f2fa9234eab0f144a Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 8 May 2018 10:10:15 +0200 Subject: [PATCH 366/956] Fixed keyboard input for negative values on realtive integer value dials (issue #168) --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 ++++++ sdrgui/gui/valuedialz.cpp | 13 +++++++++---- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index e405a11df..964e327ed 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.14.5"); + QCoreApplication::setApplicationVersion("3.14.6"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 74adea9bc..4c9c1554d 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("3.14.5"); + QCoreApplication::setApplicationVersion("3.14.6"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index d0fbbdfac..298db8ea7 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.14.5"); + QCoreApplication::setApplicationVersion("3.14.6"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index dc80e9307..7c816084a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (3.14.6-1) unstable; urgency=medium + + * Fixed keyboard input for negative values on realtive integer value dials + + -- Edouard Griffiths, F4EXB Sun, 13 May 2018 20:14:18 +0200 + sdrangel (3.14.5-1) unstable; urgency=medium * DSD demod: allow audio rates integer multiples of 8k other than 48k diff --git a/sdrgui/gui/valuedialz.cpp b/sdrgui/gui/valuedialz.cpp index 004000977..d8c74f342 100644 --- a/sdrgui/gui/valuedialz.cpp +++ b/sdrgui/gui/valuedialz.cpp @@ -599,12 +599,17 @@ void ValueDialZ::keyPressEvent(QKeyEvent* value) { int d = c.toLatin1() - '0'; quint64 e = findExponent(m_cursor); - quint64 v = (m_value / e) % 10; - if(m_animationState != 0) + quint64 value = abs(m_value); + int sign = m_value < 0 ? -1 : 1; + quint64 v = (value / e) % 10; + + if(m_animationState != 0) { m_value = m_valueNew; - v = m_value - v * e; + } + + v = value - v * e; v += d * e; - setValue(v); + setValue(sign*v); emit changed(m_valueNew); m_cursor++; From 0981d04904e6902511a09505235e3d0a54cece1c Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 8 May 2018 11:03:09 +0200 Subject: [PATCH 367/956] File record default file name fix (1) --- plugins/samplesource/airspy/airspyinput.cpp | 5 +---- plugins/samplesource/airspyhf/airspyhfinput.cpp | 5 +---- .../samplesource/bladerfinput/bladerfinput.cpp | 5 +---- plugins/samplesource/fcdpro/fcdproinput.cpp | 5 +---- .../samplesource/fcdproplus/fcdproplusinput.cpp | 5 +---- .../samplesource/hackrfinput/hackrfinput.cpp | 17 ++++++++++++----- .../hackrfinput/hackrfinputsettings.cpp | 1 + .../hackrfinput/hackrfinputsettings.h | 2 ++ .../samplesource/limesdrinput/limesdrinput.cpp | 4 +--- plugins/samplesource/perseus/perseusinput.cpp | 4 +--- .../plutosdrinput/plutosdrinput.cpp | 4 +--- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 4 +--- .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 4 +--- plugins/samplesource/sdrplay/sdrplayinput.cpp | 5 +---- .../samplesource/testsource/testsourceinput.cpp | 4 +--- sdrbase/dsp/filerecord.cpp | 12 ++++++------ sdrbase/dsp/filerecord.h | 8 ++++---- 17 files changed, 37 insertions(+), 57 deletions(-) diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 586cf15c0..2266b09f8 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -48,10 +48,7 @@ AirspyInput::AirspyInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index 564775a79..c817050d5 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -50,10 +50,7 @@ AirspyHFInput::AirspyHFInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 3837f57d8..76f6f8766 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -45,10 +45,7 @@ BladerfInput::BladerfInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); m_deviceAPI->setBuddySharedPtr(&m_sharedParams); diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index d7ab49f11..e84ec0121 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -49,10 +49,7 @@ FCDProInput::FCDProInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index 2aeeda9bb..37d658879 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -48,10 +48,7 @@ FCDProPlusInput::FCDProPlusInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index e77151439..2559fabc7 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -49,9 +49,7 @@ HackRFInput::HackRFInput(DeviceSourceAPI *deviceAPI) : { openDevice(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); m_deviceAPI->setBuddySharedPtr(&m_sharedParams); @@ -260,9 +258,18 @@ bool HackRFInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "HackRFInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec_%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/hackrfinput/hackrfinputsettings.cpp b/plugins/samplesource/hackrfinput/hackrfinputsettings.cpp index 67c667cdc..d2831bcc1 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputsettings.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputsettings.cpp @@ -40,6 +40,7 @@ void HackRFInputSettings::resetToDefaults() m_iqCorrection = false; m_devSampleRate = 2400000; m_linkTxFrequency = false; + m_fileRecordName = ""; } QByteArray HackRFInputSettings::serialize() const diff --git a/plugins/samplesource/hackrfinput/hackrfinputsettings.h b/plugins/samplesource/hackrfinput/hackrfinputsettings.h index 2a5f4ed22..b2f0b3056 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputsettings.h +++ b/plugins/samplesource/hackrfinput/hackrfinputsettings.h @@ -18,6 +18,7 @@ #define _HACKRF_HACKRFINPUTSETTINGS_H_ #include +#include struct HackRFInputSettings { typedef enum { @@ -39,6 +40,7 @@ struct HackRFInputSettings { bool m_dcBlock; bool m_iqCorrection; bool m_linkTxFrequency; + QString m_fileRecordName; HackRFInputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 05c5f15c5..07bf4222c 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -57,9 +57,7 @@ LimeSDRInput::LimeSDRInput(DeviceSourceAPI *deviceAPI) : resumeTxBuddies(); resumeRxBuddies(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/perseus/perseusinput.cpp b/plugins/samplesource/perseus/perseusinput.cpp index d3e933416..ad9b7e889 100644 --- a/plugins/samplesource/perseus/perseusinput.cpp +++ b/plugins/samplesource/perseus/perseusinput.cpp @@ -41,9 +41,7 @@ PerseusInput::PerseusInput(DeviceSourceAPI *deviceAPI) : m_perseusDescriptor(0) { openDevice(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index eb786ea7e..0f977ce6e 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -55,9 +55,7 @@ PlutoSDRInput::PlutoSDRInput(DeviceSourceAPI *deviceAPI) : openDevice(); resumeBuddies(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index a9d3a5b9b..9278ff555 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -53,9 +53,7 @@ RTLSDRInput::RTLSDRInput(DeviceSourceAPI *deviceAPI) : { openDevice(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 5652419c6..d38695119 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -63,9 +63,7 @@ SDRdaemonSourceInput::SDRdaemonSourceInput(DeviceSourceAPI *deviceAPI) : m_sampleFifo.setSize(96000 * 4); m_SDRdaemonUDPHandler = new SDRdaemonSourceUDPHandler(&m_sampleFifo, m_deviceAPI); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index 64af98d9f..d8d86e4f0 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -48,10 +48,7 @@ SDRPlayInput::SDRPlayInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index b6516090c..4ac79a456 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -41,9 +41,7 @@ TestSourceInput::TestSourceInput(DeviceSourceAPI *deviceAPI) : m_running(false), m_masterTimer(deviceAPI->getMasterTimer()) { - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); if (!m_sampleFifo.setSize(96000 * 4)) { diff --git a/sdrbase/dsp/filerecord.cpp b/sdrbase/dsp/filerecord.cpp index 82fc684c8..39e947a03 100644 --- a/sdrbase/dsp/filerecord.cpp +++ b/sdrbase/dsp/filerecord.cpp @@ -7,7 +7,7 @@ FileRecord::FileRecord() : BasebandSampleSink(), - m_fileName(std::string("test.sdriq")), + m_fileName("test.sdriq"), m_sampleRate(0), m_centerFrequency(0), m_recordOn(false), @@ -17,9 +17,9 @@ FileRecord::FileRecord() : setObjectName("FileSink"); } -FileRecord::FileRecord(const std::string& filename) : +FileRecord::FileRecord(const QString& filename) : BasebandSampleSink(), - m_fileName(std::string(filename)), + m_fileName(filename), m_sampleRate(0), m_centerFrequency(0), m_recordOn(false), @@ -34,7 +34,7 @@ FileRecord::~FileRecord() stopRecording(); } -void FileRecord::setFileName(const std::string& filename) +void FileRecord::setFileName(const QString& filename) { if (!m_recordOn) { @@ -75,7 +75,7 @@ void FileRecord::startRecording() if (!m_sampleFile.is_open()) { qDebug() << "FileRecord::startRecording"; - m_sampleFile.open(m_fileName.c_str(), std::ios::binary); + m_sampleFile.open(m_fileName.toStdString().c_str(), std::ios::binary); m_recordOn = true; m_recordStart = true; m_byteCount = 0; @@ -110,7 +110,7 @@ bool FileRecord::handleMessage(const Message& message) } } -void FileRecord::handleConfigure(const std::string& fileName) +void FileRecord::handleConfigure(const QString& fileName) { if (fileName != m_fileName) { diff --git a/sdrbase/dsp/filerecord.h b/sdrbase/dsp/filerecord.h index 9272d07a7..00840c469 100644 --- a/sdrbase/dsp/filerecord.h +++ b/sdrbase/dsp/filerecord.h @@ -23,12 +23,12 @@ public: }; FileRecord(); - FileRecord(const std::string& filename); + FileRecord(const QString& filename); virtual ~FileRecord(); quint64 getByteCount() const { return m_byteCount; } - void setFileName(const std::string& filename); + void setFileName(const QString& filename); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); @@ -39,7 +39,7 @@ public: static void readHeader(std::ifstream& samplefile, Header& header); private: - std::string m_fileName; + QString m_fileName; qint32 m_sampleRate; quint64 m_centerFrequency; bool m_recordOn; @@ -47,7 +47,7 @@ private: std::ofstream m_sampleFile; quint64 m_byteCount; - void handleConfigure(const std::string& fileName); + void handleConfigure(const QString& fileName); void writeHeader(); }; From 1ee75f127d72849a34795b4706bb0c1e0a369502 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 9 May 2018 09:57:26 +0200 Subject: [PATCH 368/956] SDRDaemon source: fixed UDP socket readyRead signal connection (removed queued connection flag) --- .../samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index 5110e75ea..5577e55d5 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -96,7 +96,7 @@ void SDRdaemonSourceUDPHandler::start() if (!m_dataConnected) { - connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()), Qt::QueuedConnection); // , Qt::QueuedConnection + connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead())); //, Qt::QueuedConnection); if (m_dataSocket->bind(m_dataAddress, m_dataPort)) { From 2d57d6d7e0dd7c18e03a3074a193243cbf51bea3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 9 May 2018 17:05:17 +0200 Subject: [PATCH 369/956] Added mention of Ubuntu 18.04 powersave CPU governor being now the default --- Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Readme.md b/Readme.md index f3a7cacb2..0bdfb47d0 100644 --- a/Readme.md +++ b/Readme.md @@ -295,6 +295,10 @@ The software is installed in `/opt/sdrangel` you can start it from the command l - `/opt/sdrangel/bin/sdrangel` **⚠** The udev rules are not set by the package installation so you will have to set it manually in order to be able to access the various SDR hardware. The `udev-rules` folder contains the rules file and the `install.sh` script that you can run as sudo to install all rules files. You may also adapt the script to copy only the required files. + +

Ubuntu 18.04

+ +The default CPU governor is now `powersave` which exhibits excessive CPU usage when running SDRangel. In the case of benchmarking and maybe high throughput usage it is recommended to switch to `performance` before running SDRangel by running the command: `sudo cpupower frequency-set --governor performance`. You can turn it back to `powersave` any time by running: `sudo cpupower frequency-set --governor powersave`. It is normal that with a lower CPU frequency the relative CPU usage rises for the same actual load. If not impairing operation this is normal and overall beneficial for heat and power consumption.

Windows distribution

From 775b2a270de308be7200035d60cc7c43dd5cb81a Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 9 May 2018 17:39:48 +0200 Subject: [PATCH 370/956] File record default file name fix (2) --- fcdlib/fcdtraits.cpp | 4 ++-- plugins/samplesource/airspy/airspyinput.cpp | 13 +++++++++++-- plugins/samplesource/airspy/airspyplugin.cpp | 2 +- plugins/samplesource/airspy/airspysettings.cpp | 1 + plugins/samplesource/airspy/airspysettings.h | 3 +++ plugins/samplesource/airspyhf/airspyhfinput.cpp | 13 +++++++++++-- plugins/samplesource/airspyhf/airspyhfplugin.cpp | 2 +- plugins/samplesource/airspyhf/airspyhfsettings.cpp | 1 + plugins/samplesource/airspyhf/airspyhfsettings.h | 3 +++ plugins/samplesource/bladerfinput/bladerfinput.cpp | 13 +++++++++++-- .../bladerfinput/bladerfinputplugin.cpp | 2 +- .../bladerfinput/bladerfinputsettings.cpp | 1 + .../bladerfinput/bladerfinputsettings.h | 2 ++ plugins/samplesource/fcdpro/fcdproinput.cpp | 13 +++++++++++-- plugins/samplesource/fcdpro/fcdprosettings.cpp | 1 + plugins/samplesource/fcdpro/fcdprosettings.h | 3 +++ plugins/samplesource/fcdproplus/fcdproplusinput.cpp | 13 +++++++++++-- .../samplesource/fcdproplus/fcdproplussettings.cpp | 1 + .../samplesource/fcdproplus/fcdproplussettings.h | 3 +++ plugins/samplesource/hackrfinput/hackrfinput.cpp | 2 +- .../samplesource/hackrfinput/hackrfinputplugin.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinput.cpp | 13 +++++++++++-- .../limesdrinput/limesdrinputplugin.cpp | 2 +- .../limesdrinput/limesdrinputsettings.cpp | 1 + .../limesdrinput/limesdrinputsettings.h | 2 ++ plugins/samplesource/perseus/perseusinput.cpp | 13 +++++++++++-- plugins/samplesource/perseus/perseusplugin.cpp | 2 +- plugins/samplesource/perseus/perseussettings.cpp | 1 + plugins/samplesource/perseus/perseussettings.h | 2 ++ .../samplesource/plutosdrinput/plutosdrinput.cpp | 13 +++++++++++-- .../plutosdrinput/plutosdrinputplugin.cpp | 2 +- .../plutosdrinput/plutosdrinputsettings.cpp | 1 + .../plutosdrinput/plutosdrinputsettings.h | 3 ++- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 13 +++++++++++-- plugins/samplesource/rtlsdr/rtlsdrplugin.cpp | 2 +- plugins/samplesource/rtlsdr/rtlsdrsettings.cpp | 1 + plugins/samplesource/rtlsdr/rtlsdrsettings.h | 3 +++ .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 13 +++++++++++-- .../sdrdaemonsource/sdrdaemonsourceplugin.cpp | 2 +- .../sdrdaemonsource/sdrdaemonsourcesettings.cpp | 1 + .../sdrdaemonsource/sdrdaemonsourcesettings.h | 1 + plugins/samplesource/sdrplay/sdrplayinput.cpp | 13 +++++++++++-- plugins/samplesource/sdrplay/sdrplayplugin.cpp | 2 +- plugins/samplesource/sdrplay/sdrplaysettings.cpp | 1 + plugins/samplesource/sdrplay/sdrplaysettings.h | 2 ++ plugins/samplesource/testsource/testsourceinput.cpp | 13 +++++++++++-- .../samplesource/testsource/testsourceplugin.cpp | 2 +- .../samplesource/testsource/testsourcesettings.cpp | 1 + .../samplesource/testsource/testsourcesettings.h | 3 +++ 49 files changed, 187 insertions(+), 39 deletions(-) diff --git a/fcdlib/fcdtraits.cpp b/fcdlib/fcdtraits.cpp index 01fd745eb..9c5ea4af1 100644 --- a/fcdlib/fcdtraits.cpp +++ b/fcdlib/fcdtraits.cpp @@ -22,8 +22,8 @@ const char *fcd_traits::displayedName = "FunCube Dongle Pro+"; const char *fcd_traits::pluginDisplayedName = "FunCube Pro Input"; const char *fcd_traits::pluginDisplayedName = "FunCube Pro+ Input"; -const char *fcd_traits::pluginVersion = "3.14.5"; -const char *fcd_traits::pluginVersion = "3.14.5"; +const char *fcd_traits::pluginVersion = "3.14.6"; +const char *fcd_traits::pluginVersion = "3.14.6"; const int64_t fcd_traits::loLowLimitFreq = 64000000L; const int64_t fcd_traits::loLowLimitFreq = 150000L; diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 2266b09f8..9fd0381c1 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -303,9 +303,18 @@ bool AirspyInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "AirspyInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/airspy/airspyplugin.cpp b/plugins/samplesource/airspy/airspyplugin.cpp index 6d1ee85f7..439cf73c8 100644 --- a/plugins/samplesource/airspy/airspyplugin.cpp +++ b/plugins/samplesource/airspy/airspyplugin.cpp @@ -29,7 +29,7 @@ const int AirspyPlugin::m_maxDevices = 32; const PluginDescriptor AirspyPlugin::m_pluginDescriptor = { QString("Airspy Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/airspy/airspysettings.cpp b/plugins/samplesource/airspy/airspysettings.cpp index ef8dbb1a0..d1c7d786b 100644 --- a/plugins/samplesource/airspy/airspysettings.cpp +++ b/plugins/samplesource/airspy/airspysettings.cpp @@ -40,6 +40,7 @@ void AirspySettings::resetToDefaults() m_iqCorrection = false; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray AirspySettings::serialize() const diff --git a/plugins/samplesource/airspy/airspysettings.h b/plugins/samplesource/airspy/airspysettings.h index 4e42fe061..b8daf0ea0 100644 --- a/plugins/samplesource/airspy/airspysettings.h +++ b/plugins/samplesource/airspy/airspysettings.h @@ -17,6 +17,8 @@ #ifndef _AIRSPY_AIRSPYSETTINGS_H_ #define _AIRSPY_AIRSPYSETTINGS_H_ +#include + struct AirspySettings { typedef enum { FC_POS_INFRA = 0, @@ -39,6 +41,7 @@ struct AirspySettings { bool m_iqCorrection; bool m_transverterMode; qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; AirspySettings(); void resetToDefaults(); diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index c817050d5..0fb3b3fdc 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -320,9 +320,18 @@ bool AirspyHFInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "AirspyHFInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/airspyhf/airspyhfplugin.cpp b/plugins/samplesource/airspyhf/airspyhfplugin.cpp index 2bdd3c46e..8612607ac 100644 --- a/plugins/samplesource/airspyhf/airspyhfplugin.cpp +++ b/plugins/samplesource/airspyhf/airspyhfplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor AirspyHFPlugin::m_pluginDescriptor = { QString("AirspyHF Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/airspyhf/airspyhfsettings.cpp b/plugins/samplesource/airspyhf/airspyhfsettings.cpp index 46818093f..a0971776c 100644 --- a/plugins/samplesource/airspyhf/airspyhfsettings.cpp +++ b/plugins/samplesource/airspyhf/airspyhfsettings.cpp @@ -33,6 +33,7 @@ void AirspyHFSettings::resetToDefaults() m_transverterMode = false; m_transverterDeltaFrequency = 0; m_bandIndex = 0; + m_fileRecordName = ""; } QByteArray AirspyHFSettings::serialize() const diff --git a/plugins/samplesource/airspyhf/airspyhfsettings.h b/plugins/samplesource/airspyhf/airspyhfsettings.h index b806b59eb..937355d96 100644 --- a/plugins/samplesource/airspyhf/airspyhfsettings.h +++ b/plugins/samplesource/airspyhf/airspyhfsettings.h @@ -17,6 +17,8 @@ #ifndef _AIRSPYHFF_AIRSPYHFSETTINGS_H_ #define _AIRSPYHFF_AIRSPYHFSETTINGS_H_ +#include + struct AirspyHFSettings { quint64 m_centerFrequency; @@ -26,6 +28,7 @@ struct AirspyHFSettings bool m_transverterMode; qint64 m_transverterDeltaFrequency; quint32 m_bandIndex; + QString m_fileRecordName; AirspyHFSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 76f6f8766..68b35b570 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -275,9 +275,18 @@ bool BladerfInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "BladerfInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index e93a56375..043b7a4b1 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { QString("BladeRF Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/bladerfinput/bladerfinputsettings.cpp b/plugins/samplesource/bladerfinput/bladerfinputsettings.cpp index 5976dd6f8..988e18647 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputsettings.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputsettings.cpp @@ -40,6 +40,7 @@ void BladeRFInputSettings::resetToDefaults() m_xb200Filter = BLADERF_XB200_AUTO_1DB; m_dcBlock = false; m_iqCorrection = false; + m_fileRecordName = ""; } QByteArray BladeRFInputSettings::serialize() const diff --git a/plugins/samplesource/bladerfinput/bladerfinputsettings.h b/plugins/samplesource/bladerfinput/bladerfinputsettings.h index 7d18fa987..c5f64b6aa 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputsettings.h +++ b/plugins/samplesource/bladerfinput/bladerfinputsettings.h @@ -18,6 +18,7 @@ #define _BLADERF_BLADERFINPUTSETTINGS_H_ #include +#include #include struct BladeRFInputSettings { @@ -40,6 +41,7 @@ struct BladeRFInputSettings { bladerf_xb200_filter m_xb200Filter; bool m_dcBlock; bool m_iqCorrection; + QString m_fileRecordName; BladeRFInputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index e84ec0121..2660f2bfc 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -245,9 +245,18 @@ bool FCDProInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "FCDProInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/fcdpro/fcdprosettings.cpp b/plugins/samplesource/fcdpro/fcdprosettings.cpp index 88f19bc73..65c92133a 100644 --- a/plugins/samplesource/fcdpro/fcdprosettings.cpp +++ b/plugins/samplesource/fcdpro/fcdprosettings.cpp @@ -47,6 +47,7 @@ void FCDProSettings::resetToDefaults() m_gain6Index = 0; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray FCDProSettings::serialize() const diff --git a/plugins/samplesource/fcdpro/fcdprosettings.h b/plugins/samplesource/fcdpro/fcdprosettings.h index 59f4e3860..f4972717d 100644 --- a/plugins/samplesource/fcdpro/fcdprosettings.h +++ b/plugins/samplesource/fcdpro/fcdprosettings.h @@ -17,6 +17,8 @@ #ifndef _FCDPRO_FCDPROSETTINGS_H_ #define _FCDPRO_FCDPROSETTINGS_H_ +#include + struct FCDProSettings { quint64 m_centerFrequency; qint32 m_LOppmTenths; @@ -40,6 +42,7 @@ struct FCDProSettings { bool m_iqCorrection; bool m_transverterMode; qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; FCDProSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index 37d658879..b6dee89dc 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -239,9 +239,18 @@ bool FCDProPlusInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "FCDProPlusInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/fcdproplus/fcdproplussettings.cpp b/plugins/samplesource/fcdproplus/fcdproplussettings.cpp index 15b7abcd2..59d10c2d1 100644 --- a/plugins/samplesource/fcdproplus/fcdproplussettings.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplussettings.cpp @@ -38,6 +38,7 @@ void FCDProPlusSettings::resetToDefaults() m_iqImbalance = false; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray FCDProPlusSettings::serialize() const diff --git a/plugins/samplesource/fcdproplus/fcdproplussettings.h b/plugins/samplesource/fcdproplus/fcdproplussettings.h index b0978fb17..f4fe21c3d 100644 --- a/plugins/samplesource/fcdproplus/fcdproplussettings.h +++ b/plugins/samplesource/fcdproplus/fcdproplussettings.h @@ -17,6 +17,8 @@ #ifndef _FCDPROPLUS_FCDPROPLUSSETTINGS_H_ #define _FCDPROPLUS_FCDPROPLUSSETTINGS_H_ +#include + struct FCDProPlusSettings { quint64 m_centerFrequency; bool m_rangeLow; @@ -31,6 +33,7 @@ struct FCDProPlusSettings { bool m_iqImbalance; bool m_transverterMode; qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; FCDProPlusSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index 2559fabc7..eab536bc7 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -263,7 +263,7 @@ bool HackRFInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec_%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp b/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp index dadc4629f..3ac127c62 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp @@ -32,7 +32,7 @@ const PluginDescriptor HackRFInputPlugin::m_pluginDescriptor = { QString("HackRF Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 07bf4222c..6bd12e525 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -687,9 +687,18 @@ bool LimeSDRInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "LimeSDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 50765becd..38066caf6 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp b/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp index 34f053d3b..88383e328 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp @@ -45,6 +45,7 @@ void LimeSDRInputSettings::resetToDefaults() m_extClockFreq = 10000000; // 10 MHz m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray LimeSDRInputSettings::serialize() const diff --git a/plugins/samplesource/limesdrinput/limesdrinputsettings.h b/plugins/samplesource/limesdrinput/limesdrinputsettings.h index a82638954..9287589a2 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputsettings.h +++ b/plugins/samplesource/limesdrinput/limesdrinputsettings.h @@ -18,6 +18,7 @@ #define PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUTSETTINGS_H_ #include +#include #include /** @@ -64,6 +65,7 @@ struct LimeSDRInputSettings uint32_t m_extClockFreq; //!< Frequency (Hz) of external clock source bool m_transverterMode; qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; LimeSDRInputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/perseus/perseusinput.cpp b/plugins/samplesource/perseus/perseusinput.cpp index ad9b7e889..8d8a80452 100644 --- a/plugins/samplesource/perseus/perseusinput.cpp +++ b/plugins/samplesource/perseus/perseusinput.cpp @@ -192,9 +192,18 @@ bool PerseusInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "PerseusInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/perseus/perseusplugin.cpp b/plugins/samplesource/perseus/perseusplugin.cpp index 241bd3171..227b875e2 100644 --- a/plugins/samplesource/perseus/perseusplugin.cpp +++ b/plugins/samplesource/perseus/perseusplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor PerseusPlugin::m_pluginDescriptor = { QString("Perseus Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/perseus/perseussettings.cpp b/plugins/samplesource/perseus/perseussettings.cpp index 77a57a83f..5ae31d97d 100644 --- a/plugins/samplesource/perseus/perseussettings.cpp +++ b/plugins/samplesource/perseus/perseussettings.cpp @@ -35,6 +35,7 @@ void PerseusSettings::resetToDefaults() m_adcPreamp = false; m_wideBand = false; m_attenuator = Attenuator_None; + m_fileRecordName = ""; } QByteArray PerseusSettings::serialize() const diff --git a/plugins/samplesource/perseus/perseussettings.h b/plugins/samplesource/perseus/perseussettings.h index 864bb0cec..1330e2747 100644 --- a/plugins/samplesource/perseus/perseussettings.h +++ b/plugins/samplesource/perseus/perseussettings.h @@ -18,6 +18,7 @@ #define PLUGINS_SAMPLESOURCE_PERSEUS_PERSEUSSETTINGS_H_ #include +#include struct PerseusSettings { @@ -40,6 +41,7 @@ struct PerseusSettings bool m_adcPreamp; bool m_wideBand; Attenuator m_attenuator; + QString m_fileRecordName; PerseusSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index 0f977ce6e..9cb8827a8 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -190,9 +190,18 @@ bool PlutoSDRInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "PlutoSDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp index 189591b5d..93e14c5ba 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp @@ -28,7 +28,7 @@ class DeviceSourceAPI; const PluginDescriptor PlutoSDRInputPlugin::m_pluginDescriptor = { QString("PlutoSDR Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputsettings.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputsettings.cpp index c90e7a630..556977d75 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputsettings.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputsettings.cpp @@ -44,6 +44,7 @@ void PlutoSDRInputSettings::resetToDefaults() m_gainMode = GAIN_MANUAL; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray PlutoSDRInputSettings::serialize() const diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputsettings.h b/plugins/samplesource/plutosdrinput/plutosdrinputsettings.h index 19d4793b2..691161181 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputsettings.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinputsettings.h @@ -18,6 +18,7 @@ #define _PLUTOSDR_PLUTOSDRINPUTSETTINGS_H_ #include +#include #include struct PlutoSDRInputSettings { @@ -73,7 +74,7 @@ struct PlutoSDRInputSettings { GainMode m_gainMode; bool m_transverterMode; qint64 m_transverterDeltaFrequency; - + QString m_fileRecordName; PlutoSDRInputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index 9278ff555..f08101d03 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -304,9 +304,18 @@ bool RTLSDRInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "RTLSDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp index a952d8063..bf9ed0ddc 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp @@ -14,7 +14,7 @@ const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = { QString("RTL-SDR Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/rtlsdr/rtlsdrsettings.cpp b/plugins/samplesource/rtlsdr/rtlsdrsettings.cpp index 479fa6c96..6b5fd6ead 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrsettings.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrsettings.cpp @@ -39,6 +39,7 @@ void RTLSDRSettings::resetToDefaults() m_transverterMode = false; m_transverterDeltaFrequency = 0; m_rfBandwidth = 2500 * 1000; // Hz + m_fileRecordName = ""; } QByteArray RTLSDRSettings::serialize() const diff --git a/plugins/samplesource/rtlsdr/rtlsdrsettings.h b/plugins/samplesource/rtlsdr/rtlsdrsettings.h index 32303eff5..bf2ad2c60 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrsettings.h +++ b/plugins/samplesource/rtlsdr/rtlsdrsettings.h @@ -17,6 +17,8 @@ #ifndef _RTLSDR_RTLSDRSETTINGS_H_ #define _RTLSDR_RTLSDRSETTINGS_H_ +#include + struct RTLSDRSettings { typedef enum { FC_POS_INFRA = 0, @@ -38,6 +40,7 @@ struct RTLSDRSettings { bool m_transverterMode; qint64 m_transverterDeltaFrequency; quint32 m_rfBandwidth; //!< RF filter bandwidth in Hz + QString m_fileRecordName; RTLSDRSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index d38695119..d8af205df 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -185,9 +185,18 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "SDRdaemonSourceInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp index fa46eeb2d..7023c4acf 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp @@ -26,7 +26,7 @@ const PluginDescriptor SDRdaemonSourcePlugin::m_pluginDescriptor = { QString("SDRdaemon source input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp index 81da4ca8a..fccb2981d 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp @@ -36,6 +36,7 @@ void SDRdaemonSourceSettings::resetToDefaults() m_dcBlock = false; m_iqCorrection = false; m_fcPos = 2; // center + m_fileRecordName = ""; } QByteArray SDRdaemonSourceSettings::serialize() const diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h index 53a2a2bb0..d9907a92d 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h @@ -33,6 +33,7 @@ struct SDRdaemonSourceSettings { bool m_dcBlock; bool m_iqCorrection; quint32 m_fcPos; + QString m_fileRecordName; SDRdaemonSourceSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index d8d86e4f0..46292c333 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -299,9 +299,18 @@ bool SDRPlayInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "SDRPlayInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/sdrplay/sdrplayplugin.cpp b/plugins/samplesource/sdrplay/sdrplayplugin.cpp index aebdd2072..6628540a8 100644 --- a/plugins/samplesource/sdrplay/sdrplayplugin.cpp +++ b/plugins/samplesource/sdrplay/sdrplayplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor SDRPlayPlugin::m_pluginDescriptor = { QString("SDRPlay RSP1 Input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/sdrplay/sdrplaysettings.cpp b/plugins/samplesource/sdrplay/sdrplaysettings.cpp index 201cd314c..8585f54c5 100644 --- a/plugins/samplesource/sdrplay/sdrplaysettings.cpp +++ b/plugins/samplesource/sdrplay/sdrplaysettings.cpp @@ -41,6 +41,7 @@ void SDRPlaySettings::resetToDefaults() m_lnaOn = false; m_mixerAmpOn = false; m_basebandGain = 29; + m_fileRecordName = ""; } QByteArray SDRPlaySettings::serialize() const diff --git a/plugins/samplesource/sdrplay/sdrplaysettings.h b/plugins/samplesource/sdrplay/sdrplaysettings.h index 8c7b06fd1..36b048928 100644 --- a/plugins/samplesource/sdrplay/sdrplaysettings.h +++ b/plugins/samplesource/sdrplay/sdrplaysettings.h @@ -19,6 +19,7 @@ #include #include +#include #include struct SDRPlaySettings { @@ -43,6 +44,7 @@ struct SDRPlaySettings { bool m_lnaOn; bool m_mixerAmpOn; int m_basebandGain; + QString m_fileRecordName; SDRPlaySettings(); void resetToDefaults(); diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 4ac79a456..4c247fe14 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -177,9 +177,18 @@ bool TestSourceInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "RTLSDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } diff --git a/plugins/samplesource/testsource/testsourceplugin.cpp b/plugins/samplesource/testsource/testsourceplugin.cpp index 6ad8bbea4..6245ec0f9 100644 --- a/plugins/samplesource/testsource/testsourceplugin.cpp +++ b/plugins/samplesource/testsource/testsourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor TestSourcePlugin::m_pluginDescriptor = { QString("Test Source input"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/testsource/testsourcesettings.cpp b/plugins/samplesource/testsource/testsourcesettings.cpp index a78d81f92..65e1b10b2 100644 --- a/plugins/samplesource/testsource/testsourcesettings.cpp +++ b/plugins/samplesource/testsource/testsourcesettings.cpp @@ -41,6 +41,7 @@ void TestSourceSettings::resetToDefaults() m_iFactor = 0.0f; m_qFactor = 0.0f; m_phaseImbalance = 0.0f; + m_fileRecordName = ""; } QByteArray TestSourceSettings::serialize() const diff --git a/plugins/samplesource/testsource/testsourcesettings.h b/plugins/samplesource/testsource/testsourcesettings.h index d4c476703..6d39c22cf 100644 --- a/plugins/samplesource/testsource/testsourcesettings.h +++ b/plugins/samplesource/testsource/testsourcesettings.h @@ -17,6 +17,8 @@ #ifndef _TESTSOURCE_TESTSOURCESETTINGS_H_ #define _TESTSOURCE_TESTSOURCESETTINGS_H_ +#include + struct TestSourceSettings { typedef enum { FC_POS_INFRA = 0, @@ -54,6 +56,7 @@ struct TestSourceSettings { float m_iFactor; //!< -1.0 < x < 1.0 float m_qFactor; //!< -1.0 < x < 1.0 float m_phaseImbalance; //!< -1.0 < x < 1.0 + QString m_fileRecordName; TestSourceSettings(); void resetToDefaults(); From 625f0eb72c2ccafdf98558e21a5906e7824acdb0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 9 May 2018 18:59:39 +0200 Subject: [PATCH 371/956] File record default file name fix (3): web API --- .../samplesource/airspyhf/airspyhfinput.cpp | 9 ++++++++ .../bladerfinput/bladerfinput.cpp | 9 ++++++++ .../samplesource/hackrfinput/hackrfinput.cpp | 9 ++++++++ .../limesdrinput/limesdrinput.cpp | 9 ++++++++ plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 9 ++++++++ .../api/swagger/include/AirspyHF.yaml | 2 ++ .../sdrangel/api/swagger/include/BladeRF.yaml | 2 ++ .../sdrangel/api/swagger/include/HackRF.yaml | 2 ++ .../sdrangel/api/swagger/include/LimeSdr.yaml | 2 ++ .../sdrangel/api/swagger/include/RtlSdr.yaml | 2 ++ swagger/sdrangel/code/html2/index.html | 17 +++++++++++++- .../code/qt5/client/SWGAirspyHFSettings.cpp | 23 +++++++++++++++++++ .../code/qt5/client/SWGAirspyHFSettings.h | 7 ++++++ .../qt5/client/SWGBladeRFInputSettings.cpp | 23 +++++++++++++++++++ .../code/qt5/client/SWGBladeRFInputSettings.h | 7 ++++++ .../qt5/client/SWGHackRFInputSettings.cpp | 23 +++++++++++++++++++ .../code/qt5/client/SWGHackRFInputSettings.h | 7 ++++++ .../qt5/client/SWGLimeSdrInputSettings.cpp | 23 +++++++++++++++++++ .../code/qt5/client/SWGLimeSdrInputSettings.h | 7 ++++++ .../code/qt5/client/SWGRtlSdrSettings.cpp | 23 +++++++++++++++++++ .../code/qt5/client/SWGRtlSdrSettings.h | 7 ++++++ 21 files changed, 221 insertions(+), 1 deletion(-) diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index 0fb3b3fdc..a8fb0f1d3 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -527,6 +527,9 @@ int AirspyHFInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("bandIndex")) { settings.m_bandIndex = response.getAirspyHfSettings()->getBandIndex() != 0; } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getAirspyHfSettings()->getFileRecordName(); + } MsgConfigureAirspyHF *msg = MsgConfigureAirspyHF::create(settings, force); m_inputMessageQueue.push(msg); @@ -550,6 +553,12 @@ void AirspyHFInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& r response.getAirspyHfSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); response.getAirspyHfSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); response.getAirspyHfSettings()->setBandIndex(settings.m_bandIndex ? 1 : 0); + + if (response.getAirspyHfSettings()->getFileRecordName()) { + *response.getAirspyHfSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getAirspyHfSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } } int AirspyHFInput::webapiRunGet( diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 68b35b570..1a9f5ef1a 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -639,6 +639,12 @@ void BladerfInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& re response.getBladeRfInputSettings()->setXb200Filter((int) settings.m_xb200Filter); response.getBladeRfInputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); response.getBladeRfInputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + + if (response.getBladeRfInputSettings()->getFileRecordName()) { + *response.getBladeRfInputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getBladeRfInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } } int BladerfInput::webapiSettingsPutPatch( @@ -688,6 +694,9 @@ int BladerfInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("iqCorrection")) { settings.m_iqCorrection = response.getBladeRfInputSettings()->getIqCorrection() != 0; } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getBladeRfInputSettings()->getFileRecordName(); + } MsgConfigureBladerf *msg = MsgConfigureBladerf::create(settings, force); m_inputMessageQueue.push(msg); diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index eab536bc7..302c7a9d2 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -602,6 +602,9 @@ int HackRFInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("linkTxFrequency")) { settings.m_linkTxFrequency = response.getHackRfInputSettings()->getLinkTxFrequency() != 0; } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getHackRfInputSettings()->getFileRecordName(); + } MsgConfigureHackRF *msg = MsgConfigureHackRF::create(settings, force); m_inputMessageQueue.push(msg); @@ -631,6 +634,12 @@ void HackRFInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& res response.getHackRfInputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); response.getHackRfInputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); response.getHackRfInputSettings()->setLinkTxFrequency(settings.m_linkTxFrequency ? 1 : 0); + + if (response.getHackRfInputSettings()->getFileRecordName()) { + *response.getHackRfInputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getHackRfInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } } int HackRFInput::webapiRunGet( diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 6bd12e525..73aca76e5 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -1337,6 +1337,9 @@ int LimeSDRInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("transverterMode")) { settings.m_transverterMode = response.getLimeSdrInputSettings()->getTransverterMode() != 0; } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getLimeSdrInputSettings()->getFileRecordName(); + } MsgConfigureLimeSDR *msg = MsgConfigureLimeSDR::create(settings, force); m_inputMessageQueue.push(msg); @@ -1374,6 +1377,12 @@ void LimeSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& re response.getLimeSdrInputSettings()->setTiaGain(settings.m_tiaGain); response.getLimeSdrInputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); response.getLimeSdrInputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getLimeSdrInputSettings()->getFileRecordName()) { + *response.getLimeSdrInputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getLimeSdrInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } } int LimeSDRInput::webapiRunGet( diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index f08101d03..b442e9047 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -608,6 +608,9 @@ int RTLSDRInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("rfBandwidth")) { settings.m_rfBandwidth = response.getRtlSdrSettings()->getRfBandwidth() != 0; } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getRtlSdrSettings()->getFileRecordName(); + } MsgConfigureRTLSDR *msg = MsgConfigureRTLSDR::create(settings, force); m_inputMessageQueue.push(msg); @@ -638,6 +641,12 @@ void RTLSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& res response.getRtlSdrSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); response.getRtlSdrSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); response.getRtlSdrSettings()->setRfBandwidth(settings.m_rfBandwidth); + + if (response.getRtlSdrSettings()->getFileRecordName()) { + *response.getRtlSdrSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getRtlSdrSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } } int RTLSDRInput::webapiRunGet( diff --git a/swagger/sdrangel/api/swagger/include/AirspyHF.yaml b/swagger/sdrangel/api/swagger/include/AirspyHF.yaml index b041dc6bb..0ec9d4f93 100644 --- a/swagger/sdrangel/api/swagger/include/AirspyHF.yaml +++ b/swagger/sdrangel/api/swagger/include/AirspyHF.yaml @@ -17,4 +17,6 @@ AirspyHFSettings: format: int64 bandIndex: type: integer + fileRecordName: + type: string \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/BladeRF.yaml b/swagger/sdrangel/api/swagger/include/BladeRF.yaml index 8f75f9460..c46945406 100644 --- a/swagger/sdrangel/api/swagger/include/BladeRF.yaml +++ b/swagger/sdrangel/api/swagger/include/BladeRF.yaml @@ -28,6 +28,8 @@ BladeRFInputSettings: type: integer iqCorrection: type: integer + fileRecordName: + type: string BladeRFOutputSettings: description: BladeRF diff --git a/swagger/sdrangel/api/swagger/include/HackRF.yaml b/swagger/sdrangel/api/swagger/include/HackRF.yaml index 21278b932..a246ad7b5 100644 --- a/swagger/sdrangel/api/swagger/include/HackRF.yaml +++ b/swagger/sdrangel/api/swagger/include/HackRF.yaml @@ -29,6 +29,8 @@ HackRFInputSettings: type: integer linkTxFrequency: type: integer + fileRecordName: + type: string HackRFOutputSettings: description: HackRF diff --git a/swagger/sdrangel/api/swagger/include/LimeSdr.yaml b/swagger/sdrangel/api/swagger/include/LimeSdr.yaml index a043b00d8..6751d466c 100644 --- a/swagger/sdrangel/api/swagger/include/LimeSdr.yaml +++ b/swagger/sdrangel/api/swagger/include/LimeSdr.yaml @@ -45,6 +45,8 @@ LimeSdrInputSettings: transverterDeltaFrequency: type: integer format: int64 + fileRecordName: + type: string LimeSdrOutputSettings: description: LimeSDR diff --git a/swagger/sdrangel/api/swagger/include/RtlSdr.yaml b/swagger/sdrangel/api/swagger/include/RtlSdr.yaml index 16e7446bc..4d8cc5245 100644 --- a/swagger/sdrangel/api/swagger/include/RtlSdr.yaml +++ b/swagger/sdrangel/api/swagger/include/RtlSdr.yaml @@ -31,4 +31,6 @@ RtlSdrSettings: format: int64 rfBandwidth: type: integer + fileRecordName: + type: string \ No newline at end of file diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 4a5243eb7..dab7b0094 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -936,6 +936,9 @@ margin-bottom: 20px; }, "bandIndex" : { "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "AirspyHF" @@ -1084,6 +1087,9 @@ margin-bottom: 20px; }, "iqCorrection" : { "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "BladeRF" @@ -1512,6 +1518,9 @@ margin-bottom: 20px; }, "linkTxFrequency" : { "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "HackRF" @@ -1688,6 +1697,9 @@ margin-bottom: 20px; "transverterDeltaFrequency" : { "type" : "integer", "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "LimeSDR" @@ -2101,6 +2113,9 @@ margin-bottom: 20px; }, "rfBandwidth" : { "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "RTLSDR" @@ -20643,7 +20658,7 @@ except ApiException as e:
- Generated 2018-04-17T00:43:20.797+02:00 + Generated 2018-05-09T18:07:27.088+02:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp index b2c1c07fa..4210412b2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp @@ -42,6 +42,8 @@ SWGAirspyHFSettings::SWGAirspyHFSettings() { m_transverter_delta_frequency_isSet = false; band_index = 0; m_band_index_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; } SWGAirspyHFSettings::~SWGAirspyHFSettings() { @@ -64,6 +66,8 @@ SWGAirspyHFSettings::init() { m_transverter_delta_frequency_isSet = false; band_index = 0; m_band_index_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; } void @@ -75,6 +79,9 @@ SWGAirspyHFSettings::cleanup() { + if(file_record_name != nullptr) { + delete file_record_name; + } } SWGAirspyHFSettings* @@ -102,6 +109,8 @@ SWGAirspyHFSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&band_index, pJson["bandIndex"], "qint32", ""); + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + } QString @@ -139,6 +148,9 @@ SWGAirspyHFSettings::asJsonObject() { if(m_band_index_isSet){ obj->insert("bandIndex", QJsonValue(band_index)); } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } return obj; } @@ -213,6 +225,16 @@ SWGAirspyHFSettings::setBandIndex(qint32 band_index) { this->m_band_index_isSet = true; } +QString* +SWGAirspyHFSettings::getFileRecordName() { + return file_record_name; +} +void +SWGAirspyHFSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + bool SWGAirspyHFSettings::isSet(){ @@ -225,6 +247,7 @@ SWGAirspyHFSettings::isSet(){ if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} if(m_band_index_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h index c6de47087..9f124e3d9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h @@ -22,6 +22,7 @@ #include +#include #include "SWGObject.h" #include "export.h" @@ -62,6 +63,9 @@ public: qint32 getBandIndex(); void setBandIndex(qint32 band_index); + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + virtual bool isSet() override; @@ -87,6 +91,9 @@ private: qint32 band_index; bool m_band_index_isSet; + QString* file_record_name; + bool m_file_record_name_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp index 3485436f4..10b2a4756 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp @@ -54,6 +54,8 @@ SWGBladeRFInputSettings::SWGBladeRFInputSettings() { m_dc_block_isSet = false; iq_correction = 0; m_iq_correction_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; } SWGBladeRFInputSettings::~SWGBladeRFInputSettings() { @@ -88,6 +90,8 @@ SWGBladeRFInputSettings::init() { m_dc_block_isSet = false; iq_correction = 0; m_iq_correction_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; } void @@ -105,6 +109,9 @@ SWGBladeRFInputSettings::cleanup() { + if(file_record_name != nullptr) { + delete file_record_name; + } } SWGBladeRFInputSettings* @@ -144,6 +151,8 @@ SWGBladeRFInputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + } QString @@ -199,6 +208,9 @@ SWGBladeRFInputSettings::asJsonObject() { if(m_iq_correction_isSet){ obj->insert("iqCorrection", QJsonValue(iq_correction)); } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } return obj; } @@ -333,6 +345,16 @@ SWGBladeRFInputSettings::setIqCorrection(qint32 iq_correction) { this->m_iq_correction_isSet = true; } +QString* +SWGBladeRFInputSettings::getFileRecordName() { + return file_record_name; +} +void +SWGBladeRFInputSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + bool SWGBladeRFInputSettings::isSet(){ @@ -351,6 +373,7 @@ SWGBladeRFInputSettings::isSet(){ if(m_xb200_filter_isSet){ isObjectUpdated = true; break;} if(m_dc_block_isSet){ isObjectUpdated = true; break;} if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h index 89e8465b9..6e9c66d66 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h @@ -22,6 +22,7 @@ #include +#include #include "SWGObject.h" #include "export.h" @@ -80,6 +81,9 @@ public: qint32 getIqCorrection(); void setIqCorrection(qint32 iq_correction); + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + virtual bool isSet() override; @@ -123,6 +127,9 @@ private: qint32 iq_correction; bool m_iq_correction_isSet; + QString* file_record_name; + bool m_file_record_name_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp index bc7a24854..d7bb4abf6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp @@ -54,6 +54,8 @@ SWGHackRFInputSettings::SWGHackRFInputSettings() { m_iq_correction_isSet = false; link_tx_frequency = 0; m_link_tx_frequency_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; } SWGHackRFInputSettings::~SWGHackRFInputSettings() { @@ -88,6 +90,8 @@ SWGHackRFInputSettings::init() { m_iq_correction_isSet = false; link_tx_frequency = 0; m_link_tx_frequency_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; } void @@ -105,6 +109,9 @@ SWGHackRFInputSettings::cleanup() { + if(file_record_name != nullptr) { + delete file_record_name; + } } SWGHackRFInputSettings* @@ -144,6 +151,8 @@ SWGHackRFInputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&link_tx_frequency, pJson["linkTxFrequency"], "qint32", ""); + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + } QString @@ -199,6 +208,9 @@ SWGHackRFInputSettings::asJsonObject() { if(m_link_tx_frequency_isSet){ obj->insert("linkTxFrequency", QJsonValue(link_tx_frequency)); } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } return obj; } @@ -333,6 +345,16 @@ SWGHackRFInputSettings::setLinkTxFrequency(qint32 link_tx_frequency) { this->m_link_tx_frequency_isSet = true; } +QString* +SWGHackRFInputSettings::getFileRecordName() { + return file_record_name; +} +void +SWGHackRFInputSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + bool SWGHackRFInputSettings::isSet(){ @@ -351,6 +373,7 @@ SWGHackRFInputSettings::isSet(){ if(m_dc_block_isSet){ isObjectUpdated = true; break;} if(m_iq_correction_isSet){ isObjectUpdated = true; break;} if(m_link_tx_frequency_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h index 13b790280..793df1a81 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h @@ -22,6 +22,7 @@ #include +#include #include "SWGObject.h" #include "export.h" @@ -80,6 +81,9 @@ public: qint32 getLinkTxFrequency(); void setLinkTxFrequency(qint32 link_tx_frequency); + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + virtual bool isSet() override; @@ -123,6 +127,9 @@ private: qint32 link_tx_frequency; bool m_link_tx_frequency_isSet; + QString* file_record_name; + bool m_file_record_name_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp index bffdd5215..b9d28084a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp @@ -70,6 +70,8 @@ SWGLimeSdrInputSettings::SWGLimeSdrInputSettings() { m_transverter_mode_isSet = false; transverter_delta_frequency = 0L; m_transverter_delta_frequency_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; } SWGLimeSdrInputSettings::~SWGLimeSdrInputSettings() { @@ -120,6 +122,8 @@ SWGLimeSdrInputSettings::init() { m_transverter_mode_isSet = false; transverter_delta_frequency = 0L; m_transverter_delta_frequency_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; } void @@ -145,6 +149,9 @@ SWGLimeSdrInputSettings::cleanup() { + if(file_record_name != nullptr) { + delete file_record_name; + } } SWGLimeSdrInputSettings* @@ -200,6 +207,8 @@ SWGLimeSdrInputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + } QString @@ -279,6 +288,9 @@ SWGLimeSdrInputSettings::asJsonObject() { if(m_transverter_delta_frequency_isSet){ obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } return obj; } @@ -493,6 +505,16 @@ SWGLimeSdrInputSettings::setTransverterDeltaFrequency(qint64 transverter_delta_f this->m_transverter_delta_frequency_isSet = true; } +QString* +SWGLimeSdrInputSettings::getFileRecordName() { + return file_record_name; +} +void +SWGLimeSdrInputSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + bool SWGLimeSdrInputSettings::isSet(){ @@ -519,6 +541,7 @@ SWGLimeSdrInputSettings::isSet(){ if(m_ext_clock_freq_isSet){ isObjectUpdated = true; break;} if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h index 5fd83cdd7..d301b13ed 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h @@ -22,6 +22,7 @@ #include +#include #include "SWGObject.h" #include "export.h" @@ -104,6 +105,9 @@ public: qint64 getTransverterDeltaFrequency(); void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + virtual bool isSet() override; @@ -171,6 +175,9 @@ private: qint64 transverter_delta_frequency; bool m_transverter_delta_frequency_isSet; + QString* file_record_name; + bool m_file_record_name_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp index 27af72caf..566c41a6f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp @@ -56,6 +56,8 @@ SWGRtlSdrSettings::SWGRtlSdrSettings() { m_transverter_delta_frequency_isSet = false; rf_bandwidth = 0; m_rf_bandwidth_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; } SWGRtlSdrSettings::~SWGRtlSdrSettings() { @@ -92,6 +94,8 @@ SWGRtlSdrSettings::init() { m_transverter_delta_frequency_isSet = false; rf_bandwidth = 0; m_rf_bandwidth_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; } void @@ -110,6 +114,9 @@ SWGRtlSdrSettings::cleanup() { + if(file_record_name != nullptr) { + delete file_record_name; + } } SWGRtlSdrSettings* @@ -151,6 +158,8 @@ SWGRtlSdrSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "qint32", ""); + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + } QString @@ -209,6 +218,9 @@ SWGRtlSdrSettings::asJsonObject() { if(m_rf_bandwidth_isSet){ obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } return obj; } @@ -353,6 +365,16 @@ SWGRtlSdrSettings::setRfBandwidth(qint32 rf_bandwidth) { this->m_rf_bandwidth_isSet = true; } +QString* +SWGRtlSdrSettings::getFileRecordName() { + return file_record_name; +} +void +SWGRtlSdrSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + bool SWGRtlSdrSettings::isSet(){ @@ -372,6 +394,7 @@ SWGRtlSdrSettings::isSet(){ if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h index b202d29eb..eb3969410 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h @@ -22,6 +22,7 @@ #include +#include #include "SWGObject.h" #include "export.h" @@ -83,6 +84,9 @@ public: qint32 getRfBandwidth(); void setRfBandwidth(qint32 rf_bandwidth); + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + virtual bool isSet() override; @@ -129,6 +133,9 @@ private: qint32 rf_bandwidth; bool m_rf_bandwidth_isSet; + QString* file_record_name; + bool m_file_record_name_isSet; + }; } From 1c354dba4d1470cda5f1c1a86261eac97087fe3b Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 9 May 2018 19:38:26 +0200 Subject: [PATCH 372/956] NFM demod: fixed squelch indicator --- plugins/channelrx/demodnfm/nfmdemod.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index a75eec537..4ddc77593 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -224,14 +224,14 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } } + m_squelchOpen = (m_squelchCount > m_squelchGate); + if (m_settings.m_audioMute) { sample = 0; } else { - m_squelchOpen = (m_squelchCount > m_squelchGate); - if (m_squelchOpen) { if (m_settings.m_ctcssOn) From c3242d618f58e9bf9e6600cf106367169c403fd5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 9 May 2018 21:50:41 +0200 Subject: [PATCH 373/956] Benchmarking: added inf/sup decimators test --- plugins/channelrx/demodnfm/nfmdemodgui.cpp | 2 +- plugins/channelrx/demodnfm/nfmplugin.cpp | 2 +- sdrbench/mainbench.cpp | 92 +++++++++++++++++++++- sdrbench/mainbench.h | 4 +- sdrbench/parserbench.cpp | 6 +- sdrbench/parserbench.h | 4 +- 6 files changed, 101 insertions(+), 9 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.cpp b/plugins/channelrx/demodnfm/nfmdemodgui.cpp index 753a222da..3f4ba6f45 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodgui.cpp @@ -77,7 +77,7 @@ bool NFMDemodGUI::handleMessage(const Message& message) { if (NFMDemod::MsgReportCTCSSFreq::match(message)) { - qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgReportCTCSSFreq"); + //qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgReportCTCSSFreq"); NFMDemod::MsgReportCTCSSFreq& report = (NFMDemod::MsgReportCTCSSFreq&) message; setCtcssFreq(report.getFrequency()); //qDebug("NFMDemodGUI::handleMessage: MsgReportCTCSSFreq: %f", report.getFrequency()); diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index f47b5e63e..928dcc072 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("3.14.5"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index 8e26c5a51..ab33f5d9b 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -49,6 +49,10 @@ void MainBench::run() if (m_parser.getTestType() == ParserBench::TestDecimatorsII) { testDecimateII(); + } else if (m_parser.getTestType() == ParserBench::TestDecimatorsInfII) { + testDecimateII(ParserBench::TestDecimatorsInfII); + } else if (m_parser.getTestType() == ParserBench::TestDecimatorsSupII) { + testDecimateII(ParserBench::TestDecimatorsSupII); } else if (m_parser.getTestType() == ParserBench::TestDecimatorsIF) { testDecimateIF(); } else if (m_parser.getTestType() == ParserBench::TestDecimatorsFI) { @@ -62,7 +66,7 @@ void MainBench::run() emit finished(); } -void MainBench::testDecimateII() +void MainBench::testDecimateII(ParserBench::TestType testType) { QElapsedTimer timer; qint64 nsecs = 0; @@ -78,9 +82,25 @@ void MainBench::testDecimateII() for (uint32_t i = 0; i < m_parser.getRepetition(); i++) { - timer.start(); - decimateII(buf, m_parser.getNbSamples()*2); - nsecs += timer.nsecsElapsed(); + switch (testType) + { + case ParserBench::TestDecimatorsInfII: + timer.start(); + decimateInfII(buf, m_parser.getNbSamples()*2); + nsecs += timer.nsecsElapsed(); + break; + case ParserBench::TestDecimatorsSupII: + timer.start(); + decimateSupII(buf, m_parser.getNbSamples()*2); + nsecs += timer.nsecsElapsed(); + break; + case ParserBench::TestDecimatorsII: + default: + timer.start(); + decimateII(buf, m_parser.getNbSamples()*2); + nsecs += timer.nsecsElapsed(); + break; + } } printResults("MainBench::testDecimateII", nsecs); @@ -202,6 +222,70 @@ void MainBench::decimateII(const qint16* buf, int len) } } +void MainBench::decimateInfII(const qint16* buf, int len) +{ + SampleVector::iterator it = m_convertBuffer.begin(); + + switch (m_parser.getLog2Factor()) + { + case 0: + m_decimatorsII.decimate1(&it, buf, len); + break; + case 1: + m_decimatorsII.decimate2_inf(&it, buf, len); + break; + case 2: + m_decimatorsII.decimate4_inf(&it, buf, len); + break; + case 3: + m_decimatorsII.decimate8_inf(&it, buf, len); + break; + case 4: + m_decimatorsII.decimate16_inf(&it, buf, len); + break; + case 5: + m_decimatorsII.decimate32_inf(&it, buf, len); + break; + case 6: + m_decimatorsII.decimate64_inf(&it, buf, len); + break; + default: + break; + } +} + +void MainBench::decimateSupII(const qint16* buf, int len) +{ + SampleVector::iterator it = m_convertBuffer.begin(); + + switch (m_parser.getLog2Factor()) + { + case 0: + m_decimatorsII.decimate1(&it, buf, len); + break; + case 1: + m_decimatorsII.decimate2_sup(&it, buf, len); + break; + case 2: + m_decimatorsII.decimate4_sup(&it, buf, len); + break; + case 3: + m_decimatorsII.decimate8_sup(&it, buf, len); + break; + case 4: + m_decimatorsII.decimate16_sup(&it, buf, len); + break; + case 5: + m_decimatorsII.decimate32_sup(&it, buf, len); + break; + case 6: + m_decimatorsII.decimate64_sup(&it, buf, len); + break; + default: + break; + } +} + void MainBench::decimateIF(const qint16* buf, int len) { FSampleVector::iterator it = m_convertBufferF.begin(); diff --git a/sdrbench/mainbench.h b/sdrbench/mainbench.h index beb17e5fd..2bd419cab 100644 --- a/sdrbench/mainbench.h +++ b/sdrbench/mainbench.h @@ -47,11 +47,13 @@ signals: void finished(); private: - void testDecimateII(); + void testDecimateII(ParserBench::TestType testType = ParserBench::TestDecimatorsII); void testDecimateIF(); void testDecimateFI(); void testDecimateFF(); void decimateII(const qint16 *buf, int len); + void decimateInfII(const qint16 *buf, int len); + void decimateSupII(const qint16 *buf, int len); void decimateIF(const qint16 *buf, int len); void decimateFI(const float *buf, int len); void decimateFF(const float *buf, int len); diff --git a/sdrbench/parserbench.cpp b/sdrbench/parserbench.cpp index 9c8444008..2d7eae974 100644 --- a/sdrbench/parserbench.cpp +++ b/sdrbench/parserbench.cpp @@ -118,8 +118,12 @@ ParserBench::TestType ParserBench::getTestType() const return TestDecimatorsFI; } else if (m_testStr == "decimateff") { return TestDecimatorsFF; - }else if (m_testStr == "decimateif") { + } else if (m_testStr == "decimateif") { return TestDecimatorsIF; + } else if (m_testStr == "decimateinfii") { + return TestDecimatorsInfII; + } else if (m_testStr == "decimatesupii") { + return TestDecimatorsSupII; } else { return TestDecimatorsII; } diff --git a/sdrbench/parserbench.h b/sdrbench/parserbench.h index 473f54bc4..b06d5f473 100644 --- a/sdrbench/parserbench.h +++ b/sdrbench/parserbench.h @@ -29,7 +29,9 @@ public: TestDecimatorsII, TestDecimatorsIF, TestDecimatorsFI, - TestDecimatorsFF + TestDecimatorsFF, + TestDecimatorsInfII, + TestDecimatorsSupII } TestType; ParserBench(); From 0e55accd0f6897ae465552a878a1616bd9efd95c Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 02:14:16 +0200 Subject: [PATCH 374/956] Inf/Sup decimators fix (1): decimators by 2 --- sdrbase/dsp/decimators.h | 201 ++++++++++++++++++++---------- sdrbase/dsp/inthalfbandfiltereo.h | 34 +++++ 2 files changed, 168 insertions(+), 67 deletions(-) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index 50fb6b97b..895fcf1f9 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -282,7 +282,8 @@ public: // interleaved I/Q input buffer void decimate1(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len); - void decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_inf_raw(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len); void decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len); @@ -414,90 +415,156 @@ void Decimators::decimate2_u(SampleVector::i } } +// No filtering: bad for Rx OK for signal tracking +//template +//void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 7; pos += 8) +// { +// xreal = (buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; +// yimag = (buf[pos+1] + buf[pos+2]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// +// xreal = (buf[pos+7] - buf[pos+4]) << decimation_shifts::pre2; +// yimag = (- buf[pos+5] - buf[pos+6]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// } +//} + +//template +//void Decimators::decimate2_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 3; pos += 4) +// { +// // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] +// xreal = (bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; +// yimag = (bufQ[pos] + bufI[pos+1]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// +// // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] +// xreal = (bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre2; +// yimag = (- bufQ[pos+2] - bufI[pos+3]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// } +//} + template void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal, yimag; + StorageType xreal[2], yimag[2]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; - yimag = (buf[pos+1] + buf[pos+2]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - - xreal = (buf[pos+7] - buf[pos+4]) << decimation_shifts::pre2; - yimag = (- buf[pos+5] - buf[pos+6]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - } -} - -template -void Decimators::decimate2_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal, yimag; - - for (int pos = 0; pos < len - 3; pos += 4) + for (int pos = 0; pos < len - 7; pos += 8) { - // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] - xreal = (bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; - yimag = (bufQ[pos] + bufI[pos+1]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); + xreal[0] = buf[pos+2] << decimation_shifts::pre2; + yimag[0] = buf[pos+3] << decimation_shifts::pre2; + xreal[1] = buf[pos+6] << decimation_shifts::pre2; + yimag[1] = buf[pos+7] << decimation_shifts::pre2; + + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + &xreal[0], + &yimag[0], + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + &xreal[1], + &yimag[1]); + + (**it).setReal(xreal[0] >> decimation_shifts::post2); + (**it).setImag(yimag[0] >> decimation_shifts::post2); ++(*it); - // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] - xreal = (bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre2; - yimag = (- bufQ[pos+2] - bufI[pos+3]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); + (**it).setReal(xreal[1] >> decimation_shifts::post2); + (**it).setImag(yimag[1] >> decimation_shifts::post2); ++(*it); } } +// No filtering: bad for Rx OK for signal tracking +//template +//void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 7; pos += 8) +// { +// xreal = (buf[pos+1] - buf[pos+2]) << decimation_shifts::pre2; +// yimag = (- buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// +// xreal = (buf[pos+6] - buf[pos+5]) << decimation_shifts::pre2; +// yimag = (buf[pos+4] + buf[pos+7]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// } +//} +// +//template +//void Decimators::decimate2_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 3; pos += 4) +// { +// // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] +// xreal = (bufQ[pos] - bufI[pos+1]) << decimation_shifts::pre2; +// yimag = (- bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// +// // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] +// xreal = (bufI[pos+3] - bufQ[pos+2]) << decimation_shifts::pre2; +// yimag = (bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// } +//} + template void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal, yimag; + StorageType xreal[2], yimag[2]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+1] - buf[pos+2]) << decimation_shifts::pre2; - yimag = (- buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - - xreal = (buf[pos+6] - buf[pos+5]) << decimation_shifts::pre2; - yimag = (buf[pos+4] + buf[pos+7]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - } -} - -template -void Decimators::decimate2_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal, yimag; - - for (int pos = 0; pos < len - 3; pos += 4) + for (int pos = 0; pos < len - 7; pos += 8) { - // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] - xreal = (bufQ[pos] - bufI[pos+1]) << decimation_shifts::pre2; - yimag = (- bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); + xreal[0] = buf[pos+2] << decimation_shifts::pre2; + yimag[0] = buf[pos+3] << decimation_shifts::pre2; + xreal[1] = buf[pos+6] << decimation_shifts::pre2; + yimag[1] = buf[pos+7] << decimation_shifts::pre2; + + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + &xreal[0], + &yimag[0], + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + &xreal[1], + &yimag[1]); + + (**it).setReal(xreal[0] >> decimation_shifts::post2); + (**it).setImag(yimag[0] >> decimation_shifts::post2); ++(*it); - // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] - xreal = (bufI[pos+3] - bufQ[pos+2]) << decimation_shifts::pre2; - yimag = (bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); + (**it).setReal(xreal[1] >> decimation_shifts::post2); + (**it).setImag(yimag[1] >> decimation_shifts::post2); ++(*it); } } diff --git a/sdrbase/dsp/inthalfbandfiltereo.h b/sdrbase/dsp/inthalfbandfiltereo.h index 8d6af22ff..0ab95a50f 100644 --- a/sdrbase/dsp/inthalfbandfiltereo.h +++ b/sdrbase/dsp/inthalfbandfiltereo.h @@ -572,6 +572,40 @@ public: advancePointer(); } + void myDecimateInf(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) + { + storeSample32(-y1, x1); + advancePointer(); + + storeSample32(-*x2, -*y2); + doFIR(x2, y2); + advancePointer(); + + storeSample32(y3, -x3); + advancePointer(); + + storeSample32(*x4, *y4); + doFIR(x4, y4); + advancePointer(); + } + + void myDecimateSup(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) + { + storeSample32(y1, -x1); + advancePointer(); + + storeSample32(-*x2, -*y2); + doFIR(x2, y2); + advancePointer(); + + storeSample32(-y3, x3); + advancePointer(); + + storeSample32(*x4, *y4); + doFIR(x4, y4); + advancePointer(); + } + /** Simple zero stuffing and filter */ void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) { From 6841bf3efa33f9c677b800b3c94d5bb6cd5cdee9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 02:26:38 +0200 Subject: [PATCH 375/956] Center decimator by 2 optimization --- sdrbase/dsp/decimators.h | 139 ++++++++++++++++++------------ sdrbase/dsp/inthalfbandfiltereo.h | 17 ++++ 2 files changed, 103 insertions(+), 53 deletions(-) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index 895fcf1f9..a49d0192a 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -492,6 +492,70 @@ void Decimators::decimate2_inf(SampleVector: } } +template +void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType xreal[2], yimag[2]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + xreal[0] = buf[pos+2] << decimation_shifts::pre2; + yimag[0] = buf[pos+3] << decimation_shifts::pre2; + xreal[1] = buf[pos+6] << decimation_shifts::pre2; + yimag[1] = buf[pos+7] << decimation_shifts::pre2; + + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + &xreal[0], + &yimag[0], + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + &xreal[1], + &yimag[1]); + + (**it).setReal(xreal[0] >> decimation_shifts::post2); + (**it).setImag(yimag[0] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(xreal[1] >> decimation_shifts::post2); + (**it).setImag(yimag[1] >> decimation_shifts::post2); + ++(*it); + } +} + +template +void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType xreal[2], yimag[2]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + xreal[0] = buf[pos+2] << decimation_shifts::pre2; + yimag[0] = buf[pos+3] << decimation_shifts::pre2; + xreal[1] = buf[pos+6] << decimation_shifts::pre2; + yimag[1] = buf[pos+7] << decimation_shifts::pre2; + + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + &xreal[0], + &yimag[0], + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + &xreal[1], + &yimag[1]); + + (**it).setReal(xreal[0] >> decimation_shifts::post2); + (**it).setImag(yimag[0] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(xreal[1] >> decimation_shifts::post2); + (**it).setImag(yimag[1] >> decimation_shifts::post2); + ++(*it); + } +} + // No filtering: bad for Rx OK for signal tracking //template //void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) @@ -537,37 +601,6 @@ void Decimators::decimate2_inf(SampleVector: // } //} -template -void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) -{ - StorageType xreal[2], yimag[2]; - - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal[0] = buf[pos+2] << decimation_shifts::pre2; - yimag[0] = buf[pos+3] << decimation_shifts::pre2; - xreal[1] = buf[pos+6] << decimation_shifts::pre2; - yimag[1] = buf[pos+7] << decimation_shifts::pre2; - - m_decimator2.myDecimateSup( - buf[pos+0] << decimation_shifts::pre2, - buf[pos+1] << decimation_shifts::pre2, - &xreal[0], - &yimag[0], - buf[pos+4] << decimation_shifts::pre2, - buf[pos+5] << decimation_shifts::pre2, - &xreal[1], - &yimag[1]); - - (**it).setReal(xreal[0] >> decimation_shifts::post2); - (**it).setImag(yimag[0] >> decimation_shifts::post2); - ++(*it); - - (**it).setReal(xreal[1] >> decimation_shifts::post2); - (**it).setImag(yimag[1] >> decimation_shifts::post2); - ++(*it); - } -} template void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) @@ -1131,28 +1164,28 @@ void Decimators::decimate64_sup(SampleVector } } -template -void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) -{ - StorageType intbuf[2]; - - for (int pos = 0; pos < len - 3; pos += 4) - { - intbuf[0] = buf[pos+2] << decimation_shifts::pre2; - intbuf[1] = buf[pos+3] << decimation_shifts::pre2; - - m_decimator2.myDecimate( - buf[pos+0] << decimation_shifts::pre2, - buf[pos+1] << decimation_shifts::pre2, - &intbuf[0], - &intbuf[1]); - - (**it).setReal(intbuf[0] >> decimation_shifts::post2); - (**it).setImag(intbuf[1] >> decimation_shifts::post2); - - ++(*it); - } -} +//template +//void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// StorageType intbuf[2]; +// +// for (int pos = 0; pos < len - 3; pos += 4) +// { +// intbuf[0] = buf[pos+2] << decimation_shifts::pre2; +// intbuf[1] = buf[pos+3] << decimation_shifts::pre2; +// +// m_decimator2.myDecimate( +// buf[pos+0] << decimation_shifts::pre2, +// buf[pos+1] << decimation_shifts::pre2, +// &intbuf[0], +// &intbuf[1]); +// +// (**it).setReal(intbuf[0] >> decimation_shifts::post2); +// (**it).setImag(intbuf[1] >> decimation_shifts::post2); +// +// ++(*it); +// } +//} template void Decimators::decimate2_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) diff --git a/sdrbase/dsp/inthalfbandfiltereo.h b/sdrbase/dsp/inthalfbandfiltereo.h index 0ab95a50f..e515a9e0c 100644 --- a/sdrbase/dsp/inthalfbandfiltereo.h +++ b/sdrbase/dsp/inthalfbandfiltereo.h @@ -572,6 +572,23 @@ public: advancePointer(); } + void myDecimateCen(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + + storeSample32(x3, y3); + advancePointer(); + + storeSample32(*x4, *y4); + doFIR(x4, y4); + advancePointer(); + } + void myDecimateInf(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) { storeSample32(-y1, x1); From f99f7cd598fd02586ff0d139217687622926da50 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 02:51:45 +0200 Subject: [PATCH 376/956] Inf/Sup decimators fix (2): decimators by 4 --- sdrbase/dsp/decimators.h | 209 +++++++++++++++++++++++++-------------- 1 file changed, 133 insertions(+), 76 deletions(-) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index a49d0192a..f4309a5dd 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -577,105 +577,162 @@ void Decimators::decimate2_cen(SampleVector: // ++(*it); // } //} -// -//template -//void Decimators::decimate2_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -//{ -// StorageType xreal, yimag; -// -// for (int pos = 0; pos < len - 3; pos += 4) -// { -// // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] -// xreal = (bufQ[pos] - bufI[pos+1]) << decimation_shifts::pre2; -// yimag = (- bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; -// (**it).setReal(xreal >> decimation_shifts::post2); -// (**it).setImag(yimag >> decimation_shifts::post2); -// ++(*it); -// -// // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] -// xreal = (bufI[pos+3] - bufQ[pos+2]) << decimation_shifts::pre2; -// yimag = (bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre2; -// (**it).setReal(xreal >> decimation_shifts::post2); -// (**it).setImag(yimag >> decimation_shifts::post2); -// ++(*it); -// } -//} - template void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal, yimag; + StorageType xreal[4], yimag[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre4; - yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre4; - - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); - - ++(*it); - } -} - -template -void Decimators::decimate4_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal, yimag; - - for (int pos = 0; pos < len - 3; pos += 4) + for (int pos = 0; pos < len - 15; pos += 16) { - xreal = (bufI[pos] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre4; - yimag = (bufQ[pos] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre4; + xreal[0] = buf[pos+2] << decimation_shifts::pre4; + yimag[0] = buf[pos+3] << decimation_shifts::pre4; + xreal[1] = buf[pos+6] << decimation_shifts::pre4; + yimag[1] = buf[pos+7] << decimation_shifts::pre4; - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + &xreal[0], + &yimag[0], + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + &xreal[1], + &yimag[1]); + xreal[2] = buf[pos+10] << decimation_shifts::pre4; + yimag[2] = buf[pos+11] << decimation_shifts::pre4; + xreal[3] = buf[pos+14] << decimation_shifts::pre4; + yimag[3] = buf[pos+15] << decimation_shifts::pre4; + + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + &xreal[2], + &yimag[2], + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + &xreal[3], + &yimag[3]); + + m_decimator4.myDecimateCen( + xreal[0], + yimag[0], + &xreal[1], + &yimag[1], + xreal[2], + yimag[2], + &xreal[3], + &yimag[3]); + + (**it).setReal(xreal[1] >> decimation_shifts::post4); + (**it).setImag(yimag[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(xreal[3] >> decimation_shifts::post4); + (**it).setImag(yimag[3] >> decimation_shifts::post4); ++(*it); } } + template void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - // Sup (USB): - // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 - // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - // Inf (LSB): - // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 - // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - StorageType xreal, yimag; + StorageType xreal[4], yimag[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre4; - yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre4; - - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); - - ++(*it); - } -} - -template -void Decimators::decimate4_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal, yimag; - - for (int pos = 0; pos < len - 3; pos += 4) + for (int pos = 0; pos < len - 15; pos += 16) { - xreal = (bufQ[pos] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre4; - yimag = (- bufI[pos] - bufQ[pos+1] + bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre4; + xreal[0] = buf[pos+2] << decimation_shifts::pre4; + yimag[0] = buf[pos+3] << decimation_shifts::pre4; + xreal[1] = buf[pos+6] << decimation_shifts::pre4; + yimag[1] = buf[pos+7] << decimation_shifts::pre4; - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + &xreal[0], + &yimag[0], + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + &xreal[1], + &yimag[1]); + xreal[2] = buf[pos+10] << decimation_shifts::pre4; + yimag[2] = buf[pos+11] << decimation_shifts::pre4; + xreal[3] = buf[pos+14] << decimation_shifts::pre4; + yimag[3] = buf[pos+15] << decimation_shifts::pre4; + + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + &xreal[2], + &yimag[2], + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + &xreal[3], + &yimag[3]); + + m_decimator4.myDecimateCen( + xreal[0], + yimag[0], + &xreal[1], + &yimag[1], + xreal[2], + yimag[2], + &xreal[3], + &yimag[3]); + + (**it).setReal(xreal[1] >> decimation_shifts::post4); + (**it).setImag(yimag[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(xreal[3] >> decimation_shifts::post4); + (**it).setImag(yimag[3] >> decimation_shifts::post4); ++(*it); } } +//template +//void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 7; pos += 8) +// { +// xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre4; +// yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre4; +// +// (**it).setReal(xreal >> decimation_shifts::post4); +// (**it).setImag(yimag >> decimation_shifts::post4); +// +// ++(*it); +// } +//} + +//template +//void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// // Sup (USB): +// // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 +// // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] +// // Inf (LSB): +// // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 +// // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 7; pos += 8) +// { +// xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre4; +// yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre4; +// +// (**it).setReal(xreal >> decimation_shifts::post4); +// (**it).setImag(yimag >> decimation_shifts::post4); +// +// ++(*it); +// } +//} + template void Decimators::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) { From 41319b63e5f741683fad987b65536a72953ba868 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 04:37:51 +0200 Subject: [PATCH 377/956] Inf/Sup decimators fix (3): all decimators but by 64 --- sdrbase/dsp/decimators.h | 1082 ++++++++++++++++++++++------- sdrbase/dsp/inthalfbandfiltereo.h | 68 ++ 2 files changed, 904 insertions(+), 246 deletions(-) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index f4309a5dd..f6b204708 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -736,91 +736,145 @@ void Decimators::decimate4_sup(SampleVector: template void Decimators::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[2], yimag[2]; + StorageType buf2[16], buf4[8], buf8[4]; - for (int pos = 0; pos < len - 15; pos += 8) + for (int pos = 0; pos < len - 31; pos += 32) { - xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre8; - yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre8; - pos += 8; + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre8, + buf[pos+1] << decimation_shifts::pre8, + buf[pos+2] << decimation_shifts::pre8, + buf[pos+3] << decimation_shifts::pre8, + buf[pos+4] << decimation_shifts::pre8, + buf[pos+5] << decimation_shifts::pre8, + buf[pos+6] << decimation_shifts::pre8, + buf[pos+7] << decimation_shifts::pre8, + &buf2[0]); - xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre8; - yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre8; + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre8, + buf[pos+9] << decimation_shifts::pre8, + buf[pos+10] << decimation_shifts::pre8, + buf[pos+11] << decimation_shifts::pre8, + buf[pos+12] << decimation_shifts::pre8, + buf[pos+13] << decimation_shifts::pre8, + buf[pos+14] << decimation_shifts::pre8, + buf[pos+15] << decimation_shifts::pre8, + &buf2[4]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre8, + buf[pos+17] << decimation_shifts::pre8, + buf[pos+18] << decimation_shifts::pre8, + buf[pos+19] << decimation_shifts::pre8, + buf[pos+20] << decimation_shifts::pre8, + buf[pos+21] << decimation_shifts::pre8, + buf[pos+22] << decimation_shifts::pre8, + buf[pos+23] << decimation_shifts::pre8, + &buf2[8]); - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre8, + buf[pos+25] << decimation_shifts::pre8, + buf[pos+26] << decimation_shifts::pre8, + buf[pos+27] << decimation_shifts::pre8, + buf[pos+28] << decimation_shifts::pre8, + buf[pos+29] << decimation_shifts::pre8, + buf[pos+30] << decimation_shifts::pre8, + buf[pos+31] << decimation_shifts::pre8, + &buf2[12]); - ++(*it); - } -} + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); -template -void Decimators::decimate8_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal[2], yimag[2]; + m_decimator4.myDecimateCen( + &buf2[8], + &buf4[4]); - for (int pos = 0; pos < len - 7; pos += 4) - { - xreal[0] = (bufI[pos] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre8; - yimag[0] = (bufQ[pos] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre8; - pos += 4; - - xreal[1] = (bufI[pos] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre8; - yimag[1] = (bufQ[pos] - bufQ[pos+2] + bufI[pos+1] - bufQ[pos+3]) << decimation_shifts::pre8; - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + (**it).setReal(buf8[0] >> decimation_shifts::post8); + (**it).setImag(buf8[1] >> decimation_shifts::post8); ++(*it); - } + + (**it).setReal(buf8[2] >> decimation_shifts::post8); + (**it).setImag(buf8[3] >> decimation_shifts::post8); + ++(*it); + } } template void Decimators::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[2], yimag[2]; + StorageType buf2[16], buf4[8], buf8[4]; - for (int pos = 0; pos < len - 15; pos += 8) - { - xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre8; - yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre8; - pos += 8; - - xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre8; - yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre8; - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); - - ++(*it); - } -} - -template -void Decimators::decimate8_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal[2], yimag[2]; - - for (int pos = 0; pos < len - 7; pos += 4) + for (int pos = 0; pos < len - 31; pos += 32) { - xreal[0] = (bufQ[pos] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre8; - yimag[0] = (- bufI[pos] - bufQ[pos+1] + bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre8; - pos += 4; + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre8, + buf[pos+1] << decimation_shifts::pre8, + buf[pos+2] << decimation_shifts::pre8, + buf[pos+3] << decimation_shifts::pre8, + buf[pos+4] << decimation_shifts::pre8, + buf[pos+5] << decimation_shifts::pre8, + buf[pos+6] << decimation_shifts::pre8, + buf[pos+7] << decimation_shifts::pre8, + &buf2[0]); - xreal[1] = (bufQ[pos] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre8; - yimag[1] = (- bufI[pos] - bufQ[pos+1] + bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre8; + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre8, + buf[pos+9] << decimation_shifts::pre8, + buf[pos+10] << decimation_shifts::pre8, + buf[pos+11] << decimation_shifts::pre8, + buf[pos+12] << decimation_shifts::pre8, + buf[pos+13] << decimation_shifts::pre8, + buf[pos+14] << decimation_shifts::pre8, + buf[pos+15] << decimation_shifts::pre8, + &buf2[4]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre8, + buf[pos+17] << decimation_shifts::pre8, + buf[pos+18] << decimation_shifts::pre8, + buf[pos+19] << decimation_shifts::pre8, + buf[pos+20] << decimation_shifts::pre8, + buf[pos+21] << decimation_shifts::pre8, + buf[pos+22] << decimation_shifts::pre8, + buf[pos+23] << decimation_shifts::pre8, + &buf2[8]); - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre8, + buf[pos+25] << decimation_shifts::pre8, + buf[pos+26] << decimation_shifts::pre8, + buf[pos+27] << decimation_shifts::pre8, + buf[pos+28] << decimation_shifts::pre8, + buf[pos+29] << decimation_shifts::pre8, + buf[pos+30] << decimation_shifts::pre8, + buf[pos+31] << decimation_shifts::pre8, + &buf2[12]); + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateCen( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[0] >> decimation_shifts::post8); + (**it).setImag(buf8[1] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[2] >> decimation_shifts::post8); + (**it).setImag(buf8[3] >> decimation_shifts::post8); ++(*it); } } @@ -828,55 +882,132 @@ void Decimators::decimate8_sup(SampleVector: template void Decimators::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - StorageType xreal[4], yimag[4]; + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; - for (int pos = 0; pos < len - 31; ) - { - for (int i = 0; i < 4; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre16; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre16; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); - - ++(*it); - } -} - -template -void Decimators::decimate16_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - StorageType xreal[4], yimag[4]; - - for (int pos = 0; pos < len - 15; ) + for (int pos = 0; pos < len - 63; pos += 64) { - for (int i = 0; i < 4; i++) - { - xreal[i] = (bufI[pos] - bufI[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre16; - yimag[i] = (bufQ[pos] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre16; - pos += 4; - } + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + buf[pos+2] << decimation_shifts::pre16, + buf[pos+3] << decimation_shifts::pre16, + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + buf[pos+6] << decimation_shifts::pre16, + buf[pos+7] << decimation_shifts::pre16, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + buf[pos+10] << decimation_shifts::pre16, + buf[pos+11] << decimation_shifts::pre16, + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + buf[pos+14] << decimation_shifts::pre16, + buf[pos+15] << decimation_shifts::pre16, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + buf[pos+18] << decimation_shifts::pre16, + buf[pos+19] << decimation_shifts::pre16, + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + buf[pos+22] << decimation_shifts::pre16, + buf[pos+23] << decimation_shifts::pre16, + &buf2[8]); - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + buf[pos+26] << decimation_shifts::pre16, + buf[pos+27] << decimation_shifts::pre16, + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + buf[pos+30] << decimation_shifts::pre16, + buf[pos+31] << decimation_shifts::pre16, + &buf2[12]); + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre16, + buf[pos+33] << decimation_shifts::pre16, + buf[pos+34] << decimation_shifts::pre16, + buf[pos+35] << decimation_shifts::pre16, + buf[pos+36] << decimation_shifts::pre16, + buf[pos+37] << decimation_shifts::pre16, + buf[pos+38] << decimation_shifts::pre16, + buf[pos+39] << decimation_shifts::pre16, + &buf2[16]); + + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre16, + buf[pos+41] << decimation_shifts::pre16, + buf[pos+42] << decimation_shifts::pre16, + buf[pos+43] << decimation_shifts::pre16, + buf[pos+44] << decimation_shifts::pre16, + buf[pos+45] << decimation_shifts::pre16, + buf[pos+46] << decimation_shifts::pre16, + buf[pos+47] << decimation_shifts::pre16, + &buf2[20]); + + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre16, + buf[pos+49] << decimation_shifts::pre16, + buf[pos+50] << decimation_shifts::pre16, + buf[pos+51] << decimation_shifts::pre16, + buf[pos+52] << decimation_shifts::pre16, + buf[pos+53] << decimation_shifts::pre16, + buf[pos+54] << decimation_shifts::pre16, + buf[pos+55] << decimation_shifts::pre16, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre16, + buf[pos+57] << decimation_shifts::pre16, + buf[pos+58] << decimation_shifts::pre16, + buf[pos+59] << decimation_shifts::pre16, + buf[pos+60] << decimation_shifts::pre16, + buf[pos+61] << decimation_shifts::pre16, + buf[pos+62] << decimation_shifts::pre16, + buf[pos+63] << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateCen( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateCen( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateCen( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateCen( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[0] >> decimation_shifts::post16); + (**it).setImag(buf16[1] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[2] >> decimation_shifts::post16); + (**it).setImag(buf16[3] >> decimation_shifts::post16); ++(*it); } } @@ -884,55 +1015,132 @@ void Decimators::decimate16_inf(SampleVector template void Decimators::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - StorageType xreal[4], yimag[4]; + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; - for (int pos = 0; pos < len - 31; ) - { - for (int i = 0; i < 4; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre16; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre16; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); - - ++(*it); - } -} - -template -void Decimators::decimate16_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - StorageType xreal[4], yimag[4]; - - for (int pos = 0; pos < len - 15; ) + for (int pos = 0; pos < len - 63; pos += 64) { - for (int i = 0; i < 4; i++) - { - xreal[i] = (bufQ[pos+0] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre16; - yimag[i] = (bufI[pos+2] + bufQ[pos+3] - bufI[pos+0] - bufQ[pos+1]) << decimation_shifts::pre16; - pos += 4; - } + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + buf[pos+2] << decimation_shifts::pre16, + buf[pos+3] << decimation_shifts::pre16, + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + buf[pos+6] << decimation_shifts::pre16, + buf[pos+7] << decimation_shifts::pre16, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + buf[pos+10] << decimation_shifts::pre16, + buf[pos+11] << decimation_shifts::pre16, + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + buf[pos+14] << decimation_shifts::pre16, + buf[pos+15] << decimation_shifts::pre16, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + buf[pos+18] << decimation_shifts::pre16, + buf[pos+19] << decimation_shifts::pre16, + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + buf[pos+22] << decimation_shifts::pre16, + buf[pos+23] << decimation_shifts::pre16, + &buf2[8]); - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + buf[pos+26] << decimation_shifts::pre16, + buf[pos+27] << decimation_shifts::pre16, + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + buf[pos+30] << decimation_shifts::pre16, + buf[pos+31] << decimation_shifts::pre16, + &buf2[12]); + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre16, + buf[pos+33] << decimation_shifts::pre16, + buf[pos+34] << decimation_shifts::pre16, + buf[pos+35] << decimation_shifts::pre16, + buf[pos+36] << decimation_shifts::pre16, + buf[pos+37] << decimation_shifts::pre16, + buf[pos+38] << decimation_shifts::pre16, + buf[pos+39] << decimation_shifts::pre16, + &buf2[16]); + + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre16, + buf[pos+41] << decimation_shifts::pre16, + buf[pos+42] << decimation_shifts::pre16, + buf[pos+43] << decimation_shifts::pre16, + buf[pos+44] << decimation_shifts::pre16, + buf[pos+45] << decimation_shifts::pre16, + buf[pos+46] << decimation_shifts::pre16, + buf[pos+47] << decimation_shifts::pre16, + &buf2[20]); + + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre16, + buf[pos+49] << decimation_shifts::pre16, + buf[pos+50] << decimation_shifts::pre16, + buf[pos+51] << decimation_shifts::pre16, + buf[pos+52] << decimation_shifts::pre16, + buf[pos+53] << decimation_shifts::pre16, + buf[pos+54] << decimation_shifts::pre16, + buf[pos+55] << decimation_shifts::pre16, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre16, + buf[pos+57] << decimation_shifts::pre16, + buf[pos+58] << decimation_shifts::pre16, + buf[pos+59] << decimation_shifts::pre16, + buf[pos+60] << decimation_shifts::pre16, + buf[pos+61] << decimation_shifts::pre16, + buf[pos+62] << decimation_shifts::pre16, + buf[pos+63] << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateCen( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateCen( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateCen( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateCen( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[0] >> decimation_shifts::post16); + (**it).setImag(buf16[1] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[2] >> decimation_shifts::post16); + (**it).setImag(buf16[3] >> decimation_shifts::post16); ++(*it); } } @@ -940,61 +1148,252 @@ void Decimators::decimate16_sup(SampleVector template void Decimators::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[8], yimag[8]; + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; - for (int pos = 0; pos < len - 63; ) - { - for (int i = 0; i < 8; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre32; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); - - ++(*it); - } -} - -template -void Decimators::decimate32_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal[8], yimag[8]; - - for (int pos = 0; pos < len - 31; ) + for (int pos = 0; pos < len - 127; pos += 128) { - for (int i = 0; i < 8; i++) - { - xreal[i] = (bufI[pos+0] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre32; - yimag[i] = (bufQ[pos+0] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre32; - pos += 4; - } + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + buf[pos+2] << decimation_shifts::pre32, + buf[pos+3] << decimation_shifts::pre32, + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + buf[pos+6] << decimation_shifts::pre32, + buf[pos+7] << decimation_shifts::pre32, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + buf[pos+10] << decimation_shifts::pre32, + buf[pos+11] << decimation_shifts::pre32, + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + buf[pos+14] << decimation_shifts::pre32, + buf[pos+15] << decimation_shifts::pre32, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + buf[pos+18] << decimation_shifts::pre32, + buf[pos+19] << decimation_shifts::pre32, + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + buf[pos+22] << decimation_shifts::pre32, + buf[pos+23] << decimation_shifts::pre32, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + buf[pos+26] << decimation_shifts::pre32, + buf[pos+27] << decimation_shifts::pre32, + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + buf[pos+30] << decimation_shifts::pre32, + buf[pos+31] << decimation_shifts::pre32, + &buf2[12]); - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + buf[pos+34] << decimation_shifts::pre32, + buf[pos+35] << decimation_shifts::pre32, + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + buf[pos+38] << decimation_shifts::pre32, + buf[pos+39] << decimation_shifts::pre32, + &buf2[16]); + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + buf[pos+42] << decimation_shifts::pre32, + buf[pos+43] << decimation_shifts::pre32, + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + buf[pos+46] << decimation_shifts::pre32, + buf[pos+47] << decimation_shifts::pre32, + &buf2[20]); + + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + buf[pos+50] << decimation_shifts::pre32, + buf[pos+51] << decimation_shifts::pre32, + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + buf[pos+54] << decimation_shifts::pre32, + buf[pos+55] << decimation_shifts::pre32, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + buf[pos+58] << decimation_shifts::pre32, + buf[pos+59] << decimation_shifts::pre32, + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + buf[pos+62] << decimation_shifts::pre32, + buf[pos+63] << decimation_shifts::pre32, + &buf2[28]); + + m_decimator2.myDecimateInf( + buf[pos+64] << decimation_shifts::pre32, + buf[pos+65] << decimation_shifts::pre32, + buf[pos+66] << decimation_shifts::pre32, + buf[pos+67] << decimation_shifts::pre32, + buf[pos+68] << decimation_shifts::pre32, + buf[pos+69] << decimation_shifts::pre32, + buf[pos+70] << decimation_shifts::pre32, + buf[pos+71] << decimation_shifts::pre32, + &buf2[32]); + + m_decimator2.myDecimateInf( + buf[pos+72] << decimation_shifts::pre32, + buf[pos+73] << decimation_shifts::pre32, + buf[pos+74] << decimation_shifts::pre32, + buf[pos+75] << decimation_shifts::pre32, + buf[pos+76] << decimation_shifts::pre32, + buf[pos+77] << decimation_shifts::pre32, + buf[pos+78] << decimation_shifts::pre32, + buf[pos+79] << decimation_shifts::pre32, + &buf2[36]); + + m_decimator2.myDecimateInf( + buf[pos+80] << decimation_shifts::pre32, + buf[pos+81] << decimation_shifts::pre32, + buf[pos+82] << decimation_shifts::pre32, + buf[pos+83] << decimation_shifts::pre32, + buf[pos+84] << decimation_shifts::pre32, + buf[pos+85] << decimation_shifts::pre32, + buf[pos+86] << decimation_shifts::pre32, + buf[pos+87] << decimation_shifts::pre32, + &buf2[40]); + + m_decimator2.myDecimateInf( + buf[pos+88] << decimation_shifts::pre32, + buf[pos+89] << decimation_shifts::pre32, + buf[pos+90] << decimation_shifts::pre32, + buf[pos+91] << decimation_shifts::pre32, + buf[pos+92] << decimation_shifts::pre32, + buf[pos+93] << decimation_shifts::pre32, + buf[pos+94] << decimation_shifts::pre32, + buf[pos+95] << decimation_shifts::pre32, + &buf2[44]); + + m_decimator2.myDecimateInf( + buf[pos+96] << decimation_shifts::pre32, + buf[pos+97] << decimation_shifts::pre32, + buf[pos+98] << decimation_shifts::pre32, + buf[pos+99] << decimation_shifts::pre32, + buf[pos+100] << decimation_shifts::pre32, + buf[pos+101] << decimation_shifts::pre32, + buf[pos+102] << decimation_shifts::pre32, + buf[pos+103] << decimation_shifts::pre32, + &buf2[48]); + + m_decimator2.myDecimateInf( + buf[pos+104] << decimation_shifts::pre32, + buf[pos+105] << decimation_shifts::pre32, + buf[pos+106] << decimation_shifts::pre32, + buf[pos+107] << decimation_shifts::pre32, + buf[pos+108] << decimation_shifts::pre32, + buf[pos+109] << decimation_shifts::pre32, + buf[pos+110] << decimation_shifts::pre32, + buf[pos+111] << decimation_shifts::pre32, + &buf2[52]); + + m_decimator2.myDecimateInf( + buf[pos+112] << decimation_shifts::pre32, + buf[pos+113] << decimation_shifts::pre32, + buf[pos+114] << decimation_shifts::pre32, + buf[pos+115] << decimation_shifts::pre32, + buf[pos+116] << decimation_shifts::pre32, + buf[pos+117] << decimation_shifts::pre32, + buf[pos+118] << decimation_shifts::pre32, + buf[pos+119] << decimation_shifts::pre32, + &buf2[56]); + + m_decimator2.myDecimateInf( + buf[pos+120] << decimation_shifts::pre32, + buf[pos+121] << decimation_shifts::pre32, + buf[pos+122] << decimation_shifts::pre32, + buf[pos+123] << decimation_shifts::pre32, + buf[pos+124] << decimation_shifts::pre32, + buf[pos+125] << decimation_shifts::pre32, + buf[pos+126] << decimation_shifts::pre32, + buf[pos+127] << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateCen( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateCen( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateCen( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateCen( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateCen( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateCen( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateCen( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateCen( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateCen( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateCen( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateCen( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[0] >> decimation_shifts::post32); + (**it).setImag(buf32[1] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[2] >> decimation_shifts::post32); + (**it).setImag(buf32[3] >> decimation_shifts::post32); ++(*it); } } @@ -1002,61 +1401,252 @@ void Decimators::decimate32_inf(SampleVector template void Decimators::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[8], yimag[8]; + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; - for (int pos = 0; pos < len - 63; ) - { - for (int i = 0; i < 8; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); - - ++(*it); - } -} - -template -void Decimators::decimate32_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal[8], yimag[8]; - - for (int pos = 0; pos < len - 31; ) + for (int pos = 0; pos < len - 127; pos += 128) { - for (int i = 0; i < 8; i++) - { - xreal[i] = (bufQ[pos+0] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre32; - yimag[i] = (bufI[pos+2] + bufQ[pos+3] - bufI[pos+0] - bufQ[pos+1]) << decimation_shifts::pre32; - pos += 4; - } + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + buf[pos+2] << decimation_shifts::pre32, + buf[pos+3] << decimation_shifts::pre32, + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + buf[pos+6] << decimation_shifts::pre32, + buf[pos+7] << decimation_shifts::pre32, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + buf[pos+10] << decimation_shifts::pre32, + buf[pos+11] << decimation_shifts::pre32, + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + buf[pos+14] << decimation_shifts::pre32, + buf[pos+15] << decimation_shifts::pre32, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + buf[pos+18] << decimation_shifts::pre32, + buf[pos+19] << decimation_shifts::pre32, + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + buf[pos+22] << decimation_shifts::pre32, + buf[pos+23] << decimation_shifts::pre32, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + buf[pos+26] << decimation_shifts::pre32, + buf[pos+27] << decimation_shifts::pre32, + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + buf[pos+30] << decimation_shifts::pre32, + buf[pos+31] << decimation_shifts::pre32, + &buf2[12]); - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + buf[pos+34] << decimation_shifts::pre32, + buf[pos+35] << decimation_shifts::pre32, + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + buf[pos+38] << decimation_shifts::pre32, + buf[pos+39] << decimation_shifts::pre32, + &buf2[16]); + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + buf[pos+42] << decimation_shifts::pre32, + buf[pos+43] << decimation_shifts::pre32, + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + buf[pos+46] << decimation_shifts::pre32, + buf[pos+47] << decimation_shifts::pre32, + &buf2[20]); + + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + buf[pos+50] << decimation_shifts::pre32, + buf[pos+51] << decimation_shifts::pre32, + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + buf[pos+54] << decimation_shifts::pre32, + buf[pos+55] << decimation_shifts::pre32, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + buf[pos+58] << decimation_shifts::pre32, + buf[pos+59] << decimation_shifts::pre32, + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + buf[pos+62] << decimation_shifts::pre32, + buf[pos+63] << decimation_shifts::pre32, + &buf2[28]); + + m_decimator2.myDecimateSup( + buf[pos+64] << decimation_shifts::pre32, + buf[pos+65] << decimation_shifts::pre32, + buf[pos+66] << decimation_shifts::pre32, + buf[pos+67] << decimation_shifts::pre32, + buf[pos+68] << decimation_shifts::pre32, + buf[pos+69] << decimation_shifts::pre32, + buf[pos+70] << decimation_shifts::pre32, + buf[pos+71] << decimation_shifts::pre32, + &buf2[32]); + + m_decimator2.myDecimateSup( + buf[pos+72] << decimation_shifts::pre32, + buf[pos+73] << decimation_shifts::pre32, + buf[pos+74] << decimation_shifts::pre32, + buf[pos+75] << decimation_shifts::pre32, + buf[pos+76] << decimation_shifts::pre32, + buf[pos+77] << decimation_shifts::pre32, + buf[pos+78] << decimation_shifts::pre32, + buf[pos+79] << decimation_shifts::pre32, + &buf2[36]); + + m_decimator2.myDecimateSup( + buf[pos+80] << decimation_shifts::pre32, + buf[pos+81] << decimation_shifts::pre32, + buf[pos+82] << decimation_shifts::pre32, + buf[pos+83] << decimation_shifts::pre32, + buf[pos+84] << decimation_shifts::pre32, + buf[pos+85] << decimation_shifts::pre32, + buf[pos+86] << decimation_shifts::pre32, + buf[pos+87] << decimation_shifts::pre32, + &buf2[40]); + + m_decimator2.myDecimateSup( + buf[pos+88] << decimation_shifts::pre32, + buf[pos+89] << decimation_shifts::pre32, + buf[pos+90] << decimation_shifts::pre32, + buf[pos+91] << decimation_shifts::pre32, + buf[pos+92] << decimation_shifts::pre32, + buf[pos+93] << decimation_shifts::pre32, + buf[pos+94] << decimation_shifts::pre32, + buf[pos+95] << decimation_shifts::pre32, + &buf2[44]); + + m_decimator2.myDecimateSup( + buf[pos+96] << decimation_shifts::pre32, + buf[pos+97] << decimation_shifts::pre32, + buf[pos+98] << decimation_shifts::pre32, + buf[pos+99] << decimation_shifts::pre32, + buf[pos+100] << decimation_shifts::pre32, + buf[pos+101] << decimation_shifts::pre32, + buf[pos+102] << decimation_shifts::pre32, + buf[pos+103] << decimation_shifts::pre32, + &buf2[48]); + + m_decimator2.myDecimateSup( + buf[pos+104] << decimation_shifts::pre32, + buf[pos+105] << decimation_shifts::pre32, + buf[pos+106] << decimation_shifts::pre32, + buf[pos+107] << decimation_shifts::pre32, + buf[pos+108] << decimation_shifts::pre32, + buf[pos+109] << decimation_shifts::pre32, + buf[pos+110] << decimation_shifts::pre32, + buf[pos+111] << decimation_shifts::pre32, + &buf2[52]); + + m_decimator2.myDecimateSup( + buf[pos+112] << decimation_shifts::pre32, + buf[pos+113] << decimation_shifts::pre32, + buf[pos+114] << decimation_shifts::pre32, + buf[pos+115] << decimation_shifts::pre32, + buf[pos+116] << decimation_shifts::pre32, + buf[pos+117] << decimation_shifts::pre32, + buf[pos+118] << decimation_shifts::pre32, + buf[pos+119] << decimation_shifts::pre32, + &buf2[56]); + + m_decimator2.myDecimateSup( + buf[pos+120] << decimation_shifts::pre32, + buf[pos+121] << decimation_shifts::pre32, + buf[pos+122] << decimation_shifts::pre32, + buf[pos+123] << decimation_shifts::pre32, + buf[pos+124] << decimation_shifts::pre32, + buf[pos+125] << decimation_shifts::pre32, + buf[pos+126] << decimation_shifts::pre32, + buf[pos+127] << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateCen( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateCen( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateCen( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateCen( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateCen( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateCen( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateCen( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateCen( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateCen( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateCen( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateCen( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[0] >> decimation_shifts::post32); + (**it).setImag(buf32[1] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[2] >> decimation_shifts::post32); + (**it).setImag(buf32[3] >> decimation_shifts::post32); ++(*it); } } diff --git a/sdrbase/dsp/inthalfbandfiltereo.h b/sdrbase/dsp/inthalfbandfiltereo.h index e515a9e0c..b60871bfe 100644 --- a/sdrbase/dsp/inthalfbandfiltereo.h +++ b/sdrbase/dsp/inthalfbandfiltereo.h @@ -589,6 +589,40 @@ public: advancePointer(); } + void myDecimateCen(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(x2, y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(x3, y3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateCen(int32_t *in, int32_t *out) + { + storeSample32(in[0], in[1]); + advancePointer(); + + storeSample32(in[2], in[3]); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(in[4], in[5]); + advancePointer(); + + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); + advancePointer(); + } + void myDecimateInf(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) { storeSample32(-y1, x1); @@ -606,6 +640,23 @@ public: advancePointer(); } + void myDecimateInf(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(-y1, x1); + advancePointer(); + + storeSample32(-x2, -y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(y3, -x3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + void myDecimateSup(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) { storeSample32(y1, -x1); @@ -623,6 +674,23 @@ public: advancePointer(); } + void myDecimateSup(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(y1, -x1); + advancePointer(); + + storeSample32(-x2, -y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(-y3, x3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + /** Simple zero stuffing and filter */ void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) { From 1fadbf3b8a7e57aa042c0c3c0c291d676a433e57 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 10:07:42 +0200 Subject: [PATCH 378/956] Inf/Sup decimators fix (4): added decimators by 64 --- sdrbase/dsp/decimators.h | 1150 ++++++++++++++++++++++++++++++++------ 1 file changed, 988 insertions(+), 162 deletions(-) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index f6b204708..e94448b6c 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -524,38 +524,6 @@ void Decimators::decimate2_sup(SampleVector: } } -template -void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) -{ - StorageType xreal[2], yimag[2]; - - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal[0] = buf[pos+2] << decimation_shifts::pre2; - yimag[0] = buf[pos+3] << decimation_shifts::pre2; - xreal[1] = buf[pos+6] << decimation_shifts::pre2; - yimag[1] = buf[pos+7] << decimation_shifts::pre2; - - m_decimator2.myDecimateCen( - buf[pos+0] << decimation_shifts::pre2, - buf[pos+1] << decimation_shifts::pre2, - &xreal[0], - &yimag[0], - buf[pos+4] << decimation_shifts::pre2, - buf[pos+5] << decimation_shifts::pre2, - &xreal[1], - &yimag[1]); - - (**it).setReal(xreal[0] >> decimation_shifts::post2); - (**it).setImag(yimag[0] >> decimation_shifts::post2); - ++(*it); - - (**it).setReal(xreal[1] >> decimation_shifts::post2); - (**it).setImag(yimag[1] >> decimation_shifts::post2); - ++(*it); - } -} - // No filtering: bad for Rx OK for signal tracking //template //void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) @@ -1654,79 +1622,492 @@ void Decimators::decimate32_sup(SampleVector template void Decimators::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[16], yimag[16]; + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; - for (int pos = 0; pos < len - 127; ) - { - for (int i = 0; i < 16; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre64; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre64; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); - - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); - - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); - - ++(*it); - } -} - -template -void Decimators::decimate64_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal[16], yimag[16]; - - for (int pos = 0; pos < len - 63; ) + for (int pos = 0; pos < len - 255; pos += 256) { - for (int i = 0; i < 16; i++) - { - xreal[i] = (bufI[pos+0] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre64; - yimag[i] = (bufQ[pos+0] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre64; - pos += 4; - } + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre64, + buf[pos+1] << decimation_shifts::pre64, + buf[pos+2] << decimation_shifts::pre64, + buf[pos+3] << decimation_shifts::pre64, + buf[pos+4] << decimation_shifts::pre64, + buf[pos+5] << decimation_shifts::pre64, + buf[pos+6] << decimation_shifts::pre64, + buf[pos+7] << decimation_shifts::pre64, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre64, + buf[pos+9] << decimation_shifts::pre64, + buf[pos+10] << decimation_shifts::pre64, + buf[pos+11] << decimation_shifts::pre64, + buf[pos+12] << decimation_shifts::pre64, + buf[pos+13] << decimation_shifts::pre64, + buf[pos+14] << decimation_shifts::pre64, + buf[pos+15] << decimation_shifts::pre64, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre64, + buf[pos+17] << decimation_shifts::pre64, + buf[pos+18] << decimation_shifts::pre64, + buf[pos+19] << decimation_shifts::pre64, + buf[pos+20] << decimation_shifts::pre64, + buf[pos+21] << decimation_shifts::pre64, + buf[pos+22] << decimation_shifts::pre64, + buf[pos+23] << decimation_shifts::pre64, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre64, + buf[pos+25] << decimation_shifts::pre64, + buf[pos+26] << decimation_shifts::pre64, + buf[pos+27] << decimation_shifts::pre64, + buf[pos+28] << decimation_shifts::pre64, + buf[pos+29] << decimation_shifts::pre64, + buf[pos+30] << decimation_shifts::pre64, + buf[pos+31] << decimation_shifts::pre64, + &buf2[12]); - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre64, + buf[pos+33] << decimation_shifts::pre64, + buf[pos+34] << decimation_shifts::pre64, + buf[pos+35] << decimation_shifts::pre64, + buf[pos+36] << decimation_shifts::pre64, + buf[pos+37] << decimation_shifts::pre64, + buf[pos+38] << decimation_shifts::pre64, + buf[pos+39] << decimation_shifts::pre64, + &buf2[16]); - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre64, + buf[pos+41] << decimation_shifts::pre64, + buf[pos+42] << decimation_shifts::pre64, + buf[pos+43] << decimation_shifts::pre64, + buf[pos+44] << decimation_shifts::pre64, + buf[pos+45] << decimation_shifts::pre64, + buf[pos+46] << decimation_shifts::pre64, + buf[pos+47] << decimation_shifts::pre64, + &buf2[20]); + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre64, + buf[pos+49] << decimation_shifts::pre64, + buf[pos+50] << decimation_shifts::pre64, + buf[pos+51] << decimation_shifts::pre64, + buf[pos+52] << decimation_shifts::pre64, + buf[pos+53] << decimation_shifts::pre64, + buf[pos+54] << decimation_shifts::pre64, + buf[pos+55] << decimation_shifts::pre64, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre64, + buf[pos+57] << decimation_shifts::pre64, + buf[pos+58] << decimation_shifts::pre64, + buf[pos+59] << decimation_shifts::pre64, + buf[pos+60] << decimation_shifts::pre64, + buf[pos+61] << decimation_shifts::pre64, + buf[pos+62] << decimation_shifts::pre64, + buf[pos+63] << decimation_shifts::pre64, + &buf2[28]); + + m_decimator2.myDecimateInf( + buf[pos+64] << decimation_shifts::pre64, + buf[pos+65] << decimation_shifts::pre64, + buf[pos+66] << decimation_shifts::pre64, + buf[pos+67] << decimation_shifts::pre64, + buf[pos+68] << decimation_shifts::pre64, + buf[pos+69] << decimation_shifts::pre64, + buf[pos+70] << decimation_shifts::pre64, + buf[pos+71] << decimation_shifts::pre64, + &buf2[32]); + + m_decimator2.myDecimateInf( + buf[pos+72] << decimation_shifts::pre64, + buf[pos+73] << decimation_shifts::pre64, + buf[pos+74] << decimation_shifts::pre64, + buf[pos+75] << decimation_shifts::pre64, + buf[pos+76] << decimation_shifts::pre64, + buf[pos+77] << decimation_shifts::pre64, + buf[pos+78] << decimation_shifts::pre64, + buf[pos+79] << decimation_shifts::pre64, + &buf2[36]); + + m_decimator2.myDecimateInf( + buf[pos+80] << decimation_shifts::pre64, + buf[pos+81] << decimation_shifts::pre64, + buf[pos+82] << decimation_shifts::pre64, + buf[pos+83] << decimation_shifts::pre64, + buf[pos+84] << decimation_shifts::pre64, + buf[pos+85] << decimation_shifts::pre64, + buf[pos+86] << decimation_shifts::pre64, + buf[pos+87] << decimation_shifts::pre64, + &buf2[40]); + + m_decimator2.myDecimateInf( + buf[pos+88] << decimation_shifts::pre64, + buf[pos+89] << decimation_shifts::pre64, + buf[pos+90] << decimation_shifts::pre64, + buf[pos+91] << decimation_shifts::pre64, + buf[pos+92] << decimation_shifts::pre64, + buf[pos+93] << decimation_shifts::pre64, + buf[pos+94] << decimation_shifts::pre64, + buf[pos+95] << decimation_shifts::pre64, + &buf2[44]); + + m_decimator2.myDecimateInf( + buf[pos+96] << decimation_shifts::pre64, + buf[pos+97] << decimation_shifts::pre64, + buf[pos+98] << decimation_shifts::pre64, + buf[pos+99] << decimation_shifts::pre64, + buf[pos+100] << decimation_shifts::pre64, + buf[pos+101] << decimation_shifts::pre64, + buf[pos+102] << decimation_shifts::pre64, + buf[pos+103] << decimation_shifts::pre64, + &buf2[48]); + + m_decimator2.myDecimateInf( + buf[pos+104] << decimation_shifts::pre64, + buf[pos+105] << decimation_shifts::pre64, + buf[pos+106] << decimation_shifts::pre64, + buf[pos+107] << decimation_shifts::pre64, + buf[pos+108] << decimation_shifts::pre64, + buf[pos+109] << decimation_shifts::pre64, + buf[pos+110] << decimation_shifts::pre64, + buf[pos+111] << decimation_shifts::pre64, + &buf2[52]); + + m_decimator2.myDecimateInf( + buf[pos+112] << decimation_shifts::pre64, + buf[pos+113] << decimation_shifts::pre64, + buf[pos+114] << decimation_shifts::pre64, + buf[pos+115] << decimation_shifts::pre64, + buf[pos+116] << decimation_shifts::pre64, + buf[pos+117] << decimation_shifts::pre64, + buf[pos+118] << decimation_shifts::pre64, + buf[pos+119] << decimation_shifts::pre64, + &buf2[56]); + + m_decimator2.myDecimateInf( + buf[pos+120] << decimation_shifts::pre64, + buf[pos+121] << decimation_shifts::pre64, + buf[pos+122] << decimation_shifts::pre64, + buf[pos+123] << decimation_shifts::pre64, + buf[pos+124] << decimation_shifts::pre64, + buf[pos+125] << decimation_shifts::pre64, + buf[pos+126] << decimation_shifts::pre64, + buf[pos+127] << decimation_shifts::pre64, + &buf2[60]); + + m_decimator2.myDecimateInf( + buf[pos+128] << decimation_shifts::pre64, + buf[pos+129] << decimation_shifts::pre64, + buf[pos+130] << decimation_shifts::pre64, + buf[pos+131] << decimation_shifts::pre64, + buf[pos+132] << decimation_shifts::pre64, + buf[pos+133] << decimation_shifts::pre64, + buf[pos+134] << decimation_shifts::pre64, + buf[pos+135] << decimation_shifts::pre64, + &buf2[64]); + + m_decimator2.myDecimateInf( + buf[pos+136] << decimation_shifts::pre64, + buf[pos+137] << decimation_shifts::pre64, + buf[pos+138] << decimation_shifts::pre64, + buf[pos+139] << decimation_shifts::pre64, + buf[pos+140] << decimation_shifts::pre64, + buf[pos+141] << decimation_shifts::pre64, + buf[pos+142] << decimation_shifts::pre64, + buf[pos+143] << decimation_shifts::pre64, + &buf2[68]); + + m_decimator2.myDecimateInf( + buf[pos+144] << decimation_shifts::pre64, + buf[pos+145] << decimation_shifts::pre64, + buf[pos+146] << decimation_shifts::pre64, + buf[pos+147] << decimation_shifts::pre64, + buf[pos+148] << decimation_shifts::pre64, + buf[pos+149] << decimation_shifts::pre64, + buf[pos+150] << decimation_shifts::pre64, + buf[pos+151] << decimation_shifts::pre64, + &buf2[72]); + + m_decimator2.myDecimateInf( + buf[pos+152] << decimation_shifts::pre64, + buf[pos+153] << decimation_shifts::pre64, + buf[pos+154] << decimation_shifts::pre64, + buf[pos+155] << decimation_shifts::pre64, + buf[pos+156] << decimation_shifts::pre64, + buf[pos+157] << decimation_shifts::pre64, + buf[pos+158] << decimation_shifts::pre64, + buf[pos+159] << decimation_shifts::pre64, + &buf2[76]); + + m_decimator2.myDecimateInf( + buf[pos+160] << decimation_shifts::pre64, + buf[pos+161] << decimation_shifts::pre64, + buf[pos+162] << decimation_shifts::pre64, + buf[pos+163] << decimation_shifts::pre64, + buf[pos+164] << decimation_shifts::pre64, + buf[pos+165] << decimation_shifts::pre64, + buf[pos+166] << decimation_shifts::pre64, + buf[pos+167] << decimation_shifts::pre64, + &buf2[80]); + + m_decimator2.myDecimateInf( + buf[pos+168] << decimation_shifts::pre64, + buf[pos+169] << decimation_shifts::pre64, + buf[pos+170] << decimation_shifts::pre64, + buf[pos+171] << decimation_shifts::pre64, + buf[pos+172] << decimation_shifts::pre64, + buf[pos+173] << decimation_shifts::pre64, + buf[pos+174] << decimation_shifts::pre64, + buf[pos+175] << decimation_shifts::pre64, + &buf2[84]); + + m_decimator2.myDecimateInf( + buf[pos+176] << decimation_shifts::pre64, + buf[pos+177] << decimation_shifts::pre64, + buf[pos+178] << decimation_shifts::pre64, + buf[pos+179] << decimation_shifts::pre64, + buf[pos+180] << decimation_shifts::pre64, + buf[pos+181] << decimation_shifts::pre64, + buf[pos+182] << decimation_shifts::pre64, + buf[pos+183] << decimation_shifts::pre64, + &buf2[88]); + + m_decimator2.myDecimateInf( + buf[pos+184] << decimation_shifts::pre64, + buf[pos+185] << decimation_shifts::pre64, + buf[pos+186] << decimation_shifts::pre64, + buf[pos+187] << decimation_shifts::pre64, + buf[pos+188] << decimation_shifts::pre64, + buf[pos+189] << decimation_shifts::pre64, + buf[pos+190] << decimation_shifts::pre64, + buf[pos+191] << decimation_shifts::pre64, + &buf2[92]); + + m_decimator2.myDecimateInf( + buf[pos+192] << decimation_shifts::pre64, + buf[pos+193] << decimation_shifts::pre64, + buf[pos+194] << decimation_shifts::pre64, + buf[pos+195] << decimation_shifts::pre64, + buf[pos+196] << decimation_shifts::pre64, + buf[pos+197] << decimation_shifts::pre64, + buf[pos+198] << decimation_shifts::pre64, + buf[pos+199] << decimation_shifts::pre64, + &buf2[96]); + + m_decimator2.myDecimateInf( + buf[pos+200] << decimation_shifts::pre64, + buf[pos+201] << decimation_shifts::pre64, + buf[pos+202] << decimation_shifts::pre64, + buf[pos+203] << decimation_shifts::pre64, + buf[pos+204] << decimation_shifts::pre64, + buf[pos+205] << decimation_shifts::pre64, + buf[pos+206] << decimation_shifts::pre64, + buf[pos+207] << decimation_shifts::pre64, + &buf2[100]); + + m_decimator2.myDecimateInf( + buf[pos+208] << decimation_shifts::pre64, + buf[pos+209] << decimation_shifts::pre64, + buf[pos+210] << decimation_shifts::pre64, + buf[pos+211] << decimation_shifts::pre64, + buf[pos+212] << decimation_shifts::pre64, + buf[pos+213] << decimation_shifts::pre64, + buf[pos+214] << decimation_shifts::pre64, + buf[pos+215] << decimation_shifts::pre64, + &buf2[104]); + + m_decimator2.myDecimateInf( + buf[pos+216] << decimation_shifts::pre64, + buf[pos+217] << decimation_shifts::pre64, + buf[pos+218] << decimation_shifts::pre64, + buf[pos+219] << decimation_shifts::pre64, + buf[pos+220] << decimation_shifts::pre64, + buf[pos+221] << decimation_shifts::pre64, + buf[pos+222] << decimation_shifts::pre64, + buf[pos+223] << decimation_shifts::pre64, + &buf2[108]); + + m_decimator2.myDecimateInf( + buf[pos+224] << decimation_shifts::pre64, + buf[pos+225] << decimation_shifts::pre64, + buf[pos+226] << decimation_shifts::pre64, + buf[pos+227] << decimation_shifts::pre64, + buf[pos+228] << decimation_shifts::pre64, + buf[pos+229] << decimation_shifts::pre64, + buf[pos+230] << decimation_shifts::pre64, + buf[pos+231] << decimation_shifts::pre64, + &buf2[112]); + + m_decimator2.myDecimateInf( + buf[pos+232] << decimation_shifts::pre64, + buf[pos+233] << decimation_shifts::pre64, + buf[pos+234] << decimation_shifts::pre64, + buf[pos+235] << decimation_shifts::pre64, + buf[pos+236] << decimation_shifts::pre64, + buf[pos+237] << decimation_shifts::pre64, + buf[pos+238] << decimation_shifts::pre64, + buf[pos+239] << decimation_shifts::pre64, + &buf2[116]); + + m_decimator2.myDecimateInf( + buf[pos+240] << decimation_shifts::pre64, + buf[pos+241] << decimation_shifts::pre64, + buf[pos+242] << decimation_shifts::pre64, + buf[pos+243] << decimation_shifts::pre64, + buf[pos+244] << decimation_shifts::pre64, + buf[pos+245] << decimation_shifts::pre64, + buf[pos+246] << decimation_shifts::pre64, + buf[pos+247] << decimation_shifts::pre64, + &buf2[120]); + + m_decimator2.myDecimateInf( + buf[pos+248] << decimation_shifts::pre64, + buf[pos+249] << decimation_shifts::pre64, + buf[pos+250] << decimation_shifts::pre64, + buf[pos+251] << decimation_shifts::pre64, + buf[pos+252] << decimation_shifts::pre64, + buf[pos+253] << decimation_shifts::pre64, + buf[pos+254] << decimation_shifts::pre64, + buf[pos+255] << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateCen( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateCen( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateCen( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateCen( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateCen( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateCen( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateCen( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateCen( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateCen( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateCen( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateCen( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateCen( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateCen( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateCen( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateCen( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateCen( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateCen( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateCen( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateCen( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateCen( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateCen( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateCen( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateCen( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateCen( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateCen( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateCen( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[0] >> decimation_shifts::post64); + (**it).setImag(buf64[1] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[2] >> decimation_shifts::post64); + (**it).setImag(buf64[3] >> decimation_shifts::post64); ++(*it); } } @@ -1734,79 +2115,492 @@ void Decimators::decimate64_inf(SampleVector template void Decimators::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[16], yimag[16]; + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; - for (int pos = 0; pos < len - 127; ) - { - for (int i = 0; i < 16; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); - - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); - - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); - - ++(*it); - } -} - -template -void Decimators::decimate64_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - StorageType xreal[16], yimag[16]; - - for (int pos = 0; pos < len - 63; ) + for (int pos = 0; pos < len - 255; pos += 256) { - for (int i = 0; i < 16; i++) - { - xreal[i] = (bufQ[pos+0] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre32; - yimag[i] = (bufI[pos+2] + bufQ[pos+3] - bufI[pos+0] - bufQ[pos+1]) << decimation_shifts::pre32; - pos += 4; - } + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre64, + buf[pos+1] << decimation_shifts::pre64, + buf[pos+2] << decimation_shifts::pre64, + buf[pos+3] << decimation_shifts::pre64, + buf[pos+4] << decimation_shifts::pre64, + buf[pos+5] << decimation_shifts::pre64, + buf[pos+6] << decimation_shifts::pre64, + buf[pos+7] << decimation_shifts::pre64, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre64, + buf[pos+9] << decimation_shifts::pre64, + buf[pos+10] << decimation_shifts::pre64, + buf[pos+11] << decimation_shifts::pre64, + buf[pos+12] << decimation_shifts::pre64, + buf[pos+13] << decimation_shifts::pre64, + buf[pos+14] << decimation_shifts::pre64, + buf[pos+15] << decimation_shifts::pre64, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre64, + buf[pos+17] << decimation_shifts::pre64, + buf[pos+18] << decimation_shifts::pre64, + buf[pos+19] << decimation_shifts::pre64, + buf[pos+20] << decimation_shifts::pre64, + buf[pos+21] << decimation_shifts::pre64, + buf[pos+22] << decimation_shifts::pre64, + buf[pos+23] << decimation_shifts::pre64, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre64, + buf[pos+25] << decimation_shifts::pre64, + buf[pos+26] << decimation_shifts::pre64, + buf[pos+27] << decimation_shifts::pre64, + buf[pos+28] << decimation_shifts::pre64, + buf[pos+29] << decimation_shifts::pre64, + buf[pos+30] << decimation_shifts::pre64, + buf[pos+31] << decimation_shifts::pre64, + &buf2[12]); - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre64, + buf[pos+33] << decimation_shifts::pre64, + buf[pos+34] << decimation_shifts::pre64, + buf[pos+35] << decimation_shifts::pre64, + buf[pos+36] << decimation_shifts::pre64, + buf[pos+37] << decimation_shifts::pre64, + buf[pos+38] << decimation_shifts::pre64, + buf[pos+39] << decimation_shifts::pre64, + &buf2[16]); - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre64, + buf[pos+41] << decimation_shifts::pre64, + buf[pos+42] << decimation_shifts::pre64, + buf[pos+43] << decimation_shifts::pre64, + buf[pos+44] << decimation_shifts::pre64, + buf[pos+45] << decimation_shifts::pre64, + buf[pos+46] << decimation_shifts::pre64, + buf[pos+47] << decimation_shifts::pre64, + &buf2[20]); + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre64, + buf[pos+49] << decimation_shifts::pre64, + buf[pos+50] << decimation_shifts::pre64, + buf[pos+51] << decimation_shifts::pre64, + buf[pos+52] << decimation_shifts::pre64, + buf[pos+53] << decimation_shifts::pre64, + buf[pos+54] << decimation_shifts::pre64, + buf[pos+55] << decimation_shifts::pre64, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre64, + buf[pos+57] << decimation_shifts::pre64, + buf[pos+58] << decimation_shifts::pre64, + buf[pos+59] << decimation_shifts::pre64, + buf[pos+60] << decimation_shifts::pre64, + buf[pos+61] << decimation_shifts::pre64, + buf[pos+62] << decimation_shifts::pre64, + buf[pos+63] << decimation_shifts::pre64, + &buf2[28]); + + m_decimator2.myDecimateSup( + buf[pos+64] << decimation_shifts::pre64, + buf[pos+65] << decimation_shifts::pre64, + buf[pos+66] << decimation_shifts::pre64, + buf[pos+67] << decimation_shifts::pre64, + buf[pos+68] << decimation_shifts::pre64, + buf[pos+69] << decimation_shifts::pre64, + buf[pos+70] << decimation_shifts::pre64, + buf[pos+71] << decimation_shifts::pre64, + &buf2[32]); + + m_decimator2.myDecimateSup( + buf[pos+72] << decimation_shifts::pre64, + buf[pos+73] << decimation_shifts::pre64, + buf[pos+74] << decimation_shifts::pre64, + buf[pos+75] << decimation_shifts::pre64, + buf[pos+76] << decimation_shifts::pre64, + buf[pos+77] << decimation_shifts::pre64, + buf[pos+78] << decimation_shifts::pre64, + buf[pos+79] << decimation_shifts::pre64, + &buf2[36]); + + m_decimator2.myDecimateSup( + buf[pos+80] << decimation_shifts::pre64, + buf[pos+81] << decimation_shifts::pre64, + buf[pos+82] << decimation_shifts::pre64, + buf[pos+83] << decimation_shifts::pre64, + buf[pos+84] << decimation_shifts::pre64, + buf[pos+85] << decimation_shifts::pre64, + buf[pos+86] << decimation_shifts::pre64, + buf[pos+87] << decimation_shifts::pre64, + &buf2[40]); + + m_decimator2.myDecimateSup( + buf[pos+88] << decimation_shifts::pre64, + buf[pos+89] << decimation_shifts::pre64, + buf[pos+90] << decimation_shifts::pre64, + buf[pos+91] << decimation_shifts::pre64, + buf[pos+92] << decimation_shifts::pre64, + buf[pos+93] << decimation_shifts::pre64, + buf[pos+94] << decimation_shifts::pre64, + buf[pos+95] << decimation_shifts::pre64, + &buf2[44]); + + m_decimator2.myDecimateSup( + buf[pos+96] << decimation_shifts::pre64, + buf[pos+97] << decimation_shifts::pre64, + buf[pos+98] << decimation_shifts::pre64, + buf[pos+99] << decimation_shifts::pre64, + buf[pos+100] << decimation_shifts::pre64, + buf[pos+101] << decimation_shifts::pre64, + buf[pos+102] << decimation_shifts::pre64, + buf[pos+103] << decimation_shifts::pre64, + &buf2[48]); + + m_decimator2.myDecimateSup( + buf[pos+104] << decimation_shifts::pre64, + buf[pos+105] << decimation_shifts::pre64, + buf[pos+106] << decimation_shifts::pre64, + buf[pos+107] << decimation_shifts::pre64, + buf[pos+108] << decimation_shifts::pre64, + buf[pos+109] << decimation_shifts::pre64, + buf[pos+110] << decimation_shifts::pre64, + buf[pos+111] << decimation_shifts::pre64, + &buf2[52]); + + m_decimator2.myDecimateSup( + buf[pos+112] << decimation_shifts::pre64, + buf[pos+113] << decimation_shifts::pre64, + buf[pos+114] << decimation_shifts::pre64, + buf[pos+115] << decimation_shifts::pre64, + buf[pos+116] << decimation_shifts::pre64, + buf[pos+117] << decimation_shifts::pre64, + buf[pos+118] << decimation_shifts::pre64, + buf[pos+119] << decimation_shifts::pre64, + &buf2[56]); + + m_decimator2.myDecimateSup( + buf[pos+120] << decimation_shifts::pre64, + buf[pos+121] << decimation_shifts::pre64, + buf[pos+122] << decimation_shifts::pre64, + buf[pos+123] << decimation_shifts::pre64, + buf[pos+124] << decimation_shifts::pre64, + buf[pos+125] << decimation_shifts::pre64, + buf[pos+126] << decimation_shifts::pre64, + buf[pos+127] << decimation_shifts::pre64, + &buf2[60]); + + m_decimator2.myDecimateSup( + buf[pos+128] << decimation_shifts::pre64, + buf[pos+129] << decimation_shifts::pre64, + buf[pos+130] << decimation_shifts::pre64, + buf[pos+131] << decimation_shifts::pre64, + buf[pos+132] << decimation_shifts::pre64, + buf[pos+133] << decimation_shifts::pre64, + buf[pos+134] << decimation_shifts::pre64, + buf[pos+135] << decimation_shifts::pre64, + &buf2[64]); + + m_decimator2.myDecimateSup( + buf[pos+136] << decimation_shifts::pre64, + buf[pos+137] << decimation_shifts::pre64, + buf[pos+138] << decimation_shifts::pre64, + buf[pos+139] << decimation_shifts::pre64, + buf[pos+140] << decimation_shifts::pre64, + buf[pos+141] << decimation_shifts::pre64, + buf[pos+142] << decimation_shifts::pre64, + buf[pos+143] << decimation_shifts::pre64, + &buf2[68]); + + m_decimator2.myDecimateSup( + buf[pos+144] << decimation_shifts::pre64, + buf[pos+145] << decimation_shifts::pre64, + buf[pos+146] << decimation_shifts::pre64, + buf[pos+147] << decimation_shifts::pre64, + buf[pos+148] << decimation_shifts::pre64, + buf[pos+149] << decimation_shifts::pre64, + buf[pos+150] << decimation_shifts::pre64, + buf[pos+151] << decimation_shifts::pre64, + &buf2[72]); + + m_decimator2.myDecimateSup( + buf[pos+152] << decimation_shifts::pre64, + buf[pos+153] << decimation_shifts::pre64, + buf[pos+154] << decimation_shifts::pre64, + buf[pos+155] << decimation_shifts::pre64, + buf[pos+156] << decimation_shifts::pre64, + buf[pos+157] << decimation_shifts::pre64, + buf[pos+158] << decimation_shifts::pre64, + buf[pos+159] << decimation_shifts::pre64, + &buf2[76]); + + m_decimator2.myDecimateSup( + buf[pos+160] << decimation_shifts::pre64, + buf[pos+161] << decimation_shifts::pre64, + buf[pos+162] << decimation_shifts::pre64, + buf[pos+163] << decimation_shifts::pre64, + buf[pos+164] << decimation_shifts::pre64, + buf[pos+165] << decimation_shifts::pre64, + buf[pos+166] << decimation_shifts::pre64, + buf[pos+167] << decimation_shifts::pre64, + &buf2[80]); + + m_decimator2.myDecimateSup( + buf[pos+168] << decimation_shifts::pre64, + buf[pos+169] << decimation_shifts::pre64, + buf[pos+170] << decimation_shifts::pre64, + buf[pos+171] << decimation_shifts::pre64, + buf[pos+172] << decimation_shifts::pre64, + buf[pos+173] << decimation_shifts::pre64, + buf[pos+174] << decimation_shifts::pre64, + buf[pos+175] << decimation_shifts::pre64, + &buf2[84]); + + m_decimator2.myDecimateSup( + buf[pos+176] << decimation_shifts::pre64, + buf[pos+177] << decimation_shifts::pre64, + buf[pos+178] << decimation_shifts::pre64, + buf[pos+179] << decimation_shifts::pre64, + buf[pos+180] << decimation_shifts::pre64, + buf[pos+181] << decimation_shifts::pre64, + buf[pos+182] << decimation_shifts::pre64, + buf[pos+183] << decimation_shifts::pre64, + &buf2[88]); + + m_decimator2.myDecimateSup( + buf[pos+184] << decimation_shifts::pre64, + buf[pos+185] << decimation_shifts::pre64, + buf[pos+186] << decimation_shifts::pre64, + buf[pos+187] << decimation_shifts::pre64, + buf[pos+188] << decimation_shifts::pre64, + buf[pos+189] << decimation_shifts::pre64, + buf[pos+190] << decimation_shifts::pre64, + buf[pos+191] << decimation_shifts::pre64, + &buf2[92]); + + m_decimator2.myDecimateSup( + buf[pos+192] << decimation_shifts::pre64, + buf[pos+193] << decimation_shifts::pre64, + buf[pos+194] << decimation_shifts::pre64, + buf[pos+195] << decimation_shifts::pre64, + buf[pos+196] << decimation_shifts::pre64, + buf[pos+197] << decimation_shifts::pre64, + buf[pos+198] << decimation_shifts::pre64, + buf[pos+199] << decimation_shifts::pre64, + &buf2[96]); + + m_decimator2.myDecimateSup( + buf[pos+200] << decimation_shifts::pre64, + buf[pos+201] << decimation_shifts::pre64, + buf[pos+202] << decimation_shifts::pre64, + buf[pos+203] << decimation_shifts::pre64, + buf[pos+204] << decimation_shifts::pre64, + buf[pos+205] << decimation_shifts::pre64, + buf[pos+206] << decimation_shifts::pre64, + buf[pos+207] << decimation_shifts::pre64, + &buf2[100]); + + m_decimator2.myDecimateSup( + buf[pos+208] << decimation_shifts::pre64, + buf[pos+209] << decimation_shifts::pre64, + buf[pos+210] << decimation_shifts::pre64, + buf[pos+211] << decimation_shifts::pre64, + buf[pos+212] << decimation_shifts::pre64, + buf[pos+213] << decimation_shifts::pre64, + buf[pos+214] << decimation_shifts::pre64, + buf[pos+215] << decimation_shifts::pre64, + &buf2[104]); + + m_decimator2.myDecimateSup( + buf[pos+216] << decimation_shifts::pre64, + buf[pos+217] << decimation_shifts::pre64, + buf[pos+218] << decimation_shifts::pre64, + buf[pos+219] << decimation_shifts::pre64, + buf[pos+220] << decimation_shifts::pre64, + buf[pos+221] << decimation_shifts::pre64, + buf[pos+222] << decimation_shifts::pre64, + buf[pos+223] << decimation_shifts::pre64, + &buf2[108]); + + m_decimator2.myDecimateSup( + buf[pos+224] << decimation_shifts::pre64, + buf[pos+225] << decimation_shifts::pre64, + buf[pos+226] << decimation_shifts::pre64, + buf[pos+227] << decimation_shifts::pre64, + buf[pos+228] << decimation_shifts::pre64, + buf[pos+229] << decimation_shifts::pre64, + buf[pos+230] << decimation_shifts::pre64, + buf[pos+231] << decimation_shifts::pre64, + &buf2[112]); + + m_decimator2.myDecimateSup( + buf[pos+232] << decimation_shifts::pre64, + buf[pos+233] << decimation_shifts::pre64, + buf[pos+234] << decimation_shifts::pre64, + buf[pos+235] << decimation_shifts::pre64, + buf[pos+236] << decimation_shifts::pre64, + buf[pos+237] << decimation_shifts::pre64, + buf[pos+238] << decimation_shifts::pre64, + buf[pos+239] << decimation_shifts::pre64, + &buf2[116]); + + m_decimator2.myDecimateSup( + buf[pos+240] << decimation_shifts::pre64, + buf[pos+241] << decimation_shifts::pre64, + buf[pos+242] << decimation_shifts::pre64, + buf[pos+243] << decimation_shifts::pre64, + buf[pos+244] << decimation_shifts::pre64, + buf[pos+245] << decimation_shifts::pre64, + buf[pos+246] << decimation_shifts::pre64, + buf[pos+247] << decimation_shifts::pre64, + &buf2[120]); + + m_decimator2.myDecimateSup( + buf[pos+248] << decimation_shifts::pre64, + buf[pos+249] << decimation_shifts::pre64, + buf[pos+250] << decimation_shifts::pre64, + buf[pos+251] << decimation_shifts::pre64, + buf[pos+252] << decimation_shifts::pre64, + buf[pos+253] << decimation_shifts::pre64, + buf[pos+254] << decimation_shifts::pre64, + buf[pos+255] << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateCen( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateCen( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateCen( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateCen( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateCen( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateCen( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateCen( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateCen( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateCen( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateCen( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateCen( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateCen( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateCen( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateCen( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateCen( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateCen( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateCen( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateCen( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateCen( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateCen( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateCen( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateCen( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateCen( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateCen( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateCen( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateCen( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[0] >> decimation_shifts::post64); + (**it).setImag(buf64[1] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[2] >> decimation_shifts::post64); + (**it).setImag(buf64[3] >> decimation_shifts::post64); ++(*it); } } @@ -1834,6 +2628,38 @@ void Decimators::decimate64_sup(SampleVector // } //} +template +void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType xreal[2], yimag[2]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + xreal[0] = buf[pos+2] << decimation_shifts::pre2; + yimag[0] = buf[pos+3] << decimation_shifts::pre2; + xreal[1] = buf[pos+6] << decimation_shifts::pre2; + yimag[1] = buf[pos+7] << decimation_shifts::pre2; + + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + &xreal[0], + &yimag[0], + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + &xreal[1], + &yimag[1]); + + (**it).setReal(xreal[0] >> decimation_shifts::post2); + (**it).setImag(yimag[0] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(xreal[1] >> decimation_shifts::post2); + (**it).setImag(yimag[1] >> decimation_shifts::post2); + ++(*it); + } +} + template void Decimators::decimate2_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { From fd4d2bb64fcbbce1392754af2df0c47186a1954a Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 11:47:13 +0200 Subject: [PATCH 379/956] Common static function to calculate device center frequency from all contributing parameters --- sdrbase/dsp/devicesamplesource.cpp | 43 ++++++++++++++++++++++++++++++ sdrbase/dsp/devicesamplesource.h | 14 ++++++++++ 2 files changed, 57 insertions(+) diff --git a/sdrbase/dsp/devicesamplesource.cpp b/sdrbase/dsp/devicesamplesource.cpp index 4295d2f3b..bc2e8a963 100644 --- a/sdrbase/dsp/devicesamplesource.cpp +++ b/sdrbase/dsp/devicesamplesource.cpp @@ -15,6 +15,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include DeviceSampleSource::DeviceSampleSource() : @@ -39,3 +40,45 @@ void DeviceSampleSource::handleInputMessages() } } } + +qint64 DeviceSampleSource::calculateDeviceCenterFrequency( + quint64 centerFrequency, + qint64 transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + bool transverterMode) +{ + qint64 deviceCenterFrequency = centerFrequency; + deviceCenterFrequency -= transverterMode ? transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + qint64 f_img = deviceCenterFrequency; + + if ((log2Decim == 0) || (fcPos == FC_POS_CENTER)) + { + f_img = deviceCenterFrequency; + } + else + { + if (fcPos == FC_POS_INFRA) + { + deviceCenterFrequency += (devSampleRate / 4); + f_img = deviceCenterFrequency + devSampleRate/2; + } + else if (fcPos == FC_POS_SUPRA) + { + deviceCenterFrequency -= (devSampleRate / 4); + f_img = deviceCenterFrequency - devSampleRate/2; + } + } + + qDebug() << "DeviceSampleSource::calculateDeviceCenterFrequency:" + << " desired center freq: " << centerFrequency << " Hz" + << " device center freq: " << deviceCenterFrequency << " Hz" + << " device sample rate: " << devSampleRate << "S/s" + << " Actual sample rate: " << devSampleRate/(1< Date: Thu, 10 May 2018 11:48:06 +0200 Subject: [PATCH 380/956] PlutoSDR and RTL-SDR input: rework of center frequency setting --- .../plutosdrinput/plutosdrinput.cpp | 49 +++----------- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 67 +++++++------------ 2 files changed, 34 insertions(+), 82 deletions(-) diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index 9cb8827a8..740c86706 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -447,42 +447,21 @@ bool PlutoSDRInput::applySettings(const PlutoSDRInputSettings& settings, bool fo if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_log2Decim != settings.m_log2Decim) + || (m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_transverterMode != settings.m_transverterMode) || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) || force) { - qint64 deviceCenterFrequency = settings.m_centerFrequency; - deviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; - qint64 f_img = deviceCenterFrequency; - quint32 devSampleRate = settings.m_devSampleRate; + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + settings.m_transverterDeltaFrequency, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_devSampleRate, + settings.m_transverterMode); - if ((settings.m_log2Decim == 0) || (settings.m_fcPos == PlutoSDRInputSettings::FC_POS_CENTER)) - { - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == PlutoSDRInputSettings::FC_POS_INFRA) - { - deviceCenterFrequency += (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == PlutoSDRInputSettings::FC_POS_SUPRA) - { - deviceCenterFrequency -= (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } - - deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; - - if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency) - || (m_settings.m_transverterMode != settings.m_transverterMode) - || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency)) - { - params.push_back(QString(tr("out_altvoltage0_RX_LO_frequency=%1").arg(deviceCenterFrequency)).toStdString()); - paramsToSet = true; - forwardChangeOwnDSP = true; - } + params.push_back(QString(tr("out_altvoltage0_RX_LO_frequency=%1").arg(deviceCenterFrequency)).toStdString()); + paramsToSet = true; + forwardChangeOwnDSP = true; if ((m_settings.m_fcPos != settings.m_fcPos) || force) { @@ -492,12 +471,6 @@ bool PlutoSDRInput::applySettings(const PlutoSDRInputSettings& settings, bool fo qDebug() << "PlutoSDRInput::applySettings: set fcPos to " << settings.m_fcPos; } } - - qDebug() << "PlutoSDRInput::applySettings: center freq: " << settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "S/s" - << " Actual sample rate: " << devSampleRate/(1<setFcPos((int) m_settings.m_fcPos); } + + qDebug() << "RTLSDRInput::applySettings: set fc pos (enum) to " << (int) m_settings.m_fcPos; } if (m_dev != 0) { - if (rtlsdr_set_center_freq( m_dev, deviceCenterFrequency ) != 0) - { - qDebug("rtlsdr_set_center_freq(%lld) failed", deviceCenterFrequency); - } - else - { - qDebug() << "RTLSDRInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "S/s" - << " Actual sample rate: " << devSampleRate/(1<setFcPos((int) m_settings.m_fcPos); - } - - qDebug() << "RTLSDRInput: set fc pos (enum) to " << (int) m_settings.m_fcPos; - } - if ((m_settings.m_noModMode != settings.m_noModMode) || force) { m_settings.m_noModMode = settings.m_noModMode; - qDebug() << "RTLSDRInput: set noModMode to " << m_settings.m_noModMode; + qDebug() << "RTLSDRInput::applySettings: set noModMode to " << m_settings.m_noModMode; // Direct Modes: 0: off, 1: I, 2: Q, 3: NoMod. if (m_settings.m_noModMode) { From de2e018e427f4c4c7eaf2299087c5bfd0ce86859 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 13:16:27 +0200 Subject: [PATCH 381/956] Test source: rework of center frequency setting --- .../testsource/testsourceinput.cpp | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 4c247fe14..5eaf39a63 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -259,26 +259,25 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, bool for if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_frequencyShift != settings.m_frequencyShift) + || (m_settings.m_sampleRate != settings.m_sampleRate) || (m_settings.m_log2Decim != settings.m_log2Decim) || force) { - qint64 deviceCenterFrequency = settings.m_centerFrequency; + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, // no transverter mode + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_sampleRate); + int frequencyShift = settings.m_frequencyShift; - qint64 f_img = deviceCenterFrequency; quint32 devSampleRate = settings.m_sampleRate; if (settings.m_log2Decim != 0) { - if (settings.m_fcPos == TestSourceSettings::FC_POS_INFRA) - { - deviceCenterFrequency += (devSampleRate / 4); + if (settings.m_fcPos == TestSourceSettings::FC_POS_INFRA) { frequencyShift -= (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == TestSourceSettings::FC_POS_SUPRA) - { - deviceCenterFrequency -= (devSampleRate / 4); + } else if (settings.m_fcPos == TestSourceSettings::FC_POS_SUPRA) { frequencyShift += (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; } } @@ -291,7 +290,6 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, bool for << " device center freq: " << deviceCenterFrequency << " Hz" << " device sample rate: " << devSampleRate << "Hz" << " Actual sample rate: " << devSampleRate/(1< Date: Thu, 10 May 2018 14:33:17 +0200 Subject: [PATCH 382/956] Removed Fc pos references in Sample Sink side --- plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp | 3 +-- plugins/samplesink/bladerfoutput/bladerfoutputthread.h | 1 - plugins/samplesink/limesdroutput/limesdroutputthread.cpp | 8 +------- plugins/samplesink/limesdroutput/limesdroutputthread.h | 2 -- 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp index daa028e66..79f26f92d 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp @@ -26,8 +26,7 @@ BladerfOutputThread::BladerfOutputThread(struct bladerf* dev, SampleSourceFifo* m_running(false), m_dev(dev), m_sampleFifo(sampleFifo), - m_log2Interp(0), - m_fcPos(0) + m_log2Interp(0) { std::fill(m_buf, m_buf + 2*BLADERFOUTPUT_BLOCKSIZE, 0); } diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputthread.h b/plugins/samplesink/bladerfoutput/bladerfoutputthread.h index 4a93140e0..434ccaf83 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputthread.h +++ b/plugins/samplesink/bladerfoutput/bladerfoutputthread.h @@ -49,7 +49,6 @@ private: SampleSourceFifo* m_sampleFifo; unsigned int m_log2Interp; - int m_fcPos; Interpolators m_interpolators; diff --git a/plugins/samplesink/limesdroutput/limesdroutputthread.cpp b/plugins/samplesink/limesdroutput/limesdroutputthread.cpp index 3dac3cf8f..f16eb2c7c 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputthread.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputthread.cpp @@ -25,8 +25,7 @@ LimeSDROutputThread::LimeSDROutputThread(lms_stream_t* stream, SampleSourceFifo* m_running(false), m_stream(stream), m_sampleFifo(sampleFifo), - m_log2Interp(0), - m_fcPos(LimeSDROutputSettings::FC_POS_CENTER) + m_log2Interp(0) { std::fill(m_buf, m_buf + 2*LIMESDROUTPUT_BLOCKSIZE, 0); } @@ -74,11 +73,6 @@ void LimeSDROutputThread::setLog2Interpolation(unsigned int log2_interp) m_log2Interp = log2_interp; } -void LimeSDROutputThread::setFcPos(int fcPos) -{ - m_fcPos = fcPos; -} - void LimeSDROutputThread::run() { int res; diff --git a/plugins/samplesink/limesdroutput/limesdroutputthread.h b/plugins/samplesink/limesdroutput/limesdroutputthread.h index e543d510c..2761f45d5 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputthread.h +++ b/plugins/samplesink/limesdroutput/limesdroutputthread.h @@ -42,7 +42,6 @@ public: virtual void setDeviceSampleRate(int __attribute__((unused)) sampleRate) {} virtual bool isRunning() { return m_running; } void setLog2Interpolation(unsigned int log2_ioterp); - void setFcPos(int fcPos); private: QMutex m_startWaitMutex; @@ -54,7 +53,6 @@ private: SampleSourceFifo* m_sampleFifo; unsigned int m_log2Interp; // soft decimation - int m_fcPos; Interpolators m_interpolators; From a465812132c54edcea628b03202af66b7ba80fdc Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 14:51:05 +0200 Subject: [PATCH 383/956] Airspy input: rework of center frequency setting --- plugins/samplesource/airspy/airspyinput.cpp | 41 +++++---------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 9fd0381c1..72e062830 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -407,45 +407,22 @@ bool AirspyInput::applySettings(const AirspySettings& settings, bool force) || (m_settings.m_transverterMode != settings.m_transverterMode) || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) || force) { + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + settings.m_transverterDeltaFrequency, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + m_sampleRates[m_settings.m_devSampleRateIndex], + settings.m_transverterMode); + m_settings.m_centerFrequency = settings.m_centerFrequency; m_settings.m_log2Decim = settings.m_log2Decim; m_settings.m_transverterMode = settings.m_transverterMode; m_settings.m_transverterDeltaFrequency = settings.m_transverterDeltaFrequency; m_settings.m_LOppmTenths = settings.m_LOppmTenths; - qint64 deviceCenterFrequency = m_settings.m_centerFrequency; - deviceCenterFrequency -= m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency : 0; - deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; - qint64 f_img = deviceCenterFrequency; - quint32 devSampleRate = m_sampleRates[m_settings.m_devSampleRateIndex]; - - if ((m_settings.m_log2Decim == 0) || (settings.m_fcPos == AirspySettings::FC_POS_CENTER)) - { - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == AirspySettings::FC_POS_INFRA) - { - deviceCenterFrequency += (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == AirspySettings::FC_POS_SUPRA) - { - deviceCenterFrequency -= (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } - - if (m_dev != 0) - { + if (m_dev != 0) { setDeviceCenterFrequency(deviceCenterFrequency); - - qDebug() << "AirspyInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "Hz" - << " Actual sample rate: " << devSampleRate/(1< Date: Thu, 10 May 2018 14:58:07 +0200 Subject: [PATCH 384/956] BladeRF input: rework of center frequency setting --- .../bladerfinput/bladerfinput.cpp | 50 +++++-------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 1a9f5ef1a..5fa0dbafd 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -534,55 +534,29 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc } if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_log2Decim != settings.m_log2Decim) || force) { + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_devSampleRate); + m_settings.m_centerFrequency = settings.m_centerFrequency; m_settings.m_log2Decim = settings.m_log2Decim; m_settings.m_fcPos = settings.m_fcPos; - qint64 deviceCenterFrequency = m_settings.m_centerFrequency; - deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; - qint64 f_img = deviceCenterFrequency; - qint64 f_cut = deviceCenterFrequency + m_settings.m_bandwidth/2; - quint32 devSampleRate = m_settings.m_devSampleRate; - forwardChange = true; - if ((m_settings.m_log2Decim == 0) || (settings.m_fcPos == BladeRFInputSettings::FC_POS_CENTER)) - { - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == BladeRFInputSettings::FC_POS_INFRA) - { - deviceCenterFrequency += (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == BladeRFInputSettings::FC_POS_SUPRA) - { - deviceCenterFrequency -= (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } - if (m_dev != 0) { - if (bladerf_set_frequency( m_dev, BLADERF_MODULE_RX, deviceCenterFrequency ) != 0) - { - qDebug("BladerfInput::applySettings: bladerf_set_frequency(%lld) failed", m_settings.m_centerFrequency); - } - else - { - qDebug() << "BladerfInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << m_settings.m_devSampleRate << "S/s" - << " Actual sample rate: " << m_settings.m_devSampleRate/(1< Date: Thu, 10 May 2018 15:06:46 +0200 Subject: [PATCH 385/956] HackRF input: rework of center frequency setting --- .../samplesource/hackrfinput/hackrfinput.cpp | 40 +++++-------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index 302c7a9d2..dee2fe047 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -366,10 +366,6 @@ bool HackRFInput::applySettings(const HackRFInputSettings& settings, bool force) } } - qint64 deviceCenterFrequency = settings.m_centerFrequency; - qint64 f_img = deviceCenterFrequency; - quint32 devSampleRate = settings.m_devSampleRate; - if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency)) // forward delta to buddy if necessary { if (m_settings.m_linkTxFrequency && (m_deviceAPI->getSinkBuddies().size() > 0)) @@ -389,38 +385,20 @@ bool HackRFInput::applySettings(const HackRFInputSettings& settings, bool force) } if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || + (m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_LOppmTenths != settings.m_LOppmTenths) || (m_settings.m_log2Decim != settings.m_log2Decim) || (m_settings.m_fcPos != settings.m_fcPos) || force) { - if ((settings.m_log2Decim == 0) || (settings.m_fcPos == HackRFInputSettings::FC_POS_CENTER)) - { - deviceCenterFrequency = settings.m_centerFrequency; - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == HackRFInputSettings::FC_POS_INFRA) - { - deviceCenterFrequency = settings.m_centerFrequency + (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == HackRFInputSettings::FC_POS_SUPRA) - { - deviceCenterFrequency = settings.m_centerFrequency - (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_devSampleRate); - if (m_dev != 0) - { + if (m_dev != 0) { setDeviceCenterFrequency(deviceCenterFrequency); - - qDebug() << "HackRFInput::applySettings: center freq: " << settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "Hz" - << " Actual sample rate: " << devSampleRate/(1<handleMessage(*notif); // forward to file sink m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); From 6a5a35285ee784a1e5ad0f90138bf860cb0e2bbe Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 15:14:43 +0200 Subject: [PATCH 386/956] SDRplay input: rework of center frequency setting --- plugins/samplesource/sdrplay/sdrplayinput.cpp | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index 46292c333..015f1d1fa 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -532,44 +532,24 @@ bool SDRPlayInput::applySettings(const SDRPlaySettings& settings, bool forwardCh || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_log2Decim != settings.m_log2Decim) || force) { + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + SDRPlaySampleRates::getRate(m_settings.m_devSampleRateIndex)); + m_settings.m_centerFrequency = settings.m_centerFrequency; m_settings.m_LOppmTenths = settings.m_LOppmTenths; m_settings.m_fcPos = settings.m_fcPos; m_settings.m_log2Decim = settings.m_log2Decim; - qint64 deviceCenterFrequency = m_settings.m_centerFrequency; - qint64 f_img = deviceCenterFrequency; - quint32 devSampleRate = SDRPlaySampleRates::getRate(m_settings.m_devSampleRateIndex); forwardChange = true; - if ((m_settings.m_log2Decim == 0) || (settings.m_fcPos == SDRPlaySettings::FC_POS_CENTER)) - { - deviceCenterFrequency = m_settings.m_centerFrequency; - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == SDRPlaySettings::FC_POS_INFRA) - { - deviceCenterFrequency = m_settings.m_centerFrequency + (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == SDRPlaySettings::FC_POS_SUPRA) - { - deviceCenterFrequency = m_settings.m_centerFrequency - (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } - if(m_dev != 0) { - if (setDeviceCenterFrequency(deviceCenterFrequency)) - { - qDebug() << "SDRPlayInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "Hz" - << " Actual sample rate: " << devSampleRate/(1< Date: Thu, 10 May 2018 22:17:39 +0200 Subject: [PATCH 387/956] Inf/Sup frequency shift scheme change to set bandwidth closer to device center frequency --- plugins/samplesource/bladerfinput/readme.md | 11 +- plugins/samplesource/hackrfinput/readme.md | 21 +- plugins/samplesource/plutosdrinput/readme.md | 13 +- plugins/samplesource/rtlsdr/readme.md | 13 +- plugins/samplesource/sdrplay/readme.md | 13 +- plugins/samplesource/testsource/readme.md | 11 +- .../testsource/testsourceinput.cpp | 9 +- sdrbase/dsp/decimators.h | 371 ++++++++---------- sdrbase/dsp/devicesamplesource.cpp | 60 ++- sdrbase/dsp/devicesamplesource.h | 5 + sdrbase/dsp/inthalfbandfiltereo.h | 48 +-- 11 files changed, 306 insertions(+), 269 deletions(-) diff --git a/plugins/samplesource/bladerfinput/readme.md b/plugins/samplesource/bladerfinput/readme.md index 94eea48b4..2f0e8f01e 100644 --- a/plugins/samplesource/bladerfinput/readme.md +++ b/plugins/samplesource/bladerfinput/readme.md @@ -72,9 +72,14 @@ The I/Q stream from the BladeRF ADC is downsampled by a power of two before bein Possible values are: - - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency - - **Inf**: the decimation operation takes place around the center of the lower half of the BladeRF Rx passband. - - **Sup**: the decimation operation takes place around the center of the upper half of the BladeRF Rx passband. + - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

7: Rx filter bandwidth

diff --git a/plugins/samplesource/hackrfinput/readme.md b/plugins/samplesource/hackrfinput/readme.md index 02e220dc5..8b9df7c7e 100644 --- a/plugins/samplesource/hackrfinput/readme.md +++ b/plugins/samplesource/hackrfinput/readme.md @@ -75,11 +75,7 @@ This is the HackRF device ADC sample rate in S/s. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -

7: Rx filter bandwidth

- -This is the Rx filter bandwidth in kHz. Possible values are: 1750, 2500, 3500, 5000, 5500, 6000, 7000, 8000, 9000, 10000, 12000, 14000, 15000, 20000, 24000, 28000 kHz. - -

8: Decimation factor

+

7: Decimation factor

The device stream from the HackRF is decimated to obtain the baseband stream. Possible values are: @@ -90,6 +86,21 @@ The device stream from the HackRF is decimated to obtain the baseband stream. Po - **16**: divide device stream sample rate by 16 - **32**: divide device stream sample rate by 32 +

8: Baseband center frequency position relative the the HackRF Rx center frequency

+ + - **Cen**: the decimation operation takes place around the HackRF Rx center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband. + +

9: Rx filter bandwidth

+ +This is the Rx filter bandwidth in kHz. Possible values are: 1750, 2500, 3500, 5000, 5500, 6000, 7000, 8000, 9000, 10000, 12000, 14000, 15000, 20000, 24000, 28000 kHz. +

10: Internal LNA gain

The LNA gain can be adjusted from 0 dB to 40 dB in 8 dB steps. diff --git a/plugins/samplesource/plutosdrinput/readme.md b/plugins/samplesource/plutosdrinput/readme.md index cc4507d74..11614c512 100644 --- a/plugins/samplesource/plutosdrinput/readme.md +++ b/plugins/samplesource/plutosdrinput/readme.md @@ -106,11 +106,16 @@ Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. The I/Q stream from the PlutoSDR is downsampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. -

6: Decimated bandpass center frequency placement

+

6: Decimated bandpass center frequency position relative the the PlutoSDR Rx center frequency

- - **Inf**: Infradyne: the decimation takes place in the lower sideband - - **Sup**: Supradyne: the decimation takes place in the lower sideband - - **Cen**: Centered: the decimation takes place around the center + - **Cen**: the decimation operation takes place around the PlutoSDR Rx center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

7: Antenna (input) connection

diff --git a/plugins/samplesource/rtlsdr/readme.md b/plugins/samplesource/rtlsdr/readme.md index cd0a60941..ebe304077 100644 --- a/plugins/samplesource/rtlsdr/readme.md +++ b/plugins/samplesource/rtlsdr/readme.md @@ -49,13 +49,16 @@ These buttons control the local DSP auto correction options: - **DC**: auto remove DC component - **IQ**: auto make I/Q balance. The DC correction must be enabled for this to be effective. -

4: Baseband center frequency position relative the center frequency

+

4: Decimated bandpass center frequency position relative the RTL-SDR center frequency

-Possible values are: + - **Cen**: the decimation operation takes place around the RTL-SDR center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: - - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency - - **Inf**: the decimation operation takes place around the center of the lower half of the BladeRF Rx passband. - - **Sup**: the decimation operation takes place around the center of the upper half of the BladeRF Rx passband. + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

4a: Transverter mode open dialog

diff --git a/plugins/samplesource/sdrplay/readme.md b/plugins/samplesource/sdrplay/readme.md index c955f62c1..8c99f42aa 100644 --- a/plugins/samplesource/sdrplay/readme.md +++ b/plugins/samplesource/sdrplay/readme.md @@ -75,13 +75,16 @@ You have the choice between various sample rates from 1536 to 8192 kHz. Some val Decimation in powers of two from 1 (no decimation) to 64. -

9. Center frequency position

+

9: Decimated bandpass center frequency position relative the SDRplay center frequency

-Relative position of center frequency of decimated baseband relative to the original: + - **Cen**: the decimation operation takes place around the SDRplay center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: - - Inf: infradyne i.e. in the lower half of original baseband: to be used with non zero IFs - - Cen: Centered i.e. around the center of the original baseband - - Sup: Supradyne i.e. in the upper half of original baseband + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

10. Tuner gain mode

diff --git a/plugins/samplesource/testsource/readme.md b/plugins/samplesource/testsource/readme.md index 912a86bd4..8f64bffbd 100644 --- a/plugins/samplesource/testsource/readme.md +++ b/plugins/samplesource/testsource/readme.md @@ -56,11 +56,14 @@ This exercises the decimation chain.

2.3: Baseband center frequency position relative the center frequency

-Possible values are: + - **Cen**: the decimation operation takes place around the center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: - - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency - - **Inf**: the decimation operation takes place around the center of the lower half of the BladeRF Rx passband. - - **Sup**: the decimation operation takes place around the center of the upper half of the BladeRF Rx passband. + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

2.4: Sample size

diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 5eaf39a63..65e39aa89 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -274,11 +274,10 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, bool for if (settings.m_log2Decim != 0) { - if (settings.m_fcPos == TestSourceSettings::FC_POS_INFRA) { - frequencyShift -= (devSampleRate / 4); - } else if (settings.m_fcPos == TestSourceSettings::FC_POS_SUPRA) { - frequencyShift += (devSampleRate / 4); - } + frequencyShift += DeviceSampleSource::calculateFrequencyShift( + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_sampleRate); } if (m_testSourceThread != 0) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index e94448b6c..18eec7b76 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -463,31 +463,27 @@ void Decimators::decimate2_u(SampleVector::i template void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[2], yimag[2]; + StorageType buf2[4]; for (int pos = 0; pos < len - 7; pos += 8) { - xreal[0] = buf[pos+2] << decimation_shifts::pre2; - yimag[0] = buf[pos+3] << decimation_shifts::pre2; - xreal[1] = buf[pos+6] << decimation_shifts::pre2; - yimag[1] = buf[pos+7] << decimation_shifts::pre2; - m_decimator2.myDecimateInf( buf[pos+0] << decimation_shifts::pre2, buf[pos+1] << decimation_shifts::pre2, - &xreal[0], - &yimag[0], + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, buf[pos+4] << decimation_shifts::pre2, buf[pos+5] << decimation_shifts::pre2, - &xreal[1], - &yimag[1]); + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); - (**it).setReal(xreal[0] >> decimation_shifts::post2); - (**it).setImag(yimag[0] >> decimation_shifts::post2); + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); ++(*it); - (**it).setReal(xreal[1] >> decimation_shifts::post2); - (**it).setImag(yimag[1] >> decimation_shifts::post2); + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); ++(*it); } } @@ -495,31 +491,27 @@ void Decimators::decimate2_inf(SampleVector: template void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[2], yimag[2]; + StorageType buf2[4]; for (int pos = 0; pos < len - 7; pos += 8) { - xreal[0] = buf[pos+2] << decimation_shifts::pre2; - yimag[0] = buf[pos+3] << decimation_shifts::pre2; - xreal[1] = buf[pos+6] << decimation_shifts::pre2; - yimag[1] = buf[pos+7] << decimation_shifts::pre2; - m_decimator2.myDecimateSup( buf[pos+0] << decimation_shifts::pre2, buf[pos+1] << decimation_shifts::pre2, - &xreal[0], - &yimag[0], + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, buf[pos+4] << decimation_shifts::pre2, buf[pos+5] << decimation_shifts::pre2, - &xreal[1], - &yimag[1]); + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); - (**it).setReal(xreal[0] >> decimation_shifts::post2); - (**it).setImag(yimag[0] >> decimation_shifts::post2); + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); ++(*it); - (**it).setReal(xreal[1] >> decimation_shifts::post2); - (**it).setImag(yimag[1] >> decimation_shifts::post2); + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); ++(*it); } } @@ -549,114 +541,99 @@ void Decimators::decimate2_sup(SampleVector: template void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[4], yimag[4]; + StorageType buf2[8], buf4[4]; for (int pos = 0; pos < len - 15; pos += 16) { - xreal[0] = buf[pos+2] << decimation_shifts::pre4; - yimag[0] = buf[pos+3] << decimation_shifts::pre4; - xreal[1] = buf[pos+6] << decimation_shifts::pre4; - yimag[1] = buf[pos+7] << decimation_shifts::pre4; - m_decimator2.myDecimateInf( buf[pos+0] << decimation_shifts::pre4, buf[pos+1] << decimation_shifts::pre4, - &xreal[0], - &yimag[0], + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, buf[pos+4] << decimation_shifts::pre4, buf[pos+5] << decimation_shifts::pre4, - &xreal[1], - &yimag[1]); - - xreal[2] = buf[pos+10] << decimation_shifts::pre4; - yimag[2] = buf[pos+11] << decimation_shifts::pre4; - xreal[3] = buf[pos+14] << decimation_shifts::pre4; - yimag[3] = buf[pos+15] << decimation_shifts::pre4; + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); m_decimator2.myDecimateInf( buf[pos+8] << decimation_shifts::pre4, buf[pos+9] << decimation_shifts::pre4, - &xreal[2], - &yimag[2], + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, buf[pos+12] << decimation_shifts::pre4, buf[pos+13] << decimation_shifts::pre4, - &xreal[3], - &yimag[3]); + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); - m_decimator4.myDecimateCen( - xreal[0], - yimag[0], - &xreal[1], - &yimag[1], - xreal[2], - yimag[2], - &xreal[3], - &yimag[3]); + m_decimator4.myDecimateSup( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); - (**it).setReal(xreal[1] >> decimation_shifts::post4); - (**it).setImag(yimag[1] >> decimation_shifts::post4); + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); ++(*it); - (**it).setReal(xreal[3] >> decimation_shifts::post4); - (**it).setImag(yimag[3] >> decimation_shifts::post4); + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); ++(*it); } } - template void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[4], yimag[4]; + StorageType buf2[8], buf4[4]; for (int pos = 0; pos < len - 15; pos += 16) { - xreal[0] = buf[pos+2] << decimation_shifts::pre4; - yimag[0] = buf[pos+3] << decimation_shifts::pre4; - xreal[1] = buf[pos+6] << decimation_shifts::pre4; - yimag[1] = buf[pos+7] << decimation_shifts::pre4; - m_decimator2.myDecimateSup( buf[pos+0] << decimation_shifts::pre4, buf[pos+1] << decimation_shifts::pre4, - &xreal[0], - &yimag[0], + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, buf[pos+4] << decimation_shifts::pre4, buf[pos+5] << decimation_shifts::pre4, - &xreal[1], - &yimag[1]); - - xreal[2] = buf[pos+10] << decimation_shifts::pre4; - yimag[2] = buf[pos+11] << decimation_shifts::pre4; - xreal[3] = buf[pos+14] << decimation_shifts::pre4; - yimag[3] = buf[pos+15] << decimation_shifts::pre4; + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); m_decimator2.myDecimateSup( buf[pos+8] << decimation_shifts::pre4, buf[pos+9] << decimation_shifts::pre4, - &xreal[2], - &yimag[2], + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, buf[pos+12] << decimation_shifts::pre4, buf[pos+13] << decimation_shifts::pre4, - &xreal[3], - &yimag[3]); + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); - m_decimator4.myDecimateCen( - xreal[0], - yimag[0], - &xreal[1], - &yimag[1], - xreal[2], - yimag[2], - &xreal[3], - &yimag[3]); + m_decimator4.myDecimateInf( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); - (**it).setReal(xreal[1] >> decimation_shifts::post4); - (**it).setImag(yimag[1] >> decimation_shifts::post4); + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); ++(*it); - (**it).setReal(xreal[3] >> decimation_shifts::post4); - (**it).setImag(yimag[3] >> decimation_shifts::post4); + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); ++(*it); } } @@ -752,11 +729,11 @@ void Decimators::decimate8_inf(SampleVector: buf[pos+31] << decimation_shifts::pre8, &buf2[12]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[0], &buf4[0]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[8], &buf4[4]); @@ -825,11 +802,11 @@ void Decimators::decimate8_sup(SampleVector: buf[pos+31] << decimation_shifts::pre8, &buf2[12]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[0], &buf4[0]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[8], &buf4[4]); @@ -942,27 +919,27 @@ void Decimators::decimate16_inf(SampleVector buf[pos+63] << decimation_shifts::pre16, &buf2[28]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[0], &buf4[0]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[8], &buf4[4]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[16], &buf4[8]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[24], &buf4[12]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[0], &buf8[0]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[8], &buf8[4]); @@ -1075,27 +1052,27 @@ void Decimators::decimate16_sup(SampleVector buf[pos+63] << decimation_shifts::pre16, &buf2[28]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[0], &buf4[0]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[8], &buf4[4]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[16], &buf4[8]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[24], &buf4[12]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[0], &buf8[0]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[8], &buf8[4]); @@ -1296,59 +1273,59 @@ void Decimators::decimate32_inf(SampleVector buf[pos+127] << decimation_shifts::pre32, &buf2[60]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[0], &buf4[0]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[8], &buf4[4]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[16], &buf4[8]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[24], &buf4[12]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[32], &buf4[16]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[40], &buf4[20]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[48], &buf4[24]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[56], &buf4[28]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[0], &buf8[0]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[8], &buf8[4]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[16], &buf8[8]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[24], &buf8[12]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateSup( &buf8[0], &buf16[0]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateSup( &buf8[8], &buf16[4]); @@ -1549,59 +1526,59 @@ void Decimators::decimate32_sup(SampleVector buf[pos+127] << decimation_shifts::pre32, &buf2[60]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[0], &buf4[0]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[8], &buf4[4]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[16], &buf4[8]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[24], &buf4[12]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[32], &buf4[16]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[40], &buf4[20]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[48], &buf4[24]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[56], &buf4[28]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[0], &buf8[0]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[8], &buf8[4]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[16], &buf8[8]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[24], &buf8[12]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateInf( &buf8[0], &buf16[0]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateInf( &buf8[8], &buf16[4]); @@ -1978,123 +1955,123 @@ void Decimators::decimate64_inf(SampleVector buf[pos+255] << decimation_shifts::pre64, &buf2[124]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[0], &buf4[0]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[8], &buf4[4]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[16], &buf4[8]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[24], &buf4[12]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[32], &buf4[16]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[40], &buf4[20]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[48], &buf4[24]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[56], &buf4[28]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[64], &buf4[32]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[72], &buf4[36]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[80], &buf4[40]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[88], &buf4[44]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[96], &buf4[48]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[104], &buf4[52]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[112], &buf4[56]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateSup( &buf2[120], &buf4[60]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[0], &buf8[0]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[8], &buf8[4]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[16], &buf8[8]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[24], &buf8[12]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[32], &buf8[16]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[40], &buf8[20]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[48], &buf8[24]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateSup( &buf4[56], &buf8[28]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateSup( &buf8[0], &buf16[0]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateSup( &buf8[8], &buf16[4]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateSup( &buf8[16], &buf16[8]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateSup( &buf8[24], &buf16[12]); - m_decimator32.myDecimateCen( + m_decimator32.myDecimateSup( &buf16[0], &buf32[0]); - m_decimator32.myDecimateCen( + m_decimator32.myDecimateSup( &buf16[8], &buf32[4]); @@ -2471,123 +2448,123 @@ void Decimators::decimate64_sup(SampleVector buf[pos+255] << decimation_shifts::pre64, &buf2[124]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[0], &buf4[0]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[8], &buf4[4]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[16], &buf4[8]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[24], &buf4[12]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[32], &buf4[16]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[40], &buf4[20]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[48], &buf4[24]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[56], &buf4[28]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[64], &buf4[32]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[72], &buf4[36]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[80], &buf4[40]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[88], &buf4[44]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[96], &buf4[48]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[104], &buf4[52]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[112], &buf4[56]); - m_decimator4.myDecimateCen( + m_decimator4.myDecimateInf( &buf2[120], &buf4[60]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[0], &buf8[0]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[8], &buf8[4]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[16], &buf8[8]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[24], &buf8[12]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[32], &buf8[16]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[40], &buf8[20]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[48], &buf8[24]); - m_decimator8.myDecimateCen( + m_decimator8.myDecimateInf( &buf4[56], &buf8[28]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateInf( &buf8[0], &buf16[0]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateInf( &buf8[8], &buf16[4]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateInf( &buf8[16], &buf16[8]); - m_decimator16.myDecimateCen( + m_decimator16.myDecimateInf( &buf8[24], &buf16[12]); - m_decimator32.myDecimateCen( + m_decimator32.myDecimateInf( &buf16[0], &buf32[0]); - m_decimator32.myDecimateCen( + m_decimator32.myDecimateInf( &buf16[8], &buf32[4]); diff --git a/sdrbase/dsp/devicesamplesource.cpp b/sdrbase/dsp/devicesamplesource.cpp index bc2e8a963..1a637bc0b 100644 --- a/sdrbase/dsp/devicesamplesource.cpp +++ b/sdrbase/dsp/devicesamplesource.cpp @@ -54,23 +54,8 @@ qint64 DeviceSampleSource::calculateDeviceCenterFrequency( deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; qint64 f_img = deviceCenterFrequency; - if ((log2Decim == 0) || (fcPos == FC_POS_CENTER)) - { - f_img = deviceCenterFrequency; - } - else - { - if (fcPos == FC_POS_INFRA) - { - deviceCenterFrequency += (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (fcPos == FC_POS_SUPRA) - { - deviceCenterFrequency -= (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } + deviceCenterFrequency -= calculateFrequencyShift(log2Decim, fcPos, devSampleRate); + f_img -= 2*calculateFrequencyShift(log2Decim, fcPos, devSampleRate); qDebug() << "DeviceSampleSource::calculateDeviceCenterFrequency:" << " desired center freq: " << centerFrequency << " Hz" @@ -82,3 +67,44 @@ qint64 DeviceSampleSource::calculateDeviceCenterFrequency( return deviceCenterFrequency; } + +/** + * log2Decim = 0: no shift + * + * n = log2Decim <= 2: fc = +/- 1/2^(n-1) + * center + * | ^ | + * | inf | sup | + * ^ ^ + * + * n = log2Decim > 2: fc = +/- 1/2^n + * center + * | ^ | + * | |inf| | |sup| | + * ^ ^ + */ +qint32 DeviceSampleSource::calculateFrequencyShift( + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate) +{ + if (log2Decim == 0) { // no shift at all + return 0; + } else if (log2Decim < 3) { + if (fcPos == FC_POS_INFRA) { // shift in the square next to center frequency + return -(devSampleRate / (1<<(log2Decim+1))); + } else if (fcPos == FC_POS_SUPRA) { + return devSampleRate / (1<<(log2Decim+1)); + } else { + return 0; + } + } else { + if (fcPos == FC_POS_INFRA) { // shift centered in the square next to center frequency + return -(devSampleRate / (1<<(log2Decim))); + } else if (fcPos == FC_POS_SUPRA) { + return devSampleRate / (1<<(log2Decim)); + } else { + return 0; + } + } +} diff --git a/sdrbase/dsp/devicesamplesource.h b/sdrbase/dsp/devicesamplesource.h index 776ed7f00..40d1ecd9f 100644 --- a/sdrbase/dsp/devicesamplesource.h +++ b/sdrbase/dsp/devicesamplesource.h @@ -94,6 +94,11 @@ public: quint32 devSampleRate, bool transverterMode = false); + static qint32 calculateFrequencyShift( + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate); + protected slots: void handleInputMessages(); diff --git a/sdrbase/dsp/inthalfbandfiltereo.h b/sdrbase/dsp/inthalfbandfiltereo.h index b60871bfe..a5cff5344 100644 --- a/sdrbase/dsp/inthalfbandfiltereo.h +++ b/sdrbase/dsp/inthalfbandfiltereo.h @@ -623,23 +623,6 @@ public: advancePointer(); } - void myDecimateInf(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) - { - storeSample32(-y1, x1); - advancePointer(); - - storeSample32(-*x2, -*y2); - doFIR(x2, y2); - advancePointer(); - - storeSample32(y3, -x3); - advancePointer(); - - storeSample32(*x4, *y4); - doFIR(x4, y4); - advancePointer(); - } - void myDecimateInf(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) { storeSample32(-y1, x1); @@ -657,20 +640,20 @@ public: advancePointer(); } - void myDecimateSup(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) + void myDecimateInf(int32_t *in, int32_t *out) { - storeSample32(y1, -x1); + storeSample32(-in[1], in[0]); advancePointer(); - storeSample32(-*x2, -*y2); - doFIR(x2, y2); + storeSample32(-in[2], -in[3]); + doFIR(&out[0], &out[1]); advancePointer(); - storeSample32(-y3, x3); + storeSample32(in[5], -in[4]); advancePointer(); - storeSample32(*x4, *y4); - doFIR(x4, y4); + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); advancePointer(); } @@ -691,6 +674,23 @@ public: advancePointer(); } + void myDecimateSup(int32_t *in, int32_t *out) + { + storeSample32(in[1], -in[0]); + advancePointer(); + + storeSample32(-in[2], -in[3]); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(-in[5], in[4]); + advancePointer(); + + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); + advancePointer(); + } + /** Simple zero stuffing and filter */ void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) { From 3ea37e3dccf5555a5e0937ebbad9883c2a255728 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 10 May 2018 23:45:43 +0200 Subject: [PATCH 388/956] Get rid of ugly native dialogs on color chooser dialogs --- sdrgui/gui/basicchannelsettingsdialog.cpp | 2 +- sdrgui/gui/glscopemultigui.cpp | 4 ++-- sdrgui/gui/glscopenggui.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdrgui/gui/basicchannelsettingsdialog.cpp b/sdrgui/gui/basicchannelsettingsdialog.cpp index 313cf3dc2..76b21c982 100644 --- a/sdrgui/gui/basicchannelsettingsdialog.cpp +++ b/sdrgui/gui/basicchannelsettingsdialog.cpp @@ -37,7 +37,7 @@ void BasicChannelSettingsDialog::paintColor() void BasicChannelSettingsDialog::on_colorBtn_clicked() { QColor c = m_color; - c = QColorDialog::getColor(c, this, tr("Select Color for Channel")); + c = QColorDialog::getColor(c, this, tr("Select Color for Channel"), QColorDialog::DontUseNativeDialog); if(c.isValid()) { m_color = c; paintColor(); diff --git a/sdrgui/gui/glscopemultigui.cpp b/sdrgui/gui/glscopemultigui.cpp index b83cf231c..e525e7cd5 100644 --- a/sdrgui/gui/glscopemultigui.cpp +++ b/sdrgui/gui/glscopemultigui.cpp @@ -721,7 +721,7 @@ void GLScopeMultiGUI::on_traceView_toggled(bool checked __attribute__((unused))) void GLScopeMultiGUI::on_traceColor_clicked() { - QColor newColor = QColorDialog::getColor(m_focusedTraceColor); + QColor newColor = QColorDialog::getColor(m_focusedTraceColor, this, tr("Select Color for trace"), QColorDialog::DontUseNativeDialog); if (newColor.isValid()) // user clicked OK and selected a color { @@ -822,7 +822,7 @@ void GLScopeMultiGUI::on_trigPre_valueChanged(int value __attribute__((unused))) void GLScopeMultiGUI::on_trigColor_clicked() { - QColor newColor = QColorDialog::getColor(m_focusedTriggerColor); + QColor newColor = QColorDialog::getColor(m_focusedTriggerColor, this, tr("Select Color for trigger line"), QColorDialog::DontUseNativeDialog); if (newColor.isValid()) // user clicked "OK" { diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 12f043b35..737d3ff85 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -711,7 +711,7 @@ void GLScopeNGGUI::on_traceView_toggled(bool checked __attribute__((unused))) void GLScopeNGGUI::on_traceColor_clicked() { - QColor newColor = QColorDialog::getColor(m_focusedTraceColor); + QColor newColor = QColorDialog::getColor(m_focusedTraceColor, this, tr("Select Color for trace"), QColorDialog::DontUseNativeDialog); if (newColor.isValid()) // user clicked OK and selected a color { @@ -810,7 +810,7 @@ void GLScopeNGGUI::on_trigPre_valueChanged(int value __attribute__((unused))) void GLScopeNGGUI::on_trigColor_clicked() { - QColor newColor = QColorDialog::getColor(m_focusedTriggerColor); + QColor newColor = QColorDialog::getColor(m_focusedTriggerColor, this, tr("Select Color for trigger line"), QColorDialog::DontUseNativeDialog); if (newColor.isValid()) // user clicked "OK" { From 7bf777e49868cd82825ddeb01648a0a84f35728f Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 11 May 2018 00:00:15 +0200 Subject: [PATCH 389/956] Get rid of ugly native dialogs on file chooser dialogs --- debian/changelog | 6 +++++- plugins/channelrx/demoddsd/dsdstatustextdialog.cpp | 2 +- plugins/channeltx/modam/ammodgui.cpp | 2 +- plugins/channeltx/modatv/atvmodgui.cpp | 4 ++-- plugins/channeltx/modnfm/nfmmodgui.cpp | 2 +- plugins/channeltx/modssb/ssbmodgui.cpp | 2 +- plugins/channeltx/modwfm/wfmmodgui.cpp | 2 +- plugins/samplesink/filesink/filesinkgui.cpp | 2 +- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp | 1 - plugins/samplesource/filesource/filesourcegui.cpp | 2 +- plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp | 1 - plugins/samplesource/testsource/testsourcegui.cpp | 1 - sdrgui/gui/editcommanddialog.cpp | 2 +- sdrgui/gui/loggingdialog.cpp | 2 +- sdrgui/mainwindow.cpp | 4 ++-- 15 files changed, 18 insertions(+), 17 deletions(-) diff --git a/debian/changelog b/debian/changelog index 7c816084a..e338fc591 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,12 @@ sdrangel (3.14.6-1) unstable; urgency=medium * Fixed keyboard input for negative values on realtive integer value dials + * Get rid of ugly native dialogs + * Inf/Sup frequency shift scheme change to be closer to device center frequency + * PlutoSDR input: fixed Inf/Sup frequency shift calculation + * File record default file name with ISO datetime stamp - -- Edouard Griffiths, F4EXB Sun, 13 May 2018 20:14:18 +0200 + -- Edouard Griffiths, F4EXB Fri, 11 May 2018 20:14:18 +0200 sdrangel (3.14.5-1) unstable; urgency=medium diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp index 3b7e3d1da..86715a5b8 100644 --- a/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp @@ -60,7 +60,7 @@ void DSDStatusTextDialog::on_clear_clicked() void DSDStatusTextDialog::on_saveLog_clicked() { QString fileName = QFileDialog::getSaveFileName(this, - tr("Open log file"), ".", tr("Log files (*.log)")); + tr("Open log file"), ".", tr("Log files (*.log)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index ec611423c..6c856902c 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -251,7 +251,7 @@ void AMModGUI::on_navTimeSlider_valueChanged(int value) void AMModGUI::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index cd22db2b2..60c02fb6f 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -497,7 +497,7 @@ void ATVModGUI::on_forceDecimator_toggled(bool checked) void ATVModGUI::on_imageFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open image file"), ".", tr("Image Files (*.png *.jpg *.bmp *.gif *.tiff)")); + tr("Open image file"), ".", tr("Image Files (*.png *.jpg *.bmp *.gif *.tiff)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -510,7 +510,7 @@ void ATVModGUI::on_imageFileDialog_clicked(bool checked __attribute__((unused))) void ATVModGUI::on_videoFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open video file"), ".", tr("Video Files (*.avi *.mpg *.mp4 *.mov *.m4v *.mkv *.vob *.wmv)")); + tr("Open video file"), ".", tr("Video Files (*.avi *.mpg *.mp4 *.mov *.m4v *.mkv *.vob *.wmv)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/plugins/channeltx/modnfm/nfmmodgui.cpp b/plugins/channeltx/modnfm/nfmmodgui.cpp index a5d72cfd5..dd0e2b50f 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.cpp +++ b/plugins/channeltx/modnfm/nfmmodgui.cpp @@ -255,7 +255,7 @@ void NFMModGUI::on_navTimeSlider_valueChanged(int value) void NFMModGUI::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index d5e966927..57da9b8df 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -341,7 +341,7 @@ void SSBModGUI::on_navTimeSlider_valueChanged(int value) void SSBModGUI::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/plugins/channeltx/modwfm/wfmmodgui.cpp b/plugins/channeltx/modwfm/wfmmodgui.cpp index f6701772f..57170cfca 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.cpp +++ b/plugins/channeltx/modwfm/wfmmodgui.cpp @@ -257,7 +257,7 @@ void WFMModGUI::on_navTimeSlider_valueChanged(int value) void WFMModGUI::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/plugins/samplesink/filesink/filesinkgui.cpp b/plugins/samplesink/filesink/filesinkgui.cpp index c56bb4105..a307f0028 100644 --- a/plugins/samplesink/filesink/filesinkgui.cpp +++ b/plugins/samplesink/filesink/filesinkgui.cpp @@ -287,7 +287,7 @@ void FileSinkGui::on_startStop_toggled(bool checked) void FileSinkGui::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getSaveFileName(this, - tr("Save I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)")); + tr("Save I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 96773dc4c..23aa0590c 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index c74a6270f..5a9a6d7c9 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -285,7 +285,7 @@ void FileSourceGui::on_navTimeSlider_valueChanged(int value) void FileSourceGui::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)")); + tr("Open I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 9c07e33b8..9dcd34249 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #ifdef _WIN32 #include diff --git a/plugins/samplesource/testsource/testsourcegui.cpp b/plugins/samplesource/testsource/testsourcegui.cpp index e9a0e912d..f228c686f 100644 --- a/plugins/samplesource/testsource/testsourcegui.cpp +++ b/plugins/samplesource/testsource/testsourcegui.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include "ui_testsourcegui.h" diff --git a/sdrgui/gui/editcommanddialog.cpp b/sdrgui/gui/editcommanddialog.cpp index 0dcae0ce6..d2f5d7bec 100644 --- a/sdrgui/gui/editcommanddialog.cpp +++ b/sdrgui/gui/editcommanddialog.cpp @@ -142,7 +142,7 @@ void EditCommandDialog::on_showFileDialog_clicked(bool checked __attribute__((un this, tr("Select command"), dirStr, - tr("All (*);;Python (*.py);;Shell (*.sh *.bat);;Binary (*.bin *.exe)")); + tr("All (*);;Python (*.py);;Shell (*.sh *.bat);;Binary (*.bin *.exe)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { ui->command->setText(fileName); diff --git a/sdrgui/gui/loggingdialog.cpp b/sdrgui/gui/loggingdialog.cpp index 03c151c14..8ee5648ba 100644 --- a/sdrgui/gui/loggingdialog.cpp +++ b/sdrgui/gui/loggingdialog.cpp @@ -50,7 +50,7 @@ void LoggingDialog::accept() void LoggingDialog::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getSaveFileName(this, - tr("Save log file"), ".", tr("Log Files (*.log)")); + tr("Save log file"), ".", tr("Log Files (*.log)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index d41da2f39..edf9e08ab 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -1260,7 +1260,7 @@ void MainWindow::on_presetExport_clicked() const Preset* preset = qvariant_cast(item->data(0, Qt::UserRole)); QString base64Str = preset->serialize().toBase64(); QString fileName = QFileDialog::getSaveFileName(this, - tr("Open preset export file"), ".", tr("Preset export files (*.prex)")); + tr("Open preset export file"), ".", tr("Preset export files (*.prex)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -1304,7 +1304,7 @@ void MainWindow::on_presetImport_clicked() } QString fileName = QFileDialog::getOpenFileName(this, - tr("Open preset export file"), ".", tr("Preset export files (*.prex)")); + tr("Open preset export file"), ".", tr("Preset export files (*.prex)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { From 50c868562a3326ae62c0fede8fb6369c7d8cef6b Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 11 May 2018 00:48:37 +0200 Subject: [PATCH 390/956] Decimators simplification --- sdrbase/dsp/decimators.h | 82 +++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index 18eec7b76..3de3fb5e7 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -638,6 +638,7 @@ void Decimators::decimate4_sup(SampleVector: } } +// No filtering: bad for Rx OK for signal tracking //template //void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) //{ @@ -655,6 +656,7 @@ void Decimators::decimate4_sup(SampleVector: // } //} +// No filtering: bad for Rx OK for signal tracking //template //void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) //{ @@ -2608,31 +2610,27 @@ void Decimators::decimate64_sup(SampleVector template void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[2], yimag[2]; + StorageType buf2[4]; for (int pos = 0; pos < len - 7; pos += 8) { - xreal[0] = buf[pos+2] << decimation_shifts::pre2; - yimag[0] = buf[pos+3] << decimation_shifts::pre2; - xreal[1] = buf[pos+6] << decimation_shifts::pre2; - yimag[1] = buf[pos+7] << decimation_shifts::pre2; - m_decimator2.myDecimateCen( buf[pos+0] << decimation_shifts::pre2, buf[pos+1] << decimation_shifts::pre2, - &xreal[0], - &yimag[0], + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, buf[pos+4] << decimation_shifts::pre2, buf[pos+5] << decimation_shifts::pre2, - &xreal[1], - &yimag[1]); + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); - (**it).setReal(xreal[0] >> decimation_shifts::post2); - (**it).setImag(yimag[0] >> decimation_shifts::post2); + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); ++(*it); - (**it).setReal(xreal[1] >> decimation_shifts::post2); - (**it).setImag(yimag[1] >> decimation_shifts::post2); + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); ++(*it); } } @@ -2662,35 +2660,43 @@ void Decimators::decimate2_cen(SampleVector: template void Decimators::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType intbuf[4]; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) + for (int pos = 0; pos < len - 15; pos += 16) { - intbuf[0] = buf[pos+2] << decimation_shifts::pre4; - intbuf[1] = buf[pos+3] << decimation_shifts::pre4; - intbuf[2] = buf[pos+6] << decimation_shifts::pre4; - intbuf[3] = buf[pos+7] << decimation_shifts::pre4; + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); - m_decimator2.myDecimate( - buf[pos+0] << decimation_shifts::pre4, - buf[pos+1] << decimation_shifts::pre4, - &intbuf[0], - &intbuf[1]); - m_decimator2.myDecimate( - buf[pos+4] << decimation_shifts::pre4, - buf[pos+5] << decimation_shifts::pre4, - &intbuf[2], - &intbuf[3]); + m_decimator2.myDecimateCen( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); - m_decimator4.myDecimate( - intbuf[0], - intbuf[1], - &intbuf[2], - &intbuf[3]); + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); - (**it).setReal(intbuf[2] >> decimation_shifts::post4); - (**it).setImag(intbuf[3] >> decimation_shifts::post4); - ++(*it); + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); } } From 15078c9c07d11a0f482743adf7be27ca61e61310 Mon Sep 17 00:00:00 2001 From: beta-tester Date: Fri, 11 May 2018 09:08:20 +0200 Subject: [PATCH 391/956] modified unique file name --- plugins/samplesource/airspy/airspyinput.cpp | 2 +- plugins/samplesource/airspyhf/airspyhfinput.cpp | 2 +- plugins/samplesource/bladerfinput/bladerfinput.cpp | 2 +- plugins/samplesource/fcdpro/fcdproinput.cpp | 2 +- plugins/samplesource/fcdproplus/fcdproplusinput.cpp | 2 +- plugins/samplesource/hackrfinput/hackrfinput.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinput.cpp | 2 +- plugins/samplesource/perseus/perseusinput.cpp | 2 +- plugins/samplesource/plutosdrinput/plutosdrinput.cpp | 2 +- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 2 +- .../samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp | 2 +- plugins/samplesource/sdrplay/sdrplayinput.cpp | 2 +- plugins/samplesource/testsource/testsourceinput.cpp | 2 +- sdrbase/dsp/filerecord.cpp | 6 ++++++ sdrbase/dsp/filerecord.h | 1 + 15 files changed, 20 insertions(+), 13 deletions(-) diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 72e062830..6a5321718 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -308,7 +308,7 @@ bool AirspyInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index a8fb0f1d3..b076ed10c 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -325,7 +325,7 @@ bool AirspyHFInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 5fa0dbafd..6bcd2d163 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -280,7 +280,7 @@ bool BladerfInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index 2660f2bfc..d84fd40bd 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -250,7 +250,7 @@ bool FCDProInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index b6dee89dc..0f61e9a6e 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -244,7 +244,7 @@ bool FCDProPlusInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index dee2fe047..b287f9b16 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -263,7 +263,7 @@ bool HackRFInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 73aca76e5..ff7877da1 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -692,7 +692,7 @@ bool LimeSDRInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/perseus/perseusinput.cpp b/plugins/samplesource/perseus/perseusinput.cpp index 8d8a80452..beca7d33f 100644 --- a/plugins/samplesource/perseus/perseusinput.cpp +++ b/plugins/samplesource/perseus/perseusinput.cpp @@ -197,7 +197,7 @@ bool PerseusInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index 740c86706..bd9d1afae 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -195,7 +195,7 @@ bool PlutoSDRInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index 867d53541..d0d722a16 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -309,7 +309,7 @@ bool RTLSDRInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index d8af205df..62fc4cadd 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -190,7 +190,7 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index 015f1d1fa..a87b35e36 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -304,7 +304,7 @@ bool SDRPlayInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 65e39aa89..382d966c9 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -182,7 +182,7 @@ bool TestSourceInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); } m_fileSink->startRecording(); diff --git a/sdrbase/dsp/filerecord.cpp b/sdrbase/dsp/filerecord.cpp index 39e947a03..56f1e07cc 100644 --- a/sdrbase/dsp/filerecord.cpp +++ b/sdrbase/dsp/filerecord.cpp @@ -4,6 +4,7 @@ #include "util/message.h" #include +#include FileRecord::FileRecord() : BasebandSampleSink(), @@ -42,6 +43,11 @@ void FileRecord::setFileName(const QString& filename) } } +void FileRecord::genUniqueFileName(uint deviceUID) +{ + setFileName(QString("rec%1_%2.sdriq").arg(deviceUID).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"))); +} + void FileRecord::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) { // if no recording is active, send the samples to /dev/null diff --git a/sdrbase/dsp/filerecord.h b/sdrbase/dsp/filerecord.h index 00840c469..08970e004 100644 --- a/sdrbase/dsp/filerecord.h +++ b/sdrbase/dsp/filerecord.h @@ -29,6 +29,7 @@ public: quint64 getByteCount() const { return m_byteCount; } void setFileName(const QString& filename); + void genUniqueFileName(uint deviceUID); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); From b0f7063e92f0728c137625c7d40cc23e0ec2e7e6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 11 May 2018 09:40:11 +0200 Subject: [PATCH 392/956] Default record file name: removed colons to break ISO date compatibility and cope with Windows compatibility --- plugins/samplesource/airspy/airspyinput.cpp | 2 +- plugins/samplesource/airspyhf/airspyhfinput.cpp | 2 +- plugins/samplesource/bladerfinput/bladerfinput.cpp | 2 +- plugins/samplesource/fcdpro/fcdproinput.cpp | 2 +- plugins/samplesource/fcdproplus/fcdproplusinput.cpp | 2 +- plugins/samplesource/hackrfinput/hackrfinput.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinput.cpp | 2 +- plugins/samplesource/perseus/perseusinput.cpp | 2 +- plugins/samplesource/plutosdrinput/plutosdrinput.cpp | 2 +- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 2 +- plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp | 2 +- plugins/samplesource/sdrplay/sdrplayinput.cpp | 2 +- plugins/samplesource/testsource/testsourceinput.cpp | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 72e062830..dde26e172 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -308,7 +308,7 @@ bool AirspyInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index a8fb0f1d3..d9cc7f767 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -325,7 +325,7 @@ bool AirspyHFInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 5fa0dbafd..ba44e51d3 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -280,7 +280,7 @@ bool BladerfInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index 2660f2bfc..945ae0adc 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -250,7 +250,7 @@ bool FCDProInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index b6dee89dc..233eda8b9 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -244,7 +244,7 @@ bool FCDProPlusInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index dee2fe047..06f5ac9c5 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -263,7 +263,7 @@ bool HackRFInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 73aca76e5..6d4adacbc 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -692,7 +692,7 @@ bool LimeSDRInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/perseus/perseusinput.cpp b/plugins/samplesource/perseus/perseusinput.cpp index 8d8a80452..847c16d62 100644 --- a/plugins/samplesource/perseus/perseusinput.cpp +++ b/plugins/samplesource/perseus/perseusinput.cpp @@ -197,7 +197,7 @@ bool PerseusInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index 740c86706..b913cd7c8 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -195,7 +195,7 @@ bool PlutoSDRInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index 867d53541..23d20f1c5 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -309,7 +309,7 @@ bool RTLSDRInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index d8af205df..23677f9eb 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -190,7 +190,7 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index 015f1d1fa..e25e1fc84 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -304,7 +304,7 @@ bool SDRPlayInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 65e39aa89..48cef3c70 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -182,7 +182,7 @@ bool TestSourceInput::handleMessage(const Message& message) if (m_settings.m_fileRecordName.size() != 0) { m_fileSink->setFileName(m_settings.m_fileRecordName); } else { - m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss"))); + m_fileSink->setFileName(QString("rec%1_%2.sdriq").arg(m_deviceAPI->getDeviceUID()).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThhmmss"))); } m_fileSink->startRecording(); From 1c952d3b0ef3ff4ce4a6bf4002820608a3b603b2 Mon Sep 17 00:00:00 2001 From: beta-tester Date: Fri, 11 May 2018 11:00:08 +0200 Subject: [PATCH 393/956] force 24h time format changed hh:mm to HH:mm changed dd.MM.yyyy to yyyy-MM-dd to uniform all date format --- logging/filelogger.cpp | 2 +- logging/filelogger.h | 4 ++-- logging/fileloggersettings.h | 2 +- logging/logger.cpp | 2 +- logging/logger.h | 4 ++-- logging/logmessage.h | 2 +- plugins/channelrx/demoddsd/dsdstatustextdialog.cpp | 2 +- plugins/channeltx/modam/ammodgui.cpp | 6 +++--- plugins/channeltx/modatv/atvmodgui.cpp | 6 +++--- plugins/channeltx/modnfm/nfmmodgui.cpp | 6 +++--- plugins/channeltx/modssb/ssbmodgui.cpp | 6 +++--- plugins/channeltx/modwfm/wfmmodgui.cpp | 6 +++--- plugins/samplesink/filesink/filesinkgui.cpp | 2 +- plugins/samplesink/sdrdaemonsink/readme.md | 2 +- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp | 4 ++-- plugins/samplesource/filesource/filesourcegui.cpp | 8 ++++---- plugins/samplesource/sdrdaemonsource/readme.md | 2 +- .../samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp | 4 ++-- sdrgui/gui/commandoutputdialog.cpp | 4 ++-- sdrgui/mainwindow.cpp | 2 +- sdrgui/readme.md | 2 +- 21 files changed, 39 insertions(+), 39 deletions(-) diff --git a/logging/filelogger.cpp b/logging/filelogger.cpp index d73dada71..63e4113a3 100644 --- a/logging/filelogger.cpp +++ b/logging/filelogger.cpp @@ -52,7 +52,7 @@ void FileLogger::refreshQtSettings() maxSize = settings->value("maxSize", 0).toLongLong(); maxBackups = settings->value("maxBackups", 0).toInt(); msgFormat = settings->value("msgFormat", "{timestamp} {type} {msg}").toString(); - timestampFormat = settings->value("timestampFormat", "yyyy-MM-dd hh:mm:ss.zzz").toString(); + timestampFormat = settings->value("timestampFormat", "yyyy-MM-dd HH:mm:ss.zzz").toString(); minLevel = static_cast(settings->value("minLevel", 0).toInt()); bufferSize = settings->value("bufferSize", 0).toInt(); diff --git a/logging/filelogger.h b/logging/filelogger.h index 3d1723a33..1304ac896 100644 --- a/logging/filelogger.h +++ b/logging/filelogger.h @@ -30,7 +30,7 @@ namespace qtwebapp { maxBackups=2 minLevel=0 msgformat={timestamp} {typeNr} {type} thread={thread}: {msg} - timestampFormat=dd.MM.yyyy hh:mm:ss.zzz + timestampFormat=yyyy-MM-dd HH:mm:ss.zzz bufferSize=0 @@ -43,7 +43,7 @@ namespace qtwebapp { - maxBackups defines the number of backup files to keep. Default is 0=unlimited. - minLevel defines the minimum type of messages that are written (together with buffered messages) into the file. Defaults is 0=debug. - msgFormat defines the decoration of log messages, see LogMessage class. Default is "{timestamp} {type} {msg}". - - timestampFormat defines the format of timestamps, see QDateTime::toString(). Default is "yyyy-MM-dd hh:mm:ss.zzz". + - timestampFormat defines the format of timestamps, see QDateTime::toString(). Default is "yyyy-MM-dd HH:mm:ss.zzz". - bufferSize defines the size of the buffer. Default is 0=disabled. @see set() describes how to set logger variables diff --git a/logging/fileloggersettings.h b/logging/fileloggersettings.h index 8daee5ca1..0d5b127e4 100644 --- a/logging/fileloggersettings.h +++ b/logging/fileloggersettings.h @@ -31,7 +31,7 @@ struct FileLoggerSettings maxSize = 1000000; maxBackups = 2; msgFormat = "{timestamp} {type} {msg}"; - timestampFormat = "dd.MM.yyyy hh:mm:ss.zzz"; + timestampFormat = "yyyy-MM-dd HH:mm:ss.zzz"; minLevel = QtDebugMsg; bufferSize = 100; } diff --git a/logging/logger.cpp b/logging/logger.cpp index 8e440a3e8..fca4a307f 100644 --- a/logging/logger.cpp +++ b/logging/logger.cpp @@ -25,7 +25,7 @@ QMutex Logger::mutex; Logger::Logger(QObject* parent) : QObject(parent), msgFormat("{timestamp} {type} {msg}"), - timestampFormat("dd.MM.yyyy hh:mm:ss.zzz"), + timestampFormat("yyyy-MM-dd HH:mm:ss.zzz"), minLevel(QtDebugMsg), bufferSize(0) {} diff --git a/logging/logger.h b/logging/logger.h index 663d4b315..9169691f8 100644 --- a/logging/logger.h +++ b/logging/logger.h @@ -65,13 +65,13 @@ public: /** Constructor. @param msgFormat Format of the decoration, e.g. "{timestamp} {type} thread={thread}: {msg}" - @param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz" + @param timestampFormat Format of timestamp, e.g. "yyyy-MM-dd HH:mm:ss.zzz" @param minLevel Minimum severity that genertes an output (0=debug, 1=warning, 2=critical, 3=fatal). @param bufferSize Size of the backtrace buffer, number of messages per thread. 0=disabled. @param parent Parent object @see LogMessage for a description of the message decoration. */ - Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="dd.MM.yyyy hh:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0); + Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="yyyy-MM-dd HH:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0); /** Destructor */ virtual ~Logger(); diff --git a/logging/logmessage.h b/logging/logmessage.h index 91df741f4..5fb4920ee 100644 --- a/logging/logmessage.h +++ b/logging/logmessage.h @@ -56,7 +56,7 @@ public: Returns the log message as decorated string. @param msgFormat Format of the decoration. May contain variables and static text, e.g. "{timestamp} {type} thread={thread}: {msg}". - @param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz", see QDateTime::toString(). + @param timestampFormat Format of timestamp, e.g. "yyyy-MM-dd HH:mm:ss.zzz", see QDateTime::toString(). @see QDatetime for a description of the timestamp format pattern */ QString toString(const QString& msgFormat, const QString& timestampFormat) const; diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp index 86715a5b8..df574c699 100644 --- a/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp @@ -41,7 +41,7 @@ void DSDStatusTextDialog::addLine(const QString& line) if ((line.size() > 0) && (line != m_lastLine)) { QDateTime dt = QDateTime::currentDateTime(); - QString dateStr = dt.toString("hh:mm:ss"); + QString dateStr = dt.toString("HH:mm:ss"); QTextCursor cursor = ui->logEdit->textCursor(); cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); cursor.insertText(tr("%1 %2\n").arg(dateStr).arg(line)); diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index 6c856902c..bf27a61ca 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -463,7 +463,7 @@ void AMModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -482,8 +482,8 @@ void AMModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index 60c02fb6f..1059423a1 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -797,7 +797,7 @@ void ATVModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_videoLength / m_videoFrameRate); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -817,8 +817,8 @@ void ATVModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modnfm/nfmmodgui.cpp b/plugins/channeltx/modnfm/nfmmodgui.cpp index dd0e2b50f..6db1738a8 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.cpp +++ b/plugins/channeltx/modnfm/nfmmodgui.cpp @@ -498,7 +498,7 @@ void NFMModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -517,8 +517,8 @@ void NFMModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index 57da9b8df..b15818d42 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -748,7 +748,7 @@ void SSBModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -767,8 +767,8 @@ void SSBModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modwfm/wfmmodgui.cpp b/plugins/channeltx/modwfm/wfmmodgui.cpp index 57170cfca..b5cf6d6c2 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.cpp +++ b/plugins/channeltx/modwfm/wfmmodgui.cpp @@ -483,7 +483,7 @@ void WFMModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -502,8 +502,8 @@ void WFMModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/samplesink/filesink/filesinkgui.cpp b/plugins/samplesink/filesink/filesinkgui.cpp index a307f0028..4b054c748 100644 --- a/plugins/samplesink/filesink/filesinkgui.cpp +++ b/plugins/samplesink/filesink/filesinkgui.cpp @@ -322,7 +322,7 @@ void FileSinkGui::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); + QString s_timems = t.toString("HH:mm:ss.zzz"); ui->relTimeText->setText(s_timems); } diff --git a/plugins/samplesink/sdrdaemonsink/readme.md b/plugins/samplesink/sdrdaemonsink/readme.md index 906cc2983..3bb218cb5 100644 --- a/plugins/samplesink/sdrdaemonsink/readme.md +++ b/plugins/samplesink/sdrdaemonsink/readme.md @@ -95,7 +95,7 @@ This counter counts the unrecoverable error conditions found (i.e. 6.4 between 1

6.8: events counters timer

-This hh:mm:ss time display shows the time since the reset events counters button (4.6) was pushed. +This HH:mm:ss time display shows the time since the reset events counters button (4.6) was pushed.

7: Network parameters

diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 23aa0590c..794a7c343 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -544,7 +544,7 @@ void SDRdaemonSinkGui::displayEventTimer() int elapsedTimeMillis = m_time.elapsed(); QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(elapsedTimeMillis/1000); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->eventCountsTimeText->setText(s_time); } @@ -561,7 +561,7 @@ void SDRdaemonSinkGui::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); + QString s_timems = t.toString("HH:mm:ss.zzz"); //ui->relTimeText->setText(s_timems); TODO with absolute time } diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index 5a9a6d7c9..918eff372 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -317,7 +317,7 @@ void FileSourceGui::updateWithStreamData() ui->play->setEnabled(m_acquisition); QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); // TODO: remove when time data is implemented } @@ -335,15 +335,15 @@ void FileSourceGui::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); quint64 startingTimeStampMsec = (quint64) m_startingTimeStamp * 1000LL; QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); dt = dt.addSecs((quint64) t_sec); dt = dt.addMSecs((quint64) t_msec); - QString s_date = dt.toString("yyyy-MM-dd hh:mm:ss.zzz"); + QString s_date = dt.toString("yyyy-MM-dd HH:mm:ss.zzz"); ui->absTimeText->setText(s_date); if (!m_enableNavTime) diff --git a/plugins/samplesource/sdrdaemonsource/readme.md b/plugins/samplesource/sdrdaemonsource/readme.md index 07cfd9415..a69433a8b 100644 --- a/plugins/samplesource/sdrdaemonsource/readme.md +++ b/plugins/samplesource/sdrdaemonsource/readme.md @@ -122,7 +122,7 @@ This counter counts the unrecoverable error conditions found (i.e. 4.4 between 1

4.9: events counters timer

-This hh:mm:ss time display shows the time since the reset events counters button (4.6) was pushed. +This HH:mm:ss time display shows the time since the reset events counters button (4.6) was pushed.

5: Network parameters

diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 9dcd34249..a6cc54129 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -519,7 +519,7 @@ void SDRdaemonSourceGui::displayEventTimer() int elapsedTimeMillis = m_eventsTime.elapsed(); QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(elapsedTimeMillis/1000); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->eventCountsTimeText->setText(s_time); } @@ -532,7 +532,7 @@ void SDRdaemonSourceGui::updateWithStreamTime() bool updateEventCounts = false; quint64 startingTimeStampMsec = ((quint64) m_startingTimeStamp.tv_sec * 1000LL) + ((quint64) m_startingTimeStamp.tv_usec / 1000LL); QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); - QString s_date = dt.toString("yyyy-MM-dd hh:mm:ss.zzz"); + QString s_date = dt.toString("yyyy-MM-dd HH:mm:ss.zzz"); ui->absTimeText->setText(s_date); if (m_framesDecodingStatus == 2) diff --git a/sdrgui/gui/commandoutputdialog.cpp b/sdrgui/gui/commandoutputdialog.cpp index 02a878288..f1c85457c 100644 --- a/sdrgui/gui/commandoutputdialog.cpp +++ b/sdrgui/gui/commandoutputdialog.cpp @@ -46,7 +46,7 @@ void CommandOutputDialog::refresh() { struct timeval tv = m_command.getLastProcessStartTimestamp(); QDateTime dt = QDateTime::fromMSecsSinceEpoch(tv.tv_sec * 1000LL + tv.tv_usec / 1000LL); - QString dateStr = dt.toString("yyyy-MM-dd hh:mm:ss.zzz"); + QString dateStr = dt.toString("yyyy-MM-dd HH:mm:ss.zzz"); ui->startTime->setText(dateStr); } @@ -57,7 +57,7 @@ void CommandOutputDialog::refresh() { struct timeval tv = m_command.getLastProcessFinishTimestamp(); QDateTime dt = QDateTime::fromMSecsSinceEpoch(tv.tv_sec * 1000LL + tv.tv_usec / 1000LL); - QString dateStr = dt.toString("yyyy-MM-dd hh:mm:ss.zzz"); + QString dateStr = dt.toString("yyyy-MM-dd HH:mm:ss.zzz"); ui->endTime->setText(dateStr); } diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index edf9e08ab..6f5146621 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -1726,7 +1726,7 @@ void MainWindow::tabInputViewIndexChanged() void MainWindow::updateStatus() { - m_dateTimeWidget->setText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss t")); + m_dateTimeWidget->setText(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss t")); } void MainWindow::setLoggingOptions() diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 21da04252..4feffd754 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -70,7 +70,7 @@ Log message will appear as follows: (1) (2) (3) ``` - - 1: Timestamp in `dd.MM.yyyy hh:mm:ss.zzz` format + - 1: Timestamp in `yyyy-MM-dd HH:mm:ss.zzz` format - 2: Message level: `(D)`: debug, `(I)`: info, `(W)`: warning, `(C)`: critical, `(F)`: fatal - 3: Message text From 79c22f11a59a05ed7f01fe7d43977f3a441f3599 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 11 May 2018 11:14:02 +0200 Subject: [PATCH 394/956] BladeRF: updated library version for Debian build --- libbladerf/include/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbladerf/include/version.h b/libbladerf/include/version.h index acacd8034..4688b3dfe 100644 --- a/libbladerf/include/version.h +++ b/libbladerf/include/version.h @@ -1,7 +1,7 @@ #ifndef VERSION_H__ #define VERSION_H__ -#define LIBBLADERF_VERSION "1.7.2-git-unknown" +#define LIBBLADERF_VERSION "1.9.0-git-23c6379e" #define LIBBLADERF_VERSION_MAJOR 1 #define LIBBLADERF_VERSION_MINOR 7 From 9ef3876780e6947713fbace24303273f0f0c0320 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 11 May 2018 11:26:19 +0200 Subject: [PATCH 395/956] BladeRF: updated library version for Debian build (2) --- libbladerf/include/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libbladerf/include/version.h b/libbladerf/include/version.h index 4688b3dfe..543e3563b 100644 --- a/libbladerf/include/version.h +++ b/libbladerf/include/version.h @@ -4,7 +4,7 @@ #define LIBBLADERF_VERSION "1.9.0-git-23c6379e" #define LIBBLADERF_VERSION_MAJOR 1 -#define LIBBLADERF_VERSION_MINOR 7 -#define LIBBLADERF_VERSION_PATCH 2 +#define LIBBLADERF_VERSION_MINOR 9 +#define LIBBLADERF_VERSION_PATCH 0 #endif From 8dca2d7b2804023d88d43add065c103025020833 Mon Sep 17 00:00:00 2001 From: beta-tester Date: Fri, 11 May 2018 11:35:10 +0200 Subject: [PATCH 396/956] uniformed date format in Broadcast FM Demod --- plugins/channelrx/demodbfm/bfmdemodgui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index 92b73f77c..72d7021ed 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -671,8 +671,8 @@ void BFMDemodGUI::rdsUpdate(bool force) { ui->g04Label->setStyleSheet("QLabel { background-color : green; }"); ui->g04CountText->setNum((int) m_bfmDemod->getRDSParser().m_g4_count); - std::string time = str(boost::format("%02i.%02i.%4i, %02i:%02i (%+.1fh)")\ - % m_bfmDemod->getRDSParser().m_g4_day % m_bfmDemod->getRDSParser().m_g4_month % (1900 + m_bfmDemod->getRDSParser().m_g4_year) % m_bfmDemod->getRDSParser().m_g4_hours % m_bfmDemod->getRDSParser().m_g4_minutes % m_bfmDemod->getRDSParser().m_g4_local_time_offset); + std::string time = str(boost::format("%4i-%02i-%02i %02i:%02i (%+.1fh)")\ + % (1900 + m_bfmDemod->getRDSParser().m_g4_year) % m_bfmDemod->getRDSParser().m_g4_month % m_bfmDemod->getRDSParser().m_g4_day % m_bfmDemod->getRDSParser().m_g4_hours % m_bfmDemod->getRDSParser().m_g4_minutes % m_bfmDemod->getRDSParser().m_g4_local_time_offset); ui->g04Time->setText(QString(time.c_str())); } else From 91b000c3ed8b51c88ae3dfce1ad31d9d811931d9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 11 May 2018 12:53:54 +0200 Subject: [PATCH 397/956] Windows build fixes after bladeRF library upgrade to 1.9.0 --- libbladerf/libbladerf.pro | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libbladerf/libbladerf.pro b/libbladerf/libbladerf.pro index f25f027d0..610a9c797 100644 --- a/libbladerf/libbladerf.pro +++ b/libbladerf/libbladerf.pro @@ -63,7 +63,8 @@ SOURCES = $$LIBBLADERFLIBSRC/src/async.c\ $$LIBBLADERFSRC/fpga_common/src/lms.c\ $$LIBBLADERFCOMMONSRC/src/conversions.c\ $$LIBBLADERFCOMMONSRC/src/devcfg.c\ - $$LIBBLADERFCOMMONSRC/src/sha256.c + $$LIBBLADERFCOMMONSRC/src/sha256.c\ + $$LIBBLADERFCOMMONSRC/src/parse.c HEADERS = $$LIBBLADERFLIBSRC/src/async.h\ $$LIBBLADERFLIBSRC/src/capabilities.h\ @@ -96,6 +97,7 @@ HEADERS = $$LIBBLADERFLIBSRC/src/async.h\ $$LIBBLADERFSRC/fpga_common/include/band_select.h\ $$LIBBLADERFSRC/fpga_common/include/lms.h\ $$LIBBLADERFCOMMONSRC/include/sha256.h\ + $$LIBBLADERFCOMMONSRC/include/parse.h\ $$PWD/include/host_config.h\ $$PWD/include/backend/backend_config.h\ $$PWD/include/version.h From 3ae7cda9be1a948853a3d731251be548415ab5c1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 12 May 2018 06:01:54 +0200 Subject: [PATCH 398/956] ChanelAnalyzerNG: added PLL option --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 + .../channelrx/chanalyzerng/chanalyzerng.cpp | 11 +- plugins/channelrx/chanalyzerng/chanalyzerng.h | 52 ++++++-- .../chanalyzerng/chanalyzernggui.cpp | 14 +- .../channelrx/chanalyzerng/chanalyzernggui.h | 1 + .../channelrx/chanalyzerng/chanalyzernggui.ui | 18 +++ .../chanalyzerng/chanalyzerngplugin.cpp | 2 +- sdrbase/dsp/phaselock.cpp | 120 +++++++++++------- sdrbase/dsp/phaselock.h | 3 + 12 files changed, 167 insertions(+), 66 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 964e327ed..809d3f48f 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.14.6"); + QCoreApplication::setApplicationVersion("3.14.7"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 4c9c1554d..8f1214937 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("3.14.6"); + QCoreApplication::setApplicationVersion("3.14.7"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 298db8ea7..c4ec4d5d7 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.14.6"); + QCoreApplication::setApplicationVersion("3.14.7"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index e338fc591..a71e6207f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (3.14.7-1) unstable; urgency=medium + + * ChanelAnalyzerNG: added PLL option + + -- Edouard Griffiths, F4EXB Sun, 13 May 2018 20:14:18 +0200 + sdrangel (3.14.6-1) unstable; urgency=medium * Fixed keyboard input for negative values on realtive integer value dials diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index fed5b3e2f..63f654cfc 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -35,6 +35,7 @@ const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzerNG"; ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_deviceAPI(deviceAPI), + m_pll(0,0.05,0.01), m_sampleSink(0), m_settingsMutex(QMutex::Recursive) { @@ -73,9 +74,10 @@ void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, Real Bandwidth, Real LowCutoff, int spanLog2, - bool ssb) + bool ssb, + bool pll) { - Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb); + Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll); messageQueue->push(cmd); } @@ -165,13 +167,15 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) m_config.m_LowCutoff = cfg.getLoCutoff(); m_config.m_spanLog2 = cfg.getSpanLog2(); m_config.m_ssb = cfg.getSSB(); + m_config.m_pll = cfg.getPLL(); qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:" << " m_channelSampleRate: " << m_config.m_channelSampleRate << " m_Bandwidth: " << m_config.m_Bandwidth << " m_LowCutoff: " << m_config.m_LowCutoff << " m_spanLog2: " << m_config.m_spanLog2 - << " m_ssb: " << m_config.m_ssb; + << " m_ssb: " << m_config.m_ssb + << " m_pll: " << m_config.m_pll; apply(); return true; @@ -254,5 +258,6 @@ void ChannelAnalyzerNG::apply(bool force) //m_settingsMutex.lock(); m_running.m_spanLog2 = m_config.m_spanLog2; m_running.m_ssb = m_config.m_ssb; + m_running.m_pll = m_config.m_pll; //m_settingsMutex.unlock(); } diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index aea652558..f9f194bd2 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -25,6 +25,7 @@ #include "dsp/interpolator.h" #include "dsp/ncof.h" #include "dsp/fftfilt.h" +#include "dsp/phaselock.h" #include "audio/audiofifo.h" #include "util/message.h" @@ -45,20 +46,23 @@ public: Real getLoCutoff() const { return m_LowCutoff; } int getSpanLog2() const { return m_spanLog2; } bool getSSB() const { return m_ssb; } + bool getPLL() const { return m_pll; } static MsgConfigureChannelAnalyzer* create( int channelSampleRate, Real Bandwidth, Real LowCutoff, int spanLog2, - bool ssb) + bool ssb, + bool pll) { return new MsgConfigureChannelAnalyzer( channelSampleRate, Bandwidth, LowCutoff, spanLog2, - ssb); + ssb, + pll); } private: @@ -67,19 +71,22 @@ public: Real m_LowCutoff; int m_spanLog2; bool m_ssb; + bool m_pll; MsgConfigureChannelAnalyzer( int channelSampleRate, Real Bandwidth, Real LowCutoff, int spanLog2, - bool ssb) : + bool ssb, + bool pll) : Message(), m_channelSampleRate(channelSampleRate), m_Bandwidth(Bandwidth), m_LowCutoff(LowCutoff), m_spanLog2(spanLog2), - m_ssb(ssb) + m_ssb(ssb), + m_pll(pll) { } }; @@ -133,12 +140,14 @@ public: Real Bandwidth, Real LowCutoff, int spanLog2, - bool ssb); + bool ssb, + bool pll); DownChannelizer *getChannelizer() { return m_channelizer; } int getInputSampleRate() const { return m_running.m_inputSampleRate; } int getChannelSampleRate() const { return m_running.m_channelSampleRate; } double getMagSq() const { return m_magsq; } + bool isPllLocked() const { return m_running.m_pll && m_pll.locked(); } virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); @@ -166,6 +175,7 @@ private: Real m_LowCutoff; int m_spanLog2; bool m_ssb; + bool m_pll; Config() : m_frequency(0), @@ -174,7 +184,8 @@ private: m_Bandwidth(5000), m_LowCutoff(300), m_spanLog2(3), - m_ssb(false) + m_ssb(false), + m_pll(false) {} }; @@ -192,6 +203,7 @@ private: bool m_useInterpolator; NCOF m_nco; + SimplePhaseLock m_pll; Interpolator m_interpolator; Real m_interpolatorDistance; Real m_interpolatorDistanceRemain; @@ -233,13 +245,33 @@ private: Real im = m_sum.imag() / SDR_RX_SCALED; m_magsq = re*re + im*im; - if (m_running.m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); + if (m_running.m_pll) + { + Real ncopll[2]; + m_pll.process(re, im, ncopll); + + Real mixI = m_sum.real() * ncopll[0] - m_sum.imag() * ncopll[1]; + Real mixQ = m_sum.real() * ncopll[1] + m_sum.imag() * ncopll[0]; + + if (m_running.m_ssb & !m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(mixQ, mixI)); + } + else + { + m_sampleBuffer.push_back(Sample(mixI, mixQ)); + } } else { - m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); + if (m_running.m_ssb & !m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); + } + else + { + m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); + } } m_sum = 0; diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 72be0e3bf..14fba25a3 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -237,6 +237,12 @@ void ChannelAnalyzerNGGUI::tick() double powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq()); m_channelPowerDbAvg(powDb); ui->channelPower->setText(tr("%1 dB").arg((Real) m_channelPowerDbAvg, 0, 'f', 1)); + + if (m_channelAnalyzer->isPllLocked()) { + ui->pll->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } } void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) @@ -251,6 +257,11 @@ void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) } } +void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked __attribute__((unused))) +{ + applySettings(); +} + void ChannelAnalyzerNGGUI::on_useRationalDownsampler_toggled(bool checked __attribute__((unused))) { setNewFinalRate(m_spanLog2); @@ -578,7 +589,8 @@ void ChannelAnalyzerNGGUI::applySettings() ui->BW->value() * 100.0, ui->lowCut->value() * 100.0, m_spanLog2, - ui->ssb->isChecked()); + ui->ssb->isChecked(), + ui->pll->isChecked()); } } diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h index e620b00fe..c30a6087d 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.h @@ -92,6 +92,7 @@ private: private slots: void on_deltaFrequency_changed(qint64 value); void on_channelSampleRate_changed(quint64 value); + void on_pll_toggled(bool checked); void on_useRationalDownsampler_toggled(bool checked); void on_BW_valueChanged(int value); void on_lowCut_valueChanged(int value); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index c6e712619..1c72fa3a1 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -179,6 +179,24 @@
+ + + + PLL lock + + + + + + + :/unlocked.png + :/locked.png:/unlocked.png + + + true + + + diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp index 076d9b967..b2450da84 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = { QString("Channel Analyzer NG"), - QString("3.14.5"), + QString("3.14.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/dsp/phaselock.cpp b/sdrbase/dsp/phaselock.cpp index ce6c57a97..86de423ae 100644 --- a/sdrbase/dsp/phaselock.cpp +++ b/sdrbase/dsp/phaselock.cpp @@ -262,61 +262,85 @@ void PhaseLock::process(const Real& sample_in, Real *samples_out) processPhase(samples_out); // Multiply locked tone with input. - Real x = sample_in; - Real phasor_i = m_psin * x; - Real phasor_q = m_pcos * x; + Real phasor_i = m_psin * sample_in; + Real phasor_q = m_pcos * sample_in; - // Run IQ phase error through low-pass filter. - phasor_i = m_phasor_b0 * phasor_i - - m_phasor_a1 * m_phasor_i1 - - m_phasor_a2 * m_phasor_i2; - phasor_q = m_phasor_b0 * phasor_q - - m_phasor_a1 * m_phasor_q1 - - m_phasor_a2 * m_phasor_q2; - m_phasor_i2 = m_phasor_i1; - m_phasor_i1 = phasor_i; - m_phasor_q2 = m_phasor_q1; - m_phasor_q1 = phasor_q; + // Actual PLL + process_phasor(phasor_i, phasor_q); +} - // Convert I/Q ratio to estimate of phase error. - Real phase_err; +void PhaseLock::process(const Real& real_in, const Real& imag_in, Real *samples_out) +{ + m_pps_events.clear(); + + // Generate locked pilot tone. + m_psin = sin(m_phase); + m_pcos = cos(m_phase); + + // Generate output + processPhase(samples_out); + + // Multiply locked tone with input. + Real phasor_i = m_psin * real_in - m_pcos * imag_in; + Real phasor_q = m_pcos * real_in + m_psin * imag_in; + + // Actual PLL + process_phasor(phasor_i, phasor_q); +} + +void PhaseLock::process_phasor(Real& phasor_i, Real& phasor_q) +{ + // Run IQ phase error through low-pass filter. + phasor_i = m_phasor_b0 * phasor_i + - m_phasor_a1 * m_phasor_i1 + - m_phasor_a2 * m_phasor_i2; + phasor_q = m_phasor_b0 * phasor_q + - m_phasor_a1 * m_phasor_q1 + - m_phasor_a2 * m_phasor_q2; + m_phasor_i2 = m_phasor_i1; + m_phasor_i1 = phasor_i; + m_phasor_q2 = m_phasor_q1; + m_phasor_q1 = phasor_q; + + // Convert I/Q ratio to estimate of phase error. + Real phase_err; if (phasor_i > std::abs(phasor_q)) { - // We are within +/- 45 degrees from lock. - // Use simple linear approximation of arctan. - phase_err = phasor_q / phasor_i; - } else if (phasor_q > 0) { - // We are lagging more than 45 degrees behind the input. - phase_err = 1; - } else { - // We are more than 45 degrees ahead of the input. - phase_err = -1; - } + // We are within +/- 45 degrees from lock. + // Use simple linear approximation of arctan. + phase_err = phasor_q / phasor_i; + } else if (phasor_q > 0) { + // We are lagging more than 45 degrees behind the input. + phase_err = 1; + } else { + // We are more than 45 degrees ahead of the input. + phase_err = -1; + } - // Detect pilot level (conservative). - // m_pilot_level = std::min(m_pilot_level, phasor_i); - m_pilot_level = phasor_i; + // Detect pilot level (conservative). + // m_pilot_level = std::min(m_pilot_level, phasor_i); + m_pilot_level = phasor_i; - // Run phase error through loop filter and update frequency estimate. - m_freq += m_loopfilter_b0 * phase_err - + m_loopfilter_b1 * m_loopfilter_x1; - m_loopfilter_x1 = phase_err; + // Run phase error through loop filter and update frequency estimate. + m_freq += m_loopfilter_b0 * phase_err + + m_loopfilter_b1 * m_loopfilter_x1; + m_loopfilter_x1 = phase_err; - // Limit frequency to allowable range. - m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq)); + // Limit frequency to allowable range. + m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq)); - // Update locked phase. - m_phase += m_freq; - if (m_phase > 2.0 * M_PI) - { - m_phase -= 2.0 * M_PI; - m_pilot_periods++; + // Update locked phase. + m_phase += m_freq; + if (m_phase > 2.0 * M_PI) + { + m_phase -= 2.0 * M_PI; + m_pilot_periods++; - // Generate pulse-per-second. - if (m_pilot_periods == pilot_frequency) - { - m_pilot_periods = 0; - } - } + // Generate pulse-per-second. + if (m_pilot_periods == pilot_frequency) + { + m_pilot_periods = 0; + } + } // Update lock status. if (2 * m_pilot_level > m_minsignal) @@ -328,7 +352,7 @@ void PhaseLock::process(const Real& sample_in, Real *samples_out) } else { - m_lock_cnt = 0; + m_lock_cnt = 0; } // Drop PPS events when pilot not locked. diff --git a/sdrbase/dsp/phaselock.h b/sdrbase/dsp/phaselock.h index 06196c5e7..ab1b2af03 100644 --- a/sdrbase/dsp/phaselock.h +++ b/sdrbase/dsp/phaselock.h @@ -73,6 +73,7 @@ public: * This is the in flow version */ void process(const Real& sample_in, Real *samples_out); + void process(const Real& real_in, const Real& imag_in, Real *samples_out); /** Return true if the phase-locked loop is locked. */ bool locked() const @@ -111,6 +112,8 @@ private: quint64 m_pps_cnt; quint64 m_sample_cnt; std::vector m_pps_events; + + void process_phasor(Real& phasor_i, Real& phasor_q); }; class SimplePhaseLock : public PhaseLock From f4ac9bf114598a67e6a0f1da2154fab5f2028c6e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 12 May 2018 07:25:53 +0200 Subject: [PATCH 399/956] RTL-SDR: fixed inf/sup decimators --- plugins/samplesource/rtlsdr/rtlsdrplugin.cpp | 2 +- sdrbase/dsp/decimators.h | 1 - sdrbase/dsp/decimatorsu.h | 2146 +++++++++++++++--- 3 files changed, 1887 insertions(+), 262 deletions(-) diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp index bf9ed0ddc..7cd29731b 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp @@ -14,7 +14,7 @@ const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = { QString("RTL-SDR Input"), - QString("3.14.6"), + QString("3.14.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index 3de3fb5e7..ee54b56e9 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -282,7 +282,6 @@ public: // interleaved I/Q input buffer void decimate1(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len); - void decimate2_inf_raw(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len); diff --git a/sdrbase/dsp/decimatorsu.h b/sdrbase/dsp/decimatorsu.h index 60c122b11..91a3f1994 100644 --- a/sdrbase/dsp/decimatorsu.h +++ b/sdrbase/dsp/decimatorsu.h @@ -233,384 +233,2010 @@ void DecimatorsU::decimate1(SampleVec template void DecimatorsU::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - qint32 xreal, yimag; + StorageType buf2[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; - yimag = (buf[pos+1] + buf[pos+2] - 2*Shift) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); - xreal = (buf[pos+7] - buf[pos+4]) << decimation_shifts::pre2; - yimag = (2*Shift - buf[pos+5] - buf[pos+6]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - } + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } } template void DecimatorsU::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal, yimag; + StorageType buf2[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+1] - buf[pos+2]) << decimation_shifts::pre2; - yimag = (2*Shift - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); - xreal = (buf[pos+6] - buf[pos+5]) << decimation_shifts::pre2; - yimag = (buf[pos+4] + buf[pos+7] - 2*Shift) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - } + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } } template void DecimatorsU::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal, yimag; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre4; - yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre4; + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); - ++(*it); - } + m_decimator4.myDecimateSup( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); + } } template void DecimatorsU::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - // Sup (USB): - // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 - // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - // Inf (LSB): - // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 - // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - StorageType xreal, yimag; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre4; - yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre4; + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); - ++(*it); - } + m_decimator4.myDecimateInf( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); + } } template -void DecimatorsU::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) +void DecimatorsU::decimate8_inf(SampleVector::iterator* it, const T* buf __attribute__((unused)), qint32 len) { - StorageType xreal[2], yimag[2]; + for (int pos = 0; pos < len - 15; pos += 8) + { + (**it).setReal(0); + (**it).setImag(0); - for (int pos = 0; pos < len - 15; pos += 8) - { - xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre8; - yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre8; - pos += 8; - - xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre8; - yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre8; - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); - - ++(*it); - } + ++(*it); + } } template -void DecimatorsU::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) +void DecimatorsU::decimate8_sup(SampleVector::iterator* it, const T* buf __attribute__((unused)), qint32 len) { - StorageType xreal[2], yimag[2]; + for (int pos = 0; pos < len - 15; pos += 8) + { + (**it).setReal(0); + (**it).setImag(0); - for (int pos = 0; pos < len - 15; pos += 8) - { - xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre8; - yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre8; - pos += 8; - - xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre8; - yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre8; - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); - - ++(*it); - } + ++(*it); + } } template void DecimatorsU::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - StorageType xreal[4], yimag[4]; + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; - for (int pos = 0; pos < len - 31; ) - { - for (int i = 0; i < 4; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre16; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre16; - pos += 8; - } + for (int pos = 0; pos < len - 63; pos += 64) + { + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + buf[pos+2] << decimation_shifts::pre16, + buf[pos+3] << decimation_shifts::pre16, + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + buf[pos+6] << decimation_shifts::pre16, + buf[pos+7] << decimation_shifts::pre16, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + buf[pos+10] << decimation_shifts::pre16, + buf[pos+11] << decimation_shifts::pre16, + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + buf[pos+14] << decimation_shifts::pre16, + buf[pos+15] << decimation_shifts::pre16, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + buf[pos+18] << decimation_shifts::pre16, + buf[pos+19] << decimation_shifts::pre16, + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + buf[pos+22] << decimation_shifts::pre16, + buf[pos+23] << decimation_shifts::pre16, + &buf2[8]); - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + buf[pos+26] << decimation_shifts::pre16, + buf[pos+27] << decimation_shifts::pre16, + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + buf[pos+30] << decimation_shifts::pre16, + buf[pos+31] << decimation_shifts::pre16, + &buf2[12]); - ++(*it); - } + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre16, + buf[pos+33] << decimation_shifts::pre16, + buf[pos+34] << decimation_shifts::pre16, + buf[pos+35] << decimation_shifts::pre16, + buf[pos+36] << decimation_shifts::pre16, + buf[pos+37] << decimation_shifts::pre16, + buf[pos+38] << decimation_shifts::pre16, + buf[pos+39] << decimation_shifts::pre16, + &buf2[16]); + + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre16, + buf[pos+41] << decimation_shifts::pre16, + buf[pos+42] << decimation_shifts::pre16, + buf[pos+43] << decimation_shifts::pre16, + buf[pos+44] << decimation_shifts::pre16, + buf[pos+45] << decimation_shifts::pre16, + buf[pos+46] << decimation_shifts::pre16, + buf[pos+47] << decimation_shifts::pre16, + &buf2[20]); + + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre16, + buf[pos+49] << decimation_shifts::pre16, + buf[pos+50] << decimation_shifts::pre16, + buf[pos+51] << decimation_shifts::pre16, + buf[pos+52] << decimation_shifts::pre16, + buf[pos+53] << decimation_shifts::pre16, + buf[pos+54] << decimation_shifts::pre16, + buf[pos+55] << decimation_shifts::pre16, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre16, + buf[pos+57] << decimation_shifts::pre16, + buf[pos+58] << decimation_shifts::pre16, + buf[pos+59] << decimation_shifts::pre16, + buf[pos+60] << decimation_shifts::pre16, + buf[pos+61] << decimation_shifts::pre16, + buf[pos+62] << decimation_shifts::pre16, + buf[pos+63] << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[0] >> decimation_shifts::post16); + (**it).setImag(buf16[1] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[2] >> decimation_shifts::post16); + (**it).setImag(buf16[3] >> decimation_shifts::post16); + ++(*it); + } } template void DecimatorsU::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - StorageType xreal[4], yimag[4]; + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; - for (int pos = 0; pos < len - 31; ) - { - for (int i = 0; i < 4; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre16; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre16; - pos += 8; - } + for (int pos = 0; pos < len - 63; pos += 64) + { + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + buf[pos+2] << decimation_shifts::pre16, + buf[pos+3] << decimation_shifts::pre16, + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + buf[pos+6] << decimation_shifts::pre16, + buf[pos+7] << decimation_shifts::pre16, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + buf[pos+10] << decimation_shifts::pre16, + buf[pos+11] << decimation_shifts::pre16, + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + buf[pos+14] << decimation_shifts::pre16, + buf[pos+15] << decimation_shifts::pre16, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + buf[pos+18] << decimation_shifts::pre16, + buf[pos+19] << decimation_shifts::pre16, + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + buf[pos+22] << decimation_shifts::pre16, + buf[pos+23] << decimation_shifts::pre16, + &buf2[8]); - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + buf[pos+26] << decimation_shifts::pre16, + buf[pos+27] << decimation_shifts::pre16, + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + buf[pos+30] << decimation_shifts::pre16, + buf[pos+31] << decimation_shifts::pre16, + &buf2[12]); - ++(*it); - } + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre16, + buf[pos+33] << decimation_shifts::pre16, + buf[pos+34] << decimation_shifts::pre16, + buf[pos+35] << decimation_shifts::pre16, + buf[pos+36] << decimation_shifts::pre16, + buf[pos+37] << decimation_shifts::pre16, + buf[pos+38] << decimation_shifts::pre16, + buf[pos+39] << decimation_shifts::pre16, + &buf2[16]); + + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre16, + buf[pos+41] << decimation_shifts::pre16, + buf[pos+42] << decimation_shifts::pre16, + buf[pos+43] << decimation_shifts::pre16, + buf[pos+44] << decimation_shifts::pre16, + buf[pos+45] << decimation_shifts::pre16, + buf[pos+46] << decimation_shifts::pre16, + buf[pos+47] << decimation_shifts::pre16, + &buf2[20]); + + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre16, + buf[pos+49] << decimation_shifts::pre16, + buf[pos+50] << decimation_shifts::pre16, + buf[pos+51] << decimation_shifts::pre16, + buf[pos+52] << decimation_shifts::pre16, + buf[pos+53] << decimation_shifts::pre16, + buf[pos+54] << decimation_shifts::pre16, + buf[pos+55] << decimation_shifts::pre16, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre16, + buf[pos+57] << decimation_shifts::pre16, + buf[pos+58] << decimation_shifts::pre16, + buf[pos+59] << decimation_shifts::pre16, + buf[pos+60] << decimation_shifts::pre16, + buf[pos+61] << decimation_shifts::pre16, + buf[pos+62] << decimation_shifts::pre16, + buf[pos+63] << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[0] >> decimation_shifts::post16); + (**it).setImag(buf16[1] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[2] >> decimation_shifts::post16); + (**it).setImag(buf16[3] >> decimation_shifts::post16); + ++(*it); + } } template void DecimatorsU::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[8], yimag[8]; + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; - for (int pos = 0; pos < len - 63; ) - { - for (int i = 0; i < 8; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre32; - pos += 8; - } + for (int pos = 0; pos < len - 127; pos += 128) + { + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + buf[pos+2] << decimation_shifts::pre32, + buf[pos+3] << decimation_shifts::pre32, + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + buf[pos+6] << decimation_shifts::pre32, + buf[pos+7] << decimation_shifts::pre32, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + buf[pos+10] << decimation_shifts::pre32, + buf[pos+11] << decimation_shifts::pre32, + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + buf[pos+14] << decimation_shifts::pre32, + buf[pos+15] << decimation_shifts::pre32, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + buf[pos+18] << decimation_shifts::pre32, + buf[pos+19] << decimation_shifts::pre32, + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + buf[pos+22] << decimation_shifts::pre32, + buf[pos+23] << decimation_shifts::pre32, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + buf[pos+26] << decimation_shifts::pre32, + buf[pos+27] << decimation_shifts::pre32, + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + buf[pos+30] << decimation_shifts::pre32, + buf[pos+31] << decimation_shifts::pre32, + &buf2[12]); - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + buf[pos+34] << decimation_shifts::pre32, + buf[pos+35] << decimation_shifts::pre32, + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + buf[pos+38] << decimation_shifts::pre32, + buf[pos+39] << decimation_shifts::pre32, + &buf2[16]); - ++(*it); - } + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + buf[pos+42] << decimation_shifts::pre32, + buf[pos+43] << decimation_shifts::pre32, + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + buf[pos+46] << decimation_shifts::pre32, + buf[pos+47] << decimation_shifts::pre32, + &buf2[20]); + + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + buf[pos+50] << decimation_shifts::pre32, + buf[pos+51] << decimation_shifts::pre32, + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + buf[pos+54] << decimation_shifts::pre32, + buf[pos+55] << decimation_shifts::pre32, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + buf[pos+58] << decimation_shifts::pre32, + buf[pos+59] << decimation_shifts::pre32, + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + buf[pos+62] << decimation_shifts::pre32, + buf[pos+63] << decimation_shifts::pre32, + &buf2[28]); + + m_decimator2.myDecimateInf( + buf[pos+64] << decimation_shifts::pre32, + buf[pos+65] << decimation_shifts::pre32, + buf[pos+66] << decimation_shifts::pre32, + buf[pos+67] << decimation_shifts::pre32, + buf[pos+68] << decimation_shifts::pre32, + buf[pos+69] << decimation_shifts::pre32, + buf[pos+70] << decimation_shifts::pre32, + buf[pos+71] << decimation_shifts::pre32, + &buf2[32]); + + m_decimator2.myDecimateInf( + buf[pos+72] << decimation_shifts::pre32, + buf[pos+73] << decimation_shifts::pre32, + buf[pos+74] << decimation_shifts::pre32, + buf[pos+75] << decimation_shifts::pre32, + buf[pos+76] << decimation_shifts::pre32, + buf[pos+77] << decimation_shifts::pre32, + buf[pos+78] << decimation_shifts::pre32, + buf[pos+79] << decimation_shifts::pre32, + &buf2[36]); + + m_decimator2.myDecimateInf( + buf[pos+80] << decimation_shifts::pre32, + buf[pos+81] << decimation_shifts::pre32, + buf[pos+82] << decimation_shifts::pre32, + buf[pos+83] << decimation_shifts::pre32, + buf[pos+84] << decimation_shifts::pre32, + buf[pos+85] << decimation_shifts::pre32, + buf[pos+86] << decimation_shifts::pre32, + buf[pos+87] << decimation_shifts::pre32, + &buf2[40]); + + m_decimator2.myDecimateInf( + buf[pos+88] << decimation_shifts::pre32, + buf[pos+89] << decimation_shifts::pre32, + buf[pos+90] << decimation_shifts::pre32, + buf[pos+91] << decimation_shifts::pre32, + buf[pos+92] << decimation_shifts::pre32, + buf[pos+93] << decimation_shifts::pre32, + buf[pos+94] << decimation_shifts::pre32, + buf[pos+95] << decimation_shifts::pre32, + &buf2[44]); + + m_decimator2.myDecimateInf( + buf[pos+96] << decimation_shifts::pre32, + buf[pos+97] << decimation_shifts::pre32, + buf[pos+98] << decimation_shifts::pre32, + buf[pos+99] << decimation_shifts::pre32, + buf[pos+100] << decimation_shifts::pre32, + buf[pos+101] << decimation_shifts::pre32, + buf[pos+102] << decimation_shifts::pre32, + buf[pos+103] << decimation_shifts::pre32, + &buf2[48]); + + m_decimator2.myDecimateInf( + buf[pos+104] << decimation_shifts::pre32, + buf[pos+105] << decimation_shifts::pre32, + buf[pos+106] << decimation_shifts::pre32, + buf[pos+107] << decimation_shifts::pre32, + buf[pos+108] << decimation_shifts::pre32, + buf[pos+109] << decimation_shifts::pre32, + buf[pos+110] << decimation_shifts::pre32, + buf[pos+111] << decimation_shifts::pre32, + &buf2[52]); + + m_decimator2.myDecimateInf( + buf[pos+112] << decimation_shifts::pre32, + buf[pos+113] << decimation_shifts::pre32, + buf[pos+114] << decimation_shifts::pre32, + buf[pos+115] << decimation_shifts::pre32, + buf[pos+116] << decimation_shifts::pre32, + buf[pos+117] << decimation_shifts::pre32, + buf[pos+118] << decimation_shifts::pre32, + buf[pos+119] << decimation_shifts::pre32, + &buf2[56]); + + m_decimator2.myDecimateInf( + buf[pos+120] << decimation_shifts::pre32, + buf[pos+121] << decimation_shifts::pre32, + buf[pos+122] << decimation_shifts::pre32, + buf[pos+123] << decimation_shifts::pre32, + buf[pos+124] << decimation_shifts::pre32, + buf[pos+125] << decimation_shifts::pre32, + buf[pos+126] << decimation_shifts::pre32, + buf[pos+127] << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[0] >> decimation_shifts::post32); + (**it).setImag(buf32[1] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[2] >> decimation_shifts::post32); + (**it).setImag(buf32[3] >> decimation_shifts::post32); + ++(*it); + } } template void DecimatorsU::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[8], yimag[8]; + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; - for (int pos = 0; pos < len - 63; ) - { - for (int i = 0; i < 8; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; - pos += 8; - } + for (int pos = 0; pos < len - 127; pos += 128) + { + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + buf[pos+2] << decimation_shifts::pre32, + buf[pos+3] << decimation_shifts::pre32, + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + buf[pos+6] << decimation_shifts::pre32, + buf[pos+7] << decimation_shifts::pre32, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + buf[pos+10] << decimation_shifts::pre32, + buf[pos+11] << decimation_shifts::pre32, + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + buf[pos+14] << decimation_shifts::pre32, + buf[pos+15] << decimation_shifts::pre32, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + buf[pos+18] << decimation_shifts::pre32, + buf[pos+19] << decimation_shifts::pre32, + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + buf[pos+22] << decimation_shifts::pre32, + buf[pos+23] << decimation_shifts::pre32, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + buf[pos+26] << decimation_shifts::pre32, + buf[pos+27] << decimation_shifts::pre32, + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + buf[pos+30] << decimation_shifts::pre32, + buf[pos+31] << decimation_shifts::pre32, + &buf2[12]); - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + buf[pos+34] << decimation_shifts::pre32, + buf[pos+35] << decimation_shifts::pre32, + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + buf[pos+38] << decimation_shifts::pre32, + buf[pos+39] << decimation_shifts::pre32, + &buf2[16]); - ++(*it); - } + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + buf[pos+42] << decimation_shifts::pre32, + buf[pos+43] << decimation_shifts::pre32, + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + buf[pos+46] << decimation_shifts::pre32, + buf[pos+47] << decimation_shifts::pre32, + &buf2[20]); + + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + buf[pos+50] << decimation_shifts::pre32, + buf[pos+51] << decimation_shifts::pre32, + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + buf[pos+54] << decimation_shifts::pre32, + buf[pos+55] << decimation_shifts::pre32, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + buf[pos+58] << decimation_shifts::pre32, + buf[pos+59] << decimation_shifts::pre32, + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + buf[pos+62] << decimation_shifts::pre32, + buf[pos+63] << decimation_shifts::pre32, + &buf2[28]); + + m_decimator2.myDecimateSup( + buf[pos+64] << decimation_shifts::pre32, + buf[pos+65] << decimation_shifts::pre32, + buf[pos+66] << decimation_shifts::pre32, + buf[pos+67] << decimation_shifts::pre32, + buf[pos+68] << decimation_shifts::pre32, + buf[pos+69] << decimation_shifts::pre32, + buf[pos+70] << decimation_shifts::pre32, + buf[pos+71] << decimation_shifts::pre32, + &buf2[32]); + + m_decimator2.myDecimateSup( + buf[pos+72] << decimation_shifts::pre32, + buf[pos+73] << decimation_shifts::pre32, + buf[pos+74] << decimation_shifts::pre32, + buf[pos+75] << decimation_shifts::pre32, + buf[pos+76] << decimation_shifts::pre32, + buf[pos+77] << decimation_shifts::pre32, + buf[pos+78] << decimation_shifts::pre32, + buf[pos+79] << decimation_shifts::pre32, + &buf2[36]); + + m_decimator2.myDecimateSup( + buf[pos+80] << decimation_shifts::pre32, + buf[pos+81] << decimation_shifts::pre32, + buf[pos+82] << decimation_shifts::pre32, + buf[pos+83] << decimation_shifts::pre32, + buf[pos+84] << decimation_shifts::pre32, + buf[pos+85] << decimation_shifts::pre32, + buf[pos+86] << decimation_shifts::pre32, + buf[pos+87] << decimation_shifts::pre32, + &buf2[40]); + + m_decimator2.myDecimateSup( + buf[pos+88] << decimation_shifts::pre32, + buf[pos+89] << decimation_shifts::pre32, + buf[pos+90] << decimation_shifts::pre32, + buf[pos+91] << decimation_shifts::pre32, + buf[pos+92] << decimation_shifts::pre32, + buf[pos+93] << decimation_shifts::pre32, + buf[pos+94] << decimation_shifts::pre32, + buf[pos+95] << decimation_shifts::pre32, + &buf2[44]); + + m_decimator2.myDecimateSup( + buf[pos+96] << decimation_shifts::pre32, + buf[pos+97] << decimation_shifts::pre32, + buf[pos+98] << decimation_shifts::pre32, + buf[pos+99] << decimation_shifts::pre32, + buf[pos+100] << decimation_shifts::pre32, + buf[pos+101] << decimation_shifts::pre32, + buf[pos+102] << decimation_shifts::pre32, + buf[pos+103] << decimation_shifts::pre32, + &buf2[48]); + + m_decimator2.myDecimateSup( + buf[pos+104] << decimation_shifts::pre32, + buf[pos+105] << decimation_shifts::pre32, + buf[pos+106] << decimation_shifts::pre32, + buf[pos+107] << decimation_shifts::pre32, + buf[pos+108] << decimation_shifts::pre32, + buf[pos+109] << decimation_shifts::pre32, + buf[pos+110] << decimation_shifts::pre32, + buf[pos+111] << decimation_shifts::pre32, + &buf2[52]); + + m_decimator2.myDecimateSup( + buf[pos+112] << decimation_shifts::pre32, + buf[pos+113] << decimation_shifts::pre32, + buf[pos+114] << decimation_shifts::pre32, + buf[pos+115] << decimation_shifts::pre32, + buf[pos+116] << decimation_shifts::pre32, + buf[pos+117] << decimation_shifts::pre32, + buf[pos+118] << decimation_shifts::pre32, + buf[pos+119] << decimation_shifts::pre32, + &buf2[56]); + + m_decimator2.myDecimateSup( + buf[pos+120] << decimation_shifts::pre32, + buf[pos+121] << decimation_shifts::pre32, + buf[pos+122] << decimation_shifts::pre32, + buf[pos+123] << decimation_shifts::pre32, + buf[pos+124] << decimation_shifts::pre32, + buf[pos+125] << decimation_shifts::pre32, + buf[pos+126] << decimation_shifts::pre32, + buf[pos+127] << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[0] >> decimation_shifts::post32); + (**it).setImag(buf32[1] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[2] >> decimation_shifts::post32); + (**it).setImag(buf32[3] >> decimation_shifts::post32); + ++(*it); + } } template void DecimatorsU::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[16], yimag[16]; + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; - for (int pos = 0; pos < len - 127; ) - { - for (int i = 0; i < 16; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre64; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre64; - pos += 8; - } + for (int pos = 0; pos < len - 255; pos += 256) + { + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre64, + buf[pos+1] << decimation_shifts::pre64, + buf[pos+2] << decimation_shifts::pre64, + buf[pos+3] << decimation_shifts::pre64, + buf[pos+4] << decimation_shifts::pre64, + buf[pos+5] << decimation_shifts::pre64, + buf[pos+6] << decimation_shifts::pre64, + buf[pos+7] << decimation_shifts::pre64, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre64, + buf[pos+9] << decimation_shifts::pre64, + buf[pos+10] << decimation_shifts::pre64, + buf[pos+11] << decimation_shifts::pre64, + buf[pos+12] << decimation_shifts::pre64, + buf[pos+13] << decimation_shifts::pre64, + buf[pos+14] << decimation_shifts::pre64, + buf[pos+15] << decimation_shifts::pre64, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre64, + buf[pos+17] << decimation_shifts::pre64, + buf[pos+18] << decimation_shifts::pre64, + buf[pos+19] << decimation_shifts::pre64, + buf[pos+20] << decimation_shifts::pre64, + buf[pos+21] << decimation_shifts::pre64, + buf[pos+22] << decimation_shifts::pre64, + buf[pos+23] << decimation_shifts::pre64, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre64, + buf[pos+25] << decimation_shifts::pre64, + buf[pos+26] << decimation_shifts::pre64, + buf[pos+27] << decimation_shifts::pre64, + buf[pos+28] << decimation_shifts::pre64, + buf[pos+29] << decimation_shifts::pre64, + buf[pos+30] << decimation_shifts::pre64, + buf[pos+31] << decimation_shifts::pre64, + &buf2[12]); - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre64, + buf[pos+33] << decimation_shifts::pre64, + buf[pos+34] << decimation_shifts::pre64, + buf[pos+35] << decimation_shifts::pre64, + buf[pos+36] << decimation_shifts::pre64, + buf[pos+37] << decimation_shifts::pre64, + buf[pos+38] << decimation_shifts::pre64, + buf[pos+39] << decimation_shifts::pre64, + &buf2[16]); - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre64, + buf[pos+41] << decimation_shifts::pre64, + buf[pos+42] << decimation_shifts::pre64, + buf[pos+43] << decimation_shifts::pre64, + buf[pos+44] << decimation_shifts::pre64, + buf[pos+45] << decimation_shifts::pre64, + buf[pos+46] << decimation_shifts::pre64, + buf[pos+47] << decimation_shifts::pre64, + &buf2[20]); - ++(*it); - } + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre64, + buf[pos+49] << decimation_shifts::pre64, + buf[pos+50] << decimation_shifts::pre64, + buf[pos+51] << decimation_shifts::pre64, + buf[pos+52] << decimation_shifts::pre64, + buf[pos+53] << decimation_shifts::pre64, + buf[pos+54] << decimation_shifts::pre64, + buf[pos+55] << decimation_shifts::pre64, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre64, + buf[pos+57] << decimation_shifts::pre64, + buf[pos+58] << decimation_shifts::pre64, + buf[pos+59] << decimation_shifts::pre64, + buf[pos+60] << decimation_shifts::pre64, + buf[pos+61] << decimation_shifts::pre64, + buf[pos+62] << decimation_shifts::pre64, + buf[pos+63] << decimation_shifts::pre64, + &buf2[28]); + + m_decimator2.myDecimateInf( + buf[pos+64] << decimation_shifts::pre64, + buf[pos+65] << decimation_shifts::pre64, + buf[pos+66] << decimation_shifts::pre64, + buf[pos+67] << decimation_shifts::pre64, + buf[pos+68] << decimation_shifts::pre64, + buf[pos+69] << decimation_shifts::pre64, + buf[pos+70] << decimation_shifts::pre64, + buf[pos+71] << decimation_shifts::pre64, + &buf2[32]); + + m_decimator2.myDecimateInf( + buf[pos+72] << decimation_shifts::pre64, + buf[pos+73] << decimation_shifts::pre64, + buf[pos+74] << decimation_shifts::pre64, + buf[pos+75] << decimation_shifts::pre64, + buf[pos+76] << decimation_shifts::pre64, + buf[pos+77] << decimation_shifts::pre64, + buf[pos+78] << decimation_shifts::pre64, + buf[pos+79] << decimation_shifts::pre64, + &buf2[36]); + + m_decimator2.myDecimateInf( + buf[pos+80] << decimation_shifts::pre64, + buf[pos+81] << decimation_shifts::pre64, + buf[pos+82] << decimation_shifts::pre64, + buf[pos+83] << decimation_shifts::pre64, + buf[pos+84] << decimation_shifts::pre64, + buf[pos+85] << decimation_shifts::pre64, + buf[pos+86] << decimation_shifts::pre64, + buf[pos+87] << decimation_shifts::pre64, + &buf2[40]); + + m_decimator2.myDecimateInf( + buf[pos+88] << decimation_shifts::pre64, + buf[pos+89] << decimation_shifts::pre64, + buf[pos+90] << decimation_shifts::pre64, + buf[pos+91] << decimation_shifts::pre64, + buf[pos+92] << decimation_shifts::pre64, + buf[pos+93] << decimation_shifts::pre64, + buf[pos+94] << decimation_shifts::pre64, + buf[pos+95] << decimation_shifts::pre64, + &buf2[44]); + + m_decimator2.myDecimateInf( + buf[pos+96] << decimation_shifts::pre64, + buf[pos+97] << decimation_shifts::pre64, + buf[pos+98] << decimation_shifts::pre64, + buf[pos+99] << decimation_shifts::pre64, + buf[pos+100] << decimation_shifts::pre64, + buf[pos+101] << decimation_shifts::pre64, + buf[pos+102] << decimation_shifts::pre64, + buf[pos+103] << decimation_shifts::pre64, + &buf2[48]); + + m_decimator2.myDecimateInf( + buf[pos+104] << decimation_shifts::pre64, + buf[pos+105] << decimation_shifts::pre64, + buf[pos+106] << decimation_shifts::pre64, + buf[pos+107] << decimation_shifts::pre64, + buf[pos+108] << decimation_shifts::pre64, + buf[pos+109] << decimation_shifts::pre64, + buf[pos+110] << decimation_shifts::pre64, + buf[pos+111] << decimation_shifts::pre64, + &buf2[52]); + + m_decimator2.myDecimateInf( + buf[pos+112] << decimation_shifts::pre64, + buf[pos+113] << decimation_shifts::pre64, + buf[pos+114] << decimation_shifts::pre64, + buf[pos+115] << decimation_shifts::pre64, + buf[pos+116] << decimation_shifts::pre64, + buf[pos+117] << decimation_shifts::pre64, + buf[pos+118] << decimation_shifts::pre64, + buf[pos+119] << decimation_shifts::pre64, + &buf2[56]); + + m_decimator2.myDecimateInf( + buf[pos+120] << decimation_shifts::pre64, + buf[pos+121] << decimation_shifts::pre64, + buf[pos+122] << decimation_shifts::pre64, + buf[pos+123] << decimation_shifts::pre64, + buf[pos+124] << decimation_shifts::pre64, + buf[pos+125] << decimation_shifts::pre64, + buf[pos+126] << decimation_shifts::pre64, + buf[pos+127] << decimation_shifts::pre64, + &buf2[60]); + + m_decimator2.myDecimateInf( + buf[pos+128] << decimation_shifts::pre64, + buf[pos+129] << decimation_shifts::pre64, + buf[pos+130] << decimation_shifts::pre64, + buf[pos+131] << decimation_shifts::pre64, + buf[pos+132] << decimation_shifts::pre64, + buf[pos+133] << decimation_shifts::pre64, + buf[pos+134] << decimation_shifts::pre64, + buf[pos+135] << decimation_shifts::pre64, + &buf2[64]); + + m_decimator2.myDecimateInf( + buf[pos+136] << decimation_shifts::pre64, + buf[pos+137] << decimation_shifts::pre64, + buf[pos+138] << decimation_shifts::pre64, + buf[pos+139] << decimation_shifts::pre64, + buf[pos+140] << decimation_shifts::pre64, + buf[pos+141] << decimation_shifts::pre64, + buf[pos+142] << decimation_shifts::pre64, + buf[pos+143] << decimation_shifts::pre64, + &buf2[68]); + + m_decimator2.myDecimateInf( + buf[pos+144] << decimation_shifts::pre64, + buf[pos+145] << decimation_shifts::pre64, + buf[pos+146] << decimation_shifts::pre64, + buf[pos+147] << decimation_shifts::pre64, + buf[pos+148] << decimation_shifts::pre64, + buf[pos+149] << decimation_shifts::pre64, + buf[pos+150] << decimation_shifts::pre64, + buf[pos+151] << decimation_shifts::pre64, + &buf2[72]); + + m_decimator2.myDecimateInf( + buf[pos+152] << decimation_shifts::pre64, + buf[pos+153] << decimation_shifts::pre64, + buf[pos+154] << decimation_shifts::pre64, + buf[pos+155] << decimation_shifts::pre64, + buf[pos+156] << decimation_shifts::pre64, + buf[pos+157] << decimation_shifts::pre64, + buf[pos+158] << decimation_shifts::pre64, + buf[pos+159] << decimation_shifts::pre64, + &buf2[76]); + + m_decimator2.myDecimateInf( + buf[pos+160] << decimation_shifts::pre64, + buf[pos+161] << decimation_shifts::pre64, + buf[pos+162] << decimation_shifts::pre64, + buf[pos+163] << decimation_shifts::pre64, + buf[pos+164] << decimation_shifts::pre64, + buf[pos+165] << decimation_shifts::pre64, + buf[pos+166] << decimation_shifts::pre64, + buf[pos+167] << decimation_shifts::pre64, + &buf2[80]); + + m_decimator2.myDecimateInf( + buf[pos+168] << decimation_shifts::pre64, + buf[pos+169] << decimation_shifts::pre64, + buf[pos+170] << decimation_shifts::pre64, + buf[pos+171] << decimation_shifts::pre64, + buf[pos+172] << decimation_shifts::pre64, + buf[pos+173] << decimation_shifts::pre64, + buf[pos+174] << decimation_shifts::pre64, + buf[pos+175] << decimation_shifts::pre64, + &buf2[84]); + + m_decimator2.myDecimateInf( + buf[pos+176] << decimation_shifts::pre64, + buf[pos+177] << decimation_shifts::pre64, + buf[pos+178] << decimation_shifts::pre64, + buf[pos+179] << decimation_shifts::pre64, + buf[pos+180] << decimation_shifts::pre64, + buf[pos+181] << decimation_shifts::pre64, + buf[pos+182] << decimation_shifts::pre64, + buf[pos+183] << decimation_shifts::pre64, + &buf2[88]); + + m_decimator2.myDecimateInf( + buf[pos+184] << decimation_shifts::pre64, + buf[pos+185] << decimation_shifts::pre64, + buf[pos+186] << decimation_shifts::pre64, + buf[pos+187] << decimation_shifts::pre64, + buf[pos+188] << decimation_shifts::pre64, + buf[pos+189] << decimation_shifts::pre64, + buf[pos+190] << decimation_shifts::pre64, + buf[pos+191] << decimation_shifts::pre64, + &buf2[92]); + + m_decimator2.myDecimateInf( + buf[pos+192] << decimation_shifts::pre64, + buf[pos+193] << decimation_shifts::pre64, + buf[pos+194] << decimation_shifts::pre64, + buf[pos+195] << decimation_shifts::pre64, + buf[pos+196] << decimation_shifts::pre64, + buf[pos+197] << decimation_shifts::pre64, + buf[pos+198] << decimation_shifts::pre64, + buf[pos+199] << decimation_shifts::pre64, + &buf2[96]); + + m_decimator2.myDecimateInf( + buf[pos+200] << decimation_shifts::pre64, + buf[pos+201] << decimation_shifts::pre64, + buf[pos+202] << decimation_shifts::pre64, + buf[pos+203] << decimation_shifts::pre64, + buf[pos+204] << decimation_shifts::pre64, + buf[pos+205] << decimation_shifts::pre64, + buf[pos+206] << decimation_shifts::pre64, + buf[pos+207] << decimation_shifts::pre64, + &buf2[100]); + + m_decimator2.myDecimateInf( + buf[pos+208] << decimation_shifts::pre64, + buf[pos+209] << decimation_shifts::pre64, + buf[pos+210] << decimation_shifts::pre64, + buf[pos+211] << decimation_shifts::pre64, + buf[pos+212] << decimation_shifts::pre64, + buf[pos+213] << decimation_shifts::pre64, + buf[pos+214] << decimation_shifts::pre64, + buf[pos+215] << decimation_shifts::pre64, + &buf2[104]); + + m_decimator2.myDecimateInf( + buf[pos+216] << decimation_shifts::pre64, + buf[pos+217] << decimation_shifts::pre64, + buf[pos+218] << decimation_shifts::pre64, + buf[pos+219] << decimation_shifts::pre64, + buf[pos+220] << decimation_shifts::pre64, + buf[pos+221] << decimation_shifts::pre64, + buf[pos+222] << decimation_shifts::pre64, + buf[pos+223] << decimation_shifts::pre64, + &buf2[108]); + + m_decimator2.myDecimateInf( + buf[pos+224] << decimation_shifts::pre64, + buf[pos+225] << decimation_shifts::pre64, + buf[pos+226] << decimation_shifts::pre64, + buf[pos+227] << decimation_shifts::pre64, + buf[pos+228] << decimation_shifts::pre64, + buf[pos+229] << decimation_shifts::pre64, + buf[pos+230] << decimation_shifts::pre64, + buf[pos+231] << decimation_shifts::pre64, + &buf2[112]); + + m_decimator2.myDecimateInf( + buf[pos+232] << decimation_shifts::pre64, + buf[pos+233] << decimation_shifts::pre64, + buf[pos+234] << decimation_shifts::pre64, + buf[pos+235] << decimation_shifts::pre64, + buf[pos+236] << decimation_shifts::pre64, + buf[pos+237] << decimation_shifts::pre64, + buf[pos+238] << decimation_shifts::pre64, + buf[pos+239] << decimation_shifts::pre64, + &buf2[116]); + + m_decimator2.myDecimateInf( + buf[pos+240] << decimation_shifts::pre64, + buf[pos+241] << decimation_shifts::pre64, + buf[pos+242] << decimation_shifts::pre64, + buf[pos+243] << decimation_shifts::pre64, + buf[pos+244] << decimation_shifts::pre64, + buf[pos+245] << decimation_shifts::pre64, + buf[pos+246] << decimation_shifts::pre64, + buf[pos+247] << decimation_shifts::pre64, + &buf2[120]); + + m_decimator2.myDecimateInf( + buf[pos+248] << decimation_shifts::pre64, + buf[pos+249] << decimation_shifts::pre64, + buf[pos+250] << decimation_shifts::pre64, + buf[pos+251] << decimation_shifts::pre64, + buf[pos+252] << decimation_shifts::pre64, + buf[pos+253] << decimation_shifts::pre64, + buf[pos+254] << decimation_shifts::pre64, + buf[pos+255] << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateSup( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateSup( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateSup( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateSup( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateSup( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateSup( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateSup( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateSup( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateSup( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateSup( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateSup( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateSup( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateSup( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateSup( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateSup( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateSup( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[0] >> decimation_shifts::post64); + (**it).setImag(buf64[1] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[2] >> decimation_shifts::post64); + (**it).setImag(buf64[3] >> decimation_shifts::post64); + ++(*it); + } } template void DecimatorsU::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType xreal[16], yimag[16]; + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; - for (int pos = 0; pos < len - 127; ) - { - for (int i = 0; i < 16; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; - pos += 8; - } + for (int pos = 0; pos < len - 255; pos += 256) + { + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre64, + buf[pos+1] << decimation_shifts::pre64, + buf[pos+2] << decimation_shifts::pre64, + buf[pos+3] << decimation_shifts::pre64, + buf[pos+4] << decimation_shifts::pre64, + buf[pos+5] << decimation_shifts::pre64, + buf[pos+6] << decimation_shifts::pre64, + buf[pos+7] << decimation_shifts::pre64, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre64, + buf[pos+9] << decimation_shifts::pre64, + buf[pos+10] << decimation_shifts::pre64, + buf[pos+11] << decimation_shifts::pre64, + buf[pos+12] << decimation_shifts::pre64, + buf[pos+13] << decimation_shifts::pre64, + buf[pos+14] << decimation_shifts::pre64, + buf[pos+15] << decimation_shifts::pre64, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre64, + buf[pos+17] << decimation_shifts::pre64, + buf[pos+18] << decimation_shifts::pre64, + buf[pos+19] << decimation_shifts::pre64, + buf[pos+20] << decimation_shifts::pre64, + buf[pos+21] << decimation_shifts::pre64, + buf[pos+22] << decimation_shifts::pre64, + buf[pos+23] << decimation_shifts::pre64, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre64, + buf[pos+25] << decimation_shifts::pre64, + buf[pos+26] << decimation_shifts::pre64, + buf[pos+27] << decimation_shifts::pre64, + buf[pos+28] << decimation_shifts::pre64, + buf[pos+29] << decimation_shifts::pre64, + buf[pos+30] << decimation_shifts::pre64, + buf[pos+31] << decimation_shifts::pre64, + &buf2[12]); - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre64, + buf[pos+33] << decimation_shifts::pre64, + buf[pos+34] << decimation_shifts::pre64, + buf[pos+35] << decimation_shifts::pre64, + buf[pos+36] << decimation_shifts::pre64, + buf[pos+37] << decimation_shifts::pre64, + buf[pos+38] << decimation_shifts::pre64, + buf[pos+39] << decimation_shifts::pre64, + &buf2[16]); - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre64, + buf[pos+41] << decimation_shifts::pre64, + buf[pos+42] << decimation_shifts::pre64, + buf[pos+43] << decimation_shifts::pre64, + buf[pos+44] << decimation_shifts::pre64, + buf[pos+45] << decimation_shifts::pre64, + buf[pos+46] << decimation_shifts::pre64, + buf[pos+47] << decimation_shifts::pre64, + &buf2[20]); - ++(*it); - } + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre64, + buf[pos+49] << decimation_shifts::pre64, + buf[pos+50] << decimation_shifts::pre64, + buf[pos+51] << decimation_shifts::pre64, + buf[pos+52] << decimation_shifts::pre64, + buf[pos+53] << decimation_shifts::pre64, + buf[pos+54] << decimation_shifts::pre64, + buf[pos+55] << decimation_shifts::pre64, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre64, + buf[pos+57] << decimation_shifts::pre64, + buf[pos+58] << decimation_shifts::pre64, + buf[pos+59] << decimation_shifts::pre64, + buf[pos+60] << decimation_shifts::pre64, + buf[pos+61] << decimation_shifts::pre64, + buf[pos+62] << decimation_shifts::pre64, + buf[pos+63] << decimation_shifts::pre64, + &buf2[28]); + + m_decimator2.myDecimateSup( + buf[pos+64] << decimation_shifts::pre64, + buf[pos+65] << decimation_shifts::pre64, + buf[pos+66] << decimation_shifts::pre64, + buf[pos+67] << decimation_shifts::pre64, + buf[pos+68] << decimation_shifts::pre64, + buf[pos+69] << decimation_shifts::pre64, + buf[pos+70] << decimation_shifts::pre64, + buf[pos+71] << decimation_shifts::pre64, + &buf2[32]); + + m_decimator2.myDecimateSup( + buf[pos+72] << decimation_shifts::pre64, + buf[pos+73] << decimation_shifts::pre64, + buf[pos+74] << decimation_shifts::pre64, + buf[pos+75] << decimation_shifts::pre64, + buf[pos+76] << decimation_shifts::pre64, + buf[pos+77] << decimation_shifts::pre64, + buf[pos+78] << decimation_shifts::pre64, + buf[pos+79] << decimation_shifts::pre64, + &buf2[36]); + + m_decimator2.myDecimateSup( + buf[pos+80] << decimation_shifts::pre64, + buf[pos+81] << decimation_shifts::pre64, + buf[pos+82] << decimation_shifts::pre64, + buf[pos+83] << decimation_shifts::pre64, + buf[pos+84] << decimation_shifts::pre64, + buf[pos+85] << decimation_shifts::pre64, + buf[pos+86] << decimation_shifts::pre64, + buf[pos+87] << decimation_shifts::pre64, + &buf2[40]); + + m_decimator2.myDecimateSup( + buf[pos+88] << decimation_shifts::pre64, + buf[pos+89] << decimation_shifts::pre64, + buf[pos+90] << decimation_shifts::pre64, + buf[pos+91] << decimation_shifts::pre64, + buf[pos+92] << decimation_shifts::pre64, + buf[pos+93] << decimation_shifts::pre64, + buf[pos+94] << decimation_shifts::pre64, + buf[pos+95] << decimation_shifts::pre64, + &buf2[44]); + + m_decimator2.myDecimateSup( + buf[pos+96] << decimation_shifts::pre64, + buf[pos+97] << decimation_shifts::pre64, + buf[pos+98] << decimation_shifts::pre64, + buf[pos+99] << decimation_shifts::pre64, + buf[pos+100] << decimation_shifts::pre64, + buf[pos+101] << decimation_shifts::pre64, + buf[pos+102] << decimation_shifts::pre64, + buf[pos+103] << decimation_shifts::pre64, + &buf2[48]); + + m_decimator2.myDecimateSup( + buf[pos+104] << decimation_shifts::pre64, + buf[pos+105] << decimation_shifts::pre64, + buf[pos+106] << decimation_shifts::pre64, + buf[pos+107] << decimation_shifts::pre64, + buf[pos+108] << decimation_shifts::pre64, + buf[pos+109] << decimation_shifts::pre64, + buf[pos+110] << decimation_shifts::pre64, + buf[pos+111] << decimation_shifts::pre64, + &buf2[52]); + + m_decimator2.myDecimateSup( + buf[pos+112] << decimation_shifts::pre64, + buf[pos+113] << decimation_shifts::pre64, + buf[pos+114] << decimation_shifts::pre64, + buf[pos+115] << decimation_shifts::pre64, + buf[pos+116] << decimation_shifts::pre64, + buf[pos+117] << decimation_shifts::pre64, + buf[pos+118] << decimation_shifts::pre64, + buf[pos+119] << decimation_shifts::pre64, + &buf2[56]); + + m_decimator2.myDecimateSup( + buf[pos+120] << decimation_shifts::pre64, + buf[pos+121] << decimation_shifts::pre64, + buf[pos+122] << decimation_shifts::pre64, + buf[pos+123] << decimation_shifts::pre64, + buf[pos+124] << decimation_shifts::pre64, + buf[pos+125] << decimation_shifts::pre64, + buf[pos+126] << decimation_shifts::pre64, + buf[pos+127] << decimation_shifts::pre64, + &buf2[60]); + + m_decimator2.myDecimateSup( + buf[pos+128] << decimation_shifts::pre64, + buf[pos+129] << decimation_shifts::pre64, + buf[pos+130] << decimation_shifts::pre64, + buf[pos+131] << decimation_shifts::pre64, + buf[pos+132] << decimation_shifts::pre64, + buf[pos+133] << decimation_shifts::pre64, + buf[pos+134] << decimation_shifts::pre64, + buf[pos+135] << decimation_shifts::pre64, + &buf2[64]); + + m_decimator2.myDecimateSup( + buf[pos+136] << decimation_shifts::pre64, + buf[pos+137] << decimation_shifts::pre64, + buf[pos+138] << decimation_shifts::pre64, + buf[pos+139] << decimation_shifts::pre64, + buf[pos+140] << decimation_shifts::pre64, + buf[pos+141] << decimation_shifts::pre64, + buf[pos+142] << decimation_shifts::pre64, + buf[pos+143] << decimation_shifts::pre64, + &buf2[68]); + + m_decimator2.myDecimateSup( + buf[pos+144] << decimation_shifts::pre64, + buf[pos+145] << decimation_shifts::pre64, + buf[pos+146] << decimation_shifts::pre64, + buf[pos+147] << decimation_shifts::pre64, + buf[pos+148] << decimation_shifts::pre64, + buf[pos+149] << decimation_shifts::pre64, + buf[pos+150] << decimation_shifts::pre64, + buf[pos+151] << decimation_shifts::pre64, + &buf2[72]); + + m_decimator2.myDecimateSup( + buf[pos+152] << decimation_shifts::pre64, + buf[pos+153] << decimation_shifts::pre64, + buf[pos+154] << decimation_shifts::pre64, + buf[pos+155] << decimation_shifts::pre64, + buf[pos+156] << decimation_shifts::pre64, + buf[pos+157] << decimation_shifts::pre64, + buf[pos+158] << decimation_shifts::pre64, + buf[pos+159] << decimation_shifts::pre64, + &buf2[76]); + + m_decimator2.myDecimateSup( + buf[pos+160] << decimation_shifts::pre64, + buf[pos+161] << decimation_shifts::pre64, + buf[pos+162] << decimation_shifts::pre64, + buf[pos+163] << decimation_shifts::pre64, + buf[pos+164] << decimation_shifts::pre64, + buf[pos+165] << decimation_shifts::pre64, + buf[pos+166] << decimation_shifts::pre64, + buf[pos+167] << decimation_shifts::pre64, + &buf2[80]); + + m_decimator2.myDecimateSup( + buf[pos+168] << decimation_shifts::pre64, + buf[pos+169] << decimation_shifts::pre64, + buf[pos+170] << decimation_shifts::pre64, + buf[pos+171] << decimation_shifts::pre64, + buf[pos+172] << decimation_shifts::pre64, + buf[pos+173] << decimation_shifts::pre64, + buf[pos+174] << decimation_shifts::pre64, + buf[pos+175] << decimation_shifts::pre64, + &buf2[84]); + + m_decimator2.myDecimateSup( + buf[pos+176] << decimation_shifts::pre64, + buf[pos+177] << decimation_shifts::pre64, + buf[pos+178] << decimation_shifts::pre64, + buf[pos+179] << decimation_shifts::pre64, + buf[pos+180] << decimation_shifts::pre64, + buf[pos+181] << decimation_shifts::pre64, + buf[pos+182] << decimation_shifts::pre64, + buf[pos+183] << decimation_shifts::pre64, + &buf2[88]); + + m_decimator2.myDecimateSup( + buf[pos+184] << decimation_shifts::pre64, + buf[pos+185] << decimation_shifts::pre64, + buf[pos+186] << decimation_shifts::pre64, + buf[pos+187] << decimation_shifts::pre64, + buf[pos+188] << decimation_shifts::pre64, + buf[pos+189] << decimation_shifts::pre64, + buf[pos+190] << decimation_shifts::pre64, + buf[pos+191] << decimation_shifts::pre64, + &buf2[92]); + + m_decimator2.myDecimateSup( + buf[pos+192] << decimation_shifts::pre64, + buf[pos+193] << decimation_shifts::pre64, + buf[pos+194] << decimation_shifts::pre64, + buf[pos+195] << decimation_shifts::pre64, + buf[pos+196] << decimation_shifts::pre64, + buf[pos+197] << decimation_shifts::pre64, + buf[pos+198] << decimation_shifts::pre64, + buf[pos+199] << decimation_shifts::pre64, + &buf2[96]); + + m_decimator2.myDecimateSup( + buf[pos+200] << decimation_shifts::pre64, + buf[pos+201] << decimation_shifts::pre64, + buf[pos+202] << decimation_shifts::pre64, + buf[pos+203] << decimation_shifts::pre64, + buf[pos+204] << decimation_shifts::pre64, + buf[pos+205] << decimation_shifts::pre64, + buf[pos+206] << decimation_shifts::pre64, + buf[pos+207] << decimation_shifts::pre64, + &buf2[100]); + + m_decimator2.myDecimateSup( + buf[pos+208] << decimation_shifts::pre64, + buf[pos+209] << decimation_shifts::pre64, + buf[pos+210] << decimation_shifts::pre64, + buf[pos+211] << decimation_shifts::pre64, + buf[pos+212] << decimation_shifts::pre64, + buf[pos+213] << decimation_shifts::pre64, + buf[pos+214] << decimation_shifts::pre64, + buf[pos+215] << decimation_shifts::pre64, + &buf2[104]); + + m_decimator2.myDecimateSup( + buf[pos+216] << decimation_shifts::pre64, + buf[pos+217] << decimation_shifts::pre64, + buf[pos+218] << decimation_shifts::pre64, + buf[pos+219] << decimation_shifts::pre64, + buf[pos+220] << decimation_shifts::pre64, + buf[pos+221] << decimation_shifts::pre64, + buf[pos+222] << decimation_shifts::pre64, + buf[pos+223] << decimation_shifts::pre64, + &buf2[108]); + + m_decimator2.myDecimateSup( + buf[pos+224] << decimation_shifts::pre64, + buf[pos+225] << decimation_shifts::pre64, + buf[pos+226] << decimation_shifts::pre64, + buf[pos+227] << decimation_shifts::pre64, + buf[pos+228] << decimation_shifts::pre64, + buf[pos+229] << decimation_shifts::pre64, + buf[pos+230] << decimation_shifts::pre64, + buf[pos+231] << decimation_shifts::pre64, + &buf2[112]); + + m_decimator2.myDecimateSup( + buf[pos+232] << decimation_shifts::pre64, + buf[pos+233] << decimation_shifts::pre64, + buf[pos+234] << decimation_shifts::pre64, + buf[pos+235] << decimation_shifts::pre64, + buf[pos+236] << decimation_shifts::pre64, + buf[pos+237] << decimation_shifts::pre64, + buf[pos+238] << decimation_shifts::pre64, + buf[pos+239] << decimation_shifts::pre64, + &buf2[116]); + + m_decimator2.myDecimateSup( + buf[pos+240] << decimation_shifts::pre64, + buf[pos+241] << decimation_shifts::pre64, + buf[pos+242] << decimation_shifts::pre64, + buf[pos+243] << decimation_shifts::pre64, + buf[pos+244] << decimation_shifts::pre64, + buf[pos+245] << decimation_shifts::pre64, + buf[pos+246] << decimation_shifts::pre64, + buf[pos+247] << decimation_shifts::pre64, + &buf2[120]); + + m_decimator2.myDecimateSup( + buf[pos+248] << decimation_shifts::pre64, + buf[pos+249] << decimation_shifts::pre64, + buf[pos+250] << decimation_shifts::pre64, + buf[pos+251] << decimation_shifts::pre64, + buf[pos+252] << decimation_shifts::pre64, + buf[pos+253] << decimation_shifts::pre64, + buf[pos+254] << decimation_shifts::pre64, + buf[pos+255] << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateInf( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateInf( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateInf( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateInf( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateInf( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateInf( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateInf( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateInf( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateInf( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateInf( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateInf( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateInf( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateInf( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateInf( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateInf( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateInf( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[0] >> decimation_shifts::post64); + (**it).setImag(buf64[1] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[2] >> decimation_shifts::post64); + (**it).setImag(buf64[3] >> decimation_shifts::post64); + ++(*it); + } } template void DecimatorsU::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType intbuf[2]; + StorageType buf2[4]; - for (int pos = 0; pos < len - 3; pos += 4) - { - intbuf[0] = (buf[pos+2] - Shift) << decimation_shifts::pre2; - intbuf[1] = (buf[pos+3] - Shift) << decimation_shifts::pre2; + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); - m_decimator2.myDecimate( - (buf[pos+0] - Shift) << decimation_shifts::pre2, - (buf[pos+1] - Shift) << decimation_shifts::pre2, - &intbuf[0], - &intbuf[1]); + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); - (**it).setReal(intbuf[0] >> decimation_shifts::post2); - (**it).setImag(intbuf[1] >> decimation_shifts::post2); - ++(*it); - } + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } } template void DecimatorsU::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - StorageType intbuf[4]; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - intbuf[0] = (buf[pos+2] - Shift) << decimation_shifts::pre4; - intbuf[1] = (buf[pos+3] - Shift) << decimation_shifts::pre4; - intbuf[2] = (buf[pos+6] - Shift) << decimation_shifts::pre4; - intbuf[3] = (buf[pos+7] - Shift) << decimation_shifts::pre4; + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); - m_decimator2.myDecimate( - (buf[pos+0] - Shift) << decimation_shifts::pre4, - (buf[pos+1] - Shift) << decimation_shifts::pre4, - &intbuf[0], - &intbuf[1]); - m_decimator2.myDecimate( - (buf[pos+4] - Shift) << decimation_shifts::pre4, - (buf[pos+5] - Shift) << decimation_shifts::pre4, - &intbuf[2], - &intbuf[3]); + m_decimator2.myDecimateCen( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); - m_decimator4.myDecimate( - intbuf[0], - intbuf[1], - &intbuf[2], - &intbuf[3]); + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); - (**it).setReal(intbuf[2] >> decimation_shifts::post4); - (**it).setImag(intbuf[3] >> decimation_shifts::post4); - ++(*it); - } + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); + } } template From 65df319167fc5e8506fa142c6b17c85bab32801c Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 12 May 2018 08:05:46 +0200 Subject: [PATCH 400/956] RTL-SDR: fixed inf/sup decimators (2) --- sdrbase/dsp/decimatorsu.h | 2210 ++++++++++++++++++++----------------- 1 file changed, 1222 insertions(+), 988 deletions(-) diff --git a/sdrbase/dsp/decimatorsu.h b/sdrbase/dsp/decimatorsu.h index 91a3f1994..36f897634 100644 --- a/sdrbase/dsp/decimatorsu.h +++ b/sdrbase/dsp/decimatorsu.h @@ -238,14 +238,14 @@ void DecimatorsU::decimate2_inf(Sampl for (int pos = 0; pos < len - 7; pos += 8) { m_decimator2.myDecimateInf( - buf[pos+0] << decimation_shifts::pre2, - buf[pos+1] << decimation_shifts::pre2, - buf[pos+2] << decimation_shifts::pre2, - buf[pos+3] << decimation_shifts::pre2, - buf[pos+4] << decimation_shifts::pre2, - buf[pos+5] << decimation_shifts::pre2, - buf[pos+6] << decimation_shifts::pre2, - buf[pos+7] << decimation_shifts::pre2, + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, &buf2[0]); (**it).setReal(buf2[0] >> decimation_shifts::post2); @@ -266,14 +266,14 @@ void DecimatorsU::decimate2_sup(Sampl for (int pos = 0; pos < len - 7; pos += 8) { m_decimator2.myDecimateSup( - buf[pos+0] << decimation_shifts::pre2, - buf[pos+1] << decimation_shifts::pre2, - buf[pos+2] << decimation_shifts::pre2, - buf[pos+3] << decimation_shifts::pre2, - buf[pos+4] << decimation_shifts::pre2, - buf[pos+5] << decimation_shifts::pre2, - buf[pos+6] << decimation_shifts::pre2, - buf[pos+7] << decimation_shifts::pre2, + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, &buf2[0]); (**it).setReal(buf2[0] >> decimation_shifts::post2); @@ -294,25 +294,25 @@ void DecimatorsU::decimate4_inf(Sampl for (int pos = 0; pos < len - 15; pos += 16) { m_decimator2.myDecimateInf( - buf[pos+0] << decimation_shifts::pre4, - buf[pos+1] << decimation_shifts::pre4, - buf[pos+2] << decimation_shifts::pre4, - buf[pos+3] << decimation_shifts::pre4, - buf[pos+4] << decimation_shifts::pre4, - buf[pos+5] << decimation_shifts::pre4, - buf[pos+6] << decimation_shifts::pre4, - buf[pos+7] << decimation_shifts::pre4, + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, &buf2[0]); m_decimator2.myDecimateInf( - buf[pos+8] << decimation_shifts::pre4, - buf[pos+9] << decimation_shifts::pre4, - buf[pos+10] << decimation_shifts::pre4, - buf[pos+11] << decimation_shifts::pre4, - buf[pos+12] << decimation_shifts::pre4, - buf[pos+13] << decimation_shifts::pre4, - buf[pos+14] << decimation_shifts::pre4, - buf[pos+15] << decimation_shifts::pre4, + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, &buf2[4]); m_decimator4.myDecimateSup( @@ -344,25 +344,25 @@ void DecimatorsU::decimate4_sup(Sampl for (int pos = 0; pos < len - 15; pos += 16) { m_decimator2.myDecimateSup( - buf[pos+0] << decimation_shifts::pre4, - buf[pos+1] << decimation_shifts::pre4, - buf[pos+2] << decimation_shifts::pre4, - buf[pos+3] << decimation_shifts::pre4, - buf[pos+4] << decimation_shifts::pre4, - buf[pos+5] << decimation_shifts::pre4, - buf[pos+6] << decimation_shifts::pre4, - buf[pos+7] << decimation_shifts::pre4, + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, &buf2[0]); m_decimator2.myDecimateSup( - buf[pos+8] << decimation_shifts::pre4, - buf[pos+9] << decimation_shifts::pre4, - buf[pos+10] << decimation_shifts::pre4, - buf[pos+11] << decimation_shifts::pre4, - buf[pos+12] << decimation_shifts::pre4, - buf[pos+13] << decimation_shifts::pre4, - buf[pos+14] << decimation_shifts::pre4, - buf[pos+15] << decimation_shifts::pre4, + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, &buf2[4]); m_decimator4.myDecimateInf( @@ -389,11 +389,75 @@ void DecimatorsU::decimate4_sup(Sampl template void DecimatorsU::decimate8_inf(SampleVector::iterator* it, const T* buf __attribute__((unused)), qint32 len) { - for (int pos = 0; pos < len - 15; pos += 8) - { - (**it).setReal(0); - (**it).setImag(0); + StorageType buf2[16], buf4[8], buf8[4]; + for (int pos = 0; pos < len - 31; pos += 32) + { + m_decimator2.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre8, + (buf[pos+1] - Shift) << decimation_shifts::pre8, + (buf[pos+2] - Shift) << decimation_shifts::pre8, + (buf[pos+3] - Shift) << decimation_shifts::pre8, + (buf[pos+4] - Shift) << decimation_shifts::pre8, + (buf[pos+5] - Shift) << decimation_shifts::pre8, + (buf[pos+6] - Shift) << decimation_shifts::pre8, + (buf[pos+7] - Shift) << decimation_shifts::pre8, + &buf2[0]); + + + m_decimator2.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre8, + (buf[pos+9] - Shift) << decimation_shifts::pre8, + (buf[pos+10] - Shift) << decimation_shifts::pre8, + (buf[pos+11] - Shift) << decimation_shifts::pre8, + (buf[pos+12] - Shift) << decimation_shifts::pre8, + (buf[pos+13] - Shift) << decimation_shifts::pre8, + (buf[pos+14] - Shift) << decimation_shifts::pre8, + (buf[pos+15] - Shift) << decimation_shifts::pre8, + &buf2[4]); + + + m_decimator2.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre8, + (buf[pos+17] - Shift) << decimation_shifts::pre8, + (buf[pos+18] - Shift) << decimation_shifts::pre8, + (buf[pos+19] - Shift) << decimation_shifts::pre8, + (buf[pos+20] - Shift) << decimation_shifts::pre8, + (buf[pos+21] - Shift) << decimation_shifts::pre8, + (buf[pos+22] - Shift) << decimation_shifts::pre8, + (buf[pos+23] - Shift) << decimation_shifts::pre8, + &buf2[8]); + + + m_decimator2.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre8, + (buf[pos+25] - Shift) << decimation_shifts::pre8, + (buf[pos+26] - Shift) << decimation_shifts::pre8, + (buf[pos+27] - Shift) << decimation_shifts::pre8, + (buf[pos+28] - Shift) << decimation_shifts::pre8, + (buf[pos+29] - Shift) << decimation_shifts::pre8, + (buf[pos+30] - Shift) << decimation_shifts::pre8, + (buf[pos+31] - Shift) << decimation_shifts::pre8, + &buf2[12]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[0] >> decimation_shifts::post8); + (**it).setImag(buf8[1] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[2] >> decimation_shifts::post8); + (**it).setImag(buf8[3] >> decimation_shifts::post8); ++(*it); } } @@ -401,11 +465,75 @@ void DecimatorsU::decimate8_inf(Sampl template void DecimatorsU::decimate8_sup(SampleVector::iterator* it, const T* buf __attribute__((unused)), qint32 len) { - for (int pos = 0; pos < len - 15; pos += 8) - { - (**it).setReal(0); - (**it).setImag(0); + StorageType buf2[16], buf4[8], buf8[4]; + for (int pos = 0; pos < len - 31; pos += 32) + { + m_decimator2.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre8, + (buf[pos+1] - Shift) << decimation_shifts::pre8, + (buf[pos+2] - Shift) << decimation_shifts::pre8, + (buf[pos+3] - Shift) << decimation_shifts::pre8, + (buf[pos+4] - Shift) << decimation_shifts::pre8, + (buf[pos+5] - Shift) << decimation_shifts::pre8, + (buf[pos+6] - Shift) << decimation_shifts::pre8, + (buf[pos+7] - Shift) << decimation_shifts::pre8, + &buf2[0]); + + + m_decimator2.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre8, + (buf[pos+9] - Shift) << decimation_shifts::pre8, + (buf[pos+10] - Shift) << decimation_shifts::pre8, + (buf[pos+11] - Shift) << decimation_shifts::pre8, + (buf[pos+12] - Shift) << decimation_shifts::pre8, + (buf[pos+13] - Shift) << decimation_shifts::pre8, + (buf[pos+14] - Shift) << decimation_shifts::pre8, + (buf[pos+15] - Shift) << decimation_shifts::pre8, + &buf2[4]); + + + m_decimator2.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre8, + (buf[pos+17] - Shift) << decimation_shifts::pre8, + (buf[pos+18] - Shift) << decimation_shifts::pre8, + (buf[pos+19] - Shift) << decimation_shifts::pre8, + (buf[pos+20] - Shift) << decimation_shifts::pre8, + (buf[pos+21] - Shift) << decimation_shifts::pre8, + (buf[pos+22] - Shift) << decimation_shifts::pre8, + (buf[pos+23] - Shift) << decimation_shifts::pre8, + &buf2[8]); + + + m_decimator2.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre8, + (buf[pos+25] - Shift) << decimation_shifts::pre8, + (buf[pos+26] - Shift) << decimation_shifts::pre8, + (buf[pos+27] - Shift) << decimation_shifts::pre8, + (buf[pos+28] - Shift) << decimation_shifts::pre8, + (buf[pos+29] - Shift) << decimation_shifts::pre8, + (buf[pos+30] - Shift) << decimation_shifts::pre8, + (buf[pos+31] - Shift) << decimation_shifts::pre8, + &buf2[12]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[0] >> decimation_shifts::post8); + (**it).setImag(buf8[1] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[2] >> decimation_shifts::post8); + (**it).setImag(buf8[3] >> decimation_shifts::post8); ++(*it); } } @@ -418,91 +546,98 @@ void DecimatorsU::decimate16_inf(Samp for (int pos = 0; pos < len - 63; pos += 64) { m_decimator2.myDecimateInf( - buf[pos+0] << decimation_shifts::pre16, - buf[pos+1] << decimation_shifts::pre16, - buf[pos+2] << decimation_shifts::pre16, - buf[pos+3] << decimation_shifts::pre16, - buf[pos+4] << decimation_shifts::pre16, - buf[pos+5] << decimation_shifts::pre16, - buf[pos+6] << decimation_shifts::pre16, - buf[pos+7] << decimation_shifts::pre16, + (buf[pos+0] - Shift) << decimation_shifts::pre16, + (buf[pos+1] - Shift) << decimation_shifts::pre16, + (buf[pos+2] - Shift) << decimation_shifts::pre16, + (buf[pos+3] - Shift) << decimation_shifts::pre16, + (buf[pos+4] - Shift) << decimation_shifts::pre16, + (buf[pos+5] - Shift) << decimation_shifts::pre16, + (buf[pos+6] - Shift) << decimation_shifts::pre16, + (buf[pos+7] - Shift) << decimation_shifts::pre16, &buf2[0]); + m_decimator2.myDecimateInf( - buf[pos+8] << decimation_shifts::pre16, - buf[pos+9] << decimation_shifts::pre16, - buf[pos+10] << decimation_shifts::pre16, - buf[pos+11] << decimation_shifts::pre16, - buf[pos+12] << decimation_shifts::pre16, - buf[pos+13] << decimation_shifts::pre16, - buf[pos+14] << decimation_shifts::pre16, - buf[pos+15] << decimation_shifts::pre16, + (buf[pos+8] - Shift) << decimation_shifts::pre16, + (buf[pos+9] - Shift) << decimation_shifts::pre16, + (buf[pos+10] - Shift) << decimation_shifts::pre16, + (buf[pos+11] - Shift) << decimation_shifts::pre16, + (buf[pos+12] - Shift) << decimation_shifts::pre16, + (buf[pos+13] - Shift) << decimation_shifts::pre16, + (buf[pos+14] - Shift) << decimation_shifts::pre16, + (buf[pos+15] - Shift) << decimation_shifts::pre16, &buf2[4]); + m_decimator2.myDecimateInf( - buf[pos+16] << decimation_shifts::pre16, - buf[pos+17] << decimation_shifts::pre16, - buf[pos+18] << decimation_shifts::pre16, - buf[pos+19] << decimation_shifts::pre16, - buf[pos+20] << decimation_shifts::pre16, - buf[pos+21] << decimation_shifts::pre16, - buf[pos+22] << decimation_shifts::pre16, - buf[pos+23] << decimation_shifts::pre16, + (buf[pos+16] - Shift) << decimation_shifts::pre16, + (buf[pos+17] - Shift) << decimation_shifts::pre16, + (buf[pos+18] - Shift) << decimation_shifts::pre16, + (buf[pos+19] - Shift) << decimation_shifts::pre16, + (buf[pos+20] - Shift) << decimation_shifts::pre16, + (buf[pos+21] - Shift) << decimation_shifts::pre16, + (buf[pos+22] - Shift) << decimation_shifts::pre16, + (buf[pos+23] - Shift) << decimation_shifts::pre16, &buf2[8]); + m_decimator2.myDecimateInf( - buf[pos+24] << decimation_shifts::pre16, - buf[pos+25] << decimation_shifts::pre16, - buf[pos+26] << decimation_shifts::pre16, - buf[pos+27] << decimation_shifts::pre16, - buf[pos+28] << decimation_shifts::pre16, - buf[pos+29] << decimation_shifts::pre16, - buf[pos+30] << decimation_shifts::pre16, - buf[pos+31] << decimation_shifts::pre16, + (buf[pos+24] - Shift) << decimation_shifts::pre16, + (buf[pos+25] - Shift) << decimation_shifts::pre16, + (buf[pos+26] - Shift) << decimation_shifts::pre16, + (buf[pos+27] - Shift) << decimation_shifts::pre16, + (buf[pos+28] - Shift) << decimation_shifts::pre16, + (buf[pos+29] - Shift) << decimation_shifts::pre16, + (buf[pos+30] - Shift) << decimation_shifts::pre16, + (buf[pos+31] - Shift) << decimation_shifts::pre16, &buf2[12]); + m_decimator2.myDecimateInf( - buf[pos+32] << decimation_shifts::pre16, - buf[pos+33] << decimation_shifts::pre16, - buf[pos+34] << decimation_shifts::pre16, - buf[pos+35] << decimation_shifts::pre16, - buf[pos+36] << decimation_shifts::pre16, - buf[pos+37] << decimation_shifts::pre16, - buf[pos+38] << decimation_shifts::pre16, - buf[pos+39] << decimation_shifts::pre16, + (buf[pos+32] - Shift) << decimation_shifts::pre16, + (buf[pos+33] - Shift) << decimation_shifts::pre16, + (buf[pos+34] - Shift) << decimation_shifts::pre16, + (buf[pos+35] - Shift) << decimation_shifts::pre16, + (buf[pos+36] - Shift) << decimation_shifts::pre16, + (buf[pos+37] - Shift) << decimation_shifts::pre16, + (buf[pos+38] - Shift) << decimation_shifts::pre16, + (buf[pos+39] - Shift) << decimation_shifts::pre16, &buf2[16]); + m_decimator2.myDecimateInf( - buf[pos+40] << decimation_shifts::pre16, - buf[pos+41] << decimation_shifts::pre16, - buf[pos+42] << decimation_shifts::pre16, - buf[pos+43] << decimation_shifts::pre16, - buf[pos+44] << decimation_shifts::pre16, - buf[pos+45] << decimation_shifts::pre16, - buf[pos+46] << decimation_shifts::pre16, - buf[pos+47] << decimation_shifts::pre16, + (buf[pos+40] - Shift) << decimation_shifts::pre16, + (buf[pos+41] - Shift) << decimation_shifts::pre16, + (buf[pos+42] - Shift) << decimation_shifts::pre16, + (buf[pos+43] - Shift) << decimation_shifts::pre16, + (buf[pos+44] - Shift) << decimation_shifts::pre16, + (buf[pos+45] - Shift) << decimation_shifts::pre16, + (buf[pos+46] - Shift) << decimation_shifts::pre16, + (buf[pos+47] - Shift) << decimation_shifts::pre16, &buf2[20]); - m_decimator2.myDecimateInf( - buf[pos+48] << decimation_shifts::pre16, - buf[pos+49] << decimation_shifts::pre16, - buf[pos+50] << decimation_shifts::pre16, - buf[pos+51] << decimation_shifts::pre16, - buf[pos+52] << decimation_shifts::pre16, - buf[pos+53] << decimation_shifts::pre16, - buf[pos+54] << decimation_shifts::pre16, - buf[pos+55] << decimation_shifts::pre16, - &buf2[24]); m_decimator2.myDecimateInf( - buf[pos+56] << decimation_shifts::pre16, - buf[pos+57] << decimation_shifts::pre16, - buf[pos+58] << decimation_shifts::pre16, - buf[pos+59] << decimation_shifts::pre16, - buf[pos+60] << decimation_shifts::pre16, - buf[pos+61] << decimation_shifts::pre16, - buf[pos+62] << decimation_shifts::pre16, - buf[pos+63] << decimation_shifts::pre16, + (buf[pos+48] - Shift) << decimation_shifts::pre16, + (buf[pos+49] - Shift) << decimation_shifts::pre16, + (buf[pos+50] - Shift) << decimation_shifts::pre16, + (buf[pos+51] - Shift) << decimation_shifts::pre16, + (buf[pos+52] - Shift) << decimation_shifts::pre16, + (buf[pos+53] - Shift) << decimation_shifts::pre16, + (buf[pos+54] - Shift) << decimation_shifts::pre16, + (buf[pos+55] - Shift) << decimation_shifts::pre16, + &buf2[24]); + + + m_decimator2.myDecimateInf( + (buf[pos+56] - Shift) << decimation_shifts::pre16, + (buf[pos+57] - Shift) << decimation_shifts::pre16, + (buf[pos+58] - Shift) << decimation_shifts::pre16, + (buf[pos+59] - Shift) << decimation_shifts::pre16, + (buf[pos+60] - Shift) << decimation_shifts::pre16, + (buf[pos+61] - Shift) << decimation_shifts::pre16, + (buf[pos+62] - Shift) << decimation_shifts::pre16, + (buf[pos+63] - Shift) << decimation_shifts::pre16, &buf2[28]); m_decimator4.myDecimateSup( @@ -551,91 +686,98 @@ void DecimatorsU::decimate16_sup(Samp for (int pos = 0; pos < len - 63; pos += 64) { m_decimator2.myDecimateSup( - buf[pos+0] << decimation_shifts::pre16, - buf[pos+1] << decimation_shifts::pre16, - buf[pos+2] << decimation_shifts::pre16, - buf[pos+3] << decimation_shifts::pre16, - buf[pos+4] << decimation_shifts::pre16, - buf[pos+5] << decimation_shifts::pre16, - buf[pos+6] << decimation_shifts::pre16, - buf[pos+7] << decimation_shifts::pre16, + (buf[pos+0] - Shift) << decimation_shifts::pre16, + (buf[pos+1] - Shift) << decimation_shifts::pre16, + (buf[pos+2] - Shift) << decimation_shifts::pre16, + (buf[pos+3] - Shift) << decimation_shifts::pre16, + (buf[pos+4] - Shift) << decimation_shifts::pre16, + (buf[pos+5] - Shift) << decimation_shifts::pre16, + (buf[pos+6] - Shift) << decimation_shifts::pre16, + (buf[pos+7] - Shift) << decimation_shifts::pre16, &buf2[0]); + m_decimator2.myDecimateSup( - buf[pos+8] << decimation_shifts::pre16, - buf[pos+9] << decimation_shifts::pre16, - buf[pos+10] << decimation_shifts::pre16, - buf[pos+11] << decimation_shifts::pre16, - buf[pos+12] << decimation_shifts::pre16, - buf[pos+13] << decimation_shifts::pre16, - buf[pos+14] << decimation_shifts::pre16, - buf[pos+15] << decimation_shifts::pre16, + (buf[pos+8] - Shift) << decimation_shifts::pre16, + (buf[pos+9] - Shift) << decimation_shifts::pre16, + (buf[pos+10] - Shift) << decimation_shifts::pre16, + (buf[pos+11] - Shift) << decimation_shifts::pre16, + (buf[pos+12] - Shift) << decimation_shifts::pre16, + (buf[pos+13] - Shift) << decimation_shifts::pre16, + (buf[pos+14] - Shift) << decimation_shifts::pre16, + (buf[pos+15] - Shift) << decimation_shifts::pre16, &buf2[4]); + m_decimator2.myDecimateSup( - buf[pos+16] << decimation_shifts::pre16, - buf[pos+17] << decimation_shifts::pre16, - buf[pos+18] << decimation_shifts::pre16, - buf[pos+19] << decimation_shifts::pre16, - buf[pos+20] << decimation_shifts::pre16, - buf[pos+21] << decimation_shifts::pre16, - buf[pos+22] << decimation_shifts::pre16, - buf[pos+23] << decimation_shifts::pre16, + (buf[pos+16] - Shift) << decimation_shifts::pre16, + (buf[pos+17] - Shift) << decimation_shifts::pre16, + (buf[pos+18] - Shift) << decimation_shifts::pre16, + (buf[pos+19] - Shift) << decimation_shifts::pre16, + (buf[pos+20] - Shift) << decimation_shifts::pre16, + (buf[pos+21] - Shift) << decimation_shifts::pre16, + (buf[pos+22] - Shift) << decimation_shifts::pre16, + (buf[pos+23] - Shift) << decimation_shifts::pre16, &buf2[8]); + m_decimator2.myDecimateSup( - buf[pos+24] << decimation_shifts::pre16, - buf[pos+25] << decimation_shifts::pre16, - buf[pos+26] << decimation_shifts::pre16, - buf[pos+27] << decimation_shifts::pre16, - buf[pos+28] << decimation_shifts::pre16, - buf[pos+29] << decimation_shifts::pre16, - buf[pos+30] << decimation_shifts::pre16, - buf[pos+31] << decimation_shifts::pre16, + (buf[pos+24] - Shift) << decimation_shifts::pre16, + (buf[pos+25] - Shift) << decimation_shifts::pre16, + (buf[pos+26] - Shift) << decimation_shifts::pre16, + (buf[pos+27] - Shift) << decimation_shifts::pre16, + (buf[pos+28] - Shift) << decimation_shifts::pre16, + (buf[pos+29] - Shift) << decimation_shifts::pre16, + (buf[pos+30] - Shift) << decimation_shifts::pre16, + (buf[pos+31] - Shift) << decimation_shifts::pre16, &buf2[12]); + m_decimator2.myDecimateSup( - buf[pos+32] << decimation_shifts::pre16, - buf[pos+33] << decimation_shifts::pre16, - buf[pos+34] << decimation_shifts::pre16, - buf[pos+35] << decimation_shifts::pre16, - buf[pos+36] << decimation_shifts::pre16, - buf[pos+37] << decimation_shifts::pre16, - buf[pos+38] << decimation_shifts::pre16, - buf[pos+39] << decimation_shifts::pre16, + (buf[pos+32] - Shift) << decimation_shifts::pre16, + (buf[pos+33] - Shift) << decimation_shifts::pre16, + (buf[pos+34] - Shift) << decimation_shifts::pre16, + (buf[pos+35] - Shift) << decimation_shifts::pre16, + (buf[pos+36] - Shift) << decimation_shifts::pre16, + (buf[pos+37] - Shift) << decimation_shifts::pre16, + (buf[pos+38] - Shift) << decimation_shifts::pre16, + (buf[pos+39] - Shift) << decimation_shifts::pre16, &buf2[16]); + m_decimator2.myDecimateSup( - buf[pos+40] << decimation_shifts::pre16, - buf[pos+41] << decimation_shifts::pre16, - buf[pos+42] << decimation_shifts::pre16, - buf[pos+43] << decimation_shifts::pre16, - buf[pos+44] << decimation_shifts::pre16, - buf[pos+45] << decimation_shifts::pre16, - buf[pos+46] << decimation_shifts::pre16, - buf[pos+47] << decimation_shifts::pre16, + (buf[pos+40] - Shift) << decimation_shifts::pre16, + (buf[pos+41] - Shift) << decimation_shifts::pre16, + (buf[pos+42] - Shift) << decimation_shifts::pre16, + (buf[pos+43] - Shift) << decimation_shifts::pre16, + (buf[pos+44] - Shift) << decimation_shifts::pre16, + (buf[pos+45] - Shift) << decimation_shifts::pre16, + (buf[pos+46] - Shift) << decimation_shifts::pre16, + (buf[pos+47] - Shift) << decimation_shifts::pre16, &buf2[20]); - m_decimator2.myDecimateSup( - buf[pos+48] << decimation_shifts::pre16, - buf[pos+49] << decimation_shifts::pre16, - buf[pos+50] << decimation_shifts::pre16, - buf[pos+51] << decimation_shifts::pre16, - buf[pos+52] << decimation_shifts::pre16, - buf[pos+53] << decimation_shifts::pre16, - buf[pos+54] << decimation_shifts::pre16, - buf[pos+55] << decimation_shifts::pre16, - &buf2[24]); m_decimator2.myDecimateSup( - buf[pos+56] << decimation_shifts::pre16, - buf[pos+57] << decimation_shifts::pre16, - buf[pos+58] << decimation_shifts::pre16, - buf[pos+59] << decimation_shifts::pre16, - buf[pos+60] << decimation_shifts::pre16, - buf[pos+61] << decimation_shifts::pre16, - buf[pos+62] << decimation_shifts::pre16, - buf[pos+63] << decimation_shifts::pre16, + (buf[pos+48] - Shift) << decimation_shifts::pre16, + (buf[pos+49] - Shift) << decimation_shifts::pre16, + (buf[pos+50] - Shift) << decimation_shifts::pre16, + (buf[pos+51] - Shift) << decimation_shifts::pre16, + (buf[pos+52] - Shift) << decimation_shifts::pre16, + (buf[pos+53] - Shift) << decimation_shifts::pre16, + (buf[pos+54] - Shift) << decimation_shifts::pre16, + (buf[pos+55] - Shift) << decimation_shifts::pre16, + &buf2[24]); + + + m_decimator2.myDecimateSup( + (buf[pos+56] - Shift) << decimation_shifts::pre16, + (buf[pos+57] - Shift) << decimation_shifts::pre16, + (buf[pos+58] - Shift) << decimation_shifts::pre16, + (buf[pos+59] - Shift) << decimation_shifts::pre16, + (buf[pos+60] - Shift) << decimation_shifts::pre16, + (buf[pos+61] - Shift) << decimation_shifts::pre16, + (buf[pos+62] - Shift) << decimation_shifts::pre16, + (buf[pos+63] - Shift) << decimation_shifts::pre16, &buf2[28]); m_decimator4.myDecimateInf( @@ -684,179 +826,194 @@ void DecimatorsU::decimate32_inf(Samp for (int pos = 0; pos < len - 127; pos += 128) { m_decimator2.myDecimateInf( - buf[pos+0] << decimation_shifts::pre32, - buf[pos+1] << decimation_shifts::pre32, - buf[pos+2] << decimation_shifts::pre32, - buf[pos+3] << decimation_shifts::pre32, - buf[pos+4] << decimation_shifts::pre32, - buf[pos+5] << decimation_shifts::pre32, - buf[pos+6] << decimation_shifts::pre32, - buf[pos+7] << decimation_shifts::pre32, + (buf[pos+0] - Shift) << decimation_shifts::pre32, + (buf[pos+1] - Shift) << decimation_shifts::pre32, + (buf[pos+2] - Shift) << decimation_shifts::pre32, + (buf[pos+3] - Shift) << decimation_shifts::pre32, + (buf[pos+4] - Shift) << decimation_shifts::pre32, + (buf[pos+5] - Shift) << decimation_shifts::pre32, + (buf[pos+6] - Shift) << decimation_shifts::pre32, + (buf[pos+7] - Shift) << decimation_shifts::pre32, &buf2[0]); + m_decimator2.myDecimateInf( - buf[pos+8] << decimation_shifts::pre32, - buf[pos+9] << decimation_shifts::pre32, - buf[pos+10] << decimation_shifts::pre32, - buf[pos+11] << decimation_shifts::pre32, - buf[pos+12] << decimation_shifts::pre32, - buf[pos+13] << decimation_shifts::pre32, - buf[pos+14] << decimation_shifts::pre32, - buf[pos+15] << decimation_shifts::pre32, + (buf[pos+8] - Shift) << decimation_shifts::pre32, + (buf[pos+9] - Shift) << decimation_shifts::pre32, + (buf[pos+10] - Shift) << decimation_shifts::pre32, + (buf[pos+11] - Shift) << decimation_shifts::pre32, + (buf[pos+12] - Shift) << decimation_shifts::pre32, + (buf[pos+13] - Shift) << decimation_shifts::pre32, + (buf[pos+14] - Shift) << decimation_shifts::pre32, + (buf[pos+15] - Shift) << decimation_shifts::pre32, &buf2[4]); + m_decimator2.myDecimateInf( - buf[pos+16] << decimation_shifts::pre32, - buf[pos+17] << decimation_shifts::pre32, - buf[pos+18] << decimation_shifts::pre32, - buf[pos+19] << decimation_shifts::pre32, - buf[pos+20] << decimation_shifts::pre32, - buf[pos+21] << decimation_shifts::pre32, - buf[pos+22] << decimation_shifts::pre32, - buf[pos+23] << decimation_shifts::pre32, + (buf[pos+16] - Shift) << decimation_shifts::pre32, + (buf[pos+17] - Shift) << decimation_shifts::pre32, + (buf[pos+18] - Shift) << decimation_shifts::pre32, + (buf[pos+19] - Shift) << decimation_shifts::pre32, + (buf[pos+20] - Shift) << decimation_shifts::pre32, + (buf[pos+21] - Shift) << decimation_shifts::pre32, + (buf[pos+22] - Shift) << decimation_shifts::pre32, + (buf[pos+23] - Shift) << decimation_shifts::pre32, &buf2[8]); + m_decimator2.myDecimateInf( - buf[pos+24] << decimation_shifts::pre32, - buf[pos+25] << decimation_shifts::pre32, - buf[pos+26] << decimation_shifts::pre32, - buf[pos+27] << decimation_shifts::pre32, - buf[pos+28] << decimation_shifts::pre32, - buf[pos+29] << decimation_shifts::pre32, - buf[pos+30] << decimation_shifts::pre32, - buf[pos+31] << decimation_shifts::pre32, + (buf[pos+24] - Shift) << decimation_shifts::pre32, + (buf[pos+25] - Shift) << decimation_shifts::pre32, + (buf[pos+26] - Shift) << decimation_shifts::pre32, + (buf[pos+27] - Shift) << decimation_shifts::pre32, + (buf[pos+28] - Shift) << decimation_shifts::pre32, + (buf[pos+29] - Shift) << decimation_shifts::pre32, + (buf[pos+30] - Shift) << decimation_shifts::pre32, + (buf[pos+31] - Shift) << decimation_shifts::pre32, &buf2[12]); + m_decimator2.myDecimateInf( - buf[pos+32] << decimation_shifts::pre32, - buf[pos+33] << decimation_shifts::pre32, - buf[pos+34] << decimation_shifts::pre32, - buf[pos+35] << decimation_shifts::pre32, - buf[pos+36] << decimation_shifts::pre32, - buf[pos+37] << decimation_shifts::pre32, - buf[pos+38] << decimation_shifts::pre32, - buf[pos+39] << decimation_shifts::pre32, + (buf[pos+32] - Shift) << decimation_shifts::pre32, + (buf[pos+33] - Shift) << decimation_shifts::pre32, + (buf[pos+34] - Shift) << decimation_shifts::pre32, + (buf[pos+35] - Shift) << decimation_shifts::pre32, + (buf[pos+36] - Shift) << decimation_shifts::pre32, + (buf[pos+37] - Shift) << decimation_shifts::pre32, + (buf[pos+38] - Shift) << decimation_shifts::pre32, + (buf[pos+39] - Shift) << decimation_shifts::pre32, &buf2[16]); + m_decimator2.myDecimateInf( - buf[pos+40] << decimation_shifts::pre32, - buf[pos+41] << decimation_shifts::pre32, - buf[pos+42] << decimation_shifts::pre32, - buf[pos+43] << decimation_shifts::pre32, - buf[pos+44] << decimation_shifts::pre32, - buf[pos+45] << decimation_shifts::pre32, - buf[pos+46] << decimation_shifts::pre32, - buf[pos+47] << decimation_shifts::pre32, + (buf[pos+40] - Shift) << decimation_shifts::pre32, + (buf[pos+41] - Shift) << decimation_shifts::pre32, + (buf[pos+42] - Shift) << decimation_shifts::pre32, + (buf[pos+43] - Shift) << decimation_shifts::pre32, + (buf[pos+44] - Shift) << decimation_shifts::pre32, + (buf[pos+45] - Shift) << decimation_shifts::pre32, + (buf[pos+46] - Shift) << decimation_shifts::pre32, + (buf[pos+47] - Shift) << decimation_shifts::pre32, &buf2[20]); + m_decimator2.myDecimateInf( - buf[pos+48] << decimation_shifts::pre32, - buf[pos+49] << decimation_shifts::pre32, - buf[pos+50] << decimation_shifts::pre32, - buf[pos+51] << decimation_shifts::pre32, - buf[pos+52] << decimation_shifts::pre32, - buf[pos+53] << decimation_shifts::pre32, - buf[pos+54] << decimation_shifts::pre32, - buf[pos+55] << decimation_shifts::pre32, + (buf[pos+48] - Shift) << decimation_shifts::pre32, + (buf[pos+49] - Shift) << decimation_shifts::pre32, + (buf[pos+50] - Shift) << decimation_shifts::pre32, + (buf[pos+51] - Shift) << decimation_shifts::pre32, + (buf[pos+52] - Shift) << decimation_shifts::pre32, + (buf[pos+53] - Shift) << decimation_shifts::pre32, + (buf[pos+54] - Shift) << decimation_shifts::pre32, + (buf[pos+55] - Shift) << decimation_shifts::pre32, &buf2[24]); + m_decimator2.myDecimateInf( - buf[pos+56] << decimation_shifts::pre32, - buf[pos+57] << decimation_shifts::pre32, - buf[pos+58] << decimation_shifts::pre32, - buf[pos+59] << decimation_shifts::pre32, - buf[pos+60] << decimation_shifts::pre32, - buf[pos+61] << decimation_shifts::pre32, - buf[pos+62] << decimation_shifts::pre32, - buf[pos+63] << decimation_shifts::pre32, + (buf[pos+56] - Shift) << decimation_shifts::pre32, + (buf[pos+57] - Shift) << decimation_shifts::pre32, + (buf[pos+58] - Shift) << decimation_shifts::pre32, + (buf[pos+59] - Shift) << decimation_shifts::pre32, + (buf[pos+60] - Shift) << decimation_shifts::pre32, + (buf[pos+61] - Shift) << decimation_shifts::pre32, + (buf[pos+62] - Shift) << decimation_shifts::pre32, + (buf[pos+63] - Shift) << decimation_shifts::pre32, &buf2[28]); + m_decimator2.myDecimateInf( - buf[pos+64] << decimation_shifts::pre32, - buf[pos+65] << decimation_shifts::pre32, - buf[pos+66] << decimation_shifts::pre32, - buf[pos+67] << decimation_shifts::pre32, - buf[pos+68] << decimation_shifts::pre32, - buf[pos+69] << decimation_shifts::pre32, - buf[pos+70] << decimation_shifts::pre32, - buf[pos+71] << decimation_shifts::pre32, + (buf[pos+64] - Shift) << decimation_shifts::pre32, + (buf[pos+65] - Shift) << decimation_shifts::pre32, + (buf[pos+66] - Shift) << decimation_shifts::pre32, + (buf[pos+67] - Shift) << decimation_shifts::pre32, + (buf[pos+68] - Shift) << decimation_shifts::pre32, + (buf[pos+69] - Shift) << decimation_shifts::pre32, + (buf[pos+70] - Shift) << decimation_shifts::pre32, + (buf[pos+71] - Shift) << decimation_shifts::pre32, &buf2[32]); + m_decimator2.myDecimateInf( - buf[pos+72] << decimation_shifts::pre32, - buf[pos+73] << decimation_shifts::pre32, - buf[pos+74] << decimation_shifts::pre32, - buf[pos+75] << decimation_shifts::pre32, - buf[pos+76] << decimation_shifts::pre32, - buf[pos+77] << decimation_shifts::pre32, - buf[pos+78] << decimation_shifts::pre32, - buf[pos+79] << decimation_shifts::pre32, + (buf[pos+72] - Shift) << decimation_shifts::pre32, + (buf[pos+73] - Shift) << decimation_shifts::pre32, + (buf[pos+74] - Shift) << decimation_shifts::pre32, + (buf[pos+75] - Shift) << decimation_shifts::pre32, + (buf[pos+76] - Shift) << decimation_shifts::pre32, + (buf[pos+77] - Shift) << decimation_shifts::pre32, + (buf[pos+78] - Shift) << decimation_shifts::pre32, + (buf[pos+79] - Shift) << decimation_shifts::pre32, &buf2[36]); + m_decimator2.myDecimateInf( - buf[pos+80] << decimation_shifts::pre32, - buf[pos+81] << decimation_shifts::pre32, - buf[pos+82] << decimation_shifts::pre32, - buf[pos+83] << decimation_shifts::pre32, - buf[pos+84] << decimation_shifts::pre32, - buf[pos+85] << decimation_shifts::pre32, - buf[pos+86] << decimation_shifts::pre32, - buf[pos+87] << decimation_shifts::pre32, + (buf[pos+80] - Shift) << decimation_shifts::pre32, + (buf[pos+81] - Shift) << decimation_shifts::pre32, + (buf[pos+82] - Shift) << decimation_shifts::pre32, + (buf[pos+83] - Shift) << decimation_shifts::pre32, + (buf[pos+84] - Shift) << decimation_shifts::pre32, + (buf[pos+85] - Shift) << decimation_shifts::pre32, + (buf[pos+86] - Shift) << decimation_shifts::pre32, + (buf[pos+87] - Shift) << decimation_shifts::pre32, &buf2[40]); + m_decimator2.myDecimateInf( - buf[pos+88] << decimation_shifts::pre32, - buf[pos+89] << decimation_shifts::pre32, - buf[pos+90] << decimation_shifts::pre32, - buf[pos+91] << decimation_shifts::pre32, - buf[pos+92] << decimation_shifts::pre32, - buf[pos+93] << decimation_shifts::pre32, - buf[pos+94] << decimation_shifts::pre32, - buf[pos+95] << decimation_shifts::pre32, + (buf[pos+88] - Shift) << decimation_shifts::pre32, + (buf[pos+89] - Shift) << decimation_shifts::pre32, + (buf[pos+90] - Shift) << decimation_shifts::pre32, + (buf[pos+91] - Shift) << decimation_shifts::pre32, + (buf[pos+92] - Shift) << decimation_shifts::pre32, + (buf[pos+93] - Shift) << decimation_shifts::pre32, + (buf[pos+94] - Shift) << decimation_shifts::pre32, + (buf[pos+95] - Shift) << decimation_shifts::pre32, &buf2[44]); + m_decimator2.myDecimateInf( - buf[pos+96] << decimation_shifts::pre32, - buf[pos+97] << decimation_shifts::pre32, - buf[pos+98] << decimation_shifts::pre32, - buf[pos+99] << decimation_shifts::pre32, - buf[pos+100] << decimation_shifts::pre32, - buf[pos+101] << decimation_shifts::pre32, - buf[pos+102] << decimation_shifts::pre32, - buf[pos+103] << decimation_shifts::pre32, + (buf[pos+96] - Shift) << decimation_shifts::pre32, + (buf[pos+97] - Shift) << decimation_shifts::pre32, + (buf[pos+98] - Shift) << decimation_shifts::pre32, + (buf[pos+99] - Shift) << decimation_shifts::pre32, + (buf[pos+100] - Shift) << decimation_shifts::pre32, + (buf[pos+101] - Shift) << decimation_shifts::pre32, + (buf[pos+102] - Shift) << decimation_shifts::pre32, + (buf[pos+103] - Shift) << decimation_shifts::pre32, &buf2[48]); + m_decimator2.myDecimateInf( - buf[pos+104] << decimation_shifts::pre32, - buf[pos+105] << decimation_shifts::pre32, - buf[pos+106] << decimation_shifts::pre32, - buf[pos+107] << decimation_shifts::pre32, - buf[pos+108] << decimation_shifts::pre32, - buf[pos+109] << decimation_shifts::pre32, - buf[pos+110] << decimation_shifts::pre32, - buf[pos+111] << decimation_shifts::pre32, + (buf[pos+104] - Shift) << decimation_shifts::pre32, + (buf[pos+105] - Shift) << decimation_shifts::pre32, + (buf[pos+106] - Shift) << decimation_shifts::pre32, + (buf[pos+107] - Shift) << decimation_shifts::pre32, + (buf[pos+108] - Shift) << decimation_shifts::pre32, + (buf[pos+109] - Shift) << decimation_shifts::pre32, + (buf[pos+110] - Shift) << decimation_shifts::pre32, + (buf[pos+111] - Shift) << decimation_shifts::pre32, &buf2[52]); - m_decimator2.myDecimateInf( - buf[pos+112] << decimation_shifts::pre32, - buf[pos+113] << decimation_shifts::pre32, - buf[pos+114] << decimation_shifts::pre32, - buf[pos+115] << decimation_shifts::pre32, - buf[pos+116] << decimation_shifts::pre32, - buf[pos+117] << decimation_shifts::pre32, - buf[pos+118] << decimation_shifts::pre32, - buf[pos+119] << decimation_shifts::pre32, - &buf2[56]); m_decimator2.myDecimateInf( - buf[pos+120] << decimation_shifts::pre32, - buf[pos+121] << decimation_shifts::pre32, - buf[pos+122] << decimation_shifts::pre32, - buf[pos+123] << decimation_shifts::pre32, - buf[pos+124] << decimation_shifts::pre32, - buf[pos+125] << decimation_shifts::pre32, - buf[pos+126] << decimation_shifts::pre32, - buf[pos+127] << decimation_shifts::pre32, + (buf[pos+112] - Shift) << decimation_shifts::pre32, + (buf[pos+113] - Shift) << decimation_shifts::pre32, + (buf[pos+114] - Shift) << decimation_shifts::pre32, + (buf[pos+115] - Shift) << decimation_shifts::pre32, + (buf[pos+116] - Shift) << decimation_shifts::pre32, + (buf[pos+117] - Shift) << decimation_shifts::pre32, + (buf[pos+118] - Shift) << decimation_shifts::pre32, + (buf[pos+119] - Shift) << decimation_shifts::pre32, + &buf2[56]); + + + m_decimator2.myDecimateInf( + (buf[pos+120] - Shift) << decimation_shifts::pre32, + (buf[pos+121] - Shift) << decimation_shifts::pre32, + (buf[pos+122] - Shift) << decimation_shifts::pre32, + (buf[pos+123] - Shift) << decimation_shifts::pre32, + (buf[pos+124] - Shift) << decimation_shifts::pre32, + (buf[pos+125] - Shift) << decimation_shifts::pre32, + (buf[pos+126] - Shift) << decimation_shifts::pre32, + (buf[pos+127] - Shift) << decimation_shifts::pre32, &buf2[60]); m_decimator4.myDecimateSup( @@ -937,179 +1094,194 @@ void DecimatorsU::decimate32_sup(Samp for (int pos = 0; pos < len - 127; pos += 128) { m_decimator2.myDecimateSup( - buf[pos+0] << decimation_shifts::pre32, - buf[pos+1] << decimation_shifts::pre32, - buf[pos+2] << decimation_shifts::pre32, - buf[pos+3] << decimation_shifts::pre32, - buf[pos+4] << decimation_shifts::pre32, - buf[pos+5] << decimation_shifts::pre32, - buf[pos+6] << decimation_shifts::pre32, - buf[pos+7] << decimation_shifts::pre32, + (buf[pos+0] - Shift) << decimation_shifts::pre32, + (buf[pos+1] - Shift) << decimation_shifts::pre32, + (buf[pos+2] - Shift) << decimation_shifts::pre32, + (buf[pos+3] - Shift) << decimation_shifts::pre32, + (buf[pos+4] - Shift) << decimation_shifts::pre32, + (buf[pos+5] - Shift) << decimation_shifts::pre32, + (buf[pos+6] - Shift) << decimation_shifts::pre32, + (buf[pos+7] - Shift) << decimation_shifts::pre32, &buf2[0]); + m_decimator2.myDecimateSup( - buf[pos+8] << decimation_shifts::pre32, - buf[pos+9] << decimation_shifts::pre32, - buf[pos+10] << decimation_shifts::pre32, - buf[pos+11] << decimation_shifts::pre32, - buf[pos+12] << decimation_shifts::pre32, - buf[pos+13] << decimation_shifts::pre32, - buf[pos+14] << decimation_shifts::pre32, - buf[pos+15] << decimation_shifts::pre32, + (buf[pos+8] - Shift) << decimation_shifts::pre32, + (buf[pos+9] - Shift) << decimation_shifts::pre32, + (buf[pos+10] - Shift) << decimation_shifts::pre32, + (buf[pos+11] - Shift) << decimation_shifts::pre32, + (buf[pos+12] - Shift) << decimation_shifts::pre32, + (buf[pos+13] - Shift) << decimation_shifts::pre32, + (buf[pos+14] - Shift) << decimation_shifts::pre32, + (buf[pos+15] - Shift) << decimation_shifts::pre32, &buf2[4]); + m_decimator2.myDecimateSup( - buf[pos+16] << decimation_shifts::pre32, - buf[pos+17] << decimation_shifts::pre32, - buf[pos+18] << decimation_shifts::pre32, - buf[pos+19] << decimation_shifts::pre32, - buf[pos+20] << decimation_shifts::pre32, - buf[pos+21] << decimation_shifts::pre32, - buf[pos+22] << decimation_shifts::pre32, - buf[pos+23] << decimation_shifts::pre32, + (buf[pos+16] - Shift) << decimation_shifts::pre32, + (buf[pos+17] - Shift) << decimation_shifts::pre32, + (buf[pos+18] - Shift) << decimation_shifts::pre32, + (buf[pos+19] - Shift) << decimation_shifts::pre32, + (buf[pos+20] - Shift) << decimation_shifts::pre32, + (buf[pos+21] - Shift) << decimation_shifts::pre32, + (buf[pos+22] - Shift) << decimation_shifts::pre32, + (buf[pos+23] - Shift) << decimation_shifts::pre32, &buf2[8]); + m_decimator2.myDecimateSup( - buf[pos+24] << decimation_shifts::pre32, - buf[pos+25] << decimation_shifts::pre32, - buf[pos+26] << decimation_shifts::pre32, - buf[pos+27] << decimation_shifts::pre32, - buf[pos+28] << decimation_shifts::pre32, - buf[pos+29] << decimation_shifts::pre32, - buf[pos+30] << decimation_shifts::pre32, - buf[pos+31] << decimation_shifts::pre32, + (buf[pos+24] - Shift) << decimation_shifts::pre32, + (buf[pos+25] - Shift) << decimation_shifts::pre32, + (buf[pos+26] - Shift) << decimation_shifts::pre32, + (buf[pos+27] - Shift) << decimation_shifts::pre32, + (buf[pos+28] - Shift) << decimation_shifts::pre32, + (buf[pos+29] - Shift) << decimation_shifts::pre32, + (buf[pos+30] - Shift) << decimation_shifts::pre32, + (buf[pos+31] - Shift) << decimation_shifts::pre32, &buf2[12]); + m_decimator2.myDecimateSup( - buf[pos+32] << decimation_shifts::pre32, - buf[pos+33] << decimation_shifts::pre32, - buf[pos+34] << decimation_shifts::pre32, - buf[pos+35] << decimation_shifts::pre32, - buf[pos+36] << decimation_shifts::pre32, - buf[pos+37] << decimation_shifts::pre32, - buf[pos+38] << decimation_shifts::pre32, - buf[pos+39] << decimation_shifts::pre32, + (buf[pos+32] - Shift) << decimation_shifts::pre32, + (buf[pos+33] - Shift) << decimation_shifts::pre32, + (buf[pos+34] - Shift) << decimation_shifts::pre32, + (buf[pos+35] - Shift) << decimation_shifts::pre32, + (buf[pos+36] - Shift) << decimation_shifts::pre32, + (buf[pos+37] - Shift) << decimation_shifts::pre32, + (buf[pos+38] - Shift) << decimation_shifts::pre32, + (buf[pos+39] - Shift) << decimation_shifts::pre32, &buf2[16]); + m_decimator2.myDecimateSup( - buf[pos+40] << decimation_shifts::pre32, - buf[pos+41] << decimation_shifts::pre32, - buf[pos+42] << decimation_shifts::pre32, - buf[pos+43] << decimation_shifts::pre32, - buf[pos+44] << decimation_shifts::pre32, - buf[pos+45] << decimation_shifts::pre32, - buf[pos+46] << decimation_shifts::pre32, - buf[pos+47] << decimation_shifts::pre32, + (buf[pos+40] - Shift) << decimation_shifts::pre32, + (buf[pos+41] - Shift) << decimation_shifts::pre32, + (buf[pos+42] - Shift) << decimation_shifts::pre32, + (buf[pos+43] - Shift) << decimation_shifts::pre32, + (buf[pos+44] - Shift) << decimation_shifts::pre32, + (buf[pos+45] - Shift) << decimation_shifts::pre32, + (buf[pos+46] - Shift) << decimation_shifts::pre32, + (buf[pos+47] - Shift) << decimation_shifts::pre32, &buf2[20]); + m_decimator2.myDecimateSup( - buf[pos+48] << decimation_shifts::pre32, - buf[pos+49] << decimation_shifts::pre32, - buf[pos+50] << decimation_shifts::pre32, - buf[pos+51] << decimation_shifts::pre32, - buf[pos+52] << decimation_shifts::pre32, - buf[pos+53] << decimation_shifts::pre32, - buf[pos+54] << decimation_shifts::pre32, - buf[pos+55] << decimation_shifts::pre32, + (buf[pos+48] - Shift) << decimation_shifts::pre32, + (buf[pos+49] - Shift) << decimation_shifts::pre32, + (buf[pos+50] - Shift) << decimation_shifts::pre32, + (buf[pos+51] - Shift) << decimation_shifts::pre32, + (buf[pos+52] - Shift) << decimation_shifts::pre32, + (buf[pos+53] - Shift) << decimation_shifts::pre32, + (buf[pos+54] - Shift) << decimation_shifts::pre32, + (buf[pos+55] - Shift) << decimation_shifts::pre32, &buf2[24]); + m_decimator2.myDecimateSup( - buf[pos+56] << decimation_shifts::pre32, - buf[pos+57] << decimation_shifts::pre32, - buf[pos+58] << decimation_shifts::pre32, - buf[pos+59] << decimation_shifts::pre32, - buf[pos+60] << decimation_shifts::pre32, - buf[pos+61] << decimation_shifts::pre32, - buf[pos+62] << decimation_shifts::pre32, - buf[pos+63] << decimation_shifts::pre32, + (buf[pos+56] - Shift) << decimation_shifts::pre32, + (buf[pos+57] - Shift) << decimation_shifts::pre32, + (buf[pos+58] - Shift) << decimation_shifts::pre32, + (buf[pos+59] - Shift) << decimation_shifts::pre32, + (buf[pos+60] - Shift) << decimation_shifts::pre32, + (buf[pos+61] - Shift) << decimation_shifts::pre32, + (buf[pos+62] - Shift) << decimation_shifts::pre32, + (buf[pos+63] - Shift) << decimation_shifts::pre32, &buf2[28]); + m_decimator2.myDecimateSup( - buf[pos+64] << decimation_shifts::pre32, - buf[pos+65] << decimation_shifts::pre32, - buf[pos+66] << decimation_shifts::pre32, - buf[pos+67] << decimation_shifts::pre32, - buf[pos+68] << decimation_shifts::pre32, - buf[pos+69] << decimation_shifts::pre32, - buf[pos+70] << decimation_shifts::pre32, - buf[pos+71] << decimation_shifts::pre32, + (buf[pos+64] - Shift) << decimation_shifts::pre32, + (buf[pos+65] - Shift) << decimation_shifts::pre32, + (buf[pos+66] - Shift) << decimation_shifts::pre32, + (buf[pos+67] - Shift) << decimation_shifts::pre32, + (buf[pos+68] - Shift) << decimation_shifts::pre32, + (buf[pos+69] - Shift) << decimation_shifts::pre32, + (buf[pos+70] - Shift) << decimation_shifts::pre32, + (buf[pos+71] - Shift) << decimation_shifts::pre32, &buf2[32]); + m_decimator2.myDecimateSup( - buf[pos+72] << decimation_shifts::pre32, - buf[pos+73] << decimation_shifts::pre32, - buf[pos+74] << decimation_shifts::pre32, - buf[pos+75] << decimation_shifts::pre32, - buf[pos+76] << decimation_shifts::pre32, - buf[pos+77] << decimation_shifts::pre32, - buf[pos+78] << decimation_shifts::pre32, - buf[pos+79] << decimation_shifts::pre32, + (buf[pos+72] - Shift) << decimation_shifts::pre32, + (buf[pos+73] - Shift) << decimation_shifts::pre32, + (buf[pos+74] - Shift) << decimation_shifts::pre32, + (buf[pos+75] - Shift) << decimation_shifts::pre32, + (buf[pos+76] - Shift) << decimation_shifts::pre32, + (buf[pos+77] - Shift) << decimation_shifts::pre32, + (buf[pos+78] - Shift) << decimation_shifts::pre32, + (buf[pos+79] - Shift) << decimation_shifts::pre32, &buf2[36]); + m_decimator2.myDecimateSup( - buf[pos+80] << decimation_shifts::pre32, - buf[pos+81] << decimation_shifts::pre32, - buf[pos+82] << decimation_shifts::pre32, - buf[pos+83] << decimation_shifts::pre32, - buf[pos+84] << decimation_shifts::pre32, - buf[pos+85] << decimation_shifts::pre32, - buf[pos+86] << decimation_shifts::pre32, - buf[pos+87] << decimation_shifts::pre32, + (buf[pos+80] - Shift) << decimation_shifts::pre32, + (buf[pos+81] - Shift) << decimation_shifts::pre32, + (buf[pos+82] - Shift) << decimation_shifts::pre32, + (buf[pos+83] - Shift) << decimation_shifts::pre32, + (buf[pos+84] - Shift) << decimation_shifts::pre32, + (buf[pos+85] - Shift) << decimation_shifts::pre32, + (buf[pos+86] - Shift) << decimation_shifts::pre32, + (buf[pos+87] - Shift) << decimation_shifts::pre32, &buf2[40]); + m_decimator2.myDecimateSup( - buf[pos+88] << decimation_shifts::pre32, - buf[pos+89] << decimation_shifts::pre32, - buf[pos+90] << decimation_shifts::pre32, - buf[pos+91] << decimation_shifts::pre32, - buf[pos+92] << decimation_shifts::pre32, - buf[pos+93] << decimation_shifts::pre32, - buf[pos+94] << decimation_shifts::pre32, - buf[pos+95] << decimation_shifts::pre32, + (buf[pos+88] - Shift) << decimation_shifts::pre32, + (buf[pos+89] - Shift) << decimation_shifts::pre32, + (buf[pos+90] - Shift) << decimation_shifts::pre32, + (buf[pos+91] - Shift) << decimation_shifts::pre32, + (buf[pos+92] - Shift) << decimation_shifts::pre32, + (buf[pos+93] - Shift) << decimation_shifts::pre32, + (buf[pos+94] - Shift) << decimation_shifts::pre32, + (buf[pos+95] - Shift) << decimation_shifts::pre32, &buf2[44]); + m_decimator2.myDecimateSup( - buf[pos+96] << decimation_shifts::pre32, - buf[pos+97] << decimation_shifts::pre32, - buf[pos+98] << decimation_shifts::pre32, - buf[pos+99] << decimation_shifts::pre32, - buf[pos+100] << decimation_shifts::pre32, - buf[pos+101] << decimation_shifts::pre32, - buf[pos+102] << decimation_shifts::pre32, - buf[pos+103] << decimation_shifts::pre32, + (buf[pos+96] - Shift) << decimation_shifts::pre32, + (buf[pos+97] - Shift) << decimation_shifts::pre32, + (buf[pos+98] - Shift) << decimation_shifts::pre32, + (buf[pos+99] - Shift) << decimation_shifts::pre32, + (buf[pos+100] - Shift) << decimation_shifts::pre32, + (buf[pos+101] - Shift) << decimation_shifts::pre32, + (buf[pos+102] - Shift) << decimation_shifts::pre32, + (buf[pos+103] - Shift) << decimation_shifts::pre32, &buf2[48]); + m_decimator2.myDecimateSup( - buf[pos+104] << decimation_shifts::pre32, - buf[pos+105] << decimation_shifts::pre32, - buf[pos+106] << decimation_shifts::pre32, - buf[pos+107] << decimation_shifts::pre32, - buf[pos+108] << decimation_shifts::pre32, - buf[pos+109] << decimation_shifts::pre32, - buf[pos+110] << decimation_shifts::pre32, - buf[pos+111] << decimation_shifts::pre32, + (buf[pos+104] - Shift) << decimation_shifts::pre32, + (buf[pos+105] - Shift) << decimation_shifts::pre32, + (buf[pos+106] - Shift) << decimation_shifts::pre32, + (buf[pos+107] - Shift) << decimation_shifts::pre32, + (buf[pos+108] - Shift) << decimation_shifts::pre32, + (buf[pos+109] - Shift) << decimation_shifts::pre32, + (buf[pos+110] - Shift) << decimation_shifts::pre32, + (buf[pos+111] - Shift) << decimation_shifts::pre32, &buf2[52]); - m_decimator2.myDecimateSup( - buf[pos+112] << decimation_shifts::pre32, - buf[pos+113] << decimation_shifts::pre32, - buf[pos+114] << decimation_shifts::pre32, - buf[pos+115] << decimation_shifts::pre32, - buf[pos+116] << decimation_shifts::pre32, - buf[pos+117] << decimation_shifts::pre32, - buf[pos+118] << decimation_shifts::pre32, - buf[pos+119] << decimation_shifts::pre32, - &buf2[56]); m_decimator2.myDecimateSup( - buf[pos+120] << decimation_shifts::pre32, - buf[pos+121] << decimation_shifts::pre32, - buf[pos+122] << decimation_shifts::pre32, - buf[pos+123] << decimation_shifts::pre32, - buf[pos+124] << decimation_shifts::pre32, - buf[pos+125] << decimation_shifts::pre32, - buf[pos+126] << decimation_shifts::pre32, - buf[pos+127] << decimation_shifts::pre32, + (buf[pos+112] - Shift) << decimation_shifts::pre32, + (buf[pos+113] - Shift) << decimation_shifts::pre32, + (buf[pos+114] - Shift) << decimation_shifts::pre32, + (buf[pos+115] - Shift) << decimation_shifts::pre32, + (buf[pos+116] - Shift) << decimation_shifts::pre32, + (buf[pos+117] - Shift) << decimation_shifts::pre32, + (buf[pos+118] - Shift) << decimation_shifts::pre32, + (buf[pos+119] - Shift) << decimation_shifts::pre32, + &buf2[56]); + + + m_decimator2.myDecimateSup( + (buf[pos+120] - Shift) << decimation_shifts::pre32, + (buf[pos+121] - Shift) << decimation_shifts::pre32, + (buf[pos+122] - Shift) << decimation_shifts::pre32, + (buf[pos+123] - Shift) << decimation_shifts::pre32, + (buf[pos+124] - Shift) << decimation_shifts::pre32, + (buf[pos+125] - Shift) << decimation_shifts::pre32, + (buf[pos+126] - Shift) << decimation_shifts::pre32, + (buf[pos+127] - Shift) << decimation_shifts::pre32, &buf2[60]); m_decimator4.myDecimateInf( @@ -1190,355 +1362,386 @@ void DecimatorsU::decimate64_inf(Samp for (int pos = 0; pos < len - 255; pos += 256) { m_decimator2.myDecimateInf( - buf[pos+0] << decimation_shifts::pre64, - buf[pos+1] << decimation_shifts::pre64, - buf[pos+2] << decimation_shifts::pre64, - buf[pos+3] << decimation_shifts::pre64, - buf[pos+4] << decimation_shifts::pre64, - buf[pos+5] << decimation_shifts::pre64, - buf[pos+6] << decimation_shifts::pre64, - buf[pos+7] << decimation_shifts::pre64, + (buf[pos+0] - Shift) << decimation_shifts::pre64, + (buf[pos+1] - Shift) << decimation_shifts::pre64, + (buf[pos+2] - Shift) << decimation_shifts::pre64, + (buf[pos+3] - Shift) << decimation_shifts::pre64, + (buf[pos+4] - Shift) << decimation_shifts::pre64, + (buf[pos+5] - Shift) << decimation_shifts::pre64, + (buf[pos+6] - Shift) << decimation_shifts::pre64, + (buf[pos+7] - Shift) << decimation_shifts::pre64, &buf2[0]); + m_decimator2.myDecimateInf( - buf[pos+8] << decimation_shifts::pre64, - buf[pos+9] << decimation_shifts::pre64, - buf[pos+10] << decimation_shifts::pre64, - buf[pos+11] << decimation_shifts::pre64, - buf[pos+12] << decimation_shifts::pre64, - buf[pos+13] << decimation_shifts::pre64, - buf[pos+14] << decimation_shifts::pre64, - buf[pos+15] << decimation_shifts::pre64, + (buf[pos+8] - Shift) << decimation_shifts::pre64, + (buf[pos+9] - Shift) << decimation_shifts::pre64, + (buf[pos+10] - Shift) << decimation_shifts::pre64, + (buf[pos+11] - Shift) << decimation_shifts::pre64, + (buf[pos+12] - Shift) << decimation_shifts::pre64, + (buf[pos+13] - Shift) << decimation_shifts::pre64, + (buf[pos+14] - Shift) << decimation_shifts::pre64, + (buf[pos+15] - Shift) << decimation_shifts::pre64, &buf2[4]); + m_decimator2.myDecimateInf( - buf[pos+16] << decimation_shifts::pre64, - buf[pos+17] << decimation_shifts::pre64, - buf[pos+18] << decimation_shifts::pre64, - buf[pos+19] << decimation_shifts::pre64, - buf[pos+20] << decimation_shifts::pre64, - buf[pos+21] << decimation_shifts::pre64, - buf[pos+22] << decimation_shifts::pre64, - buf[pos+23] << decimation_shifts::pre64, + (buf[pos+16] - Shift) << decimation_shifts::pre64, + (buf[pos+17] - Shift) << decimation_shifts::pre64, + (buf[pos+18] - Shift) << decimation_shifts::pre64, + (buf[pos+19] - Shift) << decimation_shifts::pre64, + (buf[pos+20] - Shift) << decimation_shifts::pre64, + (buf[pos+21] - Shift) << decimation_shifts::pre64, + (buf[pos+22] - Shift) << decimation_shifts::pre64, + (buf[pos+23] - Shift) << decimation_shifts::pre64, &buf2[8]); + m_decimator2.myDecimateInf( - buf[pos+24] << decimation_shifts::pre64, - buf[pos+25] << decimation_shifts::pre64, - buf[pos+26] << decimation_shifts::pre64, - buf[pos+27] << decimation_shifts::pre64, - buf[pos+28] << decimation_shifts::pre64, - buf[pos+29] << decimation_shifts::pre64, - buf[pos+30] << decimation_shifts::pre64, - buf[pos+31] << decimation_shifts::pre64, + (buf[pos+24] - Shift) << decimation_shifts::pre64, + (buf[pos+25] - Shift) << decimation_shifts::pre64, + (buf[pos+26] - Shift) << decimation_shifts::pre64, + (buf[pos+27] - Shift) << decimation_shifts::pre64, + (buf[pos+28] - Shift) << decimation_shifts::pre64, + (buf[pos+29] - Shift) << decimation_shifts::pre64, + (buf[pos+30] - Shift) << decimation_shifts::pre64, + (buf[pos+31] - Shift) << decimation_shifts::pre64, &buf2[12]); + m_decimator2.myDecimateInf( - buf[pos+32] << decimation_shifts::pre64, - buf[pos+33] << decimation_shifts::pre64, - buf[pos+34] << decimation_shifts::pre64, - buf[pos+35] << decimation_shifts::pre64, - buf[pos+36] << decimation_shifts::pre64, - buf[pos+37] << decimation_shifts::pre64, - buf[pos+38] << decimation_shifts::pre64, - buf[pos+39] << decimation_shifts::pre64, + (buf[pos+32] - Shift) << decimation_shifts::pre64, + (buf[pos+33] - Shift) << decimation_shifts::pre64, + (buf[pos+34] - Shift) << decimation_shifts::pre64, + (buf[pos+35] - Shift) << decimation_shifts::pre64, + (buf[pos+36] - Shift) << decimation_shifts::pre64, + (buf[pos+37] - Shift) << decimation_shifts::pre64, + (buf[pos+38] - Shift) << decimation_shifts::pre64, + (buf[pos+39] - Shift) << decimation_shifts::pre64, &buf2[16]); + m_decimator2.myDecimateInf( - buf[pos+40] << decimation_shifts::pre64, - buf[pos+41] << decimation_shifts::pre64, - buf[pos+42] << decimation_shifts::pre64, - buf[pos+43] << decimation_shifts::pre64, - buf[pos+44] << decimation_shifts::pre64, - buf[pos+45] << decimation_shifts::pre64, - buf[pos+46] << decimation_shifts::pre64, - buf[pos+47] << decimation_shifts::pre64, + (buf[pos+40] - Shift) << decimation_shifts::pre64, + (buf[pos+41] - Shift) << decimation_shifts::pre64, + (buf[pos+42] - Shift) << decimation_shifts::pre64, + (buf[pos+43] - Shift) << decimation_shifts::pre64, + (buf[pos+44] - Shift) << decimation_shifts::pre64, + (buf[pos+45] - Shift) << decimation_shifts::pre64, + (buf[pos+46] - Shift) << decimation_shifts::pre64, + (buf[pos+47] - Shift) << decimation_shifts::pre64, &buf2[20]); + m_decimator2.myDecimateInf( - buf[pos+48] << decimation_shifts::pre64, - buf[pos+49] << decimation_shifts::pre64, - buf[pos+50] << decimation_shifts::pre64, - buf[pos+51] << decimation_shifts::pre64, - buf[pos+52] << decimation_shifts::pre64, - buf[pos+53] << decimation_shifts::pre64, - buf[pos+54] << decimation_shifts::pre64, - buf[pos+55] << decimation_shifts::pre64, + (buf[pos+48] - Shift) << decimation_shifts::pre64, + (buf[pos+49] - Shift) << decimation_shifts::pre64, + (buf[pos+50] - Shift) << decimation_shifts::pre64, + (buf[pos+51] - Shift) << decimation_shifts::pre64, + (buf[pos+52] - Shift) << decimation_shifts::pre64, + (buf[pos+53] - Shift) << decimation_shifts::pre64, + (buf[pos+54] - Shift) << decimation_shifts::pre64, + (buf[pos+55] - Shift) << decimation_shifts::pre64, &buf2[24]); + m_decimator2.myDecimateInf( - buf[pos+56] << decimation_shifts::pre64, - buf[pos+57] << decimation_shifts::pre64, - buf[pos+58] << decimation_shifts::pre64, - buf[pos+59] << decimation_shifts::pre64, - buf[pos+60] << decimation_shifts::pre64, - buf[pos+61] << decimation_shifts::pre64, - buf[pos+62] << decimation_shifts::pre64, - buf[pos+63] << decimation_shifts::pre64, + (buf[pos+56] - Shift) << decimation_shifts::pre64, + (buf[pos+57] - Shift) << decimation_shifts::pre64, + (buf[pos+58] - Shift) << decimation_shifts::pre64, + (buf[pos+59] - Shift) << decimation_shifts::pre64, + (buf[pos+60] - Shift) << decimation_shifts::pre64, + (buf[pos+61] - Shift) << decimation_shifts::pre64, + (buf[pos+62] - Shift) << decimation_shifts::pre64, + (buf[pos+63] - Shift) << decimation_shifts::pre64, &buf2[28]); + m_decimator2.myDecimateInf( - buf[pos+64] << decimation_shifts::pre64, - buf[pos+65] << decimation_shifts::pre64, - buf[pos+66] << decimation_shifts::pre64, - buf[pos+67] << decimation_shifts::pre64, - buf[pos+68] << decimation_shifts::pre64, - buf[pos+69] << decimation_shifts::pre64, - buf[pos+70] << decimation_shifts::pre64, - buf[pos+71] << decimation_shifts::pre64, + (buf[pos+64] - Shift) << decimation_shifts::pre64, + (buf[pos+65] - Shift) << decimation_shifts::pre64, + (buf[pos+66] - Shift) << decimation_shifts::pre64, + (buf[pos+67] - Shift) << decimation_shifts::pre64, + (buf[pos+68] - Shift) << decimation_shifts::pre64, + (buf[pos+69] - Shift) << decimation_shifts::pre64, + (buf[pos+70] - Shift) << decimation_shifts::pre64, + (buf[pos+71] - Shift) << decimation_shifts::pre64, &buf2[32]); + m_decimator2.myDecimateInf( - buf[pos+72] << decimation_shifts::pre64, - buf[pos+73] << decimation_shifts::pre64, - buf[pos+74] << decimation_shifts::pre64, - buf[pos+75] << decimation_shifts::pre64, - buf[pos+76] << decimation_shifts::pre64, - buf[pos+77] << decimation_shifts::pre64, - buf[pos+78] << decimation_shifts::pre64, - buf[pos+79] << decimation_shifts::pre64, + (buf[pos+72] - Shift) << decimation_shifts::pre64, + (buf[pos+73] - Shift) << decimation_shifts::pre64, + (buf[pos+74] - Shift) << decimation_shifts::pre64, + (buf[pos+75] - Shift) << decimation_shifts::pre64, + (buf[pos+76] - Shift) << decimation_shifts::pre64, + (buf[pos+77] - Shift) << decimation_shifts::pre64, + (buf[pos+78] - Shift) << decimation_shifts::pre64, + (buf[pos+79] - Shift) << decimation_shifts::pre64, &buf2[36]); + m_decimator2.myDecimateInf( - buf[pos+80] << decimation_shifts::pre64, - buf[pos+81] << decimation_shifts::pre64, - buf[pos+82] << decimation_shifts::pre64, - buf[pos+83] << decimation_shifts::pre64, - buf[pos+84] << decimation_shifts::pre64, - buf[pos+85] << decimation_shifts::pre64, - buf[pos+86] << decimation_shifts::pre64, - buf[pos+87] << decimation_shifts::pre64, + (buf[pos+80] - Shift) << decimation_shifts::pre64, + (buf[pos+81] - Shift) << decimation_shifts::pre64, + (buf[pos+82] - Shift) << decimation_shifts::pre64, + (buf[pos+83] - Shift) << decimation_shifts::pre64, + (buf[pos+84] - Shift) << decimation_shifts::pre64, + (buf[pos+85] - Shift) << decimation_shifts::pre64, + (buf[pos+86] - Shift) << decimation_shifts::pre64, + (buf[pos+87] - Shift) << decimation_shifts::pre64, &buf2[40]); + m_decimator2.myDecimateInf( - buf[pos+88] << decimation_shifts::pre64, - buf[pos+89] << decimation_shifts::pre64, - buf[pos+90] << decimation_shifts::pre64, - buf[pos+91] << decimation_shifts::pre64, - buf[pos+92] << decimation_shifts::pre64, - buf[pos+93] << decimation_shifts::pre64, - buf[pos+94] << decimation_shifts::pre64, - buf[pos+95] << decimation_shifts::pre64, + (buf[pos+88] - Shift) << decimation_shifts::pre64, + (buf[pos+89] - Shift) << decimation_shifts::pre64, + (buf[pos+90] - Shift) << decimation_shifts::pre64, + (buf[pos+91] - Shift) << decimation_shifts::pre64, + (buf[pos+92] - Shift) << decimation_shifts::pre64, + (buf[pos+93] - Shift) << decimation_shifts::pre64, + (buf[pos+94] - Shift) << decimation_shifts::pre64, + (buf[pos+95] - Shift) << decimation_shifts::pre64, &buf2[44]); + m_decimator2.myDecimateInf( - buf[pos+96] << decimation_shifts::pre64, - buf[pos+97] << decimation_shifts::pre64, - buf[pos+98] << decimation_shifts::pre64, - buf[pos+99] << decimation_shifts::pre64, - buf[pos+100] << decimation_shifts::pre64, - buf[pos+101] << decimation_shifts::pre64, - buf[pos+102] << decimation_shifts::pre64, - buf[pos+103] << decimation_shifts::pre64, + (buf[pos+96] - Shift) << decimation_shifts::pre64, + (buf[pos+97] - Shift) << decimation_shifts::pre64, + (buf[pos+98] - Shift) << decimation_shifts::pre64, + (buf[pos+99] - Shift) << decimation_shifts::pre64, + (buf[pos+100] - Shift) << decimation_shifts::pre64, + (buf[pos+101] - Shift) << decimation_shifts::pre64, + (buf[pos+102] - Shift) << decimation_shifts::pre64, + (buf[pos+103] - Shift) << decimation_shifts::pre64, &buf2[48]); + m_decimator2.myDecimateInf( - buf[pos+104] << decimation_shifts::pre64, - buf[pos+105] << decimation_shifts::pre64, - buf[pos+106] << decimation_shifts::pre64, - buf[pos+107] << decimation_shifts::pre64, - buf[pos+108] << decimation_shifts::pre64, - buf[pos+109] << decimation_shifts::pre64, - buf[pos+110] << decimation_shifts::pre64, - buf[pos+111] << decimation_shifts::pre64, + (buf[pos+104] - Shift) << decimation_shifts::pre64, + (buf[pos+105] - Shift) << decimation_shifts::pre64, + (buf[pos+106] - Shift) << decimation_shifts::pre64, + (buf[pos+107] - Shift) << decimation_shifts::pre64, + (buf[pos+108] - Shift) << decimation_shifts::pre64, + (buf[pos+109] - Shift) << decimation_shifts::pre64, + (buf[pos+110] - Shift) << decimation_shifts::pre64, + (buf[pos+111] - Shift) << decimation_shifts::pre64, &buf2[52]); + m_decimator2.myDecimateInf( - buf[pos+112] << decimation_shifts::pre64, - buf[pos+113] << decimation_shifts::pre64, - buf[pos+114] << decimation_shifts::pre64, - buf[pos+115] << decimation_shifts::pre64, - buf[pos+116] << decimation_shifts::pre64, - buf[pos+117] << decimation_shifts::pre64, - buf[pos+118] << decimation_shifts::pre64, - buf[pos+119] << decimation_shifts::pre64, + (buf[pos+112] - Shift) << decimation_shifts::pre64, + (buf[pos+113] - Shift) << decimation_shifts::pre64, + (buf[pos+114] - Shift) << decimation_shifts::pre64, + (buf[pos+115] - Shift) << decimation_shifts::pre64, + (buf[pos+116] - Shift) << decimation_shifts::pre64, + (buf[pos+117] - Shift) << decimation_shifts::pre64, + (buf[pos+118] - Shift) << decimation_shifts::pre64, + (buf[pos+119] - Shift) << decimation_shifts::pre64, &buf2[56]); + m_decimator2.myDecimateInf( - buf[pos+120] << decimation_shifts::pre64, - buf[pos+121] << decimation_shifts::pre64, - buf[pos+122] << decimation_shifts::pre64, - buf[pos+123] << decimation_shifts::pre64, - buf[pos+124] << decimation_shifts::pre64, - buf[pos+125] << decimation_shifts::pre64, - buf[pos+126] << decimation_shifts::pre64, - buf[pos+127] << decimation_shifts::pre64, + (buf[pos+120] - Shift) << decimation_shifts::pre64, + (buf[pos+121] - Shift) << decimation_shifts::pre64, + (buf[pos+122] - Shift) << decimation_shifts::pre64, + (buf[pos+123] - Shift) << decimation_shifts::pre64, + (buf[pos+124] - Shift) << decimation_shifts::pre64, + (buf[pos+125] - Shift) << decimation_shifts::pre64, + (buf[pos+126] - Shift) << decimation_shifts::pre64, + (buf[pos+127] - Shift) << decimation_shifts::pre64, &buf2[60]); + m_decimator2.myDecimateInf( - buf[pos+128] << decimation_shifts::pre64, - buf[pos+129] << decimation_shifts::pre64, - buf[pos+130] << decimation_shifts::pre64, - buf[pos+131] << decimation_shifts::pre64, - buf[pos+132] << decimation_shifts::pre64, - buf[pos+133] << decimation_shifts::pre64, - buf[pos+134] << decimation_shifts::pre64, - buf[pos+135] << decimation_shifts::pre64, + (buf[pos+128] - Shift) << decimation_shifts::pre64, + (buf[pos+129] - Shift) << decimation_shifts::pre64, + (buf[pos+130] - Shift) << decimation_shifts::pre64, + (buf[pos+131] - Shift) << decimation_shifts::pre64, + (buf[pos+132] - Shift) << decimation_shifts::pre64, + (buf[pos+133] - Shift) << decimation_shifts::pre64, + (buf[pos+134] - Shift) << decimation_shifts::pre64, + (buf[pos+135] - Shift) << decimation_shifts::pre64, &buf2[64]); + m_decimator2.myDecimateInf( - buf[pos+136] << decimation_shifts::pre64, - buf[pos+137] << decimation_shifts::pre64, - buf[pos+138] << decimation_shifts::pre64, - buf[pos+139] << decimation_shifts::pre64, - buf[pos+140] << decimation_shifts::pre64, - buf[pos+141] << decimation_shifts::pre64, - buf[pos+142] << decimation_shifts::pre64, - buf[pos+143] << decimation_shifts::pre64, + (buf[pos+136] - Shift) << decimation_shifts::pre64, + (buf[pos+137] - Shift) << decimation_shifts::pre64, + (buf[pos+138] - Shift) << decimation_shifts::pre64, + (buf[pos+139] - Shift) << decimation_shifts::pre64, + (buf[pos+140] - Shift) << decimation_shifts::pre64, + (buf[pos+141] - Shift) << decimation_shifts::pre64, + (buf[pos+142] - Shift) << decimation_shifts::pre64, + (buf[pos+143] - Shift) << decimation_shifts::pre64, &buf2[68]); + m_decimator2.myDecimateInf( - buf[pos+144] << decimation_shifts::pre64, - buf[pos+145] << decimation_shifts::pre64, - buf[pos+146] << decimation_shifts::pre64, - buf[pos+147] << decimation_shifts::pre64, - buf[pos+148] << decimation_shifts::pre64, - buf[pos+149] << decimation_shifts::pre64, - buf[pos+150] << decimation_shifts::pre64, - buf[pos+151] << decimation_shifts::pre64, + (buf[pos+144] - Shift) << decimation_shifts::pre64, + (buf[pos+145] - Shift) << decimation_shifts::pre64, + (buf[pos+146] - Shift) << decimation_shifts::pre64, + (buf[pos+147] - Shift) << decimation_shifts::pre64, + (buf[pos+148] - Shift) << decimation_shifts::pre64, + (buf[pos+149] - Shift) << decimation_shifts::pre64, + (buf[pos+150] - Shift) << decimation_shifts::pre64, + (buf[pos+151] - Shift) << decimation_shifts::pre64, &buf2[72]); + m_decimator2.myDecimateInf( - buf[pos+152] << decimation_shifts::pre64, - buf[pos+153] << decimation_shifts::pre64, - buf[pos+154] << decimation_shifts::pre64, - buf[pos+155] << decimation_shifts::pre64, - buf[pos+156] << decimation_shifts::pre64, - buf[pos+157] << decimation_shifts::pre64, - buf[pos+158] << decimation_shifts::pre64, - buf[pos+159] << decimation_shifts::pre64, + (buf[pos+152] - Shift) << decimation_shifts::pre64, + (buf[pos+153] - Shift) << decimation_shifts::pre64, + (buf[pos+154] - Shift) << decimation_shifts::pre64, + (buf[pos+155] - Shift) << decimation_shifts::pre64, + (buf[pos+156] - Shift) << decimation_shifts::pre64, + (buf[pos+157] - Shift) << decimation_shifts::pre64, + (buf[pos+158] - Shift) << decimation_shifts::pre64, + (buf[pos+159] - Shift) << decimation_shifts::pre64, &buf2[76]); + m_decimator2.myDecimateInf( - buf[pos+160] << decimation_shifts::pre64, - buf[pos+161] << decimation_shifts::pre64, - buf[pos+162] << decimation_shifts::pre64, - buf[pos+163] << decimation_shifts::pre64, - buf[pos+164] << decimation_shifts::pre64, - buf[pos+165] << decimation_shifts::pre64, - buf[pos+166] << decimation_shifts::pre64, - buf[pos+167] << decimation_shifts::pre64, + (buf[pos+160] - Shift) << decimation_shifts::pre64, + (buf[pos+161] - Shift) << decimation_shifts::pre64, + (buf[pos+162] - Shift) << decimation_shifts::pre64, + (buf[pos+163] - Shift) << decimation_shifts::pre64, + (buf[pos+164] - Shift) << decimation_shifts::pre64, + (buf[pos+165] - Shift) << decimation_shifts::pre64, + (buf[pos+166] - Shift) << decimation_shifts::pre64, + (buf[pos+167] - Shift) << decimation_shifts::pre64, &buf2[80]); + m_decimator2.myDecimateInf( - buf[pos+168] << decimation_shifts::pre64, - buf[pos+169] << decimation_shifts::pre64, - buf[pos+170] << decimation_shifts::pre64, - buf[pos+171] << decimation_shifts::pre64, - buf[pos+172] << decimation_shifts::pre64, - buf[pos+173] << decimation_shifts::pre64, - buf[pos+174] << decimation_shifts::pre64, - buf[pos+175] << decimation_shifts::pre64, + (buf[pos+168] - Shift) << decimation_shifts::pre64, + (buf[pos+169] - Shift) << decimation_shifts::pre64, + (buf[pos+170] - Shift) << decimation_shifts::pre64, + (buf[pos+171] - Shift) << decimation_shifts::pre64, + (buf[pos+172] - Shift) << decimation_shifts::pre64, + (buf[pos+173] - Shift) << decimation_shifts::pre64, + (buf[pos+174] - Shift) << decimation_shifts::pre64, + (buf[pos+175] - Shift) << decimation_shifts::pre64, &buf2[84]); + m_decimator2.myDecimateInf( - buf[pos+176] << decimation_shifts::pre64, - buf[pos+177] << decimation_shifts::pre64, - buf[pos+178] << decimation_shifts::pre64, - buf[pos+179] << decimation_shifts::pre64, - buf[pos+180] << decimation_shifts::pre64, - buf[pos+181] << decimation_shifts::pre64, - buf[pos+182] << decimation_shifts::pre64, - buf[pos+183] << decimation_shifts::pre64, + (buf[pos+176] - Shift) << decimation_shifts::pre64, + (buf[pos+177] - Shift) << decimation_shifts::pre64, + (buf[pos+178] - Shift) << decimation_shifts::pre64, + (buf[pos+179] - Shift) << decimation_shifts::pre64, + (buf[pos+180] - Shift) << decimation_shifts::pre64, + (buf[pos+181] - Shift) << decimation_shifts::pre64, + (buf[pos+182] - Shift) << decimation_shifts::pre64, + (buf[pos+183] - Shift) << decimation_shifts::pre64, &buf2[88]); + m_decimator2.myDecimateInf( - buf[pos+184] << decimation_shifts::pre64, - buf[pos+185] << decimation_shifts::pre64, - buf[pos+186] << decimation_shifts::pre64, - buf[pos+187] << decimation_shifts::pre64, - buf[pos+188] << decimation_shifts::pre64, - buf[pos+189] << decimation_shifts::pre64, - buf[pos+190] << decimation_shifts::pre64, - buf[pos+191] << decimation_shifts::pre64, + (buf[pos+184] - Shift) << decimation_shifts::pre64, + (buf[pos+185] - Shift) << decimation_shifts::pre64, + (buf[pos+186] - Shift) << decimation_shifts::pre64, + (buf[pos+187] - Shift) << decimation_shifts::pre64, + (buf[pos+188] - Shift) << decimation_shifts::pre64, + (buf[pos+189] - Shift) << decimation_shifts::pre64, + (buf[pos+190] - Shift) << decimation_shifts::pre64, + (buf[pos+191] - Shift) << decimation_shifts::pre64, &buf2[92]); + m_decimator2.myDecimateInf( - buf[pos+192] << decimation_shifts::pre64, - buf[pos+193] << decimation_shifts::pre64, - buf[pos+194] << decimation_shifts::pre64, - buf[pos+195] << decimation_shifts::pre64, - buf[pos+196] << decimation_shifts::pre64, - buf[pos+197] << decimation_shifts::pre64, - buf[pos+198] << decimation_shifts::pre64, - buf[pos+199] << decimation_shifts::pre64, + (buf[pos+192] - Shift) << decimation_shifts::pre64, + (buf[pos+193] - Shift) << decimation_shifts::pre64, + (buf[pos+194] - Shift) << decimation_shifts::pre64, + (buf[pos+195] - Shift) << decimation_shifts::pre64, + (buf[pos+196] - Shift) << decimation_shifts::pre64, + (buf[pos+197] - Shift) << decimation_shifts::pre64, + (buf[pos+198] - Shift) << decimation_shifts::pre64, + (buf[pos+199] - Shift) << decimation_shifts::pre64, &buf2[96]); + m_decimator2.myDecimateInf( - buf[pos+200] << decimation_shifts::pre64, - buf[pos+201] << decimation_shifts::pre64, - buf[pos+202] << decimation_shifts::pre64, - buf[pos+203] << decimation_shifts::pre64, - buf[pos+204] << decimation_shifts::pre64, - buf[pos+205] << decimation_shifts::pre64, - buf[pos+206] << decimation_shifts::pre64, - buf[pos+207] << decimation_shifts::pre64, + (buf[pos+200] - Shift) << decimation_shifts::pre64, + (buf[pos+201] - Shift) << decimation_shifts::pre64, + (buf[pos+202] - Shift) << decimation_shifts::pre64, + (buf[pos+203] - Shift) << decimation_shifts::pre64, + (buf[pos+204] - Shift) << decimation_shifts::pre64, + (buf[pos+205] - Shift) << decimation_shifts::pre64, + (buf[pos+206] - Shift) << decimation_shifts::pre64, + (buf[pos+207] - Shift) << decimation_shifts::pre64, &buf2[100]); + m_decimator2.myDecimateInf( - buf[pos+208] << decimation_shifts::pre64, - buf[pos+209] << decimation_shifts::pre64, - buf[pos+210] << decimation_shifts::pre64, - buf[pos+211] << decimation_shifts::pre64, - buf[pos+212] << decimation_shifts::pre64, - buf[pos+213] << decimation_shifts::pre64, - buf[pos+214] << decimation_shifts::pre64, - buf[pos+215] << decimation_shifts::pre64, + (buf[pos+208] - Shift) << decimation_shifts::pre64, + (buf[pos+209] - Shift) << decimation_shifts::pre64, + (buf[pos+210] - Shift) << decimation_shifts::pre64, + (buf[pos+211] - Shift) << decimation_shifts::pre64, + (buf[pos+212] - Shift) << decimation_shifts::pre64, + (buf[pos+213] - Shift) << decimation_shifts::pre64, + (buf[pos+214] - Shift) << decimation_shifts::pre64, + (buf[pos+215] - Shift) << decimation_shifts::pre64, &buf2[104]); + m_decimator2.myDecimateInf( - buf[pos+216] << decimation_shifts::pre64, - buf[pos+217] << decimation_shifts::pre64, - buf[pos+218] << decimation_shifts::pre64, - buf[pos+219] << decimation_shifts::pre64, - buf[pos+220] << decimation_shifts::pre64, - buf[pos+221] << decimation_shifts::pre64, - buf[pos+222] << decimation_shifts::pre64, - buf[pos+223] << decimation_shifts::pre64, + (buf[pos+216] - Shift) << decimation_shifts::pre64, + (buf[pos+217] - Shift) << decimation_shifts::pre64, + (buf[pos+218] - Shift) << decimation_shifts::pre64, + (buf[pos+219] - Shift) << decimation_shifts::pre64, + (buf[pos+220] - Shift) << decimation_shifts::pre64, + (buf[pos+221] - Shift) << decimation_shifts::pre64, + (buf[pos+222] - Shift) << decimation_shifts::pre64, + (buf[pos+223] - Shift) << decimation_shifts::pre64, &buf2[108]); + m_decimator2.myDecimateInf( - buf[pos+224] << decimation_shifts::pre64, - buf[pos+225] << decimation_shifts::pre64, - buf[pos+226] << decimation_shifts::pre64, - buf[pos+227] << decimation_shifts::pre64, - buf[pos+228] << decimation_shifts::pre64, - buf[pos+229] << decimation_shifts::pre64, - buf[pos+230] << decimation_shifts::pre64, - buf[pos+231] << decimation_shifts::pre64, + (buf[pos+224] - Shift) << decimation_shifts::pre64, + (buf[pos+225] - Shift) << decimation_shifts::pre64, + (buf[pos+226] - Shift) << decimation_shifts::pre64, + (buf[pos+227] - Shift) << decimation_shifts::pre64, + (buf[pos+228] - Shift) << decimation_shifts::pre64, + (buf[pos+229] - Shift) << decimation_shifts::pre64, + (buf[pos+230] - Shift) << decimation_shifts::pre64, + (buf[pos+231] - Shift) << decimation_shifts::pre64, &buf2[112]); + m_decimator2.myDecimateInf( - buf[pos+232] << decimation_shifts::pre64, - buf[pos+233] << decimation_shifts::pre64, - buf[pos+234] << decimation_shifts::pre64, - buf[pos+235] << decimation_shifts::pre64, - buf[pos+236] << decimation_shifts::pre64, - buf[pos+237] << decimation_shifts::pre64, - buf[pos+238] << decimation_shifts::pre64, - buf[pos+239] << decimation_shifts::pre64, + (buf[pos+232] - Shift) << decimation_shifts::pre64, + (buf[pos+233] - Shift) << decimation_shifts::pre64, + (buf[pos+234] - Shift) << decimation_shifts::pre64, + (buf[pos+235] - Shift) << decimation_shifts::pre64, + (buf[pos+236] - Shift) << decimation_shifts::pre64, + (buf[pos+237] - Shift) << decimation_shifts::pre64, + (buf[pos+238] - Shift) << decimation_shifts::pre64, + (buf[pos+239] - Shift) << decimation_shifts::pre64, &buf2[116]); - m_decimator2.myDecimateInf( - buf[pos+240] << decimation_shifts::pre64, - buf[pos+241] << decimation_shifts::pre64, - buf[pos+242] << decimation_shifts::pre64, - buf[pos+243] << decimation_shifts::pre64, - buf[pos+244] << decimation_shifts::pre64, - buf[pos+245] << decimation_shifts::pre64, - buf[pos+246] << decimation_shifts::pre64, - buf[pos+247] << decimation_shifts::pre64, - &buf2[120]); m_decimator2.myDecimateInf( - buf[pos+248] << decimation_shifts::pre64, - buf[pos+249] << decimation_shifts::pre64, - buf[pos+250] << decimation_shifts::pre64, - buf[pos+251] << decimation_shifts::pre64, - buf[pos+252] << decimation_shifts::pre64, - buf[pos+253] << decimation_shifts::pre64, - buf[pos+254] << decimation_shifts::pre64, - buf[pos+255] << decimation_shifts::pre64, + (buf[pos+240] - Shift) << decimation_shifts::pre64, + (buf[pos+241] - Shift) << decimation_shifts::pre64, + (buf[pos+242] - Shift) << decimation_shifts::pre64, + (buf[pos+243] - Shift) << decimation_shifts::pre64, + (buf[pos+244] - Shift) << decimation_shifts::pre64, + (buf[pos+245] - Shift) << decimation_shifts::pre64, + (buf[pos+246] - Shift) << decimation_shifts::pre64, + (buf[pos+247] - Shift) << decimation_shifts::pre64, + &buf2[120]); + + + m_decimator2.myDecimateInf( + (buf[pos+248] - Shift) << decimation_shifts::pre64, + (buf[pos+249] - Shift) << decimation_shifts::pre64, + (buf[pos+250] - Shift) << decimation_shifts::pre64, + (buf[pos+251] - Shift) << decimation_shifts::pre64, + (buf[pos+252] - Shift) << decimation_shifts::pre64, + (buf[pos+253] - Shift) << decimation_shifts::pre64, + (buf[pos+254] - Shift) << decimation_shifts::pre64, + (buf[pos+255] - Shift) << decimation_shifts::pre64, &buf2[124]); m_decimator4.myDecimateSup( @@ -1683,355 +1886,386 @@ void DecimatorsU::decimate64_sup(Samp for (int pos = 0; pos < len - 255; pos += 256) { m_decimator2.myDecimateSup( - buf[pos+0] << decimation_shifts::pre64, - buf[pos+1] << decimation_shifts::pre64, - buf[pos+2] << decimation_shifts::pre64, - buf[pos+3] << decimation_shifts::pre64, - buf[pos+4] << decimation_shifts::pre64, - buf[pos+5] << decimation_shifts::pre64, - buf[pos+6] << decimation_shifts::pre64, - buf[pos+7] << decimation_shifts::pre64, + (buf[pos+0] - Shift) << decimation_shifts::pre64, + (buf[pos+1] - Shift) << decimation_shifts::pre64, + (buf[pos+2] - Shift) << decimation_shifts::pre64, + (buf[pos+3] - Shift) << decimation_shifts::pre64, + (buf[pos+4] - Shift) << decimation_shifts::pre64, + (buf[pos+5] - Shift) << decimation_shifts::pre64, + (buf[pos+6] - Shift) << decimation_shifts::pre64, + (buf[pos+7] - Shift) << decimation_shifts::pre64, &buf2[0]); + m_decimator2.myDecimateSup( - buf[pos+8] << decimation_shifts::pre64, - buf[pos+9] << decimation_shifts::pre64, - buf[pos+10] << decimation_shifts::pre64, - buf[pos+11] << decimation_shifts::pre64, - buf[pos+12] << decimation_shifts::pre64, - buf[pos+13] << decimation_shifts::pre64, - buf[pos+14] << decimation_shifts::pre64, - buf[pos+15] << decimation_shifts::pre64, + (buf[pos+8] - Shift) << decimation_shifts::pre64, + (buf[pos+9] - Shift) << decimation_shifts::pre64, + (buf[pos+10] - Shift) << decimation_shifts::pre64, + (buf[pos+11] - Shift) << decimation_shifts::pre64, + (buf[pos+12] - Shift) << decimation_shifts::pre64, + (buf[pos+13] - Shift) << decimation_shifts::pre64, + (buf[pos+14] - Shift) << decimation_shifts::pre64, + (buf[pos+15] - Shift) << decimation_shifts::pre64, &buf2[4]); + m_decimator2.myDecimateSup( - buf[pos+16] << decimation_shifts::pre64, - buf[pos+17] << decimation_shifts::pre64, - buf[pos+18] << decimation_shifts::pre64, - buf[pos+19] << decimation_shifts::pre64, - buf[pos+20] << decimation_shifts::pre64, - buf[pos+21] << decimation_shifts::pre64, - buf[pos+22] << decimation_shifts::pre64, - buf[pos+23] << decimation_shifts::pre64, + (buf[pos+16] - Shift) << decimation_shifts::pre64, + (buf[pos+17] - Shift) << decimation_shifts::pre64, + (buf[pos+18] - Shift) << decimation_shifts::pre64, + (buf[pos+19] - Shift) << decimation_shifts::pre64, + (buf[pos+20] - Shift) << decimation_shifts::pre64, + (buf[pos+21] - Shift) << decimation_shifts::pre64, + (buf[pos+22] - Shift) << decimation_shifts::pre64, + (buf[pos+23] - Shift) << decimation_shifts::pre64, &buf2[8]); + m_decimator2.myDecimateSup( - buf[pos+24] << decimation_shifts::pre64, - buf[pos+25] << decimation_shifts::pre64, - buf[pos+26] << decimation_shifts::pre64, - buf[pos+27] << decimation_shifts::pre64, - buf[pos+28] << decimation_shifts::pre64, - buf[pos+29] << decimation_shifts::pre64, - buf[pos+30] << decimation_shifts::pre64, - buf[pos+31] << decimation_shifts::pre64, + (buf[pos+24] - Shift) << decimation_shifts::pre64, + (buf[pos+25] - Shift) << decimation_shifts::pre64, + (buf[pos+26] - Shift) << decimation_shifts::pre64, + (buf[pos+27] - Shift) << decimation_shifts::pre64, + (buf[pos+28] - Shift) << decimation_shifts::pre64, + (buf[pos+29] - Shift) << decimation_shifts::pre64, + (buf[pos+30] - Shift) << decimation_shifts::pre64, + (buf[pos+31] - Shift) << decimation_shifts::pre64, &buf2[12]); + m_decimator2.myDecimateSup( - buf[pos+32] << decimation_shifts::pre64, - buf[pos+33] << decimation_shifts::pre64, - buf[pos+34] << decimation_shifts::pre64, - buf[pos+35] << decimation_shifts::pre64, - buf[pos+36] << decimation_shifts::pre64, - buf[pos+37] << decimation_shifts::pre64, - buf[pos+38] << decimation_shifts::pre64, - buf[pos+39] << decimation_shifts::pre64, + (buf[pos+32] - Shift) << decimation_shifts::pre64, + (buf[pos+33] - Shift) << decimation_shifts::pre64, + (buf[pos+34] - Shift) << decimation_shifts::pre64, + (buf[pos+35] - Shift) << decimation_shifts::pre64, + (buf[pos+36] - Shift) << decimation_shifts::pre64, + (buf[pos+37] - Shift) << decimation_shifts::pre64, + (buf[pos+38] - Shift) << decimation_shifts::pre64, + (buf[pos+39] - Shift) << decimation_shifts::pre64, &buf2[16]); + m_decimator2.myDecimateSup( - buf[pos+40] << decimation_shifts::pre64, - buf[pos+41] << decimation_shifts::pre64, - buf[pos+42] << decimation_shifts::pre64, - buf[pos+43] << decimation_shifts::pre64, - buf[pos+44] << decimation_shifts::pre64, - buf[pos+45] << decimation_shifts::pre64, - buf[pos+46] << decimation_shifts::pre64, - buf[pos+47] << decimation_shifts::pre64, + (buf[pos+40] - Shift) << decimation_shifts::pre64, + (buf[pos+41] - Shift) << decimation_shifts::pre64, + (buf[pos+42] - Shift) << decimation_shifts::pre64, + (buf[pos+43] - Shift) << decimation_shifts::pre64, + (buf[pos+44] - Shift) << decimation_shifts::pre64, + (buf[pos+45] - Shift) << decimation_shifts::pre64, + (buf[pos+46] - Shift) << decimation_shifts::pre64, + (buf[pos+47] - Shift) << decimation_shifts::pre64, &buf2[20]); + m_decimator2.myDecimateSup( - buf[pos+48] << decimation_shifts::pre64, - buf[pos+49] << decimation_shifts::pre64, - buf[pos+50] << decimation_shifts::pre64, - buf[pos+51] << decimation_shifts::pre64, - buf[pos+52] << decimation_shifts::pre64, - buf[pos+53] << decimation_shifts::pre64, - buf[pos+54] << decimation_shifts::pre64, - buf[pos+55] << decimation_shifts::pre64, + (buf[pos+48] - Shift) << decimation_shifts::pre64, + (buf[pos+49] - Shift) << decimation_shifts::pre64, + (buf[pos+50] - Shift) << decimation_shifts::pre64, + (buf[pos+51] - Shift) << decimation_shifts::pre64, + (buf[pos+52] - Shift) << decimation_shifts::pre64, + (buf[pos+53] - Shift) << decimation_shifts::pre64, + (buf[pos+54] - Shift) << decimation_shifts::pre64, + (buf[pos+55] - Shift) << decimation_shifts::pre64, &buf2[24]); + m_decimator2.myDecimateSup( - buf[pos+56] << decimation_shifts::pre64, - buf[pos+57] << decimation_shifts::pre64, - buf[pos+58] << decimation_shifts::pre64, - buf[pos+59] << decimation_shifts::pre64, - buf[pos+60] << decimation_shifts::pre64, - buf[pos+61] << decimation_shifts::pre64, - buf[pos+62] << decimation_shifts::pre64, - buf[pos+63] << decimation_shifts::pre64, + (buf[pos+56] - Shift) << decimation_shifts::pre64, + (buf[pos+57] - Shift) << decimation_shifts::pre64, + (buf[pos+58] - Shift) << decimation_shifts::pre64, + (buf[pos+59] - Shift) << decimation_shifts::pre64, + (buf[pos+60] - Shift) << decimation_shifts::pre64, + (buf[pos+61] - Shift) << decimation_shifts::pre64, + (buf[pos+62] - Shift) << decimation_shifts::pre64, + (buf[pos+63] - Shift) << decimation_shifts::pre64, &buf2[28]); + m_decimator2.myDecimateSup( - buf[pos+64] << decimation_shifts::pre64, - buf[pos+65] << decimation_shifts::pre64, - buf[pos+66] << decimation_shifts::pre64, - buf[pos+67] << decimation_shifts::pre64, - buf[pos+68] << decimation_shifts::pre64, - buf[pos+69] << decimation_shifts::pre64, - buf[pos+70] << decimation_shifts::pre64, - buf[pos+71] << decimation_shifts::pre64, + (buf[pos+64] - Shift) << decimation_shifts::pre64, + (buf[pos+65] - Shift) << decimation_shifts::pre64, + (buf[pos+66] - Shift) << decimation_shifts::pre64, + (buf[pos+67] - Shift) << decimation_shifts::pre64, + (buf[pos+68] - Shift) << decimation_shifts::pre64, + (buf[pos+69] - Shift) << decimation_shifts::pre64, + (buf[pos+70] - Shift) << decimation_shifts::pre64, + (buf[pos+71] - Shift) << decimation_shifts::pre64, &buf2[32]); + m_decimator2.myDecimateSup( - buf[pos+72] << decimation_shifts::pre64, - buf[pos+73] << decimation_shifts::pre64, - buf[pos+74] << decimation_shifts::pre64, - buf[pos+75] << decimation_shifts::pre64, - buf[pos+76] << decimation_shifts::pre64, - buf[pos+77] << decimation_shifts::pre64, - buf[pos+78] << decimation_shifts::pre64, - buf[pos+79] << decimation_shifts::pre64, + (buf[pos+72] - Shift) << decimation_shifts::pre64, + (buf[pos+73] - Shift) << decimation_shifts::pre64, + (buf[pos+74] - Shift) << decimation_shifts::pre64, + (buf[pos+75] - Shift) << decimation_shifts::pre64, + (buf[pos+76] - Shift) << decimation_shifts::pre64, + (buf[pos+77] - Shift) << decimation_shifts::pre64, + (buf[pos+78] - Shift) << decimation_shifts::pre64, + (buf[pos+79] - Shift) << decimation_shifts::pre64, &buf2[36]); + m_decimator2.myDecimateSup( - buf[pos+80] << decimation_shifts::pre64, - buf[pos+81] << decimation_shifts::pre64, - buf[pos+82] << decimation_shifts::pre64, - buf[pos+83] << decimation_shifts::pre64, - buf[pos+84] << decimation_shifts::pre64, - buf[pos+85] << decimation_shifts::pre64, - buf[pos+86] << decimation_shifts::pre64, - buf[pos+87] << decimation_shifts::pre64, + (buf[pos+80] - Shift) << decimation_shifts::pre64, + (buf[pos+81] - Shift) << decimation_shifts::pre64, + (buf[pos+82] - Shift) << decimation_shifts::pre64, + (buf[pos+83] - Shift) << decimation_shifts::pre64, + (buf[pos+84] - Shift) << decimation_shifts::pre64, + (buf[pos+85] - Shift) << decimation_shifts::pre64, + (buf[pos+86] - Shift) << decimation_shifts::pre64, + (buf[pos+87] - Shift) << decimation_shifts::pre64, &buf2[40]); + m_decimator2.myDecimateSup( - buf[pos+88] << decimation_shifts::pre64, - buf[pos+89] << decimation_shifts::pre64, - buf[pos+90] << decimation_shifts::pre64, - buf[pos+91] << decimation_shifts::pre64, - buf[pos+92] << decimation_shifts::pre64, - buf[pos+93] << decimation_shifts::pre64, - buf[pos+94] << decimation_shifts::pre64, - buf[pos+95] << decimation_shifts::pre64, + (buf[pos+88] - Shift) << decimation_shifts::pre64, + (buf[pos+89] - Shift) << decimation_shifts::pre64, + (buf[pos+90] - Shift) << decimation_shifts::pre64, + (buf[pos+91] - Shift) << decimation_shifts::pre64, + (buf[pos+92] - Shift) << decimation_shifts::pre64, + (buf[pos+93] - Shift) << decimation_shifts::pre64, + (buf[pos+94] - Shift) << decimation_shifts::pre64, + (buf[pos+95] - Shift) << decimation_shifts::pre64, &buf2[44]); + m_decimator2.myDecimateSup( - buf[pos+96] << decimation_shifts::pre64, - buf[pos+97] << decimation_shifts::pre64, - buf[pos+98] << decimation_shifts::pre64, - buf[pos+99] << decimation_shifts::pre64, - buf[pos+100] << decimation_shifts::pre64, - buf[pos+101] << decimation_shifts::pre64, - buf[pos+102] << decimation_shifts::pre64, - buf[pos+103] << decimation_shifts::pre64, + (buf[pos+96] - Shift) << decimation_shifts::pre64, + (buf[pos+97] - Shift) << decimation_shifts::pre64, + (buf[pos+98] - Shift) << decimation_shifts::pre64, + (buf[pos+99] - Shift) << decimation_shifts::pre64, + (buf[pos+100] - Shift) << decimation_shifts::pre64, + (buf[pos+101] - Shift) << decimation_shifts::pre64, + (buf[pos+102] - Shift) << decimation_shifts::pre64, + (buf[pos+103] - Shift) << decimation_shifts::pre64, &buf2[48]); + m_decimator2.myDecimateSup( - buf[pos+104] << decimation_shifts::pre64, - buf[pos+105] << decimation_shifts::pre64, - buf[pos+106] << decimation_shifts::pre64, - buf[pos+107] << decimation_shifts::pre64, - buf[pos+108] << decimation_shifts::pre64, - buf[pos+109] << decimation_shifts::pre64, - buf[pos+110] << decimation_shifts::pre64, - buf[pos+111] << decimation_shifts::pre64, + (buf[pos+104] - Shift) << decimation_shifts::pre64, + (buf[pos+105] - Shift) << decimation_shifts::pre64, + (buf[pos+106] - Shift) << decimation_shifts::pre64, + (buf[pos+107] - Shift) << decimation_shifts::pre64, + (buf[pos+108] - Shift) << decimation_shifts::pre64, + (buf[pos+109] - Shift) << decimation_shifts::pre64, + (buf[pos+110] - Shift) << decimation_shifts::pre64, + (buf[pos+111] - Shift) << decimation_shifts::pre64, &buf2[52]); + m_decimator2.myDecimateSup( - buf[pos+112] << decimation_shifts::pre64, - buf[pos+113] << decimation_shifts::pre64, - buf[pos+114] << decimation_shifts::pre64, - buf[pos+115] << decimation_shifts::pre64, - buf[pos+116] << decimation_shifts::pre64, - buf[pos+117] << decimation_shifts::pre64, - buf[pos+118] << decimation_shifts::pre64, - buf[pos+119] << decimation_shifts::pre64, + (buf[pos+112] - Shift) << decimation_shifts::pre64, + (buf[pos+113] - Shift) << decimation_shifts::pre64, + (buf[pos+114] - Shift) << decimation_shifts::pre64, + (buf[pos+115] - Shift) << decimation_shifts::pre64, + (buf[pos+116] - Shift) << decimation_shifts::pre64, + (buf[pos+117] - Shift) << decimation_shifts::pre64, + (buf[pos+118] - Shift) << decimation_shifts::pre64, + (buf[pos+119] - Shift) << decimation_shifts::pre64, &buf2[56]); + m_decimator2.myDecimateSup( - buf[pos+120] << decimation_shifts::pre64, - buf[pos+121] << decimation_shifts::pre64, - buf[pos+122] << decimation_shifts::pre64, - buf[pos+123] << decimation_shifts::pre64, - buf[pos+124] << decimation_shifts::pre64, - buf[pos+125] << decimation_shifts::pre64, - buf[pos+126] << decimation_shifts::pre64, - buf[pos+127] << decimation_shifts::pre64, + (buf[pos+120] - Shift) << decimation_shifts::pre64, + (buf[pos+121] - Shift) << decimation_shifts::pre64, + (buf[pos+122] - Shift) << decimation_shifts::pre64, + (buf[pos+123] - Shift) << decimation_shifts::pre64, + (buf[pos+124] - Shift) << decimation_shifts::pre64, + (buf[pos+125] - Shift) << decimation_shifts::pre64, + (buf[pos+126] - Shift) << decimation_shifts::pre64, + (buf[pos+127] - Shift) << decimation_shifts::pre64, &buf2[60]); + m_decimator2.myDecimateSup( - buf[pos+128] << decimation_shifts::pre64, - buf[pos+129] << decimation_shifts::pre64, - buf[pos+130] << decimation_shifts::pre64, - buf[pos+131] << decimation_shifts::pre64, - buf[pos+132] << decimation_shifts::pre64, - buf[pos+133] << decimation_shifts::pre64, - buf[pos+134] << decimation_shifts::pre64, - buf[pos+135] << decimation_shifts::pre64, + (buf[pos+128] - Shift) << decimation_shifts::pre64, + (buf[pos+129] - Shift) << decimation_shifts::pre64, + (buf[pos+130] - Shift) << decimation_shifts::pre64, + (buf[pos+131] - Shift) << decimation_shifts::pre64, + (buf[pos+132] - Shift) << decimation_shifts::pre64, + (buf[pos+133] - Shift) << decimation_shifts::pre64, + (buf[pos+134] - Shift) << decimation_shifts::pre64, + (buf[pos+135] - Shift) << decimation_shifts::pre64, &buf2[64]); + m_decimator2.myDecimateSup( - buf[pos+136] << decimation_shifts::pre64, - buf[pos+137] << decimation_shifts::pre64, - buf[pos+138] << decimation_shifts::pre64, - buf[pos+139] << decimation_shifts::pre64, - buf[pos+140] << decimation_shifts::pre64, - buf[pos+141] << decimation_shifts::pre64, - buf[pos+142] << decimation_shifts::pre64, - buf[pos+143] << decimation_shifts::pre64, + (buf[pos+136] - Shift) << decimation_shifts::pre64, + (buf[pos+137] - Shift) << decimation_shifts::pre64, + (buf[pos+138] - Shift) << decimation_shifts::pre64, + (buf[pos+139] - Shift) << decimation_shifts::pre64, + (buf[pos+140] - Shift) << decimation_shifts::pre64, + (buf[pos+141] - Shift) << decimation_shifts::pre64, + (buf[pos+142] - Shift) << decimation_shifts::pre64, + (buf[pos+143] - Shift) << decimation_shifts::pre64, &buf2[68]); + m_decimator2.myDecimateSup( - buf[pos+144] << decimation_shifts::pre64, - buf[pos+145] << decimation_shifts::pre64, - buf[pos+146] << decimation_shifts::pre64, - buf[pos+147] << decimation_shifts::pre64, - buf[pos+148] << decimation_shifts::pre64, - buf[pos+149] << decimation_shifts::pre64, - buf[pos+150] << decimation_shifts::pre64, - buf[pos+151] << decimation_shifts::pre64, + (buf[pos+144] - Shift) << decimation_shifts::pre64, + (buf[pos+145] - Shift) << decimation_shifts::pre64, + (buf[pos+146] - Shift) << decimation_shifts::pre64, + (buf[pos+147] - Shift) << decimation_shifts::pre64, + (buf[pos+148] - Shift) << decimation_shifts::pre64, + (buf[pos+149] - Shift) << decimation_shifts::pre64, + (buf[pos+150] - Shift) << decimation_shifts::pre64, + (buf[pos+151] - Shift) << decimation_shifts::pre64, &buf2[72]); + m_decimator2.myDecimateSup( - buf[pos+152] << decimation_shifts::pre64, - buf[pos+153] << decimation_shifts::pre64, - buf[pos+154] << decimation_shifts::pre64, - buf[pos+155] << decimation_shifts::pre64, - buf[pos+156] << decimation_shifts::pre64, - buf[pos+157] << decimation_shifts::pre64, - buf[pos+158] << decimation_shifts::pre64, - buf[pos+159] << decimation_shifts::pre64, + (buf[pos+152] - Shift) << decimation_shifts::pre64, + (buf[pos+153] - Shift) << decimation_shifts::pre64, + (buf[pos+154] - Shift) << decimation_shifts::pre64, + (buf[pos+155] - Shift) << decimation_shifts::pre64, + (buf[pos+156] - Shift) << decimation_shifts::pre64, + (buf[pos+157] - Shift) << decimation_shifts::pre64, + (buf[pos+158] - Shift) << decimation_shifts::pre64, + (buf[pos+159] - Shift) << decimation_shifts::pre64, &buf2[76]); + m_decimator2.myDecimateSup( - buf[pos+160] << decimation_shifts::pre64, - buf[pos+161] << decimation_shifts::pre64, - buf[pos+162] << decimation_shifts::pre64, - buf[pos+163] << decimation_shifts::pre64, - buf[pos+164] << decimation_shifts::pre64, - buf[pos+165] << decimation_shifts::pre64, - buf[pos+166] << decimation_shifts::pre64, - buf[pos+167] << decimation_shifts::pre64, + (buf[pos+160] - Shift) << decimation_shifts::pre64, + (buf[pos+161] - Shift) << decimation_shifts::pre64, + (buf[pos+162] - Shift) << decimation_shifts::pre64, + (buf[pos+163] - Shift) << decimation_shifts::pre64, + (buf[pos+164] - Shift) << decimation_shifts::pre64, + (buf[pos+165] - Shift) << decimation_shifts::pre64, + (buf[pos+166] - Shift) << decimation_shifts::pre64, + (buf[pos+167] - Shift) << decimation_shifts::pre64, &buf2[80]); + m_decimator2.myDecimateSup( - buf[pos+168] << decimation_shifts::pre64, - buf[pos+169] << decimation_shifts::pre64, - buf[pos+170] << decimation_shifts::pre64, - buf[pos+171] << decimation_shifts::pre64, - buf[pos+172] << decimation_shifts::pre64, - buf[pos+173] << decimation_shifts::pre64, - buf[pos+174] << decimation_shifts::pre64, - buf[pos+175] << decimation_shifts::pre64, + (buf[pos+168] - Shift) << decimation_shifts::pre64, + (buf[pos+169] - Shift) << decimation_shifts::pre64, + (buf[pos+170] - Shift) << decimation_shifts::pre64, + (buf[pos+171] - Shift) << decimation_shifts::pre64, + (buf[pos+172] - Shift) << decimation_shifts::pre64, + (buf[pos+173] - Shift) << decimation_shifts::pre64, + (buf[pos+174] - Shift) << decimation_shifts::pre64, + (buf[pos+175] - Shift) << decimation_shifts::pre64, &buf2[84]); + m_decimator2.myDecimateSup( - buf[pos+176] << decimation_shifts::pre64, - buf[pos+177] << decimation_shifts::pre64, - buf[pos+178] << decimation_shifts::pre64, - buf[pos+179] << decimation_shifts::pre64, - buf[pos+180] << decimation_shifts::pre64, - buf[pos+181] << decimation_shifts::pre64, - buf[pos+182] << decimation_shifts::pre64, - buf[pos+183] << decimation_shifts::pre64, + (buf[pos+176] - Shift) << decimation_shifts::pre64, + (buf[pos+177] - Shift) << decimation_shifts::pre64, + (buf[pos+178] - Shift) << decimation_shifts::pre64, + (buf[pos+179] - Shift) << decimation_shifts::pre64, + (buf[pos+180] - Shift) << decimation_shifts::pre64, + (buf[pos+181] - Shift) << decimation_shifts::pre64, + (buf[pos+182] - Shift) << decimation_shifts::pre64, + (buf[pos+183] - Shift) << decimation_shifts::pre64, &buf2[88]); + m_decimator2.myDecimateSup( - buf[pos+184] << decimation_shifts::pre64, - buf[pos+185] << decimation_shifts::pre64, - buf[pos+186] << decimation_shifts::pre64, - buf[pos+187] << decimation_shifts::pre64, - buf[pos+188] << decimation_shifts::pre64, - buf[pos+189] << decimation_shifts::pre64, - buf[pos+190] << decimation_shifts::pre64, - buf[pos+191] << decimation_shifts::pre64, + (buf[pos+184] - Shift) << decimation_shifts::pre64, + (buf[pos+185] - Shift) << decimation_shifts::pre64, + (buf[pos+186] - Shift) << decimation_shifts::pre64, + (buf[pos+187] - Shift) << decimation_shifts::pre64, + (buf[pos+188] - Shift) << decimation_shifts::pre64, + (buf[pos+189] - Shift) << decimation_shifts::pre64, + (buf[pos+190] - Shift) << decimation_shifts::pre64, + (buf[pos+191] - Shift) << decimation_shifts::pre64, &buf2[92]); + m_decimator2.myDecimateSup( - buf[pos+192] << decimation_shifts::pre64, - buf[pos+193] << decimation_shifts::pre64, - buf[pos+194] << decimation_shifts::pre64, - buf[pos+195] << decimation_shifts::pre64, - buf[pos+196] << decimation_shifts::pre64, - buf[pos+197] << decimation_shifts::pre64, - buf[pos+198] << decimation_shifts::pre64, - buf[pos+199] << decimation_shifts::pre64, + (buf[pos+192] - Shift) << decimation_shifts::pre64, + (buf[pos+193] - Shift) << decimation_shifts::pre64, + (buf[pos+194] - Shift) << decimation_shifts::pre64, + (buf[pos+195] - Shift) << decimation_shifts::pre64, + (buf[pos+196] - Shift) << decimation_shifts::pre64, + (buf[pos+197] - Shift) << decimation_shifts::pre64, + (buf[pos+198] - Shift) << decimation_shifts::pre64, + (buf[pos+199] - Shift) << decimation_shifts::pre64, &buf2[96]); + m_decimator2.myDecimateSup( - buf[pos+200] << decimation_shifts::pre64, - buf[pos+201] << decimation_shifts::pre64, - buf[pos+202] << decimation_shifts::pre64, - buf[pos+203] << decimation_shifts::pre64, - buf[pos+204] << decimation_shifts::pre64, - buf[pos+205] << decimation_shifts::pre64, - buf[pos+206] << decimation_shifts::pre64, - buf[pos+207] << decimation_shifts::pre64, + (buf[pos+200] - Shift) << decimation_shifts::pre64, + (buf[pos+201] - Shift) << decimation_shifts::pre64, + (buf[pos+202] - Shift) << decimation_shifts::pre64, + (buf[pos+203] - Shift) << decimation_shifts::pre64, + (buf[pos+204] - Shift) << decimation_shifts::pre64, + (buf[pos+205] - Shift) << decimation_shifts::pre64, + (buf[pos+206] - Shift) << decimation_shifts::pre64, + (buf[pos+207] - Shift) << decimation_shifts::pre64, &buf2[100]); + m_decimator2.myDecimateSup( - buf[pos+208] << decimation_shifts::pre64, - buf[pos+209] << decimation_shifts::pre64, - buf[pos+210] << decimation_shifts::pre64, - buf[pos+211] << decimation_shifts::pre64, - buf[pos+212] << decimation_shifts::pre64, - buf[pos+213] << decimation_shifts::pre64, - buf[pos+214] << decimation_shifts::pre64, - buf[pos+215] << decimation_shifts::pre64, + (buf[pos+208] - Shift) << decimation_shifts::pre64, + (buf[pos+209] - Shift) << decimation_shifts::pre64, + (buf[pos+210] - Shift) << decimation_shifts::pre64, + (buf[pos+211] - Shift) << decimation_shifts::pre64, + (buf[pos+212] - Shift) << decimation_shifts::pre64, + (buf[pos+213] - Shift) << decimation_shifts::pre64, + (buf[pos+214] - Shift) << decimation_shifts::pre64, + (buf[pos+215] - Shift) << decimation_shifts::pre64, &buf2[104]); + m_decimator2.myDecimateSup( - buf[pos+216] << decimation_shifts::pre64, - buf[pos+217] << decimation_shifts::pre64, - buf[pos+218] << decimation_shifts::pre64, - buf[pos+219] << decimation_shifts::pre64, - buf[pos+220] << decimation_shifts::pre64, - buf[pos+221] << decimation_shifts::pre64, - buf[pos+222] << decimation_shifts::pre64, - buf[pos+223] << decimation_shifts::pre64, + (buf[pos+216] - Shift) << decimation_shifts::pre64, + (buf[pos+217] - Shift) << decimation_shifts::pre64, + (buf[pos+218] - Shift) << decimation_shifts::pre64, + (buf[pos+219] - Shift) << decimation_shifts::pre64, + (buf[pos+220] - Shift) << decimation_shifts::pre64, + (buf[pos+221] - Shift) << decimation_shifts::pre64, + (buf[pos+222] - Shift) << decimation_shifts::pre64, + (buf[pos+223] - Shift) << decimation_shifts::pre64, &buf2[108]); + m_decimator2.myDecimateSup( - buf[pos+224] << decimation_shifts::pre64, - buf[pos+225] << decimation_shifts::pre64, - buf[pos+226] << decimation_shifts::pre64, - buf[pos+227] << decimation_shifts::pre64, - buf[pos+228] << decimation_shifts::pre64, - buf[pos+229] << decimation_shifts::pre64, - buf[pos+230] << decimation_shifts::pre64, - buf[pos+231] << decimation_shifts::pre64, + (buf[pos+224] - Shift) << decimation_shifts::pre64, + (buf[pos+225] - Shift) << decimation_shifts::pre64, + (buf[pos+226] - Shift) << decimation_shifts::pre64, + (buf[pos+227] - Shift) << decimation_shifts::pre64, + (buf[pos+228] - Shift) << decimation_shifts::pre64, + (buf[pos+229] - Shift) << decimation_shifts::pre64, + (buf[pos+230] - Shift) << decimation_shifts::pre64, + (buf[pos+231] - Shift) << decimation_shifts::pre64, &buf2[112]); + m_decimator2.myDecimateSup( - buf[pos+232] << decimation_shifts::pre64, - buf[pos+233] << decimation_shifts::pre64, - buf[pos+234] << decimation_shifts::pre64, - buf[pos+235] << decimation_shifts::pre64, - buf[pos+236] << decimation_shifts::pre64, - buf[pos+237] << decimation_shifts::pre64, - buf[pos+238] << decimation_shifts::pre64, - buf[pos+239] << decimation_shifts::pre64, + (buf[pos+232] - Shift) << decimation_shifts::pre64, + (buf[pos+233] - Shift) << decimation_shifts::pre64, + (buf[pos+234] - Shift) << decimation_shifts::pre64, + (buf[pos+235] - Shift) << decimation_shifts::pre64, + (buf[pos+236] - Shift) << decimation_shifts::pre64, + (buf[pos+237] - Shift) << decimation_shifts::pre64, + (buf[pos+238] - Shift) << decimation_shifts::pre64, + (buf[pos+239] - Shift) << decimation_shifts::pre64, &buf2[116]); - m_decimator2.myDecimateSup( - buf[pos+240] << decimation_shifts::pre64, - buf[pos+241] << decimation_shifts::pre64, - buf[pos+242] << decimation_shifts::pre64, - buf[pos+243] << decimation_shifts::pre64, - buf[pos+244] << decimation_shifts::pre64, - buf[pos+245] << decimation_shifts::pre64, - buf[pos+246] << decimation_shifts::pre64, - buf[pos+247] << decimation_shifts::pre64, - &buf2[120]); m_decimator2.myDecimateSup( - buf[pos+248] << decimation_shifts::pre64, - buf[pos+249] << decimation_shifts::pre64, - buf[pos+250] << decimation_shifts::pre64, - buf[pos+251] << decimation_shifts::pre64, - buf[pos+252] << decimation_shifts::pre64, - buf[pos+253] << decimation_shifts::pre64, - buf[pos+254] << decimation_shifts::pre64, - buf[pos+255] << decimation_shifts::pre64, + (buf[pos+240] - Shift) << decimation_shifts::pre64, + (buf[pos+241] - Shift) << decimation_shifts::pre64, + (buf[pos+242] - Shift) << decimation_shifts::pre64, + (buf[pos+243] - Shift) << decimation_shifts::pre64, + (buf[pos+244] - Shift) << decimation_shifts::pre64, + (buf[pos+245] - Shift) << decimation_shifts::pre64, + (buf[pos+246] - Shift) << decimation_shifts::pre64, + (buf[pos+247] - Shift) << decimation_shifts::pre64, + &buf2[120]); + + + m_decimator2.myDecimateSup( + (buf[pos+248] - Shift) << decimation_shifts::pre64, + (buf[pos+249] - Shift) << decimation_shifts::pre64, + (buf[pos+250] - Shift) << decimation_shifts::pre64, + (buf[pos+251] - Shift) << decimation_shifts::pre64, + (buf[pos+252] - Shift) << decimation_shifts::pre64, + (buf[pos+253] - Shift) << decimation_shifts::pre64, + (buf[pos+254] - Shift) << decimation_shifts::pre64, + (buf[pos+255] - Shift) << decimation_shifts::pre64, &buf2[124]); m_decimator4.myDecimateInf( @@ -2176,14 +2410,14 @@ void DecimatorsU::decimate2_cen(Sampl for (int pos = 0; pos < len - 7; pos += 8) { m_decimator2.myDecimateCen( - buf[pos+0] << decimation_shifts::pre2, - buf[pos+1] << decimation_shifts::pre2, - buf[pos+2] << decimation_shifts::pre2, - buf[pos+3] << decimation_shifts::pre2, - buf[pos+4] << decimation_shifts::pre2, - buf[pos+5] << decimation_shifts::pre2, - buf[pos+6] << decimation_shifts::pre2, - buf[pos+7] << decimation_shifts::pre2, + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, &buf2[0]); (**it).setReal(buf2[0] >> decimation_shifts::post2); @@ -2204,25 +2438,25 @@ void DecimatorsU::decimate4_cen(Sampl for (int pos = 0; pos < len - 15; pos += 16) { m_decimator2.myDecimateCen( - buf[pos+0] << decimation_shifts::pre4, - buf[pos+1] << decimation_shifts::pre4, - buf[pos+2] << decimation_shifts::pre4, - buf[pos+3] << decimation_shifts::pre4, - buf[pos+4] << decimation_shifts::pre4, - buf[pos+5] << decimation_shifts::pre4, - buf[pos+6] << decimation_shifts::pre4, - buf[pos+7] << decimation_shifts::pre4, + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, &buf2[0]); m_decimator2.myDecimateCen( - buf[pos+8] << decimation_shifts::pre4, - buf[pos+9] << decimation_shifts::pre4, - buf[pos+10] << decimation_shifts::pre4, - buf[pos+11] << decimation_shifts::pre4, - buf[pos+12] << decimation_shifts::pre4, - buf[pos+13] << decimation_shifts::pre4, - buf[pos+14] << decimation_shifts::pre4, - buf[pos+15] << decimation_shifts::pre4, + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, &buf2[4]); m_decimator4.myDecimateCen( From 1549ecaa0faeeb4371904ba48552e51ceba9a153 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 13 May 2018 08:55:14 +0200 Subject: [PATCH 401/956] New PLL with complex signal input and w, zeta, K parameters --- sdrbase/CMakeLists.txt | 2 + sdrbase/dsp/phaselockcomplex.cpp | 137 +++++++++++++++++++++++++++++++ sdrbase/dsp/phaselockcomplex.h | 70 ++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 sdrbase/dsp/phaselockcomplex.cpp create mode 100644 sdrbase/dsp/phaselockcomplex.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 9a55e6dd8..0459c898c 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -40,6 +40,7 @@ set(sdrbase_SOURCES dsp/nco.cpp dsp/ncof.cpp dsp/phaselock.cpp + dsp/phaselockcomplex.cpp dsp/projector.cpp dsp/samplesinkfifo.cpp dsp/samplesourcefifo.cpp @@ -146,6 +147,7 @@ set(sdrbase_HEADERS dsp/ncof.h dsp/phasediscri.h dsp/phaselock.h + dsp/phaselockcomplex.h dsp/projector.h dsp/recursivefilters.h dsp/samplesinkfifo.h diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp new file mode 100644 index 000000000..1fedb512d --- /dev/null +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -0,0 +1,137 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixes filter registers saturation // +// // +// 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 "phaselockcomplex.h" + +PhaseLockComplex::PhaseLockComplex() : + m_a1(1.0), + m_a2(1.0), + m_b0(1.0), + m_b1(1.0), + m_b2(1.0), + m_v0(0.0), + m_v1(0.0), + m_v2(0.0), + m_deltaPhi(0.0), + m_phiHatLast(0.0), + m_phiHat(0.0), + m_y(1.0, 0.0), + m_yRe(1.0), + m_yIm(0.0), + m_freq(0.0) +{ +} + +void PhaseLockComplex::computeCoefficients(Real wn, Real zeta, Real K) +{ + double t1 = K/(wn*wn); // + double t2 = 2*zeta/wn - 1/K; // + + double b0 = 2*K*(1.+t2/2.0f); + double b1 = 2*K*2.; + double b2 = 2*K*(1.-t2/2.0f); + + double a0 = 1 + t1/2.0f; + double a1 = -t1; + double a2 = -1 + t1/2.0f; + + qDebug("PhaseLockComplex::computeCoefficients: b_raw: %f %f %f", b0, b1, b2); + qDebug("PhaseLockComplex::computeCoefficients: a_raw: %f %f %f", a0, a1, a2); + + m_b0 = b0 / a0; + m_b1 = b1 / a0; + m_b2 = b2 / a0; + + // a0 = 1.0 is implied + m_a1 = a1 / a0; + m_a2 = a2 / a0; + + qDebug("PhaseLockComplex::computeCoefficients: b: %f %f %f", m_b0, m_b1, m_b2); + qDebug("PhaseLockComplex::computeCoefficients: a: 1.0 %f %f", m_a1, m_a2); + + reset(); +} + +void PhaseLockComplex::reset() +{ + // reset filter accumulators and phase + m_v0 = 0.0f; + m_v1 = 0.0f; + m_v2 = 0.0f; + m_deltaPhi = 0.0f; + m_phiHatLast = 0.0f; + m_phiHat = 0.0f; + m_y.real(1.0); + m_y.real(0.0); + m_yRe = 1.0f; + m_yIm = 0.0f; + m_freq = 0.0f; +} + +void PhaseLockComplex::feed(float re, float im) +{ + m_yRe = cos(m_phiHat); + m_yIm = sin(m_phiHat); + m_y.real(m_yRe); + m_y.imag(m_yIm); + std::complex x(re, im); + m_deltaPhi = std::arg(x * std::conj(m_y)); + + // advance buffer + m_v2 = m_v1; // shift center register to upper register + m_v1 = m_v0; // shift lower register to center register + + // compute new lower register + m_v0 = m_deltaPhi - m_v1*m_a1 - m_v2*m_a2; + + // compute new output + m_phiHat = m_v0*m_b0 + m_v1*m_b1 + m_v2*m_b2; + + // prevent saturation + if (m_phiHat > 2.0*M_PI) + { + m_v0 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_v1 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_v2 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_phiHat -= 2.0*M_PI; + } + + if (m_phiHat < -2.0*M_PI) + { + m_v0 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_v1 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_v2 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_phiHat += 2.0*M_PI; + } + + m_freq = (m_phiHat - m_phiHatLast) / (2.0*M_PI); + + if (m_freq < -1.0f) { + m_freq += 2.0f; + } else if (m_freq > 1.0f) { + m_freq -= 2.0f; + } + + m_phiHatLast = m_phiHat; +} + + diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h new file mode 100644 index 000000000..de5479c51 --- /dev/null +++ b/sdrbase/dsp/phaselockcomplex.h @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixes filter registers saturation // +// // +// 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 SDRBASE_DSP_PHASELOCKCOMPLEX_H_ +#define SDRBASE_DSP_PHASELOCKCOMPLEX_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +/** General purpose Phase-locked loop using complex analytic signal input. */ +class SDRBASE_API PhaseLockComplex +{ +public: + PhaseLockComplex(); + /** Compute loop filter parameters (active PI design) + * \param wn PLL bandwidth relative to Nyquist frequency + * \param zeta PLL damping factor + * \param K PLL loop gain + * */ + void computeCoefficients(Real wn, Real zeta, Real K); + void reset(); + void feed(float re, float im); + const std::complex& getComplex() const { return m_y; } + float getReal() const { return m_yRe; } + float getImag() const { return m_yIm; } + bool locked() const { return (m_deltaPhi > -0.1) && (m_deltaPhi < 0.1); } + float getFrequency() const { return m_freq; } + float getDeltaPhi() const { return m_deltaPhi; } + float getPhiHat() const { return m_phiHat; } + +private: + // a0 = 1 is implied + float m_a1; + float m_a2; + float m_b0; + float m_b1; + float m_b2; + float m_v0; + float m_v1; + float m_v2; + float m_deltaPhi; + float m_phiHatLast; + float m_phiHat; + std::complex m_y; + float m_yRe; + float m_yIm; + float m_freq; + +}; + + + +#endif /* SDRBASE_DSP_PHASELOCKCOMPLEX_H_ */ From e9f64a05f2c44bfda6395c98ccf3c5c23f719245 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 13 May 2018 17:27:24 +0200 Subject: [PATCH 402/956] AM demod: basic synchronous AM detection option --- plugins/channelrx/demodam/amdemod.cpp | 10 +++++ plugins/channelrx/demodam/amdemod.h | 43 +++++++++++++++++-- plugins/channelrx/demodam/amdemodgui.cpp | 20 +++++++++ plugins/channelrx/demodam/amdemodgui.h | 1 + plugins/channelrx/demodam/amdemodgui.ui | 25 +++++++++++ plugins/channelrx/demodam/amdemodplugin.cpp | 2 +- plugins/channelrx/demodam/amdemodsettings.cpp | 3 ++ plugins/channelrx/demodam/amdemodsettings.h | 1 + sdrbase/dsp/fftfilt.cxx | 5 ++- sdrbase/dsp/fftfilt.h | 2 +- 10 files changed, 106 insertions(+), 6 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index fafa7fb69..b93284476 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -66,6 +66,7 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); + DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024); applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); applySettings(m_settings, true); @@ -74,6 +75,10 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); + + m_pllFilt.create(101, m_audioSampleRate, 500.0); + m_pll.computeCoefficients(0.05, 0.707, 1000); + m_syncAMBuffIndex = 0; } AMDemod::~AMDemod() @@ -83,6 +88,7 @@ AMDemod::~AMDemod() m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete DSBFilter; } void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) @@ -233,6 +239,8 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); m_audioFifo.setSize(sampleRate); m_squelchDelayLine.resize(sampleRate/5); + DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate); + m_pllFilt.create(101, sampleRate, 500.0); m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; @@ -273,6 +281,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) << " m_audioMute: " << settings.m_audioMute << " m_bandpassEnable: " << settings.m_bandpassEnable << " m_audioDeviceName: " << settings.m_audioDeviceName + << " m_pll: " << settings.m_pll << " force: " << force; if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || @@ -283,6 +292,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) m_interpolatorDistanceRemain = 0; m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); + DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate); m_settingsMutex.unlock(); } diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index 9c32eb8ff..e9e380898 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -27,6 +27,9 @@ #include "util/movingaverage.h" #include "dsp/agc.h" #include "dsp/bandpass.h" +#include "dsp/lowpass.h" +#include "dsp/phaselockcomplex.h" +#include "dsp/fftfilt.h" #include "audio/audiofifo.h" #include "util/message.h" #include "util/doublebufferfifo.h" @@ -118,6 +121,7 @@ public: uint32_t getAudioSampleRate() const { return m_audioSampleRate; } double getMagSq() const { return m_magsq; } bool getSquelchOpen() const { return m_squelchOpen; } + bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } void getMagSqLevels(double& avg, double& peak, int& nbSamples) { @@ -165,6 +169,11 @@ private: MovingAverageUtil m_movingAverage; SimpleAGC<4096> m_volumeAGC; Bandpass m_bandpass; + Lowpass > m_pllFilt; + PhaseLockComplex m_pll; + fftfilt* DSBFilter; + Real m_syncAMBuff[2*1024]; + uint32_t m_syncAMBuffIndex; AudioVector m_audioBuffer; uint32_t m_audioBufferFill; @@ -217,9 +226,37 @@ private: if (m_squelchOpen && !m_settings.m_audioMute) { - Real demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); - m_volumeAGC.feed(demod); - demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); + Real demod; + + if (m_settings.m_pll) + { + std::complex s(re, im); + s = m_pllFilt.filter(s); + m_pll.feed(s.real(), s.imag()); + float yr = re * m_pll.getImag() - im * m_pll.getReal(); + float yi = re * m_pll.getReal() + im * m_pll.getImag(); + + fftfilt::cmplx *sideband; + std::complex cs(yr, yi); + int n_out = DSBFilter->runDSB(cs, &sideband, false); + + for (int i = 0; i < n_out; i++) + { + m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); + m_syncAMBuffIndex = 0; + } + + m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; + demod = m_syncAMBuff[m_syncAMBuffIndex++]*0.7*(SDR_RX_SCALEF/602.0f); + m_volumeAGC.feed(demod); + demod /= (10.0*m_volumeAGC.getValue()); + } + else + { + demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); + m_volumeAGC.feed(demod); + demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); + } if (m_settings.m_bandpassEnable) { diff --git a/plugins/channelrx/demodam/amdemodgui.cpp b/plugins/channelrx/demodam/amdemodgui.cpp index d9488350d..4cc4faf8d 100644 --- a/plugins/channelrx/demodam/amdemodgui.cpp +++ b/plugins/channelrx/demodam/amdemodgui.cpp @@ -139,6 +139,16 @@ void AMDemodGUI::on_deltaFrequency_changed(qint64 value) applySettings(); } +void AMDemodGUI::on_pll_toggled(bool checked) +{ + if (!checked) { + ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + + m_settings.m_pll = checked; + applySettings(); +} + void AMDemodGUI::on_bandpassEnable_toggled(bool checked) { m_settings.m_bandpassEnable = checked; @@ -302,6 +312,7 @@ void AMDemodGUI::displaySettings() ui->audioMute->setChecked(m_settings.m_audioMute); ui->bandpassEnable->setChecked(m_settings.m_bandpassEnable); + ui->pll->setChecked(m_settings.m_pll); blockApplySettings(false); } @@ -359,6 +370,15 @@ void AMDemodGUI::tick() } } + if (m_settings.m_pll) + { + if (m_amDemod->getPllLocked()) { + ui->pll->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + } + m_tickCount++; } diff --git a/plugins/channelrx/demodam/amdemodgui.h b/plugins/channelrx/demodam/amdemodgui.h index 14efddf93..464474a89 100644 --- a/plugins/channelrx/demodam/amdemodgui.h +++ b/plugins/channelrx/demodam/amdemodgui.h @@ -65,6 +65,7 @@ private: private slots: void on_deltaFrequency_changed(qint64 value); + void on_pll_toggled(bool checked); void on_bandpassEnable_toggled(bool checked); void on_rfBW_valueChanged(int value); void on_volume_valueChanged(int value); diff --git a/plugins/channelrx/demodam/amdemodgui.ui b/plugins/channelrx/demodam/amdemodgui.ui index b2d4fc427..6efd1cb09 100644 --- a/plugins/channelrx/demodam/amdemodgui.ui +++ b/plugins/channelrx/demodam/amdemodgui.ui @@ -129,6 +129,31 @@ + + + + Qt::Vertical + + + + + + + PLL for synchronous AM + + + + + + + :/unlocked.png + :/locked.png:/unlocked.png + + + true + + + diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index 1cffd61c3..3760131c7 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("3.14.5"), + QString("3.14.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodam/amdemodsettings.cpp b/plugins/channelrx/demodam/amdemodsettings.cpp index 53d7f8832..fb5192cb0 100644 --- a/plugins/channelrx/demodam/amdemodsettings.cpp +++ b/plugins/channelrx/demodam/amdemodsettings.cpp @@ -38,6 +38,7 @@ void AMDemodSettings::resetToDefaults() m_rgbColor = QColor(255, 255, 0).rgb(); m_title = "AM Demodulator"; m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; + m_pll = false; } QByteArray AMDemodSettings::serialize() const @@ -56,6 +57,7 @@ QByteArray AMDemodSettings::serialize() const s.writeBool(8, m_bandpassEnable); s.writeString(9, m_title); s.writeString(11, m_audioDeviceName); + s.writeBool(12, m_pll); return s.final(); } @@ -93,6 +95,7 @@ bool AMDemodSettings::deserialize(const QByteArray& data) d.readBool(8, &m_bandpassEnable, false); d.readString(9, &m_title, "AM Demodulator"); d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + d.readBool(12, &m_pll, false); return true; } diff --git a/plugins/channelrx/demodam/amdemodsettings.h b/plugins/channelrx/demodam/amdemodsettings.h index 54cb97a5b..166875394 100644 --- a/plugins/channelrx/demodam/amdemodsettings.h +++ b/plugins/channelrx/demodam/amdemodsettings.h @@ -33,6 +33,7 @@ struct AMDemodSettings QString m_title; Serializable *m_channelMarker; QString m_audioDeviceName; + bool m_pll; AMDemodSettings(); void resetToDefaults(); diff --git a/sdrbase/dsp/fftfilt.cxx b/sdrbase/dsp/fftfilt.cxx index fe6af7ab8..edae8deaa 100644 --- a/sdrbase/dsp/fftfilt.cxx +++ b/sdrbase/dsp/fftfilt.cxx @@ -298,7 +298,7 @@ int fftfilt::runSSB(const cmplx & in, cmplx **out, bool usb, bool getDC) } // Version for double sideband. You have to double the FFT size used for SSB. -int fftfilt::runDSB(const cmplx & in, cmplx **out) +int fftfilt::runDSB(const cmplx & in, cmplx **out, bool getDC) { data[inptr++] = in; if (inptr < flen2) @@ -312,6 +312,9 @@ int fftfilt::runDSB(const cmplx & in, cmplx **out) data[flen2 + i] *= filter[flen2 + i]; } + // get or reject DC component + data[0] = getDC ? data[0] : 0; + // in-place FFT: freqdata overwritten with filtered timedata fft->InverseComplexFFT(data); diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index b52d2bd83..a9db21f11 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -32,7 +32,7 @@ public: int noFilt(const cmplx& in, cmplx **out); int runFilt(const cmplx& in, cmplx **out); int runSSB(const cmplx& in, cmplx **out, bool usb, bool getDC = true); - int runDSB(const cmplx& in, cmplx **out); + int runDSB(const cmplx& in, cmplx **out, bool getDC = true); int runAsym(const cmplx & in, cmplx **out, bool usb); //!< Asymmetrical fitering can be used for vestigial sideband protected: From 21840c5dd3cf1e401e8b53ddf6b428b1189598dd Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 13 May 2018 22:30:50 +0200 Subject: [PATCH 403/956] AM demod: synchronous AM: implemented sidebands selection --- debian/changelog | 4 +- .../channelrx/chanalyzerng/chanalyzerng.cpp | 9 +- plugins/channelrx/chanalyzerng/chanalyzerng.h | 17 ++-- .../chanalyzerng/chanalyzernggui.cpp | 19 +++- .../channelrx/chanalyzerng/chanalyzernggui.h | 1 + plugins/channelrx/demodam/CMakeLists.txt | 3 + plugins/channelrx/demodam/amdemod.cpp | 29 +++++- plugins/channelrx/demodam/amdemod.h | 22 ++++- plugins/channelrx/demodam/amdemodgui.cpp | 50 ++++++++++ plugins/channelrx/demodam/amdemodgui.h | 10 +- plugins/channelrx/demodam/amdemodgui.ui | 18 ++++ plugins/channelrx/demodam/amdemodsettings.cpp | 4 + plugins/channelrx/demodam/amdemodsettings.h | 8 ++ plugins/channelrx/demodam/amdemodssb.ui | 96 +++++++++++++++++++ .../channelrx/demodam/amdemodssbdialog.cpp | 41 ++++++++ plugins/channelrx/demodam/amdemodssbdialog.h | 43 +++++++++ sdrbase/dsp/agc.h | 7 ++ sdrbase/dsp/recursivefilters.cpp | 10 +- sdrbase/dsp/recursivefilters.h | 1 + 19 files changed, 372 insertions(+), 20 deletions(-) create mode 100644 plugins/channelrx/demodam/amdemodssb.ui create mode 100644 plugins/channelrx/demodam/amdemodssbdialog.cpp create mode 100644 plugins/channelrx/demodam/amdemodssbdialog.h diff --git a/debian/changelog b/debian/changelog index a71e6207f..3e95d948d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,10 @@ sdrangel (3.14.7-1) unstable; urgency=medium * ChanelAnalyzerNG: added PLL option + * RTL-SDR: fixed inf/sup decimators + * AM demod: syncrhronous AM detection option - -- Edouard Griffiths, F4EXB Sun, 13 May 2018 20:14:18 +0200 + -- Edouard Griffiths, F4EXB Sun, 20 May 2018 20:14:18 +0200 sdrangel (3.14.6-1) unstable; urgency=medium diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 63f654cfc..67d464ab3 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -35,7 +35,6 @@ const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzerNG"; ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_deviceAPI(deviceAPI), - m_pll(0,0.05,0.01), m_sampleSink(0), m_settingsMutex(QMutex::Recursive) { @@ -50,6 +49,7 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : m_interpolatorDistanceRemain = 0.0f; SSBFilter = new fftfilt(m_config.m_LowCutoff / m_config.m_inputSampleRate, m_config.m_Bandwidth / m_config.m_inputSampleRate, ssbFftLen); DSBFilter = new fftfilt(m_config.m_Bandwidth / m_config.m_inputSampleRate, 2*ssbFftLen); + m_pll.computeCoefficients(0.05f, 0.707f, 1000.0f); // bandwidth, damping factor, loop gain apply(true); @@ -249,6 +249,13 @@ void ChannelAnalyzerNG::apply(bool force) m_settingsMutex.unlock(); } + if (m_running.m_pll != m_config.m_pll || force) + { + if (m_config.m_pll) { + m_pll.reset(); + } + } + m_running.m_frequency = m_config.m_frequency; m_running.m_channelSampleRate = m_config.m_channelSampleRate; m_running.m_inputSampleRate = m_config.m_inputSampleRate; diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index f9f194bd2..d65786762 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -25,7 +25,7 @@ #include "dsp/interpolator.h" #include "dsp/ncof.h" #include "dsp/fftfilt.h" -#include "dsp/phaselock.h" +#include "dsp/phaselockcomplex.h" #include "audio/audiofifo.h" #include "util/message.h" @@ -148,6 +148,9 @@ public: int getChannelSampleRate() const { return m_running.m_channelSampleRate; } double getMagSq() const { return m_magsq; } bool isPllLocked() const { return m_running.m_pll && m_pll.locked(); } + Real getPllFrequency() const { return m_pll.getFrequency(); } + Real getPllDeltaPhase() const { return m_pll.getDeltaPhi(); } + Real getPllPhase() const { return m_pll.getPhiHat(); } virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); @@ -203,7 +206,7 @@ private: bool m_useInterpolator; NCOF m_nco; - SimplePhaseLock m_pll; + PhaseLockComplex m_pll; Interpolator m_interpolator; Real m_interpolatorDistance; Real m_interpolatorDistanceRemain; @@ -247,11 +250,13 @@ private: if (m_running.m_pll) { - Real ncopll[2]; - m_pll.process(re, im, ncopll); + m_pll.feed(re, im); - Real mixI = m_sum.real() * ncopll[0] - m_sum.imag() * ncopll[1]; - Real mixQ = m_sum.real() * ncopll[1] + m_sum.imag() * ncopll[0]; + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + Real mixI = m_sum.real() * m_pll.getImag() - m_sum.imag() * m_pll.getReal(); + Real mixQ = m_sum.real() * m_pll.getReal() + m_sum.imag() * m_pll.getImag(); +// Real mixI = m_pll.getReal() * SDR_RX_SCALED; +// Real mixQ = m_pll.getImag() * SDR_RX_SCALED; if (m_running.m_ssb & !m_usb) { // invert spectrum for LSB diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 14fba25a3..90dbd44e0 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -243,6 +243,15 @@ void ChannelAnalyzerNGGUI::tick() } else { ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); } + + if (ui->pll->isChecked()) + { + int fHz = round(m_channelAnalyzer->getPllFrequency()*m_rate); + ui->pll->setToolTip(tr("PLL lock (f:%1 Hz e:%2 rad p:%3 rad)") + .arg(fHz) + .arg(m_channelAnalyzer->getPllDeltaPhase()) + .arg(m_channelAnalyzer->getPllPhase())); + } } void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) @@ -257,8 +266,13 @@ void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) } } -void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked __attribute__((unused))) +void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) { + if (!checked && m_usePll) { + ui->pll->setToolTip("PLL lock"); + } + + m_usePll = checked; applySettings(); } @@ -399,7 +413,8 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de m_channelMarker(this), m_doApplySettings(true), m_rate(6000), - m_spanLog2(0) + m_spanLog2(0), + m_usePll(false) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h index c30a6087d..0bc962e92 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.h @@ -66,6 +66,7 @@ private: bool m_doApplySettings; int m_rate; //!< sample rate after final in-channel decimation (spanlog2) int m_spanLog2; + bool m_usePll; MovingAverageUtil m_channelPowerDbAvg; ChannelAnalyzerNG* m_channelAnalyzer; diff --git a/plugins/channelrx/demodam/CMakeLists.txt b/plugins/channelrx/demodam/CMakeLists.txt index 0465d3345..adbc2d1e4 100644 --- a/plugins/channelrx/demodam/CMakeLists.txt +++ b/plugins/channelrx/demodam/CMakeLists.txt @@ -7,6 +7,7 @@ set(am_SOURCES amdemodgui.cpp amdemodsettings.cpp amdemodplugin.cpp + amdemodssbdialog.cpp ) set(am_HEADERS @@ -14,10 +15,12 @@ set(am_HEADERS amdemodgui.h amdemodsettings.h amdemodplugin.h + amdemodssbdialog.h ) set(am_FORMS amdemodgui.ui + amdemodssb.ui ) include_directories( diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index b93284476..d7a94dd99 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -67,6 +67,7 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024); + SSBFilter = new fftfilt(0.0f, m_settings.m_rfBandwidth / m_audioSampleRate, 1024); applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); applySettings(m_settings, true); @@ -89,6 +90,7 @@ AMDemod::~AMDemod() delete m_threadedChannelizer; delete m_channelizer; delete DSBFilter; + delete SSBFilter; } void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) @@ -233,6 +235,7 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_inputMessageQueue.push(channelConfigMsg); m_settingsMutex.lock(); + m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); m_interpolatorDistanceRemain = 0; m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; @@ -241,8 +244,14 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_squelchDelayLine.resize(sampleRate/5); DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate); m_pllFilt.create(101, sampleRate, 500.0); - m_settingsMutex.unlock(); + if (m_settings.m_pll) { + m_volumeAGC.resizeNew(sampleRate, 0.003); + } else { + m_volumeAGC.resizeNew(sampleRate/10, 0.003); + } + + m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; } @@ -282,6 +291,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) << " m_bandpassEnable: " << settings.m_bandpassEnable << " m_audioDeviceName: " << settings.m_audioDeviceName << " m_pll: " << settings.m_pll + << " m_syncAMOperation: " << (int) settings.m_syncAMOperation << " force: " << force; if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || @@ -314,6 +324,23 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) } } + if ((m_settings.m_pll != settings.m_pll) || force) + { + if (settings.m_pll) + { + m_volumeAGC.resizeNew(m_audioSampleRate/4, 0.003); + m_syncAMBuffIndex = 0; + } + else + { + m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003); + } + } + + if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) { + m_syncAMBuffIndex = 0; + } + m_settings = settings; } diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index e9e380898..ff53b2ed3 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -167,11 +167,12 @@ private: int m_magsqCount; MovingAverageUtil m_movingAverage; - SimpleAGC<4096> m_volumeAGC; + SimpleAGC<4800> m_volumeAGC; Bandpass m_bandpass; Lowpass > m_pllFilt; PhaseLockComplex m_pll; fftfilt* DSBFilter; + fftfilt* SSBFilter; Real m_syncAMBuff[2*1024]; uint32_t m_syncAMBuffIndex; @@ -238,16 +239,29 @@ private: fftfilt::cmplx *sideband; std::complex cs(yr, yi); - int n_out = DSBFilter->runDSB(cs, &sideband, false); + int n_out; + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + n_out = DSBFilter->runDSB(cs, &sideband, false); + } else { + n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); + } for (int i = 0; i < n_out; i++) { - m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f; + } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { + m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); + } else { + m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); + } + m_syncAMBuffIndex = 0; } m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; - demod = m_syncAMBuff[m_syncAMBuffIndex++]*0.7*(SDR_RX_SCALEF/602.0f); + demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f); m_volumeAGC.feed(demod); demod /= (10.0*m_volumeAGC.getValue()); } diff --git a/plugins/channelrx/demodam/amdemodgui.cpp b/plugins/channelrx/demodam/amdemodgui.cpp index 4cc4faf8d..2c0eff5a4 100644 --- a/plugins/channelrx/demodam/amdemodgui.cpp +++ b/plugins/channelrx/demodam/amdemodgui.cpp @@ -18,6 +18,7 @@ #include #include "amdemodgui.h" +#include "amdemodssbdialog.h" #include "device/devicesourceapi.h" #include "device/deviceuiset.h" @@ -149,6 +150,12 @@ void AMDemodGUI::on_pll_toggled(bool checked) applySettings(); } +void AMDemodGUI::on_ssb_toggled(bool checked) +{ + m_settings.m_syncAMOperation = checked ? m_samUSB ? AMDemodSettings::SyncAMUSB : AMDemodSettings::SyncAMLSB : AMDemodSettings::SyncAMDSB; + applySettings(); +} + void AMDemodGUI::on_bandpassEnable_toggled(bool checked) { m_settings.m_bandpassEnable = checked; @@ -215,6 +222,7 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS m_channelMarker(this), m_doApplySettings(true), m_squelchOpen(false), + m_samUSB(true), m_tickCount(0) { ui->setupUi(this); @@ -230,6 +238,9 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + CRightClickEnabler *samSidebandRightClickEnabler = new CRightClickEnabler(ui->ssb); + connect(samSidebandRightClickEnabler, SIGNAL(rightClick()), this, SLOT(samSSBSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -254,6 +265,11 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_iconDSBUSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::Off); + m_iconDSBUSB.addPixmap(QPixmap("://usb.png"), QIcon::Normal, QIcon::On); + m_iconDSBLSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::Off); + m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::On); + displaySettings(); applySettings(true); } @@ -314,6 +330,20 @@ void AMDemodGUI::displaySettings() ui->bandpassEnable->setChecked(m_settings.m_bandpassEnable); ui->pll->setChecked(m_settings.m_pll); + if (m_settings.m_pll) { + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMLSB) { + m_samUSB = false; + ui->ssb->setIcon(m_iconDSBLSB); + } else { + m_samUSB = true; + ui->ssb->setIcon(m_iconDSBUSB); + } + } + else + { + ui->ssb->setIcon(m_iconDSBUSB); + } + blockApplySettings(false); } @@ -340,6 +370,26 @@ void AMDemodGUI::audioSelect() } } +void AMDemodGUI::samSSBSelect() +{ + AMDemodSSBDialog ssbSelect(m_samUSB); + ssbSelect.exec(); + + ui->ssb->setIcon(ssbSelect.isUsb() ? m_iconDSBUSB : m_iconDSBLSB); + + if (ssbSelect.isUsb() != m_samUSB) + { + qDebug("AMDemodGUI::samSSBSelect: %s", ssbSelect.isUsb() ? "usb" : "lsb"); + m_samUSB = ssbSelect.isUsb(); + + if (m_settings.m_syncAMOperation != AMDemodSettings::SyncAMDSB) + { + m_settings.m_syncAMOperation = m_samUSB ? AMDemodSettings::SyncAMUSB : AMDemodSettings::SyncAMLSB; + applySettings(); + } + } +} + void AMDemodGUI::tick() { double magsqAvg, magsqPeak; diff --git a/plugins/channelrx/demodam/amdemodgui.h b/plugins/channelrx/demodam/amdemodgui.h index 464474a89..4849f1196 100644 --- a/plugins/channelrx/demodam/amdemodgui.h +++ b/plugins/channelrx/demodam/amdemodgui.h @@ -1,7 +1,9 @@ #ifndef INCLUDE_AMDEMODGUI_H #define INCLUDE_AMDEMODGUI_H -#include +#include + +#include "plugin/plugininstancegui.h" #include "gui/rollupwidget.h" #include "dsp/channelmarker.h" #include "dsp/movingaverage.h" @@ -50,9 +52,13 @@ private: AMDemod* m_amDemod; bool m_squelchOpen; + bool m_samUSB; uint32_t m_tickCount; MessageQueue m_inputMessageQueue; + QIcon m_iconDSBUSB; + QIcon m_iconDSBLSB; + explicit AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~AMDemodGUI(); @@ -66,6 +72,7 @@ private: private slots: void on_deltaFrequency_changed(qint64 value); void on_pll_toggled(bool checked); + void on_ssb_toggled(bool checked); void on_bandpassEnable_toggled(bool checked); void on_rfBW_valueChanged(int value); void on_volume_valueChanged(int value); @@ -75,6 +82,7 @@ private slots: void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); void audioSelect(); + void samSSBSelect(); void tick(); }; diff --git a/plugins/channelrx/demodam/amdemodgui.ui b/plugins/channelrx/demodam/amdemodgui.ui index 6efd1cb09..c62321c77 100644 --- a/plugins/channelrx/demodam/amdemodgui.ui +++ b/plugins/channelrx/demodam/amdemodgui.ui @@ -154,6 +154,24 @@ + + + + Synchronous AM SSB/DSB toggle right click to select side band + + + + + + + :/dsb.png + :/usb.png:/dsb.png + + + true + + + diff --git a/plugins/channelrx/demodam/amdemodsettings.cpp b/plugins/channelrx/demodam/amdemodsettings.cpp index fb5192cb0..cf3c36ae5 100644 --- a/plugins/channelrx/demodam/amdemodsettings.cpp +++ b/plugins/channelrx/demodam/amdemodsettings.cpp @@ -39,6 +39,7 @@ void AMDemodSettings::resetToDefaults() m_title = "AM Demodulator"; m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; m_pll = false; + m_syncAMOperation = SyncAMDSB; } QByteArray AMDemodSettings::serialize() const @@ -58,6 +59,7 @@ QByteArray AMDemodSettings::serialize() const s.writeString(9, m_title); s.writeString(11, m_audioDeviceName); s.writeBool(12, m_pll); + s.writeS32(13, (int) m_syncAMOperation); return s.final(); } @@ -96,6 +98,8 @@ bool AMDemodSettings::deserialize(const QByteArray& data) d.readString(9, &m_title, "AM Demodulator"); d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); d.readBool(12, &m_pll, false); + d.readS32(13, &tmp, 0); + m_syncAMOperation = tmp < 0 ? SyncAMDSB : tmp > 2 ? SyncAMDSB : (SyncAMOperation) tmp; return true; } diff --git a/plugins/channelrx/demodam/amdemodsettings.h b/plugins/channelrx/demodam/amdemodsettings.h index 166875394..3aaebd230 100644 --- a/plugins/channelrx/demodam/amdemodsettings.h +++ b/plugins/channelrx/demodam/amdemodsettings.h @@ -23,6 +23,13 @@ class Serializable; struct AMDemodSettings { + enum SyncAMOperation + { + SyncAMDSB, + SyncAMUSB, + SyncAMLSB + }; + qint32 m_inputFrequencyOffset; Real m_rfBandwidth; Real m_squelch; @@ -34,6 +41,7 @@ struct AMDemodSettings Serializable *m_channelMarker; QString m_audioDeviceName; bool m_pll; + SyncAMOperation m_syncAMOperation; AMDemodSettings(); void resetToDefaults(); diff --git a/plugins/channelrx/demodam/amdemodssb.ui b/plugins/channelrx/demodam/amdemodssb.ui new file mode 100644 index 000000000..5e4b20a22 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodssb.ui @@ -0,0 +1,96 @@ + + + AMDemodSSBDialog + + + + 0 + 0 + 199 + 115 + + + + + Liberation Sans + 9 + + + + Dialog + + + + + + SAM sideband selection + + + + + + LSB + + + + + + + USB + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + buttonBox + + + + + buttonBox + accepted() + AMDemodSSBDialog + accept() + + + 257 + 194 + + + 157 + 203 + + + + + buttonBox + rejected() + AMDemodSSBDialog + reject() + + + 314 + 194 + + + 286 + 203 + + + + + diff --git a/plugins/channelrx/demodam/amdemodssbdialog.cpp b/plugins/channelrx/demodam/amdemodssbdialog.cpp new file mode 100644 index 000000000..d7c42ed70 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodssbdialog.cpp @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "amdemodssbdialog.h" +#include "ui_amdemodssb.h" + +AMDemodSSBDialog::AMDemodSSBDialog(bool usb, QWidget* parent) : + QDialog(parent), + ui(new Ui::AMDemodSSBDialog), + m_usb(usb) +{ + ui->setupUi(this); + ui->usb->setChecked(usb); + ui->lsb->setChecked(!usb); +} + +AMDemodSSBDialog::~AMDemodSSBDialog() +{ + delete ui; +} + +void AMDemodSSBDialog::accept() +{ + m_usb = ui->usb->isChecked(); + QDialog::accept(); +} + + diff --git a/plugins/channelrx/demodam/amdemodssbdialog.h b/plugins/channelrx/demodam/amdemodssbdialog.h new file mode 100644 index 000000000..1ce527488 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodssbdialog.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_CHANNELRX_DEMODAM_AMDEMODSSBDIALOG_H_ +#define PLUGINS_CHANNELRX_DEMODAM_AMDEMODSSBDIALOG_H_ + +#include + +namespace Ui { + class AMDemodSSBDialog; +} + +class AMDemodSSBDialog : public QDialog +{ + Q_OBJECT +public: + explicit AMDemodSSBDialog(bool usb, QWidget* parent = 0); + ~AMDemodSSBDialog(); + + bool isUsb() const { return m_usb; } + +private: + Ui::AMDemodSSBDialog* ui; + bool m_usb; + +private slots: + void accept(); +}; + +#endif /* PLUGINS_CHANNELRX_DEMODAM_AMDEMODSSBDIALOG_H_ */ diff --git a/sdrbase/dsp/agc.h b/sdrbase/dsp/agc.h index 4e0e2939c..196b523af 100644 --- a/sdrbase/dsp/agc.h +++ b/sdrbase/dsp/agc.h @@ -89,6 +89,13 @@ public: m_moving_average.resize(AvgSize, initial); } + void resizeNew(uint32_t newSize, Real initial, Real cutoff=0, Real clip=0) + { + m_cutoff = cutoff; + m_clip = clip; + m_moving_average.resize(newSize, initial); + } + void fill(double value) { m_moving_average.fill(value); diff --git a/sdrbase/dsp/recursivefilters.cpp b/sdrbase/dsp/recursivefilters.cpp index 2749bfab2..b63385e07 100644 --- a/sdrbase/dsp/recursivefilters.cpp +++ b/sdrbase/dsp/recursivefilters.cpp @@ -18,11 +18,12 @@ #include "recursivefilters.h" #undef M_PI -#define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 SecondOrderRecursiveFilter::SecondOrderRecursiveFilter(float samplingFrequency, float centerFrequency, float r) : m_r(r), - m_frequencyRatio(centerFrequency/samplingFrequency) + m_frequencyRatio(centerFrequency/samplingFrequency), + m_f(cos(2.0*M_PI*(m_frequencyRatio))) { init(); } @@ -33,6 +34,7 @@ SecondOrderRecursiveFilter::~SecondOrderRecursiveFilter() void SecondOrderRecursiveFilter::setFrequencies(float samplingFrequency, float centerFrequency) { m_frequencyRatio = centerFrequency / samplingFrequency; + m_f = cos(2.0*M_PI*m_frequencyRatio); init(); } @@ -44,7 +46,7 @@ void SecondOrderRecursiveFilter::setR(float r) short SecondOrderRecursiveFilter::run(short sample) { - m_v[0] = ((1.0f - m_r) * (float) sample) + (2.0f * m_r * cos(2.0*M_PI*m_frequencyRatio) * m_v[1]) - (m_r * m_r * m_v[2]); + m_v[0] = ((1.0f - m_r) * (float) sample) + (2.0f * m_r * m_f * m_v[1]) - (m_r * m_r * m_v[2]); float y = m_v[0] - m_v[2]; m_v[2] = m_v[1]; m_v[1] = m_v[0]; @@ -54,7 +56,7 @@ short SecondOrderRecursiveFilter::run(short sample) float SecondOrderRecursiveFilter::run(float sample) { - m_v[0] = ((1.0f - m_r) * sample) + (2.0f * m_r * cos(2.0*M_PI*m_frequencyRatio) * m_v[1]) - (m_r * m_r * m_v[2]); + m_v[0] = ((1.0f - m_r) * sample) + (2.0f * m_r * m_f * m_v[1]) - (m_r * m_r * m_v[2]); float y = m_v[0] - m_v[2]; m_v[2] = m_v[1]; m_v[1] = m_v[0]; diff --git a/sdrbase/dsp/recursivefilters.h b/sdrbase/dsp/recursivefilters.h index 1c457df22..cd17b6dea 100644 --- a/sdrbase/dsp/recursivefilters.h +++ b/sdrbase/dsp/recursivefilters.h @@ -39,6 +39,7 @@ private: float m_r; float m_frequencyRatio; + float m_f; float m_v[3]; }; From 85d4d8029ca70f0c6ccc0bb0ec79b707c4bf5ac4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 14 May 2018 00:34:37 +0200 Subject: [PATCH 404/956] AM demod: use MagAGC for synchronous AM --- plugins/channelrx/demodam/amdemod.cpp | 7 +- plugins/channelrx/demodam/amdemod.h | 633 +++++++++++++------------- 2 files changed, 330 insertions(+), 310 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index d7a94dd99..a48561fc0 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -54,6 +54,7 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_magsqPeak(0.0f), m_magsqCount(0), m_volumeAGC(0.003), + m_syncAMAGC(12000, 0.1, 1e-2), m_audioFifo(48000), m_settingsMutex(QMutex::Recursive) { @@ -68,6 +69,8 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024); SSBFilter = new fftfilt(0.0f, m_settings.m_rfBandwidth / m_audioSampleRate, 1024); + m_syncAMAGC.setThresholdEnable(false); + m_syncAMAGC.resize(12000, 6000, 0.1); applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); applySettings(m_settings, true); @@ -251,6 +254,8 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_volumeAGC.resizeNew(sampleRate/10, 0.003); } + m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1); + m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; } @@ -308,7 +313,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) if ((m_settings.m_squelch != settings.m_squelch) || force) { - m_squelchLevel = pow(10.0, settings.m_squelch / 10.0); + m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch); } if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index ff53b2ed3..a70f4070e 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -1,309 +1,324 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 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 // -// // -// 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_AMDEMOD_H -#define INCLUDE_AMDEMOD_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/nco.h" -#include "dsp/interpolator.h" -#include "util/movingaverage.h" -#include "dsp/agc.h" -#include "dsp/bandpass.h" -#include "dsp/lowpass.h" -#include "dsp/phaselockcomplex.h" -#include "dsp/fftfilt.h" -#include "audio/audiofifo.h" -#include "util/message.h" -#include "util/doublebufferfifo.h" -#include "amdemodsettings.h" - -class DeviceSourceAPI; -class DownChannelizer; -class ThreadedBasebandSampleSink; - -class AMDemod : public BasebandSampleSink, public ChannelSinkAPI { - Q_OBJECT -public: - class MsgConfigureAMDemod : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const AMDemodSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureAMDemod* create(const AMDemodSettings& settings, bool force) - { - return new MsgConfigureAMDemod(settings, force); - } - - private: - AMDemodSettings m_settings; - bool m_force; - - MsgConfigureAMDemod(const AMDemodSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - AMDemod(DeviceSourceAPI *deviceAPI); - ~AMDemod(); - 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 = m_settings.m_title; } - virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } - - 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); - - virtual int webapiReportGet( - SWGSDRangel::SWGChannelReport& response, - QString& errorMessage); - - uint32_t getAudioSampleRate() const { return m_audioSampleRate; } - double getMagSq() const { return m_magsq; } - bool getSquelchOpen() const { return m_squelchOpen; } - bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } - - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - enum RateState { - RSInitialFill, - RSRunning - }; - - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; - AMDemodSettings m_settings; - uint32_t m_audioSampleRate; - bool m_running; - - NCO m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - - Real m_squelchLevel; - uint32_t m_squelchCount; - bool m_squelchOpen; - DoubleBufferFIFO m_squelchDelayLine; - double m_magsq; - double m_magsqSum; - double m_magsqPeak; - int m_magsqCount; - - MovingAverageUtil m_movingAverage; - SimpleAGC<4800> m_volumeAGC; - Bandpass m_bandpass; - Lowpass > m_pllFilt; - PhaseLockComplex m_pll; - fftfilt* DSBFilter; - fftfilt* SSBFilter; - Real m_syncAMBuff[2*1024]; - uint32_t m_syncAMBuffIndex; - - AudioVector m_audioBuffer; - uint32_t m_audioBufferFill; - AudioFifo m_audioFifo; - - static const int m_udpBlockSize; - - QMutex m_settingsMutex; - - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const AMDemodSettings& settings, bool force = false); - void applyAudioSampleRate(int sampleRate); - void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings); - void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); - - void processOneSample(Complex &ci) - { - Real re = ci.real() / SDR_RX_SCALEF; - Real im = ci.imag() / SDR_RX_SCALEF; - Real magsq = re*re + im*im; - m_movingAverage(magsq); - m_magsq = m_movingAverage.asDouble(); - m_magsqSum += magsq; - - if (magsq > m_magsqPeak) - { - m_magsqPeak = magsq; - } - - m_magsqCount++; - - m_squelchDelayLine.write(magsq); - - if (m_magsq < m_squelchLevel) - { - if (m_squelchCount > 0) { - m_squelchCount--; - } - } - else - { - if (m_squelchCount < m_audioSampleRate / 10) { - m_squelchCount++; - } - } - - qint16 sample; - - m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); - - if (m_squelchOpen && !m_settings.m_audioMute) - { - Real demod; - - if (m_settings.m_pll) - { - std::complex s(re, im); - s = m_pllFilt.filter(s); - m_pll.feed(s.real(), s.imag()); - float yr = re * m_pll.getImag() - im * m_pll.getReal(); - float yi = re * m_pll.getReal() + im * m_pll.getImag(); - - fftfilt::cmplx *sideband; - std::complex cs(yr, yi); - int n_out; - - if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { - n_out = DSBFilter->runDSB(cs, &sideband, false); - } else { - n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); - } - - for (int i = 0; i < n_out; i++) - { - if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { - m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f; - } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { - m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); - } else { - m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); - } - - m_syncAMBuffIndex = 0; - } - - m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; - demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f); - m_volumeAGC.feed(demod); - demod /= (10.0*m_volumeAGC.getValue()); - } - else - { - demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); - m_volumeAGC.feed(demod); - demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); - } - - if (m_settings.m_bandpassEnable) - { - demod = m_bandpass.filter(demod); - demod /= 301.0f; - } - - Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); - sample = demod * attack * (m_audioSampleRate/24) * m_settings.m_volume; - } - else - { - sample = 0; - } - - m_audioBuffer[m_audioBufferFill].l = sample; - m_audioBuffer[m_audioBufferFill].r = sample; - ++m_audioBufferFill; - - if (m_audioBufferFill >= m_audioBuffer.size()) - { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); - - if (res != m_audioBufferFill) - { - qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); - m_audioFifo.clear(); - } - - m_audioBufferFill = 0; - } - } - -}; - -#endif // INCLUDE_AMDEMOD_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 // +// // +// 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_AMDEMOD_H +#define INCLUDE_AMDEMOD_H + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "util/movingaverage.h" +#include "dsp/agc.h" +#include "dsp/bandpass.h" +#include "dsp/lowpass.h" +#include "dsp/phaselockcomplex.h" +#include "dsp/fftfilt.h" +#include "audio/audiofifo.h" +#include "util/message.h" +#include "util/doublebufferfifo.h" +#include "util/stepfunctions.h" + +#include "amdemodsettings.h" + +class DeviceSourceAPI; +class DownChannelizer; +class ThreadedBasebandSampleSink; + +class AMDemod : public BasebandSampleSink, public ChannelSinkAPI { + Q_OBJECT +public: + class MsgConfigureAMDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const AMDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureAMDemod* create(const AMDemodSettings& settings, bool force) + { + return new MsgConfigureAMDemod(settings, force); + } + + private: + AMDemodSettings m_settings; + bool m_force; + + MsgConfigureAMDemod(const AMDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + AMDemod(DeviceSourceAPI *deviceAPI); + ~AMDemod(); + 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 = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } + + 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } + double getMagSq() const { return m_magsq; } + bool getSquelchOpen() const { return m_squelchOpen; } + bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; + peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + enum RateState { + RSInitialFill, + RSRunning + }; + + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + + int m_inputSampleRate; + int m_inputFrequencyOffset; + AMDemodSettings m_settings; + uint32_t m_audioSampleRate; + bool m_running; + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + Real m_squelchLevel; + uint32_t m_squelchCount; + bool m_squelchOpen; + DoubleBufferFIFO m_squelchDelayLine; + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + + MovingAverageUtil m_movingAverage; + SimpleAGC<4800> m_volumeAGC; + Bandpass m_bandpass; + Lowpass > m_pllFilt; + PhaseLockComplex m_pll; + fftfilt* DSBFilter; + fftfilt* SSBFilter; + Real m_syncAMBuff[2*1024]; + uint32_t m_syncAMBuffIndex; + MagAGC m_syncAMAGC; + + AudioVector m_audioBuffer; + uint32_t m_audioBufferFill; + AudioFifo m_audioFifo; + + static const int m_udpBlockSize; + + QMutex m_settingsMutex; + + void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); + void applySettings(const AMDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + + void processOneSample(Complex &ci) + { + Real re = ci.real() / SDR_RX_SCALEF; + Real im = ci.imag() / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + + m_magsqCount++; + + m_squelchDelayLine.write(magsq); + + if (m_magsq < m_squelchLevel) + { + if (m_squelchCount > 0) { + m_squelchCount--; + } + } + else + { + if (m_squelchCount < m_audioSampleRate / 10) { + m_squelchCount++; + } + } + + qint16 sample; + + m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); + + if (m_squelchOpen && !m_settings.m_audioMute) + { + Real demod; + + if (m_settings.m_pll) + { + std::complex s(re, im); + s = m_pllFilt.filter(s); + m_pll.feed(s.real(), s.imag()); + float yr = re * m_pll.getImag() - im * m_pll.getReal(); + float yi = re * m_pll.getReal() + im * m_pll.getImag(); + + fftfilt::cmplx *sideband; + std::complex cs(yr, yi); + int n_out; + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + n_out = DSBFilter->runDSB(cs, &sideband, false); + } else { + n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); + } + + for (int i = 0; i < n_out; i++) + { + float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]); + fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue(); + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + m_syncAMBuff[i] = (z.real() + z.imag())/2.0f; + } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { + m_syncAMBuff[i] = (z.real() + z.imag()); + } else { + m_syncAMBuff[i] = (z.real() + z.imag()); + } + +// if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f; +// } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); +// } else { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); +// } + + m_syncAMBuffIndex = 0; + } + + m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; + demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico +// demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f); +// m_volumeAGC.feed(demod); +// demod /= (10.0*m_volumeAGC.getValue()); + } + else + { + demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); + m_volumeAGC.feed(demod); + demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); + } + + if (m_settings.m_bandpassEnable) + { + demod = m_bandpass.filter(demod); + demod /= 301.0f; + } + + Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); + sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume; + } + else + { + sample = 0; + } + + m_audioBuffer[m_audioBufferFill].l = sample; + m_audioBuffer[m_audioBufferFill].r = sample; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + + if (res != m_audioBufferFill) + { + qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); + m_audioFifo.clear(); + } + + m_audioBufferFill = 0; + } + } + +}; + +#endif // INCLUDE_AMDEMOD_H From b5d6d56cc97f20ed70c8d078d9e765893ce68cde Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 14 May 2018 00:44:43 +0200 Subject: [PATCH 405/956] AM demod: re-integrate processOneSample method in .cpp --- plugins/channelrx/demodam/amdemod.cpp | 128 +++++++++++++++++++++++++ plugins/channelrx/demodam/amdemod.h | 130 +------------------------- 2 files changed, 130 insertions(+), 128 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index a48561fc0..4a9611666 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -32,8 +32,10 @@ #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" +#include "dsp/fftfilt.h" #include "device/devicesourceapi.h" #include "util/db.h" +#include "util/stepfunctions.h" MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureAMDemod, Message) MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureChannelizer, Message) @@ -147,6 +149,132 @@ void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector m_settingsMutex.unlock(); } +void AMDemod::processOneSample(Complex &ci) +{ + Real re = ci.real() / SDR_RX_SCALEF; + Real im = ci.imag() / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + + m_magsqCount++; + + m_squelchDelayLine.write(magsq); + + if (m_magsq < m_squelchLevel) + { + if (m_squelchCount > 0) { + m_squelchCount--; + } + } + else + { + if (m_squelchCount < m_audioSampleRate / 10) { + m_squelchCount++; + } + } + + qint16 sample; + + m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); + + if (m_squelchOpen && !m_settings.m_audioMute) + { + Real demod; + + if (m_settings.m_pll) + { + std::complex s(re, im); + s = m_pllFilt.filter(s); + m_pll.feed(s.real(), s.imag()); + float yr = re * m_pll.getImag() - im * m_pll.getReal(); + float yi = re * m_pll.getReal() + im * m_pll.getImag(); + + fftfilt::cmplx *sideband; + std::complex cs(yr, yi); + int n_out; + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + n_out = DSBFilter->runDSB(cs, &sideband, false); + } else { + n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); + } + + for (int i = 0; i < n_out; i++) + { + float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]); + fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue(); + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + m_syncAMBuff[i] = (z.real() + z.imag()); + } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { + m_syncAMBuff[i] = (z.real() + z.imag()); + } else { + m_syncAMBuff[i] = (z.real() + z.imag()); + } + +// if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f; +// } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); +// } else { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); +// } + + m_syncAMBuffIndex = 0; + } + + m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; + demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico +// demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f); +// m_volumeAGC.feed(demod); +// demod /= (10.0*m_volumeAGC.getValue()); + } + else + { + demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); + m_volumeAGC.feed(demod); + demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); + } + + if (m_settings.m_bandpassEnable) + { + demod = m_bandpass.filter(demod); + demod /= 301.0f; + } + + Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); + sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume; + } + else + { + sample = 0; + } + + m_audioBuffer[m_audioBufferFill].l = sample; + m_audioBuffer[m_audioBufferFill].r = sample; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + + if (res != m_audioBufferFill) + { + qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); + m_audioFifo.clear(); + } + + m_audioBufferFill = 0; + } +} + void AMDemod::start() { qDebug("AMDemod::start"); diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index a70f4070e..bd971b9ad 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -29,17 +29,16 @@ #include "dsp/bandpass.h" #include "dsp/lowpass.h" #include "dsp/phaselockcomplex.h" -#include "dsp/fftfilt.h" #include "audio/audiofifo.h" #include "util/message.h" #include "util/doublebufferfifo.h" -#include "util/stepfunctions.h" #include "amdemodsettings.h" class DeviceSourceAPI; class DownChannelizer; class ThreadedBasebandSampleSink; +class fftfilt; class AMDemod : public BasebandSampleSink, public ChannelSinkAPI { Q_OBJECT @@ -193,132 +192,7 @@ private: void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); - void processOneSample(Complex &ci) - { - Real re = ci.real() / SDR_RX_SCALEF; - Real im = ci.imag() / SDR_RX_SCALEF; - Real magsq = re*re + im*im; - m_movingAverage(magsq); - m_magsq = m_movingAverage.asDouble(); - m_magsqSum += magsq; - - if (magsq > m_magsqPeak) - { - m_magsqPeak = magsq; - } - - m_magsqCount++; - - m_squelchDelayLine.write(magsq); - - if (m_magsq < m_squelchLevel) - { - if (m_squelchCount > 0) { - m_squelchCount--; - } - } - else - { - if (m_squelchCount < m_audioSampleRate / 10) { - m_squelchCount++; - } - } - - qint16 sample; - - m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); - - if (m_squelchOpen && !m_settings.m_audioMute) - { - Real demod; - - if (m_settings.m_pll) - { - std::complex s(re, im); - s = m_pllFilt.filter(s); - m_pll.feed(s.real(), s.imag()); - float yr = re * m_pll.getImag() - im * m_pll.getReal(); - float yi = re * m_pll.getReal() + im * m_pll.getImag(); - - fftfilt::cmplx *sideband; - std::complex cs(yr, yi); - int n_out; - - if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { - n_out = DSBFilter->runDSB(cs, &sideband, false); - } else { - n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); - } - - for (int i = 0; i < n_out; i++) - { - float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]); - fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue(); - - if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { - m_syncAMBuff[i] = (z.real() + z.imag())/2.0f; - } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { - m_syncAMBuff[i] = (z.real() + z.imag()); - } else { - m_syncAMBuff[i] = (z.real() + z.imag()); - } - -// if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { -// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f; -// } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { -// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); -// } else { -// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); -// } - - m_syncAMBuffIndex = 0; - } - - m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; - demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico -// demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f); -// m_volumeAGC.feed(demod); -// demod /= (10.0*m_volumeAGC.getValue()); - } - else - { - demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); - m_volumeAGC.feed(demod); - demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); - } - - if (m_settings.m_bandpassEnable) - { - demod = m_bandpass.filter(demod); - demod /= 301.0f; - } - - Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); - sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume; - } - else - { - sample = 0; - } - - m_audioBuffer[m_audioBufferFill].l = sample; - m_audioBuffer[m_audioBufferFill].r = sample; - ++m_audioBufferFill; - - if (m_audioBufferFill >= m_audioBuffer.size()) - { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); - - if (res != m_audioBufferFill) - { - qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); - m_audioFifo.clear(); - } - - m_audioBufferFill = 0; - } - } - + void processOneSample(Complex &ci); }; #endif // INCLUDE_AMDEMOD_H From 68c50769fe2992de24b44bf469301000a8271c10 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 14 May 2018 19:14:30 +0200 Subject: [PATCH 406/956] New PLL: implemented trick on the phase comparator for M-ary PSK operation --- sdrbase/dsp/phaselockcomplex.cpp | 28 +++++++++++++++++++++++++--- sdrbase/dsp/phaselockcomplex.h | 13 +++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index 1fedb512d..b6572ef58 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -3,7 +3,9 @@ // written by Edouard Griffiths // // // // See: http://liquidsdr.org/blog/pll-howto/ // -// Fixes filter registers saturation // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // // // // 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 // @@ -37,7 +39,8 @@ PhaseLockComplex::PhaseLockComplex() : m_y(1.0, 0.0), m_yRe(1.0), m_yIm(0.0), - m_freq(0.0) + m_freq(0.0), + m_pskOrder(1) { } @@ -71,6 +74,11 @@ void PhaseLockComplex::computeCoefficients(Real wn, Real zeta, Real K) reset(); } +void PhaseLockComplex::setPskOrder(unsigned int order) +{ + m_pskOrder = order > 0 ? order : 1; +} + void PhaseLockComplex::reset() { // reset filter accumulators and phase @@ -96,6 +104,11 @@ void PhaseLockComplex::feed(float re, float im) std::complex x(re, im); m_deltaPhi = std::arg(x * std::conj(m_y)); + // bring phase 0 on any of the PSK symbols + if (m_pskOrder > 1) { + m_deltaPhi = normalizeAngle(m_pskOrder*m_deltaPhi); + } + // advance buffer m_v2 = m_v1; // shift center register to upper register m_v1 = m_v0; // shift lower register to center register @@ -134,4 +147,13 @@ void PhaseLockComplex::feed(float re, float im) m_phiHatLast = m_phiHat; } - +float PhaseLockComplex::normalizeAngle(float angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; +} diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index de5479c51..ed5d7633e 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -3,7 +3,9 @@ // written by Edouard Griffiths // // // // See: http://liquidsdr.org/blog/pll-howto/ // -// Fixes filter registers saturation // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // // // // 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 // @@ -35,6 +37,10 @@ public: * \param K PLL loop gain * */ void computeCoefficients(Real wn, Real zeta, Real K); + /** Set the PSK order for the phase comparator + * \param order 0,1: no PSK (CW), 2: BPSK, 4: QPSK, 8: 8-PSK, ... use powers of two for real cases + */ + void setPskOrder(unsigned int order); void reset(); void feed(float re, float im); const std::complex& getComplex() const { return m_y; } @@ -46,6 +52,9 @@ public: float getPhiHat() const { return m_phiHat; } private: + /** Normalize angle in radians into the [-pi,+pi] region */ + static float normalizeAngle(float angle); + // a0 = 1 is implied float m_a1; float m_a2; @@ -62,7 +71,7 @@ private: float m_yRe; float m_yIm; float m_freq; - + unsigned int m_pskOrder; }; From 06c9f7f20dba4a68ca05b276396359c15ec03a75 Mon Sep 17 00:00:00 2001 From: Edouard Griffiths Date: Mon, 14 May 2018 20:47:23 +0200 Subject: [PATCH 407/956] Channel Analyzer NG: implemented PLL with PSK order --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 15 ++++++-- plugins/channelrx/chanalyzerng/chanalyzerng.h | 21 +++++++--- .../chanalyzerng/chanalyzernggui.cpp | 8 +++- .../channelrx/chanalyzerng/chanalyzernggui.h | 1 + .../channelrx/chanalyzerng/chanalyzernggui.ui | 38 +++++++++++++++++++ 5 files changed, 73 insertions(+), 10 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 67d464ab3..5e6e8a7c8 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -75,9 +75,10 @@ void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, Real LowCutoff, int spanLog2, bool ssb, - bool pll) + bool pll, + unsigned int pllPskOrder) { - Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll); + Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, pllPskOrder); messageQueue->push(cmd); } @@ -168,6 +169,7 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) m_config.m_spanLog2 = cfg.getSpanLog2(); m_config.m_ssb = cfg.getSSB(); m_config.m_pll = cfg.getPLL(); + m_config.m_pllPskOrder = cfg.getPLLPSKOrder(); qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:" << " m_channelSampleRate: " << m_config.m_channelSampleRate @@ -175,7 +177,8 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) << " m_LowCutoff: " << m_config.m_LowCutoff << " m_spanLog2: " << m_config.m_spanLog2 << " m_ssb: " << m_config.m_ssb - << " m_pll: " << m_config.m_pll; + << " m_pll: " << m_config.m_pll + << " m_pllPskOrder: " << m_config.m_pllPskOrder; apply(); return true; @@ -256,6 +259,11 @@ void ChannelAnalyzerNG::apply(bool force) } } + if (m_running.m_pll != m_config.m_pll || force) + { + m_pll.setPskOrder(m_config.m_pllPskOrder); + } + m_running.m_frequency = m_config.m_frequency; m_running.m_channelSampleRate = m_config.m_channelSampleRate; m_running.m_inputSampleRate = m_config.m_inputSampleRate; @@ -266,5 +274,6 @@ void ChannelAnalyzerNG::apply(bool force) m_running.m_spanLog2 = m_config.m_spanLog2; m_running.m_ssb = m_config.m_ssb; m_running.m_pll = m_config.m_pll; + m_running.m_pllPskOrder = m_config.m_pllPskOrder; //m_settingsMutex.unlock(); } diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index d65786762..222b30628 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -47,6 +47,7 @@ public: int getSpanLog2() const { return m_spanLog2; } bool getSSB() const { return m_ssb; } bool getPLL() const { return m_pll; } + unsigned int getPLLPSKOrder() const { return m_pllPskOrder; } static MsgConfigureChannelAnalyzer* create( int channelSampleRate, @@ -54,7 +55,8 @@ public: Real LowCutoff, int spanLog2, bool ssb, - bool pll) + bool pll, + unsigned int pllPskOrder) { return new MsgConfigureChannelAnalyzer( channelSampleRate, @@ -62,7 +64,8 @@ public: LowCutoff, spanLog2, ssb, - pll); + pll, + pllPskOrder); } private: @@ -72,6 +75,7 @@ public: int m_spanLog2; bool m_ssb; bool m_pll; + unsigned int m_pllPskOrder; MsgConfigureChannelAnalyzer( int channelSampleRate, @@ -79,14 +83,16 @@ public: Real LowCutoff, int spanLog2, bool ssb, - bool pll) : + bool pll, + unsigned int pllPskOrder) : Message(), m_channelSampleRate(channelSampleRate), m_Bandwidth(Bandwidth), m_LowCutoff(LowCutoff), m_spanLog2(spanLog2), m_ssb(ssb), - m_pll(pll) + m_pll(pll), + m_pllPskOrder(pllPskOrder) { } }; @@ -141,7 +147,8 @@ public: Real LowCutoff, int spanLog2, bool ssb, - bool pll); + bool pll, + unsigned int pllPskOrder); DownChannelizer *getChannelizer() { return m_channelizer; } int getInputSampleRate() const { return m_running.m_inputSampleRate; } @@ -179,6 +186,7 @@ private: int m_spanLog2; bool m_ssb; bool m_pll; + unsigned int m_pllPskOrder; Config() : m_frequency(0), @@ -188,7 +196,8 @@ private: m_LowCutoff(300), m_spanLog2(3), m_ssb(false), - m_pll(false) + m_pll(false), + m_pllPskOrder(1) {} }; diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 90dbd44e0..a18da957b 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -276,6 +276,11 @@ void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) applySettings(); } +void ChannelAnalyzerNGGUI::on_pllPskOrder_currentIndexChanged(int index __attribute__((unused))) +{ + applySettings(); +} + void ChannelAnalyzerNGGUI::on_useRationalDownsampler_toggled(bool checked __attribute__((unused))) { setNewFinalRate(m_spanLog2); @@ -605,7 +610,8 @@ void ChannelAnalyzerNGGUI::applySettings() ui->lowCut->value() * 100.0, m_spanLog2, ui->ssb->isChecked(), - ui->pll->isChecked()); + ui->pll->isChecked(), + 1<pllPskOrder->currentIndex()); } } diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h index 0bc962e92..499ded960 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.h @@ -94,6 +94,7 @@ private slots: void on_deltaFrequency_changed(qint64 value); void on_channelSampleRate_changed(quint64 value); void on_pll_toggled(bool checked); + void on_pllPskOrder_currentIndexChanged(int index); void on_useRationalDownsampler_toggled(bool checked); void on_BW_valueChanged(int value); void on_lowCut_valueChanged(int value); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index 1c72fa3a1..d9e5beffa 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -197,6 +197,44 @@ + + + + + 40 + 16777215 + + + + PLL PSK order (1 for CW) + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + From 5327856827a6b76d752c54c835ced456f80ba6c2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 15 May 2018 00:30:01 +0200 Subject: [PATCH 408/956] Channel Analyzer NG: toggle polar points/segments display --- sdrgui/gui/glscopeng.cpp | 9 ++++-- sdrgui/gui/glscopeng.h | 2 ++ sdrgui/gui/glscopenggui.cpp | 35 +++++++++++++++++++++ sdrgui/gui/glscopenggui.h | 1 + sdrgui/gui/glscopenggui.ui | 58 ++++++++++++++++++++++++++++++++++- sdrgui/gui/glshadersimple.cpp | 5 +++ sdrgui/gui/glshadersimple.h | 1 + 7 files changed, 108 insertions(+), 3 deletions(-) diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index 3bda3ff10..e2cae6be7 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -42,7 +42,8 @@ GLScopeNG::GLScopeNG(QWidget* parent) : m_timeOffset(0), m_focusedTraceIndex(0), m_displayGridIntensity(10), - m_displayTraceIntensity(50) + m_displayTraceIntensity(50), + m_displayXYPoints(false) { setAttribute(Qt::WA_OpaquePaintEvent); connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); @@ -910,7 +911,11 @@ void GLScopeNG::paintGL() mat.setToIdentity(); mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); mat.scale(2.0f * rectW, -2.0f * rectH); - m_glShaderSimple.drawPolyline(mat, color, q3, end -start); + if (m_displayXYPoints) { + m_glShaderSimple.drawPoints(mat, color, q3, end -start); + } else { + m_glShaderSimple.drawPolyline(mat, color, q3, end -start); + } } // XY polar display } // trace length > 0 } // XY mixed + polar display diff --git a/sdrgui/gui/glscopeng.h b/sdrgui/gui/glscopeng.h index 929afe5b4..12f1cca99 100644 --- a/sdrgui/gui/glscopeng.h +++ b/sdrgui/gui/glscopeng.h @@ -74,6 +74,7 @@ public: bool getDataChanged() const { return m_dataChanged; } DisplayMode getDisplayMode() const { return m_displayMode; } + void setDisplayXYPoints(bool value) { m_displayXYPoints = value; } signals: void sampleRateChanged(int); @@ -117,6 +118,7 @@ private: int m_displayGridIntensity; int m_displayTraceIntensity; + bool m_displayXYPoints; ScaleEngine m_x1Scale; //!< Display #1 X scale. Time scale ScaleEngine m_x2Scale; //!< Display #2 X scale. Time scale diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 737d3ff85..55da3719b 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -422,6 +422,12 @@ void GLScopeNGGUI::on_onlyX_toggled(bool checked) ui->polar->setChecked(false); m_glScope->setDisplayMode(GLScopeNG::DisplayX); } + else + { + if (!ui->onlyY->isChecked() && !ui->horizontalXY->isChecked() && !ui->verticalXY->isChecked() && !ui->polar->isChecked()) { + ui->polar->setChecked(true); + } + } } void GLScopeNGGUI::on_onlyY_toggled(bool checked) @@ -434,6 +440,12 @@ void GLScopeNGGUI::on_onlyY_toggled(bool checked) ui->polar->setChecked(false); m_glScope->setDisplayMode(GLScopeNG::DisplayY); } + else + { + if (!ui->onlyX->isChecked() && !ui->horizontalXY->isChecked() && !ui->verticalXY->isChecked() && !ui->polar->isChecked()) { + ui->polar->setChecked(true); + } + } } void GLScopeNGGUI::on_horizontalXY_toggled(bool checked) @@ -446,6 +458,12 @@ void GLScopeNGGUI::on_horizontalXY_toggled(bool checked) ui->polar->setChecked(false); m_glScope->setDisplayMode(GLScopeNG::DisplayXYH); } + else + { + if (!ui->onlyX->isChecked() && !ui->onlyY->isChecked() && !ui->verticalXY->isChecked() && !ui->polar->isChecked()) { + ui->polar->setChecked(true); + } + } } void GLScopeNGGUI::on_verticalXY_toggled(bool checked) @@ -458,6 +476,12 @@ void GLScopeNGGUI::on_verticalXY_toggled(bool checked) ui->polar->setChecked(false); m_glScope->setDisplayMode(GLScopeNG::DisplayXYV); } + else + { + if (!ui->onlyX->isChecked() && !ui->onlyY->isChecked() && !ui->horizontalXY->isChecked() && !ui->polar->isChecked()) { + ui->polar->setChecked(true); + } + } } void GLScopeNGGUI::on_polar_toggled(bool checked) @@ -470,6 +494,17 @@ void GLScopeNGGUI::on_polar_toggled(bool checked) ui->verticalXY->setChecked(false); m_glScope->setDisplayMode(GLScopeNG::DisplayPol); } + else + { + if (!ui->onlyX->isChecked() && !ui->onlyY->isChecked() && !ui->horizontalXY->isChecked() && !ui->verticalXY->isChecked()) { + ui->polar->setChecked(true); + } + } +} + +void GLScopeNGGUI::on_polarPoints_toggled(bool checked) +{ + m_glScope->setDisplayXYPoints(checked); } void GLScopeNGGUI::on_traceIntensity_valueChanged(int value) diff --git a/sdrgui/gui/glscopenggui.h b/sdrgui/gui/glscopenggui.h index 43f6ad660..fb98c0c29 100644 --- a/sdrgui/gui/glscopenggui.h +++ b/sdrgui/gui/glscopenggui.h @@ -191,6 +191,7 @@ private slots: void on_horizontalXY_toggled(bool checked); void on_verticalXY_toggled(bool checked); void on_polar_toggled(bool checked); + void on_polarPoints_toggled(bool checked); void on_traceIntensity_valueChanged(int value); void on_gridIntensity_valueChanged(int value); void on_time_valueChanged(int value); diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index e7a8afe70..0f9c0b679 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -206,6 +206,62 @@ + + + 24 + 24 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + + + 190 + 190 + 190 + + + + + + + + Display XY traces and polar trace + + + XY + + + true + + + + + 24 @@ -213,7 +269,7 @@ - Display XY traces and polar trace + Toggle points or segments display for polar trace ... diff --git a/sdrgui/gui/glshadersimple.cpp b/sdrgui/gui/glshadersimple.cpp index 913dc03e4..627c0bcde 100644 --- a/sdrgui/gui/glshadersimple.cpp +++ b/sdrgui/gui/glshadersimple.cpp @@ -59,6 +59,11 @@ void GLShaderSimple::initializeGL() m_program->release(); } +void GLShaderSimple::drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices) +{ + draw(GL_POINTS, transformMatrix, color, vertices, nbVertices); +} + void GLShaderSimple::drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices) { draw(GL_LINE_STRIP, transformMatrix, color, vertices, nbVertices); diff --git a/sdrgui/gui/glshadersimple.h b/sdrgui/gui/glshadersimple.h index 583e95c15..84c5a43fd 100644 --- a/sdrgui/gui/glshadersimple.h +++ b/sdrgui/gui/glshadersimple.h @@ -34,6 +34,7 @@ public: ~GLShaderSimple(); void initializeGL(); + void drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices); void drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices); void drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices); void drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices); From cf5901f82c80976c25840291bf48e928085daebe Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 15 May 2018 01:03:43 +0200 Subject: [PATCH 409/956] Moving average with variable history size --- sdrbase/util/movingaverage.h | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/sdrbase/util/movingaverage.h b/sdrbase/util/movingaverage.h index 4a6276f95..0461fbb09 100644 --- a/sdrbase/util/movingaverage.h +++ b/sdrbase/util/movingaverage.h @@ -66,4 +66,56 @@ class MovingAverageUtil Total m_total; }; + +template +class MovingAverageUtilVar +{ + public: + MovingAverageUtilVar(unsigned int size) + : m_num_samples(0), m_index(0), m_total(0) + { + m_samples.resize(size); + } + + void reset() + { + m_num_samples = 0; + m_index = 0; + m_total = 0; + } + + void resize(unsigned int size) + { + reset(); + m_samples.resize(size); + } + + void operator()(T sample) + { + if (m_num_samples < m_samples.size()) // fill up + { + m_samples[m_num_samples++] = sample; + m_total += sample; + } + else // roll + { + T& oldest = m_samples[m_index]; + m_total += sample - oldest; + oldest = sample; + m_index = (m_index + 1) % m_samples.size(); + } + } + + double asDouble() const { return ((double)m_total) / m_samples.size(); } + float asFloat() const { return ((float)m_total) / m_samples.size(); } + operator T() const { return m_total / m_samples.size(); } + + private: + std::vector m_samples; + unsigned int m_num_samples; + unsigned int m_index; + Total m_total; +}; + + #endif /* GR_SDRDAEMONFEC_LIB_MOVINGAVERAGE_H_ */ From 7f3bec34c9a7f850589db465736349c2f918e8e8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 15 May 2018 09:17:54 +0200 Subject: [PATCH 410/956] ChannelAnalyzerNG: adjust PLL loop parameters --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 559 +++++++++--------- 1 file changed, 280 insertions(+), 279 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 5e6e8a7c8..63994c9c0 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -1,279 +1,280 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 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 // -// // -// 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 "chanalyzerng.h" - -#include -#include -#include - -#include "device/devicesourceapi.h" -#include "audio/audiooutput.h" -#include "dsp/threadedbasebandsamplesink.h" -#include "dsp/downchannelizer.h" - -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgReportChannelSampleRateChanged, Message) - -const QString ChannelAnalyzerNG::m_channelIdURI = "sdrangel.channel.chanalyzerng"; -const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzerNG"; - -ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : - ChannelSinkAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_sampleSink(0), - m_settingsMutex(QMutex::Recursive) -{ - setObjectName(m_channelId); - - m_undersampleCount = 0; - m_sum = 0; - m_usb = true; - m_magsq = 0; - m_useInterpolator = false; - m_interpolatorDistance = 1.0f; - m_interpolatorDistanceRemain = 0.0f; - SSBFilter = new fftfilt(m_config.m_LowCutoff / m_config.m_inputSampleRate, m_config.m_Bandwidth / m_config.m_inputSampleRate, ssbFftLen); - DSBFilter = new fftfilt(m_config.m_Bandwidth / m_config.m_inputSampleRate, 2*ssbFftLen); - m_pll.computeCoefficients(0.05f, 0.707f, 1000.0f); // bandwidth, damping factor, loop gain - - apply(true); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addThreadedSink(m_threadedChannelizer); - m_deviceAPI->addChannelAPI(this); -} - -ChannelAnalyzerNG::~ChannelAnalyzerNG() -{ - m_deviceAPI->removeChannelAPI(this); - m_deviceAPI->removeThreadedSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; - delete SSBFilter; - delete DSBFilter; -} - -void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, - int channelSampleRate, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb, - bool pll, - unsigned int pllPskOrder) -{ - Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, pllPskOrder); - messageQueue->push(cmd); -} - -void ChannelAnalyzerNG::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) -{ - fftfilt::cmplx *sideband = 0; - Complex ci; - - m_settingsMutex.lock(); - - for(SampleVector::const_iterator it = begin; it < end; ++it) - { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_useInterpolator) - { - if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci, sideband); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - else - { - processOneSample(c, sideband); - } - } - - if(m_sampleSink != 0) - { - m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_running.m_ssb); // m_ssb = positive only - } - - m_sampleBuffer.clear(); - - m_settingsMutex.unlock(); -} - -void ChannelAnalyzerNG::start() -{ -} - -void ChannelAnalyzerNG::stop() -{ -} - -bool ChannelAnalyzerNG::handleMessage(const Message& cmd) -{ - qDebug() << "ChannelAnalyzerNG::handleMessage: " << cmd.getIdentifier(); - - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - m_config.m_inputSampleRate = notif.getSampleRate(); - m_config.m_frequency = notif.getFrequencyOffset(); - - qDebug() << "ChannelAnalyzerNG::handleMessage: MsgChannelizerNotification:" - << " m_sampleRate: " << m_config.m_inputSampleRate - << " frequencyOffset: " << m_config.m_frequency; - - apply(); - - if (getMessageQueueToGUI()) - { - MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(); - getMessageQueueToGUI()->push(msg); - } - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - return true; - } - else if (MsgConfigureChannelAnalyzer::match(cmd)) - { - MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; - - m_config.m_channelSampleRate = cfg.getChannelSampleRate(); - m_config.m_Bandwidth = cfg.getBandwidth(); - m_config.m_LowCutoff = cfg.getLoCutoff(); - m_config.m_spanLog2 = cfg.getSpanLog2(); - m_config.m_ssb = cfg.getSSB(); - m_config.m_pll = cfg.getPLL(); - m_config.m_pllPskOrder = cfg.getPLLPSKOrder(); - - qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:" - << " m_channelSampleRate: " << m_config.m_channelSampleRate - << " m_Bandwidth: " << m_config.m_Bandwidth - << " m_LowCutoff: " << m_config.m_LowCutoff - << " m_spanLog2: " << m_config.m_spanLog2 - << " m_ssb: " << m_config.m_ssb - << " m_pll: " << m_config.m_pll - << " m_pllPskOrder: " << m_config.m_pllPskOrder; - - apply(); - return true; - } - else - { - if (m_sampleSink != 0) - { - return m_sampleSink->handleMessage(cmd); - } - else - { - return false; - } - } -} - - - -void ChannelAnalyzerNG::apply(bool force) -{ - if ((m_running.m_frequency != m_config.m_frequency) || - (m_running.m_inputSampleRate != m_config.m_inputSampleRate) || - force) - { - m_nco.setFreq(-m_config.m_frequency, m_config.m_inputSampleRate); - } - - if ((m_running.m_inputSampleRate != m_config.m_inputSampleRate) || - (m_running.m_channelSampleRate != m_config.m_channelSampleRate) || - force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, m_config.m_inputSampleRate, m_config.m_inputSampleRate / 2.2); - m_interpolatorDistanceRemain = 0.0f; - m_interpolatorDistance = (Real) m_config.m_inputSampleRate / (Real) m_config.m_channelSampleRate; - m_useInterpolator = (m_config.m_inputSampleRate != m_config.m_channelSampleRate); // optim - m_settingsMutex.unlock(); - } - - if ((m_running.m_channelSampleRate != m_config.m_channelSampleRate) || - (m_running.m_Bandwidth != m_config.m_Bandwidth) || - (m_running.m_LowCutoff != m_config.m_LowCutoff) || - force) - { - float bandwidth = m_config.m_Bandwidth; - float lowCutoff = m_config.m_LowCutoff; - - if (bandwidth < 0) - { - bandwidth = -bandwidth; - lowCutoff = -lowCutoff; - m_usb = false; - } - else - { - m_usb = true; - } - - if (bandwidth < 100.0f) - { - bandwidth = 100.0f; - lowCutoff = 0; - } - - m_settingsMutex.lock(); - - SSBFilter->create_filter(lowCutoff / m_config.m_channelSampleRate, bandwidth / m_config.m_channelSampleRate); - DSBFilter->create_dsb_filter(bandwidth / m_config.m_channelSampleRate); - - m_settingsMutex.unlock(); - } - - if (m_running.m_pll != m_config.m_pll || force) - { - if (m_config.m_pll) { - m_pll.reset(); - } - } - - if (m_running.m_pll != m_config.m_pll || force) - { - m_pll.setPskOrder(m_config.m_pllPskOrder); - } - - m_running.m_frequency = m_config.m_frequency; - m_running.m_channelSampleRate = m_config.m_channelSampleRate; - m_running.m_inputSampleRate = m_config.m_inputSampleRate; - m_running.m_Bandwidth = m_config.m_Bandwidth; - m_running.m_LowCutoff = m_config.m_LowCutoff; - - //m_settingsMutex.lock(); - m_running.m_spanLog2 = m_config.m_spanLog2; - m_running.m_ssb = m_config.m_ssb; - m_running.m_pll = m_config.m_pll; - m_running.m_pllPskOrder = m_config.m_pllPskOrder; - //m_settingsMutex.unlock(); -} +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// 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 "chanalyzerng.h" + +#include +#include +#include + +#include "device/devicesourceapi.h" +#include "audio/audiooutput.h" +#include "dsp/threadedbasebandsamplesink.h" +#include "dsp/downchannelizer.h" + +MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzer, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgReportChannelSampleRateChanged, Message) + +const QString ChannelAnalyzerNG::m_channelIdURI = "sdrangel.channel.chanalyzerng"; +const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzerNG"; + +ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : + ChannelSinkAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_sampleSink(0), + m_settingsMutex(QMutex::Recursive) +{ + setObjectName(m_channelId); + + m_undersampleCount = 0; + m_sum = 0; + m_usb = true; + m_magsq = 0; + m_useInterpolator = false; + m_interpolatorDistance = 1.0f; + m_interpolatorDistanceRemain = 0.0f; + SSBFilter = new fftfilt(m_config.m_LowCutoff / m_config.m_inputSampleRate, m_config.m_Bandwidth / m_config.m_inputSampleRate, ssbFftLen); + DSBFilter = new fftfilt(m_config.m_Bandwidth / m_config.m_inputSampleRate, 2*ssbFftLen); + //m_pll.computeCoefficients(0.05f, 0.707f, 1000.0f); // bandwidth, damping factor, loop gain + m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain + + apply(true); + + m_channelizer = new DownChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); + m_deviceAPI->addThreadedSink(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); +} + +ChannelAnalyzerNG::~ChannelAnalyzerNG() +{ + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSink(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; + delete SSBFilter; + delete DSBFilter; +} + +void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, + int channelSampleRate, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb, + bool pll, + unsigned int pllPskOrder) +{ + Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, pllPskOrder); + messageQueue->push(cmd); +} + +void ChannelAnalyzerNG::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) +{ + fftfilt::cmplx *sideband = 0; + Complex ci; + + m_settingsMutex.lock(); + + for(SampleVector::const_iterator it = begin; it < end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_useInterpolator) + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci, sideband); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else + { + processOneSample(c, sideband); + } + } + + if(m_sampleSink != 0) + { + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_running.m_ssb); // m_ssb = positive only + } + + m_sampleBuffer.clear(); + + m_settingsMutex.unlock(); +} + +void ChannelAnalyzerNG::start() +{ +} + +void ChannelAnalyzerNG::stop() +{ +} + +bool ChannelAnalyzerNG::handleMessage(const Message& cmd) +{ + qDebug() << "ChannelAnalyzerNG::handleMessage: " << cmd.getIdentifier(); + + if (DownChannelizer::MsgChannelizerNotification::match(cmd)) + { + DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; + + m_config.m_inputSampleRate = notif.getSampleRate(); + m_config.m_frequency = notif.getFrequencyOffset(); + + qDebug() << "ChannelAnalyzerNG::handleMessage: MsgChannelizerNotification:" + << " m_sampleRate: " << m_config.m_inputSampleRate + << " frequencyOffset: " << m_config.m_frequency; + + apply(); + + if (getMessageQueueToGUI()) + { + MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(); + getMessageQueueToGUI()->push(msg); + } + + return true; + } + else if (MsgConfigureChannelizer::match(cmd)) + { + MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; + m_channelizer->configure(m_channelizer->getInputMessageQueue(), + cfg.getSampleRate(), + cfg.getCenterFrequency()); + return true; + } + else if (MsgConfigureChannelAnalyzer::match(cmd)) + { + MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; + + m_config.m_channelSampleRate = cfg.getChannelSampleRate(); + m_config.m_Bandwidth = cfg.getBandwidth(); + m_config.m_LowCutoff = cfg.getLoCutoff(); + m_config.m_spanLog2 = cfg.getSpanLog2(); + m_config.m_ssb = cfg.getSSB(); + m_config.m_pll = cfg.getPLL(); + m_config.m_pllPskOrder = cfg.getPLLPSKOrder(); + + qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:" + << " m_channelSampleRate: " << m_config.m_channelSampleRate + << " m_Bandwidth: " << m_config.m_Bandwidth + << " m_LowCutoff: " << m_config.m_LowCutoff + << " m_spanLog2: " << m_config.m_spanLog2 + << " m_ssb: " << m_config.m_ssb + << " m_pll: " << m_config.m_pll + << " m_pllPskOrder: " << m_config.m_pllPskOrder; + + apply(); + return true; + } + else + { + if (m_sampleSink != 0) + { + return m_sampleSink->handleMessage(cmd); + } + else + { + return false; + } + } +} + + + +void ChannelAnalyzerNG::apply(bool force) +{ + if ((m_running.m_frequency != m_config.m_frequency) || + (m_running.m_inputSampleRate != m_config.m_inputSampleRate) || + force) + { + m_nco.setFreq(-m_config.m_frequency, m_config.m_inputSampleRate); + } + + if ((m_running.m_inputSampleRate != m_config.m_inputSampleRate) || + (m_running.m_channelSampleRate != m_config.m_channelSampleRate) || + force) + { + m_settingsMutex.lock(); + m_interpolator.create(16, m_config.m_inputSampleRate, m_config.m_inputSampleRate / 2.2); + m_interpolatorDistanceRemain = 0.0f; + m_interpolatorDistance = (Real) m_config.m_inputSampleRate / (Real) m_config.m_channelSampleRate; + m_useInterpolator = (m_config.m_inputSampleRate != m_config.m_channelSampleRate); // optim + m_settingsMutex.unlock(); + } + + if ((m_running.m_channelSampleRate != m_config.m_channelSampleRate) || + (m_running.m_Bandwidth != m_config.m_Bandwidth) || + (m_running.m_LowCutoff != m_config.m_LowCutoff) || + force) + { + float bandwidth = m_config.m_Bandwidth; + float lowCutoff = m_config.m_LowCutoff; + + if (bandwidth < 0) + { + bandwidth = -bandwidth; + lowCutoff = -lowCutoff; + m_usb = false; + } + else + { + m_usb = true; + } + + if (bandwidth < 100.0f) + { + bandwidth = 100.0f; + lowCutoff = 0; + } + + m_settingsMutex.lock(); + + SSBFilter->create_filter(lowCutoff / m_config.m_channelSampleRate, bandwidth / m_config.m_channelSampleRate); + DSBFilter->create_dsb_filter(bandwidth / m_config.m_channelSampleRate); + + m_settingsMutex.unlock(); + } + + if (m_running.m_pll != m_config.m_pll || force) + { + if (m_config.m_pll) { + m_pll.reset(); + } + } + + if (m_running.m_pll != m_config.m_pll || force) + { + m_pll.setPskOrder(m_config.m_pllPskOrder); + } + + m_running.m_frequency = m_config.m_frequency; + m_running.m_channelSampleRate = m_config.m_channelSampleRate; + m_running.m_inputSampleRate = m_config.m_inputSampleRate; + m_running.m_Bandwidth = m_config.m_Bandwidth; + m_running.m_LowCutoff = m_config.m_LowCutoff; + + //m_settingsMutex.lock(); + m_running.m_spanLog2 = m_config.m_spanLog2; + m_running.m_ssb = m_config.m_ssb; + m_running.m_pll = m_config.m_pll; + m_running.m_pllPskOrder = m_config.m_pllPskOrder; + //m_settingsMutex.unlock(); +} From 88d7a97b97726f4266461ed2bcd90bffec9bba2b Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 15 May 2018 14:37:44 +0200 Subject: [PATCH 411/956] ChannelAnalyzerNG: fixed missing delta frequency dial update in de-serializaiton --- plugins/channelrx/chanalyzerng/chanalyzernggui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index a18da957b..8637cded3 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -120,6 +120,7 @@ bool ChannelAnalyzerNGGUI::deserialize(const QByteArray& data) d.readS32(1, &tmp, 0); m_channelMarker.setCenterFrequency(tmp); + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); d.readS32(2, &bw, 30); d.readBlob(3, &bytetmp); ui->spectrumGUI->deserialize(bytetmp); From 457b9aa2c899784d8b755d7d0754440ee621ef3c Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 15 May 2018 16:26:53 +0200 Subject: [PATCH 412/956] ChannelAnalzyerNG: fixed bandwidth expand/shrink with baseband sample rate changes --- plugins/channelrx/chanalyzerng/chanalyzernggui.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 8637cded3..afc23019b 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -163,19 +163,12 @@ bool ChannelAnalyzerNGGUI::handleMessage(const Message& message) { int newRate = getRequestedChannelSampleRate() / (1<BW->value() * 100L * m_rate) / newRate; - uint64_t newLC = (ui->lowCut->value() * 100L * m_rate) / newRate; - qDebug() << "ChannelAnalyzerNGGUI::handleMessage: MsgReportChannelSampleRateChanged:" - << " newRate: " << newRate - << " newBW: " << newBW - << " newLC: " << newLC; + << "m_rate: " << m_rate + << " newRate: " << newRate; m_rate = newRate; - ui->BW->setValue(newBW/100); - ui->lowCut->setValue(newLC/100); - blockApplySettings(true); setFiltersUIBoundaries(); blockApplySettings(false); From bb2d53012243b768b1681f6cab631568691fc0e7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 15 May 2018 19:40:53 +0200 Subject: [PATCH 413/956] New PLL: phase lock status draft --- plugins/channelrx/chanalyzerng/chanalyzerng.h | 8 +-- sdrbase/dsp/phaselockcomplex.cpp | 53 ++++++++++++++++--- sdrbase/dsp/phaselockcomplex.h | 10 +++- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index 222b30628..ee7f7b573 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -262,10 +262,10 @@ private: m_pll.feed(re, im); // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) - Real mixI = m_sum.real() * m_pll.getImag() - m_sum.imag() * m_pll.getReal(); - Real mixQ = m_sum.real() * m_pll.getReal() + m_sum.imag() * m_pll.getImag(); -// Real mixI = m_pll.getReal() * SDR_RX_SCALED; -// Real mixQ = m_pll.getImag() * SDR_RX_SCALED; + // Real mixI = m_sum.real() * m_pll.getImag() - m_sum.imag() * m_pll.getReal(); + // Real mixQ = m_sum.real() * m_pll.getReal() + m_sum.imag() * m_pll.getImag(); + Real mixI = m_pll.getReal() * SDR_RX_SCALED; + Real mixQ = m_pll.getImag() * SDR_RX_SCALED; if (m_running.m_ssb & !m_usb) { // invert spectrum for LSB diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index b6572ef58..c3440c0f2 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -34,12 +34,18 @@ PhaseLockComplex::PhaseLockComplex() : m_v1(0.0), m_v2(0.0), m_deltaPhi(0.0), - m_phiHatLast(0.0), m_phiHat(0.0), + m_phiHatPrev(0.0), + m_phiHat1(0.0), + m_phiHat2(0.0), + m_dPhiHatAccum(0.0), + m_phiHatCount(0), m_y(1.0, 0.0), m_yRe(1.0), m_yIm(0.0), m_freq(0.0), + m_lock(0.0), + m_lockCount(0), m_pskOrder(1) { } @@ -86,13 +92,19 @@ void PhaseLockComplex::reset() m_v1 = 0.0f; m_v2 = 0.0f; m_deltaPhi = 0.0f; - m_phiHatLast = 0.0f; m_phiHat = 0.0f; + m_phiHatPrev = 0.0f; + m_phiHat1 = 0.0f; + m_phiHat2 = 0.0f; + m_dPhiHatAccum = 0.0f; + m_phiHatCount = 0; m_y.real(1.0); m_y.real(0.0); m_yRe = 1.0f; m_yIm = 0.0f; m_freq = 0.0f; + m_lock = 0.0f; + m_lockCount = 0; } void PhaseLockComplex::feed(float re, float im) @@ -136,15 +148,40 @@ void PhaseLockComplex::feed(float re, float im) m_phiHat += 2.0*M_PI; } - m_freq = (m_phiHat - m_phiHatLast) / (2.0*M_PI); + float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); + m_phiHatPrev = m_phiHat; - if (m_freq < -1.0f) { - m_freq += 2.0f; - } else if (m_freq > 1.0f) { - m_freq -= 2.0f; + if (m_phiHatCount < 9) + { + m_dPhiHatAccum += dPhi; + } + else + { + float dPhi1 = (m_phiHat1 - m_dPhiHatAccum) / 10.0f; + float dPhi1Prev = (m_phiHat2 - m_phiHat1) / 10.0f; + m_lock = dPhi1 - dPhi1Prev; // second derivative of phase + + if ((m_lock > -0.01) && (m_lock < 0.01)) + { + if (m_lockCount < 1000) { + m_lockCount++; + } + } + else + { + if (m_lockCount > 0) { + m_lockCount--; + } + } + + m_freq = dPhi1 / 2.0*M_PI; // first derivative of phase + m_phiHat2 = m_phiHat1; + m_phiHat1 = m_dPhiHatAccum; + m_dPhiHatAccum = 0.0f; + m_phiHatCount = 0; } - m_phiHatLast = m_phiHat; + m_dPhiHatAccum += dPhi; } float PhaseLockComplex::normalizeAngle(float angle) diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index ed5d7633e..675c12bb8 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -46,7 +46,7 @@ public: const std::complex& getComplex() const { return m_y; } float getReal() const { return m_yRe; } float getImag() const { return m_yIm; } - bool locked() const { return (m_deltaPhi > -0.1) && (m_deltaPhi < 0.1); } + bool locked() const { return m_lockCount > 500; } float getFrequency() const { return m_freq; } float getDeltaPhi() const { return m_deltaPhi; } float getPhiHat() const { return m_phiHat; } @@ -65,12 +65,18 @@ private: float m_v1; float m_v2; float m_deltaPhi; - float m_phiHatLast; float m_phiHat; + float m_phiHatPrev; + float m_phiHat1; + float m_phiHat2; + float m_dPhiHatAccum; + int m_phiHatCount; std::complex m_y; float m_yRe; float m_yIm; float m_freq; + float m_lock; + int m_lockCount; unsigned int m_pskOrder; }; From 660d8d22ae18081abf62d514294b3eaae66f137a Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 16 May 2018 01:57:16 +0200 Subject: [PATCH 414/956] New PLL: heuristics to find locked state --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 8 +- plugins/channelrx/chanalyzerng/chanalyzerng.h | 593 +++++++++--------- .../chanalyzerng/chanalyzernggui.cpp | 13 - plugins/channelrx/demodam/amdemod.cpp | 5 +- sdrbase/dsp/phaselockcomplex.cpp | 87 ++- sdrbase/dsp/phaselockcomplex.h | 10 +- 6 files changed, 379 insertions(+), 337 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 63994c9c0..546985628 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -197,8 +197,6 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) } } - - void ChannelAnalyzerNG::apply(bool force) { if ((m_running.m_frequency != m_config.m_frequency) || @@ -253,6 +251,12 @@ void ChannelAnalyzerNG::apply(bool force) m_settingsMutex.unlock(); } + if ((m_running.m_channelSampleRate != m_config.m_channelSampleRate) || + (m_running.m_spanLog2 != m_config.m_spanLog2) || force) + { + m_pll.setSampleRate(m_running.m_channelSampleRate / (1<. // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_CHANALYZERNG_H -#define INCLUDE_CHANALYZERNG_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/interpolator.h" -#include "dsp/ncof.h" -#include "dsp/fftfilt.h" -#include "dsp/phaselockcomplex.h" -#include "audio/audiofifo.h" -#include "util/message.h" - -#define ssbFftLen 1024 - -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -class ChannelAnalyzerNG : public BasebandSampleSink, public ChannelSinkAPI { -public: - class MsgConfigureChannelAnalyzer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getChannelSampleRate() const { return m_channelSampleRate; } - Real getBandwidth() const { return m_Bandwidth; } - Real getLoCutoff() const { return m_LowCutoff; } - int getSpanLog2() const { return m_spanLog2; } - bool getSSB() const { return m_ssb; } - bool getPLL() const { return m_pll; } - unsigned int getPLLPSKOrder() const { return m_pllPskOrder; } - - static MsgConfigureChannelAnalyzer* create( - int channelSampleRate, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb, - bool pll, - unsigned int pllPskOrder) - { - return new MsgConfigureChannelAnalyzer( - channelSampleRate, - Bandwidth, - LowCutoff, - spanLog2, - ssb, - pll, - pllPskOrder); - } - - private: - int m_channelSampleRate; - Real m_Bandwidth; - Real m_LowCutoff; - int m_spanLog2; - bool m_ssb; - bool m_pll; - unsigned int m_pllPskOrder; - - MsgConfigureChannelAnalyzer( - int channelSampleRate, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb, - bool pll, - unsigned int pllPskOrder) : - Message(), - m_channelSampleRate(channelSampleRate), - m_Bandwidth(Bandwidth), - m_LowCutoff(LowCutoff), - m_spanLog2(spanLog2), - m_ssb(ssb), - m_pll(pll), - m_pllPskOrder(pllPskOrder) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - class MsgReportChannelSampleRateChanged : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgReportChannelSampleRateChanged* create() - { - return new MsgReportChannelSampleRateChanged(); - } - - private: - - MsgReportChannelSampleRateChanged() : - Message() - { } - }; - - ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI); - virtual ~ChannelAnalyzerNG(); - virtual void destroy() { delete this; } - void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } - - void configure(MessageQueue* messageQueue, - int channelSampleRate, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb, - bool pll, - unsigned int pllPskOrder); - - DownChannelizer *getChannelizer() { return m_channelizer; } - int getInputSampleRate() const { return m_running.m_inputSampleRate; } - int getChannelSampleRate() const { return m_running.m_channelSampleRate; } - double getMagSq() const { return m_magsq; } - bool isPllLocked() const { return m_running.m_pll && m_pll.locked(); } - Real getPllFrequency() const { return m_pll.getFrequency(); } - Real getPllDeltaPhase() const { return m_pll.getDeltaPhi(); } - Real getPllPhase() const { return m_pll.getPhiHat(); } - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); - 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 = objectName(); } - virtual qint64 getCenterFrequency() const { return m_running.m_frequency; } - - virtual QByteArray serialize() const { return QByteArray(); } - virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - - struct Config - { - int m_frequency; - int m_inputSampleRate; - int m_channelSampleRate; - Real m_Bandwidth; - Real m_LowCutoff; - int m_spanLog2; - bool m_ssb; - bool m_pll; - unsigned int m_pllPskOrder; - - Config() : - m_frequency(0), - m_inputSampleRate(96000), - m_channelSampleRate(96000), - m_Bandwidth(5000), - m_LowCutoff(300), - m_spanLog2(3), - m_ssb(false), - m_pll(false), - m_pllPskOrder(1) - {} - }; - - Config m_config; - Config m_running; - - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_undersampleCount; - fftfilt::cmplx m_sum; - bool m_usb; - double m_magsq; - bool m_useInterpolator; - - NCOF m_nco; - PhaseLockComplex m_pll; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - - fftfilt* SSBFilter; - fftfilt* DSBFilter; - - BasebandSampleSink* m_sampleSink; - SampleVector m_sampleBuffer; - QMutex m_settingsMutex; - - void apply(bool force = false); - - void processOneSample(Complex& c, fftfilt::cmplx *sideband) - { - int n_out; - int decim = 1<runSSB(c, &sideband, m_usb); - } - else - { - n_out = DSBFilter->runDSB(c, &sideband); - } - - for (int i = 0; i < n_out; i++) - { - // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display - // smart decimation with bit gain using float arithmetic (23 bits significand) - - m_sum += sideband[i]; - - if (!(m_undersampleCount++ & (decim - 1))) // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) - { - m_sum /= decim; - Real re = m_sum.real() / SDR_RX_SCALED; - Real im = m_sum.imag() / SDR_RX_SCALED; - m_magsq = re*re + im*im; - - if (m_running.m_pll) - { - m_pll.feed(re, im); - - // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) - // Real mixI = m_sum.real() * m_pll.getImag() - m_sum.imag() * m_pll.getReal(); - // Real mixQ = m_sum.real() * m_pll.getReal() + m_sum.imag() * m_pll.getImag(); - Real mixI = m_pll.getReal() * SDR_RX_SCALED; - Real mixQ = m_pll.getImag() * SDR_RX_SCALED; - - if (m_running.m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(mixQ, mixI)); - } - else - { - m_sampleBuffer.push_back(Sample(mixI, mixQ)); - } - } - else - { - if (m_running.m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); - } - else - { - m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); - } - } - - m_sum = 0; - } - } - } -}; - -#endif // INCLUDE_CHANALYZERNG_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// 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_CHANALYZERNG_H +#define INCLUDE_CHANALYZERNG_H + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "dsp/interpolator.h" +#include "dsp/ncof.h" +#include "dsp/fftfilt.h" +#include "dsp/phaselockcomplex.h" +#include "audio/audiofifo.h" +#include "util/message.h" + +#define ssbFftLen 1024 + +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; + +class ChannelAnalyzerNG : public BasebandSampleSink, public ChannelSinkAPI { +public: + class MsgConfigureChannelAnalyzer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getChannelSampleRate() const { return m_channelSampleRate; } + Real getBandwidth() const { return m_Bandwidth; } + Real getLoCutoff() const { return m_LowCutoff; } + int getSpanLog2() const { return m_spanLog2; } + bool getSSB() const { return m_ssb; } + bool getPLL() const { return m_pll; } + unsigned int getPLLPSKOrder() const { return m_pllPskOrder; } + + static MsgConfigureChannelAnalyzer* create( + int channelSampleRate, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb, + bool pll, + unsigned int pllPskOrder) + { + return new MsgConfigureChannelAnalyzer( + channelSampleRate, + Bandwidth, + LowCutoff, + spanLog2, + ssb, + pll, + pllPskOrder); + } + + private: + int m_channelSampleRate; + Real m_Bandwidth; + Real m_LowCutoff; + int m_spanLog2; + bool m_ssb; + bool m_pll; + unsigned int m_pllPskOrder; + + MsgConfigureChannelAnalyzer( + int channelSampleRate, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb, + bool pll, + unsigned int pllPskOrder) : + Message(), + m_channelSampleRate(channelSampleRate), + m_Bandwidth(Bandwidth), + m_LowCutoff(LowCutoff), + m_spanLog2(spanLog2), + m_ssb(ssb), + m_pll(pll), + m_pllPskOrder(pllPskOrder) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + class MsgReportChannelSampleRateChanged : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgReportChannelSampleRateChanged* create() + { + return new MsgReportChannelSampleRateChanged(); + } + + private: + + MsgReportChannelSampleRateChanged() : + Message() + { } + }; + + ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI); + virtual ~ChannelAnalyzerNG(); + virtual void destroy() { delete this; } + void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } + + void configure(MessageQueue* messageQueue, + int channelSampleRate, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb, + bool pll, + unsigned int pllPskOrder); + + DownChannelizer *getChannelizer() { return m_channelizer; } + int getInputSampleRate() const { return m_running.m_inputSampleRate; } + int getChannelSampleRate() const { return m_running.m_channelSampleRate; } + double getMagSq() const { return m_magsq; } + bool isPllLocked() const { return m_running.m_pll && m_pll.locked(); } + Real getPllDeltaPhase() const { return m_pll.getDeltaPhi(); } + Real getPllPhase() const { return m_pll.getPhiHat(); } + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); + 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 = objectName(); } + virtual qint64 getCenterFrequency() const { return m_running.m_frequency; } + + virtual QByteArray serialize() const { return QByteArray(); } + virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + + struct Config + { + int m_frequency; + int m_inputSampleRate; + int m_channelSampleRate; + Real m_Bandwidth; + Real m_LowCutoff; + int m_spanLog2; + bool m_ssb; + bool m_pll; + unsigned int m_pllPskOrder; + + Config() : + m_frequency(0), + m_inputSampleRate(96000), + m_channelSampleRate(96000), + m_Bandwidth(5000), + m_LowCutoff(300), + m_spanLog2(3), + m_ssb(false), + m_pll(false), + m_pllPskOrder(1) + {} + }; + + Config m_config; + Config m_running; + + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + + int m_undersampleCount; + fftfilt::cmplx m_sum; + bool m_usb; + double m_magsq; + bool m_useInterpolator; + + NCOF m_nco; + PhaseLockComplex m_pll; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + fftfilt* SSBFilter; + fftfilt* DSBFilter; + + BasebandSampleSink* m_sampleSink; + SampleVector m_sampleBuffer; + QMutex m_settingsMutex; + + void apply(bool force = false); + + void processOneSample(Complex& c, fftfilt::cmplx *sideband) + { + int n_out; + int decim = 1<runSSB(c, &sideband, m_usb); + } + else + { + n_out = DSBFilter->runDSB(c, &sideband); + } + + for (int i = 0; i < n_out; i++) + { + // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display + // smart decimation with bit gain using float arithmetic (23 bits significand) + + m_sum += sideband[i]; + + if (!(m_undersampleCount++ & (decim - 1))) // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) + { + m_sum /= decim; + Real re = m_sum.real() / SDR_RX_SCALED; + Real im = m_sum.imag() / SDR_RX_SCALED; + m_magsq = re*re + im*im; + + if (m_running.m_pll) + { + m_pll.feed(re, im); + + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + Real mixI = m_sum.real() * m_pll.getImag() - m_sum.imag() * m_pll.getReal(); + Real mixQ = m_sum.real() * m_pll.getReal() + m_sum.imag() * m_pll.getImag(); +// Real mixI = m_pll.getReal() * SDR_RX_SCALED; +// Real mixQ = m_pll.getImag() * SDR_RX_SCALED; + + if (m_running.m_ssb & !m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(mixQ, mixI)); + } + else + { + m_sampleBuffer.push_back(Sample(mixI, mixQ)); + } + } + else + { + if (m_running.m_ssb & !m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); + } + else + { + m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); + } + } + + m_sum = 0; + } + } + } +}; + +#endif // INCLUDE_CHANALYZERNG_H diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index afc23019b..e8a862580 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -237,15 +237,6 @@ void ChannelAnalyzerNGGUI::tick() } else { ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); } - - if (ui->pll->isChecked()) - { - int fHz = round(m_channelAnalyzer->getPllFrequency()*m_rate); - ui->pll->setToolTip(tr("PLL lock (f:%1 Hz e:%2 rad p:%3 rad)") - .arg(fHz) - .arg(m_channelAnalyzer->getPllDeltaPhase()) - .arg(m_channelAnalyzer->getPllPhase())); - } } void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) @@ -262,10 +253,6 @@ void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) { - if (!checked && m_usePll) { - ui->pll->setToolTip("PLL lock"); - } - m_usePll = checked; applySettings(); } diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index 4a9611666..e81436311 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -82,7 +82,7 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - m_pllFilt.create(101, m_audioSampleRate, 500.0); + m_pllFilt.create(101, m_audioSampleRate, 200.0); m_pll.computeCoefficients(0.05, 0.707, 1000); m_syncAMBuffIndex = 0; } @@ -374,7 +374,7 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_audioFifo.setSize(sampleRate); m_squelchDelayLine.resize(sampleRate/5); DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate); - m_pllFilt.create(101, sampleRate, 500.0); + m_pllFilt.create(101, sampleRate, 200.0); if (m_settings.m_pll) { m_volumeAGC.resizeNew(sampleRate, 0.003); @@ -383,6 +383,7 @@ void AMDemod::applyAudioSampleRate(int sampleRate) } m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1); + m_pll.setSampleRate(sampleRate); m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index c3440c0f2..b9d2fe255 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -44,9 +44,14 @@ PhaseLockComplex::PhaseLockComplex() : m_yRe(1.0), m_yIm(0.0), m_freq(0.0), + m_freqPrev(0.0), m_lock(0.0), m_lockCount(0), - m_pskOrder(1) + m_pskOrder(1), + m_lockTime1(480), + m_lockTime(2400), + m_lockTimef(2400.0f), + m_lockThreshold(4.8f) { } @@ -83,6 +88,16 @@ void PhaseLockComplex::computeCoefficients(Real wn, Real zeta, Real K) void PhaseLockComplex::setPskOrder(unsigned int order) { m_pskOrder = order > 0 ? order : 1; + reset(); +} + +void PhaseLockComplex::setSampleRate(unsigned int sampleRate) +{ + m_lockTime1 = sampleRate / 100; // 10ms for order 1 + m_lockTime = sampleRate / 20; // 50ms for order > 1 + m_lockTimef = (float) m_lockTime; + m_lockThreshold = m_lockTime * 0.002f; // threshold of 0.002 taking division by lock time into account + reset(); } void PhaseLockComplex::reset() @@ -103,6 +118,7 @@ void PhaseLockComplex::reset() m_yRe = 1.0f; m_yIm = 0.0f; m_freq = 0.0f; + m_freqPrev = 0.0f; m_lock = 0.0f; m_lockCount = 0; } @@ -148,40 +164,69 @@ void PhaseLockComplex::feed(float re, float im) m_phiHat += 2.0*M_PI; } - float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); - m_phiHatPrev = m_phiHat; - - if (m_phiHatCount < 9) + // lock estimation + if (m_pskOrder > 1) { - m_dPhiHatAccum += dPhi; + float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); + + if (m_phiHatCount < (m_lockTime-1)) + { + m_dPhiHatAccum += dPhi; // re-accumulate phase for differential calculation + m_phiHatCount++; + } + else + { + float dPhi11 = (m_dPhiHatAccum - m_phiHat1); // optimized out division by lock time + float dPhi12 = (m_phiHat1 - m_phiHat2); + m_lock = dPhi11 - dPhi12; // second derivative of phase to get lock status + + if ((m_lock > -m_lockThreshold) && (m_lock < m_lockThreshold)) // includes re-multiplication by lock time + { + if (m_lockCount < 20) { // [0..20] + m_lockCount++; + } + } + else + { + if (m_lockCount > 0) { + m_lockCount -= 2; + } + } + + m_phiHat2 = m_phiHat1; + m_phiHat1 = m_dPhiHatAccum; + m_dPhiHatAccum = 0.0f; + m_phiHatCount = 0; + } + + m_phiHatPrev = m_phiHat; } else { - float dPhi1 = (m_phiHat1 - m_dPhiHatAccum) / 10.0f; - float dPhi1Prev = (m_phiHat2 - m_phiHat1) / 10.0f; - m_lock = dPhi1 - dPhi1Prev; // second derivative of phase + m_freq = (m_phiHat - m_phiHatPrev) / (2.0*M_PI); - if ((m_lock > -0.01) && (m_lock < 0.01)) + if (m_freq < -1.0f) { + m_freq += 2.0f; + } else if (m_freq > 1.0f) { + m_freq -= 2.0f; + } + + float dFreq = m_freq - m_freqPrev; + + if ((dFreq > -0.01) && (dFreq < 0.01)) { - if (m_lockCount < 1000) { + if (m_lockCount < (m_lockTime1-1)) { // [0..479] m_lockCount++; } } else { - if (m_lockCount > 0) { - m_lockCount--; - } + m_lockCount = 0; } - m_freq = dPhi1 / 2.0*M_PI; // first derivative of phase - m_phiHat2 = m_phiHat1; - m_phiHat1 = m_dPhiHatAccum; - m_dPhiHatAccum = 0.0f; - m_phiHatCount = 0; + m_phiHatPrev = m_phiHat; + m_freqPrev = m_freq; } - - m_dPhiHatAccum += dPhi; } float PhaseLockComplex::normalizeAngle(float angle) diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index 675c12bb8..340d83dec 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -41,13 +41,14 @@ public: * \param order 0,1: no PSK (CW), 2: BPSK, 4: QPSK, 8: 8-PSK, ... use powers of two for real cases */ void setPskOrder(unsigned int order); + /** Set sample rate information only for frequency and lock condition calculation */ + void setSampleRate(unsigned int sampleRate); void reset(); void feed(float re, float im); const std::complex& getComplex() const { return m_y; } float getReal() const { return m_yRe; } float getImag() const { return m_yIm; } - bool locked() const { return m_lockCount > 500; } - float getFrequency() const { return m_freq; } + bool locked() const { return m_lockCount > (m_pskOrder > 1 ? 15 : (m_lockTime1-2)); } // 6 float getDeltaPhi() const { return m_deltaPhi; } float getPhiHat() const { return m_phiHat; } @@ -75,9 +76,14 @@ private: float m_yRe; float m_yIm; float m_freq; + float m_freqPrev; float m_lock; int m_lockCount; unsigned int m_pskOrder; + int m_lockTime1; + int m_lockTime; + float m_lockTimef; + float m_lockThreshold; }; From 10c56fc47a617cd7654c725b80026535cb552a25 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 16 May 2018 08:42:08 +0200 Subject: [PATCH 415/956] New PLL: experimental lock condition algorithm based on phi hat averaging --- sdrbase/dsp/phaselockcomplex.cpp | 18 +++++++++++++++++- sdrbase/dsp/phaselockcomplex.h | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index b9d2fe255..2ff6d8d76 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -51,7 +51,8 @@ PhaseLockComplex::PhaseLockComplex() : m_lockTime1(480), m_lockTime(2400), m_lockTimef(2400.0f), - m_lockThreshold(4.8f) + m_lockThreshold(4.8f), + m_avgPhi(240) { } @@ -97,6 +98,7 @@ void PhaseLockComplex::setSampleRate(unsigned int sampleRate) m_lockTime = sampleRate / 20; // 50ms for order > 1 m_lockTimef = (float) m_lockTime; m_lockThreshold = m_lockTime * 0.002f; // threshold of 0.002 taking division by lock time into account + m_avgPhi.resize(sampleRate / 200); reset(); } @@ -167,6 +169,20 @@ void PhaseLockComplex::feed(float re, float im) // lock estimation if (m_pskOrder > 1) { +// m_avgPhi(m_phiHat); +// float vPhi = normalizeAngle(m_phiHat - m_avgPhi.asFloat()); +// +// if ((vPhi > -0.2) && (vPhi < 0.2)) // locked condition +// { +// if (m_lockCount < 20) { // [0..20] +// m_lockCount++; +// } +// } +// else // unlocked condition +// { +// m_lockCount = 0; +// } + float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); if (m_phiHatCount < (m_lockTime-1)) diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index 340d83dec..59b475d49 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -24,6 +24,7 @@ #define SDRBASE_DSP_PHASELOCKCOMPLEX_H_ #include "dsp/dsptypes.h" +#include "util/movingaverage.h" #include "export.h" /** General purpose Phase-locked loop using complex analytic signal input. */ @@ -84,6 +85,7 @@ private: int m_lockTime; float m_lockTimef; float m_lockThreshold; + MovingAverageUtilVar m_avgPhi; }; From a1a2078d7d8382cb964fe48419c529b7ccc2b813 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 16 May 2018 14:20:26 +0200 Subject: [PATCH 416/956] New PLL: experimental lock condition algorithm based on phi hat averaging (2) + FLL input and locking mechanixm --- sdrbase/dsp/phaselockcomplex.cpp | 130 +++++++++++++++++++++++-------- sdrbase/dsp/phaselockcomplex.h | 5 +- 2 files changed, 102 insertions(+), 33 deletions(-) diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index 2ff6d8d76..f3286579d 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -52,7 +52,7 @@ PhaseLockComplex::PhaseLockComplex() : m_lockTime(2400), m_lockTimef(2400.0f), m_lockThreshold(4.8f), - m_avgPhi(240) + m_avgF(2400) { } @@ -97,8 +97,8 @@ void PhaseLockComplex::setSampleRate(unsigned int sampleRate) m_lockTime1 = sampleRate / 100; // 10ms for order 1 m_lockTime = sampleRate / 20; // 50ms for order > 1 m_lockTimef = (float) m_lockTime; - m_lockThreshold = m_lockTime * 0.002f; // threshold of 0.002 taking division by lock time into account - m_avgPhi.resize(sampleRate / 200); + m_lockThreshold = m_lockTime * 0.00015f; // threshold of 0.002 taking division by lock time into account + m_avgF.resize(m_lockTime); reset(); } @@ -125,6 +125,44 @@ void PhaseLockComplex::reset() m_lockCount = 0; } +void PhaseLockComplex::feedFLL(float re, float im) +{ + std::complex x(re, im); + m_phiHat1 = std::arg(x); + float dPhi = normalizeAngle(m_phiHat1 - m_phiHat2); // instantanoeus radian valued signal frequency in [-pi..pi] range + m_phiHat2 = m_phiHat1; + + // advance buffer + m_v2 = m_v1; // shift center register to upper register + m_v1 = m_v0; // shift lower register to center register + + // compute new lower register + m_v0 = dPhi - m_v1*m_a1 - m_v2*m_a2; + + // compute new output + float freqHat = m_v0*m_b0 + m_v1*m_b1 + m_v2*m_b2; + + // prevent saturation + if (freqHat > 2.0*M_PI) + { + m_v0 *= (freqHat - 2.0*M_PI) / freqHat; + m_v1 *= (freqHat - 2.0*M_PI) / freqHat; + m_v2 *= (freqHat - 2.0*M_PI) / freqHat; + freqHat -= 2.0*M_PI; + } + + if (freqHat < -2.0*M_PI) + { + m_v0 *= (freqHat + 2.0*M_PI) / freqHat; + m_v1 *= (freqHat + 2.0*M_PI) / freqHat; + m_v2 *= (freqHat + 2.0*M_PI) / freqHat; + freqHat += 2.0*M_PI; + } + + m_phiHat += freqHat; // advance phase estimate with filtered signal frequency + m_freq = freqHat / 2.0*M_PI; +} + void PhaseLockComplex::feed(float re, float im) { m_yRe = cos(m_phiHat); @@ -169,6 +207,35 @@ void PhaseLockComplex::feed(float re, float im) // lock estimation if (m_pskOrder > 1) { + float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); + + m_avgF(dPhi); + + if (m_phiHatCount < (m_lockTime-1)) + { + m_phiHatCount++; + } + else + { + m_freq = m_avgF.asFloat(); + float dFreq = m_freq - m_freqPrev; + + if ((dFreq > -m_lockThreshold) && (dFreq < m_lockThreshold)) + { + if (m_lockCount < 20) { + m_lockCount++; + } + } + else{ + if (m_lockCount > 0) { + m_lockCount--; + } + } + + m_freqPrev = m_freq; + m_phiHatCount = 0; + } + // m_avgPhi(m_phiHat); // float vPhi = normalizeAngle(m_phiHat - m_avgPhi.asFloat()); // @@ -183,39 +250,38 @@ void PhaseLockComplex::feed(float re, float im) // m_lockCount = 0; // } - float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); - if (m_phiHatCount < (m_lockTime-1)) - { - m_dPhiHatAccum += dPhi; // re-accumulate phase for differential calculation - m_phiHatCount++; - } - else - { - float dPhi11 = (m_dPhiHatAccum - m_phiHat1); // optimized out division by lock time - float dPhi12 = (m_phiHat1 - m_phiHat2); - m_lock = dPhi11 - dPhi12; // second derivative of phase to get lock status + // if (m_phiHatCount < (m_lockTime-1)) + // { + // m_dPhiHatAccum += dPhi; // re-accumulate phase for differential calculation + // m_phiHatCount++; + // } + // else + // { + // float dPhi11 = (m_dPhiHatAccum - m_phiHat1); // optimized out division by lock time + // float dPhi12 = (m_phiHat1 - m_phiHat2); + // m_lock = dPhi11 - dPhi12; // second derivative of phase to get lock status - if ((m_lock > -m_lockThreshold) && (m_lock < m_lockThreshold)) // includes re-multiplication by lock time - { - if (m_lockCount < 20) { // [0..20] - m_lockCount++; - } - } - else - { - if (m_lockCount > 0) { - m_lockCount -= 2; - } - } + // if ((m_lock > -m_lockThreshold) && (m_lock < m_lockThreshold)) // includes re-multiplication by lock time + // { + // if (m_lockCount < 20) { // [0..20] + // m_lockCount++; + // } + // } + // else + // { + // if (m_lockCount > 0) { + // m_lockCount -= 2; + // } + // } - m_phiHat2 = m_phiHat1; - m_phiHat1 = m_dPhiHatAccum; - m_dPhiHatAccum = 0.0f; - m_phiHatCount = 0; - } + // m_phiHat2 = m_phiHat1; + // m_phiHat1 = m_dPhiHatAccum; + // m_dPhiHatAccum = 0.0f; + // m_phiHatCount = 0; + // } - m_phiHatPrev = m_phiHat; + // m_phiHatPrev = m_phiHat; } else { diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index 59b475d49..0d3154041 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -45,7 +45,10 @@ public: /** Set sample rate information only for frequency and lock condition calculation */ void setSampleRate(unsigned int sampleRate); void reset(); + /** Feed PLL with a new signa sample */ void feed(float re, float im); + /** Same but turns into a FLL using the same filtering structure and NCO output. No lock condition. */ + void feedFLL(float re, float im); const std::complex& getComplex() const { return m_y; } float getReal() const { return m_yRe; } float getImag() const { return m_yIm; } @@ -85,7 +88,7 @@ private: int m_lockTime; float m_lockTimef; float m_lockThreshold; - MovingAverageUtilVar m_avgPhi; + MovingAverageUtilVar m_avgF; }; From d38d926a87d04732ac4f5ec39aeab7c1d878fc20 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 16 May 2018 18:53:16 +0200 Subject: [PATCH 417/956] New PLL: simple FLL code to be put in its own class later --- sdrbase/dsp/phaselockcomplex.cpp | 62 ++++++++------------------------ sdrbase/dsp/phaselockcomplex.h | 1 + 2 files changed, 15 insertions(+), 48 deletions(-) diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index f3286579d..22693e257 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -41,6 +41,7 @@ PhaseLockComplex::PhaseLockComplex() : m_dPhiHatAccum(0.0), m_phiHatCount(0), m_y(1.0, 0.0), + m_p(1.0, 0.0), m_yRe(1.0), m_yIm(0.0), m_freq(0.0), @@ -116,7 +117,9 @@ void PhaseLockComplex::reset() m_dPhiHatAccum = 0.0f; m_phiHatCount = 0; m_y.real(1.0); - m_y.real(0.0); + m_y.imag(0.0); + m_p.real(1.0); + m_p.imag(0.0); m_yRe = 1.0f; m_yIm = 0.0f; m_freq = 0.0f; @@ -127,40 +130,18 @@ void PhaseLockComplex::reset() void PhaseLockComplex::feedFLL(float re, float im) { + m_yRe = cos(m_phiHat); + m_yIm = sin(m_phiHat); + std::complex y(m_yRe, m_yIm); std::complex x(re, im); - m_phiHat1 = std::arg(x); - float dPhi = normalizeAngle(m_phiHat1 - m_phiHat2); // instantanoeus radian valued signal frequency in [-pi..pi] range - m_phiHat2 = m_phiHat1; + std::complex p = x * m_y; + float cross = m_p.real()*p.imag() - p.real()*m_p.imag(); + float dot = m_p.real()*p.real() + m_p.imag()*p.imag(); + float eF = cross * (dot < 0 ? -1 : 1); // frequency error - // advance buffer - m_v2 = m_v1; // shift center register to upper register - m_v1 = m_v0; // shift lower register to center register - - // compute new lower register - m_v0 = dPhi - m_v1*m_a1 - m_v2*m_a2; - - // compute new output - float freqHat = m_v0*m_b0 + m_v1*m_b1 + m_v2*m_b2; - - // prevent saturation - if (freqHat > 2.0*M_PI) - { - m_v0 *= (freqHat - 2.0*M_PI) / freqHat; - m_v1 *= (freqHat - 2.0*M_PI) / freqHat; - m_v2 *= (freqHat - 2.0*M_PI) / freqHat; - freqHat -= 2.0*M_PI; - } - - if (freqHat < -2.0*M_PI) - { - m_v0 *= (freqHat + 2.0*M_PI) / freqHat; - m_v1 *= (freqHat + 2.0*M_PI) / freqHat; - m_v2 *= (freqHat + 2.0*M_PI) / freqHat; - freqHat += 2.0*M_PI; - } - - m_phiHat += freqHat; // advance phase estimate with filtered signal frequency - m_freq = freqHat / 2.0*M_PI; + m_freq += eF; // correct instantaneous frequency + m_phiHat += eF; // advance phase with instantaneous frequency + m_p = p; // store previous product } void PhaseLockComplex::feed(float re, float im) @@ -236,21 +217,6 @@ void PhaseLockComplex::feed(float re, float im) m_phiHatCount = 0; } -// m_avgPhi(m_phiHat); -// float vPhi = normalizeAngle(m_phiHat - m_avgPhi.asFloat()); -// -// if ((vPhi > -0.2) && (vPhi < 0.2)) // locked condition -// { -// if (m_lockCount < 20) { // [0..20] -// m_lockCount++; -// } -// } -// else // unlocked condition -// { -// m_lockCount = 0; -// } - - // if (m_phiHatCount < (m_lockTime-1)) // { // m_dPhiHatAccum += dPhi; // re-accumulate phase for differential calculation diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index 0d3154041..4e2e64962 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -77,6 +77,7 @@ private: float m_dPhiHatAccum; int m_phiHatCount; std::complex m_y; + std::complex m_p; float m_yRe; float m_yIm; float m_freq; From c495f8223511a55824a84d41c219121a50725fd4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 17 May 2018 00:09:56 +0200 Subject: [PATCH 418/956] Imported Iowa Hills Software IIR and FIR calculator --- CMakeLists.txt | 1 + kitiirfir/CMakeLists.txt | 38 ++ kitiirfir/FFTCode.cpp | 770 +++++++++++++++++++++++++++++++ kitiirfir/FFTCode.h | 60 +++ kitiirfir/FIRFilterCode.cpp | 401 ++++++++++++++++ kitiirfir/FIRFilterCode.h | 36 ++ kitiirfir/FreqSamplingCode.cpp | 228 +++++++++ kitiirfir/FreqSamplingCode.h | 17 + kitiirfir/IIRFilterCode.cpp | 526 +++++++++++++++++++++ kitiirfir/IIRFilterCode.h | 38 ++ kitiirfir/LowPassPrototypes.cpp | 469 +++++++++++++++++++ kitiirfir/LowPassPrototypes.h | 45 ++ kitiirfir/LowPassRoots.cpp | 683 +++++++++++++++++++++++++++ kitiirfir/LowPassRoots.h | 27 ++ kitiirfir/NewParksMcClellan.cpp | 627 +++++++++++++++++++++++++ kitiirfir/NewParksMcClellan.h | 31 ++ kitiirfir/PFiftyOneRevE.cpp | 649 ++++++++++++++++++++++++++ kitiirfir/PFiftyOneRevE.h | 43 ++ kitiirfir/QuadRootsRevH.cpp | 344 ++++++++++++++ kitiirfir/QuadRootsRevH.h | 27 ++ kitiirfir/readme.md | 8 + sdrbase/dsp/phaselockcomplex.cpp | 18 +- sdrbase/dsp/phaselockcomplex.h | 2 - 23 files changed, 5069 insertions(+), 19 deletions(-) create mode 100644 kitiirfir/CMakeLists.txt create mode 100644 kitiirfir/FFTCode.cpp create mode 100644 kitiirfir/FFTCode.h create mode 100644 kitiirfir/FIRFilterCode.cpp create mode 100644 kitiirfir/FIRFilterCode.h create mode 100644 kitiirfir/FreqSamplingCode.cpp create mode 100644 kitiirfir/FreqSamplingCode.h create mode 100644 kitiirfir/IIRFilterCode.cpp create mode 100644 kitiirfir/IIRFilterCode.h create mode 100644 kitiirfir/LowPassPrototypes.cpp create mode 100644 kitiirfir/LowPassPrototypes.h create mode 100644 kitiirfir/LowPassRoots.cpp create mode 100644 kitiirfir/LowPassRoots.h create mode 100644 kitiirfir/NewParksMcClellan.cpp create mode 100644 kitiirfir/NewParksMcClellan.h create mode 100644 kitiirfir/PFiftyOneRevE.cpp create mode 100644 kitiirfir/PFiftyOneRevE.h create mode 100644 kitiirfir/QuadRootsRevH.cpp create mode 100644 kitiirfir/QuadRootsRevH.h create mode 100644 kitiirfir/readme.md diff --git a/CMakeLists.txt b/CMakeLists.txt index f3b26d126..9f5929be5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,6 +219,7 @@ endif() ############################################################################## # base libraries +add_subdirectory(kitiirfir) add_subdirectory(sdrbase) add_subdirectory(sdrgui) add_subdirectory(sdrsrv) diff --git a/kitiirfir/CMakeLists.txt b/kitiirfir/CMakeLists.txt new file mode 100644 index 000000000..0cb88df33 --- /dev/null +++ b/kitiirfir/CMakeLists.txt @@ -0,0 +1,38 @@ +project(kitiirfir) + +set(kitiirfir_SOURCES + FFTCode.cpp + FIRFilterCode.cpp + FreqSamplingCode.cpp + IIRFilterCode.cpp + LowPassPrototypes.cpp + LowPassRoots.cpp + NewParksMcClellan.cpp + PFiftyOneRevE.cpp + QuadRootsRevH.cpp +) + +set(kitiirfir_HEADERS + FFTCode.h + FIRFilterCode.h + FreqSamplingCode.h + IIRFilterCode.h + LowPassPrototypes.h + LowPassRoots.h + NewParksMcClellan.h + PFiftyOneRevE.h + QuadRootsRevH.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} +) + +add_definitions(-DQT_SHARED) + +add_library(kitiirfir SHARED + ${kitiirfir_SOURCES} +) + +install(TARGETS kitiirfir DESTINATION lib) diff --git a/kitiirfir/FFTCode.cpp b/kitiirfir/FFTCode.cpp new file mode 100644 index 000000000..914a95c08 --- /dev/null +++ b/kitiirfir/FFTCode.cpp @@ -0,0 +1,770 @@ +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + If you find a problem, please leave a note at: + http://www.iowahills.com/feedbackcomments.html + June 6, 2016 + + ShowMessage is a C++ Builder function, and it usage has been commented out. + If you are using C++ Builder, include vcl.h for ShowMessage. + Otherwise replace ShowMessage with something appropriate for your compiler. + */ + +#include "FFTCode.h" +#include + +namespace kitiirfir +{ + +//--------------------------------------------------------------------------- +// This calculates the required FFT size for a given number of points. +int RequiredFFTSize(int NumPts) { + int N = MINIMUM_FFT_SIZE; + while (N < NumPts && N < MAXIMUM_FFT_SIZE) { + N *= 2; + } + return N; +} + +//--------------------------------------------------------------------------- + +// This verifies that the FFT Size N = 2^M. M is returned +// N must be >= 8 for the Twiddle calculations +int IsValidFFTSize(int N) { + if (N < MINIMUM_FFT_SIZE || N > MAXIMUM_FFT_SIZE || (N & (N - 1)) != 0) + return (0); // N & (N - 1) ensures a power of 2 + return ((int) (log((double) N) / M_LN2 + 0.5)); // return M where N = 2^M +} + +//--------------------------------------------------------------------------- + +// Fast Fourier Transform +// This code puts DC in bin 0 and scales the output of a forward transform by 1/N. +// InputR and InputI are the real and imaginary input arrays of length N. +// The output values are returned in the Input arrays. +// TTransFormType is either FORWARD or INVERSE (defined in the header file) +// 256 pts in 50 us +void FFT(double *InputR, double *InputI, int N, TTransFormType Type) { + int j, LogTwoOfN, *RevBits; + double *BufferR, *BufferI, *TwiddleR, *TwiddleI; + double OneOverN; + + // Verify the FFT size and type. + LogTwoOfN = IsValidFFTSize(N); + if (LogTwoOfN == 0 || (Type != FORWARD && Type != INVERSE)) { + // ShowMessage("Invalid FFT type or size."); + return; + } + + // Memory allocation for all the arrays. + BufferR = new double[N]; + BufferI = new double[N]; + TwiddleR = new double[N / 2]; + TwiddleI = new double[N / 2]; + RevBits = new int[N]; + + if (BufferR == NULL || BufferI == NULL || TwiddleR == NULL + || TwiddleI == NULL || RevBits == NULL) { + // ShowMessage("FFT Memory Allocation Error"); + return; + } + + ReArrangeInput(InputR, InputI, BufferR, BufferI, RevBits, N); + FillTwiddleArray(TwiddleR, TwiddleI, N, Type); + Transform(InputR, InputI, BufferR, BufferI, TwiddleR, TwiddleI, N); + + // The ReArrangeInput function swapped Input[] and Buffer[]. Then Transform() + // swapped them again, LogTwoOfN times. Ultimately, these swaps must be done + // an even number of times, or the pointer to Buffer gets returned. + // So we must do one more swap here, for N = 16, 64, 256, 1024, ... + OneOverN = 1.0; + if (Type == FORWARD) + OneOverN = 1.0 / (double) N; + + if (LogTwoOfN % 2 == 1) { + for (j = 0; j < N; j++) + InputR[j] = InputR[j] * OneOverN; + for (j = 0; j < N; j++) + InputI[j] = InputI[j] * OneOverN; + } else // if(LogTwoOfN % 2 == 0) then the results are still in Buffer. + { + for (j = 0; j < N; j++) + InputR[j] = BufferR[j] * OneOverN; + for (j = 0; j < N; j++) + InputI[j] = BufferI[j] * OneOverN; + } + + delete[] BufferR; + delete[] BufferI; + delete[] TwiddleR; + delete[] TwiddleI; + delete[] RevBits; +} +//--------------------------------------------------------------------------- + +// This puts the input arrays in bit reversed order. +// The while loop generates an array of bit reversed numbers. e.g. +// e.g. N=8: RevBits = 0,4,2,6,1,5,3,7 N=16: RevBits = 0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15 +void ReArrangeInput(double *InputR, double *InputI, double *BufferR, + double *BufferI, int *RevBits, int N) { + int j, k, J, K; + + J = N / 2; + K = 1; + RevBits[0] = 0; + while (J >= 1) { + for (k = 0; k < K; k++) { + RevBits[k + K] = RevBits[k] + J; + } + K *= 2; + J /= 2; + } + + // Move the rearranged input values to Buffer. + // Take note of the pointer swaps at the top of the transform algorithm. + for (j = 0; j < N; j++) { + BufferR[j] = InputR[RevBits[j]]; + BufferI[j] = InputI[RevBits[j]]; + } + +} + +//--------------------------------------------------------------------------- + +/* + The Pentium takes a surprising amount of time to calculate the sine and cosine. + You may want to make the twiddle arrays static if doing repeated FFTs of the same size. + This uses 4 fold symmetry to calculate the twiddle factors. As a result, this function + requires a minimum FFT size of 8. + */ +void FillTwiddleArray(double *TwiddleR, double *TwiddleI, int N, + TTransFormType Type) { + int j; + double Theta, TwoPiOverN; + + TwoPiOverN = M_2PI / (double) N; + + if (Type == FORWARD) { + TwiddleR[0] = 1.0; + TwiddleI[0] = 0.0; + TwiddleR[N / 4] = 0.0; + TwiddleI[N / 4] = -1.0; + TwiddleR[N / 8] = M_SQRT_2; + TwiddleI[N / 8] = -M_SQRT_2; + TwiddleR[3 * N / 8] = -M_SQRT_2; + TwiddleI[3 * N / 8] = -M_SQRT_2; + for (j = 1; j < N / 8; j++) { + Theta = (double) j * -TwoPiOverN; + TwiddleR[j] = cos(Theta); + TwiddleI[j] = sin(Theta); + TwiddleR[N / 4 - j] = -TwiddleI[j]; + TwiddleI[N / 4 - j] = -TwiddleR[j]; + TwiddleR[N / 4 + j] = TwiddleI[j]; + TwiddleI[N / 4 + j] = -TwiddleR[j]; + TwiddleR[N / 2 - j] = -TwiddleR[j]; + TwiddleI[N / 2 - j] = TwiddleI[j]; + } + } + + else { + TwiddleR[0] = 1.0; + TwiddleI[0] = 0.0; + TwiddleR[N / 4] = 0.0; + TwiddleI[N / 4] = 1.0; + TwiddleR[N / 8] = M_SQRT_2; + TwiddleI[N / 8] = M_SQRT_2; + TwiddleR[3 * N / 8] = -M_SQRT_2; + TwiddleI[3 * N / 8] = M_SQRT_2; + for (j = 1; j < N / 8; j++) { + Theta = (double) j * TwoPiOverN; + TwiddleR[j] = cos(Theta); + TwiddleI[j] = sin(Theta); + TwiddleR[N / 4 - j] = TwiddleI[j]; + TwiddleI[N / 4 - j] = TwiddleR[j]; + TwiddleR[N / 4 + j] = -TwiddleI[j]; + TwiddleI[N / 4 + j] = TwiddleR[j]; + TwiddleR[N / 2 - j] = -TwiddleR[j]; + TwiddleI[N / 2 - j] = TwiddleI[j]; + } + } + +} + +//--------------------------------------------------------------------------- + +// The Fast Fourier Transform. +void Transform(double *InputR, double *InputI, double *BufferR, double *BufferI, + double *TwiddleR, double *TwiddleI, int N) { + int j, k, J, K, I, T; + double *TempPointer; + double TempR, TempI; + + J = N / 2; // J increments down to 1 + K = 1; // K increments up to N/2 + while (J > 0) // Loops Log2(N) times. + { + // Swap pointers, instead doing this: for(j=0; j 0.0) + Mag = sqrt(Mag); + else + Mag = 1.0E-12; + + return (Mag); +} + +//--------------------------------------------------------------------------- + +/* + These are the window definitions. These windows can be used for either + FIR filter design or with an FFT for spectral analysis. + For definitions, see this article: http://en.wikipedia.org/wiki/Window_function + + This function has 6 inputs + Data is the array, of length N, containing the data to to be windowed. + This data is either an FIR filter sinc pulse, or the data to be analyzed by an fft. + + WindowType is an enum defined in the header file. + e.g. wtKAISER, wtSINC, wtHANNING, wtHAMMING, wtBLACKMAN, ... + + Alpha sets the width of the flat top. + Windows such as the Tukey and Trapezoid are defined to have a variably wide flat top. + As can be seen by its definition, the Tukey is just a Hanning window with a flat top. + Alpha can be used to give any of these windows a partial flat top, except the Flattop and Kaiser. + Alpha = 0 gives the original window. (i.e. no flat top) + To generate a Tukey window, use a Hanning with 0 < Alpha < 1 + To generate a Bartlett window (triangular), use a Trapezoid window with Alpha = 0. + Alpha = 1 generates a rectangular window in all cases. (except the Flattop and Kaiser) + + + Beta is used with the Kaiser, Sinc, and Sine windows only. + These three windows are used primarily for FIR filter design. Then + Beta controls the filter's transition bandwidth and the sidelobe levels. + All other windows ignore Beta. + + UnityGain controls whether the gain of these windows is set to unity. + Only the Flattop window has unity gain by design. The Hanning window, for example, has a gain + of 1/2. UnityGain = true sets the gain to 1, which preserves the signal's energy level + when these windows are used for spectral analysis. + + Don't use this with FIR filter design however. Since most of the enegy in an FIR sinc pulse + is in the middle of the window, the window needs a peak amplitude of one, not unity gain. + Setting UnityGain = true will simply cause the resulting FIR filter to have excess gain. + + If using these windows for FIR filters, start with the Kaiser, Sinc, or Sine windows and + adjust Beta for the desired transition BW and sidelobe levels (set Alpha = 0). + While the FlatTop is an excellent window for spectral analysis, don't use it for FIR filter design. + It has a peak amplitude of ~ 4.7 which causes the resulting FIR filter to have about this much gain. + It works poorly for FIR filters even if you adjust its peak amplitude. + The Trapezoid also works poorly for FIR filter design. + + If using these windows with an fft for spectral analysis, start with the Hanning, Gauss, or Flattop. + When choosing a window for spectral analysis, you must trade off between resolution and amplitude + accuracy. The Hanning has the best resolution while the Flatop has the best amplitude accuracy. + The Gauss is midway between these two for both accuracy and resolution. These three were + the only windows available in the HP 89410A Vector Signal Analyzer. Which is to say, these three + are the probably the best windows for general purpose signal analysis. + */ + +void WindowData(double *Data, int N, TWindowType WindowType, double Alpha, + double Beta, bool UnityGain) { + if (WindowType == wtNONE) + return; + + int j, M, TopWidth; + double dM, *WinCoeff; + + if (WindowType == wtKAISER || WindowType == wtFLATTOP) + Alpha = 0.0; + + if (Alpha < 0.0) + Alpha = 0.0; + if (Alpha > 1.0) + Alpha = 1.0; + + if (Beta < 0.0) + Beta = 0.0; + if (Beta > 10.0) + Beta = 10.0; + + WinCoeff = new double[N + 2]; + if (WinCoeff == NULL) { + // ShowMessage("Failed to allocate memory in WindowData() "); + return; + } + + TopWidth = (int) (Alpha * (double) N); + if (TopWidth % 2 != 0) + TopWidth++; + if (TopWidth > N) + TopWidth = N; + M = N - TopWidth; + dM = M + 1; + + // Calculate the window for N/2 points, then fold the window over (at the bottom). + // TopWidth points will be set to 1. + if (WindowType == wtKAISER) { + double Arg; + for (j = 0; j < M; j++) { + Arg = Beta * sqrt(1.0 - pow(((double) (2 * j + 2) - dM) / dM, 2.0)); + WinCoeff[j] = Bessel(Arg) / Bessel(Beta); + } + } + + else if (WindowType == wtSINC) // Lanczos + { + for (j = 0; j < M; j++) + WinCoeff[j] = Sinc((double) (2 * j + 1 - M) / dM * M_PI); + for (j = 0; j < M; j++) + WinCoeff[j] = pow(WinCoeff[j], Beta); + } + + else if (WindowType == wtSINE) // Hanning if Beta = 2 + { + for (j = 0; j < M / 2; j++) + WinCoeff[j] = sin((double) (j + 1) * M_PI / dM); + for (j = 0; j < M / 2; j++) + WinCoeff[j] = pow(WinCoeff[j], Beta); + } + + else if (WindowType == wtHANNING) { + for (j = 0; j < M / 2; j++) + WinCoeff[j] = 0.5 - 0.5 * cos((double) (j + 1) * M_2PI / dM); + } + + else if (WindowType == wtHAMMING) { + for (j = 0; j < M / 2; j++) + WinCoeff[j] = 0.54 - 0.46 * cos((double) (j + 1) * M_2PI / dM); + } + + else if (WindowType == wtBLACKMAN) { + for (j = 0; j < M / 2; j++) { + WinCoeff[j] = 0.42 - 0.50 * cos((double) (j + 1) * M_2PI / dM) + + 0.08 * cos((double) (j + 1) * M_2PI * 2.0 / dM); + } + } + + // Defined at: http://www.bth.se/fou/forskinfo.nsf/0/130c0940c5e7ffcdc1256f7f0065ac60/$file/ICOTA_2004_ttr_icl_mdh.pdf + else if (WindowType == wtFLATTOP) { + for (j = 0; j <= M / 2; j++) { + WinCoeff[j] = 1.0 + - 1.93293488969227 * cos((double) (j + 1) * M_2PI / dM) + + 1.28349769674027 + * cos((double) (j + 1) * M_2PI * 2.0 / dM) + - 0.38130801681619 + * cos((double) (j + 1) * M_2PI * 3.0 / dM) + + 0.02929730258511 + * cos((double) (j + 1) * M_2PI * 4.0 / dM); + } + } + + else if (WindowType == wtBLACKMAN_HARRIS) { + for (j = 0; j < M / 2; j++) { + WinCoeff[j] = 0.35875 - 0.48829 * cos((double) (j + 1) * M_2PI / dM) + + 0.14128 * cos((double) (j + 1) * M_2PI * 2.0 / dM) + - 0.01168 * cos((double) (j + 1) * M_2PI * 3.0 / dM); + } + } + + else if (WindowType == wtBLACKMAN_NUTTALL) { + for (j = 0; j < M / 2; j++) { + WinCoeff[j] = 0.3535819 + - 0.4891775 * cos((double) (j + 1) * M_2PI / dM) + + 0.1365995 * cos((double) (j + 1) * M_2PI * 2.0 / dM) + - 0.0106411 * cos((double) (j + 1) * M_2PI * 3.0 / dM); + } + } + + else if (WindowType == wtNUTTALL) { + for (j = 0; j < M / 2; j++) { + WinCoeff[j] = 0.355768 + - 0.487396 * cos((double) (j + 1) * M_2PI / dM) + + 0.144232 * cos((double) (j + 1) * M_2PI * 2.0 / dM) + - 0.012604 * cos((double) (j + 1) * M_2PI * 3.0 / dM); + } + } + + else if (WindowType == wtKAISER_BESSEL) { + for (j = 0; j <= M / 2; j++) { + WinCoeff[j] = 0.402 - 0.498 * cos(M_2PI * (double) (j + 1) / dM) + + 0.098 * cos(2.0 * M_2PI * (double) (j + 1) / dM) + + 0.001 * cos(3.0 * M_2PI * (double) (j + 1) / dM); + } + } + + else if (WindowType == wtTRAPEZOID) // Rectangle for Alpha = 1 Triangle for Alpha = 0 + { + int K = M / 2; + if (M % 2) + K++; + for (j = 0; j < K; j++) + WinCoeff[j] = (double) (j + 1) / (double) K; + } + + // This definition is from http://en.wikipedia.org/wiki/Window_function (Gauss Generalized normal window) + // We set their p = 2, and use Alpha in the numerator, instead of Sigma in the denominator, as most others do. + // Alpha = 2.718 puts the Gauss window response midway between the Hanning and the Flattop (basically what we want). + // It also gives the same BW as the Gauss window used in the HP 89410A Vector Signal Analyzer. + else if (WindowType == wtGAUSS) { + for (j = 0; j < M / 2; j++) { + WinCoeff[j] = ((double) (j + 1) - dM / 2.0) / (dM / 2.0) * 2.7183; + WinCoeff[j] *= WinCoeff[j]; + WinCoeff[j] = exp(-WinCoeff[j]); + } + } + + else // Error. + { + // ShowMessage("Incorrect window type in WindowFFTData"); + delete[] WinCoeff; + return; + } + + // Fold the coefficients over. + for (j = 0; j < M / 2; j++) + WinCoeff[N - j - 1] = WinCoeff[j]; + + // This is the flat top if Alpha > 0. Cannot be applied to a Kaiser or Flat Top. + if (WindowType != wtKAISER && WindowType != wtFLATTOP) { + for (j = M / 2; j < N - M / 2; j++) + WinCoeff[j] = 1.0; + } + + // UnityGain = true will set the gain of these windows to 1. Don't use this with FIR filter design. + if (UnityGain) { + double Sum = 0.0; + for (j = 0; j < N; j++) + Sum += WinCoeff[j]; + Sum /= (double) N; + if (Sum != 0.0) + for (j = 0; j < N; j++) + WinCoeff[j] /= Sum; + } + + // Apply the window to the data. + for (j = 0; j < N; j++) + Data[j] *= WinCoeff[j]; + + delete[] WinCoeff; + +} + +//--------------------------------------------------------------------------- + +// This gets used with the Kaiser window. +double Bessel(double x) { + double Sum = 0.0, XtoIpower; + int i, j, Factorial; + for (i = 1; i < 10; i++) { + XtoIpower = pow(x / 2.0, (double) i); + Factorial = 1; + for (j = 1; j <= i; j++) + Factorial *= j; + Sum += pow(XtoIpower / (double) Factorial, 2.0); + } + return (1.0 + Sum); +} + +//----------------------------------------------------------------------------- + +// This gets used with the Sinc window. +double Sinc(double x) { + if (x > -1.0E-5 && x < 1.0E-5) + return (1.0); + return (sin(x) / x); +} + +//--------------------------------------------------------------------------- + +} // namespace diff --git a/kitiirfir/FFTCode.h b/kitiirfir/FFTCode.h new file mode 100644 index 000000000..59bdb4388 --- /dev/null +++ b/kitiirfir/FFTCode.h @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------------- + +#ifndef FFTCodeH +#define FFTCodeH + +#define M_2PI 6.28318530717958647692 // 2*Pi +#define M_SQRT_2 0.707106781186547524401 // sqrt(2)/2 +#define MAXIMUM_FFT_SIZE 1048576 +#define MINIMUM_FFT_SIZE 8 + +namespace kitiirfir +{ + +//--------------------------------------------------------------------------- +// Must retain the order on the 1st line for legacy FIR code. +enum TWindowType { + wtFIRSTWINDOW, + wtNONE, + wtKAISER, + wtSINC, + wtHANNING, + wtHAMMING, + wtBLACKMAN, + wtFLATTOP, + wtBLACKMAN_HARRIS, + wtBLACKMAN_NUTTALL, + wtNUTTALL, + wtKAISER_BESSEL, + wtTRAPEZOID, + wtGAUSS, + wtSINE, + wtTEST +}; + +enum TTransFormType { + FORWARD, INVERSE +}; + +int RequiredFFTSize(int NumPts); +int IsValidFFTSize(int x); +void FFT(double *InputR, double *InputI, int N, TTransFormType Type); +void ReArrangeInput(double *InputR, double *InputI, double *BufferR, + double *BufferI, int *RevBits, int N); +void FillTwiddleArray(double *TwiddleR, double *TwiddleI, int N, + TTransFormType Type); +void Transform(double *InputR, double *InputI, double *BufferR, double *BufferI, + double *TwiddleR, double *TwiddleI, int N); +void DFT(double *InputR, double *InputI, int N, int Type); +void RealSigDFT(double *Samples, double *OutputR, double *OutputI, int N); +double SingleFreqDFT(double *Samples, int N, double Omega); +double Goertzel(double *Samples, int N, double Omega); +void WindowData(double *Data, int N, TWindowType WindowType, double Alpha, + double Beta, bool UnityGain); +double Bessel(double x); +double Sinc(double x); + +} // namespace + +#endif + diff --git a/kitiirfir/FIRFilterCode.cpp b/kitiirfir/FIRFilterCode.cpp new file mode 100644 index 000000000..f3cb9186f --- /dev/null +++ b/kitiirfir/FIRFilterCode.cpp @@ -0,0 +1,401 @@ +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + If you find a problem, please leave a note at: + http://www.iowahills.com/feedbackcomments.html + May 1, 2016 + + ShowMessage is a C++ Builder function, and it usage has been commented out. + If you are using C++ Builder, include vcl.h for ShowMessage. + Otherwise replace ShowMessage with something appropriate for yor compiler. + + RectWinFIR() generates the impulse response for a rectangular windowed low pass, high pass, + band pass, or notch filter. Then a window, such as the Kaiser, is applied to the FIR coefficients. + See the FilterKitMain.cpp file for an example on how to use this code. + + double FirCoeff[MAXNUMTAPS]; + int NumTaps; NumTaps can be even or odd and < MAXNUMTAPS + TPassTypeName PassType; PassType is defined in the header file. firLPF, firHPF, firBPF, firNOTCH, firALLPASS + double OmegaC 0.0 < OmegaC < 1.0 The corner freq, or center freq if BPF or NOTCH + double BW 0.0 < BW < 1.0 The band width if BPF or NOTCH + */ + +#include "FIRFilterCode.h" +#include + +namespace kitiirfir +{ + +// Rectangular Windowed FIR. The equations used here are developed in numerous textbooks. +void RectWinFIR(double *FirCoeff, int NumTaps, TFIRPassTypes PassType, + double OmegaC, double BW) { + int j; + double Arg, OmegaLow, OmegaHigh; + + switch (PassType) { + case firLPF: // Low Pass + for (j = 0; j < NumTaps; j++) { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + FirCoeff[j] = OmegaC * Sinc(OmegaC * Arg * M_PI); + } + break; + + case firHPF: // High Pass + if (NumTaps % 2 == 1) // Odd tap counts + { + for (j = 0; j < NumTaps; j++) { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + FirCoeff[j] = Sinc(Arg * M_PI) + - OmegaC * Sinc(OmegaC * Arg * M_PI); + } + } + + else // Even tap counts + { + for (j = 0; j < NumTaps; j++) { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + if (Arg == 0.0) + FirCoeff[j] = 0.0; + else + FirCoeff[j] = cos(OmegaC * Arg * M_PI) / M_PI / Arg + + cos(Arg * M_PI); + } + } + break; + + case firBPF: // Band Pass + OmegaLow = OmegaC - BW / 2.0; + OmegaHigh = OmegaC + BW / 2.0; + for (j = 0; j < NumTaps; j++) { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + if (Arg == 0.0) + FirCoeff[j] = 0.0; + else + FirCoeff[j] = (cos(OmegaLow * Arg * M_PI) + - cos(OmegaHigh * Arg * M_PI)) / M_PI / Arg; + } + break; + + case firNOTCH: // Notch, if NumTaps is even, the response at Pi is attenuated. + OmegaLow = OmegaC - BW / 2.0; + OmegaHigh = OmegaC + BW / 2.0; + for (j = 0; j < NumTaps; j++) { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + FirCoeff[j] = Sinc(Arg * M_PI) + - OmegaHigh * Sinc(OmegaHigh * Arg * M_PI) + - OmegaLow * Sinc(OmegaLow * Arg * M_PI); + } + break; + + case firALLPASS: // All Pass, this is trivial, but it shows how an fir all pass (delay) can be done. + for (j = 0; j < NumTaps; j++) + FirCoeff[j] = 0.0; + FirCoeff[(NumTaps - 1) / 2] = 1.0; + break; + case firNOT_FIR: + default: + break; + } + // Now use the FIRFilterWindow() function to reduce the sinc(x) effects. +} + +//--------------------------------------------------------------------------- + +// Used to reduce the sinc(x) effects on a set of FIR coefficients. This will, unfortunately, +// widen the filter's transition band, but the stop band attenuation will improve dramatically. +void FIRFilterWindow(double *FIRCoeff, int N, TWindowType WindowType, + double Beta) { + if (WindowType == wtNONE) + return; + + int j; + double dN, *WinCoeff; + + if (Beta < 0.0) + Beta = 0.0; + if (Beta > 10.0) + Beta = 10.0; + + WinCoeff = new double[N + 2]; + if (WinCoeff == NULL) { + // ShowMessage("Failed to allocate memory in WindowData() "); + return; + } + + // Calculate the window for N/2 points, then fold the window over (at the bottom). + dN = N + 1; // a double + if (WindowType == wtKAISER) { + double Arg; + for (j = 0; j < N; j++) { + Arg = Beta * sqrt(1.0 - pow(((double) (2 * j + 2) - dN) / dN, 2.0)); + WinCoeff[j] = Bessel(Arg) / Bessel(Beta); + } + } + + else if (WindowType == wtSINC) // Lanczos + { + for (j = 0; j < N; j++) + WinCoeff[j] = Sinc((double) (2 * j + 1 - N) / dN * M_PI); + for (j = 0; j < N; j++) + WinCoeff[j] = pow(WinCoeff[j], Beta); + } + + else if (WindowType == wtSINE) // Hanning if Beta = 2 + { + for (j = 0; j < N / 2; j++) + WinCoeff[j] = sin((double) (j + 1) * M_PI / dN); + for (j = 0; j < N / 2; j++) + WinCoeff[j] = pow(WinCoeff[j], Beta); + } + + else // Error. + { + // ShowMessage("Incorrect window type in WindowFFTData"); + delete[] WinCoeff; + return; + } + + // Fold the coefficients over. + for (j = 0; j < N / 2; j++) + WinCoeff[N - j - 1] = WinCoeff[j]; + + // Apply the window to the FIR coefficients. + for (j = 0; j < N; j++) + FIRCoeff[j] *= WinCoeff[j]; + + delete[] WinCoeff; + +} + +//--------------------------------------------------------------------------- + +// This implements an FIR filter. The register shifts are done by rotating the indexes. +void FilterWithFIR(double *FirCoeff, int NumTaps, double *Signal, + double *FilteredSignal, int NumSigPts) { + int j, k, n, Top = 0; + double y, Reg[MAX_NUMTAPS]; + + for (j = 0; j < NumTaps; j++) + Reg[j] = 0.0; + + for (j = 0; j < NumSigPts; j++) { + Reg[Top] = Signal[j]; + y = 0.0; + n = 0; + + // The FirCoeff index increases while the Reg index decreases. + for (k = Top; k >= 0; k--) { + y += FirCoeff[n++] * Reg[k]; + } + for (k = NumTaps - 1; k > Top; k--) { + y += FirCoeff[n++] * Reg[k]; + } + FilteredSignal[j] = y; + + Top++; + if (Top >= NumTaps) + Top = 0; + } + +} + +//--------------------------------------------------------------------------- + +// This code is equivalent to the code above. It uses register shifts, which makes it +// less efficient, but it is easier to follow (i.e. compare to a FIR flow chart). +void FilterWithFIR2(double *FirCoeff, int NumTaps, double *Signal, + double *FilteredSignal, int NumSigPts) { + int j, k; + double y, Reg[MAX_NUMTAPS]; + + for (j = 0; j < NumTaps; j++) + Reg[j] = 0.0; // Init the delay registers. + + for (j = 0; j < NumSigPts; j++) { + // Shift the register values down and set Reg[0]. + for (k = NumTaps; k > 1; k--) + Reg[k - 1] = Reg[k - 2]; + Reg[0] = Signal[j]; + + y = 0.0; + for (k = 0; k < NumTaps; k++) + y += FirCoeff[k] * Reg[k]; + FilteredSignal[j] = y; + } + +} + +//--------------------------------------------------------------------------- + +// This function is used to correct the corner frequency values on FIR filters. +// We normally specify the 3 dB frequencies when specifing a filter. The Parks McClellan routine +// uses OmegaC and BW to set the 0 dB band edges, so its final OmegaC and BW values are not close +// to -3 dB. The Rectangular Windowed filters are better for high tap counts, but for low tap counts, +// their 3 dB frequencies are also well off the mark. + +// To use this function, first calculate a set of FIR coefficients, then pass them here, along with +// OmegaC and BW. This calculates a corrected OmegaC for low and high pass filters. It calcultes a +// corrected BW for band pass and notch filters. Use these corrected values to recalculate the FIR filter. + +// The Goertzel algorithm is used to calculate the filter's magnitude response at the single +// frequency defined in the loop. We start in the pass band and work out to the -20dB freq. + +void FIRFreqError(double *Coeff, int NumTaps, int PassType, double *OmegaC, + double *BW) { + int j, J3dB, CenterJ; + double Omega, CorrectedOmega, CorrectedBW, Omega1, Omega2, Mag; + + // In these loops, we break at -20 dB to ensure that large ripple is ignored. + if (PassType == firLPF) { + J3dB = 10; + for (j = 0; j < NUM_FREQ_ERR_PTS; j++) { + Omega = (double) j / dNUM_FREQ_ERR_PTS; + Mag = Goertzel(Coeff, NumTaps, Omega); + if (Mag > 0.707) + J3dB = j; // J3dB will be the last j where the response was > -3 dB + if (Mag < 0.1) + break; // Stop when the response is down to -20 dB. + } + Omega = (double) J3dB / dNUM_FREQ_ERR_PTS; + } + + else if (PassType == firHPF) { + J3dB = NUM_FREQ_ERR_PTS - 10; + for (j = NUM_FREQ_ERR_PTS - 1; j >= 0; j--) { + Omega = (double) j / dNUM_FREQ_ERR_PTS; + Mag = Goertzel(Coeff, NumTaps, Omega); + if (Mag > 0.707) + J3dB = j; // J3dB will be the last j where the response was > -3 dB + if (Mag < 0.1) + break; // Stop when the response is down to -20 dB. + } + Omega = (double) J3dB / dNUM_FREQ_ERR_PTS; + } + + else if (PassType == firBPF) { + CenterJ = (int) (dNUM_FREQ_ERR_PTS * *OmegaC); + J3dB = CenterJ; + for (j = CenterJ; j >= 0; j--) { + Omega = (double) j / dNUM_FREQ_ERR_PTS; + Mag = Goertzel(Coeff, NumTaps, Omega); + if (Mag > 0.707) + J3dB = j; + if (Mag < 0.1) + break; + } + Omega1 = (double) J3dB / dNUM_FREQ_ERR_PTS; + + J3dB = CenterJ; + for (j = CenterJ; j < NUM_FREQ_ERR_PTS; j++) { + Omega = (double) j / dNUM_FREQ_ERR_PTS; + Mag = Goertzel(Coeff, NumTaps, Omega); + if (Mag > 0.707) + J3dB = j; + if (Mag < 0.1) + break; + } + Omega2 = (double) J3dB / dNUM_FREQ_ERR_PTS; + } + + // The code above starts in the pass band. This starts in the stop band. + else // PassType == firNOTCH + { + CenterJ = (int) (dNUM_FREQ_ERR_PTS * *OmegaC); + J3dB = CenterJ; + for (j = CenterJ; j >= 0; j--) { + Omega = (double) j / dNUM_FREQ_ERR_PTS; + Mag = Goertzel(Coeff, NumTaps, Omega); + if (Mag <= 0.707) + J3dB = j; + if (Mag > 0.99) + break; + } + Omega1 = (double) J3dB / dNUM_FREQ_ERR_PTS; + + J3dB = CenterJ; + for (j = CenterJ; j < NUM_FREQ_ERR_PTS; j++) { + Omega = (double) j / dNUM_FREQ_ERR_PTS; + Mag = Goertzel(Coeff, NumTaps, Omega); + if (Mag <= 0.707) + J3dB = j; + if (Mag > 0.99) + break; + } + Omega2 = (double) J3dB / dNUM_FREQ_ERR_PTS; + } + + // This calculates the corrected OmegaC and BW and error checks the values. + if (PassType == firLPF || PassType == firHPF) { + CorrectedOmega = *OmegaC * 2.0 - Omega; // This is usually OK. + if (CorrectedOmega < 0.001) + CorrectedOmega = 0.001; + if (CorrectedOmega > 0.99) + CorrectedOmega = 0.99; + *OmegaC = CorrectedOmega; + } + + else // PassType == firBPF || PassType == firNOTCH + { + CorrectedBW = *BW * 2.0 - (Omega2 - Omega1); // This routinely goes neg with Notch. + if (CorrectedBW < 0.01) + CorrectedBW = 0.01; + if (CorrectedBW > *BW * 2.0) + CorrectedBW = *BW * 2.0; + if (CorrectedBW > 0.98) + CorrectedBW = 0.98; + *BW = CorrectedBW; + } + +} + +//----------------------------------------------------------------------------- + +// This shows how to adjust the delay of an FIR by a fractional amount. +// We take the FFT of the FIR coefficients to get to the frequency domain, +// then apply the Laplace delay operator, and then do an inverse FFT. + +// Use this function last. i.e. After the window was applied to the coefficients. +// The Delay value is in terms of a fraction of a sample (not in terms of sampling freq). +// Delay may be pos or neg. Typically a filter's delay can be adjusted by +/- NumTaps/20 +// without affecting its performance significantly. A typical Delay value would be 0.75 +void AdjustDelay(double *FirCoeff, int NumTaps, double Delay) { + int j, FFTSize; + double *FFTInputR, *FFTInputI, Arg, Temp; + FFTSize = RequiredFFTSize(NumTaps + (int) fabs(Delay) + 1); // Zero pad by at least Delay + 1 to prevent the impulse response from wrapping around. + + FFTInputR = new double[FFTSize]; // Real part + FFTInputI = new double[FFTSize]; // Imag part + if (FFTInputR == NULL || FFTInputI == NULL) { + //ShowMessage("Unable to allocate memory in AdjustDelay"); + return; + } + for (j = 0; j < FFTSize; j++) + FFTInputR[j] = FFTInputI[j] = 0.0; // A mandatory init. + for (j = 0; j < NumTaps; j++) + FFTInputR[j] = FirCoeff[j]; // Fill the real part with the FIR coeff. + + FFT(FFTInputR, FFTInputI, FFTSize, FORWARD); // Do an FFT + for (j = 0; j <= FFTSize / 2; j++) // Apply the Laplace Delay operator e^(-j*omega*Delay). + { + Arg = -Delay * (double) j / (double) FFTSize * M_2PI; // This is -Delay * (the FFT bin frequency). + Temp = cos(Arg) * FFTInputR[j] - sin(Arg) * FFTInputI[j]; + FFTInputI[j] = cos(Arg) * FFTInputI[j] + sin(Arg) * FFTInputR[j]; + FFTInputR[j] = Temp; + } + for (j = 1; j < FFTSize / 2; j++) // Fill the neg freq bins with the conjugate values. + { + FFTInputR[FFTSize - j] = FFTInputR[j]; + FFTInputI[FFTSize - j] = -FFTInputI[j]; + } + + FFT(FFTInputR, FFTInputI, FFTSize, INVERSE); // Inverse FFT + for (j = 0; j < NumTaps; j++) { + FirCoeff[j] = FFTInputR[j]; + } + + delete[] FFTInputR; + delete[] FFTInputI; +} +//----------------------------------------------------------------------------- + +} //namedspace diff --git a/kitiirfir/FIRFilterCode.h b/kitiirfir/FIRFilterCode.h new file mode 100644 index 000000000..ee60fee02 --- /dev/null +++ b/kitiirfir/FIRFilterCode.h @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------------- + +#ifndef FIRFilterCodeH +#define FIRFilterCodeH +#include "FFTCode.h" // For the definition of TWindowType +//--------------------------------------------------------------------------- + +#define MAX_NUMTAPS 256 +#define M_2PI 6.28318530717958647692 +#define NUM_FREQ_ERR_PTS 1000 // these are only used in the FIRFreqError function. +#define dNUM_FREQ_ERR_PTS 1000.0 + +namespace kitiirfir +{ + +enum TFIRPassTypes { + firLPF, firHPF, firBPF, firNOTCH, firALLPASS, firNOT_FIR +}; + +void FilterWithFIR(double *FirCoeff, int NumTaps, double *Signal, + double *FilteredSignal, int NumSigPts); +void FilterWithFIR2(double *FirCoeff, int NumTaps, double *Signal, + double *FilteredSignal, int NumSigPts); +void RectWinFIR(double *FirCoeff, int NumTaps, TFIRPassTypes PassType, + double OmegaC, double BW); +void WindowData(double *Data, int N, TWindowType WindowType, double Alpha, + double Beta, bool UnityGain); +void FIRFreqError(double *Coeff, int NumTaps, int PassType, double *OmegaC, + double *BW); +void FIRFilterWindow(double *FIRCoeff, int N, TWindowType WindowType, + double Beta); +void AdjustDelay(double *FirCoeff, int NumTaps, double Delay); + +} // namespace + +#endif diff --git a/kitiirfir/FreqSamplingCode.cpp b/kitiirfir/FreqSamplingCode.cpp new file mode 100644 index 000000000..64565ec8c --- /dev/null +++ b/kitiirfir/FreqSamplingCode.cpp @@ -0,0 +1,228 @@ +#include +#include +#include "FreqSamplingCode.h" +#include "FIRFilterCode.h" // for the definition of TFIRPassTypes +#include "FFTCode.h" +#include "LowPassPrototypes.h" + +namespace kitiirfir +{ + +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + If you find a problem, please leave a note at: + http://www.iowahills.com/feedbackcomments.html + May 1, 2016 + + This code generates an FIR filter with the frequency over-sampling method. By this we + mean that we always sample the frequency domain at least 1024 times (a large power of + 2 suitable for an FFT) even though we typically want a relatively small number of FIR + coefficients (< 50). + + Most authors use N frequency samples to generate N taps. While valid, this tends to loose a + significant amount of frequency domain information if the tap count is small. + + Using a large number of samples will generate a filter equivalent to those generated with + the equations for a classic Rectangular Windowed FIR filter. Those equations were derived + by using an infinite number of frequency samples, which is to say, performing an integral. + + To see how well this works, use this code to sample a simple rectanglular low pass response + and compare the results to the coefficients generated by Windowed FIR code used the + BasicFIR() function in FIRFilterCode.cpp + + See the example code in FilterKitMain for an example of using SampledFreqFIR(). + This function is called after the HofSReal array is filled with the desired magnitude response. + The only trick to this method is getting the phase set correctly. There are two aspects to + setting the phase. Using the correct slope, which is the same for all filters, but does + depend on even or odd tap counts. And we must ensure that the phase value is correct at + Omega = 0.0 and Pi. + + If the filter has a low pass response (magnitude != 0.0 at DC), then the phase at Omega=0 must + be zero. If the filter has a high pass response (magnitude != 0.0 at Pi), then the phase + must be zero of Omega = Pi. + + A band pass filter, which has neither a low or high pass response, can have any phase value at + Omega = 0 and Pi. But a Notch filter, which has both a low and high pass response, must have + zero phase at both zero and Pi. The code below should make this more clear. + + NumTaps Number of FIR taps + FirCoeff The output array for the FIR coefficients. + HofSReal The array containing the desired magnitude response. + HofSImag The imag part of the response (zero when this function is called). + OmegaC The center frequency. (Only needed for notch filters.) + PassType firLPF, firHPF, firBPF, firNOTCH Needed to set the phase properly. (defined in FIRFilterCode.h) + */ + +void SampledFreqFIR(int NumTaps, double *FirCoeff, double *HofSReal, + double *HofSImag, double OmegaC, TFIRPassTypes PassType) { + int j, CenterJ, NumSamples, StartJ; + double dNumSamples, RadPerSample, Arg; + NumSamples = NUM_POS_FREQ_SAMPLES; + dNumSamples = (double) NumSamples; + + // Limit test NumTaps + if (NumTaps > MAX_NUMTAPS) + NumTaps = MAX_NUMTAPS; + if (NumTaps > 2 * NUM_POS_FREQ_SAMPLES) + NumTaps = 2 * NUM_POS_FREQ_SAMPLES; + + // Set the slope of the phase. + RadPerSample = -M_PI_2 * (2.0 * dNumSamples - 1.0) / dNumSamples; // Even tap count. + if (NumTaps % 2 == 1) + RadPerSample = -M_PI; // Odd tap count. + + // Set the phase according to the type of response. + switch (PassType) { + case firLPF: // Low pass and band pass phase = 0 at DC + case firBPF: + for (j = 0; j < NumSamples; j++) { + Arg = RadPerSample * (double) j; // For band pass filters ONLY, an arbitrary amount of phase can be added to Arg. e.g. Add Pi/2 to generate a Hilbert filter, or +/- Pi/4 to 2 different filters to generate a pair of 45 degree Hilberts. + HofSImag[j] = HofSReal[j] * sin(Arg); + HofSReal[j] = HofSReal[j] * cos(Arg); + } + break; + + case firHPF: // High pass phase = 0 at Pi + for (j = NumSamples; j >= 0; j--) { + Arg = RadPerSample * (double) (j - NumSamples); + HofSImag[j] = HofSReal[j] * sin(Arg); + HofSReal[j] = HofSReal[j] * cos(Arg); + } + break; + + case firNOTCH: // Notch phase = 0 at DC and Pi + CenterJ = (int) (OmegaC * dNumSamples); + for (j = 0; j <= CenterJ; j++) { + Arg = RadPerSample * (double) j; + HofSImag[j] = HofSReal[j] * sin(Arg); + HofSReal[j] = HofSReal[j] * cos(Arg); + } + for (j = NumSamples; j >= CenterJ; j--) { + Arg = RadPerSample * (double) (j - NumSamples); + HofSImag[j] = HofSReal[j] * sin(Arg); + HofSReal[j] = HofSReal[j] * cos(Arg); + } + break; + case firALLPASS: + case firNOT_FIR: + default: + break; + } + + // Fill the negative frequency bins of HofS with the conjugate of HofS for the FFT. + for (j = 1; j < NumSamples; j++) + HofSReal[2 * NumSamples - j] = HofSReal[j]; + for (j = 1; j < NumSamples; j++) + HofSImag[2 * NumSamples - j] = -HofSImag[j]; + + // The Fourier Transform requires the center freq bins to be 0 for LPF and BPF, 1 for HPF and Notch + if (PassType == firLPF || PassType == firBPF) { + HofSReal[NumSamples] = 0.0; + HofSImag[NumSamples] = 0.0; + } else { + HofSReal[NumSamples] = 1.0; + HofSImag[NumSamples] = 0.0; + } + + // Do an inverse FFT on HofS to generate the impulse response. On return, HofSImag will be zero. + FFT(HofSReal, HofSImag, 2 * NumSamples, INVERSE); + + // We just generated an impulse response that is 2*NumSamples long. Since we used linear phase + // the response will be symmetric about the center. In general, we only need a small number + // of these taps, so we use the taps from the center of HofSReal, starting at StartJ. + // We also need to scale the FFT's output by the size of the FFT. + StartJ = NumSamples - NumTaps / 2; + for (j = 0; j < NumTaps; j++) + FirCoeff[j] = HofSReal[StartJ + j] / (2.0 * dNumSamples); + +} +//--------------------------------------------------------------------------- + +// This function shows how to sample an analog transfer function H(s) to generate an FIR filter. +// We didn't put much effort into this example because, in general, an analog prototype generates +// a rather poor FIR filter in the sense that it requires such a large number of taps to realize +// the response. For a discussion on this, see this article: +// http://iowahills.com/B2PolynomialFIRFilters.html +// In this example, we generate an FIR low pass from an Inverse Chebyshev low pass prototype. +// We sample the low pass prototype H(s) at frequencies determined by the bilinear transform. +void SampledFreqAnalog(int NumTaps, double *FirCoeff, double *HofSReal, + double *HofSImag, double OmegaC) { + int j, k, NumSamples; + double dNumSamples, Omega, Omega0; + std::complex s, H; + TFIRPassTypes PassType = firLPF; + NumSamples = NUM_POS_FREQ_SAMPLES; + dNumSamples = (double) NumSamples; + + // Limit test NumTaps + if (NumTaps > MAX_NUMTAPS) + NumTaps = MAX_NUMTAPS; + if (NumTaps > 2 * NUM_POS_FREQ_SAMPLES) + NumTaps = 2 * NUM_POS_FREQ_SAMPLES; + + // Define the low pass filter prototype + TLowPassParams LPFProto; // defined in LowPassPrototypes.h + TSPlaneCoeff SCoeff; // defined in LowPassPrototypes.h + LPFProto.ProtoType = INVERSE_CHEBY; // BUTTERWORTH, CHEBYSHEV, GAUSSIAN, BESSEL, ADJUSTABLE, INVERSE_CHEBY, PAPOULIS, ELLIPTIC (defined in LowPassPrototypes.h) + LPFProto.NumPoles = 8; // 1 <= NumPoles <= 12, 15, 20 Depending on the filter. + LPFProto.Ripple = 0.25; // 0.0 <= Ripple <= 1.0 dB Chebyshev and Elliptic (less for high order Chebyshev). + LPFProto.StopBanddB = 60.0; // 20 <= StopBand <= 120 dB Inv Cheby and Elliptic + LPFProto.Gamma = 0.0; // -1.0 <= Gamma <= 1.0 Adjustable Gauss Controls the transition BW. + + // Get the prototype filter's 2nd order s plane coefficients. + SCoeff = CalcLowPassProtoCoeff(LPFProto); + + // Evaluate the prototype's H(s) + Omega0 = 1.0 / tan(OmegaC * M_PI_2); // This sets the corner frequency. + for (j = 0; j < NumSamples; j++) { + Omega = Omega0 * tan(M_PI_2 * (double) j / dNumSamples); // Frequencies per the bilinear transform. + s = std::complex(0.0, Omega); + H = std::complex(1.0, 0.0); + for (k = 0; k < SCoeff.NumSections; k++) { + H *= SCoeff.N2[k] * s * s + SCoeff.N1[k] * s + SCoeff.N0[k]; // The numerator + H /= SCoeff.D2[k] * s * s + SCoeff.D1[k] * s + SCoeff.D0[k]; // The denominator + H *= SCoeff.D0[k] / SCoeff.N0[k]; // The gain constants. + } + HofSReal[j] = H.real(); // We need to do this for the FFT, which uses real arrays. + HofSImag[j] = H.imag(); + } + + // Fill the negative frequency bins of HofS with the conjugate of HofS for the FFT. + for (j = 1; j < NumSamples; j++) + HofSReal[2 * NumSamples - j] = HofSReal[j]; + for (j = 1; j < NumSamples; j++) + HofSImag[2 * NumSamples - j] = -HofSImag[j]; + + // The Fourier Transform requires the center freq bins to be 0 for LPF and BPF, 1 for HPF and Notch + if (PassType == firLPF || PassType == firBPF) { + HofSReal[NumSamples] = 0.0; + HofSImag[NumSamples] = 0.0; + } else { + HofSReal[NumSamples] = 1.0; + HofSImag[NumSamples] = 0.0; + } + + // Do an inverse FFT on HofS to generate the impulse response. On return, HofSImag will be zero. + FFT(HofSReal, HofSImag, 2 * NumSamples, INVERSE); + + // We just generated an impulse response that is 2*NumSamples long. We can use as many of these + // as we like, but if you look at HofSReal you will see that the tail of the response goes to + // zero rather quickly. The desired part of impulse response is at the beginning of HofSReal + // instead of the center because H(s) didn't have linear phase (more like minimum phase). + + // Take the taps from the start of HofSReal and scale by the size of the FFT. + for (j = 0; j < NumTaps; j++) + FirCoeff[j] = HofSReal[j] / (2.0 * dNumSamples); + + // Most FIR responses benefit from the application of a window to reduce the effects of + // truncating the impulse response. But a typical window, such as the Kaiser, can't be used + // because this impulse response isn't symmetric about the center tap. + // A Half Cosine window works quite nicely with these types of responses. + for (j = 0; j < NumTaps; j++) + FirCoeff[j] = FirCoeff[j] * cos(M_PI_2 * j / (double) NumTaps); +} + +//--------------------------------------------------------------------------- + +} // namespace diff --git a/kitiirfir/FreqSamplingCode.h b/kitiirfir/FreqSamplingCode.h new file mode 100644 index 000000000..28c1784b7 --- /dev/null +++ b/kitiirfir/FreqSamplingCode.h @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------------- + +#ifndef FreqSamplingCodeH +#define FreqSamplingCodeH +#include "FIRFilterCode.h" // for the definition of TFIRPassTypes + +#define NUM_POS_FREQ_SAMPLES 1024 // needs to be 1024 for the kit test app + +namespace kitiirfir +{ + +void SampledFreqFIR(int NumTaps, double *FirCoeff, double *HofSReal, double *HofSImag, double OmegaC, TFIRPassTypes PassType); +void SampledFreqAnalog(int NumTaps, double *FirCoeff, double *HofSReal, double *HofSImag, double OmegaC); + +} // namespace + +#endif diff --git a/kitiirfir/IIRFilterCode.cpp b/kitiirfir/IIRFilterCode.cpp new file mode 100644 index 000000000..f700eaca0 --- /dev/null +++ b/kitiirfir/IIRFilterCode.cpp @@ -0,0 +1,526 @@ +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + If you find a problem, please leave a note at: + http://www.iowahills.com/feedbackcomments.html + May 1, 2016 + + ShowMessage is a C++ Builder function, and it usage has been commented out. + If you are using C++ Builder, include vcl.h for ShowMessage. + Otherwise replace ShowMessage with something appropriate for your compiler. + + See the FilterKitMain.cpp file for an example on how to use this code. + */ + +#include "IIRFilterCode.h" +#include "PFiftyOneRevE.h" +#include "LowPassPrototypes.h" +#include +#include + +namespace kitiirfir +{ + +//--------------------------------------------------------------------------- +/* + This calculates the coefficients for IIR filters from a set of 2nd order s plane coefficients + which are obtained by calling CalcLowPassProtoCoeff() in LowPassPrototypes.cpp. + The s plane filters are frequency scaled so their 3 dB frequency is at s = omega = 1 rad/sec. + The poles and zeros are also ordered in a manner appropriate for IIR filters. + For a derivation of the formulas used here, see the IIREquationDerivations.pdf + This shows how the various poly coefficients are defined. + H(s) = ( Ds^2 + Es + F ) / ( As^2 + Bs + C ) + H(z) = ( b2z^2 + b1z + b0 ) / ( a2z^2 + a1z + a0 ) + */ +TIIRCoeff CalcIIRFilterCoeff(TIIRFilterParams IIRFilt) { + int j, k; + double Scalar, SectionGain, Coeff[5]; + double A, B, C, D, E, F, T, Q, Arg; + double a2[ARRAY_DIM], a1[ARRAY_DIM], a0[ARRAY_DIM]; + double b2[ARRAY_DIM], b1[ARRAY_DIM], b0[ARRAY_DIM]; + std::complex Roots[5]; + + TIIRCoeff IIR; // Gets returned by this function. + TLowPassParams LowPassFilt; // Passed to the CalcLowPassProtoCoeff() function. + TSPlaneCoeff SPlaneCoeff; // Filled by the CalcLowPassProtoCoeff() function. + + // We can set the TLowPassParams variables directly from the TIIRFilterParams variables. + LowPassFilt.ProtoType = IIRFilt.ProtoType; + LowPassFilt.NumPoles = IIRFilt.NumPoles; + LowPassFilt.Ripple = IIRFilt.Ripple; + LowPassFilt.Gamma = IIRFilt.Gamma; + LowPassFilt.StopBanddB = IIRFilt.StopBanddB; + + // Get the low pass prototype 2nd order s plane coefficients. + SPlaneCoeff = CalcLowPassProtoCoeff(LowPassFilt); + + // Init the IIR structure. + for (j = 0; j < ARRAY_DIM; j++) { + IIR.a0[j] = 0.0; + IIR.b0[j] = 0.0; + IIR.a1[j] = 0.0; + IIR.b1[j] = 0.0; + IIR.a2[j] = 0.0; + IIR.b2[j] = 0.0; + IIR.a3[j] = 0.0; + IIR.b3[j] = 0.0; + IIR.a4[j] = 0.0; + IIR.b4[j] = 0.0; + } + + // Set the number of IIR filter sections we will be generating. + IIR.NumSections = (IIRFilt.NumPoles + 1) / 2; + if (IIRFilt.IIRPassType == iirBPF || IIRFilt.IIRPassType == iirNOTCH) + IIR.NumSections = IIRFilt.NumPoles; + + // For All Pass filters, the numerator is set to the denominator values as shown here. + // If the prototype was an Inv Cheby or Elliptic, the S plane numerator is discarded. + // Use the Gauss as the prototype for the best all pass results (most linear phase). + // The all pass H(s) = ( As^2 - Bs + C ) / ( As^2 + Bs + C ) + if (IIRFilt.IIRPassType == iirALLPASS) { + for (j = 0; j < SPlaneCoeff.NumSections; j++) { + SPlaneCoeff.N2[j] = SPlaneCoeff.D2[j]; + SPlaneCoeff.N1[j] = -SPlaneCoeff.D1[j]; + SPlaneCoeff.N0[j] = SPlaneCoeff.D0[j]; + } + } + + // T sets the IIR filter's corner frequency, or center freqency. + // The Bilinear transform is defined as: s = 2/T * tan(Omega/2) = 2/T * (1 - z)/(1 + z) + T = 2.0 * tan(IIRFilt.OmegaC * M_PI_2); + Q = 1.0 + IIRFilt.OmegaC; // Q is used for band pass and notch filters. + if (Q > 1.95) + Q = 1.95; + Q = 0.8 * tan(Q * M_PI_4); // This is a correction factor for Q. + Q = IIRFilt.OmegaC / IIRFilt.BW / Q; // This is the corrected Q. + + // Calc the IIR coefficients. + // SPlaneCoeff.NumSections is the number of 1st and 2nd order s plane factors. + k = 0; + for (j = 0; j < SPlaneCoeff.NumSections; j++) { + A = SPlaneCoeff.D2[j]; // We use A - F to make the code easier to read. + B = SPlaneCoeff.D1[j]; + C = SPlaneCoeff.D0[j]; + D = SPlaneCoeff.N2[j]; + E = SPlaneCoeff.N1[j]; // N1 is always zero, except for the all pass. Consequently, the equations below can be simplified a bit by removing E. + F = SPlaneCoeff.N0[j]; + + // b's are the numerator a's are the denominator + if (IIRFilt.IIRPassType == iirLPF || IIRFilt.IIRPassType == iirALLPASS) // Low Pass and All Pass + { + if (A == 0.0 && D == 0.0) // 1 pole case + { + Arg = (2.0 * B + C * T); + IIR.a2[j] = 0.0; + IIR.a1[j] = (-2.0 * B + C * T) / Arg; + IIR.a0[j] = 1.0; + + IIR.b2[j] = 0.0; + IIR.b1[j] = (-2.0 * E + F * T) / Arg * C / F; + IIR.b0[j] = (2.0 * E + F * T) / Arg * C / F; + } else // 2 poles + { + Arg = (4.0 * A + 2.0 * B * T + C * T * T); + IIR.a2[j] = (4.0 * A - 2.0 * B * T + C * T * T) / Arg; + IIR.a1[j] = (2.0 * C * T * T - 8.0 * A) / Arg; + IIR.a0[j] = 1.0; + + // With all pole filters, our LPF numerator is (z+1)^2, so all our Z Plane zeros are at -1 + IIR.b2[j] = (4.0 * D - 2.0 * E * T + F * T * T) / Arg * C / F; + IIR.b1[j] = (2.0 * F * T * T - 8.0 * D) / Arg * C / F; + IIR.b0[j] = (4 * D + F * T * T + 2.0 * E * T) / Arg * C / F; + } + } + + if (IIRFilt.IIRPassType == iirHPF) // High Pass + { + if (A == 0.0 && D == 0.0) // 1 pole + { + Arg = 2.0 * C + B * T; + IIR.a2[j] = 0.0; + IIR.a1[j] = (B * T - 2.0 * C) / Arg; + IIR.a0[j] = 1.0; + + IIR.b2[j] = 0.0; + IIR.b1[j] = (E * T - 2.0 * F) / Arg * C / F; + IIR.b0[j] = (E * T + 2.0 * F) / Arg * C / F; + } else // 2 poles + { + Arg = A * T * T + 4.0 * C + 2.0 * B * T; + IIR.a2[j] = (A * T * T + 4.0 * C - 2.0 * B * T) / Arg; + IIR.a1[j] = (2.0 * A * T * T - 8.0 * C) / Arg; + IIR.a0[j] = 1.0; + + // With all pole filters, our HPF numerator is (z-1)^2, so all our Z Plane zeros are at 1 + IIR.b2[j] = (D * T * T - 2.0 * E * T + 4.0 * F) / Arg * C / F; + IIR.b1[j] = (2.0 * D * T * T - 8.0 * F) / Arg * C / F; + IIR.b0[j] = (D * T * T + 4.0 * F + 2.0 * E * T) / Arg * C / F; + } + } + + if (IIRFilt.IIRPassType == iirBPF) // Band Pass + { + if (A == 0.0 && D == 0.0) // 1 pole + { + Arg = 4.0 * B * Q + 2.0 * C * T + B * Q * T * T; + a2[k] = (B * Q * T * T + 4.0 * B * Q - 2.0 * C * T) / Arg; + a1[k] = (2.0 * B * Q * T * T - 8.0 * B * Q) / Arg; + a0[k] = 1.0; + + b2[k] = (E * Q * T * T + 4.0 * E * Q - 2.0 * F * T) / Arg * C + / F; + b1[k] = (2.0 * E * Q * T * T - 8.0 * E * Q) / Arg * C / F; + b0[k] = (4.0 * E * Q + 2.0 * F * T + E * Q * T * T) / Arg * C + / F; + k++; + } else //2 Poles + { + IIR.a4[j] = (16.0 * A * Q * Q + A * Q * Q * T * T * T * T + + 8.0 * A * Q * Q * T * T - 2.0 * B * Q * T * T * T + - 8.0 * B * Q * T + 4.0 * C * T * T) * F; + IIR.a3[j] = (4.0 * T * T * T * T * A * Q * Q + - 4.0 * Q * T * T * T * B + 16.0 * Q * B * T + - 64.0 * A * Q * Q) * F; + IIR.a2[j] = (96.0 * A * Q * Q - 16.0 * A * Q * Q * T * T + + 6.0 * A * Q * Q * T * T * T * T - 8.0 * C * T * T) + * F; + IIR.a1[j] = (4.0 * T * T * T * T * A * Q * Q + + 4.0 * Q * T * T * T * B - 16.0 * Q * B * T + - 64.0 * A * Q * Q) * F; + IIR.a0[j] = (16.0 * A * Q * Q + A * Q * Q * T * T * T * T + + 8.0 * A * Q * Q * T * T + 2.0 * B * Q * T * T * T + + 8.0 * B * Q * T + 4.0 * C * T * T) * F; + + // With all pole filters, our BPF numerator is (z-1)^2 * (z+1)^2 so the zeros come back as +/- 1 pairs + IIR.b4[j] = (8.0 * D * Q * Q * T * T - 8.0 * E * Q * T + + 16.0 * D * Q * Q - 2.0 * E * Q * T * T * T + + D * Q * Q * T * T * T * T + 4.0 * F * T * T) * C; + IIR.b3[j] = (16.0 * E * Q * T - 4.0 * E * Q * T * T * T + - 64.0 * D * Q * Q + 4.0 * D * Q * Q * T * T * T * T) + * C; + IIR.b2[j] = (96.0 * D * Q * Q - 8.0 * F * T * T + + 6.0 * D * Q * Q * T * T * T * T + - 16.0 * D * Q * Q * T * T) * C; + IIR.b1[j] = (4.0 * D * Q * Q * T * T * T * T - 64.0 * D * Q * Q + + 4.0 * E * Q * T * T * T - 16.0 * E * Q * T) * C; + IIR.b0[j] = (16.0 * D * Q * Q + 8.0 * E * Q * T + + 8.0 * D * Q * Q * T * T + 2.0 * E * Q * T * T * T + + 4.0 * F * T * T + D * Q * Q * T * T * T * T) * C; + + // T = 2 makes these values approach 0.0 (~ 1.0E-12) The root solver needs 0.0 for numerical reasons. + if (fabs(T - 2.0) < 0.0005) { + IIR.a3[j] = 0.0; + IIR.a1[j] = 0.0; + IIR.b3[j] = 0.0; + IIR.b1[j] = 0.0; + } + + // We now have a 4th order poly in the form a4*s^4 + a3*s^3 + a2*s^2 + a2*s + a0 + // We find the roots of this so we can form two 2nd order polys. + Coeff[0] = IIR.a4[j]; + Coeff[1] = IIR.a3[j]; + Coeff[2] = IIR.a2[j]; + Coeff[3] = IIR.a1[j]; + Coeff[4] = IIR.a0[j]; + FindRoots(4, Coeff, Roots); + + // In effect, the root finder scales the poly by 1/a4 so we have to apply this factor back into + // the two 2nd order equations we are forming. + Scalar = sqrt(fabs(IIR.a4[j])); + + // Form the two 2nd order polys from the roots. + a2[k] = Scalar; + a1[k] = -(Roots[0] + Roots[1]).real() * Scalar; + a0[k] = (Roots[0] * Roots[1]).real() * Scalar; + k++; + a2[k] = Scalar; + a1[k] = -(Roots[2] + Roots[3]).real() * Scalar; + a0[k] = (Roots[2] * Roots[3]).real() * Scalar; + k--; + + // Now do the same with the numerator. + Coeff[0] = IIR.b4[j]; + Coeff[1] = IIR.b3[j]; + Coeff[2] = IIR.b2[j]; + Coeff[3] = IIR.b1[j]; + Coeff[4] = IIR.b0[j]; + + if (IIRFilt.ProtoType == INVERSE_CHEBY + || IIRFilt.ProtoType == ELLIPTIC) { + FindRoots(4, Coeff, Roots); + } else // With all pole filters (Butter, Cheb, etc), we know we have these 4 real roots. The root finder won't necessarily pair real roots the way we need, so rather than compute these, we simply set them. + { + Roots[0] = std::complex(-1.0, 0.0); + Roots[1] = std::complex(1.0, 0.0); + Roots[2] = std::complex(-1.0, 0.0); + Roots[3] = std::complex(1.0, 0.0); + } + + Scalar = sqrt(fabs(IIR.b4[j])); + + b2[k] = Scalar; + if (IIRFilt.ProtoType == INVERSE_CHEBY + || IIRFilt.ProtoType == ELLIPTIC) { + b1[k] = -(Roots[0] + Roots[1]).real() * Scalar; // = 0.0 + } else // else the prototype is an all pole filter + { + b1[k] = 0.0; // b1 = 0 for all pole filters, but the addition above won't always equal zero exactly. + } + b0[k] = (Roots[0] * Roots[1]).real() * Scalar; + + k++; + + b2[k] = Scalar; + if (IIRFilt.ProtoType == INVERSE_CHEBY + || IIRFilt.ProtoType == ELLIPTIC) { + b1[k] = -(Roots[2] + Roots[3]).real() * Scalar; + } else // All pole + { + b1[k] = 0.0; + } + b0[k] = (Roots[2] * Roots[3]).real() * Scalar; + k++; + // Go below to see where we store these 2nd order polys back into IIR + } + } + + if (IIRFilt.IIRPassType == iirNOTCH) // Notch + { + if (A == 0.0 && D == 0.0) // 1 pole + { + Arg = 2.0 * B * T + C * Q * T * T + 4.0 * C * Q; + a2[k] = (4.0 * C * Q - 2.0 * B * T + C * Q * T * T) / Arg; + a1[k] = (2.0 * C * Q * T * T - 8.0 * C * Q) / Arg; + a0[k] = 1.0; + + b2[k] = (4.0 * F * Q - 2.0 * E * T + F * Q * T * T) / Arg * C + / F; + b1[k] = (2.0 * F * Q * T * T - 8.0 * F * Q) / Arg * C / F; + b0[k] = (2.0 * E * T + F * Q * T * T + 4.0 * F * Q) / Arg * C + / F; + k++; + } else { + IIR.a4[j] = (4.0 * A * T * T - 2.0 * B * T * T * T * Q + + 8.0 * C * Q * Q * T * T - 8.0 * B * T * Q + + C * Q * Q * T * T * T * T + 16.0 * C * Q * Q) * -F; + IIR.a3[j] = (16.0 * B * T * Q + 4.0 * C * Q * Q * T * T * T * T + - 64.0 * C * Q * Q - 4.0 * B * T * T * T * Q) * -F; + IIR.a2[j] = (96.0 * C * Q * Q - 8.0 * A * T * T + - 16.0 * C * Q * Q * T * T + + 6.0 * C * Q * Q * T * T * T * T) * -F; + IIR.a1[j] = (4.0 * B * T * T * T * Q - 16.0 * B * T * Q + - 64.0 * C * Q * Q + 4.0 * C * Q * Q * T * T * T * T) + * -F; + IIR.a0[j] = (4.0 * A * T * T + 2.0 * B * T * T * T * Q + + 8.0 * C * Q * Q * T * T + 8.0 * B * T * Q + + C * Q * Q * T * T * T * T + 16.0 * C * Q * Q) * -F; + + // Our Notch Numerator isn't simple. [ (4+T^2)*z^2 - 2*(4-T^2)*z + (4+T^2) ]^2 + IIR.b4[j] = (2.0 * E * T * T * T * Q - 4.0 * D * T * T + - 8.0 * F * Q * Q * T * T + 8.0 * E * T * Q + - 16.0 * F * Q * Q - F * Q * Q * T * T * T * T) * C; + IIR.b3[j] = (64.0 * F * Q * Q + 4.0 * E * T * T * T * Q + - 16.0 * E * T * Q - 4.0 * F * Q * Q * T * T * T * T) + * C; + IIR.b2[j] = (8.0 * D * T * T - 96.0 * F * Q * Q + + 16.0 * F * Q * Q * T * T + - 6.0 * F * Q * Q * T * T * T * T) * C; + IIR.b1[j] = (16.0 * E * T * Q - 4.0 * E * T * T * T * Q + + 64.0 * F * Q * Q - 4.0 * F * Q * Q * T * T * T * T) + * C; + IIR.b0[j] = (-4.0 * D * T * T - 2.0 * E * T * T * T * Q + - 8.0 * E * T * Q - 8.0 * F * Q * Q * T * T + - F * Q * Q * T * T * T * T - 16.0 * F * Q * Q) * C; + + // T = 2 (OmegaC = 0.5) makes these values approach 0.0 (~ 1.0E-12). The root solver wants 0.0 for numerical reasons. + if (fabs(T - 2.0) < 0.0005) { + IIR.a3[j] = 0.0; + IIR.a1[j] = 0.0; + IIR.b3[j] = 0.0; + IIR.b1[j] = 0.0; + } + + // We now have a 4th order poly in the form a4*s^4 + a3*s^3 + a2*s^2 + a2*s + a0 + // We find the roots of this so we can form two 2nd order polys. + Coeff[0] = IIR.a4[j]; + Coeff[1] = IIR.a3[j]; + Coeff[2] = IIR.a2[j]; + Coeff[3] = IIR.a1[j]; + Coeff[4] = IIR.a0[j]; + + // In effect, the root finder scales the poly by 1/a4 so we have to apply this factor back into + // the two 2nd order equations we are forming. + FindRoots(4, Coeff, Roots); + Scalar = sqrt(fabs(IIR.a4[j])); + a2[k] = Scalar; + a1[k] = -(Roots[0] + Roots[1]).real() * Scalar; + a0[k] = (Roots[0] * Roots[1]).real() * Scalar; + + k++; + a2[k] = Scalar; + a1[k] = -(Roots[2] + Roots[3]).real() * Scalar; + a0[k] = (Roots[2] * Roots[3]).real() * Scalar; + k--; + + // Now do the same with the numerator. + Coeff[0] = IIR.b4[j]; + Coeff[1] = IIR.b3[j]; + Coeff[2] = IIR.b2[j]; + Coeff[3] = IIR.b1[j]; + Coeff[4] = IIR.b0[j]; + FindRoots(4, Coeff, Roots); + + Scalar = sqrt(fabs(IIR.b4[j])); + b2[k] = Scalar; + b1[k] = -(Roots[0] + Roots[1]).real() * Scalar; + b0[k] = (Roots[0] * Roots[1]).real() * Scalar; + + k++; + b2[k] = Scalar; + b1[k] = -(Roots[2] + Roots[3]).real() * Scalar; + b0[k] = (Roots[2] * Roots[3]).real() * Scalar; + k++; + } + } + } + + if (IIRFilt.IIRPassType == iirBPF || IIRFilt.IIRPassType == iirNOTCH) { + // In the calcs above for the BPF and Notch, we didn't set a0=1, so we do it here. + for (j = 0; j < IIR.NumSections; j++) { + b2[j] /= a0[j]; + b1[j] /= a0[j]; + b0[j] /= a0[j]; + a2[j] /= a0[j]; + a1[j] /= a0[j]; + a0[j] = 1.0; + } + + for (j = 0; j < IIR.NumSections; j++) { + IIR.a0[j] = a0[j]; + IIR.a1[j] = a1[j]; + IIR.a2[j] = a2[j]; + IIR.b0[j] = b0[j]; + IIR.b1[j] = b1[j]; + IIR.b2[j] = b2[j]; + } + } + + // Adjust the b's or a0 for the desired Gain. + SectionGain = pow(10.0, IIRFilt.dBGain / 20.0); + SectionGain = pow(SectionGain, 1.0 / (double) IIR.NumSections); + for (j = 0; j < IIR.NumSections; j++) { + IIR.b0[j] *= SectionGain; + IIR.b1[j] *= SectionGain; + IIR.b2[j] *= SectionGain; + // This is an alternative to adjusting the b's + // IIR.a0[j] = SectionGain; + } + + return (IIR); +} + +//--------------------------------------------------------------------------- + +// This code implements an IIR filter as a Form 1 Biquad. +// It uses 2 sets of shift registers, RegX on the input side and RegY on the output side. +// There are many ways to implement an IIR filter, some very good, and some extremely bad. +// For numerical reasons, a Form 1 Biquad implementation is among the best. +void FilterWithIIR(TIIRCoeff IIRCoeff, double *Signal, double *FilteredSignal, + int NumSigPts) { + double y; + int j, k; + + for (j = 0; j < NumSigPts; j++) { + k = 0; + y = SectCalc(j, k, Signal[j], IIRCoeff); + for (k = 1; k < IIRCoeff.NumSections; k++) { + y = SectCalc(j, k, y, IIRCoeff); + } + FilteredSignal[j] = y; + } + +} +//--------------------------------------------------------------------------- + +// This gets used with the function above, FilterWithIIR() +// Note the use of MaxRegVal to avoid a math overflow condition. +double SectCalc(int j, int k, double x, TIIRCoeff IIRCoeff) { + double y, CenterTap; + static double RegX1[ARRAY_DIM], RegX2[ARRAY_DIM], RegY1[ARRAY_DIM], + RegY2[ARRAY_DIM], MaxRegVal; + static bool MessageShown = false; + + // Zero the regiisters on the 1st call or on an overflow condition. The overflow limit used + // here is small for double variables, but a filter that reaches this threshold is broken. + if ((j == 0 && k == 0) || MaxRegVal > OVERFLOW_LIMIT) { + if (MaxRegVal > OVERFLOW_LIMIT && !MessageShown) { + // ShowMessage("ERROR: Math Over Flow in IIR Section Calc. \nThe register values exceeded 1.0E20 \n"); + MessageShown = true; // So this message doesn't get shown thousands of times. + } + + MaxRegVal = 1.0E-12; + for (int i = 0; i < ARRAY_DIM; i++) { + RegX1[i] = 0.0; + RegX2[i] = 0.0; + RegY1[i] = 0.0; + RegY2[i] = 0.0; + } + } + + CenterTap = x * IIRCoeff.b0[k] + IIRCoeff.b1[k] * RegX1[k] + + IIRCoeff.b2[k] * RegX2[k]; + y = IIRCoeff.a0[k] * CenterTap - IIRCoeff.a1[k] * RegY1[k] + - IIRCoeff.a2[k] * RegY2[k]; + + RegX2[k] = RegX1[k]; + RegX1[k] = x; + RegY2[k] = RegY1[k]; + RegY1[k] = y; + + // MaxRegVal is used to prevent overflow. Overflow seldom occurs, but will + // if the filter has faulty coefficients. MaxRegVal is usually less than 100.0 + if (fabs(CenterTap) > MaxRegVal) + MaxRegVal = fabs(CenterTap); + if (fabs(y) > MaxRegVal) + MaxRegVal = fabs(y); + return (y); +} +//--------------------------------------------------------------------------- + +// This function calculates the frequency response of an IIR filter. +// Probably the easiest way to determine the frequency response of an IIR filter is to send +// an impulse through the filter and do an FFT on the output. This method does a DFT on +// the coefficients of each biquad section. The results from the cascaded sections are +// then multiplied together. + +// This approach works better than an FFT when the filter is very narrow. To analyze highly selective +// filters with an FFT can require a very large number of points, which can be quite cumbersome. +// This approach allows you to set the range of frequencies to be analyzed by modifying the statement +// Arg = M_PI * (double)j / (double)NumPts; . +void IIRFreqResponse(TIIRCoeff IIR, int NumSections, double *RealHofZ, + double *ImagHofZ, int NumPts) { + int j, n; + double Arg; + std::complex z1, z2, HofZ, Denom; + for (j = 0; j < NumPts; j++) { + Arg = M_PI * (double) j / (double) NumPts; + z1 = std::complex(cos(Arg), -sin(Arg)); // z = e^(j*omega) + z2 = z1 * z1; // z squared + + HofZ = std::complex(1.0, 0.0); + for (n = 0; n < NumSections; n++) { + HofZ *= IIR.a0[n]; // This can be in the denominator, but only if a0=1. a0 can be other than 1.0 to adjust the filter's gain. See the bottom of the CalcIIRFilterCoeff() function. + HofZ *= IIR.b0[n] + IIR.b1[n] * z1 + IIR.b2[n] * z2; // Numerator + Denom = 1.0 + IIR.a1[n] * z1 + IIR.a2[n] * z2; // Denominator + if (std::abs(Denom) < 1.0E-12) + Denom = 1.0E-12; // A pole on the unit circle would cause this to be zero, so this should never happen. It could happen however if the filter also has a zero at this frequency. Then H(z) needs to be determined by L'Hopitals rule at this freq. + HofZ /= Denom; + } + RealHofZ[j] = HofZ.real(); + ImagHofZ[j] = HofZ.imag(); + } +} + +//--------------------------------------------------------------------------- + +} // namespace diff --git a/kitiirfir/IIRFilterCode.h b/kitiirfir/IIRFilterCode.h new file mode 100644 index 000000000..d917ead22 --- /dev/null +++ b/kitiirfir/IIRFilterCode.h @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------------- + +#ifndef IIRFilterCodeH +#define IIRFilterCodeH +//--------------------------------------------------------------------------- +#include "LowPassPrototypes.h" // defines TFilterPoly and ARRAY_DIM +#define OVERFLOW_LIMIT 1.0E20 + +namespace kitiirfir +{ + +enum TIIRPassTypes {iirLPF, iirHPF, iirBPF, iirNOTCH, iirALLPASS}; + +struct TIIRCoeff {double a0[ARRAY_DIM]; double a1[ARRAY_DIM]; double a2[ARRAY_DIM]; double a3[ARRAY_DIM]; double a4[ARRAY_DIM]; + double b0[ARRAY_DIM]; double b1[ARRAY_DIM]; double b2[ARRAY_DIM]; double b3[ARRAY_DIM]; double b4[ARRAY_DIM]; + int NumSections; }; + +struct TIIRFilterParams { TIIRPassTypes IIRPassType; // Defined above: Low pass, High Pass, etc. + double OmegaC; // The IIR filter's 3 dB corner freq for low pass and high pass, the center freq for band pass and notch. + double BW; // The IIR filter's 3 dB bandwidth for band pass and notch filters. + double dBGain; // Sets the Gain of the filter + + // These define the low pass prototype to be used + TFilterPoly ProtoType; // Butterworth, Cheby, etc. + int NumPoles; // Pole count + double Ripple; // Passband Ripple for the Elliptic and Chebyshev + double StopBanddB; // Stop Band Attenuation in dB for the Elliptic and Inverse Chebyshev + double Gamma; // Controls the transition bandwidth on the Adjustable Gauss. -1 <= Gamma <= 1 + }; + +TIIRCoeff CalcIIRFilterCoeff(TIIRFilterParams IIRFilt); +void FilterWithIIR(TIIRCoeff IIRCoeff, double *Signal, double *FilteredSignal, int NumSigPts); +double SectCalc(int j, int k, double x, TIIRCoeff IIRCoeff); +void IIRFreqResponse(TIIRCoeff IIRCoeff, int NumSections, double *RealHofZ, double *ImagHofZ, int NumPts); + +} // namespace + +#endif diff --git a/kitiirfir/LowPassPrototypes.cpp b/kitiirfir/LowPassPrototypes.cpp new file mode 100644 index 000000000..084351e9f --- /dev/null +++ b/kitiirfir/LowPassPrototypes.cpp @@ -0,0 +1,469 @@ +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + If you find a problem, please leave a note at: + http://www.iowahills.com/feedbackcomments.html + May 1, 2016 + + ShowMessage is a C++ Builder function, and it usage has been commented out. + If you are using C++ Builder, include vcl.h for ShowMessage. + Otherwise replace ShowMessage with something appropriate for your compiler. + + This code generates 2nd order S plane coefficients for the following low pass filter prototypes. + Butterworth, Chebyshev, Bessel, Gauss, Adjustable Gauss, Inverse Chebyshev, Papoulis, and Elliptic. + This code does 3 things. + 1. Gets the filter's roots from the code in LowPassRoots.cpp + 2. Uses the left hand plane roots to form 2nd order polynomials. + 3. Frequency scales the polynomials so the 3 dB corner frequency is at omege = 1 rad/sec. + + Note that we range check the Filt struct variables here, but we don't report + back any changes we make. + */ + +#include +#include "LowPassPrototypes.h" +#include "PFiftyOneRevE.h" +#include "LowPassRoots.h" + +namespace kitiirfir +{ + +//--------------------------------------------------------------------------- + +// TLowPassParams defines the low pass prototype (NumPoles, Ripple, etc.). +// We return SPlaneCoeff filled with the 2nd order S plane coefficients. +TSPlaneCoeff CalcLowPassProtoCoeff(TLowPassParams Filt) { + int j, DenomCount = 0, NumerCount, NumRoots, ZeroCount; + std::complex Poles[ARRAY_DIM], Zeros[ARRAY_DIM]; + TSPlaneCoeff Coeff; // The return value. + + // Init the S Plane Coeff. H(s) = (N2*s^2 + N1*s + N0) / (D2*s^2 + D1*s + D0) + for (j = 0; j < ARRAY_DIM; j++) { + Coeff.N2[j] = 0.0; + Coeff.N1[j] = 0.0; + Coeff.N0[j] = 1.0; + Coeff.D2[j] = 0.0; + Coeff.D1[j] = 0.0; + Coeff.D0[j] = 1.0; + } + Coeff.NumSections = 0; + + // We need to range check the various argument values here. + // These are the practical limits the max number of poles. + if (Filt.NumPoles < 1) + Filt.NumPoles = 1; + if (Filt.NumPoles > MAX_POLE_COUNT) + Filt.NumPoles = MAX_POLE_COUNT; + if (Filt.ProtoType == ELLIPTIC || Filt.ProtoType == INVERSE_CHEBY) { + if (Filt.NumPoles > 15) + Filt.NumPoles = 15; + } + if (Filt.ProtoType == GAUSSIAN || Filt.ProtoType == BESSEL) { + if (Filt.NumPoles > 12) + Filt.NumPoles = 12; + } + + // Gamma is used by the Adjustable Gauss. + if (Filt.Gamma < -1.0) + Filt.Gamma = -1.0; // -1 gives ~ Gauss response + if (Filt.Gamma > 1.0) + Filt.Gamma = 1.0; // +1 gives ~ Butterworth response. + + // Ripple is used by the Chebyshev and Elliptic + if (Filt.Ripple < 0.0001) + Filt.Ripple = 0.0001; + if (Filt.Ripple > 1.0) + Filt.Ripple = 1.0; + + // With the Chebyshev we need to use less ripple for large pole counts to keep the poles out of the RHP. + if (Filt.ProtoType == CHEBYSHEV && Filt.NumPoles > 15) { + double MaxRipple = 1.0; + if (Filt.NumPoles == 16) + MaxRipple = 0.5; + if (Filt.NumPoles == 17) + MaxRipple = 0.4; + if (Filt.NumPoles == 18) + MaxRipple = 0.25; + if (Filt.NumPoles == 19) + MaxRipple = 0.125; + if (Filt.NumPoles >= 20) + MaxRipple = 0.10; + if (Filt.Ripple > MaxRipple) + Filt.Ripple = MaxRipple; + } + + // StopBanddB is used by the Inverse Chebyshev and the Elliptic + // It is given in positive dB values. + if (Filt.StopBanddB < 20.0) + Filt.StopBanddB = 20.0; + if (Filt.StopBanddB > 120.0) + Filt.StopBanddB = 120.0; + + // There isn't such a thing as a 1 pole Chebyshev, or 1 pole Bessel, etc. + // A one pole filter is simply 1/(s+1). + NumerCount = 0; // init + if (Filt.NumPoles == 1) { + Coeff.D1[0] = 1.0; + DenomCount = 1; // DenomCount is the number of denominator factors (1st or 2nd order). + } else if (Filt.ProtoType == BUTTERWORTH) { + NumRoots = ButterworthPoly(Filt.NumPoles, Poles); + DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, + Coeff.D0); + // A Butterworth doesn't require frequncy scaling with SetCornerFreq(). + } + + else if (Filt.ProtoType == ADJUSTABLE) // Adjustable Gauss + { + NumRoots = AdjustablePoly(Filt.NumPoles, Poles, Filt.Gamma); + DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, + Coeff.D0); + SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, + Coeff.N1, Coeff.N0); + } + + else if (Filt.ProtoType == CHEBYSHEV) { + NumRoots = ChebyshevPoly(Filt.NumPoles, Filt.Ripple, Poles); + DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, + Coeff.D0); + SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, + Coeff.N1, Coeff.N0); + } + + else if (Filt.ProtoType == INVERSE_CHEBY) { + NumRoots = InvChebyPoly(Filt.NumPoles, Filt.StopBanddB, Poles, Zeros, + &ZeroCount); + DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, + Coeff.D0); + NumerCount = GetFilterCoeff(ZeroCount, Zeros, Coeff.N2, Coeff.N1, + Coeff.N0); + SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, + Coeff.N1, Coeff.N0); + } + + else if (Filt.ProtoType == ELLIPTIC) { + NumRoots = EllipticPoly(Filt.NumPoles, Filt.Ripple, Filt.StopBanddB, + Poles, Zeros, &ZeroCount); + DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, + Coeff.D0); + NumerCount = GetFilterCoeff(ZeroCount, Zeros, Coeff.N2, Coeff.N1, + Coeff.N0); + SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, + Coeff.N1, Coeff.N0); + } + + // Papoulis works OK, but it doesn't accomplish anything the Chebyshev can't. + else if (Filt.ProtoType == PAPOULIS) { + NumRoots = PapoulisPoly(Filt.NumPoles, Poles); + DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, + Coeff.D0); + SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, + Coeff.N1, Coeff.N0); + } + + else if (Filt.ProtoType == BESSEL) { + NumRoots = BesselPoly(Filt.NumPoles, Poles); + DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, + Coeff.D0); + SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, + Coeff.N1, Coeff.N0); + } + + else if (Filt.ProtoType == GAUSSIAN) { + NumRoots = GaussianPoly(Filt.NumPoles, Poles); + DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, + Coeff.D0); + SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, + Coeff.N1, Coeff.N0); + } + + Coeff.NumSections = DenomCount; + + // If we have an odd pole count, there will be 1 less zero than poles, so we need to shift the + // zeros down in the arrays so the 1st zero (which is zero) and aligns with the real pole. + if (NumerCount != 0 && Filt.NumPoles % 2 == 1) { + for (j = NumerCount; j >= 0; j--) { + Coeff.N2[j + 1] = Coeff.N2[j]; // Coeff.N1's are always zero + Coeff.N0[j + 1] = Coeff.N0[j]; + } + Coeff.N2[0] = 0.0; // Set the 1st zero to zero for odd pole counts. + Coeff.N0[0] = 1.0; + } + + return (Coeff); + +} + +//--------------------------------------------------------------------------- + +// This sets the polynomial's the 3 dB corner to 1 rad/sec. This isn' required for a Butterworth, +// but the rest of the polynomials need correction. Esp the Adj Gauss, Inv Cheby and Bessel. +// Poly Count is the number of 2nd order sections. D and N are the Denom and Num coefficients. +// H(s) = (N2*s^2 + N1*s + N0) / (D2*s^2 + D1*s + D0) +void SetCornerFreq(int PolyCount, double *D2, double *D1, double *D0, + double *N2, double *N1, double *N0) { + int j, n; + double Omega, FreqScalar, Zeta, Gain; + std::complex s, H; + + Gain = 1.0; + for (j = 0; j < PolyCount; j++) + Gain *= D0[j] / N0[j]; + + // Evaluate H(s) by increasing Omega until |H(s)| < -3 dB + for (j = 1; j < 6000; j++) { + Omega = (double) j / 512.0; // The step size for Omega is 1/512 radians. + s = std::complex(0.0, Omega); + + H = std::complex(1.0, 0.0); + for (n = 0; n < PolyCount; n++) { + H = H * (N2[n] * s * s + N1[n] * s + N0[n]) + / (D2[n] * s * s + D1[n] * s + D0[n]); + } + H *= Gain; + if (std::abs(H) < 0.7071) + break; // -3 dB + } + + FreqScalar = 1.0 / Omega; + + // Freq scale the denominator. We hold the damping factor Zeta constant. + for (j = 0; j < PolyCount; j++) { + Omega = sqrt(D0[j]); + if (Omega == 0.0) + continue; // should never happen + Zeta = D1[j] / Omega / 2.0; + if (D2[j] != 0.0) // 2nd degree poly + { + D0[j] = Omega * Omega * FreqScalar * FreqScalar; + D1[j] = 2.0 * Zeta * Omega * FreqScalar; + } else // 1st degree poly + { + D0[j] *= FreqScalar; + } + } + + // Scale the numerator. H(s) = (N2*s^2 + N1*s + N0) / (D2*s^2 + D1*s + D0) + // N1 is always zero. N2 is either 1 or 0. If N2 = 0, then N0 = 1 and there isn't a zero to scale. + // For all pole filters (Butter, Cheby, etc) N2 = 0 and N0 = 1. + for (j = 0; j < PolyCount; j++) { + if (N2[j] == 0.0) + continue; + N0[j] *= FreqScalar * FreqScalar; + } + +} + +//--------------------------------------------------------------------------- + +// Some of the Polys generate both left hand and right hand plane roots. +// We use this function to get the left hand plane poles and imag axis zeros to +// create the 2nd order polynomials with coefficients A2, A1, A0. +// We return the Polynomial count. + +// We first sort the roots according the the real part (a zeta sort). Then all the left +// hand plane roots are grouped and in the correct order for IIR and Opamp filters. +// We then check for duplicate roots, and set an inconsequential real or imag part to zero. +// Then the 2nd order coefficients are calculated. +int GetFilterCoeff(int RootCount, std::complex *Roots, double *A2, double *A1, + double *A0) { + int PolyCount, j, k; + + SortRootsByZeta(Roots, RootCount, stMin); // stMin places the most negative real part 1st. + + // Check for duplicate roots. The Inv Cheby generates duplcate imag roots, and the + // Elliptic generates duplicate real roots. We set duplicates to a RHP value. + for (j = 0; j < RootCount - 1; j++) { + for (k = j + 1; k < RootCount; k++) { + if (fabs(Roots[j].real() - Roots[k].real()) < 1.0E-3 + && fabs(Roots[j].imag() - Roots[k].imag()) < 1.0E-3) { + Roots[k] = std::complex((double) k, 0.0); // RHP roots are ignored below, Use k is to prevent duplicate checks for matches. + } + } + } + + // This forms the 2nd order coefficients from the root value. + // We ignore roots in the Right Hand Plane. + PolyCount = 0; + for (j = 0; j < RootCount; j++) { + if (Roots[j].real() > 0.0) + continue; // Right Hand Plane + if (Roots[j].real() == 0.0 && Roots[j].imag() == 0.0) + continue; // At the origin. This should never happen. + + if (Roots[j].real() == 0.0) // Imag Root (A poly zero) + { + A2[PolyCount] = 1.0; + A1[PolyCount] = 0.0; + A0[PolyCount] = Roots[j].imag() * Roots[j].imag(); + j++; + PolyCount++; + } else if (Roots[j].imag() == 0.0) // Real Pole + { + A2[PolyCount] = 0.0; + A1[PolyCount] = 1.0; + A0[PolyCount] = -Roots[j].real(); + PolyCount++; + } else // Complex Pole + { + A2[PolyCount] = 1.0; + A1[PolyCount] = -2.0 * Roots[j].real(); + A0[PolyCount] = Roots[j].real() * Roots[j].real() + + Roots[j].imag() * Roots[j].imag(); + j++; + PolyCount++; + } + } + + return (PolyCount); + +} + +//--------------------------------------------------------------------------- + +// This rebuilds an Nth order poly from its 2nd order constituents. +// PolyCount is the number of 2nd order polys. e.g. NumPoles = 7 PolyCount = 4 +// PolyCoeff gets filled such that PolyCoeff[5]*s^5 + PolyCoeff[4]*s^4 + PolyCoeff[3]*s^3 + .. +// The poly order is returned. +int RebuildPoly(int PolyCount, double *PolyCoeff, double *A2, double *A1, + double *A0) { + int j, k, n; + double Sum[P51_ARRAY_SIZE]; + for (j = 0; j <= 2 * PolyCount; j++) + PolyCoeff[j] = 0.0; + for (j = 0; j < P51_ARRAY_SIZE; j++) + Sum[j] = 0.0; + + PolyCoeff[2] = A2[0]; + PolyCoeff[1] = A1[0]; + PolyCoeff[0] = A0[0]; + + for (j = 1; j < PolyCount; j++) { + for (n = 0; n <= 2 * j; n++) { + Sum[n + 2] += PolyCoeff[n] * A2[j]; + Sum[n + 1] += PolyCoeff[n] * A1[j]; + Sum[n + 0] += PolyCoeff[n] * A0[j]; + } + for (k = 0; k <= 2 * j + 2; k++) + PolyCoeff[k] = Sum[k]; + for (k = 0; k < P51_ARRAY_SIZE; k++) + Sum[k] = 0.0; + } + + // Want to return the poly order. This will be 2 * PolyCount if there aren't any 1st order polys. + // 1st order Polys create leading zeros. N 1st order polys Gives N leading zeros. + for (j = 2 * PolyCount; j >= 0; j--) + if (PolyCoeff[j] != 0.0) + break; + return (j); +} + +//--------------------------------------------------------------------------- +// This sorts on the real part if the real part of the 1st root != 0 (a Zeta sort) +// else we sort on the imag part. If SortType == stMin for both the poles and zeros, then +// the poles and zeros will be properly matched. +// This also sets an inconsequential real or imag part to zero. +// A matched pair of z plane real roots, such as +/- 1, don't come out together. +// Used above in GetFilterCoeff and the FIR zero plot. +void SortRootsByZeta(std::complex *Roots, int Count, TOurSortTypes SortType) { + if (Count >= P51_MAXDEGREE) { + //ShowMessage("Count > P51_MAXDEGREE in TPolyForm::SortRootsByZeta()"); + return; + } + + int j, k, RootJ[P51_ARRAY_SIZE]; + double SortValue[P51_ARRAY_SIZE]; + std::complex TempRoots[P51_ARRAY_SIZE]; + + // Set an inconsequential real or imag part to zero. + for (j = 0; j < Count; j++) { + if (fabs(Roots[j].real()) * 1.0E3 < fabs(Roots[j].imag())) + Roots[j].real(0.0); + if (fabs(Roots[j].imag()) * 1.0E3 < fabs(Roots[j].real())) + Roots[j].imag(0.0); + } + + // Sort the roots. + for (j = 0; j < Count; j++) + RootJ[j] = j; // Needed for HeapIndexSort + if (Roots[0].real() != 0.0) // Cplx roots + { + for (j = 0; j < Count; j++) + SortValue[j] = Roots[j].real(); + } else // Imag roots, so we sort on imag part. + { + for (j = 0; j < Count; j++) + SortValue[j] = fabs(Roots[j].imag()); + } + HeapIndexSort(SortValue, RootJ, Count, SortType); // stMin gives the most negative root on top + + for (j = 0; j < Count; j++) { + k = RootJ[j]; // RootJ is the sort index + TempRoots[j] = Roots[k]; + } + for (j = 0; j < Count; j++) { + Roots[j] = TempRoots[j]; + } + +} +//--------------------------------------------------------------------------- + +// Remember to set the Index array to 0, 1, 2, 3, ... N-1 +bool HeapIndexSort(double *Data, int *Index, int N, TOurSortTypes SortType) { + int i, j, k, m, IndexTemp; + long long FailSafe, NSquared; // need this for big sorts + + NSquared = (long long) N * (long long) N; + m = N / 2; + k = N - 1; + for (FailSafe = 0; FailSafe < NSquared; FailSafe++) // typical FailSafe value on return is N*log2(N) + { + if (m > 0) + IndexTemp = Index[--m]; + else { + IndexTemp = Index[k]; + Index[k] = Index[0]; + if (--k == 0) { + Index[0] = IndexTemp; + return (true); + } + } + + i = m + 1; + j = 2 * i; + + if (SortType == stMax) + while (j < k + 2) { + FailSafe++; + if (j <= k && Data[Index[j - 1]] > Data[Index[j]]) + j++; + if (Data[IndexTemp] > Data[Index[j - 1]]) { + Index[i - 1] = Index[j - 1]; + i = j; + j += i; + } else + break; + } + + else + // SortType == stMin + while (j < k + 2) { + FailSafe++; + if (j <= k && Data[Index[j - 1]] < Data[Index[j]]) + j++; + if (Data[IndexTemp] < Data[Index[j - 1]]) { + Index[i - 1] = Index[j - 1]; + i = j; + j += i; + } else + break; + } + + Index[i - 1] = IndexTemp; + } + return (false); +} + +//---------------------------------------------------------------------------------- + +} // namespace + diff --git a/kitiirfir/LowPassPrototypes.h b/kitiirfir/LowPassPrototypes.h new file mode 100644 index 000000000..d463f8e38 --- /dev/null +++ b/kitiirfir/LowPassPrototypes.h @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------------- + +#ifndef LowPassPrototypesH +#define LowPassPrototypesH + +#include + +#define MAX_POLE_COUNT 20 +#define ARRAY_DIM 50 // This MUST be at least 2*MAX_POLE_COUNT because some filter polys are defined in terms of 2 * NumPoles + +namespace kitiirfir +{ + +enum TOurSortTypes{stMax, stMin}; + +// These coeff form H(s) = (N2*s^2 + N1*s + N0) / (D2*s^2 + D1*s + D0) +// NumSections is the number of 1st and 2nd order polynomial factors . +struct TSPlaneCoeff { double N2[ARRAY_DIM]; double N1[ARRAY_DIM]; double N0[ARRAY_DIM]; + double D2[ARRAY_DIM]; double D1[ARRAY_DIM]; double D0[ARRAY_DIM]; + int NumSections; }; + +// These are the available filter polynomials. NOT_IIR is for code testing. +enum TFilterPoly {BUTTERWORTH, GAUSSIAN, BESSEL, ADJUSTABLE, CHEBYSHEV, + INVERSE_CHEBY, PAPOULIS, ELLIPTIC, NOT_IIR}; + +// This structure defines the low pass filter prototype. +// The 3 dB corner frequency is 1 rad/sec for all filters. +struct TLowPassParams {TFilterPoly ProtoType; // Butterworth, Cheby, etc. + int NumPoles; // Pole count + double Ripple; // Passband Ripple for the Elliptic and Chebyshev + double StopBanddB; // Stop Band Attenuation in dB for the Elliptic and Inverse Cheby + double Gamma; // Controls the transition bandwidth on the Adjustable Gauss. -1 <= Gamma <= 1 + }; + + +TSPlaneCoeff CalcLowPassProtoCoeff(TLowPassParams Filt); +void SetCornerFreq(int Count, double *D2, double *D1, double *D0, double *N2, double *N1, double *N0); +int GetFilterCoeff(int RootCount, std::complex *Roots, double *A2, double *A1, double *A0); +int RebuildPoly(int PolyCount, double *PolyCoeff, double *A2, double *A1, double *A0 ); +void SortRootsByZeta(std::complex *Roots, int Count, TOurSortTypes SortType); +bool HeapIndexSort(double *Data, int *Index, int N, TOurSortTypes SortType); + +} // namespace + +#endif diff --git a/kitiirfir/LowPassRoots.cpp b/kitiirfir/LowPassRoots.cpp new file mode 100644 index 000000000..d74332cd4 --- /dev/null +++ b/kitiirfir/LowPassRoots.cpp @@ -0,0 +1,683 @@ +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + If you find a problem, please leave a note at: + http://www.iowahills.com/feedbackcomments.html + May 1, 2016 + + + This code calculates the roots for the following filter polynomials. + Butterworth, Chebyshev, Bessel, Gauss, Adjustable Gauss, Inverse Chebyshev, Papoulis, and Elliptic. + + These filters are described in most filter design texts, except the Papoulis and Adjustable Gauss. + + The Adjustable Gauss is a transitional filter invented by Iowa Hills that can generate a response + anywhere between a Gauss and a Butterworth. Use Gamma to control the response. + Gamma = -1.0 nearly a Gauss + Gamma = -0.7 nearly a Bessel + Gamma = 1.0 nearly a Butterworth + + The Papoulis (Classic L) provides a response between the Butterworth the Chebyshev. + It has a faster roll off than the Butterworth but without the Chebyshev ripple. + It does however have some roll off in the pass band, which can be controlled by the RollOff parameter. + + The limits on the arguments for these functions (such as pole count or ripple) are not + checked here. See the LowPassPrototypes.cpp for argument limits. + */ + +#include +#include +#include "LowPassRoots.h" +#include "PFiftyOneRevE.h" + +namespace kitiirfir +{ + +//--------------------------------------------------------------------------- +// This used by several of the functions below. It returns a double so +// we don't overflow and because we need a double for subsequent calculations. +double Factorial(int N) { + int j; + double Fact = 1.0; + for (j = 1; j <= N; j++) + Fact *= (double) j; + return (Fact); +} + +//--------------------------------------------------------------------------- + +// Some of the code below generates the coefficients in reverse order +// needed for the root finder. This reverses the poly. +void ReverseCoeff(double *P, int N) { + int j; + double Temp; + for (j = 0; j <= N / 2; j++) { + Temp = P[j]; + P[j] = P[N - j]; + P[N - j] = Temp; + } + + for (j = N; j >= 1; j--) { + if (P[0] != 0.0) + P[j] /= P[0]; + } + P[0] = 1.0; + +} + +//--------------------------------------------------------------------------- + +// We calculate the roots for a Butterwoth filter directly. (No root finder needed) +// We fill the array Roots[] and return the number of roots. +int ButterworthPoly(int NumPoles, std::complex *Roots) { + int j, n, N; + double Theta; + + N = NumPoles; + n = 0; + for (j = 0; j < N / 2; j++) { + Theta = M_PI * (double) (2 * j + N + 1) / (double) (2 * N); + Roots[n++] = std::complex(cos(Theta), sin(Theta)); + Roots[n++] = std::complex(cos(Theta), -sin(Theta)); + } + if (N % 2 == 1) + Roots[n++] = std::complex(-1.0, 0.0); // The real root for odd pole counts. + return (N); +} + +//--------------------------------------------------------------------------- + +// This calculates the roots for a Chebyshev filter directly. (No root finder needed) +int ChebyshevPoly(int NumPoles, double Ripple, std::complex *Roots) { + int j, n, N; + double Sigma, Omega; + double Arg, Theta, Epsilon; + + N = NumPoles; + Epsilon = pow(10.0, Ripple / 10.0) - 1.0; + Epsilon = sqrt(Epsilon); + if (Epsilon < 0.00001) + Epsilon = 0.00001; + if (Epsilon > 0.996) + Epsilon = 0.996; + Epsilon = 1.0 / Epsilon; + Arg = log(Epsilon + sqrt(Epsilon * Epsilon + 1.0)) / (double) N; // = asinh(Epsilon) / (double)N; + n = 0; + for (j = 0; j < N / 2; j++) { + Theta = (2 * j + 1) * M_PI_2 / (double) N; + Sigma = -sinh(Arg) * sin(Theta); + Omega = cosh(Arg) * cos(Theta); + Roots[n++] = std::complex(Sigma, Omega); + Roots[n++] = std::complex(Sigma, -Omega); + } + if (N % 2 == 1) + Roots[n++] = std::complex(-sinh(Arg), 0.0); // The real root for odd pole counts. + return (N); +} + +//--------------------------------------------------------------------------- + +// The Gaussian Poly is simply 1 - s^2 + s^4 /2! - s^6 / 3! .... seeHumpherys p. 414. +int GaussianPoly(int NumPoles, std::complex *Roots) { + int j, N, RootsCount; + double GaussCoeff[P51_ARRAY_SIZE]; + + N = NumPoles; + GaussCoeff[0] = 1.0; + GaussCoeff[1] = 0.0; + for (j = 2; j <= 2 * N; j += 2) { + GaussCoeff[j] = 1.0 / Factorial(j / 2); + GaussCoeff[j + 1] = 0.0; + if ((j / 2) % 2 == 1) + GaussCoeff[j] *= -1.0; + } + + // The coefficients are generated in reverse order needed for P51. + ReverseCoeff(GaussCoeff, N * 2); + RootsCount = FindRoots(N * 2, GaussCoeff, Roots); + return (RootsCount); +} + +//--------------------------------------------------------------------------- + +// This function starts with the Gauss and modifies the coefficients. +// The Gaussian Poly is simply 1 - s^2 + s^4 /2! - s^6 / 3! .... seeHumpherys p. 414. +int AdjustablePoly(int NumPoles, std::complex *Roots, double Gamma) { + int j, N, RootsCount; + double GaussCoeff[P51_ARRAY_SIZE]; + + N = NumPoles; + if (Gamma > 0.0) + Gamma *= 2.0; // Gamma < 0 is the orig Gauss and Bessel responses. Gamma > 0 has an asymptotic response, so we double it, which also makes the user interface a bit nicer. i.e. -1 <= Gamma <= 1 + + GaussCoeff[0] = 1.0; + GaussCoeff[1] = 0.0; + for (j = 2; j <= 2 * N; j += 2) { + GaussCoeff[j] = pow(Factorial(j / 2), Gamma); // Gamma = -1 is orig Gauss poly, Gamma = 1 approaches a Butterworth response. + GaussCoeff[j + 1] = 0.0; + if ((j / 2) % 2 == 1) + GaussCoeff[j] *= -1.0; + } + + // The coefficients are generated in reverse order needed for P51. + ReverseCoeff(GaussCoeff, N * 2); + RootsCount = FindRoots(N * 2, GaussCoeff, Roots); + + // Scale the imag part of the root by 1.1 to get a response closer to a Butterworth when Gamma = -2 + for (j = 0; j < N * 2; j++) + Roots[j] = std::complex(Roots[j].real(), Roots[j].imag() * 1.10); + return (RootsCount); +} + +//--------------------------------------------------------------------------- + +// These Bessel coefficients are calc'd with the formula given in Johnson & Moore. Page 164. eq 7-26 +// The highet term is 1, the rest of the terms are calc'd +int BesselPoly(int NumPoles, std::complex *Roots) { + int k, N, RootsCount; + double b, PolyCoeff[P51_ARRAY_SIZE]; + + N = NumPoles; + for (k = N - 1; k >= 0; k--) { + // b is calc'd as a double because of all the division, but the result is essentially a large int. + b = Factorial(2 * N - k) / Factorial(k) / Factorial(N - k) + / pow(2.0, (double) (N - k)); + PolyCoeff[k] = b; + } + PolyCoeff[N] = 1.0; + + // The coefficients are generated in reverse order needed for P51. + ReverseCoeff(PolyCoeff, N); + RootsCount = FindRoots(N, PolyCoeff, Roots); + return (RootsCount); +} + +//--------------------------------------------------------------------------- + +// This Inverse Cheby code is identical to the Cheby code, except for two things. +// First, epsilon represents stop band atten, not ripple, so this epsilon is 1/epsilon. +// More importantly, the inverse cheby is defined in terms of F(1/s) instead of the usual F(s); +// After a bit of algebra, it is easy to see that the 1/s terms can be made s terms by simply +// multiplying the num and denom by s^2N. Then the 0th term becomes the highest term, so the +// coefficients are simply fed to the root finder in reverse order. +// Since 1 doesn't get added to the numerator, the lowest 1 or 2 terms will be 0, but we can't +// feed the root finder 0's as the highest term, so we have to be sure not to feed them to the root finder. + +int InvChebyPoly(int NumPoles, double StopBanddB, std::complex *ChebyPoles, + std::complex *ChebyZeros, int *ZeroCount) { + int j, k, N, PolesCount; + double Arg, Epsilon, ChebPolyCoeff[P51_ARRAY_SIZE], + PolyCoeff[P51_ARRAY_SIZE]; + std::complex SquaredPolyCoeff[P51_ARRAY_SIZE], A, B; + + N = NumPoles; + Epsilon = 1.0 / (pow(10.0, StopBanddB / 10.0) - 1.0); // actually Epsilon Squared + + // This algorithm is from the paper by Richard J Mathar. It generates the coefficients for the Cheb poly. + // It stores the Nth order coefficient in ChebPolyCoeff[N], and so on. Every other Cheb coeff is 0. See Wikipedia for a table that this code will generate. + for (j = 0; j <= N / 2; j++) { + Arg = Factorial(N - j - 1) / Factorial(j) / Factorial(N - 2 * j); + if (j % 2 == 1) + Arg *= -1.0; + Arg *= pow(2.0, (double) (N - 2 * j)) * (double) N / 2.0; + ChebPolyCoeff[N - 2 * j] = Arg; + if (N - (2 * j + 1) >= 0) { + ChebPolyCoeff[N - (2 * j + 1)] = 0.0; + } + } + + // Now square the Chebshev polynomial where we assume s = jw. To get the signs correct, + // we need to take j to the power. Then its a simple matter of adding powers and + // multiplying coefficients. j and k represent the exponents. That is, j=3 is the x^3 coeff, and so on. + for (j = 0; j <= 2 * N; j++) + SquaredPolyCoeff[j] = std::complex(0.0, 0.0); + + for (j = 0; j <= N; j++) + for (k = 0; k <= N; k++) { + A = pow(std::complex(0.0, 1.0), (double) j) * ChebPolyCoeff[j]; + B = pow(std::complex(0.0, 1.0), (double) k) * ChebPolyCoeff[k]; + SquaredPolyCoeff[j + k] = SquaredPolyCoeff[j + k] + A * B; // these end up entirely real. + } + + // Denominator + // Now we multiply the coefficients by Epsilon and add 1 to the denominator poly. + k = 0; + for (j = 0; j <= 2 * N; j++) + ChebPolyCoeff[j] = SquaredPolyCoeff[j].real() * Epsilon; + ChebPolyCoeff[0] += 1.0; + for (j = 0; j <= 2 * N; j++) + PolyCoeff[k++] = ChebPolyCoeff[j]; // Note this order is reversed from the Chebyshev routine. + k--; + PolesCount = FindRoots(k, PolyCoeff, ChebyPoles); + + // Numerator + k = 0; + for (j = 0; j <= 2 * N; j++) + ChebPolyCoeff[j] = SquaredPolyCoeff[j].real(); // Not using Epsilon here so the check for 0 on the next line is easier. Since the root finder normalizes the poly, it gets factored out anyway. + for (j = 0; j <= 2 * N; j++) + if (fabs(ChebPolyCoeff[j]) > 0.01) + break; // Get rid of the high order zeros. There will be eithe 0ne or two zeros to delete. + for (; j <= 2 * N; j++) + PolyCoeff[k++] = ChebPolyCoeff[j]; + k--; + *ZeroCount = FindRoots(k, PolyCoeff, ChebyZeros); + + return (PolesCount); + +} + +//--------------------------------------------------------------------------- + +// The complete Papouls poly is 1 + Epsilon * P(n) where P(n) is the 2*N Legendre poly. +int PapoulisPoly(int NumPoles, std::complex *Roots) { + int j, N, RootsCount; + double Epsilon, PolyCoeff[P51_ARRAY_SIZE]; + + N = NumPoles; + for (j = 0; j < 2 * N; j++) + PolyCoeff[j] = 0.0; // so we don't have to fill all the zero's. + + switch (N) { + case 1: // 1 pole + PolyCoeff[2] = 1.0; + break; + + case 2: // 2 pole + PolyCoeff[4] = 1.0; + break; + + case 3: // 3 pole + PolyCoeff[6] = 3.0; + PolyCoeff[4] = -3.0; + PolyCoeff[2] = 1.0; + break; + + case 4: + PolyCoeff[8] = 6.0; + PolyCoeff[6] = -8.0; + PolyCoeff[4] = 3.0; + break; + + case 5: + PolyCoeff[10] = 20.0; + PolyCoeff[8] = -40.0; + PolyCoeff[6] = 28.0; + PolyCoeff[4] = -8.0; + PolyCoeff[2] = 1.0; + break; + + case 6: + PolyCoeff[12] = 50.0; + PolyCoeff[10] = -120.0; + PolyCoeff[8] = 105.0; + PolyCoeff[6] = -40.0; + PolyCoeff[4] = 6.0; + break; + + case 7: + PolyCoeff[14] = 175.0; + PolyCoeff[12] = -525.0; + PolyCoeff[10] = 615.0; + PolyCoeff[8] = -355.0; + PolyCoeff[6] = 105.0; + PolyCoeff[4] = -15.0; + PolyCoeff[2] = 1.0; + break; + + case 8: + PolyCoeff[16] = 490.0; + PolyCoeff[14] = -1680.0; + PolyCoeff[12] = 2310.0; + PolyCoeff[10] = -1624.0; + PolyCoeff[8] = 615.0; + PolyCoeff[6] = -120.0; + PolyCoeff[4] = 10.0; + break; + + case 9: + PolyCoeff[18] = 1764.0; + PolyCoeff[16] = -7056.0; + PolyCoeff[14] = 11704.0; + PolyCoeff[12] = -10416.0; + PolyCoeff[10] = 5376.0; + PolyCoeff[8] = -1624.0; + PolyCoeff[6] = 276.0; + PolyCoeff[4] = -24.0; + PolyCoeff[2] = 1.0; + break; + + case 10: + PolyCoeff[20] = 5292.0; + PolyCoeff[18] = -23520.0; + PolyCoeff[16] = 44100.0; + PolyCoeff[14] = -45360.0; + PolyCoeff[12] = 27860.0; + PolyCoeff[10] = -10416.0; + PolyCoeff[8] = 2310.0; + PolyCoeff[6] = -280.0; + PolyCoeff[4] = 15.0; + break; + + case 11: + PolyCoeff[22] = 19404; + PolyCoeff[20] = -97020.0; + PolyCoeff[18] = 208740.0; + PolyCoeff[16] = -252840.0; + PolyCoeff[14] = 189420.0; + PolyCoeff[12] = -90804.0; + PolyCoeff[10] = 27860.0; + PolyCoeff[8] = -5320.0; + PolyCoeff[6] = 595.0; + PolyCoeff[4] = -35.0; + PolyCoeff[2] = 1.0; + break; + + case 12: + PolyCoeff[24] = 60984.0; + PolyCoeff[22] = -332640.0; + PolyCoeff[20] = 790020.0; + PolyCoeff[18] = -1071840.0; + PolyCoeff[16] = 916020.0; + PolyCoeff[14] = -512784.0; + PolyCoeff[12] = 189420.0; + PolyCoeff[10] = -45360.0; + PolyCoeff[8] = 6720.0; + PolyCoeff[6] = -560.0; + PolyCoeff[4] = 21.0; + break; + + case 13: + PolyCoeff[26] = 226512.0; + PolyCoeff[24] = -1359072.0; + PolyCoeff[22] = 3597264.0; + PolyCoeff[20] = -5528160.0; + PolyCoeff[18] = 5462820.0; + PolyCoeff[16] = -3632112.0; + PolyCoeff[14] = 1652232.0; + PolyCoeff[12] = -512784.0; + PolyCoeff[10] = 106380.0; + PolyCoeff[8] = -14160.0; + PolyCoeff[6] = 1128.0; + PolyCoeff[4] = -48.0; + PolyCoeff[2] = 1.0; + break; + + case 14: + PolyCoeff[28] = 736164.0; + PolyCoeff[26] = -4756752.0; + PolyCoeff[24] = 13675662.0; + PolyCoeff[22] = -23063040.0; + PolyCoeff[20] = 25322220.0; + PolyCoeff[18] = -18993744.0; + PolyCoeff[16] = 9934617.0; + PolyCoeff[14] = -3632112.0; + PolyCoeff[12] = 916020.0; + PolyCoeff[10] = -154560.0; + PolyCoeff[8] = 16506.0; + PolyCoeff[6] = -1008.0; + PolyCoeff[4] = 28.0; + break; + + case 15: + PolyCoeff[30] = 2760615.0; + PolyCoeff[28] = -19324305.0; + PolyCoeff[26] = 60747687.0; + PolyCoeff[24] = -113270157.0; + PolyCoeff[22] = 139378239.0; + PolyCoeff[20] = -119144025.0; + PolyCoeff[18] = 72539775.0; + PolyCoeff[16] = -31730787.0; + PolyCoeff[14] = 9934617.0; + PolyCoeff[12] = -2191959.0; + PolyCoeff[10] = 331065.0; + PolyCoeff[8] = -32655.0; + PolyCoeff[6] = 1953.0; + PolyCoeff[4] = -63.0; + PolyCoeff[2] = 1.0; + break; + + case 16: + PolyCoeff[32] = 9202050.0; + PolyCoeff[30] = -68708640.0; + PolyCoeff[28] = 231891660.0; + PolyCoeff[26] = -467747280.0; + PolyCoeff[24] = 628221594.0; + PolyCoeff[22] = -592431840.0; + PolyCoeff[20] = 403062660.0; + PolyCoeff[18] = -200142800.0; + PolyCoeff[16] = 72539775.0; + PolyCoeff[14] = -18993744.0; + PolyCoeff[12] = 3515820.0; + PolyCoeff[10] = -443520.0; + PolyCoeff[8] = 35910.0; + PolyCoeff[6] = -1680.0; + PolyCoeff[4] = 36.0; + break; + + case 17: + PolyCoeff[34] = 34763300.0; + PolyCoeff[32] = -278106400.0; + PolyCoeff[30] = 1012634480.0; + PolyCoeff[28] = -2221579360.0; + PolyCoeff[26] = 3276433160.0; + PolyCoeff[24] = -3431908480.0; + PolyCoeff[22] = 2629731104.0; + PolyCoeff[20] = -1496123200.0; + PolyCoeff[18] = 634862800.0; + PolyCoeff[16] = -200142800.0; + PolyCoeff[14] = 46307800.0; + PolyCoeff[12] = -7696304.0; + PolyCoeff[10] = 888580.0; + PolyCoeff[8] = -67760.0; + PolyCoeff[6] = 3160.0; + PolyCoeff[4] = -80.0; + PolyCoeff[2] = 1.0; + break; + + case 18: + PolyCoeff[36] = 118195220.0; + PolyCoeff[34] = -1001183040.0; + PolyCoeff[32] = 3879584280.0; + PolyCoeff[30] = -9110765664.0; + PolyCoeff[28] = 14480345880.0; + PolyCoeff[26] = -16474217760.0; + PolyCoeff[24] = 13838184360.0; + PolyCoeff[22] = -8725654080.0; + PolyCoeff[20] = 4158224928.0; + PolyCoeff[18] = -1496123200.0; + PolyCoeff[16] = 403062660.0; + PolyCoeff[14] = -79999920.0; + PolyCoeff[12] = 11397540.0; + PolyCoeff[10] = -1119888.0; + PolyCoeff[8] = 71280.0; + PolyCoeff[6] = -2640.0; + PolyCoeff[4] = 45.0; + break; + + case 19: + PolyCoeff[38] = 449141836.0; + PolyCoeff[36] = -4042276524.0; + PolyCoeff[34] = 16732271556.0; + PolyCoeff[32] = -42233237904.0; + PolyCoeff[30] = 72660859128.0; + PolyCoeff[28] = -90231621480.0; + PolyCoeff[26] = 83545742280.0; + PolyCoeff[24] = -58751550000.0; + PolyCoeff[22] = 31671113760.0; + PolyCoeff[20] = -13117232128.0; + PolyCoeff[18] = 4158224928.0; + PolyCoeff[16] = -999092952.0; + PolyCoeff[14] = 178966788.0; + PolyCoeff[12] = -23315292.0; + PolyCoeff[10] = 2130876.0; + PolyCoeff[8] = -129624.0; + PolyCoeff[6] = 4851.0; + PolyCoeff[4] = -99.0; + PolyCoeff[2] = 1.0; + break; + + case 20: + PolyCoeff[40] = 1551580888.0; + PolyCoeff[38] = -14699187360.0; + PolyCoeff[36] = 64308944700.0; + PolyCoeff[34] = -172355177280.0; + PolyCoeff[32] = 316521742680.0; + PolyCoeff[30] = -422089668000.0; + PolyCoeff[28] = 422594051880.0; + PolyCoeff[26] = -323945724960.0; + PolyCoeff[24] = 192167478360.0; + PolyCoeff[22] = -88572527680.0; + PolyCoeff[20] = 31671113760.0; + PolyCoeff[18] = -8725654080.0; + PolyCoeff[16] = 1829127300.0; + PolyCoeff[14] = -286125840.0; + PolyCoeff[12] = 32458140.0; + PolyCoeff[10] = -2560272.0; + PolyCoeff[8] = 131670.0; + PolyCoeff[6] = -3960.0; + PolyCoeff[4] = 55.0; + break; + } + + Epsilon = 0.1; // This controls the amount of pass band roll off. 0.01 < Epsilon < 0.250 + + // The poly is in terms of omega, but we need it in term of s = jw. So we need to + // multiply the approp coeff by neg 1 to account for j. Then mult by epsilon. + for (j = 0; j <= 2 * N; j++) { + if ((j / 2) % 2 == 1) + PolyCoeff[j] *= -1.0; + PolyCoeff[j] *= Epsilon; + } + + // Now add 1 to the poly. + PolyCoeff[0] = 1.0; + + // The coefficients are in reverse order needed for P51. + ReverseCoeff(PolyCoeff, N * 2); + RootsCount = FindRoots(N * 2, PolyCoeff, Roots); + + return (RootsCount); +} + +//--------------------------------------------------------------------------- + +// This code was described in "Elliptic Functions for Filter Design" +// H J Orchard and Alan N Willson IEE Trans on Circuits and Systems April 97 +// The equation numbers in the comments are from the paper. +// As the stop band attenuation -> infinity, the Elliptic -> Chebyshev. +int EllipticPoly(int FiltOrder, double Ripple, double DesiredSBdB, + std::complex *EllipPoles, std::complex *EllipZeros, int *ZeroCount) { + int j, k, n, LastK; + double K[ELLIPARRAYSIZE], G[ELLIPARRAYSIZE], Epsilon[ELLIPARRAYSIZE]; + double A, D, SBdB, dBErr, RealPart, ImagPart; + double DeltaK, PrevErr, Deriv; + std::complex C; + + for (j = 0; j < ELLIPARRAYSIZE; j++) + K[j] = G[j] = Epsilon[j] = 0.0; + if (Ripple < 0.001) + Ripple = 0.001; + if (Ripple > 1.0) + Ripple = 1.0; + Epsilon[0] = sqrt(pow(10.0, Ripple / 10.0) - 1.0); + + // Estimate K[0] to get the algorithm started. + K[0] = (double) (FiltOrder - 2) * 0.1605 + 0.016; + if (K[0] < 0.01) + K[0] = 0.01; + if (K[0] > 0.7) + K[0] = 0.7; + + // This loop calculates K[0] for the desired stopband attenuation. It typically loops < 5 times. + for (j = 0; j < MAX_ELLIP_ITER; j++) { + // Compute K with a forward Landen Transformation. + for (k = 1; k < 10; k++) { + K[k] = pow(K[k - 1] / (1.0 + sqrt(1.0 - K[k - 1] * K[k - 1])), 2.0); // eq. 10 + if (K[k] <= 1.0E-6) + break; + } + LastK = k; + + // Compute G with a backwards Landen Transformation. + G[LastK] = 4.0 * pow(K[LastK] / 4.0, (double) FiltOrder); + for (k = LastK; k >= 1; k--) { + G[k - 1] = 2.0 * sqrt(G[k]) / (1.0 + G[k]); // eq. 9 + } + + if (G[0] <= 0.0) + G[0] = 1.0E-10; + SBdB = 10.0 * log10(1.0 + pow(Epsilon[0] / G[0], 2.0)); // Current stopband attenuation dB + dBErr = DesiredSBdB - SBdB; + + if (fabs(dBErr) < 0.1) + break; + if (j == 0) // Do this on the 1st loop so we can calc a derivative. + { + if (dBErr > 0) + DeltaK = 0.005; + else + DeltaK = -0.005; + PrevErr = dBErr; + } else { + // Use Newtons Method to adjust K[0]. + Deriv = (PrevErr - dBErr) / DeltaK; + PrevErr = dBErr; + if (Deriv == 0.0) + break; // This happens when K[0] hits one of the limits set below. + DeltaK = dBErr / Deriv; + if (DeltaK > 0.1) + DeltaK = 0.1; + if (DeltaK < -0.1) + DeltaK = -0.1; + } + K[0] -= DeltaK; + if (K[0] < 0.001) + K[0] = 0.001; // must not be < 0.0 + if (K[0] > 0.990) + K[0] = 0.990; // if > 0.990 we get a pole in the RHP. This means we were unable to set the stop band atten to the desired level (the Ripple is too large for the Pole Count). + } + + // Epsilon[0] was calulated above, now calculate Epsilon[LastK] from G + for (j = 1; j <= LastK; j++) { + A = (1.0 + G[j]) * Epsilon[j - 1] / 2.0; // eq. 37 + Epsilon[j] = A + sqrt(A * A + G[j]); + } + + // Calulate the poles and zeros. + ImagPart = log( + (1.0 + sqrt(1.0 + Epsilon[LastK] * Epsilon[LastK])) + / Epsilon[LastK]) / (double) FiltOrder; // eq. 22 + n = 0; + for (j = 1; j <= FiltOrder / 2; j++) { + RealPart = (double) (2 * j - 1) * M_PI_2 / (double) FiltOrder; // eq. 19 + C = std::complex(0.0, -1.0) / cos(std::complex(-RealPart, ImagPart)); // eq. 20 + D = 1.0 / cos(RealPart); + for (k = LastK; k >= 1; k--) { + C = (C - K[k] / C) / (1.0 + K[k]); // eq. 36 + D = (D + K[k] / D) / (1.0 + K[k]); + } + + EllipPoles[n] = 1.0 / C; + EllipPoles[n + 1] = std::conj(EllipPoles[n]); + EllipZeros[n] = std::complex(0.0, D / K[0]); + EllipZeros[n + 1] = std::conj(EllipZeros[n]); + n += 2; + } + *ZeroCount = n; // n is the num zeros + + if (FiltOrder % 2 == 1) // The real pole for odd pole counts + { + A = 1.0 / sinh(ImagPart); + for (k = LastK; k >= 1; k--) { + A = (A - K[k] / A) / (1.0 + K[k]); // eq. 38 + } + EllipPoles[n] = std::complex(-1.0 / A, 0.0); + n++; + } + + return (n); // n is the num poles. There will be 1 more pole than zeros for odd pole counts. + +} +//--------------------------------------------------------------------------- + +} // namespace diff --git a/kitiirfir/LowPassRoots.h b/kitiirfir/LowPassRoots.h new file mode 100644 index 000000000..5c614a57f --- /dev/null +++ b/kitiirfir/LowPassRoots.h @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------------- + +#ifndef LowPassRootsH +#define LowPassRootsH + +#include +#define MAX_ELLIP_ITER 15 +#define ELLIPARRAYSIZE 20 // needs to be > 10 and >= Max Num Poles + 1 + +namespace kitiirfir +{ + +void ReverseCoeff(double *P, int N); +int ButterworthPoly(int NumPoles, std::complex *Roots); +int GaussianPoly(int NumPoles, std::complex *Roots); +int AdjustablePoly(int NumPoles, std::complex *Roots, double Gamma); +int ChebyshevPoly(int NumPoles, double Ripple, std::complex *Roots); +int BesselPoly(int NumPoles, std::complex *Roots); +int InvChebyPoly(int NumPoles, double StopBanddB, std::complex *ChebyPoles, std::complex *ChebyZeros, int *ZeroCount); +int PapoulisPoly(int NumPoles, std::complex *Roots); +int EllipticPoly(int FiltOrder, double Ripple, double DesiredSBdB, std::complex *EllipPoles, std::complex *EllipZeros, int *ZeroCount); + +} // namespace + +#endif + + diff --git a/kitiirfir/NewParksMcClellan.cpp b/kitiirfir/NewParksMcClellan.cpp new file mode 100644 index 000000000..507b766b8 --- /dev/null +++ b/kitiirfir/NewParksMcClellan.cpp @@ -0,0 +1,627 @@ +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + If you find a problem, please leave a note at: + http://www.iowahills.com/feedbackcomments.html + May 1, 2016 + + ShowMessage is a C++ Builder function, and it usage has been commented out. + If you are using C++ Builder, include vcl.h for ShowMessage. + Otherwise replace ShowMessage with something appropriate for your compiler. + + This is a C translation of the Parks McClellan algorithm originally done in Fortran. + + The original fortran code came from the Parks McClellan article on Wikipedia. + http://en.wikipedia.org/wiki/Parks%E2%80%93McClellan_filter_design_algorithm + + This code is quite different from the original. The original code had 69 goto statements, + which made it nearly impossible to follow. And of course, it was Fortran code, so many changes + had to be made regardless of style. + + Apparently, Fortran doesn't use global variables. Instead, is uses something called + common memory space. e.g. COMMON PI2,AD,DEV,X,Y,GRID,DES,WT,ALPHA,IEXT,NFCNS,NGRID + I simply converted these to globals. It isn't pretty, but for this purpose, who cares? + + The first step was to get a C version of the code working with as few changes as possible. + That version is also available on: http://www.iowahills.com/A7ExampleCodePage.html + Then, in our desire to see if the code could be made more understandable, we decided to + remove as many goto statements as possible. We checked our work by comparing the coefficients + between this code and our original translation on more than 1000 filters while varying all the parameters. + + Ultimately, we were able to reduce the goto count from 69 to 7, all of which are in the Remez + function. Of the 7 remaining, 3 of these are at the very bottom of the function, and go + back to the very top of the function. These could have been removed, but our goal was to + clarify the code, not restyle it, and since they are clear, we let them be. + + The other 4 goto statements are intertwined in a rather nasty way. We recommend you print out + the Remez code, tape the sheets end to end, and trace out the goto's. It wasn't apparent to + us that they can be removed without an extensive study of the code. + + For better or worse, we also removed any code that was obviously related to Hilbert transforms + and Differentiators. We did this because we aren't interested in these, and we also don't + believe this algorithm does a very good job with them (far too much ripple). + + We added the functions CalcCoefficients() and ErrTest() as a way to simplify things a bit. + + We also found 3 sections of code that never executed. Two of the sections were just a few lines + that the goto's always went around. The third section represented nearly half of the CalcCoefficients() + function. This statement always tested the same, which never allowed the code to execute. + if(GRID[1] < 0.01 && GRID[NGRID] > 0.49) KKK = 1; + This may be due to the 0.01 minimum width limit we set for the bands. + + Note our use of MIN_TEST_VAL. The original code wasn't guarding against division by zero. + Limiting the return values as we have also helped the algorithm's convergence behavior. + + In an effort to improve readability, we made a large number of variable name changes and also + deleted a large number of variables. We left many variable names in tact, in part as an aid when + comparing to the original code, and in part because a better name wasn't obvious. + + This code is essentially straight c, and should compile with few, if any changes. Note the error + message in CalcParkCoeff2. It warns of the possibility of convergence failure, but you will + find that the iteration count NITER, isn't always an indicator of convergence problems when + it is less than 3, as stated in the original Fortran code comments. + + If you find a problem with this code, please leave us a note on: + http://www.iowahills.com/feedbackcomments.html + + */ + +#include "NewParksMcClellan.h" +#include +#define M_2PI 6.28318530717958647692 + +namespace kitiirfir +{ + +//--------------------------------------------------------------------------- + +// Global variables. +int HalfTapCount, ExchangeIndex[PARKS_SMALL]; +double LeGrangeD[PARKS_SMALL], Alpha[PARKS_SMALL], CosOfGrid[PARKS_SMALL], + DesPlus[PARKS_SMALL]; +double Coeff[PARKS_SMALL], Edge[PARKS_SMALL], BandMag[PARKS_SMALL], + InitWeight[PARKS_SMALL]; +double DesiredMag[PARKS_BIG], Grid[PARKS_BIG], Weight[PARKS_BIG]; + +void NewParksMcClellan(double *FirCoeff, int NumTaps, TFIRPassTypes PassType, + double OmegaC, double BW, double ParksWidth) { + if (NumTaps > MAX_NUM_PARKS_TAPS) + return; + int j, NumBands; + + // Note: There is no feedback to the caller if ParksWidth or NumTaps are modified here. + if (PassType == firBPF || PassType == firNOTCH || NumTaps > 70) { + if (ParksWidth > 0.15) + ParksWidth = 0.15; // Else we have covergence problems. + } + + if (PassType == firNOTCH || PassType == firHPF) { + if (NumTaps % 2 == 0) + NumTaps++; // High pass and notch filters must have odd tap counts. + } + + if (NumTaps > MAX_NUM_PARKS_TAPS) + NumTaps = MAX_NUM_PARKS_TAPS; + + // It helps the algorithm a great deal if each band is at least 0.01 wide. + // The weights used here came from the orig PM code. + if (PassType == firLPF) { + NumBands = 2; + Edge[1] = 0.0; // Omega = 0 + Edge[2] = OmegaC; // Pass band edge + if (Edge[2] < 0.01) + Edge[2] = 0.01; + if (Edge[2] > 0.98) + Edge[2] = 0.98; + Edge[3] = Edge[2] + ParksWidth; // Stop band edge + if (Edge[3] > 0.99) + Edge[3] = 0.99; + Edge[4] = 1.0; // Omega = Pi + BandMag[1] = 1.0; + BandMag[2] = 0.0; + InitWeight[1] = 1.0; + InitWeight[2] = 10.0; + } + + if (PassType == firHPF) { + NumBands = 2; + Edge[1] = 0.0; // Omega = 0 + Edge[3] = OmegaC; // Pass band edge + if (Edge[3] > 0.99) + Edge[3] = 0.99; + if (Edge[3] < 0.02) + Edge[3] = 0.02; + Edge[2] = Edge[3] - ParksWidth; // Stop band edge + if (Edge[2] < 0.01) + Edge[2] = 0.01; + Edge[4] = 1.0; // Omega = Pi + BandMag[1] = 0.0; + BandMag[2] = 1.0; + InitWeight[1] = 10.0; + InitWeight[2] = 1.0; + } + + if (PassType == firBPF) { + NumBands = 3; + Edge[1] = 0.0; // Omega = 0 + Edge[3] = OmegaC - BW / 2.0; // Left pass band edge. + if (Edge[3] < 0.02) + Edge[3] = 0.02; + Edge[2] = Edge[3] - ParksWidth; // Left stop band edge + if (Edge[2] < 0.01) + Edge[2] = 0.01; + Edge[4] = OmegaC + BW / 2.0; // Right pass band edge + if (Edge[4] > 0.98) + Edge[4] = 0.98; + Edge[5] = Edge[4] + ParksWidth; // Right stop band edge + if (Edge[5] > 0.99) + Edge[5] = 0.99; + Edge[6] = 1.0; // Omega = Pi + + BandMag[1] = 0.0; + BandMag[2] = 1.0; + BandMag[3] = 0.0; + InitWeight[1] = 10.0; + InitWeight[2] = 1.0; + InitWeight[3] = 10.0; + } + + // This algorithm tends to have problems with narrow band notch filters. + if (PassType == firNOTCH) { + NumBands = 3; + Edge[1] = 0.0; // Omega = 0 + Edge[3] = OmegaC - BW / 2.0; // Left stop band edge. + if (Edge[3] < 0.02) + Edge[3] = 0.02; + Edge[2] = Edge[3] - ParksWidth; // Left pass band edge + if (Edge[2] < 0.01) + Edge[2] = 0.01; + Edge[4] = OmegaC + BW / 2.0; // Right stop band edge + if (Edge[4] > 0.98) + Edge[4] = 0.98; + Edge[5] = Edge[4] + ParksWidth; // Right pass band edge + if (Edge[5] > 0.99) + Edge[5] = 0.99; + Edge[6] = 1.0; // Omega = Pi + + BandMag[1] = 1.0; + BandMag[2] = 0.0; + BandMag[3] = 1.0; + InitWeight[1] = 1.0; + InitWeight[2] = 10.0; + InitWeight[3] = 1.0; + } + + // Parks McClellan's edges are based on 2Pi, we are based on Pi. + for (j = 1; j <= 2 * NumBands; j++) + Edge[j] /= 2.0; + + CalcParkCoeff2(NumBands, NumTaps, FirCoeff); + +} +//--------------------------------------------------------------------------- + +void CalcParkCoeff2(int NumBands, int TapCount, double *FirCoeff) { + int j, k, GridCount, GridIndex, BandIndex, NumIterations; + double LowFreqEdge, UpperFreq, TempVar, Change; + bool OddNumTaps; + GridCount = 16; // Grid Density + + if (TapCount % 2) + OddNumTaps = true; + else + OddNumTaps = false; + + HalfTapCount = TapCount / 2; + if (OddNumTaps) + HalfTapCount++; + + Grid[1] = Edge[1]; + LowFreqEdge = GridCount * HalfTapCount; + LowFreqEdge = 0.5 / LowFreqEdge; + j = 1; + k = 1; + BandIndex = 1; + while (BandIndex <= NumBands) { + UpperFreq = Edge[k + 1]; + while (Grid[j] <= UpperFreq) { + TempVar = Grid[j]; + DesiredMag[j] = BandMag[BandIndex]; + Weight[j] = InitWeight[BandIndex]; + j++; + ; + Grid[j] = TempVar + LowFreqEdge; + } + + Grid[j - 1] = UpperFreq; + DesiredMag[j - 1] = BandMag[BandIndex]; + Weight[j - 1] = InitWeight[BandIndex]; + k += 2; + BandIndex++; + if (BandIndex <= NumBands) + Grid[j] = Edge[k]; + } + + GridIndex = j - 1; + if (!OddNumTaps && Grid[GridIndex] > (0.5 - LowFreqEdge)) + GridIndex--; + + if (!OddNumTaps) { + for (j = 1; j <= GridIndex; j++) { + Change = cos(M_PI * Grid[j]); + DesiredMag[j] = DesiredMag[j] / Change; + Weight[j] = Weight[j] * Change; + } + } + + TempVar = (double) (GridIndex - 1) / (double) HalfTapCount; + for (j = 1; j <= HalfTapCount; j++) { + ExchangeIndex[j] = (int) ((double) (j - 1) * TempVar + 1.0); + } + ExchangeIndex[HalfTapCount + 1] = GridIndex; + + NumIterations = Remez2(GridIndex); + CalcCoefficients(); + + // Calculate the impulse response. + if (OddNumTaps) { + for (j = 1; j <= HalfTapCount - 1; j++) { + Coeff[j] = 0.5 * Alpha[HalfTapCount + 1 - j]; + } + Coeff[HalfTapCount] = Alpha[1]; + } else { + Coeff[1] = 0.25 * Alpha[HalfTapCount]; + for (j = 2; j <= HalfTapCount - 1; j++) { + Coeff[j] = + 0.25 + * (Alpha[HalfTapCount + 1 - j] + + Alpha[HalfTapCount + 2 - j]); + } + Coeff[HalfTapCount] = 0.5 * Alpha[1] + 0.25 * Alpha[2]; + } + + // Output section. + for (j = 1; j <= HalfTapCount; j++) + FirCoeff[j - 1] = Coeff[j]; + if (OddNumTaps) + for (j = 1; j < HalfTapCount; j++) + FirCoeff[HalfTapCount + j - 1] = Coeff[HalfTapCount - j]; + else + for (j = 1; j <= HalfTapCount; j++) + FirCoeff[HalfTapCount + j - 1] = Coeff[HalfTapCount - j + 1]; + + // Display the iteration count. + if (NumIterations < 3) { + // ShowMessage("Parks McClellan unable to coverge"); + } + +} + +//--------------------------------------------------------------------------------------- +int Remez2(int GridIndex) { + int j, JET, K, k, NU, JCHNGE, K1, KNZ, KLOW, NUT, KUP; + int NUT1 = 0, LUCK, KN, NITER; + double Deviation, DNUM, DDEN, TempVar; + double DEVL, COMP = 0.0, YNZ = 0.0, Y1 = 0.0, ERR; + + LUCK = 0; + DEVL = -1.0; + NITER = 1; // Init this to 1 to be consistent with the orig code. + + TOP_LINE: // We come back to here from 3 places at the bottom. + ExchangeIndex[HalfTapCount + 2] = GridIndex + 1; + + for (j = 1; j <= HalfTapCount + 1; j++) { + TempVar = Grid[ExchangeIndex[j]]; + CosOfGrid[j] = cos(TempVar * M_2PI); + } + + JET = (HalfTapCount - 1) / 15 + 1; + for (j = 1; j <= HalfTapCount + 1; j++) { + LeGrangeD[j] = LeGrangeInterp2(j, HalfTapCount + 1, JET); + } + + DNUM = 0.0; + DDEN = 0.0; + K = 1; + for (j = 1; j <= HalfTapCount + 1; j++) { + k = ExchangeIndex[j]; + DNUM += LeGrangeD[j] * DesiredMag[k]; + DDEN += (double) K * LeGrangeD[j] / Weight[k]; + K = -K; + } + Deviation = DNUM / DDEN; + + NU = 1; + if (Deviation > 0.0) + NU = -1; + Deviation = -(double) NU * Deviation; + K = NU; + for (j = 1; j <= HalfTapCount + 1; j++) { + k = ExchangeIndex[j]; + TempVar = (double) K * Deviation / Weight[k]; + DesPlus[j] = DesiredMag[k] + TempVar; + K = -K; + } + + if (Deviation <= DEVL) + return (NITER); // Ouch + + DEVL = Deviation; + JCHNGE = 0; + K1 = ExchangeIndex[1]; + KNZ = ExchangeIndex[HalfTapCount + 1]; + KLOW = 0; + NUT = -NU; + + //Search for the extremal frequencies of the best approximation. + + j = 1; + while (j < HalfTapCount + 2) { + KUP = ExchangeIndex[j + 1]; + k = ExchangeIndex[j] + 1; + NUT = -NUT; + if (j == 2) + Y1 = COMP; + COMP = Deviation; + + if (k < KUP && !ErrTest(k, NUT, COMP, &ERR)) { + L210: COMP = (double) NUT * ERR; + for (k++; k < KUP; k++) { + if (ErrTest(k, NUT, COMP, &ERR)) + break; // for loop + COMP = (double) NUT * ERR; + } + + ExchangeIndex[j] = k - 1; + j++; + KLOW = k - 1; + JCHNGE++; + continue; // while loop + } + + k--; + + L225: k--; + if (k <= KLOW) { + k = ExchangeIndex[j] + 1; + if (JCHNGE > 0) { + ExchangeIndex[j] = k - 1; + j++; + KLOW = k - 1; + JCHNGE++; + continue; // while loop + } else // JCHNGE <= 0 + { + for (k++; k < KUP; k++) { + if (ErrTest(k, NUT, COMP, &ERR)) + continue; // for loop + goto L210; + } + + KLOW = ExchangeIndex[j]; + j++; + continue; // while loop + } + } + // Can't use a do while loop here, it would outdent the two continue statements. + if (ErrTest(k, NUT, COMP, &ERR) && JCHNGE <= 0) + goto L225; + + if (ErrTest(k, NUT, COMP, &ERR)) { + KLOW = ExchangeIndex[j]; + j++; + continue; // while loop + } + + COMP = (double) NUT * ERR; + + L235: for (k--; k > KLOW; k--) { + if (ErrTest(k, NUT, COMP, &ERR)) + break; // for loop + COMP = (double) NUT * ERR; + } + + KLOW = ExchangeIndex[j]; + ExchangeIndex[j] = k + 1; + j++; + JCHNGE++; + } // end while(j ExchangeIndex[1]) + K1 = ExchangeIndex[1]; + if (KNZ < ExchangeIndex[HalfTapCount + 1]) + KNZ = ExchangeIndex[HalfTapCount + 1]; + NUT1 = NUT; + NUT = -NU; + k = 0; + KUP = K1; + COMP = YNZ * 1.00001; + LUCK = 1; + + for (k++; k < KUP; k++) { + if (ErrTest(k, NUT, COMP, &ERR)) + continue; // for loop + j = HalfTapCount + 2; + goto L210; + } + LUCK = 2; + break; // break while(j <= HalfTapCount+2) loop + } // end while(j <= HalfTapCount+2) + + if (LUCK == 1 || LUCK == 2) { + if (LUCK == 1) { + if (COMP > Y1) + Y1 = COMP; + K1 = ExchangeIndex[HalfTapCount + 2]; + } + + k = GridIndex + 1; + KLOW = KNZ; + NUT = -NUT1; + COMP = Y1 * 1.00001; + + for (k--; k > KLOW; k--) { + if (ErrTest(k, NUT, COMP, &ERR)) + continue; // for loop + j = HalfTapCount + 2; + COMP = (double) NUT * ERR; + LUCK = 3; // last time in this if(LUCK == 1 || LUCK == 2) + goto L235; + } + + if (LUCK == 2) { + if (JCHNGE > 0 && NITER++ < ITRMAX) + goto TOP_LINE; + else + return (NITER); + } + + for (j = 1; j <= HalfTapCount; j++) { + ExchangeIndex[HalfTapCount + 2 - j] = ExchangeIndex[HalfTapCount + 1 + - j]; + } + ExchangeIndex[1] = K1; + if (NITER++ < ITRMAX) + goto TOP_LINE; + } // end if(LUCK == 1 || LUCK == 2) + + KN = ExchangeIndex[HalfTapCount + 2]; + for (j = 1; j <= HalfTapCount; j++) { + ExchangeIndex[j] = ExchangeIndex[j + 1]; + } + ExchangeIndex[HalfTapCount + 1] = KN; + if (NITER++ < ITRMAX) + goto TOP_LINE; + + return (NITER); + +} + +//----------------------------------------------------------------------- +// Function to calculate the lagrange interpolation coefficients for use in the function gee. +double LeGrangeInterp2(int K, int N, int M) // D + { + int j, k; + double Dee, Q; + Dee = 1.0; + Q = CosOfGrid[K]; + for (k = 1; k <= M; k++) + for (j = k; j <= N; j += M) { + if (j != K) + Dee = 2.0 * Dee * (Q - CosOfGrid[j]); + } + if (fabs(Dee) < MIN_TEST_VAL) { + if (Dee < 0.0) + Dee = -MIN_TEST_VAL; + else + Dee = MIN_TEST_VAL; + } + return (1.0 / Dee); +} + +//----------------------------------------------------------------------- +// Function to evaluate the frequency response using the Lagrange interpolation +// formula in the barycentric form. +double GEE2(int K, int N) { + int j; + double P, C, Dee, XF; + P = 0.0; + XF = Grid[K]; + XF = cos(M_2PI * XF); + Dee = 0.0; + for (j = 1; j <= N; j++) { + C = XF - CosOfGrid[j]; + if (fabs(C) < MIN_TEST_VAL) { + if (C < 0.0) + C = -MIN_TEST_VAL; + else + C = MIN_TEST_VAL; + } + C = LeGrangeD[j] / C; + Dee = Dee + C; + P = P + C * DesPlus[j]; + } + if (fabs(Dee) < MIN_TEST_VAL) { + if (Dee < 0.0) + Dee = -MIN_TEST_VAL; + else + Dee = MIN_TEST_VAL; + } + return (P / Dee); +} + +//----------------------------------------------------------------------- + +bool ErrTest(int k, int Nut, double Comp, double *Err) { + *Err = GEE2(k, HalfTapCount + 1); + *Err = (*Err - DesiredMag[k]) * Weight[k]; + if ((double) Nut * *Err - Comp <= 0.0) + return (true); + else + return (false); +} + +//----------------------------------------------------------------------- + +// Calculation of the coefficients of the best approximation using the inverse discrete fourier transform. +void CalcCoefficients(void) { + int j, k, n; + double GTempVar, OneOverNumTaps; + double Omega, TempVar, FreqN, TempX, GridCos; + double GeeArray[PARKS_SMALL]; + + GTempVar = Grid[1]; + CosOfGrid[HalfTapCount + 2] = -2.0; + OneOverNumTaps = 1.0 / (double) (2 * HalfTapCount - 1); + k = 1; + + for (j = 1; j <= HalfTapCount; j++) { + FreqN = (double) (j - 1) * OneOverNumTaps; + TempX = cos(M_2PI * FreqN); + + GridCos = CosOfGrid[k]; + if (TempX <= GridCos) { + while (TempX <= GridCos && (GridCos - TempX) >= MIN_TEST_VAL) // MIN_TEST_VAL = 1.0E-6 + { + k++; + ; + GridCos = CosOfGrid[k]; + } + } + if (TempX <= GridCos || (TempX - GridCos) < MIN_TEST_VAL) { + GeeArray[j] = DesPlus[k]; // Desired Response + } else { + Grid[1] = FreqN; + GeeArray[j] = GEE2(1, HalfTapCount + 1); + } + if (k > 1) + k--; + } + + Grid[1] = GTempVar; + for (j = 1; j <= HalfTapCount; j++) { + TempVar = 0.0; + Omega = (double) (j - 1) * M_2PI * OneOverNumTaps; + for (n = 1; n <= HalfTapCount - 1; n++) { + TempVar += GeeArray[n + 1] * cos(Omega * (double) n); + } + TempVar = 2.0 * TempVar + GeeArray[1]; + Alpha[j] = TempVar; + } + + Alpha[1] = Alpha[1] * OneOverNumTaps; + for (j = 2; j <= HalfTapCount; j++) { + Alpha[j] = 2.0 * Alpha[j] * OneOverNumTaps; + } + +} + +//----------------------------------------------------------------------- + +} // namespace + diff --git a/kitiirfir/NewParksMcClellan.h b/kitiirfir/NewParksMcClellan.h new file mode 100644 index 000000000..16af86df1 --- /dev/null +++ b/kitiirfir/NewParksMcClellan.h @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------------- + +#ifndef NewParksMcClellanH +#define NewParksMcClellanH + +//--------------------------------------------------------------------------- +#define PARKS_BIG 1100 // Per the original code, this must be > 8 * MaxNumTaps = 8 * 128 = 1024 +#define PARKS_SMALL 256 // This needs to be greater than or equal to MAX_NUM_PARKS_TAPS +#define MAX_NUM_PARKS_TAPS 127 // This was the limit set in the original code. +#define ITRMAX 50 // Max Number of Iterations. Some Notch and BPF are running ~ 43 +#define MIN_TEST_VAL 1.0E-6 // Min value used in LeGrangeInterp and GEE + +// If the FIRFilterCode.cpp file is in the project along with this NewParksMcClellan file, +// we need to include FIRFilterCode.h for the TFIRPassTypes enum. +#include "FIRFilterCode.h" +//enum TFIRPassTypes {firLPF, firHPF, firBPF, firNOTCH, ftNOT_FIR}; + +namespace kitiirfir +{ + +void NewParksMcClellan(double *FirCoeff, int NumTaps, TFIRPassTypes PassType, double OmegaC, double BW, double ParksWidth); +void CalcParkCoeff2(int NBANDS, int NFILT, double *FirCoeff); +double LeGrangeInterp2(int K, int N, int M); +double GEE2(int K, int N); +int Remez2(int GridIndex); +bool ErrTest(int k, int Nut, double Comp, double *Err); +void CalcCoefficients(void); + +} // namespace + +#endif diff --git a/kitiirfir/PFiftyOneRevE.cpp b/kitiirfir/PFiftyOneRevE.cpp new file mode 100644 index 000000000..8d03e94af --- /dev/null +++ b/kitiirfir/PFiftyOneRevE.cpp @@ -0,0 +1,649 @@ + +//--------------------------------------------------------------------------- + +#include "PFiftyOneRevE.h" +#include "QuadRootsRevH.h" +#include +#include + +//--------------------------------------------------------------------------- +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + Sept 21, 2016 Rev E + + If you find a problem with this code, please leave a note on: + http://www.iowahills.com/feedbackcomments.html + + This is for polynomials with real coefficients, up to 100th order. + + PFiftyOne has 4 loops. The outermost while loop runs until the polynomial's order + N, has been reduced to 1 or 2. + + The TypeOfK loop controls how the K polynomial is initialized. About 99.999% of all + roots are found with TypeOfK = 0. + + The AngleNumber loop controls how we initialize the 2nd order polynomial TUV. + The angles move between the 1st and 2nd quadrants and are defined in the + SetTUVandK() function. About 99% of roots are found in the 1st 4 angles. + + The Iter loop first calls the QuadIterate routine, which finds quadratic factors, + real or complex. If QuadIterate fails to converge in QUAD_ITER_MAX iterations, + the RealIterate routine is called to try to extract a single real root. + + The input array Coeff[] contains the coefficients arranged in descending order. + For example, a 4th order poly would be: + P(x) = Coeff[0]*x^4 + Coeff[1]*x^3 + Coeff[2]*x^2 + Coeff[3]*x + Coeff[4] + + On return, there is no particular ordering to the roots, but complex root pairs + will be in adjacent array cells. It may be helpful to know that the complex root pairs + are exact cunjugates of each other, meaning that you can use the == operator + to search for complex pairs in the arrays.F + + This returns Degree on success, 0 on failure. + */ + +namespace kitiirfir +{ + +//--------------------------------------------------------------------------- +// None of the code uses long double variables except for the P51 root finder code +// where long doubles were essential. FindRoots is an interface to P51 for code that is +// using complex variables CplxD. +int FindRoots(int N, double *Coeff, std::complex *Roots) { + int j; + long double P[P51_ARRAY_SIZE], RealRoot[P51_ARRAY_SIZE], + ImagRoot[P51_ARRAY_SIZE]; + + for (j = 0; j <= N; j++) + P[j] = Coeff[j]; // double to long double + N = PFiftyOne(P, N, RealRoot, ImagRoot); + for (j = 0; j < N; j++) + Roots[j] = std::complex((double) RealRoot[j], + (double) ImagRoot[j]); // long double to double + return (N); +} + +//--------------------------------------------------------------------------- + +int PFiftyOne(long double *Coeff, int Degree, long double *RealRoot, + long double *ImagRoot) { + if (Degree > P51_MAXDEGREE || Degree < 0) { + //ShowMessage("Poly Degree is out of range in the P51 Root Finder."); + return (0); + } + + TUpdateStatus UpdateStatus; + int N, NZ, j, Iter, AngleNumber, TypeOfK; + long double RealZero, QuadX; + long double TUV[3]; + long double *P, *QuadQP, *RealQP, *QuadK, *RealK, *QK; + + N = Degree; // N is decremented as roots are found. + + P = new (std::nothrow) long double[N + 2]; + QuadQP = new (std::nothrow) long double[N + 2]; + RealQP = new (std::nothrow) long double[N + 2]; + QuadK = new (std::nothrow) long double[N + 2]; + RealK = new (std::nothrow) long double[N + 2]; + QK = new (std::nothrow) long double[N + 2]; + if (P == NULL || QuadQP == NULL || RealQP == NULL || QuadK == NULL + || RealK == NULL || QK == NULL) { + //ShowMessage("Memory not Allocated in PFiftyOne root finder."); + return (0); + } + + for (j = 0; j <= N; j++) + P[j] = Coeff[j]; // Copy the coeff. P gets modified. + for (j = 0; j < N; j++) + RealRoot[j] = ImagRoot[j] = 0.0; // Init to zero, in case any leading or trailing zeros are removed. + + // Remove trailing zeros. A tiny P[N] relative to P[N-1] is not allowed. + while (N > 0 && fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1])) { + N--; + } + + // Remove leading zeros. + while (N > 0 && P[0] == 0.0) { + for (j = 0; j < N; j++) + P[j] = P[j + 1]; + N--; + } + + // P[0] must = 1 + if (P[0] != 1.0) { + for (j = 1; j <= N; j++) + P[j] /= P[0]; + P[0] = 1.0; + } + + TypeOfK = 0; + while (N > 4 && TypeOfK < MAX_NUM_K) { + NZ = 0; // Num Zeros found. (Used in the loop controls below.) + QuadX = powl(fabsl(P[N]), 1.0 / (long double) N) / 2.0; // QuadX is used to init TUV + + for (TypeOfK = 0; TypeOfK < MAX_NUM_K && NZ == 0; TypeOfK++) // Iterate on the different possible QuadK inits. + { + for (AngleNumber = 0; AngleNumber < MAX_NUM_ANGLES && NZ == 0; + AngleNumber++) // Iterate on the angle used to init TUV. + { + SetTUVandK(P, N, TUV, RealK, QuadK, QuadX, AngleNumber, + TypeOfK); // Init TUV and both K's + for (Iter = 0; Iter < N && NZ == 0; Iter++) // Allow N calls to QuadIterate for N*QUAD_ITER_MAX iterations, then try a different angle. + { + NZ = QuadIterate(Iter, P, QuadQP, QuadK, QK, N, TUV, + &UpdateStatus); // NZ = 2 for a pair of complex roots or 2 real roots. + + if (NZ == 0) // Try for a real root. + { + if (fabsl(QuadK[N - 2]) > TINY_VALUE * fabsl(P[N])) + RealZero = -P[N] / QuadK[N - 2]; // This value gets refined by QuadIterate. + else + RealZero = 0.0; + NZ = RealIterate(Iter, P, RealQP, RealK, QK, N, + &RealZero); // NZ = 1 for a single real root. + } + + if (NZ == 0 && UpdateStatus == BAD_ANGLE) + break; // If RealIterate failed and UpdateTUV called this a bad angle, it's pointless to iterate further on this angle. + + } // Iter loop Note the use of NZ in the loop controls. + } // AngleNumber loop + } // TypeOfK loop + + // Done iterating. If NZ==0 at this point, we failed and will exit below. + // Decrement N, and set P to the quotient QP. QP = P/TUV or QP = P/(x-RealZero) + if (NZ == 2) // Store a pair of complex roots or 2 real roots. + { + j = Degree - N; + QuadRoots(TUV, &RealRoot[j], &ImagRoot[j]); + N -= NZ; + for (j = 0; j <= N; j++) + P[j] = QuadQP[j]; + TypeOfK = 0; + } + + if (NZ == 1) // Store a single real root + { + j = Degree - N; + RealRoot[j] = RealZero; + ImagRoot[j] = 0.0; + N -= NZ; + for (j = 0; j <= N; j++) + P[j] = RealQP[j]; + TypeOfK = 0; + } + + // Remove any trailing zeros on P. P[N] should never equal zero, but can approach zero + // because of roundoff errors. If P[N] is zero or tiny relative to P[N-1], we take the hit, + // and place a root at the origin. This needs to be checked, but virtually never happens. + while (fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1]) && N > 0) { + j = Degree - N; + RealRoot[j] = 0.0; + ImagRoot[j] = 0.0; + N--; + //ShowMessage("Removed a zero at the origin."); + } + + } // The outermost loop while(N > 2) + + delete[] QuadQP; + delete[] RealQP; + delete[] QuadK; + delete[] RealK; + delete[] QK; + + // Done, except for the last 1 or 2 roots. If N isn't 1 or 2 at this point, we failed. + if (N == 1) { + j = Degree - N; + RealRoot[j] = -P[1] / P[0]; + ImagRoot[j] = 0.0; + delete[] P; + return (Degree); + } + + if (N == 2) { + j = Degree - N; + QuadRoots(P, &RealRoot[j], &ImagRoot[j]); + delete[] P; + return (Degree); + } + + if (N == 3) { + j = Degree - N; + CubicRoots(P, &RealRoot[j], &ImagRoot[j]); + delete[] P; + return (Degree); + } + + if (N == 4) { + j = Degree - N; + BiQuadRoots(P, &RealRoot[j], &ImagRoot[j]); + delete[] P; + return (Degree); + } + + // ShowMessage("The P51 root finder failed to converge on a solution."); + return (0); + +} + +//--------------------------------------------------------------------------- +// The purpose of this function is to find a 2nd order polynomial, TUV, which is a factor of P. +// When called, the TUV and K polynomials have been initialized to our best guess. +// This function can call UpdateTUV() at most QUAD_ITER_MAX times. This returns 2 if we find +// 2 roots, else 0. UpdateStatus has two possible values on return, UPDATED and BAD_ANGLE. +// UPDATED means the UpdateTUV function is able to do its calculations with TUV at this angle. +// BAD_ANGLE tells P51 to move TUV to a different angle before calling this function again. +// We use ErrScalar to account for the wide variations in N and P[N]. ErrScalar can vary by +// as much as 1E50 if all the root locations are much less than 1 or much greater than 1. +// The ErrScalar equation was determined empirically. If this root finder is used in an app +// where the root locations and poly orders fall within a narrow range, esp low order, then +// ErrScalar can be modified to give more accurate results. + +int QuadIterate(int P51_Iter, long double *P, long double *QP, long double *K, + long double *QK, int N, long double *TUV, TUpdateStatus *UpdateStatus) { + int Iter; + long double Err, MinErr, ErrScalar, QKCheck; + + ErrScalar = 1.0 / (16.0 * powl((long double) N, 3.0) * fabsl(P[N])); + + P51_Iter *= QUAD_ITER_MAX; + Err = MinErr = 1.0E100; + *UpdateStatus = UPDATED; + QuadSynDiv(P, N, TUV, QP); // Init QP + QuadSynDiv(K, N - 1, TUV, QK); // Init QK + + for (Iter = 0; Iter < QUAD_ITER_MAX; Iter++) { + UpdateTUV(P51_Iter + Iter, P, N, QP, K, QK, TUV, UpdateStatus); + if (*UpdateStatus == BAD_ANGLE) { + return (0); // Failure, need a different angle. + } + + Err = fabsl(QP[N - 1]) + fabsl(QP[N + 1]); // QP[N-1] & QP[N+1] are the remainder terms of P/TUV. + Err *= ErrScalar; // Normalize the error. + + if (Err < LDBL_EPSILON) { + return (2); // Success!! 2 roots have been found. + } + + // ZERO_DEL means both DelU and DelV were ~ 0 in UpdateTUV which means the algorithm has stalled. + // It might be stalled in a dead zone with large error, or stalled because it can't adjust u and v with a resolution fine enough to meet our convergence criteria. + if (*UpdateStatus == ZERO_DEL) { + if (Err < 4.0 * (long double) N * LDBL_EPSILON) // Small error, this is the best we can do. + { + *UpdateStatus = UPDATED; + return (2); + } else // Large error, get a different angle + { + *UpdateStatus = BAD_ANGLE; + return (0); + } + } + + QKCheck = fabsl(QK[N - 2]) + fabsl(QK[N]); // QK[N-2] & QK[N] are the remainder terms of K/TUV. + QKCheck *= ErrScalar; + + // Huge values indicate the algorithm is diverging and overflow is imminent. This can indicate + // a single real root, or that we simply need a different angle on TUV. This happens frequently. + if (Err > HUGE_VALUE || QKCheck > HUGE_VALUE) { + *UpdateStatus = BAD_ANGLE; + return (0); + } + + // Record our best result thus far. We turn on the damper in UpdateTUV if the errs increase. + if (Err < MinErr) { + *UpdateStatus = DAMPER_OFF; + MinErr = Err; + } else if (Iter > 2) { + *UpdateStatus = DAMPER_ON; + } + } + + // If we get here, we didn't converge, but TUV is getting updated. + // If RealIterate can't find a real zero, this function will get called again. + *UpdateStatus = UPDATED; + return (0); +} + +//--------------------------------------------------------------------------- + +// This function updates TUV[1] and TUV[2] using the J-T mathematics. +// When called, UpdateStatus equals either DAMPER_ON or DAMPER_OFF, which controls the damping factor. +// On return UpdateStatus equals UPDATED, BAD_ANGLE or ZERO_DEL; +// BAD_ANGLE indicates imminent overflow, or TUV[2] is going to zero. In either case the QuadIterate +// function will immediately return to P51 which will change the angle on TUV. ZERO_DEL indicates +// we have either stalled in a dead zone, or we lack the needed precision to further refine TUV. +// TUV = tx^2 + ux + v t = 1 always. v = 0 (a root at the origin) isn't allowed. +// The poly degrees are P[N] QP[N-2] K[N-1] QK[N-3] +void UpdateTUV(int Iter, long double *P, int N, long double *QP, long double *K, + long double *QK, long double *TUV, TUpdateStatus *UpdateStatus) { + int j; + long double DelU, DelV, Denom; + long double E2, E3, E4, E5; + static int FirstDamperlIter; + + if (Iter == 0) + FirstDamperlIter = 0; // Reset this static var. + if (*UpdateStatus == DAMPER_ON) + FirstDamperlIter = Iter; + + // Update K, unless E3 is tiny relative to E2. The algorithm will work its way out of a tiny E3. + // These equations are from the Jenkins and Traub paper "A Three Stage Algorithm for Real Polynomials Using Quadratic Iteration" Equation 9.8 + E2 = QP[N] * QP[N] + TUV[1] * QP[N] * QP[N - 1] + + TUV[2] * QP[N - 1] * QP[N - 1]; + E3 = QP[N] * QK[N - 1] + TUV[1] * QP[N] * QK[N - 2] + + TUV[2] * QP[N - 1] * QK[N - 2]; + + if (fabsl(E3) * HUGE_VALUE > fabsl(E2)) { + E2 /= -E3; + for (j = 1; j <= N - 2; j++) + K[j] = E2 * QK[j - 1] + QP[j]; // At covergence, E2 ~ 0, so K ~ QP. + } else { + for (j = 1; j <= N - 2; j++) + K[j] = QK[j - 1]; + } + K[0] = QP[0]; // QP[0] = 1.0 always + K[N - 1] = 0.0; + + QuadSynDiv(K, N - 1, TUV, QK); // Update QK QK = K/TUV + + // These equations are modified versions of those used in the original Jenkins Traub Fortran algorithm RealPoly, found at www.netlib.org/toms/493 + E3 = QP[N] * QK[N - 1] + TUV[1] * QP[N] * QK[N - 2] + + TUV[2] * QP[N - 1] * QK[N - 2]; + E4 = QK[N - 1] * QK[N - 1] + TUV[1] * QK[N - 1] * QK[N - 2] + + TUV[2] * QK[N - 2] * QK[N - 2]; + E5 = QP[N - 1] * QK[N - 1] - QP[N] * QK[N - 2]; + + Denom = E5 * K[N - 2] * TUV[2] + E4 * P[N]; + if (fabsl(Denom) * HUGE_VALUE < fabsl(P[N])) { + *UpdateStatus = BAD_ANGLE; // Denom is tiny, overflow is imminent, get a new angle. + return; + } + + // Calc DelU and DelV. If they are effectively zero, bump them by epsilon. + DelU = E3 * K[N - 2] * TUV[2] / Denom; + if (fabsl(DelU) < LDBL_EPSILON * fabsl(TUV[1])) { + if (DelU < 0.0) + DelU = -fabsl(TUV[1]) * LDBL_EPSILON; + else + DelU = fabsl(TUV[1]) * LDBL_EPSILON; + } + + DelV = -E5 * K[N - 2] * TUV[2] * TUV[2] / Denom; + if (fabsl(DelV) < LDBL_EPSILON * fabsl(TUV[2])) { + if (DelV < 0.0) + DelV = -fabsl(TUV[2]) * LDBL_EPSILON; + else + DelV = fabsl(TUV[2]) * LDBL_EPSILON; + } + + // If we haven't converged by QUAD_ITER_MAX iters, we need to test DelU and DelV for effectiveness. + if (Iter >= QUAD_ITER_MAX - 1) { + // We can't improve u and v any further because both DelU and DelV ~ 0 This usually happens when we are near a solution, but we don't have the precision needed to ine u and v enough to meet our convergence criteria. This can also happen in a dead zone where the errors are large, which means we need a different angle on TUV. We test for this in the QuadIterate function. + if (fabsl(DelU) < 8.0 * LDBL_EPSILON * fabsl(TUV[1]) + && fabsl(DelV) < 8.0 * LDBL_EPSILON * fabsl(TUV[2])) { + *UpdateStatus = ZERO_DEL; + return; + } + // A big change after this many iterations means we are wasting our time on this angle. + if (fabsl(DelU) > 10.0 * fabsl(TUV[1]) + || fabsl(DelV) > 10.0 * fabsl(TUV[2])) { + *UpdateStatus = BAD_ANGLE; + return; + } + } + + // Dampen the changes for 3 iterations after Damper was set in QuadIterate. + if (Iter - FirstDamperlIter < 3) { + DelU *= 0.75; + DelV *= 0.75; + } + + // Update U and V + TUV[1] += DelU; + if (fabsl(TUV[2] + DelV) < TINY_VALUE) + DelV *= 0.9; // If this, we would set TUV[2] = 0 which we can't allow, so we use 90% of DelV. + TUV[2] += DelV; + + if (fabsl(TUV[2]) < fabsl(TUV[1]) * TINY_VALUE) { + *UpdateStatus = BAD_ANGLE; // TUV[2] is effectively 0, which is never allowed. + return; + } + + *UpdateStatus = UPDATED; // TUV was updated. + QuadSynDiv(P, N, TUV, QP); // Update QP QP = P/TUV +} + +//--------------------------------------------------------------------------- + +// This function is used to find single real roots. It is similar to Newton's Method. +// Horners method is used to calculate QK and QP. For an explanation of these methods, see these 2 links. +// http://mathworld.wolfram.com/NewtonsMethod.html http://en.wikipedia.org/wiki/Horner%27s_method + +// When called, RealZero contains our best guess for a root, and K is init to the 1st derivative of P. +// The return value is either 1 or 0, the number of roots found. On return, RealZero contains +// the root, and QP contains the next P (i.e. P with this root removed). +// As with QuadIterate, at convergence, K = QP +int RealIterate(int P51_Iter, long double *P, long double *QP, long double *K, + long double *QK, int N, long double *RealZero) { + int Iter, k; + long double X, DelX, Damper, Err, ErrScalar; + static long double PrevQPN; + + ErrScalar = 1.0 / (16.0 * powl((long double) N, 2.0) * fabsl(P[N])); + + X = *RealZero; // Init with our best guess for X. + if (P51_Iter == 0) + PrevQPN = 0.0; + QK[0] = K[0]; + for (k = 1; k <= N - 1; k++) { + QK[k] = QK[k - 1] * X + K[k]; + } + + for (Iter = 0; Iter < REAL_ITER_MAX; Iter++) { + // Calculate a new QP. This is poly division QP = P/(x+X) QP[0] to QP[N-1] is the quotient. + // The remainder is QP[N], which is P(X), the error term. + QP[0] = P[0]; + for (k = 1; k <= N; k++) { + QP[k] = QP[k - 1] * X + P[k]; + } + Err = fabsl(QP[N]) * ErrScalar; // QP[N] is the error. ErrScalar accounts for the wide variations in N and P[N]. + + if (Err < LDBL_EPSILON) { + *RealZero = X; + return (1); // Success!! + } else if (Err > HUGE_VALUE) + return (0); // Overflow is imminent. + + // Calculate a new K. QK[N-1] is the remainder of K /(x-X). + // QK[N-1] is approximately P'(X) when P(X) = QP[N] ~ 0 + if (fabsl(QK[N - 1]) > fabsl(P[N] * TINY_VALUE)) { + DelX = -QP[N] / QK[N - 1]; + K[0] = QP[0]; + for (k = 1; k <= N - 1; k++) { + K[k] = DelX * QK[k - 1] + QP[k]; + } + } else // Use this if QK[N-1] ~ 0 + { + K[0] = 0.0; + for (k = 1; k <= N - 1; k++) + K[k] = QK[k - 1]; + } + + if (fabsl(K[N - 1]) > HUGE_VALUE) + return (0); // Overflow is imminent. + + // Calculate a new QK. This is poly division QK = K /(x+X). QK[0] to QK[N-2] is the quotient. + QK[0] = K[0]; + for (k = 1; k <= N - 1; k++) { + QK[k] = QK[k - 1] * X + K[k]; + } + if (fabsl(QK[N - 1]) <= TINY_VALUE * fabsl(P[N])) + return (0); // QK[N-1] ~ 0 will cause overflow below. + + // This algorithm routinely oscillates back and forth about a zero with nearly equal pos and neg error magnitudes. + // If the current and previous error add to give a value less than the current error, we dampen the change. + Damper = 1.0; + if (fabsl(QP[N] + PrevQPN) < fabsl(QP[N])) + Damper = 0.5; + PrevQPN = QP[N]; + + // QP[N] is P(X) and at convergence QK[N-1] ~ P'(X), so this is ~ Newtons Method. + DelX = QP[N] / QK[N - 1] * Damper; + if (X != 0.0) { + if (fabsl(DelX) < LDBL_EPSILON * fabsl(X)) // If true, the algorithm is stalled, so bump X by 2 epsilons. + { + if (DelX < 0.0) + DelX = -2.0 * X * LDBL_EPSILON; + else + DelX = 2.0 * X * LDBL_EPSILON; + } + } else // X = 0 + { + if (DelX == 0.0) + return (0); // Stalled at the origin, so exit. + } + X -= DelX; // Update X + + } // end of loop + + // If we get here, we failed to converge. + return (0); +} + +//--------------------------------------------------------------------------- + +// Derivative of P. Called from SetTUVandK(). +void DerivOfP(long double *P, int N, long double *dP) { + int j; + long double Power; + for (j = 0; j < N; j++) { + Power = N - j; + dP[j] = Power * P[j]; + } + dP[N] = 0.0; + +} +//--------------------------------------------------------------------------- + +// Synthetic Division of P by x^2 + ux + v (TUV) +// The qotient is Q[0] to Q[N-2]. The actual poly remainder terms are Q[N-1] and Q[N+1] +// The JT math requires the values Q[N-1] and Q[N] to calculate K. +// These form a 1st order remainder b*x + (b*u + a). +void QuadSynDiv(long double *P, int N, long double *TUV, long double *Q) { + int j; + Q[0] = P[0]; + Q[1] = P[1] - TUV[1] * Q[0]; + for (j = 2; j <= N; j++) { + Q[j] = P[j] - TUV[1] * Q[j - 1] - TUV[2] * Q[j - 2]; + } + +// Here we calculate the final remainder term used to calculate the convergence error. +// This and Q[N-1] are the remainder values you get if you do this poly division manually. + Q[N + 1] = Q[N - 1] * TUV[1] + Q[N]; // = b*u + a +} + +//--------------------------------------------------------------------------- + +// This function intializes the TUV and K polynomials. +void SetTUVandK(long double *P, int N, long double *TUV, long double *RealK, + long double *QuadK, long double X, int AngleNumber, int TypeOfQuadK) { + int j; + long double a, Theta; + + // These angles define our search pattern in the complex plane. We start in the 1st quadrant, + // go to the 2nd, then the real axis, etc. The roots are conjugates, so there isn't a need + // to go to the 3rd or 4th quadrants. The first 2 angles find about 99% of all roots. + const long double Angle[] = { 45.0, 135.0, 0.0, 90.0, 15.0, 30.0, 60.0, + 75.0, 105.0, 120.0, 150.0, + 165.0, // 12 angles + 6.0, 51.0, 96.0, 141.0, 12.0, 57.0, 102.0, 147.0, 21.0, 66.0, 111.0, + 156.0, 27.0, 72.0, 117.0, 162.0, 36.0, 81.0, 126.0, 171.0, 42.0, + 87.0, 132.0, 177.0, 3.0, 48.0, 93.0, 138.0, 9.0, 54.0, 99.0, 144.0, + 18.0, 63.0, 108.0, 153.0, 24.0, 69.0, 114.0, 159.0, 33.0, 78.0, + 123.0, 168.0, 39.0, 84.0, 129.0, + 174.0, // 60 angles + 46.0, 136.0, 91.0, 1.0, 16.0, 31.0, 61.0, 76.0, 106.0, 121.0, 151.0, + 166.0, 7.0, 52.0, 97.0, 142.0, 13.0, 58.0, 103.0, 148.0, 22.0, 67.0, + 112.0, 157.0, 28.0, 73.0, 118.0, 163.0, 37.0, 82.0, 127.0, 172.0, + 43.0, 88.0, 133.0, 178.0, 4.0, 49.0, 94.0, 139.0, 10.0, 55.0, 100.0, + 145.0, 19.0, 64.0, 109.0, 154.0, 25.0, 70.0, 115.0, 160.0, 34.0, + 79.0, 124.0, 169.0, 40.0, 85.0, 130.0, 175.0, 47.0, 137.0, 92.0, + 2.0, 17.0, 32.0, 62.0, 77.0, 107.0, 122.0, 152.0, 167.0, 8.0, 53.0, + 98.0, 143.0, 14.0, 59.0, 104.0, 149.0, 23.0, 68.0, 113.0, 158.0, + 29.0, 74.0, 119.0, 164.0, 38.0, 83.0, 128.0, 173.0, 44.0, 89.0, + 134.0, 179.0, 5.0, 50.0, 95.0, 140.0, 11.0, 56.0, 101.0, 146.0, + 20.0, 65.0, 110.0, 155.0, 26.0, 71.0, 116.0, 161.0, 35.0, 80.0, + 125.0, 170.0, 41.0, 86.0, 131.0, 176.0 }; // 180 angles + + // Initialize TUV to form (x - (a + jb)) * (x - (a - jb)) = x^2 - 2ax + a^2 + b^2 + // We init TUV for complex roots, except at angle 0, where we use real roots at +/- X + if (AngleNumber == 2) // At 0 degrees we int to 2 real roots at +/- X. + { + TUV[0] = 1.0; // t + TUV[1] = 0.0; // u + TUV[2] = -(X * X); // v + } else // We init to a complex root at a +/- jb + { + Theta = Angle[AngleNumber] / 180.0 * M_PI; + a = X * cosl(Theta); + //b = X * sinl(Theta); + TUV[0] = 1.0; + TUV[1] = -2.0 * a; + TUV[2] = X * X; // = a*a + b*b because cos^2 + sin^2 = 1 + } + + // The code below initializes the K polys used by RealIterate and QuadIterate. + + // Initialize the K poly used in RealIterate to P'. + DerivOfP(P, N, RealK); + RealK[N] = 0.0; + + // Initialize the K poly used in QuadIterate. Initializing QuadK to P" works virtually + // 100% of the time, but if P51 stalls on a difficult root, these alternate inits give + // us a way to work out of the stall. All these inits work almost as well as P". + if (TypeOfQuadK == 0) // Init QuadK 2nd derivative of P + { + long double *Temp = new (std::nothrow) long double[N + 2]; + if (Temp == NULL) { + //ShowMessage("Memory not Allocated in PFiftyOne SetTUVandK."); + return; + } + + DerivOfP(P, N, Temp); + DerivOfP(Temp, N - 1, QuadK); + QuadK[N] = QuadK[N - 1] = 0.0; + delete[] Temp; + } + + else if (TypeOfQuadK == 1) // Set QuadK to QP, because K = QP at convergence. + { + QuadSynDiv(P, N, TUV, QuadK); + QuadK[N] = QuadK[N - 1] = 0.0; + } + + else if (TypeOfQuadK == 2) // Set QuadK to the 1st derivative of P + { + for (j = 0; j <= N - 2; j++) + QuadK[j] = RealK[j + 1]; + QuadK[N] = QuadK[N - 1] = 0.0; + } + + else // Set QuadK to zero, except QuadK[0]. + { + for (j = 1; j <= N; j++) + QuadK[j] = 0.0; + QuadK[0] = 1.0; + } + + if (QuadK[0] == 0.0) + QuadK[0] = 1.0; // This can happen if TypeOfQuadK == 2 and P[1] == 0.0 + for (j = N - 2; j > 0; j--) + QuadK[j] /= QuadK[0]; + QuadK[0] = 1.0; +} + +//--------------------------------------------------------------------------- + +} // namespace diff --git a/kitiirfir/PFiftyOneRevE.h b/kitiirfir/PFiftyOneRevE.h new file mode 100644 index 000000000..7b34144ed --- /dev/null +++ b/kitiirfir/PFiftyOneRevE.h @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------------- + +#ifndef PFiftyOneRevEH +#define PFiftyOneRevEH +//--------------------------------------------------------------------------- + +#include + +// TUpdateStatus represent the status of the UpdateTUV function. +// The first 3 are returned from UpdateTUV. The last two are sent to UpdateTUV. +enum TUpdateStatus {UPDATED, BAD_ANGLE, ZERO_DEL, DAMPER_ON, DAMPER_OFF}; + + +// This value for LDBL_EPSILON is for an 80 bit variable (64 bit significand). It should be in float.h +#define LDBL_EPSILON 1.084202172485504434E-19L // = 2^-63 +#define P51_MAXDEGREE 100 // The max poly order allowed. Used at the top of P51. This was set arbitrarily. +#define P51_ARRAY_SIZE 102 // P51 uses the new operator. P51 arrays must be MaxDegree + 2 +#define MAX_NUM_ANGLES 180 // The number of defined angles for initializing TUV. +#define REAL_ITER_MAX 20 // Max number of iterations in RealIterate. +#define QUAD_ITER_MAX 20 // Max number of iterations in QuadIterate. +#define MAX_NUM_K 4 // This is the number of methods we have to define K. +#define MAX_NEWT_ITERS 10 // Used to limit the Newton Iterations in the GetX function. +#define TINY_VALUE 1.0E-30 // This is what we use to test for zero. Usually to avoid divide by zero. +#define HUGE_VALUE 1.0E200 // This gets used to test for imminent overflow. + +namespace kitiirfir +{ + +int FindRoots(int N, double *Coeff, std::complex *Roots); +int PFiftyOne(long double *Coeff, int Degree, long double *RealRoot, long double *ImagRoot); +int QuadIterate(int Iter, long double *P, long double *QP, long double *K, long double *QK, int N, long double *TUV, TUpdateStatus *UpdateStatus); +void UpdateTUV(int Iter, long double *P, int N, long double *QP, long double *K, long double *QK, long double *TUV, TUpdateStatus *UpdateStatus); +int RealIterate(int P51_Iter, long double *P, long double *QP, long double *K, long double *QK, int N, long double *RealZero); +void QuadraticFormula(long double *TUV, long double *RealRoot, long double *ImagRoot); +void QuadSynDiv(long double *P, int N, long double *TUV, long double *Q); +void DerivOfP(long double *P, int N, long double *dP); +void SetTUVandK(long double *P, int N, long double *TUV, long double *RealK, long double *QuadK, long double X, int AngleNumber, int TypeOfQuadK); + +} // namespace + +#endif + + diff --git a/kitiirfir/QuadRootsRevH.cpp b/kitiirfir/QuadRootsRevH.cpp new file mode 100644 index 000000000..d51393341 --- /dev/null +++ b/kitiirfir/QuadRootsRevH.cpp @@ -0,0 +1,344 @@ +/* + By Daniel Klostermann + Iowa Hills Software, LLC IowaHills.com + If you find a problem, please leave a note at: + http://www.iowahills.com/feedbackcomments.html + Sept 12, 2016 Rev H + + This root solver code finds 1st, 2nd, 3rd, and 4th order roots algebraically, as + opposed to iteration. + + It is composed of the functions: QuadCubicRoots, QuadRoots, CubicRoots, and BiQuadRoots. + This code originated at: http://www.netlib.org/toms/ Algorithm 326 + Roots of Low Order Polynomials by Terence R.F.Nonweiler CACM (Apr 1968) p269 + Original C translation by: M.Dow Australian National University, Canberra, Australia + + We use the same basic mathematics used in that code, but in order to improve numerical + accuracy we made extensive modifications to the test conditions that control the flow of + the algorithm, and also added scaling and reversals. + + The input array Coeff[] contains the coefficients arranged in descending order. + For example, a 4th order poly would be: + P(x) = Coeff[0]*x^4 + Coeff[1]*x^3 + Coeff[2]*x^2 + Coeff[3]*x + Coeff[4] + + The roots are returned in RootsReal and RootsImag. N is the polynomial's order. 1 <= N <= 4 + N is modified if there are leading or trailing zeros. N is returned. + Coeff needs to be length N+1. RealRoot and ImagRoot need to be length N. + + Do not call QuadRoots, CubicRoots, and BiQuadRoots directly. They assume that QuadCubicRoots + has removed leading and trailing zeros and normalized P. + + On a 2 GHz Pentium it takes about 2 micro sec for QuadCubicRoots to return. + + ShowMessage is a C++ Builder function, and it usage has been commented out. + If you are using C++ Builder, include vcl.h for ShowMessage. + Otherwise replace ShowMessage with something appropriate for your compiler. + */ + +#include "QuadRootsRevH.h" +#include + +namespace kitiirfir +{ + +//--------------------------------------------------------------------------- + +// Same interface as P51. +int QuadCubicRoots(long double *Coeff, int N, long double *RealRoot, + long double *ImagRoot) { + if (N < 1 || N > 4) { + //ShowMessage("Invalid Poly Order in QuadCubicRoots()"); + return (0); + } + + int j; + long double P[5]; + + // Must init to zero, in case N is reduced. + for (j = 0; j < N; j++) + RealRoot[j] = ImagRoot[j] = 0.0; + for (j = 0; j < 5; j++) + P[j] = 0.0; + + // The functions below modify the coeff array, so we pass P instead of Coeff. + for (j = 0; j <= N; j++) + P[j] = (long double) Coeff[j]; + + // Remove trailing zeros. A tiny P[N] relative to P[N-1] is not allowed. + while (N > 0 && fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1])) { + N--; + } + + // Remove leading zeros. + while (N > 0 && P[0] == 0.0) { + for (j = 0; j < N; j++) + P[j] = P[j + 1]; + N--; + } + + // P[0] must = 1 + if (P[0] != 1.0) { + for (j = 1; j <= N; j++) + P[j] /= P[0]; + P[0] = 1.0; + } + + // Calculate the roots. + if (N == 4) + BiQuadRoots(P, RealRoot, ImagRoot); + else if (N == 3) + CubicRoots(P, RealRoot, ImagRoot); + else if (N == 2) + QuadRoots(P, RealRoot, ImagRoot); + else if (N == 1) { + RealRoot[0] = -P[1] / P[0]; + ImagRoot[0] = 0.0; + } + + return (N); +} + +//--------------------------------------------------------------------------- + +// This function is the quadratic formula with P[0] = 1 +// y = P[0]*x^2 + P[1]*x + P[2] +// Normally, we don't allow P[2] = 0, but this can happen in a call from BiQuadRoots. +// If P[2] = 0, the zero is returned in RealRoot[0]. +void QuadRoots(long double *P, long double *RealRoot, long double *ImagRoot) { + long double D; + D = P[1] * P[1] - 4.0 * P[2]; + if (D >= 0.0) // 1 or 2 real roots + { + RealRoot[0] = (-P[1] - sqrtl(D)) * 0.5; // = -P[1] if P[2] = 0 + RealRoot[1] = (-P[1] + sqrtl(D)) * 0.5; // = 0 if P[2] = 0 + ImagRoot[0] = ImagRoot[1] = 0.0; + } else // 2 complex roots + { + RealRoot[0] = RealRoot[1] = -P[1] * 0.5; + ImagRoot[0] = sqrtl(-D) * 0.5; + ImagRoot[1] = -ImagRoot[0]; + } +} + +//--------------------------------------------------------------------------- +// This finds the roots of y = P0x^3 + P1x^2 + P2x+ P3 P[0] = 1 +void CubicRoots(long double *P, long double *RealRoot, long double *ImagRoot) { + int j; + long double s, t, b, c, d, Scalar; + bool CubicPolyReversed = false; + + // Scale the polynomial so that P[N] = +/-1. This moves the roots toward unit circle. + Scalar = powl(fabsl(P[3]), 1.0 / 3.0); + for (j = 1; j <= 3; j++) + P[j] /= powl(Scalar, (long double) j); + + if (fabsl(P[3]) < fabsl(P[2]) && P[2] > 0.0) { + ReversePoly(P, 3); + CubicPolyReversed = true; + } + + s = P[1] / 3.0; + b = (6.0 * P[1] * P[1] * P[1] - 27.0 * P[1] * P[2] + 81.0 * P[3]) / 162.0; + t = (P[1] * P[1] - 3.0 * P[2]) / 9.0; + c = t * t * t; + d = 2.0 * P[1] * P[1] * P[1] - 9.0 * P[1] * P[2] + 27.0 * P[3]; + d = d * d / 2916.0 - c; + + // if(d > 0) 1 complex and 1 real root. We use LDBL_EPSILON to account for round off err. + if (d > LDBL_EPSILON) { + d = powl((sqrtl(d) + fabsl(b)), 1.0 / 3.0); + if (d != 0.0) { + if (b > 0) + b = -d; + else + b = d; + c = t / b; + } + d = M_SQRT3_2 * (b - c); + b = b + c; + c = -b / 2.0 - s; + + RealRoot[0] = (b - s); + ImagRoot[0] = 0.0; + RealRoot[1] = RealRoot[2] = c; + ImagRoot[1] = d; + ImagRoot[2] = -ImagRoot[1]; + } + + else // d < 0.0 3 real roots + { + if (b == 0.0) + d = M_PI_2 / 3.0; // b can be as small as 1.0E-25 + else + d = atanl(sqrtl(fabsl(d)) / fabsl(b)) / 3.0; + + if (b < 0.0) + b = 2.0 * sqrtl(fabsl(t)); + else + b = -2.0 * sqrtl(fabsl(t)); + + c = cosl(d) * b; + t = -M_SQRT3_2 * sinl(d) * b - 0.5 * c; + + RealRoot[0] = (t - s); + RealRoot[1] = -(t + c + s); + RealRoot[2] = (c - s); + ImagRoot[0] = 0.0; + ImagRoot[1] = 0.0; + ImagRoot[2] = 0.0; + } + + // If we reversed the poly, the roots need to be inverted. + if (CubicPolyReversed) + InvertRoots(3, RealRoot, ImagRoot); + + // Apply the Scalar to the roots. + for (j = 0; j < 3; j++) + RealRoot[j] *= Scalar; + for (j = 0; j < 3; j++) + ImagRoot[j] *= Scalar; +} + +//--------------------------------------------------------------------------- + +// This finds the roots of y = P0x^4 + P1x^3 + P2x^2 + P3x + P4 P[0] = 1 +// This function calls CubicRoots and QuadRoots +void BiQuadRoots(long double *P, long double *RealRoot, long double *ImagRoot) { + int j; + long double a, b, c, d, e, Q3Limit, Scalar, Q[4], MinRoot; + bool QuadPolyReversed = false; + + // Scale the polynomial so that P[N] = +/- 1. This moves the roots toward unit circle. + Scalar = powl(fabsl(P[4]), 0.25); + for (j = 1; j <= 4; j++) + P[j] /= powl(Scalar, (long double) j); + + // Having P[1] < P[3] helps with the Q[3] calculation and test. + if (fabsl(P[1]) > fabsl(P[3])) { + ReversePoly(P, 4); + QuadPolyReversed = true; + } + + a = P[2] - P[1] * P[1] * 0.375; + b = P[3] + P[1] * P[1] * P[1] * 0.125 - P[1] * P[2] * 0.5; + c = P[4] + 0.0625 * P[1] * P[1] * P[2] + - 0.01171875 * P[1] * P[1] * P[1] * P[1] - 0.25 * P[1] * P[3]; + e = P[1] * 0.25; + + Q[0] = 1.0; + Q[1] = P[2] * 0.5 - P[1] * P[1] * 0.1875; + Q[2] = (P[2] * P[2] - P[1] * P[1] * P[2] + + 0.1875 * P[1] * P[1] * P[1] * P[1] - 4.0 * P[4] + P[1] * P[3]) + * 0.0625; + Q[3] = -b * b * 0.015625; + + /* The value of Q[3] can cause problems when it should have calculated to zero (just above) but + is instead ~ -1.0E-17 because of round off errors. Consequently, we need to determine whether + a tiny Q[3] is due to roundoff, or if it is legitimately small. It can legitimately have values + of ~ -1E-28. When this happens, we assume Q[2] should also be small. Q[3] can also be tiny with + 2 sets of equal real roots. Then P[1] and P[3], are approx equal. */ + + Q3Limit = ZERO_MINUS; + if (fabsl(fabsl(P[1]) - fabsl(P[3])) >= ZERO_PLUS && Q[3] > ZERO_MINUS + && fabsl(Q[2]) < 1.0E-5) + Q3Limit = 0.0; + + if (Q[3] < Q3Limit && fabsl(Q[2]) < 1.0E20 * fabsl(Q[3])) { + CubicRoots(Q, RealRoot, ImagRoot); + + // Find the smallest positive real root. One of the real roots is always positive. + MinRoot = 1.0E100; + for (j = 0; j < 3; j++) { + if (ImagRoot[j] == 0.0 && RealRoot[j] > 0 && RealRoot[j] < MinRoot) + MinRoot = RealRoot[j]; + } + + d = 4.0 * MinRoot; + a += d; + if (a * b < 0.0) + Q[1] = -sqrtl(d); + else + Q[1] = sqrtl(d); + b = 0.5 * (a + b / Q[1]); + } else { + if (Q[2] < 0.0) // 2 sets of equal imag roots + { + b = sqrtl(fabsl(c)); + d = b + b - a; + if (d > 0.0) + Q[1] = sqrtl(fabsl(d)); + else + Q[1] = 0.0; + } else { + if (Q[1] > 0.0) + b = 2.0 * sqrtl(fabsl(Q[2])) + Q[1]; + else + b = -2.0 * sqrtl(fabsl(Q[2])) + Q[1]; + Q[1] = 0.0; + } + } + + // Calc the roots from two 2nd order polys and subtract e from the real part. + if (fabsl(b) > 1.0E-8) { + Q[2] = c / b; + QuadRoots(Q, RealRoot, ImagRoot); + + Q[1] = -Q[1]; + Q[2] = b; + QuadRoots(Q, RealRoot + 2, ImagRoot + 2); + + for (j = 0; j < 4; j++) + RealRoot[j] -= e; + } else // b==0 with 4 equal real roots + { + for (j = 0; j < 4; j++) + RealRoot[j] = -e; + for (j = 0; j < 4; j++) + ImagRoot[j] = 0.0; + } + + // If we reversed the poly, the roots need to be inverted. + if (QuadPolyReversed) + InvertRoots(4, RealRoot, ImagRoot); + + // Apply the Scalar to the roots. + for (j = 0; j < 4; j++) + RealRoot[j] *= Scalar; + for (j = 0; j < 4; j++) + ImagRoot[j] *= Scalar; +} + +//--------------------------------------------------------------------------- + +// A reversed polynomial has its roots at the same angle, but reflected about the unit circle. +void ReversePoly(long double *P, int N) { + int j; + long double Temp; + for (j = 0; j <= N / 2; j++) { + Temp = P[j]; + P[j] = P[N - j]; + P[N - j] = Temp; + } + if (P[0] != 0.0) { + for (j = N; j >= 0; j--) + P[j] /= P[0]; + } +} + +//--------------------------------------------------------------------------- +// This is used in conjunction with ReversePoly +void InvertRoots(int N, long double *RealRoot, long double *ImagRoot) { + int j; + long double Mag; + for (j = 0; j < N; j++) { + // complex math for 1/x + Mag = RealRoot[j] * RealRoot[j] + ImagRoot[j] * ImagRoot[j]; + if (Mag != 0.0) { + RealRoot[j] /= Mag; + ImagRoot[j] /= -Mag; + } + } +} +//--------------------------------------------------------------------------- + +} // namespace diff --git a/kitiirfir/QuadRootsRevH.h b/kitiirfir/QuadRootsRevH.h new file mode 100644 index 000000000..f7e0e7544 --- /dev/null +++ b/kitiirfir/QuadRootsRevH.h @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------------- + +#ifndef QuadRootsRevHH +#define QuadRootsRevHH +//--------------------------------------------------------------------------- + +#define LDBL_EPSILON 1.084202172485504434E-19L +// #define M_SQRT3 1.7320508075688772935274463L // sqrt(3) +#define M_SQRT3_2 0.8660254037844386467637231L // sqrt(3)/2 +// #define DBL_EPSILON 2.2204460492503131E-16 // 2^-52 typically defined in the compiler's float.h +#define ZERO_PLUS 8.88178419700125232E-16 // 2^-50 = 4*DBL_EPSILON +#define ZERO_MINUS -8.88178419700125232E-16 +#define TINY_VALUE 1.0E-30 // This is what we use to test for zero. Usually to avoid divide by zero. + +namespace kitiirfir +{ + +void QuadRoots(long double *P, long double *RealPart, long double *ImagPart); +void CubicRoots(long double *P, long double *RealPart, long double *ImagPart); +void BiQuadRoots(long double *P, long double *RealPart, long double *ImagPart); +void ReversePoly(long double *P, int N); +void InvertRoots(int N, long double *RealRoot, long double *ImagRoot); + +} // namespace + +#endif + diff --git a/kitiirfir/readme.md b/kitiirfir/readme.md new file mode 100644 index 000000000..86fff210b --- /dev/null +++ b/kitiirfir/readme.md @@ -0,0 +1,8 @@ +

IIR and FIR filter kit

+ +This is the code for IIR and FIR filter design that can be found [here](http://www.iowahills.com/) +Thnks to Iowa Hills Software for providing this code for free. + +This is only the filter design part that can be downloaded [here](http://www.iowahills.com/8DownloadPage.html) Under "IR FIR Source Code Kit". It also includes some dependencies found on the other files in this page. + +Code has been re-implemented in more modern C++ and moving it into a kitiirfir namespace \ No newline at end of file diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index 22693e257..16d32327a 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -128,22 +128,6 @@ void PhaseLockComplex::reset() m_lockCount = 0; } -void PhaseLockComplex::feedFLL(float re, float im) -{ - m_yRe = cos(m_phiHat); - m_yIm = sin(m_phiHat); - std::complex y(m_yRe, m_yIm); - std::complex x(re, im); - std::complex p = x * m_y; - float cross = m_p.real()*p.imag() - p.real()*m_p.imag(); - float dot = m_p.real()*p.real() + m_p.imag()*p.imag(); - float eF = cross * (dot < 0 ? -1 : 1); // frequency error - - m_freq += eF; // correct instantaneous frequency - m_phiHat += eF; // advance phase with instantaneous frequency - m_p = p; // store previous product -} - void PhaseLockComplex::feed(float re, float im) { m_yRe = cos(m_phiHat); @@ -210,7 +194,7 @@ void PhaseLockComplex::feed(float re, float im) else{ if (m_lockCount > 0) { m_lockCount--; - } + } } m_freqPrev = m_freq; diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index 4e2e64962..56f455328 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -47,8 +47,6 @@ public: void reset(); /** Feed PLL with a new signa sample */ void feed(float re, float im); - /** Same but turns into a FLL using the same filtering structure and NCO output. No lock condition. */ - void feedFLL(float re, float im); const std::complex& getComplex() const { return m_y; } float getReal() const { return m_yRe; } float getImag() const { return m_yIm; } From d29958d51f2d99538973d39f6ec789d044ec366d Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 17 May 2018 01:05:48 +0200 Subject: [PATCH 419/956] Added a frequency lock loop --- sdrbase/CMakeLists.txt | 4 ++ sdrbase/dsp/freqlockcomplex.cpp | 122 ++++++++++++++++++++++++++++++++ sdrbase/dsp/freqlockcomplex.h | 62 ++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 sdrbase/dsp/freqlockcomplex.cpp create mode 100644 sdrbase/dsp/freqlockcomplex.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 0459c898c..ba63ce3e4 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -34,6 +34,7 @@ set(sdrbase_SOURCES dsp/filterrc.cpp dsp/filtermbe.cpp dsp/filerecord.cpp + dsp/freqlockcomplex.cpp dsp/interpolator.cpp dsp/hbfiltertraits.cpp dsp/lowpass.cpp @@ -124,6 +125,7 @@ set(sdrbase_HEADERS dsp/filterrc.h dsp/filtermbe.h dsp/filerecord.h + dsp/freqlockcomplex.h dsp/gfft.h dsp/iirfilter.h dsp/interpolator.h @@ -271,6 +273,7 @@ include_directories( . ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/httpserver + ${CMAKE_SOURCE_DIR}/kitiirfir ${CMAKE_SOURCE_DIR}/qrtplib ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) @@ -278,6 +281,7 @@ include_directories( target_link_libraries(sdrbase ${QT_LIBRARIES} httpserver + kitiirfir qrtplib swagger ) diff --git a/sdrbase/dsp/freqlockcomplex.cpp b/sdrbase/dsp/freqlockcomplex.cpp new file mode 100644 index 000000000..d0d4a5772 --- /dev/null +++ b/sdrbase/dsp/freqlockcomplex.cpp @@ -0,0 +1,122 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// 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 "freqlockcomplex.h" +#include "IIRFilterCode.h" + +FreqLockComplex::FreqLockComplex() : + m_a0(1.0), + m_a1(1.0), + m_a2(1.0), + m_b0(1.0), + m_b1(1.0), + m_b2(1.0), + m_v0(0.0), + m_v1(0.0), + m_v2(0.0), + m_y(1.0, 0.0), + m_prod(1.0, 0.0), + m_yRe(1.0), + m_yIm(0.0), + m_freq(0.0), + m_phi(0.0), + m_iir(0) +{ +} + +FreqLockComplex::~FreqLockComplex() +{ + if (m_iir) { + delete m_iir; + } +} + +void FreqLockComplex::reset() +{ + m_v0 = 0.0f; + m_v1 = 0.0f; + m_v2 = 0.0f; + m_y.real(1.0); + m_y.imag(0.0); + m_prod.real(1.0); + m_prod.imag(0.0); + m_yRe = 1.0f; + m_yIm = 0.0f; + m_freq = 0.0f; + m_phi = 0.0f; +} + +// wn is in terms of Nyquist. For example, if the sampling frequency = 20 kHz +// and the 3 dB corner frequency is 1.5 kHz, then OmegaC = 0.15 +// i.e. 100.0 / (SR/2) or 200 / SR for 100 Hz +void FreqLockComplex::computeCoefficients(float wn) +{ + kitiirfir::TIIRFilterParams params; + + params.BW = 0.0; // For band pass and notch filters - unused here + params.Gamma = 0.0; // For Adjustable Gauss. - unused here + params.IIRPassType = kitiirfir::iirLPF; + params.NumPoles = 1; + params.OmegaC = wn; + params.ProtoType = kitiirfir::BUTTERWORTH; + params.Ripple = 0.0; // For Elliptic and Chebyshev - unused here + params.StopBanddB = 0.0; // For Elliptic and Inverse Chebyshev - unused here + params.dBGain = 0.0; + + kitiirfir::TIIRCoeff coeff = kitiirfir::CalcIIRFilterCoeff(params); + float a[3], b[3]; + + a[0] = coeff.a0[0]; + a[1] = coeff.a1[0]; + a[2] = coeff.a2[0]; + b[0] = coeff.b0[0]; + b[1] = coeff.b1[0]; + b[2] = coeff.b2[0]; + + qDebug("FreqLockComplex::computeCoefficients: b: %f %f %f", b[0], b[1], b[2]); + qDebug("FreqLockComplex::computeCoefficients: a: %f %f %f", a[0], a[1], a[2]); + + m_iir = new IIRFilter(a, b); +} + +void FreqLockComplex::feed(float re, float im) +{ + m_yRe = cos(m_phi); + m_yIm = sin(m_phi); + std::complex y(m_yRe, m_yIm); + std::complex x(re, im); + + std::complex prod = x * m_y; + + // Discriminator: cross * sign(dot) / dt + float cross = m_prod.real()*prod.imag() - prod.real()*m_prod.imag(); + float dot = m_prod.real()*prod.real() + m_prod.imag()*prod.imag(); + float eF = cross * (dot < 0 ? -1 : 1); // frequency error + + // LPF section + float efHat = m_iir->run(eF); + + m_freq = efHat; // correct instantaneous frequency + m_phi += efHat; // advance phase with instantaneous frequency + m_prod = prod; // store previous product +} + diff --git a/sdrbase/dsp/freqlockcomplex.h b/sdrbase/dsp/freqlockcomplex.h new file mode 100644 index 000000000..e860821d0 --- /dev/null +++ b/sdrbase/dsp/freqlockcomplex.h @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// 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 SDRBASE_DSP_FREQLOCKCOMPLEX_H_ +#define SDRBASE_DSP_FREQLOCKCOMPLEX_H_ + +#include "dsp/dsptypes.h" +#include "iirfilter.h" +#include "export.h" + +/** General purpose Phase-locked loop using complex analytic signal input. */ +class SDRBASE_API FreqLockComplex +{ +public: + FreqLockComplex(); + ~FreqLockComplex(); + + void reset(); + void computeCoefficients(float wn); + /** Feed PLL with a new signa sample */ + void feed(float re, float im); + +private: + float m_a0; + float m_a1; + float m_a2; + float m_b0; + float m_b1; + float m_b2; + float m_v0; + float m_v1; + float m_v2; + std::complex m_y; + std::complex m_prod; + float m_yRe; + float m_yIm; + float m_freq; + float m_phi; + IIRFilter *m_iir; +}; + + +#endif /* SDRBASE_DSP_FREQLOCKCOMPLEX_H_ */ From e723764376cfff89ba899bae9e27cafc0b3c9784 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 17 May 2018 02:35:06 +0200 Subject: [PATCH 420/956] New PLL: removed locked status heuristics for order > 1 --- sdrbase/dsp/phaselockcomplex.cpp | 89 ++------------------------------ sdrbase/dsp/phaselockcomplex.h | 14 +---- 2 files changed, 5 insertions(+), 98 deletions(-) diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index 16d32327a..1eb0b1edc 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -36,24 +36,15 @@ PhaseLockComplex::PhaseLockComplex() : m_deltaPhi(0.0), m_phiHat(0.0), m_phiHatPrev(0.0), - m_phiHat1(0.0), - m_phiHat2(0.0), - m_dPhiHatAccum(0.0), - m_phiHatCount(0), m_y(1.0, 0.0), m_p(1.0, 0.0), m_yRe(1.0), m_yIm(0.0), m_freq(0.0), m_freqPrev(0.0), - m_lock(0.0), m_lockCount(0), m_pskOrder(1), - m_lockTime1(480), - m_lockTime(2400), - m_lockTimef(2400.0f), - m_lockThreshold(4.8f), - m_avgF(2400) + m_lockTime(480) { } @@ -95,11 +86,7 @@ void PhaseLockComplex::setPskOrder(unsigned int order) void PhaseLockComplex::setSampleRate(unsigned int sampleRate) { - m_lockTime1 = sampleRate / 100; // 10ms for order 1 - m_lockTime = sampleRate / 20; // 50ms for order > 1 - m_lockTimef = (float) m_lockTime; - m_lockThreshold = m_lockTime * 0.00015f; // threshold of 0.002 taking division by lock time into account - m_avgF.resize(m_lockTime); + m_lockTime = sampleRate / 100; // 10ms for order 1 reset(); } @@ -112,10 +99,6 @@ void PhaseLockComplex::reset() m_deltaPhi = 0.0f; m_phiHat = 0.0f; m_phiHatPrev = 0.0f; - m_phiHat1 = 0.0f; - m_phiHat2 = 0.0f; - m_dPhiHatAccum = 0.0f; - m_phiHatCount = 0; m_y.real(1.0); m_y.imag(0.0); m_p.real(1.0); @@ -124,7 +107,6 @@ void PhaseLockComplex::reset() m_yIm = 0.0f; m_freq = 0.0f; m_freqPrev = 0.0f; - m_lock = 0.0f; m_lockCount = 0; } @@ -170,70 +152,7 @@ void PhaseLockComplex::feed(float re, float im) } // lock estimation - if (m_pskOrder > 1) - { - float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); - - m_avgF(dPhi); - - if (m_phiHatCount < (m_lockTime-1)) - { - m_phiHatCount++; - } - else - { - m_freq = m_avgF.asFloat(); - float dFreq = m_freq - m_freqPrev; - - if ((dFreq > -m_lockThreshold) && (dFreq < m_lockThreshold)) - { - if (m_lockCount < 20) { - m_lockCount++; - } - } - else{ - if (m_lockCount > 0) { - m_lockCount--; - } - } - - m_freqPrev = m_freq; - m_phiHatCount = 0; - } - - // if (m_phiHatCount < (m_lockTime-1)) - // { - // m_dPhiHatAccum += dPhi; // re-accumulate phase for differential calculation - // m_phiHatCount++; - // } - // else - // { - // float dPhi11 = (m_dPhiHatAccum - m_phiHat1); // optimized out division by lock time - // float dPhi12 = (m_phiHat1 - m_phiHat2); - // m_lock = dPhi11 - dPhi12; // second derivative of phase to get lock status - - // if ((m_lock > -m_lockThreshold) && (m_lock < m_lockThreshold)) // includes re-multiplication by lock time - // { - // if (m_lockCount < 20) { // [0..20] - // m_lockCount++; - // } - // } - // else - // { - // if (m_lockCount > 0) { - // m_lockCount -= 2; - // } - // } - - // m_phiHat2 = m_phiHat1; - // m_phiHat1 = m_dPhiHatAccum; - // m_dPhiHatAccum = 0.0f; - // m_phiHatCount = 0; - // } - - // m_phiHatPrev = m_phiHat; - } - else + if (m_pskOrder <= 1) { m_freq = (m_phiHat - m_phiHatPrev) / (2.0*M_PI); @@ -247,7 +166,7 @@ void PhaseLockComplex::feed(float re, float im) if ((dFreq > -0.01) && (dFreq < 0.01)) { - if (m_lockCount < (m_lockTime1-1)) { // [0..479] + if (m_lockCount < (m_lockTime-1)) { // [0..479] m_lockCount++; } } diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index 56f455328..baf4af9b7 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -24,7 +24,6 @@ #define SDRBASE_DSP_PHASELOCKCOMPLEX_H_ #include "dsp/dsptypes.h" -#include "util/movingaverage.h" #include "export.h" /** General purpose Phase-locked loop using complex analytic signal input. */ @@ -50,7 +49,7 @@ public: const std::complex& getComplex() const { return m_y; } float getReal() const { return m_yRe; } float getImag() const { return m_yIm; } - bool locked() const { return m_lockCount > (m_pskOrder > 1 ? 15 : (m_lockTime1-2)); } // 6 + bool locked() const { return m_pskOrder > 1 ? false : m_lockCount > m_lockTime-2; } float getDeltaPhi() const { return m_deltaPhi; } float getPhiHat() const { return m_phiHat; } @@ -70,26 +69,15 @@ private: float m_deltaPhi; float m_phiHat; float m_phiHatPrev; - float m_phiHat1; - float m_phiHat2; - float m_dPhiHatAccum; - int m_phiHatCount; std::complex m_y; std::complex m_p; float m_yRe; float m_yIm; float m_freq; float m_freqPrev; - float m_lock; int m_lockCount; unsigned int m_pskOrder; - int m_lockTime1; int m_lockTime; - float m_lockTimef; - float m_lockThreshold; - MovingAverageUtilVar m_avgF; }; - - #endif /* SDRBASE_DSP_PHASELOCKCOMPLEX_H_ */ From 47f214fdf0062b95ebc573cf5eff14403f691520 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 17 May 2018 09:09:57 +0200 Subject: [PATCH 421/956] ChannelAnalyzerNG: implemented FLL --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 23 +++++++++++++++---- plugins/channelrx/chanalyzerng/chanalyzerng.h | 17 +++++++++++++- .../chanalyzerng/chanalyzernggui.cpp | 7 +++--- .../channelrx/chanalyzerng/chanalyzernggui.h | 1 - .../channelrx/chanalyzerng/chanalyzernggui.ui | 5 ++++ 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 546985628..5d7c0c60d 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -51,6 +51,7 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : DSBFilter = new fftfilt(m_config.m_Bandwidth / m_config.m_inputSampleRate, 2*ssbFftLen); //m_pll.computeCoefficients(0.05f, 0.707f, 1000.0f); // bandwidth, damping factor, loop gain m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain + m_fll.computeCoefficients(0.004f); // ~100Hz @ 48 kHz apply(true); @@ -77,9 +78,10 @@ void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, int spanLog2, bool ssb, bool pll, + bool fll, unsigned int pllPskOrder) { - Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, pllPskOrder); + Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, fll, pllPskOrder); messageQueue->push(cmd); } @@ -170,6 +172,7 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) m_config.m_spanLog2 = cfg.getSpanLog2(); m_config.m_ssb = cfg.getSSB(); m_config.m_pll = cfg.getPLL(); + m_config.m_fll = cfg.getFLL(); m_config.m_pllPskOrder = cfg.getPLLPSKOrder(); qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:" @@ -179,6 +182,7 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) << " m_spanLog2: " << m_config.m_spanLog2 << " m_ssb: " << m_config.m_ssb << " m_pll: " << m_config.m_pll + << " m_fll: " << m_config.m_fll << " m_pllPskOrder: " << m_config.m_pllPskOrder; apply(); @@ -254,7 +258,9 @@ void ChannelAnalyzerNG::apply(bool force) if ((m_running.m_channelSampleRate != m_config.m_channelSampleRate) || (m_running.m_spanLog2 != m_config.m_spanLog2) || force) { - m_pll.setSampleRate(m_running.m_channelSampleRate / (1<setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); @@ -592,6 +590,7 @@ void ChannelAnalyzerNGGUI::applySettings() m_spanLog2, ui->ssb->isChecked(), ui->pll->isChecked(), + ui->pllPskOrder->currentIndex() == 5, 1<pllPskOrder->currentIndex()); } } diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h index 499ded960..8b58f92a2 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.h @@ -66,7 +66,6 @@ private: bool m_doApplySettings; int m_rate; //!< sample rate after final in-channel decimation (spanlog2) int m_spanLog2; - bool m_usePll; MovingAverageUtil m_channelPowerDbAvg; ChannelAnalyzerNG* m_channelAnalyzer; diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index d9e5beffa..f9c740101 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -233,6 +233,11 @@ 16
+ + + F + +
From 0f821b013570229e9d5c44348dceac13960fd742 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 17 May 2018 11:01:28 +0200 Subject: [PATCH 422/956] IIR and FIR Kit: replaced NULL by 0 --- kitiirfir/FFTCode.cpp | 12 ++++++------ kitiirfir/FIRFilterCode.cpp | 4 ++-- kitiirfir/PFiftyOneRevE.cpp | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/kitiirfir/FFTCode.cpp b/kitiirfir/FFTCode.cpp index 914a95c08..ac4f484da 100644 --- a/kitiirfir/FFTCode.cpp +++ b/kitiirfir/FFTCode.cpp @@ -63,8 +63,8 @@ void FFT(double *InputR, double *InputI, int N, TTransFormType Type) { TwiddleI = new double[N / 2]; RevBits = new int[N]; - if (BufferR == NULL || BufferI == NULL || TwiddleR == NULL - || TwiddleI == NULL || RevBits == NULL) { + if (BufferR == 0 || BufferI == 0 || TwiddleR == 0 + || TwiddleI == 0 || RevBits == 0) { // ShowMessage("FFT Memory Allocation Error"); return; } @@ -339,8 +339,8 @@ void DFT(double *InputR, double *InputI, int N, int Type) { TwiddleReal = new double[N]; TwiddleImag = new double[N]; - if (SumR == NULL || SumI == NULL || TwiddleReal == NULL - || TwiddleImag == NULL || (Type != FORWARD && Type != INVERSE)) { + if (SumR == 0 || SumI == 0 || TwiddleReal == 0 + || TwiddleImag == 0 || (Type != FORWARD && Type != INVERSE)) { // ShowMessage("Incorrect DFT Type or unable to allocate memory"); return; } @@ -398,7 +398,7 @@ void RealSigDFT(double *Samples, double *OutputR, double *OutputI, int N) { TwiddleReal = new double[N]; TwiddleImag = new double[N]; - if (TwiddleReal == NULL || TwiddleImag == NULL) { + if (TwiddleReal == 0 || TwiddleImag == 0) { // ShowMessage("Failed to allocate memory in RealSigDFT"); return; } @@ -579,7 +579,7 @@ void WindowData(double *Data, int N, TWindowType WindowType, double Alpha, Beta = 10.0; WinCoeff = new double[N + 2]; - if (WinCoeff == NULL) { + if (WinCoeff == 0) { // ShowMessage("Failed to allocate memory in WindowData() "); return; } diff --git a/kitiirfir/FIRFilterCode.cpp b/kitiirfir/FIRFilterCode.cpp index f3cb9186f..5a2bc24cf 100644 --- a/kitiirfir/FIRFilterCode.cpp +++ b/kitiirfir/FIRFilterCode.cpp @@ -117,7 +117,7 @@ void FIRFilterWindow(double *FIRCoeff, int N, TWindowType WindowType, Beta = 10.0; WinCoeff = new double[N + 2]; - if (WinCoeff == NULL) { + if (WinCoeff == 0) { // ShowMessage("Failed to allocate memory in WindowData() "); return; } @@ -365,7 +365,7 @@ void AdjustDelay(double *FirCoeff, int NumTaps, double Delay) { FFTInputR = new double[FFTSize]; // Real part FFTInputI = new double[FFTSize]; // Imag part - if (FFTInputR == NULL || FFTInputI == NULL) { + if (FFTInputR == 0 || FFTInputI == 0) { //ShowMessage("Unable to allocate memory in AdjustDelay"); return; } diff --git a/kitiirfir/PFiftyOneRevE.cpp b/kitiirfir/PFiftyOneRevE.cpp index 8d03e94af..a896ffcbc 100644 --- a/kitiirfir/PFiftyOneRevE.cpp +++ b/kitiirfir/PFiftyOneRevE.cpp @@ -87,8 +87,8 @@ int PFiftyOne(long double *Coeff, int Degree, long double *RealRoot, QuadK = new (std::nothrow) long double[N + 2]; RealK = new (std::nothrow) long double[N + 2]; QK = new (std::nothrow) long double[N + 2]; - if (P == NULL || QuadQP == NULL || RealQP == NULL || QuadK == NULL - || RealK == NULL || QK == NULL) { + if (P == 0 || QuadQP == 0 || RealQP == 0 || QuadK == 0 + || RealK == 0 || QK == 0) { //ShowMessage("Memory not Allocated in PFiftyOne root finder."); return (0); } @@ -606,7 +606,7 @@ void SetTUVandK(long double *P, int N, long double *TUV, long double *RealK, if (TypeOfQuadK == 0) // Init QuadK 2nd derivative of P { long double *Temp = new (std::nothrow) long double[N + 2]; - if (Temp == NULL) { + if (Temp == 0) { //ShowMessage("Memory not Allocated in PFiftyOne SetTUVandK."); return; } From 6d95c0407606c917b20a0301931df6b98a6ef8d4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 18 May 2018 02:36:29 +0200 Subject: [PATCH 423/956] ChannelAnalyzerNG: fixed FLL and removed IIR and FIR kit that is now useless --- CMakeLists.txt | 1 - kitiirfir/CMakeLists.txt | 38 - kitiirfir/FFTCode.cpp | 770 ------------------ kitiirfir/FFTCode.h | 60 -- kitiirfir/FIRFilterCode.cpp | 401 --------- kitiirfir/FIRFilterCode.h | 36 - kitiirfir/FreqSamplingCode.cpp | 228 ------ kitiirfir/FreqSamplingCode.h | 17 - kitiirfir/IIRFilterCode.cpp | 526 ------------ kitiirfir/IIRFilterCode.h | 38 - kitiirfir/LowPassPrototypes.cpp | 469 ----------- kitiirfir/LowPassPrototypes.h | 45 - kitiirfir/LowPassRoots.cpp | 683 ---------------- kitiirfir/LowPassRoots.h | 27 - kitiirfir/NewParksMcClellan.cpp | 627 -------------- kitiirfir/NewParksMcClellan.h | 31 - kitiirfir/PFiftyOneRevE.cpp | 649 --------------- kitiirfir/PFiftyOneRevE.h | 43 - kitiirfir/QuadRootsRevH.cpp | 344 -------- kitiirfir/QuadRootsRevH.h | 27 - kitiirfir/readme.md | 8 - .../channelrx/chanalyzerng/chanalyzerng.cpp | 9 +- plugins/channelrx/chanalyzerng/chanalyzerng.h | 25 +- sdrbase/CMakeLists.txt | 2 - sdrbase/dsp/freqlockcomplex.cpp | 96 +-- sdrbase/dsp/freqlockcomplex.h | 21 +- 26 files changed, 65 insertions(+), 5156 deletions(-) delete mode 100644 kitiirfir/CMakeLists.txt delete mode 100644 kitiirfir/FFTCode.cpp delete mode 100644 kitiirfir/FFTCode.h delete mode 100644 kitiirfir/FIRFilterCode.cpp delete mode 100644 kitiirfir/FIRFilterCode.h delete mode 100644 kitiirfir/FreqSamplingCode.cpp delete mode 100644 kitiirfir/FreqSamplingCode.h delete mode 100644 kitiirfir/IIRFilterCode.cpp delete mode 100644 kitiirfir/IIRFilterCode.h delete mode 100644 kitiirfir/LowPassPrototypes.cpp delete mode 100644 kitiirfir/LowPassPrototypes.h delete mode 100644 kitiirfir/LowPassRoots.cpp delete mode 100644 kitiirfir/LowPassRoots.h delete mode 100644 kitiirfir/NewParksMcClellan.cpp delete mode 100644 kitiirfir/NewParksMcClellan.h delete mode 100644 kitiirfir/PFiftyOneRevE.cpp delete mode 100644 kitiirfir/PFiftyOneRevE.h delete mode 100644 kitiirfir/QuadRootsRevH.cpp delete mode 100644 kitiirfir/QuadRootsRevH.h delete mode 100644 kitiirfir/readme.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f5929be5..f3b26d126 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,7 +219,6 @@ endif() ############################################################################## # base libraries -add_subdirectory(kitiirfir) add_subdirectory(sdrbase) add_subdirectory(sdrgui) add_subdirectory(sdrsrv) diff --git a/kitiirfir/CMakeLists.txt b/kitiirfir/CMakeLists.txt deleted file mode 100644 index 0cb88df33..000000000 --- a/kitiirfir/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -project(kitiirfir) - -set(kitiirfir_SOURCES - FFTCode.cpp - FIRFilterCode.cpp - FreqSamplingCode.cpp - IIRFilterCode.cpp - LowPassPrototypes.cpp - LowPassRoots.cpp - NewParksMcClellan.cpp - PFiftyOneRevE.cpp - QuadRootsRevH.cpp -) - -set(kitiirfir_HEADERS - FFTCode.h - FIRFilterCode.h - FreqSamplingCode.h - IIRFilterCode.h - LowPassPrototypes.h - LowPassRoots.h - NewParksMcClellan.h - PFiftyOneRevE.h - QuadRootsRevH.h -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} -) - -add_definitions(-DQT_SHARED) - -add_library(kitiirfir SHARED - ${kitiirfir_SOURCES} -) - -install(TARGETS kitiirfir DESTINATION lib) diff --git a/kitiirfir/FFTCode.cpp b/kitiirfir/FFTCode.cpp deleted file mode 100644 index ac4f484da..000000000 --- a/kitiirfir/FFTCode.cpp +++ /dev/null @@ -1,770 +0,0 @@ -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - If you find a problem, please leave a note at: - http://www.iowahills.com/feedbackcomments.html - June 6, 2016 - - ShowMessage is a C++ Builder function, and it usage has been commented out. - If you are using C++ Builder, include vcl.h for ShowMessage. - Otherwise replace ShowMessage with something appropriate for your compiler. - */ - -#include "FFTCode.h" -#include - -namespace kitiirfir -{ - -//--------------------------------------------------------------------------- -// This calculates the required FFT size for a given number of points. -int RequiredFFTSize(int NumPts) { - int N = MINIMUM_FFT_SIZE; - while (N < NumPts && N < MAXIMUM_FFT_SIZE) { - N *= 2; - } - return N; -} - -//--------------------------------------------------------------------------- - -// This verifies that the FFT Size N = 2^M. M is returned -// N must be >= 8 for the Twiddle calculations -int IsValidFFTSize(int N) { - if (N < MINIMUM_FFT_SIZE || N > MAXIMUM_FFT_SIZE || (N & (N - 1)) != 0) - return (0); // N & (N - 1) ensures a power of 2 - return ((int) (log((double) N) / M_LN2 + 0.5)); // return M where N = 2^M -} - -//--------------------------------------------------------------------------- - -// Fast Fourier Transform -// This code puts DC in bin 0 and scales the output of a forward transform by 1/N. -// InputR and InputI are the real and imaginary input arrays of length N. -// The output values are returned in the Input arrays. -// TTransFormType is either FORWARD or INVERSE (defined in the header file) -// 256 pts in 50 us -void FFT(double *InputR, double *InputI, int N, TTransFormType Type) { - int j, LogTwoOfN, *RevBits; - double *BufferR, *BufferI, *TwiddleR, *TwiddleI; - double OneOverN; - - // Verify the FFT size and type. - LogTwoOfN = IsValidFFTSize(N); - if (LogTwoOfN == 0 || (Type != FORWARD && Type != INVERSE)) { - // ShowMessage("Invalid FFT type or size."); - return; - } - - // Memory allocation for all the arrays. - BufferR = new double[N]; - BufferI = new double[N]; - TwiddleR = new double[N / 2]; - TwiddleI = new double[N / 2]; - RevBits = new int[N]; - - if (BufferR == 0 || BufferI == 0 || TwiddleR == 0 - || TwiddleI == 0 || RevBits == 0) { - // ShowMessage("FFT Memory Allocation Error"); - return; - } - - ReArrangeInput(InputR, InputI, BufferR, BufferI, RevBits, N); - FillTwiddleArray(TwiddleR, TwiddleI, N, Type); - Transform(InputR, InputI, BufferR, BufferI, TwiddleR, TwiddleI, N); - - // The ReArrangeInput function swapped Input[] and Buffer[]. Then Transform() - // swapped them again, LogTwoOfN times. Ultimately, these swaps must be done - // an even number of times, or the pointer to Buffer gets returned. - // So we must do one more swap here, for N = 16, 64, 256, 1024, ... - OneOverN = 1.0; - if (Type == FORWARD) - OneOverN = 1.0 / (double) N; - - if (LogTwoOfN % 2 == 1) { - for (j = 0; j < N; j++) - InputR[j] = InputR[j] * OneOverN; - for (j = 0; j < N; j++) - InputI[j] = InputI[j] * OneOverN; - } else // if(LogTwoOfN % 2 == 0) then the results are still in Buffer. - { - for (j = 0; j < N; j++) - InputR[j] = BufferR[j] * OneOverN; - for (j = 0; j < N; j++) - InputI[j] = BufferI[j] * OneOverN; - } - - delete[] BufferR; - delete[] BufferI; - delete[] TwiddleR; - delete[] TwiddleI; - delete[] RevBits; -} -//--------------------------------------------------------------------------- - -// This puts the input arrays in bit reversed order. -// The while loop generates an array of bit reversed numbers. e.g. -// e.g. N=8: RevBits = 0,4,2,6,1,5,3,7 N=16: RevBits = 0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15 -void ReArrangeInput(double *InputR, double *InputI, double *BufferR, - double *BufferI, int *RevBits, int N) { - int j, k, J, K; - - J = N / 2; - K = 1; - RevBits[0] = 0; - while (J >= 1) { - for (k = 0; k < K; k++) { - RevBits[k + K] = RevBits[k] + J; - } - K *= 2; - J /= 2; - } - - // Move the rearranged input values to Buffer. - // Take note of the pointer swaps at the top of the transform algorithm. - for (j = 0; j < N; j++) { - BufferR[j] = InputR[RevBits[j]]; - BufferI[j] = InputI[RevBits[j]]; - } - -} - -//--------------------------------------------------------------------------- - -/* - The Pentium takes a surprising amount of time to calculate the sine and cosine. - You may want to make the twiddle arrays static if doing repeated FFTs of the same size. - This uses 4 fold symmetry to calculate the twiddle factors. As a result, this function - requires a minimum FFT size of 8. - */ -void FillTwiddleArray(double *TwiddleR, double *TwiddleI, int N, - TTransFormType Type) { - int j; - double Theta, TwoPiOverN; - - TwoPiOverN = M_2PI / (double) N; - - if (Type == FORWARD) { - TwiddleR[0] = 1.0; - TwiddleI[0] = 0.0; - TwiddleR[N / 4] = 0.0; - TwiddleI[N / 4] = -1.0; - TwiddleR[N / 8] = M_SQRT_2; - TwiddleI[N / 8] = -M_SQRT_2; - TwiddleR[3 * N / 8] = -M_SQRT_2; - TwiddleI[3 * N / 8] = -M_SQRT_2; - for (j = 1; j < N / 8; j++) { - Theta = (double) j * -TwoPiOverN; - TwiddleR[j] = cos(Theta); - TwiddleI[j] = sin(Theta); - TwiddleR[N / 4 - j] = -TwiddleI[j]; - TwiddleI[N / 4 - j] = -TwiddleR[j]; - TwiddleR[N / 4 + j] = TwiddleI[j]; - TwiddleI[N / 4 + j] = -TwiddleR[j]; - TwiddleR[N / 2 - j] = -TwiddleR[j]; - TwiddleI[N / 2 - j] = TwiddleI[j]; - } - } - - else { - TwiddleR[0] = 1.0; - TwiddleI[0] = 0.0; - TwiddleR[N / 4] = 0.0; - TwiddleI[N / 4] = 1.0; - TwiddleR[N / 8] = M_SQRT_2; - TwiddleI[N / 8] = M_SQRT_2; - TwiddleR[3 * N / 8] = -M_SQRT_2; - TwiddleI[3 * N / 8] = M_SQRT_2; - for (j = 1; j < N / 8; j++) { - Theta = (double) j * TwoPiOverN; - TwiddleR[j] = cos(Theta); - TwiddleI[j] = sin(Theta); - TwiddleR[N / 4 - j] = TwiddleI[j]; - TwiddleI[N / 4 - j] = TwiddleR[j]; - TwiddleR[N / 4 + j] = -TwiddleI[j]; - TwiddleI[N / 4 + j] = TwiddleR[j]; - TwiddleR[N / 2 - j] = -TwiddleR[j]; - TwiddleI[N / 2 - j] = TwiddleI[j]; - } - } - -} - -//--------------------------------------------------------------------------- - -// The Fast Fourier Transform. -void Transform(double *InputR, double *InputI, double *BufferR, double *BufferI, - double *TwiddleR, double *TwiddleI, int N) { - int j, k, J, K, I, T; - double *TempPointer; - double TempR, TempI; - - J = N / 2; // J increments down to 1 - K = 1; // K increments up to N/2 - while (J > 0) // Loops Log2(N) times. - { - // Swap pointers, instead doing this: for(j=0; j 0.0) - Mag = sqrt(Mag); - else - Mag = 1.0E-12; - - return (Mag); -} - -//--------------------------------------------------------------------------- - -/* - These are the window definitions. These windows can be used for either - FIR filter design or with an FFT for spectral analysis. - For definitions, see this article: http://en.wikipedia.org/wiki/Window_function - - This function has 6 inputs - Data is the array, of length N, containing the data to to be windowed. - This data is either an FIR filter sinc pulse, or the data to be analyzed by an fft. - - WindowType is an enum defined in the header file. - e.g. wtKAISER, wtSINC, wtHANNING, wtHAMMING, wtBLACKMAN, ... - - Alpha sets the width of the flat top. - Windows such as the Tukey and Trapezoid are defined to have a variably wide flat top. - As can be seen by its definition, the Tukey is just a Hanning window with a flat top. - Alpha can be used to give any of these windows a partial flat top, except the Flattop and Kaiser. - Alpha = 0 gives the original window. (i.e. no flat top) - To generate a Tukey window, use a Hanning with 0 < Alpha < 1 - To generate a Bartlett window (triangular), use a Trapezoid window with Alpha = 0. - Alpha = 1 generates a rectangular window in all cases. (except the Flattop and Kaiser) - - - Beta is used with the Kaiser, Sinc, and Sine windows only. - These three windows are used primarily for FIR filter design. Then - Beta controls the filter's transition bandwidth and the sidelobe levels. - All other windows ignore Beta. - - UnityGain controls whether the gain of these windows is set to unity. - Only the Flattop window has unity gain by design. The Hanning window, for example, has a gain - of 1/2. UnityGain = true sets the gain to 1, which preserves the signal's energy level - when these windows are used for spectral analysis. - - Don't use this with FIR filter design however. Since most of the enegy in an FIR sinc pulse - is in the middle of the window, the window needs a peak amplitude of one, not unity gain. - Setting UnityGain = true will simply cause the resulting FIR filter to have excess gain. - - If using these windows for FIR filters, start with the Kaiser, Sinc, or Sine windows and - adjust Beta for the desired transition BW and sidelobe levels (set Alpha = 0). - While the FlatTop is an excellent window for spectral analysis, don't use it for FIR filter design. - It has a peak amplitude of ~ 4.7 which causes the resulting FIR filter to have about this much gain. - It works poorly for FIR filters even if you adjust its peak amplitude. - The Trapezoid also works poorly for FIR filter design. - - If using these windows with an fft for spectral analysis, start with the Hanning, Gauss, or Flattop. - When choosing a window for spectral analysis, you must trade off between resolution and amplitude - accuracy. The Hanning has the best resolution while the Flatop has the best amplitude accuracy. - The Gauss is midway between these two for both accuracy and resolution. These three were - the only windows available in the HP 89410A Vector Signal Analyzer. Which is to say, these three - are the probably the best windows for general purpose signal analysis. - */ - -void WindowData(double *Data, int N, TWindowType WindowType, double Alpha, - double Beta, bool UnityGain) { - if (WindowType == wtNONE) - return; - - int j, M, TopWidth; - double dM, *WinCoeff; - - if (WindowType == wtKAISER || WindowType == wtFLATTOP) - Alpha = 0.0; - - if (Alpha < 0.0) - Alpha = 0.0; - if (Alpha > 1.0) - Alpha = 1.0; - - if (Beta < 0.0) - Beta = 0.0; - if (Beta > 10.0) - Beta = 10.0; - - WinCoeff = new double[N + 2]; - if (WinCoeff == 0) { - // ShowMessage("Failed to allocate memory in WindowData() "); - return; - } - - TopWidth = (int) (Alpha * (double) N); - if (TopWidth % 2 != 0) - TopWidth++; - if (TopWidth > N) - TopWidth = N; - M = N - TopWidth; - dM = M + 1; - - // Calculate the window for N/2 points, then fold the window over (at the bottom). - // TopWidth points will be set to 1. - if (WindowType == wtKAISER) { - double Arg; - for (j = 0; j < M; j++) { - Arg = Beta * sqrt(1.0 - pow(((double) (2 * j + 2) - dM) / dM, 2.0)); - WinCoeff[j] = Bessel(Arg) / Bessel(Beta); - } - } - - else if (WindowType == wtSINC) // Lanczos - { - for (j = 0; j < M; j++) - WinCoeff[j] = Sinc((double) (2 * j + 1 - M) / dM * M_PI); - for (j = 0; j < M; j++) - WinCoeff[j] = pow(WinCoeff[j], Beta); - } - - else if (WindowType == wtSINE) // Hanning if Beta = 2 - { - for (j = 0; j < M / 2; j++) - WinCoeff[j] = sin((double) (j + 1) * M_PI / dM); - for (j = 0; j < M / 2; j++) - WinCoeff[j] = pow(WinCoeff[j], Beta); - } - - else if (WindowType == wtHANNING) { - for (j = 0; j < M / 2; j++) - WinCoeff[j] = 0.5 - 0.5 * cos((double) (j + 1) * M_2PI / dM); - } - - else if (WindowType == wtHAMMING) { - for (j = 0; j < M / 2; j++) - WinCoeff[j] = 0.54 - 0.46 * cos((double) (j + 1) * M_2PI / dM); - } - - else if (WindowType == wtBLACKMAN) { - for (j = 0; j < M / 2; j++) { - WinCoeff[j] = 0.42 - 0.50 * cos((double) (j + 1) * M_2PI / dM) - + 0.08 * cos((double) (j + 1) * M_2PI * 2.0 / dM); - } - } - - // Defined at: http://www.bth.se/fou/forskinfo.nsf/0/130c0940c5e7ffcdc1256f7f0065ac60/$file/ICOTA_2004_ttr_icl_mdh.pdf - else if (WindowType == wtFLATTOP) { - for (j = 0; j <= M / 2; j++) { - WinCoeff[j] = 1.0 - - 1.93293488969227 * cos((double) (j + 1) * M_2PI / dM) - + 1.28349769674027 - * cos((double) (j + 1) * M_2PI * 2.0 / dM) - - 0.38130801681619 - * cos((double) (j + 1) * M_2PI * 3.0 / dM) - + 0.02929730258511 - * cos((double) (j + 1) * M_2PI * 4.0 / dM); - } - } - - else if (WindowType == wtBLACKMAN_HARRIS) { - for (j = 0; j < M / 2; j++) { - WinCoeff[j] = 0.35875 - 0.48829 * cos((double) (j + 1) * M_2PI / dM) - + 0.14128 * cos((double) (j + 1) * M_2PI * 2.0 / dM) - - 0.01168 * cos((double) (j + 1) * M_2PI * 3.0 / dM); - } - } - - else if (WindowType == wtBLACKMAN_NUTTALL) { - for (j = 0; j < M / 2; j++) { - WinCoeff[j] = 0.3535819 - - 0.4891775 * cos((double) (j + 1) * M_2PI / dM) - + 0.1365995 * cos((double) (j + 1) * M_2PI * 2.0 / dM) - - 0.0106411 * cos((double) (j + 1) * M_2PI * 3.0 / dM); - } - } - - else if (WindowType == wtNUTTALL) { - for (j = 0; j < M / 2; j++) { - WinCoeff[j] = 0.355768 - - 0.487396 * cos((double) (j + 1) * M_2PI / dM) - + 0.144232 * cos((double) (j + 1) * M_2PI * 2.0 / dM) - - 0.012604 * cos((double) (j + 1) * M_2PI * 3.0 / dM); - } - } - - else if (WindowType == wtKAISER_BESSEL) { - for (j = 0; j <= M / 2; j++) { - WinCoeff[j] = 0.402 - 0.498 * cos(M_2PI * (double) (j + 1) / dM) - + 0.098 * cos(2.0 * M_2PI * (double) (j + 1) / dM) - + 0.001 * cos(3.0 * M_2PI * (double) (j + 1) / dM); - } - } - - else if (WindowType == wtTRAPEZOID) // Rectangle for Alpha = 1 Triangle for Alpha = 0 - { - int K = M / 2; - if (M % 2) - K++; - for (j = 0; j < K; j++) - WinCoeff[j] = (double) (j + 1) / (double) K; - } - - // This definition is from http://en.wikipedia.org/wiki/Window_function (Gauss Generalized normal window) - // We set their p = 2, and use Alpha in the numerator, instead of Sigma in the denominator, as most others do. - // Alpha = 2.718 puts the Gauss window response midway between the Hanning and the Flattop (basically what we want). - // It also gives the same BW as the Gauss window used in the HP 89410A Vector Signal Analyzer. - else if (WindowType == wtGAUSS) { - for (j = 0; j < M / 2; j++) { - WinCoeff[j] = ((double) (j + 1) - dM / 2.0) / (dM / 2.0) * 2.7183; - WinCoeff[j] *= WinCoeff[j]; - WinCoeff[j] = exp(-WinCoeff[j]); - } - } - - else // Error. - { - // ShowMessage("Incorrect window type in WindowFFTData"); - delete[] WinCoeff; - return; - } - - // Fold the coefficients over. - for (j = 0; j < M / 2; j++) - WinCoeff[N - j - 1] = WinCoeff[j]; - - // This is the flat top if Alpha > 0. Cannot be applied to a Kaiser or Flat Top. - if (WindowType != wtKAISER && WindowType != wtFLATTOP) { - for (j = M / 2; j < N - M / 2; j++) - WinCoeff[j] = 1.0; - } - - // UnityGain = true will set the gain of these windows to 1. Don't use this with FIR filter design. - if (UnityGain) { - double Sum = 0.0; - for (j = 0; j < N; j++) - Sum += WinCoeff[j]; - Sum /= (double) N; - if (Sum != 0.0) - for (j = 0; j < N; j++) - WinCoeff[j] /= Sum; - } - - // Apply the window to the data. - for (j = 0; j < N; j++) - Data[j] *= WinCoeff[j]; - - delete[] WinCoeff; - -} - -//--------------------------------------------------------------------------- - -// This gets used with the Kaiser window. -double Bessel(double x) { - double Sum = 0.0, XtoIpower; - int i, j, Factorial; - for (i = 1; i < 10; i++) { - XtoIpower = pow(x / 2.0, (double) i); - Factorial = 1; - for (j = 1; j <= i; j++) - Factorial *= j; - Sum += pow(XtoIpower / (double) Factorial, 2.0); - } - return (1.0 + Sum); -} - -//----------------------------------------------------------------------------- - -// This gets used with the Sinc window. -double Sinc(double x) { - if (x > -1.0E-5 && x < 1.0E-5) - return (1.0); - return (sin(x) / x); -} - -//--------------------------------------------------------------------------- - -} // namespace diff --git a/kitiirfir/FFTCode.h b/kitiirfir/FFTCode.h deleted file mode 100644 index 59bdb4388..000000000 --- a/kitiirfir/FFTCode.h +++ /dev/null @@ -1,60 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef FFTCodeH -#define FFTCodeH - -#define M_2PI 6.28318530717958647692 // 2*Pi -#define M_SQRT_2 0.707106781186547524401 // sqrt(2)/2 -#define MAXIMUM_FFT_SIZE 1048576 -#define MINIMUM_FFT_SIZE 8 - -namespace kitiirfir -{ - -//--------------------------------------------------------------------------- -// Must retain the order on the 1st line for legacy FIR code. -enum TWindowType { - wtFIRSTWINDOW, - wtNONE, - wtKAISER, - wtSINC, - wtHANNING, - wtHAMMING, - wtBLACKMAN, - wtFLATTOP, - wtBLACKMAN_HARRIS, - wtBLACKMAN_NUTTALL, - wtNUTTALL, - wtKAISER_BESSEL, - wtTRAPEZOID, - wtGAUSS, - wtSINE, - wtTEST -}; - -enum TTransFormType { - FORWARD, INVERSE -}; - -int RequiredFFTSize(int NumPts); -int IsValidFFTSize(int x); -void FFT(double *InputR, double *InputI, int N, TTransFormType Type); -void ReArrangeInput(double *InputR, double *InputI, double *BufferR, - double *BufferI, int *RevBits, int N); -void FillTwiddleArray(double *TwiddleR, double *TwiddleI, int N, - TTransFormType Type); -void Transform(double *InputR, double *InputI, double *BufferR, double *BufferI, - double *TwiddleR, double *TwiddleI, int N); -void DFT(double *InputR, double *InputI, int N, int Type); -void RealSigDFT(double *Samples, double *OutputR, double *OutputI, int N); -double SingleFreqDFT(double *Samples, int N, double Omega); -double Goertzel(double *Samples, int N, double Omega); -void WindowData(double *Data, int N, TWindowType WindowType, double Alpha, - double Beta, bool UnityGain); -double Bessel(double x); -double Sinc(double x); - -} // namespace - -#endif - diff --git a/kitiirfir/FIRFilterCode.cpp b/kitiirfir/FIRFilterCode.cpp deleted file mode 100644 index 5a2bc24cf..000000000 --- a/kitiirfir/FIRFilterCode.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - If you find a problem, please leave a note at: - http://www.iowahills.com/feedbackcomments.html - May 1, 2016 - - ShowMessage is a C++ Builder function, and it usage has been commented out. - If you are using C++ Builder, include vcl.h for ShowMessage. - Otherwise replace ShowMessage with something appropriate for yor compiler. - - RectWinFIR() generates the impulse response for a rectangular windowed low pass, high pass, - band pass, or notch filter. Then a window, such as the Kaiser, is applied to the FIR coefficients. - See the FilterKitMain.cpp file for an example on how to use this code. - - double FirCoeff[MAXNUMTAPS]; - int NumTaps; NumTaps can be even or odd and < MAXNUMTAPS - TPassTypeName PassType; PassType is defined in the header file. firLPF, firHPF, firBPF, firNOTCH, firALLPASS - double OmegaC 0.0 < OmegaC < 1.0 The corner freq, or center freq if BPF or NOTCH - double BW 0.0 < BW < 1.0 The band width if BPF or NOTCH - */ - -#include "FIRFilterCode.h" -#include - -namespace kitiirfir -{ - -// Rectangular Windowed FIR. The equations used here are developed in numerous textbooks. -void RectWinFIR(double *FirCoeff, int NumTaps, TFIRPassTypes PassType, - double OmegaC, double BW) { - int j; - double Arg, OmegaLow, OmegaHigh; - - switch (PassType) { - case firLPF: // Low Pass - for (j = 0; j < NumTaps; j++) { - Arg = (double) j - (double) (NumTaps - 1) / 2.0; - FirCoeff[j] = OmegaC * Sinc(OmegaC * Arg * M_PI); - } - break; - - case firHPF: // High Pass - if (NumTaps % 2 == 1) // Odd tap counts - { - for (j = 0; j < NumTaps; j++) { - Arg = (double) j - (double) (NumTaps - 1) / 2.0; - FirCoeff[j] = Sinc(Arg * M_PI) - - OmegaC * Sinc(OmegaC * Arg * M_PI); - } - } - - else // Even tap counts - { - for (j = 0; j < NumTaps; j++) { - Arg = (double) j - (double) (NumTaps - 1) / 2.0; - if (Arg == 0.0) - FirCoeff[j] = 0.0; - else - FirCoeff[j] = cos(OmegaC * Arg * M_PI) / M_PI / Arg - + cos(Arg * M_PI); - } - } - break; - - case firBPF: // Band Pass - OmegaLow = OmegaC - BW / 2.0; - OmegaHigh = OmegaC + BW / 2.0; - for (j = 0; j < NumTaps; j++) { - Arg = (double) j - (double) (NumTaps - 1) / 2.0; - if (Arg == 0.0) - FirCoeff[j] = 0.0; - else - FirCoeff[j] = (cos(OmegaLow * Arg * M_PI) - - cos(OmegaHigh * Arg * M_PI)) / M_PI / Arg; - } - break; - - case firNOTCH: // Notch, if NumTaps is even, the response at Pi is attenuated. - OmegaLow = OmegaC - BW / 2.0; - OmegaHigh = OmegaC + BW / 2.0; - for (j = 0; j < NumTaps; j++) { - Arg = (double) j - (double) (NumTaps - 1) / 2.0; - FirCoeff[j] = Sinc(Arg * M_PI) - - OmegaHigh * Sinc(OmegaHigh * Arg * M_PI) - - OmegaLow * Sinc(OmegaLow * Arg * M_PI); - } - break; - - case firALLPASS: // All Pass, this is trivial, but it shows how an fir all pass (delay) can be done. - for (j = 0; j < NumTaps; j++) - FirCoeff[j] = 0.0; - FirCoeff[(NumTaps - 1) / 2] = 1.0; - break; - case firNOT_FIR: - default: - break; - } - // Now use the FIRFilterWindow() function to reduce the sinc(x) effects. -} - -//--------------------------------------------------------------------------- - -// Used to reduce the sinc(x) effects on a set of FIR coefficients. This will, unfortunately, -// widen the filter's transition band, but the stop band attenuation will improve dramatically. -void FIRFilterWindow(double *FIRCoeff, int N, TWindowType WindowType, - double Beta) { - if (WindowType == wtNONE) - return; - - int j; - double dN, *WinCoeff; - - if (Beta < 0.0) - Beta = 0.0; - if (Beta > 10.0) - Beta = 10.0; - - WinCoeff = new double[N + 2]; - if (WinCoeff == 0) { - // ShowMessage("Failed to allocate memory in WindowData() "); - return; - } - - // Calculate the window for N/2 points, then fold the window over (at the bottom). - dN = N + 1; // a double - if (WindowType == wtKAISER) { - double Arg; - for (j = 0; j < N; j++) { - Arg = Beta * sqrt(1.0 - pow(((double) (2 * j + 2) - dN) / dN, 2.0)); - WinCoeff[j] = Bessel(Arg) / Bessel(Beta); - } - } - - else if (WindowType == wtSINC) // Lanczos - { - for (j = 0; j < N; j++) - WinCoeff[j] = Sinc((double) (2 * j + 1 - N) / dN * M_PI); - for (j = 0; j < N; j++) - WinCoeff[j] = pow(WinCoeff[j], Beta); - } - - else if (WindowType == wtSINE) // Hanning if Beta = 2 - { - for (j = 0; j < N / 2; j++) - WinCoeff[j] = sin((double) (j + 1) * M_PI / dN); - for (j = 0; j < N / 2; j++) - WinCoeff[j] = pow(WinCoeff[j], Beta); - } - - else // Error. - { - // ShowMessage("Incorrect window type in WindowFFTData"); - delete[] WinCoeff; - return; - } - - // Fold the coefficients over. - for (j = 0; j < N / 2; j++) - WinCoeff[N - j - 1] = WinCoeff[j]; - - // Apply the window to the FIR coefficients. - for (j = 0; j < N; j++) - FIRCoeff[j] *= WinCoeff[j]; - - delete[] WinCoeff; - -} - -//--------------------------------------------------------------------------- - -// This implements an FIR filter. The register shifts are done by rotating the indexes. -void FilterWithFIR(double *FirCoeff, int NumTaps, double *Signal, - double *FilteredSignal, int NumSigPts) { - int j, k, n, Top = 0; - double y, Reg[MAX_NUMTAPS]; - - for (j = 0; j < NumTaps; j++) - Reg[j] = 0.0; - - for (j = 0; j < NumSigPts; j++) { - Reg[Top] = Signal[j]; - y = 0.0; - n = 0; - - // The FirCoeff index increases while the Reg index decreases. - for (k = Top; k >= 0; k--) { - y += FirCoeff[n++] * Reg[k]; - } - for (k = NumTaps - 1; k > Top; k--) { - y += FirCoeff[n++] * Reg[k]; - } - FilteredSignal[j] = y; - - Top++; - if (Top >= NumTaps) - Top = 0; - } - -} - -//--------------------------------------------------------------------------- - -// This code is equivalent to the code above. It uses register shifts, which makes it -// less efficient, but it is easier to follow (i.e. compare to a FIR flow chart). -void FilterWithFIR2(double *FirCoeff, int NumTaps, double *Signal, - double *FilteredSignal, int NumSigPts) { - int j, k; - double y, Reg[MAX_NUMTAPS]; - - for (j = 0; j < NumTaps; j++) - Reg[j] = 0.0; // Init the delay registers. - - for (j = 0; j < NumSigPts; j++) { - // Shift the register values down and set Reg[0]. - for (k = NumTaps; k > 1; k--) - Reg[k - 1] = Reg[k - 2]; - Reg[0] = Signal[j]; - - y = 0.0; - for (k = 0; k < NumTaps; k++) - y += FirCoeff[k] * Reg[k]; - FilteredSignal[j] = y; - } - -} - -//--------------------------------------------------------------------------- - -// This function is used to correct the corner frequency values on FIR filters. -// We normally specify the 3 dB frequencies when specifing a filter. The Parks McClellan routine -// uses OmegaC and BW to set the 0 dB band edges, so its final OmegaC and BW values are not close -// to -3 dB. The Rectangular Windowed filters are better for high tap counts, but for low tap counts, -// their 3 dB frequencies are also well off the mark. - -// To use this function, first calculate a set of FIR coefficients, then pass them here, along with -// OmegaC and BW. This calculates a corrected OmegaC for low and high pass filters. It calcultes a -// corrected BW for band pass and notch filters. Use these corrected values to recalculate the FIR filter. - -// The Goertzel algorithm is used to calculate the filter's magnitude response at the single -// frequency defined in the loop. We start in the pass band and work out to the -20dB freq. - -void FIRFreqError(double *Coeff, int NumTaps, int PassType, double *OmegaC, - double *BW) { - int j, J3dB, CenterJ; - double Omega, CorrectedOmega, CorrectedBW, Omega1, Omega2, Mag; - - // In these loops, we break at -20 dB to ensure that large ripple is ignored. - if (PassType == firLPF) { - J3dB = 10; - for (j = 0; j < NUM_FREQ_ERR_PTS; j++) { - Omega = (double) j / dNUM_FREQ_ERR_PTS; - Mag = Goertzel(Coeff, NumTaps, Omega); - if (Mag > 0.707) - J3dB = j; // J3dB will be the last j where the response was > -3 dB - if (Mag < 0.1) - break; // Stop when the response is down to -20 dB. - } - Omega = (double) J3dB / dNUM_FREQ_ERR_PTS; - } - - else if (PassType == firHPF) { - J3dB = NUM_FREQ_ERR_PTS - 10; - for (j = NUM_FREQ_ERR_PTS - 1; j >= 0; j--) { - Omega = (double) j / dNUM_FREQ_ERR_PTS; - Mag = Goertzel(Coeff, NumTaps, Omega); - if (Mag > 0.707) - J3dB = j; // J3dB will be the last j where the response was > -3 dB - if (Mag < 0.1) - break; // Stop when the response is down to -20 dB. - } - Omega = (double) J3dB / dNUM_FREQ_ERR_PTS; - } - - else if (PassType == firBPF) { - CenterJ = (int) (dNUM_FREQ_ERR_PTS * *OmegaC); - J3dB = CenterJ; - for (j = CenterJ; j >= 0; j--) { - Omega = (double) j / dNUM_FREQ_ERR_PTS; - Mag = Goertzel(Coeff, NumTaps, Omega); - if (Mag > 0.707) - J3dB = j; - if (Mag < 0.1) - break; - } - Omega1 = (double) J3dB / dNUM_FREQ_ERR_PTS; - - J3dB = CenterJ; - for (j = CenterJ; j < NUM_FREQ_ERR_PTS; j++) { - Omega = (double) j / dNUM_FREQ_ERR_PTS; - Mag = Goertzel(Coeff, NumTaps, Omega); - if (Mag > 0.707) - J3dB = j; - if (Mag < 0.1) - break; - } - Omega2 = (double) J3dB / dNUM_FREQ_ERR_PTS; - } - - // The code above starts in the pass band. This starts in the stop band. - else // PassType == firNOTCH - { - CenterJ = (int) (dNUM_FREQ_ERR_PTS * *OmegaC); - J3dB = CenterJ; - for (j = CenterJ; j >= 0; j--) { - Omega = (double) j / dNUM_FREQ_ERR_PTS; - Mag = Goertzel(Coeff, NumTaps, Omega); - if (Mag <= 0.707) - J3dB = j; - if (Mag > 0.99) - break; - } - Omega1 = (double) J3dB / dNUM_FREQ_ERR_PTS; - - J3dB = CenterJ; - for (j = CenterJ; j < NUM_FREQ_ERR_PTS; j++) { - Omega = (double) j / dNUM_FREQ_ERR_PTS; - Mag = Goertzel(Coeff, NumTaps, Omega); - if (Mag <= 0.707) - J3dB = j; - if (Mag > 0.99) - break; - } - Omega2 = (double) J3dB / dNUM_FREQ_ERR_PTS; - } - - // This calculates the corrected OmegaC and BW and error checks the values. - if (PassType == firLPF || PassType == firHPF) { - CorrectedOmega = *OmegaC * 2.0 - Omega; // This is usually OK. - if (CorrectedOmega < 0.001) - CorrectedOmega = 0.001; - if (CorrectedOmega > 0.99) - CorrectedOmega = 0.99; - *OmegaC = CorrectedOmega; - } - - else // PassType == firBPF || PassType == firNOTCH - { - CorrectedBW = *BW * 2.0 - (Omega2 - Omega1); // This routinely goes neg with Notch. - if (CorrectedBW < 0.01) - CorrectedBW = 0.01; - if (CorrectedBW > *BW * 2.0) - CorrectedBW = *BW * 2.0; - if (CorrectedBW > 0.98) - CorrectedBW = 0.98; - *BW = CorrectedBW; - } - -} - -//----------------------------------------------------------------------------- - -// This shows how to adjust the delay of an FIR by a fractional amount. -// We take the FFT of the FIR coefficients to get to the frequency domain, -// then apply the Laplace delay operator, and then do an inverse FFT. - -// Use this function last. i.e. After the window was applied to the coefficients. -// The Delay value is in terms of a fraction of a sample (not in terms of sampling freq). -// Delay may be pos or neg. Typically a filter's delay can be adjusted by +/- NumTaps/20 -// without affecting its performance significantly. A typical Delay value would be 0.75 -void AdjustDelay(double *FirCoeff, int NumTaps, double Delay) { - int j, FFTSize; - double *FFTInputR, *FFTInputI, Arg, Temp; - FFTSize = RequiredFFTSize(NumTaps + (int) fabs(Delay) + 1); // Zero pad by at least Delay + 1 to prevent the impulse response from wrapping around. - - FFTInputR = new double[FFTSize]; // Real part - FFTInputI = new double[FFTSize]; // Imag part - if (FFTInputR == 0 || FFTInputI == 0) { - //ShowMessage("Unable to allocate memory in AdjustDelay"); - return; - } - for (j = 0; j < FFTSize; j++) - FFTInputR[j] = FFTInputI[j] = 0.0; // A mandatory init. - for (j = 0; j < NumTaps; j++) - FFTInputR[j] = FirCoeff[j]; // Fill the real part with the FIR coeff. - - FFT(FFTInputR, FFTInputI, FFTSize, FORWARD); // Do an FFT - for (j = 0; j <= FFTSize / 2; j++) // Apply the Laplace Delay operator e^(-j*omega*Delay). - { - Arg = -Delay * (double) j / (double) FFTSize * M_2PI; // This is -Delay * (the FFT bin frequency). - Temp = cos(Arg) * FFTInputR[j] - sin(Arg) * FFTInputI[j]; - FFTInputI[j] = cos(Arg) * FFTInputI[j] + sin(Arg) * FFTInputR[j]; - FFTInputR[j] = Temp; - } - for (j = 1; j < FFTSize / 2; j++) // Fill the neg freq bins with the conjugate values. - { - FFTInputR[FFTSize - j] = FFTInputR[j]; - FFTInputI[FFTSize - j] = -FFTInputI[j]; - } - - FFT(FFTInputR, FFTInputI, FFTSize, INVERSE); // Inverse FFT - for (j = 0; j < NumTaps; j++) { - FirCoeff[j] = FFTInputR[j]; - } - - delete[] FFTInputR; - delete[] FFTInputI; -} -//----------------------------------------------------------------------------- - -} //namedspace diff --git a/kitiirfir/FIRFilterCode.h b/kitiirfir/FIRFilterCode.h deleted file mode 100644 index ee60fee02..000000000 --- a/kitiirfir/FIRFilterCode.h +++ /dev/null @@ -1,36 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef FIRFilterCodeH -#define FIRFilterCodeH -#include "FFTCode.h" // For the definition of TWindowType -//--------------------------------------------------------------------------- - -#define MAX_NUMTAPS 256 -#define M_2PI 6.28318530717958647692 -#define NUM_FREQ_ERR_PTS 1000 // these are only used in the FIRFreqError function. -#define dNUM_FREQ_ERR_PTS 1000.0 - -namespace kitiirfir -{ - -enum TFIRPassTypes { - firLPF, firHPF, firBPF, firNOTCH, firALLPASS, firNOT_FIR -}; - -void FilterWithFIR(double *FirCoeff, int NumTaps, double *Signal, - double *FilteredSignal, int NumSigPts); -void FilterWithFIR2(double *FirCoeff, int NumTaps, double *Signal, - double *FilteredSignal, int NumSigPts); -void RectWinFIR(double *FirCoeff, int NumTaps, TFIRPassTypes PassType, - double OmegaC, double BW); -void WindowData(double *Data, int N, TWindowType WindowType, double Alpha, - double Beta, bool UnityGain); -void FIRFreqError(double *Coeff, int NumTaps, int PassType, double *OmegaC, - double *BW); -void FIRFilterWindow(double *FIRCoeff, int N, TWindowType WindowType, - double Beta); -void AdjustDelay(double *FirCoeff, int NumTaps, double Delay); - -} // namespace - -#endif diff --git a/kitiirfir/FreqSamplingCode.cpp b/kitiirfir/FreqSamplingCode.cpp deleted file mode 100644 index 64565ec8c..000000000 --- a/kitiirfir/FreqSamplingCode.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include -#include -#include "FreqSamplingCode.h" -#include "FIRFilterCode.h" // for the definition of TFIRPassTypes -#include "FFTCode.h" -#include "LowPassPrototypes.h" - -namespace kitiirfir -{ - -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - If you find a problem, please leave a note at: - http://www.iowahills.com/feedbackcomments.html - May 1, 2016 - - This code generates an FIR filter with the frequency over-sampling method. By this we - mean that we always sample the frequency domain at least 1024 times (a large power of - 2 suitable for an FFT) even though we typically want a relatively small number of FIR - coefficients (< 50). - - Most authors use N frequency samples to generate N taps. While valid, this tends to loose a - significant amount of frequency domain information if the tap count is small. - - Using a large number of samples will generate a filter equivalent to those generated with - the equations for a classic Rectangular Windowed FIR filter. Those equations were derived - by using an infinite number of frequency samples, which is to say, performing an integral. - - To see how well this works, use this code to sample a simple rectanglular low pass response - and compare the results to the coefficients generated by Windowed FIR code used the - BasicFIR() function in FIRFilterCode.cpp - - See the example code in FilterKitMain for an example of using SampledFreqFIR(). - This function is called after the HofSReal array is filled with the desired magnitude response. - The only trick to this method is getting the phase set correctly. There are two aspects to - setting the phase. Using the correct slope, which is the same for all filters, but does - depend on even or odd tap counts. And we must ensure that the phase value is correct at - Omega = 0.0 and Pi. - - If the filter has a low pass response (magnitude != 0.0 at DC), then the phase at Omega=0 must - be zero. If the filter has a high pass response (magnitude != 0.0 at Pi), then the phase - must be zero of Omega = Pi. - - A band pass filter, which has neither a low or high pass response, can have any phase value at - Omega = 0 and Pi. But a Notch filter, which has both a low and high pass response, must have - zero phase at both zero and Pi. The code below should make this more clear. - - NumTaps Number of FIR taps - FirCoeff The output array for the FIR coefficients. - HofSReal The array containing the desired magnitude response. - HofSImag The imag part of the response (zero when this function is called). - OmegaC The center frequency. (Only needed for notch filters.) - PassType firLPF, firHPF, firBPF, firNOTCH Needed to set the phase properly. (defined in FIRFilterCode.h) - */ - -void SampledFreqFIR(int NumTaps, double *FirCoeff, double *HofSReal, - double *HofSImag, double OmegaC, TFIRPassTypes PassType) { - int j, CenterJ, NumSamples, StartJ; - double dNumSamples, RadPerSample, Arg; - NumSamples = NUM_POS_FREQ_SAMPLES; - dNumSamples = (double) NumSamples; - - // Limit test NumTaps - if (NumTaps > MAX_NUMTAPS) - NumTaps = MAX_NUMTAPS; - if (NumTaps > 2 * NUM_POS_FREQ_SAMPLES) - NumTaps = 2 * NUM_POS_FREQ_SAMPLES; - - // Set the slope of the phase. - RadPerSample = -M_PI_2 * (2.0 * dNumSamples - 1.0) / dNumSamples; // Even tap count. - if (NumTaps % 2 == 1) - RadPerSample = -M_PI; // Odd tap count. - - // Set the phase according to the type of response. - switch (PassType) { - case firLPF: // Low pass and band pass phase = 0 at DC - case firBPF: - for (j = 0; j < NumSamples; j++) { - Arg = RadPerSample * (double) j; // For band pass filters ONLY, an arbitrary amount of phase can be added to Arg. e.g. Add Pi/2 to generate a Hilbert filter, or +/- Pi/4 to 2 different filters to generate a pair of 45 degree Hilberts. - HofSImag[j] = HofSReal[j] * sin(Arg); - HofSReal[j] = HofSReal[j] * cos(Arg); - } - break; - - case firHPF: // High pass phase = 0 at Pi - for (j = NumSamples; j >= 0; j--) { - Arg = RadPerSample * (double) (j - NumSamples); - HofSImag[j] = HofSReal[j] * sin(Arg); - HofSReal[j] = HofSReal[j] * cos(Arg); - } - break; - - case firNOTCH: // Notch phase = 0 at DC and Pi - CenterJ = (int) (OmegaC * dNumSamples); - for (j = 0; j <= CenterJ; j++) { - Arg = RadPerSample * (double) j; - HofSImag[j] = HofSReal[j] * sin(Arg); - HofSReal[j] = HofSReal[j] * cos(Arg); - } - for (j = NumSamples; j >= CenterJ; j--) { - Arg = RadPerSample * (double) (j - NumSamples); - HofSImag[j] = HofSReal[j] * sin(Arg); - HofSReal[j] = HofSReal[j] * cos(Arg); - } - break; - case firALLPASS: - case firNOT_FIR: - default: - break; - } - - // Fill the negative frequency bins of HofS with the conjugate of HofS for the FFT. - for (j = 1; j < NumSamples; j++) - HofSReal[2 * NumSamples - j] = HofSReal[j]; - for (j = 1; j < NumSamples; j++) - HofSImag[2 * NumSamples - j] = -HofSImag[j]; - - // The Fourier Transform requires the center freq bins to be 0 for LPF and BPF, 1 for HPF and Notch - if (PassType == firLPF || PassType == firBPF) { - HofSReal[NumSamples] = 0.0; - HofSImag[NumSamples] = 0.0; - } else { - HofSReal[NumSamples] = 1.0; - HofSImag[NumSamples] = 0.0; - } - - // Do an inverse FFT on HofS to generate the impulse response. On return, HofSImag will be zero. - FFT(HofSReal, HofSImag, 2 * NumSamples, INVERSE); - - // We just generated an impulse response that is 2*NumSamples long. Since we used linear phase - // the response will be symmetric about the center. In general, we only need a small number - // of these taps, so we use the taps from the center of HofSReal, starting at StartJ. - // We also need to scale the FFT's output by the size of the FFT. - StartJ = NumSamples - NumTaps / 2; - for (j = 0; j < NumTaps; j++) - FirCoeff[j] = HofSReal[StartJ + j] / (2.0 * dNumSamples); - -} -//--------------------------------------------------------------------------- - -// This function shows how to sample an analog transfer function H(s) to generate an FIR filter. -// We didn't put much effort into this example because, in general, an analog prototype generates -// a rather poor FIR filter in the sense that it requires such a large number of taps to realize -// the response. For a discussion on this, see this article: -// http://iowahills.com/B2PolynomialFIRFilters.html -// In this example, we generate an FIR low pass from an Inverse Chebyshev low pass prototype. -// We sample the low pass prototype H(s) at frequencies determined by the bilinear transform. -void SampledFreqAnalog(int NumTaps, double *FirCoeff, double *HofSReal, - double *HofSImag, double OmegaC) { - int j, k, NumSamples; - double dNumSamples, Omega, Omega0; - std::complex s, H; - TFIRPassTypes PassType = firLPF; - NumSamples = NUM_POS_FREQ_SAMPLES; - dNumSamples = (double) NumSamples; - - // Limit test NumTaps - if (NumTaps > MAX_NUMTAPS) - NumTaps = MAX_NUMTAPS; - if (NumTaps > 2 * NUM_POS_FREQ_SAMPLES) - NumTaps = 2 * NUM_POS_FREQ_SAMPLES; - - // Define the low pass filter prototype - TLowPassParams LPFProto; // defined in LowPassPrototypes.h - TSPlaneCoeff SCoeff; // defined in LowPassPrototypes.h - LPFProto.ProtoType = INVERSE_CHEBY; // BUTTERWORTH, CHEBYSHEV, GAUSSIAN, BESSEL, ADJUSTABLE, INVERSE_CHEBY, PAPOULIS, ELLIPTIC (defined in LowPassPrototypes.h) - LPFProto.NumPoles = 8; // 1 <= NumPoles <= 12, 15, 20 Depending on the filter. - LPFProto.Ripple = 0.25; // 0.0 <= Ripple <= 1.0 dB Chebyshev and Elliptic (less for high order Chebyshev). - LPFProto.StopBanddB = 60.0; // 20 <= StopBand <= 120 dB Inv Cheby and Elliptic - LPFProto.Gamma = 0.0; // -1.0 <= Gamma <= 1.0 Adjustable Gauss Controls the transition BW. - - // Get the prototype filter's 2nd order s plane coefficients. - SCoeff = CalcLowPassProtoCoeff(LPFProto); - - // Evaluate the prototype's H(s) - Omega0 = 1.0 / tan(OmegaC * M_PI_2); // This sets the corner frequency. - for (j = 0; j < NumSamples; j++) { - Omega = Omega0 * tan(M_PI_2 * (double) j / dNumSamples); // Frequencies per the bilinear transform. - s = std::complex(0.0, Omega); - H = std::complex(1.0, 0.0); - for (k = 0; k < SCoeff.NumSections; k++) { - H *= SCoeff.N2[k] * s * s + SCoeff.N1[k] * s + SCoeff.N0[k]; // The numerator - H /= SCoeff.D2[k] * s * s + SCoeff.D1[k] * s + SCoeff.D0[k]; // The denominator - H *= SCoeff.D0[k] / SCoeff.N0[k]; // The gain constants. - } - HofSReal[j] = H.real(); // We need to do this for the FFT, which uses real arrays. - HofSImag[j] = H.imag(); - } - - // Fill the negative frequency bins of HofS with the conjugate of HofS for the FFT. - for (j = 1; j < NumSamples; j++) - HofSReal[2 * NumSamples - j] = HofSReal[j]; - for (j = 1; j < NumSamples; j++) - HofSImag[2 * NumSamples - j] = -HofSImag[j]; - - // The Fourier Transform requires the center freq bins to be 0 for LPF and BPF, 1 for HPF and Notch - if (PassType == firLPF || PassType == firBPF) { - HofSReal[NumSamples] = 0.0; - HofSImag[NumSamples] = 0.0; - } else { - HofSReal[NumSamples] = 1.0; - HofSImag[NumSamples] = 0.0; - } - - // Do an inverse FFT on HofS to generate the impulse response. On return, HofSImag will be zero. - FFT(HofSReal, HofSImag, 2 * NumSamples, INVERSE); - - // We just generated an impulse response that is 2*NumSamples long. We can use as many of these - // as we like, but if you look at HofSReal you will see that the tail of the response goes to - // zero rather quickly. The desired part of impulse response is at the beginning of HofSReal - // instead of the center because H(s) didn't have linear phase (more like minimum phase). - - // Take the taps from the start of HofSReal and scale by the size of the FFT. - for (j = 0; j < NumTaps; j++) - FirCoeff[j] = HofSReal[j] / (2.0 * dNumSamples); - - // Most FIR responses benefit from the application of a window to reduce the effects of - // truncating the impulse response. But a typical window, such as the Kaiser, can't be used - // because this impulse response isn't symmetric about the center tap. - // A Half Cosine window works quite nicely with these types of responses. - for (j = 0; j < NumTaps; j++) - FirCoeff[j] = FirCoeff[j] * cos(M_PI_2 * j / (double) NumTaps); -} - -//--------------------------------------------------------------------------- - -} // namespace diff --git a/kitiirfir/FreqSamplingCode.h b/kitiirfir/FreqSamplingCode.h deleted file mode 100644 index 28c1784b7..000000000 --- a/kitiirfir/FreqSamplingCode.h +++ /dev/null @@ -1,17 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef FreqSamplingCodeH -#define FreqSamplingCodeH -#include "FIRFilterCode.h" // for the definition of TFIRPassTypes - -#define NUM_POS_FREQ_SAMPLES 1024 // needs to be 1024 for the kit test app - -namespace kitiirfir -{ - -void SampledFreqFIR(int NumTaps, double *FirCoeff, double *HofSReal, double *HofSImag, double OmegaC, TFIRPassTypes PassType); -void SampledFreqAnalog(int NumTaps, double *FirCoeff, double *HofSReal, double *HofSImag, double OmegaC); - -} // namespace - -#endif diff --git a/kitiirfir/IIRFilterCode.cpp b/kitiirfir/IIRFilterCode.cpp deleted file mode 100644 index f700eaca0..000000000 --- a/kitiirfir/IIRFilterCode.cpp +++ /dev/null @@ -1,526 +0,0 @@ -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - If you find a problem, please leave a note at: - http://www.iowahills.com/feedbackcomments.html - May 1, 2016 - - ShowMessage is a C++ Builder function, and it usage has been commented out. - If you are using C++ Builder, include vcl.h for ShowMessage. - Otherwise replace ShowMessage with something appropriate for your compiler. - - See the FilterKitMain.cpp file for an example on how to use this code. - */ - -#include "IIRFilterCode.h" -#include "PFiftyOneRevE.h" -#include "LowPassPrototypes.h" -#include -#include - -namespace kitiirfir -{ - -//--------------------------------------------------------------------------- -/* - This calculates the coefficients for IIR filters from a set of 2nd order s plane coefficients - which are obtained by calling CalcLowPassProtoCoeff() in LowPassPrototypes.cpp. - The s plane filters are frequency scaled so their 3 dB frequency is at s = omega = 1 rad/sec. - The poles and zeros are also ordered in a manner appropriate for IIR filters. - For a derivation of the formulas used here, see the IIREquationDerivations.pdf - This shows how the various poly coefficients are defined. - H(s) = ( Ds^2 + Es + F ) / ( As^2 + Bs + C ) - H(z) = ( b2z^2 + b1z + b0 ) / ( a2z^2 + a1z + a0 ) - */ -TIIRCoeff CalcIIRFilterCoeff(TIIRFilterParams IIRFilt) { - int j, k; - double Scalar, SectionGain, Coeff[5]; - double A, B, C, D, E, F, T, Q, Arg; - double a2[ARRAY_DIM], a1[ARRAY_DIM], a0[ARRAY_DIM]; - double b2[ARRAY_DIM], b1[ARRAY_DIM], b0[ARRAY_DIM]; - std::complex Roots[5]; - - TIIRCoeff IIR; // Gets returned by this function. - TLowPassParams LowPassFilt; // Passed to the CalcLowPassProtoCoeff() function. - TSPlaneCoeff SPlaneCoeff; // Filled by the CalcLowPassProtoCoeff() function. - - // We can set the TLowPassParams variables directly from the TIIRFilterParams variables. - LowPassFilt.ProtoType = IIRFilt.ProtoType; - LowPassFilt.NumPoles = IIRFilt.NumPoles; - LowPassFilt.Ripple = IIRFilt.Ripple; - LowPassFilt.Gamma = IIRFilt.Gamma; - LowPassFilt.StopBanddB = IIRFilt.StopBanddB; - - // Get the low pass prototype 2nd order s plane coefficients. - SPlaneCoeff = CalcLowPassProtoCoeff(LowPassFilt); - - // Init the IIR structure. - for (j = 0; j < ARRAY_DIM; j++) { - IIR.a0[j] = 0.0; - IIR.b0[j] = 0.0; - IIR.a1[j] = 0.0; - IIR.b1[j] = 0.0; - IIR.a2[j] = 0.0; - IIR.b2[j] = 0.0; - IIR.a3[j] = 0.0; - IIR.b3[j] = 0.0; - IIR.a4[j] = 0.0; - IIR.b4[j] = 0.0; - } - - // Set the number of IIR filter sections we will be generating. - IIR.NumSections = (IIRFilt.NumPoles + 1) / 2; - if (IIRFilt.IIRPassType == iirBPF || IIRFilt.IIRPassType == iirNOTCH) - IIR.NumSections = IIRFilt.NumPoles; - - // For All Pass filters, the numerator is set to the denominator values as shown here. - // If the prototype was an Inv Cheby or Elliptic, the S plane numerator is discarded. - // Use the Gauss as the prototype for the best all pass results (most linear phase). - // The all pass H(s) = ( As^2 - Bs + C ) / ( As^2 + Bs + C ) - if (IIRFilt.IIRPassType == iirALLPASS) { - for (j = 0; j < SPlaneCoeff.NumSections; j++) { - SPlaneCoeff.N2[j] = SPlaneCoeff.D2[j]; - SPlaneCoeff.N1[j] = -SPlaneCoeff.D1[j]; - SPlaneCoeff.N0[j] = SPlaneCoeff.D0[j]; - } - } - - // T sets the IIR filter's corner frequency, or center freqency. - // The Bilinear transform is defined as: s = 2/T * tan(Omega/2) = 2/T * (1 - z)/(1 + z) - T = 2.0 * tan(IIRFilt.OmegaC * M_PI_2); - Q = 1.0 + IIRFilt.OmegaC; // Q is used for band pass and notch filters. - if (Q > 1.95) - Q = 1.95; - Q = 0.8 * tan(Q * M_PI_4); // This is a correction factor for Q. - Q = IIRFilt.OmegaC / IIRFilt.BW / Q; // This is the corrected Q. - - // Calc the IIR coefficients. - // SPlaneCoeff.NumSections is the number of 1st and 2nd order s plane factors. - k = 0; - for (j = 0; j < SPlaneCoeff.NumSections; j++) { - A = SPlaneCoeff.D2[j]; // We use A - F to make the code easier to read. - B = SPlaneCoeff.D1[j]; - C = SPlaneCoeff.D0[j]; - D = SPlaneCoeff.N2[j]; - E = SPlaneCoeff.N1[j]; // N1 is always zero, except for the all pass. Consequently, the equations below can be simplified a bit by removing E. - F = SPlaneCoeff.N0[j]; - - // b's are the numerator a's are the denominator - if (IIRFilt.IIRPassType == iirLPF || IIRFilt.IIRPassType == iirALLPASS) // Low Pass and All Pass - { - if (A == 0.0 && D == 0.0) // 1 pole case - { - Arg = (2.0 * B + C * T); - IIR.a2[j] = 0.0; - IIR.a1[j] = (-2.0 * B + C * T) / Arg; - IIR.a0[j] = 1.0; - - IIR.b2[j] = 0.0; - IIR.b1[j] = (-2.0 * E + F * T) / Arg * C / F; - IIR.b0[j] = (2.0 * E + F * T) / Arg * C / F; - } else // 2 poles - { - Arg = (4.0 * A + 2.0 * B * T + C * T * T); - IIR.a2[j] = (4.0 * A - 2.0 * B * T + C * T * T) / Arg; - IIR.a1[j] = (2.0 * C * T * T - 8.0 * A) / Arg; - IIR.a0[j] = 1.0; - - // With all pole filters, our LPF numerator is (z+1)^2, so all our Z Plane zeros are at -1 - IIR.b2[j] = (4.0 * D - 2.0 * E * T + F * T * T) / Arg * C / F; - IIR.b1[j] = (2.0 * F * T * T - 8.0 * D) / Arg * C / F; - IIR.b0[j] = (4 * D + F * T * T + 2.0 * E * T) / Arg * C / F; - } - } - - if (IIRFilt.IIRPassType == iirHPF) // High Pass - { - if (A == 0.0 && D == 0.0) // 1 pole - { - Arg = 2.0 * C + B * T; - IIR.a2[j] = 0.0; - IIR.a1[j] = (B * T - 2.0 * C) / Arg; - IIR.a0[j] = 1.0; - - IIR.b2[j] = 0.0; - IIR.b1[j] = (E * T - 2.0 * F) / Arg * C / F; - IIR.b0[j] = (E * T + 2.0 * F) / Arg * C / F; - } else // 2 poles - { - Arg = A * T * T + 4.0 * C + 2.0 * B * T; - IIR.a2[j] = (A * T * T + 4.0 * C - 2.0 * B * T) / Arg; - IIR.a1[j] = (2.0 * A * T * T - 8.0 * C) / Arg; - IIR.a0[j] = 1.0; - - // With all pole filters, our HPF numerator is (z-1)^2, so all our Z Plane zeros are at 1 - IIR.b2[j] = (D * T * T - 2.0 * E * T + 4.0 * F) / Arg * C / F; - IIR.b1[j] = (2.0 * D * T * T - 8.0 * F) / Arg * C / F; - IIR.b0[j] = (D * T * T + 4.0 * F + 2.0 * E * T) / Arg * C / F; - } - } - - if (IIRFilt.IIRPassType == iirBPF) // Band Pass - { - if (A == 0.0 && D == 0.0) // 1 pole - { - Arg = 4.0 * B * Q + 2.0 * C * T + B * Q * T * T; - a2[k] = (B * Q * T * T + 4.0 * B * Q - 2.0 * C * T) / Arg; - a1[k] = (2.0 * B * Q * T * T - 8.0 * B * Q) / Arg; - a0[k] = 1.0; - - b2[k] = (E * Q * T * T + 4.0 * E * Q - 2.0 * F * T) / Arg * C - / F; - b1[k] = (2.0 * E * Q * T * T - 8.0 * E * Q) / Arg * C / F; - b0[k] = (4.0 * E * Q + 2.0 * F * T + E * Q * T * T) / Arg * C - / F; - k++; - } else //2 Poles - { - IIR.a4[j] = (16.0 * A * Q * Q + A * Q * Q * T * T * T * T - + 8.0 * A * Q * Q * T * T - 2.0 * B * Q * T * T * T - - 8.0 * B * Q * T + 4.0 * C * T * T) * F; - IIR.a3[j] = (4.0 * T * T * T * T * A * Q * Q - - 4.0 * Q * T * T * T * B + 16.0 * Q * B * T - - 64.0 * A * Q * Q) * F; - IIR.a2[j] = (96.0 * A * Q * Q - 16.0 * A * Q * Q * T * T - + 6.0 * A * Q * Q * T * T * T * T - 8.0 * C * T * T) - * F; - IIR.a1[j] = (4.0 * T * T * T * T * A * Q * Q - + 4.0 * Q * T * T * T * B - 16.0 * Q * B * T - - 64.0 * A * Q * Q) * F; - IIR.a0[j] = (16.0 * A * Q * Q + A * Q * Q * T * T * T * T - + 8.0 * A * Q * Q * T * T + 2.0 * B * Q * T * T * T - + 8.0 * B * Q * T + 4.0 * C * T * T) * F; - - // With all pole filters, our BPF numerator is (z-1)^2 * (z+1)^2 so the zeros come back as +/- 1 pairs - IIR.b4[j] = (8.0 * D * Q * Q * T * T - 8.0 * E * Q * T - + 16.0 * D * Q * Q - 2.0 * E * Q * T * T * T - + D * Q * Q * T * T * T * T + 4.0 * F * T * T) * C; - IIR.b3[j] = (16.0 * E * Q * T - 4.0 * E * Q * T * T * T - - 64.0 * D * Q * Q + 4.0 * D * Q * Q * T * T * T * T) - * C; - IIR.b2[j] = (96.0 * D * Q * Q - 8.0 * F * T * T - + 6.0 * D * Q * Q * T * T * T * T - - 16.0 * D * Q * Q * T * T) * C; - IIR.b1[j] = (4.0 * D * Q * Q * T * T * T * T - 64.0 * D * Q * Q - + 4.0 * E * Q * T * T * T - 16.0 * E * Q * T) * C; - IIR.b0[j] = (16.0 * D * Q * Q + 8.0 * E * Q * T - + 8.0 * D * Q * Q * T * T + 2.0 * E * Q * T * T * T - + 4.0 * F * T * T + D * Q * Q * T * T * T * T) * C; - - // T = 2 makes these values approach 0.0 (~ 1.0E-12) The root solver needs 0.0 for numerical reasons. - if (fabs(T - 2.0) < 0.0005) { - IIR.a3[j] = 0.0; - IIR.a1[j] = 0.0; - IIR.b3[j] = 0.0; - IIR.b1[j] = 0.0; - } - - // We now have a 4th order poly in the form a4*s^4 + a3*s^3 + a2*s^2 + a2*s + a0 - // We find the roots of this so we can form two 2nd order polys. - Coeff[0] = IIR.a4[j]; - Coeff[1] = IIR.a3[j]; - Coeff[2] = IIR.a2[j]; - Coeff[3] = IIR.a1[j]; - Coeff[4] = IIR.a0[j]; - FindRoots(4, Coeff, Roots); - - // In effect, the root finder scales the poly by 1/a4 so we have to apply this factor back into - // the two 2nd order equations we are forming. - Scalar = sqrt(fabs(IIR.a4[j])); - - // Form the two 2nd order polys from the roots. - a2[k] = Scalar; - a1[k] = -(Roots[0] + Roots[1]).real() * Scalar; - a0[k] = (Roots[0] * Roots[1]).real() * Scalar; - k++; - a2[k] = Scalar; - a1[k] = -(Roots[2] + Roots[3]).real() * Scalar; - a0[k] = (Roots[2] * Roots[3]).real() * Scalar; - k--; - - // Now do the same with the numerator. - Coeff[0] = IIR.b4[j]; - Coeff[1] = IIR.b3[j]; - Coeff[2] = IIR.b2[j]; - Coeff[3] = IIR.b1[j]; - Coeff[4] = IIR.b0[j]; - - if (IIRFilt.ProtoType == INVERSE_CHEBY - || IIRFilt.ProtoType == ELLIPTIC) { - FindRoots(4, Coeff, Roots); - } else // With all pole filters (Butter, Cheb, etc), we know we have these 4 real roots. The root finder won't necessarily pair real roots the way we need, so rather than compute these, we simply set them. - { - Roots[0] = std::complex(-1.0, 0.0); - Roots[1] = std::complex(1.0, 0.0); - Roots[2] = std::complex(-1.0, 0.0); - Roots[3] = std::complex(1.0, 0.0); - } - - Scalar = sqrt(fabs(IIR.b4[j])); - - b2[k] = Scalar; - if (IIRFilt.ProtoType == INVERSE_CHEBY - || IIRFilt.ProtoType == ELLIPTIC) { - b1[k] = -(Roots[0] + Roots[1]).real() * Scalar; // = 0.0 - } else // else the prototype is an all pole filter - { - b1[k] = 0.0; // b1 = 0 for all pole filters, but the addition above won't always equal zero exactly. - } - b0[k] = (Roots[0] * Roots[1]).real() * Scalar; - - k++; - - b2[k] = Scalar; - if (IIRFilt.ProtoType == INVERSE_CHEBY - || IIRFilt.ProtoType == ELLIPTIC) { - b1[k] = -(Roots[2] + Roots[3]).real() * Scalar; - } else // All pole - { - b1[k] = 0.0; - } - b0[k] = (Roots[2] * Roots[3]).real() * Scalar; - k++; - // Go below to see where we store these 2nd order polys back into IIR - } - } - - if (IIRFilt.IIRPassType == iirNOTCH) // Notch - { - if (A == 0.0 && D == 0.0) // 1 pole - { - Arg = 2.0 * B * T + C * Q * T * T + 4.0 * C * Q; - a2[k] = (4.0 * C * Q - 2.0 * B * T + C * Q * T * T) / Arg; - a1[k] = (2.0 * C * Q * T * T - 8.0 * C * Q) / Arg; - a0[k] = 1.0; - - b2[k] = (4.0 * F * Q - 2.0 * E * T + F * Q * T * T) / Arg * C - / F; - b1[k] = (2.0 * F * Q * T * T - 8.0 * F * Q) / Arg * C / F; - b0[k] = (2.0 * E * T + F * Q * T * T + 4.0 * F * Q) / Arg * C - / F; - k++; - } else { - IIR.a4[j] = (4.0 * A * T * T - 2.0 * B * T * T * T * Q - + 8.0 * C * Q * Q * T * T - 8.0 * B * T * Q - + C * Q * Q * T * T * T * T + 16.0 * C * Q * Q) * -F; - IIR.a3[j] = (16.0 * B * T * Q + 4.0 * C * Q * Q * T * T * T * T - - 64.0 * C * Q * Q - 4.0 * B * T * T * T * Q) * -F; - IIR.a2[j] = (96.0 * C * Q * Q - 8.0 * A * T * T - - 16.0 * C * Q * Q * T * T - + 6.0 * C * Q * Q * T * T * T * T) * -F; - IIR.a1[j] = (4.0 * B * T * T * T * Q - 16.0 * B * T * Q - - 64.0 * C * Q * Q + 4.0 * C * Q * Q * T * T * T * T) - * -F; - IIR.a0[j] = (4.0 * A * T * T + 2.0 * B * T * T * T * Q - + 8.0 * C * Q * Q * T * T + 8.0 * B * T * Q - + C * Q * Q * T * T * T * T + 16.0 * C * Q * Q) * -F; - - // Our Notch Numerator isn't simple. [ (4+T^2)*z^2 - 2*(4-T^2)*z + (4+T^2) ]^2 - IIR.b4[j] = (2.0 * E * T * T * T * Q - 4.0 * D * T * T - - 8.0 * F * Q * Q * T * T + 8.0 * E * T * Q - - 16.0 * F * Q * Q - F * Q * Q * T * T * T * T) * C; - IIR.b3[j] = (64.0 * F * Q * Q + 4.0 * E * T * T * T * Q - - 16.0 * E * T * Q - 4.0 * F * Q * Q * T * T * T * T) - * C; - IIR.b2[j] = (8.0 * D * T * T - 96.0 * F * Q * Q - + 16.0 * F * Q * Q * T * T - - 6.0 * F * Q * Q * T * T * T * T) * C; - IIR.b1[j] = (16.0 * E * T * Q - 4.0 * E * T * T * T * Q - + 64.0 * F * Q * Q - 4.0 * F * Q * Q * T * T * T * T) - * C; - IIR.b0[j] = (-4.0 * D * T * T - 2.0 * E * T * T * T * Q - - 8.0 * E * T * Q - 8.0 * F * Q * Q * T * T - - F * Q * Q * T * T * T * T - 16.0 * F * Q * Q) * C; - - // T = 2 (OmegaC = 0.5) makes these values approach 0.0 (~ 1.0E-12). The root solver wants 0.0 for numerical reasons. - if (fabs(T - 2.0) < 0.0005) { - IIR.a3[j] = 0.0; - IIR.a1[j] = 0.0; - IIR.b3[j] = 0.0; - IIR.b1[j] = 0.0; - } - - // We now have a 4th order poly in the form a4*s^4 + a3*s^3 + a2*s^2 + a2*s + a0 - // We find the roots of this so we can form two 2nd order polys. - Coeff[0] = IIR.a4[j]; - Coeff[1] = IIR.a3[j]; - Coeff[2] = IIR.a2[j]; - Coeff[3] = IIR.a1[j]; - Coeff[4] = IIR.a0[j]; - - // In effect, the root finder scales the poly by 1/a4 so we have to apply this factor back into - // the two 2nd order equations we are forming. - FindRoots(4, Coeff, Roots); - Scalar = sqrt(fabs(IIR.a4[j])); - a2[k] = Scalar; - a1[k] = -(Roots[0] + Roots[1]).real() * Scalar; - a0[k] = (Roots[0] * Roots[1]).real() * Scalar; - - k++; - a2[k] = Scalar; - a1[k] = -(Roots[2] + Roots[3]).real() * Scalar; - a0[k] = (Roots[2] * Roots[3]).real() * Scalar; - k--; - - // Now do the same with the numerator. - Coeff[0] = IIR.b4[j]; - Coeff[1] = IIR.b3[j]; - Coeff[2] = IIR.b2[j]; - Coeff[3] = IIR.b1[j]; - Coeff[4] = IIR.b0[j]; - FindRoots(4, Coeff, Roots); - - Scalar = sqrt(fabs(IIR.b4[j])); - b2[k] = Scalar; - b1[k] = -(Roots[0] + Roots[1]).real() * Scalar; - b0[k] = (Roots[0] * Roots[1]).real() * Scalar; - - k++; - b2[k] = Scalar; - b1[k] = -(Roots[2] + Roots[3]).real() * Scalar; - b0[k] = (Roots[2] * Roots[3]).real() * Scalar; - k++; - } - } - } - - if (IIRFilt.IIRPassType == iirBPF || IIRFilt.IIRPassType == iirNOTCH) { - // In the calcs above for the BPF and Notch, we didn't set a0=1, so we do it here. - for (j = 0; j < IIR.NumSections; j++) { - b2[j] /= a0[j]; - b1[j] /= a0[j]; - b0[j] /= a0[j]; - a2[j] /= a0[j]; - a1[j] /= a0[j]; - a0[j] = 1.0; - } - - for (j = 0; j < IIR.NumSections; j++) { - IIR.a0[j] = a0[j]; - IIR.a1[j] = a1[j]; - IIR.a2[j] = a2[j]; - IIR.b0[j] = b0[j]; - IIR.b1[j] = b1[j]; - IIR.b2[j] = b2[j]; - } - } - - // Adjust the b's or a0 for the desired Gain. - SectionGain = pow(10.0, IIRFilt.dBGain / 20.0); - SectionGain = pow(SectionGain, 1.0 / (double) IIR.NumSections); - for (j = 0; j < IIR.NumSections; j++) { - IIR.b0[j] *= SectionGain; - IIR.b1[j] *= SectionGain; - IIR.b2[j] *= SectionGain; - // This is an alternative to adjusting the b's - // IIR.a0[j] = SectionGain; - } - - return (IIR); -} - -//--------------------------------------------------------------------------- - -// This code implements an IIR filter as a Form 1 Biquad. -// It uses 2 sets of shift registers, RegX on the input side and RegY on the output side. -// There are many ways to implement an IIR filter, some very good, and some extremely bad. -// For numerical reasons, a Form 1 Biquad implementation is among the best. -void FilterWithIIR(TIIRCoeff IIRCoeff, double *Signal, double *FilteredSignal, - int NumSigPts) { - double y; - int j, k; - - for (j = 0; j < NumSigPts; j++) { - k = 0; - y = SectCalc(j, k, Signal[j], IIRCoeff); - for (k = 1; k < IIRCoeff.NumSections; k++) { - y = SectCalc(j, k, y, IIRCoeff); - } - FilteredSignal[j] = y; - } - -} -//--------------------------------------------------------------------------- - -// This gets used with the function above, FilterWithIIR() -// Note the use of MaxRegVal to avoid a math overflow condition. -double SectCalc(int j, int k, double x, TIIRCoeff IIRCoeff) { - double y, CenterTap; - static double RegX1[ARRAY_DIM], RegX2[ARRAY_DIM], RegY1[ARRAY_DIM], - RegY2[ARRAY_DIM], MaxRegVal; - static bool MessageShown = false; - - // Zero the regiisters on the 1st call or on an overflow condition. The overflow limit used - // here is small for double variables, but a filter that reaches this threshold is broken. - if ((j == 0 && k == 0) || MaxRegVal > OVERFLOW_LIMIT) { - if (MaxRegVal > OVERFLOW_LIMIT && !MessageShown) { - // ShowMessage("ERROR: Math Over Flow in IIR Section Calc. \nThe register values exceeded 1.0E20 \n"); - MessageShown = true; // So this message doesn't get shown thousands of times. - } - - MaxRegVal = 1.0E-12; - for (int i = 0; i < ARRAY_DIM; i++) { - RegX1[i] = 0.0; - RegX2[i] = 0.0; - RegY1[i] = 0.0; - RegY2[i] = 0.0; - } - } - - CenterTap = x * IIRCoeff.b0[k] + IIRCoeff.b1[k] * RegX1[k] - + IIRCoeff.b2[k] * RegX2[k]; - y = IIRCoeff.a0[k] * CenterTap - IIRCoeff.a1[k] * RegY1[k] - - IIRCoeff.a2[k] * RegY2[k]; - - RegX2[k] = RegX1[k]; - RegX1[k] = x; - RegY2[k] = RegY1[k]; - RegY1[k] = y; - - // MaxRegVal is used to prevent overflow. Overflow seldom occurs, but will - // if the filter has faulty coefficients. MaxRegVal is usually less than 100.0 - if (fabs(CenterTap) > MaxRegVal) - MaxRegVal = fabs(CenterTap); - if (fabs(y) > MaxRegVal) - MaxRegVal = fabs(y); - return (y); -} -//--------------------------------------------------------------------------- - -// This function calculates the frequency response of an IIR filter. -// Probably the easiest way to determine the frequency response of an IIR filter is to send -// an impulse through the filter and do an FFT on the output. This method does a DFT on -// the coefficients of each biquad section. The results from the cascaded sections are -// then multiplied together. - -// This approach works better than an FFT when the filter is very narrow. To analyze highly selective -// filters with an FFT can require a very large number of points, which can be quite cumbersome. -// This approach allows you to set the range of frequencies to be analyzed by modifying the statement -// Arg = M_PI * (double)j / (double)NumPts; . -void IIRFreqResponse(TIIRCoeff IIR, int NumSections, double *RealHofZ, - double *ImagHofZ, int NumPts) { - int j, n; - double Arg; - std::complex z1, z2, HofZ, Denom; - for (j = 0; j < NumPts; j++) { - Arg = M_PI * (double) j / (double) NumPts; - z1 = std::complex(cos(Arg), -sin(Arg)); // z = e^(j*omega) - z2 = z1 * z1; // z squared - - HofZ = std::complex(1.0, 0.0); - for (n = 0; n < NumSections; n++) { - HofZ *= IIR.a0[n]; // This can be in the denominator, but only if a0=1. a0 can be other than 1.0 to adjust the filter's gain. See the bottom of the CalcIIRFilterCoeff() function. - HofZ *= IIR.b0[n] + IIR.b1[n] * z1 + IIR.b2[n] * z2; // Numerator - Denom = 1.0 + IIR.a1[n] * z1 + IIR.a2[n] * z2; // Denominator - if (std::abs(Denom) < 1.0E-12) - Denom = 1.0E-12; // A pole on the unit circle would cause this to be zero, so this should never happen. It could happen however if the filter also has a zero at this frequency. Then H(z) needs to be determined by L'Hopitals rule at this freq. - HofZ /= Denom; - } - RealHofZ[j] = HofZ.real(); - ImagHofZ[j] = HofZ.imag(); - } -} - -//--------------------------------------------------------------------------- - -} // namespace diff --git a/kitiirfir/IIRFilterCode.h b/kitiirfir/IIRFilterCode.h deleted file mode 100644 index d917ead22..000000000 --- a/kitiirfir/IIRFilterCode.h +++ /dev/null @@ -1,38 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef IIRFilterCodeH -#define IIRFilterCodeH -//--------------------------------------------------------------------------- -#include "LowPassPrototypes.h" // defines TFilterPoly and ARRAY_DIM -#define OVERFLOW_LIMIT 1.0E20 - -namespace kitiirfir -{ - -enum TIIRPassTypes {iirLPF, iirHPF, iirBPF, iirNOTCH, iirALLPASS}; - -struct TIIRCoeff {double a0[ARRAY_DIM]; double a1[ARRAY_DIM]; double a2[ARRAY_DIM]; double a3[ARRAY_DIM]; double a4[ARRAY_DIM]; - double b0[ARRAY_DIM]; double b1[ARRAY_DIM]; double b2[ARRAY_DIM]; double b3[ARRAY_DIM]; double b4[ARRAY_DIM]; - int NumSections; }; - -struct TIIRFilterParams { TIIRPassTypes IIRPassType; // Defined above: Low pass, High Pass, etc. - double OmegaC; // The IIR filter's 3 dB corner freq for low pass and high pass, the center freq for band pass and notch. - double BW; // The IIR filter's 3 dB bandwidth for band pass and notch filters. - double dBGain; // Sets the Gain of the filter - - // These define the low pass prototype to be used - TFilterPoly ProtoType; // Butterworth, Cheby, etc. - int NumPoles; // Pole count - double Ripple; // Passband Ripple for the Elliptic and Chebyshev - double StopBanddB; // Stop Band Attenuation in dB for the Elliptic and Inverse Chebyshev - double Gamma; // Controls the transition bandwidth on the Adjustable Gauss. -1 <= Gamma <= 1 - }; - -TIIRCoeff CalcIIRFilterCoeff(TIIRFilterParams IIRFilt); -void FilterWithIIR(TIIRCoeff IIRCoeff, double *Signal, double *FilteredSignal, int NumSigPts); -double SectCalc(int j, int k, double x, TIIRCoeff IIRCoeff); -void IIRFreqResponse(TIIRCoeff IIRCoeff, int NumSections, double *RealHofZ, double *ImagHofZ, int NumPts); - -} // namespace - -#endif diff --git a/kitiirfir/LowPassPrototypes.cpp b/kitiirfir/LowPassPrototypes.cpp deleted file mode 100644 index 084351e9f..000000000 --- a/kitiirfir/LowPassPrototypes.cpp +++ /dev/null @@ -1,469 +0,0 @@ -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - If you find a problem, please leave a note at: - http://www.iowahills.com/feedbackcomments.html - May 1, 2016 - - ShowMessage is a C++ Builder function, and it usage has been commented out. - If you are using C++ Builder, include vcl.h for ShowMessage. - Otherwise replace ShowMessage with something appropriate for your compiler. - - This code generates 2nd order S plane coefficients for the following low pass filter prototypes. - Butterworth, Chebyshev, Bessel, Gauss, Adjustable Gauss, Inverse Chebyshev, Papoulis, and Elliptic. - This code does 3 things. - 1. Gets the filter's roots from the code in LowPassRoots.cpp - 2. Uses the left hand plane roots to form 2nd order polynomials. - 3. Frequency scales the polynomials so the 3 dB corner frequency is at omege = 1 rad/sec. - - Note that we range check the Filt struct variables here, but we don't report - back any changes we make. - */ - -#include -#include "LowPassPrototypes.h" -#include "PFiftyOneRevE.h" -#include "LowPassRoots.h" - -namespace kitiirfir -{ - -//--------------------------------------------------------------------------- - -// TLowPassParams defines the low pass prototype (NumPoles, Ripple, etc.). -// We return SPlaneCoeff filled with the 2nd order S plane coefficients. -TSPlaneCoeff CalcLowPassProtoCoeff(TLowPassParams Filt) { - int j, DenomCount = 0, NumerCount, NumRoots, ZeroCount; - std::complex Poles[ARRAY_DIM], Zeros[ARRAY_DIM]; - TSPlaneCoeff Coeff; // The return value. - - // Init the S Plane Coeff. H(s) = (N2*s^2 + N1*s + N0) / (D2*s^2 + D1*s + D0) - for (j = 0; j < ARRAY_DIM; j++) { - Coeff.N2[j] = 0.0; - Coeff.N1[j] = 0.0; - Coeff.N0[j] = 1.0; - Coeff.D2[j] = 0.0; - Coeff.D1[j] = 0.0; - Coeff.D0[j] = 1.0; - } - Coeff.NumSections = 0; - - // We need to range check the various argument values here. - // These are the practical limits the max number of poles. - if (Filt.NumPoles < 1) - Filt.NumPoles = 1; - if (Filt.NumPoles > MAX_POLE_COUNT) - Filt.NumPoles = MAX_POLE_COUNT; - if (Filt.ProtoType == ELLIPTIC || Filt.ProtoType == INVERSE_CHEBY) { - if (Filt.NumPoles > 15) - Filt.NumPoles = 15; - } - if (Filt.ProtoType == GAUSSIAN || Filt.ProtoType == BESSEL) { - if (Filt.NumPoles > 12) - Filt.NumPoles = 12; - } - - // Gamma is used by the Adjustable Gauss. - if (Filt.Gamma < -1.0) - Filt.Gamma = -1.0; // -1 gives ~ Gauss response - if (Filt.Gamma > 1.0) - Filt.Gamma = 1.0; // +1 gives ~ Butterworth response. - - // Ripple is used by the Chebyshev and Elliptic - if (Filt.Ripple < 0.0001) - Filt.Ripple = 0.0001; - if (Filt.Ripple > 1.0) - Filt.Ripple = 1.0; - - // With the Chebyshev we need to use less ripple for large pole counts to keep the poles out of the RHP. - if (Filt.ProtoType == CHEBYSHEV && Filt.NumPoles > 15) { - double MaxRipple = 1.0; - if (Filt.NumPoles == 16) - MaxRipple = 0.5; - if (Filt.NumPoles == 17) - MaxRipple = 0.4; - if (Filt.NumPoles == 18) - MaxRipple = 0.25; - if (Filt.NumPoles == 19) - MaxRipple = 0.125; - if (Filt.NumPoles >= 20) - MaxRipple = 0.10; - if (Filt.Ripple > MaxRipple) - Filt.Ripple = MaxRipple; - } - - // StopBanddB is used by the Inverse Chebyshev and the Elliptic - // It is given in positive dB values. - if (Filt.StopBanddB < 20.0) - Filt.StopBanddB = 20.0; - if (Filt.StopBanddB > 120.0) - Filt.StopBanddB = 120.0; - - // There isn't such a thing as a 1 pole Chebyshev, or 1 pole Bessel, etc. - // A one pole filter is simply 1/(s+1). - NumerCount = 0; // init - if (Filt.NumPoles == 1) { - Coeff.D1[0] = 1.0; - DenomCount = 1; // DenomCount is the number of denominator factors (1st or 2nd order). - } else if (Filt.ProtoType == BUTTERWORTH) { - NumRoots = ButterworthPoly(Filt.NumPoles, Poles); - DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, - Coeff.D0); - // A Butterworth doesn't require frequncy scaling with SetCornerFreq(). - } - - else if (Filt.ProtoType == ADJUSTABLE) // Adjustable Gauss - { - NumRoots = AdjustablePoly(Filt.NumPoles, Poles, Filt.Gamma); - DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, - Coeff.D0); - SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, - Coeff.N1, Coeff.N0); - } - - else if (Filt.ProtoType == CHEBYSHEV) { - NumRoots = ChebyshevPoly(Filt.NumPoles, Filt.Ripple, Poles); - DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, - Coeff.D0); - SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, - Coeff.N1, Coeff.N0); - } - - else if (Filt.ProtoType == INVERSE_CHEBY) { - NumRoots = InvChebyPoly(Filt.NumPoles, Filt.StopBanddB, Poles, Zeros, - &ZeroCount); - DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, - Coeff.D0); - NumerCount = GetFilterCoeff(ZeroCount, Zeros, Coeff.N2, Coeff.N1, - Coeff.N0); - SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, - Coeff.N1, Coeff.N0); - } - - else if (Filt.ProtoType == ELLIPTIC) { - NumRoots = EllipticPoly(Filt.NumPoles, Filt.Ripple, Filt.StopBanddB, - Poles, Zeros, &ZeroCount); - DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, - Coeff.D0); - NumerCount = GetFilterCoeff(ZeroCount, Zeros, Coeff.N2, Coeff.N1, - Coeff.N0); - SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, - Coeff.N1, Coeff.N0); - } - - // Papoulis works OK, but it doesn't accomplish anything the Chebyshev can't. - else if (Filt.ProtoType == PAPOULIS) { - NumRoots = PapoulisPoly(Filt.NumPoles, Poles); - DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, - Coeff.D0); - SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, - Coeff.N1, Coeff.N0); - } - - else if (Filt.ProtoType == BESSEL) { - NumRoots = BesselPoly(Filt.NumPoles, Poles); - DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, - Coeff.D0); - SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, - Coeff.N1, Coeff.N0); - } - - else if (Filt.ProtoType == GAUSSIAN) { - NumRoots = GaussianPoly(Filt.NumPoles, Poles); - DenomCount = GetFilterCoeff(NumRoots, Poles, Coeff.D2, Coeff.D1, - Coeff.D0); - SetCornerFreq(DenomCount, Coeff.D2, Coeff.D1, Coeff.D0, Coeff.N2, - Coeff.N1, Coeff.N0); - } - - Coeff.NumSections = DenomCount; - - // If we have an odd pole count, there will be 1 less zero than poles, so we need to shift the - // zeros down in the arrays so the 1st zero (which is zero) and aligns with the real pole. - if (NumerCount != 0 && Filt.NumPoles % 2 == 1) { - for (j = NumerCount; j >= 0; j--) { - Coeff.N2[j + 1] = Coeff.N2[j]; // Coeff.N1's are always zero - Coeff.N0[j + 1] = Coeff.N0[j]; - } - Coeff.N2[0] = 0.0; // Set the 1st zero to zero for odd pole counts. - Coeff.N0[0] = 1.0; - } - - return (Coeff); - -} - -//--------------------------------------------------------------------------- - -// This sets the polynomial's the 3 dB corner to 1 rad/sec. This isn' required for a Butterworth, -// but the rest of the polynomials need correction. Esp the Adj Gauss, Inv Cheby and Bessel. -// Poly Count is the number of 2nd order sections. D and N are the Denom and Num coefficients. -// H(s) = (N2*s^2 + N1*s + N0) / (D2*s^2 + D1*s + D0) -void SetCornerFreq(int PolyCount, double *D2, double *D1, double *D0, - double *N2, double *N1, double *N0) { - int j, n; - double Omega, FreqScalar, Zeta, Gain; - std::complex s, H; - - Gain = 1.0; - for (j = 0; j < PolyCount; j++) - Gain *= D0[j] / N0[j]; - - // Evaluate H(s) by increasing Omega until |H(s)| < -3 dB - for (j = 1; j < 6000; j++) { - Omega = (double) j / 512.0; // The step size for Omega is 1/512 radians. - s = std::complex(0.0, Omega); - - H = std::complex(1.0, 0.0); - for (n = 0; n < PolyCount; n++) { - H = H * (N2[n] * s * s + N1[n] * s + N0[n]) - / (D2[n] * s * s + D1[n] * s + D0[n]); - } - H *= Gain; - if (std::abs(H) < 0.7071) - break; // -3 dB - } - - FreqScalar = 1.0 / Omega; - - // Freq scale the denominator. We hold the damping factor Zeta constant. - for (j = 0; j < PolyCount; j++) { - Omega = sqrt(D0[j]); - if (Omega == 0.0) - continue; // should never happen - Zeta = D1[j] / Omega / 2.0; - if (D2[j] != 0.0) // 2nd degree poly - { - D0[j] = Omega * Omega * FreqScalar * FreqScalar; - D1[j] = 2.0 * Zeta * Omega * FreqScalar; - } else // 1st degree poly - { - D0[j] *= FreqScalar; - } - } - - // Scale the numerator. H(s) = (N2*s^2 + N1*s + N0) / (D2*s^2 + D1*s + D0) - // N1 is always zero. N2 is either 1 or 0. If N2 = 0, then N0 = 1 and there isn't a zero to scale. - // For all pole filters (Butter, Cheby, etc) N2 = 0 and N0 = 1. - for (j = 0; j < PolyCount; j++) { - if (N2[j] == 0.0) - continue; - N0[j] *= FreqScalar * FreqScalar; - } - -} - -//--------------------------------------------------------------------------- - -// Some of the Polys generate both left hand and right hand plane roots. -// We use this function to get the left hand plane poles and imag axis zeros to -// create the 2nd order polynomials with coefficients A2, A1, A0. -// We return the Polynomial count. - -// We first sort the roots according the the real part (a zeta sort). Then all the left -// hand plane roots are grouped and in the correct order for IIR and Opamp filters. -// We then check for duplicate roots, and set an inconsequential real or imag part to zero. -// Then the 2nd order coefficients are calculated. -int GetFilterCoeff(int RootCount, std::complex *Roots, double *A2, double *A1, - double *A0) { - int PolyCount, j, k; - - SortRootsByZeta(Roots, RootCount, stMin); // stMin places the most negative real part 1st. - - // Check for duplicate roots. The Inv Cheby generates duplcate imag roots, and the - // Elliptic generates duplicate real roots. We set duplicates to a RHP value. - for (j = 0; j < RootCount - 1; j++) { - for (k = j + 1; k < RootCount; k++) { - if (fabs(Roots[j].real() - Roots[k].real()) < 1.0E-3 - && fabs(Roots[j].imag() - Roots[k].imag()) < 1.0E-3) { - Roots[k] = std::complex((double) k, 0.0); // RHP roots are ignored below, Use k is to prevent duplicate checks for matches. - } - } - } - - // This forms the 2nd order coefficients from the root value. - // We ignore roots in the Right Hand Plane. - PolyCount = 0; - for (j = 0; j < RootCount; j++) { - if (Roots[j].real() > 0.0) - continue; // Right Hand Plane - if (Roots[j].real() == 0.0 && Roots[j].imag() == 0.0) - continue; // At the origin. This should never happen. - - if (Roots[j].real() == 0.0) // Imag Root (A poly zero) - { - A2[PolyCount] = 1.0; - A1[PolyCount] = 0.0; - A0[PolyCount] = Roots[j].imag() * Roots[j].imag(); - j++; - PolyCount++; - } else if (Roots[j].imag() == 0.0) // Real Pole - { - A2[PolyCount] = 0.0; - A1[PolyCount] = 1.0; - A0[PolyCount] = -Roots[j].real(); - PolyCount++; - } else // Complex Pole - { - A2[PolyCount] = 1.0; - A1[PolyCount] = -2.0 * Roots[j].real(); - A0[PolyCount] = Roots[j].real() * Roots[j].real() - + Roots[j].imag() * Roots[j].imag(); - j++; - PolyCount++; - } - } - - return (PolyCount); - -} - -//--------------------------------------------------------------------------- - -// This rebuilds an Nth order poly from its 2nd order constituents. -// PolyCount is the number of 2nd order polys. e.g. NumPoles = 7 PolyCount = 4 -// PolyCoeff gets filled such that PolyCoeff[5]*s^5 + PolyCoeff[4]*s^4 + PolyCoeff[3]*s^3 + .. -// The poly order is returned. -int RebuildPoly(int PolyCount, double *PolyCoeff, double *A2, double *A1, - double *A0) { - int j, k, n; - double Sum[P51_ARRAY_SIZE]; - for (j = 0; j <= 2 * PolyCount; j++) - PolyCoeff[j] = 0.0; - for (j = 0; j < P51_ARRAY_SIZE; j++) - Sum[j] = 0.0; - - PolyCoeff[2] = A2[0]; - PolyCoeff[1] = A1[0]; - PolyCoeff[0] = A0[0]; - - for (j = 1; j < PolyCount; j++) { - for (n = 0; n <= 2 * j; n++) { - Sum[n + 2] += PolyCoeff[n] * A2[j]; - Sum[n + 1] += PolyCoeff[n] * A1[j]; - Sum[n + 0] += PolyCoeff[n] * A0[j]; - } - for (k = 0; k <= 2 * j + 2; k++) - PolyCoeff[k] = Sum[k]; - for (k = 0; k < P51_ARRAY_SIZE; k++) - Sum[k] = 0.0; - } - - // Want to return the poly order. This will be 2 * PolyCount if there aren't any 1st order polys. - // 1st order Polys create leading zeros. N 1st order polys Gives N leading zeros. - for (j = 2 * PolyCount; j >= 0; j--) - if (PolyCoeff[j] != 0.0) - break; - return (j); -} - -//--------------------------------------------------------------------------- -// This sorts on the real part if the real part of the 1st root != 0 (a Zeta sort) -// else we sort on the imag part. If SortType == stMin for both the poles and zeros, then -// the poles and zeros will be properly matched. -// This also sets an inconsequential real or imag part to zero. -// A matched pair of z plane real roots, such as +/- 1, don't come out together. -// Used above in GetFilterCoeff and the FIR zero plot. -void SortRootsByZeta(std::complex *Roots, int Count, TOurSortTypes SortType) { - if (Count >= P51_MAXDEGREE) { - //ShowMessage("Count > P51_MAXDEGREE in TPolyForm::SortRootsByZeta()"); - return; - } - - int j, k, RootJ[P51_ARRAY_SIZE]; - double SortValue[P51_ARRAY_SIZE]; - std::complex TempRoots[P51_ARRAY_SIZE]; - - // Set an inconsequential real or imag part to zero. - for (j = 0; j < Count; j++) { - if (fabs(Roots[j].real()) * 1.0E3 < fabs(Roots[j].imag())) - Roots[j].real(0.0); - if (fabs(Roots[j].imag()) * 1.0E3 < fabs(Roots[j].real())) - Roots[j].imag(0.0); - } - - // Sort the roots. - for (j = 0; j < Count; j++) - RootJ[j] = j; // Needed for HeapIndexSort - if (Roots[0].real() != 0.0) // Cplx roots - { - for (j = 0; j < Count; j++) - SortValue[j] = Roots[j].real(); - } else // Imag roots, so we sort on imag part. - { - for (j = 0; j < Count; j++) - SortValue[j] = fabs(Roots[j].imag()); - } - HeapIndexSort(SortValue, RootJ, Count, SortType); // stMin gives the most negative root on top - - for (j = 0; j < Count; j++) { - k = RootJ[j]; // RootJ is the sort index - TempRoots[j] = Roots[k]; - } - for (j = 0; j < Count; j++) { - Roots[j] = TempRoots[j]; - } - -} -//--------------------------------------------------------------------------- - -// Remember to set the Index array to 0, 1, 2, 3, ... N-1 -bool HeapIndexSort(double *Data, int *Index, int N, TOurSortTypes SortType) { - int i, j, k, m, IndexTemp; - long long FailSafe, NSquared; // need this for big sorts - - NSquared = (long long) N * (long long) N; - m = N / 2; - k = N - 1; - for (FailSafe = 0; FailSafe < NSquared; FailSafe++) // typical FailSafe value on return is N*log2(N) - { - if (m > 0) - IndexTemp = Index[--m]; - else { - IndexTemp = Index[k]; - Index[k] = Index[0]; - if (--k == 0) { - Index[0] = IndexTemp; - return (true); - } - } - - i = m + 1; - j = 2 * i; - - if (SortType == stMax) - while (j < k + 2) { - FailSafe++; - if (j <= k && Data[Index[j - 1]] > Data[Index[j]]) - j++; - if (Data[IndexTemp] > Data[Index[j - 1]]) { - Index[i - 1] = Index[j - 1]; - i = j; - j += i; - } else - break; - } - - else - // SortType == stMin - while (j < k + 2) { - FailSafe++; - if (j <= k && Data[Index[j - 1]] < Data[Index[j]]) - j++; - if (Data[IndexTemp] < Data[Index[j - 1]]) { - Index[i - 1] = Index[j - 1]; - i = j; - j += i; - } else - break; - } - - Index[i - 1] = IndexTemp; - } - return (false); -} - -//---------------------------------------------------------------------------------- - -} // namespace - diff --git a/kitiirfir/LowPassPrototypes.h b/kitiirfir/LowPassPrototypes.h deleted file mode 100644 index d463f8e38..000000000 --- a/kitiirfir/LowPassPrototypes.h +++ /dev/null @@ -1,45 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef LowPassPrototypesH -#define LowPassPrototypesH - -#include - -#define MAX_POLE_COUNT 20 -#define ARRAY_DIM 50 // This MUST be at least 2*MAX_POLE_COUNT because some filter polys are defined in terms of 2 * NumPoles - -namespace kitiirfir -{ - -enum TOurSortTypes{stMax, stMin}; - -// These coeff form H(s) = (N2*s^2 + N1*s + N0) / (D2*s^2 + D1*s + D0) -// NumSections is the number of 1st and 2nd order polynomial factors . -struct TSPlaneCoeff { double N2[ARRAY_DIM]; double N1[ARRAY_DIM]; double N0[ARRAY_DIM]; - double D2[ARRAY_DIM]; double D1[ARRAY_DIM]; double D0[ARRAY_DIM]; - int NumSections; }; - -// These are the available filter polynomials. NOT_IIR is for code testing. -enum TFilterPoly {BUTTERWORTH, GAUSSIAN, BESSEL, ADJUSTABLE, CHEBYSHEV, - INVERSE_CHEBY, PAPOULIS, ELLIPTIC, NOT_IIR}; - -// This structure defines the low pass filter prototype. -// The 3 dB corner frequency is 1 rad/sec for all filters. -struct TLowPassParams {TFilterPoly ProtoType; // Butterworth, Cheby, etc. - int NumPoles; // Pole count - double Ripple; // Passband Ripple for the Elliptic and Chebyshev - double StopBanddB; // Stop Band Attenuation in dB for the Elliptic and Inverse Cheby - double Gamma; // Controls the transition bandwidth on the Adjustable Gauss. -1 <= Gamma <= 1 - }; - - -TSPlaneCoeff CalcLowPassProtoCoeff(TLowPassParams Filt); -void SetCornerFreq(int Count, double *D2, double *D1, double *D0, double *N2, double *N1, double *N0); -int GetFilterCoeff(int RootCount, std::complex *Roots, double *A2, double *A1, double *A0); -int RebuildPoly(int PolyCount, double *PolyCoeff, double *A2, double *A1, double *A0 ); -void SortRootsByZeta(std::complex *Roots, int Count, TOurSortTypes SortType); -bool HeapIndexSort(double *Data, int *Index, int N, TOurSortTypes SortType); - -} // namespace - -#endif diff --git a/kitiirfir/LowPassRoots.cpp b/kitiirfir/LowPassRoots.cpp deleted file mode 100644 index d74332cd4..000000000 --- a/kitiirfir/LowPassRoots.cpp +++ /dev/null @@ -1,683 +0,0 @@ -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - If you find a problem, please leave a note at: - http://www.iowahills.com/feedbackcomments.html - May 1, 2016 - - - This code calculates the roots for the following filter polynomials. - Butterworth, Chebyshev, Bessel, Gauss, Adjustable Gauss, Inverse Chebyshev, Papoulis, and Elliptic. - - These filters are described in most filter design texts, except the Papoulis and Adjustable Gauss. - - The Adjustable Gauss is a transitional filter invented by Iowa Hills that can generate a response - anywhere between a Gauss and a Butterworth. Use Gamma to control the response. - Gamma = -1.0 nearly a Gauss - Gamma = -0.7 nearly a Bessel - Gamma = 1.0 nearly a Butterworth - - The Papoulis (Classic L) provides a response between the Butterworth the Chebyshev. - It has a faster roll off than the Butterworth but without the Chebyshev ripple. - It does however have some roll off in the pass band, which can be controlled by the RollOff parameter. - - The limits on the arguments for these functions (such as pole count or ripple) are not - checked here. See the LowPassPrototypes.cpp for argument limits. - */ - -#include -#include -#include "LowPassRoots.h" -#include "PFiftyOneRevE.h" - -namespace kitiirfir -{ - -//--------------------------------------------------------------------------- -// This used by several of the functions below. It returns a double so -// we don't overflow and because we need a double for subsequent calculations. -double Factorial(int N) { - int j; - double Fact = 1.0; - for (j = 1; j <= N; j++) - Fact *= (double) j; - return (Fact); -} - -//--------------------------------------------------------------------------- - -// Some of the code below generates the coefficients in reverse order -// needed for the root finder. This reverses the poly. -void ReverseCoeff(double *P, int N) { - int j; - double Temp; - for (j = 0; j <= N / 2; j++) { - Temp = P[j]; - P[j] = P[N - j]; - P[N - j] = Temp; - } - - for (j = N; j >= 1; j--) { - if (P[0] != 0.0) - P[j] /= P[0]; - } - P[0] = 1.0; - -} - -//--------------------------------------------------------------------------- - -// We calculate the roots for a Butterwoth filter directly. (No root finder needed) -// We fill the array Roots[] and return the number of roots. -int ButterworthPoly(int NumPoles, std::complex *Roots) { - int j, n, N; - double Theta; - - N = NumPoles; - n = 0; - for (j = 0; j < N / 2; j++) { - Theta = M_PI * (double) (2 * j + N + 1) / (double) (2 * N); - Roots[n++] = std::complex(cos(Theta), sin(Theta)); - Roots[n++] = std::complex(cos(Theta), -sin(Theta)); - } - if (N % 2 == 1) - Roots[n++] = std::complex(-1.0, 0.0); // The real root for odd pole counts. - return (N); -} - -//--------------------------------------------------------------------------- - -// This calculates the roots for a Chebyshev filter directly. (No root finder needed) -int ChebyshevPoly(int NumPoles, double Ripple, std::complex *Roots) { - int j, n, N; - double Sigma, Omega; - double Arg, Theta, Epsilon; - - N = NumPoles; - Epsilon = pow(10.0, Ripple / 10.0) - 1.0; - Epsilon = sqrt(Epsilon); - if (Epsilon < 0.00001) - Epsilon = 0.00001; - if (Epsilon > 0.996) - Epsilon = 0.996; - Epsilon = 1.0 / Epsilon; - Arg = log(Epsilon + sqrt(Epsilon * Epsilon + 1.0)) / (double) N; // = asinh(Epsilon) / (double)N; - n = 0; - for (j = 0; j < N / 2; j++) { - Theta = (2 * j + 1) * M_PI_2 / (double) N; - Sigma = -sinh(Arg) * sin(Theta); - Omega = cosh(Arg) * cos(Theta); - Roots[n++] = std::complex(Sigma, Omega); - Roots[n++] = std::complex(Sigma, -Omega); - } - if (N % 2 == 1) - Roots[n++] = std::complex(-sinh(Arg), 0.0); // The real root for odd pole counts. - return (N); -} - -//--------------------------------------------------------------------------- - -// The Gaussian Poly is simply 1 - s^2 + s^4 /2! - s^6 / 3! .... seeHumpherys p. 414. -int GaussianPoly(int NumPoles, std::complex *Roots) { - int j, N, RootsCount; - double GaussCoeff[P51_ARRAY_SIZE]; - - N = NumPoles; - GaussCoeff[0] = 1.0; - GaussCoeff[1] = 0.0; - for (j = 2; j <= 2 * N; j += 2) { - GaussCoeff[j] = 1.0 / Factorial(j / 2); - GaussCoeff[j + 1] = 0.0; - if ((j / 2) % 2 == 1) - GaussCoeff[j] *= -1.0; - } - - // The coefficients are generated in reverse order needed for P51. - ReverseCoeff(GaussCoeff, N * 2); - RootsCount = FindRoots(N * 2, GaussCoeff, Roots); - return (RootsCount); -} - -//--------------------------------------------------------------------------- - -// This function starts with the Gauss and modifies the coefficients. -// The Gaussian Poly is simply 1 - s^2 + s^4 /2! - s^6 / 3! .... seeHumpherys p. 414. -int AdjustablePoly(int NumPoles, std::complex *Roots, double Gamma) { - int j, N, RootsCount; - double GaussCoeff[P51_ARRAY_SIZE]; - - N = NumPoles; - if (Gamma > 0.0) - Gamma *= 2.0; // Gamma < 0 is the orig Gauss and Bessel responses. Gamma > 0 has an asymptotic response, so we double it, which also makes the user interface a bit nicer. i.e. -1 <= Gamma <= 1 - - GaussCoeff[0] = 1.0; - GaussCoeff[1] = 0.0; - for (j = 2; j <= 2 * N; j += 2) { - GaussCoeff[j] = pow(Factorial(j / 2), Gamma); // Gamma = -1 is orig Gauss poly, Gamma = 1 approaches a Butterworth response. - GaussCoeff[j + 1] = 0.0; - if ((j / 2) % 2 == 1) - GaussCoeff[j] *= -1.0; - } - - // The coefficients are generated in reverse order needed for P51. - ReverseCoeff(GaussCoeff, N * 2); - RootsCount = FindRoots(N * 2, GaussCoeff, Roots); - - // Scale the imag part of the root by 1.1 to get a response closer to a Butterworth when Gamma = -2 - for (j = 0; j < N * 2; j++) - Roots[j] = std::complex(Roots[j].real(), Roots[j].imag() * 1.10); - return (RootsCount); -} - -//--------------------------------------------------------------------------- - -// These Bessel coefficients are calc'd with the formula given in Johnson & Moore. Page 164. eq 7-26 -// The highet term is 1, the rest of the terms are calc'd -int BesselPoly(int NumPoles, std::complex *Roots) { - int k, N, RootsCount; - double b, PolyCoeff[P51_ARRAY_SIZE]; - - N = NumPoles; - for (k = N - 1; k >= 0; k--) { - // b is calc'd as a double because of all the division, but the result is essentially a large int. - b = Factorial(2 * N - k) / Factorial(k) / Factorial(N - k) - / pow(2.0, (double) (N - k)); - PolyCoeff[k] = b; - } - PolyCoeff[N] = 1.0; - - // The coefficients are generated in reverse order needed for P51. - ReverseCoeff(PolyCoeff, N); - RootsCount = FindRoots(N, PolyCoeff, Roots); - return (RootsCount); -} - -//--------------------------------------------------------------------------- - -// This Inverse Cheby code is identical to the Cheby code, except for two things. -// First, epsilon represents stop band atten, not ripple, so this epsilon is 1/epsilon. -// More importantly, the inverse cheby is defined in terms of F(1/s) instead of the usual F(s); -// After a bit of algebra, it is easy to see that the 1/s terms can be made s terms by simply -// multiplying the num and denom by s^2N. Then the 0th term becomes the highest term, so the -// coefficients are simply fed to the root finder in reverse order. -// Since 1 doesn't get added to the numerator, the lowest 1 or 2 terms will be 0, but we can't -// feed the root finder 0's as the highest term, so we have to be sure not to feed them to the root finder. - -int InvChebyPoly(int NumPoles, double StopBanddB, std::complex *ChebyPoles, - std::complex *ChebyZeros, int *ZeroCount) { - int j, k, N, PolesCount; - double Arg, Epsilon, ChebPolyCoeff[P51_ARRAY_SIZE], - PolyCoeff[P51_ARRAY_SIZE]; - std::complex SquaredPolyCoeff[P51_ARRAY_SIZE], A, B; - - N = NumPoles; - Epsilon = 1.0 / (pow(10.0, StopBanddB / 10.0) - 1.0); // actually Epsilon Squared - - // This algorithm is from the paper by Richard J Mathar. It generates the coefficients for the Cheb poly. - // It stores the Nth order coefficient in ChebPolyCoeff[N], and so on. Every other Cheb coeff is 0. See Wikipedia for a table that this code will generate. - for (j = 0; j <= N / 2; j++) { - Arg = Factorial(N - j - 1) / Factorial(j) / Factorial(N - 2 * j); - if (j % 2 == 1) - Arg *= -1.0; - Arg *= pow(2.0, (double) (N - 2 * j)) * (double) N / 2.0; - ChebPolyCoeff[N - 2 * j] = Arg; - if (N - (2 * j + 1) >= 0) { - ChebPolyCoeff[N - (2 * j + 1)] = 0.0; - } - } - - // Now square the Chebshev polynomial where we assume s = jw. To get the signs correct, - // we need to take j to the power. Then its a simple matter of adding powers and - // multiplying coefficients. j and k represent the exponents. That is, j=3 is the x^3 coeff, and so on. - for (j = 0; j <= 2 * N; j++) - SquaredPolyCoeff[j] = std::complex(0.0, 0.0); - - for (j = 0; j <= N; j++) - for (k = 0; k <= N; k++) { - A = pow(std::complex(0.0, 1.0), (double) j) * ChebPolyCoeff[j]; - B = pow(std::complex(0.0, 1.0), (double) k) * ChebPolyCoeff[k]; - SquaredPolyCoeff[j + k] = SquaredPolyCoeff[j + k] + A * B; // these end up entirely real. - } - - // Denominator - // Now we multiply the coefficients by Epsilon and add 1 to the denominator poly. - k = 0; - for (j = 0; j <= 2 * N; j++) - ChebPolyCoeff[j] = SquaredPolyCoeff[j].real() * Epsilon; - ChebPolyCoeff[0] += 1.0; - for (j = 0; j <= 2 * N; j++) - PolyCoeff[k++] = ChebPolyCoeff[j]; // Note this order is reversed from the Chebyshev routine. - k--; - PolesCount = FindRoots(k, PolyCoeff, ChebyPoles); - - // Numerator - k = 0; - for (j = 0; j <= 2 * N; j++) - ChebPolyCoeff[j] = SquaredPolyCoeff[j].real(); // Not using Epsilon here so the check for 0 on the next line is easier. Since the root finder normalizes the poly, it gets factored out anyway. - for (j = 0; j <= 2 * N; j++) - if (fabs(ChebPolyCoeff[j]) > 0.01) - break; // Get rid of the high order zeros. There will be eithe 0ne or two zeros to delete. - for (; j <= 2 * N; j++) - PolyCoeff[k++] = ChebPolyCoeff[j]; - k--; - *ZeroCount = FindRoots(k, PolyCoeff, ChebyZeros); - - return (PolesCount); - -} - -//--------------------------------------------------------------------------- - -// The complete Papouls poly is 1 + Epsilon * P(n) where P(n) is the 2*N Legendre poly. -int PapoulisPoly(int NumPoles, std::complex *Roots) { - int j, N, RootsCount; - double Epsilon, PolyCoeff[P51_ARRAY_SIZE]; - - N = NumPoles; - for (j = 0; j < 2 * N; j++) - PolyCoeff[j] = 0.0; // so we don't have to fill all the zero's. - - switch (N) { - case 1: // 1 pole - PolyCoeff[2] = 1.0; - break; - - case 2: // 2 pole - PolyCoeff[4] = 1.0; - break; - - case 3: // 3 pole - PolyCoeff[6] = 3.0; - PolyCoeff[4] = -3.0; - PolyCoeff[2] = 1.0; - break; - - case 4: - PolyCoeff[8] = 6.0; - PolyCoeff[6] = -8.0; - PolyCoeff[4] = 3.0; - break; - - case 5: - PolyCoeff[10] = 20.0; - PolyCoeff[8] = -40.0; - PolyCoeff[6] = 28.0; - PolyCoeff[4] = -8.0; - PolyCoeff[2] = 1.0; - break; - - case 6: - PolyCoeff[12] = 50.0; - PolyCoeff[10] = -120.0; - PolyCoeff[8] = 105.0; - PolyCoeff[6] = -40.0; - PolyCoeff[4] = 6.0; - break; - - case 7: - PolyCoeff[14] = 175.0; - PolyCoeff[12] = -525.0; - PolyCoeff[10] = 615.0; - PolyCoeff[8] = -355.0; - PolyCoeff[6] = 105.0; - PolyCoeff[4] = -15.0; - PolyCoeff[2] = 1.0; - break; - - case 8: - PolyCoeff[16] = 490.0; - PolyCoeff[14] = -1680.0; - PolyCoeff[12] = 2310.0; - PolyCoeff[10] = -1624.0; - PolyCoeff[8] = 615.0; - PolyCoeff[6] = -120.0; - PolyCoeff[4] = 10.0; - break; - - case 9: - PolyCoeff[18] = 1764.0; - PolyCoeff[16] = -7056.0; - PolyCoeff[14] = 11704.0; - PolyCoeff[12] = -10416.0; - PolyCoeff[10] = 5376.0; - PolyCoeff[8] = -1624.0; - PolyCoeff[6] = 276.0; - PolyCoeff[4] = -24.0; - PolyCoeff[2] = 1.0; - break; - - case 10: - PolyCoeff[20] = 5292.0; - PolyCoeff[18] = -23520.0; - PolyCoeff[16] = 44100.0; - PolyCoeff[14] = -45360.0; - PolyCoeff[12] = 27860.0; - PolyCoeff[10] = -10416.0; - PolyCoeff[8] = 2310.0; - PolyCoeff[6] = -280.0; - PolyCoeff[4] = 15.0; - break; - - case 11: - PolyCoeff[22] = 19404; - PolyCoeff[20] = -97020.0; - PolyCoeff[18] = 208740.0; - PolyCoeff[16] = -252840.0; - PolyCoeff[14] = 189420.0; - PolyCoeff[12] = -90804.0; - PolyCoeff[10] = 27860.0; - PolyCoeff[8] = -5320.0; - PolyCoeff[6] = 595.0; - PolyCoeff[4] = -35.0; - PolyCoeff[2] = 1.0; - break; - - case 12: - PolyCoeff[24] = 60984.0; - PolyCoeff[22] = -332640.0; - PolyCoeff[20] = 790020.0; - PolyCoeff[18] = -1071840.0; - PolyCoeff[16] = 916020.0; - PolyCoeff[14] = -512784.0; - PolyCoeff[12] = 189420.0; - PolyCoeff[10] = -45360.0; - PolyCoeff[8] = 6720.0; - PolyCoeff[6] = -560.0; - PolyCoeff[4] = 21.0; - break; - - case 13: - PolyCoeff[26] = 226512.0; - PolyCoeff[24] = -1359072.0; - PolyCoeff[22] = 3597264.0; - PolyCoeff[20] = -5528160.0; - PolyCoeff[18] = 5462820.0; - PolyCoeff[16] = -3632112.0; - PolyCoeff[14] = 1652232.0; - PolyCoeff[12] = -512784.0; - PolyCoeff[10] = 106380.0; - PolyCoeff[8] = -14160.0; - PolyCoeff[6] = 1128.0; - PolyCoeff[4] = -48.0; - PolyCoeff[2] = 1.0; - break; - - case 14: - PolyCoeff[28] = 736164.0; - PolyCoeff[26] = -4756752.0; - PolyCoeff[24] = 13675662.0; - PolyCoeff[22] = -23063040.0; - PolyCoeff[20] = 25322220.0; - PolyCoeff[18] = -18993744.0; - PolyCoeff[16] = 9934617.0; - PolyCoeff[14] = -3632112.0; - PolyCoeff[12] = 916020.0; - PolyCoeff[10] = -154560.0; - PolyCoeff[8] = 16506.0; - PolyCoeff[6] = -1008.0; - PolyCoeff[4] = 28.0; - break; - - case 15: - PolyCoeff[30] = 2760615.0; - PolyCoeff[28] = -19324305.0; - PolyCoeff[26] = 60747687.0; - PolyCoeff[24] = -113270157.0; - PolyCoeff[22] = 139378239.0; - PolyCoeff[20] = -119144025.0; - PolyCoeff[18] = 72539775.0; - PolyCoeff[16] = -31730787.0; - PolyCoeff[14] = 9934617.0; - PolyCoeff[12] = -2191959.0; - PolyCoeff[10] = 331065.0; - PolyCoeff[8] = -32655.0; - PolyCoeff[6] = 1953.0; - PolyCoeff[4] = -63.0; - PolyCoeff[2] = 1.0; - break; - - case 16: - PolyCoeff[32] = 9202050.0; - PolyCoeff[30] = -68708640.0; - PolyCoeff[28] = 231891660.0; - PolyCoeff[26] = -467747280.0; - PolyCoeff[24] = 628221594.0; - PolyCoeff[22] = -592431840.0; - PolyCoeff[20] = 403062660.0; - PolyCoeff[18] = -200142800.0; - PolyCoeff[16] = 72539775.0; - PolyCoeff[14] = -18993744.0; - PolyCoeff[12] = 3515820.0; - PolyCoeff[10] = -443520.0; - PolyCoeff[8] = 35910.0; - PolyCoeff[6] = -1680.0; - PolyCoeff[4] = 36.0; - break; - - case 17: - PolyCoeff[34] = 34763300.0; - PolyCoeff[32] = -278106400.0; - PolyCoeff[30] = 1012634480.0; - PolyCoeff[28] = -2221579360.0; - PolyCoeff[26] = 3276433160.0; - PolyCoeff[24] = -3431908480.0; - PolyCoeff[22] = 2629731104.0; - PolyCoeff[20] = -1496123200.0; - PolyCoeff[18] = 634862800.0; - PolyCoeff[16] = -200142800.0; - PolyCoeff[14] = 46307800.0; - PolyCoeff[12] = -7696304.0; - PolyCoeff[10] = 888580.0; - PolyCoeff[8] = -67760.0; - PolyCoeff[6] = 3160.0; - PolyCoeff[4] = -80.0; - PolyCoeff[2] = 1.0; - break; - - case 18: - PolyCoeff[36] = 118195220.0; - PolyCoeff[34] = -1001183040.0; - PolyCoeff[32] = 3879584280.0; - PolyCoeff[30] = -9110765664.0; - PolyCoeff[28] = 14480345880.0; - PolyCoeff[26] = -16474217760.0; - PolyCoeff[24] = 13838184360.0; - PolyCoeff[22] = -8725654080.0; - PolyCoeff[20] = 4158224928.0; - PolyCoeff[18] = -1496123200.0; - PolyCoeff[16] = 403062660.0; - PolyCoeff[14] = -79999920.0; - PolyCoeff[12] = 11397540.0; - PolyCoeff[10] = -1119888.0; - PolyCoeff[8] = 71280.0; - PolyCoeff[6] = -2640.0; - PolyCoeff[4] = 45.0; - break; - - case 19: - PolyCoeff[38] = 449141836.0; - PolyCoeff[36] = -4042276524.0; - PolyCoeff[34] = 16732271556.0; - PolyCoeff[32] = -42233237904.0; - PolyCoeff[30] = 72660859128.0; - PolyCoeff[28] = -90231621480.0; - PolyCoeff[26] = 83545742280.0; - PolyCoeff[24] = -58751550000.0; - PolyCoeff[22] = 31671113760.0; - PolyCoeff[20] = -13117232128.0; - PolyCoeff[18] = 4158224928.0; - PolyCoeff[16] = -999092952.0; - PolyCoeff[14] = 178966788.0; - PolyCoeff[12] = -23315292.0; - PolyCoeff[10] = 2130876.0; - PolyCoeff[8] = -129624.0; - PolyCoeff[6] = 4851.0; - PolyCoeff[4] = -99.0; - PolyCoeff[2] = 1.0; - break; - - case 20: - PolyCoeff[40] = 1551580888.0; - PolyCoeff[38] = -14699187360.0; - PolyCoeff[36] = 64308944700.0; - PolyCoeff[34] = -172355177280.0; - PolyCoeff[32] = 316521742680.0; - PolyCoeff[30] = -422089668000.0; - PolyCoeff[28] = 422594051880.0; - PolyCoeff[26] = -323945724960.0; - PolyCoeff[24] = 192167478360.0; - PolyCoeff[22] = -88572527680.0; - PolyCoeff[20] = 31671113760.0; - PolyCoeff[18] = -8725654080.0; - PolyCoeff[16] = 1829127300.0; - PolyCoeff[14] = -286125840.0; - PolyCoeff[12] = 32458140.0; - PolyCoeff[10] = -2560272.0; - PolyCoeff[8] = 131670.0; - PolyCoeff[6] = -3960.0; - PolyCoeff[4] = 55.0; - break; - } - - Epsilon = 0.1; // This controls the amount of pass band roll off. 0.01 < Epsilon < 0.250 - - // The poly is in terms of omega, but we need it in term of s = jw. So we need to - // multiply the approp coeff by neg 1 to account for j. Then mult by epsilon. - for (j = 0; j <= 2 * N; j++) { - if ((j / 2) % 2 == 1) - PolyCoeff[j] *= -1.0; - PolyCoeff[j] *= Epsilon; - } - - // Now add 1 to the poly. - PolyCoeff[0] = 1.0; - - // The coefficients are in reverse order needed for P51. - ReverseCoeff(PolyCoeff, N * 2); - RootsCount = FindRoots(N * 2, PolyCoeff, Roots); - - return (RootsCount); -} - -//--------------------------------------------------------------------------- - -// This code was described in "Elliptic Functions for Filter Design" -// H J Orchard and Alan N Willson IEE Trans on Circuits and Systems April 97 -// The equation numbers in the comments are from the paper. -// As the stop band attenuation -> infinity, the Elliptic -> Chebyshev. -int EllipticPoly(int FiltOrder, double Ripple, double DesiredSBdB, - std::complex *EllipPoles, std::complex *EllipZeros, int *ZeroCount) { - int j, k, n, LastK; - double K[ELLIPARRAYSIZE], G[ELLIPARRAYSIZE], Epsilon[ELLIPARRAYSIZE]; - double A, D, SBdB, dBErr, RealPart, ImagPart; - double DeltaK, PrevErr, Deriv; - std::complex C; - - for (j = 0; j < ELLIPARRAYSIZE; j++) - K[j] = G[j] = Epsilon[j] = 0.0; - if (Ripple < 0.001) - Ripple = 0.001; - if (Ripple > 1.0) - Ripple = 1.0; - Epsilon[0] = sqrt(pow(10.0, Ripple / 10.0) - 1.0); - - // Estimate K[0] to get the algorithm started. - K[0] = (double) (FiltOrder - 2) * 0.1605 + 0.016; - if (K[0] < 0.01) - K[0] = 0.01; - if (K[0] > 0.7) - K[0] = 0.7; - - // This loop calculates K[0] for the desired stopband attenuation. It typically loops < 5 times. - for (j = 0; j < MAX_ELLIP_ITER; j++) { - // Compute K with a forward Landen Transformation. - for (k = 1; k < 10; k++) { - K[k] = pow(K[k - 1] / (1.0 + sqrt(1.0 - K[k - 1] * K[k - 1])), 2.0); // eq. 10 - if (K[k] <= 1.0E-6) - break; - } - LastK = k; - - // Compute G with a backwards Landen Transformation. - G[LastK] = 4.0 * pow(K[LastK] / 4.0, (double) FiltOrder); - for (k = LastK; k >= 1; k--) { - G[k - 1] = 2.0 * sqrt(G[k]) / (1.0 + G[k]); // eq. 9 - } - - if (G[0] <= 0.0) - G[0] = 1.0E-10; - SBdB = 10.0 * log10(1.0 + pow(Epsilon[0] / G[0], 2.0)); // Current stopband attenuation dB - dBErr = DesiredSBdB - SBdB; - - if (fabs(dBErr) < 0.1) - break; - if (j == 0) // Do this on the 1st loop so we can calc a derivative. - { - if (dBErr > 0) - DeltaK = 0.005; - else - DeltaK = -0.005; - PrevErr = dBErr; - } else { - // Use Newtons Method to adjust K[0]. - Deriv = (PrevErr - dBErr) / DeltaK; - PrevErr = dBErr; - if (Deriv == 0.0) - break; // This happens when K[0] hits one of the limits set below. - DeltaK = dBErr / Deriv; - if (DeltaK > 0.1) - DeltaK = 0.1; - if (DeltaK < -0.1) - DeltaK = -0.1; - } - K[0] -= DeltaK; - if (K[0] < 0.001) - K[0] = 0.001; // must not be < 0.0 - if (K[0] > 0.990) - K[0] = 0.990; // if > 0.990 we get a pole in the RHP. This means we were unable to set the stop band atten to the desired level (the Ripple is too large for the Pole Count). - } - - // Epsilon[0] was calulated above, now calculate Epsilon[LastK] from G - for (j = 1; j <= LastK; j++) { - A = (1.0 + G[j]) * Epsilon[j - 1] / 2.0; // eq. 37 - Epsilon[j] = A + sqrt(A * A + G[j]); - } - - // Calulate the poles and zeros. - ImagPart = log( - (1.0 + sqrt(1.0 + Epsilon[LastK] * Epsilon[LastK])) - / Epsilon[LastK]) / (double) FiltOrder; // eq. 22 - n = 0; - for (j = 1; j <= FiltOrder / 2; j++) { - RealPart = (double) (2 * j - 1) * M_PI_2 / (double) FiltOrder; // eq. 19 - C = std::complex(0.0, -1.0) / cos(std::complex(-RealPart, ImagPart)); // eq. 20 - D = 1.0 / cos(RealPart); - for (k = LastK; k >= 1; k--) { - C = (C - K[k] / C) / (1.0 + K[k]); // eq. 36 - D = (D + K[k] / D) / (1.0 + K[k]); - } - - EllipPoles[n] = 1.0 / C; - EllipPoles[n + 1] = std::conj(EllipPoles[n]); - EllipZeros[n] = std::complex(0.0, D / K[0]); - EllipZeros[n + 1] = std::conj(EllipZeros[n]); - n += 2; - } - *ZeroCount = n; // n is the num zeros - - if (FiltOrder % 2 == 1) // The real pole for odd pole counts - { - A = 1.0 / sinh(ImagPart); - for (k = LastK; k >= 1; k--) { - A = (A - K[k] / A) / (1.0 + K[k]); // eq. 38 - } - EllipPoles[n] = std::complex(-1.0 / A, 0.0); - n++; - } - - return (n); // n is the num poles. There will be 1 more pole than zeros for odd pole counts. - -} -//--------------------------------------------------------------------------- - -} // namespace diff --git a/kitiirfir/LowPassRoots.h b/kitiirfir/LowPassRoots.h deleted file mode 100644 index 5c614a57f..000000000 --- a/kitiirfir/LowPassRoots.h +++ /dev/null @@ -1,27 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef LowPassRootsH -#define LowPassRootsH - -#include -#define MAX_ELLIP_ITER 15 -#define ELLIPARRAYSIZE 20 // needs to be > 10 and >= Max Num Poles + 1 - -namespace kitiirfir -{ - -void ReverseCoeff(double *P, int N); -int ButterworthPoly(int NumPoles, std::complex *Roots); -int GaussianPoly(int NumPoles, std::complex *Roots); -int AdjustablePoly(int NumPoles, std::complex *Roots, double Gamma); -int ChebyshevPoly(int NumPoles, double Ripple, std::complex *Roots); -int BesselPoly(int NumPoles, std::complex *Roots); -int InvChebyPoly(int NumPoles, double StopBanddB, std::complex *ChebyPoles, std::complex *ChebyZeros, int *ZeroCount); -int PapoulisPoly(int NumPoles, std::complex *Roots); -int EllipticPoly(int FiltOrder, double Ripple, double DesiredSBdB, std::complex *EllipPoles, std::complex *EllipZeros, int *ZeroCount); - -} // namespace - -#endif - - diff --git a/kitiirfir/NewParksMcClellan.cpp b/kitiirfir/NewParksMcClellan.cpp deleted file mode 100644 index 507b766b8..000000000 --- a/kitiirfir/NewParksMcClellan.cpp +++ /dev/null @@ -1,627 +0,0 @@ -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - If you find a problem, please leave a note at: - http://www.iowahills.com/feedbackcomments.html - May 1, 2016 - - ShowMessage is a C++ Builder function, and it usage has been commented out. - If you are using C++ Builder, include vcl.h for ShowMessage. - Otherwise replace ShowMessage with something appropriate for your compiler. - - This is a C translation of the Parks McClellan algorithm originally done in Fortran. - - The original fortran code came from the Parks McClellan article on Wikipedia. - http://en.wikipedia.org/wiki/Parks%E2%80%93McClellan_filter_design_algorithm - - This code is quite different from the original. The original code had 69 goto statements, - which made it nearly impossible to follow. And of course, it was Fortran code, so many changes - had to be made regardless of style. - - Apparently, Fortran doesn't use global variables. Instead, is uses something called - common memory space. e.g. COMMON PI2,AD,DEV,X,Y,GRID,DES,WT,ALPHA,IEXT,NFCNS,NGRID - I simply converted these to globals. It isn't pretty, but for this purpose, who cares? - - The first step was to get a C version of the code working with as few changes as possible. - That version is also available on: http://www.iowahills.com/A7ExampleCodePage.html - Then, in our desire to see if the code could be made more understandable, we decided to - remove as many goto statements as possible. We checked our work by comparing the coefficients - between this code and our original translation on more than 1000 filters while varying all the parameters. - - Ultimately, we were able to reduce the goto count from 69 to 7, all of which are in the Remez - function. Of the 7 remaining, 3 of these are at the very bottom of the function, and go - back to the very top of the function. These could have been removed, but our goal was to - clarify the code, not restyle it, and since they are clear, we let them be. - - The other 4 goto statements are intertwined in a rather nasty way. We recommend you print out - the Remez code, tape the sheets end to end, and trace out the goto's. It wasn't apparent to - us that they can be removed without an extensive study of the code. - - For better or worse, we also removed any code that was obviously related to Hilbert transforms - and Differentiators. We did this because we aren't interested in these, and we also don't - believe this algorithm does a very good job with them (far too much ripple). - - We added the functions CalcCoefficients() and ErrTest() as a way to simplify things a bit. - - We also found 3 sections of code that never executed. Two of the sections were just a few lines - that the goto's always went around. The third section represented nearly half of the CalcCoefficients() - function. This statement always tested the same, which never allowed the code to execute. - if(GRID[1] < 0.01 && GRID[NGRID] > 0.49) KKK = 1; - This may be due to the 0.01 minimum width limit we set for the bands. - - Note our use of MIN_TEST_VAL. The original code wasn't guarding against division by zero. - Limiting the return values as we have also helped the algorithm's convergence behavior. - - In an effort to improve readability, we made a large number of variable name changes and also - deleted a large number of variables. We left many variable names in tact, in part as an aid when - comparing to the original code, and in part because a better name wasn't obvious. - - This code is essentially straight c, and should compile with few, if any changes. Note the error - message in CalcParkCoeff2. It warns of the possibility of convergence failure, but you will - find that the iteration count NITER, isn't always an indicator of convergence problems when - it is less than 3, as stated in the original Fortran code comments. - - If you find a problem with this code, please leave us a note on: - http://www.iowahills.com/feedbackcomments.html - - */ - -#include "NewParksMcClellan.h" -#include -#define M_2PI 6.28318530717958647692 - -namespace kitiirfir -{ - -//--------------------------------------------------------------------------- - -// Global variables. -int HalfTapCount, ExchangeIndex[PARKS_SMALL]; -double LeGrangeD[PARKS_SMALL], Alpha[PARKS_SMALL], CosOfGrid[PARKS_SMALL], - DesPlus[PARKS_SMALL]; -double Coeff[PARKS_SMALL], Edge[PARKS_SMALL], BandMag[PARKS_SMALL], - InitWeight[PARKS_SMALL]; -double DesiredMag[PARKS_BIG], Grid[PARKS_BIG], Weight[PARKS_BIG]; - -void NewParksMcClellan(double *FirCoeff, int NumTaps, TFIRPassTypes PassType, - double OmegaC, double BW, double ParksWidth) { - if (NumTaps > MAX_NUM_PARKS_TAPS) - return; - int j, NumBands; - - // Note: There is no feedback to the caller if ParksWidth or NumTaps are modified here. - if (PassType == firBPF || PassType == firNOTCH || NumTaps > 70) { - if (ParksWidth > 0.15) - ParksWidth = 0.15; // Else we have covergence problems. - } - - if (PassType == firNOTCH || PassType == firHPF) { - if (NumTaps % 2 == 0) - NumTaps++; // High pass and notch filters must have odd tap counts. - } - - if (NumTaps > MAX_NUM_PARKS_TAPS) - NumTaps = MAX_NUM_PARKS_TAPS; - - // It helps the algorithm a great deal if each band is at least 0.01 wide. - // The weights used here came from the orig PM code. - if (PassType == firLPF) { - NumBands = 2; - Edge[1] = 0.0; // Omega = 0 - Edge[2] = OmegaC; // Pass band edge - if (Edge[2] < 0.01) - Edge[2] = 0.01; - if (Edge[2] > 0.98) - Edge[2] = 0.98; - Edge[3] = Edge[2] + ParksWidth; // Stop band edge - if (Edge[3] > 0.99) - Edge[3] = 0.99; - Edge[4] = 1.0; // Omega = Pi - BandMag[1] = 1.0; - BandMag[2] = 0.0; - InitWeight[1] = 1.0; - InitWeight[2] = 10.0; - } - - if (PassType == firHPF) { - NumBands = 2; - Edge[1] = 0.0; // Omega = 0 - Edge[3] = OmegaC; // Pass band edge - if (Edge[3] > 0.99) - Edge[3] = 0.99; - if (Edge[3] < 0.02) - Edge[3] = 0.02; - Edge[2] = Edge[3] - ParksWidth; // Stop band edge - if (Edge[2] < 0.01) - Edge[2] = 0.01; - Edge[4] = 1.0; // Omega = Pi - BandMag[1] = 0.0; - BandMag[2] = 1.0; - InitWeight[1] = 10.0; - InitWeight[2] = 1.0; - } - - if (PassType == firBPF) { - NumBands = 3; - Edge[1] = 0.0; // Omega = 0 - Edge[3] = OmegaC - BW / 2.0; // Left pass band edge. - if (Edge[3] < 0.02) - Edge[3] = 0.02; - Edge[2] = Edge[3] - ParksWidth; // Left stop band edge - if (Edge[2] < 0.01) - Edge[2] = 0.01; - Edge[4] = OmegaC + BW / 2.0; // Right pass band edge - if (Edge[4] > 0.98) - Edge[4] = 0.98; - Edge[5] = Edge[4] + ParksWidth; // Right stop band edge - if (Edge[5] > 0.99) - Edge[5] = 0.99; - Edge[6] = 1.0; // Omega = Pi - - BandMag[1] = 0.0; - BandMag[2] = 1.0; - BandMag[3] = 0.0; - InitWeight[1] = 10.0; - InitWeight[2] = 1.0; - InitWeight[3] = 10.0; - } - - // This algorithm tends to have problems with narrow band notch filters. - if (PassType == firNOTCH) { - NumBands = 3; - Edge[1] = 0.0; // Omega = 0 - Edge[3] = OmegaC - BW / 2.0; // Left stop band edge. - if (Edge[3] < 0.02) - Edge[3] = 0.02; - Edge[2] = Edge[3] - ParksWidth; // Left pass band edge - if (Edge[2] < 0.01) - Edge[2] = 0.01; - Edge[4] = OmegaC + BW / 2.0; // Right stop band edge - if (Edge[4] > 0.98) - Edge[4] = 0.98; - Edge[5] = Edge[4] + ParksWidth; // Right pass band edge - if (Edge[5] > 0.99) - Edge[5] = 0.99; - Edge[6] = 1.0; // Omega = Pi - - BandMag[1] = 1.0; - BandMag[2] = 0.0; - BandMag[3] = 1.0; - InitWeight[1] = 1.0; - InitWeight[2] = 10.0; - InitWeight[3] = 1.0; - } - - // Parks McClellan's edges are based on 2Pi, we are based on Pi. - for (j = 1; j <= 2 * NumBands; j++) - Edge[j] /= 2.0; - - CalcParkCoeff2(NumBands, NumTaps, FirCoeff); - -} -//--------------------------------------------------------------------------- - -void CalcParkCoeff2(int NumBands, int TapCount, double *FirCoeff) { - int j, k, GridCount, GridIndex, BandIndex, NumIterations; - double LowFreqEdge, UpperFreq, TempVar, Change; - bool OddNumTaps; - GridCount = 16; // Grid Density - - if (TapCount % 2) - OddNumTaps = true; - else - OddNumTaps = false; - - HalfTapCount = TapCount / 2; - if (OddNumTaps) - HalfTapCount++; - - Grid[1] = Edge[1]; - LowFreqEdge = GridCount * HalfTapCount; - LowFreqEdge = 0.5 / LowFreqEdge; - j = 1; - k = 1; - BandIndex = 1; - while (BandIndex <= NumBands) { - UpperFreq = Edge[k + 1]; - while (Grid[j] <= UpperFreq) { - TempVar = Grid[j]; - DesiredMag[j] = BandMag[BandIndex]; - Weight[j] = InitWeight[BandIndex]; - j++; - ; - Grid[j] = TempVar + LowFreqEdge; - } - - Grid[j - 1] = UpperFreq; - DesiredMag[j - 1] = BandMag[BandIndex]; - Weight[j - 1] = InitWeight[BandIndex]; - k += 2; - BandIndex++; - if (BandIndex <= NumBands) - Grid[j] = Edge[k]; - } - - GridIndex = j - 1; - if (!OddNumTaps && Grid[GridIndex] > (0.5 - LowFreqEdge)) - GridIndex--; - - if (!OddNumTaps) { - for (j = 1; j <= GridIndex; j++) { - Change = cos(M_PI * Grid[j]); - DesiredMag[j] = DesiredMag[j] / Change; - Weight[j] = Weight[j] * Change; - } - } - - TempVar = (double) (GridIndex - 1) / (double) HalfTapCount; - for (j = 1; j <= HalfTapCount; j++) { - ExchangeIndex[j] = (int) ((double) (j - 1) * TempVar + 1.0); - } - ExchangeIndex[HalfTapCount + 1] = GridIndex; - - NumIterations = Remez2(GridIndex); - CalcCoefficients(); - - // Calculate the impulse response. - if (OddNumTaps) { - for (j = 1; j <= HalfTapCount - 1; j++) { - Coeff[j] = 0.5 * Alpha[HalfTapCount + 1 - j]; - } - Coeff[HalfTapCount] = Alpha[1]; - } else { - Coeff[1] = 0.25 * Alpha[HalfTapCount]; - for (j = 2; j <= HalfTapCount - 1; j++) { - Coeff[j] = - 0.25 - * (Alpha[HalfTapCount + 1 - j] - + Alpha[HalfTapCount + 2 - j]); - } - Coeff[HalfTapCount] = 0.5 * Alpha[1] + 0.25 * Alpha[2]; - } - - // Output section. - for (j = 1; j <= HalfTapCount; j++) - FirCoeff[j - 1] = Coeff[j]; - if (OddNumTaps) - for (j = 1; j < HalfTapCount; j++) - FirCoeff[HalfTapCount + j - 1] = Coeff[HalfTapCount - j]; - else - for (j = 1; j <= HalfTapCount; j++) - FirCoeff[HalfTapCount + j - 1] = Coeff[HalfTapCount - j + 1]; - - // Display the iteration count. - if (NumIterations < 3) { - // ShowMessage("Parks McClellan unable to coverge"); - } - -} - -//--------------------------------------------------------------------------------------- -int Remez2(int GridIndex) { - int j, JET, K, k, NU, JCHNGE, K1, KNZ, KLOW, NUT, KUP; - int NUT1 = 0, LUCK, KN, NITER; - double Deviation, DNUM, DDEN, TempVar; - double DEVL, COMP = 0.0, YNZ = 0.0, Y1 = 0.0, ERR; - - LUCK = 0; - DEVL = -1.0; - NITER = 1; // Init this to 1 to be consistent with the orig code. - - TOP_LINE: // We come back to here from 3 places at the bottom. - ExchangeIndex[HalfTapCount + 2] = GridIndex + 1; - - for (j = 1; j <= HalfTapCount + 1; j++) { - TempVar = Grid[ExchangeIndex[j]]; - CosOfGrid[j] = cos(TempVar * M_2PI); - } - - JET = (HalfTapCount - 1) / 15 + 1; - for (j = 1; j <= HalfTapCount + 1; j++) { - LeGrangeD[j] = LeGrangeInterp2(j, HalfTapCount + 1, JET); - } - - DNUM = 0.0; - DDEN = 0.0; - K = 1; - for (j = 1; j <= HalfTapCount + 1; j++) { - k = ExchangeIndex[j]; - DNUM += LeGrangeD[j] * DesiredMag[k]; - DDEN += (double) K * LeGrangeD[j] / Weight[k]; - K = -K; - } - Deviation = DNUM / DDEN; - - NU = 1; - if (Deviation > 0.0) - NU = -1; - Deviation = -(double) NU * Deviation; - K = NU; - for (j = 1; j <= HalfTapCount + 1; j++) { - k = ExchangeIndex[j]; - TempVar = (double) K * Deviation / Weight[k]; - DesPlus[j] = DesiredMag[k] + TempVar; - K = -K; - } - - if (Deviation <= DEVL) - return (NITER); // Ouch - - DEVL = Deviation; - JCHNGE = 0; - K1 = ExchangeIndex[1]; - KNZ = ExchangeIndex[HalfTapCount + 1]; - KLOW = 0; - NUT = -NU; - - //Search for the extremal frequencies of the best approximation. - - j = 1; - while (j < HalfTapCount + 2) { - KUP = ExchangeIndex[j + 1]; - k = ExchangeIndex[j] + 1; - NUT = -NUT; - if (j == 2) - Y1 = COMP; - COMP = Deviation; - - if (k < KUP && !ErrTest(k, NUT, COMP, &ERR)) { - L210: COMP = (double) NUT * ERR; - for (k++; k < KUP; k++) { - if (ErrTest(k, NUT, COMP, &ERR)) - break; // for loop - COMP = (double) NUT * ERR; - } - - ExchangeIndex[j] = k - 1; - j++; - KLOW = k - 1; - JCHNGE++; - continue; // while loop - } - - k--; - - L225: k--; - if (k <= KLOW) { - k = ExchangeIndex[j] + 1; - if (JCHNGE > 0) { - ExchangeIndex[j] = k - 1; - j++; - KLOW = k - 1; - JCHNGE++; - continue; // while loop - } else // JCHNGE <= 0 - { - for (k++; k < KUP; k++) { - if (ErrTest(k, NUT, COMP, &ERR)) - continue; // for loop - goto L210; - } - - KLOW = ExchangeIndex[j]; - j++; - continue; // while loop - } - } - // Can't use a do while loop here, it would outdent the two continue statements. - if (ErrTest(k, NUT, COMP, &ERR) && JCHNGE <= 0) - goto L225; - - if (ErrTest(k, NUT, COMP, &ERR)) { - KLOW = ExchangeIndex[j]; - j++; - continue; // while loop - } - - COMP = (double) NUT * ERR; - - L235: for (k--; k > KLOW; k--) { - if (ErrTest(k, NUT, COMP, &ERR)) - break; // for loop - COMP = (double) NUT * ERR; - } - - KLOW = ExchangeIndex[j]; - ExchangeIndex[j] = k + 1; - j++; - JCHNGE++; - } // end while(j ExchangeIndex[1]) - K1 = ExchangeIndex[1]; - if (KNZ < ExchangeIndex[HalfTapCount + 1]) - KNZ = ExchangeIndex[HalfTapCount + 1]; - NUT1 = NUT; - NUT = -NU; - k = 0; - KUP = K1; - COMP = YNZ * 1.00001; - LUCK = 1; - - for (k++; k < KUP; k++) { - if (ErrTest(k, NUT, COMP, &ERR)) - continue; // for loop - j = HalfTapCount + 2; - goto L210; - } - LUCK = 2; - break; // break while(j <= HalfTapCount+2) loop - } // end while(j <= HalfTapCount+2) - - if (LUCK == 1 || LUCK == 2) { - if (LUCK == 1) { - if (COMP > Y1) - Y1 = COMP; - K1 = ExchangeIndex[HalfTapCount + 2]; - } - - k = GridIndex + 1; - KLOW = KNZ; - NUT = -NUT1; - COMP = Y1 * 1.00001; - - for (k--; k > KLOW; k--) { - if (ErrTest(k, NUT, COMP, &ERR)) - continue; // for loop - j = HalfTapCount + 2; - COMP = (double) NUT * ERR; - LUCK = 3; // last time in this if(LUCK == 1 || LUCK == 2) - goto L235; - } - - if (LUCK == 2) { - if (JCHNGE > 0 && NITER++ < ITRMAX) - goto TOP_LINE; - else - return (NITER); - } - - for (j = 1; j <= HalfTapCount; j++) { - ExchangeIndex[HalfTapCount + 2 - j] = ExchangeIndex[HalfTapCount + 1 - - j]; - } - ExchangeIndex[1] = K1; - if (NITER++ < ITRMAX) - goto TOP_LINE; - } // end if(LUCK == 1 || LUCK == 2) - - KN = ExchangeIndex[HalfTapCount + 2]; - for (j = 1; j <= HalfTapCount; j++) { - ExchangeIndex[j] = ExchangeIndex[j + 1]; - } - ExchangeIndex[HalfTapCount + 1] = KN; - if (NITER++ < ITRMAX) - goto TOP_LINE; - - return (NITER); - -} - -//----------------------------------------------------------------------- -// Function to calculate the lagrange interpolation coefficients for use in the function gee. -double LeGrangeInterp2(int K, int N, int M) // D - { - int j, k; - double Dee, Q; - Dee = 1.0; - Q = CosOfGrid[K]; - for (k = 1; k <= M; k++) - for (j = k; j <= N; j += M) { - if (j != K) - Dee = 2.0 * Dee * (Q - CosOfGrid[j]); - } - if (fabs(Dee) < MIN_TEST_VAL) { - if (Dee < 0.0) - Dee = -MIN_TEST_VAL; - else - Dee = MIN_TEST_VAL; - } - return (1.0 / Dee); -} - -//----------------------------------------------------------------------- -// Function to evaluate the frequency response using the Lagrange interpolation -// formula in the barycentric form. -double GEE2(int K, int N) { - int j; - double P, C, Dee, XF; - P = 0.0; - XF = Grid[K]; - XF = cos(M_2PI * XF); - Dee = 0.0; - for (j = 1; j <= N; j++) { - C = XF - CosOfGrid[j]; - if (fabs(C) < MIN_TEST_VAL) { - if (C < 0.0) - C = -MIN_TEST_VAL; - else - C = MIN_TEST_VAL; - } - C = LeGrangeD[j] / C; - Dee = Dee + C; - P = P + C * DesPlus[j]; - } - if (fabs(Dee) < MIN_TEST_VAL) { - if (Dee < 0.0) - Dee = -MIN_TEST_VAL; - else - Dee = MIN_TEST_VAL; - } - return (P / Dee); -} - -//----------------------------------------------------------------------- - -bool ErrTest(int k, int Nut, double Comp, double *Err) { - *Err = GEE2(k, HalfTapCount + 1); - *Err = (*Err - DesiredMag[k]) * Weight[k]; - if ((double) Nut * *Err - Comp <= 0.0) - return (true); - else - return (false); -} - -//----------------------------------------------------------------------- - -// Calculation of the coefficients of the best approximation using the inverse discrete fourier transform. -void CalcCoefficients(void) { - int j, k, n; - double GTempVar, OneOverNumTaps; - double Omega, TempVar, FreqN, TempX, GridCos; - double GeeArray[PARKS_SMALL]; - - GTempVar = Grid[1]; - CosOfGrid[HalfTapCount + 2] = -2.0; - OneOverNumTaps = 1.0 / (double) (2 * HalfTapCount - 1); - k = 1; - - for (j = 1; j <= HalfTapCount; j++) { - FreqN = (double) (j - 1) * OneOverNumTaps; - TempX = cos(M_2PI * FreqN); - - GridCos = CosOfGrid[k]; - if (TempX <= GridCos) { - while (TempX <= GridCos && (GridCos - TempX) >= MIN_TEST_VAL) // MIN_TEST_VAL = 1.0E-6 - { - k++; - ; - GridCos = CosOfGrid[k]; - } - } - if (TempX <= GridCos || (TempX - GridCos) < MIN_TEST_VAL) { - GeeArray[j] = DesPlus[k]; // Desired Response - } else { - Grid[1] = FreqN; - GeeArray[j] = GEE2(1, HalfTapCount + 1); - } - if (k > 1) - k--; - } - - Grid[1] = GTempVar; - for (j = 1; j <= HalfTapCount; j++) { - TempVar = 0.0; - Omega = (double) (j - 1) * M_2PI * OneOverNumTaps; - for (n = 1; n <= HalfTapCount - 1; n++) { - TempVar += GeeArray[n + 1] * cos(Omega * (double) n); - } - TempVar = 2.0 * TempVar + GeeArray[1]; - Alpha[j] = TempVar; - } - - Alpha[1] = Alpha[1] * OneOverNumTaps; - for (j = 2; j <= HalfTapCount; j++) { - Alpha[j] = 2.0 * Alpha[j] * OneOverNumTaps; - } - -} - -//----------------------------------------------------------------------- - -} // namespace - diff --git a/kitiirfir/NewParksMcClellan.h b/kitiirfir/NewParksMcClellan.h deleted file mode 100644 index 16af86df1..000000000 --- a/kitiirfir/NewParksMcClellan.h +++ /dev/null @@ -1,31 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef NewParksMcClellanH -#define NewParksMcClellanH - -//--------------------------------------------------------------------------- -#define PARKS_BIG 1100 // Per the original code, this must be > 8 * MaxNumTaps = 8 * 128 = 1024 -#define PARKS_SMALL 256 // This needs to be greater than or equal to MAX_NUM_PARKS_TAPS -#define MAX_NUM_PARKS_TAPS 127 // This was the limit set in the original code. -#define ITRMAX 50 // Max Number of Iterations. Some Notch and BPF are running ~ 43 -#define MIN_TEST_VAL 1.0E-6 // Min value used in LeGrangeInterp and GEE - -// If the FIRFilterCode.cpp file is in the project along with this NewParksMcClellan file, -// we need to include FIRFilterCode.h for the TFIRPassTypes enum. -#include "FIRFilterCode.h" -//enum TFIRPassTypes {firLPF, firHPF, firBPF, firNOTCH, ftNOT_FIR}; - -namespace kitiirfir -{ - -void NewParksMcClellan(double *FirCoeff, int NumTaps, TFIRPassTypes PassType, double OmegaC, double BW, double ParksWidth); -void CalcParkCoeff2(int NBANDS, int NFILT, double *FirCoeff); -double LeGrangeInterp2(int K, int N, int M); -double GEE2(int K, int N); -int Remez2(int GridIndex); -bool ErrTest(int k, int Nut, double Comp, double *Err); -void CalcCoefficients(void); - -} // namespace - -#endif diff --git a/kitiirfir/PFiftyOneRevE.cpp b/kitiirfir/PFiftyOneRevE.cpp deleted file mode 100644 index a896ffcbc..000000000 --- a/kitiirfir/PFiftyOneRevE.cpp +++ /dev/null @@ -1,649 +0,0 @@ - -//--------------------------------------------------------------------------- - -#include "PFiftyOneRevE.h" -#include "QuadRootsRevH.h" -#include -#include - -//--------------------------------------------------------------------------- -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - Sept 21, 2016 Rev E - - If you find a problem with this code, please leave a note on: - http://www.iowahills.com/feedbackcomments.html - - This is for polynomials with real coefficients, up to 100th order. - - PFiftyOne has 4 loops. The outermost while loop runs until the polynomial's order - N, has been reduced to 1 or 2. - - The TypeOfK loop controls how the K polynomial is initialized. About 99.999% of all - roots are found with TypeOfK = 0. - - The AngleNumber loop controls how we initialize the 2nd order polynomial TUV. - The angles move between the 1st and 2nd quadrants and are defined in the - SetTUVandK() function. About 99% of roots are found in the 1st 4 angles. - - The Iter loop first calls the QuadIterate routine, which finds quadratic factors, - real or complex. If QuadIterate fails to converge in QUAD_ITER_MAX iterations, - the RealIterate routine is called to try to extract a single real root. - - The input array Coeff[] contains the coefficients arranged in descending order. - For example, a 4th order poly would be: - P(x) = Coeff[0]*x^4 + Coeff[1]*x^3 + Coeff[2]*x^2 + Coeff[3]*x + Coeff[4] - - On return, there is no particular ordering to the roots, but complex root pairs - will be in adjacent array cells. It may be helpful to know that the complex root pairs - are exact cunjugates of each other, meaning that you can use the == operator - to search for complex pairs in the arrays.F - - This returns Degree on success, 0 on failure. - */ - -namespace kitiirfir -{ - -//--------------------------------------------------------------------------- -// None of the code uses long double variables except for the P51 root finder code -// where long doubles were essential. FindRoots is an interface to P51 for code that is -// using complex variables CplxD. -int FindRoots(int N, double *Coeff, std::complex *Roots) { - int j; - long double P[P51_ARRAY_SIZE], RealRoot[P51_ARRAY_SIZE], - ImagRoot[P51_ARRAY_SIZE]; - - for (j = 0; j <= N; j++) - P[j] = Coeff[j]; // double to long double - N = PFiftyOne(P, N, RealRoot, ImagRoot); - for (j = 0; j < N; j++) - Roots[j] = std::complex((double) RealRoot[j], - (double) ImagRoot[j]); // long double to double - return (N); -} - -//--------------------------------------------------------------------------- - -int PFiftyOne(long double *Coeff, int Degree, long double *RealRoot, - long double *ImagRoot) { - if (Degree > P51_MAXDEGREE || Degree < 0) { - //ShowMessage("Poly Degree is out of range in the P51 Root Finder."); - return (0); - } - - TUpdateStatus UpdateStatus; - int N, NZ, j, Iter, AngleNumber, TypeOfK; - long double RealZero, QuadX; - long double TUV[3]; - long double *P, *QuadQP, *RealQP, *QuadK, *RealK, *QK; - - N = Degree; // N is decremented as roots are found. - - P = new (std::nothrow) long double[N + 2]; - QuadQP = new (std::nothrow) long double[N + 2]; - RealQP = new (std::nothrow) long double[N + 2]; - QuadK = new (std::nothrow) long double[N + 2]; - RealK = new (std::nothrow) long double[N + 2]; - QK = new (std::nothrow) long double[N + 2]; - if (P == 0 || QuadQP == 0 || RealQP == 0 || QuadK == 0 - || RealK == 0 || QK == 0) { - //ShowMessage("Memory not Allocated in PFiftyOne root finder."); - return (0); - } - - for (j = 0; j <= N; j++) - P[j] = Coeff[j]; // Copy the coeff. P gets modified. - for (j = 0; j < N; j++) - RealRoot[j] = ImagRoot[j] = 0.0; // Init to zero, in case any leading or trailing zeros are removed. - - // Remove trailing zeros. A tiny P[N] relative to P[N-1] is not allowed. - while (N > 0 && fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1])) { - N--; - } - - // Remove leading zeros. - while (N > 0 && P[0] == 0.0) { - for (j = 0; j < N; j++) - P[j] = P[j + 1]; - N--; - } - - // P[0] must = 1 - if (P[0] != 1.0) { - for (j = 1; j <= N; j++) - P[j] /= P[0]; - P[0] = 1.0; - } - - TypeOfK = 0; - while (N > 4 && TypeOfK < MAX_NUM_K) { - NZ = 0; // Num Zeros found. (Used in the loop controls below.) - QuadX = powl(fabsl(P[N]), 1.0 / (long double) N) / 2.0; // QuadX is used to init TUV - - for (TypeOfK = 0; TypeOfK < MAX_NUM_K && NZ == 0; TypeOfK++) // Iterate on the different possible QuadK inits. - { - for (AngleNumber = 0; AngleNumber < MAX_NUM_ANGLES && NZ == 0; - AngleNumber++) // Iterate on the angle used to init TUV. - { - SetTUVandK(P, N, TUV, RealK, QuadK, QuadX, AngleNumber, - TypeOfK); // Init TUV and both K's - for (Iter = 0; Iter < N && NZ == 0; Iter++) // Allow N calls to QuadIterate for N*QUAD_ITER_MAX iterations, then try a different angle. - { - NZ = QuadIterate(Iter, P, QuadQP, QuadK, QK, N, TUV, - &UpdateStatus); // NZ = 2 for a pair of complex roots or 2 real roots. - - if (NZ == 0) // Try for a real root. - { - if (fabsl(QuadK[N - 2]) > TINY_VALUE * fabsl(P[N])) - RealZero = -P[N] / QuadK[N - 2]; // This value gets refined by QuadIterate. - else - RealZero = 0.0; - NZ = RealIterate(Iter, P, RealQP, RealK, QK, N, - &RealZero); // NZ = 1 for a single real root. - } - - if (NZ == 0 && UpdateStatus == BAD_ANGLE) - break; // If RealIterate failed and UpdateTUV called this a bad angle, it's pointless to iterate further on this angle. - - } // Iter loop Note the use of NZ in the loop controls. - } // AngleNumber loop - } // TypeOfK loop - - // Done iterating. If NZ==0 at this point, we failed and will exit below. - // Decrement N, and set P to the quotient QP. QP = P/TUV or QP = P/(x-RealZero) - if (NZ == 2) // Store a pair of complex roots or 2 real roots. - { - j = Degree - N; - QuadRoots(TUV, &RealRoot[j], &ImagRoot[j]); - N -= NZ; - for (j = 0; j <= N; j++) - P[j] = QuadQP[j]; - TypeOfK = 0; - } - - if (NZ == 1) // Store a single real root - { - j = Degree - N; - RealRoot[j] = RealZero; - ImagRoot[j] = 0.0; - N -= NZ; - for (j = 0; j <= N; j++) - P[j] = RealQP[j]; - TypeOfK = 0; - } - - // Remove any trailing zeros on P. P[N] should never equal zero, but can approach zero - // because of roundoff errors. If P[N] is zero or tiny relative to P[N-1], we take the hit, - // and place a root at the origin. This needs to be checked, but virtually never happens. - while (fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1]) && N > 0) { - j = Degree - N; - RealRoot[j] = 0.0; - ImagRoot[j] = 0.0; - N--; - //ShowMessage("Removed a zero at the origin."); - } - - } // The outermost loop while(N > 2) - - delete[] QuadQP; - delete[] RealQP; - delete[] QuadK; - delete[] RealK; - delete[] QK; - - // Done, except for the last 1 or 2 roots. If N isn't 1 or 2 at this point, we failed. - if (N == 1) { - j = Degree - N; - RealRoot[j] = -P[1] / P[0]; - ImagRoot[j] = 0.0; - delete[] P; - return (Degree); - } - - if (N == 2) { - j = Degree - N; - QuadRoots(P, &RealRoot[j], &ImagRoot[j]); - delete[] P; - return (Degree); - } - - if (N == 3) { - j = Degree - N; - CubicRoots(P, &RealRoot[j], &ImagRoot[j]); - delete[] P; - return (Degree); - } - - if (N == 4) { - j = Degree - N; - BiQuadRoots(P, &RealRoot[j], &ImagRoot[j]); - delete[] P; - return (Degree); - } - - // ShowMessage("The P51 root finder failed to converge on a solution."); - return (0); - -} - -//--------------------------------------------------------------------------- -// The purpose of this function is to find a 2nd order polynomial, TUV, which is a factor of P. -// When called, the TUV and K polynomials have been initialized to our best guess. -// This function can call UpdateTUV() at most QUAD_ITER_MAX times. This returns 2 if we find -// 2 roots, else 0. UpdateStatus has two possible values on return, UPDATED and BAD_ANGLE. -// UPDATED means the UpdateTUV function is able to do its calculations with TUV at this angle. -// BAD_ANGLE tells P51 to move TUV to a different angle before calling this function again. -// We use ErrScalar to account for the wide variations in N and P[N]. ErrScalar can vary by -// as much as 1E50 if all the root locations are much less than 1 or much greater than 1. -// The ErrScalar equation was determined empirically. If this root finder is used in an app -// where the root locations and poly orders fall within a narrow range, esp low order, then -// ErrScalar can be modified to give more accurate results. - -int QuadIterate(int P51_Iter, long double *P, long double *QP, long double *K, - long double *QK, int N, long double *TUV, TUpdateStatus *UpdateStatus) { - int Iter; - long double Err, MinErr, ErrScalar, QKCheck; - - ErrScalar = 1.0 / (16.0 * powl((long double) N, 3.0) * fabsl(P[N])); - - P51_Iter *= QUAD_ITER_MAX; - Err = MinErr = 1.0E100; - *UpdateStatus = UPDATED; - QuadSynDiv(P, N, TUV, QP); // Init QP - QuadSynDiv(K, N - 1, TUV, QK); // Init QK - - for (Iter = 0; Iter < QUAD_ITER_MAX; Iter++) { - UpdateTUV(P51_Iter + Iter, P, N, QP, K, QK, TUV, UpdateStatus); - if (*UpdateStatus == BAD_ANGLE) { - return (0); // Failure, need a different angle. - } - - Err = fabsl(QP[N - 1]) + fabsl(QP[N + 1]); // QP[N-1] & QP[N+1] are the remainder terms of P/TUV. - Err *= ErrScalar; // Normalize the error. - - if (Err < LDBL_EPSILON) { - return (2); // Success!! 2 roots have been found. - } - - // ZERO_DEL means both DelU and DelV were ~ 0 in UpdateTUV which means the algorithm has stalled. - // It might be stalled in a dead zone with large error, or stalled because it can't adjust u and v with a resolution fine enough to meet our convergence criteria. - if (*UpdateStatus == ZERO_DEL) { - if (Err < 4.0 * (long double) N * LDBL_EPSILON) // Small error, this is the best we can do. - { - *UpdateStatus = UPDATED; - return (2); - } else // Large error, get a different angle - { - *UpdateStatus = BAD_ANGLE; - return (0); - } - } - - QKCheck = fabsl(QK[N - 2]) + fabsl(QK[N]); // QK[N-2] & QK[N] are the remainder terms of K/TUV. - QKCheck *= ErrScalar; - - // Huge values indicate the algorithm is diverging and overflow is imminent. This can indicate - // a single real root, or that we simply need a different angle on TUV. This happens frequently. - if (Err > HUGE_VALUE || QKCheck > HUGE_VALUE) { - *UpdateStatus = BAD_ANGLE; - return (0); - } - - // Record our best result thus far. We turn on the damper in UpdateTUV if the errs increase. - if (Err < MinErr) { - *UpdateStatus = DAMPER_OFF; - MinErr = Err; - } else if (Iter > 2) { - *UpdateStatus = DAMPER_ON; - } - } - - // If we get here, we didn't converge, but TUV is getting updated. - // If RealIterate can't find a real zero, this function will get called again. - *UpdateStatus = UPDATED; - return (0); -} - -//--------------------------------------------------------------------------- - -// This function updates TUV[1] and TUV[2] using the J-T mathematics. -// When called, UpdateStatus equals either DAMPER_ON or DAMPER_OFF, which controls the damping factor. -// On return UpdateStatus equals UPDATED, BAD_ANGLE or ZERO_DEL; -// BAD_ANGLE indicates imminent overflow, or TUV[2] is going to zero. In either case the QuadIterate -// function will immediately return to P51 which will change the angle on TUV. ZERO_DEL indicates -// we have either stalled in a dead zone, or we lack the needed precision to further refine TUV. -// TUV = tx^2 + ux + v t = 1 always. v = 0 (a root at the origin) isn't allowed. -// The poly degrees are P[N] QP[N-2] K[N-1] QK[N-3] -void UpdateTUV(int Iter, long double *P, int N, long double *QP, long double *K, - long double *QK, long double *TUV, TUpdateStatus *UpdateStatus) { - int j; - long double DelU, DelV, Denom; - long double E2, E3, E4, E5; - static int FirstDamperlIter; - - if (Iter == 0) - FirstDamperlIter = 0; // Reset this static var. - if (*UpdateStatus == DAMPER_ON) - FirstDamperlIter = Iter; - - // Update K, unless E3 is tiny relative to E2. The algorithm will work its way out of a tiny E3. - // These equations are from the Jenkins and Traub paper "A Three Stage Algorithm for Real Polynomials Using Quadratic Iteration" Equation 9.8 - E2 = QP[N] * QP[N] + TUV[1] * QP[N] * QP[N - 1] - + TUV[2] * QP[N - 1] * QP[N - 1]; - E3 = QP[N] * QK[N - 1] + TUV[1] * QP[N] * QK[N - 2] - + TUV[2] * QP[N - 1] * QK[N - 2]; - - if (fabsl(E3) * HUGE_VALUE > fabsl(E2)) { - E2 /= -E3; - for (j = 1; j <= N - 2; j++) - K[j] = E2 * QK[j - 1] + QP[j]; // At covergence, E2 ~ 0, so K ~ QP. - } else { - for (j = 1; j <= N - 2; j++) - K[j] = QK[j - 1]; - } - K[0] = QP[0]; // QP[0] = 1.0 always - K[N - 1] = 0.0; - - QuadSynDiv(K, N - 1, TUV, QK); // Update QK QK = K/TUV - - // These equations are modified versions of those used in the original Jenkins Traub Fortran algorithm RealPoly, found at www.netlib.org/toms/493 - E3 = QP[N] * QK[N - 1] + TUV[1] * QP[N] * QK[N - 2] - + TUV[2] * QP[N - 1] * QK[N - 2]; - E4 = QK[N - 1] * QK[N - 1] + TUV[1] * QK[N - 1] * QK[N - 2] - + TUV[2] * QK[N - 2] * QK[N - 2]; - E5 = QP[N - 1] * QK[N - 1] - QP[N] * QK[N - 2]; - - Denom = E5 * K[N - 2] * TUV[2] + E4 * P[N]; - if (fabsl(Denom) * HUGE_VALUE < fabsl(P[N])) { - *UpdateStatus = BAD_ANGLE; // Denom is tiny, overflow is imminent, get a new angle. - return; - } - - // Calc DelU and DelV. If they are effectively zero, bump them by epsilon. - DelU = E3 * K[N - 2] * TUV[2] / Denom; - if (fabsl(DelU) < LDBL_EPSILON * fabsl(TUV[1])) { - if (DelU < 0.0) - DelU = -fabsl(TUV[1]) * LDBL_EPSILON; - else - DelU = fabsl(TUV[1]) * LDBL_EPSILON; - } - - DelV = -E5 * K[N - 2] * TUV[2] * TUV[2] / Denom; - if (fabsl(DelV) < LDBL_EPSILON * fabsl(TUV[2])) { - if (DelV < 0.0) - DelV = -fabsl(TUV[2]) * LDBL_EPSILON; - else - DelV = fabsl(TUV[2]) * LDBL_EPSILON; - } - - // If we haven't converged by QUAD_ITER_MAX iters, we need to test DelU and DelV for effectiveness. - if (Iter >= QUAD_ITER_MAX - 1) { - // We can't improve u and v any further because both DelU and DelV ~ 0 This usually happens when we are near a solution, but we don't have the precision needed to ine u and v enough to meet our convergence criteria. This can also happen in a dead zone where the errors are large, which means we need a different angle on TUV. We test for this in the QuadIterate function. - if (fabsl(DelU) < 8.0 * LDBL_EPSILON * fabsl(TUV[1]) - && fabsl(DelV) < 8.0 * LDBL_EPSILON * fabsl(TUV[2])) { - *UpdateStatus = ZERO_DEL; - return; - } - // A big change after this many iterations means we are wasting our time on this angle. - if (fabsl(DelU) > 10.0 * fabsl(TUV[1]) - || fabsl(DelV) > 10.0 * fabsl(TUV[2])) { - *UpdateStatus = BAD_ANGLE; - return; - } - } - - // Dampen the changes for 3 iterations after Damper was set in QuadIterate. - if (Iter - FirstDamperlIter < 3) { - DelU *= 0.75; - DelV *= 0.75; - } - - // Update U and V - TUV[1] += DelU; - if (fabsl(TUV[2] + DelV) < TINY_VALUE) - DelV *= 0.9; // If this, we would set TUV[2] = 0 which we can't allow, so we use 90% of DelV. - TUV[2] += DelV; - - if (fabsl(TUV[2]) < fabsl(TUV[1]) * TINY_VALUE) { - *UpdateStatus = BAD_ANGLE; // TUV[2] is effectively 0, which is never allowed. - return; - } - - *UpdateStatus = UPDATED; // TUV was updated. - QuadSynDiv(P, N, TUV, QP); // Update QP QP = P/TUV -} - -//--------------------------------------------------------------------------- - -// This function is used to find single real roots. It is similar to Newton's Method. -// Horners method is used to calculate QK and QP. For an explanation of these methods, see these 2 links. -// http://mathworld.wolfram.com/NewtonsMethod.html http://en.wikipedia.org/wiki/Horner%27s_method - -// When called, RealZero contains our best guess for a root, and K is init to the 1st derivative of P. -// The return value is either 1 or 0, the number of roots found. On return, RealZero contains -// the root, and QP contains the next P (i.e. P with this root removed). -// As with QuadIterate, at convergence, K = QP -int RealIterate(int P51_Iter, long double *P, long double *QP, long double *K, - long double *QK, int N, long double *RealZero) { - int Iter, k; - long double X, DelX, Damper, Err, ErrScalar; - static long double PrevQPN; - - ErrScalar = 1.0 / (16.0 * powl((long double) N, 2.0) * fabsl(P[N])); - - X = *RealZero; // Init with our best guess for X. - if (P51_Iter == 0) - PrevQPN = 0.0; - QK[0] = K[0]; - for (k = 1; k <= N - 1; k++) { - QK[k] = QK[k - 1] * X + K[k]; - } - - for (Iter = 0; Iter < REAL_ITER_MAX; Iter++) { - // Calculate a new QP. This is poly division QP = P/(x+X) QP[0] to QP[N-1] is the quotient. - // The remainder is QP[N], which is P(X), the error term. - QP[0] = P[0]; - for (k = 1; k <= N; k++) { - QP[k] = QP[k - 1] * X + P[k]; - } - Err = fabsl(QP[N]) * ErrScalar; // QP[N] is the error. ErrScalar accounts for the wide variations in N and P[N]. - - if (Err < LDBL_EPSILON) { - *RealZero = X; - return (1); // Success!! - } else if (Err > HUGE_VALUE) - return (0); // Overflow is imminent. - - // Calculate a new K. QK[N-1] is the remainder of K /(x-X). - // QK[N-1] is approximately P'(X) when P(X) = QP[N] ~ 0 - if (fabsl(QK[N - 1]) > fabsl(P[N] * TINY_VALUE)) { - DelX = -QP[N] / QK[N - 1]; - K[0] = QP[0]; - for (k = 1; k <= N - 1; k++) { - K[k] = DelX * QK[k - 1] + QP[k]; - } - } else // Use this if QK[N-1] ~ 0 - { - K[0] = 0.0; - for (k = 1; k <= N - 1; k++) - K[k] = QK[k - 1]; - } - - if (fabsl(K[N - 1]) > HUGE_VALUE) - return (0); // Overflow is imminent. - - // Calculate a new QK. This is poly division QK = K /(x+X). QK[0] to QK[N-2] is the quotient. - QK[0] = K[0]; - for (k = 1; k <= N - 1; k++) { - QK[k] = QK[k - 1] * X + K[k]; - } - if (fabsl(QK[N - 1]) <= TINY_VALUE * fabsl(P[N])) - return (0); // QK[N-1] ~ 0 will cause overflow below. - - // This algorithm routinely oscillates back and forth about a zero with nearly equal pos and neg error magnitudes. - // If the current and previous error add to give a value less than the current error, we dampen the change. - Damper = 1.0; - if (fabsl(QP[N] + PrevQPN) < fabsl(QP[N])) - Damper = 0.5; - PrevQPN = QP[N]; - - // QP[N] is P(X) and at convergence QK[N-1] ~ P'(X), so this is ~ Newtons Method. - DelX = QP[N] / QK[N - 1] * Damper; - if (X != 0.0) { - if (fabsl(DelX) < LDBL_EPSILON * fabsl(X)) // If true, the algorithm is stalled, so bump X by 2 epsilons. - { - if (DelX < 0.0) - DelX = -2.0 * X * LDBL_EPSILON; - else - DelX = 2.0 * X * LDBL_EPSILON; - } - } else // X = 0 - { - if (DelX == 0.0) - return (0); // Stalled at the origin, so exit. - } - X -= DelX; // Update X - - } // end of loop - - // If we get here, we failed to converge. - return (0); -} - -//--------------------------------------------------------------------------- - -// Derivative of P. Called from SetTUVandK(). -void DerivOfP(long double *P, int N, long double *dP) { - int j; - long double Power; - for (j = 0; j < N; j++) { - Power = N - j; - dP[j] = Power * P[j]; - } - dP[N] = 0.0; - -} -//--------------------------------------------------------------------------- - -// Synthetic Division of P by x^2 + ux + v (TUV) -// The qotient is Q[0] to Q[N-2]. The actual poly remainder terms are Q[N-1] and Q[N+1] -// The JT math requires the values Q[N-1] and Q[N] to calculate K. -// These form a 1st order remainder b*x + (b*u + a). -void QuadSynDiv(long double *P, int N, long double *TUV, long double *Q) { - int j; - Q[0] = P[0]; - Q[1] = P[1] - TUV[1] * Q[0]; - for (j = 2; j <= N; j++) { - Q[j] = P[j] - TUV[1] * Q[j - 1] - TUV[2] * Q[j - 2]; - } - -// Here we calculate the final remainder term used to calculate the convergence error. -// This and Q[N-1] are the remainder values you get if you do this poly division manually. - Q[N + 1] = Q[N - 1] * TUV[1] + Q[N]; // = b*u + a -} - -//--------------------------------------------------------------------------- - -// This function intializes the TUV and K polynomials. -void SetTUVandK(long double *P, int N, long double *TUV, long double *RealK, - long double *QuadK, long double X, int AngleNumber, int TypeOfQuadK) { - int j; - long double a, Theta; - - // These angles define our search pattern in the complex plane. We start in the 1st quadrant, - // go to the 2nd, then the real axis, etc. The roots are conjugates, so there isn't a need - // to go to the 3rd or 4th quadrants. The first 2 angles find about 99% of all roots. - const long double Angle[] = { 45.0, 135.0, 0.0, 90.0, 15.0, 30.0, 60.0, - 75.0, 105.0, 120.0, 150.0, - 165.0, // 12 angles - 6.0, 51.0, 96.0, 141.0, 12.0, 57.0, 102.0, 147.0, 21.0, 66.0, 111.0, - 156.0, 27.0, 72.0, 117.0, 162.0, 36.0, 81.0, 126.0, 171.0, 42.0, - 87.0, 132.0, 177.0, 3.0, 48.0, 93.0, 138.0, 9.0, 54.0, 99.0, 144.0, - 18.0, 63.0, 108.0, 153.0, 24.0, 69.0, 114.0, 159.0, 33.0, 78.0, - 123.0, 168.0, 39.0, 84.0, 129.0, - 174.0, // 60 angles - 46.0, 136.0, 91.0, 1.0, 16.0, 31.0, 61.0, 76.0, 106.0, 121.0, 151.0, - 166.0, 7.0, 52.0, 97.0, 142.0, 13.0, 58.0, 103.0, 148.0, 22.0, 67.0, - 112.0, 157.0, 28.0, 73.0, 118.0, 163.0, 37.0, 82.0, 127.0, 172.0, - 43.0, 88.0, 133.0, 178.0, 4.0, 49.0, 94.0, 139.0, 10.0, 55.0, 100.0, - 145.0, 19.0, 64.0, 109.0, 154.0, 25.0, 70.0, 115.0, 160.0, 34.0, - 79.0, 124.0, 169.0, 40.0, 85.0, 130.0, 175.0, 47.0, 137.0, 92.0, - 2.0, 17.0, 32.0, 62.0, 77.0, 107.0, 122.0, 152.0, 167.0, 8.0, 53.0, - 98.0, 143.0, 14.0, 59.0, 104.0, 149.0, 23.0, 68.0, 113.0, 158.0, - 29.0, 74.0, 119.0, 164.0, 38.0, 83.0, 128.0, 173.0, 44.0, 89.0, - 134.0, 179.0, 5.0, 50.0, 95.0, 140.0, 11.0, 56.0, 101.0, 146.0, - 20.0, 65.0, 110.0, 155.0, 26.0, 71.0, 116.0, 161.0, 35.0, 80.0, - 125.0, 170.0, 41.0, 86.0, 131.0, 176.0 }; // 180 angles - - // Initialize TUV to form (x - (a + jb)) * (x - (a - jb)) = x^2 - 2ax + a^2 + b^2 - // We init TUV for complex roots, except at angle 0, where we use real roots at +/- X - if (AngleNumber == 2) // At 0 degrees we int to 2 real roots at +/- X. - { - TUV[0] = 1.0; // t - TUV[1] = 0.0; // u - TUV[2] = -(X * X); // v - } else // We init to a complex root at a +/- jb - { - Theta = Angle[AngleNumber] / 180.0 * M_PI; - a = X * cosl(Theta); - //b = X * sinl(Theta); - TUV[0] = 1.0; - TUV[1] = -2.0 * a; - TUV[2] = X * X; // = a*a + b*b because cos^2 + sin^2 = 1 - } - - // The code below initializes the K polys used by RealIterate and QuadIterate. - - // Initialize the K poly used in RealIterate to P'. - DerivOfP(P, N, RealK); - RealK[N] = 0.0; - - // Initialize the K poly used in QuadIterate. Initializing QuadK to P" works virtually - // 100% of the time, but if P51 stalls on a difficult root, these alternate inits give - // us a way to work out of the stall. All these inits work almost as well as P". - if (TypeOfQuadK == 0) // Init QuadK 2nd derivative of P - { - long double *Temp = new (std::nothrow) long double[N + 2]; - if (Temp == 0) { - //ShowMessage("Memory not Allocated in PFiftyOne SetTUVandK."); - return; - } - - DerivOfP(P, N, Temp); - DerivOfP(Temp, N - 1, QuadK); - QuadK[N] = QuadK[N - 1] = 0.0; - delete[] Temp; - } - - else if (TypeOfQuadK == 1) // Set QuadK to QP, because K = QP at convergence. - { - QuadSynDiv(P, N, TUV, QuadK); - QuadK[N] = QuadK[N - 1] = 0.0; - } - - else if (TypeOfQuadK == 2) // Set QuadK to the 1st derivative of P - { - for (j = 0; j <= N - 2; j++) - QuadK[j] = RealK[j + 1]; - QuadK[N] = QuadK[N - 1] = 0.0; - } - - else // Set QuadK to zero, except QuadK[0]. - { - for (j = 1; j <= N; j++) - QuadK[j] = 0.0; - QuadK[0] = 1.0; - } - - if (QuadK[0] == 0.0) - QuadK[0] = 1.0; // This can happen if TypeOfQuadK == 2 and P[1] == 0.0 - for (j = N - 2; j > 0; j--) - QuadK[j] /= QuadK[0]; - QuadK[0] = 1.0; -} - -//--------------------------------------------------------------------------- - -} // namespace diff --git a/kitiirfir/PFiftyOneRevE.h b/kitiirfir/PFiftyOneRevE.h deleted file mode 100644 index 7b34144ed..000000000 --- a/kitiirfir/PFiftyOneRevE.h +++ /dev/null @@ -1,43 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef PFiftyOneRevEH -#define PFiftyOneRevEH -//--------------------------------------------------------------------------- - -#include - -// TUpdateStatus represent the status of the UpdateTUV function. -// The first 3 are returned from UpdateTUV. The last two are sent to UpdateTUV. -enum TUpdateStatus {UPDATED, BAD_ANGLE, ZERO_DEL, DAMPER_ON, DAMPER_OFF}; - - -// This value for LDBL_EPSILON is for an 80 bit variable (64 bit significand). It should be in float.h -#define LDBL_EPSILON 1.084202172485504434E-19L // = 2^-63 -#define P51_MAXDEGREE 100 // The max poly order allowed. Used at the top of P51. This was set arbitrarily. -#define P51_ARRAY_SIZE 102 // P51 uses the new operator. P51 arrays must be MaxDegree + 2 -#define MAX_NUM_ANGLES 180 // The number of defined angles for initializing TUV. -#define REAL_ITER_MAX 20 // Max number of iterations in RealIterate. -#define QUAD_ITER_MAX 20 // Max number of iterations in QuadIterate. -#define MAX_NUM_K 4 // This is the number of methods we have to define K. -#define MAX_NEWT_ITERS 10 // Used to limit the Newton Iterations in the GetX function. -#define TINY_VALUE 1.0E-30 // This is what we use to test for zero. Usually to avoid divide by zero. -#define HUGE_VALUE 1.0E200 // This gets used to test for imminent overflow. - -namespace kitiirfir -{ - -int FindRoots(int N, double *Coeff, std::complex *Roots); -int PFiftyOne(long double *Coeff, int Degree, long double *RealRoot, long double *ImagRoot); -int QuadIterate(int Iter, long double *P, long double *QP, long double *K, long double *QK, int N, long double *TUV, TUpdateStatus *UpdateStatus); -void UpdateTUV(int Iter, long double *P, int N, long double *QP, long double *K, long double *QK, long double *TUV, TUpdateStatus *UpdateStatus); -int RealIterate(int P51_Iter, long double *P, long double *QP, long double *K, long double *QK, int N, long double *RealZero); -void QuadraticFormula(long double *TUV, long double *RealRoot, long double *ImagRoot); -void QuadSynDiv(long double *P, int N, long double *TUV, long double *Q); -void DerivOfP(long double *P, int N, long double *dP); -void SetTUVandK(long double *P, int N, long double *TUV, long double *RealK, long double *QuadK, long double X, int AngleNumber, int TypeOfQuadK); - -} // namespace - -#endif - - diff --git a/kitiirfir/QuadRootsRevH.cpp b/kitiirfir/QuadRootsRevH.cpp deleted file mode 100644 index d51393341..000000000 --- a/kitiirfir/QuadRootsRevH.cpp +++ /dev/null @@ -1,344 +0,0 @@ -/* - By Daniel Klostermann - Iowa Hills Software, LLC IowaHills.com - If you find a problem, please leave a note at: - http://www.iowahills.com/feedbackcomments.html - Sept 12, 2016 Rev H - - This root solver code finds 1st, 2nd, 3rd, and 4th order roots algebraically, as - opposed to iteration. - - It is composed of the functions: QuadCubicRoots, QuadRoots, CubicRoots, and BiQuadRoots. - This code originated at: http://www.netlib.org/toms/ Algorithm 326 - Roots of Low Order Polynomials by Terence R.F.Nonweiler CACM (Apr 1968) p269 - Original C translation by: M.Dow Australian National University, Canberra, Australia - - We use the same basic mathematics used in that code, but in order to improve numerical - accuracy we made extensive modifications to the test conditions that control the flow of - the algorithm, and also added scaling and reversals. - - The input array Coeff[] contains the coefficients arranged in descending order. - For example, a 4th order poly would be: - P(x) = Coeff[0]*x^4 + Coeff[1]*x^3 + Coeff[2]*x^2 + Coeff[3]*x + Coeff[4] - - The roots are returned in RootsReal and RootsImag. N is the polynomial's order. 1 <= N <= 4 - N is modified if there are leading or trailing zeros. N is returned. - Coeff needs to be length N+1. RealRoot and ImagRoot need to be length N. - - Do not call QuadRoots, CubicRoots, and BiQuadRoots directly. They assume that QuadCubicRoots - has removed leading and trailing zeros and normalized P. - - On a 2 GHz Pentium it takes about 2 micro sec for QuadCubicRoots to return. - - ShowMessage is a C++ Builder function, and it usage has been commented out. - If you are using C++ Builder, include vcl.h for ShowMessage. - Otherwise replace ShowMessage with something appropriate for your compiler. - */ - -#include "QuadRootsRevH.h" -#include - -namespace kitiirfir -{ - -//--------------------------------------------------------------------------- - -// Same interface as P51. -int QuadCubicRoots(long double *Coeff, int N, long double *RealRoot, - long double *ImagRoot) { - if (N < 1 || N > 4) { - //ShowMessage("Invalid Poly Order in QuadCubicRoots()"); - return (0); - } - - int j; - long double P[5]; - - // Must init to zero, in case N is reduced. - for (j = 0; j < N; j++) - RealRoot[j] = ImagRoot[j] = 0.0; - for (j = 0; j < 5; j++) - P[j] = 0.0; - - // The functions below modify the coeff array, so we pass P instead of Coeff. - for (j = 0; j <= N; j++) - P[j] = (long double) Coeff[j]; - - // Remove trailing zeros. A tiny P[N] relative to P[N-1] is not allowed. - while (N > 0 && fabsl(P[N]) <= TINY_VALUE * fabsl(P[N - 1])) { - N--; - } - - // Remove leading zeros. - while (N > 0 && P[0] == 0.0) { - for (j = 0; j < N; j++) - P[j] = P[j + 1]; - N--; - } - - // P[0] must = 1 - if (P[0] != 1.0) { - for (j = 1; j <= N; j++) - P[j] /= P[0]; - P[0] = 1.0; - } - - // Calculate the roots. - if (N == 4) - BiQuadRoots(P, RealRoot, ImagRoot); - else if (N == 3) - CubicRoots(P, RealRoot, ImagRoot); - else if (N == 2) - QuadRoots(P, RealRoot, ImagRoot); - else if (N == 1) { - RealRoot[0] = -P[1] / P[0]; - ImagRoot[0] = 0.0; - } - - return (N); -} - -//--------------------------------------------------------------------------- - -// This function is the quadratic formula with P[0] = 1 -// y = P[0]*x^2 + P[1]*x + P[2] -// Normally, we don't allow P[2] = 0, but this can happen in a call from BiQuadRoots. -// If P[2] = 0, the zero is returned in RealRoot[0]. -void QuadRoots(long double *P, long double *RealRoot, long double *ImagRoot) { - long double D; - D = P[1] * P[1] - 4.0 * P[2]; - if (D >= 0.0) // 1 or 2 real roots - { - RealRoot[0] = (-P[1] - sqrtl(D)) * 0.5; // = -P[1] if P[2] = 0 - RealRoot[1] = (-P[1] + sqrtl(D)) * 0.5; // = 0 if P[2] = 0 - ImagRoot[0] = ImagRoot[1] = 0.0; - } else // 2 complex roots - { - RealRoot[0] = RealRoot[1] = -P[1] * 0.5; - ImagRoot[0] = sqrtl(-D) * 0.5; - ImagRoot[1] = -ImagRoot[0]; - } -} - -//--------------------------------------------------------------------------- -// This finds the roots of y = P0x^3 + P1x^2 + P2x+ P3 P[0] = 1 -void CubicRoots(long double *P, long double *RealRoot, long double *ImagRoot) { - int j; - long double s, t, b, c, d, Scalar; - bool CubicPolyReversed = false; - - // Scale the polynomial so that P[N] = +/-1. This moves the roots toward unit circle. - Scalar = powl(fabsl(P[3]), 1.0 / 3.0); - for (j = 1; j <= 3; j++) - P[j] /= powl(Scalar, (long double) j); - - if (fabsl(P[3]) < fabsl(P[2]) && P[2] > 0.0) { - ReversePoly(P, 3); - CubicPolyReversed = true; - } - - s = P[1] / 3.0; - b = (6.0 * P[1] * P[1] * P[1] - 27.0 * P[1] * P[2] + 81.0 * P[3]) / 162.0; - t = (P[1] * P[1] - 3.0 * P[2]) / 9.0; - c = t * t * t; - d = 2.0 * P[1] * P[1] * P[1] - 9.0 * P[1] * P[2] + 27.0 * P[3]; - d = d * d / 2916.0 - c; - - // if(d > 0) 1 complex and 1 real root. We use LDBL_EPSILON to account for round off err. - if (d > LDBL_EPSILON) { - d = powl((sqrtl(d) + fabsl(b)), 1.0 / 3.0); - if (d != 0.0) { - if (b > 0) - b = -d; - else - b = d; - c = t / b; - } - d = M_SQRT3_2 * (b - c); - b = b + c; - c = -b / 2.0 - s; - - RealRoot[0] = (b - s); - ImagRoot[0] = 0.0; - RealRoot[1] = RealRoot[2] = c; - ImagRoot[1] = d; - ImagRoot[2] = -ImagRoot[1]; - } - - else // d < 0.0 3 real roots - { - if (b == 0.0) - d = M_PI_2 / 3.0; // b can be as small as 1.0E-25 - else - d = atanl(sqrtl(fabsl(d)) / fabsl(b)) / 3.0; - - if (b < 0.0) - b = 2.0 * sqrtl(fabsl(t)); - else - b = -2.0 * sqrtl(fabsl(t)); - - c = cosl(d) * b; - t = -M_SQRT3_2 * sinl(d) * b - 0.5 * c; - - RealRoot[0] = (t - s); - RealRoot[1] = -(t + c + s); - RealRoot[2] = (c - s); - ImagRoot[0] = 0.0; - ImagRoot[1] = 0.0; - ImagRoot[2] = 0.0; - } - - // If we reversed the poly, the roots need to be inverted. - if (CubicPolyReversed) - InvertRoots(3, RealRoot, ImagRoot); - - // Apply the Scalar to the roots. - for (j = 0; j < 3; j++) - RealRoot[j] *= Scalar; - for (j = 0; j < 3; j++) - ImagRoot[j] *= Scalar; -} - -//--------------------------------------------------------------------------- - -// This finds the roots of y = P0x^4 + P1x^3 + P2x^2 + P3x + P4 P[0] = 1 -// This function calls CubicRoots and QuadRoots -void BiQuadRoots(long double *P, long double *RealRoot, long double *ImagRoot) { - int j; - long double a, b, c, d, e, Q3Limit, Scalar, Q[4], MinRoot; - bool QuadPolyReversed = false; - - // Scale the polynomial so that P[N] = +/- 1. This moves the roots toward unit circle. - Scalar = powl(fabsl(P[4]), 0.25); - for (j = 1; j <= 4; j++) - P[j] /= powl(Scalar, (long double) j); - - // Having P[1] < P[3] helps with the Q[3] calculation and test. - if (fabsl(P[1]) > fabsl(P[3])) { - ReversePoly(P, 4); - QuadPolyReversed = true; - } - - a = P[2] - P[1] * P[1] * 0.375; - b = P[3] + P[1] * P[1] * P[1] * 0.125 - P[1] * P[2] * 0.5; - c = P[4] + 0.0625 * P[1] * P[1] * P[2] - - 0.01171875 * P[1] * P[1] * P[1] * P[1] - 0.25 * P[1] * P[3]; - e = P[1] * 0.25; - - Q[0] = 1.0; - Q[1] = P[2] * 0.5 - P[1] * P[1] * 0.1875; - Q[2] = (P[2] * P[2] - P[1] * P[1] * P[2] - + 0.1875 * P[1] * P[1] * P[1] * P[1] - 4.0 * P[4] + P[1] * P[3]) - * 0.0625; - Q[3] = -b * b * 0.015625; - - /* The value of Q[3] can cause problems when it should have calculated to zero (just above) but - is instead ~ -1.0E-17 because of round off errors. Consequently, we need to determine whether - a tiny Q[3] is due to roundoff, or if it is legitimately small. It can legitimately have values - of ~ -1E-28. When this happens, we assume Q[2] should also be small. Q[3] can also be tiny with - 2 sets of equal real roots. Then P[1] and P[3], are approx equal. */ - - Q3Limit = ZERO_MINUS; - if (fabsl(fabsl(P[1]) - fabsl(P[3])) >= ZERO_PLUS && Q[3] > ZERO_MINUS - && fabsl(Q[2]) < 1.0E-5) - Q3Limit = 0.0; - - if (Q[3] < Q3Limit && fabsl(Q[2]) < 1.0E20 * fabsl(Q[3])) { - CubicRoots(Q, RealRoot, ImagRoot); - - // Find the smallest positive real root. One of the real roots is always positive. - MinRoot = 1.0E100; - for (j = 0; j < 3; j++) { - if (ImagRoot[j] == 0.0 && RealRoot[j] > 0 && RealRoot[j] < MinRoot) - MinRoot = RealRoot[j]; - } - - d = 4.0 * MinRoot; - a += d; - if (a * b < 0.0) - Q[1] = -sqrtl(d); - else - Q[1] = sqrtl(d); - b = 0.5 * (a + b / Q[1]); - } else { - if (Q[2] < 0.0) // 2 sets of equal imag roots - { - b = sqrtl(fabsl(c)); - d = b + b - a; - if (d > 0.0) - Q[1] = sqrtl(fabsl(d)); - else - Q[1] = 0.0; - } else { - if (Q[1] > 0.0) - b = 2.0 * sqrtl(fabsl(Q[2])) + Q[1]; - else - b = -2.0 * sqrtl(fabsl(Q[2])) + Q[1]; - Q[1] = 0.0; - } - } - - // Calc the roots from two 2nd order polys and subtract e from the real part. - if (fabsl(b) > 1.0E-8) { - Q[2] = c / b; - QuadRoots(Q, RealRoot, ImagRoot); - - Q[1] = -Q[1]; - Q[2] = b; - QuadRoots(Q, RealRoot + 2, ImagRoot + 2); - - for (j = 0; j < 4; j++) - RealRoot[j] -= e; - } else // b==0 with 4 equal real roots - { - for (j = 0; j < 4; j++) - RealRoot[j] = -e; - for (j = 0; j < 4; j++) - ImagRoot[j] = 0.0; - } - - // If we reversed the poly, the roots need to be inverted. - if (QuadPolyReversed) - InvertRoots(4, RealRoot, ImagRoot); - - // Apply the Scalar to the roots. - for (j = 0; j < 4; j++) - RealRoot[j] *= Scalar; - for (j = 0; j < 4; j++) - ImagRoot[j] *= Scalar; -} - -//--------------------------------------------------------------------------- - -// A reversed polynomial has its roots at the same angle, but reflected about the unit circle. -void ReversePoly(long double *P, int N) { - int j; - long double Temp; - for (j = 0; j <= N / 2; j++) { - Temp = P[j]; - P[j] = P[N - j]; - P[N - j] = Temp; - } - if (P[0] != 0.0) { - for (j = N; j >= 0; j--) - P[j] /= P[0]; - } -} - -//--------------------------------------------------------------------------- -// This is used in conjunction with ReversePoly -void InvertRoots(int N, long double *RealRoot, long double *ImagRoot) { - int j; - long double Mag; - for (j = 0; j < N; j++) { - // complex math for 1/x - Mag = RealRoot[j] * RealRoot[j] + ImagRoot[j] * ImagRoot[j]; - if (Mag != 0.0) { - RealRoot[j] /= Mag; - ImagRoot[j] /= -Mag; - } - } -} -//--------------------------------------------------------------------------- - -} // namespace diff --git a/kitiirfir/QuadRootsRevH.h b/kitiirfir/QuadRootsRevH.h deleted file mode 100644 index f7e0e7544..000000000 --- a/kitiirfir/QuadRootsRevH.h +++ /dev/null @@ -1,27 +0,0 @@ -//--------------------------------------------------------------------------- - -#ifndef QuadRootsRevHH -#define QuadRootsRevHH -//--------------------------------------------------------------------------- - -#define LDBL_EPSILON 1.084202172485504434E-19L -// #define M_SQRT3 1.7320508075688772935274463L // sqrt(3) -#define M_SQRT3_2 0.8660254037844386467637231L // sqrt(3)/2 -// #define DBL_EPSILON 2.2204460492503131E-16 // 2^-52 typically defined in the compiler's float.h -#define ZERO_PLUS 8.88178419700125232E-16 // 2^-50 = 4*DBL_EPSILON -#define ZERO_MINUS -8.88178419700125232E-16 -#define TINY_VALUE 1.0E-30 // This is what we use to test for zero. Usually to avoid divide by zero. - -namespace kitiirfir -{ - -void QuadRoots(long double *P, long double *RealPart, long double *ImagPart); -void CubicRoots(long double *P, long double *RealPart, long double *ImagPart); -void BiQuadRoots(long double *P, long double *RealPart, long double *ImagPart); -void ReversePoly(long double *P, int N); -void InvertRoots(int N, long double *RealRoot, long double *ImagRoot); - -} // namespace - -#endif - diff --git a/kitiirfir/readme.md b/kitiirfir/readme.md deleted file mode 100644 index 86fff210b..000000000 --- a/kitiirfir/readme.md +++ /dev/null @@ -1,8 +0,0 @@ -

IIR and FIR filter kit

- -This is the code for IIR and FIR filter design that can be found [here](http://www.iowahills.com/) -Thnks to Iowa Hills Software for providing this code for free. - -This is only the filter design part that can be downloaded [here](http://www.iowahills.com/8DownloadPage.html) Under "IR FIR Source Code Kit". It also includes some dependencies found on the other files in this page. - -Code has been re-implemented in more modern C++ and moving it into a kitiirfir namespace \ No newline at end of file diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 5d7c0c60d..19ee567bf 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -51,7 +51,7 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : DSBFilter = new fftfilt(m_config.m_Bandwidth / m_config.m_inputSampleRate, 2*ssbFftLen); //m_pll.computeCoefficients(0.05f, 0.707f, 1000.0f); // bandwidth, damping factor, loop gain m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain - m_fll.computeCoefficients(0.004f); // ~100Hz @ 48 kHz + m_fll.setSampleRate(48000); apply(true); @@ -260,13 +260,15 @@ void ChannelAnalyzerNG::apply(bool force) { int sampleRate = m_running.m_channelSampleRate / (1< FreqLockComplex::FreqLockComplex() : - m_a0(1.0), - m_a1(1.0), - m_a2(1.0), - m_b0(1.0), - m_b1(1.0), - m_b2(1.0), - m_v0(0.0), - m_v1(0.0), - m_v2(0.0), + m_a0(0.998), + m_a1(0.002), m_y(1.0, 0.0), - m_prod(1.0, 0.0), m_yRe(1.0), m_yIm(0.0), m_freq(0.0), m_phi(0.0), - m_iir(0) + m_phiX0(0.0), + m_phiX1(0.0), + m_y1(0.0f) { } FreqLockComplex::~FreqLockComplex() { - if (m_iir) { - delete m_iir; - } } void FreqLockComplex::reset() { - m_v0 = 0.0f; - m_v1 = 0.0f; - m_v2 = 0.0f; m_y.real(1.0); m_y.imag(0.0); - m_prod.real(1.0); - m_prod.imag(0.0); m_yRe = 1.0f; m_yIm = 0.0f; m_freq = 0.0f; m_phi = 0.0f; + m_phiX0 = 0.0f; + m_phiX1 = 0.0f; + m_y1 = 0.0f; } -// wn is in terms of Nyquist. For example, if the sampling frequency = 20 kHz -// and the 3 dB corner frequency is 1.5 kHz, then OmegaC = 0.15 -// i.e. 100.0 / (SR/2) or 200 / SR for 100 Hz -void FreqLockComplex::computeCoefficients(float wn) +void FreqLockComplex::setSampleRate(unsigned int sampleRate) { - kitiirfir::TIIRFilterParams params; - - params.BW = 0.0; // For band pass and notch filters - unused here - params.Gamma = 0.0; // For Adjustable Gauss. - unused here - params.IIRPassType = kitiirfir::iirLPF; - params.NumPoles = 1; - params.OmegaC = wn; - params.ProtoType = kitiirfir::BUTTERWORTH; - params.Ripple = 0.0; // For Elliptic and Chebyshev - unused here - params.StopBanddB = 0.0; // For Elliptic and Inverse Chebyshev - unused here - params.dBGain = 0.0; - - kitiirfir::TIIRCoeff coeff = kitiirfir::CalcIIRFilterCoeff(params); - float a[3], b[3]; - - a[0] = coeff.a0[0]; - a[1] = coeff.a1[0]; - a[2] = coeff.a2[0]; - b[0] = coeff.b0[0]; - b[1] = coeff.b1[0]; - b[2] = coeff.b2[0]; - - qDebug("FreqLockComplex::computeCoefficients: b: %f %f %f", b[0], b[1], b[2]); - qDebug("FreqLockComplex::computeCoefficients: a: %f %f %f", a[0], a[1], a[2]); - - m_iir = new IIRFilter(a, b); + m_a1 = 10.0f / sampleRate; // 1 - alpha + m_a0 = 1.0f - m_a1; // alpha + reset(); } void FreqLockComplex::feed(float re, float im) { m_yRe = cos(m_phi); m_yIm = sin(m_phi); - std::complex y(m_yRe, m_yIm); + m_y.real(m_yRe); + m_y.imag(m_yIm); std::complex x(re, im); + m_phiX0 = std::arg(x); - std::complex prod = x * m_y; + float eF = normalizeAngle(m_phiX0 - m_phiX1); + float fHat = m_a1*eF + m_a0*m_y1; + m_y1 = fHat; - // Discriminator: cross * sign(dot) / dt - float cross = m_prod.real()*prod.imag() - prod.real()*m_prod.imag(); - float dot = m_prod.real()*prod.real() + m_prod.imag()*prod.imag(); - float eF = cross * (dot < 0 ? -1 : 1); // frequency error - - // LPF section - float efHat = m_iir->run(eF); - - m_freq = efHat; // correct instantaneous frequency - m_phi += efHat; // advance phase with instantaneous frequency - m_prod = prod; // store previous product + m_freq = fHat; // correct instantaneous frequency + m_phi += fHat; // advance phase with instantaneous frequency + m_phiX1 = m_phiX0; +} + +float FreqLockComplex::normalizeAngle(float angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; } diff --git a/sdrbase/dsp/freqlockcomplex.h b/sdrbase/dsp/freqlockcomplex.h index e860821d0..279fe3fc7 100644 --- a/sdrbase/dsp/freqlockcomplex.h +++ b/sdrbase/dsp/freqlockcomplex.h @@ -24,7 +24,6 @@ #define SDRBASE_DSP_FREQLOCKCOMPLEX_H_ #include "dsp/dsptypes.h" -#include "iirfilter.h" #include "export.h" /** General purpose Phase-locked loop using complex analytic signal input. */ @@ -35,27 +34,27 @@ public: ~FreqLockComplex(); void reset(); - void computeCoefficients(float wn); + void setSampleRate(unsigned int sampleRate); /** Feed PLL with a new signa sample */ void feed(float re, float im); + const std::complex& getComplex() const { return m_y; } + float getReal() const { return m_yRe; } + float getImag() const { return m_yIm; } private: + /** Normalize angle in radians into the [-pi,+pi] region */ + static float normalizeAngle(float angle); + float m_a0; float m_a1; - float m_a2; - float m_b0; - float m_b1; - float m_b2; - float m_v0; - float m_v1; - float m_v2; std::complex m_y; - std::complex m_prod; float m_yRe; float m_yIm; float m_freq; float m_phi; - IIRFilter *m_iir; + float m_phiX0; + float m_phiX1; + float m_y1; }; From 9f483786774b54d44de3abd6132936c6a2830bec Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 18 May 2018 19:03:54 +0200 Subject: [PATCH 424/956] Channel analyzer NG: return of the lock status indicator and PLL frequency shift for PSK modulated signals --- plugins/channelrx/chanalyzerng/chanalyzerng.h | 1 + .../chanalyzerng/chanalyzernggui.cpp | 12 +++++- sdrbase/dsp/phaselockcomplex.cpp | 43 +++++++++++++++++-- sdrbase/dsp/phaselockcomplex.h | 6 ++- sdrbase/util/movingaverage.h | 5 +++ 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index 44670ee57..770fdbd96 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -163,6 +163,7 @@ public: int getChannelSampleRate() const { return m_running.m_channelSampleRate; } double getMagSq() const { return m_magsq; } bool isPllLocked() const { return m_running.m_pll && m_pll.locked(); } + Real getPllFrequency() const { return m_pll.getFreq(); } Real getPllDeltaPhase() const { return m_pll.getDeltaPhi(); } Real getPllPhase() const { return m_pll.getPhiHat(); } diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 4d63a3b34..586f22612 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -237,6 +237,12 @@ void ChannelAnalyzerNGGUI::tick() } else { ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); } + + if (ui->pll->isChecked()) + { + int freq = (m_channelAnalyzer->getPllFrequency() * m_channelAnalyzer->getChannelSampleRate()) / (2.0*M_PI); + ui->pll->setToolTip(tr("PLL lock. Freq = %1 Hz").arg(freq)); + } } void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) @@ -251,8 +257,12 @@ void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) } } -void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked __attribute__((unused))) +void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) { + if (!checked) { + ui->pll->setToolTip(tr("PLL lock")); + } + applySettings(); } diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index 1eb0b1edc..87b8b6158 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -43,8 +43,10 @@ PhaseLockComplex::PhaseLockComplex() : m_freq(0.0), m_freqPrev(0.0), m_lockCount(0), + m_lockFreq(0.026f), m_pskOrder(1), - m_lockTime(480) + m_lockTime(480), + m_lockTimeCount(0) { } @@ -87,6 +89,7 @@ void PhaseLockComplex::setPskOrder(unsigned int order) void PhaseLockComplex::setSampleRate(unsigned int sampleRate) { m_lockTime = sampleRate / 100; // 10ms for order 1 + m_lockFreq = (2.0*M_PI*5.0) / sampleRate; // +/- 5 Hz frequency swing reset(); } @@ -108,6 +111,7 @@ void PhaseLockComplex::reset() m_freq = 0.0f; m_freqPrev = 0.0f; m_lockCount = 0; + m_lockTimeCount = 0; } void PhaseLockComplex::feed(float re, float im) @@ -151,8 +155,41 @@ void PhaseLockComplex::feed(float re, float im) m_phiHat += 2.0*M_PI; } - // lock estimation - if (m_pskOrder <= 1) + // lock and frequency estimation + if (m_pskOrder > 1) + { + float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); + m_freq = 0.001*dPhi + 0.999*m_freqPrev; + + if (m_lockTimeCount < m_lockTime-1) + { + m_lockTimeCount++; + } + else + { + float dF = m_freq - m_freqTest; + + if ((dF > -m_lockFreq) && (dF < m_lockFreq)) + { + if (m_lockCount < 20) { + m_lockCount++; + } + } + else + { + if (m_lockCount > 0) { + m_lockCount--; + } + } + + m_freqTest = m_freq; + m_lockTimeCount = 0; + } + + m_freqPrev = m_freq; + m_phiHatPrev = m_phiHat; + } + else { m_freq = (m_phiHat - m_phiHatPrev) / (2.0*M_PI); diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index baf4af9b7..cd52e72d0 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -49,7 +49,8 @@ public: const std::complex& getComplex() const { return m_y; } float getReal() const { return m_yRe; } float getImag() const { return m_yIm; } - bool locked() const { return m_pskOrder > 1 ? false : m_lockCount > m_lockTime-2; } + bool locked() const { return m_pskOrder > 1 ? m_lockCount > 16 : m_lockCount > m_lockTime-2; } + float getFreq() const { return m_freq; } float getDeltaPhi() const { return m_deltaPhi; } float getPhiHat() const { return m_phiHat; } @@ -75,9 +76,12 @@ private: float m_yIm; float m_freq; float m_freqPrev; + float m_freqTest; int m_lockCount; + float m_lockFreq; unsigned int m_pskOrder; int m_lockTime; + int m_lockTimeCount; }; #endif /* SDRBASE_DSP_PHASELOCKCOMPLEX_H_ */ diff --git a/sdrbase/util/movingaverage.h b/sdrbase/util/movingaverage.h index 0461fbb09..74ac1e94e 100644 --- a/sdrbase/util/movingaverage.h +++ b/sdrbase/util/movingaverage.h @@ -90,6 +90,11 @@ class MovingAverageUtilVar m_samples.resize(size); } + unsigned int size() const + { + return m_samples.size(); + } + void operator()(T sample) { if (m_num_samples < m_samples.size()) // fill up From 3e5bcf7e0092dde05ccff0b21fc5f1a3e1fda69a Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 19 May 2018 05:03:56 +0200 Subject: [PATCH 425/956] Channel analyzer NG: created settings --- plugins/channelrx/chanalyzerng/CMakeLists.txt | 2 + .../channelrx/chanalyzerng/chanalyzerng.cpp | 131 +++++++++++++++++- plugins/channelrx/chanalyzerng/chanalyzerng.h | 37 ++++- .../chanalyzerng/chanalyzerngsettings.cpp | 110 +++++++++++++++ .../chanalyzerng/chanalyzerngsettings.h | 51 +++++++ 5 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp create mode 100644 plugins/channelrx/chanalyzerng/chanalyzerngsettings.h diff --git a/plugins/channelrx/chanalyzerng/CMakeLists.txt b/plugins/channelrx/chanalyzerng/CMakeLists.txt index 773912188..e3d9f3c75 100644 --- a/plugins/channelrx/chanalyzerng/CMakeLists.txt +++ b/plugins/channelrx/chanalyzerng/CMakeLists.txt @@ -4,12 +4,14 @@ set(chanalyzerng_SOURCES chanalyzerng.cpp chanalyzernggui.cpp chanalyzerngplugin.cpp + chanalyzerngsettings.cpp ) set(chanalyzerng_HEADERS chanalyzerng.h chanalyzernggui.h chanalyzerngplugin.h + chanalyzerngsettings.h ) set(chanalyzerng_FORMS diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 19ee567bf..f66c44e85 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -26,6 +26,7 @@ #include "dsp/downchannelizer.h" MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzer, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzerOld, Message) MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgReportChannelSampleRateChanged, Message) @@ -81,7 +82,7 @@ void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, bool fll, unsigned int pllPskOrder) { - Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, fll, pllPskOrder); + Message* cmd = MsgConfigureChannelAnalyzerOld::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, fll, pllPskOrder); messageQueue->push(cmd); } @@ -162,9 +163,9 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) cfg.getCenterFrequency()); return true; } - else if (MsgConfigureChannelAnalyzer::match(cmd)) + else if (MsgConfigureChannelAnalyzerOld::match(cmd)) { - MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; + MsgConfigureChannelAnalyzerOld& cfg = (MsgConfigureChannelAnalyzerOld&) cmd; m_config.m_channelSampleRate = cfg.getChannelSampleRate(); m_config.m_Bandwidth = cfg.getBandwidth(); @@ -300,3 +301,127 @@ void ChannelAnalyzerNG::apply(bool force) m_running.m_pllPskOrder = m_config.m_pllPskOrder; //m_settingsMutex.unlock(); } + +void ChannelAnalyzerNG::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) +{ + qDebug() << "ChannelAnalyzerNG::applyChannelSettings:" + << " inputSampleRate: " << inputSampleRate + << " inputFrequencyOffset: " << inputFrequencyOffset; + + if ((m_inputFrequencyOffset != inputFrequencyOffset) || + (m_inputSampleRate != inputSampleRate) || force) + { + m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); + } + + if ((m_inputSampleRate != inputSampleRate) || force) + { + m_settingsMutex.lock(); + + m_interpolator.create(16, inputSampleRate, inputSampleRate / 2.2f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_downSampleRate; + + if (!m_settings.m_downSample) + { + setFilters(inputSampleRate, m_settings.m_bandwidth, m_settings.m_lowCutoff); + m_pll.setSampleRate(inputSampleRate / (1<create_filter(lowCutoff / sampleRate, bandwidth / sampleRate); + DSBFilter->create_dsb_filter(bandwidth / sampleRate); +} + +void ChannelAnalyzerNG::applySettings(const ChannelAnalyzerNGSettings& settings, bool force) +{ + if ((settings.m_downSampleRate != m_settings.m_downSampleRate) || force) + { + m_settingsMutex.lock(); + m_interpolator.create(16, m_inputSampleRate, m_inputSampleRate / 2.2); + m_interpolatorDistanceRemain = 0.0f; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_downSampleRate; + m_settingsMutex.unlock(); + } + + if ((settings.m_downSample != m_settings.m_downSample) || force) + { + m_settingsMutex.lock(); + m_useInterpolator = settings.m_downSample; + + if (settings.m_downSample) + { + setFilters(settings.m_downSampleRate, settings.m_bandwidth, settings.m_lowCutoff); + m_pll.setSampleRate(settings.m_downSampleRate / (1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "chanalyzerngsettings.h" + +ChannelAnalyzerNGSettings::ChannelAnalyzerNGSettings() : + m_channelMarker(0) +{ + resetToDefaults(); +} + +void ChannelAnalyzerNGSettings::resetToDefaults() +{ + m_frequency = 0; + m_downSample = false; + m_downSampleRate = 0; + m_bandwidth = 5000; + m_lowCutoff = 300; + m_spanLog2 = 3; + m_ssb = false; + m_pll = false; + m_fll = false; + m_pllPskOrder = 1; +} + +QByteArray ChannelAnalyzerNGSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_frequency); + s.writeS32(2, m_bandwidth); + s.writeBlob(3, m_spectrumGUI->serialize()); + s.writeU32(4, m_rgbColor); + s.writeS32(5, m_lowCutoff); + s.writeS32(6, m_spanLog2); + s.writeBool(7, m_ssb); + s.writeBlob(8, m_scopeGUI->serialize()); + s.writeBool(9, m_downSample); + s.writeU32(10, m_downSampleRate); + s.writeBool(11, m_pll); + s.writeBool(12, m_fll); + s.writeU32(13, m_pllPskOrder); + + return s.final(); +} + +bool ChannelAnalyzerNGSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + + d.readS32(1, &m_frequency, 0); + d.readS32(2, &m_bandwidth, 5000); + + if (m_spectrumGUI) { + d.readBlob(3, &bytetmp); + m_spectrumGUI->deserialize(bytetmp); + } + + d.readU32(4, &m_rgbColor); + d.readS32(5, &m_lowCutoff, 3); + d.readS32(6, &m_spanLog2, 3); + d.readBool(7, &m_ssb, false); + + if (m_scopeGUI) { + d.readBlob(8, &bytetmp); + m_scopeGUI->deserialize(bytetmp); + } + + d.readBool(9, &m_downSample, false); + d.readU32(10, &m_downSampleRate, 2000U); + d.readBool(11, &m_pll, false); + d.readBool(12, &m_fll, false); + d.readU32(13, &m_pllPskOrder, 1); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h new file mode 100644 index 000000000..0ec677e76 --- /dev/null +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERNGSETTINGS_H_ +#define PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERNGSETTINGS_H_ + +#include + +class Serializable; + +struct ChannelAnalyzerNGSettings +{ + int m_frequency; + bool m_downSample; + quint32 m_downSampleRate; + int m_bandwidth; + int m_lowCutoff; + int m_spanLog2; + bool m_ssb; + bool m_pll; + bool m_fll; + unsigned int m_pllPskOrder; + quint32 m_rgbColor; + QString m_title; + Serializable *m_channelMarker; + Serializable *m_spectrumGUI; + Serializable *m_scopeGUI; + + ChannelAnalyzerNGSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + + +#endif /* PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERNGSETTINGS_H_ */ From d6f5de1ad7cd6daab1b85541c15f17c38d7f88dd Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 19 May 2018 05:10:17 +0200 Subject: [PATCH 426/956] Channel analyzer NG: created settings - correction --- plugins/channelrx/chanalyzerng/chanalyzerng.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index f66c44e85..b45a436d2 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -372,16 +372,13 @@ void ChannelAnalyzerNG::applySettings(const ChannelAnalyzerNGSettings& settings, if ((settings.m_downSample != m_settings.m_downSample) || force) { + int sampleRate = settings.m_downSample ? settings.m_downSampleRate : m_inputSampleRate; + m_settingsMutex.lock(); m_useInterpolator = settings.m_downSample; - - if (settings.m_downSample) - { - setFilters(settings.m_downSampleRate, settings.m_bandwidth, settings.m_lowCutoff); - m_pll.setSampleRate(settings.m_downSampleRate / (1< Date: Sun, 20 May 2018 01:10:08 +0200 Subject: [PATCH 427/956] Channel analyzer NG: use settings --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 255 ++++----- plugins/channelrx/chanalyzerng/chanalyzerng.h | 119 +---- .../chanalyzerng/chanalyzernggui.cpp | 485 +++++++----------- .../channelrx/chanalyzerng/chanalyzernggui.h | 15 +- .../chanalyzerng/chanalyzerngsettings.cpp | 9 +- .../chanalyzerng/chanalyzerngsettings.h | 2 + 6 files changed, 326 insertions(+), 559 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index b45a436d2..95c2cfcaa 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -48,13 +48,15 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : m_useInterpolator = false; m_interpolatorDistance = 1.0f; m_interpolatorDistanceRemain = 0.0f; - SSBFilter = new fftfilt(m_config.m_LowCutoff / m_config.m_inputSampleRate, m_config.m_Bandwidth / m_config.m_inputSampleRate, ssbFftLen); - DSBFilter = new fftfilt(m_config.m_Bandwidth / m_config.m_inputSampleRate, 2*ssbFftLen); + m_inputSampleRate = 48000; + m_inputFrequencyOffset = 0; + SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_inputSampleRate, m_settings.m_bandwidth / m_inputSampleRate, ssbFftLen); + DSBFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); //m_pll.computeCoefficients(0.05f, 0.707f, 1000.0f); // bandwidth, damping factor, loop gain m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain - m_fll.setSampleRate(48000); - apply(true); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); @@ -114,7 +116,7 @@ void ChannelAnalyzerNG::feed(const SampleVector::const_iterator& begin, const Sa if(m_sampleSink != 0) { - m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_running.m_ssb); // m_ssb = positive only + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_settings.m_ssb); // m_ssb = positive only } m_sampleBuffer.clear(); @@ -122,8 +124,81 @@ void ChannelAnalyzerNG::feed(const SampleVector::const_iterator& begin, const Sa m_settingsMutex.unlock(); } +void ChannelAnalyzerNG::processOneSample(Complex& c, fftfilt::cmplx *sideband) +{ + int n_out; + int decim = 1<runSSB(c, &sideband, m_usb); + } else { + n_out = DSBFilter->runDSB(c, &sideband); + } + + for (int i = 0; i < n_out; i++) + { + // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display + // smart decimation with bit gain using float arithmetic (23 bits significand) + + m_sum += sideband[i]; + + if (!(m_undersampleCount++ & (decim - 1))) // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) + { + m_sum /= decim; + Real re = m_sum.real() / SDR_RX_SCALED; + Real im = m_sum.imag() / SDR_RX_SCALED; + m_magsq = re*re + im*im; + Real mixI = 1.0f; + Real mixQ = 0.0f; + + if (m_settings.m_pll) + { + if (m_settings.m_fll) + { + m_fll.feed(re, im); + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + mixI = m_sum.real() * m_fll.getImag() - m_sum.imag() * m_fll.getReal(); + mixQ = m_sum.real() * m_fll.getReal() + m_sum.imag() * m_fll.getImag(); +// mixI = m_fll.getReal() * SDR_RX_SCALED; +// mixQ = m_fll.getImag() * SDR_RX_SCALED; + } + else + { + m_pll.feed(re, im); + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + mixI = m_sum.real() * m_pll.getImag() - m_sum.imag() * m_pll.getReal(); + mixQ = m_sum.real() * m_pll.getReal() + m_sum.imag() * m_pll.getImag(); + } + + if (m_settings.m_ssb & !m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(mixQ, mixI)); + } + else + { + m_sampleBuffer.push_back(Sample(mixI, mixQ)); + } + } + else + { + if (m_settings.m_ssb & !m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); + } + else + { + m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); + } + } + + m_sum = 0; + } + } +} + void ChannelAnalyzerNG::start() { + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); } void ChannelAnalyzerNG::stop() @@ -132,20 +207,14 @@ void ChannelAnalyzerNG::stop() bool ChannelAnalyzerNG::handleMessage(const Message& cmd) { - qDebug() << "ChannelAnalyzerNG::handleMessage: " << cmd.getIdentifier(); - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) { DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "ChannelAnalyzerNG::handleMessage: DownChannelizer::MsgChannelizerNotification:" + << " sampleRate: " << notif.getSampleRate() + << " frequencyOffset: " << notif.getFrequencyOffset(); - m_config.m_inputSampleRate = notif.getSampleRate(); - m_config.m_frequency = notif.getFrequencyOffset(); - - qDebug() << "ChannelAnalyzerNG::handleMessage: MsgChannelizerNotification:" - << " m_sampleRate: " << m_config.m_inputSampleRate - << " frequencyOffset: " << m_config.m_frequency; - - apply(); + applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); if (getMessageQueueToGUI()) { @@ -158,37 +227,25 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) else if (MsgConfigureChannelizer::match(cmd)) { MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; + qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelizer:" + << " sampleRate: " << cfg.getSampleRate() + << " centerFrequency: " << cfg.getCenterFrequency(); + m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); + cfg.getSampleRate(), + cfg.getCenterFrequency()); + return true; } - else if (MsgConfigureChannelAnalyzerOld::match(cmd)) - { - MsgConfigureChannelAnalyzerOld& cfg = (MsgConfigureChannelAnalyzerOld&) cmd; + else if (MsgConfigureChannelAnalyzer::match(cmd)) + { + qDebug("ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer"); + MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; - m_config.m_channelSampleRate = cfg.getChannelSampleRate(); - m_config.m_Bandwidth = cfg.getBandwidth(); - m_config.m_LowCutoff = cfg.getLoCutoff(); - m_config.m_spanLog2 = cfg.getSpanLog2(); - m_config.m_ssb = cfg.getSSB(); - m_config.m_pll = cfg.getPLL(); - m_config.m_fll = cfg.getFLL(); - m_config.m_pllPskOrder = cfg.getPLLPSKOrder(); + applySettings(cfg.getSettings(), cfg.getForce()); - qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:" - << " m_channelSampleRate: " << m_config.m_channelSampleRate - << " m_Bandwidth: " << m_config.m_Bandwidth - << " m_LowCutoff: " << m_config.m_LowCutoff - << " m_spanLog2: " << m_config.m_spanLog2 - << " m_ssb: " << m_config.m_ssb - << " m_pll: " << m_config.m_pll - << " m_fll: " << m_config.m_fll - << " m_pllPskOrder: " << m_config.m_pllPskOrder; - - apply(); - return true; - } + return true; + } else { if (m_sampleSink != 0) @@ -202,106 +259,6 @@ bool ChannelAnalyzerNG::handleMessage(const Message& cmd) } } -void ChannelAnalyzerNG::apply(bool force) -{ - if ((m_running.m_frequency != m_config.m_frequency) || - (m_running.m_inputSampleRate != m_config.m_inputSampleRate) || - force) - { - m_nco.setFreq(-m_config.m_frequency, m_config.m_inputSampleRate); - } - - if ((m_running.m_inputSampleRate != m_config.m_inputSampleRate) || - (m_running.m_channelSampleRate != m_config.m_channelSampleRate) || - force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, m_config.m_inputSampleRate, m_config.m_inputSampleRate / 2.2); - m_interpolatorDistanceRemain = 0.0f; - m_interpolatorDistance = (Real) m_config.m_inputSampleRate / (Real) m_config.m_channelSampleRate; - m_useInterpolator = (m_config.m_inputSampleRate != m_config.m_channelSampleRate); // optim - m_settingsMutex.unlock(); - } - - if ((m_running.m_channelSampleRate != m_config.m_channelSampleRate) || - (m_running.m_Bandwidth != m_config.m_Bandwidth) || - (m_running.m_LowCutoff != m_config.m_LowCutoff) || - force) - { - float bandwidth = m_config.m_Bandwidth; - float lowCutoff = m_config.m_LowCutoff; - - if (bandwidth < 0) - { - bandwidth = -bandwidth; - lowCutoff = -lowCutoff; - m_usb = false; - } - else - { - m_usb = true; - } - - if (bandwidth < 100.0f) - { - bandwidth = 100.0f; - lowCutoff = 0; - } - - m_settingsMutex.lock(); - - SSBFilter->create_filter(lowCutoff / m_config.m_channelSampleRate, bandwidth / m_config.m_channelSampleRate); - DSBFilter->create_dsb_filter(bandwidth / m_config.m_channelSampleRate); - - m_settingsMutex.unlock(); - } - - if ((m_running.m_channelSampleRate != m_config.m_channelSampleRate) || - (m_running.m_spanLog2 != m_config.m_spanLog2) || force) - { - int sampleRate = m_running.m_channelSampleRate / (1<runSSB(c, &sideband, m_usb); - } - else - { - n_out = DSBFilter->runDSB(c, &sideband); - } - - for (int i = 0; i < n_out; i++) - { - // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display - // smart decimation with bit gain using float arithmetic (23 bits significand) - - m_sum += sideband[i]; - - if (!(m_undersampleCount++ & (decim - 1))) // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) - { - m_sum /= decim; - Real re = m_sum.real() / SDR_RX_SCALED; - Real im = m_sum.imag() / SDR_RX_SCALED; - m_magsq = re*re + im*im; - Real mixI = 1.0f; - Real mixQ = 0.0f; - - if (m_running.m_pll) - { - if (m_running.m_fll) - { - m_fll.feed(re, im); - // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) - mixI = m_sum.real() * m_fll.getImag() - m_sum.imag() * m_fll.getReal(); - mixQ = m_sum.real() * m_fll.getReal() + m_sum.imag() * m_fll.getImag(); -// mixI = m_fll.getReal() * SDR_RX_SCALED; -// mixQ = m_fll.getImag() * SDR_RX_SCALED; - } - else - { - m_pll.feed(re, im); - // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) - mixI = m_sum.real() * m_pll.getImag() - m_sum.imag() * m_pll.getReal(); - mixQ = m_sum.real() * m_pll.getReal() + m_sum.imag() * m_pll.getImag(); - } - - if (m_running.m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(mixQ, mixI)); - } - else - { - m_sampleBuffer.push_back(Sample(mixI, mixQ)); - } - } - else - { - if (m_running.m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); - } - else - { - m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); - } - } - - m_sum = 0; - } - } - } + void processOneSample(Complex& c, fftfilt::cmplx *sideband); }; #endif // INCLUDE_CHANALYZERNG_H diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 586f22612..73278162a 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -29,6 +29,7 @@ #include "dsp/scopevis.h" #include "gui/glspectrum.h" #include "gui/glscopeng.h" +#include "gui/basicchannelsettingsdialog.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" @@ -66,133 +67,103 @@ qint64 ChannelAnalyzerNGGUI::getCenterFrequency() const void ChannelAnalyzerNGGUI::setCenterFrequency(qint64 centerFrequency) { m_channelMarker.setCenterFrequency(centerFrequency); + m_settings.m_frequency = m_channelMarker.getCenterFrequency(); applySettings(); } void ChannelAnalyzerNGGUI::resetToDefaults() { - blockApplySettings(true); + m_settings.resetToDefaults(); +} - ui->useRationalDownsampler->setChecked(false); - ui->BW->setValue(30); - ui->deltaFrequency->setValue(0); - ui->spanLog2->setCurrentIndex(3); +void ChannelAnalyzerNGGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(m_settings.m_frequency); + m_channelMarker.setBandwidth(m_settings.m_bandwidth * 2); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setLowCutoff(m_settings.m_lowCutoff); - blockApplySettings(false); - applySettings(); + if (m_settings.m_ssb) + { + if (m_settings.m_bandwidth < 0) { + m_channelMarker.setSidebands(ChannelMarker::lsb); + } else { + m_channelMarker.setSidebands(ChannelMarker::usb); + } + } + else + { + m_channelMarker.setSidebands(ChannelMarker::dsb); + } + + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + ui->useRationalDownsampler->setChecked(m_settings.m_downSample); + ui->BW->setValue(m_settings.m_bandwidth/100); + ui->lowCut->setValue(m_settings.m_lowCutoff/100); + ui->deltaFrequency->setValue(m_settings.m_frequency); + ui->spanLog2->setCurrentIndex(m_settings.m_spanLog2); + + blockApplySettings(false); + + setNewFinalRate(); +} + +void ChannelAnalyzerNGGUI::setSpectrumDisplay() +{ + qDebug("ChannelAnalyzerNGGUI::setSpectrumDisplay: m_rate: %d", m_rate); + if (m_settings.m_ssb) + { + ui->glSpectrum->setCenterFrequency(m_rate/4); + ui->glSpectrum->setSampleRate(m_rate/2); + ui->glSpectrum->setSsbSpectrum(true); + ui->glSpectrum->setLsbDisplay(ui->BW->value() < 0); + } + else + { + ui->glSpectrum->setCenterFrequency(0); + ui->glSpectrum->setSampleRate(m_rate); + ui->glSpectrum->setSsbSpectrum(false); + ui->glSpectrum->setLsbDisplay(false); + } } QByteArray ChannelAnalyzerNGGUI::serialize() const { - SimpleSerializer s(1); - s.writeS32(1, m_channelMarker.getCenterFrequency()); - s.writeS32(2, ui->BW->value()); - s.writeBlob(3, ui->spectrumGUI->serialize()); - s.writeU32(4, m_channelMarker.getColor().rgb()); - s.writeS32(5, ui->lowCut->value()); - s.writeS32(6, ui->spanLog2->currentIndex()); - s.writeBool(7, ui->ssb->isChecked()); - s.writeBlob(8, ui->scopeGUI->serialize()); - s.writeU64(9, ui->channelSampleRate->getValueNew()); - return s.final(); + return m_settings.serialize(); } bool ChannelAnalyzerNGGUI::deserialize(const QByteArray& data) { - SimpleDeserializer d(data); - - if(!d.isValid()) + if(m_settings.deserialize(data)) { - resetToDefaults(); - return false; - } - - if(d.getVersion() == 1) - { - QByteArray bytetmp; - quint32 u32tmp; - quint64 u64tmp; - qint32 tmp, spanLog2, bw, lowCut; - bool tmpBool; - - blockApplySettings(true); - m_channelMarker.blockSignals(true); - - d.readS32(1, &tmp, 0); - m_channelMarker.setCenterFrequency(tmp); - ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); - d.readS32(2, &bw, 30); - d.readBlob(3, &bytetmp); - ui->spectrumGUI->deserialize(bytetmp); - - if(d.readU32(4, &u32tmp)) - { - m_channelMarker.setColor(u32tmp); - } - - d.readS32(5, &lowCut, 3); - d.readS32(6, &spanLog2, 3); - d.readBool(7, &tmpBool, false); - ui->ssb->setChecked(tmpBool); - d.readBlob(8, &bytetmp); - ui->scopeGUI->deserialize(bytetmp); - d.readU64(9, &u64tmp, 2000U); - ui->channelSampleRate->setValue(u64tmp); - - blockApplySettings(false); - m_channelMarker.blockSignals(false); - m_channelMarker.emitChangedByAPI(); - - ui->spanLog2->setCurrentIndex(spanLog2); - setNewFinalRate(spanLog2); - ui->BW->setValue(bw); - ui->lowCut->setValue(lowCut); // does applySettings(); - - return true; - } + displaySettings(); + applySettings(true); // will have true + return true; + } else { - resetToDefaults(); - return false; - } + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); // will have true + return false; + } } bool ChannelAnalyzerNGGUI::handleMessage(const Message& message) { if (ChannelAnalyzerNG::MsgReportChannelSampleRateChanged::match(message)) { - int newRate = getRequestedChannelSampleRate() / (1<ssb->isChecked()) - { - QString s = QString::number(ui->BW->value()/10.0, 'f', 1); - ui->BWText->setText(tr("%1k").arg(s)); - } - else - { - QString s = QString::number(ui->BW->value()/5.0, 'f', 1); // BW = value * 2 - ui->BWText->setText(tr("%1k").arg(s)); - } - - QString s = QString::number(ui->lowCut->value()/10.0, 'f', 1); - ui->lowCutText->setText(tr("%1k").arg(s)); - - s = QString::number(m_rate/1000.0, 'f', 1); - ui->spanText->setText(tr("%1 kS/s").arg(s)); - - ui->glScope->setSampleRate(m_rate); - - displayBandwidth(); // sets ui->glSpectrum sample rate + qDebug() << "ChannelAnalyzerNGGUI::handleMessage: MsgReportChannelSampleRateChanged"; + ui->channelSampleRate->setValueRange(7, 2000U, m_channelAnalyzer->getInputSampleRate()); + setNewFinalRate(); return true; } @@ -238,7 +209,7 @@ void ChannelAnalyzerNGGUI::tick() ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); } - if (ui->pll->isChecked()) + if (ui->pll->isChecked()) { int freq = (m_channelAnalyzer->getPllFrequency() * m_channelAnalyzer->getChannelSampleRate()) / (2.0*M_PI); ui->pll->setToolTip(tr("PLL lock. Freq = %1 Hz").arg(freq)); @@ -247,14 +218,9 @@ void ChannelAnalyzerNGGUI::tick() void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) { - ui->channelSampleRate->setValueRange(7, 2000U, m_channelAnalyzer->getInputSampleRate()); - - if (ui->useRationalDownsampler->isChecked()) - { - qDebug("ChannelAnalyzerNGGUI::on_channelSampleRate_changed: %llu", value); - setNewFinalRate(m_spanLog2); - applySettings(); - } + m_settings.m_downSampleRate = value; + setNewFinalRate(); + applySettings(); } void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) @@ -266,14 +232,19 @@ void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) applySettings(); } -void ChannelAnalyzerNGGUI::on_pllPskOrder_currentIndexChanged(int index __attribute__((unused))) +void ChannelAnalyzerNGGUI::on_pllPskOrder_currentIndexChanged(int index) { - applySettings(); + if (index < 5) + { + m_settings.m_pllPskOrder = (1<ssb->isChecked()) - { - QString s = QString::number(value/10.0, 'f', 1); - ui->BWText->setText(tr("%1k").arg(s)); - } - else - { - QString s = QString::number(value/5.0, 'f', 1); // BW = value * 2 - ui->BWText->setText(tr("%1k").arg(s)); - } - - displayBandwidth(); - on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100); // does apply settings + setFiltersUIBoundaries(); + m_settings.m_bandwidth = ui->BW->value() * 100; + m_settings.m_lowCutoff = ui->lowCut->value() * 100; + applySettings(); } -int ChannelAnalyzerNGGUI::getEffectiveLowCutoff(int lowCutoff) +void ChannelAnalyzerNGGUI::on_lowCut_valueChanged(int value __attribute__((unused))) { - int ssbBW = m_channelMarker.getBandwidth() / 2; - int effectiveLowCutoff = lowCutoff; - const int guard = 100; - - if (ssbBW < 0) { - if (effectiveLowCutoff < ssbBW + guard) { - effectiveLowCutoff = ssbBW + guard; - } - if (effectiveLowCutoff > 0) { - effectiveLowCutoff = 0; - } - } else { - if (effectiveLowCutoff > ssbBW - guard) { - effectiveLowCutoff = ssbBW - guard; - } - if (effectiveLowCutoff < 0) { - effectiveLowCutoff = 0; - } - } - - return effectiveLowCutoff; -} - -void ChannelAnalyzerNGGUI::on_lowCut_valueChanged(int value) -{ - blockApplySettings(true); - int lowCutoff = getEffectiveLowCutoff(value * 100); - m_channelMarker.setLowCutoff(lowCutoff); - QString s = QString::number(lowCutoff/1000.0, 'f', 1); - ui->lowCutText->setText(tr("%1k").arg(s)); - ui->lowCut->setValue(lowCutoff/100); - blockApplySettings(false); + setFiltersUIBoundaries(); + m_settings.m_bandwidth = ui->BW->value() * 100; + m_settings.m_lowCutoff = ui->lowCut->value() * 100; applySettings(); } void ChannelAnalyzerNGGUI::on_spanLog2_currentIndexChanged(int index) { - if (setNewFinalRate(index)) { - applySettings(); - } + if ((index < 0) || (index > 6)) { + return; + } + m_settings.m_spanLog2 = index; + setNewFinalRate(); + applySettings(); } void ChannelAnalyzerNGGUI::on_ssb_toggled(bool checked) { - //int bw = m_channelMarker.getBandwidth(); - - if (checked) - { - setFiltersUIBoundaries(); - - ui->BWLabel->setText("LP"); - QString s = QString::number(ui->BW->value()/10.0, 'f', 1); // bw/2 - ui->BWText->setText(tr("%1k").arg(s)); - - on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100); - } - else - { - if (ui->BW->value() < 0) { - ui->BW->setValue(-ui->BW->value()); - } - - setFiltersUIBoundaries(); - //m_channelMarker.setBandwidth(ui->BW->value() * 200.0); - - ui->BWLabel->setText("BP"); - QString s = QString::number(ui->BW->value()/5.0, 'f', 1); // bw - ui->BWText->setText(tr("%1k").arg(s)); - - ui->lowCut->setEnabled(false); - ui->lowCut->setValue(0); - ui->lowCutText->setText("0.0k"); - } - + m_settings.m_ssb = checked; + setFiltersUIBoundaries(); applySettings(); - displayBandwidth(); } void ChannelAnalyzerNGGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { - /* - if((widget == ui->spectrumContainer) && (m_ssbDemod != NULL)) - m_ssbDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown); - */ +} + +void ChannelAnalyzerNGGUI::onMenuDialogCalled(const QPoint& p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_frequency = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); } ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : @@ -407,12 +325,12 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de m_deviceUISet(deviceUISet), m_channelMarker(this), m_doApplySettings(true), - m_rate(6000), - m_spanLog2(0) + m_rate(48000) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); m_scopeVis = new ScopeVisNG(ui->glScope); @@ -456,12 +374,16 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); + m_settings.setChannelMarker(&m_channelMarker); + m_settings.setSpectrumGUI(ui->spectrumGUI); + m_settings.setScopeGUI(ui->scopeGUI); + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); - applySettings(); - setNewFinalRate(m_spanLog2); + displaySettings(); + applySettings(true); } ChannelAnalyzerNGGUI::~ChannelAnalyzerNGGUI() @@ -474,101 +396,79 @@ ChannelAnalyzerNGGUI::~ChannelAnalyzerNGGUI() delete ui; } -bool ChannelAnalyzerNGGUI::setNewFinalRate(int spanLog2) +void ChannelAnalyzerNGGUI::setNewFinalRate() { - qDebug("ChannelAnalyzerNGGUI::setNewRate"); - - if ((spanLog2 < 0) || (spanLog2 > 6)) { - return false; - } - - m_spanLog2 = spanLog2; - m_rate = getRequestedChannelSampleRate() / (1<spanText->setText(tr("%1 kS/s").arg(s)); - displayBandwidth(); - ui->glScope->setSampleRate(m_rate); - ui->glSpectrum->setSampleRate(m_rate); m_scopeVis->setSampleRate(m_rate); - - return true; -} - -void ChannelAnalyzerNGGUI::displayBandwidth() -{ - blockApplySettings(true); - - m_channelMarker.setBandwidth(ui->BW->value() * 100 * 2); - - if (ui->ssb->isChecked()) - { - if (ui->BW->value() < 0) - { - m_channelMarker.setSidebands(ChannelMarker::lsb); - ui->glSpectrum->setLsbDisplay(true); - } - else - { - m_channelMarker.setSidebands(ChannelMarker::usb); - ui->glSpectrum->setLsbDisplay(false); - } - - m_channelMarker.setLowCutoff(ui->lowCut->value()*100); - ui->glSpectrum->setSampleRate(m_rate/2); - ui->glSpectrum->setCenterFrequency(m_rate/4); - ui->glSpectrum->setSsbSpectrum(true); - } - else - { - m_channelMarker.setSidebands(ChannelMarker::dsb); - - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(m_rate); - ui->glSpectrum->setLsbDisplay(false); - ui->glSpectrum->setSsbSpectrum(false); - } - - blockApplySettings(false); } void ChannelAnalyzerNGGUI::setFiltersUIBoundaries() { - if (ui->BW->value() < -m_rate/200) { - ui->BW->setValue(-m_rate/200); - m_channelMarker.setBandwidth(-m_rate*2); - } else if (ui->BW->value() > m_rate/200) { - ui->BW->setValue(m_rate/200); - m_channelMarker.setBandwidth(m_rate*2); - } + bool dsb = !ui->ssb->isChecked(); + int bw = ui->BW->value(); + int lw = ui->lowCut->value(); + int bwMax = m_rate / 200; - if (ui->lowCut->value() < -m_rate/200) { - ui->lowCut->setValue(-m_rate/200); - m_channelMarker.setLowCutoff(-m_rate); - } else if (ui->lowCut->value() > m_rate/200) { - ui->lowCut->setValue(m_rate/200); - m_channelMarker.setLowCutoff(m_rate); - } + bw = bw < -bwMax ? -bwMax : bw > bwMax ? bwMax : bw; - if (ui->ssb->isChecked()) { - ui->BW->setMinimum(-m_rate/200); - ui->lowCut->setMinimum(-m_rate/200); + if (bw < 0) { + lw = lw < bw+1 ? bw+1 : lw < 0 ? lw : 0; + } else if (bw > 0) { + lw = lw > bw-1 ? bw-1 : lw < 0 ? 0 : lw; } else { - ui->BW->setMinimum(0); - ui->lowCut->setMinimum(-m_rate/200); - ui->lowCut->setValue(0); + lw = 0; } - ui->BW->setMaximum(m_rate/200); - ui->lowCut->setMaximum(m_rate/200); + if (dsb) + { + bw = bw < 0 ? -bw : bw; + lw = 0; + } + + QString bwStr = QString::number(bw/10.0, 'f', 1); + QString lwStr = QString::number(lw/10.0, 'f', 1); + + if (dsb) { + ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(bwStr)); + } else { + ui->BWText->setText(tr("%1k").arg(bwStr)); + } + + ui->lowCutText->setText(tr("%1k").arg(lwStr)); + + ui->BW->blockSignals(true); + ui->lowCut->blockSignals(true); + + ui->BW->setMaximum(bwMax); + ui->BW->setMinimum(dsb ? 0 : -bwMax); + ui->BW->setValue(bw); + + ui->lowCut->setMaximum(dsb ? 0 : bw); + ui->lowCut->setMinimum(dsb ? 0 : -bw); + ui->lowCut->setValue(lw); + + ui->lowCut->blockSignals(false); + ui->BW->blockSignals(false); + + setSpectrumDisplay(); + + m_channelMarker.setBandwidth(bw * 200); + m_channelMarker.setSidebands(dsb ? ChannelMarker::dsb : bw < 0 ? ChannelMarker::lsb : ChannelMarker::usb); + + if (!dsb) { + m_channelMarker.setLowCutoff(lw * 100); + } } void ChannelAnalyzerNGGUI::blockApplySettings(bool block) @@ -578,30 +478,23 @@ void ChannelAnalyzerNGGUI::blockApplySettings(bool block) m_doApplySettings = !block; } -void ChannelAnalyzerNGGUI::applySettings() +void ChannelAnalyzerNGGUI::applySettings(bool force) { if (m_doApplySettings) { int sampleRate = getRequestedChannelSampleRate(); - ChannelAnalyzerNG::MsgConfigureChannelizer *msgChannelizer = ChannelAnalyzerNG::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); + ChannelAnalyzerNG::MsgConfigureChannelizer *msgChannelizer = + ChannelAnalyzerNG::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); m_channelAnalyzer->getInputMessageQueue()->push(msgChannelizer); ChannelAnalyzerNG::MsgConfigureChannelizer *msg = - ChannelAnalyzerNG::MsgConfigureChannelizer::create( - sampleRate, - m_channelMarker.getCenterFrequency()); + ChannelAnalyzerNG::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); m_channelAnalyzer->getInputMessageQueue()->push(msg); - m_channelAnalyzer->configure(m_channelAnalyzer->getInputMessageQueue(), - sampleRate, - ui->BW->value() * 100.0, - ui->lowCut->value() * 100.0, - m_spanLog2, - ui->ssb->isChecked(), - ui->pll->isChecked(), - ui->pllPskOrder->currentIndex() == 5, - 1<pllPskOrder->currentIndex()); + ChannelAnalyzerNG::MsgConfigureChannelAnalyzer* message = + ChannelAnalyzerNG::MsgConfigureChannelAnalyzer::create( m_settings, force); + m_channelAnalyzer->getInputMessageQueue()->push(message); } } diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h index 8b58f92a2..5538d5cf1 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.h @@ -17,13 +17,15 @@ #ifndef INCLUDE_CHANNELANALYZERNGGUI_H #define INCLUDE_CHANNELANALYZERNGGUI_H -#include +#include "plugin/plugininstancegui.h" #include "gui/rollupwidget.h" #include "dsp/channelmarker.h" #include "dsp/dsptypes.h" #include "util/movingaverage.h" #include "util/messagequeue.h" +#include "chanalyzerngsettings.h" + class PluginAPI; class DeviceUISet; class BasebandSampleSink; @@ -63,9 +65,9 @@ private: PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; ChannelMarker m_channelMarker; + ChannelAnalyzerNGSettings m_settings; bool m_doApplySettings; int m_rate; //!< sample rate after final in-channel decimation (spanlog2) - int m_spanLog2; MovingAverageUtil m_channelPowerDbAvg; ChannelAnalyzerNG* m_channelAnalyzer; @@ -78,13 +80,13 @@ private: virtual ~ChannelAnalyzerNGGUI(); int getRequestedChannelSampleRate(); - int getEffectiveLowCutoff(int lowCutoff); - bool setNewFinalRate(int spanLog2); //!< set sample rate after final in-channel decimation + void setNewFinalRate(); //!< set sample rate after final in-channel decimation void setFiltersUIBoundaries(); void blockApplySettings(bool block); - void applySettings(); - void displayBandwidth(); + void applySettings(bool force = false); + void displaySettings(); + void setSpectrumDisplay(); void leaveEvent(QEvent*); void enterEvent(QEvent*); @@ -100,6 +102,7 @@ private slots: void on_spanLog2_currentIndexChanged(int index); void on_ssb_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); void tick(); }; diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp index 5873cf0f2..c282220f1 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp @@ -22,7 +22,9 @@ #include "chanalyzerngsettings.h" ChannelAnalyzerNGSettings::ChannelAnalyzerNGSettings() : - m_channelMarker(0) + m_channelMarker(0), + m_spectrumGUI(0), + m_scopeGUI(0) { resetToDefaults(); } @@ -34,11 +36,12 @@ void ChannelAnalyzerNGSettings::resetToDefaults() m_downSampleRate = 0; m_bandwidth = 5000; m_lowCutoff = 300; - m_spanLog2 = 3; + m_spanLog2 = 0; m_ssb = false; m_pll = false; m_fll = false; m_pllPskOrder = 1; + m_rgbColor = QColor(128, 128, 128).rgb(); } QByteArray ChannelAnalyzerNGSettings::serialize() const @@ -86,7 +89,7 @@ bool ChannelAnalyzerNGSettings::deserialize(const QByteArray& data) d.readU32(4, &m_rgbColor); d.readS32(5, &m_lowCutoff, 3); - d.readS32(6, &m_spanLog2, 3); + d.readS32(6, &m_spanLog2, 0); d.readBool(7, &m_ssb, false); if (m_scopeGUI) { diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h index 0ec677e76..a030ba943 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h @@ -42,6 +42,8 @@ struct ChannelAnalyzerNGSettings ChannelAnalyzerNGSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } + void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; } QByteArray serialize() const; bool deserialize(const QByteArray& data); }; From ed08480226118cca5a675ab557843d728326427d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 20 May 2018 02:24:38 +0200 Subject: [PATCH 428/956] Channel analyzer NG: fixes --- .../chanalyzerng/chanalyzernggui.cpp | 28 ++++++++++++++++--- .../channelrx/chanalyzerng/chanalyzernggui.h | 1 + sdrbase/dsp/phaselockcomplex.cpp | 10 ++++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 73278162a..2e6149092 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -106,14 +106,31 @@ void ChannelAnalyzerNGGUI::displaySettings() blockApplySettings(true); ui->useRationalDownsampler->setChecked(m_settings.m_downSample); + ui->channelSampleRate->setValue(m_settings.m_downSampleRate); + setNewFinalRate(); ui->BW->setValue(m_settings.m_bandwidth/100); ui->lowCut->setValue(m_settings.m_lowCutoff/100); ui->deltaFrequency->setValue(m_settings.m_frequency); ui->spanLog2->setCurrentIndex(m_settings.m_spanLog2); + displayPLLSettings(); blockApplySettings(false); +} - setNewFinalRate(); +void ChannelAnalyzerNGGUI::displayPLLSettings() +{ + if (m_settings.m_fll) + { + ui->pllPskOrder->setCurrentIndex(5); + } + else + { + int i = 0; + for(; ((m_settings.m_pllPskOrder>>i) & 1) == 0; i++); + ui->pllPskOrder->setCurrentIndex(i); + } + + ui->pll->setChecked(m_settings.m_pll); } void ChannelAnalyzerNGGUI::setSpectrumDisplay() @@ -163,6 +180,7 @@ bool ChannelAnalyzerNGGUI::handleMessage(const Message& message) { qDebug() << "ChannelAnalyzerNGGUI::handleMessage: MsgReportChannelSampleRateChanged"; ui->channelSampleRate->setValueRange(7, 2000U, m_channelAnalyzer->getInputSampleRate()); + ui->channelSampleRate->setValue(m_settings.m_downSampleRate); setNewFinalRate(); return true; @@ -229,16 +247,18 @@ void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) ui->pll->setToolTip(tr("PLL lock")); } + m_settings.m_pll = checked; applySettings(); } void ChannelAnalyzerNGGUI::on_pllPskOrder_currentIndexChanged(int index) { - if (index < 5) - { + if (index < 5) { m_settings.m_pllPskOrder = (1< -m_lockFreq) && (dF < m_lockFreq)) + if ((dF > -m_lockFreq) && (dF < m_lockFreq)) { if (m_lockCount < 20) { m_lockCount++; } - } - else + } + else { if (m_lockCount > 0) { m_lockCount--; @@ -187,7 +189,7 @@ void PhaseLockComplex::feed(float re, float im) } m_freqPrev = m_freq; - m_phiHatPrev = m_phiHat; + m_phiHatPrev = m_phiHat; } else { From 48cac5385bd532fa6bb44331f99147a5163af4d2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 20 May 2018 03:50:22 +0200 Subject: [PATCH 429/956] PLL lock indication fixes --- plugins/channelrx/demodam/amdemod.h | 1 + plugins/channelrx/demodam/amdemodgui.cpp | 7 ++++++- sdrbase/dsp/phaselockcomplex.cpp | 18 ++++++----------- sdrbase/dsp/phaselockcomplex.h | 25 +++++++++++++++++++++++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index bd971b9ad..d8b292417 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -123,6 +123,7 @@ public: double getMagSq() const { return m_magsq; } bool getSquelchOpen() const { return m_squelchOpen; } bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } + Real getPllFrequency() const { return m_pll.getFreq(); } void getMagSqLevels(double& avg, double& peak, int& nbSamples) { diff --git a/plugins/channelrx/demodam/amdemodgui.cpp b/plugins/channelrx/demodam/amdemodgui.cpp index 2c0eff5a4..c1a8184d2 100644 --- a/plugins/channelrx/demodam/amdemodgui.cpp +++ b/plugins/channelrx/demodam/amdemodgui.cpp @@ -142,8 +142,10 @@ void AMDemodGUI::on_deltaFrequency_changed(qint64 value) void AMDemodGUI::on_pll_toggled(bool checked) { - if (!checked) { + if (!checked) + { ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + ui->pll->setToolTip(tr("PLL for synchronous AM")); } m_settings.m_pll = checked; @@ -427,6 +429,9 @@ void AMDemodGUI::tick() } else { ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); } + + int freq = (m_amDemod->getPllFrequency() * m_amDemod->getAudioSampleRate()) / (2.0*M_PI); + ui->pll->setToolTip(tr("PLL for synchronous AM. Freq = %1 Hz").arg(freq)); } m_tickCount++; diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp index 991e3b157..0c8d604f3 100644 --- a/sdrbase/dsp/phaselockcomplex.cpp +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -90,7 +90,7 @@ void PhaseLockComplex::setPskOrder(unsigned int order) void PhaseLockComplex::setSampleRate(unsigned int sampleRate) { m_lockTime = sampleRate / 100; // 10ms for order 1 - m_lockFreq = (2.0*M_PI*5.0) / sampleRate; // +/- 5 Hz frequency swing + m_lockFreq = (2.0*M_PI*(m_pskOrder > 1 ? 6.0 : 1.0)) / sampleRate; // +/- 6 Hz frequency swing reset(); } @@ -161,7 +161,7 @@ void PhaseLockComplex::feed(float re, float im) if (m_pskOrder > 1) { float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); - m_freq = 0.001*dPhi + 0.999*m_freqPrev; + m_freq = m_expAvg.feed(dPhi); if (m_lockTimeCount < m_lockTime-1) { @@ -188,20 +188,14 @@ void PhaseLockComplex::feed(float re, float im) m_lockTimeCount = 0; } - m_freqPrev = m_freq; m_phiHatPrev = m_phiHat; } else { - m_freq = (m_phiHat - m_phiHatPrev) / (2.0*M_PI); + m_freqTest = normalizeAngle(m_phiHat - m_phiHatPrev); + m_freq = m_expAvg.feed(m_freqTest); - if (m_freq < -1.0f) { - m_freq += 2.0f; - } else if (m_freq > 1.0f) { - m_freq -= 2.0f; - } - - float dFreq = m_freq - m_freqPrev; + float dFreq = m_freqTest - m_freqPrev; if ((dFreq > -0.01) && (dFreq < 0.01)) { @@ -215,7 +209,7 @@ void PhaseLockComplex::feed(float re, float im) } m_phiHatPrev = m_phiHat; - m_freqPrev = m_freq; + m_freqPrev = m_freqTest; } } diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h index cd52e72d0..247181e74 100644 --- a/sdrbase/dsp/phaselockcomplex.h +++ b/sdrbase/dsp/phaselockcomplex.h @@ -49,12 +49,34 @@ public: const std::complex& getComplex() const { return m_y; } float getReal() const { return m_yRe; } float getImag() const { return m_yIm; } - bool locked() const { return m_pskOrder > 1 ? m_lockCount > 16 : m_lockCount > m_lockTime-2; } + bool locked() const { return m_pskOrder > 1 ? m_lockCount > 10 : m_lockCount > m_lockTime-2; } float getFreq() const { return m_freq; } float getDeltaPhi() const { return m_deltaPhi; } float getPhiHat() const { return m_phiHat; } private: + class ExpAvg + { + public: + ExpAvg() : m_a0(0.999), m_a1(0.001), m_y1(0.0f) + {} + void setAlpha(const float& alpha) + { + m_a0 = alpha; + m_a1 = 1.0 - alpha; + } + float feed(const float& x) + { + float y = m_a1*x + m_a0*m_y1; + m_y1 = y; + return y; + } + private: + float m_a0; //!< alpha + float m_a1; //!< 1 - alpha + float m_y1; + }; + /** Normalize angle in radians into the [-pi,+pi] region */ static float normalizeAngle(float angle); @@ -82,6 +104,7 @@ private: unsigned int m_pskOrder; int m_lockTime; int m_lockTimeCount; + ExpAvg m_expAvg; }; #endif /* SDRBASE_DSP_PHASELOCKCOMPLEX_H_ */ From d673278f86c21c4579346c48a94b7a6d51f5b21e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 20 May 2018 10:42:14 +0200 Subject: [PATCH 430/956] Added a FFT based correlation class --- sdrbase/CMakeLists.txt | 2 + sdrbase/dsp/fftcorr.cpp | 112 ++++++++++++++++++++++++++++++++++++++++ sdrbase/dsp/fftcorr.h | 57 ++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 sdrbase/dsp/fftcorr.cpp create mode 100644 sdrbase/dsp/fftcorr.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 5bacdd135..da49b3469 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -28,6 +28,7 @@ set(sdrbase_SOURCES dsp/dspengine.cpp dsp/dspdevicesourceengine.cpp dsp/dspdevicesinkengine.cpp + dsp/fftcorr.cpp dsp/fftengine.cpp dsp/fftfilt.cxx dsp/fftwindow.cpp @@ -118,6 +119,7 @@ set(sdrbase_HEADERS dsp/dspdevicesourceengine.h dsp/dspdevicesinkengine.h dsp/dsptypes.h + dsp/fftcorr.h dsp/fftengine.h dsp/fftfilt.h dsp/fftwengine.h diff --git a/sdrbase/dsp/fftcorr.cpp b/sdrbase/dsp/fftcorr.cpp new file mode 100644 index 000000000..22fa00ba4 --- /dev/null +++ b/sdrbase/dsp/fftcorr.cpp @@ -0,0 +1,112 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// FFT based cross correlation // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// 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 "fftcorr.h" + +void fftcorr::init_fft() +{ + flen2 = flen >> 1; + fftA = new g_fft(flen); + fftB = new g_fft(flen); + + dataA = new cmplx[flen]; + dataB = new cmplx[flen]; + dataBj = new cmplx[flen]; + dataP = new cmplx[flen]; + output = new cmplx[flen2]; + ovlbuf = new cmplx[flen2]; + + std::fill(dataA, dataA+flen, 0); + std::fill(dataB, dataB+flen, 0); + std::fill(output, output+flen2, 0); + std::fill(ovlbuf, ovlbuf+flen2, 0); + + inptrA = 0; + inptrB = 0; +} + +fftcorr::fftcorr(int len) : flen(len), flen2(len>>1) +{ + init_fft(); +} + +fftcorr::~fftcorr() +{ + delete fftA; + delete fftB; + delete[] dataA; + delete[] dataB; + delete[] dataBj; + delete[] dataP; + delete[] output; + delete[] ovlbuf; +} + +int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) +{ + dataA[inptrA++] = inA; + + if (inB) { + dataB[inptrB++] = *inB; + } + + if (inptrA < flen2) { + return 0; + } + + fftA->ComplexFFT(dataA); + + if (inB) { + fftB->ComplexFFT(dataB); + } + + if (inB) { + std::transform(dataB, dataB+flen, dataBj, [](const cmplx& c) -> cmplx { return std::conj(c); }); + } else { + std::transform(dataA, dataA+flen, dataBj, [](const cmplx& c) -> cmplx { return std::conj(c); }); + } + + std::transform(dataA, dataA+flen, dataBj, dataP, [](const cmplx& a, const cmplx& b) -> cmplx { return a*b; }); + + fftA->InverseComplexFFT(dataP); + + for (int i = 0; i < flen2; i++) + { + output[i] = ovlbuf[i] + dataP[i]; + ovlbuf[i] = dataP[flen2 + i]; + } + + std::fill(dataA, dataA+flen, 0); + inptrA = 0; + + if (inB) + { + std::fill(dataB, dataB+flen, 0); + inptrB = 0; + } + + *out = output; + return flen2; +} diff --git a/sdrbase/dsp/fftcorr.h b/sdrbase/dsp/fftcorr.h new file mode 100644 index 000000000..cd7777340 --- /dev/null +++ b/sdrbase/dsp/fftcorr.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// FFT based cross correlation // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// 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 SDRBASE_DSP_FFTCORR_H_ +#define SDRBASE_DSP_FFTCORR_H_ + +#include +#include "gfft.h" +#include "export.h" + +class SDRBASE_API fftcorr { +public: + typedef std::complex cmplx; + fftcorr(int len); + ~fftcorr(); + + int run(const cmplx& inA, const cmplx* inB, cmplx **out); //!< if inB = 0 then run auto-correlation + +private: + void init_fft(); + int flen; //!< FFT length + int flen2; //!< half FFT length + g_fft *fftA; + g_fft *fftB; + cmplx *dataA; // from A input + cmplx *dataB; // from B input + cmplx *dataBj; // conjugate of B + cmplx *dataP; // product of A with conjugate of B + cmplx *ovlbuf; + cmplx *output; + int inptrA; + int inptrB; +}; + + +#endif /* SDRBASE_DSP_FFTCORR_H_ */ From cbda404926baf374aba25bf7316047ffa43d4370 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 20 May 2018 11:54:05 +0200 Subject: [PATCH 431/956] Channel analyzer NG: use input selection --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 32 +++---------------- plugins/channelrx/chanalyzerng/chanalyzerng.h | 26 +++++++++++++++ .../chanalyzerng/chanalyzerngsettings.cpp | 5 +++ .../chanalyzerng/chanalyzerngsettings.h | 8 +++++ 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 95c2cfcaa..0cb8a928e 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -148,8 +148,7 @@ void ChannelAnalyzerNG::processOneSample(Complex& c, fftfilt::cmplx *sideband) Real re = m_sum.real() / SDR_RX_SCALED; Real im = m_sum.imag() / SDR_RX_SCALED; m_magsq = re*re + im*im; - Real mixI = 1.0f; - Real mixQ = 0.0f; + std::complex mix; if (m_settings.m_pll) { @@ -157,40 +156,17 @@ void ChannelAnalyzerNG::processOneSample(Complex& c, fftfilt::cmplx *sideband) { m_fll.feed(re, im); // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) - mixI = m_sum.real() * m_fll.getImag() - m_sum.imag() * m_fll.getReal(); - mixQ = m_sum.real() * m_fll.getReal() + m_sum.imag() * m_fll.getImag(); -// mixI = m_fll.getReal() * SDR_RX_SCALED; -// mixQ = m_fll.getImag() * SDR_RX_SCALED; + mix = m_sum * std::conj(m_fll.getComplex()); } else { m_pll.feed(re, im); // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) - mixI = m_sum.real() * m_pll.getImag() - m_sum.imag() * m_pll.getReal(); - mixQ = m_sum.real() * m_pll.getReal() + m_sum.imag() * m_pll.getImag(); - } - - if (m_settings.m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(mixQ, mixI)); - } - else - { - m_sampleBuffer.push_back(Sample(mixI, mixQ)); - } - } - else - { - if (m_settings.m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); - } - else - { - m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); + mix = m_sum * std::conj(m_pll.getComplex()); } } + feedOneSample(m_settings.m_pll ? mix : m_sum, m_settings.m_fll ? m_fll.getComplex() : m_pll.getComplex()); m_sum = 0; } } diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index 8b326bdd1..f4afa6bda 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -240,6 +240,32 @@ private: void applySettings(const ChannelAnalyzerNGSettings& settings, bool force = false); void setFilters(int sampleRate, float bandwidth, float lowCutoff); void processOneSample(Complex& c, fftfilt::cmplx *sideband); + + inline void feedOneSample(const fftfilt::cmplx& s, const fftfilt::cmplx& pll) + { + switch (m_settings.m_inputType) + { + case ChannelAnalyzerNGSettings::InputPLL: + { + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(pll.imag(), pll.real())); + } else { + m_sampleBuffer.push_back(Sample(pll.real(), pll.imag())); + } + } + break; + case ChannelAnalyzerNGSettings::InputSignal: + default: + { + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(s.imag(), s.real())); + } else { + m_sampleBuffer.push_back(Sample(s.real(), s.imag())); + } + } + break; + } + } }; #endif // INCLUDE_CHANALYZERNG_H diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp index c282220f1..0cf60f4d1 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp @@ -41,6 +41,7 @@ void ChannelAnalyzerNGSettings::resetToDefaults() m_pll = false; m_fll = false; m_pllPskOrder = 1; + m_inputType = InputSignal; m_rgbColor = QColor(128, 128, 128).rgb(); } @@ -61,6 +62,7 @@ QByteArray ChannelAnalyzerNGSettings::serialize() const s.writeBool(11, m_pll); s.writeBool(12, m_fll); s.writeU32(13, m_pllPskOrder); + s.writeS32(14, (int) m_inputType); return s.final(); } @@ -78,6 +80,7 @@ bool ChannelAnalyzerNGSettings::deserialize(const QByteArray& data) if(d.getVersion() == 1) { QByteArray bytetmp; + int tmp; d.readS32(1, &m_frequency, 0); d.readS32(2, &m_bandwidth, 5000); @@ -102,6 +105,8 @@ bool ChannelAnalyzerNGSettings::deserialize(const QByteArray& data) d.readBool(11, &m_pll, false); d.readBool(12, &m_fll, false); d.readU32(13, &m_pllPskOrder, 1); + d.readS32(14, &tmp, 0); + m_inputType = (InputType) tmp; return true; } diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h index a030ba943..55a9ff42d 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h @@ -23,6 +23,13 @@ class Serializable; struct ChannelAnalyzerNGSettings { + enum InputType + { + InputSignal, + InputPLL, + InputAutoCorr + }; + int m_frequency; bool m_downSample; quint32 m_downSampleRate; @@ -33,6 +40,7 @@ struct ChannelAnalyzerNGSettings bool m_pll; bool m_fll; unsigned int m_pllPskOrder; + InputType m_inputType; quint32 m_rgbColor; QString m_title; Serializable *m_channelMarker; From f600f78c0fdcb2df7664235cdc4559ece40e707b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 20 May 2018 18:17:53 +0200 Subject: [PATCH 432/956] Channel analyzer NG: implemented input source selection --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 8 ++++--- plugins/channelrx/chanalyzerng/chanalyzerng.h | 17 ++++++++++++-- .../chanalyzerng/chanalyzernggui.cpp | 6 +++++ .../channelrx/chanalyzerng/chanalyzernggui.h | 1 + .../channelrx/chanalyzerng/chanalyzernggui.ui | 22 +++++++++++++++++++ sdrbase/dsp/fftcorr.cpp | 12 ++++++++++ sdrbase/dsp/fftcorr.h | 2 ++ 7 files changed, 63 insertions(+), 5 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 0cb8a928e..9c562703c 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -52,6 +52,7 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : m_inputFrequencyOffset = 0; SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_inputSampleRate, m_settings.m_bandwidth / m_inputSampleRate, ssbFftLen); DSBFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); + m_corr = new fftcorr(4*ssbFftLen); //m_pll.computeCoefficients(0.05f, 0.707f, 1000.0f); // bandwidth, damping factor, loop gain m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain @@ -145,8 +146,8 @@ void ChannelAnalyzerNG::processOneSample(Complex& c, fftfilt::cmplx *sideband) if (!(m_undersampleCount++ & (decim - 1))) // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) { m_sum /= decim; - Real re = m_sum.real() / SDR_RX_SCALED; - Real im = m_sum.imag() / SDR_RX_SCALED; + Real re = m_sum.real() / SDR_RX_SCALEF; + Real im = m_sum.imag() / SDR_RX_SCALEF; m_magsq = re*re + im*im; std::complex mix; @@ -306,7 +307,8 @@ void ChannelAnalyzerNG::applySettings(const ChannelAnalyzerNGSettings& settings, << " m_ssb: " << settings.m_ssb << " m_pll: " << settings.m_pll << " m_fll: " << settings.m_fll - << " m_pllPskOrder: " << settings.m_pllPskOrder; + << " m_pllPskOrder: " << settings.m_pllPskOrder + << " m_inputType: " << (int) settings.m_inputType; if ((settings.m_downSampleRate != m_settings.m_downSampleRate) || force) { diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index f4afa6bda..24ac8204c 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -24,6 +24,7 @@ #include "channel/channelsinkapi.h" #include "dsp/interpolator.h" #include "dsp/ncof.h" +#include "dsp/fftcorr.h" #include "dsp/fftfilt.h" #include "dsp/phaselockcomplex.h" #include "dsp/freqlockcomplex.h" @@ -230,6 +231,7 @@ private: fftfilt* SSBFilter; fftfilt* DSBFilter; + fftcorr* m_corr; BasebandSampleSink* m_sampleSink; SampleVector m_sampleBuffer; @@ -248,12 +250,23 @@ private: case ChannelAnalyzerNGSettings::InputPLL: { if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(pll.imag(), pll.real())); + m_sampleBuffer.push_back(Sample(pll.imag()*SDR_RX_SCALEF, pll.real()*SDR_RX_SCALEF)); } else { - m_sampleBuffer.push_back(Sample(pll.real(), pll.imag())); + m_sampleBuffer.push_back(Sample(pll.real()*SDR_RX_SCALEF, pll.imag()*SDR_RX_SCALEF)); } } break; + case ChannelAnalyzerNGSettings::InputAutoCorr: + { + std::complex a = m_corr->run(s/(SDR_RX_SCALEF/1024.0f), 0); + + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(a.imag(), a.real())); + } else { + m_sampleBuffer.push_back(Sample(a.real(), a.imag())); + } + } + break; case ChannelAnalyzerNGSettings::InputSignal: default: { diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 2e6149092..bb7ce2a5f 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -277,6 +277,12 @@ int ChannelAnalyzerNGGUI::getRequestedChannelSampleRate() } } +void ChannelAnalyzerNGGUI::on_signalSelect_currentIndexChanged(int index) +{ + m_settings.m_inputType = (ChannelAnalyzerNGSettings::InputType) index; + applySettings(); +} + void ChannelAnalyzerNGGUI::on_deltaFrequency_changed(qint64 value) { m_channelMarker.setCenterFrequency(value); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h index 79d031fdd..fb68d20eb 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.h @@ -98,6 +98,7 @@ private slots: void on_pll_toggled(bool checked); void on_pllPskOrder_currentIndexChanged(int index); void on_useRationalDownsampler_toggled(bool checked); + void on_signalSelect_currentIndexChanged(int index); void on_BW_valueChanged(int value); void on_lowCut_valueChanged(int value); void on_spanLog2_currentIndexChanged(int index); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index f9c740101..ecee494a2 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -356,6 +356,28 @@
+ + + + Select input signal + + + + Sig + + + + + Lock + + + + + ACorr + + + +
diff --git a/sdrbase/dsp/fftcorr.cpp b/sdrbase/dsp/fftcorr.cpp index 22fa00ba4..d82b5d423 100644 --- a/sdrbase/dsp/fftcorr.cpp +++ b/sdrbase/dsp/fftcorr.cpp @@ -45,6 +45,7 @@ void fftcorr::init_fft() inptrA = 0; inptrB = 0; + outptr = 0; } fftcorr::fftcorr(int len) : flen(len), flen2(len>>1) @@ -110,3 +111,14 @@ int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) *out = output; return flen2; } + +const fftcorr::cmplx& fftcorr::run(const cmplx& inA, const cmplx* inB) +{ + cmplx *dummy; + + if (run(inA, inB, &dummy)) { + outptr = 0; + } + + return output[outptr++]; +} diff --git a/sdrbase/dsp/fftcorr.h b/sdrbase/dsp/fftcorr.h index cd7777340..233d4a745 100644 --- a/sdrbase/dsp/fftcorr.h +++ b/sdrbase/dsp/fftcorr.h @@ -36,6 +36,7 @@ public: ~fftcorr(); int run(const cmplx& inA, const cmplx* inB, cmplx **out); //!< if inB = 0 then run auto-correlation + const cmplx& run(const cmplx& inA, const cmplx* inB); private: void init_fft(); @@ -51,6 +52,7 @@ private: cmplx *output; int inptrA; int inptrB; + int outptr; }; From 8050266b2808c27da603c7253df66610b470a4f9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 20 May 2018 19:41:36 +0200 Subject: [PATCH 433/956] Channel analyzer NG: autocorrelation corrections (1) --- debian/changelog | 2 +- .../chanalyzerng/chanalyzernggui.cpp | 1 + sdrbase/dsp/fftcorr.cpp | 24 ++++--------------- sdrbase/dsp/fftcorr.h | 3 --- 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/debian/changelog b/debian/changelog index 3e95d948d..f5dbacd38 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ sdrangel (3.14.7-1) unstable; urgency=medium - * ChanelAnalyzerNG: added PLL option + * ChanelAnalyzerNG: added PLL option and source selection with auto correlation * RTL-SDR: fixed inf/sup decimators * AM demod: syncrhronous AM detection option diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index bb7ce2a5f..4be9fb1af 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -113,6 +113,7 @@ void ChannelAnalyzerNGGUI::displaySettings() ui->deltaFrequency->setValue(m_settings.m_frequency); ui->spanLog2->setCurrentIndex(m_settings.m_spanLog2); displayPLLSettings(); + ui->signalSelect->setCurrentIndex((int) m_settings.m_inputType); blockApplySettings(false); } diff --git a/sdrbase/dsp/fftcorr.cpp b/sdrbase/dsp/fftcorr.cpp index d82b5d423..5be2c3c9b 100644 --- a/sdrbase/dsp/fftcorr.cpp +++ b/sdrbase/dsp/fftcorr.cpp @@ -27,7 +27,6 @@ void fftcorr::init_fft() { - flen2 = flen >> 1; fftA = new g_fft(flen); fftB = new g_fft(flen); @@ -35,20 +34,16 @@ void fftcorr::init_fft() dataB = new cmplx[flen]; dataBj = new cmplx[flen]; dataP = new cmplx[flen]; - output = new cmplx[flen2]; - ovlbuf = new cmplx[flen2]; std::fill(dataA, dataA+flen, 0); std::fill(dataB, dataB+flen, 0); - std::fill(output, output+flen2, 0); - std::fill(ovlbuf, ovlbuf+flen2, 0); inptrA = 0; inptrB = 0; outptr = 0; } -fftcorr::fftcorr(int len) : flen(len), flen2(len>>1) +fftcorr::fftcorr(int len) : flen(len) { init_fft(); } @@ -61,8 +56,6 @@ fftcorr::~fftcorr() delete[] dataB; delete[] dataBj; delete[] dataP; - delete[] output; - delete[] ovlbuf; } int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) @@ -73,7 +66,7 @@ int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) dataB[inptrB++] = *inB; } - if (inptrA < flen2) { + if (inptrA < flen) { return 0; } @@ -90,15 +83,8 @@ int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) } std::transform(dataA, dataA+flen, dataBj, dataP, [](const cmplx& a, const cmplx& b) -> cmplx { return a*b; }); - fftA->InverseComplexFFT(dataP); - for (int i = 0; i < flen2; i++) - { - output[i] = ovlbuf[i] + dataP[i]; - ovlbuf[i] = dataP[flen2 + i]; - } - std::fill(dataA, dataA+flen, 0); inptrA = 0; @@ -108,8 +94,8 @@ int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) inptrB = 0; } - *out = output; - return flen2; + *out = dataP; + return flen; } const fftcorr::cmplx& fftcorr::run(const cmplx& inA, const cmplx* inB) @@ -120,5 +106,5 @@ const fftcorr::cmplx& fftcorr::run(const cmplx& inA, const cmplx* inB) outptr = 0; } - return output[outptr++]; + return dataP[outptr++]; } diff --git a/sdrbase/dsp/fftcorr.h b/sdrbase/dsp/fftcorr.h index 233d4a745..9da7419e6 100644 --- a/sdrbase/dsp/fftcorr.h +++ b/sdrbase/dsp/fftcorr.h @@ -41,15 +41,12 @@ public: private: void init_fft(); int flen; //!< FFT length - int flen2; //!< half FFT length g_fft *fftA; g_fft *fftB; cmplx *dataA; // from A input cmplx *dataB; // from B input cmplx *dataBj; // conjugate of B cmplx *dataP; // product of A with conjugate of B - cmplx *ovlbuf; - cmplx *output; int inptrA; int inptrB; int outptr; From f2f34ad9a9768c3712d49f3e4962eda491d87bcb Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 20 May 2018 20:23:41 +0200 Subject: [PATCH 434/956] Channel analyzer NG: autocorrelation corrections (2): corrected FFT aliasing --- plugins/channelrx/chanalyzerng/chanalyzerng.cpp | 3 +-- sdrbase/dsp/fftcorr.cpp | 7 ++++--- sdrbase/dsp/fftcorr.h | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 9c562703c..148e53959 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -52,8 +52,7 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : m_inputFrequencyOffset = 0; SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_inputSampleRate, m_settings.m_bandwidth / m_inputSampleRate, ssbFftLen); DSBFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); - m_corr = new fftcorr(4*ssbFftLen); - //m_pll.computeCoefficients(0.05f, 0.707f, 1000.0f); // bandwidth, damping factor, loop gain + m_corr = new fftcorr(8*ssbFftLen); // 8k for 4k effective samples m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); diff --git a/sdrbase/dsp/fftcorr.cpp b/sdrbase/dsp/fftcorr.cpp index 5be2c3c9b..2e4fbd242 100644 --- a/sdrbase/dsp/fftcorr.cpp +++ b/sdrbase/dsp/fftcorr.cpp @@ -43,7 +43,7 @@ void fftcorr::init_fft() outptr = 0; } -fftcorr::fftcorr(int len) : flen(len) +fftcorr::fftcorr(int len) : flen(len), flen2(len>>1) { init_fft(); } @@ -66,7 +66,7 @@ int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) dataB[inptrB++] = *inB; } - if (inptrA < flen) { + if (inptrA < flen2) { return 0; } @@ -83,6 +83,7 @@ int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) } std::transform(dataA, dataA+flen, dataBj, dataP, [](const cmplx& a, const cmplx& b) -> cmplx { return a*b; }); + fftA->InverseComplexFFT(dataP); std::fill(dataA, dataA+flen, 0); @@ -95,7 +96,7 @@ int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) } *out = dataP; - return flen; + return flen2; } const fftcorr::cmplx& fftcorr::run(const cmplx& inA, const cmplx* inB) diff --git a/sdrbase/dsp/fftcorr.h b/sdrbase/dsp/fftcorr.h index 9da7419e6..0e998e526 100644 --- a/sdrbase/dsp/fftcorr.h +++ b/sdrbase/dsp/fftcorr.h @@ -41,6 +41,7 @@ public: private: void init_fft(); int flen; //!< FFT length + int flen2; //!< half FFT length g_fft *fftA; g_fft *fftB; cmplx *dataA; // from A input From 3f373b9e91bb69e6e61aa1c772b7a0f6eaf2b504 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 21 May 2018 01:40:21 +0200 Subject: [PATCH 435/956] Channel analyzer NG: updated documentation + fixes --- doc/img/ChAnalyzerNG_plugin.png | Bin 103195 -> 104882 bytes doc/img/ChAnalyzerNG_plugin.xcf | Bin 634758 -> 629264 bytes doc/img/ChAnalyzerNG_plugin_settings.png | Bin 16352 -> 19035 bytes doc/img/ChAnalyzerNG_plugin_settings.xcf | Bin 102994 -> 122563 bytes plugins/channelrx/chanalyzerng/chanalyzerng.h | 2 +- .../chanalyzerng/chanalyzernggui.cpp | 1 + .../chanalyzerng/chanalyzerngsettings.cpp | 3 ++ plugins/channelrx/chanalyzerng/readme.md | 45 ++++++++++++++---- 8 files changed, 40 insertions(+), 11 deletions(-) diff --git a/doc/img/ChAnalyzerNG_plugin.png b/doc/img/ChAnalyzerNG_plugin.png index 79d52dc8a690509f0de40da01b32d10e8dbaf1c1..fa6aba68739ae572e273298f1e171572680b68fc 100644 GIT binary patch literal 104882 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

EhQrr&`50^6N5Eh2OXy9jTC86fe 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 diff --git a/doc/img/ChAnalyzerNG_plugin.xcf b/doc/img/ChAnalyzerNG_plugin.xcf index e76c7c0aebd63628c820e89edd9891b5ae9d066b..8da958131ad75a026f32bb49ebb0b8090f6ca339 100644 GIT binary patch delta 25826 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 DDcBL% delta 22085 zcmeI)dstM}{`m2=29TMd%v2PeKt;K!2qGhl+(kybfL8)EF>i>Mv@{LL9Mrs4mXd9> zqGM*Mm0irzI+AA2Kw_1EE#rOKYKr4`HcpM>yRBSiQ9LWJEaMCL*vhPs59_PG!x zu|n*aAjGZ#LY&wp#Fh3!MxGLK+`GcG>6}nMrspfh?C7-jH+ewH0~N3Nbh!0|-Y!IE z%0MA}zS{el&qRZ?RrGJu!7)XMZdZ9SiJQ>{VV(#0wH3lpA%xik+Rah0VHgTA3rnyL zTd)fUa2%&`0kuK|n9v1LuwfVqF-r(Xz!EmrVGDNQ0FL7{E}&M3n@s3}DA+Izg_wmU zScfgxg#$Q_)3|_IA#OII3!-4dFce~zaEP0iu(1wXunPxp9H(&swL-Ktp$np5!!Q(L z7M5TgwqO?y;5bg>0&0c0#e^=1f(^q^h*=JPEx|f$!7d!Yah%2l)C$qggf57J4Z~1~ zSy+N~*n(X+fa5rg3#b+1Ruj4)3N{QwAsn;#wFK+11-ozn$8j1LP%A`x6S^P@HVi`{ zW?>1|VGDNQ0FL7{E}&M34kmO#6l@rVLi)eMEH;*49kyT>4&XRW;{s}h=x9P0M8Sq( zD8wu*!8!(a0hZzq_yFgH=yVg9HJ!$z6i?w0zGp%h>Hp4s*%*y`QHej|8zF*T!Fxh< zEyjB6!6&#TgrzG|F%_$^8z=Fr5Vv*0AQWK*p2vr{C`8C+?7%)8#b-nK#k>t+3U>2D zAi`ip7V<>X6rbVTUTMDCIXu)eYkBo-1j1K{(2fm_4Q-*l_TCm+<>NTw zX>Oe1>d>&C2{j4G#3)R}%;v{M5e=fYV=p*I)TcO$%R=<_K?n520AykurePsgq5|9L z_1=5gID$`c7MF$S2Q=G+RA^Q5D1A1ZrGBFO*un;Rz zf$j8q-@R-c!KXNj%R)r^paXhh05UNS)36XLQGxB)izE0HXK`7Gem>}co)~~kjKef6 z#7b0PJF~vuUL3)vIE%|d#Q2~CdSU=FF%HwP5GzrE?bwSW_!MVxS%_F4bU;rGKqkgv z8Wv(DD&W}8ue~^ePjMEPh3M~t4(N#i$iz5I!$Pb?1-4@^j^IQ07f0|Z&f>BV1ANc{Juv{87>8+Ch?S_ocI?Fwe2TNUEJVByItYQ0!_6d=4GiC^p&WCZjh8OS_ z{)8{^BYqPi!ylayfdmXiK4#)Rtbv9X@D~0=oXYqDKjJqb2K%EkB9MTg$j40Fhc(df z0^Y)(@CAOvZ%k8vbVdY`V3m1qyZESE!v;5>`D%)1-QK({t9Wlw)=(b@kxl)0M}uq@ z8`HLe=r!$i90XC!`?!(Igc!L2Rp1;WIcDTZoWVuZ36awlA?Sxx?wiZBn$umM%rgM&DUGq{L4A;z>t2>KxvxhTRsEW-w( z!I&!S!9kqF8C*o25M$dS1pSbTTohp*mSF>`um=Zm5@&D`bwZ45ixBifDsoYTc~}O= z27Xmx4-Voa&fp^Igc#ozA?Sxxxl+d)7}^J{&h>(4$su}UK`9f(7bKN@xAZQ2=Z}! z=V|ti2D5mo9U3}}x~#9=VTVk#D31vcSXypF^81mEFTA?6#<9-)ZCV2lML zh0K4#+5DUnU7DS^EtMD4W5@MkVJF#Dg`wrt1 ze1~6!C^euxLJ^0-7>lV`fECz;XYo1?;}fDm>32db{RhbUmbU>*%H`qU(l58;b`)bV zR^tggk2g_`&+!AU39+ILIw4$$)vSY8rwg&3k+PnVvYwH$o{_SCJ+@*u9B=dMLwt#! z;1uE^GrFQT24OfRV73sCvTlC#K_M!b#1%~93MO&IL7c=HTtuA^kGDk#`XLp$D8f7} z!v<8*|BvqxqLRy5c}$2WXQBe{fd%SQe9AqQiTQXEf5K%Up6-SbxDVU$S6mmODgxuc zRoxbXen>?wiZBn$umM%rL;r6($i_*W!9~;w@oZa!pdV6^iz3X!GHgH<_TV5+;tVdL zPKfPo5rTe5MJ|dk56iFtRoFu`*nSWvaRwJrC&Y7Y5rTe5MJ|dk56iFtRoH`rIEgd3 zh&rZgTcW`(!`^Z8PbA2wiXJa?xHUzezfJkV3psnUUeFAVbDn0uww~-?EgW_egip-C zJz!RxSclEck9&Ea5PP1(M_}N-5{dCB$4fXR#H*a^RR-a!cVI2vz&Ap?Mw0egBBs;- zuRX>FN!aTzf`I(yQ7{GH76hpO-)m|}-H`C(3e z_^J>`{1AvRSdoQ1%)lb7!e;EiJ{-koIESl3eBj4O{U8uwup$e2n1MxDh0WN3eK?BG za1K|6IO>N$gu#j|R2dLLO#d5msR{c3>Zl;xnAXRr>#!pAg3a5e6%=kcSyqgjLv#9oUDX z_zdT8Rfyw$2t*jH$U+`wU=dbfGj?Dfj^Z<%!&UnKLq8!t3`7{L$U+`wU=dbfGj?Df zj^Z<%!&M>v?1w;v!HO*8VFngq6*glB_TeZ#!#P}K{{O`ffe3>YS;)f-EW#>m#t!Vm zQGAATxXRS@Lmz>wZw*+Locf$G$W(2S+6JGo|%;9D3~^g<7#6qR;!4QkP)?M6Q>r9 z=o4XxaK&5VBg_$3R1wkCs#gb$x?@Vw_(Tq>jaLs$>=V$(m1s%qV{TE@CaT>@F#$2I zBui3^xkXW%5Rl*+Y#E$jZc)?@R=YD30}@@Cmdr$R zi=sAD?H*zau(^gL*?Iq)hj&e4LmiiF z_7t*3vrn?CxcU_i@;q*6icG0lQoVZSbBD&T=^APonqp45tcnz`W`}Apq+B1o?~kug zUK*;Nb68qh#yd}?r42pwXqrrO4YLeOGpAivMayPG+VxdWZ+|7tkalI5dgSo*^oiB? zq|0>o)BE`C8g3b$ZceXJMXPq1?i_!}F>d=S>4x+R!_}ilWMtf3Jt-qQW8KlLjEoVM z5gF!;f2yKID+BD=hcdRmnqkPeFhV^rCo{9GI)B}f$t$aKGc$86Ihp3nbE;_3nmPZS z5t+}unrXAY&Gqz|Qy8pfm&l6tJpO7mwzxoa?&Ts3E zW~QZiu279CJk8%UHLrecXjay9uV!Vaqj;%Y{=(|{^oaYZef)MbbWDxv2k#wD)$mlc zU-ds0rljb-Voyp+d1q@<(vbHyBuUR$F(kQ)7K}{tNxG~Ckk?+Ps*kzDaL47{Z`_fc zl%x-(AvS5NSzP_#^zDao*whE5!(ekgQ9Z-vW4o-zsIA3rUz>Z|9-G0Yk5xvxA<bj@zi9Cy}pS&nnOJkA!A^`Kv#w(W1!iqaHwi+hPpEUVHT#w1;n{h zEva$l7Da8UdUSGZK&&g-k{oMpQPd`@-L~j}XqU}mi#9he9MQElwf{dvkobsz#(2~a zmKq9A{83TQIbh_(X(K~@8e`s7J?1qw65xn{6Q=o0s|#z2cYT88#9-%;{GoRa5B3Rm z^|AB`HV0o-MQ~$djb1Ap6>o^I3kjCN&OYkZj!qiwkivQ+?51)~Im($ex^%uey}C}J zX6>Htvd3+n%QIMo))DtEE4`zi3avyl6kTTtZ9ZT7`5YDB##_d#_@=$zLw4`V;dv^GZLEK8$;saeRTRs!ZmPd?=k>W& z_2V)#Ct4<|NTYojDuXQ-CN`X5V|~$6zfOC)eoW&T&S_IR$qrM7|GXh1s0lLvkbE?fAPbt zoTMbZZ^o*Cq4n=9J2{9CV=ZHCwnyuqU3}uV+g12bb>hRHsIckUP!A09vN{JccP zhcOmacfC`;Sj7ibBR(|SOMLkAS1LZ}V`Y?z52GxjRD5Vw)Q++kdDAzBUrDV#{6?yX zFw!zoMTBNW?MQWzhO3CcCy$B<&5Bz6!)B<82n1Ob5t?f zVST99PR$BWSdLe*Wn$^F1tV2#;Q$p|RKePb=GYtUES|=cFXAZA1Fb6TOemi+cK!ou z9p$oGta{jar>_jjzeLR{64KJr@1L8Nmia)j3Ofmw1hqy|MayQw(3H9J7pX9mpdOi+ zo}N>lufovWQWb_+x9VZ2ceLz2{(`<>wPj@Fm*-{-$(UB2nUTS8P+>?FEm~O!&7Ynz zf00@U>Ek0QGjme;$mx#JQ_8b5Gg&Qiwnc577OY5u0EdCKC~V`4FnK8rmJ60T`o#Qj8@=1*K+1ZH#(z|6%m^RS~MMrLT%m&5GK->e0Pb zgz9bSts+#jqPDj_IU-buim*he5Y?=xjZhE&4-+?31uV~eZ(ew&y^0*p-qz%Fs|r|p z+z7p@icq~VnpPqR)#P-a^g0z>JOQPPideCO;?m+(#Oh+{q9T?yDpq!NIJ>Bq)fl79 zqFyah(nOtF9=QfiOAPgGTpaywLgfC`5j6}0 zlEv1Epm%mC`_#2g1-tf3 z>RRW7-ie}2x@J#Kwr_az{Z~}va@igKJ~?ceEXw4oBqG}nE}Hg8^dGK$mZ!|^V%gnIu0OB99LwEA9rU2A(`xPc@@pU5;8cs*I9aeBEfZNL1-0wS>AcYDuVa zNot98W7P^!C6d%^b>r27Pi2u^IuixQCnt}1<-%`g9#K)yD}(H)9ql38btTr3FP*&d z$bvof8&s@oNFwtrT2+5(=^<0DsFswC`DtV7x2dr9&8I4?dGfK#`4;U<{Vr!HQ~>+@Qx(7(vd999);UfFhsg9xs%2(9sbuxJnYn9R%T(O(q>`VgRI-CMH%^+h zS3;#<(1Z0Pl-cjr*VnF5VabzEemv2lZH<$b+w}Z$lRDEI|9t4)UDsFKIMe5PN~!&< z&NRQ1^c%miK0j?vy+ehY_b(D|T4j@$o3cqyc6rIGo|p1S{T(XYJYAowl1G(f*KmC% z4v@hS4cBKz{rIHPdWQ-t?>SjvwM-|qX9h@1giN~R$t=g(27he&dE$6UpMOc@gtJStQf4pZw$bmWiX@H>ugsAjYEJMjEnCLQs5ia>n%12No6R ziKekJ)70)CBrT4H+>;i&25`pqnr@UZ4mMaqq2vt|8?XMaLkR{5POI4!fU@~#+hi*x+lrtK7=flgXWZz9Nu+bZdQRK zPX!uJa`=fVn$k{fO0*2TBfp$HGi!Ev`LaS4wLEF&$Es+Ne736c3^U6L$4+akk{eW%;>xmQQP)U&dLq zpY(%mmsH|uE9jq?m|k4^z`X@F73S3K4uaau@=@ykSbvMwB}EMu&kjaq>zva&10~G; z$Ende@AS?>iF~ayPltCRO6Y5ydwOT0gueec!`1z!=J}_0jc7=U88$FA%@(DiVY(+C zc_pIcq0MWT^jOrpOK@;vN}tHY)KC=;8}iUD7VV!Yva`b{xK_0U$9e}p64C^#Nz{$D zN^5P?caiQ`Rd+Ui>*$VGRbwA_qF%* zDetM*Hhlr-9;)`&Hhrt+9;WJbH@+)#4_6IWZhZZvS8sf&rB`oc82Y2tH&8X6v_Y>o zey^l%ws@Ye>HDI_*VFjzkLPtaeVfzpb_TjL)QOCZZ#T}Js@G%P$$A;>w&`W#<%#fI zuImk7eR!_r^?Zlt8x40Kb+CE#AnT3mKC1CM3eTCHjbBMLT-QFH*J+ILTpwOYj{bxR zau!7r;4V-NS8m+$ z*Q+;f@axqZxAXN!t6TOpp4F3Hzi}hJ;pq-L8gIHczM#gN=APHxbQiqg?F@8}QYUJ9 zvvKZ`dOg-NCXCUZ&SX&PgUlG=xmed5?lya_b^Ec#6Xx~$jeCa;PuFy>(9!sU^jm*KX3y(( z7M3o%XBa`Z;q44`$EXu=-92wN&fQn9$GUs#Wwbj&FB>mUgt}Y?tIKoG%yTKPH{1zx zcTtC`vF-s0Tsg}>3VQ>G@R#QMulfjSxJk%cEkbsaLiSRxg}-_E${H$e<1aLFGG(SL zb{5TC?Z{%&m19@mmOE9E$!DZnWDSyzLA4W?uU|DMeUM?0E6biW$UMlYib47Tp08T% zDsy%wQ^ayj>f*!Oh1bPfeK>*V%Tl{acb)MR5nLGeh(|ZK+w6XH1jo8Xbhm(Ru4sF7 zH*>eEs_165#y`4oO}jO&jgQ82w03@pyxDjy!X6P6WVLGUB{Il(t(U!5U?8>NGF1z; zhqiA|Ek&<&w|8&Xj@qO$*~54(*dBcI&D2(x$v(zw7Q4l4rgorA_BLMYV((&9wV(BU zo$Q_b`cezIUq&0Rb+mUh47FM-{(8EzHv4|r$#|{3y}cO6{+-AQmpg*-9OJ$VtS~q*QTW+N`cBwkD@wGmz zSByQThq;GS6+OHz`f&ZC4+swkcMT}C4+uAhI|opNx9GRTz5#t*iT1?4=Dto<^woPi zrGG$wSBgEQzq!9t75(*A&tJ9J)h$ft5DJ^sqNV*^b{^_Akn(b5j`Qvn3)e5s@yT)J z+4FMDIZjpN=%;8e0)$IlV6$+lg734%d(a1}_VH3uedj~P50#a1>R*=1Acyn*;`>WV z^vBN456E}TEuNci&UdOJU%lJd7{;JGMh!=|n4BGu?V4OXIoq6lMHSgrYpiYRoVji1 zUY|477OP)%{p;#6#p+JORaGQftua%J3)&U9il@ftN7&URu@~F9B-duDlED%_r=Wd- zdyc2$Jl_fytCa8BV2TL#&sF>9diJM=29V+v^KE?SHC2SR*l!CCAeSpvU;ABCMKFWr zrw7y^wia9U!QXzF8eaX2`*-NTzN}?xK=m!|+g1&)`}BR0#gV2YYP*-ILENjjm)|66 zU+cB*#ofgmY8@SF0EZQawV6jP+o8_Vr?}6})2Nk^wCc~$aR{}S9cln47AIO5WZD`1 z$c9(0wJVpA#%n{0heSoGBmb@@;_%|(L;$OHLe;L0E*_nbK<%G@mtBol@`|~yLhY8X zYN56RZ*z-?)@bJB`($3<`Sy`Qbr{3f%D(K z&wq1m^efnSB6VdwSM%Rr|NpMn{^$D~;*iy0Dz8^%n(+EmysX``QuZ|z&D5S*DWi+8 z=^uXTBQe&$2Z8N>m(q=FkwcA(8B!i!y zRI$wM;PFc|c2DO}oFpR=S$@fB@~))iTX+%Prc!R!eKZFZ$=y zoz?dY>!ii4k%I=iX1@M@S7YM`Q^V(zcj4RB)cCQ~@ag1T_=cD?`L%jWJxp3ReIwkY zrRW=dP1;0#qrXWj|C;yq$k)=};A_(=zvh*#d|3PFYZ-IXHT}~ z-{i}3hK|!aP204FxAfvW+F#bl=v%Mp9}&<0eEnbfM*RaqlWX}>*Eip!^;oN3Nw!H# z(>D@L+9Z90S8~6;5n%up|q%HqOz4H{4 z_6!?=zIM}SLver?ztJXb+cDWmd-a&~^YJY-Y45ArN7M|yCE88j@;WcpYeT+OJ)(!$ zzx?B4Cp|LeIEUSNT>1sv=#k;tx8KTVv~RxUO3r^+#u@Hes%?K*#v6u2YNsis(AqvC z2U29I!e3kZ2$wPI0d21;Q|{A#dPELlZ`cO?pgT6mMQyGomJf*#+NljPzKx3zC`?UH z64}AeGZ-3XahT|0b!oOoWpAG#t6M91R7N<0tbQ#ftvgv*X>BoC-SK*Bi%IH^7S_(z zy4WD!AeUN@ni~sugs^sE)nN-QMZCVZB5$73?t$H~T%kI58YXLyO9_P57mh9P{W7P66H5?-uL z-YAn*`SeCP$oH4<8UEUr8)Xv5&Ir(gHpxsLK3yx?BnP((pT6b2zU^iE%MJ5d`@<#~ zq#fHNZ#INa)4tdw``g^%Q@i@MnBeXx{ndw03Gr<)x!rvm_Vn=8KK@PyPIpJ&9`4Jc zQeRUx6z&0@H$R}SZ{upRVNvN$)URYrf8WLhWy8ACouXfOzG&1JlnsS@h-$6VGS13g zbY`74+q2=PZ9FSG-Xu=z5x~>oF%=CRUQ{6?rqZ>ItHXvxqI<6X42>(ohJ~Sfvfj!! z4*D9fp>U7!bhvu`o~2(y;kN7VEY=p`t9?@;1G~9nrt06A>C3x@Rh~Q5bB$&PbB*qj z9ZY%$pBxor&@SC2!;GD*bngdONjc`p2yN-(vc2Bl8z1-dw@Lf>aZi7D-0bP^9`l)G zb2iIx)#+1IW$R{ni?)BW3{>5Jv-Z(uPxp7!zEyjFm@fl0+Y>TK9|%3}rfK#QYIIH4 zDxZ)8RM($)!qfG!zAXZZdw4?!C-}AqChpMp z3>fX(6kgoLo=z|HZCvOztoPjW^oy@$d@nob-COv*>~#zM{Q1K^4_|EvGg|rgvZKSj zRX?kDAJbkS2^Ey#yCU}CaWmC-5mM!74TePTIy-kz7 z)U5t^F~RU}RP?UCwUVy@Un-TiYT<0PVUml&yR|z@WlwENrR=2U`jSdd-@jccliP$B zcTO!2&_15c!*1Ckd-~Rf&k$N{DcwALiwx%kGn!6Nxuv1sZ#>BtG^x)2*A^M0I-eKS z!(puCsSIy6L)y4mgw7C9>afT;CLrt01_8HmD@K>uA^Nj3m*k#owKS^0-)gDqM^|w@cf~4vh*~jqP zy;{4c$mAD(pBtL%o~A!pj(e(Jj&x7)F6$nWS zs*Kvf|8u4APp|bK&psLTK3U~ALfScI+~&82c9gI5c8urWXsRFI-DJ5jYD38JTlr72 zc3~gh#|fO)4{JP5=8qL}eyWhmD}>zejF6RY3HjVwAzwKv z|3`$uxE?{r@JX)`J`;8epG7By&*Se2pO;=2J|F!kd@i09z5(|O->|!dZ;V;^ChQTu zqhf^b%;Um$`E234AyWA6{7(3O@vZRle^vOAeWD z-Wg%M2CQ7%kN+0Vbkvn}xZ<8W zR-afeyIhYuv3A0oc58mgkTAdpzVJgE_`~?tFBzu3$&QLj&*=C1x8A0TL&CJ<6u!qV zaPeQt8Jitv(n?6OO0gD|;7w`!aSS|GJC7P+sx%@9kw`=~3Q&Sl;V@OMWup>1u^-29 z3g=NHOk0cyLL?HAjRKUQ6l+n5o!F0KIEC}55vHw11R)ZM$VLH5Q0m~%tr*IxM!c=8M5F(L?Y!skGm>gB5Y^+5kc49w{ z;S|oJMwp&4A_$R4L^cXgf>NwSC3a#zj^Px}qehtiU_=lik%(**poISaLn#|;QHh<{ zk7GE6^QaM~ZAJtk5{bx00ZLGcwW!2S?8h;j!g#+fw@C5jD-CYF&-|pwJ8~k?~ zrrocz@h08|Prmyws)gxAp8UnR;N&k7gSJN$s3a1p=4agAS2)Ctqe2DE`0ZP6Z`&=nyFML2q+AL1|&gOG~BuwyuKF&4LD z0*Wvd#h8tGSil^Ac`+MhScVl?jrG`oO?U!ZQH5vmJa%IbUdNkw8wYV1)%Xx6@d-Z1 zmpFs(@B@CrMf^(tzkH1iC+dW0j{$99Mq9K;Cv-&!LJ^MM=!ZBA#2}<%FzgtPT#Uu- zn1CWoMKNY$9v0C5dlsV%%di5gu^t<+2~S`vs_-nH$8PMw>v$7y;~);B8Xw{$KEdbs z5@+xoe!x$-h`e9pw) zK6pYllol5S&)~WisrLp?PwgZ$91sxDu;C9xg!n{!@Ft3*r0hqOl_#%oiFj?a+FQX} zB<9NMjv}_!)+RQN;3Wcrh`ot{qlq!Ov$^AEa&bx7PsYq9Cu24e$XZi`lAJp&DM3jFFT}ey-AU%^}`JU$Z zg6iv!_`eY^Mxh5y-UW;^#$((GvL`U@DP2jYjbwI*je(7UMl)0NuQ*u$bA@XM=OO*i z3#?x0K#~7_A^!ir=>7jV2i?*t*rhY_BY_Q=Ey0v{S`UVF>2w=22owy$lL~UlPZleM-vpVV{VOl|<$lmak$1 zd3W%?C>u!;{|pE~{Ud#WmVs4nAxsIbiQg~%DUsd{%dwhsI5wh|l~L@lHe3r|d3||O zAd^j$`Vjf|kD$wSTi~0kb8PkV_r8+~4-ERf^+&r-TY{R_VtJ%GWqJr9u_`dQwd7{O zZHT~wvmp72JLdaSioYjfSJt#1mxObZQ-cg+t6nZ=DAVWc!IsGKTt7&JTT2N3ZZ1E| zPj{(LB3K4H_|wjhN!`vr81CgdFGmX< zjk8`k`4Xv__J~ebzQWyIs+-4{_ixQ3z)=)WX?{$*y4>=%5-;Xy!Sg`crDN0y z-+v7TpeU|<5_P!|l#uKfjz8#mii+v5`V(LFs27#Tneu@n|4G>>Lxxw^!V^Kh_frO} zs|5eGvAwB`8THN%p@CzT3yHS^nOF#sjHP0J=#S>JXTAgw-{DgiDypu$V+7~tU*A@L z*zH<6G$Wc9+C+)H^Vtyti`g+o2iArW8vD@r>s?I-4x{#N7fxyH@?li%0_(edKh`6< z90J6Y+J~<1Nwd>p#-W}>7oz!vHOimfWYFZ13MBTT_6vU{X4mFN#^`P%nQ9fswi?rg zr4g!1NqMx&i+j1@#5VlqxSdLOZh^|TJt}VD?zgty5WX{*JpGi7+i)AWBLVM5B)l7P z*iZDDtYCMUyf_sv=WYDo>OsAPn}`-j(NVZ+zThC;E$8Q!*kgOJ>g~6k<7g8N%-Ai` z+9{CbC}7HweofK!lU>a766k205`k^}J#!0yaSy|0EiwjWP~`H)_U^dB6xW#$EW zhty$tEj}S*sySLfIbBYE@ZrCKe?KMV%J!MvD=f&1m!IA3?ZV{j?jozCUov!L*)Ea3 z7MMY;^1vJ}@UtX!+@)`Lcr}jt-Ca+y{tey?!O}iTz1O$n{iW^bqbWuF)92ckKX$rB zZnK38u1-IU9yPE;w~U75k<~BN*>&$8omy7Wwxa11)yn$Qlu_Jsl=p@jT zY@^lW5S$S|-K|zTBh;W+#%D}?-0X?r-0;+^)rRhR($e6H9RHp&kvG3eftkj+oEp8_ z10^ok?t;ey#&KJTZsSJa;iD{0`o}?w$AwrI|LI5&xx;H>?Z=o4X>}00I~Uf?>6iY7 z++Ua-tc6=?`56=e9+{^i9CM%sh2 zr!u*>9`wx`tjM2ivSs2DDi%2JEI8moXo1|$kdl|2gY&deto0I`g8k!pc>irw6&J^< zxr=k+eq#kXK08U1-v3MyAy_8S$!|F92K~BQf_2{`{Pf)mPIV4N7R68GEgb``Ipo_E zstfvjiod2x-%rgJR>hTEMj;4N&ONlW1Fr)}S!57WLxuMUV7L`Q)G_?M^f1`C=1|(dc$FBqh6MJ#uR4z0^Hq zS;9yUuqURTn8%ysoFqaTk5gFudNHRDUNgY`WwLY(eUP$)0q1g-<-P370%sw4AH=F~ zF`MH-r78tqL9rI=;tz5(i9)E0LXY|vM=7~x19Ft>^p}6uCz)? zo;`s-buZ!Os|?In*#Qz*)o&x{kE7aP=I;7uA$`JBd*c*^;=&gHiev7M!q}{BWdG>m zp>?P;j|{WajwzT3`T0)U@fhC1;YcsncdNx^3*Gnouo*A0LM5Z#)lZDEgLVpIw_~#$ zh(S-%jFjd74Os>r{d4i7iJ_U>{R^r(E+yO#D9+D+9P0X{4!6%3y~}B*M>~ z25t?{%k87WXm4*v3?Om; zZc4^6k^FzrR&{{a)(b3KKj?!HWJNS;x`^I2_k?pwJ016*mBFXo=0q}TE{{HnXTZws zNhA{U2HqlSr5YAFk0}F1DO4E?hmkG~yEnWJ60^mF2k&Zb*{tf|V4h-Ndx5FiiDHZT zR@MdmD1#l754JZI6-}45$-QG1rdFYv+RvNq)pKmMsfC^BGSdYA^5Sa=PANJ(tb|V> zF2b)q-{d?-#gFIDwEL&Cf>P6AeYcQy>W;|*;-_WOW!}cd-Rj&E(piKV|HV^fJ{2wq zK5==-68=;u&Cie!m7s9@kvdt+h{y6N1*yZzBI*Jy*+i%O)%nG){g zEu~y)&CRBm@y^BWj<5vzSG7YpD#M*2U$UsyC$q5MGS_ABAfHgbs@lgi-MwG3=bhxM zmdaAfH>U0Wvq|0aOseX)_}wdQmZAr%h#lHXh}XwEPi^iuSyDEelXtCF{4df-n8BK@ zq(;2v$A-#Qn;^M7MYXuddgVWJ{A+KN{qD0 zIvZDOxAzIjIC=)Ux-LZ(@<CTE&Nq4<1VomNanEFqr5;d`7UgTb{@v63Z%}l#J8oK8d0s@R z_A^ntFJ2WgS-l}xo^~s zqKS0wD$ee8sfnf1fh6(G&5a>jT+hIOxi=IiYpQIUA!a+C1rY?@6#ogrJZDeS^1~)4q!|j4^rwLme zYn)8xPX_A-LqXxH0_(iF{v$Spa#Pva3Ui;7loaXcxbX1z%F3^3ST=WUxe|1ugBcjS zy}i->z6%>MhaRnzHhpRN)HO_zA60Osta>1eC|xzC5iDjDC|0o{_nD#Sq^mZFmc*G_ zP$(#YXY!HX&y78;Ls1 z6^7A8OV)LXtB?#-DfgoCrThjNE{%x{04)5=D>A#-Q46P4EdQ~0I_M%oQVvrk04Fas@^$SJ$a={i#`TX%Csc2 zaE5o4x@Lu!9OrB=3`}RJt)BVi7fE@-ez(!w={_v2Nv`#xx00%s$87k(g;iKg{Jc5e zJzWhGK7racN7XQ@%}bBqL_|cOK7UTl{~9Xz1UJHk>r+{WqdE5M&(l9V+%y=1+cgZ^ z$8pKYp9|zgkmd+LUJ;UWQk=TYS@hHn^NsQr!Tc?I?DcP2TeajvK0yLC%Y((n!W1y z!&C3Cvn~^A{LUE5yKQVGjT{)H3-UDzCn>T0GMKKseY%}>YQoe%kFYxhl2J74b%K~f#ixtcx1|0-@qXz9>`P4$7eG}rIpjJ zd00@A7xV?j!qR+eA1IF>#AhxbEcp7h&-w1uScW8ao%5kwcPNQQlZR`d+=oJS!5yjz zqsdM?gOutmIulx-TyyotW$YUg*pYld1i7ZeJ~|J=yRZNI1je zS52blI4FAvTKh>v^kS0r>i`FP_eHYNHSI_Tsl-LjzJpq#)@-dmiF4G!RY4oQCG=B0 zHV7Kd3H;8?4x2+J?S9YK_0(ZrcJ0!Cj|@vHN;JcoH3*gAt{ zCjIJ79=s`@7X&eSUbXE4hfOeMJRL^`$#M}4>n5daxrXyeEsu*CGnsgf+`mS(X?n?P zdHF>v;AWG-c+P=j!2}xF__YMHu3!S06rp+ZhOjV9mZmF8=@@2{k+hF<_Wew9n+Zam zd_E|cPJGtWB?cZ3cb5g~rQfNgqmSkshL?AD-BZJ6ngngZ9>|6IO<2I)n^}ryG_S+P z(Q!8!yY(ywbJLY!fht1?5w|J$B2uVso*{hAbLC72<2$ot6Nq*7yzeSK-B!CouCG?Z zW=aemfj2lw_?;N-G?Mt8O)vIl3UunD$-J&wGk34oBIReNY{7>;VEff)+hg6FXopVx z!CJ`P#or;a%QKVul`W^uALenTi0hy&-yU6`Pw8nixV-g2eqPorY|!?&sOiQ6bfi7) zW6)<1dX{PLo+P$h?0kxuV7a8fkhIAU(6>N#E5AQvzqnLnAG8v7iMC;~tY?ILB3g@ywDLo1qMruC0|GwdXTcj4nng)^({zKfF)nDgy<7wQY3LN(jXD_WI-& zTrXlFeQ0=UOS1QENdiNo~+7hrtNfC_>grN zwD=j=!?*sL-J*I_badmMUlhTlg7NN$jWLsjT6pdxH)q@JVESE;T2Z#4)hx~nuC&jI zxQx%XMlM|Ioc8-R1{0+3;;bq)3BW=_t8LBJdcx2%Z#d2Dz))wpz3qK6tsA<1HTxf)wPb{0svoAa2bg=LKO;2FlEtuHk*Bpubh-3?h zsto9HIUT9>&j~pKO!}isO$P~?8_yKm+S(9YuGeF(?=BbPcpYd&{Lty_*9^Y(QvQ^4 zT2EdmkwZY}UKxFhkN)_^)qoYBhcrd5G2ZLSC83WxeB-A**+X+Qu(2T=GSkjVJ;cpk?7i)fC?x3!ulz=Adg;?52iQ||9zr$_CWk;%zJ zty?}%Xb?}EJUl#dbn3ZMQ&Z*iz+mywkv%*;kB^V(#w4e}{B0gRS2QtU+Kptsm#yGF zRF8a=@Uq1%b&zDfA0pd*YI7uKU=BJSVcr$q*3l?(C#-wraO`k45FrhS-`#@nNr+75%9zR^$+cyXDv zB|d+CGnl{)(2roEkSF2Sjt9U60j7f z9f}%p5C#SZ8;@nlUJRcIKio!Ur+CGWk1GS@@R>}302jCGH=R=KTO;H!ffLj);rp81 zCUcM_hg;C`^CuJ(FqV~-!%U(p993%Z}QV+xm8Owhx(fA;1m2rI$C=+R7CVyddRz~J6!JsGz^sH7yf z&`D6X#%cqYvvJE;L=2K6Lo-b@{P%{egnMIX zKl@3@Uh2Es8@omOj@%_UoZ^HF9QVp8vNPeMzt}sJHMFQ2n~o?dX6WCaVGhP!elK&+ zwX@hC_B^q9yr#q+vFbLm=iBtFl}PqL)bq`Ye8QuyM!eIbL=s|wIL*&iAFXU94NvK1 zYoA;%KHQthGb3EVVC4X_`n!Jj@p0X+oN~FGcMjyo#*jsmBi@(<-h$6*PhJ0Hba9a^ zvRCwUV{m&cJM7L!;F1lkw}e+_IO#Xbu0)vE-OWgz3Pbi7fKckS_L*b*EV=#u0`Js~ z(LS_M?&)Dv)LM}ngfmHm3b)qTE-?e{OS*5f^QDs(5pdd(n8I!PRP}%$MD4$Zo9%3nl=ZIRS7#G(0@9 z;jhKnT#ob=m6Qlr^%2yxAy2%C>2GSJB_!-zC~rFP^sMA5qiLNpj3kHP;&nrJ98Tw@2+iCZ4W-#!Bf0 z<^^`g=>Yri(U`n&sr#kP%a<<$0s|q_v|uCFAJhdsLcut?-b@(-n>X3}-WBw1Spim9 zR?QELeaGtWpZ9ySmHi8t=92{k;$fuJAXgC@2L1@Vj_dEoz{E7``%M>2#2s3wRn6vc zb%bP#x3<1+BCjhZE*|sNAs&(%8LcKI@YwgmC;W^Km8<()+}9W=n51x6%9>y;f1p$6 znA^VIM{Q85zQ^KC_PW)J=KGz@l`AywX*cQW5dUFC^NCPK$7c?5)i#!X*I((l*qe!_QSjLCk}Hx)&5cy z=eX@`vY0iCk9TDLc|z6e1`U+7``b+A6bfW?{1D^@kKi?9E&CATa%vohGDa6zoHie2 zG2^-~7wVtGfUJQOT;I;bT|}CXAs3Bre!R!zG+V_i9dI>9(G#dn01mu5#MURKdgOK0 za1z8dgiJU&#p@=JLd35`zZD7YEfo+SrL1;K?<~f1S^@NLt6ucd zT3Ia;@y9^NXZ2ve%Mx*Y~ ziw>O+ACN%M$a23taB+7(y@wq-oOJXWR)$nBxCKq*tBnCiA#}C$Xu;!D7M{EM-x*YD zngfUrc3`(fWtrs^b10ec0VIZ4Efb#YDUx9O*AAQ9-Q7vb%6=Kh%*=T4;>FTQ8EAhH z_|m;cgH2Z}Xo`x8Cxwe(2Cm={o9_!f0FM-#LrDx;t#lAOE?mva0p~7>Q*T@04YCz1 zShk@Uv6m^+ZKFFnGeci@w||o->;w)p7oh;aVbjWG$R==6H+d@`hz=)Q>dNFxbX)G> z33zATUGny)#Z?~!2r@f+*w?5wp{EuF zOCgn&7-lQC@0RpvyeBcZJ~Wwa(nT(_XN+Zpo-TqXoE_P}6&B*ntWY4y&l(Xu8?SSG28XHw)c2+U{PhwH);o4e7MnhzaX**g0Cv z)U<`NI-||R;mRx^zm(5BHGDW%XHsRvWzZE8D;pg&5xRHij;azsSG94i!eJ2N?bv>u zi!nC5_)zx8sCY4ehIrl~G_#yVn~$tqIB>J6IRc}xeY$E-+1ECuekF09f`fq}2;>XV z;DHNB3pcQESI4Woo;Mu8IZ$zMj0+iB1N;oq3qgUSmPdPw%_$&qU@LuJEdq;-PQv%Q zHMSvZ;h7fW-OCO1~U4*xstCWpYg%u1B^-5VX%gg>`RbdiOu6B!?T~Se}H0h^)ib-f-;!0uO5!{fd(W>NVP&_s%g5rRTO zLJO}iwNyUC=@FHYUlbzv!d?Oh{LTl)V5eM|lkhqI(QS0YP(#Vc&c?>a-zo|I^M_n6 ziEsVnOTJPz2B2Rrng>nUY32F6p4J=pzSMQwrVbW+4YIaXus@eU+EbuWo@M3B;`a0) z?m4%dU(}&b49gtH4A>_oHt zTbB84K?#$_SDdo=m%YCIaiQJWI4&pNYZd!c4ew*g+(T(*jR=1r+tI)E87N4!m&}OD zoa_4X=rcZUO9WogklGMSq}$Q#rMpH0ZVo zz!m;9KTR$IgH)ggfN})rul+JV+5LdGp|P>{;T}f7Z9~lMS(>kYKAtI! zXH&n=rR%cn`+=pfu%O$_wRR^jzXp5?p6~2GZ3Lben}DDf7zYCMkre>waqMOoAg8hq z5carYWzwuDp`F&Ow$WJ<{M`&Y;14C?@5+#jtl3O<@9FJL1(xk(Li9|$1;E`DnF)k@-5RhTpBk3r> zUW`D}bKyERI!fHMEG&F7f~~Ug&yA{pJ_G<3i#Lf2fSAbwjnyM+OGifzkfU`>O-9nZ`FQG!f6kaI)unmFYuZhv6zTSjBcH5ew6K)4 zwvjq;5QP5dF0dU@pFOj$(c5ec3Vor~*I@u$0_xMJzy?3v;hN14>*1=+Qkg4<331yNZI?A?m4umfR|=v^Xx!vTMtE7wWkJq_=yrHdbj{Hw$}f2U$%bln1hgDL~-4G0wQif$iTZ3Yr8bON@H1-@CZ*G`e3pxlr2M0B+Fec%>6K z9)G}gr)ZR$LI4DC06r3qoB#w8v9n|4Ui4rG8QmY%Ld~n2n_1g?&wi%r7a*Ym3818u z)C%yV>Q7lJSnP$4ucwW|-a2f^0L&FIVX+S9L{;)t2NJl4m%UfMJwmBD>0{t^+^~|+3v4VTkiIHmt(G=GW0{=^3g3?x2jIYH**8rWIrPu8B!_TkGCTagY<(J(_ zldZq0AdEZ$9QnxSj{bf$78VwmwGo)enVFf7MeXsNmiTWCABTsBcXV{vf&68|-LR}x zzr~o%fGzGbz`OvSF88xEJIYeuUexYKL1S&nP34PVtBY8+8mDH`yg%<3M%hmxlHNd_EyGBZq5u>VH2gO z7Ojh=$P88A{ywwvXbj#9kb6@xw-Dp!;BYB+dvQsy62X?M*Po(QWFab67003~?he2! z71x8??}oXCh{(uoz_K)50;233Ef6YBHpnfQwJWhTnW^4e*WeAizMf(68FCjcTItyA z)#ssN9TNUF4uvKFS`GokRLmNtRE22lxx_sgFPMQ}|75#i>Cx_#|27|MB9unEBP+!a zwP?M)2a+ihOdvNPMJZRKVs z8;<8F2wcuN0M}w@Xc!AP0YFJL+@ACgB=Jur7l0fR|8#S>+pzT4uV4E09+elIA4#2c z>Yb=LIDTI)dM_G8gFGxEHg-$lAzzRVaUxwD6QK27)-=o|K+&=|j&FeN&I4Kj%>wfZ z>k1))7at-3p#q#UkH?i=>bhJ4R|o*d5nwi>ru4j%-WqkWIv;4mOoqH~k5Pb+T`%ir zHtLT?OHWn<@U|lm`x!>ob}7*TbQ$x%{Mcc$7p+!J5N znI#*nbOaV?*Mi~UgMN$Kmod>|5fb(R2+-?K<`pnwl?5=i$JMg`#TD?|FK}==kCxkm zwEY2MWd++(*Zb}Z8yj0dXy}l$$qpdn2zczsK@h#Va#1Z%4=*lOr{|U})rr3C^ub#| zd*+MrXem3Fv^=Gc1u~t5H^x8Xm-p$eL4z!^O>N#= z=d;fP!n6^thC{fa$&yTLlgo@)CAfCNwI>Wykmt{&-Xw(yI@0?FGqP9f%*X0`+m4po zPhZF*m3#5Fyv1BtSn1+Z8nqYv)@$6>-`{`l9v2#=O`8iYIQ7i;ejv()&p{wcBg|&` zfz*63fSWHOF1~$;GyBTCtBDeOyK?6mPyl3>&3rO$KTl!0YatBMu0zq5_1Su@5UrR_ z!%EHCiv!cM#$sMx4N$re?hi(JpkkuyH>w)efw)@Flu_%UY&Xnx{{+O9d(hY{+yt~ynZu@B_AV|7$skBh zFsGTLyl#)X_m4qyG}P1||LJAvBG>z4Hr=l_UHvi1OgjRw6P3o<;=f0fT6C0wfwvkFcpq6S}DoiSLuA^$mY4B z&1W{0iAT7}cps#Ka&0)#O4-ktvv-_gYj(Q{o!32Z#T>jXS1)xBqrv4V&bGCM7@{f8 z+0R3cy&z)flSMiS0G|w9EHACAthmrEc=0SYr8!d{9rT1y+r-E+7X@}2u20!w;JN>H z(CF)D;-m|rWq!z1hiFB2m6(_ZciV#6u_%3c{b5r=-Iv{yE+X4c#kzu;RaWNSNEL8B z$nVj34gC|prm(iZ+!W`GRyqj6GC%el!!!m@B#G;eN&OB~<58SmZ5nHrM)89Za^ISt zeIoTeKu=qrWk2&oaC;ca%Ov}+l+Bh{!y`=$HQxkxj&N%=k1vfGZ0B@9$m9?TWb}gP^JstzQ(btJ=vPccm+PQs&-J_6VXGGQf?HA`o@yceMOMyFTgf+6ul z5GE4X{*ol=vrO2b!X71e@WsB$eW_5d#6 z+h(e5$-wdyT^}8tl!Y|0@fYXUutRPEw?GP+gG(Mb2+=$g&x)pr7jz(9)c;8xJX^Q+ zBZgmutmP9z4p#oni98uuis4`i@k|{47xAA3=?#39GHr?F(Zmj+^r%N{t|Rr8h=97> zRPFt5o`j=rdC9>deUg*I;N|uEe@)Ul1^^YzI=Tw!MGXxNfF2gh3K|*xef?0T<*!Zh-iihpH8dZm=5ciO5AYk z?^k#A>L9ge=DkM?VvuzGLWuIWNIWNUzvN`MVbwQU4M=aYVkXH;vbZ;_7_!4Muo{`L z96~4lxzPhdB3_l+dP~APQ2V1)7{fFlJg^rHw zkp?eT)u4&(99FYZ9F-y*hVIp|X|h!p!Z#usY*b16VRL}BX92D=Y_UE+tyR6p6Z_T0Nr5MEaW(wgmY`Zzfi z)?-}a@~8mWo7*?J%{$qwuS?jQdTEsjIxD_-m(afl81*(f9sJxhWKcFh~0f3xl?OX=RvoT;M3FvJIUv1Bto|>IE*VU>!{Oo z4gEm>+3^=Gh7!wXR9Dw=`S<55^QkQN%gYGHNU+vu-+qL#kfju}dn@uSPzj=bvj|i< zebNYC{YW>PEbYy|Y8>@NkpK2v^dYP;y?cE1(Ctt)OagKLwiaMqU9p}=Pvt}e^nT+{ z=~B~M@bJG$sQAHsEShJlduz`{X#-hfexJ2klq_+y_$X!Cu7sn2L~Wp*4sSJC`Wo{$tY*W+=?od&-#2LvS=jl=k!u*Zs9h@EVr26|dcNt>CB5;9sE$f+>| zqvX#H8)Bo|#Oq%>u1Qu_lU==`iTE|GWW&h$QzDK2_J2CJ)EA2xaBvyk56FaPi1YnF$~LV=!O< zj6sS32D-Ac@+^^?is}d8?Ez%>*5@GPJozM=@e@=Y8A0-!0p9QE?q>JC_qe}oe&9{I zoYb;40kv!ni}3&e-f)>TJLYN~qy-d9$ct|E97aCk9fQnur0$C)DB#1f8K~1*&DY@o zRmdYu0gy^R$^rBdsK#0?G{k|DNROIZuEm4# zTdk|-{eYYYNHx|&xEIK2X}hU+%bXZw;#ZURlR5gvUiznKFl0TMGqUS7luP7k1>f@n z$UE*}mu90GlD9w<(b3hl^F|5953JPf?QK9<7>nZqm_?9aNQsFd(?m*=;A4f;TTJAs zcz{%&ik5b)24VzP6kw3>b^If67fMQ47rY-lF^GBIfjSAiAYe9>xD4j`EXVrf;$j2N zRe~{@?oF41GBuy$*4G&;_g}pv&U39mvD}U=FVG5*TCQHR&_4wWo37WjNi?Izhj;G~ zKtaqD)R*QU-6ZQ}O`HACK)ng5HQ*Jl3#zPuoJA%|`-Su{At^xP?dz>I0oj76xOn*& z%llMsKyR#rbzD#NC#3|XGax*x1U(M)j5WPN*Q3$ zaT(QHjKT!P%1sAPs4+ZyTKXe~0*&vC@|eL&(?mYap8#0`oxK-$28K&V_cXFMy=Zw zs~`hJ+^GMO02EB}Al-lqiX^3_A1#5Tw)yY5#h4%9Yk}6{0m1HUO93eQ;tw+xt+*l+ z69+(zs&+kEixnT8nMh~5pkTE-3{GDb)gSO>pXMIYq?jC{@jIx~2MuH0cH>fFraoRXVoudrgr#F)62%-LiR z(U0xFsRQZ60mWViu!^@hCcXOmKBh;-&sBp$?=8rgdHoM;vjGJBIq{8GSYA~0F_@0L zcR;S;2ox5;fax>#z!E!*$#Mfw^cZOJs!`~wNd;=WtDX2`YCrsul(PqiSwf%UC zJ>BHVzmHt&>@Jrq&%$p4?dH^&c~4FQD*=e)sNcyWrEy!^L61 z3M39K7HF`8ExyKT3#cyO!!B4qsZOYj35Yt(-)SCD#N}QIdH?UZwYuBgve`x1 zD!|_V6Zl|mg8a*V7Ev(V>MP+O`E_jQ%?>3LJ$7&LGw%AW0N6!~9zO$qUy3_AIr&E2 zEk4}G_@d$2A9v!7fdV7IvO&QFoEERZbbz<{8oYtN1p?;kt<4`F^GTW2?K^2w0q5bhju^N$3!edqL1Y#R`#4+ePvy-6dN z*aMUr2v_SddhiFx0MGVjBH^5=_k$NK6AE%_Q$Sl@U0vCA;;0>g^qk#s>k}|8jkNPv zz{oSG6;(XUH@Z86a{31AFyJYH0+JU@ns0JI&i~y>&}(Bo5FGEMezvr{^B?hX-NZyk zuXrGN_3H6EH#fK1bLftLTVLGKnfu=+-m?Qm$AL|j)?OE$rLMz|{lXA@b)qgNpDZ1H zE9?MgdtS)|UDmaaPyyse#0$^R^u9Od_j{fLadtTO(vzOIS0krM4v7MI#pzA)Jl@4o z5l;GG^XJ(LeH85r2M)Zcsfo0d8Q4otPY+TySnd5ex7|XpC;H62SAFqxmjY>%^#Nhv zanegmS}F^HKtK`q7ZB7eY(LZi5rG|`i9yD_Z8t3b6^K~?ogfZmD?n_w23#WJW|si-Eep{w0UiL>|t_$cWr#SZHV#s3U-S z6!KS4fVE%gcM&6Fe;tUtoG`hj!La<%9w|-ERJ!D<>EsXT zH<;04HN%fBPaPIvb)svSCBnGfh<|>5($?KgyC|Hn(pTp8Xkk1j^-*&jsE<%^a#nVb z#QysA0wi0?-$02ixVc%Fd(Q3=DCRJL)dRjKu+<0U6qLY_K7lHW9|lPuVEb4T2Im~d zWYNil5`oKu$9GT}D^X`$TU(osaRm+%E(-)U&^t7=0czNGFsF%Py%czD9k^FRBO_o} z7;ugocq7E6DyYpWAhh9;6cq8s030FYwiy8WA8S270GW*0miPB%0Y&{34Q&+&Msezz z$DOei0KUm%3F>Sc&6Fkp>@!@)`rZfmYqFDz~oumEUrW^!K18w6l$0G`v#EICt8|7?z>g6lH=H`}-bE3A%o<^Q;du zXopNLh|C1W2^SW&F$F_#+1}CMHTYhQN{gsa8oFPglT%YMVE=vi@PP-=ovnk7V$Z>% zADy31tiJ@4`WtAC;KV1eCJ^ATh=}^ZB3}W;2qMsGuLJSK8W1#fSaCT_={PH7%aQoI zBi@4@CIF~$jDAp^(g*bWKs;xEw6efuomMpiCufw~`Ofh{{ozZ1G3@Qx61c2a8ZQ^h zog@686Tt)T(aFgQs0GW!v2`Y^KaOQJK*FYxAqTQ6K!Ac`zL>J|Z^Dp&u^(px+_(z* zVg|N1;6YA6<#tw_Y-7KgNv|mpa3jY+WCK!4WOphk8OWbRjAX~w|+ zD{Oy`K;mZ=OAiM~YAo#|n#EL6NUMGicM1RnV1Pg=3l3b2e*<|?8W4)P0A>#@u?Yxw z0O4i&0Lx0Sp?7rkLDq5cH~|$wxNHXze|saFo))aN>&Y5YcCmVC+Fx36yvbpDOK)oc zzA*I8>hc)D1_YMV1XhbiCiYW-7f}mb$A2zQRmQ$E-7uFt@dIEpy4gZuSHqC5J#`z0DWpo*V zXm(SSxW`$8BOU);;q)sDF;Wj1#?IPl9=e^v&G+@kpPF&zj%mh!Rx}=XV7A4#+-^=p z{7)}L>EB(4@DLxpGtiDDC_e3YJG+O z|J;``x-&sQ6JoEYQKak@*e-ui+4ysHiL7cZHOQ*Yvs^P1WO6+4BW|Z^KEYOG{UFUw z?e*P$4ee%QdsDTdRv~#`Emjq_9?EwdRd?T9gxAOYW44->o|o(49#DE9g0nD~KJ*yf zSbaQOwBMbXxyTD{$TNJjcOk1r;tM8dg*?mhv19q&{m22T*TP&>xQ`+J4AtcQg19s# zgNL9)@t7;we&|x+#322bI~2Xh_Y#dVunSS@%|$YBx1uM^q;sFlRVt-0=N<}vtsu%> zua@Tj6b%F|=d#pNBd6Rh%Q)YW3kW2#L(F=5halVZX9x2M;KlUalug)7I9T*K5WYa2 zM=j+YXjBfI^i!pVC)xqOzXpLE)|v&gWT>9jvAj^4@tw$2%&`gm(jrow&awjk>H7RT zkm*nSTWWZROikp87mPkJ0eh0jd`Uzk4UPe#@3<^CsxFtJ@2DSN_M77zczC9BsT}}0 zr8%}#PxB>Da7YL>*YyXTZodjjF8g1ES>Wa`SfliM%>}H<(`F1g577}3ZGqTyDyC{G zA@0Otg}%Og2Ve~|9((!=2wZ1P7{pwSZ7gPJncz4DdbeT=ZtcL&X&zfeUB7=Fr7$5$ z%*Trer&x2JwJ~Fm@2w!H{D698 zfiruxhpj)X-L?avphX;s|9-o}O0r$jrf&NpLt#CE=WzfuN^DN&A+}Ve)|pAYh&epUvbL9K3{aH{ypLM34Rn7jF&w9-?+hl!FBR~=nK?J z5&cuVD*qe~3>KINrHZP)wF{(tpMpL$Bx!hTTg=5va6kafVRR|e`tK&iL=u=llu>Z( z9bCSsm_F2p9}LSZH%=cfw2BpZP)BGM-fB3hacpNhoD5>Sl?q$?`6}jDl0{oF^mdnB zZPu43PX9_1qZpb%^{0(_r)k)FdR9XQDke?tqzkpA!D3smH|W4C>rbFhq{F{z1pI`= z^~1#hCr3VC=%XuV4LP;J@u;fA*>}Hm-xwGhm_No5RV=1Y|19!`{`uN@*yxL<{xpVR1&z}sI?@SashutD5%O zaedKAnL3>?qTE&{G)JDaUON=wlff&01Oy*ZXm*l2=TuqU6~{^|r%JAM!O(_E)9NoT zyOk%sQNzEhnjfe%JmgN+q4tRXrw4n-p^#ao%qdatd(nJ>X=gG<>39sOBBGQ6FXr0o zkhL?0H6(;6XChPtgnDN?Srxga{n&9?g5=AMhhL#g^B+=E&Zw;ysacq17F-2t?G)Jd z>`D>dJy9S=LMYMl=Om8MknY|*dMa`{dA^vrA1Rl5v^WteDc8(q9;+!(1!jBeDC0n# z7xBMaK7E4naInX4WLUb}E;LvedCmW&esv+Mr)PgWx)z)t)ZHa7>sA+8C2NoL_1Vh$ z8w7+s!|5%GI7oMml~(iX!{G;tM(>^F;Uf5rmxwteG5ZF*K%O;lTfkSRqYMLr{$i<*%a^Hz`zgy3|EztnU^QEPJg?< z_~8F*PcBdTEcR}HF(U)BTOkL-gp_MrE?EU!@s*u-QgZM8b4L}7D#J1v8Q5)(Dl!??r(Cp-S(dA|fClT|jyX5Q_8;Dng_~ zD53Y7Py|A6bK?EY{AXs(m-#YlP1e2g^77`qr|k3WXFq$N5H%G!va8frArJ@|OkPF< z0=Y~MuJisT0sm$gxV!*ANX!)FWWZ1GIh3vs06(t0lGk^EK(5^;{(k|I^yMzNN$Lty zeoi_|a+Q(`$}^mF1>Cyl`drUd+R@$~?%)cMc80%ngAoSq1A#n% zz+|3kd5*15dU$HV$Ix5TO3+5NXsKr`w=PI)hd#*sOY|x`M=0D_-$S2chO6u=zBrSc z{RP~3jRo#kn$g6D@wx59^bRV2{U4iK4}y4LP&jnw?8!f$mAoIuByKL(_3-m9e>oOg z_UcJYwAv7EebCMYx<>reyo-YVdj(OW4w3%PC6_$S#eXkfJ)QgK->cWxZUz76&qkkw z{`&WF=Izgi|G9aGk^I8Hmo*QZ{{Hvs+vWfFP2N_9K!(hxbpkpC2pN9(iQv5>!#xI) zJvm~R=)ypM2fww+Af(MEIUUs;S(^mc4IHBd6kzvp4qi@y*)^X3c{2RZlka5^9i(p6 z4XlyTdHK4q``JO7;JFrLjrQE9)0?10lh&4AHfLrq_#1q_+DLE1y&vN-h!@0{NwsYM zE)SgmbMQ?XzR{z(#HEc4uI@?j`+^*_ti0fE z)R7p+Xec65_T`ZYYgwbKMKGat0CUzI-lPy%;vv?w)7qL%w7;4D4w0CZkjom)$2sfM zeG)@5bN~G28HG{N8v3aqtCm?eWuiVdkpxw2X`-XL2ffuk*L{q5;H0Nm`ULY4nSIAn z(T5yE7cQ%G2W^hV=QJ0^o7AX?RH4*$^ED08^Ec{q5^v|IOEbnq66Wbv1EvzH)-1~} zK_K5+4i8}B8?Iuij81ySvPqENT*+s?O2JfV+732RYXa@@j@Z%D%VWEHtgvXuuQ$TA zu8{;1W+_-#zs?s|TK`q$%OUO;#|M@7Mtq7r3tPDVU{bucUzd|_WJ_rECE_Gdqx>Lwb(>tMWlGR0je?IaVNE?4}|Jg(DUJR=FJ+SlAiGcdu>!zo$nHZSKK zbDE5E5fnHo(BGzw?EaBm%#ic?ECM|wDdOilIvvi5;q^+oK~?MR)!p} z7N#Tfvu3Y$J2^S5|K5E+KwAZtGOpNsj*N|;y}Q5jP|i%$t(Kvk47BXY5vX3bicBbq{(KjJSa>-u({)MxOr&%7YhlRz$S^c_j{M62A4(1fO6X`CROf+S> z!hvuNy=+&u#kmU?^Qa<({%RNmyNwCf^iY<6eW9c{i(zOsb*XGTr;*+QBgL;Z74?n= zv69=y9j(*Em17t85z5MCa5Xef#&Jf zKk#^C*y`Q9)A6&h%=cgmnG~}i_3n*x9&LFhZ5#;c6gggHa{7C-e@8bWqhbJo5$qK_ z!WAihk}!UFYWbD0lh8BZyq(?L{NLNMt(lJ+h_(Hw8FxWQ;<_&_Gxrh6Rw;ygd%GJ` zRl?+wYr*L{0k@y@!Nf{nR<$DY=JJ+vkkheX4^dJm=v(ES8?x^nr&RDJ8iT2T(BVQQ zG&~JgCs+~Z`xSCzPQ?9m)O51gdsEhTocM(=tnT4iTYgPICJ4?FDln=8!c75E!R0E_>+YH0ekBS zE=Ka&rk6zk{2RL3w&dJejg={!+|n;HD6w&su?sfJBdZoyzv&{iUyaE!Xn#+i!oQgE&GUT>d9A0qU({0 zixT?lwauJ&PP=Yjt74d8KRG9DF^Ykm2Fcl82~0w;F)h9v5jG3c0>mQ3UW zBE0_Vl~Vr&XA&n>n?DaiAUSst|M6^)9PoyJ@BW{?*zeEvY;tmPiqRNoF8f}}o}GR= z-7em&-_3Xj@%P|Jz&^G#*PR>1> z)=*lxvs>kRE@j)kv)-PephTY2ubw}Z(uEspSNh}Lr5IjjUV#kCnmOlLa))O;RJ)vC zsX~)#JEXYuc`7`&?KI0@{d~n#!Q>b_p#Cj<*2V1j?D5o}HDQ=94Qc5cT)(QUTsQ#F z&B-Z6r<)j2dc<`t@_ zEtI^!lq1Y}r)YbJAJ>uh5C65jp(Q_k1>r=)BDbb>9Mx6APJiY*f&#WSW30BI6b`*! zpzh$Zt6^hfld^`)k9L`R+{(C9_gcjJ&O$vS`2YqJ`$@KyLw=PkEewZXaT@psYtW@F_{)cX(a> zV6{joJl~`?e4PK%IBsGqj1qyJbh1!?nyP%^JG3(5ns!EOu6?IU-iZ6w&cnh=?R%v5 zp$!-`YZF^XXc3~eNF*pv8~G2Mwwy&0%NCQS-mm+fr6#dt&%Or}5<8)Q70y&nubZaG zZ@kazhLbmm!`42;`3RfiHT`JxO|Z|rp$eQdy@E?^wua7mKQyX+o})u-4TzQzZhD%- z@jsKOgZGY)V_><|7T!wG`s|CygO^h}e z*b1Z0L^H|-3idkl55A}2W%}zKqP9a*WTPS}CY9NqsECX*Dk}Wi728@jr-UPusgWa1 zdbxOPw_o!zFaLga3>2 zJ)i4Cqmi;9()4L_)acvzv(n0OWa$0#Kb>8geFmp%I)QB^#V0#f;lD*zDu!;Jf4}L- zcl67;@JK{Z#*f>PgJ?94eRt1BN^a!e3Xv9KzG>rL;OrDWAL4dCGPbU{n=Hh^!qPWd ze!0>_MK4{Q%}N`VciT|?&ll63Y(D>dIjF5sG8^%*s>Fg%;Gpsv>P&h>PC`P0KNN)! zE8UIBqRkc%RLTJU+X`8o2Yh7vuhRbrY1H_UGyGCle-aD z<Qr{=cxs{Bdp-{WEg zA34d})s`kabh0khqxk2a83C9yOvGoe@JCCFwgqPjR26UiyYm|xwVr=+TMXV0>f%6teNaF{_^L=TwdC9 zzf(M>)DyUj`8{OpD?^&N_fTs&8!fHwT?s(tg(J?ju*#^p$tK=7As>3WmsRx63$A|Q~X77&ln#ga` z4(C*K>*VC*Rm#ZM3(m7Wjbq0b-^+_{V}?Y$Sai>+wY@aN@$1wKwTHickv+5&^X6iHkzv?XT?8WSlyIbo6<&@;Fe$2t}r zo3ku2#ZaG3N>YV-JWSg`>*i8Op8Bi*4T0ufMC-I zEynr&GoxnFT>|oWcfXgs;QTAIP28_K*BU1aL$Z70>Y~^$gUp(Hc`Vbp{k-06Wy)VW z8{nA6SnZpkh6W^Cr9$A_F{aZWy~5#tsMg%Iu^~7R9C@ck#_XSC4Mm zPgD_SfI~W%eCR#d{R;PWgYFbj-A12%_oTDx=2PYUDIdr1eHcAG{pswDfIdUV#+*pW zGd*6*F16_Dd0F&SUu&f7!?)8Z&1QzT(@$%KV5=dC&IEkrcl4U!VRy3eEI$DWu}4gN?W9` z%aZlZQn$g^7jL}x*7P^Zy!SVZF_EW7_!7LU`1y81b8O-C4at*q?xurh)f3LwLZnq) zvBn;Vfm~&SGK-M+*Y9cY_-`+?*KM`k=XYPz^E+OzwgDZlyv2Cu{nJT9>&c3z+GqXK z`Pz9&B*(b=G+u?aMXHgC`5DCS`bfi6G?|;aL-MZ|lcMRBmYkBFP3i^lM#7RPUu6lN z!@H=s2Z#?xI)Zzc4CvzU&yiB|O0(9`=$D|&IBd!E;`syCIKDpYHr3Vu#lIR@2hjeEBN~~9N|!7eGPt0W#1F94?{QaAn4wvqC zC-I|9hTTBVaMR7r4t1+gqSa|4Rf@LKb{WYhM?O82u5eydC^Bs}!5X=QKYH|t5eC+D z0Bk+cy%w9aAbDsU79Gr3J;$CItuh^NKM zq#j>%kWIS@6eVHK4C8;X1;wmB|Kk49TRP3=acz7?}g+}co^?xZ!07x&p? zVP=-IEY~-zvg_|j5mn5sbeuN1eEG71sw$RJ*C=DO+{$)+tkR&|GJI}+9u@n?-L8$6 zR|cH>rwF^q+71`?gO#JWe}8-?q~&=S6#~qth|}CVa(aovUWsERK*2qdn0a_K7~~=b zN$GvnW_>oR#vKNl{EjQUcWn~8EgQigaiitP%&aVSziIc43H`7;+{3$A(-L3<0ByCd zQE}v9+^<}}K1f&{s03dsGOpLzS?<+UQ~O8!1uy_D7;w>7uU*sD)60V@C*^r;P8rm> zB4CpIP^j!?^LdlAi_7kit|_aiX#K?WMW6kRp-@WpFI-#Nv|C$SM_?;g0@M-T9n^%M z4SyZA>*(#pcEqt0n;R}N^y#kJ4z;Cm#}6xKWMquhxuNU?lKoG%?I zmszL-`_*_F8)_8iTi%{9!s36Ji3=PkOb$rO-d@_!aClmJorIOIPuI#htm9+R#?dgm zcFUMy@S%tdw{Mh|vrKQ0W;i>WZgdr`P}V6zxP#|Tp0;QDMsr4`d6Zv!0V#N zRS$87DI*vg4F@lYvjcO=JEjdSGA~}F>lEtE@~K)|=b8BJYBvI?>FD~PqsO%FI{6h% zd2`_D%bP_C(Nj*R8yYf7q>1I2%B`J%Ar>bVSd=|YdtjG&M0S3_r^u_@b{%34>PF{( zA08e4GSD)*r7O}T0ym)sfIfAqCC9`8({(RkV0BPi#BTCgQL?QDbL`gF$C0m#q1If( z=8l5db2am*8h*Pp8aI~<>^4r6!tdRcR1;MZ7?^7Gaod;>Mj|eeTxY3G{Z->MU*WZ5 zDGp|kNLw0DOE8ov9m+KENLUOMAVy9Ejj%?R?TslXG%eWqnu!C^(b2$Ua6H(2wmOf^ z;!C9DLgC54RW9|Upe80Ju9}xLY%3t~l$I72E5K4h!^4Zd&JUt=xs_8yv}2f6ha+f3 z7w~u#xWv^irw(L34`Z9;YI#o<)7;z)aQYEys6Zt}q}`?xa|vkKQk7))cx385Hg#-l z3K&@h1a#yg>D)w)p~AxYrKP2ACho4T6}c)Y1)#sIPoAh}$%bnHcG97eo~Xps-WeN9 zQ8FrF;`DR2FGCvG*I2Y>F>#e#>zv&v?%YYZ{>EQ6h~$8U@0;9(rik2z-y-0uNup=R zl-iNo%U(AkM?U^pGDpyGG<{M+TU!N;t&^)_7{&=nl2PgmNgB9H@fD*Y8UTG`Zv&duVh5O1YVM> z9kV)s#0S&+_W>v_sGz5xXfjb>ug|w%_QxD9sAwG*heoUyXVWL>o(C>OYib7mpn(lM zQ!FKsbk9jO7Lu~B7f^p$%z&W2i!ClOfZ9p#f~`doo>ODh)?wdS3R&4 zVC$jao$+jJY#MF(A8j_%pfRx9P^G@3UA)`EuPAE2y%M8ZjeGZs;Z_zFv59_X0W(OK4QWvf=#0mriz0cAh|a=(ysBtH zs{C+gnVp##4L_}Nm@MEltco>)t^jmoB*za0?}bS}pU1sUOG|U8Ul%Acsx7ma0L@cU zQs&gu)EF?4pYI;i9W5o|ce{D9ct?if=zeP!uT$bLSZr%-ax9M_ja>5O!+X2^M=eJ~ zgPX1ox(iYB;T^&V>sXj`o|+jTcXa=0~^< zo-FGx`;A);Y>)Qh7G~3IcbmSBb3>JPTtc<1OY{4j??um5vS1e+`%+YY+H^Oxbnxm* zvPvwp>s2=A<~Jp7g)N=BRiD*AR&Gumgc;Ab0253Qt>Pg181emyEYnk*E*mgemtJvZ z*i~|J9l%{HY_VK048Ek9E2cdg%qo7TS5h}Pg0{oR#0z=#ez@%EB6ongiAkzuZ>lkB zAVTtZjdyWrDZZhnyL-qg%}-}tGmd)&cq`b&&1cUBuvp#O%qoMZk|u+k{nS=7C)lm` z@7`&7X3r7X9$?b8fPtyZs{)Pa90%sY!2IG37| zmo4w(CSF+Vt&OS-5P=6U02@Fb;`W?&wzqM8@H$;h81B1c% z6+X#hnyH|yBh&rw#K#1O@{&vXvTN^@kh+Pn_pZwA=WNNT2>TCKM*Q73m1U)1W)Mue zx{&BgNnw!E!C~y~He6lYVUK+zZ~y3R1>x|%afiMHJl*8fCM}*ZTiZ5;EIM?Z80O?O z4TW$P5h){Q^+e|PKvLVmJoO<7<=+R~AxAcC=5_ee1SS|t;!EayvmL76+o-N+yX%jW zP{&J%6uQyV#xnvwe{FOePvgcNn>k&^h~V#k>W$2Pp`Z{7crHUgKma8L zg?zM#`&u5o|3O9%mXm=&da};Vg&GUO59fdwTA^1`At52uk6*kVE-_V|`#L3NR%7>^ zj8;SoK+GWE$jdzIU5UKj^WlO8m14(Cu;NBJF6g_rr+$p}75u{GKj(-oW zW6yMej|{1H<&8n<`sRTuO8SDnXw>Nye#yygjLb+rLqs;h6Az&s_1O}?H6D(u-BpmQE|wwpw|ULUW<118YX)1y=FJjI<8 z`J4Cdb!y-RllTyscT9cNNdliXH8qtP6jdq=D`G@98#EU;!^&J&G0#hy&+2kj(~6Dj z-Q!O3iBxcKPz$Mua9tZw=e6n?l)fRZ8I&L=ZW0+sAuZ2M3-^jEpNF zKEVP;&EC*73;ZlG@@qUlI|kkib8@&tbOHcYh)z#IQ4!@@PuQ3=ym#*&FpAGTsp9OY zA)GU*B`~G*M|XPu@jF^YrnGI2SF0ir2x?x-k0AcZ0_J5rJ3Bj8s}%?H0}xv4g$6s_ym1&!0b!D{cXvli!PN4X zLsO}#kZ2N*zElP)*Yj`gJa@BI1!)SlZ+d#{hxHd>WmY%oH`EMtOd8{poqw#AozN&o zohZw$E43GYok%VY85X}ZhC5EPNrKaKlYS1h;N$=Piu zfO4E};+i-0S+9&!??MX zUEU$CnOA&?q0+vVVU+5w878hV34tysU~Vm08#222 z|5`|bg+Ov8&%jW0jgZ&cSWr^M_`u+QrIH6D48^q-n<)ee;R;rCapJCcc7If4Bgh@7> zT6yN=CHi+}Cq3K$p6YT)Z@}xU zD+m`g_l%ot+*ni$@HO>3JIrm_-hHw%b$@L2&6{=C2a|QH8;nW< z%3!q2&m!LFx|R_m+`(P3+H`-M7-z*lLsilL8ZKNscIyhq8xD<>X;n|ObsZXbVsJM! z`A+hMCJ*&UX{TIdxAsM&bMcZz-Wn%$_2#bKMe0Yr(;h7BMK-Xxq?)z>W&E0n=xx`1OsA5Xw zUy)&9n(ppZJiOd=wc_S&*SkMI$y#v3Rhnlv3F;n=eo0Ks2S~twu>V{ixSL#{8)+l% z9q+9VfVo=c+xU?(!8KBWh!FsRaL%H!H=ntz?E4K6zC>fW8-70{~!CQMxebjcabcGdN+a3OU0Uj%(=aWvOosBOh z>Lqnp-KYC`7klsJ$=*J{1IM*U^g~0&tol z|7g7-`CC}!+8&gezDC1?-h%zJ3T+RGVVI!ggvobh-?$XZ{<-QH!sH#fFLRUmL{YE+ z$S4Eq_LZZglZ$?{S8U4vq;mNjc4LHmf6kPW>BQK^7=QR5LVgGO()1nMCHldnLw;42 zfP*#i)lnSS;)WZSH$NVic9iO`dL4Oo^cbw0lc`U4-ZBg?3B-6D7Lnfy-bD(GUk3X? zYTh#QO2N7GS12qewAfzOZa#81Y2leLO5s_`h_m^99~~O-6r|y9NQ=#*r_b#C)-=PP zs_E6(4VRUy%Y$b^&G5+}oCJykB^8y9^YZNb;cVD#Kvg>yTCl{l#Ot;&Q@$avdBA@* z6uJYRW|NXtRmWO>+c&PO>&eKkQ<+txfZL4y_~wuUz9}TXUxm zYOl1HC#FX1{C0MBGy#DDj73XLZH8)^^6c!4hdO+FYu3n~*iiT#FgSo7Wd>ybOO^y6 z844Ejx=#P|6Vqs0v-f{887Aa_!$xPnehndt)iozwPa5NJIlFrnjN~W~Nf+ehSvuv+ zXC+_Glh*qBDEWY$@mqFmOzl_$H%g(DOnal`xKhJ99q+KS>VdYX9A!6Nrcyp}Q$B`< z{=J(p>406Gcw~Rbq`16Y2pmH}TYOI4qQKt%WiTt^PSYSthp6!{eKz4i6EYu)T?hRp zhKuS^jU-MwFj?fmO72S=syWnPp_uVKN;aQTW?yr)iTv}U);&e9zU z940WVgQHqM-Jv-*h^Lp*8h^$CB>Cs>4m;&~7V95;S?kpXthGKIk@cxquZ!(JMLy{%Lp~;nV*)^u=ks>DJO5KK+f328*5=N{J`Esc2mdnu>bHuHf4h1zgr;1P%<7$h! zdW`q7mvSr}r|N9*4aXJpPx6B1`yb^iljesf4}&ac&JeV!lr?3+o{Bbi#+O$ELDEuWDeVpAFC{we#(B!-~V;Sh;RKtsv#^Q3T; zE&9X$C7d#?lJ`EwtmMlm6$U$wixEfW~Y z$Hy0@o6a^I#tXFBIVYnOVlKsfxG|M?kQ(icp~|Cf-qSg(-tW>pyGEWEtKWy6=Z1oE zMz%XiNqUIwQeCvm#yOfmiTO#|J+>W_=07sO`D8p86s17I3pD#bs|5YMn#fZ5qP~{b zW8)pPdTZc~fIr>n#`3U&r3*2M-=gw2T+ZBKC*OT$Fo z@!|39^G$*=5lYZ75*l1sMBW` zybKH@$&}CS_$*x_2WAlcR5YC`XA(1moMAOKK zqkgjZk*#{|&Owqd{PA52K5E2c$SciCVWzgp1%SE^)w=9$FCMc934Nx$@Om7;8jv31 zTg_NKQontZx)&~+s6=z=5+orI?KeiwHkRJ-F2_!PP=A?;g+(4{*Sb>HuYnWl6ayq0 zgc*Q{r^O~D$T5>ZCbaBTLh^4YCU6ab&@=Xn(W#`^x_f#CQ(hbuqS5Q(S#?hdKVqzc!6b?-to%ma~Zt>ZVRYd#ipd)CRU|Jk$^F{sHv$rNb>--=)@IK{s`ZO6t_V#|j z1^KykZz?#i{^d zIFy`LBpc)crr=W_KK$LdJ8VcypN@eHUxKN>J_x-GDy*XnhuVR1L=5lVUcScXG$(WK zI*Hrnl(9haye#yF*zea4O^24spC3O3O0YH%5{m0LM38W$WC3Mh2HI+BpXhwno)Fb$ z@T^p0;bu@7NNViPg>mEBnbSC6W*3Rmap&Z)H$W$hic{h2mmdh>W22)pfBuw7@Nxt) zVK@-I=i9;uYF(E3T$Yqh&-Rj1CAEmFbz(@%L+SS` z$&O8|tDC++Mxz#VFakFfkL?ro62&ON6=8GQ^v#<$#K_rJnsV8;<+cFp*IEni+|mZA z+|hyy=RKNEPEK(mkMAsjMIv6?*~Acu6G(@|loD7nh1|`t zN=0Hu6NAB2n4`&ava@548}Cw3WD_Me5E1T1^EGIJd`)rlnKy{zve#6&`~+Y73qnfP zt3i;?%EIz%HR)Y@dpos&onGP&x~+|{x;7nB9>XG2S)5ASYzJE~j6ENE^=TEe-y=I7^IB-0>j)Aaq%pOfkA$cl|9^x&@TrTsp8e_Gvt=w#H&Erk9RYOqC8MZ$%f+tPUh)%f4AJQ z#bm>5z#IZh07k*h3U;We-)`LY_BNlzuYbbt3#tHkNCM>wO$T#f(QvWzlO6A~!(}EI zNH799wy?iWDTxm@kB;Q_TF2w@&bL2<)FE+eXvxce*xQ&SigNW`@P3a=D;}4APx+4r z$t>oaR)E|Ncod8tP_=~xdFK;#uZK=6(@TS7*QzR#bX6MmvJb$+^EX*0x@*u8W$;6vYkjo-rb)vH(Si4@Yu+`g^3K>+D`Q({^+D^kM1GGP92ISmV386N;;kPF9h;$lH;3QPr;XqC#k zQ}1wkv@7C!@D=Q|71wS#7#j!2U|aZoqKuF#?qhSfy*R{nJW=Z+;)aVIz+-@xU=;Bz zdy}MZlSHa{U4vQWiyF}H!MF9Cm0Libjg^Thr|VEtLt`*Qnv|Gkj~*;Fm&-vSMVY84 zOQ+q1J(cJBfQ;4K+q>AATX1!~DcW}^`--El$Fu`fx^6Z!sfEVl_X9|*bL}~-FO^Ua|qWQeGEtFHm z^ugpRC@T+zaW~~FF($KO1m4p&f7Atsjw0UZOL zJmC;{M1Xg0zh|Bv9vt+&xd6FA6n2rR-USn@KyuLpaHnVzdG|>ss295DytUU3cmZ*- z8DZvJJH~O{mgOd%vq8?t5|hTdAEI))gS`dz0rsFiLM4DOtgR0r!j>-p7a+|!#~s;6 z^I7%KbSLs64Ne6dzd21xT?Ttl3%n83UF4;wr%xP$da^|T(5@n4#8~iX64Lh>R9P4F zg+$R{t1d#m(cZmI0!kn%#-2a`k%Xwx{y8*6oj5b|f|?j_??!|@BYqQn{x41*(Rt}E z7VR^Cg&wfwP2cZ;LvYdl={k|q%L^!G&k)1rt5L{+2gg=G(MRygC69YQO=E8zg z29MQp?csnO3c3WLi5byfrI!>!BO-KUE#DVD9~R-9&rNpF0Ke>=RG{r~v@|q@L^J~| zcm6gAxOhIBdk+vR&87G~jpkluIW`OmnncG!VfMJDkE+g0m)$=d?RlW;RL2?eH}CHK zr`mlmDFjL!7ZhxB#FZ=5n4P&Am3{;@rhHcQ$ts2M-Vv0EZ^M?Rtu=^!?S%hNiL+(X z$S~q%N-P!cJ6dqI&GF`Ndyc8jO^>zmIBY;?d%L=~cLdswtyi7~YX6C9y9tbMXhk&k z*7W$3nye3~OOzfGtHXNFjcE1Ge%5%R!Rytocx;q=J?e$g#BYJ8A>VGx5dPKxb^6pq z!61PW$p-G0R6bP4kZ#mEtQs@AZOybTe*}lCUS+8EeK@v*7o$j@$d{ETfahxxKG3}h ztr_Eui7WGBLqw~H*5I6@00e0-S0^RRh;DAE3UraC2;a{%I zd)X@z567jy5y^C$tfc{`WDF}t!FiNXCy1&V)(WakYW3toUT$~@=Kat2tUkoUX4O_z z>><)xg`J!j`}v$IUB#E2*)GUz^ZFcZ-kdZnMgjs6$#fX5?V3nIb{WT$Adti3M?k6D%yYdBj1WTVnq_1 z%K+n@V!^THV_%{F8Q0~Z?rf%e>9%zFwf8G+QN+OmbYC@U-aM~Rv5m0Kb~@{@I_$53 zr^0|NzEc`uP7EAe+q4tC70i-mnm)hP*{m2}9RXx5e z^EoyRG1Nb_9DAZ)wI)y{I;tTWS_X1&ci9rV(AcXYy#M0P_; zVUpdC_qlhj?PSk2$nJ~Hr-+VY4Qv!;@BS&*Y80zRay{eu8HWFcNZa9o`~ce9Y8)Ke z>DV9rYt^;Db>}vwXr3#gSL~7BmItEs`qZkrMU!mdX%A_Ipl;R-jfc9c zH(IjKjUU;M%}U^XHeS8a5KN4#YcDihuHg1)qVju7;bNW_>TEwI!fC8uze6#_An(U4 z$8Px|k9=Pw>*G3=JLFrk_;`^&aY6gm&kDLdTCA6`^?QoVDIBy09LD_}qT>-jo&g7j zSQ#h0jO1&~0h*gQd>e{DPoG9s2tH?HaTbg4K07(c*r;s6(}`!AO7!UT^k-gOw)Jz# zV`TMuTE%l~N6Uy2&bUPUg)zUI^QE0Hz)?!)o^xIAna>`>X<+DrW=XEgGT{6YMzT&m z;QFgCCa-2l(FZp*i8i9ko4N^$Yl@u>!k$vEJ{9OLzWRwV<=_bxaT0`8?o|ca&~Mk< z0eDZNuNcMS`n8KJ*w^Y2=-Pl!n0?srMb>pIL=VF<@;pM|BE+-OUQ(1!^2=OJRYr5k z{FdbT{M~72fzvSig9#`8oon%)-$<<}1;1cvlYv1l4P zxm{Di^|YfsMkOs~>OjfczRA2!xY}vBGv0JM!r_vwLeDU>8nknsM# zb&}DJNe!GDr}|>`cH3{Kf$wGh;3@fPPFf05KPEh`+Xq2mDgYCQ87^y%tJ%CWyN zMhG9929I<66!|vG4=&-mwYfWEZ3>HK8t8Zrrk4&JK*V%~XgHPfSr$}S$9Pne%F$%{ z`slzkkL0hMGs9`33xlGCHOETzyje|-yvXeTf=ID8K@n^6=p9N!YAafm^Js2ZDDKW0 z-`Nj5Z~LL0HLwAv)Wg%(?Adv4*txz^@~fW@+1%|C2tD#+zniC@Y7gzT=I7 z@IBgUq`icTe;S=aOvAGn>U+$h$|Lk>P34e<$!UD=a9?f_4`}W`|F84j49egJ@srN? f|HaYouyfL~!<9Xog-N2vhQOYy$P_<&@%Fy~mF!|u diff --git a/doc/img/ChAnalyzerNG_plugin_settings.xcf b/doc/img/ChAnalyzerNG_plugin_settings.xcf index 6ab93483aea8264713405bbff76243c1709017f5..8cf8b1c1b287243985aa23c9ab16792df6e4a467 100644 GIT binary patch literal 122563 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

Hue8yR1ganFN7}0e259eeY~a_%UQo2x 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 K9GCtfeD%LH&C<~T diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index 24ac8204c..b68b6dd81 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -258,7 +258,7 @@ private: break; case ChannelAnalyzerNGSettings::InputAutoCorr: { - std::complex a = m_corr->run(s/(SDR_RX_SCALEF/1024.0f), 0); + std::complex a = m_corr->run(s/(SDR_RX_SCALEF/512.0f), 0); if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB m_sampleBuffer.push_back(Sample(a.imag(), a.real())); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 4be9fb1af..43e084284 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -390,6 +390,7 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de m_channelMarker.setBandwidth(m_rate); m_channelMarker.setSidebands(ChannelMarker::usb); m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("Channel Analyzer NG"); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only setTitleColor(m_channelMarker.getColor()); diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp index 0cf60f4d1..bac4e1286 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp @@ -43,6 +43,7 @@ void ChannelAnalyzerNGSettings::resetToDefaults() m_pllPskOrder = 1; m_inputType = InputSignal; m_rgbColor = QColor(128, 128, 128).rgb(); + m_title = "Channel Analyzer NG"; } QByteArray ChannelAnalyzerNGSettings::serialize() const @@ -63,6 +64,7 @@ QByteArray ChannelAnalyzerNGSettings::serialize() const s.writeBool(12, m_fll); s.writeU32(13, m_pllPskOrder); s.writeS32(14, (int) m_inputType); + s.writeString(15, m_title); return s.final(); } @@ -107,6 +109,7 @@ bool ChannelAnalyzerNGSettings::deserialize(const QByteArray& data) d.readU32(13, &m_pllPskOrder, 1); d.readS32(14, &tmp, 0); m_inputType = (InputType) tmp; + d.readString(15, &m_title, "Channel Analyzer NG"); return true; } diff --git a/plugins/channelrx/chanalyzerng/readme.md b/plugins/channelrx/chanalyzerng/readme.md index d4fc6ab17..6dd3e30e1 100644 --- a/plugins/channelrx/chanalyzerng/readme.md +++ b/plugins/channelrx/chanalyzerng/readme.md @@ -37,51 +37,76 @@ Note: the spectrum view (Channel spectrum) is not presented here. Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -

3: Toggle the rational downsampler

+

2: Locked loop

+ +Locks a PLL or FLL (depends on control 3) on the signal and mixes its NCO with the input signal. This is mostly useful for carrier recovery on PSK modulations (PLL is used). This effectively de-rotates the signal and symbol points (constellation) can be seen in XY mode with real part as X and imagiary part as Y. + +

3: Locked loop mode

+ +Use this combo to control the locked loop type: + + - 1: PLL with no phase modulation. Locks to CW carrier. + - 2: PLL for BPSK modulation (bi-phase). Locks to a BPSK transmission + - 4: PLL for QPSK modulation (quad-phase). Locks to a QPSK transmission + - 8: PLL for 8-PSK modulation (octo-phase). Locks to a 8-PSK transmission + - 16: PLL for 16-PSK modulation (16-phase). Locks to a 16-PSK transmission + - F: FLL. Actually a frequency follower. This effectively implements an AFC for FM modulations. + +

4: Toggle the rational downsampler

The input channel sample rate is given by the source device sample rate possibly downsampled by a power of two in the source device plugin. This input sample rate can be optionally downsampled to any value using a rational downsampler. This allows a precise control of the timings independently of the source plugin sample rate. Some devices are flexible on their sample rate some like the Airspy are not. -

4: Rational downsampler output rate

+

5: Rational downsampler output rate

Use the wheels to adjust the sample rate that will be used in the rest of the signal processing in the channel. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 2000 S/s and the maximum value is the source plugin output sample rate. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -

5: Downsampler by a power of two

+

6: Downsampler by a power of two

This combo can select a further downsampling by a power of two. This downsampling applies on the signal coming either directly from the source plugin when the rational downsampler is disabled or from the output of the rational downsampler if it is engaged. -

6: Processing sample rate

+

7: Processing sample rate

This is the resulting sample rate that will be used by the spectrum and scope visualizations -

7. Channel power

+

8: signal selection

+ +Use this combo to select which (complex) signal to use as the display source: + + - Sig: the main signal possibly mixed with PLL/FLL output (see 2 and 3) + - Lock: the output signal (NCO) from PLL or FLL + - ACorr: Auto-correlation of the main signal. It is a fixed 4096 point auto-correlation using FFT technique thus spanning the length of 4096 samples. The trace may show more samples in which case you will see the successive auto-correlation results. + +☞ Auto-correlation hint: because there is always a peak of magnitude at t=0 triggering on the magnitude will make sure the trace starts at t=0 + +

9. Channel power

Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. -

8. Select lowpass filter cut-off frequency

+

10. Select lowpass filter cut-off frequency

In SSB mode this filter is a complex filter that can lowpass on either side of the center frequency. It is therefore labeled as "LP". For negative frequencies (LSB) the cut-off frequency is therefore negative. In fact setting a negative frequency in SSB mode automatically turns on the LSB mode processing and the spectrum is reversed. In normal (DSB) mode this filter is a real filter that lowpass on both sides of the zero (center) frequency symmetrically. Therefore it acts as a bandpass filter centered on the zero frequency and therefore it is labeled as "BP". The value displayed in (9) is the full bandwidth of the filter. -

9. Lowpass filter cut-off frequency

+

11. Lowpass filter cut-off frequency

In SSB mode this is the complex cut-off frequency and is negative for LSB. In normal (DSB) mode this is the full bandwidth of the real lowpass filter centered around zero frequency. -

10. SSB filtering

+

12. SSB filtering

When this toggle is engaged the signal is filtered either above (USB) or below (LSB) the channel center frequency. The sideband is selected according to the sign of the lowpass filter cut-off frequency (8): if positive the USB is selected else the LSB. In LSB mode the spectrum is reversed. When SSB is off the lowpass filter is actually a bandpass filter around the channel center frequency. -

11. Select highpass filter cut-off frequency

+

13. Select highpass filter cut-off frequency

In SSB mode this controls the cut-off frequency of the complex highpass filter which is the filter closest to the zero frequency. This cut-off frequency is always at least 0.1 kHz in absolute value below the lowpass filter cut-off frequency (8). In normal (DSB) mode this filter is not active. -

12. Highpass filter cut-off frequency

+

14. Highpass filter cut-off frequency

This is the cut-off frequency of the highpass filter in kHz. It is zero or negative in LSB mode. From aaeec634fe3c0dc8003277d2b2b56338b9cb6391 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 21 May 2018 08:13:35 +0200 Subject: [PATCH 436/956] DSD demod: updated status log text font --- plugins/channelrx/demoddsd/dsdstatustextdialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.ui b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui index 9fc1c1954..df743fc08 100644 --- a/plugins/channelrx/demoddsd/dsdstatustextdialog.ui +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui @@ -108,7 +108,7 @@ - Monospace + Liberation Mono From c71f1fdc3a4df13314b8d8b004e00123c328f304 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 21 May 2018 14:43:11 +0200 Subject: [PATCH 437/956] SSB demod: experimental clipping limiter --- sdrbase/dsp/agc.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sdrbase/dsp/agc.cpp b/sdrbase/dsp/agc.cpp index 1a94b7457..c7a3ebcf7 100644 --- a/sdrbase/dsp/agc.cpp +++ b/sdrbase/dsp/agc.cpp @@ -105,12 +105,24 @@ double MagAGC::feedAndGetValue(const Complex& ci) if (m_squared) { double u0 = m_R / m_moving_average.average(); - m_u0 = (u0 * m_magsq > m_clampMax) ? m_clampMax / m_magsq : u0; + double du = (u0*m_magsq) - (m_clampMax/2.0); + if (du > 0) { + m_u0 = (m_clampMax/2.0)*(1.0 + (log10(1+du)/4.0)); // experimental clipping limiter + } else { + m_u0 = u0; + } + //m_u0 = (u0 * m_magsq > m_clampMax) ? m_clampMax / m_magsq : u0; } else { double u02 = m_R2 / m_moving_average.average(); - m_u0 = (u02 * m_magsq > m_clampMax) ? sqrt(m_clampMax / m_magsq) : sqrt(u02); + double du = (u02*m_magsq) - (m_clampMax/2.0); + if (du > 0) { + m_u0 = (m_clampMax/2.0)*(1.0 + (log10(1+du)/4.0)); // experimental clipping limiter + } else { + m_u0 = sqrt(u02); + } + //m_u0 = (u02 * m_magsq > m_clampMax) ? sqrt(m_clampMax / m_magsq) : sqrt(u02); } } else From 25e1439dcd53c693959e6f3822083025d437b190 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 21 May 2018 17:39:07 +0200 Subject: [PATCH 438/956] Updated AM demod documentation --- doc/img/AMDemod_plugin.png | Bin 17685 -> 20247 bytes doc/img/AMDemod_plugin.xcf | Bin 74429 -> 86277 bytes doc/img/ChAnalyzerNG_plugin_settings.png | Bin 19035 -> 19483 bytes doc/img/ChAnalyzerNG_plugin_settings.xcf | Bin 122563 -> 123948 bytes plugins/channelrx/chanalyzerng/chanalyzerng.h | 2 +- plugins/channelrx/chanalyzerng/readme.md | 2 ++ plugins/channelrx/demodam/readme.md | 24 +++++++++++++----- 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/doc/img/AMDemod_plugin.png b/doc/img/AMDemod_plugin.png index 8a3870050b3ac7eb9c41ab904c8c4e904ec8d1bb..0a20eddfc466cfa068ce3fddc3f005dd2ef464e7 100644 GIT binary patch literal 20247 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
3#b|(-oxOG;ef*pH3(y{jyAlO? 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ɒdw4nBb3GS-5ix;{ zSt%_$S^=foHz)m9u#M)6nC4udj|%w)9>MqJQ8ZTxkkxd7Bemhsd9zyINPxe4mL?$K zcy+C$3GGl1yK{1U)OpOPMRBR1jB+OC!>3!$Qyj}d4DSYmT`6GB@f3KWlvlHfjq>b8 zeIyo33rCjP{!;BvB{e>74J=lMLLt0ano!^tHIzXves;jVuxl3hI2+y?<8O1$yrq{n zjqK=rSFUu^Ph;aQVwOMoj>4yuq*pKr{Rf+rtNb0D-wr%AI+zZmsn$m-8@?9duFw(=SZy8NoEeJQz(Q&ggCWqyFUz8P3rfv!dN zrD6^Lo;4gV%CtKD=+jCJAq@YnZV4?4MW;YGxtnYu{O-PNM@nx15z9WUOWyae;0jf` zh9_&F7eHt}=$4QM6-6d`wh<#+36xKeU4!&5AYaqldcE78$T_f!q|`$5w|n0*BUD&Y zlCw+ZC`V)8wl&6CkU!lMmgrfKNb6l3<&tw14xk!Pdi23bLHU9H23lj9_miv9Kw`&I zi@POh;rQ;(Tn%jkqs|s2&IO}Jg%>J9W>nQ6$G8k)Te%9s>huwb#IdnW+84i+G0n-I zkB9Xe>gkh2{LDs;vfi|&YMDIcz(w_A(me9gUQ+8cyu13ojpCip)69&EynC`<#x{&M zNc|0{TtM!DGz<+F;1~jNqrSf0W^aO8!{zH&;_GE^0f?dsKKCQ43RNn|+;ffAT590q zqb=xAu|XPf=Ii%rhC=^-j``x!ZOe!=I-(dBPf2cF5t zEE0Jd=ggY9IfEm$!)vU#PK%J%2`it{p3qacb?!V`pP$@8`-1%lntvcFk7`Sd5iuZ~!w!DuR&D1*8BW&6+0e2e479fGPv} zcmkz#=0)P5krV|cKr$i)ia6ZF0|3?lZMw?)*$YIzBytsysjxyaGKhgPE(S)5!)jq= zRFr72#v4EzFoZKt&p-uo-OWuvl(GhG8X1orIaQ_yoZ%Ko{}zr4^1Gwia3DZte6rHf z&Lu}@mttUGKu`h*eZA4~7xMAO&`8r03q9vj9yu#e_U2T8enpeC0+oYVkj=#k2qQtkaUzVF)=By zhyk!uA$owi1(PBMAx;8-ChF?3#uoY5l$213TnuPLSCCN#KK;i0$d8~PVgO?Z`4F|! z7fe`j3YKs=ZHPhp2dZt#i?84@0zldzCUkewh05Y|pbBAEIp%l3O)#A)&pdqQcHDzl z3-+OFZFxMWmyXSIJefr$zjpPHSUm?8YZ-5aVDqMz&`1V@g+De6}Vh6W&#kw1U-jpa&;OG>T-1%&r}fTh}UK0aGCVlG^TChq3`J|;R^Z@-q|hv=OU zDghbyUXF6pmwnD|vFQku#KA36gxwN-5c>mzq4$wQ2S8|BJDSMU87ClQ1OFHT04}ZW?}x<1V8Z%kwqJ;bHVuVXW!(!c6$|3<3D-w!tC5yjs(8|g8yvusPtC$joFR2|`N!XY%r z0{;?E)q7Y=`D(WfKzj(hhf3jg`~jq9PT6VEI;%yCXH-1)P=HgHm>{!8+w0bFz7^as zGoU$OF(1y?QS$PpfQTokB!);lb{l^QS`szt8ykVg(+f^xLVNQ3aE=OLlz_;7EStuB z_YUDB7+eTqrd25&ATM`5m}4{i_{$~7`F%f3KH`Db@Ugb9BVoBqj*w3`O_9Ml2Vl27 zV21c97a0N*iB7#b2vXE|elGO(BDC3(bx5R*-RpP#}7G6oB!;_>GQ^J^&=_aJ-ay zy50kp0Dg2#jO#%~Pd!MKQ_7~%ExF6XIm@|;g*j2iCbf6#x#u7T2rWe+zET)l`T@w@ ztXJFP)jVX+5rou$s042hE;1~rN|w*dxH*a=SPY-4W0bp zsc1eoH8rtVFY$uROFs}DiXQo@kw}ziRx0?XmiL_#bofDwi=q6dUq79vKX1?=gNpUL z8@p^W20UHBmPht4AuESoqc$=wt{ZR^NHQLLMnzZ#3M%S0cn)B@#7X$EsQr(OUV}~p z=2JlSnSo^qGP4bCmoy|KBpx7Y9g4>h4ie6YU|$6|DJ0dLd5(9kKU&6-SabV*!o+!H zif3#MaW_{o0YKd|;iAc`@wlg&AKiK#2F%zqUwNF5@THU4H$jzy&H#oFP{ZmrD6nb&B?kF>`8#+Fu+z3KsO-4Mi)?UfzeD4D9&cGb?YjSAAnZ1vDexwn}}rQ zIaNqbi?@gX6iMwe#@finhK62Eh0M>Fq3R_fCCb3k7%{5>ralPdn}EU$=qBhL0w%1u z5J_Uzf&$DvD3G{Mb6GBUld-!j!qf}V^_~b&1Dcf3K;Z-!)6!ljgeK?c=m>J}phVek{6z<1GC*tV-EVX@ zCc$<;aMZzk-SCvwc{?;}Ip|B8$Iga#lCw${K=SqL_?Dckk-7K`IuR)w8+NJ;2-E?o=uR|ae}x)rsF8E&Ch;oRT?0GWLkrMD?qnHHHD)9QQJwsS*S9W zP2=QxQ{BsD!a`Z8&S*psxRf}KDZn3$hH?vTgn;A8ga2N1vk;17~U;pNp-W-o+ddbL0C0&rij1Ct^L zj7+VwJvB8oWPE_$vcNRhIl{*Na#zno5UmfzoR3V1CYD7@-FRi)9xYcvT*PgbE;7S@ zM3+6CvR<21a1bexW$Lfu!7&Wj4;=fhzpsAUA|>k%(R7$Nk+0&yR8fVyc4Ry%=1dO3 zSMEG$0XnKlGC?FHZ6r(d@=DAdMvWu$>{N`|_PxZ^G&W4;n(6om)B-yiqa8loq z;6Uk;P~na$)9u03#rwN7$|5PByS~^CMt54T)cM3@Zasz1Vh~EY#-j4#SuuIP!0A}b z+1$;J%|$4VYq=;c&ek#`n0Ey1ffVAitf%>RclTzv0t)mK5&_YxWt>O{lq*j-yzOt) z{Z=wn>PQ9uWPH1FK0x+2GgChaLekgTIV!b+Mfj4Wu53OeRT;Z2C#%q3ndZA|lM{+cDgsetP5y*v#p-)!2yF7?E1_}5Q7-(yaL;`AQ1%P zkcbG;0!5I^G6ybD6n`5zyzf^=R+Eu$z%$X-fm#iNxmxaK6QSZ5W_G=X2&| zaSN^|eN4D#3gO4*+V{>{OCr}ZVPS9_6KLa&m;}i!D*umi9Kj2E%TEv0s$Hzwi zc8h>QYaw~RSw~JzzPhsV(ao(9BAF<`<|_0)+@5pUuEBt6zTHFQZLo{>0t)JNDCGA7 zZ1|z_w@*&8Ew6)X=?6)tB!Epo1IPv@D(~gQ7xMlGk#K7>H}-+2!|{c?=>i;+1O1kL zv*qC%aHbQmR|OLb>-TC`;0qfkr(XatLplf4M3G8_ijHTTa)~-fOrgZnsda+u{Sn&= zz;QI^62>G@Nfl}0cr$&8H-xfBS5{WOs-!QP1CbO+-Vh-%CpWjkYLOelK>*u2d;DLkrul-8eM7?2)bbRg(NnBj6n8K|h<3KEa$-u~{ z5>PYP*Z3xwV<7Emv)P9Y%BMU{9Gz@c$dC zDwxCjMg{_wosx2}oty%;QEUMW+yMJUL|Awfq=a5ZESKwkD%A(sC`l)29)UpZm|{Yc zB7R^4n7e#mU6z=HDL7q=QpN%*D+Id%VLB=;Z3x`VY_%yS6xcG|1-uAQQUOZ`5pD>t z=e=3+6UG?h@hlP#t|w!UCmf((u@K=+^n>4Sa-1sJUz zXb_sm68d_2CLs9{HO5dXS|~?#P`EFG26NxBaY$z%bfc26OX8_YghW&KPLe#{0Pq52)+%c+?AuV9S(T-btZ;n3p!wAy%!8Sq= ztAOkkob3b+UK;4Wpiysw_)0E_L0tfy8T8DAkpDs%f@p;>pmnW*?RsF}l@C~+BOvMs z@|6(#0SY)Vyr;i0!NY?G5Dm1DBz8+&Q0|F=X98@f4IZijF^gNMIesWy0$$$n#zj(I zESV=W4yQsU&rq;05}(Ts#@N`{37}lb6$p|X0YV5^41YiqNCfzrQWob)OAZJ#GI=0F zw6e9OymRq}gM&li%a_qzhp`e`c%eA3#q@lysB8=r8?VPp6=0q9Lb8Vd$U}j^JBas; zP4(A$-aA3koI*lEs&!Tb06!Z5fDV@Lg?8dNcIQ;rU{Pce>?nHqmD0@xdQ;_>FuO3s zGiFAe)&V5O=1A{slYX395a%z81XC_6E6W57IRG-RN-c7x_qp7!=>FmD5Zw)6y0-uo z`4F1h*w~boL&}*!Z!~isFEYRT?<33io}sxPizXo)2!LXXW7M-xdE=Q11pvV5v4UR| zU?u__WUOc6tiu)F3xy#V!Ti;n8@S*|bjWYG=tBeQXkP=AuxLXtM08I3 zZ#{RCZwv^XO}?$!i!AVN8{=O-{600-ta>e(i3KBA{^gESn_`%51c$F!L;6argC?tr z)+GGkAovz(cd@jmzG;=|U$3_B71Z*6UzG}ycX9LG;ysjC6HXcHI%Mx1Y7m^-!45s>#Yg6&I@(ofZ9lRp5e!Fc;NsMlbv zGXTlswEK;JKtOK{acppqk)h!z@B$~8%m8>t;++~CM9Cig`}BA}YQjqTOQ@mhYsX8s zi@#piiShCA`_D&izqw{lSLrT(24=d+$5H912q)xPuhyK2#1@3--4UEZzi-NvKnJNly%&*H_^a}R4 z{L!1hJUzD*%F9MWnWn@0 zdp;BbZbjqaG!!~>3yTSm^ifh&oH!%Txrb&KyT71&GH9rN$Qv7L=;-%q`gqs#dN&)3 zx`Q+MbUp}mt^6g%B{c952T2KAR zt8;dn)8;|y$tj|qN{0QcR9LY8bhW;|j*X3-3u@gf5#oB6Q@yVZ9be_FtV#j7q%E5+ zijKzE+}xxFMY{_fNoaF4LuJ^&Sw7}l`tWv4uYrQS{W)I1`o5f{g@wU;;_9YdR3s#K z$`UO8bXn~(4ihiX$w5Flb3E^H&&C-lZa*K?}HEd;EW?%owIp6!o_s{pe=XZPG=lMPF zODx{6P1V%YOc;5xjX)sOMN!(GKktCza_oh*R4Xht0%8YowmL|^IP87?<5DguQkVXo zXkkT;`bFrj;(^1BpL^>wGAWPy(GHCj+1w4RfG094st)K+SO7Q%9LopU5kel?276M_>g3kH z)>ai6U>@l@Mv@L5inUyedTDXwJK6oG6lHUzOe^}P=LvC_NYp4{+jug75o4B=+{?^V z0ZPFOs#XUw0)l!Hmp@eFN=Ks^x1Yv!m&yYN%qC)lQ5>~P8UMOPlnc#uA!Cq%dfynY zuD#zE?wm#LAyCWv64-TSL4gTdQU;q)_3{`X@X;jo3kU42tpl~Gr9>rT`O~Fl(oGf% z17iaPCsB_wRbKTEtEiK4RXS(b(w{Ir|2Y+VP4ffc? zb#-+WVafiY>fD?h;M0Y6jY)3q?n3q+G#Y-P&=$%%J2`y*VHgr0h@K3!7_-o34e2-0 zgL)d4o-?P`CSrw4Y=@Ys-UirpOcskbKOgUqJm&-(7&I(?1On4|0j;47C-<8Q^qLZ-v-qZ2*CyM}ggGQzl@?SGs+YtD~bZb-k zZ$E$}15!Ir8jS{2VR(?e;-8SILbF#DZ+T>Co zm1`ke#rvnE)I*5pfudZEGF`^yo>3d2?W4J|BqS>Jl;g9gXo{|X@6UZ1*trE$4NCd%@7YK|7_RmBR@kxC%NS zJK|t}|6QmKiU4LgV~p7&5kF+B@t_TZgd`h(Sqc(l^GlC9hHJ)>LrL-zM*39VFC)4qY zG|zX&v)jo-XS_8R!yuK0Noq)6ZGftzL9(r&_uKpyPDh7t63U4#cCbp6jyd8D+VkCL zbnofYeK+11_?49(6FG%l;Td11QU#VsX6@`MseG}wQ0gP)ZW~(Y@m4NzYZu~{gCfMO z4n2mnjy8Sek;c&3^L_SPkhI~OQQ?XFNtsgez~Efd${bng6KkJN>dY#b;MA=5C)3+5FdNr9a{ZN4&yEth*{!zN?YeXS1 zxtD<=H;ZLUMgfY2oqq-(8MnaAjn4B?8^H47p|r52+|!Q-Q7xxNJ&Xcg9u2slz_B!hje%6ciqo( zzA@f$_TGP-GmZ>YZthrXt~sykS91lul9RwdB}7F+Lc)-e6jek*y3r5cSa(t2k=xG? zgWw+&0~rZXq-(_2*ShRTc;udqq`Ey4652z=_YI`@L;`s5j)Rn}*qyms#CLdT4y1~R zkdVlcq(p_4T*f!1oLz7hr(1Wox1NqUko&X0%216qc$M3aj3as@I_o*oUz(eo;aQ|Q z&qzO}(TK&{(MXtKiRAdu+@p$mj*ex9ErKoC`?h59(omlwBRpJIfB#%-M5n!{i>+jT zqigc~r81JZAC}17UCAg#WRbfRez$YsCkkO&cu+DNmBJ4TI|K1>I5{r-^F-1getS<5 z9v1OYf`|XtZ~x!C=-;>eKm4Ts=|%r}@c-rT{;ys%r!cD4`RU~3o#fDDhyJ)t1i=gvTt zz46)cc8dH5r>8VDcSBm;3J)vR$Q_-n6&CbMif0GpW%K)vr+v+azSb9k_~^fEY^~@!uw0F z^6t5_X2?kXr0v!YRSSP|7&P4U`)Ia(iK>cyJ+q5mSHybU`etm+{( zIm?4-4>sre^IlQSD(tK6>)Bz39EDq1vTTlAgsG}buNzugMGn`P-5h#$Z=*CZ)jxOP zuH9*>D@JN>&#G?=ZA|fQQq^tDl*zJkKWdO5Yg`-3>OJY8`%_V98V^@pWjJ56tw6hG zr9Ttx^6an^H*HwX=io+A&{q}T1kJ`~3}O>rt8XgJHQP+uGXY%yht3;~kkdd)eCr4mOPAzkygJ82C&b$4~ue zG}lK8xdKhaP!)dk6S3l0tNNQBZ{=$u2j&)T*Ig$ik~4H4)azir&Th(*4Ub2a&dk+S ziFxL*EjQ12C(zxf5vRe)$@RG24*5PAXIa3u^L#+AGQA&`k(BL>6`$A@ezJ79MopHi z?2T%xq*a2|%TKL7jMALyb%aivK9}dRZBm8?4ZXdrHM)7KTxD;CPRO0>*GCp>9*RUI zojWhn;-FSLtQuJ5N{6d3n{jj{TaMR#Qxxt^&ca(x7WiRLO|PS6bGqD_+E2BTxedoiN4wH*0>RGmDF8|ju#)M>-j2VVi^fS#Peoa%!h>Izqpi2HcwA2 z@>+b!RpvF1S>?`1r#v}ND4_KGlKO&zn;_X0MN3qhY%GUcmDvF$6 z3~H!fvh<%@pU2{xPYsd#VP!V9uPP@d(jIXR)A}Bq*s8|RM<8p#sLUV5jk929EKa43 zKL~$}_8m(kicbYEO@%1qYCSp9Um2c~8-A4v6F>NEcYI&1>%Itv`^G3s=!|#aPi%JA zy*F^$tsT*HMj_bHT>AIcOL_&<#VNLvoUbF%2stf&j}{r`suq&E)g^bvGVM2G3fXLo z7Ts+7J3rqXfI(#9IrBos{b)lfmQnj;_UFWd&Cw#?g98WkGenmv^+@2heGJ#ECHL#= zj(70*Q@O;u@AC5*_LqB&H^<9feqp}hFCrZIRQAvMA>O$bCfb{S`r2YsOECUD42(?6 ziE^4~SD}?wJ!ULI!j2ZcuvfcBM_G1@9pANUVm^IBN4ifYn3(=@q(J)-uLDhTa|g4r_x! zetx&thVlYKLNH3+cC!ZNmz1!jcwg~cU0qGqx<-EeO3v%B(lFcTYinbpx3|!Ka(m+u`gSl!VX2qlpOYPZ+x4X}nCa(-smm~>sxY~1!`4ramX5isCdnBYL!Z6Q`r){) z($w56B~|5&x?|rf&<11sNXUynA|m2odnN^Oj1(^dc6QsyO7xpi+z!`_NWB_;kgJ?F z1y`z;lMR1;c#z2J_)#TagT<%=$L*L?yY}L5Aen)o;fpX*fgGh=>>1Bv62cIacRF=$ zsVaG^ed%|2lR~Q<*V6`8-{j(nd{BII*h?uZC--)~HRO%4@$y=1Qc`zo2$9s!bM{TX zsQ^qeIRk^oWS$2PgoTAY&NnNTG_j6W2Lcb*M;<ujWx^}_m{qodZA7UXwwNxVgG+OgRQF>GyZFSfm}J9jA?y1PRNIn4Cf34=a- zc%C=Zp$J3P|G4aXmHl$O{FCYw=}d#I$*QXAww9LNZmt=m*}s1q7uqA^SdCEACBj(1UDOpMsr*s6M!yu~&$?*+tn5OuQN)x{~y zx*x{9r0T`6qN8Z&IzAPsmppB=lFwJ^jOQ@lLvN0tlu%3;$Jp4|*xBEoKOE7e=I1AV z7ed6vYVwC1*4YP<)Q0x!JMDS2U7xa^l$o>IWMJu!i~{ck3^8#d&xwr>3zP z_jK8Rfz?-ATdVd%@5a{FRz_A9Ha2!sg>&*B&xPL)mU>fqms7k$Q&PxQS67Jz+@k8- zkJI{0)P8#K)L-nOadL8gB4F`>OMy*7BKztU7Cfu=Y%NdhtP2f~es;fC$oTwdbE(8h z*X1uVl82|~>BWApt-U>^fIt!npOejO!wvOsFf=&oi~Evj)nc=-2$=~(mP!oBxGv9+ zm*b3+S+>n!g0{&@UcYiBU7g>!l6g&fXDClqB45S+Zxix2)xw?x>!0muV!>}@aS)eM zfgm(A^kU}vl1%xV5(NW82-J!q_hYMEf`1UR|8B3)_0HLPp^O*;S(~m(NlJ!Wt04Zr zn!ffY)z_m0dbJfpBss$f?TgMezBfF$a}=Rs)ixKkc}jjXo){To8e6{XvttD6ul!xU zJl6&2P5=CvGL&Nx$zXQ*HcD-7yw%4u#aPblg$dFn)wzlf0Jh`x6iMR8SR6-MtKTg# zLA>Ur(LK*`I<5EtJa_z7i3|jd=`VBTq<+!@e|5OXZ5TtRg`voERI|o zNhG*_hsv|v`^K8;tLZb{MXVJ%VU52DJDi-O=5T24k0T#?d)c_oUG7$5Ba( z%x1;Td&3_yKApzrOMeQ`@0;f+eS2&@DZ7Xsrlr%N@(EsjdzIxvBv$Q^U;IU~up529 z|1;ymH{3?y-^yODRE<{6MlJn{t&|McXu2|F_AFX#XwQ zGDrQhR>u7-sWL*{=7WlYYU9vwbe^dy%XG!UH)XR=;*=WAb;HXH-3+xPwS8-3(~mt9 zF|VV8$wN*ychyIqNM?S&o%`dhxZ#yddPsl314oHj)9vxTayE zpnnh1IF}swU$4cezf0tlp!1ig<5(n1iB^F&I!tLxp;JbEorz9DD)?!49>u*wU8>A# zA&u;Bqi?61Sgwc#B~)jLz2#_E6drBW?v3;5t&2RrMAA9-OxhW=@gVWARpROI$ipQ) zB2E118UMcQ4z>$bc-qo?jP&FFn(&dx0)?i zly-h>;yG^pNW5LfIBlX`E9Lf$q0Gr6U?TGtZh@TF)u}P!`#uS;()VE#FNXfVhXDgs zcE$krCAyC|Otq?53I$JTH47~=y}Pe0ssMJFg${AYIuyKOm~1l9z(jEh4DA(8qJEwo zxRPK)gZ@+VPBv4NFN%to#-E?pSMEof6M-7}J$-f7Gd~jrJ>#Jn;L(bb9UkhP{>)F& zb2#SR5PZ~jz+wF7&HCwElh@zec?C)>)MbxbP)G-N9Vrej7}&?$aF9J1&A-g_)1QCU z^}dWw7I0r$87?xE;Ix`l`|CT%bM!=h^U5b%^&sOk4d1lX38|-uS~&IbnSWnqv9a0V z+R(-2@r>({_iASB-Fx@+SN^1)hNi<2UF>x-T>v(*n5vG}Zw|mcmpId&ZwsS-^29GZ z9P8?AooTpGSH3Nj1a0Oq$GgHhCSP;Rx1v)Ix=dJn?;nxeB_M7{eG$mKf{`fCD{oZO z@#d|u9ozm>N9}bxflc1tYJuR6oDP>#)=cS!uZl%jJ@t&j}ZrPP9UNB5A|4^#^U;q=ouoBURy{cc|FV&4m6x|HA9g{ zChu&?{*^sbs4K+E#zf9l^@wzhFGH_c{;BZi8LGT|Zy>wWcHNrNx8@%j%`0D5o=Y5u(w~~%1I)}4 z=;`s6pJkP$GIy#n!q&Gsdd*I7mA_2OEL9}hP^;wu_I<;*!gYdr$NZJ0@9})~rWk)H zGGbw{Tv?fT46wZp38ay_eeNMU2x}n%Pt}2cPp6d;QV&Oz$`4JX(uN z+7dRTAM({YX#=|l2N`hn>2(s1<5+IoxM49-9s)Yd&9)MgzQ>`&JWT+&xNYZdyB}|5 z+RnH7Ul9YYHw1jSF;)_YDRjZyJ5>tsEJXYTzy^){7_*^oxQXc~>8F{@w3RrHL5$kf zHYYp!fY2d`O`h8GQQWZIFK%Z>Eq)wT47DrD|k*%N=k}!aXxdswDY(5Vy7ANU~OnUmEpAv zvZVQv=h+(F+nz)OLjr`B1xv^EzPX{H;dDL`F7Dw@#LUQNB^sR3iHT*` z3dTC&UZ~8IgI1G0)gc<0;&}sUiaD>+N+r$Nd-3W8*mE)VTW5G~7pHH;)}h+I2mtIp zKQ|{LDR~zN8l^<&4lL3VU=9+zoCZN9PTC}!NWTXO3C8mA3hQUvDvhY6dRf1|dKc`= zAhkN?8p9wzpiJ|-B!#037DIQZ1exbA;3VDKmX?-PKvoJuc1leLxZRKFL1QQkr-)ap zLF0B2)mq{dcNxWgYFP`;wUv6!x{ zdevMHivj^F!?6aYQh>m;yt{GBj)Qb-%ae@&y(@t$Y++%c$)Cpmu5kLZm&&NPfb5Y` z(ZyeEX;XJ~27VA{}pK6B5S$8RKUG0hQyHlFRjlD5Dd}HRg#5TE=(&K9seC8@+S1nHIdQ=i{ z%CK4Ns$T2DQscbMS#J#!)E5P#gQbgayVkd&g6qZ&+Sb45TgvL8o7LoRS4#iQiHiLeeBG}~w7I|+fD|vo;9Yz^+R?6YcAlZd`B=MMYjoRR z;eBvE+Ou(-M2ke|?V~gn%mf$&2LbN$EKyy`{mOao!U9)O1kYH1{%T$G9qj1|?MdQe z^SU@8uKAVqGEM|UD4<<5PT_K!Ghde1s}g2#1_^CTUl+U07^!U6 zpg&(W{${`~V)oW4=yq^swmCZ?8g53|Jfcq2W@Kd8>@Rf_Z*Px$Q>FuXuQ#0H1!~=c z@~68PZnmeHy$yO9OIy#lr#}{tR#sSyH37=++RlUK{7|01vB@9p#y~zvv@{jenm0{; z_xO(|?Mp4jJ}2=x-<;jpcsYlMX!e0vks)2QPQ}Yum&~f;GNZ=RhxiH+**>vlz(2 zc=YHI@VYPfH`DumH~QWIIvptHa}c<>e7e{ZfIX`rB3__RI{8PZ%9Qt~Vs~@$PTp|= z-S?#}Nv+QT^!bc4#Vz;ng5@j1h0nMV&4%F{NDOS2e(RAu#|0 ztYy3~Z7KUriPmiRyHqe9!wHbl8`(Ei$h%_mw4|$lS*vA>w#4wggSiAGe2L70 zv7AxKzM%Cg?>tu{pY~1x+1CJb3`S~Q@$8YQnvIsG{FaGaERSCfdwq+RJ5F3sk;n4a z7Z+O^eXCT9VcHO*d8LX7h0T7bj?l=lJB|&jOn#|Y9aA2{KV~~v8r(Y881S|%@5j1} zMW`vi+L2ecS=Ha=t>-JJqE?f`QR5EVmoDAeDY%F##qbRP7)+P0SY0pEExbpM+AD46 zzYB^Q#@88E>kR76w%=mgy~tjWFJ>h`=g|m(Ny4$I@pFIy7-DIu`?+jMNlE+f-{c%a zrJgp<&c+SzZqRF#-mTN=ZFqkRnq8Nz);Tao`oUv-BBIVukJ!F|j6@g`kdu?672*Q~ ze4x(Vd4C!7gSS)Fj(;pn-cRJ#bj}Y&d0u=t)}r&1th!=X&aH|&=5kMs(Wmr@^();k{(GRn(WwL< zC}>Ed-12JK%>`vCBu`Y@Ct2nD8|ddZ8)JRDONPzE%=MYtN5YuN{5KIo#nHyttpCguw&@tATY+$<{1a!{?(%K>MCcRvVXfZCky6&*K1{$rDp?0 zaXpd8;rX5A-%c62i$$U?Q5dfmS$OE;klXNP4VHbf)2WRg_F&`T2c!g(O)y%d&qtE* zxLlIkuG+fh7l(acN6S*x!TzjwQHzR`v6+yt)oR_o8;vYeCb_Lqw){BnNXsLm7%8QY zzj2vP9wui6NmXk(WSu`IIgzV<$( zjmjU*4zi^bVYYb9!Q1gxcTN=1cJffwH9mX{_*(xm_UEJ|{ka)k8M&qE$KC+w|@=E6_kJP8kv2+upu@6trx3UOERPU5WVC{YCQ_AGo$Li-OXl_Z(d zTxe8u@#6~M$D*1p9*?cdQ8oLE`*YF?K3mh^MJMT4UyfiP7J7x|4F- z^a1p;eUi@~H4t=f?4yFp!jm>(8P&`MX>J{s9r+8Pj7R->{v8BQUEein!rA%+g6O1^Gx}k1s(-_AbyU@X? zK}8K}(JPAAwqfm@c?r^N>Vi)sWsDmoBQnLKkkkwQv1C8Ag@Xv;@q%`#HlWbdpEiDet~E$iBuAQI8U08qcf5HOLC#RG`^w$m;?^4}T#*lZ)k!@cioTI6cc}j341@N> z*w1*yx8>I_X6PtCum7H-*3W)=Bp|*fye0x)X|E1{I8A97Xsu~%W{RPHyezF0 zQB&}VbZK+gtqBXmM54_4w1C!TigXe{-^k<;)bbRUknm%lNrpnYIIh>?T};eaxA1Dj z7mboY?a{co--#0!nNvc{E^=`FSlaD1Y16YX?9uw1QURW0p8TqirY106xl2)b7`_+N$5}UswPTSMn;DWfU z=U0HZS*2`^4(O~M2wi71oobHG<+P^+ord-#hwWU&kNxBSKMNo#^5TWPL!ODF?fH?V zcnG1`%a`bYec$|T@-HHf>@tlIxok=vo<|_y zCVT8j9c@hoCnb?glv}17wuP!_;}_N)5tP+3z`;!^$fV!N31x+}PYi7)vh# z&>I^Y-_LlR=Qj=h1HauW4-JC zQ}CTGKu)Nvto-v;_?F6dHDN6+VwezVF9Pp_yS~4cSE$jA8GNxe2MpI?wcj5muYs|# zYU=vjl@*9G5}As-Q_eFtlq#!pl=JYePkX%yxU3RSPk8i(I>i(u1)?e%%Qm zQ%P`^X(Q|R?%n&lU4Pxf@(Jn~1;uf4Iu0>$;(C;~`4tC`!LJYe=l)P-V2D({L<<*= zu)%rd2H)vQ;;RrDX*t=Q-``Y?h(*f{+#7x@aDnEbfq9eo@A?teIp*kib6#YyfEi3$ z;QK%?m#q5w`gj6@LPA<*X2`s~z11nLhh6dy!G0SrPnMDBJ&)z= zi)D)>lETCF1l8vX?##?E2?{39&g$uS?6%mQ`nNN*WibBx)e*brTHNP2Ky`<^3fEl; z3_vhs`g31XLwO1T6UK79R3GRNPr&P3B_tO&H-xpEmX@|>#m|x{9W*hXDqdR(D!hpg_3yp3u$$-%k^AJ#=@h= z)W!Gy&Gvvnm~*x%i#cDuZ~~^whmQ|4HeWJ%)Kcu}V?0Hmb8PEm;p9 zK73JkcK9aufyjq6D737f&(2%&fxxw=LczmnX_wkZM_Sd3 z#nC9Fr{cKVw5Hs)ziAo_OiWVs^ZGbkvwX2?EWbE-FpQr;kP5{$M~N0{H~qkzqhlug zNX+N-a#)8#v?|7i*4HmGF2?*+aQ13ef#8b+&xbi}m?6AT) zL2!ef`~_Mf2n4dgZK2o$?OWjKfmt^>HPznL<^8v-I^!{&G#)aAXuHD5V5*n2u1$Dy z*AoNBw3aM&vg41q8NI{)?af~;*=%h6(wFsyrSI~q?c#B#vn*%UfqiTMR1Kl`oSe9z zpc6DqSl4%hSh)gy3DyrW81xa@vA9Mr)^yy}2mG<9B=`T2duoBP;hBkXVa+)@HoZQ4 zrarLV7#T!Mh>wqt=nJ5e+`E4t1>w*4rAx5c&WS<=d~;u+OhYzLCBM>XlMtU#>o@N2 zCRPVt5)|Lm29=-xc6K5n(^WJDEo2fQ4uVe>n@&Fs?&QW2eLw5RN#ncn3tMXTU;k5y z=W(dhVi7V;_737DPzORu1qL z4QPnfYO>O!jmu(`8LlBXCU1eL!kKU_St{z6nU91*Q5yi&iFgW9A)Rm(W8i)S4T|X) zPnCaJkS#kZ_;nTcRCs)|g|D&fO6Nn2ADqmL#mvtnm`-~`s=B*{-j{R|;fN^9Beu5- zCa(MzTHrN*BIfxFqW(v&8IMS4-RLN{FbLUa;qZ{yEytsv?Jumws7fy5W;pZk@UR&C zx(hf5>J1eOO9WWc5y{Ct=iBwx$$@YxOSDYFW>w@vChTy5kG|GWyPC2SD$z#Y&ZS`u z5pM=5KS!&I6%ih@|8jMC{*Z*kQb%zApHSV=W+z~2Nhv9Ph#k~IB#B;q&t*DFO#hb7 zl^oJ9$#9V=>X+WvyvY014YQ;t1fLXT)A|(i8 zf3jqWQ0^1|236}GI{Fi4<{xT~kDV-vQ(=BqXHx`gM0rKH9+JOI1D&>+)BfDw`D8ijJl6v5=#oQ3{_XX&o8C z*VNR^eIAirS;>gyq z5x)MSV(TyN`FGASM;T+u`*xCMyd3u$^S6_MI9^9uC`HJ#P?$iIXBsI8gY}m%bG$wh zHc??MfI)?thj^SJ1Je=#7 z54C+(dG$;qK6>EiZAkECahMO&19c&THATRzhlGaDxBxk!u6+wT2?>e*f$b??4u)== z=OD|SqO8Igxa_vzBXU0N?d?ljIyyQ_ba%IAo z0_ok6bNs~GR$Kyt4?yjhYIklz`I?6_odePVbl1?>SOUaclkw7L!24dL_hG?e-YPd8 z$P)41f>A9oXh8*+ad&rDI9CbMHQhiLOeV@1AYF&Zk3knjgjnq7TQPc)1vE?N(~hjZ zI>a)JH`A0gL_e;O#6|Tdro=sU8?3{H%+}I%{mC2}kTNlF;2=QP&0!%fjv(Gk$IkMK z9oyK9(Aw&~E>95eX^Hj_8=HXF!6eiQ930QkV=F7Gu6T}M2+rB<&WQyN$GShfGZ<|t z88-CUPD+#UXEQ5if4I%-a9X?TmCF6RX}bYd!jMD(_ZSdacflH7RwIBG0@RYe{{$}n zo%_V(aJiwH6&tqQho;A4zl08K;qmnJ^b(s%b5j%IDggj&Cm+Rs_d!3tSxd$IxVjrAQVVcvHIBt<1jR#FDb7k zNYd(cOJOASEr;4pL~cD5oJW~aCmy2xK`5#hhE@9@AV@JtI*ShOc zK9Rc-0!?xAqpLvdikikT z44(5$qXy#{b+33BaG>(<0lx|;hRY@M$HA#bCMC%_IIv$IdtVz1%H7L)7z%|%Tutpu zm8O~+0Z=|p-G>^Z(83eV2j@k>!zzzTcJRT=MT%BcsL!ZE- z_QW&Mox1cD`mghbpjytZt!d0|T}wyPLfsAk8+xjGE&|cE0k6c(knuWvfTDzgN%{%k z9ht|@`_meXPtJU!H&WwoD9tBV1C5hQ=`IUj6CC zf=+=@FJNJv!uX&aR)Hv>3@dvKRFSF)fC)igPmbO5wNyh4l{ML=c&+@+pGa_UDmi{WN>Exr=m2LPLV2CX zJ>rnx)2ek*D4@U%gL5>jDyMV?8?KWDQWc9GNebB#5>%+L(ndsc zpt+iWsS1M3Tj;DHOaYBKhwVM?dxR zpTy|jvn#i%LRF0L4i{X3R{N{96$~yB8yn{G^74bt34E|_;LV^3Me-3vi+u4}?Y}`G zx;n;pAzgK$m5GOt@JU-O+jo_Jk$3m>aFQj+$jTzb3#cMnsq^$7saN|&ABy1uZ5co< zXvAKlKlE=SBNusJdkF&EL+x{+IVUfxv0o0tND-dH{bQVPRZzeGn{;AsD(*2Pi6INJ zXvP2%O%kknhiZu$%V?l8>3w?E*7A0C#izqwu63UyBa133B+~m3d!R^D-o2uY6L9A= z?nz7q$qZ5Ztbd-O;N!>g2X0SRu>lM<0xj$S02U(O>^$Q+vbLVP;Y~|?h0I%zS=jq- z##mU9Ug^j`IVJAw`p~>c+HmtzS%iueeM&PfoQWqx1MY7NXR{nvQ)|6HOlj<~{>b5$uOb6W6VyZIJEjO> z)$5WMI3=tiNFz6sd0#Yw0%tnFI|0YQob$0Gv59Vaf=-AZY)J>o> z>XkMW5aSm~@B0IXUv52f|2ZHq$fv(;|4g;wzzdD19hkrC>0&H|8!+89Ixm}VfDD3A zo%W)VlC%p1!=Mj9jpV7$%*6c+ba0gI0yTL-g0jV)^K|#T`qJD5r)?q7Xe*m_AZ5di!>6VIis{WPal%NGQq8#(?q(rlDc;liut9JZSb~b-Oq<++jJu zmBmO3esfdS#DoTrBoZ9K?CL7$mLWVZJv}_SE*VHNV5dVg zUM|*Se82&NYkQ8@>p3|zP@Y3I@}JUmI>_(vI<6sf!9_j~=o4cV)+w%llGGsi4%Syx ziRSdwcCyjBQp@AWQwRtm2)ew$Ar9;~NUAyq^*ty!*bvs!1q8nb3w2Xq`mn-&2<&@` zs;a70F-muL)exkHg@xtz?b|;bRuy4akWWnwA7DhJuOQ;r9u8~LYgJ-FF-K^-fn;8i zFpU8Epp)`*_eOsHE<3huSnJk*;5^zpJAh;{m{s2#v%I1E5;hqi@BlS6HKf;4C}#z) zP7LGOO+~=?fJJTyYDc1g=8nBE9D33r#M@O;jfn6XE zhUVsU`n1xM2pBT5HGTRlE-sE|y25fI2I7g~p!wJVYe&ReMP|&kE(akoP8(xU9~vBt zh6rgM%ScK6{T)HsmB_Q-lN6qo&ncPj#ZX>WW(4{RF^`=eB+>e7oUI}26rfWW%kff; zUXp_l1GKv*)YSMcHNQat1*;1rJpXEm608_z$SZ49SjoZ`lhw(plBya=n1N-g*Ow+% z3&(({9UwvBnrnET9sKpj*U_78cn=N$E0FxW!Wl)=2tt{iCmLeGKIagAeqCi}1ntb+ z>t-V~fbD2i8UjHNZtL1z6o9x!oiQw?{a{O5rz}52+xVkr`1=W`vIcgOXyHh@VlM2z z@JDT{Nd*z_vHVSJ9Z*?>0bupJO`O1SZQHgM|~W)uv)prBOte+b1_>_^9S4~k$&?Au^p+3v_B~( zCT3lK8tK2ayErBhak^^>F|i`>YVRqUpGF`5H(R9!2H4ts9Dx1bY+HOSb#EL!M@wSZ z&2~Av8n04u0WVi16+CLu`z=#1b{AuH`|~xqkbuDSz`hzg;*M&MQ`TPoeRL9D2Xa={ z&s|+zTB)lL&2w|x;`4KNalr!QGJo-TWo6QtZ5xDl5EQw2dG}ynWHET_5CMH6tkF*> zh^+g`$Emwv=w4cy{uthuV>*IQF-a=_vB^sN5kiu=0v!N3X5`t1DVI5Dm45|G1@LDX|M8zS_s>KvV%Ca_CmAH*}Flz)+GX%^x|EFw)+qlH+s2zpu3wv zf(!nDld+d52a<m& zku7qSq*Od?m)oM{B;4HGB76sjhH#09h;pAR7z~dM`SOn)7T9qhedCe7M&3|-$uwq?t@2zofd}&nCkD3D)I^zA`l$V8OfL>RN0824 zn+{2K_G4-_GY2FZN*^3EdhhPCOjWQf)u`p@ua!Uf6H7I5`LWi05hB{BI#-WHlig`c zHQ?Al0p)~+M1$ixIriJ+2}n=D_)O4Z7KFFcMDDFTkbT}uFp$tWd+s$IJ1QA`5aHE5 zGxnRmvaf2t!pKk^N$k}4Kd7kDB46S^R8;B%s=inUzhqdle|fiFi^D*{RL=UV62h(& zy*1q_&7V(pPyC*}gZ2^{A~vhCgY0Z^MtD_`%II0UXP)G>`U3>D2ul( zVVmH9whjF?A9ghFvO6tl7)B94=D4iix9RK1GGF|B6ON#lwG`Df0w$;{v(W=T3E_OR ztOS<9!W&Y#JM-3J7n%0s_lLPVKSS$DTP2zFDfaYMmsk z|7@c}+{YpuFE_Ky+NxTp;9txURi;>D>KWAi9PJbvZh4{R`Y&IV4*lnb5C7g0^9>uo;%NSo4 diff --git a/doc/img/AMDemod_plugin.xcf b/doc/img/AMDemod_plugin.xcf index fa9fd9baa486d2d2d828fa7627621ee70c0afdf2..1c398b3d90b9e8851fb220181a787c3456f9e62c 100644 GIT binary patch delta 17674 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¥hS^?twg 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 delta 7872 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 diff --git a/doc/img/ChAnalyzerNG_plugin_settings.png b/doc/img/ChAnalyzerNG_plugin_settings.png index b8c36c34785fe7eea2ed169dcc4b5f58075dd892..4395dd633ed4d5b88e02861796ca797cbf45a81b 100644 GIT binary patch literal 19483 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 Xt)J&mBzai89Au`atDnm{r-UW|Cti=s literal 19035 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 U3*%ujybdmSYtxnQwM6ZI01O$(JOBUy diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h index b68b6dd81..e990095ed 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h @@ -258,7 +258,7 @@ private: break; case ChannelAnalyzerNGSettings::InputAutoCorr: { - std::complex a = m_corr->run(s/(SDR_RX_SCALEF/512.0f), 0); + std::complex a = m_corr->run(s/(SDR_RX_SCALEF/768.0f), 0); if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB m_sampleBuffer.push_back(Sample(a.imag(), a.real())); diff --git a/plugins/channelrx/chanalyzerng/readme.md b/plugins/channelrx/chanalyzerng/readme.md index 6dd3e30e1..9c815dfe6 100644 --- a/plugins/channelrx/chanalyzerng/readme.md +++ b/plugins/channelrx/chanalyzerng/readme.md @@ -41,6 +41,8 @@ Use the wheels to adjust the frequency shift in Hz from the center frequency of Locks a PLL or FLL (depends on control 3) on the signal and mixes its NCO with the input signal. This is mostly useful for carrier recovery on PSK modulations (PLL is used). This effectively de-rotates the signal and symbol points (constellation) can be seen in XY mode with real part as X and imagiary part as Y. +When the PLL is locked the icon lights up in green. The frequency shift from carrier appears in the tooltip. Locking indicator is pretty sharp with about +/- 100 Hz range. The FLL has no indicator. +

3: Locked loop mode

Use this combo to control the locked loop type: diff --git a/plugins/channelrx/demodam/readme.md b/plugins/channelrx/demodam/readme.md index ba7031d9d..418dcce9b 100644 --- a/plugins/channelrx/demodam/readme.md +++ b/plugins/channelrx/demodam/readme.md @@ -12,34 +12,44 @@ This plugin can be used to listen to a narrowband amplitude modulated signal. "N Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -

2: Channel power

+

2: PLL and synchronous AM

+ +Use this toggle button to turn on or off the PLL locking and synchronous AM detection. When on the input signal is mixed with the NCO of the PLL that locks to the carrier of the AM transmission. Then the signal is processed as a DSB or SSB (see control 3) modulated signal. The main advantage compared to enveloppe detection is a better resilience to carrier selective fading. This does not prevents all selective fading distorsion but addresses the most annoying. + +When the PLL is locked the icon lights up in green. The frequency shift from carrier appears in the tooltip. Locking indicator is pretty sharp with about +/- 100 Hz range. + +

3: DSB/SSB selection

+ +Use the left mouse button to toggle DSB/SSB operation. Soemtimes one of the two sidebands is affected by interference. Selecting SSB may help by using only the sideband without interference. Right click to open a dialog to select which sideband is used (LSB or USB). + +

4: Channel power

Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. -

3: Audio mute and audio output select

+

5: Audio mute and audio output select

Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. -

5: Level meter in dB

+

6: Level meter in dB

- top bar (green): average value - bottom bar (blue green): instantaneous peak value - tip vertical bar (bright green): peak hold value -

6:Bandpass boxcar filter toggle

+

7:Bandpass boxcar filter toggle

Use this button to enable or disable the bandpass boxcar (sharp) filter with low cutoff at 300 Hz and high cutoff at half the RF bandwidth. This may help readability of low signals on air traffic communications but degrades audio on comfortable AM broadcast transmissions. -

7: RF bandwidth

+

8: RF bandwidth

This is the bandwidth in kHz of the channel signal before demodulation. It can be set continuously in 1 kHz steps from 1 to 40 kHz. -

8: Volume

+

9: Volume

This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button. -

9: Squelch threshold

+

10: Squelch threshold

This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button. From 35c4d5a3257c899d6dec3e1afe94a32a7f25d19b Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 21 May 2018 18:00:10 +0200 Subject: [PATCH 439/956] ScopeVisNG: more memory traces --- sdrgui/dsp/scopevisng.cpp | 24 +++++++++++++++++++----- sdrgui/dsp/scopevisng.h | 2 +- sdrgui/gui/glscopenggui.ui | 4 ++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index 439fed933..0d50a1548 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -546,8 +546,6 @@ void ScopeVisNG::stop() bool ScopeVisNG::handleMessage(const Message& message) { - qDebug() << "ScopeVisNG::handleMessage" << message.getIdentifier(); - if (DSPSignalNotification::match(message)) { DSPSignalNotification& notif = (DSPSignalNotification&) message; @@ -624,6 +622,7 @@ bool ScopeVisNG::handleMessage(const Message& message) } else if (MsgScopeVisNGAddTrigger::match(message)) { + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGAddTrigger"; QMutexLocker configLocker(&m_mutex); MsgScopeVisNGAddTrigger& conf = (MsgScopeVisNGAddTrigger&) message; m_triggerConditions.push_back(new TriggerCondition(conf.getTriggerData())); @@ -635,6 +634,7 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGChangeTrigger& conf = (MsgScopeVisNGChangeTrigger&) message; uint32_t triggerIndex = conf.getTriggerIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGChangeTrigger: " << triggerIndex; if (triggerIndex < m_triggerConditions.size()) { @@ -655,6 +655,7 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGRemoveTrigger& conf = (MsgScopeVisNGRemoveTrigger&) message; uint32_t triggerIndex = conf.getTriggerIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGRemoveTrigger: " << triggerIndex; if (triggerIndex < m_triggerConditions.size()) { @@ -670,6 +671,7 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGMoveTrigger& conf = (MsgScopeVisNGMoveTrigger&) message; int triggerIndex = conf.getTriggerIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGMoveTrigger: " << triggerIndex; if (!conf.getMoveUp() && (triggerIndex == 0)) { return true; @@ -691,6 +693,7 @@ bool ScopeVisNG::handleMessage(const Message& message) { MsgScopeVisNGFocusOnTrigger& conf = (MsgScopeVisNGFocusOnTrigger&) message; uint32_t triggerIndex = conf.getTriggerIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGFocusOnTrigger: " << triggerIndex; if (triggerIndex < m_triggerConditions.size()) { @@ -704,6 +707,7 @@ bool ScopeVisNG::handleMessage(const Message& message) } else if (MsgScopeVisNGAddTrace::match(message)) { + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGAddTrace"; QMutexLocker configLocker(&m_mutex); MsgScopeVisNGAddTrace& conf = (MsgScopeVisNGAddTrace&) message; m_traces.addTrace(conf.getTraceData(), m_traceSize); @@ -718,7 +722,9 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGChangeTrace& conf = (MsgScopeVisNGChangeTrace&) message; bool doComputeTriggerLevelsOnDisplay = m_traces.isVerticalDisplayChange(conf.getTraceData(), conf.getTraceIndex()); - m_traces.changeTrace(conf.getTraceData(), conf.getTraceIndex()); + uint32_t traceIndex = conf.getTraceIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGChangeTrace: " << traceIndex; + m_traces.changeTrace(conf.getTraceData(), traceIndex); updateMaxTraceDelay(); if (doComputeTriggerLevelsOnDisplay) computeDisplayTriggerLevels(); updateGLScopeDisplay(); @@ -728,7 +734,9 @@ bool ScopeVisNG::handleMessage(const Message& message) { QMutexLocker configLocker(&m_mutex); MsgScopeVisNGRemoveTrace& conf = (MsgScopeVisNGRemoveTrace&) message; - m_traces.removeTrace(conf.getTraceIndex()); + uint32_t traceIndex = conf.getTraceIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGRemoveTrace: " << traceIndex; + m_traces.removeTrace(traceIndex); updateMaxTraceDelay(); computeDisplayTriggerLevels(); updateGLScopeDisplay(); @@ -738,7 +746,9 @@ bool ScopeVisNG::handleMessage(const Message& message) { QMutexLocker configLocker(&m_mutex); MsgScopeVisNGMoveTrace& conf = (MsgScopeVisNGMoveTrace&) message; - m_traces.moveTrace(conf.getTraceIndex(), conf.getMoveUp()); + uint32_t traceIndex = conf.getTraceIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGMoveTrace: " << traceIndex; + m_traces.moveTrace(traceIndex, conf.getMoveUp()); //updateMaxTraceDelay(); computeDisplayTriggerLevels(); updateGLScopeDisplay(); @@ -748,6 +758,7 @@ bool ScopeVisNG::handleMessage(const Message& message) { MsgScopeVisNGFocusOnTrace& conf = (MsgScopeVisNGFocusOnTrace&) message; uint32_t traceIndex = conf.getTraceIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGFocusOnTrace: " << traceIndex; if (traceIndex < m_traces.m_tracesData.size()) { @@ -761,6 +772,7 @@ bool ScopeVisNG::handleMessage(const Message& message) } else if (MsgScopeVisNGOneShot::match(message)) { + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGOneShot"; MsgScopeVisNGOneShot& conf = (MsgScopeVisNGOneShot&) message; bool oneShot = conf.getOneShot(); m_triggerOneShot = oneShot; @@ -771,6 +783,7 @@ bool ScopeVisNG::handleMessage(const Message& message) { MsgScopeVisNGMemoryTrace& conf = (MsgScopeVisNGMemoryTrace&) message; uint32_t memoryIndex = conf.getMemoryIndex(); + qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGMemoryTrace: " << memoryIndex; if (memoryIndex != m_currentTraceMemoryIndex) { @@ -784,6 +797,7 @@ bool ScopeVisNG::handleMessage(const Message& message) } else { + qDebug() << "ScopeVisNG::handleMessage" << message.getIdentifier() << " not handled"; return false; } } diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index 59143be21..dc9f31cc9 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -145,7 +145,7 @@ public: static const uint32_t m_traceChunkSize; static const uint32_t m_maxNbTriggers = 10; static const uint32_t m_maxNbTraces = 10; - static const uint32_t m_nbTraceMemories = 16; + static const uint32_t m_nbTraceMemories = 50; ScopeVisNG(GLScopeNG* glScope = 0); virtual ~ScopeVisNG(); diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index 0f9c0b679..11d47e44a 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -248,7 +248,7 @@ - + Display XY traces and polar trace @@ -1131,7 +1131,7 @@ kS/s Select trace memory (0 is live) - 15 + 49 1 From 74286a5767696b5126e08ababd43885c86287b47 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 22 May 2018 00:10:56 +0200 Subject: [PATCH 440/956] Added a FFT based RRC filter --- sdrbase/dsp/fftfilt.cxx | 35 +++++++++++++++++++++++++++++++---- sdrbase/dsp/fftfilt.h | 30 ++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/sdrbase/dsp/fftfilt.cxx b/sdrbase/dsp/fftfilt.cxx index edae8deaa..8b7a6698f 100644 --- a/sdrbase/dsp/fftfilt.cxx +++ b/sdrbase/dsp/fftfilt.cxx @@ -28,6 +28,7 @@ // ---------------------------------------------------------------------------- #include +#include #include #include #include @@ -130,7 +131,7 @@ void fftfilt::create_filter(float f1, float f2) for (int i = 0; i < flen2; i++) filter[i] *= _blackman(i, flen2); - fft->ComplexFFT(filter); + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) // normalize the output filter for unity gain float scale = 0, mag; @@ -155,7 +156,7 @@ void fftfilt::create_dsb_filter(float f2) filter[i] *= _blackman(i, flen2); } - fft->ComplexFFT(filter); + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) // normalize the output filter for unity gain float scale = 0, mag; @@ -182,7 +183,7 @@ void fftfilt::create_asym_filter(float fopp, float fin) filter[i] *= _blackman(i, flen2); } - fft->ComplexFFT(filter); + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) // normalize the output filter for unity gain float scale = 0, mag; @@ -204,7 +205,7 @@ void fftfilt::create_asym_filter(float fopp, float fin) filterOpp[i] *= _blackman(i, flen2); } - fft->ComplexFFT(filterOpp); + fft->ComplexFFT(filterOpp); // filter was expressed in the time domain (impulse response) // normalize the output filter for unity gain scale = 0; @@ -218,6 +219,32 @@ void fftfilt::create_asym_filter(float fopp, float fin) } } +// This filter is constructed directly from frequency domain response. Run with runFilt. +void fftfilt::create_rrc_filter(float fb, float a) +{ + std::fill(filter, filter+flen, 0); + + for (int i = 0; i < flen; i++) { + filter[i] = frrc(fb, a, i, flen); + } + + // normalize the output filter for unity gain + float scale = 0, mag; + for (int i = 0; i < flen; i++) + { + mag = abs(filter[i]); + if (mag > scale) { + scale = mag; + } + } + if (scale != 0) + { + for (int i = 0; i < flen; i++) { + filter[i] /= scale; + } + } +} + // test bypass int fftfilt::noFilt(const cmplx & in, cmplx **out) { diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index a9db21f11..3c98fb7eb 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -28,6 +28,7 @@ public: void create_filter(float f1, float f2); void create_dsb_filter(float f2); void create_asym_filter(float fopp, float fin); //!< two different filters for in band and opposite band + void create_rrc_filter(float fb, float a); //!< root raised cosine. fb is half the band pass int noFilt(const cmplx& in, cmplx **out); int runFilt(const cmplx& in, cmplx **out); @@ -48,18 +49,43 @@ protected: int pass; int window; - inline float fsinc(float fc, int i, int len) { + inline float fsinc(float fc, int i, int len) + { int len2 = len/2; return (i == len2) ? 2.0 * fc: sin(2 * M_PI * fc * (i - len2)) / (M_PI * (i - len2)); } - inline float _blackman(int i, int len) { + inline float _blackman(int i, int len) + { return (0.42 - 0.50 * cos(2.0 * M_PI * i / len) + 0.08 * cos(4.0 * M_PI * i / len)); } + /** RRC function in the frequency domain. Zero frequency is on the sides with first half in positive frequencies + * and second half in negative frequencies */ + inline cmplx frrc(float fb, float a, int i, int len) + { + float x = i/(float)len; // normalize to [0..1] + x = 0.5-fabs(x-0.5); // apply symmetry: now both halves overlap near 0 + float tr = (fb*a)/2.0; // half the transition zone + + if (x < fb-tr) + { + return 1.0; // in band + } + else if (x < fb+tr) // transition + { + float y = ((x-(fb-tr)) / (2.0*tr))*M_PI; + return (cos(y) + 1.0f)/2.0f; + } + else + { + return 0.0; // out of band + } + } + void init_filter(); void init_dsb_filter(); }; From d7247dbccffec9f718678fe15eab88b4f981cb7d Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 22 May 2018 00:13:40 +0200 Subject: [PATCH 441/956] Normalize fftfilt.cxx to fftfilt.cpp --- sdrbase/CMakeLists.txt | 2 +- sdrbase/dsp/{fftfilt.cxx => fftfilt.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename sdrbase/dsp/{fftfilt.cxx => fftfilt.cpp} (100%) diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index da49b3469..c3ac5e833 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -30,7 +30,7 @@ set(sdrbase_SOURCES dsp/dspdevicesinkengine.cpp dsp/fftcorr.cpp dsp/fftengine.cpp - dsp/fftfilt.cxx + dsp/fftfilt.cpp dsp/fftwindow.cpp dsp/filterrc.cpp dsp/filtermbe.cpp diff --git a/sdrbase/dsp/fftfilt.cxx b/sdrbase/dsp/fftfilt.cpp similarity index 100% rename from sdrbase/dsp/fftfilt.cxx rename to sdrbase/dsp/fftfilt.cpp From 1dcb84ef8f9cb1bc6cab84fd9766d7b32d9a76a6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 22 May 2018 00:14:37 +0200 Subject: [PATCH 442/956] Windows build fixes --- plugins/channelrx/chanalyzerng/chanalyzerng.pro | 9 ++++++--- plugins/channelrx/demodam/demodam.pro | 9 ++++++--- sdrbase/sdrbase.pro | 6 ++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.pro b/plugins/channelrx/chanalyzerng/chanalyzerng.pro index 07cddc9d2..0960426e8 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.pro +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.pro @@ -32,11 +32,14 @@ CONFIG(Debug):build_subdir = debug SOURCES += chanalyzerng.cpp\ chanalyzernggui.cpp\ - chanalyzerngplugin.cpp + chanalyzerngplugin.cpp\ + chanalyzerngsettings.cpp\ HEADERS += chanalyzerng.h\ -chanalyzernggui.h\ -chanalyzerngplugin.h + chanalyzernggui.h\ + chanalyzerngplugin.h\ + chanalyzerngplugin.h + FORMS += chanalyzernggui.ui diff --git a/plugins/channelrx/demodam/demodam.pro b/plugins/channelrx/demodam/demodam.pro index 910d07a44..72a77a5fb 100644 --- a/plugins/channelrx/demodam/demodam.pro +++ b/plugins/channelrx/demodam/demodam.pro @@ -29,14 +29,17 @@ CONFIG(Debug):build_subdir = debug SOURCES += amdemod.cpp\ amdemodgui.cpp\ amdemodplugin.cpp\ - amdemodsettings.cpp + amdemodsettings.cpp\ + amdemodssbdialog.cpp HEADERS += amdemod.h\ amdemodgui.h\ amdemodplugin.h\ - amdemodsettings.h + amdemodsettings.h\ + amdemodssbdialog.h -FORMS += amdemodgui.ui +FORMS += amdemodgui.ui\ + amdemodssb.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index 399cc14c5..54436d149 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -75,17 +75,20 @@ SOURCES += audio/audiodevicemanager.cpp\ dsp/dspdevicesinkengine.cpp\ dsp/fftengine.cpp\ dsp/kissengine.cpp\ + dsp/fftcorr.cpp\ dsp/fftfilt.cxx\ dsp/fftwindow.cpp\ dsp/filterrc.cpp\ dsp/filtermbe.cpp\ dsp/filerecord.cpp\ + dsp/freqlockcomplex.cpp\ dsp/interpolator.cpp\ dsp/hbfiltertraits.cpp\ dsp/lowpass.cpp\ dsp/nco.cpp\ dsp/ncof.cpp\ dsp/phaselock.cpp\ + dsp/phaselockcomplex.cpp\ dsp/projector.cpp\ dsp/recursivefilters.cpp\ dsp/samplesinkfifo.cpp\ @@ -146,6 +149,7 @@ HEADERS += audio/audiodevicemanager.h\ dsp/dspdevicesourceengine.h\ dsp/dspdevicesinkengine.h\ dsp/dsptypes.h\ + dsp/fftcorr.h\ dsp/fftengine.h\ dsp/fftfilt.h\ dsp/fftwengine.h\ @@ -153,6 +157,7 @@ HEADERS += audio/audiodevicemanager.h\ dsp/filterrc.h\ dsp/filtermbe.h\ dsp/filerecord.h\ + dsp/freqlockcomplex.h\ dsp/gfft.h\ dsp/hbfiltertraits.h\ dsp/iirfilter.h\ @@ -172,6 +177,7 @@ HEADERS += audio/audiodevicemanager.h\ dsp/ncof.h\ dsp/phasediscri.h\ dsp/phaselock.h\ + dsp/phaselockcomplex.h\ dsp/projector.h\ dsp/recursivefilters.h\ dsp/samplesinkfifo.h\ From 775a9775ebb47a5ed33f0978565f54bee8da48a7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 22 May 2018 02:20:36 +0200 Subject: [PATCH 443/956] Channel analyzer NG: implemented optional RRC filter --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 26 ++++++++-- plugins/channelrx/chanalyzerng/chanalyzerng.h | 1 + .../chanalyzerng/chanalyzernggui.cpp | 17 ++++++ .../channelrx/chanalyzerng/chanalyzernggui.h | 2 + .../channelrx/chanalyzerng/chanalyzernggui.ui | 52 +++++++++++++++++++ .../chanalyzerng/chanalyzerngsettings.cpp | 6 +++ .../chanalyzerng/chanalyzerngsettings.h | 2 + sdrbase/dsp/fftfilt.h | 2 +- 8 files changed, 104 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 148e53959..958c59b63 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -52,6 +52,7 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : m_inputFrequencyOffset = 0; SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_inputSampleRate, m_settings.m_bandwidth / m_inputSampleRate, ssbFftLen); DSBFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); + RRCFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); m_corr = new fftcorr(8*ssbFftLen); // 8k for 4k effective samples m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain @@ -72,6 +73,7 @@ ChannelAnalyzerNG::~ChannelAnalyzerNG() delete m_channelizer; delete SSBFilter; delete DSBFilter; + delete RRCFilter; } void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, @@ -129,10 +131,17 @@ void ChannelAnalyzerNG::processOneSample(Complex& c, fftfilt::cmplx *sideband) int n_out; int decim = 1<runSSB(c, &sideband, m_usb); - } else { - n_out = DSBFilter->runDSB(c, &sideband); + } + else + { + if (m_settings.m_rrc) { + n_out = RRCFilter->runFilt(c, &sideband); + } else { + n_out = DSBFilter->runDSB(c, &sideband); + } } for (int i = 0; i < n_out; i++) @@ -293,6 +302,7 @@ void ChannelAnalyzerNG::setFilters(int sampleRate, float bandwidth, float lowCut SSBFilter->create_filter(lowCutoff / sampleRate, bandwidth / sampleRate); DSBFilter->create_dsb_filter(bandwidth / sampleRate); + RRCFilter->create_rrc_filter(bandwidth / sampleRate, m_settings.m_rrcRolloff / 100.0); } void ChannelAnalyzerNG::applySettings(const ChannelAnalyzerNGSettings& settings, bool force) @@ -300,6 +310,8 @@ void ChannelAnalyzerNG::applySettings(const ChannelAnalyzerNGSettings& settings, qDebug() << "ChannelAnalyzerNG::applySettings:" << " m_downSample: " << settings.m_downSample << " m_downSampleRate: " << settings.m_downSampleRate + << " m_rcc: " << settings.m_rrc + << " m_rrcRolloff: " << settings.m_rrcRolloff / 100.0 << " m_bandwidth: " << settings.m_bandwidth << " m_lowCutoff: " << settings.m_lowCutoff << " m_spanLog2: " << settings.m_spanLog2 @@ -338,6 +350,14 @@ void ChannelAnalyzerNG::applySettings(const ChannelAnalyzerNGSettings& settings, m_settingsMutex.unlock(); } + if ((settings.m_rrcRolloff != m_settings.m_rrcRolloff) || force) + { + float sampleRate = settings.m_downSample ? (float) settings.m_downSampleRate : (float) m_inputSampleRate; + m_settingsMutex.lock(); + RRCFilter->create_rrc_filter(settings.m_bandwidth / sampleRate, settings.m_rrcRolloff / 100.0); + m_settingsMutex.unlock(); + } + if ((settings.m_spanLog2 != m_settings.m_spanLog2) || force) { int sampleRate = (settings.m_downSample ? settings.m_downSampleRate : m_inputSampleRate) / (1<spanLog2->setCurrentIndex(m_settings.m_spanLog2); displayPLLSettings(); ui->signalSelect->setCurrentIndex((int) m_settings.m_inputType); + ui->rrcFilter->setChecked(m_settings.m_rrc); + QString rolloffStr = QString::number(m_settings.m_rrcRolloff/100.0, 'f', 2); + ui->rrcRolloffText->setText(rolloffStr); blockApplySettings(false); } @@ -291,6 +294,20 @@ void ChannelAnalyzerNGGUI::on_deltaFrequency_changed(qint64 value) applySettings(); } +void ChannelAnalyzerNGGUI::on_rrcFilter_toggled(bool checked) +{ + m_settings.m_rrc = checked; + applySettings(); +} + +void ChannelAnalyzerNGGUI::on_rrcRolloff_valueChanged(int value) +{ + m_settings.m_rrcRolloff = value; + QString rolloffStr = QString::number(value/100.0, 'f', 2); + ui->rrcRolloffText->setText(rolloffStr); + applySettings(); +} + void ChannelAnalyzerNGGUI::on_BW_valueChanged(int value __attribute__((unused))) { setFiltersUIBoundaries(); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h index fb68d20eb..c19a10ec4 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.h @@ -99,6 +99,8 @@ private slots: void on_pllPskOrder_currentIndexChanged(int index); void on_useRationalDownsampler_toggled(bool checked); void on_signalSelect_currentIndexChanged(int index); + void on_rrcFilter_toggled(bool checked); + void on_rrcRolloff_valueChanged(int value); void on_BW_valueChanged(int value); void on_lowCut_valueChanged(int value); void on_spanLog2_currentIndexChanged(int index); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index ecee494a2..0765ef73a 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -419,6 +419,58 @@ + + + + Toggle RRC filter + + + + + + + :/dsb.png:/dsb.png + + + true + + + + + + + + 24 + 24 + + + + Tune RRC filter rolloff factor + + + 10 + + + 50 + + + 1 + + + 30 + + + + + + + RRC filter rolloff factor value + + + 0.00 + + + diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp index bac4e1286..20ec8bf75 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp @@ -40,6 +40,8 @@ void ChannelAnalyzerNGSettings::resetToDefaults() m_ssb = false; m_pll = false; m_fll = false; + m_rrc = false; + m_rrcRolloff = 30; // 0.3 m_pllPskOrder = 1; m_inputType = InputSignal; m_rgbColor = QColor(128, 128, 128).rgb(); @@ -65,6 +67,8 @@ QByteArray ChannelAnalyzerNGSettings::serialize() const s.writeU32(13, m_pllPskOrder); s.writeS32(14, (int) m_inputType); s.writeString(15, m_title); + s.writeBool(16, m_rrc); + s.writeU32(17, m_rrcRolloff); return s.final(); } @@ -110,6 +114,8 @@ bool ChannelAnalyzerNGSettings::deserialize(const QByteArray& data) d.readS32(14, &tmp, 0); m_inputType = (InputType) tmp; d.readString(15, &m_title, "Channel Analyzer NG"); + d.readBool(16, &m_rrc, false); + d.readU32(17, &m_rrcRolloff, 30); return true; } diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h index 55a9ff42d..df37a5282 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h @@ -39,6 +39,8 @@ struct ChannelAnalyzerNGSettings bool m_ssb; bool m_pll; bool m_fll; + bool m_rrc; + quint32 m_rrcRolloff; //!< in 100ths unsigned int m_pllPskOrder; InputType m_inputType; quint32 m_rgbColor; diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index 3c98fb7eb..5c3e0222c 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -69,7 +69,7 @@ protected: { float x = i/(float)len; // normalize to [0..1] x = 0.5-fabs(x-0.5); // apply symmetry: now both halves overlap near 0 - float tr = (fb*a)/2.0; // half the transition zone + float tr = fb*a; // half the transition zone if (x < fb-tr) { From c69d203bd012310e5753dc9495fa444aa4b8810a Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 22 May 2018 03:03:12 +0200 Subject: [PATCH 444/956] Windows build fixes --- sdrbase/sdrbase.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index 54436d149..fa6d0d6ed 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -76,7 +76,7 @@ SOURCES += audio/audiodevicemanager.cpp\ dsp/fftengine.cpp\ dsp/kissengine.cpp\ dsp/fftcorr.cpp\ - dsp/fftfilt.cxx\ + dsp/fftfilt.cpp\ dsp/fftwindow.cpp\ dsp/filterrc.cpp\ dsp/filtermbe.cpp\ From 85df6218de80efe89af087060b7b0c8b51ae603c Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 22 May 2018 13:54:01 +0200 Subject: [PATCH 445/956] Channel analyzer NG and Projector: PSK symbol mapping projection --- sdrbase/dsp/projector.cpp | 113 ++++++++++++++++++++++++++++++++++++ sdrbase/dsp/projector.h | 5 ++ sdrgui/gui/glscopenggui.cpp | 4 ++ 3 files changed, 122 insertions(+) diff --git a/sdrbase/dsp/projector.cpp b/sdrbase/dsp/projector.cpp index a678409f7..1d9a66c86 100644 --- a/sdrbase/dsp/projector.cpp +++ b/sdrbase/dsp/projector.cpp @@ -15,6 +15,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include "projector.h" Projector::Projector(ProjectionType projectionType) : @@ -77,6 +78,106 @@ Real Projector::run(const Sample& s) v = dPhi; } break; + case ProjectionBPSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(2*arg) / (2.0*M_PI); // generic estimation around 0 + // mapping on 2 symbols + if (arg < -M_PI/2) { + v -= 1.0/2; + } else if (arg < M_PI/2) { + v += 1.0/2; + } else if (arg < M_PI) { + v -= 1.0/2; + } + } + break; + case ProjectionQPSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(4*arg) / (4.0*M_PI); // generic estimation around 0 + // mapping on 4 symbols + if (arg < -3*M_PI/4) { + v -= 3.0/4; + } else if (arg < -M_PI/4) { + v -= 1.0/4; + } else if (arg < M_PI/4) { + v += 1.0/4; + } else if (arg < 3*M_PI/4) { + v += 3.0/4; + } else if (arg < M_PI) { + v -= 3.0/4; + } + } + break; + case Projection8PSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(8*arg) / (8.0*M_PI); // generic estimation around 0 + // mapping on 8 symbols + if (arg < -7*M_PI/8) { + v -= 7.0/8; + } else if (arg < -5*M_PI/8) { + v -= 5.0/8; + } else if (arg < -3*M_PI/8) { + v -= 3.0/8; + } else if (arg < -M_PI/8) { + v -= 1.0/8; + } else if (arg < M_PI/8) { + v += 1.0/8; + } else if (arg < 3*M_PI/8) { + v += 3.0/8; + } else if (arg < 5*M_PI/8) { + v += 5.0/8; + } else if (arg < 7*M_PI/8) { + v += 7.0/8; + } else if (arg < M_PI) { + v -= 7.0/8; + } + } + break; + case Projection16PSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(16*arg) / (16.0*M_PI); // generic estimation around 0 + // mapping on 16 symbols + if (arg < -15*M_PI/16) { + v -= 15.0/16; + } else if (arg < -13*M_PI/16) { + v -= 13.0/6; + } else if (arg < -11*M_PI/16) { + v -= 11.0/16; + } else if (arg < -9*M_PI/16) { + v -= 9.0/16; + } else if (arg < -7*M_PI/16) { + v -= 7.0/16; + } else if (arg < -5*M_PI/16) { + v -= 5.0/16; + } else if (arg < -3*M_PI/16) { + v -= 3.0/16; + } else if (arg < -M_PI/16) { + v -= 1.0/16; + } else if (arg < M_PI/16) { + v += 1.0/16; + } else if (arg < 3.0*M_PI/16) { + v += 3.0/16; + } else if (arg < 5.0*M_PI/16) { + v += 5.0/16; + } else if (arg < 7.0*M_PI/16) { + v += 7.0/16; + } else if (arg < 9.0*M_PI/16) { + v += 9.0/16; + } else if (arg < 11.0*M_PI/16) { + v += 11.0/16; + } else if (arg < 13.0*M_PI/16) { + v += 13.0/16; + } else if (arg < 15.0*M_PI/16) { + v += 15.0/16; + } else if (arg < M_PI) { + v -= 15.0/16; + } + } + break; case ProjectionReal: default: v = s.m_real / SDR_RX_SCALEF; @@ -91,3 +192,15 @@ Real Projector::run(const Sample& s) } } +Real Projector::normalizeAngle(Real angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; +} + + diff --git a/sdrbase/dsp/projector.h b/sdrbase/dsp/projector.h index b0cdf88af..094c8c592 100644 --- a/sdrbase/dsp/projector.h +++ b/sdrbase/dsp/projector.h @@ -28,6 +28,10 @@ public: ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude ProjectionPhase, //!< Calculate phase ProjectionDPhase, //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate + ProjectionBPSK, //!< Phase comparator BPSK evaluation + ProjectionQPSK, //!< Phase comparator QPSK evaluation + Projection8PSK, //!< Phase comparator 8-PSK evaluation + Projection16PSK, //!< Phase comparator 16-PSK evaluation nbProjectionTypes //!< Gives the number of projections in the enum }; @@ -42,6 +46,7 @@ public: Real run(const Sample& s); private: + static Real normalizeAngle(Real angle); ProjectionType m_projectionType; Real m_prevArg; Real *m_cache; diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 55da3719b..32087f0dc 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -1187,6 +1187,10 @@ void GLScopeNGGUI::fillProjectionCombo(QComboBox* comboBox) comboBox->addItem("MagdB", Projector::ProjectionMagDB); comboBox->addItem("Phi", Projector::ProjectionPhase); comboBox->addItem("dPhi", Projector::ProjectionDPhase); + comboBox->addItem("BPSK", Projector::ProjectionBPSK); + comboBox->addItem("QPSK", Projector::ProjectionQPSK); + comboBox->addItem("8PSK", Projector::Projection8PSK); + comboBox->addItem("16PSK", Projector::Projection16PSK); } void GLScopeNGGUI::disableLiveMode(bool disable) From e37a986b1608cd417a2746d74177c30291bfedd8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 22 May 2018 19:25:55 +0200 Subject: [PATCH 446/956] Channel analyzer NG: set RRC filter rollof factor range from 0.1 to 0.7. Updated documentation --- doc/img/ChAnalyzerNG_plugin.png | Bin 104882 -> 107904 bytes doc/img/ChAnalyzerNG_plugin.xcf | Bin 629264 -> 641984 bytes doc/img/ChAnalyzerNG_plugin_scope1.png | Bin 11901 -> 13383 bytes doc/img/ChAnalyzerNG_plugin_scope1.xcf | Bin 65996 -> 78902 bytes doc/img/ChAnalyzerNG_plugin_settings.png | Bin 19483 -> 22617 bytes doc/img/ChAnalyzerNG_plugin_settings.xcf | Bin 123948 -> 136213 bytes doc/img/ChAnalyzerNG_plugin_tetra.png | Bin 0 -> 90951 bytes doc/img/ChAnalyzerNG_plugin_tetra.xcf | Bin 0 -> 530785 bytes .../chanalyzerng/chanalyzernggui.cpp | 12 ++- .../channelrx/chanalyzerng/chanalyzernggui.ui | 4 +- .../chanalyzerng/chanalyzerngsettings.cpp | 4 +- plugins/channelrx/chanalyzerng/readme.md | 80 +++++++++++++++--- 12 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 doc/img/ChAnalyzerNG_plugin_tetra.png create mode 100644 doc/img/ChAnalyzerNG_plugin_tetra.xcf diff --git a/doc/img/ChAnalyzerNG_plugin.png b/doc/img/ChAnalyzerNG_plugin.png index fa6aba68739ae572e273298f1e171572680b68fc..0e0d37d82b4caea9ad05014a65d3f9bee4eb531a 100644 GIT binary patch literal 107904 zcmeFZcRbbq8$W!Iy=OuZg=FvSO%aj3XUQhA_s*&$5eXrC@4c0T%gy7`=$8SQH0#Xfe)rJT-AyC_=#?ORAv#39d9Vp?^y8;TasCuhT|Myq|t>j z(_ODX==u0M{3=@eIgT^kyStWN`+Vx&^Cv%8c{V>L5F`5h!~WjB5Z%;5A^v+KZ*RN# z_wU5lOy*wweOOo|r=x<*-<#Zf!CUlyZwyK_B!8dtnwXIw{O@zRut-DyK10SlC|vsQ zGw8Wn{{Of6|N5fQ7m(&}tq^{&W2oZ$8!}&p71Az4_L|*A+;=`Jmu3C9MNo?NwAl4; ze5vLa-y=|4_xoT3&0V@!VEI zA6$0lM)l2~RtSv!<<4m3zaLKH$IuYD>qZw*@@+Kzl_%7zQ(@9~**3yXjE$!+5Ps3u zg`4_|QA9*Ul-|WeMae#Rz+yL5_jpl=-%dYfvFC%?*342*=45tu_R*hoozb%8P^!Yy z^q40mJHN9nf0d9o^8I^S|0E1&#n8&oONLk9D%TAxecIdFnME4iIXd{uMRkcJ^r7vL zn^kOMv(Sq*zqK_buic)J+Z{^pIZ?#K)*W{jZO@--73g=QN;fjFZ`}N>c1J|?!+h}7 zfWFL!<4--#S@jO?-4F@uNaUNApnp9WZd>q(nbUUcR+(i#Ns8Bw(fN}2c^NtVYofx2 z6AXkz?hyU0-<&^^?nQn6d`nxeyDu*RGX(G2OM>fOM?L?hA@$z<<@rzwHWA0QQ7%^9 zvM%$4VzW-uzE6?}KjhW7w50tgAapdC8jX&|fAEkBwP`qCkIVPM>-=;e>$0uFR^06S zdq=m8YWAYCG8wlc+azJvg9K%o%*;$Wkuw5wZfpH&r)#2~2Of)Kp9YrUhmMR&oJfR= zi&(4BP;%*qfl`OvSQ+u*=5%LnVMFvgHa$r?qM40JtfYIpSO3d=S6(alnRP}llLj_k zoa8p|1$;J9P{69It6Moc`7#jRIv4Vm!svi?a7AQoq8g2(QG@^u6JsUIb$#r*L5=f3 zV{cbi)6RmTFO#T-QGFw|)eKEYOi6S%tb~LDHXv8EW z0S2`%Oa?L^B7O`n@82|!=Om-1j#yi>*<1THV5vK6K3M(yDW}JtcAj1(@sA()#jcxn z=Vd>VYfX=LG_*^fu+dz7&Z0a_aI2=Ti@8#*W#hz0# zOoFxcfh-jigzLscNA&e^PWK&kI35_z8x!c(BR(37OmWG=u2l6N`*NF8K2_eQ6h8+B z@H3|yeF>YJQ;fg9;du4-Z5Zy=AMw=>kA>rzsCXTkdOnQor<|W3>g;W-VCw-yKuVj`lja%%zOvm=3GlU4x*6_tVBCHVb&K}Vc- z9G4_Li3!SBr^ow}hKB4dEmBdLlhx00A13nhDNy~*s|h=+g^hE6`qL?SEs5YWSij}X z<9IK~N-r!-ZCZ?;m#^4RY*A?FV>wlKugw&9Z+YM<7Z(=-@mk@E@1+==pCCvakNOnD zBI~S)#%n8S*Vfih5Z8rY{Om;ad&J!K>gF*6-un9b?8X$Hqaz^-B1<_L8BcC*Zc@x- zrjVmvzk3(*`SWMBadL7O+WYtK!?iF?Onzt{8p2=dP2pkYvFsxXlc9&7GW%(i;$p&Y z6Pc(N7v5Wk)5p6SEaX)_=Tsj1J3P#!*RKZ~`A&vh;}P>yVwAT0gYM?RFCTNGo{kKs zKl7npnf1txk;2PZzqMbb07b4uO z7cjVd(_Q-O_}?O>V88Su`Q*2Ww-Obq%bD83%cA09f8AuvYLVKu->HFtfftu**<|;@+67 z4TaoA$*L$PSJQ9NDw{0oiLpLbKElN6xxdr;tMn!u$FjmD&7ykg|p5_x(Fwo4H=yoqdOE4c z;X&&sN$JSUjLb|5>MYHro4k9dgI0V~p7ufriF8$I_IL)yMHDC*S~?%r*4O(pqpq?W z;IvNrrpTsFj_oc^n8in?0D(^=d3KrZ1tI)1x}k^CDns&!BW_so|vP*exV?s?&?}} z^BXrw(1>|q5#ErI&KiKn(zCLb4SLcT)_b7q<`W?Eg>1w-gva5C#p*| zrWnP=eS?C|{NKOF{4?C6b$JZMNRs}w@!lUUj0JCRPdaXHlB?`Vp>pXESfO2AwF>v| z7xa0o{LG*>=T2y@C(wf|Xf%BTk`#G?QDX??w629;$jstb!o>J*oPOE3o5VX&ZXMrj z(G3MCUtl#=IYuxtDk}fcuLkea*0C`R-?MGu3ft3bB;i-sbj#XsIj+PBrNF<<9;|%rQ!qtiqG*x1tM zC4c|GB8%=Pk<^D)aTVE$@dcwL7Cb_(8#s!Jiswtd=lfS#*)om}8>a_+`5ouTkcYAK zsc#T$+-z1#g#BUweE9Fq?mxD%$y5DV)bt}+ICWr|)4Yqw`&W%JV&+$4^>`P0Qx~M@6qbvK ztjagDtK+25x6s8uHCr9dza4V9YIGS87l&7A7yO(sn;xRL!@4jkG)6_f0PkPW;=u?X zQmXqM+XFA6Z`~Y?kvFA7W-iW|p=&^~p1WGUZ5cmEO9T#{r-J}l{!}8OQG=!p#;H+)yDYxcYmQ;&LnCA&xM}+xF!>6 zXhY+Rr-irw{oH9*SEuW_X7mKcHM2GyM>7>gtr2Pvr6Y_Z!a&ePyLoTHq{RlCH_`B;-Lz7fykFH zrL?svPY%{`Qy*EDL~7>B!ripAGz6lcpun>46UvLVQIudQ6vWZ#W+T2S^q&EhQy#eZ z)cg=|S133PBae>USdAKJ;P{*#Zek+5E{>Pp-+RuOmzP)Md+F1);8b_Al8X_~f{BiP zlarI^>({TA;}vXerd$n2bGY74{T&?`&CShQ`}?wLY9vxpQk)Jm5|OmxH{f`^7qGwK z~#84&fNSKRQQNq@k<&~Mx~}uTAwiJlWsCID;W9o zB=X6{vs@jj_jFZC_<`0SMl*rdD%EP2t5zhB<18n znxAgg?GmV|sd0K9*aQUy%|Ig^@ZkeBVkRTrum#RBY&lB4X2kkAisAR;FA zpP0};u=>kE*!#xGdU;}^ql9Zc!mr>;-o{EYJG$XvveqMnW zZGojmLBRGjQ`^`1LlFf`(=;|lg@S@YgcA(CWvDi5NJK=0j}PDr2N#!MZT(`J_3imi zF2^|;ZC%|o)pR*ZQI9eo-H`%={yZHXsIlhe<`VguTFV0||Ka=#l98+qc32^Z|C7rP z^zq&OUsM_LbV@N`b)m17vb1D|Zuc&tsXgLqvB$pE6->~9#x)-XPmr0aAieSe* z2&VNpLJ_|_!_%Ja@F`oUVnAnY*H*dnj+_u}Vc9(y*9?p|=aNs1kF7(+q$Enb; z9)XCW7LbEqLGO1nzT5F=%j66ecx~u!f1v&h6;boFft=wgf65k5;y;7gu@^s|{TEN7+mlRl#hWt!&njR`I#L;Rjf`whiLJ1T<%V4)plA*}zosYd0*hPVFyZEmOY-vzzE2~PV_k*~~1fJs2pWvKYy`)oWY zU98gmU#^FPWc+{R`grDb6t}^i3hZk4zk3z-h57#-r$@iY!kki6#9449hk`*qTE@#u zMC;1G*O!3KNO1R)eby7Vw;C53BZHbzU(E4!g(CL0#d_8dD4)9en{-)1v>xj*Kj%A| zcS7aWH3%;*SGE7p!EZVLB(RF?_HP!xTNF}d&gK4r<>)^`vgm|V4GS!841Pt|Bq1gU z620S+U~MfL&o}&bXHan;)7S4y;}P2bh)q#|LU~;eNFz*4OjP{^5q*?xxh|+~0u}yB zEHVYUq4IZFCc=6G9^DFgr0!&6JNTiXk6u_lV%U!Vl`dB5@Sj(ykLG*Fv)$f&v@Wu- z(0P(7f$*0w=AG0Z)BC9X_?s5T5L5E0JKe78MTo17O?Wg4KJjw~(mOG49US7)+?O!I zYLcSmXLU{}Ycs8GO=`7f#A(y&QOM4-=o&rd&?{S+LO8mXyxLK~PF3*BG_0_kVQi0U zz2BwW!{&Hz{P1~*I%7**Yq*8(k3>78hi(5`QBg$}Xs*(78*ZP*H+g9#YU4SgbBmf5 z6G%i$dMP=vo4$D*@s>=AmEs+h9`*i{g+C5lEV-lmYEu?s-U6j9#eC%-yQ!=n9Is?} z>@Vms-%H+C5ByT3#JgP)B9E!}o4L%*;Wlabp$6ld?G-1c!H5oxvW7Nha_2ET-t7{( z#~jp_JI=4Navhfnd4HQP?-jSRWvMd;2x)1pm5jVNwPK9^Ct&|v6LQVpqoKb*rN79l zxxKSO=(f+Z8plWy&hl1}EWC_7`f1*Ar)Gi5GwU)!#SArocfWuCR{wRzOZ70Wr*Qc9 zolgVLUS;PcuA4l)9V9`A%4*(W)FAuD><;1Xs~yhCPm$XS`pI8M)f)diY=bK-lehXT z`i(QEl~eMG$32FNZ`?XD_sR5sR)^Me}pZ%qv*23(rUEE(bhSSddqi)VBpnBakZc04- zJn$U3IA1ikTC#D7!e!ux{AI4Ch*^(#MTvJ#CRVUine8}*R4NgWb{gC&)YQ~)fA~)- zt-HH>QAvqZ?Tht2rYGguGcw0eAs?~CVK9XYC1dzc@e{pddzG;B`N2x;uX{K9#xWZ8 zHB&9St+|VP1b1KN+AUU0YqVe7U!z@fMdpI?bPv+d0$YD}hFv2zFO<~2yiEgb&45eM zzI1v=@~D4yrb1(+>d1+}N+-Emxu-S6YRKpFAbBVi+k0S9p0achu zz?_A=D4tT86ek!e%tE4(J3yqG@E<4@Iv$q+yTx5_>l) zT)IP3J|$ho*&BVmS z9QQ!s)|)iyG@wW0ykpeMeE*L(dT^kUbNF|67B%eWI4}5#YH+c}(OIhvo@(mK%Hfc= zY4Cn5UZmCgC8iM@Bj3O5oWV%&+{vlPdW3y#x{+=TnQWuQx^~pDH;Rr%rPuMDjASj%AVqXD%nL;q_*v z?566*XvGX;L9zlXS3xK4!@kwa#Fk$MAvD1lj`31t37_a{Zo-c`ua4`HHDEfDGz?Iy`5*Dy!*kvp*j0;7G2x} zd@#4(FyoCBv*L)P2Y zQ*gdovH(5m4%ZJ?8Pc<98xH+Z+8B;Y7vc}%br>jD-j~(jn!liUGSHCt^6s~R0a7EM z60ER0Nn*@r*84=21QmwEXaStrC?=tG%8q+zqfsO*a=C+ zab@t1%|sP3u)=Z{!vzKr8H(|Vk?{(*j`jvr9DjEaBZD1+hF`~S`DX?tmub+~XZCg5 z7%w)bq5yo&_N7X{7j`XZc2o)_xW2UzZ={*y3oHyJ0PE(;X`cva`BKh3Cn4zsAPK3vvpIkXR;_MW&n}DOSA-z~_@KuWm{sk6WI0 zF=|Cc1>;hNT6U4^rlHMfQL65{edwDdpv&VlZbEono*x2TOiaaZ)755TFbEB%o2TbI zWUfEcjg8H`T3T8nZd;#h?ymdBXKDP_5Hw_^Fga^l6!c`eHXST8zZpjM{qmT?8PT!7 z&&)MUvvOU$2tAFY#PRYz|CRQ)cc|_XAz~hD2zm?unnB0Y|~lW%}Xbq&x~J+R(G*v4DNTX4}2Z1Ut>>nwLm3MyTaQg3Ope4>ND|3I~4TmPQv z%}1^^c-6?uAI^{2pT1N|5jV~XyUCXp-S42cMv=JimeVV@3WkbG^XLd;1tT z{&NsZ2!y@8JrbZ$3)ltevv)$pd7YT}(&zjnA!!7XBr+mJ+|`OP4GvaEkl3<0oRkR& z$juKTFMe9`KFU_d!p4?_mrdli{RW}HYctB3B<4*8nnD`@to@W;fO_61d(8m#kjS0a zeyXnRm`q?UqcH|o*=M-%d%EcuZ-p$HHEvS%uKHzyha#7|b!}UQt(-K&!;oRh z^${{^0{L_MjJMuxsR+Md?JLC!+6AhC<^5AT9TYwqpQm2F=eMPbrn#5q3$!i;&(j~Q zhxgNE==}ktCM6{)HQ3nNitipO{PhCdB*KC;2Kb)E<>&smeWMAm8og((FAGRiYMATR z^@ zXV(CQTS0AVK?Ty2uU@|mWI8PA`6qyQ{nut^{Q*jm>e^o56R9K%1q)2Mp(P|FAP}^) zv=a^9)R5ain`1$qaL6Rakn?Uc=Di%Na%5;}Z3TesFx!F-NBq5fp2et2JgZIsWEsia zp}E2A$Ckr+2|hLX`D{}jt86xtHRYr30M|_+e7e4Vl?2KH)+E3m4fCCLVPPQ$pkGWV z)xc$wAo&YmMi6wOqSqmqkCwAi@V1f2|I zZ0fL>7)%66lBk5VVnJ%zgYn^!kunh!Y&msy0s;b{F=9b+ZU!hSev;0^PDSRp-pA227BzcW5VHt?(?mm*0dWcdQ^1{2 z>T!5$3o0sVU-k1P)A;sp-*D((V}XVzBO&2;G4iQpm(|)JSpHD+#ph1=Y#@?Q#}2@< zIGi0h#&)Y4iUwvjRo}lmdQV_@bJb8Zq#5p*XNWW#H5RaroZ-YsSod|2T)SUw+1G_b zyZ*=j%iZOp*;eW3-Ih+Bja>3_|c$x+J;kM%yvp+Kw z-A?xIfb6($;j#C}A0X%G*qG^DTR0S=E3n}q1lL~z1Amj9y_+s= zjPdtGKHNKZ?g)Y|3i})S{ku|@4)e1M!uhe?iIv>aefU-iX&~5DSQu0OhP! zAf#|1bETi6kgwBHMPUH`JnHqm2%Pr4ybifn%E~I|Vy6C69y}EEw`fR;Ca`r|+uO~M zd?VrF5{6!Q4xe5pCRX)UqM^7@9fZAes-F0y^x_4-Ik!&Sh$z%K{TgTN)Ko}0w7zG= zh4lxC{X67%Vz6jOJKY?}NVTs}slVnpnktQXem)1H?-duWNJ)kd zM0~Uv`+5t3uqdF2O|cL6raevd=JD)$UPgq4(7PL9Rqb3^vQMz(D0x?a`s_?=;*wpQ zqG>RrBh!>)Ql+y-JdOBlm(Azx~y$Zz9=;-Jt@6&_0nog71 zow)ik)?2r(sHBMTxwrU2N5xWmi*4oGohtrKzb8|oewVkk3_etb>fzGL=!@uY9FyP5 z0`j(vrvV zOiH4$EZ4=O=Es9xPY_t^7aJ2g8|ObdJ29n03EH7me6$|#Gdy^qLkiR`*NZjnJi~e_ zL|^t}Ca5?x@cHjLibInHS+UXJ0u3U-w z_Jn)SGcUOL5bwDyUaE3?B3IV;LWJOkh<4__`q=$9PzuC-rlzN9fzMrd$>9;8+rMm4 z?0x#8?)+f<#OM)hM5$ZD9!Zd+0B60l+Ux&rWxGt>4vdh zQBYBz8(8X<$QpvuD9K7pua%v>cPKF{7$v8ddP3J{vX{g!cyNw(tNj~(=GBSIk}7Y9 zt?`d;Zo+8t{Bq0PZSqe{H#VlWuFXW=y%}CU1+6}hOK2Z3x?mn56?WYK9lqHQ1&xS? zCK8-8GP1JGU%%cLHwA3`>}aI%A{c}Oq__y-#7EhqIp(vlv=pjk=#Gwim7O<1JDY&7 z*jKZ%sYya9fg2slqy!XaAXmlCwxn6Li#`o*Rzi#eU%gD|Y{9Klj9DMc_%PrRGw&Eb zO6m2wk_av3Zq{kR79P|2|juy5`jrWF|Byh7JwzN8_k| z4X(r)3`MqCCH}Csq~Ixf_3g+0MQQq z2T3)xHy0X5hJ@%}uP7>ZbBk+~vNEtii9uqWwaMD6s;a8wiSiFmpvy%fjDE{4$V^&_ z*JCyeJ2uE;Q@=k5RMW3pa7)t!9m;2AaxujQ>^5V{H;MAhlzBd$|bDZHjB z{PN~J%|Tyw<++t9wyV8x-I1g7}t?TdBIxBP&=G3q#lHX5`YR&5xNyN;Xv#ggKQ}1 z^czd;_&4d%>FGT5!hA1d!owwVHS_m2Aq(rRwT>bspFI6)62P*4+DVt=c8xygGjnrk z9J^by(!jLnT>8ZYoKjOD^C0pKK7FDS6cz>x7*|F`|JSeBleMlRjlJF7t&j{IP7iF; zJ+beQ;RJREy%a9u$Lw`#1W_eNr8+S!&|Kh!c%mS) zod*gBOWnLhg573XeDRVH7dL{NtrbGGFI}FH_t_g?;c_@(=}e5cS5SfQ1ntGm-91&W z(yp%4B<@$*-Q!YUY^%?&0gw2D&;wHAEs}7tWyi(u*Fboy(^tu1^(w(a+&z?S(oJln z66Uw}+eQ=RIz9E>@ZDpURb8`89sPW!D*P~+&GFQvMl3Ts7B766<@fI#{1sMRSNC_7 zIV<}PubU@zLZnmnxN?@Z0vfa{glRBaZ^TOMHdXYiDhbWp<3bONEv)^^u4L$x6*A7_ z4X3KVyu3JUMCKo)FqEtuy7bW#6d&g%=;@(Yf2Mu7xV|0*!~-={a3tnXQo^gNukS9K z{%X#R6gmOT48#!SK0~HIJ|JArmc%cc-`;tQCx-%b!N|h$LMAbtxz)FmLmdptG{Faza2MdwOs|g(_!B z1NjS6z}qv;Xh%m!O_P(8c^v!R6#wfO`x||Iks1>8%W|EEm&d)n zoJd_*+=m9Nc1Vl@tsvs}<)7m*>q3+Bll?wR-aix7JRnvU#)v~M1PH+Cb9ST_KMQ~# zxw!8*j0iw=`}4;#ou zBs%pJZdtfjnqA(<(ovN4BfyGMV&A5#rxTk~#rU~U{b#)4CV|m6m5N{5;r$tLXwio5 zrVej(cq@w7*{=7ueZ?_3RD1O*GiTUC;LMS5{34R;{iP>IMR5Ih)QA@oE$QaL%?lC1 zX6^JSgH)}gxV>`2ma|rY4DqxdEIhkO_0(lo^26Y~Lpw>S{{qsH9pr;*{jW7?n`~@s zH{%}cUvk)cIa*k7CtP5Erl6oWAr^X^sgzwD1mstx5C`VcL0qcxZ9Z<%4msmnd?e0;XcT~@8_R6=} zxeWTul)c5PY-r>C)U|ysPQ{CJ#)WX=hzI)}zp03L@$HfUC&&GxSkp!xs}f{deCJ0vvYH%&c;V`5ghN1B;LZhw6(Y21jQHnu z`)Xb7!9#`%%x^yFA=dAI4BmX3-~?XUN>9JQqxa1?i&F=^lGW*Wbh&><7RQO>=4?BmiAKZ~LlEkA!w z$D!oYKyKHbX=YD$0eXKeqIUsF46R2=fht86TH?{@#b z3g)ry%li`zhlb^A0`#vHe6#hNiMC#Tj3gJk8QZ-8Qn_=zxFqXDWs2AvvsTaet(M3N zH&5B8zq^L2d{~GZ3+we(d}aY)BXM)@I@hBoOYfFzv$*#Y674TU&tJyW((F)zDCJM8 zYuu;Dmil9eZpDgfSh*fc_DR7%@cCtp^YWYi!aQ`G&AUM2fFOQnAjUxUu=a75vMA4? zF;~`=AAlVF#V?N12c5v86@T&b8sg}8tjgXcvL3bs<955y>_9h#2MzJ%1$du$-S579 z^@=EPya*g}W~O^S00Fwc4c(;-AlCnRqFvs; z&;7I$PINBm>P#gz;gvDi9e~q$=^=zcL60JL>S?TRL^$F5CYZ3HJ}6XWA)|vL@ucxt zCZShup<(N7X1go8l52r&Hea0hCok36yeFXY$-Ch$N@vn{&WK~h>A6-nQ zaX2?eRlCuBx19EawSKa7O3}f=_EU7Z|;B=vJ<@L*?Ev-fK*y3I}8;jGoUocLKvST~)^(t33MYZFY40_e)iiF;k27y{E z_GIOTpYS{V!bkeDtGUZnHKPq>`x!4k+HfEI3iH-f)&>YchJz0D=Iew6&{WGn4g?nq7)j*P`+eDg}gqtG4NZkpw$s|=YN6c-l*|9B1Q_8?NtduR-Xi(ExCriA8gLFjDZYG=p$3-+yzE?;E_(Y&rX!s{MpFih*F&Uf+cWB+{S%heV14Mhm}q7M=>B6ZMBI zi8Hx*Z%9(mnM|QmU2J@=*F}dgHbPs9ri#j(2p?|=U*Bo_N1blrDW6u2GP|ThMs&?hFd|kXp(IPFc=E4OS7A`ce>QS^pw)?j*kaX`s`fx7DdyrDh?s7 zr(l&Xd1ApGl5`9@&M-DTgAhmZd=@1`gVU;M%LP zCbk@ss~gcoItYPFCGHOgLrOHTx5UK#rGeqC4?b?ib136WSQ2dxHxUr~?=g z6YqpyB!k9lpL8iUPhgB9@4WnTFmFt2-4Q7<%gFQ+q3mErh>XjzBjkK=5J&3UMrYft zsaClxtgLvzxtRB+T7kQ;Hx57s^0vpC-0hkY*4Y5_jQpKiEeCB6{x^q}zY!z&{jo&W z0NrC9KEj$m!(VZ?xegJy*AD->W9f5BS6<9<{y*Ii$3?iw1l14jMTb6RxR1?9_}5YN zA+rf zn&*zwRmjUrg{PFbvv5DAD=sPM%jg9<8%TEW9{54m1EU9YL|C7OtWt-peo@1Fg7wfJ zt(pXx0>5Yuo_o^ZTqze*Db|j}z^D2IYUS3>4$^Z7ZUvyvh)GEUbF~WRpydT_H!wID z9VA>&#(#Dl{upB6(n3M_BS;jlQB|%5jS62)9k9x=83w}70g4UiM|2dIr8U4^Pzl&Y z0(Ncc?Ipa*Vb}(vNkp`?gfK9Hi;HU)PX&@7Twkg>O9Y#lg#|h|P>ast0R|U1#{jd@pV78<2}9QMZ6nUS7VynSQSL9 zCrJQ?LnKLV))CnZVh^%G#JUn-dP#ZkrymVeu^?a*0Ad$A%t`@T8vLSfY)psj9N{8l zLgV((qVqTWK23Z3) zp@RhA)((cko)@*&<+|WN27U+yg6Gbi_G!$4A3y%VjK{l6!1TD^fzK++Z#~Sq2a;lR zbTnA-n*p(H?e>a)U_Aw9oq?Gd3;eRu9v(tS5eitYc+bzq#hsiQM~7d)QHyi}k5$;> zM#%-fe@}^Qc#-TA48s7fgei_kf`Wo{w{N2(HG#F#UIYSWqHuL9?eGz>B@hWP^wsSM z*v^Tqm80=2094L-5NA$$fy3qWK5?$w`>81Bl983g0B5;7oHdRPGgpdtOkYkuU%j87B2sVy(~#rF0s7 zM8SCs0k}P+WhBWME1CM}lSZy4(qs}GEL(hCoiWyAwk24*!sZ%`T!8!0ZYDiOjGJYo z?Ac?0ikRTpk%f)6o2c@KYeYo=_g4gF1>k#DjWXYapvwV3_Vx8evg>AG#%JZ>{Ur$p~0kg-Re!m)R;&dkK2=b> zkMJ&ruRC7+pdbEaiGcuJig-fTB_Kcj?(gN}^!+QSH^)w6WTf`k{y3AL zN&{^)Ge3_8vrRzSo-07Y#Y}yK1=tMaD{VbJX=XAh`qwb8Sc}{vsZ^xl-tqFn`_}E- zlKGm{Q9^tB`$)~f5r*Fo$BhlNq8?=b8*DmpvMQ&7sm<_!^-m{L9Lwl|8S!MBE z=8VK`9v)Kq*J0B%;7jWQHy5-k<=o#s&)9PyK$F0FkU;{T5k8$4z*C%DW@Tp1{r(yY zd<+`EO=I=p@o{RTngk4EbmQekt=#zUm6ex}-i<*rf5c3NFfPI~G&Dqth)DM>0`f=d zMNVJ~(>aK8;95g^w2|@UFY(AC{qDn|{XhK-KnWXny}HTRozTsNNxVIp6xM0W=Q{zJ zavPK^vxBu?P#>gqb&-O805O9c66nb-0Bso!($GqbYlY3P0MHrdK?CH;ov#>sR7e_1 zl1(lmOm^)ySgOn2F-HY?Wi+i!gBPuzJ;UbU;5er(3lO(M^+&)(nHEz@i!{1h{q+KX zd;~<)P1w+;q^+4t&nzOfFjjU{8{ve>ptW~VrpvZ6FRNjmXycV zm+u3^zPZ_4WJvk8t31Q`w)M|$wL5}6U5SyW>H$4S9x$Z9*j)+QK8$JzFt08yW;O~9 z>PL@TN!KgW2vvEc-Ct#wOR44Em73Q~b`;oCfPFDBK>?Fc;OPbV#gIX^dYb)o1Gv6P zVNKy+1;R17b{`HqpqZOc&@Z~5_`v}(sEKc^8-*cLv!hBXV(j+Ev5r(v^ zLvBueHjt@Z@4*LX?;nYMceKG<7}yAm;^4(Rhp?@^TMx0Ve zXzH)qor&NhRI3Se5692z zjG!`3-dTcyp(l&APk$UkL2R>S)vqD~=HLoqaBy&@xbuefW8E9RXO7>K?hUMb{Pu$6 zedi5Nyvvi3Mj~*DfxtQ&B{21BQnRvpiH<&gvuE;Lb-K4VMwB4jZJJRLOZ~y-z{<+X z8V0AdlvLh3@~J*N0s;!O(8<|`{JTfaGmpHN`NCc*+U>hbJi=mc@G$>?77BRK$Hx~l z<2R~bR8$leF0Qnd6$|h>?R4p1&840{=K&9bshcRyvrw>WofaeXXsS(Nz ze;oA5Oj6Q+D6&%xIeN%T)idqBJV^}|3$Y*fmS{kc!t|)Wf$R<$VnhOf zzq)nn)(lt)iD-@0S>hBMQQs>uu1(cb-@bjj>TIXG{lls7W*w=ltn80&Ibyt?RSo6> zmd&5@9M|RBla2Ub{O_v~e5XZ?wuA%%M$v0!b)mn0^XAQ(kGM(87372$q1X}Kw{AV7 zQ>5umr}W9JKjw`l&&~P~wjFg%%@CK>VUvxC>XJj`UUl{M%0sK8S8vcaI2Z)8o`XX} zCNoX`Fj^U*cM1bgNc}$V!ZsIl1q}|FW6$VMc80)xsLc52OEGc+~%Vo$R%}j zu3hY=Tp9yatzGSO8?vp#YJTM$OoTbz@r9`)=){RJ@m=I&b(I`Su&_EUX!DRRY_LV7WoBX^2N3fsr(S}+5rKe1>)0!LI0Nazuivr_n%`)! zjhR5c{*spmo+yM8W9$%R6FE6K@;q$+5Z?S$UHxk%%I^mWjN(C$Xbem_Gb^hMs7M=C z^8`r9gm4%!a80eMq6AB!jI=Zw3@jrD3zL1(b~Hi}* zFu4KhOWpR!s{mN-uqiSV18!O_a-0||erg$m;4xw(2v7b}Huet75J^Mz# zHhywb`TA67PRRiKdZ#7t96;i#gE8y;4nZ;xq6bV$#pfJ?f|Q^_m3#Dz9Fz}*gkT`& z-{1@gnwpw!A?=57{t>K_^?G*I=)h5pmOdpEyErndgDW+SX}kA)vB{i!cKY%{0IBst zJx{Nwpg^7|cnM1Zm6qJDC#Bcm#2|&Fo!wne8v~eJz{10mF)+9eXirr`!@~7fSC;}L zdpb_eSA|kU#K)ZJ{b9&4TR0pNj~)@>;Nl{U1fV`#^IO38gX3})Bmw?NIR>yAh`}_e zb`<*i*w{cgPdc`TaHx^{hkOPl9){P>SEl5|LP!gsc9?)^84NH2R#sL;PkV2GvskI_ z{zpMa`jh=tOeoUOtj zF$%(Dx*=d|)6nsmVCBcAEDsv~ zBFX?PDAH^C?c28tF^M&gX_K?_^M1&EpsQ9SX6UlOEG)8X%c%3N*?=*A@TaXA{T==5lD^6fq_)}iqcPno4i&{wFFkL`dd}@Fq5M-I%VrdQeFTH?*0}z$3x8 zj=;BW0E{9#J3D)%r)SM~4abi?p&$uKZ6R+8GeiE;!7YT-7pf7|>;%#>n^!*77yfzQ zV`=rJ^@RZ&*@F>O&x?x-fJBydcF3}!`!hqr#3WU)yM&BM8p-0{84p+e=e2|)TpM%N zXajign=aQ!(rZ$u7(PT|Y3c@qSP2w$OvdQh6Eb)%f^nJ7s@2?7BmCMFAS4` zu@2TN$jw>do)Yg!^~EAX(AP>biQV-3?<>RxCyPC)|FUYt<#Tb9(+5hU-336KG7#@M?;eyRfDzedB*%*+hF zxJ7)&=I;c()=67D4+&BlJ2gv=O54eyx(`uTyg;dGjs1)d#&}0 z+Y>~8m?Rt|3m`cFhxYq}mSzS<=It`BdC!aaORySYl#Mhq*j$6hTD)kmI&WV<8tecRMVYvms{)e#a9522fp~NpSc}iZZjjT*+x3AS$##(LBpLt7uyT z4@j-S;J^cYG0=CkUAWq$5@Dl!R{3~R#}flAOIR<(b{^zWMf$B2AeklR_<#bkmvZ|} zU`8eRu0t0O7A$Xp>Kg^~ardQQ&ni%Bi-9uEZsSc(m|T`8{|N8!#$l{BDTZCep>nPRp-|R%Q$)-vgT-21Z6P3kzne zmMh~T%|;}-NV&Sbnps6Ht+3{sb865Sp|O4v3U$9&D!py}l#+rD8f$xtRy|3dIZ-gs z0zpi3RCO#=j}N>h2hw+#ARk}ULY5g6#4vHm3n(CXcP@Ck+ZHV0ga7*k>d)fYnr}c= zT|4K22D^Nd1B$iH&DB1p5(%InDYKmANgMoo?j*+Pa`d!9)SUvUmVU67_9adtVs;ir zK_FFTucn$NISMPz2lFx2fL|cjcMQMQ2W(gi4aPnPEvOf~XM`5_M;MJC7o4>@q@y1K z$deOkC`7;8icol-D$U*W0qd8$VNr|zit3)$A2;cqK{&%DxRWeGHFzSsI&hE>PI4G! z%JzV%5L|UcU}9uX^_U4&e*F`WbcnF}w%1WkyH_6F?hUWL-Va8ZJEp>gQ`vb*HvuNMShtflaSW|Gj*P#Awc&HLUJ79y{Iyr`q$Bh=<$7JJ)& zjMse{$atzi#v~Lp$#t>Zu+vSoV2TK#O;PpQ>QybW{|ZTa0NdxG2t}%oQVnV!OGMW+r(OpF&-wl0Mm_)iwhsy z8vsCS@T(d5`ETZeGs??lU$qjD?^SEHG&PZdke63n94MXL0VvHs4#0tB3+VF@QNYe~8?|83xNsarJm`%y{pdxfOcPCj7_J!Zm0Z3G?H}kH6;TBAgdJi9j7*e|hyQOG-&s zHxkruupXz{4g)@F;nS=X(2aZ#<064eV`OB+28addjk(eeg6afT6HI(;_5=84Vy*K3 zR1zyDV7}G*L2nPHB4K#`{3S?TV6b*{pxd}jJ4Jhk%fIgL}A|M=5uGU_Jqx6zSbo7sIXPFyP>Uh2RIOQ!EHzOoABD#tCYMzaDwvR}#_U zvAu}RdeN|ELaOsZJ4oL)Y_4EOI0zGf>e>PJVV9%GTA}^7KvoCZ ziJgE21B-X`=^nV;+}sc!*cSj1`2o{+wY7=DDlS+_1bGL{6=0ooLS_vgFx7Dh2{1Z1 z0|P_b_C(>)m0Jg<^Y)+_D#Oeb>)C_k{Q6eMz_AUEmzuWJXVQZp5e z7+ZBmPngYwR{t6UP*M4|ziR^kO}X8=G*C-@mC}KM1TfN<(9sV(=Knx{yn9JyM4_d( zcSuG`N-^qd{e|vi!lxtR0GhQ@=zjT62$fjd0r^fH;OB43W z&F`O?2$9fx!Z*ihz!uH?Z-SsC=wh^__=OB6WBcoom*x7Ao~P7H)fE_Z)vbgv>1a8D z0+!Ea$w#K26$o{JwGlF)#*bOWPJ#jQ07_7pNNrf{?-!<~Hs>wHB!m5eV&McU3y=qg zHtsjb+}28FwS&$wj5^1~#RY2tv>{V^_yB$%g(e};W5Yll)}})-;z&u@SpjOw6-17I zuaLIC5Fr>QaRfczaL~sH?gs(M0JL3KT0vnI2^xEbSI6tHl`=9i;=@$hgbRR&-56hS zkft_)tpK{X7!>ZiFTgg3Enms37!%09BhY)i-L?!G=`e-|%zp*&1Du|Khz3^D3SeGA ze1MnIRK_b09b%&G^34p>DK7Z(QuCV>Zx5Ug1Q zBh|v3CSXlg(20U1OagBZ7?=n{L$|lhZRdfr1Wc;{3^%B(>XwlLf`eDVFZY1;NxgrO zlWf%iKrL7x0x51paD>f|Cz0211>4BO#LYS0O)&` zk9sJ3;??y+ySpVl&ZkW-!Mb<-{qKOCL8PsRmsYyM4cLiyy{y@ZVV$g_MIs#=7`Ax? zWDr$azb$yey`xFEQLUTL2w~R+I|%`23=smR08l3l=jv?h!24AM@jo}st^;mKpK%Chp}9s!L55Zy$<7Khm(0bDV&zWxbdTruAn3FYVI5??H?3D0R9n#;ZGk+>XATT z=>%|I2newt9mWC2qei0>OG)d!Tbkp)dlu@`N5pXjoOrb4Jg5&(PC7wOYksDws!9M( zBH*sF%g!t* ztN{QMR$M}W7zQp;*Xar1#2UpX02C*ytZc;K30MG2V91dmfPw_G2h%8f2S?#`X#m<2 z%OHMytd@6h;F6Y+DR#3UBp}dyBoGM6%lN-08eQrg|0#{!m9L~F6Irx_0LBpdvH^5) zV7=Jl1VP}_P*7M1FcvfJZmM^#7@F4&75}e90_*QY0u2biXSW;wf2o91?B~xP62n77 zeE}-x9}Ff7)Jp`3IC@!x|F`_&2WRBJK|5IKT3S*Bu=vY+C?TMB(UcRQzVn!_FaqZa z5OJjN4B2nI+vE{x9#+VG$-2T8l_-Pz=9 z>g6lIt%wGf41h~mn*$^bb*oW=Hh)H_AZ?(hV|y|0Y}n=l2kKtXu!I>c0yC$e&!2IH z4%-kwvEJhYhhPKJ3z+(DrA8UpQ337(=*Pnnoj+r_Sb$#}xY`pEp&_mMD?)d zxLTnt*qO&gFxDCDu??rv5;qOoh=5Lx_fTU3Y!1LK>IMpHn7!4XGq-=p6~1GhK&=iZ z9)#Ww0;nIXl?q@u3YiD6_ai3ty~)Afiv!&&fGf(Z7lmOucFai-ki|hV2^>|!U~338 zHDNkukbglmj5YNBueku8f{-r=Tc{wA!mJ~Y!0|7ZI~F>74|KGjfZrzQlVw&_;e!l@ z7IeZO5ZD*SLn)y!!I`hG?Isolb0<_eAg~UdLZOT;-O^&Vza&kOw56mZGwMgTOeySG(yF($igS7uBUt zv049lD@sgyyxo~O{G>fz{C)q`n<#mCK0;K=vHw57s_GZ;ugz$e2 z>k+^Ix|oE8Oan9>mCV@Wpht!c8XQU5+Oa*6Zn)TzUhZ+#@01uF5)vS34+U5>1ZV<* z_8CXy_EVs`-WAr{x8oYE_uA-WE-n2x-UrPWKH?^a_rq-Ou8vQ@+@$O2E+8PGx3~L? z+(^tHXEz;BakLIFo%A<>PlcPQLiyHbnmRg^ad9cfp@us$u9ga3H>Zz*XxjAb3giNp zD%P7XTRADu$HvBDVq;h1jkbzicebwj`lLaHz`A2dUq#n4U;-cf=1ni(LDT(IlPeU~ zYjE1@aaoI@Cqu#n>-`XkdD=-Xq5hQ47frNqSz`;~RjHd9S0Q!|ZRU*A*iesNIuaF49{AM?GR zA1}w(7(i(ua$ko~JMWb09{61ac*actYm5m{o6tj+29nKlo$V)kmFun$1)pzA>fdXNqc{y-MOjEbfpsH*_L*R9cb(Cees zbKBxSI>UY0`!5&3zF7~exrG>Fb6JL|sk!)WB%}TPyO_V8JT?vv?hmgc<7ZGlN$Gju zbZOU8Gq7SNGHV4KqOnzJ)tH_b-`0EZM_^!J)+Vu=8kyOORuQ%eC_m(lYBsr`W@pK0{$R*@m#yYgzlak8C_0cAoeYUwiB`rG-`i{RN~ zG|cX+hif8S$;gW9rj4ICWNsTz{)C4MGE<_)qi(;6#dhz+K#_kfdbK}40=;35I6Gr4 zDr(CPC#1Rt7=*Aep9yB4^8wPphP)uf(NXABrBraVup80Irij&>o^}sVvv+*pfQNKP zHsS-6RT$F2QQNb|j0o|xF- zYzlws=i(8syAgo>W}-hXzXk^78njWQ!JLcU(cPbMY3H0shur^A-3qwZbnYc#AgSCR zqshw2L44Z&mz}F z5%VWjnmvZMbKabX!gcqx-iyse@=}}s{3Ktm zpn83?+LW*St7x*b=q=S#D!3T&V0dXdHw;oFRTcd0{=N|vWrRvGxQB&iUFpP@Ot@>$ z8|I4zP!FUSbiM<4CRKhu3Qv*%mqKpq4JK+fE)l@6;BQV}0vGD{*kcB|Y7E#@xmaIG zNffK>=?7Ta#Z=!@Q{&;K?$`vhT?qZ&b?oHmG{HpKHE1Z)cM`)^n z9{+ASTl1i!XNpNnqrFc*E2rrfc+xVE3m;rxfAhlO51mshCEuY(p*N*)k+@2nnA0>{ zi-te=Sal*HK|@uHk3|qyB`L78Yi?*7gcLM6Ts!Lz{qHff@*-d)xRy%|%Tdl%Jlv~Q zR7isN^ztgKKnaTuZhaeioGPeP@6LER^;vdg_P9xJU3k+J7v|n1iWA3LRxdrFZn_>XhMI4z+AM-vz9Tlco+x_vo#`2Sy?hyRxA+j)k^ylA z^6cwadcjRNCN6xrt}asncftvg2j-gsyTXPd#f+nzWSb#X_2r5{v|lQEQ5|PHN{7=X z?2XvF5d#Aj&J9^vhKD2K(eKCea9pmpOtd^(Z4C)Qy;fs&M=~sQf=@y!XhmqDkVBUn zI~zg9W|h^%Z4KqtPf9WeG(sABWG53fW6x6+O_Pqq=YRJMbQ#$NNcW}# zt5r0So(8~}?=hNN&g#C5!wab& zT|@|wfoFIm!zpl>ltFK^#dIW*ZvFgmVq<(W>i8~$gK4fx8Z%5?iwTT5Z)vEPyq5z_ z8JH#BIMv8Bk0bj2`c=0J>O)l2j?zlwm-;GeF+x|5kf^KlMFLWtDIgi(y`pAkFN&4E z^v_Vq$4*KJIt*tEqGm>K2d%OC8>^JtQ{@y9LnObImBZQ9gvT%74Gm-1?P$e5$LenL z=N+Di?>bIWJ$;5GRcjTPSySV#4yk++tX_iu@Z@t!gbXmDEoCZ|SniHwja1(%$jj6F z**Aj*fgOZ73hD3=(3>f=xmx!J)pub?R8%=xWe^gxUK8ZeBX2m!P#R0mSMCR_EUCq1 zM>DGJH@j!-wA&~!P78QAzVKASaf%=|I7?#ZN$yyrY=86Sb;}OxQ6q@1*j`-(32jt9 zK7a}4)&7W~zc(iJ3k~}-Bwt%wQ)b4BuP^w)H;F8o@J6B#JCniVKRZp1S65T?rzusL zhbMUczK{&`$1mT>qJQHiNzti9qVl{EdxHIL%hH6Wla1kL#kcP!-@GLd#jej^ttN4f zB*tlotOW#8*|kB&!BXj_^!g)=td%KhBL;tabLPKzf5*OkRuP^gmxGy%pIuS$Z30`) z=d4UlYYz?JVgU4p`}WO^ijNQR)vH__GBUqkS=K!VtqX*b5D zeRmo~snFJGnBw$_2nBnx0T0O$_^H*ImVBOzpo2-i;=aF61pqMkZ$$hKKX z?ooO_U9Oq6*{U2C)7Oazh@nyPKK^c$U;t5Mz>ZG{k|AX2=zX{mNzth8GrxH(9`tnO zTe#xwAg}3XL}|g#ql=W!FXn?4t4YAfAA+s&AVu-=jM6*f!z>T+nHvK@ZcT1oBxKVf zmeRe%_R_uK%bI?tau~obZ6a6x3ei*7@m&f)Ou4>2!GvO#goK86<)&MSYH08c7~|Dg zF9M2^-tGw)B;aZC_p&&92M4@eZdbyU?s_OqO8^wD`;f=L5GQ>~A)se zWD0yW4$t3T?BThSqV}Bqlwm9P!&6E&t-d0=_#}XCqWW)vu?36!ue-d1ZRCSoYaOPU)S7Y-Y8kw8SvoNA=Ta$?^kTe>+~ zeQ#}EAJ@G3mKNzNzCGHVf}wiB&B2D1JPb{I^sAS+YV$>Qny1|IS>#Xm*$*|F5_^q2 zHS5==Y$yzf=VBAvYKs%=n}TyW4yizQnx}0n1cEEBh)9h?{)E?V@c|`)ku#6`);S^( z^ek?oHPQ|Gm5Hf%REUF3frnBIyHW-3@wz5_3+qV;t?Bc*KqcA}6xM~d~39xP=)v1~tYMQlh$eZ^b!b6p{83Vdlg(09mC zxC=;%=@qk@E#v{ntT5oYpR&O8ftWb?LBD>9NClc)AT?A2gD;wyY&32#on*uea zoJN+Ly$EE=Sx*=7w#Mf5eHOUH{kVto-|PEsvVt6c)~%9yp&>VRK9c)Whu3q}j$hsO zJ##FZKOvz*lPHj|LN{yamgo_$x4|39FfeoCdCx^+n>4y>@q(S8KrP%?0BnU9@1A+6 zB_)UFRdHQDdh{~0B7il{%PYb0=KK*p>W?VmU;C%XC=ZEfkeYZggU15!iVWI035wH? zTfYo?BhwEWUxL%-DF?11!#kd&5gpw~@H`MvV)K~98+sp)({z5k`1<-4W3dVIr{3gT znwkll-}Ywro+;3qQ7QxTYVqQ|BbO&Q^W`ob12nt%ks# zun(lOCCk@~CY>-0d_ku(&nR0+U@{Ec_TVVp3igF(b zZ;n4oOlz8R)a%Om+&`OMulJx}^*IaEvW|UR!r^n}PhXR^eqXKZe-R`nXuEK5-$H}# zG-^LBW*WkCH(J_v%;FqdAn}U2awC+Ie4!JwWLCM`#@KXKKh4SU z5+y;Pp-f@}*R!m`i_mdPmfmCIuI;KYG)#(3#85 z8b~I!zGL1Jpzoa$AFH@OMDaPh-^k>{l;x>qbe2P7&EjL6qR znP}vC!=-(mwe8%-E3^KFsDw%k)5eMtrki*s$}N+tLiO6~g>#SB)sMAiS*_Xj_MR&{ zM;CLMPW`qoY2LE@NYS|cOnLMDRbPC#WrJm6`Mi_68?S3a#O^x%gm`n=ZmRt}onrD# zPg?G(fFPzIV+*yCn(<|I0 zob^`U*!af5tL`zV26g55HaZF$Eys5FALd+&fq^dx2$8^Xe+!SGjZAxco1w_qHR;xi z@tGo0Ivy1c$j!sH{WC6I5HGe6S}$E-GX_RA1MI;EypoR{M`THf^!LtE_MBpG-!2j1 zBZ4iuvf0rvN~vn`AQOKT-GqP;tT#SVyZKBmHlA+blJ6$)_dKb%JFjsArkm*20I8h& z&Ai8cy_0vU?fK!FbeoD5sa?vaB^Fvm~dG+$E8WQw>fP6k5@Ldr(5}gVb6?h#*X|~ z{S{#teM#22-(1PA{=OD#EtH(nqE{-Nbp5^(1Kv{jLg#3K8u*?5{22pbF71@e&ZaWH zv;ngb@tYzXD40r z=WqS|K!OUAOE6KeKjZY^`xcBLK5)2P_hDJ=Xeygk!{<{R{)?$RmHZ4vh784g?3r3N zzm?VEXRbJYk)xvDnA#<_r!NaC5l8fSXYF&HGv{{VEjy)h>E&l{ATa_uY9+2Bo#s5Z7uVwqrCdB`Q z-jaeb+gH`BzCo*If1Fpnzm#QFWjd!(-L0X2udvy^U+J#8SlRAO$!(#A&L-F1Zb4wC z#PY_JEw#q+jg@vysR!EX+8Uc`=i+cKmd1SjP>Qt1X=Uev@BG7wC8e z_zvnmL7O}$8>v6~`l*ugyMyJQtKGh*7efp0J_**&+CMB?kk;JD#NWRAy1ju-9p#o@ zKV#1_kuzB!D8ekASUnhA*qK?yYxBGq;d}o**3v~ z-(|I|fS9ht@k}Ll9`_S;cC-gIh97BZYCY1KWr!5o!WU@h65!9eUNE?0k%t}6H)>Zq z)(qzeB$?pWI79I|uc4nR(~QK{T2(fdTZ{2@>%@relPWCO2hf2XHT~ zT3?Vv?(XD{?#k8{zQG|nYtVOo0ARmD}FbE#fg({)$?PfTTz39I=-FxRq0gQ^(NamUa6Z;zdhyD z)C#9BzrU1_T5axol%$hduEHQyf6{+`O%oSb2)zkVuS!W>Y;bBx*tYPHbM7H=I+Max zpTU;8iD_wIa<$he}XnRqGH+E+NJAVY;mtwrG zRt}09N-QD9^t&8jm_!&zb=Tdh$9G!v458pbFQ<{bozIf9sxhGF#8xKaNX`WNP9-0< zZvo?PVr=XKyM|vYPmc<*H-}1bkp8=;?}M{QXLc%D0ujyc9$`q95miGPQ!bzQUs9ZApxifm{bUZTO*7=zxuQTrQUO;xhc#U#P}@O+!=tZV>V1I1@#UHmu--< zrl?;q8Xev}%PDg{+53Z3TikrRmXLS@lKopSgp@xAs@%mH2eT@Jg%w)*_WJxkRjz-B zqRvQ{Oy)PInM7Ib?mgmn;J`vX-_F~^h_7th8a!x59<(3k$7n$HNAEu)Ge1ClvEMYq z?CkY=C*2Z@Ps+@`7CB`|zHDBJ9N$fczb}``HCE7he#`N>Wo%(8=Q6-TjLM1mRve_bIW;4V8G-?8R9s4lle=R7hh!|4}_z*vBK+ zz;jM!A#;;HGnD2wRFZ!_gltw{7b|b}wXc;MsWuRLIZMej>xRBN&&xN{T4}FFuk5Yc zoYP>J5;Eha>*&ak0{9InkN8!JYdl)s!GWxo)paAObyZ6K+-!Vjy1z^?7SS;t4sIo4 z5XATqBBD+aMpW<>d+jM1cd~54M4U8mR~9EAe1=3qAWa8yzc1)WIu$lltZv)SK~bmz z4skBQWXmTA!O+RkY^HLTBQcA)O9Hjhdon<(?Kxg&6XYw!2OcC7U-*tNfmYLCZzd8b zk$8Y~Q{>OQAW1k;tKY{`@(|#M3Ai&a!R_{pfi9zkLYZQJV1HZs@aJ@4+Wxtd{kiDA z`P1Hc()S9^=}vS@Yo-Iw)%&KLDEEgV=6-wBX*-P?ZtQn`FD9ZiOPWS{wLAYqX6y-v z9j$!JNmM_t$=>tynzY=swEXE*WZ~Hg$J~;iXDaq|KI`&j1M^Fv4$SE_3Au9(qm$-> zoQJDh$vpK<^(1w%!YOsv{sBYs*4(M2&B(K#0p|M z#vg9miK^hsA|d$QoZ_6Afb`x5P>-{|WuT(@=jZp@-C`ic?JYzqR%@kj3o^1`493TU zW1zM`pPc^|%~3f0G1)UZy49wWYr~U`Gr+a}0dSSo7^yTHV!oo1k`91$K866c?a#sa z=s0{C!>>uMXy63J4IlY$<9Xe#KjD9h1|u1V`HbCmYcO7vu!_70Hk%lb4TXWmGUikvqOLRIyyZD^x`zr?bWxmsy`>z@`TU#-(=e+vtaBg=tW#x?# zv{#y$_@U8mot2KouyLK&o7t^iY2Rw1^VcMe!Fr_?xiXcA-I-iTl$&4Ho%BweXh|A^ z=x!^0*4omrnq!w0boTyh9hr}szG{Tb4!9L)&(x39WkxF|wr8wYus%Nv9`p2x4#&eG~}w9&auTZkkmEjdEpo*C#dk zr++KwZib~gsSlKf@0H96laX83b6CYYB_G>XK}{T`t#qj{0Q3j9blG5fL2Th&JmJ*k zY3YqoOLQ0bS&+?MLqx|+iab?)=r>yWah0leMuLvN4!_iTRDI`(`m@EMsK9x<@-ZI% zhD{rlXx;gi{+_PA{cig+3rZMe+i%2m~NR z3CRu->JG-&#ylh}zI6U@-3hga-)AB3l3r>~Tp1vljbMS=o&~xAD&hBSV-_5ttJlv1 zGSbE1vePNSE%X388tyety-w z0uE~Wo-;HLP~wMSEW#yKemZGOX%!i+;<9N-VJvKY%{9}(%hsnL}pr1 zu{H)&`1x8aDtAFzNW$^+lBwFRRgJ?5o~rm}G6Eepl#Dul_NIn)Nj7f9Hfk=g$n8pg9~p19(?XT7*0| za)TE5!*u1F0rPiS+vQdFKQ}sa()brycYhn1U4J;CaTf0X-fNIFGaY1O(N)v_Qcsuv zoLtPRaden$oU_p~4(+O{0D;43e&b|ONt=m33gR=Mna5zdQ%}A4tp7J2z1e3L%A|Ak zujfwH(X2JL-ns3c)q^LyYHCs+d(JN2*PJzd))@=jp@xi;-QJL_<$rutXk>A`XXj-! zq+ai+%huYUc7amkII&feGHxw(fm@Q0+i&TY+Q43+PFdKnyWK!hpCDk?NM0RiIX$`9 z)6vW`ZO)%`Q^ne7Rc#^e#{8k*qR%0>=XdHta8gp*L-`jsp4qe-&}1V{N(=_&dszD8 z_H@uL%{1o*#S62jQ%KdZk1NzN3)L5&uz!E4@qkg5~k&g$LUT zY4`{cqZvhUBdf|N0*v@!Q3vgLiXqKR;9Ld^8JykW){wJwer4v;jXvok`^;c-SU$e0 zo0x7rv7`4iH6*MV^9kQ>3^*PD(ui!;xF>*E|0_}}|Gz=++=+8wb-<1I=+Wx=EWLxv zHThr|)9rkMrNGJ`iGw^_4IwDF{hG5~Y5&YU$I5=;WP=s^goo4+`kZP{RdGsl--6Qm zTAxcosv}w8i|(1PNOsK>wHJ=I>XSPmPLmT<*bNmPZhreS_fD@!XA>M9Q*WhP3NO%f zv!RyGQj_Ash)%csV&gcQ<^^iA<{`xDJEmaihv&$NuP=A5XoNsO9~%i&BVdq^CVu<; zd~LfcSUHWZyt*htE#MKaNx~J2h#fc?IEds=JRXbo<4yYdcr1Omw+w==T|eo@uoR%R z^{Tc>dVh=r~pnCpXt&&Rn%sdXQZbsID8j(NHHYB?f zq4;2;-~2UwCfSL4DdCC1Y@?zUWq<(HslbO}@q=Yf-%jA_K*hs@DVc>MF#j@wgqtuh zFgLX%SCOI2VM`iVjN`I3pQc>80(4u)e$XZ^J|4d8)O1y9q~u>NfP<0yz8xL*geouH zf)nRjsAwp?6|N)2*LQ71{jD?zFH&_&(p>VahKUW=gqORka#LuaNA1(o=QC~#F2E9h zTK;v96IwdQBYqacPDfQu=UlsO)=g>kNY%-)eS{|Nx{Yo0^Z8zpds`8ZdC38l3k1?~ z=fq?@a7hojpier@sk%I_j5-vT0KLU+UTaCSm%j7YMZJ9$4MfLisPHO`0Jr$DlyF#3 z*7kv`Ls6S)mQ#Vm0oTv8!(=q$Ynj4U%U)zAi*s=}36$h(L4n%Kl5+=?m}LG1O##V` zb$*>3e(6UlB#Ho#n3`2U8F}L#g-X3L{8HJdnXkO?DT6QtXvjwl^!t0O#MPDPY$8iR zk@w`Lj0Ba8wCT&g?RNo&2^dlsoC_OWQXUWA#)GsFkXmT6a60c(lkt@y1LY;Yi2G63 zI!Hx;&tlC)1&~^{qp>l+va{1yQAz2_Q!;#EHkwMl@bhN-@uX@b1}Wdc#P2s+A-h|> zzB5q*SVGP7X`hFNgp51Btk$z16=pW1ALi3~c`7+Mw1T#Jx6XUsyZ!!n zN!KXNjF9Q^y_x9|0I~RZy~*WYnFENZsJj%u`*|D^x%h;6M1CIjq`viubVDUa4Uxne zH1OguUp=1E(}6^-=n@5RLV<-uBeZ*3j`^&>yOc;<&k_Syca6u_GfMzPRJA|-fjwrX+KNal{b5H@Z;PHQ1|q-O}d&a z7cn6yHTm}7UJ=yhr>3WrP~e_^+0T~Oa3I_`E0e^L?Fg;W6mBv~!tZS{2m6+dH_E#1 zg9!IQ>tcb`X`CU?pC5@o=dF?;-6MABkc>Y0Gz=Q4r&R)oCan(?N(jhWEqQsODj#US zX+le&aSnGm(+O{nj)%CGVHYNS^~Z#y=4KuRD;R$uX!M+UdytPLaQo4L@{;&8AqreSFP*Yvw*!dxB2p$&c(rEM z`FmyB{CtwqH2L}HqGn~C@^D9+0a2yp_rF#|NXQ@SG%sC^FQ#-*XtP}NK?=0m1`?9PMqO4z4*F{`{q@q2D6)9&e_9o;CMLW+ATCL6P zqhBFmzX|jGd*r@Z$>&Z0NiU|o1T>z>dX^tG!r6G<`eU?sYAlyiaK^Vrqc+GlYs00o z@Lg2}f7Vqh!oTh|0dC_2I+X`4_#iBG8LfuZcxZS^gdBGl+*qZdzZgZ_7S~sI`ou5F*Vl|A&XMNi}1m~isa{v5M%sVGev2Aa?r{cu~6aKUY7w`U&Wm zP{!hJb=t+hapD-PPv;yGP8V;`+=bq%zduaLGdZkY2(mOw{Jzc!-Qfm8Q0n4sdDF6) zWsShm$BI>2+!d8fvnj^2doj-MmSF1NpTQqY)ZbSaOHD%2ZQrA&KSd0NV;7Z`*jouN zWvIO(VLB&E2|PV@0JI;7W+@e#>Iz2`{rz0SkM0($!7^JxGyVhN8d<108Xoyc7KDXD^ zxS)E>Y?pzdQ8!Ot1%oI;eOwy z_)LQ+wi#cbcN0`W99%I8>$+r}iiN7RsQ6lL7Mqoao<0hXFu|>{LV<^fAYd+>B`dl7HPA!b4CgXfP0fSjuMc=*OFVs zjh}gM)v%DBA7_=geX-p6Xf3fe>%ncxfVf50tjGLCh@S#d=H^&lSMro!Gjw2}wU%y< zXg(0D>j;e-_<;=l#GWk2z%(|>rUH{&L-Z}{^bYzQ?Vzj4cNh^9u=8yfT<^+VPuyU4 zS=yV4KKeFMiI*1!7%*AjRC!||QfrK3lN2R0c9 z5Ja~-$KR=jOmT82Ze@vQ27NYl=i=JNns5hZ0j137RyBR6<(R@Y4C2!5^N(62DoG!XUx-~PD<|I{L62uP|3%mRud2A(KH2kAvOAfZCj#)- z3BnT5*>Xrl5D)%Fg_KvI6`n$7-P-oQ3BpbaNuiMmB12r685uE-nmHDBb_I&6miP}t z1G{wh^V2=g|0WkFp6kY&0WAwbj;vW*5XQPt4Rp&R!qlNW%27`*+p&yk2rhb!*Ca~y z9UbSGvlj}@!KAWZ(AOg7>0z15`JH99CZ1c(nryi*X#}Ce6@{M|l@Tu#qAHmy8@^{3 zb!+{$)Wm{RXw~4Qr9_Mm$U@rm9LaJ_wu);$YIoZ~fAIf$3@3bEP$8Dt7A>;hW?8En zoRN{?WV!PXub7Ums}9gNi?lCTfQl~ySojI(@(R)*n1)`$2=!Nt`pLg|4hDr%1n+c$ z4VCyipX7)B)XuI!tZ?>UsPSBTjW%mOE_El^C@kkA_Vap3$S zHlx4SSFNzy<;Ut=#5^Xrft^ICw@eRL6|Z(hL>Ogp6LBwSP3ZB*9qFdxpz$5?n#-LG-?qeq{4qnNsT zrJcwWQ>F#j@>i#Sj7j8G@mL?2mHjOnMEFocro6C&8xv8oYxHN!saI3@KJAK6|NN%0 z%G{O%&kS<#Mz9}ko&K_D{lPFH_nmC%&EfJkF0CY;f!lAy#vS^cz~$EZ>OX~;Vk&W{ zcR`G{veshH&12$q1JUJZb4=7jp=6v~F)UEpYPGEoH~Xnzk!fhSpn`m$9i;o^2AxE8 zH~XPeRc5IZ9QK>69li+eWsW>Uyqt@$m!@AM4inuqT}Nk0E?6?38eDzU)cN;^Yye z&NN`St|YYbRvpg@ds8RU8pdXINZ1|di0Re6D5EK&FbWk``yyw>UASdUC;lh&0UibUc55%lDH^E_ZrDeKF@y<^Vb`ck~v9HBTJb*Lu3< zdj3Wyknrh3!%I}w6&P2dQ{rP5eB@x zXm?!o7tg+=rw>zO0zn=o(9Oy5-RvqXhtl6aij57%M$rkAVcTo3ZN2mEw5m09av~JD zY$x1ZxZ7z^(n5sy6@5BRQdM-INi3Qm9dI37rz@-d#ENP6PJm81`!F863)|60ppA1_ zC?DIw91%Wo<mkA%?SGADHw)IBm029{Q`X*ys)N>eHn4!)v_mbQ0|G9q%;i zi8WDN@Ui+bN2*rJH9ZAH2P%R*Fnw9??DmnR7fSjiUhLdUie3Fu`C-7-fCYv2JEoUv z*m}uNZ46==oNNT!b)yZHSAM}&tE#%@ zKF(qe^|w+JVuA_ZT1p(!;eF~RWdya!Pe}$yvn7GiY-f$(^Bt$|HV91ztka6(Via3aAax#&!FeO^}KN1D(Tcr?FmTEkt&o>a5zL$}rMDl=oSIMb)%xC`0mrM9% zp*S*--qn~*6G9|a+uph>y(Dqh=Tt+(aIqehF}nXHPQ>~#Mf*C@rdDH~QUA>M-;)vV z(?bM#N@I(eW}h9cd=y+d5X4%(Yjo__c|)FkTD*iaS1xaLAzf?ud^;HhDV}SNuA)h^ z`8pi9=zjV7^#`aod=vGqBabaMhj6C$oNkX3hgVx}c-}cV)f8E(6jVqs3SGP+aj{e~ zou3NzHTo9fvlv;~xj57hZy8|E+*cx~Q7g|_!Fp_BZO!J_PQ#zz;YPQxtgxfEY1T+*v=#)~u|AUl?~8 z**>E;%;ET8)Oh(mf4@h|rU#RYGlY*88{Fj0SCJEC)!KLOBbC)6Qn^AvYiU&vY4YA5 zPWzVjZPI%$qhprrF<$TVfHp^5$+RJnhxbiHlLFUwcPN44j?;;~&)-?DsJCA8r@31R z0z<0BaqaP1Wp~&3T+4STsrg*^&fVOQ=Atbg-Bi)*d1d-mVk9^k7p*?!nYjI9w>8k;HwbP=>kHZz`t0%oC_Z8RT@d;~X zL-S1~r-Us23^>rK$Gb!-vtED9zeIYbKe=_1 zEe$ecuy|pxnBUsPEz*E=BjEK>KxALL9t&b~$T`MW^C!SqhfwMxt8v}l*V)6v6D&}b zHUS&2-iCzYxB3uz0bvNwEo$;b>f<6IU%btxcujW?@kR{D+63lq^PdM-dK(pY-K@C$ z)c~t|k0!j$KD!y$eQg(!qm~j2*YVMYu#)cjdO z^XcQei{2>DsoG=264!fKi?pPor0P}6$+;j;sf*>WrRkw~o42TfV|%S9bG3)R^XU(_ z?1lXYZ+H+EAw0vfD_=KdA^Y$Gw2 z@Sk6FPb!O%=?1S!*1PchAhIln#N|EP12f1FvCOR$u4ya>bo0WOj?LFMv+)(jguY@k zSrr1sX=ywWQ4|WBvE@SJ70e$Jq9uQ41zaQI1QT@k>6iXEnN9E6E=>y!rW|CJRdX)t z?u-_Wh$lCJK+?QV{ToU@x?2k_gf@-g+x^!Orn%80?CPa6ZHsiMb?v?={qBnIwtq-s z=_}ULFux(aJLNZU>XSk`!hb<9z}b@p9^EY6SR>GMtCo^r``XgH(`PZa=6cT z-P(kbsed!NcWowg`^UPxVl^VAMqqX72{x6sv3YeY#gv=z9(j5hLI2i1w^EzySux)k=k^Flys*cASK-(4bqLY z(rmgLq`SNPZr*dw|Id6e!#EB&^6cl1wbm7DJ+1XGe8%uA;V((3Ti=VCs@~pqCW8CX z$JFwV`T@GZOSdkdcO|<=ZKg|46hc(;wx`BXI}`rI@^NP)n1Jr|D0c+vvZocced?{U z>vL$mKRR>G1yY^PtfvdX>GD?8Q}@@{x+l2&w;QOn^_c-5KM^*&KEkd%9`#h$D={Pl z!-$DS^xi*-@DoI>H;q!JzahpP);eM_mht5h6Atdg6}A3csa7Sr5hlV=tw86OyWS@0 z#6#8{vG?~_Jw`l~^Z@f~zV$4GIlGIdT$NK+nZJo53+PHXdwS9`c})*!}rB@|^kn1<6$y$CBHhdpB$sU45(OM-E8)%%2mcYqAJiq~n!H zU%&RvNcTAx6t``Qe?BTfnb}nX>*dXMoqBFdiSK+@JJK3AVn^6*wUO@DlX_WIx}G{1 zc9BFAInq{+Rp_sASPEV?b4ACf9WV+!vP!3Jj)x~Pr4&UK`escj(4|w&YGu@wQciW@ zDdFrbRrI|JI%uBG__nt``0Z%l9+-U8w5R=ZsI%MK31Z_r`a;nHS5-?zOseuo#fVK( zxLL+>@pXo^M9t?R3Y6ms6qj#EQD6O6mWdB*OnfLT($z(D)=nnn!}azUGyh*3b=To! z55(|r($4@h``(A5J-4rY5zR))KB^vN%W;v>c#Y_YE#7v?uH{}FzWg1_*##u%n#|S68d6CIJvjGLYVH@;Y2YKv|Bh}p%vm7Lt*{mm1lPs$G!BWKATI~m6Vkh0CwLMVj!HaU@jA1veAg=z(88=o`xj`r;B%!oZBFZ1? z#4FGIYv4-ZZ<~zs3oZLNgyb8n==J^TaU3#fZI|HN@%vbF?zrLvcD%WOH#I-IuX+fY z4=jy`wH6sM7aL?>?fS?Z-QR;>Qo6_ZX8b$V>$)vXjc3`~W0L=~6|NZuPWh3mpyNov1x}@ew^wROCEDp(2JrI+fg5K>I6AuRK>gyuoa$I0FV9OU+Y zL*1ainmipk!ZzIv>Qs(msadaXd9Ry|bq2`{oPsCGYHu10b~l(VX-K6vg~z8uBU5>? zF=>FVYsISE)Bc#|+O*encFR2ey+Lx&?u(YjarYDv6Y4_WYD26Kw+f-g=xXCFv(BPksknw{T?gKM>S$k#K6$d$PW%ud zji%dB_LqVF))hwY9oVK6`tC;hB!L&wV#{v_y2PU$gVaZ48HvJ0E?LaCcD~G9LiD|d zd@FJB&OQ+qPd9%_Xs`KBbvY>&KV|lJowZ1Y$JLKG3%i64PN{2ixgvQfQ%o$$dOCw3 z3^gR+_ix2u@`&zOupVP8D^Z`h3(#v*&b;DTt8@E3H!R8q+k?jclVgom63dB-lKsFf zuPKrk&qLoPxPica9* zAAFQ1-@>F=_)uxM(<1H`ITGXi@aRKNZ{1RY#J_Be3Zu)&hY{xL&aXJc&+hU$m4Cu< zIp?Z=aIb{&@}}PG)E$upa-Mryh^cCqcdSk1cx+_Ql8z7Af7SOUNWPSF5;0sE#C{G%U>$Se{YfR_# zc#lbhL9UlJhh*Q3uOvg8QL082B&#yROQ!t(+~~;pgb=KlSJNTzK#rcE*R8_D$W_Pl zsg~EC{AUWpADzq6r#o9)!(a5}joyE@&_f#trFsfwJ)c|vD{ys%ken$P_E~EouJS#K zL#|2dLq^CapFTeI{>A;Iss}#z6UDopNv%;);QOoGF&DpG&}Vo zPiOI%;~x)gQ{HCF6MB2G9uJ(2ZOH4D*5k%}eZF2xYMJ5fR4~PF?k6dqx9IDp_Dp)i ze&eL?*XBV>1`Xapcwz`!^g*_yQ--}IRf{b?P=qbvBHSo>HfHFMXPZ;Z5f^#9dWH3O zfxn+$TbQ6cS*;;*$5XCkzF-dIUJm)tTC%uvGGvtFhZGy{Pu$4N>#6Hi7DI_!R?1fT zAe@68^41KZ5okSKyNPb(2vHR|%=gvwu^=IPjT_LvB7gxcv<|Mfr@i6m#-p z)XH%{@A{zjO0j@cbi_^d+6lRFJwl#%p5Z(H&`w(72MEri0T#@tWT{H1f0+(JI3AIP@6wAkSmyWHbuVNeMdA`~jC)Wz7iXRL7l13D3JpV@v;6+dy(I+O#<6NFCkpcfU?Zj-j zxF;zFlpx(giWi1S76gfmw!dR37-nmz%DER;dxhKfUzBLr3fI@55#Q^JEU!iTY#*OZ zIJM*;Fl^Hg?y1TZp06}FNisCJwKM=qVcT)^F0Y9mHBGNI?wMW^Jgmya z)fKIV2fb1qwq;p$6;UE3rDt`N(Dr&u9@b^k9S*Aj)iP_`s4nr^ci|#fBVkmY0VzxB zUZO@$^YAQhN>kpiij}O1A7Z{!$DjOsUo8(3lc6RNGqKSktVRAX9c#~TOWXe)*Y8X? zI8}$-XABskHf1l>iJj=%^)Er>>bfU$ZlKP-sTx^vC=3Ar!u4bI`@cd8ji04u4fz(O zNrRoZVBGj@lQS}UIsYOy3b2GCpx9K-ZMf;^5J*$1;o!#$T>Lzqgfd3QtF@JSFh}3N zEo%tK+Y!!vm?AgMeMs&k643Co7@?x{jIW<*qKdI6qI(QeCi;;(z|lst`kpMi>J?nQ z3JLp&d#&R`>!cZ@i@-pFK8e+PPX%F~0ad#>&ix1V5h-q~VdA;z`KE%|5_L--hz>WO zypZ&}Z)3j$9d)OqvBPtM_AB2+!`uB9LRQK~BS|M%4rHQi_*{hB91>^@Z)EiEaWrBr{ZIp#IX0o-}?7zYaPkQThBhc zhP=l3*}7XyKBi?`U7Er`T-KL^LmKZ)6`+hmPV+)W*0A#{1-OD=!JOPe{yNWt>kA+3 z7-k_fU61?^{p3=@voE5113&=>11>ipdZ9J5VMgowlv5dt1fP@WBGdYGk}q{v+)7ol zjuZ$T)ke-{0gz?(NCVIru}7UcyaKJhUh9^tDXje3Ezn`%yy7&j!u0giFcY}n)4V^u zUm@zcX$*Sv;V6GO4>D-Gv`73WFMyL>!&Ij(*!?|&=zaoxMevXu4L+T;%zWLm$%a{Q~RFkiKbT$2C3^Oh$@N1k!%Mi-ozpn8wNf+c5cZjM@_@he7p zaQ)_#hdi!2-$<52{g*B$2&3Su%UEVpi>{fea{kk^-KPa?TjZIBzR1O^tg;2D{4rZrFT?NxXfMqit81I|;g_ zomD)CHd`HT_T05YEJ80iENlQZD3d56s2*ecF+t?#CzQJLqrS#JJ8{Riw1(o4%gbL- z_gd1Ou4huq;^0*73!1nvNTf}9WsN%G7|FBBWrPn{`_Sn|*$JW2mtgWqG zEiTOL;A+2Ruvxm(>%0by@7@oc7@i2NyS01a9=CbHqlUl<{kwGnJ>;yxTJd#Wv@B!6 z_?L7Sc=w)SU0gM7{+low__up(*8tbrdX)NUqRe1E>~f?$vE5#PXn&^tja!-;&3%pp z%0{)5QE6MLJ|dcLM0WOwp}Tw2-ld@nbL6+77t}fi zMjo$U*RJ=)sL4bBK&L+mAO~yaw%+>o{P}9xRhKEI`#NIxx6A zg{P*`n>oclXgap!!wbWON=Vc_T88#w**5WxFe`KFtaxQAHd@<{J(i28Ac3%?B$Lq4 z11EAp^g@E>PL{;*a%vn~<5G#mNYZCu-&W;myIsepJnDI*OGjFY&QzAo@|3I+-Ez5J zFa41E=Cljs5q8nrdXiG|&^$gQ!F48lq{6;d@bZkA_B=-^!??J@a??P1;phGF33P=8 zKLm0&^7D1=d|;$biLJ&rmpAfv6*<5n^5qMn(6BZAhf4*9j8cwh%$oA?I0=^ZyivjK znFe-`E-MQ;YJYUrdOWo_OUQ@-p~c{<`B zy4>Kt9-ZPyb#T`*+vix8kDMW{-+pHeCiU{DxPfTm$;^CpA8=H$iBZ#XB6<^wz!L&CDZNsskGw^pXjN+%D{!8_}WYiM#C9T^V zP7SY}saOP}t1+oLBXdV~9LOmEC>n z@BR5ShCH#S204wm`&enBaVvzQn>fiISdXoU-4ZU(NOge{n-F-XNM0=9Q z9IWVtD$nodNC2M?^GG+y{azy^S|f~^&;FZ|#yTVz<@(QG7yukPt)T?U{W?C`kh#61 z-BQp-8C*CU9K?tD(drZ6_?kDZ@!ik)myyo+zm!J?c$!!gm}}NIAMPTe=($j31Y(U|GWW4UUU_QxI17+FC9YHn_B;{~2;EKP2l zzohb_`G;#+n{hdj9ZXSS%%<6Lm)>`f%^Jwor*|DW)H}=Ps>Ol^BWeAP_V`2d<&$o_ zSQA9Mf)!h&s-f(~1bRuw_}crtzx1~vM1AGw@AAteD%uA?)4TRgU@Pj;u~%5961q*n z&<~C4m!y@;-J>Bh?kzP4&+!)vF&|rQgYlOqM-G&8$gDDzL0Pm5F*dX0(l(wyJbOoyd!MQ@=Q?;KWJ!eS#+OrcC3&;i{qsNcYSCHUFmJFKOxn#xA z@dbE;;SiU*!0jd`H)tx*%VZe{p zIR$v1FCU{n-h9eg4AI0p)L21(_e-tSHgCj=f6u2(h(*hBJw4ienfQ~wzMr6 zt{5Z8P7Y|GpPcw*YCG#r{YVgEfABJs?r-2O z%>*EGSy3R!g%J*)L4m1kXesyZbzoX-Z9V(9%M5tcK_|x20$!ywK0Ui;RnYlQB1?6z zqqp;Pk-4?0_06Zc}^gduL~Q@yV>GQSd1Wtc9_Rk zFZd+U3ijhr;eJdqtT0n9q%*^yEqS6zMibSN+tVF?xx4f~+Y zppH+&55a`QGL4oDgZXb$Bjt!N2a9_hdzVgIp2X42B|hDI&E`!#(@SN#awFptcq4-jCwR;StANUT{~-OBoeOBj+f$K!%Egc_<9X@oVlP`Ga0u#Bn;YGF)D3NAKJf;+tf@bjc6CX|H2K;Mbh{1 zLGl~mYY-FDU-Y@=&-CbtbX!sQ8;D|R-Kiw_LXk&ep{5!OA9Xc?4gcoG1)4dW+7?|mbtj^Kz*Z{+ksQcaFPf*cPjlaHrKHD@Epx2D0^QlCy`A1s&7+cvx zNXWLrrgHgHA5^nK9-LQvekAu+i@cR8nqzQ{mueY-vXPdKfX=66xhJ&&o;NYHlUL+F zO#dCKyhbUv5S0yJ`71rn#ZphRyjg>&vR-ehf1T@>f$4ZSCwy%`49<2WG#Nu z{(o@VMs-1^%#2BV)YO}5AyvF*Mx#jKQsfFD7<(5tZgGH|e!EXO_K?4GkfS%<)FdKK|not4fdS)*AeviFa;N=G;|Y1Lr7A_Q#hNy*517vPFyWm&3|_-$ znrluDb51V$=BE)o<`i3pi!G#0r#G&BTxhwj<9qhr-Z*{!`S}SUDk*9(=6v%USvvhf zLx%_rTo8WYJfytg@h%kt;U4et7!hvLu{e;bZ@n(dA=f;m4jEjMdW?#+0$lXW7r zzhH12&9AtA*(8dbTq0<&?6xKncxg>8TRx10*Cc;-W2DJpldRuQmo1>EV1AdbHF0#r z+s@qo-Q-e@xM7IyzW!#87F;$ajr-;KpmYuG-Tr&ZO`kAg4yQ`fu?cH#zV-R>(lsKg zhXjeSd)<*6UyM04hkhYYY6HZM}(R^!>J)Vp-tW1gh30IKxd>V-6`JbAO6lik`pC+xM&Lh5VRNj z{}9j_U;O^&Xipet`K0CeE9!MkGZ)ev3Q}D$k4NuM#paS%$EwRZntQC+p7a$eX6gXB@9uz)NS_xzS9RF2vey_7p7l-779!8+keqTH7mm1e2kN z?bl{?ej55UBQA;^IPn&~k z$FWco#b@$UyQRi>)!Z>~2qERoe6m_*G;jNmhF3D5I=HSKT2%Y6bu2N*8)FKiPkq~i!~BnVa@&x0=-IZaF*mFh}xU-YgPJs6ij2nG;hgJ=%G z7+9H91Q_zTty@rza^nkImykWS3qZAH0gA+puZA=%8Y;AfGdc*Zj4A&)Qhlv_f;)73}P~+tAs|(JfsVn3X z-BcZzs!_`*PT5DOd!0 zi+33A;fGe>Sa=5SDKNB7AT@MKx9otKB<3smQ#PK*e!H2ceMGzP5sG>1EB4@t! zT9x`20xrR=)Wt_1jJ(HS5BPF3CEMCDApY1KrOU5n%p)70WCDEB;g=RQO++PuOOxkE zoo(j&_Su~%6Wpdr22TtybNw3`Zi4dpEB`K!L5oT=YZZX<7hwi=FDtS?($n-=?t z(7UeLJMV{@lX#Rq$Szs7_dp(h7vcJ91vGLdkqG`DprFdEdDA z54K zb0(7dh!Dri7=-1(S4GbU0z`XHCkqOnOxy_a1bO@*>83=BhzOG`u<92VGW6Qxfgfe- zIZZV}%o9TxlipO#;?K|Hf6voK5=Hs%;ybR88d9!deGg!h(?(a^+Xs&nR;8%}J}*in#(bt5?+h?x1v!VBTHH84OtoSr%COSW zEyI!op zJU_4IFrw!~Y*;XoCG_M+?J{;k55k(YG0Ic*#pkO>M!`NBqT!?aK!$evSp^fMWDZBa z2!~p8^u1UJ`U|ZlkN$pUl+k`AFt@M}E=``Q={M?hhJmcmd3^nWM6I{|H5tv5a^ zKy(rl`f==ZVzB5zWs~j+<@0*<=CRP-ht)nJ>F}A=9?4t5wcOl}_mK2=()IA7SEO-K z0n~?M{#YYN=hm1L&eosq(S9GguFh7er>iKM-(&8)<~M1G2^6SbzI1e$z}R#M`N82Vh+vGWFg^($zL=GEi%^#x!~Ddn z@a;S2jRFbN8@eCH^k4dZ7CEO)K^8hTbFt{FhU2w6B(Zeb|7Y@1sO#?6^Xwb=09 zN;Gca(8McEe?Xh{ZKn!9$R}z@LrWk-)m(Y?F!YbiKp*{%&+u4CZGQ%4ayobpxtxVT zpH>11t63%c4SjY&kHY(VlPXj8MO+L0WE9Q!-OOjZDff>m&BNv;;xr zi1*e4F;sQrHRi-AR${u3JfWrXiOU4O9?5ut%IAjHIItZE*nJ1O^oc>l zjkPnW9_Zg7ipk_O0ma30^k<{AHk@;k{hQUlcWZxdR?{i+Pr~O+m6Voh5pI>xGg4ck}#8AjThge>CsSK_b@(BUw@k0Fajq7 z+}IB5B;6Bdi@Lt>q{QurU=B#F>05(e8o`127qd$dp4UWMa)bklW*@%4LHwrJ@T6V2W3b(x z0XfXnmxhl>mD9^1W!F-Y-XP%xddkPgLvVEIwG2D|cGhTB=OJA2%V!<9&OuHyy%K3} zO%EF+y`KxpaHiq*berhdS4n4gERj^i`Ac z!=y{{-p0sr)LZJfQoxc%E~pM#CDgyp`4um0$*L!IPL=(lBdQn`ZDjp7q8TX*`uEY5 zm2xe7AV8qom;;$K@fXbHweN4$z}Zbs-U%SCeFm5DdD&4!Ec@y73g3YU4Hc90W&vDb z%iZ4lf{hG9nj!__Eh%`cGsS#n)-^G9-Z&R7@@yWJ`P>2XYaFa|DdJ>Wy{3^u6|S4h z&$_i&oV3ozMr}pdQoM=#Vp3>8_mpk7Wm~;UG70zRFAjfZ#Gwi`dnAZ2gwZwR%Yk+D zcR(ss<>iLY`C)W!4vY_9_sCC?H9O)z*tLSvY~NiveH~URjnKS&47)8*w0zsh*7kABnbqt|=wcpS3GCNRXql1>6~><9BiS?0B8ExPsQU}^&!1o~ zP@6?W+z=y`=O9sXc=XZZ*w!-x+~6DCVrcgkeW8Wi0x=K%b8?uU&acvYq+Tn#w%ct4 zZ%iU?TK4EKtnn3|P(cFUZtjel)2;=3X$e-f7!%A3D#%&p@z(kyf$ahyc;2yznsiUK zUs!%v83g|&Y_@m8%N9LU#A-}&!Q?sVOY0)ei5|7|X~GHoVYcj}}0$Wj6eLxEY42<)p&W%Ide8l0bmH@43!w(`7S- zPw(p^PAX5YrOypKtC#iEE4NBKnGZ|*wlBs{0fn^cey507@zB49twYF^M0h`Z)^VCx zTCMd)r`A%$bx!@Xde5D|-F=T)WBF6Ci(6JXuD!yDfzf06vC(Sv!kdYS*aqIgPt7_$ zZtm`QI>#wp<+(jZ!F?KZHbYG^rT?y20CV)=t9FbV#h*b_ojqncleXk16ApQ~`tFNf z(LbeoUqvsw0Vnf&+Qp#f;=)wsURLlFd{=d#x%vJgb}!Y~1w+WshF3d3y$Z_#=CZk zSbCzf?^F}0M41Y45C*W}MyI#>Kq{t_Cg!mBXRjMcs6?uK=!kN#H

t?r_PY@2HL( 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 literal 104882 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 diff --git a/doc/img/ChAnalyzerNG_plugin_settings.png b/doc/img/ChAnalyzerNG_plugin_settings.png index 4395dd633ed4d5b88e02861796ca797cbf45a81b..7d6feeaae062627fb689b7e56381ac34db78a929 100644 GIT binary patch literal 22617 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 Xt)J&mBzai89Au`atDnm{r-UW|Cti=s diff --git a/doc/img/ChAnalyzerNG_plugin_settings.xcf b/doc/img/ChAnalyzerNG_plugin_settings.xcf index 8d1c00a9569eb7114ce2e48aac979f493e7b45cc..56d69b3778bd594b8e959efcd4c15151617f9556 100644 GIT binary patch delta 20396 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 bS6BhPh9?g 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 literal 0 HcmV?d00001 diff --git a/doc/img/ChAnalyzerNG_plugin_tetra.xcf b/doc/img/ChAnalyzerNG_plugin_tetra.xcf new file mode 100644 index 0000000000000000000000000000000000000000..462a0aa4330742bbe9f973443a24ca2a551576c5 GIT binary patch literal 530785 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

&O<|a4HUOL;8>&Z<{>pa1!v$)`s^^Dy$(7EOO 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≦zqHm5b?rOd#c7F;%%sWDWQW|GLfB}!xzg^ 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 literal 0 HcmV?d00001 diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 6d4c07b75..50661d3da 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -108,6 +108,12 @@ void ChannelAnalyzerNGGUI::displaySettings() ui->useRationalDownsampler->setChecked(m_settings.m_downSample); ui->channelSampleRate->setValue(m_settings.m_downSampleRate); setNewFinalRate(); + if (m_settings.m_ssb) { + ui->BWLabel->setText("LP"); + } else { + ui->BWLabel->setText("BP"); + } + ui->ssb->setChecked(m_settings.m_ssb); ui->BW->setValue(m_settings.m_bandwidth/100); ui->lowCut->setValue(m_settings.m_lowCutoff/100); ui->deltaFrequency->setValue(m_settings.m_frequency); @@ -338,6 +344,11 @@ void ChannelAnalyzerNGGUI::on_spanLog2_currentIndexChanged(int index) void ChannelAnalyzerNGGUI::on_ssb_toggled(bool checked) { m_settings.m_ssb = checked; + if (checked) { + ui->BWLabel->setText("LP"); + } else { + ui->BWLabel->setText("BP"); + } setFiltersUIBoundaries(); applySettings(); } @@ -396,7 +407,6 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de ui->glSpectrum->setDisplayMaxHold(true); ui->glSpectrum->setSsbSpectrum(false); ui->glSpectrum->setLsbDisplay(false); - ui->BWLabel->setText("BP"); ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index 0765ef73a..f027cbb56 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -451,13 +451,13 @@ 10 - 50 + 70 1 - 30 + 35 diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp index 20ec8bf75..aea2c0328 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp @@ -41,7 +41,7 @@ void ChannelAnalyzerNGSettings::resetToDefaults() m_pll = false; m_fll = false; m_rrc = false; - m_rrcRolloff = 30; // 0.3 + m_rrcRolloff = 35; // 0.35 m_pllPskOrder = 1; m_inputType = InputSignal; m_rgbColor = QColor(128, 128, 128).rgb(); @@ -115,7 +115,7 @@ bool ChannelAnalyzerNGSettings::deserialize(const QByteArray& data) m_inputType = (InputType) tmp; d.readString(15, &m_title, "Channel Analyzer NG"); d.readBool(16, &m_rrc, false); - d.readU32(17, &m_rrcRolloff, 30); + d.readU32(17, &m_rrcRolloff, 35); return true; } diff --git a/plugins/channelrx/chanalyzerng/readme.md b/plugins/channelrx/chanalyzerng/readme.md index 9c815dfe6..454979b19 100644 --- a/plugins/channelrx/chanalyzerng/readme.md +++ b/plugins/channelrx/chanalyzerng/readme.md @@ -84,31 +84,39 @@ Use this combo to select which (complex) signal to use as the display source: Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. -

10. Select lowpass filter cut-off frequency

+

10. Toggle root raised cosine filter

+ +Use this toggle button to activate or de-activate the root raised cosine (RRC) filter. When active the bnadpass boxcar filter is replaced by a RRC filter. This takes effect only in normal (DSB) mode (see control 14). + +

11. Tune RRC filter rolloff factor

+ +This button tunes the rolloff factor (a.k.a alpha) of the RRC filter in 0.01 steps between 0.1 and 0.7. Default is 0.35. + +

12. Select lowpass filter cut-off frequency

In SSB mode this filter is a complex filter that can lowpass on either side of the center frequency. It is therefore labeled as "LP". For negative frequencies (LSB) the cut-off frequency is therefore negative. In fact setting a negative frequency in SSB mode automatically turns on the LSB mode processing and the spectrum is reversed. In normal (DSB) mode this filter is a real filter that lowpass on both sides of the zero (center) frequency symmetrically. Therefore it acts as a bandpass filter centered on the zero frequency and therefore it is labeled as "BP". The value displayed in (9) is the full bandwidth of the filter. -

11. Lowpass filter cut-off frequency

+

13. Lowpass filter cut-off frequency

In SSB mode this is the complex cut-off frequency and is negative for LSB. In normal (DSB) mode this is the full bandwidth of the real lowpass filter centered around zero frequency. -

12. SSB filtering

+

14. SSB filtering

When this toggle is engaged the signal is filtered either above (USB) or below (LSB) the channel center frequency. The sideband is selected according to the sign of the lowpass filter cut-off frequency (8): if positive the USB is selected else the LSB. In LSB mode the spectrum is reversed. When SSB is off the lowpass filter is actually a bandpass filter around the channel center frequency. -

13. Select highpass filter cut-off frequency

+

15. Select highpass filter cut-off frequency

In SSB mode this controls the cut-off frequency of the complex highpass filter which is the filter closest to the zero frequency. This cut-off frequency is always at least 0.1 kHz in absolute value below the lowpass filter cut-off frequency (8). In normal (DSB) mode this filter is not active. -

14. Highpass filter cut-off frequency

+

16. Highpass filter cut-off frequency

This is the cut-off frequency of the highpass filter in kHz. It is zero or negative in LSB mode. @@ -148,27 +156,31 @@ This button selects the display of "X" and "Yn" traces on top of each other with This button selects the display of all traces on the left side of the screen and the polar display of the "Yn" traces using the "X" trace as the x coordinates. -

6. Select trace intensity

+

6. Line or points only display on the 2D XY display (right)

+ +Use this button to toggle points display (on) or line display (off) for the 2D XY display on the right. The points display may yield a more visible graph when the distinct artifact is an aggregation of points. + +

7. Select trace intensity

This button lets you adjust the traces intensity. The value in percent of the maximum intensity appears as a tooltip -

7. Select grid intensity

+

8. Select grid intensity

This button lets you adjust the grid intensity. The value in percent of the maximum intensity appears as a tooltip -

8. Displayed trace length

+

9. Displayed trace length

This slider lets you adjust the length of the traces on display. Each step further divides the length of the full trace controlled by (10). The duration of the full length shown on display appears on the left of the slider and the corresponding number of samples appears as a tooltip. -

9. Trace offset

+

10. Trace offset

This slider lets you move the start of traces on display. Each step moves the trace by an amount of time corresponding to 1/100 of the length of the full trace controlled by (10). The time offset from the start of the traces appears on the left of the slider and the corresponding number of samples appears as a tooltip. -

10. Trace length

+

11. Trace length

This slider lets you control the full length of the trace. Each step increases the corresponding amount of samples by 4800 samples with a minimum of 4800 samples and a maximum of 20*4800 = 96000 samples. The duration of a full trace appears on the left of the slider and he corresponding number of samples appears as a tooltip. -

11. Trace sample rate

+

12. Trace sample rate

This is the sample rate used by the scope and corresponds to the final sample rate after the whole decimation chain. It should be the same amount as the one displayed on the plugin control (C.6) @@ -202,8 +214,52 @@ To construct a trace which represents real values the incoming complex signal mu - MagDB: calculate power in log representation as 10*log10(x) or decibel (dB) representation. This is the squared module of the complex sample expressed in decibels - Phi: instantaneous phase. This is the argument of the complex sample. - dPhi: instantaneous derivative of the phase. This is the difference of arguments between successive samples thus represents the instantaneous frequency. + - BPSK: maps -π to π phase into two π wide sectors centered on 0 and π on the -1 to +1 range (sector is 1.0 wide): + - 0 → 0.5, + - π → -0.5 + - QPSK: maps -π to π phase into four π/2 wide sectors centered on 0, π/2, π, -π/2 on the -1 to +1 range (sector is 0.5 wide): + - 0 → 0.25 + - π/2 → 0.75 + - π → -0.75 + - -π/2 → -0.25 + - 8PSK: maps -π to π phase into eight π/4 wide sectors centered on 0, π/4, π/2, 3π/4, π, -3π/4, -π/2, -π/4 on the -1 to +1 range (sector is 0.25 wide): + - 0 → 0.125 + - π/4 → 0.375 + - π/2 → 0.625 + - 3π/4 → 0.875 + - π → -0.875 + - -3π/4 → -0.625 + - -π/2 → -0.375 + - -π/4 → -0.125 + - 16PSK: maps -π to π phase into sixteen π/8 wide sectors centered on 0, π/8, π/4, 3π/8, π/2, 5π/8, 3π/4, 7π/8, π, -7π/8, -3π/4, -5π/8, -π/2, -3π/8, -π/4, -π/8 on the -1 to +1 range (sector is 0.125 wide): + - 0 → 0.0625 + - π/8 → 0.1875 + - π/4 → 0.3125 + - 3π/8 → 0.4375 + - π/2 → 0.5625 + - 5π/8 → 0.6875 + - 3π/4 → 0.8125 + - 7π/8 → 0.9375 + - π → -0.9375 + - -7π/8 → -0.8125 + - -3π/4 → -0.6875 + - -5π/8 → -0.5625 + - -π/2 → -0.4375 + - -3π/8 → -0.3125 + - -π/4 → -0.1875 + - -π/8 → -0.0625 + +**Note1**: example of QPSK projection on a synchronized Tetra signal: -in the MagDB mode when the trace is selected (1) the display overlay on the top right of the trace shows 3 figures. From left to right: peak power in dB, average power in dB and peak to average difference in dB. +![Channel Analyzer NG plugin tetra example](../../../doc/img/ChAnalyzerNG_plugin_tetra.png) + +The signal is synchronized with the PLL in 4 phase mode (locker icon is green). + - A Tetra signal is QPSK modulated at 18 kSym/s therefore the sample rate is set at 90 kS/s thus we have an integer number of samples per symbol (5 samples per symbol). See green square. + - We have set two traces (X and Y1) with QPSK projection. The Y1 trace is delayed by two symbols (10 samples) which makes a 111.11 μs delay. See blue square + - In XY mode on the XY display (right) we can see an accumulation of points around the 16 possible symbol transitions. In two places the same symbol is repeated several times which results in a stronger aggregation. One is with the symbol at 0 (see red circle and square) and the other is with the symbol at π (see yellow circle and square). + - On the left panel of the XY mode display we can see that the 4 possible symbols mark 4 vertical stronger areas centered on 0.25, 0.75, -0.25 and -0.75. + +**Note2**: in the MagDB mode when the trace is selected (1) the display overlay on the top right of the trace shows 3 figures. From left to right: peak power in dB, average power in dB and peak to average difference in dB. ![Channel Analyzer NG plugin scope1 controls](../../../doc/img/ChAnalyzerNG_plugin_overlay_dB.png) From ac041ca1ca40b1ba6d150e098b2634e69833c06d Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 22 May 2018 22:43:41 +0200 Subject: [PATCH 447/956] Channel analyzer NG: updated documentation --- plugins/channelrx/chanalyzerng/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/readme.md b/plugins/channelrx/chanalyzerng/readme.md index 454979b19..5deb96ab7 100644 --- a/plugins/channelrx/chanalyzerng/readme.md +++ b/plugins/channelrx/chanalyzerng/readme.md @@ -158,7 +158,7 @@ This button selects the display of all traces on the left side of the screen and

6. Line or points only display on the 2D XY display (right)

-Use this button to toggle points display (on) or line display (off) for the 2D XY display on the right. The points display may yield a more visible graph when the distinct artifact is an aggregation of points. +Use this button to toggle points display (on) or line display (off) for the 2D XY display on the right. The points display may yield a more visible graph when the distinct artifact is an accumulation of points.

7. Select trace intensity

@@ -256,7 +256,7 @@ To construct a trace which represents real values the incoming complex signal mu The signal is synchronized with the PLL in 4 phase mode (locker icon is green). - A Tetra signal is QPSK modulated at 18 kSym/s therefore the sample rate is set at 90 kS/s thus we have an integer number of samples per symbol (5 samples per symbol). See green square. - We have set two traces (X and Y1) with QPSK projection. The Y1 trace is delayed by two symbols (10 samples) which makes a 111.11 μs delay. See blue square - - In XY mode on the XY display (right) we can see an accumulation of points around the 16 possible symbol transitions. In two places the same symbol is repeated several times which results in a stronger aggregation. One is with the symbol at 0 (see red circle and square) and the other is with the symbol at π (see yellow circle and square). + - In XY mode on the XY display (right) we can see an accumulation of points around the 16 possible symbol transitions. In two places the same symbol is repeated several times which results in a stronger accumulation. One is with the symbol at 0 (see red circle and square) and the other is with the symbol at π (see yellow circle and square). - On the left panel of the XY mode display we can see that the 4 possible symbols mark 4 vertical stronger areas centered on 0.25, 0.75, -0.25 and -0.75. **Note2**: in the MagDB mode when the trace is selected (1) the display overlay on the top right of the trace shows 3 figures. From left to right: peak power in dB, average power in dB and peak to average difference in dB. From 8d7b58187950f12d85f0b96df354acc9aceacb99 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 23 May 2018 14:56:29 +0200 Subject: [PATCH 448/956] BFM demod: implemeted WEB API --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- plugins/channelrx/demodbfm/CMakeLists.txt | 1 + plugins/channelrx/demodbfm/bfmdemod.cpp | 171 ++++++++ plugins/channelrx/demodbfm/bfmdemod.h | 22 + plugins/channelrx/demodbfm/bfmdemodgui.cpp | 10 + plugins/channelrx/demodbfm/bfmplugin.cpp | 2 +- sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 170 +++++++- .../webapi/doc/swagger/include/AirspyHF.yaml | 2 + .../webapi/doc/swagger/include/BFMDemod.yaml | 105 +++++ .../webapi/doc/swagger/include/BladeRF.yaml | 2 + .../webapi/doc/swagger/include/HackRF.yaml | 2 + .../webapi/doc/swagger/include/LimeSdr.yaml | 2 + .../webapi/doc/swagger/include/RtlSdr.yaml | 2 + .../resources/webapi/doc/swagger/swagger.yaml | 4 + sdrbase/webapi/webapirequestmapper.cpp | 14 + .../api/swagger/include/BFMDemod.yaml | 105 +++++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 155 ++++++- .../code/qt5/client/SWGBFMDemodReport.cpp | 234 +++++++++++ .../code/qt5/client/SWGBFMDemodReport.h | 95 +++++ .../code/qt5/client/SWGBFMDemodSettings.cpp | 341 ++++++++++++++++ .../code/qt5/client/SWGBFMDemodSettings.h | 125 ++++++ .../code/qt5/client/SWGChannelReport.cpp | 23 ++ .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 ++ .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 16 + .../code/qt5/client/SWGRDSFrequency.cpp | 106 +++++ .../code/qt5/client/SWGRDSFrequency.h | 58 +++ .../sdrangel/code/qt5/client/SWGRDSReport.cpp | 380 ++++++++++++++++++ .../sdrangel/code/qt5/client/SWGRDSReport.h | 133 ++++++ .../client/SWGRDSReport_altFrequencies.cpp | 106 +++++ .../qt5/client/SWGRDSReport_altFrequencies.h | 58 +++ 36 files changed, 2486 insertions(+), 6 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/BFMDemod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/BFMDemod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGRDSFrequency.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGRDSFrequency.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGRDSReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h diff --git a/app/main.cpp b/app/main.cpp index 809d3f48f..7a414c771 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.14.7"); + QCoreApplication::setApplicationVersion("4.0.0"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 8f1214937..97b7a8f41 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("3.14.7"); + QCoreApplication::setApplicationVersion("4.0.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index c4ec4d5d7..b3e3b1cde 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.14.7"); + QCoreApplication::setApplicationVersion("4.0.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/plugins/channelrx/demodbfm/CMakeLists.txt b/plugins/channelrx/demodbfm/CMakeLists.txt index 5a3a27a05..a80c86d56 100644 --- a/plugins/channelrx/demodbfm/CMakeLists.txt +++ b/plugins/channelrx/demodbfm/CMakeLists.txt @@ -35,6 +35,7 @@ set(bfm_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index c7c8050ef..dca52d7b1 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -17,15 +17,23 @@ #include #include +#include "boost/format.hpp" #include #include +#include "SWGChannelSettings.h" +#include "SWGBFMDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGBFMDemodReport.h" +#include "SWGRDSReport.h" + #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/downchannelizer.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" +#include "util/db.h" #include "rdsparser.h" #include "bfmdemod.h" @@ -536,3 +544,166 @@ bool BFMDemod::deserialize(const QByteArray& data) } } +int BFMDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBfmDemodSettings(new SWGSDRangel::SWGBFMDemodSettings()); + response.getBfmDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int BFMDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + BFMDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getBfmDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getBfmDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("afBandwidth")) { + settings.m_afBandwidth = response.getBfmDemodSettings()->getAfBandwidth(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getBfmDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getBfmDemodSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("audioStereo")) { + settings.m_audioStereo = response.getBfmDemodSettings()->getAudioStereo() != 0; + } + if (channelSettingsKeys.contains("lsbStereo")) { + settings.m_lsbStereo = response.getBfmDemodSettings()->getLsbStereo() != 0; + } + if (channelSettingsKeys.contains("showPilot")) { + settings.m_showPilot = response.getBfmDemodSettings()->getShowPilot() != 0; + } + if (channelSettingsKeys.contains("rdsActive")) { + settings.m_rdsActive = response.getBfmDemodSettings()->getRdsActive() != 0; + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAmDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getAmDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getAmDemodSettings()->getAudioDeviceName(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureBFMDemod *msg = MsgConfigureBFMDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("BFMDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBFMDemod *msgToGUI = MsgConfigureBFMDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int BFMDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBfmDemodReport(new SWGSDRangel::SWGBFMDemodReport()); + response.getBfmDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void BFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const BFMDemodSettings& settings) +{ + response.getBfmDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getBfmDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getBfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth); + response.getBfmDemodSettings()->setVolume(settings.m_volume); + response.getBfmDemodSettings()->setSquelch(settings.m_squelch); + response.getBfmDemodSettings()->setAudioStereo(settings.m_audioStereo ? 1 : 0); + response.getBfmDemodSettings()->setLsbStereo(settings.m_lsbStereo ? 1 : 0); + response.getBfmDemodSettings()->setShowPilot(settings.m_showPilot ? 1 : 0); + response.getBfmDemodSettings()->setRdsActive(settings.m_rdsActive ? 1 : 0); + response.getBfmDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getBfmDemodSettings()->getTitle()) { + *response.getBfmDemodSettings()->getTitle() = settings.m_title; + } else { + response.getBfmDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getBfmDemodSettings()->getAudioDeviceName()) { + *response.getBfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getBfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void BFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getBfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getBfmDemodReport()->setSquelch(m_squelchState > 0 ? 1 : 0); + response.getBfmDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getBfmDemodReport()->setChannelSampleRate(m_inputSampleRate); + response.getBfmDemodReport()->setPilotLocked(getPilotLock() ? 1 : 0); + response.getBfmDemodReport()->setPilotPowerDb(CalcDb::dbPower(getPilotLevel())); + + if (m_settings.m_rdsActive) + { + response.getBfmDemodReport()->setRdsReport(new SWGSDRangel::SWGRDSReport()); + webapiFormatRDSReport(response.getBfmDemodReport()->getRdsReport()); + } +} + +void BFMDemod::webapiFormatRDSReport(SWGSDRangel::SWGRDSReport *report) +{ + report->setDemodStatus(round(getDemodQua())); + report->setDecodStatus(round(getDecoderQua())); + report->setRdsDemodAccumDb(CalcDb::dbPower(std::fabs(getDemodAcc()))); + report->setRdsDemodFrequency(getDemodFclk()); + report->setPid(new QString(str(boost::format("%04X") % getRDSParser().m_pi_program_identification).c_str())); + report->setPiType(new QString(getRDSParser().pty_table[getRDSParser().m_pi_program_type].c_str())); + report->setPiCoverage(new QString(getRDSParser().coverage_area_codes[getRDSParser().m_pi_area_coverage_index].c_str())); + report->setProgServiceName(new QString(getRDSParser().m_g0_program_service_name)); + report->setMusicSpeech(new QString((getRDSParser().m_g0_music_speech ? "Music" : "Speech"))); + report->setMonoStereo(new QString((getRDSParser().m_g0_mono_stereo ? "Mono" : "Stereo"))); + report->setRadioText(new QString(getRDSParser().m_g2_radiotext)); + std::string time = str(boost::format("%4i-%02i-%02i %02i:%02i (%+.1fh)")\ + % (1900 + getRDSParser().m_g4_year) % getRDSParser().m_g4_month % getRDSParser().m_g4_day % getRDSParser().m_g4_hours % getRDSParser().m_g4_minutes % getRDSParser().m_g4_local_time_offset); + report->setTime(new QString(time.c_str())); + report->setAltFrequencies(new QList); + + for (std::set::iterator it = getRDSParser().m_g0_alt_freq.begin(); it != getRDSParser().m_g0_alt_freq.end(); ++it) + { + if (*it > 76.0) + { + report->getAltFrequencies()->append(new SWGSDRangel::SWGRDSReport_altFrequencies); + report->getAltFrequencies()->back()->setFrequency(*it); + } + } +} diff --git a/plugins/channelrx/demodbfm/bfmdemod.h b/plugins/channelrx/demodbfm/bfmdemod.h index 9c2a9c447..633d2a615 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.h +++ b/plugins/channelrx/demodbfm/bfmdemod.h @@ -43,6 +43,10 @@ class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; +namespace SWGSDRangel { + class SWGRDSReport; +} + class BFMDemod : public BasebandSampleSink, public ChannelSinkAPI { public: class MsgConfigureBFMDemod : public Message { @@ -153,6 +157,20 @@ public: RDSParser& getRDSParser() { return m_rdsParser; } + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + static const QString m_channelIdURI; static const QString m_channelId; @@ -227,6 +245,10 @@ private: void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const BFMDemodSettings& settings, bool force = false); + + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const BFMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + void webapiFormatRDSReport(SWGSDRangel::SWGRDSReport *report); }; #endif // INCLUDE_BFMDEMOD_H diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index 72d7021ed..97cf243ea 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -119,6 +119,16 @@ bool BFMDemodGUI::handleMessage(const Message& message) ui->glSpectrum->setSampleRate(m_rate / 2); return true; } + else if (BFMDemod::MsgConfigureBFMDemod::match(message)) + { + qDebug("BFMDemodGUI::handleMessage: BFMDemod::MsgConfigureBFMDemod"); + const BFMDemod::MsgConfigureBFMDemod& cfg = (BFMDemod::MsgConfigureBFMDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } else { return false; diff --git a/plugins/channelrx/demodbfm/bfmplugin.cpp b/plugins/channelrx/demodbfm/bfmplugin.cpp index d4f420623..55ffb78dc 100644 --- a/plugins/channelrx/demodbfm/bfmplugin.cpp +++ b/plugins/channelrx/demodbfm/bfmplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor BFMPlugin::m_pluginDescriptor = { QString("Broadcast FM Demodulator"), - QString("3.14.5"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 4b9543477..304d48a65 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -11,6 +11,7 @@ webapi/doc/swagger/include/AMDemod.yaml webapi/doc/swagger/include/AMMod.yaml webapi/doc/swagger/include/ATVMod.yaml + webapi/doc/swagger/include/BFMDemod.yaml webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml webapi/doc/swagger/include/SSBMod.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 4a5243eb7..598f63bb6 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -936,6 +936,9 @@ margin-bottom: 20px; }, "bandIndex" : { "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "AirspyHF" @@ -1042,6 +1045,84 @@ margin-bottom: 20px; } }, "description" : "Audio output device" +}; + defs.BFMDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + }, + "pilotLocked" : { + "type" : "integer", + "description" : "pilot locked status (1 if open else 0)" + }, + "pilotPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power of stereo pilot (dB)" + }, + "rdsReport" : { + "$ref" : "#/definitions/RDSReport" + } + }, + "description" : "BFMDemod" +}; + defs.BFMDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "afBandwidth" : { + "type" : "number", + "format" : "float" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "squelch" : { + "type" : "number", + "format" : "float" + }, + "audioStereo" : { + "type" : "integer" + }, + "lsbStereo" : { + "type" : "integer" + }, + "showPilot" : { + "type" : "integer" + }, + "rdsActive" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + } + }, + "description" : "BFMDemod" }; defs.BladeRFInputSettings = { "properties" : { @@ -1084,6 +1165,9 @@ margin-bottom: 20px; }, "iqCorrection" : { "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "BladeRF" @@ -1220,6 +1304,9 @@ margin-bottom: 20px; "ATVModReport" : { "$ref" : "#/definitions/ATVModReport" }, + "BFMDemodReport" : { + "$ref" : "#/definitions/BFMDemodReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1259,6 +1346,9 @@ margin-bottom: 20px; "ATVModSettings" : { "$ref" : "#/definitions/ATVModSettings" }, + "BFMDemodSettings" : { + "$ref" : "#/definitions/BFMDemodSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -1512,6 +1602,9 @@ margin-bottom: 20px; }, "linkTxFrequency" : { "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "HackRF" @@ -1688,6 +1781,9 @@ margin-bottom: 20px; "transverterDeltaFrequency" : { "type" : "integer", "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "LimeSDR" @@ -2055,6 +2151,75 @@ margin-bottom: 20px; } }, "description" : "Settings presets" +}; + defs.RDSReport = { + "properties" : { + "demodStatus" : { + "type" : "integer", + "description" : "Demodulation success (%)" + }, + "decodStatus" : { + "type" : "integer", + "description" : "Decoding success (%)" + }, + "rdsDemodAccumDB" : { + "type" : "number", + "format" : "float", + "description" : "RDS demodulator accumulator level (dB)" + }, + "rdsDemodFrequency" : { + "type" : "number", + "format" : "float", + "description" : "RDS demodulator clock frequency" + }, + "pid" : { + "type" : "string", + "description" : "Program information (PI) ID in string format (hex)" + }, + "piType" : { + "type" : "string", + "description" : "Program information (PI) type information" + }, + "piCoverage" : { + "type" : "string", + "description" : "Program information (PI) coverage information" + }, + "progServiceName" : { + "type" : "string", + "description" : "Program service name" + }, + "musicSpeech" : { + "type" : "string", + "description" : "Music or speech" + }, + "monoStereo" : { + "type" : "string", + "description" : "Mono or stereo" + }, + "radioText" : { + "type" : "string", + "description" : "Freeflow text" + }, + "time" : { + "type" : "string", + "description" : "Timestamp in string format ~ISO" + }, + "altFrequencies" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/RDSReport_altFrequencies" + } + } + }, + "description" : "RDS information" +}; + defs.RDSReport_altFrequencies = { + "properties" : { + "frequency" : { + "type" : "number", + "format" : "float" + } + } }; defs.RtlSdrSettings = { "properties" : { @@ -2101,6 +2266,9 @@ margin-bottom: 20px; }, "rfBandwidth" : { "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" } }, "description" : "RTLSDR" @@ -20643,7 +20811,7 @@ except ApiException as e:
- Generated 2018-04-17T00:43:20.797+02:00 + Generated 2018-05-23T14:44:33.513+02:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml b/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml index b041dc6bb..0ec9d4f93 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml @@ -17,4 +17,6 @@ AirspyHFSettings: format: int64 bandIndex: type: integer + fileRecordName: + type: string \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/include/BFMDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/BFMDemod.yaml new file mode 100644 index 000000000..65b979633 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/BFMDemod.yaml @@ -0,0 +1,105 @@ +BFMDemodSettings: + description: BFMDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + afBandwidth: + type: number + format: float + volume: + type: number + format: float + squelch: + type: number + format: float + audioStereo: + type: integer + lsbStereo: + type: integer + showPilot: + type: integer + rdsActive: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + +BFMDemodReport: + description: BFMDemod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + squelch: + description: squelch status (1 if open else 0) + type: integer + audioSampleRate: + type: integer + channelSampleRate: + type: integer + pilotLocked: + description: pilot locked status (1 if open else 0) + type: integer + pilotPowerDB: + description: power of stereo pilot (dB) + type: number + format: float + rdsReport: + $ref: "#/RDSReport" + +RDSReport: + description: RDS information + properties: + demodStatus: + description: Demodulation success (%) + type: integer + decodStatus: + description: Decoding success (%) + type: integer + rdsDemodAccumDB: + description: RDS demodulator accumulator level (dB) + type: number + format: float + rdsDemodFrequency: + description: RDS demodulator clock frequency + type: number + format: float + pid: + description: Program information (PI) ID in string format (hex) + type: string + piType: + description: Program information (PI) type information + type: string + piCoverage: + description: Program information (PI) coverage information + type: string + progServiceName: + description: Program service name + type: string + musicSpeech: + description: Music or speech + type: string + monoStereo: + description: Mono or stereo + type: string + radioText: + description: Freeflow text + type: string + time: + description: Timestamp in string format ~ISO + type: string + altFrequencies: + type: array + items: + properties: + frequency: + type: number + format: float diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF.yaml index 8f75f9460..c46945406 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/BladeRF.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF.yaml @@ -28,6 +28,8 @@ BladeRFInputSettings: type: integer iqCorrection: type: integer + fileRecordName: + type: string BladeRFOutputSettings: description: BladeRF diff --git a/sdrbase/resources/webapi/doc/swagger/include/HackRF.yaml b/sdrbase/resources/webapi/doc/swagger/include/HackRF.yaml index 21278b932..a246ad7b5 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/HackRF.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/HackRF.yaml @@ -29,6 +29,8 @@ HackRFInputSettings: type: integer linkTxFrequency: type: integer + fileRecordName: + type: string HackRFOutputSettings: description: HackRF diff --git a/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml index a043b00d8..6751d466c 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml @@ -45,6 +45,8 @@ LimeSdrInputSettings: transverterDeltaFrequency: type: integer format: int64 + fileRecordName: + type: string LimeSdrOutputSettings: description: LimeSDR diff --git a/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml index 16e7446bc..4d8cc5245 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml @@ -31,4 +31,6 @@ RtlSdrSettings: format: int64 rfBandwidth: type: integer + fileRecordName: + type: string \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 855f814f0..32fe1bc67 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1751,6 +1751,8 @@ definitions: $ref: "/doc/swagger/include/AMMod.yaml#/AMModSettings" ATVModSettings: $ref: "/doc/swagger/include/ATVMod.yaml#/ATVModSettings" + BFMDemodSettings: + $ref: "/doc/swagger/include/BFMDemod.yaml#/BFMDemodSettings" NFMDemodSettings: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: @@ -1778,6 +1780,8 @@ definitions: $ref: "/doc/swagger/include/AMMod.yaml#/AMModReport" ATVModReport: $ref: "/doc/swagger/include/ATVMod.yaml#/ATVModReport" + BFMDemodReport: + $ref: "/doc/swagger/include/BFMDemod.yaml#/BFMDemodReport" NFMDemodReport: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 342bb0285..a4a745bc8 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1875,6 +1875,20 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "BFMDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject bfmDemodSettingsJsonObject = jsonObject["BFMDemodSettings"].toObject(); + channelSettingsKeys = bfmDemodSettingsJsonObject.keys(); + channelSettings.setBfmDemodSettings(new SWGSDRangel::SWGBFMDemodSettings()); + channelSettings.getBfmDemodSettings()->fromJsonObject(bfmDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "NFMDemod") { if (channelSettings.getTx() == 0) diff --git a/swagger/sdrangel/api/swagger/include/BFMDemod.yaml b/swagger/sdrangel/api/swagger/include/BFMDemod.yaml new file mode 100644 index 000000000..65b979633 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/BFMDemod.yaml @@ -0,0 +1,105 @@ +BFMDemodSettings: + description: BFMDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + afBandwidth: + type: number + format: float + volume: + type: number + format: float + squelch: + type: number + format: float + audioStereo: + type: integer + lsbStereo: + type: integer + showPilot: + type: integer + rdsActive: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + +BFMDemodReport: + description: BFMDemod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + squelch: + description: squelch status (1 if open else 0) + type: integer + audioSampleRate: + type: integer + channelSampleRate: + type: integer + pilotLocked: + description: pilot locked status (1 if open else 0) + type: integer + pilotPowerDB: + description: power of stereo pilot (dB) + type: number + format: float + rdsReport: + $ref: "#/RDSReport" + +RDSReport: + description: RDS information + properties: + demodStatus: + description: Demodulation success (%) + type: integer + decodStatus: + description: Decoding success (%) + type: integer + rdsDemodAccumDB: + description: RDS demodulator accumulator level (dB) + type: number + format: float + rdsDemodFrequency: + description: RDS demodulator clock frequency + type: number + format: float + pid: + description: Program information (PI) ID in string format (hex) + type: string + piType: + description: Program information (PI) type information + type: string + piCoverage: + description: Program information (PI) coverage information + type: string + progServiceName: + description: Program service name + type: string + musicSpeech: + description: Music or speech + type: string + monoStereo: + description: Mono or stereo + type: string + radioText: + description: Freeflow text + type: string + time: + description: Timestamp in string format ~ISO + type: string + altFrequencies: + type: array + items: + properties: + frequency: + type: number + format: float diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 77c6224a4..63c27d7ac 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1751,6 +1751,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/AMMod.yaml#/AMModSettings" ATVModSettings: $ref: "http://localhost:8081/api/swagger/include/ATVMod.yaml#/ATVModSettings" + BFMDemodSettings: + $ref: "http://localhost:8081/api/swagger/include/BFMDemod.yaml#/BFMDemodSettings" NFMDemodSettings: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: @@ -1778,6 +1780,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/AMMod.yaml#/AMModReport" ATVModReport: $ref: "http://localhost:8081/api/swagger/include/ATVMod.yaml#/ATVModReport" + BFMDemodReport: + $ref: "http://localhost:8081/api/swagger/include/BFMDemod.yaml#/BFMDemodReport" NFMDemodReport: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index dab7b0094..598f63bb6 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1045,6 +1045,84 @@ margin-bottom: 20px; } }, "description" : "Audio output device" +}; + defs.BFMDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + }, + "pilotLocked" : { + "type" : "integer", + "description" : "pilot locked status (1 if open else 0)" + }, + "pilotPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power of stereo pilot (dB)" + }, + "rdsReport" : { + "$ref" : "#/definitions/RDSReport" + } + }, + "description" : "BFMDemod" +}; + defs.BFMDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "afBandwidth" : { + "type" : "number", + "format" : "float" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "squelch" : { + "type" : "number", + "format" : "float" + }, + "audioStereo" : { + "type" : "integer" + }, + "lsbStereo" : { + "type" : "integer" + }, + "showPilot" : { + "type" : "integer" + }, + "rdsActive" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + } + }, + "description" : "BFMDemod" }; defs.BladeRFInputSettings = { "properties" : { @@ -1226,6 +1304,9 @@ margin-bottom: 20px; "ATVModReport" : { "$ref" : "#/definitions/ATVModReport" }, + "BFMDemodReport" : { + "$ref" : "#/definitions/BFMDemodReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1265,6 +1346,9 @@ margin-bottom: 20px; "ATVModSettings" : { "$ref" : "#/definitions/ATVModSettings" }, + "BFMDemodSettings" : { + "$ref" : "#/definitions/BFMDemodSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -2067,6 +2151,75 @@ margin-bottom: 20px; } }, "description" : "Settings presets" +}; + defs.RDSReport = { + "properties" : { + "demodStatus" : { + "type" : "integer", + "description" : "Demodulation success (%)" + }, + "decodStatus" : { + "type" : "integer", + "description" : "Decoding success (%)" + }, + "rdsDemodAccumDB" : { + "type" : "number", + "format" : "float", + "description" : "RDS demodulator accumulator level (dB)" + }, + "rdsDemodFrequency" : { + "type" : "number", + "format" : "float", + "description" : "RDS demodulator clock frequency" + }, + "pid" : { + "type" : "string", + "description" : "Program information (PI) ID in string format (hex)" + }, + "piType" : { + "type" : "string", + "description" : "Program information (PI) type information" + }, + "piCoverage" : { + "type" : "string", + "description" : "Program information (PI) coverage information" + }, + "progServiceName" : { + "type" : "string", + "description" : "Program service name" + }, + "musicSpeech" : { + "type" : "string", + "description" : "Music or speech" + }, + "monoStereo" : { + "type" : "string", + "description" : "Mono or stereo" + }, + "radioText" : { + "type" : "string", + "description" : "Freeflow text" + }, + "time" : { + "type" : "string", + "description" : "Timestamp in string format ~ISO" + }, + "altFrequencies" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/RDSReport_altFrequencies" + } + } + }, + "description" : "RDS information" +}; + defs.RDSReport_altFrequencies = { + "properties" : { + "frequency" : { + "type" : "number", + "format" : "float" + } + } }; defs.RtlSdrSettings = { "properties" : { @@ -20658,7 +20811,7 @@ except ApiException as e:
- Generated 2018-05-09T18:07:27.088+02:00 + Generated 2018-05-23T14:44:33.513+02:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp new file mode 100644 index 000000000..15fe1ac61 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp @@ -0,0 +1,234 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBFMDemodReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBFMDemodReport::SWGBFMDemodReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBFMDemodReport::SWGBFMDemodReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + squelch = 0; + m_squelch_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; + pilot_locked = 0; + m_pilot_locked_isSet = false; + pilot_power_db = 0.0f; + m_pilot_power_db_isSet = false; + rds_report = nullptr; + m_rds_report_isSet = false; +} + +SWGBFMDemodReport::~SWGBFMDemodReport() { + this->cleanup(); +} + +void +SWGBFMDemodReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + squelch = 0; + m_squelch_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; + pilot_locked = 0; + m_pilot_locked_isSet = false; + pilot_power_db = 0.0f; + m_pilot_power_db_isSet = false; + rds_report = new SWGRDSReport(); + m_rds_report_isSet = false; +} + +void +SWGBFMDemodReport::cleanup() { + + + + + + + if(rds_report != nullptr) { + delete rds_report; + } +} + +SWGBFMDemodReport* +SWGBFMDemodReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBFMDemodReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&pilot_locked, pJson["pilotLocked"], "qint32", ""); + + ::SWGSDRangel::setValue(&pilot_power_db, pJson["pilotPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&rds_report, pJson["rdsReport"], "SWGRDSReport", "SWGRDSReport"); + +} + +QString +SWGBFMDemodReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBFMDemodReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_audio_sample_rate_isSet){ + obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + if(m_pilot_locked_isSet){ + obj->insert("pilotLocked", QJsonValue(pilot_locked)); + } + if(m_pilot_power_db_isSet){ + obj->insert("pilotPowerDB", QJsonValue(pilot_power_db)); + } + if((rds_report != nullptr) && (rds_report->isSet())){ + toJsonValue(QString("rdsReport"), rds_report, obj, QString("SWGRDSReport")); + } + + return obj; +} + +float +SWGBFMDemodReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGBFMDemodReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGBFMDemodReport::getSquelch() { + return squelch; +} +void +SWGBFMDemodReport::setSquelch(qint32 squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +qint32 +SWGBFMDemodReport::getAudioSampleRate() { + return audio_sample_rate; +} +void +SWGBFMDemodReport::setAudioSampleRate(qint32 audio_sample_rate) { + this->audio_sample_rate = audio_sample_rate; + this->m_audio_sample_rate_isSet = true; +} + +qint32 +SWGBFMDemodReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGBFMDemodReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + +qint32 +SWGBFMDemodReport::getPilotLocked() { + return pilot_locked; +} +void +SWGBFMDemodReport::setPilotLocked(qint32 pilot_locked) { + this->pilot_locked = pilot_locked; + this->m_pilot_locked_isSet = true; +} + +float +SWGBFMDemodReport::getPilotPowerDb() { + return pilot_power_db; +} +void +SWGBFMDemodReport::setPilotPowerDb(float pilot_power_db) { + this->pilot_power_db = pilot_power_db; + this->m_pilot_power_db_isSet = true; +} + +SWGRDSReport* +SWGBFMDemodReport::getRdsReport() { + return rds_report; +} +void +SWGBFMDemodReport::setRdsReport(SWGRDSReport* rds_report) { + this->rds_report = rds_report; + this->m_rds_report_isSet = true; +} + + +bool +SWGBFMDemodReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_pilot_locked_isSet){ isObjectUpdated = true; break;} + if(m_pilot_power_db_isSet){ isObjectUpdated = true; break;} + if(rds_report != nullptr && rds_report->isSet()){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h new file mode 100644 index 000000000..19c4d999e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h @@ -0,0 +1,95 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBFMDemodReport.h + * + * BFMDemod + */ + +#ifndef SWGBFMDemodReport_H_ +#define SWGBFMDemodReport_H_ + +#include + + +#include "SWGRDSReport.h" + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBFMDemodReport: public SWGObject { +public: + SWGBFMDemodReport(); + SWGBFMDemodReport(QString* json); + virtual ~SWGBFMDemodReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBFMDemodReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getSquelch(); + void setSquelch(qint32 squelch); + + qint32 getAudioSampleRate(); + void setAudioSampleRate(qint32 audio_sample_rate); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + qint32 getPilotLocked(); + void setPilotLocked(qint32 pilot_locked); + + float getPilotPowerDb(); + void setPilotPowerDb(float pilot_power_db); + + SWGRDSReport* getRdsReport(); + void setRdsReport(SWGRDSReport* rds_report); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 squelch; + bool m_squelch_isSet; + + qint32 audio_sample_rate; + bool m_audio_sample_rate_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + + qint32 pilot_locked; + bool m_pilot_locked_isSet; + + float pilot_power_db; + bool m_pilot_power_db_isSet; + + SWGRDSReport* rds_report; + bool m_rds_report_isSet; + +}; + +} + +#endif /* SWGBFMDemodReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp new file mode 100644 index 000000000..594455f05 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp @@ -0,0 +1,341 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBFMDemodSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBFMDemodSettings::SWGBFMDemodSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBFMDemodSettings::SWGBFMDemodSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + af_bandwidth = 0.0f; + m_af_bandwidth_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + squelch = 0.0f; + m_squelch_isSet = false; + audio_stereo = 0; + m_audio_stereo_isSet = false; + lsb_stereo = 0; + m_lsb_stereo_isSet = false; + show_pilot = 0; + m_show_pilot_isSet = false; + rds_active = 0; + m_rds_active_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; +} + +SWGBFMDemodSettings::~SWGBFMDemodSettings() { + this->cleanup(); +} + +void +SWGBFMDemodSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + af_bandwidth = 0.0f; + m_af_bandwidth_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + squelch = 0.0f; + m_squelch_isSet = false; + audio_stereo = 0; + m_audio_stereo_isSet = false; + lsb_stereo = 0; + m_lsb_stereo_isSet = false; + show_pilot = 0; + m_show_pilot_isSet = false; + rds_active = 0; + m_rds_active_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; +} + +void +SWGBFMDemodSettings::cleanup() { + + + + + + + + + + + if(title != nullptr) { + delete title; + } + if(audio_device_name != nullptr) { + delete audio_device_name; + } +} + +SWGBFMDemodSettings* +SWGBFMDemodSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBFMDemodSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&af_bandwidth, pJson["afBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&volume, pJson["volume"], "float", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "float", ""); + + ::SWGSDRangel::setValue(&audio_stereo, pJson["audioStereo"], "qint32", ""); + + ::SWGSDRangel::setValue(&lsb_stereo, pJson["lsbStereo"], "qint32", ""); + + ::SWGSDRangel::setValue(&show_pilot, pJson["showPilot"], "qint32", ""); + + ::SWGSDRangel::setValue(&rds_active, pJson["rdsActive"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + +} + +QString +SWGBFMDemodSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBFMDemodSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_af_bandwidth_isSet){ + obj->insert("afBandwidth", QJsonValue(af_bandwidth)); + } + if(m_volume_isSet){ + obj->insert("volume", QJsonValue(volume)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_audio_stereo_isSet){ + obj->insert("audioStereo", QJsonValue(audio_stereo)); + } + if(m_lsb_stereo_isSet){ + obj->insert("lsbStereo", QJsonValue(lsb_stereo)); + } + if(m_show_pilot_isSet){ + obj->insert("showPilot", QJsonValue(show_pilot)); + } + if(m_rds_active_isSet){ + obj->insert("rdsActive", QJsonValue(rds_active)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGBFMDemodSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGBFMDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGBFMDemodSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGBFMDemodSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGBFMDemodSettings::getAfBandwidth() { + return af_bandwidth; +} +void +SWGBFMDemodSettings::setAfBandwidth(float af_bandwidth) { + this->af_bandwidth = af_bandwidth; + this->m_af_bandwidth_isSet = true; +} + +float +SWGBFMDemodSettings::getVolume() { + return volume; +} +void +SWGBFMDemodSettings::setVolume(float volume) { + this->volume = volume; + this->m_volume_isSet = true; +} + +float +SWGBFMDemodSettings::getSquelch() { + return squelch; +} +void +SWGBFMDemodSettings::setSquelch(float squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +qint32 +SWGBFMDemodSettings::getAudioStereo() { + return audio_stereo; +} +void +SWGBFMDemodSettings::setAudioStereo(qint32 audio_stereo) { + this->audio_stereo = audio_stereo; + this->m_audio_stereo_isSet = true; +} + +qint32 +SWGBFMDemodSettings::getLsbStereo() { + return lsb_stereo; +} +void +SWGBFMDemodSettings::setLsbStereo(qint32 lsb_stereo) { + this->lsb_stereo = lsb_stereo; + this->m_lsb_stereo_isSet = true; +} + +qint32 +SWGBFMDemodSettings::getShowPilot() { + return show_pilot; +} +void +SWGBFMDemodSettings::setShowPilot(qint32 show_pilot) { + this->show_pilot = show_pilot; + this->m_show_pilot_isSet = true; +} + +qint32 +SWGBFMDemodSettings::getRdsActive() { + return rds_active; +} +void +SWGBFMDemodSettings::setRdsActive(qint32 rds_active) { + this->rds_active = rds_active; + this->m_rds_active_isSet = true; +} + +qint32 +SWGBFMDemodSettings::getRgbColor() { + return rgb_color; +} +void +SWGBFMDemodSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGBFMDemodSettings::getTitle() { + return title; +} +void +SWGBFMDemodSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +QString* +SWGBFMDemodSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGBFMDemodSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + + +bool +SWGBFMDemodSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_af_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_volume_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_audio_stereo_isSet){ isObjectUpdated = true; break;} + if(m_lsb_stereo_isSet){ isObjectUpdated = true; break;} + if(m_show_pilot_isSet){ isObjectUpdated = true; break;} + if(m_rds_active_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h new file mode 100644 index 000000000..eeeaa3076 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h @@ -0,0 +1,125 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBFMDemodSettings.h + * + * BFMDemod + */ + +#ifndef SWGBFMDemodSettings_H_ +#define SWGBFMDemodSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBFMDemodSettings: public SWGObject { +public: + SWGBFMDemodSettings(); + SWGBFMDemodSettings(QString* json); + virtual ~SWGBFMDemodSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBFMDemodSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getAfBandwidth(); + void setAfBandwidth(float af_bandwidth); + + float getVolume(); + void setVolume(float volume); + + float getSquelch(); + void setSquelch(float squelch); + + qint32 getAudioStereo(); + void setAudioStereo(qint32 audio_stereo); + + qint32 getLsbStereo(); + void setLsbStereo(qint32 lsb_stereo); + + qint32 getShowPilot(); + void setShowPilot(qint32 show_pilot); + + qint32 getRdsActive(); + void setRdsActive(qint32 rds_active); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float af_bandwidth; + bool m_af_bandwidth_isSet; + + float volume; + bool m_volume_isSet; + + float squelch; + bool m_squelch_isSet; + + qint32 audio_stereo; + bool m_audio_stereo_isSet; + + qint32 lsb_stereo; + bool m_lsb_stereo_isSet; + + qint32 show_pilot; + bool m_show_pilot_isSet; + + qint32 rds_active; + bool m_rds_active_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + QString* audio_device_name; + bool m_audio_device_name_isSet; + +}; + +} + +#endif /* SWGBFMDemodSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index c103eaa9b..d1811cdc9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -38,6 +38,8 @@ SWGChannelReport::SWGChannelReport() { m_am_mod_report_isSet = false; atv_mod_report = nullptr; m_atv_mod_report_isSet = false; + bfm_demod_report = nullptr; + m_bfm_demod_report_isSet = false; nfm_demod_report = nullptr; m_nfm_demod_report_isSet = false; nfm_mod_report = nullptr; @@ -66,6 +68,8 @@ SWGChannelReport::init() { m_am_mod_report_isSet = false; atv_mod_report = new SWGATVModReport(); m_atv_mod_report_isSet = false; + bfm_demod_report = new SWGBFMDemodReport(); + m_bfm_demod_report_isSet = false; nfm_demod_report = new SWGNFMDemodReport(); m_nfm_demod_report_isSet = false; nfm_mod_report = new SWGNFMModReport(); @@ -93,6 +97,9 @@ SWGChannelReport::cleanup() { if(atv_mod_report != nullptr) { delete atv_mod_report; } + if(bfm_demod_report != nullptr) { + delete bfm_demod_report; + } if(nfm_demod_report != nullptr) { delete nfm_demod_report; } @@ -131,6 +138,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&atv_mod_report, pJson["ATVModReport"], "SWGATVModReport", "SWGATVModReport"); + ::SWGSDRangel::setValue(&bfm_demod_report, pJson["BFMDemodReport"], "SWGBFMDemodReport", "SWGBFMDemodReport"); + ::SWGSDRangel::setValue(&nfm_demod_report, pJson["NFMDemodReport"], "SWGNFMDemodReport", "SWGNFMDemodReport"); ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); @@ -172,6 +181,9 @@ SWGChannelReport::asJsonObject() { if((atv_mod_report != nullptr) && (atv_mod_report->isSet())){ toJsonValue(QString("ATVModReport"), atv_mod_report, obj, QString("SWGATVModReport")); } + if((bfm_demod_report != nullptr) && (bfm_demod_report->isSet())){ + toJsonValue(QString("BFMDemodReport"), bfm_demod_report, obj, QString("SWGBFMDemodReport")); + } if((nfm_demod_report != nullptr) && (nfm_demod_report->isSet())){ toJsonValue(QString("NFMDemodReport"), nfm_demod_report, obj, QString("SWGNFMDemodReport")); } @@ -241,6 +253,16 @@ SWGChannelReport::setAtvModReport(SWGATVModReport* atv_mod_report) { this->m_atv_mod_report_isSet = true; } +SWGBFMDemodReport* +SWGChannelReport::getBfmDemodReport() { + return bfm_demod_report; +} +void +SWGChannelReport::setBfmDemodReport(SWGBFMDemodReport* bfm_demod_report) { + this->bfm_demod_report = bfm_demod_report; + this->m_bfm_demod_report_isSet = true; +} + SWGNFMDemodReport* SWGChannelReport::getNfmDemodReport() { return nfm_demod_report; @@ -301,6 +323,7 @@ SWGChannelReport::isSet(){ if(am_demod_report != nullptr && am_demod_report->isSet()){ isObjectUpdated = true; break;} if(am_mod_report != nullptr && am_mod_report->isSet()){ isObjectUpdated = true; break;} if(atv_mod_report != nullptr && atv_mod_report->isSet()){ isObjectUpdated = true; break;} + if(bfm_demod_report != nullptr && bfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 5236d39b3..2633909a8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -25,6 +25,7 @@ #include "SWGAMDemodReport.h" #include "SWGAMModReport.h" #include "SWGATVModReport.h" +#include "SWGBFMDemodReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" #include "SWGSSBModReport.h" @@ -65,6 +66,9 @@ public: SWGATVModReport* getAtvModReport(); void setAtvModReport(SWGATVModReport* atv_mod_report); + SWGBFMDemodReport* getBfmDemodReport(); + void setBfmDemodReport(SWGBFMDemodReport* bfm_demod_report); + SWGNFMDemodReport* getNfmDemodReport(); void setNfmDemodReport(SWGNFMDemodReport* nfm_demod_report); @@ -99,6 +103,9 @@ private: SWGATVModReport* atv_mod_report; bool m_atv_mod_report_isSet; + SWGBFMDemodReport* bfm_demod_report; + bool m_bfm_demod_report_isSet; + SWGNFMDemodReport* nfm_demod_report; bool m_nfm_demod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index b85dd6121..0c81a0906 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -38,6 +38,8 @@ SWGChannelSettings::SWGChannelSettings() { m_am_mod_settings_isSet = false; atv_mod_settings = nullptr; m_atv_mod_settings_isSet = false; + bfm_demod_settings = nullptr; + m_bfm_demod_settings_isSet = false; nfm_demod_settings = nullptr; m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; @@ -66,6 +68,8 @@ SWGChannelSettings::init() { m_am_mod_settings_isSet = false; atv_mod_settings = new SWGATVModSettings(); m_atv_mod_settings_isSet = false; + bfm_demod_settings = new SWGBFMDemodSettings(); + m_bfm_demod_settings_isSet = false; nfm_demod_settings = new SWGNFMDemodSettings(); m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); @@ -93,6 +97,9 @@ SWGChannelSettings::cleanup() { if(atv_mod_settings != nullptr) { delete atv_mod_settings; } + if(bfm_demod_settings != nullptr) { + delete bfm_demod_settings; + } if(nfm_demod_settings != nullptr) { delete nfm_demod_settings; } @@ -131,6 +138,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&atv_mod_settings, pJson["ATVModSettings"], "SWGATVModSettings", "SWGATVModSettings"); + ::SWGSDRangel::setValue(&bfm_demod_settings, pJson["BFMDemodSettings"], "SWGBFMDemodSettings", "SWGBFMDemodSettings"); + ::SWGSDRangel::setValue(&nfm_demod_settings, pJson["NFMDemodSettings"], "SWGNFMDemodSettings", "SWGNFMDemodSettings"); ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); @@ -172,6 +181,9 @@ SWGChannelSettings::asJsonObject() { if((atv_mod_settings != nullptr) && (atv_mod_settings->isSet())){ toJsonValue(QString("ATVModSettings"), atv_mod_settings, obj, QString("SWGATVModSettings")); } + if((bfm_demod_settings != nullptr) && (bfm_demod_settings->isSet())){ + toJsonValue(QString("BFMDemodSettings"), bfm_demod_settings, obj, QString("SWGBFMDemodSettings")); + } if((nfm_demod_settings != nullptr) && (nfm_demod_settings->isSet())){ toJsonValue(QString("NFMDemodSettings"), nfm_demod_settings, obj, QString("SWGNFMDemodSettings")); } @@ -241,6 +253,16 @@ SWGChannelSettings::setAtvModSettings(SWGATVModSettings* atv_mod_settings) { this->m_atv_mod_settings_isSet = true; } +SWGBFMDemodSettings* +SWGChannelSettings::getBfmDemodSettings() { + return bfm_demod_settings; +} +void +SWGChannelSettings::setBfmDemodSettings(SWGBFMDemodSettings* bfm_demod_settings) { + this->bfm_demod_settings = bfm_demod_settings; + this->m_bfm_demod_settings_isSet = true; +} + SWGNFMDemodSettings* SWGChannelSettings::getNfmDemodSettings() { return nfm_demod_settings; @@ -301,6 +323,7 @@ SWGChannelSettings::isSet(){ if(am_demod_settings != nullptr && am_demod_settings->isSet()){ isObjectUpdated = true; break;} if(am_mod_settings != nullptr && am_mod_settings->isSet()){ isObjectUpdated = true; break;} if(atv_mod_settings != nullptr && atv_mod_settings->isSet()){ isObjectUpdated = true; break;} + if(bfm_demod_settings != nullptr && bfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 94fdab7d0..16335a07e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -25,6 +25,7 @@ #include "SWGAMDemodSettings.h" #include "SWGAMModSettings.h" #include "SWGATVModSettings.h" +#include "SWGBFMDemodSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" #include "SWGSSBModSettings.h" @@ -65,6 +66,9 @@ public: SWGATVModSettings* getAtvModSettings(); void setAtvModSettings(SWGATVModSettings* atv_mod_settings); + SWGBFMDemodSettings* getBfmDemodSettings(); + void setBfmDemodSettings(SWGBFMDemodSettings* bfm_demod_settings); + SWGNFMDemodSettings* getNfmDemodSettings(); void setNfmDemodSettings(SWGNFMDemodSettings* nfm_demod_settings); @@ -99,6 +103,9 @@ private: SWGATVModSettings* atv_mod_settings; bool m_atv_mod_settings_isSet; + SWGBFMDemodSettings* bfm_demod_settings; + bool m_bfm_demod_settings_isSet; + SWGNFMDemodSettings* nfm_demod_settings; bool m_nfm_demod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 992ff5081..155057536 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -24,6 +24,8 @@ #include "SWGAudioDevices.h" #include "SWGAudioInputDevice.h" #include "SWGAudioOutputDevice.h" +#include "SWGBFMDemodReport.h" +#include "SWGBFMDemodSettings.h" #include "SWGBladeRFInputSettings.h" #include "SWGBladeRFOutputSettings.h" #include "SWGCWKeyerSettings.h" @@ -61,6 +63,8 @@ #include "SWGPresetItem.h" #include "SWGPresetTransfer.h" #include "SWGPresets.h" +#include "SWGRDSReport.h" +#include "SWGRDSReport_altFrequencies.h" #include "SWGRtlSdrSettings.h" #include "SWGSSBModReport.h" #include "SWGSSBModSettings.h" @@ -104,6 +108,12 @@ namespace SWGSDRangel { if(QString("SWGAudioOutputDevice").compare(type) == 0) { return new SWGAudioOutputDevice(); } + if(QString("SWGBFMDemodReport").compare(type) == 0) { + return new SWGBFMDemodReport(); + } + if(QString("SWGBFMDemodSettings").compare(type) == 0) { + return new SWGBFMDemodSettings(); + } if(QString("SWGBladeRFInputSettings").compare(type) == 0) { return new SWGBladeRFInputSettings(); } @@ -215,6 +225,12 @@ namespace SWGSDRangel { if(QString("SWGPresets").compare(type) == 0) { return new SWGPresets(); } + if(QString("SWGRDSReport").compare(type) == 0) { + return new SWGRDSReport(); + } + if(QString("SWGRDSReport_altFrequencies").compare(type) == 0) { + return new SWGRDSReport_altFrequencies(); + } if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.cpp b/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.cpp new file mode 100644 index 000000000..ba57f7724 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGRDSFrequency.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRDSFrequency::SWGRDSFrequency(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRDSFrequency::SWGRDSFrequency() { + frequency = 0.0f; + m_frequency_isSet = false; +} + +SWGRDSFrequency::~SWGRDSFrequency() { + this->cleanup(); +} + +void +SWGRDSFrequency::init() { + frequency = 0.0f; + m_frequency_isSet = false; +} + +void +SWGRDSFrequency::cleanup() { + +} + +SWGRDSFrequency* +SWGRDSFrequency::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRDSFrequency::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&frequency, pJson["frequency"], "float", ""); + +} + +QString +SWGRDSFrequency::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRDSFrequency::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_frequency_isSet){ + obj->insert("frequency", QJsonValue(frequency)); + } + + return obj; +} + +float +SWGRDSFrequency::getFrequency() { + return frequency; +} +void +SWGRDSFrequency::setFrequency(float frequency) { + this->frequency = frequency; + this->m_frequency_isSet = true; +} + + +bool +SWGRDSFrequency::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_frequency_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.h b/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.h new file mode 100644 index 000000000..28f8670c3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGRDSFrequency.h + * + * Frequency information + */ + +#ifndef SWGRDSFrequency_H_ +#define SWGRDSFrequency_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRDSFrequency: public SWGObject { +public: + SWGRDSFrequency(); + SWGRDSFrequency(QString* json); + virtual ~SWGRDSFrequency(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRDSFrequency* fromJson(QString &jsonString) override; + + float getFrequency(); + void setFrequency(float frequency); + + + virtual bool isSet() override; + +private: + float frequency; + bool m_frequency_isSet; + +}; + +} + +#endif /* SWGRDSFrequency_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp new file mode 100644 index 000000000..a1eadab3d --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp @@ -0,0 +1,380 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGRDSReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRDSReport::SWGRDSReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRDSReport::SWGRDSReport() { + demod_status = 0; + m_demod_status_isSet = false; + decod_status = 0; + m_decod_status_isSet = false; + rds_demod_accum_db = 0.0f; + m_rds_demod_accum_db_isSet = false; + rds_demod_frequency = 0.0f; + m_rds_demod_frequency_isSet = false; + pid = nullptr; + m_pid_isSet = false; + pi_type = nullptr; + m_pi_type_isSet = false; + pi_coverage = nullptr; + m_pi_coverage_isSet = false; + prog_service_name = nullptr; + m_prog_service_name_isSet = false; + music_speech = nullptr; + m_music_speech_isSet = false; + mono_stereo = nullptr; + m_mono_stereo_isSet = false; + radio_text = nullptr; + m_radio_text_isSet = false; + time = nullptr; + m_time_isSet = false; + alt_frequencies = nullptr; + m_alt_frequencies_isSet = false; +} + +SWGRDSReport::~SWGRDSReport() { + this->cleanup(); +} + +void +SWGRDSReport::init() { + demod_status = 0; + m_demod_status_isSet = false; + decod_status = 0; + m_decod_status_isSet = false; + rds_demod_accum_db = 0.0f; + m_rds_demod_accum_db_isSet = false; + rds_demod_frequency = 0.0f; + m_rds_demod_frequency_isSet = false; + pid = new QString(""); + m_pid_isSet = false; + pi_type = new QString(""); + m_pi_type_isSet = false; + pi_coverage = new QString(""); + m_pi_coverage_isSet = false; + prog_service_name = new QString(""); + m_prog_service_name_isSet = false; + music_speech = new QString(""); + m_music_speech_isSet = false; + mono_stereo = new QString(""); + m_mono_stereo_isSet = false; + radio_text = new QString(""); + m_radio_text_isSet = false; + time = new QString(""); + m_time_isSet = false; + alt_frequencies = new QList(); + m_alt_frequencies_isSet = false; +} + +void +SWGRDSReport::cleanup() { + + + + + if(pid != nullptr) { + delete pid; + } + if(pi_type != nullptr) { + delete pi_type; + } + if(pi_coverage != nullptr) { + delete pi_coverage; + } + if(prog_service_name != nullptr) { + delete prog_service_name; + } + if(music_speech != nullptr) { + delete music_speech; + } + if(mono_stereo != nullptr) { + delete mono_stereo; + } + if(radio_text != nullptr) { + delete radio_text; + } + if(time != nullptr) { + delete time; + } + if(alt_frequencies != nullptr) { + auto arr = alt_frequencies; + for(auto o: *arr) { + delete o; + } + delete alt_frequencies; + } +} + +SWGRDSReport* +SWGRDSReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRDSReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&demod_status, pJson["demodStatus"], "qint32", ""); + + ::SWGSDRangel::setValue(&decod_status, pJson["decodStatus"], "qint32", ""); + + ::SWGSDRangel::setValue(&rds_demod_accum_db, pJson["rdsDemodAccumDB"], "float", ""); + + ::SWGSDRangel::setValue(&rds_demod_frequency, pJson["rdsDemodFrequency"], "float", ""); + + ::SWGSDRangel::setValue(&pid, pJson["pid"], "QString", "QString"); + + ::SWGSDRangel::setValue(&pi_type, pJson["piType"], "QString", "QString"); + + ::SWGSDRangel::setValue(&pi_coverage, pJson["piCoverage"], "QString", "QString"); + + ::SWGSDRangel::setValue(&prog_service_name, pJson["progServiceName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&music_speech, pJson["musicSpeech"], "QString", "QString"); + + ::SWGSDRangel::setValue(&mono_stereo, pJson["monoStereo"], "QString", "QString"); + + ::SWGSDRangel::setValue(&radio_text, pJson["radioText"], "QString", "QString"); + + ::SWGSDRangel::setValue(&time, pJson["time"], "QString", "QString"); + + + ::SWGSDRangel::setValue(&alt_frequencies, pJson["altFrequencies"], "QList", "SWGRDSReport_altFrequencies"); +} + +QString +SWGRDSReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRDSReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_demod_status_isSet){ + obj->insert("demodStatus", QJsonValue(demod_status)); + } + if(m_decod_status_isSet){ + obj->insert("decodStatus", QJsonValue(decod_status)); + } + if(m_rds_demod_accum_db_isSet){ + obj->insert("rdsDemodAccumDB", QJsonValue(rds_demod_accum_db)); + } + if(m_rds_demod_frequency_isSet){ + obj->insert("rdsDemodFrequency", QJsonValue(rds_demod_frequency)); + } + if(pid != nullptr && *pid != QString("")){ + toJsonValue(QString("pid"), pid, obj, QString("QString")); + } + if(pi_type != nullptr && *pi_type != QString("")){ + toJsonValue(QString("piType"), pi_type, obj, QString("QString")); + } + if(pi_coverage != nullptr && *pi_coverage != QString("")){ + toJsonValue(QString("piCoverage"), pi_coverage, obj, QString("QString")); + } + if(prog_service_name != nullptr && *prog_service_name != QString("")){ + toJsonValue(QString("progServiceName"), prog_service_name, obj, QString("QString")); + } + if(music_speech != nullptr && *music_speech != QString("")){ + toJsonValue(QString("musicSpeech"), music_speech, obj, QString("QString")); + } + if(mono_stereo != nullptr && *mono_stereo != QString("")){ + toJsonValue(QString("monoStereo"), mono_stereo, obj, QString("QString")); + } + if(radio_text != nullptr && *radio_text != QString("")){ + toJsonValue(QString("radioText"), radio_text, obj, QString("QString")); + } + if(time != nullptr && *time != QString("")){ + toJsonValue(QString("time"), time, obj, QString("QString")); + } + if(alt_frequencies->size() > 0){ + toJsonArray((QList*)alt_frequencies, obj, "altFrequencies", "SWGRDSReport_altFrequencies"); + } + + return obj; +} + +qint32 +SWGRDSReport::getDemodStatus() { + return demod_status; +} +void +SWGRDSReport::setDemodStatus(qint32 demod_status) { + this->demod_status = demod_status; + this->m_demod_status_isSet = true; +} + +qint32 +SWGRDSReport::getDecodStatus() { + return decod_status; +} +void +SWGRDSReport::setDecodStatus(qint32 decod_status) { + this->decod_status = decod_status; + this->m_decod_status_isSet = true; +} + +float +SWGRDSReport::getRdsDemodAccumDb() { + return rds_demod_accum_db; +} +void +SWGRDSReport::setRdsDemodAccumDb(float rds_demod_accum_db) { + this->rds_demod_accum_db = rds_demod_accum_db; + this->m_rds_demod_accum_db_isSet = true; +} + +float +SWGRDSReport::getRdsDemodFrequency() { + return rds_demod_frequency; +} +void +SWGRDSReport::setRdsDemodFrequency(float rds_demod_frequency) { + this->rds_demod_frequency = rds_demod_frequency; + this->m_rds_demod_frequency_isSet = true; +} + +QString* +SWGRDSReport::getPid() { + return pid; +} +void +SWGRDSReport::setPid(QString* pid) { + this->pid = pid; + this->m_pid_isSet = true; +} + +QString* +SWGRDSReport::getPiType() { + return pi_type; +} +void +SWGRDSReport::setPiType(QString* pi_type) { + this->pi_type = pi_type; + this->m_pi_type_isSet = true; +} + +QString* +SWGRDSReport::getPiCoverage() { + return pi_coverage; +} +void +SWGRDSReport::setPiCoverage(QString* pi_coverage) { + this->pi_coverage = pi_coverage; + this->m_pi_coverage_isSet = true; +} + +QString* +SWGRDSReport::getProgServiceName() { + return prog_service_name; +} +void +SWGRDSReport::setProgServiceName(QString* prog_service_name) { + this->prog_service_name = prog_service_name; + this->m_prog_service_name_isSet = true; +} + +QString* +SWGRDSReport::getMusicSpeech() { + return music_speech; +} +void +SWGRDSReport::setMusicSpeech(QString* music_speech) { + this->music_speech = music_speech; + this->m_music_speech_isSet = true; +} + +QString* +SWGRDSReport::getMonoStereo() { + return mono_stereo; +} +void +SWGRDSReport::setMonoStereo(QString* mono_stereo) { + this->mono_stereo = mono_stereo; + this->m_mono_stereo_isSet = true; +} + +QString* +SWGRDSReport::getRadioText() { + return radio_text; +} +void +SWGRDSReport::setRadioText(QString* radio_text) { + this->radio_text = radio_text; + this->m_radio_text_isSet = true; +} + +QString* +SWGRDSReport::getTime() { + return time; +} +void +SWGRDSReport::setTime(QString* time) { + this->time = time; + this->m_time_isSet = true; +} + +QList* +SWGRDSReport::getAltFrequencies() { + return alt_frequencies; +} +void +SWGRDSReport::setAltFrequencies(QList* alt_frequencies) { + this->alt_frequencies = alt_frequencies; + this->m_alt_frequencies_isSet = true; +} + + +bool +SWGRDSReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_demod_status_isSet){ isObjectUpdated = true; break;} + if(m_decod_status_isSet){ isObjectUpdated = true; break;} + if(m_rds_demod_accum_db_isSet){ isObjectUpdated = true; break;} + if(m_rds_demod_frequency_isSet){ isObjectUpdated = true; break;} + if(pid != nullptr && *pid != QString("")){ isObjectUpdated = true; break;} + if(pi_type != nullptr && *pi_type != QString("")){ isObjectUpdated = true; break;} + if(pi_coverage != nullptr && *pi_coverage != QString("")){ isObjectUpdated = true; break;} + if(prog_service_name != nullptr && *prog_service_name != QString("")){ isObjectUpdated = true; break;} + if(music_speech != nullptr && *music_speech != QString("")){ isObjectUpdated = true; break;} + if(mono_stereo != nullptr && *mono_stereo != QString("")){ isObjectUpdated = true; break;} + if(radio_text != nullptr && *radio_text != QString("")){ isObjectUpdated = true; break;} + if(time != nullptr && *time != QString("")){ isObjectUpdated = true; break;} + if(alt_frequencies->size() > 0){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport.h b/swagger/sdrangel/code/qt5/client/SWGRDSReport.h new file mode 100644 index 000000000..4bba8c371 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport.h @@ -0,0 +1,133 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGRDSReport.h + * + * RDS information + */ + +#ifndef SWGRDSReport_H_ +#define SWGRDSReport_H_ + +#include + + +#include "SWGRDSReport_altFrequencies.h" +#include +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRDSReport: public SWGObject { +public: + SWGRDSReport(); + SWGRDSReport(QString* json); + virtual ~SWGRDSReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRDSReport* fromJson(QString &jsonString) override; + + qint32 getDemodStatus(); + void setDemodStatus(qint32 demod_status); + + qint32 getDecodStatus(); + void setDecodStatus(qint32 decod_status); + + float getRdsDemodAccumDb(); + void setRdsDemodAccumDb(float rds_demod_accum_db); + + float getRdsDemodFrequency(); + void setRdsDemodFrequency(float rds_demod_frequency); + + QString* getPid(); + void setPid(QString* pid); + + QString* getPiType(); + void setPiType(QString* pi_type); + + QString* getPiCoverage(); + void setPiCoverage(QString* pi_coverage); + + QString* getProgServiceName(); + void setProgServiceName(QString* prog_service_name); + + QString* getMusicSpeech(); + void setMusicSpeech(QString* music_speech); + + QString* getMonoStereo(); + void setMonoStereo(QString* mono_stereo); + + QString* getRadioText(); + void setRadioText(QString* radio_text); + + QString* getTime(); + void setTime(QString* time); + + QList* getAltFrequencies(); + void setAltFrequencies(QList* alt_frequencies); + + + virtual bool isSet() override; + +private: + qint32 demod_status; + bool m_demod_status_isSet; + + qint32 decod_status; + bool m_decod_status_isSet; + + float rds_demod_accum_db; + bool m_rds_demod_accum_db_isSet; + + float rds_demod_frequency; + bool m_rds_demod_frequency_isSet; + + QString* pid; + bool m_pid_isSet; + + QString* pi_type; + bool m_pi_type_isSet; + + QString* pi_coverage; + bool m_pi_coverage_isSet; + + QString* prog_service_name; + bool m_prog_service_name_isSet; + + QString* music_speech; + bool m_music_speech_isSet; + + QString* mono_stereo; + bool m_mono_stereo_isSet; + + QString* radio_text; + bool m_radio_text_isSet; + + QString* time; + bool m_time_isSet; + + QList* alt_frequencies; + bool m_alt_frequencies_isSet; + +}; + +} + +#endif /* SWGRDSReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp new file mode 100644 index 000000000..3c2fd069c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGRDSReport_altFrequencies.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRDSReport_altFrequencies::SWGRDSReport_altFrequencies(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRDSReport_altFrequencies::SWGRDSReport_altFrequencies() { + frequency = 0.0f; + m_frequency_isSet = false; +} + +SWGRDSReport_altFrequencies::~SWGRDSReport_altFrequencies() { + this->cleanup(); +} + +void +SWGRDSReport_altFrequencies::init() { + frequency = 0.0f; + m_frequency_isSet = false; +} + +void +SWGRDSReport_altFrequencies::cleanup() { + +} + +SWGRDSReport_altFrequencies* +SWGRDSReport_altFrequencies::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRDSReport_altFrequencies::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&frequency, pJson["frequency"], "float", ""); + +} + +QString +SWGRDSReport_altFrequencies::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRDSReport_altFrequencies::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_frequency_isSet){ + obj->insert("frequency", QJsonValue(frequency)); + } + + return obj; +} + +float +SWGRDSReport_altFrequencies::getFrequency() { + return frequency; +} +void +SWGRDSReport_altFrequencies::setFrequency(float frequency) { + this->frequency = frequency; + this->m_frequency_isSet = true; +} + + +bool +SWGRDSReport_altFrequencies::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_frequency_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h new file mode 100644 index 000000000..c83910aa3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGRDSReport_altFrequencies.h + * + * + */ + +#ifndef SWGRDSReport_altFrequencies_H_ +#define SWGRDSReport_altFrequencies_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRDSReport_altFrequencies: public SWGObject { +public: + SWGRDSReport_altFrequencies(); + SWGRDSReport_altFrequencies(QString* json); + virtual ~SWGRDSReport_altFrequencies(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRDSReport_altFrequencies* fromJson(QString &jsonString) override; + + float getFrequency(); + void setFrequency(float frequency); + + + virtual bool isSet() override; + +private: + float frequency; + bool m_frequency_isSet; + +}; + +} + +#endif /* SWGRDSReport_altFrequencies_H_ */ From 128ac7ea1fc6a7f0f9f18e0811afcc3b61ddc271 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 24 May 2018 12:17:29 +0200 Subject: [PATCH 449/956] DSD demod: implemeted WEB API --- plugins/channelrx/demoddsd/CMakeLists.txt | 1 + plugins/channelrx/demoddsd/dsddemod.cpp | 333 ++++++++++- plugins/channelrx/demoddsd/dsddemod.h | 34 +- plugins/channelrx/demoddsd/dsddemodgui.cpp | 152 +---- plugins/channelrx/demoddsd/dsddemodgui.h | 19 +- .../channelrx/demoddsd/dsddemodsettings.cpp | 2 - plugins/channelrx/demoddsd/dsddemodsettings.h | 2 - sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 144 ++++- .../webapi/doc/swagger/include/DSDDemod.yaml | 100 ++++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + .../api/swagger/include/DSDDemod.yaml | 100 ++++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 144 ++++- .../code/qt5/client/SWGChannelReport.cpp | 23 + .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 + .../code/qt5/client/SWGChannelSettings.h | 7 + .../sdrangel/code/qt5/client/SWGDSDDemod.cpp | 85 +++ .../sdrangel/code/qt5/client/SWGDSDDemod.h | 52 ++ .../code/qt5/client/SWGDSDDemodReport.cpp | 362 ++++++++++++ .../code/qt5/client/SWGDSDDemodReport.h | 131 +++++ .../code/qt5/client/SWGDSDDemodSettings.cpp | 551 ++++++++++++++++++ .../code/qt5/client/SWGDSDDemodSettings.h | 185 ++++++ .../code/qt5/client/SWGModelFactory.h | 8 + 25 files changed, 2306 insertions(+), 168 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/DSDDemod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/DSDDemod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGDSDDemod.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGDSDDemod.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h diff --git a/plugins/channelrx/demoddsd/CMakeLists.txt b/plugins/channelrx/demoddsd/CMakeLists.txt index c4c35e25a..9197fece8 100644 --- a/plugins/channelrx/demoddsd/CMakeLists.txt +++ b/plugins/channelrx/demoddsd/CMakeLists.txt @@ -38,6 +38,7 @@ else (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBDSDCC_INCLUDE_DIR} ${LIBMBE_INCLUDE_DIR} ) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index d16142854..b522944db 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -21,12 +21,19 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGDSDDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGDSDDemodReport.h" +#include "SWGRDSReport.h" + #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" +#include "util/db.h" #include "dsddemod.h" @@ -56,6 +63,7 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : m_scopeXY(0), m_scopeEnabled(true), m_dsdDecoder(), + m_signalFormat(signalFormatNone), m_settingsMutex(QMutex::Recursive) { setObjectName(m_channelId); @@ -467,8 +475,6 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) << " m_slot2On: " << m_settings.m_slot2On << " m_tdmaStereo: " << m_settings.m_tdmaStereo << " m_pllLock: " << m_settings.m_pllLock - << " m_udpAddress: " << m_settings.m_udpAddress - << " m_udpPort: " << m_settings.m_udpPort << " m_highPassFilter: "<< m_settings.m_highPassFilter << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; @@ -568,3 +574,326 @@ bool DSDDemod::deserialize(const QByteArray& data) return false; } } + +const char *DSDDemod::updateAndGetStatusText() +{ + formatStatusText(); + return m_formatStatusText; +} + +void DSDDemod::formatStatusText() +{ + switch (getDecoder().getSyncType()) + { + case DSDcc::DSDDecoder::DSDSyncDMRDataMS: + case DSDcc::DSDDecoder::DSDSyncDMRDataP: + case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS: + case DSDcc::DSDDecoder::DSDSyncDMRVoiceP: + if (m_signalFormat != signalFormatDMR) + { + strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________"); + } + + switch (getDecoder().getStationType()) + { + case DSDcc::DSDDecoder::DSDBaseStation: + memcpy(&m_formatStatusText[5], "BS ", 3); + break; + case DSDcc::DSDDecoder::DSDMobileStation: + memcpy(&m_formatStatusText[5], "MS ", 3); + break; + default: + memcpy(&m_formatStatusText[5], "NA ", 3); + break; + } + + memcpy(&m_formatStatusText[12], getDecoder().getDMRDecoder().getSlot0Text(), 26); + memcpy(&m_formatStatusText[43], getDecoder().getDMRDecoder().getSlot1Text(), 26); + m_signalFormat = signalFormatDMR; + break; + case DSDcc::DSDDecoder::DSDSyncDStarHeaderN: + case DSDcc::DSDDecoder::DSDSyncDStarHeaderP: + case DSDcc::DSDDecoder::DSDSyncDStarN: + case DSDcc::DSDDecoder::DSDSyncDStarP: + if (m_signalFormat != signalFormatDStar) + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._"); + // MY UR RPT1 RPT2 Info Loc Target + } + + { + const std::string& rpt1 = getDecoder().getDStarDecoder().getRpt1(); + const std::string& rpt2 = getDecoder().getDStarDecoder().getRpt2(); + const std::string& mySign = getDecoder().getDStarDecoder().getMySign(); + const std::string& yrSign = getDecoder().getDStarDecoder().getYourSign(); + + if (rpt1.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[23], rpt1.c_str(), 8); + } + if (rpt2.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[32], rpt2.c_str(), 8); + } + if (yrSign.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[14], yrSign.c_str(), 8); + } + if (mySign.length() > 0) { // 0 or 13 + memcpy(&m_formatStatusText[0], mySign.c_str(), 13); + } + memcpy(&m_formatStatusText[41], getDecoder().getDStarDecoder().getInfoText(), 20); + memcpy(&m_formatStatusText[62], getDecoder().getDStarDecoder().getLocator(), 6); + snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f", + getDecoder().getDStarDecoder().getBearing(), + getDecoder().getDStarDecoder().getDistance()); + } + + m_formatStatusText[82] = '\0'; + m_signalFormat = signalFormatDStar; + break; + case DSDcc::DSDDecoder::DSDSyncDPMR: + snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d", + DSDcc::DSDdPMR::dpmrFrameTypes[(int) getDecoder().getDPMRDecoder().getFrameType()], + getDecoder().getDPMRDecoder().getColorCode(), + getDecoder().getDPMRDecoder().getOwnId(), + getDecoder().getDPMRDecoder().getCalledId()); + m_signalFormat = signalFormatDPMR; + break; + case DSDcc::DSDDecoder::DSDSyncYSF: + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444 + if (getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError) + { + snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) getDecoder().getYSFDecoder().getFICH().getFrameInformation()]); + } + else + { + snprintf(m_formatStatusText, 82, "%d ", (int) getDecoder().getYSFDecoder().getFICHError()); + } + + snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c", + DSDcc::DSDYSF::ysfDataTypeText[(int) getDecoder().getYSFDecoder().getFICH().getDataType()], + DSDcc::DSDYSF::ysfCallModeText[(int) getDecoder().getYSFDecoder().getFICH().getCallMode()], + getDecoder().getYSFDecoder().getFICH().getBlockTotal(), + getDecoder().getYSFDecoder().getFICH().getFrameTotal(), + (getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'), + (getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L')); + + if (getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled()) + { + snprintf(&m_formatStatusText[14], 82-14, "%03d", getDecoder().getYSFDecoder().getFICH().getSquelchCode()); + } + else + { + strncpy(&m_formatStatusText[14], "---", 82-14); + } + + char dest[13]; + + if (getDecoder().getYSFDecoder().radioIdMode()) + { + snprintf(dest, 12, "%-5s:%-5s", + getDecoder().getYSFDecoder().getDestId(), + getDecoder().getYSFDecoder().getSrcId()); + } + else + { + snprintf(dest, 11, "%-10s", getDecoder().getYSFDecoder().getDest()); + } + + snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s", + getDecoder().getYSFDecoder().getSrc(), + dest, + getDecoder().getYSFDecoder().getUplink(), + getDecoder().getYSFDecoder().getDownlink(), + getDecoder().getYSFDecoder().getRem4()); + + m_signalFormat = signalFormatYSF; + break; + default: + m_signalFormat = signalFormatNone; + m_formatStatusText[0] = '\0'; + break; + } + + m_formatStatusText[82] = '\0'; // guard +} + +int DSDDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings()); + response.getDsdDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int DSDDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + DSDDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getDsdDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getDsdDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getDsdDemodSettings()->getFmDeviation(); + } + if (channelSettingsKeys.contains("demodGain")) { + settings.m_demodGain = response.getDsdDemodSettings()->getDemodGain(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getDsdDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("baudRate")) { + settings.m_baudRate = response.getDsdDemodSettings()->getBaudRate(); + } + if (channelSettingsKeys.contains("squelchGate")) { + settings.m_squelchGate = response.getDsdDemodSettings()->getSquelchGate(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getDsdDemodSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getDsdDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("enableCosineFiltering")) { + settings.m_enableCosineFiltering = response.getDsdDemodSettings()->getEnableCosineFiltering() != 0; + } + if (channelSettingsKeys.contains("syncOrConstellation")) { + settings.m_syncOrConstellation = response.getDsdDemodSettings()->getSyncOrConstellation() != 0; + } + if (channelSettingsKeys.contains("slot1On")) { + settings.m_slot1On = response.getDsdDemodSettings()->getSlot1On() != 0; + } + if (channelSettingsKeys.contains("slot2On")) { + settings.m_slot2On = response.getDsdDemodSettings()->getSlot2On() != 0; + } + if (channelSettingsKeys.contains("tdmaStereo")) { + settings.m_tdmaStereo = response.getDsdDemodSettings()->getTdmaStereo() != 0; + } + if (channelSettingsKeys.contains("pllLock")) { + settings.m_pllLock = response.getDsdDemodSettings()->getPllLock() != 0; + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAmDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getAmDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getAmDemodSettings()->getAudioDeviceName(); + } + if (channelSettingsKeys.contains("highPassFilter")) { + settings.m_highPassFilter = response.getDsdDemodSettings()->getHighPassFilter() != 0; + } + if (channelSettingsKeys.contains("traceLengthMutliplier")) { + settings.m_traceLengthMutliplier = response.getDsdDemodSettings()->getTraceLengthMutliplier(); + } + if (channelSettingsKeys.contains("traceStroke")) { + settings.m_traceStroke = response.getDsdDemodSettings()->getTraceStroke(); + } + if (channelSettingsKeys.contains("traceDecay")) { + settings.m_traceDecay = response.getDsdDemodSettings()->getTraceDecay(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureDSDDemod *msg = MsgConfigureDSDDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("DSDDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureDSDDemod *msgToGUI = MsgConfigureDSDDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int DSDDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setDsdDemodReport(new SWGSDRangel::SWGDSDDemodReport()); + response.getDsdDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void DSDDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DSDDemodSettings& settings) +{ + response.getDsdDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getDsdDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getDsdDemodSettings()->setFmDeviation(settings.m_fmDeviation); + response.getDsdDemodSettings()->setDemodGain(settings.m_demodGain); + response.getDsdDemodSettings()->setVolume(settings.m_volume); + response.getDsdDemodSettings()->setBaudRate(settings.m_baudRate); + response.getDsdDemodSettings()->setSquelchGate(settings.m_squelchGate); + response.getDsdDemodSettings()->setSquelch(settings.m_squelch); + response.getDsdDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getDsdDemodSettings()->setEnableCosineFiltering(settings.m_enableCosineFiltering ? 1 : 0); + response.getDsdDemodSettings()->setSyncOrConstellation(settings.m_syncOrConstellation ? 1 : 0); + response.getDsdDemodSettings()->setSlot1On(settings.m_slot1On ? 1 : 0); + response.getDsdDemodSettings()->setSlot2On(settings.m_slot2On ? 1 : 0); + response.getDsdDemodSettings()->setTdmaStereo(settings.m_tdmaStereo ? 1 : 0); + response.getDsdDemodSettings()->setPllLock(settings.m_pllLock ? 1 : 0); + response.getDsdDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getDsdDemodSettings()->getTitle()) { + *response.getDsdDemodSettings()->getTitle() = settings.m_title; + } else { + response.getDsdDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getDsdDemodSettings()->getAudioDeviceName()) { + *response.getDsdDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getDsdDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + + response.getDsdDemodSettings()->setHighPassFilter(settings.m_highPassFilter ? 1 : 0); + response.getDsdDemodSettings()->setTraceLengthMutliplier(settings.m_traceLengthMutliplier); + response.getDsdDemodSettings()->setTraceStroke(settings.m_traceStroke); + response.getDsdDemodSettings()->setTraceDecay(settings.m_traceDecay); +} + +void DSDDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getDsdDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getDsdDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getDsdDemodReport()->setChannelSampleRate(m_inputSampleRate); + response.getDsdDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getDsdDemodReport()->setPllLocked(getDecoder().getSymbolPLLLocked() ? 1 : 0); + response.getDsdDemodReport()->setSlot1On(getDecoder().getVoice1On() ? 1 : 0); + response.getDsdDemodReport()->setSlot2On(getDecoder().getVoice2On() ? 1 : 0); + response.getDsdDemodReport()->setSyncType(new QString(getDecoder().getFrameTypeText())); + response.getDsdDemodReport()->setInLevel(getDecoder().getInLevel()); + response.getDsdDemodReport()->setCarierPosition(getDecoder().getCarrierPos()); + response.getDsdDemodReport()->setZeroCrossingPosition(getDecoder().getZeroCrossingPos()); + response.getDsdDemodReport()->setSyncRate(getDecoder().getSymbolSyncQuality()); + response.getDsdDemodReport()->setStatusText(new QString(updateAndGetStatusText())); +} diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index f7c71d21a..d426746f0 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -126,10 +126,35 @@ public: m_magsqCount = 0; } + const char *updateAndGetStatusText(); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + static const QString m_channelIdURI; static const QString m_channelId; private: + typedef enum + { + signalFormatNone, + signalFormatDMR, + signalFormatDStar, + signalFormatDPMR, + signalFormatYSF + } SignalFormat; //!< Used for status text formatting + class MsgConfigureMyPosition : public Message { MESSAGE_CLASS_DECLARATION @@ -196,15 +221,22 @@ private: bool m_scopeEnabled; DSDDecoder m_dsdDecoder; - QMutex m_settingsMutex; + char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text + SignalFormat m_signalFormat; //!< Used to keep formatting during successive calls for the same standard type PhaseDiscriminators m_phaseDiscri; + QMutex m_settingsMutex; + static const int m_udpBlockSize; void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const DSDDemodSettings& settings, bool force = false); + void formatStatusText(); + + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DSDDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; #endif // INCLUDE_DSDDEMOD_H diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index ccd4e2937..a59cb7327 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -242,10 +242,6 @@ void DSDDemodGUI::on_symbolPLLLock_toggled(bool checked) void DSDDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { - /* - if((widget == ui->spectrumContainer) && (DSDDemodGUI != NULL)) - m_dsdDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown); - */ } void DSDDemodGUI::onMenuDialogCalled(const QPoint &p) @@ -278,7 +274,6 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_deviceUISet(deviceUISet), m_channelMarker(this), m_doApplySettings(true), - m_signalFormat(signalFormatNone), m_enableCosineFiltering(false), m_syncOrConstellation(false), m_slot1On(false), @@ -460,145 +455,6 @@ void DSDDemodGUI::blockApplySettings(bool block) m_doApplySettings = !block; } -void DSDDemodGUI::formatStatusText() -{ - switch (m_dsdDemod->getDecoder().getSyncType()) - { - case DSDcc::DSDDecoder::DSDSyncDMRDataMS: - case DSDcc::DSDDecoder::DSDSyncDMRDataP: - case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS: - case DSDcc::DSDDecoder::DSDSyncDMRVoiceP: - if (m_signalFormat != signalFormatDMR) - { - strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________"); - } - - switch (m_dsdDemod->getDecoder().getStationType()) - { - case DSDcc::DSDDecoder::DSDBaseStation: - memcpy(&m_formatStatusText[5], "BS ", 3); - break; - case DSDcc::DSDDecoder::DSDMobileStation: - memcpy(&m_formatStatusText[5], "MS ", 3); - break; - default: - memcpy(&m_formatStatusText[5], "NA ", 3); - break; - } - - memcpy(&m_formatStatusText[12], m_dsdDemod->getDecoder().getDMRDecoder().getSlot0Text(), 26); - memcpy(&m_formatStatusText[43], m_dsdDemod->getDecoder().getDMRDecoder().getSlot1Text(), 26); - m_signalFormat = signalFormatDMR; - break; - case DSDcc::DSDDecoder::DSDSyncDStarHeaderN: - case DSDcc::DSDDecoder::DSDSyncDStarHeaderP: - case DSDcc::DSDDecoder::DSDSyncDStarN: - case DSDcc::DSDDecoder::DSDSyncDStarP: - if (m_signalFormat != signalFormatDStar) - { - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._"); - // MY UR RPT1 RPT2 Info Loc Target - } - - { - const std::string& rpt1 = m_dsdDemod->getDecoder().getDStarDecoder().getRpt1(); - const std::string& rpt2 = m_dsdDemod->getDecoder().getDStarDecoder().getRpt2(); - const std::string& mySign = m_dsdDemod->getDecoder().getDStarDecoder().getMySign(); - const std::string& yrSign = m_dsdDemod->getDecoder().getDStarDecoder().getYourSign(); - - if (rpt1.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[23], rpt1.c_str(), 8); - } - if (rpt2.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[32], rpt2.c_str(), 8); - } - if (yrSign.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[14], yrSign.c_str(), 8); - } - if (mySign.length() > 0) { // 0 or 13 - memcpy(&m_formatStatusText[0], mySign.c_str(), 13); - } - memcpy(&m_formatStatusText[41], m_dsdDemod->getDecoder().getDStarDecoder().getInfoText(), 20); - memcpy(&m_formatStatusText[62], m_dsdDemod->getDecoder().getDStarDecoder().getLocator(), 6); - snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f", - m_dsdDemod->getDecoder().getDStarDecoder().getBearing(), - m_dsdDemod->getDecoder().getDStarDecoder().getDistance()); - } - - m_formatStatusText[82] = '\0'; - m_signalFormat = signalFormatDStar; - break; - case DSDcc::DSDDecoder::DSDSyncDPMR: - snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d", - DSDcc::DSDdPMR::dpmrFrameTypes[(int) m_dsdDemod->getDecoder().getDPMRDecoder().getFrameType()], - m_dsdDemod->getDecoder().getDPMRDecoder().getColorCode(), - m_dsdDemod->getDecoder().getDPMRDecoder().getOwnId(), - m_dsdDemod->getDecoder().getDPMRDecoder().getCalledId()); - m_signalFormat = signalFormatDPMR; - break; - case DSDcc::DSDDecoder::DSDSyncYSF: - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - // C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444 - if (m_dsdDemod->getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError) - { - snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getFrameInformation()]); - } - else - { - snprintf(m_formatStatusText, 82, "%d ", (int) m_dsdDemod->getDecoder().getYSFDecoder().getFICHError()); - } - - snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c", - DSDcc::DSDYSF::ysfDataTypeText[(int) m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getDataType()], - DSDcc::DSDYSF::ysfCallModeText[(int) m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getCallMode()], - m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getBlockTotal(), - m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getFrameTotal(), - (m_dsdDemod->getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'), - (m_dsdDemod->getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L')); - - if (m_dsdDemod->getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled()) - { - snprintf(&m_formatStatusText[14], 82-14, "%03d", m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getSquelchCode()); - } - else - { - strncpy(&m_formatStatusText[14], "---", 82-14); - } - - char dest[13]; - - if ( m_dsdDemod->getDecoder().getYSFDecoder().radioIdMode()) - { - snprintf(dest, 12, "%-5s:%-5s", - m_dsdDemod->getDecoder().getYSFDecoder().getDestId(), - m_dsdDemod->getDecoder().getYSFDecoder().getSrcId()); - } - else - { - snprintf(dest, 11, "%-10s", m_dsdDemod->getDecoder().getYSFDecoder().getDest()); - } - - snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s", - m_dsdDemod->getDecoder().getYSFDecoder().getSrc(), - dest, - m_dsdDemod->getDecoder().getYSFDecoder().getUplink(), - m_dsdDemod->getDecoder().getYSFDecoder().getDownlink(), - m_dsdDemod->getDecoder().getYSFDecoder().getRem4()); - - m_signalFormat = signalFormatYSF; - break; - default: - m_signalFormat = signalFormatNone; - m_formatStatusText[0] = '\0'; - break; - } - - m_formatStatusText[82] = '\0'; // guard -} - void DSDDemodGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); @@ -685,14 +541,14 @@ void DSDDemodGUI::tick() ui->syncText->setText(QString(frameTypeText)); - formatStatusText(); - ui->formatStatusText->setText(QString(m_formatStatusText)); + const char *formatStatusText = m_dsdDemod->updateAndGetStatusText(); + ui->formatStatusText->setText(QString(formatStatusText)); if (ui->activateStatusLog->isChecked()) { - m_dsdStatusTextDialog.addLine(QString(m_formatStatusText)); + m_dsdStatusTextDialog.addLine(QString(formatStatusText)); } - if (m_formatStatusText[0] == '\0') { + if (formatStatusText[0] == '\0') { ui->formatStatusText->setStyleSheet("QLabel { background:rgb(53,53,53); }"); // turn off background } else { ui->formatStatusText->setStyleSheet("QLabel { background:rgb(37,53,39); }"); // turn on background diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index 728fdf446..01f1e4e04 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -64,14 +64,14 @@ public slots: void channelMarkerHighlightedByCursor(); private: - typedef enum - { - signalFormatNone, - signalFormatDMR, - signalFormatDStar, - signalFormatDPMR, - signalFormatYSF - } SignalFormat; +// typedef enum +// { +// signalFormatNone, +// signalFormatDMR, +// signalFormatDStar, +// signalFormatDPMR, +// signalFormatYSF +// } SignalFormat; Ui::DSDDemodGUI* ui; PluginAPI* m_pluginAPI; @@ -79,8 +79,6 @@ private: ChannelMarker m_channelMarker; DSDDemodSettings m_settings; bool m_doApplySettings; - char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text - SignalFormat m_signalFormat; ScopeVisXY* m_scopeVisXY; @@ -113,7 +111,6 @@ private: void enterEvent(QEvent*); private slots: - void formatStatusText(); void on_deltaFrequency_changed(qint64 value); void on_rfBW_valueChanged(int index); void on_demodGain_valueChanged(int value); diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index de7c76c80..ee2734137 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -45,8 +45,6 @@ void DSDDemodSettings::resetToDefaults() m_slot2On = false; m_tdmaStereo = false; m_pllLock = true; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; m_rgbColor = QColor(0, 255, 255).rgb(); m_title = "DSD Demodulator"; m_highPassFilter = false; diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.h b/plugins/channelrx/demoddsd/dsddemodsettings.h index 271b6dac0..e22bfa3f0 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.h +++ b/plugins/channelrx/demoddsd/dsddemodsettings.h @@ -38,8 +38,6 @@ struct DSDDemodSettings bool m_slot2On; bool m_tdmaStereo; bool m_pllLock; - QString m_udpAddress; - quint16 m_udpPort; quint32 m_rgbColor; QString m_title; bool m_highPassFilter; diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 304d48a65..4646fb380 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -12,6 +12,7 @@ webapi/doc/swagger/include/AMMod.yaml webapi/doc/swagger/include/ATVMod.yaml webapi/doc/swagger/include/BFMDemod.yaml + webapi/doc/swagger/include/DSDDemod.yaml webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml webapi/doc/swagger/include/SSBMod.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 598f63bb6..5e0f819be 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1307,6 +1307,9 @@ margin-bottom: 20px; "BFMDemodReport" : { "$ref" : "#/definitions/BFMDemodReport" }, + "DSDDemodReport" : { + "$ref" : "#/definitions/DSDDemodReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1349,6 +1352,9 @@ margin-bottom: 20px; "BFMDemodSettings" : { "$ref" : "#/definitions/BFMDemodSettings" }, + "DSDDemodSettings" : { + "$ref" : "#/definitions/DSDDemodSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -1383,6 +1389,142 @@ margin-bottom: 20px; } }, "description" : "All channels detailed information" +}; + defs.DSDDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "pllLocked" : { + "type" : "integer", + "description" : "symbol PLL status (1 if locked else 0)" + }, + "slot1On" : { + "type" : "integer", + "description" : "slot 1 status (1 if active else 0)" + }, + "slot2On" : { + "type" : "integer", + "description" : "slot 2 status (1 if active else 0)" + }, + "syncType" : { + "type" : "string", + "description" : "type of frame synchronized" + }, + "inLevel" : { + "type" : "integer", + "description" : "decoder input level after discriminator. Percent of mid scale (aim 100)." + }, + "carierPosition" : { + "type" : "integer", + "description" : "position of carrier relative to discriminator center. Percent of scale (aim 0)." + }, + "zeroCrossingPosition" : { + "type" : "integer", + "description" : "position of symbol synchronizer zero crossing in number of samples (aim 0)." + }, + "syncRate" : { + "type" : "integer", + "description" : "successful synchronization rate. Percent of expected symbols (aim 100)." + }, + "statusText" : { + "type" : "string", + "description" : "mode dependent status messages (ref UI documentation)" + } + }, + "description" : "DSDDemod" +}; + defs.DSDDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "number", + "format" : "float" + }, + "demodGain" : { + "type" : "number", + "format" : "float" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "baudRate" : { + "type" : "integer" + }, + "squelchGate" : { + "type" : "integer" + }, + "squelch" : { + "type" : "number", + "format" : "float" + }, + "audioMute" : { + "type" : "integer" + }, + "enableCosineFiltering" : { + "type" : "integer" + }, + "syncOrConstellation" : { + "type" : "integer" + }, + "slot1On" : { + "type" : "integer" + }, + "slot2On" : { + "type" : "integer" + }, + "tdmaStereo" : { + "type" : "integer" + }, + "pllLock" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "highPassFilter" : { + "type" : "integer" + }, + "traceLengthMutliplier" : { + "type" : "integer", + "description" : "multiply by 50ms" + }, + "traceStroke" : { + "type" : "integer", + "description" : "0 to 255" + }, + "traceDecay" : { + "type" : "integer", + "description" : "0 to 255" + } + }, + "description" : "DSDDemod" }; defs.DVSeralDevices = { "required" : [ "nbDevices" ], @@ -20811,7 +20953,7 @@ except ApiException as e:
- Generated 2018-05-23T14:44:33.513+02:00 + Generated 2018-05-24T10:19:21.195+02:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/DSDDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/DSDDemod.yaml new file mode 100644 index 000000000..273a71222 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/DSDDemod.yaml @@ -0,0 +1,100 @@ +DSDDemodSettings: + description: DSDDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + fmDeviation: + type: number + format: float + demodGain: + type: number + format: float + volume: + type: number + format: float + baudRate: + type: integer + squelchGate: + type: integer + squelch: + type: number + format: float + audioMute: + type: integer + enableCosineFiltering: + type: integer + syncOrConstellation: + type: integer + slot1On: + type: integer + slot2On: + type: integer + tdmaStereo: + type: integer + pllLock: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + highPassFilter: + type: integer + traceLengthMutliplier: + description: multiply by 50ms + type: integer + traceStroke: + description: 0 to 255 + type: integer + traceDecay: + description: 0 to 255 + type: integer + +DSDDemodReport: + description: DSDDemod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer + squelch: + description: squelch status (1 if open else 0) + type: integer + pllLocked: + description: symbol PLL status (1 if locked else 0) + type: integer + slot1On: + description: slot 1 status (1 if active else 0) + type: integer + slot2On: + description: slot 2 status (1 if active else 0) + type: integer + syncType: + description: type of frame synchronized + type: string + inLevel: + description: decoder input level after discriminator. Percent of mid scale (aim 100). + type: integer + carierPosition: + description: position of carrier relative to discriminator center. Percent of scale (aim 0). + type: integer + zeroCrossingPosition: + description: position of symbol synchronizer zero crossing in number of samples (aim 0). + type: integer + syncRate: + description: successful synchronization rate. Percent of expected symbols (aim 100). + type: integer + statusText: + description: mode dependent status messages (ref UI documentation) + type: string + + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 32fe1bc67..0a9e735f9 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1753,6 +1753,8 @@ definitions: $ref: "/doc/swagger/include/ATVMod.yaml#/ATVModSettings" BFMDemodSettings: $ref: "/doc/swagger/include/BFMDemod.yaml#/BFMDemodSettings" + DSDDemodSettings: + $ref: "/doc/swagger/include/DSDDemod.yaml#/DSDDemodSettings" NFMDemodSettings: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: @@ -1782,6 +1784,8 @@ definitions: $ref: "/doc/swagger/include/ATVMod.yaml#/ATVModReport" BFMDemodReport: $ref: "/doc/swagger/include/BFMDemod.yaml#/BFMDemodReport" + DSDDemodReport: + $ref: "/doc/swagger/include/DSDDemod.yaml#/DSDDemodReport" NFMDemodReport: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: diff --git a/swagger/sdrangel/api/swagger/include/DSDDemod.yaml b/swagger/sdrangel/api/swagger/include/DSDDemod.yaml new file mode 100644 index 000000000..91895f382 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/DSDDemod.yaml @@ -0,0 +1,100 @@ +DSDDemodSettings: + description: DSDDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + fmDeviation: + type: number + format: float + demodGain: + type: number + format: float + volume: + type: number + format: float + baudRate: + type: integer + squelchGate: + type: integer + squelch: + type: number + format: float + audioMute: + type: integer + enableCosineFiltering: + type: integer + syncOrConstellation: + type: integer + slot1On: + type: integer + slot2On: + type: integer + tdmaStereo: + type: integer + pllLock: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + highPassFilter: + type: integer + traceLengthMutliplier: + description: multiply by 50ms + type: integer + traceStroke: + description: 0 to 255 + type: integer + traceDecay: + description: 0 to 255 + type: integer + +DSDDemodReport: + description: DSDDemod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer + squelch: + description: squelch status (1 if open else 0) + type: integer + pllLocked: + description: symbol PLL status (1 if locked else 0) + type: integer + slot1On: + description: slot 1 status (1 if voice active else 0) + type: integer + slot2On: + description: slot 2 status (1 if voice active else 0) + type: integer + syncType: + description: type of frame synchronized + type: string + inLevel: + description: decoder input level after discriminator. Percent of mid scale (aim 100). + type: integer + carierPosition: + description: position of carrier relative to discriminator center. Percent of scale (aim 0). + type: integer + zeroCrossingPosition: + description: position of symbol synchronizer zero crossing in number of samples (aim 0). + type: integer + syncRate: + description: successful synchronization rate. Percent of expected symbols (aim 100). + type: integer + statusText: + description: mode dependent status messages (ref UI documentation) + type: string + + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 63c27d7ac..fee0e1bd5 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1753,6 +1753,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/ATVMod.yaml#/ATVModSettings" BFMDemodSettings: $ref: "http://localhost:8081/api/swagger/include/BFMDemod.yaml#/BFMDemodSettings" + DSDDemodSettings: + $ref: "http://localhost:8081/api/swagger/include/DSDDemod.yaml#/DSDDemodSettings" NFMDemodSettings: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: @@ -1782,6 +1784,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/ATVMod.yaml#/ATVModReport" BFMDemodReport: $ref: "http://localhost:8081/api/swagger/include/BFMDemod.yaml#/BFMDemodReport" + DSDDemodReport: + $ref: "http://localhost:8081/api/swagger/include/DSDDemod.yaml#/DSDDemodReport" NFMDemodReport: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 598f63bb6..5e0f819be 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1307,6 +1307,9 @@ margin-bottom: 20px; "BFMDemodReport" : { "$ref" : "#/definitions/BFMDemodReport" }, + "DSDDemodReport" : { + "$ref" : "#/definitions/DSDDemodReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1349,6 +1352,9 @@ margin-bottom: 20px; "BFMDemodSettings" : { "$ref" : "#/definitions/BFMDemodSettings" }, + "DSDDemodSettings" : { + "$ref" : "#/definitions/DSDDemodSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -1383,6 +1389,142 @@ margin-bottom: 20px; } }, "description" : "All channels detailed information" +}; + defs.DSDDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "pllLocked" : { + "type" : "integer", + "description" : "symbol PLL status (1 if locked else 0)" + }, + "slot1On" : { + "type" : "integer", + "description" : "slot 1 status (1 if active else 0)" + }, + "slot2On" : { + "type" : "integer", + "description" : "slot 2 status (1 if active else 0)" + }, + "syncType" : { + "type" : "string", + "description" : "type of frame synchronized" + }, + "inLevel" : { + "type" : "integer", + "description" : "decoder input level after discriminator. Percent of mid scale (aim 100)." + }, + "carierPosition" : { + "type" : "integer", + "description" : "position of carrier relative to discriminator center. Percent of scale (aim 0)." + }, + "zeroCrossingPosition" : { + "type" : "integer", + "description" : "position of symbol synchronizer zero crossing in number of samples (aim 0)." + }, + "syncRate" : { + "type" : "integer", + "description" : "successful synchronization rate. Percent of expected symbols (aim 100)." + }, + "statusText" : { + "type" : "string", + "description" : "mode dependent status messages (ref UI documentation)" + } + }, + "description" : "DSDDemod" +}; + defs.DSDDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "number", + "format" : "float" + }, + "demodGain" : { + "type" : "number", + "format" : "float" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "baudRate" : { + "type" : "integer" + }, + "squelchGate" : { + "type" : "integer" + }, + "squelch" : { + "type" : "number", + "format" : "float" + }, + "audioMute" : { + "type" : "integer" + }, + "enableCosineFiltering" : { + "type" : "integer" + }, + "syncOrConstellation" : { + "type" : "integer" + }, + "slot1On" : { + "type" : "integer" + }, + "slot2On" : { + "type" : "integer" + }, + "tdmaStereo" : { + "type" : "integer" + }, + "pllLock" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "highPassFilter" : { + "type" : "integer" + }, + "traceLengthMutliplier" : { + "type" : "integer", + "description" : "multiply by 50ms" + }, + "traceStroke" : { + "type" : "integer", + "description" : "0 to 255" + }, + "traceDecay" : { + "type" : "integer", + "description" : "0 to 255" + } + }, + "description" : "DSDDemod" }; defs.DVSeralDevices = { "required" : [ "nbDevices" ], @@ -20811,7 +20953,7 @@ except ApiException as e:
- Generated 2018-05-23T14:44:33.513+02:00 + Generated 2018-05-24T10:19:21.195+02:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index d1811cdc9..c358588a8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -40,6 +40,8 @@ SWGChannelReport::SWGChannelReport() { m_atv_mod_report_isSet = false; bfm_demod_report = nullptr; m_bfm_demod_report_isSet = false; + dsd_demod_report = nullptr; + m_dsd_demod_report_isSet = false; nfm_demod_report = nullptr; m_nfm_demod_report_isSet = false; nfm_mod_report = nullptr; @@ -70,6 +72,8 @@ SWGChannelReport::init() { m_atv_mod_report_isSet = false; bfm_demod_report = new SWGBFMDemodReport(); m_bfm_demod_report_isSet = false; + dsd_demod_report = new SWGDSDDemodReport(); + m_dsd_demod_report_isSet = false; nfm_demod_report = new SWGNFMDemodReport(); m_nfm_demod_report_isSet = false; nfm_mod_report = new SWGNFMModReport(); @@ -100,6 +104,9 @@ SWGChannelReport::cleanup() { if(bfm_demod_report != nullptr) { delete bfm_demod_report; } + if(dsd_demod_report != nullptr) { + delete dsd_demod_report; + } if(nfm_demod_report != nullptr) { delete nfm_demod_report; } @@ -140,6 +147,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&bfm_demod_report, pJson["BFMDemodReport"], "SWGBFMDemodReport", "SWGBFMDemodReport"); + ::SWGSDRangel::setValue(&dsd_demod_report, pJson["DSDDemodReport"], "SWGDSDDemodReport", "SWGDSDDemodReport"); + ::SWGSDRangel::setValue(&nfm_demod_report, pJson["NFMDemodReport"], "SWGNFMDemodReport", "SWGNFMDemodReport"); ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); @@ -184,6 +193,9 @@ SWGChannelReport::asJsonObject() { if((bfm_demod_report != nullptr) && (bfm_demod_report->isSet())){ toJsonValue(QString("BFMDemodReport"), bfm_demod_report, obj, QString("SWGBFMDemodReport")); } + if((dsd_demod_report != nullptr) && (dsd_demod_report->isSet())){ + toJsonValue(QString("DSDDemodReport"), dsd_demod_report, obj, QString("SWGDSDDemodReport")); + } if((nfm_demod_report != nullptr) && (nfm_demod_report->isSet())){ toJsonValue(QString("NFMDemodReport"), nfm_demod_report, obj, QString("SWGNFMDemodReport")); } @@ -263,6 +275,16 @@ SWGChannelReport::setBfmDemodReport(SWGBFMDemodReport* bfm_demod_report) { this->m_bfm_demod_report_isSet = true; } +SWGDSDDemodReport* +SWGChannelReport::getDsdDemodReport() { + return dsd_demod_report; +} +void +SWGChannelReport::setDsdDemodReport(SWGDSDDemodReport* dsd_demod_report) { + this->dsd_demod_report = dsd_demod_report; + this->m_dsd_demod_report_isSet = true; +} + SWGNFMDemodReport* SWGChannelReport::getNfmDemodReport() { return nfm_demod_report; @@ -324,6 +346,7 @@ SWGChannelReport::isSet(){ if(am_mod_report != nullptr && am_mod_report->isSet()){ isObjectUpdated = true; break;} if(atv_mod_report != nullptr && atv_mod_report->isSet()){ isObjectUpdated = true; break;} if(bfm_demod_report != nullptr && bfm_demod_report->isSet()){ isObjectUpdated = true; break;} + if(dsd_demod_report != nullptr && dsd_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 2633909a8..ff112531c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -26,6 +26,7 @@ #include "SWGAMModReport.h" #include "SWGATVModReport.h" #include "SWGBFMDemodReport.h" +#include "SWGDSDDemodReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" #include "SWGSSBModReport.h" @@ -69,6 +70,9 @@ public: SWGBFMDemodReport* getBfmDemodReport(); void setBfmDemodReport(SWGBFMDemodReport* bfm_demod_report); + SWGDSDDemodReport* getDsdDemodReport(); + void setDsdDemodReport(SWGDSDDemodReport* dsd_demod_report); + SWGNFMDemodReport* getNfmDemodReport(); void setNfmDemodReport(SWGNFMDemodReport* nfm_demod_report); @@ -106,6 +110,9 @@ private: SWGBFMDemodReport* bfm_demod_report; bool m_bfm_demod_report_isSet; + SWGDSDDemodReport* dsd_demod_report; + bool m_dsd_demod_report_isSet; + SWGNFMDemodReport* nfm_demod_report; bool m_nfm_demod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 0c81a0906..32c6b59df 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -40,6 +40,8 @@ SWGChannelSettings::SWGChannelSettings() { m_atv_mod_settings_isSet = false; bfm_demod_settings = nullptr; m_bfm_demod_settings_isSet = false; + dsd_demod_settings = nullptr; + m_dsd_demod_settings_isSet = false; nfm_demod_settings = nullptr; m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; @@ -70,6 +72,8 @@ SWGChannelSettings::init() { m_atv_mod_settings_isSet = false; bfm_demod_settings = new SWGBFMDemodSettings(); m_bfm_demod_settings_isSet = false; + dsd_demod_settings = new SWGDSDDemodSettings(); + m_dsd_demod_settings_isSet = false; nfm_demod_settings = new SWGNFMDemodSettings(); m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); @@ -100,6 +104,9 @@ SWGChannelSettings::cleanup() { if(bfm_demod_settings != nullptr) { delete bfm_demod_settings; } + if(dsd_demod_settings != nullptr) { + delete dsd_demod_settings; + } if(nfm_demod_settings != nullptr) { delete nfm_demod_settings; } @@ -140,6 +147,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&bfm_demod_settings, pJson["BFMDemodSettings"], "SWGBFMDemodSettings", "SWGBFMDemodSettings"); + ::SWGSDRangel::setValue(&dsd_demod_settings, pJson["DSDDemodSettings"], "SWGDSDDemodSettings", "SWGDSDDemodSettings"); + ::SWGSDRangel::setValue(&nfm_demod_settings, pJson["NFMDemodSettings"], "SWGNFMDemodSettings", "SWGNFMDemodSettings"); ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); @@ -184,6 +193,9 @@ SWGChannelSettings::asJsonObject() { if((bfm_demod_settings != nullptr) && (bfm_demod_settings->isSet())){ toJsonValue(QString("BFMDemodSettings"), bfm_demod_settings, obj, QString("SWGBFMDemodSettings")); } + if((dsd_demod_settings != nullptr) && (dsd_demod_settings->isSet())){ + toJsonValue(QString("DSDDemodSettings"), dsd_demod_settings, obj, QString("SWGDSDDemodSettings")); + } if((nfm_demod_settings != nullptr) && (nfm_demod_settings->isSet())){ toJsonValue(QString("NFMDemodSettings"), nfm_demod_settings, obj, QString("SWGNFMDemodSettings")); } @@ -263,6 +275,16 @@ SWGChannelSettings::setBfmDemodSettings(SWGBFMDemodSettings* bfm_demod_settings) this->m_bfm_demod_settings_isSet = true; } +SWGDSDDemodSettings* +SWGChannelSettings::getDsdDemodSettings() { + return dsd_demod_settings; +} +void +SWGChannelSettings::setDsdDemodSettings(SWGDSDDemodSettings* dsd_demod_settings) { + this->dsd_demod_settings = dsd_demod_settings; + this->m_dsd_demod_settings_isSet = true; +} + SWGNFMDemodSettings* SWGChannelSettings::getNfmDemodSettings() { return nfm_demod_settings; @@ -324,6 +346,7 @@ SWGChannelSettings::isSet(){ if(am_mod_settings != nullptr && am_mod_settings->isSet()){ isObjectUpdated = true; break;} if(atv_mod_settings != nullptr && atv_mod_settings->isSet()){ isObjectUpdated = true; break;} if(bfm_demod_settings != nullptr && bfm_demod_settings->isSet()){ isObjectUpdated = true; break;} + if(dsd_demod_settings != nullptr && dsd_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 16335a07e..d3fb1d576 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -26,6 +26,7 @@ #include "SWGAMModSettings.h" #include "SWGATVModSettings.h" #include "SWGBFMDemodSettings.h" +#include "SWGDSDDemodSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" #include "SWGSSBModSettings.h" @@ -69,6 +70,9 @@ public: SWGBFMDemodSettings* getBfmDemodSettings(); void setBfmDemodSettings(SWGBFMDemodSettings* bfm_demod_settings); + SWGDSDDemodSettings* getDsdDemodSettings(); + void setDsdDemodSettings(SWGDSDDemodSettings* dsd_demod_settings); + SWGNFMDemodSettings* getNfmDemodSettings(); void setNfmDemodSettings(SWGNFMDemodSettings* nfm_demod_settings); @@ -106,6 +110,9 @@ private: SWGBFMDemodSettings* bfm_demod_settings; bool m_bfm_demod_settings_isSet; + SWGDSDDemodSettings* dsd_demod_settings; + bool m_dsd_demod_settings_isSet; + SWGNFMDemodSettings* nfm_demod_settings; bool m_nfm_demod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemod.cpp b/swagger/sdrangel/code/qt5/client/SWGDSDDemod.cpp new file mode 100644 index 000000000..2bfe85a51 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemod.cpp @@ -0,0 +1,85 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGDSDDemod.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDSDDemod::SWGDSDDemod(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDSDDemod::SWGDSDDemod() { +} + +SWGDSDDemod::~SWGDSDDemod() { + this->cleanup(); +} + +void +SWGDSDDemod::init() { +} + +void +SWGDSDDemod::cleanup() { +} + +SWGDSDDemod* +SWGDSDDemod::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDSDDemod::fromJsonObject(QJsonObject &pJson) { +} + +QString +SWGDSDDemod::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDSDDemod::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + + return obj; +} + + +bool +SWGDSDDemod::isSet(){ + bool isObjectUpdated = false; + do{ + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemod.h b/swagger/sdrangel/code/qt5/client/SWGDSDDemod.h new file mode 100644 index 000000000..6bb00ee4a --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemod.h @@ -0,0 +1,52 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGDSDDemod.h + * + * + */ + +#ifndef SWGDSDDemod_H_ +#define SWGDSDDemod_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDSDDemod: public SWGObject { +public: + SWGDSDDemod(); + SWGDSDDemod(QString* json); + virtual ~SWGDSDDemod(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDSDDemod* fromJson(QString &jsonString) override; + + + virtual bool isSet() override; + +private: +}; + +} + +#endif /* SWGDSDDemod_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp new file mode 100644 index 000000000..944192981 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp @@ -0,0 +1,362 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGDSDDemodReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDSDDemodReport::SWGDSDDemodReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDSDDemodReport::SWGDSDDemodReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; + squelch = 0; + m_squelch_isSet = false; + pll_locked = 0; + m_pll_locked_isSet = false; + slot1_on = 0; + m_slot1_on_isSet = false; + slot2_on = 0; + m_slot2_on_isSet = false; + sync_type = nullptr; + m_sync_type_isSet = false; + in_level = 0; + m_in_level_isSet = false; + carier_position = 0; + m_carier_position_isSet = false; + zero_crossing_position = 0; + m_zero_crossing_position_isSet = false; + sync_rate = 0; + m_sync_rate_isSet = false; + status_text = nullptr; + m_status_text_isSet = false; +} + +SWGDSDDemodReport::~SWGDSDDemodReport() { + this->cleanup(); +} + +void +SWGDSDDemodReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; + squelch = 0; + m_squelch_isSet = false; + pll_locked = 0; + m_pll_locked_isSet = false; + slot1_on = 0; + m_slot1_on_isSet = false; + slot2_on = 0; + m_slot2_on_isSet = false; + sync_type = new QString(""); + m_sync_type_isSet = false; + in_level = 0; + m_in_level_isSet = false; + carier_position = 0; + m_carier_position_isSet = false; + zero_crossing_position = 0; + m_zero_crossing_position_isSet = false; + sync_rate = 0; + m_sync_rate_isSet = false; + status_text = new QString(""); + m_status_text_isSet = false; +} + +void +SWGDSDDemodReport::cleanup() { + + + + + + + + if(sync_type != nullptr) { + delete sync_type; + } + + + + + if(status_text != nullptr) { + delete status_text; + } +} + +SWGDSDDemodReport* +SWGDSDDemodReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDSDDemodReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "qint32", ""); + + ::SWGSDRangel::setValue(&pll_locked, pJson["pllLocked"], "qint32", ""); + + ::SWGSDRangel::setValue(&slot1_on, pJson["slot1On"], "qint32", ""); + + ::SWGSDRangel::setValue(&slot2_on, pJson["slot2On"], "qint32", ""); + + ::SWGSDRangel::setValue(&sync_type, pJson["syncType"], "QString", "QString"); + + ::SWGSDRangel::setValue(&in_level, pJson["inLevel"], "qint32", ""); + + ::SWGSDRangel::setValue(&carier_position, pJson["carierPosition"], "qint32", ""); + + ::SWGSDRangel::setValue(&zero_crossing_position, pJson["zeroCrossingPosition"], "qint32", ""); + + ::SWGSDRangel::setValue(&sync_rate, pJson["syncRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&status_text, pJson["statusText"], "QString", "QString"); + +} + +QString +SWGDSDDemodReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDSDDemodReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_audio_sample_rate_isSet){ + obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_pll_locked_isSet){ + obj->insert("pllLocked", QJsonValue(pll_locked)); + } + if(m_slot1_on_isSet){ + obj->insert("slot1On", QJsonValue(slot1_on)); + } + if(m_slot2_on_isSet){ + obj->insert("slot2On", QJsonValue(slot2_on)); + } + if(sync_type != nullptr && *sync_type != QString("")){ + toJsonValue(QString("syncType"), sync_type, obj, QString("QString")); + } + if(m_in_level_isSet){ + obj->insert("inLevel", QJsonValue(in_level)); + } + if(m_carier_position_isSet){ + obj->insert("carierPosition", QJsonValue(carier_position)); + } + if(m_zero_crossing_position_isSet){ + obj->insert("zeroCrossingPosition", QJsonValue(zero_crossing_position)); + } + if(m_sync_rate_isSet){ + obj->insert("syncRate", QJsonValue(sync_rate)); + } + if(status_text != nullptr && *status_text != QString("")){ + toJsonValue(QString("statusText"), status_text, obj, QString("QString")); + } + + return obj; +} + +float +SWGDSDDemodReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGDSDDemodReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGDSDDemodReport::getAudioSampleRate() { + return audio_sample_rate; +} +void +SWGDSDDemodReport::setAudioSampleRate(qint32 audio_sample_rate) { + this->audio_sample_rate = audio_sample_rate; + this->m_audio_sample_rate_isSet = true; +} + +qint32 +SWGDSDDemodReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGDSDDemodReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + +qint32 +SWGDSDDemodReport::getSquelch() { + return squelch; +} +void +SWGDSDDemodReport::setSquelch(qint32 squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +qint32 +SWGDSDDemodReport::getPllLocked() { + return pll_locked; +} +void +SWGDSDDemodReport::setPllLocked(qint32 pll_locked) { + this->pll_locked = pll_locked; + this->m_pll_locked_isSet = true; +} + +qint32 +SWGDSDDemodReport::getSlot1On() { + return slot1_on; +} +void +SWGDSDDemodReport::setSlot1On(qint32 slot1_on) { + this->slot1_on = slot1_on; + this->m_slot1_on_isSet = true; +} + +qint32 +SWGDSDDemodReport::getSlot2On() { + return slot2_on; +} +void +SWGDSDDemodReport::setSlot2On(qint32 slot2_on) { + this->slot2_on = slot2_on; + this->m_slot2_on_isSet = true; +} + +QString* +SWGDSDDemodReport::getSyncType() { + return sync_type; +} +void +SWGDSDDemodReport::setSyncType(QString* sync_type) { + this->sync_type = sync_type; + this->m_sync_type_isSet = true; +} + +qint32 +SWGDSDDemodReport::getInLevel() { + return in_level; +} +void +SWGDSDDemodReport::setInLevel(qint32 in_level) { + this->in_level = in_level; + this->m_in_level_isSet = true; +} + +qint32 +SWGDSDDemodReport::getCarierPosition() { + return carier_position; +} +void +SWGDSDDemodReport::setCarierPosition(qint32 carier_position) { + this->carier_position = carier_position; + this->m_carier_position_isSet = true; +} + +qint32 +SWGDSDDemodReport::getZeroCrossingPosition() { + return zero_crossing_position; +} +void +SWGDSDDemodReport::setZeroCrossingPosition(qint32 zero_crossing_position) { + this->zero_crossing_position = zero_crossing_position; + this->m_zero_crossing_position_isSet = true; +} + +qint32 +SWGDSDDemodReport::getSyncRate() { + return sync_rate; +} +void +SWGDSDDemodReport::setSyncRate(qint32 sync_rate) { + this->sync_rate = sync_rate; + this->m_sync_rate_isSet = true; +} + +QString* +SWGDSDDemodReport::getStatusText() { + return status_text; +} +void +SWGDSDDemodReport::setStatusText(QString* status_text) { + this->status_text = status_text; + this->m_status_text_isSet = true; +} + + +bool +SWGDSDDemodReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_pll_locked_isSet){ isObjectUpdated = true; break;} + if(m_slot1_on_isSet){ isObjectUpdated = true; break;} + if(m_slot2_on_isSet){ isObjectUpdated = true; break;} + if(sync_type != nullptr && *sync_type != QString("")){ isObjectUpdated = true; break;} + if(m_in_level_isSet){ isObjectUpdated = true; break;} + if(m_carier_position_isSet){ isObjectUpdated = true; break;} + if(m_zero_crossing_position_isSet){ isObjectUpdated = true; break;} + if(m_sync_rate_isSet){ isObjectUpdated = true; break;} + if(status_text != nullptr && *status_text != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h new file mode 100644 index 000000000..6bb87411a --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h @@ -0,0 +1,131 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGDSDDemodReport.h + * + * DSDDemod + */ + +#ifndef SWGDSDDemodReport_H_ +#define SWGDSDDemodReport_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDSDDemodReport: public SWGObject { +public: + SWGDSDDemodReport(); + SWGDSDDemodReport(QString* json); + virtual ~SWGDSDDemodReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDSDDemodReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getAudioSampleRate(); + void setAudioSampleRate(qint32 audio_sample_rate); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + qint32 getSquelch(); + void setSquelch(qint32 squelch); + + qint32 getPllLocked(); + void setPllLocked(qint32 pll_locked); + + qint32 getSlot1On(); + void setSlot1On(qint32 slot1_on); + + qint32 getSlot2On(); + void setSlot2On(qint32 slot2_on); + + QString* getSyncType(); + void setSyncType(QString* sync_type); + + qint32 getInLevel(); + void setInLevel(qint32 in_level); + + qint32 getCarierPosition(); + void setCarierPosition(qint32 carier_position); + + qint32 getZeroCrossingPosition(); + void setZeroCrossingPosition(qint32 zero_crossing_position); + + qint32 getSyncRate(); + void setSyncRate(qint32 sync_rate); + + QString* getStatusText(); + void setStatusText(QString* status_text); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 audio_sample_rate; + bool m_audio_sample_rate_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + + qint32 squelch; + bool m_squelch_isSet; + + qint32 pll_locked; + bool m_pll_locked_isSet; + + qint32 slot1_on; + bool m_slot1_on_isSet; + + qint32 slot2_on; + bool m_slot2_on_isSet; + + QString* sync_type; + bool m_sync_type_isSet; + + qint32 in_level; + bool m_in_level_isSet; + + qint32 carier_position; + bool m_carier_position_isSet; + + qint32 zero_crossing_position; + bool m_zero_crossing_position_isSet; + + qint32 sync_rate; + bool m_sync_rate_isSet; + + QString* status_text; + bool m_status_text_isSet; + +}; + +} + +#endif /* SWGDSDDemodReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp new file mode 100644 index 000000000..41e90a276 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp @@ -0,0 +1,551 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGDSDDemodSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDSDDemodSettings::SWGDSDDemodSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDSDDemodSettings::SWGDSDDemodSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + fm_deviation = 0.0f; + m_fm_deviation_isSet = false; + demod_gain = 0.0f; + m_demod_gain_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + baud_rate = 0; + m_baud_rate_isSet = false; + squelch_gate = 0; + m_squelch_gate_isSet = false; + squelch = 0.0f; + m_squelch_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + enable_cosine_filtering = 0; + m_enable_cosine_filtering_isSet = false; + sync_or_constellation = 0; + m_sync_or_constellation_isSet = false; + slot1_on = 0; + m_slot1_on_isSet = false; + slot2_on = 0; + m_slot2_on_isSet = false; + tdma_stereo = 0; + m_tdma_stereo_isSet = false; + pll_lock = 0; + m_pll_lock_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; + high_pass_filter = 0; + m_high_pass_filter_isSet = false; + trace_length_mutliplier = 0; + m_trace_length_mutliplier_isSet = false; + trace_stroke = 0; + m_trace_stroke_isSet = false; + trace_decay = 0; + m_trace_decay_isSet = false; +} + +SWGDSDDemodSettings::~SWGDSDDemodSettings() { + this->cleanup(); +} + +void +SWGDSDDemodSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + fm_deviation = 0.0f; + m_fm_deviation_isSet = false; + demod_gain = 0.0f; + m_demod_gain_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + baud_rate = 0; + m_baud_rate_isSet = false; + squelch_gate = 0; + m_squelch_gate_isSet = false; + squelch = 0.0f; + m_squelch_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + enable_cosine_filtering = 0; + m_enable_cosine_filtering_isSet = false; + sync_or_constellation = 0; + m_sync_or_constellation_isSet = false; + slot1_on = 0; + m_slot1_on_isSet = false; + slot2_on = 0; + m_slot2_on_isSet = false; + tdma_stereo = 0; + m_tdma_stereo_isSet = false; + pll_lock = 0; + m_pll_lock_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; + high_pass_filter = 0; + m_high_pass_filter_isSet = false; + trace_length_mutliplier = 0; + m_trace_length_mutliplier_isSet = false; + trace_stroke = 0; + m_trace_stroke_isSet = false; + trace_decay = 0; + m_trace_decay_isSet = false; +} + +void +SWGDSDDemodSettings::cleanup() { + + + + + + + + + + + + + + + + + if(title != nullptr) { + delete title; + } + if(audio_device_name != nullptr) { + delete audio_device_name; + } + + + + +} + +SWGDSDDemodSettings* +SWGDSDDemodSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDSDDemodSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&fm_deviation, pJson["fmDeviation"], "float", ""); + + ::SWGSDRangel::setValue(&demod_gain, pJson["demodGain"], "float", ""); + + ::SWGSDRangel::setValue(&volume, pJson["volume"], "float", ""); + + ::SWGSDRangel::setValue(&baud_rate, pJson["baudRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&squelch_gate, pJson["squelchGate"], "qint32", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "float", ""); + + ::SWGSDRangel::setValue(&audio_mute, pJson["audioMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&enable_cosine_filtering, pJson["enableCosineFiltering"], "qint32", ""); + + ::SWGSDRangel::setValue(&sync_or_constellation, pJson["syncOrConstellation"], "qint32", ""); + + ::SWGSDRangel::setValue(&slot1_on, pJson["slot1On"], "qint32", ""); + + ::SWGSDRangel::setValue(&slot2_on, pJson["slot2On"], "qint32", ""); + + ::SWGSDRangel::setValue(&tdma_stereo, pJson["tdmaStereo"], "qint32", ""); + + ::SWGSDRangel::setValue(&pll_lock, pJson["pllLock"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&high_pass_filter, pJson["highPassFilter"], "qint32", ""); + + ::SWGSDRangel::setValue(&trace_length_mutliplier, pJson["traceLengthMutliplier"], "qint32", ""); + + ::SWGSDRangel::setValue(&trace_stroke, pJson["traceStroke"], "qint32", ""); + + ::SWGSDRangel::setValue(&trace_decay, pJson["traceDecay"], "qint32", ""); + +} + +QString +SWGDSDDemodSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDSDDemodSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_fm_deviation_isSet){ + obj->insert("fmDeviation", QJsonValue(fm_deviation)); + } + if(m_demod_gain_isSet){ + obj->insert("demodGain", QJsonValue(demod_gain)); + } + if(m_volume_isSet){ + obj->insert("volume", QJsonValue(volume)); + } + if(m_baud_rate_isSet){ + obj->insert("baudRate", QJsonValue(baud_rate)); + } + if(m_squelch_gate_isSet){ + obj->insert("squelchGate", QJsonValue(squelch_gate)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_audio_mute_isSet){ + obj->insert("audioMute", QJsonValue(audio_mute)); + } + if(m_enable_cosine_filtering_isSet){ + obj->insert("enableCosineFiltering", QJsonValue(enable_cosine_filtering)); + } + if(m_sync_or_constellation_isSet){ + obj->insert("syncOrConstellation", QJsonValue(sync_or_constellation)); + } + if(m_slot1_on_isSet){ + obj->insert("slot1On", QJsonValue(slot1_on)); + } + if(m_slot2_on_isSet){ + obj->insert("slot2On", QJsonValue(slot2_on)); + } + if(m_tdma_stereo_isSet){ + obj->insert("tdmaStereo", QJsonValue(tdma_stereo)); + } + if(m_pll_lock_isSet){ + obj->insert("pllLock", QJsonValue(pll_lock)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } + if(m_high_pass_filter_isSet){ + obj->insert("highPassFilter", QJsonValue(high_pass_filter)); + } + if(m_trace_length_mutliplier_isSet){ + obj->insert("traceLengthMutliplier", QJsonValue(trace_length_mutliplier)); + } + if(m_trace_stroke_isSet){ + obj->insert("traceStroke", QJsonValue(trace_stroke)); + } + if(m_trace_decay_isSet){ + obj->insert("traceDecay", QJsonValue(trace_decay)); + } + + return obj; +} + +qint64 +SWGDSDDemodSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGDSDDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGDSDDemodSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGDSDDemodSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGDSDDemodSettings::getFmDeviation() { + return fm_deviation; +} +void +SWGDSDDemodSettings::setFmDeviation(float fm_deviation) { + this->fm_deviation = fm_deviation; + this->m_fm_deviation_isSet = true; +} + +float +SWGDSDDemodSettings::getDemodGain() { + return demod_gain; +} +void +SWGDSDDemodSettings::setDemodGain(float demod_gain) { + this->demod_gain = demod_gain; + this->m_demod_gain_isSet = true; +} + +float +SWGDSDDemodSettings::getVolume() { + return volume; +} +void +SWGDSDDemodSettings::setVolume(float volume) { + this->volume = volume; + this->m_volume_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getBaudRate() { + return baud_rate; +} +void +SWGDSDDemodSettings::setBaudRate(qint32 baud_rate) { + this->baud_rate = baud_rate; + this->m_baud_rate_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getSquelchGate() { + return squelch_gate; +} +void +SWGDSDDemodSettings::setSquelchGate(qint32 squelch_gate) { + this->squelch_gate = squelch_gate; + this->m_squelch_gate_isSet = true; +} + +float +SWGDSDDemodSettings::getSquelch() { + return squelch; +} +void +SWGDSDDemodSettings::setSquelch(float squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getAudioMute() { + return audio_mute; +} +void +SWGDSDDemodSettings::setAudioMute(qint32 audio_mute) { + this->audio_mute = audio_mute; + this->m_audio_mute_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getEnableCosineFiltering() { + return enable_cosine_filtering; +} +void +SWGDSDDemodSettings::setEnableCosineFiltering(qint32 enable_cosine_filtering) { + this->enable_cosine_filtering = enable_cosine_filtering; + this->m_enable_cosine_filtering_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getSyncOrConstellation() { + return sync_or_constellation; +} +void +SWGDSDDemodSettings::setSyncOrConstellation(qint32 sync_or_constellation) { + this->sync_or_constellation = sync_or_constellation; + this->m_sync_or_constellation_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getSlot1On() { + return slot1_on; +} +void +SWGDSDDemodSettings::setSlot1On(qint32 slot1_on) { + this->slot1_on = slot1_on; + this->m_slot1_on_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getSlot2On() { + return slot2_on; +} +void +SWGDSDDemodSettings::setSlot2On(qint32 slot2_on) { + this->slot2_on = slot2_on; + this->m_slot2_on_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getTdmaStereo() { + return tdma_stereo; +} +void +SWGDSDDemodSettings::setTdmaStereo(qint32 tdma_stereo) { + this->tdma_stereo = tdma_stereo; + this->m_tdma_stereo_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getPllLock() { + return pll_lock; +} +void +SWGDSDDemodSettings::setPllLock(qint32 pll_lock) { + this->pll_lock = pll_lock; + this->m_pll_lock_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getRgbColor() { + return rgb_color; +} +void +SWGDSDDemodSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGDSDDemodSettings::getTitle() { + return title; +} +void +SWGDSDDemodSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +QString* +SWGDSDDemodSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGDSDDemodSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getHighPassFilter() { + return high_pass_filter; +} +void +SWGDSDDemodSettings::setHighPassFilter(qint32 high_pass_filter) { + this->high_pass_filter = high_pass_filter; + this->m_high_pass_filter_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getTraceLengthMutliplier() { + return trace_length_mutliplier; +} +void +SWGDSDDemodSettings::setTraceLengthMutliplier(qint32 trace_length_mutliplier) { + this->trace_length_mutliplier = trace_length_mutliplier; + this->m_trace_length_mutliplier_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getTraceStroke() { + return trace_stroke; +} +void +SWGDSDDemodSettings::setTraceStroke(qint32 trace_stroke) { + this->trace_stroke = trace_stroke; + this->m_trace_stroke_isSet = true; +} + +qint32 +SWGDSDDemodSettings::getTraceDecay() { + return trace_decay; +} +void +SWGDSDDemodSettings::setTraceDecay(qint32 trace_decay) { + this->trace_decay = trace_decay; + this->m_trace_decay_isSet = true; +} + + +bool +SWGDSDDemodSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_fm_deviation_isSet){ isObjectUpdated = true; break;} + if(m_demod_gain_isSet){ isObjectUpdated = true; break;} + if(m_volume_isSet){ isObjectUpdated = true; break;} + if(m_baud_rate_isSet){ isObjectUpdated = true; break;} + if(m_squelch_gate_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_audio_mute_isSet){ isObjectUpdated = true; break;} + if(m_enable_cosine_filtering_isSet){ isObjectUpdated = true; break;} + if(m_sync_or_constellation_isSet){ isObjectUpdated = true; break;} + if(m_slot1_on_isSet){ isObjectUpdated = true; break;} + if(m_slot2_on_isSet){ isObjectUpdated = true; break;} + if(m_tdma_stereo_isSet){ isObjectUpdated = true; break;} + if(m_pll_lock_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} + if(m_high_pass_filter_isSet){ isObjectUpdated = true; break;} + if(m_trace_length_mutliplier_isSet){ isObjectUpdated = true; break;} + if(m_trace_stroke_isSet){ isObjectUpdated = true; break;} + if(m_trace_decay_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h new file mode 100644 index 000000000..30f5003a8 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h @@ -0,0 +1,185 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGDSDDemodSettings.h + * + * DSDDemod + */ + +#ifndef SWGDSDDemodSettings_H_ +#define SWGDSDDemodSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDSDDemodSettings: public SWGObject { +public: + SWGDSDDemodSettings(); + SWGDSDDemodSettings(QString* json); + virtual ~SWGDSDDemodSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDSDDemodSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getFmDeviation(); + void setFmDeviation(float fm_deviation); + + float getDemodGain(); + void setDemodGain(float demod_gain); + + float getVolume(); + void setVolume(float volume); + + qint32 getBaudRate(); + void setBaudRate(qint32 baud_rate); + + qint32 getSquelchGate(); + void setSquelchGate(qint32 squelch_gate); + + float getSquelch(); + void setSquelch(float squelch); + + qint32 getAudioMute(); + void setAudioMute(qint32 audio_mute); + + qint32 getEnableCosineFiltering(); + void setEnableCosineFiltering(qint32 enable_cosine_filtering); + + qint32 getSyncOrConstellation(); + void setSyncOrConstellation(qint32 sync_or_constellation); + + qint32 getSlot1On(); + void setSlot1On(qint32 slot1_on); + + qint32 getSlot2On(); + void setSlot2On(qint32 slot2_on); + + qint32 getTdmaStereo(); + void setTdmaStereo(qint32 tdma_stereo); + + qint32 getPllLock(); + void setPllLock(qint32 pll_lock); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + + qint32 getHighPassFilter(); + void setHighPassFilter(qint32 high_pass_filter); + + qint32 getTraceLengthMutliplier(); + void setTraceLengthMutliplier(qint32 trace_length_mutliplier); + + qint32 getTraceStroke(); + void setTraceStroke(qint32 trace_stroke); + + qint32 getTraceDecay(); + void setTraceDecay(qint32 trace_decay); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float fm_deviation; + bool m_fm_deviation_isSet; + + float demod_gain; + bool m_demod_gain_isSet; + + float volume; + bool m_volume_isSet; + + qint32 baud_rate; + bool m_baud_rate_isSet; + + qint32 squelch_gate; + bool m_squelch_gate_isSet; + + float squelch; + bool m_squelch_isSet; + + qint32 audio_mute; + bool m_audio_mute_isSet; + + qint32 enable_cosine_filtering; + bool m_enable_cosine_filtering_isSet; + + qint32 sync_or_constellation; + bool m_sync_or_constellation_isSet; + + qint32 slot1_on; + bool m_slot1_on_isSet; + + qint32 slot2_on; + bool m_slot2_on_isSet; + + qint32 tdma_stereo; + bool m_tdma_stereo_isSet; + + qint32 pll_lock; + bool m_pll_lock_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + QString* audio_device_name; + bool m_audio_device_name_isSet; + + qint32 high_pass_filter; + bool m_high_pass_filter_isSet; + + qint32 trace_length_mutliplier; + bool m_trace_length_mutliplier_isSet; + + qint32 trace_stroke; + bool m_trace_stroke_isSet; + + qint32 trace_decay; + bool m_trace_decay_isSet; + +}; + +} + +#endif /* SWGDSDDemodSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 155057536..2505a20a4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -34,6 +34,8 @@ #include "SWGChannelReport.h" #include "SWGChannelSettings.h" #include "SWGChannelsDetail.h" +#include "SWGDSDDemodReport.h" +#include "SWGDSDDemodSettings.h" #include "SWGDVSeralDevices.h" #include "SWGDVSerialDevice.h" #include "SWGDeviceListItem.h" @@ -138,6 +140,12 @@ namespace SWGSDRangel { if(QString("SWGChannelsDetail").compare(type) == 0) { return new SWGChannelsDetail(); } + if(QString("SWGDSDDemodReport").compare(type) == 0) { + return new SWGDSDDemodReport(); + } + if(QString("SWGDSDDemodSettings").compare(type) == 0) { + return new SWGDSDDemodSettings(); + } if(QString("SWGDVSeralDevices").compare(type) == 0) { return new SWGDVSeralDevices(); } From 72e58534e3d6537b79195112636c4cdc1366fb7b Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 24 May 2018 18:23:08 +0200 Subject: [PATCH 450/956] Web API: BFM and DSD demod fixes. SSB demod: change clipping limiter constants --- plugins/channelrx/demodbfm/bfmdemod.cpp | 6 ++--- plugins/channelrx/demoddsd/dsddemod.cpp | 6 ++--- plugins/channelrx/demoddsd/dsddemodgui.cpp | 31 ++++++++++++++++++++-- plugins/channelrx/demoddsd/dsddemodgui.h | 1 + sdrbase/dsp/agc.cpp | 8 +++--- sdrbase/webapi/webapirequestmapper.cpp | 14 ++++++++++ swagger/sdrangel/examples/scanner.py | 20 +++++++++++--- 7 files changed, 71 insertions(+), 15 deletions(-) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index dca52d7b1..526d885d5 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -593,13 +593,13 @@ int BFMDemod::webapiSettingsPutPatch( settings.m_rdsActive = response.getBfmDemodSettings()->getRdsActive() != 0; } if (channelSettingsKeys.contains("rgbColor")) { - settings.m_rgbColor = response.getAmDemodSettings()->getRgbColor(); + settings.m_rgbColor = response.getBfmDemodSettings()->getRgbColor(); } if (channelSettingsKeys.contains("title")) { - settings.m_title = *response.getAmDemodSettings()->getTitle(); + settings.m_title = *response.getBfmDemodSettings()->getTitle(); } if (channelSettingsKeys.contains("audioDeviceName")) { - settings.m_audioDeviceName = *response.getAmDemodSettings()->getAudioDeviceName(); + settings.m_audioDeviceName = *response.getBfmDemodSettings()->getAudioDeviceName(); } if (frequencyOffsetChanged) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index b522944db..d6601cffb 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -787,13 +787,13 @@ int DSDDemod::webapiSettingsPutPatch( settings.m_pllLock = response.getDsdDemodSettings()->getPllLock() != 0; } if (channelSettingsKeys.contains("rgbColor")) { - settings.m_rgbColor = response.getAmDemodSettings()->getRgbColor(); + settings.m_rgbColor = response.getDsdDemodSettings()->getRgbColor(); } if (channelSettingsKeys.contains("title")) { - settings.m_title = *response.getAmDemodSettings()->getTitle(); + settings.m_title = *response.getDsdDemodSettings()->getTitle(); } if (channelSettingsKeys.contains("audioDeviceName")) { - settings.m_audioDeviceName = *response.getAmDemodSettings()->getAudioDeviceName(); + settings.m_audioDeviceName = *response.getDsdDemodSettings()->getAudioDeviceName(); } if (channelSettingsKeys.contains("highPassFilter")) { settings.m_highPassFilter = response.getDsdDemodSettings()->getHighPassFilter() != 0; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index a59cb7327..3b2dc2653 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -105,9 +105,35 @@ bool DSDDemodGUI::deserialize(const QByteArray& data) } } -bool DSDDemodGUI::handleMessage(const Message& message __attribute__((unused))) +bool DSDDemodGUI::handleMessage(const Message& message) { - return false; + if (DSDDemod::MsgConfigureDSDDemod::match(message)) + { + qDebug("DSDDemodGUI::handleMessage: DSDDemod::MsgConfigureDSDDemod"); + const DSDDemod::MsgConfigureDSDDemod& cfg = (DSDDemod::MsgConfigureDSDDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void DSDDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } } void DSDDemodGUI::on_deltaFrequency_changed(qint64 value) @@ -290,6 +316,7 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index 01f1e4e04..a5e1bdb37 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -133,6 +133,7 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void on_viewStatusLog_clicked(); + void handleInputMessages(); void audioSelect(); void tick(); }; diff --git a/sdrbase/dsp/agc.cpp b/sdrbase/dsp/agc.cpp index c7a3ebcf7..95329508e 100644 --- a/sdrbase/dsp/agc.cpp +++ b/sdrbase/dsp/agc.cpp @@ -105,9 +105,9 @@ double MagAGC::feedAndGetValue(const Complex& ci) if (m_squared) { double u0 = m_R / m_moving_average.average(); - double du = (u0*m_magsq) - (m_clampMax/2.0); + double du = (u0*m_magsq) - (m_clampMax/4.0); if (du > 0) { - m_u0 = (m_clampMax/2.0)*(1.0 + (log10(1+du)/4.0)); // experimental clipping limiter + m_u0 = (m_clampMax/4.0)*(1.0 + (log10(1+du)/8.0)); // experimental clipping limiter } else { m_u0 = u0; } @@ -116,9 +116,9 @@ double MagAGC::feedAndGetValue(const Complex& ci) else { double u02 = m_R2 / m_moving_average.average(); - double du = (u02*m_magsq) - (m_clampMax/2.0); + double du = (u02*m_magsq) - (m_clampMax/4.0); if (du > 0) { - m_u0 = (m_clampMax/2.0)*(1.0 + (log10(1+du)/4.0)); // experimental clipping limiter + m_u0 = (m_clampMax/4.0)*(1.0 + (log10(1+du)/8.0)); // experimental clipping limiter } else { m_u0 = sqrt(u02); } diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index a4a745bc8..6c57241db 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1889,6 +1889,20 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "DSDDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject dsdDemodSettingsJsonObject = jsonObject["DSDDemodSettings"].toObject(); + channelSettingsKeys = dsdDemodSettingsJsonObject.keys(); + channelSettings.setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings()); + channelSettings.getDsdDemodSettings()->fromJsonObject(dsdDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "NFMDemod") { if (channelSettings.getTx() == 0) diff --git a/swagger/sdrangel/examples/scanner.py b/swagger/sdrangel/examples/scanner.py index 26fe228ab..fb0abcb69 100755 --- a/swagger/sdrangel/examples/scanner.py +++ b/swagger/sdrangel/examples/scanner.py @@ -65,6 +65,8 @@ def getInputOptions(): parser.add_option("-t", "--settling-time", dest="settling_time", help="Scan step settling time in seconds", metavar="SECONDS", type="float", default=1.0) parser.add_option("--sq", dest="squelch_db", help="Squelsch threshold in dB", metavar="DECIBEL", type="float", default=-50.0) parser.add_option("--sq-gate", dest="squelch_gate", help="Squelsch gate in ms", metavar="MILLISECONDS", type="int", default=50) + parser.add_option("--baud-rate", dest="baud_rate", help="Baud rate for digial modulation (DV)", metavar="RATE", type="int", default=4800) + parser.add_option("--fm-dev", dest="fm_dev", help="FM deviation for FM digial modulation (DV)", metavar="FREQUENCY", type="int", default=5400) parser.add_option("--re-run", dest="rerun", help="re run with given parameters without setting up device and channels", metavar="BOOLEAN", action="store_true", default=False) parser.add_option("-x", "--excl-list", dest="excl_fstr", help="frequencies (in Hz) exclusion comma separated list", metavar="LIST", type="string") parser.add_option("--excl-tol", dest="excl_tol", help="match tolerance interval (in Hz) for exclusion frequencies", metavar="FREQUENCY", type="float", default=10.0) @@ -167,11 +169,11 @@ def changeDeviceFrequency(fc, options): def setupChannels(scan_control, options): i = 0 for shift in scan_control.channel_shifts: - settings = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": options.channel_id, "tx": 0}, "Create NFM demod") + settings = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": options.channel_id, "tx": 0}, "Create demod") if settings is None: exit(-1) - settings = callAPI(deviceset_url + "/channel/%d/settings" % i, "GET", None, None, "Get NFM demod settings") + settings = callAPI(deviceset_url + "/channel/%d/settings" % i, "GET", None, None, "Get demod settings") if settings is None: exit(-1) @@ -190,6 +192,14 @@ def setupChannels(scan_control, options): settings["AMDemodSettings"]["squelch"] = options.squelch_db settings["AMDemodSettings"]["title"] = "Channel %d" % i settings["AMDemodSettings"]["bandpassEnable"] = 1 # bandpass filter + elif options.channel_id == "DSDDemod": + settings["DSDDemodSettings"]["inputFrequencyOffset"] = int(shift) + settings["DSDDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["DSDDemodSettings"]["volume"] = options.volume + settings["DSDDemodSettings"]["squelch"] = options.squelch_db + settings["DSDDemodSettings"]["baudRate"] = options.baud_rate + settings["DSDDemodSettings"]["fmDeviation"] = options.fm_dev + settings["DSDDemodSettings"]["title"] = "Channel %d" % i r = callAPI(deviceset_url + "/channel/%d/settings" % i, "PATCH", None, settings, "Change demod") if r is None: @@ -207,7 +217,11 @@ def checkScanning(fc, options, display_message): channel = reports["channels"][i] if "report" in channel: if reportKey in channel["report"]: - if channel["report"][reportKey]["squelch"] == 1: + if options.channel_id == "DSDDemod": # DSD is special because it only stops on voice + stopCondition = channel["report"][reportKey]["slot1On"] == 1 or channel["report"][reportKey]["slot2On"] == 1 + else: + stopCondition = channel["report"][reportKey]["squelch"] == 1 + if stopCondition: f_channel = channel["deltaFrequency"]+fc f_frac = round(f_channel/options.excl_tol) if f_frac not in options.excl_flist: From c961fa368d28acc6935e3e3808a8259806a40c89 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 25 May 2018 02:02:21 +0200 Subject: [PATCH 451/956] Web API: fixes in BFM demod and HackRF input --- plugins/channelrx/demodbfm/bfmdemod.cpp | 6 +- plugins/channelrx/demodbfm/bfmdemod.h | 9 ++ plugins/channelrx/demodbfm/bfmdemodgui.cpp | 2 +- plugins/channelrx/demodbfm/bfmdemodgui.h | 9 -- .../samplesource/hackrfinput/hackrfinput.cpp | 6 + swagger/sdrangel/examples/rx_test.py | 149 ++++++++++++------ swagger/sdrangel/examples/scanner.py | 13 +- 7 files changed, 130 insertions(+), 64 deletions(-) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index 526d885d5..56fe6f37f 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -447,6 +447,7 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) qDebug() << "BFMDemod::applySettings: MsgConfigureBFMDemod:" << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_afBandwidth: " << settings.m_afBandwidth << " m_volume: " << settings.m_volume << " m_squelch: " << settings.m_squelch << " m_audioStereo: " << settings.m_audioStereo @@ -480,8 +481,7 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) m_settingsMutex.unlock(); } - if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || - (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { m_settingsMutex.lock(); Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_inputSampleRate; @@ -605,7 +605,7 @@ int BFMDemod::webapiSettingsPutPatch( if (frequencyOffsetChanged) { MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - m_audioSampleRate, settings.m_inputFrequencyOffset); + requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset); // FIXME m_inputMessageQueue.push(channelConfigMsg); } diff --git a/plugins/channelrx/demodbfm/bfmdemod.h b/plugins/channelrx/demodbfm/bfmdemod.h index 633d2a615..99c76d07f 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.h +++ b/plugins/channelrx/demodbfm/bfmdemod.h @@ -171,6 +171,15 @@ public: SWGSDRangel::SWGChannelReport& response, QString& errorMessage); + static int requiredBW(int rfBW) + { + if (rfBW <= 48000) { + return 48000; + } else { + return (3*rfBW)/2; + } + } + static const QString m_channelIdURI; static const QString m_channelId; diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index 97cf243ea..5074cad04 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -412,7 +412,7 @@ void BFMDemodGUI::applySettings(bool force) if (m_doApplySettings) { BFMDemod::MsgConfigureChannelizer *msgChan = BFMDemod::MsgConfigureChannelizer::create( - requiredBW(m_settings.m_rfBandwidth), + BFMDemod::requiredBW(m_settings.m_rfBandwidth), m_settings.m_inputFrequencyOffset); m_bfmDemod->getInputMessageQueue()->push(msgChan); diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.h b/plugins/channelrx/demodbfm/bfmdemodgui.h index 5850f4ef1..06f0f4842 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.h +++ b/plugins/channelrx/demodbfm/bfmdemodgui.h @@ -88,15 +88,6 @@ private: void changeFrequency(qint64 f); - static int requiredBW(int rfBW) - { - if (rfBW <= 48000) { - return 48000; - } else { - return (3*rfBW)/2; - } - } - private slots: void on_deltaFrequency_changed(qint64 value); void on_rfBW_valueChanged(int value); diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index b287f9b16..744119e0c 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -562,6 +562,12 @@ int HackRFInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("log2Decim")) { settings.m_log2Decim = response.getHackRfInputSettings()->getLog2Decim(); } + if (deviceSettingsKeys.contains("fcPos")) + { + int fcPos = response.getHackRfInputSettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (HackRFInputSettings::fcPos_t) fcPos; + } if (deviceSettingsKeys.contains("devSampleRate")) { settings.m_devSampleRate = response.getHackRfInputSettings()->getDevSampleRate(); } diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 02528d557..2fe2a9bf5 100644 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import requests, json, traceback, sys +import requests, json, traceback, sys, time from optparse import OptionParser base_url = "http://127.0.0.1:8091/sdrangel" @@ -20,12 +20,24 @@ def getInputOptions(): parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") parser.add_option("-D", "--device-hwid", dest="device_hwid", help="device hardware id", metavar="HWID", type="string") + parser.add_option("-C", "--channel-id", dest="channel_id", help="channel id", metavar="ID", type="string", default="NFMDemod") parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") parser.add_option("-f", "--channel-freq", dest="channel_freq", help="channel center frequency (Hz)", metavar="FREQ", type="int") parser.add_option("-U", "--copy-to-udp", dest="udp_copy", help="UDP audio copy to
[:]", metavar="IP:PORT", type="string") parser.add_option("-A", "--antenna-path", dest="antenna_path", help="antenna path index", metavar="INDEX", type="int") parser.add_option("-s", "--sample-rate", dest="sample_rate", help="device to host sample rate (kS/s)", metavar="RATE", type="int") + parser.add_option("-l", "--log2-decim", dest="log2_decim", help="log2 of the desired software decimation factor", metavar="LOG2", type="int", default=4) + parser.add_option("-b", "--af-bw", dest="af_bw", help="audio babdwidth (kHz)", metavar="FREQUENCY_KHZ", type="int" ,default=3) + parser.add_option("-r", "--rf-bw", dest="rf_bw", help="RF babdwidth (Hz). Sets to nearest available", metavar="FREQUENCY", type="int", default=10000) + parser.add_option("--vol", dest="volume", help="audio volume", metavar="VOLUME", type="float", default=1.0) parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="CREATE", action="store_true", default=False) + parser.add_option("--ppm", dest="lo_ppm", help="LO correction in PPM", metavar="PPM", type="float", default=0.0) + parser.add_option("--fc-pos", dest="fc_pos", help="Center frequency position 0:inf 1:sup 2:cen", metavar="ENUM", default=2) + parser.add_option("--sq", dest="squelch_db", help="Squelsch threshold in dB", metavar="DECIBEL", type="float", default=-50.0) + parser.add_option("--sq-gate", dest="squelch_gate", help="Squelsch gate in ms", metavar="MILLISECONDS", type="int", default=50) + parser.add_option("--stereo", dest="stereo", help="Broadcast FM stereo", metavar="BOOL", action="store_true", default=False) + parser.add_option("--lsb-stereo", dest="lsb_stereo", help="Broadcast FM LSB stereo", metavar="BOOL", action="store_true", default=False) + parser.add_option("--rds", dest="rds", help="Broadcast FM RDS", metavar="BOOL", action="store_true", default=False) (options, args) = parser.parse_args() @@ -76,20 +88,7 @@ def callAPI(url, method, params, json, text): return None # ====================================================================== -def main(): - try: - options = getInputOptions() - - global base_url - base_url = "http://%s/sdrangel" % options.address - - if options.create: - r = callAPI("/deviceset", "POST", {"tx": 0}, None, "Add Rx device set") - if r is None: - exit(-1) - - deviceset_url = "/deviceset/%d" % options.device_index - +def setupDevice(deviceset_url, options): r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "%s" % options.device_hwid, "tx": 0}, "setup device on Rx device set") if r is None: exit(-1) @@ -98,11 +97,20 @@ def main(): if settings is None: exit(-1) - if options.device_hwid == "LimeSDR": + if options.device_hwid == "AirspyHF": + if options.device_freq > 30000: + settings["airspyHFSettings"]["bandIndex"] = 1 + else: + settings["airspyHFSettings"]["bandIndex"] = 0 + settings["airspyHFSettings"]["centerFrequency"] = options.device_freq*1000 + settings["airspyHFSettings"]["devSampleRateIndex"] = 0 + settings['airspyHFSettings']['log2Decim'] = options.log2_decim + settings['airspyHFSettings']['LOppmTenths'] = int(options.lo_ppm * 10) # in tenths of PPM + elif options.device_hwid == "LimeSDR": settings["limeSdrInputSettings"]["antennaPath"] = options.antenna_path settings["limeSdrInputSettings"]["devSampleRate"] = options.sample_rate*1000 settings["limeSdrInputSettings"]["log2HardDecim"] = 4 - settings["limeSdrInputSettings"]["log2SoftDecim"] = 3 + settings["limeSdrInputSettings"]["log2SoftDecim"] = options.log2_decim settings["limeSdrInputSettings"]["centerFrequency"] = options.device_freq*1000 + 500000 settings["limeSdrInputSettings"]["ncoEnable"] = 1 settings["limeSdrInputSettings"]["ncoFrequency"] = -500000 @@ -114,51 +122,98 @@ def main(): settings['rtlSdrSettings']['devSampleRate'] = options.sample_rate*1000 settings['rtlSdrSettings']['centerFrequency'] = options.device_freq*1000 settings['rtlSdrSettings']['gain'] = 496 - settings['rtlSdrSettings']['log2Decim'] = 4 - settings['rtlSdrSettings']['dcBlock'] = 1 + settings['rtlSdrSettings']['log2Decim'] = options.log2_decim + settings['rtlSdrSettings']['fcPos'] = options.fc_pos + settings['rtlSdrSettings']['dcBlock'] = options.fc_pos == 2 + settings['rtlSdrSettings']['iqImbalance'] = options.fc_pos == 2 settings['rtlSdrSettings']['agc'] = 1 + settings['rtlSdrSettings']['loPpmCorrection'] = int(options.lo_ppm) elif options.device_hwid == "HackRF": - settings['hackRFInputSettings']['LOppmTenths'] = -51 + settings['hackRFInputSettings']['LOppmTenths'] = int(options.lo_ppm * 10) # in tenths of PPM settings['hackRFInputSettings']['centerFrequency'] = options.device_freq*1000 - settings['hackRFInputSettings']['dcBlock'] = 1 + settings['hackRFInputSettings']['fcPos'] = options.fc_pos + settings['hackRFInputSettings']['dcBlock'] = options.fc_pos == 2 + settings['hackRFInputSettings']['iqImbalance'] = options.fc_pos == 2 settings['hackRFInputSettings']['devSampleRate'] = options.sample_rate*1000 settings['hackRFInputSettings']['lnaExt'] = 1 settings['hackRFInputSettings']['lnaGain'] = 32 - settings['hackRFInputSettings']['log2Decim'] = 4 + settings['hackRFInputSettings']['log2Decim'] = options.log2_decim settings['hackRFInputSettings']['vgaGain'] = 24 r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") if r is None: exit(-1) - - r = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": "NFMDemod", "tx": 0}, "Create NFM demod") - if r is None: - exit(-1) - - settings = callAPI(deviceset_url + "/channel/0/settings", "GET", None, None, "Get NFM demod settings") - if settings is None: - exit(-1) - - settings["NFMDemodSettings"]["title"] = "Test NFM" + +# ====================================================================== +def setupChannel(deviceset_url, options): + i = 0 + + settings = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": options.channel_id, "tx": 0}, "Create demod") + if settings is None: + exit(-1) + + settings = callAPI(deviceset_url + "/channel/%d/settings" % i, "GET", None, None, "Get demod settings") + if settings is None: + exit(-1) + + if options.channel_id == "NFMDemod": settings["NFMDemodSettings"]["inputFrequencyOffset"] = options.channel_freq - settings["NFMDemodSettings"]["rfBandwidth"] = 12500 - settings["NFMDemodSettings"]["fmDeviation"] = 3000 - settings["NFMDemodSettings"]["afBandwidth"] = 4000 - settings["NFMDemodSettings"]["squelch"] = -700 - settings["NFMDemodSettings"]["volume"] = 2.0 + settings["NFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 + settings["NFMDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["NFMDemodSettings"]["volume"] = options.volume + settings["NFMDemodSettings"]["squelch"] = options.squelch_db * 10 # centi-Bels + settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate / 10 # 10's of ms + settings["NFMDemodSettings"]["title"] = "Channel %d" % i + elif options.channel_id == "BFMDemod": + settings["BFMDemodSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["BFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 + settings["BFMDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["BFMDemodSettings"]["volume"] = options.volume + settings["BFMDemodSettings"]["squelch"] = options.squelch_db # dB + settings["BFMDemodSettings"]["audioStereo"] = 1 if options.stereo else 0 + settings["BFMDemodSettings"]["lsbStereo"] = 1 if options.lsb_stereo else 0 + settings["BFMDemodSettings"]["rdsActive"] = 1 if options.rds else 0 + settings["BFMDemodSettings"]["title"] = "Channel %d" % i + elif options.channel_id == "AMDemod": + settings["AMDemodSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["AMDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["AMDemodSettings"]["volume"] = options.volume + settings["AMDemodSettings"]["squelch"] = options.squelch_db + settings["AMDemodSettings"]["title"] = "Channel %d" % i + settings["AMDemodSettings"]["bandpassEnable"] = 1 # bandpass filter + elif options.channel_id == "DSDDemod": + settings["DSDDemodSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["DSDDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["DSDDemodSettings"]["volume"] = options.volume + settings["DSDDemodSettings"]["squelch"] = options.squelch_db + settings["DSDDemodSettings"]["baudRate"] = options.baud_rate + settings["DSDDemodSettings"]["fmDeviation"] = options.fm_dev + settings["DSDDemodSettings"]["enableCosineFiltering"] = 1 + settings["DSDDemodSettings"]["pllLock"] = 1 + settings["DSDDemodSettings"]["title"] = "Channel %d" % i + + r = callAPI(deviceset_url + "/channel/%d/settings" % i, "PATCH", None, settings, "Change demod") + if r is None: + exit(-1) + + +# ====================================================================== +def main(): + try: + options = getInputOptions() - if options.udp_copy is not None: - address_port = options.udp_copy.split(':') - if len(address_port) > 1: - settings["NFMDemodSettings"]["udpPort"] = address_port[1] - if len(address_port) > 0: - settings["NFMDemodSettings"]["udpAddress"] = address_port[0] - settings["NFMDemodSettings"]["copyAudioToUDP"] = 1 + global base_url + base_url = "http://%s/sdrangel" % options.address + deviceset_url = "/deviceset/%d" % options.device_index - r = callAPI(deviceset_url + "/channel/0/settings", "PATCH", None, settings, "Change NFM demod") - if r is None: - exit(-1) + if options.create: + r = callAPI("/deviceset", "POST", {"tx": 0}, None, "Add Rx device set") + if r is None: + exit(-1) + setupDevice(deviceset_url, options) + setupChannel(deviceset_url, options) + r = callAPI(deviceset_url + "/device/run", "POST", None, None, "Start running device") if r is None: exit(-1) diff --git a/swagger/sdrangel/examples/scanner.py b/swagger/sdrangel/examples/scanner.py index fb0abcb69..961d8604a 100755 --- a/swagger/sdrangel/examples/scanner.py +++ b/swagger/sdrangel/examples/scanner.py @@ -62,6 +62,7 @@ def getInputOptions(): parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="BOOLEAN", action="store_true", default=False) parser.add_option("-m", "--mock", dest="mock", help="just print calculated values and exit", metavar="BOOLEAN", action="store_true", default=False) parser.add_option("--ppm", dest="lo_ppm", help="LO correction in PPM", metavar="PPM", type="float", default=0.0) + parser.add_option("--fc-pos", dest="fc_pos", help="Center frequency position 0:inf 1:sup 2:cen", metavar="ENUM", default=2) parser.add_option("-t", "--settling-time", dest="settling_time", help="Scan step settling time in seconds", metavar="SECONDS", type="float", default=1.0) parser.add_option("--sq", dest="squelch_db", help="Squelsch threshold in dB", metavar="DECIBEL", type="float", default=-50.0) parser.add_option("--sq-gate", dest="squelch_gate", help="Squelsch gate in ms", metavar="MILLISECONDS", type="int", default=50) @@ -126,16 +127,18 @@ def setupDevice(scan_control, options): settings['rtlSdrSettings']['centerFrequency'] = scan_control.device_start_freq settings['rtlSdrSettings']['gain'] = 496 settings['rtlSdrSettings']['log2Decim'] = options.log2_decim - settings['rtlSdrSettings']['dcBlock'] = 1 - settings['rtlSdrSettings']['iqImbalance'] = 1 + settings['rtlSdrSettings']['fcPos'] = options.fc_pos + settings['rtlSdrSettings']['dcBlock'] = options.fc_pos == 2 + settings['rtlSdrSettings']['iqImbalance'] = options.fc_pos == 2 settings['rtlSdrSettings']['agc'] = 1 settings['rtlSdrSettings']['loPpmCorrection'] = int(options.lo_ppm) settings['rtlSdrSettings']['rfBandwidth'] = scan_control.device_step_freq + 100000 elif options.device_hwid == "HackRF": settings['hackRFInputSettings']['LOppmTenths'] = int(options.lo_ppm * 10) # in tenths of PPM settings['hackRFInputSettings']['centerFrequency'] = scan_control.device_start_freq - settings['hackRFInputSettings']['dcBlock'] = 1 - settings['hackRFInputSettings']['iqImbalance'] = 1 + settings['hackRFInputSettings']['fcPos'] = options.fc_pos + settings['hackRFInputSettings']['dcBlock'] = options.fc_pos == 2 + settings['hackRFInputSettings']['iqImbalance'] = options.fc_pos == 2 settings['hackRFInputSettings']['devSampleRate'] = scan_control.device_sample_rate settings['hackRFInputSettings']['lnaExt'] = 1 settings['hackRFInputSettings']['lnaGain'] = 32 @@ -199,6 +202,8 @@ def setupChannels(scan_control, options): settings["DSDDemodSettings"]["squelch"] = options.squelch_db settings["DSDDemodSettings"]["baudRate"] = options.baud_rate settings["DSDDemodSettings"]["fmDeviation"] = options.fm_dev + settings["DSDDemodSettings"]["enableCosineFiltering"] = 1 + settings["DSDDemodSettings"]["pllLock"] = 1 settings["DSDDemodSettings"]["title"] = "Channel %d" % i r = callAPI(deviceset_url + "/channel/%d/settings" % i, "PATCH", None, settings, "Change demod") From 111b3da5ce496933e8db36e030bb7eadb27d675c Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 25 May 2018 09:27:02 +0200 Subject: [PATCH 452/956] Removal of ChannelAnalyzer and TCPSrc plugins --- plugins/channelrx/CMakeLists.txt | 2 - plugins/channelrx/chanalyzer/CMakeLists.txt | 46 -- plugins/channelrx/chanalyzer/chanalyzer.cpp | 242 -------- plugins/channelrx/chanalyzer/chanalyzer.h | 167 ------ plugins/channelrx/chanalyzer/chanalyzer.pro | 46 -- .../channelrx/chanalyzer/chanalyzergui.cpp | 487 ---------------- plugins/channelrx/chanalyzer/chanalyzergui.h | 101 ---- plugins/channelrx/chanalyzer/chanalyzergui.ui | 540 ------------------ .../channelrx/chanalyzer/chanalyzerplugin.cpp | 50 -- .../channelrx/chanalyzer/chanalyzerplugin.h | 32 -- plugins/channelrx/tcpsrc/CMakeLists.txt | 48 -- plugins/channelrx/tcpsrc/tcpsrc.cpp | 487 ---------------- plugins/channelrx/tcpsrc/tcpsrc.h | 224 -------- plugins/channelrx/tcpsrc/tcpsrc.pro | 43 -- plugins/channelrx/tcpsrc/tcpsrcgui.cpp | 397 ------------- plugins/channelrx/tcpsrc/tcpsrcgui.h | 92 --- plugins/channelrx/tcpsrc/tcpsrcgui.ui | 480 ---------------- plugins/channelrx/tcpsrc/tcpsrcplugin.cpp | 51 -- plugins/channelrx/tcpsrc/tcpsrcplugin.h | 31 - plugins/channelrx/tcpsrc/tcpsrcsettings.cpp | 150 ----- plugins/channelrx/tcpsrc/tcpsrcsettings.h | 72 --- 21 files changed, 3788 deletions(-) delete mode 100644 plugins/channelrx/chanalyzer/CMakeLists.txt delete mode 100644 plugins/channelrx/chanalyzer/chanalyzer.cpp delete mode 100644 plugins/channelrx/chanalyzer/chanalyzer.h delete mode 100644 plugins/channelrx/chanalyzer/chanalyzer.pro delete mode 100644 plugins/channelrx/chanalyzer/chanalyzergui.cpp delete mode 100644 plugins/channelrx/chanalyzer/chanalyzergui.h delete mode 100644 plugins/channelrx/chanalyzer/chanalyzergui.ui delete mode 100644 plugins/channelrx/chanalyzer/chanalyzerplugin.cpp delete mode 100644 plugins/channelrx/chanalyzer/chanalyzerplugin.h delete mode 100644 plugins/channelrx/tcpsrc/CMakeLists.txt delete mode 100644 plugins/channelrx/tcpsrc/tcpsrc.cpp delete mode 100644 plugins/channelrx/tcpsrc/tcpsrc.h delete mode 100644 plugins/channelrx/tcpsrc/tcpsrc.pro delete mode 100644 plugins/channelrx/tcpsrc/tcpsrcgui.cpp delete mode 100644 plugins/channelrx/tcpsrc/tcpsrcgui.h delete mode 100644 plugins/channelrx/tcpsrc/tcpsrcgui.ui delete mode 100644 plugins/channelrx/tcpsrc/tcpsrcplugin.cpp delete mode 100644 plugins/channelrx/tcpsrc/tcpsrcplugin.h delete mode 100644 plugins/channelrx/tcpsrc/tcpsrcsettings.cpp delete mode 100644 plugins/channelrx/tcpsrc/tcpsrcsettings.h diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 110fb1dd3..099c9afd3 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -5,10 +5,8 @@ add_subdirectory(demodam) add_subdirectory(demodbfm) add_subdirectory(demodnfm) add_subdirectory(demodssb) -add_subdirectory(tcpsrc) add_subdirectory(udpsrc) add_subdirectory(demodwfm) -add_subdirectory(chanalyzer) add_subdirectory(chanalyzerng) add_subdirectory(demodatv) diff --git a/plugins/channelrx/chanalyzer/CMakeLists.txt b/plugins/channelrx/chanalyzer/CMakeLists.txt deleted file mode 100644 index 1d099f8fe..000000000 --- a/plugins/channelrx/chanalyzer/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -project(chanalyzer) - -set(chanalyzer_SOURCES - chanalyzer.cpp - chanalyzergui.cpp - chanalyzerplugin.cpp -) - -set(chanalyzer_HEADERS - chanalyzer.h - chanalyzergui.h - chanalyzerplugin.h -) - -set(chanalyzer_FORMS - chanalyzergui.ui -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} -) - -#include(${QT_USE_FILE}) -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -#qt5_wrap_cpp(chanalyzer_HEADERS_MOC ${chanalyzer_HEADERS}) -qt5_wrap_ui(chanalyzer_FORMS_HEADERS ${chanalyzer_FORMS}) - -add_library(chanalyzer SHARED - ${chanalyzer_SOURCES} - ${chanalyzer_HEADERS_MOC} - ${chanalyzer_FORMS_HEADERS} -) - -target_link_libraries(chanalyzer - ${QT_LIBRARIES} - sdrbase - sdrgui -) - -qt5_use_modules(chanalyzer Core Widgets ) - -install(TARGETS chanalyzer DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/chanalyzer/chanalyzer.cpp b/plugins/channelrx/chanalyzer/chanalyzer.cpp deleted file mode 100644 index 8af199fb5..000000000 --- a/plugins/channelrx/chanalyzer/chanalyzer.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 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 // -// // -// 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 "chanalyzer.h" - -#include -#include -#include - -#include -#include "dsp/threadedbasebandsamplesink.h" -#include "device/devicesourceapi.h" -#include "audio/audiooutput.h" - -MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgReportChannelSampleRateChanged, Message) - -const QString ChannelAnalyzer::m_channelIdURI = "org.f4exb.sdrangelove.channel.chanalyzer"; -const QString ChannelAnalyzer::m_channelId = "ChannelAnalyzer"; - -ChannelAnalyzer::ChannelAnalyzer(DeviceSourceAPI *deviceAPI) : - ChannelSinkAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_sampleSink(0), - m_settingsMutex(QMutex::Recursive) -{ - setObjectName(m_channelId); - - m_Bandwidth = 5000; - m_LowCutoff = 300; - m_spanLog2 = 3; - m_sampleRate = 96000; - m_frequency = 0; - m_nco.setFreq(m_frequency, m_sampleRate); - m_undersampleCount = 0; - m_sum = 0; - m_usb = true; - m_ssb = true; - m_magsq = 0; - SSBFilter = new fftfilt(m_LowCutoff / m_sampleRate, m_Bandwidth / m_sampleRate, ssbFftLen); - DSBFilter = new fftfilt(m_Bandwidth / m_sampleRate, 2*ssbFftLen); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - m_deviceAPI->addThreadedSink(m_threadedChannelizer); - m_deviceAPI->addChannelAPI(this); -} - -ChannelAnalyzer::~ChannelAnalyzer() -{ - m_deviceAPI->removeChannelAPI(this); - m_deviceAPI->removeThreadedSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; - if (SSBFilter) delete SSBFilter; - if (DSBFilter) delete DSBFilter; -} - -void ChannelAnalyzer::configure(MessageQueue* messageQueue, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) -{ - Message* cmd = MsgConfigureChannelAnalyzer::create(Bandwidth, LowCutoff, spanLog2, ssb); - messageQueue->push(cmd); -} - -void ChannelAnalyzer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) -{ - fftfilt::cmplx *sideband; - int n_out; - int decim = 1<real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_ssb) - { - n_out = SSBFilter->runSSB(c, &sideband, m_usb); - } - else - { - n_out = DSBFilter->runDSB(c, &sideband); - } - - for (int i = 0; i < n_out; i++) - { - // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display - // smart decimation with bit gain using float arithmetic (23 bits significand) - - m_sum += sideband[i]; - - if (!(m_undersampleCount++ & decim_mask)) - { - m_sum /= decim; - Real re = m_sum.real() / SDR_RX_SCALED; - Real im = m_sum.imag() / SDR_RX_SCALED; - m_magsq = re*re + im*im; - - if (m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); - } - else - { - m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); - } - - m_sum = 0; - } - } - } - - if(m_sampleSink != NULL) - { - m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_ssb); // m_ssb = positive only - } - - m_sampleBuffer.clear(); - - m_settingsMutex.unlock(); -} - -void ChannelAnalyzer::start() -{ -} - -void ChannelAnalyzer::stop() -{ -} - -void ChannelAnalyzer::channelSampleRateChanged() -{ - MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(); - getMessageQueueToGUI()->push(msg); -} - -bool ChannelAnalyzer::handleMessage(const Message& cmd) -{ - float band, lowCutoff; - - qDebug() << "ChannelAnalyzer::handleMessage"; - - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - m_sampleRate = notif.getSampleRate(); - m_nco.setFreq(-notif.getFrequencyOffset(), m_sampleRate); - - qDebug() << "ChannelAnalyzer::handleMessage: MsgChannelizerNotification: m_sampleRate: " << m_sampleRate - << " frequencyOffset: " << notif.getFrequencyOffset(); - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - m_channelizer->getInputSampleRate(), - cfg.getCenterFrequency()); - - return true; - } - else if (MsgConfigureChannelAnalyzer::match(cmd)) - { - MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; - - band = cfg.getBandwidth(); - lowCutoff = cfg.getLoCutoff(); - - if (band < 0) - { - band = -band; - lowCutoff = -lowCutoff; - m_usb = false; - } - else - { - m_usb = true; - } - - if (band < 100.0f) - { - band = 100.0f; - lowCutoff = 0; - } - - m_settingsMutex.lock(); - - m_Bandwidth = band; - m_LowCutoff = lowCutoff; - - SSBFilter->create_filter(m_LowCutoff / m_sampleRate, m_Bandwidth / m_sampleRate); - DSBFilter->create_dsb_filter(m_Bandwidth / m_sampleRate); - - m_spanLog2 = cfg.getSpanLog2(); - m_ssb = cfg.getSSB(); - - m_settingsMutex.unlock(); - - qDebug() << "ChannelAnalyzer::handleMessage: MsgConfigureChannelAnalyzer: m_Bandwidth: " << m_Bandwidth - << " m_LowCutoff: " << m_LowCutoff - << " m_spanLog2: " << m_spanLog2 - << " m_ssb: " << m_ssb; - - return true; - } - else - { - if (m_sampleSink != 0) - { - return m_sampleSink->handleMessage(cmd); - } - else - { - return false; - } - } -} diff --git a/plugins/channelrx/chanalyzer/chanalyzer.h b/plugins/channelrx/chanalyzer/chanalyzer.h deleted file mode 100644 index a45a1d1e2..000000000 --- a/plugins/channelrx/chanalyzer/chanalyzer.h +++ /dev/null @@ -1,167 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 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 // -// // -// 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_CHANALYZER_H -#define INCLUDE_CHANALYZER_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/ncof.h" -#include "dsp/fftfilt.h" -#include "audio/audiofifo.h" -#include "util/message.h" - -#define ssbFftLen 1024 - -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -class ChannelAnalyzer : public BasebandSampleSink, public ChannelSinkAPI { -public: - class MsgConfigureChannelAnalyzer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - Real getBandwidth() const { return m_Bandwidth; } - Real getLoCutoff() const { return m_LowCutoff; } - int getSpanLog2() const { return m_spanLog2; } - bool getSSB() const { return m_ssb; } - - static MsgConfigureChannelAnalyzer* create(Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) - { - return new MsgConfigureChannelAnalyzer(Bandwidth, LowCutoff, spanLog2, ssb); - } - - private: - Real m_Bandwidth; - Real m_LowCutoff; - int m_spanLog2; - bool m_ssb; - - MsgConfigureChannelAnalyzer(Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) : - Message(), - m_Bandwidth(Bandwidth), - m_LowCutoff(LowCutoff), - m_spanLog2(spanLog2), - m_ssb(ssb) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int centerFrequency) - { - return new MsgConfigureChannelizer(centerFrequency); - } - - private: - int m_centerFrequency; - - MsgConfigureChannelizer(int centerFrequency) : - Message(), - m_centerFrequency(centerFrequency) - { } - }; - - class MsgReportChannelSampleRateChanged : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgReportChannelSampleRateChanged* create() - { - return new MsgReportChannelSampleRateChanged(); - } - - private: - - MsgReportChannelSampleRateChanged() : - Message() - { } - }; - - ChannelAnalyzer(DeviceSourceAPI *deviceAPI); - virtual ~ChannelAnalyzer(); - virtual void destroy() { delete this; } - void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } - - void configure(MessageQueue* messageQueue, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb); - - int getSampleRate() const { return m_sampleRate; } - Real getMagSq() const { return m_magsq; } - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); - 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 = objectName(); } - virtual qint64 getCenterFrequency() const { return m_frequency; } - - virtual QByteArray serialize() const { return QByteArray(); } - virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private slots: - void channelSampleRateChanged(); - -private: - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - Real m_Bandwidth; - Real m_LowCutoff; - int m_spanLog2; - int m_undersampleCount; - fftfilt::cmplx m_sum; - int m_sampleRate; - int m_frequency; - bool m_usb; - bool m_ssb; - Real m_magsq; - - NCOF m_nco; - fftfilt* SSBFilter; - fftfilt* DSBFilter; - - BasebandSampleSink* m_sampleSink; - SampleVector m_sampleBuffer; - QMutex m_settingsMutex; -}; - -#endif // INCLUDE_CHANALYZER_H diff --git a/plugins/channelrx/chanalyzer/chanalyzer.pro b/plugins/channelrx/chanalyzer/chanalyzer.pro deleted file mode 100644 index e46850563..000000000 --- a/plugins/channelrx/chanalyzer/chanalyzer.pro +++ /dev/null @@ -1,46 +0,0 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -TEMPLATE = lib -CONFIG += plugin - -QT += core gui widgets multimedia opengl - -TARGET = chanalyzer - -DEFINES += USE_SSE2=1 -QMAKE_CXXFLAGS += -msse2 -DEFINES += USE_SSE4_1=1 -QMAKE_CXXFLAGS += -msse4.1 -QMAKE_CXXFLAGS += -std=c++11 - -INCLUDEPATH += $$PWD -INCLUDEPATH += ../../../exports -INCLUDEPATH += ../../../sdrbase -INCLUDEPATH += ../../../sdrgui - -CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" - -CONFIG(Release):build_subdir = release -CONFIG(Debug):build_subdir = debug - -SOURCES += chanalyzer.cpp\ - chanalyzergui.cpp\ - chanalyzerplugin.cpp - -HEADERS += chanalyzer.h\ -chanalyzergui.h\ -chanalyzerplugin.h - -FORMS += chanalyzergui.ui - -LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase -LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui - -RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp deleted file mode 100644 index 8a966b09c..000000000 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ /dev/null @@ -1,487 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 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 // -// // -// 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 "chanalyzergui.h" - -#include -#include "device/deviceuiset.h" -#include -#include -#include - -#include "dsp/threadedbasebandsamplesink.h" -#include "ui_chanalyzergui.h" -#include "dsp/spectrumscopecombovis.h" -#include "dsp/spectrumvis.h" -#include "dsp/scopevis.h" -#include "gui/glspectrum.h" -#include "gui/glscope.h" -#include "plugin/pluginapi.h" -#include "util/simpleserializer.h" -#include "util/db.h" -#include "dsp/dspengine.h" -#include "mainwindow.h" - -#include "chanalyzer.h" - -ChannelAnalyzerGUI* ChannelAnalyzerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) -{ - ChannelAnalyzerGUI* gui = new ChannelAnalyzerGUI(pluginAPI, deviceUISet, rxChannel); - return gui; -} - -void ChannelAnalyzerGUI::destroy() -{ - delete this; -} - -void ChannelAnalyzerGUI::setName(const QString& name) -{ - setObjectName(name); -} - -QString ChannelAnalyzerGUI::getName() const -{ - return objectName(); -} - -qint64 ChannelAnalyzerGUI::getCenterFrequency() const -{ - return m_channelMarker.getCenterFrequency(); -} - -void ChannelAnalyzerGUI::setCenterFrequency(qint64 centerFrequency) -{ - m_channelMarker.setCenterFrequency(centerFrequency); - applySettings(); -} - -void ChannelAnalyzerGUI::resetToDefaults() -{ - blockApplySettings(true); - - ui->BW->setValue(30); - ui->deltaFrequency->setValue(0); - ui->spanLog2->setValue(3); - - blockApplySettings(false); - applySettings(); -} - -QByteArray ChannelAnalyzerGUI::serialize() const -{ - SimpleSerializer s(1); - s.writeS32(1, m_channelMarker.getCenterFrequency()); - s.writeS32(2, ui->BW->value()); - s.writeBlob(3, ui->spectrumGUI->serialize()); - s.writeU32(4, m_channelMarker.getColor().rgb()); - s.writeS32(5, ui->lowCut->value()); - s.writeS32(6, ui->spanLog2->value()); - s.writeBool(7, ui->ssb->isChecked()); - s.writeBlob(8, ui->scopeGUI->serialize()); - - return s.final(); -} - -bool ChannelAnalyzerGUI::deserialize(const QByteArray& data) -{ - SimpleDeserializer d(data); - - if(!d.isValid()) - { - resetToDefaults(); - return false; - } - - if(d.getVersion() == 1) - { - QByteArray bytetmp; - quint32 u32tmp; - qint32 tmp, bw, lowCut; - bool tmpBool; - - blockApplySettings(true); - m_channelMarker.blockSignals(true); - - d.readS32(1, &tmp, 0); - m_channelMarker.setCenterFrequency(tmp); - d.readS32(2, &bw, 30); - ui->BW->setValue(bw); - d.readBlob(3, &bytetmp); - ui->spectrumGUI->deserialize(bytetmp); - - if(d.readU32(4, &u32tmp)) - { - m_channelMarker.setColor(u32tmp); - } - - d.readS32(5, &lowCut, 3); - ui->lowCut->setValue(lowCut); - d.readS32(6, &tmp, 20); - ui->spanLog2->setValue(tmp); - setNewRate(tmp); - d.readBool(7, &tmpBool, false); - ui->ssb->setChecked(tmpBool); - d.readBlob(8, &bytetmp); - ui->scopeGUI->deserialize(bytetmp); - - blockApplySettings(false); - m_channelMarker.blockSignals(false); - m_channelMarker.emitChangedByAPI(); - - ui->BW->setValue(bw); - ui->lowCut->setValue(lowCut); // does applySettings(); - - return true; - } - else - { - resetToDefaults(); - return false; - } -} - -bool ChannelAnalyzerGUI::handleMessage(const Message& message) -{ - if (ChannelAnalyzer::MsgReportChannelSampleRateChanged::match(message)) - { - setNewRate(m_spanLog2); - return true; - } - - return false; -} - -void ChannelAnalyzerGUI::handleInputMessages() -{ - Message* message; - - while ((message = getInputMessageQueue()->pop()) != 0) - { - qDebug("ChannelAnalyzerGUI::handleInputMessages: message: %s", message->getIdentifier()); - - if (handleMessage(*message)) - { - delete message; - } - } -} - -void ChannelAnalyzerGUI::channelMarkerChangedByCursor() -{ - ui->deltaFrequency->setValue(abs(m_channelMarker.getCenterFrequency())); - ui->deltaMinus->setChecked(m_channelMarker.getCenterFrequency() < 0); - - applySettings(); -} - -void ChannelAnalyzerGUI::channelMarkerHighlightedByCursor() -{ - setHighlighted(m_channelMarker.getHighlighted()); -} - -void ChannelAnalyzerGUI::tick() -{ - Real powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq()); - m_channelPowerDbAvg(powDb); - ui->channelPower->setText(QString::number((Real) m_channelPowerDbAvg, 'f', 1)); -} - -void ChannelAnalyzerGUI::on_deltaMinus_toggled(bool minus) -{ - int deltaFrequency = m_channelMarker.getCenterFrequency(); - bool minusDelta = (deltaFrequency < 0); - - if (minus ^ minusDelta) // sign change - { - m_channelMarker.setCenterFrequency(-deltaFrequency); - } -} - -void ChannelAnalyzerGUI::on_deltaFrequency_changed(quint64 value) -{ - if (ui->deltaMinus->isChecked()) { - m_channelMarker.setCenterFrequency(-value); - } else { - m_channelMarker.setCenterFrequency(value); - } - - applySettings(); -} - -void ChannelAnalyzerGUI::on_BW_valueChanged(int value) -{ - QString s = QString::number(value/10.0, 'f', 1); - ui->BWText->setText(tr("%1k").arg(s)); - m_channelMarker.setBandwidth(value * 100 * 2); - - if (ui->ssb->isChecked()) - { - if (value < 0) { - m_channelMarker.setSidebands(ChannelMarker::lsb); - } else { - m_channelMarker.setSidebands(ChannelMarker::usb); - } - } - else - { - m_channelMarker.setSidebands(ChannelMarker::dsb); - } - - on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100); -} - -int ChannelAnalyzerGUI::getEffectiveLowCutoff(int lowCutoff) -{ - int ssbBW = m_channelMarker.getBandwidth() / 2; - int effectiveLowCutoff = lowCutoff; - const int guard = 100; - - if (ssbBW < 0) { - if (effectiveLowCutoff < ssbBW + guard) { - effectiveLowCutoff = ssbBW + guard; - } - if (effectiveLowCutoff > 0) { - effectiveLowCutoff = 0; - } - } else { - if (effectiveLowCutoff > ssbBW - guard) { - effectiveLowCutoff = ssbBW - guard; - } - if (effectiveLowCutoff < 0) { - effectiveLowCutoff = 0; - } - } - - return effectiveLowCutoff; -} - -void ChannelAnalyzerGUI::on_lowCut_valueChanged(int value) -{ - int lowCutoff = getEffectiveLowCutoff(value * 100); - m_channelMarker.setLowCutoff(lowCutoff); - QString s = QString::number(lowCutoff/1000.0, 'f', 1); - ui->lowCutText->setText(tr("%1k").arg(s)); - ui->lowCut->setValue(lowCutoff/100); - applySettings(); -} - -void ChannelAnalyzerGUI::on_spanLog2_valueChanged(int value) -{ - if (setNewRate(value)) { - applySettings(); - } - -} - -void ChannelAnalyzerGUI::on_ssb_toggled(bool checked) -{ - if (checked) - { - if (ui->BW->value() < 0) { - m_channelMarker.setSidebands(ChannelMarker::lsb); - } else { - m_channelMarker.setSidebands(ChannelMarker::usb); - } - - ui->glSpectrum->setCenterFrequency(m_rate/4); - ui->glSpectrum->setSampleRate(m_rate/2); - ui->glSpectrum->setSsbSpectrum(true); - - on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100); - } - else - { - m_channelMarker.setSidebands(ChannelMarker::dsb); - - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(m_rate); - ui->glSpectrum->setSsbSpectrum(false); - - applySettings(); - } -} - -void ChannelAnalyzerGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) -{ - /* - if((widget == ui->spectrumContainer) && (m_ssbDemod != NULL)) - m_ssbDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown); - */ -} - -ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : - RollupWidget(parent), - ui(new Ui::ChannelAnalyzerGUI), - m_pluginAPI(pluginAPI), - m_deviceUISet(deviceUISet), - m_channelMarker(this), - m_doApplySettings(true), - m_rate(6000), - m_spanLog2(3) -{ - ui->setupUi(this); - setAttribute(Qt::WA_DeleteOnClose, true); - connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - - m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); - m_scopeVis = new ScopeVis(SDR_RX_SCALEF, ui->glScope); - m_spectrumScopeComboVis = new SpectrumScopeComboVis(m_spectrumVis, m_scopeVis); - m_channelAnalyzer = (ChannelAnalyzer*) rxChannel; //new ChannelAnalyzer(m_deviceUISet->m_deviceSourceAPI); - m_channelAnalyzer->setSampleSink(m_spectrumScopeComboVis); - m_channelAnalyzer->setMessageQueueToGUI(getInputMessageQueue()); - - ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold)); - ui->deltaFrequency->setValueRange(7, 0U, 9999999U); - - ui->glSpectrum->setCenterFrequency(m_rate/2); - ui->glSpectrum->setSampleRate(m_rate); - ui->glSpectrum->setDisplayWaterfall(true); - ui->glSpectrum->setDisplayMaxHold(true); - ui->glSpectrum->setSsbSpectrum(true); - - ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); - ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); - connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); - - m_channelMarker.blockSignals(true); - m_channelMarker.setColor(Qt::gray); - m_channelMarker.setBandwidth(m_rate); - m_channelMarker.setSidebands(ChannelMarker::usb); - m_channelMarker.setCenterFrequency(0); - m_channelMarker.blockSignals(false); - m_channelMarker.setVisible(true); // activate signal on the last setting only - setTitleColor(m_channelMarker.getColor()); - - m_deviceUISet->registerRxChannelInstance(ChannelAnalyzer::m_channelIdURI, this); - m_deviceUISet->addChannelMarker(&m_channelMarker); - m_deviceUISet->addRollupWidget(this); - - ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); - ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); - - connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); - connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); - connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); - - applySettings(); - setNewRate(m_spanLog2); -} - -ChannelAnalyzerGUI::~ChannelAnalyzerGUI() -{ - m_deviceUISet->removeRxChannelInstance(this); - delete m_channelAnalyzer; // TODO: check this: when the GUI closes it has to delete the demodulator - delete m_spectrumVis; - delete m_scopeVis; - delete m_spectrumScopeComboVis; - delete ui; -} - -bool ChannelAnalyzerGUI::setNewRate(int spanLog2) -{ - qDebug("ChannelAnalyzerGUI::setNewRate"); - - if ((spanLog2 < 0) || (spanLog2 > 6)) { - return false; - } - - m_spanLog2 = spanLog2; - m_rate = m_channelAnalyzer->getSampleRate() / (1<BW->value() < -m_rate/200) { - ui->BW->setValue(-m_rate/200); - m_channelMarker.setBandwidth(-m_rate*2); - } else if (ui->BW->value() > m_rate/200) { - ui->BW->setValue(m_rate/200); - m_channelMarker.setBandwidth(m_rate*2); - } - - if (ui->lowCut->value() < -m_rate/200) { - ui->lowCut->setValue(-m_rate/200); - m_channelMarker.setLowCutoff(-m_rate); - } else if (ui->lowCut->value() > m_rate/200) { - ui->lowCut->setValue(m_rate/200); - m_channelMarker.setLowCutoff(m_rate); - } - - ui->BW->setMinimum(-m_rate/200); - ui->lowCut->setMinimum(-m_rate/200); - ui->BW->setMaximum(m_rate/200); - ui->lowCut->setMaximum(m_rate/200); - - QString s = QString::number(m_rate/1000.0, 'f', 1); - ui->spanText->setText(tr("%1k").arg(s)); - - if (ui->ssb->isChecked()) - { - if (ui->BW->value() < 0) { - m_channelMarker.setSidebands(ChannelMarker::lsb); - } else { - m_channelMarker.setSidebands(ChannelMarker::usb); - } - - ui->glSpectrum->setCenterFrequency(m_rate/4); - ui->glSpectrum->setSampleRate(m_rate/2); - ui->glSpectrum->setSsbSpectrum(true); - } - else - { - m_channelMarker.setSidebands(ChannelMarker::dsb); - - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(m_rate); - ui->glSpectrum->setSsbSpectrum(false); - } - - ui->glScope->setSampleRate(m_rate); - m_scopeVis->setSampleRate(m_rate); - - return true; -} - -void ChannelAnalyzerGUI::blockApplySettings(bool block) -{ - ui->glScope->blockSignals(block); - ui->glSpectrum->blockSignals(block); - m_doApplySettings = !block; -} - -void ChannelAnalyzerGUI::applySettings() -{ - if (m_doApplySettings) - { - ChannelAnalyzer::MsgConfigureChannelizer *msg = ChannelAnalyzer::MsgConfigureChannelizer::create(m_channelMarker.getCenterFrequency()); - m_channelAnalyzer->getInputMessageQueue()->push(msg); - - m_channelAnalyzer->configure(m_channelAnalyzer->getInputMessageQueue(), - ui->BW->value() * 100.0, - ui->lowCut->value() * 100.0, - m_spanLog2, - ui->ssb->isChecked()); - } -} - -void ChannelAnalyzerGUI::leaveEvent(QEvent*) -{ - m_channelMarker.setHighlighted(false); -} - -void ChannelAnalyzerGUI::enterEvent(QEvent*) -{ - m_channelMarker.setHighlighted(true); -} - diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h deleted file mode 100644 index d81a8dc9c..000000000 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ /dev/null @@ -1,101 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 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 // -// // -// 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_CHANNELANALYZERGUI_H -#define INCLUDE_CHANNELANALYZERGUI_H - -#include -#include "gui/rollupwidget.h" -#include "dsp/channelmarker.h" -#include "dsp/dsptypes.h" -#include "util/movingaverage.h" -#include "util/messagequeue.h" - -class PluginAPI; -class DeviceUISet; -class BasebandSampleSink; -class ChannelAnalyzer; -class SpectrumScopeComboVis; -class SpectrumVis; -class ScopeVis; - -namespace Ui { - class ChannelAnalyzerGUI; -} - -class ChannelAnalyzerGUI : public RollupWidget, public PluginInstanceGUI { - Q_OBJECT - -public: - static ChannelAnalyzerGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUIset, BasebandSampleSink *rxChannel); - virtual void destroy(); - - void setName(const QString& name); - QString getName() const; - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); - virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } - virtual bool handleMessage(const Message& message); - -public slots: - void channelMarkerChangedByCursor(); - void channelMarkerHighlightedByCursor(); - -private: - Ui::ChannelAnalyzerGUI* ui; - PluginAPI* m_pluginAPI; - DeviceUISet* m_deviceUISet; - ChannelMarker m_channelMarker; - bool m_doApplySettings; - int m_rate; - int m_spanLog2; - MovingAverageUtil m_channelPowerDbAvg; - - ChannelAnalyzer* m_channelAnalyzer; - SpectrumScopeComboVis* m_spectrumScopeComboVis; - SpectrumVis* m_spectrumVis; - ScopeVis* m_scopeVis; - MessageQueue m_inputMessageQueue; - - explicit ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); - virtual ~ChannelAnalyzerGUI(); - - int getEffectiveLowCutoff(int lowCutoff); - bool setNewRate(int spanLog2); - - void blockApplySettings(bool block); - void applySettings(); - - void leaveEvent(QEvent*); - void enterEvent(QEvent*); - -private slots: - void on_deltaFrequency_changed(quint64 value); - void on_deltaMinus_toggled(bool minus); - void on_BW_valueChanged(int value); - void on_lowCut_valueChanged(int value); - void on_spanLog2_valueChanged(int value); - void on_ssb_toggled(bool checked); - void onWidgetRolled(QWidget* widget, bool rollDown); - void handleInputMessages(); - void tick(); -}; - -#endif // INCLUDE_CHANNELANALYZERGUI_H diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui deleted file mode 100644 index 2ea84a2bb..000000000 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ /dev/null @@ -1,540 +0,0 @@ - - - ChannelAnalyzerGUI - - - - 0 - 0 - 640 - 814 - - - - - 640 - 0 - - - - - Liberation Sans - 9 - - - - Channel Analyzer - - - - - 0 - 10 - 301 - 131 - - - - Settings - - - - 3 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - - - - - Frequency shift direction - - - ... - - - - :/plus.png - :/minus.png - - - - true - - - false - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - Monospace - 12 - - - - SizeVerCursor - - - Qt::StrongFocus - - - Demod shift frequency from center in Hz - - - - - - - - - - - - 26 - 26 - 26 - - - - - - - 255 - 255 - 255 - - - - - - - - - 26 - 26 - 26 - - - - - - - 255 - 255 - 255 - - - - - - - - - 118 - 118 - 117 - - - - - - - 255 - 255 - 255 - - - - - - - - Hz - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Channel power - - - Qt::LeftToRight - - - 0.0 - - - - - - - dB - - - - - - - - - - - - - Rate - - - - - - - Channel sample rate - - - 0 - - - 6 - - - 1 - - - 3 - - - 3 - - - Qt::Horizontal - - - true - - - true - - - - - - - 6.0k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - SSB/DSB toggle - - - SSB - - - - - - - - - - - BW - - - - - - - Lowpass filter cutoff frequency - - - -60 - - - 60 - - - 1 - - - 30 - - - Qt::Horizontal - - - - - - - - 50 - 0 - - - - 3.0k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - Low cut. - - - - - - - Highpass filter cutoff frequency (SSB) - - - -60 - - - 60 - - - 1 - - - 3 - - - Qt::Horizontal - - - - - - - - 50 - 0 - - - - 0.3k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - 10 - 180 - 636 - 284 - - - - - 636 - 0 - - - - Channel Spectrum - - - - 2 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - - 200 - 250 - - - - - Monospace - 8 - - - - - - - - - - - - - 0 - 470 - 636 - 284 - - - - - 636 - 0 - - - - Channel Scope - - - - 2 - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 200 - 250 - - - - - Monospace - 8 - - - - - - - - - - - - - RollupWidget - QWidget -
gui/rollupwidget.h
- 1 -
- - ValueDial - QWidget -
gui/valuedial.h
- 1 -
- - GLSpectrum - QWidget -
gui/glspectrum.h
- 1 -
- - GLSpectrumGUI - QWidget -
gui/glspectrumgui.h
- 1 -
- - GLScope - QWidget -
gui/glscope.h
- 1 -
- - GLScopeGUI - QWidget -
gui/glscopegui.h
- 1 -
-
- - - - -
diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp deleted file mode 100644 index 32f33cc41..000000000 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "chanalyzerplugin.h" - -#include -#include "plugin/pluginapi.h" - -#include "chanalyzergui.h" -#include "chanalyzer.h" - -const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { - QString("Channel Analyzer"), - QString("3.14.5"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -ChannelAnalyzerPlugin::ChannelAnalyzerPlugin(QObject* parent) : - QObject(parent), - m_pluginAPI(0) -{ -} - -const PluginDescriptor& ChannelAnalyzerPlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void ChannelAnalyzerPlugin::initPlugin(PluginAPI* pluginAPI) -{ - m_pluginAPI = pluginAPI; - - // register demodulator - m_pluginAPI->registerRxChannel(ChannelAnalyzer::m_channelIdURI, ChannelAnalyzer::m_channelId, this); -} - -PluginInstanceGUI* ChannelAnalyzerPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) -{ - return ChannelAnalyzerGUI::create(m_pluginAPI, deviceUISet, rxChannel); -} - -BasebandSampleSink* ChannelAnalyzerPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) -{ - return new ChannelAnalyzer(deviceAPI); -} - -ChannelSinkAPI* ChannelAnalyzerPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) -{ - return new ChannelAnalyzer(deviceAPI); -} diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.h b/plugins/channelrx/chanalyzer/chanalyzerplugin.h deleted file mode 100644 index e8a48d3b6..000000000 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef INCLUDE_CHANALYZERPLUGIN_H -#define INCLUDE_CHANALYZERPLUGIN_H - -#include -#include "plugin/plugininterface.h" - -class DeviceUISet; -class BasebandSampleSink; -class ChannelSinkAPI; - -class ChannelAnalyzerPlugin : public QObject, PluginInterface { - Q_OBJECT - Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "org.f4exb.sdrangelove.channel.chanalyzer") - -public: - explicit ChannelAnalyzerPlugin(QObject* parent = NULL); - - const PluginDescriptor& getPluginDescriptor() const; - void initPlugin(PluginAPI* pluginAPI); - - virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); - virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); - virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); - -private: - static const PluginDescriptor m_pluginDescriptor; - - PluginAPI* m_pluginAPI; -}; - -#endif // INCLUDE_CHANALYZERPLUGIN_H diff --git a/plugins/channelrx/tcpsrc/CMakeLists.txt b/plugins/channelrx/tcpsrc/CMakeLists.txt deleted file mode 100644 index 6986907b6..000000000 --- a/plugins/channelrx/tcpsrc/CMakeLists.txt +++ /dev/null @@ -1,48 +0,0 @@ -project(tcpsrc) - -set(tcpsrc_SOURCES - tcpsrc.cpp - tcpsrcgui.cpp - tcpsrcplugin.cpp - tcpsrcsettings.cpp -) - -set(tcpsrc_HEADERS - tcpsrc.h - tcpsrcgui.h - tcpsrcplugin.h - tcpsrcsettings.h -) - -set(tcpsrc_FORMS - tcpsrcgui.ui -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} -) - -#include(${QT_USE_FILE}) -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -#qt5_wrap_cpp(tcpsrc_HEADERS_MOC ${tcpsrc_HEADERS}) -qt5_wrap_ui(tcpsrc_FORMS_HEADERS ${tcpsrc_FORMS}) - -add_library(demodtcpsrc SHARED - ${tcpsrc_SOURCES} - ${tcpsrc_HEADERS_MOC} - ${tcpsrc_FORMS_HEADERS} -) - -target_link_libraries(demodtcpsrc - ${QT_LIBRARIES} - sdrbase - sdrgui -) - -qt5_use_modules(demodtcpsrc Core Widgets Network) - -install(TARGETS demodtcpsrc DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/tcpsrc/tcpsrc.cpp b/plugins/channelrx/tcpsrc/tcpsrc.cpp deleted file mode 100644 index 5e19f537f..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrc.cpp +++ /dev/null @@ -1,487 +0,0 @@ -// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // -// (C) 2015 John Greb // -// // -// 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 "tcpsrc.h" - -#include -#include - -#include "dsp/downchannelizer.h" -#include "dsp/threadedbasebandsamplesink.h" -#include "dsp/dspcommands.h" -#include "device/devicesourceapi.h" - -#include "tcpsrcgui.h" - -MESSAGE_CLASS_DEFINITION(TCPSrc::MsgConfigureTCPSrc, Message) -MESSAGE_CLASS_DEFINITION(TCPSrc::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(TCPSrc::MsgTCPSrcConnection, Message) -MESSAGE_CLASS_DEFINITION(TCPSrc::MsgTCPSrcSpectrum, Message) - -const QString TCPSrc::m_channelIdURI = "sdrangel.channel.tcpsrc"; -const QString TCPSrc::m_channelId = "TCPSrc"; - -TCPSrc::TCPSrc(DeviceSourceAPI* deviceAPI) : - ChannelSinkAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_absoluteFrequencyOffset(0), - m_settingsMutex(QMutex::Recursive) -{ - setObjectName(m_channelId); - - m_inputSampleRate = 96000; - m_sampleFormat = TCPSrcSettings::FormatSSB; - m_outputSampleRate = 48000; - m_rfBandwidth = 32000; - m_tcpServer = 0; - m_tcpPort = 9999; - m_nco.setFreq(0, m_inputSampleRate); - m_interpolator.create(16, m_inputSampleRate, m_rfBandwidth / 2.0); - m_sampleDistanceRemain = m_inputSampleRate / m_outputSampleRate; - m_spectrum = 0; - m_spectrumEnabled = false; - m_nextSSBId = 0; - m_nextS16leId = 0; - - m_last = 0; - m_this = 0; - m_scale = 0; - m_volume = 0; - m_magsq = 0; - - m_sampleBufferSSB.resize(tcpFftLen); - TCPFilter = new fftfilt(0.3 / 48.0, 16.0 / 48.0, tcpFftLen); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addThreadedSink(m_threadedChannelizer); - m_deviceAPI->addChannelAPI(this); -} - -TCPSrc::~TCPSrc() -{ - m_deviceAPI->removeChannelAPI(this); - m_deviceAPI->removeThreadedSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; - delete TCPFilter; -} - -void TCPSrc::setSpectrum(MessageQueue* messageQueue, bool enabled) -{ - Message* cmd = MsgTCPSrcSpectrum::create(enabled); - messageQueue->push(cmd); -} - -void TCPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) -{ - Complex ci; - fftfilt::cmplx* sideband; - Real l, r; - - m_sampleBuffer.clear(); - - m_settingsMutex.lock(); - - // Rtl-Sdr uses full 16-bit scale; FCDPP does not - int rescale = (1 << m_volume); - - for(SampleVector::const_iterator it = begin; it < end; ++it) { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if(m_interpolator.decimate(&m_sampleDistanceRemain, c, &ci)) - { - m_magsq = ((ci.real()*ci.real() + ci.imag()*ci.imag())*rescale*rescale) / (SDR_RX_SCALED*SDR_RX_SCALED); - m_sampleBuffer.push_back(Sample(ci.real() * rescale, ci.imag() * rescale)); - m_sampleDistanceRemain += m_inputSampleRate / m_outputSampleRate; - } - } - - if((m_spectrum != 0) && (m_spectrumEnabled)) - { - m_spectrum->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), positiveOnly); - } - - for(int i = 0; i < m_s16leSockets.count(); i++) - { - m_s16leSockets[i].socket->write((const char*)&m_sampleBuffer[0], m_sampleBuffer.size() * 4); - } - - if((m_sampleFormat == TCPSrcSettings::FormatSSB) && (m_ssbSockets.count() > 0)) { - for(SampleVector::const_iterator it = m_sampleBuffer.begin(); it != m_sampleBuffer.end(); ++it) { - //Complex cj(it->real() / 30000.0, it->imag() / 30000.0); - Complex cj(it->real(), it->imag()); - int n_out = TCPFilter->runSSB(cj, &sideband, true); - if (n_out) { - for (int i = 0; i < n_out; i+=2) { - //l = (sideband[i].real() + sideband[i].imag()) * 0.7 * 32000.0; - //r = (sideband[i+1].real() + sideband[i+1].imag()) * 0.7 * 32000.0; - l = (sideband[i].real() + sideband[i].imag()) * 0.7; - r = (sideband[i+1].real() + sideband[i+1].imag()) * 0.7; - m_sampleBufferSSB.push_back(Sample(l, r)); - } - for(int i = 0; i < m_ssbSockets.count(); i++) - m_ssbSockets[i].socket->write((const char*)&m_sampleBufferSSB[0], n_out * 2); - m_sampleBufferSSB.clear(); - } - } - } - - if((m_sampleFormat == TCPSrcSettings::FormatNFM) && (m_ssbSockets.count() > 0)) { - for(SampleVector::const_iterator it = m_sampleBuffer.begin(); it != m_sampleBuffer.end(); ++it) { - Complex cj(it->real() / SDR_RX_SCALEF, it->imag() / SDR_RX_SCALEF); - // An FFT filter here is overkill, but was already set up for SSB - int n_out = TCPFilter->runFilt(cj, &sideband); - if (n_out) { - Real sum = 1.0; - for (int i = 0; i < n_out; i+=2) { - l = m_this.real() * (m_last.imag() - sideband[i].imag()) - - m_this.imag() * (m_last.real() - sideband[i].real()); - m_last = sideband[i]; - r = m_last.real() * (m_this.imag() - sideband[i+1].imag()) - - m_last.imag() * (m_this.real() - sideband[i+1].real()); - m_this = sideband[i+1]; - m_sampleBufferSSB.push_back(Sample(l * m_scale, r * m_scale)); - sum += m_this.real() * m_this.real() + m_this.imag() * m_this.imag(); - } - // TODO: correct levels - m_scale = 24000 * tcpFftLen / sum; - for(int i = 0; i < m_ssbSockets.count(); i++) - m_ssbSockets[i].socket->write((const char*)&m_sampleBufferSSB[0], n_out * 2); - m_sampleBufferSSB.clear(); - } - } - } - - m_settingsMutex.unlock(); -} - -void TCPSrc::start() -{ - m_tcpServer = new QTcpServer(); - connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); - connect(m_tcpServer, SIGNAL(acceptError(QAbstractSocket::SocketError)), this, SLOT(onTcpServerError(QAbstractSocket::SocketError))); - m_tcpServer->listen(QHostAddress::Any, m_tcpPort); -} - -void TCPSrc::stop() -{ - closeAllSockets(&m_ssbSockets); - closeAllSockets(&m_s16leSockets); - - if(m_tcpServer->isListening()) - m_tcpServer->close(); - delete m_tcpServer; -} - -bool TCPSrc::handleMessage(const Message& cmd) -{ - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - m_settingsMutex.lock(); - - m_inputSampleRate = notif.getSampleRate(); - m_nco.setFreq(-notif.getFrequencyOffset(), m_inputSampleRate); - m_interpolator.create(16, m_inputSampleRate, m_rfBandwidth / 2.0); - m_sampleDistanceRemain = m_inputSampleRate / m_outputSampleRate; - - m_settingsMutex.unlock(); - - qDebug() << "TCPSrc::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << m_inputSampleRate - << " frequencyOffset: " << notif.getFrequencyOffset(); - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - - qDebug() << "TCPSrc::handleMessage: MsgConfigureChannelizer:" - << " sampleRate: " << cfg.getSampleRate() - << " centerFrequency: " << cfg.getCenterFrequency(); - - return true; - } - else if (MsgConfigureTCPSrc::match(cmd)) - { - MsgConfigureTCPSrc& cfg = (MsgConfigureTCPSrc&) cmd; - - TCPSrcSettings settings = cfg.getSettings(); - - // These settings are set with DownChannelizer::MsgChannelizerNotification - m_absoluteFrequencyOffset = settings.m_inputFrequencyOffset; - settings.m_inputSampleRate = m_settings.m_inputSampleRate; - settings.m_inputFrequencyOffset = m_settings.m_inputFrequencyOffset; - - m_settingsMutex.lock(); - - m_sampleFormat = settings.m_sampleFormat; - m_outputSampleRate = settings.m_outputSampleRate; - m_rfBandwidth = settings.m_rfBandwidth; - - if (settings.m_tcpPort != m_tcpPort) - { - m_tcpPort = settings.m_tcpPort; - - if(m_tcpServer->isListening()) - { - m_tcpServer->close(); - } - - m_tcpServer->listen(QHostAddress::Any, m_tcpPort); - } - - m_volume = settings.m_volume; - m_interpolator.create(16, m_inputSampleRate, m_rfBandwidth / 2.0); - m_sampleDistanceRemain = m_inputSampleRate / m_outputSampleRate; - - if (m_sampleFormat == TCPSrcSettings::FormatSSB) - { - TCPFilter->create_filter(0.3 / 48.0, m_rfBandwidth / 2.0 / m_outputSampleRate); - } - else - { - TCPFilter->create_filter(0.0, m_rfBandwidth / 2.0 / m_outputSampleRate); - } - - m_settingsMutex.unlock(); - - qDebug() << "TCPSrc::handleMessage: MsgConfigureTCPSrc:" - << " m_sampleFormat: " << m_sampleFormat - << " m_outputSampleRate: " << m_outputSampleRate - << " m_rfBandwidth: " << m_rfBandwidth - << " m_volume: " << m_volume; - - m_settings = settings; - - return true; - } - else if (MsgTCPSrcSpectrum::match(cmd)) - { - MsgTCPSrcSpectrum& spc = (MsgTCPSrcSpectrum&) cmd; - - m_spectrumEnabled = spc.getEnabled(); - - qDebug() << "TCPSrc::handleMessage: MsgTCPSrcSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; - - return true; - } - else if (MsgTCPSrcConnection::match(cmd)) - { - MsgTCPSrcConnection& con = (MsgTCPSrcConnection&) cmd; - - qDebug() << "TCPSrc::handleMessage: MsgTCPSrcConnection" - << " connect: " << con.getConnect() - << " id: " << con.getID() - << " peer address: " << con.getPeerAddress() - << " peer port: " << con.getPeerPort(); - - if (con.getConnect()) - { - processNewConnection(); - } - else - { - processDeconnection(); - } - } - else if (DSPSignalNotification::match(cmd)) - { - return true; - } - else - { - if(m_spectrum != 0) - { - return m_spectrum->handleMessage(cmd); - } - else - { - return false; - } - } - - return false; -} - -void TCPSrc::closeAllSockets(Sockets* sockets) -{ - for(int i = 0; i < sockets->count(); ++i) - { - MsgTCPSrcConnection* msg = MsgTCPSrcConnection::create(false, sockets->at(i).id, QHostAddress(), 0); - getInputMessageQueue()->push(msg); - - if (getMessageQueueToGUI()) { // Propagate to GUI - getMessageQueueToGUI()->push(msg); - } - - sockets->at(i).socket->close(); - } -} - -void TCPSrc::onNewConnection() -{ - qDebug("TCPSrc::onNewConnection"); - processNewConnection(); -} - -void TCPSrc::processNewConnection() -{ - qDebug("TCPSrc::processNewConnection"); - - while(m_tcpServer->hasPendingConnections()) - { - qDebug("TCPSrc::processNewConnection: has a pending connection"); - QTcpSocket* connection = m_tcpServer->nextPendingConnection(); - connection->setSocketOption(QAbstractSocket:: KeepAliveOption, 1); - connect(connection, SIGNAL(disconnected()), this, SLOT(onDisconnected())); - - switch(m_sampleFormat) { - - case TCPSrcSettings::FormatNFM: - case TCPSrcSettings::FormatSSB: - { - quint32 id = (TCPSrcSettings::FormatSSB << 24) | m_nextSSBId; - m_nextSSBId = (m_nextSSBId + 1) & 0xffffff; - m_ssbSockets.push_back(Socket(id, connection)); - - if (getMessageQueueToGUI()) // Notify GUI of peer details - { - MsgTCPSrcConnection* msg = MsgTCPSrcConnection::create(true, id, connection->peerAddress(), connection->peerPort()); - getMessageQueueToGUI()->push(msg); - } - - break; - } - - case TCPSrcSettings::FormatS16LE: - { - qDebug("TCPSrc::processNewConnection: establish new S16LE connection"); - quint32 id = (TCPSrcSettings::FormatS16LE << 24) | m_nextS16leId; - m_nextS16leId = (m_nextS16leId + 1) & 0xffffff; - m_s16leSockets.push_back(Socket(id, connection)); - - if (getMessageQueueToGUI()) // Notify GUI of peer details - { - MsgTCPSrcConnection* msg = MsgTCPSrcConnection::create(true, id, connection->peerAddress(), connection->peerPort()); - getMessageQueueToGUI()->push(msg); - } - - break; - } - - default: - delete connection; - break; - } - } -} - -void TCPSrc::onDisconnected() -{ - qDebug("TCPSrc::onDisconnected"); - MsgTCPSrcConnection *cmd = MsgTCPSrcConnection::create(false, 0, QHostAddress::Any, 0); - getInputMessageQueue()->push(cmd); - - if (getMessageQueueToGUI()) { // Propagate to GUI - getMessageQueueToGUI()->push(cmd); - } - -} - -void TCPSrc::processDeconnection() -{ - quint32 id; - QTcpSocket* socket = 0; - - qDebug("TCPSrc::processDeconnection"); - - for(int i = 0; i < m_ssbSockets.count(); i++) - { - if(m_ssbSockets[i].socket == sender()) - { - id = m_ssbSockets[i].id; - socket = m_ssbSockets[i].socket; - socket->close(); - m_ssbSockets.removeAt(i); - break; - } - } - - if(socket == 0) - { - for(int i = 0; i < m_s16leSockets.count(); i++) - { - if(m_s16leSockets[i].socket == sender()) - { - qDebug("TCPSrc::processDeconnection: remove S16LE socket #%d", i); - - id = m_s16leSockets[i].id; - socket = m_s16leSockets[i].socket; - socket->close(); - m_s16leSockets.removeAt(i); - break; - } - } - } - - if(socket != 0) - { - MsgTCPSrcConnection* msg = MsgTCPSrcConnection::create(false, id, QHostAddress(), 0); - getInputMessageQueue()->push(msg); - - if (getMessageQueueToGUI()) { // Propagate to GUI - getMessageQueueToGUI()->push(msg); - } - - socket->deleteLater(); - } -} - -void TCPSrc::onTcpServerError(QAbstractSocket::SocketError socketError __attribute__((unused))) -{ - qDebug("TCPSrc::onTcpServerError: %s", qPrintable(m_tcpServer->errorString())); -} - -QByteArray TCPSrc::serialize() const -{ - return m_settings.serialize(); -} - -bool TCPSrc::deserialize(const QByteArray& data) -{ - if (m_settings.deserialize(data)) - { - MsgConfigureTCPSrc *msg = MsgConfigureTCPSrc::create(m_settings, true); - m_inputMessageQueue.push(msg); - return true; - } - else - { - m_settings.resetToDefaults(); - MsgConfigureTCPSrc *msg = MsgConfigureTCPSrc::create(m_settings, true); - m_inputMessageQueue.push(msg); - return false; - } -} - diff --git a/plugins/channelrx/tcpsrc/tcpsrc.h b/plugins/channelrx/tcpsrc/tcpsrc.h deleted file mode 100644 index 471a8c3e7..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrc.h +++ /dev/null @@ -1,224 +0,0 @@ -#ifndef INCLUDE_TCPSRC_H -#define INCLUDE_TCPSRC_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/nco.h" -#include "dsp/fftfilt.h" -#include "dsp/interpolator.h" -#include "util/message.h" - -#include "tcpsrcsettings.h" - -#define tcpFftLen 2048 - -class QTcpServer; -class QTcpSocket; -class TCPSrcGUI; -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -class TCPSrc : public BasebandSampleSink, public ChannelSinkAPI { - Q_OBJECT - -public: - class MsgConfigureTCPSrc : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const TCPSrcSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureTCPSrc* create(const TCPSrcSettings& settings, bool force) - { - return new MsgConfigureTCPSrc(settings, force); - } - - private: - TCPSrcSettings m_settings; - bool m_force; - - MsgConfigureTCPSrc(const TCPSrcSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { - } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - class MsgTCPSrcConnection : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getConnect() const { return m_connect; } - quint32 getID() const { return m_id; } - const QHostAddress& getPeerAddress() const { return m_peerAddress; } - int getPeerPort() const { return m_peerPort; } - - static MsgTCPSrcConnection* create(bool connect, quint32 id, const QHostAddress& peerAddress, int peerPort) - { - return new MsgTCPSrcConnection(connect, id, peerAddress, peerPort); - } - - private: - bool m_connect; - quint32 m_id; - QHostAddress m_peerAddress; - int m_peerPort; - - MsgTCPSrcConnection(bool connect, quint32 id, const QHostAddress& peerAddress, int peerPort) : - Message(), - m_connect(connect), - m_id(id), - m_peerAddress(peerAddress), - m_peerPort(peerPort) - { } - }; - - TCPSrc(DeviceSourceAPI* m_deviceAPI); - virtual ~TCPSrc(); - virtual void destroy() { delete this; } - void setSpectrum(BasebandSampleSink* spectrum) { m_spectrum = spectrum; } - - void setSpectrum(MessageQueue* messageQueue, bool enabled); - double getMagSq() const { return m_magsq; } - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); - 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 = m_settings.m_title; } - virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - static const QString m_channelIdURI; - static const QString m_channelId; - -protected: - class MsgTCPSrcSpectrum : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getEnabled() const { return m_enabled; } - - static MsgTCPSrcSpectrum* create(bool enabled) - { - return new MsgTCPSrcSpectrum(enabled); - } - - private: - bool m_enabled; - - MsgTCPSrcSpectrum(bool enabled) : - Message(), - m_enabled(enabled) - { } - }; - class MsgTCPConnection : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getConnect() const { return m_connect; } - - static MsgTCPConnection* create(bool connect) - { - return new MsgTCPConnection(connect); - } - - private: - bool m_connect; - - MsgTCPConnection(bool connect) : - Message(), - m_connect(connect) - { } - }; - - DeviceSourceAPI* m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - TCPSrcSettings m_settings; - int m_absoluteFrequencyOffset; - - int m_inputSampleRate; - - int m_sampleFormat; - Real m_outputSampleRate; - Real m_rfBandwidth; - int m_tcpPort; - int m_volume; - double m_magsq; - - Real m_scale; - Complex m_last, m_this; - - NCO m_nco; - Interpolator m_interpolator; - Real m_sampleDistanceRemain; - fftfilt* TCPFilter; - - SampleVector m_sampleBuffer; - SampleVector m_sampleBufferSSB; - BasebandSampleSink* m_spectrum; - bool m_spectrumEnabled; - - QTcpServer* m_tcpServer; - struct Socket { - quint32 id; - QTcpSocket* socket; - Socket(quint32 _id, QTcpSocket* _socket) : - id(_id), - socket(_socket) - { } - }; - typedef QList Sockets; - Sockets m_ssbSockets; - Sockets m_s16leSockets; - quint32 m_nextSSBId; - quint32 m_nextS16leId; - - QMutex m_settingsMutex; - - void closeAllSockets(Sockets* sockets); - void processNewConnection(); - void processDeconnection(); - -protected slots: - void onNewConnection(); - void onDisconnected(); - void onTcpServerError(QAbstractSocket::SocketError socketError); -}; - -#endif // INCLUDE_TCPSRC_H diff --git a/plugins/channelrx/tcpsrc/tcpsrc.pro b/plugins/channelrx/tcpsrc/tcpsrc.pro deleted file mode 100644 index dd4e04b60..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrc.pro +++ /dev/null @@ -1,43 +0,0 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -TEMPLATE = lib -CONFIG += plugin - -QT += core gui widgets multimedia network opengl - -TARGET = tcpsrc - -DEFINES += USE_SSE2=1 -QMAKE_CXXFLAGS += -msse2 -DEFINES += USE_SSE4_1=1 -QMAKE_CXXFLAGS += -msse4.1 -QMAKE_CXXFLAGS += -std=c++11 - -INCLUDEPATH += $$PWD -INCLUDEPATH += ../../../exports -INCLUDEPATH += ../../../sdrbase -INCLUDEPATH += ../../../sdrgui - -CONFIG(Release):build_subdir = release -CONFIG(Debug):build_subdir = debug - -SOURCES += tcpsrc.cpp\ - tcpsrcgui.cpp\ - tcpsrcplugin.cpp\ - tcpsrcsettings.cpp - -HEADERS += tcpsrc.h\ - tcpsrcgui.h\ - tcpsrcplugin.h\ - tcpsrcsettings.h - -FORMS += tcpsrcgui.ui - -LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase -LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui - -RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/tcpsrc/tcpsrcgui.cpp b/plugins/channelrx/tcpsrc/tcpsrcgui.cpp deleted file mode 100644 index 804b92a33..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcgui.cpp +++ /dev/null @@ -1,397 +0,0 @@ -#include "tcpsrcgui.h" - -#include -#include "device/deviceuiset.h" -#include "plugin/pluginapi.h" -#include "dsp/spectrumvis.h" -#include "dsp/dspengine.h" -#include "util/simpleserializer.h" -#include "util/db.h" -#include "ui_tcpsrcgui.h" -#include "mainwindow.h" -#include "tcpsrc.h" - -TCPSrcGUI* TCPSrcGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) -{ - TCPSrcGUI* gui = new TCPSrcGUI(pluginAPI, deviceUISet, rxChannel); - return gui; -} - -void TCPSrcGUI::destroy() -{ - delete this; -} - -void TCPSrcGUI::setName(const QString& name) -{ - setObjectName(name); -} - -qint64 TCPSrcGUI::getCenterFrequency() const -{ - return m_channelMarker.getCenterFrequency(); -} - -void TCPSrcGUI::setCenterFrequency(qint64 centerFrequency) -{ - m_channelMarker.setCenterFrequency(centerFrequency); - applySettings(); -} - -QString TCPSrcGUI::getName() const -{ - return objectName(); -} - -void TCPSrcGUI::resetToDefaults() -{ - m_settings.resetToDefaults(); - displaySettings(); - applySettings(); -} - -QByteArray TCPSrcGUI::serialize() const -{ - return m_settings.serialize(); -} - -bool TCPSrcGUI::deserialize(const QByteArray& data) -{ - if(m_settings.deserialize(data)) - { - qDebug("TCPSrcGUI::deserialize: m_squelchGate: %d", m_settings.m_squelchGate); - displaySettings(); - applySettings(); - return true; - } else { - resetToDefaults(); - return false; - } -} - -bool TCPSrcGUI::handleMessage(const Message& message) -{ - if (TCPSrc::MsgTCPSrcConnection::match(message)) - { - TCPSrc::MsgTCPSrcConnection& con = (TCPSrc::MsgTCPSrcConnection&) message; - - if(con.getConnect()) - { - addConnection(con.getID(), con.getPeerAddress(), con.getPeerPort()); - } - else - { - delConnection(con.getID()); - } - - qDebug() << "TCPSrcGUI::handleMessage: TCPSrc::MsgTCPSrcConnection: " << con.getConnect() - << " ID: " << con.getID() - << " peerAddress: " << con.getPeerAddress() - << " peerPort: " << con.getPeerPort(); - - return true; - } - else - { - return false; - } -} - -void TCPSrcGUI::channelMarkerChangedByCursor() -{ - ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); - m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - applySettings(); -} - -void TCPSrcGUI::channelMarkerHighlightedByCursor() -{ - setHighlighted(m_channelMarker.getHighlighted()); -} - -void TCPSrcGUI::tick() -{ - double powDb = CalcDb::dbPower(m_tcpSrc->getMagSq()); - m_channelPowerDbAvg(powDb); - ui->channelPower->setText(QString::number(m_channelPowerDbAvg.asDouble(), 'f', 1)); -} - -TCPSrcGUI::TCPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : - RollupWidget(parent), - ui(new Ui::TCPSrcGUI), - m_pluginAPI(pluginAPI), - m_deviceUISet(deviceUISet), - m_tcpSrc(0), - m_channelMarker(this), - m_rfBandwidthChanged(false), - m_doApplySettings(true) -{ - ui->setupUi(this); - ui->connectedClientsBox->hide(); - connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - setAttribute(Qt::WA_DeleteOnClose, true); - - m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); - m_tcpSrc = (TCPSrc*) rxChannel; //new TCPSrc(m_deviceUISet->m_deviceSourceAPI); - m_tcpSrc->setSpectrum(m_spectrumVis); - - ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); - ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); - ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); - - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); - ui->glSpectrum->setDisplayWaterfall(true); - ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); - - ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); - connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); - - m_channelMarker.blockSignals(true); - m_channelMarker.setBandwidth(16000); - m_channelMarker.setCenterFrequency(0); - m_channelMarker.setColor(m_settings.m_rgbColor); - m_channelMarker.blockSignals(false); - m_channelMarker.setVisible(true); // activate signal on the last setting only - - setTitleColor(m_channelMarker.getColor()); - - m_deviceUISet->registerRxChannelInstance(TCPSrc::m_channelIdURI, this); - m_deviceUISet->addChannelMarker(&m_channelMarker); - m_deviceUISet->addRollupWidget(this); - - connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); - connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); - - ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); - - m_settings.setSpectrumGUI(ui->spectrumGUI); - m_settings.setChannelMarker(&m_channelMarker); - - displaySettings(); - applySettings(); -} - -TCPSrcGUI::~TCPSrcGUI() -{ - m_deviceUISet->removeRxChannelInstance(this); - delete m_tcpSrc; // TODO: check this: when the GUI closes it has to delete the demodulator - delete m_spectrumVis; - delete ui; -} - -void TCPSrcGUI::blockApplySettings(bool block) -{ - m_doApplySettings = !block; -} - -void TCPSrcGUI::applySettings() -{ - if (m_doApplySettings) - { - ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); - - TCPSrc::MsgConfigureChannelizer* channelConfigMsg = TCPSrc::MsgConfigureChannelizer::create( - m_settings.m_outputSampleRate, m_channelMarker.getCenterFrequency()); - m_tcpSrc->getInputMessageQueue()->push(channelConfigMsg); - - TCPSrc::MsgConfigureTCPSrc* message = TCPSrc::MsgConfigureTCPSrc::create( m_settings, false); - m_tcpSrc->getInputMessageQueue()->push(message); - } -} - -void TCPSrcGUI::on_deltaFrequency_changed(qint64 value) -{ - m_channelMarker.setCenterFrequency(value); - m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - applySettings(); -} - -void TCPSrcGUI::on_sampleFormat_currentIndexChanged(int index) -{ - setSampleFormat(index); - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - -void TCPSrcGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) -{ - bool ok; - Real outputSampleRate = ui->sampleRate->text().toDouble(&ok); - - if((!ok) || (outputSampleRate < 1000)) - { - m_settings.m_outputSampleRate = 48000; - ui->sampleRate->setText(QString("%1").arg(outputSampleRate, 0)); - } - else - { - m_settings.m_outputSampleRate = outputSampleRate; - } - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - -void TCPSrcGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) -{ - bool ok; - Real rfBandwidth = ui->rfBandwidth->text().toDouble(&ok); - - if((!ok) || (rfBandwidth > m_settings.m_outputSampleRate)) - { - m_settings.m_rfBandwidth = m_settings.m_outputSampleRate; - ui->rfBandwidth->setText(QString("%1").arg(m_settings.m_rfBandwidth, 0)); - } - else - { - m_settings.m_rfBandwidth = rfBandwidth; - } - - m_rfBandwidthChanged = true; - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - -void TCPSrcGUI::on_tcpPort_textEdited(const QString& arg1 __attribute__((unused))) -{ - bool ok; - int tcpPort = ui->tcpPort->text().toInt(&ok); - - if((!ok) || (tcpPort < 1) || (tcpPort > 65535)) - { - m_settings.m_tcpPort = 9999; - ui->tcpPort->setText(QString("%1").arg(m_settings.m_tcpPort, 0)); - } - else - { - m_settings.m_tcpPort = tcpPort; - } - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - -void TCPSrcGUI::on_applyBtn_clicked() -{ - if (m_rfBandwidthChanged) - { - blockApplySettings(true); - m_channelMarker.setBandwidth((int) m_settings.m_rfBandwidth); - m_rfBandwidthChanged = false; - blockApplySettings(false); - } - - ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); - - ui->applyBtn->setEnabled(false); - ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); - - applySettings(); -} - -void TCPSrcGUI::displaySettings() -{ - m_channelMarker.blockSignals(true); - m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); - m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); - m_channelMarker.setTitle(m_settings.m_title); - m_channelMarker.blockSignals(false); - m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only - - setTitleColor(m_settings.m_rgbColor); - setWindowTitle(m_channelMarker.getTitle()); - - blockApplySettings(true); - - ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); - - ui->sampleRate->setText(QString("%1").arg(m_settings.m_outputSampleRate, 0)); - setSampleFormatIndex(m_settings.m_sampleFormat); - - ui->rfBandwidth->setText(QString("%1").arg(m_settings.m_rfBandwidth, 0)); - - ui->volume->setValue(m_settings.m_volume); - ui->volumeText->setText(QString("%1").arg(ui->volume->value())); - - ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); - - blockApplySettings(false); -} - -void TCPSrcGUI::setSampleFormatIndex(const TCPSrcSettings::SampleFormat& sampleFormat) -{ - switch(sampleFormat) - { - case TCPSrcSettings::FormatS16LE: - ui->sampleFormat->setCurrentIndex(0); - break; - case TCPSrcSettings::FormatNFM: - ui->sampleFormat->setCurrentIndex(1); - break; - case TCPSrcSettings::FormatSSB: - ui->sampleFormat->setCurrentIndex(2); - break; - default: - ui->sampleFormat->setCurrentIndex(0); - break; - } -} - -void TCPSrcGUI::setSampleFormat(int index) -{ - switch(index) - { - case 0: - m_settings.m_sampleFormat = TCPSrcSettings::FormatS16LE; - break; - case 1: - m_settings.m_sampleFormat = TCPSrcSettings::FormatNFM; - break; - case 2: - m_settings.m_sampleFormat = TCPSrcSettings::FormatSSB; - break; - default: - m_settings.m_sampleFormat = TCPSrcSettings::FormatS16LE; - break; - } -} - -void TCPSrcGUI::on_volume_valueChanged(int value) -{ - ui->volume->setValue(value); - ui->volumeText->setText(QString("%1").arg(value)); - ui->applyBtn->setEnabled(true); -} - -void TCPSrcGUI::onWidgetRolled(QWidget* widget, bool rollDown) -{ - if ((widget == ui->spectrumBox) && (m_tcpSrc != 0)) - { - m_tcpSrc->setSpectrum(m_tcpSrc->getInputMessageQueue(), rollDown); - } -} - -void TCPSrcGUI::addConnection(quint32 id, const QHostAddress& peerAddress, int peerPort) -{ - QStringList l; - l.append(QString("%1:%2").arg(peerAddress.toString()).arg(peerPort)); - new QTreeWidgetItem(ui->connections, l, id); - ui->connectedClientsBox->setWindowTitle(tr("Connected Clients (%1)").arg(ui->connections->topLevelItemCount())); -} - -void TCPSrcGUI::delConnection(quint32 id) -{ - for(int i = 0; i < ui->connections->topLevelItemCount(); i++) - { - if(ui->connections->topLevelItem(i)->type() == (int)id) - { - delete ui->connections->topLevelItem(i); - ui->connectedClientsBox->setWindowTitle(tr("Connected Clients (%1)").arg(ui->connections->topLevelItemCount())); - return; - } - } -} diff --git a/plugins/channelrx/tcpsrc/tcpsrcgui.h b/plugins/channelrx/tcpsrc/tcpsrcgui.h deleted file mode 100644 index c671a7d80..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcgui.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef INCLUDE_TCPSRCGUI_H -#define INCLUDE_TCPSRCGUI_H - -#include -#include - -#include "gui/rollupwidget.h" -#include "dsp/channelmarker.h" -#include "util/movingaverage.h" -#include "util/messagequeue.h" - -#include "tcpsrc.h" -#include "tcpsrcsettings.h" - -class PluginAPI; -class DeviceUISet; -class TCPSrc; -class SpectrumVis; - -namespace Ui { - class TCPSrcGUI; -} - -class TCPSrcGUI : public RollupWidget, public PluginInstanceGUI { - Q_OBJECT - -public: - static TCPSrcGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); - virtual void destroy(); - - void setName(const QString& name); - QString getName() const; - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); - virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } - virtual bool handleMessage(const Message& message); - -public slots: - void channelMarkerChangedByCursor(); - void channelMarkerHighlightedByCursor(); - -private: - Ui::TCPSrcGUI* ui; - PluginAPI* m_pluginAPI; - DeviceUISet* m_deviceUISet; - TCPSrc* m_tcpSrc; - ChannelMarker m_channelMarker; - MovingAverageUtil m_channelPowerDbAvg; - - // settings - TCPSrcSettings m_settings; - TCPSrcSettings::SampleFormat m_sampleFormat; - Real m_outputSampleRate; - Real m_rfBandwidth; - int m_boost; - int m_tcpPort; - bool m_rfBandwidthChanged; - bool m_doApplySettings; - - // RF path - SpectrumVis* m_spectrumVis; - MessageQueue m_inputMessageQueue; - - explicit TCPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); - virtual ~TCPSrcGUI(); - - void blockApplySettings(bool block); - void applySettings(); - void displaySettings(); - void setSampleFormat(int index); - void setSampleFormatIndex(const TCPSrcSettings::SampleFormat& sampleFormat); - - void addConnection(quint32 id, const QHostAddress& peerAddress, int peerPort); - void delConnection(quint32 id); - -private slots: - void on_deltaFrequency_changed(qint64 value); - void on_sampleFormat_currentIndexChanged(int index); - void on_sampleRate_textEdited(const QString& arg1); - void on_rfBandwidth_textEdited(const QString& arg1); - void on_tcpPort_textEdited(const QString& arg1); - void on_applyBtn_clicked(); - void onWidgetRolled(QWidget* widget, bool rollDown); - void on_volume_valueChanged(int value); - void tick(); -}; - -#endif // INCLUDE_TCPSRCGUI_H diff --git a/plugins/channelrx/tcpsrc/tcpsrcgui.ui b/plugins/channelrx/tcpsrc/tcpsrcgui.ui deleted file mode 100644 index bb9965fa7..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcgui.ui +++ /dev/null @@ -1,480 +0,0 @@ - - - TCPSrcGUI - - - - 0 - 0 - 400 - 443 - - - - - Liberation Sans - 9 - - - - TCP Sample Source - - - - - 10 - 5 - 201 - 142 - - - - Settings - - - - 2 - - - 2 - - - 2 - - - 2 - - - 3 - - - - - Sample Format - - - - - - - Sample format - - - 0 - - - - S16LE I/Q - - - - - S16LE NFM - - - - - S16LE SSB - - - - - - - - Channel bandwidth - - - 32000 - - - - - - - RF Bandwidth (Hz) - - - - - - - Samplerate (Hz) - - - - - - - TCP Port - - - - - - - Data stream sample rate - - - 48000 - - - - - - - TCP port number - - - 9999 - - - - - - - false - - - Apply changes - - - Apply - - - - - - - 2 - - - - - - 16 - 16777215 - - - - Df - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - Liberation Mono - 12 - - - - PointingHandCursor - - - Qt::StrongFocus - - - Demod shift frequency from center in Hz - - - - - - - - - - - - 26 - 26 - 26 - - - - - - - 255 - 255 - 255 - - - - - - - - - 26 - 26 - 26 - - - - - - - 255 - 255 - 255 - - - - - - - - - 118 - 118 - 117 - - - - - - - 255 - 255 - 255 - - - - - - - - - 8 - - - - Hz - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Channel power - - - Qt::LeftToRight - - - 0.0 - - - - - - - dB - - - - - - - - - - - Vol - - - - - - - Amplitude boost - - - 3 - - - 1 - - - Qt::Horizontal - - - - - - - 0 - - - - - - - - - - - 15 - 160 - 231 - 156 - - - - Channel Spectrum - - - - 3 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - - Liberation Mono - 9 - - - - - - - - - - - - - 15 - 330 - 274 - 101 - - - - Connected Clients (0) - - - - 3 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - - 400 - 100 - - - - false - - - false - - - false - - - - IP:Port - - - - - - - - - - RollupWidget - QWidget -
gui/rollupwidget.h
- 1 -
- - GLSpectrum - QWidget -
gui/glspectrum.h
- 1 -
- - GLSpectrumGUI - QWidget -
gui/glspectrumgui.h
- 1 -
- - ValueDialZ - QWidget -
gui/valuedialz.h
- 1 -
-
- - sampleFormat - tcpPort - sampleRate - rfBandwidth - applyBtn - connections - - - -
diff --git a/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp b/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp deleted file mode 100644 index e3925ed20..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "tcpsrcplugin.h" - -#include -#include "plugin/pluginapi.h" - -#include "tcpsrcgui.h" -#include "tcpsrc.h" - -const PluginDescriptor TCPSrcPlugin::m_pluginDescriptor = { - QString("TCP Channel Source"), - QString("3.14.5"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -TCPSrcPlugin::TCPSrcPlugin(QObject* parent) : - QObject(parent), - m_pluginAPI(0) -{ -} - -const PluginDescriptor& TCPSrcPlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void TCPSrcPlugin::initPlugin(PluginAPI* pluginAPI) -{ - m_pluginAPI = pluginAPI; - - // register TCP Channel Source - m_pluginAPI->registerRxChannel(TCPSrc::m_channelIdURI, TCPSrc::m_channelId, this); -} - -PluginInstanceGUI* TCPSrcPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) -{ - return TCPSrcGUI::create(m_pluginAPI, deviceUISet, rxChannel); -} - -BasebandSampleSink* TCPSrcPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) -{ - return new TCPSrc(deviceAPI); -} - -ChannelSinkAPI* TCPSrcPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) -{ - return new TCPSrc(deviceAPI); -} - diff --git a/plugins/channelrx/tcpsrc/tcpsrcplugin.h b/plugins/channelrx/tcpsrc/tcpsrcplugin.h deleted file mode 100644 index 74d3d5996..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcplugin.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef INCLUDE_TCPSRCPLUGIN_H -#define INCLUDE_TCPSRCPLUGIN_H - -#include -#include "plugin/plugininterface.h" - -class DeviceUISet; -class BasebandSampleSink; - -class TCPSrcPlugin : public QObject, PluginInterface { - Q_OBJECT - Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "sdrangel.demod.tcpsrc") - -public: - explicit TCPSrcPlugin(QObject* parent = NULL); - - const PluginDescriptor& getPluginDescriptor() const; - void initPlugin(PluginAPI* pluginAPI); - - virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); - virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); - virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); - -private: - static const PluginDescriptor m_pluginDescriptor; - - PluginAPI* m_pluginAPI; -}; - -#endif // INCLUDE_TCPSRCPLUGIN_H diff --git a/plugins/channelrx/tcpsrc/tcpsrcsettings.cpp b/plugins/channelrx/tcpsrc/tcpsrcsettings.cpp deleted file mode 100644 index 711454926..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcsettings.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 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 // -// // -// 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/dspengine.h" -#include "util/simpleserializer.h" -#include "settings/serializable.h" -#include "tcpsrcsettings.h" - -TCPSrcSettings::TCPSrcSettings() : - m_channelMarker(0), - m_spectrumGUI(0) -{ - resetToDefaults(); -} - -void TCPSrcSettings::resetToDefaults() -{ - m_outputSampleRate = 48000; - m_sampleFormat = FormatS16LE; - m_inputSampleRate = 48000; - m_inputFrequencyOffset = 0; - m_rfBandwidth = 12500; - m_tcpPort = 9999; - m_fmDeviation = 2500; - m_channelMute = false; - m_gain = 1.0; - m_squelchdB = -60; - m_squelchGate = 0.0; - m_squelchEnabled = true; - m_agc = false; - m_audioActive = false; - m_audioStereo = false; - m_volume = 20; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; - m_audioPort = 9998; - m_rgbColor = QColor(225, 25, 99).rgb(); - m_title = "TCP Source"; -} - -QByteArray TCPSrcSettings::serialize() const -{ - SimpleSerializer s(1); - s.writeS32(2, m_inputFrequencyOffset); - s.writeS32(3, (int) m_sampleFormat); - s.writeReal(4, m_outputSampleRate); - s.writeReal(5, m_rfBandwidth); - s.writeS32(6, m_tcpPort); - - if (m_channelMarker) { - s.writeBlob(10, m_channelMarker->serialize()); - } - - if (m_spectrumGUI) { - s.writeBlob(7, m_spectrumGUI->serialize()); - } - - s.writeS32(8, m_gain*10.0); - s.writeU32(9, m_rgbColor); - s.writeBool(11, m_audioActive); - s.writeS32(12, m_volume); - s.writeBool(14, m_audioStereo); - s.writeS32(15, m_fmDeviation); - s.writeS32(16, m_squelchdB); - s.writeS32(17, m_squelchGate); - s.writeBool(18, m_agc); - s.writeString(19, m_title); - - return s.final(); - -} - -bool TCPSrcSettings::deserialize(const QByteArray& data) -{ - SimpleDeserializer d(data); - - if (!d.isValid()) - { - resetToDefaults(); - return false; - } - - if (d.getVersion() == 1) - { - QByteArray bytetmp; - QString strtmp; - int32_t s32tmp; - - if (m_channelMarker) { - d.readBlob(10, &bytetmp); - m_channelMarker->deserialize(bytetmp); - } - - d.readS32(2, &s32tmp, 0); - m_inputFrequencyOffset = s32tmp; - - d.readS32(3, &s32tmp, FormatS16LE); - - if ((s32tmp >= 0) && (s32tmp < (int) FormatNone)) { - m_sampleFormat = (SampleFormat) s32tmp; - } else { - m_sampleFormat = FormatS16LE; - } - - d.readReal(4, &m_outputSampleRate, 48000.0); - d.readReal(5, &m_rfBandwidth, 32000.0); - d.readS32(6, &s32tmp, 10); - m_tcpPort = s32tmp < 1024 ? 9999 : s32tmp % (1<<16); - - if (m_spectrumGUI) { - d.readBlob(7, &bytetmp); - m_spectrumGUI->deserialize(bytetmp); - } - - d.readS32(8, &s32tmp, 10); - m_gain = s32tmp / 10.0; - d.readU32(9, &m_rgbColor); - d.readBool(11, &m_audioActive, false); - d.readS32(12, &m_volume, 0); - d.readBool(14, &m_audioStereo, false); - d.readS32(15, &m_fmDeviation, 2500); - d.readS32(16, &m_squelchdB, -60); - d.readS32(17, &m_squelchGate, 5); - d.readBool(18, &m_agc, false); - d.readString(19, &m_title, "TCP Source"); - - return true; - } - else - { - resetToDefaults(); - return false; - } -} - diff --git a/plugins/channelrx/tcpsrc/tcpsrcsettings.h b/plugins/channelrx/tcpsrc/tcpsrcsettings.h deleted file mode 100644 index 9c1aee574..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcsettings.h +++ /dev/null @@ -1,72 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 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 // -// // -// 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 PLUGINS_CHANNELRX_TCPSRC_TCPSRCSETTINGS_H_ -#define PLUGINS_CHANNELRX_TCPSRC_TCPSRCSETTINGS_H_ - -#include -#include -#include - -class Serializable; - -struct TCPSrcSettings -{ - enum SampleFormat { - FormatS16LE, - FormatNFM, - FormatSSB, - FormatNone - }; - - float m_outputSampleRate; - SampleFormat m_sampleFormat; - float m_inputSampleRate; - int64_t m_inputFrequencyOffset; - float m_rfBandwidth; - uint16_t m_tcpPort; - int m_fmDeviation; - bool m_channelMute; - float m_gain; - int m_squelchdB; //!< power dB - int m_squelchGate; //!< 100ths seconds - bool m_squelchEnabled; - bool m_agc; - bool m_audioActive; - bool m_audioStereo; - int m_volume; - quint32 m_rgbColor; - - QString m_udpAddress; - uint16_t m_udpPort; - uint16_t m_audioPort; - - QString m_title; - - Serializable *m_channelMarker; - Serializable *m_spectrumGUI; - - TCPSrcSettings(); - void resetToDefaults(); - void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } - void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } - QByteArray serialize() const; - bool deserialize(const QByteArray& data); -}; - - - -#endif /* PLUGINS_CHANNELRX_TCPSRC_TCPSRCSETTINGS_H_ */ From 0c328546b099c1277bb833d4532db13598f43c9f Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 25 May 2018 10:08:47 +0200 Subject: [PATCH 453/956] WFM demod: implemeted WEB API --- plugins/channelrx/demodbfm/bfmdemod.cpp | 2 +- plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- plugins/channelrx/demodssb/ssbplugin.cpp | 2 +- plugins/channelrx/demodwfm/CMakeLists.txt | 1 + plugins/channelrx/demodwfm/wfmdemod.cpp | 122 ++++++++ plugins/channelrx/demodwfm/wfmdemod.h | 26 ++ plugins/channelrx/demodwfm/wfmdemodgui.cpp | 32 +- plugins/channelrx/demodwfm/wfmdemodgui.h | 10 +- sdrbase/resources/webapi/doc/html2/index.html | 70 ++++- .../webapi/doc/swagger/include/DSDDemod.yaml | 4 +- .../webapi/doc/swagger/include/WFMDemod.yaml | 42 +++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + sdrbase/webapi/webapirequestmapper.cpp | 14 + .../api/swagger/include/WFMDemod.yaml | 42 +++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 70 ++++- .../code/qt5/client/SWGChannelReport.cpp | 23 ++ .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 ++ .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../code/qt5/client/SWGWFMDemodReport.cpp | 169 +++++++++++ .../code/qt5/client/SWGWFMDemodReport.h | 76 +++++ .../code/qt5/client/SWGWFMDemodSettings.cpp | 278 ++++++++++++++++++ .../code/qt5/client/SWGWFMDemodSettings.h | 107 +++++++ swagger/sdrangel/examples/rx_test.py | 8 + 26 files changed, 1131 insertions(+), 22 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/WFMDemod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/WFMDemod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index 56fe6f37f..65bb03a19 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -605,7 +605,7 @@ int BFMDemod::webapiSettingsPutPatch( if (frequencyOffsetChanged) { MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset); // FIXME + requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset); m_inputMessageQueue.push(channelConfigMsg); } diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index 8403767d0..577846410 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("3.14.5"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp index 2e04494ed..c5e930f84 100644 --- a/plugins/channelrx/demodssb/ssbplugin.cpp +++ b/plugins/channelrx/demodssb/ssbplugin.cpp @@ -8,7 +8,7 @@ const PluginDescriptor SSBPlugin::m_pluginDescriptor = { QString("SSB Demodulator"), - QString("3.14.5"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodwfm/CMakeLists.txt b/plugins/channelrx/demodwfm/CMakeLists.txt index c088697c2..914a448ff 100644 --- a/plugins/channelrx/demodwfm/CMakeLists.txt +++ b/plugins/channelrx/demodwfm/CMakeLists.txt @@ -23,6 +23,7 @@ set(wfm_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index e12606162..5e4a993ea 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -21,12 +21,18 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGWFMDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGWFMDemodReport.h" + #include #include "dsp/threadedbasebandsamplesink.h" #include "device/devicesourceapi.h" #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" +#include "util/db.h" #include "wfmdemod.h" @@ -375,3 +381,119 @@ bool WFMDemod::deserialize(const QByteArray& data) } } +int WFMDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setWfmDemodSettings(new SWGSDRangel::SWGWFMDemodSettings()); + response.getWfmDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int WFMDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + WFMDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getWfmDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getWfmDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("afBandwidth")) { + settings.m_afBandwidth = response.getWfmDemodSettings()->getAfBandwidth(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getWfmDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getWfmDemodSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getWfmDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getWfmDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getWfmDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getWfmDemodSettings()->getAudioDeviceName(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureWFMDemod *msg = MsgConfigureWFMDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("WFMDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureWFMDemod *msgToGUI = MsgConfigureWFMDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int WFMDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setWfmDemodReport(new SWGSDRangel::SWGWFMDemodReport()); + response.getWfmDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void WFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMDemodSettings& settings) +{ + response.getWfmDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getWfmDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getWfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth); + response.getWfmDemodSettings()->setVolume(settings.m_volume); + response.getWfmDemodSettings()->setSquelch(settings.m_squelch); + response.getWfmDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getWfmDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getWfmDemodSettings()->getTitle()) { + *response.getWfmDemodSettings()->getTitle() = settings.m_title; + } else { + response.getWfmDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getWfmDemodSettings()->getAudioDeviceName()) { + *response.getWfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getWfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void WFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getWfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getWfmDemodReport()->setSquelch(m_squelchState > 0 ? 1 : 0); + response.getWfmDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getWfmDemodReport()->setChannelSampleRate(m_inputSampleRate); +} + diff --git a/plugins/channelrx/demodwfm/wfmdemod.h b/plugins/channelrx/demodwfm/wfmdemod.h index 32ebccfbd..0e594eb33 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.h +++ b/plugins/channelrx/demodwfm/wfmdemod.h @@ -118,6 +118,29 @@ public: m_magsqCount = 0; } + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + static int requiredBW(int rfBW) + { + if (rfBW <= 48000) { + return 48000; + } else { + return (3*rfBW)/2; + } + } + static const QString m_channelIdURI; static const QString m_channelId; @@ -167,6 +190,9 @@ private: void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const WFMDemodSettings& settings, bool force = false); + + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; #endif // INCLUDE_WFMDEMOD_H diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.cpp b/plugins/channelrx/demodwfm/wfmdemodgui.cpp index 6f78a3ad9..883884621 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.cpp +++ b/plugins/channelrx/demodwfm/wfmdemodgui.cpp @@ -77,7 +77,33 @@ bool WFMDemodGUI::deserialize(const QByteArray& data) bool WFMDemodGUI::handleMessage(const Message& message __attribute__((unused))) { - return false; + if (WFMDemod::MsgConfigureWFMDemod::match(message)) + { + qDebug("WFMDemodGUI::handleMessage: WFMDemod::MsgConfigureWFMDemod"); + const WFMDemod::MsgConfigureWFMDemod& cfg = (WFMDemod::MsgConfigureWFMDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void WFMDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } } void WFMDemodGUI::channelMarkerChangedByCursor() @@ -165,8 +191,10 @@ WFMDemodGUI::WFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); m_wfmDemod = (WFMDemod*) rxChannel; //new WFMDemod(m_deviceUISet->m_deviceSourceAPI); + m_wfmDemod->setMessageQueueToGUI(getInputMessageQueue()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); @@ -226,7 +254,7 @@ void WFMDemodGUI::applySettings(bool force) if (m_doApplySettings) { WFMDemod::MsgConfigureChannelizer *msgChan = WFMDemod::MsgConfigureChannelizer::create( - requiredBW(WFMDemodSettings::getRFBW(ui->rfBW->currentIndex())), + WFMDemod::requiredBW(WFMDemodSettings::getRFBW(ui->rfBW->currentIndex())), m_channelMarker.getCenterFrequency()); m_wfmDemod->getInputMessageQueue()->push(msgChan); diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.h b/plugins/channelrx/demodwfm/wfmdemodgui.h index 8920a522c..c7998cc8e 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.h +++ b/plugins/channelrx/demodwfm/wfmdemodgui.h @@ -63,15 +63,6 @@ private: void leaveEvent(QEvent*); void enterEvent(QEvent*); - static int requiredBW(int rfBW) - { - if (rfBW <= 48000) { - return 48000; - } else { - return (3*rfBW)/2; - } - } - private slots: void on_deltaFrequency_changed(qint64 value); void on_rfBW_currentIndexChanged(int index); @@ -81,6 +72,7 @@ private slots: void on_audioMute_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); void audioSelect(); void tick(); }; diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 5e0f819be..baf09ca35 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1322,6 +1322,9 @@ margin-bottom: 20px; "UDPSinkReport" : { "$ref" : "#/definitions/UDPSinkReport" }, + "WFMDemodReport" : { + "$ref" : "#/definitions/WFMDemodReport" + }, "WFMModReport" : { "$ref" : "#/definitions/WFMModReport" } @@ -1367,6 +1370,9 @@ margin-bottom: 20px; "UDPSinkSettings" : { "$ref" : "#/definitions/UDPSinkSettings" }, + "WFMDemodSettings" : { + "$ref" : "#/definitions/WFMDemodSettings" + }, "WFMModSettings" : { "$ref" : "#/definitions/WFMModSettings" } @@ -1413,11 +1419,11 @@ margin-bottom: 20px; }, "slot1On" : { "type" : "integer", - "description" : "slot 1 status (1 if active else 0)" + "description" : "slot 1 status (1 if voice active else 0)" }, "slot2On" : { "type" : "integer", - "description" : "slot 2 status (1 if active else 0)" + "description" : "slot 2 status (1 if voice active else 0)" }, "syncType" : { "type" : "string", @@ -2653,6 +2659,64 @@ margin-bottom: 20px; } }, "description" : "UDPSink" +}; + defs.WFMDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "WFMDemod" +}; + defs.WFMDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "afBandwidth" : { + "type" : "number", + "format" : "float" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "squelch" : { + "type" : "number", + "format" : "float" + }, + "audioMute" : { + "type" : "integer", + "description" : "audio mute (1 if muted else 0)" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + } + }, + "description" : "WFMDemod" }; defs.WFMModReport = { "properties" : { @@ -20953,7 +21017,7 @@ except ApiException as e:
- Generated 2018-05-24T10:19:21.195+02:00 + Generated 2018-05-25T09:35:58.275+02:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/DSDDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/DSDDemod.yaml index 273a71222..91895f382 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/DSDDemod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/DSDDemod.yaml @@ -73,10 +73,10 @@ DSDDemodReport: description: symbol PLL status (1 if locked else 0) type: integer slot1On: - description: slot 1 status (1 if active else 0) + description: slot 1 status (1 if voice active else 0) type: integer slot2On: - description: slot 2 status (1 if active else 0) + description: slot 2 status (1 if voice active else 0) type: integer syncType: description: type of frame synchronized diff --git a/sdrbase/resources/webapi/doc/swagger/include/WFMDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/WFMDemod.yaml new file mode 100644 index 000000000..bf112fe15 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/WFMDemod.yaml @@ -0,0 +1,42 @@ +WFMDemodSettings: + description: WFMDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + afBandwidth: + type: number + format: float + volume: + type: number + format: float + squelch: + type: number + format: float + audioMute: + description: audio mute (1 if muted else 0) + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + +WFMDemodReport: + description: WFMDemod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + squelch: + description: squelch status (1 if open else 0) + type: integer + audioSampleRate: + type: integer + channelSampleRate: + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 0a9e735f9..0cc758421 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1763,6 +1763,8 @@ definitions: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" UDPSinkSettings: $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkSettings" + WFMDemodSettings: + $ref: "/doc/swagger/include/WFMDemod.yaml#/WFMDemodSettings" WFMModSettings: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModSettings" @@ -1794,6 +1796,8 @@ definitions: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkReport" + WFMDemodReport: + $ref: "/doc/swagger/include/WFMDemod.yaml#/WFMDemodReport" WFMModReport: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModReport" diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 6c57241db..ad630411a 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1973,6 +1973,20 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "WFMDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject wfmDemodSettingsJsonObject = jsonObject["WFMDemodSettings"].toObject(); + channelSettingsKeys = wfmDemodSettingsJsonObject.keys(); + channelSettings.setWfmDemodSettings(new SWGSDRangel::SWGWFMDemodSettings()); + channelSettings.getWfmDemodSettings()->fromJsonObject(wfmDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "WFMMod") { if (channelSettings.getTx() != 0) diff --git a/swagger/sdrangel/api/swagger/include/WFMDemod.yaml b/swagger/sdrangel/api/swagger/include/WFMDemod.yaml new file mode 100644 index 000000000..bf112fe15 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/WFMDemod.yaml @@ -0,0 +1,42 @@ +WFMDemodSettings: + description: WFMDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + afBandwidth: + type: number + format: float + volume: + type: number + format: float + squelch: + type: number + format: float + audioMute: + description: audio mute (1 if muted else 0) + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + +WFMDemodReport: + description: WFMDemod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + squelch: + description: squelch status (1 if open else 0) + type: integer + audioSampleRate: + type: integer + channelSampleRate: + type: integer diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index fee0e1bd5..072153c37 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1763,6 +1763,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" UDPSinkSettings: $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkSettings" + WFMDemodSettings: + $ref: "http://localhost:8081/api/swagger/include/WFMDemod.yaml#/WFMDemodSettings" WFMModSettings: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModSettings" @@ -1794,6 +1796,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkReport" + WFMDemodReport: + $ref: "http://localhost:8081/api/swagger/include/WFMDemod.yaml#/WFMDemodReport" WFMModReport: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModReport" diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 5e0f819be..baf09ca35 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1322,6 +1322,9 @@ margin-bottom: 20px; "UDPSinkReport" : { "$ref" : "#/definitions/UDPSinkReport" }, + "WFMDemodReport" : { + "$ref" : "#/definitions/WFMDemodReport" + }, "WFMModReport" : { "$ref" : "#/definitions/WFMModReport" } @@ -1367,6 +1370,9 @@ margin-bottom: 20px; "UDPSinkSettings" : { "$ref" : "#/definitions/UDPSinkSettings" }, + "WFMDemodSettings" : { + "$ref" : "#/definitions/WFMDemodSettings" + }, "WFMModSettings" : { "$ref" : "#/definitions/WFMModSettings" } @@ -1413,11 +1419,11 @@ margin-bottom: 20px; }, "slot1On" : { "type" : "integer", - "description" : "slot 1 status (1 if active else 0)" + "description" : "slot 1 status (1 if voice active else 0)" }, "slot2On" : { "type" : "integer", - "description" : "slot 2 status (1 if active else 0)" + "description" : "slot 2 status (1 if voice active else 0)" }, "syncType" : { "type" : "string", @@ -2653,6 +2659,64 @@ margin-bottom: 20px; } }, "description" : "UDPSink" +}; + defs.WFMDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "WFMDemod" +}; + defs.WFMDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "afBandwidth" : { + "type" : "number", + "format" : "float" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "squelch" : { + "type" : "number", + "format" : "float" + }, + "audioMute" : { + "type" : "integer", + "description" : "audio mute (1 if muted else 0)" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + } + }, + "description" : "WFMDemod" }; defs.WFMModReport = { "properties" : { @@ -20953,7 +21017,7 @@ except ApiException as e:
- Generated 2018-05-24T10:19:21.195+02:00 + Generated 2018-05-25T09:35:58.275+02:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index c358588a8..40e23b6d1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -50,6 +50,8 @@ SWGChannelReport::SWGChannelReport() { m_ssb_mod_report_isSet = false; udp_sink_report = nullptr; m_udp_sink_report_isSet = false; + wfm_demod_report = nullptr; + m_wfm_demod_report_isSet = false; wfm_mod_report = nullptr; m_wfm_mod_report_isSet = false; } @@ -82,6 +84,8 @@ SWGChannelReport::init() { m_ssb_mod_report_isSet = false; udp_sink_report = new SWGUDPSinkReport(); m_udp_sink_report_isSet = false; + wfm_demod_report = new SWGWFMDemodReport(); + m_wfm_demod_report_isSet = false; wfm_mod_report = new SWGWFMModReport(); m_wfm_mod_report_isSet = false; } @@ -119,6 +123,9 @@ SWGChannelReport::cleanup() { if(udp_sink_report != nullptr) { delete udp_sink_report; } + if(wfm_demod_report != nullptr) { + delete wfm_demod_report; + } if(wfm_mod_report != nullptr) { delete wfm_mod_report; } @@ -157,6 +164,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&udp_sink_report, pJson["UDPSinkReport"], "SWGUDPSinkReport", "SWGUDPSinkReport"); + ::SWGSDRangel::setValue(&wfm_demod_report, pJson["WFMDemodReport"], "SWGWFMDemodReport", "SWGWFMDemodReport"); + ::SWGSDRangel::setValue(&wfm_mod_report, pJson["WFMModReport"], "SWGWFMModReport", "SWGWFMModReport"); } @@ -208,6 +217,9 @@ SWGChannelReport::asJsonObject() { if((udp_sink_report != nullptr) && (udp_sink_report->isSet())){ toJsonValue(QString("UDPSinkReport"), udp_sink_report, obj, QString("SWGUDPSinkReport")); } + if((wfm_demod_report != nullptr) && (wfm_demod_report->isSet())){ + toJsonValue(QString("WFMDemodReport"), wfm_demod_report, obj, QString("SWGWFMDemodReport")); + } if((wfm_mod_report != nullptr) && (wfm_mod_report->isSet())){ toJsonValue(QString("WFMModReport"), wfm_mod_report, obj, QString("SWGWFMModReport")); } @@ -325,6 +337,16 @@ SWGChannelReport::setUdpSinkReport(SWGUDPSinkReport* udp_sink_report) { this->m_udp_sink_report_isSet = true; } +SWGWFMDemodReport* +SWGChannelReport::getWfmDemodReport() { + return wfm_demod_report; +} +void +SWGChannelReport::setWfmDemodReport(SWGWFMDemodReport* wfm_demod_report) { + this->wfm_demod_report = wfm_demod_report; + this->m_wfm_demod_report_isSet = true; +} + SWGWFMModReport* SWGChannelReport::getWfmModReport() { return wfm_mod_report; @@ -351,6 +373,7 @@ SWGChannelReport::isSet(){ if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} if(udp_sink_report != nullptr && udp_sink_report->isSet()){ isObjectUpdated = true; break;} + if(wfm_demod_report != nullptr && wfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_report != nullptr && wfm_mod_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index ff112531c..a2f747ad6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -31,6 +31,7 @@ #include "SWGNFMModReport.h" #include "SWGSSBModReport.h" #include "SWGUDPSinkReport.h" +#include "SWGWFMDemodReport.h" #include "SWGWFMModReport.h" #include @@ -85,6 +86,9 @@ public: SWGUDPSinkReport* getUdpSinkReport(); void setUdpSinkReport(SWGUDPSinkReport* udp_sink_report); + SWGWFMDemodReport* getWfmDemodReport(); + void setWfmDemodReport(SWGWFMDemodReport* wfm_demod_report); + SWGWFMModReport* getWfmModReport(); void setWfmModReport(SWGWFMModReport* wfm_mod_report); @@ -125,6 +129,9 @@ private: SWGUDPSinkReport* udp_sink_report; bool m_udp_sink_report_isSet; + SWGWFMDemodReport* wfm_demod_report; + bool m_wfm_demod_report_isSet; + SWGWFMModReport* wfm_mod_report; bool m_wfm_mod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 32c6b59df..9acec3f34 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -50,6 +50,8 @@ SWGChannelSettings::SWGChannelSettings() { m_ssb_mod_settings_isSet = false; udp_sink_settings = nullptr; m_udp_sink_settings_isSet = false; + wfm_demod_settings = nullptr; + m_wfm_demod_settings_isSet = false; wfm_mod_settings = nullptr; m_wfm_mod_settings_isSet = false; } @@ -82,6 +84,8 @@ SWGChannelSettings::init() { m_ssb_mod_settings_isSet = false; udp_sink_settings = new SWGUDPSinkSettings(); m_udp_sink_settings_isSet = false; + wfm_demod_settings = new SWGWFMDemodSettings(); + m_wfm_demod_settings_isSet = false; wfm_mod_settings = new SWGWFMModSettings(); m_wfm_mod_settings_isSet = false; } @@ -119,6 +123,9 @@ SWGChannelSettings::cleanup() { if(udp_sink_settings != nullptr) { delete udp_sink_settings; } + if(wfm_demod_settings != nullptr) { + delete wfm_demod_settings; + } if(wfm_mod_settings != nullptr) { delete wfm_mod_settings; } @@ -157,6 +164,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&udp_sink_settings, pJson["UDPSinkSettings"], "SWGUDPSinkSettings", "SWGUDPSinkSettings"); + ::SWGSDRangel::setValue(&wfm_demod_settings, pJson["WFMDemodSettings"], "SWGWFMDemodSettings", "SWGWFMDemodSettings"); + ::SWGSDRangel::setValue(&wfm_mod_settings, pJson["WFMModSettings"], "SWGWFMModSettings", "SWGWFMModSettings"); } @@ -208,6 +217,9 @@ SWGChannelSettings::asJsonObject() { if((udp_sink_settings != nullptr) && (udp_sink_settings->isSet())){ toJsonValue(QString("UDPSinkSettings"), udp_sink_settings, obj, QString("SWGUDPSinkSettings")); } + if((wfm_demod_settings != nullptr) && (wfm_demod_settings->isSet())){ + toJsonValue(QString("WFMDemodSettings"), wfm_demod_settings, obj, QString("SWGWFMDemodSettings")); + } if((wfm_mod_settings != nullptr) && (wfm_mod_settings->isSet())){ toJsonValue(QString("WFMModSettings"), wfm_mod_settings, obj, QString("SWGWFMModSettings")); } @@ -325,6 +337,16 @@ SWGChannelSettings::setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings) { this->m_udp_sink_settings_isSet = true; } +SWGWFMDemodSettings* +SWGChannelSettings::getWfmDemodSettings() { + return wfm_demod_settings; +} +void +SWGChannelSettings::setWfmDemodSettings(SWGWFMDemodSettings* wfm_demod_settings) { + this->wfm_demod_settings = wfm_demod_settings; + this->m_wfm_demod_settings_isSet = true; +} + SWGWFMModSettings* SWGChannelSettings::getWfmModSettings() { return wfm_mod_settings; @@ -351,6 +373,7 @@ SWGChannelSettings::isSet(){ if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} + if(wfm_demod_settings != nullptr && wfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_settings != nullptr && wfm_mod_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index d3fb1d576..8dd292d0f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -31,6 +31,7 @@ #include "SWGNFMModSettings.h" #include "SWGSSBModSettings.h" #include "SWGUDPSinkSettings.h" +#include "SWGWFMDemodSettings.h" #include "SWGWFMModSettings.h" #include @@ -85,6 +86,9 @@ public: SWGUDPSinkSettings* getUdpSinkSettings(); void setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings); + SWGWFMDemodSettings* getWfmDemodSettings(); + void setWfmDemodSettings(SWGWFMDemodSettings* wfm_demod_settings); + SWGWFMModSettings* getWfmModSettings(); void setWfmModSettings(SWGWFMModSettings* wfm_mod_settings); @@ -125,6 +129,9 @@ private: SWGUDPSinkSettings* udp_sink_settings; bool m_udp_sink_settings_isSet; + SWGWFMDemodSettings* wfm_demod_settings; + bool m_wfm_demod_settings_isSet; + SWGWFMModSettings* wfm_mod_settings; bool m_wfm_mod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 2505a20a4..b4a2ffd13 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -74,6 +74,8 @@ #include "SWGSuccessResponse.h" #include "SWGUDPSinkReport.h" #include "SWGUDPSinkSettings.h" +#include "SWGWFMDemodReport.h" +#include "SWGWFMDemodSettings.h" #include "SWGWFMModReport.h" #include "SWGWFMModSettings.h" @@ -260,6 +262,12 @@ namespace SWGSDRangel { if(QString("SWGUDPSinkSettings").compare(type) == 0) { return new SWGUDPSinkSettings(); } + if(QString("SWGWFMDemodReport").compare(type) == 0) { + return new SWGWFMDemodReport(); + } + if(QString("SWGWFMDemodSettings").compare(type) == 0) { + return new SWGWFMDemodSettings(); + } if(QString("SWGWFMModReport").compare(type) == 0) { return new SWGWFMModReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp new file mode 100644 index 000000000..a7ae40494 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp @@ -0,0 +1,169 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGWFMDemodReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGWFMDemodReport::SWGWFMDemodReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGWFMDemodReport::SWGWFMDemodReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + squelch = 0; + m_squelch_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGWFMDemodReport::~SWGWFMDemodReport() { + this->cleanup(); +} + +void +SWGWFMDemodReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + squelch = 0; + m_squelch_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGWFMDemodReport::cleanup() { + + + + +} + +SWGWFMDemodReport* +SWGWFMDemodReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGWFMDemodReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGWFMDemodReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGWFMDemodReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_audio_sample_rate_isSet){ + obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGWFMDemodReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGWFMDemodReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGWFMDemodReport::getSquelch() { + return squelch; +} +void +SWGWFMDemodReport::setSquelch(qint32 squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +qint32 +SWGWFMDemodReport::getAudioSampleRate() { + return audio_sample_rate; +} +void +SWGWFMDemodReport::setAudioSampleRate(qint32 audio_sample_rate) { + this->audio_sample_rate = audio_sample_rate; + this->m_audio_sample_rate_isSet = true; +} + +qint32 +SWGWFMDemodReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGWFMDemodReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGWFMDemodReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h new file mode 100644 index 000000000..cba57e09e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h @@ -0,0 +1,76 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGWFMDemodReport.h + * + * WFMDemod + */ + +#ifndef SWGWFMDemodReport_H_ +#define SWGWFMDemodReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGWFMDemodReport: public SWGObject { +public: + SWGWFMDemodReport(); + SWGWFMDemodReport(QString* json); + virtual ~SWGWFMDemodReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGWFMDemodReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getSquelch(); + void setSquelch(qint32 squelch); + + qint32 getAudioSampleRate(); + void setAudioSampleRate(qint32 audio_sample_rate); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 squelch; + bool m_squelch_isSet; + + qint32 audio_sample_rate; + bool m_audio_sample_rate_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGWFMDemodReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp new file mode 100644 index 000000000..8e7999fff --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp @@ -0,0 +1,278 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGWFMDemodSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGWFMDemodSettings::SWGWFMDemodSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGWFMDemodSettings::SWGWFMDemodSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + af_bandwidth = 0.0f; + m_af_bandwidth_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + squelch = 0.0f; + m_squelch_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; +} + +SWGWFMDemodSettings::~SWGWFMDemodSettings() { + this->cleanup(); +} + +void +SWGWFMDemodSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + af_bandwidth = 0.0f; + m_af_bandwidth_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + squelch = 0.0f; + m_squelch_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; +} + +void +SWGWFMDemodSettings::cleanup() { + + + + + + + + if(title != nullptr) { + delete title; + } + if(audio_device_name != nullptr) { + delete audio_device_name; + } +} + +SWGWFMDemodSettings* +SWGWFMDemodSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGWFMDemodSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&af_bandwidth, pJson["afBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&volume, pJson["volume"], "float", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "float", ""); + + ::SWGSDRangel::setValue(&audio_mute, pJson["audioMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + +} + +QString +SWGWFMDemodSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGWFMDemodSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_af_bandwidth_isSet){ + obj->insert("afBandwidth", QJsonValue(af_bandwidth)); + } + if(m_volume_isSet){ + obj->insert("volume", QJsonValue(volume)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_audio_mute_isSet){ + obj->insert("audioMute", QJsonValue(audio_mute)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGWFMDemodSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGWFMDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGWFMDemodSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGWFMDemodSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGWFMDemodSettings::getAfBandwidth() { + return af_bandwidth; +} +void +SWGWFMDemodSettings::setAfBandwidth(float af_bandwidth) { + this->af_bandwidth = af_bandwidth; + this->m_af_bandwidth_isSet = true; +} + +float +SWGWFMDemodSettings::getVolume() { + return volume; +} +void +SWGWFMDemodSettings::setVolume(float volume) { + this->volume = volume; + this->m_volume_isSet = true; +} + +float +SWGWFMDemodSettings::getSquelch() { + return squelch; +} +void +SWGWFMDemodSettings::setSquelch(float squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +qint32 +SWGWFMDemodSettings::getAudioMute() { + return audio_mute; +} +void +SWGWFMDemodSettings::setAudioMute(qint32 audio_mute) { + this->audio_mute = audio_mute; + this->m_audio_mute_isSet = true; +} + +qint32 +SWGWFMDemodSettings::getRgbColor() { + return rgb_color; +} +void +SWGWFMDemodSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGWFMDemodSettings::getTitle() { + return title; +} +void +SWGWFMDemodSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +QString* +SWGWFMDemodSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGWFMDemodSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + + +bool +SWGWFMDemodSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_af_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_volume_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_audio_mute_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h new file mode 100644 index 000000000..91432b19a --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h @@ -0,0 +1,107 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGWFMDemodSettings.h + * + * WFMDemod + */ + +#ifndef SWGWFMDemodSettings_H_ +#define SWGWFMDemodSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGWFMDemodSettings: public SWGObject { +public: + SWGWFMDemodSettings(); + SWGWFMDemodSettings(QString* json); + virtual ~SWGWFMDemodSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGWFMDemodSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getAfBandwidth(); + void setAfBandwidth(float af_bandwidth); + + float getVolume(); + void setVolume(float volume); + + float getSquelch(); + void setSquelch(float squelch); + + qint32 getAudioMute(); + void setAudioMute(qint32 audio_mute); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float af_bandwidth; + bool m_af_bandwidth_isSet; + + float volume; + bool m_volume_isSet; + + float squelch; + bool m_squelch_isSet; + + qint32 audio_mute; + bool m_audio_mute_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + QString* audio_device_name; + bool m_audio_device_name_isSet; + +}; + +} + +#endif /* SWGWFMDemodSettings_H_ */ diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 2fe2a9bf5..ba5272f96 100644 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -174,6 +174,14 @@ def setupChannel(deviceset_url, options): settings["BFMDemodSettings"]["lsbStereo"] = 1 if options.lsb_stereo else 0 settings["BFMDemodSettings"]["rdsActive"] = 1 if options.rds else 0 settings["BFMDemodSettings"]["title"] = "Channel %d" % i + elif options.channel_id == "WFMDemod": + settings["WFMDemodSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["WFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 + settings["WFMDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["WFMDemodSettings"]["volume"] = options.volume + settings["WFMDemodSettings"]["squelch"] = options.squelch_db # dB + settings["WFMDemodSettings"]["audioMute"] = 0 + settings["WFMDemodSettings"]["title"] = "Channel %d" % i elif options.channel_id == "AMDemod": settings["AMDemodSettings"]["inputFrequencyOffset"] = options.channel_freq settings["AMDemodSettings"]["rfBandwidth"] = options.rf_bw From 54019d7a06fc5f705bca3305d3d8b2b8428ae927 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 25 May 2018 10:45:40 +0200 Subject: [PATCH 454/956] UDP source: implemeted WEB API (1) --- plugins/channelrx/udpsrc/CMakeLists.txt | 1 + plugins/channelrx/udpsrc/udpsrc.cpp | 46 ++ plugins/channelrx/udpsrc/udpsrc.h | 5 +- sdrbase/resources/webapi/doc/html2/index.html | 103 +++- .../webapi/doc/swagger/include/UDPSrc.yaml | 71 +++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + .../sdrangel/api/swagger/include/UDPSrc.yaml | 71 +++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 103 +++- .../code/qt5/client/SWGChannelReport.cpp | 23 + .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 + .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../code/qt5/client/SWGUDPSrcReport.cpp | 148 ++++++ .../code/qt5/client/SWGUDPSrcReport.h | 70 +++ .../code/qt5/client/SWGUDPSrcSettings.cpp | 488 ++++++++++++++++++ .../code/qt5/client/SWGUDPSrcSettings.h | 167 ++++++ 18 files changed, 1346 insertions(+), 3 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/UDPSrc.yaml create mode 100644 swagger/sdrangel/api/swagger/include/UDPSrc.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h diff --git a/plugins/channelrx/udpsrc/CMakeLists.txt b/plugins/channelrx/udpsrc/CMakeLists.txt index c3bd55b6e..f4bfb1962 100644 --- a/plugins/channelrx/udpsrc/CMakeLists.txt +++ b/plugins/channelrx/udpsrc/CMakeLists.txt @@ -21,6 +21,7 @@ set(udpsrc_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index 191700b1f..b82b5406f 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -18,6 +18,11 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGUDPSrcSettings.h" +#include "SWGChannelReport.h" +#include "SWGUDPSrcReport.h" + #include "dsp/dspengine.h" #include "util/db.h" #include "dsp/downchannelizer.h" @@ -637,3 +642,44 @@ bool UDPSrc::deserialize(const QByteArray& data) return false; } } + +void UDPSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSrcSettings& settings) +{ + response.getUdpSrcSettings()->setOutputSampleRate(settings.m_outputSampleRate); + response.getUdpSrcSettings()->setSampleFormat((int) settings.m_sampleFormat); + response.getUdpSrcSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getUdpSrcSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getUdpSrcSettings()->setFmDeviation(settings.m_fmDeviation); + response.getUdpSrcSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getUdpSrcSettings()->setGain(settings.m_gain); + response.getUdpSrcSettings()->setSquelchDb(settings.m_squelchdB); + response.getUdpSrcSettings()->setSquelchGate(settings.m_squelchGate); + response.getUdpSrcSettings()->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); + response.getUdpSrcSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getUdpSrcSettings()->setAudioActive(settings.m_audioActive ? 1 : 0); + response.getUdpSrcSettings()->setAudioStereo(settings.m_audioStereo ? 1 : 0); + response.getUdpSrcSettings()->setVolume(settings.m_volume); + + if (response.getUdpSrcSettings()->getUdpAddress()) { + *response.getUdpSrcSettings()->getUdpAddress() = settings.m_udpAddress; + } else { + response.getUdpSrcSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + } + + response.getUdpSrcSettings()->setUdpPort(settings.m_udpPort); + response.getUdpSrcSettings()->setAudioPort(settings.m_audioPort); + response.getUdpSrcSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getUdpSrcSettings()->getTitle()) { + *response.getUdpSrcSettings()->getTitle() = settings.m_title; + } else { + response.getUdpSrcSettings()->setTitle(new QString(settings.m_title)); + } +} + +void UDPSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getUdpSrcReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getUdpSrcReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getUdpSrcReport()->setInputSampleRate(m_inputSampleRate); +} diff --git a/plugins/channelrx/udpsrc/udpsrc.h b/plugins/channelrx/udpsrc/udpsrc.h index 8ef8f75dc..9be4c71b1 100644 --- a/plugins/channelrx/udpsrc/udpsrc.h +++ b/plugins/channelrx/udpsrc/udpsrc.h @@ -218,6 +218,9 @@ protected: void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = true); void applySettings(const UDPSrcSettings& settings, bool force = false); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSrcSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + inline void calculateSquelch(double value) { if ((!m_settings.m_squelchEnabled) || (value > m_squelch)) @@ -295,7 +298,7 @@ protected: } else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ24) { m_udpBuffer24->write(Sample24(real, imag)); } else { - m_udpBuffer16->write(Sample16(real>>8, imag>>8)); + m_udpBuffer16->write(Sample16(real>>8, imag>>8)); } } } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index baf09ca35..686abdb6c 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1322,6 +1322,9 @@ margin-bottom: 20px; "UDPSinkReport" : { "$ref" : "#/definitions/UDPSinkReport" }, + "UDPSrcReport" : { + "$ref" : "#/definitions/UDPSrcReport" + }, "WFMDemodReport" : { "$ref" : "#/definitions/WFMDemodReport" }, @@ -1370,6 +1373,9 @@ margin-bottom: 20px; "UDPSinkSettings" : { "$ref" : "#/definitions/UDPSinkSettings" }, + "UDPSrcSettings" : { + "$ref" : "#/definitions/UDPSrcSettings" + }, "WFMDemodSettings" : { "$ref" : "#/definitions/WFMDemodSettings" }, @@ -2659,6 +2665,101 @@ margin-bottom: 20px; } }, "description" : "UDPSink" +}; + defs.UDPSrcReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "inputSampleRate" : { + "type" : "integer" + } + }, + "description" : "UDPSink" +}; + defs.UDPSrcSettings = { + "properties" : { + "outputSampleRate" : { + "type" : "number", + "format" : "float" + }, + "sampleFormat" : { + "type" : "integer" + }, + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "integer" + }, + "channelMute" : { + "type" : "integer", + "description" : "channel mute (1 if muted else 0)" + }, + "gain" : { + "type" : "number", + "format" : "float" + }, + "squelchDB" : { + "type" : "integer", + "description" : "power dB" + }, + "squelchGate" : { + "type" : "integer", + "description" : "100ths seconds" + }, + "squelchEnabled" : { + "type" : "integer", + "description" : "squelch enable (1 if enabled else 0)" + }, + "agc" : { + "type" : "integer", + "description" : "AGC enable (1 if enabled else 0)" + }, + "audioActive" : { + "type" : "integer", + "description" : "Audio return enable (1 if enabled else 0)" + }, + "audioStereo" : { + "type" : "integer", + "description" : "Audio return stereo (1 if stereo else 0)" + }, + "volume" : { + "type" : "integer" + }, + "udpAddress" : { + "type" : "string", + "description" : "destination UDP address (remote)" + }, + "udpPort" : { + "type" : "integer", + "format" : "uint16", + "description" : "destination UDP port (remote)" + }, + "audioPort" : { + "type" : "integer", + "format" : "uint16", + "description" : "audio return UDP port (local)" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + } + }, + "description" : "UDPSrc" }; defs.WFMDemodReport = { "properties" : { @@ -21017,7 +21118,7 @@ except ApiException as e:
- Generated 2018-05-25T09:35:58.275+02:00 + Generated 2018-05-25T10:44:21.402+02:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/UDPSrc.yaml b/sdrbase/resources/webapi/doc/swagger/include/UDPSrc.yaml new file mode 100644 index 000000000..af2515655 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/UDPSrc.yaml @@ -0,0 +1,71 @@ +UDPSrcSettings: + description: UDPSrc + properties: + outputSampleRate: + type: number + format: float + sampleFormat: + type: integer + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + fmDeviation: + type: integer + channelMute: + description: channel mute (1 if muted else 0) + type: integer + gain: + type: number + format: float + squelchDB: + description: power dB + type: integer + squelchGate: + description: 100ths seconds + type: integer + squelchEnabled: + description: squelch enable (1 if enabled else 0) + type: integer + agc: + description: AGC enable (1 if enabled else 0) + type: integer + audioActive: + description: Audio return enable (1 if enabled else 0) + type: integer + audioStereo: + description: Audio return stereo (1 if stereo else 0) + type: integer + volume: + type: integer + udpAddress: + description: destination UDP address (remote) + type: string + udpPort: + description: destination UDP port (remote) + type: integer + format: uint16 + audioPort: + description: audio return UDP port (local) + type: integer + format: uint16 + rgbColor: + type: integer + title: + type: string + +UDPSrcReport: + description: UDPSink + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + squelch: + description: squelch status (1 if open else 0) + type: integer + inputSampleRate: + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 0cc758421..c1bad45d9 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1763,6 +1763,8 @@ definitions: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" UDPSinkSettings: $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkSettings" + UDPSrcSettings: + $ref: "/doc/swagger/include/UDPSrc.yaml#/UDPSrcSettings" WFMDemodSettings: $ref: "/doc/swagger/include/WFMDemod.yaml#/WFMDemodSettings" WFMModSettings: @@ -1796,6 +1798,8 @@ definitions: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkReport" + UDPSrcReport: + $ref: "/doc/swagger/include/UDPSrc.yaml#/UDPSrcReport" WFMDemodReport: $ref: "/doc/swagger/include/WFMDemod.yaml#/WFMDemodReport" WFMModReport: diff --git a/swagger/sdrangel/api/swagger/include/UDPSrc.yaml b/swagger/sdrangel/api/swagger/include/UDPSrc.yaml new file mode 100644 index 000000000..af2515655 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/UDPSrc.yaml @@ -0,0 +1,71 @@ +UDPSrcSettings: + description: UDPSrc + properties: + outputSampleRate: + type: number + format: float + sampleFormat: + type: integer + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + fmDeviation: + type: integer + channelMute: + description: channel mute (1 if muted else 0) + type: integer + gain: + type: number + format: float + squelchDB: + description: power dB + type: integer + squelchGate: + description: 100ths seconds + type: integer + squelchEnabled: + description: squelch enable (1 if enabled else 0) + type: integer + agc: + description: AGC enable (1 if enabled else 0) + type: integer + audioActive: + description: Audio return enable (1 if enabled else 0) + type: integer + audioStereo: + description: Audio return stereo (1 if stereo else 0) + type: integer + volume: + type: integer + udpAddress: + description: destination UDP address (remote) + type: string + udpPort: + description: destination UDP port (remote) + type: integer + format: uint16 + audioPort: + description: audio return UDP port (local) + type: integer + format: uint16 + rgbColor: + type: integer + title: + type: string + +UDPSrcReport: + description: UDPSink + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + squelch: + description: squelch status (1 if open else 0) + type: integer + inputSampleRate: + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 072153c37..55214e17c 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1763,6 +1763,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" UDPSinkSettings: $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkSettings" + UDPSrcSettings: + $ref: "http://localhost:8081/api/swagger/include/UDPSrc.yaml#/UDPSrcSettings" WFMDemodSettings: $ref: "http://localhost:8081/api/swagger/include/WFMDemod.yaml#/WFMDemodSettings" WFMModSettings: @@ -1796,6 +1798,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkReport" + UDPSrcReport: + $ref: "http://localhost:8081/api/swagger/include/UDPSrc.yaml#/UDPSrcReport" WFMDemodReport: $ref: "http://localhost:8081/api/swagger/include/WFMDemod.yaml#/WFMDemodReport" WFMModReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index baf09ca35..686abdb6c 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1322,6 +1322,9 @@ margin-bottom: 20px; "UDPSinkReport" : { "$ref" : "#/definitions/UDPSinkReport" }, + "UDPSrcReport" : { + "$ref" : "#/definitions/UDPSrcReport" + }, "WFMDemodReport" : { "$ref" : "#/definitions/WFMDemodReport" }, @@ -1370,6 +1373,9 @@ margin-bottom: 20px; "UDPSinkSettings" : { "$ref" : "#/definitions/UDPSinkSettings" }, + "UDPSrcSettings" : { + "$ref" : "#/definitions/UDPSrcSettings" + }, "WFMDemodSettings" : { "$ref" : "#/definitions/WFMDemodSettings" }, @@ -2659,6 +2665,101 @@ margin-bottom: 20px; } }, "description" : "UDPSink" +}; + defs.UDPSrcReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "inputSampleRate" : { + "type" : "integer" + } + }, + "description" : "UDPSink" +}; + defs.UDPSrcSettings = { + "properties" : { + "outputSampleRate" : { + "type" : "number", + "format" : "float" + }, + "sampleFormat" : { + "type" : "integer" + }, + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "integer" + }, + "channelMute" : { + "type" : "integer", + "description" : "channel mute (1 if muted else 0)" + }, + "gain" : { + "type" : "number", + "format" : "float" + }, + "squelchDB" : { + "type" : "integer", + "description" : "power dB" + }, + "squelchGate" : { + "type" : "integer", + "description" : "100ths seconds" + }, + "squelchEnabled" : { + "type" : "integer", + "description" : "squelch enable (1 if enabled else 0)" + }, + "agc" : { + "type" : "integer", + "description" : "AGC enable (1 if enabled else 0)" + }, + "audioActive" : { + "type" : "integer", + "description" : "Audio return enable (1 if enabled else 0)" + }, + "audioStereo" : { + "type" : "integer", + "description" : "Audio return stereo (1 if stereo else 0)" + }, + "volume" : { + "type" : "integer" + }, + "udpAddress" : { + "type" : "string", + "description" : "destination UDP address (remote)" + }, + "udpPort" : { + "type" : "integer", + "format" : "uint16", + "description" : "destination UDP port (remote)" + }, + "audioPort" : { + "type" : "integer", + "format" : "uint16", + "description" : "audio return UDP port (local)" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + } + }, + "description" : "UDPSrc" }; defs.WFMDemodReport = { "properties" : { @@ -21017,7 +21118,7 @@ except ApiException as e:
- Generated 2018-05-25T09:35:58.275+02:00 + Generated 2018-05-25T10:44:21.402+02:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 40e23b6d1..943ce5f63 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -50,6 +50,8 @@ SWGChannelReport::SWGChannelReport() { m_ssb_mod_report_isSet = false; udp_sink_report = nullptr; m_udp_sink_report_isSet = false; + udp_src_report = nullptr; + m_udp_src_report_isSet = false; wfm_demod_report = nullptr; m_wfm_demod_report_isSet = false; wfm_mod_report = nullptr; @@ -84,6 +86,8 @@ SWGChannelReport::init() { m_ssb_mod_report_isSet = false; udp_sink_report = new SWGUDPSinkReport(); m_udp_sink_report_isSet = false; + udp_src_report = new SWGUDPSrcReport(); + m_udp_src_report_isSet = false; wfm_demod_report = new SWGWFMDemodReport(); m_wfm_demod_report_isSet = false; wfm_mod_report = new SWGWFMModReport(); @@ -123,6 +127,9 @@ SWGChannelReport::cleanup() { if(udp_sink_report != nullptr) { delete udp_sink_report; } + if(udp_src_report != nullptr) { + delete udp_src_report; + } if(wfm_demod_report != nullptr) { delete wfm_demod_report; } @@ -164,6 +171,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&udp_sink_report, pJson["UDPSinkReport"], "SWGUDPSinkReport", "SWGUDPSinkReport"); + ::SWGSDRangel::setValue(&udp_src_report, pJson["UDPSrcReport"], "SWGUDPSrcReport", "SWGUDPSrcReport"); + ::SWGSDRangel::setValue(&wfm_demod_report, pJson["WFMDemodReport"], "SWGWFMDemodReport", "SWGWFMDemodReport"); ::SWGSDRangel::setValue(&wfm_mod_report, pJson["WFMModReport"], "SWGWFMModReport", "SWGWFMModReport"); @@ -217,6 +226,9 @@ SWGChannelReport::asJsonObject() { if((udp_sink_report != nullptr) && (udp_sink_report->isSet())){ toJsonValue(QString("UDPSinkReport"), udp_sink_report, obj, QString("SWGUDPSinkReport")); } + if((udp_src_report != nullptr) && (udp_src_report->isSet())){ + toJsonValue(QString("UDPSrcReport"), udp_src_report, obj, QString("SWGUDPSrcReport")); + } if((wfm_demod_report != nullptr) && (wfm_demod_report->isSet())){ toJsonValue(QString("WFMDemodReport"), wfm_demod_report, obj, QString("SWGWFMDemodReport")); } @@ -337,6 +349,16 @@ SWGChannelReport::setUdpSinkReport(SWGUDPSinkReport* udp_sink_report) { this->m_udp_sink_report_isSet = true; } +SWGUDPSrcReport* +SWGChannelReport::getUdpSrcReport() { + return udp_src_report; +} +void +SWGChannelReport::setUdpSrcReport(SWGUDPSrcReport* udp_src_report) { + this->udp_src_report = udp_src_report; + this->m_udp_src_report_isSet = true; +} + SWGWFMDemodReport* SWGChannelReport::getWfmDemodReport() { return wfm_demod_report; @@ -373,6 +395,7 @@ SWGChannelReport::isSet(){ if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} if(udp_sink_report != nullptr && udp_sink_report->isSet()){ isObjectUpdated = true; break;} + if(udp_src_report != nullptr && udp_src_report->isSet()){ isObjectUpdated = true; break;} if(wfm_demod_report != nullptr && wfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_report != nullptr && wfm_mod_report->isSet()){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index a2f747ad6..d8c7f0aef 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -31,6 +31,7 @@ #include "SWGNFMModReport.h" #include "SWGSSBModReport.h" #include "SWGUDPSinkReport.h" +#include "SWGUDPSrcReport.h" #include "SWGWFMDemodReport.h" #include "SWGWFMModReport.h" #include @@ -86,6 +87,9 @@ public: SWGUDPSinkReport* getUdpSinkReport(); void setUdpSinkReport(SWGUDPSinkReport* udp_sink_report); + SWGUDPSrcReport* getUdpSrcReport(); + void setUdpSrcReport(SWGUDPSrcReport* udp_src_report); + SWGWFMDemodReport* getWfmDemodReport(); void setWfmDemodReport(SWGWFMDemodReport* wfm_demod_report); @@ -129,6 +133,9 @@ private: SWGUDPSinkReport* udp_sink_report; bool m_udp_sink_report_isSet; + SWGUDPSrcReport* udp_src_report; + bool m_udp_src_report_isSet; + SWGWFMDemodReport* wfm_demod_report; bool m_wfm_demod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 9acec3f34..642a823c6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -50,6 +50,8 @@ SWGChannelSettings::SWGChannelSettings() { m_ssb_mod_settings_isSet = false; udp_sink_settings = nullptr; m_udp_sink_settings_isSet = false; + udp_src_settings = nullptr; + m_udp_src_settings_isSet = false; wfm_demod_settings = nullptr; m_wfm_demod_settings_isSet = false; wfm_mod_settings = nullptr; @@ -84,6 +86,8 @@ SWGChannelSettings::init() { m_ssb_mod_settings_isSet = false; udp_sink_settings = new SWGUDPSinkSettings(); m_udp_sink_settings_isSet = false; + udp_src_settings = new SWGUDPSrcSettings(); + m_udp_src_settings_isSet = false; wfm_demod_settings = new SWGWFMDemodSettings(); m_wfm_demod_settings_isSet = false; wfm_mod_settings = new SWGWFMModSettings(); @@ -123,6 +127,9 @@ SWGChannelSettings::cleanup() { if(udp_sink_settings != nullptr) { delete udp_sink_settings; } + if(udp_src_settings != nullptr) { + delete udp_src_settings; + } if(wfm_demod_settings != nullptr) { delete wfm_demod_settings; } @@ -164,6 +171,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&udp_sink_settings, pJson["UDPSinkSettings"], "SWGUDPSinkSettings", "SWGUDPSinkSettings"); + ::SWGSDRangel::setValue(&udp_src_settings, pJson["UDPSrcSettings"], "SWGUDPSrcSettings", "SWGUDPSrcSettings"); + ::SWGSDRangel::setValue(&wfm_demod_settings, pJson["WFMDemodSettings"], "SWGWFMDemodSettings", "SWGWFMDemodSettings"); ::SWGSDRangel::setValue(&wfm_mod_settings, pJson["WFMModSettings"], "SWGWFMModSettings", "SWGWFMModSettings"); @@ -217,6 +226,9 @@ SWGChannelSettings::asJsonObject() { if((udp_sink_settings != nullptr) && (udp_sink_settings->isSet())){ toJsonValue(QString("UDPSinkSettings"), udp_sink_settings, obj, QString("SWGUDPSinkSettings")); } + if((udp_src_settings != nullptr) && (udp_src_settings->isSet())){ + toJsonValue(QString("UDPSrcSettings"), udp_src_settings, obj, QString("SWGUDPSrcSettings")); + } if((wfm_demod_settings != nullptr) && (wfm_demod_settings->isSet())){ toJsonValue(QString("WFMDemodSettings"), wfm_demod_settings, obj, QString("SWGWFMDemodSettings")); } @@ -337,6 +349,16 @@ SWGChannelSettings::setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings) { this->m_udp_sink_settings_isSet = true; } +SWGUDPSrcSettings* +SWGChannelSettings::getUdpSrcSettings() { + return udp_src_settings; +} +void +SWGChannelSettings::setUdpSrcSettings(SWGUDPSrcSettings* udp_src_settings) { + this->udp_src_settings = udp_src_settings; + this->m_udp_src_settings_isSet = true; +} + SWGWFMDemodSettings* SWGChannelSettings::getWfmDemodSettings() { return wfm_demod_settings; @@ -373,6 +395,7 @@ SWGChannelSettings::isSet(){ if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} + if(udp_src_settings != nullptr && udp_src_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_demod_settings != nullptr && wfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_settings != nullptr && wfm_mod_settings->isSet()){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 8dd292d0f..59a38aead 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -31,6 +31,7 @@ #include "SWGNFMModSettings.h" #include "SWGSSBModSettings.h" #include "SWGUDPSinkSettings.h" +#include "SWGUDPSrcSettings.h" #include "SWGWFMDemodSettings.h" #include "SWGWFMModSettings.h" #include @@ -86,6 +87,9 @@ public: SWGUDPSinkSettings* getUdpSinkSettings(); void setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings); + SWGUDPSrcSettings* getUdpSrcSettings(); + void setUdpSrcSettings(SWGUDPSrcSettings* udp_src_settings); + SWGWFMDemodSettings* getWfmDemodSettings(); void setWfmDemodSettings(SWGWFMDemodSettings* wfm_demod_settings); @@ -129,6 +133,9 @@ private: SWGUDPSinkSettings* udp_sink_settings; bool m_udp_sink_settings_isSet; + SWGUDPSrcSettings* udp_src_settings; + bool m_udp_src_settings_isSet; + SWGWFMDemodSettings* wfm_demod_settings; bool m_wfm_demod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index b4a2ffd13..853f9bb66 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -74,6 +74,8 @@ #include "SWGSuccessResponse.h" #include "SWGUDPSinkReport.h" #include "SWGUDPSinkSettings.h" +#include "SWGUDPSrcReport.h" +#include "SWGUDPSrcSettings.h" #include "SWGWFMDemodReport.h" #include "SWGWFMDemodSettings.h" #include "SWGWFMModReport.h" @@ -262,6 +264,12 @@ namespace SWGSDRangel { if(QString("SWGUDPSinkSettings").compare(type) == 0) { return new SWGUDPSinkSettings(); } + if(QString("SWGUDPSrcReport").compare(type) == 0) { + return new SWGUDPSrcReport(); + } + if(QString("SWGUDPSrcSettings").compare(type) == 0) { + return new SWGUDPSrcSettings(); + } if(QString("SWGWFMDemodReport").compare(type) == 0) { return new SWGWFMDemodReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp new file mode 100644 index 000000000..ff110a308 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp @@ -0,0 +1,148 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGUDPSrcReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGUDPSrcReport::SWGUDPSrcReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGUDPSrcReport::SWGUDPSrcReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + squelch = 0; + m_squelch_isSet = false; + input_sample_rate = 0; + m_input_sample_rate_isSet = false; +} + +SWGUDPSrcReport::~SWGUDPSrcReport() { + this->cleanup(); +} + +void +SWGUDPSrcReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + squelch = 0; + m_squelch_isSet = false; + input_sample_rate = 0; + m_input_sample_rate_isSet = false; +} + +void +SWGUDPSrcReport::cleanup() { + + + +} + +SWGUDPSrcReport* +SWGUDPSrcReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGUDPSrcReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "qint32", ""); + + ::SWGSDRangel::setValue(&input_sample_rate, pJson["inputSampleRate"], "qint32", ""); + +} + +QString +SWGUDPSrcReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGUDPSrcReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_input_sample_rate_isSet){ + obj->insert("inputSampleRate", QJsonValue(input_sample_rate)); + } + + return obj; +} + +float +SWGUDPSrcReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGUDPSrcReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGUDPSrcReport::getSquelch() { + return squelch; +} +void +SWGUDPSrcReport::setSquelch(qint32 squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +qint32 +SWGUDPSrcReport::getInputSampleRate() { + return input_sample_rate; +} +void +SWGUDPSrcReport::setInputSampleRate(qint32 input_sample_rate) { + this->input_sample_rate = input_sample_rate; + this->m_input_sample_rate_isSet = true; +} + + +bool +SWGUDPSrcReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_input_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h new file mode 100644 index 000000000..4abd1b719 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h @@ -0,0 +1,70 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGUDPSrcReport.h + * + * UDPSink + */ + +#ifndef SWGUDPSrcReport_H_ +#define SWGUDPSrcReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGUDPSrcReport: public SWGObject { +public: + SWGUDPSrcReport(); + SWGUDPSrcReport(QString* json); + virtual ~SWGUDPSrcReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGUDPSrcReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getSquelch(); + void setSquelch(qint32 squelch); + + qint32 getInputSampleRate(); + void setInputSampleRate(qint32 input_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 squelch; + bool m_squelch_isSet; + + qint32 input_sample_rate; + bool m_input_sample_rate_isSet; + +}; + +} + +#endif /* SWGUDPSrcReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp new file mode 100644 index 000000000..22406dd4b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp @@ -0,0 +1,488 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGUDPSrcSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGUDPSrcSettings::SWGUDPSrcSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGUDPSrcSettings::SWGUDPSrcSettings() { + output_sample_rate = 0.0f; + m_output_sample_rate_isSet = false; + sample_format = 0; + m_sample_format_isSet = false; + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + fm_deviation = 0; + m_fm_deviation_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + gain = 0.0f; + m_gain_isSet = false; + squelch_db = 0; + m_squelch_db_isSet = false; + squelch_gate = 0; + m_squelch_gate_isSet = false; + squelch_enabled = 0; + m_squelch_enabled_isSet = false; + agc = 0; + m_agc_isSet = false; + audio_active = 0; + m_audio_active_isSet = false; + audio_stereo = 0; + m_audio_stereo_isSet = false; + volume = 0; + m_volume_isSet = false; + udp_address = nullptr; + m_udp_address_isSet = false; + udp_port = 0; + m_udp_port_isSet = false; + audio_port = 0; + m_audio_port_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; +} + +SWGUDPSrcSettings::~SWGUDPSrcSettings() { + this->cleanup(); +} + +void +SWGUDPSrcSettings::init() { + output_sample_rate = 0.0f; + m_output_sample_rate_isSet = false; + sample_format = 0; + m_sample_format_isSet = false; + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + fm_deviation = 0; + m_fm_deviation_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + gain = 0.0f; + m_gain_isSet = false; + squelch_db = 0; + m_squelch_db_isSet = false; + squelch_gate = 0; + m_squelch_gate_isSet = false; + squelch_enabled = 0; + m_squelch_enabled_isSet = false; + agc = 0; + m_agc_isSet = false; + audio_active = 0; + m_audio_active_isSet = false; + audio_stereo = 0; + m_audio_stereo_isSet = false; + volume = 0; + m_volume_isSet = false; + udp_address = new QString(""); + m_udp_address_isSet = false; + udp_port = 0; + m_udp_port_isSet = false; + audio_port = 0; + m_audio_port_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; +} + +void +SWGUDPSrcSettings::cleanup() { + + + + + + + + + + + + + + + if(udp_address != nullptr) { + delete udp_address; + } + + + + if(title != nullptr) { + delete title; + } +} + +SWGUDPSrcSettings* +SWGUDPSrcSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGUDPSrcSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&output_sample_rate, pJson["outputSampleRate"], "float", ""); + + ::SWGSDRangel::setValue(&sample_format, pJson["sampleFormat"], "qint32", ""); + + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&fm_deviation, pJson["fmDeviation"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_mute, pJson["channelMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain, pJson["gain"], "float", ""); + + ::SWGSDRangel::setValue(&squelch_db, pJson["squelchDB"], "qint32", ""); + + ::SWGSDRangel::setValue(&squelch_gate, pJson["squelchGate"], "qint32", ""); + + ::SWGSDRangel::setValue(&squelch_enabled, pJson["squelchEnabled"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc, pJson["agc"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_active, pJson["audioActive"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_stereo, pJson["audioStereo"], "qint32", ""); + + ::SWGSDRangel::setValue(&volume, pJson["volume"], "qint32", ""); + + ::SWGSDRangel::setValue(&udp_address, pJson["udpAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&udp_port, pJson["udpPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_port, pJson["audioPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + +} + +QString +SWGUDPSrcSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGUDPSrcSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_output_sample_rate_isSet){ + obj->insert("outputSampleRate", QJsonValue(output_sample_rate)); + } + if(m_sample_format_isSet){ + obj->insert("sampleFormat", QJsonValue(sample_format)); + } + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_fm_deviation_isSet){ + obj->insert("fmDeviation", QJsonValue(fm_deviation)); + } + if(m_channel_mute_isSet){ + obj->insert("channelMute", QJsonValue(channel_mute)); + } + if(m_gain_isSet){ + obj->insert("gain", QJsonValue(gain)); + } + if(m_squelch_db_isSet){ + obj->insert("squelchDB", QJsonValue(squelch_db)); + } + if(m_squelch_gate_isSet){ + obj->insert("squelchGate", QJsonValue(squelch_gate)); + } + if(m_squelch_enabled_isSet){ + obj->insert("squelchEnabled", QJsonValue(squelch_enabled)); + } + if(m_agc_isSet){ + obj->insert("agc", QJsonValue(agc)); + } + if(m_audio_active_isSet){ + obj->insert("audioActive", QJsonValue(audio_active)); + } + if(m_audio_stereo_isSet){ + obj->insert("audioStereo", QJsonValue(audio_stereo)); + } + if(m_volume_isSet){ + obj->insert("volume", QJsonValue(volume)); + } + if(udp_address != nullptr && *udp_address != QString("")){ + toJsonValue(QString("udpAddress"), udp_address, obj, QString("QString")); + } + if(m_udp_port_isSet){ + obj->insert("udpPort", QJsonValue(udp_port)); + } + if(m_audio_port_isSet){ + obj->insert("audioPort", QJsonValue(audio_port)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + + return obj; +} + +float +SWGUDPSrcSettings::getOutputSampleRate() { + return output_sample_rate; +} +void +SWGUDPSrcSettings::setOutputSampleRate(float output_sample_rate) { + this->output_sample_rate = output_sample_rate; + this->m_output_sample_rate_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getSampleFormat() { + return sample_format; +} +void +SWGUDPSrcSettings::setSampleFormat(qint32 sample_format) { + this->sample_format = sample_format; + this->m_sample_format_isSet = true; +} + +qint64 +SWGUDPSrcSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGUDPSrcSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGUDPSrcSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGUDPSrcSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getFmDeviation() { + return fm_deviation; +} +void +SWGUDPSrcSettings::setFmDeviation(qint32 fm_deviation) { + this->fm_deviation = fm_deviation; + this->m_fm_deviation_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getChannelMute() { + return channel_mute; +} +void +SWGUDPSrcSettings::setChannelMute(qint32 channel_mute) { + this->channel_mute = channel_mute; + this->m_channel_mute_isSet = true; +} + +float +SWGUDPSrcSettings::getGain() { + return gain; +} +void +SWGUDPSrcSettings::setGain(float gain) { + this->gain = gain; + this->m_gain_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getSquelchDb() { + return squelch_db; +} +void +SWGUDPSrcSettings::setSquelchDb(qint32 squelch_db) { + this->squelch_db = squelch_db; + this->m_squelch_db_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getSquelchGate() { + return squelch_gate; +} +void +SWGUDPSrcSettings::setSquelchGate(qint32 squelch_gate) { + this->squelch_gate = squelch_gate; + this->m_squelch_gate_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getSquelchEnabled() { + return squelch_enabled; +} +void +SWGUDPSrcSettings::setSquelchEnabled(qint32 squelch_enabled) { + this->squelch_enabled = squelch_enabled; + this->m_squelch_enabled_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getAgc() { + return agc; +} +void +SWGUDPSrcSettings::setAgc(qint32 agc) { + this->agc = agc; + this->m_agc_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getAudioActive() { + return audio_active; +} +void +SWGUDPSrcSettings::setAudioActive(qint32 audio_active) { + this->audio_active = audio_active; + this->m_audio_active_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getAudioStereo() { + return audio_stereo; +} +void +SWGUDPSrcSettings::setAudioStereo(qint32 audio_stereo) { + this->audio_stereo = audio_stereo; + this->m_audio_stereo_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getVolume() { + return volume; +} +void +SWGUDPSrcSettings::setVolume(qint32 volume) { + this->volume = volume; + this->m_volume_isSet = true; +} + +QString* +SWGUDPSrcSettings::getUdpAddress() { + return udp_address; +} +void +SWGUDPSrcSettings::setUdpAddress(QString* udp_address) { + this->udp_address = udp_address; + this->m_udp_address_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getUdpPort() { + return udp_port; +} +void +SWGUDPSrcSettings::setUdpPort(qint32 udp_port) { + this->udp_port = udp_port; + this->m_udp_port_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getAudioPort() { + return audio_port; +} +void +SWGUDPSrcSettings::setAudioPort(qint32 audio_port) { + this->audio_port = audio_port; + this->m_audio_port_isSet = true; +} + +qint32 +SWGUDPSrcSettings::getRgbColor() { + return rgb_color; +} +void +SWGUDPSrcSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGUDPSrcSettings::getTitle() { + return title; +} +void +SWGUDPSrcSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + + +bool +SWGUDPSrcSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_output_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_sample_format_isSet){ isObjectUpdated = true; break;} + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_fm_deviation_isSet){ isObjectUpdated = true; break;} + if(m_channel_mute_isSet){ isObjectUpdated = true; break;} + if(m_gain_isSet){ isObjectUpdated = true; break;} + if(m_squelch_db_isSet){ isObjectUpdated = true; break;} + if(m_squelch_gate_isSet){ isObjectUpdated = true; break;} + if(m_squelch_enabled_isSet){ isObjectUpdated = true; break;} + if(m_agc_isSet){ isObjectUpdated = true; break;} + if(m_audio_active_isSet){ isObjectUpdated = true; break;} + if(m_audio_stereo_isSet){ isObjectUpdated = true; break;} + if(m_volume_isSet){ isObjectUpdated = true; break;} + if(udp_address != nullptr && *udp_address != QString("")){ isObjectUpdated = true; break;} + if(m_udp_port_isSet){ isObjectUpdated = true; break;} + if(m_audio_port_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h new file mode 100644 index 000000000..645f558cd --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h @@ -0,0 +1,167 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGUDPSrcSettings.h + * + * UDPSrc + */ + +#ifndef SWGUDPSrcSettings_H_ +#define SWGUDPSrcSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGUDPSrcSettings: public SWGObject { +public: + SWGUDPSrcSettings(); + SWGUDPSrcSettings(QString* json); + virtual ~SWGUDPSrcSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGUDPSrcSettings* fromJson(QString &jsonString) override; + + float getOutputSampleRate(); + void setOutputSampleRate(float output_sample_rate); + + qint32 getSampleFormat(); + void setSampleFormat(qint32 sample_format); + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + qint32 getFmDeviation(); + void setFmDeviation(qint32 fm_deviation); + + qint32 getChannelMute(); + void setChannelMute(qint32 channel_mute); + + float getGain(); + void setGain(float gain); + + qint32 getSquelchDb(); + void setSquelchDb(qint32 squelch_db); + + qint32 getSquelchGate(); + void setSquelchGate(qint32 squelch_gate); + + qint32 getSquelchEnabled(); + void setSquelchEnabled(qint32 squelch_enabled); + + qint32 getAgc(); + void setAgc(qint32 agc); + + qint32 getAudioActive(); + void setAudioActive(qint32 audio_active); + + qint32 getAudioStereo(); + void setAudioStereo(qint32 audio_stereo); + + qint32 getVolume(); + void setVolume(qint32 volume); + + QString* getUdpAddress(); + void setUdpAddress(QString* udp_address); + + qint32 getUdpPort(); + void setUdpPort(qint32 udp_port); + + qint32 getAudioPort(); + void setAudioPort(qint32 audio_port); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + + virtual bool isSet() override; + +private: + float output_sample_rate; + bool m_output_sample_rate_isSet; + + qint32 sample_format; + bool m_sample_format_isSet; + + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + qint32 fm_deviation; + bool m_fm_deviation_isSet; + + qint32 channel_mute; + bool m_channel_mute_isSet; + + float gain; + bool m_gain_isSet; + + qint32 squelch_db; + bool m_squelch_db_isSet; + + qint32 squelch_gate; + bool m_squelch_gate_isSet; + + qint32 squelch_enabled; + bool m_squelch_enabled_isSet; + + qint32 agc; + bool m_agc_isSet; + + qint32 audio_active; + bool m_audio_active_isSet; + + qint32 audio_stereo; + bool m_audio_stereo_isSet; + + qint32 volume; + bool m_volume_isSet; + + QString* udp_address; + bool m_udp_address_isSet; + + qint32 udp_port; + bool m_udp_port_isSet; + + qint32 audio_port; + bool m_audio_port_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + +}; + +} + +#endif /* SWGUDPSrcSettings_H_ */ From 3d8d9d34e058749ac5dba38c7ccc046031e430d4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 25 May 2018 18:44:24 +0200 Subject: [PATCH 455/956] UDP source: implemeted WEB API (2) --- plugins/channelrx/udpsrc/udpsrc.cpp | 112 +++++++++++++++++++++++ plugins/channelrx/udpsrc/udpsrc.h | 14 +++ plugins/channelrx/udpsrc/udpsrcgui.cpp | 32 ++++++- plugins/channelrx/udpsrc/udpsrcgui.h | 1 + plugins/channeltx/udpsink/udpsinkgui.cpp | 2 +- sdrbase/webapi/webapirequestmapper.cpp | 14 +++ swagger/sdrangel/examples/rx_test.py | 7 ++ 7 files changed, 178 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index b82b5406f..bc6e35c2b 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -643,6 +643,118 @@ bool UDPSrc::deserialize(const QByteArray& data) } } +int UDPSrc::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setUdpSrcSettings(new SWGSDRangel::SWGUDPSrcSettings()); + response.getUdpSrcSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int UDPSrc::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + UDPSrcSettings settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("outputSampleRate")) { + settings.m_outputSampleRate = response.getUdpSrcSettings()->getOutputSampleRate(); + } + if (channelSettingsKeys.contains("sampleFormat")) { + settings.m_sampleFormat = (UDPSrcSettings::SampleFormat) response.getUdpSrcSettings()->getSampleFormat(); + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getUdpSrcSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getUdpSrcSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getUdpSrcSettings()->getFmDeviation(); + } + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getUdpSrcSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("gain")) { + settings.m_gain = response.getUdpSrcSettings()->getGain(); + } + if (channelSettingsKeys.contains("squelchDB")) { + settings.m_squelchdB = response.getUdpSrcSettings()->getSquelchDb(); + } + if (channelSettingsKeys.contains("squelchGate")) { + settings.m_squelchGate = response.getUdpSrcSettings()->getSquelchGate(); + } + if (channelSettingsKeys.contains("squelchEnabled")) { + settings.m_squelchEnabled = response.getUdpSrcSettings()->getSquelchEnabled() != 0; + } + if (channelSettingsKeys.contains("agc")) { + settings.m_agc = response.getUdpSrcSettings()->getAgc() != 0; + } + if (channelSettingsKeys.contains("audioActive")) { + settings.m_audioActive = response.getUdpSrcSettings()->getAudioActive() != 0; + } + if (channelSettingsKeys.contains("audioStereo")) { + settings.m_audioStereo = response.getUdpSrcSettings()->getAudioStereo() != 0; + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getUdpSrcSettings()->getVolume(); + } + if (channelSettingsKeys.contains("udpAddress")) { + settings.m_udpAddress = *response.getUdpSrcSettings()->getUdpAddress(); + } + if (channelSettingsKeys.contains("udpPort")) { + settings.m_udpPort = response.getUdpSrcSettings()->getUdpPort(); + } + if (channelSettingsKeys.contains("audioPort")) { + settings.m_audioPort = response.getUdpSrcSettings()->getAudioPort(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getUdpSrcSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getUdpSrcSettings()->getTitle(); + } + + if (frequencyOffsetChanged) + { + UDPSrc::MsgConfigureChannelizer *msgChan = UDPSrc::MsgConfigureChannelizer::create( + (int) settings.m_outputSampleRate, + (int) settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureUDPSrc *msg = MsgConfigureUDPSrc::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("UDPSrc::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureUDPSrc *msgToGUI = MsgConfigureUDPSrc::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int UDPSrc::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setUdpSrcReport(new SWGSDRangel::SWGUDPSrcReport()); + response.getUdpSrcReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + void UDPSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSrcSettings& settings) { response.getUdpSrcSettings()->setOutputSampleRate(settings.m_outputSampleRate); diff --git a/plugins/channelrx/udpsrc/udpsrc.h b/plugins/channelrx/udpsrc/udpsrc.h index 9be4c71b1..e582a8845 100644 --- a/plugins/channelrx/udpsrc/udpsrc.h +++ b/plugins/channelrx/udpsrc/udpsrc.h @@ -114,6 +114,20 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + static const QString m_channelIdURI; static const QString m_channelId; static const int udpBlockSize = 512; // UDP block size in number of bytes diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsrc/udpsrcgui.cpp index b19b4bb83..28ba37359 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsrc/udpsrcgui.cpp @@ -89,10 +89,34 @@ bool UDPSrcGUI::deserialize(const QByteArray& data) } } -bool UDPSrcGUI::handleMessage(const Message& message __attribute__((unused))) +bool UDPSrcGUI::handleMessage(const Message& message ) { - qDebug() << "UDPSrcGUI::handleMessage"; - return false; + if (UDPSrc::MsgConfigureUDPSrc::match(message)) + { + const UDPSrc::MsgConfigureUDPSrc& cfg = (UDPSrc::MsgConfigureUDPSrc&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void UDPSrcGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } } void UDPSrcGUI::channelMarkerChangedByCursor() @@ -150,6 +174,7 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); m_udpSrc = (UDPSrc*) rxChannel; //new UDPSrc(m_deviceUISet->m_deviceSourceAPI); m_udpSrc->setSpectrum(m_spectrumVis); + m_udpSrc->setMessageQueueToGUI(getInputMessageQueue()); ui->fmDeviation->setEnabled(false); @@ -186,6 +211,7 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); diff --git a/plugins/channelrx/udpsrc/udpsrcgui.h b/plugins/channelrx/udpsrc/udpsrcgui.h index 12035dbb9..a35ac9324 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.h +++ b/plugins/channelrx/udpsrc/udpsrcgui.h @@ -92,6 +92,7 @@ private: void enterEvent(QEvent*); private slots: + void handleSourceMessages(); void on_deltaFrequency_changed(qint64 value); void on_sampleFormat_currentIndexChanged(int index); void on_outputUDPAddress_editingFinished(); diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index 3bfa7e620..fe2ac399d 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -83,7 +83,7 @@ bool UDPSinkGUI::deserialize(const QByteArray& data) } } -bool UDPSinkGUI::handleMessage(const Message& message __attribute__((unused))) +bool UDPSinkGUI::handleMessage(const Message& message) { if (UDPSink::MsgConfigureUDPSink::match(message)) { diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index ad630411a..0007b5669 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1973,6 +1973,20 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "UDPSrc") + { + if (channelSettings.getTx() == 0) + { + QJsonObject udpSrcSettingsJsonObject = jsonObject["UDPSrcSettings"].toObject(); + channelSettingsKeys = udpSrcSettingsJsonObject.keys(); + channelSettings.setUdpSrcSettings(new SWGSDRangel::SWGUDPSrcSettings()); + channelSettings.getUdpSrcSettings()->fromJsonObject(udpSrcSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "WFMDemod") { if (channelSettings.getTx() == 0) diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index ba5272f96..c22ffd263 100644 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -199,6 +199,13 @@ def setupChannel(deviceset_url, options): settings["DSDDemodSettings"]["enableCosineFiltering"] = 1 settings["DSDDemodSettings"]["pllLock"] = 1 settings["DSDDemodSettings"]["title"] = "Channel %d" % i + elif options.channel_id == "UDPSrc": + settings["UDPSrcSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["UDPSrcSettings"]["rfBandwidth"] = options.rf_bw + settings["UDPSrcSettings"]["volume"] = options.volume + settings["UDPSrcSettings"]["squelchDB"] = options.squelch_db + settings["UDPSrcSettings"]["channelMute"] = 0 + settings["UDPSrcSettings"]["title"] = "Channel %d" % i r = callAPI(deviceset_url + "/channel/%d/settings" % i, "PATCH", None, settings, "Change demod") if r is None: From 3203a5511dd107d1a24f3311c0ac3b5b8b1dd284 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 25 May 2018 19:28:57 +0200 Subject: [PATCH 456/956] Airspy input: implemeted WEB API --- plugins/channelrx/demodwfm/wfmplugin.cpp | 2 +- plugins/channelrx/udpsrc/udpsrcplugin.cpp | 2 +- plugins/samplesource/airspy/airspygui.cpp | 1 + plugins/samplesource/airspy/airspyinput.cpp | 107 +++++ plugins/samplesource/airspy/airspyinput.h | 11 + plugins/samplesource/airspyhf/airspyhfgui.cpp | 1 + .../bladerfinput/bladerfinputgui.cpp | 1 + plugins/samplesource/fcdpro/fcdprogui.cpp | 1 + .../samplesource/fcdproplus/fcdproplusgui.cpp | 1 + .../samplesource/filesource/filesourcegui.cpp | 1 + .../hackrfinput/hackrfinputgui.cpp | 1 + .../limesdrinput/limesdrinputgui.cpp | 1 + plugins/samplesource/perseus/perseusgui.cpp | 1 + .../plutosdrinput/plutosdrinputgui.cpp | 1 + plugins/samplesource/rtlsdr/rtlsdrgui.cpp | 1 + .../sdrdaemonsource/sdrdaemonsourcegui.cpp | 1 + plugins/samplesource/sdrplay/sdrplaygui.cpp | 1 + .../samplesource/testsource/testsourcegui.cpp | 1 + sdrbase/resources/webapi/doc/html2/index.html | 64 ++- .../webapi/doc/swagger/include/Airspy.yaml | 42 ++ .../resources/webapi/doc/swagger/swagger.yaml | 2 + .../sdrangel/api/swagger/include/Airspy.yaml | 42 ++ swagger/sdrangel/api/swagger/swagger.yaml | 2 + swagger/sdrangel/code/html2/index.html | 64 ++- .../code/qt5/client/SWGAirspySettings.cpp | 423 ++++++++++++++++++ .../code/qt5/client/SWGAirspySettings.h | 149 ++++++ .../code/qt5/client/SWGDeviceSettings.cpp | 23 + .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 4 + 29 files changed, 954 insertions(+), 4 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml create mode 100644 swagger/sdrangel/api/swagger/include/Airspy.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspySettings.h diff --git a/plugins/channelrx/demodwfm/wfmplugin.cpp b/plugins/channelrx/demodwfm/wfmplugin.cpp index bc2fa2c04..735ab1a3d 100644 --- a/plugins/channelrx/demodwfm/wfmplugin.cpp +++ b/plugins/channelrx/demodwfm/wfmplugin.cpp @@ -8,7 +8,7 @@ const PluginDescriptor WFMPlugin::m_pluginDescriptor = { QString("WFM Demodulator"), - QString("3.14.5"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.cpp b/plugins/channelrx/udpsrc/udpsrcplugin.cpp index c6e52b8f9..d623733eb 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.cpp +++ b/plugins/channelrx/udpsrc/udpsrcplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor UDPSrcPlugin::m_pluginDescriptor = { QString("UDP Channel Source"), - QString("3.14.5"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/airspy/airspygui.cpp b/plugins/samplesource/airspy/airspygui.cpp index 1bda2b1c8..a1517425a 100644 --- a/plugins/samplesource/airspy/airspygui.cpp +++ b/plugins/samplesource/airspy/airspygui.cpp @@ -56,6 +56,7 @@ AirspyGui::AirspyGui(DeviceUISet *deviceUISet, QWidget* parent) : m_rates = ((AirspyInput*) m_sampleSource)->getSampleRates(); displaySampleRates(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 6a5321718..b6ceca4b9 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -616,3 +616,110 @@ int AirspyInput::webapiRun( return 200; } +int AirspyInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAirspySettings(new SWGSDRangel::SWGAirspySettings()); + response.getAirspySettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int AirspyInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + AirspySettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getAirspySettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getAirspySettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("devSampleRateIndex")) { + settings.m_devSampleRateIndex = response.getAirspySettings()->getDevSampleRateIndex(); + } + if (deviceSettingsKeys.contains("lnaGain")) { + settings.m_lnaGain = response.getAirspySettings()->getLnaGain(); + } + if (deviceSettingsKeys.contains("mixerGain")) { + settings.m_mixerGain = response.getAirspySettings()->getMixerGain(); + } + if (deviceSettingsKeys.contains("vgaGain")) { + settings.m_vgaGain = response.getAirspySettings()->getVgaGain(); + } + if (deviceSettingsKeys.contains("vgaGain")) { + settings.m_vgaGain = response.getAirspySettings()->getVgaGain(); + } + if (deviceSettingsKeys.contains("lnaAGC")) { + settings.m_lnaAGC = response.getAirspySettings()->getLnaAgc() != 0; + } + if (deviceSettingsKeys.contains("mixerAGC")) { + settings.m_mixerAGC = response.getAirspySettings()->getMixerAgc() != 0; + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getAirspySettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) { + settings.m_fcPos = (AirspySettings::fcPos_t) response.getAirspySettings()->getFcPos(); + } + if (deviceSettingsKeys.contains("biasT")) { + settings.m_biasT = response.getAirspySettings()->getBiasT() != 0; + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getAirspySettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getAirspySettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getAirspySettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getAirspySettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getAirspySettings()->getFileRecordName(); + } + + MsgConfigureAirspy *msg = MsgConfigureAirspy::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAirspy *msgToGUI = MsgConfigureAirspy::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void AirspyInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspySettings& settings) +{ + response.getAirspySettings()->setCenterFrequency(settings.m_centerFrequency); + response.getAirspySettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getAirspySettings()->setDevSampleRateIndex(settings.m_devSampleRateIndex); + response.getAirspySettings()->setLnaGain(settings.m_lnaGain); + response.getAirspySettings()->setMixerGain(settings.m_mixerGain); + response.getAirspySettings()->setVgaGain(settings.m_vgaGain); + response.getAirspySettings()->setLnaAgc(settings.m_lnaAGC ? 1 : 0); + response.getAirspySettings()->setMixerAgc(settings.m_mixerAGC ? 1 : 0); + response.getAirspySettings()->setLog2Decim(settings.m_log2Decim); + response.getAirspySettings()->setFcPos((int) settings.m_fcPos); + response.getAirspySettings()->setBiasT(settings.m_biasT ? 1 : 0); + response.getAirspySettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getAirspySettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getAirspySettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getAirspySettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getAirspySettings()->getFileRecordName()) { + *response.getAirspySettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getAirspySettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} diff --git a/plugins/samplesource/airspy/airspyinput.h b/plugins/samplesource/airspy/airspyinput.h index d120c155d..33bf6552e 100644 --- a/plugins/samplesource/airspy/airspyinput.h +++ b/plugins/samplesource/airspy/airspyinput.h @@ -111,6 +111,16 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -129,6 +139,7 @@ private: bool applySettings(const AirspySettings& settings, bool force); struct airspy_device *open_airspy_from_sequence(int sequence); void setDeviceCenterFrequency(quint64 freq); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspySettings& settings); DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; diff --git a/plugins/samplesource/airspyhf/airspyhfgui.cpp b/plugins/samplesource/airspyhf/airspyhfgui.cpp index 745e3951b..3ce6e2e57 100644 --- a/plugins/samplesource/airspyhf/airspyhfgui.cpp +++ b/plugins/samplesource/airspyhf/airspyhfgui.cpp @@ -55,6 +55,7 @@ AirspyHFGui::AirspyHFGui(DeviceUISet *deviceUISet, QWidget* parent) : m_rates = ((AirspyHFInput*) m_sampleSource)->getSampleRates(); displaySampleRates(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } diff --git a/plugins/samplesource/bladerfinput/bladerfinputgui.cpp b/plugins/samplesource/bladerfinput/bladerfinputgui.cpp index c3b264b80..1e4b8979d 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputgui.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputgui.cpp @@ -63,6 +63,7 @@ BladerfInputGui::BladerfInputGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } diff --git a/plugins/samplesource/fcdpro/fcdprogui.cpp b/plugins/samplesource/fcdpro/fcdprogui.cpp index 16692ce68..d2bd2765e 100644 --- a/plugins/samplesource/fcdpro/fcdprogui.cpp +++ b/plugins/samplesource/fcdpro/fcdprogui.cpp @@ -146,6 +146,7 @@ FCDProGui::FCDProGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } FCDProGui::~FCDProGui() diff --git a/plugins/samplesource/fcdproplus/fcdproplusgui.cpp b/plugins/samplesource/fcdproplus/fcdproplusgui.cpp index 77bd5aa17..2a67120a1 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusgui.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusgui.cpp @@ -64,6 +64,7 @@ FCDProPlusGui::FCDProPlusGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } FCDProPlusGui::~FCDProPlusGui() diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index 918eff372..23f27a956 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -71,6 +71,7 @@ FileSourceGui::FileSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleSource = m_deviceUISet->m_deviceSourceAPI->getSampleSource(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } FileSourceGui::~FileSourceGui() diff --git a/plugins/samplesource/hackrfinput/hackrfinputgui.cpp b/plugins/samplesource/hackrfinput/hackrfinputgui.cpp index f45962dd6..9868313f2 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputgui.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputgui.cpp @@ -59,6 +59,7 @@ HackRFInputGui::HackRFInputGui(DeviceUISet *deviceUISet, QWidget* parent) : displayBandwidths(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index 10a930672..dc243470f 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -76,6 +76,7 @@ LimeSDRInputGUI::LimeSDRInputGUI(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_limeSDRInput->setMessageQueueToGUI(&m_inputMessageQueue); } LimeSDRInputGUI::~LimeSDRInputGUI() diff --git a/plugins/samplesource/perseus/perseusgui.cpp b/plugins/samplesource/perseus/perseusgui.cpp index f25f30afd..b2764a2eb 100644 --- a/plugins/samplesource/perseus/perseusgui.cpp +++ b/plugins/samplesource/perseus/perseusgui.cpp @@ -53,6 +53,7 @@ PerseusGui::PerseusGui(DeviceUISet *deviceUISet, QWidget* parent) : m_rates = m_sampleSource->getSampleRates(); displaySampleRates(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp index 302f2a882..af07995d1 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp @@ -68,6 +68,7 @@ PlutoSDRInputGui::PlutoSDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_statusTimer.start(500); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } PlutoSDRInputGui::~PlutoSDRInputGui() diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp index 8b2c72963..d5db76d98 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp @@ -62,6 +62,7 @@ RTLSDRGui::RTLSDRGui(DeviceUISet *deviceUISet, QWidget* parent) : displayGains(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } RTLSDRGui::~RTLSDRGui() diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index a6cc54129..86510b77a 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -96,6 +96,7 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent m_sampleSource = (SDRdaemonSourceInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); m_eventsTime.start(); displayEventCounts(); diff --git a/plugins/samplesource/sdrplay/sdrplaygui.cpp b/plugins/samplesource/sdrplay/sdrplaygui.cpp index 21c0392c2..b7fa82e93 100644 --- a/plugins/samplesource/sdrplay/sdrplaygui.cpp +++ b/plugins/samplesource/sdrplay/sdrplaygui.cpp @@ -73,6 +73,7 @@ SDRPlayGui::SDRPlayGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } SDRPlayGui::~SDRPlayGui() diff --git a/plugins/samplesource/testsource/testsourcegui.cpp b/plugins/samplesource/testsource/testsourcegui.cpp index f228c686f..7fbc84f32 100644 --- a/plugins/samplesource/testsource/testsourcegui.cpp +++ b/plugins/samplesource/testsource/testsourcegui.cpp @@ -65,6 +65,7 @@ TestSourceGui::TestSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_statusTimer.start(500); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } TestSourceGui::~TestSourceGui() diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 686abdb6c..1a93cd249 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -942,6 +942,65 @@ margin-bottom: 20px; } }, "description" : "AirspyHF" +}; + defs.AirspySettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "devSampleRateIndex" : { + "type" : "integer" + }, + "lnaGain" : { + "type" : "integer" + }, + "mixerGain" : { + "type" : "integer" + }, + "vgaGain" : { + "type" : "integer" + }, + "lnaAGC" : { + "type" : "integer", + "description" : "LNA AGC (1 if active else 0)" + }, + "mixerAGC" : { + "type" : "integer", + "description" : "Mixer AGC (1 if active else 0)" + }, + "log2Decim" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer", + "description" : "0=Infra 1=Supra 2=Center" + }, + "biasT" : { + "type" : "integer", + "description" : "Bias tee (1 if active else 0)" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "Airspy" }; defs.AudioDevices = { "required" : [ "nbInputDevices", "nbOutputDevices" ], @@ -1657,6 +1716,9 @@ margin-bottom: 20px; "type" : "integer", "description" : "Not zero if it is a tx device else it is a rx device" }, + "airspySettings" : { + "$ref" : "#/definitions/AirspySettings" + }, "airspyHFSettings" : { "$ref" : "#/definitions/AirspyHFSettings" }, @@ -21118,7 +21180,7 @@ except ApiException as e:
- Generated 2018-05-25T10:44:21.402+02:00 + Generated 2018-05-25T18:55:39.036+02:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml b/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml new file mode 100644 index 000000000..f3c28a03c --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml @@ -0,0 +1,42 @@ +AirspySettings: + description: Airspy + properties: + centerFrequency: + type: integer + format: int64 + LOppmTenths: + type: integer + devSampleRateIndex: + type: integer + lnaGain: + type: integer + mixerGain: + type: integer + vgaGain: + type: integer + lnaAGC: + description: LNA AGC (1 if active else 0) + type: integer + mixerAGC: + description: Mixer AGC (1 if active else 0) + type: integer + log2Decim: + type: integer + fcPos: + description: 0=Infra 1=Supra 2=Center + type: integer + biasT: + description: Bias tee (1 if active else 0) + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index c1bad45d9..bb7a84212 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1713,6 +1713,8 @@ definitions: tx: description: Not zero if it is a tx device else it is a rx device type: integer + airspySettings: + $ref: "/doc/swagger/include/Airspy.yaml#/AirspySettings" airspyHFSettings: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFSettings" bladeRFInputSettings: diff --git a/swagger/sdrangel/api/swagger/include/Airspy.yaml b/swagger/sdrangel/api/swagger/include/Airspy.yaml new file mode 100644 index 000000000..f3c28a03c --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/Airspy.yaml @@ -0,0 +1,42 @@ +AirspySettings: + description: Airspy + properties: + centerFrequency: + type: integer + format: int64 + LOppmTenths: + type: integer + devSampleRateIndex: + type: integer + lnaGain: + type: integer + mixerGain: + type: integer + vgaGain: + type: integer + lnaAGC: + description: LNA AGC (1 if active else 0) + type: integer + mixerAGC: + description: Mixer AGC (1 if active else 0) + type: integer + log2Decim: + type: integer + fcPos: + description: 0=Infra 1=Supra 2=Center + type: integer + biasT: + description: Bias tee (1 if active else 0) + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 55214e17c..e31ea9c19 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1713,6 +1713,8 @@ definitions: tx: description: Not zero if it is a tx device else it is a rx device type: integer + airspySettings: + $ref: "http://localhost:8081/api/swagger/include/Airspy.yaml#/AirspySettings" airspyHFSettings: $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFSettings" bladeRFInputSettings: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 686abdb6c..1a93cd249 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -942,6 +942,65 @@ margin-bottom: 20px; } }, "description" : "AirspyHF" +}; + defs.AirspySettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "devSampleRateIndex" : { + "type" : "integer" + }, + "lnaGain" : { + "type" : "integer" + }, + "mixerGain" : { + "type" : "integer" + }, + "vgaGain" : { + "type" : "integer" + }, + "lnaAGC" : { + "type" : "integer", + "description" : "LNA AGC (1 if active else 0)" + }, + "mixerAGC" : { + "type" : "integer", + "description" : "Mixer AGC (1 if active else 0)" + }, + "log2Decim" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer", + "description" : "0=Infra 1=Supra 2=Center" + }, + "biasT" : { + "type" : "integer", + "description" : "Bias tee (1 if active else 0)" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "Airspy" }; defs.AudioDevices = { "required" : [ "nbInputDevices", "nbOutputDevices" ], @@ -1657,6 +1716,9 @@ margin-bottom: 20px; "type" : "integer", "description" : "Not zero if it is a tx device else it is a rx device" }, + "airspySettings" : { + "$ref" : "#/definitions/AirspySettings" + }, "airspyHFSettings" : { "$ref" : "#/definitions/AirspyHFSettings" }, @@ -21118,7 +21180,7 @@ except ApiException as e:
- Generated 2018-05-25T10:44:21.402+02:00 + Generated 2018-05-25T18:55:39.036+02:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp new file mode 100644 index 000000000..2d7ed1d52 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp @@ -0,0 +1,423 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAirspySettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAirspySettings::SWGAirspySettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAirspySettings::SWGAirspySettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + dev_sample_rate_index = 0; + m_dev_sample_rate_index_isSet = false; + lna_gain = 0; + m_lna_gain_isSet = false; + mixer_gain = 0; + m_mixer_gain_isSet = false; + vga_gain = 0; + m_vga_gain_isSet = false; + lna_agc = 0; + m_lna_agc_isSet = false; + mixer_agc = 0; + m_mixer_agc_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + bias_t = 0; + m_bias_t_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; +} + +SWGAirspySettings::~SWGAirspySettings() { + this->cleanup(); +} + +void +SWGAirspySettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + dev_sample_rate_index = 0; + m_dev_sample_rate_index_isSet = false; + lna_gain = 0; + m_lna_gain_isSet = false; + mixer_gain = 0; + m_mixer_gain_isSet = false; + vga_gain = 0; + m_vga_gain_isSet = false; + lna_agc = 0; + m_lna_agc_isSet = false; + mixer_agc = 0; + m_mixer_agc_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + bias_t = 0; + m_bias_t_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; +} + +void +SWGAirspySettings::cleanup() { + + + + + + + + + + + + + + + + if(file_record_name != nullptr) { + delete file_record_name; + } +} + +SWGAirspySettings* +SWGAirspySettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAirspySettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate_index, pJson["devSampleRateIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&lna_gain, pJson["lnaGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&mixer_gain, pJson["mixerGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&vga_gain, pJson["vgaGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&lna_agc, pJson["lnaAGC"], "qint32", ""); + + ::SWGSDRangel::setValue(&mixer_agc, pJson["mixerAGC"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&fc_pos, pJson["fcPos"], "qint32", ""); + + ::SWGSDRangel::setValue(&bias_t, pJson["biasT"], "qint32", ""); + + ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); + + ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + +} + +QString +SWGAirspySettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAirspySettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); + } + if(m_dev_sample_rate_index_isSet){ + obj->insert("devSampleRateIndex", QJsonValue(dev_sample_rate_index)); + } + if(m_lna_gain_isSet){ + obj->insert("lnaGain", QJsonValue(lna_gain)); + } + if(m_mixer_gain_isSet){ + obj->insert("mixerGain", QJsonValue(mixer_gain)); + } + if(m_vga_gain_isSet){ + obj->insert("vgaGain", QJsonValue(vga_gain)); + } + if(m_lna_agc_isSet){ + obj->insert("lnaAGC", QJsonValue(lna_agc)); + } + if(m_mixer_agc_isSet){ + obj->insert("mixerAGC", QJsonValue(mixer_agc)); + } + if(m_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_fc_pos_isSet){ + obj->insert("fcPos", QJsonValue(fc_pos)); + } + if(m_bias_t_isSet){ + obj->insert("biasT", QJsonValue(bias_t)); + } + if(m_dc_block_isSet){ + obj->insert("dcBlock", QJsonValue(dc_block)); + } + if(m_iq_correction_isSet){ + obj->insert("iqCorrection", QJsonValue(iq_correction)); + } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGAirspySettings::getCenterFrequency() { + return center_frequency; +} +void +SWGAirspySettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGAirspySettings::getLOppmTenths() { + return l_oppm_tenths; +} +void +SWGAirspySettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; +} + +qint32 +SWGAirspySettings::getDevSampleRateIndex() { + return dev_sample_rate_index; +} +void +SWGAirspySettings::setDevSampleRateIndex(qint32 dev_sample_rate_index) { + this->dev_sample_rate_index = dev_sample_rate_index; + this->m_dev_sample_rate_index_isSet = true; +} + +qint32 +SWGAirspySettings::getLnaGain() { + return lna_gain; +} +void +SWGAirspySettings::setLnaGain(qint32 lna_gain) { + this->lna_gain = lna_gain; + this->m_lna_gain_isSet = true; +} + +qint32 +SWGAirspySettings::getMixerGain() { + return mixer_gain; +} +void +SWGAirspySettings::setMixerGain(qint32 mixer_gain) { + this->mixer_gain = mixer_gain; + this->m_mixer_gain_isSet = true; +} + +qint32 +SWGAirspySettings::getVgaGain() { + return vga_gain; +} +void +SWGAirspySettings::setVgaGain(qint32 vga_gain) { + this->vga_gain = vga_gain; + this->m_vga_gain_isSet = true; +} + +qint32 +SWGAirspySettings::getLnaAgc() { + return lna_agc; +} +void +SWGAirspySettings::setLnaAgc(qint32 lna_agc) { + this->lna_agc = lna_agc; + this->m_lna_agc_isSet = true; +} + +qint32 +SWGAirspySettings::getMixerAgc() { + return mixer_agc; +} +void +SWGAirspySettings::setMixerAgc(qint32 mixer_agc) { + this->mixer_agc = mixer_agc; + this->m_mixer_agc_isSet = true; +} + +qint32 +SWGAirspySettings::getLog2Decim() { + return log2_decim; +} +void +SWGAirspySettings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +qint32 +SWGAirspySettings::getFcPos() { + return fc_pos; +} +void +SWGAirspySettings::setFcPos(qint32 fc_pos) { + this->fc_pos = fc_pos; + this->m_fc_pos_isSet = true; +} + +qint32 +SWGAirspySettings::getBiasT() { + return bias_t; +} +void +SWGAirspySettings::setBiasT(qint32 bias_t) { + this->bias_t = bias_t; + this->m_bias_t_isSet = true; +} + +qint32 +SWGAirspySettings::getDcBlock() { + return dc_block; +} +void +SWGAirspySettings::setDcBlock(qint32 dc_block) { + this->dc_block = dc_block; + this->m_dc_block_isSet = true; +} + +qint32 +SWGAirspySettings::getIqCorrection() { + return iq_correction; +} +void +SWGAirspySettings::setIqCorrection(qint32 iq_correction) { + this->iq_correction = iq_correction; + this->m_iq_correction_isSet = true; +} + +qint32 +SWGAirspySettings::getTransverterMode() { + return transverter_mode; +} +void +SWGAirspySettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGAirspySettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGAirspySettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + +QString* +SWGAirspySettings::getFileRecordName() { + return file_record_name; +} +void +SWGAirspySettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + + +bool +SWGAirspySettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_index_isSet){ isObjectUpdated = true; break;} + if(m_lna_gain_isSet){ isObjectUpdated = true; break;} + if(m_mixer_gain_isSet){ isObjectUpdated = true; break;} + if(m_vga_gain_isSet){ isObjectUpdated = true; break;} + if(m_lna_agc_isSet){ isObjectUpdated = true; break;} + if(m_mixer_agc_isSet){ isObjectUpdated = true; break;} + if(m_log2_decim_isSet){ isObjectUpdated = true; break;} + if(m_fc_pos_isSet){ isObjectUpdated = true; break;} + if(m_bias_t_isSet){ isObjectUpdated = true; break;} + if(m_dc_block_isSet){ isObjectUpdated = true; break;} + if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h new file mode 100644 index 000000000..f9d30f809 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h @@ -0,0 +1,149 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAirspySettings.h + * + * Airspy + */ + +#ifndef SWGAirspySettings_H_ +#define SWGAirspySettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAirspySettings: public SWGObject { +public: + SWGAirspySettings(); + SWGAirspySettings(QString* json); + virtual ~SWGAirspySettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAirspySettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); + + qint32 getDevSampleRateIndex(); + void setDevSampleRateIndex(qint32 dev_sample_rate_index); + + qint32 getLnaGain(); + void setLnaGain(qint32 lna_gain); + + qint32 getMixerGain(); + void setMixerGain(qint32 mixer_gain); + + qint32 getVgaGain(); + void setVgaGain(qint32 vga_gain); + + qint32 getLnaAgc(); + void setLnaAgc(qint32 lna_agc); + + qint32 getMixerAgc(); + void setMixerAgc(qint32 mixer_agc); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + qint32 getFcPos(); + void setFcPos(qint32 fc_pos); + + qint32 getBiasT(); + void setBiasT(qint32 bias_t); + + qint32 getDcBlock(); + void setDcBlock(qint32 dc_block); + + qint32 getIqCorrection(); + void setIqCorrection(qint32 iq_correction); + + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; + + qint32 dev_sample_rate_index; + bool m_dev_sample_rate_index_isSet; + + qint32 lna_gain; + bool m_lna_gain_isSet; + + qint32 mixer_gain; + bool m_mixer_gain_isSet; + + qint32 vga_gain; + bool m_vga_gain_isSet; + + qint32 lna_agc; + bool m_lna_agc_isSet; + + qint32 mixer_agc; + bool m_mixer_agc_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + qint32 fc_pos; + bool m_fc_pos_isSet; + + qint32 bias_t; + bool m_bias_t_isSet; + + qint32 dc_block; + bool m_dc_block_isSet; + + qint32 iq_correction; + bool m_iq_correction_isSet; + + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + +}; + +} + +#endif /* SWGAirspySettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index d798ead5b..50b53fc35 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -32,6 +32,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_device_hw_type_isSet = false; tx = 0; m_tx_isSet = false; + airspy_settings = nullptr; + m_airspy_settings_isSet = false; airspy_hf_settings = nullptr; m_airspy_hf_settings_isSet = false; blade_rf_input_settings = nullptr; @@ -62,6 +64,8 @@ SWGDeviceSettings::init() { m_device_hw_type_isSet = false; tx = 0; m_tx_isSet = false; + airspy_settings = new SWGAirspySettings(); + m_airspy_settings_isSet = false; airspy_hf_settings = new SWGAirspyHFSettings(); m_airspy_hf_settings_isSet = false; blade_rf_input_settings = new SWGBladeRFInputSettings(); @@ -88,6 +92,9 @@ SWGDeviceSettings::cleanup() { delete device_hw_type; } + if(airspy_settings != nullptr) { + delete airspy_settings; + } if(airspy_hf_settings != nullptr) { delete airspy_hf_settings; } @@ -132,6 +139,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&tx, pJson["tx"], "qint32", ""); + ::SWGSDRangel::setValue(&airspy_settings, pJson["airspySettings"], "SWGAirspySettings", "SWGAirspySettings"); + ::SWGSDRangel::setValue(&airspy_hf_settings, pJson["airspyHFSettings"], "SWGAirspyHFSettings", "SWGAirspyHFSettings"); ::SWGSDRangel::setValue(&blade_rf_input_settings, pJson["bladeRFInputSettings"], "SWGBladeRFInputSettings", "SWGBladeRFInputSettings"); @@ -172,6 +181,9 @@ SWGDeviceSettings::asJsonObject() { if(m_tx_isSet){ obj->insert("tx", QJsonValue(tx)); } + if((airspy_settings != nullptr) && (airspy_settings->isSet())){ + toJsonValue(QString("airspySettings"), airspy_settings, obj, QString("SWGAirspySettings")); + } if((airspy_hf_settings != nullptr) && (airspy_hf_settings->isSet())){ toJsonValue(QString("airspyHFSettings"), airspy_hf_settings, obj, QString("SWGAirspyHFSettings")); } @@ -223,6 +235,16 @@ SWGDeviceSettings::setTx(qint32 tx) { this->m_tx_isSet = true; } +SWGAirspySettings* +SWGDeviceSettings::getAirspySettings() { + return airspy_settings; +} +void +SWGDeviceSettings::setAirspySettings(SWGAirspySettings* airspy_settings) { + this->airspy_settings = airspy_settings; + this->m_airspy_settings_isSet = true; +} + SWGAirspyHFSettings* SWGDeviceSettings::getAirspyHfSettings() { return airspy_hf_settings; @@ -320,6 +342,7 @@ SWGDeviceSettings::isSet(){ do{ if(device_hw_type != nullptr && *device_hw_type != QString("")){ isObjectUpdated = true; break;} if(m_tx_isSet){ isObjectUpdated = true; break;} + if(airspy_settings != nullptr && airspy_settings->isSet()){ isObjectUpdated = true; break;} if(airspy_hf_settings != nullptr && airspy_hf_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf_input_settings != nullptr && blade_rf_input_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf_output_settings != nullptr && blade_rf_output_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index c9ec69600..2c38ac566 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -23,6 +23,7 @@ #include "SWGAirspyHFSettings.h" +#include "SWGAirspySettings.h" #include "SWGBladeRFInputSettings.h" #include "SWGBladeRFOutputSettings.h" #include "SWGFileSourceSettings.h" @@ -57,6 +58,9 @@ public: qint32 getTx(); void setTx(qint32 tx); + SWGAirspySettings* getAirspySettings(); + void setAirspySettings(SWGAirspySettings* airspy_settings); + SWGAirspyHFSettings* getAirspyHfSettings(); void setAirspyHfSettings(SWGAirspyHFSettings* airspy_hf_settings); @@ -94,6 +98,9 @@ private: qint32 tx; bool m_tx_isSet; + SWGAirspySettings* airspy_settings; + bool m_airspy_settings_isSet; + SWGAirspyHFSettings* airspy_hf_settings; bool m_airspy_hf_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 853f9bb66..c8f0a18b1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -21,6 +21,7 @@ #include "SWGATVModReport.h" #include "SWGATVModSettings.h" #include "SWGAirspyHFSettings.h" +#include "SWGAirspySettings.h" #include "SWGAudioDevices.h" #include "SWGAudioInputDevice.h" #include "SWGAudioOutputDevice.h" @@ -105,6 +106,9 @@ namespace SWGSDRangel { if(QString("SWGAirspyHFSettings").compare(type) == 0) { return new SWGAirspyHFSettings(); } + if(QString("SWGAirspySettings").compare(type) == 0) { + return new SWGAirspySettings(); + } if(QString("SWGAudioDevices").compare(type) == 0) { return new SWGAudioDevices(); } From 83c0935e4199b5d8ae926a3e21f076b3e004aaad Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 25 May 2018 22:39:38 +0200 Subject: [PATCH 457/956] Airspy input: implemeted WEB API (2) --- sdrbase/webapi/webapirequestmapper.cpp | 15 +++++++++++++++ swagger/sdrangel/examples/rx_test.py | 14 +++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 0007b5669..2fb880e6f 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1681,6 +1681,21 @@ bool WebAPIRequestMapper::validateDeviceSettings( return false; } } + else if ((*deviceHwType == "Airspy") && (deviceSettings.getTx() == 0)) + { + if (jsonObject.contains("airspySettings") && jsonObject["airspySettings"].isObject()) + { + QJsonObject airspySettingsJsonObject = jsonObject["airspySettings"].toObject(); + deviceSettingsKeys = airspySettingsJsonObject.keys(); + deviceSettings.setAirspySettings(new SWGSDRangel::SWGAirspySettings()); + deviceSettings.getAirspySettings()->fromJsonObject(airspySettingsJsonObject); + return true; + } + else + { + return false; + } + } else if ((*deviceHwType == "AirspyHF") && (deviceSettings.getTx() == 0)) { if (jsonObject.contains("airspyHFSettings") && jsonObject["airspyHFSettings"].isObject()) diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index c22ffd263..fe854f05a 100644 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -97,7 +97,19 @@ def setupDevice(deviceset_url, options): if settings is None: exit(-1) - if options.device_hwid == "AirspyHF": + if options.device_hwid == "Airspy": + settings["airspySettings"]["centerFrequency"] = options.device_freq*1000 + settings["airspySettings"]["devSampleRateIndex"] = 1 + settings['airspySettings']['log2Decim'] = options.log2_decim + settings['airspySettings']['fcPos'] = options.fc_pos + settings['airspySettings']['dcBlock'] = options.fc_pos == 2 + settings['airspySettings']['iqImbalance'] = options.fc_pos == 2 + settings['airspySettings']['lnaGain'] = 14 + settings['airspySettings']['mixerGain'] = 15 + settings['airspySettings']['vgaGain'] = 4 + settings['airspySettings']['lnaAGC'] = 1 + settings['airspySettings']['mixerAGC'] = 1 + elif options.device_hwid == "AirspyHF": if options.device_freq > 30000: settings["airspyHFSettings"]["bandIndex"] = 1 else: From ae0729838778dd4d87f1c4bb3827f15a1ce9ee48 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 25 May 2018 23:17:46 +0200 Subject: [PATCH 458/956] Web API: removed useless method in mapper --- sdrbase/webapi/webapirequestmapper.cpp | 137 ------------------------- sdrbase/webapi/webapirequestmapper.h | 1 - 2 files changed, 138 deletions(-) diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 2fb880e6f..6a149d513 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2043,143 +2043,6 @@ bool WebAPIRequestMapper::validateChannelSettings( } } -bool WebAPIRequestMapper::validateChannelReport( - SWGSDRangel::SWGChannelReport& channelReport, - QJsonObject& jsonObject, - QStringList& channelReportKeys) -{ - if (jsonObject.contains("tx")) { - channelReport.setTx(jsonObject["tx"].toInt()); - } else { - channelReport.setTx(0); // assume Rx - } - - if (jsonObject.contains("channelType") && jsonObject["channelType"].isString()) { - channelReport.setChannelType(new QString(jsonObject["channelType"].toString())); - } else { - return false; - } - - QString *channelType = channelReport.getChannelType(); - - if (*channelType == "AMDemod") - { - if (channelReport.getTx() == 0) - { - QJsonObject amDemodReportJsonObject = jsonObject["AMDemodReport"].toObject(); - channelReportKeys = amDemodReportJsonObject.keys(); - channelReport.setAmDemodReport(new SWGSDRangel::SWGAMDemodReport()); - channelReport.getAmDemodReport()->fromJsonObject(amDemodReportJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "AMMod") - { - if (channelReport.getTx() != 0) - { - QJsonObject amModReportJsonObject = jsonObject["AMModReport"].toObject(); - channelReportKeys = amModReportJsonObject.keys(); - channelReport.setAmModReport(new SWGSDRangel::SWGAMModReport()); - channelReport.getAmModReport()->fromJsonObject(amModReportJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "ATVMod") - { - if (channelReport.getTx() != 0) - { - QJsonObject atvModReportJsonObject = jsonObject["ATVModReport"].toObject(); - channelReportKeys = atvModReportJsonObject.keys(); - channelReport.setAtvModReport(new SWGSDRangel::SWGATVModReport()); - channelReport.getAtvModReport()->fromJsonObject(atvModReportJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "NFMDemod") - { - if (channelReport.getTx() == 0) - { - QJsonObject nfmDemodReportJsonObject = jsonObject["NFMDemodReport"].toObject(); - channelReportKeys = nfmDemodReportJsonObject.keys(); - channelReport.setNfmDemodReport(new SWGSDRangel::SWGNFMDemodReport()); - channelReport.getNfmDemodReport()->fromJsonObject(nfmDemodReportJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "NFMMod") - { - if (channelReport.getTx() != 0) - { - QJsonObject nfmModReportJsonObject = jsonObject["NFMModReport"].toObject(); - channelReportKeys = nfmModReportJsonObject.keys(); - channelReport.setNfmModReport(new SWGSDRangel::SWGNFMModReport()); - channelReport.getNfmModReport()->fromJsonObject(nfmModReportJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "SSBMod") - { - if (channelReport.getTx() != 0) - { - QJsonObject ssbModReportJsonObject = jsonObject["SSBModReport"].toObject(); - channelReportKeys = ssbModReportJsonObject.keys(); - channelReport.setSsbModReport(new SWGSDRangel::SWGSSBModReport()); - channelReport.getSsbModReport()->fromJsonObject(ssbModReportJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "UDPSink") - { - if (channelReport.getTx() != 0) - { - QJsonObject udpSinkReportJsonObject = jsonObject["UDPSinkReport"].toObject(); - channelReportKeys = udpSinkReportJsonObject.keys(); - channelReport.setUdpSinkReport(new SWGSDRangel::SWGUDPSinkReport()); - channelReport.getUdpSinkReport()->fromJsonObject(udpSinkReportJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "WFMMod") - { - if (channelReport.getTx() != 0) - { - QJsonObject wfmModReportJsonObject = jsonObject["WFMModReport"].toObject(); - channelReportKeys = wfmModReportJsonObject.keys(); - channelReport.setWfmModReport(new SWGSDRangel::SWGWFMModReport()); - channelReport.getWfmModReport()->fromJsonObject(wfmModReportJsonObject); - return true; - } - else { - return false; - } - } - else - { - return false; - } -} - bool WebAPIRequestMapper::validateAudioInputDevice( SWGSDRangel::SWGAudioInputDevice& audioInputDevice, QJsonObject& jsonObject, diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index 33a598d30..0abb90913 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -81,7 +81,6 @@ private: bool validateDeviceListItem(SWGSDRangel::SWGDeviceListItem& deviceListItem, QJsonObject& jsonObject); bool validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys); bool validateChannelSettings(SWGSDRangel::SWGChannelSettings& deviceSettings, QJsonObject& jsonObject, QStringList& channelSettingsKeys); - bool validateChannelReport(SWGSDRangel::SWGChannelReport& deviceReport, QJsonObject& jsonObject, QStringList& channelReportKeys); bool validateAudioInputDevice(SWGSDRangel::SWGAudioInputDevice& audioInputDevice, QJsonObject& jsonObject, QStringList& audioInputDeviceKeys); bool validateAudioOutputDevice(SWGSDRangel::SWGAudioOutputDevice& audioOutputDevice, QJsonObject& jsonObject, QStringList& audioOutputDeviceKeys); From f8f976fd509c09f33d879ace50f20ca7ec72e9ce Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 01:43:28 +0200 Subject: [PATCH 459/956] Web API: implemented device report interface. Applied to Airspy --- plugins/samplesource/airspy/airspyinput.cpp | 26 + plugins/samplesource/airspy/airspyinput.h | 5 + sdrbase/dsp/devicesamplesink.h | 6 + sdrbase/dsp/devicesamplesource.h | 6 + sdrbase/resources/res.qrc | 3 + sdrbase/resources/webapi/doc/html2/index.html | 503 +++++++++++++++++- .../webapi/doc/swagger/include/Airspy.yaml | 12 + .../resources/webapi/doc/swagger/swagger.yaml | 48 ++ sdrbase/webapi/webapiadapterinterface.cpp | 1 + sdrbase/webapi/webapiadapterinterface.h | 16 + sdrbase/webapi/webapirequestmapper.cpp | 40 ++ sdrbase/webapi/webapirequestmapper.h | 1 + sdrgui/webapi/webapiadaptergui.cpp | 40 ++ sdrgui/webapi/webapiadaptergui.h | 5 + .../sdrangel/api/swagger/include/Airspy.yaml | 12 + swagger/sdrangel/api/swagger/swagger.yaml | 48 ++ swagger/sdrangel/code/html2/index.html | 503 +++++++++++++++++- .../code/qt5/client/SWGAirspyReport.cpp | 112 ++++ .../code/qt5/client/SWGAirspyReport.h | 60 +++ .../client/SWGAirspyReport_sampleRates.cpp | 106 ++++ .../qt5/client/SWGAirspyReport_sampleRates.h | 58 ++ .../code/qt5/client/SWGDeviceReport.cpp | 152 ++++++ .../code/qt5/client/SWGDeviceReport.h | 72 +++ .../code/qt5/client/SWGDeviceSetApi.cpp | 54 ++ .../code/qt5/client/SWGDeviceSetApi.h | 6 + .../code/qt5/client/SWGModelFactory.h | 12 + 26 files changed, 1905 insertions(+), 2 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspyReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGDeviceReport.h diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index b6ceca4b9..27f2875b7 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -17,9 +17,12 @@ #include #include #include +#include #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGAirspyReport.h" #include "airspygui.h" #include "airspyinput.h" @@ -699,6 +702,17 @@ int AirspyInput::webapiSettingsPutPatch( return 200; } +int AirspyInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAirspyReport(new SWGSDRangel::SWGAirspyReport()); + response.getAirspyReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + + void AirspyInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspySettings& settings) { response.getAirspySettings()->setCenterFrequency(settings.m_centerFrequency); @@ -723,3 +737,15 @@ void AirspyInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& res response.getAirspySettings()->setFileRecordName(new QString(settings.m_fileRecordName)); } } + +void AirspyInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.setAirspyReport(new SWGSDRangel::SWGAirspyReport()); + response.getAirspyReport()->setSampleRates(new QList); + + for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) + { + response.getAirspyReport()->getSampleRates()->append(new SWGSDRangel::SWGAirspyReport_sampleRates); + response.getAirspyReport()->getSampleRates()->back()->setSampleRate(*it); + } +} diff --git a/plugins/samplesource/airspy/airspyinput.h b/plugins/samplesource/airspy/airspyinput.h index 33bf6552e..f6d4788f2 100644 --- a/plugins/samplesource/airspy/airspyinput.h +++ b/plugins/samplesource/airspy/airspyinput.h @@ -130,6 +130,10 @@ public: SWGSDRangel::SWGDeviceState& response, QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + static const qint64 loLowLimitFreq; static const qint64 loHighLimitFreq; @@ -140,6 +144,7 @@ private: struct airspy_device *open_airspy_from_sequence(int sequence); void setDeviceCenterFrequency(quint64 freq); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspySettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; diff --git a/sdrbase/dsp/devicesamplesink.h b/sdrbase/dsp/devicesamplesink.h index 8ed05e8b2..5666f5e8c 100644 --- a/sdrbase/dsp/devicesamplesink.h +++ b/sdrbase/dsp/devicesamplesink.h @@ -29,6 +29,7 @@ namespace SWGSDRangel { class SWGDeviceSettings; class SWGDeviceState; + class SWGDeviceReport; } class SDRBASE_API DeviceSampleSink : public QObject { @@ -74,6 +75,11 @@ public: QString& errorMessage) { errorMessage = "Not implemented"; return 501; } + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response __attribute__((unused)), + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } diff --git a/sdrbase/dsp/devicesamplesource.h b/sdrbase/dsp/devicesamplesource.h index 40d1ecd9f..60c541d8a 100644 --- a/sdrbase/dsp/devicesamplesource.h +++ b/sdrbase/dsp/devicesamplesource.h @@ -30,6 +30,7 @@ namespace SWGSDRangel { class SWGDeviceSettings; class SWGDeviceState; + class SWGDeviceReport; } class SDRBASE_API DeviceSampleSource : public QObject { @@ -81,6 +82,11 @@ public: QString& errorMessage) { errorMessage = "Not implemented"; return 501; } + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response __attribute__((unused)), + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 4646fb380..7c837c5ce 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -3,6 +3,7 @@ webapi/doc/html2/index.html webapi/doc/swagger/swagger.yaml webapi/doc/swagger/include/CWKeyer.yaml + webapi/doc/swagger/include/Airspy.yaml webapi/doc/swagger/include/AirspyHF.yaml webapi/doc/swagger/include/BladeRF.yaml webapi/doc/swagger/include/FileSource.yaml @@ -17,6 +18,8 @@ webapi/doc/swagger/include/NFMMod.yaml webapi/doc/swagger/include/SSBMod.yaml webapi/doc/swagger/include/UDPSink.yaml + webapi/doc/swagger/include/UDPSrc.yaml + webapi/doc/swagger/include/WFMDemod.yaml webapi/doc/swagger/include/WFMMod.yaml webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger-ui/swagger-ui.js.map diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 1a93cd249..78e660702 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -942,6 +942,25 @@ margin-bottom: 20px; } }, "description" : "AirspyHF" +}; + defs.AirspyReport = { + "properties" : { + "sampleRates" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AirspyReport_sampleRates" + } + } + }, + "description" : "Airspy" +}; + defs.AirspyReport_sampleRates = { + "properties" : { + "sampleRate" : { + "type" : "integer", + "description" : "sample rate in S/s" + } + } }; defs.AirspySettings = { "properties" : { @@ -1663,6 +1682,24 @@ margin-bottom: 20px; } }, "description" : "Summarized information about attached hardware device" +}; + defs.DeviceReport = { + "required" : [ "deviceHwType", "tx" ], + "discriminator" : "deviceHwType", + "properties" : { + "deviceHwType" : { + "type" : "string", + "description" : "Device hardware type code" + }, + "tx" : { + "type" : "integer", + "description" : "Not zero if it is a tx device else it is a rx device" + }, + "airspyReport" : { + "$ref" : "#/definitions/AirspyReport" + } + }, + "description" : "Base device report. The specific device report present depeds on deviceHwType" }; defs.DeviceSet = { "required" : [ "channelcount", "samplingDevice" ], @@ -2988,6 +3025,9 @@ margin-bottom: 20px;
  • devicesetDevicePut
  • +
  • + devicesetDeviceReportGet +
  • devicesetDeviceRunDelete
  • @@ -7189,6 +7229,467 @@ $(document).ready(function() {
    +
    +
    +
    +

    devicesetDeviceReportGet

    +

    +
    +
    +
    +

    +

    get the device report

    +

    +
    +
    /sdrangel/deviceset/{deviceSetIndex}/device/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrangel/deviceset/{deviceSetIndex}/device/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DeviceSetApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        try {
    +            DeviceReport result = apiInstance.devicesetDeviceReportGet(deviceSetIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetDeviceReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DeviceSetApi;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        try {
    +            DeviceReport result = apiInstance.devicesetDeviceReportGet(deviceSetIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetDeviceReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    Integer *deviceSetIndex = 56; // Index of device set in the device set list
    +
    +DeviceSetApi *apiInstance = [[DeviceSetApi alloc] init];
    +
    +[apiInstance devicesetDeviceReportGetWith:deviceSetIndex
    +              completionHandler: ^(DeviceReport output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DeviceSetApi()
    +
    +var deviceSetIndex = 56; // {Integer} Index of device set in the device set list
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.devicesetDeviceReportGet(deviceSetIndex, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class devicesetDeviceReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DeviceSetApi();
    +            var deviceSetIndex = 56;  // Integer | Index of device set in the device set list
    +
    +            try
    +            {
    +                DeviceReport result = apiInstance.devicesetDeviceReportGet(deviceSetIndex);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DeviceSetApi.devicesetDeviceReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DeviceSetApi();
    +$deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +
    +try {
    +    $result = $api_instance->devicesetDeviceReportGet($deviceSetIndex);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DeviceSetApi->devicesetDeviceReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DeviceSetApi;
    +
    +my $api_instance = SWGSDRangel::DeviceSetApi->new();
    +my $deviceSetIndex = 56; # Integer | Index of device set in the device set list
    +
    +eval { 
    +    my $result = $api_instance->devicesetDeviceReportGet(deviceSetIndex => $deviceSetIndex);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DeviceSetApi->devicesetDeviceReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DeviceSetApi()
    +deviceSetIndex = 56 # Integer | Index of device set in the device set list
    +
    +try: 
    +    api_response = api_instance.deviceset_device_report_get(deviceSetIndex)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DeviceSetApi->devicesetDeviceReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + +
    Path parameters
    +

    EhQrr&`50^6N5Eh2OXy9jTC86fe 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 delta 8375 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 VNlGWq+xp6JbNB({PiDRSe*sQYsRaN4 diff --git a/doc/img/ChAnalyzerNG_plugin_scope1.png b/doc/img/ChAnalyzerNG_plugin_scope1.png index 810a357c7d3aee0acf198a262daebeb02dd7d303..fb5c7b80753fc9084ae70381797aa80dd53326fd 100644 GIT binary patch literal 13383 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 literal 11901 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 V{_6J`fA>Bq%Bsl}KYj7;{{VqJBXR%$ diff --git a/doc/img/ChAnalyzerNG_plugin_scope1.xcf b/doc/img/ChAnalyzerNG_plugin_scope1.xcf index 151d063019ca5a2c68628981e491460f286ba372..93c815c43a59342c2d626ebf1e1b0d91237a8efa 100644 GIT binary patch literal 78902 zcmeHQ2|!iF`k!+zi;_!`Sy>u_Tvudsa8IPvas$^uG;_ffm(->hfmJM+zabC@`J z+RT8t<0b@5n4Fx%81weU&$9qsHvocwpBsTXF-sUD;A#YT0g*ss#Z7!Sfxi##^(P|P zka07nO-q_S2YyC@{&&qv8Z#$pe83}<=S&LdbMJsS3b|flojh&K#H5gkvnG!x8n^}4 zYWAE*lam5w&q$s;zO`Sg2@}W7NS-k(pp|qBnKg0jn1HbMVbbNn5v~NaM%1w$qN?Em z7l@B~A}X|yho(=S6EJ!DxJgN~DgKNJ6J{sP(F#bh%aUY@ zF)Fo#a+#Vi=KCEQ(81U(QyFW~fU!0$8RHum>l({gpTmsx&tdF73uB2R8A}??*pv>8 z%~{FV+;13rGLx}1Gh<5;zG4ew=>?3fN4!lDjBUA&v8;iNeLtHCZRydW>BHfdu4eef zBbA<>cR+YV08vf!gUbh?J|F^h6gNHQx{4b&cDv5)r*k7yDK2iTp3Yrg=WYl$()Icl z_ig}nOv;Zgeg8w_NOUR}^}hhzqg4R9>x!77$2v3B#lEz7yCC`}!V(D(J^%Xdx zB0BMo;aVT19y#Tq*>fgOcr+k6X~G;za>m?{X*0$r1&n`m`j}~x$0;PK7ivYDG#O<> zCzc9EZJ}IV=oHeWK4`SB*Ud(n7djeaDW->6ck8Xptig+w3|p6OO=o82vZkBToz`LK z8fGiJYpf=#D;+*py46c|5nV`^$O0=EX0@iTT#;_IrsAo{L3T!I3Csp+I%8JB>RKWN z`Y;ED_%Qn-x>FVk$(xlt3(^`r4W*(&Sy3vD4<%Yl>8(RI(TFBivKCpbVBT(;z)DgP zc@^^k4YAU#-d1P&TB{G$K`y95Tj!?9ZPMM^U4%;m%CW)e5?v^V%i%J)oX!nYOwr7o zHkT-fE*CYAQ}mKu6kvweg=`8oAYNese!iki5n^-M8Ks3Vo67|u;1tEbq%gDu1)y<` z)17iqNNAkjL2~G6C{z(>P83SxGqaKs(CpMUYFxAp3h64Kwoz$ln{3d|QrnnWK0S;Yx4%|QjsAdKpt=uEtHFQ5#E&b@HVY?1UL

    + + + + + + + + +
    NameDescription
    deviceSetIndex* + + +
    +
    +
    + + Integer + + +
    + Index of device set in the device set list +
    +
    +
    + Required +
    +
    +
    +
    + + + + + +

    Responses

    +

    Status: 200 - On success return device report

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + + + +
    @@ -21180,7 +21681,7 @@ except ApiException as e:
    - Generated 2018-05-25T18:55:39.036+02:00 + Generated 2018-05-26T00:44:20.967+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml b/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml index f3c28a03c..ebb14bd7d 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml @@ -39,4 +39,16 @@ AirspySettings: format: int64 fileRecordName: type: string + +AirspyReport: + description: Airspy + properties: + sampleRates: + type: array + items: + properties: + sampleRate: + description: sample rate in S/s + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index bb7a84212..746cffbe1 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -936,6 +936,38 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + + /sdrangel/deviceset/{deviceSetIndex}/device/report: + x-swagger-router-controller: deviceset + get: + description: get the device report + operationId: devicesetDeviceReportGet + tags: + - DeviceSet + parameters: + - in: path + name: deviceSetIndex + type: integer + required: true + description: Index of device set in the device set list + responses: + "200": + description: On success return device report + schema: + $ref: "#/definitions/DeviceReport" + "400": + description: Invalid device set + schema: + $ref: "#/definitions/ErrorResponse" + "404": + description: Device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + /sdrangel/deviceset/{deviceSetIndex}/channels/report: x-swagger-router-controller: deviceset @@ -1734,6 +1766,22 @@ definitions: rtlSdrSettings: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrSettings" + DeviceReport: + description: Base device report. The specific device report present depeds on deviceHwType + discriminator: deviceHwType + required: + - deviceHwType + - tx + properties: + deviceHwType: + description: Device hardware type code + type: string + tx: + description: Not zero if it is a tx device else it is a rx device + type: integer + airspyReport: + $ref: "/doc/swagger/include/Airspy.yaml#/AirspyReport" + ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. discriminator: channelType diff --git a/sdrbase/webapi/webapiadapterinterface.cpp b/sdrbase/webapi/webapiadapterinterface.cpp index 7fdf79c9a..4019e90f9 100644 --- a/sdrbase/webapi/webapiadapterinterface.cpp +++ b/sdrbase/webapi/webapiadapterinterface.cpp @@ -40,6 +40,7 @@ std::regex WebAPIAdapterInterface::devicesetFocusURLRe("^/sdrangel/deviceset/([0 std::regex WebAPIAdapterInterface::devicesetDeviceURLRe("^/sdrangel/deviceset/([0-9]{1,2})/device$"); std::regex WebAPIAdapterInterface::devicesetDeviceSettingsURLRe("^/sdrangel/deviceset/([0-9]{1,2})/device/settings$"); std::regex WebAPIAdapterInterface::devicesetDeviceRunURLRe("^/sdrangel/deviceset/([0-9]{1,2})/device/run"); +std::regex WebAPIAdapterInterface::devicesetDeviceReportURLRe("^/sdrangel/deviceset/([0-9]{1,2})/device/report$"); std::regex WebAPIAdapterInterface::devicesetChannelsReportURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channels/report$"); std::regex WebAPIAdapterInterface::devicesetChannelURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel$"); std::regex WebAPIAdapterInterface::devicesetChannelIndexURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel/([0-9]{1,2})$"); diff --git a/sdrbase/webapi/webapiadapterinterface.h b/sdrbase/webapi/webapiadapterinterface.h index d76dd8492..b7a5ab8ea 100644 --- a/sdrbase/webapi/webapiadapterinterface.h +++ b/sdrbase/webapi/webapiadapterinterface.h @@ -47,6 +47,7 @@ namespace SWGSDRangel class SWGDeviceListItem; class SWGDeviceSettings; class SWGDeviceState; + class SWGDeviceReport; class SWGChannelsDetail; class SWGChannelSettings; class SWGChannelReport; @@ -523,6 +524,20 @@ public: return 501; } + /** + * Handler of /sdrangel/deviceset/{devicesetIndex}/device/report (GET) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * returns the Http status code (default 501: not implemented) + */ + virtual int devicesetDeviceReportGet( + int deviceSetIndex __attribute__((unused)), + SWGSDRangel::SWGDeviceReport& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) + { + error.init(); + *error.getMessage() = QString("Function not implemented"); + return 501; + } + /** * Handler of /sdrangel/deviceset/{devicesetIndex}/channels/report (GET) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels * returns the Http status code (default 501: not implemented) @@ -636,6 +651,7 @@ public: static std::regex devicesetDeviceURLRe; static std::regex devicesetDeviceSettingsURLRe; static std::regex devicesetDeviceRunURLRe; + static std::regex devicesetDeviceReportURLRe; static std::regex devicesetChannelURLRe; static std::regex devicesetChannelIndexURLRe; static std::regex devicesetChannelSettingsURLRe; diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 6a149d513..8fea23fbd 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -38,6 +38,7 @@ #include "SWGPresetExport.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" #include "SWGChannelsDetail.h" #include "SWGChannelSettings.h" #include "SWGChannelReport.h" @@ -122,6 +123,8 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http devicesetDeviceSettingsService(std::string(desc_match[1]), request, response); } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetDeviceRunURLRe)) { devicesetDeviceRunService(std::string(desc_match[1]), request, response); + } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetDeviceReportURLRe)) { + devicesetDeviceReportService(std::string(desc_match[1]), request, response); } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetChannelsReportURLRe)) { devicesetChannelsReportService(std::string(desc_match[1]), request, response); } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetChannelURLRe)) { @@ -1241,6 +1244,43 @@ void WebAPIRequestMapper::devicesetDeviceRunService(const std::string& indexStr, } } +void WebAPIRequestMapper::devicesetDeviceReportService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +{ + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + + if (request.getMethod() == "GET") + { + try + { + SWGSDRangel::SWGDeviceReport normalResponse; + int deviceSetIndex = boost::lexical_cast(indexStr); + int status = m_adapter->devicesetDeviceReportGet(deviceSetIndex, normalResponse, errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + catch (const boost::bad_lexical_cast &e) + { + errorResponse.init(); + *errorResponse.getMessage() = "Wrong integer conversion on device set index"; + response.setStatus(400,"Invalid data"); + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(405,"Invalid HTTP method"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid HTTP method"; + response.write(errorResponse.asJson().toUtf8()); + } +} + void WebAPIRequestMapper::devicesetChannelsReportService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) { SWGSDRangel::SWGErrorResponse errorResponse; diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index 0abb90913..d56aaf451 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -69,6 +69,7 @@ private: void devicesetDeviceService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetDeviceSettingsService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetDeviceRunService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void devicesetDeviceReportService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetChannelsReportService(const std::string& deviceSetIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetChannelService(const std::string& deviceSetIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetChannelIndexService(const std::string& deviceSetIndexStr, const std::string& channelIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); diff --git a/sdrgui/webapi/webapiadaptergui.cpp b/sdrgui/webapi/webapiadaptergui.cpp index 53246a3b8..c78e32d5e 100644 --- a/sdrgui/webapi/webapiadaptergui.cpp +++ b/sdrgui/webapi/webapiadaptergui.cpp @@ -53,6 +53,7 @@ #include "SWGPresetIdentifier.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" #include "SWGChannelsDetail.h" #include "SWGChannelSettings.h" #include "SWGChannelReport.h" @@ -1169,6 +1170,45 @@ int WebAPIAdapterGUI::devicesetDeviceRunDelete( } } + +int WebAPIAdapterGUI::devicesetDeviceReportGet( + int deviceSetIndex, + SWGSDRangel::SWGDeviceReport& response, + SWGSDRangel::SWGErrorResponse& error) +{ + error.init(); + + if ((deviceSetIndex >= 0) && (deviceSetIndex < (int) m_mainWindow.m_deviceUIs.size())) + { + DeviceUISet *deviceSet = m_mainWindow.m_deviceUIs[deviceSetIndex]; + + if (deviceSet->m_deviceSourceEngine) // Rx + { + response.setDeviceHwType(new QString(deviceSet->m_deviceSourceAPI->getHardwareId())); + response.setTx(0); + DeviceSampleSource *source = deviceSet->m_deviceSourceAPI->getSampleSource(); + return source->webapiReportGet(response, *error.getMessage()); + } + else if (deviceSet->m_deviceSinkEngine) // Tx + { + response.setDeviceHwType(new QString(deviceSet->m_deviceSinkAPI->getHardwareId())); + response.setTx(1); + DeviceSampleSink *sink = deviceSet->m_deviceSinkAPI->getSampleSink(); + return sink->webapiReportGet(response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } + } + else + { + *error.getMessage() = QString("There is no device set with index %1").arg(deviceSetIndex); + return 404; + } +} + int WebAPIAdapterGUI::devicesetChannelsReportGet( int deviceSetIndex, SWGSDRangel::SWGChannelsDetail& response, diff --git a/sdrgui/webapi/webapiadaptergui.h b/sdrgui/webapi/webapiadaptergui.h index 0f8fde22e..82cff5749 100644 --- a/sdrgui/webapi/webapiadaptergui.h +++ b/sdrgui/webapi/webapiadaptergui.h @@ -181,6 +181,11 @@ public: SWGSDRangel::SWGDeviceState& response, SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetDeviceReportGet( + int deviceSetIndex, + SWGSDRangel::SWGDeviceReport& response, + SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetChannelsReportGet( int deviceSetIndex, SWGSDRangel::SWGChannelsDetail& response, diff --git a/swagger/sdrangel/api/swagger/include/Airspy.yaml b/swagger/sdrangel/api/swagger/include/Airspy.yaml index f3c28a03c..ebb14bd7d 100644 --- a/swagger/sdrangel/api/swagger/include/Airspy.yaml +++ b/swagger/sdrangel/api/swagger/include/Airspy.yaml @@ -39,4 +39,16 @@ AirspySettings: format: int64 fileRecordName: type: string + +AirspyReport: + description: Airspy + properties: + sampleRates: + type: array + items: + properties: + sampleRate: + description: sample rate in S/s + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index e31ea9c19..9408a65c8 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -936,6 +936,38 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + + /sdrangel/deviceset/{deviceSetIndex}/device/report: + x-swagger-router-controller: deviceset + get: + description: get the device report + operationId: devicesetDeviceReportGet + tags: + - DeviceSet + parameters: + - in: path + name: deviceSetIndex + type: integer + required: true + description: Index of device set in the device set list + responses: + "200": + description: On success return device report + schema: + $ref: "#/definitions/DeviceReport" + "400": + description: Invalid device set + schema: + $ref: "#/definitions/ErrorResponse" + "404": + description: Device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + /sdrangel/deviceset/{deviceSetIndex}/channels/report: x-swagger-router-controller: deviceset @@ -1734,6 +1766,22 @@ definitions: rtlSdrSettings: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrSettings" + DeviceReport: + description: Base device report. The specific device report present depeds on deviceHwType + discriminator: deviceHwType + required: + - deviceHwType + - tx + properties: + deviceHwType: + description: Device hardware type code + type: string + tx: + description: Not zero if it is a tx device else it is a rx device + type: integer + airspyReport: + $ref: "http://localhost:8081/api/swagger/include/Airspy.yaml#/AirspyReport" + ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. discriminator: channelType diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 1a93cd249..78e660702 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -942,6 +942,25 @@ margin-bottom: 20px; } }, "description" : "AirspyHF" +}; + defs.AirspyReport = { + "properties" : { + "sampleRates" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AirspyReport_sampleRates" + } + } + }, + "description" : "Airspy" +}; + defs.AirspyReport_sampleRates = { + "properties" : { + "sampleRate" : { + "type" : "integer", + "description" : "sample rate in S/s" + } + } }; defs.AirspySettings = { "properties" : { @@ -1663,6 +1682,24 @@ margin-bottom: 20px; } }, "description" : "Summarized information about attached hardware device" +}; + defs.DeviceReport = { + "required" : [ "deviceHwType", "tx" ], + "discriminator" : "deviceHwType", + "properties" : { + "deviceHwType" : { + "type" : "string", + "description" : "Device hardware type code" + }, + "tx" : { + "type" : "integer", + "description" : "Not zero if it is a tx device else it is a rx device" + }, + "airspyReport" : { + "$ref" : "#/definitions/AirspyReport" + } + }, + "description" : "Base device report. The specific device report present depeds on deviceHwType" }; defs.DeviceSet = { "required" : [ "channelcount", "samplingDevice" ], @@ -2988,6 +3025,9 @@ margin-bottom: 20px;
  • devicesetDevicePut
  • +
  • + devicesetDeviceReportGet +
  • devicesetDeviceRunDelete
  • @@ -7189,6 +7229,467 @@ $(document).ready(function() {
    +
    +
    +
    +

    devicesetDeviceReportGet

    +

    +
    +
    +
    +

    +

    get the device report

    +

    +
    +
    /sdrangel/deviceset/{deviceSetIndex}/device/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrangel/deviceset/{deviceSetIndex}/device/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DeviceSetApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        try {
    +            DeviceReport result = apiInstance.devicesetDeviceReportGet(deviceSetIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetDeviceReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DeviceSetApi;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        try {
    +            DeviceReport result = apiInstance.devicesetDeviceReportGet(deviceSetIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetDeviceReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    Integer *deviceSetIndex = 56; // Index of device set in the device set list
    +
    +DeviceSetApi *apiInstance = [[DeviceSetApi alloc] init];
    +
    +[apiInstance devicesetDeviceReportGetWith:deviceSetIndex
    +              completionHandler: ^(DeviceReport output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DeviceSetApi()
    +
    +var deviceSetIndex = 56; // {Integer} Index of device set in the device set list
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.devicesetDeviceReportGet(deviceSetIndex, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class devicesetDeviceReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DeviceSetApi();
    +            var deviceSetIndex = 56;  // Integer | Index of device set in the device set list
    +
    +            try
    +            {
    +                DeviceReport result = apiInstance.devicesetDeviceReportGet(deviceSetIndex);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DeviceSetApi.devicesetDeviceReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DeviceSetApi();
    +$deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +
    +try {
    +    $result = $api_instance->devicesetDeviceReportGet($deviceSetIndex);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DeviceSetApi->devicesetDeviceReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DeviceSetApi;
    +
    +my $api_instance = SWGSDRangel::DeviceSetApi->new();
    +my $deviceSetIndex = 56; # Integer | Index of device set in the device set list
    +
    +eval { 
    +    my $result = $api_instance->devicesetDeviceReportGet(deviceSetIndex => $deviceSetIndex);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DeviceSetApi->devicesetDeviceReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DeviceSetApi()
    +deviceSetIndex = 56 # Integer | Index of device set in the device set list
    +
    +try: 
    +    api_response = api_instance.deviceset_device_report_get(deviceSetIndex)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DeviceSetApi->devicesetDeviceReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + +
    Path parameters
    + + + + + + + + + +
    NameDescription
    deviceSetIndex* + + +
    +
    +
    + + Integer + + +
    + Index of device set in the device set list +
    +
    +
    + Required +
    +
    +
    +
    + + + + + +

    Responses

    +

    Status: 200 - On success return device report

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -21180,7 +21681,7 @@ except ApiException as e:
    - Generated 2018-05-25T18:55:39.036+02:00 + Generated 2018-05-26T00:44:20.967+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp new file mode 100644 index 000000000..21bd1abf5 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp @@ -0,0 +1,112 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAirspyReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAirspyReport::SWGAirspyReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAirspyReport::SWGAirspyReport() { + sample_rates = nullptr; + m_sample_rates_isSet = false; +} + +SWGAirspyReport::~SWGAirspyReport() { + this->cleanup(); +} + +void +SWGAirspyReport::init() { + sample_rates = new QList(); + m_sample_rates_isSet = false; +} + +void +SWGAirspyReport::cleanup() { + if(sample_rates != nullptr) { + auto arr = sample_rates; + for(auto o: *arr) { + delete o; + } + delete sample_rates; + } +} + +SWGAirspyReport* +SWGAirspyReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAirspyReport::fromJsonObject(QJsonObject &pJson) { + + ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGAirspyReport_sampleRates"); +} + +QString +SWGAirspyReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAirspyReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(sample_rates->size() > 0){ + toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGAirspyReport_sampleRates"); + } + + return obj; +} + +QList* +SWGAirspyReport::getSampleRates() { + return sample_rates; +} +void +SWGAirspyReport::setSampleRates(QList* sample_rates) { + this->sample_rates = sample_rates; + this->m_sample_rates_isSet = true; +} + + +bool +SWGAirspyReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(sample_rates->size() > 0){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h new file mode 100644 index 000000000..290307372 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h @@ -0,0 +1,60 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAirspyReport.h + * + * Airspy + */ + +#ifndef SWGAirspyReport_H_ +#define SWGAirspyReport_H_ + +#include + + +#include "SWGAirspyReport_sampleRates.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAirspyReport: public SWGObject { +public: + SWGAirspyReport(); + SWGAirspyReport(QString* json); + virtual ~SWGAirspyReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAirspyReport* fromJson(QString &jsonString) override; + + QList* getSampleRates(); + void setSampleRates(QList* sample_rates); + + + virtual bool isSet() override; + +private: + QList* sample_rates; + bool m_sample_rates_isSet; + +}; + +} + +#endif /* SWGAirspyReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.cpp new file mode 100644 index 000000000..66dcde762 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAirspyReport_sampleRates.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAirspyReport_sampleRates::SWGAirspyReport_sampleRates(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAirspyReport_sampleRates::SWGAirspyReport_sampleRates() { + sample_rate = 0; + m_sample_rate_isSet = false; +} + +SWGAirspyReport_sampleRates::~SWGAirspyReport_sampleRates() { + this->cleanup(); +} + +void +SWGAirspyReport_sampleRates::init() { + sample_rate = 0; + m_sample_rate_isSet = false; +} + +void +SWGAirspyReport_sampleRates::cleanup() { + +} + +SWGAirspyReport_sampleRates* +SWGAirspyReport_sampleRates::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAirspyReport_sampleRates::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + +} + +QString +SWGAirspyReport_sampleRates::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAirspyReport_sampleRates::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + + return obj; +} + +qint32 +SWGAirspyReport_sampleRates::getSampleRate() { + return sample_rate; +} +void +SWGAirspyReport_sampleRates::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + + +bool +SWGAirspyReport_sampleRates::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.h b/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.h new file mode 100644 index 000000000..47c080614 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAirspyReport_sampleRates.h + * + * + */ + +#ifndef SWGAirspyReport_sampleRates_H_ +#define SWGAirspyReport_sampleRates_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAirspyReport_sampleRates: public SWGObject { +public: + SWGAirspyReport_sampleRates(); + SWGAirspyReport_sampleRates(QString* json); + virtual ~SWGAirspyReport_sampleRates(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAirspyReport_sampleRates* fromJson(QString &jsonString) override; + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + + virtual bool isSet() override; + +private: + qint32 sample_rate; + bool m_sample_rate_isSet; + +}; + +} + +#endif /* SWGAirspyReport_sampleRates_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp new file mode 100644 index 000000000..f1212ae30 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -0,0 +1,152 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGDeviceReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDeviceReport::SWGDeviceReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDeviceReport::SWGDeviceReport() { + device_hw_type = nullptr; + m_device_hw_type_isSet = false; + tx = 0; + m_tx_isSet = false; + airspy_report = nullptr; + m_airspy_report_isSet = false; +} + +SWGDeviceReport::~SWGDeviceReport() { + this->cleanup(); +} + +void +SWGDeviceReport::init() { + device_hw_type = new QString(""); + m_device_hw_type_isSet = false; + tx = 0; + m_tx_isSet = false; + airspy_report = new SWGAirspyReport(); + m_airspy_report_isSet = false; +} + +void +SWGDeviceReport::cleanup() { + if(device_hw_type != nullptr) { + delete device_hw_type; + } + + if(airspy_report != nullptr) { + delete airspy_report; + } +} + +SWGDeviceReport* +SWGDeviceReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&device_hw_type, pJson["deviceHwType"], "QString", "QString"); + + ::SWGSDRangel::setValue(&tx, pJson["tx"], "qint32", ""); + + ::SWGSDRangel::setValue(&airspy_report, pJson["airspyReport"], "SWGAirspyReport", "SWGAirspyReport"); + +} + +QString +SWGDeviceReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDeviceReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(device_hw_type != nullptr && *device_hw_type != QString("")){ + toJsonValue(QString("deviceHwType"), device_hw_type, obj, QString("QString")); + } + if(m_tx_isSet){ + obj->insert("tx", QJsonValue(tx)); + } + if((airspy_report != nullptr) && (airspy_report->isSet())){ + toJsonValue(QString("airspyReport"), airspy_report, obj, QString("SWGAirspyReport")); + } + + return obj; +} + +QString* +SWGDeviceReport::getDeviceHwType() { + return device_hw_type; +} +void +SWGDeviceReport::setDeviceHwType(QString* device_hw_type) { + this->device_hw_type = device_hw_type; + this->m_device_hw_type_isSet = true; +} + +qint32 +SWGDeviceReport::getTx() { + return tx; +} +void +SWGDeviceReport::setTx(qint32 tx) { + this->tx = tx; + this->m_tx_isSet = true; +} + +SWGAirspyReport* +SWGDeviceReport::getAirspyReport() { + return airspy_report; +} +void +SWGDeviceReport::setAirspyReport(SWGAirspyReport* airspy_report) { + this->airspy_report = airspy_report; + this->m_airspy_report_isSet = true; +} + + +bool +SWGDeviceReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(device_hw_type != nullptr && *device_hw_type != QString("")){ isObjectUpdated = true; break;} + if(m_tx_isSet){ isObjectUpdated = true; break;} + if(airspy_report != nullptr && airspy_report->isSet()){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h new file mode 100644 index 000000000..7f9bb02f0 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -0,0 +1,72 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGDeviceReport.h + * + * Base device report. The specific device report present depeds on deviceHwType + */ + +#ifndef SWGDeviceReport_H_ +#define SWGDeviceReport_H_ + +#include + + +#include "SWGAirspyReport.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDeviceReport: public SWGObject { +public: + SWGDeviceReport(); + SWGDeviceReport(QString* json); + virtual ~SWGDeviceReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDeviceReport* fromJson(QString &jsonString) override; + + QString* getDeviceHwType(); + void setDeviceHwType(QString* device_hw_type); + + qint32 getTx(); + void setTx(qint32 tx); + + SWGAirspyReport* getAirspyReport(); + void setAirspyReport(SWGAirspyReport* airspy_report); + + + virtual bool isSet() override; + +private: + QString* device_hw_type; + bool m_device_hw_type_isSet; + + qint32 tx; + bool m_tx_isSet; + + SWGAirspyReport* airspy_report; + bool m_airspy_report_isSet; + +}; + +} + +#endif /* SWGDeviceReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp index bb41cf948..44a4418b8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp @@ -482,6 +482,60 @@ SWGDeviceSetApi::devicesetDevicePutCallback(SWGHttpRequestWorker * worker) { } } +void +SWGDeviceSetApi::devicesetDeviceReportGet(qint32 device_set_index) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrangel/deviceset/{deviceSetIndex}/device/report"); + + QString device_set_indexPathParam("{"); device_set_indexPathParam.append("deviceSetIndex").append("}"); + fullPath.replace(device_set_indexPathParam, stringValue(device_set_index)); + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "GET"); + + + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDeviceSetApi::devicesetDeviceReportGetCallback); + + worker->execute(&input); +} + +void +SWGDeviceSetApi::devicesetDeviceReportGetCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGDeviceReport* output = static_cast(create(json, QString("SWGDeviceReport"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit devicesetDeviceReportGetSignal(output); + } else { + emit devicesetDeviceReportGetSignalE(output, error_type, error_str); + emit devicesetDeviceReportGetSignalEFull(worker, error_type, error_str); + } +} + void SWGDeviceSetApi::devicesetDeviceRunDelete(qint32 device_set_index) { QString fullPath; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h index 678114871..d42e88e5c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h @@ -19,6 +19,7 @@ #include "SWGChannelSettings.h" #include "SWGChannelsDetail.h" #include "SWGDeviceListItem.h" +#include "SWGDeviceReport.h" #include "SWGDeviceSet.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" @@ -49,6 +50,7 @@ public: void devicesetChannelSettingsPut(qint32 device_set_index, qint32 channel_index, SWGChannelSettings& body); void devicesetChannelsReportGet(qint32 device_set_index); void devicesetDevicePut(qint32 device_set_index, SWGDeviceListItem& body); + void devicesetDeviceReportGet(qint32 device_set_index); void devicesetDeviceRunDelete(qint32 device_set_index); void devicesetDeviceRunGet(qint32 device_set_index); void devicesetDeviceRunPost(qint32 device_set_index); @@ -69,6 +71,7 @@ private: void devicesetChannelSettingsPutCallback (SWGHttpRequestWorker * worker); void devicesetChannelsReportGetCallback (SWGHttpRequestWorker * worker); void devicesetDevicePutCallback (SWGHttpRequestWorker * worker); + void devicesetDeviceReportGetCallback (SWGHttpRequestWorker * worker); void devicesetDeviceRunDeleteCallback (SWGHttpRequestWorker * worker); void devicesetDeviceRunGetCallback (SWGHttpRequestWorker * worker); void devicesetDeviceRunPostCallback (SWGHttpRequestWorker * worker); @@ -89,6 +92,7 @@ signals: void devicesetChannelSettingsPutSignal(SWGChannelSettings* summary); void devicesetChannelsReportGetSignal(SWGChannelsDetail* summary); void devicesetDevicePutSignal(SWGDeviceListItem* summary); + void devicesetDeviceReportGetSignal(SWGDeviceReport* summary); void devicesetDeviceRunDeleteSignal(SWGDeviceState* summary); void devicesetDeviceRunGetSignal(SWGDeviceState* summary); void devicesetDeviceRunPostSignal(SWGDeviceState* summary); @@ -108,6 +112,7 @@ signals: void devicesetChannelSettingsPutSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelsReportGetSignalE(SWGChannelsDetail* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDevicePutSignalE(SWGDeviceListItem* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void devicesetDeviceReportGetSignalE(SWGDeviceReport* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunDeleteSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunGetSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunPostSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); @@ -127,6 +132,7 @@ signals: void devicesetChannelSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelsReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDevicePutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void devicesetDeviceReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunPostSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index c8f0a18b1..6aae27271 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -21,6 +21,8 @@ #include "SWGATVModReport.h" #include "SWGATVModSettings.h" #include "SWGAirspyHFSettings.h" +#include "SWGAirspyReport.h" +#include "SWGAirspyReport_sampleRates.h" #include "SWGAirspySettings.h" #include "SWGAudioDevices.h" #include "SWGAudioInputDevice.h" @@ -40,6 +42,7 @@ #include "SWGDVSeralDevices.h" #include "SWGDVSerialDevice.h" #include "SWGDeviceListItem.h" +#include "SWGDeviceReport.h" #include "SWGDeviceSet.h" #include "SWGDeviceSetList.h" #include "SWGDeviceSettings.h" @@ -106,6 +109,12 @@ namespace SWGSDRangel { if(QString("SWGAirspyHFSettings").compare(type) == 0) { return new SWGAirspyHFSettings(); } + if(QString("SWGAirspyReport").compare(type) == 0) { + return new SWGAirspyReport(); + } + if(QString("SWGAirspyReport_sampleRates").compare(type) == 0) { + return new SWGAirspyReport_sampleRates(); + } if(QString("SWGAirspySettings").compare(type) == 0) { return new SWGAirspySettings(); } @@ -163,6 +172,9 @@ namespace SWGSDRangel { if(QString("SWGDeviceListItem").compare(type) == 0) { return new SWGDeviceListItem(); } + if(QString("SWGDeviceReport").compare(type) == 0) { + return new SWGDeviceReport(); + } if(QString("SWGDeviceSet").compare(type) == 0) { return new SWGDeviceSet(); } From 1fda69c667f71c8e732df22d8fa2b25087152ab2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 02:01:59 +0200 Subject: [PATCH 460/956] Web API: Airspy fixes --- plugins/samplesource/airspy/airspyinput.cpp | 4 +++- swagger/sdrangel/examples/rx_test.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 27f2875b7..da89e98a0 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -668,7 +668,9 @@ int AirspyInput::webapiSettingsPutPatch( settings.m_log2Decim = response.getAirspySettings()->getLog2Decim(); } if (deviceSettingsKeys.contains("fcPos")) { - settings.m_fcPos = (AirspySettings::fcPos_t) response.getAirspySettings()->getFcPos(); + int fcPos = response.getAirspySettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (AirspySettings::fcPos_t) fcPos; } if (deviceSettingsKeys.contains("biasT")) { settings.m_biasT = response.getAirspySettings()->getBiasT() != 0; diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index fe854f05a..b9b904f4b 100644 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -32,7 +32,7 @@ def getInputOptions(): parser.add_option("--vol", dest="volume", help="audio volume", metavar="VOLUME", type="float", default=1.0) parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="CREATE", action="store_true", default=False) parser.add_option("--ppm", dest="lo_ppm", help="LO correction in PPM", metavar="PPM", type="float", default=0.0) - parser.add_option("--fc-pos", dest="fc_pos", help="Center frequency position 0:inf 1:sup 2:cen", metavar="ENUM", default=2) + parser.add_option("--fc-pos", dest="fc_pos", help="Center frequency position 0:inf 1:sup 2:cen", metavar="ENUM", type="int", default=2) parser.add_option("--sq", dest="squelch_db", help="Squelsch threshold in dB", metavar="DECIBEL", type="float", default=-50.0) parser.add_option("--sq-gate", dest="squelch_gate", help="Squelsch gate in ms", metavar="MILLISECONDS", type="int", default=50) parser.add_option("--stereo", dest="stereo", help="Broadcast FM stereo", metavar="BOOL", action="store_true", default=False) @@ -98,6 +98,7 @@ def setupDevice(deviceset_url, options): exit(-1) if options.device_hwid == "Airspy": + settings['airspySettings']['LOppmTenths'] = int(options.lo_ppm * 10) # in tenths of PPM settings["airspySettings"]["centerFrequency"] = options.device_freq*1000 settings["airspySettings"]["devSampleRateIndex"] = 1 settings['airspySettings']['log2Decim'] = options.log2_decim From 7767d538f639106d582ec7913e841ad3bfd46c19 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 02:17:49 +0200 Subject: [PATCH 461/956] BladeRF: cleanup: removed useless fixed sample rates support --- devices/bladerf/devicebladerf.cpp | 55 ------------------------------- devices/bladerf/devicebladerf.h | 10 ------ 2 files changed, 65 deletions(-) diff --git a/devices/bladerf/devicebladerf.cpp b/devices/bladerf/devicebladerf.cpp index ffa065cb4..ff88ed756 100644 --- a/devices/bladerf/devicebladerf.cpp +++ b/devices/bladerf/devicebladerf.cpp @@ -84,61 +84,6 @@ struct bladerf *DeviceBladeRF::open_bladerf_from_serial(const char *serial) } } -const unsigned int BladerfSampleRates::m_nb_rates = 22; -const unsigned int BladerfSampleRates::m_rates[BladerfSampleRates::m_nb_rates] = { - 1536000, - 1600000, - 2000000, - 2304000, - 2400000, - 3072000, - 3200000, - 4333333, // for GSM - 4608000, - 4800000, - 6144000, - 7680000, - 9216000, - 9600000, - 10752000, - 12288000, - 18432000, - 19200000, - 24576000, - 30720000, - 36864000, - 39936000}; - -unsigned int BladerfSampleRates::getRate(unsigned int rate_index) -{ - if (rate_index < m_nb_rates) - { - return m_rates[rate_index]; - } - else - { - return m_rates[0]; - } -} - -unsigned int BladerfSampleRates::getRateIndex(unsigned int rate) -{ - for (unsigned int i=0; i < m_nb_rates; i++) - { - if (rate == m_rates[i]) - { - return i; - } - } - - return 0; -} - -unsigned int BladerfSampleRates::getNbRates() -{ - return BladerfSampleRates::m_nb_rates; -} - const unsigned int BladerfBandwidths::m_nb_halfbw = 16; const unsigned int BladerfBandwidths::m_halfbw[BladerfBandwidths::m_nb_halfbw] = { 750, diff --git a/devices/bladerf/devicebladerf.h b/devices/bladerf/devicebladerf.h index 20c2bab5a..09751ea93 100644 --- a/devices/bladerf/devicebladerf.h +++ b/devices/bladerf/devicebladerf.h @@ -30,16 +30,6 @@ private: static struct bladerf *open_bladerf_from_serial(const char *serial); }; -class BladerfSampleRates { -public: - static unsigned int getRate(unsigned int rate_index); - static unsigned int getRateIndex(unsigned int rate); - static unsigned int getNbRates(); -private: - static const unsigned int m_nb_rates; - static const unsigned int m_rates[]; -}; - class BladerfBandwidths { public: static unsigned int getBandwidth(unsigned int bandwidth_index); From 30cca075547b90a9da1ee2722fc76828bd59ef98 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 02:33:35 +0200 Subject: [PATCH 462/956] Web API: AirspyHF device report --- plugins/samplesource/airspy/airspyinput.cpp | 1 - .../samplesource/airspyhf/airspyhfinput.cpp | 23 ++++ plugins/samplesource/airspyhf/airspyhfinput.h | 5 + sdrbase/resources/webapi/doc/html2/index.html | 16 ++- .../webapi/doc/swagger/include/AirspyHF.yaml | 14 ++- .../resources/webapi/doc/swagger/swagger.yaml | 2 + .../api/swagger/include/AirspyHF.yaml | 14 ++- swagger/sdrangel/api/swagger/swagger.yaml | 2 + swagger/sdrangel/code/html2/index.html | 16 ++- .../code/qt5/client/SWGAirspyHFReport.cpp | 112 ++++++++++++++++++ .../code/qt5/client/SWGAirspyHFReport.h | 60 ++++++++++ .../code/qt5/client/SWGDeviceReport.cpp | 23 ++++ .../code/qt5/client/SWGDeviceReport.h | 7 ++ .../code/qt5/client/SWGModelFactory.h | 4 + 14 files changed, 294 insertions(+), 5 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index da89e98a0..0beea475e 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -742,7 +742,6 @@ void AirspyInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& res void AirspyInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { - response.setAirspyReport(new SWGSDRangel::SWGAirspyReport()); response.getAirspyReport()->setSampleRates(new QList); for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index b076ed10c..0009ddc8f 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -20,6 +20,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGAirspyHFReport.h" #include #include @@ -561,6 +563,27 @@ void AirspyHFInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& r } } +void AirspyHFInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getAirspyHfReport()->setSampleRates(new QList); + + for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) + { + response.getAirspyHfReport()->getSampleRates()->append(new SWGSDRangel::SWGAirspyReport_sampleRates); + response.getAirspyHfReport()->getSampleRates()->back()->setSampleRate(*it); + } +} + +int AirspyHFInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAirspyHfReport(new SWGSDRangel::SWGAirspyHFReport()); + response.getAirspyHfReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + int AirspyHFInput::webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage __attribute__((unused))) diff --git a/plugins/samplesource/airspyhf/airspyhfinput.h b/plugins/samplesource/airspyhf/airspyhfinput.h index 4392841c0..bb5e5e213 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.h +++ b/plugins/samplesource/airspyhf/airspyhfinput.h @@ -122,6 +122,10 @@ public: SWGSDRangel::SWGDeviceSettings& response, // query + response QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -143,6 +147,7 @@ private: airspyhf_device_t *open_airspyhf_from_serial(const QString& serialStr); void setDeviceCenterFrequency(quint64 freq, const AirspyHFSettings& settings); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspyHFSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 78e660702..5a04f8ddb 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -911,6 +911,17 @@ margin-bottom: 20px; } }, "description" : "ATVMod" +}; + defs.AirspyHFReport = { + "properties" : { + "sampleRates" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AirspyReport_sampleRates" + } + } + }, + "description" : "AirspyHF" }; defs.AirspyHFSettings = { "properties" : { @@ -1697,6 +1708,9 @@ margin-bottom: 20px; }, "airspyReport" : { "$ref" : "#/definitions/AirspyReport" + }, + "airspyHFReport" : { + "$ref" : "#/definitions/AirspyHFReport" } }, "description" : "Base device report. The specific device report present depeds on deviceHwType" @@ -21681,7 +21695,7 @@ except ApiException as e:
    - Generated 2018-05-26T00:44:20.967+02:00 + Generated 2018-05-26T02:22:55.108+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml b/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml index 0ec9d4f93..e0317e414 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml @@ -19,4 +19,16 @@ AirspyHFSettings: type: integer fileRecordName: type: string - \ No newline at end of file + +AirspyHFReport: + description: AirspyHF + properties: + sampleRates: + type: array + items: + properties: + sampleRate: + description: sample rate in S/s + type: integer + + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 746cffbe1..62f07987c 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1781,6 +1781,8 @@ definitions: type: integer airspyReport: $ref: "/doc/swagger/include/Airspy.yaml#/AirspyReport" + airspyHFReport: + $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/api/swagger/include/AirspyHF.yaml b/swagger/sdrangel/api/swagger/include/AirspyHF.yaml index 0ec9d4f93..e0317e414 100644 --- a/swagger/sdrangel/api/swagger/include/AirspyHF.yaml +++ b/swagger/sdrangel/api/swagger/include/AirspyHF.yaml @@ -19,4 +19,16 @@ AirspyHFSettings: type: integer fileRecordName: type: string - \ No newline at end of file + +AirspyHFReport: + description: AirspyHF + properties: + sampleRates: + type: array + items: + properties: + sampleRate: + description: sample rate in S/s + type: integer + + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 9408a65c8..1c6fb1704 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1781,6 +1781,8 @@ definitions: type: integer airspyReport: $ref: "http://localhost:8081/api/swagger/include/Airspy.yaml#/AirspyReport" + airspyHFReport: + $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 78e660702..5a04f8ddb 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -911,6 +911,17 @@ margin-bottom: 20px; } }, "description" : "ATVMod" +}; + defs.AirspyHFReport = { + "properties" : { + "sampleRates" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AirspyReport_sampleRates" + } + } + }, + "description" : "AirspyHF" }; defs.AirspyHFSettings = { "properties" : { @@ -1697,6 +1708,9 @@ margin-bottom: 20px; }, "airspyReport" : { "$ref" : "#/definitions/AirspyReport" + }, + "airspyHFReport" : { + "$ref" : "#/definitions/AirspyHFReport" } }, "description" : "Base device report. The specific device report present depeds on deviceHwType" @@ -21681,7 +21695,7 @@ except ApiException as e:
    - Generated 2018-05-26T00:44:20.967+02:00 + Generated 2018-05-26T02:22:55.108+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp new file mode 100644 index 000000000..e7e1da594 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp @@ -0,0 +1,112 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAirspyHFReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAirspyHFReport::SWGAirspyHFReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAirspyHFReport::SWGAirspyHFReport() { + sample_rates = nullptr; + m_sample_rates_isSet = false; +} + +SWGAirspyHFReport::~SWGAirspyHFReport() { + this->cleanup(); +} + +void +SWGAirspyHFReport::init() { + sample_rates = new QList(); + m_sample_rates_isSet = false; +} + +void +SWGAirspyHFReport::cleanup() { + if(sample_rates != nullptr) { + auto arr = sample_rates; + for(auto o: *arr) { + delete o; + } + delete sample_rates; + } +} + +SWGAirspyHFReport* +SWGAirspyHFReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAirspyHFReport::fromJsonObject(QJsonObject &pJson) { + + ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGAirspyReport_sampleRates"); +} + +QString +SWGAirspyHFReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAirspyHFReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(sample_rates->size() > 0){ + toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGAirspyReport_sampleRates"); + } + + return obj; +} + +QList* +SWGAirspyHFReport::getSampleRates() { + return sample_rates; +} +void +SWGAirspyHFReport::setSampleRates(QList* sample_rates) { + this->sample_rates = sample_rates; + this->m_sample_rates_isSet = true; +} + + +bool +SWGAirspyHFReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(sample_rates->size() > 0){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h new file mode 100644 index 000000000..668dd2c50 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h @@ -0,0 +1,60 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAirspyHFReport.h + * + * AirspyHF + */ + +#ifndef SWGAirspyHFReport_H_ +#define SWGAirspyHFReport_H_ + +#include + + +#include "SWGAirspyReport_sampleRates.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAirspyHFReport: public SWGObject { +public: + SWGAirspyHFReport(); + SWGAirspyHFReport(QString* json); + virtual ~SWGAirspyHFReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAirspyHFReport* fromJson(QString &jsonString) override; + + QList* getSampleRates(); + void setSampleRates(QList* sample_rates); + + + virtual bool isSet() override; + +private: + QList* sample_rates; + bool m_sample_rates_isSet; + +}; + +} + +#endif /* SWGAirspyHFReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index f1212ae30..8eead58ba 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -34,6 +34,8 @@ SWGDeviceReport::SWGDeviceReport() { m_tx_isSet = false; airspy_report = nullptr; m_airspy_report_isSet = false; + airspy_hf_report = nullptr; + m_airspy_hf_report_isSet = false; } SWGDeviceReport::~SWGDeviceReport() { @@ -48,6 +50,8 @@ SWGDeviceReport::init() { m_tx_isSet = false; airspy_report = new SWGAirspyReport(); m_airspy_report_isSet = false; + airspy_hf_report = new SWGAirspyHFReport(); + m_airspy_hf_report_isSet = false; } void @@ -59,6 +63,9 @@ SWGDeviceReport::cleanup() { if(airspy_report != nullptr) { delete airspy_report; } + if(airspy_hf_report != nullptr) { + delete airspy_hf_report; + } } SWGDeviceReport* @@ -78,6 +85,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&airspy_report, pJson["airspyReport"], "SWGAirspyReport", "SWGAirspyReport"); + ::SWGSDRangel::setValue(&airspy_hf_report, pJson["airspyHFReport"], "SWGAirspyHFReport", "SWGAirspyHFReport"); + } QString @@ -103,6 +112,9 @@ SWGDeviceReport::asJsonObject() { if((airspy_report != nullptr) && (airspy_report->isSet())){ toJsonValue(QString("airspyReport"), airspy_report, obj, QString("SWGAirspyReport")); } + if((airspy_hf_report != nullptr) && (airspy_hf_report->isSet())){ + toJsonValue(QString("airspyHFReport"), airspy_hf_report, obj, QString("SWGAirspyHFReport")); + } return obj; } @@ -137,6 +149,16 @@ SWGDeviceReport::setAirspyReport(SWGAirspyReport* airspy_report) { this->m_airspy_report_isSet = true; } +SWGAirspyHFReport* +SWGDeviceReport::getAirspyHfReport() { + return airspy_hf_report; +} +void +SWGDeviceReport::setAirspyHfReport(SWGAirspyHFReport* airspy_hf_report) { + this->airspy_hf_report = airspy_hf_report; + this->m_airspy_hf_report_isSet = true; +} + bool SWGDeviceReport::isSet(){ @@ -145,6 +167,7 @@ SWGDeviceReport::isSet(){ if(device_hw_type != nullptr && *device_hw_type != QString("")){ isObjectUpdated = true; break;} if(m_tx_isSet){ isObjectUpdated = true; break;} if(airspy_report != nullptr && airspy_report->isSet()){ isObjectUpdated = true; break;} + if(airspy_hf_report != nullptr && airspy_hf_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index 7f9bb02f0..8b7b0f1b3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -22,6 +22,7 @@ #include +#include "SWGAirspyHFReport.h" #include "SWGAirspyReport.h" #include @@ -52,6 +53,9 @@ public: SWGAirspyReport* getAirspyReport(); void setAirspyReport(SWGAirspyReport* airspy_report); + SWGAirspyHFReport* getAirspyHfReport(); + void setAirspyHfReport(SWGAirspyHFReport* airspy_hf_report); + virtual bool isSet() override; @@ -65,6 +69,9 @@ private: SWGAirspyReport* airspy_report; bool m_airspy_report_isSet; + SWGAirspyHFReport* airspy_hf_report; + bool m_airspy_hf_report_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 6aae27271..0e413ef74 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -20,6 +20,7 @@ #include "SWGAMModSettings.h" #include "SWGATVModReport.h" #include "SWGATVModSettings.h" +#include "SWGAirspyHFReport.h" #include "SWGAirspyHFSettings.h" #include "SWGAirspyReport.h" #include "SWGAirspyReport_sampleRates.h" @@ -106,6 +107,9 @@ namespace SWGSDRangel { if(QString("SWGATVModSettings").compare(type) == 0) { return new SWGATVModSettings(); } + if(QString("SWGAirspyHFReport").compare(type) == 0) { + return new SWGAirspyHFReport(); + } if(QString("SWGAirspyHFSettings").compare(type) == 0) { return new SWGAirspyHFSettings(); } From 4a5369c1b4e0cde3a96ea7b35ca7163d67d4a5e3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 10:27:28 +0200 Subject: [PATCH 463/956] FCD Pro input: implemeted WEB API --- fcdlib/fcdtraits.cpp | 4 +- plugins/samplesource/airspy/airspyplugin.cpp | 2 +- .../samplesource/airspyhf/airspyhfplugin.cpp | 2 +- plugins/samplesource/fcdpro/fcdprogui.cpp | 6 +- plugins/samplesource/fcdpro/fcdproinput.cpp | 147 ++++- plugins/samplesource/fcdpro/fcdproinput.h | 20 +- sdrbase/resources/webapi/doc/html2/index.html | 81 ++- .../webapi/doc/swagger/include/FCDPro.yaml | 54 ++ .../resources/webapi/doc/swagger/swagger.yaml | 2 + .../sdrangel/api/swagger/include/FCDPro.yaml | 52 ++ swagger/sdrangel/api/swagger/swagger.yaml | 2 + swagger/sdrangel/code/html2/index.html | 81 ++- .../code/qt5/client/SWGDeviceSettings.cpp | 23 + .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGFCDProSettings.cpp | 570 ++++++++++++++++++ .../code/qt5/client/SWGFCDProSettings.h | 191 ++++++ .../code/qt5/client/SWGModelFactory.h | 4 + 17 files changed, 1228 insertions(+), 20 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/FCDPro.yaml create mode 100644 swagger/sdrangel/api/swagger/include/FCDPro.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h diff --git a/fcdlib/fcdtraits.cpp b/fcdlib/fcdtraits.cpp index 9c5ea4af1..41f8b66a6 100644 --- a/fcdlib/fcdtraits.cpp +++ b/fcdlib/fcdtraits.cpp @@ -22,8 +22,8 @@ const char *fcd_traits::displayedName = "FunCube Dongle Pro+"; const char *fcd_traits::pluginDisplayedName = "FunCube Pro Input"; const char *fcd_traits::pluginDisplayedName = "FunCube Pro+ Input"; -const char *fcd_traits::pluginVersion = "3.14.6"; -const char *fcd_traits::pluginVersion = "3.14.6"; +const char *fcd_traits::pluginVersion = "4.0.0"; +const char *fcd_traits::pluginVersion = "4.0.0"; const int64_t fcd_traits::loLowLimitFreq = 64000000L; const int64_t fcd_traits::loLowLimitFreq = 150000L; diff --git a/plugins/samplesource/airspy/airspyplugin.cpp b/plugins/samplesource/airspy/airspyplugin.cpp index 439cf73c8..91eadbb08 100644 --- a/plugins/samplesource/airspy/airspyplugin.cpp +++ b/plugins/samplesource/airspy/airspyplugin.cpp @@ -29,7 +29,7 @@ const int AirspyPlugin::m_maxDevices = 32; const PluginDescriptor AirspyPlugin::m_pluginDescriptor = { QString("Airspy Input"), - QString("3.14.6"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/airspyhf/airspyhfplugin.cpp b/plugins/samplesource/airspyhf/airspyhfplugin.cpp index 8612607ac..b593150df 100644 --- a/plugins/samplesource/airspyhf/airspyhfplugin.cpp +++ b/plugins/samplesource/airspyhf/airspyhfplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor AirspyHFPlugin::m_pluginDescriptor = { QString("AirspyHF Input"), - QString("3.14.6"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/fcdpro/fcdprogui.cpp b/plugins/samplesource/fcdpro/fcdprogui.cpp index d2bd2765e..bc3dafebb 100644 --- a/plugins/samplesource/fcdpro/fcdprogui.cpp +++ b/plugins/samplesource/fcdpro/fcdprogui.cpp @@ -211,9 +211,9 @@ bool FCDProGui::deserialize(const QByteArray& data) bool FCDProGui::handleMessage(const Message& message __attribute__((unused))) { - if (FCDProInput::MsgConfigureFCD::match(message)) + if (FCDProInput::MsgConfigureFCDPro::match(message)) { - const FCDProInput::MsgConfigureFCD& cfg = (FCDProInput::MsgConfigureFCD&) message; + const FCDProInput::MsgConfigureFCDPro& cfg = (FCDProInput::MsgConfigureFCDPro&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -517,7 +517,7 @@ void FCDProGui::updateStatus() void FCDProGui::updateHardware() { - FCDProInput::MsgConfigureFCD* message = FCDProInput::MsgConfigureFCD::create(m_settings, m_forceSettings); + FCDProInput::MsgConfigureFCDPro* message = FCDProInput::MsgConfigureFCDPro::create(m_settings, m_forceSettings); m_sampleSource->getInputMessageQueue()->push(message); m_forceSettings = false; m_updateTimer.stop(); diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index d84fd40bd..39f200078 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -36,7 +36,7 @@ #include "fcdtraits.h" #include "fcdproconst.h" -MESSAGE_CLASS_DEFINITION(FCDProInput::MsgConfigureFCD, Message) +MESSAGE_CLASS_DEFINITION(FCDProInput::MsgConfigureFCDPro, Message) MESSAGE_CLASS_DEFINITION(FCDProInput::MsgStartStop, Message) MESSAGE_CLASS_DEFINITION(FCDProInput::MsgFileRecord, Message) @@ -170,12 +170,12 @@ bool FCDProInput::deserialize(const QByteArray& data) success = false; } - MsgConfigureFCD* message = MsgConfigureFCD::create(m_settings, true); + MsgConfigureFCDPro* message = MsgConfigureFCDPro::create(m_settings, true); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureFCD* messageToGUI = MsgConfigureFCD::create(m_settings, true); + MsgConfigureFCDPro* messageToGUI = MsgConfigureFCDPro::create(m_settings, true); m_guiMessageQueue->push(messageToGUI); } @@ -202,22 +202,22 @@ void FCDProInput::setCenterFrequency(qint64 centerFrequency) FCDProSettings settings = m_settings; settings.m_centerFrequency = centerFrequency; - MsgConfigureFCD* message = MsgConfigureFCD::create(settings, false); + MsgConfigureFCDPro* message = MsgConfigureFCDPro::create(settings, false); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureFCD* messageToGUI = MsgConfigureFCD::create(settings, false); + MsgConfigureFCDPro* messageToGUI = MsgConfigureFCDPro::create(settings, false); m_guiMessageQueue->push(messageToGUI); } } bool FCDProInput::handleMessage(const Message& message) { - if(MsgConfigureFCD::match(message)) + if(MsgConfigureFCDPro::match(message)) { qDebug() << "FCDProInput::handleMessage: MsgConfigureFCD"; - MsgConfigureFCD& conf = (MsgConfigureFCD&) message; + MsgConfigureFCDPro& conf = (MsgConfigureFCDPro&) message; applySettings(conf.getSettings(), conf.getForce()); return true; } @@ -773,3 +773,136 @@ int FCDProInput::webapiRun( return 200; } + +int FCDProInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFcdProSettings(new SWGSDRangel::SWGFCDProSettings()); + response.getFcdProSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int FCDProInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + FCDProSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getFcdProSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getFcdProSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("lnaGainIndex")) { + settings.m_lnaGainIndex = response.getFcdProSettings()->getLnaGainIndex(); + } + if (deviceSettingsKeys.contains("rfFilterIndex")) { + settings.m_rfFilterIndex = response.getFcdProSettings()->getRfFilterIndex(); + } + if (deviceSettingsKeys.contains("lnaEnhanceIndex")) { + settings.m_lnaEnhanceIndex = response.getFcdProSettings()->getLnaEnhanceIndex(); + } + if (deviceSettingsKeys.contains("bandIndex")) { + settings.m_bandIndex = response.getFcdProSettings()->getBandIndex(); + } + if (deviceSettingsKeys.contains("mixerGainIndex")) { + settings.m_mixerGainIndex = response.getFcdProSettings()->getMixerGainIndex(); + } + if (deviceSettingsKeys.contains("mixerFilterIndex")) { + settings.m_mixerFilterIndex = response.getFcdProSettings()->getMixerFilterIndex(); + } + if (deviceSettingsKeys.contains("biasCurrentIndex")) { + settings.m_biasCurrentIndex = response.getFcdProSettings()->getBiasCurrentIndex(); + } + if (deviceSettingsKeys.contains("modeIndex")) { + settings.m_modeIndex = response.getFcdProSettings()->getModeIndex(); + } + if (deviceSettingsKeys.contains("gain1Index")) { + settings.m_gain1Index = response.getFcdProSettings()->getGain1Index(); + } + if (deviceSettingsKeys.contains("gain2Index")) { + settings.m_gain2Index = response.getFcdProSettings()->getGain2Index(); + } + if (deviceSettingsKeys.contains("gain3Index")) { + settings.m_gain3Index = response.getFcdProSettings()->getGain3Index(); + } + if (deviceSettingsKeys.contains("gain4Index")) { + settings.m_gain4Index = response.getFcdProSettings()->getGain4Index(); + } + if (deviceSettingsKeys.contains("gain5Index")) { + settings.m_gain5Index = response.getFcdProSettings()->getGain5Index(); + } + if (deviceSettingsKeys.contains("gain6Index")) { + settings.m_gain6Index = response.getFcdProSettings()->getGain6Index(); + } + if (deviceSettingsKeys.contains("rcFilterIndex")) { + settings.m_rcFilterIndex = response.getFcdProSettings()->getRcFilterIndex(); + } + if (deviceSettingsKeys.contains("ifFilterIndex")) { + settings.m_ifFilterIndex = response.getFcdProSettings()->getIfFilterIndex(); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getFcdProSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getFcdProSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getFcdProSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getFcdProSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getFcdProSettings()->getFileRecordName(); + } + + MsgConfigureFCDPro *msg = MsgConfigureFCDPro::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureFCDPro *msgToGUI = MsgConfigureFCDPro::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void FCDProInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FCDProSettings& settings) +{ + response.getFcdProSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getFcdProSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getFcdProSettings()->setLnaGainIndex(settings.m_lnaGainIndex); + response.getFcdProSettings()->setRfFilterIndex(settings.m_lnaGainIndex); + response.getFcdProSettings()->setLnaEnhanceIndex(settings.m_lnaEnhanceIndex); + response.getFcdProSettings()->setBandIndex(settings.m_bandIndex); + response.getFcdProSettings()->setMixerGainIndex(settings.m_mixerGainIndex); + response.getFcdProSettings()->setMixerFilterIndex(settings.m_mixerFilterIndex); + response.getFcdProSettings()->setBiasCurrentIndex(settings.m_biasCurrentIndex); + response.getFcdProSettings()->setModeIndex(settings.m_modeIndex); + response.getFcdProSettings()->setGain1Index(settings.m_gain1Index); + response.getFcdProSettings()->setGain2Index(settings.m_gain2Index); + response.getFcdProSettings()->setGain3Index(settings.m_gain3Index); + response.getFcdProSettings()->setGain4Index(settings.m_gain4Index); + response.getFcdProSettings()->setGain5Index(settings.m_gain5Index); + response.getFcdProSettings()->setGain6Index(settings.m_gain6Index); + response.getFcdProSettings()->setRcFilterIndex(settings.m_rcFilterIndex); + response.getFcdProSettings()->setIfFilterIndex(settings.m_ifFilterIndex); + response.getFcdProSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getFcdProSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getFcdProSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getFcdProSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getFcdProSettings()->getFileRecordName()) { + *response.getFcdProSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getFcdProSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} diff --git a/plugins/samplesource/fcdpro/fcdproinput.h b/plugins/samplesource/fcdpro/fcdproinput.h index f7ed3792b..dfdbabe0d 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.h +++ b/plugins/samplesource/fcdpro/fcdproinput.h @@ -37,23 +37,23 @@ class FileRecord; class FCDProInput : public DeviceSampleSource { public: - class MsgConfigureFCD : public Message { + class MsgConfigureFCDPro : public Message { MESSAGE_CLASS_DECLARATION public: const FCDProSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureFCD* create(const FCDProSettings& settings, bool force) + static MsgConfigureFCDPro* create(const FCDProSettings& settings, bool force) { - return new MsgConfigureFCD(settings, force); + return new MsgConfigureFCDPro(settings, force); } private: FCDProSettings m_settings; bool m_force; - MsgConfigureFCD(const FCDProSettings& settings, bool force) : + MsgConfigureFCDPro(const FCDProSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -117,6 +117,16 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -151,6 +161,8 @@ private: void applySettings(const FCDProSettings& settings, bool force); void set_lo_ppm(); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FCDProSettings& settings); + DeviceSourceAPI *m_deviceAPI; hid_device *m_dev; QMutex m_mutex; diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 5a04f8ddb..eae18279d 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1779,6 +1779,9 @@ margin-bottom: 20px; "bladeRFOutputSettings" : { "$ref" : "#/definitions/BladeRFOutputSettings" }, + "fcdProSettings" : { + "$ref" : "#/definitions/FCDProSettings" + }, "fileSourceSettings" : { "$ref" : "#/definitions/FileSourceSettings" }, @@ -1818,6 +1821,82 @@ margin-bottom: 20px; "example" : "KO" } } +}; + defs.FCDProSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "lnaGainIndex" : { + "type" : "integer" + }, + "rfFilterIndex" : { + "type" : "integer" + }, + "lnaEnhanceIndex" : { + "type" : "integer" + }, + "bandIndex" : { + "type" : "integer" + }, + "mixerGainIndex" : { + "type" : "integer" + }, + "mixerFilterIndex" : { + "type" : "integer" + }, + "biasCurrentIndex" : { + "type" : "integer" + }, + "modeIndex" : { + "type" : "integer" + }, + "gain1Index" : { + "type" : "integer" + }, + "rcFilterIndex" : { + "type" : "integer" + }, + "gain2Index" : { + "type" : "integer" + }, + "gain3Index" : { + "type" : "integer" + }, + "gain4Index" : { + "type" : "integer" + }, + "ifFilterIndex" : { + "type" : "integer" + }, + "gain5Index" : { + "type" : "integer" + }, + "gain6Index" : { + "type" : "integer" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "FCDPro" }; defs.FileSourceSettings = { "properties" : { @@ -21695,7 +21774,7 @@ except ApiException as e:
    - Generated 2018-05-26T02:22:55.108+02:00 + Generated 2018-05-26T10:03:22.430+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/FCDPro.yaml b/sdrbase/resources/webapi/doc/swagger/include/FCDPro.yaml new file mode 100644 index 000000000..8519d7d87 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/FCDPro.yaml @@ -0,0 +1,54 @@ +FCDProSettings: + description: FCDPro + properties: + centerFrequency: + type: integer + format: int64 + LOppmTenths: + type: integer + lnaGainIndex: + type: integer + rfFilterIndex: + type: integer + lnaEnhanceIndex: + type: integer + bandIndex: + type: integer + mixerGainIndex: + type: integer + mixerFilterIndex: + type: integer + biasCurrentIndex: + type: integer + modeIndex: + type: integer + gain1Index: + type: integer + rcFilterIndex: + type: integer + rcFilterIndex: + type: integer + gain2Index: + type: integer + gain3Index: + type: integer + gain4Index: + type: integer + ifFilterIndex: + type: integer + gain5Index: + type: integer + gain6Index: + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string + diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 62f07987c..852e73dac 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1753,6 +1753,8 @@ definitions: $ref: "/doc/swagger/include/BladeRF.yaml#/BladeRFInputSettings" bladeRFOutputSettings: $ref: "/doc/swagger/include/BladeRF.yaml#/BladeRFOutputSettings" + fcdProSettings: + $ref: "/doc/swagger/include/FCDPro.yaml#/FCDProSettings" fileSourceSettings: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceSettings" hackRFInputSettings: diff --git a/swagger/sdrangel/api/swagger/include/FCDPro.yaml b/swagger/sdrangel/api/swagger/include/FCDPro.yaml new file mode 100644 index 000000000..5eb4072b3 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/FCDPro.yaml @@ -0,0 +1,52 @@ +FCDProSettings: + description: FCDPro + properties: + centerFrequency: + type: integer + format: int64 + LOppmTenths: + type: integer + lnaGainIndex: + type: integer + rfFilterIndex: + type: integer + lnaEnhanceIndex: + type: integer + bandIndex: + type: integer + mixerGainIndex: + type: integer + mixerFilterIndex: + type: integer + biasCurrentIndex: + type: integer + modeIndex: + type: integer + gain1Index: + type: integer + rcFilterIndex: + type: integer + gain2Index: + type: integer + gain3Index: + type: integer + gain4Index: + type: integer + ifFilterIndex: + type: integer + gain5Index: + type: integer + gain6Index: + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string + diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 1c6fb1704..b539061ed 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1753,6 +1753,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/BladeRF.yaml#/BladeRFInputSettings" bladeRFOutputSettings: $ref: "http://localhost:8081/api/swagger/include/BladeRF.yaml#/BladeRFOutputSettings" + fcdProSettings: + $ref: "http://localhost:8081/api/swagger/include/FCDPro.yaml#/FCDProSettings" fileSourceSettings: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceSettings" hackRFInputSettings: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 5a04f8ddb..eae18279d 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1779,6 +1779,9 @@ margin-bottom: 20px; "bladeRFOutputSettings" : { "$ref" : "#/definitions/BladeRFOutputSettings" }, + "fcdProSettings" : { + "$ref" : "#/definitions/FCDProSettings" + }, "fileSourceSettings" : { "$ref" : "#/definitions/FileSourceSettings" }, @@ -1818,6 +1821,82 @@ margin-bottom: 20px; "example" : "KO" } } +}; + defs.FCDProSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "lnaGainIndex" : { + "type" : "integer" + }, + "rfFilterIndex" : { + "type" : "integer" + }, + "lnaEnhanceIndex" : { + "type" : "integer" + }, + "bandIndex" : { + "type" : "integer" + }, + "mixerGainIndex" : { + "type" : "integer" + }, + "mixerFilterIndex" : { + "type" : "integer" + }, + "biasCurrentIndex" : { + "type" : "integer" + }, + "modeIndex" : { + "type" : "integer" + }, + "gain1Index" : { + "type" : "integer" + }, + "rcFilterIndex" : { + "type" : "integer" + }, + "gain2Index" : { + "type" : "integer" + }, + "gain3Index" : { + "type" : "integer" + }, + "gain4Index" : { + "type" : "integer" + }, + "ifFilterIndex" : { + "type" : "integer" + }, + "gain5Index" : { + "type" : "integer" + }, + "gain6Index" : { + "type" : "integer" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "FCDPro" }; defs.FileSourceSettings = { "properties" : { @@ -21695,7 +21774,7 @@ except ApiException as e:
    - Generated 2018-05-26T02:22:55.108+02:00 + Generated 2018-05-26T10:03:22.430+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 50b53fc35..81b72b57b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -40,6 +40,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_blade_rf_input_settings_isSet = false; blade_rf_output_settings = nullptr; m_blade_rf_output_settings_isSet = false; + fcd_pro_settings = nullptr; + m_fcd_pro_settings_isSet = false; file_source_settings = nullptr; m_file_source_settings_isSet = false; hack_rf_input_settings = nullptr; @@ -72,6 +74,8 @@ SWGDeviceSettings::init() { m_blade_rf_input_settings_isSet = false; blade_rf_output_settings = new SWGBladeRFOutputSettings(); m_blade_rf_output_settings_isSet = false; + fcd_pro_settings = new SWGFCDProSettings(); + m_fcd_pro_settings_isSet = false; file_source_settings = new SWGFileSourceSettings(); m_file_source_settings_isSet = false; hack_rf_input_settings = new SWGHackRFInputSettings(); @@ -104,6 +108,9 @@ SWGDeviceSettings::cleanup() { if(blade_rf_output_settings != nullptr) { delete blade_rf_output_settings; } + if(fcd_pro_settings != nullptr) { + delete fcd_pro_settings; + } if(file_source_settings != nullptr) { delete file_source_settings; } @@ -147,6 +154,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&blade_rf_output_settings, pJson["bladeRFOutputSettings"], "SWGBladeRFOutputSettings", "SWGBladeRFOutputSettings"); + ::SWGSDRangel::setValue(&fcd_pro_settings, pJson["fcdProSettings"], "SWGFCDProSettings", "SWGFCDProSettings"); + ::SWGSDRangel::setValue(&file_source_settings, pJson["fileSourceSettings"], "SWGFileSourceSettings", "SWGFileSourceSettings"); ::SWGSDRangel::setValue(&hack_rf_input_settings, pJson["hackRFInputSettings"], "SWGHackRFInputSettings", "SWGHackRFInputSettings"); @@ -193,6 +202,9 @@ SWGDeviceSettings::asJsonObject() { if((blade_rf_output_settings != nullptr) && (blade_rf_output_settings->isSet())){ toJsonValue(QString("bladeRFOutputSettings"), blade_rf_output_settings, obj, QString("SWGBladeRFOutputSettings")); } + if((fcd_pro_settings != nullptr) && (fcd_pro_settings->isSet())){ + toJsonValue(QString("fcdProSettings"), fcd_pro_settings, obj, QString("SWGFCDProSettings")); + } if((file_source_settings != nullptr) && (file_source_settings->isSet())){ toJsonValue(QString("fileSourceSettings"), file_source_settings, obj, QString("SWGFileSourceSettings")); } @@ -275,6 +287,16 @@ SWGDeviceSettings::setBladeRfOutputSettings(SWGBladeRFOutputSettings* blade_rf_o this->m_blade_rf_output_settings_isSet = true; } +SWGFCDProSettings* +SWGDeviceSettings::getFcdProSettings() { + return fcd_pro_settings; +} +void +SWGDeviceSettings::setFcdProSettings(SWGFCDProSettings* fcd_pro_settings) { + this->fcd_pro_settings = fcd_pro_settings; + this->m_fcd_pro_settings_isSet = true; +} + SWGFileSourceSettings* SWGDeviceSettings::getFileSourceSettings() { return file_source_settings; @@ -346,6 +368,7 @@ SWGDeviceSettings::isSet(){ if(airspy_hf_settings != nullptr && airspy_hf_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf_input_settings != nullptr && blade_rf_input_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf_output_settings != nullptr && blade_rf_output_settings->isSet()){ isObjectUpdated = true; break;} + if(fcd_pro_settings != nullptr && fcd_pro_settings->isSet()){ isObjectUpdated = true; break;} if(file_source_settings != nullptr && file_source_settings->isSet()){ isObjectUpdated = true; break;} if(hack_rf_input_settings != nullptr && hack_rf_input_settings->isSet()){ isObjectUpdated = true; break;} if(hack_rf_output_settings != nullptr && hack_rf_output_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 2c38ac566..142858ca8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -26,6 +26,7 @@ #include "SWGAirspySettings.h" #include "SWGBladeRFInputSettings.h" #include "SWGBladeRFOutputSettings.h" +#include "SWGFCDProSettings.h" #include "SWGFileSourceSettings.h" #include "SWGHackRFInputSettings.h" #include "SWGHackRFOutputSettings.h" @@ -70,6 +71,9 @@ public: SWGBladeRFOutputSettings* getBladeRfOutputSettings(); void setBladeRfOutputSettings(SWGBladeRFOutputSettings* blade_rf_output_settings); + SWGFCDProSettings* getFcdProSettings(); + void setFcdProSettings(SWGFCDProSettings* fcd_pro_settings); + SWGFileSourceSettings* getFileSourceSettings(); void setFileSourceSettings(SWGFileSourceSettings* file_source_settings); @@ -110,6 +114,9 @@ private: SWGBladeRFOutputSettings* blade_rf_output_settings; bool m_blade_rf_output_settings_isSet; + SWGFCDProSettings* fcd_pro_settings; + bool m_fcd_pro_settings_isSet; + SWGFileSourceSettings* file_source_settings; bool m_file_source_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp new file mode 100644 index 000000000..b69368ab3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp @@ -0,0 +1,570 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGFCDProSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGFCDProSettings::SWGFCDProSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGFCDProSettings::SWGFCDProSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + lna_gain_index = 0; + m_lna_gain_index_isSet = false; + rf_filter_index = 0; + m_rf_filter_index_isSet = false; + lna_enhance_index = 0; + m_lna_enhance_index_isSet = false; + band_index = 0; + m_band_index_isSet = false; + mixer_gain_index = 0; + m_mixer_gain_index_isSet = false; + mixer_filter_index = 0; + m_mixer_filter_index_isSet = false; + bias_current_index = 0; + m_bias_current_index_isSet = false; + mode_index = 0; + m_mode_index_isSet = false; + gain1_index = 0; + m_gain1_index_isSet = false; + rc_filter_index = 0; + m_rc_filter_index_isSet = false; + gain2_index = 0; + m_gain2_index_isSet = false; + gain3_index = 0; + m_gain3_index_isSet = false; + gain4_index = 0; + m_gain4_index_isSet = false; + if_filter_index = 0; + m_if_filter_index_isSet = false; + gain5_index = 0; + m_gain5_index_isSet = false; + gain6_index = 0; + m_gain6_index_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; +} + +SWGFCDProSettings::~SWGFCDProSettings() { + this->cleanup(); +} + +void +SWGFCDProSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + lna_gain_index = 0; + m_lna_gain_index_isSet = false; + rf_filter_index = 0; + m_rf_filter_index_isSet = false; + lna_enhance_index = 0; + m_lna_enhance_index_isSet = false; + band_index = 0; + m_band_index_isSet = false; + mixer_gain_index = 0; + m_mixer_gain_index_isSet = false; + mixer_filter_index = 0; + m_mixer_filter_index_isSet = false; + bias_current_index = 0; + m_bias_current_index_isSet = false; + mode_index = 0; + m_mode_index_isSet = false; + gain1_index = 0; + m_gain1_index_isSet = false; + rc_filter_index = 0; + m_rc_filter_index_isSet = false; + gain2_index = 0; + m_gain2_index_isSet = false; + gain3_index = 0; + m_gain3_index_isSet = false; + gain4_index = 0; + m_gain4_index_isSet = false; + if_filter_index = 0; + m_if_filter_index_isSet = false; + gain5_index = 0; + m_gain5_index_isSet = false; + gain6_index = 0; + m_gain6_index_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; +} + +void +SWGFCDProSettings::cleanup() { + + + + + + + + + + + + + + + + + + + + + + + if(file_record_name != nullptr) { + delete file_record_name; + } +} + +SWGFCDProSettings* +SWGFCDProSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGFCDProSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); + + ::SWGSDRangel::setValue(&lna_gain_index, pJson["lnaGainIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&rf_filter_index, pJson["rfFilterIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&lna_enhance_index, pJson["lnaEnhanceIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&band_index, pJson["bandIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&mixer_gain_index, pJson["mixerGainIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&mixer_filter_index, pJson["mixerFilterIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&bias_current_index, pJson["biasCurrentIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&mode_index, pJson["modeIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain1_index, pJson["gain1Index"], "qint32", ""); + + ::SWGSDRangel::setValue(&rc_filter_index, pJson["rcFilterIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain2_index, pJson["gain2Index"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain3_index, pJson["gain3Index"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain4_index, pJson["gain4Index"], "qint32", ""); + + ::SWGSDRangel::setValue(&if_filter_index, pJson["ifFilterIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain5_index, pJson["gain5Index"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain6_index, pJson["gain6Index"], "qint32", ""); + + ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); + + ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + +} + +QString +SWGFCDProSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGFCDProSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); + } + if(m_lna_gain_index_isSet){ + obj->insert("lnaGainIndex", QJsonValue(lna_gain_index)); + } + if(m_rf_filter_index_isSet){ + obj->insert("rfFilterIndex", QJsonValue(rf_filter_index)); + } + if(m_lna_enhance_index_isSet){ + obj->insert("lnaEnhanceIndex", QJsonValue(lna_enhance_index)); + } + if(m_band_index_isSet){ + obj->insert("bandIndex", QJsonValue(band_index)); + } + if(m_mixer_gain_index_isSet){ + obj->insert("mixerGainIndex", QJsonValue(mixer_gain_index)); + } + if(m_mixer_filter_index_isSet){ + obj->insert("mixerFilterIndex", QJsonValue(mixer_filter_index)); + } + if(m_bias_current_index_isSet){ + obj->insert("biasCurrentIndex", QJsonValue(bias_current_index)); + } + if(m_mode_index_isSet){ + obj->insert("modeIndex", QJsonValue(mode_index)); + } + if(m_gain1_index_isSet){ + obj->insert("gain1Index", QJsonValue(gain1_index)); + } + if(m_rc_filter_index_isSet){ + obj->insert("rcFilterIndex", QJsonValue(rc_filter_index)); + } + if(m_gain2_index_isSet){ + obj->insert("gain2Index", QJsonValue(gain2_index)); + } + if(m_gain3_index_isSet){ + obj->insert("gain3Index", QJsonValue(gain3_index)); + } + if(m_gain4_index_isSet){ + obj->insert("gain4Index", QJsonValue(gain4_index)); + } + if(m_if_filter_index_isSet){ + obj->insert("ifFilterIndex", QJsonValue(if_filter_index)); + } + if(m_gain5_index_isSet){ + obj->insert("gain5Index", QJsonValue(gain5_index)); + } + if(m_gain6_index_isSet){ + obj->insert("gain6Index", QJsonValue(gain6_index)); + } + if(m_dc_block_isSet){ + obj->insert("dcBlock", QJsonValue(dc_block)); + } + if(m_iq_correction_isSet){ + obj->insert("iqCorrection", QJsonValue(iq_correction)); + } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGFCDProSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGFCDProSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGFCDProSettings::getLOppmTenths() { + return l_oppm_tenths; +} +void +SWGFCDProSettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; +} + +qint32 +SWGFCDProSettings::getLnaGainIndex() { + return lna_gain_index; +} +void +SWGFCDProSettings::setLnaGainIndex(qint32 lna_gain_index) { + this->lna_gain_index = lna_gain_index; + this->m_lna_gain_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getRfFilterIndex() { + return rf_filter_index; +} +void +SWGFCDProSettings::setRfFilterIndex(qint32 rf_filter_index) { + this->rf_filter_index = rf_filter_index; + this->m_rf_filter_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getLnaEnhanceIndex() { + return lna_enhance_index; +} +void +SWGFCDProSettings::setLnaEnhanceIndex(qint32 lna_enhance_index) { + this->lna_enhance_index = lna_enhance_index; + this->m_lna_enhance_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getBandIndex() { + return band_index; +} +void +SWGFCDProSettings::setBandIndex(qint32 band_index) { + this->band_index = band_index; + this->m_band_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getMixerGainIndex() { + return mixer_gain_index; +} +void +SWGFCDProSettings::setMixerGainIndex(qint32 mixer_gain_index) { + this->mixer_gain_index = mixer_gain_index; + this->m_mixer_gain_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getMixerFilterIndex() { + return mixer_filter_index; +} +void +SWGFCDProSettings::setMixerFilterIndex(qint32 mixer_filter_index) { + this->mixer_filter_index = mixer_filter_index; + this->m_mixer_filter_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getBiasCurrentIndex() { + return bias_current_index; +} +void +SWGFCDProSettings::setBiasCurrentIndex(qint32 bias_current_index) { + this->bias_current_index = bias_current_index; + this->m_bias_current_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getModeIndex() { + return mode_index; +} +void +SWGFCDProSettings::setModeIndex(qint32 mode_index) { + this->mode_index = mode_index; + this->m_mode_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getGain1Index() { + return gain1_index; +} +void +SWGFCDProSettings::setGain1Index(qint32 gain1_index) { + this->gain1_index = gain1_index; + this->m_gain1_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getRcFilterIndex() { + return rc_filter_index; +} +void +SWGFCDProSettings::setRcFilterIndex(qint32 rc_filter_index) { + this->rc_filter_index = rc_filter_index; + this->m_rc_filter_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getGain2Index() { + return gain2_index; +} +void +SWGFCDProSettings::setGain2Index(qint32 gain2_index) { + this->gain2_index = gain2_index; + this->m_gain2_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getGain3Index() { + return gain3_index; +} +void +SWGFCDProSettings::setGain3Index(qint32 gain3_index) { + this->gain3_index = gain3_index; + this->m_gain3_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getGain4Index() { + return gain4_index; +} +void +SWGFCDProSettings::setGain4Index(qint32 gain4_index) { + this->gain4_index = gain4_index; + this->m_gain4_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getIfFilterIndex() { + return if_filter_index; +} +void +SWGFCDProSettings::setIfFilterIndex(qint32 if_filter_index) { + this->if_filter_index = if_filter_index; + this->m_if_filter_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getGain5Index() { + return gain5_index; +} +void +SWGFCDProSettings::setGain5Index(qint32 gain5_index) { + this->gain5_index = gain5_index; + this->m_gain5_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getGain6Index() { + return gain6_index; +} +void +SWGFCDProSettings::setGain6Index(qint32 gain6_index) { + this->gain6_index = gain6_index; + this->m_gain6_index_isSet = true; +} + +qint32 +SWGFCDProSettings::getDcBlock() { + return dc_block; +} +void +SWGFCDProSettings::setDcBlock(qint32 dc_block) { + this->dc_block = dc_block; + this->m_dc_block_isSet = true; +} + +qint32 +SWGFCDProSettings::getIqCorrection() { + return iq_correction; +} +void +SWGFCDProSettings::setIqCorrection(qint32 iq_correction) { + this->iq_correction = iq_correction; + this->m_iq_correction_isSet = true; +} + +qint32 +SWGFCDProSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGFCDProSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGFCDProSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGFCDProSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + +QString* +SWGFCDProSettings::getFileRecordName() { + return file_record_name; +} +void +SWGFCDProSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + + +bool +SWGFCDProSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} + if(m_lna_gain_index_isSet){ isObjectUpdated = true; break;} + if(m_rf_filter_index_isSet){ isObjectUpdated = true; break;} + if(m_lna_enhance_index_isSet){ isObjectUpdated = true; break;} + if(m_band_index_isSet){ isObjectUpdated = true; break;} + if(m_mixer_gain_index_isSet){ isObjectUpdated = true; break;} + if(m_mixer_filter_index_isSet){ isObjectUpdated = true; break;} + if(m_bias_current_index_isSet){ isObjectUpdated = true; break;} + if(m_mode_index_isSet){ isObjectUpdated = true; break;} + if(m_gain1_index_isSet){ isObjectUpdated = true; break;} + if(m_rc_filter_index_isSet){ isObjectUpdated = true; break;} + if(m_gain2_index_isSet){ isObjectUpdated = true; break;} + if(m_gain3_index_isSet){ isObjectUpdated = true; break;} + if(m_gain4_index_isSet){ isObjectUpdated = true; break;} + if(m_if_filter_index_isSet){ isObjectUpdated = true; break;} + if(m_gain5_index_isSet){ isObjectUpdated = true; break;} + if(m_gain6_index_isSet){ isObjectUpdated = true; break;} + if(m_dc_block_isSet){ isObjectUpdated = true; break;} + if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h new file mode 100644 index 000000000..e0e6508c7 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h @@ -0,0 +1,191 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGFCDProSettings.h + * + * FCDPro + */ + +#ifndef SWGFCDProSettings_H_ +#define SWGFCDProSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGFCDProSettings: public SWGObject { +public: + SWGFCDProSettings(); + SWGFCDProSettings(QString* json); + virtual ~SWGFCDProSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGFCDProSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); + + qint32 getLnaGainIndex(); + void setLnaGainIndex(qint32 lna_gain_index); + + qint32 getRfFilterIndex(); + void setRfFilterIndex(qint32 rf_filter_index); + + qint32 getLnaEnhanceIndex(); + void setLnaEnhanceIndex(qint32 lna_enhance_index); + + qint32 getBandIndex(); + void setBandIndex(qint32 band_index); + + qint32 getMixerGainIndex(); + void setMixerGainIndex(qint32 mixer_gain_index); + + qint32 getMixerFilterIndex(); + void setMixerFilterIndex(qint32 mixer_filter_index); + + qint32 getBiasCurrentIndex(); + void setBiasCurrentIndex(qint32 bias_current_index); + + qint32 getModeIndex(); + void setModeIndex(qint32 mode_index); + + qint32 getGain1Index(); + void setGain1Index(qint32 gain1_index); + + qint32 getRcFilterIndex(); + void setRcFilterIndex(qint32 rc_filter_index); + + qint32 getGain2Index(); + void setGain2Index(qint32 gain2_index); + + qint32 getGain3Index(); + void setGain3Index(qint32 gain3_index); + + qint32 getGain4Index(); + void setGain4Index(qint32 gain4_index); + + qint32 getIfFilterIndex(); + void setIfFilterIndex(qint32 if_filter_index); + + qint32 getGain5Index(); + void setGain5Index(qint32 gain5_index); + + qint32 getGain6Index(); + void setGain6Index(qint32 gain6_index); + + qint32 getDcBlock(); + void setDcBlock(qint32 dc_block); + + qint32 getIqCorrection(); + void setIqCorrection(qint32 iq_correction); + + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; + + qint32 lna_gain_index; + bool m_lna_gain_index_isSet; + + qint32 rf_filter_index; + bool m_rf_filter_index_isSet; + + qint32 lna_enhance_index; + bool m_lna_enhance_index_isSet; + + qint32 band_index; + bool m_band_index_isSet; + + qint32 mixer_gain_index; + bool m_mixer_gain_index_isSet; + + qint32 mixer_filter_index; + bool m_mixer_filter_index_isSet; + + qint32 bias_current_index; + bool m_bias_current_index_isSet; + + qint32 mode_index; + bool m_mode_index_isSet; + + qint32 gain1_index; + bool m_gain1_index_isSet; + + qint32 rc_filter_index; + bool m_rc_filter_index_isSet; + + qint32 gain2_index; + bool m_gain2_index_isSet; + + qint32 gain3_index; + bool m_gain3_index_isSet; + + qint32 gain4_index; + bool m_gain4_index_isSet; + + qint32 if_filter_index; + bool m_if_filter_index_isSet; + + qint32 gain5_index; + bool m_gain5_index_isSet; + + qint32 gain6_index; + bool m_gain6_index_isSet; + + qint32 dc_block; + bool m_dc_block_isSet; + + qint32 iq_correction; + bool m_iq_correction_isSet; + + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + +}; + +} + +#endif /* SWGFCDProSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 0e413ef74..83744bff4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -49,6 +49,7 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGErrorResponse.h" +#include "SWGFCDProSettings.h" #include "SWGFileSourceSettings.h" #include "SWGHackRFInputSettings.h" #include "SWGHackRFOutputSettings.h" @@ -194,6 +195,9 @@ namespace SWGSDRangel { if(QString("SWGErrorResponse").compare(type) == 0) { return new SWGErrorResponse(); } + if(QString("SWGFCDProSettings").compare(type) == 0) { + return new SWGFCDProSettings(); + } if(QString("SWGFileSourceSettings").compare(type) == 0) { return new SWGFileSourceSettings(); } From 268ad2b33f22e9fd824d6c07c8d290d4f09e7c76 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 10:54:31 +0200 Subject: [PATCH 464/956] FCD Pro Plus input: implemeted WEB API --- plugins/samplesource/fcdpro/fcdproinput.cpp | 2 +- .../samplesource/fcdproplus/fcdproplusgui.cpp | 6 +- .../fcdproplus/fcdproplusinput.cpp | 110 ++++- .../samplesource/fcdproplus/fcdproplusinput.h | 19 +- .../fcdproplus/fcdproplusinputqt.cpp | 6 +- .../fcdproplus/fcdproplusinputqt.h | 6 +- sdrbase/resources/webapi/doc/html2/index.html | 58 ++- .../webapi/doc/swagger/include/FCDPro.yaml | 2 - .../doc/swagger/include/FCDProPlus.yaml | 37 ++ .../resources/webapi/doc/swagger/swagger.yaml | 2 + .../api/swagger/include/FCDProPlus.yaml | 37 ++ swagger/sdrangel/api/swagger/swagger.yaml | 2 + swagger/sdrangel/code/html2/index.html | 58 ++- .../code/qt5/client/SWGDeviceSettings.cpp | 23 ++ .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGFCDProPlusSettings.cpp | 381 ++++++++++++++++++ .../code/qt5/client/SWGFCDProPlusSettings.h | 137 +++++++ .../code/qt5/client/SWGModelFactory.h | 4 + 18 files changed, 872 insertions(+), 25 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/FCDProPlus.yaml create mode 100644 swagger/sdrangel/api/swagger/include/FCDProPlus.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index 39f200078..a0f748efa 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -880,7 +880,7 @@ void FCDProInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& res response.getFcdProSettings()->setCenterFrequency(settings.m_centerFrequency); response.getFcdProSettings()->setLOppmTenths(settings.m_LOppmTenths); response.getFcdProSettings()->setLnaGainIndex(settings.m_lnaGainIndex); - response.getFcdProSettings()->setRfFilterIndex(settings.m_lnaGainIndex); + response.getFcdProSettings()->setRfFilterIndex(settings.m_rfFilterIndex); response.getFcdProSettings()->setLnaEnhanceIndex(settings.m_lnaEnhanceIndex); response.getFcdProSettings()->setBandIndex(settings.m_bandIndex); response.getFcdProSettings()->setMixerGainIndex(settings.m_mixerGainIndex); diff --git a/plugins/samplesource/fcdproplus/fcdproplusgui.cpp b/plugins/samplesource/fcdproplus/fcdproplusgui.cpp index 2a67120a1..9a38c7122 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusgui.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusgui.cpp @@ -129,9 +129,9 @@ bool FCDProPlusGui::deserialize(const QByteArray& data) bool FCDProPlusGui::handleMessage(const Message& message __attribute__((unused))) { - if (FCDProPlusInput::MsgConfigureFCD::match(message)) + if (FCDProPlusInput::MsgConfigureFCDProPlus::match(message)) { - const FCDProPlusInput::MsgConfigureFCD& cfg = (FCDProPlusInput::MsgConfigureFCD&) message; + const FCDProPlusInput::MsgConfigureFCDProPlus& cfg = (FCDProPlusInput::MsgConfigureFCDProPlus&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -241,7 +241,7 @@ void FCDProPlusGui::on_iqImbalance_toggled(bool checked) void FCDProPlusGui::updateHardware() { - FCDProPlusInput::MsgConfigureFCD* message = FCDProPlusInput::MsgConfigureFCD::create(m_settings, m_forceSettings); + FCDProPlusInput::MsgConfigureFCDProPlus* message = FCDProPlusInput::MsgConfigureFCDProPlus::create(m_settings, m_forceSettings); m_sampleSource->getInputMessageQueue()->push(message); m_forceSettings = false; m_updateTimer.stop(); diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index 0f61e9a6e..8bbc3fac7 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -35,7 +35,7 @@ #include "fcdtraits.h" #include "fcdproplusconst.h" -MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCD, Message) +MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCDProPlus, Message) MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgStartStop, Message) MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgFileRecord, Message) @@ -164,12 +164,12 @@ bool FCDProPlusInput::deserialize(const QByteArray& data) success = false; } - MsgConfigureFCD* message = MsgConfigureFCD::create(m_settings, true); + MsgConfigureFCDProPlus* message = MsgConfigureFCDProPlus::create(m_settings, true); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureFCD* messageToGUI = MsgConfigureFCD::create(m_settings, true); + MsgConfigureFCDProPlus* messageToGUI = MsgConfigureFCDProPlus::create(m_settings, true); m_guiMessageQueue->push(messageToGUI); } @@ -196,22 +196,22 @@ void FCDProPlusInput::setCenterFrequency(qint64 centerFrequency) FCDProPlusSettings settings = m_settings; settings.m_centerFrequency = centerFrequency; - MsgConfigureFCD* message = MsgConfigureFCD::create(settings, false); + MsgConfigureFCDProPlus* message = MsgConfigureFCDProPlus::create(settings, false); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureFCD* messageToGUI = MsgConfigureFCD::create(settings, false); + MsgConfigureFCDProPlus* messageToGUI = MsgConfigureFCDProPlus::create(settings, false); m_guiMessageQueue->push(messageToGUI); } } bool FCDProPlusInput::handleMessage(const Message& message) { - if(MsgConfigureFCD::match(message)) + if(MsgConfigureFCDProPlus::match(message)) { qDebug() << "FCDProPlusInput::handleMessage: MsgConfigureFCD"; - MsgConfigureFCD& conf = (MsgConfigureFCD&) message; + MsgConfigureFCDProPlus& conf = (MsgConfigureFCDProPlus&) message; applySettings(conf.getSettings(), conf.getForce()); return true; } @@ -485,5 +485,101 @@ int FCDProPlusInput::webapiRun( return 200; } +int FCDProPlusInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFcdProPlusSettings(new SWGSDRangel::SWGFCDProPlusSettings()); + response.getFcdProPlusSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int FCDProPlusInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + FCDProPlusSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getFcdProPlusSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("rangeLow")) { + settings.m_rangeLow = response.getFcdProPlusSettings()->getRangeLow() != 0; + } + if (deviceSettingsKeys.contains("lnaGain")) { + settings.m_lnaGain = response.getFcdProPlusSettings()->getLnaGain() != 0; + } + if (deviceSettingsKeys.contains("mixGain")) { + settings.m_mixGain = response.getFcdProPlusSettings()->getMixGain() != 0; + } + if (deviceSettingsKeys.contains("biasT")) { + settings.m_biasT = response.getFcdProPlusSettings()->getBiasT() != 0; + } + if (deviceSettingsKeys.contains("ifGain")) { + settings.m_ifGain = response.getFcdProPlusSettings()->getIfGain(); + } + if (deviceSettingsKeys.contains("ifFilterIndex")) { + settings.m_ifFilterIndex = response.getFcdProPlusSettings()->getIfFilterIndex(); + } + if (deviceSettingsKeys.contains("rfFilterIndex")) { + settings.m_rfFilterIndex = response.getFcdProPlusSettings()->getRfFilterIndex(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getFcdProPlusSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getFcdProPlusSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqImbalance")) { + settings.m_iqImbalance = response.getFcdProPlusSettings()->getIqImbalance() != 0; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getFcdProPlusSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getFcdProPlusSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getFcdProPlusSettings()->getFileRecordName(); + } + + MsgConfigureFCDProPlus *msg = MsgConfigureFCDProPlus::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureFCDProPlus *msgToGUI = MsgConfigureFCDProPlus::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void FCDProPlusInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FCDProPlusSettings& settings) +{ + response.getFcdProPlusSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getFcdProPlusSettings()->setRangeLow(settings.m_rangeLow ? 1 : 0); + response.getFcdProPlusSettings()->setLnaGain(settings.m_lnaGain ? 1 : 0); + response.getFcdProPlusSettings()->setMixGain(settings.m_mixGain ? 1 : 0); + response.getFcdProPlusSettings()->setBiasT(settings.m_biasT ? 1 : 0); + response.getFcdProPlusSettings()->setIfGain(settings.m_ifGain); + response.getFcdProPlusSettings()->setIfFilterIndex(settings.m_ifFilterIndex); + response.getFcdProPlusSettings()->setRfFilterIndex(settings.m_rfFilterIndex); + response.getFcdProPlusSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getFcdProPlusSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getFcdProPlusSettings()->setIqImbalance(settings.m_iqImbalance ? 1 : 0); + response.getFcdProPlusSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getFcdProPlusSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getFcdProPlusSettings()->getFileRecordName()) { + *response.getFcdProPlusSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getFcdProPlusSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.h b/plugins/samplesource/fcdproplus/fcdproplusinput.h index f73884d5e..38c4cac11 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.h +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.h @@ -36,23 +36,23 @@ class FileRecord; class FCDProPlusInput : public DeviceSampleSource { public: - class MsgConfigureFCD : public Message { + class MsgConfigureFCDProPlus : public Message { MESSAGE_CLASS_DECLARATION public: const FCDProPlusSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureFCD* create(const FCDProPlusSettings& settings, bool force) + static MsgConfigureFCDProPlus* create(const FCDProPlusSettings& settings, bool force) { - return new MsgConfigureFCD(settings, force); + return new MsgConfigureFCDProPlus(settings, force); } private: FCDProPlusSettings m_settings; bool m_force; - MsgConfigureFCD(const FCDProPlusSettings& settings, bool force) : + MsgConfigureFCDProPlus(const FCDProPlusSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -125,6 +125,16 @@ public: SWGSDRangel::SWGDeviceState& response, QString& errorMessage); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + void set_center_freq(double freq); void set_bias_t(bool on); void set_lna_gain(bool on); @@ -138,6 +148,7 @@ private: bool openDevice(); void closeDevice(); void applySettings(const FCDProPlusSettings& settings, bool force); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FCDProPlusSettings& settings); DeviceSourceAPI *m_deviceAPI; hid_device *m_dev; diff --git a/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp b/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp index 3a8f8a328..391a4f0ef 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp @@ -28,7 +28,7 @@ #include "fcdtraits.h" #include "fcdproplusconst.h" -MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCD, Message) +MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCDProPlus, Message) FCDProPlusInput::FCDProPlusInput() : m_dev(0), @@ -115,10 +115,10 @@ quint64 FCDProPlusInput::getCenterFrequency() const bool FCDProPlusInput::handleMessage(const Message& message) { - if(MsgConfigureFCD::match(message)) + if(MsgConfigureFCDProPlus::match(message)) { qDebug() << "FCDProPlusInput::handleMessage: MsgConfigureFCD"; - MsgConfigureFCD& conf = (MsgConfigureFCD&) message; + MsgConfigureFCDProPlus& conf = (MsgConfigureFCDProPlus&) message; applySettings(conf.getSettings(), false); return true; } diff --git a/plugins/samplesource/fcdproplus/fcdproplusinputqt.h b/plugins/samplesource/fcdproplus/fcdproplusinputqt.h index 215af1856..af974e582 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinputqt.h +++ b/plugins/samplesource/fcdproplus/fcdproplusinputqt.h @@ -35,15 +35,15 @@ class FCDProPlusReader; class FCDProPlusInput : public DeviceSampleSource { public: - class MsgConfigureFCD : public Message { + class MsgConfigureFCDProPlus : public Message { MESSAGE_CLASS_DECLARATION public: const FCDProPlusSettings& getSettings() const { return m_settings; } - static MsgConfigureFCD* create(const FCDProPlusSettings& settings) + static MsgConfigureFCDProPlus* create(const FCDProPlusSettings& settings) { - return new MsgConfigureFCD(settings); + return new MsgConfigureFCDProPlus(settings); } private: diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index eae18279d..ddadc405b 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1782,6 +1782,9 @@ margin-bottom: 20px; "fcdProSettings" : { "$ref" : "#/definitions/FCDProSettings" }, + "fcdProPlusSettings" : { + "$ref" : "#/definitions/FCDProPlusSettings" + }, "fileSourceSettings" : { "$ref" : "#/definitions/FileSourceSettings" }, @@ -1821,6 +1824,59 @@ margin-bottom: 20px; "example" : "KO" } } +}; + defs.FCDProPlusSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "rangeLow" : { + "type" : "integer", + "description" : "Range select (1 if low else 0)" + }, + "lnaGain" : { + "type" : "integer", + "description" : "LNA select (1 if selected else 0)" + }, + "mixGain" : { + "type" : "integer", + "description" : "Mixer amplifier select (1 if selected else 0)" + }, + "biasT" : { + "type" : "integer", + "description" : "Bias tee select (1 if selected else 0)" + }, + "ifGain" : { + "type" : "integer" + }, + "ifFilterIndex" : { + "type" : "integer" + }, + "rfFilterIndex" : { + "type" : "integer" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqImbalance" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "FCDProPlus" }; defs.FCDProSettings = { "properties" : { @@ -21774,7 +21830,7 @@ except ApiException as e:
    - Generated 2018-05-26T10:03:22.430+02:00 + Generated 2018-05-26T10:50:36.916+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/FCDPro.yaml b/sdrbase/resources/webapi/doc/swagger/include/FCDPro.yaml index 8519d7d87..5eb4072b3 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/FCDPro.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/FCDPro.yaml @@ -26,8 +26,6 @@ FCDProSettings: type: integer rcFilterIndex: type: integer - rcFilterIndex: - type: integer gain2Index: type: integer gain3Index: diff --git a/sdrbase/resources/webapi/doc/swagger/include/FCDProPlus.yaml b/sdrbase/resources/webapi/doc/swagger/include/FCDProPlus.yaml new file mode 100644 index 000000000..581f1e051 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/FCDProPlus.yaml @@ -0,0 +1,37 @@ +FCDProPlusSettings: + description: FCDProPlus + properties: + centerFrequency: + type: integer + format: int64 + rangeLow: + description: Range select (1 if low else 0) + type: integer + lnaGain: + description: LNA select (1 if selected else 0) + type: integer + mixGain: + description: Mixer amplifier select (1 if selected else 0) + type: integer + biasT: + description: Bias tee select (1 if selected else 0) + type: integer + ifGain: + type: integer + ifFilterIndex: + type: integer + rfFilterIndex: + type: integer + LOppmTenths: + type: integer + dcBlock: + type: integer + iqImbalance: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 852e73dac..1519e0336 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1755,6 +1755,8 @@ definitions: $ref: "/doc/swagger/include/BladeRF.yaml#/BladeRFOutputSettings" fcdProSettings: $ref: "/doc/swagger/include/FCDPro.yaml#/FCDProSettings" + fcdProPlusSettings: + $ref: "/doc/swagger/include/FCDProPlus.yaml#/FCDProPlusSettings" fileSourceSettings: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceSettings" hackRFInputSettings: diff --git a/swagger/sdrangel/api/swagger/include/FCDProPlus.yaml b/swagger/sdrangel/api/swagger/include/FCDProPlus.yaml new file mode 100644 index 000000000..581f1e051 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/FCDProPlus.yaml @@ -0,0 +1,37 @@ +FCDProPlusSettings: + description: FCDProPlus + properties: + centerFrequency: + type: integer + format: int64 + rangeLow: + description: Range select (1 if low else 0) + type: integer + lnaGain: + description: LNA select (1 if selected else 0) + type: integer + mixGain: + description: Mixer amplifier select (1 if selected else 0) + type: integer + biasT: + description: Bias tee select (1 if selected else 0) + type: integer + ifGain: + type: integer + ifFilterIndex: + type: integer + rfFilterIndex: + type: integer + LOppmTenths: + type: integer + dcBlock: + type: integer + iqImbalance: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index b539061ed..597b2d287 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1755,6 +1755,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/BladeRF.yaml#/BladeRFOutputSettings" fcdProSettings: $ref: "http://localhost:8081/api/swagger/include/FCDPro.yaml#/FCDProSettings" + fcdProPlusSettings: + $ref: "http://localhost:8081/api/swagger/include/FCDProPlus.yaml#/FCDProPlusSettings" fileSourceSettings: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceSettings" hackRFInputSettings: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index eae18279d..ddadc405b 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1782,6 +1782,9 @@ margin-bottom: 20px; "fcdProSettings" : { "$ref" : "#/definitions/FCDProSettings" }, + "fcdProPlusSettings" : { + "$ref" : "#/definitions/FCDProPlusSettings" + }, "fileSourceSettings" : { "$ref" : "#/definitions/FileSourceSettings" }, @@ -1821,6 +1824,59 @@ margin-bottom: 20px; "example" : "KO" } } +}; + defs.FCDProPlusSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "rangeLow" : { + "type" : "integer", + "description" : "Range select (1 if low else 0)" + }, + "lnaGain" : { + "type" : "integer", + "description" : "LNA select (1 if selected else 0)" + }, + "mixGain" : { + "type" : "integer", + "description" : "Mixer amplifier select (1 if selected else 0)" + }, + "biasT" : { + "type" : "integer", + "description" : "Bias tee select (1 if selected else 0)" + }, + "ifGain" : { + "type" : "integer" + }, + "ifFilterIndex" : { + "type" : "integer" + }, + "rfFilterIndex" : { + "type" : "integer" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqImbalance" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "FCDProPlus" }; defs.FCDProSettings = { "properties" : { @@ -21774,7 +21830,7 @@ except ApiException as e:
    - Generated 2018-05-26T10:03:22.430+02:00 + Generated 2018-05-26T10:50:36.916+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 81b72b57b..1e2f855d6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -42,6 +42,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_blade_rf_output_settings_isSet = false; fcd_pro_settings = nullptr; m_fcd_pro_settings_isSet = false; + fcd_pro_plus_settings = nullptr; + m_fcd_pro_plus_settings_isSet = false; file_source_settings = nullptr; m_file_source_settings_isSet = false; hack_rf_input_settings = nullptr; @@ -76,6 +78,8 @@ SWGDeviceSettings::init() { m_blade_rf_output_settings_isSet = false; fcd_pro_settings = new SWGFCDProSettings(); m_fcd_pro_settings_isSet = false; + fcd_pro_plus_settings = new SWGFCDProPlusSettings(); + m_fcd_pro_plus_settings_isSet = false; file_source_settings = new SWGFileSourceSettings(); m_file_source_settings_isSet = false; hack_rf_input_settings = new SWGHackRFInputSettings(); @@ -111,6 +115,9 @@ SWGDeviceSettings::cleanup() { if(fcd_pro_settings != nullptr) { delete fcd_pro_settings; } + if(fcd_pro_plus_settings != nullptr) { + delete fcd_pro_plus_settings; + } if(file_source_settings != nullptr) { delete file_source_settings; } @@ -156,6 +163,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&fcd_pro_settings, pJson["fcdProSettings"], "SWGFCDProSettings", "SWGFCDProSettings"); + ::SWGSDRangel::setValue(&fcd_pro_plus_settings, pJson["fcdProPlusSettings"], "SWGFCDProPlusSettings", "SWGFCDProPlusSettings"); + ::SWGSDRangel::setValue(&file_source_settings, pJson["fileSourceSettings"], "SWGFileSourceSettings", "SWGFileSourceSettings"); ::SWGSDRangel::setValue(&hack_rf_input_settings, pJson["hackRFInputSettings"], "SWGHackRFInputSettings", "SWGHackRFInputSettings"); @@ -205,6 +214,9 @@ SWGDeviceSettings::asJsonObject() { if((fcd_pro_settings != nullptr) && (fcd_pro_settings->isSet())){ toJsonValue(QString("fcdProSettings"), fcd_pro_settings, obj, QString("SWGFCDProSettings")); } + if((fcd_pro_plus_settings != nullptr) && (fcd_pro_plus_settings->isSet())){ + toJsonValue(QString("fcdProPlusSettings"), fcd_pro_plus_settings, obj, QString("SWGFCDProPlusSettings")); + } if((file_source_settings != nullptr) && (file_source_settings->isSet())){ toJsonValue(QString("fileSourceSettings"), file_source_settings, obj, QString("SWGFileSourceSettings")); } @@ -297,6 +309,16 @@ SWGDeviceSettings::setFcdProSettings(SWGFCDProSettings* fcd_pro_settings) { this->m_fcd_pro_settings_isSet = true; } +SWGFCDProPlusSettings* +SWGDeviceSettings::getFcdProPlusSettings() { + return fcd_pro_plus_settings; +} +void +SWGDeviceSettings::setFcdProPlusSettings(SWGFCDProPlusSettings* fcd_pro_plus_settings) { + this->fcd_pro_plus_settings = fcd_pro_plus_settings; + this->m_fcd_pro_plus_settings_isSet = true; +} + SWGFileSourceSettings* SWGDeviceSettings::getFileSourceSettings() { return file_source_settings; @@ -369,6 +391,7 @@ SWGDeviceSettings::isSet(){ if(blade_rf_input_settings != nullptr && blade_rf_input_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf_output_settings != nullptr && blade_rf_output_settings->isSet()){ isObjectUpdated = true; break;} if(fcd_pro_settings != nullptr && fcd_pro_settings->isSet()){ isObjectUpdated = true; break;} + if(fcd_pro_plus_settings != nullptr && fcd_pro_plus_settings->isSet()){ isObjectUpdated = true; break;} if(file_source_settings != nullptr && file_source_settings->isSet()){ isObjectUpdated = true; break;} if(hack_rf_input_settings != nullptr && hack_rf_input_settings->isSet()){ isObjectUpdated = true; break;} if(hack_rf_output_settings != nullptr && hack_rf_output_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 142858ca8..3768d9223 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -26,6 +26,7 @@ #include "SWGAirspySettings.h" #include "SWGBladeRFInputSettings.h" #include "SWGBladeRFOutputSettings.h" +#include "SWGFCDProPlusSettings.h" #include "SWGFCDProSettings.h" #include "SWGFileSourceSettings.h" #include "SWGHackRFInputSettings.h" @@ -74,6 +75,9 @@ public: SWGFCDProSettings* getFcdProSettings(); void setFcdProSettings(SWGFCDProSettings* fcd_pro_settings); + SWGFCDProPlusSettings* getFcdProPlusSettings(); + void setFcdProPlusSettings(SWGFCDProPlusSettings* fcd_pro_plus_settings); + SWGFileSourceSettings* getFileSourceSettings(); void setFileSourceSettings(SWGFileSourceSettings* file_source_settings); @@ -117,6 +121,9 @@ private: SWGFCDProSettings* fcd_pro_settings; bool m_fcd_pro_settings_isSet; + SWGFCDProPlusSettings* fcd_pro_plus_settings; + bool m_fcd_pro_plus_settings_isSet; + SWGFileSourceSettings* file_source_settings; bool m_file_source_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp new file mode 100644 index 000000000..5d495a06d --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp @@ -0,0 +1,381 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGFCDProPlusSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGFCDProPlusSettings::SWGFCDProPlusSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGFCDProPlusSettings::SWGFCDProPlusSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + range_low = 0; + m_range_low_isSet = false; + lna_gain = 0; + m_lna_gain_isSet = false; + mix_gain = 0; + m_mix_gain_isSet = false; + bias_t = 0; + m_bias_t_isSet = false; + if_gain = 0; + m_if_gain_isSet = false; + if_filter_index = 0; + m_if_filter_index_isSet = false; + rf_filter_index = 0; + m_rf_filter_index_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_imbalance = 0; + m_iq_imbalance_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; +} + +SWGFCDProPlusSettings::~SWGFCDProPlusSettings() { + this->cleanup(); +} + +void +SWGFCDProPlusSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + range_low = 0; + m_range_low_isSet = false; + lna_gain = 0; + m_lna_gain_isSet = false; + mix_gain = 0; + m_mix_gain_isSet = false; + bias_t = 0; + m_bias_t_isSet = false; + if_gain = 0; + m_if_gain_isSet = false; + if_filter_index = 0; + m_if_filter_index_isSet = false; + rf_filter_index = 0; + m_rf_filter_index_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_imbalance = 0; + m_iq_imbalance_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; +} + +void +SWGFCDProPlusSettings::cleanup() { + + + + + + + + + + + + + + if(file_record_name != nullptr) { + delete file_record_name; + } +} + +SWGFCDProPlusSettings* +SWGFCDProPlusSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGFCDProPlusSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&range_low, pJson["rangeLow"], "qint32", ""); + + ::SWGSDRangel::setValue(&lna_gain, pJson["lnaGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&mix_gain, pJson["mixGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&bias_t, pJson["biasT"], "qint32", ""); + + ::SWGSDRangel::setValue(&if_gain, pJson["ifGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&if_filter_index, pJson["ifFilterIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&rf_filter_index, pJson["rfFilterIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); + + ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); + + ::SWGSDRangel::setValue(&iq_imbalance, pJson["iqImbalance"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + +} + +QString +SWGFCDProPlusSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGFCDProPlusSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_range_low_isSet){ + obj->insert("rangeLow", QJsonValue(range_low)); + } + if(m_lna_gain_isSet){ + obj->insert("lnaGain", QJsonValue(lna_gain)); + } + if(m_mix_gain_isSet){ + obj->insert("mixGain", QJsonValue(mix_gain)); + } + if(m_bias_t_isSet){ + obj->insert("biasT", QJsonValue(bias_t)); + } + if(m_if_gain_isSet){ + obj->insert("ifGain", QJsonValue(if_gain)); + } + if(m_if_filter_index_isSet){ + obj->insert("ifFilterIndex", QJsonValue(if_filter_index)); + } + if(m_rf_filter_index_isSet){ + obj->insert("rfFilterIndex", QJsonValue(rf_filter_index)); + } + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); + } + if(m_dc_block_isSet){ + obj->insert("dcBlock", QJsonValue(dc_block)); + } + if(m_iq_imbalance_isSet){ + obj->insert("iqImbalance", QJsonValue(iq_imbalance)); + } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGFCDProPlusSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGFCDProPlusSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getRangeLow() { + return range_low; +} +void +SWGFCDProPlusSettings::setRangeLow(qint32 range_low) { + this->range_low = range_low; + this->m_range_low_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getLnaGain() { + return lna_gain; +} +void +SWGFCDProPlusSettings::setLnaGain(qint32 lna_gain) { + this->lna_gain = lna_gain; + this->m_lna_gain_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getMixGain() { + return mix_gain; +} +void +SWGFCDProPlusSettings::setMixGain(qint32 mix_gain) { + this->mix_gain = mix_gain; + this->m_mix_gain_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getBiasT() { + return bias_t; +} +void +SWGFCDProPlusSettings::setBiasT(qint32 bias_t) { + this->bias_t = bias_t; + this->m_bias_t_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getIfGain() { + return if_gain; +} +void +SWGFCDProPlusSettings::setIfGain(qint32 if_gain) { + this->if_gain = if_gain; + this->m_if_gain_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getIfFilterIndex() { + return if_filter_index; +} +void +SWGFCDProPlusSettings::setIfFilterIndex(qint32 if_filter_index) { + this->if_filter_index = if_filter_index; + this->m_if_filter_index_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getRfFilterIndex() { + return rf_filter_index; +} +void +SWGFCDProPlusSettings::setRfFilterIndex(qint32 rf_filter_index) { + this->rf_filter_index = rf_filter_index; + this->m_rf_filter_index_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getLOppmTenths() { + return l_oppm_tenths; +} +void +SWGFCDProPlusSettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getDcBlock() { + return dc_block; +} +void +SWGFCDProPlusSettings::setDcBlock(qint32 dc_block) { + this->dc_block = dc_block; + this->m_dc_block_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getIqImbalance() { + return iq_imbalance; +} +void +SWGFCDProPlusSettings::setIqImbalance(qint32 iq_imbalance) { + this->iq_imbalance = iq_imbalance; + this->m_iq_imbalance_isSet = true; +} + +qint32 +SWGFCDProPlusSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGFCDProPlusSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGFCDProPlusSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGFCDProPlusSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + +QString* +SWGFCDProPlusSettings::getFileRecordName() { + return file_record_name; +} +void +SWGFCDProPlusSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + + +bool +SWGFCDProPlusSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_range_low_isSet){ isObjectUpdated = true; break;} + if(m_lna_gain_isSet){ isObjectUpdated = true; break;} + if(m_mix_gain_isSet){ isObjectUpdated = true; break;} + if(m_bias_t_isSet){ isObjectUpdated = true; break;} + if(m_if_gain_isSet){ isObjectUpdated = true; break;} + if(m_if_filter_index_isSet){ isObjectUpdated = true; break;} + if(m_rf_filter_index_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} + if(m_dc_block_isSet){ isObjectUpdated = true; break;} + if(m_iq_imbalance_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h new file mode 100644 index 000000000..7b31f6632 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h @@ -0,0 +1,137 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGFCDProPlusSettings.h + * + * FCDProPlus + */ + +#ifndef SWGFCDProPlusSettings_H_ +#define SWGFCDProPlusSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGFCDProPlusSettings: public SWGObject { +public: + SWGFCDProPlusSettings(); + SWGFCDProPlusSettings(QString* json); + virtual ~SWGFCDProPlusSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGFCDProPlusSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getRangeLow(); + void setRangeLow(qint32 range_low); + + qint32 getLnaGain(); + void setLnaGain(qint32 lna_gain); + + qint32 getMixGain(); + void setMixGain(qint32 mix_gain); + + qint32 getBiasT(); + void setBiasT(qint32 bias_t); + + qint32 getIfGain(); + void setIfGain(qint32 if_gain); + + qint32 getIfFilterIndex(); + void setIfFilterIndex(qint32 if_filter_index); + + qint32 getRfFilterIndex(); + void setRfFilterIndex(qint32 rf_filter_index); + + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); + + qint32 getDcBlock(); + void setDcBlock(qint32 dc_block); + + qint32 getIqImbalance(); + void setIqImbalance(qint32 iq_imbalance); + + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 range_low; + bool m_range_low_isSet; + + qint32 lna_gain; + bool m_lna_gain_isSet; + + qint32 mix_gain; + bool m_mix_gain_isSet; + + qint32 bias_t; + bool m_bias_t_isSet; + + qint32 if_gain; + bool m_if_gain_isSet; + + qint32 if_filter_index; + bool m_if_filter_index_isSet; + + qint32 rf_filter_index; + bool m_rf_filter_index_isSet; + + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; + + qint32 dc_block; + bool m_dc_block_isSet; + + qint32 iq_imbalance; + bool m_iq_imbalance_isSet; + + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + +}; + +} + +#endif /* SWGFCDProPlusSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 83744bff4..472e6ef41 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -49,6 +49,7 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGErrorResponse.h" +#include "SWGFCDProPlusSettings.h" #include "SWGFCDProSettings.h" #include "SWGFileSourceSettings.h" #include "SWGHackRFInputSettings.h" @@ -195,6 +196,9 @@ namespace SWGSDRangel { if(QString("SWGErrorResponse").compare(type) == 0) { return new SWGErrorResponse(); } + if(QString("SWGFCDProPlusSettings").compare(type) == 0) { + return new SWGFCDProPlusSettings(); + } if(QString("SWGFCDProSettings").compare(type) == 0) { return new SWGFCDProSettings(); } From 4c31da6c174e576c10373aa8b413f54a4ae35ce4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 11:40:37 +0200 Subject: [PATCH 465/956] File source input: implemeted WEB API for reporting --- plugins/samplesource/airspy/airspyinput.cpp | 1 - .../samplesource/filesource/filesourcegui.cpp | 1 - .../filesource/filesourceinput.cpp | 49 ++++ .../samplesource/filesource/filesourceinput.h | 5 + sdrbase/resources/webapi/doc/html2/index.html | 33 ++- .../doc/swagger/include/FileSource.yaml | 24 +- .../resources/webapi/doc/swagger/swagger.yaml | 2 + .../api/swagger/include/FileSource.yaml | 24 +- swagger/sdrangel/api/swagger/swagger.yaml | 2 + swagger/sdrangel/code/html2/index.html | 33 ++- .../code/qt5/client/SWGDeviceReport.cpp | 23 ++ .../code/qt5/client/SWGDeviceReport.h | 7 + .../code/qt5/client/SWGFileSourceReport.cpp | 219 ++++++++++++++++++ .../code/qt5/client/SWGFileSourceReport.h | 89 +++++++ .../code/qt5/client/SWGModelFactory.h | 4 + 15 files changed, 510 insertions(+), 6 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 0beea475e..78396f660 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -714,7 +714,6 @@ int AirspyInput::webapiReportGet( return 200; } - void AirspyInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspySettings& settings) { response.getAirspySettings()->setCenterFrequency(settings.m_centerFrequency); diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index 23f27a956..a3049fef9 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -337,7 +337,6 @@ void FileSourceGui::updateWithStreamTime() t = t.addSecs(t_sec); t = t.addMSecs(t_msec); QString s_timems = t.toString("HH:mm:ss.zzz"); - QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); quint64 startingTimeStampMsec = (quint64) m_startingTimeStamp * 1000LL; diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index d225318e2..3736dcc0a 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -21,6 +21,8 @@ #include "SWGDeviceSettings.h" #include "SWGFileSourceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGFileSourceSettings.h" #include "util/simpleserializer.h" #include "dsp/dspcommands.h" @@ -376,3 +378,50 @@ int FileSourceInput::webapiRun( return 200; } +int FileSourceInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFileSourceReport(new SWGSDRangel::SWGFileSourceReport()); + response.getFileSourceReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void FileSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + int t_sec = 0; + int t_msec = 0; + std::size_t samplesCount = 0; + + if (m_fileSourceThread) { + samplesCount = m_fileSourceThread->getSamplesCount(); + } + + if (m_sampleRate > 0) + { + t_sec = samplesCount / m_sampleRate; + t_msec = (samplesCount - (t_sec * m_sampleRate)) * 1000 / m_sampleRate; + } + + QTime t(0, 0, 0, 0); + t = t.addSecs(t_sec); + t = t.addMSecs(t_msec); + response.getFileSourceReport()->setElapsedTime(new QString(t.toString("HH:mm:ss.zzz"))); + + quint64 startingTimeStampMsec = (quint64) m_startingTimeStamp * 1000LL; + QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); + dt = dt.addSecs((quint64) t_sec); + dt = dt.addMSecs((quint64) t_msec); + response.getFileSourceReport()->setAbsoluteTime(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz"))); + + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addSecs(m_recordLength); + response.getFileSourceReport()->setDurationTime(new QString(recordLength.toString("HH:mm:ss"))); + + response.getFileSourceReport()->setFileName(new QString(m_fileName)); + response.getFileSourceReport()->setSampleRate(m_sampleRate); + response.getFileSourceReport()->setSampleSize(m_sampleSize); +} + + diff --git a/plugins/samplesource/filesource/filesourceinput.h b/plugins/samplesource/filesource/filesourceinput.h index 075a40726..38c160261 100644 --- a/plugins/samplesource/filesource/filesourceinput.h +++ b/plugins/samplesource/filesource/filesourceinput.h @@ -261,6 +261,10 @@ public: SWGSDRangel::SWGDeviceState& response, QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + private: DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; @@ -279,6 +283,7 @@ public: void openFileStream(); void seekFileStream(int seekPercentage); bool applySettings(const FileSourceSettings& settings, bool force = false); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif // INCLUDE_FILESOURCEINPUT_H diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index ddadc405b..9a367afc3 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1711,6 +1711,9 @@ margin-bottom: 20px; }, "airspyHFReport" : { "$ref" : "#/definitions/AirspyHFReport" + }, + "fileSourceReport" : { + "$ref" : "#/definitions/FileSourceReport" } }, "description" : "Base device report. The specific device report present depeds on deviceHwType" @@ -1953,6 +1956,34 @@ margin-bottom: 20px; } }, "description" : "FCDPro" +}; + defs.FileSourceReport = { + "properties" : { + "fileName" : { + "type" : "string" + }, + "sampleRate" : { + "type" : "integer", + "description" : "Record sample rate in S/s" + }, + "sampleSize" : { + "type" : "integer", + "description" : "Record sample size in number of bits" + }, + "absoluteTime" : { + "type" : "string", + "description" : "Absolute record time string representation" + }, + "elapsedTime" : { + "type" : "string", + "description" : "Elapsed time since beginning string representation" + }, + "durationTime" : { + "type" : "string", + "description" : "Duration time string representation" + } + }, + "description" : "FileSource" }; defs.FileSourceSettings = { "properties" : { @@ -21830,7 +21861,7 @@ except ApiException as e:
    - Generated 2018-05-26T10:50:36.916+02:00 + Generated 2018-05-26T11:35:33.686+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/FileSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/FileSource.yaml index ffa8ba650..2a3c847f9 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/FileSource.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/FileSource.yaml @@ -2,4 +2,26 @@ FileSourceSettings: description: FileSource properties: fileName: - type: string \ No newline at end of file + type: string + +FileSourceReport: + description: FileSource + properties: + fileName: + type: string + sampleRate: + description: Record sample rate in S/s + type: integer + sampleSize: + description: Record sample size in number of bits + type: integer + absoluteTime: + description: Absolute record time string representation + type: string + elapsedTime: + description: Elapsed time since beginning string representation + type: string + durationTime: + description: Duration time string representation + type: string + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 1519e0336..7e1cb2921 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1787,6 +1787,8 @@ definitions: $ref: "/doc/swagger/include/Airspy.yaml#/AirspyReport" airspyHFReport: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFReport" + fileSourceReport: + $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/api/swagger/include/FileSource.yaml b/swagger/sdrangel/api/swagger/include/FileSource.yaml index ffa8ba650..2a3c847f9 100644 --- a/swagger/sdrangel/api/swagger/include/FileSource.yaml +++ b/swagger/sdrangel/api/swagger/include/FileSource.yaml @@ -2,4 +2,26 @@ FileSourceSettings: description: FileSource properties: fileName: - type: string \ No newline at end of file + type: string + +FileSourceReport: + description: FileSource + properties: + fileName: + type: string + sampleRate: + description: Record sample rate in S/s + type: integer + sampleSize: + description: Record sample size in number of bits + type: integer + absoluteTime: + description: Absolute record time string representation + type: string + elapsedTime: + description: Elapsed time since beginning string representation + type: string + durationTime: + description: Duration time string representation + type: string + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 597b2d287..195bb5aac 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1787,6 +1787,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/Airspy.yaml#/AirspyReport" airspyHFReport: $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFReport" + fileSourceReport: + $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index ddadc405b..9a367afc3 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1711,6 +1711,9 @@ margin-bottom: 20px; }, "airspyHFReport" : { "$ref" : "#/definitions/AirspyHFReport" + }, + "fileSourceReport" : { + "$ref" : "#/definitions/FileSourceReport" } }, "description" : "Base device report. The specific device report present depeds on deviceHwType" @@ -1953,6 +1956,34 @@ margin-bottom: 20px; } }, "description" : "FCDPro" +}; + defs.FileSourceReport = { + "properties" : { + "fileName" : { + "type" : "string" + }, + "sampleRate" : { + "type" : "integer", + "description" : "Record sample rate in S/s" + }, + "sampleSize" : { + "type" : "integer", + "description" : "Record sample size in number of bits" + }, + "absoluteTime" : { + "type" : "string", + "description" : "Absolute record time string representation" + }, + "elapsedTime" : { + "type" : "string", + "description" : "Elapsed time since beginning string representation" + }, + "durationTime" : { + "type" : "string", + "description" : "Duration time string representation" + } + }, + "description" : "FileSource" }; defs.FileSourceSettings = { "properties" : { @@ -21830,7 +21861,7 @@ except ApiException as e:
    - Generated 2018-05-26T10:50:36.916+02:00 + Generated 2018-05-26T11:35:33.686+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index 8eead58ba..a453fd4f8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -36,6 +36,8 @@ SWGDeviceReport::SWGDeviceReport() { m_airspy_report_isSet = false; airspy_hf_report = nullptr; m_airspy_hf_report_isSet = false; + file_source_report = nullptr; + m_file_source_report_isSet = false; } SWGDeviceReport::~SWGDeviceReport() { @@ -52,6 +54,8 @@ SWGDeviceReport::init() { m_airspy_report_isSet = false; airspy_hf_report = new SWGAirspyHFReport(); m_airspy_hf_report_isSet = false; + file_source_report = new SWGFileSourceReport(); + m_file_source_report_isSet = false; } void @@ -66,6 +70,9 @@ SWGDeviceReport::cleanup() { if(airspy_hf_report != nullptr) { delete airspy_hf_report; } + if(file_source_report != nullptr) { + delete file_source_report; + } } SWGDeviceReport* @@ -87,6 +94,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&airspy_hf_report, pJson["airspyHFReport"], "SWGAirspyHFReport", "SWGAirspyHFReport"); + ::SWGSDRangel::setValue(&file_source_report, pJson["fileSourceReport"], "SWGFileSourceReport", "SWGFileSourceReport"); + } QString @@ -115,6 +124,9 @@ SWGDeviceReport::asJsonObject() { if((airspy_hf_report != nullptr) && (airspy_hf_report->isSet())){ toJsonValue(QString("airspyHFReport"), airspy_hf_report, obj, QString("SWGAirspyHFReport")); } + if((file_source_report != nullptr) && (file_source_report->isSet())){ + toJsonValue(QString("fileSourceReport"), file_source_report, obj, QString("SWGFileSourceReport")); + } return obj; } @@ -159,6 +171,16 @@ SWGDeviceReport::setAirspyHfReport(SWGAirspyHFReport* airspy_hf_report) { this->m_airspy_hf_report_isSet = true; } +SWGFileSourceReport* +SWGDeviceReport::getFileSourceReport() { + return file_source_report; +} +void +SWGDeviceReport::setFileSourceReport(SWGFileSourceReport* file_source_report) { + this->file_source_report = file_source_report; + this->m_file_source_report_isSet = true; +} + bool SWGDeviceReport::isSet(){ @@ -168,6 +190,7 @@ SWGDeviceReport::isSet(){ if(m_tx_isSet){ isObjectUpdated = true; break;} if(airspy_report != nullptr && airspy_report->isSet()){ isObjectUpdated = true; break;} if(airspy_hf_report != nullptr && airspy_hf_report->isSet()){ isObjectUpdated = true; break;} + if(file_source_report != nullptr && file_source_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index 8b7b0f1b3..d719692e4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -24,6 +24,7 @@ #include "SWGAirspyHFReport.h" #include "SWGAirspyReport.h" +#include "SWGFileSourceReport.h" #include #include "SWGObject.h" @@ -56,6 +57,9 @@ public: SWGAirspyHFReport* getAirspyHfReport(); void setAirspyHfReport(SWGAirspyHFReport* airspy_hf_report); + SWGFileSourceReport* getFileSourceReport(); + void setFileSourceReport(SWGFileSourceReport* file_source_report); + virtual bool isSet() override; @@ -72,6 +76,9 @@ private: SWGAirspyHFReport* airspy_hf_report; bool m_airspy_hf_report_isSet; + SWGFileSourceReport* file_source_report; + bool m_file_source_report_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp new file mode 100644 index 000000000..d14ea84ac --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp @@ -0,0 +1,219 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGFileSourceReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGFileSourceReport::SWGFileSourceReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGFileSourceReport::SWGFileSourceReport() { + file_name = nullptr; + m_file_name_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + sample_size = 0; + m_sample_size_isSet = false; + absolute_time = nullptr; + m_absolute_time_isSet = false; + elapsed_time = nullptr; + m_elapsed_time_isSet = false; + duration_time = nullptr; + m_duration_time_isSet = false; +} + +SWGFileSourceReport::~SWGFileSourceReport() { + this->cleanup(); +} + +void +SWGFileSourceReport::init() { + file_name = new QString(""); + m_file_name_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + sample_size = 0; + m_sample_size_isSet = false; + absolute_time = new QString(""); + m_absolute_time_isSet = false; + elapsed_time = new QString(""); + m_elapsed_time_isSet = false; + duration_time = new QString(""); + m_duration_time_isSet = false; +} + +void +SWGFileSourceReport::cleanup() { + if(file_name != nullptr) { + delete file_name; + } + + + if(absolute_time != nullptr) { + delete absolute_time; + } + if(elapsed_time != nullptr) { + delete elapsed_time; + } + if(duration_time != nullptr) { + delete duration_time; + } +} + +SWGFileSourceReport* +SWGFileSourceReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGFileSourceReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&file_name, pJson["fileName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_size, pJson["sampleSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&absolute_time, pJson["absoluteTime"], "QString", "QString"); + + ::SWGSDRangel::setValue(&elapsed_time, pJson["elapsedTime"], "QString", "QString"); + + ::SWGSDRangel::setValue(&duration_time, pJson["durationTime"], "QString", "QString"); + +} + +QString +SWGFileSourceReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGFileSourceReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(file_name != nullptr && *file_name != QString("")){ + toJsonValue(QString("fileName"), file_name, obj, QString("QString")); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + if(m_sample_size_isSet){ + obj->insert("sampleSize", QJsonValue(sample_size)); + } + if(absolute_time != nullptr && *absolute_time != QString("")){ + toJsonValue(QString("absoluteTime"), absolute_time, obj, QString("QString")); + } + if(elapsed_time != nullptr && *elapsed_time != QString("")){ + toJsonValue(QString("elapsedTime"), elapsed_time, obj, QString("QString")); + } + if(duration_time != nullptr && *duration_time != QString("")){ + toJsonValue(QString("durationTime"), duration_time, obj, QString("QString")); + } + + return obj; +} + +QString* +SWGFileSourceReport::getFileName() { + return file_name; +} +void +SWGFileSourceReport::setFileName(QString* file_name) { + this->file_name = file_name; + this->m_file_name_isSet = true; +} + +qint32 +SWGFileSourceReport::getSampleRate() { + return sample_rate; +} +void +SWGFileSourceReport::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + +qint32 +SWGFileSourceReport::getSampleSize() { + return sample_size; +} +void +SWGFileSourceReport::setSampleSize(qint32 sample_size) { + this->sample_size = sample_size; + this->m_sample_size_isSet = true; +} + +QString* +SWGFileSourceReport::getAbsoluteTime() { + return absolute_time; +} +void +SWGFileSourceReport::setAbsoluteTime(QString* absolute_time) { + this->absolute_time = absolute_time; + this->m_absolute_time_isSet = true; +} + +QString* +SWGFileSourceReport::getElapsedTime() { + return elapsed_time; +} +void +SWGFileSourceReport::setElapsedTime(QString* elapsed_time) { + this->elapsed_time = elapsed_time; + this->m_elapsed_time_isSet = true; +} + +QString* +SWGFileSourceReport::getDurationTime() { + return duration_time; +} +void +SWGFileSourceReport::setDurationTime(QString* duration_time) { + this->duration_time = duration_time; + this->m_duration_time_isSet = true; +} + + +bool +SWGFileSourceReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(file_name != nullptr && *file_name != QString("")){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_sample_size_isSet){ isObjectUpdated = true; break;} + if(absolute_time != nullptr && *absolute_time != QString("")){ isObjectUpdated = true; break;} + if(elapsed_time != nullptr && *elapsed_time != QString("")){ isObjectUpdated = true; break;} + if(duration_time != nullptr && *duration_time != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h new file mode 100644 index 000000000..32aaaec4b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h @@ -0,0 +1,89 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGFileSourceReport.h + * + * FileSource + */ + +#ifndef SWGFileSourceReport_H_ +#define SWGFileSourceReport_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGFileSourceReport: public SWGObject { +public: + SWGFileSourceReport(); + SWGFileSourceReport(QString* json); + virtual ~SWGFileSourceReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGFileSourceReport* fromJson(QString &jsonString) override; + + QString* getFileName(); + void setFileName(QString* file_name); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + qint32 getSampleSize(); + void setSampleSize(qint32 sample_size); + + QString* getAbsoluteTime(); + void setAbsoluteTime(QString* absolute_time); + + QString* getElapsedTime(); + void setElapsedTime(QString* elapsed_time); + + QString* getDurationTime(); + void setDurationTime(QString* duration_time); + + + virtual bool isSet() override; + +private: + QString* file_name; + bool m_file_name_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + + qint32 sample_size; + bool m_sample_size_isSet; + + QString* absolute_time; + bool m_absolute_time_isSet; + + QString* elapsed_time; + bool m_elapsed_time_isSet; + + QString* duration_time; + bool m_duration_time_isSet; + +}; + +} + +#endif /* SWGFileSourceReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 472e6ef41..9dacba422 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -51,6 +51,7 @@ #include "SWGErrorResponse.h" #include "SWGFCDProPlusSettings.h" #include "SWGFCDProSettings.h" +#include "SWGFileSourceReport.h" #include "SWGFileSourceSettings.h" #include "SWGHackRFInputSettings.h" #include "SWGHackRFOutputSettings.h" @@ -202,6 +203,9 @@ namespace SWGSDRangel { if(QString("SWGFCDProSettings").compare(type) == 0) { return new SWGFCDProSettings(); } + if(QString("SWGFileSourceReport").compare(type) == 0) { + return new SWGFileSourceReport(); + } if(QString("SWGFileSourceSettings").compare(type) == 0) { return new SWGFileSourceSettings(); } From 73a3291008859b55fee6935ec329116b6c48b8d7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 13:24:35 +0200 Subject: [PATCH 466/956] Perseus input: implemeted WEB API --- plugins/samplesource/perseus/perseusinput.cpp | 111 ++++++ plugins/samplesource/perseus/perseusinput.h | 16 + sdrbase/resources/res.qrc | 15 +- sdrbase/resources/webapi/doc/html2/index.html | 63 +++- .../webapi/doc/swagger/include/Perseus.yaml | 44 +++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + sdrbase/webapi/webapirequestmapper.cpp | 77 ++++- .../sdrangel/api/swagger/include/Perseus.yaml | 44 +++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 63 +++- .../code/qt5/client/SWGDeviceReport.cpp | 23 ++ .../code/qt5/client/SWGDeviceReport.h | 7 + .../code/qt5/client/SWGDeviceSettings.cpp | 23 ++ .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../code/qt5/client/SWGPerseusReport.cpp | 112 ++++++ .../code/qt5/client/SWGPerseusReport.h | 60 ++++ .../code/qt5/client/SWGPerseusSettings.cpp | 318 ++++++++++++++++++ .../code/qt5/client/SWGPerseusSettings.h | 119 +++++++ swagger/sdrangel/examples/rx_test.py | 9 + 20 files changed, 1103 insertions(+), 24 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/Perseus.yaml create mode 100644 swagger/sdrangel/api/swagger/include/Perseus.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGPerseusReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h diff --git a/plugins/samplesource/perseus/perseusinput.cpp b/plugins/samplesource/perseus/perseusinput.cpp index beca7d33f..91ab52cc2 100644 --- a/plugins/samplesource/perseus/perseusinput.cpp +++ b/plugins/samplesource/perseus/perseusinput.cpp @@ -18,6 +18,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGPerseusReport.h" #include "dsp/filerecord.h" #include "dsp/dspcommands.h" @@ -423,3 +425,112 @@ int PerseusInput::webapiRun( return 200; } + +int PerseusInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPerseusSettings(new SWGSDRangel::SWGPerseusSettings()); + response.getPerseusSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int PerseusInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + PerseusSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getPerseusSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getPerseusSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("devSampleRateIndex")) { + settings.m_devSampleRateIndex = response.getPerseusSettings()->getDevSampleRateIndex(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getPerseusSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("adcDither")) { + settings.m_adcDither = response.getPerseusSettings()->getAdcDither() != 0; + } + if (deviceSettingsKeys.contains("adcPreamp")) { + settings.m_adcPreamp = response.getPerseusSettings()->getAdcPreamp() != 0; + } + if (deviceSettingsKeys.contains("wideBand")) { + settings.m_wideBand = response.getPerseusSettings()->getWideBand() != 0; + } + if (deviceSettingsKeys.contains("attenuator")) { + int attenuator = response.getPerseusSettings()->getAttenuator(); + attenuator = attenuator < 0 ? 0 : attenuator > 3 ? 3 : attenuator; + settings.m_attenuator = (PerseusSettings::Attenuator) attenuator; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getPerseusSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getPerseusSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getPerseusSettings()->getFileRecordName(); + } + + MsgConfigurePerseus *msg = MsgConfigurePerseus::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigurePerseus *msgToGUI = MsgConfigurePerseus::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int PerseusInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPerseusReport(new SWGSDRangel::SWGPerseusReport()); + response.getPerseusReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void PerseusInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PerseusSettings& settings) +{ + response.getPerseusSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getPerseusSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getPerseusSettings()->setDevSampleRateIndex(settings.m_devSampleRateIndex); + response.getPerseusSettings()->setLog2Decim(settings.m_log2Decim); + response.getPerseusSettings()->setAdcDither(settings.m_adcDither ? 1 : 0); + response.getPerseusSettings()->setAdcPreamp(settings.m_adcPreamp ? 1 : 0); + response.getPerseusSettings()->setWideBand(settings.m_wideBand ? 1 : 0); + response.getPerseusSettings()->setAttenuator((int) settings.m_attenuator); + response.getPerseusSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getPerseusSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getPerseusSettings()->getFileRecordName()) { + *response.getPerseusSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getPerseusSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +void PerseusInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getPerseusReport()->setSampleRates(new QList); + + for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) + { + response.getPerseusReport()->getSampleRates()->append(new SWGSDRangel::SWGAirspyReport_sampleRates); + response.getPerseusReport()->getSampleRates()->back()->setSampleRate(*it); + } +} + diff --git a/plugins/samplesource/perseus/perseusinput.h b/plugins/samplesource/perseus/perseusinput.h index 9973921c7..f30ed9877 100644 --- a/plugins/samplesource/perseus/perseusinput.h +++ b/plugins/samplesource/perseus/perseusinput.h @@ -110,6 +110,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -135,6 +149,8 @@ private: void closeDevice(); void setDeviceCenterFrequency(quint64 freq, const PerseusSettings& settings); bool applySettings(const PerseusSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PerseusSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif /* PLUGINS_SAMPLESOURCE_PERSEUS_PERSEUSINPUT_H_ */ diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 7c837c5ce..9a40ae94a 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -2,26 +2,29 @@ webapi/doc/html2/index.html webapi/doc/swagger/swagger.yaml - webapi/doc/swagger/include/CWKeyer.yaml webapi/doc/swagger/include/Airspy.yaml webapi/doc/swagger/include/AirspyHF.yaml - webapi/doc/swagger/include/BladeRF.yaml - webapi/doc/swagger/include/FileSource.yaml - webapi/doc/swagger/include/HackRF.yaml - webapi/doc/swagger/include/LimeSdr.yaml webapi/doc/swagger/include/AMDemod.yaml webapi/doc/swagger/include/AMMod.yaml webapi/doc/swagger/include/ATVMod.yaml webapi/doc/swagger/include/BFMDemod.yaml + webapi/doc/swagger/include/BladeRF.yaml + webapi/doc/swagger/include/CWKeyer.yaml webapi/doc/swagger/include/DSDDemod.yaml + webapi/doc/swagger/include/FCDPro.yaml + webapi/doc/swagger/include/FCDProPlus.yaml + webapi/doc/swagger/include/FileSource.yaml + webapi/doc/swagger/include/HackRF.yaml + webapi/doc/swagger/include/LimeSdr.yaml webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml + webapi/doc/swagger/include/Perseus.yaml + webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger/include/SSBMod.yaml webapi/doc/swagger/include/UDPSink.yaml webapi/doc/swagger/include/UDPSrc.yaml webapi/doc/swagger/include/WFMDemod.yaml webapi/doc/swagger/include/WFMMod.yaml - webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger-ui/swagger-ui.js.map webapi/doc/swagger-ui/swagger-ui.js webapi/doc/swagger-ui/swagger-ui.css.map diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 9a367afc3..597e8388b 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1714,6 +1714,9 @@ margin-bottom: 20px; }, "fileSourceReport" : { "$ref" : "#/definitions/FileSourceReport" + }, + "perseusReport" : { + "$ref" : "#/definitions/PerseusReport" } }, "description" : "Base device report. The specific device report present depeds on deviceHwType" @@ -1803,6 +1806,9 @@ margin-bottom: 20px; "limeSdrOutputSettings" : { "$ref" : "#/definitions/LimeSdrOutputSettings" }, + "perseusSettings" : { + "$ref" : "#/definitions/PerseusSettings" + }, "rtlSdrSettings" : { "$ref" : "#/definitions/RtlSdrSettings" } @@ -2463,6 +2469,61 @@ margin-bottom: 20px; } }, "description" : "NFMMod" +}; + defs.PerseusReport = { + "properties" : { + "sampleRates" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AirspyReport_sampleRates" + } + } + }, + "description" : "Perseus" +}; + defs.PerseusSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "devSampleRateIndex" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "adcDither" : { + "type" : "integer", + "description" : "ADC dithering (1 if active else 0)" + }, + "adcPreamp" : { + "type" : "integer", + "description" : "ADC preamplifier (1 if active else 0)" + }, + "wideBand" : { + "type" : "integer", + "description" : "Wideband mode i.e. bypass automatic RF filter (1 if active else 0)" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + }, + "attenuator" : { + "type" : "integer", + "description" : "Attenuator setting in Bels (0, 10, 20 30 dB)" + } + }, + "description" : "Perseus" }; defs.PresetExport = { "properties" : { @@ -21861,7 +21922,7 @@ except ApiException as e:
    - Generated 2018-05-26T11:35:33.686+02:00 + Generated 2018-05-26T12:32:33.813+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/Perseus.yaml b/sdrbase/resources/webapi/doc/swagger/include/Perseus.yaml new file mode 100644 index 000000000..79e075806 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/Perseus.yaml @@ -0,0 +1,44 @@ +PerseusSettings: + description: Perseus + properties: + centerFrequency: + type: integer + format: int64 + LOppmTenths: + type: integer + devSampleRateIndex: + type: integer + log2Decim: + type: integer + adcDither: + description: ADC dithering (1 if active else 0) + type: integer + adcPreamp: + description: ADC preamplifier (1 if active else 0) + type: integer + wideBand: + description: Wideband mode i.e. bypass automatic RF filter (1 if active else 0) + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string + attenuator: + description: Attenuator setting in Bels (0, 10, 20 30 dB) + type: integer + +PerseusReport: + description: Perseus + properties: + sampleRates: + type: array + items: + properties: + sampleRate: + description: sample rate in S/s + type: integer + + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 7e1cb2921..73e762e5c 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1767,6 +1767,8 @@ definitions: $ref: "/doc/swagger/include/LimeSdr.yaml#/LimeSdrInputSettings" limeSdrOutputSettings: $ref: "/doc/swagger/include/LimeSdr.yaml#/LimeSdrOutputSettings" + perseusSettings: + $ref: "/doc/swagger/include/Perseus.yaml#/PerseusSettings" rtlSdrSettings: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrSettings" @@ -1789,6 +1791,8 @@ definitions: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFReport" fileSourceReport: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceReport" + perseusReport: + $ref: "/doc/swagger/include/Perseus.yaml#/PerseusReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 8fea23fbd..ce331a84d 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1706,22 +1706,7 @@ bool WebAPIRequestMapper::validateDeviceSettings( QString *deviceHwType = deviceSettings.getDeviceHwType(); - if (*deviceHwType == "FileSource") - { - if (jsonObject.contains("fileSourceSettings") && jsonObject["fileSourceSettings"].isObject()) - { - QJsonObject fileSourceSettingsJsonObject = jsonObject["fileSourceSettings"].toObject(); - deviceSettingsKeys = fileSourceSettingsJsonObject.keys(); - deviceSettings.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings()); - deviceSettings.getFileSourceSettings()->fromJsonObject(fileSourceSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "Airspy") && (deviceSettings.getTx() == 0)) + if ((*deviceHwType == "Airspy") && (deviceSettings.getTx() == 0)) { if (jsonObject.contains("airspySettings") && jsonObject["airspySettings"].isObject()) { @@ -1781,6 +1766,51 @@ bool WebAPIRequestMapper::validateDeviceSettings( return false; } } + else if (*deviceHwType == "FCDPro") + { + if (jsonObject.contains("fcdProSettings") && jsonObject["fcdProSettings"].isObject()) + { + QJsonObject fcdProSettingsJsonObject = jsonObject["fcdProSettings"].toObject(); + deviceSettingsKeys = fcdProSettingsJsonObject.keys(); + deviceSettings.setFcdProSettings(new SWGSDRangel::SWGFCDProSettings()); + deviceSettings.getFcdProSettings()->fromJsonObject(fcdProSettingsJsonObject); + return true; + } + else + { + return false; + } + } + else if (*deviceHwType == "FCDProPlus") + { + if (jsonObject.contains("fcdProPlusSettings") && jsonObject["fcdProPlusSettings"].isObject()) + { + QJsonObject fcdProPlusSettingsJsonObject = jsonObject["fcdProPlusSettings"].toObject(); + deviceSettingsKeys = fcdProPlusSettingsJsonObject.keys(); + deviceSettings.setFcdProPlusSettings(new SWGSDRangel::SWGFCDProPlusSettings()); + deviceSettings.getFcdProPlusSettings()->fromJsonObject(fcdProPlusSettingsJsonObject); + return true; + } + else + { + return false; + } + } + else if (*deviceHwType == "FileSource") + { + if (jsonObject.contains("fileSourceSettings") && jsonObject["fileSourceSettings"].isObject()) + { + QJsonObject fileSourceSettingsJsonObject = jsonObject["fileSourceSettings"].toObject(); + deviceSettingsKeys = fileSourceSettingsJsonObject.keys(); + deviceSettings.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings()); + deviceSettings.getFileSourceSettings()->fromJsonObject(fileSourceSettingsJsonObject); + return true; + } + else + { + return false; + } + } else if ((*deviceHwType == "HackRF") && (deviceSettings.getTx() == 0)) { if (jsonObject.contains("hackRFInputSettings") && jsonObject["hackRFInputSettings"].isObject()) @@ -1841,6 +1871,21 @@ bool WebAPIRequestMapper::validateDeviceSettings( return false; } } + else if (*deviceHwType == "Perseus") + { + if (jsonObject.contains("perseusSettings") && jsonObject["perseusSettings"].isObject()) + { + QJsonObject perseusSettingsJsonObject = jsonObject["perseusSettings"].toObject(); + deviceSettingsKeys = perseusSettingsJsonObject.keys(); + deviceSettings.setPerseusSettings(new SWGSDRangel::SWGPerseusSettings()); + deviceSettings.getPerseusSettings()->fromJsonObject(perseusSettingsJsonObject); + return true; + } + else + { + return false; + } + } else if (*deviceHwType == "RTLSDR") { if (jsonObject.contains("rtlSdrSettings") && jsonObject["rtlSdrSettings"].isObject()) diff --git a/swagger/sdrangel/api/swagger/include/Perseus.yaml b/swagger/sdrangel/api/swagger/include/Perseus.yaml new file mode 100644 index 000000000..79e075806 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/Perseus.yaml @@ -0,0 +1,44 @@ +PerseusSettings: + description: Perseus + properties: + centerFrequency: + type: integer + format: int64 + LOppmTenths: + type: integer + devSampleRateIndex: + type: integer + log2Decim: + type: integer + adcDither: + description: ADC dithering (1 if active else 0) + type: integer + adcPreamp: + description: ADC preamplifier (1 if active else 0) + type: integer + wideBand: + description: Wideband mode i.e. bypass automatic RF filter (1 if active else 0) + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string + attenuator: + description: Attenuator setting in Bels (0, 10, 20 30 dB) + type: integer + +PerseusReport: + description: Perseus + properties: + sampleRates: + type: array + items: + properties: + sampleRate: + description: sample rate in S/s + type: integer + + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 195bb5aac..721a0b2fe 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1767,6 +1767,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/LimeSdr.yaml#/LimeSdrInputSettings" limeSdrOutputSettings: $ref: "http://localhost:8081/api/swagger/include/LimeSdr.yaml#/LimeSdrOutputSettings" + perseusSettings: + $ref: "http://localhost:8081/api/swagger/include/Perseus.yaml#/PerseusSettings" rtlSdrSettings: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrSettings" @@ -1789,6 +1791,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFReport" fileSourceReport: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceReport" + perseusReport: + $ref: "http://localhost:8081/api/swagger/include/Perseus.yaml#/PerseusReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 9a367afc3..597e8388b 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1714,6 +1714,9 @@ margin-bottom: 20px; }, "fileSourceReport" : { "$ref" : "#/definitions/FileSourceReport" + }, + "perseusReport" : { + "$ref" : "#/definitions/PerseusReport" } }, "description" : "Base device report. The specific device report present depeds on deviceHwType" @@ -1803,6 +1806,9 @@ margin-bottom: 20px; "limeSdrOutputSettings" : { "$ref" : "#/definitions/LimeSdrOutputSettings" }, + "perseusSettings" : { + "$ref" : "#/definitions/PerseusSettings" + }, "rtlSdrSettings" : { "$ref" : "#/definitions/RtlSdrSettings" } @@ -2463,6 +2469,61 @@ margin-bottom: 20px; } }, "description" : "NFMMod" +}; + defs.PerseusReport = { + "properties" : { + "sampleRates" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AirspyReport_sampleRates" + } + } + }, + "description" : "Perseus" +}; + defs.PerseusSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "devSampleRateIndex" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "adcDither" : { + "type" : "integer", + "description" : "ADC dithering (1 if active else 0)" + }, + "adcPreamp" : { + "type" : "integer", + "description" : "ADC preamplifier (1 if active else 0)" + }, + "wideBand" : { + "type" : "integer", + "description" : "Wideband mode i.e. bypass automatic RF filter (1 if active else 0)" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + }, + "attenuator" : { + "type" : "integer", + "description" : "Attenuator setting in Bels (0, 10, 20 30 dB)" + } + }, + "description" : "Perseus" }; defs.PresetExport = { "properties" : { @@ -21861,7 +21922,7 @@ except ApiException as e:
    - Generated 2018-05-26T11:35:33.686+02:00 + Generated 2018-05-26T12:32:33.813+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index a453fd4f8..7160ee595 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -38,6 +38,8 @@ SWGDeviceReport::SWGDeviceReport() { m_airspy_hf_report_isSet = false; file_source_report = nullptr; m_file_source_report_isSet = false; + perseus_report = nullptr; + m_perseus_report_isSet = false; } SWGDeviceReport::~SWGDeviceReport() { @@ -56,6 +58,8 @@ SWGDeviceReport::init() { m_airspy_hf_report_isSet = false; file_source_report = new SWGFileSourceReport(); m_file_source_report_isSet = false; + perseus_report = new SWGPerseusReport(); + m_perseus_report_isSet = false; } void @@ -73,6 +77,9 @@ SWGDeviceReport::cleanup() { if(file_source_report != nullptr) { delete file_source_report; } + if(perseus_report != nullptr) { + delete perseus_report; + } } SWGDeviceReport* @@ -96,6 +103,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&file_source_report, pJson["fileSourceReport"], "SWGFileSourceReport", "SWGFileSourceReport"); + ::SWGSDRangel::setValue(&perseus_report, pJson["perseusReport"], "SWGPerseusReport", "SWGPerseusReport"); + } QString @@ -127,6 +136,9 @@ SWGDeviceReport::asJsonObject() { if((file_source_report != nullptr) && (file_source_report->isSet())){ toJsonValue(QString("fileSourceReport"), file_source_report, obj, QString("SWGFileSourceReport")); } + if((perseus_report != nullptr) && (perseus_report->isSet())){ + toJsonValue(QString("perseusReport"), perseus_report, obj, QString("SWGPerseusReport")); + } return obj; } @@ -181,6 +193,16 @@ SWGDeviceReport::setFileSourceReport(SWGFileSourceReport* file_source_report) { this->m_file_source_report_isSet = true; } +SWGPerseusReport* +SWGDeviceReport::getPerseusReport() { + return perseus_report; +} +void +SWGDeviceReport::setPerseusReport(SWGPerseusReport* perseus_report) { + this->perseus_report = perseus_report; + this->m_perseus_report_isSet = true; +} + bool SWGDeviceReport::isSet(){ @@ -191,6 +213,7 @@ SWGDeviceReport::isSet(){ if(airspy_report != nullptr && airspy_report->isSet()){ isObjectUpdated = true; break;} if(airspy_hf_report != nullptr && airspy_hf_report->isSet()){ isObjectUpdated = true; break;} if(file_source_report != nullptr && file_source_report->isSet()){ isObjectUpdated = true; break;} + if(perseus_report != nullptr && perseus_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index d719692e4..ba3a9f73d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -25,6 +25,7 @@ #include "SWGAirspyHFReport.h" #include "SWGAirspyReport.h" #include "SWGFileSourceReport.h" +#include "SWGPerseusReport.h" #include #include "SWGObject.h" @@ -60,6 +61,9 @@ public: SWGFileSourceReport* getFileSourceReport(); void setFileSourceReport(SWGFileSourceReport* file_source_report); + SWGPerseusReport* getPerseusReport(); + void setPerseusReport(SWGPerseusReport* perseus_report); + virtual bool isSet() override; @@ -79,6 +83,9 @@ private: SWGFileSourceReport* file_source_report; bool m_file_source_report_isSet; + SWGPerseusReport* perseus_report; + bool m_perseus_report_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 1e2f855d6..04f16b434 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -54,6 +54,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_lime_sdr_input_settings_isSet = false; lime_sdr_output_settings = nullptr; m_lime_sdr_output_settings_isSet = false; + perseus_settings = nullptr; + m_perseus_settings_isSet = false; rtl_sdr_settings = nullptr; m_rtl_sdr_settings_isSet = false; } @@ -90,6 +92,8 @@ SWGDeviceSettings::init() { m_lime_sdr_input_settings_isSet = false; lime_sdr_output_settings = new SWGLimeSdrOutputSettings(); m_lime_sdr_output_settings_isSet = false; + perseus_settings = new SWGPerseusSettings(); + m_perseus_settings_isSet = false; rtl_sdr_settings = new SWGRtlSdrSettings(); m_rtl_sdr_settings_isSet = false; } @@ -133,6 +137,9 @@ SWGDeviceSettings::cleanup() { if(lime_sdr_output_settings != nullptr) { delete lime_sdr_output_settings; } + if(perseus_settings != nullptr) { + delete perseus_settings; + } if(rtl_sdr_settings != nullptr) { delete rtl_sdr_settings; } @@ -175,6 +182,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&lime_sdr_output_settings, pJson["limeSdrOutputSettings"], "SWGLimeSdrOutputSettings", "SWGLimeSdrOutputSettings"); + ::SWGSDRangel::setValue(&perseus_settings, pJson["perseusSettings"], "SWGPerseusSettings", "SWGPerseusSettings"); + ::SWGSDRangel::setValue(&rtl_sdr_settings, pJson["rtlSdrSettings"], "SWGRtlSdrSettings", "SWGRtlSdrSettings"); } @@ -232,6 +241,9 @@ SWGDeviceSettings::asJsonObject() { if((lime_sdr_output_settings != nullptr) && (lime_sdr_output_settings->isSet())){ toJsonValue(QString("limeSdrOutputSettings"), lime_sdr_output_settings, obj, QString("SWGLimeSdrOutputSettings")); } + if((perseus_settings != nullptr) && (perseus_settings->isSet())){ + toJsonValue(QString("perseusSettings"), perseus_settings, obj, QString("SWGPerseusSettings")); + } if((rtl_sdr_settings != nullptr) && (rtl_sdr_settings->isSet())){ toJsonValue(QString("rtlSdrSettings"), rtl_sdr_settings, obj, QString("SWGRtlSdrSettings")); } @@ -369,6 +381,16 @@ SWGDeviceSettings::setLimeSdrOutputSettings(SWGLimeSdrOutputSettings* lime_sdr_o this->m_lime_sdr_output_settings_isSet = true; } +SWGPerseusSettings* +SWGDeviceSettings::getPerseusSettings() { + return perseus_settings; +} +void +SWGDeviceSettings::setPerseusSettings(SWGPerseusSettings* perseus_settings) { + this->perseus_settings = perseus_settings; + this->m_perseus_settings_isSet = true; +} + SWGRtlSdrSettings* SWGDeviceSettings::getRtlSdrSettings() { return rtl_sdr_settings; @@ -397,6 +419,7 @@ SWGDeviceSettings::isSet(){ if(hack_rf_output_settings != nullptr && hack_rf_output_settings->isSet()){ isObjectUpdated = true; break;} if(lime_sdr_input_settings != nullptr && lime_sdr_input_settings->isSet()){ isObjectUpdated = true; break;} if(lime_sdr_output_settings != nullptr && lime_sdr_output_settings->isSet()){ isObjectUpdated = true; break;} + if(perseus_settings != nullptr && perseus_settings->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_settings != nullptr && rtl_sdr_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 3768d9223..9de16ea26 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -33,6 +33,7 @@ #include "SWGHackRFOutputSettings.h" #include "SWGLimeSdrInputSettings.h" #include "SWGLimeSdrOutputSettings.h" +#include "SWGPerseusSettings.h" #include "SWGRtlSdrSettings.h" #include @@ -93,6 +94,9 @@ public: SWGLimeSdrOutputSettings* getLimeSdrOutputSettings(); void setLimeSdrOutputSettings(SWGLimeSdrOutputSettings* lime_sdr_output_settings); + SWGPerseusSettings* getPerseusSettings(); + void setPerseusSettings(SWGPerseusSettings* perseus_settings); + SWGRtlSdrSettings* getRtlSdrSettings(); void setRtlSdrSettings(SWGRtlSdrSettings* rtl_sdr_settings); @@ -139,6 +143,9 @@ private: SWGLimeSdrOutputSettings* lime_sdr_output_settings; bool m_lime_sdr_output_settings_isSet; + SWGPerseusSettings* perseus_settings; + bool m_perseus_settings_isSet; + SWGRtlSdrSettings* rtl_sdr_settings; bool m_rtl_sdr_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 9dacba422..9e70778cb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -66,6 +66,8 @@ #include "SWGNFMDemodSettings.h" #include "SWGNFMModReport.h" #include "SWGNFMModSettings.h" +#include "SWGPerseusReport.h" +#include "SWGPerseusSettings.h" #include "SWGPresetExport.h" #include "SWGPresetGroup.h" #include "SWGPresetIdentifier.h" @@ -248,6 +250,12 @@ namespace SWGSDRangel { if(QString("SWGNFMModSettings").compare(type) == 0) { return new SWGNFMModSettings(); } + if(QString("SWGPerseusReport").compare(type) == 0) { + return new SWGPerseusReport(); + } + if(QString("SWGPerseusSettings").compare(type) == 0) { + return new SWGPerseusSettings(); + } if(QString("SWGPresetExport").compare(type) == 0) { return new SWGPresetExport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp new file mode 100644 index 000000000..dada1aa3a --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp @@ -0,0 +1,112 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPerseusReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPerseusReport::SWGPerseusReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPerseusReport::SWGPerseusReport() { + sample_rates = nullptr; + m_sample_rates_isSet = false; +} + +SWGPerseusReport::~SWGPerseusReport() { + this->cleanup(); +} + +void +SWGPerseusReport::init() { + sample_rates = new QList(); + m_sample_rates_isSet = false; +} + +void +SWGPerseusReport::cleanup() { + if(sample_rates != nullptr) { + auto arr = sample_rates; + for(auto o: *arr) { + delete o; + } + delete sample_rates; + } +} + +SWGPerseusReport* +SWGPerseusReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPerseusReport::fromJsonObject(QJsonObject &pJson) { + + ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGAirspyReport_sampleRates"); +} + +QString +SWGPerseusReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPerseusReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(sample_rates->size() > 0){ + toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGAirspyReport_sampleRates"); + } + + return obj; +} + +QList* +SWGPerseusReport::getSampleRates() { + return sample_rates; +} +void +SWGPerseusReport::setSampleRates(QList* sample_rates) { + this->sample_rates = sample_rates; + this->m_sample_rates_isSet = true; +} + + +bool +SWGPerseusReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(sample_rates->size() > 0){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h new file mode 100644 index 000000000..091090da1 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h @@ -0,0 +1,60 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPerseusReport.h + * + * Perseus + */ + +#ifndef SWGPerseusReport_H_ +#define SWGPerseusReport_H_ + +#include + + +#include "SWGAirspyReport_sampleRates.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPerseusReport: public SWGObject { +public: + SWGPerseusReport(); + SWGPerseusReport(QString* json); + virtual ~SWGPerseusReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPerseusReport* fromJson(QString &jsonString) override; + + QList* getSampleRates(); + void setSampleRates(QList* sample_rates); + + + virtual bool isSet() override; + +private: + QList* sample_rates; + bool m_sample_rates_isSet; + +}; + +} + +#endif /* SWGPerseusReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp new file mode 100644 index 000000000..6d5eb31f3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp @@ -0,0 +1,318 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPerseusSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPerseusSettings::SWGPerseusSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPerseusSettings::SWGPerseusSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + dev_sample_rate_index = 0; + m_dev_sample_rate_index_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + adc_dither = 0; + m_adc_dither_isSet = false; + adc_preamp = 0; + m_adc_preamp_isSet = false; + wide_band = 0; + m_wide_band_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; + attenuator = 0; + m_attenuator_isSet = false; +} + +SWGPerseusSettings::~SWGPerseusSettings() { + this->cleanup(); +} + +void +SWGPerseusSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + dev_sample_rate_index = 0; + m_dev_sample_rate_index_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + adc_dither = 0; + m_adc_dither_isSet = false; + adc_preamp = 0; + m_adc_preamp_isSet = false; + wide_band = 0; + m_wide_band_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; + attenuator = 0; + m_attenuator_isSet = false; +} + +void +SWGPerseusSettings::cleanup() { + + + + + + + + + + if(file_record_name != nullptr) { + delete file_record_name; + } + +} + +SWGPerseusSettings* +SWGPerseusSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPerseusSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate_index, pJson["devSampleRateIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&adc_dither, pJson["adcDither"], "qint32", ""); + + ::SWGSDRangel::setValue(&adc_preamp, pJson["adcPreamp"], "qint32", ""); + + ::SWGSDRangel::setValue(&wide_band, pJson["wideBand"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&attenuator, pJson["attenuator"], "qint32", ""); + +} + +QString +SWGPerseusSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPerseusSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); + } + if(m_dev_sample_rate_index_isSet){ + obj->insert("devSampleRateIndex", QJsonValue(dev_sample_rate_index)); + } + if(m_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_adc_dither_isSet){ + obj->insert("adcDither", QJsonValue(adc_dither)); + } + if(m_adc_preamp_isSet){ + obj->insert("adcPreamp", QJsonValue(adc_preamp)); + } + if(m_wide_band_isSet){ + obj->insert("wideBand", QJsonValue(wide_band)); + } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + if(m_attenuator_isSet){ + obj->insert("attenuator", QJsonValue(attenuator)); + } + + return obj; +} + +qint64 +SWGPerseusSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGPerseusSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGPerseusSettings::getLOppmTenths() { + return l_oppm_tenths; +} +void +SWGPerseusSettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; +} + +qint32 +SWGPerseusSettings::getDevSampleRateIndex() { + return dev_sample_rate_index; +} +void +SWGPerseusSettings::setDevSampleRateIndex(qint32 dev_sample_rate_index) { + this->dev_sample_rate_index = dev_sample_rate_index; + this->m_dev_sample_rate_index_isSet = true; +} + +qint32 +SWGPerseusSettings::getLog2Decim() { + return log2_decim; +} +void +SWGPerseusSettings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +qint32 +SWGPerseusSettings::getAdcDither() { + return adc_dither; +} +void +SWGPerseusSettings::setAdcDither(qint32 adc_dither) { + this->adc_dither = adc_dither; + this->m_adc_dither_isSet = true; +} + +qint32 +SWGPerseusSettings::getAdcPreamp() { + return adc_preamp; +} +void +SWGPerseusSettings::setAdcPreamp(qint32 adc_preamp) { + this->adc_preamp = adc_preamp; + this->m_adc_preamp_isSet = true; +} + +qint32 +SWGPerseusSettings::getWideBand() { + return wide_band; +} +void +SWGPerseusSettings::setWideBand(qint32 wide_band) { + this->wide_band = wide_band; + this->m_wide_band_isSet = true; +} + +qint32 +SWGPerseusSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGPerseusSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGPerseusSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGPerseusSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + +QString* +SWGPerseusSettings::getFileRecordName() { + return file_record_name; +} +void +SWGPerseusSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + +qint32 +SWGPerseusSettings::getAttenuator() { + return attenuator; +} +void +SWGPerseusSettings::setAttenuator(qint32 attenuator) { + this->attenuator = attenuator; + this->m_attenuator_isSet = true; +} + + +bool +SWGPerseusSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_index_isSet){ isObjectUpdated = true; break;} + if(m_log2_decim_isSet){ isObjectUpdated = true; break;} + if(m_adc_dither_isSet){ isObjectUpdated = true; break;} + if(m_adc_preamp_isSet){ isObjectUpdated = true; break;} + if(m_wide_band_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + if(m_attenuator_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h new file mode 100644 index 000000000..a2c9c5146 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h @@ -0,0 +1,119 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPerseusSettings.h + * + * Perseus + */ + +#ifndef SWGPerseusSettings_H_ +#define SWGPerseusSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPerseusSettings: public SWGObject { +public: + SWGPerseusSettings(); + SWGPerseusSettings(QString* json); + virtual ~SWGPerseusSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPerseusSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); + + qint32 getDevSampleRateIndex(); + void setDevSampleRateIndex(qint32 dev_sample_rate_index); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + qint32 getAdcDither(); + void setAdcDither(qint32 adc_dither); + + qint32 getAdcPreamp(); + void setAdcPreamp(qint32 adc_preamp); + + qint32 getWideBand(); + void setWideBand(qint32 wide_band); + + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + qint32 getAttenuator(); + void setAttenuator(qint32 attenuator); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; + + qint32 dev_sample_rate_index; + bool m_dev_sample_rate_index_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + qint32 adc_dither; + bool m_adc_dither_isSet; + + qint32 adc_preamp; + bool m_adc_preamp_isSet; + + qint32 wide_band; + bool m_wide_band_isSet; + + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + + qint32 attenuator; + bool m_attenuator_isSet; + +}; + +} + +#endif /* SWGPerseusSettings_H_ */ diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index b9b904f4b..5cc659704 100644 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -152,6 +152,15 @@ def setupDevice(deviceset_url, options): settings['hackRFInputSettings']['lnaGain'] = 32 settings['hackRFInputSettings']['log2Decim'] = options.log2_decim settings['hackRFInputSettings']['vgaGain'] = 24 + elif options.device_hwid == "Perseus": + settings['perseusSettings']['LOppmTenths'] = int(options.lo_ppm * 10) # in tenths of PPM + settings['perseusSettings']['centerFrequency'] = options.device_freq*1000 + settings["perseusSettings"]["devSampleRateIndex"] = 0 + settings['perseusSettings']['log2Decim'] = options.log2_decim + settings['perseusSettings']['adcDither'] = 0 + settings['perseusSettings']['adcPreamp'] = 0 + settings['perseusSettings']['wideBand'] = 0 + settings['perseusSettings']['attenuator'] = 0 r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") if r is None: From f9cba5844b5e520b21ef807bf767bb08bdbc41e1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 14:28:06 +0200 Subject: [PATCH 467/956] RTL-SDR input: implemeted WEB API for reporting --- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 25 ++++ plugins/samplesource/rtlsdr/rtlsdrinput.h | 5 + sdrbase/resources/webapi/doc/html2/index.html | 24 +++- .../webapi/doc/swagger/include/RtlSdr.yaml | 12 +- .../resources/webapi/doc/swagger/swagger.yaml | 2 + .../sdrangel/api/swagger/include/RtlSdr.yaml | 12 +- swagger/sdrangel/api/swagger/swagger.yaml | 2 + swagger/sdrangel/code/html2/index.html | 24 +++- .../code/qt5/client/SWGDeviceReport.cpp | 23 ++++ .../code/qt5/client/SWGDeviceReport.h | 7 ++ .../code/qt5/client/SWGModelFactory.h | 8 ++ .../code/qt5/client/SWGRtlSdrReport.cpp | 112 ++++++++++++++++++ .../code/qt5/client/SWGRtlSdrReport.h | 60 ++++++++++ .../code/qt5/client/SWGRtlSdrReport_gains.cpp | 106 +++++++++++++++++ .../code/qt5/client/SWGRtlSdrReport_gains.h | 58 +++++++++ 15 files changed, 476 insertions(+), 4 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.h diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index d0d722a16..2b1125fe9 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -22,6 +22,8 @@ #include "SWGDeviceSettings.h" #include "SWGRtlSdrSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGRtlSdrReport.h" #include "rtlsdrinput.h" #include "device/devicesourceapi.h" @@ -653,3 +655,26 @@ int RTLSDRInput::webapiRun( return 200; } + +int RTLSDRInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setRtlSdrReport(new SWGSDRangel::SWGRtlSdrReport()); + response.getRtlSdrReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void RTLSDRInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getRtlSdrReport()->setGains(new QList); + + for (std::vector::const_iterator it = getGains().begin(); it != getGains().end(); ++it) + { + response.getRtlSdrReport()->getGains()->append(new SWGSDRangel::SWGRtlSdrReport_gains); + response.getRtlSdrReport()->getGains()->back()->setGain(*it); + } +} + + diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.h b/plugins/samplesource/rtlsdr/rtlsdrinput.h index 2ec558f5f..c67b6815d 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.h +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.h @@ -121,6 +121,10 @@ public: SWGSDRangel::SWGDeviceSettings& response, // query + response QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -157,6 +161,7 @@ private: void closeDevice(); bool applySettings(const RTLSDRSettings& settings, bool force); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const RTLSDRSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif // INCLUDE_RTLSDRINPUT_H diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 597e8388b..58108848b 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1717,6 +1717,9 @@ margin-bottom: 20px; }, "perseusReport" : { "$ref" : "#/definitions/PerseusReport" + }, + "rtlSdrReport" : { + "$ref" : "#/definitions/RtlSdrReport" } }, "description" : "Base device report. The specific device report present depeds on deviceHwType" @@ -2714,6 +2717,25 @@ margin-bottom: 20px; "format" : "float" } } +}; + defs.RtlSdrReport = { + "properties" : { + "gains" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/RtlSdrReport_gains" + } + } + }, + "description" : "RTLSDR" +}; + defs.RtlSdrReport_gains = { + "properties" : { + "gain" : { + "type" : "integer", + "description" : "gain in centi Bels" + } + } }; defs.RtlSdrSettings = { "properties" : { @@ -21922,7 +21944,7 @@ except ApiException as e:
    - Generated 2018-05-26T12:32:33.813+02:00 + Generated 2018-05-26T14:09:06.509+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml index 4d8cc5245..bc27d1981 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml @@ -33,4 +33,14 @@ RtlSdrSettings: type: integer fileRecordName: type: string - \ No newline at end of file + +RtlSdrReport: + description: RTLSDR + properties: + gains: + type: array + items: + properties: + gain: + description: gain in centi Bels + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 73e762e5c..d2df88a99 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1793,6 +1793,8 @@ definitions: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceReport" perseusReport: $ref: "/doc/swagger/include/Perseus.yaml#/PerseusReport" + rtlSdrReport: + $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/api/swagger/include/RtlSdr.yaml b/swagger/sdrangel/api/swagger/include/RtlSdr.yaml index 4d8cc5245..bc27d1981 100644 --- a/swagger/sdrangel/api/swagger/include/RtlSdr.yaml +++ b/swagger/sdrangel/api/swagger/include/RtlSdr.yaml @@ -33,4 +33,14 @@ RtlSdrSettings: type: integer fileRecordName: type: string - \ No newline at end of file + +RtlSdrReport: + description: RTLSDR + properties: + gains: + type: array + items: + properties: + gain: + description: gain in centi Bels + type: integer diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 721a0b2fe..0c67bad7c 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1793,6 +1793,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceReport" perseusReport: $ref: "http://localhost:8081/api/swagger/include/Perseus.yaml#/PerseusReport" + rtlSdrReport: + $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 597e8388b..58108848b 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1717,6 +1717,9 @@ margin-bottom: 20px; }, "perseusReport" : { "$ref" : "#/definitions/PerseusReport" + }, + "rtlSdrReport" : { + "$ref" : "#/definitions/RtlSdrReport" } }, "description" : "Base device report. The specific device report present depeds on deviceHwType" @@ -2714,6 +2717,25 @@ margin-bottom: 20px; "format" : "float" } } +}; + defs.RtlSdrReport = { + "properties" : { + "gains" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/RtlSdrReport_gains" + } + } + }, + "description" : "RTLSDR" +}; + defs.RtlSdrReport_gains = { + "properties" : { + "gain" : { + "type" : "integer", + "description" : "gain in centi Bels" + } + } }; defs.RtlSdrSettings = { "properties" : { @@ -21922,7 +21944,7 @@ except ApiException as e:
    - Generated 2018-05-26T12:32:33.813+02:00 + Generated 2018-05-26T14:09:06.509+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index 7160ee595..07d6195e4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -40,6 +40,8 @@ SWGDeviceReport::SWGDeviceReport() { m_file_source_report_isSet = false; perseus_report = nullptr; m_perseus_report_isSet = false; + rtl_sdr_report = nullptr; + m_rtl_sdr_report_isSet = false; } SWGDeviceReport::~SWGDeviceReport() { @@ -60,6 +62,8 @@ SWGDeviceReport::init() { m_file_source_report_isSet = false; perseus_report = new SWGPerseusReport(); m_perseus_report_isSet = false; + rtl_sdr_report = new SWGRtlSdrReport(); + m_rtl_sdr_report_isSet = false; } void @@ -80,6 +84,9 @@ SWGDeviceReport::cleanup() { if(perseus_report != nullptr) { delete perseus_report; } + if(rtl_sdr_report != nullptr) { + delete rtl_sdr_report; + } } SWGDeviceReport* @@ -105,6 +112,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&perseus_report, pJson["perseusReport"], "SWGPerseusReport", "SWGPerseusReport"); + ::SWGSDRangel::setValue(&rtl_sdr_report, pJson["rtlSdrReport"], "SWGRtlSdrReport", "SWGRtlSdrReport"); + } QString @@ -139,6 +148,9 @@ SWGDeviceReport::asJsonObject() { if((perseus_report != nullptr) && (perseus_report->isSet())){ toJsonValue(QString("perseusReport"), perseus_report, obj, QString("SWGPerseusReport")); } + if((rtl_sdr_report != nullptr) && (rtl_sdr_report->isSet())){ + toJsonValue(QString("rtlSdrReport"), rtl_sdr_report, obj, QString("SWGRtlSdrReport")); + } return obj; } @@ -203,6 +215,16 @@ SWGDeviceReport::setPerseusReport(SWGPerseusReport* perseus_report) { this->m_perseus_report_isSet = true; } +SWGRtlSdrReport* +SWGDeviceReport::getRtlSdrReport() { + return rtl_sdr_report; +} +void +SWGDeviceReport::setRtlSdrReport(SWGRtlSdrReport* rtl_sdr_report) { + this->rtl_sdr_report = rtl_sdr_report; + this->m_rtl_sdr_report_isSet = true; +} + bool SWGDeviceReport::isSet(){ @@ -214,6 +236,7 @@ SWGDeviceReport::isSet(){ if(airspy_hf_report != nullptr && airspy_hf_report->isSet()){ isObjectUpdated = true; break;} if(file_source_report != nullptr && file_source_report->isSet()){ isObjectUpdated = true; break;} if(perseus_report != nullptr && perseus_report->isSet()){ isObjectUpdated = true; break;} + if(rtl_sdr_report != nullptr && rtl_sdr_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index ba3a9f73d..d54c7d20c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -26,6 +26,7 @@ #include "SWGAirspyReport.h" #include "SWGFileSourceReport.h" #include "SWGPerseusReport.h" +#include "SWGRtlSdrReport.h" #include #include "SWGObject.h" @@ -64,6 +65,9 @@ public: SWGPerseusReport* getPerseusReport(); void setPerseusReport(SWGPerseusReport* perseus_report); + SWGRtlSdrReport* getRtlSdrReport(); + void setRtlSdrReport(SWGRtlSdrReport* rtl_sdr_report); + virtual bool isSet() override; @@ -86,6 +90,9 @@ private: SWGPerseusReport* perseus_report; bool m_perseus_report_isSet; + SWGRtlSdrReport* rtl_sdr_report; + bool m_rtl_sdr_report_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 9e70778cb..68b4ccfda 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -77,6 +77,8 @@ #include "SWGPresets.h" #include "SWGRDSReport.h" #include "SWGRDSReport_altFrequencies.h" +#include "SWGRtlSdrReport.h" +#include "SWGRtlSdrReport_gains.h" #include "SWGRtlSdrSettings.h" #include "SWGSSBModReport.h" #include "SWGSSBModSettings.h" @@ -283,6 +285,12 @@ namespace SWGSDRangel { if(QString("SWGRDSReport_altFrequencies").compare(type) == 0) { return new SWGRDSReport_altFrequencies(); } + if(QString("SWGRtlSdrReport").compare(type) == 0) { + return new SWGRtlSdrReport(); + } + if(QString("SWGRtlSdrReport_gains").compare(type) == 0) { + return new SWGRtlSdrReport_gains(); + } if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp new file mode 100644 index 000000000..824ff9dae --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp @@ -0,0 +1,112 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGRtlSdrReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRtlSdrReport::SWGRtlSdrReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRtlSdrReport::SWGRtlSdrReport() { + gains = nullptr; + m_gains_isSet = false; +} + +SWGRtlSdrReport::~SWGRtlSdrReport() { + this->cleanup(); +} + +void +SWGRtlSdrReport::init() { + gains = new QList(); + m_gains_isSet = false; +} + +void +SWGRtlSdrReport::cleanup() { + if(gains != nullptr) { + auto arr = gains; + for(auto o: *arr) { + delete o; + } + delete gains; + } +} + +SWGRtlSdrReport* +SWGRtlSdrReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRtlSdrReport::fromJsonObject(QJsonObject &pJson) { + + ::SWGSDRangel::setValue(&gains, pJson["gains"], "QList", "SWGRtlSdrReport_gains"); +} + +QString +SWGRtlSdrReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRtlSdrReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(gains->size() > 0){ + toJsonArray((QList*)gains, obj, "gains", "SWGRtlSdrReport_gains"); + } + + return obj; +} + +QList* +SWGRtlSdrReport::getGains() { + return gains; +} +void +SWGRtlSdrReport::setGains(QList* gains) { + this->gains = gains; + this->m_gains_isSet = true; +} + + +bool +SWGRtlSdrReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(gains->size() > 0){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h new file mode 100644 index 000000000..f6fa9a371 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h @@ -0,0 +1,60 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGRtlSdrReport.h + * + * RTLSDR + */ + +#ifndef SWGRtlSdrReport_H_ +#define SWGRtlSdrReport_H_ + +#include + + +#include "SWGRtlSdrReport_gains.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRtlSdrReport: public SWGObject { +public: + SWGRtlSdrReport(); + SWGRtlSdrReport(QString* json); + virtual ~SWGRtlSdrReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRtlSdrReport* fromJson(QString &jsonString) override; + + QList* getGains(); + void setGains(QList* gains); + + + virtual bool isSet() override; + +private: + QList* gains; + bool m_gains_isSet; + +}; + +} + +#endif /* SWGRtlSdrReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.cpp new file mode 100644 index 000000000..001232465 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGRtlSdrReport_gains.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRtlSdrReport_gains::SWGRtlSdrReport_gains(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRtlSdrReport_gains::SWGRtlSdrReport_gains() { + gain = 0; + m_gain_isSet = false; +} + +SWGRtlSdrReport_gains::~SWGRtlSdrReport_gains() { + this->cleanup(); +} + +void +SWGRtlSdrReport_gains::init() { + gain = 0; + m_gain_isSet = false; +} + +void +SWGRtlSdrReport_gains::cleanup() { + +} + +SWGRtlSdrReport_gains* +SWGRtlSdrReport_gains::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRtlSdrReport_gains::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&gain, pJson["gain"], "qint32", ""); + +} + +QString +SWGRtlSdrReport_gains::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRtlSdrReport_gains::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_gain_isSet){ + obj->insert("gain", QJsonValue(gain)); + } + + return obj; +} + +qint32 +SWGRtlSdrReport_gains::getGain() { + return gain; +} +void +SWGRtlSdrReport_gains::setGain(qint32 gain) { + this->gain = gain; + this->m_gain_isSet = true; +} + + +bool +SWGRtlSdrReport_gains::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_gain_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.h new file mode 100644 index 000000000..b876573ca --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGRtlSdrReport_gains.h + * + * + */ + +#ifndef SWGRtlSdrReport_gains_H_ +#define SWGRtlSdrReport_gains_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRtlSdrReport_gains: public SWGObject { +public: + SWGRtlSdrReport_gains(); + SWGRtlSdrReport_gains(QString* json); + virtual ~SWGRtlSdrReport_gains(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRtlSdrReport_gains* fromJson(QString &jsonString) override; + + qint32 getGain(); + void setGain(qint32 gain); + + + virtual bool isSet() override; + +private: + qint32 gain; + bool m_gain_isSet; + +}; + +} + +#endif /* SWGRtlSdrReport_gains_H_ */ From c424ce10e491359015e0a6cbbb2267b9a4773caa Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 15:53:22 +0200 Subject: [PATCH 468/956] SSB demod: implemeted WEB API --- plugins/channelrx/demodssb/CMakeLists.txt | 1 + plugins/channelrx/demodssb/ssbdemod.cpp | 155 ++++++ plugins/channelrx/demodssb/ssbdemod.h | 16 + plugins/channelrx/demodssb/ssbdemodgui.cpp | 12 +- sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 99 +++- .../webapi/doc/swagger/include/SSBDemod.yaml | 65 +++ .../webapi/doc/swagger/include/SSBMod.yaml | 2 +- .../resources/webapi/doc/swagger/swagger.yaml | 4 + sdrbase/webapi/webapirequestmapper.cpp | 14 + .../api/swagger/include/SSBDemod.yaml | 65 +++ .../sdrangel/api/swagger/include/SSBMod.yaml | 2 +- swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 99 +++- .../code/qt5/client/SWGChannelReport.cpp | 23 + .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 + .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../code/qt5/client/SWGSSBDemodReport.cpp | 169 +++++++ .../code/qt5/client/SWGSSBDemodReport.h | 76 +++ .../code/qt5/client/SWGSSBDemodSettings.cpp | 446 ++++++++++++++++++ .../code/qt5/client/SWGSSBDemodSettings.h | 155 ++++++ .../code/qt5/client/SWGSSBModReport.h | 2 +- swagger/sdrangel/examples/rx_test.py | 15 + 25 files changed, 1462 insertions(+), 8 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/SSBDemod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/SSBDemod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h diff --git a/plugins/channelrx/demodssb/CMakeLists.txt b/plugins/channelrx/demodssb/CMakeLists.txt index 355098143..514f1e01b 100644 --- a/plugins/channelrx/demodssb/CMakeLists.txt +++ b/plugins/channelrx/demodssb/CMakeLists.txt @@ -23,6 +23,7 @@ set(ssb_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index e75b562a4..cf7a50ad4 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -21,6 +21,11 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGSSBDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGSSBDemodReport.h" + #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/downchannelizer.h" @@ -577,3 +582,153 @@ bool SSBDemod::deserialize(const QByteArray& data) return false; } } + +int SSBDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings()); + response.getSsbDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int SSBDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + SSBDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getSsbDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getSsbDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("lowCutoff")) { + settings.m_lowCutoff = response.getSsbDemodSettings()->getLowCutoff(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getSsbDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("spanLog2")) { + settings.m_spanLog2 = response.getSsbDemodSettings()->getSpanLog2(); + } + if (channelSettingsKeys.contains("audioBinaural")) { + settings.m_audioBinaural = response.getSsbDemodSettings()->getAudioBinaural() != 0; + } + if (channelSettingsKeys.contains("audioFlipChannels")) { + settings.m_audioFlipChannels = response.getSsbDemodSettings()->getAudioFlipChannels() != 0; + } + if (channelSettingsKeys.contains("dsb")) { + settings.m_dsb = response.getSsbDemodSettings()->getDsb() != 0; + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getSsbDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("agc")) { + settings.m_agc = response.getSsbDemodSettings()->getAgc() != 0; + } + if (channelSettingsKeys.contains("agcClamping")) { + settings.m_agcClamping = response.getSsbDemodSettings()->getAgcClamping() != 0; + } + if (channelSettingsKeys.contains("agcTimeLog2")) { + settings.m_agcTimeLog2 = response.getSsbDemodSettings()->getAgcTimeLog2(); + } + if (channelSettingsKeys.contains("agcPowerThreshold")) { + settings.m_agcPowerThreshold = response.getSsbDemodSettings()->getAgcPowerThreshold(); + } + if (channelSettingsKeys.contains("agcThresholdGate")) { + settings.m_agcThresholdGate = response.getSsbDemodSettings()->getAgcThresholdGate(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getSsbDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getSsbDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getSsbDemodSettings()->getAudioDeviceName(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureSSBDemod *msg = MsgConfigureSSBDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("SSBDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSSBDemod *msgToGUI = MsgConfigureSSBDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int SSBDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSsbDemodReport(new SWGSDRangel::SWGSSBDemodReport()); + response.getSsbDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void SSBDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBDemodSettings& settings) +{ + response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getSsbDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getSsbDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getSsbDemodSettings()->setLowCutoff(settings.m_lowCutoff); + response.getSsbDemodSettings()->setVolume(settings.m_volume); + response.getSsbDemodSettings()->setSpanLog2(settings.m_spanLog2); + response.getSsbDemodSettings()->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); + response.getSsbDemodSettings()->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); + response.getSsbDemodSettings()->setDsb(settings.m_dsb ? 1 : 0); + response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getSsbDemodSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getSsbDemodSettings()->setAgcClamping(settings.m_agcClamping ? 1 : 0); + response.getSsbDemodSettings()->setAgcTimeLog2(settings.m_agcTimeLog2); + response.getSsbDemodSettings()->setAgcPowerThreshold(settings.m_agcPowerThreshold); + response.getSsbDemodSettings()->setAgcThresholdGate(settings.m_agcThresholdGate); + response.getSsbDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getSsbDemodSettings()->getTitle()) { + *response.getSsbDemodSettings()->getTitle() = settings.m_title; + } else { + response.getSsbDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getSsbDemodSettings()->getAudioDeviceName()) { + *response.getSsbDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getSsbDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void SSBDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getSsbDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getSsbDemodReport()->setSquelch(m_audioActive ? 1 : 0); + response.getSsbDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getSsbDemodReport()->setChannelSampleRate(m_inputSampleRate); +} + diff --git a/plugins/channelrx/demodssb/ssbdemod.h b/plugins/channelrx/demodssb/ssbdemod.h index 27e721e18..4dabab796 100644 --- a/plugins/channelrx/demodssb/ssbdemod.h +++ b/plugins/channelrx/demodssb/ssbdemod.h @@ -135,6 +135,20 @@ public: m_magsqCount = 0; } + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + static const QString m_channelIdURI; static const QString m_channelId; @@ -283,6 +297,8 @@ private: void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const SSBDemodSettings& settings, bool force = false); void applyAudioSampleRate(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; #endif // INCLUDE_SSBDEMOD_H diff --git a/plugins/channelrx/demodssb/ssbdemodgui.cpp b/plugins/channelrx/demodssb/ssbdemodgui.cpp index 42a43237e..788b1d946 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.cpp +++ b/plugins/channelrx/demodssb/ssbdemodgui.cpp @@ -82,7 +82,17 @@ bool SSBDemodGUI::deserialize(const QByteArray& data) bool SSBDemodGUI::handleMessage(const Message& message) { - if (DSPConfigureAudio::match(message)) + if (SSBDemod::MsgConfigureSSBDemod::match(message)) + { + qDebug("SSBDemodGUI::handleMessage: SSBDemod::MsgConfigureSSBDemod"); + const SSBDemod::MsgConfigureSSBDemod& cfg = (SSBDemod::MsgConfigureSSBDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (DSPConfigureAudio::match(message)) { qDebug("SSBDemodGUI::handleMessage: DSPConfigureAudio: %d", m_ssbDemod->getAudioSampleRate()); applyBandwidths(5 - ui->spanLog2->value()); // will update spectrum details with new sample rate diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 9a40ae94a..486d4d98b 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -20,6 +20,7 @@ webapi/doc/swagger/include/NFMMod.yaml webapi/doc/swagger/include/Perseus.yaml webapi/doc/swagger/include/RtlSdr.yaml + webapi/doc/swagger/include/SSBDemod.yaml webapi/doc/swagger/include/SSBMod.yaml webapi/doc/swagger/include/UDPSink.yaml webapi/doc/swagger/include/UDPSrc.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 58108848b..da3557fe4 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1405,6 +1405,9 @@ margin-bottom: 20px; "NFMModReport" : { "$ref" : "#/definitions/NFMModReport" }, + "SSBDemodReport" : { + "$ref" : "#/definitions/SSBDemodReport" + }, "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" }, @@ -1459,6 +1462,9 @@ margin-bottom: 20px; "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" }, + "SSBDemodSettings" : { + "$ref" : "#/definitions/SSBDemodSettings" + }, "UDPSinkSettings" : { "$ref" : "#/definitions/UDPSinkSettings" }, @@ -2788,6 +2794,95 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SSBDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power received in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "Audio squelch status (1 if open else 0)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "SSBDemod" +}; + defs.SSBDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "lowCutoff" : { + "type" : "number", + "format" : "float" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "spanLog2" : { + "type" : "integer" + }, + "audioBinaural" : { + "type" : "integer", + "description" : "Audio binaural mode (1 if active else 0)" + }, + "audioFlipChannels" : { + "type" : "integer", + "description" : "Flip audio channels (1 if flipped else 0)" + }, + "dsb" : { + "type" : "integer", + "description" : "Double sidebands mode (1 if DSB else 0)" + }, + "audioMute" : { + "type" : "integer", + "description" : "Mute audio (1 if muted else 0)" + }, + "agc" : { + "type" : "integer", + "description" : "AGC (1 if AGC active else 0)" + }, + "agcClamping" : { + "type" : "integer", + "description" : "AGC clamping (1 if AGC clampingactive else 0)" + }, + "agcTimeLog2" : { + "type" : "integer", + "description" : "AGC averaging time log2 in milliseconds" + }, + "agcPowerThreshold" : { + "type" : "integer", + "description" : "Audio open RF threshold (dB)" + }, + "agcThresholdGate" : { + "type" : "integer", + "description" : "Audio squelch gate in ms" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + } + }, + "description" : "SSBDemod" }; defs.SSBModReport = { "properties" : { @@ -2803,7 +2898,7 @@ margin-bottom: 20px; "type" : "integer" } }, - "description" : "AMMod" + "description" : "SSBMod" }; defs.SSBModSettings = { "properties" : { @@ -21944,7 +22039,7 @@ except ApiException as e:
    - Generated 2018-05-26T14:09:06.509+02:00 + Generated 2018-05-26T14:52:58.621+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SSBDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/SSBDemod.yaml new file mode 100644 index 000000000..b66eacda9 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/SSBDemod.yaml @@ -0,0 +1,65 @@ +SSBDemodSettings: + description: SSBDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + lowCutoff: + type: number + format: float + volume: + type: number + format: float + spanLog2: + type: integer + audioBinaural: + description: Audio binaural mode (1 if active else 0) + type: integer + audioFlipChannels: + description: Flip audio channels (1 if flipped else 0) + type: integer + dsb: + description: Double sidebands mode (1 if DSB else 0) + type: integer + audioMute: + description: Mute audio (1 if muted else 0) + type: integer + agc: + description: AGC (1 if AGC active else 0) + type: integer + agcClamping: + description: AGC clamping (1 if AGC clampingactive else 0) + type: integer + agcTimeLog2: + description: AGC averaging time log2 in milliseconds + type: integer + agcPowerThreshold: + description: Audio open RF threshold (dB) + type: integer + agcThresholdGate: + description: Audio squelch gate in ms + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + +SSBDemodReport: + description: SSBDemod + properties: + channelPowerDB: + description: power received in channel (dB) + type: number + format: float + squelch: + description: Audio squelch status (1 if open else 0) + type: integer + audioSampleRate: + type: integer + channelSampleRate: + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/include/SSBMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/SSBMod.yaml index 2692d04aa..a460bff62 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SSBMod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SSBMod.yaml @@ -57,7 +57,7 @@ SSBModSettings: $ref: "/doc/swagger/include/CWKeyer.yaml#/CWKeyerSettings" SSBModReport: - description: AMMod + description: SSBMod properties: channelPowerDB: description: power transmitted in channel (dB) diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index d2df88a99..7b5368635 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1825,6 +1825,8 @@ definitions: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" SSBModSettings: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" + SSBDemodSettings: + $ref: "/doc/swagger/include/SSBDemod.yaml#/SSBDemodSettings" UDPSinkSettings: $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkSettings" UDPSrcSettings: @@ -1858,6 +1860,8 @@ definitions: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModReport" + SSBDemodReport: + $ref: "/doc/swagger/include/SSBDemod.yaml#/SSBDemodReport" SSBModReport: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index ce331a84d..d3d2a7de8 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2038,6 +2038,20 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "SSBDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject ssbDemodSettingsJsonObject = jsonObject["SSBDemodSettings"].toObject(); + channelSettingsKeys = ssbDemodSettingsJsonObject.keys(); + channelSettings.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings()); + channelSettings.getSsbDemodSettings()->fromJsonObject(ssbDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "SSBMod") { if (channelSettings.getTx() != 0) diff --git a/swagger/sdrangel/api/swagger/include/SSBDemod.yaml b/swagger/sdrangel/api/swagger/include/SSBDemod.yaml new file mode 100644 index 000000000..b66eacda9 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/SSBDemod.yaml @@ -0,0 +1,65 @@ +SSBDemodSettings: + description: SSBDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + lowCutoff: + type: number + format: float + volume: + type: number + format: float + spanLog2: + type: integer + audioBinaural: + description: Audio binaural mode (1 if active else 0) + type: integer + audioFlipChannels: + description: Flip audio channels (1 if flipped else 0) + type: integer + dsb: + description: Double sidebands mode (1 if DSB else 0) + type: integer + audioMute: + description: Mute audio (1 if muted else 0) + type: integer + agc: + description: AGC (1 if AGC active else 0) + type: integer + agcClamping: + description: AGC clamping (1 if AGC clampingactive else 0) + type: integer + agcTimeLog2: + description: AGC averaging time log2 in milliseconds + type: integer + agcPowerThreshold: + description: Audio open RF threshold (dB) + type: integer + agcThresholdGate: + description: Audio squelch gate in ms + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + +SSBDemodReport: + description: SSBDemod + properties: + channelPowerDB: + description: power received in channel (dB) + type: number + format: float + squelch: + description: Audio squelch status (1 if open else 0) + type: integer + audioSampleRate: + type: integer + channelSampleRate: + type: integer diff --git a/swagger/sdrangel/api/swagger/include/SSBMod.yaml b/swagger/sdrangel/api/swagger/include/SSBMod.yaml index bf3d75099..d66cbc458 100644 --- a/swagger/sdrangel/api/swagger/include/SSBMod.yaml +++ b/swagger/sdrangel/api/swagger/include/SSBMod.yaml @@ -57,7 +57,7 @@ SSBModSettings: $ref: "http://localhost:8081/api/swagger/include/CWKeyer.yaml#/CWKeyerSettings" SSBModReport: - description: AMMod + description: SSBMod properties: channelPowerDB: description: power transmitted in channel (dB) diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 0c67bad7c..307efde8e 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1825,6 +1825,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" SSBModSettings: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" + SSBDemodSettings: + $ref: "http://localhost:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodSettings" UDPSinkSettings: $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkSettings" UDPSrcSettings: @@ -1858,6 +1860,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModReport" + SSBDemodReport: + $ref: "http://localhost:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodReport" SSBModReport: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 58108848b..da3557fe4 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1405,6 +1405,9 @@ margin-bottom: 20px; "NFMModReport" : { "$ref" : "#/definitions/NFMModReport" }, + "SSBDemodReport" : { + "$ref" : "#/definitions/SSBDemodReport" + }, "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" }, @@ -1459,6 +1462,9 @@ margin-bottom: 20px; "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" }, + "SSBDemodSettings" : { + "$ref" : "#/definitions/SSBDemodSettings" + }, "UDPSinkSettings" : { "$ref" : "#/definitions/UDPSinkSettings" }, @@ -2788,6 +2794,95 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SSBDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power received in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "Audio squelch status (1 if open else 0)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "SSBDemod" +}; + defs.SSBDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "lowCutoff" : { + "type" : "number", + "format" : "float" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "spanLog2" : { + "type" : "integer" + }, + "audioBinaural" : { + "type" : "integer", + "description" : "Audio binaural mode (1 if active else 0)" + }, + "audioFlipChannels" : { + "type" : "integer", + "description" : "Flip audio channels (1 if flipped else 0)" + }, + "dsb" : { + "type" : "integer", + "description" : "Double sidebands mode (1 if DSB else 0)" + }, + "audioMute" : { + "type" : "integer", + "description" : "Mute audio (1 if muted else 0)" + }, + "agc" : { + "type" : "integer", + "description" : "AGC (1 if AGC active else 0)" + }, + "agcClamping" : { + "type" : "integer", + "description" : "AGC clamping (1 if AGC clampingactive else 0)" + }, + "agcTimeLog2" : { + "type" : "integer", + "description" : "AGC averaging time log2 in milliseconds" + }, + "agcPowerThreshold" : { + "type" : "integer", + "description" : "Audio open RF threshold (dB)" + }, + "agcThresholdGate" : { + "type" : "integer", + "description" : "Audio squelch gate in ms" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + } + }, + "description" : "SSBDemod" }; defs.SSBModReport = { "properties" : { @@ -2803,7 +2898,7 @@ margin-bottom: 20px; "type" : "integer" } }, - "description" : "AMMod" + "description" : "SSBMod" }; defs.SSBModSettings = { "properties" : { @@ -21944,7 +22039,7 @@ except ApiException as e:
    - Generated 2018-05-26T14:09:06.509+02:00 + Generated 2018-05-26T14:52:58.621+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 943ce5f63..32de362f7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -46,6 +46,8 @@ SWGChannelReport::SWGChannelReport() { m_nfm_demod_report_isSet = false; nfm_mod_report = nullptr; m_nfm_mod_report_isSet = false; + ssb_demod_report = nullptr; + m_ssb_demod_report_isSet = false; ssb_mod_report = nullptr; m_ssb_mod_report_isSet = false; udp_sink_report = nullptr; @@ -82,6 +84,8 @@ SWGChannelReport::init() { m_nfm_demod_report_isSet = false; nfm_mod_report = new SWGNFMModReport(); m_nfm_mod_report_isSet = false; + ssb_demod_report = new SWGSSBDemodReport(); + m_ssb_demod_report_isSet = false; ssb_mod_report = new SWGSSBModReport(); m_ssb_mod_report_isSet = false; udp_sink_report = new SWGUDPSinkReport(); @@ -121,6 +125,9 @@ SWGChannelReport::cleanup() { if(nfm_mod_report != nullptr) { delete nfm_mod_report; } + if(ssb_demod_report != nullptr) { + delete ssb_demod_report; + } if(ssb_mod_report != nullptr) { delete ssb_mod_report; } @@ -167,6 +174,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); + ::SWGSDRangel::setValue(&ssb_demod_report, pJson["SSBDemodReport"], "SWGSSBDemodReport", "SWGSSBDemodReport"); + ::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport"); ::SWGSDRangel::setValue(&udp_sink_report, pJson["UDPSinkReport"], "SWGUDPSinkReport", "SWGUDPSinkReport"); @@ -220,6 +229,9 @@ SWGChannelReport::asJsonObject() { if((nfm_mod_report != nullptr) && (nfm_mod_report->isSet())){ toJsonValue(QString("NFMModReport"), nfm_mod_report, obj, QString("SWGNFMModReport")); } + if((ssb_demod_report != nullptr) && (ssb_demod_report->isSet())){ + toJsonValue(QString("SSBDemodReport"), ssb_demod_report, obj, QString("SWGSSBDemodReport")); + } if((ssb_mod_report != nullptr) && (ssb_mod_report->isSet())){ toJsonValue(QString("SSBModReport"), ssb_mod_report, obj, QString("SWGSSBModReport")); } @@ -329,6 +341,16 @@ SWGChannelReport::setNfmModReport(SWGNFMModReport* nfm_mod_report) { this->m_nfm_mod_report_isSet = true; } +SWGSSBDemodReport* +SWGChannelReport::getSsbDemodReport() { + return ssb_demod_report; +} +void +SWGChannelReport::setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report) { + this->ssb_demod_report = ssb_demod_report; + this->m_ssb_demod_report_isSet = true; +} + SWGSSBModReport* SWGChannelReport::getSsbModReport() { return ssb_mod_report; @@ -393,6 +415,7 @@ SWGChannelReport::isSet(){ if(dsd_demod_report != nullptr && dsd_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} + if(ssb_demod_report != nullptr && ssb_demod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} if(udp_sink_report != nullptr && udp_sink_report->isSet()){ isObjectUpdated = true; break;} if(udp_src_report != nullptr && udp_src_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index d8c7f0aef..b865f5c63 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -29,6 +29,7 @@ #include "SWGDSDDemodReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" +#include "SWGSSBDemodReport.h" #include "SWGSSBModReport.h" #include "SWGUDPSinkReport.h" #include "SWGUDPSrcReport.h" @@ -81,6 +82,9 @@ public: SWGNFMModReport* getNfmModReport(); void setNfmModReport(SWGNFMModReport* nfm_mod_report); + SWGSSBDemodReport* getSsbDemodReport(); + void setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report); + SWGSSBModReport* getSsbModReport(); void setSsbModReport(SWGSSBModReport* ssb_mod_report); @@ -127,6 +131,9 @@ private: SWGNFMModReport* nfm_mod_report; bool m_nfm_mod_report_isSet; + SWGSSBDemodReport* ssb_demod_report; + bool m_ssb_demod_report_isSet; + SWGSSBModReport* ssb_mod_report; bool m_ssb_mod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 642a823c6..ec8c90ea3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -48,6 +48,8 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_mod_settings_isSet = false; ssb_mod_settings = nullptr; m_ssb_mod_settings_isSet = false; + ssb_demod_settings = nullptr; + m_ssb_demod_settings_isSet = false; udp_sink_settings = nullptr; m_udp_sink_settings_isSet = false; udp_src_settings = nullptr; @@ -84,6 +86,8 @@ SWGChannelSettings::init() { m_nfm_mod_settings_isSet = false; ssb_mod_settings = new SWGSSBModSettings(); m_ssb_mod_settings_isSet = false; + ssb_demod_settings = new SWGSSBDemodSettings(); + m_ssb_demod_settings_isSet = false; udp_sink_settings = new SWGUDPSinkSettings(); m_udp_sink_settings_isSet = false; udp_src_settings = new SWGUDPSrcSettings(); @@ -124,6 +128,9 @@ SWGChannelSettings::cleanup() { if(ssb_mod_settings != nullptr) { delete ssb_mod_settings; } + if(ssb_demod_settings != nullptr) { + delete ssb_demod_settings; + } if(udp_sink_settings != nullptr) { delete udp_sink_settings; } @@ -169,6 +176,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ssb_mod_settings, pJson["SSBModSettings"], "SWGSSBModSettings", "SWGSSBModSettings"); + ::SWGSDRangel::setValue(&ssb_demod_settings, pJson["SSBDemodSettings"], "SWGSSBDemodSettings", "SWGSSBDemodSettings"); + ::SWGSDRangel::setValue(&udp_sink_settings, pJson["UDPSinkSettings"], "SWGUDPSinkSettings", "SWGUDPSinkSettings"); ::SWGSDRangel::setValue(&udp_src_settings, pJson["UDPSrcSettings"], "SWGUDPSrcSettings", "SWGUDPSrcSettings"); @@ -223,6 +232,9 @@ SWGChannelSettings::asJsonObject() { if((ssb_mod_settings != nullptr) && (ssb_mod_settings->isSet())){ toJsonValue(QString("SSBModSettings"), ssb_mod_settings, obj, QString("SWGSSBModSettings")); } + if((ssb_demod_settings != nullptr) && (ssb_demod_settings->isSet())){ + toJsonValue(QString("SSBDemodSettings"), ssb_demod_settings, obj, QString("SWGSSBDemodSettings")); + } if((udp_sink_settings != nullptr) && (udp_sink_settings->isSet())){ toJsonValue(QString("UDPSinkSettings"), udp_sink_settings, obj, QString("SWGUDPSinkSettings")); } @@ -339,6 +351,16 @@ SWGChannelSettings::setSsbModSettings(SWGSSBModSettings* ssb_mod_settings) { this->m_ssb_mod_settings_isSet = true; } +SWGSSBDemodSettings* +SWGChannelSettings::getSsbDemodSettings() { + return ssb_demod_settings; +} +void +SWGChannelSettings::setSsbDemodSettings(SWGSSBDemodSettings* ssb_demod_settings) { + this->ssb_demod_settings = ssb_demod_settings; + this->m_ssb_demod_settings_isSet = true; +} + SWGUDPSinkSettings* SWGChannelSettings::getUdpSinkSettings() { return udp_sink_settings; @@ -394,6 +416,7 @@ SWGChannelSettings::isSet(){ if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} + if(ssb_demod_settings != nullptr && ssb_demod_settings->isSet()){ isObjectUpdated = true; break;} if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} if(udp_src_settings != nullptr && udp_src_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_demod_settings != nullptr && wfm_demod_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 59a38aead..a23ece475 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -29,6 +29,7 @@ #include "SWGDSDDemodSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" +#include "SWGSSBDemodSettings.h" #include "SWGSSBModSettings.h" #include "SWGUDPSinkSettings.h" #include "SWGUDPSrcSettings.h" @@ -84,6 +85,9 @@ public: SWGSSBModSettings* getSsbModSettings(); void setSsbModSettings(SWGSSBModSettings* ssb_mod_settings); + SWGSSBDemodSettings* getSsbDemodSettings(); + void setSsbDemodSettings(SWGSSBDemodSettings* ssb_demod_settings); + SWGUDPSinkSettings* getUdpSinkSettings(); void setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings); @@ -130,6 +134,9 @@ private: SWGSSBModSettings* ssb_mod_settings; bool m_ssb_mod_settings_isSet; + SWGSSBDemodSettings* ssb_demod_settings; + bool m_ssb_demod_settings_isSet; + SWGUDPSinkSettings* udp_sink_settings; bool m_udp_sink_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 68b4ccfda..63916e9d8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -80,6 +80,8 @@ #include "SWGRtlSdrReport.h" #include "SWGRtlSdrReport_gains.h" #include "SWGRtlSdrSettings.h" +#include "SWGSSBDemodReport.h" +#include "SWGSSBDemodSettings.h" #include "SWGSSBModReport.h" #include "SWGSSBModSettings.h" #include "SWGSamplingDevice.h" @@ -294,6 +296,12 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } + if(QString("SWGSSBDemodReport").compare(type) == 0) { + return new SWGSSBDemodReport(); + } + if(QString("SWGSSBDemodSettings").compare(type) == 0) { + return new SWGSSBDemodSettings(); + } if(QString("SWGSSBModReport").compare(type) == 0) { return new SWGSSBModReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp new file mode 100644 index 000000000..619710937 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp @@ -0,0 +1,169 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSSBDemodReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSSBDemodReport::SWGSSBDemodReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSSBDemodReport::SWGSSBDemodReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + squelch = 0; + m_squelch_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGSSBDemodReport::~SWGSSBDemodReport() { + this->cleanup(); +} + +void +SWGSSBDemodReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + squelch = 0; + m_squelch_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGSSBDemodReport::cleanup() { + + + + +} + +SWGSSBDemodReport* +SWGSSBDemodReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSSBDemodReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGSSBDemodReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSSBDemodReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_audio_sample_rate_isSet){ + obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGSSBDemodReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGSSBDemodReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGSSBDemodReport::getSquelch() { + return squelch; +} +void +SWGSSBDemodReport::setSquelch(qint32 squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +qint32 +SWGSSBDemodReport::getAudioSampleRate() { + return audio_sample_rate; +} +void +SWGSSBDemodReport::setAudioSampleRate(qint32 audio_sample_rate) { + this->audio_sample_rate = audio_sample_rate; + this->m_audio_sample_rate_isSet = true; +} + +qint32 +SWGSSBDemodReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGSSBDemodReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGSSBDemodReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h new file mode 100644 index 000000000..de0f1a02b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h @@ -0,0 +1,76 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSSBDemodReport.h + * + * SSBDemod + */ + +#ifndef SWGSSBDemodReport_H_ +#define SWGSSBDemodReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSSBDemodReport: public SWGObject { +public: + SWGSSBDemodReport(); + SWGSSBDemodReport(QString* json); + virtual ~SWGSSBDemodReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSSBDemodReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getSquelch(); + void setSquelch(qint32 squelch); + + qint32 getAudioSampleRate(); + void setAudioSampleRate(qint32 audio_sample_rate); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 squelch; + bool m_squelch_isSet; + + qint32 audio_sample_rate; + bool m_audio_sample_rate_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGSSBDemodReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp new file mode 100644 index 000000000..3f06d1e98 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp @@ -0,0 +1,446 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSSBDemodSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSSBDemodSettings::SWGSSBDemodSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSSBDemodSettings::SWGSSBDemodSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + low_cutoff = 0.0f; + m_low_cutoff_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + span_log2 = 0; + m_span_log2_isSet = false; + audio_binaural = 0; + m_audio_binaural_isSet = false; + audio_flip_channels = 0; + m_audio_flip_channels_isSet = false; + dsb = 0; + m_dsb_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + agc = 0; + m_agc_isSet = false; + agc_clamping = 0; + m_agc_clamping_isSet = false; + agc_time_log2 = 0; + m_agc_time_log2_isSet = false; + agc_power_threshold = 0; + m_agc_power_threshold_isSet = false; + agc_threshold_gate = 0; + m_agc_threshold_gate_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; +} + +SWGSSBDemodSettings::~SWGSSBDemodSettings() { + this->cleanup(); +} + +void +SWGSSBDemodSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + low_cutoff = 0.0f; + m_low_cutoff_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + span_log2 = 0; + m_span_log2_isSet = false; + audio_binaural = 0; + m_audio_binaural_isSet = false; + audio_flip_channels = 0; + m_audio_flip_channels_isSet = false; + dsb = 0; + m_dsb_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + agc = 0; + m_agc_isSet = false; + agc_clamping = 0; + m_agc_clamping_isSet = false; + agc_time_log2 = 0; + m_agc_time_log2_isSet = false; + agc_power_threshold = 0; + m_agc_power_threshold_isSet = false; + agc_threshold_gate = 0; + m_agc_threshold_gate_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; +} + +void +SWGSSBDemodSettings::cleanup() { + + + + + + + + + + + + + + + + if(title != nullptr) { + delete title; + } + if(audio_device_name != nullptr) { + delete audio_device_name; + } +} + +SWGSSBDemodSettings* +SWGSSBDemodSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSSBDemodSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&low_cutoff, pJson["lowCutoff"], "float", ""); + + ::SWGSDRangel::setValue(&volume, pJson["volume"], "float", ""); + + ::SWGSDRangel::setValue(&span_log2, pJson["spanLog2"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_binaural, pJson["audioBinaural"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_flip_channels, pJson["audioFlipChannels"], "qint32", ""); + + ::SWGSDRangel::setValue(&dsb, pJson["dsb"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_mute, pJson["audioMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc, pJson["agc"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_clamping, pJson["agcClamping"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_time_log2, pJson["agcTimeLog2"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_power_threshold, pJson["agcPowerThreshold"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_threshold_gate, pJson["agcThresholdGate"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + +} + +QString +SWGSSBDemodSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSSBDemodSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_low_cutoff_isSet){ + obj->insert("lowCutoff", QJsonValue(low_cutoff)); + } + if(m_volume_isSet){ + obj->insert("volume", QJsonValue(volume)); + } + if(m_span_log2_isSet){ + obj->insert("spanLog2", QJsonValue(span_log2)); + } + if(m_audio_binaural_isSet){ + obj->insert("audioBinaural", QJsonValue(audio_binaural)); + } + if(m_audio_flip_channels_isSet){ + obj->insert("audioFlipChannels", QJsonValue(audio_flip_channels)); + } + if(m_dsb_isSet){ + obj->insert("dsb", QJsonValue(dsb)); + } + if(m_audio_mute_isSet){ + obj->insert("audioMute", QJsonValue(audio_mute)); + } + if(m_agc_isSet){ + obj->insert("agc", QJsonValue(agc)); + } + if(m_agc_clamping_isSet){ + obj->insert("agcClamping", QJsonValue(agc_clamping)); + } + if(m_agc_time_log2_isSet){ + obj->insert("agcTimeLog2", QJsonValue(agc_time_log2)); + } + if(m_agc_power_threshold_isSet){ + obj->insert("agcPowerThreshold", QJsonValue(agc_power_threshold)); + } + if(m_agc_threshold_gate_isSet){ + obj->insert("agcThresholdGate", QJsonValue(agc_threshold_gate)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGSSBDemodSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGSSBDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGSSBDemodSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGSSBDemodSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGSSBDemodSettings::getLowCutoff() { + return low_cutoff; +} +void +SWGSSBDemodSettings::setLowCutoff(float low_cutoff) { + this->low_cutoff = low_cutoff; + this->m_low_cutoff_isSet = true; +} + +float +SWGSSBDemodSettings::getVolume() { + return volume; +} +void +SWGSSBDemodSettings::setVolume(float volume) { + this->volume = volume; + this->m_volume_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getSpanLog2() { + return span_log2; +} +void +SWGSSBDemodSettings::setSpanLog2(qint32 span_log2) { + this->span_log2 = span_log2; + this->m_span_log2_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getAudioBinaural() { + return audio_binaural; +} +void +SWGSSBDemodSettings::setAudioBinaural(qint32 audio_binaural) { + this->audio_binaural = audio_binaural; + this->m_audio_binaural_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getAudioFlipChannels() { + return audio_flip_channels; +} +void +SWGSSBDemodSettings::setAudioFlipChannels(qint32 audio_flip_channels) { + this->audio_flip_channels = audio_flip_channels; + this->m_audio_flip_channels_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getDsb() { + return dsb; +} +void +SWGSSBDemodSettings::setDsb(qint32 dsb) { + this->dsb = dsb; + this->m_dsb_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getAudioMute() { + return audio_mute; +} +void +SWGSSBDemodSettings::setAudioMute(qint32 audio_mute) { + this->audio_mute = audio_mute; + this->m_audio_mute_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getAgc() { + return agc; +} +void +SWGSSBDemodSettings::setAgc(qint32 agc) { + this->agc = agc; + this->m_agc_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getAgcClamping() { + return agc_clamping; +} +void +SWGSSBDemodSettings::setAgcClamping(qint32 agc_clamping) { + this->agc_clamping = agc_clamping; + this->m_agc_clamping_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getAgcTimeLog2() { + return agc_time_log2; +} +void +SWGSSBDemodSettings::setAgcTimeLog2(qint32 agc_time_log2) { + this->agc_time_log2 = agc_time_log2; + this->m_agc_time_log2_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getAgcPowerThreshold() { + return agc_power_threshold; +} +void +SWGSSBDemodSettings::setAgcPowerThreshold(qint32 agc_power_threshold) { + this->agc_power_threshold = agc_power_threshold; + this->m_agc_power_threshold_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getAgcThresholdGate() { + return agc_threshold_gate; +} +void +SWGSSBDemodSettings::setAgcThresholdGate(qint32 agc_threshold_gate) { + this->agc_threshold_gate = agc_threshold_gate; + this->m_agc_threshold_gate_isSet = true; +} + +qint32 +SWGSSBDemodSettings::getRgbColor() { + return rgb_color; +} +void +SWGSSBDemodSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGSSBDemodSettings::getTitle() { + return title; +} +void +SWGSSBDemodSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +QString* +SWGSSBDemodSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGSSBDemodSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + + +bool +SWGSSBDemodSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_low_cutoff_isSet){ isObjectUpdated = true; break;} + if(m_volume_isSet){ isObjectUpdated = true; break;} + if(m_span_log2_isSet){ isObjectUpdated = true; break;} + if(m_audio_binaural_isSet){ isObjectUpdated = true; break;} + if(m_audio_flip_channels_isSet){ isObjectUpdated = true; break;} + if(m_dsb_isSet){ isObjectUpdated = true; break;} + if(m_audio_mute_isSet){ isObjectUpdated = true; break;} + if(m_agc_isSet){ isObjectUpdated = true; break;} + if(m_agc_clamping_isSet){ isObjectUpdated = true; break;} + if(m_agc_time_log2_isSet){ isObjectUpdated = true; break;} + if(m_agc_power_threshold_isSet){ isObjectUpdated = true; break;} + if(m_agc_threshold_gate_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h new file mode 100644 index 000000000..84bc8500c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h @@ -0,0 +1,155 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSSBDemodSettings.h + * + * SSBDemod + */ + +#ifndef SWGSSBDemodSettings_H_ +#define SWGSSBDemodSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSSBDemodSettings: public SWGObject { +public: + SWGSSBDemodSettings(); + SWGSSBDemodSettings(QString* json); + virtual ~SWGSSBDemodSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSSBDemodSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getLowCutoff(); + void setLowCutoff(float low_cutoff); + + float getVolume(); + void setVolume(float volume); + + qint32 getSpanLog2(); + void setSpanLog2(qint32 span_log2); + + qint32 getAudioBinaural(); + void setAudioBinaural(qint32 audio_binaural); + + qint32 getAudioFlipChannels(); + void setAudioFlipChannels(qint32 audio_flip_channels); + + qint32 getDsb(); + void setDsb(qint32 dsb); + + qint32 getAudioMute(); + void setAudioMute(qint32 audio_mute); + + qint32 getAgc(); + void setAgc(qint32 agc); + + qint32 getAgcClamping(); + void setAgcClamping(qint32 agc_clamping); + + qint32 getAgcTimeLog2(); + void setAgcTimeLog2(qint32 agc_time_log2); + + qint32 getAgcPowerThreshold(); + void setAgcPowerThreshold(qint32 agc_power_threshold); + + qint32 getAgcThresholdGate(); + void setAgcThresholdGate(qint32 agc_threshold_gate); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float low_cutoff; + bool m_low_cutoff_isSet; + + float volume; + bool m_volume_isSet; + + qint32 span_log2; + bool m_span_log2_isSet; + + qint32 audio_binaural; + bool m_audio_binaural_isSet; + + qint32 audio_flip_channels; + bool m_audio_flip_channels_isSet; + + qint32 dsb; + bool m_dsb_isSet; + + qint32 audio_mute; + bool m_audio_mute_isSet; + + qint32 agc; + bool m_agc_isSet; + + qint32 agc_clamping; + bool m_agc_clamping_isSet; + + qint32 agc_time_log2; + bool m_agc_time_log2_isSet; + + qint32 agc_power_threshold; + bool m_agc_power_threshold_isSet; + + qint32 agc_threshold_gate; + bool m_agc_threshold_gate_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + QString* audio_device_name; + bool m_audio_device_name_isSet; + +}; + +} + +#endif /* SWGSSBDemodSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h index 233980633..0a2faf32b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h @@ -13,7 +13,7 @@ /* * SWGSSBModReport.h * - * AMMod + * SSBMod */ #ifndef SWGSSBModReport_H_ diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 5cc659704..799cfec20 100644 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -211,6 +211,21 @@ def setupChannel(deviceset_url, options): settings["AMDemodSettings"]["squelch"] = options.squelch_db settings["AMDemodSettings"]["title"] = "Channel %d" % i settings["AMDemodSettings"]["bandpassEnable"] = 1 # bandpass filter + elif options.channel_id == "SSBDemod": + settings["SSBDemodSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["SSBDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["SSBDemodSettings"]["lowCutoff"] = -300 if options.rf_bw < 0 else 300 + settings["SSBDemodSettings"]["audioBinaural"] = 1 + settings["SSBDemodSettings"]["audioFlipChannels"] = 0 + settings["SSBDemodSettings"]["dsb"] = 0 + settings["SSBDemodSettings"]["audioMute"] = 0 + settings["SSBDemodSettings"]["agc"] = 1 + settings["SSBDemodSettings"]["agcClamping"] = 0 + settings["SSBDemodSettings"]["agcTimeLog2"] = 8 + settings["SSBDemodSettings"]["agcPowerThreshold"] = options.squelch_db + settings["SSBDemodSettings"]["agcThresholdGate"] = 4 + settings["SSBDemodSettings"]["volume"] = options.volume + settings["SSBDemodSettings"]["title"] = "Channel %d" % i elif options.channel_id == "DSDDemod": settings["DSDDemodSettings"]["inputFrequencyOffset"] = options.channel_freq settings["DSDDemodSettings"]["rfBandwidth"] = options.rf_bw From 3f303a0c0d28a6db56706a590f15ece0e48f5bb1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 18:07:21 +0200 Subject: [PATCH 469/956] PlutoSDR input: implemeted WEB API --- .../plutosdrinput/plutosdrinput.cpp | 143 ++++++ .../plutosdrinput/plutosdrinput.h | 16 + .../plutosdrinput/plutosdrinputgui.cpp | 1 + sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 154 +++++- .../webapi/doc/swagger/include/PlutoSdr.yaml | 98 ++++ .../resources/webapi/doc/swagger/swagger.yaml | 10 +- sdrbase/webapi/webapirequestmapper.cpp | 15 + .../api/swagger/include/PlutoSdr.yaml | 98 ++++ swagger/sdrangel/api/swagger/swagger.yaml | 10 +- swagger/sdrangel/code/html2/index.html | 154 +++++- .../code/qt5/client/SWGDeviceReport.cpp | 46 ++ .../code/qt5/client/SWGDeviceReport.h | 16 +- .../code/qt5/client/SWGDeviceSettings.cpp | 46 ++ .../code/qt5/client/SWGDeviceSettings.h | 14 + .../code/qt5/client/SWGModelFactory.h | 16 + .../qt5/client/SWGPlutoSdrInputReport.cpp | 171 +++++++ .../code/qt5/client/SWGPlutoSdrInputReport.h | 77 +++ .../qt5/client/SWGPlutoSdrInputSettings.cpp | 465 ++++++++++++++++++ .../qt5/client/SWGPlutoSdrInputSettings.h | 161 ++++++ .../qt5/client/SWGPlutoSdrOutputReport.cpp | 150 ++++++ .../code/qt5/client/SWGPlutoSdrOutputReport.h | 71 +++ .../qt5/client/SWGPlutoSdrOutputSettings.cpp | 295 +++++++++++ .../qt5/client/SWGPlutoSdrOutputSettings.h | 112 +++++ swagger/sdrangel/examples/rx_test.py | 30 +- 25 files changed, 2361 insertions(+), 9 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/PlutoSdr.yaml create mode 100644 swagger/sdrangel/api/swagger/include/PlutoSdr.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index bd9d1afae..4cfcb7e01 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -18,6 +18,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGPlutoSdrInputReport.h" #include "dsp/filerecord.h" #include "dsp/dspcommands.h" @@ -624,3 +626,144 @@ int PlutoSDRInput::webapiRun( return 200; } +int PlutoSDRInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPlutoSdrInputSettings(new SWGSDRangel::SWGPlutoSdrInputSettings()); + response.getPlutoSdrInputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int PlutoSDRInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + PlutoSDRInputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getPlutoSdrInputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getPlutoSdrInputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getPlutoSdrInputSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("lpfFIREnable")) { + settings.m_lpfFIREnable = response.getPlutoSdrInputSettings()->getLpfFirEnable() != 0; + } + if (deviceSettingsKeys.contains("lpfFIRBW")) { + settings.m_lpfFIRBW = response.getPlutoSdrInputSettings()->getLpfFirbw(); + } + if (deviceSettingsKeys.contains("lpfFIRlog2Decim")) { + settings.m_lpfFIRlog2Decim = response.getPlutoSdrInputSettings()->getLpfFiRlog2Decim(); + } + if (deviceSettingsKeys.contains("lpfFIRGain")) { + settings.m_lpfFIRGain = response.getPlutoSdrInputSettings()->getLpfFirGain(); + } + if (deviceSettingsKeys.contains("fcPos")) { + int fcPos = response.getPlutoSdrInputSettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (PlutoSDRInputSettings::fcPos_t) fcPos; + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getPlutoSdrInputSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getPlutoSdrInputSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getPlutoSdrInputSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("lpfBW")) { + settings.m_lpfBW = response.getPlutoSdrInputSettings()->getLpfBw(); + } + if (deviceSettingsKeys.contains("gain")) { + settings.m_gain = response.getPlutoSdrInputSettings()->getGain(); + } + if (deviceSettingsKeys.contains("antennaPath")) { + int antennaPath = response.getPlutoSdrInputSettings()->getAntennaPath(); + antennaPath = antennaPath < 0 ? 0 : antennaPath >= PlutoSDRInputSettings::RFPATH_END ? PlutoSDRInputSettings::RFPATH_END-1 : antennaPath; + settings.m_antennaPath = (PlutoSDRInputSettings::RFPath) antennaPath; + } + if (deviceSettingsKeys.contains("gainMode")) { + int gainMode = response.getPlutoSdrInputSettings()->getGainMode(); + gainMode = gainMode < 0 ? 0 : gainMode >= PlutoSDRInputSettings::GAIN_END ? PlutoSDRInputSettings::GAIN_END-1 : gainMode; + settings.m_gainMode = (PlutoSDRInputSettings::GainMode) gainMode; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getPlutoSdrInputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getPlutoSdrInputSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getPlutoSdrInputSettings()->getFileRecordName(); + } + + MsgConfigurePlutoSDR *msg = MsgConfigurePlutoSDR::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigurePlutoSDR *msgToGUI = MsgConfigurePlutoSDR::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int PlutoSDRInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPlutoSdrInputReport(new SWGSDRangel::SWGPlutoSdrInputReport()); + response.getPlutoSdrInputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void PlutoSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PlutoSDRInputSettings& settings) +{ + response.getPlutoSdrInputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getPlutoSdrInputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getPlutoSdrInputSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getPlutoSdrInputSettings()->setLpfFirEnable(settings.m_lpfFIREnable ? 1 : 0); + response.getPlutoSdrInputSettings()->setLpfFirbw(settings.m_lpfFIRBW); + response.getPlutoSdrInputSettings()->setLpfFiRlog2Decim(settings.m_lpfFIRlog2Decim); + response.getPlutoSdrInputSettings()->setLpfFirGain(settings.m_lpfFIRGain); + response.getPlutoSdrInputSettings()->setFcPos((int) settings.m_fcPos); + response.getPlutoSdrInputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getPlutoSdrInputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getPlutoSdrInputSettings()->setLog2Decim(settings.m_log2Decim); + response.getPlutoSdrInputSettings()->setLpfBw(settings.m_lpfBW); + response.getPlutoSdrInputSettings()->setGain(settings.m_gain); + response.getPlutoSdrInputSettings()->setAntennaPath((int) m_settings.m_antennaPath); + response.getPlutoSdrInputSettings()->setGainMode((int) settings.m_gainMode); + response.getPlutoSdrInputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getPlutoSdrInputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getPlutoSdrInputSettings()->getFileRecordName()) { + *response.getPlutoSdrInputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getPlutoSdrInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +void PlutoSDRInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getPlutoSdrInputReport()->setAdcRate(getADCSampleRate()); + std::string rssiStr; + getRSSI(rssiStr); + response.getPlutoSdrInputReport()->setRssi(new QString(rssiStr.c_str())); + int gainDB; + getGain(gainDB); + response.getPlutoSdrInputReport()->setGainDb(gainDB); + fetchTemperature(); + response.getPlutoSdrInputReport()->setTemperature(getTemperature()); +} diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.h b/plugins/samplesource/plutosdrinput/plutosdrinput.h index daefbdda7..16e3e9acb 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.h @@ -121,6 +121,20 @@ public: SWGSDRangel::SWGDeviceState& response, QString& errorMessage); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + uint32_t getADCSampleRate() const { return m_deviceSampleRates.m_addaConnvRate; } uint32_t getFIRSampleRate() const { return m_deviceSampleRates.m_hb1Rate; } void getRSSI(std::string& rssiStr); @@ -145,6 +159,8 @@ public: void suspendBuddies(); void resumeBuddies(); bool applySettings(const PlutoSDRInputSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PlutoSDRInputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp index af07995d1..65ce1477f 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp @@ -311,6 +311,7 @@ void PlutoSDRInputGui::displaySettings() ui->loPPMText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); ui->swDecim->setCurrentIndex(m_settings.m_log2Decim); + ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); ui->lpf->setValue(m_settings.m_lpfBW / 1000); diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 486d4d98b..40ed2f324 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -19,6 +19,7 @@ webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml webapi/doc/swagger/include/Perseus.yaml + webapi/doc/swagger/include/PlutoSdr.yaml webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger/include/SSBDemod.yaml webapi/doc/swagger/include/SSBMod.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index da3557fe4..479c597c1 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1724,11 +1724,17 @@ margin-bottom: 20px; "perseusReport" : { "$ref" : "#/definitions/PerseusReport" }, + "plutoSdrInputReport" : { + "$ref" : "#/definitions/PlutoSdrInputReport" + }, + "plutoSdrOutputReport" : { + "$ref" : "#/definitions/PlutoSdrOutputReport" + }, "rtlSdrReport" : { "$ref" : "#/definitions/RtlSdrReport" } }, - "description" : "Base device report. The specific device report present depeds on deviceHwType" + "description" : "Base device report. The specific device report present depends on deviceHwType" }; defs.DeviceSet = { "required" : [ "channelcount", "samplingDevice" ], @@ -1818,6 +1824,12 @@ margin-bottom: 20px; "perseusSettings" : { "$ref" : "#/definitions/PerseusSettings" }, + "plutoSdrInputSettings" : { + "$ref" : "#/definitions/PlutoSdrInputSettings" + }, + "plutoSdrOutputSettings" : { + "$ref" : "#/definitions/PlutoSdrOutputSettings" + }, "rtlSdrSettings" : { "$ref" : "#/definitions/RtlSdrSettings" } @@ -2533,6 +2545,144 @@ margin-bottom: 20px; } }, "description" : "Perseus" +}; + defs.PlutoSdrInputReport = { + "properties" : { + "adcRate" : { + "type" : "integer" + }, + "rssi" : { + "type" : "string" + }, + "gainDB" : { + "type" : "integer" + }, + "temperature" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "PlutoSDR" +}; + defs.PlutoSdrInputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "lpfFIREnable" : { + "type" : "integer", + "description" : "Low pass FIR filter enable (1 if enabled else 0)" + }, + "lpfFIRBW" : { + "type" : "integer", + "description" : "digital lowpass FIR filter bandwidth (Hz)" + }, + "lpfFIRlog2Decim" : { + "type" : "integer", + "description" : "digital lowpass FIR filter log2 of decimation factor (0..2)" + }, + "lpfFIRGain" : { + "type" : "integer", + "description" : "digital lowpass FIR filter gain (dB)" + }, + "fcPos" : { + "type" : "integer", + "description" : "0=Infra 1=Supra 2=Center" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "lpfBW" : { + "type" : "integer", + "description" : "Analog lowpass filter bandwidth (Hz)" + }, + "gain" : { + "type" : "integer", + "description" : "Hardware gain" + }, + "antennaPath" : { + "type" : "integer" + }, + "gainMode" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "PlutoSDR" +}; + defs.PlutoSdrOutputReport = { + "properties" : { + "dacRate" : { + "type" : "integer" + }, + "rssi" : { + "type" : "string" + }, + "temperature" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "PlutoSDR" +}; + defs.PlutoSdrOutputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "log2HardInterp" : { + "type" : "integer" + }, + "log2SoftInterp" : { + "type" : "integer" + }, + "lpfBW" : { + "type" : "integer" + }, + "lpfFIREnable" : { + "type" : "integer" + }, + "lpfFIRBW" : { + "type" : "integer" + }, + "gain" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + } + }, + "description" : "PlutoSDR" }; defs.PresetExport = { "properties" : { @@ -22039,7 +22189,7 @@ except ApiException as e:
    - Generated 2018-05-26T14:52:58.621+02:00 + Generated 2018-05-26T16:46:01.765+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/PlutoSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/PlutoSdr.yaml new file mode 100644 index 000000000..488d06c05 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/PlutoSdr.yaml @@ -0,0 +1,98 @@ +PlutoSdrInputSettings: + description: PlutoSDR + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + LOppmTenths: + type: integer + lpfFIREnable: + description: Low pass FIR filter enable (1 if enabled else 0) + type: integer + lpfFIRBW: + description: digital lowpass FIR filter bandwidth (Hz) + type: integer + lpfFIRlog2Decim: + description: digital lowpass FIR filter log2 of decimation factor (0..2) + type: integer + lpfFIRGain: + description: digital lowpass FIR filter gain (dB) + type: integer + fcPos: + description: 0=Infra 1=Supra 2=Center + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + log2Decim: + type: integer + lpfBW: + description: Analog lowpass filter bandwidth (Hz) + type: integer + gain: + description: Hardware gain + type: integer + antennaPath: + type: integer + gainMode: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string + +PlutoSdrOutputSettings: + description: PlutoSDR + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + log2HardInterp: + type: integer + log2SoftInterp: + type: integer + lpfBW: + type: integer + lpfFIREnable: + type: integer + lpfFIRBW: + type: integer + gain: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + +PlutoSdrInputReport: + description: PlutoSDR + properties: + adcRate: + type: integer + rssi: + type: string + gainDB: + type: integer + temperature: + type: number + format: float + +PlutoSdrOutputReport: + description: PlutoSDR + properties: + dacRate: + type: integer + rssi: + type: string + temperature: + type: number + format: float \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 7b5368635..a63c5e693 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1769,11 +1769,15 @@ definitions: $ref: "/doc/swagger/include/LimeSdr.yaml#/LimeSdrOutputSettings" perseusSettings: $ref: "/doc/swagger/include/Perseus.yaml#/PerseusSettings" + plutoSdrInputSettings: + $ref: "/doc/swagger/include/PlutoSdr.yaml#/PlutoSdrInputSettings" + plutoSdrOutputSettings: + $ref: "/doc/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputSettings" rtlSdrSettings: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrSettings" DeviceReport: - description: Base device report. The specific device report present depeds on deviceHwType + description: Base device report. The specific device report present depends on deviceHwType discriminator: deviceHwType required: - deviceHwType @@ -1793,6 +1797,10 @@ definitions: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceReport" perseusReport: $ref: "/doc/swagger/include/Perseus.yaml#/PerseusReport" + plutoSdrInputReport: + $ref: "/doc/swagger/include/PlutoSdr.yaml#/PlutoSdrInputReport" + plutoSdrOutputReport: + $ref: "/doc/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputReport" rtlSdrReport: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrReport" diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index d3d2a7de8..6a3ca471c 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1886,6 +1886,21 @@ bool WebAPIRequestMapper::validateDeviceSettings( return false; } } + else if ((*deviceHwType == "PlutoSDR") && (deviceSettings.getTx() == 0)) + { + if (jsonObject.contains("plutoSdrInputSettings") && jsonObject["plutoSdrInputSettings"].isObject()) + { + QJsonObject plutoSdrInputSettingsJsonObject = jsonObject["plutoSdrInputSettings"].toObject(); + deviceSettingsKeys = plutoSdrInputSettingsJsonObject.keys(); + deviceSettings.setPlutoSdrInputSettings(new SWGSDRangel::SWGPlutoSdrInputSettings()); + deviceSettings.getPlutoSdrInputSettings()->fromJsonObject(plutoSdrInputSettingsJsonObject); + return true; + } + else + { + return false; + } + } else if (*deviceHwType == "RTLSDR") { if (jsonObject.contains("rtlSdrSettings") && jsonObject["rtlSdrSettings"].isObject()) diff --git a/swagger/sdrangel/api/swagger/include/PlutoSdr.yaml b/swagger/sdrangel/api/swagger/include/PlutoSdr.yaml new file mode 100644 index 000000000..488d06c05 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/PlutoSdr.yaml @@ -0,0 +1,98 @@ +PlutoSdrInputSettings: + description: PlutoSDR + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + LOppmTenths: + type: integer + lpfFIREnable: + description: Low pass FIR filter enable (1 if enabled else 0) + type: integer + lpfFIRBW: + description: digital lowpass FIR filter bandwidth (Hz) + type: integer + lpfFIRlog2Decim: + description: digital lowpass FIR filter log2 of decimation factor (0..2) + type: integer + lpfFIRGain: + description: digital lowpass FIR filter gain (dB) + type: integer + fcPos: + description: 0=Infra 1=Supra 2=Center + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + log2Decim: + type: integer + lpfBW: + description: Analog lowpass filter bandwidth (Hz) + type: integer + gain: + description: Hardware gain + type: integer + antennaPath: + type: integer + gainMode: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + fileRecordName: + type: string + +PlutoSdrOutputSettings: + description: PlutoSDR + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + log2HardInterp: + type: integer + log2SoftInterp: + type: integer + lpfBW: + type: integer + lpfFIREnable: + type: integer + lpfFIRBW: + type: integer + gain: + type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 + +PlutoSdrInputReport: + description: PlutoSDR + properties: + adcRate: + type: integer + rssi: + type: string + gainDB: + type: integer + temperature: + type: number + format: float + +PlutoSdrOutputReport: + description: PlutoSDR + properties: + dacRate: + type: integer + rssi: + type: string + temperature: + type: number + format: float \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 307efde8e..726561834 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1769,11 +1769,15 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/LimeSdr.yaml#/LimeSdrOutputSettings" perseusSettings: $ref: "http://localhost:8081/api/swagger/include/Perseus.yaml#/PerseusSettings" + plutoSdrInputSettings: + $ref: "http://localhost:8081/api/swagger/include/PlutoSdr.yaml#/PlutoSdrInputSettings" + plutoSdrOutputSettings: + $ref: "http://localhost:8081/api/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputSettings" rtlSdrSettings: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrSettings" DeviceReport: - description: Base device report. The specific device report present depeds on deviceHwType + description: Base device report. The specific device report present depends on deviceHwType discriminator: deviceHwType required: - deviceHwType @@ -1793,6 +1797,10 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceReport" perseusReport: $ref: "http://localhost:8081/api/swagger/include/Perseus.yaml#/PerseusReport" + plutoSdrInputReport: + $ref: "http://localhost:8081/api/swagger/include/PlutoSdr.yaml#/PlutoSdrInputReport" + plutoSdrOutputReport: + $ref: "http://localhost:8081/api/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputReport" rtlSdrReport: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrReport" diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index da3557fe4..479c597c1 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1724,11 +1724,17 @@ margin-bottom: 20px; "perseusReport" : { "$ref" : "#/definitions/PerseusReport" }, + "plutoSdrInputReport" : { + "$ref" : "#/definitions/PlutoSdrInputReport" + }, + "plutoSdrOutputReport" : { + "$ref" : "#/definitions/PlutoSdrOutputReport" + }, "rtlSdrReport" : { "$ref" : "#/definitions/RtlSdrReport" } }, - "description" : "Base device report. The specific device report present depeds on deviceHwType" + "description" : "Base device report. The specific device report present depends on deviceHwType" }; defs.DeviceSet = { "required" : [ "channelcount", "samplingDevice" ], @@ -1818,6 +1824,12 @@ margin-bottom: 20px; "perseusSettings" : { "$ref" : "#/definitions/PerseusSettings" }, + "plutoSdrInputSettings" : { + "$ref" : "#/definitions/PlutoSdrInputSettings" + }, + "plutoSdrOutputSettings" : { + "$ref" : "#/definitions/PlutoSdrOutputSettings" + }, "rtlSdrSettings" : { "$ref" : "#/definitions/RtlSdrSettings" } @@ -2533,6 +2545,144 @@ margin-bottom: 20px; } }, "description" : "Perseus" +}; + defs.PlutoSdrInputReport = { + "properties" : { + "adcRate" : { + "type" : "integer" + }, + "rssi" : { + "type" : "string" + }, + "gainDB" : { + "type" : "integer" + }, + "temperature" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "PlutoSDR" +}; + defs.PlutoSdrInputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "lpfFIREnable" : { + "type" : "integer", + "description" : "Low pass FIR filter enable (1 if enabled else 0)" + }, + "lpfFIRBW" : { + "type" : "integer", + "description" : "digital lowpass FIR filter bandwidth (Hz)" + }, + "lpfFIRlog2Decim" : { + "type" : "integer", + "description" : "digital lowpass FIR filter log2 of decimation factor (0..2)" + }, + "lpfFIRGain" : { + "type" : "integer", + "description" : "digital lowpass FIR filter gain (dB)" + }, + "fcPos" : { + "type" : "integer", + "description" : "0=Infra 1=Supra 2=Center" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "lpfBW" : { + "type" : "integer", + "description" : "Analog lowpass filter bandwidth (Hz)" + }, + "gain" : { + "type" : "integer", + "description" : "Hardware gain" + }, + "antennaPath" : { + "type" : "integer" + }, + "gainMode" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "PlutoSDR" +}; + defs.PlutoSdrOutputReport = { + "properties" : { + "dacRate" : { + "type" : "integer" + }, + "rssi" : { + "type" : "string" + }, + "temperature" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "PlutoSDR" +}; + defs.PlutoSdrOutputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "log2HardInterp" : { + "type" : "integer" + }, + "log2SoftInterp" : { + "type" : "integer" + }, + "lpfBW" : { + "type" : "integer" + }, + "lpfFIREnable" : { + "type" : "integer" + }, + "lpfFIRBW" : { + "type" : "integer" + }, + "gain" : { + "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + } + }, + "description" : "PlutoSDR" }; defs.PresetExport = { "properties" : { @@ -22039,7 +22189,7 @@ except ApiException as e:
    - Generated 2018-05-26T14:52:58.621+02:00 + Generated 2018-05-26T16:46:01.765+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index 07d6195e4..ce627f326 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -40,6 +40,10 @@ SWGDeviceReport::SWGDeviceReport() { m_file_source_report_isSet = false; perseus_report = nullptr; m_perseus_report_isSet = false; + pluto_sdr_input_report = nullptr; + m_pluto_sdr_input_report_isSet = false; + pluto_sdr_output_report = nullptr; + m_pluto_sdr_output_report_isSet = false; rtl_sdr_report = nullptr; m_rtl_sdr_report_isSet = false; } @@ -62,6 +66,10 @@ SWGDeviceReport::init() { m_file_source_report_isSet = false; perseus_report = new SWGPerseusReport(); m_perseus_report_isSet = false; + pluto_sdr_input_report = new SWGPlutoSdrInputReport(); + m_pluto_sdr_input_report_isSet = false; + pluto_sdr_output_report = new SWGPlutoSdrOutputReport(); + m_pluto_sdr_output_report_isSet = false; rtl_sdr_report = new SWGRtlSdrReport(); m_rtl_sdr_report_isSet = false; } @@ -84,6 +92,12 @@ SWGDeviceReport::cleanup() { if(perseus_report != nullptr) { delete perseus_report; } + if(pluto_sdr_input_report != nullptr) { + delete pluto_sdr_input_report; + } + if(pluto_sdr_output_report != nullptr) { + delete pluto_sdr_output_report; + } if(rtl_sdr_report != nullptr) { delete rtl_sdr_report; } @@ -112,6 +126,10 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&perseus_report, pJson["perseusReport"], "SWGPerseusReport", "SWGPerseusReport"); + ::SWGSDRangel::setValue(&pluto_sdr_input_report, pJson["plutoSdrInputReport"], "SWGPlutoSdrInputReport", "SWGPlutoSdrInputReport"); + + ::SWGSDRangel::setValue(&pluto_sdr_output_report, pJson["plutoSdrOutputReport"], "SWGPlutoSdrOutputReport", "SWGPlutoSdrOutputReport"); + ::SWGSDRangel::setValue(&rtl_sdr_report, pJson["rtlSdrReport"], "SWGRtlSdrReport", "SWGRtlSdrReport"); } @@ -148,6 +166,12 @@ SWGDeviceReport::asJsonObject() { if((perseus_report != nullptr) && (perseus_report->isSet())){ toJsonValue(QString("perseusReport"), perseus_report, obj, QString("SWGPerseusReport")); } + if((pluto_sdr_input_report != nullptr) && (pluto_sdr_input_report->isSet())){ + toJsonValue(QString("plutoSdrInputReport"), pluto_sdr_input_report, obj, QString("SWGPlutoSdrInputReport")); + } + if((pluto_sdr_output_report != nullptr) && (pluto_sdr_output_report->isSet())){ + toJsonValue(QString("plutoSdrOutputReport"), pluto_sdr_output_report, obj, QString("SWGPlutoSdrOutputReport")); + } if((rtl_sdr_report != nullptr) && (rtl_sdr_report->isSet())){ toJsonValue(QString("rtlSdrReport"), rtl_sdr_report, obj, QString("SWGRtlSdrReport")); } @@ -215,6 +239,26 @@ SWGDeviceReport::setPerseusReport(SWGPerseusReport* perseus_report) { this->m_perseus_report_isSet = true; } +SWGPlutoSdrInputReport* +SWGDeviceReport::getPlutoSdrInputReport() { + return pluto_sdr_input_report; +} +void +SWGDeviceReport::setPlutoSdrInputReport(SWGPlutoSdrInputReport* pluto_sdr_input_report) { + this->pluto_sdr_input_report = pluto_sdr_input_report; + this->m_pluto_sdr_input_report_isSet = true; +} + +SWGPlutoSdrOutputReport* +SWGDeviceReport::getPlutoSdrOutputReport() { + return pluto_sdr_output_report; +} +void +SWGDeviceReport::setPlutoSdrOutputReport(SWGPlutoSdrOutputReport* pluto_sdr_output_report) { + this->pluto_sdr_output_report = pluto_sdr_output_report; + this->m_pluto_sdr_output_report_isSet = true; +} + SWGRtlSdrReport* SWGDeviceReport::getRtlSdrReport() { return rtl_sdr_report; @@ -236,6 +280,8 @@ SWGDeviceReport::isSet(){ if(airspy_hf_report != nullptr && airspy_hf_report->isSet()){ isObjectUpdated = true; break;} if(file_source_report != nullptr && file_source_report->isSet()){ isObjectUpdated = true; break;} if(perseus_report != nullptr && perseus_report->isSet()){ isObjectUpdated = true; break;} + if(pluto_sdr_input_report != nullptr && pluto_sdr_input_report->isSet()){ isObjectUpdated = true; break;} + if(pluto_sdr_output_report != nullptr && pluto_sdr_output_report->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_report != nullptr && rtl_sdr_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index d54c7d20c..c8c2fa654 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -13,7 +13,7 @@ /* * SWGDeviceReport.h * - * Base device report. The specific device report present depeds on deviceHwType + * Base device report. The specific device report present depends on deviceHwType */ #ifndef SWGDeviceReport_H_ @@ -26,6 +26,8 @@ #include "SWGAirspyReport.h" #include "SWGFileSourceReport.h" #include "SWGPerseusReport.h" +#include "SWGPlutoSdrInputReport.h" +#include "SWGPlutoSdrOutputReport.h" #include "SWGRtlSdrReport.h" #include @@ -65,6 +67,12 @@ public: SWGPerseusReport* getPerseusReport(); void setPerseusReport(SWGPerseusReport* perseus_report); + SWGPlutoSdrInputReport* getPlutoSdrInputReport(); + void setPlutoSdrInputReport(SWGPlutoSdrInputReport* pluto_sdr_input_report); + + SWGPlutoSdrOutputReport* getPlutoSdrOutputReport(); + void setPlutoSdrOutputReport(SWGPlutoSdrOutputReport* pluto_sdr_output_report); + SWGRtlSdrReport* getRtlSdrReport(); void setRtlSdrReport(SWGRtlSdrReport* rtl_sdr_report); @@ -90,6 +98,12 @@ private: SWGPerseusReport* perseus_report; bool m_perseus_report_isSet; + SWGPlutoSdrInputReport* pluto_sdr_input_report; + bool m_pluto_sdr_input_report_isSet; + + SWGPlutoSdrOutputReport* pluto_sdr_output_report; + bool m_pluto_sdr_output_report_isSet; + SWGRtlSdrReport* rtl_sdr_report; bool m_rtl_sdr_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 04f16b434..e8f645521 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -56,6 +56,10 @@ SWGDeviceSettings::SWGDeviceSettings() { m_lime_sdr_output_settings_isSet = false; perseus_settings = nullptr; m_perseus_settings_isSet = false; + pluto_sdr_input_settings = nullptr; + m_pluto_sdr_input_settings_isSet = false; + pluto_sdr_output_settings = nullptr; + m_pluto_sdr_output_settings_isSet = false; rtl_sdr_settings = nullptr; m_rtl_sdr_settings_isSet = false; } @@ -94,6 +98,10 @@ SWGDeviceSettings::init() { m_lime_sdr_output_settings_isSet = false; perseus_settings = new SWGPerseusSettings(); m_perseus_settings_isSet = false; + pluto_sdr_input_settings = new SWGPlutoSdrInputSettings(); + m_pluto_sdr_input_settings_isSet = false; + pluto_sdr_output_settings = new SWGPlutoSdrOutputSettings(); + m_pluto_sdr_output_settings_isSet = false; rtl_sdr_settings = new SWGRtlSdrSettings(); m_rtl_sdr_settings_isSet = false; } @@ -140,6 +148,12 @@ SWGDeviceSettings::cleanup() { if(perseus_settings != nullptr) { delete perseus_settings; } + if(pluto_sdr_input_settings != nullptr) { + delete pluto_sdr_input_settings; + } + if(pluto_sdr_output_settings != nullptr) { + delete pluto_sdr_output_settings; + } if(rtl_sdr_settings != nullptr) { delete rtl_sdr_settings; } @@ -184,6 +198,10 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&perseus_settings, pJson["perseusSettings"], "SWGPerseusSettings", "SWGPerseusSettings"); + ::SWGSDRangel::setValue(&pluto_sdr_input_settings, pJson["plutoSdrInputSettings"], "SWGPlutoSdrInputSettings", "SWGPlutoSdrInputSettings"); + + ::SWGSDRangel::setValue(&pluto_sdr_output_settings, pJson["plutoSdrOutputSettings"], "SWGPlutoSdrOutputSettings", "SWGPlutoSdrOutputSettings"); + ::SWGSDRangel::setValue(&rtl_sdr_settings, pJson["rtlSdrSettings"], "SWGRtlSdrSettings", "SWGRtlSdrSettings"); } @@ -244,6 +262,12 @@ SWGDeviceSettings::asJsonObject() { if((perseus_settings != nullptr) && (perseus_settings->isSet())){ toJsonValue(QString("perseusSettings"), perseus_settings, obj, QString("SWGPerseusSettings")); } + if((pluto_sdr_input_settings != nullptr) && (pluto_sdr_input_settings->isSet())){ + toJsonValue(QString("plutoSdrInputSettings"), pluto_sdr_input_settings, obj, QString("SWGPlutoSdrInputSettings")); + } + if((pluto_sdr_output_settings != nullptr) && (pluto_sdr_output_settings->isSet())){ + toJsonValue(QString("plutoSdrOutputSettings"), pluto_sdr_output_settings, obj, QString("SWGPlutoSdrOutputSettings")); + } if((rtl_sdr_settings != nullptr) && (rtl_sdr_settings->isSet())){ toJsonValue(QString("rtlSdrSettings"), rtl_sdr_settings, obj, QString("SWGRtlSdrSettings")); } @@ -391,6 +415,26 @@ SWGDeviceSettings::setPerseusSettings(SWGPerseusSettings* perseus_settings) { this->m_perseus_settings_isSet = true; } +SWGPlutoSdrInputSettings* +SWGDeviceSettings::getPlutoSdrInputSettings() { + return pluto_sdr_input_settings; +} +void +SWGDeviceSettings::setPlutoSdrInputSettings(SWGPlutoSdrInputSettings* pluto_sdr_input_settings) { + this->pluto_sdr_input_settings = pluto_sdr_input_settings; + this->m_pluto_sdr_input_settings_isSet = true; +} + +SWGPlutoSdrOutputSettings* +SWGDeviceSettings::getPlutoSdrOutputSettings() { + return pluto_sdr_output_settings; +} +void +SWGDeviceSettings::setPlutoSdrOutputSettings(SWGPlutoSdrOutputSettings* pluto_sdr_output_settings) { + this->pluto_sdr_output_settings = pluto_sdr_output_settings; + this->m_pluto_sdr_output_settings_isSet = true; +} + SWGRtlSdrSettings* SWGDeviceSettings::getRtlSdrSettings() { return rtl_sdr_settings; @@ -420,6 +464,8 @@ SWGDeviceSettings::isSet(){ if(lime_sdr_input_settings != nullptr && lime_sdr_input_settings->isSet()){ isObjectUpdated = true; break;} if(lime_sdr_output_settings != nullptr && lime_sdr_output_settings->isSet()){ isObjectUpdated = true; break;} if(perseus_settings != nullptr && perseus_settings->isSet()){ isObjectUpdated = true; break;} + if(pluto_sdr_input_settings != nullptr && pluto_sdr_input_settings->isSet()){ isObjectUpdated = true; break;} + if(pluto_sdr_output_settings != nullptr && pluto_sdr_output_settings->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_settings != nullptr && rtl_sdr_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 9de16ea26..243e9e233 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -34,6 +34,8 @@ #include "SWGLimeSdrInputSettings.h" #include "SWGLimeSdrOutputSettings.h" #include "SWGPerseusSettings.h" +#include "SWGPlutoSdrInputSettings.h" +#include "SWGPlutoSdrOutputSettings.h" #include "SWGRtlSdrSettings.h" #include @@ -97,6 +99,12 @@ public: SWGPerseusSettings* getPerseusSettings(); void setPerseusSettings(SWGPerseusSettings* perseus_settings); + SWGPlutoSdrInputSettings* getPlutoSdrInputSettings(); + void setPlutoSdrInputSettings(SWGPlutoSdrInputSettings* pluto_sdr_input_settings); + + SWGPlutoSdrOutputSettings* getPlutoSdrOutputSettings(); + void setPlutoSdrOutputSettings(SWGPlutoSdrOutputSettings* pluto_sdr_output_settings); + SWGRtlSdrSettings* getRtlSdrSettings(); void setRtlSdrSettings(SWGRtlSdrSettings* rtl_sdr_settings); @@ -146,6 +154,12 @@ private: SWGPerseusSettings* perseus_settings; bool m_perseus_settings_isSet; + SWGPlutoSdrInputSettings* pluto_sdr_input_settings; + bool m_pluto_sdr_input_settings_isSet; + + SWGPlutoSdrOutputSettings* pluto_sdr_output_settings; + bool m_pluto_sdr_output_settings_isSet; + SWGRtlSdrSettings* rtl_sdr_settings; bool m_rtl_sdr_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 63916e9d8..5d82a9df7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -68,6 +68,10 @@ #include "SWGNFMModSettings.h" #include "SWGPerseusReport.h" #include "SWGPerseusSettings.h" +#include "SWGPlutoSdrInputReport.h" +#include "SWGPlutoSdrInputSettings.h" +#include "SWGPlutoSdrOutputReport.h" +#include "SWGPlutoSdrOutputSettings.h" #include "SWGPresetExport.h" #include "SWGPresetGroup.h" #include "SWGPresetIdentifier.h" @@ -260,6 +264,18 @@ namespace SWGSDRangel { if(QString("SWGPerseusSettings").compare(type) == 0) { return new SWGPerseusSettings(); } + if(QString("SWGPlutoSdrInputReport").compare(type) == 0) { + return new SWGPlutoSdrInputReport(); + } + if(QString("SWGPlutoSdrInputSettings").compare(type) == 0) { + return new SWGPlutoSdrInputSettings(); + } + if(QString("SWGPlutoSdrOutputReport").compare(type) == 0) { + return new SWGPlutoSdrOutputReport(); + } + if(QString("SWGPlutoSdrOutputSettings").compare(type) == 0) { + return new SWGPlutoSdrOutputSettings(); + } if(QString("SWGPresetExport").compare(type) == 0) { return new SWGPresetExport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp new file mode 100644 index 000000000..f9a037f97 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp @@ -0,0 +1,171 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPlutoSdrInputReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPlutoSdrInputReport::SWGPlutoSdrInputReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPlutoSdrInputReport::SWGPlutoSdrInputReport() { + adc_rate = 0; + m_adc_rate_isSet = false; + rssi = nullptr; + m_rssi_isSet = false; + gain_db = 0; + m_gain_db_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; +} + +SWGPlutoSdrInputReport::~SWGPlutoSdrInputReport() { + this->cleanup(); +} + +void +SWGPlutoSdrInputReport::init() { + adc_rate = 0; + m_adc_rate_isSet = false; + rssi = new QString(""); + m_rssi_isSet = false; + gain_db = 0; + m_gain_db_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; +} + +void +SWGPlutoSdrInputReport::cleanup() { + + if(rssi != nullptr) { + delete rssi; + } + + +} + +SWGPlutoSdrInputReport* +SWGPlutoSdrInputReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPlutoSdrInputReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&adc_rate, pJson["adcRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&rssi, pJson["rssi"], "QString", "QString"); + + ::SWGSDRangel::setValue(&gain_db, pJson["gainDB"], "qint32", ""); + + ::SWGSDRangel::setValue(&temperature, pJson["temperature"], "float", ""); + +} + +QString +SWGPlutoSdrInputReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPlutoSdrInputReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_adc_rate_isSet){ + obj->insert("adcRate", QJsonValue(adc_rate)); + } + if(rssi != nullptr && *rssi != QString("")){ + toJsonValue(QString("rssi"), rssi, obj, QString("QString")); + } + if(m_gain_db_isSet){ + obj->insert("gainDB", QJsonValue(gain_db)); + } + if(m_temperature_isSet){ + obj->insert("temperature", QJsonValue(temperature)); + } + + return obj; +} + +qint32 +SWGPlutoSdrInputReport::getAdcRate() { + return adc_rate; +} +void +SWGPlutoSdrInputReport::setAdcRate(qint32 adc_rate) { + this->adc_rate = adc_rate; + this->m_adc_rate_isSet = true; +} + +QString* +SWGPlutoSdrInputReport::getRssi() { + return rssi; +} +void +SWGPlutoSdrInputReport::setRssi(QString* rssi) { + this->rssi = rssi; + this->m_rssi_isSet = true; +} + +qint32 +SWGPlutoSdrInputReport::getGainDb() { + return gain_db; +} +void +SWGPlutoSdrInputReport::setGainDb(qint32 gain_db) { + this->gain_db = gain_db; + this->m_gain_db_isSet = true; +} + +float +SWGPlutoSdrInputReport::getTemperature() { + return temperature; +} +void +SWGPlutoSdrInputReport::setTemperature(float temperature) { + this->temperature = temperature; + this->m_temperature_isSet = true; +} + + +bool +SWGPlutoSdrInputReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_adc_rate_isSet){ isObjectUpdated = true; break;} + if(rssi != nullptr && *rssi != QString("")){ isObjectUpdated = true; break;} + if(m_gain_db_isSet){ isObjectUpdated = true; break;} + if(m_temperature_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h new file mode 100644 index 000000000..69bbbecc5 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h @@ -0,0 +1,77 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPlutoSdrInputReport.h + * + * PlutoSDR + */ + +#ifndef SWGPlutoSdrInputReport_H_ +#define SWGPlutoSdrInputReport_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPlutoSdrInputReport: public SWGObject { +public: + SWGPlutoSdrInputReport(); + SWGPlutoSdrInputReport(QString* json); + virtual ~SWGPlutoSdrInputReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPlutoSdrInputReport* fromJson(QString &jsonString) override; + + qint32 getAdcRate(); + void setAdcRate(qint32 adc_rate); + + QString* getRssi(); + void setRssi(QString* rssi); + + qint32 getGainDb(); + void setGainDb(qint32 gain_db); + + float getTemperature(); + void setTemperature(float temperature); + + + virtual bool isSet() override; + +private: + qint32 adc_rate; + bool m_adc_rate_isSet; + + QString* rssi; + bool m_rssi_isSet; + + qint32 gain_db; + bool m_gain_db_isSet; + + float temperature; + bool m_temperature_isSet; + +}; + +} + +#endif /* SWGPlutoSdrInputReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp new file mode 100644 index 000000000..1dbc566c0 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp @@ -0,0 +1,465 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPlutoSdrInputSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPlutoSdrInputSettings::SWGPlutoSdrInputSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPlutoSdrInputSettings::SWGPlutoSdrInputSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + lpf_fir_enable = 0; + m_lpf_fir_enable_isSet = false; + lpf_firbw = 0; + m_lpf_firbw_isSet = false; + lpf_fi_rlog2_decim = 0; + m_lpf_fi_rlog2_decim_isSet = false; + lpf_fir_gain = 0; + m_lpf_fir_gain_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + lpf_bw = 0; + m_lpf_bw_isSet = false; + gain = 0; + m_gain_isSet = false; + antenna_path = 0; + m_antenna_path_isSet = false; + gain_mode = 0; + m_gain_mode_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; +} + +SWGPlutoSdrInputSettings::~SWGPlutoSdrInputSettings() { + this->cleanup(); +} + +void +SWGPlutoSdrInputSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + lpf_fir_enable = 0; + m_lpf_fir_enable_isSet = false; + lpf_firbw = 0; + m_lpf_firbw_isSet = false; + lpf_fi_rlog2_decim = 0; + m_lpf_fi_rlog2_decim_isSet = false; + lpf_fir_gain = 0; + m_lpf_fir_gain_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + lpf_bw = 0; + m_lpf_bw_isSet = false; + gain = 0; + m_gain_isSet = false; + antenna_path = 0; + m_antenna_path_isSet = false; + gain_mode = 0; + m_gain_mode_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; +} + +void +SWGPlutoSdrInputSettings::cleanup() { + + + + + + + + + + + + + + + + + + if(file_record_name != nullptr) { + delete file_record_name; + } +} + +SWGPlutoSdrInputSettings* +SWGPlutoSdrInputSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPlutoSdrInputSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_fir_enable, pJson["lpfFIREnable"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_firbw, pJson["lpfFIRBW"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_fi_rlog2_decim, pJson["lpfFIRlog2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_fir_gain, pJson["lpfFIRGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&fc_pos, pJson["fcPos"], "qint32", ""); + + ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); + + ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_bw, pJson["lpfBW"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain, pJson["gain"], "qint32", ""); + + ::SWGSDRangel::setValue(&antenna_path, pJson["antennaPath"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain_mode, pJson["gainMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + +} + +QString +SWGPlutoSdrInputSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPlutoSdrInputSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_dev_sample_rate_isSet){ + obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); + } + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); + } + if(m_lpf_fir_enable_isSet){ + obj->insert("lpfFIREnable", QJsonValue(lpf_fir_enable)); + } + if(m_lpf_firbw_isSet){ + obj->insert("lpfFIRBW", QJsonValue(lpf_firbw)); + } + if(m_lpf_fi_rlog2_decim_isSet){ + obj->insert("lpfFIRlog2Decim", QJsonValue(lpf_fi_rlog2_decim)); + } + if(m_lpf_fir_gain_isSet){ + obj->insert("lpfFIRGain", QJsonValue(lpf_fir_gain)); + } + if(m_fc_pos_isSet){ + obj->insert("fcPos", QJsonValue(fc_pos)); + } + if(m_dc_block_isSet){ + obj->insert("dcBlock", QJsonValue(dc_block)); + } + if(m_iq_correction_isSet){ + obj->insert("iqCorrection", QJsonValue(iq_correction)); + } + if(m_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_lpf_bw_isSet){ + obj->insert("lpfBW", QJsonValue(lpf_bw)); + } + if(m_gain_isSet){ + obj->insert("gain", QJsonValue(gain)); + } + if(m_antenna_path_isSet){ + obj->insert("antennaPath", QJsonValue(antenna_path)); + } + if(m_gain_mode_isSet){ + obj->insert("gainMode", QJsonValue(gain_mode)); + } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGPlutoSdrInputSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGPlutoSdrInputSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getDevSampleRate() { + return dev_sample_rate; +} +void +SWGPlutoSdrInputSettings::setDevSampleRate(qint32 dev_sample_rate) { + this->dev_sample_rate = dev_sample_rate; + this->m_dev_sample_rate_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getLOppmTenths() { + return l_oppm_tenths; +} +void +SWGPlutoSdrInputSettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getLpfFirEnable() { + return lpf_fir_enable; +} +void +SWGPlutoSdrInputSettings::setLpfFirEnable(qint32 lpf_fir_enable) { + this->lpf_fir_enable = lpf_fir_enable; + this->m_lpf_fir_enable_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getLpfFirbw() { + return lpf_firbw; +} +void +SWGPlutoSdrInputSettings::setLpfFirbw(qint32 lpf_firbw) { + this->lpf_firbw = lpf_firbw; + this->m_lpf_firbw_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getLpfFiRlog2Decim() { + return lpf_fi_rlog2_decim; +} +void +SWGPlutoSdrInputSettings::setLpfFiRlog2Decim(qint32 lpf_fi_rlog2_decim) { + this->lpf_fi_rlog2_decim = lpf_fi_rlog2_decim; + this->m_lpf_fi_rlog2_decim_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getLpfFirGain() { + return lpf_fir_gain; +} +void +SWGPlutoSdrInputSettings::setLpfFirGain(qint32 lpf_fir_gain) { + this->lpf_fir_gain = lpf_fir_gain; + this->m_lpf_fir_gain_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getFcPos() { + return fc_pos; +} +void +SWGPlutoSdrInputSettings::setFcPos(qint32 fc_pos) { + this->fc_pos = fc_pos; + this->m_fc_pos_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getDcBlock() { + return dc_block; +} +void +SWGPlutoSdrInputSettings::setDcBlock(qint32 dc_block) { + this->dc_block = dc_block; + this->m_dc_block_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getIqCorrection() { + return iq_correction; +} +void +SWGPlutoSdrInputSettings::setIqCorrection(qint32 iq_correction) { + this->iq_correction = iq_correction; + this->m_iq_correction_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getLog2Decim() { + return log2_decim; +} +void +SWGPlutoSdrInputSettings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getLpfBw() { + return lpf_bw; +} +void +SWGPlutoSdrInputSettings::setLpfBw(qint32 lpf_bw) { + this->lpf_bw = lpf_bw; + this->m_lpf_bw_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getGain() { + return gain; +} +void +SWGPlutoSdrInputSettings::setGain(qint32 gain) { + this->gain = gain; + this->m_gain_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getAntennaPath() { + return antenna_path; +} +void +SWGPlutoSdrInputSettings::setAntennaPath(qint32 antenna_path) { + this->antenna_path = antenna_path; + this->m_antenna_path_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getGainMode() { + return gain_mode; +} +void +SWGPlutoSdrInputSettings::setGainMode(qint32 gain_mode) { + this->gain_mode = gain_mode; + this->m_gain_mode_isSet = true; +} + +qint32 +SWGPlutoSdrInputSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGPlutoSdrInputSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGPlutoSdrInputSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGPlutoSdrInputSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + +QString* +SWGPlutoSdrInputSettings::getFileRecordName() { + return file_record_name; +} +void +SWGPlutoSdrInputSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + + +bool +SWGPlutoSdrInputSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} + if(m_lpf_fir_enable_isSet){ isObjectUpdated = true; break;} + if(m_lpf_firbw_isSet){ isObjectUpdated = true; break;} + if(m_lpf_fi_rlog2_decim_isSet){ isObjectUpdated = true; break;} + if(m_lpf_fir_gain_isSet){ isObjectUpdated = true; break;} + if(m_fc_pos_isSet){ isObjectUpdated = true; break;} + if(m_dc_block_isSet){ isObjectUpdated = true; break;} + if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + if(m_log2_decim_isSet){ isObjectUpdated = true; break;} + if(m_lpf_bw_isSet){ isObjectUpdated = true; break;} + if(m_gain_isSet){ isObjectUpdated = true; break;} + if(m_antenna_path_isSet){ isObjectUpdated = true; break;} + if(m_gain_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h new file mode 100644 index 000000000..696d9c630 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h @@ -0,0 +1,161 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPlutoSdrInputSettings.h + * + * PlutoSDR + */ + +#ifndef SWGPlutoSdrInputSettings_H_ +#define SWGPlutoSdrInputSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPlutoSdrInputSettings: public SWGObject { +public: + SWGPlutoSdrInputSettings(); + SWGPlutoSdrInputSettings(QString* json); + virtual ~SWGPlutoSdrInputSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPlutoSdrInputSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getDevSampleRate(); + void setDevSampleRate(qint32 dev_sample_rate); + + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); + + qint32 getLpfFirEnable(); + void setLpfFirEnable(qint32 lpf_fir_enable); + + qint32 getLpfFirbw(); + void setLpfFirbw(qint32 lpf_firbw); + + qint32 getLpfFiRlog2Decim(); + void setLpfFiRlog2Decim(qint32 lpf_fi_rlog2_decim); + + qint32 getLpfFirGain(); + void setLpfFirGain(qint32 lpf_fir_gain); + + qint32 getFcPos(); + void setFcPos(qint32 fc_pos); + + qint32 getDcBlock(); + void setDcBlock(qint32 dc_block); + + qint32 getIqCorrection(); + void setIqCorrection(qint32 iq_correction); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + qint32 getLpfBw(); + void setLpfBw(qint32 lpf_bw); + + qint32 getGain(); + void setGain(qint32 gain); + + qint32 getAntennaPath(); + void setAntennaPath(qint32 antenna_path); + + qint32 getGainMode(); + void setGainMode(qint32 gain_mode); + + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 dev_sample_rate; + bool m_dev_sample_rate_isSet; + + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; + + qint32 lpf_fir_enable; + bool m_lpf_fir_enable_isSet; + + qint32 lpf_firbw; + bool m_lpf_firbw_isSet; + + qint32 lpf_fi_rlog2_decim; + bool m_lpf_fi_rlog2_decim_isSet; + + qint32 lpf_fir_gain; + bool m_lpf_fir_gain_isSet; + + qint32 fc_pos; + bool m_fc_pos_isSet; + + qint32 dc_block; + bool m_dc_block_isSet; + + qint32 iq_correction; + bool m_iq_correction_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + qint32 lpf_bw; + bool m_lpf_bw_isSet; + + qint32 gain; + bool m_gain_isSet; + + qint32 antenna_path; + bool m_antenna_path_isSet; + + qint32 gain_mode; + bool m_gain_mode_isSet; + + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + +}; + +} + +#endif /* SWGPlutoSdrInputSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp new file mode 100644 index 000000000..eedeb7944 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp @@ -0,0 +1,150 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPlutoSdrOutputReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPlutoSdrOutputReport::SWGPlutoSdrOutputReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPlutoSdrOutputReport::SWGPlutoSdrOutputReport() { + dac_rate = 0; + m_dac_rate_isSet = false; + rssi = nullptr; + m_rssi_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; +} + +SWGPlutoSdrOutputReport::~SWGPlutoSdrOutputReport() { + this->cleanup(); +} + +void +SWGPlutoSdrOutputReport::init() { + dac_rate = 0; + m_dac_rate_isSet = false; + rssi = new QString(""); + m_rssi_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; +} + +void +SWGPlutoSdrOutputReport::cleanup() { + + if(rssi != nullptr) { + delete rssi; + } + +} + +SWGPlutoSdrOutputReport* +SWGPlutoSdrOutputReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPlutoSdrOutputReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&dac_rate, pJson["dacRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&rssi, pJson["rssi"], "QString", "QString"); + + ::SWGSDRangel::setValue(&temperature, pJson["temperature"], "float", ""); + +} + +QString +SWGPlutoSdrOutputReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPlutoSdrOutputReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_dac_rate_isSet){ + obj->insert("dacRate", QJsonValue(dac_rate)); + } + if(rssi != nullptr && *rssi != QString("")){ + toJsonValue(QString("rssi"), rssi, obj, QString("QString")); + } + if(m_temperature_isSet){ + obj->insert("temperature", QJsonValue(temperature)); + } + + return obj; +} + +qint32 +SWGPlutoSdrOutputReport::getDacRate() { + return dac_rate; +} +void +SWGPlutoSdrOutputReport::setDacRate(qint32 dac_rate) { + this->dac_rate = dac_rate; + this->m_dac_rate_isSet = true; +} + +QString* +SWGPlutoSdrOutputReport::getRssi() { + return rssi; +} +void +SWGPlutoSdrOutputReport::setRssi(QString* rssi) { + this->rssi = rssi; + this->m_rssi_isSet = true; +} + +float +SWGPlutoSdrOutputReport::getTemperature() { + return temperature; +} +void +SWGPlutoSdrOutputReport::setTemperature(float temperature) { + this->temperature = temperature; + this->m_temperature_isSet = true; +} + + +bool +SWGPlutoSdrOutputReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_dac_rate_isSet){ isObjectUpdated = true; break;} + if(rssi != nullptr && *rssi != QString("")){ isObjectUpdated = true; break;} + if(m_temperature_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h new file mode 100644 index 000000000..8d9d11dd3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h @@ -0,0 +1,71 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPlutoSdrOutputReport.h + * + * PlutoSDR + */ + +#ifndef SWGPlutoSdrOutputReport_H_ +#define SWGPlutoSdrOutputReport_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPlutoSdrOutputReport: public SWGObject { +public: + SWGPlutoSdrOutputReport(); + SWGPlutoSdrOutputReport(QString* json); + virtual ~SWGPlutoSdrOutputReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPlutoSdrOutputReport* fromJson(QString &jsonString) override; + + qint32 getDacRate(); + void setDacRate(qint32 dac_rate); + + QString* getRssi(); + void setRssi(QString* rssi); + + float getTemperature(); + void setTemperature(float temperature); + + + virtual bool isSet() override; + +private: + qint32 dac_rate; + bool m_dac_rate_isSet; + + QString* rssi; + bool m_rssi_isSet; + + float temperature; + bool m_temperature_isSet; + +}; + +} + +#endif /* SWGPlutoSdrOutputReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp new file mode 100644 index 000000000..d901b5a54 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp @@ -0,0 +1,295 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPlutoSdrOutputSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPlutoSdrOutputSettings::SWGPlutoSdrOutputSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPlutoSdrOutputSettings::SWGPlutoSdrOutputSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + log2_hard_interp = 0; + m_log2_hard_interp_isSet = false; + log2_soft_interp = 0; + m_log2_soft_interp_isSet = false; + lpf_bw = 0; + m_lpf_bw_isSet = false; + lpf_fir_enable = 0; + m_lpf_fir_enable_isSet = false; + lpf_firbw = 0; + m_lpf_firbw_isSet = false; + gain = 0; + m_gain_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; +} + +SWGPlutoSdrOutputSettings::~SWGPlutoSdrOutputSettings() { + this->cleanup(); +} + +void +SWGPlutoSdrOutputSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + log2_hard_interp = 0; + m_log2_hard_interp_isSet = false; + log2_soft_interp = 0; + m_log2_soft_interp_isSet = false; + lpf_bw = 0; + m_lpf_bw_isSet = false; + lpf_fir_enable = 0; + m_lpf_fir_enable_isSet = false; + lpf_firbw = 0; + m_lpf_firbw_isSet = false; + gain = 0; + m_gain_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; +} + +void +SWGPlutoSdrOutputSettings::cleanup() { + + + + + + + + + + +} + +SWGPlutoSdrOutputSettings* +SWGPlutoSdrOutputSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPlutoSdrOutputSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_hard_interp, pJson["log2HardInterp"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_soft_interp, pJson["log2SoftInterp"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_bw, pJson["lpfBW"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_fir_enable, pJson["lpfFIREnable"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_firbw, pJson["lpfFIRBW"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain, pJson["gain"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + +} + +QString +SWGPlutoSdrOutputSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPlutoSdrOutputSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_dev_sample_rate_isSet){ + obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); + } + if(m_log2_hard_interp_isSet){ + obj->insert("log2HardInterp", QJsonValue(log2_hard_interp)); + } + if(m_log2_soft_interp_isSet){ + obj->insert("log2SoftInterp", QJsonValue(log2_soft_interp)); + } + if(m_lpf_bw_isSet){ + obj->insert("lpfBW", QJsonValue(lpf_bw)); + } + if(m_lpf_fir_enable_isSet){ + obj->insert("lpfFIREnable", QJsonValue(lpf_fir_enable)); + } + if(m_lpf_firbw_isSet){ + obj->insert("lpfFIRBW", QJsonValue(lpf_firbw)); + } + if(m_gain_isSet){ + obj->insert("gain", QJsonValue(gain)); + } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } + + return obj; +} + +qint64 +SWGPlutoSdrOutputSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGPlutoSdrOutputSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getDevSampleRate() { + return dev_sample_rate; +} +void +SWGPlutoSdrOutputSettings::setDevSampleRate(qint32 dev_sample_rate) { + this->dev_sample_rate = dev_sample_rate; + this->m_dev_sample_rate_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getLog2HardInterp() { + return log2_hard_interp; +} +void +SWGPlutoSdrOutputSettings::setLog2HardInterp(qint32 log2_hard_interp) { + this->log2_hard_interp = log2_hard_interp; + this->m_log2_hard_interp_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getLog2SoftInterp() { + return log2_soft_interp; +} +void +SWGPlutoSdrOutputSettings::setLog2SoftInterp(qint32 log2_soft_interp) { + this->log2_soft_interp = log2_soft_interp; + this->m_log2_soft_interp_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getLpfBw() { + return lpf_bw; +} +void +SWGPlutoSdrOutputSettings::setLpfBw(qint32 lpf_bw) { + this->lpf_bw = lpf_bw; + this->m_lpf_bw_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getLpfFirEnable() { + return lpf_fir_enable; +} +void +SWGPlutoSdrOutputSettings::setLpfFirEnable(qint32 lpf_fir_enable) { + this->lpf_fir_enable = lpf_fir_enable; + this->m_lpf_fir_enable_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getLpfFirbw() { + return lpf_firbw; +} +void +SWGPlutoSdrOutputSettings::setLpfFirbw(qint32 lpf_firbw) { + this->lpf_firbw = lpf_firbw; + this->m_lpf_firbw_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getGain() { + return gain; +} +void +SWGPlutoSdrOutputSettings::setGain(qint32 gain) { + this->gain = gain; + this->m_gain_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGPlutoSdrOutputSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGPlutoSdrOutputSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGPlutoSdrOutputSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + + +bool +SWGPlutoSdrOutputSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_log2_hard_interp_isSet){ isObjectUpdated = true; break;} + if(m_log2_soft_interp_isSet){ isObjectUpdated = true; break;} + if(m_lpf_bw_isSet){ isObjectUpdated = true; break;} + if(m_lpf_fir_enable_isSet){ isObjectUpdated = true; break;} + if(m_lpf_firbw_isSet){ isObjectUpdated = true; break;} + if(m_gain_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h new file mode 100644 index 000000000..e21dbe1cb --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h @@ -0,0 +1,112 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPlutoSdrOutputSettings.h + * + * PlutoSDR + */ + +#ifndef SWGPlutoSdrOutputSettings_H_ +#define SWGPlutoSdrOutputSettings_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPlutoSdrOutputSettings: public SWGObject { +public: + SWGPlutoSdrOutputSettings(); + SWGPlutoSdrOutputSettings(QString* json); + virtual ~SWGPlutoSdrOutputSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPlutoSdrOutputSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getDevSampleRate(); + void setDevSampleRate(qint32 dev_sample_rate); + + qint32 getLog2HardInterp(); + void setLog2HardInterp(qint32 log2_hard_interp); + + qint32 getLog2SoftInterp(); + void setLog2SoftInterp(qint32 log2_soft_interp); + + qint32 getLpfBw(); + void setLpfBw(qint32 lpf_bw); + + qint32 getLpfFirEnable(); + void setLpfFirEnable(qint32 lpf_fir_enable); + + qint32 getLpfFirbw(); + void setLpfFirbw(qint32 lpf_firbw); + + qint32 getGain(); + void setGain(qint32 gain); + + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 dev_sample_rate; + bool m_dev_sample_rate_isSet; + + qint32 log2_hard_interp; + bool m_log2_hard_interp_isSet; + + qint32 log2_soft_interp; + bool m_log2_soft_interp_isSet; + + qint32 lpf_bw; + bool m_lpf_bw_isSet; + + qint32 lpf_fir_enable; + bool m_lpf_fir_enable_isSet; + + qint32 lpf_firbw; + bool m_lpf_firbw_isSet; + + qint32 gain; + bool m_gain_isSet; + + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + +}; + +} + +#endif /* SWGPlutoSdrOutputSettings_H_ */ diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 799cfec20..238617a6f 100644 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -97,6 +97,20 @@ def setupDevice(deviceset_url, options): if settings is None: exit(-1) + # calculate RF analog and FIR optimal bandpass filters bandwidths + lpFIRBW = options.sample_rate*1000 / (1< Date: Sat, 26 May 2018 20:33:02 +0200 Subject: [PATCH 470/956] PlutoSDR output: implemeted WEB API --- .../plutosdroutput/plutosdroutput.cpp | 127 ++++++++++++ .../plutosdroutput/plutosdroutput.h | 16 ++ .../plutosdrinput/plutosdrinput.cpp | 17 ++ sdrbase/resources/webapi/doc/html2/index.html | 35 +++- .../webapi/doc/swagger/include/PlutoSdr.yaml | 24 ++- sdrbase/webapi/webapirequestmapper.cpp | 15 ++ .../api/swagger/include/PlutoSdr.yaml | 24 ++- swagger/sdrangel/code/html2/index.html | 35 +++- .../qt5/client/SWGPlutoSdrOutputSettings.cpp | 195 ++++++++++++------ .../qt5/client/SWGPlutoSdrOutputSettings.h | 58 ++++-- swagger/sdrangel/examples/rx_test.py | 0 swagger/sdrangel/examples/tx_test.py | 22 +- 12 files changed, 445 insertions(+), 123 deletions(-) mode change 100644 => 100755 swagger/sdrangel/examples/rx_test.py mode change 100644 => 100755 swagger/sdrangel/examples/tx_test.py diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.cpp b/plugins/samplesink/plutosdroutput/plutosdroutput.cpp index eb2da3694..3c81a79a2 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.cpp @@ -18,6 +18,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGPlutoSdrOutputReport.h" #include "dsp/dspcommands.h" #include "dsp/dspengine.h" @@ -312,6 +314,22 @@ bool PlutoSDROutput::applySettings(const PlutoSDROutputSettings& settings, bool bool ownThreadWasRunning = false; bool suspendAllOtherThreads = false; // All others means Rx in fact DevicePlutoSDRBox *plutoBox = m_deviceShared.m_deviceParams->getBox(); + QLocale loc; + + qDebug().noquote() << "PlutoSDROutput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " m_devSampleRate: " << loc.toString(m_settings.m_devSampleRate) << "S/s" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_lpfFIREnable: " << m_settings.m_lpfFIREnable + << " m_lpfFIRBW: " << loc.toString(m_settings.m_lpfFIRBW) + << " m_lpfFIRlog2Interp: " << m_settings.m_lpfFIRlog2Interp + << " m_lpfFIRGain: " << m_settings.m_lpfFIRGain + << " m_log2Interp: " << loc.toString(1<init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int PlutoSDROutput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + PlutoSDROutputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getPlutoSdrOutputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getPlutoSdrOutputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getPlutoSdrOutputSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("lpfFIREnable")) { + settings.m_lpfFIREnable = response.getPlutoSdrOutputSettings()->getLpfFirEnable() != 0; + } + if (deviceSettingsKeys.contains("lpfFIRBW")) { + settings.m_lpfFIRBW = response.getPlutoSdrOutputSettings()->getLpfFirbw(); + } + if (deviceSettingsKeys.contains("lpfFIRlog2Interp")) { + settings.m_lpfFIRlog2Interp = response.getPlutoSdrOutputSettings()->getLpfFiRlog2Interp(); + } + if (deviceSettingsKeys.contains("lpfFIRGain")) { + settings.m_lpfFIRGain = response.getPlutoSdrOutputSettings()->getLpfFirGain(); + } + if (deviceSettingsKeys.contains("log2Interp")) { + settings.m_log2Interp = response.getPlutoSdrOutputSettings()->getLog2Interp(); + } + if (deviceSettingsKeys.contains("lpfBW")) { + settings.m_lpfBW = response.getPlutoSdrOutputSettings()->getLpfBw(); + } + if (deviceSettingsKeys.contains("att")) { + settings.m_att = response.getPlutoSdrOutputSettings()->getAtt(); + } + if (deviceSettingsKeys.contains("antennaPath")) { + int antennaPath = response.getPlutoSdrOutputSettings()->getAntennaPath(); + antennaPath = antennaPath < 0 ? 0 : antennaPath >= PlutoSDROutputSettings::RFPATH_END ? PlutoSDROutputSettings::RFPATH_END-1 : antennaPath; + settings.m_antennaPath = (PlutoSDROutputSettings::RFPath) antennaPath; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getPlutoSdrOutputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getPlutoSdrOutputSettings()->getTransverterMode() != 0; + } + + MsgConfigurePlutoSDR *msg = MsgConfigurePlutoSDR::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigurePlutoSDR *msgToGUI = MsgConfigurePlutoSDR::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int PlutoSDROutput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPlutoSdrOutputReport(new SWGSDRangel::SWGPlutoSdrOutputReport()); + response.getPlutoSdrOutputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void PlutoSDROutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PlutoSDROutputSettings& settings) +{ + response.getPlutoSdrOutputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getPlutoSdrOutputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getPlutoSdrOutputSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getPlutoSdrOutputSettings()->setLpfFirEnable(settings.m_lpfFIREnable ? 1 : 0); + response.getPlutoSdrOutputSettings()->setLpfFirbw(settings.m_lpfFIRBW); + response.getPlutoSdrOutputSettings()->setLpfFiRlog2Interp(settings.m_lpfFIRlog2Interp); + response.getPlutoSdrOutputSettings()->setLpfFirGain(settings.m_lpfFIRGain); + response.getPlutoSdrOutputSettings()->setLog2Interp(settings.m_log2Interp); + response.getPlutoSdrOutputSettings()->setLpfBw(settings.m_lpfBW); + response.getPlutoSdrOutputSettings()->setAtt(settings.m_att); + response.getPlutoSdrOutputSettings()->setAntennaPath((int) settings.m_antennaPath); + response.getPlutoSdrOutputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getPlutoSdrOutputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); +} + +void PlutoSDROutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getPlutoSdrOutputReport()->setDacRate(getDACSampleRate()); + std::string rssiStr; + getRSSI(rssiStr); + response.getPlutoSdrOutputReport()->setRssi(new QString(rssiStr.c_str())); + fetchTemperature(); + response.getPlutoSdrOutputReport()->setTemperature(getTemperature()); +} diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.h b/plugins/samplesink/plutosdroutput/plutosdroutput.h index bffb9f276..ac501fd78 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.h +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.h @@ -91,6 +91,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -122,6 +136,8 @@ public: void suspendBuddies(); void resumeBuddies(); bool applySettings(const PlutoSDROutputSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PlutoSDROutputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index 4cfcb7e01..09c2317fc 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -351,6 +351,23 @@ bool PlutoSDRInput::applySettings(const PlutoSDRInputSettings& settings, bool fo bool ownThreadWasRunning = false; bool suspendAllOtherThreads = false; // All others means Tx in fact DevicePlutoSDRBox *plutoBox = m_deviceShared.m_deviceParams->getBox(); + QLocale loc; + + qDebug().noquote() << "PlutoSDRInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " m_devSampleRate: " << loc.toString(m_settings.m_devSampleRate) << "S/s" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_lpfFIREnable: " << m_settings.m_lpfFIREnable + << " m_lpfFIRBW: " << loc.toString(m_settings.m_lpfFIRBW) + << " m_lpfFIRlog2Decim: " << m_settings.m_lpfFIRlog2Decim + << " m_lpfFIRGain: " << m_settings.m_lpfFIRGain + << " m_log2Decim: " << loc.toString(1<
    - Generated 2018-05-26T16:46:01.765+02:00 + Generated 2018-05-26T20:31:05.824+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/PlutoSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/PlutoSdr.yaml index 488d06c05..8f9ecc6eb 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/PlutoSdr.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/PlutoSdr.yaml @@ -55,17 +55,29 @@ PlutoSdrOutputSettings: format: int64 devSampleRate: type: integer - log2HardInterp: + LOppmTenths: type: integer - log2SoftInterp: + lpfFIREnable: + description: Low pass FIR filter enable (1 if enabled else 0) type: integer - lpfBW: + lpfFIRBW: + description: digital lowpass FIR filter bandwidth (Hz) type: integer - lpfFIREnable: + lpfFIRlog2Interp: + description: digital lowpass FIR filter log2 of interpolation factor (0..2) type: integer - lpfFIRBW: + lpfFIRGain: + description: digital lowpass FIR filter gain (dB) type: integer - gain: + log2Interp: + type: integer + lpfBW: + description: Analog lowpass filter bandwidth (Hz) + type: integer + att: + description: Hardware attenuator gain in decibel fourths (negative) + type: integer + antennaPath: type: integer transverterMode: type: integer diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 6a3ca471c..5162fdedc 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1901,6 +1901,21 @@ bool WebAPIRequestMapper::validateDeviceSettings( return false; } } + else if ((*deviceHwType == "PlutoSDR") && (deviceSettings.getTx() != 0)) + { + if (jsonObject.contains("plutoSdrOutputSettings") && jsonObject["plutoSdrOutputSettings"].isObject()) + { + QJsonObject plutoSdrOutputSettingsJsonObject = jsonObject["plutoSdrOutputSettings"].toObject(); + deviceSettingsKeys = plutoSdrOutputSettingsJsonObject.keys(); + deviceSettings.setPlutoSdrOutputSettings(new SWGSDRangel::SWGPlutoSdrOutputSettings()); + deviceSettings.getPlutoSdrOutputSettings()->fromJsonObject(plutoSdrOutputSettingsJsonObject); + return true; + } + else + { + return false; + } + } else if (*deviceHwType == "RTLSDR") { if (jsonObject.contains("rtlSdrSettings") && jsonObject["rtlSdrSettings"].isObject()) diff --git a/swagger/sdrangel/api/swagger/include/PlutoSdr.yaml b/swagger/sdrangel/api/swagger/include/PlutoSdr.yaml index 488d06c05..8f9ecc6eb 100644 --- a/swagger/sdrangel/api/swagger/include/PlutoSdr.yaml +++ b/swagger/sdrangel/api/swagger/include/PlutoSdr.yaml @@ -55,17 +55,29 @@ PlutoSdrOutputSettings: format: int64 devSampleRate: type: integer - log2HardInterp: + LOppmTenths: type: integer - log2SoftInterp: + lpfFIREnable: + description: Low pass FIR filter enable (1 if enabled else 0) type: integer - lpfBW: + lpfFIRBW: + description: digital lowpass FIR filter bandwidth (Hz) type: integer - lpfFIREnable: + lpfFIRlog2Interp: + description: digital lowpass FIR filter log2 of interpolation factor (0..2) type: integer - lpfFIRBW: + lpfFIRGain: + description: digital lowpass FIR filter gain (dB) type: integer - gain: + log2Interp: + type: integer + lpfBW: + description: Analog lowpass filter bandwidth (Hz) + type: integer + att: + description: Hardware attenuator gain in decibel fourths (negative) + type: integer + antennaPath: type: integer transverterMode: type: integer diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 479c597c1..1529f474d 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -2656,22 +2656,37 @@ margin-bottom: 20px; "devSampleRate" : { "type" : "integer" }, - "log2HardInterp" : { - "type" : "integer" - }, - "log2SoftInterp" : { - "type" : "integer" - }, - "lpfBW" : { + "LOppmTenths" : { "type" : "integer" }, "lpfFIREnable" : { - "type" : "integer" + "type" : "integer", + "description" : "Low pass FIR filter enable (1 if enabled else 0)" }, "lpfFIRBW" : { + "type" : "integer", + "description" : "digital lowpass FIR filter bandwidth (Hz)" + }, + "lpfFIRlog2Interp" : { + "type" : "integer", + "description" : "digital lowpass FIR filter log2 of interpolation factor (0..2)" + }, + "lpfFIRGain" : { + "type" : "integer", + "description" : "digital lowpass FIR filter gain (dB)" + }, + "log2Interp" : { "type" : "integer" }, - "gain" : { + "lpfBW" : { + "type" : "integer", + "description" : "Analog lowpass filter bandwidth (Hz)" + }, + "att" : { + "type" : "integer", + "description" : "Hardware attenuator gain in decibel fourths (negative)" + }, + "antennaPath" : { "type" : "integer" }, "transverterMode" : { @@ -22189,7 +22204,7 @@ except ApiException as e:
    - Generated 2018-05-26T16:46:01.765+02:00 + Generated 2018-05-26T20:31:05.824+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp index d901b5a54..b00053e64 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp @@ -32,18 +32,24 @@ SWGPlutoSdrOutputSettings::SWGPlutoSdrOutputSettings() { m_center_frequency_isSet = false; dev_sample_rate = 0; m_dev_sample_rate_isSet = false; - log2_hard_interp = 0; - m_log2_hard_interp_isSet = false; - log2_soft_interp = 0; - m_log2_soft_interp_isSet = false; - lpf_bw = 0; - m_lpf_bw_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; lpf_fir_enable = 0; m_lpf_fir_enable_isSet = false; lpf_firbw = 0; m_lpf_firbw_isSet = false; - gain = 0; - m_gain_isSet = false; + lpf_fi_rlog2_interp = 0; + m_lpf_fi_rlog2_interp_isSet = false; + lpf_fir_gain = 0; + m_lpf_fir_gain_isSet = false; + log2_interp = 0; + m_log2_interp_isSet = false; + lpf_bw = 0; + m_lpf_bw_isSet = false; + att = 0; + m_att_isSet = false; + antenna_path = 0; + m_antenna_path_isSet = false; transverter_mode = 0; m_transverter_mode_isSet = false; transverter_delta_frequency = 0L; @@ -60,18 +66,24 @@ SWGPlutoSdrOutputSettings::init() { m_center_frequency_isSet = false; dev_sample_rate = 0; m_dev_sample_rate_isSet = false; - log2_hard_interp = 0; - m_log2_hard_interp_isSet = false; - log2_soft_interp = 0; - m_log2_soft_interp_isSet = false; - lpf_bw = 0; - m_lpf_bw_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; lpf_fir_enable = 0; m_lpf_fir_enable_isSet = false; lpf_firbw = 0; m_lpf_firbw_isSet = false; - gain = 0; - m_gain_isSet = false; + lpf_fi_rlog2_interp = 0; + m_lpf_fi_rlog2_interp_isSet = false; + lpf_fir_gain = 0; + m_lpf_fir_gain_isSet = false; + log2_interp = 0; + m_log2_interp_isSet = false; + lpf_bw = 0; + m_lpf_bw_isSet = false; + att = 0; + m_att_isSet = false; + antenna_path = 0; + m_antenna_path_isSet = false; transverter_mode = 0; m_transverter_mode_isSet = false; transverter_delta_frequency = 0L; @@ -90,6 +102,9 @@ SWGPlutoSdrOutputSettings::cleanup() { + + + } SWGPlutoSdrOutputSettings* @@ -107,17 +122,23 @@ SWGPlutoSdrOutputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); - ::SWGSDRangel::setValue(&log2_hard_interp, pJson["log2HardInterp"], "qint32", ""); - - ::SWGSDRangel::setValue(&log2_soft_interp, pJson["log2SoftInterp"], "qint32", ""); - - ::SWGSDRangel::setValue(&lpf_bw, pJson["lpfBW"], "qint32", ""); + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); ::SWGSDRangel::setValue(&lpf_fir_enable, pJson["lpfFIREnable"], "qint32", ""); ::SWGSDRangel::setValue(&lpf_firbw, pJson["lpfFIRBW"], "qint32", ""); - ::SWGSDRangel::setValue(&gain, pJson["gain"], "qint32", ""); + ::SWGSDRangel::setValue(&lpf_fi_rlog2_interp, pJson["lpfFIRlog2Interp"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_fir_gain, pJson["lpfFIRGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_interp, pJson["log2Interp"], "qint32", ""); + + ::SWGSDRangel::setValue(&lpf_bw, pJson["lpfBW"], "qint32", ""); + + ::SWGSDRangel::setValue(&att, pJson["att"], "qint32", ""); + + ::SWGSDRangel::setValue(&antenna_path, pJson["antennaPath"], "qint32", ""); ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); @@ -145,14 +166,8 @@ SWGPlutoSdrOutputSettings::asJsonObject() { if(m_dev_sample_rate_isSet){ obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); } - if(m_log2_hard_interp_isSet){ - obj->insert("log2HardInterp", QJsonValue(log2_hard_interp)); - } - if(m_log2_soft_interp_isSet){ - obj->insert("log2SoftInterp", QJsonValue(log2_soft_interp)); - } - if(m_lpf_bw_isSet){ - obj->insert("lpfBW", QJsonValue(lpf_bw)); + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); } if(m_lpf_fir_enable_isSet){ obj->insert("lpfFIREnable", QJsonValue(lpf_fir_enable)); @@ -160,8 +175,23 @@ SWGPlutoSdrOutputSettings::asJsonObject() { if(m_lpf_firbw_isSet){ obj->insert("lpfFIRBW", QJsonValue(lpf_firbw)); } - if(m_gain_isSet){ - obj->insert("gain", QJsonValue(gain)); + if(m_lpf_fi_rlog2_interp_isSet){ + obj->insert("lpfFIRlog2Interp", QJsonValue(lpf_fi_rlog2_interp)); + } + if(m_lpf_fir_gain_isSet){ + obj->insert("lpfFIRGain", QJsonValue(lpf_fir_gain)); + } + if(m_log2_interp_isSet){ + obj->insert("log2Interp", QJsonValue(log2_interp)); + } + if(m_lpf_bw_isSet){ + obj->insert("lpfBW", QJsonValue(lpf_bw)); + } + if(m_att_isSet){ + obj->insert("att", QJsonValue(att)); + } + if(m_antenna_path_isSet){ + obj->insert("antennaPath", QJsonValue(antenna_path)); } if(m_transverter_mode_isSet){ obj->insert("transverterMode", QJsonValue(transverter_mode)); @@ -194,33 +224,13 @@ SWGPlutoSdrOutputSettings::setDevSampleRate(qint32 dev_sample_rate) { } qint32 -SWGPlutoSdrOutputSettings::getLog2HardInterp() { - return log2_hard_interp; +SWGPlutoSdrOutputSettings::getLOppmTenths() { + return l_oppm_tenths; } void -SWGPlutoSdrOutputSettings::setLog2HardInterp(qint32 log2_hard_interp) { - this->log2_hard_interp = log2_hard_interp; - this->m_log2_hard_interp_isSet = true; -} - -qint32 -SWGPlutoSdrOutputSettings::getLog2SoftInterp() { - return log2_soft_interp; -} -void -SWGPlutoSdrOutputSettings::setLog2SoftInterp(qint32 log2_soft_interp) { - this->log2_soft_interp = log2_soft_interp; - this->m_log2_soft_interp_isSet = true; -} - -qint32 -SWGPlutoSdrOutputSettings::getLpfBw() { - return lpf_bw; -} -void -SWGPlutoSdrOutputSettings::setLpfBw(qint32 lpf_bw) { - this->lpf_bw = lpf_bw; - this->m_lpf_bw_isSet = true; +SWGPlutoSdrOutputSettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; } qint32 @@ -244,13 +254,63 @@ SWGPlutoSdrOutputSettings::setLpfFirbw(qint32 lpf_firbw) { } qint32 -SWGPlutoSdrOutputSettings::getGain() { - return gain; +SWGPlutoSdrOutputSettings::getLpfFiRlog2Interp() { + return lpf_fi_rlog2_interp; } void -SWGPlutoSdrOutputSettings::setGain(qint32 gain) { - this->gain = gain; - this->m_gain_isSet = true; +SWGPlutoSdrOutputSettings::setLpfFiRlog2Interp(qint32 lpf_fi_rlog2_interp) { + this->lpf_fi_rlog2_interp = lpf_fi_rlog2_interp; + this->m_lpf_fi_rlog2_interp_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getLpfFirGain() { + return lpf_fir_gain; +} +void +SWGPlutoSdrOutputSettings::setLpfFirGain(qint32 lpf_fir_gain) { + this->lpf_fir_gain = lpf_fir_gain; + this->m_lpf_fir_gain_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getLog2Interp() { + return log2_interp; +} +void +SWGPlutoSdrOutputSettings::setLog2Interp(qint32 log2_interp) { + this->log2_interp = log2_interp; + this->m_log2_interp_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getLpfBw() { + return lpf_bw; +} +void +SWGPlutoSdrOutputSettings::setLpfBw(qint32 lpf_bw) { + this->lpf_bw = lpf_bw; + this->m_lpf_bw_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getAtt() { + return att; +} +void +SWGPlutoSdrOutputSettings::setAtt(qint32 att) { + this->att = att; + this->m_att_isSet = true; +} + +qint32 +SWGPlutoSdrOutputSettings::getAntennaPath() { + return antenna_path; +} +void +SWGPlutoSdrOutputSettings::setAntennaPath(qint32 antenna_path) { + this->antenna_path = antenna_path; + this->m_antenna_path_isSet = true; } qint32 @@ -280,12 +340,15 @@ SWGPlutoSdrOutputSettings::isSet(){ do{ if(m_center_frequency_isSet){ isObjectUpdated = true; break;} if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} - if(m_log2_hard_interp_isSet){ isObjectUpdated = true; break;} - if(m_log2_soft_interp_isSet){ isObjectUpdated = true; break;} - if(m_lpf_bw_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} if(m_lpf_fir_enable_isSet){ isObjectUpdated = true; break;} if(m_lpf_firbw_isSet){ isObjectUpdated = true; break;} - if(m_gain_isSet){ isObjectUpdated = true; break;} + if(m_lpf_fi_rlog2_interp_isSet){ isObjectUpdated = true; break;} + if(m_lpf_fir_gain_isSet){ isObjectUpdated = true; break;} + if(m_log2_interp_isSet){ isObjectUpdated = true; break;} + if(m_lpf_bw_isSet){ isObjectUpdated = true; break;} + if(m_att_isSet){ isObjectUpdated = true; break;} + if(m_antenna_path_isSet){ isObjectUpdated = true; break;} if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h index e21dbe1cb..5f3dfc841 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h @@ -47,14 +47,8 @@ public: qint32 getDevSampleRate(); void setDevSampleRate(qint32 dev_sample_rate); - qint32 getLog2HardInterp(); - void setLog2HardInterp(qint32 log2_hard_interp); - - qint32 getLog2SoftInterp(); - void setLog2SoftInterp(qint32 log2_soft_interp); - - qint32 getLpfBw(); - void setLpfBw(qint32 lpf_bw); + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); qint32 getLpfFirEnable(); void setLpfFirEnable(qint32 lpf_fir_enable); @@ -62,8 +56,23 @@ public: qint32 getLpfFirbw(); void setLpfFirbw(qint32 lpf_firbw); - qint32 getGain(); - void setGain(qint32 gain); + qint32 getLpfFiRlog2Interp(); + void setLpfFiRlog2Interp(qint32 lpf_fi_rlog2_interp); + + qint32 getLpfFirGain(); + void setLpfFirGain(qint32 lpf_fir_gain); + + qint32 getLog2Interp(); + void setLog2Interp(qint32 log2_interp); + + qint32 getLpfBw(); + void setLpfBw(qint32 lpf_bw); + + qint32 getAtt(); + void setAtt(qint32 att); + + qint32 getAntennaPath(); + void setAntennaPath(qint32 antenna_path); qint32 getTransverterMode(); void setTransverterMode(qint32 transverter_mode); @@ -81,14 +90,8 @@ private: qint32 dev_sample_rate; bool m_dev_sample_rate_isSet; - qint32 log2_hard_interp; - bool m_log2_hard_interp_isSet; - - qint32 log2_soft_interp; - bool m_log2_soft_interp_isSet; - - qint32 lpf_bw; - bool m_lpf_bw_isSet; + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; qint32 lpf_fir_enable; bool m_lpf_fir_enable_isSet; @@ -96,8 +99,23 @@ private: qint32 lpf_firbw; bool m_lpf_firbw_isSet; - qint32 gain; - bool m_gain_isSet; + qint32 lpf_fi_rlog2_interp; + bool m_lpf_fi_rlog2_interp_isSet; + + qint32 lpf_fir_gain; + bool m_lpf_fir_gain_isSet; + + qint32 log2_interp; + bool m_log2_interp_isSet; + + qint32 lpf_bw; + bool m_lpf_bw_isSet; + + qint32 att; + bool m_att_isSet; + + qint32 antenna_path; + bool m_antenna_path_isSet; qint32 transverter_mode; bool m_transverter_mode_isSet; diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py old mode 100644 new mode 100755 diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py old mode 100644 new mode 100755 index e1720ade6..cf15d8dc5 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -112,6 +112,10 @@ def setupDevice(options): print(options.sample_rate) + # calculate RF analog and FIR optimal bandpass filters bandwidths + lpFIRBW = options.sample_rate / (1< Date: Sat, 26 May 2018 22:16:59 +0200 Subject: [PATCH 471/956] LimeSDR: implemeted WEB API for reporting --- .../limesdroutput/limesdroutput.cpp | 44 +++ .../samplesink/limesdroutput/limesdroutput.h | 5 + .../limesdrinput/limesdrinput.cpp | 44 +++ .../samplesource/limesdrinput/limesdrinput.h | 5 + sdrbase/resources/webapi/doc/html2/index.html | 90 +++++- .../webapi/doc/swagger/include/LimeSdr.yaml | 61 ++++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + .../sdrangel/api/swagger/include/LimeSdr.yaml | 61 ++++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 90 +++++- .../code/qt5/client/SWGDeviceReport.cpp | 46 +++ .../code/qt5/client/SWGDeviceReport.h | 14 + .../code/qt5/client/SWGLimeSdrInputReport.cpp | 295 ++++++++++++++++++ .../code/qt5/client/SWGLimeSdrInputReport.h | 112 +++++++ .../qt5/client/SWGLimeSdrOutputReport.cpp | 295 ++++++++++++++++++ .../code/qt5/client/SWGLimeSdrOutputReport.h | 112 +++++++ .../code/qt5/client/SWGModelFactory.h | 8 + 17 files changed, 1288 insertions(+), 2 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp index 4e2c7f5dc..657864a44 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp @@ -23,6 +23,8 @@ #include "SWGDeviceSettings.h" #include "SWGLimeSdrOutputSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGLimeSdrOutputReport.h" #include "device/devicesourceapi.h" #include "device/devicesinkapi.h" @@ -1172,6 +1174,15 @@ int LimeSDROutput::webapiSettingsPutPatch( return 200; } +int LimeSDROutput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setLimeSdrOutputReport(new SWGSDRangel::SWGLimeSdrOutputReport()); + response.getLimeSdrOutputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} void LimeSDROutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const LimeSDROutputSettings& settings) { response.getLimeSdrOutputSettings()->setAntennaPath((int) settings.m_antennaPath); @@ -1216,3 +1227,36 @@ int LimeSDROutput::webapiRun( return 200; } + +void LimeSDROutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + bool success = false; + double temp = 0.0; + lms_stream_status_t status; + status.active = false; + status.fifoFilledCount = 0; + status.fifoSize = 1; + status.underrun = 0; + status.overrun = 0; + status.droppedPackets = 0; + status.linkRate = 0.0; + status.timestamp = 0; + + success = (m_streamId.handle && (LMS_GetStreamStatus(&m_streamId, &status) == 0)); + + response.getLimeSdrOutputReport()->setSuccess(success ? 1 : 0); + response.getLimeSdrOutputReport()->setStreamActive(status.active ? 1 : 0); + response.getLimeSdrOutputReport()->setFifoSize(status.fifoSize); + response.getLimeSdrOutputReport()->setFifoFill(status.fifoFilledCount); + response.getLimeSdrOutputReport()->setUnderrunCount(status.underrun); + response.getLimeSdrOutputReport()->setOverrunCount(status.overrun); + response.getLimeSdrOutputReport()->setDroppedPacketsCount(status.droppedPackets); + response.getLimeSdrOutputReport()->setLinkRate(status.linkRate); + response.getLimeSdrOutputReport()->setHwTimestamp(status.timestamp); + + if (m_deviceShared.m_deviceParams->getDevice()) { + LMS_GetChipTemperature(m_deviceShared.m_deviceParams->getDevice(), 0, &temp); + } + + response.getLimeSdrOutputReport()->setTemperature(temp); +} diff --git a/plugins/samplesink/limesdroutput/limesdroutput.h b/plugins/samplesink/limesdroutput/limesdroutput.h index e51d58675..e4979c5ca 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.h +++ b/plugins/samplesink/limesdroutput/limesdroutput.h @@ -207,6 +207,10 @@ public: SWGSDRangel::SWGDeviceSettings& response, // query + response QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -244,6 +248,7 @@ private: void resumeTxBuddies(); bool applySettings(const LimeSDROutputSettings& settings, bool force = false, bool forceNCOFrequency = false); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const LimeSDROutputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif /* PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUT_H_ */ diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index ff7877da1..8a7aa88fe 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -23,6 +23,8 @@ #include "SWGDeviceSettings.h" #include "SWGLimeSdrInputSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGLimeSdrInputReport.h" #include "device/devicesourceapi.h" #include "device/devicesinkapi.h" @@ -1385,6 +1387,16 @@ void LimeSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& re } } +int LimeSDRInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setLimeSdrInputReport(new SWGSDRangel::SWGLimeSdrInputReport()); + response.getLimeSdrInputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + int LimeSDRInput::webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage __attribute__((unused))) @@ -1411,3 +1423,35 @@ int LimeSDRInput::webapiRun( return 200; } +void LimeSDRInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + bool success = false; + double temp = 0.0; + lms_stream_status_t status; + status.active = false; + status.fifoFilledCount = 0; + status.fifoSize = 1; + status.underrun = 0; + status.overrun = 0; + status.droppedPackets = 0; + status.linkRate = 0.0; + status.timestamp = 0; + + success = (m_streamId.handle && (LMS_GetStreamStatus(&m_streamId, &status) == 0)); + + response.getLimeSdrInputReport()->setSuccess(success ? 1 : 0); + response.getLimeSdrInputReport()->setStreamActive(status.active ? 1 : 0); + response.getLimeSdrInputReport()->setFifoSize(status.fifoSize); + response.getLimeSdrInputReport()->setFifoFill(status.fifoFilledCount); + response.getLimeSdrInputReport()->setUnderrunCount(status.underrun); + response.getLimeSdrInputReport()->setOverrunCount(status.overrun); + response.getLimeSdrInputReport()->setDroppedPacketsCount(status.droppedPackets); + response.getLimeSdrInputReport()->setLinkRate(status.linkRate); + response.getLimeSdrInputReport()->setHwTimestamp(status.timestamp); + + if (m_deviceShared.m_deviceParams->getDevice()) { + LMS_GetChipTemperature(m_deviceShared.m_deviceParams->getDevice(), 0, &temp); + } + + response.getLimeSdrInputReport()->setTemperature(temp); +} diff --git a/plugins/samplesource/limesdrinput/limesdrinput.h b/plugins/samplesource/limesdrinput/limesdrinput.h index 629a5f131..081cc3d3a 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.h +++ b/plugins/samplesource/limesdrinput/limesdrinput.h @@ -228,6 +228,10 @@ public: SWGSDRangel::SWGDeviceSettings& response, // query + response QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -265,6 +269,7 @@ private: void resumeTxBuddies(); bool applySettings(const LimeSDRInputSettings& settings, bool force = false, bool forceNCOFrequency = false); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const LimeSDRInputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif /* PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUT_H_ */ diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 1529f474d..d5445a952 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1721,6 +1721,12 @@ margin-bottom: 20px; "fileSourceReport" : { "$ref" : "#/definitions/FileSourceReport" }, + "limeSdrInputReport" : { + "$ref" : "#/definitions/LimeSdrInputReport" + }, + "limeSdrOutputReport" : { + "$ref" : "#/definitions/LimeSdrOutputReport" + }, "perseusReport" : { "$ref" : "#/definitions/PerseusReport" }, @@ -2174,6 +2180,47 @@ margin-bottom: 20px; } }, "description" : "Summarized information about this SDRangel instance" +}; + defs.LimeSdrInputReport = { + "properties" : { + "success" : { + "type" : "integer", + "description" : "1 if info was successfullt retrieved else 0" + }, + "streamActive" : { + "type" : "integer", + "description" : "1 if active else 0" + }, + "fifoSize" : { + "type" : "integer" + }, + "fifoFill" : { + "type" : "integer" + }, + "underrunCount" : { + "type" : "integer" + }, + "overrunCount" : { + "type" : "integer" + }, + "droppedPacketsCount" : { + "type" : "integer" + }, + "linkRate" : { + "type" : "number", + "format" : "float" + }, + "hwTimestamp" : { + "type" : "integer", + "format" : "uint64", + "description" : "Hardware timestamp" + }, + "temperature" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "LimeSDR" }; defs.LimeSdrInputSettings = { "properties" : { @@ -2247,6 +2294,47 @@ margin-bottom: 20px; } }, "description" : "LimeSDR" +}; + defs.LimeSdrOutputReport = { + "properties" : { + "success" : { + "type" : "integer", + "description" : "1 if info was successfullt retrieved else 0" + }, + "streamActive" : { + "type" : "integer", + "description" : "1 if active else 0" + }, + "fifoSize" : { + "type" : "integer" + }, + "fifoFill" : { + "type" : "integer" + }, + "underrunCount" : { + "type" : "integer" + }, + "overrunCount" : { + "type" : "integer" + }, + "droppedPacketsCount" : { + "type" : "integer" + }, + "linkRate" : { + "type" : "number", + "format" : "float" + }, + "hwTimestamp" : { + "type" : "integer", + "format" : "uint64", + "description" : "Hardware timestamp" + }, + "temperature" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "LimeSDR" }; defs.LimeSdrOutputSettings = { "properties" : { @@ -22204,7 +22292,7 @@ except ApiException as e:
    - Generated 2018-05-26T20:31:05.824+02:00 + Generated 2018-05-26T22:09:48.356+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml index 6751d466c..48225a4a2 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml @@ -83,3 +83,64 @@ LimeSdrOutputSettings: transverterDeltaFrequency: type: integer format: int64 + +LimeSdrInputReport: + description: LimeSDR + properties: + success: + description: 1 if info was successfullt retrieved else 0 + type: integer + streamActive: + description: 1 if active else 0 + type: integer + fifoSize: + type: integer + fifoFill: + type: integer + underrunCount: + type: integer + overrunCount: + type: integer + droppedPacketsCount: + type: integer + linkRate: + type: number + format: float + hwTimestamp: + description: Hardware timestamp + type: integer + format: uint64 + temperature: + type: number + format: float + +LimeSdrOutputReport: + description: LimeSDR + properties: + success: + description: 1 if info was successfullt retrieved else 0 + type: integer + streamActive: + description: 1 if active else 0 + type: integer + fifoSize: + type: integer + fifoFill: + type: integer + underrunCount: + type: integer + overrunCount: + type: integer + droppedPacketsCount: + type: integer + linkRate: + type: number + format: float + hwTimestamp: + description: Hardware timestamp + type: integer + format: uint64 + temperature: + type: number + format: float + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index a63c5e693..e4a21ae40 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1795,6 +1795,10 @@ definitions: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFReport" fileSourceReport: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceReport" + limeSdrInputReport: + $ref: "/doc/swagger/include/LimeSdr.yaml#/LimeSdrInputReport" + limeSdrOutputReport: + $ref: "/doc/swagger/include/LimeSdr.yaml#/LimeSdrOutputReport" perseusReport: $ref: "/doc/swagger/include/Perseus.yaml#/PerseusReport" plutoSdrInputReport: diff --git a/swagger/sdrangel/api/swagger/include/LimeSdr.yaml b/swagger/sdrangel/api/swagger/include/LimeSdr.yaml index 6751d466c..48225a4a2 100644 --- a/swagger/sdrangel/api/swagger/include/LimeSdr.yaml +++ b/swagger/sdrangel/api/swagger/include/LimeSdr.yaml @@ -83,3 +83,64 @@ LimeSdrOutputSettings: transverterDeltaFrequency: type: integer format: int64 + +LimeSdrInputReport: + description: LimeSDR + properties: + success: + description: 1 if info was successfullt retrieved else 0 + type: integer + streamActive: + description: 1 if active else 0 + type: integer + fifoSize: + type: integer + fifoFill: + type: integer + underrunCount: + type: integer + overrunCount: + type: integer + droppedPacketsCount: + type: integer + linkRate: + type: number + format: float + hwTimestamp: + description: Hardware timestamp + type: integer + format: uint64 + temperature: + type: number + format: float + +LimeSdrOutputReport: + description: LimeSDR + properties: + success: + description: 1 if info was successfullt retrieved else 0 + type: integer + streamActive: + description: 1 if active else 0 + type: integer + fifoSize: + type: integer + fifoFill: + type: integer + underrunCount: + type: integer + overrunCount: + type: integer + droppedPacketsCount: + type: integer + linkRate: + type: number + format: float + hwTimestamp: + description: Hardware timestamp + type: integer + format: uint64 + temperature: + type: number + format: float + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 726561834..209a7ba68 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1795,6 +1795,10 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFReport" fileSourceReport: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceReport" + limeSdrInputReport: + $ref: "http://localhost:8081/api/swagger/include/LimeSdr.yaml#/LimeSdrInputReport" + limeSdrOutputReport: + $ref: "http://localhost:8081/api/swagger/include/LimeSdr.yaml#/LimeSdrOutputReport" perseusReport: $ref: "http://localhost:8081/api/swagger/include/Perseus.yaml#/PerseusReport" plutoSdrInputReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 1529f474d..d5445a952 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1721,6 +1721,12 @@ margin-bottom: 20px; "fileSourceReport" : { "$ref" : "#/definitions/FileSourceReport" }, + "limeSdrInputReport" : { + "$ref" : "#/definitions/LimeSdrInputReport" + }, + "limeSdrOutputReport" : { + "$ref" : "#/definitions/LimeSdrOutputReport" + }, "perseusReport" : { "$ref" : "#/definitions/PerseusReport" }, @@ -2174,6 +2180,47 @@ margin-bottom: 20px; } }, "description" : "Summarized information about this SDRangel instance" +}; + defs.LimeSdrInputReport = { + "properties" : { + "success" : { + "type" : "integer", + "description" : "1 if info was successfullt retrieved else 0" + }, + "streamActive" : { + "type" : "integer", + "description" : "1 if active else 0" + }, + "fifoSize" : { + "type" : "integer" + }, + "fifoFill" : { + "type" : "integer" + }, + "underrunCount" : { + "type" : "integer" + }, + "overrunCount" : { + "type" : "integer" + }, + "droppedPacketsCount" : { + "type" : "integer" + }, + "linkRate" : { + "type" : "number", + "format" : "float" + }, + "hwTimestamp" : { + "type" : "integer", + "format" : "uint64", + "description" : "Hardware timestamp" + }, + "temperature" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "LimeSDR" }; defs.LimeSdrInputSettings = { "properties" : { @@ -2247,6 +2294,47 @@ margin-bottom: 20px; } }, "description" : "LimeSDR" +}; + defs.LimeSdrOutputReport = { + "properties" : { + "success" : { + "type" : "integer", + "description" : "1 if info was successfullt retrieved else 0" + }, + "streamActive" : { + "type" : "integer", + "description" : "1 if active else 0" + }, + "fifoSize" : { + "type" : "integer" + }, + "fifoFill" : { + "type" : "integer" + }, + "underrunCount" : { + "type" : "integer" + }, + "overrunCount" : { + "type" : "integer" + }, + "droppedPacketsCount" : { + "type" : "integer" + }, + "linkRate" : { + "type" : "number", + "format" : "float" + }, + "hwTimestamp" : { + "type" : "integer", + "format" : "uint64", + "description" : "Hardware timestamp" + }, + "temperature" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "LimeSDR" }; defs.LimeSdrOutputSettings = { "properties" : { @@ -22204,7 +22292,7 @@ except ApiException as e:
    - Generated 2018-05-26T20:31:05.824+02:00 + Generated 2018-05-26T22:09:48.356+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index ce627f326..019c1fc7f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -38,6 +38,10 @@ SWGDeviceReport::SWGDeviceReport() { m_airspy_hf_report_isSet = false; file_source_report = nullptr; m_file_source_report_isSet = false; + lime_sdr_input_report = nullptr; + m_lime_sdr_input_report_isSet = false; + lime_sdr_output_report = nullptr; + m_lime_sdr_output_report_isSet = false; perseus_report = nullptr; m_perseus_report_isSet = false; pluto_sdr_input_report = nullptr; @@ -64,6 +68,10 @@ SWGDeviceReport::init() { m_airspy_hf_report_isSet = false; file_source_report = new SWGFileSourceReport(); m_file_source_report_isSet = false; + lime_sdr_input_report = new SWGLimeSdrInputReport(); + m_lime_sdr_input_report_isSet = false; + lime_sdr_output_report = new SWGLimeSdrOutputReport(); + m_lime_sdr_output_report_isSet = false; perseus_report = new SWGPerseusReport(); m_perseus_report_isSet = false; pluto_sdr_input_report = new SWGPlutoSdrInputReport(); @@ -89,6 +97,12 @@ SWGDeviceReport::cleanup() { if(file_source_report != nullptr) { delete file_source_report; } + if(lime_sdr_input_report != nullptr) { + delete lime_sdr_input_report; + } + if(lime_sdr_output_report != nullptr) { + delete lime_sdr_output_report; + } if(perseus_report != nullptr) { delete perseus_report; } @@ -124,6 +138,10 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&file_source_report, pJson["fileSourceReport"], "SWGFileSourceReport", "SWGFileSourceReport"); + ::SWGSDRangel::setValue(&lime_sdr_input_report, pJson["limeSdrInputReport"], "SWGLimeSdrInputReport", "SWGLimeSdrInputReport"); + + ::SWGSDRangel::setValue(&lime_sdr_output_report, pJson["limeSdrOutputReport"], "SWGLimeSdrOutputReport", "SWGLimeSdrOutputReport"); + ::SWGSDRangel::setValue(&perseus_report, pJson["perseusReport"], "SWGPerseusReport", "SWGPerseusReport"); ::SWGSDRangel::setValue(&pluto_sdr_input_report, pJson["plutoSdrInputReport"], "SWGPlutoSdrInputReport", "SWGPlutoSdrInputReport"); @@ -163,6 +181,12 @@ SWGDeviceReport::asJsonObject() { if((file_source_report != nullptr) && (file_source_report->isSet())){ toJsonValue(QString("fileSourceReport"), file_source_report, obj, QString("SWGFileSourceReport")); } + if((lime_sdr_input_report != nullptr) && (lime_sdr_input_report->isSet())){ + toJsonValue(QString("limeSdrInputReport"), lime_sdr_input_report, obj, QString("SWGLimeSdrInputReport")); + } + if((lime_sdr_output_report != nullptr) && (lime_sdr_output_report->isSet())){ + toJsonValue(QString("limeSdrOutputReport"), lime_sdr_output_report, obj, QString("SWGLimeSdrOutputReport")); + } if((perseus_report != nullptr) && (perseus_report->isSet())){ toJsonValue(QString("perseusReport"), perseus_report, obj, QString("SWGPerseusReport")); } @@ -229,6 +253,26 @@ SWGDeviceReport::setFileSourceReport(SWGFileSourceReport* file_source_report) { this->m_file_source_report_isSet = true; } +SWGLimeSdrInputReport* +SWGDeviceReport::getLimeSdrInputReport() { + return lime_sdr_input_report; +} +void +SWGDeviceReport::setLimeSdrInputReport(SWGLimeSdrInputReport* lime_sdr_input_report) { + this->lime_sdr_input_report = lime_sdr_input_report; + this->m_lime_sdr_input_report_isSet = true; +} + +SWGLimeSdrOutputReport* +SWGDeviceReport::getLimeSdrOutputReport() { + return lime_sdr_output_report; +} +void +SWGDeviceReport::setLimeSdrOutputReport(SWGLimeSdrOutputReport* lime_sdr_output_report) { + this->lime_sdr_output_report = lime_sdr_output_report; + this->m_lime_sdr_output_report_isSet = true; +} + SWGPerseusReport* SWGDeviceReport::getPerseusReport() { return perseus_report; @@ -279,6 +323,8 @@ SWGDeviceReport::isSet(){ if(airspy_report != nullptr && airspy_report->isSet()){ isObjectUpdated = true; break;} if(airspy_hf_report != nullptr && airspy_hf_report->isSet()){ isObjectUpdated = true; break;} if(file_source_report != nullptr && file_source_report->isSet()){ isObjectUpdated = true; break;} + if(lime_sdr_input_report != nullptr && lime_sdr_input_report->isSet()){ isObjectUpdated = true; break;} + if(lime_sdr_output_report != nullptr && lime_sdr_output_report->isSet()){ isObjectUpdated = true; break;} if(perseus_report != nullptr && perseus_report->isSet()){ isObjectUpdated = true; break;} if(pluto_sdr_input_report != nullptr && pluto_sdr_input_report->isSet()){ isObjectUpdated = true; break;} if(pluto_sdr_output_report != nullptr && pluto_sdr_output_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index c8c2fa654..0b51c016a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -25,6 +25,8 @@ #include "SWGAirspyHFReport.h" #include "SWGAirspyReport.h" #include "SWGFileSourceReport.h" +#include "SWGLimeSdrInputReport.h" +#include "SWGLimeSdrOutputReport.h" #include "SWGPerseusReport.h" #include "SWGPlutoSdrInputReport.h" #include "SWGPlutoSdrOutputReport.h" @@ -64,6 +66,12 @@ public: SWGFileSourceReport* getFileSourceReport(); void setFileSourceReport(SWGFileSourceReport* file_source_report); + SWGLimeSdrInputReport* getLimeSdrInputReport(); + void setLimeSdrInputReport(SWGLimeSdrInputReport* lime_sdr_input_report); + + SWGLimeSdrOutputReport* getLimeSdrOutputReport(); + void setLimeSdrOutputReport(SWGLimeSdrOutputReport* lime_sdr_output_report); + SWGPerseusReport* getPerseusReport(); void setPerseusReport(SWGPerseusReport* perseus_report); @@ -95,6 +103,12 @@ private: SWGFileSourceReport* file_source_report; bool m_file_source_report_isSet; + SWGLimeSdrInputReport* lime_sdr_input_report; + bool m_lime_sdr_input_report_isSet; + + SWGLimeSdrOutputReport* lime_sdr_output_report; + bool m_lime_sdr_output_report_isSet; + SWGPerseusReport* perseus_report; bool m_perseus_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp new file mode 100644 index 000000000..580daef2d --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp @@ -0,0 +1,295 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGLimeSdrInputReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGLimeSdrInputReport::SWGLimeSdrInputReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGLimeSdrInputReport::SWGLimeSdrInputReport() { + success = 0; + m_success_isSet = false; + stream_active = 0; + m_stream_active_isSet = false; + fifo_size = 0; + m_fifo_size_isSet = false; + fifo_fill = 0; + m_fifo_fill_isSet = false; + underrun_count = 0; + m_underrun_count_isSet = false; + overrun_count = 0; + m_overrun_count_isSet = false; + dropped_packets_count = 0; + m_dropped_packets_count_isSet = false; + link_rate = 0.0f; + m_link_rate_isSet = false; + hw_timestamp = 0; + m_hw_timestamp_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; +} + +SWGLimeSdrInputReport::~SWGLimeSdrInputReport() { + this->cleanup(); +} + +void +SWGLimeSdrInputReport::init() { + success = 0; + m_success_isSet = false; + stream_active = 0; + m_stream_active_isSet = false; + fifo_size = 0; + m_fifo_size_isSet = false; + fifo_fill = 0; + m_fifo_fill_isSet = false; + underrun_count = 0; + m_underrun_count_isSet = false; + overrun_count = 0; + m_overrun_count_isSet = false; + dropped_packets_count = 0; + m_dropped_packets_count_isSet = false; + link_rate = 0.0f; + m_link_rate_isSet = false; + hw_timestamp = 0; + m_hw_timestamp_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; +} + +void +SWGLimeSdrInputReport::cleanup() { + + + + + + + + + + +} + +SWGLimeSdrInputReport* +SWGLimeSdrInputReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGLimeSdrInputReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&success, pJson["success"], "qint32", ""); + + ::SWGSDRangel::setValue(&stream_active, pJson["streamActive"], "qint32", ""); + + ::SWGSDRangel::setValue(&fifo_size, pJson["fifoSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&fifo_fill, pJson["fifoFill"], "qint32", ""); + + ::SWGSDRangel::setValue(&underrun_count, pJson["underrunCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&overrun_count, pJson["overrunCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&dropped_packets_count, pJson["droppedPacketsCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&link_rate, pJson["linkRate"], "float", ""); + + ::SWGSDRangel::setValue(&hw_timestamp, pJson["hwTimestamp"], "qint32", ""); + + ::SWGSDRangel::setValue(&temperature, pJson["temperature"], "float", ""); + +} + +QString +SWGLimeSdrInputReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGLimeSdrInputReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_success_isSet){ + obj->insert("success", QJsonValue(success)); + } + if(m_stream_active_isSet){ + obj->insert("streamActive", QJsonValue(stream_active)); + } + if(m_fifo_size_isSet){ + obj->insert("fifoSize", QJsonValue(fifo_size)); + } + if(m_fifo_fill_isSet){ + obj->insert("fifoFill", QJsonValue(fifo_fill)); + } + if(m_underrun_count_isSet){ + obj->insert("underrunCount", QJsonValue(underrun_count)); + } + if(m_overrun_count_isSet){ + obj->insert("overrunCount", QJsonValue(overrun_count)); + } + if(m_dropped_packets_count_isSet){ + obj->insert("droppedPacketsCount", QJsonValue(dropped_packets_count)); + } + if(m_link_rate_isSet){ + obj->insert("linkRate", QJsonValue(link_rate)); + } + if(m_hw_timestamp_isSet){ + obj->insert("hwTimestamp", QJsonValue(hw_timestamp)); + } + if(m_temperature_isSet){ + obj->insert("temperature", QJsonValue(temperature)); + } + + return obj; +} + +qint32 +SWGLimeSdrInputReport::getSuccess() { + return success; +} +void +SWGLimeSdrInputReport::setSuccess(qint32 success) { + this->success = success; + this->m_success_isSet = true; +} + +qint32 +SWGLimeSdrInputReport::getStreamActive() { + return stream_active; +} +void +SWGLimeSdrInputReport::setStreamActive(qint32 stream_active) { + this->stream_active = stream_active; + this->m_stream_active_isSet = true; +} + +qint32 +SWGLimeSdrInputReport::getFifoSize() { + return fifo_size; +} +void +SWGLimeSdrInputReport::setFifoSize(qint32 fifo_size) { + this->fifo_size = fifo_size; + this->m_fifo_size_isSet = true; +} + +qint32 +SWGLimeSdrInputReport::getFifoFill() { + return fifo_fill; +} +void +SWGLimeSdrInputReport::setFifoFill(qint32 fifo_fill) { + this->fifo_fill = fifo_fill; + this->m_fifo_fill_isSet = true; +} + +qint32 +SWGLimeSdrInputReport::getUnderrunCount() { + return underrun_count; +} +void +SWGLimeSdrInputReport::setUnderrunCount(qint32 underrun_count) { + this->underrun_count = underrun_count; + this->m_underrun_count_isSet = true; +} + +qint32 +SWGLimeSdrInputReport::getOverrunCount() { + return overrun_count; +} +void +SWGLimeSdrInputReport::setOverrunCount(qint32 overrun_count) { + this->overrun_count = overrun_count; + this->m_overrun_count_isSet = true; +} + +qint32 +SWGLimeSdrInputReport::getDroppedPacketsCount() { + return dropped_packets_count; +} +void +SWGLimeSdrInputReport::setDroppedPacketsCount(qint32 dropped_packets_count) { + this->dropped_packets_count = dropped_packets_count; + this->m_dropped_packets_count_isSet = true; +} + +float +SWGLimeSdrInputReport::getLinkRate() { + return link_rate; +} +void +SWGLimeSdrInputReport::setLinkRate(float link_rate) { + this->link_rate = link_rate; + this->m_link_rate_isSet = true; +} + +qint32 +SWGLimeSdrInputReport::getHwTimestamp() { + return hw_timestamp; +} +void +SWGLimeSdrInputReport::setHwTimestamp(qint32 hw_timestamp) { + this->hw_timestamp = hw_timestamp; + this->m_hw_timestamp_isSet = true; +} + +float +SWGLimeSdrInputReport::getTemperature() { + return temperature; +} +void +SWGLimeSdrInputReport::setTemperature(float temperature) { + this->temperature = temperature; + this->m_temperature_isSet = true; +} + + +bool +SWGLimeSdrInputReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_success_isSet){ isObjectUpdated = true; break;} + if(m_stream_active_isSet){ isObjectUpdated = true; break;} + if(m_fifo_size_isSet){ isObjectUpdated = true; break;} + if(m_fifo_fill_isSet){ isObjectUpdated = true; break;} + if(m_underrun_count_isSet){ isObjectUpdated = true; break;} + if(m_overrun_count_isSet){ isObjectUpdated = true; break;} + if(m_dropped_packets_count_isSet){ isObjectUpdated = true; break;} + if(m_link_rate_isSet){ isObjectUpdated = true; break;} + if(m_hw_timestamp_isSet){ isObjectUpdated = true; break;} + if(m_temperature_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h new file mode 100644 index 000000000..3003f831d --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h @@ -0,0 +1,112 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGLimeSdrInputReport.h + * + * LimeSDR + */ + +#ifndef SWGLimeSdrInputReport_H_ +#define SWGLimeSdrInputReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGLimeSdrInputReport: public SWGObject { +public: + SWGLimeSdrInputReport(); + SWGLimeSdrInputReport(QString* json); + virtual ~SWGLimeSdrInputReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGLimeSdrInputReport* fromJson(QString &jsonString) override; + + qint32 getSuccess(); + void setSuccess(qint32 success); + + qint32 getStreamActive(); + void setStreamActive(qint32 stream_active); + + qint32 getFifoSize(); + void setFifoSize(qint32 fifo_size); + + qint32 getFifoFill(); + void setFifoFill(qint32 fifo_fill); + + qint32 getUnderrunCount(); + void setUnderrunCount(qint32 underrun_count); + + qint32 getOverrunCount(); + void setOverrunCount(qint32 overrun_count); + + qint32 getDroppedPacketsCount(); + void setDroppedPacketsCount(qint32 dropped_packets_count); + + float getLinkRate(); + void setLinkRate(float link_rate); + + qint32 getHwTimestamp(); + void setHwTimestamp(qint32 hw_timestamp); + + float getTemperature(); + void setTemperature(float temperature); + + + virtual bool isSet() override; + +private: + qint32 success; + bool m_success_isSet; + + qint32 stream_active; + bool m_stream_active_isSet; + + qint32 fifo_size; + bool m_fifo_size_isSet; + + qint32 fifo_fill; + bool m_fifo_fill_isSet; + + qint32 underrun_count; + bool m_underrun_count_isSet; + + qint32 overrun_count; + bool m_overrun_count_isSet; + + qint32 dropped_packets_count; + bool m_dropped_packets_count_isSet; + + float link_rate; + bool m_link_rate_isSet; + + qint32 hw_timestamp; + bool m_hw_timestamp_isSet; + + float temperature; + bool m_temperature_isSet; + +}; + +} + +#endif /* SWGLimeSdrInputReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp new file mode 100644 index 000000000..b0d24d6da --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp @@ -0,0 +1,295 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGLimeSdrOutputReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGLimeSdrOutputReport::SWGLimeSdrOutputReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGLimeSdrOutputReport::SWGLimeSdrOutputReport() { + success = 0; + m_success_isSet = false; + stream_active = 0; + m_stream_active_isSet = false; + fifo_size = 0; + m_fifo_size_isSet = false; + fifo_fill = 0; + m_fifo_fill_isSet = false; + underrun_count = 0; + m_underrun_count_isSet = false; + overrun_count = 0; + m_overrun_count_isSet = false; + dropped_packets_count = 0; + m_dropped_packets_count_isSet = false; + link_rate = 0.0f; + m_link_rate_isSet = false; + hw_timestamp = 0; + m_hw_timestamp_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; +} + +SWGLimeSdrOutputReport::~SWGLimeSdrOutputReport() { + this->cleanup(); +} + +void +SWGLimeSdrOutputReport::init() { + success = 0; + m_success_isSet = false; + stream_active = 0; + m_stream_active_isSet = false; + fifo_size = 0; + m_fifo_size_isSet = false; + fifo_fill = 0; + m_fifo_fill_isSet = false; + underrun_count = 0; + m_underrun_count_isSet = false; + overrun_count = 0; + m_overrun_count_isSet = false; + dropped_packets_count = 0; + m_dropped_packets_count_isSet = false; + link_rate = 0.0f; + m_link_rate_isSet = false; + hw_timestamp = 0; + m_hw_timestamp_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; +} + +void +SWGLimeSdrOutputReport::cleanup() { + + + + + + + + + + +} + +SWGLimeSdrOutputReport* +SWGLimeSdrOutputReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGLimeSdrOutputReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&success, pJson["success"], "qint32", ""); + + ::SWGSDRangel::setValue(&stream_active, pJson["streamActive"], "qint32", ""); + + ::SWGSDRangel::setValue(&fifo_size, pJson["fifoSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&fifo_fill, pJson["fifoFill"], "qint32", ""); + + ::SWGSDRangel::setValue(&underrun_count, pJson["underrunCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&overrun_count, pJson["overrunCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&dropped_packets_count, pJson["droppedPacketsCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&link_rate, pJson["linkRate"], "float", ""); + + ::SWGSDRangel::setValue(&hw_timestamp, pJson["hwTimestamp"], "qint32", ""); + + ::SWGSDRangel::setValue(&temperature, pJson["temperature"], "float", ""); + +} + +QString +SWGLimeSdrOutputReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGLimeSdrOutputReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_success_isSet){ + obj->insert("success", QJsonValue(success)); + } + if(m_stream_active_isSet){ + obj->insert("streamActive", QJsonValue(stream_active)); + } + if(m_fifo_size_isSet){ + obj->insert("fifoSize", QJsonValue(fifo_size)); + } + if(m_fifo_fill_isSet){ + obj->insert("fifoFill", QJsonValue(fifo_fill)); + } + if(m_underrun_count_isSet){ + obj->insert("underrunCount", QJsonValue(underrun_count)); + } + if(m_overrun_count_isSet){ + obj->insert("overrunCount", QJsonValue(overrun_count)); + } + if(m_dropped_packets_count_isSet){ + obj->insert("droppedPacketsCount", QJsonValue(dropped_packets_count)); + } + if(m_link_rate_isSet){ + obj->insert("linkRate", QJsonValue(link_rate)); + } + if(m_hw_timestamp_isSet){ + obj->insert("hwTimestamp", QJsonValue(hw_timestamp)); + } + if(m_temperature_isSet){ + obj->insert("temperature", QJsonValue(temperature)); + } + + return obj; +} + +qint32 +SWGLimeSdrOutputReport::getSuccess() { + return success; +} +void +SWGLimeSdrOutputReport::setSuccess(qint32 success) { + this->success = success; + this->m_success_isSet = true; +} + +qint32 +SWGLimeSdrOutputReport::getStreamActive() { + return stream_active; +} +void +SWGLimeSdrOutputReport::setStreamActive(qint32 stream_active) { + this->stream_active = stream_active; + this->m_stream_active_isSet = true; +} + +qint32 +SWGLimeSdrOutputReport::getFifoSize() { + return fifo_size; +} +void +SWGLimeSdrOutputReport::setFifoSize(qint32 fifo_size) { + this->fifo_size = fifo_size; + this->m_fifo_size_isSet = true; +} + +qint32 +SWGLimeSdrOutputReport::getFifoFill() { + return fifo_fill; +} +void +SWGLimeSdrOutputReport::setFifoFill(qint32 fifo_fill) { + this->fifo_fill = fifo_fill; + this->m_fifo_fill_isSet = true; +} + +qint32 +SWGLimeSdrOutputReport::getUnderrunCount() { + return underrun_count; +} +void +SWGLimeSdrOutputReport::setUnderrunCount(qint32 underrun_count) { + this->underrun_count = underrun_count; + this->m_underrun_count_isSet = true; +} + +qint32 +SWGLimeSdrOutputReport::getOverrunCount() { + return overrun_count; +} +void +SWGLimeSdrOutputReport::setOverrunCount(qint32 overrun_count) { + this->overrun_count = overrun_count; + this->m_overrun_count_isSet = true; +} + +qint32 +SWGLimeSdrOutputReport::getDroppedPacketsCount() { + return dropped_packets_count; +} +void +SWGLimeSdrOutputReport::setDroppedPacketsCount(qint32 dropped_packets_count) { + this->dropped_packets_count = dropped_packets_count; + this->m_dropped_packets_count_isSet = true; +} + +float +SWGLimeSdrOutputReport::getLinkRate() { + return link_rate; +} +void +SWGLimeSdrOutputReport::setLinkRate(float link_rate) { + this->link_rate = link_rate; + this->m_link_rate_isSet = true; +} + +qint32 +SWGLimeSdrOutputReport::getHwTimestamp() { + return hw_timestamp; +} +void +SWGLimeSdrOutputReport::setHwTimestamp(qint32 hw_timestamp) { + this->hw_timestamp = hw_timestamp; + this->m_hw_timestamp_isSet = true; +} + +float +SWGLimeSdrOutputReport::getTemperature() { + return temperature; +} +void +SWGLimeSdrOutputReport::setTemperature(float temperature) { + this->temperature = temperature; + this->m_temperature_isSet = true; +} + + +bool +SWGLimeSdrOutputReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_success_isSet){ isObjectUpdated = true; break;} + if(m_stream_active_isSet){ isObjectUpdated = true; break;} + if(m_fifo_size_isSet){ isObjectUpdated = true; break;} + if(m_fifo_fill_isSet){ isObjectUpdated = true; break;} + if(m_underrun_count_isSet){ isObjectUpdated = true; break;} + if(m_overrun_count_isSet){ isObjectUpdated = true; break;} + if(m_dropped_packets_count_isSet){ isObjectUpdated = true; break;} + if(m_link_rate_isSet){ isObjectUpdated = true; break;} + if(m_hw_timestamp_isSet){ isObjectUpdated = true; break;} + if(m_temperature_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h new file mode 100644 index 000000000..2042cf490 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h @@ -0,0 +1,112 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGLimeSdrOutputReport.h + * + * LimeSDR + */ + +#ifndef SWGLimeSdrOutputReport_H_ +#define SWGLimeSdrOutputReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGLimeSdrOutputReport: public SWGObject { +public: + SWGLimeSdrOutputReport(); + SWGLimeSdrOutputReport(QString* json); + virtual ~SWGLimeSdrOutputReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGLimeSdrOutputReport* fromJson(QString &jsonString) override; + + qint32 getSuccess(); + void setSuccess(qint32 success); + + qint32 getStreamActive(); + void setStreamActive(qint32 stream_active); + + qint32 getFifoSize(); + void setFifoSize(qint32 fifo_size); + + qint32 getFifoFill(); + void setFifoFill(qint32 fifo_fill); + + qint32 getUnderrunCount(); + void setUnderrunCount(qint32 underrun_count); + + qint32 getOverrunCount(); + void setOverrunCount(qint32 overrun_count); + + qint32 getDroppedPacketsCount(); + void setDroppedPacketsCount(qint32 dropped_packets_count); + + float getLinkRate(); + void setLinkRate(float link_rate); + + qint32 getHwTimestamp(); + void setHwTimestamp(qint32 hw_timestamp); + + float getTemperature(); + void setTemperature(float temperature); + + + virtual bool isSet() override; + +private: + qint32 success; + bool m_success_isSet; + + qint32 stream_active; + bool m_stream_active_isSet; + + qint32 fifo_size; + bool m_fifo_size_isSet; + + qint32 fifo_fill; + bool m_fifo_fill_isSet; + + qint32 underrun_count; + bool m_underrun_count_isSet; + + qint32 overrun_count; + bool m_overrun_count_isSet; + + qint32 dropped_packets_count; + bool m_dropped_packets_count_isSet; + + float link_rate; + bool m_link_rate_isSet; + + qint32 hw_timestamp; + bool m_hw_timestamp_isSet; + + float temperature; + bool m_temperature_isSet; + +}; + +} + +#endif /* SWGLimeSdrOutputReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 5d82a9df7..a34da8f7a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -58,7 +58,9 @@ #include "SWGInstanceChannelsResponse.h" #include "SWGInstanceDevicesResponse.h" #include "SWGInstanceSummaryResponse.h" +#include "SWGLimeSdrInputReport.h" #include "SWGLimeSdrInputSettings.h" +#include "SWGLimeSdrOutputReport.h" #include "SWGLimeSdrOutputSettings.h" #include "SWGLocationInformation.h" #include "SWGLoggingInfo.h" @@ -234,9 +236,15 @@ namespace SWGSDRangel { if(QString("SWGInstanceSummaryResponse").compare(type) == 0) { return new SWGInstanceSummaryResponse(); } + if(QString("SWGLimeSdrInputReport").compare(type) == 0) { + return new SWGLimeSdrInputReport(); + } if(QString("SWGLimeSdrInputSettings").compare(type) == 0) { return new SWGLimeSdrInputSettings(); } + if(QString("SWGLimeSdrOutputReport").compare(type) == 0) { + return new SWGLimeSdrOutputReport(); + } if(QString("SWGLimeSdrOutputSettings").compare(type) == 0) { return new SWGLimeSdrOutputSettings(); } From f7b2220d1ccebab6a057bfcb826871fcd091c041 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 26 May 2018 22:39:50 +0200 Subject: [PATCH 472/956] Web API: fixed tx_test --- swagger/sdrangel/examples/tx_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index cf15d8dc5..06fb64605 100755 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -110,8 +110,6 @@ def setupDevice(options): if settings is None: exit(-1) - print(options.sample_rate) - # calculate RF analog and FIR optimal bandpass filters bandwidths lpFIRBW = options.sample_rate / (1< Date: Sun, 27 May 2018 11:07:24 +0200 Subject: [PATCH 473/956] SDRdaemon input: implemeted WEB API --- .../sdrdaemonsource/sdrdaemonsourcegui.cpp | 2 +- .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 127 +++++- .../sdrdaemonsource/sdrdaemonsourceinput.h | 16 + .../sdrdaemonsourceudphandler.h | 5 + sdrbase/resources/webapi/doc/html2/index.html | 84 +++- .../doc/swagger/include/SDRDaemonSource.yaml | 55 +++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + .../api/swagger/include/SDRDaemonSource.yaml | 55 +++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 84 +++- .../code/qt5/client/SWGDeviceReport.cpp | 23 ++ .../code/qt5/client/SWGDeviceReport.h | 7 + .../code/qt5/client/SWGDeviceSettings.cpp | 23 ++ .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../qt5/client/SWGSDRdaemonSourceReport.cpp | 213 ++++++++++ .../qt5/client/SWGSDRdaemonSourceReport.h | 89 +++++ .../qt5/client/SWGSDRdaemonSourceSettings.cpp | 364 ++++++++++++++++++ .../qt5/client/SWGSDRdaemonSourceSettings.h | 131 +++++++ 19 files changed, 1295 insertions(+), 6 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSource.yaml create mode 100644 swagger/sdrangel/api/swagger/include/SDRDaemonSource.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 86510b77a..a0e23d9e2 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -633,7 +633,7 @@ void SDRdaemonSourceGui::updateStatus() } } -void SDRdaemonSourceGui::tick() +void SDRdaemonSourceGui::tick() // FIXME: needed? { if ((++m_tickCount & 0xf) == 0) { SDRdaemonSourceInput::MsgConfigureSDRdaemonStreamTiming* message = SDRdaemonSourceInput::MsgConfigureSDRdaemonStreamTiming::create(); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 62fc4cadd..84232843c 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -16,8 +16,10 @@ #include #include +#include #include + #ifdef _WIN32 #include #include @@ -28,6 +30,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGSDRdaemonSourceReport.h" #include "util/simpleserializer.h" #include "dsp/dspcommands.h" @@ -228,11 +232,11 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) applySettings(conf.getSettings(), conf.getForce()); return true; } - else if (MsgConfigureSDRdaemonStreamTiming::match(message)) + else if (MsgConfigureSDRdaemonStreamTiming::match(message)) // FIXME: really needed? UDP handler is connected to timer { return true; } - else if (MsgReportSDRdaemonSourceStreamData::match(message)) + else if (MsgReportSDRdaemonSourceStreamData::match(message)) // FIXME: really needed? UDP handler sends it to GUI already { // Forward message to the GUI if it is present if (getMessageQueueToGUI()) { @@ -242,7 +246,7 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) return true; // delete the unused message } } - else if (MsgReportSDRdaemonSourceStreamTiming::match(message)) + else if (MsgReportSDRdaemonSourceStreamTiming::match(message)) // FIXME: really needed? UDP handler sends it to GUI already { // Forward message to the GUI if it is present if (getMessageQueueToGUI()) { @@ -432,3 +436,120 @@ int SDRdaemonSourceInput::webapiRun( return 200; } +int SDRdaemonSourceInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonSourceSettings(new SWGSDRangel::SWGSDRdaemonSourceSettings()); + response.getSdrDaemonSourceSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int SDRdaemonSourceInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + SDRdaemonSourceSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getSdrDaemonSourceSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("sampleRate")) { + settings.m_sampleRate = response.getSdrDaemonSourceSettings()->getSampleRate(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getSdrDaemonSourceSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("txDelay")) { + settings.m_txDelay = response.getSdrDaemonSourceSettings()->getTxDelay(); + } + if (deviceSettingsKeys.contains("nbFECBlocks")) { + settings.m_txDelay = response.getSdrDaemonSourceSettings()->getNbFecBlocks(); + } + if (deviceSettingsKeys.contains("address")) { + settings.m_address = *response.getSdrDaemonSourceSettings()->getAddress(); + } + if (deviceSettingsKeys.contains("dataPort")) { + settings.m_dataPort = response.getSdrDaemonSourceSettings()->getDataPort(); + } + if (deviceSettingsKeys.contains("controlPort")) { + settings.m_controlPort = response.getSdrDaemonSourceSettings()->getControlPort(); + } + if (deviceSettingsKeys.contains("specificParameters")) { + settings.m_specificParameters = *response.getSdrDaemonSourceSettings()->getSpecificParameters(); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getSdrDaemonSourceSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getSdrDaemonSourceSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("fcPos")) { + int fcPos = response.getSdrDaemonSourceSettings()->getFcPos(); + settings.m_fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getSdrDaemonSourceSettings()->getFileRecordName(); + } + + MsgConfigureSDRdaemonSource *msg = MsgConfigureSDRdaemonSource::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRdaemonSource *msgToGUI = MsgConfigureSDRdaemonSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void SDRdaemonSourceInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSourceSettings& settings) +{ + response.getSdrDaemonSourceSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getSdrDaemonSourceSettings()->setSampleRate(settings.m_sampleRate); + response.getSdrDaemonSourceSettings()->setLog2Decim(settings.m_log2Decim); + response.getSdrDaemonSourceSettings()->setTxDelay(settings.m_txDelay); + response.getSdrDaemonSourceSettings()->setNbFecBlocks(settings.m_nbFECBlocks); + response.getSdrDaemonSourceSettings()->setAddress(new QString(settings.m_address)); + response.getSdrDaemonSourceSettings()->setDataPort(settings.m_dataPort); + response.getSdrDaemonSourceSettings()->setControlPort(settings.m_controlPort); + response.getSdrDaemonSourceSettings()->setSpecificParameters(new QString(settings.m_specificParameters)); + response.getSdrDaemonSourceSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getSdrDaemonSourceSettings()->setIqCorrection(settings.m_iqCorrection); + response.getSdrDaemonSourceSettings()->setFcPos((int) settings.m_fcPos); + + if (response.getSdrDaemonSourceSettings()->getFileRecordName()) { + *response.getSdrDaemonSourceSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getSdrDaemonSourceSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +int SDRdaemonSourceInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonSourceReport(new SWGSDRangel::SWGSDRdaemonSourceReport()); + response.getSdrDaemonSourceReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void SDRdaemonSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getSdrDaemonSourceReport()->setCenterFrequency(m_SDRdaemonUDPHandler->getCenterFrequency()); + response.getSdrDaemonSourceReport()->setSampleRate(m_SDRdaemonUDPHandler->getSampleRate()); + response.getSdrDaemonSourceReport()->setBufferRwBalance(m_SDRdaemonUDPHandler->getBufferGauge()); + + quint64 startingTimeStampMsec = ((quint64) m_SDRdaemonUDPHandler->getTVSec() * 1000LL) + ((quint64) m_SDRdaemonUDPHandler->getTVuSec() / 1000LL); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); + response.getSdrDaemonSourceReport()->setDaemonTimestamp(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz"))); + + response.getSdrDaemonSourceReport()->setMinNbBlocks(m_SDRdaemonUDPHandler->getMinNbBlocks()); + response.getSdrDaemonSourceReport()->setMaxNbRecovery(m_SDRdaemonUDPHandler->getMaxNbRecovery()); +} diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h index 0de1e372b..6e035f606 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h @@ -279,6 +279,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -300,6 +314,8 @@ private: FileRecord *m_fileSink; //!< File sink to record device I/Q output void applySettings(const SDRdaemonSourceSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSourceSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif // INCLUDE_SDRDAEMONSOURCEINPUT_H diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h index bbb4c0799..44c26bbc8 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h @@ -47,6 +47,11 @@ public: bool isStreaming() const { return m_masterTimerConnected; } int getSampleRate() const { return m_samplerate; } int getCenterFrequency() const { return m_centerFrequency * 1000; } + int getBufferGauge() const { return m_sdrDaemonBuffer.getBufferGauge(); } + uint32_t getTVSec() const { return m_tv_sec; } + uint32_t getTVuSec() const { return m_tv_usec; } + int getMinNbBlocks() { return m_sdrDaemonBuffer.getMinNbBlocks(); } + int getMaxNbRecovery() { return m_sdrDaemonBuffer.getMaxNbRecovery(); } public slots: void dataReadyRead(); diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index d5445a952..49a75bb9f 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1738,6 +1738,9 @@ margin-bottom: 20px; }, "rtlSdrReport" : { "$ref" : "#/definitions/RtlSdrReport" + }, + "sdrDaemonSourceReport" : { + "$ref" : "#/definitions/SDRdaemonSourceReport" } }, "description" : "Base device report. The specific device report present depends on deviceHwType" @@ -1838,6 +1841,9 @@ margin-bottom: 20px; }, "rtlSdrSettings" : { "$ref" : "#/definitions/RtlSdrSettings" + }, + "sdrDaemonSourceSettings" : { + "$ref" : "#/definitions/SDRdaemonSourceSettings" } }, "description" : "Base device settings. The specific device settings present depends on deviceHwType." @@ -3047,6 +3053,82 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SDRdaemonSourceReport = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "sampleRate" : { + "type" : "integer" + }, + "bufferRWBalance" : { + "type" : "integer", + "description" : "percentage off the mid buffer (positive read leads)" + }, + "daemonTimestamp" : { + "type" : "string", + "description" : "string representation of timestamp as sent by the SDRdaemon instance" + }, + "minNbBlocks" : { + "type" : "integer", + "description" : "Minimum number of blocks retrieved per frame" + }, + "maxNbRecovery" : { + "type" : "integer", + "description" : "Maximum number of recovery blocks used per frame" + } + }, + "description" : "SDRdaemonSource" +}; + defs.SDRdaemonSourceSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "sampleRate" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "txDelay" : { + "type" : "number", + "format" : "float", + "description" : "minimum delay in ms between two consecutive packets sending" + }, + "nbFECBlocks" : { + "type" : "integer" + }, + "address" : { + "type" : "string" + }, + "dataPort" : { + "type" : "integer" + }, + "controlPort" : { + "type" : "integer" + }, + "specificParameters" : { + "type" : "string" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer", + "description" : "0=Infra 1=Supra 2=Center" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "SDRdaemonSource" }; defs.SSBDemodReport = { "properties" : { @@ -22292,7 +22374,7 @@ except ApiException as e:
    - Generated 2018-05-26T22:09:48.356+02:00 + Generated 2018-05-26T23:41:15.758+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSource.yaml new file mode 100644 index 000000000..66bf84bd6 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSource.yaml @@ -0,0 +1,55 @@ +SDRdaemonSourceSettings: + description: SDRdaemonSource + properties: + centerFrequency: + type: integer + format: uint64 + sampleRate: + type: integer + log2Decim: + type: integer + txDelay: + description: minimum delay in ms between two consecutive packets sending + type: number + format: float + nbFECBlocks: + type: integer + address: + type: string + dataPort: + type: integer + controlPort: + type: integer + specificParameters: + type: string + dcBlock: + type: integer + iqCorrection: + type: integer + fcPos: + description: 0=Infra 1=Supra 2=Center + type: integer + fileRecordName: + type: string + +SDRdaemonSourceReport: + description: SDRdaemonSource + properties: + centerFrequency: + type: integer + format: uint64 + sampleRate: + type: integer + bufferRWBalance: + description: percentage off the mid buffer (positive read leads) + type: integer + daemonTimestamp: + description: string representation of timestamp as sent by the SDRdaemon instance + type: string + minNbBlocks: + description: Minimum number of blocks retrieved per frame + type: integer + maxNbRecovery: + description: Maximum number of recovery blocks used per frame + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index e4a21ae40..9bd8c4f9a 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1775,6 +1775,8 @@ definitions: $ref: "/doc/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputSettings" rtlSdrSettings: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrSettings" + sdrDaemonSourceSettings: + $ref: "/doc/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceSettings" DeviceReport: description: Base device report. The specific device report present depends on deviceHwType @@ -1807,6 +1809,8 @@ definitions: $ref: "/doc/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputReport" rtlSdrReport: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrReport" + sdrDaemonSourceReport: + $ref: "/doc/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceReport" ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonSource.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonSource.yaml new file mode 100644 index 000000000..66bf84bd6 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonSource.yaml @@ -0,0 +1,55 @@ +SDRdaemonSourceSettings: + description: SDRdaemonSource + properties: + centerFrequency: + type: integer + format: uint64 + sampleRate: + type: integer + log2Decim: + type: integer + txDelay: + description: minimum delay in ms between two consecutive packets sending + type: number + format: float + nbFECBlocks: + type: integer + address: + type: string + dataPort: + type: integer + controlPort: + type: integer + specificParameters: + type: string + dcBlock: + type: integer + iqCorrection: + type: integer + fcPos: + description: 0=Infra 1=Supra 2=Center + type: integer + fileRecordName: + type: string + +SDRdaemonSourceReport: + description: SDRdaemonSource + properties: + centerFrequency: + type: integer + format: uint64 + sampleRate: + type: integer + bufferRWBalance: + description: percentage off the mid buffer (positive read leads) + type: integer + daemonTimestamp: + description: string representation of timestamp as sent by the SDRdaemon instance + type: string + minNbBlocks: + description: Minimum number of blocks retrieved per frame + type: integer + maxNbRecovery: + description: Maximum number of recovery blocks used per frame + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 209a7ba68..10d8f8183 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1775,6 +1775,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputSettings" rtlSdrSettings: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrSettings" + sdrDaemonSourceSettings: + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceSettings" DeviceReport: description: Base device report. The specific device report present depends on deviceHwType @@ -1807,6 +1809,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputReport" rtlSdrReport: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrReport" + sdrDaemonSourceReport: + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceReport " ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index d5445a952..49a75bb9f 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1738,6 +1738,9 @@ margin-bottom: 20px; }, "rtlSdrReport" : { "$ref" : "#/definitions/RtlSdrReport" + }, + "sdrDaemonSourceReport" : { + "$ref" : "#/definitions/SDRdaemonSourceReport" } }, "description" : "Base device report. The specific device report present depends on deviceHwType" @@ -1838,6 +1841,9 @@ margin-bottom: 20px; }, "rtlSdrSettings" : { "$ref" : "#/definitions/RtlSdrSettings" + }, + "sdrDaemonSourceSettings" : { + "$ref" : "#/definitions/SDRdaemonSourceSettings" } }, "description" : "Base device settings. The specific device settings present depends on deviceHwType." @@ -3047,6 +3053,82 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SDRdaemonSourceReport = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "sampleRate" : { + "type" : "integer" + }, + "bufferRWBalance" : { + "type" : "integer", + "description" : "percentage off the mid buffer (positive read leads)" + }, + "daemonTimestamp" : { + "type" : "string", + "description" : "string representation of timestamp as sent by the SDRdaemon instance" + }, + "minNbBlocks" : { + "type" : "integer", + "description" : "Minimum number of blocks retrieved per frame" + }, + "maxNbRecovery" : { + "type" : "integer", + "description" : "Maximum number of recovery blocks used per frame" + } + }, + "description" : "SDRdaemonSource" +}; + defs.SDRdaemonSourceSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "sampleRate" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "txDelay" : { + "type" : "number", + "format" : "float", + "description" : "minimum delay in ms between two consecutive packets sending" + }, + "nbFECBlocks" : { + "type" : "integer" + }, + "address" : { + "type" : "string" + }, + "dataPort" : { + "type" : "integer" + }, + "controlPort" : { + "type" : "integer" + }, + "specificParameters" : { + "type" : "string" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer", + "description" : "0=Infra 1=Supra 2=Center" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "SDRdaemonSource" }; defs.SSBDemodReport = { "properties" : { @@ -22292,7 +22374,7 @@ except ApiException as e:
    - Generated 2018-05-26T22:09:48.356+02:00 + Generated 2018-05-26T23:41:15.758+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index 019c1fc7f..c0956bf08 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -50,6 +50,8 @@ SWGDeviceReport::SWGDeviceReport() { m_pluto_sdr_output_report_isSet = false; rtl_sdr_report = nullptr; m_rtl_sdr_report_isSet = false; + sdr_daemon_source_report = nullptr; + m_sdr_daemon_source_report_isSet = false; } SWGDeviceReport::~SWGDeviceReport() { @@ -80,6 +82,8 @@ SWGDeviceReport::init() { m_pluto_sdr_output_report_isSet = false; rtl_sdr_report = new SWGRtlSdrReport(); m_rtl_sdr_report_isSet = false; + sdr_daemon_source_report = new SWGSDRdaemonSourceReport(); + m_sdr_daemon_source_report_isSet = false; } void @@ -115,6 +119,9 @@ SWGDeviceReport::cleanup() { if(rtl_sdr_report != nullptr) { delete rtl_sdr_report; } + if(sdr_daemon_source_report != nullptr) { + delete sdr_daemon_source_report; + } } SWGDeviceReport* @@ -150,6 +157,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&rtl_sdr_report, pJson["rtlSdrReport"], "SWGRtlSdrReport", "SWGRtlSdrReport"); + ::SWGSDRangel::setValue(&sdr_daemon_source_report, pJson["sdrDaemonSourceReport"], "SWGSDRdaemonSourceReport", "SWGSDRdaemonSourceReport"); + } QString @@ -199,6 +208,9 @@ SWGDeviceReport::asJsonObject() { if((rtl_sdr_report != nullptr) && (rtl_sdr_report->isSet())){ toJsonValue(QString("rtlSdrReport"), rtl_sdr_report, obj, QString("SWGRtlSdrReport")); } + if((sdr_daemon_source_report != nullptr) && (sdr_daemon_source_report->isSet())){ + toJsonValue(QString("sdrDaemonSourceReport"), sdr_daemon_source_report, obj, QString("SWGSDRdaemonSourceReport")); + } return obj; } @@ -313,6 +325,16 @@ SWGDeviceReport::setRtlSdrReport(SWGRtlSdrReport* rtl_sdr_report) { this->m_rtl_sdr_report_isSet = true; } +SWGSDRdaemonSourceReport* +SWGDeviceReport::getSdrDaemonSourceReport() { + return sdr_daemon_source_report; +} +void +SWGDeviceReport::setSdrDaemonSourceReport(SWGSDRdaemonSourceReport* sdr_daemon_source_report) { + this->sdr_daemon_source_report = sdr_daemon_source_report; + this->m_sdr_daemon_source_report_isSet = true; +} + bool SWGDeviceReport::isSet(){ @@ -329,6 +351,7 @@ SWGDeviceReport::isSet(){ if(pluto_sdr_input_report != nullptr && pluto_sdr_input_report->isSet()){ isObjectUpdated = true; break;} if(pluto_sdr_output_report != nullptr && pluto_sdr_output_report->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_report != nullptr && rtl_sdr_report->isSet()){ isObjectUpdated = true; break;} + if(sdr_daemon_source_report != nullptr && sdr_daemon_source_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index 0b51c016a..ed8ea3348 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -31,6 +31,7 @@ #include "SWGPlutoSdrInputReport.h" #include "SWGPlutoSdrOutputReport.h" #include "SWGRtlSdrReport.h" +#include "SWGSDRdaemonSourceReport.h" #include #include "SWGObject.h" @@ -84,6 +85,9 @@ public: SWGRtlSdrReport* getRtlSdrReport(); void setRtlSdrReport(SWGRtlSdrReport* rtl_sdr_report); + SWGSDRdaemonSourceReport* getSdrDaemonSourceReport(); + void setSdrDaemonSourceReport(SWGSDRdaemonSourceReport* sdr_daemon_source_report); + virtual bool isSet() override; @@ -121,6 +125,9 @@ private: SWGRtlSdrReport* rtl_sdr_report; bool m_rtl_sdr_report_isSet; + SWGSDRdaemonSourceReport* sdr_daemon_source_report; + bool m_sdr_daemon_source_report_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index e8f645521..a041a3b1c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -62,6 +62,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_pluto_sdr_output_settings_isSet = false; rtl_sdr_settings = nullptr; m_rtl_sdr_settings_isSet = false; + sdr_daemon_source_settings = nullptr; + m_sdr_daemon_source_settings_isSet = false; } SWGDeviceSettings::~SWGDeviceSettings() { @@ -104,6 +106,8 @@ SWGDeviceSettings::init() { m_pluto_sdr_output_settings_isSet = false; rtl_sdr_settings = new SWGRtlSdrSettings(); m_rtl_sdr_settings_isSet = false; + sdr_daemon_source_settings = new SWGSDRdaemonSourceSettings(); + m_sdr_daemon_source_settings_isSet = false; } void @@ -157,6 +161,9 @@ SWGDeviceSettings::cleanup() { if(rtl_sdr_settings != nullptr) { delete rtl_sdr_settings; } + if(sdr_daemon_source_settings != nullptr) { + delete sdr_daemon_source_settings; + } } SWGDeviceSettings* @@ -204,6 +211,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&rtl_sdr_settings, pJson["rtlSdrSettings"], "SWGRtlSdrSettings", "SWGRtlSdrSettings"); + ::SWGSDRangel::setValue(&sdr_daemon_source_settings, pJson["sdrDaemonSourceSettings"], "SWGSDRdaemonSourceSettings", "SWGSDRdaemonSourceSettings"); + } QString @@ -271,6 +280,9 @@ SWGDeviceSettings::asJsonObject() { if((rtl_sdr_settings != nullptr) && (rtl_sdr_settings->isSet())){ toJsonValue(QString("rtlSdrSettings"), rtl_sdr_settings, obj, QString("SWGRtlSdrSettings")); } + if((sdr_daemon_source_settings != nullptr) && (sdr_daemon_source_settings->isSet())){ + toJsonValue(QString("sdrDaemonSourceSettings"), sdr_daemon_source_settings, obj, QString("SWGSDRdaemonSourceSettings")); + } return obj; } @@ -445,6 +457,16 @@ SWGDeviceSettings::setRtlSdrSettings(SWGRtlSdrSettings* rtl_sdr_settings) { this->m_rtl_sdr_settings_isSet = true; } +SWGSDRdaemonSourceSettings* +SWGDeviceSettings::getSdrDaemonSourceSettings() { + return sdr_daemon_source_settings; +} +void +SWGDeviceSettings::setSdrDaemonSourceSettings(SWGSDRdaemonSourceSettings* sdr_daemon_source_settings) { + this->sdr_daemon_source_settings = sdr_daemon_source_settings; + this->m_sdr_daemon_source_settings_isSet = true; +} + bool SWGDeviceSettings::isSet(){ @@ -467,6 +489,7 @@ SWGDeviceSettings::isSet(){ if(pluto_sdr_input_settings != nullptr && pluto_sdr_input_settings->isSet()){ isObjectUpdated = true; break;} if(pluto_sdr_output_settings != nullptr && pluto_sdr_output_settings->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_settings != nullptr && rtl_sdr_settings->isSet()){ isObjectUpdated = true; break;} + if(sdr_daemon_source_settings != nullptr && sdr_daemon_source_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 243e9e233..8d81a5a02 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -37,6 +37,7 @@ #include "SWGPlutoSdrInputSettings.h" #include "SWGPlutoSdrOutputSettings.h" #include "SWGRtlSdrSettings.h" +#include "SWGSDRdaemonSourceSettings.h" #include #include "SWGObject.h" @@ -108,6 +109,9 @@ public: SWGRtlSdrSettings* getRtlSdrSettings(); void setRtlSdrSettings(SWGRtlSdrSettings* rtl_sdr_settings); + SWGSDRdaemonSourceSettings* getSdrDaemonSourceSettings(); + void setSdrDaemonSourceSettings(SWGSDRdaemonSourceSettings* sdr_daemon_source_settings); + virtual bool isSet() override; @@ -163,6 +167,9 @@ private: SWGRtlSdrSettings* rtl_sdr_settings; bool m_rtl_sdr_settings_isSet; + SWGSDRdaemonSourceSettings* sdr_daemon_source_settings; + bool m_sdr_daemon_source_settings_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index a34da8f7a..b065d68da 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -86,6 +86,8 @@ #include "SWGRtlSdrReport.h" #include "SWGRtlSdrReport_gains.h" #include "SWGRtlSdrSettings.h" +#include "SWGSDRdaemonSourceReport.h" +#include "SWGSDRdaemonSourceSettings.h" #include "SWGSSBDemodReport.h" #include "SWGSSBDemodSettings.h" #include "SWGSSBModReport.h" @@ -320,6 +322,12 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } + if(QString("SWGSDRdaemonSourceReport").compare(type) == 0) { + return new SWGSDRdaemonSourceReport(); + } + if(QString("SWGSDRdaemonSourceSettings").compare(type) == 0) { + return new SWGSDRdaemonSourceSettings(); + } if(QString("SWGSSBDemodReport").compare(type) == 0) { return new SWGSSBDemodReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp new file mode 100644 index 000000000..252069449 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp @@ -0,0 +1,213 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRdaemonSourceReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRdaemonSourceReport::SWGSDRdaemonSourceReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRdaemonSourceReport::SWGSDRdaemonSourceReport() { + center_frequency = 0; + m_center_frequency_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + buffer_rw_balance = 0; + m_buffer_rw_balance_isSet = false; + daemon_timestamp = nullptr; + m_daemon_timestamp_isSet = false; + min_nb_blocks = 0; + m_min_nb_blocks_isSet = false; + max_nb_recovery = 0; + m_max_nb_recovery_isSet = false; +} + +SWGSDRdaemonSourceReport::~SWGSDRdaemonSourceReport() { + this->cleanup(); +} + +void +SWGSDRdaemonSourceReport::init() { + center_frequency = 0; + m_center_frequency_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + buffer_rw_balance = 0; + m_buffer_rw_balance_isSet = false; + daemon_timestamp = new QString(""); + m_daemon_timestamp_isSet = false; + min_nb_blocks = 0; + m_min_nb_blocks_isSet = false; + max_nb_recovery = 0; + m_max_nb_recovery_isSet = false; +} + +void +SWGSDRdaemonSourceReport::cleanup() { + + + + if(daemon_timestamp != nullptr) { + delete daemon_timestamp; + } + + +} + +SWGSDRdaemonSourceReport* +SWGSDRdaemonSourceReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRdaemonSourceReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&buffer_rw_balance, pJson["bufferRWBalance"], "qint32", ""); + + ::SWGSDRangel::setValue(&daemon_timestamp, pJson["daemonTimestamp"], "QString", "QString"); + + ::SWGSDRangel::setValue(&min_nb_blocks, pJson["minNbBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(&max_nb_recovery, pJson["maxNbRecovery"], "qint32", ""); + +} + +QString +SWGSDRdaemonSourceReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRdaemonSourceReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + if(m_buffer_rw_balance_isSet){ + obj->insert("bufferRWBalance", QJsonValue(buffer_rw_balance)); + } + if(daemon_timestamp != nullptr && *daemon_timestamp != QString("")){ + toJsonValue(QString("daemonTimestamp"), daemon_timestamp, obj, QString("QString")); + } + if(m_min_nb_blocks_isSet){ + obj->insert("minNbBlocks", QJsonValue(min_nb_blocks)); + } + if(m_max_nb_recovery_isSet){ + obj->insert("maxNbRecovery", QJsonValue(max_nb_recovery)); + } + + return obj; +} + +qint32 +SWGSDRdaemonSourceReport::getCenterFrequency() { + return center_frequency; +} +void +SWGSDRdaemonSourceReport::setCenterFrequency(qint32 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGSDRdaemonSourceReport::getSampleRate() { + return sample_rate; +} +void +SWGSDRdaemonSourceReport::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + +qint32 +SWGSDRdaemonSourceReport::getBufferRwBalance() { + return buffer_rw_balance; +} +void +SWGSDRdaemonSourceReport::setBufferRwBalance(qint32 buffer_rw_balance) { + this->buffer_rw_balance = buffer_rw_balance; + this->m_buffer_rw_balance_isSet = true; +} + +QString* +SWGSDRdaemonSourceReport::getDaemonTimestamp() { + return daemon_timestamp; +} +void +SWGSDRdaemonSourceReport::setDaemonTimestamp(QString* daemon_timestamp) { + this->daemon_timestamp = daemon_timestamp; + this->m_daemon_timestamp_isSet = true; +} + +qint32 +SWGSDRdaemonSourceReport::getMinNbBlocks() { + return min_nb_blocks; +} +void +SWGSDRdaemonSourceReport::setMinNbBlocks(qint32 min_nb_blocks) { + this->min_nb_blocks = min_nb_blocks; + this->m_min_nb_blocks_isSet = true; +} + +qint32 +SWGSDRdaemonSourceReport::getMaxNbRecovery() { + return max_nb_recovery; +} +void +SWGSDRdaemonSourceReport::setMaxNbRecovery(qint32 max_nb_recovery) { + this->max_nb_recovery = max_nb_recovery; + this->m_max_nb_recovery_isSet = true; +} + + +bool +SWGSDRdaemonSourceReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_buffer_rw_balance_isSet){ isObjectUpdated = true; break;} + if(daemon_timestamp != nullptr && *daemon_timestamp != QString("")){ isObjectUpdated = true; break;} + if(m_min_nb_blocks_isSet){ isObjectUpdated = true; break;} + if(m_max_nb_recovery_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h new file mode 100644 index 000000000..e191cbdfe --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h @@ -0,0 +1,89 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRdaemonSourceReport.h + * + * SDRdaemonSource + */ + +#ifndef SWGSDRdaemonSourceReport_H_ +#define SWGSDRdaemonSourceReport_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRdaemonSourceReport: public SWGObject { +public: + SWGSDRdaemonSourceReport(); + SWGSDRdaemonSourceReport(QString* json); + virtual ~SWGSDRdaemonSourceReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRdaemonSourceReport* fromJson(QString &jsonString) override; + + qint32 getCenterFrequency(); + void setCenterFrequency(qint32 center_frequency); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + qint32 getBufferRwBalance(); + void setBufferRwBalance(qint32 buffer_rw_balance); + + QString* getDaemonTimestamp(); + void setDaemonTimestamp(QString* daemon_timestamp); + + qint32 getMinNbBlocks(); + void setMinNbBlocks(qint32 min_nb_blocks); + + qint32 getMaxNbRecovery(); + void setMaxNbRecovery(qint32 max_nb_recovery); + + + virtual bool isSet() override; + +private: + qint32 center_frequency; + bool m_center_frequency_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + + qint32 buffer_rw_balance; + bool m_buffer_rw_balance_isSet; + + QString* daemon_timestamp; + bool m_daemon_timestamp_isSet; + + qint32 min_nb_blocks; + bool m_min_nb_blocks_isSet; + + qint32 max_nb_recovery; + bool m_max_nb_recovery_isSet; + +}; + +} + +#endif /* SWGSDRdaemonSourceReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp new file mode 100644 index 000000000..239d58117 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp @@ -0,0 +1,364 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRdaemonSourceSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRdaemonSourceSettings::SWGSDRdaemonSourceSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRdaemonSourceSettings::SWGSDRdaemonSourceSettings() { + center_frequency = 0; + m_center_frequency_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + tx_delay = 0.0f; + m_tx_delay_isSet = false; + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + address = nullptr; + m_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + control_port = 0; + m_control_port_isSet = false; + specific_parameters = nullptr; + m_specific_parameters_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; +} + +SWGSDRdaemonSourceSettings::~SWGSDRdaemonSourceSettings() { + this->cleanup(); +} + +void +SWGSDRdaemonSourceSettings::init() { + center_frequency = 0; + m_center_frequency_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + tx_delay = 0.0f; + m_tx_delay_isSet = false; + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + address = new QString(""); + m_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + control_port = 0; + m_control_port_isSet = false; + specific_parameters = new QString(""); + m_specific_parameters_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; +} + +void +SWGSDRdaemonSourceSettings::cleanup() { + + + + + + if(address != nullptr) { + delete address; + } + + + if(specific_parameters != nullptr) { + delete specific_parameters; + } + + + + if(file_record_name != nullptr) { + delete file_record_name; + } +} + +SWGSDRdaemonSourceSettings* +SWGSDRdaemonSourceSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRdaemonSourceSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&tx_delay, pJson["txDelay"], "float", ""); + + ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(&address, pJson["address"], "QString", "QString"); + + ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&control_port, pJson["controlPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&specific_parameters, pJson["specificParameters"], "QString", "QString"); + + ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); + + ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + + ::SWGSDRangel::setValue(&fc_pos, pJson["fcPos"], "qint32", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + +} + +QString +SWGSDRdaemonSourceSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRdaemonSourceSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + if(m_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_tx_delay_isSet){ + obj->insert("txDelay", QJsonValue(tx_delay)); + } + if(m_nb_fec_blocks_isSet){ + obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); + } + if(address != nullptr && *address != QString("")){ + toJsonValue(QString("address"), address, obj, QString("QString")); + } + if(m_data_port_isSet){ + obj->insert("dataPort", QJsonValue(data_port)); + } + if(m_control_port_isSet){ + obj->insert("controlPort", QJsonValue(control_port)); + } + if(specific_parameters != nullptr && *specific_parameters != QString("")){ + toJsonValue(QString("specificParameters"), specific_parameters, obj, QString("QString")); + } + if(m_dc_block_isSet){ + obj->insert("dcBlock", QJsonValue(dc_block)); + } + if(m_iq_correction_isSet){ + obj->insert("iqCorrection", QJsonValue(iq_correction)); + } + if(m_fc_pos_isSet){ + obj->insert("fcPos", QJsonValue(fc_pos)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + + return obj; +} + +qint32 +SWGSDRdaemonSourceSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGSDRdaemonSourceSettings::setCenterFrequency(qint32 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGSDRdaemonSourceSettings::getSampleRate() { + return sample_rate; +} +void +SWGSDRdaemonSourceSettings::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + +qint32 +SWGSDRdaemonSourceSettings::getLog2Decim() { + return log2_decim; +} +void +SWGSDRdaemonSourceSettings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +float +SWGSDRdaemonSourceSettings::getTxDelay() { + return tx_delay; +} +void +SWGSDRdaemonSourceSettings::setTxDelay(float tx_delay) { + this->tx_delay = tx_delay; + this->m_tx_delay_isSet = true; +} + +qint32 +SWGSDRdaemonSourceSettings::getNbFecBlocks() { + return nb_fec_blocks; +} +void +SWGSDRdaemonSourceSettings::setNbFecBlocks(qint32 nb_fec_blocks) { + this->nb_fec_blocks = nb_fec_blocks; + this->m_nb_fec_blocks_isSet = true; +} + +QString* +SWGSDRdaemonSourceSettings::getAddress() { + return address; +} +void +SWGSDRdaemonSourceSettings::setAddress(QString* address) { + this->address = address; + this->m_address_isSet = true; +} + +qint32 +SWGSDRdaemonSourceSettings::getDataPort() { + return data_port; +} +void +SWGSDRdaemonSourceSettings::setDataPort(qint32 data_port) { + this->data_port = data_port; + this->m_data_port_isSet = true; +} + +qint32 +SWGSDRdaemonSourceSettings::getControlPort() { + return control_port; +} +void +SWGSDRdaemonSourceSettings::setControlPort(qint32 control_port) { + this->control_port = control_port; + this->m_control_port_isSet = true; +} + +QString* +SWGSDRdaemonSourceSettings::getSpecificParameters() { + return specific_parameters; +} +void +SWGSDRdaemonSourceSettings::setSpecificParameters(QString* specific_parameters) { + this->specific_parameters = specific_parameters; + this->m_specific_parameters_isSet = true; +} + +qint32 +SWGSDRdaemonSourceSettings::getDcBlock() { + return dc_block; +} +void +SWGSDRdaemonSourceSettings::setDcBlock(qint32 dc_block) { + this->dc_block = dc_block; + this->m_dc_block_isSet = true; +} + +qint32 +SWGSDRdaemonSourceSettings::getIqCorrection() { + return iq_correction; +} +void +SWGSDRdaemonSourceSettings::setIqCorrection(qint32 iq_correction) { + this->iq_correction = iq_correction; + this->m_iq_correction_isSet = true; +} + +qint32 +SWGSDRdaemonSourceSettings::getFcPos() { + return fc_pos; +} +void +SWGSDRdaemonSourceSettings::setFcPos(qint32 fc_pos) { + this->fc_pos = fc_pos; + this->m_fc_pos_isSet = true; +} + +QString* +SWGSDRdaemonSourceSettings::getFileRecordName() { + return file_record_name; +} +void +SWGSDRdaemonSourceSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + + +bool +SWGSDRdaemonSourceSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_log2_decim_isSet){ isObjectUpdated = true; break;} + if(m_tx_delay_isSet){ isObjectUpdated = true; break;} + if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} + if(address != nullptr && *address != QString("")){ isObjectUpdated = true; break;} + if(m_data_port_isSet){ isObjectUpdated = true; break;} + if(m_control_port_isSet){ isObjectUpdated = true; break;} + if(specific_parameters != nullptr && *specific_parameters != QString("")){ isObjectUpdated = true; break;} + if(m_dc_block_isSet){ isObjectUpdated = true; break;} + if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + if(m_fc_pos_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h new file mode 100644 index 000000000..b6965db11 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h @@ -0,0 +1,131 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRdaemonSourceSettings.h + * + * SDRdaemonSource + */ + +#ifndef SWGSDRdaemonSourceSettings_H_ +#define SWGSDRdaemonSourceSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRdaemonSourceSettings: public SWGObject { +public: + SWGSDRdaemonSourceSettings(); + SWGSDRdaemonSourceSettings(QString* json); + virtual ~SWGSDRdaemonSourceSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRdaemonSourceSettings* fromJson(QString &jsonString) override; + + qint32 getCenterFrequency(); + void setCenterFrequency(qint32 center_frequency); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + float getTxDelay(); + void setTxDelay(float tx_delay); + + qint32 getNbFecBlocks(); + void setNbFecBlocks(qint32 nb_fec_blocks); + + QString* getAddress(); + void setAddress(QString* address); + + qint32 getDataPort(); + void setDataPort(qint32 data_port); + + qint32 getControlPort(); + void setControlPort(qint32 control_port); + + QString* getSpecificParameters(); + void setSpecificParameters(QString* specific_parameters); + + qint32 getDcBlock(); + void setDcBlock(qint32 dc_block); + + qint32 getIqCorrection(); + void setIqCorrection(qint32 iq_correction); + + qint32 getFcPos(); + void setFcPos(qint32 fc_pos); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + + virtual bool isSet() override; + +private: + qint32 center_frequency; + bool m_center_frequency_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + float tx_delay; + bool m_tx_delay_isSet; + + qint32 nb_fec_blocks; + bool m_nb_fec_blocks_isSet; + + QString* address; + bool m_address_isSet; + + qint32 data_port; + bool m_data_port_isSet; + + qint32 control_port; + bool m_control_port_isSet; + + QString* specific_parameters; + bool m_specific_parameters_isSet; + + qint32 dc_block; + bool m_dc_block_isSet; + + qint32 iq_correction; + bool m_iq_correction_isSet; + + qint32 fc_pos; + bool m_fc_pos_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + +}; + +} + +#endif /* SWGSDRdaemonSourceSettings_H_ */ From 19a0d55b4fde554acd13d5cb25b8c4e90f3f6a6d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 27 May 2018 11:10:41 +0200 Subject: [PATCH 474/956] SDRdaemon input: removed old message and tick handling --- .../sdrdaemonsource/sdrdaemonsourcegui.cpp | 9 ------- .../sdrdaemonsource/sdrdaemonsourcegui.h | 1 - .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 24 ------------------- .../sdrdaemonsource/sdrdaemonsourceplugin.cpp | 2 +- 4 files changed, 1 insertion(+), 35 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index a0e23d9e2..0762af326 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -90,7 +90,6 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(500); - connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); m_sampleSource = (SDRdaemonSourceInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -632,11 +631,3 @@ void SDRdaemonSourceGui::updateStatus() ui->startStop->setEnabled(false); } } - -void SDRdaemonSourceGui::tick() // FIXME: needed? -{ - if ((++m_tickCount & 0xf) == 0) { - SDRdaemonSourceInput::MsgConfigureSDRdaemonStreamTiming* message = SDRdaemonSourceInput::MsgConfigureSDRdaemonStreamTiming::create(); - m_sampleSource->getInputMessageQueue()->push(message); - } -} diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h index 3c04ff99f..b85dd08df 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h @@ -130,7 +130,6 @@ private slots: void on_nbFECBlocks_valueChanged(int value); void updateHardware(); void updateStatus(); - void tick(); }; #endif // INCLUDE_SDRDAEMONSOURCEGUI_H diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 84232843c..df62efdf6 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -232,30 +232,6 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) applySettings(conf.getSettings(), conf.getForce()); return true; } - else if (MsgConfigureSDRdaemonStreamTiming::match(message)) // FIXME: really needed? UDP handler is connected to timer - { - return true; - } - else if (MsgReportSDRdaemonSourceStreamData::match(message)) // FIXME: really needed? UDP handler sends it to GUI already - { - // Forward message to the GUI if it is present - if (getMessageQueueToGUI()) { - getMessageQueueToGUI()->push(const_cast(&message)); - return false; // deletion of message is handled by the GUI - } else { - return true; // delete the unused message - } - } - else if (MsgReportSDRdaemonSourceStreamTiming::match(message)) // FIXME: really needed? UDP handler sends it to GUI already - { - // Forward message to the GUI if it is present - if (getMessageQueueToGUI()) { - getMessageQueueToGUI()->push(const_cast(&message)); - return false; // deletion of message is handled by the GUI - } else { - return true; // delete the unused message - } - } else { return false; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp index 7023c4acf..f470d454a 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp @@ -26,7 +26,7 @@ const PluginDescriptor SDRdaemonSourcePlugin::m_pluginDescriptor = { QString("SDRdaemon source input"), - QString("3.14.6"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 9360c4de0b449ba20e2bc7e0a6341563af4dea27 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 27 May 2018 11:57:23 +0200 Subject: [PATCH 475/956] SDRPlay input: implemeted WEB API (1) --- plugins/samplesource/sdrplay/sdrplaygui.cpp | 213 --------- plugins/samplesource/sdrplay/sdrplaygui.h | 46 -- plugins/samplesource/sdrplay/sdrplayinput.cpp | 263 +++++++++++ plugins/samplesource/sdrplay/sdrplayinput.h | 61 +++ .../samplesource/sdrplay/sdrplayplugin.cpp | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 123 ++++- .../webapi/doc/swagger/include/SDRPlay.yaml | 77 ++++ .../resources/webapi/doc/swagger/swagger.yaml | 7 +- .../sdrangel/api/swagger/include/SDRPlay.yaml | 77 ++++ swagger/sdrangel/api/swagger/swagger.yaml | 9 +- swagger/sdrangel/code/html2/index.html | 123 ++++- .../code/qt5/client/SWGDeviceReport.cpp | 23 + .../code/qt5/client/SWGDeviceReport.h | 7 + .../code/qt5/client/SWGDeviceSettings.cpp | 23 + .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 20 + .../code/qt5/client/SWGSDRPlayReport.cpp | 193 ++++++++ .../code/qt5/client/SWGSDRPlayReport.h | 81 ++++ .../client/SWGSDRPlayReport_bandwidths.cpp | 106 +++++ .../qt5/client/SWGSDRPlayReport_bandwidths.h | 58 +++ .../SWGSDRPlayReport_frequencyBands.cpp | 150 +++++++ .../client/SWGSDRPlayReport_frequencyBands.h | 71 +++ ...GSDRPlayReport_intermediateFrequencies.cpp | 106 +++++ ...SWGSDRPlayReport_intermediateFrequencies.h | 58 +++ .../code/qt5/client/SWGSDRPlaySettings.cpp | 423 ++++++++++++++++++ .../code/qt5/client/SWGSDRPlaySettings.h | 149 ++++++ 26 files changed, 2211 insertions(+), 265 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml create mode 100644 swagger/sdrangel/api/swagger/include/SDRPlay.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h diff --git a/plugins/samplesource/sdrplay/sdrplaygui.cpp b/plugins/samplesource/sdrplay/sdrplaygui.cpp index b7fa82e93..34e4a3d13 100644 --- a/plugins/samplesource/sdrplay/sdrplaygui.cpp +++ b/plugins/samplesource/sdrplay/sdrplaygui.cpp @@ -472,216 +472,3 @@ void SDRPlayGui::on_record_toggled(bool checked) m_sampleSource->getInputMessageQueue()->push(message); } -// ==================================================================== - -unsigned int SDRPlaySampleRates::m_rates[m_nb_rates] = { - 1536000, // 0 - 1792000, // 1 - 2000000, // 2 - 2048000, // 3 - 2304000, // 4 - 2400000, // 5 - 3072000, // 6 - 3200000, // 7 - 4000000, // 8 - 4096000, // 9 - 4608000, // 10 - 4800000, // 11 - 5000000, // 12 - 6000000, // 13 - 6144000, // 14 - 6400000, // 15 - 8000000, // 16 - 8192000, // 17 -}; - -unsigned int SDRPlaySampleRates::getRate(unsigned int rate_index) -{ - if (rate_index < m_nb_rates) - { - return m_rates[rate_index]; - } - else - { - return m_rates[0]; - } -} - -unsigned int SDRPlaySampleRates::getRateIndex(unsigned int rate) -{ - for (unsigned int i=0; i < m_nb_rates; i++) - { - if (rate == m_rates[i]) - { - return i; - } - } - - return 0; -} - -unsigned int SDRPlaySampleRates::getNbRates() -{ - return SDRPlaySampleRates::m_nb_rates; -} - -// ==================================================================== - -unsigned int SDRPlayBandwidths::m_bw[m_nb_bw] = { - 200000, // 0 - 300000, // 1 - 600000, // 2 - 1536000, // 3 - 5000000, // 4 - 6000000, // 5 - 7000000, // 6 - 8000000, // 7 -}; - -unsigned int SDRPlayBandwidths::getBandwidth(unsigned int bandwidth_index) -{ - if (bandwidth_index < m_nb_bw) - { - return m_bw[bandwidth_index]; - } - else - { - return m_bw[0]; - } -} - -unsigned int SDRPlayBandwidths::getBandwidthIndex(unsigned int bandwidth) -{ - for (unsigned int i=0; i < m_nb_bw; i++) - { - if (bandwidth == m_bw[i]) - { - return i; - } - } - - return 0; -} - -unsigned int SDRPlayBandwidths::getNbBandwidths() -{ - return SDRPlayBandwidths::m_nb_bw; -} - -// ==================================================================== - -unsigned int SDRPlayIF::m_if[m_nb_if] = { - 0, // 0 - 450000, // 1 - 1620000, // 2 - 2048000, // 3 -}; - -unsigned int SDRPlayIF::getIF(unsigned int if_index) -{ - if (if_index < m_nb_if) - { - return m_if[if_index]; - } - else - { - return m_if[0]; - } -} - -unsigned int SDRPlayIF::getIFIndex(unsigned int iff) -{ - for (unsigned int i=0; i < m_nb_if; i++) - { - if (iff == m_if[i]) - { - return i; - } - } - - return 0; -} - -unsigned int SDRPlayIF::getNbIFs() -{ - return SDRPlayIF::m_nb_if; -} - -// ==================================================================== - -/** Lower frequency bound in kHz inclusive */ -unsigned int SDRPlayBands::m_bandLow[m_nb_bands] = { - 10, // 0 - 12000, // 1 - 30000, // 2 - 50000, // 3 - 120000, // 4 - 250000, // 5 - 380000, // 6 - 1000000, // 7 -}; - -/** Lower frequency bound in kHz exclusive */ -unsigned int SDRPlayBands::m_bandHigh[m_nb_bands] = { - 12000, // 0 - 30000, // 1 - 50000, // 2 - 120000, // 3 - 250000, // 4 - 380000, // 5 - 1000000, // 6 - 2000000, // 7 -}; - -const char* SDRPlayBands::m_bandName[m_nb_bands] = { - "10k-12M", // 0 - "12-30M", // 1 - "30-50M", // 2 - "50-120M", // 3 - "120-250M", // 4 - "250-380M", // 5 - "380M-1G", // 6 - "1-2G", // 7 -}; - -QString SDRPlayBands::getBandName(unsigned int band_index) -{ - if (band_index < m_nb_bands) - { - return QString(m_bandName[band_index]); - } - else - { - return QString(m_bandName[0]); - } -} - -unsigned int SDRPlayBands::getBandLow(unsigned int band_index) -{ - if (band_index < m_nb_bands) - { - return m_bandLow[band_index]; - } - else - { - return m_bandLow[0]; - } -} - -unsigned int SDRPlayBands::getBandHigh(unsigned int band_index) -{ - if (band_index < m_nb_bands) - { - return m_bandHigh[band_index]; - } - else - { - return m_bandHigh[0]; - } -} - - -unsigned int SDRPlayBands::getNbBands() -{ - return SDRPlayBands::m_nb_bands; -} diff --git a/plugins/samplesource/sdrplay/sdrplaygui.h b/plugins/samplesource/sdrplay/sdrplaygui.h index cab175969..12616b2c1 100644 --- a/plugins/samplesource/sdrplay/sdrplaygui.h +++ b/plugins/samplesource/sdrplay/sdrplaygui.h @@ -97,50 +97,4 @@ private slots: void on_record_toggled(bool checked); }; - -// ==================================================================== - -class SDRPlaySampleRates { -public: - static unsigned int getRate(unsigned int rate_index); - static unsigned int getRateIndex(unsigned int rate); - static unsigned int getNbRates(); -private: - static const unsigned int m_nb_rates = 18; - static unsigned int m_rates[m_nb_rates]; -}; - -class SDRPlayBandwidths { -public: - static unsigned int getBandwidth(unsigned int bandwidth_index); - static unsigned int getBandwidthIndex(unsigned int bandwidth); - static unsigned int getNbBandwidths(); -private: - static const unsigned int m_nb_bw = 8; - static unsigned int m_bw[m_nb_bw]; -}; - -class SDRPlayIF { -public: - static unsigned int getIF(unsigned int if_index); - static unsigned int getIFIndex(unsigned int iff); - static unsigned int getNbIFs(); -private: - static const unsigned int m_nb_if = 4; - static unsigned int m_if[m_nb_if]; -}; - -class SDRPlayBands { -public: - static QString getBandName(unsigned int band_index); - static unsigned int getBandLow(unsigned int band_index); - static unsigned int getBandHigh(unsigned int band_index); - static unsigned int getNbBands(); -private: - static const unsigned int m_nb_bands = 8; - static unsigned int m_bandLow[m_nb_bands]; - static unsigned int m_bandHigh[m_nb_bands]; - static const char* m_bandName[m_nb_bands]; -}; - #endif /* PLUGINS_SAMPLESOURCE_SDRPLAY_SDRPLAYGUI_H_ */ diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index a87b35e36..98c846291 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -20,6 +20,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGSDRPlayReport.h" #include "util/simpleserializer.h" #include "dsp/dspcommands.h" @@ -648,3 +650,264 @@ int SDRPlayInput::webapiRun( return 200; } +int SDRPlayInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrPlayReport(new SWGSDRangel::SWGSDRPlayReport()); + response.getSdrPlayReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void SDRPlayInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getSdrPlayReport()->setSampleRates(new QList); + + for (unsigned int i = 0; i < SDRPlaySampleRates::getNbRates(); i++) + { + response.getSdrPlayReport()->getSampleRates()->append(new SWGSDRangel::SWGAirspyReport_sampleRates); + response.getSdrPlayReport()->getSampleRates()->back()->setSampleRate(SDRPlaySampleRates::getRate(i)); + } + + response.getSdrPlayReport()->setIntermediateFrequencies(new QList); + + for (unsigned int i = 0; i < SDRPlayIF::getNbIFs(); i++) + { + response.getSdrPlayReport()->getIntermediateFrequencies()->append(new SWGSDRangel::SWGSDRPlayReport_intermediateFrequencies); + response.getSdrPlayReport()->getIntermediateFrequencies()->back()->setIntermediateFrequency(SDRPlayIF::getIF(i)); + } + + response.getSdrPlayReport()->setBandwidths(new QList); + + for (unsigned int i = 0; i < SDRPlayBandwidths::getNbBandwidths(); i++) + { + response.getSdrPlayReport()->getBandwidths()->append(new SWGSDRangel::SWGSDRPlayReport_bandwidths); + response.getSdrPlayReport()->getBandwidths()->back()->setBandwidth(SDRPlayBandwidths::getBandwidth(i)); + } + + response.getSdrPlayReport()->setFrequencyBands(new QList); + + for (unsigned int i = 0; i < SDRPlayBands::getNbBands(); i++) + { + response.getSdrPlayReport()->getFrequencyBands()->append(new SWGSDRangel::SWGSDRPlayReport_frequencyBands); + response.getSdrPlayReport()->getFrequencyBands()->back()->setBandName(new QString(SDRPlayBands::getBandName(i))); + response.getSdrPlayReport()->getFrequencyBands()->back()->setBandLow(SDRPlayBands::getBandLow(i)); + response.getSdrPlayReport()->getFrequencyBands()->back()->setBandHigh(SDRPlayBands::getBandHigh(i)); + } +} + +// ==================================================================== + +unsigned int SDRPlaySampleRates::m_rates[m_nb_rates] = { + 1536000, // 0 + 1792000, // 1 + 2000000, // 2 + 2048000, // 3 + 2304000, // 4 + 2400000, // 5 + 3072000, // 6 + 3200000, // 7 + 4000000, // 8 + 4096000, // 9 + 4608000, // 10 + 4800000, // 11 + 5000000, // 12 + 6000000, // 13 + 6144000, // 14 + 6400000, // 15 + 8000000, // 16 + 8192000, // 17 +}; + +unsigned int SDRPlaySampleRates::getRate(unsigned int rate_index) +{ + if (rate_index < m_nb_rates) + { + return m_rates[rate_index]; + } + else + { + return m_rates[0]; + } +} + +unsigned int SDRPlaySampleRates::getRateIndex(unsigned int rate) +{ + for (unsigned int i=0; i < m_nb_rates; i++) + { + if (rate == m_rates[i]) + { + return i; + } + } + + return 0; +} + +unsigned int SDRPlaySampleRates::getNbRates() +{ + return SDRPlaySampleRates::m_nb_rates; +} + +// ==================================================================== + +unsigned int SDRPlayBandwidths::m_bw[m_nb_bw] = { + 200000, // 0 + 300000, // 1 + 600000, // 2 + 1536000, // 3 + 5000000, // 4 + 6000000, // 5 + 7000000, // 6 + 8000000, // 7 +}; + +unsigned int SDRPlayBandwidths::getBandwidth(unsigned int bandwidth_index) +{ + if (bandwidth_index < m_nb_bw) + { + return m_bw[bandwidth_index]; + } + else + { + return m_bw[0]; + } +} + +unsigned int SDRPlayBandwidths::getBandwidthIndex(unsigned int bandwidth) +{ + for (unsigned int i=0; i < m_nb_bw; i++) + { + if (bandwidth == m_bw[i]) + { + return i; + } + } + + return 0; +} + +unsigned int SDRPlayBandwidths::getNbBandwidths() +{ + return SDRPlayBandwidths::m_nb_bw; +} + +// ==================================================================== + +unsigned int SDRPlayIF::m_if[m_nb_if] = { + 0, // 0 + 450000, // 1 + 1620000, // 2 + 2048000, // 3 +}; + +unsigned int SDRPlayIF::getIF(unsigned int if_index) +{ + if (if_index < m_nb_if) + { + return m_if[if_index]; + } + else + { + return m_if[0]; + } +} + +unsigned int SDRPlayIF::getIFIndex(unsigned int iff) +{ + for (unsigned int i=0; i < m_nb_if; i++) + { + if (iff == m_if[i]) + { + return i; + } + } + + return 0; +} + +unsigned int SDRPlayIF::getNbIFs() +{ + return SDRPlayIF::m_nb_if; +} + +// ==================================================================== + +/** Lower frequency bound in kHz inclusive */ +unsigned int SDRPlayBands::m_bandLow[m_nb_bands] = { + 10, // 0 + 12000, // 1 + 30000, // 2 + 50000, // 3 + 120000, // 4 + 250000, // 5 + 380000, // 6 + 1000000, // 7 +}; + +/** Lower frequency bound in kHz exclusive */ +unsigned int SDRPlayBands::m_bandHigh[m_nb_bands] = { + 12000, // 0 + 30000, // 1 + 50000, // 2 + 120000, // 3 + 250000, // 4 + 380000, // 5 + 1000000, // 6 + 2000000, // 7 +}; + +const char* SDRPlayBands::m_bandName[m_nb_bands] = { + "10k-12M", // 0 + "12-30M", // 1 + "30-50M", // 2 + "50-120M", // 3 + "120-250M", // 4 + "250-380M", // 5 + "380M-1G", // 6 + "1-2G", // 7 +}; + +QString SDRPlayBands::getBandName(unsigned int band_index) +{ + if (band_index < m_nb_bands) + { + return QString(m_bandName[band_index]); + } + else + { + return QString(m_bandName[0]); + } +} + +unsigned int SDRPlayBands::getBandLow(unsigned int band_index) +{ + if (band_index < m_nb_bands) + { + return m_bandLow[band_index]; + } + else + { + return m_bandLow[0]; + } +} + +unsigned int SDRPlayBands::getBandHigh(unsigned int band_index) +{ + if (band_index < m_nb_bands) + { + return m_bandHigh[band_index]; + } + else + { + return m_bandHigh[0]; + } +} + + +unsigned int SDRPlayBands::getNbBands() +{ + return SDRPlayBands::m_nb_bands; +} + diff --git a/plugins/samplesource/sdrplay/sdrplayinput.h b/plugins/samplesource/sdrplay/sdrplayinput.h index 3ee347188..3d5b52d6a 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.h +++ b/plugins/samplesource/sdrplay/sdrplayinput.h @@ -148,6 +148,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -164,6 +178,8 @@ private: void closeDevice(); bool applySettings(const SDRPlaySettings& settings, bool forwardChange, bool force); bool setDeviceCenterFrequency(quint64 freq); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRPlaySettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; @@ -177,4 +193,49 @@ private: FileRecord *m_fileSink; //!< File sink to record device I/Q output }; +// ==================================================================== + +class SDRPlaySampleRates { +public: + static unsigned int getRate(unsigned int rate_index); + static unsigned int getRateIndex(unsigned int rate); + static unsigned int getNbRates(); +private: + static const unsigned int m_nb_rates = 18; + static unsigned int m_rates[m_nb_rates]; +}; + +class SDRPlayBandwidths { +public: + static unsigned int getBandwidth(unsigned int bandwidth_index); + static unsigned int getBandwidthIndex(unsigned int bandwidth); + static unsigned int getNbBandwidths(); +private: + static const unsigned int m_nb_bw = 8; + static unsigned int m_bw[m_nb_bw]; +}; + +class SDRPlayIF { +public: + static unsigned int getIF(unsigned int if_index); + static unsigned int getIFIndex(unsigned int iff); + static unsigned int getNbIFs(); +private: + static const unsigned int m_nb_if = 4; + static unsigned int m_if[m_nb_if]; +}; + +class SDRPlayBands { +public: + static QString getBandName(unsigned int band_index); + static unsigned int getBandLow(unsigned int band_index); + static unsigned int getBandHigh(unsigned int band_index); + static unsigned int getNbBands(); +private: + static const unsigned int m_nb_bands = 8; + static unsigned int m_bandLow[m_nb_bands]; + static unsigned int m_bandHigh[m_nb_bands]; + static const char* m_bandName[m_nb_bands]; +}; + #endif /* PLUGINS_SAMPLESOURCE_SDRPLAY_SDRPLAYINPUT_H_ */ diff --git a/plugins/samplesource/sdrplay/sdrplayplugin.cpp b/plugins/samplesource/sdrplay/sdrplayplugin.cpp index 6628540a8..cb1ed4660 100644 --- a/plugins/samplesource/sdrplay/sdrplayplugin.cpp +++ b/plugins/samplesource/sdrplay/sdrplayplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor SDRPlayPlugin::m_pluginDescriptor = { QString("SDRPlay RSP1 Input"), - QString("3.14.6"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 49a75bb9f..13e3d1406 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1741,6 +1741,9 @@ margin-bottom: 20px; }, "sdrDaemonSourceReport" : { "$ref" : "#/definitions/SDRdaemonSourceReport" + }, + "sdrPlayReport" : { + "$ref" : "#/definitions/SDRPlayReport" } }, "description" : "Base device report. The specific device report present depends on deviceHwType" @@ -1844,6 +1847,9 @@ margin-bottom: 20px; }, "sdrDaemonSourceSettings" : { "$ref" : "#/definitions/SDRdaemonSourceSettings" + }, + "sdrPlaySettings" : { + "$ref" : "#/definitions/SDRPlaySettings" } }, "description" : "Base device settings. The specific device settings present depends on deviceHwType." @@ -3053,6 +3059,121 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SDRPlayReport = { + "properties" : { + "sampleRates" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AirspyReport_sampleRates" + } + }, + "bandwidths" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/SDRPlayReport_bandwidths" + } + }, + "intermediateFrequencies" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/SDRPlayReport_intermediateFrequencies" + } + }, + "frequencyBands" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/SDRPlayReport_frequencyBands" + } + } + }, + "description" : "SDRplay1" +}; + defs.SDRPlayReport_bandwidths = { + "properties" : { + "bandwidth" : { + "type" : "integer", + "description" : "bandwidth in Hz" + } + } +}; + defs.SDRPlayReport_frequencyBands = { + "properties" : { + "bandName" : { + "type" : "string" + }, + "bandLow" : { + "type" : "integer", + "description" : "lower frequency bound in Hz" + }, + "bandHigh" : { + "type" : "integer", + "description" : "higher frequency bound in Hz" + } + } +}; + defs.SDRPlayReport_intermediateFrequencies = { + "properties" : { + "intermediateFrequency" : { + "type" : "integer", + "description" : "frequency in Hz" + } + } +}; + defs.SDRPlaySettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "tunerGain" : { + "type" : "integer" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "frequencyBandIndex" : { + "type" : "integer" + }, + "ifFrequencyIndex" : { + "type" : "integer" + }, + "bandwidthIndex" : { + "type" : "integer" + }, + "devSampleRateIndex" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "tunerGainMode" : { + "type" : "integer", + "description" : "1 tuner (table) gain, 0 manual (LNA, Mixer, BB) gain" + }, + "lnaOn" : { + "type" : "integer" + }, + "mixerAmpOn" : { + "type" : "integer" + }, + "basebandGain" : { + "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "SDRplay1" }; defs.SDRdaemonSourceReport = { "properties" : { @@ -22374,7 +22495,7 @@ except ApiException as e:
    - Generated 2018-05-26T23:41:15.758+02:00 + Generated 2018-05-27T11:38:07.698+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml new file mode 100644 index 000000000..a54937f24 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml @@ -0,0 +1,77 @@ +SDRPlaySettings: + description: SDRplay1 + properties: + centerFrequency: + type: integer + format: uint64 + tunerGain: + type: integer + LOppmTenths: + type: integer + frequencyBandIndex: + type: integer + ifFrequencyIndex: + type: integer + bandwidthIndex: + type: integer + devSampleRateIndex: + type: integer + log2Decim: + type: integer + fcPos: + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + tunerGainMode: + description: 1 tuner (table) gain, 0 manual (LNA, Mixer, BB) gain + type: integer + lnaOn: + type: integer + mixerAmpOn: + type: integer + basebandGain: + type: integer + iqCorrection: + type: integer + fileRecordName: + type: string + +SDRPlayReport: + description: SDRplay1 + properties: + sampleRates: + type: array + items: + properties: + sampleRate: + description: sample rate in S/s + type: integer + bandwidths: + type: array + items: + properties: + bandwidth: + description: bandwidth in Hz + type: integer + intermediateFrequencies: + type: array + items: + properties: + intermediateFrequency: + description: frequency in Hz + type: integer + frequencyBands: + type: array + items: + properties: + bandName: + type: string + bandLow: + description: lower frequency bound in Hz + type: integer + bandHigh: + description: higher frequency bound in Hz + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 9bd8c4f9a..5ef701b74 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1777,7 +1777,10 @@ definitions: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrSettings" sdrDaemonSourceSettings: $ref: "/doc/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceSettings" + sdrPlaySettings: + $ref: "/doc/swagger/include/SDRPlay.yaml#/SDRPlaySettings" + DeviceReport: description: Base device report. The specific device report present depends on deviceHwType discriminator: deviceHwType @@ -1811,7 +1814,9 @@ definitions: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrReport" sdrDaemonSourceReport: $ref: "/doc/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceReport" - + sdrPlayReport: + $ref: "/doc/swagger/include/SDRPlay.yaml#/SDRPlayReport" + ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. discriminator: channelType diff --git a/swagger/sdrangel/api/swagger/include/SDRPlay.yaml b/swagger/sdrangel/api/swagger/include/SDRPlay.yaml new file mode 100644 index 000000000..a54937f24 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/SDRPlay.yaml @@ -0,0 +1,77 @@ +SDRPlaySettings: + description: SDRplay1 + properties: + centerFrequency: + type: integer + format: uint64 + tunerGain: + type: integer + LOppmTenths: + type: integer + frequencyBandIndex: + type: integer + ifFrequencyIndex: + type: integer + bandwidthIndex: + type: integer + devSampleRateIndex: + type: integer + log2Decim: + type: integer + fcPos: + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + tunerGainMode: + description: 1 tuner (table) gain, 0 manual (LNA, Mixer, BB) gain + type: integer + lnaOn: + type: integer + mixerAmpOn: + type: integer + basebandGain: + type: integer + iqCorrection: + type: integer + fileRecordName: + type: string + +SDRPlayReport: + description: SDRplay1 + properties: + sampleRates: + type: array + items: + properties: + sampleRate: + description: sample rate in S/s + type: integer + bandwidths: + type: array + items: + properties: + bandwidth: + description: bandwidth in Hz + type: integer + intermediateFrequencies: + type: array + items: + properties: + intermediateFrequency: + description: frequency in Hz + type: integer + frequencyBands: + type: array + items: + properties: + bandName: + type: string + bandLow: + description: lower frequency bound in Hz + type: integer + bandHigh: + description: higher frequency bound in Hz + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 10d8f8183..dade77108 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1777,7 +1777,10 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrSettings" sdrDaemonSourceSettings: $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceSettings" + sdrPlaySettings: + $ref: "http://localhost:8081/api/swagger/include/SDRPlay.yaml#/SDRPlaySettings" + DeviceReport: description: Base device report. The specific device report present depends on deviceHwType discriminator: deviceHwType @@ -1810,8 +1813,10 @@ definitions: rtlSdrReport: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrReport" sdrDaemonSourceReport: - $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceReport " - + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceReport" + sdrPlayReport: + $ref: "http://localhost:8081/api/swagger/include/SDRPlay.yaml#/SDRPlayReport" + ChannelSettings: description: Base channel settings. The specific channel settings present depends on channelType. discriminator: channelType diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 49a75bb9f..13e3d1406 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1741,6 +1741,9 @@ margin-bottom: 20px; }, "sdrDaemonSourceReport" : { "$ref" : "#/definitions/SDRdaemonSourceReport" + }, + "sdrPlayReport" : { + "$ref" : "#/definitions/SDRPlayReport" } }, "description" : "Base device report. The specific device report present depends on deviceHwType" @@ -1844,6 +1847,9 @@ margin-bottom: 20px; }, "sdrDaemonSourceSettings" : { "$ref" : "#/definitions/SDRdaemonSourceSettings" + }, + "sdrPlaySettings" : { + "$ref" : "#/definitions/SDRPlaySettings" } }, "description" : "Base device settings. The specific device settings present depends on deviceHwType." @@ -3053,6 +3059,121 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SDRPlayReport = { + "properties" : { + "sampleRates" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AirspyReport_sampleRates" + } + }, + "bandwidths" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/SDRPlayReport_bandwidths" + } + }, + "intermediateFrequencies" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/SDRPlayReport_intermediateFrequencies" + } + }, + "frequencyBands" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/SDRPlayReport_frequencyBands" + } + } + }, + "description" : "SDRplay1" +}; + defs.SDRPlayReport_bandwidths = { + "properties" : { + "bandwidth" : { + "type" : "integer", + "description" : "bandwidth in Hz" + } + } +}; + defs.SDRPlayReport_frequencyBands = { + "properties" : { + "bandName" : { + "type" : "string" + }, + "bandLow" : { + "type" : "integer", + "description" : "lower frequency bound in Hz" + }, + "bandHigh" : { + "type" : "integer", + "description" : "higher frequency bound in Hz" + } + } +}; + defs.SDRPlayReport_intermediateFrequencies = { + "properties" : { + "intermediateFrequency" : { + "type" : "integer", + "description" : "frequency in Hz" + } + } +}; + defs.SDRPlaySettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "tunerGain" : { + "type" : "integer" + }, + "LOppmTenths" : { + "type" : "integer" + }, + "frequencyBandIndex" : { + "type" : "integer" + }, + "ifFrequencyIndex" : { + "type" : "integer" + }, + "bandwidthIndex" : { + "type" : "integer" + }, + "devSampleRateIndex" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "tunerGainMode" : { + "type" : "integer", + "description" : "1 tuner (table) gain, 0 manual (LNA, Mixer, BB) gain" + }, + "lnaOn" : { + "type" : "integer" + }, + "mixerAmpOn" : { + "type" : "integer" + }, + "basebandGain" : { + "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "SDRplay1" }; defs.SDRdaemonSourceReport = { "properties" : { @@ -22374,7 +22495,7 @@ except ApiException as e:
    - Generated 2018-05-26T23:41:15.758+02:00 + Generated 2018-05-27T11:38:07.698+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index c0956bf08..93f904470 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -52,6 +52,8 @@ SWGDeviceReport::SWGDeviceReport() { m_rtl_sdr_report_isSet = false; sdr_daemon_source_report = nullptr; m_sdr_daemon_source_report_isSet = false; + sdr_play_report = nullptr; + m_sdr_play_report_isSet = false; } SWGDeviceReport::~SWGDeviceReport() { @@ -84,6 +86,8 @@ SWGDeviceReport::init() { m_rtl_sdr_report_isSet = false; sdr_daemon_source_report = new SWGSDRdaemonSourceReport(); m_sdr_daemon_source_report_isSet = false; + sdr_play_report = new SWGSDRPlayReport(); + m_sdr_play_report_isSet = false; } void @@ -122,6 +126,9 @@ SWGDeviceReport::cleanup() { if(sdr_daemon_source_report != nullptr) { delete sdr_daemon_source_report; } + if(sdr_play_report != nullptr) { + delete sdr_play_report; + } } SWGDeviceReport* @@ -159,6 +166,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&sdr_daemon_source_report, pJson["sdrDaemonSourceReport"], "SWGSDRdaemonSourceReport", "SWGSDRdaemonSourceReport"); + ::SWGSDRangel::setValue(&sdr_play_report, pJson["sdrPlayReport"], "SWGSDRPlayReport", "SWGSDRPlayReport"); + } QString @@ -211,6 +220,9 @@ SWGDeviceReport::asJsonObject() { if((sdr_daemon_source_report != nullptr) && (sdr_daemon_source_report->isSet())){ toJsonValue(QString("sdrDaemonSourceReport"), sdr_daemon_source_report, obj, QString("SWGSDRdaemonSourceReport")); } + if((sdr_play_report != nullptr) && (sdr_play_report->isSet())){ + toJsonValue(QString("sdrPlayReport"), sdr_play_report, obj, QString("SWGSDRPlayReport")); + } return obj; } @@ -335,6 +347,16 @@ SWGDeviceReport::setSdrDaemonSourceReport(SWGSDRdaemonSourceReport* sdr_daemon_s this->m_sdr_daemon_source_report_isSet = true; } +SWGSDRPlayReport* +SWGDeviceReport::getSdrPlayReport() { + return sdr_play_report; +} +void +SWGDeviceReport::setSdrPlayReport(SWGSDRPlayReport* sdr_play_report) { + this->sdr_play_report = sdr_play_report; + this->m_sdr_play_report_isSet = true; +} + bool SWGDeviceReport::isSet(){ @@ -352,6 +374,7 @@ SWGDeviceReport::isSet(){ if(pluto_sdr_output_report != nullptr && pluto_sdr_output_report->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_report != nullptr && rtl_sdr_report->isSet()){ isObjectUpdated = true; break;} if(sdr_daemon_source_report != nullptr && sdr_daemon_source_report->isSet()){ isObjectUpdated = true; break;} + if(sdr_play_report != nullptr && sdr_play_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index ed8ea3348..05ac916f5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -31,6 +31,7 @@ #include "SWGPlutoSdrInputReport.h" #include "SWGPlutoSdrOutputReport.h" #include "SWGRtlSdrReport.h" +#include "SWGSDRPlayReport.h" #include "SWGSDRdaemonSourceReport.h" #include @@ -88,6 +89,9 @@ public: SWGSDRdaemonSourceReport* getSdrDaemonSourceReport(); void setSdrDaemonSourceReport(SWGSDRdaemonSourceReport* sdr_daemon_source_report); + SWGSDRPlayReport* getSdrPlayReport(); + void setSdrPlayReport(SWGSDRPlayReport* sdr_play_report); + virtual bool isSet() override; @@ -128,6 +132,9 @@ private: SWGSDRdaemonSourceReport* sdr_daemon_source_report; bool m_sdr_daemon_source_report_isSet; + SWGSDRPlayReport* sdr_play_report; + bool m_sdr_play_report_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index a041a3b1c..d3322d466 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -64,6 +64,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_rtl_sdr_settings_isSet = false; sdr_daemon_source_settings = nullptr; m_sdr_daemon_source_settings_isSet = false; + sdr_play_settings = nullptr; + m_sdr_play_settings_isSet = false; } SWGDeviceSettings::~SWGDeviceSettings() { @@ -108,6 +110,8 @@ SWGDeviceSettings::init() { m_rtl_sdr_settings_isSet = false; sdr_daemon_source_settings = new SWGSDRdaemonSourceSettings(); m_sdr_daemon_source_settings_isSet = false; + sdr_play_settings = new SWGSDRPlaySettings(); + m_sdr_play_settings_isSet = false; } void @@ -164,6 +168,9 @@ SWGDeviceSettings::cleanup() { if(sdr_daemon_source_settings != nullptr) { delete sdr_daemon_source_settings; } + if(sdr_play_settings != nullptr) { + delete sdr_play_settings; + } } SWGDeviceSettings* @@ -213,6 +220,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&sdr_daemon_source_settings, pJson["sdrDaemonSourceSettings"], "SWGSDRdaemonSourceSettings", "SWGSDRdaemonSourceSettings"); + ::SWGSDRangel::setValue(&sdr_play_settings, pJson["sdrPlaySettings"], "SWGSDRPlaySettings", "SWGSDRPlaySettings"); + } QString @@ -283,6 +292,9 @@ SWGDeviceSettings::asJsonObject() { if((sdr_daemon_source_settings != nullptr) && (sdr_daemon_source_settings->isSet())){ toJsonValue(QString("sdrDaemonSourceSettings"), sdr_daemon_source_settings, obj, QString("SWGSDRdaemonSourceSettings")); } + if((sdr_play_settings != nullptr) && (sdr_play_settings->isSet())){ + toJsonValue(QString("sdrPlaySettings"), sdr_play_settings, obj, QString("SWGSDRPlaySettings")); + } return obj; } @@ -467,6 +479,16 @@ SWGDeviceSettings::setSdrDaemonSourceSettings(SWGSDRdaemonSourceSettings* sdr_da this->m_sdr_daemon_source_settings_isSet = true; } +SWGSDRPlaySettings* +SWGDeviceSettings::getSdrPlaySettings() { + return sdr_play_settings; +} +void +SWGDeviceSettings::setSdrPlaySettings(SWGSDRPlaySettings* sdr_play_settings) { + this->sdr_play_settings = sdr_play_settings; + this->m_sdr_play_settings_isSet = true; +} + bool SWGDeviceSettings::isSet(){ @@ -490,6 +512,7 @@ SWGDeviceSettings::isSet(){ if(pluto_sdr_output_settings != nullptr && pluto_sdr_output_settings->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_settings != nullptr && rtl_sdr_settings->isSet()){ isObjectUpdated = true; break;} if(sdr_daemon_source_settings != nullptr && sdr_daemon_source_settings->isSet()){ isObjectUpdated = true; break;} + if(sdr_play_settings != nullptr && sdr_play_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 8d81a5a02..784d4d05d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -37,6 +37,7 @@ #include "SWGPlutoSdrInputSettings.h" #include "SWGPlutoSdrOutputSettings.h" #include "SWGRtlSdrSettings.h" +#include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSourceSettings.h" #include @@ -112,6 +113,9 @@ public: SWGSDRdaemonSourceSettings* getSdrDaemonSourceSettings(); void setSdrDaemonSourceSettings(SWGSDRdaemonSourceSettings* sdr_daemon_source_settings); + SWGSDRPlaySettings* getSdrPlaySettings(); + void setSdrPlaySettings(SWGSDRPlaySettings* sdr_play_settings); + virtual bool isSet() override; @@ -170,6 +174,9 @@ private: SWGSDRdaemonSourceSettings* sdr_daemon_source_settings; bool m_sdr_daemon_source_settings_isSet; + SWGSDRPlaySettings* sdr_play_settings; + bool m_sdr_play_settings_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index b065d68da..c9f9f2718 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -86,6 +86,11 @@ #include "SWGRtlSdrReport.h" #include "SWGRtlSdrReport_gains.h" #include "SWGRtlSdrSettings.h" +#include "SWGSDRPlayReport.h" +#include "SWGSDRPlayReport_bandwidths.h" +#include "SWGSDRPlayReport_frequencyBands.h" +#include "SWGSDRPlayReport_intermediateFrequencies.h" +#include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSourceReport.h" #include "SWGSDRdaemonSourceSettings.h" #include "SWGSSBDemodReport.h" @@ -322,6 +327,21 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } + if(QString("SWGSDRPlayReport").compare(type) == 0) { + return new SWGSDRPlayReport(); + } + if(QString("SWGSDRPlayReport_bandwidths").compare(type) == 0) { + return new SWGSDRPlayReport_bandwidths(); + } + if(QString("SWGSDRPlayReport_frequencyBands").compare(type) == 0) { + return new SWGSDRPlayReport_frequencyBands(); + } + if(QString("SWGSDRPlayReport_intermediateFrequencies").compare(type) == 0) { + return new SWGSDRPlayReport_intermediateFrequencies(); + } + if(QString("SWGSDRPlaySettings").compare(type) == 0) { + return new SWGSDRPlaySettings(); + } if(QString("SWGSDRdaemonSourceReport").compare(type) == 0) { return new SWGSDRdaemonSourceReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp new file mode 100644 index 000000000..d339f4a83 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp @@ -0,0 +1,193 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRPlayReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRPlayReport::SWGSDRPlayReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRPlayReport::SWGSDRPlayReport() { + sample_rates = nullptr; + m_sample_rates_isSet = false; + bandwidths = nullptr; + m_bandwidths_isSet = false; + intermediate_frequencies = nullptr; + m_intermediate_frequencies_isSet = false; + frequency_bands = nullptr; + m_frequency_bands_isSet = false; +} + +SWGSDRPlayReport::~SWGSDRPlayReport() { + this->cleanup(); +} + +void +SWGSDRPlayReport::init() { + sample_rates = new QList(); + m_sample_rates_isSet = false; + bandwidths = new QList(); + m_bandwidths_isSet = false; + intermediate_frequencies = new QList(); + m_intermediate_frequencies_isSet = false; + frequency_bands = new QList(); + m_frequency_bands_isSet = false; +} + +void +SWGSDRPlayReport::cleanup() { + if(sample_rates != nullptr) { + auto arr = sample_rates; + for(auto o: *arr) { + delete o; + } + delete sample_rates; + } + if(bandwidths != nullptr) { + auto arr = bandwidths; + for(auto o: *arr) { + delete o; + } + delete bandwidths; + } + if(intermediate_frequencies != nullptr) { + auto arr = intermediate_frequencies; + for(auto o: *arr) { + delete o; + } + delete intermediate_frequencies; + } + if(frequency_bands != nullptr) { + auto arr = frequency_bands; + for(auto o: *arr) { + delete o; + } + delete frequency_bands; + } +} + +SWGSDRPlayReport* +SWGSDRPlayReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRPlayReport::fromJsonObject(QJsonObject &pJson) { + + ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGAirspyReport_sampleRates"); + + ::SWGSDRangel::setValue(&bandwidths, pJson["bandwidths"], "QList", "SWGSDRPlayReport_bandwidths"); + + ::SWGSDRangel::setValue(&intermediate_frequencies, pJson["intermediateFrequencies"], "QList", "SWGSDRPlayReport_intermediateFrequencies"); + + ::SWGSDRangel::setValue(&frequency_bands, pJson["frequencyBands"], "QList", "SWGSDRPlayReport_frequencyBands"); +} + +QString +SWGSDRPlayReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRPlayReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(sample_rates->size() > 0){ + toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGAirspyReport_sampleRates"); + } + if(bandwidths->size() > 0){ + toJsonArray((QList*)bandwidths, obj, "bandwidths", "SWGSDRPlayReport_bandwidths"); + } + if(intermediate_frequencies->size() > 0){ + toJsonArray((QList*)intermediate_frequencies, obj, "intermediateFrequencies", "SWGSDRPlayReport_intermediateFrequencies"); + } + if(frequency_bands->size() > 0){ + toJsonArray((QList*)frequency_bands, obj, "frequencyBands", "SWGSDRPlayReport_frequencyBands"); + } + + return obj; +} + +QList* +SWGSDRPlayReport::getSampleRates() { + return sample_rates; +} +void +SWGSDRPlayReport::setSampleRates(QList* sample_rates) { + this->sample_rates = sample_rates; + this->m_sample_rates_isSet = true; +} + +QList* +SWGSDRPlayReport::getBandwidths() { + return bandwidths; +} +void +SWGSDRPlayReport::setBandwidths(QList* bandwidths) { + this->bandwidths = bandwidths; + this->m_bandwidths_isSet = true; +} + +QList* +SWGSDRPlayReport::getIntermediateFrequencies() { + return intermediate_frequencies; +} +void +SWGSDRPlayReport::setIntermediateFrequencies(QList* intermediate_frequencies) { + this->intermediate_frequencies = intermediate_frequencies; + this->m_intermediate_frequencies_isSet = true; +} + +QList* +SWGSDRPlayReport::getFrequencyBands() { + return frequency_bands; +} +void +SWGSDRPlayReport::setFrequencyBands(QList* frequency_bands) { + this->frequency_bands = frequency_bands; + this->m_frequency_bands_isSet = true; +} + + +bool +SWGSDRPlayReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(sample_rates->size() > 0){ isObjectUpdated = true; break;} + if(bandwidths->size() > 0){ isObjectUpdated = true; break;} + if(intermediate_frequencies->size() > 0){ isObjectUpdated = true; break;} + if(frequency_bands->size() > 0){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h new file mode 100644 index 000000000..3fd62716c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h @@ -0,0 +1,81 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRPlayReport.h + * + * SDRplay1 + */ + +#ifndef SWGSDRPlayReport_H_ +#define SWGSDRPlayReport_H_ + +#include + + +#include "SWGAirspyReport_sampleRates.h" +#include "SWGSDRPlayReport_bandwidths.h" +#include "SWGSDRPlayReport_frequencyBands.h" +#include "SWGSDRPlayReport_intermediateFrequencies.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRPlayReport: public SWGObject { +public: + SWGSDRPlayReport(); + SWGSDRPlayReport(QString* json); + virtual ~SWGSDRPlayReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRPlayReport* fromJson(QString &jsonString) override; + + QList* getSampleRates(); + void setSampleRates(QList* sample_rates); + + QList* getBandwidths(); + void setBandwidths(QList* bandwidths); + + QList* getIntermediateFrequencies(); + void setIntermediateFrequencies(QList* intermediate_frequencies); + + QList* getFrequencyBands(); + void setFrequencyBands(QList* frequency_bands); + + + virtual bool isSet() override; + +private: + QList* sample_rates; + bool m_sample_rates_isSet; + + QList* bandwidths; + bool m_bandwidths_isSet; + + QList* intermediate_frequencies; + bool m_intermediate_frequencies_isSet; + + QList* frequency_bands; + bool m_frequency_bands_isSet; + +}; + +} + +#endif /* SWGSDRPlayReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.cpp new file mode 100644 index 000000000..9801e4a99 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRPlayReport_bandwidths.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRPlayReport_bandwidths::SWGSDRPlayReport_bandwidths(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRPlayReport_bandwidths::SWGSDRPlayReport_bandwidths() { + bandwidth = 0; + m_bandwidth_isSet = false; +} + +SWGSDRPlayReport_bandwidths::~SWGSDRPlayReport_bandwidths() { + this->cleanup(); +} + +void +SWGSDRPlayReport_bandwidths::init() { + bandwidth = 0; + m_bandwidth_isSet = false; +} + +void +SWGSDRPlayReport_bandwidths::cleanup() { + +} + +SWGSDRPlayReport_bandwidths* +SWGSDRPlayReport_bandwidths::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRPlayReport_bandwidths::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); + +} + +QString +SWGSDRPlayReport_bandwidths::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRPlayReport_bandwidths::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_bandwidth_isSet){ + obj->insert("bandwidth", QJsonValue(bandwidth)); + } + + return obj; +} + +qint32 +SWGSDRPlayReport_bandwidths::getBandwidth() { + return bandwidth; +} +void +SWGSDRPlayReport_bandwidths::setBandwidth(qint32 bandwidth) { + this->bandwidth = bandwidth; + this->m_bandwidth_isSet = true; +} + + +bool +SWGSDRPlayReport_bandwidths::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_bandwidth_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.h new file mode 100644 index 000000000..67b0161c2 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRPlayReport_bandwidths.h + * + * + */ + +#ifndef SWGSDRPlayReport_bandwidths_H_ +#define SWGSDRPlayReport_bandwidths_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRPlayReport_bandwidths: public SWGObject { +public: + SWGSDRPlayReport_bandwidths(); + SWGSDRPlayReport_bandwidths(QString* json); + virtual ~SWGSDRPlayReport_bandwidths(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRPlayReport_bandwidths* fromJson(QString &jsonString) override; + + qint32 getBandwidth(); + void setBandwidth(qint32 bandwidth); + + + virtual bool isSet() override; + +private: + qint32 bandwidth; + bool m_bandwidth_isSet; + +}; + +} + +#endif /* SWGSDRPlayReport_bandwidths_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.cpp new file mode 100644 index 000000000..20c6cbf1d --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.cpp @@ -0,0 +1,150 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRPlayReport_frequencyBands.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRPlayReport_frequencyBands::SWGSDRPlayReport_frequencyBands(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRPlayReport_frequencyBands::SWGSDRPlayReport_frequencyBands() { + band_name = nullptr; + m_band_name_isSet = false; + band_low = 0; + m_band_low_isSet = false; + band_high = 0; + m_band_high_isSet = false; +} + +SWGSDRPlayReport_frequencyBands::~SWGSDRPlayReport_frequencyBands() { + this->cleanup(); +} + +void +SWGSDRPlayReport_frequencyBands::init() { + band_name = new QString(""); + m_band_name_isSet = false; + band_low = 0; + m_band_low_isSet = false; + band_high = 0; + m_band_high_isSet = false; +} + +void +SWGSDRPlayReport_frequencyBands::cleanup() { + if(band_name != nullptr) { + delete band_name; + } + + +} + +SWGSDRPlayReport_frequencyBands* +SWGSDRPlayReport_frequencyBands::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRPlayReport_frequencyBands::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&band_name, pJson["bandName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&band_low, pJson["bandLow"], "qint32", ""); + + ::SWGSDRangel::setValue(&band_high, pJson["bandHigh"], "qint32", ""); + +} + +QString +SWGSDRPlayReport_frequencyBands::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRPlayReport_frequencyBands::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(band_name != nullptr && *band_name != QString("")){ + toJsonValue(QString("bandName"), band_name, obj, QString("QString")); + } + if(m_band_low_isSet){ + obj->insert("bandLow", QJsonValue(band_low)); + } + if(m_band_high_isSet){ + obj->insert("bandHigh", QJsonValue(band_high)); + } + + return obj; +} + +QString* +SWGSDRPlayReport_frequencyBands::getBandName() { + return band_name; +} +void +SWGSDRPlayReport_frequencyBands::setBandName(QString* band_name) { + this->band_name = band_name; + this->m_band_name_isSet = true; +} + +qint32 +SWGSDRPlayReport_frequencyBands::getBandLow() { + return band_low; +} +void +SWGSDRPlayReport_frequencyBands::setBandLow(qint32 band_low) { + this->band_low = band_low; + this->m_band_low_isSet = true; +} + +qint32 +SWGSDRPlayReport_frequencyBands::getBandHigh() { + return band_high; +} +void +SWGSDRPlayReport_frequencyBands::setBandHigh(qint32 band_high) { + this->band_high = band_high; + this->m_band_high_isSet = true; +} + + +bool +SWGSDRPlayReport_frequencyBands::isSet(){ + bool isObjectUpdated = false; + do{ + if(band_name != nullptr && *band_name != QString("")){ isObjectUpdated = true; break;} + if(m_band_low_isSet){ isObjectUpdated = true; break;} + if(m_band_high_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.h new file mode 100644 index 000000000..29653f799 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.h @@ -0,0 +1,71 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRPlayReport_frequencyBands.h + * + * + */ + +#ifndef SWGSDRPlayReport_frequencyBands_H_ +#define SWGSDRPlayReport_frequencyBands_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRPlayReport_frequencyBands: public SWGObject { +public: + SWGSDRPlayReport_frequencyBands(); + SWGSDRPlayReport_frequencyBands(QString* json); + virtual ~SWGSDRPlayReport_frequencyBands(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRPlayReport_frequencyBands* fromJson(QString &jsonString) override; + + QString* getBandName(); + void setBandName(QString* band_name); + + qint32 getBandLow(); + void setBandLow(qint32 band_low); + + qint32 getBandHigh(); + void setBandHigh(qint32 band_high); + + + virtual bool isSet() override; + +private: + QString* band_name; + bool m_band_name_isSet; + + qint32 band_low; + bool m_band_low_isSet; + + qint32 band_high; + bool m_band_high_isSet; + +}; + +} + +#endif /* SWGSDRPlayReport_frequencyBands_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.cpp new file mode 100644 index 000000000..653aae36c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRPlayReport_intermediateFrequencies.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRPlayReport_intermediateFrequencies::SWGSDRPlayReport_intermediateFrequencies(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRPlayReport_intermediateFrequencies::SWGSDRPlayReport_intermediateFrequencies() { + intermediate_frequency = 0; + m_intermediate_frequency_isSet = false; +} + +SWGSDRPlayReport_intermediateFrequencies::~SWGSDRPlayReport_intermediateFrequencies() { + this->cleanup(); +} + +void +SWGSDRPlayReport_intermediateFrequencies::init() { + intermediate_frequency = 0; + m_intermediate_frequency_isSet = false; +} + +void +SWGSDRPlayReport_intermediateFrequencies::cleanup() { + +} + +SWGSDRPlayReport_intermediateFrequencies* +SWGSDRPlayReport_intermediateFrequencies::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRPlayReport_intermediateFrequencies::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&intermediate_frequency, pJson["intermediateFrequency"], "qint32", ""); + +} + +QString +SWGSDRPlayReport_intermediateFrequencies::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRPlayReport_intermediateFrequencies::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_intermediate_frequency_isSet){ + obj->insert("intermediateFrequency", QJsonValue(intermediate_frequency)); + } + + return obj; +} + +qint32 +SWGSDRPlayReport_intermediateFrequencies::getIntermediateFrequency() { + return intermediate_frequency; +} +void +SWGSDRPlayReport_intermediateFrequencies::setIntermediateFrequency(qint32 intermediate_frequency) { + this->intermediate_frequency = intermediate_frequency; + this->m_intermediate_frequency_isSet = true; +} + + +bool +SWGSDRPlayReport_intermediateFrequencies::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_intermediate_frequency_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.h new file mode 100644 index 000000000..93504fc3e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRPlayReport_intermediateFrequencies.h + * + * + */ + +#ifndef SWGSDRPlayReport_intermediateFrequencies_H_ +#define SWGSDRPlayReport_intermediateFrequencies_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRPlayReport_intermediateFrequencies: public SWGObject { +public: + SWGSDRPlayReport_intermediateFrequencies(); + SWGSDRPlayReport_intermediateFrequencies(QString* json); + virtual ~SWGSDRPlayReport_intermediateFrequencies(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRPlayReport_intermediateFrequencies* fromJson(QString &jsonString) override; + + qint32 getIntermediateFrequency(); + void setIntermediateFrequency(qint32 intermediate_frequency); + + + virtual bool isSet() override; + +private: + qint32 intermediate_frequency; + bool m_intermediate_frequency_isSet; + +}; + +} + +#endif /* SWGSDRPlayReport_intermediateFrequencies_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp new file mode 100644 index 000000000..f5417988c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp @@ -0,0 +1,423 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRPlaySettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRPlaySettings::SWGSDRPlaySettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRPlaySettings::SWGSDRPlaySettings() { + center_frequency = 0; + m_center_frequency_isSet = false; + tuner_gain = 0; + m_tuner_gain_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + frequency_band_index = 0; + m_frequency_band_index_isSet = false; + if_frequency_index = 0; + m_if_frequency_index_isSet = false; + bandwidth_index = 0; + m_bandwidth_index_isSet = false; + dev_sample_rate_index = 0; + m_dev_sample_rate_index_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + tuner_gain_mode = 0; + m_tuner_gain_mode_isSet = false; + lna_on = 0; + m_lna_on_isSet = false; + mixer_amp_on = 0; + m_mixer_amp_on_isSet = false; + baseband_gain = 0; + m_baseband_gain_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; +} + +SWGSDRPlaySettings::~SWGSDRPlaySettings() { + this->cleanup(); +} + +void +SWGSDRPlaySettings::init() { + center_frequency = 0; + m_center_frequency_isSet = false; + tuner_gain = 0; + m_tuner_gain_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; + frequency_band_index = 0; + m_frequency_band_index_isSet = false; + if_frequency_index = 0; + m_if_frequency_index_isSet = false; + bandwidth_index = 0; + m_bandwidth_index_isSet = false; + dev_sample_rate_index = 0; + m_dev_sample_rate_index_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + tuner_gain_mode = 0; + m_tuner_gain_mode_isSet = false; + lna_on = 0; + m_lna_on_isSet = false; + mixer_amp_on = 0; + m_mixer_amp_on_isSet = false; + baseband_gain = 0; + m_baseband_gain_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; +} + +void +SWGSDRPlaySettings::cleanup() { + + + + + + + + + + + + + + + + if(file_record_name != nullptr) { + delete file_record_name; + } +} + +SWGSDRPlaySettings* +SWGSDRPlaySettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRPlaySettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint32", ""); + + ::SWGSDRangel::setValue(&tuner_gain, pJson["tunerGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); + + ::SWGSDRangel::setValue(&frequency_band_index, pJson["frequencyBandIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&if_frequency_index, pJson["ifFrequencyIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&bandwidth_index, pJson["bandwidthIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate_index, pJson["devSampleRateIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&fc_pos, pJson["fcPos"], "qint32", ""); + + ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); + + ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + + ::SWGSDRangel::setValue(&tuner_gain_mode, pJson["tunerGainMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&lna_on, pJson["lnaOn"], "qint32", ""); + + ::SWGSDRangel::setValue(&mixer_amp_on, pJson["mixerAmpOn"], "qint32", ""); + + ::SWGSDRangel::setValue(&baseband_gain, pJson["basebandGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + +} + +QString +SWGSDRPlaySettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRPlaySettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_tuner_gain_isSet){ + obj->insert("tunerGain", QJsonValue(tuner_gain)); + } + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); + } + if(m_frequency_band_index_isSet){ + obj->insert("frequencyBandIndex", QJsonValue(frequency_band_index)); + } + if(m_if_frequency_index_isSet){ + obj->insert("ifFrequencyIndex", QJsonValue(if_frequency_index)); + } + if(m_bandwidth_index_isSet){ + obj->insert("bandwidthIndex", QJsonValue(bandwidth_index)); + } + if(m_dev_sample_rate_index_isSet){ + obj->insert("devSampleRateIndex", QJsonValue(dev_sample_rate_index)); + } + if(m_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_fc_pos_isSet){ + obj->insert("fcPos", QJsonValue(fc_pos)); + } + if(m_dc_block_isSet){ + obj->insert("dcBlock", QJsonValue(dc_block)); + } + if(m_iq_correction_isSet){ + obj->insert("iqCorrection", QJsonValue(iq_correction)); + } + if(m_tuner_gain_mode_isSet){ + obj->insert("tunerGainMode", QJsonValue(tuner_gain_mode)); + } + if(m_lna_on_isSet){ + obj->insert("lnaOn", QJsonValue(lna_on)); + } + if(m_mixer_amp_on_isSet){ + obj->insert("mixerAmpOn", QJsonValue(mixer_amp_on)); + } + if(m_baseband_gain_isSet){ + obj->insert("basebandGain", QJsonValue(baseband_gain)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + + return obj; +} + +qint32 +SWGSDRPlaySettings::getCenterFrequency() { + return center_frequency; +} +void +SWGSDRPlaySettings::setCenterFrequency(qint32 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getTunerGain() { + return tuner_gain; +} +void +SWGSDRPlaySettings::setTunerGain(qint32 tuner_gain) { + this->tuner_gain = tuner_gain; + this->m_tuner_gain_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getLOppmTenths() { + return l_oppm_tenths; +} +void +SWGSDRPlaySettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getFrequencyBandIndex() { + return frequency_band_index; +} +void +SWGSDRPlaySettings::setFrequencyBandIndex(qint32 frequency_band_index) { + this->frequency_band_index = frequency_band_index; + this->m_frequency_band_index_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getIfFrequencyIndex() { + return if_frequency_index; +} +void +SWGSDRPlaySettings::setIfFrequencyIndex(qint32 if_frequency_index) { + this->if_frequency_index = if_frequency_index; + this->m_if_frequency_index_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getBandwidthIndex() { + return bandwidth_index; +} +void +SWGSDRPlaySettings::setBandwidthIndex(qint32 bandwidth_index) { + this->bandwidth_index = bandwidth_index; + this->m_bandwidth_index_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getDevSampleRateIndex() { + return dev_sample_rate_index; +} +void +SWGSDRPlaySettings::setDevSampleRateIndex(qint32 dev_sample_rate_index) { + this->dev_sample_rate_index = dev_sample_rate_index; + this->m_dev_sample_rate_index_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getLog2Decim() { + return log2_decim; +} +void +SWGSDRPlaySettings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getFcPos() { + return fc_pos; +} +void +SWGSDRPlaySettings::setFcPos(qint32 fc_pos) { + this->fc_pos = fc_pos; + this->m_fc_pos_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getDcBlock() { + return dc_block; +} +void +SWGSDRPlaySettings::setDcBlock(qint32 dc_block) { + this->dc_block = dc_block; + this->m_dc_block_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getIqCorrection() { + return iq_correction; +} +void +SWGSDRPlaySettings::setIqCorrection(qint32 iq_correction) { + this->iq_correction = iq_correction; + this->m_iq_correction_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getTunerGainMode() { + return tuner_gain_mode; +} +void +SWGSDRPlaySettings::setTunerGainMode(qint32 tuner_gain_mode) { + this->tuner_gain_mode = tuner_gain_mode; + this->m_tuner_gain_mode_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getLnaOn() { + return lna_on; +} +void +SWGSDRPlaySettings::setLnaOn(qint32 lna_on) { + this->lna_on = lna_on; + this->m_lna_on_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getMixerAmpOn() { + return mixer_amp_on; +} +void +SWGSDRPlaySettings::setMixerAmpOn(qint32 mixer_amp_on) { + this->mixer_amp_on = mixer_amp_on; + this->m_mixer_amp_on_isSet = true; +} + +qint32 +SWGSDRPlaySettings::getBasebandGain() { + return baseband_gain; +} +void +SWGSDRPlaySettings::setBasebandGain(qint32 baseband_gain) { + this->baseband_gain = baseband_gain; + this->m_baseband_gain_isSet = true; +} + +QString* +SWGSDRPlaySettings::getFileRecordName() { + return file_record_name; +} +void +SWGSDRPlaySettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + + +bool +SWGSDRPlaySettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_tuner_gain_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} + if(m_frequency_band_index_isSet){ isObjectUpdated = true; break;} + if(m_if_frequency_index_isSet){ isObjectUpdated = true; break;} + if(m_bandwidth_index_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_index_isSet){ isObjectUpdated = true; break;} + if(m_log2_decim_isSet){ isObjectUpdated = true; break;} + if(m_fc_pos_isSet){ isObjectUpdated = true; break;} + if(m_dc_block_isSet){ isObjectUpdated = true; break;} + if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + if(m_tuner_gain_mode_isSet){ isObjectUpdated = true; break;} + if(m_lna_on_isSet){ isObjectUpdated = true; break;} + if(m_mixer_amp_on_isSet){ isObjectUpdated = true; break;} + if(m_baseband_gain_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h new file mode 100644 index 000000000..b17561716 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h @@ -0,0 +1,149 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRPlaySettings.h + * + * SDRplay1 + */ + +#ifndef SWGSDRPlaySettings_H_ +#define SWGSDRPlaySettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRPlaySettings: public SWGObject { +public: + SWGSDRPlaySettings(); + SWGSDRPlaySettings(QString* json); + virtual ~SWGSDRPlaySettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRPlaySettings* fromJson(QString &jsonString) override; + + qint32 getCenterFrequency(); + void setCenterFrequency(qint32 center_frequency); + + qint32 getTunerGain(); + void setTunerGain(qint32 tuner_gain); + + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); + + qint32 getFrequencyBandIndex(); + void setFrequencyBandIndex(qint32 frequency_band_index); + + qint32 getIfFrequencyIndex(); + void setIfFrequencyIndex(qint32 if_frequency_index); + + qint32 getBandwidthIndex(); + void setBandwidthIndex(qint32 bandwidth_index); + + qint32 getDevSampleRateIndex(); + void setDevSampleRateIndex(qint32 dev_sample_rate_index); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + qint32 getFcPos(); + void setFcPos(qint32 fc_pos); + + qint32 getDcBlock(); + void setDcBlock(qint32 dc_block); + + qint32 getIqCorrection(); + void setIqCorrection(qint32 iq_correction); + + qint32 getTunerGainMode(); + void setTunerGainMode(qint32 tuner_gain_mode); + + qint32 getLnaOn(); + void setLnaOn(qint32 lna_on); + + qint32 getMixerAmpOn(); + void setMixerAmpOn(qint32 mixer_amp_on); + + qint32 getBasebandGain(); + void setBasebandGain(qint32 baseband_gain); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + + virtual bool isSet() override; + +private: + qint32 center_frequency; + bool m_center_frequency_isSet; + + qint32 tuner_gain; + bool m_tuner_gain_isSet; + + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; + + qint32 frequency_band_index; + bool m_frequency_band_index_isSet; + + qint32 if_frequency_index; + bool m_if_frequency_index_isSet; + + qint32 bandwidth_index; + bool m_bandwidth_index_isSet; + + qint32 dev_sample_rate_index; + bool m_dev_sample_rate_index_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + qint32 fc_pos; + bool m_fc_pos_isSet; + + qint32 dc_block; + bool m_dc_block_isSet; + + qint32 iq_correction; + bool m_iq_correction_isSet; + + qint32 tuner_gain_mode; + bool m_tuner_gain_mode_isSet; + + qint32 lna_on; + bool m_lna_on_isSet; + + qint32 mixer_amp_on; + bool m_mixer_amp_on_isSet; + + qint32 baseband_gain; + bool m_baseband_gain_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + +}; + +} + +#endif /* SWGSDRPlaySettings_H_ */ From 2184425311381b2eec6e5ff97db795e9f457fb41 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 27 May 2018 19:54:33 +0200 Subject: [PATCH 476/956] Web API: put some array items in their own classes --- plugins/samplesource/airspy/airspyinput.cpp | 6 +- .../samplesource/airspyhf/airspyhfinput.cpp | 6 +- plugins/samplesource/perseus/perseusinput.cpp | 6 +- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 6 +- plugins/samplesource/sdrplay/sdrplayinput.cpp | 26 +-- sdrbase/resources/res.qrc | 5 +- sdrbase/resources/webapi/doc/html2/index.html | 111 +++++++------ .../webapi/doc/swagger/include/Airspy.yaml | 6 +- .../webapi/doc/swagger/include/AirspyHF.yaml | 6 +- .../webapi/doc/swagger/include/Perseus.yaml | 6 +- .../webapi/doc/swagger/include/RtlSdr.yaml | 5 +- .../webapi/doc/swagger/include/SDRPlay.yaml | 26 +-- .../webapi/doc/swagger/include/Structs.yaml | 34 ++++ .../sdrangel/api/swagger/include/Airspy.yaml | 6 +- .../api/swagger/include/AirspyHF.yaml | 6 +- .../sdrangel/api/swagger/include/Perseus.yaml | 6 +- .../sdrangel/api/swagger/include/RtlSdr.yaml | 5 +- .../sdrangel/api/swagger/include/SDRPlay.yaml | 26 +-- .../sdrangel/api/swagger/include/Structs.yaml | 34 ++++ swagger/sdrangel/code/html2/index.html | 111 +++++++------ .../code/qt5/client/SWGAirspyHFReport.cpp | 10 +- .../code/qt5/client/SWGAirspyHFReport.h | 8 +- .../code/qt5/client/SWGAirspyReport.cpp | 10 +- .../code/qt5/client/SWGAirspyReport.h | 8 +- .../sdrangel/code/qt5/client/SWGBandwidth.cpp | 106 +++++++++++++ .../sdrangel/code/qt5/client/SWGBandwidth.h | 58 +++++++ .../sdrangel/code/qt5/client/SWGFrequency.cpp | 106 +++++++++++++ .../sdrangel/code/qt5/client/SWGFrequency.h | 58 +++++++ .../code/qt5/client/SWGFrequencyBand.cpp | 150 ++++++++++++++++++ .../code/qt5/client/SWGFrequencyBand.h | 71 +++++++++ swagger/sdrangel/code/qt5/client/SWGGain.cpp | 106 +++++++++++++ swagger/sdrangel/code/qt5/client/SWGGain.h | 58 +++++++ .../code/qt5/client/SWGModelFactory.h | 40 ++--- .../code/qt5/client/SWGPerseusReport.cpp | 10 +- .../code/qt5/client/SWGPerseusReport.h | 8 +- .../code/qt5/client/SWGRtlSdrReport.cpp | 10 +- .../code/qt5/client/SWGRtlSdrReport.h | 8 +- .../code/qt5/client/SWGSDRPlayReport.cpp | 40 ++--- .../code/qt5/client/SWGSDRPlayReport.h | 32 ++-- .../code/qt5/client/SWGSampleRate.cpp | 106 +++++++++++++ .../sdrangel/code/qt5/client/SWGSampleRate.h | 58 +++++++ 41 files changed, 1192 insertions(+), 312 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/Structs.yaml create mode 100644 swagger/sdrangel/api/swagger/include/Structs.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGBandwidth.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGFrequency.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGFrequency.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGGain.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGGain.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSampleRate.h diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index 78396f660..a78ab4ff3 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -741,11 +741,11 @@ void AirspyInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& res void AirspyInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { - response.getAirspyReport()->setSampleRates(new QList); + response.getAirspyReport()->setSampleRates(new QList); for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) { - response.getAirspyReport()->getSampleRates()->append(new SWGSDRangel::SWGAirspyReport_sampleRates); - response.getAirspyReport()->getSampleRates()->back()->setSampleRate(*it); + response.getAirspyReport()->getSampleRates()->append(new SWGSDRangel::SWGSampleRate); + response.getAirspyReport()->getSampleRates()->back()->setRate(*it); } } diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index 0009ddc8f..93c468621 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -565,12 +565,12 @@ void AirspyHFInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& r void AirspyHFInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { - response.getAirspyHfReport()->setSampleRates(new QList); + response.getAirspyHfReport()->setSampleRates(new QList); for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) { - response.getAirspyHfReport()->getSampleRates()->append(new SWGSDRangel::SWGAirspyReport_sampleRates); - response.getAirspyHfReport()->getSampleRates()->back()->setSampleRate(*it); + response.getAirspyHfReport()->getSampleRates()->append(new SWGSDRangel::SWGSampleRate); + response.getAirspyHfReport()->getSampleRates()->back()->setRate(*it); } } diff --git a/plugins/samplesource/perseus/perseusinput.cpp b/plugins/samplesource/perseus/perseusinput.cpp index 91ab52cc2..aaf431566 100644 --- a/plugins/samplesource/perseus/perseusinput.cpp +++ b/plugins/samplesource/perseus/perseusinput.cpp @@ -525,12 +525,12 @@ void PerseusInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& re void PerseusInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { - response.getPerseusReport()->setSampleRates(new QList); + response.getPerseusReport()->setSampleRates(new QList); for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) { - response.getPerseusReport()->getSampleRates()->append(new SWGSDRangel::SWGAirspyReport_sampleRates); - response.getPerseusReport()->getSampleRates()->back()->setSampleRate(*it); + response.getPerseusReport()->getSampleRates()->append(new SWGSDRangel::SWGSampleRate); + response.getPerseusReport()->getSampleRates()->back()->setRate(*it); } } diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index 2b1125fe9..e742458b2 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -668,12 +668,12 @@ int RTLSDRInput::webapiReportGet( void RTLSDRInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { - response.getRtlSdrReport()->setGains(new QList); + response.getRtlSdrReport()->setGains(new QList); for (std::vector::const_iterator it = getGains().begin(); it != getGains().end(); ++it) { - response.getRtlSdrReport()->getGains()->append(new SWGSDRangel::SWGRtlSdrReport_gains); - response.getRtlSdrReport()->getGains()->back()->setGain(*it); + response.getRtlSdrReport()->getGains()->append(new SWGSDRangel::SWGGain); + response.getRtlSdrReport()->getGains()->back()->setGainCb(*it); } } diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index 98c846291..c43f9bba9 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -662,38 +662,38 @@ int SDRPlayInput::webapiReportGet( void SDRPlayInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { - response.getSdrPlayReport()->setSampleRates(new QList); + response.getSdrPlayReport()->setSampleRates(new QList); for (unsigned int i = 0; i < SDRPlaySampleRates::getNbRates(); i++) { - response.getSdrPlayReport()->getSampleRates()->append(new SWGSDRangel::SWGAirspyReport_sampleRates); - response.getSdrPlayReport()->getSampleRates()->back()->setSampleRate(SDRPlaySampleRates::getRate(i)); + response.getSdrPlayReport()->getSampleRates()->append(new SWGSDRangel::SWGSampleRate); + response.getSdrPlayReport()->getSampleRates()->back()->setRate(SDRPlaySampleRates::getRate(i)); } - response.getSdrPlayReport()->setIntermediateFrequencies(new QList); + response.getSdrPlayReport()->setIntermediateFrequencies(new QList); for (unsigned int i = 0; i < SDRPlayIF::getNbIFs(); i++) { - response.getSdrPlayReport()->getIntermediateFrequencies()->append(new SWGSDRangel::SWGSDRPlayReport_intermediateFrequencies); - response.getSdrPlayReport()->getIntermediateFrequencies()->back()->setIntermediateFrequency(SDRPlayIF::getIF(i)); + response.getSdrPlayReport()->getIntermediateFrequencies()->append(new SWGSDRangel::SWGFrequency); + response.getSdrPlayReport()->getIntermediateFrequencies()->back()->setFrequency(SDRPlayIF::getIF(i)); } - response.getSdrPlayReport()->setBandwidths(new QList); + response.getSdrPlayReport()->setBandwidths(new QList); for (unsigned int i = 0; i < SDRPlayBandwidths::getNbBandwidths(); i++) { - response.getSdrPlayReport()->getBandwidths()->append(new SWGSDRangel::SWGSDRPlayReport_bandwidths); + response.getSdrPlayReport()->getBandwidths()->append(new SWGSDRangel::SWGBandwidth); response.getSdrPlayReport()->getBandwidths()->back()->setBandwidth(SDRPlayBandwidths::getBandwidth(i)); } - response.getSdrPlayReport()->setFrequencyBands(new QList); + response.getSdrPlayReport()->setFrequencyBands(new QList); for (unsigned int i = 0; i < SDRPlayBands::getNbBands(); i++) { - response.getSdrPlayReport()->getFrequencyBands()->append(new SWGSDRangel::SWGSDRPlayReport_frequencyBands); - response.getSdrPlayReport()->getFrequencyBands()->back()->setBandName(new QString(SDRPlayBands::getBandName(i))); - response.getSdrPlayReport()->getFrequencyBands()->back()->setBandLow(SDRPlayBands::getBandLow(i)); - response.getSdrPlayReport()->getFrequencyBands()->back()->setBandHigh(SDRPlayBands::getBandHigh(i)); + response.getSdrPlayReport()->getFrequencyBands()->append(new SWGSDRangel::SWGFrequencyBand); + response.getSdrPlayReport()->getFrequencyBands()->back()->setName(new QString(SDRPlayBands::getBandName(i))); + response.getSdrPlayReport()->getFrequencyBands()->back()->setLowerBound(SDRPlayBands::getBandLow(i)); + response.getSdrPlayReport()->getFrequencyBands()->back()->setHigherBound(SDRPlayBands::getBandHigh(i)); } } diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 40ed2f324..b4e926d0b 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -21,8 +21,11 @@ webapi/doc/swagger/include/Perseus.yaml webapi/doc/swagger/include/PlutoSdr.yaml webapi/doc/swagger/include/RtlSdr.yaml + webapi/doc/swagger/include/SDRDaemonSource.yaml + webapi/doc/swagger/include/SDRPlay.yaml webapi/doc/swagger/include/SSBDemod.yaml - webapi/doc/swagger/include/SSBMod.yaml + webapi/doc/swagger/include/SSBMod.yaml + webapi/doc/swagger/include/Structs.yaml webapi/doc/swagger/include/UDPSink.yaml webapi/doc/swagger/include/UDPSrc.yaml webapi/doc/swagger/include/WFMDemod.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 13e3d1406..61e42cd5a 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -917,7 +917,7 @@ margin-bottom: 20px; "sampleRates" : { "type" : "array", "items" : { - "$ref" : "#/definitions/AirspyReport_sampleRates" + "$ref" : "#/definitions/SampleRate" } } }, @@ -959,19 +959,11 @@ margin-bottom: 20px; "sampleRates" : { "type" : "array", "items" : { - "$ref" : "#/definitions/AirspyReport_sampleRates" + "$ref" : "#/definitions/SampleRate" } } }, "description" : "Airspy" -}; - defs.AirspyReport_sampleRates = { - "properties" : { - "sampleRate" : { - "type" : "integer", - "description" : "sample rate in S/s" - } - } }; defs.AirspySettings = { "properties" : { @@ -1212,6 +1204,14 @@ margin-bottom: 20px; } }, "description" : "BFMDemod" +}; + defs.Bandwidth = { + "properties" : { + "bandwidth" : { + "type" : "integer" + } + }, + "description" : "A bandwidth expressed in Hertz (Hz)" }; defs.BladeRFInputSettings = { "properties" : { @@ -2037,6 +2037,36 @@ margin-bottom: 20px; } }, "description" : "FileSource" +}; + defs.Frequency = { + "properties" : { + "frequency" : { + "type" : "integer" + } + }, + "description" : "A frequency expressed in Hertz (Hz)" +}; + defs.FrequencyBand = { + "properties" : { + "name" : { + "type" : "string" + }, + "lowerBound" : { + "type" : "integer" + }, + "higherBound" : { + "type" : "integer" + } + }, + "description" : "A band of frequencies given its boudaries in Hertz (Hz)" +}; + defs.Gain = { + "properties" : { + "gainCB" : { + "type" : "integer" + } + }, + "description" : "A gain expressed in centi-Bels (tenths of dB)" }; defs.HackRFInputSettings = { "properties" : { @@ -2596,7 +2626,7 @@ margin-bottom: 20px; "sampleRates" : { "type" : "array", "items" : { - "$ref" : "#/definitions/AirspyReport_sampleRates" + "$ref" : "#/definitions/SampleRate" } } }, @@ -2994,19 +3024,11 @@ margin-bottom: 20px; "gains" : { "type" : "array", "items" : { - "$ref" : "#/definitions/RtlSdrReport_gains" + "$ref" : "#/definitions/Gain" } } }, "description" : "RTLSDR" -}; - defs.RtlSdrReport_gains = { - "properties" : { - "gain" : { - "type" : "integer", - "description" : "gain in centi Bels" - } - } }; defs.RtlSdrSettings = { "properties" : { @@ -3065,60 +3087,29 @@ margin-bottom: 20px; "sampleRates" : { "type" : "array", "items" : { - "$ref" : "#/definitions/AirspyReport_sampleRates" + "$ref" : "#/definitions/SampleRate" } }, "bandwidths" : { "type" : "array", "items" : { - "$ref" : "#/definitions/SDRPlayReport_bandwidths" + "$ref" : "#/definitions/Bandwidth" } }, "intermediateFrequencies" : { "type" : "array", "items" : { - "$ref" : "#/definitions/SDRPlayReport_intermediateFrequencies" + "$ref" : "#/definitions/Frequency" } }, "frequencyBands" : { "type" : "array", "items" : { - "$ref" : "#/definitions/SDRPlayReport_frequencyBands" + "$ref" : "#/definitions/FrequencyBand" } } }, "description" : "SDRplay1" -}; - defs.SDRPlayReport_bandwidths = { - "properties" : { - "bandwidth" : { - "type" : "integer", - "description" : "bandwidth in Hz" - } - } -}; - defs.SDRPlayReport_frequencyBands = { - "properties" : { - "bandName" : { - "type" : "string" - }, - "bandLow" : { - "type" : "integer", - "description" : "lower frequency bound in Hz" - }, - "bandHigh" : { - "type" : "integer", - "description" : "higher frequency bound in Hz" - } - } -}; - defs.SDRPlayReport_intermediateFrequencies = { - "properties" : { - "intermediateFrequency" : { - "type" : "integer", - "description" : "frequency in Hz" - } - } }; defs.SDRPlaySettings = { "properties" : { @@ -3438,6 +3429,14 @@ margin-bottom: 20px; } }, "description" : "SSBMod" +}; + defs.SampleRate = { + "properties" : { + "rate" : { + "type" : "integer" + } + }, + "description" : "A sample rate expressed in samples per second (S/s)" }; defs.SamplingDevice = { "required" : [ "bandwidth", "centerFrequency", "hwType", "index", "sequence", "serial", "state", "streamIndex" ], @@ -22495,7 +22494,7 @@ except ApiException as e:
    - Generated 2018-05-27T11:38:07.698+02:00 + Generated 2018-05-27T13:20:33.079+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml b/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml index ebb14bd7d..532e6e0e6 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/Airspy.yaml @@ -46,9 +46,5 @@ AirspyReport: sampleRates: type: array items: - properties: - sampleRate: - description: sample rate in S/s - type: integer - + $ref: "/doc/swagger/include/Structs.yaml#/SampleRate" \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml b/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml index e0317e414..023146e7d 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/AirspyHF.yaml @@ -26,9 +26,5 @@ AirspyHFReport: sampleRates: type: array items: - properties: - sampleRate: - description: sample rate in S/s - type: integer - + $ref: "/doc/swagger/include/Structs.yaml#/SampleRate" \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/include/Perseus.yaml b/sdrbase/resources/webapi/doc/swagger/include/Perseus.yaml index 79e075806..a8a8a3881 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/Perseus.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/Perseus.yaml @@ -36,9 +36,5 @@ PerseusReport: sampleRates: type: array items: - properties: - sampleRate: - description: sample rate in S/s - type: integer - + $ref: "/doc/swagger/include/Structs.yaml#/SampleRate" \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml index bc27d1981..566866e1a 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/RtlSdr.yaml @@ -40,7 +40,4 @@ RtlSdrReport: gains: type: array items: - properties: - gain: - description: gain in centi Bels - type: integer + $ref: "/doc/swagger/include/Structs.yaml#/Gain" \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml index a54937f24..10377d623 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml @@ -44,34 +44,16 @@ SDRPlayReport: sampleRates: type: array items: - properties: - sampleRate: - description: sample rate in S/s - type: integer + $ref: "/doc/swagger/include/Structs.yaml#/SampleRate" bandwidths: type: array items: - properties: - bandwidth: - description: bandwidth in Hz - type: integer + $ref: "/doc/swagger/include/Structs.yaml#/Bandwidth" intermediateFrequencies: type: array items: - properties: - intermediateFrequency: - description: frequency in Hz - type: integer + $ref: "/doc/swagger/include/Structs.yaml#/Frequency" frequencyBands: type: array items: - properties: - bandName: - type: string - bandLow: - description: lower frequency bound in Hz - type: integer - bandHigh: - description: higher frequency bound in Hz - type: integer - \ No newline at end of file + $ref: "/doc/swagger/include/Structs.yaml#/FrequencyBand" diff --git a/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml b/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml new file mode 100644 index 000000000..58b6cf8ed --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml @@ -0,0 +1,34 @@ +SampleRate: + description: A sample rate expressed in samples per second (S/s) + properties: + rate: + type: integer + +Bandwidth: + description: A bandwidth expressed in Hertz (Hz) + properties: + bandwidth: + type: integer + +Frequency: + description: A frequency expressed in Hertz (Hz) + properties: + frequency: + type: integer + +FrequencyBand: + description: A band of frequencies given its boudaries in Hertz (Hz) + properties: + name: + type: string + lowerBound: + type: integer + higherBound: + type: integer + +Gain: + description: A gain expressed in centi-Bels (tenths of dB) + properties: + gainCB: + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/Airspy.yaml b/swagger/sdrangel/api/swagger/include/Airspy.yaml index ebb14bd7d..e7f03c8e5 100644 --- a/swagger/sdrangel/api/swagger/include/Airspy.yaml +++ b/swagger/sdrangel/api/swagger/include/Airspy.yaml @@ -46,9 +46,5 @@ AirspyReport: sampleRates: type: array items: - properties: - sampleRate: - description: sample rate in S/s - type: integer - + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/SampleRate" \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/AirspyHF.yaml b/swagger/sdrangel/api/swagger/include/AirspyHF.yaml index e0317e414..2835f21ce 100644 --- a/swagger/sdrangel/api/swagger/include/AirspyHF.yaml +++ b/swagger/sdrangel/api/swagger/include/AirspyHF.yaml @@ -26,9 +26,5 @@ AirspyHFReport: sampleRates: type: array items: - properties: - sampleRate: - description: sample rate in S/s - type: integer - + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/SampleRate" \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/Perseus.yaml b/swagger/sdrangel/api/swagger/include/Perseus.yaml index 79e075806..6ed9052b8 100644 --- a/swagger/sdrangel/api/swagger/include/Perseus.yaml +++ b/swagger/sdrangel/api/swagger/include/Perseus.yaml @@ -36,9 +36,5 @@ PerseusReport: sampleRates: type: array items: - properties: - sampleRate: - description: sample rate in S/s - type: integer - + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/SampleRate" \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/RtlSdr.yaml b/swagger/sdrangel/api/swagger/include/RtlSdr.yaml index bc27d1981..848b5a61f 100644 --- a/swagger/sdrangel/api/swagger/include/RtlSdr.yaml +++ b/swagger/sdrangel/api/swagger/include/RtlSdr.yaml @@ -40,7 +40,4 @@ RtlSdrReport: gains: type: array items: - properties: - gain: - description: gain in centi Bels - type: integer + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Gain" \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/SDRPlay.yaml b/swagger/sdrangel/api/swagger/include/SDRPlay.yaml index a54937f24..3f74929e5 100644 --- a/swagger/sdrangel/api/swagger/include/SDRPlay.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRPlay.yaml @@ -44,34 +44,16 @@ SDRPlayReport: sampleRates: type: array items: - properties: - sampleRate: - description: sample rate in S/s - type: integer + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/SampleRate" bandwidths: type: array items: - properties: - bandwidth: - description: bandwidth in Hz - type: integer + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Bandwidth" intermediateFrequencies: type: array items: - properties: - intermediateFrequency: - description: frequency in Hz - type: integer + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Frequency" frequencyBands: type: array items: - properties: - bandName: - type: string - bandLow: - description: lower frequency bound in Hz - type: integer - bandHigh: - description: higher frequency bound in Hz - type: integer - \ No newline at end of file + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/FrequencyBand" diff --git a/swagger/sdrangel/api/swagger/include/Structs.yaml b/swagger/sdrangel/api/swagger/include/Structs.yaml new file mode 100644 index 000000000..58b6cf8ed --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/Structs.yaml @@ -0,0 +1,34 @@ +SampleRate: + description: A sample rate expressed in samples per second (S/s) + properties: + rate: + type: integer + +Bandwidth: + description: A bandwidth expressed in Hertz (Hz) + properties: + bandwidth: + type: integer + +Frequency: + description: A frequency expressed in Hertz (Hz) + properties: + frequency: + type: integer + +FrequencyBand: + description: A band of frequencies given its boudaries in Hertz (Hz) + properties: + name: + type: string + lowerBound: + type: integer + higherBound: + type: integer + +Gain: + description: A gain expressed in centi-Bels (tenths of dB) + properties: + gainCB: + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 13e3d1406..61e42cd5a 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -917,7 +917,7 @@ margin-bottom: 20px; "sampleRates" : { "type" : "array", "items" : { - "$ref" : "#/definitions/AirspyReport_sampleRates" + "$ref" : "#/definitions/SampleRate" } } }, @@ -959,19 +959,11 @@ margin-bottom: 20px; "sampleRates" : { "type" : "array", "items" : { - "$ref" : "#/definitions/AirspyReport_sampleRates" + "$ref" : "#/definitions/SampleRate" } } }, "description" : "Airspy" -}; - defs.AirspyReport_sampleRates = { - "properties" : { - "sampleRate" : { - "type" : "integer", - "description" : "sample rate in S/s" - } - } }; defs.AirspySettings = { "properties" : { @@ -1212,6 +1204,14 @@ margin-bottom: 20px; } }, "description" : "BFMDemod" +}; + defs.Bandwidth = { + "properties" : { + "bandwidth" : { + "type" : "integer" + } + }, + "description" : "A bandwidth expressed in Hertz (Hz)" }; defs.BladeRFInputSettings = { "properties" : { @@ -2037,6 +2037,36 @@ margin-bottom: 20px; } }, "description" : "FileSource" +}; + defs.Frequency = { + "properties" : { + "frequency" : { + "type" : "integer" + } + }, + "description" : "A frequency expressed in Hertz (Hz)" +}; + defs.FrequencyBand = { + "properties" : { + "name" : { + "type" : "string" + }, + "lowerBound" : { + "type" : "integer" + }, + "higherBound" : { + "type" : "integer" + } + }, + "description" : "A band of frequencies given its boudaries in Hertz (Hz)" +}; + defs.Gain = { + "properties" : { + "gainCB" : { + "type" : "integer" + } + }, + "description" : "A gain expressed in centi-Bels (tenths of dB)" }; defs.HackRFInputSettings = { "properties" : { @@ -2596,7 +2626,7 @@ margin-bottom: 20px; "sampleRates" : { "type" : "array", "items" : { - "$ref" : "#/definitions/AirspyReport_sampleRates" + "$ref" : "#/definitions/SampleRate" } } }, @@ -2994,19 +3024,11 @@ margin-bottom: 20px; "gains" : { "type" : "array", "items" : { - "$ref" : "#/definitions/RtlSdrReport_gains" + "$ref" : "#/definitions/Gain" } } }, "description" : "RTLSDR" -}; - defs.RtlSdrReport_gains = { - "properties" : { - "gain" : { - "type" : "integer", - "description" : "gain in centi Bels" - } - } }; defs.RtlSdrSettings = { "properties" : { @@ -3065,60 +3087,29 @@ margin-bottom: 20px; "sampleRates" : { "type" : "array", "items" : { - "$ref" : "#/definitions/AirspyReport_sampleRates" + "$ref" : "#/definitions/SampleRate" } }, "bandwidths" : { "type" : "array", "items" : { - "$ref" : "#/definitions/SDRPlayReport_bandwidths" + "$ref" : "#/definitions/Bandwidth" } }, "intermediateFrequencies" : { "type" : "array", "items" : { - "$ref" : "#/definitions/SDRPlayReport_intermediateFrequencies" + "$ref" : "#/definitions/Frequency" } }, "frequencyBands" : { "type" : "array", "items" : { - "$ref" : "#/definitions/SDRPlayReport_frequencyBands" + "$ref" : "#/definitions/FrequencyBand" } } }, "description" : "SDRplay1" -}; - defs.SDRPlayReport_bandwidths = { - "properties" : { - "bandwidth" : { - "type" : "integer", - "description" : "bandwidth in Hz" - } - } -}; - defs.SDRPlayReport_frequencyBands = { - "properties" : { - "bandName" : { - "type" : "string" - }, - "bandLow" : { - "type" : "integer", - "description" : "lower frequency bound in Hz" - }, - "bandHigh" : { - "type" : "integer", - "description" : "higher frequency bound in Hz" - } - } -}; - defs.SDRPlayReport_intermediateFrequencies = { - "properties" : { - "intermediateFrequency" : { - "type" : "integer", - "description" : "frequency in Hz" - } - } }; defs.SDRPlaySettings = { "properties" : { @@ -3438,6 +3429,14 @@ margin-bottom: 20px; } }, "description" : "SSBMod" +}; + defs.SampleRate = { + "properties" : { + "rate" : { + "type" : "integer" + } + }, + "description" : "A sample rate expressed in samples per second (S/s)" }; defs.SamplingDevice = { "required" : [ "bandwidth", "centerFrequency", "hwType", "index", "sequence", "serial", "state", "streamIndex" ], @@ -22495,7 +22494,7 @@ except ApiException as e:
    - Generated 2018-05-27T11:38:07.698+02:00 + Generated 2018-05-27T13:20:33.079+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp index e7e1da594..497e318e2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp @@ -38,7 +38,7 @@ SWGAirspyHFReport::~SWGAirspyHFReport() { void SWGAirspyHFReport::init() { - sample_rates = new QList(); + sample_rates = new QList(); m_sample_rates_isSet = false; } @@ -65,7 +65,7 @@ SWGAirspyHFReport::fromJson(QString &json) { void SWGAirspyHFReport::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGAirspyReport_sampleRates"); + ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGSampleRate"); } QString @@ -83,18 +83,18 @@ QJsonObject* SWGAirspyHFReport::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(sample_rates->size() > 0){ - toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGAirspyReport_sampleRates"); + toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGSampleRate"); } return obj; } -QList* +QList* SWGAirspyHFReport::getSampleRates() { return sample_rates; } void -SWGAirspyHFReport::setSampleRates(QList* sample_rates) { +SWGAirspyHFReport::setSampleRates(QList* sample_rates) { this->sample_rates = sample_rates; this->m_sample_rates_isSet = true; } diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h index 668dd2c50..e4e707e31 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h @@ -22,7 +22,7 @@ #include -#include "SWGAirspyReport_sampleRates.h" +#include "SWGSampleRate.h" #include #include "SWGObject.h" @@ -43,14 +43,14 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGAirspyHFReport* fromJson(QString &jsonString) override; - QList* getSampleRates(); - void setSampleRates(QList* sample_rates); + QList* getSampleRates(); + void setSampleRates(QList* sample_rates); virtual bool isSet() override; private: - QList* sample_rates; + QList* sample_rates; bool m_sample_rates_isSet; }; diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp index 21bd1abf5..defe0192e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp @@ -38,7 +38,7 @@ SWGAirspyReport::~SWGAirspyReport() { void SWGAirspyReport::init() { - sample_rates = new QList(); + sample_rates = new QList(); m_sample_rates_isSet = false; } @@ -65,7 +65,7 @@ SWGAirspyReport::fromJson(QString &json) { void SWGAirspyReport::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGAirspyReport_sampleRates"); + ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGSampleRate"); } QString @@ -83,18 +83,18 @@ QJsonObject* SWGAirspyReport::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(sample_rates->size() > 0){ - toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGAirspyReport_sampleRates"); + toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGSampleRate"); } return obj; } -QList* +QList* SWGAirspyReport::getSampleRates() { return sample_rates; } void -SWGAirspyReport::setSampleRates(QList* sample_rates) { +SWGAirspyReport::setSampleRates(QList* sample_rates) { this->sample_rates = sample_rates; this->m_sample_rates_isSet = true; } diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h index 290307372..3bb0f3ee4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h @@ -22,7 +22,7 @@ #include -#include "SWGAirspyReport_sampleRates.h" +#include "SWGSampleRate.h" #include #include "SWGObject.h" @@ -43,14 +43,14 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGAirspyReport* fromJson(QString &jsonString) override; - QList* getSampleRates(); - void setSampleRates(QList* sample_rates); + QList* getSampleRates(); + void setSampleRates(QList* sample_rates); virtual bool isSet() override; private: - QList* sample_rates; + QList* sample_rates; bool m_sample_rates_isSet; }; diff --git a/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp b/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp new file mode 100644 index 000000000..95ba9cf27 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBandwidth.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBandwidth::SWGBandwidth(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBandwidth::SWGBandwidth() { + bandwidth = 0; + m_bandwidth_isSet = false; +} + +SWGBandwidth::~SWGBandwidth() { + this->cleanup(); +} + +void +SWGBandwidth::init() { + bandwidth = 0; + m_bandwidth_isSet = false; +} + +void +SWGBandwidth::cleanup() { + +} + +SWGBandwidth* +SWGBandwidth::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBandwidth::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); + +} + +QString +SWGBandwidth::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBandwidth::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_bandwidth_isSet){ + obj->insert("bandwidth", QJsonValue(bandwidth)); + } + + return obj; +} + +qint32 +SWGBandwidth::getBandwidth() { + return bandwidth; +} +void +SWGBandwidth::setBandwidth(qint32 bandwidth) { + this->bandwidth = bandwidth; + this->m_bandwidth_isSet = true; +} + + +bool +SWGBandwidth::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_bandwidth_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBandwidth.h b/swagger/sdrangel/code/qt5/client/SWGBandwidth.h new file mode 100644 index 000000000..98f885f07 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBandwidth.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBandwidth.h + * + * A bandwidth expressed in Hertz (Hz) + */ + +#ifndef SWGBandwidth_H_ +#define SWGBandwidth_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBandwidth: public SWGObject { +public: + SWGBandwidth(); + SWGBandwidth(QString* json); + virtual ~SWGBandwidth(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBandwidth* fromJson(QString &jsonString) override; + + qint32 getBandwidth(); + void setBandwidth(qint32 bandwidth); + + + virtual bool isSet() override; + +private: + qint32 bandwidth; + bool m_bandwidth_isSet; + +}; + +} + +#endif /* SWGBandwidth_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp b/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp new file mode 100644 index 000000000..41a8e1117 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGFrequency.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGFrequency::SWGFrequency(QString* json) { + init(); + this->fromJson(*json); +} + +SWGFrequency::SWGFrequency() { + frequency = 0; + m_frequency_isSet = false; +} + +SWGFrequency::~SWGFrequency() { + this->cleanup(); +} + +void +SWGFrequency::init() { + frequency = 0; + m_frequency_isSet = false; +} + +void +SWGFrequency::cleanup() { + +} + +SWGFrequency* +SWGFrequency::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGFrequency::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&frequency, pJson["frequency"], "qint32", ""); + +} + +QString +SWGFrequency::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGFrequency::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_frequency_isSet){ + obj->insert("frequency", QJsonValue(frequency)); + } + + return obj; +} + +qint32 +SWGFrequency::getFrequency() { + return frequency; +} +void +SWGFrequency::setFrequency(qint32 frequency) { + this->frequency = frequency; + this->m_frequency_isSet = true; +} + + +bool +SWGFrequency::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_frequency_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequency.h b/swagger/sdrangel/code/qt5/client/SWGFrequency.h new file mode 100644 index 000000000..08718a9ac --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFrequency.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGFrequency.h + * + * A frequency expressed in Hertz (Hz) + */ + +#ifndef SWGFrequency_H_ +#define SWGFrequency_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGFrequency: public SWGObject { +public: + SWGFrequency(); + SWGFrequency(QString* json); + virtual ~SWGFrequency(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGFrequency* fromJson(QString &jsonString) override; + + qint32 getFrequency(); + void setFrequency(qint32 frequency); + + + virtual bool isSet() override; + +private: + qint32 frequency; + bool m_frequency_isSet; + +}; + +} + +#endif /* SWGFrequency_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp new file mode 100644 index 000000000..3c516f363 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp @@ -0,0 +1,150 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGFrequencyBand.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGFrequencyBand::SWGFrequencyBand(QString* json) { + init(); + this->fromJson(*json); +} + +SWGFrequencyBand::SWGFrequencyBand() { + name = nullptr; + m_name_isSet = false; + lower_bound = 0; + m_lower_bound_isSet = false; + higher_bound = 0; + m_higher_bound_isSet = false; +} + +SWGFrequencyBand::~SWGFrequencyBand() { + this->cleanup(); +} + +void +SWGFrequencyBand::init() { + name = new QString(""); + m_name_isSet = false; + lower_bound = 0; + m_lower_bound_isSet = false; + higher_bound = 0; + m_higher_bound_isSet = false; +} + +void +SWGFrequencyBand::cleanup() { + if(name != nullptr) { + delete name; + } + + +} + +SWGFrequencyBand* +SWGFrequencyBand::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGFrequencyBand::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&name, pJson["name"], "QString", "QString"); + + ::SWGSDRangel::setValue(&lower_bound, pJson["lowerBound"], "qint32", ""); + + ::SWGSDRangel::setValue(&higher_bound, pJson["higherBound"], "qint32", ""); + +} + +QString +SWGFrequencyBand::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGFrequencyBand::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(name != nullptr && *name != QString("")){ + toJsonValue(QString("name"), name, obj, QString("QString")); + } + if(m_lower_bound_isSet){ + obj->insert("lowerBound", QJsonValue(lower_bound)); + } + if(m_higher_bound_isSet){ + obj->insert("higherBound", QJsonValue(higher_bound)); + } + + return obj; +} + +QString* +SWGFrequencyBand::getName() { + return name; +} +void +SWGFrequencyBand::setName(QString* name) { + this->name = name; + this->m_name_isSet = true; +} + +qint32 +SWGFrequencyBand::getLowerBound() { + return lower_bound; +} +void +SWGFrequencyBand::setLowerBound(qint32 lower_bound) { + this->lower_bound = lower_bound; + this->m_lower_bound_isSet = true; +} + +qint32 +SWGFrequencyBand::getHigherBound() { + return higher_bound; +} +void +SWGFrequencyBand::setHigherBound(qint32 higher_bound) { + this->higher_bound = higher_bound; + this->m_higher_bound_isSet = true; +} + + +bool +SWGFrequencyBand::isSet(){ + bool isObjectUpdated = false; + do{ + if(name != nullptr && *name != QString("")){ isObjectUpdated = true; break;} + if(m_lower_bound_isSet){ isObjectUpdated = true; break;} + if(m_higher_bound_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h new file mode 100644 index 000000000..39e194249 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h @@ -0,0 +1,71 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGFrequencyBand.h + * + * A band of frequencies given its boudaries in Hertz (Hz) + */ + +#ifndef SWGFrequencyBand_H_ +#define SWGFrequencyBand_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGFrequencyBand: public SWGObject { +public: + SWGFrequencyBand(); + SWGFrequencyBand(QString* json); + virtual ~SWGFrequencyBand(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGFrequencyBand* fromJson(QString &jsonString) override; + + QString* getName(); + void setName(QString* name); + + qint32 getLowerBound(); + void setLowerBound(qint32 lower_bound); + + qint32 getHigherBound(); + void setHigherBound(qint32 higher_bound); + + + virtual bool isSet() override; + +private: + QString* name; + bool m_name_isSet; + + qint32 lower_bound; + bool m_lower_bound_isSet; + + qint32 higher_bound; + bool m_higher_bound_isSet; + +}; + +} + +#endif /* SWGFrequencyBand_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGGain.cpp b/swagger/sdrangel/code/qt5/client/SWGGain.cpp new file mode 100644 index 000000000..2baf4f7ea --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGGain.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGGain.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGGain::SWGGain(QString* json) { + init(); + this->fromJson(*json); +} + +SWGGain::SWGGain() { + gain_cb = 0; + m_gain_cb_isSet = false; +} + +SWGGain::~SWGGain() { + this->cleanup(); +} + +void +SWGGain::init() { + gain_cb = 0; + m_gain_cb_isSet = false; +} + +void +SWGGain::cleanup() { + +} + +SWGGain* +SWGGain::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGGain::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&gain_cb, pJson["gainCB"], "qint32", ""); + +} + +QString +SWGGain::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGGain::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_gain_cb_isSet){ + obj->insert("gainCB", QJsonValue(gain_cb)); + } + + return obj; +} + +qint32 +SWGGain::getGainCb() { + return gain_cb; +} +void +SWGGain::setGainCb(qint32 gain_cb) { + this->gain_cb = gain_cb; + this->m_gain_cb_isSet = true; +} + + +bool +SWGGain::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_gain_cb_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGGain.h b/swagger/sdrangel/code/qt5/client/SWGGain.h new file mode 100644 index 000000000..5be40e4bb --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGGain.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGGain.h + * + * A gain expressed in centi-Bels (tenths of dB) + */ + +#ifndef SWGGain_H_ +#define SWGGain_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGGain: public SWGObject { +public: + SWGGain(); + SWGGain(QString* json); + virtual ~SWGGain(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGGain* fromJson(QString &jsonString) override; + + qint32 getGainCb(); + void setGainCb(qint32 gain_cb); + + + virtual bool isSet() override; + +private: + qint32 gain_cb; + bool m_gain_cb_isSet; + +}; + +} + +#endif /* SWGGain_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index c9f9f2718..0e797f2f4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -23,13 +23,13 @@ #include "SWGAirspyHFReport.h" #include "SWGAirspyHFSettings.h" #include "SWGAirspyReport.h" -#include "SWGAirspyReport_sampleRates.h" #include "SWGAirspySettings.h" #include "SWGAudioDevices.h" #include "SWGAudioInputDevice.h" #include "SWGAudioOutputDevice.h" #include "SWGBFMDemodReport.h" #include "SWGBFMDemodSettings.h" +#include "SWGBandwidth.h" #include "SWGBladeRFInputSettings.h" #include "SWGBladeRFOutputSettings.h" #include "SWGCWKeyerSettings.h" @@ -53,6 +53,9 @@ #include "SWGFCDProSettings.h" #include "SWGFileSourceReport.h" #include "SWGFileSourceSettings.h" +#include "SWGFrequency.h" +#include "SWGFrequencyBand.h" +#include "SWGGain.h" #include "SWGHackRFInputSettings.h" #include "SWGHackRFOutputSettings.h" #include "SWGInstanceChannelsResponse.h" @@ -84,12 +87,8 @@ #include "SWGRDSReport.h" #include "SWGRDSReport_altFrequencies.h" #include "SWGRtlSdrReport.h" -#include "SWGRtlSdrReport_gains.h" #include "SWGRtlSdrSettings.h" #include "SWGSDRPlayReport.h" -#include "SWGSDRPlayReport_bandwidths.h" -#include "SWGSDRPlayReport_frequencyBands.h" -#include "SWGSDRPlayReport_intermediateFrequencies.h" #include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSourceReport.h" #include "SWGSDRdaemonSourceSettings.h" @@ -97,6 +96,7 @@ #include "SWGSSBDemodSettings.h" #include "SWGSSBModReport.h" #include "SWGSSBModSettings.h" +#include "SWGSampleRate.h" #include "SWGSamplingDevice.h" #include "SWGSuccessResponse.h" #include "SWGUDPSinkReport.h" @@ -138,9 +138,6 @@ namespace SWGSDRangel { if(QString("SWGAirspyReport").compare(type) == 0) { return new SWGAirspyReport(); } - if(QString("SWGAirspyReport_sampleRates").compare(type) == 0) { - return new SWGAirspyReport_sampleRates(); - } if(QString("SWGAirspySettings").compare(type) == 0) { return new SWGAirspySettings(); } @@ -159,6 +156,9 @@ namespace SWGSDRangel { if(QString("SWGBFMDemodSettings").compare(type) == 0) { return new SWGBFMDemodSettings(); } + if(QString("SWGBandwidth").compare(type) == 0) { + return new SWGBandwidth(); + } if(QString("SWGBladeRFInputSettings").compare(type) == 0) { return new SWGBladeRFInputSettings(); } @@ -228,6 +228,15 @@ namespace SWGSDRangel { if(QString("SWGFileSourceSettings").compare(type) == 0) { return new SWGFileSourceSettings(); } + if(QString("SWGFrequency").compare(type) == 0) { + return new SWGFrequency(); + } + if(QString("SWGFrequencyBand").compare(type) == 0) { + return new SWGFrequencyBand(); + } + if(QString("SWGGain").compare(type) == 0) { + return new SWGGain(); + } if(QString("SWGHackRFInputSettings").compare(type) == 0) { return new SWGHackRFInputSettings(); } @@ -321,24 +330,12 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrReport").compare(type) == 0) { return new SWGRtlSdrReport(); } - if(QString("SWGRtlSdrReport_gains").compare(type) == 0) { - return new SWGRtlSdrReport_gains(); - } if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } if(QString("SWGSDRPlayReport").compare(type) == 0) { return new SWGSDRPlayReport(); } - if(QString("SWGSDRPlayReport_bandwidths").compare(type) == 0) { - return new SWGSDRPlayReport_bandwidths(); - } - if(QString("SWGSDRPlayReport_frequencyBands").compare(type) == 0) { - return new SWGSDRPlayReport_frequencyBands(); - } - if(QString("SWGSDRPlayReport_intermediateFrequencies").compare(type) == 0) { - return new SWGSDRPlayReport_intermediateFrequencies(); - } if(QString("SWGSDRPlaySettings").compare(type) == 0) { return new SWGSDRPlaySettings(); } @@ -360,6 +357,9 @@ namespace SWGSDRangel { if(QString("SWGSSBModSettings").compare(type) == 0) { return new SWGSSBModSettings(); } + if(QString("SWGSampleRate").compare(type) == 0) { + return new SWGSampleRate(); + } if(QString("SWGSamplingDevice").compare(type) == 0) { return new SWGSamplingDevice(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp index dada1aa3a..8254287b6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp @@ -38,7 +38,7 @@ SWGPerseusReport::~SWGPerseusReport() { void SWGPerseusReport::init() { - sample_rates = new QList(); + sample_rates = new QList(); m_sample_rates_isSet = false; } @@ -65,7 +65,7 @@ SWGPerseusReport::fromJson(QString &json) { void SWGPerseusReport::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGAirspyReport_sampleRates"); + ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGSampleRate"); } QString @@ -83,18 +83,18 @@ QJsonObject* SWGPerseusReport::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(sample_rates->size() > 0){ - toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGAirspyReport_sampleRates"); + toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGSampleRate"); } return obj; } -QList* +QList* SWGPerseusReport::getSampleRates() { return sample_rates; } void -SWGPerseusReport::setSampleRates(QList* sample_rates) { +SWGPerseusReport::setSampleRates(QList* sample_rates) { this->sample_rates = sample_rates; this->m_sample_rates_isSet = true; } diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h index 091090da1..5d118602b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h @@ -22,7 +22,7 @@ #include -#include "SWGAirspyReport_sampleRates.h" +#include "SWGSampleRate.h" #include #include "SWGObject.h" @@ -43,14 +43,14 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGPerseusReport* fromJson(QString &jsonString) override; - QList* getSampleRates(); - void setSampleRates(QList* sample_rates); + QList* getSampleRates(); + void setSampleRates(QList* sample_rates); virtual bool isSet() override; private: - QList* sample_rates; + QList* sample_rates; bool m_sample_rates_isSet; }; diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp index 824ff9dae..61095fbbb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp @@ -38,7 +38,7 @@ SWGRtlSdrReport::~SWGRtlSdrReport() { void SWGRtlSdrReport::init() { - gains = new QList(); + gains = new QList(); m_gains_isSet = false; } @@ -65,7 +65,7 @@ SWGRtlSdrReport::fromJson(QString &json) { void SWGRtlSdrReport::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&gains, pJson["gains"], "QList", "SWGRtlSdrReport_gains"); + ::SWGSDRangel::setValue(&gains, pJson["gains"], "QList", "SWGGain"); } QString @@ -83,18 +83,18 @@ QJsonObject* SWGRtlSdrReport::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(gains->size() > 0){ - toJsonArray((QList*)gains, obj, "gains", "SWGRtlSdrReport_gains"); + toJsonArray((QList*)gains, obj, "gains", "SWGGain"); } return obj; } -QList* +QList* SWGRtlSdrReport::getGains() { return gains; } void -SWGRtlSdrReport::setGains(QList* gains) { +SWGRtlSdrReport::setGains(QList* gains) { this->gains = gains; this->m_gains_isSet = true; } diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h index f6fa9a371..6d2cb4958 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h @@ -22,7 +22,7 @@ #include -#include "SWGRtlSdrReport_gains.h" +#include "SWGGain.h" #include #include "SWGObject.h" @@ -43,14 +43,14 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGRtlSdrReport* fromJson(QString &jsonString) override; - QList* getGains(); - void setGains(QList* gains); + QList* getGains(); + void setGains(QList* gains); virtual bool isSet() override; private: - QList* gains; + QList* gains; bool m_gains_isSet; }; diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp index d339f4a83..e7a429872 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp @@ -44,13 +44,13 @@ SWGSDRPlayReport::~SWGSDRPlayReport() { void SWGSDRPlayReport::init() { - sample_rates = new QList(); + sample_rates = new QList(); m_sample_rates_isSet = false; - bandwidths = new QList(); + bandwidths = new QList(); m_bandwidths_isSet = false; - intermediate_frequencies = new QList(); + intermediate_frequencies = new QList(); m_intermediate_frequencies_isSet = false; - frequency_bands = new QList(); + frequency_bands = new QList(); m_frequency_bands_isSet = false; } @@ -98,13 +98,13 @@ SWGSDRPlayReport::fromJson(QString &json) { void SWGSDRPlayReport::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGAirspyReport_sampleRates"); + ::SWGSDRangel::setValue(&sample_rates, pJson["sampleRates"], "QList", "SWGSampleRate"); - ::SWGSDRangel::setValue(&bandwidths, pJson["bandwidths"], "QList", "SWGSDRPlayReport_bandwidths"); + ::SWGSDRangel::setValue(&bandwidths, pJson["bandwidths"], "QList", "SWGBandwidth"); - ::SWGSDRangel::setValue(&intermediate_frequencies, pJson["intermediateFrequencies"], "QList", "SWGSDRPlayReport_intermediateFrequencies"); + ::SWGSDRangel::setValue(&intermediate_frequencies, pJson["intermediateFrequencies"], "QList", "SWGFrequency"); - ::SWGSDRangel::setValue(&frequency_bands, pJson["frequencyBands"], "QList", "SWGSDRPlayReport_frequencyBands"); + ::SWGSDRangel::setValue(&frequency_bands, pJson["frequencyBands"], "QList", "SWGFrequencyBand"); } QString @@ -122,57 +122,57 @@ QJsonObject* SWGSDRPlayReport::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(sample_rates->size() > 0){ - toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGAirspyReport_sampleRates"); + toJsonArray((QList*)sample_rates, obj, "sampleRates", "SWGSampleRate"); } if(bandwidths->size() > 0){ - toJsonArray((QList*)bandwidths, obj, "bandwidths", "SWGSDRPlayReport_bandwidths"); + toJsonArray((QList*)bandwidths, obj, "bandwidths", "SWGBandwidth"); } if(intermediate_frequencies->size() > 0){ - toJsonArray((QList*)intermediate_frequencies, obj, "intermediateFrequencies", "SWGSDRPlayReport_intermediateFrequencies"); + toJsonArray((QList*)intermediate_frequencies, obj, "intermediateFrequencies", "SWGFrequency"); } if(frequency_bands->size() > 0){ - toJsonArray((QList*)frequency_bands, obj, "frequencyBands", "SWGSDRPlayReport_frequencyBands"); + toJsonArray((QList*)frequency_bands, obj, "frequencyBands", "SWGFrequencyBand"); } return obj; } -QList* +QList* SWGSDRPlayReport::getSampleRates() { return sample_rates; } void -SWGSDRPlayReport::setSampleRates(QList* sample_rates) { +SWGSDRPlayReport::setSampleRates(QList* sample_rates) { this->sample_rates = sample_rates; this->m_sample_rates_isSet = true; } -QList* +QList* SWGSDRPlayReport::getBandwidths() { return bandwidths; } void -SWGSDRPlayReport::setBandwidths(QList* bandwidths) { +SWGSDRPlayReport::setBandwidths(QList* bandwidths) { this->bandwidths = bandwidths; this->m_bandwidths_isSet = true; } -QList* +QList* SWGSDRPlayReport::getIntermediateFrequencies() { return intermediate_frequencies; } void -SWGSDRPlayReport::setIntermediateFrequencies(QList* intermediate_frequencies) { +SWGSDRPlayReport::setIntermediateFrequencies(QList* intermediate_frequencies) { this->intermediate_frequencies = intermediate_frequencies; this->m_intermediate_frequencies_isSet = true; } -QList* +QList* SWGSDRPlayReport::getFrequencyBands() { return frequency_bands; } void -SWGSDRPlayReport::setFrequencyBands(QList* frequency_bands) { +SWGSDRPlayReport::setFrequencyBands(QList* frequency_bands) { this->frequency_bands = frequency_bands; this->m_frequency_bands_isSet = true; } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h index 3fd62716c..4a9590c46 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h @@ -22,10 +22,10 @@ #include -#include "SWGAirspyReport_sampleRates.h" -#include "SWGSDRPlayReport_bandwidths.h" -#include "SWGSDRPlayReport_frequencyBands.h" -#include "SWGSDRPlayReport_intermediateFrequencies.h" +#include "SWGBandwidth.h" +#include "SWGFrequency.h" +#include "SWGFrequencyBand.h" +#include "SWGSampleRate.h" #include #include "SWGObject.h" @@ -46,32 +46,32 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGSDRPlayReport* fromJson(QString &jsonString) override; - QList* getSampleRates(); - void setSampleRates(QList* sample_rates); + QList* getSampleRates(); + void setSampleRates(QList* sample_rates); - QList* getBandwidths(); - void setBandwidths(QList* bandwidths); + QList* getBandwidths(); + void setBandwidths(QList* bandwidths); - QList* getIntermediateFrequencies(); - void setIntermediateFrequencies(QList* intermediate_frequencies); + QList* getIntermediateFrequencies(); + void setIntermediateFrequencies(QList* intermediate_frequencies); - QList* getFrequencyBands(); - void setFrequencyBands(QList* frequency_bands); + QList* getFrequencyBands(); + void setFrequencyBands(QList* frequency_bands); virtual bool isSet() override; private: - QList* sample_rates; + QList* sample_rates; bool m_sample_rates_isSet; - QList* bandwidths; + QList* bandwidths; bool m_bandwidths_isSet; - QList* intermediate_frequencies; + QList* intermediate_frequencies; bool m_intermediate_frequencies_isSet; - QList* frequency_bands; + QList* frequency_bands; bool m_frequency_bands_isSet; }; diff --git a/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp b/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp new file mode 100644 index 000000000..e3316dd27 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSampleRate.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSampleRate::SWGSampleRate(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSampleRate::SWGSampleRate() { + rate = 0; + m_rate_isSet = false; +} + +SWGSampleRate::~SWGSampleRate() { + this->cleanup(); +} + +void +SWGSampleRate::init() { + rate = 0; + m_rate_isSet = false; +} + +void +SWGSampleRate::cleanup() { + +} + +SWGSampleRate* +SWGSampleRate::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSampleRate::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&rate, pJson["rate"], "qint32", ""); + +} + +QString +SWGSampleRate::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSampleRate::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_rate_isSet){ + obj->insert("rate", QJsonValue(rate)); + } + + return obj; +} + +qint32 +SWGSampleRate::getRate() { + return rate; +} +void +SWGSampleRate::setRate(qint32 rate) { + this->rate = rate; + this->m_rate_isSet = true; +} + + +bool +SWGSampleRate::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSampleRate.h b/swagger/sdrangel/code/qt5/client/SWGSampleRate.h new file mode 100644 index 000000000..11bd10e8a --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSampleRate.h @@ -0,0 +1,58 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSampleRate.h + * + * A sample rate expressed in samples per second (S/s) + */ + +#ifndef SWGSampleRate_H_ +#define SWGSampleRate_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSampleRate: public SWGObject { +public: + SWGSampleRate(); + SWGSampleRate(QString* json); + virtual ~SWGSampleRate(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSampleRate* fromJson(QString &jsonString) override; + + qint32 getRate(); + void setRate(qint32 rate); + + + virtual bool isSet() override; + +private: + qint32 rate; + bool m_rate_isSet; + +}; + +} + +#endif /* SWGSampleRate_H_ */ From da3cb2d981add96c9400c9a1b9444b04e1e41e48 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 27 May 2018 20:15:55 +0200 Subject: [PATCH 477/956] SDRPlay input: implemeted WEB API (2) --- plugins/samplesource/sdrplay/sdrplayinput.cpp | 108 ++++++++++++++++++ .../sdrangel/api/swagger/include/SDRPlay.yaml | 2 - 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index c43f9bba9..781ef22b3 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -650,6 +650,114 @@ int SDRPlayInput::webapiRun( return 200; } +int SDRPlayInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrPlaySettings(new SWGSDRangel::SWGSDRPlaySettings()); + response.getSdrPlaySettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int SDRPlayInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + SDRPlaySettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getSdrPlaySettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("tunerGain")) { + settings.m_tunerGain = response.getSdrPlaySettings()->getTunerGain(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getSdrPlaySettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("frequencyBandIndex")) { + settings.m_frequencyBandIndex = response.getSdrPlaySettings()->getFrequencyBandIndex(); + } + if (deviceSettingsKeys.contains("ifFrequencyIndex")) { + settings.m_ifFrequencyIndex = response.getSdrPlaySettings()->getIfFrequencyIndex(); + } + if (deviceSettingsKeys.contains("bandwidthIndex")) { + settings.m_bandwidthIndex = response.getSdrPlaySettings()->getBandwidthIndex(); + } + if (deviceSettingsKeys.contains("devSampleRateIndex")) { + settings.m_devSampleRateIndex = response.getSdrPlaySettings()->getDevSampleRateIndex(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getSdrPlaySettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) + { + int fcPos = response.getSdrPlaySettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (SDRPlaySettings::fcPos_t) fcPos; + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getSdrPlaySettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getSdrPlaySettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("tunerGainMode")) { + settings.m_tunerGainMode = response.getSdrPlaySettings()->getTunerGainMode() != 0; + } + if (deviceSettingsKeys.contains("lnaOn")) { + settings.m_lnaOn = response.getSdrPlaySettings()->getLnaOn() != 0; + } + if (deviceSettingsKeys.contains("mixerAmpOn")) { + settings.m_mixerAmpOn = response.getSdrPlaySettings()->getMixerAmpOn() != 0; + } + if (deviceSettingsKeys.contains("basebandGain")) { + settings.m_basebandGain = response.getSdrPlaySettings()->getBasebandGain(); + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getRtlSdrSettings()->getFileRecordName(); + } + + MsgConfigureSDRPlay *msg = MsgConfigureSDRPlay::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRPlay *msgToGUI = MsgConfigureSDRPlay::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void SDRPlayInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRPlaySettings& settings) +{ + response.getSdrPlaySettings()->setCenterFrequency(settings.m_centerFrequency); + response.getSdrPlaySettings()->setTunerGain(settings.m_tunerGain); + response.getSdrPlaySettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getSdrPlaySettings()->setFrequencyBandIndex(settings.m_frequencyBandIndex); + response.getSdrPlaySettings()->setIfFrequencyIndex(settings.m_ifFrequencyIndex); + response.getSdrPlaySettings()->setBandwidthIndex(settings.m_bandwidthIndex); + response.getSdrPlaySettings()->setDevSampleRateIndex(settings.m_devSampleRateIndex); + response.getSdrPlaySettings()->setLog2Decim(settings.m_log2Decim); + response.getSdrPlaySettings()->setFcPos((int) settings.m_fcPos); + response.getSdrPlaySettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getSdrPlaySettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getSdrPlaySettings()->setTunerGainMode((int) settings.m_tunerGainMode); + response.getSdrPlaySettings()->setLnaOn(settings.m_lnaOn ? 1 : 0); + response.getSdrPlaySettings()->setMixerAmpOn(settings.m_mixerAmpOn ? 1 : 0); + response.getSdrPlaySettings()->setBasebandGain(settings.m_basebandGain); + + if (response.getSdrPlaySettings()->getFileRecordName()) { + *response.getSdrPlaySettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getSdrPlaySettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + int SDRPlayInput::webapiReportGet( SWGSDRangel::SWGDeviceReport& response, QString& errorMessage __attribute__((unused))) diff --git a/swagger/sdrangel/api/swagger/include/SDRPlay.yaml b/swagger/sdrangel/api/swagger/include/SDRPlay.yaml index 3f74929e5..e4ae5f13c 100644 --- a/swagger/sdrangel/api/swagger/include/SDRPlay.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRPlay.yaml @@ -33,8 +33,6 @@ SDRPlaySettings: type: integer basebandGain: type: integer - iqCorrection: - type: integer fileRecordName: type: string From f5bcbf2e9e8aa52107110379c78469d6a94ab262 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 27 May 2018 22:25:01 +0200 Subject: [PATCH 478/956] Test source: implemeted WEB API --- .../testsource/testsourceinput.cpp | 118 +++++ .../samplesource/testsource/testsourceinput.h | 11 + .../testsource/testsourceplugin.cpp | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 66 ++- .../webapi/doc/swagger/include/SDRPlay.yaml | 2 - .../doc/swagger/include/TestSource.yaml | 43 ++ .../resources/webapi/doc/swagger/swagger.yaml | 2 + .../api/swagger/include/TestSource.yaml | 43 ++ swagger/sdrangel/api/swagger/swagger.yaml | 2 + swagger/sdrangel/code/html2/index.html | 66 ++- .../code/qt5/client/SWGDeviceSettings.cpp | 23 + .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 4 + .../code/qt5/client/SWGTestSourceSettings.cpp | 444 ++++++++++++++++++ .../code/qt5/client/SWGTestSourceSettings.h | 155 ++++++ 15 files changed, 983 insertions(+), 5 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/TestSource.yaml create mode 100644 swagger/sdrangel/api/swagger/include/TestSource.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 382d966c9..74cbe0163 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -403,3 +403,121 @@ int TestSourceInput::webapiRun( return 200; } + +int TestSourceInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setTestSourceSettings(new SWGSDRangel::SWGTestSourceSettings()); + response.getTestSourceSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int TestSourceInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + TestSourceSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getTestSourceSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("frequencyShift")) { + settings.m_frequencyShift = response.getTestSourceSettings()->getFrequencyShift(); + } + if (deviceSettingsKeys.contains("sampleRate")) { + settings.m_sampleRate = response.getTestSourceSettings()->getSampleRate(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getTestSourceSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) { + int fcPos = response.getTestSourceSettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (TestSourceSettings::fcPos_t) fcPos; + } + if (deviceSettingsKeys.contains("sampleSizeIndex")) { + int sampleSizeIndex = response.getTestSourceSettings()->getSampleSizeIndex(); + sampleSizeIndex = sampleSizeIndex < 0 ? 0 : sampleSizeIndex > 1 ? 2 : sampleSizeIndex; + settings.m_sampleSizeIndex = sampleSizeIndex; + } + if (deviceSettingsKeys.contains("amplitudeBits")) { + settings.m_amplitudeBits = response.getTestSourceSettings()->getAmplitudeBits(); + } + if (deviceSettingsKeys.contains("autoCorrOptions")) { + int autoCorrOptions = response.getTestSourceSettings()->getAutoCorrOptions(); + autoCorrOptions = autoCorrOptions < 0 ? 0 : autoCorrOptions >= TestSourceSettings::AutoCorrLast ? TestSourceSettings::AutoCorrLast-1 : autoCorrOptions; + settings.m_sampleSizeIndex = (TestSourceSettings::AutoCorrOptions) autoCorrOptions; + } + if (deviceSettingsKeys.contains("modulation")) { + int modulation = response.getTestSourceSettings()->getModulation(); + modulation = modulation < 0 ? 0 : modulation >= TestSourceSettings::ModulationLast ? TestSourceSettings::ModulationLast-1 : modulation; + settings.m_modulation = (TestSourceSettings::Modulation) modulation; + } + if (deviceSettingsKeys.contains("modulationTone")) { + settings.m_modulationTone = response.getTestSourceSettings()->getModulationTone(); + } + if (deviceSettingsKeys.contains("amModulation")) { + settings.m_amModulation = response.getTestSourceSettings()->getAmModulation(); + }; + if (deviceSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getTestSourceSettings()->getFmDeviation(); + }; + if (deviceSettingsKeys.contains("dcFactor")) { + settings.m_dcFactor = response.getTestSourceSettings()->getDcFactor(); + }; + if (deviceSettingsKeys.contains("iFactor")) { + settings.m_iFactor = response.getTestSourceSettings()->getIFactor(); + }; + if (deviceSettingsKeys.contains("qFactor")) { + settings.m_qFactor = response.getTestSourceSettings()->getQFactor(); + }; + if (deviceSettingsKeys.contains("phaseImbalance")) { + settings.m_phaseImbalance = response.getTestSourceSettings()->getPhaseImbalance(); + }; + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getTestSourceSettings()->getFileRecordName(); + } + + MsgConfigureTestSource *msg = MsgConfigureTestSource::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureTestSource *msgToGUI = MsgConfigureTestSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void TestSourceInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const TestSourceSettings& settings) +{ + response.getTestSourceSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getTestSourceSettings()->setFrequencyShift(settings.m_frequencyShift); + response.getTestSourceSettings()->setSampleRate(settings.m_sampleRate); + response.getTestSourceSettings()->setLog2Decim(settings.m_log2Decim); + response.getTestSourceSettings()->setFcPos((int) settings.m_fcPos); + response.getTestSourceSettings()->setSampleSizeIndex((int) settings.m_sampleSizeIndex); + response.getTestSourceSettings()->setAmplitudeBits(settings.m_amplitudeBits); + response.getTestSourceSettings()->setAutoCorrOptions((int) settings.m_autoCorrOptions); + response.getTestSourceSettings()->setModulation((int) settings.m_modulation); + response.getTestSourceSettings()->setModulationTone(settings.m_modulationTone); + response.getTestSourceSettings()->setAmModulation(settings.m_amModulation); + response.getTestSourceSettings()->setFmDeviation(settings.m_fmDeviation); + response.getTestSourceSettings()->setDcFactor(settings.m_dcFactor); + response.getTestSourceSettings()->setIFactor(settings.m_iFactor); + response.getTestSourceSettings()->setQFactor(settings.m_qFactor); + response.getTestSourceSettings()->setPhaseImbalance(settings.m_phaseImbalance); + + if (response.getTestSourceSettings()->getFileRecordName()) { + *response.getTestSourceSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getTestSourceSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + diff --git a/plugins/samplesource/testsource/testsourceinput.h b/plugins/samplesource/testsource/testsourceinput.h index e69624604..6938f7b52 100644 --- a/plugins/samplesource/testsource/testsourceinput.h +++ b/plugins/samplesource/testsource/testsourceinput.h @@ -110,6 +110,16 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -130,6 +140,7 @@ private: const QTimer& m_masterTimer; bool applySettings(const TestSourceSettings& settings, bool force); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const TestSourceSettings& settings); }; #endif // _TESTSOURCE_TESTSOURCEINPUT_H_ diff --git a/plugins/samplesource/testsource/testsourceplugin.cpp b/plugins/samplesource/testsource/testsourceplugin.cpp index 6245ec0f9..f0994e203 100644 --- a/plugins/samplesource/testsource/testsourceplugin.cpp +++ b/plugins/samplesource/testsource/testsourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor TestSourcePlugin::m_pluginDescriptor = { QString("Test Source input"), - QString("3.14.6"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 61e42cd5a..8be095293 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1850,6 +1850,9 @@ margin-bottom: 20px; }, "sdrPlaySettings" : { "$ref" : "#/definitions/SDRPlaySettings" + }, + "testSourceSettings" : { + "$ref" : "#/definitions/TestSourceSettings" } }, "description" : "Base device settings. The specific device settings present depends on deviceHwType." @@ -3492,6 +3495,67 @@ margin-bottom: 20px; "type" : "string" } } +}; + defs.TestSourceSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "frequencyShift" : { + "type" : "integer" + }, + "sampleRate" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer" + }, + "sampleSizeIndex" : { + "type" : "integer" + }, + "amplitudeBits" : { + "type" : "integer" + }, + "autoCorrOptions" : { + "type" : "integer" + }, + "modulation" : { + "type" : "integer" + }, + "modulationTone" : { + "type" : "integer" + }, + "amModulation" : { + "type" : "integer" + }, + "fmDeviation" : { + "type" : "integer" + }, + "dcFactor" : { + "type" : "number", + "format" : "float" + }, + "iFactor" : { + "type" : "number", + "format" : "float" + }, + "qFactor" : { + "type" : "number", + "format" : "float" + }, + "phaseImbalance" : { + "type" : "number", + "format" : "float" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "TestSource" }; defs.UDPSinkReport = { "properties" : { @@ -22494,7 +22558,7 @@ except ApiException as e:
    - Generated 2018-05-27T13:20:33.079+02:00 + Generated 2018-05-27T20:33:10.219+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml index 10377d623..7aac12513 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRPlay.yaml @@ -33,8 +33,6 @@ SDRPlaySettings: type: integer basebandGain: type: integer - iqCorrection: - type: integer fileRecordName: type: string diff --git a/sdrbase/resources/webapi/doc/swagger/include/TestSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/TestSource.yaml new file mode 100644 index 000000000..d5c88645c --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/TestSource.yaml @@ -0,0 +1,43 @@ +TestSourceSettings: + description: TestSource + properties: + centerFrequency: + type: integer + format: uint64 + frequencyShift: + type: integer + sampleRate: + type: integer + log2Decim: + type: integer + fcPos: + type: integer + sampleSizeIndex: + type: integer + amplitudeBits: + type: integer + autoCorrOptions: + type: integer + modulation: + type: integer + modulationTone: + type: integer + amModulation: + type: integer + fmDeviation: + type: integer + dcFactor: + type: number + format: float + iFactor: + type: number + format: float + qFactor: + type: number + format: float + phaseImbalance: + type: number + format: float + fileRecordName: + type: string + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 5ef701b74..4ee62b184 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1779,6 +1779,8 @@ definitions: $ref: "/doc/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceSettings" sdrPlaySettings: $ref: "/doc/swagger/include/SDRPlay.yaml#/SDRPlaySettings" + testSourceSettings: + $ref: "/doc/swagger/include/TestSource.yaml#/TestSourceSettings" DeviceReport: diff --git a/swagger/sdrangel/api/swagger/include/TestSource.yaml b/swagger/sdrangel/api/swagger/include/TestSource.yaml new file mode 100644 index 000000000..d5c88645c --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/TestSource.yaml @@ -0,0 +1,43 @@ +TestSourceSettings: + description: TestSource + properties: + centerFrequency: + type: integer + format: uint64 + frequencyShift: + type: integer + sampleRate: + type: integer + log2Decim: + type: integer + fcPos: + type: integer + sampleSizeIndex: + type: integer + amplitudeBits: + type: integer + autoCorrOptions: + type: integer + modulation: + type: integer + modulationTone: + type: integer + amModulation: + type: integer + fmDeviation: + type: integer + dcFactor: + type: number + format: float + iFactor: + type: number + format: float + qFactor: + type: number + format: float + phaseImbalance: + type: number + format: float + fileRecordName: + type: string + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index dade77108..f4253b5a7 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1779,6 +1779,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceSettings" sdrPlaySettings: $ref: "http://localhost:8081/api/swagger/include/SDRPlay.yaml#/SDRPlaySettings" + testSourceSettings: + $ref: "http://localhost:8081/api/swagger/include/TestSource.yaml#/TestSourceSettings" DeviceReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 61e42cd5a..8be095293 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1850,6 +1850,9 @@ margin-bottom: 20px; }, "sdrPlaySettings" : { "$ref" : "#/definitions/SDRPlaySettings" + }, + "testSourceSettings" : { + "$ref" : "#/definitions/TestSourceSettings" } }, "description" : "Base device settings. The specific device settings present depends on deviceHwType." @@ -3492,6 +3495,67 @@ margin-bottom: 20px; "type" : "string" } } +}; + defs.TestSourceSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "frequencyShift" : { + "type" : "integer" + }, + "sampleRate" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer" + }, + "sampleSizeIndex" : { + "type" : "integer" + }, + "amplitudeBits" : { + "type" : "integer" + }, + "autoCorrOptions" : { + "type" : "integer" + }, + "modulation" : { + "type" : "integer" + }, + "modulationTone" : { + "type" : "integer" + }, + "amModulation" : { + "type" : "integer" + }, + "fmDeviation" : { + "type" : "integer" + }, + "dcFactor" : { + "type" : "number", + "format" : "float" + }, + "iFactor" : { + "type" : "number", + "format" : "float" + }, + "qFactor" : { + "type" : "number", + "format" : "float" + }, + "phaseImbalance" : { + "type" : "number", + "format" : "float" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "TestSource" }; defs.UDPSinkReport = { "properties" : { @@ -22494,7 +22558,7 @@ except ApiException as e:
    - Generated 2018-05-27T13:20:33.079+02:00 + Generated 2018-05-27T20:33:10.219+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index d3322d466..7ccda43c1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -66,6 +66,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_sdr_daemon_source_settings_isSet = false; sdr_play_settings = nullptr; m_sdr_play_settings_isSet = false; + test_source_settings = nullptr; + m_test_source_settings_isSet = false; } SWGDeviceSettings::~SWGDeviceSettings() { @@ -112,6 +114,8 @@ SWGDeviceSettings::init() { m_sdr_daemon_source_settings_isSet = false; sdr_play_settings = new SWGSDRPlaySettings(); m_sdr_play_settings_isSet = false; + test_source_settings = new SWGTestSourceSettings(); + m_test_source_settings_isSet = false; } void @@ -171,6 +175,9 @@ SWGDeviceSettings::cleanup() { if(sdr_play_settings != nullptr) { delete sdr_play_settings; } + if(test_source_settings != nullptr) { + delete test_source_settings; + } } SWGDeviceSettings* @@ -222,6 +229,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&sdr_play_settings, pJson["sdrPlaySettings"], "SWGSDRPlaySettings", "SWGSDRPlaySettings"); + ::SWGSDRangel::setValue(&test_source_settings, pJson["testSourceSettings"], "SWGTestSourceSettings", "SWGTestSourceSettings"); + } QString @@ -295,6 +304,9 @@ SWGDeviceSettings::asJsonObject() { if((sdr_play_settings != nullptr) && (sdr_play_settings->isSet())){ toJsonValue(QString("sdrPlaySettings"), sdr_play_settings, obj, QString("SWGSDRPlaySettings")); } + if((test_source_settings != nullptr) && (test_source_settings->isSet())){ + toJsonValue(QString("testSourceSettings"), test_source_settings, obj, QString("SWGTestSourceSettings")); + } return obj; } @@ -489,6 +501,16 @@ SWGDeviceSettings::setSdrPlaySettings(SWGSDRPlaySettings* sdr_play_settings) { this->m_sdr_play_settings_isSet = true; } +SWGTestSourceSettings* +SWGDeviceSettings::getTestSourceSettings() { + return test_source_settings; +} +void +SWGDeviceSettings::setTestSourceSettings(SWGTestSourceSettings* test_source_settings) { + this->test_source_settings = test_source_settings; + this->m_test_source_settings_isSet = true; +} + bool SWGDeviceSettings::isSet(){ @@ -513,6 +535,7 @@ SWGDeviceSettings::isSet(){ if(rtl_sdr_settings != nullptr && rtl_sdr_settings->isSet()){ isObjectUpdated = true; break;} if(sdr_daemon_source_settings != nullptr && sdr_daemon_source_settings->isSet()){ isObjectUpdated = true; break;} if(sdr_play_settings != nullptr && sdr_play_settings->isSet()){ isObjectUpdated = true; break;} + if(test_source_settings != nullptr && test_source_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 784d4d05d..c07994698 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -39,6 +39,7 @@ #include "SWGRtlSdrSettings.h" #include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSourceSettings.h" +#include "SWGTestSourceSettings.h" #include #include "SWGObject.h" @@ -116,6 +117,9 @@ public: SWGSDRPlaySettings* getSdrPlaySettings(); void setSdrPlaySettings(SWGSDRPlaySettings* sdr_play_settings); + SWGTestSourceSettings* getTestSourceSettings(); + void setTestSourceSettings(SWGTestSourceSettings* test_source_settings); + virtual bool isSet() override; @@ -177,6 +181,9 @@ private: SWGSDRPlaySettings* sdr_play_settings; bool m_sdr_play_settings_isSet; + SWGTestSourceSettings* test_source_settings; + bool m_test_source_settings_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 0e797f2f4..ccfba90e6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -99,6 +99,7 @@ #include "SWGSampleRate.h" #include "SWGSamplingDevice.h" #include "SWGSuccessResponse.h" +#include "SWGTestSourceSettings.h" #include "SWGUDPSinkReport.h" #include "SWGUDPSinkSettings.h" #include "SWGUDPSrcReport.h" @@ -366,6 +367,9 @@ namespace SWGSDRangel { if(QString("SWGSuccessResponse").compare(type) == 0) { return new SWGSuccessResponse(); } + if(QString("SWGTestSourceSettings").compare(type) == 0) { + return new SWGTestSourceSettings(); + } if(QString("SWGUDPSinkReport").compare(type) == 0) { return new SWGUDPSinkReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp new file mode 100644 index 000000000..cde7b0bf4 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp @@ -0,0 +1,444 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGTestSourceSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGTestSourceSettings::SWGTestSourceSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGTestSourceSettings::SWGTestSourceSettings() { + center_frequency = 0; + m_center_frequency_isSet = false; + frequency_shift = 0; + m_frequency_shift_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + sample_size_index = 0; + m_sample_size_index_isSet = false; + amplitude_bits = 0; + m_amplitude_bits_isSet = false; + auto_corr_options = 0; + m_auto_corr_options_isSet = false; + modulation = 0; + m_modulation_isSet = false; + modulation_tone = 0; + m_modulation_tone_isSet = false; + am_modulation = 0; + m_am_modulation_isSet = false; + fm_deviation = 0; + m_fm_deviation_isSet = false; + dc_factor = 0.0f; + m_dc_factor_isSet = false; + i_factor = 0.0f; + m_i_factor_isSet = false; + q_factor = 0.0f; + m_q_factor_isSet = false; + phase_imbalance = 0.0f; + m_phase_imbalance_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; +} + +SWGTestSourceSettings::~SWGTestSourceSettings() { + this->cleanup(); +} + +void +SWGTestSourceSettings::init() { + center_frequency = 0; + m_center_frequency_isSet = false; + frequency_shift = 0; + m_frequency_shift_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + sample_size_index = 0; + m_sample_size_index_isSet = false; + amplitude_bits = 0; + m_amplitude_bits_isSet = false; + auto_corr_options = 0; + m_auto_corr_options_isSet = false; + modulation = 0; + m_modulation_isSet = false; + modulation_tone = 0; + m_modulation_tone_isSet = false; + am_modulation = 0; + m_am_modulation_isSet = false; + fm_deviation = 0; + m_fm_deviation_isSet = false; + dc_factor = 0.0f; + m_dc_factor_isSet = false; + i_factor = 0.0f; + m_i_factor_isSet = false; + q_factor = 0.0f; + m_q_factor_isSet = false; + phase_imbalance = 0.0f; + m_phase_imbalance_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; +} + +void +SWGTestSourceSettings::cleanup() { + + + + + + + + + + + + + + + + + if(file_record_name != nullptr) { + delete file_record_name; + } +} + +SWGTestSourceSettings* +SWGTestSourceSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGTestSourceSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint32", ""); + + ::SWGSDRangel::setValue(&frequency_shift, pJson["frequencyShift"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&fc_pos, pJson["fcPos"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_size_index, pJson["sampleSizeIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&litude_bits, pJson["amplitudeBits"], "qint32", ""); + + ::SWGSDRangel::setValue(&auto_corr_options, pJson["autoCorrOptions"], "qint32", ""); + + ::SWGSDRangel::setValue(&modulation, pJson["modulation"], "qint32", ""); + + ::SWGSDRangel::setValue(&modulation_tone, pJson["modulationTone"], "qint32", ""); + + ::SWGSDRangel::setValue(&am_modulation, pJson["amModulation"], "qint32", ""); + + ::SWGSDRangel::setValue(&fm_deviation, pJson["fmDeviation"], "qint32", ""); + + ::SWGSDRangel::setValue(&dc_factor, pJson["dcFactor"], "float", ""); + + ::SWGSDRangel::setValue(&i_factor, pJson["iFactor"], "float", ""); + + ::SWGSDRangel::setValue(&q_factor, pJson["qFactor"], "float", ""); + + ::SWGSDRangel::setValue(&phase_imbalance, pJson["phaseImbalance"], "float", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + +} + +QString +SWGTestSourceSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGTestSourceSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_frequency_shift_isSet){ + obj->insert("frequencyShift", QJsonValue(frequency_shift)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + if(m_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_fc_pos_isSet){ + obj->insert("fcPos", QJsonValue(fc_pos)); + } + if(m_sample_size_index_isSet){ + obj->insert("sampleSizeIndex", QJsonValue(sample_size_index)); + } + if(m_amplitude_bits_isSet){ + obj->insert("amplitudeBits", QJsonValue(amplitude_bits)); + } + if(m_auto_corr_options_isSet){ + obj->insert("autoCorrOptions", QJsonValue(auto_corr_options)); + } + if(m_modulation_isSet){ + obj->insert("modulation", QJsonValue(modulation)); + } + if(m_modulation_tone_isSet){ + obj->insert("modulationTone", QJsonValue(modulation_tone)); + } + if(m_am_modulation_isSet){ + obj->insert("amModulation", QJsonValue(am_modulation)); + } + if(m_fm_deviation_isSet){ + obj->insert("fmDeviation", QJsonValue(fm_deviation)); + } + if(m_dc_factor_isSet){ + obj->insert("dcFactor", QJsonValue(dc_factor)); + } + if(m_i_factor_isSet){ + obj->insert("iFactor", QJsonValue(i_factor)); + } + if(m_q_factor_isSet){ + obj->insert("qFactor", QJsonValue(q_factor)); + } + if(m_phase_imbalance_isSet){ + obj->insert("phaseImbalance", QJsonValue(phase_imbalance)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + + return obj; +} + +qint32 +SWGTestSourceSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGTestSourceSettings::setCenterFrequency(qint32 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGTestSourceSettings::getFrequencyShift() { + return frequency_shift; +} +void +SWGTestSourceSettings::setFrequencyShift(qint32 frequency_shift) { + this->frequency_shift = frequency_shift; + this->m_frequency_shift_isSet = true; +} + +qint32 +SWGTestSourceSettings::getSampleRate() { + return sample_rate; +} +void +SWGTestSourceSettings::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + +qint32 +SWGTestSourceSettings::getLog2Decim() { + return log2_decim; +} +void +SWGTestSourceSettings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +qint32 +SWGTestSourceSettings::getFcPos() { + return fc_pos; +} +void +SWGTestSourceSettings::setFcPos(qint32 fc_pos) { + this->fc_pos = fc_pos; + this->m_fc_pos_isSet = true; +} + +qint32 +SWGTestSourceSettings::getSampleSizeIndex() { + return sample_size_index; +} +void +SWGTestSourceSettings::setSampleSizeIndex(qint32 sample_size_index) { + this->sample_size_index = sample_size_index; + this->m_sample_size_index_isSet = true; +} + +qint32 +SWGTestSourceSettings::getAmplitudeBits() { + return amplitude_bits; +} +void +SWGTestSourceSettings::setAmplitudeBits(qint32 amplitude_bits) { + this->amplitude_bits = amplitude_bits; + this->m_amplitude_bits_isSet = true; +} + +qint32 +SWGTestSourceSettings::getAutoCorrOptions() { + return auto_corr_options; +} +void +SWGTestSourceSettings::setAutoCorrOptions(qint32 auto_corr_options) { + this->auto_corr_options = auto_corr_options; + this->m_auto_corr_options_isSet = true; +} + +qint32 +SWGTestSourceSettings::getModulation() { + return modulation; +} +void +SWGTestSourceSettings::setModulation(qint32 modulation) { + this->modulation = modulation; + this->m_modulation_isSet = true; +} + +qint32 +SWGTestSourceSettings::getModulationTone() { + return modulation_tone; +} +void +SWGTestSourceSettings::setModulationTone(qint32 modulation_tone) { + this->modulation_tone = modulation_tone; + this->m_modulation_tone_isSet = true; +} + +qint32 +SWGTestSourceSettings::getAmModulation() { + return am_modulation; +} +void +SWGTestSourceSettings::setAmModulation(qint32 am_modulation) { + this->am_modulation = am_modulation; + this->m_am_modulation_isSet = true; +} + +qint32 +SWGTestSourceSettings::getFmDeviation() { + return fm_deviation; +} +void +SWGTestSourceSettings::setFmDeviation(qint32 fm_deviation) { + this->fm_deviation = fm_deviation; + this->m_fm_deviation_isSet = true; +} + +float +SWGTestSourceSettings::getDcFactor() { + return dc_factor; +} +void +SWGTestSourceSettings::setDcFactor(float dc_factor) { + this->dc_factor = dc_factor; + this->m_dc_factor_isSet = true; +} + +float +SWGTestSourceSettings::getIFactor() { + return i_factor; +} +void +SWGTestSourceSettings::setIFactor(float i_factor) { + this->i_factor = i_factor; + this->m_i_factor_isSet = true; +} + +float +SWGTestSourceSettings::getQFactor() { + return q_factor; +} +void +SWGTestSourceSettings::setQFactor(float q_factor) { + this->q_factor = q_factor; + this->m_q_factor_isSet = true; +} + +float +SWGTestSourceSettings::getPhaseImbalance() { + return phase_imbalance; +} +void +SWGTestSourceSettings::setPhaseImbalance(float phase_imbalance) { + this->phase_imbalance = phase_imbalance; + this->m_phase_imbalance_isSet = true; +} + +QString* +SWGTestSourceSettings::getFileRecordName() { + return file_record_name; +} +void +SWGTestSourceSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + + +bool +SWGTestSourceSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_frequency_shift_isSet){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_log2_decim_isSet){ isObjectUpdated = true; break;} + if(m_fc_pos_isSet){ isObjectUpdated = true; break;} + if(m_sample_size_index_isSet){ isObjectUpdated = true; break;} + if(m_amplitude_bits_isSet){ isObjectUpdated = true; break;} + if(m_auto_corr_options_isSet){ isObjectUpdated = true; break;} + if(m_modulation_isSet){ isObjectUpdated = true; break;} + if(m_modulation_tone_isSet){ isObjectUpdated = true; break;} + if(m_am_modulation_isSet){ isObjectUpdated = true; break;} + if(m_fm_deviation_isSet){ isObjectUpdated = true; break;} + if(m_dc_factor_isSet){ isObjectUpdated = true; break;} + if(m_i_factor_isSet){ isObjectUpdated = true; break;} + if(m_q_factor_isSet){ isObjectUpdated = true; break;} + if(m_phase_imbalance_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h new file mode 100644 index 000000000..50029d997 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h @@ -0,0 +1,155 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGTestSourceSettings.h + * + * TestSource + */ + +#ifndef SWGTestSourceSettings_H_ +#define SWGTestSourceSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGTestSourceSettings: public SWGObject { +public: + SWGTestSourceSettings(); + SWGTestSourceSettings(QString* json); + virtual ~SWGTestSourceSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGTestSourceSettings* fromJson(QString &jsonString) override; + + qint32 getCenterFrequency(); + void setCenterFrequency(qint32 center_frequency); + + qint32 getFrequencyShift(); + void setFrequencyShift(qint32 frequency_shift); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + qint32 getFcPos(); + void setFcPos(qint32 fc_pos); + + qint32 getSampleSizeIndex(); + void setSampleSizeIndex(qint32 sample_size_index); + + qint32 getAmplitudeBits(); + void setAmplitudeBits(qint32 amplitude_bits); + + qint32 getAutoCorrOptions(); + void setAutoCorrOptions(qint32 auto_corr_options); + + qint32 getModulation(); + void setModulation(qint32 modulation); + + qint32 getModulationTone(); + void setModulationTone(qint32 modulation_tone); + + qint32 getAmModulation(); + void setAmModulation(qint32 am_modulation); + + qint32 getFmDeviation(); + void setFmDeviation(qint32 fm_deviation); + + float getDcFactor(); + void setDcFactor(float dc_factor); + + float getIFactor(); + void setIFactor(float i_factor); + + float getQFactor(); + void setQFactor(float q_factor); + + float getPhaseImbalance(); + void setPhaseImbalance(float phase_imbalance); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + + virtual bool isSet() override; + +private: + qint32 center_frequency; + bool m_center_frequency_isSet; + + qint32 frequency_shift; + bool m_frequency_shift_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + qint32 fc_pos; + bool m_fc_pos_isSet; + + qint32 sample_size_index; + bool m_sample_size_index_isSet; + + qint32 amplitude_bits; + bool m_amplitude_bits_isSet; + + qint32 auto_corr_options; + bool m_auto_corr_options_isSet; + + qint32 modulation; + bool m_modulation_isSet; + + qint32 modulation_tone; + bool m_modulation_tone_isSet; + + qint32 am_modulation; + bool m_am_modulation_isSet; + + qint32 fm_deviation; + bool m_fm_deviation_isSet; + + float dc_factor; + bool m_dc_factor_isSet; + + float i_factor; + bool m_i_factor_isSet; + + float q_factor; + bool m_q_factor_isSet; + + float phase_imbalance; + bool m_phase_imbalance_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + +}; + +} + +#endif /* SWGTestSourceSettings_H_ */ From 8155825bc4174c43245c44ce3b96266f8a9223dc Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 28 May 2018 00:40:33 +0200 Subject: [PATCH 479/956] SDR daemon sink: implemeted WEB API --- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 90 ++++++ .../sdrdaemonsink/sdrdaemonsinkoutput.h | 16 + .../sdrdaemonsink/sdrdaemonsinkplugin.cpp | 2 +- sdrbase/dsp/samplesourcefifo.h | 12 + sdrbase/resources/webapi/doc/html2/index.html | 57 +++- .../doc/swagger/include/SDRDaemonSink.yaml | 36 +++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + .../api/swagger/include/SDRDaemonSink.yaml | 36 +++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 57 +++- .../code/qt5/client/SWGDeviceReport.cpp | 23 ++ .../code/qt5/client/SWGDeviceReport.h | 7 + .../code/qt5/client/SWGDeviceSettings.cpp | 23 ++ .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../qt5/client/SWGSDRdaemonSinkReport.cpp | 127 ++++++++ .../code/qt5/client/SWGSDRdaemonSinkReport.h | 64 ++++ .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 278 ++++++++++++++++++ .../qt5/client/SWGSDRdaemonSinkSettings.h | 107 +++++++ 19 files changed, 955 insertions(+), 3 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml create mode 100644 swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index b92d2c2ec..c81f50c47 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -20,6 +20,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGSDRdaemonSinkReport.h" #include "util/simpleserializer.h" #include "dsp/dspcommands.h" @@ -363,4 +365,92 @@ int SDRdaemonSinkOutput::webapiRun( return 200; } +int SDRdaemonSinkOutput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonSinkSettings(new SWGSDRangel::SWGSDRdaemonSinkSettings()); + response.getSdrDaemonSinkSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int SDRdaemonSinkOutput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + SDRdaemonSinkSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getSdrDaemonSinkSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("sampleRate")) { + settings.m_sampleRate = response.getSdrDaemonSinkSettings()->getSampleRate(); + } + if (deviceSettingsKeys.contains("log2Interp")) { + settings.m_log2Interp = response.getSdrDaemonSinkSettings()->getLog2Interp(); + } + if (deviceSettingsKeys.contains("txDelay")) { + settings.m_txDelay = response.getSdrDaemonSinkSettings()->getTxDelay(); + } + if (deviceSettingsKeys.contains("nbFECBlocks")) { + settings.m_nbFECBlocks = response.getSdrDaemonSinkSettings()->getNbFecBlocks(); + } + if (deviceSettingsKeys.contains("address")) { + settings.m_address = *response.getSdrDaemonSinkSettings()->getAddress(); + } + if (deviceSettingsKeys.contains("dataPort")) { + settings.m_dataPort = response.getSdrDaemonSinkSettings()->getDataPort(); + } + if (deviceSettingsKeys.contains("controlPort")) { + settings.m_controlPort = response.getSdrDaemonSinkSettings()->getControlPort(); + } + if (deviceSettingsKeys.contains("specificParameters")) { + settings.m_specificParameters = *response.getSdrDaemonSinkSettings()->getSpecificParameters(); + } + + MsgConfigureSDRdaemonSink *msg = MsgConfigureSDRdaemonSink::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRdaemonSink *msgToGUI = MsgConfigureSDRdaemonSink::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int SDRdaemonSinkOutput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonSinkReport(new SWGSDRangel::SWGSDRdaemonSinkReport()); + response.getSdrDaemonSinkReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void SDRdaemonSinkOutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSinkSettings& settings) +{ + response.getSdrDaemonSinkSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getSdrDaemonSinkSettings()->setSampleRate(settings.m_sampleRate); + response.getSdrDaemonSinkSettings()->setLog2Interp(settings.m_log2Interp); + response.getSdrDaemonSinkSettings()->setTxDelay(settings.m_txDelay); + response.getSdrDaemonSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); + response.getSdrDaemonSinkSettings()->setAddress(new QString(settings.m_address)); + response.getSdrDaemonSinkSettings()->setDataPort(settings.m_dataPort); + response.getSdrDaemonSinkSettings()->setControlPort(settings.m_controlPort); + response.getSdrDaemonSinkSettings()->setSpecificParameters(new QString(settings.m_specificParameters)); +} + +void SDRdaemonSinkOutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getSdrDaemonSinkReport()->setBufferRwBalance(m_sampleSourceFifo.getRWBalance()); + response.getSdrDaemonSinkReport()->setSampleCount(m_sdrDaemonSinkThread ? (int) m_sdrDaemonSinkThread->getSamplesCount() : 0); +} + diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index 5e5e85551..51c7b21c7 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -171,6 +171,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -190,6 +204,8 @@ private: const QTimer& m_masterTimer; void applySettings(const SDRdaemonSinkSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSinkSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif // INCLUDE_SDRDAEMONSINKOUTPUT_H diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp index c5e0cd345..f5bc24726 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp @@ -26,7 +26,7 @@ const PluginDescriptor SDRdaemonSinkPlugin::m_pluginDescriptor = { QString("SDRdaemon sink output"), - QString("3.14.5"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/dsp/samplesourcefifo.h b/sdrbase/dsp/samplesourcefifo.h index f856a631f..1374e3d57 100644 --- a/sdrbase/dsp/samplesourcefifo.h +++ b/sdrbase/dsp/samplesourcefifo.h @@ -43,6 +43,18 @@ public: void write(const Sample& sample); //!< write directly - phase 1 + phase 2 + /** returns ratio of off center over buffer size with sign: negative real lags and positive read leads */ + float getRWBalance() const + { + int delta; + if (m_iw > m_ir) { + delta = (m_size/2) - (m_iw - m_ir); + } else { + delta = (m_ir - m_iw) - (m_size/2); + } + return delta / (float) m_size; + } + private: uint32_t m_size; SampleVector m_data; diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 8be095293..5116073ab 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1739,6 +1739,9 @@ margin-bottom: 20px; "rtlSdrReport" : { "$ref" : "#/definitions/RtlSdrReport" }, + "sdrDaemonSinkReport" : { + "$ref" : "#/definitions/SDRdaemonSinkReport" + }, "sdrDaemonSourceReport" : { "$ref" : "#/definitions/SDRdaemonSourceReport" }, @@ -1845,6 +1848,9 @@ margin-bottom: 20px; "rtlSdrSettings" : { "$ref" : "#/definitions/RtlSdrSettings" }, + "sdrDaemonSinkSettings" : { + "$ref" : "#/definitions/SDRdaemonSinkSettings" + }, "sdrDaemonSourceSettings" : { "$ref" : "#/definitions/SDRdaemonSourceSettings" }, @@ -3168,6 +3174,55 @@ margin-bottom: 20px; } }, "description" : "SDRplay1" +}; + defs.SDRdaemonSinkReport = { + "properties" : { + "bufferRWBalance" : { + "type" : "number", + "format" : "float", + "description" : "ratio off the mid buffer (positive read leads)" + }, + "sampleCount" : { + "type" : "integer", + "description" : "count of samples that have been sent" + } + }, + "description" : "SDRdaemonSource" +}; + defs.SDRdaemonSinkSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "sampleRate" : { + "type" : "integer" + }, + "log2Interp" : { + "type" : "integer" + }, + "txDelay" : { + "type" : "number", + "format" : "float", + "description" : "minimum delay in ms between two consecutive packets sending" + }, + "nbFECBlocks" : { + "type" : "integer" + }, + "address" : { + "type" : "string" + }, + "dataPort" : { + "type" : "integer" + }, + "controlPort" : { + "type" : "integer" + }, + "specificParameters" : { + "type" : "string" + } + }, + "description" : "SDRdaemonSink" }; defs.SDRdaemonSourceReport = { "properties" : { @@ -22558,7 +22613,7 @@ except ApiException as e:
    - Generated 2018-05-27T20:33:10.219+02:00 + Generated 2018-05-28T00:22:45.302+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml new file mode 100644 index 000000000..40bac741e --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml @@ -0,0 +1,36 @@ +SDRdaemonSinkSettings: + description: SDRdaemonSink + properties: + centerFrequency: + type: integer + format: uint64 + sampleRate: + type: integer + log2Interp: + type: integer + txDelay: + description: minimum delay in ms between two consecutive packets sending + type: number + format: float + nbFECBlocks: + type: integer + address: + type: string + dataPort: + type: integer + controlPort: + type: integer + specificParameters: + type: string + +SDRdaemonSinkReport: + description: SDRdaemonSource + properties: + bufferRWBalance: + description: ratio off the mid buffer (positive read leads) + type: number + format: float + sampleCount: + description: count of samples that have been sent + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 4ee62b184..2c658d14a 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1775,6 +1775,8 @@ definitions: $ref: "/doc/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputSettings" rtlSdrSettings: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrSettings" + sdrDaemonSinkSettings: + $ref: "/doc/swagger/include/SDRDaemonSink.yaml#/SDRdaemonSinkSettings" sdrDaemonSourceSettings: $ref: "/doc/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceSettings" sdrPlaySettings: @@ -1814,6 +1816,8 @@ definitions: $ref: "/doc/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputReport" rtlSdrReport: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrReport" + sdrDaemonSinkReport: + $ref: "/doc/swagger/include/SDRDaemonSink.yaml#/SDRdaemonSinkReport" sdrDaemonSourceReport: $ref: "/doc/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceReport" sdrPlayReport: diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml new file mode 100644 index 000000000..40bac741e --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml @@ -0,0 +1,36 @@ +SDRdaemonSinkSettings: + description: SDRdaemonSink + properties: + centerFrequency: + type: integer + format: uint64 + sampleRate: + type: integer + log2Interp: + type: integer + txDelay: + description: minimum delay in ms between two consecutive packets sending + type: number + format: float + nbFECBlocks: + type: integer + address: + type: string + dataPort: + type: integer + controlPort: + type: integer + specificParameters: + type: string + +SDRdaemonSinkReport: + description: SDRdaemonSource + properties: + bufferRWBalance: + description: ratio off the mid buffer (positive read leads) + type: number + format: float + sampleCount: + description: count of samples that have been sent + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index f4253b5a7..0c999255b 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1775,6 +1775,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputSettings" rtlSdrSettings: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrSettings" + sdrDaemonSinkSettings: + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSink.yaml#/SDRdaemonSinkSettings" sdrDaemonSourceSettings: $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceSettings" sdrPlaySettings: @@ -1814,6 +1816,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/PlutoSdr.yaml#/PlutoSdrOutputReport" rtlSdrReport: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrReport" + sdrDaemonSinkReport: + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSink.yaml#/SDRdaemonSinkReport" sdrDaemonSourceReport: $ref: "http://localhost:8081/api/swagger/include/SDRDaemonSource.yaml#/SDRdaemonSourceReport" sdrPlayReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 8be095293..5116073ab 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1739,6 +1739,9 @@ margin-bottom: 20px; "rtlSdrReport" : { "$ref" : "#/definitions/RtlSdrReport" }, + "sdrDaemonSinkReport" : { + "$ref" : "#/definitions/SDRdaemonSinkReport" + }, "sdrDaemonSourceReport" : { "$ref" : "#/definitions/SDRdaemonSourceReport" }, @@ -1845,6 +1848,9 @@ margin-bottom: 20px; "rtlSdrSettings" : { "$ref" : "#/definitions/RtlSdrSettings" }, + "sdrDaemonSinkSettings" : { + "$ref" : "#/definitions/SDRdaemonSinkSettings" + }, "sdrDaemonSourceSettings" : { "$ref" : "#/definitions/SDRdaemonSourceSettings" }, @@ -3168,6 +3174,55 @@ margin-bottom: 20px; } }, "description" : "SDRplay1" +}; + defs.SDRdaemonSinkReport = { + "properties" : { + "bufferRWBalance" : { + "type" : "number", + "format" : "float", + "description" : "ratio off the mid buffer (positive read leads)" + }, + "sampleCount" : { + "type" : "integer", + "description" : "count of samples that have been sent" + } + }, + "description" : "SDRdaemonSource" +}; + defs.SDRdaemonSinkSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "uint64" + }, + "sampleRate" : { + "type" : "integer" + }, + "log2Interp" : { + "type" : "integer" + }, + "txDelay" : { + "type" : "number", + "format" : "float", + "description" : "minimum delay in ms between two consecutive packets sending" + }, + "nbFECBlocks" : { + "type" : "integer" + }, + "address" : { + "type" : "string" + }, + "dataPort" : { + "type" : "integer" + }, + "controlPort" : { + "type" : "integer" + }, + "specificParameters" : { + "type" : "string" + } + }, + "description" : "SDRdaemonSink" }; defs.SDRdaemonSourceReport = { "properties" : { @@ -22558,7 +22613,7 @@ except ApiException as e:
    - Generated 2018-05-27T20:33:10.219+02:00 + Generated 2018-05-28T00:22:45.302+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index 93f904470..2bb78a2cb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -50,6 +50,8 @@ SWGDeviceReport::SWGDeviceReport() { m_pluto_sdr_output_report_isSet = false; rtl_sdr_report = nullptr; m_rtl_sdr_report_isSet = false; + sdr_daemon_sink_report = nullptr; + m_sdr_daemon_sink_report_isSet = false; sdr_daemon_source_report = nullptr; m_sdr_daemon_source_report_isSet = false; sdr_play_report = nullptr; @@ -84,6 +86,8 @@ SWGDeviceReport::init() { m_pluto_sdr_output_report_isSet = false; rtl_sdr_report = new SWGRtlSdrReport(); m_rtl_sdr_report_isSet = false; + sdr_daemon_sink_report = new SWGSDRdaemonSinkReport(); + m_sdr_daemon_sink_report_isSet = false; sdr_daemon_source_report = new SWGSDRdaemonSourceReport(); m_sdr_daemon_source_report_isSet = false; sdr_play_report = new SWGSDRPlayReport(); @@ -123,6 +127,9 @@ SWGDeviceReport::cleanup() { if(rtl_sdr_report != nullptr) { delete rtl_sdr_report; } + if(sdr_daemon_sink_report != nullptr) { + delete sdr_daemon_sink_report; + } if(sdr_daemon_source_report != nullptr) { delete sdr_daemon_source_report; } @@ -164,6 +171,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&rtl_sdr_report, pJson["rtlSdrReport"], "SWGRtlSdrReport", "SWGRtlSdrReport"); + ::SWGSDRangel::setValue(&sdr_daemon_sink_report, pJson["sdrDaemonSinkReport"], "SWGSDRdaemonSinkReport", "SWGSDRdaemonSinkReport"); + ::SWGSDRangel::setValue(&sdr_daemon_source_report, pJson["sdrDaemonSourceReport"], "SWGSDRdaemonSourceReport", "SWGSDRdaemonSourceReport"); ::SWGSDRangel::setValue(&sdr_play_report, pJson["sdrPlayReport"], "SWGSDRPlayReport", "SWGSDRPlayReport"); @@ -217,6 +226,9 @@ SWGDeviceReport::asJsonObject() { if((rtl_sdr_report != nullptr) && (rtl_sdr_report->isSet())){ toJsonValue(QString("rtlSdrReport"), rtl_sdr_report, obj, QString("SWGRtlSdrReport")); } + if((sdr_daemon_sink_report != nullptr) && (sdr_daemon_sink_report->isSet())){ + toJsonValue(QString("sdrDaemonSinkReport"), sdr_daemon_sink_report, obj, QString("SWGSDRdaemonSinkReport")); + } if((sdr_daemon_source_report != nullptr) && (sdr_daemon_source_report->isSet())){ toJsonValue(QString("sdrDaemonSourceReport"), sdr_daemon_source_report, obj, QString("SWGSDRdaemonSourceReport")); } @@ -337,6 +349,16 @@ SWGDeviceReport::setRtlSdrReport(SWGRtlSdrReport* rtl_sdr_report) { this->m_rtl_sdr_report_isSet = true; } +SWGSDRdaemonSinkReport* +SWGDeviceReport::getSdrDaemonSinkReport() { + return sdr_daemon_sink_report; +} +void +SWGDeviceReport::setSdrDaemonSinkReport(SWGSDRdaemonSinkReport* sdr_daemon_sink_report) { + this->sdr_daemon_sink_report = sdr_daemon_sink_report; + this->m_sdr_daemon_sink_report_isSet = true; +} + SWGSDRdaemonSourceReport* SWGDeviceReport::getSdrDaemonSourceReport() { return sdr_daemon_source_report; @@ -373,6 +395,7 @@ SWGDeviceReport::isSet(){ if(pluto_sdr_input_report != nullptr && pluto_sdr_input_report->isSet()){ isObjectUpdated = true; break;} if(pluto_sdr_output_report != nullptr && pluto_sdr_output_report->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_report != nullptr && rtl_sdr_report->isSet()){ isObjectUpdated = true; break;} + if(sdr_daemon_sink_report != nullptr && sdr_daemon_sink_report->isSet()){ isObjectUpdated = true; break;} if(sdr_daemon_source_report != nullptr && sdr_daemon_source_report->isSet()){ isObjectUpdated = true; break;} if(sdr_play_report != nullptr && sdr_play_report->isSet()){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index 05ac916f5..3e12b5057 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -32,6 +32,7 @@ #include "SWGPlutoSdrOutputReport.h" #include "SWGRtlSdrReport.h" #include "SWGSDRPlayReport.h" +#include "SWGSDRdaemonSinkReport.h" #include "SWGSDRdaemonSourceReport.h" #include @@ -86,6 +87,9 @@ public: SWGRtlSdrReport* getRtlSdrReport(); void setRtlSdrReport(SWGRtlSdrReport* rtl_sdr_report); + SWGSDRdaemonSinkReport* getSdrDaemonSinkReport(); + void setSdrDaemonSinkReport(SWGSDRdaemonSinkReport* sdr_daemon_sink_report); + SWGSDRdaemonSourceReport* getSdrDaemonSourceReport(); void setSdrDaemonSourceReport(SWGSDRdaemonSourceReport* sdr_daemon_source_report); @@ -129,6 +133,9 @@ private: SWGRtlSdrReport* rtl_sdr_report; bool m_rtl_sdr_report_isSet; + SWGSDRdaemonSinkReport* sdr_daemon_sink_report; + bool m_sdr_daemon_sink_report_isSet; + SWGSDRdaemonSourceReport* sdr_daemon_source_report; bool m_sdr_daemon_source_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 7ccda43c1..3b8e208da 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -62,6 +62,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_pluto_sdr_output_settings_isSet = false; rtl_sdr_settings = nullptr; m_rtl_sdr_settings_isSet = false; + sdr_daemon_sink_settings = nullptr; + m_sdr_daemon_sink_settings_isSet = false; sdr_daemon_source_settings = nullptr; m_sdr_daemon_source_settings_isSet = false; sdr_play_settings = nullptr; @@ -110,6 +112,8 @@ SWGDeviceSettings::init() { m_pluto_sdr_output_settings_isSet = false; rtl_sdr_settings = new SWGRtlSdrSettings(); m_rtl_sdr_settings_isSet = false; + sdr_daemon_sink_settings = new SWGSDRdaemonSinkSettings(); + m_sdr_daemon_sink_settings_isSet = false; sdr_daemon_source_settings = new SWGSDRdaemonSourceSettings(); m_sdr_daemon_source_settings_isSet = false; sdr_play_settings = new SWGSDRPlaySettings(); @@ -169,6 +173,9 @@ SWGDeviceSettings::cleanup() { if(rtl_sdr_settings != nullptr) { delete rtl_sdr_settings; } + if(sdr_daemon_sink_settings != nullptr) { + delete sdr_daemon_sink_settings; + } if(sdr_daemon_source_settings != nullptr) { delete sdr_daemon_source_settings; } @@ -225,6 +232,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&rtl_sdr_settings, pJson["rtlSdrSettings"], "SWGRtlSdrSettings", "SWGRtlSdrSettings"); + ::SWGSDRangel::setValue(&sdr_daemon_sink_settings, pJson["sdrDaemonSinkSettings"], "SWGSDRdaemonSinkSettings", "SWGSDRdaemonSinkSettings"); + ::SWGSDRangel::setValue(&sdr_daemon_source_settings, pJson["sdrDaemonSourceSettings"], "SWGSDRdaemonSourceSettings", "SWGSDRdaemonSourceSettings"); ::SWGSDRangel::setValue(&sdr_play_settings, pJson["sdrPlaySettings"], "SWGSDRPlaySettings", "SWGSDRPlaySettings"); @@ -298,6 +307,9 @@ SWGDeviceSettings::asJsonObject() { if((rtl_sdr_settings != nullptr) && (rtl_sdr_settings->isSet())){ toJsonValue(QString("rtlSdrSettings"), rtl_sdr_settings, obj, QString("SWGRtlSdrSettings")); } + if((sdr_daemon_sink_settings != nullptr) && (sdr_daemon_sink_settings->isSet())){ + toJsonValue(QString("sdrDaemonSinkSettings"), sdr_daemon_sink_settings, obj, QString("SWGSDRdaemonSinkSettings")); + } if((sdr_daemon_source_settings != nullptr) && (sdr_daemon_source_settings->isSet())){ toJsonValue(QString("sdrDaemonSourceSettings"), sdr_daemon_source_settings, obj, QString("SWGSDRdaemonSourceSettings")); } @@ -481,6 +493,16 @@ SWGDeviceSettings::setRtlSdrSettings(SWGRtlSdrSettings* rtl_sdr_settings) { this->m_rtl_sdr_settings_isSet = true; } +SWGSDRdaemonSinkSettings* +SWGDeviceSettings::getSdrDaemonSinkSettings() { + return sdr_daemon_sink_settings; +} +void +SWGDeviceSettings::setSdrDaemonSinkSettings(SWGSDRdaemonSinkSettings* sdr_daemon_sink_settings) { + this->sdr_daemon_sink_settings = sdr_daemon_sink_settings; + this->m_sdr_daemon_sink_settings_isSet = true; +} + SWGSDRdaemonSourceSettings* SWGDeviceSettings::getSdrDaemonSourceSettings() { return sdr_daemon_source_settings; @@ -533,6 +555,7 @@ SWGDeviceSettings::isSet(){ if(pluto_sdr_input_settings != nullptr && pluto_sdr_input_settings->isSet()){ isObjectUpdated = true; break;} if(pluto_sdr_output_settings != nullptr && pluto_sdr_output_settings->isSet()){ isObjectUpdated = true; break;} if(rtl_sdr_settings != nullptr && rtl_sdr_settings->isSet()){ isObjectUpdated = true; break;} + if(sdr_daemon_sink_settings != nullptr && sdr_daemon_sink_settings->isSet()){ isObjectUpdated = true; break;} if(sdr_daemon_source_settings != nullptr && sdr_daemon_source_settings->isSet()){ isObjectUpdated = true; break;} if(sdr_play_settings != nullptr && sdr_play_settings->isSet()){ isObjectUpdated = true; break;} if(test_source_settings != nullptr && test_source_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index c07994698..d11fb177f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -38,6 +38,7 @@ #include "SWGPlutoSdrOutputSettings.h" #include "SWGRtlSdrSettings.h" #include "SWGSDRPlaySettings.h" +#include "SWGSDRdaemonSinkSettings.h" #include "SWGSDRdaemonSourceSettings.h" #include "SWGTestSourceSettings.h" #include @@ -111,6 +112,9 @@ public: SWGRtlSdrSettings* getRtlSdrSettings(); void setRtlSdrSettings(SWGRtlSdrSettings* rtl_sdr_settings); + SWGSDRdaemonSinkSettings* getSdrDaemonSinkSettings(); + void setSdrDaemonSinkSettings(SWGSDRdaemonSinkSettings* sdr_daemon_sink_settings); + SWGSDRdaemonSourceSettings* getSdrDaemonSourceSettings(); void setSdrDaemonSourceSettings(SWGSDRdaemonSourceSettings* sdr_daemon_source_settings); @@ -175,6 +179,9 @@ private: SWGRtlSdrSettings* rtl_sdr_settings; bool m_rtl_sdr_settings_isSet; + SWGSDRdaemonSinkSettings* sdr_daemon_sink_settings; + bool m_sdr_daemon_sink_settings_isSet; + SWGSDRdaemonSourceSettings* sdr_daemon_source_settings; bool m_sdr_daemon_source_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index ccfba90e6..2172187d7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -90,6 +90,8 @@ #include "SWGRtlSdrSettings.h" #include "SWGSDRPlayReport.h" #include "SWGSDRPlaySettings.h" +#include "SWGSDRdaemonSinkReport.h" +#include "SWGSDRdaemonSinkSettings.h" #include "SWGSDRdaemonSourceReport.h" #include "SWGSDRdaemonSourceSettings.h" #include "SWGSSBDemodReport.h" @@ -340,6 +342,12 @@ namespace SWGSDRangel { if(QString("SWGSDRPlaySettings").compare(type) == 0) { return new SWGSDRPlaySettings(); } + if(QString("SWGSDRdaemonSinkReport").compare(type) == 0) { + return new SWGSDRdaemonSinkReport(); + } + if(QString("SWGSDRdaemonSinkSettings").compare(type) == 0) { + return new SWGSDRdaemonSinkSettings(); + } if(QString("SWGSDRdaemonSourceReport").compare(type) == 0) { return new SWGSDRdaemonSourceReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp new file mode 100644 index 000000000..981c18149 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp @@ -0,0 +1,127 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRdaemonSinkReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRdaemonSinkReport::SWGSDRdaemonSinkReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRdaemonSinkReport::SWGSDRdaemonSinkReport() { + buffer_rw_balance = 0.0f; + m_buffer_rw_balance_isSet = false; + sample_count = 0; + m_sample_count_isSet = false; +} + +SWGSDRdaemonSinkReport::~SWGSDRdaemonSinkReport() { + this->cleanup(); +} + +void +SWGSDRdaemonSinkReport::init() { + buffer_rw_balance = 0.0f; + m_buffer_rw_balance_isSet = false; + sample_count = 0; + m_sample_count_isSet = false; +} + +void +SWGSDRdaemonSinkReport::cleanup() { + + +} + +SWGSDRdaemonSinkReport* +SWGSDRdaemonSinkReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRdaemonSinkReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&buffer_rw_balance, pJson["bufferRWBalance"], "float", ""); + + ::SWGSDRangel::setValue(&sample_count, pJson["sampleCount"], "qint32", ""); + +} + +QString +SWGSDRdaemonSinkReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRdaemonSinkReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_buffer_rw_balance_isSet){ + obj->insert("bufferRWBalance", QJsonValue(buffer_rw_balance)); + } + if(m_sample_count_isSet){ + obj->insert("sampleCount", QJsonValue(sample_count)); + } + + return obj; +} + +float +SWGSDRdaemonSinkReport::getBufferRwBalance() { + return buffer_rw_balance; +} +void +SWGSDRdaemonSinkReport::setBufferRwBalance(float buffer_rw_balance) { + this->buffer_rw_balance = buffer_rw_balance; + this->m_buffer_rw_balance_isSet = true; +} + +qint32 +SWGSDRdaemonSinkReport::getSampleCount() { + return sample_count; +} +void +SWGSDRdaemonSinkReport::setSampleCount(qint32 sample_count) { + this->sample_count = sample_count; + this->m_sample_count_isSet = true; +} + + +bool +SWGSDRdaemonSinkReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_buffer_rw_balance_isSet){ isObjectUpdated = true; break;} + if(m_sample_count_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h new file mode 100644 index 000000000..33a3327e7 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h @@ -0,0 +1,64 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRdaemonSinkReport.h + * + * SDRdaemonSource + */ + +#ifndef SWGSDRdaemonSinkReport_H_ +#define SWGSDRdaemonSinkReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRdaemonSinkReport: public SWGObject { +public: + SWGSDRdaemonSinkReport(); + SWGSDRdaemonSinkReport(QString* json); + virtual ~SWGSDRdaemonSinkReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRdaemonSinkReport* fromJson(QString &jsonString) override; + + float getBufferRwBalance(); + void setBufferRwBalance(float buffer_rw_balance); + + qint32 getSampleCount(); + void setSampleCount(qint32 sample_count); + + + virtual bool isSet() override; + +private: + float buffer_rw_balance; + bool m_buffer_rw_balance_isSet; + + qint32 sample_count; + bool m_sample_count_isSet; + +}; + +} + +#endif /* SWGSDRdaemonSinkReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp new file mode 100644 index 000000000..d4a326f68 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp @@ -0,0 +1,278 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRdaemonSinkSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRdaemonSinkSettings::SWGSDRdaemonSinkSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRdaemonSinkSettings::SWGSDRdaemonSinkSettings() { + center_frequency = 0; + m_center_frequency_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + log2_interp = 0; + m_log2_interp_isSet = false; + tx_delay = 0.0f; + m_tx_delay_isSet = false; + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + address = nullptr; + m_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + control_port = 0; + m_control_port_isSet = false; + specific_parameters = nullptr; + m_specific_parameters_isSet = false; +} + +SWGSDRdaemonSinkSettings::~SWGSDRdaemonSinkSettings() { + this->cleanup(); +} + +void +SWGSDRdaemonSinkSettings::init() { + center_frequency = 0; + m_center_frequency_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + log2_interp = 0; + m_log2_interp_isSet = false; + tx_delay = 0.0f; + m_tx_delay_isSet = false; + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + address = new QString(""); + m_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + control_port = 0; + m_control_port_isSet = false; + specific_parameters = new QString(""); + m_specific_parameters_isSet = false; +} + +void +SWGSDRdaemonSinkSettings::cleanup() { + + + + + + if(address != nullptr) { + delete address; + } + + + if(specific_parameters != nullptr) { + delete specific_parameters; + } +} + +SWGSDRdaemonSinkSettings* +SWGSDRdaemonSinkSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRdaemonSinkSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_interp, pJson["log2Interp"], "qint32", ""); + + ::SWGSDRangel::setValue(&tx_delay, pJson["txDelay"], "float", ""); + + ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(&address, pJson["address"], "QString", "QString"); + + ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&control_port, pJson["controlPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&specific_parameters, pJson["specificParameters"], "QString", "QString"); + +} + +QString +SWGSDRdaemonSinkSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRdaemonSinkSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + if(m_log2_interp_isSet){ + obj->insert("log2Interp", QJsonValue(log2_interp)); + } + if(m_tx_delay_isSet){ + obj->insert("txDelay", QJsonValue(tx_delay)); + } + if(m_nb_fec_blocks_isSet){ + obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); + } + if(address != nullptr && *address != QString("")){ + toJsonValue(QString("address"), address, obj, QString("QString")); + } + if(m_data_port_isSet){ + obj->insert("dataPort", QJsonValue(data_port)); + } + if(m_control_port_isSet){ + obj->insert("controlPort", QJsonValue(control_port)); + } + if(specific_parameters != nullptr && *specific_parameters != QString("")){ + toJsonValue(QString("specificParameters"), specific_parameters, obj, QString("QString")); + } + + return obj; +} + +qint32 +SWGSDRdaemonSinkSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGSDRdaemonSinkSettings::setCenterFrequency(qint32 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGSDRdaemonSinkSettings::getSampleRate() { + return sample_rate; +} +void +SWGSDRdaemonSinkSettings::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + +qint32 +SWGSDRdaemonSinkSettings::getLog2Interp() { + return log2_interp; +} +void +SWGSDRdaemonSinkSettings::setLog2Interp(qint32 log2_interp) { + this->log2_interp = log2_interp; + this->m_log2_interp_isSet = true; +} + +float +SWGSDRdaemonSinkSettings::getTxDelay() { + return tx_delay; +} +void +SWGSDRdaemonSinkSettings::setTxDelay(float tx_delay) { + this->tx_delay = tx_delay; + this->m_tx_delay_isSet = true; +} + +qint32 +SWGSDRdaemonSinkSettings::getNbFecBlocks() { + return nb_fec_blocks; +} +void +SWGSDRdaemonSinkSettings::setNbFecBlocks(qint32 nb_fec_blocks) { + this->nb_fec_blocks = nb_fec_blocks; + this->m_nb_fec_blocks_isSet = true; +} + +QString* +SWGSDRdaemonSinkSettings::getAddress() { + return address; +} +void +SWGSDRdaemonSinkSettings::setAddress(QString* address) { + this->address = address; + this->m_address_isSet = true; +} + +qint32 +SWGSDRdaemonSinkSettings::getDataPort() { + return data_port; +} +void +SWGSDRdaemonSinkSettings::setDataPort(qint32 data_port) { + this->data_port = data_port; + this->m_data_port_isSet = true; +} + +qint32 +SWGSDRdaemonSinkSettings::getControlPort() { + return control_port; +} +void +SWGSDRdaemonSinkSettings::setControlPort(qint32 control_port) { + this->control_port = control_port; + this->m_control_port_isSet = true; +} + +QString* +SWGSDRdaemonSinkSettings::getSpecificParameters() { + return specific_parameters; +} +void +SWGSDRdaemonSinkSettings::setSpecificParameters(QString* specific_parameters) { + this->specific_parameters = specific_parameters; + this->m_specific_parameters_isSet = true; +} + + +bool +SWGSDRdaemonSinkSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_log2_interp_isSet){ isObjectUpdated = true; break;} + if(m_tx_delay_isSet){ isObjectUpdated = true; break;} + if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} + if(address != nullptr && *address != QString("")){ isObjectUpdated = true; break;} + if(m_data_port_isSet){ isObjectUpdated = true; break;} + if(m_control_port_isSet){ isObjectUpdated = true; break;} + if(specific_parameters != nullptr && *specific_parameters != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h new file mode 100644 index 000000000..2beffa783 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h @@ -0,0 +1,107 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRdaemonSinkSettings.h + * + * SDRdaemonSink + */ + +#ifndef SWGSDRdaemonSinkSettings_H_ +#define SWGSDRdaemonSinkSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRdaemonSinkSettings: public SWGObject { +public: + SWGSDRdaemonSinkSettings(); + SWGSDRdaemonSinkSettings(QString* json); + virtual ~SWGSDRdaemonSinkSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRdaemonSinkSettings* fromJson(QString &jsonString) override; + + qint32 getCenterFrequency(); + void setCenterFrequency(qint32 center_frequency); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + qint32 getLog2Interp(); + void setLog2Interp(qint32 log2_interp); + + float getTxDelay(); + void setTxDelay(float tx_delay); + + qint32 getNbFecBlocks(); + void setNbFecBlocks(qint32 nb_fec_blocks); + + QString* getAddress(); + void setAddress(QString* address); + + qint32 getDataPort(); + void setDataPort(qint32 data_port); + + qint32 getControlPort(); + void setControlPort(qint32 control_port); + + QString* getSpecificParameters(); + void setSpecificParameters(QString* specific_parameters); + + + virtual bool isSet() override; + +private: + qint32 center_frequency; + bool m_center_frequency_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + + qint32 log2_interp; + bool m_log2_interp_isSet; + + float tx_delay; + bool m_tx_delay_isSet; + + qint32 nb_fec_blocks; + bool m_nb_fec_blocks_isSet; + + QString* address; + bool m_address_isSet; + + qint32 data_port; + bool m_data_port_isSet; + + qint32 control_port; + bool m_control_port_isSet; + + QString* specific_parameters; + bool m_specific_parameters_isSet; + +}; + +} + +#endif /* SWGSDRdaemonSinkSettings_H_ */ From 723747e9dc0baf8f2dd5575fb156de4ec2354119 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 28 May 2018 02:40:26 +0200 Subject: [PATCH 480/956] SSB demod: yet again another clamping algorithm --- plugins/channelrx/demodssb/ssbdemod.cpp | 2 +- sdrbase/dsp/agc.cpp | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index cf7a50ad4..2efeb6ea2 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -85,7 +85,7 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : m_magsqPeak = 0.0f; m_magsqCount = 0; - m_agc.setClampMax(SDR_RX_SCALED*SDR_RX_SCALED); + m_agc.setClampMax(SDR_RX_SCALED/100.0); m_agc.setClamping(m_agcClamping); SSBFilter = new fftfilt(m_LowCutoff / m_audioSampleRate, m_Bandwidth / m_audioSampleRate, ssbFftLen); diff --git a/sdrbase/dsp/agc.cpp b/sdrbase/dsp/agc.cpp index 95329508e..93671c617 100644 --- a/sdrbase/dsp/agc.cpp +++ b/sdrbase/dsp/agc.cpp @@ -104,25 +104,19 @@ double MagAGC::feedAndGetValue(const Complex& ci) { if (m_squared) { - double u0 = m_R / m_moving_average.average(); - double du = (u0*m_magsq) - (m_clampMax/4.0); - if (du > 0) { - m_u0 = (m_clampMax/4.0)*(1.0 + (log10(1+du)/8.0)); // experimental clipping limiter + if (m_magsq > m_clampMax) { + m_u0 = m_clampMax / m_magsq; } else { - m_u0 = u0; + m_u0 = m_R / m_moving_average.average(); } - //m_u0 = (u0 * m_magsq > m_clampMax) ? m_clampMax / m_magsq : u0; } else { - double u02 = m_R2 / m_moving_average.average(); - double du = (u02*m_magsq) - (m_clampMax/4.0); - if (du > 0) { - m_u0 = (m_clampMax/4.0)*(1.0 + (log10(1+du)/8.0)); // experimental clipping limiter + if (sqrt(m_magsq) > m_clampMax) { + m_u0 = m_clampMax / sqrt(m_magsq); } else { - m_u0 = sqrt(u02); + m_u0 = m_R / sqrt(m_moving_average.average()); } - //m_u0 = (u02 * m_magsq > m_clampMax) ? sqrt(m_clampMax / m_magsq) : sqrt(u02); } } else From 37cf94896380768f819a0e35f9b8c4bb9ecd6d53 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 28 May 2018 08:53:08 +0200 Subject: [PATCH 481/956] UDP source: use c++11 flag --- plugins/channelrx/udpsrc/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/channelrx/udpsrc/CMakeLists.txt b/plugins/channelrx/udpsrc/CMakeLists.txt index f4bfb1962..d4ccd6a79 100644 --- a/plugins/channelrx/udpsrc/CMakeLists.txt +++ b/plugins/channelrx/udpsrc/CMakeLists.txt @@ -1,5 +1,7 @@ project(udpsrc) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(udpsrc_SOURCES udpsrc.cpp udpsrcgui.cpp From f05dd1fb8e364a6c0fe96580daa854560e75ff87 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 28 May 2018 09:05:52 +0200 Subject: [PATCH 482/956] DATV demod: added missing AVUTIL cmake variables --- plugins/channelrx/demoddatv/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt index f12dac3ff..bf41c23f0 100644 --- a/plugins/channelrx/demoddatv/CMakeLists.txt +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -25,6 +25,7 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${AVCODEC_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIRS} + ${AVUTIL_INCLUDE_DIRS} ${SWSCALE_INCLUDE_DIRS} ) @@ -48,6 +49,7 @@ target_link_libraries(demoddatv sdrgui ${AVCODEC_LIBRARIES} ${AVFORMAT_LIBRARIES} + ${AVUTIL_LIBRARIES} ${SWSCALE_LIBRARIES} ) From 4c86106b2a360dc34f1ce498b76db86a21c5f3df Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 28 May 2018 10:06:17 +0200 Subject: [PATCH 483/956] Server: added devicesetDeviceReportGet method in the API adapter --- sdrsrv/webapi/webapiadaptersrv.cpp | 39 ++++++++++++++++++++++++++++++ sdrsrv/webapi/webapiadaptersrv.h | 5 ++++ 2 files changed, 44 insertions(+) diff --git a/sdrsrv/webapi/webapiadaptersrv.cpp b/sdrsrv/webapi/webapiadaptersrv.cpp index 269b268a0..3791b8f75 100644 --- a/sdrsrv/webapi/webapiadaptersrv.cpp +++ b/sdrsrv/webapi/webapiadaptersrv.cpp @@ -42,6 +42,7 @@ #include "SWGSuccessResponse.h" #include "SWGErrorResponse.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" #include "maincore.h" #include "loggerwithfile.h" @@ -1274,6 +1275,44 @@ int WebAPIAdapterSrv::devicesetDeviceRunDelete( } } +int WebAPIAdapterSrv::devicesetDeviceReportGet( + int deviceSetIndex, + SWGSDRangel::SWGDeviceReport& response, + SWGSDRangel::SWGErrorResponse& error) +{ + error.init(); + + if ((deviceSetIndex >= 0) && (deviceSetIndex < (int) m_mainCore.m_deviceSets.size())) + { + DeviceSet *deviceSet = m_mainCore.m_deviceSets[deviceSetIndex]; + + if (deviceSet->m_deviceSourceEngine) // Rx + { + response.setDeviceHwType(new QString(deviceSet->m_deviceSourceAPI->getHardwareId())); + response.setTx(0); + DeviceSampleSource *source = deviceSet->m_deviceSourceAPI->getSampleSource(); + return source->webapiReportGet(response, *error.getMessage()); + } + else if (deviceSet->m_deviceSinkEngine) // Tx + { + response.setDeviceHwType(new QString(deviceSet->m_deviceSinkAPI->getHardwareId())); + response.setTx(1); + DeviceSampleSink *sink = deviceSet->m_deviceSinkAPI->getSampleSink(); + return sink->webapiReportGet(response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } + } + else + { + *error.getMessage() = QString("There is no device set with index %1").arg(deviceSetIndex); + return 404; + } +} + int WebAPIAdapterSrv::devicesetChannelsReportGet( int deviceSetIndex, SWGSDRangel::SWGChannelsDetail& response, diff --git a/sdrsrv/webapi/webapiadaptersrv.h b/sdrsrv/webapi/webapiadaptersrv.h index ff1c74bfa..5f312a198 100644 --- a/sdrsrv/webapi/webapiadaptersrv.h +++ b/sdrsrv/webapi/webapiadaptersrv.h @@ -191,6 +191,11 @@ public: SWGSDRangel::SWGDeviceState& response, SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetDeviceReportGet( + int deviceSetIndex, + SWGSDRangel::SWGDeviceReport& response, + SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetChannelsReportGet( int deviceSetIndex, SWGSDRangel::SWGChannelsDetail& response, From e27ca22c862658ec7c56b6270605474ef93a8477 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 28 May 2018 11:43:24 +0200 Subject: [PATCH 484/956] BFM demod: fixed report mapper --- plugins/channelrx/demodbfm/bfmdemod.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index 65bb03a19..cbc77d2e7 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -678,6 +678,10 @@ void BFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response response.getBfmDemodReport()->setRdsReport(new SWGSDRangel::SWGRDSReport()); webapiFormatRDSReport(response.getBfmDemodReport()->getRdsReport()); } + else + { + response.getBfmDemodReport()->setRdsReport(0); + } } void BFMDemod::webapiFormatRDSReport(SWGSDRangel::SWGRDSReport *report) From 7feec443a7c9128ea625048d6f7fa98d17424195 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 28 May 2018 11:44:32 +0200 Subject: [PATCH 485/956] Web API: fixed mapper channels and reports initialization --- sdrbase/webapi/webapirequestmapper.cpp | 54 ++++++++++++++++++++++++++ sdrbase/webapi/webapirequestmapper.h | 1 + 2 files changed, 55 insertions(+) diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 5162fdedc..135557b45 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1254,6 +1254,7 @@ void WebAPIRequestMapper::devicesetDeviceReportService(const std::string& indexS try { SWGSDRangel::SWGDeviceReport normalResponse; + resetDeviceReport(normalResponse); int deviceSetIndex = boost::lexical_cast(indexStr); int status = m_adapter->devicesetDeviceReportGet(deviceSetIndex, normalResponse, errorResponse); response.setStatus(status); @@ -2256,28 +2257,81 @@ void WebAPIRequestMapper::resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& de { deviceSettings.cleanup(); deviceSettings.setDeviceHwType(0); + deviceSettings.setAirspySettings(0); + deviceSettings.setAirspyHfSettings(0); + deviceSettings.setBladeRfInputSettings(0); + deviceSettings.setBladeRfOutputSettings(0); + deviceSettings.setFcdProPlusSettings(0); + deviceSettings.setFcdProSettings(0); deviceSettings.setFileSourceSettings(0); deviceSettings.setHackRfInputSettings(0); deviceSettings.setHackRfOutputSettings(0); deviceSettings.setLimeSdrInputSettings(0); deviceSettings.setLimeSdrOutputSettings(0); + deviceSettings.setPerseusSettings(0); + deviceSettings.setPlutoSdrInputSettings(0); + deviceSettings.setPlutoSdrOutputSettings(0); deviceSettings.setRtlSdrSettings(0); + deviceSettings.setSdrDaemonSinkSettings(0); + deviceSettings.setSdrDaemonSourceSettings(0); + deviceSettings.setSdrPlaySettings(0); + deviceSettings.setTestSourceSettings(0); +} + +void WebAPIRequestMapper::resetDeviceReport(SWGSDRangel::SWGDeviceReport& deviceReport) +{ + deviceReport.cleanup(); + deviceReport.setDeviceHwType(0); + deviceReport.setAirspyHfReport(0); + deviceReport.setAirspyReport(0); + deviceReport.setFileSourceReport(0); + deviceReport.setLimeSdrInputReport(0); + deviceReport.setLimeSdrOutputReport(0); + deviceReport.setPerseusReport(0); + deviceReport.setPlutoSdrInputReport(0); + deviceReport.setPlutoSdrOutputReport(0); + deviceReport.setRtlSdrReport(0); + deviceReport.setSdrDaemonSinkReport(0); + deviceReport.setSdrDaemonSourceReport(0); + deviceReport.setSdrPlayReport(0); } void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings) { channelSettings.cleanup(); channelSettings.setChannelType(0); + channelSettings.setAmDemodSettings(0); + channelSettings.setAmModSettings(0); + channelSettings.setAtvModSettings(0); + channelSettings.setBfmDemodSettings(0); + channelSettings.setDsdDemodSettings(0); channelSettings.setNfmDemodSettings(0); channelSettings.setNfmModSettings(0); + channelSettings.setSsbDemodSettings(0); + channelSettings.setSsbModSettings(0); + channelSettings.setUdpSinkSettings(0); + channelSettings.setUdpSrcSettings(0); + channelSettings.setWfmDemodSettings(0); + channelSettings.setWfmModSettings(0); } void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& channelReport) { channelReport.cleanup(); channelReport.setChannelType(0); + channelReport.setAmDemodReport(0); + channelReport.setAmModReport(0); + channelReport.setAtvModReport(0); + channelReport.setBfmDemodReport(0); + channelReport.setDsdDemodReport(0); channelReport.setNfmDemodReport(0); channelReport.setNfmModReport(0); + channelReport.setSsbDemodReport(0); + channelReport.setSsbModReport(0); + channelReport.setUdpSinkReport(0); + channelReport.setUdpSrcReport(0); + channelReport.setWfmDemodReport(0); + channelReport.setWfmModReport(0); } void WebAPIRequestMapper::resetAudioInputDevice(SWGSDRangel::SWGAudioInputDevice& audioInputDevice) diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index d56aaf451..92c35a809 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -94,6 +94,7 @@ private: bool parseJsonBody(QString& jsonStr, QJsonObject& jsonObject, qtwebapp::HttpResponse& response); void resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings); + void resetDeviceReport(SWGSDRangel::SWGDeviceReport& deviceReport); void resetChannelSettings(SWGSDRangel::SWGChannelSettings& deviceSettings); void resetChannelReport(SWGSDRangel::SWGChannelReport& deviceSettings); void resetAudioInputDevice(SWGSDRangel::SWGAudioInputDevice& audioInputDevice); From a8966789c00d82a07c5d1f95e3fd4d67999d2620 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 28 May 2018 11:52:24 +0200 Subject: [PATCH 486/956] Server: added BFM demod plugin --- plugins/channelrx/demodbfm/bfmplugin.cpp | 12 ++++- pluginssrv/channelrx/CMakeLists.txt | 1 + pluginssrv/channelrx/demodbfm/CMakeLists.txt | 55 ++++++++++++++++++++ swagger/sdrangel/examples/rx_test.py | 30 +++++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 pluginssrv/channelrx/demodbfm/CMakeLists.txt diff --git a/plugins/channelrx/demodbfm/bfmplugin.cpp b/plugins/channelrx/demodbfm/bfmplugin.cpp index 55ffb78dc..1d08ffad4 100644 --- a/plugins/channelrx/demodbfm/bfmplugin.cpp +++ b/plugins/channelrx/demodbfm/bfmplugin.cpp @@ -20,7 +20,9 @@ #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "bfmdemodgui.h" +#endif #include "bfmdemod.h" const PluginDescriptor BFMPlugin::m_pluginDescriptor = { @@ -51,11 +53,19 @@ void BFMPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(BFMDemod::m_channelIdURI, BFMDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* BFMPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* BFMPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return BFMDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } - +#endif BasebandSampleSink* BFMPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index 5e24cddff..25438c882 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -1,4 +1,5 @@ project(demod) add_subdirectory(demodam) +add_subdirectory(demodbfm) add_subdirectory(demodnfm) diff --git a/pluginssrv/channelrx/demodbfm/CMakeLists.txt b/pluginssrv/channelrx/demodbfm/CMakeLists.txt new file mode 100644 index 000000000..dffd1854a --- /dev/null +++ b/pluginssrv/channelrx/demodbfm/CMakeLists.txt @@ -0,0 +1,55 @@ +project(bfm) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demodbfm") + +set(bfm_SOURCES + ${PLUGIN_PREFIX}/bfmdemod.cpp + ${PLUGIN_PREFIX}/bfmdemodsettings.cpp + ${PLUGIN_PREFIX}/bfmplugin.cpp + ${PLUGIN_PREFIX}/rdsdemod.cpp + ${PLUGIN_PREFIX}/rdsdecoder.cpp + ${PLUGIN_PREFIX}/rdsparser.cpp + ${PLUGIN_PREFIX}/rdstmc.cpp +) + +set(bfm_HEADERS + ${PLUGIN_PREFIX}/bfmdemod.h + ${PLUGIN_PREFIX}/bfmdemodsettings.h + ${PLUGIN_PREFIX}/bfmplugin.h + ${PLUGIN_PREFIX}/rdsdemod.h + ${PLUGIN_PREFIX}/rdsdecoder.h + ${PLUGIN_PREFIX}/rdsparser.h + ${PLUGIN_PREFIX}/rdstmc.h +) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set_source_files_properties(rdstmc.cpp PROPERTIES COMPILE_FLAGS -fno-var-tracking-assignments) +endif() + +include_directories( + . + ${CMAKE_CURRENT_BINARY_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) + +add_library(demodbfmsrv SHARED + ${bfm_SOURCES} + ${bfm_HEADERS_MOC} + ${bfm_FORMS_HEADERS} +) + +target_link_libraries(demodbfmsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +qt5_use_modules(demodbfmsrv Core) + +install(TARGETS demodbfmsrv DESTINATION lib/pluginssrv/channelrx) diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 238617a6f..e22791769 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -38,6 +38,12 @@ def getInputOptions(): parser.add_option("--stereo", dest="stereo", help="Broadcast FM stereo", metavar="BOOL", action="store_true", default=False) parser.add_option("--lsb-stereo", dest="lsb_stereo", help="Broadcast FM LSB stereo", metavar="BOOL", action="store_true", default=False) parser.add_option("--rds", dest="rds", help="Broadcast FM RDS", metavar="BOOL", action="store_true", default=False) + parser.add_option("--audio-name", dest="audio_name", help="Audio: audio device name", metavar="STRING", type="string") + parser.add_option("--audio-udp", dest="audio_udp", help="Audio: set copy to UDP", metavar="BOOL_INT", type="int") + parser.add_option("--audio-rtp", dest="audio_rtp", help="Audio: use RTP over UDP", metavar="BOOL_INT", type="int") + parser.add_option("--audio-address", dest="audio_address", help="Audio: UDP destination address", metavar="IP_ADDRESS", type="string") + parser.add_option("--audio-port", dest="audio_port", help="Audio: UDP destination port", metavar="IP_PORT", type="int") + parser.add_option("--audio-channels", dest="audio_channels", help="Audio: UDP mode (0: L only 1: R only 2: L+R mono 3: LR stereo)", metavar="ENUM_INT", type="int") (options, args) = parser.parse_args() @@ -77,6 +83,7 @@ def printResponse(response): def callAPI(url, method, params, json, text): request_method = requests_methods.get(method, None) if request_method is not None: + #print(base_url, url, json) r = request_method(url=base_url+url, params=params, json=json) if r.status_code / 100 == 2: print(text + " succeeded") @@ -87,6 +94,26 @@ def callAPI(url, method, params, json, text): printResponse(r) return None +# ====================================================================== +def setup_audio(options): + audio_dict = {} + if options.audio_name: # must not be None and reference a valid audio device + audio_dict["name"] = options.audio_name + if options.audio_udp: + audio_dict["copyToUDP"] = 0 if options.audio_udp == 0 else 1 + if options.audio_rtp: + audio_dict["udpUsesRTP"] = 0 if options.audio_rtp == 0 else 1 + if options.audio_address: + audio_dict["udpAddress"] = options.audio_address + if options.audio_port: + audio_dict["udpPort"] = options.audio_port + if options.audio_channels: + audio_dict["udpChannelMode"] = 0 if options.audio_channels < 0 else 3 if options.audio_channels > 3 else options.audio_channels + + r = callAPI('/audio/output/parameters', "PATCH", None, audio_dict, "setup audio {}".format(options.audio_name)) + if r is None: + exit(-1) + # ====================================================================== def setupDevice(deviceset_url, options): r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "%s" % options.device_hwid, "tx": 0}, "setup device on Rx device set") @@ -284,6 +311,9 @@ def main(): base_url = "http://%s/sdrangel" % options.address deviceset_url = "/deviceset/%d" % options.device_index + if options.audio_name: + setup_audio(options) + if options.create: r = callAPI("/deviceset", "POST", {"tx": 0}, None, "Add Rx device set") if r is None: From f11cda633d78d44dde881bbce18e92b2dab0bf54 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 00:14:45 +0200 Subject: [PATCH 487/956] BFM demod: fixed squelch to be compatible with server mode --- plugins/channelrx/demodbfm/bfmdemod.cpp | 32 ++++++++++++------------- sdrbase/audio/audiodevicemanager.cpp | 10 ++++++-- swagger/sdrangel/examples/rx_test.py | 14 ++++++++--- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index cbc77d2e7..f9836e411 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -145,26 +145,26 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_magsqCount++; -// m_movingAverage.feed(msq); - - if(m_magsq >= m_squelchLevel) { - m_squelchState = m_settings.m_rfBandwidth / 20; // decay rate - } - - if(m_squelchState > 0) + if (msq >= m_squelchLevel) { - m_squelchState--; - - //demod = phaseDiscriminator2(rf[i], msq); - demod = m_phaseDiscri.phaseDiscriminator(rf[i]); + if (m_squelchState < m_settings.m_rfBandwidth / 10) { // twice attack and decay rate + m_squelchState++; + } } else { + if (m_squelchState > 0) { + m_squelchState--; + } + } + + if (m_squelchState > m_settings.m_rfBandwidth / 20) { // squelch open + demod = m_phaseDiscri.phaseDiscriminator(rf[i]); + } else { demod = 0; } - if (!m_settings.m_showPilot) - { + if (!m_settings.m_showPilot) { m_sampleBuffer.push_back(Sample(demod * SDR_RX_SCALEF, 0.0)); } @@ -179,8 +179,7 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_rdsDemod.process(cr.real(), bit)) { - if (m_rdsDecoder.frameSync(bit)) - { + if (m_rdsDecoder.frameSync(bit)) { m_rdsParser.parseGroup(m_rdsDecoder.getGroup()); } } @@ -197,8 +196,7 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { m_pilotPLL.process(demod, m_pilotPLLSamples); - if (m_settings.m_showPilot) - { + if (m_settings.m_showPilot) { m_sampleBuffer.push_back(Sample(m_pilotPLLSamples[1] * SDR_RX_SCALEF, 0.0)); // debug 38 kHz pilot } diff --git a/sdrbase/audio/audiodevicemanager.cpp b/sdrbase/audio/audiodevicemanager.cpp index d150b7244..439bbfbd0 100644 --- a/sdrbase/audio/audiodevicemanager.cpp +++ b/sdrbase/audio/audiodevicemanager.cpp @@ -543,12 +543,15 @@ void AudioDeviceManager::setOutputDeviceInfo(int outputDeviceIndex, const Output if (!getOutputDeviceInfo(deviceName, oldDeviceInfo)) { - qDebug("AudioDeviceManager::setOutputDeviceInfo: unknown device %s", qPrintable(deviceName)); + qInfo("AudioDeviceManager::setOutputDeviceInfo: unknown device %s", qPrintable(deviceName)); } m_audioOutputInfos[deviceName] = deviceInfo; - if (m_audioOutputs.find(outputDeviceIndex) == m_audioOutputs.end()) { // no FIFO registered yet hence no audio output has been allocated yet + if (m_audioOutputs.find(outputDeviceIndex) == m_audioOutputs.end()) + { + qWarning("AudioDeviceManager::setOutputDeviceInfo: index: %d device: %s no FIFO registered yet hence no audio output has been allocated yet", + outputDeviceIndex, qPrintable(deviceName)); return; } @@ -575,6 +578,9 @@ void AudioDeviceManager::setOutputDeviceInfo(int outputDeviceIndex, const Output audioOutput->setUdpUseRTP(deviceInfo.udpUseRTP); audioOutput->setUdpChannelMode(deviceInfo.udpChannelMode); audioOutput->setUdpChannelFormat(deviceInfo.udpChannelMode == AudioOutput::UDPChannelStereo, deviceInfo.sampleRate); + + qDebug("AudioDeviceManager::setOutputDeviceInfo: index: %d device: %s updated", + outputDeviceIndex, qPrintable(deviceName)); } void AudioDeviceManager::unsetOutputDeviceInfo(int outputDeviceIndex) diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index e22791769..e22365064 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -301,6 +301,10 @@ def setupChannel(deviceset_url, options): if r is None: exit(-1) +# ====================================================================== +def channelsReport(deviceset_url): + report = callAPI(deviceset_url + "/channels/report", "GET", None, None, "Get channels report") + # ====================================================================== def main(): @@ -311,9 +315,6 @@ def main(): base_url = "http://%s/sdrangel" % options.address deviceset_url = "/deviceset/%d" % options.device_index - if options.audio_name: - setup_audio(options) - if options.create: r = callAPI("/deviceset", "POST", {"tx": 0}, None, "Add Rx device set") if r is None: @@ -326,6 +327,13 @@ def main(): if r is None: exit(-1) + if options.audio_name: + time.sleep(1) + setup_audio(options) + +# if options.channel_id == "BFMDemod": +# channelsReport(deviceset_url) + except Exception, msg: tb = traceback.format_exc() print >> sys.stderr, tb From 9b42dd1bfa205893df9e004409b96093a34d61bf Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 00:33:27 +0200 Subject: [PATCH 488/956] Server: added DSD demod plugin --- plugins/channelrx/demoddsd/dsddemodplugin.cpp | 11 +++ pluginssrv/channelrx/CMakeLists.txt | 1 + pluginssrv/channelrx/demoddsd/CMakeLists.txt | 68 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 pluginssrv/channelrx/demoddsd/CMakeLists.txt diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index 577846410..afeed48da 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -20,7 +20,9 @@ #include #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "dsddemodgui.h" +#endif #include "dsddemod.h" const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { @@ -51,10 +53,19 @@ void DSDDemodPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(DSDDemod::m_channelIdURI, DSDDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* DSDDemodPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* DSDDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return DSDDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* DSDDemodPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index 25438c882..38a342c1f 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -2,4 +2,5 @@ project(demod) add_subdirectory(demodam) add_subdirectory(demodbfm) +add_subdirectory(demoddsd) add_subdirectory(demodnfm) diff --git a/pluginssrv/channelrx/demoddsd/CMakeLists.txt b/pluginssrv/channelrx/demoddsd/CMakeLists.txt new file mode 100644 index 000000000..79c481a00 --- /dev/null +++ b/pluginssrv/channelrx/demoddsd/CMakeLists.txt @@ -0,0 +1,68 @@ +project(dsddemod) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demoddsd") + +set(dsddemod_SOURCES + ${PLUGIN_PREFIX}/dsddemod.cpp + ${PLUGIN_PREFIX}/dsddemodplugin.cpp + ${PLUGIN_PREFIX}/dsddemodbaudrates.cpp + ${PLUGIN_PREFIX}/dsddemodsettings.cpp + ${PLUGIN_PREFIX}/dsddecoder.cpp +) + +set(dsddemod_HEADERS + ${PLUGIN_PREFIX}/dsddemod.h + ${PLUGIN_PREFIX}/dsddemodplugin.h + ${PLUGIN_PREFIX}/dsddemodbaudrates.h + ${PLUGIN_PREFIX}/dsddemodsettings.h + ${PLUGIN_PREFIX}/dsddecoder.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${LIBDSDCCSRC} + ${LIBMBELIBSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBDSDCC_INCLUDE_DIR} + ${LIBMBE_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(demoddsdsrv SHARED + ${dsddemod_SOURCES} + ${dsddemod_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(demoddsdsrv + ${QT_LIBRARIES} + sdrbase + dsdcc + mbelib +) +else (BUILD_DEBIAN) +target_link_libraries(demoddsdsrv + ${QT_LIBRARIES} + sdrbase + ${LIBDSDCC_LIBRARIES} + ${LIBMBE_LIBRARY} +) +endif (BUILD_DEBIAN) + + +qt5_use_modules(demoddsdsrv Core) + +install(TARGETS demoddsdsrv DESTINATION lib/pluginssrv/channelrx) From c07bb0cd4d86b81826239a37671cb2d9256fc0c1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 01:04:23 +0200 Subject: [PATCH 489/956] DSDdemod: debug message fix --- plugins/channelrx/demoddsd/dsddemod.cpp | 32 ++++++++++++------------- swagger/sdrangel/examples/rx_test.py | 6 +++-- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index d6601cffb..806d5c4ee 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -460,22 +460,22 @@ void DSDDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) { qDebug() << "DSDDemod::applySettings: " - << " m_inputFrequencyOffset: " << m_settings.m_inputFrequencyOffset - << " m_rfBandwidth: " << m_settings.m_rfBandwidth - << " m_fmDeviation: " << m_settings.m_fmDeviation - << " m_demodGain: " << m_settings.m_demodGain - << " m_volume: " << m_settings.m_volume - << " m_baudRate: " << m_settings.m_baudRate - << " m_squelchGate" << m_settings.m_squelchGate - << " m_squelch: " << m_settings.m_squelch - << " m_audioMute: " << m_settings.m_audioMute - << " m_enableCosineFiltering: " << m_settings.m_enableCosineFiltering - << " m_syncOrConstellation: " << m_settings.m_syncOrConstellation - << " m_slot1On: " << m_settings.m_slot1On - << " m_slot2On: " << m_settings.m_slot2On - << " m_tdmaStereo: " << m_settings.m_tdmaStereo - << " m_pllLock: " << m_settings.m_pllLock - << " m_highPassFilter: "<< m_settings.m_highPassFilter + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_fmDeviation: " << settings.m_fmDeviation + << " m_demodGain: " << settings.m_demodGain + << " m_volume: " << settings.m_volume + << " m_baudRate: " << settings.m_baudRate + << " m_squelchGate" << settings.m_squelchGate + << " m_squelch: " << settings.m_squelch + << " m_audioMute: " << settings.m_audioMute + << " m_enableCosineFiltering: " << settings.m_enableCosineFiltering + << " m_syncOrConstellation: " << settings.m_syncOrConstellation + << " m_slot1On: " << settings.m_slot1On + << " m_slot2On: " << settings.m_slot2On + << " m_tdmaStereo: " << settings.m_tdmaStereo + << " m_pllLock: " << settings.m_pllLock + << " m_highPassFilter: "<< settings.m_highPassFilter << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index e22365064..80033bbd6 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -44,7 +44,9 @@ def getInputOptions(): parser.add_option("--audio-address", dest="audio_address", help="Audio: UDP destination address", metavar="IP_ADDRESS", type="string") parser.add_option("--audio-port", dest="audio_port", help="Audio: UDP destination port", metavar="IP_PORT", type="int") parser.add_option("--audio-channels", dest="audio_channels", help="Audio: UDP mode (0: L only 1: R only 2: L+R mono 3: LR stereo)", metavar="ENUM_INT", type="int") - + parser.add_option("--baud-rate", dest="baud_rate", help="DSD: baud rate in Baud", metavar="BAUD", type="int", default=4800) + parser.add_option("--fm-dev", dest="fm_deviation", help="DSD: expected FM deviation", metavar="FREQ", type="int", default=5400) + (options, args) = parser.parse_args() if options.address == None: @@ -285,7 +287,7 @@ def setupChannel(deviceset_url, options): settings["DSDDemodSettings"]["volume"] = options.volume settings["DSDDemodSettings"]["squelch"] = options.squelch_db settings["DSDDemodSettings"]["baudRate"] = options.baud_rate - settings["DSDDemodSettings"]["fmDeviation"] = options.fm_dev + settings["DSDDemodSettings"]["fmDeviation"] = options.fm_deviation settings["DSDDemodSettings"]["enableCosineFiltering"] = 1 settings["DSDDemodSettings"]["pllLock"] = 1 settings["DSDDemodSettings"]["title"] = "Channel %d" % i From f30b3fab0f59396056cf776bcc8a3187a17c82f0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 10:34:00 +0200 Subject: [PATCH 490/956] Server: added SSB demod plugin --- plugins/channelrx/demodssb/ssbplugin.cpp | 11 +++++ pluginssrv/channelrx/CMakeLists.txt | 1 + pluginssrv/channelrx/demodssb/CMakeLists.txt | 43 ++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 pluginssrv/channelrx/demodssb/CMakeLists.txt diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp index c5e930f84..646de708f 100644 --- a/plugins/channelrx/demodssb/ssbplugin.cpp +++ b/plugins/channelrx/demodssb/ssbplugin.cpp @@ -3,7 +3,9 @@ #include #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "ssbdemodgui.h" +#endif #include "ssbdemod.h" const PluginDescriptor SSBPlugin::m_pluginDescriptor = { @@ -34,10 +36,19 @@ void SSBPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(SSBDemod::m_channelIdURI, SSBDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* SSBPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SSBPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return SSBDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* SSBPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index 38a342c1f..93d93d349 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory(demodam) add_subdirectory(demodbfm) add_subdirectory(demoddsd) add_subdirectory(demodnfm) +add_subdirectory(demodssb) diff --git a/pluginssrv/channelrx/demodssb/CMakeLists.txt b/pluginssrv/channelrx/demodssb/CMakeLists.txt new file mode 100644 index 000000000..d7c87016e --- /dev/null +++ b/pluginssrv/channelrx/demodssb/CMakeLists.txt @@ -0,0 +1,43 @@ +project(ssb) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demodssb") + +set(ssb_SOURCES + ${PLUGIN_PREFIX}/ssbdemod.cpp + ${PLUGIN_PREFIX}/ssbdemodsettings.cpp + ${PLUGIN_PREFIX}/ssbplugin.cpp +) + +set(ssb_HEADERS + ${PLUGIN_PREFIX}/ssbdemod.h + ${PLUGIN_PREFIX}/ssbdemodsettings.h + ${PLUGIN_PREFIX}/ssbplugin.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_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_cpp(ssb_HEADERS_MOC ${ssb_HEADERS}) + +add_library(demodssbsrv SHARED + ${ssb_SOURCES} + ${ssb_HEADERS_MOC} +) + +target_link_libraries(demodssbsrv + ${QT_LIBRARIES} + sdrbase +) + +qt5_use_modules(demodssbsrv Core) + +install(TARGETS demodssbsrv DESTINATION lib/pluginssrv/channelrx) From f3aa41d76c137a811889594d69adbbca4a575333 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 10:56:30 +0200 Subject: [PATCH 491/956] Server: ATV modulator: fixed link to OpenCV library --- pluginssrv/channeltx/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pluginssrv/channeltx/CMakeLists.txt b/pluginssrv/channeltx/CMakeLists.txt index 6e54c0637..30118e09e 100644 --- a/pluginssrv/channeltx/CMakeLists.txt +++ b/pluginssrv/channeltx/CMakeLists.txt @@ -1,8 +1,13 @@ project(mod) +find_package(OpenCV) + add_subdirectory(modam) -add_subdirectory(modatv) add_subdirectory(modnfm) add_subdirectory(modssb) add_subdirectory(modwfm) add_subdirectory(udpsink) + +if (OpenCV_FOUND) +add_subdirectory(modatv) +endif() From c31a34ea41d0f7eff8b4bdf6994938f6259a9a0c Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 10:59:30 +0200 Subject: [PATCH 492/956] BFM demod: code cosmetic changes --- plugins/channelrx/demodbfm/bfmdemod.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index f9836e411..7dd65ee6d 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -135,11 +135,9 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto for (int i =0 ; i m_magsqPeak) - { + if (msq > m_magsqPeak) { m_magsqPeak = msq; } @@ -246,12 +244,11 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto ++m_audioBufferFill; - if(m_audioBufferFill >= m_audioBuffer.size()) + if (m_audioBufferFill >= m_audioBuffer.size()) { uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); - if(res != m_audioBufferFill) - { + if(res != m_audioBufferFill) { qDebug("BFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill); } @@ -263,20 +260,18 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } } - if(m_audioBufferFill > 0) + if (m_audioBufferFill > 0) { uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); - if(res != m_audioBufferFill) - { + if (res != m_audioBufferFill) { qDebug("BFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); } m_audioBufferFill = 0; } - if(m_sampleSink != 0) - { + if (m_sampleSink != 0) { m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true); } From 1770192116caad4041da9fbf49b6f282726e60d6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 11:14:24 +0200 Subject: [PATCH 493/956] Server: added WFM demod plugin --- plugins/channelrx/demodbfm/bfmdemod.cpp | 3 +- plugins/channelrx/demodwfm/wfmdemod.cpp | 55 +++++++++----------- plugins/channelrx/demodwfm/wfmplugin.cpp | 11 ++++ pluginssrv/channelrx/CMakeLists.txt | 1 + pluginssrv/channelrx/demodwfm/CMakeLists.txt | 43 +++++++++++++++ 5 files changed, 82 insertions(+), 31 deletions(-) create mode 100644 pluginssrv/channelrx/demodwfm/CMakeLists.txt diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index 7dd65ee6d..f17ae25d3 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -495,8 +495,7 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) if ((settings.m_squelch != m_settings.m_squelch) || force) { qDebug() << "BFMDemod::handleMessage: set m_squelchLevel"; - m_squelchLevel = std::pow(10.0, settings.m_squelch / 20.0); - m_squelchLevel *= m_squelchLevel; + m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); } if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index 5e4a993ea..81378b46f 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -107,41 +107,41 @@ void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto for (int i = 0 ; i < rf_out; i++) { - demod = m_phaseDiscri.phaseDiscriminatorDelta(rf[i], msq, fmDev); + msq = rf[i].real()*rf[i].real() + rf[i].imag()*rf[i].imag(); Real magsq = msq / (SDR_RX_SCALED*SDR_RX_SCALED); + m_magsqSum += magsq; + m_movingAverage(magsq); - m_movingAverage(magsq); - m_magsqSum += magsq; - - if (magsq > m_magsqPeak) - { + if (magsq > m_magsqPeak) { m_magsqPeak = magsq; } m_magsqCount++; - if((Real) m_movingAverage >= m_squelchLevel) - m_squelchState = m_settings.m_rfBandwidth / 20; // decay rate - - if (m_squelchState > 0) - { - m_squelchState--; - m_squelchOpen = true; - } - else - { - demod = 0; - m_squelchOpen = false; - } - - if (m_settings.m_audioMute) + if (magsq >= m_squelchLevel) { + if (m_squelchState < m_settings.m_rfBandwidth / 10) { // twice attack and decay rate + m_squelchState++; + } + } + else + { + if (m_squelchState > 0) { + m_squelchState--; + } + } + + m_squelchOpen = (m_squelchState > (m_settings.m_rfBandwidth / 20)); + + if (m_squelchOpen && !m_settings.m_audioMute) { // squelch open and not mute + demod = m_phaseDiscri.phaseDiscriminatorDelta(rf[i], msq, fmDev); + } else { demod = 0; } Complex e(demod, 0); - if(m_interpolator.decimate(&m_interpolatorDistanceRemain, e, &ci)) + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, e, &ci)) { qint16 sample = (qint16)(ci.real() * 3276.8f * m_settings.m_volume); m_sampleBuffer.push_back(Sample(sample, sample)); @@ -154,8 +154,7 @@ void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); - if(res != m_audioBufferFill) - { + if (res != m_audioBufferFill) { qDebug("WFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill); } @@ -167,12 +166,11 @@ void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } } - if(m_audioBufferFill > 0) + if (m_audioBufferFill > 0) { uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); - if(res != m_audioBufferFill) - { + if (res != m_audioBufferFill) { qDebug("WFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); } @@ -339,8 +337,7 @@ void WFMDemod::applySettings(const WFMDemodSettings& settings, bool force) if ((settings.m_squelch != m_settings.m_squelch) || force) { qDebug() << "WFMDemod::applySettings: set m_squelchLevel"; - m_squelchLevel = pow(10.0, settings.m_squelch / 20.0); - m_squelchLevel *= m_squelchLevel; + m_squelchLevel = pow(10.0, settings.m_squelch / 10.0); } if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) diff --git a/plugins/channelrx/demodwfm/wfmplugin.cpp b/plugins/channelrx/demodwfm/wfmplugin.cpp index 735ab1a3d..fa0c2ad8f 100644 --- a/plugins/channelrx/demodwfm/wfmplugin.cpp +++ b/plugins/channelrx/demodwfm/wfmplugin.cpp @@ -3,7 +3,9 @@ #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "wfmdemodgui.h" +#endif #include "wfmdemod.h" const PluginDescriptor WFMPlugin::m_pluginDescriptor = { @@ -34,10 +36,19 @@ void WFMPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(WFMDemod::m_channelIdURI, WFMDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* WFMPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* WFMPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return WFMDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* WFMPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index 93d93d349..09ac520ab 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory(demodbfm) add_subdirectory(demoddsd) add_subdirectory(demodnfm) add_subdirectory(demodssb) +add_subdirectory(demodwfm) diff --git a/pluginssrv/channelrx/demodwfm/CMakeLists.txt b/pluginssrv/channelrx/demodwfm/CMakeLists.txt new file mode 100644 index 000000000..ef69350ad --- /dev/null +++ b/pluginssrv/channelrx/demodwfm/CMakeLists.txt @@ -0,0 +1,43 @@ +project(wfm) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demodwfm") + +set(wfm_SOURCES + ${PLUGIN_PREFIX}/wfmdemod.cpp + ${PLUGIN_PREFIX}/wfmdemodsettings.cpp + ${PLUGIN_PREFIX}/wfmplugin.cpp +) + +set(wfm_HEADERS + ${PLUGIN_PREFIX}/wfmdemod.h + ${PLUGIN_PREFIX}/wfmdemodsettings.h + ${PLUGIN_PREFIX}/wfmplugin.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_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_cpp(nfm_HEADERS_MOC ${nfm_HEADERS}) + +add_library(demodwfmsrv SHARED + ${wfm_SOURCES} + ${wfm_HEADERS_MOC} +) + +target_link_libraries(demodwfmsrv + ${QT_LIBRARIES} + sdrbase +) + +qt5_use_modules(demodwfmsrv Core) + +install(TARGETS demodwfmsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file From 358f683e375639b83bb45ca2fe25fbe7bde138a4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 11:24:20 +0200 Subject: [PATCH 494/956] Server: added UDP source plugin --- plugins/channelrx/udpsrc/udpsrc.cpp | 1 - plugins/channelrx/udpsrc/udpsrcplugin.cpp | 11 ++++++ pluginssrv/channelrx/CMakeLists.txt | 7 +++- pluginssrv/channelrx/udpsrc/CMakeLists.txt | 41 ++++++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 pluginssrv/channelrx/udpsrc/CMakeLists.txt diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index bc6e35c2b..dc2e31df8 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -30,7 +30,6 @@ #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" -#include "udpsrcgui.h" #include "udpsrc.h" const Real UDPSrc::m_agcTarget = 16384.0f; diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.cpp b/plugins/channelrx/udpsrc/udpsrcplugin.cpp index d623733eb..46e9be6f5 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.cpp +++ b/plugins/channelrx/udpsrc/udpsrcplugin.cpp @@ -20,7 +20,9 @@ #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "udpsrcgui.h" +#endif #include "udpsrc.h" const PluginDescriptor UDPSrcPlugin::m_pluginDescriptor = { @@ -51,10 +53,19 @@ void UDPSrcPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(UDPSrc::m_channelIdURI, UDPSrc::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* UDPSrcPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* UDPSrcPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return UDPSrcGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* UDPSrcPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index 09ac520ab..8864a4058 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -2,7 +2,12 @@ project(demod) add_subdirectory(demodam) add_subdirectory(demodbfm) -add_subdirectory(demoddsd) + +if((LIBDSDCC_FOUND AND LIBMBE_FOUND) OR BUILD_DEBIAN) + add_subdirectory(demoddsd) +endif() + add_subdirectory(demodnfm) add_subdirectory(demodssb) add_subdirectory(demodwfm) +add_subdirectory(udpsrc) diff --git a/pluginssrv/channelrx/udpsrc/CMakeLists.txt b/pluginssrv/channelrx/udpsrc/CMakeLists.txt new file mode 100644 index 000000000..0b0efbf05 --- /dev/null +++ b/pluginssrv/channelrx/udpsrc/CMakeLists.txt @@ -0,0 +1,41 @@ +project(udpsrc) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/udpsrc") + +set(udpsrc_SOURCES + ${PLUGIN_PREFIX}/udpsrc.cpp + ${PLUGIN_PREFIX}/udpsrcplugin.cpp + ${PLUGIN_PREFIX}/udpsrcsettings.cpp +) + +set(udpsrc_HEADERS + ${PLUGIN_PREFIX}/udpsrc.h + ${PLUGIN_PREFIX}/udpsrcplugin.h + ${PLUGIN_PREFIX}/udpsrcsettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_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) + +add_library(demodudpsrcsrv SHARED + ${udpsrc_SOURCES} + ${udpsrc_HEADERS_MOC} +) + +target_link_libraries(demodudpsrcsrv + ${QT_LIBRARIES} + sdrbase +) + +qt5_use_modules(demodudpsrcsrv Core Network) + +install(TARGETS demodudpsrcsrv DESTINATION lib/pluginssrv/channelrx) From d87fb7e348891626becc47d743e0550bb310c4ec Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 16:48:41 +0200 Subject: [PATCH 495/956] Server: added Airspy input plugin --- plugins/samplesource/airspy/airspyinput.cpp | 1 - plugins/samplesource/airspy/airspyplugin.cpp | 15 ++++- pluginssrv/samplesource/CMakeLists.txt | 6 ++ pluginssrv/samplesource/airspy/CMakeLists.txt | 65 +++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 pluginssrv/samplesource/airspy/CMakeLists.txt diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index a78ab4ff3..a22bd51d3 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -24,7 +24,6 @@ #include "SWGDeviceReport.h" #include "SWGAirspyReport.h" -#include "airspygui.h" #include "airspyinput.h" #include "airspyplugin.h" diff --git a/plugins/samplesource/airspy/airspyplugin.cpp b/plugins/samplesource/airspy/airspyplugin.cpp index 91eadbb08..a3a65cf75 100644 --- a/plugins/samplesource/airspy/airspyplugin.cpp +++ b/plugins/samplesource/airspy/airspyplugin.cpp @@ -15,10 +15,13 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include +#ifdef SERVER_MODE +#include "airspyinput.h" +#else #include "airspygui.h" +#endif #include "airspyplugin.h" #include @@ -125,6 +128,15 @@ PluginInterface::SamplingDevices AirspyPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* AirspyPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* AirspyPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -141,6 +153,7 @@ PluginInstanceGUI* AirspyPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *AirspyPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 075a0727f..a556439a2 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -13,6 +13,11 @@ if(V4L-MSI) # add_subdirectory(v4l-msi) endif() +find_package(LibAIRSPY) +if(LIBUSB_FOUND AND LIBAIRSPY_FOUND) + add_subdirectory(airspy) +endif(LIBUSB_FOUND AND LIBAIRSPY_FOUND) + find_package(LibAIRSPYHF) if(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) add_subdirectory(airspyhf) @@ -39,6 +44,7 @@ if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) if (BUILD_DEBIAN) + add_subdirectory(airspy) add_subdirectory(airspyhf) add_subdirectory(hackrfinput) add_subdirectory(limesdrinput) diff --git a/pluginssrv/samplesource/airspy/CMakeLists.txt b/pluginssrv/samplesource/airspy/CMakeLists.txt new file mode 100644 index 000000000..2ce19f4f6 --- /dev/null +++ b/pluginssrv/samplesource/airspy/CMakeLists.txt @@ -0,0 +1,65 @@ +project(airspy) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/airspy") + +set(airspy_SOURCES + ${PLUGIN_PREFIX}/airspyinput.cpp + ${PLUGIN_PREFIX}/airspyplugin.cpp + ${PLUGIN_PREFIX}/airspysettings.cpp + ${PLUGIN_PREFIX}/airspythread.cpp +) + +set(airspy_HEADERS + ${PLUGIN_PREFIX}/airspyinput.h + ${PLUGIN_PREFIX}/airspyplugin.h + ${PLUGIN_PREFIX}/airspysettings.h + ${PLUGIN_PREFIX}/airspythread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBAIRSPYSRC} + ${LIBAIRSPYSRC}/libairspy/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBAIRSPY_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputairspysrv SHARED + ${airspy_SOURCES} + ${airspy_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputairspysrv + ${QT_LIBRARIES} + airspy + sdrbase + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(inputairspysrv + ${QT_LIBRARIES} + ${LIBAIRSPY_LIBRARIES} + sdrbase + swagger +) +endif (BUILD_DEBIAN) + + +qt5_use_modules(inputairspysrv Core) + +install(TARGETS inputairspysrv DESTINATION lib/pluginssrv/samplesource) From 3542e43b573738f2b19ff56cffcc9da515fb0ca1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 17:00:53 +0200 Subject: [PATCH 496/956] Server: added FCDPro input plugin --- plugins/samplesource/CMakeLists.txt | 98 +++++++++---------- plugins/samplesource/fcdpro/fcdproinput.cpp | 1 - plugins/samplesource/fcdpro/fcdproplugin.cpp | 15 ++- pluginssrv/samplesource/CMakeLists.txt | 9 ++ pluginssrv/samplesource/fcdpro/CMakeLists.txt | 48 +++++++++ 5 files changed, 120 insertions(+), 51 deletions(-) create mode 100644 pluginssrv/samplesource/fcdpro/CMakeLists.txt diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index 3bfb5c675..ee7866c33 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -13,25 +13,6 @@ if(V4L-MSI) # add_subdirectory(v4l-msi) endif() -if(LIBUSB_FOUND AND UNIX) - FIND_PATH (ASOUNDH alsa/asoundlib.h) - FIND_LIBRARY (LIBASOUND asound) -endif() -if(LIBASOUND AND ASOUNDH) - add_subdirectory(fcdpro) - add_subdirectory(fcdproplus) -endif() - -find_package(LibRTLSDR) -if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) - add_subdirectory(rtlsdr) -endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) - -find_package(LibBLADERF) -if(LIBUSB_FOUND AND LIBBLADERF_FOUND) - add_subdirectory(bladerfinput) -endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) - find_package(LibAIRSPY) if(LIBUSB_FOUND AND LIBAIRSPY_FOUND) add_subdirectory(airspy) @@ -42,11 +23,52 @@ if(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) add_subdirectory(airspyhf) endif(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) +find_package(LibBLADERF) +if(LIBUSB_FOUND AND LIBBLADERF_FOUND) + add_subdirectory(bladerfinput) +endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) + +if(LIBUSB_FOUND AND UNIX) + FIND_PATH (ASOUNDH alsa/asoundlib.h) + FIND_LIBRARY (LIBASOUND asound) +endif() +if(LIBASOUND AND ASOUNDH) + add_subdirectory(fcdpro) + add_subdirectory(fcdproplus) +endif() + find_package(LibHACKRF) if(LIBUSB_FOUND AND LIBHACKRF_FOUND) add_subdirectory(hackrfinput) endif(LIBUSB_FOUND AND LIBHACKRF_FOUND) +find_package(LimeSuite) +if(LIBUSB_FOUND AND LIMESUITE_FOUND) + add_subdirectory(limesdrinput) +endif(LIBUSB_FOUND AND LIMESUITE_FOUND) + +find_package(LibPerseus) +if(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) + message(STATUS "Add Persesus plugin") + add_subdirectory(perseus) +endif(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) + +find_package(LibIIO) +if(LIBUSB_FOUND AND LIBIIO_FOUND) + add_subdirectory(plutosdrinput) +endif(LIBUSB_FOUND AND LIBIIO_FOUND) + +find_package(LibRTLSDR) +if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) + add_subdirectory(rtlsdr) +endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) + +find_package(CM256cc) +find_package(LibNANOMSG) +if(CM256CC_FOUND AND LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsource) +endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) + find_package(LibMiriSDR) if(LIBUSB_FOUND AND LIBMIRISDR_FOUND) add_subdirectory(sdrplay) @@ -55,41 +77,19 @@ else(LIBUSB_FOUND AND LIBMIRISDR_FOUND) message(STATUS "LibMiriSDR NOT found") endif(LIBUSB_FOUND AND LIBMIRISDR_FOUND) -find_package(LimeSuite) -if(LIBUSB_FOUND AND LIMESUITE_FOUND) - add_subdirectory(limesdrinput) -endif(LIBUSB_FOUND AND LIMESUITE_FOUND) - -find_package(LibIIO) -if(LIBUSB_FOUND AND LIBIIO_FOUND) - add_subdirectory(plutosdrinput) -endif(LIBUSB_FOUND AND LIBIIO_FOUND) - -find_package(CM256cc) -find_package(LibNANOMSG) -if(CM256CC_FOUND AND LIBNANOMSG_FOUND) - add_subdirectory(sdrdaemonsource) -endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) - -find_package(LibPerseus) -if(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) - message(STATUS "Add Persesus plugin") - add_subdirectory(perseus) -endif(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) - if (BUILD_DEBIAN) + add_subdirectory(airspy) + add_subdirectory(airspyhf) + add_subdirectory(bladerfinput) + add_subdirectory(hackrfinput) + add_subdirectory(limesdrinput) + add_subdirectory(perseus) + add_subdirectory(plutosdrinput) + add_subdirectory(rtlsdr) if (LIBNANOMSG_FOUND) add_subdirectory(sdrdaemonsource) endif (LIBNANOMSG_FOUND) - add_subdirectory(airspy) - add_subdirectory(airspyhf) - add_subdirectory(hackrfinput) - add_subdirectory(rtlsdr) - add_subdirectory(bladerfinput) add_subdirectory(sdrplay) - add_subdirectory(limesdrinput) - add_subdirectory(perseus) - add_subdirectory(plutosdrinput) endif (BUILD_DEBIAN) add_subdirectory(filesource) diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index a0f748efa..5059a79fd 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -31,7 +31,6 @@ #include -#include "fcdprogui.h" #include "fcdprothread.h" #include "fcdtraits.h" #include "fcdproconst.h" diff --git a/plugins/samplesource/fcdpro/fcdproplugin.cpp b/plugins/samplesource/fcdpro/fcdproplugin.cpp index 7b81c8f87..e42f3047d 100644 --- a/plugins/samplesource/fcdpro/fcdproplugin.cpp +++ b/plugins/samplesource/fcdpro/fcdproplugin.cpp @@ -15,14 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "fcdproplugin.h" #include +#ifdef SERVER_MODE +#include "fcdproinput.h" +#else #include "fcdprogui.h" +#endif #include "fcdtraits.h" const PluginDescriptor FCDProPlugin::m_pluginDescriptor = { @@ -78,6 +81,15 @@ PluginInterface::SamplingDevices FCDProPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* FCDProPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* FCDProPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -94,6 +106,7 @@ PluginInstanceGUI* FCDProPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *FCDProPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index a556439a2..4bc82f663 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -28,6 +28,15 @@ if(LIBUSB_FOUND AND LIBBLADERF_FOUND) add_subdirectory(bladerfinput) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) +if(LIBUSB_FOUND AND UNIX) + FIND_PATH (ASOUNDH alsa/asoundlib.h) + FIND_LIBRARY (LIBASOUND asound) +endif() +if(LIBASOUND AND ASOUNDH) + add_subdirectory(fcdpro) +# add_subdirectory(fcdproplus) +endif() + find_package(LibHACKRF) if(LIBUSB_FOUND AND LIBHACKRF_FOUND) add_subdirectory(hackrfinput) diff --git a/pluginssrv/samplesource/fcdpro/CMakeLists.txt b/pluginssrv/samplesource/fcdpro/CMakeLists.txt new file mode 100644 index 000000000..ffcd0948b --- /dev/null +++ b/pluginssrv/samplesource/fcdpro/CMakeLists.txt @@ -0,0 +1,48 @@ +project(fcdpro) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/fcdpro") + +set(fcdpro_SOURCES + ${PLUGIN_PREFIX}/fcdproinput.cpp + ${PLUGIN_PREFIX}/fcdproplugin.cpp + ${PLUGIN_PREFIX}/fcdprosettings.cpp + ${PLUGIN_PREFIX}/fcdprothread.cpp +) + +set(fcdpro_HEADERS + ${PLUGIN_PREFIX}/fcdproinput.h + ${PLUGIN_PREFIX}/fcdproplugin.h + ${PLUGIN_PREFIX}/fcdprosettings.h + ${PLUGIN_PREFIX}/fcdprothread.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/fcdhid + ${CMAKE_SOURCE_DIR}/fcdlib +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputfcdprosrv SHARED + ${fcdpro_SOURCES} + ${fcdpro_HEADERS_MOC} +) + +target_link_libraries(inputfcdprosrv + ${QT_LIBRARIES} + asound + fcdhid + fcdlib + sdrbase + swagger +) + +qt5_use_modules(inputfcdprosrv Core) + +install(TARGETS inputfcdprosrv DESTINATION lib/pluginssrv/samplesource) \ No newline at end of file From 26ebcc6d1dfa0f516a6060ccb1d76559224dcd18 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 17:17:57 +0200 Subject: [PATCH 497/956] Server: added FCDPro Plus input plugin --- plugins/samplesource/CMakeLists.txt | 2 +- .../samplesource/fcdproplus/fcdproplusinput.cpp | 1 - .../samplesource/fcdproplus/fcdproplusplugin.cpp | 15 ++++++++++++++- pluginssrv/samplesource/CMakeLists.txt | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index ee7866c33..3a2419ee9 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -8,7 +8,7 @@ if(V4L-RTL) # add_subdirectory(v4l-rtl) endif() if(V4L-MSI) - FIND_LIBRARY (LIBV4L2 v4l2) + FIND_LIBRARY (LIBV4L2 v4l2) FIND_PATH (LIBV4L2H libv4l2.h) # add_subdirectory(v4l-msi) endif() diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index 8bbc3fac7..a15a0819e 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -30,7 +30,6 @@ #include -#include "fcdproplusgui.h" #include "fcdproplusthread.h" #include "fcdtraits.h" #include "fcdproplusconst.h" diff --git a/plugins/samplesource/fcdproplus/fcdproplusplugin.cpp b/plugins/samplesource/fcdproplus/fcdproplusplugin.cpp index 419563aa6..16207daa0 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusplugin.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusplugin.cpp @@ -15,14 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "fcdproplusplugin.h" #include +#ifdef SERVER_MODE +#include "fcdproplusinput.h" +#else #include "fcdproplusgui.h" +#endif #include "fcdtraits.h" const PluginDescriptor FCDProPlusPlugin::m_pluginDescriptor = { @@ -80,6 +83,15 @@ PluginInterface::SamplingDevices FCDProPlusPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* FCDProPlusPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* FCDProPlusPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -96,6 +108,7 @@ PluginInstanceGUI* FCDProPlusPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *FCDProPlusPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 4bc82f663..702b153b7 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -34,7 +34,7 @@ if(LIBUSB_FOUND AND UNIX) endif() if(LIBASOUND AND ASOUNDH) add_subdirectory(fcdpro) -# add_subdirectory(fcdproplus) + add_subdirectory(fcdproplus) endif() find_package(LibHACKRF) From 362e8a629d0ed369897e1f7c69ec2dd24518a2e7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 17:35:20 +0200 Subject: [PATCH 498/956] Server: added Perseus input plugin --- .../samplesource/perseus/perseusplugin.cpp | 17 ++++- pluginssrv/samplesource/CMakeLists.txt | 7 ++ .../samplesource/fcdproplus/CMakeLists.txt | 48 +++++++++++++ .../samplesource/perseus/CMakeLists.txt | 69 +++++++++++++++++++ 4 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 pluginssrv/samplesource/fcdproplus/CMakeLists.txt create mode 100644 pluginssrv/samplesource/perseus/CMakeLists.txt diff --git a/plugins/samplesource/perseus/perseusplugin.cpp b/plugins/samplesource/perseus/perseusplugin.cpp index 227b875e2..f51ee3005 100644 --- a/plugins/samplesource/perseus/perseusplugin.cpp +++ b/plugins/samplesource/perseus/perseusplugin.cpp @@ -15,7 +15,6 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "perseus-sdr.h" #include @@ -23,11 +22,15 @@ #include "util/simpleserializer.h" #include "perseus/deviceperseus.h" #include "perseusplugin.h" +#ifdef SERVER_MODE +#include "perseusinput.h" +#else #include "perseusgui.h" +#endif const PluginDescriptor PerseusPlugin::m_pluginDescriptor = { QString("Perseus Input"), - QString("3.14.6"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -84,6 +87,15 @@ PluginInterface::SamplingDevices PerseusPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* PerseusPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* PerseusPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -100,6 +112,7 @@ PluginInstanceGUI* PerseusPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *PerseusPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 702b153b7..4262445ce 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -47,6 +47,12 @@ if(LIBUSB_FOUND AND LIMESUITE_FOUND) add_subdirectory(limesdrinput) endif(LIBUSB_FOUND AND LIMESUITE_FOUND) +find_package(LibPerseus) +if(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) + message(STATUS "Server: add Persesus plugin") + add_subdirectory(perseus) +endif(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) + find_package(LibRTLSDR) if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) add_subdirectory(rtlsdr) @@ -57,6 +63,7 @@ if (BUILD_DEBIAN) add_subdirectory(airspyhf) add_subdirectory(hackrfinput) add_subdirectory(limesdrinput) + add_subdirectory(perseus) add_subdirectory(rtlsdr) endif (BUILD_DEBIAN) diff --git a/pluginssrv/samplesource/fcdproplus/CMakeLists.txt b/pluginssrv/samplesource/fcdproplus/CMakeLists.txt new file mode 100644 index 000000000..e23a61855 --- /dev/null +++ b/pluginssrv/samplesource/fcdproplus/CMakeLists.txt @@ -0,0 +1,48 @@ +project(fcdproplus) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/fcdproplus") + +set(fcdproplus_SOURCES + ${PLUGIN_PREFIX}/fcdproplusinput.cpp + ${PLUGIN_PREFIX}/fcdproplusplugin.cpp + ${PLUGIN_PREFIX}/fcdproplussettings.cpp + ${PLUGIN_PREFIX}/fcdproplusthread.cpp +) + +set(fcdproplus_HEADERS + ${PLUGIN_PREFIX}/fcdproplusinput.h + ${PLUGIN_PREFIX}/fcdproplusplugin.h + ${PLUGIN_PREFIX}/fcdproplussettings.h + ${PLUGIN_PREFIX}/fcdproplusthread.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/fcdhid + ${CMAKE_SOURCE_DIR}/fcdlib +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputfcdproplussrv SHARED + ${fcdproplus_SOURCES} + ${fcdproplus_HEADERS_MOC} +) + +target_link_libraries(inputfcdproplussrv + ${QT_LIBRARIES} + asound + fcdhid + fcdlib + sdrbase + swagger +) + +qt5_use_modules(inputfcdproplussrv Core) + +install(TARGETS inputfcdproplussrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/perseus/CMakeLists.txt b/pluginssrv/samplesource/perseus/CMakeLists.txt new file mode 100644 index 000000000..732db4f69 --- /dev/null +++ b/pluginssrv/samplesource/perseus/CMakeLists.txt @@ -0,0 +1,69 @@ +project(perseus) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/perseus") + +set(perseus_SOURCES + ${PLUGIN_PREFIX}/perseusinput.cpp + ${PLUGIN_PREFIX}/perseusplugin.cpp + ${PLUGIN_PREFIX}/perseussettings.cpp + ${PLUGIN_PREFIX}/perseusthread.cpp +) + +set(perseus_HEADERS + ${PLUGIN_PREFIX}/perseusinput.h + ${PLUGIN_PREFIX}/perseusplugin.h + ${PLUGIN_PREFIX}/perseussettings.h + ${PLUGIN_PREFIX}/perseusthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBPERSEUSSRC} + ${LIBPERSEUSSRC}/libperseus/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBPERSEUS_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputperseussrv SHARED + ${perseus_SOURCES} + ${perseus_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputperseussrv + ${QT_LIBRARIES} + perseus + sdrbase + swagger + perseusdevice +) +else (BUILD_DEBIAN) +target_link_libraries(inputperseussrv + ${QT_LIBRARIES} + ${LIBPERSEUS_LIBRARIES} + sdrbase + swagger + perseusdevice +) +endif (BUILD_DEBIAN) + + +qt5_use_modules(inputperseussrv Core) + +install(TARGETS inputperseussrv DESTINATION lib/pluginssrv/samplesource) From 3e31a8b23bf445b7fdfd89fcd28905cd3e75e22b Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 20:27:36 +0200 Subject: [PATCH 499/956] Server: added PlutoSDR input plugin --- .../plutosdrinput/plutosdrinput.h | 1 + .../plutosdrinput/plutosdrinputgui.h | 3 +- .../plutosdrinput/plutosdrinputplugin.cpp | 18 ++++- pluginssrv/samplesource/CMakeLists.txt | 6 ++ .../samplesource/plutosdrinput/CMakeLists.txt | 66 +++++++++++++++++++ swagger/sdrangel/examples/rx_test.py | 1 + 6 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 pluginssrv/samplesource/plutosdrinput/CMakeLists.txt diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.h b/plugins/samplesource/plutosdrinput/plutosdrinput.h index 16e3e9acb..a2191f755 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.h @@ -24,6 +24,7 @@ #include #include "util/message.h" #include "plutosdr/deviceplutosdrshared.h" +#include "plutosdr/deviceplutosdrbox.h" #include "plutosdrinputsettings.h" class DeviceSourceAPI; diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.h b/plugins/samplesource/plutosdrinput/plutosdrinputgui.h index 3e2c622db..0c5cba146 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.h @@ -17,13 +17,14 @@ #ifndef PLUGINS_SAMPLESOURCE_PLUTOSDRINPUT_PLUTOSDRINPUTGUI_H_ #define PLUGINS_SAMPLESOURCE_PLUTOSDRINPUT_PLUTOSDRINPUTGUI_H_ -#include #include #include #include #include "util/messagequeue.h" +#include "plugin/plugininstancegui.h" +#include "plutosdrinput.h" #include "plutosdrinputsettings.h" class DeviceSampleSource; diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp index 93e14c5ba..1fb15bfc6 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp @@ -15,20 +15,22 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" #include "plutosdr/deviceplutosdr.h" -#include "plutosdrinputgui.h" +#ifdef SERVER_MODE #include "plutosdrinput.h" +#else +#include "plutosdrinputgui.h" +#endif #include "plutosdrinputplugin.h" class DeviceSourceAPI; const PluginDescriptor PlutoSDRInputPlugin::m_pluginDescriptor = { QString("PlutoSDR Input"), - QString("3.14.6"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -85,6 +87,15 @@ PluginInterface::SamplingDevices PlutoSDRInputPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* PlutoSDRInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* PlutoSDRInputPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -101,6 +112,7 @@ PluginInstanceGUI* PlutoSDRInputPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *PlutoSDRInputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 4262445ce..58dcd79d3 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -53,6 +53,11 @@ if(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) add_subdirectory(perseus) endif(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) +find_package(LibIIO) +if(LIBUSB_FOUND AND LIBIIO_FOUND) + add_subdirectory(plutosdrinput) +endif(LIBUSB_FOUND AND LIBIIO_FOUND) + find_package(LibRTLSDR) if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) add_subdirectory(rtlsdr) @@ -64,6 +69,7 @@ if (BUILD_DEBIAN) add_subdirectory(hackrfinput) add_subdirectory(limesdrinput) add_subdirectory(perseus) + add_subdirectory(plutosdrinput) add_subdirectory(rtlsdr) endif (BUILD_DEBIAN) diff --git a/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt b/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt new file mode 100644 index 000000000..c56037df4 --- /dev/null +++ b/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt @@ -0,0 +1,66 @@ +project(plutosdrinput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/plutosdrinput") + +set(plutosdrinput_SOURCES + ${PLUGIN_PREFIX}/plutosdrinput.cpp + ${PLUGIN_PREFIX}/plutosdrinputplugin.cpp + ${PLUGIN_PREFIX}/plutosdrinputsettings.cpp + ${PLUGIN_PREFIX}/plutosdrinputthread.cpp +) + +set(plutosdrinput_HEADERS + ${PLUGIN_PREFIX}/plutosdrinput.h + ${PLUGIN_PREFIX}/plutosdrinputplugin.h + ${PLUGIN_PREFIX}/plutosdrinputsettings.h + ${PLUGIN_PREFIX}/plutosdrinputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBIIOSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBIIO_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputplutosdrsrv SHARED + ${plutosdrinput_SOURCES} + ${plutosdrinput_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputplutosdrsrv + ${QT_LIBRARIES} + iio + sdrbase + swagger + plutosdrdevice +) +else (BUILD_DEBIAN) +target_link_libraries(inputplutosdrsrv + ${QT_LIBRARIES} + ${LIBIIO_LIBRARIES} + sdrbase + swagger + plutosdrdevice +) +endif (BUILD_DEBIAN) + +qt5_use_modules(inputplutosdrsrv Core) + +install(TARGETS inputplutosdrsrv DESTINATION lib/plugins/samplesource) diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 80033bbd6..ae2fbf696 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -186,6 +186,7 @@ def setupDevice(deviceset_url, options): settings['plutoSdrInputSettings']['fcPos'] = options.fc_pos settings['plutoSdrInputSettings']['dcBlock'] = options.fc_pos == 2 settings['plutoSdrInputSettings']['iqImbalance'] = options.fc_pos == 2 + settings['plutoSdrInputSettings']['LOppmTenths'] = int(options.lo_ppm * 10) # in tenths of PPM elif options.device_hwid == "RTLSDR": settings['rtlSdrSettings']['devSampleRate'] = options.sample_rate*1000 settings['rtlSdrSettings']['centerFrequency'] = options.device_freq*1000 From ae4ce3912626401acc41cbcc6b07284d1e740569 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 20:46:52 +0200 Subject: [PATCH 500/956] Server: added SDRdaemon source plugin --- .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 1 - .../sdrdaemonsource/sdrdaemonsourceplugin.cpp | 19 ++++- pluginssrv/samplesource/CMakeLists.txt | 9 +++ .../sdrdaemonsource/CMakeLists.txt | 79 +++++++++++++++++++ 4 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index df62efdf6..9c1350a49 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -39,7 +39,6 @@ #include #include -#include "sdrdaemonsourcegui.h" #include "sdrdaemonsourceinput.h" #include "sdrdaemonsourceudphandler.h" diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp index f470d454a..e9fad0b43 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp @@ -15,13 +15,16 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include + #include "plugin/pluginapi.h" #include "util/simpleserializer.h" +#include "device/devicesourceapi.h" -#include - +#ifdef SERVER_MODE +#include "sdrdaemonsourceinput.h" +#else #include "sdrdaemonsourcegui.h" +#endif #include "sdrdaemonsourceplugin.h" const PluginDescriptor SDRdaemonSourcePlugin::m_pluginDescriptor = { @@ -69,6 +72,15 @@ PluginInterface::SamplingDevices SDRdaemonSourcePlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* SDRdaemonSourcePlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute((unused)), + QWidget **widget __attribute((unused)), + DeviceUISet *deviceUISet __attribute((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SDRdaemonSourcePlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -85,6 +97,7 @@ PluginInstanceGUI* SDRdaemonSourcePlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *SDRdaemonSourcePlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 58dcd79d3..a93c57bed 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -63,6 +63,12 @@ if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) add_subdirectory(rtlsdr) endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) +find_package(CM256cc) +find_package(LibNANOMSG) +if(CM256CC_FOUND AND LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsource) +endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) + if (BUILD_DEBIAN) add_subdirectory(airspy) add_subdirectory(airspyhf) @@ -71,6 +77,9 @@ if (BUILD_DEBIAN) add_subdirectory(perseus) add_subdirectory(plutosdrinput) add_subdirectory(rtlsdr) + if (LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsource) + endif (LIBNANOMSG_FOUND) endif (BUILD_DEBIAN) add_subdirectory(filesource) diff --git a/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt b/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt new file mode 100644 index 000000000..8d4bdd555 --- /dev/null +++ b/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt @@ -0,0 +1,79 @@ +project(sdrdaemonsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/sdrdaemonsource") + +if (HAS_SSSE3) + message(STATUS "SDRdaemonSource: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "SDRdaemonSource: use Neon SIMD" ) +else() + message(STATUS "SDRdaemonSource: Unsupported architecture") + return() +endif() + +set(sdrdaemonsource_SOURCES + ${PLUGIN_PREFIX}/sdrdaemonsourcebuffer.cpp + ${PLUGIN_PREFIX}/sdrdaemonsourceinput.cpp + ${PLUGIN_PREFIX}/sdrdaemonsourcesettings.cpp + ${PLUGIN_PREFIX}/sdrdaemonsourceplugin.cpp + ${PLUGIN_PREFIX}/sdrdaemonsourceudphandler.cpp +) + +set(sdrdaemonsource_HEADERS + ${PLUGIN_PREFIX}/sdrdaemonsourcebuffer.h + ${PLUGIN_PREFIX}/sdrdaemonsourceinput.h + ${PLUGIN_PREFIX}/sdrdaemonsourcesettings.h + ${PLUGIN_PREFIX}/sdrdaemonsourceplugin.h + ${PLUGIN_PREFIX}/sdrdaemonsourceudphandler.h +) + + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputsdrdaemonsourcesrv SHARED + ${sdrdaemonsource_SOURCES} + ${sdrdaemonsource_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_include_directories(inputsdrdaemonsourcesrv PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBCM256CCSRC} + ${LIBNANOMSG_INCLUDE_DIR} +) +else (BUILD_DEBIAN) +target_include_directories(inputsdrdaemonsourcesrv PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CM256CC_INCLUDE_DIR} + ${LIBNANOMSG_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +if (BUILD_DEBIAN) +target_link_libraries(inputsdrdaemonsourcesrv + ${QT_LIBRARIES} + cm256cc + ${LIBNANOMSG_LIBRARIES} + sdrbase + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(inputsdrdaemonsourcesrv + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + ${LIBNANOMSG_LIBRARIES} + sdrbase + swagger +) +endif (BUILD_DEBIAN) + +qt5_use_modules(inputsdrdaemonsourcesrv Core) + +install(TARGETS inputsdrdaemonsourcesrv DESTINATION lib/pluginssrv/samplesource) From 213d19eb42bffa14a0421bbe9d675dc4603a34cb Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 20:52:44 +0200 Subject: [PATCH 501/956] Server: added SDRplay input plugin --- plugins/samplesource/sdrplay/sdrplayinput.cpp | 1 - .../samplesource/sdrplay/sdrplayplugin.cpp | 15 ++++- pluginssrv/samplesource/CMakeLists.txt | 11 +++- .../samplesource/sdrplay/CMakeLists.txt | 63 +++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 pluginssrv/samplesource/sdrplay/CMakeLists.txt diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index 781ef22b3..cc970b956 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -27,7 +27,6 @@ #include "dsp/dspcommands.h" #include "dsp/dspengine.h" #include -#include "sdrplaygui.h" #include "sdrplayinput.h" #include diff --git a/plugins/samplesource/sdrplay/sdrplayplugin.cpp b/plugins/samplesource/sdrplay/sdrplayplugin.cpp index cb1ed4660..ebb5698ce 100644 --- a/plugins/samplesource/sdrplay/sdrplayplugin.cpp +++ b/plugins/samplesource/sdrplay/sdrplayplugin.cpp @@ -15,11 +15,14 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" +#ifdef SERVER_MODE +#include "sdrplayinput.h" +#else #include "sdrplaygui.h" +#endif #include "sdrplayplugin.h" #include @@ -87,6 +90,15 @@ PluginInterface::SamplingDevices SDRPlayPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* SDRPlayPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute((unused)), + QWidget **widget __attribute((unused)), + DeviceUISet *deviceUISet __attribute((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SDRPlayPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -103,6 +115,7 @@ PluginInstanceGUI* SDRPlayPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *SDRPlayPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index a93c57bed..05984ae57 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -69,6 +69,14 @@ if(CM256CC_FOUND AND LIBNANOMSG_FOUND) add_subdirectory(sdrdaemonsource) endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) +find_package(LibMiriSDR) +if(LIBUSB_FOUND AND LIBMIRISDR_FOUND) + add_subdirectory(sdrplay) + message(STATUS "LibMiriSDR found") +else(LIBUSB_FOUND AND LIBMIRISDR_FOUND) + message(STATUS "LibMiriSDR NOT found") +endif(LIBUSB_FOUND AND LIBMIRISDR_FOUND) + if (BUILD_DEBIAN) add_subdirectory(airspy) add_subdirectory(airspyhf) @@ -79,7 +87,8 @@ if (BUILD_DEBIAN) add_subdirectory(rtlsdr) if (LIBNANOMSG_FOUND) add_subdirectory(sdrdaemonsource) - endif (LIBNANOMSG_FOUND) + endif (LIBNANOMSG_FOUND) + add_subdirectory(sdrplay) endif (BUILD_DEBIAN) add_subdirectory(filesource) diff --git a/pluginssrv/samplesource/sdrplay/CMakeLists.txt b/pluginssrv/samplesource/sdrplay/CMakeLists.txt new file mode 100644 index 000000000..37a6d4eb8 --- /dev/null +++ b/pluginssrv/samplesource/sdrplay/CMakeLists.txt @@ -0,0 +1,63 @@ +project(sdrplay) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/sdrplay") + +set(sdrplay_SOURCES + ${PLUGIN_PREFIX}/sdrplayinput.cpp + ${PLUGIN_PREFIX}/sdrplayplugin.cpp + ${PLUGIN_PREFIX}/sdrplaysettings.cpp + ${PLUGIN_PREFIX}/sdrplaythread.cpp +) + +set(sdrplay_HEADERS + ${PLUGIN_PREFIX}/sdrplayinput.h + ${PLUGIN_PREFIX}/sdrplayplugin.h + ${PLUGIN_PREFIX}/sdrplaysettings.h + ${PLUGIN_PREFIX}/sdrplaythread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBMIRISDRSRC}/include + ${LIBMIRISDRSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBMIRISDR_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputsdrplaysrv SHARED + ${sdrplay_SOURCES} + ${sdrplay_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputsdrplaysrv + ${QT_LIBRARIES} + mirisdr + sdrbase + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(inputsdrplaysrv + ${QT_LIBRARIES} + ${LIBMIRISDR_LIBRARIES} + sdrbase + swagger +) +endif (BUILD_DEBIAN) + +qt5_use_modules(inputsdrplaysrv Core) + +install(TARGETS inputsdrplaysrv DESTINATION lib/pluginssrv/samplesource) From d8b4af6dbf9c04aa6037001d5f93aa4957c6a876 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 20:57:33 +0200 Subject: [PATCH 502/956] Server: added Test source plugin --- pluginssrv/samplesource/CMakeLists.txt | 1 + .../samplesource/testsource/CMakeLists.txt | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 pluginssrv/samplesource/testsource/CMakeLists.txt diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 05984ae57..8272a564f 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -92,3 +92,4 @@ if (BUILD_DEBIAN) endif (BUILD_DEBIAN) add_subdirectory(filesource) +add_subdirectory(testsource) diff --git a/pluginssrv/samplesource/testsource/CMakeLists.txt b/pluginssrv/samplesource/testsource/CMakeLists.txt new file mode 100644 index 000000000..0e5c96f23 --- /dev/null +++ b/pluginssrv/samplesource/testsource/CMakeLists.txt @@ -0,0 +1,43 @@ +project(testsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/testsource") + +set(testsource_SOURCES + ${PLUGIN_PREFIX}/testsourceinput.cpp + ${PLUGIN_PREFIX}/testsourceplugin.cpp + ${PLUGIN_PREFIX}/testsourcethread.cpp + ${PLUGIN_PREFIX}/testsourcesettings.cpp +) + +set(testsource_HEADERS + ${PLUGIN_PREFIX}/testsourceinput.h + ${PLUGIN_PREFIX}/testsourceplugin.h + ${PLUGIN_PREFIX}/testsourcethread.h + ${PLUGIN_PREFIX}/testsourcesettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputtestsourcesrv SHARED + ${testsource_SOURCES} + ${testsource_HEADERS_MOC} +) + +target_link_libraries(inputtestsourcesrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +qt5_use_modules(inputtestsourcesrv Core) + +install(TARGETS inputtestsourcesrv DESTINATION lib/pluginssrv/samplesource) From f30717bafa53bc035deedccf5f28369bb3eb75ff Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 22:03:47 +0200 Subject: [PATCH 503/956] Server: added PlutoSDR output plugin --- plugins/samplesink/CMakeLists.txt | 2 +- .../plutosdroutput/plutosdroutput.h | 1 + .../plutosdroutput/plutosdroutputgui.h | 3 +- .../plutosdroutput/plutosdroutputplugin.cpp | 18 ++++- pluginssrv/samplesink/CMakeLists.txt | 9 ++- .../samplesink/plutosdroutput/CMakeLists.txt | 66 +++++++++++++++++++ 6 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 pluginssrv/samplesink/plutosdroutput/CMakeLists.txt diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index 179ba540e..3d32cd02e 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -32,10 +32,10 @@ if (BUILD_DEBIAN) add_subdirectory(bladerfoutput) add_subdirectory(hackrfoutput) add_subdirectory(limesdroutput) + add_subdirectory(plutosdroutput) if (LIBNANOMSG_FOUND) add_subdirectory(sdrdaemonsink) endif (LIBNANOMSG_FOUND) - add_subdirectory(plutosdroutput) endif (BUILD_DEBIAN) add_subdirectory(filesink) diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.h b/plugins/samplesink/plutosdroutput/plutosdroutput.h index ac501fd78..b5512edc8 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.h +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.h @@ -23,6 +23,7 @@ #include #include "util/message.h" #include "plutosdr/deviceplutosdrshared.h" +#include "plutosdr/deviceplutosdrbox.h" #include "plutosdroutputsettings.h" class DeviceSinkAPI; diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputgui.h b/plugins/samplesink/plutosdroutput/plutosdroutputgui.h index f9a010f4c..6ca6f2d20 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputgui.h +++ b/plugins/samplesink/plutosdroutput/plutosdroutputgui.h @@ -17,13 +17,14 @@ #ifndef PLUGINS_SAMPLESOURCE_PLUTOSDROUTPUT_PLUTOSDROUTPUTGUI_H_ #define PLUGINS_SAMPLESOURCE_PLUTOSDROUTPUT_PLUTOSDROUTPUTGUI_H_ -#include #include #include #include #include "util/messagequeue.h" +#include "plugin/plugininstancegui.h" +#include "plutosdroutput.h" #include "plutosdroutputsettings.h" class DeviceSampleSink; diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp b/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp index 66347e51a..581c8b39d 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp @@ -15,20 +15,22 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" #include "plutosdr/deviceplutosdr.h" -#include "plutosdroutputgui.h" +#ifdef SERVER_MODE #include "plutosdroutput.h" +#else +#include "plutosdroutputgui.h" +#endif #include "plutosdroutputplugin.h" class DeviceSourceAPI; const PluginDescriptor PlutoSDROutputPlugin::m_pluginDescriptor = { QString("PlutoSDR Output"), - QString("3.14.5"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -85,6 +87,15 @@ PluginInterface::SamplingDevices PlutoSDROutputPlugin::enumSampleSinks() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* PlutoSDROutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute((unused)), + QWidget **widget __attribute((unused)), + DeviceUISet *deviceUISet __attribute((unused))) +{ + return 0; +} +#else PluginInstanceGUI* PlutoSDROutputPlugin::createSampleSinkPluginInstanceGUI( const QString& sinkId, QWidget **widget, @@ -101,6 +112,7 @@ PluginInstanceGUI* PlutoSDROutputPlugin::createSampleSinkPluginInstanceGUI( return 0; } } +#endif DeviceSampleSink *PlutoSDROutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) { diff --git a/pluginssrv/samplesink/CMakeLists.txt b/pluginssrv/samplesink/CMakeLists.txt index 9a692f7f3..2e3eb6639 100644 --- a/pluginssrv/samplesink/CMakeLists.txt +++ b/pluginssrv/samplesink/CMakeLists.txt @@ -17,9 +17,16 @@ if(LIBUSB_FOUND AND LIMESUITE_FOUND) add_subdirectory(limesdroutput) endif(LIBUSB_FOUND AND LIMESUITE_FOUND) +find_package(LibIIO) +if(LIBUSB_FOUND AND LIBIIO_FOUND) + add_subdirectory(plutosdroutput) +endif(LIBUSB_FOUND AND LIBIIO_FOUND) + if (BUILD_DEBIAN) - add_subdirectory(limesdroutput) + add_subdirectory(bladerfoutput) add_subdirectory(hackrfoutput) + add_subdirectory(limesdroutput) + add_subdirectory(bladerfoutput) endif (BUILD_DEBIAN) add_subdirectory(filesink) diff --git a/pluginssrv/samplesink/plutosdroutput/CMakeLists.txt b/pluginssrv/samplesink/plutosdroutput/CMakeLists.txt new file mode 100644 index 000000000..cf668f5f4 --- /dev/null +++ b/pluginssrv/samplesink/plutosdroutput/CMakeLists.txt @@ -0,0 +1,66 @@ +project(plutosdroutput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesink/plutosdroutput") + +set(plutosdroutput_SOURCES + ${PLUGIN_PREFIX}/plutosdroutput.cpp + ${PLUGIN_PREFIX}/plutosdroutputplugin.cpp + ${PLUGIN_PREFIX}/plutosdroutputsettings.cpp + ${PLUGIN_PREFIX}/plutosdroutputthread.cpp +) + +set(plutosdroutput_HEADERS + ${PLUGIN_PREFIX}/plutosdroutput.h + ${PLUGIN_PREFIX}/plutosdroutputplugin.h + ${PLUGIN_PREFIX}/plutosdroutputsettings.h + ${PLUGIN_PREFIX}/plutosdroutputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBIIOSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBIIO_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(outputplutosdrsrv SHARED + ${plutosdroutput_SOURCES} + ${plutosdroutput_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputplutosdrsrv + ${QT_LIBRARIES} + iio + sdrbase + swagger + plutosdrdevice +) +else (BUILD_DEBIAN) +target_link_libraries(outputplutosdrsrv + ${QT_LIBRARIES} + ${LIBIIO_LIBRARIES} + sdrbase + swagger + plutosdrdevice +) +endif (BUILD_DEBIAN) + +qt5_use_modules(outputplutosdrsrv Core) + +install(TARGETS outputplutosdrsrv DESTINATION lib/pluginssrv/samplesink) From da1b7c26582d1bfc5cb8a5371fb2a75f7375eade Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 May 2018 22:14:37 +0200 Subject: [PATCH 504/956] Server: added SDRdaemon sink plugin --- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 1 - .../sdrdaemonsink/sdrdaemonsinkplugin.cpp | 17 +++- pluginssrv/samplesink/CMakeLists.txt | 11 ++- .../samplesink/sdrdaemonsink/CMakeLists.txt | 81 +++++++++++++++++++ 4 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index c81f50c47..a90b8a301 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -30,7 +30,6 @@ #include "device/devicesinkapi.h" -#include "sdrdaemonsinkgui.h" #include "sdrdaemonsinkoutput.h" #include "sdrdaemonsinkthread.h" diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp index f5bc24726..76a3c0bb0 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp @@ -15,13 +15,16 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include + #include "plugin/pluginapi.h" #include "util/simpleserializer.h" - #include "device/devicesinkapi.h" +#ifdef SERVER_MODE +#include "sdrdaemonsinkoutput.h" +#else #include "sdrdaemonsinkgui.h" +#endif #include "sdrdaemonsinkplugin.h" const PluginDescriptor SDRdaemonSinkPlugin::m_pluginDescriptor = { @@ -69,6 +72,15 @@ PluginInterface::SamplingDevices SDRdaemonSinkPlugin::enumSampleSinks() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* SDRdaemonSinkPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute((unused)), + QWidget **widget __attribute((unused)), + DeviceUISet *deviceUISet __attribute((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SDRdaemonSinkPlugin::createSampleSinkPluginInstanceGUI( const QString& sinkId, QWidget **widget, @@ -85,6 +97,7 @@ PluginInstanceGUI* SDRdaemonSinkPlugin::createSampleSinkPluginInstanceGUI( return 0; } } +#endif DeviceSampleSink* SDRdaemonSinkPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) { diff --git a/pluginssrv/samplesink/CMakeLists.txt b/pluginssrv/samplesink/CMakeLists.txt index 2e3eb6639..1e1bd6ba2 100644 --- a/pluginssrv/samplesink/CMakeLists.txt +++ b/pluginssrv/samplesink/CMakeLists.txt @@ -22,11 +22,20 @@ if(LIBUSB_FOUND AND LIBIIO_FOUND) add_subdirectory(plutosdroutput) endif(LIBUSB_FOUND AND LIBIIO_FOUND) +find_package(CM256cc) +find_package(LibNANOMSG) +if(CM256CC_FOUND AND LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsink) +endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) + if (BUILD_DEBIAN) add_subdirectory(bladerfoutput) add_subdirectory(hackrfoutput) add_subdirectory(limesdroutput) - add_subdirectory(bladerfoutput) + add_subdirectory(plutosdroutput) + if (LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsink) + endif (LIBNANOMSG_FOUND) endif (BUILD_DEBIAN) add_subdirectory(filesink) diff --git a/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt new file mode 100644 index 000000000..f24f23701 --- /dev/null +++ b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt @@ -0,0 +1,81 @@ +project(sdrdaemonsink) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesink/sdrdaemonsink") + +if (HAS_SSSE3) + message(STATUS "SDRdaemonFEC: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "SDRdaemonFEC: use Neon SIMD" ) +else() + message(STATUS "SDRdaemonFEC: Unsupported architecture") + return() +endif() + +set(sdrdaemonsink_SOURCES + ${PLUGIN_PREFIX}/sdrdaemonsinkoutput.cpp + ${PLUGIN_PREFIX}/sdrdaemonsinkplugin.cpp + ${PLUGIN_PREFIX}/sdrdaemonsinksettings.cpp + ${PLUGIN_PREFIX}/sdrdaemonsinkthread.cpp + ${PLUGIN_PREFIX}/udpsinkfec.cpp + ${PLUGIN_PREFIX}/UDPSocket.cpp +) + +set(sdrdaemonsink_HEADERS + ${PLUGIN_PREFIX}/sdrdaemonsinkoutput.h + ${PLUGIN_PREFIX}/sdrdaemonsinkplugin.h + ${PLUGIN_PREFIX}/sdrdaemonsinksettings.h + ${PLUGIN_PREFIX}/sdrdaemonsinkthread.h + ${PLUGIN_PREFIX}/udpsinkfec.h + ${PLUGIN_PREFIX}/UDPSocket.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBCM256CCSRC} + ${LIBNANOMSG_INCLUDE_DIR} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${CM256CC_INCLUDE_DIR} + ${LIBNANOMSG_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(outputsdrdaemonsinksrv SHARED + ${sdrdaemonsink_SOURCES} + ${sdrdaemonsink_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputsdrdaemonsinksrv + ${QT_LIBRARIES} + sdrbase + swagger + cm256cc + ${LIBNANOMSG_LIBRARIES} +) +else (BUILD_DEBIAN) +target_link_libraries(outputsdrdaemonsinksrv + ${QT_LIBRARIES} + sdrbase + swagger + ${CM256CC_LIBRARIES} + ${LIBNANOMSG_LIBRARIES} +) +endif (BUILD_DEBIAN) + +qt5_use_modules(outputsdrdaemonsinksrv Core) + +install(TARGETS outputsdrdaemonsinksrv DESTINATION lib/pluginssrv/samplesink) From abb335d58865bfcd8d690481f7225fbc182fd7dd Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 30 May 2018 00:57:44 +0200 Subject: [PATCH 505/956] Rest API and server: updated documentation --- Readme.md | 8 ++ doc/img/APIdocHome.png | Bin 0 -> 103892 bytes sdrbase/resources/res.qrc | 2 + sdrbase/resources/webapi/doc/html2/index.html | 20 ++--- .../resources/webapi/doc/swagger/swagger.yaml | 15 ++-- sdrsrv/readme.md | 81 +++++++++++++++++- swagger/sdrangel/api/swagger/swagger.yaml | 15 ++-- swagger/sdrangel/code/html2/index.html | 20 ++--- .../code/qt5/client/SWGAMDemodReport.cpp | 2 +- .../code/qt5/client/SWGAMDemodReport.h | 2 +- .../code/qt5/client/SWGAMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGAMDemodSettings.h | 2 +- .../code/qt5/client/SWGAMModReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGAMModReport.h | 2 +- .../code/qt5/client/SWGAMModSettings.cpp | 2 +- .../code/qt5/client/SWGAMModSettings.h | 2 +- .../code/qt5/client/SWGATVModReport.cpp | 2 +- .../code/qt5/client/SWGATVModReport.h | 2 +- .../code/qt5/client/SWGATVModSettings.cpp | 2 +- .../code/qt5/client/SWGATVModSettings.h | 2 +- .../code/qt5/client/SWGAirspyHFReport.cpp | 2 +- .../code/qt5/client/SWGAirspyHFReport.h | 2 +- .../code/qt5/client/SWGAirspyHFSettings.cpp | 2 +- .../code/qt5/client/SWGAirspyHFSettings.h | 2 +- .../code/qt5/client/SWGAirspyReport.cpp | 2 +- .../code/qt5/client/SWGAirspyReport.h | 2 +- .../code/qt5/client/SWGAirspySettings.cpp | 2 +- .../code/qt5/client/SWGAirspySettings.h | 2 +- .../code/qt5/client/SWGAudioDevices.cpp | 2 +- .../code/qt5/client/SWGAudioDevices.h | 2 +- .../code/qt5/client/SWGAudioInputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioInputDevice.h | 2 +- .../code/qt5/client/SWGAudioOutputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioOutputDevice.h | 2 +- .../code/qt5/client/SWGBFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGBFMDemodReport.h | 2 +- .../code/qt5/client/SWGBFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGBFMDemodSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.cpp | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.h | 2 +- .../qt5/client/SWGBladeRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGBladeRFInputSettings.h | 2 +- .../qt5/client/SWGBladeRFOutputSettings.cpp | 2 +- .../qt5/client/SWGBladeRFOutputSettings.h | 2 +- .../code/qt5/client/SWGCWKeyerSettings.cpp | 2 +- .../code/qt5/client/SWGCWKeyerSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGChannel.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGChannel.h | 2 +- .../code/qt5/client/SWGChannelListItem.cpp | 2 +- .../code/qt5/client/SWGChannelListItem.h | 2 +- .../code/qt5/client/SWGChannelReport.cpp | 2 +- .../code/qt5/client/SWGChannelReport.h | 4 +- .../code/qt5/client/SWGChannelSettings.cpp | 2 +- .../code/qt5/client/SWGChannelSettings.h | 4 +- .../code/qt5/client/SWGChannelsDetail.cpp | 2 +- .../code/qt5/client/SWGChannelsDetail.h | 2 +- .../code/qt5/client/SWGDSDDemodReport.cpp | 2 +- .../code/qt5/client/SWGDSDDemodReport.h | 2 +- .../code/qt5/client/SWGDSDDemodSettings.cpp | 2 +- .../code/qt5/client/SWGDSDDemodSettings.h | 2 +- .../code/qt5/client/SWGDVSeralDevices.cpp | 2 +- .../code/qt5/client/SWGDVSeralDevices.h | 2 +- .../code/qt5/client/SWGDVSerialDevice.cpp | 2 +- .../code/qt5/client/SWGDVSerialDevice.h | 2 +- .../code/qt5/client/SWGDeviceListItem.cpp | 2 +- .../code/qt5/client/SWGDeviceListItem.h | 2 +- .../code/qt5/client/SWGDeviceReport.cpp | 2 +- .../code/qt5/client/SWGDeviceReport.h | 4 +- .../sdrangel/code/qt5/client/SWGDeviceSet.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.h | 2 +- .../code/qt5/client/SWGDeviceSetApi.cpp | 2 +- .../code/qt5/client/SWGDeviceSetApi.h | 2 +- .../code/qt5/client/SWGDeviceSetList.cpp | 2 +- .../code/qt5/client/SWGDeviceSetList.h | 2 +- .../code/qt5/client/SWGDeviceSettings.cpp | 2 +- .../code/qt5/client/SWGDeviceSettings.h | 4 +- .../code/qt5/client/SWGDeviceState.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceState.h | 2 +- .../code/qt5/client/SWGErrorResponse.cpp | 2 +- .../code/qt5/client/SWGErrorResponse.h | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.h | 2 +- .../code/qt5/client/SWGFCDProSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProSettings.h | 2 +- .../code/qt5/client/SWGFileSourceReport.cpp | 2 +- .../code/qt5/client/SWGFileSourceReport.h | 2 +- .../code/qt5/client/SWGFileSourceSettings.cpp | 2 +- .../code/qt5/client/SWGFileSourceSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.cpp | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.h | 2 +- .../code/qt5/client/SWGFrequencyBand.cpp | 2 +- .../code/qt5/client/SWGFrequencyBand.h | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.h | 2 +- .../qt5/client/SWGHackRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFInputSettings.h | 2 +- .../qt5/client/SWGHackRFOutputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFOutputSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGHelpers.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGHelpers.h | 2 +- .../code/qt5/client/SWGHttpRequest.cpp | 2 +- .../sdrangel/code/qt5/client/SWGHttpRequest.h | 2 +- .../code/qt5/client/SWGInstanceApi.cpp | 2 +- .../sdrangel/code/qt5/client/SWGInstanceApi.h | 2 +- .../client/SWGInstanceChannelsResponse.cpp | 2 +- .../qt5/client/SWGInstanceChannelsResponse.h | 2 +- .../qt5/client/SWGInstanceDevicesResponse.cpp | 2 +- .../qt5/client/SWGInstanceDevicesResponse.h | 2 +- .../qt5/client/SWGInstanceSummaryResponse.cpp | 2 +- .../qt5/client/SWGInstanceSummaryResponse.h | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.h | 2 +- .../qt5/client/SWGLimeSdrInputSettings.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputSettings.h | 2 +- .../qt5/client/SWGLimeSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrOutputReport.h | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.h | 2 +- .../qt5/client/SWGLocationInformation.cpp | 2 +- .../code/qt5/client/SWGLocationInformation.h | 2 +- .../code/qt5/client/SWGLoggingInfo.cpp | 2 +- .../sdrangel/code/qt5/client/SWGLoggingInfo.h | 2 +- .../code/qt5/client/SWGModelFactory.h | 2 +- .../code/qt5/client/SWGNFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGNFMDemodReport.h | 2 +- .../code/qt5/client/SWGNFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGNFMDemodSettings.h | 2 +- .../code/qt5/client/SWGNFMModReport.cpp | 2 +- .../code/qt5/client/SWGNFMModReport.h | 2 +- .../code/qt5/client/SWGNFMModSettings.cpp | 2 +- .../code/qt5/client/SWGNFMModSettings.h | 2 +- swagger/sdrangel/code/qt5/client/SWGObject.h | 2 +- .../code/qt5/client/SWGPerseusReport.cpp | 2 +- .../code/qt5/client/SWGPerseusReport.h | 2 +- .../code/qt5/client/SWGPerseusSettings.cpp | 2 +- .../code/qt5/client/SWGPerseusSettings.h | 2 +- .../qt5/client/SWGPlutoSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrInputReport.h | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.h | 2 +- .../qt5/client/SWGPlutoSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrOutputReport.h | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.h | 2 +- .../code/qt5/client/SWGPresetExport.cpp | 2 +- .../code/qt5/client/SWGPresetExport.h | 2 +- .../code/qt5/client/SWGPresetGroup.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetGroup.h | 2 +- .../code/qt5/client/SWGPresetIdentifier.cpp | 2 +- .../code/qt5/client/SWGPresetIdentifier.h | 2 +- .../code/qt5/client/SWGPresetImport.cpp | 2 +- .../code/qt5/client/SWGPresetImport.h | 2 +- .../code/qt5/client/SWGPresetItem.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetItem.h | 2 +- .../code/qt5/client/SWGPresetTransfer.cpp | 2 +- .../code/qt5/client/SWGPresetTransfer.h | 2 +- .../sdrangel/code/qt5/client/SWGPresets.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGPresets.h | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.h | 2 +- .../client/SWGRDSReport_altFrequencies.cpp | 2 +- .../qt5/client/SWGRDSReport_altFrequencies.h | 2 +- .../code/qt5/client/SWGRtlSdrReport.cpp | 2 +- .../code/qt5/client/SWGRtlSdrReport.h | 2 +- .../code/qt5/client/SWGRtlSdrSettings.cpp | 2 +- .../code/qt5/client/SWGRtlSdrSettings.h | 2 +- .../code/qt5/client/SWGSDRPlayReport.cpp | 2 +- .../code/qt5/client/SWGSDRPlayReport.h | 2 +- .../code/qt5/client/SWGSDRPlaySettings.cpp | 2 +- .../code/qt5/client/SWGSDRPlaySettings.h | 2 +- .../qt5/client/SWGSDRdaemonSinkReport.cpp | 2 +- .../code/qt5/client/SWGSDRdaemonSinkReport.h | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.h | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.h | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.h | 2 +- .../code/qt5/client/SWGSSBDemodReport.cpp | 2 +- .../code/qt5/client/SWGSSBDemodReport.h | 2 +- .../code/qt5/client/SWGSSBDemodSettings.cpp | 2 +- .../code/qt5/client/SWGSSBDemodSettings.h | 2 +- .../code/qt5/client/SWGSSBModReport.cpp | 2 +- .../code/qt5/client/SWGSSBModReport.h | 2 +- .../code/qt5/client/SWGSSBModSettings.cpp | 2 +- .../code/qt5/client/SWGSSBModSettings.h | 2 +- .../code/qt5/client/SWGSampleRate.cpp | 2 +- .../sdrangel/code/qt5/client/SWGSampleRate.h | 2 +- .../code/qt5/client/SWGSamplingDevice.cpp | 2 +- .../code/qt5/client/SWGSamplingDevice.h | 2 +- .../code/qt5/client/SWGSuccessResponse.cpp | 2 +- .../code/qt5/client/SWGSuccessResponse.h | 2 +- .../code/qt5/client/SWGTestSourceSettings.cpp | 2 +- .../code/qt5/client/SWGTestSourceSettings.h | 2 +- .../code/qt5/client/SWGUDPSinkReport.cpp | 2 +- .../code/qt5/client/SWGUDPSinkReport.h | 2 +- .../code/qt5/client/SWGUDPSinkSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSinkSettings.h | 2 +- .../code/qt5/client/SWGUDPSrcReport.cpp | 2 +- .../code/qt5/client/SWGUDPSrcReport.h | 2 +- .../code/qt5/client/SWGUDPSrcSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSrcSettings.h | 2 +- .../code/qt5/client/SWGWFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGWFMDemodReport.h | 2 +- .../code/qt5/client/SWGWFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGWFMDemodSettings.h | 2 +- .../code/qt5/client/SWGWFMModReport.cpp | 2 +- .../code/qt5/client/SWGWFMModReport.h | 2 +- .../code/qt5/client/SWGWFMModSettings.cpp | 2 +- .../code/qt5/client/SWGWFMModSettings.h | 2 +- 210 files changed, 326 insertions(+), 247 deletions(-) create mode 100644 doc/img/APIdocHome.png diff --git a/Readme.md b/Readme.md index 0bdfb47d0..d02f3a199 100644 --- a/Readme.md +++ b/Readme.md @@ -46,6 +46,14 @@ From version 3 transmission or signal generation is supported for BladeRF, HackR - [File output or file sink plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/filesink) - [Remote device via Network with SDRdaemon](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) Linux only +

    REST API

    + +From version 4 a REST API is available to interact with the SDRangel application. More details are provided in the server instance documentation in the `sdrsrv` folder. + +

    Server instance

    + +From version 4 the `sdrangelsrv` binary launches a server mode SDRangel instance that runs wihout the GUI. More information is provided in the Readme file of the `sdrsrv` folder. +

    Notes on pulseaudio setup

    The audio devices with Qt are supported through pulseaudio and unless you are using a single sound chip (or card) with a single output port or you are an expert with pulseaudio config files you may get into trouble when trying to route the audio to a different output port. These notes are a follow-up of issue #31 with my own experiments with HDMI audio output on the Udoo x86 board. So using this example of HDMI output you can do the following: diff --git a/doc/img/APIdocHome.png b/doc/img/APIdocHome.png new file mode 100644 index 0000000000000000000000000000000000000000..0aca0bbe63fdd7412a736db9f891f231c3c396f0 GIT binary patch literal 103892 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%0DoVf@JBx z%xWC!uPPb@9N=JqjWckyeX#<}Ya_NGNg;C)z7GGO)L^4!u78rL^;d6my$gu>1h*SX zbdgBhHCIv^>XjrMJ(`rpcVxZ=CaWRgvT?bC3K)FF7AaRv+6K;FX%T5@`a{FUNu!$V z^}4pKL-D^1k|R>|o#%}`p?JNiFj$l`U>Q_yVF!_9DVFRy*c!pu0kgzpGu|IEA^ElD zD7$5sYvQypCDve;Igjz)R_uc-7!-m@0-+VK>ol#e(mA&>qn{u`;K%W~;=FCgM&u(V zyf3(Tzz@7+c|P@ldR)Lr`{o^W$##-WT=6B`^A-NtOO}r9DHP}FwK^JeVjNbUFkN|%@Pe4 zgX+Rhbb{@&I%Y8yiNq+n`E70HN*kc4Eq`vSr-MlgSHe;+w3uQi)j(y9!R|u}HE|57 z+|fH)912(dvsYX~Lj=|nBGdT!@R^UnoH6$RNzGe(blZ`=o?FJ_lNo(ZF#+VrY?fG^GFzm2D0DBh^ohO7lgszfd@K;5%E&JAgo^$@xgMAa7bl8T(;gO* zFvfk|wj3zHe}n@Nk%_4TH=7&=hhIuVrKEl&*$4*B4L#PCc57*(L?*l&SBP`Ae~9Uy z)%{ZB8~|b!Q?}RB<@5SeiKh+?gEu31TXwYjjEl4v!|JV&s3gQ$jd$f@lQxt63lg_r zzMg_(cd5;Dg5<7XE^Zf7ksJlqzBNO5ND*ypFPlCi86v5n&)A%SHuY*9J_0Txj^sMowleRiB8jbfi>lf5d{3p@=W`pV$;hmra1zWodu!Bn> zdF`u%3o%aXrB0Y~!x#+ZWbJ_inSi`tt_VjF4St`kLZ!ERD_~ve-qqF*|XHZn> zY!tk=yH7TmMm9E6hqvRpFiRFlrm}<0M7D^=It7EXDt#PAk<6XbrE|V1nlCm>?qC4* z%RCD>k4l_L5|)B|)ompD8aoq!{;qaw`lpfWd&~AQKPSH#F(waLFd`3JqB1~^umvtw z4$r4U2Ao~A5kc-NOGZi00u-n}wU%Hozad(XOX#Sm3~?wuWwt)23AVqf;h1ZsSfqSF zC=vW24}&QtLX{!LA$JuF47ci}aDK%ZR@`Xh{Q{fF@dJ|q*@RIA*_egLIWsE3(19r8 z|HTpn;-aKza@S$HZg)aI0>JocyFM2-4kYMkj3Svudgf#sh5e$SzpT~i`J+?WRFkRa z2~FZ$>OHRpEqyhcQ>V>iv&WbIp;83^(4ojCwbb7JLCre;ULFa81f&OBW3gfxRM5e zpl=))Ky4jd^#TSQMWdOv&rG&V=_0vQ=ASQc$`hKG`$k{)xya!CI&cU7HSw?7T}|{D z^!eBnquR+Z1EPjwXt;gs&-axec|&Ejn^<}{A_w=#_Qm&ew)oGSFEYa+&N4E5Q8DOj znI<+og5EP*ahgmzEv;S%fY93FYM&I@kc3Cc&P-pT7lsG zP@Tu8@K!Wlzr)mGgO6r3So+I{#6@Mr<5Li!Rq4#HF)h2YhBjKYbIzBnU(w?Ec;dqC z&`dN#4)gmZnChBwltY@IDO;?E+4xCtNqK7EkujOQF7UVHy9YDYGi5W;@s}}y3AO~ck$42QHnV~OTGHi|ugZdmx zT9T!r0X^u%U_Spz-Eck8+o|_6FuO$Z>OIa$mN{?>iA<@Js<3RN&uJV)NaZhYM-lOP z9-K5=tnIVY*nyCu+THqc_RXMxf!nMX*WBwHVVfoIy34y4s|_#D{hBV29d!TEI+>@W z{k?tMZBR3E2QsGF_~}4qPJ*29N+KVNy*9+gq8N~C0~RTH9--R2 zNnYXbG0pW^^geu`dH6HK;NvM9_OB#CaxEb{%((fWQT+w)Fk(PjCStxoR z6qBvq{7k6fFGnmFnF1AS;vHd+fjJt%tE>j(l>j*qxTzDmRJ4Qea7u-!B3cHwR$$8m$mc6fl5ko5h)l)MF9@E_Ou*)Hf5BbRN(z=X&ul#%1)MbCL#;R-*_Bx zp^SnO6AB@m>G7B2@>9DdKBzQvZ^piqXi6FoG$K?3aeZM$iXGoV0BAD2EKN--gTaP3 zzQDP0L@D+i931Ev3Vx3hRXS{q?2rHQYPB$#qzU-q@?OsRc5@)ZEwo$S@D{-#U9)AD zH_U+K;Yw}4TEEVrflY+IqmKJ(kD15tO?BFW6llNh!S7$25sW*t*-!ZT);%M~9QR?o zNjSCiZ7U2Ft@2<5BNidYv$y~=$-vXYalE1chRtWbdbcY!D86IMuhZ=?d2DR&F94L> z#l$<1b_??78y;C`7hOND&G{zwp#&Ky@1y`k(!-u!MbO#Wzyd`gPg~;3i)~b zd<9+X^(orURx;sd=jl_HY1m&Q-E|I~=1ul7zB`zqlQ(Yb3Vvr&vG+T>b^;wI7jsiN zSHiMUhTb$&)OCQ~!&ap(jEljqY%l$C^zX4J3mT)!>vF{UfY=Q9c1;A_VH24cY3}}f zj8N!rK(@e75*h9Hulg1jw^9 zjxx^Udo~!Q!>9lT4a`BDZG0!tV#6P)W*QYG^s8(Or)KQEyRUCJ_O+>X~s%%Iatb-usM`W_Kb50PeYuYPTH-v|J|-Xz=}S6^uAD|YHM-T|P6O$Wh*Lo!y;EI8TMnog_DtAnIW74);yg!pv-Y>jRFH?A;G!0 zNC~}Z{XhbM!W;0nN#JnOJPiz7!hWPTX_%J$aeL1q3%T2sA? zm)3<(8EylYNx7iSCmBdbGchrFwQ=TY^VpD}N|8;>Ys@?+VK^tEaX68Dkd$glR$x^Pu}xj>p68Oen3mao zCI$`MzLNArF*1k*6Ay3=g*XZoeVBHcdYiK)>99Kd&_8Co6F`n@uhSC+)sbY_z%a-( z^M>=|C2Qgx(c}3dP(aC$u2>a@N*W9wj}3=`tN6F5wc zYu>zW#`$4@0SP`L^q;9eWI<9GO=AAmz2t~_`#q$X-%tz9ImytpQ${t@Vr_=U#dyXc0wAHR_o1b#M zyY(`46ynLtkY@YdlZ~*=qG@s>LSWvc$yLo2NwpA-LmN#JwCNic*^f!I6X;7 z5)XQ}U$#%WutcxtDX9Gk07)Wj=!lBg`I^n{GNkIUVZ#+x{(W5ZJDdZCKW@P3X}pWc z*413U**|!xfq1ke%Ncuvx}n%su~MI1p2`il(S~j+ajZkHBs1K6PlZuBzs?#_1cL^+ z#*h76Fx8g4mFv|?FrNJ&eI8v#nFZ2!(<-6@CL)^gS=Vn>f>V4c+S8LzVun1MXq+Dh zgz#*S_3BR&=WXk7*|06w{CsTA_tCtSEgZ>b7O3yV)Q`Z{qpO}WlacA>ngJX=+(YqF zAsDeCr2RXm;i;@zOs8>~%0u+6RtxJBEuS<8X8vyD9s$r4J4OSp#PY=P*!i8Ifhn}T z-La-r6+h_vcUVFT{nN3V&g_4&#e3O3%f(vI1*58bwj!EV6S!tb#Ef&E3jY-q6*K(O zVw>_q9o|0>geAsNFAouBrjIVHi!~-3gFyg!2p#mF)&n7cZs%(Nd`|xU0?s81X-a-X zP^9EpgjmZrIq&`ZiA#<5=5R3nl$!Fnl@f+J3UuXmP>+W@LQHbMR7v2BB>ViO{{B7o z5NPw%3cQ?EbiiFs%wNE(UGv^N&YVYga76+z0pN%9xI>uM{MuUP5_hRm=>l}`Pbk3X zMbEHY7lyp;2mV)WV=k-|rOM8VI#VZ^^n=y6p0>d@1`&8j|slN4^&tuH01jyzr}~ z$%Dhc>so8GV&-1B2J5Jt&x$5(Oob#FA3&cKkfgZUZtqKEs?N%E5vam%lE)(03C}5BWgTx577))_Z)F(XW30MZ8lZ&-eHZcoD2+w0rrcvar8KRz6UGYtTM37VVhjpdV%AbiV%wpgW9vu(fo!O-kW^?IQmb`$~Bg09zsD`xuR zp7wV+ax9c+qI_4D%zLRneD!=g{KFHVFRHp8p4?7-eJvW?^4}&T-Vki;+sezfV&mQ_ zs@q8ux3#L;smF6U@JO;w#G>(_fge^W*e{TDfxQm)*#fb{s>GxdkLo_P`CG-Coy<|W z-XG%SCpU=w=08DX+j50H?g!C@%e6#_m)UM~WUwXuX2|r_KNec9H+VgjV1H?1o?gS1 zsm%0|)DJmqdOnEjPjYWg5 zz{=Q1z;R?WP}>lgf+CHX6A#L#lQ!(wK`vt(M)MG@r{uE^v_qZ*iTyaT!P~J!l-u#B z0IaI(^WgmJHOp;~Rl&3)Kl81RplNr8Bmc;#_=%HF7(e~rEC5g0J2-7b*{t!OcFT1v z-&kBF#&p>r6eyYmLqvt$W844JUD=+dTX;?t z5~wqv`h$^w?R+O~%7>xvHXs{=c%Am22Sj^gh0S{eg)<@NuqHk+Z*C@9|~Vv&P%Nv-N@_g*j5})nQkshXtsslV?tq4-7inx+wqvu+<-af9&0O z!wXN#GEV{9R&)R=`d%#`PhUBy&lfQvbEtT7(1~x+cL0Y13(}|G{e!H zP$%09#!h~!zm!j6$6Ca&f+!}~u*lfzhE|;DKXmGsFE7wVcQH7&)pzjq#M+O^oM(21 zJcKCG7uoj7fB)WVe|{+r{e_n-Df#3`Ek#1Jo|bj@`a+HQ)L5yIVi?{RNeI4(Kitgl z!g9>pUZIeg48+^6v2Rfn!2Cc3wm^QmHX%Ft8*2t~6jFABae!;35@vL1;S zWri0ZL~3sO)*6)mfA-!y#;)tW^ZcFl?)Rb6XxPdQHV}lL{bX?V^JF;)9{-$Z=#e%l#guY5JZF$F88BssI(*qhi(N@ zlZ$l6EJ-YfwPpeLh0Zr~f{g|+0x^wwqs}9Z&YsurJ!y0OO53N@(ujyNn^2o`Z>l=y za8DcMSplb_R0SpkA;c)JdUZ-zA#>02vqr5=Otc);nXH7!AZ4^7iaTM2-%=wFtt-fH z+WCA9yEb#Rd!~k8esuLuX!CtdXrJwUeL$-(hQC&8A3Ztv!s8oX^qcoCuyT-mN5|@;zqnmU$d;;Ue=MQzjpoy&wc-|;{N8(q;E_*>YV?M;zI*xE@4PfEN;2HuUOv75_+3})0o$vz=5Mb~9uP%}3ANmiQ2t`i7^A_!9@eRw zlZ&Umb^Aa2WjAcTy1j@{$FE(>NkoxWC*~&)yzSDD|7L6P$5(TmE|THWGmnOi*_k8n zyxOdGgN7S4)Z~F8ieQq?nuuUebohKt;lua-dQ$mnW zErozdEz=_A)K*$hLOrYEyUoC#F3L!#QYy&^OUu;Z?&jb!rw#&W1QSA)N=t*NM%b{5 z@fR#>F;-D#X>OD;S|dwi$91ec)zM(+6T;0;Jg*TTjEOOTRm_ZE5YMNU=AgAoiilI& zwftP9QVEQZZwD2ps*P!esFJrG#qik891wLaekArs zetCBA^{q!9UVjwG!m;LWepvsP|J)XGN8FM-KD__mHMhR{XiGjk57GM0RsR(Jo(#qssHoI)qnFixJ~B+ch7wM^x_|G z>g&(jM~>gS@Y&|pS0CU0;*%O|GCLQ3`o77#u3^iBA8vkhA^Yl+n_oOz`JGSCeB!<= zd2sk=_m9BmC*L#qJ9pVjkbUvV&KDn~fAsO{@%J>}bz%E^U*9-$Z0e6*w#~g~4~%(Z z(AV{4V|F~t^qcCk_oB>`b5A_bU3o^y0zhBoonF7;K1{>*^3v(QTR!vc?%MO$D=c2C zXI{M8UB+wanZKPo_HJ&uuP$th5Mq{d>w7=aU4MSKz4T(vv^?o`*Pc&C9WQKN-B3hq zuVQt5eT1zWV&*SBvBAtejbT`dg0g*QWQ1nk39}uW2J}?~a9Q=(pE(i)jOCL^q!~ z(22HmMfMU?^QLHF`=QLK9!OM(3=DulRkxmt)M4)a<}Ka4*i1{G+Aa^Gd}I+WmCnml zsL~iCR5?@I=TKL3rD>RGT&-B|K9rW*t!{P!`%r3IX$=M$1H*)F53ndkS;^Ry_F@2o z5Vfg|G2xhMWGATZh<-QP)BrZM5kfBa2qL8{Eaq6uW`q~AOpA;%tC50g^;pe;u10_? zYK5E-$S|=>pDQSS+BuDW+KL3A8hrBO!6#qx!5{qIOYeDp$6fpGxa%s%`u@H5`MWu) z(c^dT|BrXS@5Qc-SKKl|D0%TD#4+0XC#Bso0$7r*zi z4_U`uAM?dnDFHe2;H(!=vsdn$7zj*OFn)ToP^iS%aeg60A*VF@hU%;4aG=xDiwU6-n4*P)tr;y*L+B`YIj(7t9=s$mNoGGDj^6VpqTr12mrq0 z`C*lWOcfSn%NP;Lk!J;rl92ltV~g4j_X?56MKbT--f2$Fr|izVJInqQF2zhsIXtkd zu4I-*xor*hP3H5%G5E-Z(OZ_+@X{AsHDAd{)rdUwTV2s{LJ6l;q3I`w_oFB$}-y_rdS2hJBLg>{x0TZ%EV==~*yv9`ATSpSQ_u)unqjb0_%l!3M zU_=9pE8dyfnJ~Wd6f;nxM+e;c1H)d1f?p!$z|@N;iDRZw8ZVy(L_A9NWbv;<(I2 zW{_lzQA#|=V~nYR)J79Z2*uKr6p4UYMwCg$=JrJn;!97>9B8(yejN;WEKT|(A6dky zL$J@7Cke4Ej*Zcba?Y93T7$50Hf6h)p-CwLL>Oa^?Z)|#T5iSmElM5kGR9J!FHGN# zP8_KIXyc#XY5vOvkFxqirI1>t%;cfxb+#DcjbJJ&+a@zg%bW30KVI#|-28Mpz3Wo~ zN>a+M^Qbo-MV_;(-)zht@vD<=Wdaj+?KX^^Y~O>e#kKQKUV7>wQKT3n+qQeXo|L=W zNVr{^8>$&^j8>v7vM7tXS=8@sTv$E(SbuBr^(1Cha?^piWzj~z=q7cKu=xWoc8YO!)Hchi$ub+g#0iGkz7g30n|+2+o07$HC%-tEPmt>Fgu z3Abr2*(#zLk)_+){em3{Vc2UI)xgc;w7`5zQ!9nM zh;r8(4Rh1$b^%y!ODJe3+Uy2_M+;kzn!&VfdEH*m_NH&GJd=!QhLoNBm}bz8lB7(m z0Z&Uu_6Nh&=IZ)Nss?Eh>#>bw%$R>v^tUek@SA_;*QV!>-!*gOPRFk@%O%VrloQGj zrU+e;D8_&>S}Un!sYD@)G#PHMoqJ;W%y*;S`n3y-EE&9J4OL^zm0%0KlXg-nQRGR# zzj0|}@znO>({D&XO`eYSC~$v`x$xJ{Ju!Xg4mWIIviqy>#Qx*e)t(u>ZRN|v3q zv)})#KfdD^f2BTs$o49fJA|;E_3}!27g>rTEwgy#x$mF-`~N3>p`>s!+}^nOqk|{^ zyI1I#B8&FOlPg7$r^8n@*6f&1WSPCDdA!I*S=76lTGhL~%VNT+lM9F5{)@-n{V#S+ zBx8&*`c)1tXouI34t{lVV*hRJmA&F}jh4gq@)KYGlh*w413&i*jhVxi8&K|0W?{-O zCTJ(J$AAGaMjNG+5=s`bEJTq-y|v{t-&r~Pc$w{`Ig}Dtn?=T$@`e-h>~y=#OJrPo z-}JL*q6O`Gbw!X7LrX_L{GqjQlpP2_Vq^k#Xu|WhgRS9uQp7Uf4lNI3KQ@vO3J7Vf zT9xVN*PgDJD#MIYrj14jQ$lh>N=+*&S6SPlR2ntZR2di~-RXT+Tw3LJQNbMpktJiP zB?ihUFJuM)obtd9Gm%C`tN}c&dw^}`005)hVQzxlG-;D;86h^sY^7x0+x+On>Nn}F z+q&uupTv166w{ztVeBkt;8=^ze%6sXGjDD`k}+e(+z>{qsJnXl+g~~T?XOr)I5B^` zHn|Ws_j%PxC#X|qzo1BrD6=f;joK^yt;Nktr{ezRHD;ZEiCIFNeeACg!h1h$j8RGo zB}I`(dEAc&TX{TyH~NL?;#1$WoDg=i*&sr$xlXJ(_k*u@R-bpTT4x7}EVA72W$TtP zQGfm0U-{pf`)-}R<=xHMV?k|dC#*>;nMJ)(Z+&a=>E&m?TV~Nq*K8%r-o}MffAe1{ zeVJG0sJFITYPA=1=jNpgPyTm*+~^*39KPw!gE2oajcgICLu z`pn_?{makPrVhPozG*26Q6y3nWuBOq3Mqszp_JQ%FiaTb_SLq-EWa8~?7KE=q}aT4 zYV*=5LV0Wc#KgYaDy@0HF=IIaC2XfbMjKIPdE8Hi+r##9Z~a1Vq_Y<=JMaHHlm zT<%7)>|{}_$|xU&MWVGD#N8^Jn4dRaee7WLy)!MgSxAo%hRVn@iefXpvNciNztKMn z#@w6{?_)(WX3WosA{(uqeQfpYV?U+yYbBoh{$JfBjlKAzZ@wYt8Ff~#y+rLR=&U@~ zS$Xcd4#}eKxhMYL>pEoEzC3JS-etuEy?_7y?&h{EUqe|gpZWH6{bFPG$lLGv)m`OC z8g*CBe*f%ae?8b*y4vA{TGiJ4{E45x?R~$rYy3Imj_ud4Qw^6J7k{*I@kej$EJ}yl zXTI~7KfPIsDPq10Xu1-RPL%BnJx*vwd|vO{VQPeQY}Bl>-hgkEbd-&%ZY4@P(&P>* zLMI693PGrpWt=B~pz2oRJhG^bfDlXxp+pq~0k$gAn=GsXKq_tlra(g>jWl_Vs)kdc zMcOFxOaUmNGywQkU~?zRhe;6=!tyf3fExZJrxpSzRjG}3vGyoV5fL)a%LD*i=5;s2 zTTkXcemZV1aW*N-GOqX&q3xPNVa(BJR35$ses~sc)(GISA{jGg%$PR~mJ?3wyKQpe z_E-NY8f>2a_E#?c=$l%KJ=H_e+qe)9x1#>W`~Kx;cKr_eN6?c9P~CweGuRaKp_d)h zqADv`I}vHt8nPBfYM?8PO0`*w(zs7&5Oo^iWI5PGpb@hWAd0Dw#l~I6VjsO4bM8rn7-P<>#f9jgb?;D zKd>tCAX1$b0Bl@~VB?4+sB^O4b$l^VCYF-k5XXZi1;6Sx2xh}{AY>UhVIyc(-D=>5 zHNQ?UrI;Bo6XB%I98R2u-)aUef{E6$T5F!SR+d`}h~jp0Qc=sEIWoKOcHas{=^!hT z%l(b6J@LPsc-qAw02wQiF=NJz8S}buf_iQ8;Hy8+;z2TOUr%_HQL;2(UhU+lm3Td2 zRyRKc5l*;cc|PNAIh0W+&U&Mw4KxDFHI|caiE^ZJg!)O5>7>*d}`tBDjW}#Wn+-Eh>$Wk zM+CTQ`2b)sTN{NC4xL)VpYUzp z=F2>OX+6TXU>_37na!EajN(ZrFOpI!Wr|!z+5#Qu{K929l^`sO?T%mppc0}pLnH_$ zHLnInYpn-K&$Il%sib9Eh+G?OKwH$-T3@NZhY&^>0XPJj4#9p4;7l!PZw<;@4k1Qm zF{DK*Sw6y+VN{n&_)evlthjcF5hj@Wc7L=lt2awNz;#i(KSsk>nyTL{W$wjOH8wsAq?qa&+@~3+=LNbMWB7 z@ohim%B^e6xD&>Vxp^q_C?0H8Tl25})6C&_Y9*JS{ceBbVmj))JXaoKmTP+zuQnOh zXIl%m@4MwambXh$m}J;)ubzF~sXlIMD5e&(k)fqsTMs8{wNW?O4D2wb?7-31nNh2V zva)5bu!T-jHI7~?PwQOdjPk5Z5dg-xRhbg1%*xmp(yYxfYEjAtN#7V_z(_59C$yQX zjT{u)k%>!8|Ne0sK*{b!AZCP>uO-pOXD+26MZU3QI!pP~WZdl-QS@w5lZs^8WMyi! zVU!P&u22QVG|Hl>>g*^R#(Bgk#~34o5uo<4fMWjU-^+vVi+?MjygqZdK64me zl8p8m7aXHifAiwyGvDsKxwmW#Mg+iUqp#NVQWRP%Ly*y+O_(_Q_BYM)_J!Wr!R$Ys z2>ZPxj=Ps|dHJjfdh)IbLJ+}(6H96V00xZIs?cDt1tuFLz0j#BtxJO%wS%keXOn!W zjVxpq=OYZPCFa?701N;Kc!yE#c~UD85MxXs3#qluEH*jcssti00-JHCiV!44WQ^jJ zF~SkXtx9Wiu+Ev4l({fSYK0J@m`Z~RnUx?K&ABiW$@VhV6V>f}sGGg!#8!d*Y$SQY zsS}A@XVH`{CfrFawQIS#NK~nADm(aCk&Jm0z4ToBm(BGbb@PK>XqtJ;4encVyvz;F z-q%2uX?N|pqKmbn%fK}8!SfhS0)Vk%Y1LuveUusF4y4bwKqUQ)yu6He?> z76xvXpqsHG8S^IKx@wxf;QM0mTgz`7A!7_6^j2$^F;^D3w^qB%4nK|Y8nu^B|J|1x zGe@hf1%%197nHu*g=!@RTbG{vZ-2Ud`5A=Zwa#aDW+VEU8~YIu05Gt3nMPw#rZg(# z&VB^KKsCG~i7m33Z86(Q6a3@nzW+oXQ)#<_!DjLMo9*}3u#IJEY$u##Sr9Q7&sg+a z)t~CbgHonaqi)VBVXKlkL1rrA9srEX#>>o@r^dM;dFC zU*zJrk0u>zH0mkU}htLwAG3U!Z_$`H;RFX^1OR?+z7^9Tlzkkkg93jL{<0qc`*8g$nJs)cwdN(4k>oKm% z^wJN$*17bA1JSkIXki#&j9u3wyRyv%&BC`;Wn8}iwG+-<4v?`W}p`vlLsp`RI}}xSC8{ioTU>Jeglu* zJC~n_L(Q<+5l$udkItSbkJMd~FouWeaOQwL{r;`}#r)*)WBK~`i7yUWhkVPAvQbh{ z-wCoJA$I9XYo=jskDk8u;6f{Cbw-~}6j8Tly&CTtyEPpx@6|b)RQDySr z(RbZDd-Pqqx|QsKD6_4l)61v-ZsXz)%Pdl|xW@X=)oO)O(r$MU`dJ&bdUXy6<l)F0>s{rJpdSux7vZCf{nrNk;z{-nmXj8-=KTLfcTxb18;aobt4;ZWT0o0tY8 zO}0&MzA}97xr>{_by-#26mQHVwxX9VcNQ-fjG-h?G4bRx-u5lY1@&Bf`K+sS-N%wc zl*LL*T8Q=jW;3X-Y%WLLgi<@kY2000=`NPC;KU(NcDI%~+xB97z!Rg!FtgaKL ziq^XEMtPw*d!*{N;;1*2lqnV)w4~@n5mMZ(IrXK^bD3e4YJ(tUt5|9kOI{#O^ORyr zF=dE?&dOvfP5VPtWO>?b9+$j6ESKvPHX%_`X=Sj?bTRA>ia`=3IU=P|MwKzjo}MXA zCNaL4ijj{)LVb*Fhbe-v!(9v*1C>%nS-ii|Vwkt1?Oq&l%0kNv9Z!lO7_Y-7Rep(` z>hV~o&9F!^GE@YSTIe!D1=OPAit+4{y@7l#fVy0N$3ao3F*^D|L%A{}n0!|kNMxpDC+W;uj%LKwp2 zN@~{~e-^E@mP!gG3Muk3Pn0O$4DCZ;d2s9d;M6xkNmpcmP^#7SY=aqXUg_LW$Rf^1 zXaJ)B8g~m|Bty~kMkuC@QSYSkKVz5>kSSRq=bp{dA4%XPL&i`vEj0mCrL!Hm*@RKm49f@$4vQcM`8#%NA?#lc{7F7iT@pbY}7 zK>p}&*L$6;R7rnT)^f@0JeRa2r1U0}i)Hnn=y1S&ZxXW&QS>cJ3C6j|DWxuU5`Di~N`|W5$fRNo^z?n>(i31Q`IxY$tOZ zgQOr7UqDh5aeXrg6|54o9FMle#@>r)9})ICTt8ZX zoT={zAR=CT_v}*Dhh8#@|+&cB0L|30=#buFYj- zx>JwF=rY8mrQ=@I?#6A26k~pArJEZ=7+nIDZa(eM!r1k4d#EBXqOu?h&vuX}w+P{g zS2_0sC$O1=0C8$n-KxVK0APgK%%Pa>k!BttT=N>t3UclUY9WLjW>@VHV}f6YgV9)# zj2Sa#%$S=KL$RE5`xqGjkeEtLUo)HlI(HD`!-W_4O`9`};*lRTT)R>0 zL=7TKS0qxG2oaIDvB?-nPN)G;Mp42zwX!l3N&uLe-_%+wy_X9;LdfQhg$dJ&8UrG_AcAiP@E6oM%y++sE(3;-yh6}R$Y zv^)ajS-#6XN=Tt~UPNxP;^rg3DPTdZx?@+;GEK|05V=OFBijD*WvtaocWGqQ!S_r) z`eU_~<(lON45yZ7hx0Sj$L^?%(#W%Xp`_3PBW!a=$TCH_tdIz$Nm=&rB!DTEFh+C2 zF<>x2FeL~}Ri+{$j@;#l7tXlhAwq4&b!EzB)d?vY6{Ef3< z8B8|)pk_gl^<);K%@xzz%KOibqIIQ2Auc_%!Dj0=GolmqQOQBDh@HC2DAa)i`!DmI3E$hEanJR z<{~#>9BrJ7EBz&Ilj=PO&R5w+TBJm$Ri;{A;JFhKX%d8t(Ua9_MtGEu3Xx@HI!gP6 zC@oEcvIH_yI}=4^tpg1ORxMezWr~gVRpd}hs6tN7r8Xw@7NwjMiV?z?Sj^_sqJ-7` z=1%szZoF+#&M6~T`t54k0!c;--xrR95wdL0sWdWVviaL?SXqn}$(S)?#*Df76p8IX zbB0hfu~5f5-mJ=CgQ`v~u){q9R<$vO%u1O9fYPdv1;K<94gdy$F~+q#&km@=jZX|R zMk}5qC`-$D8C2nNk6@~eHn~EHrkL8?281bf!ch`tktb6N5sEoNSZS4IG0uf!dE3K{ ztV~N;aLTCNvYZLe4lFz1+$9KX*#UsGNTe>4A}U0#j4V|tREYpAgYC{Js!X%M$0{;P z5yCh&9x*k?_v|1kBAeO97|#kkJ5X900D_4&8WZJMM90iwE|k78SSi7&=N8?;-)1(?EsL4XaIQkLyu8xVN5FA{+Pt@l<4CIr)M+h`9>vnHX+vlS^a0MrOt z1jr!m#n~{G;8$8RR)PqtRi@{ZHO2NB{goB#}hANupNzkf>pj3@8n z!x#VOuSHk>Qw{HVXL#IyLw{f}_~L(Fx&MM%I5F|5U!MD&`zAknnDtM$zwqZPPsHPA z$Cxo=#=Nm0nB?&DEtuO6?1kmcBie<{-eRY>YV__=rY?6Vr53Yx!bS)t4tJzdz7_0j z*)U2wqpkL6la_s+W|y`q5ef(<7-57^E|R2(wNU`Zl95faZly58*V6BDlLQ( z7-K>R#niQ2MtLaxU zK!6Bg7Iy;6Cj|0#n*xrD;aWkq3)GG}mphlX2CLY}LgY5L`f;Z{+BCeTcw<-=QVEB- zHYQCbP?>nA&-XivNas;D(8h$^23i?HsI@A~Vl>=9te9vtJGhl7Ll9xq^6hZ4G8H%> zMtG*Sf5-P$$fA&iltN#npTU4Os`OQ{0J4QO!`eYiXGk(=+;P+F+Q{Ne=VzZCOD?Ux zq;$ko^^^Cu?tDW{7mJT~evtEzef;2W-d(@zSa9cw+P(Mg|ASu+t8x6*Q^okkOWA=t0mbGLYHA~d1^0JAgN@}i1NE7I(~b{UJ=p;Ha)3ZonV7~xf~hB5M;5FaINW>Td^qLf4kb4&<8q6}s9Qy=N|w;77;P}Zf!!>nNQxvaQ;~1yWt0!GUbj>nf&oq}0I0ZCi`alY&H_d_fH9z@ z6uBrUCiULBl?g(8p|#W?G(_pJJ=_>3J&RbwY#4i7+14l@G43$tVoW*ZmRliyVB|(^ z9&J6e*zazYvdqh=Bnp*3Xh|)FGI<++~z_mRI%aJ1T3Z zACn>$vdCp2ua@x)fuX+QL1Eb?rGOA3hN8kQ%7)cIJDTPCx&CdKy#KlXunG4b_?T?} z*>{tr7~HD$fe+4p@)(Du^hFBtpD%sqYr$ocg}DA@ozL|L7sbj>&s8Z=Q>(YJTn)3yz*CJQ#lQ_t(EC0bupM({S>>TRuxycXn+)maqMrFGWZ0 zJMz0HeiFZa?(66N?J4V1pPv7K4`pZYIY8_fwyl|9}ku0G-Zf{-m?>h5m)@ z`yU-WwJFkFepIiW+WfOeqjRzGn(l}0o?1Ay_=VN*-+y}gC;&kD#Dg1OI+e90nwSee zes6f@OUsYU&HcrFJM}E&na8)j`gnY9puuJfhpQj|VDpNh=x0XFT!HS(IyIjCtA0XAnYl zzp1orkMbS4P;n}Qr0-cSMR;>zUXRxBu!|6>SXPUpbJ;qiD>$gl*7kR*hfMNgquQVq zB?RnAFk!uWadMxl^U>^nGwL|VIH~CMk~l3WqpTDXfopSHs`BW}iN)>nxyS=MRK&A_ zgA7AF95!kmWt$WLvNyj1kU1-Z9 z17kpAjBReO4Aw?A`_L`3Kl*ORhf+aW3mX;N;}x6F(T&rm!=vw7{PUp@P>M_&7^OqU zt2)zM2TUfRVH#oDLX)DuY`Ux6K{_#g+_n6EuWQk`5ljrz{^np6A&iWP^N3NVl$1*C z>at@1v^QhQL|t+ql|*PQ?zi{{LuTP<5wg07uS3eie z{qctb4}eh&GJoJOrLZ=S1$AH8q#sBM-`wZHVWrEPiOcTeu3NoMCbJDoo8 zaCH3cp!SnKBek6+zv&&XcE7ij!ATzgAWqK@uuJn(tNH_nF#sg1$r@PqED+e-{M?uN z=|b(3_xUX<&pp2V<*%)j{=r{gzyOe3*!bK7BRJgn)V;13M-O~`^$DthA_f4U&po*E zhmXtS@0tAMF`8WHf9We*JO@s9=88J=^`*}}sZZY3`kiB(50bxrxc&S0%g_JD)KLmR zF$49)!)?CM`qW+Q=r~XBm@#8+dbBY@ensn|C_>3wo>d87pa15$}x2%$#Ce=Sdz{y++Ne z3&)Qfca+DIB6Y`g-s8y*iUYfka(fOqd*!2=gYXIAw+ z$LMU6asaPs9hjm3keuHB>rMO%pP1iSabeD%m6v|&;r1i%5ANOBRmn>@dGOcYxg!G9 zxdA?UY~t7Mt<3@e_{Z(ysV_y3Ea^LsVHotkdP*(az5jRbvH<{&d2Ddu&rS`_-8FS| zin|oRW(THT{_aoR4;&_so*kW$l{(`&iRfmI4r*K zr;m2eo!l`>WSsAFaMGk>Fn1RJOK67)tW!~pFUXe-ki^-R;zg(?^Z@vDiy{U#&4-mGK_Jv zSucvhn734=J!4D|_?Bh8#l}l1)oeDj)@3Q)z|#^;Fd|Y*^ZL?t-!PR*NC@5uto>=~ z5e2n+rCPJ~L6W?!F>}u8lX4s>U*cf%9?O|&D<>j00efxq>`;l zOB+?!HH=|l;=mEFNG6(H6nGP1YaikwYD@v9hNx1Avfl7H)Qw&y83-i_w`vt!@dEB5 z8a5`UqG%gUOthK}hznvgAcQUIR;sPcl(UEU>PAv+RAy_lLFGV7@qy_fDGQQ?wW=M~ zeA~jU)0&z5Rg*7wZ_Y6t&${7l0=ACD-v{LyX*j9GuLKT zo+ZkZyV|gKp1nW48A2%`gk)Ka&<)rAMK2is_vk+b!<-NNbl)xRxnYcSo~NA%)s!3} zd(v$z+md--=n{KQh7}a;xdXLy~p}gxZ5Of=MBZ#7=WVyL~>}C>=M-vX~O) zFc)L8(OomMX{URcmUh2&s6Clg%ea~=spTlk5=Evf2Pdja?RIZCibc*Q!fA?V631(s zQDK+TYMC;zz>Tv`6p#8zM=6altIEO+XDMkAJv5{4MjF4p*uN!xgsN1lJk3Yhs1TXO ztifntj3UbE5BnD7r7XL;6Xj8wXXwgc?aW(MuTO2qTkj}3ICqx1W!B#kMQX{En^A-f z-1c^O;6Fh+1XWz`JikFj;@Wt(eWh1^G8kycM}PPqMz8n-k6)-{O=>*Y*K1kZrZZUiG{!lWI=%SASH@n zz<#g+k3B&0kcY&1hyYuFg%ykP%@ZKzg7MK`}zbp)vw3n}p4lD3TJJ zeLeSmTerIBVV`rln`}~~Ja)#i>T&P2YSpT$RsZ*Y|Myn`Tv=M=+F8IW?=pjiZxuh2 z&Hn18#yp;L=)UoiO)jlGDFMJuYp~OufBAbmZ}33&Yk-HVV#(&?9|^3-k$u$|qv1XN;`!V-~8r_O@?%D?~a zSKiR%+BE%R8!%|ItAmSRjA-TcGN5JD)WxM|uSIx&I~ zjuF=G?ah^ElL@+gtFI<-+buc|+RXz=HJwi4WNvfqWu(#Jw&}w0;t+FcEp|?aMmNq| z?fM3Hxy*eG2++Y~KX{lmv*|1=P~al=R>dH?riG@}+CBVmkDGvh}H zNxjelD{q>GsY|WFLA1!B=%P71ny5pZWyF-{^zCnhYvN>w>c%x)@3rZzZtK477?pKj z@1y+&#lL!fuv(%J>$YtXB2nUu6}mHn=DL$}uj7awZVy7-;Xk~lAj^L9m&e-={+3qkVV5pNC;S&ayz%Q>ETsqz z4*&HRW)HyXcbisBlMg;l{?C`@uekim!R)yyzVgv~Q~}#w`Sge1KpdcI{Re-NgZ1n~ z>)D6ipMUm`ZhhmM)7w{9H>>Q*c>lRqHLUk9OzERr9LDLk+kO5PT&?TtRQ=)>1_;-l zzVSQX@>iZ({qLTTs}cir|KW3YpL^H7#lt`IU-;z4hpGqG2qszjBeAq2L}Gkb*JFeb z00xKvh*J+MJO%&&AOJ~3K~!#>`QbcW1ej4~oPI<9Q%ngaE_fgQk#gNJB9u^ce2Uc% z{?suh5M%iNiQ9j-VC!VOesnkz0E`fN^LtUkQ;}=iKlIoT1PEgS5Z~(9RqJhtix2|< zCYXc}IOW#c5CcGvJfVa&dU5(tj8KelD*s&OxW4@v^cWF>vGotPCV zoM2*{jcHmb@dyDmgsXRdWIN-m{$0)SBNEU_Nv$W^#SH1O2e z-RVHB6bW6_h7p#@yk{iDV5~cu-Wnf3wR&OwvCBJ$*9(?={r#Ml!=%Kt(~L6QD5mms zu;^#gcH%L7_wS}ZbtU(R0H7Gb2agafS$Ix{0Aoz%OQ z|BT=%r%fA;_PU}ZS2+`wv4o1kBS08YB9;o@QLz>wQi{E;8+%9FEV8x6b$40LrCr#0 zkVAx#5=j8GGux$@4fFB-e1BA|2TZ!@EliMNiE*a49mX`_X^e5w^fuT$AB`?m-`Q2u za_GE3yCyZczgsB71DEHp^=-)Xs`3FTLsH!mBipjcqM{3HQ=`%ZV`@@^R z_5PE%U=1z6GEPxtgn_cHTZ!IbNbqgi`!N8k?jXxL#hC{Ku5o;WBA6@Hvj`fq=9a10b&Z~A)+RVu;O&H$Xek-2@k*@?snKvTPW ze@^Y*i7Biq%AkWRy}SiKk|o}UM-ZTgE@W@3nV7k?-!n-E+i)xmt{!F!|P zX)4l2*NcAk4y%2umcQec5Kj5SPUCz?2mk?+RQMRQ?eBYfI6-MYL{I)eto)CV_W#2? zYHXEX!mT75Lz zo6kIseP{b&GBVTerIg@wU~Fr>6&NH~pp@j*!S%4BIKh|_oX9MK_Q4~Btkb=*rggK~ z-legd3yOh&n=#7EOj6D#NcF3&^KO~zYrQ6cGOGg_xZ~|mqjhH9%>)%Pm?#Ey-}|No z-M8KJu$rfj6^n<*2SDi-ym>1&rc#S=_xWZNnnApnGMCUZtPBSJD|y^Ya2 z-#gb>i|hJs@K(F3-HPOk)e3AB*O1laUo4^_Ip(!Ns97e5!TeVwHX6G}B~^t?6ig8TdjaZ2oo zGUM_kmALL3FWqw@&8?$<_w>=r{X-3Zv;b9hYW3Z7-&{;jX_f7C8~h6|HYcvGf9{Ll z`{)Lxj-+0v|m#Sl-Cx|Dv3GB?iF}-);?HBI;yJzdkL#i;( z{KfvCe0lHrH#B>?_{I*fv0MQFfJYx!?4bGDOP$?HFH->ESGMTwS0~Ti3Y*)>;CSW% z%jhXVwD-dFngakZ008px80=1S&7Jnu^cx2!myXq@gxjy)i?rW#^R<+o^wxkj-+wl}Ysj>p_4RVHjh@?`3T?x}JASh#V1sWHOa0rCczzyoOUD zxx@%F@|JEQJ${k)UpISnp5jgAm=ZlOR-x-g-+gQ6kM`F1g}ND4wPwS6m6o zt+xmWLKKH!+oQ?kwfW*sh_Um?Bi`t{f|69`lrRLy2j{$zqCjfhAX1ygdV{bfS!pq| z&WGS#aEvfc#L?mH$3`y|iZ9oz&eTa_i&0VM!+9ZfXinM*lu(?E9m_mI0Du6880PIH zM6Wkj0!wcmWr0NtlSVJr8_YVOY!%>pSE38HBYbFDF+oIuyycX@BC*w+D;xv-#_abBu`OiV%hn zW`r@q-pN+!FizVS!joL#$KKliP>pZUV>*KfJ> zq084exh&z@vpdgR?_Rz>{n8g+|5BD;q43&E(`yILRD)l-O7_2X`!ipiJp0o0>8Ee} z$xHFkr$$!*0LZEYdu#TU7iZ7!y!ks%KlC`+e{uioJNN@vQ+~>w{QSyQ+W-0IZ+_{; z`r7ryGkPjST>H#ResfzbE7{x@?AH951N`XY%NG_`wov=({-3|tymqU8=Buy&{)_wr z>tJ{0-?*h4pg;5eG^yu*^2LMauGi1Mu=D$0?(e0T(Cj0RllIlU&;G^q`Rj{kpS|(f z7tuYLZ&m)%Ro1+4`!i2Zp1a;$d+F$pK6m@iU(#0oh@D#$6<6bIHIvzWGaE6&Qkkxl z>kk~kfG{A+s2FFf`}19L%rnR3qAZxi=)umx1XB(OFTKxGJLkMVTs+(01zQ$n2*b(Ix!Uy009;NahQ!x>wklt zI2{OrDLM&?r7|C8V9FI*`3|#O$UU+gNPgPDM&FDU(Wx1Ry3n-5Q=>ug)NZ@Vzm5%i&gBv11q=Pa<@0 z*9!V1f|co9}Qc7^X3X7=!gD z#>lBSSnM9vhik>g=KAH0(FP`dXMX$e=r!LTMu>ngA_~;Tp|){!X&%DC;!s;1Vw^OS zSvMQxW1?6E{r=>J_jd2-&JMvO84qAkMq#(c*9+QO^e9 zI*BnjZvhZS7$elB8`gQ~7XXM7DyZOuVsoiM?E-W$20`g&QH?h` z)vy$&jjoUC{YEdmnF)JXP&920wKEh&+`hJ&=9|N_!)zqjGHQW*@CWnVQf6ZF12bb6 zNaEd_`OYhuZ52|A3po(kERce0S%rw z`vij3xPwLE)tK-o2^i;M|*MIVbgO?o)o;dTV52xv^gWvz$ ztv~*zz54OZU%02SB~O0r>~B6nZ-4XdXFh-D%h%fUERcjNcI zd~iK0{)gW<`=KATGk1^=-Z<^OdyvPEK6qpG0~;P=iTDW9B!KTsTV-QJ(FPui3FohD-dnsZ15`T6K?U!qU@$@WF@RQ&B9L56*auA?4Xyb>=aZ8e?yJ7n~1%Nj~5Am-peA z@lQmSi0ln;Bzf>H?lBXT=c+j7+r17a6KVMfgfOG*wApWfCQBfdiQ;Ye35w}MLpF>s z#T2^_>^g5l44r9wv@qI&Y;ZqPU(V9r_TF3T%`$3Zoz=DvF@(34lK5al^i_I1#1W$R z9wUO$^1M4l36o3_%o3huZ#wiY;b|uGH+1!>o=Gw#sGx+1x8>Dbh#~Zn8c5CwEvO@k z4Wtg#yJ#0y&t0&AbHBNTuRJ+a!-QwvJIR#f3B^oMu{oABJCB3eVHH0wijI&*9+Rk(#Mn+k} zQ$`piELRmE1c03P*1OfJyRcb1hC_^tz6L|3J|P>LxdG(vO{b-1K6O>AQp zcmC$}Y_BdPQ;hHsybW08>XTRgocCBu# z(-9)ZB=XX+5dZ-|J_P5wqCetoPa}2S8s7^h3Xw{1oRCg8if1VLoJE2WARJ;`uB!wQ z$&?Svk(qXS9-~S6MXnbWlVvfa1(8Eybwhkh5k`XxKqZ2R5GI5Wm7?Jo7scOSM}nXH z#~xDeg^ynP;zu7e{Fl~0 z_1{0BEFFC8kw3e4FaN-Y&wb#-529 zt$zKTt&i!};Mae7@U|C=PyDO*eBuFoHu&_X9+at6O6pZ)GXZ6rc4_90+I zI^7v-BE(b1f>8kw7u|f(Esh=i<)Tbetj07>ntqM|*2~Shn?ytrrq&;C|9pS&{(bK+ zr_=Xz^&vHVA~NlIryWEfn4~=O(QB(Y6O6D{hv4H+kmnV~hx)To#S40U_&--_be?0OMk1+8!M*k-=kp&&yQE zcH_pu?gd(ZxeztrHB}_1}=x2#cJJWG0wCw@V!Q!AwhndVe(;z^E zDpy>A`aApcJw_Np_!P7ugcxOl$^N+6ak-h2b{!=Q0T`zvM1n{n5-ciYdt>_$0}u`| zrZUfDG4JL;IGnur@~xNMkKkCbixECGU6im?<*^6rEdW|NTf=<3=%<`A$<@4_rZRWI zw0iMYoP@UBoueCsFa*$=4kLP6f9uVuo1V)OW73)SRPsp(ON-Thaw|Am$Wu@{cFhe36}zz9>!1QQ_y>-%M-!`gd$haiL%Pl}|P_p`TP0VSjC_R)>w zY{UmbCo&O9Z#v`j@$|#QdAr;V3^pEt$t^VBTlQp#0U>-;A4w`b_<1{B;&|iqGPY~1 z!6#SVKNXj+zV1vPAX>@c(aY@Ol{0tSqi))oMlhA|JjB2#2-i1#Etn`&5kiQLuKtS^ORp)G zoE>g#j#8EEGdeL?*}$rJgh0o;e=8lG+l~@S3FS<3%5E=SH-mFLPcSB(>sAI^ss zzcbfI08z3O0QndQrj#avCB_~^0E`L2Vtb(7`s8&*eDJeoiZQOT;oZq?v-^CE??b9& zbVezwyE+hJAbo47cu4JR6 zJA8fT;Lb2v%ZB4XLSJB`s@hEt7p${wgdnJ3ltQ){SV0!I5h9)QJG1?7eW7{(FTQek zeX$FO5|PPLG8KY##@i4|HN1Uvqe@4McHWwH+Dx_86jMrADs$s>XZmpSKSpOoSR`bH zGg%}nqd{tw#d+}c%OBWU`!kZU)%WGo?{r>kXF~8Gl5%6XnH*jF%jrM1j^xMXhbwz8 z?R@t0cb_{oRekgP4kYWc0swH=p1t|6zdYT$r^sopJG8mQ|2gzbp$dXgp$fsoARCv- zV99AKkvwts>ZP^EF#>?_Kvdh#nZ7&VVvwyC$>6Bo_rZjCUrtUGRJJozArPQ6Mbg>NFAo0Bhx$^w`CWA%@m8ljewE zvRbYKLe`r>IuuL*#27=}%@*Cn2m5z7{$|!p2_{R^e6%=dPf|dwGt+jm=;uve&%0^e z*FM;lVoix;-c9G-Y$qwkJk zmr!%jShtB#KLb*xUYav9}Dq z5JXms^$3v>wp5=qy1p~Haa8Y5o1>%VC=Sf7ZGTiBa4M8g0Fh#{IXX`< z&15=Ah7eG^56Ksr8KG1!AiM`5z(&So87&8bgStXz3u95k;}p1;^6xS-+O=d ztwN}^>Vmd<>ROSPDaWC^<(r!eKIljmf-loyE{j6tiBN*_OclQ7&wlll#lL`+e5LN| zRHQzb;3EPG05Jq@+F5g?eS7K3%FFw=BEqd{lA`y)@6}1~SHmp@%aiFsY;}zgRO#B( zh=a}E46K(}69I;Z5CC8f$py~_lHfRpgUKExL?&a+hKtBM z(=p03nO@(2*?YS`-vzXMKbOgDeXyxSI&WtH=qJwq^yT%-IWIB8R3?-P>ohffO>=xO zZ;wj^xMXAE{Isy!Pt!+UH^V!F&bm3(xNT zmE#|OD&YVCw4(Nf7j}Lh+{d4gX$Cl;{LM?`pF__$?{hgo%K$zm1QSk`aVEs* zyv=0Jh|sQAJfWER;3(z-BS=zQZI-TEbhF-eAx4NHVWNIO(-$#DxvCVOdozLPd<;t! zJwhTtSjMD})s->C7;{-zZzIHboMCJFZhAt2#6)D;YVRE*j8l#gNoCRLHj`Q1FJe3< zSMH~0GFfmcr_E7k+FTZrE7T$Y08w&Pad1sHMF=N6F|pC6V?uw(Q8E=HXsa8&C{#7fMg+4EqYq9yO$novxL_jK zG8s*p17podU2LP*fMfI_25WV1drD};1Q+8$wrX{c5GI&#%I(QfDHsv5BuHW$q~lq8 zv`nf)08|e0Vr|5CM}?hC&_2XR0jx8f!#d(-bRKn^lvaF&Ue0gCyRXkXM>$9)y>Btb zlB+%v?My@s!NdpGnVy1R-rT-rAQ@Xabs1*sE5KV4_ZVmd!K0YTTpMk@#h5K;qYu8H zyMw>2r$2EQkIqtdV4WqH3MvWeT(p1?COFGcz9OD}`ta~*FC{9M#o0}pj`D9UE=+ZP zF}`>W5y{flv>|$e$tWAlkJ#Zs@F94JOP(;tW}hXG_*t&X0Xo38(Y|+#QMH1^D5pme zNUS!pODNl1ztFno+ssZ%AEI%lfFVqKVo{Y2Z!cb7vX9Q&Sgf!t6iUP z*3Q7&)>ugrN5{q+VcJBDIpK-OGMUE^kC#*LB@>*fSv&E;Dn9H@1NlWVojZ|kUP0~_ zz4?lr%=XVMauN98{Koh6SmLZ>N#8D5gb+Mqgn;9*uaXjuGyZ}gaid1BUsuWL#*|<;@ z0OYC^OdyPMRb{eR`hT2q!4!lT@BVdkOZ^H#vzxHEyVP4Wi(NAL9^eE4iU8J|5Itpz zQR#zs-a~}Wbc_lm5{e1IG?f{n909_JXlEwP5ki0w3DNha2{F#PDIv5>D#0aWBUpXp zlxmVp4U$5Pe*&qE*7V-HG8yEm;8cwBb%6Mj=jK~-swk$I5kx)0o{%CbR*F@E2sr(b z_s)kYMhFQe5|I!>5JHS_f(b^bw_R^qf+@x{BFb5!cnS!0rX`rHl^YjUFGGws7dzd_ zUO1G{RA!RN*0gU>zhZ2W6WOcV&A7=v}XGfii@Dj5hW5umLz zcvu}rzUZ>4loQPNX16D5>{*5YqX-xgikaj|kyO^(<+*gHLS5%Qqe+$wV+)!Mde>J0U1VXn8tyh#8)qJ3Z+qw(p?~Tq)Md!6Pv_!+0jST69x_NtqOg%J06i-%yKF zfimHW!qOGR^F*Y1XoYL4q?$HIb-(CLubnmCG`cojUoFbn^Lqz_agvVb{fuJDsbqvv z%qeDyC6bGxEN1(=-nDfxmf1Q3H zf&N^csAA0%gfOH$17p!)AJOSv3D`QL(^BMV5$6E{#<7%vVbhvM*uzBS3*EKdEQHv5 z6r#fj5lpvN9yvF@n9DK~9^=JeHO_8B zH#4r!Wa@)$b*)%dCY5z2Mktah;aP+*Yo`$7O0jMTM(t5VKE#mAA{9Ad2mpH1BqEnQ z^}){DiR8&fb;TW70eh%t%~LWnLnj)dZ>N(VRgUjvAoN=B&jw&~|(GN72w+9QNW zl@8jY1?cHAdQv8X&UDD@EM1E+^t0QQs`lhX)%@KsTkmZ{l%CoMALbj=7 zOL5FF8#R6Fkv})Su;?ZT(f#?33pRu}D9`z}HF_cul1w)Jj36#~GHVW#fza!Zgsy?s z07Qau2wsO+sH)YA5Q9G6c`v3ibKYXKgeJ#f6z@I6W$Jre26R$cl&b7)GtO6CaC_6c zm)5VG8?)CBzQ;MD6k-gL3c-q|pAn4DjxIRYw}-E1ay`%l&`~DxOqL<~-gFy-Eo-|$ zTJ22l_+V3!0mKd$0Z~SGa#d2yrp**1TqY$UG*^Wa1I>{@2uIi3jt~klI9=;zDzdH6 zO<6@N~iS&dxT5&s4lv~^%?RM>5gCwFwMfH(cU;rf&gpQV}dND-nO#l$-y1w*i zJg9rgP&=J>t~GU@6r7H%^MWdev>cs6Y|1rB=z^3Rdm29?;(nQI`8Ho28=MHEXK&%X(~BECPI(cD7$=l@AYp-1PQ@m<5Ja1 z*Awkyg>{~c>wYn5Cfh3)_vgE*%ot%2LiDap3Z0f}S1_iG?FC}>$Q66*5Y=3+G0hd<^>eq8%nl9Sj7M00^-Hr)O!trkKKnku#4-mLNoxNR2brn_LxTI-r;)B28tw zq&*T*bf%3lDy|6Ofv7;zx}PKL2dgGg70Ndw;RF$k&~itRLY0JIfVfs|o*AAsPCKVr zbtWBbazd@1_qtwsQO4;+q^T%-+cClto;dHc?Fk}@s?u_Mgu{??=d^W(QNgL;RAe$Q zv$0YGj1Z@sQ?8vcH07#d`MMmOr)uO#o>dnUk@dDmfCc4+ss`ye#%R5D-Z}3wnPWs4 zVWVu76R}hz^tM9?5ngDJlrYH>K@)}aFOlQbtp+uI%R;3$JZO)_E@B4-6_~3lq%_U1SQLPNJ2$o=?c$#E_r?EGE)7Pm= z{)_i-?(KSO0T2i(7*CMQsVW5%obo&w$gRgl`I>e;LIe|GgR@RUTxy0w-7juWZp0W@ z)(S2t0B|Z(kyFB~w~VqxqzD1UbSW>DOy;T-R7R?z8WBWNQMllYGm)e~62UU#tZ|0t zBf0vR8eS=@3wp5bqUV$^6}1=<5E;wXGFb?wD5e{O%?RN3Uk%PGkuRr2k(8XWrk@w8 zL{#8(tw^de8Sr$4sR5=5Vk4PuWg=4|^}#K=d9F%MIU^h}!Bi@dg%GCA1Ob*jT?StS z&ly#crIIO(2||b>KNPd|Gu;Vk}BodO0nj=vEH-|)!%QjyVF#q7~|gd1e4zM zE;wy_eUcl95W*Py7_`;nenJt(7-59=?$Q+C>Ld1j7rV3L7GsuF>o@-2@7{iXntb~P zKI&J-xLR|dz{#@a;~6S>>=*qUBfO0FMTjupg~gr+58xsK5Zq%#FacRWGR)6Bs)k!Q z81AN&aBaJt=?&wwaXPX>$7sBDi1RWnH-|~Ng)t3?QnhjEe7~_S{p}l3&kP{}C#lFW z&&dMS-Y!diKBE*iS_^#sVpT^i1L7dAab13fpO%l z)y=e?QbM%tIOUvhjIbkxO}A;ao$zG8Ihw3(=Bi3Xx>lZ9dIPyE5}r~*6P{2+3RPfC zs$@tpm0WVll`KVerl)0q$VqtL#$-fOJbo<6R*bX8S%N8}e7WODnGVP0W|<6PjDm8t z_LxznCd#qAcZL$$nPzXkOEDei8_tIieP^|H24m`jpX#;~>qAvi6l3(g={?51H(fAE zTAj%Tg|B@Gvu<`&A093C50IETKVhjZ&Um>JaJnMO(O^R`StY}jVl@@%INu<688HDL zyb^hD?Mk_^z4|DZNv#{}9VLWd479|_Il-k1Hdj@`m0EjWl8gu@2w_1*WMxB&7=p9) zIR&|@rp*KZvSi4^g7}4G38R9DK{~p+%$rp#MTjiAMF2#p?43ER5BBDJ+F1k;V#J7I zp$O%@?K;y?%*!NKG9rY_WC(=2;F-useFxdLBULZY;dC=H(PwLti9t3RW~+HVPGyb| z>iT|wjvmS9f-BA>ru^y?n#^4qhh()d@jpe7D$$; zJjbaVms?9EEbtMqsz_H9PdcYVQYGh~>axqMIGbF0>Zm!~o82`|3!X8`V=@le8o@YM zLyQR{tTmn2Gt}*tNv>q>A-d=zgaEO2{nWWmj;d;Nt=f#q2$EsWa4D0dd>mtv$!wU7 z2q7_o@p0BpLq9JG<_PM3YP`l6=W1bu zQTuyw(fIG*I0WP`oX=K@#Rk81>D+*h&hGAhtM@7k%;`4i_s-~B716F(n`Rq#$Qg`L zZ~A4TkPFtwV0GK~b00haiN>_oU%`ucWVjaDW23`#J&}2uLEHATMlS$R2*DCs6{~Yx z%@At|#%gt?%r30ra^sBkj>|zyhg){{$|_-QW^AinW36#M9`)vk49R*%t($Jqd~H}| zmG;gCM5&Zi<;A)moN3AMZI=3Xf=HS_{lo8k@^eRT-pus1ufF=p-+k?C$FZ>PPyg^c zpZxbnlXqld{4<~b&L{t9=e2*vR`%M{FMsm)Za)8GxbY6kv^^pOV~ka3G7=~zobZuM zOYmfPIXL(6pPEn-W;zI-rZVH@dN$t9n9!yJ!gcYJh?RvLbc59vLvLGcTjvcL6y&{^ zQH-_f>t=kx# zNLH}i7$if4NQfbVA7&%6+!{oTWxBOqo+*<70zlDmBE7eYDMr|=D0@c2U?{dOj)dao z2xH2L+@J3lXBZ~dYU?`hG$nM%<$xi1?uk)4l3ZpgU-Wb1dLR69KP&A_A4rcgg2^~v zh4Hx-V-EoUwC%08sYny1tm|V8sY-K^A_R3mXP7CL8r$`{VMKV>4RM<%gGMjve&JXW zu@fY6CIMl}3#2v%=`fWUK&+A~#9)cAJZ*z-ZErvzR%)kP-OTC z)S${%db%Ecpp-iALI|y{XBH7LmXyugDJQbCjkCQAp(j;~OF&UzkUWt*5nOV@7u}2! z>SK8CTEChlr5X;>0aIf`s^wPSk|`g2s4kTGDqH57Qic)Xgyk|5qLSrf6jK0T1Pj7O z`N|+0BZM=_&#w(J_~7(-5RRJtS}za~!b-+Uib(JlAy8b^-D1%#7!ep^5*u8sh+-p( zWWgxmD$j<80!TUR_1q861rzNv=5IQEfbSmR{PVLXJud3dLbI9%d_y z@|X`qxhiCq%VH$BfUdVqG_|lDL=RXwQUPKyB)d0geZW#2)6fVedRerlK?p^}hB7kS zd+UT?MS>XWEN9)>P3JAcSTczbzHnxqAuEb$P-)Buju3)L zDl$%`^>#Md%U{{eY?sF{6nT9l2YFtUsMAgF{45}A@XhB9VWGolLLx90qNjE)+&l!g z6qhTm6qg7A!DN_?6~Au>VFVcAl&6f+GaKU7@3p)8bz|l}IGJa%N;&H;oJT274w{=7 z!MvNcrq)L6O87rpCl^*q24tidQL?M!Fh)dZx9lprY!_}fv0F|sv6H4F2a&)JBM}q@q%;yz)By>#p2Dz#^_115~YSwN^^0&>gJD*DK%8khvc3<=3yMj%$iy z$dkzsGfahO7&aQDpSZ?3Wfu>`<|0zE5K^t;@_bfLM->1-)pghGjKe85#}ykZ8Zbh+ z;VL_9sPkQXBtOU_`RmH#KYI8p(cpjisQZ2FZ9el`kG}NWwO@XQ008>ef)5im_<;t$ z4>3){3wHoy4&^4s%Q%q|`9Zg$W@mb9xIHs%gOJuWDKkx3RjfDZb^7h{&zF`_Yak}? z)#SXdhnwM`ZC5~+7#ohk(Ae#K;CYlYae4<@cIXc(ydak0Stg^%M=PrJhN6MfQKxr| zpz9L`fOH*~VwM!qwpsy!`oz3yPNXS{krCFzHQ(COU2d~?_xx(;7U zlj%y;C8n)sS{bd9;a)Gh=kUDBWjf&l~vtJF@EQ>y_5C)?kg>* zJ!*O?~LZ@=DV|bBc<%~Gp zzH5il^(=(hMh!A}$+-~*gI1-Dt{Jy|E8z8Z*c}do=_0w%Z~Vy&I%fAOHoI2&5SEv= z;So&N>C9qwT*fYUf?&tsaCaIqR1+<7!UbTaX;|E|4aaqSFnVH8%m64nzk@MXZPlT` z1!WcvX}I4B!mP?ILz>hab^3Y5B-B?s^k#4-Rg$9QN&f1J!;aO@TMf*vVbKt*U9qxl zw?S$n8cfwre~KCG`r*F#=Mk{~j#pfTFpzb)~6(X`Dd7}-Y9=;jih`<+m2QV0O=1`ak{9Cy9W#tTES z=!b`sN#E>5>qn+VDhoDU#|lj(3Y{BXt7}eO%ViZ0$ablPsGO_aKpbu!%vGwdUNK)k zebNL5qW~+eYTq=2iDdg&-3hq0G)&fUcB-b}UQl7ngq2uGM}dyhYh}A`;x&Qe;X(HJ zfkCYU3O5Pu9rRm)H+cqZ6m!G$d;NZYnST`r{jG1Cw8pmWiKb{OW&5N_Q4RwyXk{y4 zOAw=u7nn{a%Q&BN!a3zdQ&G-pgfQXK&Sj%~*zNaZckIc|&OFVFY~8OiyR$bW>-qZl zkUG?{sOe4$xlC)Eo_hW*V3>wUq!yv)V+shfdcmj{bknjhQD7Qo;G;B;lN%jYK2l1X z+ZD@MLzL?}U4Jwyw^xVLJ9|$h_fK4*@U3aMi{%Sn)~mTCtzEBO_eR@1dLkTISM8zY zFOCn7|D5oV>G`!&9t@C~EtzTb#iyIDZ4u1OP4;1))$rHOz6vMYwC_wQ8TjiJzYEePks<7LsmjmB* zZ8q&-2LNvOvz(Zw=P;*n0strLd_Ed#TRTD^R%hL3-^mx({Z&qMW%T4>NM3pO^M_Rv zUO9Ae9UgQoUkYfKZA+@e#=7U-1ORU9+}wQ{uO4Y3cl=2unoK7ybf`T5{*G$D zRv&q4`C103l}~$%W7izoBh$5_e6us!?F5~`b`7oy(nqQ_RZK9cn-pyxwVi!foh!pM z;E=->LTcMXtlK0#RR|EUG+d_&5C7wL9BuEo6}9qm)5_L0ZQ2``-9zK#rNH&*_#Gp; z5ut1rS^vfue%QqDBw8y}UfLJy(hv?C#i-n1yg%%YxaqSUNHTRCMfMbMSY- z=J8(G3FW@j_25p@?9D)D$^tl%!Yb19__YN zaFAJyL`6<;;#fYX-7x4>ZADDj^9)twTV_ng2ex5`Lj=OQc5+f>E`ee=)fabncW-IF zt%}Rg0al1H-n+t(QnF9_tk~kgyDVLmO%#}cZec51rKInB)9yjjHc%@yyuQuv4W8Vs z;Uc=cFj%$k`mHgn+a~b*5tvd|F`@e15puew0oD>uCP5C`9t6G z+Bow;cQoz~Mk)vmJndc|T;A<ra*lEJCU&U(nXxG~1j5#2I z98lVU03ew6x?yl-H(vDm^};4K8twp*6G*huZFe9#WBBL|A8?a(uGg8tSRw|`EprqjV-hef_ACuD5}SY zZ`;2SY)=1WJ~GFl-C@MU3~;dOS*BEu0hqeh@d@oOTd!+se_#}^aVmN)fW2;gL98J) z`=GO~y)Fkf_4D>%`oxR`B)jkW_Hg7S%vT4!FkFOnX zsB_(F8f)z1a=eCc&LYHBz{)Qm#| zG0Y(){mHF*I*^x@hm!y(6Z%+;eXI70_DX=MdE;-Kk*M=mX21Tq^eECaFhB62!NFgM z49&Km`1IzbvnB#C2+VgqKlrKV%x{dCQHwi^-~L?k`bxHhUccS_*oVSH0zkC-NB`Xt zeCX;wdHx$+!j3L}=68}?KXLsRZX$5!^gsH&=Er|#L|>eL@=hHA{KW14&ws?rUp)WK z7mK3|1XJ&0AKiK1H`>oOtLS$>z5c>c6A2jf%)j&g(NEsy3;+P?KlmRW{qtbwzkja& z?N4uCSt($!Th9%D?t@MU;(z+_i!TTOz}ZK?2A=uojbFZvnpOO}pI(3ItXWpTa@ehB z`akp<&<#K!4r=zq; zPYMvXz*bq~w@M%|TlKfcHs5Z^sPSP{`L&`=RwWn9I@ONhHUMo}>48Pr_JvY=0B`Gf zlUxujQ@blwyHrY=hcr*yZGM?yJH1#P#k2F4Af)5V$0yyu>Sk5_+Ue=e@CjHfOG`)! z7Y1cbZI^l0ifDU(Q>Ae4-)}A>!ce1hXj&2)rC^q>(`_nc)QV%r4vz!-$7I~Bx4LkW zT;_FE^(=*PBpE5l6D3)eu3Ih=FH8$8w6GLPSJg6FwX!89$i?pF@!gUKq>ji=C8W2> zoI*ganjIhAl}aUu41>|p*tS;L6PBNCE~6sen}n|49=-Gsg_HB;d{K#E*X}oO+*!3E z8#IMQjCB^DKRB=Fmnwm0t~n>;+p}_BGI&w`f**Y-2Mh9oAV3G zTH7Ckq>v{``S4;5Hy4Up7p1Q+kLzr2vk_?=gY;p7Oyu`Ru~@_xTI*6N)}8R^i_!8; z=^a!}o}_0&2~PNc9%xPDeA!s!)ra%m72|yM`0@P*U8gtlr%@3_MWnlAB}gy5yDTD3 z?X19_&-&CYwwu}N*rd#;QIXeqTo*-UY6z0m$>OwD&U9K`F5^{-_I9eTetDCus9^CL z!$V=XtCpGqN%j1@?y#s)XknhwX|S#qbW(-UQn z5(GS@SPfKH$)LHom`y16ZMJTP`6dFzot0?PGV%M9)*ly@wT=Wx6UDF21&Wf#65b*L zC=-^N)?1lsMF&p2J^k#TS-1c0n@3+AkMe4@x(f$3Yp=}X#px^pwywD{nF%1gaqVFp z#q)^Q!j7s+(PkJWn_+Nt*=*16uI5H`p0g+V@v^L_tumEv>zreqwC3hQRfGH3vCn5^ z(c89zQ{5ND9D|Z1B~9}WKC^l0&XwI=^wrmnmP>gn@$}ZP))jTn(#7Rw0d)yh>AV$9 zd-c6&UMwFUZ_Jn+(Z;EnFTMM`b(Su03ZNKL_t(? zW~wGA;)^IuVZlRPF6(SyX0Fa#5N-2&74AuHT`m@@3ska`^#j)kp$4g$6&mHUElQUN z5~&16Sb~KEerUc~pVxlVXe+LgXq!AJ?_aQ;X`HQ&t1NHGvqz_&SJaFviJD5Jbri7{ z1~{wQ&HY%&XqC+K%RDNj(nRtM2c(YE)p?CEewpGmh~u2SxJ{oc;tn~P&0c$OQNz0D zj2fdp>4qEGUVi@fJ!UA7Z7f9lX8u?fNG7Ep753$MY%mzBx_kf8vFzWOsWfNPT)A11 z!~g@7MXNMzA70WeI#?6d%$LK>ahJ=+2ryGsa(?=ijk@F&7JtKxB!6)E@lPh;X8#v{ z%nYjfGcR2JLH2EjuT~%bt>t;o|EZ6T4jgs<%JR2Ab$lca{>k$O0ATyd*)M*w7~Jmt z?E5*0l2888>{lZ2Yd;l!k9Y?`6o}7$YW3{<#{ca{@bZ<*kAHgptE(s+b$;$Y-3hYf zH$E}{jZaz6{#0NC0HW=G^INlff%oGd9bPx|(JRYO{Ql8G9R3&2FaRJWpq;J$=?nHh z_=&4O@2H~}&OiRb`R`1x{lZP>Cx2!HerNWj>F~e)pk)ULi0rpNaq)-1{>hK@uNz?T zVD-B%oc~wG!LPl~0Mo%g{t57_zZcu@+xywuWDtO(Z%6yxH1J&j;J+n|~1VSYwU(9+%qt27uU1J-L{;fc3zi%|Jl=*UOGIzJG14+$8}mP7cp2Q^Q+^h zUi<35xYF6#@%Io=dT?bN46QaUcCKj8&`Zx7O28oY=UW)~b2+rSprs!tl2FA~4nQyWa1;@aB&nK3v)C zOKG>2qSd+pOF*>0q$VCe6Rf_-n-?qd+AQ1`?E_Ut{b1O2 z`ka~=)9u&(_vy}!#O>zkCC9-Ci?Ll*ogEF}EIPR|z9rg6*RHVaa&VRsMbJ;Z&hD(tRBz;j^8QHzi2Ri#pzgqKAdP&=g@iiQ@jKA14X%bGVv$N|q3Ha!8 z>FqiWX2B6W=r8Z{-rmtB^I&AyHbOWoQd7aWSa+Q8EIJRT?_o3x?Es0%8R~Dd%!6p) z4>6(;!J>+zGP!|fczm;%zuv@`oEVfCNu4&bxjA_%Y3pS4^zH0~5e6U$DI;y(06%AA z!cH-U8tT43-XGk&jOU0Occu6vQ*xed74l{_r3lSpbkHrVo}!^n8A>5o;Ko zcbsmPTq^Z^G@DyPci8@d<(S%;SJ{v;!bpgb`f-@i6RbrRK?{lnpoEnRVXYWG@#FKBIbvW4!cE^Edv( z4`!b!#BA%R3(%HDy@E~;@FL1r#nm5!@9kQTPDiYl7f({FXD{#itK@S0%>49EOl}Rl zQRocgEjrwB+mhuGCXbocNbY*|?aAK1yq7@VF#}47yIGb6IRBG;=eiZ#a+fP|U2B_g zfOPQGXsq7`VYtc{AgjiyIpoWk$QEhBNLV!M{*L#?vG&d0$;{%tqQeXXT;DD`9&|1H zc>Pdm(cL^e(@!cIqF@J9t0KEowiRVY_rXiU&7L*9rewp9FE*zSe4D15hpY&mxp|$h zW?!6V`-j(V_Od^#SI))hO`=C*V2XMa6uR&76AMB_2?GElgfOD}x+vQ1nc7mP>lkiY zCa&S+bu|PJ6HS&cylg0+(*Q%XZoL2J&+?Ie{YO7ADC(+z{^xVuh`kSlR^e}E=LLf{ zMMA$>?>ix=zV^mNb`AhYsuTcVuFhO+xwgqID~ikG*W-bO$DPSzIto-ot+BR6e{82F zk-+s`_QR8}{2%7W{LlZ-he5d5u2y;Ocg7MRq=9tY!tE1YBiIy8>^2HxUR|yio$FIF z_p8~)zn%;4J#rO&PJ1MBC0YUcwE003N{nqc+dH(pr3{M_VOQhxGt zd3!MWrH_Uo006kxMg5k3et>sMkNYddoW@a0l=V#BLe^cW=# zv46!A^N#CR-Tr$2HhJ~Mf#+}E`SdgW^o-s3vjkeY>H~RGH=J+lGrx&%_IRG@t z;&WS4n}Mo@h1sb(*NWtFe&s;L8^;;vO~Db+nAI|W`bIYSQT4z6-#)y&tmAoI$2nn> z{;kBkQdCID`8HW@w!k1kV0X$kca3m~u)g(-yU|{Qx_p2X+}26s4UlN#I^nmUecC#{ zo4nbShDid?RNH)0)Co0Q2{qsz(esN2n_?@P3eb+Ay|P|z@&y3U1PFHaCQfSW+CyfU zS>)i_V36ZFrbn~_XwHKA^3@5uzO9l)zk7ApuhLp(4G0`;xIx)1s?@5N*=E%@06Dl_ zY!|+&EvBT^YcUsXlcRpmNc{LdD0D2~vQ^CTZDQhBlGDwUt!@s_Paes1bx*su2WGk@ zy_v2h0i080nxeBCKFS-!L|bdi>A2$%<0L=BNV*mrc;l>WXIAL?SBKksl<#+4@{Vgq zr&3wSob>g4=}AtCI=eD{vhVh*N=Q!gZf6_YG#4&t+NLP-Go3fm*(-f>*4iLnm|C<7 zYdqwb5}BLaz3A_$%M8X3lUs+Th|!xH8z`k@Tc@bhRk9Afa2)h=3l70n$xH_Op%Qhr zsw8uf%e`LI={woB#|+VISK49J?IG<4T~ks3bk)>Z6~9%_Y+~eLJp)ej%(HyPSf*=O zz#_BE?y;0s^zbA;HH=Oou~Ao^5*Wrr2#XrNcHDJ2;5<~3B_z%xQ%R84gu zqP3v`#YWXO9An=cH9b1Gtj0CNW} zcNkJlRd?<#SQ=Htb*SiJ2wa_yWK@-vChBZDKakRMKjq(i&vL zxAFAe3R+K(t|^A3Rx2Tzs_ase)Ga@|B)WO4M!FUi6pBGp*0IP7(CL<)jwl~DTAT+1 zU{L}YKu6JYEdJEFe1_R2w69v+s{*A z1E9IjiQz$s=)hnm$BruEtV;R%-ljPqhL87oV6yaXySy72{#;*6d^Sj$h1QA^29%U_ zPAn5?l%;D8K*OB0Onh(-Cc~OIHuViBR8;9Axk!^<3O>|%Ph zAcCkuAYQEE*{FBrE_~`;;$`eLhU540+G#=C>4(={o3H-(Q+nq;z2n!;|Ly0%et#3A zc9v%4WU9HetF6}$3dxvZt9L*DH(UK%y*|@)YF+=PAr72(_rAGWzJ8{_)O+Sz*%;*3 zO$M^+^%Ve7elG%(Yt}a-6odC&`Ded;_>tcCb1ppm#u#2eNMHauvc3^5Lbii}kU0PV z)L%N1bn4zBsu6Ep=+-q3qVnE1M*;=*t#863Brrfzsei?Rqs}jU?C__*)!Cg40thPc zJzDvHcMbfd0Q?&&dz=xGrKXn}L4r`Mv?h+{?;@kSBa(mbhl)Qw8dl13+==Z?B zYYdk=BRYB8YJ6}B8&6!Qn@`-{*&a2n)1hx4ZVeq7JnQ#Fz1=Ws~T9v{NC7(l>j}ne^PKyNf5G=L_xu)MrjWeRq&y+*F&gJ}GEJ4zDOZ2i1v!s5QsJ=%!k3 zi~wWS$xcC`3yoKl18y1wv)RVJKXZ|C(@ohn7eGW-&km>BJ6I|>*LiHhB6Rd{9Qs3> z(Xy#!wM7sS#I1T}7smn8p*PYjTmZNH3&xB1Mbc0$aoB4cF^= zW&jvZC`b&G;l>83XzM09WBFx8dlBJF+5=_mV$zCc1=xnU5)>|20~g~aR_pr}50KTh zinRxzKsX0&OH`o+Bh2kPRdAK|x_~0=O?j{j%s~kqgL+wE8( z;~j%q7I#}I9PUs;Ipv&M(W$_N^ujT0!s_9C1vwawTFcFc*Gt+lGX0^0^WE-ttyzk# zOp8@pZH>At?(8!hYUSAE0O^aX)33mcB+@s2Y zO&vVB3-ZTSCEXh3*%kreG#Lkk5^h&(ZV#&PT8fN0z{c>Z=2j)zxUHp1fk?`A?3~Ro zl_qmqVw6oTRf_;rQtGyW7-$2?CT*0AO}7C;x0Q)?J3}9&t4pBdfTJS6xHP!zT~P=wAelK{76v-nR_A#2|qrq}t?F z+_al6(49vo^4fd1JV5(b2csR^V33F0AGk!)^|Guo#NG8^SW%5bFKZ?cx4uMR=@jVdT%A7O0{nV%bb7R=5GP`ZW;i8DFFXwij?)dO@S}p zb+|fz&be@0J zdRpl_Hn$j|JyBX1^Bgpj&lDzx%GZStPSa>wsdOh`ec;v3z6Q zdGAwRcdy7cWux3l$DpConHpqOj4@^0k|8TKsM(mbaoY;kRzyQtlzOw(TJv%->>rw# zqIds2l};O@?H#t&l(fsr{o1iK9WZ*gJbq2xddC^s{#!Sfr^j?w>}SrcP?dJ|V9SNQ z?-XyuE^`WO_-0_4JFZer5k=(|!91|SCYKuH^zA}KeKPn?KxbB;71w#PTrcs;L3aSg0*JYEs?NQtrS`~#rR9$x+#My(b218`J z_d3y2!jpN$8c`kJ=d+w@Da@KP+V?s=XNWPG1Q+G80({@%_BuIM&bk`d+o;ot*L!DQ z)+$b_JWNi3%nSfq*`(ZN{YlBYR-D^)Iv;!cy=*xikk{J5qF7-bJG)_9x3Vs|xz~QJ zQCPH?)BbFUL%$i zSv(kQ94AJx1G6>qTkq|rihQ!RxUGRk2vW=_UP!GtB|&GhUXs(yZ|Gq`BzoheIJkW~ z|MVIdZA(n7%>L7GZc`XJ(|ziTtS!r?@YQjYEs-4{?}l)K#$XMAYfAc6QAm)Z?zwn8 z_V)v1Y}Y5TEQ-Uc8Ld>gpy-XE;lpM9~Z@`@lT%zc2R;tLr| z<<8YmWum<_6|mZL9i5eCvb(pZfE@Tcsk@U@mvw`yme|~;mMfFo-z@BF4c4^3ytwBL zl>)1!>N&ZdEm&K%xKk3>^~>IX?ETSWascb;-y3xQOfENt7h=DGddWrAh&IkQAkVPY zLx0kwk7soOfPz3bqD8te{Lbd;wdM<-Ppq3s{kUxMG%t1wbJ%5+U4eG90T6Y&TnS!#TXZNM8yqFr$Z}YwPS)ubKh1!_Nz-wpoOn`ah5}WaDU_M z!%7z@T`N7>)&@st(wVw0w0`(22_3{=tiwE?Ay>rY?&#yLjOwbgVTC_C1=4 zxykMK9m-6INO~fDM|pR068&RynOAR=Q;d6rGOrJ!cL(3T>@j5jUXU*hvA49I+y} z-s(R7KmRw{%0{qj9V%#5Lux5iPRwoT;BzO0s78heoo-6(A;_S}Gah2ySfIha4V?ka zmxf}d*B?9HMm*hmIzuh3&Rf^32P5j1#HeF#31fVfF&id>r!}neyt1NVYZNQn@@x%y zynWx>&pvzKW_csE+2K;wywXl3wbt6LyG|T}5xAj+x?IzAScbq{~ zM8?7M*SmI~77M!%RfrJ5j7ZB(D`rK(p$}6K;)Ks=Usg9N4<+5^p)9^?uLIbNTt2g9I&qNnE+FTlWiZiN( z#8rZ^=>UgfTu9V0^7lNQ|BE-UPy$12R4YV>8@pFEs?HLQP|t)L=oxiZ>A=SpfCUc% z=cr0Hm)y^KUMwc=aa3~?NUP_WyG1^4h_5uQM7_$k5cb{u(XHXp`B$WA^_VrS-af85 z2v1dLkWo)|nc{}wn}NkE0!dSWXMPkhGn~JyY#1F*cs$3Aq7~_Y;*pesjh}JsH2|77 zg$J?2sl^dO)WL`=p|kk;>H3e;ouUGrG440ivTZwP{7|X!z)A!L+DGE9agBols$@UOji?0emfy$`Kx*t;}! z?m!YFBI>vgQDJo5Hp??18(|Vg99L3Sg$(RY5ULm<2<1#k*YFj{%C;oPLxuyiGP7;8 zsk7zi9fynC2aDr}q=eMhHA@quQAJtHI7i8VHcr&yD(rLo=9`^%ZQ=LYJ*%NoQPpYy zq!5M{S}6#CV|WbnyiTN)oN|*IO0>!9QW`yaaI;$K?Zub1Gz_Q-(3pA_p{gbnbBw|2 z(%*;2UXbIiNFO(g8C3#lttffR&O!h{UT1;TkxG`3mw;#`5TuMyg2-D_6~PuXQ>%r% zNtd`S41#^6Tu~|71G@Do5OrO(HEmNzmqu3ARa7TJw&2_u;eN>LU-M_FfVLwXHL&IwOJ&ECK}>| zI7y4_wEXKtZRjKbU{#<0#q`+v@tFh;9emH)SAqir6xp{2FWU2O4Zmc81B$Hu_SR}q zCR#uy0RRpS0EkxKPKV?+6S?>%Rr)q^BXsjuv3I2JfL5UX)V%&3mofOuxn|_cU(O@L z``MouyziQM*yEEP`7W(szMYVKHx2wi06?0KuJlEA))tA&J;&@K7H}Z_aw!MbYD(av zTBXvL8{R}(+gefv+n{SY9o_T0{mVHFt~FVuo16lSkzvr%@2+dBdGn1LVO;WwVYUoo zNKsjftLW@WzH$vGa10Z!G&Ppo>XdIlRc@SKf@q3R>Ugc!rRIn`yKd*oh*rdZdOMhG zDM;(6w);(Ix1pp~VhQ1;H_apli?tk>X${+DC8A3Sa?GLCEXURPbNJ0+bVN5BTVr#1 zOf=HLBtfuh%Yd`40qF25<7#8>R*X|~M4Pe_QN%@SImp>bEI-S)6JxEgmXxMew}zxK zTg4mUJIP?wV%&4O5TX?vKEHD@ZI0<|?bK04bTgJ-kdfpg>HzeJfECg#=)~ z-BOt4hNYn+P9H{ORH(*an{0=A-kOSdJ9NrxyVUIo12%}|yx)zN#%fie2B@lM`U7qU z+w%+cm`&-e5dbN0k= z9SdCZ0dl((Gawsi2ZV9TeOuAyXfBT)Trsyw2yl90uIJ`yB$KQuGq*~5VvB)p2@C9O zJYsK6gvOY7W#Lw{j8c(o%=P0D&2M)K$8Y#@p&Mgh4KR0E(36G%RR-IbGZBQSfE9yD z%RCT1=>>b#ojP_O89Wl1(A&UgwV+Z-+ZmO}D{tQ{o_|~33#Ea_Q_#b8e5WP=dRD+G zgJp61Any0E${I^9`s~tFQ4a8y264Vg%WX_N!yY3{VHTk*MlvV1(QO%a-Gw zLk0%z0yCsO&Bj6N2UbyVVRw5Pa7;|os|If?um#$vm36Lp(I`>G7-A*zI(gIaEZ(F* z*2o^>?oEVuq39q^QF*D6;h5d~_qV+rllG>l+ozUkXlbfOB2ZA^Gs{)Ebh(PNrm8US zXlGJ8p?Kn^dfT(DY#_jC5j%C%u{#(NAw{c%h5|vYrOb*lj$k(|-g~%0HpdRHA%euj zm?|hVYGGShB3D4mtmvil?IoSZ1Ff|Q3!)?hfIxx};)Fr~64@YP7|aq%NhM8Y+NO(} zT0gp{ys0;I8+%YT4L15FK}eN~+Su@=RyAP-CYLYA2mHa)okv%|J(7#E@k$9A(O`rL zq6k6+5rT+Qqi!o%=CJ+xKuS(cgPD}DvMHGrWTu;8hgGdBLB|Tl8g|!9Zj4N7geGBT zDzyMwah)S96=gs*xK2!Fo77TDLx>KwM4{@3X!atEY-j@ zIxsYl>5ZA+6(l%`$G3tFg?ODri9*8c+o8p&I9vPonj678MjOjNTvn?$nyu5FDJ0uO zsX7*_lnqDBH$nm+GI-U)7)#&ox4f&gG-Pb+h{H06v@YVxa{oGvguLsvMXU6mzgT^{ zy89Ndl1aB*Q;;bn5D0L%X7e=IY8hnedSvmrFrFR4RFj`Jpcmx zdH{|dBT>g(QWZUoa1>);--&>*ILiP2}m~B3D_80fI|BAY&@2Y_x2mnIS+XuD3 zA4=*tCfhMH0~eXuLoJKM)TV)5m{d_iRkXN(S~b`LQVy$hxP3fOwVjt9Xyh`=3lRgm zB3%Y8BUe>aXH9GwZo%DZ>_G%uotM@quxSUkrgr+O@uRbf@wZKn((S7ifyHP#XZ(zW z8|gKL*33qh--U?j5}AYg`ZFNt)}ldGMQYj9vMiFcSX4epHjnCD=WtjlWEQcHP3K@r z4|^K4bn`&1@*bV*muv!E?tT(LX%=$cL#Pa#CyA#12yTEE=oM|nV{wv zRGE2exb0P^$*rkv{EwCzKgSaJG=FtKxHO@+} zYMRWH0s){cpiQ6bMdQCtL97ha0w&8hh->kPa9YJijWl|SW z6`z9SsG~~{wj9`9yQqu0mR(0zQ6+1D_k+^I;L(KzmuEdxBZR48p{Y^oq(+9cJWQpn zTLTddB!K|QqP~4a$fBx?QpU)|w#hBq=aYQ|)z+9u<`p?r;SMD>sUQ?k8fH##%%He- zg+kKU-Sq8Oyf%VLZRi1nA$B^NkH}iBvsGNIx5a{qnh}N(K@b&nuF9%=Ylc~|0b`8%q>`F9^DC@RrWuvrKS`i^2vV=7- zm;oW$WUHpm==P+o%e-bxI)S1TdwK6_w!dFFT@<$mJnPpN)ZS$fW19ocYr{JCcTfF; z{te8lyn}nk)uC!gEb2;Tb-AddYH_H9;KW1#sk&t@5}dWP!yz(s&5^P&AWfs9w96N3 zJo#&7wuaA*e)<}`|3^o^_=$^8ytw()3$tJU#KT|wWS)=0pMSsk{cxIhKW_(B_M5*q z`|O?cORp?`_4hOIEnAD8eP90;DSrDC7oWM4-#bb_^U2dsoWW;4*t-q@0OpUq-wtM* zkN@7{v;Qx9ZxZv{mY#R5e)rA4|JrlT_nqdRbI-ZexvE?xr*V;r<3I&G2o@+A*pej= zK!QP(0b~$MM3I<)5J4zHSc+nVf+3dT$O$pnCS_Bxt6WvL>h|ZJ=4=1`?)P5(V&Ej$ zQfv$!L>Biq+**3~(mUCYp7&XMeZTsZuV4R-U*`0|_(u)^@Pp4Bz|HqJzp~UFmoL9_ z`?Ig$|0hoYJ^kU?X9)kfzkB)jzu&&|s`-^)zWC36v3#e6e~6%aH)=EJM*+uywT8^6GxKBk}@zW)BO<^cF40^sHMw_kt1`LO*x8~6h|@Cl$W z;fY_5&hIxcMYKPP*RkDp&~qq5weaAWQO~CV$EnkOV0HvbtpM27{_;Xn+#?E&4ysNm zRDx^>rp_?57G$S@b0HzvG$3FK`3e~tW|#)X5@2XN3{g?^$p7k_*VegK^2ITP%{NQ+c@AT*t zBSeRe-)y7kEb13VnclEO7&VzxlLMr2$O#dX%!?fMy@+%qOaoUkpmXbmJ+=_AG4Mv7 zJUvogu#4^wbqQ>*oC3EM*mW3~fO+%VPwHMdUr`@H;F;klD9`hf0FVSp&Q^30w$~VMZ5+iU4AHi6FJ3g=w$7&#`z zF!X%s4uePy-LS>bByx}Q0WHSC;lpq`4^25H!+8GKXF&kv?Co9ffw0^&p=T)`aIhy~ zDF46b1 z3&$W$46xQYEVq8zrXvwX42&OOZ>o^j$RR#?#JwCE0o%$-t}q@j=3W5v85e^kkxQc- zbsjkeTOd$sW7-Tk-RB@`{?HP-?%zlu`ss%1Lu;#m1`##(5=P6Pk~0Dv}%%N{}}ELh*e?bw5a{fkQw zA%@7qG?zuZ+Q{w4A@vak)($duZ3=tCdtjGoSerhOmC*u|6j_H<=Tz$v6#FT~H1eW= z1rVap3yo3T#HY0PRSN*L&PXk|90oZ=UR0409)^gQ#7a%5rOW}Otp;)p5ZM_UE>5B_ zt9DcwZG9stBhe&?<_^M+F>5t~kP$EPVn!H*Pyr|bB#0mgowG_*L;V5)S2ieXhRFBI zyLYNPhno2gI##@lon4qsK7Q<5!(5G_8gRhDQKj`&Y}q^*@$j9(?6TkH7N$SO58y`{%=$>G-ey z1pK@IX#e+qzA6FCr{P!r;_+X4_Rqr=qtBfD3|al`uNHs%=PmH*qsIq7^P|7>GZODd ze(d1YFRXv|zy0WM5&ZD+^e4aO{p^3Yk^2Ar7cU?E)t`bt_m6h}@E0$Cj>9nbzWCMC zzx1W(4-xP1f9QwPU-`xQ=YC;$`qg_s^QGheV0rU%zk2nb05m&Ee*DKyzCz0DSL=WB z^Ed1#?*HUT`eR=z|BG)F|K(NpV?U96_IH>013T~uYcPVUW;QH4J91qZgkjZn^%A4t zNWC6hNR7ZSYEh1Go$EC*i6Y)wlbGRperLSBs_DT|u=gNoaLNKC;KmNcK^}*}2vB=( zvUTs%t8$jwIuV6uhXKXFD(C0Us?}4v%vA zl#Vu<)A`U9r@U6_cr$(i6LWhqNn(HT_L{!=_@E!h3!@9mU-k*SFFz zqq%3Y2+}chiQ?_WP5dsDYI^A%wTYV-@8V|LvsgncDZ`HX2>Ja$OTbw$+5mU&(>P?hPp=0uAJ|v*PV6 zPKHAQymfHkVV;-VvpH20Aq#24kN9*IL9>7<>;yJNumcoo{@y|Anxb69ASPwZw@B$s}H0__u*BD z;$${L8Wp!L)V*J<#zVcD93J=qY@hdcKg}+c2W}np2|)^2#i)gdAZ@W05AKo*s`7=j zs>5tT=@D?>t&S`4AoOPy;$3=>uUqSe9(c{Q;AMoGqN8^Q)SAtcH_WgK zUICBJ({Z;u>bAohkL^dxbn{IVFl^Lz;aVdDFCWjXZ*~|_6Hjfk@n{ff*#sx`PStR|+5AW8a*O+V*hTFg zl2B0yV_)tpBVLlugO69(F-pl8)5WPXq_4M;FbQUPt~PwwlV?A`ycU1~5Ua4nLmw+Y zK&jc(9n=|L+xM|Iag4yJ+pPPI+(Q&ZE&>q3W@rL`&MEGbL;z>30l;w-pnRSU=;8vj z(V_F(?MAA{bNkL7>m}ab!a?CUbQX3)hlS}#=1uPmcp5e@mG9`t41Ls<%vb9{bv`2w z*pMaR{&w@jhkf6mC1cJ;EDpV}>KDkmre5e1kjz*RF;^L*S5?fqu`;ypS|%Gy6z#~C0< zjd;O_vOy>JrSJMb`dJ(D^h-xizw`$z2!8TEe)5yQzn5_K`QyL&`QP>6@f$z?yCuuP z=%;^T^wYnOhjIMu@UJ~P{8!{Jrul#PQ@_U+`I~?JUwAJMj{nYI|2?AUr~k^6-(COg zM<4wBkN%!gKlzvQf7Slk*M92B*TC;vYX0o_zk2q6dr)8bi|1eYJ&VVG?XUdHs}9L$ zfAswC{L7b~Jo@>s{b4%r|FD7oKhUvM`(b=JNQF7Y4nWUJCigv?9g(F3XU;ieErHGf z5SX6Oyhvviv<72^AQvr$?W03wz(}|VVc2^k;z!M1h` zTPrD-mW3_LdjPRfp&z=YIiEE3i|{>_^Vp)7OjeH;=|z-nPL9KJ(=~}j9|woqCtYHZCec}g+5@QC!P)OO+PoPKJ*=@$+&*aHT4h$()fLQ93_WS z$8QHk$5!=L6lOL(s@tQHvaJG2I}0swkU7Gb!(<;#MA&wg5==WeNEhjJT+K$vr|GH* zQv0QQum6Y9DRr6xVA;rGxQH~Q{Gx;wA#8h$W8kMw8;2DPGKc*>ibWkedVMTp;bQljEE-x%)j}q)%mBZ3~LM^9vWypK|)D1jIw1Jqh7Q0lx}<6?4dt0*mBOR z8c+lQuz?rA7Ba$Uw=?R2l$IJvoU_g+y>$BVHHa zBCruu5ztE`d=%u-XxnTFX2@A&1;QSUQ9#54eEa1GFMs7X+#aPOO>`RdFl)jE>#jVe zLm<+&ifI!U4fQj1hq!NxYE1Edhb0Hjg{li80cT^M>>vbO^-pKlzxgZE;0~_R=}xi( z)@P@zeLW?(F`GimS*9(cMoqFkh7uXPbsj&tD3ou@5)LVm{MkE+4%*~#f zUPE6+M_{@LCW%F(`Sv-6{k(a5I_g_AmX2}(L~s|5A4o)s^r(IK#_H&dBRd1Px4(X? zwK20;yWSHU<;7Jz0lp4Qv#K}Vx1aEvA+?AIzDzDy8U%&7;u-cjs2&Bo%3%uP6zxjZv z71?d1bsYji4YV)Ao0k%RhYwwJ`MvAXKQ1;4w0E(u$4BJ;52D3eZuuH5@cqZ_?Ur>{ z7q7v*bZAmtqO84|nyTB*N%M?cX0F~meN4=%QC)9TB5v=%_0)&Z&tRImSUO~d>~VX; zea}#j4NZ(73qr+P+qb=r_xtuBs}8pxL2eO=EnyY*2Y^TAYWnKE{$e$3(d6o7;9NbP z$MM)}*>}D<`|WRM+&Mm(^eEYDJe0eMSb}gmfBl|U?*wL=CK3i~*WcWmE$=)w4n^($ z-@xAqiQ_-?NPX^M%;Zd70e~>_qk|_WxZG4Lyj{(PmnZU_nZ3M!YUjuKSZ@^1_51l>kYj5ti^$?b;pdmd+!MZ#l!6xSrZ3J=gs zM7Es!*jY20KgOf`7=suOfT)r!=^D-qAaXLPOkC^$Zudw1`@|NlpVGUZ z>FdCmC{!IW2!cSP$QtS)=da-r@&hk4TJO@q5 zcKZMjFBnJRWCD3EZm=E@F_U<^X8~Ki!f&r>$t_JmFay!N#U9Sl$*1oBZ|~lVw_6OO z{PHE9g?cd@Ay_8Ciq5dhFyYmg$pD(FXB-PK$T%~0*zjW#Ou!htuiUmkfB z7$vFK0PLzJ0>GWz@4Y+8>x<*rxg^vNvkaQv^sr% zgRXbVC`u?Jj3UAai~I;cuz))s83e2sWY6E9zV{9rGFZ+MLPCzi?#{DH^%$dq7yH2L zC+8qJ_Mvusz1z0`_`m;1xT57v&pT~Q=b=sLTpEp&DTV|>$gt;Af0|6SwibtmrJo(I z|K#TF^z9qvsZvT4pI2$jXK9-ReRA)kdNyD(8A(WBkAtY&gh|`HnQsmcmgkELvFwx* z){$nL07*~o9Hxg5Kx0kI`zB**ocDN)xrq8LkWc~YR7prQy?OnKBKd?r&QKfh>sGSJ zz{tu@w5^qBe54omi}Y@!Z+ko~{d4X##jc^pQs;}hzEdtwhpV{S@IU?Kh*4ynA8J45 zKyKrDHB!9|v_0y1Ory7d-HCcfCLg@0K!!qG8r3#YG^j4xzn(H6A-T|TWc@KOrHlRC zUmQwG@kg(Zzy9LMTaT)3KkPqvE`H!{R2Xjn!Q{{13YBe=_3)2SyQ8GrQq#@dt3};*nj8M+0#?I9)sSq zcDNgd$Nbnf2NFEJShEjO@Vsy1kr1f8d*4nMIvsy_lfl;f_!s35=Mi2;i}|OQ)v6Zl zcIyZQt}TU$dvmOMmq7rJLPt zTgmnXtyV#?=AI{yvuSQZt;mb-9=G4+*H>e2itK(@d~iDm`@9L>dD-aZmN0;DF*#=6 zT-WfdT0HFtz5hTyz0+pI_LK8%G{+x&S0^Cy(mY6~!{AxluV0GwOKdGh*cfXyLlA@k zma=qLt?NubK4^nuY4gTA*aQzFhuu5BwE`y=M>vaZ8pNRtJX>s1*pI&OIg(<_>Cxfp zl1a|avLx`4G*&qu=&=V+Q2OAb?b(ZWp_2*oW|P5ad;Y3~=}3tJ1nX zj$h55+2|k(5)WgnyNFj2mk?OwRMy^JzVTr2Q+xJ|Y8r1>I0`{_knfkiUE97B+C^S6 z%ML;>34u>70M4LfOvV{(pY!>H+rzJTn+|G6%PO~U!dL_>Q>C%pHapo>w?x@L_i6R6 zJ>P1QCRvB5iJ6=yY!tUOTfgpQM=)V=e#ZA? zH1izxH1+p7T~}iwr}K6D?;ZGl7%(ru9zR|Tr^ni7{Um{-c*>jh!AzfJKmD1vi2xu9 z5ex2`{R4-&74l7S*^nq4RW(_U9~ zyhaE)=bUp{n6C>PJTgcy%$1X6nv?^EF&;!i&8L zk8A%)QpOlykA$P+w_*j8?Z(mqQXHQp>CT zML<1-NI)ab7TPINTxqVVbSBdzNeZxN83+M{5WPu5DK&VU4jY8>0-5T%Tglp^0-)DT4s^ya3UFN#&5+r@6Dv8=8^W)LuT3o6z;m z&_r2rGOtGg2tf?D3~d8{*#d$UQpAtbID6|eE|hZlo2{u!)4On2htbGJ*{gL3gHF$Y z@|=Q1B2QVXu>%tX62$WnC86gf&g`9QAp%@L0cm9b6s6riXYk4-Oz$ZL|!X@eD)St#xwk;N!n zUpavLEDN>3QYNs?aX(@x!nEfc@V$pnVo481I!p))F+jjs;G9R;1V!`fB3kW-DCR7; zfclm{)b06DMPd+?H59|ml`gK9LnGe0$BzzSno|}MOc}JPX<|`p3o0EFsWKnF+R#Aktv6B!4J5=4R!qd;sh%i8RbbU`WodEVp)u{$^b zk;l1O3e;qYR9!$#Ujzj9&G7_`7URHW!^*$D^taa(6b4wH#!0#AvZy)AXaK?(YnGN^ z(k3xLk`R1x8~2AKBYnWU?|pYlQ<51&001BWNklt^d!Y6QGteu|>(Esy@(;%X54zp;*i;lVhKOe+kWJhdfFMmG z1sNeQ!qSHdOFik=Zyc_yj!9EXKXjN1KY_`CCoE|S#=OXjeS$-~!3eZu)F|1Cu9DTD zI|)%AX1yQt)rt+mk<5WkLuw$z2*JR^5%ZvOVv~p!bqWp2$(A`mn>|qyK|ql82(r%h zyafOtgnbrK2MB9`ynigkC@}vCcZ@FveW9gX}w)MA{-b$m|%W zQ=^61HKR(Qbc})|-EFKA1ha8IzWo4H9~Qoq8Zn897~Pi;MP4kmG|oybT`O4)b7sk;r8~m}xfkPg&ZXVQGY6MF#CJs2Ex^~UK^x*cif0gw&5mFdIskrR+cBn*{ zR`bK?swEv8-Rca58aQdR15njNcd>0mMKKLn0G;dYxS1rLSktD0M#i>?7_#Xp#H7!{0EcybO5pCg zLI-UP+r26;$Kus=c)uB5Po8{vS0BgCwLEZ_ezLCst8TOY!-!wH7+gR6qIfq>y!l8p zIgDn{ceAY^I7Ozqdr}zdS1S?k3J}q~pY60MqZo0Xy!)Qr?!=t{WI&t0c^c%z4@o?G zh403Qv7^t8l3)b6#}Yps`$r?m+r{mr#>gW#{TE3$Mr9Ol)7g&Z!}=C1Kioh4;k;T2 zLWLLa@^nu;Kb$1N7(#6Q#NIs}zA#$Jcz{WH{u!N^>TX&=6YoFFx(`Xb3*Kz`gK-*+ z7vgz63**~sda-`}n;rkS@O0bsb#?RJ?QlD?SIg1U*WSJFe=w$=@khvD&>7g+Pyy0e zca08$30G#kKo$&&J~#c%rp~GL~S^hbDBTH0Z`2 z6~P^|2U>`HvnCqt1-CaJN2_&*(vZapPa@N{s@Dih1KQ9pl2k-qXq%|mh15^>x5Rbp z+3^NO|NGl%erU&^^{O;91cDGCCLKtB?YH7Leo)M8GbphC-XQMb!l8Ys+!RHZRHIecc6=4lY4uR zv5O)92hYb-v3~UU>gf?~*YWFj3hftdO!6xFntPO#f8ukShTBcE3wO`!0QYesynIlN z9Bu%Kd>97hZu0GKEa~JTI$eVCXZUx)N7cu#60Ws$#)Q(%%xgVCC;*m3Ue<)i&*sZ> z@1L#OO{<78#>1gdA`ZsJ!Bz}$dou^3#NMYy?)pdb4^?WjP_fVw9Wx2jfTKYqv$40$Th|XeguNB+)vw&F(xSa?48>yq}Xr$hPw0l(?=64gx&gy zP|{fI5g#}wq?A(T;VAZ!c4&kc0_rnN2!e^1IA@pT%}1OzQb|NCnT0JeyQa}FjA9(? z8NNo_ALT~`Vc?vzppsxIz)t8vasvSMGmxDc zmTrPOD+#bJBkEZ|c-f<6?{?LCzZ2QxeYbr?J|@n^7*E^hxW9p7HIXkz9&X}^I83vT z`-|>$2a4QId zvh@=_;a?jE^N9M45vfrr9KWcvGJebUMnw8&BhIwdly>z7Dus z4JLGblA;(VdU5*o?Y$qF3%dy7XnydfFUyuS>J6g+Gbp-C&O<8d^Dw#2P7mjlRs427 zSuEa&LMcl=_d|7(zQ;bDPk$RWZ37~lH!NSIV{gpuw2u~PF-}<+vU2=G*`^RS0|;b! z2^al&cbIr1G(ATr^D(~8+_gPDEKQn(^OI4VQ`9FYj_54TrsuP8?i(+dy>&2y3NZrhc7m* z7)HK#$gss4dehm#8waDAIr@yc>K=Bkx8A(C`1t5eYL*9}kJ7B4Fpp+cyzplC_2<3V zzLSjSULKRFJnU6l+hvd!VQ}y-h^zOZsKF)Kh{Oe*HCFb-v z(ePBok&m*>`5730_GoB!F{JQt7H4!uV1N4PRgpgO`Vm4K%h2QywrdYW!C{864YYT# zSUqUG+h=dxQSva0K{}0+{$$=q){TQmv1k!kC!_7>?s=*M;{aVXj}FCp2t#P@L^sK! zbk(1oDO`Rrp1k)wo`8cOjG*$NL2+!*oA;JcJW&7O3ys=gLFniI`gbJ3T*ro8T#;-{+G=c|1JLQ`RBx39_`C4 z5ew*gLiORIsUuXXdw=%cr+&LVxGWmPypy{N5aNg)sDqOYH*I^^bO0?r^X1dB3aC$W z)a_Pi@%aA159jrZm~^E#Y3KLTyi!>_+B`c8;rl%Rf+P^PDq-`eK27|S5lPX*I7s~@ zJ=^mqGuonOMM=gX=t7cCqOPp$?9=h=_}=W1@;Pa6T12_&8XsB|%Y5oo0a}_=&r+no;-#3{5tkVXN8_+hfkC3l-jillVo!IaGdy)kW(*05APp?>4TA7&nA-@ zSWmi#)w3T;4mY##ns%1Hst?gJiRc&N?h{4w34e^K>$>KBTa+Y*UP0QMdbcBMOz7(R z$a@Q_rmMRGPbd4>f7RHnD7_(W8`j+H*2c3L?Zj;rp3gM54RC^Z*to1WIBd73XeALy z?5@D52CaTxbT1m?Q?VNK2JGiyK}62`o9^zXZr)KvGibf{vemXYChN>};HoRLd1KHu zM{lm%q8%H6k={%04}+$n5X-thSqK~6NUyh$x3yz&)rmluvBib7O{+U1_S!+w?O`tE zN@z(i*0MLE=apbh*PYVq;Mm)}Gfi~wCTl~L4s}I4*zad@YD??ddbf-0 z7SI}lTJWydS`aM-&AjsL49zcJ{Nwu3f##Q7TyqxH4MUv@3gN)2{uB3>|IN*5@eprb z>DdAcNA?aft3*=~+m0vUfBkok|NTGnw*JkBrY-n16`&cKcDL<%ZPjvLsCqd$$o9fC zH_J}M**MXnEt=bCisSsqw2{|e6??C&&L6H!H#ix*u%P0IwIIfB9n5+FO!8 z-nUKBmN9G%lcLV8q-k>|KRRspc0Ahe%<=Y8-Y}8S!Q};ynTK7&#mn8mTmxnW^ux{N zt8Q_p*muRgs(`^U^OFbzf2@k{$+m7FR>5x-EpgysC^m zGW?yo-?zN?X`^g|jIx|#2JPea`K#@H-XYL%LNf>=?veVdkydSl6ZHTmJqxB)vBu#Lx+Q z+lV;b4YTcyWs`De0)6#*5VRD;TVC*N-lV8-@!k)7)7XAL54)@HwA*fRxVf0kAY~ok z#0aeliKXVDUn_U}-H-YQ)8$StMxt-Z7z}P}hHzX!x9?3`cjW@YkDjah^?rLIIZbo{ zgcx3MzF%66fI;gv%_nfRR&rPEXTydYo(6!?>)x0KOu6n935v2a@fwFCu8Pj?nPJ#m zHu$(Gp+TB&SAz?L-{+CyTWV@Re9SbV>#}U>DyYh)>QuYmsd#9rrm5R@2({{bwFK*d zdx2_}H_PO3ZY!px;$m>Bwl2hdz*;XaRJpl^pA zC`NhF4}|x6C~GTgWi%{yJ7y56iLnfB|;_WfWJKLISLHFvV#LeiRw*QLLH z{fQ#^gg?e~LJAn|RA%&$NLlTR%@XryD!AL3nO|Jil0*r>0X3c43g-6Zlw80>WS}r+ z*v_9dAAM_c&&P5greQ4I1mm_tllFxjFz;I>t48R4J|A5_Z?!3+>E2dbA~$c2Lg)_H ztK?k)&cDsu2}h(Q=!2aO^5!HKqTnH@qY{_Pc~i7A{v4~S4${F^qS*@1S(6A0hi>TJ z{hk@qt>nkd3n{Z6E_}aog6cJn!uMo04lr77ip_0C>d*^`Z1})C(GLCi<2E|^RPpMc z)Y>u+ofyrg2iMQvm1d_nDc3j_Y{p{jH88`?=5}REZ`FBGl9V(bwg!uB>ot3AKoDO2 zpI7<6T}_6M{IZcglMCMj$zq{k%D(w-eCLg_mHXLkJ0PzktsPJL?OMXIot?qsz_KFm zSKd-z)|N5fgFnVwk;Otoikup>!WC&-tAtSi4TIS8+Ez>gDPY=NZs}A(80=!m{BME z=H9aZ=yH^qZ{@G;BMD*KJ1iWS>~o*LB^+TW^J8beLB zb8y+*4o^4g<>9|`{@&N$c~x%=dX~BnRUX7FX&SIAIsMMZudd$Jhfj}?NEzexCED?_ z@o%osqeqnQStBB0h2QK^q41!195lij)rn)Y{V4lXeZlvg*xj{_j#UMq*geNZrv$eg z2AX6{78yj`j2Gf#bD_5d$(Z8;Gy&sCsbMdKaYfk|`#g3vk8y3RZ2@gtu)S_Br{jZc zGFf)vv1zPS70zfnujpVzM?QXOu9+Z=^jo^J7j1GkfTUBb$ zVi0NO7jD{CyRKYQ1TjLr=(VzwcqWD(#IQQJd)Ud<%d7I#kr5-9WR&&2v*A1d-nQHw z$pbhvzCxW~ujw54andNJmR;W?NMf4w{h+W~a+YDFrD@yNIU9I^L!fB)lrYgZ3i!Jq zayE2Q?Ge$`kLoRuwrTpE3>2P_%XTOWSGB>``kw6?P*(Nr%|~2G-gmzfE>%Y5@hE0j zNfcwqRJq>lSFttD*$}+uZHb2gSCwRS+YZCJ0=o#Vit^R<>_;9xU#>Rxe7;lO?d@-) zr-`2#vb#9S5BjUZB*V~lb;VWR@kT0MsTTVq!X+T}Zoj9N?7L=50YFH>P>M7XaY3N zOC8l6;xZ&*esTxpKak8wg$`1Pg9hh@hTZ9?JsF84>7ztjIfzC(JFQ<#MTecgc40I0 zQ1qh=-oN7#hk;PD)oY5OInKIfpmj|K0@B1DN7@T~=ID%m>P&p_0*U>*cGzHHJwzmi z+jvx;Jt4WD1AfvKF&%C>p$Ltb7bFa)9!&y&5s+!Tz5(4C%8E-JFy?BC#lUUfg9Hxo zP*zKR4}I&-wCBaH@3AW{tdce6>qozt(yZcy2eJwqeE`Ry3}{iAP~Tfd$^g33Hs7IHrIyK_sL<0|Q_JIIGq>Q^gE1$=51J+#|jD6YWa!0h0qqv1kz8Y9>xecH1W@a$P zDB&V!Edqc6O%O#m#Xu_%nHr^f=ZtzX|LFNLxva*$Hmxk1RH{RoYzm8f+Ld_Ud18oX z;E{K&*W$8RL}_q5t-7xC`#MuqXxnmtufMgecce}i>vUEDPbevjGpe|F_%O{N(c1VV z@O^;7&P0M+A-f7@bjwdnl|h9OH2WLfRkH&`J#**^ohSAVnizqn<9)S4`!3Orn1D5c z=&S9cc{#8d=fUY)(NZixuLg*AVXpjuP^!I*(I6N{fwNVfxkWUPSg;fUFKmr3tVdj6 zt9GbtCvV_9Y}+qIA1o3bQ80lbgCI-LcoMvLJ@$+ViSry!%Vm7fw#e9F2mqHIrG#21 zF>)~V-Z03`yibh9Lq_M2j$jX}zTZc63rsBt$#{kljsP%BQR1CoI1^v$A!g!7z-zeJlG;dZw&Qc~&N+_4YS&RJ;DxI|U#_5M?44R2XNhH;hna zq(WH$B+&V8@^C*{K#F)0xv)>ZaRn14Pw@Hcre6izB(^2}-i|wEM;`*gIL0Wg9Q0tE z?im7w$nrF!oH=9q4FDj-i1aOtF5PbM=s*N13^~Ho!NR&F4(~pQ+iM{YmMYFukk{9A z_j;h?pk*>k;_sf`+%)y5M`=a~Q3&C3n`w|9AGr{MbW$=FP&6s=i@QAV#cExyZqm^t z7T}?0gX*n3=jr!}_!vm*yP7_>zBV3>CT zin++sa!&c#^d0>IpFJuGCTZqC;R~@mQwIw>h}r04XN`w+*}Ocucu8Y&xy|(XmiJu- z=)1%4&bA$mBT6?^yT10V1f`_0GBZws$6ntn?Qu3rWR$1FvJOHN7QxffDQMPbENj^ z`8V&BY96mcKlXjIRoZx;XW^Lbj)rp*wTNmTBiyDW0b+sG9ytHbJ@p9D`6gVT1h z97PsF%$knL-eQlC%1~|T6s@M;e6gO)(C^*z4~MlM<;YRcfxK?h%blWRjiH~NjF{Nq zvm@S_vbj>e-%B&9u?d(_ViUHv5O|Byt}BOua!Qz)`aUnKjiMmvkVgms%FZ~l#CHL@ zsAJOUDzv6UH7X}+ZDPEizr5P=8-FaZfVO3c(MJc$&8m~sLtz3m5uS?uPv)=0IP}|U zUS*4=+l`1U6h*AC`#3uDS2y9hA5*brd^8$%O)V*^!0OB}t6c{IdXoDIKDwDx>yB~Fy0EA0Gr~fdXzBB&y%~`fw&Soj;J>Gk!Kw)=N zyDo9?k&Ac!QH~p7+UPwZNq2#pr~OG%KVn_Ia~if_Mh#+yULHs{NXS0+ zJB0A#lfK*Iy`nho)Ad*M;)&^Zo$3Tls&q1_o)HEiGVMzzw;)E05F;!`*sBfzzz8$W zv|oPitswUA6e})a-sh435&`#OD=HKq6#>ZZn4Y}t`Y}b)QWM|XS1s?SFCSIQ1>5B3v*+# zQiB`nZw@DYN&DeeMXyDq5)-cWg4x*u(sGvi+QHwpQZ^u~@B) zv^6RPvX#EwUId`GO1F%H$+}KdKc;1)hi3b>Gm1^hN5}A9w(rLk@!fQD5s6J$%ds;| zn0W=}+{+DXYG&kDpY~^u{GDT!n#Sb|e|CnZ9C{xIrH;@15C33VohG6pJf$&YW1koY zTNSQ*ucvVO2u{;{+U0W7_?dM{q4wo|O?Isr{d$#r_p}kJi4Opxv@CLpZ0s&8ye2UW zElptv3TPJ7Xa`!_kcl|x`8>`}rzDxHNE)u`@x!Gqutz~f-PsG13p7h*;YZFfDfp~R z`5)K;O@M9lwqNXB6*Y}R?6+`OLuq$CIeNb$Z&&MEephVyCunxoTyj|*)AcJocblQ!*u?b- zr@;|BIuH;-0>lVHV#ZNM((24hATQwRDcY==889(UU>qH0CG*W0xDi9GhYc_tc7x`r znjN4V0~6hqozD~m1|ziFr*Ir;7^wix`~7;5G3@D7>)vL}Rm&usal)dhwQY15m5~yr z#M;ZxO{Vw38QWaDaakaaod5tJ07*naR38b0&9%7vI+wdlZ%d*9K^7rHmTh@tA0FHJ zM1>$d)?|qw^xjiU1x*;?R_&aV07T;qgm98hFhbqf5KM>bZ_L%!SzJyo=YKR+ncNx34)*By!U40I{+cU z1VX%>9yiq?!Y)|f!K}ep1!IR>^H72hIqu=@>JEGgvOhgEQydZ`N4?qinb7(gVaWsO(s<{NmTQWpg`4CQP7BRPens3ip~D z>}e&m{}1RRdHC?*_gCQeej~>CAHy~tt-sOyIGB+Y5h7=Z&?&dDNeIo|tv2Jq=U*_p zC-)fk2stJ^o}p$UlpE9MpUR*A=UH-T&?L^2?xN@ZiPnH7^LSc~zfP8#RTiwBm&T4$ zf^T1C`Yw+I#6jYwv8fR863~)C&D-7e>^=|#2uHLB8>L2o%7R|3w&JnJIZJ1Rbd4G6 zDP`5HO3pL*O=!mrI=Ly5fA`6>1j5BWR4{Uf-Iyv%J8z>HiUn&fAfiRzVgLz_kkf#% zb&f?1Xon%Q(MObL_IW&b|A0O#v5gXWkZYY?uI5H(6s5}7J)L63!=_KJt z$qw&d_~YYP<&G!i`Bl;#`w2ZrH+p*U&eMFcgvYu0 z-+na(>W1R<Kxm6YUWou$*1vxrfMK|8C`v+}4S+-r|OFddkt zr;jJA^)#7V0C~H0uX5u39i6?D^3i*Gt)Y&rGMTu%1QXpsQ(9w=nFZPq$*gbgGSaMYQ0aShlf8pO#jvD=@LDB z(4YUq`r0gL)C+UFNP0}cLELu}f3XLREn4GF%8W$ky)_eNE#>Q-iwR1YL@4|E?cy1L zALH>3W4#15(mHV&f`}WZQ|?F|o8+ORdjupyh_QlT=))!;0;+PnxuN4(7KV{7v_Y2_ z=DqjAy$=Q1XPgR7vts&4uou7lLCtF2UIJ~)-7DR)_U!!vrD*StSS2icZd^n000gou zb$T!o%5*9dh%zv7+gA6?^&_1X3NhJ%7+vZt#4Un)BLideVN)jkRW$@PAkAD(d1b3^ zZ=9Nw4C$*m_Ja3Kgxq@Laf{0^nQ9VteY$!TfA8n!^4hRyzi96qp>cu+Pq^j8I@Pp6 zK~`bJOov#?cqY5n4GnS&8ZiQ($C&^d_4QbR20AUVuu)lK;Po0@a(MdgoHT(jNU;+% zvSMG%^^*HkNNYNuU~wRr7_}^tB8G@D_TF1>wZqOkf=S8~ZME^HNXl$~?$LdQI8irc zs7f;Ga_8D2qJ_KMmT~P0dOcuC>6AB zWoMiL020g?6$H{DNt1vu4%k?!T;r4p(Xs4H!weQBkuC6}GdZ9cPL{z`fls|$ z6GRwh#u)mEh<4bcGrJe|INDc*bE&1T(h&$}kjbgNA-Lu><8R(<_N z_KW}O`sr(WrG?sJCZ7DI zS!+r|;#j;_23;){Rys9?elAJYuW#Ew6_UHN6lRM*jQW}!5^sw^T^!6JlI6%+` zZ@jvN#V(t43AE`X@pJL*Ic7ZJNf^()8;QV=-d!fir0=mK)*&-0Xq-AMmvbap@mS(H zQVK;#yM1=^9qMoDp!y_Y1c=rmb%~~w)+}B_BAMRj!;nKhO6?*lQdX`7&>1yTr{{2fU%mu_80cc4; z-j2yMJb0Jix!1uYVB9=j+H`5**cVmeuc_K&xhHn-gBnZ^VQ|>g_97CzA9uylt~T^m zEzFNU>fZlpAC|l9pr^}lknZ6MMFa!)=uSgf5g5=7zD|ir>WW?DR(SA~n z8Anj12`%#jtla#`#YmR?d`APw`tel5<9w3Nv*ILO+${sJn50)|ZoGFMU`jD1J)Y|H zj6+_b&GCEq+pm(0%ROHN#EQ*jcK!zU4Tx4!x}VPUVe$I#eT8UNK00n?5_h}9siT1Q z+rg3B{N81H^88Em!QJ}t$>v}_RDbvb_TYz+7VUC*^yBP5z4u++Z?SyajJvDNvy(?R zUG;8Fj`#S~bu336_-ML2JKF;RaFOsb+uk@FzuN!gt-Kd9o}bKPe)!_sn2uL@I?d1D z6hj;Iuxc))8L9w|Nt)-gfdw@Y;ojZ8s1OKf@h&Z#tyW_rrjo5EYg$3V(~#ZQ?cw zj}g->B>BBxFv5zY$de2qD2;>=&CpnZE<)g=AJnjJ*9j9Hxnm%LSiC>E9-nSsf6*mm zPoqzDx59UfRk&aemt)dyz~6r=U%l#s!J#YTZpfGGkWbF$OQffh`&E8Wr87Zj-mQ|| z+R<5=6y6&M-LPH`imWCFoAAKNDPl8t{Mg)C+=xB4JB{)8iR62~#VE$R*8?=K^Vo3f zFb6aRF$Ke;g@n^YObJDdGK?v>Q%#P`DHC$UP3I1e&6GkOLo%+Tfx#nXMym&46jTy) zHF7(6ilS=lq;iXctK}oh5(XSX1dOw_#X0Hb!Kbg>hzwwcRVvazJ4Ax zA&}NaHJF@VqIT_vqq6<*E}bqXA6Ho*?I-k$qAOJa^7&;`Rrb&IQLbfs`mUNMT}Pf>7QMQ4LslRw>(}G~2)X*swHY znI<_DQfj^#Dq0BGfxNo_oCAQFHCWIszF?JF7I&#%&q$`((B$k0MDXy`Bt)5Y!s{>BaPq z{$R{F#30B6h76i86v4RPb|6blE>^$z*o(;f4lishDWFK|ag&JE;&4y14(6^l#q>T? zF?dI;7XgA}5oR7~Ee&e=Jp_^1y+r8%SYJ%n1StxIAX(HX)^cKIZlBUo-w^?WPB7@4 z(2}WZ(!Ij{PV~FDc?AHDh+~9?>g2tj`NPG=4PRLh)+sm)D#1GXgk=O%M^WCAd z>p!^jx1W6qi1X~`$?W#JG=_kPT@BTxG73bXtTNVE?=LwcW%o^m(JPu{|V+rUmn?rhIX@+ZbW5>VNjjY(s`4z)OnZk zlwd-Fw5HS6FibO^raUK@jCxF2PFCNN{p&ESvhLM!b2-<}X|X;}(%C#cd%t6eMP!m> zEISPBAV#d3cG&ExX&7b*A?F;3zz3hObS6YWNWyYK)8G8|r>qLDy&-s<3-abbEm5#i(ao`0W6zV};1g34&1Fd1un-d3~Pwvi$5(w9Dg1wDS)%VrhG;&hqha zqRY8jo|!UNEU={V5JfLH(Q>x~er%JnEfyoqI+5Q_CN~|M=V|8R{N_5;Mti*R{L&fDyES9q&JKE)UbiRN9klih;cSEF_llA;CYuin8Q+wx83=n9Mo4oP_ zYTyhBU=E`wCVn?|G0(;nUpl=GQi8!NlnpRjNux6kLwEjfch8=amBbz7GCfqqm}>FxHc^T2u&()&5hsf75`c<%*&NH{*bx;TCQhCzv4gHVg9z$}G?+rm|^O>#|x?~&UBR-h!v zfBH{2+ospsV%~@a#Tgy6x@P&sd~qE@L&_G2yR$P!s1GhcY8mZMspdo6Y}KH)-5vy& zo7<6?$3k^f%#O^{*RA{ZlatH8FNBV>GLqKBZJ#XL>VtON zIgEE@E-^o>?qoc7F$4%Bw4)#THcj4Kxg7?G$Nej0dXG}Vrw2ii)$Qk-S>3H^^QO9Z zomA6u`|_@CXM_?=sN2EeRVO8bO3GLJ-8+hbxGzI4<%-UcF<| zN7hoa-847XaQzj*#5mKb9smdd$at3VQm`}^c`h=HP%iR8jd*x6s9nn{EG7w0XmNz+ zkAawuR#D7Kp09s+-wAGl0}vT9Gsi!~ci)Ff;_bTcw+flv?r9EHKkho&7^?@XtZA9q z?1x&glwxMAj4*urJWo_tz?)XLL$d|9bvk>%vkJ?%{{ej@-}~NwY_|8`huL*lJT$oF zPRS9e$R3OCqx_Tqe)5BDb#z;Q`JnjWb5*Ucgj{b&-EiX^!Q*5eAl&3jYp%RpQ{-bh z4SdMWP>pJ2^Xuf}cb>h_@4QzI@bc;(9NF=#5Ql=!k`26LU~}xxAtaC8mpOTm_nYd= zKX+u=Y|Qp}lF(zLqZV}f(I11C{u1Pm!z<_9e{=8o*SD~H*}BEih4{%m`E~L}ZjXMH zZNFSkKRm@>iDp;Y;qLC7e;HG)Zjy{w^DxE3tL@k4KlaTFz|$;Q{vKIX->pCS)ZG^a zs?hcy-()Wals|}SEMMY4!p;h+(J_rvQniN<#;-}7a`4apaC7lLM>E|1-Gv+!$BA?E z<}>xbyuSM{9^bg&Phj2R**;uH`Mk=?ljWUs-@yLUi2|!nZ~t)i@7n$D`rwa$*S$9D z3wUYCfh6}FF2Tzm+?js)tNKrtKD_wgPZq!as~k#xG-=hY-_c|9Xu*<>pTB(_KknUE zK)*@_K1+8Gt+(4HQyZ`S<9XV~9O+~s)p z9_qTg^?BQOD)=6Hg;;3VvFJ@JJYrSfkoqOTH{c@aT@T{P(Xz@NO={MY~M(@)+%`j~z4&G36E8LyV3dADuUiz_jI%SR8vq6#bE$CPAm@!{~3 zY2IJaZ@zjnwbSD8PyUOe&SqbupZ&1>jv$cQ{)phqPH%N<6gNvbIr$EnRk`yf0c(_<@~X{^*Ff z?KDLvS1*SxU_*TNCLinU-UA@=$^ZE6EH-~;{_%fj@<01oviXaD?_R$Zhre^UuTEA^ z*6ZfYA)Cy`fj>N1{Utsr5hLXM@50a6{==jC=4NYNtqu+!1VBq%xK#-32S#64X~l}k zASysX<;0=-c5ncY%pWglVS={u-su%$y zB1^ck!>-?qM#d0Nrl+cxtNpDq>NkCKK6qnYIw?-(_n*E0$A@p<(1>cQ5ry6M;`!J8 z%~lDLA1vNE$0zyU{15n-069VsJ(=8nugpwn4NDWsFL&piY!9k~8?#bYXCfPPE6w17 zbHg5j-&Atq(c}<-$T08KV7*2VtgTjNEV8LZ^*H1QIoP7L`iyVLvoWyj3+AoB@m)-`DQrB@4OY9`+i}Kj?&q&I*BIva9kPl|%h0Qkl*+g^!XOy*X1E^r zFXOoPnk07JcNhC~=4e$gm=cd#6Z;6t+=FzdCO$9M^X29!iAll&NYk7`40n59?~G|@ z$)vCYh=9QuDT^rS=uA3@I&DJH@!VO3x4iHw0ghNxZicjz*cjEqu}y@BacJA_{BWxO zv-z_b1HRzZlf*vqJ7$s@yXBEPWwC`ZFMN(b9_WgD+;~L2QzU41>71l%SYp{I_`wMSJRaTnP zci!D6bi8=OqQeYFkR^v{=C@CxmjJ}6>=w422Aw&ZTA6!ky|(ppAop~4HPSrbNjr{p zzr(>6lyM>`B38PM54c=LKOhiPG96^ERxd)|V1P$64tUZei*|NIbd!0LB9OC$vHWI} zzj!k}eOv(TVRO;Z9I%Oo2{A71-l9-bgv(|NbB>YIF^QTfFr9AuZkMx?y}PL6_3-Pb z>1J{A)o1g?I~zFXJuX0Ypz_KQTnzqX=a$l2iJG-3%BoEsKnL{2Mf0*woXmJH_25B_ zJonPHIt7lj-B|8=rUMR4;A#n?i)mdMlgp z=}YwMzig1-p#DvnPDeT$col%$d)j;Gt@NfdrVnoPdVRG&pTnEFryxB7gmVl9#=!g7 zuh8Z@9lSa_1qg!yI+|GkY_P^@9~=TGga85Lm<8Y=^okuDz(Ne12tgA7qVc93TZ~ZG zuKNB)vzY{(ieMb=$1gCwYr*^(XP-8zZ z;CYbNS#69D4glaopg7T1OCyc5%8f4Q2!ip(I%BLFAfRCc5buaL(;XD!)h|&l^Oy&ODZzeDzA1p#Ngun>P2o0tibPoXZ!CCK|4-CLc ztqZxXFq(px#Q?Hn+2sSAzWPCTh%vKC?p{eAfzSJzn1o>#vWGnxm&*QqP+5 zUYi{a#RHY!L0LMRC1;?s-nFvUdiU4g6{8Zc22SBiAT{7{yQumA0Oc)ljX1v2~nR$M*DyuRw+zWJa2<%?W>3v6qv zg(lSaUOVNLN7_S*&@`#Cxg1^w(}dvW9-f%yP!ENS+0akru8*!6Hb!6=e4EW}V8Lo* zn*_*1&NE(4vIC9ETNl#V9pP?B)T&sHbq~JzBHe6KQFg}E^h_GsiAp+$V5FENyda{I zI5%vOOb@`V_1)#JI_BjaheO1extdTX(tM^a1%nl-Ya`;i)0QvIjU=aWdEB-9yKnL! zw3&&`RdVOiWm1Ka&SHM7s)gi)-s;6y-Tbl#f%U!rhff!GADXFNtIf^k?O<*!9D3{8 z2muvUKy>96T}%3|$z2OPh7OT1T)paEf5xhlgz{)$IboI)u!w$(vob4vrM5C?Y~C0v`rO*HJs;)H*| z%^N&FLThuNF6RDHfYwASfg-3yS~Y1lrpKHGwRf-`>3&TDWt1{#tfuR4_h0;;fC_llflY%QCaBmv$|781sg(e(fjCKu+l1wh~NSMAay^mV^2$jh+v7J z0)S|}*>-E~WPJJet-O(>Fi->-F`s1BgT?IrvN|gm%iV3r-qozgx@)=iO|M2%Tc>@n zKDdOYl(79+XFNj)0RRAmoH9oEZ;m*OaK_V4_PuJ2H@PTh`4VBOV~lJX*`W`P@CxUT z>AfE}&1PI4tHVbg1{nGPf%m$T-Jp8}5dc70#R&7kDWjBC+8O{5geb;XkNZ|O<9Pe_ zmD#uH7F#)~2S&cTob!SNG#OD|f?7ZrY#@*l)yDOg!~VrK7D<|u%{Th<3*^0ZDq$i( z4E$%Y!2kdt07*naR1r&qM*X#M8X~MCB#0Pqi*$A@d^vQ&(TP(N+rjcKg#34_*I-e6 zpGdyP({Qx0=(3(9%00d%@LrD5vbo)E+^wV-gse=ip$rPwbf3EA{=2Pi1G~KVO zr{95`?>gaj-qG2X&ZX7?S5LR?XV2QWSdQe(yCZD$w0)auHJ#APub%z=wG7qj?ahFJ zM(OIkADJ`8j+qFwXY4_>w&7`|rn3{Kevbn`kL4-Kv5QO^c z=V7~p!H)Yt0=Rn^rcp{5`pGY?wkuA^p61NiRv+eKmNhupg3BsrhOCv(kdwzBm#Pr_*fB0 zMWXUNN%9#W6U^tu(ud%qIN84a;#V0@5Tp`BZ&9U-37!H*znJZUtZm` zOE{U{IVoR%MmArz?7GSN9jv#w9p9VmPU_W|elQv^pqvKYcH7(fqLFBjMAsK&aN3VW zzC;91LEXfBJG&px-W}Uq-@r1h3=`dh5BBZGUe)iS*C!$@sm^eu2&b4Y+`UzBTs`nF zm>3dc%uF#ejhW*yGc&~$Gc(7InVBJGwqs_t$IQ&k%sBSW_rLej(xS5@Kk$a5_~7KAaP%(3PNTz+l`BbhRlK0Os&4FUec9uj$9|7 zhS3fXPi-P|W$8^-L5?KC6ArR;apP#BBXpm>hkpP4$p~|G@%FFHEBQt%tHv8@TTXvDxfeb0;ti_5aSk6$C8&@z=;GnNfwc12!aR$Y&~X((=K5oa8LR zOJ0_~eAaNFwfb^tarxheV4U}5(~|yyvvm-0fa4SdyU{~QXJ6TTG#I`wq94Vr5@(cI z)ML-Zau}q(kfz@_MDL_|^lYs-cbJnx-gmeBNxi2H-bnivWb)%)(H`o^7Dc2 zK5Q>}cc`;MD`)IdU= zgXt4~Pr2w$^Aurbz)y0$Ea}3q#iH-WK}*&GUzN8$e>*j5&OCKeH&|KCf<_m%{;}Mr z%pPT7`&whmUP1XNO_f{MzH=;^?jN+}V#h90s50GK+!_H)0qFO(6^2l@-fmt(+XO4!9GmbNW7+aQj#)9R4hX^SW*oe@}5%{PDaozwOv%D z3jg<`2-hTMuMz^l+CR^%E*~r>>F!cMxz*t_a}DhlRIiURgX^%KEnaS}mfGu@AB@k` z$Sin&Z@mx^;iF84+9#VBY!Ghr&ic51`7Lq+(x%m#E-d_F5sJEK7Zb6)Jh~%Iq)0!? zf||~)EjIM&H)=x95vk*u+7H=WfGF_KAMp(R7QsAE3g{P)A)VMDMp>~!K$h%J3O zl2{_yM5Z>%#QGm{&r&lbXQj$&6TB3`Z12^cV4zFCdrjF&t>Y#Vu z^LQJ59rF|h(pQ8jD%w5UP$J->t+6FWD8yZM`*S(DhH@0*pt$E}OMeZiVFxsN0D1~G zA0vcfInsF!U%}ZX%}J%N8s!{88_-K9a(=`iw!&4ek#rh!08 zjgj{3Fj+)@L$bmHTAS|ygGKMTV(Ko)ad;AQ=yp&X3+j{1VE@5)L<6EgfOxl4DZAU z(Pp?{Lx*gfINW_Q6^RN2c;VpDJpei>S|1BmVYmZpbc_NOoSg{(B$1Q|q{TpQS5W+A zm?|5gfUoI>EvAS;JT{3Ahat*}i6YTLle1+WPi9IXHv`wV;~kkOXF(K4g#|~7)Q1XW zFYR_9JVgI&XYUx0B;hWmnVcFLpYR!rxRGK_IEJ}o0oK@^hB(Gv2`_k9EiI5Lp+E1l zATc%IkEB!_0%3}widH`>XBh(J9Y~fZWy?WVKF(1;bhjC)Zk7XM z%7-fdXQCB}&BT@Zz51{pQ2VczI+UTDai?oyU_ZQKpB_O{n*Is#4#s8yq{JlGTx;XW zT;0toWGmedOyh-OeOZftJJU5&RP<=rMOgWn5af|CQ$Z$KV3023gwOj5Ux??13xNI- zzg=M|7od|SWwpyYO2=NLqUJ|L%cq8m~$uZ4}JRo@R~vPzj3ZF;9*V zOF%4*#-SynLzMK`7$+Uu%_8GI;{p=sua?iO!pK|;Rn|hln05boUL3C4v8i;+Sjlt+ z-586bzqxa-xf*^Q9uT|OfsB-zT{Jmt=J1%~fWMB4w2sCHvk0A)m2m32C^oY(bx3~P zB%_)@Ttgs30ol9acjCUJv}M*m>Xaa71j-8>2ZeyQ07mPP5i__yylg)**jV)C;&Ail zcLM<3U&R`LB6J89?%mbH!^qVPq26Qo)OC)vwZ_sMR0kGac?1UW`34yT zoME|NK*ey@D5?Y{b9Na~74Zu>UKzaj(Uc|BQReALb68Hy7+E@VQ~3+@J~gHw+KHOd7#zMFhJUH!?DJw^*GWR6LMbvQ&9wxG~{esz1_*!!iAcK;A=;s9# zvzq1)*g(;>Uq~#L`LIe9oknTWNrl4`hr5)urVHFMRg_q~R@q~}2gL^i@TI$IRSUa& z`I!a>-_(lW3d0pwLXG)#s0Lkl00BrxeLed!_nt21Sj7{}(~fI572Z*@(H2^i`TOA- zXj-tpyoMBiL>U(Ns@44EvJ4#6l9o|Q?xuS-mz39nXH^(ja&%Njj-W{y)>gBOdF2Xf0WmbiF&x)`pVUz1N*P7sqT_To&AjFAG;aH|V=1l?ebsCFHIh1t!* zRqs19vZKxFw8nEtF(yiaaXBG?6pOluI%FUjd1jJf0a>c0)^s4~M=2%}xhQ4wiN}ZMs5;Z;H`hpc~6LnLx|lLeFL`M#n)((-GhRo3B+w zWqfwSoj^2-+TRajI0`^9OJjA>4<;3kqXTBvZ9qB_vnGx z^KMB_Ap_bz%$L~o#5jkI0u58ml)W0#?CD>jghVj}y)`V+hK>Q~7-2BzPIsK?1p_dL z)OBN=3jG9{*ncslv4m$4(9jC#4x&slOh+U3oYk?h%IgW1v?Y%*i&46*xI!vDhI z4QrZ{lHziR2loFkK_7I0N*VBwFP@15JeyFI?C&Kyl+sDkm;%2T_rNgq-lA2G(|{c0 zs={X&8cqbDnB!M6`IU2BLwZ>SHwzGEd9g6UgQW2Aq9lxU6P1ola7$HbO##+RJdxYD z%vX42NYOY*Fn`E7@=+{lc!Ia3v2x5*aCG9flFBh}0$d7%`PXato6_pj5=T;bb^esz z?F9;AldD&f!v7%~0@OPiDPs*L(Zz#QEi2WsMNsyUhLFExMUKYwlSNWll@cCG$rjwv zLFb#Y!vn-i0wM)R>ZV)DJgwshN8mr*PSxKl48CIgQt2NC%P%wtBht?D~cNlZW z(a|JvAzHm0zy3}0$e6 zwY8#u)rnSwRMhzTHvnl30YV?zxJ8C;JW4pk@BNa!v2PUS9` zZlPK}XUH=U6?uXxMT11MhZr)*)8Vg75TN0iq$QoEVR!(moQM$~Dgil{&k%+YJSH6D z+wv>*u$jQ8Bu|;}WHB1VhGp~{Q&XNoG}1(sR8QQbBqy9=C#~@Lq=b4)NnX5U3@Io4 zChFA`N?CjSX$zTw#(p_xT&YX{YGaz7$~( zoDRF(_|@-)dA_%tNrByb*vnt{+e_cm_{E8;|FN zj*b+++l=Je+8dIOCu$B31=7H5pT=R6+V;ndVR{YEcpQSLv`K--c3E*}mn>#Ry?o`; z$sD2Ay@b=+I;Y8O8zHZUM;{*zY$>9+tgPD!zDwJ+Ckb+LE-qVZ&Cd3cy4x`c5+6;2 z4+)({YmbYTYUyeXn#9fLiH`?&&}6p2$K9+8!)WWev5n2;$K=Pe8$!@dlEydPR_D0G zmVwxY@bJk>oyWv=-`%}o466n21Dvj>#haTMzL)Zjj>^`T2)d*fNR0x2&5KjcYi}Bx z&Ece<`}56!*bZ})`0MJX@6}nIj;EJbj>myaxq9Wr#rx4%+NQdi&$iCy%L2*Ab@1Lm zRFt1#jY$HAm;(U!%kG!$192a^Xbh#add>xjkgjexEGNxQhc{c z&yQ~(F&pO_@f%KjA_@PK-B*FpYj?~( z7NkB?Kw2N`6No?pTdI&WMaAIb#eg>H#P8!#BcNwX=zTZa|MRPY-^=9l^XU7jA9(gB zi~H5qWIT@FrPoKvtZAme>+AM*ZeAYNz(Bd{`3pfy=F0)shj!&cU|&xH^(Haykpa?k z9?grD!RyORp-jSi{BY=>uYOc;z4XFq_e2p~L28@qF-gjNj7I z)1j8uf!ijyOG~S$wsz78*RgHz9lUAa&x-!3`RmPXN*o1VgxI{Li_&yB2qg5e^=Fej zQ1CX5nwqNJu_d$Ucy9m??*&2_m)$qWl$kcCAn*IhX+};lvhNr?4G-)+yk(5{K6b|A zbA7Ac{CMw^q^LC-z^KuTJoG43zK6`Gvho}rw<<5teFm4GS~R zkE%B3vqB%wqjp{3g_C(JM@L7Tu&zXDH5$x0%LdNX83;{wHee!PNyQl(28MnbQp$@= z{xmjLB`!Wb3qBh?U77D~#$z8vT_Fr86voBP|no8Ke&Kaz%rx5BX$ zLi2`h+ocQjJ3PlhQGPSi*SU8_QR3z5R%CIKEVzm^s<^?PUeyd*@l6i9Iqz?W%pWg{ zUD}Y=GlkC)qz0#pezerompA(xCd<>O$hg7a$B*~QYlqFJW{DZls0%NV)X3oAdS2?# z2;lM2K!HNEQoUG}4kIjZ^U{Fdv7r5}uCMP~l(-0L|G~JyfJ0fAU$=GR-W~%U3CZZt zluWf5u6X%8$VjOT3`(LG$j|SCBPAS!EJiO+rm6aXyb`hahWpblRn_{Fn|*zY+W{BjwfmZNW2S)D!Fki>*bs~Ng_8o2(0jQ=HtnCDr@L8$k%@^yi=gqwyDo53 zbzOxJ8yh`&Uw5r<1f>M#faxfL-C8rJ3%J!qp;A}NsAg|(AIsS-$Bp~TVue=uG>dkn zt@{$I;DxRE5<`kgM8APd@)C?cM5=Hz)oD74Db+70Uo2gBRp}nn(rGWRuP3!H{nBam zd5xZ!U@I)#hfJWZy4o@B=nUCpHZxzhT@XhWQ$9qJ3`Tc646zl?15Ow1h9)M|XcE<} zHd4CY=Iyx)W=&Jam&wWduw|IUXbawXT}~+EW*vD7=PflVwTa+hs`NYFAXE6xv+wF( z-?=NJmQg;}fp6nA|*BYHH2Ilch$7P@TLRfvd47@(d@URS%o zuL}#08kKafJ2S))V6s#N{PVYqygh`>M+x2Wiavhs<1QwYRq^ z25k#m`_(n9ho{vf=rF?Nrr90`F@jHjIf!FO11EAW&n~;dcL7;UyNCu)JD0wX84?+^ z%Mr|-aOZ^ohMq(GoO@)`lOOi1OxG)S149Mu2nS6J8gKn{9q6gxM!UEv~Sa1POPARbz9^1VVG5l`0!Poq5 z;FtOK_P39>$;Xb3>&{MkOfdyncV6BVu&1*D_#`sY9b$m}ZIK8SIcRX5 z;nbkRGiuoykC>R>tt!9DR>Z>Md1~s1-$#Qv`^`{YvLu{{2=ui(;ox;+^?dhGaYudmgnCP$U(t4Bw2N}>#>m#1y*mTDFniy@F1_Mb9(xfjft%t%Pc zNT?IdRm>LJY=PTB!+_ZyNs54l4I=TLI&qBB?^vNv8HM)mPGjpLA|aVQ259RjNRVxV zKwY?acttdx$1?#tJ4x)7TOLgGqXmludydavN$P!%W&CutU(#s(}kZ<9lK~Ai6L&*FT9~1_2?tZGEDG;TE!VA?juAg0*hB zVjkexv2l8j3e#dVAlA{1s_6=!{l9Wt)(O}_%(&er$V(%m+7!jhGesy zhoAER5>a|}MPBG}ZSm!`dSLfDFE2^mDto>)vB;u9`!BOft2<8- zgH6in#oHmapxvjXRC?kV&~DA`;biOjMQN?mi}P)v3{DiN!fLVV?$JO8qCWrqTiSO_ zA5Vr%T1EqXeqP@HRK@)K2PJyg6Id{Rx*77AmPS?VHrIG_8}S&Vw^~b+sfYyo!obKLh%L3}c0QgNgxE$9r93qBZ{3%Obk8yBhF^%Ef6pH2m)W6b$1aR; zWaJtqjIX)*PjmC`b7Bk_VN{s3$SseF)BBC~<0*68cnT)HmsC@5#Gt|QGLLp;RrTdf zcQs-pv%opaHMBxwy@9v{wLXNhnBnklUckcm9+pC;UhBZtwQhQ!^iN zj8{kY@bK?GR+~Q=dEI11>vW+YRT)C?1RSPG9gzgOv}a*e$=` z^NpA=DwKcs(UBxK;WwBicm9c6{?Qi*yIOT8AVR3N!k|}nJhKUg5-5I9IaA1$*`{sbtcLV}~(8K!2GrncxpaS))spBLg zBayyBF+}fqx&hhee3~h4jbD&yR^DZ-fgm^6~#$Vy{O7 zf|<(*48A`Npg(=;F8;z@hxMOT{CfbA2K~Rnryp_u^YiKd7|fXe%aHyA{I~e!znb3z zpCbQvlkoq{ZOHY%oBWmhUyuK%hyUMHM-V~DLYS4G*R_9w($-?~^>|o_enbD$dV$tQ z(0k|q!2*P3dGEhuTo>Vb(_~DPd#iDVLPfs6ucKrg^918!6QhPRU>K zs#Gz=p-sn?lQ1o19QXG4hp$>Y9}y?7)-l<_f2tNxN@#TgHIyLP$$rDaBYi0Nxz_Pf zfiW5_MYfwX5^mqmzA2H0A-7OQFwvUOsar<&XYB|NoOv2bv2yRD5DI!fKri^C0-Xm` z@D^Zg9fRJhu9?(D;pjDOn7APG;kQD)``|=lH1Y8_*ud4%B3$_EIMhqWkXEmD3EcUY z*35C(8?UyGSh#=Jp9q>(saJXRVDih#*ln0J=t$V-+qpnq)MP21kj5^3!I{XM=$S2B zf#>hpOOah+J^~DFD?|M5NAO{v2$26xmoI|tvWtWa5@uDaJ~2bx_AL*hxXFv70wqm# zGYr+3kJkU35flq(K!Qm$Qc;($#R?t^PhnX(C+|r>cRalU|3W07WOU#LH~(D~pM%C? zZLXW2shjPPpJBt>JIPS+%0^wz`S4OFZLw(6kF=kE?1*dlI2qSFmA^6REsb=Q^Yw4q z+J}5pY&PpR{f!A1n`}38bFXa6neGn7Vc`nPrq3M`Y2)*XI>$M%*hp%)kI{1P<%O7v z!bcW(*|ZWSfjl}5zgiB4vC=uzSAL9JN4DySYlCk$)PiYgyiZ6-Gr;jfC6_t+1pKF6 zi2lrdwjW$ptIv>#8Pd4+)lbU{aeHHkRg$nG^&MQUc1^AH)Zu-H65k9ky05y|z%kug zUOTz#S*ORyw>eCIdT`;dQc+D5^y88R`9JDfR;?P2XP-0kt(-rOr8H{OP=VBwK~_0w zb@{OdZEtN>l*U;6hlKF=dd50lR3U|xnl$cLDe}8q46wI}xLCL6%Ztcx*>OLt+Fld0 zWK6f@;h`_E6sk9~@7kni|B*S~d3&GCUJbwx@SMZp#o{p9>iN)*X%M;U@5k5 z3hyEalkOkEIXT9cp&nPNkWLGe>X1`*C&n7bZ4nAJBmz!WsBJ=o-a z@D-B}H*Q*)5lMP9RiX)NTk}T9X}qdF&8ecWtv+5$Oy(oJAD_Oz zX&^xw*h`K<&Fd|YucRQkXWHEJmqy>lT9a)RlHnw2uN~l9%QwXZ@-QYNC6AQ)>9WBM&vnmUwzJD?4Y@>i za#T^jo~++vR2C^(Y!8GdFUV>y0d4f;{|SbICNzwK z*%}0B0gb`AX3z{KQXt;Q&`{Ku7BPtp1sO^DMLR9&&W(?Ba-Ji)Z*I+P3o9d+byKnX zR&&AnU0ly!lW)H#7D?viCfIHAlM+b_!;!M0@0__V*qSN0n5TEv7FdE_IPgfve{v6z zi{7~*y%#XX*QUl^wThU@hfL^?xo@{*#;;D+Z1olU9&?|>(y;iuMSaoB?|Hb0JlA2M zp_>1L>(Pdmx2&)wZdn}x+D6<@-f6vV+D^9zy%JDRxU;sbB_sYhNi@u1SaO3n~vRTAzBC6Vx z&h3?VzBl@e79NR|6_3S`*ul7}qw8)dp(XU1>Fa9|`IK5(Q=7V2Uf}Lp_r`4L)q(Nb zzxBs-pIWoN860($x;f&Fus&*CvzbG057KRykoJqIkVDWSe}{=zOMJUZui}*7RSLNo zm8jHmF--L^*)>E_iq^1)7; za5Qb9FeX+noCBC#$YYd?LDI2Dysb^5t@^36meQ_|P$`A#Ew>b!7*SH1iYO~I66WPF zU6?Xat>NH)A8#L)?i4&6r=!?F0Jtp-qQSQIi*KZp8y$9{4MW#h(!SlXH5rENKJE}vbElaN$x;3m-!YLV&%+hio`JFc3i z)gbc_(BJ)4=Z`W^JmgF9y$O`%hJLPhx%SlK#kM~rYbP(;`QZ7{<75m)KcF-MBG@J!nB3zw+pPkfRC`I5|7S|@Ol)Xs4kbr=d`|Ua-m%@4@ ze*}Tkxd6ZCuWYPqs73I`mcoQs^D_oE0+Cb#xkXIpUG{ENoL5ZXE7Lt9kllIjAGlkV z2QYi4-*zbR!RfpTBY zm0W^rEuXS{JH~ywvro|PSNqHh4+KLz-ZH86{<6g196tTr({n4UCBGXx(Dn)Yd>AnO z@KE!Z;7H2u(QH#|l24U%Uhjz!B=SyP{0A)%4omMR*^qgK9eZVG#L}Qb^iiP-?lyhC z#LVI*?cGV)ymmQ4O}_Q%K)ZvaQU*)JG$Sa|Km_$NR(sOcz`)GCMG2Twe}oaLIh z3HctJXTTa5Xwzr8A%HY@zKy~4K^dhdlsrs`ZHh5ZnGt_)8pD(@1!uvlJD<3J&|iCRu4 zhg_~Eju6z*qheFlI9>WmUDX(!SA_jb~tmFTeu6 zF1@pjau(q7G5>OJ1$yih{FPIDD6r!pvV$Y$`*2ZhvX=f_G`tc;huC_ZpIus}(!O$W zp_8T4{uc|y?9nYq*+qL8;l7e(XgF4Ojlz@ZOK%V!3g>RDy{ng3!99Z*7ISxv>h*(D zf-;V|#-4YRu-j-br_BJuy~4-&FE{fG0vPJ(dT|Oz7z4E?RY2HZgAdiGy;EqrXn8K7 znkfRC_weB3U^L2?m9-S38JIm^p|sx+O6eh)P5KYhJDXpjV~qC&Dlj`SW-4W`FHR-n z?A|X@qOhSz`(3jBb!A$*Debi$`UE1;JGyZlMXF4{j=pP97)|i8AB&cmB%B?)HWp)2 zB!Aq^*j0DT(*P`eW}WJ9uDTiJzKbgOZdHTB<#{A}1<%Anr>$3O3ydtGf?^uFIIhB- zOP3Y@#w==}!l5}@`4f^xpNra{2N^e5%FG-HUR2NRf(R?oNQ+maD3T2w=O`ap8du1WFE@&I{x+~ZX>1uw__;9}WszxC?PCdGIM7&o8 z{jiuliRJdHJ~w5*IC>-$T=R2QT_GYZUno;~2^*AINb7vLyX)eWsVt}tHkS*#GiEqQ z};WuEAv>JPV|3EFk7Et-fvJ)7>z3pOb2we*kOxyf;$_yoJ85e;jzBhHr}5G{$k4lv(8nTxL6< z%1$i6@%2TlCy5LL7U$tN@qBig2^$jBi3AIUNCY*nhgn*&AJZwt`*6U}zh#_h>Pi;7 zFyIX@d^?>XWxwiq<;9lYCX2YqAF3CTF2n^rPvi9^7}c(Ix8tpV)^mgGWO6vttg6yz zsdD}fw85-+SYwLrUFb?1OKoj?&%3B>^PdlfaJEZ5$b3|rb|T>nF6S!A!E-xruf8R+ zVTvaX-bV5@7)E~ehfJqPW9?LKPSFKIKWIi4C+Z@;s+3VgoW*ueXxPQ8ojSAelBplR z!2-P#&Ct@zW_jFbD$}Q|%4OLu&1t9io7Owsw$!Z_*vM@1OQB|Mz=T;dBi~;cSFgYi zG<2HH198(o$_vVi$IS}(>MP3|4sJJe0;J%Y3?ekv2QuS2?q3Z2VvBrQO1ZGctohWC zIaa=sB=3rgSXhb5uB^uF8mj{aibYa>84O@vwooC%wVNQb$tcYItsc1{lq*vpo!OOC z#oB=*wKb_#;QjJzCb8V^6oo&l$x4py3Gcu#$x*Dc6ZqoX(Apo!B!!ugtC++2!+Z*x zEqmK!7z2irw_{<6BD;XrE!qebgi@*YRBMQtUonSgCm6Nnk2gw%2TdOlHKZBD4Q4gtB zL%CMR4mZ#80{4cH`^3$GYtjH*v5&eKZGADqVm=($zu7A&jXz(f)OCt#27xC+>(WYW<9%DUbUKw1ezTPoREkPieSzezfkNJ<8%1owE6Hu3H<~95GLlp{XUSxz$YU*Sqm2r=zTK% z*mMQ%kpXw?AoCSUnI0|2G!-917Jw00pFM<(6!FGxmL0~_bSi{4A!7T8h_lF_>@%^D zWJ8%zR+-WIo&T@=-1j*1kE6&SNaT@eFP}SV#{Nex%gZ*I2{8-{K17;j@Z*K)6M9Bt zBcOZ)a>D#=N1!r4WD>Rm#T}s}JhM=M{1-!H3VTRT{UNPdzqJK91D!=7GwD!W@7RI& zyBwo8OEO`u$Il%zFAC~GHvHkl@ydseGR1e>!#is*jn_g(%eKFUFRJJGLDcf86SlZ| z&3hVC>U|ohrD!70A?EH&El;(Hss+ffm~qf^_Fx+K0Id-bH$S`OCAFY1Jn&~K9up|Z zsUQUremiaC>6;Dz={{=wWcI)1F`zO^^NpduG`K_EiXlZkeSVM7SU*rHN>TbNqBRZc zpb<)o7)QkPkQWjqM8ogO-uz$-Lw!Af%Qrj92X7zq- zIdMRnyfl7~&hfN00)6>)qr{1b+dAu7vKr52X6@AOUQJ2If2eO8naJq=3#V#svz#o= z5DzLxM0YZj(t-Ea1&A4Le61QL9Og*`SgN8nUsks8^hnb3Y*>bfdR{xog?j->(iDm3 zX)=-i3QntQ_Yriw!FtUY2D{`A_Z5Q&kXu(TyY=w|7%Ed~T8lvSor)H!3d0eilVP2H zCqCTFL$QW+4a!Z!(L4%%Ju`Gf(zlg3)|5G_9XrjqxSfMJ&7RaRR!#^99aJC3y{qzZ zeyI!rua63ckZf#(sm7&!KOXbPp`_EiygOB~J1E zh2DFRIEn~!*6^8_-o1wn`{Z#_N7|*;Y<4li(qg$PW-ueuNc4s1_wpX#dBt&O(}PDL)Q?lT6XB>Y zKhpQOcGPIq!W=1F$kFAY^O)MX1lbpMx!4S2Il&ZSnI?raMiv%vDE=?@BD-Jtpylnx zhN;{GL*LFw6tYCwH9YdM^Oml*F&N+L*S$T9I1e{2WDio)7hrtnvaF4sP;|u0D`aLS z9b$^?@C&3X*uL|XnHHs^P#EXh&fraBs$BfpDN^Jn9{F)AyN1%XQ+~IEbFlx9w-DbvE-tt%(tpUcc+S=(PYbZSA<;HlVeDD#v}9o(#>wj{VfU?2gpq9SLh}{ z%*PuXbMkpu%CD*LZ#HNy7xx+jc2 zfPVEmi6!LRKizKFk0`n!uS-;CPpkdMx3n+DrY^|S>f_Y*Ltj~vgeaenu zi*uT3-5gjtQmpYgVL2UY+2rKMUd@jbAvUmFc-XtGn~uWF=u^V;YPJ1?wg4D87@{&2|VOOVQS%iBK5P@ zmh$BKEDL63)ld%;PyFDM_^OvtLlaTS$?^TYOt5npp$#k8Uf@d|XZP-JsV+gC74W{0 z_v-r}Y=y}&tdjt<{znk=*Kfr{LvksHo8a`{qDy=pjmA%3yZ({56p&P8+xb4c%TE+F zOfFazP{Vq7WlFrvx=PT@j(Ajz5~r|JWP=$K{PDt&7}whW$m``P%sN#v?#8nfp>pl; zy4!XR#|3aiI{zA374QU9GH2Uo69}wvy54n6L)-EA-mPpAL#*U$ulWqPsc?u;Hrthh$(SIT6&g!E{#`_H7!-)TL z7TZF>*03fqK6fB5)K#VnN-aEPjz7F}a>6ACf}DJNwv*r_1j{XJcF9 zmsXik1CWg6scN>Wvp&f1`e8QTt}&{^)p2j%02zGsuiWbMP}-$axU5n%vsn zOIL$TnuQt3O+zvISw=T~`NLer)X&$*qaGGc{g&xUYYvtOC68QIxsETr`pRmjYI%ex zcuv##ZnNu7s^ddSXDPMZbxy!UJM**9Lf0pw$U|)S++LJBSg3Jg1Rc}Ii_sLG6CSTf z+WAOk&M3b2okn2o`O-&atFL6yHJ4yA)dq3BAEbvzd&~5s)xXgIaXIB^TzFi}W(M+A z(-?VnoxXiFAWcKcB&bclaj#lx4oYDYGg~Iz-*SlCR`1!Vs>~naO=>&b95q{!f~Eyt6YnH(wZv==XkSx(Ufw^y9{o>8N;(#CE->7-bs_Uo zjWUy&XLY;BD+QBax`aqXLs%=e3OCEuB-kReTWS;v`a;dU@xDbbbv7(@?cv*RUsru8 z$$VI&Tw5S1@|OD^aQ?GOf3v4`t#@Av#61950#VV?^<5`ELyI+D2R);Zqg}KMDaP5j zZ;vK_C8$c&m^W?s=uN`MH3NTDFgt5;kx#EM(F=ZV<(dz|YL+=1WGcjGn6E=VxC(pc zTBn`9$~TD2Wq^2=`>Z{Ft^)QC#syrDNgw$MtgR3m3s<~E)aGvAkJiAIQ9K+WL^i`I z=L0(=uF<|r9;-ogF0(~rLEK@1rzh}IbJY5^+H76z-&h^DN&%?YmRnIfjI zT3z60sfPBvI%fRX{ak73LX2d#p3|A- z)Tip5UUk{7U01LgyVZhX+au__zb|a4%W}5W&3Xh2v0;`Tyf;MZswJd&!v4JJ;T%PD zp*ycoH)CII+`}xlFV)|BQgE)uNHgA8H}O>#nru|_mXexqYckQsX$`5;O=}QD%-}Ob zQ9p5c3Fbg4`fEO4C-=MxS;zF)WPWlNZaHi?Q#pZk|G9H3#HEq&CIwS!mW7t*362~^ zv-Br0Fl~vR82d2UdrI~oPTkDvN*dMmO#}{LFdLHTuu?J(U_WRC_6%Ed;a7DR;JX&% zXvar%-3|c{EDR8?sYmCdY?9Pbl7T zG6e-U@FM!7#?YI>!ApseIX?QjEkzYbW{`weq16~akT=$7;p9Yv{um< z#B%x6d$)r=VP+P*@GnF;J7a3zTe9be1I6#r|y6N z98KCPzLqhS`kaf89?gRnEa?dRpk+yorl$F9SL}gcbcBj8kap0g5a2~<4!98f8_e|0 zCvn)SQ+Mj_Du(iZLo*;zS8M8uLr~l#$ej#;EGNOpJOtJhn*W<1eo^j^y=B`EEs6eP zh+~brK}2~J;$RdTmiAH01;TSe{onYG9HXr<_5T4c{2zqJ{}Utf{{kWT|7JAh@Wgg8U} zaL(A)R{UyES$sUw;S(`UZ8Mp{dfV6-)G$UA`p~hW`K!oc#ajY}Tx0Yi-H1r;)KVW` zmz}e%eCzEG#mcj?NKIPN@y>ddTl-V*g;As}KN=Xi3hjTo8^BnGI4XrQ%Pj5WmRqYk z=I|J8mpUE`Qywm=CU2YGj>?lvqXvJ`#(Hq9x>}b{>da})s0nu2=osUHo96?*S~};H zgtv8wZMMCp_;^f`a64F=EfdYKxt|TB_!3llTrbshARj;gL<+pz`PBF>NzC^GUuyP+ zYTO=It9G)gv$?OluSpd5U75dGt|n`=-zMwMVsTloK6s~Hs2IaTY4dJ5bZ^#HUh{Q{ zxxDf!oXK)RunS#Fjdh*xIZXlvD$9+G9RZV8OJLDeuNV0 z<)CPA2&>`zqTTAaDwC8$rxN<<-xJ=FfgI_tS<=IE0~eC@x=Z&a1x&FT;}B$4o#==> zn(#_K>re~JP8TJUt*0tEEY9FO8WoH6Xp!}}(yqsg(!V9GBAq%Z&*@FrO0ol0(L)Rt z_Kg=-WlR88OflBRa5=u{2dZkzjb6nq+>K@1_=o-tfx_93N20QP zNG}~kuNk(KbtSfg*Z#-O|NR#(&@tO`yrkBnD5jI)1Hqy5KG;<dwRx%7RC0QlU4e|~Nw>5x(F})ny>RPf?T1Fnth^p#(y&W&dV3T6$NyN(z zDD7CP&A4=LW`_24+&EAG3$qRqD)3!S3`woNoP6C?f{S&izCkVhY3Q1f%EZz#x`1p< zR4!-`LZp1yIL&JG!GeGbXK>jPm%Wbt1J$1dsnIg-e_iS6FF|wfW61{vQ4iLo|M4vS zdDOX%DoU~bbg4>NRP9oRZLYi15+Uj07_03uk<4;*`XZmNECLrnDY7!p!Y|~q5n!L8 zf=%%)>$RM+s^R=7wGwFqVX8E-;erl0m%JOtmojw>gPoSD^PVQ}d8&>9p!8wQ|0kCe zk>4@sHispbG$QAv?Gxv}%U6WZTIxL8*;Y5Xrq+-2)kUGWq3SN#Smv}*UPH4}#HG|q zPDe-VFt@cH4$pj}jxy~xJN23f(LK#)2rA_ZH_E(HsFa$36H5x21M#{EP*!ZVGk<)1 z@R3W6Wyo1(O;V>v^0L!1n9h<{P)3A7oYyr;c}TtK)Z9rzLgMzlNcl?|CsBusg;N52 znlyb`#y1zFC+~@id^s`6yzdvlIMHa^yx7Y6Jlnx(+v3T?v!$-#z|>Y9bOyONB*Jx* zeA$d|$ZI3`e`M#|zHu})Hgb}zG?a$sNk2MIuIyZv|3dx00-p|G@l&z&P;*B|NAsZ_ z)h5JL@9RyqNkygAb-S8-oj%_{>z--`nYJAF-DU@8@A0igh^pSx+TAl3siL}K z+PqsU7)jm-4%L|uQ_-~lcxy*T^P#O3QnSr%nMmu7Qi`(G?K;}p(b0Nz&!!@ZDxDIF zk%8Whebp2y+IqaZw|8J*W_r>RJ22&nx_uqpy#v0;os3XHQ5Ds76;1n&9&hfRaWmi1 zO$@S0B}t^YXh|BJ1AGImyDBJDx}&+f zx7Qiz-BUrO$L7>i_VnIr9BsCWx;;~2(7dafL8dLY{56kL7A6jT1KrKLDk!RAS8I1~ zuQM`Bzl1?d#g0R_o?lT~WJ)TM`AcY|W7kyL4;^nlerQ*nEt&Lr70JN9Y6{s(>$dD~ z9dP;vj&CickgdA9`qt;>9i`N?h?w6#^HmWu9CRz6rs^Gsnp<0&kMG-3N+rvpZ=ic$ z9ffR7N2jh$QzX9TCL2W+ZQ66RwWFi;_`WSg6lH5__T6h81&Ra!?kF|VYvy}VB;C7; z5LK5NUm4g}LEWkYQ>~z{xv7XU)$O0X*Wf%<#~{;m;hye2q0(EGQEQV4A={2rYt_4_ z6iNE~cd1CGyYaXBZnmTdOI4DQj$IYfQJmH$Q+jW*_xQej$7jhzq`AqIKFZ`gRF`aG z2liD{h^pJ~lyv`@*}JLIR44N;6)zn-lID)IZndQg8|R@-OsWo_%QDXW)#(jdq+?eR zVm2L}x>4llCJH6Lo{^rJLuk87Q{CvhF~Xdcv~PwFF9NcYiw`)3=OxYca+rj3Xsjq6N3q<2pR zl|Izev8xDC)%%@ya>@e5;Z%;R_d92IWl>r!5NX|NL(|Tkzl8dZZemc;j^x;9q`ApP z*|s)E?naR$<8CU}q;<)_zEpDO588RFh;#?}ns?PzZ`t3QZn9*5?D!TcWooG+8Q51r zNi!)%I(8H#-FXjn)T0DewgQjC>~b`UkWP1hD**=E+6Jkz`kLg#bl%R+sK0HeWZ|6u z!b=T}$LMXxj%_QZXJ0ze$ucz!HK`S3#Wgi~sIRlrHT?&svZgZmTb>L9 zWz=9>+u*#5hT2*>V|g_-#mS$cviceu+u73jU~zP3BcQyR`Z-oq>S`M6nW2_rx6%-8 zJJvF0Yk0nfMhF#eJJ! z(0O_0*o9a3zS`-U@;zPFxbrU7%jPvUR#G3odhFH%TqlloqRQtRlD{jiuT0&etg?)W z^>tpjlT#Kj4(Gyk;ld!wt4t-1uB@+Q?yiJ(wsf*gWqoo58eRYDV07@nU9Bymifbx$ zsd9}_aZRN;`B{f<6_dLN+PkbGboF~%+Kz22PC2^ROhxZl82xQ67h<;h#?&L|%7z+K zOtS220qUrS4JZ=eF@UCZ2uUmWd%-p6iXpRk?&avN6FVDr{}$Ek++CxaYk8zAip*xS zv;s5dnW>n6&?RZZZ>!(#aRI1~o~MS4`P+6kYE2VUAjw{5Rosxz0> zH10gm(l@)6T&_Vxo6XWz(QMWszGY&mfOA%voxCtAi* z^?!7*W$*L#WyN{ssh#PKzl_cJvDrLlaO(1;-_d5X4!K;eI|fhXi^ulVM2~%KokeFZ ztABp?t4TR<2jScBd<}E;*sGlgA>?W~(TQp#f`roP%xRw0A-~@ry@OL0G7jg73M$Q1 zlKaeGLj5k6f9ALMQv0O+j*@()5kj%M4%)k}BK)1N?rf->4ko(nJ0HjHCb(UQGN*+S z(&@}7>TA>DCueKd)%)5(B zd?J+*RkyG0#mX5~y!p;MJBzj+fAx9u4B=D!pxYep!U%IaTUXOqPoHQxc42Q>ap$oX zzpbIMhDHb>|A}oie{zi8^x})J))bp{G(v4V%eMF3bqw97BXpI!TL!oFwY9XIIB}xo zz}F7Hw0G-?wgY$Erebb*zGmlFPrTZ;x3ak9SR1OD_b1W2a>^r$!{|Mf(CEXD_XP>s zzPeE!wSmgi@wM9pR$ z>U6oHlEUp8bj7H=;!kdqV$s$UZ3l{58Y}?2i^q2Uzjn?SzD+HPrE0kF)Tk;`<*5*rC-RSEP15dP*U!#ZCZA7oIOkkA=W@?awRY8eL^IU- z^Du}rIf$_8SCv|(8(8xX$~-R+;dy+gp#0Q4B!tY2n=jBZTkN%MU!mmdZ_6St(EJuq zuDbY}HsXF*(k6aziCV7%Z=q*UprYcgZOJ`Os{Wa}7K{8Nk}H9?Ho#+m*QvROE~##X zEBpO^T3MBA|3xfX z-m8)@%(%)M<)6RTKm;BmUh}+ta!Cy*DG@>$qlQ%xh{KXB5mXjmcY~6uG{$jIabFg) zWV~{8NcWF4q>7TNSOH<#n+$i6Eb5~TEy9$G7`5#5(m^4Vs@3ZH-oq$zc#c3D@ zEX)N}m*>rQi4a-th?j})MdLd)<+J1r)5?{ycwCni2_b|DLDe^dWgNy|DxNfGxXef* zsUk+jrnfyS#$eS@>v`x>Kf>bzrV0?uR4wo_hb=C9Y4B&zEj#LsNQR$@oQ zMRa0nc}5MH$7PW+$G>W5UtJqQSfR8XytFhE*@lRVB(Uj6(&=0G?&t_l&^YnkfGDD@ zO53AG?Kb<{()5y=5?|)AXWJn*b!W%J$FNOLJ<(O{P))!^8aVdbOzKm-+a7+qJ>TgB ztm-P!T_?*0-H;d00!8AEnC+;xN{S?Kyj<4zE`yk{L{OydMUv%ogXgwxVB3)RG7TL1 zu6+91*2wgSW$rz5eZ$oRlrfvexG}H32+qP+hFVZdlXzU_iSMvdur%?u-32_3nCFX% zC~2~Q5W}DT-7-VJ-~>Q$$(lIU%pLxsxPhk)p{W&+|OWL(gNmD6X8s7IfQiRnO3Q$5MEd zMK1O8rWxpuN^A}$OM%D3%|-ukCJBY6;!3XvpGQ0ONMY$@N*cfw_r6ZJ)rn`_Izp?lnv911{Z^yKA+EmHJ~$nYCc`)F1~)b7Q*RG(ovN75 zM)|&4quD;}SENuz!_$MRTG^+ml-+3cQqW8x?RbN4M;FKK28X!Xbl`P4r9GcsUJb#g z^IoSpl@fcpzgR2VgYskYaO?E%WHR}D-s?2$9JlbsQ;u8C=5AS`E9zxy3U)Vx<4&{A zafquo+r9JQ*?w(4#k-yo>w;vnOdRg^&-P`q8lda*Ub|VJ{zmtBa6P#>KWx@GZYHlX z?05ER2yu<>>Girn*MsA3>sE=jd)%WQlD{UC2{hmU004g$2^Hl)2~$s5H1Vk;OE&TH z&>-L+;30r>Bme*ae;RpU`}!t z000j_7FgO*LRxyCc^bT&0e&yw90>pb0000F51bZ!k;Q@Lbv93@00000NkvXXu0mjf)-GVs literal 0 HcmV?d00001 diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index b4e926d0b..3af73f874 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -22,10 +22,12 @@ webapi/doc/swagger/include/PlutoSdr.yaml webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger/include/SDRDaemonSource.yaml + webapi/doc/swagger/include/SDRDaemonSink.yaml webapi/doc/swagger/include/SDRPlay.yaml webapi/doc/swagger/include/SSBDemod.yaml webapi/doc/swagger/include/SSBMod.yaml webapi/doc/swagger/include/Structs.yaml + webapi/doc/swagger/include/TestSource.yaml webapi/doc/swagger/include/UDPSink.yaml webapi/doc/swagger/include/UDPSrc.yaml webapi/doc/swagger/include/WFMDemod.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 5116073ab..b4a625f5d 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1424,7 +1424,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/WFMModReport" } }, - "description" : "Base channel report. The specific channel report present depends on channelType or paremt context." + "description" : "Base channel report. Only the channel report corresponding to the channel specified in the channelType field is or should be present." }; defs.ChannelSettings = { "required" : [ "channelType", "tx" ], @@ -1478,7 +1478,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/WFMModSettings" } }, - "description" : "Base channel settings. The specific channel settings present depends on channelType." + "description" : "Base channel settings. Only the channel settings corresponding to the channel specified in the channelType field is or should be present." }; defs.ChannelsDetail = { "required" : [ "channelcount" ], @@ -1749,7 +1749,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/SDRPlayReport" } }, - "description" : "Base device report. The specific device report present depends on deviceHwType" + "description" : "Base device report. Only the device report corresponding to the device specified in the deviceHwType is or should be present." }; defs.DeviceSet = { "required" : [ "channelcount", "samplingDevice" ], @@ -1861,7 +1861,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/TestSourceSettings" } }, - "description" : "Base device settings. The specific device settings present depends on deviceHwType." + "description" : "Base device settings. Only the device settings corresponding to the device specified in the deviceHwType field is or should be present." }; defs.DeviceState = { "required" : [ "state" ], @@ -4083,15 +4083,11 @@ margin-bottom: 20px;

    Limitations and specifcities:

    • In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method.
    • -
    • Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely.
    • Preset import and export from/to file is a server only feature.
    • Device set focus is a GUI only feature.
    • -
    • The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source
    • -
    • The content type returned is always application/json except in the following cases: -
        -
      • An incorrect URL was specified: this document is returned as text/html with a status 400
      • -
      -
    • +
    • The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator
    • +
    • The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time
    • +
    • The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time

    @@ -22613,7 +22609,7 @@ except ApiException as e:
    - Generated 2018-05-28T00:22:45.302+02:00 + Generated 2018-05-30T00:50:44.029+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 2c658d14a..6191298bd 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -7,12 +7,11 @@ info: Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. - * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. - * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source - * The content type returned is always application/json except in the following cases: - * An incorrect URL was specified: this document is returned as text/html with a status 400 + * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator + * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time + * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- @@ -1733,7 +1732,7 @@ definitions: $ref: "#/definitions/PresetIdentifier" DeviceSettings: - description: Base device settings. The specific device settings present depends on deviceHwType. + description: Base device settings. Only the device settings corresponding to the device specified in the deviceHwType field is or should be present. discriminator: deviceHwType required: - deviceHwType @@ -1786,7 +1785,7 @@ definitions: DeviceReport: - description: Base device report. The specific device report present depends on deviceHwType + description: Base device report. Only the device report corresponding to the device specified in the deviceHwType is or should be present. discriminator: deviceHwType required: - deviceHwType @@ -1824,7 +1823,7 @@ definitions: $ref: "/doc/swagger/include/SDRPlay.yaml#/SDRPlayReport" ChannelSettings: - description: Base channel settings. The specific channel settings present depends on channelType. + description: Base channel settings. Only the channel settings corresponding to the channel specified in the channelType field is or should be present. discriminator: channelType required: - channelType @@ -1864,7 +1863,7 @@ definitions: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModSettings" ChannelReport: - description: Base channel report. The specific channel report present depends on channelType or paremt context. + description: Base channel report. Only the channel report corresponding to the channel specified in the channelType field is or should be present. discriminator: channelType properties: channelType: diff --git a/sdrsrv/readme.md b/sdrsrv/readme.md index 1b74bbf0c..f93b6fb92 100644 --- a/sdrsrv/readme.md +++ b/sdrsrv/readme.md @@ -1,4 +1,83 @@

    SDRangel Server

    -This folder holds the objects specific to the server (headless) version of SDRangel. The `MainCore` class is the headless equivalent of the `MainWindow` class in the GUI version and plays the same central role. This document focuses on the functional description of SDRangel server. More details on the technical architecture can be found in the developer's documentation. +This folder holds the objects specific to the server (headless) version of SDRangel. The `MainCore` class is the headless equivalent of the `MainWindow` class in the GUI version and plays the same central role. Plugins are built specifically without GUI and are located in the `pluginssrv` folder. +This document focuses on the functional description of SDRangel server. More details on the technical architecture can be found in the developer's documentation. + +The main motivations are: + - be able to run SDRangel on hardware with less CPU/GPU requirements in particular without OpenGL suport. + - be used in sophisticated remote transponders or repeaters in a headless server configuration. + - possibility to use Docker technology to host SDRangel server instances in a distributed environment. + - using [SDRdaemon](https://github.com/f4exb/sdrdaemon) for the RF device interface even more distributed architectures can be supported to share workload in a cluster. + - using the Web REST API interface developers with web technology skills can implement their own GUI. + +

    Plugins supported

    + + - Rx channels: + - AM demodulator + - BFM (Broadcast FM) demodulator + - DSD (Digital Vouice) demodulator + - NFM (Narrowband FM) demodulator + - SSB demodulator + - WFM (Wideband FM) demodulator + - UDP source + + - Tx channels: + - AM modulator + - ATV modulator + - NFM (Narrowband FM) modulator + - SSB modulator + - WFM (Wideband FM) modulator + - UDP sink + + - Sample sources: + - Airspy + - Airspy HF + - BladeRF input + - FCD (Funcube Dongle) Pro + - FCD Pro Plus + - File source + - HackRF input + - LimeSDR input + - Perseus (24 bit build only) + - PlutoSDR input + - RTL-SDR + - SDRdaemon source + - SDRplay RSP1 + - Test source + + - Sample sinks: + - BladeRF output + - File sink + - HackRF output + - LimeSDR output + - PlutoSDR output + - SDRdaemon sink + +

    Command line options

    + + - **-h**: help + - **-v**: displays version information + - **-a**: Web REST API server interface IP address + - **-p**: Web REST API server port + +☞ the GUI version supports the exact same options. + +

    Interface

    + +You can control the SDRangel application (server or GUI) by the means of the REST API. For SDRangel server the REST API is the only interface as there is no GUI. The network interface on which the REST API server listens can be controlled with the `-a` option and its port with the `-p` option. By default the server listens on the loopback address `127.0.0.1` and port `8091` + +

    Documentation

    + +The API documentation is accessible online when the SDRangel application (GUI or server) is running at the address and port specified in the program options. The default is [http://127.0.0.1:8091](http://127.0.0.1:8091). + +The documentation home page dispays two links on the left: + +![API documentation home](../doc/img/APIdocHome.png) + + - **Static HTML2 documentation**: classical HTML based documentation + - **Interactive SwaggerUI documentation**: dynamic interactive documentation using the [SwaggerUI](https://swagger.io/tools/swagger-ui/) interface. It offers a way to visualize and interact with the running SDRangel application API’s resources. + +

    Python examples

    + +In the `swagger/sdrangel/examples/` directory you can check various examples of Python scripts interacting with an instance of SDRangel using the REST API. diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 0c999255b..c3e180246 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -7,12 +7,11 @@ info: Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. - * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. - * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source - * The content type returned is always application/json except in the following cases: - * An incorrect URL was specified: this document is returned as text/html with a status 400 + * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator + * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time + * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- @@ -1733,7 +1732,7 @@ definitions: $ref: "#/definitions/PresetIdentifier" DeviceSettings: - description: Base device settings. The specific device settings present depends on deviceHwType. + description: Base device settings. Only the device settings corresponding to the device specified in the deviceHwType field is or should be present. discriminator: deviceHwType required: - deviceHwType @@ -1786,7 +1785,7 @@ definitions: DeviceReport: - description: Base device report. The specific device report present depends on deviceHwType + description: Base device report. Only the device report corresponding to the device specified in the deviceHwType is or should be present. discriminator: deviceHwType required: - deviceHwType @@ -1824,7 +1823,7 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SDRPlay.yaml#/SDRPlayReport" ChannelSettings: - description: Base channel settings. The specific channel settings present depends on channelType. + description: Base channel settings. Only the channel settings corresponding to the channel specified in the channelType field is or should be present. discriminator: channelType required: - channelType @@ -1864,7 +1863,7 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModSettings" ChannelReport: - description: Base channel report. The specific channel report present depends on channelType or paremt context. + description: Base channel report. Only the channel report corresponding to the channel specified in the channelType field is or should be present. discriminator: channelType properties: channelType: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 5116073ab..b4a625f5d 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1424,7 +1424,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/WFMModReport" } }, - "description" : "Base channel report. The specific channel report present depends on channelType or paremt context." + "description" : "Base channel report. Only the channel report corresponding to the channel specified in the channelType field is or should be present." }; defs.ChannelSettings = { "required" : [ "channelType", "tx" ], @@ -1478,7 +1478,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/WFMModSettings" } }, - "description" : "Base channel settings. The specific channel settings present depends on channelType." + "description" : "Base channel settings. Only the channel settings corresponding to the channel specified in the channelType field is or should be present." }; defs.ChannelsDetail = { "required" : [ "channelcount" ], @@ -1749,7 +1749,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/SDRPlayReport" } }, - "description" : "Base device report. The specific device report present depends on deviceHwType" + "description" : "Base device report. Only the device report corresponding to the device specified in the deviceHwType is or should be present." }; defs.DeviceSet = { "required" : [ "channelcount", "samplingDevice" ], @@ -1861,7 +1861,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/TestSourceSettings" } }, - "description" : "Base device settings. The specific device settings present depends on deviceHwType." + "description" : "Base device settings. Only the device settings corresponding to the device specified in the deviceHwType field is or should be present." }; defs.DeviceState = { "required" : [ "state" ], @@ -4083,15 +4083,11 @@ margin-bottom: 20px;

    Limitations and specifcities:

    • In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method.
    • -
    • Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely.
    • Preset import and export from/to file is a server only feature.
    • Device set focus is a GUI only feature.
    • -
    • The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source
    • -
    • The content type returned is always application/json except in the following cases: -
        -
      • An incorrect URL was specified: this document is returned as text/html with a status 400
      • -
      -
    • +
    • The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator
    • +
    • The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time
    • +
    • The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time

    @@ -22613,7 +22609,7 @@ except ApiException as e:
    - Generated 2018-05-28T00:22:45.302+02:00 + Generated 2018-05-30T00:50:44.029+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp index 6ac567b1d..a69aed087 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h index b0000c43a..65d75b119 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp index a5b0c7b8e..26ff4c9c1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h index 3fbd078a8..b88c8f03b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp index 17e18907e..078aebc28 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModReport.h b/swagger/sdrangel/code/qt5/client/SWGAMModReport.h index 06d03ae2e..cc7e1a83a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp index 50ca86273..8658b6720 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h index a424b341e..4764591ee 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp index 303fac6a6..05624b2a4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModReport.h b/swagger/sdrangel/code/qt5/client/SWGATVModReport.h index df8661cdc..2be34e0d5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGATVModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp index 9272797a1..a409b771b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h index 9f6208b2a..fc1bdc2ea 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp index 497e318e2..743ef51a1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h index e4e707e31..da800b54c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp index 4210412b2..fc8db34ef 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h index 9f124e3d9..087af831a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp index defe0192e..e4c1d4dd5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h index 3bb0f3ee4..e02998076 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp index 2d7ed1d52..c465b504d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h index f9d30f809..96035ed9d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp index c6cb84a07..14cf04320 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h index b94eb307a..53ac50ef6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp index 38526f6eb..f25048d89 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h index 93210cb22..1357fad35 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp index 49c62d4ff..5fa6a7811 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h index a5e7d5841..f71da2cc5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp index 15fe1ac61..7024c9360 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h index 19c4d999e..2948e6b96 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp index 594455f05..fde086db5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h index eeeaa3076..a9927ece1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp b/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp index 95ba9cf27..4098fb722 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBandwidth.h b/swagger/sdrangel/code/qt5/client/SWGBandwidth.h index 98f885f07..e6cf0cb09 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBandwidth.h +++ b/swagger/sdrangel/code/qt5/client/SWGBandwidth.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp index 10b2a4756..ae7b72101 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h index 6e9c66d66..69f1b8272 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp index f60d18998..3cf2a60cf 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h index c24a434df..27d1be9c4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.cpp index f9351b8af..50e1185d1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h index 461f57a1a..c6eb95103 100644 --- a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannel.cpp b/swagger/sdrangel/code/qt5/client/SWGChannel.cpp index f8b666dcc..79dd4cd73 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannel.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannel.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannel.h b/swagger/sdrangel/code/qt5/client/SWGChannel.h index f5b38abd2..7bf3e73c6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannel.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannel.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.cpp index fa597b807..51d241594 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h index cf8c6b537..fd362d740 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 32de362f7..2980621a5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index b865f5c63..952ff8928 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com @@ -13,7 +13,7 @@ /* * SWGChannelReport.h * - * Base channel report. The specific channel report present depends on channelType or paremt context. + * Base channel report. Only the channel report corresponding to the channel specified in the channelType field is or should be present. */ #ifndef SWGChannelReport_H_ diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index ec8c90ea3..185dea1e6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index a23ece475..7d3bb1b68 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com @@ -13,7 +13,7 @@ /* * SWGChannelSettings.h * - * Base channel settings. The specific channel settings present depends on channelType. + * Base channel settings. Only the channel settings corresponding to the channel specified in the channelType field is or should be present. */ #ifndef SWGChannelSettings_H_ diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp index efdf56a16..0329fcff0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h index 44aa30aad..dbbd649fc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp index 944192981..ab1dbd901 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h index 6bb87411a..9f9a46df6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp index 41e90a276..449a1ef13 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h index 30f5003a8..f4de7c04d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.cpp b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.cpp index 1575da17b..cfa9de09c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h index 5a103198b..312864f20 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.cpp index 58b776264..fd12e7e63 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h index 67629119c..5853b28c2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.cpp index ae1fd9060..9fe85014e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h index 250712d62..e0b1fc887 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index 2bb78a2cb..b7ab3784f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index 3e12b5057..9241b996b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com @@ -13,7 +13,7 @@ /* * SWGDeviceReport.h * - * Base device report. The specific device report present depends on deviceHwType + * Base device report. Only the device report corresponding to the device specified in the deviceHwType is or should be present. */ #ifndef SWGDeviceReport_H_ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.cpp index 4a39496ee..733481135 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h index ec9f90e44..f4081d01b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp index 44a4418b8..9f5cd3925 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h index d42e88e5c..da38a5fa3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.cpp index 48168d10f..52e6faaa8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h index 55710bfe6..8fe98731c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 3b8e208da..e7e692d9f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index d11fb177f..f6ea3786c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com @@ -13,7 +13,7 @@ /* * SWGDeviceSettings.h * - * Base device settings. The specific device settings present depends on deviceHwType. + * Base device settings. Only the device settings corresponding to the device specified in the deviceHwType field is or should be present. */ #ifndef SWGDeviceSettings_H_ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceState.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceState.cpp index aaa70b057..04312bea6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceState.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceState.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceState.h b/swagger/sdrangel/code/qt5/client/SWGDeviceState.h index 738c97a34..75b52338f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceState.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceState.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.cpp index 34b4b80e7..b6945c951 100644 --- a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h index a2c8d6ac9..24a36f2c3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp index 5d495a06d..9e0e600f9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h index 7b31f6632..7bd35dc83 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp index b69368ab3..2d39e165b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h index e0e6508c7..601bde04f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp index d14ea84ac..b9a445699 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h index 32aaaec4b..dcdc95c1b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp index a7c5bef39..f9eb434b9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h index ef10f881d..c20733a12 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp b/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp index 41a8e1117..9341b6d1d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequency.h b/swagger/sdrangel/code/qt5/client/SWGFrequency.h index 08718a9ac..556ec46bb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFrequency.h +++ b/swagger/sdrangel/code/qt5/client/SWGFrequency.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp index 3c516f363..8e91e695d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h index 39e194249..e2f5b9136 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h +++ b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGGain.cpp b/swagger/sdrangel/code/qt5/client/SWGGain.cpp index 2baf4f7ea..23d81acc9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGain.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGGain.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGGain.h b/swagger/sdrangel/code/qt5/client/SWGGain.h index 5be40e4bb..aa8da53f8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGain.h +++ b/swagger/sdrangel/code/qt5/client/SWGGain.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp index d7bb4abf6..ee1d157a5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h index 793df1a81..027a3124f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.cpp index 58ac27071..ce3e14e7b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h index ef752ccc7..5e504bc3b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp b/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp index e6f00e591..453978650 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHelpers.h b/swagger/sdrangel/code/qt5/client/SWGHelpers.h index 9d4310900..3d752c3fd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHelpers.h +++ b/swagger/sdrangel/code/qt5/client/SWGHelpers.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHttpRequest.cpp b/swagger/sdrangel/code/qt5/client/SWGHttpRequest.cpp index 41605a340..0400fb983 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHttpRequest.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHttpRequest.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHttpRequest.h b/swagger/sdrangel/code/qt5/client/SWGHttpRequest.h index 02aadd5a1..3a60a1b96 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHttpRequest.h +++ b/swagger/sdrangel/code/qt5/client/SWGHttpRequest.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp index cd301ae6d..030e0cf19 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h index 3554b2d4a..6e56dcd6c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.cpp index 796639609..6fd80c1ee 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h index b75c7b030..7b4465e91 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.cpp index b5fed9af6..b0de0fb20 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h index d796a4749..c96758c20 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.cpp index 2f4a5f4f1..17e57b5e0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h index 002ec00e5..f594fa56f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp index 580daef2d..f2d9e30e0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h index 3003f831d..adffd02d8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp index b9d28084a..e9b5c90d2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h index d301b13ed..a8eb4b0a8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp index b0d24d6da..3bd2afb3a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h index 2042cf490..1c5f859c2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp index 39738800d..6f0758f34 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h index 9679abaa9..8a5d3fa05 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.cpp b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.cpp index 74ff6f86d..040bfd5b9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h index 25f0d90d1..c10b05cab 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h +++ b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.cpp b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.cpp index 26b3b8cfa..f21d1f629 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h index 04dd54c73..7f8ae5827 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h +++ b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 2172187d7..f05eeef20 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp index bc5ea8c51..87f3a6005 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h index cd227852e..1fca9aa8b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp index 6a7ced10d..1ea283792 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h index d96cdd488..b03500d89 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp index 41b1b423a..bbe051b74 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h index 711b636c1..592497c5d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp index da9cbac1e..4853cac06 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h index b31390067..f76a10f5f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGObject.h b/swagger/sdrangel/code/qt5/client/SWGObject.h index 7c999ecc4..77521c571 100644 --- a/swagger/sdrangel/code/qt5/client/SWGObject.h +++ b/swagger/sdrangel/code/qt5/client/SWGObject.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp index 8254287b6..751b7e5a4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h index 5d118602b..5c82d3033 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp index 6d5eb31f3..9df00abb5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h index a2c9c5146..022b259ab 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp index f9a037f97..b1c8aad06 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h index 69bbbecc5..b6d7e1870 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp index 1dbc566c0..9a96e9883 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h index 696d9c630..8b9ef6141 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp index eedeb7944..660dc2b5f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h index 8d9d11dd3..38f007b4f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp index b00053e64..c15044ba5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h index 5f3dfc841..2471a494f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetExport.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetExport.cpp index 773556634..2a970e547 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetExport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetExport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetExport.h b/swagger/sdrangel/code/qt5/client/SWGPresetExport.h index 468446f8f..faffe8d2f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetExport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetExport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.cpp index de7dc5dbd..2acd5fe06 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h index f3041a665..918435454 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.cpp index cdaba1342..a2ebcd1ab 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h index 29d507501..ec9d76e4b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetImport.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetImport.cpp index 31f10e2f1..71f9d7452 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetImport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetImport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetImport.h b/swagger/sdrangel/code/qt5/client/SWGPresetImport.h index d86d13376..3c7808713 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetImport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetImport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetItem.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetItem.cpp index 4d09f7d3e..5d5c697a4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetItem.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetItem.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetItem.h b/swagger/sdrangel/code/qt5/client/SWGPresetItem.h index 073e8586a..a954dbf93 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetItem.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.cpp index 489fe41f4..5737c500e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h index c100478dc..3f2459318 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresets.cpp b/swagger/sdrangel/code/qt5/client/SWGPresets.cpp index 39d41e0a3..399d10af6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresets.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresets.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresets.h b/swagger/sdrangel/code/qt5/client/SWGPresets.h index 5debf2e4b..bde2f85a6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresets.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresets.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp index a1eadab3d..65e8daae5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport.h b/swagger/sdrangel/code/qt5/client/SWGRDSReport.h index 4bba8c371..f1414d7c9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRDSReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp index 3c2fd069c..c25019cf0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h index c83910aa3..cd1f55a49 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp index 61095fbbb..1b64fbe15 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h index 6d2cb4958..60dbeb6bd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp index 566c41a6f..eb517aeb0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h index eb3969410..ac7610e98 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp index e7a429872..e228698d4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h index 4a9590c46..197ba528e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp index f5417988c..4700d9b3a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h index b17561716..7016eb81e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp index 981c18149..55d367b51 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h index 33a3327e7..0340f94ec 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp index d4a326f68..01728c76d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h index 2beffa783..8f7d44322 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp index 252069449..f19ae4ce9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h index e191cbdfe..7364b13bb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp index 239d58117..38d56d547 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h index b6965db11..ea64fb68e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp index 619710937..a8d0b398b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h index de0f1a02b..bef3187de 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp index 3f06d1e98..a5897f58c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h index 84bc8500c..01d8364c1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp index fbff1740b..ea8d8793b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h index 0a2faf32b..e5c67d2fe 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp index 6af279670..3eaffb9c1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h index 31c185fc8..e80750d36 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp b/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp index e3316dd27..e84d8e94f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSampleRate.h b/swagger/sdrangel/code/qt5/client/SWGSampleRate.h index 11bd10e8a..87e8b24fe 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSampleRate.h +++ b/swagger/sdrangel/code/qt5/client/SWGSampleRate.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.cpp index 0948a85fc..8917e17d8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h index d32f97d21..ddb6aa0cc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.cpp index 95be2a51a..33cff00b5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h index 2b655872d..7607734b7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp index cde7b0bf4..79fa53223 100644 --- a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h index 50029d997..9f99d6243 100644 --- a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp index 572ad9793..cb4a27bf2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h index 21f7a184b..ed1a052c0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp index 312cbf7b1..5e05bee21 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h index f8486e24b..66b131737 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp index ff110a308..971a4485f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h index 4abd1b719..7e2b12804 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp index 22406dd4b..bf4f15368 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h index 645f558cd..875a67df1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp index a7ae40494..6d0688dd3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h index cba57e09e..7cc956550 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp index 8e7999fff..4b1fadb5b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h index 91432b19a..a6c398883 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp index 2b7a7d63a..37133c5f9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h index 185320ee5..f28c13687 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp index 9969a7907..8f5b52b85 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h index 162c02983..6f6780efd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.0.0 * Contact: f4exb06@gmail.com From 766e6aac1ce24beadd6793f45709e2ef96ecca33 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 30 May 2018 11:49:54 +0200 Subject: [PATCH 506/956] Renamed Channel Analyzer NG to Channel Analyzer externally (no classes renaming) --- .../channelrx/chanalyzerng/chanalyzerng.cpp | 4 +-- .../chanalyzerng/chanalyzernggui.cpp | 2 +- .../channelrx/chanalyzerng/chanalyzernggui.ui | 2 +- .../chanalyzerng/chanalyzerngplugin.cpp | 4 +-- .../chanalyzerng/chanalyzerngsettings.cpp | 2 +- sdrgui/device/deviceuiset.cpp | 22 ++++++++++---- sdrgui/device/deviceuiset.h | 2 ++ sdrsrv/device/deviceset.cpp | 30 +++++++++++++------ sdrsrv/device/deviceset.h | 2 ++ 9 files changed, 49 insertions(+), 21 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 958c59b63..66dcda2e9 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -30,8 +30,8 @@ MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzerOld, Mess MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgReportChannelSampleRateChanged, Message) -const QString ChannelAnalyzerNG::m_channelIdURI = "sdrangel.channel.chanalyzerng"; -const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzerNG"; +const QString ChannelAnalyzerNG::m_channelIdURI = "sdrangel.channel.chanalyzer"; +const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzer"; ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp index 50661d3da..56f1d7f95 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp @@ -417,7 +417,7 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de m_channelMarker.setBandwidth(m_rate); m_channelMarker.setSidebands(ChannelMarker::usb); m_channelMarker.setCenterFrequency(0); - m_channelMarker.setTitle("Channel Analyzer NG"); + m_channelMarker.setTitle("Channel Analyzer"); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only setTitleColor(m_channelMarker.getColor()); diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui index f027cbb56..73bb3bdbb 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui @@ -23,7 +23,7 @@
    - Channel Analyzer NG + Channel Analyzer diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp index b2450da84..8f3446754 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp @@ -22,8 +22,8 @@ #include "chanalyzerng.h" const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = { - QString("Channel Analyzer NG"), - QString("3.14.7"), + QString("Channel Analyzer"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp index aea2c0328..f8173243a 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp @@ -45,7 +45,7 @@ void ChannelAnalyzerNGSettings::resetToDefaults() m_pllPskOrder = 1; m_inputType = InputSignal; m_rgbColor = QColor(128, 128, 128).rgb(); - m_title = "Channel Analyzer NG"; + m_title = "Channel Analyzer"; } QByteArray ChannelAnalyzerNGSettings::serialize() const diff --git a/sdrgui/device/deviceuiset.cpp b/sdrgui/device/deviceuiset.cpp index 7999b2486..8ab6b5d48 100644 --- a/sdrgui/device/deviceuiset.cpp +++ b/sdrgui/device/deviceuiset.cpp @@ -191,9 +191,12 @@ void DeviceUISet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginA for(int i = 0; i < channelRegistrations->count(); i++) { - if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) + //if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) + if (compareRxChannelURIs((*channelRegistrations)[i].m_channelIdURI, channelConfig.m_channelIdURI)) { - qDebug("DeviceUISet::loadRxChannelSettings: creating new channel [%s]", qPrintable(channelConfig.m_channelIdURI)); + qDebug("DeviceUISet::loadRxChannelSettings: creating new channel [%s] from config [%s]", + qPrintable((*channelRegistrations)[i].m_channelIdURI), + qPrintable(channelConfig.m_channelIdURI)); BasebandSampleSink *rxChannel = (*channelRegistrations)[i].m_plugin->createRxChannelBS(m_deviceSourceAPI); PluginInstanceGUI *rxChannelGUI = @@ -270,9 +273,11 @@ void DeviceUISet::loadTxChannelSettings(const Preset *preset, PluginAPI *pluginA for(int i = 0; i < channelRegistrations->count(); i++) { - if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) + if ((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) { - qDebug("DeviceUISet::loadTxChannelSettings: creating new channel [%s]", qPrintable(channelConfig.m_channelIdURI)); + qDebug("DeviceUISet::loadTxChannelSettings: creating new channel [%s] from config [%s]", + qPrintable((*channelRegistrations)[i].m_channelIdURI), + qPrintable(channelConfig.m_channelIdURI)); BasebandSampleSource *txChannel = (*channelRegistrations)[i].m_plugin->createTxChannelBS(m_deviceSinkAPI); PluginInstanceGUI *txChannelGUI = @@ -347,4 +352,11 @@ bool DeviceUISet::ChannelInstanceRegistration::operator<(const ChannelInstanceRe } } - +bool DeviceUISet::compareRxChannelURIs(const QString& registerdChannelURI, const QString& xChannelURI) +{ + if ((xChannelURI == "sdrangel.channel.chanalyzerng") || (xChannelURI == "sdrangel.channel.chanalyzer")) { // renamed ChanalyzerNG to Chanalyzer in 4.0.0 + return registerdChannelURI == "sdrangel.channel.chanalyzer"; + } else { + return registerdChannelURI == xChannelURI; + } +} diff --git a/sdrgui/device/deviceuiset.h b/sdrgui/device/deviceuiset.h index 2b5b38030..0bb8d32dc 100644 --- a/sdrgui/device/deviceuiset.h +++ b/sdrgui/device/deviceuiset.h @@ -97,6 +97,8 @@ private: void renameRxChannelInstances(); void renameTxChannelInstances(); + /** Use this function to support possible older identifiers in presets */ + bool compareRxChannelURIs(const QString& registerdChannelURI, const QString& xChannelURI); }; diff --git a/sdrsrv/device/deviceset.cpp b/sdrsrv/device/deviceset.cpp index 469628f47..07585b64f 100644 --- a/sdrsrv/device/deviceset.cpp +++ b/sdrsrv/device/deviceset.cpp @@ -152,18 +152,19 @@ void DeviceSet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginAPI qDebug("DeviceSet::loadChannelSettings: %d channel(s) in preset", preset->getChannelCount()); - for(int i = 0; i < preset->getChannelCount(); i++) + for (int i = 0; i < preset->getChannelCount(); i++) { const Preset::ChannelConfig& channelConfig = preset->getChannelConfig(i); ChannelInstanceRegistration reg; // if we have one instance available already, use it - for(int i = 0; i < openChannels.count(); i++) + for (int i = 0; i < openChannels.count(); i++) { qDebug("DeviceSet::loadChannelSettings: channels compare [%s] vs [%s]", qPrintable(openChannels[i].m_channelName), qPrintable(channelConfig.m_channelIdURI)); - if(openChannels[i].m_channelName == channelConfig.m_channelIdURI) + //if(openChannels[i].m_channelName == channelConfig.m_channelIdURI) + if (compareRxChannelURIs(openChannels[i].m_channelName, channelConfig.m_channelIdURI)) { qDebug("DeviceSet::loadChannelSettings: channel [%s] found", qPrintable(openChannels[i].m_channelName)); reg = openChannels.takeAt(i); @@ -174,13 +175,16 @@ void DeviceSet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginAPI // if we haven't one already, create one - if(reg.m_channelSinkAPI == 0) + if (reg.m_channelSinkAPI == 0) { - for(int i = 0; i < channelRegistrations->count(); i++) + for (int i = 0; i < channelRegistrations->count(); i++) { - if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) + //if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) + if (compareRxChannelURIs((*channelRegistrations)[i].m_channelIdURI, channelConfig.m_channelIdURI)) { - qDebug("DeviceSet::loadChannelSettings: creating new channel [%s]", qPrintable(channelConfig.m_channelIdURI)); + qDebug("DeviceSet::loadChannelSettings: creating new channel [%s] from config [%s]", + qPrintable((*channelRegistrations)[i].m_channelIdURI), + qPrintable(channelConfig.m_channelIdURI)); ChannelSinkAPI *rxChannel = (*channelRegistrations)[i].m_plugin->createRxChannelCS(m_deviceSourceAPI); reg = ChannelInstanceRegistration(channelConfig.m_channelIdURI, rxChannel); m_rxChannelInstanceRegistrations.append(reg); @@ -189,7 +193,7 @@ void DeviceSet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginAPI } } - if(reg.m_channelSinkAPI != 0) + if (reg.m_channelSinkAPI != 0) { qDebug("DeviceSet::loadChannelSettings: deserializing channel [%s]", qPrintable(channelConfig.m_channelIdURI)); reg.m_channelSinkAPI->deserialize(channelConfig.m_config); @@ -197,7 +201,7 @@ void DeviceSet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginAPI } // everything, that is still "available" is not needed anymore - for(int i = 0; i < openChannels.count(); i++) + for (int i = 0; i < openChannels.count(); i++) { qDebug("DeviceSet::loadChannelSettings: destroying spare channel [%s]", qPrintable(openChannels[i].m_channelName)); openChannels[i].m_channelSinkAPI->destroy(); @@ -368,3 +372,11 @@ bool DeviceSet::ChannelInstanceRegistration::operator<(const ChannelInstanceRegi } } +bool DeviceSet::compareRxChannelURIs(const QString& registerdChannelURI, const QString& xChannelURI) +{ + if ((xChannelURI == "sdrangel.channel.chanalyzerng") || (xChannelURI == "sdrangel.channel.chanalyzer")) { // renamed ChanalyzerNG to Chanalyzer in 4.0.0 + return registerdChannelURI == "sdrangel.channel.chanalyzer"; + } else { + return registerdChannelURI == xChannelURI; + } +} diff --git a/sdrsrv/device/deviceset.h b/sdrsrv/device/deviceset.h index dd072c0f2..e76ea7f90 100644 --- a/sdrsrv/device/deviceset.h +++ b/sdrsrv/device/deviceset.h @@ -92,6 +92,8 @@ private: void renameRxChannelInstances(); void renameTxChannelInstances(); + /** Use this function to support possible older identifiers in presets */ + bool compareRxChannelURIs(const QString& registerdChannelURI, const QString& xChannelURI); }; #endif /* SDRSRV_DEVICE_DEVICESET_H_ */ From f8c7763fc71adf143c8b782fdd44ede836d3a333 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 30 May 2018 15:37:56 +0200 Subject: [PATCH 507/956] Renamed Channel Analyzer NG to Channel Analyzer (1) --- plugins/channelrx/chanalyzerng/CMakeLists.txt | 16 +-- .../{chanalyzerng.cpp => chanalyzer.cpp} | 37 +++--- .../{chanalyzerng.h => chanalyzer.h} | 26 ++--- ...{chanalyzernggui.cpp => chanalyzergui.cpp} | 107 +++++++++--------- .../{chanalyzernggui.h => chanalyzergui.h} | 16 +-- ...lyzerngplugin.cpp => chanalyzerplugin.cpp} | 28 ++--- ...hanalyzerngplugin.h => chanalyzerplugin.h} | 4 +- ...rngsettings.cpp => chanalyzersettings.cpp} | 11 +- ...lyzerngsettings.h => chanalyzersettings.h} | 10 +- 9 files changed, 127 insertions(+), 128 deletions(-) rename plugins/channelrx/chanalyzerng/{chanalyzerng.cpp => chanalyzer.cpp} (90%) rename plugins/channelrx/chanalyzerng/{chanalyzerng.h => chanalyzer.h} (92%) rename plugins/channelrx/chanalyzerng/{chanalyzernggui.cpp => chanalyzergui.cpp} (79%) rename plugins/channelrx/chanalyzerng/{chanalyzernggui.h => chanalyzergui.h} (88%) rename plugins/channelrx/chanalyzerng/{chanalyzerngplugin.cpp => chanalyzerplugin.cpp} (63%) rename plugins/channelrx/chanalyzerng/{chanalyzerngplugin.h => chanalyzerplugin.h} (94%) rename plugins/channelrx/chanalyzerng/{chanalyzerngsettings.cpp => chanalyzersettings.cpp} (93%) rename plugins/channelrx/chanalyzerng/{chanalyzerngsettings.h => chanalyzersettings.h} (89%) diff --git a/plugins/channelrx/chanalyzerng/CMakeLists.txt b/plugins/channelrx/chanalyzerng/CMakeLists.txt index e3d9f3c75..80aab5475 100644 --- a/plugins/channelrx/chanalyzerng/CMakeLists.txt +++ b/plugins/channelrx/chanalyzerng/CMakeLists.txt @@ -1,17 +1,17 @@ project(chanalyzerng) set(chanalyzerng_SOURCES - chanalyzerng.cpp - chanalyzernggui.cpp - chanalyzerngplugin.cpp - chanalyzerngsettings.cpp + chanalyzer.cpp + chanalyzergui.cpp + chanalyzerplugin.cpp + chanalyzersettings.cpp ) set(chanalyzerng_HEADERS - chanalyzerng.h - chanalyzernggui.h - chanalyzerngplugin.h - chanalyzerngsettings.h + chanalyzer.h + chanalyzergui.h + chanalyzerplugin.h + chanalyzersettings.h ) set(chanalyzerng_FORMS diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzer.cpp similarity index 90% rename from plugins/channelrx/chanalyzerng/chanalyzerng.cpp rename to plugins/channelrx/chanalyzerng/chanalyzer.cpp index 66dcda2e9..26c8f1698 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzer.cpp @@ -14,8 +14,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "chanalyzerng.h" - #include #include #include @@ -24,16 +22,17 @@ #include "audio/audiooutput.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" +#include "chanalyzer.h" -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzerOld, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgReportChannelSampleRateChanged, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzer, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzerOld, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgReportChannelSampleRateChanged, Message) -const QString ChannelAnalyzerNG::m_channelIdURI = "sdrangel.channel.chanalyzer"; -const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzer"; +const QString ChannelAnalyzer::m_channelIdURI = "sdrangel.channel.chanalyzer"; +const QString ChannelAnalyzer::m_channelId = "ChannelAnalyzer"; -ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : +ChannelAnalyzer::ChannelAnalyzer(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_sampleSink(0), @@ -65,7 +64,7 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : m_deviceAPI->addChannelAPI(this); } -ChannelAnalyzerNG::~ChannelAnalyzerNG() +ChannelAnalyzer::~ChannelAnalyzer() { m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); @@ -76,7 +75,7 @@ ChannelAnalyzerNG::~ChannelAnalyzerNG() delete RRCFilter; } -void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, +void ChannelAnalyzer::configure(MessageQueue* messageQueue, int channelSampleRate, Real Bandwidth, Real LowCutoff, @@ -90,7 +89,7 @@ void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, messageQueue->push(cmd); } -void ChannelAnalyzerNG::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) +void ChannelAnalyzer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) { fftfilt::cmplx *sideband = 0; Complex ci; @@ -126,7 +125,7 @@ void ChannelAnalyzerNG::feed(const SampleVector::const_iterator& begin, const Sa m_settingsMutex.unlock(); } -void ChannelAnalyzerNG::processOneSample(Complex& c, fftfilt::cmplx *sideband) +void ChannelAnalyzer::processOneSample(Complex& c, fftfilt::cmplx *sideband) { int n_out; int decim = 1<create_rrc_filter(bandwidth / sampleRate, m_settings.m_rrcRolloff / 100.0); } -void ChannelAnalyzerNG::applySettings(const ChannelAnalyzerNGSettings& settings, bool force) +void ChannelAnalyzer::applySettings(const ChannelAnalyzerSettings& settings, bool force) { qDebug() << "ChannelAnalyzerNG::applySettings:" << " m_downSample: " << settings.m_downSample diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzer.h similarity index 92% rename from plugins/channelrx/chanalyzerng/chanalyzerng.h rename to plugins/channelrx/chanalyzerng/chanalyzer.h index 631965dbd..fb7db116f 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ b/plugins/channelrx/chanalyzerng/chanalyzer.h @@ -31,7 +31,7 @@ #include "audio/audiofifo.h" #include "util/message.h" -#include "chanalyzerngsettings.h" +#include "chanalyzersettings.h" #define ssbFftLen 1024 @@ -39,25 +39,25 @@ class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; -class ChannelAnalyzerNG : public BasebandSampleSink, public ChannelSinkAPI { +class ChannelAnalyzer : public BasebandSampleSink, public ChannelSinkAPI { public: class MsgConfigureChannelAnalyzer : public Message { MESSAGE_CLASS_DECLARATION public: - const ChannelAnalyzerNGSettings& getSettings() const { return m_settings; } + const ChannelAnalyzerSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureChannelAnalyzer* create(const ChannelAnalyzerNGSettings& settings, bool force) + static MsgConfigureChannelAnalyzer* create(const ChannelAnalyzerSettings& settings, bool force) { return new MsgConfigureChannelAnalyzer(settings, force); } private: - ChannelAnalyzerNGSettings m_settings; + ChannelAnalyzerSettings m_settings; bool m_force; - MsgConfigureChannelAnalyzer(const ChannelAnalyzerNGSettings& settings, bool force) : + MsgConfigureChannelAnalyzer(const ChannelAnalyzerSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -169,8 +169,8 @@ public: { } }; - ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI); - virtual ~ChannelAnalyzerNG(); + ChannelAnalyzer(DeviceSourceAPI *deviceAPI); + virtual ~ChannelAnalyzer(); virtual void destroy() { delete this; } void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } @@ -212,7 +212,7 @@ private: DeviceSourceAPI *m_deviceAPI; ThreadedBasebandSampleSink* m_threadedChannelizer; DownChannelizer* m_channelizer; - ChannelAnalyzerNGSettings m_settings; + ChannelAnalyzerSettings m_settings; int m_inputSampleRate; int m_inputFrequencyOffset; @@ -240,7 +240,7 @@ private: // void apply(bool force = false); void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const ChannelAnalyzerNGSettings& settings, bool force = false); + void applySettings(const ChannelAnalyzerSettings& settings, bool force = false); void setFilters(int sampleRate, float bandwidth, float lowCutoff); void processOneSample(Complex& c, fftfilt::cmplx *sideband); @@ -248,7 +248,7 @@ private: { switch (m_settings.m_inputType) { - case ChannelAnalyzerNGSettings::InputPLL: + case ChannelAnalyzerSettings::InputPLL: { if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB m_sampleBuffer.push_back(Sample(pll.imag()*SDR_RX_SCALEF, pll.real()*SDR_RX_SCALEF)); @@ -257,7 +257,7 @@ private: } } break; - case ChannelAnalyzerNGSettings::InputAutoCorr: + case ChannelAnalyzerSettings::InputAutoCorr: { std::complex a = m_corr->run(s/(SDR_RX_SCALEF/768.0f), 0); @@ -268,7 +268,7 @@ private: } } break; - case ChannelAnalyzerNGSettings::InputSignal: + case ChannelAnalyzerSettings::InputSignal: default: { if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzergui.cpp similarity index 79% rename from plugins/channelrx/chanalyzerng/chanalyzernggui.cpp rename to plugins/channelrx/chanalyzerng/chanalyzergui.cpp index 56f1d7f95..860839bd9 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzergui.cpp @@ -14,8 +14,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "chanalyzernggui.h" - #include #include "device/deviceuiset.h" #include @@ -36,47 +34,48 @@ #include "dsp/dspengine.h" #include "mainwindow.h" -#include "chanalyzerng.h" +#include "chanalyzer.h" +#include "chanalyzergui.h" -ChannelAnalyzerNGGUI* ChannelAnalyzerNGGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +ChannelAnalyzerGUI* ChannelAnalyzerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { - ChannelAnalyzerNGGUI* gui = new ChannelAnalyzerNGGUI(pluginAPI, deviceUISet, rxChannel); + ChannelAnalyzerGUI* gui = new ChannelAnalyzerGUI(pluginAPI, deviceUISet, rxChannel); return gui; } -void ChannelAnalyzerNGGUI::destroy() +void ChannelAnalyzerGUI::destroy() { delete this; } -void ChannelAnalyzerNGGUI::setName(const QString& name) +void ChannelAnalyzerGUI::setName(const QString& name) { setObjectName(name); } -QString ChannelAnalyzerNGGUI::getName() const +QString ChannelAnalyzerGUI::getName() const { return objectName(); } -qint64 ChannelAnalyzerNGGUI::getCenterFrequency() const +qint64 ChannelAnalyzerGUI::getCenterFrequency() const { return m_channelMarker.getCenterFrequency(); } -void ChannelAnalyzerNGGUI::setCenterFrequency(qint64 centerFrequency) +void ChannelAnalyzerGUI::setCenterFrequency(qint64 centerFrequency) { m_channelMarker.setCenterFrequency(centerFrequency); m_settings.m_frequency = m_channelMarker.getCenterFrequency(); applySettings(); } -void ChannelAnalyzerNGGUI::resetToDefaults() +void ChannelAnalyzerGUI::resetToDefaults() { m_settings.resetToDefaults(); } -void ChannelAnalyzerNGGUI::displaySettings() +void ChannelAnalyzerGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_frequency); @@ -127,7 +126,7 @@ void ChannelAnalyzerNGGUI::displaySettings() blockApplySettings(false); } -void ChannelAnalyzerNGGUI::displayPLLSettings() +void ChannelAnalyzerGUI::displayPLLSettings() { if (m_settings.m_fll) { @@ -143,7 +142,7 @@ void ChannelAnalyzerNGGUI::displayPLLSettings() ui->pll->setChecked(m_settings.m_pll); } -void ChannelAnalyzerNGGUI::setSpectrumDisplay() +void ChannelAnalyzerGUI::setSpectrumDisplay() { qDebug("ChannelAnalyzerNGGUI::setSpectrumDisplay: m_rate: %d", m_rate); if (m_settings.m_ssb) @@ -162,12 +161,12 @@ void ChannelAnalyzerNGGUI::setSpectrumDisplay() } } -QByteArray ChannelAnalyzerNGGUI::serialize() const +QByteArray ChannelAnalyzerGUI::serialize() const { return m_settings.serialize(); } -bool ChannelAnalyzerNGGUI::deserialize(const QByteArray& data) +bool ChannelAnalyzerGUI::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { @@ -184,9 +183,9 @@ bool ChannelAnalyzerNGGUI::deserialize(const QByteArray& data) } } -bool ChannelAnalyzerNGGUI::handleMessage(const Message& message) +bool ChannelAnalyzerGUI::handleMessage(const Message& message) { - if (ChannelAnalyzerNG::MsgReportChannelSampleRateChanged::match(message)) + if (ChannelAnalyzer::MsgReportChannelSampleRateChanged::match(message)) { qDebug() << "ChannelAnalyzerNGGUI::handleMessage: MsgReportChannelSampleRateChanged"; ui->channelSampleRate->setValueRange(7, 2000U, m_channelAnalyzer->getInputSampleRate()); @@ -199,7 +198,7 @@ bool ChannelAnalyzerNGGUI::handleMessage(const Message& message) return false; } -void ChannelAnalyzerNGGUI::handleInputMessages() +void ChannelAnalyzerGUI::handleInputMessages() { Message* message; @@ -214,18 +213,18 @@ void ChannelAnalyzerNGGUI::handleInputMessages() } } -void ChannelAnalyzerNGGUI::channelMarkerChangedByCursor() +void ChannelAnalyzerGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); applySettings(); } -void ChannelAnalyzerNGGUI::channelMarkerHighlightedByCursor() +void ChannelAnalyzerGUI::channelMarkerHighlightedByCursor() { setHighlighted(m_channelMarker.getHighlighted()); } -void ChannelAnalyzerNGGUI::tick() +void ChannelAnalyzerGUI::tick() { double powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq()); m_channelPowerDbAvg(powDb); @@ -244,14 +243,14 @@ void ChannelAnalyzerNGGUI::tick() } } -void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) +void ChannelAnalyzerGUI::on_channelSampleRate_changed(quint64 value) { m_settings.m_downSampleRate = value; setNewFinalRate(); applySettings(); } -void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) +void ChannelAnalyzerGUI::on_pll_toggled(bool checked) { if (!checked) { ui->pll->setToolTip(tr("PLL lock")); @@ -261,7 +260,7 @@ void ChannelAnalyzerNGGUI::on_pll_toggled(bool checked) applySettings(); } -void ChannelAnalyzerNGGUI::on_pllPskOrder_currentIndexChanged(int index) +void ChannelAnalyzerGUI::on_pllPskOrder_currentIndexChanged(int index) { if (index < 5) { m_settings.m_pllPskOrder = (1<useRationalDownsampler->isChecked()) { return ui->channelSampleRate->getValueNew(); @@ -287,26 +286,26 @@ int ChannelAnalyzerNGGUI::getRequestedChannelSampleRate() } } -void ChannelAnalyzerNGGUI::on_signalSelect_currentIndexChanged(int index) +void ChannelAnalyzerGUI::on_signalSelect_currentIndexChanged(int index) { - m_settings.m_inputType = (ChannelAnalyzerNGSettings::InputType) index; + m_settings.m_inputType = (ChannelAnalyzerSettings::InputType) index; applySettings(); } -void ChannelAnalyzerNGGUI::on_deltaFrequency_changed(qint64 value) +void ChannelAnalyzerGUI::on_deltaFrequency_changed(qint64 value) { m_channelMarker.setCenterFrequency(value); m_settings.m_frequency = m_channelMarker.getCenterFrequency(); applySettings(); } -void ChannelAnalyzerNGGUI::on_rrcFilter_toggled(bool checked) +void ChannelAnalyzerGUI::on_rrcFilter_toggled(bool checked) { m_settings.m_rrc = checked; applySettings(); } -void ChannelAnalyzerNGGUI::on_rrcRolloff_valueChanged(int value) +void ChannelAnalyzerGUI::on_rrcRolloff_valueChanged(int value) { m_settings.m_rrcRolloff = value; QString rolloffStr = QString::number(value/100.0, 'f', 2); @@ -314,7 +313,7 @@ void ChannelAnalyzerNGGUI::on_rrcRolloff_valueChanged(int value) applySettings(); } -void ChannelAnalyzerNGGUI::on_BW_valueChanged(int value __attribute__((unused))) +void ChannelAnalyzerGUI::on_BW_valueChanged(int value __attribute__((unused))) { setFiltersUIBoundaries(); m_settings.m_bandwidth = ui->BW->value() * 100; @@ -322,7 +321,7 @@ void ChannelAnalyzerNGGUI::on_BW_valueChanged(int value __attribute__((unused))) applySettings(); } -void ChannelAnalyzerNGGUI::on_lowCut_valueChanged(int value __attribute__((unused))) +void ChannelAnalyzerGUI::on_lowCut_valueChanged(int value __attribute__((unused))) { setFiltersUIBoundaries(); m_settings.m_bandwidth = ui->BW->value() * 100; @@ -330,7 +329,7 @@ void ChannelAnalyzerNGGUI::on_lowCut_valueChanged(int value __attribute__((unuse applySettings(); } -void ChannelAnalyzerNGGUI::on_spanLog2_currentIndexChanged(int index) +void ChannelAnalyzerGUI::on_spanLog2_currentIndexChanged(int index) { if ((index < 0) || (index > 6)) { return; @@ -341,7 +340,7 @@ void ChannelAnalyzerNGGUI::on_spanLog2_currentIndexChanged(int index) applySettings(); } -void ChannelAnalyzerNGGUI::on_ssb_toggled(bool checked) +void ChannelAnalyzerGUI::on_ssb_toggled(bool checked) { m_settings.m_ssb = checked; if (checked) { @@ -353,11 +352,11 @@ void ChannelAnalyzerNGGUI::on_ssb_toggled(bool checked) applySettings(); } -void ChannelAnalyzerNGGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) +void ChannelAnalyzerGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { } -void ChannelAnalyzerNGGUI::onMenuDialogCalled(const QPoint& p) +void ChannelAnalyzerGUI::onMenuDialogCalled(const QPoint& p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); dialog.move(p); @@ -373,7 +372,7 @@ void ChannelAnalyzerNGGUI::onMenuDialogCalled(const QPoint& p) applySettings(); } -ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : +ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : RollupWidget(parent), ui(new Ui::ChannelAnalyzerNGGUI), m_pluginAPI(pluginAPI), @@ -390,7 +389,7 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); m_scopeVis = new ScopeVisNG(ui->glScope); m_spectrumScopeComboVis = new SpectrumScopeNGComboVis(m_spectrumVis, m_scopeVis); - m_channelAnalyzer = (ChannelAnalyzerNG*) rxChannel; //new ChannelAnalyzerNG(m_deviceUISet->m_deviceSourceAPI); + m_channelAnalyzer = (ChannelAnalyzer*) rxChannel; //new ChannelAnalyzerNG(m_deviceUISet->m_deviceSourceAPI); m_channelAnalyzer->setSampleSink(m_spectrumScopeComboVis); m_channelAnalyzer->setMessageQueueToGUI(getInputMessageQueue()); @@ -422,7 +421,7 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de m_channelMarker.setVisible(true); // activate signal on the last setting only setTitleColor(m_channelMarker.getColor()); - m_deviceUISet->registerRxChannelInstance(ChannelAnalyzerNG::m_channelIdURI, this); + m_deviceUISet->registerRxChannelInstance(ChannelAnalyzer::m_channelIdURI, this); m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); @@ -441,7 +440,7 @@ ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *de applySettings(true); } -ChannelAnalyzerNGGUI::~ChannelAnalyzerNGGUI() +ChannelAnalyzerGUI::~ChannelAnalyzerGUI() { m_deviceUISet->removeRxChannelInstance(this); delete m_channelAnalyzer; // TODO: check this: when the GUI closes it has to delete the demodulator @@ -451,7 +450,7 @@ ChannelAnalyzerNGGUI::~ChannelAnalyzerNGGUI() delete ui; } -void ChannelAnalyzerNGGUI::setNewFinalRate() +void ChannelAnalyzerGUI::setNewFinalRate() { m_rate = getRequestedChannelSampleRate() / (1<setSampleRate(m_rate); } -void ChannelAnalyzerNGGUI::setFiltersUIBoundaries() +void ChannelAnalyzerGUI::setFiltersUIBoundaries() { bool dsb = !ui->ssb->isChecked(); int bw = ui->BW->value(); @@ -526,39 +525,39 @@ void ChannelAnalyzerNGGUI::setFiltersUIBoundaries() } } -void ChannelAnalyzerNGGUI::blockApplySettings(bool block) +void ChannelAnalyzerGUI::blockApplySettings(bool block) { ui->glScope->blockSignals(block); ui->glSpectrum->blockSignals(block); m_doApplySettings = !block; } -void ChannelAnalyzerNGGUI::applySettings(bool force) +void ChannelAnalyzerGUI::applySettings(bool force) { if (m_doApplySettings) { int sampleRate = getRequestedChannelSampleRate(); - ChannelAnalyzerNG::MsgConfigureChannelizer *msgChannelizer = - ChannelAnalyzerNG::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); + ChannelAnalyzer::MsgConfigureChannelizer *msgChannelizer = + ChannelAnalyzer::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); m_channelAnalyzer->getInputMessageQueue()->push(msgChannelizer); - ChannelAnalyzerNG::MsgConfigureChannelizer *msg = - ChannelAnalyzerNG::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); + ChannelAnalyzer::MsgConfigureChannelizer *msg = + ChannelAnalyzer::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); m_channelAnalyzer->getInputMessageQueue()->push(msg); - ChannelAnalyzerNG::MsgConfigureChannelAnalyzer* message = - ChannelAnalyzerNG::MsgConfigureChannelAnalyzer::create( m_settings, force); + ChannelAnalyzer::MsgConfigureChannelAnalyzer* message = + ChannelAnalyzer::MsgConfigureChannelAnalyzer::create( m_settings, force); m_channelAnalyzer->getInputMessageQueue()->push(message); } } -void ChannelAnalyzerNGGUI::leaveEvent(QEvent*) +void ChannelAnalyzerGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); } -void ChannelAnalyzerNGGUI::enterEvent(QEvent*) +void ChannelAnalyzerGUI::enterEvent(QEvent*) { m_channelMarker.setHighlighted(true); } diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzergui.h similarity index 88% rename from plugins/channelrx/chanalyzerng/chanalyzernggui.h rename to plugins/channelrx/chanalyzerng/chanalyzergui.h index c19a10ec4..e62a19218 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ b/plugins/channelrx/chanalyzerng/chanalyzergui.h @@ -24,12 +24,12 @@ #include "util/movingaverage.h" #include "util/messagequeue.h" -#include "chanalyzerngsettings.h" +#include "chanalyzersettings.h" class PluginAPI; class DeviceUISet; class BasebandSampleSink; -class ChannelAnalyzerNG; +class ChannelAnalyzer; class SpectrumScopeNGComboVis; class SpectrumVis; class ScopeVisNG; @@ -38,11 +38,11 @@ namespace Ui { class ChannelAnalyzerNGGUI; } -class ChannelAnalyzerNGGUI : public RollupWidget, public PluginInstanceGUI { +class ChannelAnalyzerGUI : public RollupWidget, public PluginInstanceGUI { Q_OBJECT public: - static ChannelAnalyzerNGGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + static ChannelAnalyzerGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); virtual void destroy(); void setName(const QString& name); @@ -65,19 +65,19 @@ private: PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; ChannelMarker m_channelMarker; - ChannelAnalyzerNGSettings m_settings; + ChannelAnalyzerSettings m_settings; bool m_doApplySettings; int m_rate; //!< sample rate after final in-channel decimation (spanlog2) MovingAverageUtil m_channelPowerDbAvg; - ChannelAnalyzerNG* m_channelAnalyzer; + ChannelAnalyzer* m_channelAnalyzer; SpectrumScopeNGComboVis* m_spectrumScopeComboVis; SpectrumVis* m_spectrumVis; ScopeVisNG* m_scopeVis; MessageQueue m_inputMessageQueue; - explicit ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); - virtual ~ChannelAnalyzerNGGUI(); + explicit ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~ChannelAnalyzerGUI(); int getRequestedChannelSampleRate(); void setNewFinalRate(); //!< set sample rate after final in-channel decimation diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp b/plugins/channelrx/chanalyzerng/chanalyzerplugin.cpp similarity index 63% rename from plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp rename to plugins/channelrx/chanalyzerng/chanalyzerplugin.cpp index 8f3446754..ee0b9df73 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerplugin.cpp @@ -17,11 +17,11 @@ #include #include "plugin/pluginapi.h" -#include "chanalyzerngplugin.h" -#include "chanalyzernggui.h" -#include "chanalyzerng.h" +#include "chanalyzer.h" +#include "chanalyzerplugin.h" +#include "chanalyzergui.h" -const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = { +const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), @@ -30,37 +30,37 @@ const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = { QString("https://github.com/f4exb/sdrangel") }; -ChannelAnalyzerNGPlugin::ChannelAnalyzerNGPlugin(QObject* parent) : +ChannelAnalyzerPlugin::ChannelAnalyzerPlugin(QObject* parent) : QObject(parent), m_pluginAPI(0) { } -const PluginDescriptor& ChannelAnalyzerNGPlugin::getPluginDescriptor() const +const PluginDescriptor& ChannelAnalyzerPlugin::getPluginDescriptor() const { return m_pluginDescriptor; } -void ChannelAnalyzerNGPlugin::initPlugin(PluginAPI* pluginAPI) +void ChannelAnalyzerPlugin::initPlugin(PluginAPI* pluginAPI) { m_pluginAPI = pluginAPI; // register demodulator - m_pluginAPI->registerRxChannel(ChannelAnalyzerNG::m_channelIdURI, ChannelAnalyzerNG::m_channelId, this); + m_pluginAPI->registerRxChannel(ChannelAnalyzer::m_channelIdURI, ChannelAnalyzer::m_channelId, this); } -PluginInstanceGUI* ChannelAnalyzerNGPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +PluginInstanceGUI* ChannelAnalyzerPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { - return ChannelAnalyzerNGGUI::create(m_pluginAPI, deviceUISet, rxChannel); + return ChannelAnalyzerGUI::create(m_pluginAPI, deviceUISet, rxChannel); } -BasebandSampleSink* ChannelAnalyzerNGPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) +BasebandSampleSink* ChannelAnalyzerPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { - return new ChannelAnalyzerNG(deviceAPI); + return new ChannelAnalyzer(deviceAPI); } -ChannelSinkAPI* ChannelAnalyzerNGPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) +ChannelSinkAPI* ChannelAnalyzerPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) { - return new ChannelAnalyzerNG(deviceAPI); + return new ChannelAnalyzer(deviceAPI); } diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.h b/plugins/channelrx/chanalyzerng/chanalyzerplugin.h similarity index 94% rename from plugins/channelrx/chanalyzerng/chanalyzerngplugin.h rename to plugins/channelrx/chanalyzerng/chanalyzerplugin.h index b6c0ce9d7..f8254385b 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.h +++ b/plugins/channelrx/chanalyzerng/chanalyzerplugin.h @@ -24,13 +24,13 @@ class DeviceUISet; class BasebandSampleSink; -class ChannelAnalyzerNGPlugin : public QObject, PluginInterface { +class ChannelAnalyzerPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) Q_PLUGIN_METADATA(IID "sdrangel.channel.chanalyzerng") public: - explicit ChannelAnalyzerNGPlugin(QObject* parent = NULL); + explicit ChannelAnalyzerPlugin(QObject* parent = NULL); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp b/plugins/channelrx/chanalyzerng/chanalyzersettings.cpp similarity index 93% rename from plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp rename to plugins/channelrx/chanalyzerng/chanalyzersettings.cpp index f8173243a..dd931e667 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzersettings.cpp @@ -19,9 +19,10 @@ #include "dsp/dspengine.h" #include "util/simpleserializer.h" #include "settings/serializable.h" -#include "chanalyzerngsettings.h" -ChannelAnalyzerNGSettings::ChannelAnalyzerNGSettings() : +#include "chanalyzersettings.h" + +ChannelAnalyzerSettings::ChannelAnalyzerSettings() : m_channelMarker(0), m_spectrumGUI(0), m_scopeGUI(0) @@ -29,7 +30,7 @@ ChannelAnalyzerNGSettings::ChannelAnalyzerNGSettings() : resetToDefaults(); } -void ChannelAnalyzerNGSettings::resetToDefaults() +void ChannelAnalyzerSettings::resetToDefaults() { m_frequency = 0; m_downSample = false; @@ -48,7 +49,7 @@ void ChannelAnalyzerNGSettings::resetToDefaults() m_title = "Channel Analyzer"; } -QByteArray ChannelAnalyzerNGSettings::serialize() const +QByteArray ChannelAnalyzerSettings::serialize() const { SimpleSerializer s(1); @@ -73,7 +74,7 @@ QByteArray ChannelAnalyzerNGSettings::serialize() const return s.final(); } -bool ChannelAnalyzerNGSettings::deserialize(const QByteArray& data) +bool ChannelAnalyzerSettings::deserialize(const QByteArray& data) { SimpleDeserializer d(data); diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h b/plugins/channelrx/chanalyzerng/chanalyzersettings.h similarity index 89% rename from plugins/channelrx/chanalyzerng/chanalyzerngsettings.h rename to plugins/channelrx/chanalyzerng/chanalyzersettings.h index df37a5282..065c42049 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngsettings.h +++ b/plugins/channelrx/chanalyzerng/chanalyzersettings.h @@ -14,14 +14,14 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERNGSETTINGS_H_ -#define PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERNGSETTINGS_H_ +#ifndef PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERSETTINGS_H_ +#define PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERSETTINGS_H_ #include class Serializable; -struct ChannelAnalyzerNGSettings +struct ChannelAnalyzerSettings { enum InputType { @@ -49,7 +49,7 @@ struct ChannelAnalyzerNGSettings Serializable *m_spectrumGUI; Serializable *m_scopeGUI; - ChannelAnalyzerNGSettings(); + ChannelAnalyzerSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } @@ -60,4 +60,4 @@ struct ChannelAnalyzerNGSettings -#endif /* PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERNGSETTINGS_H_ */ +#endif /* PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERSETTINGS_H_ */ From e24e924d2bb16a9686d887358871bed0c45dc0bc Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 30 May 2018 15:42:33 +0200 Subject: [PATCH 508/956] Renamed Channel Analyzer NG to Channel Analyzer (2) --- plugins/channelrx/CMakeLists.txt | 2 +- .../CMakeLists.txt | 26 +++++++++---------- .../chanalyzer.cpp | 0 .../{chanalyzerng => chanalyzer}/chanalyzer.h | 0 .../chanalyzergui.cpp | 2 +- .../chanalyzergui.h | 0 .../chanalyzergui.ui} | 0 .../chanalyzerng.pro | 0 .../chanalyzerplugin.cpp | 0 .../chanalyzerplugin.h | 0 .../chanalyzersettings.cpp | 0 .../chanalyzersettings.h | 0 .../{chanalyzerng => chanalyzer}/readme.md | 0 13 files changed, 15 insertions(+), 15 deletions(-) rename plugins/channelrx/{chanalyzerng => chanalyzer}/CMakeLists.txt (50%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzer.cpp (100%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzer.h (100%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzergui.cpp (99%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzergui.h (100%) rename plugins/channelrx/{chanalyzerng/chanalyzernggui.ui => chanalyzer/chanalyzergui.ui} (100%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzerng.pro (100%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzerplugin.cpp (100%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzerplugin.h (100%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzersettings.cpp (100%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/chanalyzersettings.h (100%) rename plugins/channelrx/{chanalyzerng => chanalyzer}/readme.md (100%) diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 099c9afd3..66b1f515f 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -7,7 +7,7 @@ add_subdirectory(demodnfm) add_subdirectory(demodssb) add_subdirectory(udpsrc) add_subdirectory(demodwfm) -add_subdirectory(chanalyzerng) +add_subdirectory(chanalyzer) add_subdirectory(demodatv) if(LIBDSDCC_FOUND AND LIBMBE_FOUND) diff --git a/plugins/channelrx/chanalyzerng/CMakeLists.txt b/plugins/channelrx/chanalyzer/CMakeLists.txt similarity index 50% rename from plugins/channelrx/chanalyzerng/CMakeLists.txt rename to plugins/channelrx/chanalyzer/CMakeLists.txt index 80aab5475..b749209a4 100644 --- a/plugins/channelrx/chanalyzerng/CMakeLists.txt +++ b/plugins/channelrx/chanalyzer/CMakeLists.txt @@ -1,21 +1,21 @@ -project(chanalyzerng) +project(chanalyzer) -set(chanalyzerng_SOURCES +set(chanalyzer_SOURCES chanalyzer.cpp chanalyzergui.cpp chanalyzerplugin.cpp chanalyzersettings.cpp ) -set(chanalyzerng_HEADERS +set(chanalyzer_HEADERS chanalyzer.h chanalyzergui.h chanalyzerplugin.h chanalyzersettings.h ) -set(chanalyzerng_FORMS - chanalyzernggui.ui +set(chanalyzer_FORMS + chanalyzergui.ui ) include_directories( @@ -29,20 +29,20 @@ add_definitions(-DQT_PLUGIN) add_definitions(-DQT_SHARED) #qt5_wrap_cpp(chanalyzer_HEADERS_MOC ${chanalyzer_HEADERS}) -qt5_wrap_ui(chanalyzerng_FORMS_HEADERS ${chanalyzerng_FORMS}) +qt5_wrap_ui(chanalyzer_FORMS_HEADERS ${chanalyzer_FORMS}) -add_library(chanalyzerng SHARED - ${chanalyzerng_SOURCES} - ${chanalyzerng_HEADERS_MOC} - ${chanalyzerng_FORMS_HEADERS} +add_library(chanalyzer SHARED + ${chanalyzer_SOURCES} + ${chanalyzer_HEADERS_MOC} + ${chanalyzer_FORMS_HEADERS} ) -target_link_libraries(chanalyzerng +target_link_libraries(chanalyzer ${QT_LIBRARIES} sdrbase sdrgui ) -qt5_use_modules(chanalyzerng Core Widgets ) +qt5_use_modules(chanalyzer Core Widgets ) -install(TARGETS chanalyzerng DESTINATION lib/plugins/channelrx) +install(TARGETS chanalyzer DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/chanalyzerng/chanalyzer.cpp b/plugins/channelrx/chanalyzer/chanalyzer.cpp similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzer.cpp rename to plugins/channelrx/chanalyzer/chanalyzer.cpp diff --git a/plugins/channelrx/chanalyzerng/chanalyzer.h b/plugins/channelrx/chanalyzer/chanalyzer.h similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzer.h rename to plugins/channelrx/chanalyzer/chanalyzer.h diff --git a/plugins/channelrx/chanalyzerng/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp similarity index 99% rename from plugins/channelrx/chanalyzerng/chanalyzergui.cpp rename to plugins/channelrx/chanalyzer/chanalyzergui.cpp index 860839bd9..a340f6888 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -21,7 +21,7 @@ #include #include "dsp/threadedbasebandsamplesink.h" -#include "ui_chanalyzernggui.h" +#include "ui_chanalyzergui.h" #include "dsp/spectrumscopengcombovis.h" #include "dsp/spectrumvis.h" #include "dsp/scopevis.h" diff --git a/plugins/channelrx/chanalyzerng/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzergui.h rename to plugins/channelrx/chanalyzer/chanalyzergui.h diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzernggui.ui rename to plugins/channelrx/chanalyzer/chanalyzergui.ui diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.pro b/plugins/channelrx/chanalyzer/chanalyzerng.pro similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzerng.pro rename to plugins/channelrx/chanalyzer/chanalyzerng.pro diff --git a/plugins/channelrx/chanalyzerng/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzerplugin.cpp rename to plugins/channelrx/chanalyzer/chanalyzerplugin.cpp diff --git a/plugins/channelrx/chanalyzerng/chanalyzerplugin.h b/plugins/channelrx/chanalyzer/chanalyzerplugin.h similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzerplugin.h rename to plugins/channelrx/chanalyzer/chanalyzerplugin.h diff --git a/plugins/channelrx/chanalyzerng/chanalyzersettings.cpp b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzersettings.cpp rename to plugins/channelrx/chanalyzer/chanalyzersettings.cpp diff --git a/plugins/channelrx/chanalyzerng/chanalyzersettings.h b/plugins/channelrx/chanalyzer/chanalyzersettings.h similarity index 100% rename from plugins/channelrx/chanalyzerng/chanalyzersettings.h rename to plugins/channelrx/chanalyzer/chanalyzersettings.h diff --git a/plugins/channelrx/chanalyzerng/readme.md b/plugins/channelrx/chanalyzer/readme.md similarity index 100% rename from plugins/channelrx/chanalyzerng/readme.md rename to plugins/channelrx/chanalyzer/readme.md From 264c08b4c3a4cccc912c221445e3eb9b7a2ea6fb Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 30 May 2018 15:49:28 +0200 Subject: [PATCH 509/956] Renamed Channel Analyzer NG to Channel Analyzer (3) --- plugins/channelrx/chanalyzer/chanalyzer.cpp | 12 ++++++------ plugins/channelrx/chanalyzer/chanalyzergui.cpp | 8 ++++---- plugins/channelrx/chanalyzer/chanalyzergui.h | 4 ++-- plugins/channelrx/chanalyzer/chanalyzergui.ui | 4 ++-- plugins/channelrx/chanalyzer/chanalyzersettings.cpp | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzer.cpp b/plugins/channelrx/chanalyzer/chanalyzer.cpp index 26c8f1698..ac3a059ae 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzer.cpp @@ -194,7 +194,7 @@ bool ChannelAnalyzer::handleMessage(const Message& cmd) if (DownChannelizer::MsgChannelizerNotification::match(cmd)) { DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "ChannelAnalyzerNG::handleMessage: DownChannelizer::MsgChannelizerNotification:" + qDebug() << "ChannelAnalyzer::handleMessage: DownChannelizer::MsgChannelizerNotification:" << " sampleRate: " << notif.getSampleRate() << " frequencyOffset: " << notif.getFrequencyOffset(); @@ -211,7 +211,7 @@ bool ChannelAnalyzer::handleMessage(const Message& cmd) else if (MsgConfigureChannelizer::match(cmd)) { MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelizer:" + qDebug() << "ChannelAnalyzer::handleMessage: MsgConfigureChannelizer:" << " sampleRate: " << cfg.getSampleRate() << " centerFrequency: " << cfg.getCenterFrequency(); @@ -223,7 +223,7 @@ bool ChannelAnalyzer::handleMessage(const Message& cmd) } else if (MsgConfigureChannelAnalyzer::match(cmd)) { - qDebug("ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer"); + qDebug("ChannelAnalyzer::handleMessage: MsgConfigureChannelAnalyzer"); MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; applySettings(cfg.getSettings(), cfg.getForce()); @@ -245,7 +245,7 @@ bool ChannelAnalyzer::handleMessage(const Message& cmd) void ChannelAnalyzer::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { - qDebug() << "ChannelAnalyzerNG::applyChannelSettings:" + qDebug() << "ChannelAnalyzer::applyChannelSettings:" << " inputSampleRate: " << inputSampleRate << " inputFrequencyOffset: " << inputFrequencyOffset; @@ -279,7 +279,7 @@ void ChannelAnalyzer::applyChannelSettings(int inputSampleRate, int inputFrequen void ChannelAnalyzer::setFilters(int sampleRate, float bandwidth, float lowCutoff) { - qDebug("ChannelAnalyzerNG::setFilters: sampleRate: %d bandwidth: %f lowCutoff: %f", + qDebug("ChannelAnalyzer::setFilters: sampleRate: %d bandwidth: %f lowCutoff: %f", sampleRate, bandwidth, lowCutoff); if (bandwidth < 0) @@ -306,7 +306,7 @@ void ChannelAnalyzer::setFilters(int sampleRate, float bandwidth, float lowCutof void ChannelAnalyzer::applySettings(const ChannelAnalyzerSettings& settings, bool force) { - qDebug() << "ChannelAnalyzerNG::applySettings:" + qDebug() << "ChannelAnalyzer::applySettings:" << " m_downSample: " << settings.m_downSample << " m_downSampleRate: " << settings.m_downSampleRate << " m_rcc: " << settings.m_rrc diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index a340f6888..81a5c84aa 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -144,7 +144,7 @@ void ChannelAnalyzerGUI::displayPLLSettings() void ChannelAnalyzerGUI::setSpectrumDisplay() { - qDebug("ChannelAnalyzerNGGUI::setSpectrumDisplay: m_rate: %d", m_rate); + qDebug("ChannelAnalyzerGUI::setSpectrumDisplay: m_rate: %d", m_rate); if (m_settings.m_ssb) { ui->glSpectrum->setCenterFrequency(m_rate/4); @@ -187,7 +187,7 @@ bool ChannelAnalyzerGUI::handleMessage(const Message& message) { if (ChannelAnalyzer::MsgReportChannelSampleRateChanged::match(message)) { - qDebug() << "ChannelAnalyzerNGGUI::handleMessage: MsgReportChannelSampleRateChanged"; + qDebug() << "ChannelAnalyzerGUI::handleMessage: MsgReportChannelSampleRateChanged"; ui->channelSampleRate->setValueRange(7, 2000U, m_channelAnalyzer->getInputSampleRate()); ui->channelSampleRate->setValue(m_settings.m_downSampleRate); setNewFinalRate(); @@ -374,7 +374,7 @@ void ChannelAnalyzerGUI::onMenuDialogCalled(const QPoint& p) ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : RollupWidget(parent), - ui(new Ui::ChannelAnalyzerNGGUI), + ui(new Ui::ChannelAnalyzerGUI), m_pluginAPI(pluginAPI), m_deviceUISet(deviceUISet), m_channelMarker(this), @@ -456,7 +456,7 @@ void ChannelAnalyzerGUI::setNewFinalRate() if (m_rate == 0) { m_rate = 48000; } - qDebug("ChannelAnalyzerNGGUI::setNewFinalRate: %d m_spanLog2: %d", m_rate, m_settings.m_spanLog2); + qDebug("ChannelAnalyzerGUI::setNewFinalRate: %d m_spanLog2: %d", m_rate, m_settings.m_spanLog2); setFiltersUIBoundaries(); diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h index e62a19218..5210b2171 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ b/plugins/channelrx/chanalyzer/chanalyzergui.h @@ -35,7 +35,7 @@ class SpectrumVis; class ScopeVisNG; namespace Ui { - class ChannelAnalyzerNGGUI; + class ChannelAnalyzerGUI; } class ChannelAnalyzerGUI : public RollupWidget, public PluginInstanceGUI { @@ -61,7 +61,7 @@ public slots: void channelMarkerHighlightedByCursor(); private: - Ui::ChannelAnalyzerNGGUI* ui; + Ui::ChannelAnalyzerGUI* ui; PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; ChannelMarker m_channelMarker; diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index 73bb3bdbb..d052da7f3 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -1,7 +1,7 @@ - ChannelAnalyzerNGGUI - + ChannelAnalyzerGUI + 0 diff --git a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp index dd931e667..02fe476e6 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp @@ -114,7 +114,7 @@ bool ChannelAnalyzerSettings::deserialize(const QByteArray& data) d.readU32(13, &m_pllPskOrder, 1); d.readS32(14, &tmp, 0); m_inputType = (InputType) tmp; - d.readString(15, &m_title, "Channel Analyzer NG"); + d.readString(15, &m_title, "Channel Analyzer"); d.readBool(16, &m_rrc, false); d.readU32(17, &m_rrcRolloff, 35); From 5f08261d376473f0e6b4c0335f019f68c426a0f0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 30 May 2018 20:22:35 +0200 Subject: [PATCH 510/956] LoRa demod: changed channel Id URI --- plugins/channelrx/demodlora/lorademod.cpp | 2 +- plugins/channelrx/demodlora/loraplugin.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demodlora/lorademod.cpp b/plugins/channelrx/demodlora/lorademod.cpp index b25fe12e3..87d9882b3 100644 --- a/plugins/channelrx/demodlora/lorademod.cpp +++ b/plugins/channelrx/demodlora/lorademod.cpp @@ -32,7 +32,7 @@ MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgConfigureLoRaDemod, Message) MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgConfigureChannelizer, Message) -const QString LoRaDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.lora"; +const QString LoRaDemod::m_channelIdURI = "sdrangel.channel.lorademod"; const QString LoRaDemod::m_channelId = "LoRaDemod"; LoRaDemod::LoRaDemod(DeviceSourceAPI* deviceAPI) : diff --git a/plugins/channelrx/demodlora/loraplugin.h b/plugins/channelrx/demodlora/loraplugin.h index 46a0a0d39..75a812af0 100644 --- a/plugins/channelrx/demodlora/loraplugin.h +++ b/plugins/channelrx/demodlora/loraplugin.h @@ -10,7 +10,7 @@ class BasebandSampleSink; class LoRaPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.lora") + Q_PLUGIN_METADATA(IID "sdrangel.channel.lorademod") public: explicit LoRaPlugin(QObject* parent = NULL); From a839695c3a3a9ceb9cd3b89f681d218d2274dd4b Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 30 May 2018 22:25:57 +0200 Subject: [PATCH 511/956] AM, NFM, SSB and WFM demods: changed channel Id URI --- plugins/channelrx/demodam/amdemod.cpp | 2 +- plugins/channelrx/demodam/amdemodplugin.h | 2 +- plugins/channelrx/demodnfm/nfmdemod.cpp | 2 +- plugins/channelrx/demodnfm/nfmplugin.h | 2 +- plugins/channelrx/demodssb/ssbdemod.cpp | 2 +- plugins/channelrx/demodssb/ssbplugin.h | 2 +- plugins/channelrx/demodwfm/wfmdemod.cpp | 2 +- plugins/channelrx/demodwfm/wfmplugin.h | 2 +- sdrgui/device/deviceuiset.cpp | 8 ++++++++ sdrsrv/device/deviceset.cpp | 8 ++++++++ 10 files changed, 24 insertions(+), 8 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index e81436311..0232d6927 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -40,7 +40,7 @@ MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureAMDemod, Message) MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureChannelizer, Message) -const QString AMDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.am"; +const QString AMDemod::m_channelIdURI = "sdrangel.channel.amdemod"; const QString AMDemod::m_channelId = "AMDemod"; const int AMDemod::m_udpBlockSize = 512; diff --git a/plugins/channelrx/demodam/amdemodplugin.h b/plugins/channelrx/demodam/amdemodplugin.h index efcaf974b..5a20b596d 100644 --- a/plugins/channelrx/demodam/amdemodplugin.h +++ b/plugins/channelrx/demodam/amdemodplugin.h @@ -26,7 +26,7 @@ class BasebandSampleSink; class AMDemodPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.am") + Q_PLUGIN_METADATA(IID "sdrangel.channel.amdemod") public: explicit AMDemodPlugin(QObject* parent = NULL); diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 4ddc77593..29c1de46f 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -40,7 +40,7 @@ MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureNFMDemod, Message) MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(NFMDemod::MsgReportCTCSSFreq, Message) -const QString NFMDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.nfm"; +const QString NFMDemod::m_channelIdURI = "sdrangel.channel.nfmdemod"; const QString NFMDemod::m_channelId = "NFMDemod"; static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0}; diff --git a/plugins/channelrx/demodnfm/nfmplugin.h b/plugins/channelrx/demodnfm/nfmplugin.h index b5f90af70..c5c07976a 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.h +++ b/plugins/channelrx/demodnfm/nfmplugin.h @@ -10,7 +10,7 @@ class BasebandSampleSink; class NFMPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.nfm") + Q_PLUGIN_METADATA(IID "sdrangel.channel.nfmdemod") public: explicit NFMPlugin(QObject* parent = NULL); diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 2efeb6ea2..82749468c 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -40,7 +40,7 @@ MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureSSBDemod, Message) MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureSSBDemodPrivate, Message) MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureChannelizer, Message) -const QString SSBDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.ssb"; +const QString SSBDemod::m_channelIdURI = "sdrangel.channel.ssbdemod"; const QString SSBDemod::m_channelId = "SSBDemod"; SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : diff --git a/plugins/channelrx/demodssb/ssbplugin.h b/plugins/channelrx/demodssb/ssbplugin.h index b965d5c1c..f76f8e73d 100644 --- a/plugins/channelrx/demodssb/ssbplugin.h +++ b/plugins/channelrx/demodssb/ssbplugin.h @@ -10,7 +10,7 @@ class BasebandSampleSink; class SSBPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.ssb") + Q_PLUGIN_METADATA(IID "sdrangel.channel.ssbdemod") public: explicit SSBPlugin(QObject* parent = NULL); diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index 81378b46f..e4354a331 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -39,7 +39,7 @@ MESSAGE_CLASS_DEFINITION(WFMDemod::MsgConfigureWFMDemod, Message) MESSAGE_CLASS_DEFINITION(WFMDemod::MsgConfigureChannelizer, Message) -const QString WFMDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.wfm"; +const QString WFMDemod::m_channelIdURI = "sdrangel.channel.wfmdemod"; const QString WFMDemod::m_channelId = "WFMDemod"; const int WFMDemod::m_udpBlockSize = 512; diff --git a/plugins/channelrx/demodwfm/wfmplugin.h b/plugins/channelrx/demodwfm/wfmplugin.h index c2676ce11..5b5e3be2a 100644 --- a/plugins/channelrx/demodwfm/wfmplugin.h +++ b/plugins/channelrx/demodwfm/wfmplugin.h @@ -10,7 +10,7 @@ class BasebandSampleSink; class WFMPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.wfm") + Q_PLUGIN_METADATA(IID "sdrangel.channel.wfmdemod") public: explicit WFMPlugin(QObject* parent = NULL); diff --git a/sdrgui/device/deviceuiset.cpp b/sdrgui/device/deviceuiset.cpp index 8ab6b5d48..deb1c5916 100644 --- a/sdrgui/device/deviceuiset.cpp +++ b/sdrgui/device/deviceuiset.cpp @@ -356,6 +356,14 @@ bool DeviceUISet::compareRxChannelURIs(const QString& registerdChannelURI, const { if ((xChannelURI == "sdrangel.channel.chanalyzerng") || (xChannelURI == "sdrangel.channel.chanalyzer")) { // renamed ChanalyzerNG to Chanalyzer in 4.0.0 return registerdChannelURI == "sdrangel.channel.chanalyzer"; + } else if ((xChannelURI == "de.maintech.sdrangelove.channel.am") || (xChannelURI == "sdrangel.channel.amdemod")) { + return registerdChannelURI == "sdrangel.channel.amdemod"; + } else if ((xChannelURI == "de.maintech.sdrangelove.channel.nfm") || (xChannelURI == "sdrangel.channel.nfmdemod")) { + return registerdChannelURI == "sdrangel.channel.nfmdemod"; + } else if ((xChannelURI == "de.maintech.sdrangelove.channel.ssb") || (xChannelURI == "sdrangel.channel.ssbdemod")) { + return registerdChannelURI == "sdrangel.channel.ssbdemod"; + } else if ((xChannelURI == "de.maintech.sdrangelove.channel.wfm") || (xChannelURI == "sdrangel.channel.wfmdemod")) { + return registerdChannelURI == "sdrangel.channel.wfmdemod"; } else { return registerdChannelURI == xChannelURI; } diff --git a/sdrsrv/device/deviceset.cpp b/sdrsrv/device/deviceset.cpp index 07585b64f..b7b9f2cca 100644 --- a/sdrsrv/device/deviceset.cpp +++ b/sdrsrv/device/deviceset.cpp @@ -376,6 +376,14 @@ bool DeviceSet::compareRxChannelURIs(const QString& registerdChannelURI, const Q { if ((xChannelURI == "sdrangel.channel.chanalyzerng") || (xChannelURI == "sdrangel.channel.chanalyzer")) { // renamed ChanalyzerNG to Chanalyzer in 4.0.0 return registerdChannelURI == "sdrangel.channel.chanalyzer"; + } else if ((xChannelURI == "de.maintech.sdrangelove.channel.am") || (xChannelURI == "sdrangel.channel.amdemod")) { + return registerdChannelURI == "sdrangel.channel.amdemod"; + } else if ((xChannelURI == "de.maintech.sdrangelove.channel.nfm") || (xChannelURI == "sdrangel.channel.nfmdemod")) { + return registerdChannelURI == "sdrangel.channel.nfmdemod"; + } else if ((xChannelURI == "de.maintech.sdrangelove.channel.ssb") || (xChannelURI == "sdrangel.channel.ssbdemod")) { + return registerdChannelURI == "sdrangel.channel.ssbdemod"; + } else if ((xChannelURI == "de.maintech.sdrangelove.channel.wfm") || (xChannelURI == "sdrangel.channel.wfmdemod")) { + return registerdChannelURI == "sdrangel.channel.wfmdemod"; } else { return registerdChannelURI == xChannelURI; } From 1defe23e9606dac1314f1e9bf26414277cc17885 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 30 May 2018 23:37:47 +0200 Subject: [PATCH 512/956] BladeRF input: fixed settings handling and therefore sample rate vs center frequency discrepancies when using shifted decimators --- .../bladerfinput/bladerfinput.cpp | 91 +++++++++++-------- 1 file changed, 54 insertions(+), 37 deletions(-) diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 6bcd2d163..419f3935e 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -327,65 +327,65 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc if ((m_settings.m_dcBlock != settings.m_dcBlock) || (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) { - m_settings.m_dcBlock = settings.m_dcBlock; - m_settings.m_iqCorrection = settings.m_iqCorrection; - m_deviceAPI->configureCorrections(m_settings.m_dcBlock, m_settings.m_iqCorrection); +// m_settings.m_dcBlock = settings.m_dcBlock; +// m_settings.m_iqCorrection = settings.m_iqCorrection; + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); } if ((m_settings.m_lnaGain != settings.m_lnaGain) || force) { - m_settings.m_lnaGain = settings.m_lnaGain; +// m_settings.m_lnaGain = settings.m_lnaGain; if (m_dev != 0) { - if(bladerf_set_lna_gain(m_dev, getLnaGain(m_settings.m_lnaGain)) != 0) + if(bladerf_set_lna_gain(m_dev, getLnaGain(settings.m_lnaGain)) != 0) { qDebug("BladerfInput::applySettings: bladerf_set_lna_gain() failed"); } else { - qDebug() << "BladerfInput::applySettings: LNA gain set to " << getLnaGain(m_settings.m_lnaGain); + qDebug() << "BladerfInput::applySettings: LNA gain set to " << getLnaGain(settings.m_lnaGain); } } } if ((m_settings.m_vga1 != settings.m_vga1) || force) { - m_settings.m_vga1 = settings.m_vga1; +// m_settings.m_vga1 = settings.m_vga1; if (m_dev != 0) { - if(bladerf_set_rxvga1(m_dev, m_settings.m_vga1) != 0) + if(bladerf_set_rxvga1(m_dev, settings.m_vga1) != 0) { qDebug("BladerfInput::applySettings: bladerf_set_rxvga1() failed"); } else { - qDebug() << "BladerfInput::applySettings: VGA1 gain set to " << m_settings.m_vga1; + qDebug() << "BladerfInput::applySettings: VGA1 gain set to " << settings.m_vga1; } } } if ((m_settings.m_vga2 != settings.m_vga2) || force) { - m_settings.m_vga2 = settings.m_vga2; +// m_settings.m_vga2 = settings.m_vga2; if(m_dev != 0) { - if(bladerf_set_rxvga2(m_dev, m_settings.m_vga2) != 0) + if(bladerf_set_rxvga2(m_dev, settings.m_vga2) != 0) { qDebug("BladerfInput::applySettings: bladerf_set_rxvga2() failed"); } else { - qDebug() << "BladerfInput::applySettings: VGA2 gain set to " << m_settings.m_vga2; + qDebug() << "BladerfInput::applySettings: VGA2 gain set to " << settings.m_vga2; } } } if ((m_settings.m_xb200 != settings.m_xb200) || force) { - m_settings.m_xb200 = settings.m_xb200; +// m_settings.m_xb200 = settings.m_xb200; if (m_dev != 0) { @@ -411,7 +411,7 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc if (changeSettings) { - if (m_settings.m_xb200) + if (settings.m_xb200) { if (bladerf_expansion_attach(m_dev, BLADERF_XB_200) != 0) { @@ -434,57 +434,57 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc } } - m_sharedParams.m_xb200Attached = m_settings.m_xb200; + m_sharedParams.m_xb200Attached = settings.m_xb200; } } } if ((m_settings.m_xb200Path != settings.m_xb200Path) || force) { - m_settings.m_xb200Path = settings.m_xb200Path; +// m_settings.m_xb200Path = settings.m_xb200Path; if (m_dev != 0) { - if(bladerf_xb200_set_path(m_dev, BLADERF_MODULE_RX, m_settings.m_xb200Path) != 0) + if(bladerf_xb200_set_path(m_dev, BLADERF_MODULE_RX, settings.m_xb200Path) != 0) { qDebug("BladerfInput::applySettings: bladerf_xb200_set_path(BLADERF_MODULE_RX) failed"); } else { - qDebug() << "BladerfInput::applySettings: set xb200 path to " << m_settings.m_xb200Path; + qDebug() << "BladerfInput::applySettings: set xb200 path to " << settings.m_xb200Path; } } } if ((m_settings.m_xb200Filter != settings.m_xb200Filter) || force) { - m_settings.m_xb200Filter = settings.m_xb200Filter; +// m_settings.m_xb200Filter = settings.m_xb200Filter; if (m_dev != 0) { - if(bladerf_xb200_set_filterbank(m_dev, BLADERF_MODULE_RX, m_settings.m_xb200Filter) != 0) + if(bladerf_xb200_set_filterbank(m_dev, BLADERF_MODULE_RX, settings.m_xb200Filter) != 0) { qDebug("BladerfInput::applySettings: bladerf_xb200_set_filterbank(BLADERF_MODULE_RX) failed"); } else { - qDebug() << "BladerfInput::applySettings: set xb200 filter to " << m_settings.m_xb200Filter; + qDebug() << "BladerfInput::applySettings: set xb200 filter to " << settings.m_xb200Filter; } } } if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) { - m_settings.m_devSampleRate = settings.m_devSampleRate; +// m_settings.m_devSampleRate = settings.m_devSampleRate; forwardChange = true; if (m_dev != 0) { unsigned int actualSamplerate; - if (bladerf_set_sample_rate(m_dev, BLADERF_MODULE_RX, m_settings.m_devSampleRate, &actualSamplerate) < 0) + if (bladerf_set_sample_rate(m_dev, BLADERF_MODULE_RX, settings.m_devSampleRate, &actualSamplerate) < 0) { - qCritical("BladerfInput::applySettings: could not set sample rate: %d", m_settings.m_devSampleRate); + qCritical("BladerfInput::applySettings: could not set sample rate: %d", settings.m_devSampleRate); } else { @@ -495,15 +495,15 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) { - m_settings.m_bandwidth = settings.m_bandwidth; +// m_settings.m_bandwidth = settings.m_bandwidth; if(m_dev != 0) { unsigned int actualBandwidth; - if( bladerf_set_bandwidth(m_dev, BLADERF_MODULE_RX, m_settings.m_bandwidth, &actualBandwidth) < 0) + if( bladerf_set_bandwidth(m_dev, BLADERF_MODULE_RX, settings.m_bandwidth, &actualBandwidth) < 0) { - qCritical("BladerfInput::applySettings: could not set bandwidth: %d", m_settings.m_bandwidth); + qCritical("BladerfInput::applySettings: could not set bandwidth: %d", settings.m_bandwidth); } else { @@ -523,13 +523,13 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) { - m_settings.m_log2Decim = settings.m_log2Decim; +// m_settings.m_log2Decim = settings.m_log2Decim; forwardChange = true; if (m_bladerfThread != 0) { - m_bladerfThread->setLog2Decimation(m_settings.m_log2Decim); - qDebug() << "BladerfInput::applySettings: set decimation to " << (1<setLog2Decimation(settings.m_log2Decim); + qDebug() << "BladerfInput::applySettings: set decimation to " << (1<handleMessage(*notif); // forward to file sink m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); } + m_settings = settings; + + qDebug() << "BladerfInput::applySettings: " + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_bandwidth: " << m_settings.m_bandwidth + << " m_lnaGain: " << m_settings.m_lnaGain + << " m_vga1: " << m_settings.m_vga1 + << " m_vga2: " << m_settings.m_vga2 + << " m_log2Decim: " << m_settings.m_log2Decim + << " m_fcPos: " << m_settings.m_fcPos + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_dcBlock: " << m_settings.m_dcBlock + << " m_iqCorrection: " << m_settings.m_iqCorrection + << " m_xb200Filter: " << m_settings.m_xb200Filter + << " m_xb200Path: " << m_settings.m_xb200Path + << " m_xb200: " << m_settings.m_xb200; + return true; } From 696084ac87467511921fbaf5cc10daa736c6579b Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 31 May 2018 01:26:38 +0200 Subject: [PATCH 513/956] BladeRF input: bumped plugin version --- plugins/samplesource/bladerfinput/bladerfinputplugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index 043b7a4b1..6cf31a871 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { QString("BladeRF Input"), - QString("3.14.6"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 9445abd9afd27fe15cbe2a1f70e8a332c7a6d50c Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 5 Jun 2018 21:27:58 +0200 Subject: [PATCH 514/956] Updated openSuSE instructions (1) --- Readme.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index d02f3a199..c12c6538b 100644 --- a/Readme.md +++ b/Readme.md @@ -103,6 +103,8 @@ If you use your own location for libbladeRF install directory you need to specif `-DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so -DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include` +☞ Please note that if you use your own library the FPGA image `hostedx40.rbf` or `hostedx115.rbf` shoud be placed in e.g. `/opt/install/libbladeRF/share/Nuand/bladeRF` +

    FunCube Dongle

    Linux only. @@ -374,7 +376,7 @@ For Debian Jessie or Stretch:

    openSUSE

    -This has been tested with the bleeding edge "Tumbleweed" distribution: +This has been tested with the Leap 42.3 distribution: `sudo zypper install Mesa-libGL1 Mesa-libEGL-devel Mesa-libGL-devel Mesa-libGLESv1_CM-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel Mesa-libglapi-devel libOSMesa-devel` @@ -382,9 +384,11 @@ This has been tested with the bleeding edge "Tumbleweed" distribution: Then you should be all set to build the software with `cmake` and `make` as discussed earlier. - - Note1: if you are on Leap you will need a more recent g++ compiler so in place of `gcc-c++` use `gcc5-c++` or `gcc6-c++` then add the following in the cmake command: `-DCMAKE_C_COMPILER=/usr/bin/gcc-6 -DCMAKE_CXX_COMPILER=/usr/bin/g++-6` (for gcc 6) and then `-DCMAKE_INSTALL_PREFIX:PATH=...` for the custom install path (not `-DCMAKE_INSTALL_PREFIX=...`) - - Note2: On Leap and aarch64 architectures you will need to build and install `libnanomsg` from [source](https://github.com/nanomsg/nanomsg) - - Note3 for udev rules: installed udev rules for BladeRF and HackRF are targeted at Debian or Ubuntu systems that have a plugdev group for USB hotplug devices. This is not the case in openSUSE. To make the udev rules file compatible just remove the `GROUP` parameter on all lines and change `MODE` parameter to `666`. + - Note1: if you are on Leap you will need a more recent g++ compiler so in place of `gcc-c++` use `gcc6-c++` or `gcc7-c++` then add the following in the cmake command: `-DCMAKE_C_COMPILER=/usr/bin/gcc-7 -DCMAKE_CXX_COMPILER=/usr/bin/g++-7` (for gcc 7) and then `-DCMAKE_INSTALL_PREFIX:PATH=...` for the custom install path (not `-DCMAKE_INSTALL_PREFIX=...`) + - Note2: On Leap and aarch64 architectures you will need to build and install `libnanomsg` from [source](https://github.com/nanomsg/nanomsg) then if your installation directory is `/opt/install/nanomsg` you will have to add this to the cmake command line: `-DLIBNANOMSG_INCLUDE_DIR=/opt/install/nanomsg/include -DLIBNANOMSG_LIBRARIES=/opt/install/nanomsg/lib64/libnanomsg.so` + - Note3 for udev rules: installed udev rules for BladeRF and HackRF are targeted at Debian or Ubuntu systems that have a plugdev group for USB hotplug devices. This is not the case in openSUSE. To fix it you can either: + - make the udev rules file compatible just remove the `GROUP` parameter on all lines and change `MODE` parameter to `666`. + - create a `plugdev` group and add it tou your user group list: `sudo groupadd plugdev` then `sudo usermod -G plugdev -a ` - Note4: A package has been created in openSUSE thanks to Martin, see: [sdrangel](https://build.opensuse.org/package/show/hardware:sdr/sdrangel). It is based on the latest release on master branch.

    Fedora

    From c62cb2653413461982a9ded6d8998c4aa66a8028 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 9 Jun 2018 21:50:30 +0200 Subject: [PATCH 515/956] Debian build: updated changelog --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index f5dbacd38..2dec7fc61 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +sdrangel (4.0.0-1) unstable; urgency=medium + + * Finalization of REST API and server instance + * Removal of old ChannelAnalyzer and TCPSrc plugins + * Renamed Channel Analyzer NG to Channel Analyzer + * DATV demod: added missing AVUTIL cmake variables + + -- Edouard Griffiths, F4EXB Sat, 09 Jun 2018 20:14:18 +0200 + sdrangel (3.14.7-1) unstable; urgency=medium * ChanelAnalyzerNG: added PLL option and source selection with auto correlation From 5c2ce71639894a6a6c0c406aca8ae95a04a5597d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 9 Jun 2018 22:00:30 +0200 Subject: [PATCH 516/956] DSD demod: fixed Debian build --- plugins/channelrx/demoddsd/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/channelrx/demoddsd/CMakeLists.txt b/plugins/channelrx/demoddsd/CMakeLists.txt index 9197fece8..e862aa291 100644 --- a/plugins/channelrx/demoddsd/CMakeLists.txt +++ b/plugins/channelrx/demoddsd/CMakeLists.txt @@ -31,6 +31,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBDSDCCSRC} ${LIBMBELIBSRC} ) From 9cfe455dd3e880e0dd3357a10ada8830979f0dee Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 9 Jun 2018 22:23:50 +0200 Subject: [PATCH 517/956] DSD demod server: fixed Debian build --- pluginssrv/channelrx/demoddsd/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/pluginssrv/channelrx/demoddsd/CMakeLists.txt b/pluginssrv/channelrx/demoddsd/CMakeLists.txt index 79c481a00..ae646e045 100644 --- a/pluginssrv/channelrx/demoddsd/CMakeLists.txt +++ b/pluginssrv/channelrx/demoddsd/CMakeLists.txt @@ -23,6 +23,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBDSDCCSRC} ${LIBMBELIBSRC} ) From 878b05c819a1fc9402c26adf03f6b409480590f2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 10 Jun 2018 00:02:51 +0200 Subject: [PATCH 518/956] Windows build: fixed icon --- CMakeLists.txt | 2 +- sdrgui/resources/sdrangel.rc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f3b26d126..e800c432e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -252,7 +252,7 @@ set(sdrangel_SOURCES ) if(WIN32) - SET(sdrangel_SOURCES ${sdrangel_SOURCES} sdrbase/resources/sdrangel.rc) + SET(sdrangel_SOURCES ${sdrangel_SOURCES} sdrgui/resources/sdrangel.rc) endif(WIN32) add_executable(sdrangel diff --git a/sdrgui/resources/sdrangel.rc b/sdrgui/resources/sdrangel.rc index b943fc309..1c791ede4 100644 --- a/sdrgui/resources/sdrangel.rc +++ b/sdrgui/resources/sdrangel.rc @@ -1 +1 @@ -IDI_ICON1 ICON DISCARDABLE "appicon.ico" +IDI_ICON1 ICON DISCARDABLE "sdrangel_icon.ico" From f76fafe8263c7313f761f986a869ed31133f4711 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 10 Jun 2018 12:17:26 +0200 Subject: [PATCH 519/956] Windows build fix --- .../{chanalyzerng.pro => chanalyzer.pro} | 20 +++++++++---------- plugins/channelrx/demodbfm/demodbfm.pro | 2 ++ plugins/channelrx/demoddsd/demoddsd.pro | 2 ++ plugins/channelrx/demodssb/demodssb.pro | 2 ++ plugins/channelrx/demodwfm/demodwfm.pro | 2 ++ plugins/channelrx/udpsrc/udpsrc.pro | 2 ++ sdrangel.windows.pro | 2 -- 7 files changed, 20 insertions(+), 12 deletions(-) rename plugins/channelrx/chanalyzer/{chanalyzerng.pro => chanalyzer.pro} (77%) diff --git a/plugins/channelrx/chanalyzer/chanalyzerng.pro b/plugins/channelrx/chanalyzer/chanalyzer.pro similarity index 77% rename from plugins/channelrx/chanalyzer/chanalyzerng.pro rename to plugins/channelrx/chanalyzer/chanalyzer.pro index 0960426e8..2731e8062 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerng.pro +++ b/plugins/channelrx/chanalyzer/chanalyzer.pro @@ -9,7 +9,7 @@ CONFIG += plugin QT += core gui widgets multimedia opengl -TARGET = chanalyzerng +TARGET = chanalyzer DEFINES += USE_SSE2=1 QMAKE_CXXFLAGS += -msse2 @@ -30,18 +30,18 @@ CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -SOURCES += chanalyzerng.cpp\ - chanalyzernggui.cpp\ - chanalyzerngplugin.cpp\ - chanalyzerngsettings.cpp\ +SOURCES += chanalyzer.cpp\ + chanalyzergui.cpp\ + chanalyzerplugin.cpp\ + chanalyzersettings.cpp\ -HEADERS += chanalyzerng.h\ - chanalyzernggui.h\ - chanalyzerngplugin.h\ - chanalyzerngplugin.h +HEADERS += chanalyzer.h\ + chanalyzergui.h\ + chanalyzerplugin.h\ + chanalyzerplugin.h -FORMS += chanalyzernggui.ui +FORMS += chanalyzergui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui diff --git a/plugins/channelrx/demodbfm/demodbfm.pro b/plugins/channelrx/demodbfm/demodbfm.pro index f0a75c3c3..7bcca3657 100644 --- a/plugins/channelrx/demodbfm/demodbfm.pro +++ b/plugins/channelrx/demodbfm/demodbfm.pro @@ -21,6 +21,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" @@ -52,5 +53,6 @@ FORMS += bfmdemodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/demoddsd/demoddsd.pro b/plugins/channelrx/demoddsd/demoddsd.pro index fb0c31393..8ea93c839 100644 --- a/plugins/channelrx/demoddsd/demoddsd.pro +++ b/plugins/channelrx/demoddsd/demoddsd.pro @@ -33,6 +33,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client INCLUDEPATH += $$LIBDSDCCSRC INCLUDEPATH += $$LIBMBELIBSRC @@ -60,6 +61,7 @@ dsdstatustextdialog.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger LIBS += -L../../../dsdcc/$${build_subdir} -ldsdcc RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/demodssb/demodssb.pro b/plugins/channelrx/demodssb/demodssb.pro index c9612f7d6..9fa689509 100644 --- a/plugins/channelrx/demodssb/demodssb.pro +++ b/plugins/channelrx/demodssb/demodssb.pro @@ -21,6 +21,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -39,5 +40,6 @@ FORMS += ssbdemodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/demodwfm/demodwfm.pro b/plugins/channelrx/demodwfm/demodwfm.pro index 4952543b4..ebb9c8859 100644 --- a/plugins/channelrx/demodwfm/demodwfm.pro +++ b/plugins/channelrx/demodwfm/demodwfm.pro @@ -21,6 +21,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -39,5 +40,6 @@ FORMS += wfmdemodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/udpsrc/udpsrc.pro b/plugins/channelrx/udpsrc/udpsrc.pro index f78274edd..e8843d37b 100644 --- a/plugins/channelrx/udpsrc/udpsrc.pro +++ b/plugins/channelrx/udpsrc/udpsrc.pro @@ -21,6 +21,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -39,5 +40,6 @@ FORMS += udpsrcgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index 8bf10973c..2d0516235 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -47,7 +47,6 @@ SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput SUBDIRS += plugins/channelrx/chanalyzer -SUBDIRS += plugins/channelrx/chanalyzerng SUBDIRS += plugins/channelrx/demodam SUBDIRS += plugins/channelrx/demodatv SUBDIRS += plugins/channelrx/demodbfm @@ -56,7 +55,6 @@ SUBDIRS += plugins/channelrx/demodlora SUBDIRS += plugins/channelrx/demodnfm SUBDIRS += plugins/channelrx/demodssb SUBDIRS += plugins/channelrx/demodwfm -SUBDIRS += plugins/channelrx/tcpsrc SUBDIRS += plugins/channelrx/udpsrc SUBDIRS += plugins/channeltx/modam SUBDIRS += plugins/channeltx/modnfm From 1eb8716a9f70ed0253bb65dafae55025649be8af Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 13 Jun 2018 00:20:51 +0200 Subject: [PATCH 520/956] Fixes for Arch. Manual merge of pull request #183. DATV demod: fixed some of ffmpeg deprecation warnings --- CMakeLists.txt | 8 +- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- httpserver/CMakeLists.txt | 2 +- logging/CMakeLists.txt | 2 +- plugins/channelrx/chanalyzer/CMakeLists.txt | 2 +- plugins/channelrx/demodam/CMakeLists.txt | 4 +- plugins/channelrx/demodatv/CMakeLists.txt | 2 +- plugins/channelrx/demodbfm/CMakeLists.txt | 2 +- plugins/channelrx/demoddatv/CMakeLists.txt | 3 +- .../channelrx/demoddatv/datvdemodplugin.cpp | 2 +- .../channelrx/demoddatv/datvideorender.cpp | 37 ++++++- plugins/channelrx/demoddatv/datvideorender.h | 2 + plugins/channelrx/demoddsd/CMakeLists.txt | 2 +- plugins/channelrx/demodlora/CMakeLists.txt | 2 +- plugins/channelrx/demodnfm/CMakeLists.txt | 2 +- plugins/channelrx/demodssb/CMakeLists.txt | 2 +- plugins/channelrx/demodwfm/CMakeLists.txt | 4 +- plugins/channelrx/udpsrc/CMakeLists.txt | 2 +- plugins/channeltx/modam/CMakeLists.txt | 101 +++++++++--------- plugins/channeltx/modatv/CMakeLists.txt | 4 +- plugins/channeltx/modnfm/CMakeLists.txt | 4 +- plugins/channeltx/modssb/CMakeLists.txt | 6 +- plugins/channeltx/modwfm/CMakeLists.txt | 4 +- plugins/channeltx/udpsink/CMakeLists.txt | 2 +- .../samplesink/bladerfoutput/CMakeLists.txt | 2 +- plugins/samplesink/filesink/CMakeLists.txt | 2 +- .../samplesink/hackrfoutput/CMakeLists.txt | 2 +- .../samplesink/limesdroutput/CMakeLists.txt | 2 +- .../samplesink/sdrdaemonsink/CMakeLists.txt | 2 +- plugins/samplesource/airspy/CMakeLists.txt | 2 +- .../samplesource/bladerfinput/CMakeLists.txt | 2 +- plugins/samplesource/fcdpro/CMakeLists.txt | 4 +- .../samplesource/fcdproplus/CMakeLists.txt | 2 +- .../samplesource/filesource/CMakeLists.txt | 2 +- .../samplesource/hackrfinput/CMakeLists.txt | 2 +- .../samplesource/limesdrinput/CMakeLists.txt | 2 +- plugins/samplesource/rtlsdr/CMakeLists.txt | 2 +- .../sdrdaemonsource/CMakeLists.txt | 2 +- plugins/samplesource/sdrplay/CMakeLists.txt | 2 +- .../samplesource/testsource/CMakeLists.txt | 2 +- pluginssrv/channelrx/demodam/CMakeLists.txt | 2 +- pluginssrv/channelrx/demodbfm/CMakeLists.txt | 2 +- pluginssrv/channelrx/demoddsd/CMakeLists.txt | 2 +- pluginssrv/channelrx/demodnfm/CMakeLists.txt | 2 +- pluginssrv/channelrx/demodssb/CMakeLists.txt | 2 +- pluginssrv/channelrx/demodwfm/CMakeLists.txt | 2 +- pluginssrv/channelrx/udpsrc/CMakeLists.txt | 2 +- pluginssrv/channeltx/modam/CMakeLists.txt | 2 +- pluginssrv/channeltx/modatv/CMakeLists.txt | 2 +- pluginssrv/channeltx/modnfm/CMakeLists.txt | 2 +- pluginssrv/channeltx/modssb/CMakeLists.txt | 2 +- pluginssrv/channeltx/modwfm/CMakeLists.txt | 2 +- pluginssrv/channeltx/udpsink/CMakeLists.txt | 2 +- .../samplesink/bladerfoutput/CMakeLists.txt | 2 +- pluginssrv/samplesink/filesink/CMakeLists.txt | 2 +- .../samplesink/hackrfoutput/CMakeLists.txt | 2 +- .../samplesink/limesdroutput/CMakeLists.txt | 2 +- .../samplesink/sdrdaemonsink/CMakeLists.txt | 2 +- pluginssrv/samplesource/airspy/CMakeLists.txt | 2 +- .../samplesource/bladerfinput/CMakeLists.txt | 2 +- pluginssrv/samplesource/fcdpro/CMakeLists.txt | 2 +- .../samplesource/fcdproplus/CMakeLists.txt | 2 +- .../samplesource/filesource/CMakeLists.txt | 2 +- .../samplesource/hackrfinput/CMakeLists.txt | 2 +- .../samplesource/limesdrinput/CMakeLists.txt | 2 +- pluginssrv/samplesource/rtlsdr/CMakeLists.txt | 2 +- .../sdrdaemonsource/CMakeLists.txt | 2 +- .../samplesource/sdrplay/CMakeLists.txt | 2 +- .../samplesource/testsource/CMakeLists.txt | 2 +- qrtplib/CMakeLists.txt | 2 +- sdrbase/CMakeLists.txt | 2 +- sdrbench/CMakeLists.txt | 2 +- sdrgui/CMakeLists.txt | 2 +- sdrsrv/CMakeLists.txt | 2 +- swagger/CMakeLists.txt | 2 +- 77 files changed, 173 insertions(+), 138 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e800c432e..133ef301d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ set(CMAKE_AUTOMOC ON) find_package(Qt5Core 5.0 REQUIRED) find_package(Qt5Widgets 5.0 REQUIRED) find_package(Qt5Multimedia 5.0 REQUIRED) -#find_package(QT5OpenGL 5.0 REQUIRED) +find_package(Qt5OpenGL 5.0 REQUIRED) find_package(OpenGL REQUIRED) find_package(PkgConfig) find_package(Boost REQUIRED) @@ -276,7 +276,7 @@ if(WIN32) set_target_properties(sdrangel PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") endif(WIN32) -qt5_use_modules(sdrangel Widgets Multimedia) +target_link_libraries(sdrangel Qt5::Widgets Qt5::Multimedia) ############################################################################## # main server application @@ -300,7 +300,7 @@ target_link_libraries(sdrangelsrv ${QT_LIBRARIES} ) -qt5_use_modules(sdrangelsrv Multimedia) +target_link_libraries(sdrangelsrv Qt5::Multimedia) ############################################################################## # main benchmark application @@ -324,7 +324,7 @@ target_link_libraries(sdrangelbench ) target_compile_features(sdrangelbench PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 -qt5_use_modules(sdrangelbench Multimedia) +target_link_libraries(sdrangelbench Qt5::Multimedia) ############################################################################## diff --git a/app/main.cpp b/app/main.cpp index 7a414c771..f03f084de 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.0.0"); + QCoreApplication::setApplicationVersion("4.0.1"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 97b7a8f41..004d2192c 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.0.0"); + QCoreApplication::setApplicationVersion("4.0.1"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index b3e3b1cde..120aa16d9 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.0.0"); + QCoreApplication::setApplicationVersion("4.0.1"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/httpserver/CMakeLists.txt b/httpserver/CMakeLists.txt index 72721e406..1b8341028 100644 --- a/httpserver/CMakeLists.txt +++ b/httpserver/CMakeLists.txt @@ -50,6 +50,6 @@ target_link_libraries(httpserver ${QT_LIBRARIES} ) -qt5_use_modules(httpserver Core Network) +target_link_libraries(httpserver Qt5::Core Qt5::Network) install(TARGETS httpserver DESTINATION lib) diff --git a/logging/CMakeLists.txt b/logging/CMakeLists.txt index b26835edb..5208d4326 100644 --- a/logging/CMakeLists.txt +++ b/logging/CMakeLists.txt @@ -36,6 +36,6 @@ target_link_libraries(logging ${QT_LIBRARIES} ) -qt5_use_modules(logging Core Network) +target_link_libraries(logging Qt5::Core Qt5::Network) install(TARGETS logging DESTINATION lib) diff --git a/plugins/channelrx/chanalyzer/CMakeLists.txt b/plugins/channelrx/chanalyzer/CMakeLists.txt index b749209a4..a33c29a51 100644 --- a/plugins/channelrx/chanalyzer/CMakeLists.txt +++ b/plugins/channelrx/chanalyzer/CMakeLists.txt @@ -43,6 +43,6 @@ target_link_libraries(chanalyzer sdrgui ) -qt5_use_modules(chanalyzer Core Widgets ) +target_link_libraries(chanalyzer Qt5::Core Qt5::Widgets) install(TARGETS chanalyzer DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodam/CMakeLists.txt b/plugins/channelrx/demodam/CMakeLists.txt index adbc2d1e4..602e4a140 100644 --- a/plugins/channelrx/demodam/CMakeLists.txt +++ b/plugins/channelrx/demodam/CMakeLists.txt @@ -49,6 +49,6 @@ target_link_libraries(demodam sdrgui ) -qt5_use_modules(demodam Core Widgets) +target_link_libraries(demodam Qt5::Core Qt5::Widgets) -install(TARGETS demodam DESTINATION lib/plugins/channelrx) \ No newline at end of file +install(TARGETS demodam DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodatv/CMakeLists.txt b/plugins/channelrx/demodatv/CMakeLists.txt index 5683fbbad..7665ee799 100644 --- a/plugins/channelrx/demodatv/CMakeLists.txt +++ b/plugins/channelrx/demodatv/CMakeLists.txt @@ -43,6 +43,6 @@ target_link_libraries(demodatv sdrgui ) -qt5_use_modules(demodatv Core Widgets) +target_link_libraries(demodatv Qt5::Core Qt5::Widgets) install(TARGETS demodatv DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodbfm/CMakeLists.txt b/plugins/channelrx/demodbfm/CMakeLists.txt index a80c86d56..71721706c 100644 --- a/plugins/channelrx/demodbfm/CMakeLists.txt +++ b/plugins/channelrx/demodbfm/CMakeLists.txt @@ -57,6 +57,6 @@ target_link_libraries(demodbfm sdrgui ) -qt5_use_modules(demodbfm Core Widgets) +target_link_libraries(demodbfm Qt5::Core Qt5::Widgets) install(TARGETS demodbfm DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt index bf41c23f0..a4bfb1b0e 100644 --- a/plugins/channelrx/demoddatv/CMakeLists.txt +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -53,6 +53,7 @@ target_link_libraries(demoddatv ${SWSCALE_LIBRARIES} ) -qt5_use_modules(demoddatv Core Widgets Multimedia MultimediaWidgets) +find_package(Qt5MultimediaWidgets 5.0 REQUIRED) +target_link_libraries(demoddatv Qt5::Core Qt5::Widgets Qt5::Multimedia Qt5::MultimediaWidgets) install(TARGETS demoddatv DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demoddatv/datvdemodplugin.cpp b/plugins/channelrx/demoddatv/datvdemodplugin.cpp index 2ac38cf0d..346a898ff 100644 --- a/plugins/channelrx/demoddatv/datvdemodplugin.cpp +++ b/plugins/channelrx/demoddatv/datvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DATVDemodPlugin::m_ptrPluginDescriptor = { QString("DATV Demodulator"), - QString("3.14.5"), + QString("4.0.1"), QString("(c) F4HKW for SDRAngel using LeanSDR framework (c) F4DAV"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddatv/datvideorender.cpp b/plugins/channelrx/demoddatv/datvideorender.cpp index 32aa01f9e..15924f548 100644 --- a/plugins/channelrx/demoddatv/datvideorender.cpp +++ b/plugins/channelrx/demoddatv/datvideorender.cpp @@ -180,6 +180,10 @@ bool DATVideoRender::PreprocessStream() //Prepare Codec and extract meta data + // FIXME: codec is depreecated but replacement fails +// AVCodecParameters *parms = m_objFormatCtx->streams[m_intVideoStreamIndex]->codecpar; +// m_objDecoderCtx = new AVCodecContext(); +// avcodec_parameters_to_context(m_objDecoderCtx, parms); m_objDecoderCtx = m_objFormatCtx->streams[m_intVideoStreamIndex]->codec; //Meta Data @@ -341,7 +345,7 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) return false; } - ptrIOBuffer = (unsigned char *)av_malloc(intIOBufferSize+ FF_INPUT_BUFFER_PADDING_SIZE); + ptrIOBuffer = (unsigned char *)av_malloc(intIOBufferSize+ AV_INPUT_BUFFER_PADDING_SIZE); objIOCtx = avio_alloc_context( ptrIOBuffer, intIOBufferSize, @@ -416,7 +420,7 @@ bool DATVideoRender::RenderStream() intGotFrame=0; - if(avcodec_decode_video2( m_objDecoderCtx, m_objFrame, &intGotFrame, &objPacket)<0) + if(new_decode( m_objDecoderCtx, m_objFrame, &intGotFrame, &objPacket)<0) { qDebug() << "DATVideoProcess::RenderStream decoding packet error"; @@ -520,7 +524,7 @@ bool DATVideoRender::RenderStream() } - av_free_packet(&objPacket); + av_packet_unref(&objPacket); //********** Rendering ********** @@ -597,3 +601,30 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) return true; } + +/** + * Replacement of deprecated avcodec_decode_video2 with the same signature + * https://blogs.gentoo.org/lu_zero/2016/03/29/new-avcodec-api/ + */ +int DATVideoRender::new_decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt) +{ + int ret; + + *got_frame = 0; + + if (pkt) { + ret = avcodec_send_packet(avctx, pkt); + // In particular, we don't expect AVERROR(EAGAIN), because we read all + // decoded frames with avcodec_receive_frame() until done. + if (ret < 0) + return ret == AVERROR_EOF ? 0 : ret; + } + + ret = avcodec_receive_frame(avctx, frame); + if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) + return ret; + if (ret >= 0) + *got_frame = 1; + + return 0; +} diff --git a/plugins/channelrx/demoddatv/datvideorender.h b/plugins/channelrx/demoddatv/datvideorender.h index 5603b6497..02dddfb3d 100644 --- a/plugins/channelrx/demoddatv/datvideorender.h +++ b/plugins/channelrx/demoddatv/datvideorender.h @@ -124,6 +124,8 @@ private: bool PreprocessStream(); void ResetMetaData(); + int new_decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt); + protected: virtual bool eventFilter(QObject *obj, QEvent *event); diff --git a/plugins/channelrx/demoddsd/CMakeLists.txt b/plugins/channelrx/demoddsd/CMakeLists.txt index e862aa291..5932da235 100644 --- a/plugins/channelrx/demoddsd/CMakeLists.txt +++ b/plugins/channelrx/demoddsd/CMakeLists.txt @@ -77,6 +77,6 @@ target_link_libraries(demoddsd endif (BUILD_DEBIAN) -qt5_use_modules(demoddsd Core Widgets) +target_link_libraries(demoddsd Qt5::Core Qt5::Widgets) install(TARGETS demoddsd DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodlora/CMakeLists.txt b/plugins/channelrx/demodlora/CMakeLists.txt index d8076dac0..a2ced900c 100644 --- a/plugins/channelrx/demodlora/CMakeLists.txt +++ b/plugins/channelrx/demodlora/CMakeLists.txt @@ -43,6 +43,6 @@ target_link_libraries(demodlora sdrgui ) -qt5_use_modules(demodlora Core Widgets) +target_link_libraries(demodlora Qt5::Core Qt5::Widgets) install(TARGETS demodlora DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodnfm/CMakeLists.txt b/plugins/channelrx/demodnfm/CMakeLists.txt index 3efccfd31..3e405ea4f 100644 --- a/plugins/channelrx/demodnfm/CMakeLists.txt +++ b/plugins/channelrx/demodnfm/CMakeLists.txt @@ -47,6 +47,6 @@ target_link_libraries(demodnfm swagger ) -qt5_use_modules(demodnfm Core Widgets) +target_link_libraries(demodnfm Qt5::Core Qt5::Widgets) install(TARGETS demodnfm DESTINATION lib/plugins/channelrx) \ No newline at end of file diff --git a/plugins/channelrx/demodssb/CMakeLists.txt b/plugins/channelrx/demodssb/CMakeLists.txt index 514f1e01b..d1c095eb0 100644 --- a/plugins/channelrx/demodssb/CMakeLists.txt +++ b/plugins/channelrx/demodssb/CMakeLists.txt @@ -46,6 +46,6 @@ target_link_libraries(demodssb sdrgui ) -qt5_use_modules(demodssb Core Widgets) +target_link_libraries(demodssb Qt5::Core Qt5::Widgets) install(TARGETS demodssb DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodwfm/CMakeLists.txt b/plugins/channelrx/demodwfm/CMakeLists.txt index 914a448ff..a2f04465c 100644 --- a/plugins/channelrx/demodwfm/CMakeLists.txt +++ b/plugins/channelrx/demodwfm/CMakeLists.txt @@ -46,6 +46,6 @@ target_link_libraries(demodwfm sdrgui ) -qt5_use_modules(demodwfm Core Widgets) +target_link_libraries(demodwfm Qt5::Core Qt5::Widgets) -install(TARGETS demodwfm DESTINATION lib/plugins/channelrx) \ No newline at end of file +install(TARGETS demodwfm DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/udpsrc/CMakeLists.txt b/plugins/channelrx/udpsrc/CMakeLists.txt index d4ccd6a79..c73e0f38b 100644 --- a/plugins/channelrx/udpsrc/CMakeLists.txt +++ b/plugins/channelrx/udpsrc/CMakeLists.txt @@ -45,6 +45,6 @@ target_link_libraries(demodudpsrc sdrgui ) -qt5_use_modules(demodudpsrc Core Widgets Network) +target_link_libraries(demodudpsrc Qt5::Core Qt5::Widgets Qt5::Network) install(TARGETS demodudpsrc DESTINATION lib/plugins/channelrx) diff --git a/plugins/channeltx/modam/CMakeLists.txt b/plugins/channeltx/modam/CMakeLists.txt index a9ac4cc4c..eb2d22c4d 100644 --- a/plugins/channeltx/modam/CMakeLists.txt +++ b/plugins/channeltx/modam/CMakeLists.txt @@ -1,50 +1,51 @@ -project(modam) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - -set(modam_SOURCES - ammod.cpp - ammodgui.cpp - ammodplugin.cpp - ammodsettings.cpp -) - -set(modam_HEADERS - ammod.h - ammodgui.h - ammodplugin.h - ammodsettings.h -) - -set(modam_FORMS - ammodgui.ui -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client -) - -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -qt5_wrap_ui(modam_FORMS_HEADERS ${modam_FORMS}) - -add_library(modam SHARED - ${modam_SOURCES} - ${modam_HEADERS_MOC} - ${modam_FORMS_HEADERS} -) - -target_link_libraries(modam - ${QT_LIBRARIES} - sdrbase - sdrgui - swagger -) - -qt5_use_modules(modam Core Widgets) - -install(TARGETS modam DESTINATION lib/plugins/channeltx) \ No newline at end of file +project(modam) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(modam_SOURCES + ammod.cpp + ammodgui.cpp + ammodplugin.cpp + ammodsettings.cpp +) + +set(modam_HEADERS + ammod.h + ammodgui.h + ammodplugin.h + ammodsettings.h +) + +set(modam_FORMS + ammodgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(modam_FORMS_HEADERS ${modam_FORMS}) + +add_library(modam SHARED + ${modam_SOURCES} + ${modam_HEADERS_MOC} + ${modam_FORMS_HEADERS} +) + +target_link_libraries(modam + ${QT_LIBRARIES} + sdrbase + sdrgui + swagger +) + +target_link_libraries(modam Qt5::Core Qt5::Widgets) + +install(TARGETS modam DESTINATION lib/plugins/channeltx) + diff --git a/plugins/channeltx/modatv/CMakeLists.txt b/plugins/channeltx/modatv/CMakeLists.txt index 21aad47e1..f3ef7ac29 100644 --- a/plugins/channeltx/modatv/CMakeLists.txt +++ b/plugins/channeltx/modatv/CMakeLists.txt @@ -49,6 +49,6 @@ target_link_libraries(modatv swagger ) -qt5_use_modules(modatv Core Widgets) +target_link_libraries(modatv Qt5::Core Qt5::Widgets) -install(TARGETS modatv DESTINATION lib/plugins/channeltx) \ No newline at end of file +install(TARGETS modatv DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/modnfm/CMakeLists.txt b/plugins/channeltx/modnfm/CMakeLists.txt index f7060fcd0..5355274b6 100644 --- a/plugins/channeltx/modnfm/CMakeLists.txt +++ b/plugins/channeltx/modnfm/CMakeLists.txt @@ -45,6 +45,6 @@ target_link_libraries(modnfm swagger ) -qt5_use_modules(modnfm Core Widgets) +target_link_libraries(modnfm Qt5::Core Qt5::Widgets) -install(TARGETS modnfm DESTINATION lib/plugins/channeltx) \ No newline at end of file +install(TARGETS modnfm DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/modssb/CMakeLists.txt b/plugins/channeltx/modssb/CMakeLists.txt index 631900497..cfb683961 100644 --- a/plugins/channeltx/modssb/CMakeLists.txt +++ b/plugins/channeltx/modssb/CMakeLists.txt @@ -42,9 +42,9 @@ target_link_libraries(modssb ${QT_LIBRARIES} sdrbase sdrgui - swagger + swagger ) -qt5_use_modules(modssb Core Widgets) +target_link_libraries(modssb Qt5::Core Qt5::Widgets) -install(TARGETS modssb DESTINATION lib/plugins/channeltx) \ No newline at end of file +install(TARGETS modssb DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/modwfm/CMakeLists.txt b/plugins/channeltx/modwfm/CMakeLists.txt index e9d104f88..800e33353 100644 --- a/plugins/channeltx/modwfm/CMakeLists.txt +++ b/plugins/channeltx/modwfm/CMakeLists.txt @@ -45,6 +45,6 @@ target_link_libraries(modwfm swagger ) -qt5_use_modules(modwfm Core Widgets) +target_link_libraries(modwfm Qt5::Core Qt5::Widgets) -install(TARGETS modwfm DESTINATION lib/plugins/channeltx) \ No newline at end of file +install(TARGETS modwfm DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/udpsink/CMakeLists.txt b/plugins/channeltx/udpsink/CMakeLists.txt index d278e2484..760a55c14 100644 --- a/plugins/channeltx/udpsink/CMakeLists.txt +++ b/plugins/channeltx/udpsink/CMakeLists.txt @@ -50,6 +50,6 @@ target_link_libraries(modudpsink swagger ) -qt5_use_modules(modudpsink Core Widgets Network) +target_link_libraries(modudpsink Qt5::Core Qt5::Widgets Qt5::Network) install(TARGETS modudpsink DESTINATION lib/plugins/channeltx) diff --git a/plugins/samplesink/bladerfoutput/CMakeLists.txt b/plugins/samplesink/bladerfoutput/CMakeLists.txt index 0f4d99354..719a7442e 100644 --- a/plugins/samplesink/bladerfoutput/CMakeLists.txt +++ b/plugins/samplesink/bladerfoutput/CMakeLists.txt @@ -75,6 +75,6 @@ target_link_libraries(outputbladerf ) endif (BUILD_DEBIAN) -qt5_use_modules(outputbladerf Core Widgets) +target_link_libraries(outputbladerf Qt5::Core Qt5::Widgets) install(TARGETS outputbladerf DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/filesink/CMakeLists.txt b/plugins/samplesink/filesink/CMakeLists.txt index e8ddcf468..ccedfe7af 100644 --- a/plugins/samplesink/filesink/CMakeLists.txt +++ b/plugins/samplesink/filesink/CMakeLists.txt @@ -47,6 +47,6 @@ target_link_libraries(outputfilesink swagger ) -qt5_use_modules(outputfilesink Core Widgets) +target_link_libraries(outputfilesink Qt5::Core Qt5::Widgets) install(TARGETS outputfilesink DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/hackrfoutput/CMakeLists.txt b/plugins/samplesink/hackrfoutput/CMakeLists.txt index 7a0ea9af9..87bbe5234 100644 --- a/plugins/samplesink/hackrfoutput/CMakeLists.txt +++ b/plugins/samplesink/hackrfoutput/CMakeLists.txt @@ -76,6 +76,6 @@ target_link_libraries(outputhackrf ) endif (BUILD_DEBIAN) -qt5_use_modules(outputhackrf Core Widgets) +target_link_libraries(outputhackrf Qt5::Core Qt5::Widgets) install(TARGETS outputhackrf DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/limesdroutput/CMakeLists.txt b/plugins/samplesink/limesdroutput/CMakeLists.txt index 8b92052e2..eed28022d 100644 --- a/plugins/samplesink/limesdroutput/CMakeLists.txt +++ b/plugins/samplesink/limesdroutput/CMakeLists.txt @@ -82,6 +82,6 @@ target_link_libraries(outputlimesdr ) endif (BUILD_DEBIAN) -qt5_use_modules(outputlimesdr Core Widgets) +target_link_libraries(outputlimesdr Qt5::Core Qt5::Widgets) install(TARGETS outputlimesdr DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt index a18319650..0d07ae186 100644 --- a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt +++ b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt @@ -86,6 +86,6 @@ target_link_libraries(outputsdrdaemonsink ) endif (BUILD_DEBIAN) -qt5_use_modules(outputsdrdaemonsink Core Widgets) +target_link_libraries(outputsdrdaemonsink Qt5::Core Qt5::Widgets) install(TARGETS outputsdrdaemonsink DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesource/airspy/CMakeLists.txt b/plugins/samplesource/airspy/CMakeLists.txt index 022530bff..9d2aa6bf4 100644 --- a/plugins/samplesource/airspy/CMakeLists.txt +++ b/plugins/samplesource/airspy/CMakeLists.txt @@ -73,6 +73,6 @@ target_link_libraries(inputairspy endif (BUILD_DEBIAN) -qt5_use_modules(inputairspy Core Widgets) +target_link_libraries(inputairspy Qt5::Core Qt5::Widgets) install(TARGETS inputairspy DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/bladerfinput/CMakeLists.txt b/plugins/samplesource/bladerfinput/CMakeLists.txt index 2daf77264..943e3a7df 100644 --- a/plugins/samplesource/bladerfinput/CMakeLists.txt +++ b/plugins/samplesource/bladerfinput/CMakeLists.txt @@ -75,6 +75,6 @@ target_link_libraries(inputbladerf ) endif (BUILD_DEBIAN) -qt5_use_modules(inputbladerf Core Widgets) +target_link_libraries(inputbladerf Qt5::Core Qt5::Widgets) install(TARGETS inputbladerf DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/fcdpro/CMakeLists.txt b/plugins/samplesource/fcdpro/CMakeLists.txt index 5441a76e9..a3270955f 100644 --- a/plugins/samplesource/fcdpro/CMakeLists.txt +++ b/plugins/samplesource/fcdpro/CMakeLists.txt @@ -54,6 +54,6 @@ target_link_libraries(inputfcdpro swagger ) -qt5_use_modules(inputfcdpro Core Widgets) +target_link_libraries(inputfcdpro Qt5::Core Qt5::Widgets) -install(TARGETS inputfcdpro DESTINATION lib/plugins/samplesource) \ No newline at end of file +install(TARGETS inputfcdpro DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/fcdproplus/CMakeLists.txt b/plugins/samplesource/fcdproplus/CMakeLists.txt index b9dd191bd..0b94f9658 100644 --- a/plugins/samplesource/fcdproplus/CMakeLists.txt +++ b/plugins/samplesource/fcdproplus/CMakeLists.txt @@ -54,6 +54,6 @@ target_link_libraries(inputfcdproplus swagger ) -qt5_use_modules(inputfcdproplus Core Widgets) +target_link_libraries(inputfcdproplus Qt5::Core Qt5::Widgets) install(TARGETS inputfcdproplus DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/filesource/CMakeLists.txt b/plugins/samplesource/filesource/CMakeLists.txt index 6984dd483..c467096d2 100644 --- a/plugins/samplesource/filesource/CMakeLists.txt +++ b/plugins/samplesource/filesource/CMakeLists.txt @@ -49,6 +49,6 @@ target_link_libraries(inputfilesource swagger ) -qt5_use_modules(inputfilesource Core Widgets) +target_link_libraries(inputfilesource Qt5::Core Qt5::Widgets) install(TARGETS inputfilesource DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/hackrfinput/CMakeLists.txt b/plugins/samplesource/hackrfinput/CMakeLists.txt index 98881b3d1..f8946515e 100644 --- a/plugins/samplesource/hackrfinput/CMakeLists.txt +++ b/plugins/samplesource/hackrfinput/CMakeLists.txt @@ -76,6 +76,6 @@ target_link_libraries(inputhackrf ) endif (BUILD_DEBIAN) -qt5_use_modules(inputhackrf Core Widgets) +target_link_libraries(inputhackrf Qt5::Core Qt5::Widgets) install(TARGETS inputhackrf DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/limesdrinput/CMakeLists.txt b/plugins/samplesource/limesdrinput/CMakeLists.txt index b5d9fd215..6bf0d5602 100644 --- a/plugins/samplesource/limesdrinput/CMakeLists.txt +++ b/plugins/samplesource/limesdrinput/CMakeLists.txt @@ -82,6 +82,6 @@ target_link_libraries(inputlimesdr ) endif (BUILD_DEBIAN) -qt5_use_modules(inputlimesdr Core Widgets) +target_link_libraries(inputlimesdr Qt5::Core Qt5::Widgets) install(TARGETS inputlimesdr DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/rtlsdr/CMakeLists.txt b/plugins/samplesource/rtlsdr/CMakeLists.txt index 5ce6daa93..918d5641b 100644 --- a/plugins/samplesource/rtlsdr/CMakeLists.txt +++ b/plugins/samplesource/rtlsdr/CMakeLists.txt @@ -72,6 +72,6 @@ target_link_libraries(inputrtlsdr endif (BUILD_DEBIAN) -qt5_use_modules(inputrtlsdr Core Widgets) +target_link_libraries(inputrtlsdr Qt5::Core Qt5::Widgets) install(TARGETS inputrtlsdr DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/sdrdaemonsource/CMakeLists.txt b/plugins/samplesource/sdrdaemonsource/CMakeLists.txt index 477561e9b..e134b5200 100644 --- a/plugins/samplesource/sdrdaemonsource/CMakeLists.txt +++ b/plugins/samplesource/sdrdaemonsource/CMakeLists.txt @@ -85,6 +85,6 @@ target_link_libraries(inputsdrdaemonsource ) endif (BUILD_DEBIAN) -qt5_use_modules(inputsdrdaemonsource Core Widgets) +target_link_libraries(inputsdrdaemonsource Qt5::Core Qt5::Widgets) install(TARGETS inputsdrdaemonsource DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/sdrplay/CMakeLists.txt b/plugins/samplesource/sdrplay/CMakeLists.txt index aa9464e53..13d79ee72 100644 --- a/plugins/samplesource/sdrplay/CMakeLists.txt +++ b/plugins/samplesource/sdrplay/CMakeLists.txt @@ -68,6 +68,6 @@ target_link_libraries(inputsdrplay ) endif (BUILD_DEBIAN) -qt5_use_modules(inputsdrplay Core Widgets) +target_link_libraries(inputsdrplay Qt5::Core Qt5::Widgets) install(TARGETS inputsdrplay DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/testsource/CMakeLists.txt b/plugins/samplesource/testsource/CMakeLists.txt index 24fd797fc..110b29e7f 100644 --- a/plugins/samplesource/testsource/CMakeLists.txt +++ b/plugins/samplesource/testsource/CMakeLists.txt @@ -49,6 +49,6 @@ target_link_libraries(inputtestsource swagger ) -qt5_use_modules(inputtestsource Core Widgets) +target_link_libraries(inputtestsource Qt5::Core Qt5::Widgets) install(TARGETS inputtestsource DESTINATION lib/plugins/samplesource) diff --git a/pluginssrv/channelrx/demodam/CMakeLists.txt b/pluginssrv/channelrx/demodam/CMakeLists.txt index 7f0ade068..b5ba1b65e 100644 --- a/pluginssrv/channelrx/demodam/CMakeLists.txt +++ b/pluginssrv/channelrx/demodam/CMakeLists.txt @@ -37,6 +37,6 @@ target_link_libraries(demodamsrv swagger ) -qt5_use_modules(demodamsrv Core) +target_link_libraries(demodamsrv Qt5::Core) install(TARGETS demodamsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file diff --git a/pluginssrv/channelrx/demodbfm/CMakeLists.txt b/pluginssrv/channelrx/demodbfm/CMakeLists.txt index dffd1854a..f1862946b 100644 --- a/pluginssrv/channelrx/demodbfm/CMakeLists.txt +++ b/pluginssrv/channelrx/demodbfm/CMakeLists.txt @@ -50,6 +50,6 @@ target_link_libraries(demodbfmsrv swagger ) -qt5_use_modules(demodbfmsrv Core) +target_link_libraries(demodbfmsrv Qt5::Core) install(TARGETS demodbfmsrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channelrx/demoddsd/CMakeLists.txt b/pluginssrv/channelrx/demoddsd/CMakeLists.txt index ae646e045..c0a5d2571 100644 --- a/pluginssrv/channelrx/demoddsd/CMakeLists.txt +++ b/pluginssrv/channelrx/demoddsd/CMakeLists.txt @@ -64,6 +64,6 @@ target_link_libraries(demoddsdsrv endif (BUILD_DEBIAN) -qt5_use_modules(demoddsdsrv Core) +target_link_libraries(demoddsdsrv Qt5::Core) install(TARGETS demoddsdsrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channelrx/demodnfm/CMakeLists.txt b/pluginssrv/channelrx/demodnfm/CMakeLists.txt index 25c04df68..1c6f98132 100644 --- a/pluginssrv/channelrx/demodnfm/CMakeLists.txt +++ b/pluginssrv/channelrx/demodnfm/CMakeLists.txt @@ -37,6 +37,6 @@ target_link_libraries(demodnfmsrv swagger ) -qt5_use_modules(demodnfmsrv Core) +target_link_libraries(demodnfmsrv Qt5::Core) install(TARGETS demodnfmsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file diff --git a/pluginssrv/channelrx/demodssb/CMakeLists.txt b/pluginssrv/channelrx/demodssb/CMakeLists.txt index d7c87016e..a2c9e8c41 100644 --- a/pluginssrv/channelrx/demodssb/CMakeLists.txt +++ b/pluginssrv/channelrx/demodssb/CMakeLists.txt @@ -38,6 +38,6 @@ target_link_libraries(demodssbsrv sdrbase ) -qt5_use_modules(demodssbsrv Core) +target_link_libraries(demodssbsrv Qt5::Core) install(TARGETS demodssbsrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channelrx/demodwfm/CMakeLists.txt b/pluginssrv/channelrx/demodwfm/CMakeLists.txt index ef69350ad..6a1c3a73f 100644 --- a/pluginssrv/channelrx/demodwfm/CMakeLists.txt +++ b/pluginssrv/channelrx/demodwfm/CMakeLists.txt @@ -38,6 +38,6 @@ target_link_libraries(demodwfmsrv sdrbase ) -qt5_use_modules(demodwfmsrv Core) +target_link_libraries(demodwfmsrv Qt5::Core) install(TARGETS demodwfmsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file diff --git a/pluginssrv/channelrx/udpsrc/CMakeLists.txt b/pluginssrv/channelrx/udpsrc/CMakeLists.txt index 0b0efbf05..36e3d15de 100644 --- a/pluginssrv/channelrx/udpsrc/CMakeLists.txt +++ b/pluginssrv/channelrx/udpsrc/CMakeLists.txt @@ -36,6 +36,6 @@ target_link_libraries(demodudpsrcsrv sdrbase ) -qt5_use_modules(demodudpsrcsrv Core Network) +target_link_libraries(demodudpsrcsrv Qt5::Core Qt5::Network) install(TARGETS demodudpsrcsrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channeltx/modam/CMakeLists.txt b/pluginssrv/channeltx/modam/CMakeLists.txt index 5f0430448..51f6493b2 100644 --- a/pluginssrv/channeltx/modam/CMakeLists.txt +++ b/pluginssrv/channeltx/modam/CMakeLists.txt @@ -36,6 +36,6 @@ target_link_libraries(modamsrv swagger ) -qt5_use_modules(modamsrv Core) +target_link_libraries(modamsrv Qt5::Core) install(TARGETS modamsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modatv/CMakeLists.txt b/pluginssrv/channeltx/modatv/CMakeLists.txt index 4338f70fb..d67f453fa 100644 --- a/pluginssrv/channeltx/modatv/CMakeLists.txt +++ b/pluginssrv/channeltx/modatv/CMakeLists.txt @@ -40,6 +40,6 @@ target_link_libraries(modatvsrv swagger ) -qt5_use_modules(modatvsrv Core) +target_link_libraries(modatvsrv Qt5::Core) install(TARGETS modatvsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modnfm/CMakeLists.txt b/pluginssrv/channeltx/modnfm/CMakeLists.txt index 7d1eb0d6f..21af13ac5 100644 --- a/pluginssrv/channeltx/modnfm/CMakeLists.txt +++ b/pluginssrv/channeltx/modnfm/CMakeLists.txt @@ -36,6 +36,6 @@ target_link_libraries(modnfmsrv swagger ) -qt5_use_modules(modnfmsrv Core) +target_link_libraries(modnfmsrv Qt5::Core) install(TARGETS modnfmsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modssb/CMakeLists.txt b/pluginssrv/channeltx/modssb/CMakeLists.txt index 4da444e3b..c65b75bac 100644 --- a/pluginssrv/channeltx/modssb/CMakeLists.txt +++ b/pluginssrv/channeltx/modssb/CMakeLists.txt @@ -36,6 +36,6 @@ target_link_libraries(modssbsrv swagger ) -qt5_use_modules(modssbsrv Core) +target_link_libraries(modssbsrv Qt5::Core) install(TARGETS modssbsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modwfm/CMakeLists.txt b/pluginssrv/channeltx/modwfm/CMakeLists.txt index 090d63b99..e8a8e145c 100644 --- a/pluginssrv/channeltx/modwfm/CMakeLists.txt +++ b/pluginssrv/channeltx/modwfm/CMakeLists.txt @@ -36,6 +36,6 @@ target_link_libraries(modwfmsrv swagger ) -qt5_use_modules(modwfmsrv Core) +target_link_libraries(modwfmsrv Qt5::Core) install(TARGETS modwfmsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/udpsink/CMakeLists.txt b/pluginssrv/channeltx/udpsink/CMakeLists.txt index bb7f3b0f3..5f81e4025 100644 --- a/pluginssrv/channeltx/udpsink/CMakeLists.txt +++ b/pluginssrv/channeltx/udpsink/CMakeLists.txt @@ -40,6 +40,6 @@ target_link_libraries(modudpsinksrv swagger ) -qt5_use_modules(modudpsinksrv Core Network) +target_link_libraries(modudpsinksrv Qt5::Core Qt5::Network) install(TARGETS modudpsinksrv DESTINATION lib/pluginssrv/channeltx) diff --git a/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt b/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt index 7c1365553..a4f4d0cc9 100644 --- a/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt +++ b/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt @@ -63,6 +63,6 @@ target_link_libraries(outputbladerfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(outputbladerfsrv Core) +target_link_libraries(outputbladerfsrv Qt5::Core) install(TARGETS outputbladerfsrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/filesink/CMakeLists.txt b/pluginssrv/samplesink/filesink/CMakeLists.txt index b224a7b0b..3efdee02c 100644 --- a/pluginssrv/samplesink/filesink/CMakeLists.txt +++ b/pluginssrv/samplesink/filesink/CMakeLists.txt @@ -39,6 +39,6 @@ target_link_libraries(outputfilesinksrv swagger ) -qt5_use_modules(outputfilesinksrv Core) +target_link_libraries(outputfilesinksrv Qt5::Core) install(TARGETS outputfilesinksrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt b/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt index ab42a142b..fcb5af765 100644 --- a/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt +++ b/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt @@ -65,6 +65,6 @@ target_link_libraries(outputhackrfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(outputhackrfsrv Core) +target_link_libraries(outputhackrfsrv Qt5::Core) install(TARGETS outputhackrfsrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/limesdroutput/CMakeLists.txt b/pluginssrv/samplesink/limesdroutput/CMakeLists.txt index e8c5f9113..4bda2a737 100644 --- a/pluginssrv/samplesink/limesdroutput/CMakeLists.txt +++ b/pluginssrv/samplesink/limesdroutput/CMakeLists.txt @@ -73,6 +73,6 @@ target_link_libraries(outputlimesdrsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(outputlimesdrsrv Core) +target_link_libraries(outputlimesdrsrv Qt5::Core) install(TARGETS outputlimesdrsrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt index f24f23701..ff4115065 100644 --- a/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt +++ b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt @@ -76,6 +76,6 @@ target_link_libraries(outputsdrdaemonsinksrv ) endif (BUILD_DEBIAN) -qt5_use_modules(outputsdrdaemonsinksrv Core) +target_link_libraries(outputsdrdaemonsinksrv Qt5::Core) install(TARGETS outputsdrdaemonsinksrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesource/airspy/CMakeLists.txt b/pluginssrv/samplesource/airspy/CMakeLists.txt index 2ce19f4f6..cbc40b880 100644 --- a/pluginssrv/samplesource/airspy/CMakeLists.txt +++ b/pluginssrv/samplesource/airspy/CMakeLists.txt @@ -60,6 +60,6 @@ target_link_libraries(inputairspysrv endif (BUILD_DEBIAN) -qt5_use_modules(inputairspysrv Core) +target_link_libraries(inputairspysrv Qt5::Core) install(TARGETS inputairspysrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/bladerfinput/CMakeLists.txt b/pluginssrv/samplesource/bladerfinput/CMakeLists.txt index b468ae23b..78ed1c820 100644 --- a/pluginssrv/samplesource/bladerfinput/CMakeLists.txt +++ b/pluginssrv/samplesource/bladerfinput/CMakeLists.txt @@ -63,6 +63,6 @@ target_link_libraries(inputbladerfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputbladerfsrv Core) +target_link_libraries(inputbladerfsrv Qt5::Core) install(TARGETS inputbladerfsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/fcdpro/CMakeLists.txt b/pluginssrv/samplesource/fcdpro/CMakeLists.txt index ffcd0948b..44ad593ad 100644 --- a/pluginssrv/samplesource/fcdpro/CMakeLists.txt +++ b/pluginssrv/samplesource/fcdpro/CMakeLists.txt @@ -43,6 +43,6 @@ target_link_libraries(inputfcdprosrv swagger ) -qt5_use_modules(inputfcdprosrv Core) +target_link_libraries(inputfcdprosrv Qt5::Core) install(TARGETS inputfcdprosrv DESTINATION lib/pluginssrv/samplesource) \ No newline at end of file diff --git a/pluginssrv/samplesource/fcdproplus/CMakeLists.txt b/pluginssrv/samplesource/fcdproplus/CMakeLists.txt index e23a61855..623d259e8 100644 --- a/pluginssrv/samplesource/fcdproplus/CMakeLists.txt +++ b/pluginssrv/samplesource/fcdproplus/CMakeLists.txt @@ -43,6 +43,6 @@ target_link_libraries(inputfcdproplussrv swagger ) -qt5_use_modules(inputfcdproplussrv Core) +target_link_libraries(inputfcdproplussrv Qt5::Core) install(TARGETS inputfcdproplussrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/filesource/CMakeLists.txt b/pluginssrv/samplesource/filesource/CMakeLists.txt index 0f6e72d72..edf9458a5 100644 --- a/pluginssrv/samplesource/filesource/CMakeLists.txt +++ b/pluginssrv/samplesource/filesource/CMakeLists.txt @@ -40,6 +40,6 @@ target_link_libraries(inputfilesourcesrv swagger ) -qt5_use_modules(inputfilesourcesrv Core) +target_link_libraries(inputfilesourcesrv Qt5::Core) install(TARGETS inputfilesourcesrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/hackrfinput/CMakeLists.txt b/pluginssrv/samplesource/hackrfinput/CMakeLists.txt index 66f9d5673..61a8760ed 100644 --- a/pluginssrv/samplesource/hackrfinput/CMakeLists.txt +++ b/pluginssrv/samplesource/hackrfinput/CMakeLists.txt @@ -65,6 +65,6 @@ target_link_libraries(inputhackrfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputhackrfsrv Core) +target_link_libraries(inputhackrfsrv Qt5::Core) install(TARGETS inputhackrfsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/limesdrinput/CMakeLists.txt b/pluginssrv/samplesource/limesdrinput/CMakeLists.txt index b92ca4b8e..85c619849 100644 --- a/pluginssrv/samplesource/limesdrinput/CMakeLists.txt +++ b/pluginssrv/samplesource/limesdrinput/CMakeLists.txt @@ -73,6 +73,6 @@ target_link_libraries(inputlimesdrsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputlimesdrsrv Core) +target_link_libraries(inputlimesdrsrv Qt5::Core) install(TARGETS inputlimesdrsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/rtlsdr/CMakeLists.txt b/pluginssrv/samplesource/rtlsdr/CMakeLists.txt index 8e15b3095..705afd1e0 100644 --- a/pluginssrv/samplesource/rtlsdr/CMakeLists.txt +++ b/pluginssrv/samplesource/rtlsdr/CMakeLists.txt @@ -63,6 +63,6 @@ target_link_libraries(inputrtlsdrsrv endif (BUILD_DEBIAN) -qt5_use_modules(inputrtlsdrsrv Core) +target_link_libraries(inputrtlsdrsrv Qt5::Core) install(TARGETS inputrtlsdrsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt b/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt index 8d4bdd555..2e9b7eb23 100644 --- a/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt +++ b/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt @@ -74,6 +74,6 @@ target_link_libraries(inputsdrdaemonsourcesrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputsdrdaemonsourcesrv Core) +target_link_libraries(inputsdrdaemonsourcesrv Qt5::Core) install(TARGETS inputsdrdaemonsourcesrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/sdrplay/CMakeLists.txt b/pluginssrv/samplesource/sdrplay/CMakeLists.txt index 37a6d4eb8..67b3939ac 100644 --- a/pluginssrv/samplesource/sdrplay/CMakeLists.txt +++ b/pluginssrv/samplesource/sdrplay/CMakeLists.txt @@ -58,6 +58,6 @@ target_link_libraries(inputsdrplaysrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputsdrplaysrv Core) +target_link_libraries(inputsdrplaysrv Qt5::Core) install(TARGETS inputsdrplaysrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/testsource/CMakeLists.txt b/pluginssrv/samplesource/testsource/CMakeLists.txt index 0e5c96f23..8b1dc76c2 100644 --- a/pluginssrv/samplesource/testsource/CMakeLists.txt +++ b/pluginssrv/samplesource/testsource/CMakeLists.txt @@ -38,6 +38,6 @@ target_link_libraries(inputtestsourcesrv swagger ) -qt5_use_modules(inputtestsourcesrv Core) +target_link_libraries(inputtestsourcesrv Qt5::Core) install(TARGETS inputtestsourcesrv DESTINATION lib/pluginssrv/samplesource) diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 7bd992492..784e7f504 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -90,6 +90,6 @@ target_link_libraries(qrtplib ${QT_LIBRARIES} ) -qt5_use_modules(qrtplib Core Network) +target_link_libraries(qrtplib Qt5::Core Qt5::Network) install(TARGETS qrtplib DESTINATION lib) diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index c3ac5e833..32191b24f 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -301,7 +301,7 @@ endif (BUILD_DEBIAN) set_target_properties(sdrbase PROPERTIES DEFINE_SYMBOL "sdrbase_EXPORTS") target_compile_features(sdrbase PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 -qt5_use_modules(sdrbase Core Multimedia) +target_link_libraries(sdrbase Qt5::Core Qt5::Multimedia) install(TARGETS sdrbase DESTINATION lib) diff --git a/sdrbench/CMakeLists.txt b/sdrbench/CMakeLists.txt index 55c3b5449..9fe9d935b 100644 --- a/sdrbench/CMakeLists.txt +++ b/sdrbench/CMakeLists.txt @@ -41,7 +41,7 @@ target_link_libraries(sdrbench target_compile_features(sdrbench PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 -qt5_use_modules(sdrbench Core Gui) +target_link_libraries(sdrbench Qt5::Core Qt5::Gui) install(TARGETS sdrbench DESTINATION lib) diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 4da2e430e..4f6289690 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -190,7 +190,7 @@ target_link_libraries(sdrgui set_target_properties(sdrgui PROPERTIES DEFINE_SYMBOL "sdrgui_EXPORTS") target_compile_features(sdrgui PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 -qt5_use_modules(sdrgui Core Widgets OpenGL Multimedia) +target_link_libraries(sdrgui Qt5::Core Qt5::Widgets Qt5::OpenGL Qt5::Multimedia) install(TARGETS sdrgui DESTINATION lib) diff --git a/sdrsrv/CMakeLists.txt b/sdrsrv/CMakeLists.txt index d06292637..cb599edbb 100644 --- a/sdrsrv/CMakeLists.txt +++ b/sdrsrv/CMakeLists.txt @@ -44,7 +44,7 @@ target_link_libraries(sdrsrv target_compile_features(sdrsrv PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 -qt5_use_modules(sdrsrv Core Multimedia) +target_link_libraries(sdrsrv Qt5::Core Qt5::Multimedia) install(TARGETS sdrsrv DESTINATION lib) diff --git a/swagger/CMakeLists.txt b/swagger/CMakeLists.txt index c906a88c8..92cd86f9b 100644 --- a/swagger/CMakeLists.txt +++ b/swagger/CMakeLists.txt @@ -33,6 +33,6 @@ target_link_libraries(swagger set_target_properties(swagger PROPERTIES DEFINE_SYMBOL "sdrangel_EXPORTS") -qt5_use_modules(swagger Core Network) +target_link_libraries(swagger Qt5::Core Qt5::Network) install(TARGETS swagger DESTINATION lib) From d17775f5e1a6d11d6acdcd2f472ab37399331c7d Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 14 Jun 2018 18:01:02 +0200 Subject: [PATCH 521/956] DATV demod: include it only if FFmpeg > 3.1 is installed --- Readme.md | 7 +++++++ plugins/channelrx/CMakeLists.txt | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index c12c6538b..a096f77e9 100644 --- a/Readme.md +++ b/Readme.md @@ -351,6 +351,13 @@ Install cmake version 3: - `sudo apt-get remove cmake` (if already installed) - `sudo apt-get install cmake` +

    Prerequisites for 16.04 LTS

    + +You need to install the ffmpeg v.3 suite. Therefore you will need to add this PPA to the sources list using this command: +`sudo add-apt-repository ppa:jonathonf/ffmpeg-3` + +Then do `sudo apt-get update` and go to the next step. Alternatively if you have an older version of ffmpeg suite already installed just do `sudo apt-get dist-upgrde`. +

    With newer versions just do:

    - `sudo apt-get install cmake g++ pkg-config libfftw3-dev libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev libusb-1.0 librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libnanomsg-dev libopencv-dev libsqlite3-dev libxml2-dev bison flex ffmpeg libavcodec-dev libavformat-dev` diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 66b1f515f..68bde180f 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -15,8 +15,14 @@ if(LIBDSDCC_FOUND AND LIBMBE_FOUND) endif(LIBDSDCC_FOUND AND LIBMBE_FOUND) find_package(FFmpeg) -if(FFMPEG_FOUND) - add_subdirectory(demoddatv) +if (FFMPEG_FOUND) + # You can only get FFmpeg version from the command line + EXECUTE_PROCESS(COMMAND ffmpeg -version COMMAND grep ffmpeg COMMAND cut -d\ -f3 COMMAND tr -d '\n' OUTPUT_VARIABLE FFMPEG_VERSION) + message(STATUS "FFmpeg version ${FFMPEG_VERSION} found") + if(FFMPEG_VERSION VERSION_GREATER "3.1") + message(STATUS "Include demoddatv") + add_subdirectory(demoddatv) + endif() endif() if (BUILD_DEBIAN) From e86120969a707c60972c48104d433f06aec803ba Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 20 Jun 2018 09:15:17 +0200 Subject: [PATCH 522/956] DSD decoder: NXDN implementation --- plugins/channelrx/demoddsd/dsddecoder.h | 1 + plugins/channelrx/demoddsd/dsddemod.cpp | 43 +++++++++++++++++++++++++ plugins/channelrx/demoddsd/dsddemod.h | 3 +- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/plugins/channelrx/demoddsd/dsddecoder.h b/plugins/channelrx/demoddsd/dsddecoder.h index b135a653c..9f7343169 100644 --- a/plugins/channelrx/demoddsd/dsddecoder.h +++ b/plugins/channelrx/demoddsd/dsddecoder.h @@ -65,6 +65,7 @@ public: const DSDcc::DSDDstar& getDStarDecoder() const { return m_decoder.getDStarDecoder(); } const DSDcc::DSDdPMR& getDPMRDecoder() const { return m_decoder.getDPMRDecoder(); } const DSDcc::DSDYSF& getYSFDecoder() const { return m_decoder.getYSFDecoder(); } + const DSDcc::DSDNXDN& getNXDNDecoder() const { return m_decoder.getNXDNDecoder(); } void setMyPoint(float lat, float lon) { m_decoder.setMyPoint(lat, lon); } void setAudioGain(float gain) { m_decoder.setAudioGain(gain); } diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 806d5c4ee..117eeb09c 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -659,6 +660,48 @@ void DSDDemod::formatStatusText() getDecoder().getDPMRDecoder().getCalledId()); m_signalFormat = signalFormatDPMR; break; + case DSDcc::DSDDecoder::DSDSyncNXDNP: + case DSDcc::DSDDecoder::DSDSyncNXDNN: + if (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRCCH) + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // RC cc mm llllll ssss + snprintf(m_formatStatusText, 82, "RC %02d %02X %06X %02X", + getDecoder().getNXDNDecoder().getRAN(), + getDecoder().getNXDNDecoder().getMessageType(), + getDecoder().getNXDNDecoder().getLocationId(), + getDecoder().getNXDNDecoder().getServicesFlag()); + } + else if ((getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRTCH) + || (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRDCH)) + { + if (getDecoder().getNXDNDecoder().isIdle()) { + snprintf(m_formatStatusText, 82, "%s IDLE", getDecoder().getNXDNDecoder().getRFChannelStr()); + } + else + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // Rx cc mm sssss>gddddd + snprintf(m_formatStatusText, 82, "%s %02d %02X %05d>%c%05d", + getDecoder().getNXDNDecoder().getRFChannelStr(), + getDecoder().getNXDNDecoder().getRAN(), + getDecoder().getNXDNDecoder().getMessageType(), + getDecoder().getNXDNDecoder().getSourceId(), + getDecoder().getNXDNDecoder().isGroupCall() ? 'G' : 'I', + getDecoder().getNXDNDecoder().getDestinationId()); + } + } + else + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // RU + snprintf(m_formatStatusText, 82, "RU"); + } + m_signalFormat = signalFormatNXDN; + break; case DSDcc::DSDDecoder::DSDSyncYSF: // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index d426746f0..9ffeef680 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -152,7 +152,8 @@ private: signalFormatDMR, signalFormatDStar, signalFormatDPMR, - signalFormatYSF + signalFormatYSF, + signalFormatNXDN } SignalFormat; //!< Used for status text formatting class MsgConfigureMyPosition : public Message { From 777a1b01234b695a7408e3993b694517eb8b3885 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 21 Jun 2018 03:57:16 +0200 Subject: [PATCH 523/956] DSD demod: NXDN support documentation --- debian/changelog | 8 ++ doc/img/DSDdemod_plugin_nxdn_rcch_status.png | Bin 0 -> 5037 bytes doc/img/DSDdemod_plugin_nxdn_rcch_status.xcf | Bin 0 -> 19717 bytes doc/img/DSDdemod_plugin_nxdn_rtdch_status.png | Bin 0 -> 5789 bytes doc/img/DSDdemod_plugin_nxdn_rtdch_status.xcf | Bin 0 -> 20868 bytes plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- plugins/channelrx/demoddsd/readme.md | 86 +++++++++++++++++- 7 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 doc/img/DSDdemod_plugin_nxdn_rcch_status.png create mode 100644 doc/img/DSDdemod_plugin_nxdn_rcch_status.xcf create mode 100644 doc/img/DSDdemod_plugin_nxdn_rtdch_status.png create mode 100644 doc/img/DSDdemod_plugin_nxdn_rtdch_status.xcf diff --git a/debian/changelog b/debian/changelog index 2dec7fc61..3da824284 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +sdrangel (4.0.1-1) unstable; urgency=medium + + * DSD demod: added NXDN support + * DATV demod: include it only if FFmpeg > 3.1 is installed + * Fixes for Arch. Manual merge of pull request #183 + + -- Edouard Griffiths, F4EXB Sat, 23 Jun 2018 09:14:18 +0200 + sdrangel (4.0.0-1) unstable; urgency=medium * Finalization of REST API and server instance diff --git a/doc/img/DSDdemod_plugin_nxdn_rcch_status.png b/doc/img/DSDdemod_plugin_nxdn_rcch_status.png new file mode 100644 index 0000000000000000000000000000000000000000..602998b47508fc0ed2631d347faeb72636c2ea75 GIT binary patch literal 5037 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 literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_nxdn_rtdch_status.png b/doc/img/DSDdemod_plugin_nxdn_rtdch_status.png new file mode 100644 index 0000000000000000000000000000000000000000..815876b9f14a5872057948f8b5b4ce9b696d0f25 GIT binary patch literal 5789 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 literal 0 HcmV?d00001 diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index afeed48da..61414fd45 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("4.0.0"), + QString("4.0.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index 7beae3a15..8198169b8 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -8,9 +8,6 @@ This plugin uses the [DSDcc](https://github.com/f4exb/dsdcc) library that has be - dPMR: Another ETSI standard at slower rate (2400 Baud / 6.25 kHz) and FDMA - D-Star: developed and promoted by Icom for Amateur Radio customers. - Yaesu System Fusion (YSF): developed and promoted by Yaesu for Amateur Radio customers. Voice full rate with DV serial devices is not supported - -It can only detect the following standards (no data, no voice): - - NXDN: A joint Icom (IDAS) and Kenwood (Nexedge) standard with 2400 and 4800 Baud versions. The modulation and standard is automatically detected and switched depending on the Baud rate chosen. @@ -294,6 +291,89 @@ This displays a summary of FICH (Frame Identification CHannel) block data. From This is the unique character string assigned to the device by the manufacturer. +

    A11.5: NXDN status display

    + +There are 3 display formats depending on the kind of transmission called RF channel in the NXDN system. + +
    A11.5.1: RCCH RF channel display
    + +This is the control channel used in trunked systems and is usually sent continuously. + +![DSD NXDN RTDCH status](../../../doc/img/DSDdemod_plugin_nxdn_rcch_status.png) + +
    A11.5.1.1: RF channel indicator
    + +This is `RC` for RCCH + +
    A11.5.1.2: RAN number
    + +This is the RAN number (0 to 63) associated to the transmission. RAN stands for "Radio Access Number" and for trunked systems this is the site identifier (Site Id) modulo 64. + +
    A11.5.1.3: Last message type code
    + +This is the type code of the last message (6 bits) displayed in hexadecimal. The complete list is found in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6. + +
    A11.5.1.4: Location Id
    + +This is the 3 byte location Id associated to the site displayed in hexadecimal + +
    A11.5.1.5: Services available flags
    + +This is a 16 bit collection of flags to indicate which services are available displayed in hexadecimal. The breakdown is listed in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6.5.33. From MSB to LSB: + + - first nibble (here `B`): + - `b15`: Multi-site service + - `b14`: Multi-system service + - `b13`: Location Registration service + - `b12`: Group Registration Service + - second nibble (here `3`): + - `b11`: Authentication Service + - `b10`: Composite Control Channel Service + - `b9`: Voice Call Service + - `b8`: Data Call Service + - third nibble (here `C`): + - `b7`: Short Data Call Service + - `b6`: Status Call & Remote Control Service + - `b5`: PSTN Network Connection Service + - `b4`: IP Network Connection Service + - fourth nibble (here `0`) is spare + +
    A11.5.2: RTCH or RDCH RF channel display
    + +This is the transmission channel either in a trunked system (RTCH) or conventional system (RDCH). + +![DSD NXDN RTDCH status](../../../doc/img/DSDdemod_plugin_nxdn_rtdch_status.png) + +
    A11.5.2.1: RF channel indicator
    + +It can be either `RT` for RTCH or `RD` for a RDCH channel + +
    A11.5.2.2: RAN number
    + +This is the RAN number (0 to 63) associated to the transmission. RAN stands for "Radio Access Number" and has a different usage in conventional or trunked systems: + + - Conventional (RDCH): this is used as a selective squelch. Code `0` means always unmute. + - Trunked (RTCH): this is the site identifier (Site Id) modulo 64. + +
    A11.5.2.3: Last message type code
    + +This is the type code of the last message (6 bits) displayed in hexadecimal. The complete list is found in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6. + +
    A11.5.2.4: Source Id
    + +This is the source of transmission identification code on two bytes (0 to 65353) displayed in decimal. + +
    A11.5.2.5: Destination Id
    + +This is the destination of transmission identification code on two bytes (0 to 65353) displayed in decimal. It is prefixed by a group call indicator: + + - `G`: this is a group call + - `I`: this is an individual call + +
    A11.5.3: Unknown or erroneous data display
    + +In this case the display is simply "RU" for "unknown" +

    B section: digital

    B.1: FM signal scope

    From b00aea898919f80be7ae918c480e3b19b65aa189 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 21 Jun 2018 19:28:11 +0200 Subject: [PATCH 524/956] PVS studio static analyzer fixes (1) --- .../bladerfoutput/bladerfoutputgui.cpp | 2 +- plugins/samplesink/filesink/filesinkgui.cpp | 2 +- .../hackrfoutput/hackrfoutputgui.cpp | 2 +- .../limesdroutput/limesdroutputgui.cpp | 2 +- .../plutosdroutput/plutosdroutputgui.cpp | 2 +- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 2 +- .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 3 +- plugins/samplesource/airspy/airspygui.cpp | 2 +- plugins/samplesource/airspyhf/airspyhfgui.cpp | 2 +- .../bladerfinput/bladerfinputgui.cpp | 2 +- plugins/samplesource/fcdpro/fcdprogui.cpp | 2 +- .../samplesource/fcdproplus/fcdproplusgui.cpp | 2 +- .../samplesource/filesource/filesourcegui.cpp | 2 +- .../hackrfinput/hackrfinputgui.cpp | 2 +- .../limesdrinput/limesdrinputgui.cpp | 2 +- plugins/samplesource/perseus/perseusgui.cpp | 2 +- .../plutosdrinput/plutosdrinputgui.cpp | 2 +- plugins/samplesource/rtlsdr/rtlsdrgui.cpp | 2 +- .../sdrdaemonsource/sdrdaemonsourcegui.cpp | 2 +- .../samplesource/testsource/testsourcegui.cpp | 2 +- qrtplib/rtcpcompoundpacket.cpp | 6 --- qrtplib/rtcpcompoundpacketbuilder.cpp | 40 ------------------- qrtplib/rtcpcompoundpacketbuilder.h | 2 - qrtplib/rtcppacketbuilder.cpp | 4 -- qrtplib/rtcpscheduler.cpp | 5 +++ qrtplib/rtcpsdesinfo.h | 2 - qrtplib/rtpkeyhashtable.h | 2 - qrtplib/rtpudptransmitter.cpp | 6 +-- sdrbase/audio/audiofifo.cpp | 2 +- sdrbase/audio/audionetsink.cpp | 12 +++--- sdrgui/mainwindow.cpp | 2 +- sdrsrv/maincore.cpp | 2 +- 32 files changed, 35 insertions(+), 91 deletions(-) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp index 12939e2bb..86ecc7461 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp @@ -38,7 +38,7 @@ BladerfOutputGui::BladerfOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_settings(), m_deviceSampleSink(NULL), m_sampleRate(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1) + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) { m_deviceSampleSink = (BladerfOutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); diff --git a/plugins/samplesink/filesink/filesinkgui.cpp b/plugins/samplesink/filesink/filesinkgui.cpp index 4b054c748..c05d525c0 100644 --- a/plugins/samplesink/filesink/filesinkgui.cpp +++ b/plugins/samplesink/filesink/filesinkgui.cpp @@ -49,7 +49,7 @@ FileSinkGui::FileSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_startingTimeStamp(0), m_samplesCount(0), m_tickCount(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1) + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) { ui->setupUi(this); diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp b/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp index fa4e31b0c..153df00d9 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp @@ -40,7 +40,7 @@ HackRFOutputGui::HackRFOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_deviceSampleSink(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_doApplySettings(true) { m_deviceSampleSink = (HackRFOutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp index ed0a89575..b04bab065 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp @@ -32,7 +32,7 @@ LimeSDROutputGUI::LimeSDROutputGUI(DeviceUISet *deviceUISet, QWidget* parent) : m_deviceUISet(deviceUISet), m_settings(), m_sampleRate(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_doApplySettings(true), m_forceSettings(true), m_statusCounter(0), diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputgui.cpp b/plugins/samplesink/plutosdroutput/plutosdroutputgui.cpp index 498081ad3..731240c46 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputgui.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutputgui.cpp @@ -37,7 +37,7 @@ PlutoSDROutputGUI::PlutoSDROutputGUI(DeviceUISet *deviceUISet, QWidget* parent) m_sampleSink(0), m_sampleRate(0), m_deviceCenterFrequency(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_doApplySettings(true), m_statusCounter(0) { diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 794a7c343..55e65d616 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -50,7 +50,7 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_samplesCount(0), m_tickCount(0), m_nbSinceLastFlowCheck(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_doApplySettings(true), m_forceSettings(true) { diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index 7e268e338..e1bad17d5 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -40,7 +40,8 @@ UDPSinkFEC::UDPSinkFEC() : m_frameCount(0), m_sampleIndex(0) { - memset((char *) m_txBlocks, 0, 4*256); + memset((char *) m_txBlocks, 0, 4*256*sizeof(SuperBlock)); + memset((char *) &m_superBlock, 0, sizeof(SuperBlock)); m_currentMetaFEC.init(); m_bufMeta = new uint8_t[m_udpSize]; m_buf = new uint8_t[m_udpSize]; diff --git a/plugins/samplesource/airspy/airspygui.cpp b/plugins/samplesource/airspy/airspygui.cpp index a1517425a..ae59a6ca7 100644 --- a/plugins/samplesource/airspy/airspygui.cpp +++ b/plugins/samplesource/airspy/airspygui.cpp @@ -39,7 +39,7 @@ AirspyGui::AirspyGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (AirspyInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/plugins/samplesource/airspyhf/airspyhfgui.cpp b/plugins/samplesource/airspyhf/airspyhfgui.cpp index 3ce6e2e57..3cfb3f103 100644 --- a/plugins/samplesource/airspyhf/airspyhfgui.cpp +++ b/plugins/samplesource/airspyhf/airspyhfgui.cpp @@ -38,7 +38,7 @@ AirspyHFGui::AirspyHFGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (AirspyHFInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/plugins/samplesource/bladerfinput/bladerfinputgui.cpp b/plugins/samplesource/bladerfinput/bladerfinputgui.cpp index 1e4b8979d..568effb5c 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputgui.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputgui.cpp @@ -38,7 +38,7 @@ BladerfInputGui::BladerfInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_settings(), m_sampleSource(NULL), m_sampleRate(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (BladerfInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/plugins/samplesource/fcdpro/fcdprogui.cpp b/plugins/samplesource/fcdpro/fcdprogui.cpp index bc3dafebb..20920db13 100644 --- a/plugins/samplesource/fcdpro/fcdprogui.cpp +++ b/plugins/samplesource/fcdpro/fcdprogui.cpp @@ -35,7 +35,7 @@ FCDProGui::FCDProGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(NULL), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (FCDProInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/plugins/samplesource/fcdproplus/fcdproplusgui.cpp b/plugins/samplesource/fcdproplus/fcdproplusgui.cpp index 9a38c7122..210bb4752 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusgui.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusgui.cpp @@ -36,7 +36,7 @@ FCDProPlusGui::FCDProPlusGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(NULL), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (FCDProPlusInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index a3049fef9..8bcedc3af 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -51,7 +51,7 @@ FileSourceGui::FileSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_samplesCount(0), m_tickCount(0), m_enableNavTime(false), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { ui->setupUi(this); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); diff --git a/plugins/samplesource/hackrfinput/hackrfinputgui.cpp b/plugins/samplesource/hackrfinput/hackrfinputgui.cpp index 9868313f2..6de6ca1b0 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputgui.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputgui.cpp @@ -40,7 +40,7 @@ HackRFInputGui::HackRFInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_doApplySettings(true), m_sampleSource(NULL), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (HackRFInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index dc243470f..0b44d28c3 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -35,7 +35,7 @@ LimeSDRInputGUI::LimeSDRInputGUI(DeviceUISet *deviceUISet, QWidget* parent) : m_deviceUISet(deviceUISet), m_settings(), m_sampleRate(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_doApplySettings(true), m_forceSettings(true), m_statusCounter(0), diff --git a/plugins/samplesource/perseus/perseusgui.cpp b/plugins/samplesource/perseus/perseusgui.cpp index b2764a2eb..7ca36aa33 100644 --- a/plugins/samplesource/perseus/perseusgui.cpp +++ b/plugins/samplesource/perseus/perseusgui.cpp @@ -36,7 +36,7 @@ PerseusGui::PerseusGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (PerseusInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp index 65ce1477f..441413b0d 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp @@ -37,7 +37,7 @@ PlutoSDRInputGui::PlutoSDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleSource(NULL), m_sampleRate(0), m_deviceCenterFrequency(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_doApplySettings(true), m_statusCounter(0) { diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp index d5db76d98..2c7e63f66 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp @@ -38,7 +38,7 @@ RTLSDRGui::RTLSDRGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (RTLSDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 0762af326..275436901 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -54,7 +54,7 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent m_acquisition(false), m_streamSampleRate(0), m_streamCenterFrequency(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_framesDecodingStatus(0), m_bufferLengthInSecs(0.0), m_bufferGauge(-50), diff --git a/plugins/samplesource/testsource/testsourcegui.cpp b/plugins/samplesource/testsource/testsourcegui.cpp index 7fbc84f32..abd4f8175 100644 --- a/plugins/samplesource/testsource/testsourcegui.cpp +++ b/plugins/samplesource/testsource/testsourcegui.cpp @@ -44,7 +44,7 @@ TestSourceGui::TestSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_sampleSource(0), m_tickCount(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { qDebug("TestSourceGui::TestSourceGui"); m_sampleSource = m_deviceUISet->m_deviceSourceAPI->getSampleSource(); diff --git a/qrtplib/rtcpcompoundpacket.cpp b/qrtplib/rtcpcompoundpacket.cpp index 23d8ea179..c0e12e472 100644 --- a/qrtplib/rtcpcompoundpacket.cpp +++ b/qrtplib/rtcpcompoundpacket.cpp @@ -171,12 +171,6 @@ int RTCPCompoundPacket::ParseData(uint8_t *data, std::size_t datalen) p = new RTCPUnknownPacket(data, length); } - if (p == 0) - { - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } - rtcppacklist.push_back(p); datalen -= length; diff --git a/qrtplib/rtcpcompoundpacketbuilder.cpp b/qrtplib/rtcpcompoundpacketbuilder.cpp index 7aeba2abc..bfb4b5ee2 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.cpp +++ b/qrtplib/rtcpcompoundpacketbuilder.cpp @@ -192,8 +192,6 @@ int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc, uint8_t fractionlos return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; uint8_t *buf = new uint8_t[sizeof(RTCPReceiverReport)]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; RTCPReceiverReport *rr = (RTCPReceiverReport *) buf; uint32_t *packlost = (uint32_t *) &packetslost; @@ -277,8 +275,6 @@ int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t, con std::size_t len; buf = new uint8_t[sizeof(RTCPSDESHeader) + (std::size_t) itemlength]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; len = sizeof(RTCPSDESHeader) + (std::size_t) itemlength; RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (buf); @@ -314,8 +310,6 @@ int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata, uint8_ std::size_t len; buf = new uint8_t[sizeof(RTCPSDESHeader) + itemlength]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; len = sizeof(RTCPSDESHeader) + (std::size_t) itemlength; RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (buf); @@ -367,8 +361,6 @@ int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, c std::size_t numwords; buf = new uint8_t[packsize]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; RTCPCommonHeader *hdr = (RTCPCommonHeader *) buf; @@ -425,8 +417,6 @@ int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype, uint32_t ssrc, cons uint8_t *buf; buf = new uint8_t[packsize]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; RTCPCommonHeader *hdr = (RTCPCommonHeader *) buf; @@ -469,8 +459,6 @@ int RTCPCompoundPacketBuilder::EndBuild() if (!external) { buf = new uint8_t[len]; - if (buf == 0) - return ERR_RTP_OUTOFMEM; } else buf = buffer; @@ -526,13 +514,6 @@ int RTCPCompoundPacketBuilder::EndBuild() p = new RTCPSRPacket(curbuf, offset); else p = new RTCPRRPacket(curbuf, offset); - if (p == 0) - { - if (!external) - delete[] buf; - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } rtcppacklist.push_back(p); curbuf += offset; @@ -600,13 +581,6 @@ int RTCPCompoundPacketBuilder::EndBuild() hdr->length = qToBigEndian((uint16_t) (numwords - 1)); p = new RTCPSDESPacket(curbuf, offset); - if (p == 0) - { - if (!external) - delete[] buf; - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } rtcppacklist.push_back(p); curbuf += offset; @@ -625,13 +599,6 @@ int RTCPCompoundPacketBuilder::EndBuild() memcpy(curbuf, (*it).packetdata, (*it).packetlength); p = new RTCPAPPPacket(curbuf, (*it).packetlength); - if (p == 0) - { - if (!external) - delete[] buf; - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } rtcppacklist.push_back(p); curbuf += (*it).packetlength; @@ -648,13 +615,6 @@ int RTCPCompoundPacketBuilder::EndBuild() memcpy(curbuf, (*it).packetdata, (*it).packetlength); p = new RTCPBYEPacket(curbuf, (*it).packetlength); - if (p == 0) - { - if (!external) - delete[] buf; - ClearPacketList(); - return ERR_RTP_OUTOFMEM; - } rtcppacklist.push_back(p); curbuf += (*it).packetlength; diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index c66210ab8..7daff9dc1 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -296,8 +296,6 @@ private: int AddSSRC(uint32_t ssrc) { SDESSource *s = new SDESSource(ssrc); - if (s == 0) - return ERR_RTP_OUTOFMEM; sdessources.push_back(s); sdesit = sdessources.end(); sdesit--; diff --git a/qrtplib/rtcppacketbuilder.cpp b/qrtplib/rtcppacketbuilder.cpp index c4b2ff543..70cd75cd1 100644 --- a/qrtplib/rtcppacketbuilder.cpp +++ b/qrtplib/rtcppacketbuilder.cpp @@ -111,8 +111,6 @@ int RTCPPacketBuilder::BuildNextPacket(RTCPCompoundPacket **pack) *pack = 0; rtcpcomppack = new RTCPCompoundPacketBuilder(); - if (rtcpcomppack == 0) - return ERR_RTP_OUTOFMEM; if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) { @@ -640,8 +638,6 @@ int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack, const void *rea *pack = 0; rtcpcomppack = new RTCPCompoundPacketBuilder(); - if (rtcpcomppack == 0) - return ERR_RTP_OUTOFMEM; if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) { diff --git a/qrtplib/rtcpscheduler.cpp b/qrtplib/rtcpscheduler.cpp index 31f7a8bb1..9d19ec038 100644 --- a/qrtplib/rtcpscheduler.cpp +++ b/qrtplib/rtcpscheduler.cpp @@ -87,6 +87,11 @@ int RTCPSchedulerParams::SetMinimumTransmissionInterval(const RTPTime &t) RTCPScheduler::RTCPScheduler(RTPSources &s, RTPRandom &r) : sources(s), nextrtcptime(0, 0), prevrtcptime(0, 0), rtprand(r) { + pmembers = 0; + byemembers = 0; + pbyemembers = 0; + avgbyepacketsize = 0; + Reset(); //std::cout << (void *)(&rtprand) << std::endl; diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h index 16e9e2ab9..f46bfbddf 100644 --- a/qrtplib/rtcpsdesinfo.h +++ b/qrtplib/rtcpsdesinfo.h @@ -230,8 +230,6 @@ private: { len = (len > RTCP_SDES_MAXITEMLENGTH) ? RTCP_SDES_MAXITEMLENGTH : len; uint8_t *str2 = new uint8_t[len]; - if (str2 == 0) - return ERR_RTP_OUTOFMEM; memcpy(str2, s, len); *destlen = len; if (*dest) diff --git a/qrtplib/rtpkeyhashtable.h b/qrtplib/rtpkeyhashtable.h index ef0386b16..cfba38d7c 100644 --- a/qrtplib/rtpkeyhashtable.h +++ b/qrtplib/rtpkeyhashtable.h @@ -297,8 +297,6 @@ inline int RTPKeyHashTable::AddElement(const K // Okay, the key doesn't exist, so we can add the new element in the hash table newelem = new HashElement(k, elem, index); - if (newelem == 0) - return ERR_RTP_OUTOFMEM; e = table[index]; table[index] = newelem; diff --git a/qrtplib/rtpudptransmitter.cpp b/qrtplib/rtpudptransmitter.cpp index bcca9c584..4b1d1ff1c 100644 --- a/qrtplib/rtpudptransmitter.cpp +++ b/qrtplib/rtpudptransmitter.cpp @@ -541,15 +541,11 @@ bool RTPUDPTransmitter::ShouldAcceptData(const RTPAddress& rtpAddress) std::list::iterator findIt = std::find(m_acceptList.begin(), m_acceptList.end(), rtpAddress); return findIt != m_acceptList.end(); } - else if (m_receivemode == RTPTransmitter::IgnoreSome) + else // this is RTPTransmitter::IgnoreSome { std::list::iterator findIt = std::find(m_ignoreList.begin(), m_ignoreList.end(), rtpAddress); return findIt == m_ignoreList.end(); } - else - { - return false; - } } } // namespace diff --git a/sdrbase/audio/audiofifo.cpp b/sdrbase/audio/audiofifo.cpp index ba9fe589e..2cade6829 100644 --- a/sdrbase/audio/audiofifo.cpp +++ b/sdrbase/audio/audiofifo.cpp @@ -266,5 +266,5 @@ bool AudioFifo::create(uint32_t numSamples) m_fifo = new qint8[numSamples * m_sampleSize]; m_size = numSamples; - return m_fifo != 0; + return true; } diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index 5b7afd47d..70c7dac0f 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -29,6 +29,7 @@ AudioNetSink::AudioNetSink(QObject *parent) : m_bufferIndex(0), m_port(9998) { + memset(m_data, 0, 65536); m_udpSocket = new QUdpSocket(parent); } @@ -38,6 +39,7 @@ AudioNetSink::AudioNetSink(QObject *parent, int sampleRate, bool stereo) : m_bufferIndex(0), m_port(9998) { + memset(m_data, 0, 65536); m_udpSocket = new QUdpSocket(parent); m_rtpBufferAudio = new RTPSink(m_udpSocket, sampleRate, stereo); } @@ -61,17 +63,13 @@ bool AudioNetSink::selectType(SinkType type) if (type == SinkUDP) { m_type = SinkUDP; - return true; } - else if (type == SinkRTP) + else // this is SinkRTP { m_type = SinkRTP; - return true; - } - else - { - return false; } + + return true; } void AudioNetSink::setDestination(const QString& address, uint16_t port) diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index 6f5146621..1b82f5e15 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -90,7 +90,7 @@ MainWindow::MainWindow(qtwebapp::LoggerWithFile *logger, const MainParser& parse m_settings(), m_masterTabIndex(0), m_dspEngine(DSPEngine::instance()), - m_lastEngineState((DSPDeviceSourceEngine::State)-1), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_inputGUI(0), m_sampleRate(0), m_centerFrequency(0), diff --git a/sdrsrv/maincore.cpp b/sdrsrv/maincore.cpp index 340837cb8..3782e7dc6 100644 --- a/sdrsrv/maincore.cpp +++ b/sdrsrv/maincore.cpp @@ -53,7 +53,7 @@ MainCore::MainCore(qtwebapp::LoggerWithFile *logger, const MainParser& parser, Q m_settings(), m_masterTabIndex(-1), m_dspEngine(DSPEngine::instance()), - m_lastEngineState((DSPDeviceSourceEngine::State)-1), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_logger(logger) { qDebug() << "MainCore::MainCore: start"; From 5777875618f5a44d8c50469ef93e487baf5520d8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 22 Jun 2018 00:15:23 +0200 Subject: [PATCH 525/956] PVS studio static analyzer fixes (2) --- plugins/channelrx/demoddatv/leansdr/dsp.h | 2 +- plugins/channelrx/demoddatv/leansdr/hdlc.h | 13 ++++++++++--- plugins/channelrx/demoddatv/leansdr/math.h | 6 +++--- plugins/channeltx/udpsink/udpsink.cpp | 3 --- plugins/samplesource/airspy/airspyinput.cpp | 3 --- plugins/samplesource/airspyhf/airspyhfinput.cpp | 8 +------- plugins/samplesource/airspyhf/airspyhfthread.cpp | 2 ++ qrtplib/rtcpsdesinfo.cpp | 3 +-- qrtplib/rtpinternalsourcedata.cpp | 2 -- qrtplib/rtppacket.cpp | 5 ----- qrtplib/rtppacketbuilder.cpp | 5 +---- qrtplib/rtpsources.cpp | 5 +---- 12 files changed, 20 insertions(+), 37 deletions(-) diff --git a/plugins/channelrx/demoddatv/leansdr/dsp.h b/plugins/channelrx/demoddatv/leansdr/dsp.h index 03725abc5..6067ee7a0 100644 --- a/plugins/channelrx/demoddatv/leansdr/dsp.h +++ b/plugins/channelrx/demoddatv/leansdr/dsp.h @@ -334,7 +334,7 @@ private: { for (unsigned int i = 0; i < ncoeffs; ++i) { - float a = 2 * M_PI * f * (i - (ncoeffs / 2)); + float a = 2 * M_PI * f * (i - (ncoeffs / 2.0f)); float c = cosf(a), s = sinf(a); // TBD Support T=complex shifted_coeffs[i].re = coeffs[i] * c; diff --git a/plugins/channelrx/demoddatv/leansdr/hdlc.h b/plugins/channelrx/demoddatv/leansdr/hdlc.h index 7c8dc9f66..ac936f5d1 100644 --- a/plugins/channelrx/demoddatv/leansdr/hdlc.h +++ b/plugins/channelrx/demoddatv/leansdr/hdlc.h @@ -13,10 +13,17 @@ struct hdlc_dec hdlc_dec(int _minframesize, // Including CRC, excluding HDLC flags. int _maxframesize, bool _invert) : - minframesize(_minframesize), maxframesize(_maxframesize), invertmask( - _invert ? 0xff : 0), framebuf(new u8[maxframesize]), debug( - false) + minframesize(_minframesize), + maxframesize(_maxframesize), + invertmask(_invert ? 0xff : 0), + framebuf(new u8[maxframesize]), + debug(false) { + byte_out = 0; + nbits_out = 0; + framesize = 0; + crc16 = 0; + reset(); } diff --git a/plugins/channelrx/demoddatv/leansdr/math.h b/plugins/channelrx/demoddatv/leansdr/math.h index c25d64c5d..26a94b250 100644 --- a/plugins/channelrx/demoddatv/leansdr/math.h +++ b/plugins/channelrx/demoddatv/leansdr/math.h @@ -9,7 +9,7 @@ namespace leansdr { template struct complex { T re, im; - complex() { } + complex() : re(0), im(0) { } complex(T x) : re(x), im(0) { } complex(T x, T y) : re(x), im(y) { } inline void operator +=(const complex &x) { re+=x.re; im+=x.im; } @@ -34,7 +34,7 @@ namespace leansdr { complex operator *(const T &k, const complex &a) { return complex(k*a.re, k*a.im); } - + // TBD Optimize with dedicated instructions inline int hamming_weight(uint8_t x) { static const int lut[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 }; @@ -72,7 +72,7 @@ namespace leansdr { for ( ; x; ++n,x>>=1 ) ; return n; } - + // Pre-computed sin/cos for 16-bit angles struct trig16 { diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index 890749b7e..9e18c5f22 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -637,9 +637,6 @@ int UDPSink::webapiSettingsPutPatch( if (channelSettingsKeys.contains("amModFactor")) { settings.m_amModFactor = response.getUdpSinkSettings()->getAmModFactor(); } - if (channelSettingsKeys.contains("amModFactor")) { - settings.m_amModFactor = response.getUdpSinkSettings()->getAmModFactor(); - } if (channelSettingsKeys.contains("channelMute")) { settings.m_channelMute = response.getUdpSinkSettings()->getChannelMute() != 0; } diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index a22bd51d3..7ec2f68f3 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -654,9 +654,6 @@ int AirspyInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("vgaGain")) { settings.m_vgaGain = response.getAirspySettings()->getVgaGain(); } - if (deviceSettingsKeys.contains("vgaGain")) { - settings.m_vgaGain = response.getAirspySettings()->getVgaGain(); - } if (deviceSettingsKeys.contains("lnaAGC")) { settings.m_lnaAGC = response.getAirspySettings()->getLnaAgc() != 0; } diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index 93c468621..1cadfcc39 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -154,13 +154,7 @@ bool AirspyHFInput::start() if (m_running) { stop(); } - if ((m_airspyHFThread = new AirspyHFThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("AirspyHFInput::start: out of memory"); - stop(); - return false; - } - + m_airspyHFThread = new AirspyHFThread(m_dev, &m_sampleFifo); int sampleRateIndex = m_settings.m_devSampleRateIndex; if (m_settings.m_devSampleRateIndex >= m_sampleRates.size()) { diff --git a/plugins/samplesource/airspyhf/airspyhfthread.cpp b/plugins/samplesource/airspyhf/airspyhfthread.cpp index 3a1fba0d6..cde2ac7f1 100644 --- a/plugins/samplesource/airspyhf/airspyhfthread.cpp +++ b/plugins/samplesource/airspyhf/airspyhfthread.cpp @@ -1,3 +1,4 @@ + /////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2018 Edouard Griffiths, F4EXB // // // @@ -31,6 +32,7 @@ AirspyHFThread::AirspyHFThread(airspyhf_device_t* dev, SampleSinkFifo* sampleFif m_samplerate(10), m_log2Decim(0) { + memset((char*) m_buf, 0, 2*AIRSPYHF_BLOCKSIZE*sizeof(qint16)); m_this = this; } diff --git a/qrtplib/rtcpsdesinfo.cpp b/qrtplib/rtcpsdesinfo.cpp index 735065061..73b43c5dd 100644 --- a/qrtplib/rtcpsdesinfo.cpp +++ b/qrtplib/rtcpsdesinfo.cpp @@ -85,8 +85,7 @@ int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, int status; item = new SDESPrivateItem(); - if (item == 0) - return ERR_RTP_OUTOFMEM; + if ((status = item->SetPrefix(prefix, prefixlen)) < 0) { delete item; diff --git a/qrtplib/rtpinternalsourcedata.cpp b/qrtplib/rtpinternalsourcedata.cpp index 4995dd97f..5ae3a5402 100644 --- a/qrtplib/rtpinternalsourcedata.cpp +++ b/qrtplib/rtpinternalsourcedata.cpp @@ -244,8 +244,6 @@ int RTPInternalSourceData::ProcessBYEPacket(const uint8_t *reason, std::size_t r byetime = receivetime; byereason = new uint8_t[reasonlen]; - if (byereason == 0) - return ERR_RTP_OUTOFMEM; memcpy(byereason, reason, reasonlen); byereasonlen = reasonlen; receivedbye = true; diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp index a0545da34..41355eff4 100644 --- a/qrtplib/rtppacket.cpp +++ b/qrtplib/rtppacket.cpp @@ -308,11 +308,6 @@ int RTPPacket::BuildPacket( if (buffer == 0) { packet = new uint8_t[packetlength]; - if (packet == 0) - { - packetlength = 0; - return ERR_RTP_OUTOFMEM; - } externalbuffer = false; } else diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib/rtppacketbuilder.cpp index a6974430a..2c6467951 100644 --- a/qrtplib/rtppacketbuilder.cpp +++ b/qrtplib/rtppacketbuilder.cpp @@ -62,6 +62,7 @@ RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r) : numcsrcs = 0; numpayloadbytes = 0; numpackets = 0; + memset((char *) csrcs, 0, RTP_MAXCSRCS*sizeof(uint32_t)); timeinit.Dummy(); //std::cout << (void *)(&rtprnd) << std::endl; @@ -81,8 +82,6 @@ int RTPPacketBuilder::Init(unsigned int max) maxpacksize = max; buffer = new uint8_t[max]; - if (buffer == 0) - return ERR_RTP_OUTOFMEM; packetlength = 0; numpackets = 0; @@ -113,8 +112,6 @@ int RTPPacketBuilder::SetMaximumPacketSize(unsigned int max) if (max <= 0) return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; newbuf = new uint8_t[max]; - if (newbuf == 0) - return ERR_RTP_OUTOFMEM; delete[] buffer; buffer = newbuf; diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp index 62bbc725f..3d49a7348 100644 --- a/qrtplib/rtpsources.cpp +++ b/qrtplib/rtpsources.cpp @@ -169,8 +169,7 @@ int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans // First, we'll see if the packet can be parsed rtppack = new RTPPacket(*rawpack); - if (rtppack == 0) - return ERR_RTP_OUTOFMEM; + if ((status = rtppack->GetCreationError()) < 0) { if (status == ERR_RTP_PACKET_INVALIDPACKET) @@ -808,8 +807,6 @@ int RTPSources::ObtainSourceDataInstance(uint32_t ssrc, RTPInternalSourceData ** if (sourcelist.GotoElement(ssrc) < 0) // No entry for this source { srcdat2 = new RTPInternalSourceData(ssrc); - if (srcdat2 == 0) - return ERR_RTP_OUTOFMEM; if ((status = sourcelist.AddElement(ssrc, srcdat2)) < 0) { delete srcdat2; From 15a51fb932c75e2c328ad452c67e5d1fef457090 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 22 Jun 2018 00:25:11 +0200 Subject: [PATCH 526/956] DSD NXDN support: updated documentation --- Readme.md | 10 ++-------- plugins/channelrx/demoddsd/readme.md | 4 +++- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Readme.md b/Readme.md index a096f77e9..e7827ac93 100644 --- a/Readme.md +++ b/Readme.md @@ -244,6 +244,7 @@ This is the `demoddsd` plugin. At present it can be used to decode the following - dPMR - D-Star - Yaesu System Fusion (YSF) + - NXDN It is based on the [DSDcc](https://github.com/f4exb/dsdcc) C++ library which is a rewrite of the original [DSD](https://github.com/szechyjs/dsd) program. So you will need to have DSDcc installed in your system. Please follow instructions in [DSDcc readme](https://github.com/f4exb/dsdcc/blob/master/Readme.md) to build and install DSDcc. If you install it in a custom location say `/opt/install/dsdcc` you will need to add these defines to the cmake command: `-DLIBDSDCC_INCLUDE_DIR=/opt/install/dsdcc/include/dsdcc -DLIBDSDCC_LIBRARIES=/opt/install/dsdcc/lib/libdsdcc.so` @@ -253,13 +254,6 @@ Although such serial devices work with a serial interface at 400 kb in practice Note that this is not supported in Windows because of trouble with COM port support (contributors welcome!). ---- -⚠ Since kernel 4.4.52 the default for FTDI devices (that is in the ftdi_sio kernel module) is not to set it as low latency. This results in the ThumbDV dongle not working anymore because its response is too slow to sustain the normal AMBE packets flow. The solution is to force low latency by changing the variable for your device (ex: /dev/ttyUSB0) as follows: - -`echo 1 | sudo tee /sys/bus/usb-serial/devices/ttyUSB0/latency_timer` or `sudo setserial /dev/ttyUSB0 low_latency` - ---- - Alternatively you can use [mbelib](https://github.com/szechyjs/mbelib) but mbelib comes with some copyright issues (see next). If you have mbelib installed in a custom location, say `/opt/install/mbelib` you will need to add these defines to the cmake command: `-DLIBMBE_INCLUDE_DIR=/opt/install/mbelib/include -DLIBMBE_LIBRARY=/opt/install/mbelib/lib/libmbe.so` Possible copyright issues apart (see next) the audio quality with the DVSI AMBE chip is much better. @@ -353,7 +347,7 @@ Install cmake version 3:

    Prerequisites for 16.04 LTS

    -You need to install the ffmpeg v.3 suite. Therefore you will need to add this PPA to the sources list using this command: +For DATV demodulator support you need to install the ffmpeg v.3 suite. Therefore you will need to add this PPA to the sources list using this command: `sudo add-apt-repository ppa:jonathonf/ffmpeg-3` Then do `sudo apt-get update` and go to the next step. Alternatively if you have an older version of ffmpeg suite already installed just do `sudo apt-get dist-upgrde`. diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index 8198169b8..67cb0a1b5 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -28,10 +28,12 @@ Note also that this is not supported in Windows because of trouble with COM port Alternatively you can use software decoding with Mbelib. Possible copyright issues apart (see next) the audio quality with the DVSI AMBE chip is much better. --- -⚠ Since kernel 4.4.52 the default for FTDI devices (that is in the ftdi_sio kernel module) is not to set it as low latency. This results in the ThumbDV dongle not working anymore because its response is too slow to sustain the normal AMBE packets flow. The solution is to force low latency by changing the variable for your device (ex: /dev/ttyUSB0) as follows: +⚠ With kernel 4.4.52 and maybe other 4.4 versions the default for FTDI devices (that is in the ftdi_sio kernel module) is not to set it as low latency. This results in the ThumbDV dongle not working anymore because its response is too slow to sustain the normal AMBE packets flow. The solution is to force low latency by changing the variable for your device (ex: /dev/ttyUSB0) as follows: `echo 1 | sudo tee /sys/bus/usb-serial/devices/ttyUSB0/latency_timer` or `sudo setserial /dev/ttyUSB0 low_latency` +Newer kernels do not seem to have this issue. + ---

    Mbelib support

    From deae4e6271e78ff9686600e987a7e48f565916fc Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 22 Jun 2018 17:41:15 +0200 Subject: [PATCH 527/956] DSD NXDN support: more documentation --- plugins/channelrx/demoddsd/readme.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index 67cb0a1b5..33005fcb4 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -470,8 +470,9 @@ This can be one of the following: - `+D-STAR_HD`: non-inverted D-Star header frame encountered - `-D-STAR_HD`: inverted D-Star header frame encountered - `+dPMR`: non-inverted dPMR non-packet frame - - `+NXDN`: non-inverted NXDN frame (detection only) - - `+YSF`: non-inverted Yaesu System Fusion frame (detection only) + - `+NXDN`: non-inverted NXDN frame + - `-NXDN`: inverted NXDN frame (not likely) + - `+YSF`: non-inverted Yaesu System Fusion frame

    B.4: Matched filter toggle

    @@ -559,12 +560,13 @@ This button tunes the persistence decay of the points displayer on B.1. The trac

    B.17: Maximum expected FM deviation

    -This is the one side deviation in kHz (±) leading to maximum (100%) deviation. You should adjust this value to make the figure on the signal scope fill the entire screen as shown in the screenshots above. The typical deviations by mode for a unit gain (1.0 at B.18) are: +This is the one side deviation in kHz (±) leading to maximum (100%) deviation at the discriminator output. The correct value depends on the maximum deviation imposed by the modulation scheme with some guard margin. In practice you should adjust this value to make the figure on the signal scope fill the entire screen as shown in the screenshots above. The typical deviations by mode for a unit gain (1.0 at B.18) are: - DMR: ±5.4k - dPMR: ±2.7k - D-Star: ±3.5k - YSF: ±7.0k + - NXDN: ±2.7k

    B.18: Gain after discriminator

    From 39bb65a198d6774580d07b581578e7084011e52a Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 22 Jun 2018 23:46:37 +0200 Subject: [PATCH 528/956] Debia: added NXDN files for DSDcc --- dsdcc/CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dsdcc/CMakeLists.txt b/dsdcc/CMakeLists.txt index b981ab010..5f5611cae 100644 --- a/dsdcc/CMakeLists.txt +++ b/dsdcc/CMakeLists.txt @@ -12,8 +12,11 @@ set(dsdcc_SOURCES ${LIBDSDCCSRC}/dsd_symbol.cpp ${LIBDSDCCSRC}/dstar.cpp ${LIBDSDCCSRC}/ysf.cpp - ${LIBDSDCCSRC}/nxdn.cpp ${LIBDSDCCSRC}/dpmr.cpp + ${LIBDSDCCSRC}/nxdn.cpp + ${LIBDSDCCSRC}/nxdnconvolution.cpp + ${LIBDSDCCSRC}/nxdncrc.cpp + ${LIBDSDCCSRC}/nxdnmessage.cpp ${LIBDSDCCSRC}/p25p1_heuristics.cpp ${LIBDSDCCSRC}/fec.cpp ${LIBDSDCCSRC}/crc.cpp @@ -39,8 +42,11 @@ set(dsdcc_HEADERS ${LIBDSDCCSRC}/dsd_symbol.h ${LIBDSDCCSRC}/dstar.h ${LIBDSDCCSRC}/ysf.h - ${LIBDSDCCSRC}/nxdn.h ${LIBDSDCCSRC}/dpmr.h + ${LIBDSDCCSRC}/nxdn.h + ${LIBDSDCCSRC}/nxdnconvolution.h + ${LIBDSDCCSRC}/nxdncrc.h + ${LIBDSDCCSRC}/nxdnmessage.h ${LIBDSDCCSRC}/p25p1_heuristics.h ${LIBDSDCCSRC}/runningmaxmin.h ${LIBDSDCCSRC}/doublebuffer.h From 175e4ca98a01236767d9dc49bf40b934a6769c3b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 24 Jun 2018 02:29:54 +0200 Subject: [PATCH 529/956] DV serial: use HP filter before interpolation and LP filter. Set HP -3dB corner at 300 Hz (for 8 kHz sampling rate) --- sdrbase/dsp/dvserialworker.cpp | 72 +++++++++++++++------------------- sdrbase/dsp/dvserialworker.h | 7 +++- sdrbase/dsp/filtermbe.cpp | 14 ++++++- sdrbase/dsp/filtermbe.h | 3 ++ 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/sdrbase/dsp/dvserialworker.cpp b/sdrbase/dsp/dvserialworker.cpp index 0423f48b6..7600c3cce 100644 --- a/sdrbase/dsp/dvserialworker.cpp +++ b/sdrbase/dsp/dvserialworker.cpp @@ -27,13 +27,16 @@ DVSerialWorker::DVSerialWorker() : m_running(false), m_currentGainIn(0), m_currentGainOut(0), - m_upsamplerLastValue(0), - m_phase(0) + m_upsamplerLastValue(0.0f), + m_phase(0), + m_upsampling(1), + m_volume(1.0f) { m_audioBuffer.resize(48000); m_audioBufferFill = 0; m_audioFifo = 0; memset(m_dvAudioSamples, 0, SerialDV::MBE_AUDIO_BLOCK_SIZE*sizeof(short)); + setVolumeFactors(); } DVSerialWorker::~DVSerialWorker() @@ -81,14 +84,21 @@ void DVSerialWorker::handleInputMessages() { MsgMbeDecode *decodeMsg = (MsgMbeDecode *) message; int dBVolume = (decodeMsg->getVolumeIndex() - 30) / 2; + float volume = pow(10.0, dBVolume / 10.0f); + int upsampling = decodeMsg->getUpsampling(); + upsampling = upsampling > 6 ? 6 : upsampling < 1 ? 1 : upsampling; + + if ((volume != m_volume) || (upsampling != m_upsampling)) + { + m_volume = volume; + m_upsampling = upsampling; + setVolumeFactors(); + } m_upsampleFilter.useHP(decodeMsg->getUseHP()); - if (m_dvController.decode(m_dvAudioSamples, decodeMsg->getMbeFrame(), decodeMsg->getMbeRate(), dBVolume)) + if (m_dvController.decode(m_dvAudioSamples, decodeMsg->getMbeFrame(), decodeMsg->getMbeRate())) { - int upsampling = decodeMsg->getUpsampling(); - upsampling = upsampling > 6 ? 6 : upsampling < 1 ? 1 : upsampling; - if (upsampling > 1) { upsample(upsampling, m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); } else { @@ -145,45 +155,17 @@ bool DVSerialWorker::hasFifo(AudioFifo *audioFifo) return m_audioFifo == audioFifo; } -void DVSerialWorker::upsample6(short *in, int nbSamplesIn, unsigned char channels) -{ - for (int i = 0; i < nbSamplesIn; i++) - { - int cur = (int) in[i]; - int prev = (int) m_upsamplerLastValue; - qint16 upsample; - - for (int j = 1; j < 7; j++) - { - upsample = m_upsampleFilter.run((qint16) ((cur*j + prev*(6-j)) / 6)); - m_audioBuffer[m_audioBufferFill].l = channels & 1 ? upsample : 0; - m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? upsample : 0; - - if (m_audioBufferFill < m_audioBuffer.size() - 1) - { - ++m_audioBufferFill; - } - else - { - qDebug("DVSerialWorker::upsample6: audio buffer is full check its size"); - } - } - - m_upsamplerLastValue = in[i]; - } -} - void DVSerialWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels) { for (int i = 0; i < nbSamplesIn; i++) { - int cur = (int) in[i]; - int prev = (int) m_upsamplerLastValue; + float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i]; + float prev = m_upsamplerLastValue; qint16 upsample; for (int j = 1; j <= upsampling; j++) { - upsample = m_upsampleFilter.run((qint16) ((cur*j + prev*(upsampling-j)) / upsampling)); + upsample = (qint16) m_upsampleFilter.runLP(cur*m_upsamplingFactors[j] + prev*m_upsamplingFactors[upsampling-j]); m_audioBuffer[m_audioBufferFill].l = channels & 1 ? upsample : 0; m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? upsample : 0; @@ -197,7 +179,7 @@ void DVSerialWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsign } } - m_upsamplerLastValue = in[i]; + m_upsamplerLastValue = cur; } } @@ -205,8 +187,9 @@ void DVSerialWorker::noUpsample(short *in, int nbSamplesIn, unsigned char channe { for (int i = 0; i < nbSamplesIn; i++) { - m_audioBuffer[m_audioBufferFill].l = channels & 1 ? in[i] : 0; - m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? in[i] : 0; + float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i]; + m_audioBuffer[m_audioBufferFill].l = channels & 1 ? cur*m_upsamplingFactors[0] : 0; + m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? cur*m_upsamplingFactors[0] : 0; if (m_audioBufferFill < m_audioBuffer.size() - 1) { @@ -219,6 +202,15 @@ void DVSerialWorker::noUpsample(short *in, int nbSamplesIn, unsigned char channe } } +void DVSerialWorker::setVolumeFactors() +{ + m_upsamplingFactors[0] = m_volume; + + for (int i = 1; i <= m_upsampling; i++) { + m_upsamplingFactors[i] = (i*m_volume) / (float) m_upsampling; + } +} + //void DVSerialWorker::upsample6(short *in, short *out, int nbSamplesIn) //{ // for (int i = 0; i < nbSamplesIn; i++) diff --git a/sdrbase/dsp/dvserialworker.h b/sdrbase/dsp/dvserialworker.h index 87f2d44ef..e1ea1a347 100644 --- a/sdrbase/dsp/dvserialworker.h +++ b/sdrbase/dsp/dvserialworker.h @@ -135,9 +135,9 @@ public slots: private: //void upsample6(short *in, short *out, int nbSamplesIn); - void upsample6(short *in, int nbSamplesIn, unsigned char channels); void upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels); void noUpsample(short *in, int nbSamplesIn, unsigned char channels); + void setVolumeFactors(); SerialDV::DVController m_dvController; volatile bool m_running; @@ -147,9 +147,12 @@ private: //short m_audioSamples[SerialDV::MBE_AUDIO_BLOCK_SIZE * 6 * 2]; // upsample to 48k and duplicate channel AudioVector m_audioBuffer; uint m_audioBufferFill; - short m_upsamplerLastValue; + float m_upsamplerLastValue; float m_phase; MBEAudioInterpolatorFilter m_upsampleFilter; + int m_upsampling; + float m_volume; + float m_upsamplingFactors[7]; }; #endif /* SDRBASE_DSP_DVSERIALWORKER_H_ */ diff --git a/sdrbase/dsp/filtermbe.cpp b/sdrbase/dsp/filtermbe.cpp index 63b5dc9da..bad18b3f2 100644 --- a/sdrbase/dsp/filtermbe.cpp +++ b/sdrbase/dsp/filtermbe.cpp @@ -20,8 +20,8 @@ const float MBEAudioInterpolatorFilter::m_lpa[3] = {1.0, 1.392667E+00, -5.474446E-01}; const float MBEAudioInterpolatorFilter::m_lpb[3] = {3.869430E-02, 7.738860E-02, 3.869430E-02}; -const float MBEAudioInterpolatorFilter::m_hpa[3] = {1.000000e+00, 1.955578e+00, -9.565437e-01}; -const float MBEAudioInterpolatorFilter::m_hpb[3] = {9.780305e-01, -1.956061e+00, 9.780305e-01}; +const float MBEAudioInterpolatorFilter::m_hpa[3] = {1.000000e+00, 1.667871e+00, -7.156964e-01}; +const float MBEAudioInterpolatorFilter::m_hpb[3] = {8.459039e-01, -1.691760e+00, 8.459039e-01}; MBEAudioInterpolatorFilter::MBEAudioInterpolatorFilter() : m_filterLP(m_lpa, m_lpb), @@ -37,3 +37,13 @@ float MBEAudioInterpolatorFilter::run(const float& sample) { return m_useHP ? m_filterLP.run(m_filterHP.run(sample)) : m_filterLP.run(sample); } + +float MBEAudioInterpolatorFilter::runHP(const float& sample) +{ + return m_filterHP.run(sample); +} + +float MBEAudioInterpolatorFilter::runLP(const float& sample) +{ + return m_filterLP.run(sample); +} diff --git a/sdrbase/dsp/filtermbe.h b/sdrbase/dsp/filtermbe.h index a9ce75db3..2b4cc3dc1 100644 --- a/sdrbase/dsp/filtermbe.h +++ b/sdrbase/dsp/filtermbe.h @@ -67,7 +67,10 @@ public: ~MBEAudioInterpolatorFilter(); void useHP(bool useHP) { m_useHP = useHP; } + bool usesHP() const { return m_useHP; } float run(const float& sample); + float runHP(const float& sample); + float runLP(const float& sample); private: IIRFilter m_filterLP; From 12380d4e517161d994a31eca7cab27ebde8a8a80 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 25 Jun 2018 00:01:25 +0200 Subject: [PATCH 530/956] DSD demod: use audio compressor when processing voice with serial DV --- sdrbase/CMakeLists.txt | 2 + sdrbase/audio/audiocompressor.cpp | 87 +++++++++++++++++++++++++++++++ sdrbase/audio/audiocompressor.h | 39 ++++++++++++++ sdrbase/dsp/dvserialworker.cpp | 2 +- sdrbase/dsp/dvserialworker.h | 2 + 5 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 sdrbase/audio/audiocompressor.cpp create mode 100644 sdrbase/audio/audiocompressor.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 32191b24f..d29478331 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -3,6 +3,7 @@ project (sdrbase) #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") set(sdrbase_SOURCES + audio/audiocompressor.cpp audio/audiodevicemanager.cpp audio/audiofifo.cpp audio/audiooutput.cpp @@ -90,6 +91,7 @@ set(sdrbase_SOURCES ) set(sdrbase_HEADERS + audio/audiocompressor.h audio/audiodevicemanager.h audio/audiofifo.h audio/audiooutput.h diff --git a/sdrbase/audio/audiocompressor.cpp b/sdrbase/audio/audiocompressor.cpp new file mode 100644 index 000000000..fd25257f7 --- /dev/null +++ b/sdrbase/audio/audiocompressor.cpp @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 "audiocompressor.h" + +AudioCompressor::AudioCompressor() +{ + fillLUT2(); +} + +AudioCompressor::~AudioCompressor() +{} + +void AudioCompressor::fillLUT() +{ + for (int i=0; i<8192; i++) { + m_lut[i] = (24576/8192)*i; + } + + for (int i=8192; i<2*8192; i++) { + m_lut[i] = 24576 + 0.5f*(i-8192); + } + + for (int i=2*8192; i<3*8192; i++) { + m_lut[i] = 24576 + 4096 + 0.25f*(i-2*8192); + } + + for (int i=3*8192; i<4*8192; i++) { + m_lut[i] = 24576 + 4096 + 2048 + 0.125f*(i-3*8192); + } +} + +void AudioCompressor::fillLUT2() +{ + for (int i=0; i<4096; i++) { + m_lut[i] = (24576/4096)*i; + } + + for (int i=4096; i<2*4096; i++) { + m_lut[i] = 24576 + 0.5f*(i-4096); + } + + for (int i=2*4096; i<3*4096; i++) { + m_lut[i] = 24576 + 2048 + 0.25f*(i-2*4096); + } + + for (int i=3*4096; i<4*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 0.125f*(i-3*4096); + } + + for (int i=4*4096; i<5*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 512 + 0.0625f*(i-4*4096); + } + + for (int i=5*4096; i<6*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 512 + 256 + 0.03125f*(i-5*4096); + } + + for (int i=6*4096; i<7*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 512 + 256 + 128 + 0.015625f*(i-6*4096); + } + + for (int i=7*4096; i<8*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 512 + 256 + 128 + 64 + 0.0078125f*(i-7*4096); + } +} + +int16_t AudioCompressor::compress(int16_t sample) +{ + int16_t sign = sample < 0 ? -1 : 1; + int16_t abs = sample < 0 ? -sample : sample; + return sign * m_lut[abs]; +} diff --git a/sdrbase/audio/audiocompressor.h b/sdrbase/audio/audiocompressor.h new file mode 100644 index 000000000..7f7ba0edd --- /dev/null +++ b/sdrbase/audio/audiocompressor.h @@ -0,0 +1,39 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 SDRBASE_AUDIO_AUDIOCOMPRESSOR_H_ +#define SDRBASE_AUDIO_AUDIOCOMPRESSOR_H_ + +#include +#include "export.h" + +class SDRBASE_API AudioCompressor +{ +public: + AudioCompressor(); + ~AudioCompressor(); + void fillLUT(); //!< 4 bands + void fillLUT2(); //!< 8 bands (default) + int16_t compress(int16_t sample); + +private: + int16_t m_lut[32768]; +}; + + + +#endif /* SDRBASE_AUDIO_AUDIOCOMPRESSOR_H_ */ diff --git a/sdrbase/dsp/dvserialworker.cpp b/sdrbase/dsp/dvserialworker.cpp index 7600c3cce..99603c942 100644 --- a/sdrbase/dsp/dvserialworker.cpp +++ b/sdrbase/dsp/dvserialworker.cpp @@ -159,7 +159,7 @@ void DVSerialWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsign { for (int i = 0; i < nbSamplesIn; i++) { - float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i]; + float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) m_compressor.compress(in[i])) : (float) m_compressor.compress(in[i]); float prev = m_upsamplerLastValue; qint16 upsample; diff --git a/sdrbase/dsp/dvserialworker.h b/sdrbase/dsp/dvserialworker.h index e1ea1a347..e395a5850 100644 --- a/sdrbase/dsp/dvserialworker.h +++ b/sdrbase/dsp/dvserialworker.h @@ -32,6 +32,7 @@ #include "export.h" #include "dsp/filtermbe.h" #include "dsp/dsptypes.h" +#include "audio/audiocompressor.h" class AudioFifo; @@ -153,6 +154,7 @@ private: int m_upsampling; float m_volume; float m_upsamplingFactors[7]; + AudioCompressor m_compressor; }; #endif /* SDRBASE_DSP_DVSERIALWORKER_H_ */ From 5f120fb2cbb96cf80b1b4759a4998bbee62374f8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 25 Jun 2018 01:05:13 +0200 Subject: [PATCH 531/956] DSD decoder: NXDN: support for EFR vocoder only with DV serial --- plugins/channelrx/demoddsd/dsddemod.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 117eeb09c..edce8becd 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -666,8 +666,9 @@ void DSDDemod::formatStatusText() { // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - // RC cc mm llllll ssss - snprintf(m_formatStatusText, 82, "RC %02d %02X %06X %02X", + // RC r cc mm llllll ssss + snprintf(m_formatStatusText, 82, "RC %s %02d %02X %06X %02X", + getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", getDecoder().getNXDNDecoder().getRAN(), getDecoder().getNXDNDecoder().getMessageType(), getDecoder().getNXDNDecoder().getLocationId(), @@ -683,9 +684,10 @@ void DSDDemod::formatStatusText() { // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - // Rx cc mm sssss>gddddd - snprintf(m_formatStatusText, 82, "%s %02d %02X %05d>%c%05d", + // Rx r cc mm sssss>gddddd + snprintf(m_formatStatusText, 82, "%s %s %02d %02X %05d>%c%05d", getDecoder().getNXDNDecoder().getRFChannelStr(), + getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", getDecoder().getNXDNDecoder().getRAN(), getDecoder().getNXDNDecoder().getMessageType(), getDecoder().getNXDNDecoder().getSourceId(), From facb282c23b3d8a8e8a4aa3e502b4d07ea8caefd Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 25 Jun 2018 23:44:11 +0200 Subject: [PATCH 532/956] Scope: corrected mean power dB overlay displays --- sdrgui/dsp/scopevisng.cpp | 25 +++++++++++++------------ sdrgui/dsp/scopevisng.h | 4 ++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index 0d50a1548..ed01d81f0 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -465,10 +465,10 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const } else if (projectionType == Projector::ProjectionMagDB) { - // there is no processing advantage in direct calculation without projector -// uint32_t magsq = begin->m_real*begin->m_real + begin->m_imag*begin->m_imag; -// v = ((log10f(magsq/1073741824.0f)*0.2f - 2.0f*itData->m_ofs) + 2.0f)*itData->m_amp - 1.0f; - float pdB = (*itCtl)->m_projector.run(*begin); + Real re = begin->m_real / SDR_RX_SCALEF; + Real im = begin->m_imag / SDR_RX_SCALEF; + double magsq = re*re + im*im; + float pdB = log10f(magsq) * 10.0f; float p = pdB - (100.0f * itData->m_ofs); v = ((p/50.0f) + 2.0f)*itData->m_amp - 1.0f; @@ -476,28 +476,29 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const { if (traceCount == shift) { - (*itCtl)->m_maxPow = -200.0f; + (*itCtl)->m_maxPow = 0.0f; (*itCtl)->m_sumPow = 0.0f; (*itCtl)->m_nbPow = 1; } - if (pdB > -200.0f) + if (magsq > 0.0f) { - if (pdB > (*itCtl)->m_maxPow) + if (magsq > (*itCtl)->m_maxPow) { - (*itCtl)->m_maxPow = pdB; + (*itCtl)->m_maxPow = magsq; } - (*itCtl)->m_sumPow += pdB; + (*itCtl)->m_sumPow += magsq; (*itCtl)->m_nbPow++; } } if ((m_nbSamples == 1) && ((*itCtl)->m_nbPow > 0)) // on last sample create power display overlay { - double avgPow = (*itCtl)->m_sumPow / (*itCtl)->m_nbPow; - double peakToAvgPow = (*itCtl)->m_maxPow - avgPow; - itData->m_textOverlay = QString("%1 %2 %3").arg((*itCtl)->m_maxPow, 0, 'f', 1).arg(avgPow, 0, 'f', 1).arg(peakToAvgPow, 4, 'f', 1, ' '); + double avgPow = log10f((*itCtl)->m_sumPow / (*itCtl)->m_nbPow)*10.0; + double peakPow = log10f((*itCtl)->m_maxPow)*10.0; + double peakToAvgPow = peakPow - avgPow; + itData->m_textOverlay = QString("%1 %2 %3").arg(peakPow, 0, 'f', 1).arg(avgPow, 0, 'f', 1).arg(peakToAvgPow, 4, 'f', 1, ' '); (*itCtl)->m_nbPow = 0; } } diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index dc9f31cc9..2560fb3b0 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -681,8 +681,8 @@ private: { Projector m_projector; //!< Projector transform from complex trace to real (displayable) trace uint32_t m_traceCount[2]; //!< Count of samples processed (double buffered) - Real m_maxPow; //!< Maximum power over the current trace for MagDB overlay display - Real m_sumPow; //!< Cumulative power over the current trace for MagDB overlay display + double m_maxPow; //!< Maximum power over the current trace for MagDB overlay display + double m_sumPow; //!< Cumulative power over the current trace for MagDB overlay display int m_nbPow; //!< Number of power samples over the current trace for MagDB overlay display TraceControl() : m_projector(Projector::ProjectionReal) From 80b7829bf7aa9985bd090898cbc8295578744d14 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 26 Jun 2018 00:43:19 +0200 Subject: [PATCH 533/956] Scope and channel analyzer: new squared magnitude (linear power) projection mainly for radioastronomy --- debian/changelog | 1 + plugins/channelrx/chanalyzer/chanalyzerplugin.cpp | 2 +- plugins/channelrx/chanalyzer/readme.md | 10 ++++++++++ sdrbase/dsp/projector.cpp | 7 +++++++ sdrbase/dsp/projector.h | 1 + sdrgui/dsp/scopevisng.cpp | 5 +++++ sdrgui/gui/glscopeng.cpp | 1 + sdrgui/gui/glscopenggui.cpp | 7 ++++--- 8 files changed, 30 insertions(+), 4 deletions(-) diff --git a/debian/changelog b/debian/changelog index 3da824284..7a90116b6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,7 @@ sdrangel (4.0.1-1) unstable; urgency=medium * DSD demod: added NXDN support * DATV demod: include it only if FFmpeg > 3.1 is installed * Fixes for Arch. Manual merge of pull request #183 + * Scope: new magnitude squared projection mainly for radioastronomy -- Edouard Griffiths, F4EXB Sat, 23 Jun 2018 09:14:18 +0200 diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index ee0b9df73..475029110 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("4.0.0"), + QString("4.0.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/chanalyzer/readme.md b/plugins/channelrx/chanalyzer/readme.md index 5deb96ab7..653c76896 100644 --- a/plugins/channelrx/chanalyzer/readme.md +++ b/plugins/channelrx/chanalyzer/readme.md @@ -9,9 +9,14 @@ This plugin can be used to analyze the complex signal received in its passband. - Real part - Imaginary part - Magnitude linear + - Power i.e. squared magnitude linear - Power i.e. squared magnitude log (dB) - Phase - Phase derivative (instant frequency) + - BPSK symbol mapping + - QPSK symbol mapping + - 8-PSK symbol mapping + - 16-PSK symbol mapping The same waveforms can be used to trigger the scope trace @@ -211,6 +216,7 @@ To construct a trace which represents real values the incoming complex signal mu - Real: take the real part - Imag: take the imaginary part - Mag: calculate magnitude in linear representation. This is just the module of the complex sample + - MagSq: calculate power in linear representation. This is the squared module of the complex sample - MagDB: calculate power in log representation as 10*log10(x) or decibel (dB) representation. This is the squared module of the complex sample expressed in decibels - Phi: instantaneous phase. This is the argument of the complex sample. - dPhi: instantaneous derivative of the phase. This is the difference of arguments between successive samples thus represents the instantaneous frequency. @@ -283,6 +289,7 @@ The top slider is a coarse adjustment. Each step moves the trace by an amount th - Real, Imag: 0.01 - Mag: 0.005 + - MagSq: 0.005 - MagDB: 1 dB - Phi, dPhi: 0.01 @@ -290,6 +297,7 @@ The bottom slider is a fine adjustment. Each step moves the trace by an amount t - Real, Imag: 50.0E-6 - Mag: 25.0sE-6 + - MagSq: 25.0sE-6 - MagDB: 0.01 dB - Phi, dPhi: 50.0E-6 @@ -370,6 +378,7 @@ The top slider is a coarse adjustment. Each step moves the trigger level by an a - Real, Imag: 0.01 - Mag: 0.005 + - MagSq: 0.005 - MagDB: 1 dB - Phi, dPhi: 0.01 @@ -377,6 +386,7 @@ The bottom slider is a fine adjustment. Each step moves the trigger level by an - Real, Imag: 50.0E-6 - Mag: 25.0sE-6 + - MagSq: 25.0sE-6 - MagDB: 0.01 dB - Phi, dPhi: 50.0E-6 diff --git a/sdrbase/dsp/projector.cpp b/sdrbase/dsp/projector.cpp index 1d9a66c86..887be37c0 100644 --- a/sdrbase/dsp/projector.cpp +++ b/sdrbase/dsp/projector.cpp @@ -52,6 +52,13 @@ Real Projector::run(const Sample& s) v = std::sqrt(magsq); } break; + case ProjectionMagSq: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + v = re*re + im*im; + } + break; case ProjectionMagDB: { Real re = s.m_real / SDR_RX_SCALEF; diff --git a/sdrbase/dsp/projector.h b/sdrbase/dsp/projector.h index 094c8c592..3e2f65602 100644 --- a/sdrbase/dsp/projector.h +++ b/sdrbase/dsp/projector.h @@ -25,6 +25,7 @@ public: ProjectionReal = 0, //!< Extract real part ProjectionImag, //!< Extract imaginary part ProjectionMagLin, //!< Calculate linear magnitude or modulus + ProjectionMagSq, //!< Calculate linear squared magnitude or power ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude ProjectionPhase, //!< Calculate phase ProjectionDPhase, //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index ed01d81f0..70a8044c3 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -463,6 +463,11 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const { v = ((*itCtl)->m_projector.run(*begin) - itData->m_ofs)*itData->m_amp - 1.0f; } + else if (projectionType == Projector::ProjectionMagSq) + { + v = ((*itCtl)->m_projector.run(*begin) - itData->m_ofs)*itData->m_amp - 1.0f; + // TODO: power display overlay for squared magnitude + } else if (projectionType == Projector::ProjectionMagDB) { Real re = begin->m_real / SDR_RX_SCALEF; diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index e2cae6be7..a802313b9 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -1877,6 +1877,7 @@ void GLScopeNG::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) scale.setRange(Unit::Decibel, pow_floor, pow_floor + pow_range); break; case Projector::ProjectionMagLin: + case Projector::ProjectionMagSq: if (amp_range < 2.0) { scale.setRange(Unit::None, amp_ofs * 1000.0, amp_range * 1000.0 + amp_ofs * 1000.0); } else { diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 32087f0dc..425d45f2f 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -1021,7 +1021,7 @@ void GLScopeNGGUI::setAmpOfsDisplay() { double a; - if (projectionType == Projector::ProjectionMagLin) + if ((projectionType == Projector::ProjectionMagLin) || (projectionType == Projector::ProjectionMagSq)) { a = o/2000.0f; } @@ -1087,7 +1087,7 @@ void GLScopeNGGUI::setTrigLevelDisplay() { double a; - if (projectionType == Projector::ProjectionMagLin) { + if ((projectionType == Projector::ProjectionMagLin) || (projectionType == Projector::ProjectionMagSq)) { a = 1.0 + t; } else { a = t; @@ -1184,6 +1184,7 @@ void GLScopeNGGUI::fillProjectionCombo(QComboBox* comboBox) comboBox->addItem("Real", Projector::ProjectionReal); comboBox->addItem("Imag", Projector::ProjectionImag); comboBox->addItem("Mag", Projector::ProjectionMagLin); + comboBox->addItem("MagSq", Projector::ProjectionMagSq); comboBox->addItem("MagdB", Projector::ProjectionMagDB); comboBox->addItem("Phi", Projector::ProjectionPhase); comboBox->addItem("dPhi", Projector::ProjectionDPhase); @@ -1225,7 +1226,7 @@ void GLScopeNGGUI::fillTraceData(ScopeVisNG::TraceData& traceData) traceData.m_ofsCoarse = ui->ofsCoarse->value(); traceData.m_ofsFine = ui->ofsFine->value(); - if (traceData.m_projectionType == Projector::ProjectionMagLin) { + if ((traceData.m_projectionType == Projector::ProjectionMagLin) || (traceData.m_projectionType == Projector::ProjectionMagSq)) { traceData.m_ofs = ((10.0 * ui->ofsCoarse->value()) + (ui->ofsFine->value() / 20.0)) / 2000.0f; } else { traceData.m_ofs = ((10.0 * ui->ofsCoarse->value()) + (ui->ofsFine->value() / 20.0)) / 1000.0f; From fd915613e42b95630743904629a293162d94df54 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 26 Jun 2018 01:06:45 +0200 Subject: [PATCH 534/956] Scope: added magnitude squared (linear power) display overlays --- doc/img/ChAnalyzerNG_plugin_overlay_lin.png | Bin 0 -> 2106 bytes doc/img/ChAnalyzerNG_plugin_overlay_lin.xcf | Bin 0 -> 6573 bytes plugins/channelrx/chanalyzer/readme.md | 4 +++ sdrgui/dsp/scopevisng.cpp | 32 ++++++++++++++++++-- sdrgui/gui/glscopenggui.cpp | 2 +- 5 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 doc/img/ChAnalyzerNG_plugin_overlay_lin.png create mode 100644 doc/img/ChAnalyzerNG_plugin_overlay_lin.xcf diff --git a/doc/img/ChAnalyzerNG_plugin_overlay_lin.png b/doc/img/ChAnalyzerNG_plugin_overlay_lin.png new file mode 100644 index 0000000000000000000000000000000000000000..23706118d0d30e1d05b0acb00900267c0a404c43 GIT binary patch literal 2106 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 literal 0 HcmV?d00001 diff --git a/plugins/channelrx/chanalyzer/readme.md b/plugins/channelrx/chanalyzer/readme.md index 653c76896..e4f069307 100644 --- a/plugins/channelrx/chanalyzer/readme.md +++ b/plugins/channelrx/chanalyzer/readme.md @@ -269,6 +269,10 @@ The signal is synchronized with the PLL in 4 phase mode (locker icon is green). ![Channel Analyzer NG plugin scope1 controls](../../../doc/img/ChAnalyzerNG_plugin_overlay_dB.png) +**Note3**: in the MagSq mode when the trace is selected (1) the display overlay on the top right of the trace shows 2 figures in scientific notation. From left to right: peak power and average power. + +![Channel Analyzer NG plugin scope2 controls](../../../doc/img/ChAnalyzerNG_plugin_overlay_lin.png) +

    5. Source select

    This is for future use when more than one incoming complex signals can be applied. The signal index appears on the right of the button diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index 70a8044c3..ca6377310 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -465,8 +465,36 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const } else if (projectionType == Projector::ProjectionMagSq) { - v = ((*itCtl)->m_projector.run(*begin) - itData->m_ofs)*itData->m_amp - 1.0f; - // TODO: power display overlay for squared magnitude + Real magsq = (*itCtl)->m_projector.run(*begin); + v = (magsq - itData->m_ofs)*itData->m_amp - 1.0f; + + if ((traceCount >= shift) && (traceCount < shift+length)) // power display overlay values construction + { + if (traceCount == shift) + { + (*itCtl)->m_maxPow = 0.0f; + (*itCtl)->m_sumPow = 0.0f; + (*itCtl)->m_nbPow = 1; + } + + if (magsq > 0.0f) + { + if (magsq > (*itCtl)->m_maxPow) + { + (*itCtl)->m_maxPow = magsq; + } + + (*itCtl)->m_sumPow += magsq; + (*itCtl)->m_nbPow++; + } + } + + if ((m_nbSamples == 1) && ((*itCtl)->m_nbPow > 0)) // on last sample create power display overlay + { + double avgPow = (*itCtl)->m_sumPow / (*itCtl)->m_nbPow; + itData->m_textOverlay = QString("%1 %2").arg((*itCtl)->m_maxPow, 0, 'e', 2).arg(avgPow, 0, 'e', 2); + (*itCtl)->m_nbPow = 0; + } } else if (projectionType == Projector::ProjectionMagDB) { diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 425d45f2f..3362ec55f 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -1217,7 +1217,7 @@ void GLScopeNGGUI::disableLiveMode(bool disable) void GLScopeNGGUI::fillTraceData(ScopeVisNG::TraceData& traceData) { traceData.m_projectionType = (Projector::ProjectionType) ui->traceMode->currentIndex(); - traceData.m_hasTextOverlay = (traceData.m_projectionType == Projector::ProjectionMagDB); + traceData.m_hasTextOverlay = (traceData.m_projectionType == Projector::ProjectionMagDB) || (traceData.m_projectionType == Projector::ProjectionMagSq); traceData.m_textOverlay.clear(); traceData.m_inputIndex = 0; traceData.m_amp = 0.2 / amps[ui->amp->value()]; From cc03445377604a320ecfa179ce09938695f8efb8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 26 Jun 2018 01:39:23 +0200 Subject: [PATCH 535/956] Scope: added amplification down to 100u full scale adnd finer trigger level down to 20u per step --- plugins/channelrx/chanalyzer/readme.md | 12 ++++++------ sdrgui/dsp/scopevisng.cpp | 2 +- sdrgui/gui/glscopenggui.cpp | 8 ++++---- sdrgui/gui/glscopenggui.h | 2 +- sdrgui/gui/glscopenggui.ui | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/channelrx/chanalyzer/readme.md b/plugins/channelrx/chanalyzer/readme.md index e4f069307..17b9f20cb 100644 --- a/plugins/channelrx/chanalyzer/readme.md +++ b/plugins/channelrx/chanalyzer/readme.md @@ -381,18 +381,18 @@ This pair of sliders let you adjust the trigger level, The level appears on the The top slider is a coarse adjustment. Each step moves the trigger level by an amount that depends on the projection type: - Real, Imag: 0.01 - - Mag: 0.005 - - MagSq: 0.005 + - Mag: 0.01 + - MagSq: 0.01 - MagDB: 1 dB - Phi, dPhi: 0.01 The bottom slider is a fine adjustment. Each step moves the trigger level by an amount that depends on the projection type: - - Real, Imag: 50.0E-6 - - Mag: 25.0sE-6 - - MagSq: 25.0sE-6 + - Real, Imag: 20.0E-6 + - Mag: 20.0sE-6 + - MagSq: 20.0sE-6 - MagDB: 0.01 dB - - Phi, dPhi: 50.0E-6 + - Phi, dPhi: 20.0E-6

    10: Trigger delay

    diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index ca6377310..92cacf781 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -915,7 +915,7 @@ void ScopeVisNG::computeDisplayTriggerLevels() float levelPowerdB = (100.0f * (level - 1.0f)); float v; - if (itData->m_projectionType == Projector::ProjectionMagLin) + if ((itData->m_projectionType == Projector::ProjectionMagLin) || (itData->m_projectionType == Projector::ProjectionMagSq)) { v = (levelPowerLin - itData->m_ofs)*itData->m_amp - 1.0f; } diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 3362ec55f..b03e29f9b 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -22,7 +22,7 @@ #include "ui_glscopenggui.h" #include "util/simpleserializer.h" -const double GLScopeNGGUI::amps[11] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001 }; +const double GLScopeNGGUI::amps[14] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001, 0.00005, 0.00002, 0.00001 }; GLScopeNGGUI::GLScopeNGGUI(QWidget* parent) : QWidget(parent), @@ -1074,11 +1074,11 @@ void GLScopeNGGUI::setTrigIndexDisplay() void GLScopeNGGUI::setTrigLevelDisplay() { - double t = (ui->trigLevelCoarse->value() / 100.0f) + (ui->trigLevelFine->value() / 20000.0f); + double t = (ui->trigLevelCoarse->value() / 100.0f) + (ui->trigLevelFine->value() / 50000.0f); Projector::ProjectionType projectionType = (Projector::ProjectionType) ui->trigMode->currentIndex(); ui->trigLevelCoarse->setToolTip(QString("Trigger level coarse: %1 %").arg(ui->trigLevelCoarse->value() / 100.0f)); - ui->trigLevelFine->setToolTip(QString("Trigger level fine: %1 ppm").arg(ui->trigLevelFine->value() * 50)); + ui->trigLevelFine->setToolTip(QString("Trigger level fine: %1 ppm").arg(ui->trigLevelFine->value() * 20)); if (projectionType == Projector::ProjectionMagDB) { ui->trigLevelText->setText(tr("%1\ndB").arg(100.0 * (t - 1.0), 0, 'f', 1)); @@ -1243,7 +1243,7 @@ void GLScopeNGGUI::fillTriggerData(ScopeVisNG::TriggerData& triggerData) { triggerData.m_projectionType = (Projector::ProjectionType) ui->trigMode->currentIndex(); triggerData.m_inputIndex = 0; - triggerData.m_triggerLevel = (ui->trigLevelCoarse->value() / 100.0) + (ui->trigLevelFine->value() / 20000.0); + triggerData.m_triggerLevel = (ui->trigLevelCoarse->value() / 100.0) + (ui->trigLevelFine->value() / 50000.0); triggerData.m_triggerLevelCoarse = ui->trigLevelCoarse->value(); triggerData.m_triggerLevelFine = ui->trigLevelFine->value(); triggerData.m_triggerPositiveEdge = ui->trigPos->isChecked(); diff --git a/sdrgui/gui/glscopenggui.h b/sdrgui/gui/glscopenggui.h index fb98c0c29..e9c1e4cb8 100644 --- a/sdrgui/gui/glscopenggui.h +++ b/sdrgui/gui/glscopenggui.h @@ -153,7 +153,7 @@ private: QColor m_focusedTraceColor; QColor m_focusedTriggerColor; - static const double amps[11]; + static const double amps[14]; void applySettings(); // First row diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index 11d47e44a..f611941bd 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -859,7 +859,7 @@ kS/s Vertical range
    - 10 + 13 1 @@ -1565,7 +1565,7 @@ kS/s Trigger level fine - 200 + 500 1 From 23ba4b9ec8b88383621ca341cd6b4075371813fa Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 26 Jun 2018 07:42:52 +0200 Subject: [PATCH 536/956] Scope: allow trace length multiplier up to 100 --- sdrgui/gui/glscopenggui.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index f611941bd..fd9583f90 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -473,7 +473,7 @@ 1 - 20 + 100 1 From 7365b2decefe86fe1faf538f5640e60d43796658 Mon Sep 17 00:00:00 2001 From: Edouard Griffiths Date: Tue, 26 Jun 2018 20:03:56 +0200 Subject: [PATCH 537/956] Channel Analyzer: corrected suqared magnitude (power) display in dB --- plugins/channelrx/chanalyzer/chanalyzer.cpp | 1 + plugins/channelrx/chanalyzer/chanalyzer.h | 3 +++ plugins/channelrx/chanalyzer/chanalyzergui.cpp | 6 +++--- plugins/channelrx/chanalyzer/chanalyzergui.h | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzer.cpp b/plugins/channelrx/chanalyzer/chanalyzer.cpp index ac3a059ae..3299e5ab5 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzer.cpp @@ -156,6 +156,7 @@ void ChannelAnalyzer::processOneSample(Complex& c, fftfilt::cmplx *sideband) Real re = m_sum.real() / SDR_RX_SCALEF; Real im = m_sum.imag() / SDR_RX_SCALEF; m_magsq = re*re + im*im; + m_channelPowerAvg(m_magsq); std::complex mix; if (m_settings.m_pll) diff --git a/plugins/channelrx/chanalyzer/chanalyzer.h b/plugins/channelrx/chanalyzer/chanalyzer.h index fb7db116f..6a56ab486 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.h +++ b/plugins/channelrx/chanalyzer/chanalyzer.h @@ -30,6 +30,7 @@ #include "dsp/freqlockcomplex.h" #include "audio/audiofifo.h" #include "util/message.h" +#include "util/movingaverage.h" #include "chanalyzersettings.h" @@ -188,6 +189,7 @@ public: int getInputSampleRate() const { return m_inputSampleRate; } int getChannelSampleRate() const { return m_settings.m_downSample ? m_settings.m_downSampleRate : m_inputSampleRate; } double getMagSq() const { return m_magsq; } + double getMagSqAvg() const { return (double) m_channelPowerAvg; } bool isPllLocked() const { return m_settings.m_pll && m_pll.locked(); } Real getPllFrequency() const { return m_pll.getFreq(); } Real getPllDeltaPhase() const { return m_pll.getDeltaPhi(); } @@ -236,6 +238,7 @@ private: BasebandSampleSink* m_sampleSink; SampleVector m_sampleBuffer; + MovingAverageUtil m_channelPowerAvg; QMutex m_settingsMutex; // void apply(bool force = false); diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 81a5c84aa..bcb9564da 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -226,9 +226,9 @@ void ChannelAnalyzerGUI::channelMarkerHighlightedByCursor() void ChannelAnalyzerGUI::tick() { - double powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq()); - m_channelPowerDbAvg(powDb); - ui->channelPower->setText(tr("%1 dB").arg((Real) m_channelPowerDbAvg, 0, 'f', 1)); + m_channelPowerAvg(m_channelAnalyzer->getMagSqAvg()); + double powDb = CalcDb::dbPower((double) m_channelPowerAvg); + ui->channelPower->setText(tr("%1 dB").arg(powDb, 0, 'f', 1)); if (m_channelAnalyzer->isPllLocked()) { ui->pll->setStyleSheet("QToolButton { background-color : green; }"); diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h index 5210b2171..48c55695a 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ b/plugins/channelrx/chanalyzer/chanalyzergui.h @@ -68,7 +68,7 @@ private: ChannelAnalyzerSettings m_settings; bool m_doApplySettings; int m_rate; //!< sample rate after final in-channel decimation (spanlog2) - MovingAverageUtil m_channelPowerDbAvg; + MovingAverageUtil m_channelPowerAvg; ChannelAnalyzer* m_channelAnalyzer; SpectrumScopeNGComboVis* m_spectrumScopeComboVis; From 8df057d7c5fd4fe49326640335487a3714a363b1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 27 Jun 2018 09:15:10 +0200 Subject: [PATCH 538/956] DSD demod: serial DV volume fix --- sdrbase/dsp/dvserialworker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrbase/dsp/dvserialworker.cpp b/sdrbase/dsp/dvserialworker.cpp index 99603c942..b17ea481c 100644 --- a/sdrbase/dsp/dvserialworker.cpp +++ b/sdrbase/dsp/dvserialworker.cpp @@ -83,7 +83,7 @@ void DVSerialWorker::handleInputMessages() if (MsgMbeDecode::match(*message)) { MsgMbeDecode *decodeMsg = (MsgMbeDecode *) message; - int dBVolume = (decodeMsg->getVolumeIndex() - 30) / 2; + int dBVolume = (decodeMsg->getVolumeIndex() - 30) / 4; float volume = pow(10.0, dBVolume / 10.0f); int upsampling = decodeMsg->getUpsampling(); upsampling = upsampling > 6 ? 6 : upsampling < 1 ? 1 : upsampling; From 95f39109f757a2640f949305ecb33c82d3cfa0c3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 27 Jun 2018 09:15:31 +0200 Subject: [PATCH 539/956] DSD demod: NXDN: updated documentation --- doc/img/DSDdemod_plugin_nxdn_rcch_status.png | Bin 5037 -> 5481 bytes doc/img/DSDdemod_plugin_nxdn_rcch_status.xcf | Bin 19717 -> 22370 bytes doc/img/DSDdemod_plugin_nxdn_rtdch_status.png | Bin 5789 -> 5722 bytes doc/img/DSDdemod_plugin_nxdn_rtdch_status.xcf | Bin 20868 -> 22885 bytes plugins/channelrx/demoddsd/readme.md | 30 +++++++++++++----- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/doc/img/DSDdemod_plugin_nxdn_rcch_status.png b/doc/img/DSDdemod_plugin_nxdn_rcch_status.png index 602998b47508fc0ed2631d347faeb72636c2ea75..de95cb2a7466cc37978123e8686d4456dfab646b 100644 GIT binary patch literal 5481 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Ⓢ5_^ihe(obLP`*y2JXO2)2D_~g*C+Lt$Y%jGga}` z;Et`2Ra%2kHd*_#AS=E^q)P~S=xatQ(pPW#=a9(ap}d_@Yb3jnMcl#g>ed>nWGE|H zD9U|U$M#C=Hf)5gq#D%g7F=B)Ld` zi0J5$B!~7HB&7GD_wA~1W6c<_{?m*hGI;}+zkjav^K}tWLXYUAEfg{x4M)#9$^PJ` zu{RBvUMJt}BO?5oC;yMJI!#{Ut72%TeP8$U)iFHNPTL5d6b!N7tBoTuTvY_lHy%1W z979&?&?ff8X#vC5Rp{UiCw^r322y&3hX0~Z0)o>nlxvLYQps|A>v5E1(Ge)B>!)6# z-(*BwbQq($WIIx@_X3E}(jEb}`L`LdfUyyGGnN_$i~?>3<^#(CM1iRrfGxm_z@LCa zjE$@Zs9%gf4c;f`cg~KVzOQt9yuXib4EPPfN0d}r(zp<$+az24JNVTzet4EzuF zihuo|xZDI8^cWw=|Av8nO;KUW^%E8LUOF&+4W^3@LsXcK7g5Tg(bDl!3Y(elk3jk` zw+r9*NmyQez; z3U460KShhFtUrZ{zEfWtU5YRDx{KgOVj{awu)qD$G@)I9$u-(pIPz~6C8oJL zBdM~GS^1lrVuREjZ6saQ^S?F15$wHA?YgPwe;bf`uPf^5 MMB%PkhbTjbTg>EHe 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 diff --git a/doc/img/DSDdemod_plugin_nxdn_rtdch_status.png b/doc/img/DSDdemod_plugin_nxdn_rtdch_status.png index 815876b9f14a5872057948f8b5b4ce9b696d0f25..329106e0d3a6842f1142cff6a57f4c46aa620ea6 100644 GIT binary patch literal 5722 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 diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index 33005fcb4..acf3ca59e 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -307,19 +307,26 @@ This is the control channel used in trunked systems and is usually sent continuo This is `RC` for RCCH -
    A11.5.1.2: RAN number
    +
    A11.5.2.2: Half/full rate
    + +Indicator of transmission rate: + + - `H`: half rate (2400 or 4800 S/s). Uses EHR vocoder (AMBE 3600/2450) + - `F`: full rate (4800 S/s only). Uses EFR vocoder (AMBE 7200/4400) + +
    A11.5.1.3: RAN number
    This is the RAN number (0 to 63) associated to the transmission. RAN stands for "Radio Access Number" and for trunked systems this is the site identifier (Site Id) modulo 64. -
    A11.5.1.3: Last message type code
    +
    A11.5.1.4: Last message type code
    This is the type code of the last message (6 bits) displayed in hexadecimal. The complete list is found in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6. -
    A11.5.1.4: Location Id
    +
    A11.5.1.5: Location Id
    This is the 3 byte location Id associated to the site displayed in hexadecimal -
    A11.5.1.5: Services available flags
    +
    A11.5.1.6: Services available flags
    This is a 16 bit collection of flags to indicate which services are available displayed in hexadecimal. The breakdown is listed in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6.5.33. From MSB to LSB: @@ -350,22 +357,29 @@ This is the transmission channel either in a trunked system (RTCH) or convention It can be either `RT` for RTCH or `RD` for a RDCH channel -
    A11.5.2.2: RAN number
    +
    A11.5.2.2: Half/full rate
    + +Indicator of transmission rate: + + - `H`: half rate (2400 or 4800 S/s). Uses EHR vocoder (AMBE 3600/2450) + - `F`: full rate (4800 S/s only). Uses EFR vocoder (AMBE 7200/4400) + +
    A11.5.2.3: RAN number
    This is the RAN number (0 to 63) associated to the transmission. RAN stands for "Radio Access Number" and has a different usage in conventional or trunked systems: - Conventional (RDCH): this is used as a selective squelch. Code `0` means always unmute. - Trunked (RTCH): this is the site identifier (Site Id) modulo 64. -
    A11.5.2.3: Last message type code
    +
    A11.5.2.4: Last message type code
    This is the type code of the last message (6 bits) displayed in hexadecimal. The complete list is found in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6. -
    A11.5.2.4: Source Id
    +
    A11.5.2.5: Source Id
    This is the source of transmission identification code on two bytes (0 to 65353) displayed in decimal. -
    A11.5.2.5: Destination Id
    +
    A11.5.2.6: Destination Id
    This is the destination of transmission identification code on two bytes (0 to 65353) displayed in decimal. It is prefixed by a group call indicator: From 833412dcc2724dd0681f608918346252474a1afa Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 28 Jun 2018 23:44:19 +0200 Subject: [PATCH 540/956] Added specialized moving average class for future spectrum averaging --- sdrbase/util/movingaverage2d.h | 122 +++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 sdrbase/util/movingaverage2d.h diff --git a/sdrbase/util/movingaverage2d.h b/sdrbase/util/movingaverage2d.h new file mode 100644 index 000000000..9fd270d69 --- /dev/null +++ b/sdrbase/util/movingaverage2d.h @@ -0,0 +1,122 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 _UTIL_MOVINGAVERAGE2D_H_ +#define _UTIL_MOVINGAVERAGE2D_H_ + +#include + +template +class MovingAverage2D +{ +public: + MovingAverage2D() : m_data(0), m_sum(0), m_dataSize(0), m_sumSize(0), m_width(0), m_depth(0), m_avgIndex(0) {} + + ~MovingAverage2D() + { + if (m_data) { + delete[] m_data; + } + + if (m_sum) { + delete[] m_sum; + } + } + + void resize(unsigned int width, unsigned int depth) + { + if (width*depth > m_dataSize) + { + m_dataSize = width*depth; + if (m_data) { + delete[] m_data; + } + m_data = new T[m_dataSize]; + } + + if (width > m_sumSize) + { + m_sumSize = width; + if (m_sum) { + delete[] m_sum; + } + m_sum = new T[m_sumSize]; + } + + m_width = width; + m_depth = depth; + + std::fill(m_data, m_data+(m_width*m_depth), 0.0); + std::fill(m_sum, m_sum+m_width, 0.0); + + m_avgIndex = 0; + } + + T storeAndGetAvg(T v, unsigned int index) + { + if (m_depth == 1) + { + return v; + } + else if (index < m_width) + { + T first = m_data[m_avgIndex*m_width+index]; + m_sum[index] += (v - first); + m_data[m_avgIndex*m_width+index] = v; + return m_sum[index] / m_depth; + } + else + { + return 0; + } + } + + T storeAndGetSum(T v, unsigned int index) + { + if (m_depth == 1) + { + return v; + } + else if (index < m_width) + { + T first = m_data[m_avgIndex*m_width+index]; + m_sum[index] += (v - first); + m_data[m_avgIndex*m_width+index] = v; + return m_sum[index]; + } + else + { + return 0; + } + } + + void nextAverage() { + m_avgIndex = m_avgIndex == m_depth-1 ? 0 : m_avgIndex+1; + } + +private: + T *m_data; + T *m_sum; + unsigned int m_dataSize; + unsigned int m_sumSize; + unsigned int m_width; + unsigned int m_depth; + unsigned int m_avgIndex; +}; + + +#endif From 9fee7b49b51a2c108a3448692d028592d83425a0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 28 Jun 2018 23:47:15 +0200 Subject: [PATCH 541/956] Spectrum averaging (1) --- sdrgui/dsp/spectrumvis.cpp | 17 ++++++++++++----- sdrgui/dsp/spectrumvis.h | 6 ++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/sdrgui/dsp/spectrumvis.cpp b/sdrgui/dsp/spectrumvis.cpp index f62928203..f1b98f5eb 100644 --- a/sdrgui/dsp/spectrumvis.cpp +++ b/sdrgui/dsp/spectrumvis.cpp @@ -14,6 +14,8 @@ inline double log2f(double n) MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureSpectrumVis, Message) +const Real SpectrumVis::m_mult = (10.0f / log2f(10.0f)); + SpectrumVis::SpectrumVis(Real scalef, GLSpectrum* glSpectrum) : BasebandSampleSink(), m_fft(FFTEngine::create()), @@ -96,8 +98,6 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV m_fft->transform(); // extract power spectrum and reorder buckets - Real ofs = 20.0f * log10f(1.0f / m_fftSize); - Real mult = (10.0f / log2f(10.0f)); const Complex* fftOut = m_fft->out(); Complex c; Real v; @@ -109,7 +109,8 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV { c = fftOut[i]; v = c.real() * c.real() + c.imag() * c.imag(); - v = mult * log2f(v) + ofs; + v = m_average.storeAndGetAvg(v, i); + v = m_mult * log2f(v) + m_ofs; m_logPowerSpectrum[i * 2] = v; m_logPowerSpectrum[i * 2 + 1] = v; } @@ -120,18 +121,21 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV { c = fftOut[i + halfSize]; v = c.real() * c.real() + c.imag() * c.imag(); - v = mult * log2f(v) + ofs; + v = m_average.storeAndGetAvg(v, i+halfSize); + v = m_mult * log2f(v) + m_ofs; m_logPowerSpectrum[i] = v; c = fftOut[i]; v = c.real() * c.real() + c.imag() * c.imag(); - v = mult * log2f(v) + ofs; + v = m_average.storeAndGetAvg(v, i); + v = m_mult * log2f(v) + m_ofs; m_logPowerSpectrum[i + halfSize] = v; } } // send new data to visualisation m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize); + m_average.nextAverage(); // advance buffer respecting the fft overlap factor std::copy(m_fftBuffer.begin() + m_refillSize, m_fftBuffer.end(), m_fftBuffer.begin()); @@ -208,4 +212,7 @@ void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, FFTWindow::Fu m_overlapSize = (m_fftSize * m_overlapPercent) / 100; m_refillSize = m_fftSize - m_overlapSize; m_fftBufferFill = m_overlapSize; + m_average.resize(fftSize, 10); + m_averageNb = 100; + m_ofs = 20.0f * log10f(1.0f / m_fftSize); } diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index 2a7279e8d..19995538a 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -7,6 +7,7 @@ #include "dsp/fftwindow.h" #include "export.h" #include "util/message.h" +#include "util/movingaverage2d.h" class GLSpectrum; class MessageQueue; @@ -62,6 +63,11 @@ private: Real m_scalef; GLSpectrum* m_glSpectrum; + MovingAverage2D m_average; + unsigned int m_averageNb; + + Real m_ofs; + static const Real m_mult; QMutex m_mutex; From e9658e03462fcf4ca97fc87f95b4e727fbb065ce Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 30 Jun 2018 22:30:42 +0200 Subject: [PATCH 542/956] Spectrum averaging (2): moving average --- plugins/channelrx/demodbfm/bfmdemodgui.cpp | 2 +- plugins/channelrx/udpsrc/udpsrcgui.cpp | 2 +- plugins/channeltx/udpsink/udpsinkgui.cpp | 2 +- sdrbase/util/movingaverage2d.h | 2 +- sdrgui/dsp/spectrumvis.cpp | 16 +- sdrgui/dsp/spectrumvis.h | 9 +- sdrgui/gui/glspectrumgui.cpp | 172 +++++++++--- sdrgui/gui/glspectrumgui.h | 14 + sdrgui/gui/glspectrumgui.ui | 290 +++++++++++++-------- 9 files changed, 348 insertions(+), 161 deletions(-) diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index 5074cad04..cda2d6889 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -359,7 +359,7 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban ui->glSpectrum->setDisplayWaterfall(false); ui->glSpectrum->setDisplayMaxHold(false); ui->glSpectrum->setSsbSpectrum(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, FFTWindow::BlackmanHarris); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); m_channelMarker.blockSignals(true); diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsrc/udpsrcgui.cpp index 28ba37359..66826ec56 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsrc/udpsrcgui.cpp @@ -187,7 +187,7 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, FFTWindow::BlackmanHarris); ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index fe2ac399d..4e40e2cce 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -142,7 +142,7 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, FFTWindow::BlackmanHarris); ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); diff --git a/sdrbase/util/movingaverage2d.h b/sdrbase/util/movingaverage2d.h index 9fd270d69..5cdc5e7f3 100644 --- a/sdrbase/util/movingaverage2d.h +++ b/sdrbase/util/movingaverage2d.h @@ -68,7 +68,7 @@ public: T storeAndGetAvg(T v, unsigned int index) { - if (m_depth == 1) + if (m_depth <= 1) { return v; } diff --git a/sdrgui/dsp/spectrumvis.cpp b/sdrgui/dsp/spectrumvis.cpp index f1b98f5eb..27eb7a5c0 100644 --- a/sdrgui/dsp/spectrumvis.cpp +++ b/sdrgui/dsp/spectrumvis.cpp @@ -25,10 +25,12 @@ SpectrumVis::SpectrumVis(Real scalef, GLSpectrum* glSpectrum) : m_needMoreSamples(false), m_scalef(scalef), m_glSpectrum(glSpectrum), + m_averageNb(0), + m_ofs(0), m_mutex(QMutex::Recursive) { setObjectName("SpectrumVis"); - handleConfigure(1024, 0, FFTWindow::BlackmanHarris); + handleConfigure(1024, 0, 0, FFTWindow::BlackmanHarris); } SpectrumVis::~SpectrumVis() @@ -36,9 +38,9 @@ SpectrumVis::~SpectrumVis() delete m_fft; } -void SpectrumVis::configure(MessageQueue* msgQueue, int fftSize, int overlapPercent, FFTWindow::Function window) +void SpectrumVis::configure(MessageQueue* msgQueue, int fftSize, int overlapPercent, unsigned int averagingNb, FFTWindow::Function window) { - MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis(fftSize, overlapPercent, window); + MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis(fftSize, overlapPercent, averagingNb, window); msgQueue->push(cmd); } @@ -171,7 +173,7 @@ bool SpectrumVis::handleMessage(const Message& message) if (MsgConfigureSpectrumVis::match(message)) { MsgConfigureSpectrumVis& conf = (MsgConfigureSpectrumVis&) message; - handleConfigure(conf.getFFTSize(), conf.getOverlapPercent(), conf.getWindow()); + handleConfigure(conf.getFFTSize(), conf.getOverlapPercent(), conf.getAverageNb(), conf.getWindow()); return true; } else @@ -180,7 +182,7 @@ bool SpectrumVis::handleMessage(const Message& message) } } -void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, FFTWindow::Function window) +void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, unsigned int averageNb, FFTWindow::Function window) { QMutexLocker mutexLocker(&m_mutex); @@ -212,7 +214,7 @@ void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, FFTWindow::Fu m_overlapSize = (m_fftSize * m_overlapPercent) / 100; m_refillSize = m_fftSize - m_overlapSize; m_fftBufferFill = m_overlapSize; - m_average.resize(fftSize, 10); - m_averageNb = 100; + m_average.resize(fftSize, averageNb); + m_averageNb = averageNb; m_ofs = 20.0f * log10f(1.0f / m_fftSize); } diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index 19995538a..8faa38647 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -19,27 +19,30 @@ public: MESSAGE_CLASS_DECLARATION public: - MsgConfigureSpectrumVis(int fftSize, int overlapPercent, FFTWindow::Function window) : + MsgConfigureSpectrumVis(int fftSize, int overlapPercent, unsigned int averageNb, FFTWindow::Function window) : Message(), m_fftSize(fftSize), m_overlapPercent(overlapPercent), + m_averageNb(averageNb), m_window(window) { } int getFFTSize() const { return m_fftSize; } int getOverlapPercent() const { return m_overlapPercent; } + unsigned int getAverageNb() const { return m_averageNb; } FFTWindow::Function getWindow() const { return m_window; } private: int m_fftSize; int m_overlapPercent; + unsigned int m_averageNb; FFTWindow::Function m_window; }; SpectrumVis(Real scalef, GLSpectrum* glSpectrum = 0); virtual ~SpectrumVis(); - void configure(MessageQueue* msgQueue, int fftSize, int overlapPercent, FFTWindow::Function window); + void configure(MessageQueue* msgQueue, int fftSize, int overlapPercent, unsigned int averagingNb, FFTWindow::Function window); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); void feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly); @@ -71,7 +74,7 @@ private: QMutex m_mutex; - void handleConfigure(int fftSize, int overlapPercent, FFTWindow::Function window); + void handleConfigure(int fftSize, int overlapPercent, unsigned int averageNb, FFTWindow::Function window); }; #endif // INCLUDE_SPECTRUMVIS_H diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index bfe576d6b..7dab42811 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -8,9 +8,9 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : QWidget(parent), ui(new Ui::GLSpectrumGUI), - m_messageQueue(NULL), - m_spectrumVis(NULL), - m_glSpectrum(NULL), + m_messageQueue(0), + m_spectrumVis(0), + m_glSpectrum(0), m_fftSize(1024), m_fftOverlap(0), m_fftWindow(FFTWindow::Hamming), @@ -27,7 +27,11 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_displayCurrent(false), m_displayHistogram(false), m_displayGrid(false), - m_invert(true) + m_invert(true), + m_averagingMode(AvgModeMoving), + m_averagingIndex(0), + m_averagingMaxScale(2), + m_averagingNb(0) { ui->setupUi(this); for(int ref = 0; ref >= -110; ref -= 5) @@ -66,6 +70,8 @@ void GLSpectrumGUI::resetToDefaults() m_displayHistogram = false; m_displayGrid = false; m_invert = true; + m_averagingMode = AvgModeMoving; + m_averagingIndex = 0; applySettings(); } @@ -90,6 +96,8 @@ QByteArray GLSpectrumGUI::serialize() const s.writeBool(16, m_displayCurrent); s.writeS32(17, m_displayTraceIntensity); s.writeReal(18, m_glSpectrum->getWaterfallShare()); + s.writeS32(19, (int) m_averagingMode); + s.writeS32(20, (qint32) getAveragingValue(m_averagingIndex)); return s.final(); } @@ -102,6 +110,8 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) return false; } + int tmp; + if(d.getVersion() == 1) { d.readS32(1, &m_fftSize, 1024); d.readS32(2, &m_fftOverlap, 0); @@ -122,6 +132,12 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) d.readS32(17, &m_displayTraceIntensity, 50); Real waterfallShare; d.readReal(18, &waterfallShare, 0.66); + d.readS32(19, &tmp, 0); + m_averagingMode = tmp < 0 ? AvgModeMoving : tmp > 1 ? AvgModeFixed : (AveragingMode) tmp; + d.readS32(20, &tmp, 0); + m_averagingIndex = getAveragingValue(tmp); + m_averagingNb = getAveragingValue(m_averagingIndex); + m_glSpectrum->setWaterfallShare(waterfallShare); applySettings(); return true; @@ -142,6 +158,8 @@ void GLSpectrumGUI::applySettings() } ui->refLevel->setCurrentIndex(-m_refLevel / 5); ui->levelRange->setCurrentIndex((100 - m_powerRange) / 5); + ui->averaging->setCurrentIndex(m_averagingIndex); + ui->averagingMode->setCurrentIndex((int) m_averagingMode); ui->decay->setSliderPosition(m_decay); ui->holdoff->setSliderPosition(m_histogramLateHoldoff); ui->stroke->setSliderPosition(m_histogramStroke); @@ -171,44 +189,63 @@ void GLSpectrumGUI::applySettings() m_glSpectrum->setDisplayGrid(m_displayGrid); m_glSpectrum->setDisplayGridIntensity(m_displayGridIntensity); - m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow); + if (m_spectrumVis) { + m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, m_averagingNb, (FFTWindow::Function)m_fftWindow); + } } void GLSpectrumGUI::on_fftWindow_currentIndexChanged(int index) { m_fftWindow = index; - if(m_spectrumVis == NULL) - return; - m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow); + if(m_spectrumVis != 0) { + m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, m_averagingNb, (FFTWindow::Function)m_fftWindow); + } } void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index) { m_fftSize = 1 << (7 + index); - if(m_spectrumVis != NULL) - m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow); + if(m_spectrumVis != 0) { + m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, m_averagingNb, (FFTWindow::Function)m_fftWindow); + } +} + +void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index __attribute__((unused))) +{ +} + +void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) +{ + m_averagingIndex = index; + m_averagingNb = getAveragingValue(index); + if(m_spectrumVis != 0) { + m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, m_averagingNb, (FFTWindow::Function)m_fftWindow); + } } void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index) { m_refLevel = 0 - index * 5; - if(m_glSpectrum != NULL) - m_glSpectrum->setReferenceLevel(m_refLevel); + if(m_glSpectrum != 0) { + m_glSpectrum->setReferenceLevel(m_refLevel); + } } void GLSpectrumGUI::on_levelRange_currentIndexChanged(int index) { m_powerRange = 100 - index * 5; - if(m_glSpectrum != NULL) - m_glSpectrum->setPowerRange(m_powerRange); + if(m_glSpectrum != 0) { + m_glSpectrum->setPowerRange(m_powerRange); + } } void GLSpectrumGUI::on_decay_valueChanged(int index) { m_decay = index; ui->decay->setToolTip(QString("Decay: %1").arg(m_decay)); - if(m_glSpectrum != NULL) - m_glSpectrum->setDecay(m_decay); + if(m_glSpectrum != 0) { + m_glSpectrum->setDecay(m_decay); + } } void GLSpectrumGUI::on_holdoff_valueChanged(int index) @@ -218,7 +255,7 @@ void GLSpectrumGUI::on_holdoff_valueChanged(int index) } m_histogramLateHoldoff = index; //ui->holdoff->setToolTip(QString("Holdoff: %1").arg(m_histogramLateHoldoff)); - if(m_glSpectrum != NULL) { + if(m_glSpectrum != 0) { applySettings(); } } @@ -230,7 +267,7 @@ void GLSpectrumGUI::on_stroke_valueChanged(int index) } m_histogramStroke = index; //ui->stroke->setToolTip(QString("Stroke: %1").arg(m_histogramStroke)); - if(m_glSpectrum != NULL) { + if(m_glSpectrum != 0) { applySettings(); } } @@ -238,63 +275,126 @@ void GLSpectrumGUI::on_stroke_valueChanged(int index) void GLSpectrumGUI::on_waterfall_toggled(bool checked) { m_displayWaterfall = checked; - if(m_glSpectrum != NULL) - m_glSpectrum->setDisplayWaterfall(m_displayWaterfall); + if(m_glSpectrum != 0) { + m_glSpectrum->setDisplayWaterfall(m_displayWaterfall); + } } void GLSpectrumGUI::on_histogram_toggled(bool checked) { m_displayHistogram = checked; - if(m_glSpectrum != NULL) - m_glSpectrum->setDisplayHistogram(m_displayHistogram); + if(m_glSpectrum != 0) { + m_glSpectrum->setDisplayHistogram(m_displayHistogram); + } } void GLSpectrumGUI::on_maxHold_toggled(bool checked) { m_displayMaxHold = checked; - if(m_glSpectrum != NULL) - m_glSpectrum->setDisplayMaxHold(m_displayMaxHold); + if(m_glSpectrum != 0) { + m_glSpectrum->setDisplayMaxHold(m_displayMaxHold); + } } void GLSpectrumGUI::on_current_toggled(bool checked) { m_displayCurrent = checked; - if(m_glSpectrum != NULL) - m_glSpectrum->setDisplayCurrent(m_displayCurrent); + if(m_glSpectrum != 0) { + m_glSpectrum->setDisplayCurrent(m_displayCurrent); + } } void GLSpectrumGUI::on_invert_toggled(bool checked) { m_invert = checked; - if(m_glSpectrum != NULL) - m_glSpectrum->setInvertedWaterfall(m_invert); + if(m_glSpectrum != 0) { + m_glSpectrum->setInvertedWaterfall(m_invert); + } } void GLSpectrumGUI::on_grid_toggled(bool checked) { m_displayGrid = checked; - if(m_glSpectrum != NULL) - m_glSpectrum->setDisplayGrid(m_displayGrid); + if(m_glSpectrum != 0) { + m_glSpectrum->setDisplayGrid(m_displayGrid); + } } void GLSpectrumGUI::on_gridIntensity_valueChanged(int index) { m_displayGridIntensity = index; ui->gridIntensity->setToolTip(QString("Grid intensity: %1").arg(m_displayGridIntensity)); - if(m_glSpectrum != NULL) - m_glSpectrum->setDisplayGridIntensity(m_displayGridIntensity); + if(m_glSpectrum != 0) { + m_glSpectrum->setDisplayGridIntensity(m_displayGridIntensity); + } } void GLSpectrumGUI::on_traceIntensity_valueChanged(int index) { m_displayTraceIntensity = index; ui->traceIntensity->setToolTip(QString("Trace intensity: %1").arg(m_displayTraceIntensity)); - if(m_glSpectrum != NULL) - m_glSpectrum->setDisplayTraceIntensity(m_displayTraceIntensity); + if(m_glSpectrum != 0) { + m_glSpectrum->setDisplayTraceIntensity(m_displayTraceIntensity); + } } void GLSpectrumGUI::on_clearSpectrum_clicked(bool checked __attribute__((unused))) { - if(m_glSpectrum != NULL) - m_glSpectrum->clearSpectrumHistogram(); + if(m_glSpectrum != 0) { + m_glSpectrum->clearSpectrumHistogram(); + } } + +int GLSpectrumGUI::getAveragingIndex(int averagingValue) const +{ + if (averagingValue <= 0) { + return 0; + } + + int v = averagingValue; + int j = 0; + + for (int i = 0; i <= m_averagingMaxScale; i++) + { + if (v < 20) + { + if (v < 2) { + j = 0; + } else if (v < 5) { + j = 1; + } else if (v < 10) { + j = 2; + } else { + j = 3; + } + + return 3*i + j; + } + + v /= 10; + } + + return 3*m_averagingMaxScale + 3; +} + +int GLSpectrumGUI::getAveragingValue(int averagingIndex) const +{ + if (averagingIndex <= 0) { + return 0; + } + + int v = averagingIndex - 1; + int m = pow(10.0, v/3 > m_averagingMaxScale ? m_averagingMaxScale : v/3); + int x; + + if (v % 3 == 0) { + x = 2; + } else if (v % 3 == 1) { + x = 5; + } else if (v % 3 == 2) { + x = 10; + } + + return x * m; +} + diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 69765777e..e31bf1136 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -18,6 +18,12 @@ class SDRGUI_API GLSpectrumGUI : public QWidget, public Serializable { Q_OBJECT public: + enum AveragingMode + { + AvgModeMoving, + AvgModeFixed + }; + explicit GLSpectrumGUI(QWidget* parent = NULL); ~GLSpectrumGUI(); @@ -51,8 +57,14 @@ private: bool m_displayHistogram; bool m_displayGrid; bool m_invert; + AveragingMode m_averagingMode; + int m_averagingIndex; + int m_averagingMaxScale; + unsigned int m_averagingNb; void applySettings(); + int getAveragingIndex(int averaging) const; + int getAveragingValue(int averagingIndex) const; private slots: void on_fftWindow_currentIndexChanged(int index); @@ -64,6 +76,8 @@ private slots: void on_stroke_valueChanged(int index); void on_gridIntensity_valueChanged(int index); void on_traceIntensity_valueChanged(int index); + void on_averagingMode_currentIndexChanged(int index); + void on_averaging_currentIndexChanged(int index); void on_waterfall_toggled(bool checked); void on_histogram_toggled(bool checked); diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index b46b17a66..ae9d75396 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -7,7 +7,7 @@ 0 0 331 - 60 + 59
    @@ -104,19 +104,49 @@
    - - - - 0 - 0 - - - + + 24 24 + + Trace intensity + + + 100 + + + 1 + + + 50 + + + + + + + Clear spectrum histogram + + + + + + + :/clear.png:/clear.png + + + + 16 + 16 + + + + + + Display phosphor effect spectrum @@ -140,18 +170,6 @@ - - - 0 - 0 - - - - - 24 - 24 - - Display max hold @@ -175,18 +193,6 @@ - - - 0 - 0 - - - - - 24 - 24 - - Display live spectrum @@ -210,18 +216,6 @@ - - - 0 - 0 - - - - - 24 - 24 - - Exchange waterfall and histogram @@ -245,18 +239,6 @@ - - - 0 - 0 - - - - - 24 - 24 - - Display waterfall @@ -283,18 +265,6 @@ - - - 0 - 0 - - - - - 24 - 24 - - Toggle the scale grid @@ -355,13 +325,13 @@
    - 30 + 50 0 - 100 + 50 16777215 @@ -373,32 +343,32 @@
    - Bartlett + Bart - Blackman-Harris + B-H - Flat Top + FT - Hamming + Ham - Hanning + Han - Rectangle + Rec
    @@ -411,6 +381,18 @@ 0
    + + + 50 + 0 + + + + + 50 + 16777215 + + FFT size @@ -459,16 +441,37 @@
    - 0 + 50 0 + + + 50 + 16777215 + + Reference level (dB) QComboBox::AdjustToContents + + + 0 + + + + + -5 + + + + + -10 + +
    @@ -481,70 +484,135 @@
    - 0 + 50 0 + + + 50 + 16777215 + + Range (dB) QComboBox::AdjustToContents + + + 100 + + + + + 50 + + + + + 5 + +
    - + - 24 - 24 + 50 + 0 - 24 - 24 + 50 + 16777215 - Clear spectrum histogram - - - - - - - :/clear.png:/clear.png - - - - 16 - 16 - + Averaging type + + + Mov + + + + + Fix + + - + + + + 50 + 0 + + - 24 - 24 + 50 + 16777215 - Trace intensity - - - 100 - - - 1 - - - 50 + Number of averaging samples + + + 0 + + + + + 2 + + + + + 5 + + + + + 10 + + + + + 20 + + + + + 50 + + + + + 100 + + + + + 200 + + + + + 500 + + + + + 1k + + From a23447ed4b803abe5376ed2a4c3bdfff32b8905d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 00:55:30 +0200 Subject: [PATCH 543/956] Spectrum averaging: set averaging number combo box programmatically --- sdrgui/gui/glspectrumgui.cpp | 34 ++++++++++++++++++++++++++++++++++ sdrgui/gui/glspectrumgui.h | 4 +++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 7dab42811..f3706a109 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -38,6 +38,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : ui->refLevel->addItem(QString("%1").arg(ref)); for(int range = 100; range >= 5; range -= 5) ui->levelRange->addItem(QString("%1").arg(range)); + setAveragingCombo(); } GLSpectrumGUI::~GLSpectrumGUI() @@ -398,3 +399,36 @@ int GLSpectrumGUI::getAveragingValue(int averagingIndex) const return x * m; } +void GLSpectrumGUI::setAveragingCombo() +{ + ui->averaging->clear(); + ui->averaging->addItem(QString("0")); + + for (int i = 0; i <= m_averagingMaxScale; i++) + { + QString s; + int m = pow(10.0, i); + int x = 2*m; + setNumberStr(x, s); + ui->averaging->addItem(s); + x = 5*m; + setNumberStr(x, s); + ui->averaging->addItem(s); + x = 10*m; + setNumberStr(x, s); + ui->averaging->addItem(s); + } +} + +void GLSpectrumGUI::setNumberStr(int n, QString& s) +{ + if (n < 1000) { + s = tr("%1").arg(n); + } else if (n < 1000000) { + s = tr("%1k").arg(n/1000); + } else if (n < 1000000000) { + s = tr("%1M").arg(n/1000000); + } else { + s = tr("%1G").arg(n/1000000000); + } +} diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index e31bf1136..604855912 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -59,12 +59,14 @@ private: bool m_invert; AveragingMode m_averagingMode; int m_averagingIndex; - int m_averagingMaxScale; + int m_averagingMaxScale; //!< Max power of 10 multiplier to 2,5,10 base ex: 2 -> 2,5,10,20,50,100,200,500,1000 unsigned int m_averagingNb; void applySettings(); int getAveragingIndex(int averaging) const; int getAveragingValue(int averagingIndex) const; + void setAveragingCombo(); + void setNumberStr(int n, QString& s); private slots: void on_fftWindow_currentIndexChanged(int index); From 56e49baa3b4eadf2e274b44a8cdfae88bfef1acf Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 02:16:59 +0200 Subject: [PATCH 544/956] Spectrum averaging: fixed average (1) --- plugins/channelrx/demodbfm/bfmdemodgui.cpp | 2 +- plugins/channelrx/udpsrc/udpsrcgui.cpp | 2 +- plugins/channeltx/udpsink/udpsinkgui.cpp | 2 +- sdrbase/util/fixedaverage2d.h | 121 ++++++++++++++++++ sdrgui/dsp/spectrumvis.cpp | 136 +++++++++++++++------ sdrgui/dsp/spectrumvis.h | 32 ++++- sdrgui/gui/glspectrumgui.cpp | 39 +++++- 7 files changed, 287 insertions(+), 47 deletions(-) create mode 100644 sdrbase/util/fixedaverage2d.h diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index cda2d6889..87dfc9ac8 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -359,7 +359,7 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban ui->glSpectrum->setDisplayWaterfall(false); ui->glSpectrum->setDisplayMaxHold(false); ui->glSpectrum->setSsbSpectrum(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, 0, FFTWindow::BlackmanHarris); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); m_channelMarker.blockSignals(true); diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsrc/udpsrcgui.cpp index 66826ec56..2a645c287 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsrc/udpsrcgui.cpp @@ -187,7 +187,7 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, 0, FFTWindow::BlackmanHarris); ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index 4e40e2cce..88e33c399 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -142,7 +142,7 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, 0, FFTWindow::BlackmanHarris); ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); diff --git a/sdrbase/util/fixedaverage2d.h b/sdrbase/util/fixedaverage2d.h new file mode 100644 index 000000000..a4735889b --- /dev/null +++ b/sdrbase/util/fixedaverage2d.h @@ -0,0 +1,121 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 SDRBASE_UTIL_FIXEDAVERAGE2D_H_ +#define SDRBASE_UTIL_FIXEDAVERAGE2D_H_ + +#include + +template +class FixedAverage2D +{ +public: + FixedAverage2D() : m_sum(0), m_sumSize(0), m_width(0), m_size(0), m_avgIndex(0) {} + + ~FixedAverage2D() + { + if (m_sum) { + delete[] m_sum; + } + } + + void resize(unsigned int width, unsigned int size) + { + if (width > m_sumSize) + { + m_sumSize = width; + if (m_sum) { + delete[] m_sum; + } + m_sum = new T[m_sumSize]; + } + + m_width = width; + m_size = size; + + std::fill(m_sum, m_sum+m_width, 0); + m_avgIndex = 0; + } + + bool storeAndGetAvg(T& avg, T v, unsigned int index) + { + if (m_size <= 1) + { + avg = v; + return true; + } + + m_sum[index] += v; + + if (m_avgIndex == m_size - 1) + { + avg = m_sum[index]/m_size; + return true; + } + else + { + return false; + } + } + + bool storeAndGetSum(T& sum, T v, unsigned int index) + { + if (m_size <= 1) + { + sum = v; + return true; + } + + m_sum[index] += v; + + if (m_avgIndex < m_size - 1) + { + sum = m_sum[index]; + return true; + } + else + { + return false; + } + } + + bool nextAverage() + { + if (m_avgIndex == m_size - 1) + { + m_avgIndex = 0; + std::fill(m_sum, m_sum+m_width, 0); + return true; + } + else + { + m_avgIndex++; + return false; + } + } + +private: + T *m_sum; + unsigned int m_sumSize; + unsigned int m_width; + unsigned int m_size; + unsigned int m_avgIndex; +}; + + + +#endif /* SDRBASE_UTIL_FIXEDAVERAGE2D_H_ */ diff --git a/sdrgui/dsp/spectrumvis.cpp b/sdrgui/dsp/spectrumvis.cpp index 27eb7a5c0..f3d30aef7 100644 --- a/sdrgui/dsp/spectrumvis.cpp +++ b/sdrgui/dsp/spectrumvis.cpp @@ -26,11 +26,12 @@ SpectrumVis::SpectrumVis(Real scalef, GLSpectrum* glSpectrum) : m_scalef(scalef), m_glSpectrum(glSpectrum), m_averageNb(0), + m_averagingMode(AvgModeMoving), m_ofs(0), m_mutex(QMutex::Recursive) { setObjectName("SpectrumVis"); - handleConfigure(1024, 0, 0, FFTWindow::BlackmanHarris); + handleConfigure(1024, 0, 0, AvgModeMoving, FFTWindow::BlackmanHarris); } SpectrumVis::~SpectrumVis() @@ -38,9 +39,14 @@ SpectrumVis::~SpectrumVis() delete m_fft; } -void SpectrumVis::configure(MessageQueue* msgQueue, int fftSize, int overlapPercent, unsigned int averagingNb, FFTWindow::Function window) +void SpectrumVis::configure(MessageQueue* msgQueue, + int fftSize, + int overlapPercent, + unsigned int averagingNb, + int averagingMode, + FFTWindow::Function window) { - MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis(fftSize, overlapPercent, averagingNb, window); + MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis(fftSize, overlapPercent, averagingNb, averagingMode, window); msgQueue->push(cmd); } @@ -105,40 +111,92 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV Real v; std::size_t halfSize = m_fftSize / 2; - if ( positiveOnly ) + if (m_averagingMode == AvgModeMoving) { - for (std::size_t i = 0; i < halfSize; i++) - { - c = fftOut[i]; - v = c.real() * c.real() + c.imag() * c.imag(); - v = m_average.storeAndGetAvg(v, i); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i * 2] = v; - m_logPowerSpectrum[i * 2 + 1] = v; - } + if ( positiveOnly ) + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_movingAverage.storeAndGetAvg(v, i); + v = m_mult * log2f(v) + m_ofs; + m_logPowerSpectrum[i * 2] = v; + m_logPowerSpectrum[i * 2 + 1] = v; + } + } + else + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i + halfSize]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_movingAverage.storeAndGetAvg(v, i+halfSize); + v = m_mult * log2f(v) + m_ofs; + m_logPowerSpectrum[i] = v; + + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_movingAverage.storeAndGetAvg(v, i); + v = m_mult * log2f(v) + m_ofs; + m_logPowerSpectrum[i + halfSize] = v; + } + } + + // send new data to visualisation + m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize); + m_movingAverage.nextAverage(); } - else + else if (m_averagingMode == AvgModeFixed) { - for (std::size_t i = 0; i < halfSize; i++) - { - c = fftOut[i + halfSize]; - v = c.real() * c.real() + c.imag() * c.imag(); - v = m_average.storeAndGetAvg(v, i+halfSize); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i] = v; + double avg; - c = fftOut[i]; - v = c.real() * c.real() + c.imag() * c.imag(); - v = m_average.storeAndGetAvg(v, i); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i + halfSize] = v; - } + if ( positiveOnly ) + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + + if (m_fixedAverage.storeAndGetAvg(avg, v, i)) + { + avg = m_mult * log2f(avg) + m_ofs; + m_logPowerSpectrum[i * 2] = avg; + m_logPowerSpectrum[i * 2 + 1] = avg; + } + } + } + else + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i + halfSize]; + v = c.real() * c.real() + c.imag() * c.imag(); + + if (m_fixedAverage.storeAndGetAvg(avg, v, i+halfSize)) + { // result available + avg = m_mult * log2f(avg) + m_ofs; + m_logPowerSpectrum[i] = avg; + } + + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + + if (m_fixedAverage.storeAndGetAvg(avg, v, i)) + { // result available + avg = m_mult * log2f(avg) + m_ofs; + m_logPowerSpectrum[i + halfSize] = avg; + } + } + } + + if (m_fixedAverage.nextAverage()) + { // result available + // send new data to visualisation + m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize); + } } - // send new data to visualisation - m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize); - m_average.nextAverage(); - // advance buffer respecting the fft overlap factor std::copy(m_fftBuffer.begin() + m_refillSize, m_fftBuffer.end(), m_fftBuffer.begin()); @@ -173,7 +231,11 @@ bool SpectrumVis::handleMessage(const Message& message) if (MsgConfigureSpectrumVis::match(message)) { MsgConfigureSpectrumVis& conf = (MsgConfigureSpectrumVis&) message; - handleConfigure(conf.getFFTSize(), conf.getOverlapPercent(), conf.getAverageNb(), conf.getWindow()); + handleConfigure(conf.getFFTSize(), + conf.getOverlapPercent(), + conf.getAverageNb(), + conf.getAveragingMode(), + conf.getWindow()); return true; } else @@ -182,7 +244,11 @@ bool SpectrumVis::handleMessage(const Message& message) } } -void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, unsigned int averageNb, FFTWindow::Function window) +void SpectrumVis::handleConfigure(int fftSize, + int overlapPercent, + unsigned int averageNb, + AveragingMode averagingMode, + FFTWindow::Function window) { QMutexLocker mutexLocker(&m_mutex); @@ -214,7 +280,9 @@ void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, unsigned int m_overlapSize = (m_fftSize * m_overlapPercent) / 100; m_refillSize = m_fftSize - m_overlapSize; m_fftBufferFill = m_overlapSize; - m_average.resize(fftSize, averageNb); + m_movingAverage.resize(fftSize, averageNb); + m_fixedAverage.resize(fftSize, averageNb); m_averageNb = averageNb; + m_averagingMode = averagingMode; m_ofs = 20.0f * log10f(1.0f / m_fftSize); } diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index 8faa38647..e2b81c2ca 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -8,6 +8,7 @@ #include "export.h" #include "util/message.h" #include "util/movingaverage2d.h" +#include "util/fixedaverage2d.h" class GLSpectrum; class MessageQueue; @@ -15,34 +16,49 @@ class MessageQueue; class SDRGUI_API SpectrumVis : public BasebandSampleSink { public: + enum AveragingMode + { + AvgModeMoving, + AvgModeFixed + }; + class MsgConfigureSpectrumVis : public Message { MESSAGE_CLASS_DECLARATION public: - MsgConfigureSpectrumVis(int fftSize, int overlapPercent, unsigned int averageNb, FFTWindow::Function window) : + MsgConfigureSpectrumVis(int fftSize, int overlapPercent, unsigned int averageNb, int averagingMode, FFTWindow::Function window) : Message(), m_fftSize(fftSize), m_overlapPercent(overlapPercent), m_averageNb(averageNb), m_window(window) - { } + { + m_averagingMode = averagingMode < 0 ? AvgModeMoving : averagingMode > 1 ? AvgModeFixed : (SpectrumVis::AveragingMode) averagingMode; + } int getFFTSize() const { return m_fftSize; } int getOverlapPercent() const { return m_overlapPercent; } unsigned int getAverageNb() const { return m_averageNb; } + SpectrumVis::AveragingMode getAveragingMode() const { return m_averagingMode; } FFTWindow::Function getWindow() const { return m_window; } private: int m_fftSize; int m_overlapPercent; unsigned int m_averageNb; + SpectrumVis::AveragingMode m_averagingMode; FFTWindow::Function m_window; }; SpectrumVis(Real scalef, GLSpectrum* glSpectrum = 0); virtual ~SpectrumVis(); - void configure(MessageQueue* msgQueue, int fftSize, int overlapPercent, unsigned int averagingNb, FFTWindow::Function window); + void configure(MessageQueue* msgQueue, + int fftSize, + int overlapPercent, + unsigned int averagingNb, + int averagingMode, + FFTWindow::Function window); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); void feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly); @@ -66,15 +82,21 @@ private: Real m_scalef; GLSpectrum* m_glSpectrum; - MovingAverage2D m_average; + MovingAverage2D m_movingAverage; + FixedAverage2D m_fixedAverage; unsigned int m_averageNb; + AveragingMode m_averagingMode; Real m_ofs; static const Real m_mult; QMutex m_mutex; - void handleConfigure(int fftSize, int overlapPercent, unsigned int averageNb, FFTWindow::Function window); + void handleConfigure(int fftSize, + int overlapPercent, + unsigned int averageNb, + AveragingMode averagingMode, + FFTWindow::Function window); }; #endif // INCLUDE_SPECTRUMVIS_H diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index f3706a109..6933fcf75 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -191,7 +191,12 @@ void GLSpectrumGUI::applySettings() m_glSpectrum->setDisplayGridIntensity(m_displayGridIntensity); if (m_spectrumVis) { - m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, m_averagingNb, (FFTWindow::Function)m_fftWindow); + m_spectrumVis->configure(m_messageQueue, + m_fftSize, + m_fftOverlap, + m_averagingNb, + m_averagingMode, + (FFTWindow::Function)m_fftWindow); } } @@ -199,7 +204,12 @@ void GLSpectrumGUI::on_fftWindow_currentIndexChanged(int index) { m_fftWindow = index; if(m_spectrumVis != 0) { - m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, m_averagingNb, (FFTWindow::Function)m_fftWindow); + m_spectrumVis->configure(m_messageQueue, + m_fftSize, + m_fftOverlap, + m_averagingNb, + m_averagingMode, + (FFTWindow::Function)m_fftWindow); } } @@ -207,12 +217,26 @@ void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index) { m_fftSize = 1 << (7 + index); if(m_spectrumVis != 0) { - m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, m_averagingNb, (FFTWindow::Function)m_fftWindow); + m_spectrumVis->configure(m_messageQueue, + m_fftSize, + m_fftOverlap, + m_averagingNb, + m_averagingMode, + (FFTWindow::Function)m_fftWindow); } } -void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index __attribute__((unused))) +void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) { + m_averagingMode = index < 0 ? AvgModeMoving : index > 1 ? AvgModeFixed : (AveragingMode) index; + if(m_spectrumVis != 0) { + m_spectrumVis->configure(m_messageQueue, + m_fftSize, + m_fftOverlap, + m_averagingNb, + m_averagingMode, + (FFTWindow::Function)m_fftWindow); + } } void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) @@ -220,7 +244,12 @@ void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) m_averagingIndex = index; m_averagingNb = getAveragingValue(index); if(m_spectrumVis != 0) { - m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, m_averagingNb, (FFTWindow::Function)m_fftWindow); + m_spectrumVis->configure(m_messageQueue, + m_fftSize, + m_fftOverlap, + m_averagingNb, + m_averagingMode, + (FFTWindow::Function)m_fftWindow); } } From 0b496bd80041d7ccc1c9a2fdfb441c2ee3cbc651 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 03:11:36 +0200 Subject: [PATCH 545/956] Spectrum averaging: fixed average (2) --- sdrgui/gui/glspectrum.cpp | 10 +++++++++- sdrgui/gui/glspectrum.h | 2 ++ sdrgui/gui/glspectrumgui.cpp | 18 ++++++++++++++++++ sdrgui/gui/scaleengine.cpp | 2 +- sdrgui/gui/scaleengine.h | 2 +- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 9a511628f..75c55b5f4 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -38,6 +38,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_powerRange(100), m_decay(0), m_sampleRate(500000), + m_timingRate(1), m_fftSize(512), m_displayGrid(true), m_displayGridIntensity(5), @@ -196,6 +197,13 @@ void GLSpectrum::setSampleRate(qint32 sampleRate) update(); } +void GLSpectrum::setTimingRate(qint32 timingRate) +{ + m_timingRate = timingRate; + m_changesPending = true; + update(); +} + void GLSpectrum::setDisplayWaterfall(bool display) { m_displayWaterfall = display; @@ -1043,7 +1051,7 @@ void GLSpectrum::applyChanges() if(m_sampleRate > 0) { - float scaleDiv = (float)m_sampleRate * (m_ssbSpectrum ? 2 : 1); + float scaleDiv = ((float)m_sampleRate / (float)m_timingRate) * (m_ssbSpectrum ? 2 : 1); if(!m_invertedWaterfall) { diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index 3b2ef97ac..a5e3dce20 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -46,6 +46,7 @@ public: void setCenterFrequency(qint64 frequency); void setSampleRate(qint32 sampleRate); + void setTimingRate(qint32 timingRate); void setReferenceLevel(Real referenceLevel); void setPowerRange(Real powerRange); void setDecay(int decay); @@ -110,6 +111,7 @@ private: Real m_powerRange; int m_decay; quint32 m_sampleRate; + quint32 m_timingRate; int m_fftSize; diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 6933fcf75..36fed96f0 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -229,6 +229,7 @@ void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index) void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) { m_averagingMode = index < 0 ? AvgModeMoving : index > 1 ? AvgModeFixed : (AveragingMode) index; + if(m_spectrumVis != 0) { m_spectrumVis->configure(m_messageQueue, m_fftSize, @@ -237,12 +238,22 @@ void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) m_averagingMode, (FFTWindow::Function)m_fftWindow); } + + if (m_glSpectrum != 0) + { + if (m_averagingMode == AvgModeFixed) { + m_glSpectrum->setTimingRate(m_averagingNb == 0 ? 1 : m_averagingNb); + } else { + m_glSpectrum->setTimingRate(1); + } + } } void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) { m_averagingIndex = index; m_averagingNb = getAveragingValue(index); + if(m_spectrumVis != 0) { m_spectrumVis->configure(m_messageQueue, m_fftSize, @@ -251,6 +262,13 @@ void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) m_averagingMode, (FFTWindow::Function)m_fftWindow); } + + if (m_glSpectrum != 0) + { + if (m_averagingMode == AvgModeFixed) { + m_glSpectrum->setTimingRate(m_averagingNb == 0 ? 1 : m_averagingNb); + } + } } void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index) diff --git a/sdrgui/gui/scaleengine.cpp b/sdrgui/gui/scaleengine.cpp index 2e921ead7..1ad5c436d 100644 --- a/sdrgui/gui/scaleengine.cpp +++ b/sdrgui/gui/scaleengine.cpp @@ -29,7 +29,7 @@ static double trunc(double d) QString ScaleEngine::formatTick(double value, int decimalPlaces, bool fancyTime) { - if((m_physicalUnit != Unit::Time) || (!fancyTime) || 1) + if((m_physicalUnit != Unit::Time) || (!fancyTime)) { return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces); } diff --git a/sdrgui/gui/scaleengine.h b/sdrgui/gui/scaleengine.h index a66f6b093..0f4e20083 100644 --- a/sdrgui/gui/scaleengine.h +++ b/sdrgui/gui/scaleengine.h @@ -58,7 +58,7 @@ private: int m_decimalPlaces; bool m_makeOpposite; // will show -value instead of value - QString formatTick(double value, int decimalPlaces, bool fancyTime = true); + QString formatTick(double value, int decimalPlaces, bool fancyTime = false); void calcCharSize(); void calcScaleFactor(); double calcMajorTickUnits(double distance, int* retDecimalPlaces); From 9913d550e0a2f200447c8ae8b93b0aa91ad60073 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 04:10:36 +0200 Subject: [PATCH 546/956] Spectrum averaging: fixed average (3): scale engine fix --- sdrbase/util/fixedaverage2d.h | 4 +++ sdrgui/gui/glspectrum.cpp | 14 ++++----- sdrgui/gui/glspectrumgui.cpp | 2 ++ sdrgui/gui/physicalunit.h | 1 + sdrgui/gui/scaleengine.cpp | 55 ++++++++++++++++++++--------------- sdrgui/gui/scaleengine.h | 2 +- 6 files changed, 47 insertions(+), 31 deletions(-) diff --git a/sdrbase/util/fixedaverage2d.h b/sdrbase/util/fixedaverage2d.h index a4735889b..192dcfe6a 100644 --- a/sdrbase/util/fixedaverage2d.h +++ b/sdrbase/util/fixedaverage2d.h @@ -95,6 +95,10 @@ public: bool nextAverage() { + if (m_size <= 1) { + return true; + } + if (m_avgIndex == m_size - 1) { m_avgIndex = 0; diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 75c55b5f4..d046c3ad5 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -1055,11 +1055,11 @@ void GLSpectrum::applyChanges() if(!m_invertedWaterfall) { - m_timeScale.setRange(Unit::Time, (waterfallHeight * m_fftSize) / scaleDiv, 0); + m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, (waterfallHeight * m_fftSize) / scaleDiv, 0); } else { - m_timeScale.setRange(Unit::Time, 0, (waterfallHeight * m_fftSize) / scaleDiv); + m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, (waterfallHeight * m_fftSize) / scaleDiv); } } else @@ -1149,26 +1149,26 @@ void GLSpectrum::applyChanges() if(m_sampleRate > 0) { - float scaleDiv = (float)m_sampleRate * (m_ssbSpectrum ? 2 : 1); + float scaleDiv = ((float)m_sampleRate / (float)m_timingRate) * (m_ssbSpectrum ? 2 : 1); if(!m_invertedWaterfall) { - m_timeScale.setRange(Unit::Time, (waterfallHeight * m_fftSize) / scaleDiv, 0); + m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, (waterfallHeight * m_fftSize) / scaleDiv, 0); } else { - m_timeScale.setRange(Unit::Time, 0, (waterfallHeight * m_fftSize) / scaleDiv); + m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, (waterfallHeight * m_fftSize) / scaleDiv); } } else { if(!m_invertedWaterfall) { - m_timeScale.setRange(Unit::Time, 10, 0); + m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 10, 0); } else { - m_timeScale.setRange(Unit::Time, 0, 10); + m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, 10); } } diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 36fed96f0..514634e99 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -34,8 +34,10 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_averagingNb(0) { ui->setupUi(this); + ui->refLevel->clear(); for(int ref = 0; ref >= -110; ref -= 5) ui->refLevel->addItem(QString("%1").arg(ref)); + ui->levelRange->clear(); for(int range = 100; range >= 5; range -= 5) ui->levelRange->addItem(QString("%1").arg(range)); setAveragingCombo(); diff --git a/sdrgui/gui/physicalunit.h b/sdrgui/gui/physicalunit.h index 778637845..27399a121 100644 --- a/sdrgui/gui/physicalunit.h +++ b/sdrgui/gui/physicalunit.h @@ -29,6 +29,7 @@ namespace Unit { DecibelMicroVolt, AngleDegrees, Time, + TimeHMS, Volt }; }; diff --git a/sdrgui/gui/scaleengine.cpp b/sdrgui/gui/scaleengine.cpp index 1ad5c436d..195aac116 100644 --- a/sdrgui/gui/scaleengine.cpp +++ b/sdrgui/gui/scaleengine.cpp @@ -27,43 +27,48 @@ static double trunc(double d) } */ -QString ScaleEngine::formatTick(double value, int decimalPlaces, bool fancyTime) +QString ScaleEngine::formatTick(double value, int decimalPlaces) { - if((m_physicalUnit != Unit::Time) || (!fancyTime)) + if (m_physicalUnit != Unit::TimeHMS) { return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces); } else { + if (m_scale < 1.0f) { // sub second prints just as is + return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces); + } + QString str; - double orig = fabs(value); + double actual = value * m_scale; // this is the actual value in seconds + double orig = fabs(actual); double tmp; if(orig >= 86400.0) { - tmp = floor(value / 86400.0); + tmp = floor(actual / 86400.0); str = QString("%1.").arg(tmp, 0, 'f', 0); - value -= tmp * 86400.0; - if(value < 0.0) - value *= -1.0; + actual -= tmp * 86400.0; + if(actual < 0.0) + actual *= -1.0; } if(orig >= 3600.0) { - tmp = floor(value / 3600.0); + tmp = floor(actual / 3600.0); str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0')); - value -= tmp * 3600.0; - if(value < 0.0) - value *= -1.0; + actual -= tmp * 3600.0; + if(actual < 0.0) + actual *= -1.0; } if(orig >= 60.0) { - tmp = floor(value / 60.0); + tmp = floor(actual / 60.0); str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0')); - value -= tmp * 60.0; - if(value < 0.0) - value *= -1.0; + actual -= tmp * 60.0; + if(actual < 0.0) + actual *= -1.0; } - tmp = m_makeOpposite ? -value : value; + tmp = m_makeOpposite ? -actual : actual; str += QString("%1").arg(tmp, 2, 'f', decimalPlaces, QChar('0')); return str; @@ -164,14 +169,18 @@ void ScaleEngine::calcScaleFactor() break; case Unit::Time: - if(median < 0.001) { + case Unit::TimeHMS: + if (median < 0.001) { m_unitStr = QString("µs"); m_scale = 0.000001; - } else if(median < 1.0) { + } else if (median < 1.0) { m_unitStr = QString("ms"); m_scale = 0.001; - } else { + } else if (median < 1000.0) { m_unitStr = QString("s"); + } else { + m_unitStr = QString("ks"); + m_scale = 1000.0; } break; @@ -581,15 +590,15 @@ const ScaleEngine::TickList& ScaleEngine::getTickList() QString ScaleEngine::getRangeMinStr() { if(m_unitStr.length() > 0) - return QString("%1 %2").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces, false)).arg(m_unitStr); - else return QString("%1").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces, false)); + return QString("%1 %2").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces)).arg(m_unitStr); + else return QString("%1").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces)); } QString ScaleEngine::getRangeMaxStr() { if(m_unitStr.length() > 0) - return QString("%1 %2").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces, false)).arg(m_unitStr); - else return QString("%1").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces, false)); + return QString("%1 %2").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces)).arg(m_unitStr); + else return QString("%1").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces)); } float ScaleEngine::getScaleWidth() diff --git a/sdrgui/gui/scaleengine.h b/sdrgui/gui/scaleengine.h index 0f4e20083..1a09bd2b8 100644 --- a/sdrgui/gui/scaleengine.h +++ b/sdrgui/gui/scaleengine.h @@ -58,7 +58,7 @@ private: int m_decimalPlaces; bool m_makeOpposite; // will show -value instead of value - QString formatTick(double value, int decimalPlaces, bool fancyTime = false); + QString formatTick(double value, int decimalPlaces); void calcCharSize(); void calcScaleFactor(); double calcMajorTickUnits(double distance, int* retDecimalPlaces); From 69ab133b6ef4e7b4b3f49771e0a2aef8890681e0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 04:14:06 +0200 Subject: [PATCH 547/956] Spectrum averaging: fixed average (4): fixed averaging combo index setting --- sdrgui/gui/glspectrumgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 514634e99..fb2a9b30e 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -138,7 +138,7 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) d.readS32(19, &tmp, 0); m_averagingMode = tmp < 0 ? AvgModeMoving : tmp > 1 ? AvgModeFixed : (AveragingMode) tmp; d.readS32(20, &tmp, 0); - m_averagingIndex = getAveragingValue(tmp); + m_averagingIndex = getAveragingIndex(tmp); m_averagingNb = getAveragingValue(m_averagingIndex); m_glSpectrum->setWaterfallShare(waterfallShare); From caf3559cdd511be095a36a8a3637be49534b796c Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 04:19:50 +0200 Subject: [PATCH 548/956] Spectrum GUI: use horizontal spacer to pack elements on the left --- sdrgui/gui/glspectrumgui.ui | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index ae9d75396..d563cb4a6 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -311,6 +311,19 @@
    + + + + Qt::Horizontal + + + + 40 + 20 + + + +
    @@ -615,6 +628,19 @@
    + + + + Qt::Horizontal + + + + 40 + 20 + + + +
    From a2674fa9de25af1dd12f648df2782d08404a13ad Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 11:07:37 +0200 Subject: [PATCH 549/956] Created combo box without arrow --- sdrgui/CMakeLists.txt | 2 ++ sdrgui/gui/comboboxnoarrow.cpp | 33 ++++++++++++++++++++++++++++ sdrgui/gui/comboboxnoarrow.h | 39 ++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 sdrgui/gui/comboboxnoarrow.cpp create mode 100644 sdrgui/gui/comboboxnoarrow.h diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 4f6289690..401f3b677 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -14,6 +14,7 @@ set(sdrgui_SOURCES gui/commanditem.cpp gui/commandkeyreceiver.cpp gui/commandoutputdialog.cpp + gui/comboboxnoarrow.cpp gui/crightclickenabler.cpp gui/cwkeyergui.cpp gui/editcommanddialog.cpp @@ -73,6 +74,7 @@ set(sdrgui_HEADERS gui/commanditem.h gui/commandkeyreceiver.h gui/commandoutputdialog.h + gui/comboboxnoarrow.h gui/crightclickenabler.h gui/cwkeyergui.h gui/editcommanddialog.h diff --git a/sdrgui/gui/comboboxnoarrow.cpp b/sdrgui/gui/comboboxnoarrow.cpp new file mode 100644 index 000000000..327b9891e --- /dev/null +++ b/sdrgui/gui/comboboxnoarrow.cpp @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 "comboboxnoarrow.h" + +void ComboBoxNoArrow::paintEvent (QPaintEvent *ev __attribute__((unused))) +{ + QPainter p; + p.begin (this); + QStyleOptionComboBox opt; + opt.initFrom (this); + style()->drawPrimitive (QStyle::PE_PanelButtonBevel, &opt, &p, this); + style()->drawPrimitive (QStyle::PE_PanelButtonCommand, &opt, &p, this); + style()->drawItemText (&p, rect(), Qt::AlignCenter, palette(), isEnabled(), currentText()); + p.end(); +} + diff --git a/sdrgui/gui/comboboxnoarrow.h b/sdrgui/gui/comboboxnoarrow.h new file mode 100644 index 000000000..4974a61be --- /dev/null +++ b/sdrgui/gui/comboboxnoarrow.h @@ -0,0 +1,39 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 SDRGUI_GUI_COMBOBOXNOARROW_H_ +#define SDRGUI_GUI_COMBOBOXNOARROW_H_ + +#include + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// This class is a custom QComboBox which does NOT display the down arrow. The down arrow takes +/// a lot of real estate when you're trying to make them narrow. So much real estate that you can't +/// see short lines of text such as "CH 1" without the digit cut off. The only thing that this +/// custom widget does is to override the paint function. The new paint function draws the combo +/// box (using all style sheet info) without the down arrow. +/////////////////////////////////////////////////////////////////////////////////////////////////// +class ComboBoxNoArrow : public QComboBox +{ + Q_OBJECT +public: + ComboBoxNoArrow (QWidget *parent) : QComboBox(parent) {} + virtual ~ComboBoxNoArrow() {} + void paintEvent (QPaintEvent *ev); +}; + +#endif /* SDRGUI_GUI_COMBOBOXNOARROW_H_ */ From e88a0d6b57812c3d35c2cea11a71d24b61674168 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 20:48:30 +0200 Subject: [PATCH 550/956] Spectrum averaging: display averaging time in tooltip. Bumped version to 4.0.2 --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 +++ sdrgui/gui/glspectrum.cpp | 9 +++- sdrgui/gui/glspectrum.h | 23 ++++++++++ sdrgui/gui/glspectrumgui.cpp | 81 ++++++++++++++++++++++++++++++++---- sdrgui/gui/glspectrumgui.h | 10 ++++- 8 files changed, 122 insertions(+), 13 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index f03f084de..d88bf36ce 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.0.1"); + QCoreApplication::setApplicationVersion("4.0.2"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 004d2192c..8224f2dcb 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.0.1"); + QCoreApplication::setApplicationVersion("4.0.2"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 120aa16d9..360568442 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.0.1"); + QCoreApplication::setApplicationVersion("4.0.2"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 7a90116b6..d36d04fb2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (4.0.2-1) unstable; urgency=medium + + * Spectrum: added averaging + + -- Edouard Griffiths, F4EXB Sun, 01 Jul 2018 21:14:18 +0200 + sdrangel (4.0.1-1) unstable; urgency=medium * DSD demod: added NXDN support diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index d046c3ad5..f565a0c95 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -24,9 +24,12 @@ #include #include #include "gui/glspectrum.h" +#include "util/messagequeue.h" #include +MESSAGE_CLASS_DEFINITION(GLSpectrum::MsgReportSampleRate, Message) + GLSpectrum::GLSpectrum(QWidget* parent) : QGLWidget(parent), m_cursorState(CSNormal), @@ -60,7 +63,8 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_displayHistogram(true), m_displayChanged(false), m_matrixLoc(0), - m_colorLoc(0) + m_colorLoc(0), + m_messageQueueToGUI(0) { setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent, true); @@ -193,6 +197,9 @@ void GLSpectrum::setHistoStroke(int stroke) void GLSpectrum::setSampleRate(qint32 sampleRate) { m_sampleRate = sampleRate; + if (m_messageQueueToGUI) { + m_messageQueueToGUI->push(new MsgReportSampleRate(m_sampleRate)); + } m_changesPending = true; update(); } diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index a5e3dce20..21301ba19 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -34,13 +34,32 @@ #include "dsp/channelmarker.h" #include "export.h" #include "util/incrementalarray.h" +#include "util/message.h" class QOpenGLShaderProgram; +class MessageQueue; class SDRGUI_API GLSpectrum : public QGLWidget { Q_OBJECT public: + class MsgReportSampleRate : public Message { + MESSAGE_CLASS_DECLARATION + + public: + MsgReportSampleRate(quint32 sampleRate) : + Message(), + m_sampleRate(sampleRate) + { + m_sampleRate = sampleRate; + } + + quint32 getSampleRate() const { return m_sampleRate; } + + private: + quint32 m_sampleRate; + }; + GLSpectrum(QWidget* parent = NULL); ~GLSpectrum(); @@ -62,9 +81,11 @@ public: void setDisplayGrid(bool display); void setDisplayGridIntensity(int intensity); void setDisplayTraceIntensity(int intensity); + qint32 getSampleRate() const { return m_sampleRate; } void addChannelMarker(ChannelMarker* channelMarker); void removeChannelMarker(ChannelMarker* channelMarker); + void setMessageQueueToGUI(MessageQueue* messageQueue) { m_messageQueueToGUI = messageQueue; } void newSpectrum(const std::vector& spectrum, int fftSize); void clearSpectrumHistogram(); @@ -172,6 +193,8 @@ private: IncrementalArray m_q3TickPower; IncrementalArray m_q3FFT; + MessageQueue *m_messageQueueToGUI; + static const int m_waterfallBufferHeight = 256; void updateWaterfall(const std::vector& spectrum); diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index fb2a9b30e..1567093be 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -8,7 +8,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : QWidget(parent), ui(new Ui::GLSpectrumGUI), - m_messageQueue(0), + m_messageQueueToVis(0), m_spectrumVis(0), m_glSpectrum(0), m_fftSize(1024), @@ -41,6 +41,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : for(int range = 100; range >= 5; range -= 5) ui->levelRange->addItem(QString("%1").arg(range)); setAveragingCombo(); + connect(&m_messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); } GLSpectrumGUI::~GLSpectrumGUI() @@ -50,9 +51,10 @@ GLSpectrumGUI::~GLSpectrumGUI() void GLSpectrumGUI::setBuddies(MessageQueue* messageQueue, SpectrumVis* spectrumVis, GLSpectrum* glSpectrum) { - m_messageQueue = messageQueue; + m_messageQueueToVis = messageQueue; m_spectrumVis = spectrumVis; m_glSpectrum = glSpectrum; + m_glSpectrum->setMessageQueueToGUI(&m_messageQueue); applySettings(); } @@ -193,20 +195,22 @@ void GLSpectrumGUI::applySettings() m_glSpectrum->setDisplayGridIntensity(m_displayGridIntensity); if (m_spectrumVis) { - m_spectrumVis->configure(m_messageQueue, + m_spectrumVis->configure(m_messageQueueToVis, m_fftSize, m_fftOverlap, m_averagingNb, m_averagingMode, (FFTWindow::Function)m_fftWindow); } + + setAveragingToolitp(); } void GLSpectrumGUI::on_fftWindow_currentIndexChanged(int index) { m_fftWindow = index; if(m_spectrumVis != 0) { - m_spectrumVis->configure(m_messageQueue, + m_spectrumVis->configure(m_messageQueueToVis, m_fftSize, m_fftOverlap, m_averagingNb, @@ -219,13 +223,14 @@ void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index) { m_fftSize = 1 << (7 + index); if(m_spectrumVis != 0) { - m_spectrumVis->configure(m_messageQueue, + m_spectrumVis->configure(m_messageQueueToVis, m_fftSize, m_fftOverlap, m_averagingNb, m_averagingMode, (FFTWindow::Function)m_fftWindow); } + setAveragingToolitp(); } void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) @@ -233,7 +238,7 @@ void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) m_averagingMode = index < 0 ? AvgModeMoving : index > 1 ? AvgModeFixed : (AveragingMode) index; if(m_spectrumVis != 0) { - m_spectrumVis->configure(m_messageQueue, + m_spectrumVis->configure(m_messageQueueToVis, m_fftSize, m_fftOverlap, m_averagingNb, @@ -257,7 +262,7 @@ void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) m_averagingNb = getAveragingValue(index); if(m_spectrumVis != 0) { - m_spectrumVis->configure(m_messageQueue, + m_spectrumVis->configure(m_messageQueueToVis, m_fftSize, m_fftOverlap, m_averagingNb, @@ -271,6 +276,8 @@ void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) m_glSpectrum->setTimingRate(m_averagingNb == 0 ? 1 : m_averagingNb); } } + + setAveragingToolitp(); } void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index) @@ -481,3 +488,63 @@ void GLSpectrumGUI::setNumberStr(int n, QString& s) s = tr("%1G").arg(n/1000000000); } } + +void GLSpectrumGUI::setNumberStr(float v, int decimalPlaces, QString& s) +{ + if (v < 1e-6) { + s = tr("%1n").arg(v*1e9, 0, 'f', decimalPlaces); + } else if (v < 1e-3) { + s = tr("%1µ").arg(v*1e6, 0, 'f', decimalPlaces); + } else if (v < 1.0) { + s = tr("%1m").arg(v*1e3, 0, 'f', decimalPlaces); + } else if (v < 1e3) { + s = tr("%1").arg(v, 0, 'f', decimalPlaces); + } else if (v < 1e6) { + s = tr("%1k").arg(v*1e-3, 0, 'f', decimalPlaces); + } else if (v < 1e9) { + s = tr("%1M").arg(v*1e-6, 0, 'f', decimalPlaces); + } else { + s = tr("%1G").arg(v*1e-9, 0, 'f', decimalPlaces); + } +} + +void GLSpectrumGUI::setAveragingToolitp() +{ + if (m_glSpectrum) + { + QString s; + float averagingTime = (m_fftSize * m_averagingNb) / (float) m_glSpectrum->getSampleRate(); + setNumberStr(averagingTime, 2, s); + ui->averaging->setToolTip(QString("Number of averaging samples (avg time: %1s)").arg(s)); + } + else + { + ui->averaging->setToolTip(QString("Number of averaging samples")); + } +} + +bool GLSpectrumGUI::handleMessage(const Message& message) +{ + if (GLSpectrum::MsgReportSampleRate::match(message)) + { + setAveragingToolitp(); + return true; + } + + return false; +} + +void GLSpectrumGUI::handleInputMessages() +{ + Message* message; + + while ((message = m_messageQueue.pop()) != 0) + { + qDebug("GLSpectrumGUI::handleInputMessages: message: %s", message->getIdentifier()); + + if (handleMessage(*message)) + { + delete message; + } + } +} diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 604855912..2028bda91 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -5,12 +5,12 @@ #include "dsp/dsptypes.h" #include "export.h" #include "settings/serializable.h" +#include "util/messagequeue.h" namespace Ui { class GLSpectrumGUI; } -class MessageQueue; class SpectrumVis; class GLSpectrum; @@ -36,9 +36,10 @@ public: private: Ui::GLSpectrumGUI* ui; - MessageQueue* m_messageQueue; + MessageQueue* m_messageQueueToVis; SpectrumVis* m_spectrumVis; GLSpectrum* m_glSpectrum; + MessageQueue m_messageQueue; qint32 m_fftSize; qint32 m_fftOverlap; @@ -67,6 +68,9 @@ private: int getAveragingValue(int averagingIndex) const; void setAveragingCombo(); void setNumberStr(int n, QString& s); + void setNumberStr(float v, int decimalPlaces, QString& s); + void setAveragingToolitp(); + bool handleMessage(const Message& message); private slots: void on_fftWindow_currentIndexChanged(int index); @@ -88,6 +92,8 @@ private slots: void on_invert_toggled(bool checked); void on_grid_toggled(bool checked); void on_clearSpectrum_clicked(bool checked); + + void handleInputMessages(); }; #endif // INCLUDE_GLSPECTRUMGUI_H From 024fbf5525c5acef71a6656b44582fc1399991ef Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Jul 2018 22:36:36 +0200 Subject: [PATCH 551/956] Spectrum averaging: added a no averaging mode that disables averaging completely --- sdrgui/dsp/spectrumvis.cpp | 38 +++++++++++++++++++++++++++++++++--- sdrgui/dsp/spectrumvis.h | 3 ++- sdrgui/gui/glspectrumgui.cpp | 8 ++++---- sdrgui/gui/glspectrumgui.h | 1 + sdrgui/gui/glspectrumgui.ui | 5 +++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/sdrgui/dsp/spectrumvis.cpp b/sdrgui/dsp/spectrumvis.cpp index f3d30aef7..2c1542ec8 100644 --- a/sdrgui/dsp/spectrumvis.cpp +++ b/sdrgui/dsp/spectrumvis.cpp @@ -26,12 +26,12 @@ SpectrumVis::SpectrumVis(Real scalef, GLSpectrum* glSpectrum) : m_scalef(scalef), m_glSpectrum(glSpectrum), m_averageNb(0), - m_averagingMode(AvgModeMoving), + m_averagingMode(AvgModeNone), m_ofs(0), m_mutex(QMutex::Recursive) { setObjectName("SpectrumVis"); - handleConfigure(1024, 0, 0, AvgModeMoving, FFTWindow::BlackmanHarris); + handleConfigure(1024, 0, 0, AvgModeNone, FFTWindow::BlackmanHarris); } SpectrumVis::~SpectrumVis() @@ -111,7 +111,39 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV Real v; std::size_t halfSize = m_fftSize / 2; - if (m_averagingMode == AvgModeMoving) + if (m_averagingMode == AvgModeNone) + { + if ( positiveOnly ) + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_mult * log2f(v) + m_ofs; + m_logPowerSpectrum[i * 2] = v; + m_logPowerSpectrum[i * 2 + 1] = v; + } + } + else + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i + halfSize]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_mult * log2f(v) + m_ofs; + m_logPowerSpectrum[i] = v; + + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_mult * log2f(v) + m_ofs; + m_logPowerSpectrum[i + halfSize] = v; + } + } + + // send new data to visualisation + m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize); + } + else if (m_averagingMode == AvgModeMoving) { if ( positiveOnly ) { diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index e2b81c2ca..e6832627a 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -18,6 +18,7 @@ class SDRGUI_API SpectrumVis : public BasebandSampleSink { public: enum AveragingMode { + AvgModeNone, AvgModeMoving, AvgModeFixed }; @@ -33,7 +34,7 @@ public: m_averageNb(averageNb), m_window(window) { - m_averagingMode = averagingMode < 0 ? AvgModeMoving : averagingMode > 1 ? AvgModeFixed : (SpectrumVis::AveragingMode) averagingMode; + m_averagingMode = averagingMode < 0 ? AvgModeNone : averagingMode > 2 ? AvgModeFixed : (SpectrumVis::AveragingMode) averagingMode; } int getFFTSize() const { return m_fftSize; } diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 1567093be..a22343e42 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -28,7 +28,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_displayHistogram(false), m_displayGrid(false), m_invert(true), - m_averagingMode(AvgModeMoving), + m_averagingMode(AvgModeNone), m_averagingIndex(0), m_averagingMaxScale(2), m_averagingNb(0) @@ -75,7 +75,7 @@ void GLSpectrumGUI::resetToDefaults() m_displayHistogram = false; m_displayGrid = false; m_invert = true; - m_averagingMode = AvgModeMoving; + m_averagingMode = AvgModeNone; m_averagingIndex = 0; applySettings(); } @@ -138,7 +138,7 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) Real waterfallShare; d.readReal(18, &waterfallShare, 0.66); d.readS32(19, &tmp, 0); - m_averagingMode = tmp < 0 ? AvgModeMoving : tmp > 1 ? AvgModeFixed : (AveragingMode) tmp; + m_averagingMode = tmp < 0 ? AvgModeNone : tmp > 2 ? AvgModeFixed : (AveragingMode) tmp; d.readS32(20, &tmp, 0); m_averagingIndex = getAveragingIndex(tmp); m_averagingNb = getAveragingValue(m_averagingIndex); @@ -235,7 +235,7 @@ void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index) void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) { - m_averagingMode = index < 0 ? AvgModeMoving : index > 1 ? AvgModeFixed : (AveragingMode) index; + m_averagingMode = index < 0 ? AvgModeNone : index > 2 ? AvgModeFixed : (AveragingMode) index; if(m_spectrumVis != 0) { m_spectrumVis->configure(m_messageQueueToVis, diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 2028bda91..773274595 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -20,6 +20,7 @@ class SDRGUI_API GLSpectrumGUI : public QWidget, public Serializable { public: enum AveragingMode { + AvgModeNone, AvgModeMoving, AvgModeFixed }; diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index d563cb4a6..ed90ebf08 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -547,6 +547,11 @@ Averaging type + + + No + + Mov From 1d1edbb0492e2adff03d9d2771772802564b1116 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 2 Jul 2018 00:15:28 +0200 Subject: [PATCH 552/956] Spectrum averaging: updated documentation --- doc/img/MainWindow_spectrum_gui.png | Bin 0 -> 17263 bytes doc/img/MainWindow_spectrum_gui.xcf | Bin 0 -> 89736 bytes sdrgui/gui/glspectrumgui.cpp | 2 +- sdrgui/readme.md | 97 +++++++++++++++++++++++++++- 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 doc/img/MainWindow_spectrum_gui.png create mode 100644 doc/img/MainWindow_spectrum_gui.xcf diff --git a/doc/img/MainWindow_spectrum_gui.png b/doc/img/MainWindow_spectrum_gui.png new file mode 100644 index 0000000000000000000000000000000000000000..59d6e82093e996f4920ccb587e4e80775ea36d2b GIT binary patch literal 17263 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 + float averagingTime = (m_fftSize * (m_averagingNb == 0 ? 1 : m_averagingNb)) / (float) m_glSpectrum->getSampleRate(); setNumberStr(averagingTime, 2, s); ui->averaging->setToolTip(QString("Number of averaging samples (avg time: %1s)").arg(s)); } diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 4feffd754..b4013862f 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -226,7 +226,102 @@ Use this push button to add a new channel with the selected plugin

    4. Spectrum display control

    -These are the controls of the main spectrum display in (7). Please refer to the spectrum display documentation (TBD) for details. +![Spectrum GUI](../doc/img/MainWindow_spectrum_gui.png) + +These are the controls of the main spectrum display in (7). The same controls are found in the plugins that feature a spectrum display: + - Channel Analyzer + - Broadcast FM demodulator + - SSB demodulator + - UDP source + - UDP sink + +

    4.1. FFT window selector

    + +Use this combo box to select which window is applied to the FFT: + - **Bart**: Bartlett + - **B-H**: Blackmann-Harris + - **FT**: Flat top + - **Ham**: Hamming (default) + - **Han**: Hanning + - **Rec**: Rectangular (no window) + +

    4.2. FFT size

    + +Select the size of the FFT window among these values: + - 128 + - 256 + - 512 + - 1k = 1024 (default) + - 2k = 2048 + - 4k = 4096 + +

    4.3. Reference level

    + +This is the level in dB at the top of the display range. You can select values between 0 and -110 in 5 dB steps + +

    4.4. Range

    + +This is the range of display in dB. You can select values between 5 and 100 in 5 dB steps + +

    4.5. Averaging mode

    + +Use this combo to select which averaging mode is applied: + - **No**: no averaging. Disables averaging regardless of the number of averaged samples (4.6). This is the default option + - **Mov**: moving average. This is a sliding average over the amount of samples specified next (4.6). There is one complete FFT line produced at every FFT sampling period + - **Fix**: fixed average. Average is done over the amount of samples specified next (4.6) and a result is produced at the end of the corresponding period then the next block of averaged samples is processed. There is one complete FFT line produced every FFT sampling period multiplied by the number of averaged samples (4.6). The time scale on the waterfall display is updated accordingly. + +

    4.6. Number of averaged samples

    + +Each FFT bin (squared magnitude) is averaged over a number of samples. This combo allows selecting the number of samples between these values: 0 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000). The tooltip mentions the resulting averaging period considering the baseband sample rate and FFT size. +Averaging reduces the noise variance and can be used to better detect weak continuous signals. The fixed averaging mode allows long time monitoring on the waterfall. + +

    4.7. Phosphor display stroke decay

    + +This controls the decay rate of the stroke when phosphor display is engaged (4.C) + +

    4.8. Phosphor display holdoff

    + +This controls the holdoff when phosphor display is engaged (4.C) + +

    4.9. Phosphor display stroke strength

    + +This controls the stroke strength when phosphor display is engaged (4.C) + +

    4.A. Trace intensity

    + +This controls the intensity of the maximum (4.D) and current (4.E) spectrum trace + +

    4.B. Clear spectrum

    + +This resets the maximum spectrum trace and phosphor remanence + +

    4.C. Phosphor display

    + +Toggles the phosphor display on the spectrum + +

    4.D. Maximum trace

    + +Toggles the maximum trace display (red trace) on the spectrum + +

    4.E. Current trace

    + +Toggles the current trace display (yellow trace) on the spectrum + +

    4.F. Waterfall/spectrum placement

    + +Toggles the spectrum on top or on bottom versus waterfall + +

    4.G. Waterfall

    + +Toggles the waterfall display + +

    4.H.Grid

    + +Toggles the grid display + +

    4.I.Grid intensity

    + +Controls the intensity of the grid display

    5. Presets and commands

    From 5e1699c981a3d91aeba26cae7d289926b3ad15d5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 2 Jul 2018 02:37:56 +0200 Subject: [PATCH 553/956] Bumped plugins version --- plugins/channelrx/chanalyzer/chanalyzerplugin.cpp | 2 +- plugins/channelrx/demodbfm/bfmplugin.cpp | 2 +- plugins/channelrx/demodssb/ssbplugin.cpp | 2 +- plugins/channelrx/udpsrc/udpsrcplugin.cpp | 2 +- plugins/channeltx/udpsink/udpsinkplugin.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index 475029110..dce4fe53a 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("4.0.1"), + QString("4.0.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodbfm/bfmplugin.cpp b/plugins/channelrx/demodbfm/bfmplugin.cpp index 1d08ffad4..bc4903103 100644 --- a/plugins/channelrx/demodbfm/bfmplugin.cpp +++ b/plugins/channelrx/demodbfm/bfmplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor BFMPlugin::m_pluginDescriptor = { QString("Broadcast FM Demodulator"), - QString("4.0.0"), + QString("4.0.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp index 646de708f..f964c394b 100644 --- a/plugins/channelrx/demodssb/ssbplugin.cpp +++ b/plugins/channelrx/demodssb/ssbplugin.cpp @@ -10,7 +10,7 @@ const PluginDescriptor SSBPlugin::m_pluginDescriptor = { QString("SSB Demodulator"), - QString("4.0.0"), + QString("4.0.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.cpp b/plugins/channelrx/udpsrc/udpsrcplugin.cpp index 46e9be6f5..025482083 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.cpp +++ b/plugins/channelrx/udpsrc/udpsrcplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor UDPSrcPlugin::m_pluginDescriptor = { QString("UDP Channel Source"), - QString("4.0.0"), + QString("4.0.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/udpsink/udpsinkplugin.cpp b/plugins/channeltx/udpsink/udpsinkplugin.cpp index c72836c08..fa9453341 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.cpp +++ b/plugins/channeltx/udpsink/udpsinkplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { QString("UDP Channel Sink"), - QString("3.14.5"), + QString("4.0.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 51e59871582a39adbec0103cdf265625bae500ef Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 4 Jul 2018 20:01:02 +0200 Subject: [PATCH 554/956] Serial DV: use audio compressor at the end of audio filter chain --- sdrbase/dsp/dvserialworker.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sdrbase/dsp/dvserialworker.cpp b/sdrbase/dsp/dvserialworker.cpp index b17ea481c..6c5e28e4c 100644 --- a/sdrbase/dsp/dvserialworker.cpp +++ b/sdrbase/dsp/dvserialworker.cpp @@ -159,15 +159,16 @@ void DVSerialWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsign { for (int i = 0; i < nbSamplesIn; i++) { - float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) m_compressor.compress(in[i])) : (float) m_compressor.compress(in[i]); + //float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) m_compressor.compress(in[i])) : (float) m_compressor.compress(in[i]); + float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i]; float prev = m_upsamplerLastValue; qint16 upsample; for (int j = 1; j <= upsampling; j++) { upsample = (qint16) m_upsampleFilter.runLP(cur*m_upsamplingFactors[j] + prev*m_upsamplingFactors[upsampling-j]); - m_audioBuffer[m_audioBufferFill].l = channels & 1 ? upsample : 0; - m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? upsample : 0; + m_audioBuffer[m_audioBufferFill].l = channels & 1 ? m_compressor.compress(upsample) : 0; + m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? m_compressor.compress(upsample) : 0; if (m_audioBufferFill < m_audioBuffer.size() - 1) { From d5f153ff751f52388effb4b82b09aa8fda8cbb0d Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 4 Jul 2018 23:11:28 +0200 Subject: [PATCH 555/956] Spectrum: added log/linear control --- plugins/channelrx/demodbfm/bfmdemodgui.cpp | 9 ++++++- plugins/channelrx/udpsrc/udpsrcgui.cpp | 8 +++++- plugins/channeltx/udpsink/udpsinkgui.cpp | 8 +++++- sdrgui/dsp/spectrumvis.cpp | 5 ++-- sdrgui/dsp/spectrumvis.h | 16 ++++++++--- sdrgui/gui/glspectrumgui.cpp | 30 +++++++++++++++++---- sdrgui/gui/glspectrumgui.h | 2 ++ sdrgui/gui/glspectrumgui.ui | 20 +++++++++++++- sdrgui/resources/linear.png | Bin 0 -> 458 bytes sdrgui/resources/linear.xcf | Bin 0 -> 14052 bytes sdrgui/resources/logarithmic.png | Bin 0 -> 542 bytes sdrgui/resources/logarithmic.xcf | Bin 0 -> 13768 bytes sdrgui/resources/res.qrc | 2 ++ 13 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 sdrgui/resources/linear.png create mode 100644 sdrgui/resources/linear.xcf create mode 100644 sdrgui/resources/logarithmic.png create mode 100644 sdrgui/resources/logarithmic.xcf diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index 87dfc9ac8..2e0f4c42a 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -359,7 +359,14 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban ui->glSpectrum->setDisplayWaterfall(false); ui->glSpectrum->setDisplayMaxHold(false); ui->glSpectrum->setSsbSpectrum(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, 0, FFTWindow::BlackmanHarris); + m_spectrumVis->configure( + m_spectrumVis->getInputMessageQueue(), + 64, // FFT size + 10, // overlapping % + 0, // number of averaging samples + 0, // no averaging + FFTWindow::BlackmanHarris, + false); // logarithmic scale connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); m_channelMarker.blockSignals(true); diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsrc/udpsrcgui.cpp index 2a645c287..a3332091f 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsrc/udpsrcgui.cpp @@ -187,7 +187,13 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, 0, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), + 64, // FFT size + 10, // overlapping % + 0, // number of averaging samples + 0, // no averaging + FFTWindow::BlackmanHarris, + false); // logarithmic scale ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index 88e33c399..1d7b8da74 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -142,7 +142,13 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, 0, 0, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), + 64, // FFT size + 10, // overlapping % + 0, // number of averaging samples + 0, // no averaging + FFTWindow::BlackmanHarris, + false); // logarithmic scale ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); diff --git a/sdrgui/dsp/spectrumvis.cpp b/sdrgui/dsp/spectrumvis.cpp index 2c1542ec8..ae386182e 100644 --- a/sdrgui/dsp/spectrumvis.cpp +++ b/sdrgui/dsp/spectrumvis.cpp @@ -44,9 +44,10 @@ void SpectrumVis::configure(MessageQueue* msgQueue, int overlapPercent, unsigned int averagingNb, int averagingMode, - FFTWindow::Function window) + FFTWindow::Function window, + bool linear) { - MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis(fftSize, overlapPercent, averagingNb, averagingMode, window); + MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis(fftSize, overlapPercent, averagingNb, averagingMode, window, linear); msgQueue->push(cmd); } diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index e6832627a..de1c35236 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -27,12 +27,19 @@ public: MESSAGE_CLASS_DECLARATION public: - MsgConfigureSpectrumVis(int fftSize, int overlapPercent, unsigned int averageNb, int averagingMode, FFTWindow::Function window) : + MsgConfigureSpectrumVis( + int fftSize, + int overlapPercent, + unsigned int averageNb, + int averagingMode, + FFTWindow::Function window, + bool linear) : Message(), m_fftSize(fftSize), m_overlapPercent(overlapPercent), m_averageNb(averageNb), - m_window(window) + m_window(window), + m_linear(linear) { m_averagingMode = averagingMode < 0 ? AvgModeNone : averagingMode > 2 ? AvgModeFixed : (SpectrumVis::AveragingMode) averagingMode; } @@ -42,6 +49,7 @@ public: unsigned int getAverageNb() const { return m_averageNb; } SpectrumVis::AveragingMode getAveragingMode() const { return m_averagingMode; } FFTWindow::Function getWindow() const { return m_window; } + bool getLinear() const { return m_linear; } private: int m_fftSize; @@ -49,6 +57,7 @@ public: unsigned int m_averageNb; SpectrumVis::AveragingMode m_averagingMode; FFTWindow::Function m_window; + bool m_linear; }; SpectrumVis(Real scalef, GLSpectrum* glSpectrum = 0); @@ -59,7 +68,8 @@ public: int overlapPercent, unsigned int averagingNb, int averagingMode, - FFTWindow::Function window); + FFTWindow::Function window, + bool m_linear); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); void feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly); diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index a13a49c40..e9040f870 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -200,7 +200,8 @@ void GLSpectrumGUI::applySettings() m_fftOverlap, m_averagingNb, m_averagingMode, - (FFTWindow::Function)m_fftWindow); + (FFTWindow::Function)m_fftWindow, + m_linear); } setAveragingToolitp(); @@ -215,7 +216,8 @@ void GLSpectrumGUI::on_fftWindow_currentIndexChanged(int index) m_fftOverlap, m_averagingNb, m_averagingMode, - (FFTWindow::Function)m_fftWindow); + (FFTWindow::Function)m_fftWindow, + m_linear); } } @@ -228,7 +230,8 @@ void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index) m_fftOverlap, m_averagingNb, m_averagingMode, - (FFTWindow::Function)m_fftWindow); + (FFTWindow::Function)m_fftWindow, + m_linear); } setAveragingToolitp(); } @@ -243,7 +246,8 @@ void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) m_fftOverlap, m_averagingNb, m_averagingMode, - (FFTWindow::Function)m_fftWindow); + (FFTWindow::Function)m_fftWindow, + m_linear); } if (m_glSpectrum != 0) @@ -267,7 +271,8 @@ void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) m_fftOverlap, m_averagingNb, m_averagingMode, - (FFTWindow::Function)m_fftWindow); + (FFTWindow::Function)m_fftWindow, + m_linear); } if (m_glSpectrum != 0) @@ -280,6 +285,21 @@ void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) setAveragingToolitp(); } +void GLSpectrumGUI::on_linscale_toggled(bool checked) +{ + m_linear = checked; + + if(m_spectrumVis != 0) { + m_spectrumVis->configure(m_messageQueueToVis, + m_fftSize, + m_fftOverlap, + m_averagingNb, + m_averagingMode, + (FFTWindow::Function)m_fftWindow, + m_linear); + } +} + void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index) { m_refLevel = 0 - index * 5; diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 773274595..13d305231 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -63,6 +63,7 @@ private: int m_averagingIndex; int m_averagingMaxScale; //!< Max power of 10 multiplier to 2,5,10 base ex: 2 -> 2,5,10,20,50,100,200,500,1000 unsigned int m_averagingNb; + bool m_linear; //!< linear else logarithmic scale void applySettings(); int getAveragingIndex(int averaging) const; @@ -85,6 +86,7 @@ private slots: void on_traceIntensity_valueChanged(int index); void on_averagingMode_currentIndexChanged(int index); void on_averaging_currentIndexChanged(int index); + void on_linscale_toggled(bool checked); void on_waterfall_toggled(bool checked); void on_histogram_toggled(bool checked); diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index ed90ebf08..6942e96bc 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -6,7 +6,7 @@ 0 0 - 331 + 342 59
    @@ -633,6 +633,24 @@
    + + + + Logarithmic / Linear scale selection + + + + + + + :/logarithmic.png + :/linear.png:/logarithmic.png + + + true + + + diff --git a/sdrgui/resources/linear.png b/sdrgui/resources/linear.png new file mode 100644 index 0000000000000000000000000000000000000000..115bcaf8a5a6dc7d35b21549ae9799dc39a16011 GIT binary patch literal 458 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYYs>cdx@v7EBhmM7C|dLzedOTKq1-8kcblJ{M_8syb>Unfx)>bHL)Z$ zMWH;iBtya7(>EYRFO{8vfl<%X#WBR=_}Xjxy=5H*7#`TxC#0%#n9aKMu5|rnx9$c7 zC6BHf8H#2Hj~zM`_9pku-+UgaO1nUn#t4 zZTS$NzP3Pw=la7&o;|Bp)!+UYbShCZl`HhM^c6;*2RB~->be-lDD|V=;h`kE#K}X; z3x$5fze!!TwE6M@`Hj)_@6T#xJ&3L0{=;~C#ikFEb6D%z^(89G-z`6 z5B)FL|4C=h{GqgtY5&3JQ??ut-`VrN@&5rYwSS!U%=?c8?TxziX!Q@#AEIJWrKi~T z$%QgapW%P-ai#G0#`pv?;roZ?OWiwrr2K~|^7Nw&)U;@YtIK=ll- xxvMY!J@!a!^2-X{69KjJzqwyHa_K|RDQ+z@iT^>gnp|vd$@?2>{!%zgPeO literal 0 HcmV?d00001 diff --git a/sdrgui/resources/linear.xcf b/sdrgui/resources/linear.xcf new file mode 100644 index 0000000000000000000000000000000000000000..53d08ebdf95007a52616fdbc74696971d4ed4482 GIT binary patch literal 14052 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 ZpK8Y7zrXC68veh+teYBsJ}@=pzX4#_BNhMv literal 0 HcmV?d00001 diff --git a/sdrgui/resources/logarithmic.png b/sdrgui/resources/logarithmic.png new file mode 100644 index 0000000000000000000000000000000000000000..32bb9bd1a8d19ad4151493ef00451f6f3430cd54 GIT binary patch literal 542 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYYs>cdx@v7EBhmM7C{SZ!K~xofI_mFArU3c`MJ5Nc_lzD1A}u>YGO%h zib8p2Nrr;Er*A-tUMf2S17nk?i(`n#@x4>+^<4r5j@EykXV2MrLQAB>X_Hi|#xxI; zMS}9K?yJMJj)s5G6r+&$URiv8rsex{F^d~K znGT%cI(jrByIi5k%7HJX?i!ODAJ4D4%rxGHbxIE=6)g~#V0_jfxqzpK>97Omi*JiV z6Bt*Rs&*9Zh-aGCV4CnPx`MglbLjW1d&v%$xtM;KY~a-SyVtYXI00|a;|x-A7oZAFxP!83s#xS zx}QCw@W0dzZKL{ILM)s`2^V*7&PwxISSz-xj6>qPo~^>f)WrEk|8KH~9sjgNX$7u<2zYJGmSy0Kpn6iuG4elF{r5}E)q9@dHg literal 0 HcmV?d00001 diff --git a/sdrgui/resources/logarithmic.xcf b/sdrgui/resources/logarithmic.xcf new file mode 100644 index 0000000000000000000000000000000000000000..81e75579723fd3bc22fbbd3acbda11035ed0eb21 GIT binary patch literal 13768 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 + linear.png + logarithmic.png pin_last.png sweep.png minusrx.png From f6c596d55a06bdb19c4e2100989882ea76d8f8e5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 6 Jul 2018 01:34:05 +0200 Subject: [PATCH 556/956] Spectrum: implemented linear scale mode --- sdrgui/dsp/spectrumvis.cpp | 62 ++++++++++++++++++++---------------- sdrgui/dsp/spectrumvis.h | 7 ++-- sdrgui/gui/glspectrum.cpp | 16 +++++++++- sdrgui/gui/glspectrum.h | 2 ++ sdrgui/gui/glspectrumgui.cpp | 35 +++++++++++++++++--- sdrgui/gui/physicalunit.h | 3 +- sdrgui/gui/scaleengine.cpp | 7 +++- 7 files changed, 95 insertions(+), 37 deletions(-) diff --git a/sdrgui/dsp/spectrumvis.cpp b/sdrgui/dsp/spectrumvis.cpp index ae386182e..7bc7f5426 100644 --- a/sdrgui/dsp/spectrumvis.cpp +++ b/sdrgui/dsp/spectrumvis.cpp @@ -20,18 +20,20 @@ SpectrumVis::SpectrumVis(Real scalef, GLSpectrum* glSpectrum) : BasebandSampleSink(), m_fft(FFTEngine::create()), m_fftBuffer(MAX_FFT_SIZE), - m_logPowerSpectrum(MAX_FFT_SIZE), + m_powerSpectrum(MAX_FFT_SIZE), m_fftBufferFill(0), m_needMoreSamples(false), m_scalef(scalef), m_glSpectrum(glSpectrum), m_averageNb(0), m_averagingMode(AvgModeNone), + m_linear(false), m_ofs(0), + m_powFFTDiv(1.0), m_mutex(QMutex::Recursive) { setObjectName("SpectrumVis"); - handleConfigure(1024, 0, 0, AvgModeNone, FFTWindow::BlackmanHarris); + handleConfigure(1024, 0, 0, AvgModeNone, FFTWindow::BlackmanHarris, false); } SpectrumVis::~SpectrumVis() @@ -120,9 +122,9 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV { c = fftOut[i]; v = c.real() * c.real() + c.imag() * c.imag(); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i * 2] = v; - m_logPowerSpectrum[i * 2 + 1] = v; + v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; + m_powerSpectrum[i * 2] = v; + m_powerSpectrum[i * 2 + 1] = v; } } else @@ -131,18 +133,18 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV { c = fftOut[i + halfSize]; v = c.real() * c.real() + c.imag() * c.imag(); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i] = v; + v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; + m_powerSpectrum[i] = v; c = fftOut[i]; v = c.real() * c.real() + c.imag() * c.imag(); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i + halfSize] = v; + v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; + m_powerSpectrum[i + halfSize] = v; } } // send new data to visualisation - m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize); + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); } else if (m_averagingMode == AvgModeMoving) { @@ -153,9 +155,9 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV c = fftOut[i]; v = c.real() * c.real() + c.imag() * c.imag(); v = m_movingAverage.storeAndGetAvg(v, i); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i * 2] = v; - m_logPowerSpectrum[i * 2 + 1] = v; + v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; + m_powerSpectrum[i * 2] = v; + m_powerSpectrum[i * 2 + 1] = v; } } else @@ -165,19 +167,19 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV c = fftOut[i + halfSize]; v = c.real() * c.real() + c.imag() * c.imag(); v = m_movingAverage.storeAndGetAvg(v, i+halfSize); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i] = v; + v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; + m_powerSpectrum[i] = v; c = fftOut[i]; v = c.real() * c.real() + c.imag() * c.imag(); v = m_movingAverage.storeAndGetAvg(v, i); - v = m_mult * log2f(v) + m_ofs; - m_logPowerSpectrum[i + halfSize] = v; + v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; + m_powerSpectrum[i + halfSize] = v; } } // send new data to visualisation - m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize); + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); m_movingAverage.nextAverage(); } else if (m_averagingMode == AvgModeFixed) @@ -193,9 +195,9 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV if (m_fixedAverage.storeAndGetAvg(avg, v, i)) { - avg = m_mult * log2f(avg) + m_ofs; - m_logPowerSpectrum[i * 2] = avg; - m_logPowerSpectrum[i * 2 + 1] = avg; + avg = m_linear ? v/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; + m_powerSpectrum[i * 2] = avg; + m_powerSpectrum[i * 2 + 1] = avg; } } } @@ -208,8 +210,8 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV if (m_fixedAverage.storeAndGetAvg(avg, v, i+halfSize)) { // result available - avg = m_mult * log2f(avg) + m_ofs; - m_logPowerSpectrum[i] = avg; + avg = m_linear ? v/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; + m_powerSpectrum[i] = avg; } c = fftOut[i]; @@ -217,8 +219,8 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV if (m_fixedAverage.storeAndGetAvg(avg, v, i)) { // result available - avg = m_mult * log2f(avg) + m_ofs; - m_logPowerSpectrum[i + halfSize] = avg; + avg = m_linear ? v/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; + m_powerSpectrum[i + halfSize] = avg; } } } @@ -226,7 +228,7 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV if (m_fixedAverage.nextAverage()) { // result available // send new data to visualisation - m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize); + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); } } @@ -268,7 +270,8 @@ bool SpectrumVis::handleMessage(const Message& message) conf.getOverlapPercent(), conf.getAverageNb(), conf.getAveragingMode(), - conf.getWindow()); + conf.getWindow(), + conf.getLinear()); return true; } else @@ -281,7 +284,8 @@ void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, unsigned int averageNb, AveragingMode averagingMode, - FFTWindow::Function window) + FFTWindow::Function window, + bool linear) { QMutexLocker mutexLocker(&m_mutex); @@ -317,5 +321,7 @@ void SpectrumVis::handleConfigure(int fftSize, m_fixedAverage.resize(fftSize, averageNb); m_averageNb = averageNb; m_averagingMode = averagingMode; + m_linear = linear; m_ofs = 20.0f * log10f(1.0f / m_fftSize); + m_powFFTDiv = m_fftSize*m_fftSize; } diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index de1c35236..a86a5c0fd 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -82,7 +82,7 @@ private: FFTWindow m_window; std::vector m_fftBuffer; - std::vector m_logPowerSpectrum; + std::vector m_powerSpectrum; std::size_t m_fftSize; std::size_t m_overlapPercent; @@ -97,8 +97,10 @@ private: FixedAverage2D m_fixedAverage; unsigned int m_averageNb; AveragingMode m_averagingMode; + bool m_linear; Real m_ofs; + Real m_powFFTDiv; static const Real m_mult; QMutex m_mutex; @@ -107,7 +109,8 @@ private: int overlapPercent, unsigned int averageNb, AveragingMode averagingMode, - FFTWindow::Function window); + FFTWindow::Function window, + bool linear); }; #endif // INCLUDE_SPECTRUMVIS_H diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index f565a0c95..1a9356469 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -39,6 +39,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_centerFrequency(100000000), m_referenceLevel(0), m_powerRange(100), + m_linear(false), m_decay(0), m_sampleRate(500000), m_timingRate(1), @@ -291,6 +292,13 @@ void GLSpectrum::setDisplayTraceIntensity(int intensity) update(); } +void GLSpectrum::setLinear(bool linear) +{ + m_linear = linear; + m_changesPending = true; + update(); +} + void GLSpectrum::addChannelMarker(ChannelMarker* channelMarker) { QMutexLocker mutexLocker(&m_mutex); @@ -1075,7 +1083,13 @@ void GLSpectrum::applyChanges() } m_powerScale.setSize(histogramHeight); - m_powerScale.setRange(Unit::Decibel, m_referenceLevel - m_powerRange, m_referenceLevel); + + if (m_linear) { + m_powerScale.setRange(Unit::Scientific, m_referenceLevel - m_powerRange, m_referenceLevel); + } else { + m_powerScale.setRange(Unit::Decibel, m_referenceLevel - m_powerRange, m_referenceLevel); + } + leftMargin = m_timeScale.getScaleWidth(); if(m_powerScale.getScaleWidth() > leftMargin) diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index 21301ba19..c1be4bdf9 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -81,6 +81,7 @@ public: void setDisplayGrid(bool display); void setDisplayGridIntensity(int intensity); void setDisplayTraceIntensity(int intensity); + void setLinear(bool linear); qint32 getSampleRate() const { return m_sampleRate; } void addChannelMarker(ChannelMarker* channelMarker); @@ -130,6 +131,7 @@ private: qint64 m_centerFrequency; Real m_referenceLevel; Real m_powerRange; + bool m_linear; int m_decay; quint32 m_sampleRate; quint32 m_timingRate; diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index e9040f870..10f11e7ad 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -77,6 +77,7 @@ void GLSpectrumGUI::resetToDefaults() m_invert = true; m_averagingMode = AvgModeNone; m_averagingIndex = 0; + m_linear = false; applySettings(); } @@ -103,6 +104,7 @@ QByteArray GLSpectrumGUI::serialize() const s.writeReal(18, m_glSpectrum->getWaterfallShare()); s.writeS32(19, (int) m_averagingMode); s.writeS32(20, (qint32) getAveragingValue(m_averagingIndex)); + s.writeBool(21, m_linear); return s.final(); } @@ -142,6 +144,7 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) d.readS32(20, &tmp, 0); m_averagingIndex = getAveragingIndex(tmp); m_averagingNb = getAveragingValue(m_averagingIndex); + d.readBool(21, &m_linear, false); m_glSpectrum->setWaterfallShare(waterfallShare); applySettings(); @@ -165,6 +168,7 @@ void GLSpectrumGUI::applySettings() ui->levelRange->setCurrentIndex((100 - m_powerRange) / 5); ui->averaging->setCurrentIndex(m_averagingIndex); ui->averagingMode->setCurrentIndex((int) m_averagingMode); + ui->linscale->setChecked(m_linear); ui->decay->setSliderPosition(m_decay); ui->holdoff->setSliderPosition(m_histogramLateHoldoff); ui->stroke->setSliderPosition(m_histogramStroke); @@ -193,6 +197,7 @@ void GLSpectrumGUI::applySettings() m_glSpectrum->setInvertedWaterfall(m_invert); m_glSpectrum->setDisplayGrid(m_displayGrid); m_glSpectrum->setDisplayGridIntensity(m_displayGridIntensity); + m_glSpectrum->setLinear(m_linear); if (m_spectrumVis) { m_spectrumVis->configure(m_messageQueueToVis, @@ -298,21 +303,43 @@ void GLSpectrumGUI::on_linscale_toggled(bool checked) (FFTWindow::Function)m_fftWindow, m_linear); } + + if(m_glSpectrum != 0) + { + Real refLevel = m_linear ? pow(10.0, m_refLevel/10.0) : m_refLevel; + Real powerRange = m_linear ? pow(10.0, m_refLevel/10.0) : m_powerRange; + qDebug("GLSpectrumGUI::on_linscale_toggled: refLevel: %e powerRange: %e", refLevel, powerRange); + m_glSpectrum->setReferenceLevel(refLevel); + m_glSpectrum->setPowerRange(powerRange); + m_glSpectrum->setLinear(m_linear); + } } void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index) { m_refLevel = 0 - index * 5; - if(m_glSpectrum != 0) { - m_glSpectrum->setReferenceLevel(m_refLevel); + + if(m_glSpectrum != 0) + { + Real refLevel = m_linear ? pow(10.0, m_refLevel/10.0) : m_refLevel; + Real powerRange = m_linear ? pow(10.0, m_refLevel/10.0) : m_powerRange; + qDebug("GLSpectrumGUI::on_refLevel_currentIndexChanged: refLevel: %e ", refLevel); + m_glSpectrum->setReferenceLevel(refLevel); + m_glSpectrum->setPowerRange(powerRange); } } void GLSpectrumGUI::on_levelRange_currentIndexChanged(int index) { m_powerRange = 100 - index * 5; - if(m_glSpectrum != 0) { - m_glSpectrum->setPowerRange(m_powerRange); + + if(m_glSpectrum != 0) + { + Real refLevel = m_linear ? pow(10.0, m_refLevel/10.0) : m_refLevel; + Real powerRange = m_linear ? pow(10.0, m_refLevel/10.0) : m_powerRange; + qDebug("GLSpectrumGUI::on_levelRange_currentIndexChanged: powerRange: %e", powerRange); + m_glSpectrum->setReferenceLevel(refLevel); + m_glSpectrum->setPowerRange(powerRange); } } diff --git a/sdrgui/gui/physicalunit.h b/sdrgui/gui/physicalunit.h index 27399a121..8ce16e6cc 100644 --- a/sdrgui/gui/physicalunit.h +++ b/sdrgui/gui/physicalunit.h @@ -30,7 +30,8 @@ namespace Unit { AngleDegrees, Time, TimeHMS, - Volt + Volt, + Scientific }; }; diff --git a/sdrgui/gui/scaleengine.cpp b/sdrgui/gui/scaleengine.cpp index 195aac116..a46e2b813 100644 --- a/sdrgui/gui/scaleengine.cpp +++ b/sdrgui/gui/scaleengine.cpp @@ -31,7 +31,11 @@ QString ScaleEngine::formatTick(double value, int decimalPlaces) { if (m_physicalUnit != Unit::TimeHMS) { - return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces); + if (m_physicalUnit == Unit::Scientific) { + return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'e', 2); + } else { + return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces); + } } else { @@ -106,6 +110,7 @@ void ScaleEngine::calcScaleFactor() switch(m_physicalUnit) { case Unit::None: + case Unit::Scientific: m_unitStr.clear(); break; From df3c838650bc0e4783fa0d590ffc730b0bd91dfb Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 7 Jul 2018 18:04:56 +0200 Subject: [PATCH 557/956] Spectrum GUI: arrange widget sizes so that upper and lower row length match --- sdrgui/gui/glspectrumgui.ui | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index 6942e96bc..277c560ed 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -6,7 +6,7 @@ 0 0 - 342 + 378 59 @@ -328,6 +328,9 @@ + + 2 + @@ -396,13 +399,13 @@ - 50 + 45 0 - 50 + 45 16777215 @@ -497,13 +500,13 @@ - 50 + 45 0 - 50 + 45 16777215 @@ -568,13 +571,13 @@ - 50 + 45 0 - 50 + 45 16777215 From ea780d72c4b5a555d160cde82eef6153267c6906 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 7 Jul 2018 18:40:56 +0200 Subject: [PATCH 558/956] Updated version, documentation and Debian changelog --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 ++++++ doc/img/MainWindow_spectrum_gui.png | Bin 17263 -> 17813 bytes doc/img/MainWindow_spectrum_gui.xcf | Bin 89736 -> 101673 bytes sdrgui/readme.md | 6 ++++++ 7 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index d88bf36ce..a8b3ba9e2 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.0.2"); + QCoreApplication::setApplicationVersion("4.0.3"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 8224f2dcb..04e011e60 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.0.2"); + QCoreApplication::setApplicationVersion("4.0.3"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 360568442..1f9080571 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.0.2"); + QCoreApplication::setApplicationVersion("4.0.3"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index d36d04fb2..6e814b12f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (4.0.3-1) unstable; urgency=medium + + * Spectrum: linear mode for spectrum + + -- Edouard Griffiths, F4EXB Sat, 07 Jul 2018 15:14:18 +0200 + sdrangel (4.0.2-1) unstable; urgency=medium * Spectrum: added averaging diff --git a/doc/img/MainWindow_spectrum_gui.png b/doc/img/MainWindow_spectrum_gui.png index 59d6e82093e996f4920ccb587e4e80775ea36d2b..ec92eaf15fe5c5cd655323e1638cb1672d2be0c5 100644 GIT binary patch literal 17813 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 literal 17263 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 F{~PbNC9?nk delta 4577 zcmZA52~?G39>?+D3ztR95S3*>6iw72MWZ|}Sc+q6pcR<8q}L>u3QMC*#;jWe6Gn-8 zU2sVeCDBTgH&RU05X&XeKpm&W%%)>mX<(M)l+E|O%b9z7&iS18InQ(N`~LsW_Hx<0 zD!8yFXwjWU%JxAih9g!Au}NXaq;}_}*gjIHD5-n7)H6`JEko+}gf!?k>8_zt`V#5B z3)0xD(xeD!YNPaMwKTh%H0QGP+pQ>9rZs(lDvynzVMSv|*&QS<>4pCDW94 zJ|^9cuL0j4SY&6CZ2L#rEsp1Rts$LcgJ&40Gypdvd!{Kxi2% zQHutgL$lN(0ByjCwW!A#T*P&$J2R51oQtx<&FB^d zp$Tiybgh38djFK54~8`?tk)H@nA`+8l@6&Aw@M`i}MaB3{Fv((XJ>>O2PpScY;`;Q&tJ z8(fiY4nhP9@n5OS4T!}pNI|;PwJT|LH+V=mI^kBNA_G(KBx${8#@;+GB;jdK_|`0W zW-5_;yNahRvBWG%Y#-<#9IN=I)RWL|>xurj%fCN~rzP>Uq$lwr7GpiO;{$wxuh1m* zGT=Zl)?pj=g6-YwODXwvQc8ZCN)3+U3;c-Rd2lPlnHfp-!3pv17{pQFO{dT8{%w7R znnU}17U=MN)Hj{}gE=uy0-fDnSKy;vh3rZcZ8SXh^~lPxwBIE|np16;E60CG%2h zu+8y{yVV=Awe5y6iRo9OD)5ynCY{a{$bIr>ancoE`eB_Qp(Oe1N8tn0(`L~V#-u!+% zOU?69Z^+h1C!Oad#44ZqKK|5ie^|0}X4yHj?3`J4&MZ4;mYp-p&Y5NZ8keQXsdx?S zj>&I>%`y2X*c@53O$|dwDQ;>nDuZw@CSw*HD26j5bh&xezBweJ%s+Z7%<1p>w@v@h z{BZilKu5N_**Bxdig=v{A`G$q{n;T>HhVI=8~S26#$pEM;$^JB6=^0%+{_3hpbt1# zW{#0&jmE=xibrEW?%}Q#EV#r_1KONq+HfUF6rfxUM}h7Zo&?HB+WZ4 z&1bUaGgg+QJ93s&D6O`p$W77L$_jd55d_H?VpFN*H1ewS}4)Smo?EM11SHRvch(~V> z=Duw8LbiIL9Z!%_;qz1$VlB2}59)CS7n!96W>NmMV57;|^h(?CAZ}?D&s-Hnm=Q&R zfe!0J5Lvvm2bK0zNxSLm_JHi!E%)O_pcfu ztr~|%kc&c;paMH_5U24iu1c#zrPo(s3wD7enfUsrR4(8e4`_)PBw_&4FaeKY9;sJ3 zOQTwwj`A+HplD)or@ePGX_r3dj?FupR@$X(4_EOFMd^C8?VGo{(+>A^aoKR)UH46U zV+X0?a}avP&;I=zS4kVUU>6SKQ(V9`sk|j(kca_DlQwamO}s@m{l>j+M0?V%=s{&5 zMq(mnVm@BQ8oY(w_}G)S%{;KJw=IYj=1DvJUDDoi%pIF=+FLl2T*Z_2mdoa}svfSi zo$u_t!Lc>n-R+z2)@*)ja5|eTIFnPuD&GFB=cTQz{B0rNp4+;iFNR|*W?(K}mbTv@ zRdS!ob=ZczAjZlsaS4B@qs%ZK(hvM3Ew-R-1mT|Ix;;roKvkYI zwW@763(-}G#5!WD-Y}#0rMV}ShkHwPdw0#7gtZ>32TFTh0AswT#J|6KC;hC~>eFD- ztFKDCSg*UH(H;FTLVEvmsfJ^*hWpekLMh(CJ|?y1IF+;bi3bR+5s%&&jQj8qW|QPf zbAR0_ThKf{ceiW%RvltO_wlxI6;FCr-1`_<-?n`#%oa!D-6<2Ix9s=KyKl;MoK}aJ zr$eK`5L?CDU+0kOim?uib=_VZ!(Kco+x(!p-%3+{7pEM{OXUMA&(E2wP34t#`v z;yixkfgy-OH}o}|>Z^mh9${0s3hmxE4.J. Logarithmic/linear scale + +Use this toggle button to switch between spectrum logarithmic and linear scale display. The face of the button will change to represent either a logaritmic or linear curve. + +When in linear mode the range control (4.4) has no effect because the actual range is between 0 and the reference level. The reference level in dB (4.3) still applies but is translated to a linear value e.g -40 dB is 1e-4. In linear mode the scale numbers are formatted using scientific notation so that they always occupy the same space. +

    5. Presets and commands

    The presets and commands tree view are by default stacked in tabs. The following sections describe the presets section 5A) and commands (section 5B) views successively From 8a9e9da5505dffe3df6a55ad3a221b1a04f390b7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 8 Jul 2018 13:07:21 +0200 Subject: [PATCH 559/956] Scope: fixed power overlay display --- sdrgui/gui/glscopeng.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index a802313b9..0941738cd 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -1911,17 +1911,18 @@ void GLScopeNG::drawChannelOverlay( } QFontMetricsF metrics(m_channelOverlayFont); - QRectF rect = metrics.boundingRect(text); - channelOverlayPixmap = QPixmap(rect.width() + 4.0f, rect.height()); + QRectF textRect = metrics.boundingRect(text); + QRectF overlayRect(0, 0, textRect.width()*1.05f + 4.0f, textRect.height()); + channelOverlayPixmap = QPixmap(overlayRect.width(), overlayRect.height()); channelOverlayPixmap.fill(Qt::transparent); QPainter painter(&channelOverlayPixmap); painter.setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing, false); - painter.fillRect(rect, QColor(0, 0, 0, 0x80)); + painter.fillRect(overlayRect, QColor(0, 0, 0, 0x80)); QColor textColor(color); textColor.setAlpha(0xC0); painter.setPen(textColor); painter.setFont(m_channelOverlayFont); - painter.drawText(QPointF(0, rect.height() - 2.0f), text); + painter.drawText(QPointF(2.0f, overlayRect.height() - 4.0f), text); painter.end(); m_glShaderPowerOverlay.initTexture(channelOverlayPixmap.toImage()); @@ -1940,11 +1941,12 @@ void GLScopeNG::drawChannelOverlay( 0, 0 }; - float shiftX = glScopeRect.width() - ((rect.width() + 4.0f) / width()); + float shiftX = glScopeRect.width() - ((overlayRect.width() + 4.0f) / width()); + float shiftY = 4.0f / height(); float rectX = glScopeRect.x() + shiftX; - float rectY = glScopeRect.y(); - float rectW = rect.width() / (float) width(); - float rectH = rect.height() / (float) height(); + float rectY = glScopeRect.y() + shiftY; + float rectW = overlayRect.width() / (float) width(); + float rectH = overlayRect.height() / (float) height(); QMatrix4x4 mat; mat.setToIdentity(); From b0eb2b6c1dfc095d4ad567ea8cb48c6319edc839 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 8 Jul 2018 15:06:33 +0200 Subject: [PATCH 560/956] Upgraded Channel analyzer version and updaed Debian changelog --- debian/changelog | 3 ++- plugins/channelrx/chanalyzer/chanalyzerplugin.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 6e814b12f..f2d900285 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ sdrangel (4.0.3-1) unstable; urgency=medium * Spectrum: linear mode for spectrum + * Scope: fixed power display overlay - -- Edouard Griffiths, F4EXB Sat, 07 Jul 2018 15:14:18 +0200 + -- Edouard Griffiths, F4EXB Sun, 08 Jul 2018 15:14:18 +0200 sdrangel (4.0.2-1) unstable; urgency=medium diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index dce4fe53a..3661f7cfa 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("4.0.2"), + QString("4.0.3"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 2670c17f6ab4bb63ee36627d5db9c707a92007e3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 8 Jul 2018 18:59:09 +0200 Subject: [PATCH 561/956] Scope: fixed vertical scale display and extended amp range --- Readme.md | 24 ++++++++++++------------ sdrgui/gui/glscopeng.cpp | 24 ++++++++++++++++-------- sdrgui/gui/glscopenggui.cpp | 24 ++++++++++++++++++------ sdrgui/gui/glscopenggui.h | 2 +- sdrgui/gui/glscopenggui.ui | 2 +- sdrgui/gui/scaleengine.cpp | 33 +++++++++++++++++---------------- sdrgui/gui/scaleengine.h | 2 ++ 7 files changed, 67 insertions(+), 44 deletions(-) diff --git a/Readme.md b/Readme.md index e7827ac93..e0d7fa8b5 100644 --- a/Readme.md +++ b/Readme.md @@ -1,10 +1,10 @@ ![SDR Angel banner](doc/img/sdrangel_banner.png) -**SDRangel** is an Open Source Qt5 / OpenGL 3.0+ (Linux) SDR and signal analyzer frontend to various hardware. +**SDRangel** is an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware. **Check the discussion group** [here](https://groups.io/g/sdrangel) -**⚠ Warning**: Windows distribution is provided as a by product of the Qt toolchain. The platform of choice to run SDRangel is definitely Linux. You are encouraged to use the group to seek help from other Windows users but the author cannot give help or any support for problems related to running the software on Windows. Issues specific to Windows problems opened on Github will be closed systematically. Windows distribution may be discontinued in the future. +**⚠ Warning**: Windows distribution is discontinued at version 4.0.0. This is the last version with a Windows build.

    Source code

    @@ -39,7 +39,7 @@ From version 2 SDRangel can integrate more than one hardware device running conc From version 3 transmission or signal generation is supported for BladeRF, HackRF (since version 3.1), LimeSDR (since version 3.4) and PlutoSDR (since version 3.7.8) using a sample sink plugin. These plugins are: - - [BladeRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerfoutput) limited support in Windows + - [BladeRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerfoutput) - [HackRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/hackrfoutput) - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) - [PlutoSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/plutosdroutput) @@ -252,8 +252,6 @@ If you have one or more serial devices interfacing the AMBE3000 chip in packet m Although such serial devices work with a serial interface at 400 kb in practice maybe for other reasons they are capable of handling only one conversation at a time. The software will allocate the device dynamically to a conversation with an inactivity timeout of 1 second so that conversations do not get interrupted constantly making the audio output too choppy. In practice you will have to have as many devices connected to your system as the number of conversations you would like to be handled in parallel. -Note that this is not supported in Windows because of trouble with COM port support (contributors welcome!). - Alternatively you can use [mbelib](https://github.com/szechyjs/mbelib) but mbelib comes with some copyright issues (see next). If you have mbelib installed in a custom location, say `/opt/install/mbelib` you will need to add these defines to the cmake command: `-DLIBMBE_INCLUDE_DIR=/opt/install/mbelib/include -DLIBMBE_LIBRARY=/opt/install/mbelib/lib/libmbe.so` Possible copyright issues apart (see next) the audio quality with the DVSI AMBE chip is much better. @@ -269,10 +267,11 @@ If you are not comfortable with this just do not install DSDcc and/or mbelib and

    Software distributions

    -In the [releases](https://github.com/f4exb/sdrangel/releases) section one can find binary distributions for some common systems: +In the [releases](https://github.com/f4exb/sdrangel/releases) section one can find binary distributions for some Debian based distributions: - - Debian x86_64 (Ubuntu 16.04, Ubuntu 17.10, Debian Stretch) - - Windows 32 bit (runs also in 64 bit Windows) + - Ubuntu 18.04 (Bionic) + - Ubuntu 16.04 (Xenial) + - Debian Stretch

    Debian distributions

    @@ -306,9 +305,11 @@ The default CPU governor is now `powersave` which exhibits excessive CPU usage w

    Windows distribution

    +The last Windows distribution is for 4.0.0. + This is the archive of the complete binary distribution that expands to the `sdrangel` directory. You can install it anywhere you like and click on `sdrangel.exe` to start. -⚠ Windows distribution is provided as a by product thanks to the Qt toolchain. The platform of choice to run SDRangel is definitely Linux and very little support can be given for the Windows distribution. +⚠ Windows distribution was provided as a by product thanks to the Qt toolchain. The platform of choice to run SDRangel is definitely Linux and very little support can be given for this Windows distribution.

    Software build

    @@ -317,7 +318,7 @@ This is the archive of the complete binary distribution that expands to the `sdr To be sure you will need at least Qt version 5.5. It definitely does not work with versions earlier than 5.3 but neither 5.3 nor 5.4 were tested. - Linux builds are made with 5.5.1 (Xenial) and 5.9 (Artful, Stretch) - - Windows build is made with 5.10.1 in 32 bit mode and has Qt ANGLE support (OpenGL emulation with DirectX) + - Windows build was made with 5.10.1 in 32 bit mode and has Qt ANGLE support (OpenGL emulation with DirectX)

    24 bit DSP

    @@ -446,8 +447,7 @@ You can uninstall the software with `make uninstall` or `sudo make uninstall` fr

    Limitations

    - Your hardware. Still SDRangel is relatively conservative on computer resources. - - OpenGL 3+ (Linux) - - OpenGL 4.3+ (Windows) for OpenGL native support however the Qt Angle framework may be able to make it work on systems supporting Direct-X only. + - OpenGL 3+

    Features

    diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index 0941738cd..d8d7b9ba3 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -1866,10 +1866,10 @@ void GLScopeNG::setPolarDisplays() void GLScopeNG::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) { ScopeVisNG::TraceData& traceData = (*m_tracesData)[highlightedTraceIndex]; - float amp_range = 2.0 / traceData.m_amp; - float amp_ofs = traceData.m_ofs; - float pow_floor = -100.0 + traceData.m_ofs * 100.0; - float pow_range = 100.0 / traceData.m_amp; + double amp_range = 2.0 / traceData.m_amp; + double amp_ofs = traceData.m_ofs; + double pow_floor = -100.0 + traceData.m_ofs * 100.0; + double pow_range = 100.0 / traceData.m_amp; switch (traceData.m_projectionType) { @@ -1878,8 +1878,12 @@ void GLScopeNG::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) break; case Projector::ProjectionMagLin: case Projector::ProjectionMagSq: - if (amp_range < 2.0) { - scale.setRange(Unit::None, amp_ofs * 1000.0, amp_range * 1000.0 + amp_ofs * 1000.0); + if (amp_range < 1e-6) { + scale.setRange(Unit::None, amp_ofs * 1e9, amp_range * 1e9 + amp_ofs * 1e9); + } else if (amp_range < 1e-3) { + scale.setRange(Unit::None, amp_ofs * 1e6, amp_range * 1e6 + amp_ofs * 1e6); + } else if (amp_range < 1.0) { + scale.setRange(Unit::None, amp_ofs * 1e3, amp_range * 1e3 + amp_ofs * 1e3); } else { scale.setRange(Unit::None, amp_ofs, amp_range + amp_ofs); } @@ -1891,8 +1895,12 @@ void GLScopeNG::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) case Projector::ProjectionReal: // Linear generic case Projector::ProjectionImag: default: - if (amp_range < 2.0) { - scale.setRange(Unit::None, - amp_range * 500.0 + amp_ofs * 1000.0, amp_range * 500.0 + amp_ofs * 1000.0); + if (amp_range < 1e-6) { + scale.setRange(Unit::None, - amp_range * 5e8 + amp_ofs * 1e9, amp_range * 5e8 + amp_ofs * 1e9); + } else if (amp_range < 1e-3) { + scale.setRange(Unit::None, - amp_range * 5e5 + amp_ofs * 1e6, amp_range * 5e5 + amp_ofs * 1e6); + } else if (amp_range < 1.0) { + scale.setRange(Unit::None, - amp_range * 5e2 + amp_ofs * 1e3, amp_range * 5e2 + amp_ofs * 1e3); } else { scale.setRange(Unit::None, - amp_range * 0.5 + amp_ofs, amp_range * 0.5 + amp_ofs); } diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index b03e29f9b..8ebdadc8d 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -22,7 +22,17 @@ #include "ui_glscopenggui.h" #include "util/simpleserializer.h" -const double GLScopeNGGUI::amps[14] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001, 0.00005, 0.00002, 0.00001 }; +const double GLScopeNGGUI::amps[27] = { + 2e-1, 1e-1, 5e-2, + 2e-2, 1e-2, 5e-3, + 2e-3, 1e-3, 5e-4, + 2e-4, 1e-4, 5e-5, + 2e-5, 1e-5, 5e-6, + 2e-6, 1e-6, 5e-7, + 2e-7, 1e-7, 5e-8, + 2e-8, 1e-8, 5e-9, + 2e-9, 1e-9, 5e-10, +}; GLScopeNGGUI::GLScopeNGGUI(QWidget* parent) : QWidget(parent), @@ -1030,12 +1040,14 @@ void GLScopeNGGUI::setAmpOfsDisplay() a = o/1000.0f; } - if(fabs(a) < 0.000001f) - ui->ofsText->setText(tr("%1\nn").arg(a * 1000000000.0)); - else if(fabs(a) < 0.001f) - ui->ofsText->setText(tr("%1\nµ").arg(a * 1000000.0)); + if(fabs(a) < 1e-9) + ui->ofsText->setText(tr("%1\np").arg(a * 1e12)); + else if(fabs(a) < 1e-6) + ui->ofsText->setText(tr("%1\nn").arg(a * 1e9)); + else if(fabs(a) < 1e-3) + ui->ofsText->setText(tr("%1\nµ").arg(a * 1e6)); else if(fabs(a) < 1.0f) - ui->ofsText->setText(tr("%1\nm").arg(a * 1000.0)); + ui->ofsText->setText(tr("%1\nm").arg(a * 1e3)); else ui->ofsText->setText(tr("%1").arg(a * 1.0)); } diff --git a/sdrgui/gui/glscopenggui.h b/sdrgui/gui/glscopenggui.h index e9c1e4cb8..3eae59aea 100644 --- a/sdrgui/gui/glscopenggui.h +++ b/sdrgui/gui/glscopenggui.h @@ -153,7 +153,7 @@ private: QColor m_focusedTraceColor; QColor m_focusedTriggerColor; - static const double amps[14]; + static const double amps[27]; void applySettings(); // First row diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index fd9583f90..a0e071382 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -859,7 +859,7 @@ kS/s Vertical range - 13 + 26 1 diff --git a/sdrgui/gui/scaleengine.cpp b/sdrgui/gui/scaleengine.cpp index a46e2b813..06fe67aa7 100644 --- a/sdrgui/gui/scaleengine.cpp +++ b/sdrgui/gui/scaleengine.cpp @@ -32,7 +32,7 @@ QString ScaleEngine::formatTick(double value, int decimalPlaces) if (m_physicalUnit != Unit::TimeHMS) { if (m_physicalUnit == Unit::Scientific) { - return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'e', 2); + return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'e', m_fixedDecimalPlaces); } else { return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces); } @@ -270,21 +270,21 @@ double ScaleEngine::calcMajorTickUnits(double distance, int* retDecimalPlaces) else if(distance < 30.0 * 86000.0) return 30.0 * 86000.0; else return 90.0 * 86000.0; - } else {*/ - if(base <= 1.0) { - base = 1.0; - } else if(base <= 2.0) { - base = 2.0; - } else if(base <= 2.5) { - base = 2.5; - if(decimalPlaces >= 0) - decimalPlaces++; - } else if(base <= 5.0) { - base = 5.0; - } else { - base = 10.0; - }/* - }*/ + } */ + + if(base <= 1.0) { + base = 1.0; + } else if(base <= 2.0) { + base = 2.0; + } else if(base <= 2.5) { + base = 2.5; + if(decimalPlaces >= 0) + decimalPlaces++; + } else if(base <= 5.0) { + base = 5.0; + } else { + base = 10.0; + } if(retDecimalPlaces != 0) { if(decimalPlaces < 0) @@ -522,6 +522,7 @@ ScaleEngine::ScaleEngine() : m_firstMajorTickValue(1.0), m_numMinorTicks(1), m_decimalPlaces(1), + m_fixedDecimalPlaces(2), m_makeOpposite(false) { } diff --git a/sdrgui/gui/scaleengine.h b/sdrgui/gui/scaleengine.h index 1a09bd2b8..a7e70f065 100644 --- a/sdrgui/gui/scaleengine.h +++ b/sdrgui/gui/scaleengine.h @@ -56,6 +56,7 @@ private: double m_firstMajorTickValue; int m_numMinorTicks; int m_decimalPlaces; + int m_fixedDecimalPlaces; bool m_makeOpposite; // will show -value instead of value QString formatTick(double value, int decimalPlaces); @@ -78,6 +79,7 @@ public: float getSize() { return m_size; } void setRange(Unit::Physical physicalUnit, float rangeMin, float rangeMax); void setMakeOpposite(bool makeOpposite) { m_makeOpposite = makeOpposite; } + void setFixedDecimalPlaces(int decimalPlaces) { m_fixedDecimalPlaces =decimalPlaces; } float getPosFromValue(double value); float getValueFromPos(double pos); From ea83e43d09e0098a2a71e98929b1ca95239d6c4d Mon Sep 17 00:00:00 2001 From: Edouard GRIFFITHS Date: Wed, 11 Jul 2018 18:05:05 +0200 Subject: [PATCH 562/956] New Windows build --- cm256cc/cm256cc.pro | 4 ++-- devices/devices.pro | 22 +++++++++---------- dsdcc/dsdcc.pro | 14 ++++++++---- fcdhid/fcdhid.pro | 8 +++---- fcdlib/fcdlib.pro | 8 +++---- libairspy/libairspy.pro | 12 +++++----- libairspyhf/libairspyhf.pro | 12 +++++----- libbladerf/libbladerf.pro | 20 ++++++++--------- libhackrf/libhackrf.pro | 12 +++++----- libiio/libiio.pro | 20 ++++++++--------- liblimesuite/liblimesuite.pro | 20 ++++++++--------- libperseus/libperseus.pro | 12 +++++----- librtlsdr/librtlsdr.pro | 12 +++++----- mbelib/mbelib.pro | 4 ++-- nanomsg/nanomsg.pro | 4 ++-- plugins/channelrx/chanalyzer/chanalyzer.pro | 4 ++-- plugins/channelrx/demodatv/demodatv.pro | 4 ++-- plugins/channelrx/demodbfm/demodbfm.pro | 4 ++-- plugins/channelrx/demoddsd/demoddsd.pro | 12 +++++----- .../bladerfoutput/bladerfoutput.pro | 4 ++-- .../samplesink/hackrfoutput/hackrfoutput.pro | 4 ++-- .../limesdroutput/limesdroutput.pro | 4 ++-- .../plutosdroutput/plutosdroutput.pro | 4 ++-- plugins/samplesource/airspy/airspy.pro | 4 ++-- plugins/samplesource/airspyhf/airspyhf.pro | 4 ++-- .../bladerfinput/bladerfinput.pro | 4 ++-- .../samplesource/hackrfinput/hackrfinput.pro | 4 ++-- .../limesdrinput/limesdrinput.pro | 4 ++-- .../plutosdrinput/plutosdrinput.pro | 4 ++-- plugins/samplesource/rtlsdr/rtlsdr.pro | 4 ++-- sdrangel.windows.pro | 2 +- sdrbase/sdrbase.pro | 10 +++++---- sdrgui/sdrgui.pro | 4 ++-- serialdv/serialdv.pro | 4 ++-- windows.install.bat | 10 ++++----- 35 files changed, 145 insertions(+), 137 deletions(-) diff --git a/cm256cc/cm256cc.pro b/cm256cc/cm256cc.pro index bf9cb836c..26a533757 100644 --- a/cm256cc/cm256cc.pro +++ b/cm256cc/cm256cc.pro @@ -9,8 +9,8 @@ QT += core TEMPLATE = lib TARGET = cm256cc -CONFIG(MINGW32):LIBCM256CCSRC = "D:\softs\cm256cc" -CONFIG(MINGW64):LIBCM256CCSRC = "D:\softs\cm256cc" +CONFIG(MINGW32):LIBCM256CCSRC = "C:\softs\cm256cc" +CONFIG(MINGW64):LIBCM256CCSRC = "C:\softs\cm256cc" CONFIG(macx):LIBCM256CCSRC = "../../deps/cm256cc" INCLUDEPATH += $$LIBCM256CCSRC diff --git a/devices/devices.pro b/devices/devices.pro index ab4834d15..de7df266a 100644 --- a/devices/devices.pro +++ b/devices/devices.pro @@ -18,26 +18,26 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 macx:QMAKE_LFLAGS += -F/Library/Frameworks -CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW32):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW64):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" CONFIG(macx):LIBHACKRFSRC = "/opt/local/include" -CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host" -CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" CONFIG(macx):LIBLIMESUITESRC = "../../../LimeSuite-17.12.0" -CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW32):LIBPERSEUSSRC = "D:\softs\libperseus-sdr" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW32):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" CONFIG(macx):LIBIIOSRC = "../../../libiio" -CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" INCLUDEPATH += $$PWD INCLUDEPATH += ../exports INCLUDEPATH += ../sdrbase INCLUDEPATH += $$LIBBLADERFSRC INCLUDEPATH += $$LIBHACKRFSRC -INCLUDEPATH += "D:\boost_1_58_0" -INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +INCLUDEPATH += "C:\softs\boost_1_66_0" +INCLUDEPATH += "C:\softs\libusb-1.0.20\include" INCLUDEPATH += ../liblimesuite/srcmw INCLUDEPATH += $$LIBLIMESUITESRC/src INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 diff --git a/dsdcc/dsdcc.pro b/dsdcc/dsdcc.pro index e4ced1a58..7cb2add66 100644 --- a/dsdcc/dsdcc.pro +++ b/dsdcc/dsdcc.pro @@ -9,12 +9,12 @@ QT += core TEMPLATE = lib TARGET = dsdcc -CONFIG(MINGW32):LIBDSDCCSRC = "D:\softs\dsdcc" -CONFIG(MINGW64):LIBDSDCCSRC = "D:\softs\dsdcc" +CONFIG(MINGW32):LIBDSDCCSRC = "C:\softs\dsdcc" +CONFIG(MINGW64):LIBDSDCCSRC = "C:\softs\dsdcc" CONFIG(macx):LIBDSDCCSRC = "../../deps/dsdcc" -CONFIG(MINGW32):LIBMBELIBSRC = "D:\softs\mbelib" -CONFIG(MINGW64):LIBMBELIBSRC = "D:\softs\mbelib" +CONFIG(MINGW32):LIBMBELIBSRC = "C:\softs\mbelib" +CONFIG(MINGW64):LIBMBELIBSRC = "C:\softs\mbelib" CONFIG(macx):LIBMBELIBSRC = "../../deps/mbelib" INCLUDEPATH += $$LIBDSDCCSRC @@ -37,6 +37,9 @@ $$LIBDSDCCSRC/dsd_symbol.cpp\ $$LIBDSDCCSRC/dstar.cpp\ $$LIBDSDCCSRC/ysf.cpp\ $$LIBDSDCCSRC/nxdn.cpp\ +$$LIBDSDCCSRC/nxdnconvolution.cpp\ +$$LIBDSDCCSRC/nxdncrc.cpp\ +$$LIBDSDCCSRC/nxdnmessage.cpp\ $$LIBDSDCCSRC/dpmr.cpp\ $$LIBDSDCCSRC/p25p1_heuristics.cpp\ $$LIBDSDCCSRC/fec.cpp\ @@ -62,6 +65,9 @@ $$LIBDSDCCSRC/dsd_symbol.h\ $$LIBDSDCCSRC/dstar.h\ $$LIBDSDCCSRC/ysf.h\ $$LIBDSDCCSRC/nxdn.h\ +$$LIBDSDCCSRC/nxdnconvolution.h\ +$$LIBDSDCCSRC/nxdncrc.h\ +$$LIBDSDCCSRC/nxdnmessage.h\ $$LIBDSDCCSRC/dpmr.h\ $$LIBDSDCCSRC/p25p1_heuristics.h\ $$LIBDSDCCSRC/runningmaxmin.h\ diff --git a/fcdhid/fcdhid.pro b/fcdhid/fcdhid.pro index 3b37e19bc..ecf5eb64a 100644 --- a/fcdhid/fcdhid.pro +++ b/fcdhid/fcdhid.pro @@ -9,10 +9,10 @@ QT += core TEMPLATE = lib TARGET = fcdhid -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" CONFIG(MINGW32):DEFINES += MINGW32=1 -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" CONFIG(MINGW64):DEFINES += MINGW32=1 CONFIG(macx):INCLUDEPATH += "/opt/local/include" @@ -26,6 +26,6 @@ HEADERS = $$PWD/fcdhid.h\ $$PWD/hid-libusb.h\ $$PWD/hidapi.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -liconv -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 -liconv +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -liconv +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 -liconv CONFIG(macx):LIBS += -L/opt/local/lib -lusb-1.0 -liconv diff --git a/fcdlib/fcdlib.pro b/fcdlib/fcdlib.pro index f6bf6c8b7..949f78d5c 100644 --- a/fcdlib/fcdlib.pro +++ b/fcdlib/fcdlib.pro @@ -9,8 +9,8 @@ QT += core TEMPLATE = lib TARGET = fcdlib -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" SOURCES = $$PWD/fcdtraits.cpp\ $$PWD/fcdproplusconst.cpp\ @@ -20,5 +20,5 @@ HEADERS = $$PWD/fcdtraits.h\ $$PWD/fcdproplusconst.h\ $$PWD/fcdproconst.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 diff --git a/libairspy/libairspy.pro b/libairspy/libairspy.pro index aa5031906..5506a510e 100644 --- a/libairspy/libairspy.pro +++ b/libairspy/libairspy.pro @@ -9,12 +9,12 @@ QT += core TEMPLATE = lib TARGET = libairspy -CONFIG(MINGW32):LIBAIRSPYSRC = "D:\softs\libairspy\libairspy" -CONFIG(MINGW64):LIBAIRSPYSRC = "D:\softs\libairspy\libairspy" +CONFIG(MINGW32):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" +CONFIG(MINGW64):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" INCLUDEPATH += $$LIBAIRSPYSRC/src -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" SOURCES = $$LIBAIRSPYSRC/src/airspy.c\ $$LIBAIRSPYSRC/src/iqconverter_float.c\ @@ -26,8 +26,8 @@ HEADERS = $$LIBAIRSPYSRC/src/airspy.h\ $$LIBAIRSPYSRC/src/iqconverter_int16.h\ $$LIBAIRSPYSRC/src/filters.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libairspyhf/libairspyhf.pro b/libairspyhf/libairspyhf.pro index 0c9efeae6..9639c7900 100644 --- a/libairspyhf/libairspyhf.pro +++ b/libairspyhf/libairspyhf.pro @@ -9,12 +9,12 @@ QT += core TEMPLATE = lib TARGET = libairspyhf -CONFIG(MINGW32):LIBAIRSPYHFSRC = "D:\softs\airspyhf\libairspyhf" -CONFIG(MINGW64):LIBAIRSPYHFSRC = "D:\softs\airspyhf\libairspyhf" +CONFIG(MINGW32):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" +CONFIG(MINGW64):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" INCLUDEPATH += $$LIBAIRSPYHFSRC/src -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" SOURCES = $$LIBAIRSPYHFSRC/src/airspyhf.c\ $$LIBAIRSPYHFSRC/src/iqbalancer.c @@ -23,8 +23,8 @@ HEADERS = $$LIBAIRSPYHFSRC/src/airspyhf.h\ $$LIBAIRSPYHFSRC/src/airspyhf_commands.h\ $$LIBAIRSPYHFSRC/src/iqbalancer.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libbladerf/libbladerf.pro b/libbladerf/libbladerf.pro index 610a9c797..4919c084f 100644 --- a/libbladerf/libbladerf.pro +++ b/libbladerf/libbladerf.pro @@ -11,12 +11,12 @@ TARGET = libbladerf DEFINES += BLADERF_OS_WINDOWS=1 -CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF" -CONFIG(MINGW32):LIBBLADERFCOMMONSRC = "D:\softs\bladeRF\host\common" -CONFIG(MINGW32):LIBBLADERFLIBSRC = "D:\softs\bladeRF\host\libraries\libbladeRF" -CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF" -CONFIG(MINGW64):LIBBLADERFCOMMONSRC = "D:\softs\bladeRF\host\common" -CONFIG(MINGW64):LIBBLADERFLIBSRC = "D:\softs\bladeRF\host\libraries\libbladeRF" +CONFIG(MINGW32):LIBBLADERFSRC = "C:\softs\bladeRF" +CONFIG(MINGW32):LIBBLADERFCOMMONSRC = "C:\softs\bladeRF\host\common" +CONFIG(MINGW32):LIBBLADERFLIBSRC = "C:\softs\bladeRF\host\libraries\libbladeRF" +CONFIG(MINGW64):LIBBLADERFSRC = "C:\softs\bladeRF" +CONFIG(MINGW64):LIBBLADERFCOMMONSRC = "C:\softs\bladeRF\host\common" +CONFIG(MINGW64):LIBBLADERFLIBSRC = "C:\softs\bladeRF\host\libraries\libbladeRF" INCLUDEPATH += $$LIBBLADERFLIBSRC/include INCLUDEPATH += $$LIBBLADERFLIBSRC/src INCLUDEPATH += $$LIBBLADERFSRC/firmware_common @@ -25,8 +25,8 @@ INCLUDEPATH += $$LIBBLADERFCOMMONSRC/include INCLUDEPATH += $$LIBBLADERFCOMMONSRC/include/windows INCLUDEPATH += $$PWD/include -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" SOURCES = $$LIBBLADERFLIBSRC/src/async.c\ $$LIBBLADERFLIBSRC/src/bladerf_priv.c\ @@ -102,8 +102,8 @@ HEADERS = $$LIBBLADERFLIBSRC/src/async.h\ $$PWD/include/backend/backend_config.h\ $$PWD/include/version.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libhackrf/libhackrf.pro b/libhackrf/libhackrf.pro index 5f92e0285..31bf07ada 100644 --- a/libhackrf/libhackrf.pro +++ b/libhackrf/libhackrf.pro @@ -9,19 +9,19 @@ QT += core TEMPLATE = lib TARGET = libhackrf -CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host\libhackrf" -CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host\libhackrf" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" INCLUDEPATH += $$LIBHACKRFSRC/src -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" SOURCES = $$LIBHACKRFSRC/src/hackrf.c HEADERS = $$LIBHACKRFSRC/src/hackrf.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libiio/libiio.pro b/libiio/libiio.pro index d02e40b64..4ea08c18a 100644 --- a/libiio/libiio.pro +++ b/libiio/libiio.pro @@ -9,23 +9,23 @@ QT += core TEMPLATE = lib TARGET = libiio -CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" DEFINES += LIBIIO_EXPORTS=1 INCLUDEPATH += $$PWD/includemw INCLUDEPATH += $$LIBIIOSRC -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" # LibXml2 Windows distribution from: # http://xmlsoft.org/sources/win32/ # http://xmlsoft.org/sources/win32/64bit/ -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libxml2-2.7.8.win32\include" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libxml2-2.9.3-win32-x86_64\include\libxml2" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libxml2-2.7.8.win32\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libxml2-2.9.3-win32-x86_64\include\libxml2" SOURCES = $$LIBIIOSRC/backend.c\ $$LIBIIOSRC/buffer.c\ @@ -49,11 +49,11 @@ HEADERS = $$LIBIIOSRC/debug.h\ $$LIBIIOSRC/iio-private.h\ $$PWD/includemw/iio-config.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 -CONFIG(MINGW32):LIBS += -LD:\softs\libxml2-2.7.8.win32\bin -llibxml2 -CONFIG(MINGW64):LIBS += -LD:\softs\libxml2-2.9.3-win32-x86_64\bin -llibxml2-2 +CONFIG(MINGW32):LIBS += -LC:\softs\libxml2-2.7.8.win32\bin -llibxml2 +CONFIG(MINGW64):LIBS += -LC:\softs\libxml2-2.9.3-win32-x86_64\bin -llibxml2-2 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/liblimesuite/liblimesuite.pro b/liblimesuite/liblimesuite.pro index a810c3336..ccd696bd1 100644 --- a/liblimesuite/liblimesuite.pro +++ b/liblimesuite/liblimesuite.pro @@ -15,14 +15,14 @@ DEFINES += "__unix__" QMAKE_CXXFLAGS += -fpermissive QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" -CONFIG(MINGW32):INCLUDEPATH += "..\libsqlite3\src" -CONFIG(MINGW64):INCLUDEPATH += "..\libsqlite3\src" +#CONFIG(MINGW32):INCLUDEPATH += "..\libsqlite3\src" +#CONFIG(MINGW64):INCLUDEPATH += "..\libsqlite3\src" INCLUDEPATH += $$LIBLIMESUITESRC/src INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 @@ -88,11 +88,11 @@ HEADERS = $$LIBLIMESUITESRC/src/API/*.h\ $$LIBLIMESUITESRC/src/FPGA_common/*.h\ $$LIBLIMESUITESRC/src/HPM7/*.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 -CONFIG(MINGW32):LIBS += -L../libsqlite3/release -llibsqlite3 -CONFIG(MINGW64):LIBS += -L../libsqlite3/release -llibsqlite3 +#CONFIG(MINGW32):LIBS += -L../libsqlite3/release -llibsqlite3 +#CONFIG(MINGW64):LIBS += -L../libsqlite3/release -llibsqlite3 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libperseus/libperseus.pro b/libperseus/libperseus.pro index e3cf9c2b3..ba418e6df 100644 --- a/libperseus/libperseus.pro +++ b/libperseus/libperseus.pro @@ -11,12 +11,12 @@ TARGET = libperseus DEFINES += HAVE_CONFIG_H=1 -CONFIG(MINGW32):LIBPERSEUSSRC = "D:\softs\libperseus-sdr" -CONFIG(MINGW64):LIBPERSEUSSRC = "D:\softs\libperseus-sdr" +CONFIG(MINGW32):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" +CONFIG(MINGW64):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" INCLUDEPATH += $$LIBPERSEUSSRC/src -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" SOURCES = fpga_data.c\ $$LIBPERSEUSSRC/fifo.c\ @@ -34,8 +34,8 @@ HEADERS = fpga_data.h\ $$LIBPERSEUSSRC/perseus-in.h\ $$LIBPERSEUSSRC/perseus-sdr.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/librtlsdr/librtlsdr.pro b/librtlsdr/librtlsdr.pro index 8568dd11e..4f5fa1e48 100644 --- a/librtlsdr/librtlsdr.pro +++ b/librtlsdr/librtlsdr.pro @@ -9,12 +9,12 @@ QT += core TEMPLATE = lib TARGET = librtlsdr -CONFIG(MINGW32):LIBRTLSDRSRC = "D:\softs\librtlsdr" -CONFIG(MINGW64):LIBRTLSDRSRC = "D:\softs\librtlsdr" +CONFIG(MINGW32):LIBRTLSDRSRC = "C:\softs\librtlsdr" +CONFIG(MINGW64):LIBRTLSDRSRC = "C:\softs\librtlsdr" INCLUDEPATH += $$LIBRTLSDRSRC/include -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" SOURCES = $$LIBRTLSDRSRC/src/librtlsdr.c\ $$LIBRTLSDRSRC/src/tuner_e4k.c\ @@ -37,8 +37,8 @@ HEADERS = $$LIBRTLSDRSRC/include/reg_field.h\ $$LIBRTLSDRSRC/src/getopt/getopt.h\ $$LIBRTLSDRSRC/src/convenience/convenience.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/mbelib/mbelib.pro b/mbelib/mbelib.pro index d4063254b..1714145f5 100644 --- a/mbelib/mbelib.pro +++ b/mbelib/mbelib.pro @@ -9,8 +9,8 @@ QT += core TEMPLATE = lib TARGET = mbelib -CONFIG(MINGW32):LIBMBELIBSRC = "D:\softs\mbelib" -CONFIG(MINGW64):LIBMBELIBSRC = "D:\softs\mbelib" +CONFIG(MINGW32):LIBMBELIBSRC = "C:\softs\mbelib" +CONFIG(MINGW64):LIBMBELIBSRC = "C:\softs\mbelib" CONFIG(macx):LIBMBELIBSRC = "../../deps/mbelib" INCLUDEPATH += $$LIBMBELIBSRC diff --git a/nanomsg/nanomsg.pro b/nanomsg/nanomsg.pro index c291592fd..efcc924bc 100644 --- a/nanomsg/nanomsg.pro +++ b/nanomsg/nanomsg.pro @@ -9,8 +9,8 @@ QT += core TEMPLATE = lib TARGET = nanomsg -CONFIG(MINGW32):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta" -CONFIG(MINGW64):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta" +CONFIG(MINGW32):LIBNANOMSGSRC = "C:\softs\nanomsg-0.8-beta" +CONFIG(MINGW64):LIBNANOMSGSRC = "C:\softs\nanomsg-0.8-beta" CONFIG(MINGW32):DEFINES += NN_HAVE_WINDOWS=1 CONFIG(MINGW32):DEFINES += _CRT_SECURE_NO_WARNINGS=1 diff --git a/plugins/channelrx/chanalyzer/chanalyzer.pro b/plugins/channelrx/chanalyzer/chanalyzer.pro index 2731e8062..e2209802a 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.pro +++ b/plugins/channelrx/chanalyzer/chanalyzer.pro @@ -23,8 +23,8 @@ INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" CONFIG(Release):build_subdir = release diff --git a/plugins/channelrx/demodatv/demodatv.pro b/plugins/channelrx/demodatv/demodatv.pro index 1b7811c02..decef5410 100644 --- a/plugins/channelrx/demodatv/demodatv.pro +++ b/plugins/channelrx/demodatv/demodatv.pro @@ -25,8 +25,8 @@ INCLUDEPATH += ../../../sdrgui CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" SOURCES += atvdemod.cpp\ diff --git a/plugins/channelrx/demodbfm/demodbfm.pro b/plugins/channelrx/demodbfm/demodbfm.pro index 7bcca3657..8f7d6bea1 100644 --- a/plugins/channelrx/demodbfm/demodbfm.pro +++ b/plugins/channelrx/demodbfm/demodbfm.pro @@ -24,8 +24,8 @@ INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" CONFIG(Release):build_subdir = release diff --git a/plugins/channelrx/demoddsd/demoddsd.pro b/plugins/channelrx/demoddsd/demoddsd.pro index 8ea93c839..8b8c96bc7 100644 --- a/plugins/channelrx/demoddsd/demoddsd.pro +++ b/plugins/channelrx/demoddsd/demoddsd.pro @@ -17,16 +17,16 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBDSDCCSRC = "D:\softs\dsdcc" -CONFIG(MINGW64):LIBDSDCCSRC = "D:\softs\dsdcc" +CONFIG(MINGW32):LIBDSDCCSRC = "C:\softs\dsdcc" +CONFIG(MINGW64):LIBDSDCCSRC = "C:\softs\dsdcc" CONFIG(macx):LIBDSDCCSRC = "../../../../deps/dsdcc" -CONFIG(MINGW32):LIBMBELIBSRC = "D:\softs\mbelib" -CONFIG(MINGW64):LIBMBELIBSRC = "D:\softs\mbelib" +CONFIG(MINGW32):LIBMBELIBSRC = "C:\softs\mbelib" +CONFIG(MINGW64):LIBMBELIBSRC = "C:\softs\mbelib" CONFIG(macx):LIBMBELIBSRC = "../../../../deps/mbelib" -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" INCLUDEPATH += $$PWD diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.pro b/plugins/samplesink/bladerfoutput/bladerfoutput.pro index b670583c8..9664b2eb4 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.pro +++ b/plugins/samplesink/bladerfoutput/bladerfoutput.pro @@ -17,8 +17,8 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW32):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW64):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase diff --git a/plugins/samplesink/hackrfoutput/hackrfoutput.pro b/plugins/samplesink/hackrfoutput/hackrfoutput.pro index 05c256d4d..3fe253caf 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutput.pro +++ b/plugins/samplesink/hackrfoutput/hackrfoutput.pro @@ -17,8 +17,8 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host" -CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase diff --git a/plugins/samplesink/limesdroutput/limesdroutput.pro b/plugins/samplesink/limesdroutput/limesdroutput.pro index 51efed10b..e296a3e6c 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.pro +++ b/plugins/samplesink/limesdroutput/limesdroutput.pro @@ -17,8 +17,8 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.pro b/plugins/samplesink/plutosdroutput/plutosdroutput.pro index f788ba60d..2d1fd03e4 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.pro +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.pro @@ -17,8 +17,8 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports diff --git a/plugins/samplesource/airspy/airspy.pro b/plugins/samplesource/airspy/airspy.pro index 9f3083501..5ba6d7050 100644 --- a/plugins/samplesource/airspy/airspy.pro +++ b/plugins/samplesource/airspy/airspy.pro @@ -11,8 +11,8 @@ QT += core gui widgets multimedia opengl TARGET = inputairspy -CONFIG(MINGW32):LIBAIRSPYSRC = "D:\softs\libairspy" -CONFIG(MINGW64):LIBAIRSPYSRC = "D:\softs\libairspy" +CONFIG(MINGW32):LIBAIRSPYSRC = "C:\softs\libairspy" +CONFIG(MINGW64):LIBAIRSPYSRC = "C:\softs\libairspy" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase diff --git a/plugins/samplesource/airspyhf/airspyhf.pro b/plugins/samplesource/airspyhf/airspyhf.pro index 70ec7bdea..6f95b2485 100644 --- a/plugins/samplesource/airspyhf/airspyhf.pro +++ b/plugins/samplesource/airspyhf/airspyhf.pro @@ -11,8 +11,8 @@ QT += core gui widgets multimedia opengl TARGET = inputairspyhf -CONFIG(MINGW32):LIBAIRSPYHFSRC = "D:\softs\airspyhf" -CONFIG(MINGW64):LIBAIRSPYHFSRC = "D:\softs\airspyhf" +CONFIG(MINGW32):LIBAIRSPYHFSRC = "C:\softs\airspyhf" +CONFIG(MINGW64):LIBAIRSPYHFSRC = "C:\softs\airspyhf" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase diff --git a/plugins/samplesource/bladerfinput/bladerfinput.pro b/plugins/samplesource/bladerfinput/bladerfinput.pro index 42543f9fd..8362897b8 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.pro +++ b/plugins/samplesource/bladerfinput/bladerfinput.pro @@ -17,8 +17,8 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW32):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW64):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase diff --git a/plugins/samplesource/hackrfinput/hackrfinput.pro b/plugins/samplesource/hackrfinput/hackrfinput.pro index 89317600b..ea5511dc7 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.pro +++ b/plugins/samplesource/hackrfinput/hackrfinput.pro @@ -17,8 +17,8 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host" -CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase diff --git a/plugins/samplesource/limesdrinput/limesdrinput.pro b/plugins/samplesource/limesdrinput/limesdrinput.pro index c1fca9b8f..f8fc5db5b 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.pro +++ b/plugins/samplesource/limesdrinput/limesdrinput.pro @@ -19,8 +19,8 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.pro b/plugins/samplesource/plutosdrinput/plutosdrinput.pro index 906ea2ea5..f18fe1bb4 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.pro +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.pro @@ -17,8 +17,8 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports diff --git a/plugins/samplesource/rtlsdr/rtlsdr.pro b/plugins/samplesource/rtlsdr/rtlsdr.pro index 8d89e19a7..3e91ff20b 100644 --- a/plugins/samplesource/rtlsdr/rtlsdr.pro +++ b/plugins/samplesource/rtlsdr/rtlsdr.pro @@ -17,8 +17,8 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBRTLSDRSRC = "D:\softs\librtlsdr" -CONFIG(MINGW64):LIBRTLSDRSRC = "D:\softs\librtlsdr" +CONFIG(MINGW32):LIBRTLSDRSRC = "C:\softs\librtlsdr" +CONFIG(MINGW64):LIBRTLSDRSRC = "C:\softs\librtlsdr" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index 2d0516235..f0fe75d8e 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -23,7 +23,7 @@ SUBDIRS += libairspyhf SUBDIRS += libbladerf SUBDIRS += libhackrf SUBDIRS += libiio -SUBDIRS += libsqlite3 +#SUBDIRS += libsqlite3 SUBDIRS += liblimesuite SUBDIRS += libperseus SUBDIRS += librtlsdr diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index fa6d0d6ed..98412c5ea 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -32,11 +32,11 @@ CONFIG(Debug):build_subdir = debug CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\serialDV" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\serialDV" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\serialDV" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\serialDV" CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" @@ -50,6 +50,7 @@ win32 { } SOURCES += audio/audiodevicemanager.cpp\ + audio/audiocompressor.cpp\ audio/audiofifo.cpp\ audio/audiooutput.cpp\ audio/audioinput.cpp\ @@ -124,6 +125,7 @@ SOURCES += audio/audiodevicemanager.cpp\ mainparser.cpp HEADERS += audio/audiodevicemanager.h\ + audio/audiocompressor.h\ audio/audiofifo.h\ audio/audiooutput.h\ audio/audioinput.h\ diff --git a/sdrgui/sdrgui.pro b/sdrgui/sdrgui.pro index 2d50bb7ec..18dcf1401 100644 --- a/sdrgui/sdrgui.pro +++ b/sdrgui/sdrgui.pro @@ -32,8 +32,8 @@ CONFIG(Debug):build_subdir = debug CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" diff --git a/serialdv/serialdv.pro b/serialdv/serialdv.pro index a79e9c490..e1b141383 100644 --- a/serialdv/serialdv.pro +++ b/serialdv/serialdv.pro @@ -9,8 +9,8 @@ QT += core TEMPLATE = lib TARGET = serialdv -CONFIG(MINGW32):LIBSERIALDVSRC = "D:\softs\serialDV" -CONFIG(MINGW64):LIBSERIALDVSRC = "D:\softs\serialDV" +CONFIG(MINGW32):LIBSERIALDVSRC = "C:\softs\serialDV" +CONFIG(MINGW64):LIBSERIALDVSRC = "C:\softs\serialDV" INCLUDEPATH += $$LIBSERIALDVSRC diff --git a/windows.install.bat b/windows.install.bat index a1774d709..2e3a43a0f 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -1,8 +1,8 @@ -SET libusbdir="D:\softs\libusb-1.0.20\MinGW32" -SET opencvdir="D:\softs\opencv\build\mw32\install\x86\mingw\bin" -SET libxml2dir="D:\softs\libxml2-2.7.8.win32" -SET libiconvdir="D:\softs\iconv-1.9.2.win32" -SET libzlib1dir="D:\softs\zlib-1.2.5" +SET libusbdir="C:\softs\libusb-1.0.20\MinGW32" +SET opencvdir="C:\softs\opencv\build\mw32\install\x86\mingw\bin" +SET libxml2dir="C:\softs\libxml2-2.7.8.win32" +SET libiconvdir="C:\softs\iconv-1.9.2.win32" +SET libzlib1dir="C:\softs\zlib-1.2.5" copy app\%1\sdrangel.exe %2 copy sdrbase\%1\sdrbase.dll %2 From 7ad7922a098db2e550df700215e2f57b4ef91a1e Mon Sep 17 00:00:00 2001 From: FFY00 Date: Thu, 12 Jul 2018 17:25:38 +0100 Subject: [PATCH 563/956] Docs: update Arch Linux information --- Readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index e0d7fa8b5..5e486aa35 100644 --- a/Readme.md +++ b/Readme.md @@ -405,16 +405,16 @@ Then you should be all set to build the software with `cmake` and `make` as disc - Note for udev rules: the same as for openSUSE applies. This is detailed in the previous paragraph for openSUSE. -

    Manjaro

    +

    Arch Linux / Manjaro

    -Tested with the 15.09 version with LXDE desktop (community supported). The exact desktop environment should not matter anyway. Since Manjaro is Arch Linux based prerequisites should be similar for Arch and all derivatives. +Tested with the 15.09 version with LXDE desktop (community supported). The exact desktop environment should not matter anyway. Prerequisites should be similar for Arch and all derivatives. `sudo pacman -S cmake pkg-config fftw qt5-multimedia qt5-tools qt5-base libusb boost boost-libs pulseaudio` Then you should be all set to build the software with `cmake` and `make` as discussed earlier. - Note1 for udev rules: the same as for openSUSE and Fedora applies. - - Note2: A package has been created in the AUR (thanks Mikos!), see: [sdrangel-git](https://aur.archlinux.org/packages/sdrangel-git). It is based on the `205fee6` commit of 8th December 2015. + - Note2: Two package are avaliable in the AUR (thanks Mikos!), [sdrangel](https://aur.archlinux.org/packages/sdrangel), which provides the lastest tagged release (stable), and [sdrangel-git](https://aur.archlinux.org/packages/sdrangel-git), which builds the latest commit from this repository (unstable).

    Windows

    From f66f9e1cfe1acd3dbb0f0bd9e504165e20ca6fd8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 16 Jul 2018 23:49:04 +0200 Subject: [PATCH 564/956] Fixed PlutoSDR output sample width. Fixes issue #198 --- plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp | 1 - plugins/samplesink/plutosdroutput/plutosdroutputthread.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp b/plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp index ea1e553a2..d68eae3b3 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp @@ -94,7 +94,6 @@ void PlutoSDROutputThread::run() for (p_dat = m_plutoBox->txBufferFirst(), ihs = 0; p_dat < p_end; p_dat += p_inc, ihs += 2) { m_plutoBox->txChannelConvert((int16_t*) p_dat, &m_buf[ihs]); - //*((int16_t*)p_dat) = m_buf[ihs] << 4; } // Schedule TX buffer for sending diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputthread.h b/plugins/samplesink/plutosdroutput/plutosdroutputthread.h index 782c6e389..45f80e218 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputthread.h +++ b/plugins/samplesink/plutosdroutput/plutosdroutputthread.h @@ -54,7 +54,7 @@ private: unsigned int m_log2Interp; // soft interpolation - Interpolators m_interpolators; + Interpolators m_interpolators; //!< Pluto is on 12 bit but iio_channel_convert_inverse converts from 16 to 12 bits void run(); void convert(qint16* buf, qint32 len); From 7c3f31af69f714ed1352a2672f3a103826e4c3f3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 17 Jul 2018 01:50:32 +0200 Subject: [PATCH 565/956] Web API: implemented CORS --- sdrbase/webapi/webapirequestmapper.cpp | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 135557b45..758ed1fe7 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -65,6 +65,7 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); response.setStatus(500,"Service not available"); errorResponse.init(); @@ -154,6 +155,7 @@ void WebAPIRequestMapper::instanceSummaryService(qtwebapp::HttpRequest& request, { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -195,6 +197,7 @@ void WebAPIRequestMapper::instanceDevicesService(qtwebapp::HttpRequest& request, SWGSDRangel::SWGInstanceDevicesResponse normalResponse; SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -228,6 +231,7 @@ void WebAPIRequestMapper::instanceChannelsService(qtwebapp::HttpRequest& request SWGSDRangel::SWGInstanceChannelsResponse normalResponse; SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -262,6 +266,7 @@ void WebAPIRequestMapper::instanceLoggingService(qtwebapp::HttpRequest& request, SWGSDRangel::SWGLoggingInfo normalResponse; SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -312,6 +317,7 @@ void WebAPIRequestMapper::instanceAudioService(qtwebapp::HttpRequest& request, q { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -340,6 +346,7 @@ void WebAPIRequestMapper::instanceAudioInputParametersService(qtwebapp::HttpRequ // TODO SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); QString jsonStr = request.getBody(); QJsonObject jsonObject; @@ -408,6 +415,7 @@ void WebAPIRequestMapper::instanceAudioOutputParametersService(qtwebapp::HttpReq { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); QString jsonStr = request.getBody(); QJsonObject jsonObject; @@ -476,6 +484,7 @@ void WebAPIRequestMapper::instanceAudioInputCleanupService(qtwebapp::HttpRequest { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "PATCH") { @@ -503,6 +512,7 @@ void WebAPIRequestMapper::instanceAudioOutputCleanupService(qtwebapp::HttpReques { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "PATCH") { @@ -530,6 +540,7 @@ void WebAPIRequestMapper::instanceLocationService(qtwebapp::HttpRequest& request { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -583,6 +594,7 @@ void WebAPIRequestMapper::instanceDVSerialService(qtwebapp::HttpRequest& request { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "PATCH") { @@ -617,6 +629,7 @@ void WebAPIRequestMapper::instancePresetsService(qtwebapp::HttpRequest& request, { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -636,6 +649,7 @@ void WebAPIRequestMapper::instancePresetService(qtwebapp::HttpRequest& request, { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "PATCH") { @@ -801,6 +815,7 @@ void WebAPIRequestMapper::instancePresetFileService(qtwebapp::HttpRequest& reque { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "PUT") { @@ -891,6 +906,7 @@ void WebAPIRequestMapper::instanceDeviceSetsService(qtwebapp::HttpRequest& reque { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -917,6 +933,7 @@ void WebAPIRequestMapper::instanceDeviceSetService(qtwebapp::HttpRequest& reques { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "POST") { @@ -962,6 +979,7 @@ void WebAPIRequestMapper::devicesetService(const std::string& indexStr, qtwebapp { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -999,6 +1017,7 @@ void WebAPIRequestMapper::devicesetFocusService(const std::string& indexStr, qtw { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); try { @@ -1038,6 +1057,7 @@ void WebAPIRequestMapper::devicesetDeviceService(const std::string& indexStr, qt { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); try { @@ -1101,6 +1121,7 @@ void WebAPIRequestMapper::devicesetDeviceSettingsService(const std::string& inde { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); try { @@ -1183,6 +1204,7 @@ void WebAPIRequestMapper::devicesetDeviceRunService(const std::string& indexStr, { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); try { @@ -1248,6 +1270,7 @@ void WebAPIRequestMapper::devicesetDeviceReportService(const std::string& indexS { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -1286,6 +1309,7 @@ void WebAPIRequestMapper::devicesetChannelsReportService(const std::string& inde { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod() == "GET") { @@ -1326,6 +1350,7 @@ void WebAPIRequestMapper::devicesetChannelService( { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); try { @@ -1403,6 +1428,7 @@ void WebAPIRequestMapper::devicesetChannelIndexService( { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); try { @@ -1447,6 +1473,7 @@ void WebAPIRequestMapper::devicesetChannelSettingsService( { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); try { @@ -1535,6 +1562,7 @@ void WebAPIRequestMapper::devicesetChannelReportService( { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); try { From 1be8663ad861cc58e117bfc10572b5c95feaf054 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 18 Jul 2018 08:36:28 +0200 Subject: [PATCH 566/956] Fix preset group delete not removing presets from he preset window --- sdrgui/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index 1b82f5e15..0580daa2c 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -1394,7 +1394,7 @@ void MainWindow::on_presetDelete_clicked() { m_settings.deletePresetGroup(item->text(0)); - ui->commandTree->clear(); + ui->presetTree->clear(); for (int i = 0; i < m_settings.getPresetCount(); ++i) { addPresetToTree(m_settings.getPreset(i)); From 06c1809b76a39eb9dcb457269ff3ff2835218487 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 18 Jul 2018 14:52:55 +0200 Subject: [PATCH 567/956] Debian: updated changelog --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index f2d900285..88db0bb1b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +sdrangel (4.0.4-1) unstable; urgency=medium + + * Fixed PlutoSDR output sample width. Fixes issue #198 + * Web API: implemented CORS + * Fix preset group delete not removing presets from the preset window + + -- Edouard Griffiths, F4EXB Wed, 18 Jul 2018 19:14:18 +0200 + sdrangel (4.0.3-1) unstable; urgency=medium * Spectrum: linear mode for spectrum From 1456725237d417db220dfd9336bce4b256cba0a2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 18 Jul 2018 21:19:11 +0200 Subject: [PATCH 568/956] Updated version --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp | 2 +- swagger/sdrangel/examples/devicesets_config.py | 5 +++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index a8b3ba9e2..e567913c1 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.0.3"); + QCoreApplication::setApplicationVersion("4.0.4"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 04e011e60..747f20d6c 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.0.3"); + QCoreApplication::setApplicationVersion("4.0.4"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 1f9080571..709724b99 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.0.3"); + QCoreApplication::setApplicationVersion("4.0.4"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp b/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp index 581c8b39d..fd5469c99 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp @@ -30,7 +30,7 @@ class DeviceSourceAPI; const PluginDescriptor PlutoSDROutputPlugin::m_pluginDescriptor = { QString("PlutoSDR Output"), - QString("4.0.0"), + QString("4.0.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/swagger/sdrangel/examples/devicesets_config.py b/swagger/sdrangel/examples/devicesets_config.py index 827074a25..15c47a6a2 100755 --- a/swagger/sdrangel/examples/devicesets_config.py +++ b/swagger/sdrangel/examples/devicesets_config.py @@ -16,11 +16,12 @@ commands = [ ["/preset", "PATCH", None, {"deviceSetIndex": 0, "preset": {"groupName": "OM144", "centerFrequency": 145480000, "type": "R", "name": "Rept + Simplex + DV"}}, "load preset on Rx 0"], ["/deviceset", "POST", None, None, "add Rx 1 device set"], ["/deviceset/1/device", "PUT", None, {"hwType": "SDRdaemonSource"}, "setup SDRdaemonSource on Rx 1"], - ["/preset", "PATCH", None, {"deviceSetIndex": 1, "preset": {"groupName": "OM430", "centerFrequency": 439550000, "type": "R", "name": "F5ZKP Daemon RPi3 SUSE"}}, "load preset on Rx 1"], +# ["/preset", "PATCH", None, {"deviceSetIndex": 1, "preset": {"groupName": "OM430", "centerFrequency": 439550000, "type": "R", "name": "F5ZKP Daemon RPi3 SUSE"}}, "load preset on Rx 1"], + ["/preset", "PATCH", None, {"deviceSetIndex": 1, "preset": {"groupName": "PRO400", "centerFrequency": 463880000, "type": "R", "name": "PM Nice NXDN daemon"}}, "load preset on Rx 1"], ["/deviceset", "POST", None, None, "add Rx 2 device set"], # ["/deviceset/2/device", "PUT", None, {"hwType": "SDRplay1"}, "setup SDRplay on Rx 2"], ["/deviceset/2/device", "PUT", None, {"hwType": "Perseus"}, "setup Perseus on Rx 2"], - ["/preset", "PATCH", None, {"deviceSetIndex": 2, "preset": {"groupName": "40m", "centerFrequency": 7140000, "type": "R", "name": "SSB low"}}, "load preset on Rx 2"], + ["/preset", "PATCH", None, {"deviceSetIndex": 2, "preset": {"groupName": "20m", "centerFrequency": 14160000, "type": "R", "name": "SSB"}}, "load preset on Rx 2"], ["/dvserial", "PATCH", {"dvserial": 1}, None, "set DV serial processing for AMBE frames decoding"], # ["/preset", "PATCH", None, {"deviceSetIndex": 0, "preset": {"groupName": "OM144", "centerFrequency": 145640000, "type": "R", "name": "Repeaters extended"}}, "load preset on Rx 0 twice"], ["/deviceset/0/device/run", "POST", None, None, "Start device on deviceset R0"], From e37c90c8d0ff246573380a0a3187579fe231ce63 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 21 Jul 2018 22:28:35 +0200 Subject: [PATCH 569/956] Web API: handle pre-flight requests --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 ++++++ sdrbase/webapi/webapirequestmapper.cpp | 11 +++++++++++ 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index e567913c1..48f0022a4 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.0.4"); + QCoreApplication::setApplicationVersion("4.0.5"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 747f20d6c..81af55fab 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.0.4"); + QCoreApplication::setApplicationVersion("4.0.5"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 709724b99..2075db47a 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.0.4"); + QCoreApplication::setApplicationVersion("4.0.5"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 88db0bb1b..2e1ec031f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (4.0.5-1) unstable; urgency=medium + + * Web API: handle pre-flight requests + + -- Edouard Griffiths, F4EXB Sun, 22 Jul 2018 09:14:18 +0200 + sdrangel (4.0.4-1) unstable; urgency=medium * Fixed PlutoSDR output sample width. Fixes issue #198 diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 758ed1fe7..c1bc2c7ef 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -76,6 +76,17 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http { QByteArray path=request.getPath(); + // Handle pre-flight requests + if (request.getMethod() == "OPTIONS") + { + qDebug("WebAPIRequestMapper::service: method OPTIONS: assume pre-flight"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Headers", "*"); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setStatus(200, "OK"); + return; + } + if (path == WebAPIAdapterInterface::instanceSummaryURL) { instanceSummaryService(request, response); } else if (path == WebAPIAdapterInterface::instanceDevicesURL) { From 1bb36f6670f0eecddc271e1d5917695570b41fa0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 30 Jul 2018 00:43:08 +0200 Subject: [PATCH 570/956] Web API: RTL-SDR: fixed RF bandwidth setting --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 8 +++++++- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 2 +- plugins/samplesource/rtlsdr/rtlsdrplugin.cpp | 2 +- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 48f0022a4..6e7708f3c 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.0.5"); + QCoreApplication::setApplicationVersion("4.0.6"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 81af55fab..0dacdc82e 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.0.5"); + QCoreApplication::setApplicationVersion("4.0.6"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 2075db47a..7e15b25e4 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.0.5"); + QCoreApplication::setApplicationVersion("4.0.6"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 2e1ec031f..2f224939d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,15 @@ +sdrangel (4.0.6-1) unstable; urgency=medium + + * Web API: RTL-SDR: fixed RF bandwidth setting + + -- Edouard Griffiths, F4EXB Sun, 05 Aug 2018 09:14:18 +0200 + sdrangel (4.0.5-1) unstable; urgency=medium * Web API: handle pre-flight requests -- Edouard Griffiths, F4EXB Sun, 22 Jul 2018 09:14:18 +0200 - + sdrangel (4.0.4-1) unstable; urgency=medium * Fixed PlutoSDR output sample width. Fixes issue #198 diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index e742458b2..0f798c7c0 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -587,7 +587,7 @@ int RTLSDRInput::webapiSettingsPutPatch( settings.m_transverterMode = response.getRtlSdrSettings()->getTransverterMode() != 0; } if (deviceSettingsKeys.contains("rfBandwidth")) { - settings.m_rfBandwidth = response.getRtlSdrSettings()->getRfBandwidth() != 0; + settings.m_rfBandwidth = response.getRtlSdrSettings()->getRfBandwidth(); } if (deviceSettingsKeys.contains("fileRecordName")) { settings.m_fileRecordName = *response.getRtlSdrSettings()->getFileRecordName(); diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp index 7cd29731b..e4d4c5959 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp @@ -14,7 +14,7 @@ const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = { QString("RTL-SDR Input"), - QString("3.14.7"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From e1bef01b966348a238d0e87c0a91cf1d88865820 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 30 Jul 2018 01:38:48 +0200 Subject: [PATCH 571/956] RTL-SDR: fixed low sample rate setting --- plugins/samplesource/rtlsdr/rtlsdrgui.cpp | 5 ++++- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp index 2c7e63f66..381a2b053 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp @@ -260,6 +260,7 @@ void RTLSDRGui::displaySettings() ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); ui->checkBox->setChecked(m_settings.m_noModMode); ui->agc->setChecked(m_settings.m_agc); + ui->lowSampleRate->setChecked(m_settings.m_lowSampleRate); } void RTLSDRGui::sendSettings() @@ -448,7 +449,9 @@ void RTLSDRGui::on_rfBW_changed(quint64 value) void RTLSDRGui::on_lowSampleRate_toggled(bool checked) { - if (checked) { + m_settings.m_lowSampleRate = checked; + + if (m_settings.m_lowSampleRate) { ui->sampleRate->setValueRange(7, RTLSDRInput::sampleRateLowRangeMin, RTLSDRInput::sampleRateLowRangeMax); } else { ui->sampleRate->setValueRange(7, RTLSDRInput::sampleRateHighRangeMin, RTLSDRInput::sampleRateHighRangeMax); diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index 0f798c7c0..406f104ba 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -496,6 +496,11 @@ bool RTLSDRInput::applySettings(const RTLSDRSettings& settings, bool force) } } + if ((m_settings.m_lowSampleRate != settings.m_lowSampleRate) || force) + { + m_settings.m_lowSampleRate = settings.m_lowSampleRate; + } + if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { m_settings.m_rfBandwidth = settings.m_rfBandwidth; @@ -608,6 +613,7 @@ int RTLSDRInput::webapiSettingsPutPatch( void RTLSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const RTLSDRSettings& settings) { + qDebug("RTLSDRInput::webapiFormatDeviceSettings: m_lowSampleRate: %s", settings.m_lowSampleRate ? "true" : "false"); response.getRtlSdrSettings()->setAgc(settings.m_agc ? 1 : 0); response.getRtlSdrSettings()->setCenterFrequency(settings.m_centerFrequency); response.getRtlSdrSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); From eef1ce9a6487b4f25f11b8087a2aed3aa72304a4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 2 Aug 2018 23:05:53 +0200 Subject: [PATCH 572/956] Web API: implemented instanceDVSerialGet --- sdrbase/resources/webapi/doc/html2/index.html | 342 +++++++++++++++++- .../resources/webapi/doc/swagger/swagger.yaml | 16 +- sdrbase/webapi/webapiadapterinterface.h | 21 +- sdrbase/webapi/webapirequestmapper.cpp | 15 +- sdrgui/webapi/webapiadaptergui.cpp | 25 +- sdrgui/webapi/webapiadaptergui.h | 4 + sdrsrv/webapi/webapiadaptersrv.cpp | 24 ++ sdrsrv/webapi/webapiadaptersrv.h | 4 + swagger/sdrangel/README.md | 7 +- swagger/sdrangel/api/swagger/swagger.yaml | 16 +- swagger/sdrangel/code/html2/index.html | 342 +++++++++++++++++- .../code/qt5/client/SWGAMDemodReport.cpp | 2 +- .../code/qt5/client/SWGAMDemodReport.h | 2 +- .../code/qt5/client/SWGAMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGAMDemodSettings.h | 2 +- .../code/qt5/client/SWGAMModReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGAMModReport.h | 2 +- .../code/qt5/client/SWGAMModSettings.cpp | 2 +- .../code/qt5/client/SWGAMModSettings.h | 2 +- .../code/qt5/client/SWGATVModReport.cpp | 2 +- .../code/qt5/client/SWGATVModReport.h | 2 +- .../code/qt5/client/SWGATVModSettings.cpp | 2 +- .../code/qt5/client/SWGATVModSettings.h | 2 +- .../code/qt5/client/SWGAirspyHFReport.cpp | 2 +- .../code/qt5/client/SWGAirspyHFReport.h | 2 +- .../code/qt5/client/SWGAirspyHFSettings.cpp | 2 +- .../code/qt5/client/SWGAirspyHFSettings.h | 2 +- .../code/qt5/client/SWGAirspyReport.cpp | 2 +- .../code/qt5/client/SWGAirspyReport.h | 2 +- .../code/qt5/client/SWGAirspySettings.cpp | 2 +- .../code/qt5/client/SWGAirspySettings.h | 2 +- .../code/qt5/client/SWGAudioDevices.cpp | 2 +- .../code/qt5/client/SWGAudioDevices.h | 2 +- .../code/qt5/client/SWGAudioInputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioInputDevice.h | 2 +- .../code/qt5/client/SWGAudioOutputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioOutputDevice.h | 2 +- .../code/qt5/client/SWGBFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGBFMDemodReport.h | 2 +- .../code/qt5/client/SWGBFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGBFMDemodSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.cpp | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.h | 2 +- .../qt5/client/SWGBladeRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGBladeRFInputSettings.h | 2 +- .../qt5/client/SWGBladeRFOutputSettings.cpp | 2 +- .../qt5/client/SWGBladeRFOutputSettings.h | 2 +- .../code/qt5/client/SWGCWKeyerSettings.cpp | 2 +- .../code/qt5/client/SWGCWKeyerSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGChannel.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGChannel.h | 2 +- .../code/qt5/client/SWGChannelListItem.cpp | 2 +- .../code/qt5/client/SWGChannelListItem.h | 2 +- .../code/qt5/client/SWGChannelReport.cpp | 2 +- .../code/qt5/client/SWGChannelReport.h | 2 +- .../code/qt5/client/SWGChannelSettings.cpp | 2 +- .../code/qt5/client/SWGChannelSettings.h | 2 +- .../code/qt5/client/SWGChannelsDetail.cpp | 2 +- .../code/qt5/client/SWGChannelsDetail.h | 2 +- .../code/qt5/client/SWGDSDDemodReport.cpp | 2 +- .../code/qt5/client/SWGDSDDemodReport.h | 2 +- .../code/qt5/client/SWGDSDDemodSettings.cpp | 2 +- .../code/qt5/client/SWGDSDDemodSettings.h | 2 +- .../code/qt5/client/SWGDVSeralDevices.cpp | 2 +- .../code/qt5/client/SWGDVSeralDevices.h | 2 +- .../code/qt5/client/SWGDVSerialDevice.cpp | 2 +- .../code/qt5/client/SWGDVSerialDevice.h | 2 +- .../code/qt5/client/SWGDeviceListItem.cpp | 2 +- .../code/qt5/client/SWGDeviceListItem.h | 2 +- .../code/qt5/client/SWGDeviceReport.cpp | 2 +- .../code/qt5/client/SWGDeviceReport.h | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.h | 2 +- .../code/qt5/client/SWGDeviceSetApi.cpp | 2 +- .../code/qt5/client/SWGDeviceSetApi.h | 2 +- .../code/qt5/client/SWGDeviceSetList.cpp | 2 +- .../code/qt5/client/SWGDeviceSetList.h | 2 +- .../code/qt5/client/SWGDeviceSettings.cpp | 2 +- .../code/qt5/client/SWGDeviceSettings.h | 2 +- .../code/qt5/client/SWGDeviceState.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceState.h | 2 +- .../code/qt5/client/SWGErrorResponse.cpp | 2 +- .../code/qt5/client/SWGErrorResponse.h | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.h | 2 +- .../code/qt5/client/SWGFCDProSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProSettings.h | 2 +- .../code/qt5/client/SWGFileSourceReport.cpp | 2 +- .../code/qt5/client/SWGFileSourceReport.h | 2 +- .../code/qt5/client/SWGFileSourceSettings.cpp | 2 +- .../code/qt5/client/SWGFileSourceSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.cpp | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.h | 2 +- .../code/qt5/client/SWGFrequencyBand.cpp | 2 +- .../code/qt5/client/SWGFrequencyBand.h | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.h | 2 +- .../qt5/client/SWGHackRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFInputSettings.h | 2 +- .../qt5/client/SWGHackRFOutputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFOutputSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGHelpers.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGHelpers.h | 2 +- .../code/qt5/client/SWGHttpRequest.cpp | 2 +- .../sdrangel/code/qt5/client/SWGHttpRequest.h | 2 +- .../code/qt5/client/SWGInstanceApi.cpp | 54 ++- .../sdrangel/code/qt5/client/SWGInstanceApi.h | 7 +- .../client/SWGInstanceChannelsResponse.cpp | 2 +- .../qt5/client/SWGInstanceChannelsResponse.h | 2 +- .../qt5/client/SWGInstanceDevicesResponse.cpp | 2 +- .../qt5/client/SWGInstanceDevicesResponse.h | 2 +- .../qt5/client/SWGInstanceSummaryResponse.cpp | 2 +- .../qt5/client/SWGInstanceSummaryResponse.h | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.h | 2 +- .../qt5/client/SWGLimeSdrInputSettings.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputSettings.h | 2 +- .../qt5/client/SWGLimeSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrOutputReport.h | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.h | 2 +- .../qt5/client/SWGLocationInformation.cpp | 2 +- .../code/qt5/client/SWGLocationInformation.h | 2 +- .../code/qt5/client/SWGLoggingInfo.cpp | 2 +- .../sdrangel/code/qt5/client/SWGLoggingInfo.h | 2 +- .../code/qt5/client/SWGModelFactory.h | 2 +- .../code/qt5/client/SWGNFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGNFMDemodReport.h | 2 +- .../code/qt5/client/SWGNFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGNFMDemodSettings.h | 2 +- .../code/qt5/client/SWGNFMModReport.cpp | 2 +- .../code/qt5/client/SWGNFMModReport.h | 2 +- .../code/qt5/client/SWGNFMModSettings.cpp | 2 +- .../code/qt5/client/SWGNFMModSettings.h | 2 +- swagger/sdrangel/code/qt5/client/SWGObject.h | 2 +- .../code/qt5/client/SWGPerseusReport.cpp | 2 +- .../code/qt5/client/SWGPerseusReport.h | 2 +- .../code/qt5/client/SWGPerseusSettings.cpp | 2 +- .../code/qt5/client/SWGPerseusSettings.h | 2 +- .../qt5/client/SWGPlutoSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrInputReport.h | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.h | 2 +- .../qt5/client/SWGPlutoSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrOutputReport.h | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.h | 2 +- .../code/qt5/client/SWGPresetExport.cpp | 2 +- .../code/qt5/client/SWGPresetExport.h | 2 +- .../code/qt5/client/SWGPresetGroup.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetGroup.h | 2 +- .../code/qt5/client/SWGPresetIdentifier.cpp | 2 +- .../code/qt5/client/SWGPresetIdentifier.h | 2 +- .../code/qt5/client/SWGPresetImport.cpp | 2 +- .../code/qt5/client/SWGPresetImport.h | 2 +- .../code/qt5/client/SWGPresetItem.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetItem.h | 2 +- .../code/qt5/client/SWGPresetTransfer.cpp | 2 +- .../code/qt5/client/SWGPresetTransfer.h | 2 +- .../sdrangel/code/qt5/client/SWGPresets.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGPresets.h | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.h | 2 +- .../client/SWGRDSReport_altFrequencies.cpp | 2 +- .../qt5/client/SWGRDSReport_altFrequencies.h | 2 +- .../code/qt5/client/SWGRtlSdrReport.cpp | 2 +- .../code/qt5/client/SWGRtlSdrReport.h | 2 +- .../code/qt5/client/SWGRtlSdrSettings.cpp | 2 +- .../code/qt5/client/SWGRtlSdrSettings.h | 2 +- .../code/qt5/client/SWGSDRPlayReport.cpp | 2 +- .../code/qt5/client/SWGSDRPlayReport.h | 2 +- .../code/qt5/client/SWGSDRPlaySettings.cpp | 2 +- .../code/qt5/client/SWGSDRPlaySettings.h | 2 +- .../qt5/client/SWGSDRdaemonSinkReport.cpp | 2 +- .../code/qt5/client/SWGSDRdaemonSinkReport.h | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.h | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.h | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.h | 2 +- .../code/qt5/client/SWGSSBDemodReport.cpp | 2 +- .../code/qt5/client/SWGSSBDemodReport.h | 2 +- .../code/qt5/client/SWGSSBDemodSettings.cpp | 2 +- .../code/qt5/client/SWGSSBDemodSettings.h | 2 +- .../code/qt5/client/SWGSSBModReport.cpp | 2 +- .../code/qt5/client/SWGSSBModReport.h | 2 +- .../code/qt5/client/SWGSSBModSettings.cpp | 2 +- .../code/qt5/client/SWGSSBModSettings.h | 2 +- .../code/qt5/client/SWGSampleRate.cpp | 2 +- .../sdrangel/code/qt5/client/SWGSampleRate.h | 2 +- .../code/qt5/client/SWGSamplingDevice.cpp | 2 +- .../code/qt5/client/SWGSamplingDevice.h | 2 +- .../code/qt5/client/SWGSuccessResponse.cpp | 2 +- .../code/qt5/client/SWGSuccessResponse.h | 2 +- .../code/qt5/client/SWGTestSourceSettings.cpp | 2 +- .../code/qt5/client/SWGTestSourceSettings.h | 2 +- .../code/qt5/client/SWGUDPSinkReport.cpp | 2 +- .../code/qt5/client/SWGUDPSinkReport.h | 2 +- .../code/qt5/client/SWGUDPSinkSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSinkSettings.h | 2 +- .../code/qt5/client/SWGUDPSrcReport.cpp | 2 +- .../code/qt5/client/SWGUDPSrcReport.h | 2 +- .../code/qt5/client/SWGUDPSrcSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSrcSettings.h | 2 +- .../code/qt5/client/SWGWFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGWFMDemodReport.h | 2 +- .../code/qt5/client/SWGWFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGWFMDemodSettings.h | 2 +- .../code/qt5/client/SWGWFMModReport.cpp | 2 +- .../code/qt5/client/SWGWFMModReport.h | 2 +- .../code/qt5/client/SWGWFMModSettings.cpp | 2 +- .../code/qt5/client/SWGWFMModSettings.h | 2 +- .../sdrangel/examples/devicesets_config.py | 4 +- 214 files changed, 1063 insertions(+), 218 deletions(-) diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index b4a625f5d..edaf05042 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -4015,6 +4015,9 @@ margin-bottom: 20px;
  • instanceChannels
  • +
  • + instanceDVSerialGet +
  • instanceDVSerialPatch
  • @@ -4076,7 +4079,7 @@ margin-bottom: 20px; diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp index 51b88b5eb..5f81d57a6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp @@ -46,6 +46,10 @@ SWGAMDemodSettings::SWGAMDemodSettings() { m_title_isSet = false; audio_device_name = nullptr; m_audio_device_name_isSet = false; + pll = 0; + m_pll_isSet = false; + sync_am_operation = 0; + m_sync_am_operation_isSet = false; } SWGAMDemodSettings::~SWGAMDemodSettings() { @@ -72,6 +76,10 @@ SWGAMDemodSettings::init() { m_title_isSet = false; audio_device_name = new QString(""); m_audio_device_name_isSet = false; + pll = 0; + m_pll_isSet = false; + sync_am_operation = 0; + m_sync_am_operation_isSet = false; } void @@ -89,6 +97,8 @@ SWGAMDemodSettings::cleanup() { if(audio_device_name != nullptr) { delete audio_device_name; } + + } SWGAMDemodSettings* @@ -120,6 +130,10 @@ SWGAMDemodSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + ::SWGSDRangel::setValue(&pll, pJson["pll"], "qint32", ""); + + ::SWGSDRangel::setValue(&sync_am_operation, pJson["syncAMOperation"], "qint32", ""); + } QString @@ -163,6 +177,12 @@ SWGAMDemodSettings::asJsonObject() { if(audio_device_name != nullptr && *audio_device_name != QString("")){ toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); } + if(m_pll_isSet){ + obj->insert("pll", QJsonValue(pll)); + } + if(m_sync_am_operation_isSet){ + obj->insert("syncAMOperation", QJsonValue(sync_am_operation)); + } return obj; } @@ -257,6 +277,26 @@ SWGAMDemodSettings::setAudioDeviceName(QString* audio_device_name) { this->m_audio_device_name_isSet = true; } +qint32 +SWGAMDemodSettings::getPll() { + return pll; +} +void +SWGAMDemodSettings::setPll(qint32 pll) { + this->pll = pll; + this->m_pll_isSet = true; +} + +qint32 +SWGAMDemodSettings::getSyncAmOperation() { + return sync_am_operation; +} +void +SWGAMDemodSettings::setSyncAmOperation(qint32 sync_am_operation) { + this->sync_am_operation = sync_am_operation; + this->m_sync_am_operation_isSet = true; +} + bool SWGAMDemodSettings::isSet(){ @@ -271,6 +311,8 @@ SWGAMDemodSettings::isSet(){ if(m_rgb_color_isSet){ isObjectUpdated = true; break;} if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} + if(m_pll_isSet){ isObjectUpdated = true; break;} + if(m_sync_am_operation_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h index 93b5f77ea..401be5ee9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h @@ -69,6 +69,12 @@ public: QString* getAudioDeviceName(); void setAudioDeviceName(QString* audio_device_name); + qint32 getPll(); + void setPll(qint32 pll); + + qint32 getSyncAmOperation(); + void setSyncAmOperation(qint32 sync_am_operation); + virtual bool isSet() override; @@ -100,6 +106,12 @@ private: QString* audio_device_name; bool m_audio_device_name_isSet; + qint32 pll; + bool m_pll_isSet; + + qint32 sync_am_operation; + bool m_sync_am_operation_isSet; + }; } From e783bcbbcbe9b832aabe122571d6be1e06392ab9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 2 Aug 2018 23:28:10 +0200 Subject: [PATCH 574/956] AM demod: bumped version --- plugins/channelrx/demodam/amdemodplugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index 3760131c7..76a6b95bb 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("3.14.7"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From f368c62329933f5ef7bc9cd153b7643d65f39cab Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 5 Aug 2018 12:44:06 +0200 Subject: [PATCH 575/956] Fixed power display going to floor value in some demods --- debian/changelog | 5 +-- plugins/channelrx/demodam/amdemod.h | 33 +++++++++++++++---- plugins/channelrx/demodbfm/bfmdemod.h | 24 ++++++++++++-- plugins/channelrx/demodbfm/bfmplugin.cpp | 2 +- plugins/channelrx/demoddsd/dsddemod.h | 24 ++++++++++++-- plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- plugins/channelrx/demodnfm/nfmdemod.h | 24 ++++++++++++-- plugins/channelrx/demodnfm/nfmplugin.cpp | 2 +- plugins/channelrx/demodssb/ssbdemod.h | 24 ++++++++++++-- plugins/channelrx/demodssb/ssbplugin.cpp | 2 +- plugins/channelrx/demodwfm/wfmdemod.h | 24 ++++++++++++-- plugins/channelrx/demodwfm/wfmplugin.cpp | 2 +- 12 files changed, 139 insertions(+), 29 deletions(-) diff --git a/debian/changelog b/debian/changelog index b947fcf68..ce6c5765b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,10 @@ sdrangel (4.0.6-1) unstable; urgency=medium * Web API: RTL-SDR: fixed RF bandwidth setting - * Web API: enhnaced DV serial and AM demod interfaces + * Web API: enhanced DV serial and AM demod interfaces + * Fixed power display going to floor value in some demods - -- Edouard Griffiths, F4EXB Sun, 05 Aug 2018 09:14:18 +0200 + -- Edouard Griffiths, F4EXB Sun, 05 Aug 2018 19:14:18 +0200 sdrangel (4.0.5-1) unstable; urgency=medium diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index d8b292417..f08ed6451 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -125,20 +125,38 @@ public: bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } Real getPllFrequency() const { return m_pll.getFreq(); } - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - m_magsqSum = 0.0f; + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; - } + } static const QString m_channelIdURI; static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + enum RateState { RSInitialFill, RSRunning @@ -167,6 +185,7 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; MovingAverageUtil m_movingAverage; SimpleAGC<4800> m_volumeAGC; diff --git a/plugins/channelrx/demodbfm/bfmdemod.h b/plugins/channelrx/demodbfm/bfmdemod.h index 99c76d07f..700deb6cf 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.h +++ b/plugins/channelrx/demodbfm/bfmdemod.h @@ -146,10 +146,17 @@ public: void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; @@ -184,6 +191,16 @@ public: static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + enum RateState { RSInitialFill, RSRunning @@ -224,6 +241,7 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; AudioVector m_audioBuffer; uint m_audioBufferFill; diff --git a/plugins/channelrx/demodbfm/bfmplugin.cpp b/plugins/channelrx/demodbfm/bfmplugin.cpp index bc4903103..0d813d9ef 100644 --- a/plugins/channelrx/demodbfm/bfmplugin.cpp +++ b/plugins/channelrx/demodbfm/bfmplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor BFMPlugin::m_pluginDescriptor = { QString("Broadcast FM Demodulator"), - QString("4.0.2"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index 9ffeef680..f5d906723 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -117,10 +117,17 @@ public: void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; @@ -146,6 +153,16 @@ public: static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + typedef enum { signalFormatNone, @@ -208,6 +225,7 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; SampleVector m_scopeSampleBuffer; AudioVector m_audioBuffer; diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index 61414fd45..32c365ae0 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("4.0.1"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index be42291c2..ae1ec8c9b 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -154,10 +154,17 @@ public: void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; @@ -167,6 +174,16 @@ public: static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + enum RateState { RSInitialFill, RSRunning @@ -203,6 +220,7 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; MovingAverageUtil m_movingAverage; AFSquelch m_afSquelch; diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index 928dcc072..e23e7fa6b 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("3.14.6"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodssb/ssbdemod.h b/plugins/channelrx/demodssb/ssbdemod.h index 4dabab796..27521aa01 100644 --- a/plugins/channelrx/demodssb/ssbdemod.h +++ b/plugins/channelrx/demodssb/ssbdemod.h @@ -126,10 +126,17 @@ public: void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; @@ -153,6 +160,16 @@ public: static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + class MsgConfigureSSBDemodPrivate : public Message { MESSAGE_CLASS_DECLARATION @@ -268,6 +285,7 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; MagAGC m_agc; bool m_agcActive; bool m_agcClamping; diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp index f964c394b..04281096d 100644 --- a/plugins/channelrx/demodssb/ssbplugin.cpp +++ b/plugins/channelrx/demodssb/ssbplugin.cpp @@ -10,7 +10,7 @@ const PluginDescriptor SSBPlugin::m_pluginDescriptor = { QString("SSB Demodulator"), - QString("4.0.2"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodwfm/wfmdemod.h b/plugins/channelrx/demodwfm/wfmdemod.h index 0e594eb33..fa707a600 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.h +++ b/plugins/channelrx/demodwfm/wfmdemod.h @@ -109,10 +109,17 @@ public: void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; @@ -145,6 +152,16 @@ public: static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + enum RateState { RSInitialFill, RSRunning @@ -172,6 +189,7 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; MovingAverageUtil m_movingAverage; Real m_fmExcursion; diff --git a/plugins/channelrx/demodwfm/wfmplugin.cpp b/plugins/channelrx/demodwfm/wfmplugin.cpp index fa0c2ad8f..8d2836832 100644 --- a/plugins/channelrx/demodwfm/wfmplugin.cpp +++ b/plugins/channelrx/demodwfm/wfmplugin.cpp @@ -10,7 +10,7 @@ const PluginDescriptor WFMPlugin::m_pluginDescriptor = { QString("WFM Demodulator"), - QString("4.0.0"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 14ec3c01d8e54efaba8dcd2a57355e5752184f9e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 5 Aug 2018 12:48:02 +0200 Subject: [PATCH 576/956] SSB modulator: set samples to zero when no modulation. Fixes issue #204 --- plugins/channeltx/modssb/ssbmod.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index b31332d19..4b44a4a20 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -370,6 +370,8 @@ void SSBMod::pullAF(Complex& sample) break; case SSBModSettings::SSBModInputNone: default: + sample.real(0.0f); + sample.imag(0.0f); break; } From 29677cba064571bc0697e045fdc7041db2d323b5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 6 Aug 2018 13:13:31 +0000 Subject: [PATCH 577/956] Web API: fixed TestSource device setting --- sdrbase/webapi/webapirequestmapper.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 3fdcecdab..90fa2e55d 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1984,6 +1984,21 @@ bool WebAPIRequestMapper::validateDeviceSettings( return false; } } + else if (*deviceHwType == "TestSource") + { + if (jsonObject.contains("testSourceSettings") && jsonObject["testSourceSettings"].isObject()) + { + QJsonObject testSourceSettingsJsonObject = jsonObject["testSourceSettings"].toObject(); + deviceSettingsKeys = testSourceSettingsJsonObject.keys(); + deviceSettings.setTestSourceSettings(new SWGSDRangel::SWGTestSourceSettings()); + deviceSettings.getTestSourceSettings()->fromJsonObject(testSourceSettingsJsonObject); + return true; + } + else + { + return false; + } + } else { return false; From b174e6b7ad305de2f7c0a20a505f6fc1ca2616cf Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 6 Aug 2018 22:46:48 +0200 Subject: [PATCH 578/956] Test Source: fixed glitches --- plugins/samplesource/testsource/testsourcegui.cpp | 6 ++++++ plugins/samplesource/testsource/testsourceinput.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/samplesource/testsource/testsourcegui.cpp b/plugins/samplesource/testsource/testsourcegui.cpp index abd4f8175..e91b49dec 100644 --- a/plugins/samplesource/testsource/testsourcegui.cpp +++ b/plugins/samplesource/testsource/testsourcegui.cpp @@ -379,11 +379,17 @@ void TestSourceGui::displaySettings() ui->amplitudeFine->setValue(amplitudeBits%100); displayAmplitude(); int dcBiasPercent = roundf(m_settings.m_dcFactor * 100.0f); + ui->dcBias->setValue((int) dcBiasPercent); ui->dcBiasText->setText(QString(tr("%1 %").arg(dcBiasPercent))); int iBiasPercent = roundf(m_settings.m_iFactor * 100.0f); + ui->iBias->setValue((int) iBiasPercent); ui->iBiasText->setText(QString(tr("%1 %").arg(iBiasPercent))); int qBiasPercent = roundf(m_settings.m_qFactor * 100.0f); + ui->qBias->setValue((int) qBiasPercent); ui->qBiasText->setText(QString(tr("%1 %").arg(qBiasPercent))); + int phaseImbalancePercent = roundf(m_settings.m_phaseImbalance * 100.0f); + ui->phaseImbalance->setValue((int) phaseImbalancePercent); + ui->phaseImbalanceText->setText(QString(tr("%1 %").arg(phaseImbalancePercent))); ui->autoCorr->setCurrentIndex(m_settings.m_autoCorrOptions); ui->sampleSize->blockSignals(false); ui->modulation->setCurrentIndex((int) m_settings.m_modulation); diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 74cbe0163..eda87732f 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -133,7 +133,7 @@ const QString& TestSourceInput::getDeviceDescription() const int TestSourceInput::getSampleRate() const { - return m_settings.m_sampleRate; + return m_settings.m_sampleRate/(1< Date: Tue, 7 Aug 2018 08:46:07 +0200 Subject: [PATCH 579/956] Removed obsolete elements in windows install script --- windows.install.bat | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/windows.install.bat b/windows.install.bat index 2e3a43a0f..481820ebc 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -21,7 +21,7 @@ copy librtlsdr\%1\librtlsdr.dll %2 copy libairspy\%1\libairspy.dll %2 copy libairspyhf\%1\libairspyhf.dll %2 copy libbladerf\%1\libbladerf.dll %2 -copy libsqlite3\%1\libsqlite3.dll %2 +REM copy libsqlite3\%1\libsqlite3.dll %2 copy liblimesuite\%1\liblimesuite.dll %2 copy libiio\%1\libiio.dll %2 copy %libusbdir%\dll\libusb-1.0.dll %2 @@ -38,7 +38,7 @@ mkdir %2\plugins\channeltx mkdir %2\plugins\samplesource mkdir %2\plugins\samplesink copy plugins\channelrx\chanalyzer\%1\chanalyzer.dll %2\plugins\channelrx -copy plugins\channelrx\chanalyzerng\%1\chanalyzerng.dll %2\plugins\channelrx +REM copy plugins\channelrx\chanalyzerng\%1\chanalyzerng.dll %2\plugins\channelrx copy plugins\channelrx\demodam\%1\demodam.dll %2\plugins\channelrx copy plugins\channelrx\demodatv\%1\demodatv.dll %2\plugins\channelrx copy plugins\channelrx\demodbfm\%1\demodbfm.dll %2\plugins\channelrx @@ -47,7 +47,7 @@ copy plugins\channelrx\demodlora\%1\demodlora.dll %2\plugins\channelrx copy plugins\channelrx\demodnfm\%1\demodnfm.dll %2\plugins\channelrx copy plugins\channelrx\demodssb\%1\demodssb.dll %2\plugins\channelrx copy plugins\channelrx\demodwfm\%1\demodwfm.dll %2\plugins\channelrx -copy plugins\channelrx\tcpsrc\%1\tcpsrc.dll %2\plugins\channelrx +REM copy plugins\channelrx\tcpsrc\%1\tcpsrc.dll %2\plugins\channelrx REM copy plugins\channelrx\udpsrc\%1\udpsrc.dll %2\plugins\channelrx copy plugins\channeltx\modam\%1\modam.dll %2\plugins\channeltx REM copy plugins\channeltx\modatv\%1\modatv.dll %2\plugins\channeltx From 710502b3b2fe562af70f34a56059946c3a0fc274 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 7 Aug 2018 19:28:50 +0200 Subject: [PATCH 580/956] WFM demod: corrected audio mute display --- plugins/channelrx/demodwfm/wfmdemodgui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.cpp b/plugins/channelrx/demodwfm/wfmdemodgui.cpp index 883884621..77ec241dd 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.cpp +++ b/plugins/channelrx/demodwfm/wfmdemodgui.cpp @@ -290,6 +290,8 @@ void WFMDemodGUI::displaySettings() ui->squelch->setValue(m_settings.m_squelch); ui->squelchText->setText(QString("%1 dB").arg(m_settings.m_squelch)); + ui->audioMute->setChecked(m_settings.m_audioMute); + blockApplySettings(false); } From a971e6da10cdf19cc85700b556e2092558d525ba Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 8 Aug 2018 09:17:25 +0200 Subject: [PATCH 581/956] Web API: fixed bug in PUT/PATCH of modulators not setting differentially --- debian/changelog | 4 +++- plugins/channeltx/modam/ammod.cpp | 2 +- plugins/channeltx/modam/ammodplugin.cpp | 2 +- plugins/channeltx/modatv/atvmod.cpp | 2 +- plugins/channeltx/modatv/atvmodplugin.cpp | 2 +- plugins/channeltx/modnfm/nfmmod.cpp | 2 +- plugins/channeltx/modnfm/nfmmodplugin.cpp | 2 +- plugins/channeltx/modssb/ssbmod.cpp | 2 +- plugins/channeltx/modssb/ssbmodplugin.cpp | 2 +- plugins/channeltx/modwfm/wfmmod.cpp | 2 +- plugins/channeltx/modwfm/wfmmodplugin.cpp | 2 +- plugins/channeltx/udpsink/udpsink.cpp | 2 +- plugins/channeltx/udpsink/udpsinkplugin.cpp | 2 +- 13 files changed, 15 insertions(+), 13 deletions(-) diff --git a/debian/changelog b/debian/changelog index ce6c5765b..cf78aa0a4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,9 +2,11 @@ sdrangel (4.0.6-1) unstable; urgency=medium * Web API: RTL-SDR: fixed RF bandwidth setting * Web API: enhanced DV serial and AM demod interfaces + * Web API: fixed bug in PUT/PATCH of modulators not setting differentially * Fixed power display going to floor value in some demods + * SSB modulator: fixed sample not reset when no modulation is present - -- Edouard Griffiths, F4EXB Sun, 05 Aug 2018 19:14:18 +0200 + -- Edouard Griffiths, F4EXB Tue, 07 Aug 2018 19:14:18 +0200 sdrangel (4.0.5-1) unstable; urgency=medium diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index 4992632e2..8fed8d862 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -527,7 +527,7 @@ int AMMod::webapiSettingsPutPatch( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - AMModSettings settings; + AMModSettings settings = m_settings; bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("channelMute")) { diff --git a/plugins/channeltx/modam/ammodplugin.cpp b/plugins/channeltx/modam/ammodplugin.cpp index a6a629378..cd356c0ab 100644 --- a/plugins/channeltx/modam/ammodplugin.cpp +++ b/plugins/channeltx/modam/ammodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor AMModPlugin::m_pluginDescriptor = { QString("AM Modulator"), - QString("3.14.5"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index 285802bb2..6c98853dd 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -1199,7 +1199,7 @@ int ATVMod::webapiSettingsPutPatch( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - ATVModSettings settings; + ATVModSettings settings = m_settings; bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("inputFrequencyOffset")) diff --git a/plugins/channeltx/modatv/atvmodplugin.cpp b/plugins/channeltx/modatv/atvmodplugin.cpp index 8a99fe362..b5bbab39f 100644 --- a/plugins/channeltx/modatv/atvmodplugin.cpp +++ b/plugins/channeltx/modatv/atvmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor ATVModPlugin::m_pluginDescriptor = { QString("ATV Modulator"), - QString("3.14.5"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp index 545ac693b..6dc2c7b1e 100644 --- a/plugins/channeltx/modnfm/nfmmod.cpp +++ b/plugins/channeltx/modnfm/nfmmod.cpp @@ -567,7 +567,7 @@ int NFMMod::webapiSettingsPutPatch( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - NFMModSettings settings; + NFMModSettings settings = m_settings; bool frequencyOffsetChanged = false; // for (int i = 0; i < channelSettingsKeys.size(); i++) { diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp index 24694c679..7498fb2c6 100644 --- a/plugins/channeltx/modnfm/nfmmodplugin.cpp +++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor NFMModPlugin::m_pluginDescriptor = { QString("NFM Modulator"), - QString("3.14.5"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index 4b44a4a20..501575ac7 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -881,7 +881,7 @@ int SSBMod::webapiSettingsPutPatch( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - SSBModSettings settings; + SSBModSettings settings = m_settings; bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("inputFrequencyOffset")) diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp index f4a5fd5cb..228ce21cd 100644 --- a/plugins/channeltx/modssb/ssbmodplugin.cpp +++ b/plugins/channeltx/modssb/ssbmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor SSBModPlugin::m_pluginDescriptor = { QString("SSB Modulator"), - QString("3.14.5"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index 6aa022f28..5f7660f5c 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -559,7 +559,7 @@ int WFMMod::webapiSettingsPutPatch( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - WFMModSettings settings; + WFMModSettings settings = m_settings; bool channelizerChange = false; if (channelSettingsKeys.contains("channelMute")) { diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp index 9c723b505..7a818e78e 100644 --- a/plugins/channeltx/modwfm/wfmmodplugin.cpp +++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor WFMModPlugin::m_pluginDescriptor = { QString("WFM Modulator"), - QString("3.14.5"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index 9e18c5f22..d076f1a19 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -611,7 +611,7 @@ int UDPSink::webapiSettingsPutPatch( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - UDPSinkSettings settings; + UDPSinkSettings settings = m_settings; bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("sampleFormat")) { diff --git a/plugins/channeltx/udpsink/udpsinkplugin.cpp b/plugins/channeltx/udpsink/udpsinkplugin.cpp index fa9453341..0f144ef1a 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.cpp +++ b/plugins/channeltx/udpsink/udpsinkplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { QString("UDP Channel Sink"), - QString("4.0.2"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From a0a23d3d737f43d9994be19c09a9c7003feab0ef Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 11 Aug 2018 17:36:02 +0200 Subject: [PATCH 582/956] Main Window: fixed some debug messages --- sdrgui/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index 0580daa2c..3884bf2d2 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -1479,7 +1479,7 @@ void MainWindow::sampleSourceChanged() if (currentSourceTabIndex >= 0) { - qDebug("MainWindow::on_sampleSource_confirmClicked: tab at %d", currentSourceTabIndex); + qDebug("MainWindow::sampleSourceChanged: tab at %d", currentSourceTabIndex); DeviceUISet *deviceUI = m_deviceUIs[currentSourceTabIndex]; deviceUI->m_deviceSourceAPI->saveSourceSettings(m_settings.getWorkingPreset()); // save old API settings deviceUI->m_deviceSourceAPI->stopAcquisition(); @@ -1568,7 +1568,7 @@ void MainWindow::sampleSinkChanged() if (currentSinkTabIndex >= 0) { - qDebug("MainWindow::on_sampleSink_confirmClicked: tab at %d", currentSinkTabIndex); + qDebug("MainWindow::sampleSinkChanged: tab at %d", currentSinkTabIndex); DeviceUISet *deviceUI = m_deviceUIs[currentSinkTabIndex]; deviceUI->m_deviceSinkAPI->saveSinkSettings(m_settings.getWorkingPreset()); // save old API settings deviceUI->m_deviceSinkAPI->stopGeneration(); From b1b6b7468186ceb428b593a75fd59125ff51a79e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 11 Aug 2018 23:29:38 +0200 Subject: [PATCH 583/956] Web API: HTTP server: reduce debug message and put more interesting information --- httpserver/httpconnectionhandler.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/httpserver/httpconnectionhandler.cpp b/httpserver/httpconnectionhandler.cpp index fc92311a2..d6046c260 100644 --- a/httpserver/httpconnectionhandler.cpp +++ b/httpserver/httpconnectionhandler.cpp @@ -97,7 +97,9 @@ void HttpConnectionHandler::createSocket() void HttpConnectionHandler::run() { +#ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): thread started", this); +#endif try { exec(); @@ -109,13 +111,17 @@ void HttpConnectionHandler::run() socket->close(); delete socket; readTimer.stop(); +#ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): thread stopped", this); +#endif } void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) { +#ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): handle new connection", this); +#endif busy = true; Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy @@ -228,7 +234,11 @@ void HttpConnectionHandler::read() if (currentRequest->getStatus()==HttpRequest::complete) { readTimer.stop(); - qDebug("HttpConnectionHandler (%p): received request",this); + qDebug("HttpConnectionHandler (%p) received request from %s (%s) %s", + this, + qPrintable(currentRequest->getPeerAddress().toString()), + currentRequest->getMethod().toStdString().c_str(), + currentRequest->getPath().toStdString().c_str()); // Copy the Connection:close header to the response HttpResponse response(socket); @@ -266,7 +276,9 @@ void HttpConnectionHandler::read() response.write(QByteArray(),true); } +#ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): finished request",this); +#endif // Find out whether the connection must be closed if (!closeConnection) From c9c19f412c03707b813603002dfb9b4bf6cf609f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 15:49:14 +0200 Subject: [PATCH 584/956] Cleanup of old scope objects --- httpserver/httpconnectionhandler.cpp | 2 +- .../channelrx/chanalyzer/chanalyzergui.cpp | 1 - plugins/channelrx/demoddsd/dsddemodgui.cpp | 2 - sdrgui/CMakeLists.txt | 9 - sdrgui/dsp/scopevis.cpp | 459 ---- sdrgui/dsp/scopevis.h | 171 -- sdrgui/dsp/spectrumscopecombovis.cpp | 41 - sdrgui/dsp/spectrumscopecombovis.h | 27 - sdrgui/gui/glscope.cpp | 2209 ----------------- sdrgui/gui/glscope.h | 193 -- sdrgui/gui/glscopegui.cpp | 919 ------- sdrgui/gui/glscopegui.h | 119 - sdrgui/gui/glscopegui.ui | 1287 ---------- 13 files changed, 1 insertion(+), 5438 deletions(-) delete mode 100644 sdrgui/dsp/scopevis.cpp delete mode 100644 sdrgui/dsp/scopevis.h delete mode 100644 sdrgui/dsp/spectrumscopecombovis.cpp delete mode 100644 sdrgui/dsp/spectrumscopecombovis.h delete mode 100644 sdrgui/gui/glscope.cpp delete mode 100644 sdrgui/gui/glscope.h delete mode 100644 sdrgui/gui/glscopegui.cpp delete mode 100644 sdrgui/gui/glscopegui.h delete mode 100644 sdrgui/gui/glscopegui.ui diff --git a/httpserver/httpconnectionhandler.cpp b/httpserver/httpconnectionhandler.cpp index d6046c260..4b3fc9a31 100644 --- a/httpserver/httpconnectionhandler.cpp +++ b/httpserver/httpconnectionhandler.cpp @@ -234,7 +234,7 @@ void HttpConnectionHandler::read() if (currentRequest->getStatus()==HttpRequest::complete) { readTimer.stop(); - qDebug("HttpConnectionHandler (%p) received request from %s (%s) %s", + qDebug("HttpConnectionHandler (%p): received request from %s (%s) %s", this, qPrintable(currentRequest->getPeerAddress().toString()), currentRequest->getMethod().toStdString().c_str(), diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index bcb9564da..966bb6898 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -24,7 +24,6 @@ #include "ui_chanalyzergui.h" #include "dsp/spectrumscopengcombovis.h" #include "dsp/spectrumvis.h" -#include "dsp/scopevis.h" #include "gui/glspectrum.h" #include "gui/glscopeng.h" #include "gui/basicchannelsettingsdialog.h" diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 3b2dc2653..8c37b095b 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -23,9 +23,7 @@ #include "dsp/threadedbasebandsamplesink.h" #include "ui_dsddemodgui.h" -#include "dsp/scopevis.h" #include "dsp/scopevisxy.h" -#include "gui/glscope.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 401f3b677..78223583c 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -20,8 +20,6 @@ set(sdrgui_SOURCES gui/editcommanddialog.cpp gui/externalclockbutton.cpp gui/externalclockdialog.cpp - gui/glscope.cpp - gui/glscopegui.cpp gui/glscopeng.cpp gui/glscopemulti.cpp gui/glscopenggui.cpp @@ -48,12 +46,10 @@ set(sdrgui_SOURCES gui/valuedial.cpp gui/valuedialz.cpp - dsp/scopevis.cpp dsp/scopevisng.cpp dsp/scopevismulti.cpp dsp/scopevisxy.cpp dsp/spectrumvis.cpp - dsp/spectrumscopecombovis.cpp dsp/spectrumscopengcombovis.cpp device/deviceuiset.cpp @@ -80,8 +76,6 @@ set(sdrgui_HEADERS gui/editcommanddialog.h gui/externalclockbutton.h gui/externalclockdialog.h - gui/glscope.h - gui/glscopegui.h gui/glscopeng.h gui/glscopemulti.h gui/glscopenggui.h @@ -109,12 +103,10 @@ set(sdrgui_HEADERS gui/valuedial.h gui/valuedialz.h - dsp/scopevis.h dsp/scopevisng.h dsp/scopevismulti.h dsp/scopevisxy.h dsp/spectrumvis.h - dsp/spectrumscopecombovis.h dsp/spectrumscopengcombovis.h device/deviceuiset.h @@ -136,7 +128,6 @@ set(sdrgui_FORMS gui/cwkeyergui.ui gui/editcommanddialog.ui gui/externalclockdialog.ui - gui/glscopegui.ui gui/glscopenggui.ui gui/glscopemultigui.ui gui/glspectrumgui.ui diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp deleted file mode 100644 index 0fd0426e5..000000000 --- a/sdrgui/dsp/scopevis.cpp +++ /dev/null @@ -1,459 +0,0 @@ -#include "dsp/scopevis.h" -#include "gui/glscope.h" -#include "dsp/dspcommands.h" -#include "util/messagequeue.h" -#include - -#include - -#ifndef LINUX -inline double log2f(double n) -{ - return log(n) / log(2.0); -} -#endif - -MESSAGE_CLASS_DEFINITION(ScopeVis::MsgConfigureScopeVis, Message) - -const uint ScopeVis::m_traceChunkSize = 4800; - -ScopeVis::ScopeVis(Real scalef, GLScope* glScope) : - m_glScope(glScope), - m_scalef(scalef), - m_tracebackCount(0), - m_fill(0), - m_triggerState(Untriggered), - m_triggerIndex(0), - m_prevTrigger(false), - m_triggerPre(0), - m_triggerOneShot(false), - m_armed(false), - m_triggerDelayCount(0), - m_triggerCount(0), - m_sampleRate(0), - m_prevArg(0.0), - m_firstArg(true) -{ - setObjectName("ScopeVis"); - m_trace.reserve(100*m_traceChunkSize); - m_trace.resize(20*m_traceChunkSize); - m_traceback.resize(20*m_traceChunkSize); - - for (unsigned int i = 0; i < m_nbTriggers; i++) - { - m_triggerChannel[i] = TriggerFreeRun; - m_triggerLevel[i] = 0.0; - m_triggerPositiveEdge[i] = true; - m_triggerBothEdges[i] = false; - m_triggerDelay[i] = 0; - m_triggerCounts[i] = 0; - } -} - -ScopeVis::~ScopeVis() -{ -} - -void ScopeVis::configure(MessageQueue* msgQueue, - uint triggerIndex, - TriggerChannel triggerChannel, - Real triggerLevel, - bool triggerPositiveEdge, - bool triggerBothEdges, - uint triggerPre, - uint triggerDelay, - uint triggerCounts, - uint traceSize) -{ - Message* cmd = MsgConfigureScopeVis::create(triggerIndex, - triggerChannel, - triggerLevel, - triggerPositiveEdge, - triggerBothEdges, - triggerPre, - triggerDelay, - triggerCounts, - traceSize); - msgQueue->push(cmd); -} - -void ScopeVis::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) -{ - SampleVector::const_iterator begin(cbegin); - - if (m_triggerChannel[m_triggerIndex] == TriggerFreeRun) { - m_triggerPoint = begin; - } - else if (m_triggerState == Triggered) { - m_triggerPoint = begin; - } - else if (m_triggerState == Untriggered) { - m_triggerPoint = end; - } - else if (m_triggerState == WaitForReset) { - m_triggerPoint = end; - } - else { - m_triggerPoint = begin; - } - - while(begin < end) - { - if (m_triggerChannel[m_triggerIndex] == TriggerFreeRun) - { - int count = end - begin; - - if(count > (int)(m_trace.size() - m_fill)) - { - count = m_trace.size() - m_fill; - } - - std::vector::iterator it = m_trace.begin() + m_fill; - - for(int i = 0; i < count; ++i) - { - *it++ = Complex(begin->real() / m_scalef, begin->imag() / m_scalef); - ++begin; - } - - m_fill += count; - - if(m_fill >= m_trace.size()) - { - m_glScope->newTrace(m_trace, m_sampleRate); - m_fill = 0; - } - } - else - { - if(m_triggerState == WaitForReset) - { - break; - } - if(m_triggerState == Config) - { - m_glScope->newTrace(m_trace, m_sampleRate); // send a dummy trace - m_triggerState = Untriggered; - m_triggerIndex = 0; - } - if(m_triggerState == Delay) - { - int count = end - begin; - if (count > (int)(m_trace.size() - m_fill)) - { - count = m_trace.size() - m_fill; - } - begin += count; - m_fill += count; - if(m_fill >= m_trace.size()) - { - m_fill = 0; - m_triggerDelayCount--; - if (m_triggerDelayCount == 0) - { - if (nextTrigger()) - { - m_triggerState = Untriggered; - } - else - { - m_triggerState = Triggered; - } - } - } - } - - if(m_triggerState == Untriggered) - { - m_firstArg = true; - - while(begin < end) - { - bool triggerCdt = triggerCondition(begin); - - if (m_tracebackCount > m_triggerPre) - { - bool trigger; - - if (m_triggerBothEdges[m_triggerIndex]) { - trigger = m_prevTrigger ^ triggerCdt; - } else { - trigger = triggerCdt ^ !m_triggerPositiveEdge[m_triggerIndex]; - } - - if (trigger) - { - if (m_armed) - { - m_armed = false; - if (m_triggerDelay[m_triggerIndex] > 0) - { - m_triggerDelayCount = m_triggerDelay[m_triggerIndex]; - m_fill = 0; - m_triggerState = Delay; - } - else - { - if (nextTrigger()) - { - m_triggerState = Untriggered; - } - else - { - m_triggerState = Triggered; - m_triggerPoint = begin; - // fill beginning of m_trace with delayed samples from the trace memory FIFO. Increment m_fill accordingly. - if (m_triggerPre) { // do this process only if there is a pre-trigger delay - std::copy(m_traceback.end() - m_triggerPre - 1, m_traceback.end() - 1, m_trace.begin()); - m_fill = m_triggerPre; // Increment m_fill accordingly (from 0). - } - } - } - break; - } - } - else - { - m_armed = true; - } - } - m_prevTrigger = triggerCdt; - ++begin; - } - } - - if(m_triggerState == Triggered) - { - int count = end - begin; - - if(count > (int)(m_trace.size() - m_fill)) - { - count = m_trace.size() - m_fill; - } - - std::vector::iterator it = m_trace.begin() + m_fill; - - for(int i = 0; i < count; ++i) - { - *it++ = Complex(begin->real() / m_scalef, begin->imag() / m_scalef); - ++begin; - } - - m_fill += count; - - if(m_fill >= m_trace.size()) - { - m_glScope->newTrace(m_trace, m_sampleRate); - m_fill = 0; - - if (m_triggerOneShot) { - m_triggerState = WaitForReset; - } else { - m_tracebackCount = 0; - m_triggerState = Untriggered; - m_triggerIndex = 0; - } - } - } - } - } -} - -void ScopeVis::start() -{ -} - -void ScopeVis::stop() -{ -} - -bool ScopeVis::handleMessage(const Message& message) -{ - qDebug() << "ScopeVis::handleMessage"; - - if (DSPSignalNotification::match(message)) - { - DSPSignalNotification& notif = (DSPSignalNotification&) message; - m_sampleRate = notif.getSampleRate(); - qDebug() << " - DSPSignalNotification: m_sampleRate: " << m_sampleRate; - - return true; - } - else if (MsgConfigureScopeVis::match(message)) - { - MsgConfigureScopeVis& conf = (MsgConfigureScopeVis&) message; - - m_tracebackCount = 0; - m_triggerState = Config; - uint index = conf.getTriggerIndex(); - m_triggerChannel[index] = (TriggerChannel) conf.getTriggerChannel(); - m_triggerLevel[index] = conf.getTriggerLevel(); - m_triggerPositiveEdge[index] = conf.getTriggerPositiveEdge(); - m_triggerBothEdges[index] = conf.getTriggerBothEdges(); - m_triggerPre = conf.getTriggerPre(); - - if (m_triggerChannel[index] == TriggerDPhase) - { - m_firstArg = true; - } - - if (m_triggerPre >= m_traceback.size()) - { - m_triggerPre = m_traceback.size() - 1; // top sample in FIFO is always the triggering one (pre-trigger delay = 0) - } - - m_triggerDelay[index] = conf.getTriggerDelay(); - m_triggerCounts[index] = conf.getTriggerCounts(); - uint newSize = conf.getTraceSize(); - - if (newSize != m_trace.size()) - { - m_trace.resize(newSize); - } - - if (newSize > m_traceback.size()) // fitting the exact required space is not a requirement for the back trace - { - m_traceback.resize(newSize); - } - - qDebug() << " - MsgConfigureScopeVis:" - << " triggerIndex: " << index - << " m_triggerChannel: " << m_triggerChannel[index] - << " m_triggerLevel: " << m_triggerLevel[index] - << " m_triggerPositiveEdge: " << (m_triggerPositiveEdge[index] ? "edge+" : "edge-") - << " m_triggerBothEdges: " << (m_triggerBothEdges[index] ? "yes" : "no") - << " m_preTrigger: " << m_triggerPre - << " m_triggerDelay: " << m_triggerDelay[index] - << " m_triggerCounts: " << m_triggerCounts[index] - << " m_traceSize: " << m_trace.size(); - - return true; - } - else - { - return false; - } -} - -void ScopeVis::setSampleRate(int sampleRate) -{ - m_sampleRate = sampleRate; -} - -bool ScopeVis::triggerCondition(SampleVector::const_iterator& it) -{ - Complex c(it->real()/m_scalef, it->imag()/m_scalef); - m_traceback.push_back(c); // store into trace memory FIFO - - if (m_tracebackCount < m_traceback.size()) - { // increment count up to trace memory size - m_tracebackCount++; - } - - if (m_triggerChannel[m_triggerIndex] == TriggerChannelI) - { - return c.real() > m_triggerLevel[m_triggerIndex]; - } - else if (m_triggerChannel[m_triggerIndex] == TriggerChannelQ) - { - return c.imag() > m_triggerLevel[m_triggerIndex]; - } - else if (m_triggerChannel[m_triggerIndex] == TriggerMagLin) - { - return abs(c) > m_triggerLevel[m_triggerIndex]; - } - else if (m_triggerChannel[m_triggerIndex] == TriggerMagDb) - { - Real mult = (10.0f / log2f(10.0f)); - Real v = c.real() * c.real() + c.imag() * c.imag(); - return mult * log2f(v) > m_triggerLevel[m_triggerIndex]; - } - else if (m_triggerChannel[m_triggerIndex] == TriggerPhase) - { - return arg(c) / M_PI > m_triggerLevel[m_triggerIndex]; - } - else if (m_triggerChannel[m_triggerIndex] == TriggerDPhase) - { - Real curArg = arg(c) - m_prevArg; - m_prevArg = arg(c); - - if (curArg < -M_PI) { - curArg += 2.0 * M_PI; - } else if (curArg > M_PI) { - curArg -= 2.0 * M_PI; - } - - if (m_firstArg) - { - m_firstArg = false; - return false; - } - else - { - return curArg / M_PI > m_triggerLevel[m_triggerIndex]; - } - } - else - { - return false; - } -} - -void ScopeVis::setOneShot(bool oneShot) -{ - m_triggerOneShot = oneShot; - - if ((m_triggerState == WaitForReset) && !oneShot) { - m_tracebackCount = 0; - m_triggerState = Untriggered; - m_triggerIndex = 0; - } -} - -void ScopeVis::blockTrigger(bool blocked) -{ - if (blocked) - { - m_triggerState = WaitForReset; - } - else - { - if (!m_triggerOneShot) { - m_tracebackCount = 0; - m_triggerState = Untriggered; - m_triggerIndex = 0; - } - } -} - -bool ScopeVis::nextTrigger() -{ - if (m_triggerCount < m_triggerCounts[m_triggerIndex]) - { - m_triggerCount++; - return true; - } - else - { - m_triggerIndex++; - m_prevTrigger = false; - m_triggerDelayCount = 0; - m_triggerCount = 0; - m_armed = false; - - if (m_triggerIndex == m_nbTriggers) - { - m_triggerIndex = 0; - return false; - } - else if (m_triggerChannel[m_triggerIndex] == TriggerFreeRun) - { - m_triggerIndex = 0; - return false; - } - else - { - return true; - } - } -} diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h deleted file mode 100644 index 0c08e59bd..000000000 --- a/sdrgui/dsp/scopevis.h +++ /dev/null @@ -1,171 +0,0 @@ -#ifndef INCLUDE_SCOPEVIS_H -#define INCLUDE_SCOPEVIS_H - -#include -#include -#include "export.h" -#include "util/message.h" - -class GLScope; -class MessageQueue; - -class SDRGUI_API ScopeVis : public BasebandSampleSink { -public: - enum TriggerChannel { - TriggerFreeRun, - TriggerChannelI, - TriggerChannelQ, - TriggerMagLin, - TriggerMagDb, - TriggerPhase, - TriggerDPhase - }; - - static const uint m_traceChunkSize; - static const uint m_nbTriggers = 10; - - ScopeVis(Real scalef, GLScope* glScope = 0); - virtual ~ScopeVis(); - - void configure(MessageQueue* msgQueue, - uint triggerIndex, - TriggerChannel triggerChannel, - Real triggerLevel, - bool triggerPositiveEdge, - bool triggerBothEdges, - uint triggerPre, - uint triggerDelay, - uint triggerCounts, - uint traceSize); - void setOneShot(bool oneShot); - void blockTrigger(bool blecked); - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& message); - - void setSampleRate(int sampleRate); - int getSampleRate() const { return m_sampleRate; } - SampleVector::const_iterator getTriggerPoint() const { return m_triggerPoint; } - -private: - class MsgConfigureScopeVis : public Message { - MESSAGE_CLASS_DECLARATION - - public: - uint getTriggerIndex() const { return m_triggerIndex; } - int getTriggerChannel() const { return m_triggerChannel; } - Real getTriggerLevel() const { return m_triggerLevel; } - bool getTriggerPositiveEdge() const { return m_triggerPositiveEdge; } - bool getTriggerBothEdges() const { return m_triggerBothEdges; } - uint getTriggerPre() const { return m_triggerPre; } - uint getTriggerDelay() const { return m_triggerDelay; } - uint getTriggerCounts() const { return m_triggerCounts; } - uint getTraceSize() const { return m_traceSize; } - - static MsgConfigureScopeVis* create(uint triggerIndex, - int triggerChannel, - Real triggerLevel, - bool triggerPositiveEdge, - bool triggerBothEdges, - uint triggerPre, - uint triggerDelay, - uint triggerCounts, - uint traceSize) - { - return new MsgConfigureScopeVis(triggerIndex, - triggerChannel, - triggerLevel, - triggerPositiveEdge, - triggerBothEdges, - triggerPre, - triggerDelay, - triggerCounts, - traceSize); - } - - private: - uint m_triggerIndex; - int m_triggerChannel; - Real m_triggerLevel; - bool m_triggerPositiveEdge; - bool m_triggerBothEdges; - uint m_triggerPre; - uint m_triggerDelay; - uint m_triggerCounts; - uint m_traceSize; - - MsgConfigureScopeVis(uint triggerIndex, - int triggerChannel, - Real triggerLevel, - bool triggerPositiveEdge, - bool triggerBothEdges, - uint triggerPre, - uint triggerDelay, - uint triggerCounts, - uint traceSize) : - Message(), - m_triggerIndex(triggerIndex), - m_triggerChannel(triggerChannel), - m_triggerLevel(triggerLevel), - m_triggerPositiveEdge(triggerPositiveEdge), - m_triggerBothEdges(triggerBothEdges), - m_triggerPre(triggerPre), - m_triggerDelay(triggerDelay), - m_triggerCounts(triggerCounts), - m_traceSize(traceSize) - { } - }; - - /** - * TriggerState: (repeat at each successive non freerun trigger) - * - * send a Trigger condition +--------------------+ - * dummy trace - Immediate m_triggerOneShot | | - * Config -------------> Untriggered ----------------------------------> Triggered ----------------> WaitForReset | - * ^ ^ | ^ | | ^ | - * | | | - Delayed Delay expired | | | | setOneShot(true)| - * | | +---------------------> Delay ----------------+ | | +-----------------+ - * | | !m_triggerOneShot | | - * | +--------------------------------------------------+ setOneShot(false) | - * +-------------------------------------------------------------------------------+ - */ - enum TriggerState { - Untriggered, //!< Search for trigger - Config, //!< New configuration has just been received - Triggered, //!< Trigger was kicked off - WaitForReset, //!< Wait for release from GUI - Delay //!< Trigger delay engaged - }; - - GLScope* m_glScope; - Real m_scalef; //!< Sample scale factor from [-1,+1] to integer sample size range - std::vector m_trace; //!< Raw trace to be used by GLScope - boost::circular_buffer m_traceback; //!< FIFO for samples prior to triggering point to support pre-trigger (when in triggered mode) - uint m_tracebackCount; //!< Count of samples stored into trace memory since triggering is active up to trace memory size - uint m_fill; - TriggerState m_triggerState; - uint m_triggerIndex; //!< current active trigger index - TriggerChannel m_triggerChannel[m_nbTriggers]; - Real m_triggerLevel[m_nbTriggers]; - bool m_triggerPositiveEdge[m_nbTriggers]; - bool m_triggerBothEdges[m_nbTriggers]; - bool m_prevTrigger; - uint m_triggerPre; //!< Pre-trigger delay in number of samples - bool m_triggerOneShot; - bool m_armed; - uint m_triggerDelay[m_nbTriggers]; //!< Trigger delay in number of trace sizes - uint m_triggerDelayCount; //!< trace sizes delay counter - uint m_triggerCounts[m_nbTriggers]; //!< Number of trigger events before the actual trigger is kicked off - uint m_triggerCount; - int m_sampleRate; - SampleVector::const_iterator m_triggerPoint; - Real m_prevArg; - bool m_firstArg; - - bool triggerCondition(SampleVector::const_iterator& it); - bool nextTrigger(); //!< move to next trigger. Returns true if next trigger is active. -}; - -#endif // INCLUDE_SCOPEVIS_H diff --git a/sdrgui/dsp/spectrumscopecombovis.cpp b/sdrgui/dsp/spectrumscopecombovis.cpp deleted file mode 100644 index feb482022..000000000 --- a/sdrgui/dsp/spectrumscopecombovis.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "dsp/spectrumscopecombovis.h" -#include "dsp/dspcommands.h" -#include "util/messagequeue.h" - -SpectrumScopeComboVis::SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVis* scopeVis) : - m_spectrumVis(spectrumVis), - m_scopeVis(scopeVis) -{ - setObjectName("SpectrumScopeComboVis"); -} - -SpectrumScopeComboVis::~SpectrumScopeComboVis() -{ -} - -void SpectrumScopeComboVis::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) -{ - m_scopeVis->feed(begin, end, false); - SampleVector::const_iterator triggerPoint = m_scopeVis->getTriggerPoint(); - m_spectrumVis->feedTriggered(triggerPoint, end, positiveOnly); -} - -void SpectrumScopeComboVis::start() -{ - m_spectrumVis->start(); - m_scopeVis->start(); -} - -void SpectrumScopeComboVis::stop() -{ - m_spectrumVis->stop(); - m_scopeVis->stop(); -} - -bool SpectrumScopeComboVis::handleMessage(const Message& message) -{ - bool spectDone = m_spectrumVis->handleMessage(message); - bool scopeDone = m_scopeVis->handleMessage(message); - - return (spectDone || scopeDone); -} diff --git a/sdrgui/dsp/spectrumscopecombovis.h b/sdrgui/dsp/spectrumscopecombovis.h deleted file mode 100644 index 6e7b6f612..000000000 --- a/sdrgui/dsp/spectrumscopecombovis.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef INCLUDE_SPECTRUMSCOPECOMBOVIS_H -#define INCLUDE_SPECTRUMSCOPECOMBOVIS_H - -#include -#include "dsp/spectrumvis.h" -#include "dsp/scopevis.h" -#include "export.h" - -class Message; - -class SDRGUI_API SpectrumScopeComboVis : public BasebandSampleSink { -public: - - SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVis* scopeVis); - virtual ~SpectrumScopeComboVis(); - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& message); - -private: - SpectrumVis* m_spectrumVis; - ScopeVis* m_scopeVis; -}; - -#endif // INCLUDE_SPECTRUMSCOPECOMBOVIS_H diff --git a/sdrgui/gui/glscope.cpp b/sdrgui/gui/glscope.cpp deleted file mode 100644 index 32ade47b5..000000000 --- a/sdrgui/gui/glscope.cpp +++ /dev/null @@ -1,2209 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2016 F4EXB // -// written by Edouard Griffiths // -// // -// OpenGL interface modernization. // -// // -// 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 -#include "gui/glscope.h" - -#include -#include - -#undef M_PI -#define M_PI 3.14159265358979323846 - -/* -#ifdef _WIN32 -static double log2f(double n) -{ - return log(n) / log(2.0); -} -#endif*/ - -GLScope::GLScope(QWidget* parent) : - QGLWidget(parent), - m_dataChanged(false), - m_configChanged(true), - m_mode(ModeIQ), - m_displays(DisplayBoth), - m_orientation(Qt::Horizontal), - m_memTraceIndex(0), - m_memTraceHistory(0), - m_memTraceIndexMax(0), - m_memTraceRecall(false), - m_displayTrace(&m_rawTrace[0]), - //m_amp(1.0), - //m_ofs(0.0), - m_maxPow(0.0f), - m_sumPow(0.0f), - m_oldTraceSize(-1), - m_sampleRate(0), - m_amp1(1.0), - m_amp2(1.0), - m_ofs1(0.0), - m_ofs2(0.0), - m_timeBase(1), - m_timeOfsProMill(0), - m_triggerChannel(ScopeVis::TriggerFreeRun), - m_triggerLevel(0.0), - m_triggerPre(0.0), - m_triggerLevelDis1(0.0), - m_triggerLevelDis2(0.0), - m_nbPow(1), - m_prevArg(0), - m_displayGridIntensity(5), - m_displayTraceIntensity(50), - m_powerOverlayFont(font()) -{ - setAttribute(Qt::WA_OpaquePaintEvent); - connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); - m_timer.start(50); - m_y1Scale.setFont(font()); - m_y1Scale.setOrientation(Qt::Vertical); - m_y2Scale.setFont(font()); - m_y2Scale.setOrientation(Qt::Vertical); - m_x1Scale.setFont(font()); - m_x1Scale.setOrientation(Qt::Horizontal); - m_x2Scale.setFont(font()); - m_x2Scale.setOrientation(Qt::Horizontal); - m_powerOverlayFont.setBold(true); - m_powerOverlayFont.setPointSize(font().pointSize()+1); - memset(m_sampleRates, 0, (1< 100) { - m_displayGridIntensity = 100; - } else if (m_displayGridIntensity < 0) { - m_displayGridIntensity = 0; - } - update(); -} - -void GLScope::setDisplayTraceIntensity(int intensity) -{ - m_displayTraceIntensity = intensity; - if (m_displayTraceIntensity > 100) { - m_displayTraceIntensity = 100; - } else if (m_displayTraceIntensity < 0) { - m_displayTraceIntensity = 0; - } - update(); -} - -void GLScope::newTrace(const std::vector& trace, int sampleRate) -{ - if (!m_memTraceRecall) - { - if(!m_mutex.tryLock(2)) - return; - if(m_dataChanged) { - m_mutex.unlock(); - return; - } - - m_memTraceIndex++; - m_rawTrace[m_memTraceIndex] = trace; - m_sampleRates[m_memTraceIndex] = sampleRate; - - if(m_memTraceIndexMax < (1<isValid()) { - qDebug() << "GLScope::initializeGL: context:" - << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion() - << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion() - << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no"); - } - else { - qDebug() << "GLScope::initializeGL: current context is invalid"; - } - } else { - qCritical() << "GLScope::initializeGL: no current context"; - return; - } - - QSurface *surface = glCurrentContext->surface(); - - if (surface == 0) - { - qCritical() << "GLScope::initializeGL: no surface attached"; - return; - } - else - { - if (surface->surfaceType() != QSurface::OpenGLSurface) - { - qCritical() << "GLScope::initializeGL: surface is not an OpenGLSurface: " << surface->surfaceType() - << " cannot use an OpenGL context"; - return; - } - else - { - qDebug() << "GLScope::initializeGL: OpenGL surface:" - << " class: " << (surface->surfaceClass() == QSurface::Window ? "Window" : "Offscreen"); - } - } - - connect(glCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, &GLScope::cleanup); // TODO: when migrating to QOpenGLWidget - - QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); - glFunctions->initializeOpenGLFunctions(); - - //glDisable(GL_DEPTH_TEST); - m_glShaderSimple.initializeGL(); - m_glShaderLeft1Scale.initializeGL(); - m_glShaderBottom1Scale.initializeGL(); - m_glShaderLeft2Scale.initializeGL(); - m_glShaderBottom2Scale.initializeGL(); - m_glShaderPowerOverlay.initializeGL(); -} - -void GLScope::resizeGL(int width, int height) -{ - QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); - glFunctions->glViewport(0, 0, width, height); - m_configChanged = true; -} - -void GLScope::paintGL() -{ - if(!m_mutex.tryLock(2)) - return; - - if(m_configChanged) { - applyConfig(); - } - - handleMode(); - - if(m_displayTrace->size() - m_oldTraceSize != 0) { - m_oldTraceSize = m_displayTrace->size(); - emit traceSizeChanged((int) m_displayTrace->size()); - } - - QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); - glFunctions->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glFunctions->glClear(GL_COLOR_BUFFER_BIT); - - // I - primary display - - if ((m_displays == DisplayBoth) || (m_displays == DisplayFirstOnly)) - { - // draw rect around - { - GLfloat q3[] { - 1, 1, - 0, 1, - 0, 0, - 1, 0 - }; - - QVector4D color(1.0f, 1.0f, 1.0f, 0.5f); - m_glShaderSimple.drawContour(m_glScopeMatrix1, color, q3, 4); - - } - - // paint grid - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - // Horizontal Y1 - tickList = &m_y1Scale.getTickList(); - - { - //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_q3TickY1.m_array; - int effectiveTicks = 0; - - for (int i= 0; i < tickList->count(); i++) - { - tick = &(*tickList)[i]; - - if (tick->major) - { - if (tick->textSize > 0) - { - float y = 1 - (tick->pos / m_y1Scale.getSize()); - q3[4*effectiveTicks] = 0; - q3[4*effectiveTicks+1] = y; - q3[4*effectiveTicks+2] = 1; - q3[4*effectiveTicks+3] = y; - effectiveTicks++; - } - } - } - - float blue = (m_mode == ModeIQPolar ? 0.25f : 1.0f); - QVector4D color(1.0f, 1.0f, blue, (float) m_displayGridIntensity / 100.0f); - m_glShaderSimple.drawSegments(m_glScopeMatrix1, color, q3, 2*effectiveTicks); - } - - { - // Vertical X1 - tickList = &m_x1Scale.getTickList(); - - //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_q3TickX1.m_array; - int effectiveTicks = 0; - for(int i= 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - float x = tick->pos / m_x1Scale.getSize(); - q3[4*effectiveTicks] = x; - q3[4*effectiveTicks+1] = 0; - q3[4*effectiveTicks+2] = x; - q3[4*effectiveTicks+3] = 1; - effectiveTicks++; - } - } - } - - QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); - m_glShaderSimple.drawSegments(m_glScopeMatrix1, color, q3, 2*effectiveTicks); - } - - // paint left #1 scale - { - GLfloat vtx1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - GLfloat tex1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - - m_glShaderLeft1Scale.drawSurface(m_glLeft1ScaleMatrix, tex1, vtx1, 4); - } - - // paint bottom #1 scale - { - GLfloat vtx1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - GLfloat tex1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - - m_glShaderBottom1Scale.drawSurface(m_glBot1ScaleMatrix, tex1, vtx1, 4); - } - - // paint trigger level #1 - if ((m_triggerChannel == ScopeVis::TriggerChannelI) - || (m_triggerChannel == ScopeVis::TriggerMagLin) - || (m_triggerChannel == ScopeVis::TriggerMagDb) - ) - { - float posLimit = 1.0 / m_amp1; - float negLimit = -1.0 / m_amp1; - - if ((m_triggerLevelDis1 > negLimit) && (m_triggerLevelDis1 < posLimit)) - { - GLfloat q3[] { - 0, m_triggerLevelDis1, - 1, m_triggerLevelDis1 - }; - - float rectX = m_glScopeRect1.x(); - float rectY = m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0f; - float rectW = m_glScopeRect1.width(); - float rectH = -(m_glScopeRect1.height() / 2.0f) * m_amp1; - - QVector4D color(0.0f, 1.0f, 0.0f, 0.4f); - QMatrix4x4 mat; - mat.setToIdentity(); - mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); - mat.scale(2.0f * rectW, -2.0f * rectH); - m_glShaderSimple.drawSegments(mat, color, q3, 2); - -// glPushMatrix(); -// glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0, 0); -// glScalef(m_glScopeRect1.width(), -(m_glScopeRect1.height() / 2) * m_amp1, 1); - } - } - - // paint trace #1 - if(m_displayTrace->size() > 0) - { - { - - int start = (m_timeOfsProMill/1000.0) * m_displayTrace->size(); - int end = std::min(start + m_displayTrace->size()/m_timeBase, m_displayTrace->size()); - if(end - start < 2) - start--; - float posLimit = 1.0 / m_amp1; - float negLimit = -1.0 / m_amp1; - - //GLfloat q3[2*(end -start)]; - m_q3Trace.allocate(2*(end - start)); - GLfloat *q3 = m_q3Trace.m_array; - - for (int i = start; i < end; i++) - { - float v = (*m_displayTrace)[i].real(); - if(v > posLimit) - v = posLimit; - else if(v < negLimit) - v = negLimit; - - q3[2*(i-start)] = i - start; - q3[2*(i-start) + 1] = v; - - if ((m_mode == ModeMagdBPha) || (m_mode == ModeMagdBDPha)) - { - if (i == start) - { - m_maxPow = m_powTrace[i]; - m_sumPow = m_powTrace[i]; - } - else - { - if (m_powTrace[i] > m_maxPow) - { - m_maxPow = m_powTrace[i]; - } - - m_sumPow += m_powTrace[i]; - } - } - } - - float rectX = m_glScopeRect1.x(); - float rectY = m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0f; - float rectW = m_glScopeRect1.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1); - float rectH = -(m_glScopeRect1.height() / 2.0f) * m_amp1; - - QVector4D color(1.0f, 1.0f, 0.25f, m_displayTraceIntensity / 100.0f); - QMatrix4x4 mat; - mat.setToIdentity(); - mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); - mat.scale(2.0f * rectW, -2.0f * rectH); - m_glShaderSimple.drawPolyline(mat, color, q3, end -start); - -// glPushMatrix(); -// glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0, 0); -// glScalef(m_glScopeRect1.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1), -(m_glScopeRect1.height() / 2) * m_amp1, 1); - m_nbPow = end - start; - } - } - - // Paint powers overlays - - if ((m_mode == ModeMagdBPha) || (m_mode == ModeMagdBDPha)) - { - if (m_nbPow > 0) - { - drawPowerOverlay(); - } - } - - if (m_mode == ModeIQPolar) - { - // Paint trace 2 (Q) over - if (m_displayTrace->size() > 0) - { - { - - int start = (m_timeOfsProMill/1000.0) * m_displayTrace->size(); - int end = std::min(start + m_displayTrace->size()/m_timeBase, m_displayTrace->size()); - - if(end - start < 2) { - start--; - } - - - float posLimit = 1.0 / m_amp2; - float negLimit = -1.0 / m_amp2; - - //GLfloat q3[2*(end - start)]; - m_q3Trace.allocate(2*(end - start)); - GLfloat *q3 = m_q3Trace.m_array; - - for(int i = start; i < end; i++) - { - float v = (*m_displayTrace)[i].imag(); - if(v > posLimit) - v = posLimit; - else if(v < negLimit) - v = negLimit; - q3[2*(i-start)] = i - start; - q3[2*(i-start) + 1] = v; - } - - float rectX = m_glScopeRect1.x(); - float rectY = m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0f; - float rectW = m_glScopeRect1.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1); - float rectH = -(m_glScopeRect1.height() / 2.0f) * m_amp2; - - QVector4D color(0.25f, 1.0f, 1.0f, m_displayTraceIntensity / 100.0); - QMatrix4x4 mat; - mat.setToIdentity(); - mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); - mat.scale(2.0f * rectW, -2.0f * rectH); - m_glShaderSimple.drawPolyline(mat, color, q3, end -start); - -// glPushMatrix(); -// glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0, 0); -// glScalef(m_glScopeRect1.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1), -(m_glScopeRect1.height() / 2) * m_amp2, 1); - } - } - - // Paint secondary grid - // draw rect around - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - // Horizontal Y2 - tickList = &m_y2Scale.getTickList(); - { - //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_q3TickY2.m_array; - int effectiveTicks = 0; - for(int i= 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - float y = 1 - (tick->pos / m_y2Scale.getSize()); - q3[4*effectiveTicks] = 0; - q3[4*effectiveTicks+1] = y; - q3[4*effectiveTicks+2] = 1; - q3[4*effectiveTicks+3] = y; - effectiveTicks++; - } - } - } - - QVector4D color(0.25f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); - m_glShaderSimple.drawSegments(m_glScopeMatrix1, color, q3, 2*effectiveTicks); - } - - // Paint secondary scale - { - GLfloat vtx1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - GLfloat tex1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - - m_glShaderLeft2Scale.drawSurface(m_glRight1ScaleMatrix, tex1, vtx1, 4); - } - } - } // Both displays or primary only - - // Q - secondary display - - if ((m_displays == DisplayBoth) || (m_displays == DisplaySecondOnly)) - { - // draw rect around - { - GLfloat q3[] { - 1, 1, - 0, 1, - 0, 0, - 1, 0 - }; - - QVector4D color(1.0f, 1.0f, 1.0f, 0.5f); - m_glShaderSimple.drawContour(m_glScopeMatrix2, color, q3, 4); - } - - // paint grid - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - // Horizontal Y2 - tickList = &m_y2Scale.getTickList(); - { - //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_q3TickY2.m_array; - int effectiveTicks = 0; - for(int i= 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - float y = 1 - (tick->pos / m_y2Scale.getSize()); - q3[4*effectiveTicks] = 0; - q3[4*effectiveTicks+1] = y; - q3[4*effectiveTicks+2] = 1; - q3[4*effectiveTicks+3] = y; - effectiveTicks++; - } - } - } - - QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); - m_glShaderSimple.drawSegments(m_glScopeMatrix2, color, q3, 2*effectiveTicks); - } - - // Vertical X2 - tickList = &m_x2Scale.getTickList(); - { - //GLfloat q3[4*tickList->count()]; - GLfloat *q3 = m_q3TickX2.m_array; - int effectiveTicks = 0; - for(int i= 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - float x = tick->pos / m_x2Scale.getSize(); - q3[4*effectiveTicks] = x; - q3[4*effectiveTicks+1] = 0; - q3[4*effectiveTicks+2] = x; - q3[4*effectiveTicks+3] = 1; - effectiveTicks++; - } - } - } - - QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); - m_glShaderSimple.drawSegments(m_glScopeMatrix2, color, q3, 2*effectiveTicks); - } - - // paint left #2 scale - { - GLfloat vtx1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - GLfloat tex1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - - m_glShaderLeft2Scale.drawSurface(m_glLeft2ScaleMatrix, tex1, vtx1, 4); - } - - // paint bottom #2 scale - { - GLfloat vtx1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - GLfloat tex1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - - m_glShaderBottom2Scale.drawSurface(m_glBot2ScaleMatrix, tex1, vtx1, 4); - } - - // paint trigger level #2 - if ((m_triggerChannel == ScopeVis::TriggerPhase) - || (m_triggerChannel == ScopeVis::TriggerDPhase) - || (m_triggerChannel == ScopeVis::TriggerChannelQ)) - { - float posLimit = 1.0 / m_amp2; - float negLimit = -1.0 / m_amp2; - - if ((m_triggerLevelDis2 > negLimit) && (m_triggerLevelDis2 < posLimit)) - { - GLfloat q3[] { - 0, m_triggerLevelDis2, - 1, m_triggerLevelDis2 - }; - - float rectX = m_glScopeRect2.x(); - float rectY = m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0f; - float rectW = m_glScopeRect2.width(); - float rectH = -(m_glScopeRect2.height() / 2.0f) * m_amp2; - - QVector4D color(0.0f, 1.0f, 0.0f, 0.4f); - QMatrix4x4 mat; - mat.setToIdentity(); - mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); - mat.scale(2.0f * rectW, -2.0f * rectH); - m_glShaderSimple.drawSegments(mat, color, q3, 2); - } - } - - // paint trace #2 - if(m_displayTrace->size() > 0) - { - if (m_mode == ModeIQPolar) - { - - int start = (m_timeOfsProMill/1000.0) * m_displayTrace->size(); - int end = std::min(start + m_displayTrace->size()/m_timeBase, m_displayTrace->size()); - - if (end - start < 2) { - start--; - } - - - { - //GLfloat q3[2*(end - start)]; - m_q3Trace.allocate(2*(end - start)); - GLfloat *q3 = m_q3Trace.m_array; - - for(int i = start; i < end; i++) - { - float x = (*m_displayTrace)[i].real() * m_amp1; - float y = (*m_displayTrace)[i].imag() * m_amp2; - if(x > 1.0f) - x = 1.0f; - else if(x < -1.0f) - x = -1.0f; - if(y > 1.0f) - y = 1.0f; - else if(y < -1.0f) - y = -1.0f; - q3[2*(i-start)] = x; - q3[2*(i-start)+1] = y; - } - - float rectX = m_glScopeRect2.x() + m_glScopeRect2.width() / 2.0f; - float rectY = m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0f; - float rectW = m_glScopeRect2.width() / 2.0f; - float rectH = -(m_glScopeRect2.height() / 2.0f); - - QVector4D color(1.0f, 1.0f, 0.25f, m_displayTraceIntensity / 100.0f); - QMatrix4x4 mat; - mat.setToIdentity(); - mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); - mat.scale(2.0f * rectW, -2.0f * rectH); - m_glShaderSimple.drawPolyline(mat, color, q3, end -start); - -// glPushMatrix(); -// glTranslatef(m_glScopeRect2.x() + m_glScopeRect2.width() / 2.0, m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0, 0); -// glScalef(m_glScopeRect2.width() / 2, -(m_glScopeRect2.height() / 2), 1); - } - } - else - { - { - int start = (m_timeOfsProMill/1000.0) * m_displayTrace->size(); - int end = std::min(start + m_displayTrace->size()/m_timeBase, m_displayTrace->size()); - - if (end - start < 2) { - start--; - } - - - float posLimit = 1.0 / m_amp2; - float negLimit = -1.0 / m_amp2; - - //GLfloat q3[2*(end - start)]; - m_q3Trace.allocate(2*(end - start)); - GLfloat *q3 = m_q3Trace.m_array; - - for(int i = start; i < end; i++) { - float v = (*m_displayTrace)[i].imag(); - if(v > posLimit) - v = posLimit; - else if(v < negLimit) - v = negLimit; - - q3[2*(i-start)] = i - start; - q3[2*(i-start)+1] = v; - } - - float rectX = m_glScopeRect2.x(); - float rectY = m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0f; - float rectW = m_glScopeRect2.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1); - float rectH = -(m_glScopeRect2.height() / 2.0f) * m_amp2; - - QVector4D color(1.0f, 1.0f, 0.25f, m_displayTraceIntensity / 100.0f); - QMatrix4x4 mat; - mat.setToIdentity(); - mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); - mat.scale(2.0f * rectW, -2.0f * rectH); - m_glShaderSimple.drawPolyline(mat, color, q3, end -start); - -// glPushMatrix(); -// glTranslatef(m_glScopeRect2.x(), m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0, 0); -// glScalef(m_glScopeRect2.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1), -(m_glScopeRect2.height() / 2) * m_amp2, 1); - } - } - } - } // Both displays or secondary display only - -// glPopMatrix(); - m_dataChanged = false; - m_mutex.unlock(); -} - -void GLScope::mousePressEvent(QMouseEvent* event __attribute__((unused))) -{ -#if 0 - int x = event->x() - 10; - int y = event->y() - 10; - Real time; - Real amplitude; - ScopeVis::TriggerChannel channel; - if((x < 0) || (x >= width() - 20)) - return; - if((y < 0) || (y >= height() - 20)) - return; - - if((m_sampleRate != 0) && (m_timeBase != 0) && (width() > 20)) - time = ((Real)x * (Real)m_displayTrace->size()) / ((Real)m_sampleRate * (Real)m_timeBase * (Real)(width() - 20)); - else time = -1.0; - - if(y < (height() - 30) / 2) { - channel = ScopeVis::TriggerChannelI; - if((m_amp != 0) && (height() > 30)) - amplitude = 2.0 * ((height() - 30) * 0.25 - (Real)y) / (m_amp * (height() - 30) / 2.0); - else amplitude = -1; - } else if(y > (height() - 30) / 2 + 10) { - y -= 10 + (height() - 30) / 2; - channel = ScopeVis::TriggerChannelQ; - if((m_amp != 0) && (height() > 30)) - amplitude = 2.0 * ((height() - 30) * 0.25 - (Real)y) / (m_amp * (height() - 30) / 2.0); - else amplitude = -1; - } else { - channel = ScopeVis::TriggerFreeRun; - } - - if(m_dspEngine != NULL) { - qDebug("amp %f", amplitude); - m_triggerLevel = amplitude + 0.01 / m_amp; - m_triggerLevelLow = amplitude - 0.01 / m_amp; - if(m_triggerLevel > 1.0) - m_triggerLevel = 1.0; - else if(m_triggerLevel < -1.0) - m_triggerLevel = -1.0; - if(m_triggerLevelLow > 1.0) - m_triggerLevelLow = 1.0; - else if(m_triggerLevelLow < -1.0) - m_triggerLevelLow = -1.0; - m_scopeVis->configure(m_dspEngine->getMessageQueue(), channel, m_triggerLevel, m_triggerLevelLow); - m_triggerChannel = channel; - m_changed = true; - update(); - } -#endif -} - -void GLScope::handleMode() -{ - BitfieldIndex memIndex = m_memTraceIndex - m_memTraceHistory; - - switch(m_mode) { - case ModeIQ: - case ModeIQPolar: - { - m_mathTrace.resize(m_rawTrace[memIndex].size()); - std::vector::iterator dst = m_mathTrace.begin(); - m_displayTrace = &m_rawTrace[memIndex]; - - for(std::vector::const_iterator src = m_rawTrace[memIndex].begin(); src != m_rawTrace[memIndex].end(); ++src) { - *dst++ = Complex(src->real() - m_ofs1, src->imag() - m_ofs2); - } - - m_triggerLevelDis1 = m_triggerLevel - m_ofs1; - m_triggerLevelDis2 = m_triggerLevel - m_ofs2; - - m_displayTrace = &m_mathTrace; - - break; - } - case ModeMagLinPha: - { - m_mathTrace.resize(m_rawTrace[memIndex].size()); - std::vector::iterator dst = m_mathTrace.begin(); - - for(std::vector::const_iterator src = m_rawTrace[memIndex].begin(); src != m_rawTrace[memIndex].end(); ++src) - { - *dst++ = Complex(abs(*src) - m_ofs1/2.0 - 1.0/m_amp1, (arg(*src) / M_PI) - m_ofs2); - } - - m_triggerLevelDis1 = (m_triggerLevel + 1) - m_ofs1/2.0 - 1.0/m_amp1; - m_triggerLevelDis2 = m_triggerLevel - m_ofs2; - - m_displayTrace = &m_mathTrace; - - break; - } - case ModeMagdBPha: - { - m_mathTrace.resize(m_rawTrace[memIndex].size()); - m_powTrace.resize(m_rawTrace[memIndex].size()); - std::vector::iterator dst = m_mathTrace.begin(); - std::vector::iterator powDst = m_powTrace.begin(); - - for(std::vector::const_iterator src = m_rawTrace[memIndex].begin(); src != m_rawTrace[memIndex].end(); ++src) { - Real v = src->real() * src->real() + src->imag() * src->imag(); - *powDst++ = v; - v = 1.0f + 2.0f*(((10.0f*log10f(v))/100.0f) - m_ofs1) + 1.0f - 1.0f/m_amp1; - *dst++ = Complex(v, (arg(*src) / M_PI) - m_ofs2); - } - - Real tdB = (m_triggerLevel - 1) * 100.0f; - m_triggerLevelDis1 = 1.0f + 2.0f*(((tdB)/100.0f) - m_ofs1) + 1.0f - 1.0f/m_amp1; - m_triggerLevelDis2 = m_triggerLevel - m_ofs2; - - m_displayTrace = &m_mathTrace; - - break; - } - case ModeMagLinDPha: - { - m_mathTrace.resize(m_rawTrace[memIndex].size()); - std::vector::iterator dst = m_mathTrace.begin(); - Real curArg; - - for(std::vector::const_iterator src = m_rawTrace[memIndex].begin(); src != m_rawTrace[memIndex].end(); ++src) - { - curArg = arg(*src) - m_prevArg; - - if (curArg < -M_PI) { - curArg += 2.0 * M_PI; - } else if (curArg > M_PI) { - curArg -= 2.0 * M_PI; - } - - *dst++ = Complex(abs(*src) - m_ofs1/2.0 - 1.0/m_amp1, (curArg / M_PI) - m_ofs2); - m_prevArg = arg(*src); - } - - m_triggerLevelDis1 = (m_triggerLevel + 1) - m_ofs1/2.0 - 1.0/m_amp1; - m_triggerLevelDis2 = m_triggerLevel - m_ofs2; - - m_displayTrace = &m_mathTrace; - - break; - } - case ModeMagdBDPha: - { - m_mathTrace.resize(m_rawTrace[memIndex].size()); - m_powTrace.resize(m_rawTrace[memIndex].size()); - std::vector::iterator dst = m_mathTrace.begin(); - std::vector::iterator powDst = m_powTrace.begin(); - Real curArg; - - for(std::vector::const_iterator src = m_rawTrace[memIndex].begin(); src != m_rawTrace[memIndex].end(); ++src) - { - Real v = src->real() * src->real() + src->imag() * src->imag(); - *powDst++ = v; - v = 1.0f + 2.0f*(((10.0f*log10f(v))/100.0f) - m_ofs1) + 1.0f - 1.0f/m_amp1; - curArg = arg(*src) - m_prevArg; - - if (curArg < -M_PI) { - curArg += 2.0 * M_PI; - } else if (curArg > M_PI) { - curArg -= 2.0 * M_PI; - } - - *dst++ = Complex(v, (curArg / M_PI) - m_ofs2); - m_prevArg = arg(*src); - } - - Real tdB = (m_triggerLevel - 1) * 100.0f; - m_triggerLevelDis1 = 1.0f + 2.0f*(((tdB)/100.0f) - m_ofs1) + 1.0f - 1.0f/m_amp1; - m_triggerLevelDis2 = m_triggerLevel - m_ofs2; - - m_displayTrace = &m_mathTrace; - - break; - } - case ModeDerived12: - { - if(m_rawTrace[memIndex].size() > 3) - { - m_mathTrace.resize(m_rawTrace[memIndex].size() - 3); - std::vector::iterator dst = m_mathTrace.begin(); - - for(uint i = 3; i < m_rawTrace[memIndex].size() ; i++) - { - *dst++ = Complex( - abs(m_rawTrace[memIndex][i] - m_rawTrace[memIndex][i - 1]), - abs(m_rawTrace[memIndex][i] - m_rawTrace[memIndex][i - 1]) - abs(m_rawTrace[memIndex][i - 2] - m_rawTrace[0][i - 3])); - } - - m_displayTrace = &m_mathTrace; - } - - break; - } - case ModeCyclostationary: - { - if(m_rawTrace[0].size() > 2) - { - m_mathTrace.resize(m_rawTrace[memIndex].size() - 2); - std::vector::iterator dst = m_mathTrace.begin(); - - for(uint i = 2; i < m_rawTrace[memIndex].size() ; i++) - *dst++ = Complex(abs(m_rawTrace[memIndex][i] - conj(m_rawTrace[memIndex][i - 1])), 0); - - m_displayTrace = &m_mathTrace; - } - - break; - } - } -} - -void GLScope::drawPowerOverlay() -{ - double maxPow = 10.0f * log10f(m_maxPow); - double avgPow = 10.0f * log10f(m_sumPow / m_nbPow); - double peakToAvgPow = maxPow - avgPow; - - QString text = QString("%1 %2 %3").arg(maxPow, 0, 'f', 1).arg(avgPow, 0, 'f', 1).arg(peakToAvgPow, 0, 'f', 1); - - QFontMetricsF metrics(m_powerOverlayFont); - QRectF rect = metrics.boundingRect(text); - m_powerOverlayPixmap1 = QPixmap(rect.width() + 4.0f, rect.height()); - m_powerOverlayPixmap1.fill(Qt::transparent); - QPainter painter(&m_powerOverlayPixmap1); - painter.setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing, false); - painter.fillRect(rect, QColor(0, 0, 0, 0x80)); - painter.setPen(QColor(0xff, 0xff, 0xff, 0x80)); - painter.setFont(m_powerOverlayFont); - painter.drawText(QPointF(0, rect.height() - 2.0f), text); - painter.end(); - - m_glShaderPowerOverlay.initTexture(m_powerOverlayPixmap1.toImage()); - - { - GLfloat vtx1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - GLfloat tex1[] = { - 0, 1, - 1, 1, - 1, 0, - 0, 0 - }; - - float shiftX = m_glScopeRect1.width() - ((rect.width() + 4.0f) / width()); - float rectX = m_glScopeRect1.x() + shiftX; - float rectY = 0; - float rectW = rect.width() / (float) width(); - float rectH = rect.height() / (float) height(); - - QMatrix4x4 mat; - mat.setToIdentity(); - mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); - mat.scale(2.0f * rectW, -2.0f * rectH); - m_glShaderPowerOverlay.drawSurface(mat, tex1, vtx1, 4); - -// glPushMatrix(); -// glTranslatef(m_glScopeRect1.x() + shiftX, m_glScopeRect1.y(), 0); -// glScalef(rect.width() / (float) width(), rect.height() / (float) height(), 1); - } -} - -void GLScope::applyConfig() -{ - m_configChanged = false; - - QFontMetrics fm(font()); - int M = fm.width("-"); - - int topMargin = 5; - int botMargin = 20; - int leftMargin = 35; - int rightMargin = 5; - - float pow_floor = -100.0 + m_ofs1 * 100.0; - float pow_range = 100.0 / m_amp1; - float amp1_range = 2.0 / m_amp1; - float amp1_ofs = m_ofs1; - float amp2_range = 2.0 / m_amp2; - float amp2_ofs = m_ofs2; - float t_start = ((m_timeOfsProMill / 1000.0) - m_triggerPre) * ((float) m_displayTrace->size() / m_sampleRates[m_memTraceIndex-m_memTraceHistory]); - float t_len = ((float) m_displayTrace->size() / m_sampleRates[m_memTraceIndex-m_memTraceHistory]) / (float) m_timeBase; - - m_x1Scale.setRange(Unit::Time, t_start, t_start + t_len); - - if (m_mode == ModeIQPolar) - { - if (amp1_range < 2.0) { - m_x2Scale.setRange(Unit::None, - amp1_range * 500.0 + amp1_ofs * 1000.0, amp1_range * 500.0 + amp1_ofs * 1000.0); - } else { - m_x2Scale.setRange(Unit::None, - amp1_range * 0.5 + amp1_ofs, amp1_range * 0.5 + amp1_ofs); - } - } - else - { - m_x2Scale.setRange(Unit::Time, t_start, t_start + t_len); - } - - switch(m_mode) { - case ModeIQ: - case ModeIQPolar: - { - if (amp1_range < 2.0) { - m_y1Scale.setRange(Unit::None, - amp1_range * 500.0 + amp1_ofs * 1000.0, amp1_range * 500.0 + amp1_ofs * 1000.0); - } else { - m_y1Scale.setRange(Unit::None, - amp1_range * 0.5 + amp1_ofs, amp1_range * 0.5 + amp1_ofs); - } - if (amp2_range < 2.0) { - m_y2Scale.setRange(Unit::None, - amp2_range * 500.0 + amp2_ofs * 1000.0, amp2_range * 500.0 + amp2_ofs * 1000.0); - } else { - m_y2Scale.setRange(Unit::None, - amp2_range * 0.5 + amp2_ofs, amp2_range * 0.5 + amp2_ofs); - } - - break; - } - case ModeMagLinPha: - case ModeMagLinDPha: - { - if (amp1_range < 2.0) { - m_y1Scale.setRange(Unit::None, amp1_ofs * 500.0, amp1_range * 1000.0 + amp1_ofs * 500.0); - } else { - m_y1Scale.setRange(Unit::None, amp1_ofs/2.0, amp1_range + amp1_ofs/2.0); - } - - m_y2Scale.setRange(Unit::None, -1.0/m_amp2 + amp2_ofs, 1.0/m_amp2 + amp2_ofs); // Scale to Pi*A2 - - break; - } - case ModeMagdBPha: - case ModeMagdBDPha: - { - m_y1Scale.setRange(Unit::Decibel, pow_floor, pow_floor + pow_range); - m_y2Scale.setRange(Unit::None, -1.0/m_amp2 + amp2_ofs, 1.0/m_amp2 + amp2_ofs); // Scale to Pi*A2 - break; - } - case ModeDerived12: { - if (amp1_range < 2.0) { - m_y1Scale.setRange(Unit::None, 0.0, amp1_range * 1000.0); - } else { - m_y1Scale.setRange(Unit::None, 0.0, amp1_range); - } - if (amp2_range < 2.0) { - m_y2Scale.setRange(Unit::None, - amp2_range * 500.0, amp2_range * 500.0); - } else { - m_y2Scale.setRange(Unit::None, - amp2_range * 0.5, amp2_range * 0.5); - } - break; - } - case ModeCyclostationary: { - if (amp1_range < 2.0) { - m_y1Scale.setRange(Unit::None, 0.0, amp1_range * 1000.0); - } else { - m_y1Scale.setRange(Unit::None, 0.0, amp1_range); - } - if (amp2_range < 2.0) { - m_y2Scale.setRange(Unit::None, - amp2_range * 500.0, amp2_range * 500.0); - } else { - m_y2Scale.setRange(Unit::None, - amp2_range * 0.5, amp2_range * 0.5); - } - break; - } - } - - // QRectF(x, y, w, h); (x, y) = top left corner - - if (m_displays == DisplayBoth) - { - if(m_orientation == Qt::Vertical) { - int scopeHeight = (height() - topMargin) / 2 - botMargin; - int scopeWidth = width() - leftMargin - rightMargin; - - if (m_mode == ModeIQPolar) - { - m_glScopeRect1 = QRectF( - (float) leftMargin / (float) width(), - (float) topMargin / (float) height(), - (float) (width() - 2*leftMargin - rightMargin) / (float) width(), - (float) scopeHeight / (float) height() - ); - m_glScopeMatrix1.setToIdentity(); - m_glScopeMatrix1.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix1.scale ( - (float) 2*(width() - 2*leftMargin - rightMargin) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot1ScaleMatrix.setToIdentity(); - m_glBot1ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot1ScaleMatrix.scale ( - (float) 2*(width() - 2*leftMargin - rightMargin) / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - - m_glRight1ScaleMatrix.setToIdentity(); - m_glRight1ScaleMatrix.translate ( - -1.0f + ((float)(2*(width() - leftMargin)) / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glRight1ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - } - else - { - m_glScopeRect1 = QRectF( - (float) leftMargin / (float) width(), - (float) topMargin / (float) height(), - (float) scopeWidth / (float) width(), - (float) scopeHeight / (float) height() - ); - m_glScopeMatrix1.setToIdentity(); - m_glScopeMatrix1.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix1.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot1ScaleMatrix.setToIdentity(); - m_glBot1ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot1ScaleMatrix.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - - m_glLeft1ScaleMatrix.setToIdentity(); - m_glLeft1ScaleMatrix.translate ( - -1.0f, - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glLeft1ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - { // Y1 scale - m_y1Scale.setSize(scopeHeight); - - m_left1ScalePixmap = QPixmap( - leftMargin - 1, - scopeHeight - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_left1ScalePixmap.fill(Qt::black); - QPainter painter(&m_left1ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_y1Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(leftMargin - M - tick->textSize, topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); - } - } - } - - m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage()); - - } // Y1 scale - { // X1 scale - m_x1Scale.setSize(scopeWidth); - - m_bot1ScalePixmap = QPixmap( - scopeWidth, - botMargin - 1 - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_bot1ScalePixmap.fill(Qt::black); - QPainter painter(&m_bot1ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_x1Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); - } - } - } - - m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage()); - - } // X1 scale - - if (m_mode == ModeIQPolar) - { - int scopeDim = std::min(scopeWidth, scopeHeight); - - m_glScopeRect2 = QRectF( - (float) leftMargin / (float)width(), - (float) (botMargin + topMargin + scopeDim) / (float)height(), - (float) scopeDim / (float)width(), - (float) scopeDim / (float)height() - ); - m_glScopeMatrix2.setToIdentity(); - m_glScopeMatrix2.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(botMargin + topMargin + scopeDim) / (float) height()) - ); - m_glScopeMatrix2.scale ( - (float) 2*scopeDim / (float) width(), - (float) -2*scopeDim / (float) height() - ); - - m_glLeft2ScaleMatrix.setToIdentity(); - m_glLeft2ScaleMatrix.translate ( - -1.0f, - 1.0f - ((float) 2*(topMargin + scopeDim + botMargin) / (float) height()) - ); - m_glLeft2ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeDim / (float) height() - ); - - m_glBot2ScaleMatrix.setToIdentity(); - m_glBot2ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeDim + topMargin + scopeDim + botMargin + 1) / (float) height()) - ); - m_glBot2ScaleMatrix.scale ( - (float) 2*scopeDim / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - else - { - m_glScopeRect2 = QRectF( - (float) leftMargin / (float)width(), - (float) (botMargin + topMargin + scopeHeight) / (float)height(), - (float) scopeWidth / (float)width(), - (float) scopeHeight / (float)height() - ); - m_glScopeMatrix2.setToIdentity(); - m_glScopeMatrix2.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(botMargin + topMargin + scopeHeight) / (float) height()) - ); - m_glScopeMatrix2.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glLeft2ScaleMatrix.setToIdentity(); - m_glLeft2ScaleMatrix.translate ( - -1.0f, - 1.0f - ((float) 2*(topMargin + scopeHeight + botMargin) / (float) height()) - ); - m_glLeft2ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot2ScaleMatrix.setToIdentity(); - m_glBot2ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + scopeHeight + botMargin + 1) / (float) height()) - ); - m_glBot2ScaleMatrix.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - { // Y2 scale - m_y2Scale.setSize(scopeHeight); - - m_left2ScalePixmap = QPixmap( - leftMargin - 1, - scopeHeight - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_left2ScalePixmap.fill(Qt::black); - QPainter painter(&m_left2ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_y2Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(leftMargin - M - tick->textSize, topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); - } - } - } - - m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage()); - - } // Y2 scale - { // X2 scale - if (m_mode == ModeIQPolar) - { - int scopeDim = std::min(scopeWidth, scopeHeight); - - m_x2Scale.setSize(scopeDim); - m_bot2ScalePixmap = QPixmap( - scopeDim, - botMargin - 1 - ); - } - else - { - m_x2Scale.setSize(scopeWidth); - m_bot2ScalePixmap = QPixmap( - scopeWidth, - botMargin - 1 - ); - } - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_bot2ScalePixmap.fill(Qt::black); - QPainter painter(&m_bot2ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_x2Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); - } - } - } - - m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage()); - - } // X2 scale - } - else // Horizontal - { - int scopeHeight = height() - topMargin - botMargin; - int scopeWidth = (width() - rightMargin)/2 - leftMargin; - - if (m_mode == ModeIQPolar) - { - m_glScopeRect1 = QRectF( - (float) leftMargin / (float) width(), - (float) topMargin / (float) height(), - (float) (scopeWidth-leftMargin) / (float) width(), - (float) scopeHeight / (float) height() - ); - m_glScopeMatrix1.setToIdentity(); - m_glScopeMatrix1.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix1.scale ( - (float) 2*(scopeWidth-leftMargin) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot1ScaleMatrix.setToIdentity(); - m_glBot1ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot1ScaleMatrix.scale ( - (float) 2*(scopeWidth-leftMargin) / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - - m_glRight1ScaleMatrix.setToIdentity(); - m_glRight1ScaleMatrix.translate ( - -1.0f + ((float) 2*scopeWidth / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glRight1ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - } - else - { - m_glScopeRect1 = QRectF( - (float) leftMargin / (float) width(), - (float) topMargin / (float) height(), - (float) scopeWidth / (float) width(), - (float) scopeHeight / (float) height() - ); - m_glScopeMatrix1.setToIdentity(); - m_glScopeMatrix1.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix1.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot1ScaleMatrix.setToIdentity(); - m_glBot1ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot1ScaleMatrix.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - - m_glLeft1ScaleMatrix.setToIdentity(); - m_glLeft1ScaleMatrix.translate ( - -1.0f, - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glLeft1ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - { // Y1 scale - m_y1Scale.setSize(scopeHeight); - - m_left1ScalePixmap = QPixmap( - leftMargin - 1, - scopeHeight - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_left1ScalePixmap.fill(Qt::black); - QPainter painter(&m_left1ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_y1Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(leftMargin - M - tick->textSize, topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); - } - } - } - - m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage()); - - } // Y1 scale - { // X1 scale - m_x1Scale.setSize(scopeWidth); - - m_bot1ScalePixmap = QPixmap( - scopeWidth, - botMargin - 1 - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_bot1ScalePixmap.fill(Qt::black); - QPainter painter(&m_bot1ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_x1Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); - } - } - } - - m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage()); - - } // X1 scale - - if (m_mode == ModeIQPolar) - { - int scopeDim = std::min(scopeWidth, scopeHeight); - - m_glScopeRect2 = QRectF( - (float)(leftMargin + scopeWidth + leftMargin) / (float)width(), - (float)topMargin / (float)height(), - (float) scopeDim / (float)width(), - (float)(height() - topMargin - botMargin) / (float)height() - ); - m_glScopeMatrix2.setToIdentity(); - m_glScopeMatrix2.translate ( - -1.0f + ((float) 2*(leftMargin + scopeWidth + leftMargin) / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix2.scale ( - (float) 2*scopeDim / (float) width(), - (float) -2*(height() - topMargin - botMargin) / (float) height() - ); - - m_glLeft2ScaleMatrix.setToIdentity(); - m_glLeft2ScaleMatrix.translate ( - -1.0f + (float) 2*(leftMargin + scopeWidth) / (float) width(), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glLeft2ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot2ScaleMatrix.setToIdentity(); - m_glBot2ScaleMatrix.translate ( - -1.0f + ((float) 2*(leftMargin + leftMargin + scopeWidth) / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot2ScaleMatrix.scale ( - (float) 2*scopeDim / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - else - { - m_glScopeRect2 = QRectF( - (float)(leftMargin + leftMargin + ((width() - leftMargin - leftMargin - rightMargin) / 2)) / (float)width(), - (float)topMargin / (float)height(), - (float)((width() - leftMargin - leftMargin - rightMargin) / 2) / (float)width(), - (float)(height() - topMargin - botMargin) / (float)height() - ); - m_glScopeMatrix2.setToIdentity(); - m_glScopeMatrix2.translate ( - -1.0f + ((float) 2*(leftMargin + leftMargin + ((width() - leftMargin - leftMargin - rightMargin) / 2)) / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix2.scale ( - (float) 2*((width() - leftMargin - leftMargin - rightMargin) / 2) / (float) width(), - (float) -2*(height() - topMargin - botMargin) / (float) height() - ); - - m_glLeft2ScaleMatrix.setToIdentity(); - m_glLeft2ScaleMatrix.translate ( - -1.0f + (float) 2*(leftMargin + scopeWidth) / (float) width(), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glLeft2ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot2ScaleMatrix.setToIdentity(); - m_glBot2ScaleMatrix.translate ( - -1.0f + ((float) 2*(leftMargin + leftMargin + scopeWidth) / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot2ScaleMatrix.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - { // Y2 scale - m_y2Scale.setSize(scopeHeight); - - m_left2ScalePixmap = QPixmap( - leftMargin - 1, - scopeHeight - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_left2ScalePixmap.fill(Qt::black); - QPainter painter(&m_left2ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_y2Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(leftMargin - M - tick->textSize, topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); - } - } - } - - m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage()); - - } // Y2 scale - { // X2 scale - if (m_mode == ModeIQPolar) - { - int scopeDim = std::min(scopeWidth, scopeHeight); - - m_x2Scale.setSize(scopeDim); - m_bot2ScalePixmap = QPixmap( - scopeDim, - botMargin - 1 - ); - } - else - { - m_x2Scale.setSize(scopeWidth); - m_bot2ScalePixmap = QPixmap( - scopeWidth, - botMargin - 1 - ); - } - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_bot2ScalePixmap.fill(Qt::black); - QPainter painter(&m_bot2ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_x2Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); - } - } - } - - m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage()); - - } // X2 scale - } - } // Both displays - else if (m_displays == DisplayFirstOnly) - { - int scopeHeight = height() - topMargin - botMargin; - int scopeWidth = width() - leftMargin - rightMargin; - - if (m_mode == ModeIQPolar) - { - m_glScopeRect1 = QRectF( - (float) leftMargin / (float) width(), - (float) topMargin / (float) height(), - (float) (scopeWidth-leftMargin) / (float) width(), - (float) scopeHeight / (float) height() - ); - m_glScopeMatrix1.setToIdentity(); - m_glScopeMatrix1.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix1.scale ( - (float) 2*(scopeWidth-leftMargin) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot1ScaleMatrix.setToIdentity(); - m_glBot1ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot1ScaleMatrix.scale ( - (float) 2*(scopeWidth-leftMargin) / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - - m_glRight1ScaleMatrix.setToIdentity(); - m_glRight1ScaleMatrix.translate ( - -1.0f + ((float) 2*(width() - leftMargin) / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glRight1ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - } - else - { - m_glScopeRect1 = QRectF( - (float) leftMargin / (float) width(), - (float) topMargin / (float) height(), - (float) scopeWidth / (float) width(), - (float) scopeHeight / (float) height() - ); - m_glScopeMatrix1.setToIdentity(); - m_glScopeMatrix1.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix1.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot1ScaleMatrix.setToIdentity(); - m_glBot1ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot1ScaleMatrix.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - - m_glLeft1ScaleMatrix.setToIdentity(); - m_glLeft1ScaleMatrix.translate ( - -1.0f, - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glLeft1ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - { // Y1 scale - m_y1Scale.setSize(scopeHeight); - - m_left1ScalePixmap = QPixmap( - leftMargin - 1, - scopeHeight - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_left1ScalePixmap.fill(Qt::black); - QPainter painter(&m_left1ScalePixmap); - if (m_mode == ModeIQPolar) { - painter.setPen(QColor(0xff, 0xff, 0x80)); - } else { - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - } - painter.setFont(font()); - tickList = &m_y1Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(leftMargin - M - tick->textSize, topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); - } - } - } - - m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage()); - - } // Y1 scale - if (m_mode == ModeIQPolar) { // Y2 scale - m_y2Scale.setSize(scopeHeight); - - m_left2ScalePixmap = QPixmap( - leftMargin - 1, - scopeHeight - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_left2ScalePixmap.fill(Qt::black); - QPainter painter(&m_left2ScalePixmap); - painter.setPen(QColor(0x80, 0xff, 0xff)); - painter.setFont(font()); - tickList = &m_y2Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(leftMargin - M - tick->textSize, topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); - } - } - } - - m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage()); - - } // Y2 scale - { // X1 scale - m_x1Scale.setSize(scopeWidth); - - m_bot1ScalePixmap = QPixmap( - scopeWidth, - botMargin - 1 - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_bot1ScalePixmap.fill(Qt::black); - QPainter painter(&m_bot1ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_x1Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); - } - } - } - - m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage()); - - } // X1 scale - } // Primary display only - else if (m_displays == DisplaySecondOnly) - { - int scopeHeight = height() - topMargin - botMargin; - int scopeWidth = width() - leftMargin - rightMargin; - - if (m_mode == ModeIQPolar) - { - int scopeDim = std::min(scopeWidth, scopeHeight); - - m_glScopeRect2 = QRectF( - (float) leftMargin / (float) width(), - (float) topMargin / (float) height(), - (float) scopeDim / (float) width(), - (float) scopeDim / (float) height() - ); - m_glScopeMatrix2.setToIdentity(); - m_glScopeMatrix2.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix2.scale ( - (float) 2*scopeDim / (float) width(), - (float) -2*scopeDim / (float) height() - ); - - m_glLeft2ScaleMatrix.setToIdentity(); - m_glLeft2ScaleMatrix.translate ( - -1.0f, - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glLeft2ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeDim / (float) height() - ); - - m_glBot2ScaleMatrix.setToIdentity(); - m_glBot2ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeDim + topMargin + 1) / (float) height()) - ); - m_glBot2ScaleMatrix.scale ( - (float) 2*scopeDim / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - else - { - m_glScopeRect2 = QRectF( - (float) leftMargin / (float) width(), - (float) topMargin / (float) height(), - (float) scopeWidth / (float) width(), - (float) scopeHeight / (float) height() - ); - m_glScopeMatrix2.setToIdentity(); - m_glScopeMatrix2.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glScopeMatrix2.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glLeft2ScaleMatrix.setToIdentity(); - m_glLeft2ScaleMatrix.translate ( - -1.0f, - 1.0f - ((float) 2*topMargin / (float) height()) - ); - m_glLeft2ScaleMatrix.scale ( - (float) 2*(leftMargin-1) / (float) width(), - (float) -2*scopeHeight / (float) height() - ); - - m_glBot2ScaleMatrix.setToIdentity(); - m_glBot2ScaleMatrix.translate ( - -1.0f + ((float) 2*leftMargin / (float) width()), - 1.0f - ((float) 2*(scopeHeight + topMargin + 1) / (float) height()) - ); - m_glBot2ScaleMatrix.scale ( - (float) 2*scopeWidth / (float) width(), - (float) -2*(botMargin - 1) / (float) height() - ); - } - - { // Y2 scale - m_y2Scale.setSize(scopeHeight); - - m_left2ScalePixmap = QPixmap( - leftMargin - 1, - scopeHeight - ); - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_left2ScalePixmap.fill(Qt::black); - QPainter painter(&m_left2ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_y2Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(leftMargin - M - tick->textSize, topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text); - } - } - } - - m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage()); - - } // Y2 scale - { // X2 scale - if (m_mode == ModeIQPolar) - { - int scopeDim = std::min(scopeWidth, scopeHeight); - - m_x2Scale.setSize(scopeDim); - m_bot2ScalePixmap = QPixmap( - scopeDim, - botMargin - 1 - ); - } - else - { - m_x2Scale.setSize(scopeWidth); - m_bot2ScalePixmap = QPixmap( - scopeWidth, - botMargin - 1 - ); - } - - const ScaleEngine::TickList* tickList; - const ScaleEngine::Tick* tick; - - m_bot2ScalePixmap.fill(Qt::black); - QPainter painter(&m_bot2ScalePixmap); - painter.setPen(QColor(0xf0, 0xf0, 0xff)); - painter.setFont(font()); - tickList = &m_x2Scale.getTickList(); - - for(int i = 0; i < tickList->count(); i++) { - tick = &(*tickList)[i]; - if(tick->major) { - if(tick->textSize > 0) { - painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text); - } - } - } - - m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage()); - - } // X2 scale - } // Secondary display only - - m_q3TickY1.allocate(4*m_y1Scale.getTickList().count()); - m_q3TickY2.allocate(4*m_y2Scale.getTickList().count()); - m_q3TickX1.allocate(4*m_x1Scale.getTickList().count()); - m_q3TickX2.allocate(4*m_x2Scale.getTickList().count()); -} - -void GLScope::applyTraceConfig(uint32_t size) -{ - m_q3Trace.allocate(2*size); -} - -void GLScope::tick() -{ - if(m_dataChanged) - update(); -} - -void GLScope::setTriggerChannel(ScopeVis::TriggerChannel triggerChannel) -{ - m_triggerChannel = triggerChannel; -} - -void GLScope::setTriggerLevel(Real triggerLevel) -{ - qDebug("GLScope::setTriggerLevel: %f", triggerLevel); - m_triggerLevel = triggerLevel; -} - -void GLScope::setTriggerPre(Real triggerPre) -{ - m_triggerPre = triggerPre; - m_configChanged = true; - update(); -} - -void GLScope::setMemHistoryShift(int value) -{ - if (value < m_memTraceIndexMax) - { - m_memTraceHistory = value; - m_configChanged = true; - update(); - } -} - -void GLScope::connectTimer(const QTimer& timer) -{ - qDebug() << "GLScope::connectTimer"; - disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); - connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); - m_timer.stop(); -} - -void GLScope::cleanup() -{ - //makeCurrent(); - m_glShaderSimple.cleanup(); - m_glShaderBottom1Scale.cleanup(); - m_glShaderBottom2Scale.cleanup(); - m_glShaderLeft1Scale.cleanup(); - m_glShaderPowerOverlay.cleanup(); - //doneCurrent(); -} diff --git a/sdrgui/gui/glscope.h b/sdrgui/gui/glscope.h deleted file mode 100644 index 6e0e1ce30..000000000 --- a/sdrgui/gui/glscope.h +++ /dev/null @@ -1,193 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2016 F4EXB // -// written by Edouard Griffiths // -// // -// OpenGL interface modernization. // -// See: http://doc.qt.io/qt-5/qopenglshaderprogram.html // -// // -// 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_GLSCOPE_H -#define INCLUDE_GLSCOPE_H - -#include -#include -#include -#include -#include -#include -#include "dsp/dsptypes.h" -#include "dsp/scopevis.h" -#include "gui/scaleengine.h" -#include "gui/glshadersimple.h" -#include "gui/glshadertextured.h" -#include "export.h" -#include "util/bitfieldindex.h" -#include "util/incrementalarray.h" - -class ScopeVis; -class QPainter; - -class SDRGUI_API GLScope: public QGLWidget { - Q_OBJECT - -public: - enum Mode { - ModeIQ, - ModeMagLinPha, - ModeMagdBPha, - ModeMagLinDPha, - ModeMagdBDPha, - ModeDerived12, - ModeCyclostationary, - ModeIQPolar - }; - - enum Displays { - DisplayBoth, - DisplayFirstOnly, - DisplaySecondOnly - }; - - GLScope(QWidget* parent = NULL); - ~GLScope(); - -// void setDSPEngine(DSPEngine* dspEngine); - void setAmp1(Real amp); - void setAmp1Ofs(Real ampOfs); - void setAmp2(Real amp); - void setAmp2Ofs(Real ampOfs); - void setTimeBase(int timeBase); - void setTimeOfsProMill(int timeOfsProMill); - void setMode(Mode mode); - void setDisplays(Displays displays); - void setOrientation(Qt::Orientation orientation); - void setDisplayGridIntensity(int intensity); - void setDisplayTraceIntensity(int intensity); - void setTriggerChannel(ScopeVis::TriggerChannel triggerChannel); - void setTriggerLevel(Real triggerLevel); - void setTriggerPre(Real triggerPre); - void setMemHistoryShift(int value); - - void newTrace(const std::vector& trace, int sampleRate); - int getTraceSize() const { return m_rawTrace[m_memTraceIndex - m_memTraceHistory].size(); } - - void setSampleRate(int sampleRate); - int getSampleRate() const { return m_sampleRates[m_memTraceIndex - m_memTraceHistory]; } - Mode getDataMode() const { return m_mode; } - void connectTimer(const QTimer& timer); - - static const int m_memHistorySizeLog2 = 5; - -signals: - void traceSizeChanged(int); - void sampleRateChanged(int); - -private: - // state - QTimer m_timer; - QMutex m_mutex; - bool m_dataChanged; - bool m_configChanged; - Mode m_mode; - Displays m_displays; - Qt::Orientation m_orientation; - - // traces - std::vector m_rawTrace[1< m_memTraceIndex; //!< current index of trace being written - BitfieldIndex m_memTraceHistory; //!< trace index shift into history - int m_memTraceIndexMax; - bool m_memTraceRecall; - std::vector m_mathTrace; - std::vector* m_displayTrace; - std::vector m_powTrace; - Real m_maxPow; - Real m_sumPow; - int m_oldTraceSize; - int m_sampleRate; - Real m_amp1; - Real m_amp2; - Real m_ofs1; - Real m_ofs2; - - // config - int m_timeBase; - int m_timeOfsProMill; - ScopeVis::TriggerChannel m_triggerChannel; - Real m_triggerLevel; - Real m_triggerPre; - Real m_triggerLevelDis1; - Real m_triggerLevelDis2; - int m_nbPow; - Real m_prevArg; - - // graphics stuff - QRectF m_glScopeRect1; - QRectF m_glScopeRect2; - QMatrix4x4 m_glScopeMatrix1; - QMatrix4x4 m_glScopeMatrix2; - QMatrix4x4 m_glLeft1ScaleMatrix; - QMatrix4x4 m_glRight1ScaleMatrix; - QMatrix4x4 m_glLeft2ScaleMatrix; - QMatrix4x4 m_glBot1ScaleMatrix; - QMatrix4x4 m_glBot2ScaleMatrix; - - QPixmap m_left1ScalePixmap; - QPixmap m_left2ScalePixmap; - QPixmap m_bot1ScalePixmap; - QPixmap m_bot2ScalePixmap; - QPixmap m_powerOverlayPixmap1; - - int m_displayGridIntensity; - int m_displayTraceIntensity; - - ScaleEngine m_x1Scale; - ScaleEngine m_x2Scale; - ScaleEngine m_y1Scale; - ScaleEngine m_y2Scale; - - QFont m_powerOverlayFont; - - GLShaderSimple m_glShaderSimple; - GLShaderTextured m_glShaderLeft1Scale; - GLShaderTextured m_glShaderBottom1Scale; - GLShaderTextured m_glShaderLeft2Scale; - GLShaderTextured m_glShaderBottom2Scale; - GLShaderTextured m_glShaderPowerOverlay; - - IncrementalArray m_q3Trace; - IncrementalArray m_q3TickY1; - IncrementalArray m_q3TickY2; - IncrementalArray m_q3TickX1; - IncrementalArray m_q3TickX2; - - void initializeGL(); - void resizeGL(int width, int height); - void paintGL(); - - void mousePressEvent(QMouseEvent*); - - void handleMode(); - void applyConfig(); - void applyTraceConfig(uint32_t size); - void drawPowerOverlay(); - -protected slots: - void cleanup(); - void tick(); -}; - -#endif // INCLUDE_GLSCOPE_H diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp deleted file mode 100644 index 777aed8c6..000000000 --- a/sdrgui/gui/glscopegui.cpp +++ /dev/null @@ -1,919 +0,0 @@ -#include "gui/glscopegui.h" -#include "dsp/scopevis.h" -#include "dsp/dspcommands.h" -#include "gui/glscope.h" -#include "util/simpleserializer.h" -#include "ui_glscopegui.h" - -#include - -const qreal GLScopeGUI::amps[11] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001 }; - -GLScopeGUI::GLScopeGUI(QWidget* parent) : - QWidget(parent), - ui(new Ui::GLScopeGUI), - m_messageQueue(0), - m_scopeVis(0), - m_glScope(0), - m_sampleRate(1), - m_displayData(GLScope::ModeIQ), - m_displayOrientation(Qt::Horizontal), - m_displays(GLScope::DisplayBoth), - m_timeBase(1), - m_timeOffset(0), - m_amplification1(0), - m_amp1OffsetCoarse(0), - m_amp1OffsetFine(0), - m_amplification2(0), - m_amp2OffsetCoarse(0), - m_amp2OffsetFine(0), - m_displayGridIntensity(1), - m_displayTraceIntensity(50), - m_triggerIndex(0), - m_triggerPre(0), - m_traceLenMult(20) -{ - for (unsigned int i = 0; i < ScopeVis::m_nbTriggers; i++) - { - m_triggerChannel[i] = ScopeVis::TriggerFreeRun; - m_triggerLevelCoarse[i] = 0; - m_triggerLevelFine[i] = 0; - m_triggerPositiveEdge[i] = true; - m_triggerBothEdges[i] = false; - m_triggerDelay[i] = 0; - m_triggerCounts[i] = 0; - } - - ui->setupUi(this); -} - -GLScopeGUI::~GLScopeGUI() -{ - delete ui; -} - -void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVis* scopeVis, GLScope* glScope) -{ - m_messageQueue = messageQueue; - m_scopeVis = scopeVis; - m_glScope = glScope; - ui->memIndex->setMaximum((1<trigPre->setValue(m_triggerPre); - setTrigPreDisplay(); - d.readS32(14, &m_traceLenMult, 20); - ui->traceLen->setValue(m_traceLenMult); - setTraceLenDisplay(); - setTrigDelayDisplay(); - d.readS32(18, &m_amp1OffsetFine, 0); - d.readS32(19, &m_amplification2, 0); - d.readS32(20, &m_amp2OffsetCoarse, 0); - d.readS32(21, &m_amp2OffsetFine, 0); - - for (unsigned int i = 0; i < ScopeVis::m_nbTriggers; i++) - { - d.readS32(50 + 10*i, &m_triggerChannel[i], ScopeVis::TriggerFreeRun); - d.readS32(51 + 10*i, &m_triggerLevelCoarse[i], 0); - d.readS32(52 + 10*i, &m_triggerLevelFine[i], 0); - d.readBool(53 + 10*i, &m_triggerPositiveEdge[i], true); - d.readBool(54 + 10*i, &m_triggerBothEdges[i], false); - d.readS32(55 + 10*i, &m_triggerDelay[i], 0); - d.readS32(56 + 10*i, &m_triggerCounts[i], 0); - m_triggerIndex = i; - applyTriggerSettings(); - } - - m_triggerIndex = 0; - - setTrigUI(m_triggerIndex); - setTrigLevelDisplay(); - applySettings(); - return true; - } else { - resetToDefaults(); - return false; - } -} - -void GLScopeGUI::applyAllTriggerSettings() -{ - quint32 currentTriggerIndex = m_triggerIndex; - - for (unsigned int i = 0; i < ScopeVis::m_nbTriggers; i++) - { - m_triggerIndex = i; - applyTriggerSettings(); - } - - m_triggerIndex = currentTriggerIndex; -} - -void GLScopeGUI::setTrigUI(uint index) -{ - index %= ScopeVis::m_nbTriggers; - - ui->trigMode->setCurrentIndex(m_triggerChannel[index]); - ui->trigLevelCoarse->setValue(m_triggerLevelCoarse[index]); - ui->trigLevelFine->setValue(m_triggerLevelFine[index]); - ui->trigDelay->setValue(m_triggerDelay[index]); - ui->trigCount->setValue(m_triggerCounts[index]); - - if (m_triggerBothEdges[index]) { - ui->slopePos->setChecked(false); - ui->slopeNeg->setChecked(false); - ui->slopeBoth->setChecked(true); - } else { - ui->slopeBoth->setChecked(false); - ui->slopePos->setChecked(m_triggerPositiveEdge[index]); - ui->slopeNeg->setChecked(!m_triggerPositiveEdge[index]); - } -} - -void GLScopeGUI::applySettings() -{ - ui->dataMode->setCurrentIndex(m_displayData); - if (m_displays == GLScope::DisplayBoth) - { - if(m_displayOrientation == Qt::Horizontal) { - m_glScope->setOrientation(Qt::Horizontal); - ui->horizView->setChecked(true); - ui->vertView->setChecked(false); - ui->onlyPrimeView->setChecked(false); - ui->onlySecondView->setChecked(false); - } else { - m_glScope->setOrientation(Qt::Vertical); - ui->horizView->setChecked(false); - ui->vertView->setChecked(true); - ui->onlyPrimeView->setChecked(false); - ui->onlySecondView->setChecked(false); - } - } - else if (m_displays == GLScope::DisplayFirstOnly) - { - m_glScope->setDisplays(GLScope::DisplayFirstOnly); - ui->onlyPrimeView->setChecked(true); - ui->horizView->setChecked(false); - ui->vertView->setChecked(false); - ui->onlySecondView->setChecked(false); - } - else if (m_displays == GLScope::DisplaySecondOnly) - { - m_glScope->setDisplays(GLScope::DisplaySecondOnly); - ui->onlySecondView->setChecked(true); - ui->onlyPrimeView->setChecked(false); - ui->horizView->setChecked(false); - ui->vertView->setChecked(false); - } - ui->time->setValue(m_timeBase); - ui->timeOfs->setValue(m_timeOffset); - ui->amp1->setValue(m_amplification1); - ui->amp1OfsCoarse->setValue(m_amp1OffsetCoarse); - ui->amp1OfsFine->setValue(m_amp1OffsetFine); - ui->amp2->setValue(m_amplification2); - ui->amp2OfsCoarse->setValue(m_amp2OffsetCoarse); - ui->amp2OfsFine->setValue(m_amp2OffsetFine); - ui->gridIntensity->setSliderPosition(m_displayGridIntensity); - ui->traceIntensity->setSliderPosition(m_displayTraceIntensity); -} - -void GLScopeGUI::applyTriggerSettings() -{ - qreal t = (m_triggerLevelCoarse[m_triggerIndex] / 100.0) + (m_triggerLevelFine[m_triggerIndex] / 20000.0); // [-1.0, 1.0] - qreal triggerLevel; - quint32 preTriggerSamples = (m_glScope->getTraceSize() * m_triggerPre) / 100; - - if (m_triggerChannel[m_triggerIndex] == ScopeVis::TriggerMagDb) { - triggerLevel = 100.0 * (t - 1.0); // [-200.0, 0.0] - } - else if (m_triggerChannel[m_triggerIndex] == ScopeVis::TriggerMagLin) { - triggerLevel = 1.0 + t; // [0.0, 2.0] - } - else { - triggerLevel = t; // [-1.0, 1.0] - } - - m_glScope->setTriggerChannel((ScopeVis::TriggerChannel) m_triggerChannel[m_triggerIndex]); - m_glScope->setTriggerLevel(t); // [-1.0, 1.0] - m_glScope->setTriggerPre(m_triggerPre/100.0); // [0.0, 1.0] - - m_scopeVis->configure(m_messageQueue, - m_triggerIndex, - (ScopeVis::TriggerChannel) m_triggerChannel[m_triggerIndex], - triggerLevel, - m_triggerPositiveEdge[m_triggerIndex], - m_triggerBothEdges[m_triggerIndex], - preTriggerSamples, - m_triggerDelay[m_triggerIndex], - m_triggerCounts[m_triggerIndex], - m_traceLenMult * ScopeVis::m_traceChunkSize); -} - -void GLScopeGUI::setTrigLevelDisplay() -{ - qreal t = (m_triggerLevelCoarse[m_triggerIndex] / 100.0) + (m_triggerLevelFine[m_triggerIndex] / 20000.0); - - ui->trigLevelCoarse->setToolTip(QString("Trigger level coarse: %1 %").arg(m_triggerLevelCoarse[m_triggerIndex])); - ui->trigLevelFine->setToolTip(QString("Trigger level fine: %1 ppm").arg(m_triggerLevelFine[m_triggerIndex] * 50)); - - if (m_triggerChannel[m_triggerIndex] == ScopeVis::TriggerMagDb) { - ui->trigText->setText(tr("%1\ndB").arg(100.0 * (t - 1.0), 0, 'f', 1)); - } - else - { - qreal a; - - if (m_triggerChannel[m_triggerIndex] == ScopeVis::TriggerMagLin) { - a = 1.0 + t; - } else { - a = t; - } - - if(fabs(a) < 0.000001) - ui->trigText->setText(tr("%1\nn").arg(a * 1000000000.0)); - else if(fabs(a) < 0.001) - ui->trigText->setText(tr("%1\nµ").arg(a * 1000000.0)); - else if(fabs(a) < 1.0) - ui->trigText->setText(tr("%1\nm").arg(a * 1000.0)); - else - ui->trigText->setText(tr("%1").arg(a * 1.0)); - } -} - -void GLScopeGUI::setAmp1ScaleDisplay() -{ - if ((m_glScope->getDataMode() == GLScope::ModeMagdBPha) || (m_glScope->getDataMode() == GLScope::ModeMagdBDPha)) - { - ui->amp1Text->setText(tr("%1\ndB").arg(amps[m_amplification1]*500.0, 0, 'f', 1)); - } - else - { - qreal a = amps[m_amplification1]*10.0; - - if(a < 0.000001) - ui->amp1Text->setText(tr("%1\nn").arg(a * 1000000000.0)); - else if(a < 0.001) - ui->amp1Text->setText(tr("%1\nµ").arg(a * 1000000.0)); - else if(a < 1.0) - ui->amp1Text->setText(tr("%1\nm").arg(a * 1000.0)); - else - ui->amp1Text->setText(tr("%1").arg(a * 1.0)); - } -} - -void GLScopeGUI::setAmp2ScaleDisplay() -{ - if ((m_glScope->getDataMode() == GLScope::ModeMagdBPha) - || (m_glScope->getDataMode() == GLScope::ModeMagdBDPha) - || (m_glScope->getDataMode() == GLScope::ModeMagLinPha) - || (m_glScope->getDataMode() == GLScope::ModeMagLinDPha)) - { - ui->amp2Text->setText(tr("%1").arg(amps[m_amplification2]*5.0, 0, 'f', 3)); - } - else - { - qreal a = amps[m_amplification2]*10.0; - - if(a < 0.000001) - ui->amp2Text->setText(tr("%1\nn").arg(a * 1000000000.0)); - else if(a < 0.001) - ui->amp2Text->setText(tr("%1\nµ").arg(a * 1000000.0)); - else if(a < 1.0) - ui->amp2Text->setText(tr("%1\nm").arg(a * 1000.0)); - else - ui->amp2Text->setText(tr("%1").arg(a * 1.0)); - } -} - -void GLScopeGUI::setAmp1OfsDisplay() -{ - qreal o = (m_amp1OffsetCoarse * 10.0) + (m_amp1OffsetFine / 20.0); - - if ((m_glScope->getDataMode() == GLScope::ModeMagdBPha) || (m_glScope->getDataMode() == GLScope::ModeMagdBDPha)) - { - ui->amp1OfsText->setText(tr("%1\ndB").arg(o/10.0 - 100.0, 0, 'f', 1)); - } - else - { - qreal a; - - if ((m_glScope->getDataMode() == GLScope::ModeMagLinPha) || (m_glScope->getDataMode() == GLScope::ModeMagLinDPha)) - { - a = o/2000.0; - } - else - { - a = o/1000.0; - } - - if(fabs(a) < 0.000001) - ui->amp1OfsText->setText(tr("%1\nn").arg(a * 1000000000.0)); - else if(fabs(a) < 0.001) - ui->amp1OfsText->setText(tr("%1\nµ").arg(a * 1000000.0)); - else if(fabs(a) < 1.0) - ui->amp1OfsText->setText(tr("%1\nm").arg(a * 1000.0)); - else - ui->amp1OfsText->setText(tr("%1").arg(a * 1.0)); - } -} - -void GLScopeGUI::setAmp2OfsDisplay() -{ - qreal o = (m_amp2OffsetCoarse * 10.0) + (m_amp2OffsetFine / 20.0); - - if ((m_glScope->getDataMode() == GLScope::ModeMagdBPha) - || (m_glScope->getDataMode() == GLScope::ModeMagdBDPha) - || (m_glScope->getDataMode() == GLScope::ModeMagLinPha) - || (m_glScope->getDataMode() == GLScope::ModeMagLinDPha)) - { - ui->amp2OfsText->setText(tr("%1").arg(o/1000.0, 0, 'f', 4)); - } - else - { - qreal a = o/1000.0; - - if(fabs(a) < 0.000001) - ui->amp2OfsText->setText(tr("%1\nn").arg(a * 1000000000.0)); - else if(fabs(a) < 0.001) - ui->amp2OfsText->setText(tr("%1\nµ").arg(a * 1000000.0)); - else if(fabs(a) < 1.0) - ui->amp2OfsText->setText(tr("%1\nm").arg(a * 1000.0)); - else - ui->amp2OfsText->setText(tr("%1").arg(a * 1.0)); - } -} - -void GLScopeGUI::on_amp1_valueChanged(int value) -{ - m_amplification1 = value; - setAmp1ScaleDisplay(); - m_glScope->setAmp1(0.2 / amps[m_amplification1]); -} - -void GLScopeGUI::on_amp1OfsCoarse_valueChanged(int value) -{ - m_amp1OffsetCoarse = value; - setAmp1OfsDisplay(); - qreal o = (m_amp1OffsetCoarse * 10.0) + (m_amp1OffsetFine / 20.0); - m_glScope->setAmp1Ofs(o/1000.0); // scale to [-1.0,1.0] -} - -void GLScopeGUI::on_amp1OfsFine_valueChanged(int value) -{ - m_amp1OffsetFine = value; - setAmp1OfsDisplay(); - qreal o = (m_amp1OffsetCoarse * 10.0) + (m_amp1OffsetFine / 20.0); - m_glScope->setAmp1Ofs(o/1000.0); // scale to [-1.0,1.0] -} - -void GLScopeGUI::on_amp2_valueChanged(int value) -{ - m_amplification2 = value; - setAmp2ScaleDisplay(); - m_glScope->setAmp2(0.2 / amps[m_amplification2]); -} - -void GLScopeGUI::on_amp2OfsCoarse_valueChanged(int value) -{ - m_amp2OffsetCoarse = value; - setAmp2OfsDisplay(); - qreal o = (m_amp2OffsetCoarse * 10.0) + (m_amp2OffsetFine / 20.0); - m_glScope->setAmp2Ofs(o/1000.0); // scale to [-1.0,1.0] -} - -void GLScopeGUI::on_amp2OfsFine_valueChanged(int value) -{ - m_amp2OffsetFine = value; - setAmp2OfsDisplay(); - qreal o = (m_amp2OffsetCoarse * 10.0) + (m_amp2OffsetFine / 20.0); - m_glScope->setAmp2Ofs(o/1000.0); // scale to [-1.0,1.0] -} - -void GLScopeGUI::on_scope_traceSizeChanged(int) -{ - setTimeScaleDisplay(); - setTraceLenDisplay(); - setTimeOfsDisplay(); - setTrigPreDisplay(); - setTrigDelayDisplay(); - applySettings(); - - if (m_triggerPre > 0) { - applyAllTriggerSettings(); // change of trace size changes number of pre-trigger samples - } -} - -void GLScopeGUI::on_scope_sampleRateChanged(int) -{ - m_sampleRate = m_glScope->getSampleRate(); - ui->sampleRateText->setText(tr("%1\nkS/s").arg(m_sampleRate / 1000.0f, 0, 'f', 2)); - setTimeScaleDisplay(); - setTraceLenDisplay(); - setTimeOfsDisplay(); - setTrigPreDisplay(); - setTrigDelayDisplay(); - applySettings(); -} - -void GLScopeGUI::setTimeScaleDisplay() -{ - m_sampleRate = m_glScope->getSampleRate(); - double t = (m_glScope->getTraceSize() * 1.0 / m_sampleRate) / (qreal)m_timeBase; - - if(t < 0.000001) - { - t = round(t * 100000000000.0) / 100.0; - ui->timeText->setText(tr("%1\nns").arg(t)); - } - else if(t < 0.001) - { - t = round(t * 100000000.0) / 100.0; - ui->timeText->setText(tr("%1\nµs").arg(t)); - } - else if(t < 1.0) - { - t = round(t * 100000.0) / 100.0; - ui->timeText->setText(tr("%1\nms").arg(t)); - } - else - { - t = round(t * 100.0) / 100.0; - ui->timeText->setText(tr("%1\ns").arg(t)); - } -} - -void GLScopeGUI::setTraceLenDisplay() -{ - uint n_samples = m_traceLenMult * ScopeVis::m_traceChunkSize; - - if (n_samples < 1000) { - ui->traceLenText->setToolTip(tr("%1S").arg(n_samples)); - } else if (n_samples < 1000000) { - ui->traceLenText->setToolTip(tr("%1kS").arg(n_samples/1000.0)); - } else { - ui->traceLenText->setToolTip(tr("%1MS").arg(n_samples/1000000.0)); - } - - m_sampleRate = m_glScope->getSampleRate(); - qreal t = (m_glScope->getTraceSize() * 1.0 / m_sampleRate); - - if(t < 0.000001) - ui->traceLenText->setText(tr("%1\nns").arg(t * 1000000000.0)); - else if(t < 0.001) - ui->traceLenText->setText(tr("%1\nµs").arg(t * 1000000.0)); - else if(t < 1.0) - ui->traceLenText->setText(tr("%1\nms").arg(t * 1000.0)); - else - ui->traceLenText->setText(tr("%1\ns").arg(t * 1.0)); -} - -void GLScopeGUI::setTrigDelayDisplay() -{ - uint n_samples_delay = m_traceLenMult * ScopeVis::m_traceChunkSize * m_triggerDelay[m_triggerIndex]; - - if (n_samples_delay < 1000) { - ui->trigDelayText->setToolTip(tr("%1S").arg(n_samples_delay)); - } else if (n_samples_delay < 1000000) { - ui->trigDelayText->setToolTip(tr("%1kS").arg(n_samples_delay/1000.0)); - } else if (n_samples_delay < 1000000000) { - ui->trigDelayText->setToolTip(tr("%1MS").arg(n_samples_delay/1000000.0)); - } else { - ui->trigDelayText->setToolTip(tr("%1GS").arg(n_samples_delay/1000000000.0)); - } - - m_sampleRate = m_glScope->getSampleRate(); - qreal t = (n_samples_delay * 1.0 / m_sampleRate); - - if(t < 0.000001) - ui->trigDelayText->setText(tr("%1\nns").arg(t * 1000000000.0)); - else if(t < 0.001) - ui->trigDelayText->setText(tr("%1\nµs").arg(t * 1000000.0)); - else if(t < 1.0) - ui->trigDelayText->setText(tr("%1\nms").arg(t * 1000.0)); - else - ui->trigDelayText->setText(tr("%1\ns").arg(t * 1.0)); -} - -void GLScopeGUI::setTimeOfsDisplay() -{ - qreal dt = m_glScope->getTraceSize() * (m_timeOffset/100.0) / m_sampleRate; - - if(dt < 0.000001) - ui->timeOfsText->setText(tr("%1\nns").arg(dt * 1000000000.0)); - else if(dt < 0.001) - ui->timeOfsText->setText(tr("%1\nµs").arg(dt * 1000000.0)); - else if(dt < 1.0) - ui->timeOfsText->setText(tr("%1\nms").arg(dt * 1000.0)); - else - ui->timeOfsText->setText(tr("%1\ns").arg(dt * 1.0)); - - //ui->timeOfsText->setText(tr("%1").arg(value/100.0, 0, 'f', 2)); -} - -void GLScopeGUI::setTrigPreDisplay() -{ - qreal dt = m_glScope->getTraceSize() * (m_triggerPre/100.0) / m_sampleRate; - - if(dt < 0.000001) - ui->trigPreText->setText(tr("%1\nns").arg(dt * 1000000000.0)); - else if(dt < 0.001) - ui->trigPreText->setText(tr("%1\nµs").arg(dt * 1000000.0)); - else if(dt < 1.0) - ui->trigPreText->setText(tr("%1\nms").arg(dt * 1000.0)); - else - ui->trigPreText->setText(tr("%1\ns").arg(dt * 1.0)); -} - -void GLScopeGUI::on_time_valueChanged(int value) -{ - m_timeBase = value; - setTimeScaleDisplay(); - m_glScope->setTimeBase(m_timeBase); -} - -void GLScopeGUI::on_traceLen_valueChanged(int value) -{ - if ((value < 1) || (value > 100)) { - return; - } - m_traceLenMult = value; - setTraceLenDisplay(); - applyTriggerSettings(); -} - -void GLScopeGUI::on_timeOfs_valueChanged(int value) -{ - if ((value < 0) || (value > 100)) { - return; - } - m_timeOffset = value; - setTimeOfsDisplay(); - m_glScope->setTimeOfsProMill(value*10); -} - -void GLScopeGUI::on_trigPre_valueChanged(int value) -{ - if ((value < 0) || (value > 100)) { - return; - } - m_triggerPre = value; - setTrigPreDisplay(); - applyTriggerSettings(); -} - -void GLScopeGUI::on_trigDelay_valueChanged(int value) -{ - if ((value < 0) || (value > 100)) { - return; - } - m_triggerDelay[m_triggerIndex] = value; - setTrigDelayDisplay(); - applyTriggerSettings(); -} - -void GLScopeGUI::on_dataMode_currentIndexChanged(int index) -{ - m_displayData = index; - - switch(index) { - case 0: // i+q - m_glScope->setMode(GLScope::ModeIQ); - break; - case 1: // clostationary - m_glScope->setMode(GLScope::ModeIQPolar); - break; - case 2: // mag(lin)+pha - m_glScope->setMode(GLScope::ModeMagLinPha); - break; - case 3: // mag(dB)+pha - m_glScope->setMode(GLScope::ModeMagdBPha); - break; - case 4: // mag(lin)+dPha - m_glScope->setMode(GLScope::ModeMagLinDPha); - break; - case 5: // mag(dB)+dPha - m_glScope->setMode(GLScope::ModeMagdBDPha); - break; - case 6: // derived1+derived2 - m_glScope->setMode(GLScope::ModeDerived12); - break; - case 7: // clostationary - m_glScope->setMode(GLScope::ModeCyclostationary); - break; - - default: - break; - } - - setAmp1ScaleDisplay(); - setAmp1OfsDisplay(); -} - -void GLScopeGUI::on_horizView_clicked() -{ - m_displayOrientation = Qt::Horizontal; - m_displays = GLScope::DisplayBoth; - if(ui->horizView->isChecked()) { - ui->vertView->setChecked(false); - ui->onlyPrimeView->setChecked(false); - ui->onlySecondView->setChecked(false); - m_glScope->setOrientation(Qt::Horizontal); - m_glScope->setDisplays(GLScope::DisplayBoth); - } else { - ui->horizView->setChecked(true); - m_glScope->setOrientation(Qt::Horizontal); - m_glScope->setDisplays(GLScope::DisplayBoth); - } -} - -void GLScopeGUI::on_vertView_clicked() -{ - m_displayOrientation = Qt::Vertical; - m_displays = GLScope::DisplayBoth; - if(ui->vertView->isChecked()) { - ui->horizView->setChecked(false); - ui->onlyPrimeView->setChecked(false); - ui->onlySecondView->setChecked(false); - m_glScope->setOrientation(Qt::Vertical); - m_glScope->setDisplays(GLScope::DisplayBoth); - } else { - ui->vertView->setChecked(true); - m_glScope->setOrientation(Qt::Vertical); - m_glScope->setDisplays(GLScope::DisplayBoth); - } -} - -void GLScopeGUI::on_onlyPrimeView_clicked() -{ - m_displays = GLScope::DisplayFirstOnly; - if(ui->onlyPrimeView->isChecked()) { - ui->horizView->setChecked(false); - ui->vertView->setChecked(false); - ui->onlySecondView->setChecked(false); - m_glScope->setDisplays(GLScope::DisplayFirstOnly); - } else { - ui->onlyPrimeView->setChecked(true); - m_glScope->setDisplays(GLScope::DisplayFirstOnly); - } -} - -void GLScopeGUI::on_onlySecondView_clicked() -{ - m_displays = GLScope::DisplaySecondOnly; - if(ui->onlySecondView->isChecked()) { - ui->horizView->setChecked(false); - ui->vertView->setChecked(false); - ui->onlyPrimeView->setChecked(false); - m_glScope->setDisplays(GLScope::DisplaySecondOnly); - } else { - ui->onlySecondView->setChecked(true); - m_glScope->setDisplays(GLScope::DisplaySecondOnly); - } -} - -void GLScopeGUI::on_gridIntensity_valueChanged(int index) -{ - m_displayGridIntensity = index; - ui->gridIntensity->setToolTip(QString("Grid intensity: %1").arg(m_displayGridIntensity)); - if(m_glScope != 0) - m_glScope->setDisplayGridIntensity(m_displayGridIntensity); -} - -void GLScopeGUI::on_traceIntensity_valueChanged(int index) -{ - m_displayTraceIntensity = index; - ui->traceIntensity->setToolTip(QString("Trace intensity: %1").arg(m_displayTraceIntensity)); - if(m_glScope != 0) - m_glScope->setDisplayTraceIntensity(m_displayTraceIntensity); -} - -void GLScopeGUI::on_trigMode_currentIndexChanged(int index) -{ - m_triggerChannel[m_triggerIndex] = index; - setTrigLevelDisplay(); - applyTriggerSettings(); -} - -void GLScopeGUI::on_trigLevelCoarse_valueChanged(int value) -{ - m_triggerLevelCoarse[m_triggerIndex] = value; - setTrigLevelDisplay(); - applyTriggerSettings(); -} - -void GLScopeGUI::on_trigLevelFine_valueChanged(int value) -{ - m_triggerLevelFine[m_triggerIndex] = value; - setTrigLevelDisplay(); - applyTriggerSettings(); -} - -void GLScopeGUI::on_memIndex_valueChanged(int value) -{ - QString text; - text.sprintf("%02d", value % (1<memIndexText->setText(text); - - if(m_glScope != 0) - { - // If entering memory history block new triggers and all trigger controls - m_scopeVis->blockTrigger(value != 0); - ui->traceLen->setDisabled(value != 0); - ui->trigIndex->setDisabled(value != 0); - ui->trigCount->setDisabled(value != 0); - ui->trigMode->setDisabled(value != 0); - ui->slopePos->setDisabled(value != 0); - ui->slopeNeg->setDisabled(value != 0); - ui->slopeBoth->setDisabled(value != 0); - ui->oneShot->setDisabled(value != 0); - ui->trigLevelCoarse->setDisabled(value != 0); - ui->trigLevelFine->setDisabled(value != 0); - ui->trigDelay->setDisabled(value != 0); - ui->trigPre->setDisabled(value != 0); - // Set value - m_glScope->setMemHistoryShift(value); - ui->sampleRateText->setText(tr("%1\nkS/s").arg(m_glScope->getSampleRate() / 1000.0f, 0, 'f', 2)); - } -} - -void GLScopeGUI::on_trigCount_valueChanged(int value) -{ - m_triggerCounts[m_triggerIndex] = value; - - QString text; - text.sprintf("%02d", value); - ui->trigCountText->setText(text); - - applyTriggerSettings(); -} - -void GLScopeGUI::on_trigIndex_valueChanged(int value) -{ - m_triggerIndex = value; - QString text; - text.sprintf("%d", value); - ui->trigIndexText->setText(text); - setTrigLevelDisplay(); - setTrigUI(m_triggerIndex); -} - -void GLScopeGUI::on_slopePos_clicked() -{ - m_triggerPositiveEdge[m_triggerIndex] = true; - m_triggerBothEdges[m_triggerIndex] = false; - - ui->slopeBoth->setChecked(false); - - if(ui->slopePos->isChecked()) { - ui->slopeNeg->setChecked(false); - } else { - ui->slopePos->setChecked(true); - } - - applyTriggerSettings(); -} - -void GLScopeGUI::on_slopeNeg_clicked() -{ - m_triggerPositiveEdge[m_triggerIndex] = false; - m_triggerBothEdges[m_triggerIndex] = false; - - ui->slopeBoth->setChecked(false); - - if(ui->slopeNeg->isChecked()) { - ui->slopePos->setChecked(false); - } else { - ui->slopeNeg->setChecked(true); - } - - applyTriggerSettings(); -} - -void GLScopeGUI::on_slopeBoth_clicked() -{ - qDebug() << "GLScopeGUI::on_slopeBoth_clicked"; - ui->slopePos->setChecked(false); - ui->slopeNeg->setChecked(false); - ui->slopeBoth->setChecked(true); - m_triggerBothEdges[m_triggerIndex] = true; - - applyTriggerSettings(); -} - -void GLScopeGUI::on_oneShot_clicked() -{ - m_scopeVis->setOneShot(ui->oneShot->isChecked()); -} - -bool GLScopeGUI::handleMessage(Message* cmd __attribute__((unused))) -{ - return false; - /* - if(DSPSignalNotification::match(cmd)) - { - DSPSignalNotification* signal = (DSPSignalNotification*)cmd; - //fprintf(stderr, "GLScopeGUI::handleMessage: %d samples/sec, %lld Hz offset", signal->getSampleRate(), signal->getFrequencyOffset()); - m_sampleRate = signal->getSampleRate(); - cmd->completed(); - return true; - } - else - { - return false; - } - */ -} diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h deleted file mode 100644 index 6b1c6d5aa..000000000 --- a/sdrgui/gui/glscopegui.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef INCLUDE_GLSCOPEGUI_H -#define INCLUDE_GLSCOPEGUI_H - -#include -#include "dsp/dsptypes.h" -#include "export.h" -#include "util/message.h" -#include "dsp/scopevis.h" -#include "settings/serializable.h" - -namespace Ui { - class GLScopeGUI; -} - -class MessageQueue; -class GLScope; - -class SDRGUI_API GLScopeGUI : public QWidget, public Serializable { - Q_OBJECT - -public: - explicit GLScopeGUI(QWidget* parent = NULL); - ~GLScopeGUI(); - - void setBuddies(MessageQueue* messageQueue, ScopeVis* scopeVis, GLScope* glScope); - - void setSampleRate(int sampleRate); - void resetToDefaults(); - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - bool handleMessage(Message* message); - -private: - Ui::GLScopeGUI* ui; - - MessageQueue* m_messageQueue; - ScopeVis* m_scopeVis; - GLScope* m_glScope; - - int m_sampleRate; - - qint32 m_displayData; - qint32 m_displayOrientation; - qint32 m_displays; - qint32 m_timeBase; - qint32 m_timeOffset; - qint32 m_amplification1; - qint32 m_amp1OffsetCoarse; - qint32 m_amp1OffsetFine; - qint32 m_amplification2; - qint32 m_amp2OffsetCoarse; - qint32 m_amp2OffsetFine; - int m_displayGridIntensity; - int m_displayTraceIntensity; - quint32 m_triggerIndex; - qint32 m_triggerChannel[ScopeVis::m_nbTriggers]; - qint32 m_triggerLevelCoarse[ScopeVis::m_nbTriggers]; // percent of full range - qint32 m_triggerLevelFine[ScopeVis::m_nbTriggers]; // percent of coarse - bool m_triggerPositiveEdge[ScopeVis::m_nbTriggers]; - bool m_triggerBothEdges[ScopeVis::m_nbTriggers]; - qint32 m_triggerPre; - qint32 m_triggerDelay[ScopeVis::m_nbTriggers]; - qint32 m_triggerCounts[ScopeVis::m_nbTriggers]; - qint32 m_traceLenMult; - - static const qreal amps[11]; - - void applySettings(); - void applyTriggerSettings(); - void applyAllTriggerSettings(); - void setTimeScaleDisplay(); - void setTraceLenDisplay(); - void setTimeOfsDisplay(); - void setAmp1ScaleDisplay(); - void setAmp1OfsDisplay(); - void setAmp2ScaleDisplay(); - void setAmp2OfsDisplay(); - void setTrigLevelDisplay(); - void setTrigPreDisplay(); - void setTrigDelayDisplay(); - void setTrigUI(uint index); - -private slots: - void on_amp1_valueChanged(int value); - void on_amp1OfsCoarse_valueChanged(int value); - void on_amp1OfsFine_valueChanged(int value); - void on_amp2_valueChanged(int value); - void on_amp2OfsCoarse_valueChanged(int value); - void on_amp2OfsFine_valueChanged(int value); - void on_scope_traceSizeChanged(int value); - void on_scope_sampleRateChanged(int value); - void on_time_valueChanged(int value); - void on_traceLen_valueChanged(int value); - void on_timeOfs_valueChanged(int value); - void on_dataMode_currentIndexChanged(int index); - void on_gridIntensity_valueChanged(int index); - void on_traceIntensity_valueChanged(int index); - void on_trigPre_valueChanged(int value); - void on_trigDelay_valueChanged(int value); - void on_memIndex_valueChanged(int value); - void on_trigCount_valueChanged(int value); - void on_trigIndex_valueChanged(int value); - - void on_horizView_clicked(); - void on_vertView_clicked(); - void on_onlyPrimeView_clicked(); - void on_onlySecondView_clicked(); - - void on_trigMode_currentIndexChanged(int index); - void on_slopePos_clicked(); - void on_slopeNeg_clicked(); - void on_slopeBoth_clicked(); - void on_oneShot_clicked(); - void on_trigLevelCoarse_valueChanged(int value); - void on_trigLevelFine_valueChanged(int value); -}; - -#endif // INCLUDE_GLSCOPEGUI_H diff --git a/sdrgui/gui/glscopegui.ui b/sdrgui/gui/glscopegui.ui deleted file mode 100644 index 8b07d6067..000000000 --- a/sdrgui/gui/glscopegui.ui +++ /dev/null @@ -1,1287 +0,0 @@ - - - GLScopeGUI - - - - 0 - 0 - 634 - 117 - - - - - 634 - 0 - - - - - Liberation Sans - 8 - - - - Oscilloscope - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 3 - - - 2 - - - 0 - - - 2 - - - 0 - - - - - - 8 - - - - Data - - - - - - - - 0 - 0 - - - - - 8 - - - - Displayed data - - - QComboBox::AdjustToContentsOnFirstShow - - - - 1:I 2:Q (lin) - - - - - 1:IQ (lin) 2:IQ (pol) - - - - - 1:Mag (lin) 2:Phi - - - - - 1:Mag (dB) 2:Phi - - - - - 1:Mag (lin) 2:dPhi - - - - - 1:Mag(dB) 2:dPhi - - - - - 1:Derived 1 2:2nd - - - - - 1,2:Cyclostationary - - - - - - - - Qt::Vertical - - - - - - - Only primary display - - - - - - - :/display1_w.png:/display1_w.png - - - - 16 - 16 - - - - true - - - true - - - - - - - Only secondary display - - - - - - - :/display2_w.png:/display2_w.png - - - - 16 - 16 - - - - true - - - true - - - - - - - Both displays horizontally arranged - - - - - - - :/horizontal_w.png:/horizontal_w.png - - - - 16 - 16 - - - - true - - - true - - - true - - - - - - - Both displays vertically arranged - - - - - - - :/vertical_w.png:/vertical_w.png - - - - 16 - 16 - - - - true - - - true - - - - - - - Qt::Vertical - - - - - - - - 8 - - - - t - - - - - - - - 0 - 0 - - - - - 8 - - - - Time range - - - 1 - - - 100 - - - 1 - - - Qt::Horizontal - - - QSlider::NoTicks - - - 5 - - - - - - - - 32 - 0 - - - - - 8 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - Time offset - - - 0 - - - 100 - - - 1 - - - Qt::Horizontal - - - QSlider::NoTicks - - - 1 - - - - - - - - 32 - 0 - - - - - 8 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - - - - - - 8 - - - - Len - - - - - - - Trace length - - - 1 - - - 100 - - - 1 - - - 20 - - - Qt::Horizontal - - - - - - - - 32 - 0 - - - - - 8 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - - - - - - 52 - 0 - - - - Currently displayed trace sample rate (kS/s) - - - 00000.00 -kS/s - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - Qt::Horizontal - - - - - - - 3 - - - 2 - - - 2 - - - - - Mem - - - - - - - - 24 - 24 - - - - Move in trace history - - - 15 - - - 1 - - - - - - - Trace history index - - - 00 - - - - - - - Qt::Vertical - - - - - - - - 8 - - - - A1 - - - - - - - - 0 - 0 - - - - Y range for display 1 - - - 0 - - - 10 - - - 1 - - - 0 - - - Qt::Horizontal - - - QSlider::NoTicks - - - 1 - - - - - - - - 32 - 0 - - - - - 8 - - - - 000 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - 0 - 0 - - - - Y coarse offset for display 1 - - - -100 - - - 99 - - - 1 - - - Qt::Horizontal - - - QSlider::NoTicks - - - 10 - - - - - - - Y fine offset for display 1 - - - 200 - - - 1 - - - Qt::Horizontal - - - - - - - - - - 32 - 0 - - - - - 8 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - - - - - A2 - - - - - - - Y range for display 2 - - - 10 - - - 1 - - - Qt::Horizontal - - - - - - - - 32 - 0 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - Y coarse offset for display 2 - - - -100 - - - 1 - - - Qt::Horizontal - - - - - - - Y fine offset for display 2 - - - 200 - - - 1 - - - Qt::Horizontal - - - - - - - - - - 32 - 0 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - - - - - - 24 - 24 - - - - Trace intensity - - - 100 - - - 1 - - - 50 - - - - - - - - 24 - 24 - - - - Grid intensity - - - 100 - - - 1 - - - - - - - - - Qt::Horizontal - - - - - - - 3 - - - 2 - - - 0 - - - 2 - - - 0 - - - - - - 8 - - - - Trig. - - - - - - - - 24 - 24 - - - - Navigate through triggers - - - 9 - - - 1 - - - - - - - Current trigger index - - - 0 - - - - - - - Qt::Vertical - - - - - - - - 24 - 24 - - - - Control number of trigger events - - - 1 - - - - - - - Number of trigger events before trigger is kicked off - - - 00 - - - - - - - - 8 - - - - Trigger source - - - - Freerun - - - - - I (lin) - - - - - Q (lin) - - - - - Mag (lin) - - - - - Mag (dB) - - - - - Phi - - - - - dPhi - - - - - - - - - 8 - - - - Trigger positive edge - - - - - - - :/slopep_icon.png:/slopep_icon.png - - - - 16 - 16 - - - - true - - - true - - - Qt::ToolButtonIconOnly - - - true - - - - - - - - 8 - - - - Trigger negative edge - - - - - - - :/slopen_icon.png:/slopen_icon.png - - - - 16 - 16 - - - - true - - - true - - - - - - - Trigger on both edges - - - - - - - :/slopeb_icon.png:/slopeb_icon.png - - - - 16 - 16 - - - - true - - - false - - - true - - - - - - - One shot trigger - - - - - - - :/display1_w.png:/display1_w.png - - - - 16 - 16 - - - - true - - - true - - - - - - - Qt::Vertical - - - - - - - - 8 - - - - Lvl - - - - - - - - - - 16777215 - 9 - - - - Trigger level coarse - - - -100 - - - 99 - - - 1 - - - Qt::Horizontal - - - - - - - - 16777215 - 9 - - - - Trigger level fine - - - 0 - - - 200 - - - 1 - - - Qt::Horizontal - - - - - - - - - - 32 - 0 - - - - - 8 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Dly - - - - - - - Trigger delay - - - 100 - - - 1 - - - Qt::Horizontal - - - - - - - - 32 - 0 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - - - - - - 8 - - - - Pre - - - - - - - Pre-trigger delay - - - 99 - - - 1 - - - Qt::Horizontal - - - - - - - - 32 - 0 - - - - - 8 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - ButtonSwitch - QToolButton -
    gui/buttonswitch.h
    -
    -
    - - - - -
    From 3463efc37e087a1750e3b04c5a8bf399b66fa0ff Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 15:52:05 +0200 Subject: [PATCH 585/956] Bumped version --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 7 +++++++ plugins/channelrx/chanalyzer/chanalyzerplugin.cpp | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 6e7708f3c..ac2132170 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.0.6"); + QCoreApplication::setApplicationVersion("4.0.7"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 0dacdc82e..eb66ea658 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.0.6"); + QCoreApplication::setApplicationVersion("4.0.7"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 7e15b25e4..ed59df91a 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.0.6"); + QCoreApplication::setApplicationVersion("4.0.7"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index cf78aa0a4..7f5c48a93 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +sdrangel (4.0.7-1) unstable; urgency=medium + + * Scope: removed old scope objects + * Web API: reduced HTTP server debug messages + + -- Edouard Griffiths, F4EXB Wed, 15 Aug 2018 19:14:18 +0200 + sdrangel (4.0.6-1) unstable; urgency=medium * Web API: RTL-SDR: fixed RF bandwidth setting diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index 3661f7cfa..4d9bff02b 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("4.0.3"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From b1b79ecc383bc54729eb99f9e865d52c2a273ebb Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 16:42:38 +0200 Subject: [PATCH 586/956] Renamed Ui::GLScopeNGGUI to Ui::GLScopeGUI --- sdrgui/gui/glscopenggui.cpp | 8 ++++---- sdrgui/gui/glscopenggui.h | 16 ++++++++-------- sdrgui/gui/glscopenggui.ui | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 8ebdadc8d..a5d53a9b4 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -36,7 +36,7 @@ const double GLScopeNGGUI::amps[27] = { GLScopeNGGUI::GLScopeNGGUI(QWidget* parent) : QWidget(parent), - ui(new Ui::GLScopeNGGUI), + ui(new Ui::GLScopeGUI), m_messageQueue(0), m_scopeVis(0), m_glScope(0), @@ -1350,7 +1350,7 @@ bool GLScopeNGGUI::handleMessage(Message* message __attribute__((unused))) return false; } -GLScopeNGGUI::TrigUIBlocker::TrigUIBlocker(Ui::GLScopeNGGUI *ui) : +GLScopeNGGUI::TrigUIBlocker::TrigUIBlocker(Ui::GLScopeGUI *ui) : m_ui(ui) { m_oldStateTrigMode = ui->trigMode->blockSignals(true); @@ -1382,7 +1382,7 @@ void GLScopeNGGUI::TrigUIBlocker::unBlock() m_ui->trigDelayFine->blockSignals(m_oldStateTrigDelayFine); } -GLScopeNGGUI::TraceUIBlocker::TraceUIBlocker(Ui::GLScopeNGGUI* ui) : +GLScopeNGGUI::TraceUIBlocker::TraceUIBlocker(Ui::GLScopeGUI* ui) : m_ui(ui) { m_oldStateTrace = m_ui->trace->blockSignals(true); @@ -1416,7 +1416,7 @@ void GLScopeNGGUI::TraceUIBlocker::unBlock() m_ui->traceColor->blockSignals(m_oldStateTraceColor); } -GLScopeNGGUI::MainUIBlocker::MainUIBlocker(Ui::GLScopeNGGUI* ui) : +GLScopeNGGUI::MainUIBlocker::MainUIBlocker(Ui::GLScopeGUI* ui) : m_ui(ui) { m_oldStateOnlyX = m_ui->onlyX->blockSignals(true); diff --git a/sdrgui/gui/glscopenggui.h b/sdrgui/gui/glscopenggui.h index 3eae59aea..734a7f1ed 100644 --- a/sdrgui/gui/glscopenggui.h +++ b/sdrgui/gui/glscopenggui.h @@ -28,7 +28,7 @@ #include "settings/serializable.h" namespace Ui { - class GLScopeNGGUI; + class GLScopeGUI; } class MessageQueue; @@ -80,13 +80,13 @@ private: class TrigUIBlocker { public: - TrigUIBlocker(Ui::GLScopeNGGUI *ui); + TrigUIBlocker(Ui::GLScopeGUI *ui); ~TrigUIBlocker(); void unBlock(); private: - Ui::GLScopeNGGUI *m_ui; + Ui::GLScopeGUI *m_ui; bool m_oldStateTrigMode; bool m_oldStateTrigCount; bool m_oldStateTrigPos; @@ -101,13 +101,13 @@ private: class TraceUIBlocker { public: - TraceUIBlocker(Ui::GLScopeNGGUI *ui); + TraceUIBlocker(Ui::GLScopeGUI *ui); ~TraceUIBlocker(); void unBlock(); private: - Ui::GLScopeNGGUI *m_ui; + Ui::GLScopeGUI *m_ui; bool m_oldStateTrace; bool m_oldStateTraceAdd; bool m_oldStateTraceDel; @@ -123,13 +123,13 @@ private: class MainUIBlocker { public: - MainUIBlocker(Ui::GLScopeNGGUI *ui); + MainUIBlocker(Ui::GLScopeGUI *ui); ~MainUIBlocker(); void unBlock(); private: - Ui::GLScopeNGGUI *m_ui; + Ui::GLScopeGUI *m_ui; bool m_oldStateOnlyX; bool m_oldStateOnlyY; bool m_oldStateHorizontalXY; @@ -140,7 +140,7 @@ private: // bool m_oldStateTraceLen; }; - Ui::GLScopeNGGUI* ui; + Ui::GLScopeGUI* ui; MessageQueue* m_messageQueue; ScopeVisNG* m_scopeVis; diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index a0e071382..e4da3f321 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -1,7 +1,7 @@ - GLScopeNGGUI - + GLScopeGUI + 0 From d9ddc673b3b2daf460619f3171faed3d3036cb66 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 16:44:12 +0200 Subject: [PATCH 587/956] Renamed glscopenggui.ui to glscopegui.ui --- sdrgui/CMakeLists.txt | 2 +- sdrgui/gui/{glscopenggui.ui => glscopegui.ui} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename sdrgui/gui/{glscopenggui.ui => glscopegui.ui} (100%) diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 78223583c..c3244503f 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -128,7 +128,7 @@ set(sdrgui_FORMS gui/cwkeyergui.ui gui/editcommanddialog.ui gui/externalclockdialog.ui - gui/glscopenggui.ui + gui/glscopegui.ui gui/glscopemultigui.ui gui/glspectrumgui.ui gui/pluginsdialog.ui diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopegui.ui similarity index 100% rename from sdrgui/gui/glscopenggui.ui rename to sdrgui/gui/glscopegui.ui From 2429f169bcb2b0ab2262b9c95f4ad7a368d7a663 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 16:48:03 +0200 Subject: [PATCH 588/956] Renamed GLScopeNGGUI to GLScopeGUI --- plugins/channelrx/chanalyzer/chanalyzergui.ui | 4 +- plugins/channelrx/demodatv/atvdemodgui.ui | 4 +- sdrgui/gui/glscopenggui.cpp | 206 +++++++++--------- sdrgui/gui/glscopenggui.h | 6 +- 4 files changed, 110 insertions(+), 110 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index d052da7f3..28e9e797d 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -700,7 +700,7 @@
    - +
    @@ -748,7 +748,7 @@ 1 - GLScopeNGGUI + GLScopeGUI QWidget
    gui/glscopenggui.h
    1 diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index 9163af836..0604beb0b 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -1104,7 +1104,7 @@
    - + @@ -1129,7 +1129,7 @@ 1 - GLScopeNGGUI + GLScopeGUI QWidget
    gui/glscopenggui.h
    1 diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index a5d53a9b4..e21ce5fd4 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -22,7 +22,7 @@ #include "ui_glscopenggui.h" #include "util/simpleserializer.h" -const double GLScopeNGGUI::amps[27] = { +const double GLScopeGUI::amps[27] = { 2e-1, 1e-1, 5e-2, 2e-2, 1e-2, 5e-3, 2e-3, 1e-3, 5e-4, @@ -34,7 +34,7 @@ const double GLScopeNGGUI::amps[27] = { 2e-9, 1e-9, 5e-10, }; -GLScopeNGGUI::GLScopeNGGUI(QWidget* parent) : +GLScopeGUI::GLScopeGUI(QWidget* parent) : QWidget(parent), ui(new Ui::GLScopeGUI), m_messageQueue(0), @@ -45,7 +45,7 @@ GLScopeNGGUI::GLScopeNGGUI(QWidget* parent) : m_timeOffset(0), m_traceLenMult(1) { - qDebug("GLScopeNGGUI::GLScopeNGGUI"); + qDebug("GLScopeGUI::GLScopeGUI"); setEnabled(false); ui->setupUi(this); ui->trigDelayFine->setMaximum(ScopeVisNG::m_traceChunkSize / 10.0); @@ -57,14 +57,14 @@ GLScopeNGGUI::GLScopeNGGUI(QWidget* parent) : ui->mem->setMaximum(ScopeVisNG::m_nbTraceMemories - 1); } -GLScopeNGGUI::~GLScopeNGGUI() +GLScopeGUI::~GLScopeGUI() { delete ui; } -void GLScopeNGGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScopeNG* glScope) +void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScopeNG* glScope) { - qDebug("GLScopeNGGUI::setBuddies"); + qDebug("GLScopeGUI::setBuddies"); m_messageQueue = messageQueue; m_scopeVis = scopeVis; @@ -129,12 +129,12 @@ void GLScopeNGGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, setTraceDelayDisplay(); } -void GLScopeNGGUI::setSampleRate(int sampleRate) +void GLScopeGUI::setSampleRate(int sampleRate) { m_sampleRate = sampleRate; } -void GLScopeNGGUI::on_scope_sampleRateChanged(int sampleRate) +void GLScopeGUI::on_scope_sampleRateChanged(int sampleRate) { //m_sampleRate = m_glScope->getSampleRate(); m_sampleRate = sampleRate; @@ -147,12 +147,12 @@ void GLScopeNGGUI::on_scope_sampleRateChanged(int sampleRate) setTrigDelayDisplay(); } -void GLScopeNGGUI::resetToDefaults() +void GLScopeGUI::resetToDefaults() { } -QByteArray GLScopeNGGUI::serialize() const +QByteArray GLScopeGUI::serialize() const { SimpleSerializer s(1); @@ -206,9 +206,9 @@ QByteArray GLScopeNGGUI::serialize() const return s.final(); } -bool GLScopeNGGUI::deserialize(const QByteArray& data) +bool GLScopeGUI::deserialize(const QByteArray& data) { - qDebug("GLScopeNGGUI::deserialize"); + qDebug("GLScopeGUI::deserialize"); SimpleDeserializer d(data); if(!d.isValid()) { @@ -278,7 +278,7 @@ bool GLScopeNGGUI::deserialize(const QByteArray& data) const std::vector& tracesData = m_scopeVis->getTracesData(); uint32_t iTrace = tracesData.size(); - qDebug("GLScopeNGGUI::deserialize: nbTracesSaved: %u tracesData.size(): %lu", nbTracesSaved, tracesData.size()); + qDebug("GLScopeGUI::deserialize: nbTracesSaved: %u tracesData.size(): %lu", nbTracesSaved, tracesData.size()); while (iTrace > nbTracesSaved) // remove possible traces in excess { @@ -350,7 +350,7 @@ bool GLScopeNGGUI::deserialize(const QByteArray& data) d.readS32(201, &intValue, 0); ui->trigPre->setValue(intValue); - qDebug("GLScopeNGGUI::deserialize: nbTriggersSaved: %u nbTriggers: %u", nbTriggersSaved, nbTriggers); + qDebug("GLScopeGUI::deserialize: nbTriggersSaved: %u nbTriggers: %u", nbTriggersSaved, nbTriggers); while (iTrigger > nbTriggersSaved) // remove possible triggers in excess { @@ -422,7 +422,7 @@ bool GLScopeNGGUI::deserialize(const QByteArray& data) } } -void GLScopeNGGUI::on_onlyX_toggled(bool checked) +void GLScopeGUI::on_onlyX_toggled(bool checked) { if (checked) { @@ -440,7 +440,7 @@ void GLScopeNGGUI::on_onlyX_toggled(bool checked) } } -void GLScopeNGGUI::on_onlyY_toggled(bool checked) +void GLScopeGUI::on_onlyY_toggled(bool checked) { if (checked) { @@ -458,7 +458,7 @@ void GLScopeNGGUI::on_onlyY_toggled(bool checked) } } -void GLScopeNGGUI::on_horizontalXY_toggled(bool checked) +void GLScopeGUI::on_horizontalXY_toggled(bool checked) { if (checked) { @@ -476,7 +476,7 @@ void GLScopeNGGUI::on_horizontalXY_toggled(bool checked) } } -void GLScopeNGGUI::on_verticalXY_toggled(bool checked) +void GLScopeGUI::on_verticalXY_toggled(bool checked) { if (checked) { @@ -494,7 +494,7 @@ void GLScopeNGGUI::on_verticalXY_toggled(bool checked) } } -void GLScopeNGGUI::on_polar_toggled(bool checked) +void GLScopeGUI::on_polar_toggled(bool checked) { if (checked) { @@ -512,24 +512,24 @@ void GLScopeNGGUI::on_polar_toggled(bool checked) } } -void GLScopeNGGUI::on_polarPoints_toggled(bool checked) +void GLScopeGUI::on_polarPoints_toggled(bool checked) { m_glScope->setDisplayXYPoints(checked); } -void GLScopeNGGUI::on_traceIntensity_valueChanged(int value) +void GLScopeGUI::on_traceIntensity_valueChanged(int value) { ui->traceIntensity->setToolTip(QString("Trace intensity: %1").arg(value)); m_glScope->setDisplayTraceIntensity(value); } -void GLScopeNGGUI::on_gridIntensity_valueChanged(int value) +void GLScopeGUI::on_gridIntensity_valueChanged(int value) { ui->gridIntensity->setToolTip(QString("Grid intensity: %1").arg(value)); m_glScope->setDisplayGridIntensity(value); } -void GLScopeNGGUI::on_time_valueChanged(int value) +void GLScopeGUI::on_time_valueChanged(int value) { m_timeBase = value; setTimeScaleDisplay(); @@ -541,7 +541,7 @@ void GLScopeNGGUI::on_time_valueChanged(int value) ui->freerun->isChecked()); } -void GLScopeNGGUI::on_timeOfs_valueChanged(int value) +void GLScopeGUI::on_timeOfs_valueChanged(int value) { if ((value < 0) || (value > 100)) { return; @@ -556,7 +556,7 @@ void GLScopeNGGUI::on_timeOfs_valueChanged(int value) ui->freerun->isChecked()); } -void GLScopeNGGUI::on_traceLen_valueChanged(int value) +void GLScopeGUI::on_traceLen_valueChanged(int value) { if ((value < 1) || (value > 100)) { return; @@ -575,14 +575,14 @@ void GLScopeNGGUI::on_traceLen_valueChanged(int value) setTrigPreDisplay(); } -void GLScopeNGGUI::on_trace_valueChanged(int value) +void GLScopeGUI::on_trace_valueChanged(int value) { ui->traceText->setText(value == 0 ? "X" : QString("Y%1").arg(ui->trace->value())); ScopeVisNG::TraceData traceData; m_scopeVis->getTraceData(traceData, value); - qDebug() << "GLScopeNGGUI::on_trace_valueChanged:" + qDebug() << "GLScopeGUI::on_trace_valueChanged:" << " m_projectionType: " << (int) traceData.m_projectionType << " m_amp" << traceData.m_amp << " m_ofs" << traceData.m_ofs @@ -593,14 +593,14 @@ void GLScopeNGGUI::on_trace_valueChanged(int value) m_scopeVis->focusOnTrace(value); } -void GLScopeNGGUI::on_traceAdd_clicked(bool checked __attribute__((unused))) +void GLScopeGUI::on_traceAdd_clicked(bool checked __attribute__((unused))) { ScopeVisNG::TraceData traceData; fillTraceData(traceData); addTrace(traceData); } -void GLScopeNGGUI::on_traceDel_clicked(bool checked __attribute__((unused))) +void GLScopeGUI::on_traceDel_clicked(bool checked __attribute__((unused))) { if (ui->trace->value() > 0) // not the X trace { @@ -621,7 +621,7 @@ void GLScopeNGGUI::on_traceDel_clicked(bool checked __attribute__((unused))) } } -void GLScopeNGGUI::on_traceUp_clicked(bool checked __attribute__((unused))) +void GLScopeGUI::on_traceUp_clicked(bool checked __attribute__((unused))) { if (ui->trace->maximum() > 0) // more than one trace { @@ -635,7 +635,7 @@ void GLScopeNGGUI::on_traceUp_clicked(bool checked __attribute__((unused))) } } -void GLScopeNGGUI::on_traceDown_clicked(bool checked __attribute__((unused))) +void GLScopeGUI::on_traceDown_clicked(bool checked __attribute__((unused))) { if (ui->trace->value() > 0) // not the X (lowest) trace { @@ -649,14 +649,14 @@ void GLScopeNGGUI::on_traceDown_clicked(bool checked __attribute__((unused))) } } -void GLScopeNGGUI::on_trig_valueChanged(int value) +void GLScopeGUI::on_trig_valueChanged(int value) { ui->trigText->setText(tr("%1").arg(value)); ScopeVisNG::TriggerData triggerData; m_scopeVis->getTriggerData(triggerData, value); - qDebug() << "GLScopeNGGUI::on_trig_valueChanged:" + qDebug() << "GLScopeGUI::on_trig_valueChanged:" << " m_projectionType: " << (int) triggerData.m_projectionType << " m_triggerRepeat" << triggerData.m_triggerRepeat << " m_triggerPositiveEdge" << triggerData.m_triggerPositiveEdge @@ -668,14 +668,14 @@ void GLScopeNGGUI::on_trig_valueChanged(int value) m_scopeVis->focusOnTrigger(value); } -void GLScopeNGGUI::on_trigAdd_clicked(bool checked __attribute__((unused))) +void GLScopeGUI::on_trigAdd_clicked(bool checked __attribute__((unused))) { ScopeVisNG::TriggerData triggerData; fillTriggerData(triggerData); addTrigger(triggerData); } -void GLScopeNGGUI::on_trigDel_clicked(bool checked __attribute__((unused))) +void GLScopeGUI::on_trigDel_clicked(bool checked __attribute__((unused))) { if (ui->trig->value() > 0) { @@ -684,7 +684,7 @@ void GLScopeNGGUI::on_trigDel_clicked(bool checked __attribute__((unused))) } } -void GLScopeNGGUI::on_trigUp_clicked(bool checked __attribute__((unused))) +void GLScopeGUI::on_trigUp_clicked(bool checked __attribute__((unused))) { if (ui->trig->maximum() > 0) // more than one trigger { @@ -698,7 +698,7 @@ void GLScopeNGGUI::on_trigUp_clicked(bool checked __attribute__((unused))) } } -void GLScopeNGGUI::on_trigDown_clicked(bool checked __attribute__((unused))) +void GLScopeGUI::on_trigDown_clicked(bool checked __attribute__((unused))) { if (ui->trig->value() > 0) // not the 0 (lowest) trigger { @@ -712,49 +712,49 @@ void GLScopeNGGUI::on_trigDown_clicked(bool checked __attribute__((unused))) } } -void GLScopeNGGUI::on_traceMode_currentIndexChanged(int index __attribute__((unused))) +void GLScopeGUI::on_traceMode_currentIndexChanged(int index __attribute__((unused))) { setAmpScaleDisplay(); setAmpOfsDisplay(); changeCurrentTrace(); } -void GLScopeNGGUI::on_amp_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_amp_valueChanged(int value __attribute__((unused))) { setAmpScaleDisplay(); changeCurrentTrace(); } -void GLScopeNGGUI::on_ofsCoarse_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_ofsCoarse_valueChanged(int value __attribute__((unused))) { setAmpOfsDisplay(); changeCurrentTrace(); } -void GLScopeNGGUI::on_ofsFine_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_ofsFine_valueChanged(int value __attribute__((unused))) { setAmpOfsDisplay(); changeCurrentTrace(); } -void GLScopeNGGUI::on_traceDelayCoarse_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_traceDelayCoarse_valueChanged(int value __attribute__((unused))) { setTraceDelayDisplay(); changeCurrentTrace(); } -void GLScopeNGGUI::on_traceDelayFine_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_traceDelayFine_valueChanged(int value __attribute__((unused))) { setTraceDelayDisplay(); changeCurrentTrace(); } -void GLScopeNGGUI::on_traceView_toggled(bool checked __attribute__((unused))) +void GLScopeGUI::on_traceView_toggled(bool checked __attribute__((unused))) { changeCurrentTrace(); } -void GLScopeNGGUI::on_traceColor_clicked() +void GLScopeGUI::on_traceColor_clicked() { QColor newColor = QColorDialog::getColor(m_focusedTraceColor, this, tr("Select Color for trace"), QColorDialog::DontUseNativeDialog); @@ -768,7 +768,7 @@ void GLScopeNGGUI::on_traceColor_clicked() } } -void GLScopeNGGUI::on_mem_valueChanged(int value) +void GLScopeGUI::on_mem_valueChanged(int value) { QString text; text.sprintf("%02d", value); @@ -777,19 +777,19 @@ void GLScopeNGGUI::on_mem_valueChanged(int value) m_scopeVis->setMemoryIndex(value); } -void GLScopeNGGUI::on_trigMode_currentIndexChanged(int index __attribute__((unused))) +void GLScopeGUI::on_trigMode_currentIndexChanged(int index __attribute__((unused))) { setTrigLevelDisplay(); changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigCount_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_trigCount_valueChanged(int value __attribute__((unused))) { setTrigCountDisplay(); changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigPos_toggled(bool checked) +void GLScopeGUI::on_trigPos_toggled(bool checked) { if (checked) { @@ -799,7 +799,7 @@ void GLScopeNGGUI::on_trigPos_toggled(bool checked) changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigNeg_toggled(bool checked) +void GLScopeGUI::on_trigNeg_toggled(bool checked) { if (checked) { @@ -809,7 +809,7 @@ void GLScopeNGGUI::on_trigNeg_toggled(bool checked) changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigBoth_toggled(bool checked) +void GLScopeGUI::on_trigBoth_toggled(bool checked) { if (checked) { @@ -819,31 +819,31 @@ void GLScopeNGGUI::on_trigBoth_toggled(bool checked) changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigLevelCoarse_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_trigLevelCoarse_valueChanged(int value __attribute__((unused))) { setTrigLevelDisplay(); changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigLevelFine_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_trigLevelFine_valueChanged(int value __attribute__((unused))) { setTrigLevelDisplay(); changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigDelayCoarse_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_trigDelayCoarse_valueChanged(int value __attribute__((unused))) { setTrigDelayDisplay(); changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigDelayFine_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_trigDelayFine_valueChanged(int value __attribute__((unused))) { setTrigDelayDisplay(); changeCurrentTrigger(); } -void GLScopeNGGUI::on_trigPre_valueChanged(int value __attribute__((unused))) +void GLScopeGUI::on_trigPre_valueChanged(int value __attribute__((unused))) { setTrigPreDisplay(); m_scopeVis->configure(m_traceLenMult*ScopeVisNG::m_traceChunkSize, @@ -853,7 +853,7 @@ void GLScopeNGGUI::on_trigPre_valueChanged(int value __attribute__((unused))) ui->freerun->isChecked()); } -void GLScopeNGGUI::on_trigColor_clicked() +void GLScopeGUI::on_trigColor_clicked() { QColor newColor = QColorDialog::getColor(m_focusedTriggerColor, this, tr("Select Color for trigger line"), QColorDialog::DontUseNativeDialog); @@ -867,12 +867,12 @@ void GLScopeNGGUI::on_trigColor_clicked() } } -void GLScopeNGGUI::on_trigOneShot_toggled(bool checked) +void GLScopeGUI::on_trigOneShot_toggled(bool checked) { m_scopeVis->setOneShot(checked); } -void GLScopeNGGUI::on_freerun_toggled(bool checked) +void GLScopeGUI::on_freerun_toggled(bool checked) { if (checked) { @@ -891,19 +891,19 @@ void GLScopeNGGUI::on_freerun_toggled(bool checked) ui->freerun->isChecked()); } -void GLScopeNGGUI::setTraceIndexDisplay() +void GLScopeGUI::setTraceIndexDisplay() { ui->traceText->setText(ui->trace->value() == 0 ? "X" : QString("Y%1").arg(ui->trace->value())); } -void GLScopeNGGUI::setTrigCountDisplay() +void GLScopeGUI::setTrigCountDisplay() { QString text; text.sprintf("%02d", ui->trigCount->value()); ui->trigCountText->setText(text); } -void GLScopeNGGUI::setTimeScaleDisplay() +void GLScopeGUI::setTimeScaleDisplay() { m_sampleRate = m_glScope->getSampleRate(); unsigned int n_samples = (m_glScope->getTraceSize() * 1.0) / (double) m_timeBase; @@ -939,7 +939,7 @@ void GLScopeNGGUI::setTimeScaleDisplay() } } -void GLScopeNGGUI::setTraceLenDisplay() +void GLScopeGUI::setTraceLenDisplay() { unsigned int n_samples = m_traceLenMult * ScopeVisNG::m_traceChunkSize; @@ -964,7 +964,7 @@ void GLScopeNGGUI::setTraceLenDisplay() ui->traceLenText->setText(tr("%1\ns").arg(t * 1.0, 0, 'f', 2)); } -void GLScopeNGGUI::setTimeOfsDisplay() +void GLScopeGUI::setTimeOfsDisplay() { unsigned int n_samples = m_glScope->getTraceSize() * (m_timeOffset/100.0); double dt = m_glScope->getTraceSize() * (m_timeOffset/100.0) / m_sampleRate; @@ -987,7 +987,7 @@ void GLScopeNGGUI::setTimeOfsDisplay() ui->timeOfsText->setText(tr("%1\ns").arg(dt * 1.0, 0, 'f', 2)); } -void GLScopeNGGUI::setAmpScaleDisplay() +void GLScopeGUI::setAmpScaleDisplay() { Projector::ProjectionType projectionType = (Projector::ProjectionType) ui->traceMode->currentIndex(); double ampValue = amps[ui->amp->value()]; @@ -1018,7 +1018,7 @@ void GLScopeNGGUI::setAmpScaleDisplay() } } -void GLScopeNGGUI::setAmpOfsDisplay() +void GLScopeGUI::setAmpOfsDisplay() { Projector::ProjectionType projectionType = (Projector::ProjectionType) ui->traceMode->currentIndex(); double o = (ui->ofsCoarse->value() * 10.0f) + (ui->ofsFine->value() / 20.0f); @@ -1053,7 +1053,7 @@ void GLScopeNGGUI::setAmpOfsDisplay() } } -void GLScopeNGGUI::setTraceDelayDisplay() +void GLScopeGUI::setTraceDelayDisplay() { if (m_sampleRate > 0) { @@ -1079,12 +1079,12 @@ void GLScopeNGGUI::setTraceDelayDisplay() } } -void GLScopeNGGUI::setTrigIndexDisplay() +void GLScopeGUI::setTrigIndexDisplay() { ui->trigText->setText(tr("%1").arg(ui->trig->value())); } -void GLScopeNGGUI::setTrigLevelDisplay() +void GLScopeGUI::setTrigLevelDisplay() { double t = (ui->trigLevelCoarse->value() / 100.0f) + (ui->trigLevelFine->value() / 50000.0f); Projector::ProjectionType projectionType = (Projector::ProjectionType) ui->trigMode->currentIndex(); @@ -1116,7 +1116,7 @@ void GLScopeNGGUI::setTrigLevelDisplay() } } -void GLScopeNGGUI::setTrigDelayDisplay() +void GLScopeGUI::setTrigDelayDisplay() { if (m_sampleRate > 0) { @@ -1147,7 +1147,7 @@ void GLScopeNGGUI::setTrigDelayDisplay() } } -void GLScopeNGGUI::setTrigPreDisplay() +void GLScopeGUI::setTrigPreDisplay() { if (m_sampleRate > 0) { @@ -1175,7 +1175,7 @@ void GLScopeNGGUI::setTrigPreDisplay() } } -void GLScopeNGGUI::changeCurrentTrace() +void GLScopeGUI::changeCurrentTrace() { ScopeVisNG::TraceData traceData; fillTraceData(traceData); @@ -1183,7 +1183,7 @@ void GLScopeNGGUI::changeCurrentTrace() m_scopeVis->changeTrace(traceData, currentTraceIndex); } -void GLScopeNGGUI::changeCurrentTrigger() +void GLScopeGUI::changeCurrentTrigger() { ScopeVisNG::TriggerData triggerData; fillTriggerData(triggerData); @@ -1191,7 +1191,7 @@ void GLScopeNGGUI::changeCurrentTrigger() m_scopeVis->changeTrigger(triggerData, currentTriggerIndex); } -void GLScopeNGGUI::fillProjectionCombo(QComboBox* comboBox) +void GLScopeGUI::fillProjectionCombo(QComboBox* comboBox) { comboBox->addItem("Real", Projector::ProjectionReal); comboBox->addItem("Imag", Projector::ProjectionImag); @@ -1206,7 +1206,7 @@ void GLScopeNGGUI::fillProjectionCombo(QComboBox* comboBox) comboBox->addItem("16PSK", Projector::Projection16PSK); } -void GLScopeNGGUI::disableLiveMode(bool disable) +void GLScopeGUI::disableLiveMode(bool disable) { ui->traceLen->setEnabled(!disable); ui->trig->setEnabled(!disable); @@ -1226,7 +1226,7 @@ void GLScopeNGGUI::disableLiveMode(bool disable) ui->freerun->setEnabled(!disable); } -void GLScopeNGGUI::fillTraceData(ScopeVisNG::TraceData& traceData) +void GLScopeGUI::fillTraceData(ScopeVisNG::TraceData& traceData) { traceData.m_projectionType = (Projector::ProjectionType) ui->traceMode->currentIndex(); traceData.m_hasTextOverlay = (traceData.m_projectionType == Projector::ProjectionMagDB) || (traceData.m_projectionType == Projector::ProjectionMagSq); @@ -1251,7 +1251,7 @@ void GLScopeNGGUI::fillTraceData(ScopeVisNG::TraceData& traceData) traceData.m_viewTrace = ui->traceView->isChecked(); } -void GLScopeNGGUI::fillTriggerData(ScopeVisNG::TriggerData& triggerData) +void GLScopeGUI::fillTriggerData(ScopeVisNG::TriggerData& triggerData) { triggerData.m_projectionType = (Projector::ProjectionType) ui->trigMode->currentIndex(); triggerData.m_inputIndex = 0; @@ -1268,7 +1268,7 @@ void GLScopeNGGUI::fillTriggerData(ScopeVisNG::TriggerData& triggerData) triggerData.setColor(m_focusedTriggerColor); } -void GLScopeNGGUI::setTraceUI(const ScopeVisNG::TraceData& traceData) +void GLScopeGUI::setTraceUI(const ScopeVisNG::TraceData& traceData) { TraceUIBlocker traceUIBlocker(ui); @@ -1292,7 +1292,7 @@ void GLScopeNGGUI::setTraceUI(const ScopeVisNG::TraceData& traceData) ui->traceView->setChecked(traceData.m_viewTrace); } -void GLScopeNGGUI::setTriggerUI(const ScopeVisNG::TriggerData& triggerData) +void GLScopeGUI::setTriggerUI(const ScopeVisNG::TriggerData& triggerData) { TrigUIBlocker trigUIBlocker(ui); @@ -1341,16 +1341,16 @@ void GLScopeNGGUI::setTriggerUI(const ScopeVisNG::TriggerData& triggerData) ui->trigColor->setStyleSheet(tr("QLabel { background-color : rgb(%1,%2,%3); }").arg(r).arg(g).arg(b)); } -void GLScopeNGGUI::applySettings() +void GLScopeGUI::applySettings() { } -bool GLScopeNGGUI::handleMessage(Message* message __attribute__((unused))) +bool GLScopeGUI::handleMessage(Message* message __attribute__((unused))) { return false; } -GLScopeNGGUI::TrigUIBlocker::TrigUIBlocker(Ui::GLScopeGUI *ui) : +GLScopeGUI::TrigUIBlocker::TrigUIBlocker(Ui::GLScopeGUI *ui) : m_ui(ui) { m_oldStateTrigMode = ui->trigMode->blockSignals(true); @@ -1364,12 +1364,12 @@ GLScopeNGGUI::TrigUIBlocker::TrigUIBlocker(Ui::GLScopeGUI *ui) : m_oldStateTrigDelayFine = ui->trigDelayFine->blockSignals(true); } -GLScopeNGGUI::TrigUIBlocker::~TrigUIBlocker() +GLScopeGUI::TrigUIBlocker::~TrigUIBlocker() { unBlock(); } -void GLScopeNGGUI::TrigUIBlocker::unBlock() +void GLScopeGUI::TrigUIBlocker::unBlock() { m_ui->trigMode->blockSignals(m_oldStateTrigMode); m_ui->trigCount->blockSignals(m_oldStateTrigCount); @@ -1382,7 +1382,7 @@ void GLScopeNGGUI::TrigUIBlocker::unBlock() m_ui->trigDelayFine->blockSignals(m_oldStateTrigDelayFine); } -GLScopeNGGUI::TraceUIBlocker::TraceUIBlocker(Ui::GLScopeGUI* ui) : +GLScopeGUI::TraceUIBlocker::TraceUIBlocker(Ui::GLScopeGUI* ui) : m_ui(ui) { m_oldStateTrace = m_ui->trace->blockSignals(true); @@ -1397,12 +1397,12 @@ GLScopeNGGUI::TraceUIBlocker::TraceUIBlocker(Ui::GLScopeGUI* ui) : m_oldStateTraceColor = m_ui->traceColor->blockSignals(true); } -GLScopeNGGUI::TraceUIBlocker::~TraceUIBlocker() +GLScopeGUI::TraceUIBlocker::~TraceUIBlocker() { unBlock(); } -void GLScopeNGGUI::TraceUIBlocker::unBlock() +void GLScopeGUI::TraceUIBlocker::unBlock() { m_ui->trace->blockSignals(m_oldStateTrace); m_ui->traceAdd->blockSignals(m_oldStateTraceAdd); @@ -1416,7 +1416,7 @@ void GLScopeNGGUI::TraceUIBlocker::unBlock() m_ui->traceColor->blockSignals(m_oldStateTraceColor); } -GLScopeNGGUI::MainUIBlocker::MainUIBlocker(Ui::GLScopeGUI* ui) : +GLScopeGUI::MainUIBlocker::MainUIBlocker(Ui::GLScopeGUI* ui) : m_ui(ui) { m_oldStateOnlyX = m_ui->onlyX->blockSignals(true); @@ -1429,12 +1429,12 @@ GLScopeNGGUI::MainUIBlocker::MainUIBlocker(Ui::GLScopeGUI* ui) : // m_oldStateTraceLen = m_ui->traceLen->blockSignals(true); } -GLScopeNGGUI::MainUIBlocker::~MainUIBlocker() +GLScopeGUI::MainUIBlocker::~MainUIBlocker() { unBlock(); } -void GLScopeNGGUI::MainUIBlocker::unBlock() +void GLScopeGUI::MainUIBlocker::unBlock() { m_ui->onlyX->blockSignals(m_oldStateOnlyX); m_ui->onlyY->blockSignals(m_oldStateOnlyY); @@ -1446,7 +1446,7 @@ void GLScopeNGGUI::MainUIBlocker::unBlock() // m_ui->traceLen->blockSignals(m_oldStateTraceLen); } -void GLScopeNGGUI::setDisplayMode(DisplayMode displayMode) +void GLScopeGUI::setDisplayMode(DisplayMode displayMode) { if (ui->trace->maximum() == 0) { @@ -1478,7 +1478,7 @@ void GLScopeNGGUI::setDisplayMode(DisplayMode displayMode) } } -void GLScopeNGGUI::setTraceIntensity(int value) +void GLScopeGUI::setTraceIntensity(int value) { if ((value < ui->traceIntensity->minimum()) || (value > ui->traceIntensity->maximum())) { return; @@ -1487,7 +1487,7 @@ void GLScopeNGGUI::setTraceIntensity(int value) ui->traceIntensity->setValue(value); } -void GLScopeNGGUI::setGridIntensity(int value) +void GLScopeGUI::setGridIntensity(int value) { if ((value < ui->gridIntensity->minimum()) || (value > ui->gridIntensity->maximum())) { return; @@ -1496,7 +1496,7 @@ void GLScopeNGGUI::setGridIntensity(int value) ui->gridIntensity->setValue(value); } -void GLScopeNGGUI::setTimeBase(int step) +void GLScopeGUI::setTimeBase(int step) { if ((step < ui->time->minimum()) || (step > ui->time->maximum())) { return; @@ -1505,7 +1505,7 @@ void GLScopeNGGUI::setTimeBase(int step) ui->time->setValue(step); } -void GLScopeNGGUI::setTimeOffset(int step) +void GLScopeGUI::setTimeOffset(int step) { if ((step < ui->timeOfs->minimum()) || (step > ui->timeOfs->maximum())) { return; @@ -1514,7 +1514,7 @@ void GLScopeNGGUI::setTimeOffset(int step) ui->timeOfs->setValue(step); } -void GLScopeNGGUI::setTraceLength(int step) +void GLScopeGUI::setTraceLength(int step) { if ((step < ui->traceLen->minimum()) || (step > ui->traceLen->maximum())) { return; @@ -1523,7 +1523,7 @@ void GLScopeNGGUI::setTraceLength(int step) ui->traceLen->setValue(step); } -void GLScopeNGGUI::setPreTrigger(int step) +void GLScopeGUI::setPreTrigger(int step) { if ((step < ui->trigPre->minimum()) || (step > ui->trigPre->maximum())) { return; @@ -1532,12 +1532,12 @@ void GLScopeNGGUI::setPreTrigger(int step) ui->trigPre->setValue(step); } -void GLScopeNGGUI::changeTrace(int traceIndex, const ScopeVisNG::TraceData& traceData) +void GLScopeGUI::changeTrace(int traceIndex, const ScopeVisNG::TraceData& traceData) { m_scopeVis->changeTrace(traceData, traceIndex); } -void GLScopeNGGUI::addTrace(const ScopeVisNG::TraceData& traceData) +void GLScopeGUI::addTrace(const ScopeVisNG::TraceData& traceData) { if (ui->trace->maximum() < 3) { @@ -1554,17 +1554,17 @@ void GLScopeNGGUI::addTrace(const ScopeVisNG::TraceData& traceData) } } -void GLScopeNGGUI::focusOnTrace(int traceIndex) +void GLScopeGUI::focusOnTrace(int traceIndex) { on_trace_valueChanged(traceIndex); } -void GLScopeNGGUI::changeTrigger(int triggerIndex, const ScopeVisNG::TriggerData& triggerData) +void GLScopeGUI::changeTrigger(int triggerIndex, const ScopeVisNG::TriggerData& triggerData) { m_scopeVis->changeTrigger(triggerData, triggerIndex); } -void GLScopeNGGUI::addTrigger(const ScopeVisNG::TriggerData& triggerData) +void GLScopeGUI::addTrigger(const ScopeVisNG::TriggerData& triggerData) { if (ui->trig->maximum() < 9) { @@ -1573,7 +1573,7 @@ void GLScopeNGGUI::addTrigger(const ScopeVisNG::TriggerData& triggerData) } } -void GLScopeNGGUI::focusOnTrigger(int triggerIndex) +void GLScopeGUI::focusOnTrigger(int triggerIndex) { on_trig_valueChanged(triggerIndex); } diff --git a/sdrgui/gui/glscopenggui.h b/sdrgui/gui/glscopenggui.h index 734a7f1ed..440e15d75 100644 --- a/sdrgui/gui/glscopenggui.h +++ b/sdrgui/gui/glscopenggui.h @@ -34,7 +34,7 @@ namespace Ui { class MessageQueue; class GLScopeNG; -class SDRGUI_API GLScopeNGGUI : public QWidget, public Serializable { +class SDRGUI_API GLScopeGUI : public QWidget, public Serializable { Q_OBJECT public: @@ -46,8 +46,8 @@ public: DisplayPol }; - explicit GLScopeNGGUI(QWidget* parent = 0); - ~GLScopeNGGUI(); + explicit GLScopeGUI(QWidget* parent = 0); + ~GLScopeGUI(); void setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScopeNG* glScope); From 081b83128f02290cad596344374f25e5898bb0fa Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 16:56:03 +0200 Subject: [PATCH 589/956] Renamed glscopenggui.h to glscopegui.h --- plugins/channelrx/chanalyzer/chanalyzergui.ui | 2 +- plugins/channelrx/demodatv/atvdemodgui.ui | 2 +- sdrgui/CMakeLists.txt | 2 +- sdrgui/gui/{glscopenggui.h => glscopegui.h} | 0 sdrgui/gui/glscopenggui.cpp | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename sdrgui/gui/{glscopenggui.h => glscopegui.h} (100%) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index 28e9e797d..1d0a9a050 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -750,7 +750,7 @@ GLScopeGUI QWidget -
    gui/glscopenggui.h
    +
    gui/glscopegui.h
    1
    diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index 0604beb0b..f8e933caa 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -1131,7 +1131,7 @@ GLScopeGUI QWidget -
    gui/glscopenggui.h
    +
    gui/glscopegui.h
    1
    diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index c3244503f..834ccc7b7 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -78,7 +78,7 @@ set(sdrgui_HEADERS gui/externalclockdialog.h gui/glscopeng.h gui/glscopemulti.h - gui/glscopenggui.h + gui/glscopegui.h gui/glscopemultigui.h gui/glshadersimple.h gui/glshadertvarray.h diff --git a/sdrgui/gui/glscopenggui.h b/sdrgui/gui/glscopegui.h similarity index 100% rename from sdrgui/gui/glscopenggui.h rename to sdrgui/gui/glscopegui.h diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index e21ce5fd4..a052b9dd2 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -17,7 +17,7 @@ #include -#include "glscopenggui.h" +#include "glscopegui.h" #include "glscopeng.h" #include "ui_glscopenggui.h" #include "util/simpleserializer.h" From 7769f4b21504f9fc41c4ecb0f374422315e20566 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 16:56:52 +0200 Subject: [PATCH 590/956] Renamed glscopenggui.cpp to glscopegui.cpp --- sdrgui/CMakeLists.txt | 2 +- sdrgui/gui/{glscopenggui.cpp => glscopegui.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename sdrgui/gui/{glscopenggui.cpp => glscopegui.cpp} (100%) diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 834ccc7b7..c3e474de6 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -22,7 +22,7 @@ set(sdrgui_SOURCES gui/externalclockdialog.cpp gui/glscopeng.cpp gui/glscopemulti.cpp - gui/glscopenggui.cpp + gui/glscopegui.cpp gui/glscopemultigui.cpp gui/glshadersimple.cpp gui/glshadertextured.cpp diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopegui.cpp similarity index 100% rename from sdrgui/gui/glscopenggui.cpp rename to sdrgui/gui/glscopegui.cpp From 0fcc694ca18b4308ab9d464f6a7da37611b7d057 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 17:01:56 +0200 Subject: [PATCH 591/956] Renamed GLScopeNG to GLScope --- plugins/channelrx/chanalyzer/chanalyzergui.ui | 4 +- plugins/channelrx/demodatv/atvdemodgui.ui | 4 +- sdrgui/dsp/scopevisng.cpp | 2 +- sdrgui/dsp/scopevisng.h | 6 +- sdrgui/gui/glscopegui.cpp | 30 ++++---- sdrgui/gui/glscopegui.h | 6 +- sdrgui/gui/glscopeng.cpp | 72 +++++++++---------- sdrgui/gui/glscopeng.h | 6 +- 8 files changed, 65 insertions(+), 65 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index 1d0a9a050..eebd82b5b 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -684,7 +684,7 @@ 3 - + 200 @@ -742,7 +742,7 @@
    gui/buttonswitch.h
    - GLScopeNG + GLScope QWidget
    gui/glscopeng.h
    1 diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index f8e933caa..e0d48cf3b 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -1088,7 +1088,7 @@ 0 - + 0 @@ -1123,7 +1123,7 @@
    gui/buttonswitch.h
    - GLScopeNG + GLScope QWidget
    gui/glscopeng.h
    1 diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index 92cacf781..ec3af3073 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -39,7 +39,7 @@ MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGMemoryTrace, Message) const uint ScopeVisNG::m_traceChunkSize = 4800; -ScopeVisNG::ScopeVisNG(GLScopeNG* glScope) : +ScopeVisNG::ScopeVisNG(GLScope* glScope) : m_glScope(glScope), m_preTriggerDelay(0), m_currentTriggerIndex(0), diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index 2560fb3b0..f8a16d1e8 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -37,7 +37,7 @@ #undef M_PI #define M_PI 3.14159265358979323846 -class GLScopeNG; +class GLScope; class SDRGUI_API ScopeVisNG : public BasebandSampleSink { @@ -147,7 +147,7 @@ public: static const uint32_t m_maxNbTraces = 10; static const uint32_t m_nbTraceMemories = 50; - ScopeVisNG(GLScopeNG* glScope = 0); + ScopeVisNG(GLScope* glScope = 0); virtual ~ScopeVisNG(); void setSampleRate(int sampleRate); @@ -941,7 +941,7 @@ private: bool m_reset; }; - GLScopeNG* m_glScope; + GLScope* m_glScope; uint32_t m_preTriggerDelay; //!< Pre-trigger delay in number of samples std::vector m_triggerConditions; //!< Chain of triggers uint32_t m_currentTriggerIndex; //!< Index of current index in the chain diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp index a052b9dd2..d553702a4 100644 --- a/sdrgui/gui/glscopegui.cpp +++ b/sdrgui/gui/glscopegui.cpp @@ -62,7 +62,7 @@ GLScopeGUI::~GLScopeGUI() delete ui; } -void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScopeNG* glScope) +void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScope* glScope) { qDebug("GLScopeGUI::setBuddies"); @@ -80,7 +80,7 @@ void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GL ui->horizontalXY->setEnabled(false); ui->verticalXY->setEnabled(false); ui->polar->setEnabled(false); - m_glScope->setDisplayMode(GLScopeNG::DisplayX); + m_glScope->setDisplayMode(GLScope::DisplayX); // initialize trigger combo ui->trigPos->setChecked(true); @@ -231,8 +231,8 @@ bool GLScopeGUI::deserialize(const QByteArray& data) ui->polar->setEnabled(false); ui->traceMode->setCurrentIndex(0); - d.readS32(1, &intValue, (int) GLScopeNG::DisplayX); - m_glScope->setDisplayMode((GLScopeNG::DisplayMode) intValue); + d.readS32(1, &intValue, (int) GLScope::DisplayX); + m_glScope->setDisplayMode((GLScope::DisplayMode) intValue); ui->onlyX->setChecked(false); ui->onlyY->setChecked(false); @@ -242,19 +242,19 @@ bool GLScopeGUI::deserialize(const QByteArray& data) switch (m_glScope->getDisplayMode()) { - case GLScopeNG::DisplayY: + case GLScope::DisplayY: ui->onlyY->setChecked(true); break; - case GLScopeNG::DisplayXYH: + case GLScope::DisplayXYH: ui->horizontalXY->setChecked(true); break; - case GLScopeNG::DisplayXYV: + case GLScope::DisplayXYV: ui->verticalXY->setChecked(true); break; - case GLScopeNG::DisplayPol: + case GLScope::DisplayPol: ui->polar->setChecked(true); break; - case GLScopeNG::DisplayX: + case GLScope::DisplayX: default: ui->onlyX->setChecked(true); break; @@ -430,7 +430,7 @@ void GLScopeGUI::on_onlyX_toggled(bool checked) ui->horizontalXY->setChecked(false); ui->verticalXY->setChecked(false); ui->polar->setChecked(false); - m_glScope->setDisplayMode(GLScopeNG::DisplayX); + m_glScope->setDisplayMode(GLScope::DisplayX); } else { @@ -448,7 +448,7 @@ void GLScopeGUI::on_onlyY_toggled(bool checked) ui->horizontalXY->setChecked(false); ui->verticalXY->setChecked(false); ui->polar->setChecked(false); - m_glScope->setDisplayMode(GLScopeNG::DisplayY); + m_glScope->setDisplayMode(GLScope::DisplayY); } else { @@ -466,7 +466,7 @@ void GLScopeGUI::on_horizontalXY_toggled(bool checked) ui->onlyY->setChecked(false); ui->verticalXY->setChecked(false); ui->polar->setChecked(false); - m_glScope->setDisplayMode(GLScopeNG::DisplayXYH); + m_glScope->setDisplayMode(GLScope::DisplayXYH); } else { @@ -484,7 +484,7 @@ void GLScopeGUI::on_verticalXY_toggled(bool checked) ui->onlyY->setChecked(false); ui->horizontalXY->setChecked(false); ui->polar->setChecked(false); - m_glScope->setDisplayMode(GLScopeNG::DisplayXYV); + m_glScope->setDisplayMode(GLScope::DisplayXYV); } else { @@ -502,7 +502,7 @@ void GLScopeGUI::on_polar_toggled(bool checked) ui->onlyY->setChecked(false); ui->horizontalXY->setChecked(false); ui->verticalXY->setChecked(false); - m_glScope->setDisplayMode(GLScopeNG::DisplayPol); + m_glScope->setDisplayMode(GLScope::DisplayPol); } else { @@ -613,7 +613,7 @@ void GLScopeGUI::on_traceDel_clicked(bool checked __attribute__((unused))) ui->horizontalXY->setEnabled(false); ui->verticalXY->setEnabled(false); ui->polar->setEnabled(false); - m_glScope->setDisplayMode(GLScopeNG::DisplayX); + m_glScope->setDisplayMode(GLScope::DisplayX); } m_scopeVis->removeTrace(ui->trace->value()); diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h index 440e15d75..96fea8ef5 100644 --- a/sdrgui/gui/glscopegui.h +++ b/sdrgui/gui/glscopegui.h @@ -32,7 +32,7 @@ namespace Ui { } class MessageQueue; -class GLScopeNG; +class GLScope; class SDRGUI_API GLScopeGUI : public QWidget, public Serializable { Q_OBJECT @@ -49,7 +49,7 @@ public: explicit GLScopeGUI(QWidget* parent = 0); ~GLScopeGUI(); - void setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScopeNG* glScope); + void setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScope* glScope); void setSampleRate(int sampleRate); void resetToDefaults(); @@ -144,7 +144,7 @@ private: MessageQueue* m_messageQueue; ScopeVisNG* m_scopeVis; - GLScopeNG* m_glScope; + GLScope* m_glScope; int m_sampleRate; int m_timeBase; diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index d8d7b9ba3..99633b431 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -26,7 +26,7 @@ #include "glscopeng.h" -GLScopeNG::GLScopeNG(QWidget* parent) : +GLScope::GLScope(QWidget* parent) : QGLWidget(parent), m_tracesData(0), m_traces(0), @@ -65,12 +65,12 @@ GLScopeNG::GLScopeNG(QWidget* parent) : //m_traceCounter = 0; } -GLScopeNG::~GLScopeNG() +GLScope::~GLScope() { cleanup(); } -void GLScopeNG::setDisplayGridIntensity(int intensity) +void GLScope::setDisplayGridIntensity(int intensity) { m_displayGridIntensity = intensity; if (m_displayGridIntensity > 100) { @@ -81,7 +81,7 @@ void GLScopeNG::setDisplayGridIntensity(int intensity) update(); } -void GLScopeNG::setDisplayTraceIntensity(int intensity) +void GLScope::setDisplayTraceIntensity(int intensity) { m_displayTraceIntensity = intensity; if (m_displayTraceIntensity > 100) { @@ -92,13 +92,13 @@ void GLScopeNG::setDisplayTraceIntensity(int intensity) update(); } -void GLScopeNG::setTraces(std::vector* tracesData, std::vector* traces) +void GLScope::setTraces(std::vector* tracesData, std::vector* traces) { m_tracesData = tracesData; m_traces = traces; } -void GLScopeNG::newTraces(std::vector* traces) +void GLScope::newTraces(std::vector* traces) { if (traces->size() > 0) { @@ -112,22 +112,22 @@ void GLScopeNG::newTraces(std::vector* traces) } } -void GLScopeNG::initializeGL() +void GLScope::initializeGL() { QOpenGLContext *glCurrentContext = QOpenGLContext::currentContext(); if (glCurrentContext) { if (QOpenGLContext::currentContext()->isValid()) { - qDebug() << "GLScopeNG::initializeGL: context:" + qDebug() << "GLScope::initializeGL: context:" << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion() << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion() << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no"); } else { - qDebug() << "GLScopeNG::initializeGL: current context is invalid"; + qDebug() << "GLScope::initializeGL: current context is invalid"; } } else { - qCritical() << "GLScopeNG::initializeGL: no current context"; + qCritical() << "GLScope::initializeGL: no current context"; return; } @@ -135,25 +135,25 @@ void GLScopeNG::initializeGL() if (surface == 0) { - qCritical() << "GLScopeNG::initializeGL: no surface attached"; + qCritical() << "GLScope::initializeGL: no surface attached"; return; } else { if (surface->surfaceType() != QSurface::OpenGLSurface) { - qCritical() << "GLScopeNG::initializeGL: surface is not an OpenGLSurface: " << surface->surfaceType() + qCritical() << "GLScope::initializeGL: surface is not an OpenGLSurface: " << surface->surfaceType() << " cannot use an OpenGL context"; return; } else { - qDebug() << "GLScopeNG::initializeGL: OpenGL surface:" + qDebug() << "GLScope::initializeGL: OpenGL surface:" << " class: " << (surface->surfaceClass() == QSurface::Window ? "Window" : "Offscreen"); } } - connect(glCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, &GLScopeNG::cleanup); // TODO: when migrating to QOpenGLWidget + connect(glCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, &GLScope::cleanup); // TODO: when migrating to QOpenGLWidget QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); glFunctions->initializeOpenGLFunctions(); @@ -167,14 +167,14 @@ void GLScopeNG::initializeGL() m_glShaderPowerOverlay.initializeGL(); } -void GLScopeNG::resizeGL(int width, int height) +void GLScope::resizeGL(int width, int height) { QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); glFunctions->glViewport(0, 0, width, height); m_configChanged = true; } -void GLScopeNG::paintGL() +void GLScope::paintGL() { if(!m_mutex.tryLock(2)) return; @@ -182,7 +182,7 @@ void GLScopeNG::paintGL() if(m_configChanged) applyConfig(); -// qDebug("GLScopeNG::paintGL: m_traceCounter: %d", m_traceCounter); +// qDebug("GLScope::paintGL: m_traceCounter: %d", m_traceCounter); // m_traceCounter = 0; m_dataChanged = false; @@ -923,7 +923,7 @@ void GLScopeNG::paintGL() m_mutex.unlock(); } -void GLScopeNG::setSampleRate(int sampleRate) +void GLScope::setSampleRate(int sampleRate) { m_sampleRate = sampleRate; m_configChanged = true; @@ -931,55 +931,55 @@ void GLScopeNG::setSampleRate(int sampleRate) emit sampleRateChanged(m_sampleRate); } -void GLScopeNG::setTimeBase(int timeBase) +void GLScope::setTimeBase(int timeBase) { m_timeBase = timeBase; m_configChanged = true; update(); } -void GLScopeNG::setTriggerPre(uint32_t triggerPre) +void GLScope::setTriggerPre(uint32_t triggerPre) { m_triggerPre = triggerPre; m_configChanged = true; update(); } -void GLScopeNG::setTimeOfsProMill(int timeOfsProMill) +void GLScope::setTimeOfsProMill(int timeOfsProMill) { m_timeOfsProMill = timeOfsProMill; m_configChanged = true; update(); } -void GLScopeNG::setFocusedTraceIndex(uint32_t traceIndex) +void GLScope::setFocusedTraceIndex(uint32_t traceIndex) { m_focusedTraceIndex = traceIndex; m_configChanged = true; update(); } -void GLScopeNG::setDisplayMode(DisplayMode displayMode) +void GLScope::setDisplayMode(DisplayMode displayMode) { m_displayMode = displayMode; m_configChanged = true; update(); } -void GLScopeNG::setTraceSize(int traceSize) +void GLScope::setTraceSize(int traceSize) { m_traceSize = traceSize; m_configChanged = true; update(); } -void GLScopeNG::updateDisplay() +void GLScope::updateDisplay() { m_configChanged = true; update(); } -void GLScopeNG::applyConfig() +void GLScope::applyConfig() { m_configChanged = false; @@ -1048,7 +1048,7 @@ void GLScopeNG::applyConfig() m_q3Polar.allocate(2*(end - start)); } -void GLScopeNG::setUniqueDisplays() +void GLScope::setUniqueDisplays() { QFontMetrics fm(font()); int M = fm.width("-"); @@ -1250,7 +1250,7 @@ void GLScopeNG::setUniqueDisplays() } // Y vertical scale } -void GLScopeNG::setVerticalDisplays() +void GLScope::setVerticalDisplays() { QFontMetrics fm(font()); int M = fm.width("-"); @@ -1451,7 +1451,7 @@ void GLScopeNG::setVerticalDisplays() } // Y vertical scale (Y2) } -void GLScopeNG::setHorizontalDisplays() +void GLScope::setHorizontalDisplays() { QFontMetrics fm(font()); int M = fm.width("-"); @@ -1651,7 +1651,7 @@ void GLScopeNG::setHorizontalDisplays() } // Y vertical scale (Y2) } -void GLScopeNG::setPolarDisplays() +void GLScope::setPolarDisplays() { QFontMetrics fm(font()); int M = fm.width("-"); @@ -1863,7 +1863,7 @@ void GLScopeNG::setPolarDisplays() } // Polar XY vertical scale (Y2) } -void GLScopeNG::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) +void GLScope::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) { ScopeVisNG::TraceData& traceData = (*m_tracesData)[highlightedTraceIndex]; double amp_range = 2.0 / traceData.m_amp; @@ -1908,7 +1908,7 @@ void GLScopeNG::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) } } -void GLScopeNG::drawChannelOverlay( +void GLScope::drawChannelOverlay( const QString& text, const QColor& color, QPixmap& channelOverlayPixmap, @@ -1964,22 +1964,22 @@ void GLScopeNG::drawChannelOverlay( } } -void GLScopeNG::tick() +void GLScope::tick() { if(m_dataChanged) { update(); } } -void GLScopeNG::connectTimer(const QTimer& timer) +void GLScope::connectTimer(const QTimer& timer) { - qDebug() << "GLScopeNG::connectTimer"; + qDebug() << "GLScope::connectTimer"; disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); m_timer.stop(); } -void GLScopeNG::cleanup() +void GLScope::cleanup() { //makeCurrent(); m_glShaderSimple.cleanup(); diff --git a/sdrgui/gui/glscopeng.h b/sdrgui/gui/glscopeng.h index 12f1cca99..722fc1965 100644 --- a/sdrgui/gui/glscopeng.h +++ b/sdrgui/gui/glscopeng.h @@ -35,7 +35,7 @@ class QPainter; -class SDRGUI_API GLScopeNG: public QGLWidget { +class SDRGUI_API GLScope: public QGLWidget { Q_OBJECT public: @@ -47,8 +47,8 @@ public: DisplayPol }; - GLScopeNG(QWidget* parent = 0); - virtual ~GLScopeNG(); + GLScope(QWidget* parent = 0); + virtual ~GLScope(); void connectTimer(const QTimer& timer); From bda60dcd4333d617de0faa072a9cf0435f1499d3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 17:06:55 +0200 Subject: [PATCH 592/956] Renamed glscopeng.h to glscope.h --- plugins/channelrx/chanalyzer/chanalyzergui.cpp | 2 +- plugins/channelrx/chanalyzer/chanalyzergui.ui | 2 +- plugins/channelrx/demodatv/atvdemodgui.ui | 2 +- sdrgui/CMakeLists.txt | 2 +- sdrgui/dsp/scopevisng.cpp | 2 +- sdrgui/gui/{glscopeng.h => glscope.h} | 0 sdrgui/gui/glscopegui.cpp | 2 +- sdrgui/gui/glscopeng.cpp | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename sdrgui/gui/{glscopeng.h => glscope.h} (100%) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 966bb6898..62088a1f8 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -25,7 +25,7 @@ #include "dsp/spectrumscopengcombovis.h" #include "dsp/spectrumvis.h" #include "gui/glspectrum.h" -#include "gui/glscopeng.h" +#include "gui/glscope.h" #include "gui/basicchannelsettingsdialog.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index eebd82b5b..30b482199 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -744,7 +744,7 @@ GLScope QWidget -
    gui/glscopeng.h
    +
    gui/glscope.h
    1
    diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index e0d48cf3b..4107c6a50 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -1125,7 +1125,7 @@ GLScope QWidget -
    gui/glscopeng.h
    +
    gui/glscope.h
    1
    diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index c3e474de6..fb8b7e215 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -76,7 +76,7 @@ set(sdrgui_HEADERS gui/editcommanddialog.h gui/externalclockbutton.h gui/externalclockdialog.h - gui/glscopeng.h + gui/glscope.h gui/glscopemulti.h gui/glscopegui.h gui/glscopemultigui.h diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index ec3af3073..a271488d5 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -20,7 +20,7 @@ #include "scopevisng.h" #include "dsp/dspcommands.h" -#include "gui/glscopeng.h" +#include "gui/glscope.h" MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgConfigureScopeVisNG, Message) MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGAddTrigger, Message) diff --git a/sdrgui/gui/glscopeng.h b/sdrgui/gui/glscope.h similarity index 100% rename from sdrgui/gui/glscopeng.h rename to sdrgui/gui/glscope.h diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp index d553702a4..1f32962e6 100644 --- a/sdrgui/gui/glscopegui.cpp +++ b/sdrgui/gui/glscopegui.cpp @@ -18,7 +18,7 @@ #include #include "glscopegui.h" -#include "glscopeng.h" +#include "glscope.h" #include "ui_glscopenggui.h" #include "util/simpleserializer.h" diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index 99633b431..4d42231d1 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -24,7 +24,7 @@ #include #include -#include "glscopeng.h" +#include "glscope.h" GLScope::GLScope(QWidget* parent) : QGLWidget(parent), From b7b9c8af8abf0187fe6937ac9ced2dbf367966d7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 17:07:43 +0200 Subject: [PATCH 593/956] Renamed glscopeng.cpp to glscope.cpp --- sdrgui/CMakeLists.txt | 2 +- sdrgui/gui/{glscopeng.cpp => glscope.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename sdrgui/gui/{glscopeng.cpp => glscope.cpp} (100%) diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index fb8b7e215..a913b9768 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -20,7 +20,7 @@ set(sdrgui_SOURCES gui/editcommanddialog.cpp gui/externalclockbutton.cpp gui/externalclockdialog.cpp - gui/glscopeng.cpp + gui/glscope.cpp gui/glscopemulti.cpp gui/glscopegui.cpp gui/glscopemultigui.cpp diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscope.cpp similarity index 100% rename from sdrgui/gui/glscopeng.cpp rename to sdrgui/gui/glscope.cpp From 37ab2e442e7abd3f42077377167af680133ade18 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 17:10:31 +0200 Subject: [PATCH 594/956] Renamed SpectrumScopeNGComboVis to SpectrumScopeComboVis --- plugins/channelrx/chanalyzer/chanalyzergui.cpp | 4 ++-- plugins/channelrx/chanalyzer/chanalyzergui.h | 4 ++-- sdrgui/dsp/spectrumscopengcombovis.cpp | 14 +++++++------- sdrgui/dsp/spectrumscopengcombovis.h | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 62088a1f8..ff5be180f 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -387,8 +387,8 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); m_scopeVis = new ScopeVisNG(ui->glScope); - m_spectrumScopeComboVis = new SpectrumScopeNGComboVis(m_spectrumVis, m_scopeVis); - m_channelAnalyzer = (ChannelAnalyzer*) rxChannel; //new ChannelAnalyzerNG(m_deviceUISet->m_deviceSourceAPI); + m_spectrumScopeComboVis = new SpectrumScopeComboVis(m_spectrumVis, m_scopeVis); + m_channelAnalyzer = (ChannelAnalyzer*) rxChannel; //new ChannelAnalyzer(m_deviceUISet->m_deviceSourceAPI); m_channelAnalyzer->setSampleSink(m_spectrumScopeComboVis); m_channelAnalyzer->setMessageQueueToGUI(getInputMessageQueue()); diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h index 48c55695a..bc24ca660 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ b/plugins/channelrx/chanalyzer/chanalyzergui.h @@ -30,7 +30,7 @@ class PluginAPI; class DeviceUISet; class BasebandSampleSink; class ChannelAnalyzer; -class SpectrumScopeNGComboVis; +class SpectrumScopeComboVis; class SpectrumVis; class ScopeVisNG; @@ -71,7 +71,7 @@ private: MovingAverageUtil m_channelPowerAvg; ChannelAnalyzer* m_channelAnalyzer; - SpectrumScopeNGComboVis* m_spectrumScopeComboVis; + SpectrumScopeComboVis* m_spectrumScopeComboVis; SpectrumVis* m_spectrumVis; ScopeVisNG* m_scopeVis; MessageQueue m_inputMessageQueue; diff --git a/sdrgui/dsp/spectrumscopengcombovis.cpp b/sdrgui/dsp/spectrumscopengcombovis.cpp index 01e23b76f..7c4fbfb41 100644 --- a/sdrgui/dsp/spectrumscopengcombovis.cpp +++ b/sdrgui/dsp/spectrumscopengcombovis.cpp @@ -2,37 +2,37 @@ #include "dsp/dspcommands.h" #include "util/messagequeue.h" -SpectrumScopeNGComboVis::SpectrumScopeNGComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis) : +SpectrumScopeComboVis::SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis) : m_spectrumVis(spectrumVis), m_scopeVis(scopeVis) { - setObjectName("SpectrumScopeNGComboVis"); + setObjectName("SpectrumScopeComboVis"); } -SpectrumScopeNGComboVis::~SpectrumScopeNGComboVis() +SpectrumScopeComboVis::~SpectrumScopeComboVis() { } -void SpectrumScopeNGComboVis::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) +void SpectrumScopeComboVis::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) { m_scopeVis->feed(begin, end, false); SampleVector::const_iterator triggerPoint = m_scopeVis->getTriggerPoint(); m_spectrumVis->feedTriggered(triggerPoint, end, positiveOnly); } -void SpectrumScopeNGComboVis::start() +void SpectrumScopeComboVis::start() { m_spectrumVis->start(); m_scopeVis->start(); } -void SpectrumScopeNGComboVis::stop() +void SpectrumScopeComboVis::stop() { m_spectrumVis->stop(); m_scopeVis->stop(); } -bool SpectrumScopeNGComboVis::handleMessage(const Message& message) +bool SpectrumScopeComboVis::handleMessage(const Message& message) { bool spectDone = m_spectrumVis->handleMessage(message); bool scopeDone = m_scopeVis->handleMessage(message); diff --git a/sdrgui/dsp/spectrumscopengcombovis.h b/sdrgui/dsp/spectrumscopengcombovis.h index 24ee8ce37..9d3f157f4 100644 --- a/sdrgui/dsp/spectrumscopengcombovis.h +++ b/sdrgui/dsp/spectrumscopengcombovis.h @@ -8,11 +8,11 @@ class Message; -class SDRGUI_API SpectrumScopeNGComboVis : public BasebandSampleSink { +class SDRGUI_API SpectrumScopeComboVis : public BasebandSampleSink { public: - SpectrumScopeNGComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis); - virtual ~SpectrumScopeNGComboVis(); + SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis); + virtual ~SpectrumScopeComboVis(); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); From 4a733cae51f7102bfdb9653766e0592e89f40a65 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 17:13:06 +0200 Subject: [PATCH 595/956] Renamed spectrumscopengcombovis.* to spectrumscopecombovis.* --- plugins/channelrx/chanalyzer/chanalyzergui.cpp | 2 +- sdrgui/CMakeLists.txt | 4 ++-- ...{spectrumscopengcombovis.cpp => spectrumscopecombovis.cpp} | 2 +- .../{spectrumscopengcombovis.h => spectrumscopecombovis.h} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename sdrgui/dsp/{spectrumscopengcombovis.cpp => spectrumscopecombovis.cpp} (96%) rename sdrgui/dsp/{spectrumscopengcombovis.h => spectrumscopecombovis.h} (100%) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index ff5be180f..5ee517885 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -22,7 +22,7 @@ #include "dsp/threadedbasebandsamplesink.h" #include "ui_chanalyzergui.h" -#include "dsp/spectrumscopengcombovis.h" +#include "dsp/spectrumscopecombovis.h" #include "dsp/spectrumvis.h" #include "gui/glspectrum.h" #include "gui/glscope.h" diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index a913b9768..1cc1e0cd8 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -50,7 +50,7 @@ set(sdrgui_SOURCES dsp/scopevismulti.cpp dsp/scopevisxy.cpp dsp/spectrumvis.cpp - dsp/spectrumscopengcombovis.cpp + dsp/spectrumscopecombovis.cpp device/deviceuiset.cpp @@ -107,7 +107,7 @@ set(sdrgui_HEADERS dsp/scopevismulti.h dsp/scopevisxy.h dsp/spectrumvis.h - dsp/spectrumscopengcombovis.h + dsp/spectrumscopecombovis.h device/deviceuiset.h diff --git a/sdrgui/dsp/spectrumscopengcombovis.cpp b/sdrgui/dsp/spectrumscopecombovis.cpp similarity index 96% rename from sdrgui/dsp/spectrumscopengcombovis.cpp rename to sdrgui/dsp/spectrumscopecombovis.cpp index 7c4fbfb41..ad1cc89a8 100644 --- a/sdrgui/dsp/spectrumscopengcombovis.cpp +++ b/sdrgui/dsp/spectrumscopecombovis.cpp @@ -1,4 +1,4 @@ -#include "dsp/spectrumscopengcombovis.h" +#include "dsp/spectrumscopecombovis.h" #include "dsp/dspcommands.h" #include "util/messagequeue.h" diff --git a/sdrgui/dsp/spectrumscopengcombovis.h b/sdrgui/dsp/spectrumscopecombovis.h similarity index 100% rename from sdrgui/dsp/spectrumscopengcombovis.h rename to sdrgui/dsp/spectrumscopecombovis.h From aa6f2e9f2ff4ea59f84519b7e94b253f5a59d3f3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 17:18:58 +0200 Subject: [PATCH 596/956] Renamed ScopeVisNG to ScopeVis --- .../channelrx/chanalyzer/chanalyzergui.cpp | 2 +- plugins/channelrx/chanalyzer/chanalyzergui.h | 4 +- plugins/channelrx/demodatv/atvdemodgui.cpp | 6 +- plugins/channelrx/demodatv/atvdemodgui.h | 4 +- sdrgui/dsp/scopevisng.cpp | 128 +++++++++--------- sdrgui/dsp/scopevisng.h | 12 +- sdrgui/dsp/spectrumscopecombovis.cpp | 2 +- sdrgui/dsp/spectrumscopecombovis.h | 4 +- sdrgui/gui/glscope.cpp | 12 +- sdrgui/gui/glscope.h | 8 +- sdrgui/gui/glscopegui.cpp | 82 +++++------ sdrgui/gui/glscopegui.h | 20 +-- 12 files changed, 142 insertions(+), 142 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 5ee517885..d9ae040f9 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -386,7 +386,7 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); - m_scopeVis = new ScopeVisNG(ui->glScope); + m_scopeVis = new ScopeVis(ui->glScope); m_spectrumScopeComboVis = new SpectrumScopeComboVis(m_spectrumVis, m_scopeVis); m_channelAnalyzer = (ChannelAnalyzer*) rxChannel; //new ChannelAnalyzer(m_deviceUISet->m_deviceSourceAPI); m_channelAnalyzer->setSampleSink(m_spectrumScopeComboVis); diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h index bc24ca660..04a278ad5 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ b/plugins/channelrx/chanalyzer/chanalyzergui.h @@ -32,7 +32,7 @@ class BasebandSampleSink; class ChannelAnalyzer; class SpectrumScopeComboVis; class SpectrumVis; -class ScopeVisNG; +class ScopeVis; namespace Ui { class ChannelAnalyzerGUI; @@ -73,7 +73,7 @@ private: ChannelAnalyzer* m_channelAnalyzer; SpectrumScopeComboVis* m_spectrumScopeComboVis; SpectrumVis* m_spectrumVis; - ScopeVisNG* m_scopeVis; + ScopeVis* m_scopeVis; MessageQueue m_inputMessageQueue; explicit ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); diff --git a/plugins/channelrx/demodatv/atvdemodgui.cpp b/plugins/channelrx/demodatv/atvdemodgui.cpp index f4a31e7d2..80a3af7db 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.cpp +++ b/plugins/channelrx/demodatv/atvdemodgui.cpp @@ -282,7 +282,7 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - m_scopeVis = new ScopeVisNG(ui->glScope); + m_scopeVis = new ScopeVis(ui->glScope); m_atvDemod = (ATVDemod*) rxChannel; //new ATVDemod(m_deviceUISet->m_deviceSourceAPI); m_atvDemod->setMessageQueueToGUI(getInputMessageQueue()); m_atvDemod->setScopeSink(m_scopeVis); @@ -313,14 +313,14 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base resetToDefaults(); // does applySettings() ui->scopeGUI->setPreTrigger(1); - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; traceData.m_amp = 2.0; // amplification factor traceData.m_ampIndex = 1; // this is second step traceData.m_ofs = 0.5; // direct offset traceData.m_ofsCoarse = 50; // this is 50 coarse steps ui->scopeGUI->changeTrace(0, traceData); ui->scopeGUI->focusOnTrace(0); // re-focus to take changes into account in the GUI - ScopeVisNG::TriggerData triggerData; + ScopeVis::TriggerData triggerData; triggerData.m_triggerLevel = 0.1; triggerData.m_triggerLevelCoarse = 10; triggerData.m_triggerPositiveEdge = false; diff --git a/plugins/channelrx/demodatv/atvdemodgui.h b/plugins/channelrx/demodatv/atvdemodgui.h index 07defe5cc..5c0d1e457 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.h +++ b/plugins/channelrx/demodatv/atvdemodgui.h @@ -28,7 +28,7 @@ class PluginAPI; class DeviceUISet; class BasebandSampleSink; class ATVDemod; -class ScopeVisNG; +class ScopeVis; namespace Ui { @@ -70,7 +70,7 @@ private: MovingAverageUtil m_objMagSqAverage; int m_intTickCount; - ScopeVisNG* m_scopeVis; + ScopeVis* m_scopeVis; float m_fltLineTimeMultiplier; float m_fltTopTimeMultiplier; diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index a271488d5..75bb58f54 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -22,24 +22,24 @@ #include "dsp/dspcommands.h" #include "gui/glscope.h" -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgConfigureScopeVisNG, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGAddTrigger, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGChangeTrigger, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGRemoveTrigger, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGMoveTrigger, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGFocusOnTrigger, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGAddTrace, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGChangeTrace, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGRemoveTrace, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGMoveTrace, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGFocusOnTrace, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGOneShot, Message) -MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGMemoryTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgConfigureScopeVisNG, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGAddTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGChangeTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGRemoveTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGMoveTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGFocusOnTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGAddTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGChangeTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGRemoveTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGMoveTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGFocusOnTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGOneShot, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGMemoryTrace, Message) -const uint ScopeVisNG::m_traceChunkSize = 4800; +const uint ScopeVis::m_traceChunkSize = 4800; -ScopeVisNG::ScopeVisNG(GLScope* glScope) : +ScopeVis::ScopeVis(GLScope* glScope) : m_glScope(glScope), m_preTriggerDelay(0), m_currentTriggerIndex(0), @@ -59,7 +59,7 @@ ScopeVisNG::ScopeVisNG(GLScope* glScope) : m_triggerWaitForReset(false), m_currentTraceMemoryIndex(0) { - setObjectName("ScopeVisNG"); + setObjectName("ScopeVis"); m_traceDiscreteMemory.resize(m_traceChunkSize); // arbitrary m_glScope->setTraces(&m_traces.m_tracesData, &m_traces.m_traces[0]); for (int i = 0; i < (int) Projector::nbProjectionTypes; i++) { @@ -67,14 +67,14 @@ ScopeVisNG::ScopeVisNG(GLScope* glScope) : } } -ScopeVisNG::~ScopeVisNG() +ScopeVis::~ScopeVis() { for (std::vector::iterator it = m_triggerConditions.begin(); it != m_triggerConditions.end(); ++ it) { delete *it; } } -void ScopeVisNG::setSampleRate(int sampleRate) +void ScopeVis::setSampleRate(int sampleRate) { if (sampleRate != m_sampleRate) { @@ -83,15 +83,15 @@ void ScopeVisNG::setSampleRate(int sampleRate) } } -void ScopeVisNG::configure(uint32_t traceSize, uint32_t timeBase, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun) +void ScopeVis::configure(uint32_t traceSize, uint32_t timeBase, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun) { Message* cmd = MsgConfigureScopeVisNG::create(traceSize, timeBase, timeOfsProMill, triggerPre, freeRun); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::addTrace(const TraceData& traceData) +void ScopeVis::addTrace(const TraceData& traceData) { - qDebug() << "ScopeVisNG::addTrace:" + qDebug() << "ScopeVis::addTrace:" << " m_amp: " << traceData.m_amp << " m_ofs: " << traceData.m_ofs << " m_traceDelay: " << traceData.m_traceDelay; @@ -99,9 +99,9 @@ void ScopeVisNG::addTrace(const TraceData& traceData) getInputMessageQueue()->push(cmd); } -void ScopeVisNG::changeTrace(const TraceData& traceData, uint32_t traceIndex) +void ScopeVis::changeTrace(const TraceData& traceData, uint32_t traceIndex) { - qDebug() << "ScopeVisNG::changeTrace:" + qDebug() << "ScopeVis::changeTrace:" << " trace: " << traceIndex << " m_amp: " << traceData.m_amp << " m_ofs: " << traceData.m_ofs @@ -110,72 +110,72 @@ void ScopeVisNG::changeTrace(const TraceData& traceData, uint32_t traceIndex) getInputMessageQueue()->push(cmd); } -void ScopeVisNG::removeTrace(uint32_t traceIndex) +void ScopeVis::removeTrace(uint32_t traceIndex) { - qDebug() << "ScopeVisNG::removeTrace:" + qDebug() << "ScopeVis::removeTrace:" << " trace: " << traceIndex; Message* cmd = MsgScopeVisNGRemoveTrace::create(traceIndex); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::moveTrace(uint32_t traceIndex, bool upElseDown) +void ScopeVis::moveTrace(uint32_t traceIndex, bool upElseDown) { - qDebug() << "ScopeVisNG::moveTrace:" + qDebug() << "ScopeVis::moveTrace:" << " trace: " << traceIndex << " up: " << upElseDown; Message* cmd = MsgScopeVisNGMoveTrace::create(traceIndex, upElseDown); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::focusOnTrace(uint32_t traceIndex) +void ScopeVis::focusOnTrace(uint32_t traceIndex) { Message* cmd = MsgScopeVisNGFocusOnTrace::create(traceIndex); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::addTrigger(const TriggerData& triggerData) +void ScopeVis::addTrigger(const TriggerData& triggerData) { Message* cmd = MsgScopeVisNGAddTrigger::create(triggerData); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::changeTrigger(const TriggerData& triggerData, uint32_t triggerIndex) +void ScopeVis::changeTrigger(const TriggerData& triggerData, uint32_t triggerIndex) { Message* cmd = MsgScopeVisNGChangeTrigger::create(triggerData, triggerIndex); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::removeTrigger(uint32_t triggerIndex) +void ScopeVis::removeTrigger(uint32_t triggerIndex) { Message* cmd = MsgScopeVisNGRemoveTrigger::create(triggerIndex); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::moveTrigger(uint32_t triggerIndex, bool upElseDown) +void ScopeVis::moveTrigger(uint32_t triggerIndex, bool upElseDown) { Message* cmd = MsgScopeVisNGMoveTrigger::create(triggerIndex, upElseDown); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::focusOnTrigger(uint32_t triggerIndex) +void ScopeVis::focusOnTrigger(uint32_t triggerIndex) { Message* cmd = MsgScopeVisNGFocusOnTrigger::create(triggerIndex); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::setOneShot(bool oneShot) +void ScopeVis::setOneShot(bool oneShot) { Message* cmd = MsgScopeVisNGOneShot::create(oneShot); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::setMemoryIndex(uint32_t memoryIndex) +void ScopeVis::setMemoryIndex(uint32_t memoryIndex) { Message* cmd = MsgScopeVisNGMemoryTrace::create(memoryIndex); getInputMessageQueue()->push(cmd); } -void ScopeVisNG::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) +void ScopeVis::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) { if (m_freeRun) { m_triggerPoint = cbegin; @@ -228,7 +228,7 @@ void ScopeVisNG::feed(const SampleVector::const_iterator& cbegin, const SampleVe m_mutex.unlock(); } -void ScopeVisNG::processMemoryTrace() +void ScopeVis::processMemoryTrace() { if ((m_currentTraceMemoryIndex > 0) && (m_currentTraceMemoryIndex < m_nbTraceMemories)) { @@ -242,7 +242,7 @@ void ScopeVisNG::processMemoryTrace() } } -void ScopeVisNG::processTrace(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, int& triggerPointToEnd) +void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, int& triggerPointToEnd) { SampleVector::const_iterator begin(cbegin); @@ -393,13 +393,13 @@ void ScopeVisNG::processTrace(const SampleVector::const_iterator& cbegin, const triggerPointToEnd = mTriggerPointToEnd; } - //qDebug("ScopeVisNG::processTrace: process remainder recursively (%d %d)", mpoint, mTriggerPoint); + //qDebug("ScopeVis::processTrace: process remainder recursively (%d %d)", mpoint, mTriggerPoint); } } } } -bool ScopeVisNG::nextTrigger() +bool ScopeVis::nextTrigger() { TriggerCondition *triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition @@ -434,7 +434,7 @@ bool ScopeVisNG::nextTrigger() } } -int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool traceBack) +int ScopeVis::processTraces(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool traceBack) { SampleVector::const_iterator begin(cbegin); uint32_t shift = (m_timeOfsProMill / 1000.0) * m_traceSize; @@ -559,7 +559,7 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const if (m_nbSamples == 0) // finished { - //sqDebug("ScopeVisNG::processTraces: m_traceCount: %d", m_traces.m_tracesControl.begin()->m_traceCount[m_traces.currentBufferIndex()]); + //sqDebug("ScopeVis::processTraces: m_traceCount: %d", m_traces.m_tracesControl.begin()->m_traceCount[m_traces.currentBufferIndex()]); m_glScope->newTraces(&m_traces.m_traces[m_traces.currentBufferIndex()]); m_traces.switchBuffer(); return end - begin; // return remainder count @@ -570,21 +570,21 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const } } -void ScopeVisNG::start() +void ScopeVis::start() { } -void ScopeVisNG::stop() +void ScopeVis::stop() { } -bool ScopeVisNG::handleMessage(const Message& message) +bool ScopeVis::handleMessage(const Message& message) { if (DSPSignalNotification::match(message)) { DSPSignalNotification& notif = (DSPSignalNotification&) message; setSampleRate(notif.getSampleRate()); - qDebug() << "ScopeVisNG::handleMessage: DSPSignalNotification: m_sampleRate: " << m_sampleRate; + qDebug() << "ScopeVis::handleMessage: DSPSignalNotification: m_sampleRate: " << m_sampleRate; return true; } else if (MsgConfigureScopeVisNG::match(message)) @@ -642,7 +642,7 @@ bool ScopeVisNG::handleMessage(const Message& message) m_freeRun = freeRun; } - qDebug() << "ScopeVisNG::handleMessage: MsgConfigureScopeVisNG:" + qDebug() << "ScopeVis::handleMessage: MsgConfigureScopeVisNG:" << " m_traceSize: " << m_traceSize << " m_timeOfsProMill: " << m_timeOfsProMill << " m_preTriggerDelay: " << m_preTriggerDelay @@ -656,7 +656,7 @@ bool ScopeVisNG::handleMessage(const Message& message) } else if (MsgScopeVisNGAddTrigger::match(message)) { - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGAddTrigger"; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGAddTrigger"; QMutexLocker configLocker(&m_mutex); MsgScopeVisNGAddTrigger& conf = (MsgScopeVisNGAddTrigger&) message; m_triggerConditions.push_back(new TriggerCondition(conf.getTriggerData())); @@ -668,7 +668,7 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGChangeTrigger& conf = (MsgScopeVisNGChangeTrigger&) message; uint32_t triggerIndex = conf.getTriggerIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGChangeTrigger: " << triggerIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGChangeTrigger: " << triggerIndex; if (triggerIndex < m_triggerConditions.size()) { @@ -689,7 +689,7 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGRemoveTrigger& conf = (MsgScopeVisNGRemoveTrigger&) message; uint32_t triggerIndex = conf.getTriggerIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGRemoveTrigger: " << triggerIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGRemoveTrigger: " << triggerIndex; if (triggerIndex < m_triggerConditions.size()) { @@ -705,7 +705,7 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGMoveTrigger& conf = (MsgScopeVisNGMoveTrigger&) message; int triggerIndex = conf.getTriggerIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGMoveTrigger: " << triggerIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGMoveTrigger: " << triggerIndex; if (!conf.getMoveUp() && (triggerIndex == 0)) { return true; @@ -727,7 +727,7 @@ bool ScopeVisNG::handleMessage(const Message& message) { MsgScopeVisNGFocusOnTrigger& conf = (MsgScopeVisNGFocusOnTrigger&) message; uint32_t triggerIndex = conf.getTriggerIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGFocusOnTrigger: " << triggerIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGFocusOnTrigger: " << triggerIndex; if (triggerIndex < m_triggerConditions.size()) { @@ -741,7 +741,7 @@ bool ScopeVisNG::handleMessage(const Message& message) } else if (MsgScopeVisNGAddTrace::match(message)) { - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGAddTrace"; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGAddTrace"; QMutexLocker configLocker(&m_mutex); MsgScopeVisNGAddTrace& conf = (MsgScopeVisNGAddTrace&) message; m_traces.addTrace(conf.getTraceData(), m_traceSize); @@ -757,7 +757,7 @@ bool ScopeVisNG::handleMessage(const Message& message) MsgScopeVisNGChangeTrace& conf = (MsgScopeVisNGChangeTrace&) message; bool doComputeTriggerLevelsOnDisplay = m_traces.isVerticalDisplayChange(conf.getTraceData(), conf.getTraceIndex()); uint32_t traceIndex = conf.getTraceIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGChangeTrace: " << traceIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGChangeTrace: " << traceIndex; m_traces.changeTrace(conf.getTraceData(), traceIndex); updateMaxTraceDelay(); if (doComputeTriggerLevelsOnDisplay) computeDisplayTriggerLevels(); @@ -769,7 +769,7 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGRemoveTrace& conf = (MsgScopeVisNGRemoveTrace&) message; uint32_t traceIndex = conf.getTraceIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGRemoveTrace: " << traceIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGRemoveTrace: " << traceIndex; m_traces.removeTrace(traceIndex); updateMaxTraceDelay(); computeDisplayTriggerLevels(); @@ -781,7 +781,7 @@ bool ScopeVisNG::handleMessage(const Message& message) QMutexLocker configLocker(&m_mutex); MsgScopeVisNGMoveTrace& conf = (MsgScopeVisNGMoveTrace&) message; uint32_t traceIndex = conf.getTraceIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGMoveTrace: " << traceIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGMoveTrace: " << traceIndex; m_traces.moveTrace(traceIndex, conf.getMoveUp()); //updateMaxTraceDelay(); computeDisplayTriggerLevels(); @@ -792,7 +792,7 @@ bool ScopeVisNG::handleMessage(const Message& message) { MsgScopeVisNGFocusOnTrace& conf = (MsgScopeVisNGFocusOnTrace&) message; uint32_t traceIndex = conf.getTraceIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGFocusOnTrace: " << traceIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGFocusOnTrace: " << traceIndex; if (traceIndex < m_traces.m_tracesData.size()) { @@ -806,7 +806,7 @@ bool ScopeVisNG::handleMessage(const Message& message) } else if (MsgScopeVisNGOneShot::match(message)) { - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGOneShot"; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGOneShot"; MsgScopeVisNGOneShot& conf = (MsgScopeVisNGOneShot&) message; bool oneShot = conf.getOneShot(); m_triggerOneShot = oneShot; @@ -817,7 +817,7 @@ bool ScopeVisNG::handleMessage(const Message& message) { MsgScopeVisNGMemoryTrace& conf = (MsgScopeVisNGMemoryTrace&) message; uint32_t memoryIndex = conf.getMemoryIndex(); - qDebug() << "ScopeVisNG::handleMessage: MsgScopeVisNGMemoryTrace: " << memoryIndex; + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGMemoryTrace: " << memoryIndex; if (memoryIndex != m_currentTraceMemoryIndex) { @@ -831,12 +831,12 @@ bool ScopeVisNG::handleMessage(const Message& message) } else { - qDebug() << "ScopeVisNG::handleMessage" << message.getIdentifier() << " not handled"; + qDebug() << "ScopeVis::handleMessage" << message.getIdentifier() << " not handled"; return false; } } -void ScopeVisNG::updateMaxTraceDelay() +void ScopeVis::updateMaxTraceDelay() { int maxTraceDelay = 0; bool allocateCache = false; @@ -883,7 +883,7 @@ void ScopeVisNG::updateMaxTraceDelay() m_maxTraceDelay = maxTraceDelay; } -void ScopeVisNG::initTraceBuffers() +void ScopeVis::initTraceBuffers() { int shift = (m_timeOfsProMill / 1000.0) * m_traceSize; @@ -902,7 +902,7 @@ void ScopeVisNG::initTraceBuffers() } } -void ScopeVisNG::computeDisplayTriggerLevels() +void ScopeVis::computeDisplayTriggerLevels() { std::vector::iterator itData = m_traces.m_tracesData.begin(); @@ -944,7 +944,7 @@ void ScopeVisNG::computeDisplayTriggerLevels() } } -void ScopeVisNG::updateGLScopeDisplay() +void ScopeVis::updateGLScopeDisplay() { if (m_currentTraceMemoryIndex > 0) { m_glScope->setConfigChanged(); diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index f8a16d1e8..0bbe91797 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -39,7 +39,7 @@ class GLScope; -class SDRGUI_API ScopeVisNG : public BasebandSampleSink { +class SDRGUI_API ScopeVis : public BasebandSampleSink { public: struct TraceData @@ -147,8 +147,8 @@ public: static const uint32_t m_maxNbTraces = 10; static const uint32_t m_nbTraceMemories = 50; - ScopeVisNG(GLScope* glScope = 0); - virtual ~ScopeVisNG(); + ScopeVis(GLScope* glScope = 0); + virtual ~ScopeVis(); void setSampleRate(int sampleRate); void configure(uint32_t traceSize, uint32_t timeBase, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun); @@ -760,7 +760,7 @@ private: { if (m_traces[0].size() < m_maxNbTraces) { - qDebug("ScopeVisNG::addTrace"); + qDebug("ScopeVis::addTrace"); m_traces[0].push_back(0); m_traces[1].push_back(0); m_tracesData.push_back(traceData); @@ -786,7 +786,7 @@ private: { if (traceIndex < m_tracesControl.size()) { - qDebug("ScopeVisNG::removeTrace"); + qDebug("ScopeVis::removeTrace"); m_traces[0].erase(m_traces[0].begin() + traceIndex); m_traces[1].erase(m_traces[1].begin() + traceIndex); TraceControl *traceControl = m_tracesControl[traceIndex]; @@ -912,7 +912,7 @@ private: } // if (trigger) { -// qDebug("ScopeVisNG::triggered: %s/%s %f/%f", +// qDebug("ScopeVis::triggered: %s/%s %f/%f", // triggerCondition.m_prevCondition ? "T" : "F", // condition ? "T" : "F", // triggerCondition.m_projector->run(s), diff --git a/sdrgui/dsp/spectrumscopecombovis.cpp b/sdrgui/dsp/spectrumscopecombovis.cpp index ad1cc89a8..feb482022 100644 --- a/sdrgui/dsp/spectrumscopecombovis.cpp +++ b/sdrgui/dsp/spectrumscopecombovis.cpp @@ -2,7 +2,7 @@ #include "dsp/dspcommands.h" #include "util/messagequeue.h" -SpectrumScopeComboVis::SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis) : +SpectrumScopeComboVis::SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVis* scopeVis) : m_spectrumVis(spectrumVis), m_scopeVis(scopeVis) { diff --git a/sdrgui/dsp/spectrumscopecombovis.h b/sdrgui/dsp/spectrumscopecombovis.h index 9d3f157f4..39cb10db6 100644 --- a/sdrgui/dsp/spectrumscopecombovis.h +++ b/sdrgui/dsp/spectrumscopecombovis.h @@ -11,7 +11,7 @@ class Message; class SDRGUI_API SpectrumScopeComboVis : public BasebandSampleSink { public: - SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis); + SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVis* scopeVis); virtual ~SpectrumScopeComboVis(); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); @@ -21,7 +21,7 @@ public: private: SpectrumVis* m_spectrumVis; - ScopeVisNG* m_scopeVis; + ScopeVis* m_scopeVis; }; #endif // INCLUDE_SPECTRUMSCOPENGCOMBOVIS_H diff --git a/sdrgui/gui/glscope.cpp b/sdrgui/gui/glscope.cpp index 4d42231d1..6c710411b 100644 --- a/sdrgui/gui/glscope.cpp +++ b/sdrgui/gui/glscope.cpp @@ -92,7 +92,7 @@ void GLScope::setDisplayTraceIntensity(int intensity) update(); } -void GLScope::setTraces(std::vector* tracesData, std::vector* traces) +void GLScope::setTraces(std::vector* tracesData, std::vector* traces) { m_tracesData = tracesData; m_traces = traces; @@ -304,7 +304,7 @@ void GLScope::paintGL() if (m_traceSize > 0) { const float *trace = (*m_traces)[0]; - const ScopeVisNG::TraceData& traceData = (*m_tracesData)[0]; + const ScopeVis::TraceData& traceData = (*m_tracesData)[0]; if (traceData.m_viewTrace) { @@ -487,7 +487,7 @@ void GLScope::paintGL() for (unsigned int i = 1; i < m_traces->size(); i++) { const float *trace = (*m_traces)[i]; - const ScopeVisNG::TraceData& traceData = (*m_tracesData)[i]; + const ScopeVis::TraceData& traceData = (*m_tracesData)[i]; if (!traceData.m_viewTrace) { continue; @@ -713,7 +713,7 @@ void GLScope::paintGL() for (unsigned int i = 0; i < m_traces->size(); i++) { const float *trace = (*m_traces)[i]; - const ScopeVisNG::TraceData& traceData = (*m_tracesData)[i]; + const ScopeVis::TraceData& traceData = (*m_tracesData)[i]; if (!traceData.m_viewTrace) { continue; @@ -889,7 +889,7 @@ void GLScope::paintGL() for (unsigned int i = 1; i < m_traces->size(); i++) { const float *trace = (*m_traces)[i]; - const ScopeVisNG::TraceData& traceData = (*m_tracesData)[i]; + const ScopeVis::TraceData& traceData = (*m_tracesData)[i]; if (!traceData.m_viewTrace) { continue; @@ -1865,7 +1865,7 @@ void GLScope::setPolarDisplays() void GLScope::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) { - ScopeVisNG::TraceData& traceData = (*m_tracesData)[highlightedTraceIndex]; + ScopeVis::TraceData& traceData = (*m_tracesData)[highlightedTraceIndex]; double amp_range = 2.0 / traceData.m_amp; double amp_ofs = traceData.m_ofs; double pow_floor = -100.0 + traceData.m_ofs * 100.0; diff --git a/sdrgui/gui/glscope.h b/sdrgui/gui/glscope.h index 722fc1965..1fbdd1317 100644 --- a/sdrgui/gui/glscope.h +++ b/sdrgui/gui/glscope.h @@ -52,7 +52,7 @@ public: void connectTimer(const QTimer& timer); - void setTraces(std::vector* tracesData, std::vector* traces); + void setTraces(std::vector* tracesData, std::vector* traces); void newTraces(std::vector* traces); int getSampleRate() const { return m_sampleRate; } @@ -68,7 +68,7 @@ public: void updateDisplay(); void setDisplayGridIntensity(int intensity); void setDisplayTraceIntensity(int intensity); - void setFocusedTriggerData(ScopeVisNG::TriggerData& triggerData) { m_focusedTriggerData = triggerData; } + void setFocusedTriggerData(ScopeVis::TriggerData& triggerData) { m_focusedTriggerData = triggerData; } void setConfigChanged() { m_configChanged = true; } //void incrementTraceCounter() { m_traceCounter++; } @@ -80,9 +80,9 @@ signals: void sampleRateChanged(int); private: - std::vector *m_tracesData; + std::vector *m_tracesData; std::vector *m_traces; - ScopeVisNG::TriggerData m_focusedTriggerData; + ScopeVis::TriggerData m_focusedTriggerData; //int m_traceCounter; uint32_t m_bufferIndex; DisplayMode m_displayMode; diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp index 1f32962e6..57b06e003 100644 --- a/sdrgui/gui/glscopegui.cpp +++ b/sdrgui/gui/glscopegui.cpp @@ -48,13 +48,13 @@ GLScopeGUI::GLScopeGUI(QWidget* parent) : qDebug("GLScopeGUI::GLScopeGUI"); setEnabled(false); ui->setupUi(this); - ui->trigDelayFine->setMaximum(ScopeVisNG::m_traceChunkSize / 10.0); + ui->trigDelayFine->setMaximum(ScopeVis::m_traceChunkSize / 10.0); ui->traceColor->setStyleSheet("QLabel { background-color : rgb(255,255,64); }"); m_focusedTraceColor.setRgb(255,255,64); ui->trigColor->setStyleSheet("QLabel { background-color : rgb(0,255,0); }"); m_focusedTriggerColor.setRgb(0,255,0); ui->traceText->setText("X"); - ui->mem->setMaximum(ScopeVisNG::m_nbTraceMemories - 1); + ui->mem->setMaximum(ScopeVis::m_nbTraceMemories - 1); } GLScopeGUI::~GLScopeGUI() @@ -62,7 +62,7 @@ GLScopeGUI::~GLScopeGUI() delete ui; } -void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScope* glScope) +void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVis* scopeVis, GLScope* glScope) { qDebug("GLScopeGUI::setBuddies"); @@ -91,12 +91,12 @@ void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GL ui->freerun->setChecked(true); // Add a trigger - ScopeVisNG::TriggerData triggerData; + ScopeVis::TriggerData triggerData; fillTriggerData(triggerData); m_scopeVis->addTrigger(triggerData); // Add a trace - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; fillTraceData(traceData); m_scopeVis->addTrace(traceData); @@ -109,13 +109,13 @@ void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GL ui->trigMode->clear(); fillProjectionCombo(ui->trigMode); - m_scopeVis->configure(2*m_traceLenMult*ScopeVisNG::m_traceChunkSize, + m_scopeVis->configure(2*m_traceLenMult*ScopeVis::m_traceChunkSize, m_timeBase, m_timeOffset*10, (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), ui->freerun->isChecked()); - m_scopeVis->configure(m_traceLenMult*ScopeVisNG::m_traceChunkSize, + m_scopeVis->configure(m_traceLenMult*ScopeVis::m_traceChunkSize, m_timeBase, m_timeOffset*10, (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), @@ -165,8 +165,8 @@ QByteArray GLScopeGUI::serialize() const s.writeS32(6, ui->traceLen->value()); // second row - by trace - const std::vector& tracesData = m_scopeVis->getTracesData(); - std::vector::const_iterator traceDataIt = tracesData.begin(); + const std::vector& tracesData = m_scopeVis->getTracesData(); + std::vector::const_iterator traceDataIt = tracesData.begin(); s.writeU32(10, (uint32_t) tracesData.size()); int i = 0; @@ -189,7 +189,7 @@ QByteArray GLScopeGUI::serialize() const for (unsigned int i = 0; i < m_scopeVis->getNbTriggers(); i++) { - const ScopeVisNG::TriggerData& triggerData = m_scopeVis->getTriggerData(i); + const ScopeVis::TriggerData& triggerData = m_scopeVis->getTriggerData(i); s.writeS32(210 + 16*i, (int) triggerData.m_projectionType); s.writeS32(211 + 16*i, triggerData.m_triggerRepeat); s.writeBool(212 + 16*i, triggerData.m_triggerPositiveEdge); @@ -275,7 +275,7 @@ bool GLScopeGUI::deserialize(const QByteArray& data) uint32_t nbTracesSaved; d.readU32(10, &nbTracesSaved, 1); - const std::vector& tracesData = m_scopeVis->getTracesData(); + const std::vector& tracesData = m_scopeVis->getTracesData(); uint32_t iTrace = tracesData.size(); qDebug("GLScopeGUI::deserialize: nbTracesSaved: %u tracesData.size(): %lu", nbTracesSaved, tracesData.size()); @@ -288,7 +288,7 @@ bool GLScopeGUI::deserialize(const QByteArray& data) for (iTrace = 0; iTrace < nbTracesSaved; iTrace++) { - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; float r, g, b; d.readS32(20 + 16*iTrace, &intValue, 0); @@ -360,7 +360,7 @@ bool GLScopeGUI::deserialize(const QByteArray& data) for (iTrigger = 0; iTrigger < nbTriggersSaved; iTrigger++) { - ScopeVisNG::TriggerData triggerData = m_scopeVis->getTriggerData(iTrigger); + ScopeVis::TriggerData triggerData = m_scopeVis->getTriggerData(iTrigger); float r, g, b; d.readS32(210 + 16*iTrigger, &intValue, 0); @@ -534,7 +534,7 @@ void GLScopeGUI::on_time_valueChanged(int value) m_timeBase = value; setTimeScaleDisplay(); setTraceDelayDisplay(); - m_scopeVis->configure(m_traceLenMult*ScopeVisNG::m_traceChunkSize, + m_scopeVis->configure(m_traceLenMult*ScopeVis::m_traceChunkSize, m_timeBase, m_timeOffset*10, (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), @@ -549,7 +549,7 @@ void GLScopeGUI::on_timeOfs_valueChanged(int value) m_timeOffset = value; setTimeOfsDisplay(); - m_scopeVis->configure(m_traceLenMult*ScopeVisNG::m_traceChunkSize, + m_scopeVis->configure(m_traceLenMult*ScopeVis::m_traceChunkSize, m_timeBase, m_timeOffset*10, (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), @@ -563,7 +563,7 @@ void GLScopeGUI::on_traceLen_valueChanged(int value) } m_traceLenMult = value; - m_scopeVis->configure(m_traceLenMult*ScopeVisNG::m_traceChunkSize, + m_scopeVis->configure(m_traceLenMult*ScopeVis::m_traceChunkSize, m_timeBase, m_timeOffset*10, (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), @@ -579,7 +579,7 @@ void GLScopeGUI::on_trace_valueChanged(int value) { ui->traceText->setText(value == 0 ? "X" : QString("Y%1").arg(ui->trace->value())); - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; m_scopeVis->getTraceData(traceData, value); qDebug() << "GLScopeGUI::on_trace_valueChanged:" @@ -595,7 +595,7 @@ void GLScopeGUI::on_trace_valueChanged(int value) void GLScopeGUI::on_traceAdd_clicked(bool checked __attribute__((unused))) { - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; fillTraceData(traceData); addTrace(traceData); } @@ -628,7 +628,7 @@ void GLScopeGUI::on_traceUp_clicked(bool checked __attribute__((unused))) int newTraceIndex = (ui->trace->value() + 1) % (ui->trace->maximum()+1); m_scopeVis->moveTrace(ui->trace->value(), true); ui->trace->setValue(newTraceIndex); // follow trace - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; m_scopeVis->getTraceData(traceData, ui->trace->value()); setTraceUI(traceData); m_scopeVis->focusOnTrace(ui->trace->value()); @@ -642,7 +642,7 @@ void GLScopeGUI::on_traceDown_clicked(bool checked __attribute__((unused))) int newTraceIndex = (ui->trace->value() - 1) % (ui->trace->maximum()+1); m_scopeVis->moveTrace(ui->trace->value(), false); ui->trace->setValue(newTraceIndex); // follow trace - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; m_scopeVis->getTraceData(traceData, ui->trace->value()); setTraceUI(traceData); m_scopeVis->focusOnTrace(ui->trace->value()); @@ -653,7 +653,7 @@ void GLScopeGUI::on_trig_valueChanged(int value) { ui->trigText->setText(tr("%1").arg(value)); - ScopeVisNG::TriggerData triggerData; + ScopeVis::TriggerData triggerData; m_scopeVis->getTriggerData(triggerData, value); qDebug() << "GLScopeGUI::on_trig_valueChanged:" @@ -670,7 +670,7 @@ void GLScopeGUI::on_trig_valueChanged(int value) void GLScopeGUI::on_trigAdd_clicked(bool checked __attribute__((unused))) { - ScopeVisNG::TriggerData triggerData; + ScopeVis::TriggerData triggerData; fillTriggerData(triggerData); addTrigger(triggerData); } @@ -691,7 +691,7 @@ void GLScopeGUI::on_trigUp_clicked(bool checked __attribute__((unused))) int newTriggerIndex = (ui->trig->value() + 1) % (ui->trig->maximum()+1); m_scopeVis->moveTrigger(ui->trace->value(), true); ui->trig->setValue(newTriggerIndex); // follow trigger - ScopeVisNG::TriggerData triggerData; + ScopeVis::TriggerData triggerData; m_scopeVis->getTriggerData(triggerData, ui->trig->value()); setTriggerUI(triggerData); m_scopeVis->focusOnTrigger(ui->trig->value()); @@ -705,7 +705,7 @@ void GLScopeGUI::on_trigDown_clicked(bool checked __attribute__((unused))) int newTriggerIndex = (ui->trig->value() - 1) % (ui->trig->maximum()+1); m_scopeVis->moveTrigger(ui->trace->value(), false); ui->trig->setValue(newTriggerIndex); // follow trigger - ScopeVisNG::TriggerData triggerData; + ScopeVis::TriggerData triggerData; m_scopeVis->getTriggerData(triggerData, ui->trig->value()); setTriggerUI(triggerData); m_scopeVis->focusOnTrigger(ui->trig->value()); @@ -846,7 +846,7 @@ void GLScopeGUI::on_trigDelayFine_valueChanged(int value __attribute__((unused)) void GLScopeGUI::on_trigPre_valueChanged(int value __attribute__((unused))) { setTrigPreDisplay(); - m_scopeVis->configure(m_traceLenMult*ScopeVisNG::m_traceChunkSize, + m_scopeVis->configure(m_traceLenMult*ScopeVis::m_traceChunkSize, m_timeBase, m_timeOffset*10, (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), @@ -884,7 +884,7 @@ void GLScopeGUI::on_freerun_toggled(bool checked) ui->trigOneShot->setEnabled(true); } - m_scopeVis->configure(m_traceLenMult*ScopeVisNG::m_traceChunkSize, + m_scopeVis->configure(m_traceLenMult*ScopeVis::m_traceChunkSize, m_timeBase, m_timeOffset*10, (uint32_t) (m_glScope->getTraceSize() * (ui->trigPre->value()/100.0f)), @@ -941,7 +941,7 @@ void GLScopeGUI::setTimeScaleDisplay() void GLScopeGUI::setTraceLenDisplay() { - unsigned int n_samples = m_traceLenMult * ScopeVisNG::m_traceChunkSize; + unsigned int n_samples = m_traceLenMult * ScopeVis::m_traceChunkSize; if (n_samples < 1000) { ui->traceLenText->setToolTip(tr("%1 S").arg(n_samples)); @@ -1120,8 +1120,8 @@ void GLScopeGUI::setTrigDelayDisplay() { if (m_sampleRate > 0) { - double delayMult = ui->trigDelayCoarse->value() + ui->trigDelayFine->value() / (ScopeVisNG::m_traceChunkSize / 10.0); - unsigned int n_samples_delay = m_traceLenMult * ScopeVisNG::m_traceChunkSize * delayMult; + double delayMult = ui->trigDelayCoarse->value() + ui->trigDelayFine->value() / (ScopeVis::m_traceChunkSize / 10.0); + unsigned int n_samples_delay = m_traceLenMult * ScopeVis::m_traceChunkSize * delayMult; if (n_samples_delay < 1000) { ui->trigDelayText->setToolTip(tr("%1 S").arg(n_samples_delay)); @@ -1177,7 +1177,7 @@ void GLScopeGUI::setTrigPreDisplay() void GLScopeGUI::changeCurrentTrace() { - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; fillTraceData(traceData); uint32_t currentTraceIndex = ui->trace->value(); m_scopeVis->changeTrace(traceData, currentTraceIndex); @@ -1185,7 +1185,7 @@ void GLScopeGUI::changeCurrentTrace() void GLScopeGUI::changeCurrentTrigger() { - ScopeVisNG::TriggerData triggerData; + ScopeVis::TriggerData triggerData; fillTriggerData(triggerData); uint32_t currentTriggerIndex = ui->trig->value(); m_scopeVis->changeTrigger(triggerData, currentTriggerIndex); @@ -1226,7 +1226,7 @@ void GLScopeGUI::disableLiveMode(bool disable) ui->freerun->setEnabled(!disable); } -void GLScopeGUI::fillTraceData(ScopeVisNG::TraceData& traceData) +void GLScopeGUI::fillTraceData(ScopeVis::TraceData& traceData) { traceData.m_projectionType = (Projector::ProjectionType) ui->traceMode->currentIndex(); traceData.m_hasTextOverlay = (traceData.m_projectionType == Projector::ProjectionMagDB) || (traceData.m_projectionType == Projector::ProjectionMagSq); @@ -1251,7 +1251,7 @@ void GLScopeGUI::fillTraceData(ScopeVisNG::TraceData& traceData) traceData.m_viewTrace = ui->traceView->isChecked(); } -void GLScopeGUI::fillTriggerData(ScopeVisNG::TriggerData& triggerData) +void GLScopeGUI::fillTriggerData(ScopeVis::TriggerData& triggerData) { triggerData.m_projectionType = (Projector::ProjectionType) ui->trigMode->currentIndex(); triggerData.m_inputIndex = 0; @@ -1261,14 +1261,14 @@ void GLScopeGUI::fillTriggerData(ScopeVisNG::TriggerData& triggerData) triggerData.m_triggerPositiveEdge = ui->trigPos->isChecked(); triggerData.m_triggerBothEdges = ui->trigBoth->isChecked(); triggerData.m_triggerRepeat = ui->trigCount->value(); - triggerData.m_triggerDelayMult = ui->trigDelayCoarse->value() + ui->trigDelayFine->value() / (ScopeVisNG::m_traceChunkSize / 10.0); - triggerData.m_triggerDelay = (int) (m_traceLenMult * ScopeVisNG::m_traceChunkSize * triggerData.m_triggerDelayMult); + triggerData.m_triggerDelayMult = ui->trigDelayCoarse->value() + ui->trigDelayFine->value() / (ScopeVis::m_traceChunkSize / 10.0); + triggerData.m_triggerDelay = (int) (m_traceLenMult * ScopeVis::m_traceChunkSize * triggerData.m_triggerDelayMult); triggerData.m_triggerDelayCoarse = ui->trigDelayCoarse->value(); triggerData.m_triggerDelayFine = ui->trigDelayFine->value(); triggerData.setColor(m_focusedTriggerColor); } -void GLScopeGUI::setTraceUI(const ScopeVisNG::TraceData& traceData) +void GLScopeGUI::setTraceUI(const ScopeVis::TraceData& traceData) { TraceUIBlocker traceUIBlocker(ui); @@ -1292,7 +1292,7 @@ void GLScopeGUI::setTraceUI(const ScopeVisNG::TraceData& traceData) ui->traceView->setChecked(traceData.m_viewTrace); } -void GLScopeGUI::setTriggerUI(const ScopeVisNG::TriggerData& triggerData) +void GLScopeGUI::setTriggerUI(const ScopeVis::TriggerData& triggerData) { TrigUIBlocker trigUIBlocker(ui); @@ -1532,12 +1532,12 @@ void GLScopeGUI::setPreTrigger(int step) ui->trigPre->setValue(step); } -void GLScopeGUI::changeTrace(int traceIndex, const ScopeVisNG::TraceData& traceData) +void GLScopeGUI::changeTrace(int traceIndex, const ScopeVis::TraceData& traceData) { m_scopeVis->changeTrace(traceData, traceIndex); } -void GLScopeGUI::addTrace(const ScopeVisNG::TraceData& traceData) +void GLScopeGUI::addTrace(const ScopeVis::TraceData& traceData) { if (ui->trace->maximum() < 3) { @@ -1559,12 +1559,12 @@ void GLScopeGUI::focusOnTrace(int traceIndex) on_trace_valueChanged(traceIndex); } -void GLScopeGUI::changeTrigger(int triggerIndex, const ScopeVisNG::TriggerData& triggerData) +void GLScopeGUI::changeTrigger(int triggerIndex, const ScopeVis::TriggerData& triggerData) { m_scopeVis->changeTrigger(triggerData, triggerIndex); } -void GLScopeGUI::addTrigger(const ScopeVisNG::TriggerData& triggerData) +void GLScopeGUI::addTrigger(const ScopeVis::TriggerData& triggerData) { if (ui->trig->maximum() < 9) { diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h index 96fea8ef5..b6b545e4f 100644 --- a/sdrgui/gui/glscopegui.h +++ b/sdrgui/gui/glscopegui.h @@ -49,7 +49,7 @@ public: explicit GLScopeGUI(QWidget* parent = 0); ~GLScopeGUI(); - void setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScope* glScope); + void setBuddies(MessageQueue* messageQueue, ScopeVis* scopeVis, GLScope* glScope); void setSampleRate(int sampleRate); void resetToDefaults(); @@ -68,12 +68,12 @@ public: void setTraceLength(int step); void setPreTrigger(int step); // trace (second line): - void changeTrace(int traceIndex, const ScopeVisNG::TraceData& traceData); - void addTrace(const ScopeVisNG::TraceData& traceData); + void changeTrace(int traceIndex, const ScopeVis::TraceData& traceData); + void addTrace(const ScopeVis::TraceData& traceData); void focusOnTrace(int traceIndex); // trigger (third line): - void changeTrigger(int triggerIndex, const ScopeVisNG::TriggerData& triggerData); - void addTrigger(const ScopeVisNG::TriggerData& triggerData); + void changeTrigger(int triggerIndex, const ScopeVis::TriggerData& triggerData); + void addTrigger(const ScopeVis::TriggerData& triggerData); void focusOnTrigger(int triggerIndex); private: @@ -143,7 +143,7 @@ private: Ui::GLScopeGUI* ui; MessageQueue* m_messageQueue; - ScopeVisNG* m_scopeVis; + ScopeVis* m_scopeVis; GLScope* m_glScope; int m_sampleRate; @@ -175,10 +175,10 @@ private: void changeCurrentTrace(); void changeCurrentTrigger(); - void fillTraceData(ScopeVisNG::TraceData& traceData); - void fillTriggerData(ScopeVisNG::TriggerData& triggerData); - void setTriggerUI(const ScopeVisNG::TriggerData& triggerData); - void setTraceUI(const ScopeVisNG::TraceData& traceData); + void fillTraceData(ScopeVis::TraceData& traceData); + void fillTriggerData(ScopeVis::TriggerData& triggerData); + void setTriggerUI(const ScopeVis::TriggerData& triggerData); + void setTraceUI(const ScopeVis::TraceData& traceData); void fillProjectionCombo(QComboBox* comboBox); void disableLiveMode(bool disable); From cee9a8b757cdae7a14a2f2e6e52a09e753278027 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 17:22:39 +0200 Subject: [PATCH 597/956] Renamed scopevisng.h to scopevis.h --- plugins/channelrx/demodatv/atvdemodgui.cpp | 2 +- sdrgui/CMakeLists.txt | 2 +- sdrgui/dsp/{scopevisng.h => scopevis.h} | 0 sdrgui/dsp/scopevisng.cpp | 2 +- sdrgui/dsp/spectrumscopecombovis.cpp | 1 + sdrgui/dsp/spectrumscopecombovis.h | 2 +- sdrgui/gui/glscope.h | 2 +- sdrgui/gui/glscopegui.h | 2 +- 8 files changed, 7 insertions(+), 6 deletions(-) rename sdrgui/dsp/{scopevisng.h => scopevis.h} (100%) diff --git a/plugins/channelrx/demodatv/atvdemodgui.cpp b/plugins/channelrx/demodatv/atvdemodgui.cpp index 80a3af7db..09ed50f86 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.cpp +++ b/plugins/channelrx/demodatv/atvdemodgui.cpp @@ -25,7 +25,7 @@ #include "dsp/downchannelizer.h" #include "dsp/threadedbasebandsamplesink.h" -#include "dsp/scopevisng.h" +#include "dsp/scopevis.h" #include "ui_atvdemodgui.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 1cc1e0cd8..6c999002a 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -103,7 +103,7 @@ set(sdrgui_HEADERS gui/valuedial.h gui/valuedialz.h - dsp/scopevisng.h + dsp/scopevis.h dsp/scopevismulti.h dsp/scopevisxy.h dsp/spectrumvis.h diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevis.h similarity index 100% rename from sdrgui/dsp/scopevisng.h rename to sdrgui/dsp/scopevis.h diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index 75bb58f54..3e6883bdf 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -18,7 +18,7 @@ #include #include -#include "scopevisng.h" +#include "scopevis.h" #include "dsp/dspcommands.h" #include "gui/glscope.h" diff --git a/sdrgui/dsp/spectrumscopecombovis.cpp b/sdrgui/dsp/spectrumscopecombovis.cpp index feb482022..970ef82ca 100644 --- a/sdrgui/dsp/spectrumscopecombovis.cpp +++ b/sdrgui/dsp/spectrumscopecombovis.cpp @@ -1,5 +1,6 @@ #include "dsp/spectrumscopecombovis.h" #include "dsp/dspcommands.h" +#include "dsp/scopevis.h" #include "util/messagequeue.h" SpectrumScopeComboVis::SpectrumScopeComboVis(SpectrumVis* spectrumVis, ScopeVis* scopeVis) : diff --git a/sdrgui/dsp/spectrumscopecombovis.h b/sdrgui/dsp/spectrumscopecombovis.h index 39cb10db6..d2b0194d2 100644 --- a/sdrgui/dsp/spectrumscopecombovis.h +++ b/sdrgui/dsp/spectrumscopecombovis.h @@ -3,10 +3,10 @@ #include #include "dsp/spectrumvis.h" -#include "dsp/scopevisng.h" #include "export.h" class Message; +class ScopeVis; class SDRGUI_API SpectrumScopeComboVis : public BasebandSampleSink { public: diff --git a/sdrgui/gui/glscope.h b/sdrgui/gui/glscope.h index 1fbdd1317..9d7acf152 100644 --- a/sdrgui/gui/glscope.h +++ b/sdrgui/gui/glscope.h @@ -25,7 +25,7 @@ #include #include #include "dsp/dsptypes.h" -#include "dsp/scopevisng.h" +#include "dsp/scopevis.h" #include "gui/scaleengine.h" #include "gui/glshadersimple.h" #include "gui/glshadertextured.h" diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h index b6b545e4f..77584c471 100644 --- a/sdrgui/gui/glscopegui.h +++ b/sdrgui/gui/glscopegui.h @@ -24,7 +24,7 @@ #include "dsp/dsptypes.h" #include "export.h" #include "util/message.h" -#include "dsp/scopevisng.h" +#include "dsp/scopevis.h" #include "settings/serializable.h" namespace Ui { From 77599e67e17667599593387b8ac36f0d2564a2f7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 17:23:32 +0200 Subject: [PATCH 598/956] Renamed scopevisng.cpp to scopevis.cpp --- sdrgui/CMakeLists.txt | 2 +- sdrgui/dsp/{scopevisng.cpp => scopevis.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename sdrgui/dsp/{scopevisng.cpp => scopevis.cpp} (100%) diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 6c999002a..95e655c92 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -46,7 +46,7 @@ set(sdrgui_SOURCES gui/valuedial.cpp gui/valuedialz.cpp - dsp/scopevisng.cpp + dsp/scopevis.cpp dsp/scopevismulti.cpp dsp/scopevisxy.cpp dsp/spectrumvis.cpp diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevis.cpp similarity index 100% rename from sdrgui/dsp/scopevisng.cpp rename to sdrgui/dsp/scopevis.cpp From 36511a4cb4c3857bdd669888a19ed0a62c0ac526 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 12 Aug 2018 18:47:50 +0200 Subject: [PATCH 599/956] GLScopeGUI: adapted to new UI name --- sdrgui/gui/glscopegui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp index 57b06e003..17b08b15b 100644 --- a/sdrgui/gui/glscopegui.cpp +++ b/sdrgui/gui/glscopegui.cpp @@ -19,7 +19,7 @@ #include "glscopegui.h" #include "glscope.h" -#include "ui_glscopenggui.h" +#include "ui_glscopegui.h" #include "util/simpleserializer.h" const double GLScopeGUI::amps[27] = { From 62deb64f57fc8d8e5d8df116507e47f2b387d747 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 13 Aug 2018 16:58:43 +0200 Subject: [PATCH 600/956] LimeSDR GUIs: always apply change of FIR bandwidth --- plugins/samplesink/limesdroutput/limesdroutputgui.cpp | 5 +---- plugins/samplesink/limesdroutput/limesdroutputplugin.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinputgui.cpp | 5 +---- plugins/samplesource/limesdrinput/limesdrinputplugin.cpp | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp index b04bab065..f904360d9 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp @@ -526,10 +526,7 @@ void LimeSDROutputGUI::on_lpFIREnable_toggled(bool checked) void LimeSDROutputGUI::on_lpFIR_changed(quint64 value) { m_settings.m_lpfFIRBW = value * 1000; - - if (m_settings.m_lpfFIREnable) { // do not send the update if the FIR is disabled - sendSettings(); - } + sendSettings(); } void LimeSDROutputGUI::on_gain_valueChanged(int value) diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp index d10f5c0c1..2783fe9cb 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp @@ -34,7 +34,7 @@ const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = { QString("LimeSDR Output"), - QString("3.14.5"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index 0b44d28c3..8693eb7e1 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -574,10 +574,7 @@ void LimeSDRInputGUI::on_lpFIREnable_toggled(bool checked) void LimeSDRInputGUI::on_lpFIR_changed(quint64 value) { m_settings.m_lpfFIRBW = value * 1000; - - if (m_settings.m_lpfFIREnable) { // do not send the update if the FIR is disabled - sendSettings(); - } + sendSettings(); } void LimeSDRInputGUI::on_gainMode_currentIndexChanged(int index) diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 38066caf6..713bb048a 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("3.14.6"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From a7e63df13d6ca330dd2480c2ade15feb40813074 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 13 Aug 2018 22:10:42 +0200 Subject: [PATCH 601/956] CW keyer: used signed char to avoid copmilation warnings in some installations --- sdrbase/dsp/cwkeyer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrbase/dsp/cwkeyer.h b/sdrbase/dsp/cwkeyer.h index a7d580964..a1c977a0c 100644 --- a/sdrbase/dsp/cwkeyer.h +++ b/sdrbase/dsp/cwkeyer.h @@ -127,7 +127,7 @@ private: bool m_dot; bool m_dash; bool m_elementOn; - char m_asciiChar; + signed char m_asciiChar; CWKeyIambicState m_keyIambicState; CWTextState m_textState; CWSmoother m_cwSmoother; From 65d2d2a664e298a2c81754756b1d31de0abe3e05 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 15 Aug 2018 02:48:59 +0200 Subject: [PATCH 602/956] UDP sink: removed queued connection on socket connection (gets stuck with Qt >= 5.8) --- plugins/channeltx/udpsink/udpsinkplugin.cpp | 2 +- plugins/channeltx/udpsink/udpsinkudphandler.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channeltx/udpsink/udpsinkplugin.cpp b/plugins/channeltx/udpsink/udpsinkplugin.cpp index 0f144ef1a..5d3b70ea0 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.cpp +++ b/plugins/channeltx/udpsink/udpsinkplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { QString("UDP Channel Sink"), - QString("4.0.6"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/udpsink/udpsinkudphandler.cpp b/plugins/channeltx/udpsink/udpsinkudphandler.cpp index da3f647a4..f017e0a3f 100644 --- a/plugins/channeltx/udpsink/udpsinkudphandler.cpp +++ b/plugins/channeltx/udpsink/udpsinkudphandler.cpp @@ -66,7 +66,7 @@ void UDPSinkUDPHandler::start() if (m_dataSocket->bind(m_dataAddress, m_dataPort)) { qDebug("UDPSinkUDPHandler::start: bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); - connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()), Qt::QueuedConnection); // , Qt::QueuedConnection + connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead())); // , Qt::QueuedConnection gets stuck since Qt 5.8.0 m_dataConnected = true; } else From 308204e65258f796bd5635a1aa8e81edf363f8bf Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 15 Aug 2018 03:04:31 +0200 Subject: [PATCH 603/956] PlutoSDR input: install server plugin in the correct sub directory --- pluginssrv/samplesource/plutosdrinput/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt b/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt index c56037df4..c66dccf4e 100644 --- a/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt +++ b/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt @@ -63,4 +63,4 @@ endif (BUILD_DEBIAN) qt5_use_modules(inputplutosdrsrv Core) -install(TARGETS inputplutosdrsrv DESTINATION lib/plugins/samplesource) +install(TARGETS inputplutosdrsrv DESTINATION lib/pluginssrv/samplesource) From 1c0e9010d9c4a68190778a3c777813292b7e90c0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 15 Aug 2018 10:30:12 +0200 Subject: [PATCH 604/956] Set spectrum default to log --- Readme.md | 2 -- sdrgui/gui/glspectrumgui.cpp | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 5e486aa35..105ff5b7d 100644 --- a/Readme.md +++ b/Readme.md @@ -4,8 +4,6 @@ **Check the discussion group** [here](https://groups.io/g/sdrangel) -**⚠ Warning**: Windows distribution is discontinued at version 4.0.0. This is the last version with a Windows build. -

    Source code

    Repository branches

    diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 10f11e7ad..62a200437 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -34,6 +34,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_averagingNb(0) { ui->setupUi(this); + on_linscale_toggled(false); ui->refLevel->clear(); for(int ref = 0; ref >= -110; ref -= 5) ui->refLevel->addItem(QString("%1").arg(ref)); From b1da29a3c441820cb7ee72fa1745480012f82352 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 15 Aug 2018 11:24:14 +0200 Subject: [PATCH 605/956] Sink plugins: corrected name getters and setters --- debian/changelog | 3 ++- plugins/channeltx/modam/ammod.h | 2 -- plugins/channeltx/modam/ammodplugin.cpp | 2 +- plugins/channeltx/modatv/atvmod.h | 2 -- plugins/channeltx/modatv/atvmodplugin.cpp | 2 +- plugins/channeltx/modnfm/nfmmod.h | 2 -- plugins/channeltx/modnfm/nfmmodplugin.cpp | 2 +- plugins/channeltx/modssb/ssbmod.h | 2 -- plugins/channeltx/modssb/ssbmodplugin.cpp | 2 +- plugins/channeltx/modwfm/wfmmod.h | 2 -- plugins/channeltx/modwfm/wfmmodplugin.cpp | 2 +- plugins/channeltx/udpsink/udpsink.h | 2 -- sdrbase/channel/channelsourceapi.h | 4 ++-- 13 files changed, 9 insertions(+), 20 deletions(-) diff --git a/debian/changelog b/debian/changelog index 7f5c48a93..87155db8d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,8 +2,9 @@ sdrangel (4.0.7-1) unstable; urgency=medium * Scope: removed old scope objects * Web API: reduced HTTP server debug messages + * Sink plugins: corrected name getters and setters - -- Edouard Griffiths, F4EXB Wed, 15 Aug 2018 19:14:18 +0200 + -- Edouard Griffiths, F4EXB Sun, 19 Aug 2018 21:14:18 +0200 sdrangel (4.0.6-1) unstable; urgency=medium diff --git a/plugins/channeltx/modam/ammod.h b/plugins/channeltx/modam/ammod.h index 5c0e4a6c1..4e41f52bf 100644 --- a/plugins/channeltx/modam/ammod.h +++ b/plugins/channeltx/modam/ammod.h @@ -208,8 +208,6 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; diff --git a/plugins/channeltx/modam/ammodplugin.cpp b/plugins/channeltx/modam/ammodplugin.cpp index cd356c0ab..2aa1bd0f5 100644 --- a/plugins/channeltx/modam/ammodplugin.cpp +++ b/plugins/channeltx/modam/ammodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor AMModPlugin::m_pluginDescriptor = { QString("AM Modulator"), - QString("4.0.6"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modatv/atvmod.h b/plugins/channeltx/modatv/atvmod.h index 55ba9d780..1e8913dc9 100644 --- a/plugins/channeltx/modatv/atvmod.h +++ b/plugins/channeltx/modatv/atvmod.h @@ -362,8 +362,6 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; diff --git a/plugins/channeltx/modatv/atvmodplugin.cpp b/plugins/channeltx/modatv/atvmodplugin.cpp index b5bbab39f..4aa4b1919 100644 --- a/plugins/channeltx/modatv/atvmodplugin.cpp +++ b/plugins/channeltx/modatv/atvmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor ATVModPlugin::m_pluginDescriptor = { QString("ATV Modulator"), - QString("4.0.6"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modnfm/nfmmod.h b/plugins/channeltx/modnfm/nfmmod.h index 3505cf71e..8a42d7a8b 100644 --- a/plugins/channeltx/modnfm/nfmmod.h +++ b/plugins/channeltx/modnfm/nfmmod.h @@ -210,8 +210,6 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp index 7498fb2c6..4b628a709 100644 --- a/plugins/channeltx/modnfm/nfmmodplugin.cpp +++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor NFMModPlugin::m_pluginDescriptor = { QString("NFM Modulator"), - QString("4.0.6"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modssb/ssbmod.h b/plugins/channeltx/modssb/ssbmod.h index 72cad1980..6dbd4760c 100644 --- a/plugins/channeltx/modssb/ssbmod.h +++ b/plugins/channeltx/modssb/ssbmod.h @@ -211,8 +211,6 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp index 228ce21cd..f8b146068 100644 --- a/plugins/channeltx/modssb/ssbmodplugin.cpp +++ b/plugins/channeltx/modssb/ssbmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor SSBModPlugin::m_pluginDescriptor = { QString("SSB Modulator"), - QString("4.0.6"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modwfm/wfmmod.h b/plugins/channeltx/modwfm/wfmmod.h index b5393921e..f742e7b26 100644 --- a/plugins/channeltx/modwfm/wfmmod.h +++ b/plugins/channeltx/modwfm/wfmmod.h @@ -209,8 +209,6 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp index 7a818e78e..afa3545d6 100644 --- a/plugins/channeltx/modwfm/wfmmodplugin.cpp +++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor WFMModPlugin::m_pluginDescriptor = { QString("WFM Modulator"), - QString("4.0.6"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/udpsink/udpsink.h b/plugins/channeltx/udpsink/udpsink.h index f2b4bd741..12497aaa2 100644 --- a/plugins/channeltx/udpsink/udpsink.h +++ b/plugins/channeltx/udpsink/udpsink.h @@ -99,8 +99,6 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; diff --git a/sdrbase/channel/channelsourceapi.h b/sdrbase/channel/channelsourceapi.h index 2b5cf8d59..44466bd33 100644 --- a/sdrbase/channel/channelsourceapi.h +++ b/sdrbase/channel/channelsourceapi.h @@ -38,8 +38,8 @@ public: virtual void getIdentifier(QString& id) = 0; virtual void getTitle(QString& title) = 0; - virtual void setName(const QString& name) = 0; - virtual QString getName() const = 0; + virtual void setName(const QString& name) { m_name = name; }; + virtual QString getName() const { return m_name; }; virtual qint64 getCenterFrequency() const = 0; virtual QByteArray serialize() const = 0; From 53d6161e8985bb480550cdc92e2df307d49ad1cd Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 15 Aug 2018 22:47:55 +0200 Subject: [PATCH 606/956] UDP source Web API setting correction. UDP sink default port set to 9998 --- Readme.md | 2 ++ plugins/channelrx/udpsrc/udpsrc.cpp | 2 +- plugins/channelrx/udpsrc/udpsrcplugin.cpp | 2 +- plugins/channeltx/udpsink/udpsinksettings.cpp | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index 105ff5b7d..ee0bfcdec 100644 --- a/Readme.md +++ b/Readme.md @@ -4,6 +4,8 @@ **Check the discussion group** [here](https://groups.io/g/sdrangel) +⚠ SDRangel is intended for the power user. We expect you to already have some experience with SDR applications and digital signal processing in general. SDRangel might be a bit overwhelming for you however you are encouraged to use the discussion group above to look for help. +

    Source code

    Repository branches

    diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index dc2e31df8..64b0689eb 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -658,7 +658,7 @@ int UDPSrc::webapiSettingsPutPatch( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - UDPSrcSettings settings; + UDPSrcSettings settings = m_settings; bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("outputSampleRate")) { diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.cpp b/plugins/channelrx/udpsrc/udpsrcplugin.cpp index 025482083..e31fc580e 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.cpp +++ b/plugins/channelrx/udpsrc/udpsrcplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor UDPSrcPlugin::m_pluginDescriptor = { QString("UDP Channel Source"), - QString("4.0.2"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/udpsink/udpsinksettings.cpp b/plugins/channeltx/udpsink/udpsinksettings.cpp index 7bcbdab13..cc8cdaf83 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.cpp +++ b/plugins/channeltx/udpsink/udpsinksettings.cpp @@ -46,7 +46,7 @@ void UDPSinkSettings::resetToDefaults() m_stereoInput = false; m_squelchEnabled = true; m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; + m_udpPort = 9998; m_rgbColor = QColor(225, 25, 99).rgb(); m_title = "UDP Sample Sink"; } From 2c7d45e9a6b0db3e68aa2f4ce752c659ec5c0f0f Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 16 Aug 2018 01:05:19 +0200 Subject: [PATCH 607/956] UDP sink: effectively implemented low cutoff setting from web API --- plugins/channeltx/udpsink/udpsink.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index d076f1a19..3390f0257 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -487,6 +487,7 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) << " m_sampleFormat: " << settings.m_sampleFormat << " m_inputSampleRate: " << settings.m_inputSampleRate << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_lowCutoff: " << settings.m_lowCutoff << " m_fmDeviation: " << settings.m_fmDeviation << " m_udpAddressStr: " << settings.m_udpAddress << " m_udpPort: " << settings.m_udpPort @@ -501,6 +502,7 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) << " force: " << force; if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || + (settings.m_lowCutoff != m_settings.m_lowCutoff) || (settings.m_inputSampleRate != m_settings.m_inputSampleRate) || force) { m_settingsMutex.lock(); From 3258331a76f9a1bb2309964b9da582e91d0871e0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 16 Aug 2018 01:05:50 +0200 Subject: [PATCH 608/956] UDP sink GUI: fixed internal widgets minimum widths --- plugins/channeltx/udpsink/udpsinkgui.ui | 56 ++++++++++++++++--------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsink/udpsinkgui.ui index 0fa5a35b1..7ce8502c6 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsink/udpsinkgui.ui @@ -6,13 +6,13 @@ 0 0 - 382 + 395 403 - 382 + 395 0 @@ -31,24 +31,24 @@ UDP Sample Sink + + -1 + UDP Sample Sink - - -1 - 2 2 - 380 + 390 141 - 380 + 390 0 @@ -56,7 +56,16 @@ Settings - + + 2 + + + 2 + + + 2 + + 2 @@ -83,7 +92,7 @@ Input sample rate (S/s) - 0009999; + 0009999 48000 @@ -98,7 +107,7 @@ - 36 + 40 0 @@ -113,7 +122,7 @@ Signal bandwidth (Hz) - 0009999; + 0009999 32000 @@ -503,7 +512,7 @@ FM deviation in Hz - 00000; + 00000 2500 @@ -519,17 +528,17 @@ - + - 60 - 16777215 + 26 + 0 Percentage of AM modulation - 000; + 000 95 @@ -806,7 +815,7 @@ Local UDP address - 000.000.000.000; + 000.000.000.000 127.0.0.1 @@ -850,7 +859,7 @@ Local UDP port - 00000; + 00000 9998 @@ -960,7 +969,16 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 From ac4b016ee7137f8d1d836b8f24f4ea1389e439ee Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 16 Aug 2018 10:46:01 +0200 Subject: [PATCH 609/956] UDP source and sink: Web API: more informative report --- plugins/channelrx/udpsrc/udpsrc.cpp | 3 +- plugins/channeltx/udpsink/udpsink.cpp | 3 + sdrbase/resources/webapi/doc/html2/index.html | 24 ++++++- .../webapi/doc/swagger/include/UDPSink.yaml | 10 +++ .../webapi/doc/swagger/include/UDPSrc.yaml | 6 +- .../resources/webapi/doc/swagger/swagger.yaml | 2 +- .../sdrangel/api/swagger/include/UDPSink.yaml | 10 +++ .../sdrangel/api/swagger/include/UDPSrc.yaml | 6 +- swagger/sdrangel/api/swagger/swagger.yaml | 2 +- swagger/sdrangel/code/html2/index.html | 24 ++++++- .../code/qt5/client/SWGAMDemodReport.cpp | 2 +- .../code/qt5/client/SWGAMDemodReport.h | 2 +- .../code/qt5/client/SWGAMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGAMDemodSettings.h | 2 +- .../code/qt5/client/SWGAMModReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGAMModReport.h | 2 +- .../code/qt5/client/SWGAMModSettings.cpp | 2 +- .../code/qt5/client/SWGAMModSettings.h | 2 +- .../code/qt5/client/SWGATVModReport.cpp | 2 +- .../code/qt5/client/SWGATVModReport.h | 2 +- .../code/qt5/client/SWGATVModSettings.cpp | 2 +- .../code/qt5/client/SWGATVModSettings.h | 2 +- .../code/qt5/client/SWGAirspyHFReport.cpp | 2 +- .../code/qt5/client/SWGAirspyHFReport.h | 2 +- .../code/qt5/client/SWGAirspyHFSettings.cpp | 2 +- .../code/qt5/client/SWGAirspyHFSettings.h | 2 +- .../code/qt5/client/SWGAirspyReport.cpp | 2 +- .../code/qt5/client/SWGAirspyReport.h | 2 +- .../code/qt5/client/SWGAirspySettings.cpp | 2 +- .../code/qt5/client/SWGAirspySettings.h | 2 +- .../code/qt5/client/SWGAudioDevices.cpp | 2 +- .../code/qt5/client/SWGAudioDevices.h | 2 +- .../code/qt5/client/SWGAudioInputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioInputDevice.h | 2 +- .../code/qt5/client/SWGAudioOutputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioOutputDevice.h | 2 +- .../code/qt5/client/SWGBFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGBFMDemodReport.h | 2 +- .../code/qt5/client/SWGBFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGBFMDemodSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.cpp | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.h | 2 +- .../qt5/client/SWGBladeRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGBladeRFInputSettings.h | 2 +- .../qt5/client/SWGBladeRFOutputSettings.cpp | 2 +- .../qt5/client/SWGBladeRFOutputSettings.h | 2 +- .../code/qt5/client/SWGCWKeyerSettings.cpp | 2 +- .../code/qt5/client/SWGCWKeyerSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGChannel.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGChannel.h | 2 +- .../code/qt5/client/SWGChannelListItem.cpp | 2 +- .../code/qt5/client/SWGChannelListItem.h | 2 +- .../code/qt5/client/SWGChannelReport.cpp | 2 +- .../code/qt5/client/SWGChannelReport.h | 2 +- .../code/qt5/client/SWGChannelSettings.cpp | 2 +- .../code/qt5/client/SWGChannelSettings.h | 2 +- .../code/qt5/client/SWGChannelsDetail.cpp | 2 +- .../code/qt5/client/SWGChannelsDetail.h | 2 +- .../code/qt5/client/SWGDSDDemodReport.cpp | 2 +- .../code/qt5/client/SWGDSDDemodReport.h | 2 +- .../code/qt5/client/SWGDSDDemodSettings.cpp | 2 +- .../code/qt5/client/SWGDSDDemodSettings.h | 2 +- .../code/qt5/client/SWGDVSeralDevices.cpp | 2 +- .../code/qt5/client/SWGDVSeralDevices.h | 2 +- .../code/qt5/client/SWGDVSerialDevice.cpp | 2 +- .../code/qt5/client/SWGDVSerialDevice.h | 2 +- .../code/qt5/client/SWGDeviceListItem.cpp | 2 +- .../code/qt5/client/SWGDeviceListItem.h | 2 +- .../code/qt5/client/SWGDeviceReport.cpp | 2 +- .../code/qt5/client/SWGDeviceReport.h | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.h | 2 +- .../code/qt5/client/SWGDeviceSetApi.cpp | 2 +- .../code/qt5/client/SWGDeviceSetApi.h | 2 +- .../code/qt5/client/SWGDeviceSetList.cpp | 2 +- .../code/qt5/client/SWGDeviceSetList.h | 2 +- .../code/qt5/client/SWGDeviceSettings.cpp | 2 +- .../code/qt5/client/SWGDeviceSettings.h | 2 +- .../code/qt5/client/SWGDeviceState.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceState.h | 2 +- .../code/qt5/client/SWGErrorResponse.cpp | 2 +- .../code/qt5/client/SWGErrorResponse.h | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.h | 2 +- .../code/qt5/client/SWGFCDProSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProSettings.h | 2 +- .../code/qt5/client/SWGFileSourceReport.cpp | 2 +- .../code/qt5/client/SWGFileSourceReport.h | 2 +- .../code/qt5/client/SWGFileSourceSettings.cpp | 2 +- .../code/qt5/client/SWGFileSourceSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.cpp | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.h | 2 +- .../code/qt5/client/SWGFrequencyBand.cpp | 2 +- .../code/qt5/client/SWGFrequencyBand.h | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.h | 2 +- .../qt5/client/SWGHackRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFInputSettings.h | 2 +- .../qt5/client/SWGHackRFOutputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFOutputSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGHelpers.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGHelpers.h | 2 +- .../code/qt5/client/SWGHttpRequest.cpp | 2 +- .../sdrangel/code/qt5/client/SWGHttpRequest.h | 2 +- .../code/qt5/client/SWGInstanceApi.cpp | 2 +- .../sdrangel/code/qt5/client/SWGInstanceApi.h | 2 +- .../client/SWGInstanceChannelsResponse.cpp | 2 +- .../qt5/client/SWGInstanceChannelsResponse.h | 2 +- .../qt5/client/SWGInstanceDevicesResponse.cpp | 2 +- .../qt5/client/SWGInstanceDevicesResponse.h | 2 +- .../qt5/client/SWGInstanceSummaryResponse.cpp | 2 +- .../qt5/client/SWGInstanceSummaryResponse.h | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.h | 2 +- .../qt5/client/SWGLimeSdrInputSettings.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputSettings.h | 2 +- .../qt5/client/SWGLimeSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrOutputReport.h | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.h | 2 +- .../qt5/client/SWGLocationInformation.cpp | 2 +- .../code/qt5/client/SWGLocationInformation.h | 2 +- .../code/qt5/client/SWGLoggingInfo.cpp | 2 +- .../sdrangel/code/qt5/client/SWGLoggingInfo.h | 2 +- .../code/qt5/client/SWGModelFactory.h | 2 +- .../code/qt5/client/SWGNFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGNFMDemodReport.h | 2 +- .../code/qt5/client/SWGNFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGNFMDemodSettings.h | 2 +- .../code/qt5/client/SWGNFMModReport.cpp | 2 +- .../code/qt5/client/SWGNFMModReport.h | 2 +- .../code/qt5/client/SWGNFMModSettings.cpp | 2 +- .../code/qt5/client/SWGNFMModSettings.h | 2 +- swagger/sdrangel/code/qt5/client/SWGObject.h | 2 +- .../code/qt5/client/SWGPerseusReport.cpp | 2 +- .../code/qt5/client/SWGPerseusReport.h | 2 +- .../code/qt5/client/SWGPerseusSettings.cpp | 2 +- .../code/qt5/client/SWGPerseusSettings.h | 2 +- .../qt5/client/SWGPlutoSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrInputReport.h | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.h | 2 +- .../qt5/client/SWGPlutoSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrOutputReport.h | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.h | 2 +- .../code/qt5/client/SWGPresetExport.cpp | 2 +- .../code/qt5/client/SWGPresetExport.h | 2 +- .../code/qt5/client/SWGPresetGroup.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetGroup.h | 2 +- .../code/qt5/client/SWGPresetIdentifier.cpp | 2 +- .../code/qt5/client/SWGPresetIdentifier.h | 2 +- .../code/qt5/client/SWGPresetImport.cpp | 2 +- .../code/qt5/client/SWGPresetImport.h | 2 +- .../code/qt5/client/SWGPresetItem.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetItem.h | 2 +- .../code/qt5/client/SWGPresetTransfer.cpp | 2 +- .../code/qt5/client/SWGPresetTransfer.h | 2 +- .../sdrangel/code/qt5/client/SWGPresets.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGPresets.h | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.h | 2 +- .../client/SWGRDSReport_altFrequencies.cpp | 2 +- .../qt5/client/SWGRDSReport_altFrequencies.h | 2 +- .../code/qt5/client/SWGRtlSdrReport.cpp | 2 +- .../code/qt5/client/SWGRtlSdrReport.h | 2 +- .../code/qt5/client/SWGRtlSdrSettings.cpp | 2 +- .../code/qt5/client/SWGRtlSdrSettings.h | 2 +- .../code/qt5/client/SWGSDRPlayReport.cpp | 2 +- .../code/qt5/client/SWGSDRPlayReport.h | 2 +- .../code/qt5/client/SWGSDRPlaySettings.cpp | 2 +- .../code/qt5/client/SWGSDRPlaySettings.h | 2 +- .../qt5/client/SWGSDRdaemonSinkReport.cpp | 2 +- .../code/qt5/client/SWGSDRdaemonSinkReport.h | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.h | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.h | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.h | 2 +- .../code/qt5/client/SWGSSBDemodReport.cpp | 2 +- .../code/qt5/client/SWGSSBDemodReport.h | 2 +- .../code/qt5/client/SWGSSBDemodSettings.cpp | 2 +- .../code/qt5/client/SWGSSBDemodSettings.h | 2 +- .../code/qt5/client/SWGSSBModReport.cpp | 2 +- .../code/qt5/client/SWGSSBModReport.h | 2 +- .../code/qt5/client/SWGSSBModSettings.cpp | 2 +- .../code/qt5/client/SWGSSBModSettings.h | 2 +- .../code/qt5/client/SWGSampleRate.cpp | 2 +- .../sdrangel/code/qt5/client/SWGSampleRate.h | 2 +- .../code/qt5/client/SWGSamplingDevice.cpp | 2 +- .../code/qt5/client/SWGSamplingDevice.h | 2 +- .../code/qt5/client/SWGSuccessResponse.cpp | 2 +- .../code/qt5/client/SWGSuccessResponse.h | 2 +- .../code/qt5/client/SWGTestSourceSettings.cpp | 2 +- .../code/qt5/client/SWGTestSourceSettings.h | 2 +- .../code/qt5/client/SWGUDPSinkReport.cpp | 65 ++++++++++++++++++- .../code/qt5/client/SWGUDPSinkReport.h | 20 +++++- .../code/qt5/client/SWGUDPSinkSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSinkSettings.h | 2 +- .../code/qt5/client/SWGUDPSrcReport.cpp | 23 ++++++- .../code/qt5/client/SWGUDPSrcReport.h | 8 ++- .../code/qt5/client/SWGUDPSrcSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSrcSettings.h | 2 +- .../code/qt5/client/SWGWFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGWFMDemodReport.h | 2 +- .../code/qt5/client/SWGWFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGWFMDemodSettings.h | 2 +- .../code/qt5/client/SWGWFMModReport.cpp | 2 +- .../code/qt5/client/SWGWFMModReport.h | 2 +- .../code/qt5/client/SWGWFMModSettings.cpp | 2 +- .../code/qt5/client/SWGWFMModSettings.h | 2 +- 212 files changed, 389 insertions(+), 213 deletions(-) diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index 64b0689eb..25689e017 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -790,7 +790,8 @@ void UDPSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon void UDPSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { - response.getUdpSrcReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getUdpSrcReport()->setChannelPowerDb(CalcDb::dbPower(getInMagSq())); + response.getUdpSrcReport()->setOutputPowerDb(CalcDb::dbPower(getMagSq())); response.getUdpSrcReport()->setSquelch(m_squelchOpen ? 1 : 0); response.getUdpSrcReport()->setInputSampleRate(m_inputSampleRate); } diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index 3390f0257..374d5cf2d 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -744,6 +744,9 @@ void UDPSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respo void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { + response.getUdpSinkReport()->setInputPowerDb(CalcDb::dbPower(getInMagSq())); response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getUdpSinkReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getUdpSinkReport()->setBufferGauge(getBufferGauge()); response.getUdpSinkReport()->setChannelSampleRate(m_outputSampleRate); } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 5ba6c571f..64864120e 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3627,6 +3627,19 @@ margin-bottom: 20px; "format" : "float", "description" : "power transmitted in channel (dB)" }, + "inputPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power received from UDP stream (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "bufferGauge" : { + "type" : "integer", + "description" : "buffer R/W balance indicator" + }, "channelSampleRate" : { "type" : "integer" } @@ -3707,11 +3720,16 @@ margin-bottom: 20px; }; defs.UDPSrcReport = { "properties" : { - "channelPowerDB" : { + "outputPowerDB" : { "type" : "number", "format" : "float", "description" : "power transmitted in channel (dB)" }, + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power received in channel (dB)" + }, "squelch" : { "type" : "integer", "description" : "squelch status (1 if open else 0)" @@ -4087,7 +4105,7 @@ margin-bottom: 20px; diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.cpp index d5427fc54..e2e81ae9d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.cpp @@ -44,6 +44,8 @@ SWGDaemonSummaryResponse::SWGDaemonSummaryResponse() { m_architecture_isSet = false; os = nullptr; m_os_isSet = false; + logging = nullptr; + m_logging_isSet = false; sampling_device = nullptr; m_sampling_device_isSet = false; } @@ -70,6 +72,8 @@ SWGDaemonSummaryResponse::init() { m_architecture_isSet = false; os = new QString(""); m_os_isSet = false; + logging = new SWGLoggingInfo(); + m_logging_isSet = false; sampling_device = new SWGSamplingDevice(); m_sampling_device_isSet = false; } @@ -94,6 +98,9 @@ SWGDaemonSummaryResponse::cleanup() { if(os != nullptr) { delete os; } + if(logging != nullptr) { + delete logging; + } if(sampling_device != nullptr) { delete sampling_device; } @@ -126,6 +133,8 @@ SWGDaemonSummaryResponse::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&os, pJson["os"], "QString", "QString"); + ::SWGSDRangel::setValue(&logging, pJson["logging"], "SWGLoggingInfo", "SWGLoggingInfo"); + ::SWGSDRangel::setValue(&sampling_device, pJson["samplingDevice"], "SWGSamplingDevice", "SWGSamplingDevice"); } @@ -168,6 +177,9 @@ SWGDaemonSummaryResponse::asJsonObject() { if(os != nullptr && *os != QString("")){ toJsonValue(QString("os"), os, obj, QString("QString")); } + if((logging != nullptr) && (logging->isSet())){ + toJsonValue(QString("logging"), logging, obj, QString("SWGLoggingInfo")); + } if((sampling_device != nullptr) && (sampling_device->isSet())){ toJsonValue(QString("samplingDevice"), sampling_device, obj, QString("SWGSamplingDevice")); } @@ -255,6 +267,16 @@ SWGDaemonSummaryResponse::setOs(QString* os) { this->m_os_isSet = true; } +SWGLoggingInfo* +SWGDaemonSummaryResponse::getLogging() { + return logging; +} +void +SWGDaemonSummaryResponse::setLogging(SWGLoggingInfo* logging) { + this->logging = logging; + this->m_logging_isSet = true; +} + SWGSamplingDevice* SWGDaemonSummaryResponse::getSamplingDevice() { return sampling_device; @@ -278,6 +300,7 @@ SWGDaemonSummaryResponse::isSet(){ if(appname != nullptr && *appname != QString("")){ isObjectUpdated = true; break;} if(architecture != nullptr && *architecture != QString("")){ isObjectUpdated = true; break;} if(os != nullptr && *os != QString("")){ isObjectUpdated = true; break;} + if(logging != nullptr && logging->isSet()){ isObjectUpdated = true; break;} if(sampling_device != nullptr && sampling_device->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.h index f0e15ef94..5444e7160 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.h @@ -22,6 +22,7 @@ #include +#include "SWGLoggingInfo.h" #include "SWGSamplingDevice.h" #include @@ -67,6 +68,9 @@ public: QString* getOs(); void setOs(QString* os); + SWGLoggingInfo* getLogging(); + void setLogging(SWGLoggingInfo* logging); + SWGSamplingDevice* getSamplingDevice(); void setSamplingDevice(SWGSamplingDevice* sampling_device); @@ -98,6 +102,9 @@ private: QString* os; bool m_os_isSet; + SWGLoggingInfo* logging; + bool m_logging_isSet; + SWGSamplingDevice* sampling_device; bool m_sampling_device_isSet; From dcfb405dd5b41dc0725bca875cb38193acb81f87 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 19 Aug 2018 19:55:12 +0200 Subject: [PATCH 623/956] SDRdaemon: Web API: implemented daemonSettingsGet --- sdrdaemon/webapi/webapiadapterdaemon.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index 84626b7b7..b70f3bea0 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -179,8 +179,26 @@ int WebAPIAdapterDaemon::daemonSettingsGet( SWGSDRangel::SWGErrorResponse& error) { error.init(); - *error.getMessage() = "Not implemented"; - return 501; + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + response.setDeviceHwType(new QString(m_sdrDaemonMain.m_deviceSourceAPI->getHardwareId())); + response.setTx(0); + DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); + return source->webapiSettingsGet(response, *error.getMessage()); + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + response.setDeviceHwType(new QString(m_sdrDaemonMain.m_deviceSinkAPI->getHardwareId())); + response.setTx(1); + DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); + return sink->webapiSettingsGet(response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("Device error"); + return 500; + } } int WebAPIAdapterDaemon::daemonSettingsPutPatch( From 1d968e9efa6222a53ba5831f882ada2605571811 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 19 Aug 2018 19:58:14 +0200 Subject: [PATCH 624/956] SDRdaemon: Web API: implemented daemonSettingsPutPatch --- sdrdaemon/webapi/webapiadapterdaemon.cpp | 49 ++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index b70f3bea0..35fda8ab5 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -202,12 +202,55 @@ int WebAPIAdapterDaemon::daemonSettingsGet( } int WebAPIAdapterDaemon::daemonSettingsPutPatch( - bool force __attribute__((unused)), - const QStringList& deviceSettingsKeys __attribute__((unused)), - SWGSDRangel::SWGDeviceSettings& response __attribute__((unused)), + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, SWGSDRangel::SWGErrorResponse& error) { error.init(); + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + if (response.getTx() != 0) + { + *error.getMessage() = QString("Rx device found but Tx device requested"); + return 400; + } + if (m_sdrDaemonMain.m_deviceSourceAPI->getHardwareId() != *response.getDeviceHwType()) + { + *error.getMessage() = QString("Device mismatch. Found %1 input").arg(m_sdrDaemonMain.m_deviceSourceAPI->getHardwareId()); + return 400; + } + else + { + DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); + return source->webapiSettingsPutPatch(force, deviceSettingsKeys, response, *error.getMessage()); + } + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + if (response.getTx() == 0) + { + *error.getMessage() = QString("Tx device found but Rx device requested"); + return 400; + } + else if (m_sdrDaemonMain.m_deviceSinkAPI->getHardwareId() != *response.getDeviceHwType()) + { + *error.getMessage() = QString("Device mismatch. Found %1 output").arg(m_sdrDaemonMain.m_deviceSinkAPI->getHardwareId()); + return 400; + } + else + { + DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); + return sink->webapiSettingsPutPatch(force, deviceSettingsKeys, response, *error.getMessage()); + } + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } + *error.getMessage() = "Not implemented"; return 501; } From 89dd62adbf198b0d9525d1058813a82f1af8215d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 19 Aug 2018 20:02:42 +0200 Subject: [PATCH 625/956] SDRdaemon: Web API: implemented daemonRunGet --- sdrdaemon/webapi/webapiadapterdaemon.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index 35fda8ab5..27e84849e 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -250,9 +250,6 @@ int WebAPIAdapterDaemon::daemonSettingsPutPatch( *error.getMessage() = QString("DeviceSet error"); return 500; } - - *error.getMessage() = "Not implemented"; - return 501; } int WebAPIAdapterDaemon::daemonRunGet( @@ -260,8 +257,24 @@ int WebAPIAdapterDaemon::daemonRunGet( SWGSDRangel::SWGErrorResponse& error) { error.init(); - *error.getMessage() = "Not implemented"; - return 501; + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); + response.init(); + return source->webapiRunGet(response, *error.getMessage()); + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); + response.init(); + return sink->webapiRunGet(response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } } int WebAPIAdapterDaemon::daemonRunPost( From b169ccefadeb5b9291b9a83b54f6e580bc1a1047 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 19 Aug 2018 20:08:07 +0200 Subject: [PATCH 626/956] SDRdaemon: Web API: implemented daemonRunPost and daemonRunDelete --- sdrdaemon/webapi/webapiadapterdaemon.cpp | 46 ++++++++++++++++++++---- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index 27e84849e..c240702fe 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -253,7 +253,7 @@ int WebAPIAdapterDaemon::daemonSettingsPutPatch( } int WebAPIAdapterDaemon::daemonRunGet( - SWGSDRangel::SWGDeviceState& response __attribute__((unused)), + SWGSDRangel::SWGDeviceState& response, SWGSDRangel::SWGErrorResponse& error) { error.init(); @@ -278,21 +278,53 @@ int WebAPIAdapterDaemon::daemonRunGet( } int WebAPIAdapterDaemon::daemonRunPost( - SWGSDRangel::SWGDeviceState& response __attribute__((unused)), + SWGSDRangel::SWGDeviceState& response, SWGSDRangel::SWGErrorResponse& error) { error.init(); - *error.getMessage() = "Not implemented"; - return 501; + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); + response.init(); + return source->webapiRun(true, response, *error.getMessage()); + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); + response.init(); + return sink->webapiRun(true, response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } } int WebAPIAdapterDaemon::daemonRunDelete( - SWGSDRangel::SWGDeviceState& response __attribute__((unused)), + SWGSDRangel::SWGDeviceState& response, SWGSDRangel::SWGErrorResponse& error) { error.init(); - *error.getMessage() = "Not implemented"; - return 501; + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); + response.init(); + return source->webapiRun(false, response, *error.getMessage()); + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); + response.init(); + return sink->webapiRun(false, response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } } int WebAPIAdapterDaemon::daemonReportGet( From ef3bc466d6d15fcf1a80586a07755334a0174567 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 19 Aug 2018 20:11:31 +0200 Subject: [PATCH 627/956] SDRdaemon: Web API: implemented daemonReportGet --- sdrdaemon/webapi/webapiadapterdaemon.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index c240702fe..a92641bc2 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -328,12 +328,30 @@ int WebAPIAdapterDaemon::daemonRunDelete( } int WebAPIAdapterDaemon::daemonReportGet( - SWGSDRangel::SWGDeviceReport& response __attribute__((unused)), + SWGSDRangel::SWGDeviceReport& response, SWGSDRangel::SWGErrorResponse& error) { error.init(); - *error.getMessage() = "Not implemented"; - return 501; + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + response.setDeviceHwType(new QString(m_sdrDaemonMain.m_deviceSourceAPI->getHardwareId())); + response.setTx(0); + DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); + return source->webapiReportGet(response, *error.getMessage()); + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + response.setDeviceHwType(new QString(m_sdrDaemonMain.m_deviceSinkAPI->getHardwareId())); + response.setTx(1); + DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); + return sink->webapiReportGet(response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } } // TODO: put in library in common with SDRangel. Can be static. From dfb619cd98115ead84512df29145d6ca837779bc Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 19 Aug 2018 23:36:30 +0200 Subject: [PATCH 628/956] SDRdaemon: added channel sink --- sdrdaemon/CMakeLists.txt | 2 + sdrdaemon/channel/sdrdaemonchannelsink.cpp | 84 ++++++++++++++++++++++ sdrdaemon/channel/sdrdaemonchannelsink.h | 64 +++++++++++++++++ sdrdaemon/sdrdaemonmain.cpp | 8 +++ sdrdaemon/sdrdaemonmain.h | 3 + 5 files changed, 161 insertions(+) create mode 100644 sdrdaemon/channel/sdrdaemonchannelsink.cpp create mode 100644 sdrdaemon/channel/sdrdaemonchannelsink.h diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index 14dc5d93c..0950d9c12 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -5,6 +5,7 @@ set(sdrdaemon_SOURCES sdrdaemonpreferences.cpp sdrdaemonsettings.cpp sdrdaemonparser.cpp + channel/sdrdaemonchannelsink.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -15,6 +16,7 @@ set(sdrdaemon_HEADERS sdrdaemonpreferences.h sdrdaemonsettings.h sdrdaemonparser.h + channel/sdrdaemonchannelsink.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp new file mode 100644 index 000000000..68f22efaf --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "util/simpleserializer.h" +#include "dsp/threadedbasebandsamplesink.h" +#include "dsp/downchannelizer.h" +#include "device/devicesourceapi.h" +#include "sdrdaemonchannelsink.h" + +const QString SDRDaemonChannelSink::m_channelIdURI = "sdrangel.channel.sdrdaemonsink"; +const QString SDRDaemonChannelSink::m_channelId = "SDRDaemonChannelSink"; + +SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : + ChannelSinkAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_running(false) +{ + setObjectName(m_channelId); + + m_channelizer = new DownChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); + m_deviceAPI->addThreadedSink(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); +} + +SDRDaemonChannelSink::~SDRDaemonChannelSink() +{ + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSink(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) +{ + qDebug("SDRDaemonChannelSink::feed: received %d samples", (int) (end - begin)); +} + +void SDRDaemonChannelSink::start() +{ + qDebug("SDRDaemonChannelSink::start"); + m_running = true; +} + +void SDRDaemonChannelSink::stop() +{ + qDebug("SDRDaemonChannelSink::stop"); + m_running = false; +} + +bool SDRDaemonChannelSink::handleMessage(const Message& cmd __attribute__((unused))) +{ + return false; +} + +QByteArray SDRDaemonChannelSink::serialize() const +{ + SimpleSerializer s(1); + return s.final(); +} + +bool SDRDaemonChannelSink::deserialize(const QByteArray& data __attribute__((unused))) +{ + return false; +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h new file mode 100644 index 000000000..df9b117dd --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" + +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; + +class SDRDaemonChannelSink : public BasebandSampleSink, public ChannelSinkAPI { + Q_OBJECT +public: + SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI); + virtual ~SDRDaemonChannelSink(); + 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); + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + + bool m_running; + +}; + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/sdrdaemonmain.cpp b/sdrdaemon/sdrdaemonmain.cpp index 0ed1393fa..e7faf6667 100644 --- a/sdrdaemon/sdrdaemonmain.cpp +++ b/sdrdaemon/sdrdaemonmain.cpp @@ -37,6 +37,7 @@ #include "webapi/webapiadapterdaemon.h" #include "webapi/webapirequestmapper.h" #include "webapi/webapiserver.h" +#include "channel/sdrdaemonchannelsink.h" #include "sdrdaemonparser.h" #include "sdrdaemonmain.h" @@ -85,6 +86,7 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa m_deviceSinkEngine = 0; m_deviceSourceAPI = 0; m_deviceSinkAPI = 0; + m_channelSink = 0; if (m_tx) { @@ -123,6 +125,7 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa QDebug info = qInfo(); info.noquote(); info << msg; + m_channelSink = new SDRDaemonChannelSink(m_deviceSourceAPI); } else { @@ -283,6 +286,11 @@ void SDRDaemonMain::removeDevice() m_deviceSourceEngine->stopAcquistion(); // deletes old UI and input object + + if (m_channelSink) { + m_channelSink->destroy(); + } + m_deviceSourceAPI->resetSampleSourceId(); m_deviceSourceAPI->getPluginInterface()->deleteSampleSourcePluginInstanceInput( m_deviceSourceAPI->getSampleSource()); diff --git a/sdrdaemon/sdrdaemonmain.h b/sdrdaemon/sdrdaemonmain.h index f57b16f1c..8eb07211a 100644 --- a/sdrdaemon/sdrdaemonmain.h +++ b/sdrdaemon/sdrdaemonmain.h @@ -47,6 +47,7 @@ class DSPDeviceSourceEngine; class DeviceSourceAPI; class DSPDeviceSinkEngine; class DeviceSinkAPI; +class SDRDaemonChannelSink; class SDRDaemonMain : public QObject { Q_OBJECT @@ -92,11 +93,13 @@ private: DeviceSourceAPI *m_deviceSourceAPI; DSPDeviceSinkEngine *m_deviceSinkEngine; DeviceSinkAPI *m_deviceSinkAPI; + SDRDaemonChannelSink *m_channelSink; void loadSettings(); void setLoggingOptions(); int getDeviceIndex(); bool handleMessage(const Message& cmd); + void addChannelSink(); private slots: void handleMessages(); From 39212e6317e3c140e1bfbd45b6a118dfde3bc257 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 20 Aug 2018 00:38:33 +0200 Subject: [PATCH 629/956] SDRdaemon: added channel source --- appdaemon/main.cpp | 13 ++- sdrdaemon/CMakeLists.txt | 2 + sdrdaemon/channel/sdrdaemonchannelsink.h | 1 - sdrdaemon/channel/sdrdaemonchannelsource.cpp | 94 ++++++++++++++++++++ sdrdaemon/channel/sdrdaemonchannelsource.h | 65 ++++++++++++++ sdrdaemon/sdrdaemonmain.cpp | 55 +++++++----- sdrdaemon/sdrdaemonmain.h | 6 ++ sdrdaemon/sdrdaemonparser.cpp | 27 ++++-- 8 files changed, 227 insertions(+), 36 deletions(-) create mode 100644 sdrdaemon/channel/sdrdaemonchannelsource.cpp create mode 100644 sdrdaemon/channel/sdrdaemonchannelsource.h diff --git a/appdaemon/main.cpp b/appdaemon/main.cpp index 937ce5960..e9feb9d2b 100644 --- a/appdaemon/main.cpp +++ b/appdaemon/main.cpp @@ -94,10 +94,17 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo SDRDaemonMain m(logger, parser, &a); - // This will cause the application to exit when SDRdaemon is finished - QObject::connect(&m, SIGNAL(finished()), &a, SLOT(quit())); + if (m.doAbort()) + { + return -1; + } + else + { + // This will cause the application to exit when SDRdaemon is finished + QObject::connect(&m, SIGNAL(finished()), &a, SLOT(quit())); - return a.exec(); + return a.exec(); + } } int main(int argc, char* argv[]) diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index 0950d9c12..52422863e 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -6,6 +6,7 @@ set(sdrdaemon_SOURCES sdrdaemonsettings.cpp sdrdaemonparser.cpp channel/sdrdaemonchannelsink.cpp + channel/sdrdaemonchannelsource.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -17,6 +18,7 @@ set(sdrdaemon_HEADERS sdrdaemonsettings.h sdrdaemonparser.h channel/sdrdaemonchannelsink.h + channel/sdrdaemonchannelsource.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index df9b117dd..f92a90da7 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -58,7 +58,6 @@ private: DownChannelizer* m_channelizer; bool m_running; - }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp new file mode 100644 index 000000000..f22886299 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -0,0 +1,94 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) // +// // +// 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 "dsp/threadedbasebandsamplesource.h" +#include "dsp/upchannelizer.h" +#include "device/devicesinkapi.h" +#include "sdrdaemonchannelsource.h" + +const QString SDRDaemonChannelSource::m_channelIdURI = "sdrangel.channel.sdrdaemonsource"; +const QString SDRDaemonChannelSource::m_channelId = "SDRDaemonChannelSource"; + +SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : + ChannelSourceAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_running(false), + m_samplesCount(0) +{ + setObjectName(m_channelId); + + m_channelizer = new UpChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); + m_deviceAPI->addThreadedSource(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); +} + +SDRDaemonChannelSource::~SDRDaemonChannelSource() +{ + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSource(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +void SDRDaemonChannelSource::pull(Sample& sample) +{ + sample.m_real = 0.0f; + sample.m_imag = 0.0f; + + if (m_samplesCount < 1023) { + m_samplesCount++; + } else { + qDebug("SDRDaemonChannelSource::pull: 1024 samples pulled"); + m_samplesCount = 0; + } +} + +void SDRDaemonChannelSource::start() +{ + qDebug("SDRDaemonChannelSink::start"); + m_running = true; +} + +void SDRDaemonChannelSource::stop() +{ + qDebug("SDRDaemonChannelSink::stop"); + m_running = false; +} + +bool SDRDaemonChannelSource::handleMessage(const Message& cmd __attribute__((unused))) +{ + return false; +} + +QByteArray SDRDaemonChannelSource::serialize() const +{ + SimpleSerializer s(1); + return s.final(); +} + +bool SDRDaemonChannelSource::deserialize(const QByteArray& data __attribute__((unused))) +{ + return false; +} + diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h new file mode 100644 index 000000000..c5b543199 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) // +// // +// 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 SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCE_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCE_H_ + +#include "dsp/basebandsamplesource.h" +#include "channel/channelsourceapi.h" + +class ThreadedBasebandSampleSource; +class UpChannelizer; +class DeviceSinkAPI; + +class SDRDaemonChannelSource : public BasebandSampleSource, public ChannelSourceAPI { + Q_OBJECT +public: + SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI); + ~SDRDaemonChannelSource(); + virtual void destroy() { delete this; } + + virtual void pull(Sample& sample); + 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 Source"; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + DeviceSinkAPI *m_deviceAPI; + ThreadedBasebandSampleSource* m_threadedChannelizer; + UpChannelizer* m_channelizer; + + bool m_running; + uint32_t m_samplesCount; +}; + + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCE_H_ */ diff --git a/sdrdaemon/sdrdaemonmain.cpp b/sdrdaemon/sdrdaemonmain.cpp index e7faf6667..ab51d6ecc 100644 --- a/sdrdaemon/sdrdaemonmain.cpp +++ b/sdrdaemon/sdrdaemonmain.cpp @@ -38,6 +38,7 @@ #include "webapi/webapirequestmapper.h" #include "webapi/webapiserver.h" #include "channel/sdrdaemonchannelsink.h" +#include "channel/sdrdaemonchannelsource.h" #include "sdrdaemonparser.h" #include "sdrdaemonmain.h" @@ -48,7 +49,8 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa m_logger(logger), m_settings(), m_dspEngine(DSPEngine::instance()), - m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), + m_abort(false) { qDebug() << "SDRDaemonMain::SDRDaemonMain: start"; @@ -87,6 +89,7 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa m_deviceSourceAPI = 0; m_deviceSinkAPI = 0; m_channelSink = 0; + m_channelSource = 0; if (m_tx) { @@ -103,11 +106,12 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa QDebug info = qInfo(); info.noquote(); info << msg; + m_channelSource = new SDRDaemonChannelSource(m_deviceSinkAPI); } else { qCritical("SDRDaemonMain::SDRDaemonMain: sink device not found aborting"); - emit finished(); + m_abort = true; } } else @@ -130,7 +134,7 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa else { qCritical("SDRDaemonMain::SDRDaemonMain: source device not found aborting"); - emit finished(); + m_abort = true; } } @@ -211,20 +215,20 @@ void SDRDaemonMain::setLoggingOptions() bool SDRDaemonMain::addSinkDevice() { - DSPDeviceSinkEngine *dspDeviceSinkEngine = m_dspEngine->addDeviceSinkEngine(); - dspDeviceSinkEngine->start(); - - uint dspDeviceSinkEngineUID = dspDeviceSinkEngine->getUID(); - char uidCStr[16]; - sprintf(uidCStr, "UID:%d", dspDeviceSinkEngineUID); - - m_deviceSinkEngine = dspDeviceSinkEngine; - - m_deviceSinkAPI = new DeviceSinkAPI(0, dspDeviceSinkEngine); int deviceIndex = getDeviceIndex(); if (deviceIndex >= 0) { + DSPDeviceSinkEngine *dspDeviceSinkEngine = m_dspEngine->addDeviceSinkEngine(); + dspDeviceSinkEngine->start(); + + uint dspDeviceSinkEngineUID = dspDeviceSinkEngine->getUID(); + char uidCStr[16]; + sprintf(uidCStr, "UID:%d", dspDeviceSinkEngineUID); + + m_deviceSinkEngine = dspDeviceSinkEngine; + m_deviceSinkAPI = new DeviceSinkAPI(0, dspDeviceSinkEngine); + PluginInterface::SamplingDevice samplingDevice = DeviceEnumerator::instance()->getTxSamplingDevice(deviceIndex); m_deviceSinkAPI->setSampleSinkSequence(samplingDevice.sequence); m_deviceSinkAPI->setNbItems(samplingDevice.deviceNbItems); @@ -246,20 +250,20 @@ bool SDRDaemonMain::addSinkDevice() bool SDRDaemonMain::addSourceDevice() { - DSPDeviceSourceEngine *dspDeviceSourceEngine = m_dspEngine->addDeviceSourceEngine(); - dspDeviceSourceEngine->start(); - - uint dspDeviceSourceEngineUID = dspDeviceSourceEngine->getUID(); - char uidCStr[16]; - sprintf(uidCStr, "UID:%d", dspDeviceSourceEngineUID); - - m_deviceSourceEngine = dspDeviceSourceEngine; - - m_deviceSourceAPI = new DeviceSourceAPI(0, dspDeviceSourceEngine); int deviceIndex = getDeviceIndex(); if (deviceIndex >= 0) { + DSPDeviceSourceEngine *dspDeviceSourceEngine = m_dspEngine->addDeviceSourceEngine(); + dspDeviceSourceEngine->start(); + + uint dspDeviceSourceEngineUID = dspDeviceSourceEngine->getUID(); + char uidCStr[16]; + sprintf(uidCStr, "UID:%d", dspDeviceSourceEngineUID); + + m_deviceSourceEngine = dspDeviceSourceEngine; + m_deviceSourceAPI = new DeviceSourceAPI(0, dspDeviceSourceEngine); + PluginInterface::SamplingDevice samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(deviceIndex); m_deviceSourceAPI->setSampleSourceSequence(samplingDevice.sequence); m_deviceSourceAPI->setNbItems(samplingDevice.deviceNbItems); @@ -307,6 +311,11 @@ void SDRDaemonMain::removeDevice() m_deviceSinkEngine->stopGeneration(); // deletes old UI and output object + + if (m_channelSource) { + m_channelSource->destroy(); + } + m_deviceSinkAPI->resetSampleSinkId(); m_deviceSinkAPI->getPluginInterface()->deleteSampleSinkPluginInstanceOutput( m_deviceSinkAPI->getSampleSink()); diff --git a/sdrdaemon/sdrdaemonmain.h b/sdrdaemon/sdrdaemonmain.h index 8eb07211a..cc4f987de 100644 --- a/sdrdaemon/sdrdaemonmain.h +++ b/sdrdaemon/sdrdaemonmain.h @@ -48,6 +48,7 @@ class DeviceSourceAPI; class DSPDeviceSinkEngine; class DeviceSinkAPI; class SDRDaemonChannelSink; +class SDRDaemonChannelSource; class SDRDaemonMain : public QObject { Q_OBJECT @@ -65,6 +66,8 @@ public: bool addSinkDevice(); void removeDevice(); + bool doAbort() const { return m_abort; } + friend class WebAPIAdapterDaemon; signals: @@ -94,6 +97,9 @@ private: DSPDeviceSinkEngine *m_deviceSinkEngine; DeviceSinkAPI *m_deviceSinkAPI; SDRDaemonChannelSink *m_channelSink; + SDRDaemonChannelSource *m_channelSource; + + bool m_abort; void loadSettings(); void setLoggingOptions(); diff --git a/sdrdaemon/sdrdaemonparser.cpp b/sdrdaemon/sdrdaemonparser.cpp index a314c8772..d1c7fc07c 100644 --- a/sdrdaemon/sdrdaemonparser.cpp +++ b/sdrdaemon/sdrdaemonparser.cpp @@ -146,22 +146,31 @@ void SDRDaemonParser::parse(const QCoreApplication& app) // device type - QString deviceType = m_parser.value(m_deviceTypeOption); - - QRegExp deviceTypeRegex("^[A-Z][A-Za-z0-9]+$"); - QRegExpValidator deviceTypeValidator(deviceTypeRegex); - - if (deviceTypeValidator.validate(deviceType, pos) == QValidator::Acceptable) + if (m_parser.isSet(m_deviceTypeOption)) { - m_deviceType = deviceType; - qDebug() << "SDRDaemonParser::parse: device type: " << m_deviceType; + QString deviceType = m_parser.value(m_deviceTypeOption); + + QRegExp deviceTypeRegex("^[A-Z][A-Za-z0-9]+$"); + QRegExpValidator deviceTypeValidator(deviceTypeRegex); + + if (deviceTypeValidator.validate(deviceType, pos) == QValidator::Acceptable) + { + m_deviceType = deviceType; + qDebug() << "SDRDaemonParser::parse: device type: " << m_deviceType; + } + else + { + m_deviceType = m_tx ? "FileSink" : "TestSource"; + qWarning() << "SDRDaemonParser::parse: device type invalid. Defaulting to " << m_deviceType; + } } else { m_deviceType = m_tx ? "FileSink" : "TestSource"; - qWarning() << "SDRDaemonParser::parse: device type invalid. Defaulting to " << m_deviceType; + qInfo() << "SDRDaemonParser::parse: device type not specified. defaulting to " << m_deviceType; } + // serial m_hasSerial = m_parser.isSet(m_serialOption); From 6691a926f16cc59412e5ce36830336f64d6369d8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 20 Aug 2018 08:41:54 +0200 Subject: [PATCH 630/956] SDRdaemon: data block definitions --- sdrdaemon/channel/sdrdaemondatablock.h | 100 +++++++++++++++++++++++++ sdrdaemon/channel/sdrdaemondataqueue.h | 55 ++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 sdrdaemon/channel/sdrdaemondatablock.h create mode 100644 sdrdaemon/channel/sdrdaemondataqueue.h diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h new file mode 100644 index 000000000..6d3c93de1 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -0,0 +1,100 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) data block // +// // +// 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 SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ + +#include +#include +#include "dsp/dsptypes.h" + +#define UDPSINKFEC_UDPSIZE 512 +#define UDPSINKFEC_NBORIGINALBLOCKS 128 +#define UDPSINKFEC_NBTXBLOCKS 8 + +namespace SDRDaemon { + +#pragma pack(push, 1) +struct MetaDataFEC +{ + uint32_t m_centerFrequency; //!< 4 center frequency in kHz + uint32_t m_sampleRate; //!< 8 sample rate in Hz + uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample + uint8_t m_sampleBits; //!< 10 number of effective bits per sample + uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data + uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC + uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing + uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing + uint32_t m_crc32; //!< 24 CRC32 of the above + + bool operator==(const MetaDataFEC& rhs) + { + return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant + } + + void init() + { + memset((void *) this, 0, sizeof(MetaDataFEC)); + m_nbFECBlocks = -1; + } +}; + +struct Header +{ + uint16_t m_frameIndex; + uint8_t m_blockIndex; + uint8_t m_filler; +}; + +static const int samplesPerBlock = (UDPSINKFEC_UDPSIZE - sizeof(Header)) / sizeof(Sample); + +struct ProtectedBlock +{ + Sample m_samples[samplesPerBlock]; +}; + +struct SuperBlock +{ + Header m_header; + ProtectedBlock m_protectedBlock; +}; +#pragma pack(pop) + +struct TxControlBlock +{ + bool m_processed; + uint16_t m_frameIndex; + int m_nbBlocksFEC; + int m_txDelay; +}; + +class DataBlock +{ + SuperBlock m_superBlock; + TxControlBlock m_controlBlock; +}; + +} // namespace SDRDaemon + + + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemondataqueue.h b/sdrdaemon/channel/sdrdaemondataqueue.h new file mode 100644 index 000000000..d83346ad1 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemondataqueue.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) data blocks queue // +// // +// 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 SDRDAEMON_CHANNEL_SDRDAEMONDATAQUEUE_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONDATAQUEUE_H_ + +#include + +class DataBlock; + +namespace SDRDaemon { + +class DataQueue : public QObject { + Q_OBJECT + +public: + DataQueue(QObject* parent = NULL); + ~DataQueue(); + + void push(DataBlock* message, bool emitSignal = true); //!< Push daa block onto queue + DataBlock* pop(); //!< Pop message from queue + + int size(); //!< Returns queue size + void clear(); //!< Empty queue + +signals: + void dataBlockEnqueued(); + +private: + QMutex m_lock; + QQueue m_queue; +}; + +} // namespace SDRDaemon + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATAQUEUE_H_ */ From 57f71e96cfca61ba90e39d744106165c994bb991 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 20 Aug 2018 14:05:27 +0200 Subject: [PATCH 631/956] SDRdaemon: added data queue body and made some fixes --- sdrdaemon/CMakeLists.txt | 3 + sdrdaemon/channel/sdrdaemondatablock.h | 43 ++++++------ sdrdaemon/channel/sdrdaemondataqueue.cpp | 86 ++++++++++++++++++++++++ sdrdaemon/channel/sdrdaemondataqueue.h | 20 +++--- 4 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 sdrdaemon/channel/sdrdaemondataqueue.cpp diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index 52422863e..b70416576 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -7,6 +7,7 @@ set(sdrdaemon_SOURCES sdrdaemonparser.cpp channel/sdrdaemonchannelsink.cpp channel/sdrdaemonchannelsource.cpp + channel/sdrdaemondataqueue.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -19,6 +20,8 @@ set(sdrdaemon_HEADERS sdrdaemonparser.h channel/sdrdaemonchannelsink.h channel/sdrdaemonchannelsource.h + channel/sdrdaemondataqueue.h + channel/sdrdaemondatablock.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 6d3c93de1..9a33e022a 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -29,57 +29,57 @@ #define UDPSINKFEC_UDPSIZE 512 #define UDPSINKFEC_NBORIGINALBLOCKS 128 -#define UDPSINKFEC_NBTXBLOCKS 8 - -namespace SDRDaemon { +//#define UDPSINKFEC_NBTXBLOCKS 8 #pragma pack(push, 1) -struct MetaDataFEC +struct SDRDaemonMetaDataFEC { uint32_t m_centerFrequency; //!< 4 center frequency in kHz uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample - uint8_t m_sampleBits; //!< 10 number of effective bits per sample + uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample (2 or 3) + uint8_t m_sampleBits; //!< 10 number of effective bits per sample (8 t0 24) uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing uint32_t m_crc32; //!< 24 CRC32 of the above - bool operator==(const MetaDataFEC& rhs) + bool operator==(const SDRDaemonMetaDataFEC& rhs) { return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant } void init() { - memset((void *) this, 0, sizeof(MetaDataFEC)); + memset((void *) this, 0, sizeof(SDRDaemonMetaDataFEC)); m_nbFECBlocks = -1; } }; -struct Header +struct SDRDaemonHeader { uint16_t m_frameIndex; uint8_t m_blockIndex; uint8_t m_filler; }; -static const int samplesPerBlock = (UDPSINKFEC_UDPSIZE - sizeof(Header)) / sizeof(Sample); +static const int SDRDaemonUdpSize = UDPSINKFEC_UDPSIZE; +static const int SDRDaemonNbOrginalBlocks = UDPSINKFEC_NBORIGINALBLOCKS; +static const int SDRDaemonSamplesPerBlock = (UDPSINKFEC_UDPSIZE - sizeof(SDRDaemonHeader)) / (SDR_RX_SAMP_SZ/4); -struct ProtectedBlock +struct SDRDaemonProtectedBlock { - Sample m_samples[samplesPerBlock]; + Sample m_samples[SDRDaemonSamplesPerBlock]; }; -struct SuperBlock +struct SDRDaemonSuperBlock { - Header m_header; - ProtectedBlock m_protectedBlock; + SDRDaemonHeader m_header; + SDRDaemonProtectedBlock m_protectedBlock; }; #pragma pack(pop) -struct TxControlBlock +struct SDRDaemonTxControlBlock { bool m_processed; uint16_t m_frameIndex; @@ -87,14 +87,11 @@ struct TxControlBlock int m_txDelay; }; -class DataBlock +class SDRDaemonDataBlock { - SuperBlock m_superBlock; - TxControlBlock m_controlBlock; +public: + SDRDaemonTxControlBlock m_controlBlock; + SDRDaemonSuperBlock m_superBlock; }; -} // namespace SDRDaemon - - - #endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemondataqueue.cpp b/sdrdaemon/channel/sdrdaemondataqueue.cpp new file mode 100644 index 000000000..b2be6ea41 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemondataqueue.cpp @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) data blocks queue // +// // +// 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 "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" + +SDRDaemonDataQueue::SDRDaemonDataQueue(QObject* parent) : + QObject(parent), + m_lock(QMutex::Recursive), + m_queue() +{ +} + +SDRDaemonDataQueue::~SDRDaemonDataQueue() +{ + SDRDaemonDataBlock* data; + + while ((data = pop()) != 0) + { + qDebug() << "SDRDaemonDataQueue::~SDRDaemonDataQueue: data block was still in queue"; + delete data; + } +} + +void SDRDaemonDataQueue::push(SDRDaemonDataBlock* data, bool emitSignal) +{ + if (data) + { + m_lock.lock(); + m_queue.append(data); + m_lock.unlock(); + } + + if (emitSignal) + { + emit dataBlockEnqueued(); + } +} + +SDRDaemonDataBlock* SDRDaemonDataQueue::pop() +{ + QMutexLocker locker(&m_lock); + + if (m_queue.isEmpty()) + { + return 0; + } + else + { + return m_queue.takeFirst(); + } +} + +int SDRDaemonDataQueue::size() +{ + QMutexLocker locker(&m_lock); + + return m_queue.size(); +} + +void SDRDaemonDataQueue::clear() +{ + QMutexLocker locker(&m_lock); + m_queue.clear(); +} diff --git a/sdrdaemon/channel/sdrdaemondataqueue.h b/sdrdaemon/channel/sdrdaemondataqueue.h index d83346ad1..25817a21d 100644 --- a/sdrdaemon/channel/sdrdaemondataqueue.h +++ b/sdrdaemon/channel/sdrdaemondataqueue.h @@ -24,20 +24,20 @@ #define SDRDAEMON_CHANNEL_SDRDAEMONDATAQUEUE_H_ #include +#include +#include -class DataBlock; +class SDRDaemonDataBlock; -namespace SDRDaemon { - -class DataQueue : public QObject { +class SDRDaemonDataQueue : public QObject { Q_OBJECT public: - DataQueue(QObject* parent = NULL); - ~DataQueue(); + SDRDaemonDataQueue(QObject* parent = NULL); + ~SDRDaemonDataQueue(); - void push(DataBlock* message, bool emitSignal = true); //!< Push daa block onto queue - DataBlock* pop(); //!< Pop message from queue + void push(SDRDaemonDataBlock* dataBlock, bool emitSignal = true); //!< Push daa block onto queue + SDRDaemonDataBlock* pop(); //!< Pop message from queue int size(); //!< Returns queue size void clear(); //!< Empty queue @@ -47,9 +47,7 @@ signals: private: QMutex m_lock; - QQueue m_queue; + QQueue m_queue; }; -} // namespace SDRDaemon - #endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATAQUEUE_H_ */ From 6c08494fd2727f3f97caef5e3e89889b43f1e3b3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 20 Aug 2018 17:36:42 +0200 Subject: [PATCH 632/956] SDRdaemon: channel sink thread canvas --- sdrdaemon/CMakeLists.txt | 2 + .../channel/sdrdaemonchannelsinkthread.cpp | 89 +++++++++++++++++++ .../channel/sdrdaemonchannelsinkthread.h | 52 +++++++++++ sdrdaemon/channel/sdrdaemondatablock.h | 21 +++-- 4 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp create mode 100644 sdrdaemon/channel/sdrdaemonchannelsinkthread.h diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index b70416576..6e6966114 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -8,6 +8,7 @@ set(sdrdaemon_SOURCES channel/sdrdaemonchannelsink.cpp channel/sdrdaemonchannelsource.cpp channel/sdrdaemondataqueue.cpp + channel/sdrdaemonchannelsinkthread.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -22,6 +23,7 @@ set(sdrdaemon_HEADERS channel/sdrdaemonchannelsource.h channel/sdrdaemondataqueue.h channel/sdrdaemondatablock.h + channel/sdrdaemonchannelsinkthread.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp new file mode 100644 index 000000000..f54628698 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemonchannelsinkthread.h" + +SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : + QThread(parent), + m_running(false), + m_dataQueue(dataQueue) +{ + connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData())); +} + +SDRDaemonChannelSinkThread::~SDRDaemonChannelSinkThread() +{ + stopWork(); +} + +void SDRDaemonChannelSinkThread::startWork() +{ + qDebug("SDRDaemonChannelSinkThread::startWork"); + m_startWaitMutex.lock(); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void SDRDaemonChannelSinkThread::stopWork() +{ + qDebug("SDRDaemonChannelSinkThread::stopWork"); + m_running = false; + wait(); +} + +void SDRDaemonChannelSinkThread::run() +{ + qDebug("SDRDaemonChannelSinkThread::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("SDRDaemonChannelSinkThread::run: end"); +} + +bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) +{ + return true; +} + +void SDRDaemonChannelSinkThread::handleData() +{ + SDRDaemonDataBlock* dataBlock; + + while (m_running && ((dataBlock = m_dataQueue->pop()) != 0)) + { + if (handleDataBlock(*dataBlock)) + { + delete dataBlock; + } + } +} \ No newline at end of file diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h new file mode 100644 index 000000000..67c7be3dd --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 + +class SDRDaemonDataQueue; +class SDRDaemonDataBlock; + +class SDRDaemonChannelSinkThread : public QThread { + Q_OBJECT + +public: + SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); + ~SDRDaemonChannelSinkThread(); + + void startWork(); + void stopWork(); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + SDRDaemonDataQueue *m_dataQueue; + + void run(); + bool handleDataBlock(SDRDaemonDataBlock& dataBlock); + +private slots: + void handleData(); +}; \ No newline at end of file diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 9a33e022a..3355cc0db 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -36,17 +36,22 @@ struct SDRDaemonMetaDataFEC { uint32_t m_centerFrequency; //!< 4 center frequency in kHz uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample (2 or 3) - uint8_t m_sampleBits; //!< 10 number of effective bits per sample (8 t0 24) - uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data - uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC - uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing - uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing - uint32_t m_crc32; //!< 24 CRC32 of the above + uint8_t m_bigEndian; //!< 9 1 if encoded as big endian, 0 for little endian + uint8_t m_reserved1; //!< 10 reserved + uint8_t m_reserved2; //!< 11 reserved + uint8_t m_reserved3; //!< 12 reserved + uint8_t m_sampleBytes; //!< 13 number of bytes per sample (2 or 3) + uint8_t m_sampleBits; //!< 14 number of effective bits per sample (8 t0 24) + uint8_t m_nbOriginalBlocks; //!< 15 number of blocks with original (protected) data + uint8_t m_nbFECBlocks; //!< 16 number of blocks carrying FEC + uint32_t m_tv_sec; //!< 20 seconds of timestamp at start time of super-frame processing + uint32_t m_tv_usec; //!< 24 microseconds of timestamp at start time of super-frame processing + uint32_t m_crc32; //!< 28 CRC32 of the above + // 32 bytes bool operator==(const SDRDaemonMetaDataFEC& rhs) { - return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant + return (memcmp((const void *) this, (const void *) &rhs, 16) == 0); // Only the 16 first bytes are relevant } void init() From 75b505d0125ff27731a7fbe3e05da88024d52a7d Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 20 Aug 2018 18:32:46 +0200 Subject: [PATCH 633/956] SDRDaemon: channel sink thread: data block handling with FEC encoding --- sdrdaemon/CMakeLists.txt | 4 +- .../channel/sdrdaemonchannelsinkthread.cpp | 66 ++++++++++++++++++- .../channel/sdrdaemonchannelsinkthread.h | 5 +- sdrdaemon/channel/sdrdaemondatablock.h | 2 +- 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index 6e6966114..cf8407a3e 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -48,12 +48,14 @@ include_directories( ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_SOURCE_DIR}/logging ${CMAKE_SOURCE_DIR}/httpserver - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CM256CC_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) target_link_libraries(sdrdaemon ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} sdrbase logging ) diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp index f54628698..f8876a96d 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp @@ -25,10 +25,13 @@ #include "channel/sdrdaemondatablock.h" #include "channel/sdrdaemonchannelsinkthread.h" -SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : +#include "cm256.h" + +SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent) : QThread(parent), m_running(false), - m_dataQueue(dataQueue) + m_dataQueue(dataQueue), + m_cm256(cm256) { connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData())); } @@ -72,6 +75,65 @@ void SDRDaemonChannelSinkThread::run() bool SDRDaemonChannelSinkThread::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_controlBlock.m_frameIndex; + int nbBlocksFEC = dataBlock.m_controlBlock.m_nbBlocksFEC; + int txDelay = dataBlock.m_controlBlock.m_txDelay; + SDRDaemonSuperBlock *txBlockx = dataBlock.m_superBlocks; + + if ((nbBlocksFEC == 0) || !m_cm256) // Do not FEC encode + { + for (int i = 0; i < SDRDaemonNbOrginalBlocks; i++) + { + // TODO: send block via UDP here + 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 + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) + { + // TODO: send block via UDP here + usleep(txDelay); + } + } + + dataBlock.m_controlBlock.m_processed = true; return true; } diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h index 67c7be3dd..afa11524d 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h @@ -26,12 +26,13 @@ class SDRDaemonDataQueue; class SDRDaemonDataBlock; +class CM256; class SDRDaemonChannelSinkThread : public QThread { Q_OBJECT public: - SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); + SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent = 0); ~SDRDaemonChannelSinkThread(); void startWork(); @@ -44,6 +45,8 @@ private: SDRDaemonDataQueue *m_dataQueue; + CM256 *m_cm256; //!< CM256 library object + void run(); bool handleDataBlock(SDRDaemonDataBlock& dataBlock); diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 3355cc0db..2d76dbb54 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -96,7 +96,7 @@ class SDRDaemonDataBlock { public: SDRDaemonTxControlBlock m_controlBlock; - SDRDaemonSuperBlock m_superBlock; + SDRDaemonSuperBlock m_superBlocks[256]; }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ */ From 4697b13e606a6938d2550589010c84867c277f31 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 00:15:40 +0200 Subject: [PATCH 634/956] SDRdaemon: write UDP socket --- .../channel/sdrdaemonchannelsinkthread.cpp | 23 ++++++++++++------- .../channel/sdrdaemonchannelsinkthread.h | 12 ++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp index f8876a96d..ad2ad6407 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp @@ -20,6 +20,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include "channel/sdrdaemondataqueue.h" #include "channel/sdrdaemondatablock.h" @@ -31,14 +32,18 @@ SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQ QThread(parent), m_running(false), m_dataQueue(dataQueue), - m_cm256(cm256) + m_cm256(cm256), + m_address(QHostAddress::LocalHost), + m_port(9090) { + m_socket = new QUdpSocket(this); connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData())); } SDRDaemonChannelSinkThread::~SDRDaemonChannelSinkThread() { stopWork(); + delete m_socket; } void SDRDaemonChannelSinkThread::startWork() @@ -69,7 +74,7 @@ void SDRDaemonChannelSinkThread::run() sleep(1); // Do nothing as everything is in the data handler (dequeuer) } - m_running = false; + m_running = false; qDebug("SDRDaemonChannelSinkThread::run: end"); } @@ -88,7 +93,8 @@ bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) { for (int i = 0; i < SDRDaemonNbOrginalBlocks; i++) { - // TODO: send block via UDP here + // send block via UDP + m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, m_port); usleep(txDelay); } } @@ -109,7 +115,7 @@ bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) 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)) @@ -117,7 +123,7 @@ bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) 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++) @@ -128,7 +134,8 @@ bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) // Transmit all blocks for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) { - // TODO: send block via UDP here + // send block via UDP + m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, m_port); usleep(txDelay); } } @@ -147,5 +154,5 @@ void SDRDaemonChannelSinkThread::handleData() { delete dataBlock; } - } -} \ No newline at end of file + } +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h index afa11524d..a64115363 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h @@ -23,10 +23,12 @@ #include #include #include +#include class SDRDaemonDataQueue; class SDRDaemonDataBlock; class CM256; +class QUdpSocket; class SDRDaemonChannelSinkThread : public QThread { Q_OBJECT @@ -38,18 +40,24 @@ public: void startWork(); void stopWork(); + void setAddress(QString& address) { m_address.setAddress(address); } + void setPort(unsigned int port) { m_port = port; } + private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; bool m_running; SDRDaemonDataQueue *m_dataQueue; - CM256 *m_cm256; //!< CM256 library object + QHostAddress m_address; + unsigned int m_port; + QUdpSocket *m_socket; + void run(); bool handleDataBlock(SDRDaemonDataBlock& dataBlock); private slots: void handleData(); -}; \ No newline at end of file +}; From dff02e944d2c815d2b765acec0739488d649449b Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 01:36:39 +0200 Subject: [PATCH 635/956] SDRdaemon: UDP channel sink (1) --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 11 ++++++++++- sdrdaemon/channel/sdrdaemonchannelsink.h | 14 +++++++++++++- sdrdaemon/channel/sdrdaemondatablock.h | 19 +++++++------------ 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 68f22efaf..1cf840aec 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -24,6 +24,7 @@ #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" #include "device/devicesourceapi.h" +#include "channel/sdrdaemonchannelsinkthread.h" #include "sdrdaemonchannelsink.h" const QString SDRDaemonChannelSink::m_channelIdURI = "sdrangel.channel.sdrdaemonsink"; @@ -32,7 +33,11 @@ const QString SDRDaemonChannelSink::m_channelId = "SDRDaemonChannelSink"; SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_deviceAPI(deviceAPI), - m_running(false) + m_running(false), + m_sinkThread(0), + m_txBlockIndex(0), + m_frameCount(0), + m_sampleIndex(0) { setObjectName(m_channelId); @@ -40,6 +45,8 @@ SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); + + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; } SDRDaemonChannelSink::~SDRDaemonChannelSink() @@ -58,6 +65,8 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const void SDRDaemonChannelSink::start() { qDebug("SDRDaemonChannelSink::start"); + if (m_running) { stop(); } + m_sinkThread = new SDRDaemonChannelSinkThread(&m_dataQueue, m_cm256p); m_running = true; } diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index f92a90da7..25abbf181 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -23,12 +23,16 @@ #ifndef SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ #define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ +#include "cm256.h" + #include "dsp/basebandsamplesink.h" #include "channel/channelsinkapi.h" +#include "channel/sdrdaemondataqueue.h" class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; +class SDRDaemonChannelSinkThread; class SDRDaemonChannelSink : public BasebandSampleSink, public ChannelSinkAPI { Q_OBJECT @@ -56,8 +60,16 @@ private: DeviceSourceAPI *m_deviceAPI; ThreadedBasebandSampleSink* m_threadedChannelizer; DownChannelizer* m_channelizer; - bool m_running; + + SDRDaemonDataQueue m_dataQueue; + SDRDaemonChannelSinkThread *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 }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 2d76dbb54..35f9f06f7 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -36,18 +36,13 @@ struct SDRDaemonMetaDataFEC { uint32_t m_centerFrequency; //!< 4 center frequency in kHz uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_bigEndian; //!< 9 1 if encoded as big endian, 0 for little endian - uint8_t m_reserved1; //!< 10 reserved - uint8_t m_reserved2; //!< 11 reserved - uint8_t m_reserved3; //!< 12 reserved - uint8_t m_sampleBytes; //!< 13 number of bytes per sample (2 or 3) - uint8_t m_sampleBits; //!< 14 number of effective bits per sample (8 t0 24) - uint8_t m_nbOriginalBlocks; //!< 15 number of blocks with original (protected) data - uint8_t m_nbFECBlocks; //!< 16 number of blocks carrying FEC - uint32_t m_tv_sec; //!< 20 seconds of timestamp at start time of super-frame processing - uint32_t m_tv_usec; //!< 24 microseconds of timestamp at start time of super-frame processing - uint32_t m_crc32; //!< 28 CRC32 of the above - // 32 bytes + uint8_t m_sampleBytes; //!< 9 number of bytes per sample (2 or 3) + uint8_t m_sampleBits; //!< 10 number of effective bits per sample (8 t0 24) + uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data + uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC + uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing + uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing + uint32_t m_crc32; //!< 24 CRC32 of the above bool operator==(const SDRDaemonMetaDataFEC& rhs) { From d2c3985de0361b61959c493ff8d67d7f65b7cd0d Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 08:51:10 +0200 Subject: [PATCH 636/956] SDRdaemon: UDP channel sink (2) --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 58 +++++++++++++++++++++- sdrdaemon/channel/sdrdaemonchannelsink.h | 4 ++ sdrdaemon/channel/sdrdaemondatablock.h | 2 +- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 1cf840aec..b34f18c63 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -20,6 +20,11 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include + #include "util/simpleserializer.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" @@ -59,12 +64,63 @@ SDRDaemonChannelSink::~SDRDaemonChannelSink() void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { - qDebug("SDRDaemonChannelSink::feed: received %d samples", (int) (end - begin)); + 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 = 0; // TODO + metaData.m_sampleRate = 48000; // TODO + metaData.m_sampleBytes = SDR_RX_SAMP_SZ/8; + metaData.m_sampleBits = SDR_RX_SAMP_SZ; + metaData.m_nbOriginalBlocks = SDRDaemonNbOrginalBlocks; + metaData.m_nbFECBlocks = 8; // TODO + metaData.m_tv_sec = tv.tv_sec; + metaData.m_tv_usec = tv.tv_usec; + + boost::crc_32_type crc32; + crc32.process_bytes(&metaData, 20); + metaData.m_crc32 = crc32.checksum(); + SDRDaemonSuperBlock& superBlock = m_dataBlock.m_superBlocks[0]; // first block + + memset((void *) &superBlock, 0, SDRDaemonUdpSize); + + 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 + } + } } void SDRDaemonChannelSink::start() { qDebug("SDRDaemonChannelSink::start"); + memset((void *) &m_currentMetaFEC, 0, sizeof(SDRDaemonMetaDataFEC)); if (m_running) { stop(); } m_sinkThread = new SDRDaemonChannelSinkThread(&m_dataQueue, m_cm256p); m_running = true; diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index 25abbf181..e9e49e4f5 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -28,6 +28,7 @@ #include "dsp/basebandsamplesink.h" #include "channel/channelsinkapi.h" #include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" class DeviceSourceAPI; class ThreadedBasebandSampleSink; @@ -70,6 +71,9 @@ private: 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; }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 35f9f06f7..422148ccd 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -46,7 +46,7 @@ struct SDRDaemonMetaDataFEC bool operator==(const SDRDaemonMetaDataFEC& rhs) { - return (memcmp((const void *) this, (const void *) &rhs, 16) == 0); // Only the 16 first bytes are relevant + return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant } void init() From 9046b379ac8d3ad45e26115b6a3a0f2dc8331349 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 14:00:56 +0200 Subject: [PATCH 637/956] SDRdaemon: UDP channel sink base final --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 85 ++++++++++++++++++++-- sdrdaemon/channel/sdrdaemonchannelsink.h | 23 +++++- sdrdaemon/channel/sdrdaemondatablock.h | 9 +++ 3 files changed, 109 insertions(+), 8 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index b34f18c63..4f4e28432 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -42,7 +42,14 @@ SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : m_sinkThread(0), m_txBlockIndex(0), m_frameCount(0), - m_sampleIndex(0) + m_sampleIndex(0), + m_dataBlock(0), + m_centerFrequency(0), + m_sampleRate(48000), + m_sampleBytes(SDR_RX_SAMP_SZ/8), + m_sampleBits(8), + m_nbBlocksFEC(0), + m_txDelay(100) { setObjectName(m_channelId); @@ -56,12 +63,29 @@ SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : SDRDaemonChannelSink::~SDRDaemonChannelSink() { + m_dataBlockMutex.lock(); + if (m_dataBlock && !m_dataBlock->m_controlBlock.m_complete) { + delete m_dataBlock; + } + m_dataBlockMutex.unlock(); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; } +void SDRDaemonChannelSink::setTxDelay(int txDelay) +{ + qDebug() << "SDRDaemonChannelSink::setTxDelay: txDelay: " << txDelay; + m_txDelay = txDelay; +} + +void SDRDaemonChannelSink::setNbBlocksFEC(int nbBlocksFEC) +{ + qDebug() << "SDRDaemonChannelSink::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC; + m_nbBlocksFEC = nbBlocksFEC; +} + void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { SampleVector::const_iterator it = begin; @@ -77,19 +101,19 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const SDRDaemonMetaDataFEC metaData; gettimeofday(&tv, 0); - metaData.m_centerFrequency = 0; // TODO - metaData.m_sampleRate = 48000; // TODO - metaData.m_sampleBytes = SDR_RX_SAMP_SZ/8; - metaData.m_sampleBits = SDR_RX_SAMP_SZ; + metaData.m_centerFrequency = m_centerFrequency; + metaData.m_sampleRate = m_sampleRate; + metaData.m_sampleBytes = m_sampleBytes; + metaData.m_sampleBits = m_sampleBits; metaData.m_nbOriginalBlocks = SDRDaemonNbOrginalBlocks; - metaData.m_nbFECBlocks = 8; // TODO + metaData.m_nbFECBlocks = m_nbBlocksFEC; metaData.m_tv_sec = tv.tv_sec; metaData.m_tv_usec = tv.tv_usec; boost::crc_32_type crc32; crc32.process_bytes(&metaData, 20); metaData.m_crc32 = crc32.checksum(); - SDRDaemonSuperBlock& superBlock = m_dataBlock.m_superBlocks[0]; // first block + SDRDaemonSuperBlock& superBlock = m_dataBlock->m_superBlocks[0]; // first block memset((void *) &superBlock, 0, SDRDaemonUdpSize); @@ -112,8 +136,55 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const m_currentMetaFEC = metaData; } + if (!m_dataBlock) { + m_dataBlock = new SDRDaemonDataBlock(); + } + 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_controlBlock.m_frameIndex = m_frameCount; + m_dataBlock->m_controlBlock.m_processed = false; + m_dataBlock->m_controlBlock.m_complete = true; + m_dataBlock->m_controlBlock.m_nbBlocksFEC = m_nbBlocksFEC; + m_dataBlock->m_controlBlock.m_txDelay = m_txDelay; + + 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++; + } + } } } diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index e9e49e4f5..1f659b60c 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -23,6 +23,8 @@ #ifndef SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ #define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ +#include + #include "cm256.h" #include "dsp/basebandsamplesink.h" @@ -54,6 +56,17 @@ public: virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + /** 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 setSampleBytes(uint8_t sampleBytes) { m_sampleBytes = sampleBytes; } + void setSampleBits(uint8_t sampleBits) { m_sampleBits = sampleBits; } + void setNbBlocksFEC(int nbBlocksFEC); + void setTxDelay(int txDelay); + static const QString m_channelIdURI; static const QString m_channelId; @@ -73,7 +86,15 @@ private: int m_sampleIndex; //!< Current sample index in protected block data SDRDaemonSuperBlock m_superBlock; SDRDaemonMetaDataFEC m_currentMetaFEC; - SDRDaemonDataBlock m_dataBlock; + SDRDaemonDataBlock *m_dataBlock; + QMutex m_dataBlockMutex; + + uint64_t m_centerFrequency; + uint32_t m_sampleRate; + uint8_t m_sampleBytes; + uint8_t m_sampleBits; + int m_nbBlocksFEC; + int m_txDelay; }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 422148ccd..4733462e6 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -81,10 +81,19 @@ struct SDRDaemonSuperBlock struct SDRDaemonTxControlBlock { + bool m_complete; bool m_processed; uint16_t m_frameIndex; int m_nbBlocksFEC; int m_txDelay; + + SDRDaemonTxControlBlock() { + m_complete = false; + m_processed = false; + m_frameIndex = 0; + m_nbBlocksFEC = 0; + m_txDelay = 100; + } }; class SDRDaemonDataBlock From 30211815641d2ad8a9ff2674af3d5df85e252533 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 15:18:23 +0200 Subject: [PATCH 638/956] SDRdaemon: pass FEC blocks and tx delay from the command line --- sdrdaemon/sdrdaemonmain.cpp | 2 ++ sdrdaemon/sdrdaemonparser.cpp | 41 +++++++++++++++++++++++++++++++++-- sdrdaemon/sdrdaemonparser.h | 6 +++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/sdrdaemon/sdrdaemonmain.cpp b/sdrdaemon/sdrdaemonmain.cpp index ab51d6ecc..486ba0efb 100644 --- a/sdrdaemon/sdrdaemonmain.cpp +++ b/sdrdaemon/sdrdaemonmain.cpp @@ -130,6 +130,8 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa info.noquote(); info << msg; m_channelSink = new SDRDaemonChannelSink(m_deviceSourceAPI); + m_channelSink->setNbBlocksFEC(parser.getNbBlocksFEC()); + m_channelSink->setTxDelay(parser.getTxDelay()); } else { diff --git a/sdrdaemon/sdrdaemonparser.cpp b/sdrdaemon/sdrdaemonparser.cpp index d1c7fc07c..b5faa6482 100644 --- a/sdrdaemon/sdrdaemonparser.cpp +++ b/sdrdaemon/sdrdaemonparser.cpp @@ -54,8 +54,15 @@ SDRDaemonParser::SDRDaemonParser() : "serial"), m_sequenceOption(QStringList() << "i" << "sequence", "Device sequence index in enumeration for the same device type.", - "sequence") - + "sequence"), + m_txDelayOption(QStringList() << "d" << "tx-delay", + "delay between transmission of UDP blocks (ms).", + "txDelay", + "100"), + m_nbBlocksFECOption(QStringList() << "f" << "fec-blocks", + "Number of FEC blocks per frame.", + "nbBlocksFEC", + "8") { m_serverAddress = "127.0.0.1"; m_serverPort = 9091; @@ -64,6 +71,8 @@ SDRDaemonParser::SDRDaemonParser() : m_deviceType = "TestSource"; m_tx = false; m_sequence = 0; + m_txDelay = 100; + m_nbBlocksFEC = 8; m_hasSequence = false; m_hasSerial = false; @@ -79,6 +88,8 @@ SDRDaemonParser::SDRDaemonParser() : m_parser.addOption(m_txOption); m_parser.addOption(m_serialOption); m_parser.addOption(m_sequenceOption); + m_parser.addOption(m_txDelayOption); + m_parser.addOption(m_nbBlocksFECOption); } SDRDaemonParser::~SDRDaemonParser() @@ -195,6 +206,32 @@ void SDRDaemonParser::parse(const QCoreApplication& app) qWarning() << "SDRDaemonParser::parse: sequence invalid. Defaulting to " << m_sequence; } } + + // Tx delay + if (m_parser.isSet(m_txDelayOption)) + { + QString txDelayStr = m_parser.value(m_txDelayOption); + int txDelay = txDelayStr.toInt(&ok); + + if (ok && (txDelay > 0)) { + m_txDelay = txDelay; + } else { + qWarning() << "SDRDaemonParser::parse: Tx delay invalid. Defaulting to " << m_txDelay; + } + } + + // nb FEC blocks + if (m_parser.isSet(m_nbBlocksFECOption)) + { + QString nbBlocksFECStr = m_parser.value(m_nbBlocksFECOption); + int nbBlocksFEC = nbBlocksFECStr.toInt(&ok); + + if (ok && (nbBlocksFEC >= 0) && (nbBlocksFEC < 128)) { + m_nbBlocksFEC = nbBlocksFEC; + } else { + qWarning() << "SDRDaemonParser::parse: Tx number of FEC blocks invalid. Defaulting to " << m_nbBlocksFEC; + } + } } diff --git a/sdrdaemon/sdrdaemonparser.h b/sdrdaemon/sdrdaemonparser.h index fb794ee80..30b23729d 100644 --- a/sdrdaemon/sdrdaemonparser.h +++ b/sdrdaemon/sdrdaemonparser.h @@ -42,6 +42,8 @@ public: bool getTx() const { return m_tx; } const QString& getSerial() const { return m_serial; } uint16_t getSequence() const { return m_sequence; } + int getTxDelay() const { return m_txDelay; } + int getNbBlocksFEC() const { return m_nbBlocksFEC; } bool hasSequence() const { return m_hasSequence; } bool hasSerial() const { return m_hasSerial; } @@ -55,6 +57,8 @@ private: bool m_tx; //!< True for Tx QString m_serial; //!< Serial number of the device uint16_t m_sequence; //!< Sequence of the device for the same type of device in enumeration process + int m_txDelay; //!< Initial delay between transmission of UDP blocks in milliseconds + int m_nbBlocksFEC; //!< Number of FEC blocks per frame; bool m_hasSerial; //!< True if serial was specified bool m_hasSequence; //!< True if sequence was specified @@ -67,6 +71,8 @@ private: QCommandLineOption m_txOption; QCommandLineOption m_serialOption; QCommandLineOption m_sequenceOption; + QCommandLineOption m_txDelayOption; + QCommandLineOption m_nbBlocksFECOption; }; From 22b5f7fdded6b27664120b1d2674a848be5076e0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 15:53:38 +0200 Subject: [PATCH 639/956] SDRdaemon: update device center frequency and channel sample rate in channel sink --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 4f4e28432..6bd9b2d87 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -28,6 +28,7 @@ #include "util/simpleserializer.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" +#include "dsp/dspcommands.h" #include "device/devicesourceapi.h" #include "channel/sdrdaemonchannelsinkthread.h" #include "sdrdaemonchannelsink.h" @@ -205,7 +206,36 @@ void SDRDaemonChannelSink::stop() bool SDRDaemonChannelSink::handleMessage(const Message& cmd __attribute__((unused))) { - return false; + if (DownChannelizer::MsgChannelizerNotification::match(cmd)) + { + DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; + + qDebug() << "SDRDaemonChannelSink::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() << "SDRDaemonChannelSink::handleMessage: DSPSignalNotification:" + << " inputSampleRate: " << notif.getSampleRate() + << " centerFrequency: " << notif.getCenterFrequency(); + + setCenterFrequency(notif.getCenterFrequency()); + + return true; + } + else + { + return false; + } } QByteArray SDRDaemonChannelSink::serialize() const From 157a77835e1a6439b8f3c320fbf16afe2b310122 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 16:04:10 +0200 Subject: [PATCH 640/956] SDRdaemon: Set effective number of sample bits as deprecated --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 3 +-- sdrdaemon/channel/sdrdaemonchannelsink.h | 2 -- sdrdaemon/channel/sdrdaemondatablock.h | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 6bd9b2d87..90c13db21 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -48,7 +48,6 @@ SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : m_centerFrequency(0), m_sampleRate(48000), m_sampleBytes(SDR_RX_SAMP_SZ/8), - m_sampleBits(8), m_nbBlocksFEC(0), m_txDelay(100) { @@ -105,7 +104,7 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const metaData.m_centerFrequency = m_centerFrequency; metaData.m_sampleRate = m_sampleRate; metaData.m_sampleBytes = m_sampleBytes; - metaData.m_sampleBits = m_sampleBits; + metaData.m_sampleBits = 0; // TODO: deprecated metaData.m_nbOriginalBlocks = SDRDaemonNbOrginalBlocks; metaData.m_nbFECBlocks = m_nbBlocksFEC; metaData.m_tv_sec = tv.tv_sec; diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index 1f659b60c..7e617cca1 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -63,7 +63,6 @@ public: void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } void setSampleBytes(uint8_t sampleBytes) { m_sampleBytes = sampleBytes; } - void setSampleBits(uint8_t sampleBits) { m_sampleBits = sampleBits; } void setNbBlocksFEC(int nbBlocksFEC); void setTxDelay(int txDelay); @@ -92,7 +91,6 @@ private: uint64_t m_centerFrequency; uint32_t m_sampleRate; uint8_t m_sampleBytes; - uint8_t m_sampleBits; int m_nbBlocksFEC; int m_txDelay; }; diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 4733462e6..da51e4896 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -37,7 +37,7 @@ struct SDRDaemonMetaDataFEC uint32_t m_centerFrequency; //!< 4 center frequency in kHz uint32_t m_sampleRate; //!< 8 sample rate in Hz uint8_t m_sampleBytes; //!< 9 number of bytes per sample (2 or 3) - uint8_t m_sampleBits; //!< 10 number of effective bits per sample (8 t0 24) + uint8_t m_sampleBits; //!< 10 number of effective bits per sample (deprecated) uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing From 84178789e439b274805aa7cebf3f1fadc4c799a7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 17:23:48 +0200 Subject: [PATCH 641/956] SDRdaemon: fixed core dump --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 12 ++++---- sdrdaemon/channel/sdrdaemondatablock.h | 35 ++++++++++++++++++++-- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 90c13db21..2b7c2141a 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -110,13 +110,15 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const 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 - - memset((void *) &superBlock, 0, SDRDaemonUdpSize); - + 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)); @@ -136,10 +138,6 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const m_currentMetaFEC = metaData; } - if (!m_dataBlock) { - m_dataBlock = new SDRDaemonDataBlock(); - } - m_txBlockIndex = 1; // next Tx block with data } // block zero diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index da51e4896..2bb9f28f4 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -25,6 +25,7 @@ #include #include +#include #include "dsp/dsptypes.h" #define UDPSINKFEC_UDPSIZE 512 @@ -51,8 +52,15 @@ struct SDRDaemonMetaDataFEC void init() { - memset((void *) this, 0, sizeof(SDRDaemonMetaDataFEC)); + m_centerFrequency = 0; + m_sampleRate = 0; + m_sampleBytes = 0; + m_sampleBits = 0; + m_nbOriginalBlocks = 0; m_nbFECBlocks = -1; + m_tv_sec = 0; + m_tv_usec = 0; + m_crc32 = 0; } }; @@ -61,6 +69,13 @@ struct SDRDaemonHeader uint16_t m_frameIndex; uint8_t m_blockIndex; uint8_t m_filler; + + void init() + { + m_frameIndex = 0; + m_blockIndex = 0; + m_filler = 0; + } }; static const int SDRDaemonUdpSize = UDPSINKFEC_UDPSIZE; @@ -70,12 +85,22 @@ static const int SDRDaemonSamplesPerBlock = (UDPSINKFEC_UDPSIZE - sizeof(SDRDaem struct SDRDaemonProtectedBlock { Sample m_samples[SDRDaemonSamplesPerBlock]; + + void init() { + std::fill(m_samples, m_samples+SDRDaemonSamplesPerBlock, Sample{0,0}); + } }; struct SDRDaemonSuperBlock { SDRDaemonHeader m_header; SDRDaemonProtectedBlock m_protectedBlock; + + void init() + { + m_header.init(); + m_protectedBlock.init(); + } }; #pragma pack(pop) @@ -99,8 +124,14 @@ struct SDRDaemonTxControlBlock class SDRDaemonDataBlock { public: + SDRDaemonDataBlock() { + m_superBlocks = new SDRDaemonSuperBlock[256]; + } + ~SDRDaemonDataBlock() { + delete[] m_superBlocks; + } SDRDaemonTxControlBlock m_controlBlock; - SDRDaemonSuperBlock m_superBlocks[256]; + SDRDaemonSuperBlock *m_superBlocks; }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ */ From 5bb3022c22d32479a7a81fb8982aeac2d7708b90 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 17:38:40 +0200 Subject: [PATCH 642/956] SDRdaemon: channel sink: activate sender thread --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 2b7c2141a..79a54e0de 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -189,15 +189,29 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const void SDRDaemonChannelSink::start() { qDebug("SDRDaemonChannelSink::start"); + memset((void *) &m_currentMetaFEC, 0, sizeof(SDRDaemonMetaDataFEC)); - if (m_running) { stop(); } + + if (m_running) { + stop(); + } + m_sinkThread = new SDRDaemonChannelSinkThread(&m_dataQueue, m_cm256p); + m_sinkThread->startWork(); m_running = true; } void SDRDaemonChannelSink::stop() { qDebug("SDRDaemonChannelSink::stop"); + + if (m_sinkThread != 0) + { + m_sinkThread->stopWork(); + delete m_sinkThread; + m_sinkThread = 0; + } + m_running = false; } From a8f53ec70b59aec440c52ac409246d371a026866 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 18:49:24 +0200 Subject: [PATCH 643/956] SDRdaemon: use a queued connection --- sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp index ad2ad6407..8241d68f6 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp @@ -37,7 +37,7 @@ SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQ m_port(9090) { m_socket = new QUdpSocket(this); - connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData())); + connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); } SDRDaemonChannelSinkThread::~SDRDaemonChannelSinkThread() From 716a77eeb29cbab064cd40c6bea7ebb142397ea5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 21 Aug 2018 23:47:44 +0200 Subject: [PATCH 644/956] SDRdaemon: channel sink thread: handle socket life cycle appropriately --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 28 +++++++------- .../channel/sdrdaemonchannelsinkthread.cpp | 37 +++++++++++++++++-- .../channel/sdrdaemonchannelsinkthread.h | 31 +++++++++++++++- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 79a54e0de..149817708 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -113,7 +113,7 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const 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(); @@ -182,7 +182,7 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const { m_txBlockIndex++; } - } + } } } @@ -191,27 +191,27 @@ void SDRDaemonChannelSink::start() qDebug("SDRDaemonChannelSink::start"); memset((void *) &m_currentMetaFEC, 0, sizeof(SDRDaemonMetaDataFEC)); - - if (m_running) { - stop(); + + if (m_running) { + stop(); } - + m_sinkThread = new SDRDaemonChannelSinkThread(&m_dataQueue, m_cm256p); - m_sinkThread->startWork(); + m_sinkThread->startStop(true); m_running = true; } void SDRDaemonChannelSink::stop() { qDebug("SDRDaemonChannelSink::stop"); - + if (m_sinkThread != 0) { - m_sinkThread->stopWork(); - delete m_sinkThread; + m_sinkThread->startStop(false); + m_sinkThread->deleteLater(); m_sinkThread = 0; } - + m_running = false; } @@ -224,7 +224,7 @@ bool SDRDaemonChannelSink::handleMessage(const Message& cmd __attribute__((unuse qDebug() << "SDRDaemonChannelSink::handleMessage: MsgChannelizerNotification:" << " channelSampleRate: " << notif.getSampleRate() << " offsetFrequency: " << notif.getFrequencyOffset(); - + if (notif.getSampleRate() > 0) { setSampleRate(notif.getSampleRate()); } @@ -238,7 +238,7 @@ bool SDRDaemonChannelSink::handleMessage(const Message& cmd __attribute__((unuse qDebug() << "SDRDaemonChannelSink::handleMessage: DSPSignalNotification:" << " inputSampleRate: " << notif.getSampleRate() << " centerFrequency: " << notif.getCenterFrequency(); - + setCenterFrequency(notif.getCenterFrequency()); return true; @@ -246,7 +246,7 @@ bool SDRDaemonChannelSink::handleMessage(const Message& cmd __attribute__((unuse else { return false; - } + } } QByteArray SDRDaemonChannelSink::serialize() const diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp index 8241d68f6..4333cd96b 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp @@ -28,6 +28,8 @@ #include "cm256.h" +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSinkThread::MsgStartStop, Message) + SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent) : QThread(parent), m_running(false), @@ -36,20 +38,26 @@ SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQ m_address(QHostAddress::LocalHost), m_port(9090) { - m_socket = new QUdpSocket(this); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); } SDRDaemonChannelSinkThread::~SDRDaemonChannelSinkThread() { - stopWork(); - delete m_socket; + qDebug("SDRDaemonChannelSinkThread::~SDRDaemonChannelSinkThread"); +} + +void SDRDaemonChannelSinkThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); } void SDRDaemonChannelSinkThread::startWork() { qDebug("SDRDaemonChannelSinkThread::startWork"); m_startWaitMutex.lock(); + m_socket = new QUdpSocket(this); start(); while(!m_running) m_startWaiter.wait(&m_startWaitMutex, 100); @@ -59,6 +67,7 @@ void SDRDaemonChannelSinkThread::startWork() void SDRDaemonChannelSinkThread::stopWork() { qDebug("SDRDaemonChannelSinkThread::stopWork"); + delete m_socket; m_running = false; wait(); } @@ -156,3 +165,25 @@ void SDRDaemonChannelSinkThread::handleData() } } } + +void SDRDaemonChannelSinkThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("SDRDaemonChannelSinkThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + } +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h index a64115363..375dda954 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h @@ -25,6 +25,9 @@ #include #include +#include "util/message.h" +#include "util/messagequeue.h" + class SDRDaemonDataQueue; class SDRDaemonDataBlock; class CM256; @@ -34,11 +37,29 @@ class SDRDaemonChannelSinkThread : 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) + { } + }; + SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent = 0); ~SDRDaemonChannelSinkThread(); - void startWork(); - void stopWork(); + void startStop(bool start); void setAddress(QString& address) { m_address.setAddress(address); } void setPort(unsigned int port) { m_port = port; } @@ -55,9 +76,15 @@ private: unsigned int m_port; QUdpSocket *m_socket; + MessageQueue m_inputMessageQueue; + + void startWork(); + void stopWork(); + void run(); bool handleDataBlock(SDRDaemonDataBlock& dataBlock); private slots: void handleData(); + void handleInputMessages(); }; From c0b5c86d8857c7e775745e32748581357309f6ec Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 22 Aug 2018 00:40:01 +0200 Subject: [PATCH 645/956] SDRdaemon: first working version --- plugins/samplesource/testsource/testsourceinput.cpp | 4 ++-- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 4 ++-- sdrdaemon/channel/sdrdaemonchannelsink.h | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index eda87732f..c73a7878d 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -175,7 +175,7 @@ bool TestSourceInput::handleMessage(const Message& message) else if (MsgFileRecord::match(message)) { MsgFileRecord& conf = (MsgFileRecord&) message; - qDebug() << "RTLSDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); + qDebug() << "TestSourceInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); if (conf.getStartStop()) { @@ -197,7 +197,7 @@ bool TestSourceInput::handleMessage(const Message& message) else if (MsgStartStop::match(message)) { MsgStartStop& cmd = (MsgStartStop&) message; - qDebug() << "RTLSDRInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + qDebug() << "TestSourceInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); if (cmd.getStartStop()) { diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 149817708..9a656db67 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -47,7 +47,7 @@ SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : m_dataBlock(0), m_centerFrequency(0), m_sampleRate(48000), - m_sampleBytes(SDR_RX_SAMP_SZ/8), + m_sampleBytes(SDR_RX_SAMP_SZ == 24 ? 4 : 2), m_nbBlocksFEC(0), m_txDelay(100) { @@ -128,7 +128,7 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const qDebug() << "SDRDaemonChannelSink::feed: meta: " << "|" << metaData.m_centerFrequency << ":" << metaData.m_sampleRate - << ":" << (int) (metaData.m_sampleBytes & 0xF) + << ":" << (int) metaData.m_sampleBytes << ":" << (int) metaData.m_sampleBits << "|" << (int) metaData.m_nbOriginalBlocks << ":" << (int) metaData.m_nbFECBlocks diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index 7e617cca1..810954cbb 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -62,7 +62,6 @@ public: /** Set sample rate given in Hz */ void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } - void setSampleBytes(uint8_t sampleBytes) { m_sampleBytes = sampleBytes; } void setNbBlocksFEC(int nbBlocksFEC); void setTxDelay(int txDelay); From cc4b5fa7e6d365193600427251be9d8f9876a787 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 22 Aug 2018 20:57:11 +0200 Subject: [PATCH 646/956] SDRdaemon: more debug messages for options parsing --- sdrdaemon/sdrdaemonparser.cpp | 52 +++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/sdrdaemon/sdrdaemonparser.cpp b/sdrdaemon/sdrdaemonparser.cpp index b5faa6482..94964f567 100644 --- a/sdrdaemon/sdrdaemonparser.cpp +++ b/sdrdaemon/sdrdaemonparser.cpp @@ -58,7 +58,7 @@ SDRDaemonParser::SDRDaemonParser() : m_txDelayOption(QStringList() << "d" << "tx-delay", "delay between transmission of UDP blocks (ms).", "txDelay", - "100"), + "100"), m_nbBlocksFECOption(QStringList() << "f" << "fec-blocks", "Number of FEC blocks per frame.", "nbBlocksFEC", @@ -113,9 +113,13 @@ void SDRDaemonParser::parse(const QCoreApplication& app) + "\\." + ipRange + "$"); QRegExpValidator ipValidator(ipRegex); - if (ipValidator.validate(serverAddress, pos) == QValidator::Acceptable) { + if (ipValidator.validate(serverAddress, pos) == QValidator::Acceptable) + { m_serverAddress = serverAddress; - } else { + qDebug() << "SDRDaemonParser::parse: server address: " << m_serverAddress; + } + else + { qWarning() << "SDRDaemonParser::parse: server address invalid. Defaulting to " << m_serverAddress; } @@ -124,9 +128,13 @@ void SDRDaemonParser::parse(const QCoreApplication& app) QString serverPortStr = m_parser.value(m_serverPortOption); int serverPort = serverPortStr.toInt(&ok); - if (ok && (serverPort > 1023) && (serverPort < 65536)) { + if (ok && (serverPort > 1023) && (serverPort < 65536)) + { m_serverPort = serverPort; - } else { + qDebug() << "SDRDaemonParser::parse: server port: " << m_serverPort; + } + else + { qWarning() << "SDRDaemonParser::parse: server port invalid. Defaulting to " << m_serverPort; } @@ -134,9 +142,13 @@ void SDRDaemonParser::parse(const QCoreApplication& app) QString dataAddress = m_parser.value(m_dataAddressOption); - if (ipValidator.validate(dataAddress, pos) == QValidator::Acceptable) { + if (ipValidator.validate(dataAddress, pos) == QValidator::Acceptable) + { m_dataAddress = dataAddress; - } else { + qDebug() << "SDRDaemonParser::parse: data address: " << m_dataAddress; + } + else + { qWarning() << "SDRDaemonParser::parse: data address invalid. Defaulting to " << m_dataAddress; } @@ -145,9 +157,13 @@ void SDRDaemonParser::parse(const QCoreApplication& app) QString dataPortStr = m_parser.value(m_dataPortOption); serverPort = serverPortStr.toInt(&ok); - if (ok && (serverPort > 1023) && (serverPort < 65536)) { + if (ok && (serverPort > 1023) && (serverPort < 65536)) + { m_dataPort = serverPort; - } else { + qWarning() << "SDRDaemonParser::parse: data port: " << m_dataPort; + } + else + { qWarning() << "SDRDaemonParser::parse: data port invalid. Defaulting to " << m_dataPort; } @@ -213,9 +229,13 @@ void SDRDaemonParser::parse(const QCoreApplication& app) QString txDelayStr = m_parser.value(m_txDelayOption); int txDelay = txDelayStr.toInt(&ok); - if (ok && (txDelay > 0)) { + if (ok && (txDelay > 0)) + { m_txDelay = txDelay; - } else { + qDebug() << "SDRDaemonParser::parse: Tx delay: " << m_txDelay; + } + else + { qWarning() << "SDRDaemonParser::parse: Tx delay invalid. Defaulting to " << m_txDelay; } } @@ -226,10 +246,14 @@ void SDRDaemonParser::parse(const QCoreApplication& app) QString nbBlocksFECStr = m_parser.value(m_nbBlocksFECOption); int nbBlocksFEC = nbBlocksFECStr.toInt(&ok); - if (ok && (nbBlocksFEC >= 0) && (nbBlocksFEC < 128)) { + if (ok && (nbBlocksFEC >= 0) && (nbBlocksFEC < 128)) + { m_nbBlocksFEC = nbBlocksFEC; - } else { - qWarning() << "SDRDaemonParser::parse: Tx number of FEC blocks invalid. Defaulting to " << m_nbBlocksFEC; + qDebug() << "SDRDaemonParser::parse: number of FEC blocks: " << m_nbBlocksFEC; + } + else + { + qWarning() << "SDRDaemonParser::parse: number of FEC blocks invalid. Defaulting to " << m_nbBlocksFEC; } } } From 09249455793c5abc7438e0e61b01ec4b95245a53 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 22 Aug 2018 19:45:24 +0000 Subject: [PATCH 647/956] SDRdaemon: fixed data port parsing --- sdrdaemon/sdrdaemonparser.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdrdaemon/sdrdaemonparser.cpp b/sdrdaemon/sdrdaemonparser.cpp index 94964f567..398ab9d48 100644 --- a/sdrdaemon/sdrdaemonparser.cpp +++ b/sdrdaemon/sdrdaemonparser.cpp @@ -42,7 +42,7 @@ SDRDaemonParser::SDRDaemonParser() : m_dataPortOption(QStringList() << "D" << "data-port", "UDP stream data port.", "dataPort", - "9091"), + "9090"), m_deviceTypeOption(QStringList() << "T" << "device-type", "Device type.", "deviceType", @@ -155,12 +155,12 @@ void SDRDaemonParser::parse(const QCoreApplication& app) // data port QString dataPortStr = m_parser.value(m_dataPortOption); - serverPort = serverPortStr.toInt(&ok); + serverPort = dataPortStr.toInt(&ok); if (ok && (serverPort > 1023) && (serverPort < 65536)) { m_dataPort = serverPort; - qWarning() << "SDRDaemonParser::parse: data port: " << m_dataPort; + qDebug() << "SDRDaemonParser::parse: data port: " << m_dataPort; } else { From e067778b7843163259f5ce641c4ac66fb559423c Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 22 Aug 2018 23:16:08 +0200 Subject: [PATCH 648/956] SDRdaemon: dhannel sink: fixed passing data address and port to the thread --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 6 ++++- sdrdaemon/channel/sdrdaemonchannelsink.h | 4 +++ .../channel/sdrdaemonchannelsinkthread.cpp | 27 ++++++++++++------- .../channel/sdrdaemonchannelsinkthread.h | 4 --- sdrdaemon/channel/sdrdaemondatablock.h | 8 ++++-- sdrdaemon/sdrdaemonmain.cpp | 2 ++ 6 files changed, 35 insertions(+), 16 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 9a656db67..9b4a270fc 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -49,7 +49,9 @@ SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : m_sampleRate(48000), m_sampleBytes(SDR_RX_SAMP_SZ == 24 ? 4 : 2), m_nbBlocksFEC(0), - m_txDelay(100) + m_txDelay(100), + m_dataAddress("127.0.0.1"), + m_dataPort(9090) { setObjectName(m_channelId); @@ -170,6 +172,8 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const m_dataBlock->m_controlBlock.m_complete = true; m_dataBlock->m_controlBlock.m_nbBlocksFEC = m_nbBlocksFEC; m_dataBlock->m_controlBlock.m_txDelay = m_txDelay; + m_dataBlock->m_controlBlock.m_dataAddress = m_dataAddress; + m_dataBlock->m_controlBlock.m_dataPort = m_dataPort; m_dataQueue.push(m_dataBlock); m_dataBlock = new SDRDaemonDataBlock(); // create a new one immediately diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index 810954cbb..11c7a7841 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -64,6 +64,8 @@ public: 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; @@ -92,6 +94,8 @@ private: uint8_t m_sampleBytes; int m_nbBlocksFEC; int m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp index 4333cd96b..e975dddcb 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp @@ -36,7 +36,7 @@ SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQ m_dataQueue(dataQueue), m_cm256(cm256), m_address(QHostAddress::LocalHost), - m_port(9090) + m_socket(0) { connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); @@ -68,6 +68,7 @@ void SDRDaemonChannelSinkThread::stopWork() { qDebug("SDRDaemonChannelSinkThread::stopWork"); delete m_socket; + m_socket = 0; m_running = false; wait(); } @@ -96,15 +97,20 @@ bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) uint16_t frameIndex = dataBlock.m_controlBlock.m_frameIndex; int nbBlocksFEC = dataBlock.m_controlBlock.m_nbBlocksFEC; int txDelay = dataBlock.m_controlBlock.m_txDelay; + m_address.setAddress(dataBlock.m_controlBlock.m_dataAddress); + uint16_t dataPort = dataBlock.m_controlBlock.m_dataPort; SDRDaemonSuperBlock *txBlockx = dataBlock.m_superBlocks; if ((nbBlocksFEC == 0) || !m_cm256) // Do not FEC encode { - for (int i = 0; i < SDRDaemonNbOrginalBlocks; i++) + if (m_socket) { - // send block via UDP - m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, m_port); - usleep(txDelay); + 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 @@ -141,11 +147,14 @@ bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) } // Transmit all blocks - for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) + if (m_socket) { - // send block via UDP - m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, m_port); - usleep(txDelay); + 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); + } } } diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h index 375dda954..1f4e655e5 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h @@ -61,9 +61,6 @@ public: void startStop(bool start); - void setAddress(QString& address) { m_address.setAddress(address); } - void setPort(unsigned int port) { m_port = port; } - private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; @@ -73,7 +70,6 @@ private: CM256 *m_cm256; //!< CM256 library object QHostAddress m_address; - unsigned int m_port; QUdpSocket *m_socket; MessageQueue m_inputMessageQueue; diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 2bb9f28f4..b8aec1a71 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -70,12 +70,12 @@ struct SDRDaemonHeader uint8_t m_blockIndex; uint8_t m_filler; - void init() + void init() { m_frameIndex = 0; m_blockIndex = 0; m_filler = 0; - } + } }; static const int SDRDaemonUdpSize = UDPSINKFEC_UDPSIZE; @@ -111,6 +111,8 @@ struct SDRDaemonTxControlBlock uint16_t m_frameIndex; int m_nbBlocksFEC; int m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; SDRDaemonTxControlBlock() { m_complete = false; @@ -118,6 +120,8 @@ struct SDRDaemonTxControlBlock m_frameIndex = 0; m_nbBlocksFEC = 0; m_txDelay = 100; + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; } }; diff --git a/sdrdaemon/sdrdaemonmain.cpp b/sdrdaemon/sdrdaemonmain.cpp index 486ba0efb..963388adf 100644 --- a/sdrdaemon/sdrdaemonmain.cpp +++ b/sdrdaemon/sdrdaemonmain.cpp @@ -132,6 +132,8 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa m_channelSink = new SDRDaemonChannelSink(m_deviceSourceAPI); m_channelSink->setNbBlocksFEC(parser.getNbBlocksFEC()); m_channelSink->setTxDelay(parser.getTxDelay()); + m_channelSink->setDataAddress(parser.getDataAddress()); + m_channelSink->setDataPort(parser.getDataPort()); } else { From 749f8a8ae77746b16dcd2979056f2fd32e30c4d0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 23 Aug 2018 01:12:46 +0200 Subject: [PATCH 649/956] SDRdaemon: differentiate data and device settings. Implement data settings (1) --- sdrbase/resources/webapi/doc/html2/index.html | 4483 +++++++++++------ .../resources/webapi/doc/swagger/swagger.yaml | 89 +- sdrdaemon/webapi/webapiadapterdaemon.cpp | 30 +- sdrdaemon/webapi/webapiadapterdaemon.h | 20 +- sdrdaemon/webapi/webapirequestmapper.cpp | 134 +- sdrdaemon/webapi/webapirequestmapper.h | 7 +- swagger/sdrangel/api/swagger/swagger.yaml | 89 +- swagger/sdrangel/code/html2/index.html | 4483 +++++++++++------ .../sdrangel/code/qt5/client/SWGDaemonApi.cpp | 590 ++- .../sdrangel/code/qt5/client/SWGDaemonApi.h | 56 +- .../code/qt5/client/SWGModelFactory.h | 4 + .../qt5/client/SWGSDRDaemonDataSettings.cpp | 171 + .../qt5/client/SWGSDRDaemonDataSettings.h | 77 + 13 files changed, 6625 insertions(+), 3608 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.h diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 870afebe9..d1c41a364 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3142,6 +3142,27 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SDRDaemonDataSettings = { + "properties" : { + "nbFECBlocks" : { + "type" : "integer", + "description" : "Number of FEC blocks per frame" + }, + "dataAddress" : { + "type" : "string", + "description" : "Receiving USB data address" + }, + "dataPort" : { + "type" : "integer", + "description" : "Receiving USB data port" + }, + "txDelay" : { + "type" : "integer", + "description" : "Minimum delay in ms between consecutive USB blocks transmissions" + } + }, + "description" : "Data handling details for SDRDaemon" }; defs.SDRPlayReport = { "properties" : { @@ -4003,6 +4024,27 @@ margin-bottom: 20px; +
  • + daemonDataSettingsGet +
  • +
  • + daemonDataSettingsPatch +
  • +
  • + daemonDataSettingsPut +
  • +
  • + daemonDeviceReportGet +
  • +
  • + daemonDeviceSettingsGet +
  • +
  • + daemonDeviceSettingsPatch +
  • +
  • + daemonDeviceSettingsPut +
  • daemonInstanceLoggingGet
  • @@ -4012,9 +4054,6 @@ margin-bottom: 20px;
  • daemonInstanceSummary
  • -
  • - daemonReportGet -
  • daemonRunDelete
  • @@ -4024,15 +4063,6 @@ margin-bottom: 20px;
  • daemonRunPost
  • -
  • - daemonSettingsGet -
  • -
  • - daemonSettingsPatch -
  • -
  • - daemonSettingsPut -
  • devicesetChannelDelete @@ -4200,6 +4230,2774 @@ margin-bottom: 20px;

    Daemon

    +
    +
    +
    +

    daemonDataSettingsGet

    +

    +
    +
    +
    +

    +

    Get data handling details

    +

    +
    +
    /sdrdaemon/data/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrdaemon/data/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDataSettingsGetWithCompletionHandler: 
    +              ^(SDRDaemonDataSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDataSettingsGet(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDataSettingsGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +
    +            try
    +            {
    +                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +
    +try {
    +    $result = $api_instance->daemonDataSettingsGet();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDataSettingsGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +
    +eval { 
    +    my $result = $api_instance->daemonDataSettingsGet();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDataSettingsGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +
    +try: 
    +    api_response = api_instance.daemon_data_settings_get()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDataSettingsGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - On success returns current data handling details

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDataSettingsPatch

    +

    +
    +
    +
    +

    +

    Apply data handling details differentially (no force)

    +

    +
    +
    /sdrdaemon/data/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrdaemon/data/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    SDRDaemonDataSettings *body = ; // Data handling detail to apply
    +
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDataSettingsPatchWith:body
    +              completionHandler: ^(SDRDaemonDataSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var body = ; // {SDRDaemonDataSettings} Data handling detail to apply
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDataSettingsPatch(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDataSettingsPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +            var body = new SDRDaemonDataSettings(); // SDRDaemonDataSettings | Data handling detail to apply
    +
    +            try
    +            {
    +                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +$body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +
    +try {
    +    $result = $api_instance->daemonDataSettingsPatch($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDataSettingsPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +my $body = SWGSDRangel::Object::SDRDaemonDataSettings->new(); # SDRDaemonDataSettings | Data handling detail to apply
    +
    +eval { 
    +    my $result = $api_instance->daemonDataSettingsPatch(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDataSettingsPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +body =  # SDRDaemonDataSettings | Data handling detail to apply
    +
    +try: 
    +    api_response = api_instance.daemon_data_settings_patch(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDataSettingsPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - On success returns new settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDataSettingsPut

    +

    +
    +
    +
    +

    +

    Apply data handling details unconditionally (force)

    +

    +
    +
    /sdrdaemon/data/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PUT "http://localhost/sdrdaemon/data/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling details to apply
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPut");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling details to apply
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPut");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    SDRDaemonDataSettings *body = ; // Data handling details to apply
    +
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDataSettingsPutWith:body
    +              completionHandler: ^(SDRDaemonDataSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var body = ; // {SDRDaemonDataSettings} Data handling details to apply
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDataSettingsPut(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDataSettingsPutExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +            var body = new SDRDaemonDataSettings(); // SDRDaemonDataSettings | Data handling details to apply
    +
    +            try
    +            {
    +                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsPut: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +$body = ; // SDRDaemonDataSettings | Data handling details to apply
    +
    +try {
    +    $result = $api_instance->daemonDataSettingsPut($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDataSettingsPut: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +my $body = SWGSDRangel::Object::SDRDaemonDataSettings->new(); # SDRDaemonDataSettings | Data handling details to apply
    +
    +eval { 
    +    my $result = $api_instance->daemonDataSettingsPut(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDataSettingsPut: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +body =  # SDRDaemonDataSettings | Data handling details to apply
    +
    +try: 
    +    api_response = api_instance.daemon_data_settings_put(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDataSettingsPut: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - On success returns new settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDeviceReportGet

    +

    +
    +
    +
    +

    +

    get the device report

    +

    +
    +
    /sdrdaemon/device/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrdaemon/device/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            DeviceReport result = apiInstance.daemonDeviceReportGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            DeviceReport result = apiInstance.daemonDeviceReportGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDeviceReportGetWithCompletionHandler: 
    +              ^(DeviceReport output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDeviceReportGet(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDeviceReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +
    +            try
    +            {
    +                DeviceReport result = apiInstance.daemonDeviceReportGet();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDeviceReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +
    +try {
    +    $result = $api_instance->daemonDeviceReportGet();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDeviceReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +
    +eval { 
    +    my $result = $api_instance->daemonDeviceReportGet();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDeviceReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +
    +try: 
    +    api_response = api_instance.daemon_device_report_get()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDeviceReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - On success return device report

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDeviceSettingsGet

    +

    +
    +
    +
    +

    +

    Get device settings

    +

    +
    +
    /sdrdaemon/device/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrdaemon/device/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDeviceSettingsGetWithCompletionHandler: 
    +              ^(DeviceSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDeviceSettingsGet(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDeviceSettingsGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +
    +            try
    +            {
    +                DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +
    +try {
    +    $result = $api_instance->daemonDeviceSettingsGet();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDeviceSettingsGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +
    +eval { 
    +    my $result = $api_instance->daemonDeviceSettingsGet();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDeviceSettingsGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +
    +try: 
    +    api_response = api_instance.daemon_device_settings_get()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDeviceSettingsGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - On success returns current settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Invalid device set index or device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDeviceSettingsPatch

    +

    +
    +
    +
    +

    +

    Apply settings differentially (no force)

    +

    +
    +
    /sdrdaemon/device/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrdaemon/device/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    DeviceSettings *body = ; // Device settings to apply
    +
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDeviceSettingsPatchWith:body
    +              completionHandler: ^(DeviceSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var body = ; // {DeviceSettings} Device settings to apply
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDeviceSettingsPatch(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDeviceSettingsPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    +
    +            try
    +            {
    +                DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +$body = ; // DeviceSettings | Device settings to apply
    +
    +try {
    +    $result = $api_instance->daemonDeviceSettingsPatch($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDeviceSettingsPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    +
    +eval { 
    +    my $result = $api_instance->daemonDeviceSettingsPatch(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDeviceSettingsPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +body =  # DeviceSettings | Device settings to apply
    +
    +try: 
    +    api_response = api_instance.daemon_device_settings_patch(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDeviceSettingsPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - On success returns new settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Invalid device set index or device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDeviceSettingsPut

    +

    +
    +
    +
    +

    +

    Apply all settings unconditionally (force)

    +

    +
    +
    /sdrdaemon/device/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PUT "http://localhost/sdrdaemon/device/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPut");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPut");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    DeviceSettings *body = ; // Device settings to apply
    +
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDeviceSettingsPutWith:body
    +              completionHandler: ^(DeviceSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var body = ; // {DeviceSettings} Device settings to apply
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDeviceSettingsPut(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDeviceSettingsPutExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    +
    +            try
    +            {
    +                DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsPut: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +$body = ; // DeviceSettings | Device settings to apply
    +
    +try {
    +    $result = $api_instance->daemonDeviceSettingsPut($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDeviceSettingsPut: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    +
    +eval { 
    +    my $result = $api_instance->daemonDeviceSettingsPut(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDeviceSettingsPut: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +body =  # DeviceSettings | Device settings to apply
    +
    +try: 
    +    api_response = api_instance.daemon_device_settings_put(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDeviceSettingsPut: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - On success returns new settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Invalid device set index or device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -5300,427 +8098,6 @@ except ApiException as e:

    -
    -
    -
    -

    daemonReportGet

    -

    -
    -
    -
    -

    -

    get the device report

    -

    -
    -
    /sdrdaemon/report
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/report"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceReport result = apiInstance.daemonReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceReport result = apiInstance.daemonReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonReportGetWithCompletionHandler: 
    -              ^(DeviceReport output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonReportGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonReportGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceReport result = apiInstance.daemonReportGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonReportGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonReportGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonReportGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonReportGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonReportGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_report_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonReportGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return device report

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    @@ -6984,1244 +9361,6 @@ except ApiException as e:

    -
    -
    -
    -

    daemonSettingsGet

    -

    -
    -
    -
    -

    -

    Get device settings

    -

    -
    -
    /sdrdaemon/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonSettingsGetWithCompletionHandler: 
    -              ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonSettingsGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonSettingsGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonSettingsGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonSettingsGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonSettingsGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonSettingsGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonSettingsGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonSettingsGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_settings_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonSettingsGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success returns current settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonSettingsPatch

    -

    -
    -
    -
    -

    -

    Apply settings differentially (no force)

    -

    -
    -
    /sdrdaemon/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PATCH "http://localhost/sdrdaemon/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    DeviceSettings *body = ; // Device settings to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonSettingsPatchWith:body
    -              completionHandler: ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {DeviceSettings} Device settings to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonSettingsPatch(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonSettingsPatchExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonSettingsPatch(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonSettingsPatch: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // DeviceSettings | Device settings to apply
    -
    -try {
    -    $result = $api_instance->daemonSettingsPatch($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonSettingsPatch: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonSettingsPatch(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonSettingsPatch: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # DeviceSettings | Device settings to apply
    -
    -try: 
    -    api_response = api_instance.daemon_settings_patch(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonSettingsPatch: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonSettingsPut

    -

    -
    -
    -
    -

    -

    Apply all settings unconditionally (force)

    -

    -
    -
    /sdrdaemon/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PUT "http://localhost/sdrdaemon/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    DeviceSettings *body = ; // Device settings to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonSettingsPutWith:body
    -              completionHandler: ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {DeviceSettings} Device settings to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonSettingsPut(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonSettingsPutExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonSettingsPut(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonSettingsPut: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // DeviceSettings | Device settings to apply
    -
    -try {
    -    $result = $api_instance->daemonSettingsPut($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonSettingsPut: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonSettingsPut(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonSettingsPut: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # DeviceSettings | Device settings to apply
    -
    -try: 
    -    api_response = api_instance.daemon_settings_put(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonSettingsPut: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -

    DeviceSet

    @@ -27073,7 +28212,7 @@ except ApiException as e:
    - Generated 2018-08-19T11:32:09.545+02:00 + Generated 2018-08-23T00:21:49.115+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 536e0e7f1..6fce1d3ae 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1297,12 +1297,73 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + + /sdrdaemon/data/settings: + x-swagger-router-controller: deviceset + get: + description: Get data handling details + operationId: daemonDataSettingsGet + tags: + - Daemon + responses: + "200": + description: On success returns current data handling details + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + put: + description: Apply data handling details unconditionally (force) + operationId: daemonDataSettingsPut + tags: + - Daemon + parameters: + - name: body + in: body + description: Data handling details to apply + required: true + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + responses: + "200": + description: On success returns new settings values + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + patch: + description: Apply data handling details differentially (no force) + operationId: daemonDataSettingsPatch + tags: + - Daemon + parameters: + - name: body + in: body + description: Data handling detail to apply + required: true + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + responses: + "200": + description: On success returns new settings values + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + - /sdrdaemon/settings: + /sdrdaemon/device/settings: x-swagger-router-controller: deviceset get: description: Get device settings - operationId: daemonSettingsGet + operationId: daemonDeviceSettingsGet tags: - Daemon responses: @@ -1320,7 +1381,7 @@ paths: $ref: "#/responses/Response_501" put: description: Apply all settings unconditionally (force) - operationId: daemonSettingsPut + operationId: daemonDeviceSettingsPut tags: - Daemon parameters: @@ -1345,7 +1406,7 @@ paths: $ref: "#/responses/Response_501" patch: description: Apply settings differentially (no force) - operationId: daemonSettingsPatch + operationId: daemonDeviceSettingsPatch tags: - Daemon parameters: @@ -1438,11 +1499,11 @@ paths: "501": $ref: "#/responses/Response_501" - /sdrdaemon/report: + /sdrdaemon/device/report: x-swagger-router-controller: deviceset get: description: get the device report - operationId: daemonReportGet + operationId: daemonDeviceReportGet tags: - Daemon responses: @@ -2183,6 +2244,22 @@ definitions: WFMModReport: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModReport" + SDRDaemonDataSettings: + description: "Data handling details for SDRDaemon" + properties: + nbFECBlocks: + description: "Number of FEC blocks per frame" + type: integer + dataAddress: + description: "Receiving USB data address" + type: string + dataPort: + description: "Receiving USB data port" + type: integer + txDelay: + description: "Minimum delay in ms between consecutive USB blocks transmissions" + type: integer + responses: Response_500: diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index a92641bc2..7d084edbd 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -23,6 +23,7 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGDeviceReport.h" +#include "SWGSDRDaemonDataSettings.h" #include "SWGErrorResponse.h" #include "dsp/dsptypes.h" @@ -38,8 +39,9 @@ QString WebAPIAdapterDaemon::daemonInstanceSummaryURL = "/sdrdaemon"; QString WebAPIAdapterDaemon::daemonInstanceLoggingURL = "/sdrdaemon/logging"; -QString WebAPIAdapterDaemon::daemonSettingsURL = "/sdrdaemon/settings"; -QString WebAPIAdapterDaemon::daemonReportURL = "/sdrdaemon/report"; +QString WebAPIAdapterDaemon::daemonDataSettingsURL = "/sdrdaemon/data/settings"; +QString WebAPIAdapterDaemon::daemonDeviceSettingsURL = "/sdrdaemon/device/settings"; +QString WebAPIAdapterDaemon::daemonDeviceReportURL = "/sdrdaemon/device/report"; QString WebAPIAdapterDaemon::daemonRunURL = "/sdrdaemon/run"; WebAPIAdapterDaemon::WebAPIAdapterDaemon(SDRDaemonMain& sdrDaemonMain) : @@ -174,7 +176,27 @@ int WebAPIAdapterDaemon::daemonInstanceLoggingPut( return 200; } -int WebAPIAdapterDaemon::daemonSettingsGet( +int WebAPIAdapterDaemon::daemonDataSettingsGet( + SWGSDRangel::SWGSDRDaemonDataSettings& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) +{ + error.init(); + *error.getMessage() = "Not implemented"; + return 501; +} + +int WebAPIAdapterDaemon::daemonDataSettingsPutPatch( + bool force __attribute__((unused)), + const QStringList& dataSettingsKeys __attribute__((unused)), + SWGSDRangel::SWGSDRDaemonDataSettings& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) +{ + error.init(); + *error.getMessage() = "Not implemented"; + return 501; +} + +int WebAPIAdapterDaemon::daemonDeviceSettingsGet( SWGSDRangel::SWGDeviceSettings& response __attribute__((unused)), SWGSDRangel::SWGErrorResponse& error) { @@ -201,7 +223,7 @@ int WebAPIAdapterDaemon::daemonSettingsGet( } } -int WebAPIAdapterDaemon::daemonSettingsPutPatch( +int WebAPIAdapterDaemon::daemonDeviceSettingsPutPatch( bool force, const QStringList& deviceSettingsKeys, SWGSDRangel::SWGDeviceSettings& response, diff --git a/sdrdaemon/webapi/webapiadapterdaemon.h b/sdrdaemon/webapi/webapiadapterdaemon.h index 8cd5af801..6403900bf 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.h +++ b/sdrdaemon/webapi/webapiadapterdaemon.h @@ -33,6 +33,7 @@ namespace SWGSDRangel class SWGSuccessResponse; class SWGErrorResponse; class SWGLoggingInfo; + class SWGSDRDaemonDataSettings; } class SDRDaemonMain; @@ -56,11 +57,21 @@ public: SWGSDRangel::SWGLoggingInfo& response, SWGSDRangel::SWGErrorResponse& error); - int daemonSettingsGet( + int daemonDataSettingsGet( + SWGSDRangel::SWGSDRDaemonDataSettings& response, + SWGSDRangel::SWGErrorResponse& error); + + int daemonDataSettingsPutPatch( + bool force, + const QStringList& dataSettingsKeys, + SWGSDRangel::SWGSDRDaemonDataSettings& response, + SWGSDRangel::SWGErrorResponse& error); + + int daemonDeviceSettingsGet( SWGSDRangel::SWGDeviceSettings& response, SWGSDRangel::SWGErrorResponse& error); - int daemonSettingsPutPatch( + int daemonDeviceSettingsPutPatch( bool force, const QStringList& deviceSettingsKeys, SWGSDRangel::SWGDeviceSettings& response, @@ -84,8 +95,9 @@ public: static QString daemonInstanceSummaryURL; static QString daemonInstanceLoggingURL; - static QString daemonSettingsURL; - static QString daemonReportURL; + static QString daemonDataSettingsURL; + static QString daemonDeviceSettingsURL; + static QString daemonDeviceReportURL; static QString daemonRunURL; private: diff --git a/sdrdaemon/webapi/webapirequestmapper.cpp b/sdrdaemon/webapi/webapirequestmapper.cpp index c8b2cd807..a5a162208 100644 --- a/sdrdaemon/webapi/webapirequestmapper.cpp +++ b/sdrdaemon/webapi/webapirequestmapper.cpp @@ -26,6 +26,7 @@ #include "webapirequestmapper.h" #include "SWGDaemonSummaryResponse.h" #include "SWGInstanceDevicesResponse.h" +#include "SWGSDRDaemonDataSettings.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGDeviceReport.h" @@ -95,10 +96,12 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http daemonInstanceSummaryService(request, response); } else if (path == WebAPIAdapterDaemon::daemonInstanceLoggingURL) { daemonInstanceLoggingService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonSettingsURL) { - daemonSettingsService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonReportURL) { - daemonReportService(request, response); + } else if (path == WebAPIAdapterDaemon::daemonDataSettingsURL) { + daemonDataSettingsService(request, response); + } else if (path == WebAPIAdapterDaemon::daemonDeviceSettingsURL) { + daemonDeviceSettingsService(request, response); + } else if (path == WebAPIAdapterDaemon::daemonDeviceReportURL) { + daemonDeviceReportService(request, response); } else if (path == WebAPIAdapterDaemon::daemonRunURL) { daemonRunService(request, response); } else { @@ -188,7 +191,75 @@ void WebAPIRequestMapper::daemonInstanceLoggingService(qtwebapp::HttpRequest& re } } -void WebAPIRequestMapper::daemonSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +void WebAPIRequestMapper::daemonDataSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +{ + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); + + if ((request.getMethod() == "PUT") || (request.getMethod() == "PATCH")) + { + QString jsonStr = request.getBody(); + QJsonObject jsonObject; + + if (parseJsonBody(jsonStr, jsonObject, response)) + { + SWGSDRangel::SWGSDRDaemonDataSettings normalResponse; + QStringList dataSettingsKeys; + + if (validateDataSettings(normalResponse, jsonObject, dataSettingsKeys)) + { + int status = m_adapter->daemonDataSettingsPutPatch( + (request.getMethod() == "PUT"), // force settings on PUT + dataSettingsKeys, + normalResponse, + errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(400,"Invalid JSON request"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid JSON request"; + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(400,"Invalid JSON format"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid JSON format"; + response.write(errorResponse.asJson().toUtf8()); + } + } + else if (request.getMethod() == "GET") + { + SWGSDRangel::SWGSDRDaemonDataSettings normalResponse; + int status = m_adapter->daemonDataSettingsGet(normalResponse, errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(405,"Invalid HTTP method"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid HTTP method"; + response.write(errorResponse.asJson().toUtf8()); + } +} + +void WebAPIRequestMapper::daemonDeviceSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); @@ -207,7 +278,7 @@ void WebAPIRequestMapper::daemonSettingsService(qtwebapp::HttpRequest& request, if (validateDeviceSettings(normalResponse, jsonObject, deviceSettingsKeys)) { - int status = m_adapter->daemonSettingsPutPatch( + int status = m_adapter->daemonDeviceSettingsPutPatch( (request.getMethod() == "PUT"), // force settings on PUT deviceSettingsKeys, normalResponse, @@ -240,7 +311,7 @@ void WebAPIRequestMapper::daemonSettingsService(qtwebapp::HttpRequest& request, { SWGSDRangel::SWGDeviceSettings normalResponse; resetDeviceSettings(normalResponse); - int status = m_adapter->daemonSettingsGet(normalResponse, errorResponse); + int status = m_adapter->daemonDeviceSettingsGet(normalResponse, errorResponse); response.setStatus(status); if (status/100 == 2) { @@ -258,7 +329,7 @@ void WebAPIRequestMapper::daemonSettingsService(qtwebapp::HttpRequest& request, } } -void WebAPIRequestMapper::daemonReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +void WebAPIRequestMapper::daemonDeviceReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); @@ -340,6 +411,53 @@ void WebAPIRequestMapper::daemonRunService(qtwebapp::HttpRequest& request, qtweb } } + +bool WebAPIRequestMapper::validateDataSettings(SWGSDRangel::SWGSDRDaemonDataSettings& dataSettings, QJsonObject& jsonObject, QStringList& dataSettingsKeys) +{ + if (jsonObject.contains("nbFECBlocks")) + { + int nbFECBlocks = jsonObject["nbFECBlocks"].toInt(); + + if (nbFECBlocks >=0 && nbFECBlocks < 127) { + dataSettings.setNbFecBlocks(nbFECBlocks); + } else { + dataSettings.setNbFecBlocks(0); + } + } + + if (jsonObject.contains("dataPort")) + { + int dataPort = jsonObject["dataPort"].toInt(); + + if (dataPort > 1023 && dataPort < 65536) { + dataSettings.setDataPort(dataPort); + } else { + dataSettings.setDataPort(9090); + } + } + + if (jsonObject.contains("txDelay")) + { + int txDelay = jsonObject["txDelay"].toInt(); + + if (txDelay > 100) { + dataSettings.setTxDelay(txDelay); + } else { + dataSettings.setTxDelay(100); + } + } + + if (jsonObject.contains("dataAddress") && jsonObject["dataAddress"].isString()) { + dataSettings.setDataAddress(new QString(jsonObject["dataAddress"].toString())); + } else { + return false; + } + + dataSettingsKeys = jsonObject.keys(); + + return true; +} + // TODO: put in library in common with SDRangel. Can be static. bool WebAPIRequestMapper::validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys) { diff --git a/sdrdaemon/webapi/webapirequestmapper.h b/sdrdaemon/webapi/webapirequestmapper.h index 14cc361c8..927fa8fc9 100644 --- a/sdrdaemon/webapi/webapirequestmapper.h +++ b/sdrdaemon/webapi/webapirequestmapper.h @@ -30,6 +30,7 @@ namespace SWGSDRangel { + class SWGSDRDaemonDataSettings; class SWGDeviceSettings; class SWGDeviceReport; } @@ -53,10 +54,12 @@ private: void daemonInstanceSummaryService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void daemonInstanceLoggingService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void daemonDataSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void daemonDeviceSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void daemonRunService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void daemonDeviceReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + bool validateDataSettings(SWGSDRangel::SWGSDRDaemonDataSettings& dataSettings, QJsonObject& jsonObject, QStringList& dataSettingsKeys); bool validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys); void appendSettingsSubKeys( diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 2d51dbbe4..c6b1d8920 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1297,12 +1297,73 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + + /sdrdaemon/data/settings: + x-swagger-router-controller: deviceset + get: + description: Get data handling details + operationId: daemonDataSettingsGet + tags: + - Daemon + responses: + "200": + description: On success returns current data handling details + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + put: + description: Apply data handling details unconditionally (force) + operationId: daemonDataSettingsPut + tags: + - Daemon + parameters: + - name: body + in: body + description: Data handling details to apply + required: true + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + responses: + "200": + description: On success returns new settings values + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + patch: + description: Apply data handling details differentially (no force) + operationId: daemonDataSettingsPatch + tags: + - Daemon + parameters: + - name: body + in: body + description: Data handling detail to apply + required: true + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + responses: + "200": + description: On success returns new settings values + schema: + $ref: "#/definitions/SDRDaemonDataSettings" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + - /sdrdaemon/settings: + /sdrdaemon/device/settings: x-swagger-router-controller: deviceset get: description: Get device settings - operationId: daemonSettingsGet + operationId: daemonDeviceSettingsGet tags: - Daemon responses: @@ -1320,7 +1381,7 @@ paths: $ref: "#/responses/Response_501" put: description: Apply all settings unconditionally (force) - operationId: daemonSettingsPut + operationId: daemonDeviceSettingsPut tags: - Daemon parameters: @@ -1345,7 +1406,7 @@ paths: $ref: "#/responses/Response_501" patch: description: Apply settings differentially (no force) - operationId: daemonSettingsPatch + operationId: daemonDeviceSettingsPatch tags: - Daemon parameters: @@ -1438,11 +1499,11 @@ paths: "501": $ref: "#/responses/Response_501" - /sdrdaemon/report: + /sdrdaemon/device/report: x-swagger-router-controller: deviceset get: description: get the device report - operationId: daemonReportGet + operationId: daemonDeviceReportGet tags: - Daemon responses: @@ -2183,6 +2244,22 @@ definitions: WFMModReport: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModReport" + SDRDaemonDataSettings: + description: "Data handling details for SDRDaemon" + properties: + nbFECBlocks: + description: "Number of FEC blocks per frame" + type: integer + dataAddress: + description: "Receiving USB data address" + type: string + dataPort: + description: "Receiving USB data port" + type: integer + txDelay: + description: "Minimum delay in ms between consecutive USB blocks transmissions" + type: integer + responses: Response_500: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 870afebe9..d1c41a364 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -3142,6 +3142,27 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SDRDaemonDataSettings = { + "properties" : { + "nbFECBlocks" : { + "type" : "integer", + "description" : "Number of FEC blocks per frame" + }, + "dataAddress" : { + "type" : "string", + "description" : "Receiving USB data address" + }, + "dataPort" : { + "type" : "integer", + "description" : "Receiving USB data port" + }, + "txDelay" : { + "type" : "integer", + "description" : "Minimum delay in ms between consecutive USB blocks transmissions" + } + }, + "description" : "Data handling details for SDRDaemon" }; defs.SDRPlayReport = { "properties" : { @@ -4003,6 +4024,27 @@ margin-bottom: 20px;
  • +
  • + daemonDataSettingsGet +
  • +
  • + daemonDataSettingsPatch +
  • +
  • + daemonDataSettingsPut +
  • +
  • + daemonDeviceReportGet +
  • +
  • + daemonDeviceSettingsGet +
  • +
  • + daemonDeviceSettingsPatch +
  • +
  • + daemonDeviceSettingsPut +
  • daemonInstanceLoggingGet
  • @@ -4012,9 +4054,6 @@ margin-bottom: 20px;
  • daemonInstanceSummary
  • -
  • - daemonReportGet -
  • daemonRunDelete
  • @@ -4024,15 +4063,6 @@ margin-bottom: 20px;
  • daemonRunPost
  • -
  • - daemonSettingsGet -
  • -
  • - daemonSettingsPatch -
  • -
  • - daemonSettingsPut -
  • devicesetChannelDelete @@ -4200,6 +4230,2774 @@ margin-bottom: 20px;

    Daemon

    +
    +
    +
    +

    daemonDataSettingsGet

    +

    +
    +
    +
    +

    +

    Get data handling details

    +

    +
    +
    /sdrdaemon/data/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrdaemon/data/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDataSettingsGetWithCompletionHandler: 
    +              ^(SDRDaemonDataSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDataSettingsGet(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDataSettingsGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +
    +            try
    +            {
    +                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +
    +try {
    +    $result = $api_instance->daemonDataSettingsGet();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDataSettingsGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +
    +eval { 
    +    my $result = $api_instance->daemonDataSettingsGet();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDataSettingsGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +
    +try: 
    +    api_response = api_instance.daemon_data_settings_get()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDataSettingsGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - On success returns current data handling details

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDataSettingsPatch

    +

    +
    +
    +
    +

    +

    Apply data handling details differentially (no force)

    +

    +
    +
    /sdrdaemon/data/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrdaemon/data/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    SDRDaemonDataSettings *body = ; // Data handling detail to apply
    +
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDataSettingsPatchWith:body
    +              completionHandler: ^(SDRDaemonDataSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var body = ; // {SDRDaemonDataSettings} Data handling detail to apply
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDataSettingsPatch(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDataSettingsPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +            var body = new SDRDaemonDataSettings(); // SDRDaemonDataSettings | Data handling detail to apply
    +
    +            try
    +            {
    +                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +$body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +
    +try {
    +    $result = $api_instance->daemonDataSettingsPatch($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDataSettingsPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +my $body = SWGSDRangel::Object::SDRDaemonDataSettings->new(); # SDRDaemonDataSettings | Data handling detail to apply
    +
    +eval { 
    +    my $result = $api_instance->daemonDataSettingsPatch(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDataSettingsPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +body =  # SDRDaemonDataSettings | Data handling detail to apply
    +
    +try: 
    +    api_response = api_instance.daemon_data_settings_patch(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDataSettingsPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - On success returns new settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDataSettingsPut

    +

    +
    +
    +
    +

    +

    Apply data handling details unconditionally (force)

    +

    +
    +
    /sdrdaemon/data/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PUT "http://localhost/sdrdaemon/data/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling details to apply
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPut");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling details to apply
    +        try {
    +            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPut");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    SDRDaemonDataSettings *body = ; // Data handling details to apply
    +
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDataSettingsPutWith:body
    +              completionHandler: ^(SDRDaemonDataSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var body = ; // {SDRDaemonDataSettings} Data handling details to apply
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDataSettingsPut(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDataSettingsPutExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +            var body = new SDRDaemonDataSettings(); // SDRDaemonDataSettings | Data handling details to apply
    +
    +            try
    +            {
    +                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsPut: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +$body = ; // SDRDaemonDataSettings | Data handling details to apply
    +
    +try {
    +    $result = $api_instance->daemonDataSettingsPut($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDataSettingsPut: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +my $body = SWGSDRangel::Object::SDRDaemonDataSettings->new(); # SDRDaemonDataSettings | Data handling details to apply
    +
    +eval { 
    +    my $result = $api_instance->daemonDataSettingsPut(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDataSettingsPut: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +body =  # SDRDaemonDataSettings | Data handling details to apply
    +
    +try: 
    +    api_response = api_instance.daemon_data_settings_put(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDataSettingsPut: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - On success returns new settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDeviceReportGet

    +

    +
    +
    +
    +

    +

    get the device report

    +

    +
    +
    /sdrdaemon/device/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrdaemon/device/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            DeviceReport result = apiInstance.daemonDeviceReportGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            DeviceReport result = apiInstance.daemonDeviceReportGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDeviceReportGetWithCompletionHandler: 
    +              ^(DeviceReport output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDeviceReportGet(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDeviceReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +
    +            try
    +            {
    +                DeviceReport result = apiInstance.daemonDeviceReportGet();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDeviceReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +
    +try {
    +    $result = $api_instance->daemonDeviceReportGet();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDeviceReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +
    +eval { 
    +    my $result = $api_instance->daemonDeviceReportGet();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDeviceReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +
    +try: 
    +    api_response = api_instance.daemon_device_report_get()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDeviceReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - On success return device report

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDeviceSettingsGet

    +

    +
    +
    +
    +

    +

    Get device settings

    +

    +
    +
    /sdrdaemon/device/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrdaemon/device/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDeviceSettingsGetWithCompletionHandler: 
    +              ^(DeviceSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDeviceSettingsGet(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDeviceSettingsGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +
    +            try
    +            {
    +                DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +
    +try {
    +    $result = $api_instance->daemonDeviceSettingsGet();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDeviceSettingsGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +
    +eval { 
    +    my $result = $api_instance->daemonDeviceSettingsGet();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDeviceSettingsGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +
    +try: 
    +    api_response = api_instance.daemon_device_settings_get()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDeviceSettingsGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - On success returns current settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Invalid device set index or device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDeviceSettingsPatch

    +

    +
    +
    +
    +

    +

    Apply settings differentially (no force)

    +

    +
    +
    /sdrdaemon/device/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrdaemon/device/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    DeviceSettings *body = ; // Device settings to apply
    +
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDeviceSettingsPatchWith:body
    +              completionHandler: ^(DeviceSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var body = ; // {DeviceSettings} Device settings to apply
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDeviceSettingsPatch(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDeviceSettingsPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    +
    +            try
    +            {
    +                DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +$body = ; // DeviceSettings | Device settings to apply
    +
    +try {
    +    $result = $api_instance->daemonDeviceSettingsPatch($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDeviceSettingsPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    +
    +eval { 
    +    my $result = $api_instance->daemonDeviceSettingsPatch(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDeviceSettingsPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +body =  # DeviceSettings | Device settings to apply
    +
    +try: 
    +    api_response = api_instance.daemon_device_settings_patch(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDeviceSettingsPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - On success returns new settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Invalid device set index or device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    daemonDeviceSettingsPut

    +

    +
    +
    +
    +

    +

    Apply all settings unconditionally (force)

    +

    +
    +
    /sdrdaemon/device/settings
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PUT "http://localhost/sdrdaemon/device/settings"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPut");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    +        try {
    +            DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPut");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    DeviceSettings *body = ; // Device settings to apply
    +
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonDeviceSettingsPutWith:body
    +              completionHandler: ^(DeviceSettings output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var body = ; // {DeviceSettings} Device settings to apply
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonDeviceSettingsPut(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonDeviceSettingsPutExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    +
    +            try
    +            {
    +                DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsPut: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +$body = ; // DeviceSettings | Device settings to apply
    +
    +try {
    +    $result = $api_instance->daemonDeviceSettingsPut($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonDeviceSettingsPut: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    +
    +eval { 
    +    my $result = $api_instance->daemonDeviceSettingsPut(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonDeviceSettingsPut: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +body =  # DeviceSettings | Device settings to apply
    +
    +try: 
    +    api_response = api_instance.daemon_device_settings_put(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonDeviceSettingsPut: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - On success returns new settings values

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Invalid device set index or device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -5300,427 +8098,6 @@ except ApiException as e:

    -
    -
    -
    -

    daemonReportGet

    -

    -
    -
    -
    -

    -

    get the device report

    -

    -
    -
    /sdrdaemon/report
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/report"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceReport result = apiInstance.daemonReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceReport result = apiInstance.daemonReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonReportGetWithCompletionHandler: 
    -              ^(DeviceReport output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonReportGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonReportGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceReport result = apiInstance.daemonReportGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonReportGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonReportGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonReportGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonReportGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonReportGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_report_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonReportGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return device report

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    @@ -6984,1244 +9361,6 @@ except ApiException as e:

    -
    -
    -
    -

    daemonSettingsGet

    -

    -
    -
    -
    -

    -

    Get device settings

    -

    -
    -
    /sdrdaemon/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonSettingsGetWithCompletionHandler: 
    -              ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonSettingsGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonSettingsGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonSettingsGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonSettingsGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonSettingsGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonSettingsGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonSettingsGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonSettingsGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_settings_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonSettingsGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success returns current settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonSettingsPatch

    -

    -
    -
    -
    -

    -

    Apply settings differentially (no force)

    -

    -
    -
    /sdrdaemon/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PATCH "http://localhost/sdrdaemon/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    DeviceSettings *body = ; // Device settings to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonSettingsPatchWith:body
    -              completionHandler: ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {DeviceSettings} Device settings to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonSettingsPatch(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonSettingsPatchExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonSettingsPatch(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonSettingsPatch: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // DeviceSettings | Device settings to apply
    -
    -try {
    -    $result = $api_instance->daemonSettingsPatch($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonSettingsPatch: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonSettingsPatch(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonSettingsPatch: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # DeviceSettings | Device settings to apply
    -
    -try: 
    -    api_response = api_instance.daemon_settings_patch(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonSettingsPatch: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonSettingsPut

    -

    -
    -
    -
    -

    -

    Apply all settings unconditionally (force)

    -

    -
    -
    /sdrdaemon/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PUT "http://localhost/sdrdaemon/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    DeviceSettings *body = ; // Device settings to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonSettingsPutWith:body
    -              completionHandler: ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {DeviceSettings} Device settings to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonSettingsPut(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonSettingsPutExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonSettingsPut(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonSettingsPut: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // DeviceSettings | Device settings to apply
    -
    -try {
    -    $result = $api_instance->daemonSettingsPut($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonSettingsPut: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonSettingsPut(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonSettingsPut: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # DeviceSettings | Device settings to apply
    -
    -try: 
    -    api_response = api_instance.daemon_settings_put(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonSettingsPut: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -

    DeviceSet

    @@ -27073,7 +28212,7 @@ except ApiException as e:
    - Generated 2018-08-19T11:32:09.545+02:00 + Generated 2018-08-23T00:21:49.115+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp index 693082006..1a8a6f613 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp @@ -28,6 +28,382 @@ SWGDaemonApi::SWGDaemonApi(QString host, QString basePath) { this->basePath = basePath; } +void +SWGDaemonApi::daemonDataSettingsGet() { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/data/settings"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "GET"); + + + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDaemonApi::daemonDataSettingsGetCallback); + + worker->execute(&input); +} + +void +SWGDaemonApi::daemonDataSettingsGetCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGSDRDaemonDataSettings* output = static_cast(create(json, QString("SWGSDRDaemonDataSettings"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit daemonDataSettingsGetSignal(output); + } else { + emit daemonDataSettingsGetSignalE(output, error_type, error_str); + emit daemonDataSettingsGetSignalEFull(worker, error_type, error_str); + } +} + +void +SWGDaemonApi::daemonDataSettingsPatch(SWGSDRDaemonDataSettings& body) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/data/settings"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "PATCH"); + + + + QString output = body.asJson(); + input.request_body.append(output); + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDaemonApi::daemonDataSettingsPatchCallback); + + worker->execute(&input); +} + +void +SWGDaemonApi::daemonDataSettingsPatchCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGSDRDaemonDataSettings* output = static_cast(create(json, QString("SWGSDRDaemonDataSettings"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit daemonDataSettingsPatchSignal(output); + } else { + emit daemonDataSettingsPatchSignalE(output, error_type, error_str); + emit daemonDataSettingsPatchSignalEFull(worker, error_type, error_str); + } +} + +void +SWGDaemonApi::daemonDataSettingsPut(SWGSDRDaemonDataSettings& body) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/data/settings"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "PUT"); + + + + QString output = body.asJson(); + input.request_body.append(output); + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDaemonApi::daemonDataSettingsPutCallback); + + worker->execute(&input); +} + +void +SWGDaemonApi::daemonDataSettingsPutCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGSDRDaemonDataSettings* output = static_cast(create(json, QString("SWGSDRDaemonDataSettings"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit daemonDataSettingsPutSignal(output); + } else { + emit daemonDataSettingsPutSignalE(output, error_type, error_str); + emit daemonDataSettingsPutSignalEFull(worker, error_type, error_str); + } +} + +void +SWGDaemonApi::daemonDeviceReportGet() { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/device/report"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "GET"); + + + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDaemonApi::daemonDeviceReportGetCallback); + + worker->execute(&input); +} + +void +SWGDaemonApi::daemonDeviceReportGetCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGDeviceReport* output = static_cast(create(json, QString("SWGDeviceReport"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit daemonDeviceReportGetSignal(output); + } else { + emit daemonDeviceReportGetSignalE(output, error_type, error_str); + emit daemonDeviceReportGetSignalEFull(worker, error_type, error_str); + } +} + +void +SWGDaemonApi::daemonDeviceSettingsGet() { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/device/settings"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "GET"); + + + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDaemonApi::daemonDeviceSettingsGetCallback); + + worker->execute(&input); +} + +void +SWGDaemonApi::daemonDeviceSettingsGetCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit daemonDeviceSettingsGetSignal(output); + } else { + emit daemonDeviceSettingsGetSignalE(output, error_type, error_str); + emit daemonDeviceSettingsGetSignalEFull(worker, error_type, error_str); + } +} + +void +SWGDaemonApi::daemonDeviceSettingsPatch(SWGDeviceSettings& body) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/device/settings"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "PATCH"); + + + + QString output = body.asJson(); + input.request_body.append(output); + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDaemonApi::daemonDeviceSettingsPatchCallback); + + worker->execute(&input); +} + +void +SWGDaemonApi::daemonDeviceSettingsPatchCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit daemonDeviceSettingsPatchSignal(output); + } else { + emit daemonDeviceSettingsPatchSignalE(output, error_type, error_str); + emit daemonDeviceSettingsPatchSignalEFull(worker, error_type, error_str); + } +} + +void +SWGDaemonApi::daemonDeviceSettingsPut(SWGDeviceSettings& body) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/device/settings"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "PUT"); + + + + QString output = body.asJson(); + input.request_body.append(output); + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDaemonApi::daemonDeviceSettingsPutCallback); + + worker->execute(&input); +} + +void +SWGDaemonApi::daemonDeviceSettingsPutCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit daemonDeviceSettingsPutSignal(output); + } else { + emit daemonDeviceSettingsPutSignalE(output, error_type, error_str); + emit daemonDeviceSettingsPutSignalEFull(worker, error_type, error_str); + } +} + void SWGDaemonApi::daemonInstanceLoggingGet() { QString fullPath; @@ -187,58 +563,6 @@ SWGDaemonApi::daemonInstanceSummaryCallback(SWGHttpRequestWorker * worker) { } } -void -SWGDaemonApi::daemonReportGet() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/report"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonReportGetCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonReportGetCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceReport* output = static_cast(create(json, QString("SWGDeviceReport"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonReportGetSignal(output); - } else { - emit daemonReportGetSignalE(output, error_type, error_str); - emit daemonReportGetSignalEFull(worker, error_type, error_str); - } -} - void SWGDaemonApi::daemonRunDelete() { QString fullPath; @@ -395,167 +719,5 @@ SWGDaemonApi::daemonRunPostCallback(SWGHttpRequestWorker * worker) { } } -void -SWGDaemonApi::daemonSettingsGet() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonSettingsGetCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonSettingsGetCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonSettingsGetSignal(output); - } else { - emit daemonSettingsGetSignalE(output, error_type, error_str); - emit daemonSettingsGetSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonSettingsPatch(SWGDeviceSettings& body) { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "PATCH"); - - - - QString output = body.asJson(); - input.request_body.append(output); - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonSettingsPatchCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonSettingsPatchCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonSettingsPatchSignal(output); - } else { - emit daemonSettingsPatchSignalE(output, error_type, error_str); - emit daemonSettingsPatchSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonSettingsPut(SWGDeviceSettings& body) { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "PUT"); - - - - QString output = body.asJson(); - input.request_body.append(output); - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonSettingsPutCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonSettingsPutCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonSettingsPutSignal(output); - } else { - emit daemonSettingsPutSignalE(output, error_type, error_str); - emit daemonSettingsPutSignalEFull(worker, error_type, error_str); - } -} - } diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h index 86f4a3359..19cbba2e6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h @@ -21,6 +21,7 @@ #include "SWGDeviceState.h" #include "SWGErrorResponse.h" #include "SWGLoggingInfo.h" +#include "SWGSDRDaemonDataSettings.h" #include @@ -38,62 +39,77 @@ public: QString basePath; QMap defaultHeaders; + void daemonDataSettingsGet(); + void daemonDataSettingsPatch(SWGSDRDaemonDataSettings& body); + void daemonDataSettingsPut(SWGSDRDaemonDataSettings& body); + void daemonDeviceReportGet(); + void daemonDeviceSettingsGet(); + void daemonDeviceSettingsPatch(SWGDeviceSettings& body); + void daemonDeviceSettingsPut(SWGDeviceSettings& body); void daemonInstanceLoggingGet(); void daemonInstanceLoggingPut(SWGLoggingInfo& body); void daemonInstanceSummary(); - void daemonReportGet(); void daemonRunDelete(); void daemonRunGet(); void daemonRunPost(); - void daemonSettingsGet(); - void daemonSettingsPatch(SWGDeviceSettings& body); - void daemonSettingsPut(SWGDeviceSettings& body); private: + void daemonDataSettingsGetCallback (SWGHttpRequestWorker * worker); + void daemonDataSettingsPatchCallback (SWGHttpRequestWorker * worker); + void daemonDataSettingsPutCallback (SWGHttpRequestWorker * worker); + void daemonDeviceReportGetCallback (SWGHttpRequestWorker * worker); + void daemonDeviceSettingsGetCallback (SWGHttpRequestWorker * worker); + void daemonDeviceSettingsPatchCallback (SWGHttpRequestWorker * worker); + void daemonDeviceSettingsPutCallback (SWGHttpRequestWorker * worker); void daemonInstanceLoggingGetCallback (SWGHttpRequestWorker * worker); void daemonInstanceLoggingPutCallback (SWGHttpRequestWorker * worker); void daemonInstanceSummaryCallback (SWGHttpRequestWorker * worker); - void daemonReportGetCallback (SWGHttpRequestWorker * worker); void daemonRunDeleteCallback (SWGHttpRequestWorker * worker); void daemonRunGetCallback (SWGHttpRequestWorker * worker); void daemonRunPostCallback (SWGHttpRequestWorker * worker); - void daemonSettingsGetCallback (SWGHttpRequestWorker * worker); - void daemonSettingsPatchCallback (SWGHttpRequestWorker * worker); - void daemonSettingsPutCallback (SWGHttpRequestWorker * worker); signals: + void daemonDataSettingsGetSignal(SWGSDRDaemonDataSettings* summary); + void daemonDataSettingsPatchSignal(SWGSDRDaemonDataSettings* summary); + void daemonDataSettingsPutSignal(SWGSDRDaemonDataSettings* summary); + void daemonDeviceReportGetSignal(SWGDeviceReport* summary); + void daemonDeviceSettingsGetSignal(SWGDeviceSettings* summary); + void daemonDeviceSettingsPatchSignal(SWGDeviceSettings* summary); + void daemonDeviceSettingsPutSignal(SWGDeviceSettings* summary); void daemonInstanceLoggingGetSignal(SWGLoggingInfo* summary); void daemonInstanceLoggingPutSignal(SWGLoggingInfo* summary); void daemonInstanceSummarySignal(SWGDaemonSummaryResponse* summary); - void daemonReportGetSignal(SWGDeviceReport* summary); void daemonRunDeleteSignal(SWGDeviceState* summary); void daemonRunGetSignal(SWGDeviceState* summary); void daemonRunPostSignal(SWGDeviceState* summary); - void daemonSettingsGetSignal(SWGDeviceSettings* summary); - void daemonSettingsPatchSignal(SWGDeviceSettings* summary); - void daemonSettingsPutSignal(SWGDeviceSettings* summary); + void daemonDataSettingsGetSignalE(SWGSDRDaemonDataSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDataSettingsPatchSignalE(SWGSDRDaemonDataSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDataSettingsPutSignalE(SWGSDRDaemonDataSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDeviceReportGetSignalE(SWGDeviceReport* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDeviceSettingsGetSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDeviceSettingsPatchSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDeviceSettingsPutSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonInstanceLoggingGetSignalE(SWGLoggingInfo* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonInstanceLoggingPutSignalE(SWGLoggingInfo* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonInstanceSummarySignalE(SWGDaemonSummaryResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonReportGetSignalE(SWGDeviceReport* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonRunDeleteSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonRunGetSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonRunPostSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonSettingsGetSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonSettingsPatchSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonSettingsPutSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDataSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDataSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDataSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDeviceReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDeviceSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDeviceSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonDeviceSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonInstanceLoggingGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonInstanceLoggingPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonInstanceSummarySignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonRunDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonRunGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonRunPostSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); }; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 11a5c1580..915d4b0c9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -89,6 +89,7 @@ #include "SWGRDSReport_altFrequencies.h" #include "SWGRtlSdrReport.h" #include "SWGRtlSdrSettings.h" +#include "SWGSDRDaemonDataSettings.h" #include "SWGSDRPlayReport.h" #include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSinkReport.h" @@ -340,6 +341,9 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } + if(QString("SWGSDRDaemonDataSettings").compare(type) == 0) { + return new SWGSDRDaemonDataSettings(); + } if(QString("SWGSDRPlayReport").compare(type) == 0) { return new SWGSDRPlayReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.cpp new file mode 100644 index 000000000..1a8925524 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.cpp @@ -0,0 +1,171 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRDaemonDataSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRDaemonDataSettings::SWGSDRDaemonDataSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRDaemonDataSettings::SWGSDRDaemonDataSettings() { + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + data_address = nullptr; + m_data_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + tx_delay = 0; + m_tx_delay_isSet = false; +} + +SWGSDRDaemonDataSettings::~SWGSDRDaemonDataSettings() { + this->cleanup(); +} + +void +SWGSDRDaemonDataSettings::init() { + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + data_address = new QString(""); + m_data_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + tx_delay = 0; + m_tx_delay_isSet = false; +} + +void +SWGSDRDaemonDataSettings::cleanup() { + + if(data_address != nullptr) { + delete data_address; + } + + +} + +SWGSDRDaemonDataSettings* +SWGSDRDaemonDataSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRDaemonDataSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&tx_delay, pJson["txDelay"], "qint32", ""); + +} + +QString +SWGSDRDaemonDataSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRDaemonDataSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_nb_fec_blocks_isSet){ + obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); + } + if(data_address != nullptr && *data_address != QString("")){ + toJsonValue(QString("dataAddress"), data_address, obj, QString("QString")); + } + if(m_data_port_isSet){ + obj->insert("dataPort", QJsonValue(data_port)); + } + if(m_tx_delay_isSet){ + obj->insert("txDelay", QJsonValue(tx_delay)); + } + + return obj; +} + +qint32 +SWGSDRDaemonDataSettings::getNbFecBlocks() { + return nb_fec_blocks; +} +void +SWGSDRDaemonDataSettings::setNbFecBlocks(qint32 nb_fec_blocks) { + this->nb_fec_blocks = nb_fec_blocks; + this->m_nb_fec_blocks_isSet = true; +} + +QString* +SWGSDRDaemonDataSettings::getDataAddress() { + return data_address; +} +void +SWGSDRDaemonDataSettings::setDataAddress(QString* data_address) { + this->data_address = data_address; + this->m_data_address_isSet = true; +} + +qint32 +SWGSDRDaemonDataSettings::getDataPort() { + return data_port; +} +void +SWGSDRDaemonDataSettings::setDataPort(qint32 data_port) { + this->data_port = data_port; + this->m_data_port_isSet = true; +} + +qint32 +SWGSDRDaemonDataSettings::getTxDelay() { + return tx_delay; +} +void +SWGSDRDaemonDataSettings::setTxDelay(qint32 tx_delay) { + this->tx_delay = tx_delay; + this->m_tx_delay_isSet = true; +} + + +bool +SWGSDRDaemonDataSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} + if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} + if(m_data_port_isSet){ isObjectUpdated = true; break;} + if(m_tx_delay_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.h new file mode 100644 index 000000000..a0edbaf28 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.h @@ -0,0 +1,77 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRDaemonDataSettings.h + * + * Data handling details for SDRDaemon + */ + +#ifndef SWGSDRDaemonDataSettings_H_ +#define SWGSDRDaemonDataSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRDaemonDataSettings: public SWGObject { +public: + SWGSDRDaemonDataSettings(); + SWGSDRDaemonDataSettings(QString* json); + virtual ~SWGSDRDaemonDataSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRDaemonDataSettings* fromJson(QString &jsonString) override; + + qint32 getNbFecBlocks(); + void setNbFecBlocks(qint32 nb_fec_blocks); + + QString* getDataAddress(); + void setDataAddress(QString* data_address); + + qint32 getDataPort(); + void setDataPort(qint32 data_port); + + qint32 getTxDelay(); + void setTxDelay(qint32 tx_delay); + + + virtual bool isSet() override; + +private: + qint32 nb_fec_blocks; + bool m_nb_fec_blocks_isSet; + + QString* data_address; + bool m_data_address_isSet; + + qint32 data_port; + bool m_data_port_isSet; + + qint32 tx_delay; + bool m_tx_delay_isSet; + +}; + +} + +#endif /* SWGSDRDaemonDataSettings_H_ */ From 7e22fe20f9e3509007071d31dac830fdd680cb83 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 23 Aug 2018 08:41:41 +0200 Subject: [PATCH 650/956] SDRdaemon channel sink: added settings and message handling --- sdrdaemon/CMakeLists.txt | 2 + sdrdaemon/channel/sdrdaemonchannelsink.cpp | 55 ++++++++++- sdrdaemon/channel/sdrdaemonchannelsink.h | 27 +++++ .../channel/sdrdaemonchannelsinksettings.cpp | 99 +++++++++++++++++++ .../channel/sdrdaemonchannelsinksettings.h | 43 ++++++++ 5 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp create mode 100644 sdrdaemon/channel/sdrdaemonchannelsinksettings.h diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index cf8407a3e..c7f9e82c7 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -9,6 +9,7 @@ set(sdrdaemon_SOURCES channel/sdrdaemonchannelsource.cpp channel/sdrdaemondataqueue.cpp channel/sdrdaemonchannelsinkthread.cpp + channel/sdrdaemonchannelsinksettings.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -24,6 +25,7 @@ set(sdrdaemon_HEADERS channel/sdrdaemondataqueue.h channel/sdrdaemondatablock.h channel/sdrdaemonchannelsinkthread.h + channel/sdrdaemonchannelsinksettings.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 9b4a270fc..62d6c5d8d 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -33,6 +33,8 @@ #include "channel/sdrdaemonchannelsinkthread.h" #include "sdrdaemonchannelsink.h" +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSink::MsgConfigureSDRDaemonChannelSink, Message) + const QString SDRDaemonChannelSink::m_channelIdURI = "sdrangel.channel.sdrdaemonsink"; const QString SDRDaemonChannelSink::m_channelId = "SDRDaemonChannelSink"; @@ -247,6 +249,14 @@ bool SDRDaemonChannelSink::handleMessage(const Message& cmd __attribute__((unuse return true; } + else if (MsgConfigureSDRDaemonChannelSink::match(cmd)) + { + MsgConfigureSDRDaemonChannelSink& cfg = (MsgConfigureSDRDaemonChannelSink&) cmd; + qDebug() << "SDRDaemonChannelSink::handleMessage: MsgConfigureSDRDaemonChannelSink"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } else { return false; @@ -255,11 +265,50 @@ bool SDRDaemonChannelSink::handleMessage(const Message& cmd __attribute__((unuse QByteArray SDRDaemonChannelSink::serialize() const { - SimpleSerializer s(1); - return s.final(); + return m_settings.serialize(); } bool SDRDaemonChannelSink::deserialize(const QByteArray& data __attribute__((unused))) { - return false; + if (m_settings.deserialize(data)) + { + MsgConfigureSDRDaemonChannelSink *msg = MsgConfigureSDRDaemonChannelSink::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureSDRDaemonChannelSink *msg = MsgConfigureSDRDaemonChannelSink::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void SDRDaemonChannelSink::applySettings(const SDRDaemonChannelSinkSettings& settings, bool force) +{ + qDebug() << "SDRDaemonChannelSink::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; } diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index 11c7a7841..06b8ebbe4 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -31,6 +31,7 @@ #include "channel/channelsinkapi.h" #include "channel/sdrdaemondataqueue.h" #include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemonchannelsinksettings.h" class DeviceSourceAPI; class ThreadedBasebandSampleSink; @@ -40,6 +41,29 @@ class SDRDaemonChannelSinkThread; class SDRDaemonChannelSink : public BasebandSampleSink, public ChannelSinkAPI { Q_OBJECT public: + class MsgConfigureSDRDaemonChannelSink : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SDRDaemonChannelSinkSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSDRDaemonChannelSink* create(const SDRDaemonChannelSinkSettings& settings, bool force) + { + return new MsgConfigureSDRDaemonChannelSink(settings, force); + } + + private: + SDRDaemonChannelSinkSettings m_settings; + bool m_force; + + MsgConfigureSDRDaemonChannelSink(const SDRDaemonChannelSinkSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI); virtual ~SDRDaemonChannelSink(); virtual void destroy() { delete this; } @@ -76,6 +100,7 @@ private: DownChannelizer* m_channelizer; bool m_running; + SDRDaemonChannelSinkSettings m_settings; SDRDaemonDataQueue m_dataQueue; SDRDaemonChannelSinkThread *m_sinkThread; CM256 m_cm256; @@ -96,6 +121,8 @@ private: int m_txDelay; QString m_dataAddress; uint16_t m_dataPort; + + void applySettings(const SDRDaemonChannelSinkSettings& settings, bool force = false); }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp b/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp new file mode 100644 index 000000000..7a5a2e57c --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp @@ -0,0 +1,99 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 + +#include "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "channel/sdrdaemonchannelsinksettings.h" + +SDRDaemonChannelSinkSettings::SDRDaemonChannelSinkSettings() +{ + resetToDefaults(); +} + +void SDRDaemonChannelSinkSettings::resetToDefaults() +{ + m_nbFECBlocks = 0; + m_txDelay = 100; + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; +} + +QByteArray SDRDaemonChannelSinkSettings::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 SDRDaemonChannelSinkSettings::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/sdrdaemon/channel/sdrdaemonchannelsinksettings.h b/sdrdaemon/channel/sdrdaemonchannelsinksettings.h new file mode 100644 index 000000000..ff9be3f25 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsinksettings.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 SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINKSETTINGS_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINKSETTINGS_H_ + +#include + +class Serializable; + +struct SDRDaemonChannelSinkSettings +{ + uint16_t m_nbFECBlocks; + uint32_t m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; + + SDRDaemonChannelSinkSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINKSETTINGS_H_ */ From 0ace2e949970ead41899c427259cc6e765760d40 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 23 Aug 2018 16:06:47 +0200 Subject: [PATCH 651/956] SDRDaemon: replaced specific channel data settings by generic channel data settings --- sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 447 +++++++++--------- .../doc/swagger/include/SDRDaemonChannel.yaml | 15 + .../resources/webapi/doc/swagger/swagger.yaml | 46 +- sdrbase/webapi/webapirequestmapper.cpp | 9 + sdrdaemon/webapi/webapiadapterdaemon.cpp | 14 +- sdrdaemon/webapi/webapiadapterdaemon.h | 14 +- sdrdaemon/webapi/webapirequestmapper.cpp | 338 ++++++++++--- sdrdaemon/webapi/webapirequestmapper.h | 7 +- .../api/swagger/include/SDRDaemonChannel.yaml | 15 + swagger/sdrangel/api/swagger/swagger.yaml | 46 +- swagger/sdrangel/code/html2/index.html | 447 +++++++++--------- .../code/qt5/client/SWGChannelSettings.cpp | 23 + .../code/qt5/client/SWGChannelSettings.h | 7 + .../sdrangel/code/qt5/client/SWGDaemonApi.cpp | 48 +- .../sdrangel/code/qt5/client/SWGDaemonApi.h | 32 +- .../code/qt5/client/SWGModelFactory.h | 6 +- ...gs.cpp => SWGSDRDaemonChannelSettings.cpp} | 40 +- ...ttings.h => SWGSDRDaemonChannelSettings.h} | 18 +- 19 files changed, 921 insertions(+), 652 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannel.yaml create mode 100644 swagger/sdrangel/api/swagger/include/SDRDaemonChannel.yaml rename swagger/sdrangel/code/qt5/client/{SWGSDRDaemonDataSettings.cpp => SWGSDRDaemonChannelSettings.cpp} (81%) rename swagger/sdrangel/code/qt5/client/{SWGSDRDaemonDataSettings.h => SWGSDRDaemonChannelSettings.h} (85%) diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 3af73f874..f5f346932 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -21,6 +21,7 @@ webapi/doc/swagger/include/Perseus.yaml webapi/doc/swagger/include/PlutoSdr.yaml webapi/doc/swagger/include/RtlSdr.yaml + webapi/doc/swagger/include/SDRDaemonChannel.yaml webapi/doc/swagger/include/SDRDaemonSource.yaml webapi/doc/swagger/include/SDRDaemonSink.yaml webapi/doc/swagger/include/SDRPlay.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index d1c41a364..20c65c84f 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1467,6 +1467,9 @@ margin-bottom: 20px; "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" }, + "SDRDaemonChannelSettings" : { + "$ref" : "#/definitions/SDRDaemonChannelSettings" + }, "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" }, @@ -3143,7 +3146,7 @@ margin-bottom: 20px; }, "description" : "RTLSDR" }; - defs.SDRDaemonDataSettings = { + defs.SDRDaemonChannelSettings = { "properties" : { "nbFECBlocks" : { "type" : "integer", @@ -4024,14 +4027,14 @@ margin-bottom: 20px;
  • -
  • - daemonDataSettingsGet +
  • + daemonChannelSettingsGet
  • -
  • - daemonDataSettingsPatch +
  • + daemonChannelSettingsPatch
  • -
  • - daemonDataSettingsPut +
  • + daemonChannelSettingsPut
  • - -
  • - daemonChannelReportGet -
  • -
  • - daemonChannelSettingsGet -
  • -
  • - daemonChannelSettingsPatch -
  • -
  • - daemonChannelSettingsPut -
  • -
  • - daemonDeviceReportGet -
  • -
  • - daemonDeviceSettingsGet -
  • -
  • - daemonDeviceSettingsPatch -
  • -
  • - daemonDeviceSettingsPut -
  • -
  • - daemonInstanceLoggingGet -
  • -
  • - daemonInstanceLoggingPut -
  • -
  • - daemonInstanceSummary -
  • -
  • - daemonRunDelete -
  • -
  • - daemonRunGet -
  • -
  • - daemonRunPost -
  • devicesetChannelDelete @@ -4303,5561 +4216,6 @@ margin-bottom: 20px;
    -
    -

    Daemon

    -
    -
    -
    -

    daemonChannelReportGet

    -

    -
    -
    -
    -

    -

    get the channel report

    -

    -
    -
    /sdrdaemon/channel/report
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/channel/report"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            ChannelReport result = apiInstance.daemonChannelReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            ChannelReport result = apiInstance.daemonChannelReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonChannelReportGetWithCompletionHandler: 
    -              ^(ChannelReport output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonChannelReportGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonChannelReportGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                ChannelReport result = apiInstance.daemonChannelReportGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonChannelReportGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonChannelReportGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonChannelReportGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonChannelReportGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonChannelReportGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_channel_report_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonChannelReportGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return channel report

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set or channel index

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device or channel not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonChannelSettingsGet

    -

    -
    -
    -
    -

    -

    Get channel handling details

    -

    -
    -
    /sdrdaemon/channel/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/channel/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonChannelSettingsGetWithCompletionHandler: 
    -              ^(ChannelSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonChannelSettingsGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonChannelSettingsGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                ChannelSettings result = apiInstance.daemonChannelSettingsGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonChannelSettingsGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonChannelSettingsGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonChannelSettingsGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonChannelSettingsGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_channel_settings_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonChannelSettingsGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return channel settings

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonChannelSettingsPatch

    -

    -
    -
    -
    -

    -

    Apply channel handling details differentially (no force)

    -

    -
    -
    /sdrdaemon/channel/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PATCH "http://localhost/sdrdaemon/channel/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        ChannelSettings body = ; // ChannelSettings | Data handling detail to apply
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        ChannelSettings body = ; // ChannelSettings | Data handling detail to apply
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    ChannelSettings *body = ; // Data handling detail to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonChannelSettingsPatchWith:body
    -              completionHandler: ^(ChannelSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {ChannelSettings} Data handling detail to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonChannelSettingsPatch(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonChannelSettingsPatchExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new ChannelSettings(); // ChannelSettings | Data handling detail to apply
    -
    -            try
    -            {
    -                ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsPatch: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // ChannelSettings | Data handling detail to apply
    -
    -try {
    -    $result = $api_instance->daemonChannelSettingsPatch($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonChannelSettingsPatch: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::ChannelSettings->new(); # ChannelSettings | Data handling detail to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonChannelSettingsPatch(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonChannelSettingsPatch: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # ChannelSettings | Data handling detail to apply
    -
    -try: 
    -    api_response = api_instance.daemon_channel_settings_patch(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonChannelSettingsPatch: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonChannelSettingsPut

    -

    -
    -
    -
    -

    -

    Apply channel handling details unconditionally (force)

    -

    -
    -
    /sdrdaemon/channel/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PUT "http://localhost/sdrdaemon/channel/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        ChannelSettings body = ; // ChannelSettings | Channel handling details to apply
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        ChannelSettings body = ; // ChannelSettings | Channel handling details to apply
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    ChannelSettings *body = ; // Channel handling details to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonChannelSettingsPutWith:body
    -              completionHandler: ^(ChannelSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {ChannelSettings} Channel handling details to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonChannelSettingsPut(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonChannelSettingsPutExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new ChannelSettings(); // ChannelSettings | Channel handling details to apply
    -
    -            try
    -            {
    -                ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsPut: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // ChannelSettings | Channel handling details to apply
    -
    -try {
    -    $result = $api_instance->daemonChannelSettingsPut($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonChannelSettingsPut: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::ChannelSettings->new(); # ChannelSettings | Channel handling details to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonChannelSettingsPut(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonChannelSettingsPut: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # ChannelSettings | Channel handling details to apply
    -
    -try: 
    -    api_response = api_instance.daemon_channel_settings_put(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonChannelSettingsPut: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonDeviceReportGet

    -

    -
    -
    -
    -

    -

    get the device report

    -

    -
    -
    /sdrdaemon/device/report
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/device/report"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceReport result = apiInstance.daemonDeviceReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceReport result = apiInstance.daemonDeviceReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonDeviceReportGetWithCompletionHandler: 
    -              ^(DeviceReport output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonDeviceReportGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonDeviceReportGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceReport result = apiInstance.daemonDeviceReportGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonDeviceReportGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonDeviceReportGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDeviceReportGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonDeviceReportGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDeviceReportGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_device_report_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDeviceReportGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return device report

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonDeviceSettingsGet

    -

    -
    -
    -
    -

    -

    Get device settings

    -

    -
    -
    /sdrdaemon/device/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/device/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonDeviceSettingsGetWithCompletionHandler: 
    -              ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonDeviceSettingsGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonDeviceSettingsGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonDeviceSettingsGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDeviceSettingsGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonDeviceSettingsGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDeviceSettingsGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_device_settings_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDeviceSettingsGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success returns current settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonDeviceSettingsPatch

    -

    -
    -
    -
    -

    -

    Apply settings differentially (no force)

    -

    -
    -
    /sdrdaemon/device/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PATCH "http://localhost/sdrdaemon/device/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    DeviceSettings *body = ; // Device settings to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonDeviceSettingsPatchWith:body
    -              completionHandler: ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {DeviceSettings} Device settings to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonDeviceSettingsPatch(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonDeviceSettingsPatchExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsPatch: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // DeviceSettings | Device settings to apply
    -
    -try {
    -    $result = $api_instance->daemonDeviceSettingsPatch($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDeviceSettingsPatch: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonDeviceSettingsPatch(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDeviceSettingsPatch: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # DeviceSettings | Device settings to apply
    -
    -try: 
    -    api_response = api_instance.daemon_device_settings_patch(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDeviceSettingsPatch: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonDeviceSettingsPut

    -

    -
    -
    -
    -

    -

    Apply all settings unconditionally (force)

    -

    -
    -
    /sdrdaemon/device/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PUT "http://localhost/sdrdaemon/device/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    DeviceSettings *body = ; // Device settings to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonDeviceSettingsPutWith:body
    -              completionHandler: ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {DeviceSettings} Device settings to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonDeviceSettingsPut(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonDeviceSettingsPutExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsPut: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // DeviceSettings | Device settings to apply
    -
    -try {
    -    $result = $api_instance->daemonDeviceSettingsPut($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDeviceSettingsPut: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonDeviceSettingsPut(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDeviceSettingsPut: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # DeviceSettings | Device settings to apply
    -
    -try: 
    -    api_response = api_instance.daemon_device_settings_put(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDeviceSettingsPut: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonInstanceLoggingGet

    -

    -
    -
    -
    -

    -

    Get logging information for this instance

    -

    -
    -
    /sdrdaemon/logging
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/logging"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            LoggingInfo result = apiInstance.daemonInstanceLoggingGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceLoggingGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            LoggingInfo result = apiInstance.daemonInstanceLoggingGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceLoggingGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonInstanceLoggingGetWithCompletionHandler: 
    -              ^(LoggingInfo output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonInstanceLoggingGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonInstanceLoggingGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                LoggingInfo result = apiInstance.daemonInstanceLoggingGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonInstanceLoggingGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonInstanceLoggingGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonInstanceLoggingGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonInstanceLoggingGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonInstanceLoggingGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_instance_logging_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonInstanceLoggingGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - Success

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonInstanceLoggingPut

    -

    -
    -
    -
    -

    -

    Change logging parmeters for this instance

    -

    -
    -
    /sdrdaemon/logging
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PUT "http://localhost/sdrdaemon/logging"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        LoggingInfo body = ; // LoggingInfo | Logging information
    -        try {
    -            LoggingInfo result = apiInstance.daemonInstanceLoggingPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceLoggingPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        LoggingInfo body = ; // LoggingInfo | Logging information
    -        try {
    -            LoggingInfo result = apiInstance.daemonInstanceLoggingPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceLoggingPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    LoggingInfo *body = ; // Logging information
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonInstanceLoggingPutWith:body
    -              completionHandler: ^(LoggingInfo output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {LoggingInfo} Logging information
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonInstanceLoggingPut(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonInstanceLoggingPutExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new LoggingInfo(); // LoggingInfo | Logging information
    -
    -            try
    -            {
    -                LoggingInfo result = apiInstance.daemonInstanceLoggingPut(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonInstanceLoggingPut: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // LoggingInfo | Logging information
    -
    -try {
    -    $result = $api_instance->daemonInstanceLoggingPut($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonInstanceLoggingPut: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::LoggingInfo->new(); # LoggingInfo | Logging information
    -
    -eval { 
    -    my $result = $api_instance->daemonInstanceLoggingPut(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonInstanceLoggingPut: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # LoggingInfo | Logging information
    -
    -try: 
    -    api_response = api_instance.daemon_instance_logging_put(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonInstanceLoggingPut: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - Return new data on success

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid data

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonInstanceSummary

    -

    -
    -
    -
    -

    -

    get SDRdaemon summary information

    -

    -
    -
    /sdrdaemon
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DaemonSummaryResponse result = apiInstance.daemonInstanceSummary();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceSummary");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DaemonSummaryResponse result = apiInstance.daemonInstanceSummary();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceSummary");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonInstanceSummaryWithCompletionHandler: 
    -              ^(DaemonSummaryResponse output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonInstanceSummary(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonInstanceSummaryExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DaemonSummaryResponse result = apiInstance.daemonInstanceSummary();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonInstanceSummary: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonInstanceSummary();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonInstanceSummary: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonInstanceSummary();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonInstanceSummary: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_instance_summary()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonInstanceSummary: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - Success

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonRunDelete

    -

    -
    -
    -
    -

    -

    stop device

    -

    -
    -
    /sdrdaemon/run
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X DELETE "http://localhost/sdrdaemon/run"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunDelete();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunDelete");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunDelete();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunDelete");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonRunDeleteWithCompletionHandler: 
    -              ^(DeviceState output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonRunDelete(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonRunDeleteExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceState result = apiInstance.daemonRunDelete();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonRunDelete: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonRunDelete();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonRunDelete: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonRunDelete();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonRunDelete: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_run_delete()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonRunDelete: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return state before change

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set index

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonRunGet

    -

    -
    -
    -
    -

    -

    get device run status

    -

    -
    -
    /sdrdaemon/run
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/run"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonRunGetWithCompletionHandler: 
    -              ^(DeviceState output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonRunGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonRunGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceState result = apiInstance.daemonRunGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonRunGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonRunGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonRunGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonRunGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonRunGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_run_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonRunGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return current state

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set index

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonRunPost

    -

    -
    -
    -
    -

    -

    start device

    -

    -
    -
    /sdrdaemon/run
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X POST "http://localhost/sdrdaemon/run"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunPost();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunPost");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunPost();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunPost");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonRunPostWithCompletionHandler: 
    -              ^(DeviceState output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonRunPost(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonRunPostExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceState result = apiInstance.daemonRunPost();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonRunPost: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonRunPost();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonRunPost: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonRunPost();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonRunPost: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_run_post()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonRunPost: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return state before change

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set index

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -

    DeviceSet

    @@ -28708,7 +23066,7 @@ except ApiException as e:
    - Generated 2018-09-11T14:35:37.698+02:00 + Generated 2018-09-11T14:47:33.212+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 68c4791d3..a59d3def7 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1,11 +1,11 @@ swagger: "2.0" info: description: > - This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube - + This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube + --- - Limitations and specifcities: - + Limitations and specifcities: + * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. @@ -20,9 +20,9 @@ info: contact: url: "https://github.com/f4exb/sdrangel" email: "f4exb06@gmail.com" -# basePath prefixes all resource paths +# basePath prefixes all resource paths basePath: / -# +# schemes: # tip: remove http to make production-grade - http @@ -32,7 +32,7 @@ consumes: # format of the responses to the client (Accepts) produces: - application/json - + paths: /sdrangel: @@ -74,7 +74,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/devices: x-swagger-router-controller: instance get: @@ -97,7 +97,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/channels: x-swagger-router-controller: instance get: @@ -120,7 +120,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/logging: x-swagger-router-controller: instance get: @@ -142,7 +142,7 @@ paths: operationId: instanceLoggingPut tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -164,7 +164,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/audio: x-swagger-router-controller: instance get: @@ -181,7 +181,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/audio/input/parameters: x-swagger-router-controller: instance patch: @@ -189,7 +189,7 @@ paths: operationId: instanceAudioInputPatch tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -216,7 +216,7 @@ paths: operationId: instanceAudioInputDelete tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -238,8 +238,8 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - - + + /sdrangel/audio/input/cleanup: x-swagger-router-controller: instance patch: @@ -264,7 +264,7 @@ paths: operationId: instanceAudioOutputPatch tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -291,7 +291,7 @@ paths: operationId: instanceAudioOutputDelete tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -313,7 +313,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/audio/output/cleanup: x-swagger-router-controller: instance patch: @@ -330,7 +330,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/location: x-swagger-router-controller: instance get: @@ -352,7 +352,7 @@ paths: operationId: instanceLocationPut tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -372,7 +372,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/dvserial: x-swagger-router-controller: instance get: @@ -409,7 +409,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/presets: x-swagger-router-controller: instance get: @@ -426,7 +426,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/preset: x-swagger-router-controller: instance patch: @@ -434,7 +434,7 @@ paths: operationId: instancePresetPatch tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -465,7 +465,7 @@ paths: operationId: instancePresetPut tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -496,7 +496,7 @@ paths: operationId: instancePresetPost tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -531,7 +531,7 @@ paths: operationId: instancePresetDelete tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -557,7 +557,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/preset/file: x-swagger-router-controller: instance put: @@ -565,7 +565,7 @@ paths: operationId: instancePresetFilePut tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -596,7 +596,7 @@ paths: operationId: instancePresetFilePost tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -622,7 +622,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/devicesets: x-swagger-router-controller: instance get: @@ -680,7 +680,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}: x-swagger-router-controller: deviceset get: @@ -707,7 +707,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/focus: x-swagger-router-controller: deviceset patch: @@ -738,7 +738,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/device: x-swagger-router-controller: deviceset put: @@ -775,7 +775,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/device/settings: x-swagger-router-controller: deviceset get: @@ -862,7 +862,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/device/run: x-swagger-router-controller: deviceset get: @@ -949,7 +949,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/device/report: x-swagger-router-controller: deviceset get: @@ -980,7 +980,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/channels/report: x-swagger-router-controller: deviceset @@ -1012,8 +1012,8 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - - + + /sdrangel/deviceset/{deviceSetIndex}/channel: x-swagger-router-controller: deviceset post: @@ -1050,7 +1050,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}: delete: description: delete channel (server only) @@ -1085,7 +1085,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/settings: x-swagger-router-controller: deviceset get: @@ -1199,7 +1199,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report: x-swagger-router-controller: deviceset get: @@ -1235,367 +1235,13 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - - /sdrdaemon: - x-swagger-router-controller: daemon - get: - description: get SDRdaemon summary information - operationId: daemonInstanceSummary - tags: - - Daemon - responses: - "200": - description: Success - schema: - # a pointer to a definition - $ref: "#/definitions/DaemonSummaryResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - /sdrdaemon/logging: - x-swagger-router-controller: daemon - get: - description: Get logging information for this instance - operationId: daemonInstanceLoggingGet - tags: - - Daemon - responses: - "200": - description: Success - schema: - $ref: "#/definitions/LoggingInfo" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - put: - description: Change logging parmeters for this instance - operationId: daemonInstanceLoggingPut - tags: - - Daemon - consumes: - - application/json - parameters: - - name: body - in: body - description: Logging information - required: true - schema: - $ref: "#/definitions/LoggingInfo" - responses: - "200": - description: Return new data on success - schema: - $ref: "#/definitions/LoggingInfo" - "400": - description: Invalid data - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - /sdrdaemon/channel/settings: - x-swagger-router-controller: daemon - get: - description: Get channel handling details - operationId: daemonChannelSettingsGet - tags: - - Daemon - responses: - "200": - description: On success return channel settings - schema: - $ref: "#/definitions/ChannelSettings" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - put: - description: Apply channel handling details unconditionally (force) - operationId: daemonChannelSettingsPut - tags: - - Daemon - parameters: - - name: body - in: body - description: Channel handling details to apply - required: true - schema: - $ref: "#/definitions/ChannelSettings" - responses: - "200": - description: On success returns new settings values - schema: - $ref: "#/definitions/ChannelSettings" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - patch: - description: Apply channel handling details differentially (no force) - operationId: daemonChannelSettingsPatch - tags: - - Daemon - parameters: - - name: body - in: body - description: Data handling detail to apply - required: true - schema: - $ref: "#/definitions/ChannelSettings" - responses: - "200": - description: On success returns new settings values - schema: - $ref: "#/definitions/ChannelSettings" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - - - /sdrdaemon/device/settings: - x-swagger-router-controller: daemon - get: - description: Get device settings - operationId: daemonDeviceSettingsGet - tags: - - Daemon - responses: - "200": - description: On success returns current settings values - schema: - $ref: "#/definitions/DeviceSettings" - "404": - description: Invalid device set index or device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - put: - description: Apply all settings unconditionally (force) - operationId: daemonDeviceSettingsPut - tags: - - Daemon - parameters: - - name: body - in: body - description: Device settings to apply - required: true - schema: - $ref: "#/definitions/DeviceSettings" - responses: - "200": - description: On success returns new settings values - schema: - $ref: "#/definitions/DeviceSettings" - "404": - description: Invalid device set index or device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - patch: - description: Apply settings differentially (no force) - operationId: daemonDeviceSettingsPatch - tags: - - Daemon - parameters: - - name: body - in: body - description: Device settings to apply - required: true - schema: - $ref: "#/definitions/DeviceSettings" - responses: - "200": - description: On success returns new settings values - schema: - $ref: "#/definitions/DeviceSettings" - "404": - description: Invalid device set index or device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - /sdrdaemon/run: - x-swagger-router-controller: daemon - get: - description: get device run status - operationId: daemonRunGet - tags: - - Daemon - responses: - "200": - description: On success return current state - schema: - $ref: "#/definitions/DeviceState" - "400": - description: Invalid device set index - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - post: - description: start device - operationId: daemonRunPost - tags: - - Daemon - responses: - "200": - description: On success return state before change - schema: - $ref: "#/definitions/DeviceState" - "400": - description: Invalid device set index - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - delete: - description: stop device - operationId: daemonRunDelete - tags: - - Daemon - responses: - "200": - description: On success return state before change - schema: - $ref: "#/definitions/DeviceState" - "400": - description: Invalid device set index - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - /sdrdaemon/device/report: - x-swagger-router-controller: daemon - get: - description: get the device report - operationId: daemonDeviceReportGet - tags: - - Daemon - responses: - "200": - description: On success return device report - schema: - $ref: "#/definitions/DeviceReport" - "400": - description: Invalid device set - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - /sdrdaemon/channel/report: - x-swagger-router-controller: daemon - get: - description: get the channel report - operationId: daemonChannelReportGet - tags: - - Daemon - responses: - "200": - description: On success return channel report - schema: - $ref: "#/definitions/ChannelReport" - "400": - description: Invalid device set or channel index - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device or channel not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - /swagger: x-swagger-pipe: swagger_raw - + # complex objects have schema definitions definitions: - DaemonSummaryResponse: - description: "Summarized information about this SDRdaemon instance" - required: - - version - - qtVersion - - dspRxBits - - dspTxBits - - pid - - appname - - architecture - - os - properties: - version: - description: "Current software version" - type: string - qtVersion: - description: "Qt version with which the software was compiled" - type: string - dspRxBits: - description: "Number of samples significant bits in software Rx DSP" - type: integer - dspTxBits: - description: "Number of samples significant bits in software Tx DSP" - type: integer - pid: - description: "PID of the SDRangel instance" - type: integer - appname: - description: "Application name: SDRangel for a GUI instance and SDRangelSrv for a server instance" - type: string - architecture: - description: "Codename of the CPU architecture on which the instance is running (available with Qt >= 5.4)" - type: string - os: - description: "Descriptive text of the operating system running the instance (available with Qt >= 5.4)" - type: string - logging: - $ref: "#/definitions/LoggingInfo" - samplingDevice: - $ref: "#/definitions/SamplingDevice" - InstanceSummaryResponse: description: "Summarized information about this SDRangel instance" required: @@ -1637,7 +1283,7 @@ definitions: $ref: "#/definitions/LoggingInfo" devicesetlist: $ref: "#/definitions/DeviceSetList" - + InstanceDevicesResponse: description: "Summarized information about logical devices from hardware devices attached to this SDRangel instance" required: @@ -1650,7 +1296,7 @@ definitions: type: array items: $ref: "#/definitions/DeviceListItem" - + InstanceChannelsResponse: description: "Summarized information about channel plugins available in this SDRangel instance" required: @@ -1663,7 +1309,7 @@ definitions: type: array items: $ref: "#/definitions/ChannelListItem" - + ErrorResponse: required: - message @@ -1671,14 +1317,14 @@ definitions: message: type: string example: "KO" - + SuccessResponse: required: - message properties: message: type: string - + LoggingInfo: description: "Logging parameters setting" properties: @@ -1694,7 +1340,7 @@ definitions: fileName: description: "Name of the log file" type: string - + DeviceListItem: description: "Summarized information about attached hardware device" properties: @@ -1713,19 +1359,19 @@ definitions: tx: description: "Set to not zero (true) if this is a Tx device" type: integer - nbStreams: + nbStreams: description: "Number of channels or streams in the device" type: integer streamIndex: description: "Index of the channel in the device" type: integer - deviceSetIndex: + deviceSetIndex: description: "Index of the device set that claimed this device (-1 if not claimed)" type: integer - index: + index: description: "Index of the device in the list of registered devices" type: integer - + ChannelListItem: description: "Summarized information about channel plugin" required: @@ -1749,7 +1395,7 @@ definitions: index: description: "Index of the channel in the list of registered channels" type: integer - + DeviceSet: description: "Sampling device and its associated channels" required: @@ -1781,7 +1427,7 @@ definitions: type: array items: $ref: "#/definitions/DeviceSet" - + DeviceState: description: "Device running state" required: @@ -1790,7 +1436,7 @@ definitions: state: description: "State: notStarted, idle, ready, running, error" type: string - + SamplingDevice: description: "Information about a logical device available from an attached hardware device that can be used as a sampling device" required: @@ -1812,7 +1458,7 @@ definitions: tx: description: "Not zero (true) if this is a Tx device" type: integer - nbStreams: + nbStreams: description: "Number of channels or streams in the device" type: integer streamIndex: @@ -1834,7 +1480,7 @@ definitions: state: description: "State: notStarted, idle, ready, running, error" type: string - + Channel: description: "Channel summarized information" required: @@ -1862,7 +1508,7 @@ definitions: type: integer report: $ref: "#/definitions/ChannelReport" - + ChannelsDetail: description: "All channels detailed information" required: @@ -1876,8 +1522,8 @@ definitions: type: array items: $ref: "#/definitions/Channel" - - + + AudioDevices: description: "List of audio devices available in the system" required: @@ -1891,7 +1537,7 @@ definitions: description: "List of input devices" type: array items: - $ref: "#/definitions/AudioInputDevice" + $ref: "#/definitions/AudioInputDevice" nbOutputDevices: description: "Number of output audio devices" type: integer @@ -1900,17 +1546,17 @@ definitions: type: array items: $ref: "#/definitions/AudioOutputDevice" - + AudioInputDevice: description: "Audio input device" properties: - name: + name: description: "Displayable name of the device" type: string index: description: "Index in attached devices list. -1 for system default" type: integer - sampleRate: + sampleRate: description: "Device sample rate in S/s" type: integer isSystemDefault: @@ -1927,13 +1573,13 @@ definitions: AudioOutputDevice: description: "Audio output device" properties: - name: + name: description: "Displayable name of the device" type: string index: description: "Index in attached devices list. -1 for system default" type: integer - sampleRate: + sampleRate: description: "Device sample rate in S/s" type: integer isSystemDefault: @@ -1972,7 +1618,7 @@ definitions: description: "Longitude in decimal degrees positive to the east" type: number format: float - + DVSeralDevices: description: "List of DV serial devices available in the system" required: @@ -1986,14 +1632,14 @@ definitions: type: array items: $ref: "#/definitions/DVSerialDevice" - + DVSerialDevice: description: "DV serial device details" properties: deviceName: description: "Name of the serial device in the system" type: string - + Presets: description: "Settings presets" required: @@ -2004,9 +1650,9 @@ definitions: type: integer groups: type: array - items: + items: $ref: "#/definitions/PresetGroup" - + PresetGroup: description: "Group of presets" required: @@ -2023,7 +1669,7 @@ definitions: type: array items: $ref: "#/definitions/PresetItem" - + PresetIdentifier: description: "Settings preset item" required: @@ -2045,7 +1691,7 @@ definitions: name: description: "Descriptive name of the preset" type: string - + PresetItem: description: "Settings preset item" required: @@ -2063,7 +1709,7 @@ definitions: name: description: "Descriptive name of the preset" type: string - + PresetTransfer: description: "Preset transfer to or from a device set" required: @@ -2071,11 +1717,11 @@ definitions: - preset properties: deviceSetIndex: - description: "Index of the device set" + description: "Index of the device set" type: integer preset: $ref: "#/definitions/PresetIdentifier" - + PresetImport: description: "Details to import preset from file in preset list" required: @@ -2090,7 +1736,7 @@ definitions: filePath: description: "Path of the import file" type: string - + PresetExport: description: "Details to export a preset to file" properties: @@ -2099,7 +1745,7 @@ definitions: type: string preset: $ref: "#/definitions/PresetIdentifier" - + DeviceSettings: description: Base device settings. Only the device settings corresponding to the device specified in the deviceHwType field is or should be present. discriminator: deviceHwType @@ -2151,7 +1797,7 @@ definitions: $ref: "/doc/swagger/include/SDRPlay.yaml#/SDRPlaySettings" testSourceSettings: $ref: "/doc/swagger/include/TestSource.yaml#/TestSourceSettings" - + DeviceReport: description: Base device report. Only the device report corresponding to the device specified in the deviceHwType is or should be present. @@ -2234,7 +1880,7 @@ definitions: $ref: "/doc/swagger/include/WFMDemod.yaml#/WFMDemodSettings" WFMModSettings: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModSettings" - + ChannelReport: description: Base channel report. Only the channel report corresponding to the channel specified in the channelType field is or should be present. discriminator: channelType @@ -2262,7 +1908,7 @@ definitions: SSBDemodReport: $ref: "/doc/swagger/include/SSBDemod.yaml#/SSBDemodReport" DaemonSourceReport: - $ref: "/doc/swagger/include/DaemonSource.yaml#/DaemonSourceReport" + $ref: "/doc/swagger/include/DaemonSource.yaml#/DaemonSourceReport" SSBModReport: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: @@ -2280,9 +1926,9 @@ responses: description: Error schema: $ref: "#/definitions/ErrorResponse" - + Response_501: description: Function not implemented schema: $ref: "#/definitions/ErrorResponse" - + diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 874d677dd..eb8269926 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1,11 +1,11 @@ swagger: "2.0" info: description: > - This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube - + This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube + --- - Limitations and specifcities: - + Limitations and specifcities: + * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. @@ -20,9 +20,9 @@ info: contact: url: "https://github.com/f4exb/sdrangel" email: "f4exb06@gmail.com" -# basePath prefixes all resource paths +# basePath prefixes all resource paths basePath: / -# +# schemes: # tip: remove http to make production-grade - http @@ -32,7 +32,7 @@ consumes: # format of the responses to the client (Accepts) produces: - application/json - + paths: /sdrangel: @@ -74,7 +74,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/devices: x-swagger-router-controller: instance get: @@ -97,7 +97,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/channels: x-swagger-router-controller: instance get: @@ -120,7 +120,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/logging: x-swagger-router-controller: instance get: @@ -142,7 +142,7 @@ paths: operationId: instanceLoggingPut tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -164,7 +164,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/audio: x-swagger-router-controller: instance get: @@ -181,7 +181,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/audio/input/parameters: x-swagger-router-controller: instance patch: @@ -189,7 +189,7 @@ paths: operationId: instanceAudioInputPatch tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -216,7 +216,7 @@ paths: operationId: instanceAudioInputDelete tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -238,8 +238,8 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - - + + /sdrangel/audio/input/cleanup: x-swagger-router-controller: instance patch: @@ -264,7 +264,7 @@ paths: operationId: instanceAudioOutputPatch tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -291,7 +291,7 @@ paths: operationId: instanceAudioOutputDelete tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -313,7 +313,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/audio/output/cleanup: x-swagger-router-controller: instance patch: @@ -330,7 +330,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/location: x-swagger-router-controller: instance get: @@ -352,7 +352,7 @@ paths: operationId: instanceLocationPut tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -372,7 +372,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/dvserial: x-swagger-router-controller: instance get: @@ -409,7 +409,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/presets: x-swagger-router-controller: instance get: @@ -426,7 +426,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/preset: x-swagger-router-controller: instance patch: @@ -434,7 +434,7 @@ paths: operationId: instancePresetPatch tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -465,7 +465,7 @@ paths: operationId: instancePresetPut tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -496,7 +496,7 @@ paths: operationId: instancePresetPost tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -531,7 +531,7 @@ paths: operationId: instancePresetDelete tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -557,7 +557,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/preset/file: x-swagger-router-controller: instance put: @@ -565,7 +565,7 @@ paths: operationId: instancePresetFilePut tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -596,7 +596,7 @@ paths: operationId: instancePresetFilePost tags: - Instance - consumes: + consumes: - application/json parameters: - name: body @@ -622,7 +622,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/devicesets: x-swagger-router-controller: instance get: @@ -680,7 +680,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}: x-swagger-router-controller: deviceset get: @@ -707,7 +707,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/focus: x-swagger-router-controller: deviceset patch: @@ -738,7 +738,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/device: x-swagger-router-controller: deviceset put: @@ -775,7 +775,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/device/settings: x-swagger-router-controller: deviceset get: @@ -862,7 +862,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/device/run: x-swagger-router-controller: deviceset get: @@ -949,7 +949,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/device/report: x-swagger-router-controller: deviceset get: @@ -980,7 +980,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/channels/report: x-swagger-router-controller: deviceset @@ -1012,8 +1012,8 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - - + + /sdrangel/deviceset/{deviceSetIndex}/channel: x-swagger-router-controller: deviceset post: @@ -1050,7 +1050,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}: delete: description: delete channel (server only) @@ -1085,7 +1085,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/settings: x-swagger-router-controller: deviceset get: @@ -1199,7 +1199,7 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - + /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report: x-swagger-router-controller: deviceset get: @@ -1235,367 +1235,13 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - - /sdrdaemon: - x-swagger-router-controller: daemon - get: - description: get SDRdaemon summary information - operationId: daemonInstanceSummary - tags: - - Daemon - responses: - "200": - description: Success - schema: - # a pointer to a definition - $ref: "#/definitions/DaemonSummaryResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - /sdrdaemon/logging: - x-swagger-router-controller: daemon - get: - description: Get logging information for this instance - operationId: daemonInstanceLoggingGet - tags: - - Daemon - responses: - "200": - description: Success - schema: - $ref: "#/definitions/LoggingInfo" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - put: - description: Change logging parmeters for this instance - operationId: daemonInstanceLoggingPut - tags: - - Daemon - consumes: - - application/json - parameters: - - name: body - in: body - description: Logging information - required: true - schema: - $ref: "#/definitions/LoggingInfo" - responses: - "200": - description: Return new data on success - schema: - $ref: "#/definitions/LoggingInfo" - "400": - description: Invalid data - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - /sdrdaemon/channel/settings: - x-swagger-router-controller: daemon - get: - description: Get channel handling details - operationId: daemonChannelSettingsGet - tags: - - Daemon - responses: - "200": - description: On success return channel settings - schema: - $ref: "#/definitions/ChannelSettings" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - put: - description: Apply channel handling details unconditionally (force) - operationId: daemonChannelSettingsPut - tags: - - Daemon - parameters: - - name: body - in: body - description: Channel handling details to apply - required: true - schema: - $ref: "#/definitions/ChannelSettings" - responses: - "200": - description: On success returns new settings values - schema: - $ref: "#/definitions/ChannelSettings" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - patch: - description: Apply channel handling details differentially (no force) - operationId: daemonChannelSettingsPatch - tags: - - Daemon - parameters: - - name: body - in: body - description: Data handling detail to apply - required: true - schema: - $ref: "#/definitions/ChannelSettings" - responses: - "200": - description: On success returns new settings values - schema: - $ref: "#/definitions/ChannelSettings" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - - - /sdrdaemon/device/settings: - x-swagger-router-controller: daemon - get: - description: Get device settings - operationId: daemonDeviceSettingsGet - tags: - - Daemon - responses: - "200": - description: On success returns current settings values - schema: - $ref: "#/definitions/DeviceSettings" - "404": - description: Invalid device set index or device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - put: - description: Apply all settings unconditionally (force) - operationId: daemonDeviceSettingsPut - tags: - - Daemon - parameters: - - name: body - in: body - description: Device settings to apply - required: true - schema: - $ref: "#/definitions/DeviceSettings" - responses: - "200": - description: On success returns new settings values - schema: - $ref: "#/definitions/DeviceSettings" - "404": - description: Invalid device set index or device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - patch: - description: Apply settings differentially (no force) - operationId: daemonDeviceSettingsPatch - tags: - - Daemon - parameters: - - name: body - in: body - description: Device settings to apply - required: true - schema: - $ref: "#/definitions/DeviceSettings" - responses: - "200": - description: On success returns new settings values - schema: - $ref: "#/definitions/DeviceSettings" - "404": - description: Invalid device set index or device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - /sdrdaemon/run: - x-swagger-router-controller: daemon - get: - description: get device run status - operationId: daemonRunGet - tags: - - Daemon - responses: - "200": - description: On success return current state - schema: - $ref: "#/definitions/DeviceState" - "400": - description: Invalid device set index - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - post: - description: start device - operationId: daemonRunPost - tags: - - Daemon - responses: - "200": - description: On success return state before change - schema: - $ref: "#/definitions/DeviceState" - "400": - description: Invalid device set index - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - delete: - description: stop device - operationId: daemonRunDelete - tags: - - Daemon - responses: - "200": - description: On success return state before change - schema: - $ref: "#/definitions/DeviceState" - "400": - description: Invalid device set index - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - /sdrdaemon/device/report: - x-swagger-router-controller: daemon - get: - description: get the device report - operationId: daemonDeviceReportGet - tags: - - Daemon - responses: - "200": - description: On success return device report - schema: - $ref: "#/definitions/DeviceReport" - "400": - description: Invalid device set - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - - /sdrdaemon/channel/report: - x-swagger-router-controller: daemon - get: - description: get the channel report - operationId: daemonChannelReportGet - tags: - - Daemon - responses: - "200": - description: On success return channel report - schema: - $ref: "#/definitions/ChannelReport" - "400": - description: Invalid device set or channel index - schema: - $ref: "#/definitions/ErrorResponse" - "404": - description: Device or channel not found - schema: - $ref: "#/definitions/ErrorResponse" - "500": - $ref: "#/responses/Response_500" - "501": - $ref: "#/responses/Response_501" - /swagger: x-swagger-pipe: swagger_raw - + # complex objects have schema definitions definitions: - DaemonSummaryResponse: - description: "Summarized information about this SDRdaemon instance" - required: - - version - - qtVersion - - dspRxBits - - dspTxBits - - pid - - appname - - architecture - - os - properties: - version: - description: "Current software version" - type: string - qtVersion: - description: "Qt version with which the software was compiled" - type: string - dspRxBits: - description: "Number of samples significant bits in software Rx DSP" - type: integer - dspTxBits: - description: "Number of samples significant bits in software Tx DSP" - type: integer - pid: - description: "PID of the SDRangel instance" - type: integer - appname: - description: "Application name: SDRangel for a GUI instance and SDRangelSrv for a server instance" - type: string - architecture: - description: "Codename of the CPU architecture on which the instance is running (available with Qt >= 5.4)" - type: string - os: - description: "Descriptive text of the operating system running the instance (available with Qt >= 5.4)" - type: string - logging: - $ref: "#/definitions/LoggingInfo" - samplingDevice: - $ref: "#/definitions/SamplingDevice" - InstanceSummaryResponse: description: "Summarized information about this SDRangel instance" required: @@ -1637,7 +1283,7 @@ definitions: $ref: "#/definitions/LoggingInfo" devicesetlist: $ref: "#/definitions/DeviceSetList" - + InstanceDevicesResponse: description: "Summarized information about logical devices from hardware devices attached to this SDRangel instance" required: @@ -1650,7 +1296,7 @@ definitions: type: array items: $ref: "#/definitions/DeviceListItem" - + InstanceChannelsResponse: description: "Summarized information about channel plugins available in this SDRangel instance" required: @@ -1663,7 +1309,7 @@ definitions: type: array items: $ref: "#/definitions/ChannelListItem" - + ErrorResponse: required: - message @@ -1671,14 +1317,14 @@ definitions: message: type: string example: "KO" - + SuccessResponse: required: - message properties: message: type: string - + LoggingInfo: description: "Logging parameters setting" properties: @@ -1694,7 +1340,7 @@ definitions: fileName: description: "Name of the log file" type: string - + DeviceListItem: description: "Summarized information about attached hardware device" properties: @@ -1713,19 +1359,19 @@ definitions: tx: description: "Set to not zero (true) if this is a Tx device" type: integer - nbStreams: + nbStreams: description: "Number of channels or streams in the device" type: integer streamIndex: description: "Index of the channel in the device" type: integer - deviceSetIndex: + deviceSetIndex: description: "Index of the device set that claimed this device (-1 if not claimed)" type: integer - index: + index: description: "Index of the device in the list of registered devices" type: integer - + ChannelListItem: description: "Summarized information about channel plugin" required: @@ -1749,7 +1395,7 @@ definitions: index: description: "Index of the channel in the list of registered channels" type: integer - + DeviceSet: description: "Sampling device and its associated channels" required: @@ -1781,7 +1427,7 @@ definitions: type: array items: $ref: "#/definitions/DeviceSet" - + DeviceState: description: "Device running state" required: @@ -1790,7 +1436,7 @@ definitions: state: description: "State: notStarted, idle, ready, running, error" type: string - + SamplingDevice: description: "Information about a logical device available from an attached hardware device that can be used as a sampling device" required: @@ -1812,7 +1458,7 @@ definitions: tx: description: "Not zero (true) if this is a Tx device" type: integer - nbStreams: + nbStreams: description: "Number of channels or streams in the device" type: integer streamIndex: @@ -1834,7 +1480,7 @@ definitions: state: description: "State: notStarted, idle, ready, running, error" type: string - + Channel: description: "Channel summarized information" required: @@ -1862,7 +1508,7 @@ definitions: type: integer report: $ref: "#/definitions/ChannelReport" - + ChannelsDetail: description: "All channels detailed information" required: @@ -1876,8 +1522,8 @@ definitions: type: array items: $ref: "#/definitions/Channel" - - + + AudioDevices: description: "List of audio devices available in the system" required: @@ -1891,7 +1537,7 @@ definitions: description: "List of input devices" type: array items: - $ref: "#/definitions/AudioInputDevice" + $ref: "#/definitions/AudioInputDevice" nbOutputDevices: description: "Number of output audio devices" type: integer @@ -1900,17 +1546,17 @@ definitions: type: array items: $ref: "#/definitions/AudioOutputDevice" - + AudioInputDevice: description: "Audio input device" properties: - name: + name: description: "Displayable name of the device" type: string index: description: "Index in attached devices list. -1 for system default" type: integer - sampleRate: + sampleRate: description: "Device sample rate in S/s" type: integer isSystemDefault: @@ -1927,13 +1573,13 @@ definitions: AudioOutputDevice: description: "Audio output device" properties: - name: + name: description: "Displayable name of the device" type: string index: description: "Index in attached devices list. -1 for system default" type: integer - sampleRate: + sampleRate: description: "Device sample rate in S/s" type: integer isSystemDefault: @@ -1972,7 +1618,7 @@ definitions: description: "Longitude in decimal degrees positive to the east" type: number format: float - + DVSeralDevices: description: "List of DV serial devices available in the system" required: @@ -1986,14 +1632,14 @@ definitions: type: array items: $ref: "#/definitions/DVSerialDevice" - + DVSerialDevice: description: "DV serial device details" properties: deviceName: description: "Name of the serial device in the system" type: string - + Presets: description: "Settings presets" required: @@ -2004,9 +1650,9 @@ definitions: type: integer groups: type: array - items: + items: $ref: "#/definitions/PresetGroup" - + PresetGroup: description: "Group of presets" required: @@ -2023,7 +1669,7 @@ definitions: type: array items: $ref: "#/definitions/PresetItem" - + PresetIdentifier: description: "Settings preset item" required: @@ -2045,7 +1691,7 @@ definitions: name: description: "Descriptive name of the preset" type: string - + PresetItem: description: "Settings preset item" required: @@ -2063,7 +1709,7 @@ definitions: name: description: "Descriptive name of the preset" type: string - + PresetTransfer: description: "Preset transfer to or from a device set" required: @@ -2071,11 +1717,11 @@ definitions: - preset properties: deviceSetIndex: - description: "Index of the device set" + description: "Index of the device set" type: integer preset: $ref: "#/definitions/PresetIdentifier" - + PresetImport: description: "Details to import preset from file in preset list" required: @@ -2090,7 +1736,7 @@ definitions: filePath: description: "Path of the import file" type: string - + PresetExport: description: "Details to export a preset to file" properties: @@ -2099,7 +1745,7 @@ definitions: type: string preset: $ref: "#/definitions/PresetIdentifier" - + DeviceSettings: description: Base device settings. Only the device settings corresponding to the device specified in the deviceHwType field is or should be present. discriminator: deviceHwType @@ -2151,7 +1797,7 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SDRPlay.yaml#/SDRPlaySettings" testSourceSettings: $ref: "http://localhost:8081/api/swagger/include/TestSource.yaml#/TestSourceSettings" - + DeviceReport: description: Base device report. Only the device report corresponding to the device specified in the deviceHwType is or should be present. @@ -2234,7 +1880,7 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/WFMDemod.yaml#/WFMDemodSettings" WFMModSettings: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModSettings" - + ChannelReport: description: Base channel report. Only the channel report corresponding to the channel specified in the channelType field is or should be present. discriminator: channelType @@ -2262,7 +1908,7 @@ definitions: SSBDemodReport: $ref: "http://localhost:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodReport" DaemonSourceReport: - $ref: "http://localhost:8081/api/swagger/include/DaemonSource.yaml#/DaemonSourceReport" + $ref: "http://localhost:8081/api/swagger/include/DaemonSource.yaml#/DaemonSourceReport" SSBModReport: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: @@ -2280,9 +1926,9 @@ responses: description: Error schema: $ref: "#/definitions/ErrorResponse" - + Response_501: description: Function not implemented schema: $ref: "#/definitions/ErrorResponse" - + diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 9599f8ea7..c18e79645 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1778,50 +1778,6 @@ margin-bottom: 20px; } }, "description" : "Daemon channel source settings" -}; - defs.DaemonSummaryResponse = { - "required" : [ "appname", "architecture", "dspRxBits", "dspTxBits", "os", "pid", "qtVersion", "version" ], - "properties" : { - "version" : { - "type" : "string", - "description" : "Current software version" - }, - "qtVersion" : { - "type" : "string", - "description" : "Qt version with which the software was compiled" - }, - "dspRxBits" : { - "type" : "integer", - "description" : "Number of samples significant bits in software Rx DSP" - }, - "dspTxBits" : { - "type" : "integer", - "description" : "Number of samples significant bits in software Tx DSP" - }, - "pid" : { - "type" : "integer", - "description" : "PID of the SDRangel instance" - }, - "appname" : { - "type" : "string", - "description" : "Application name: SDRangel for a GUI instance and SDRangelSrv for a server instance" - }, - "architecture" : { - "type" : "string", - "description" : "Codename of the CPU architecture on which the instance is running (available with Qt >= 5.4)" - }, - "os" : { - "type" : "string", - "description" : "Descriptive text of the operating system running the instance (available with Qt >= 5.4)" - }, - "logging" : { - "$ref" : "#/definitions/LoggingInfo" - }, - "samplingDevice" : { - "$ref" : "#/definitions/SamplingDevice" - } - }, - "description" : "Summarized information about this SDRdaemon instance" }; defs.DeviceListItem = { "properties" : { @@ -4095,49 +4051,6 @@ margin-bottom: 20px; -->
  • - -
  • - daemonChannelReportGet -
  • -
  • - daemonChannelSettingsGet -
  • -
  • - daemonChannelSettingsPatch -
  • -
  • - daemonChannelSettingsPut -
  • -
  • - daemonDeviceReportGet -
  • -
  • - daemonDeviceSettingsGet -
  • -
  • - daemonDeviceSettingsPatch -
  • -
  • - daemonDeviceSettingsPut -
  • -
  • - daemonInstanceLoggingGet -
  • -
  • - daemonInstanceLoggingPut -
  • -
  • - daemonInstanceSummary -
  • -
  • - daemonRunDelete -
  • -
  • - daemonRunGet -
  • -
  • - daemonRunPost -
  • devicesetChannelDelete @@ -4303,5561 +4216,6 @@ margin-bottom: 20px;
    -
    -

    Daemon

    -
    -
    -
    -

    daemonChannelReportGet

    -

    -
    -
    -
    -

    -

    get the channel report

    -

    -
    -
    /sdrdaemon/channel/report
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/channel/report"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            ChannelReport result = apiInstance.daemonChannelReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            ChannelReport result = apiInstance.daemonChannelReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonChannelReportGetWithCompletionHandler: 
    -              ^(ChannelReport output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonChannelReportGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonChannelReportGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                ChannelReport result = apiInstance.daemonChannelReportGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonChannelReportGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonChannelReportGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonChannelReportGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonChannelReportGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonChannelReportGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_channel_report_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonChannelReportGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return channel report

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set or channel index

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device or channel not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonChannelSettingsGet

    -

    -
    -
    -
    -

    -

    Get channel handling details

    -

    -
    -
    /sdrdaemon/channel/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/channel/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonChannelSettingsGetWithCompletionHandler: 
    -              ^(ChannelSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonChannelSettingsGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonChannelSettingsGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                ChannelSettings result = apiInstance.daemonChannelSettingsGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonChannelSettingsGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonChannelSettingsGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonChannelSettingsGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonChannelSettingsGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_channel_settings_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonChannelSettingsGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return channel settings

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonChannelSettingsPatch

    -

    -
    -
    -
    -

    -

    Apply channel handling details differentially (no force)

    -

    -
    -
    /sdrdaemon/channel/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PATCH "http://localhost/sdrdaemon/channel/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        ChannelSettings body = ; // ChannelSettings | Data handling detail to apply
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        ChannelSettings body = ; // ChannelSettings | Data handling detail to apply
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    ChannelSettings *body = ; // Data handling detail to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonChannelSettingsPatchWith:body
    -              completionHandler: ^(ChannelSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {ChannelSettings} Data handling detail to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonChannelSettingsPatch(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonChannelSettingsPatchExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new ChannelSettings(); // ChannelSettings | Data handling detail to apply
    -
    -            try
    -            {
    -                ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsPatch: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // ChannelSettings | Data handling detail to apply
    -
    -try {
    -    $result = $api_instance->daemonChannelSettingsPatch($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonChannelSettingsPatch: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::ChannelSettings->new(); # ChannelSettings | Data handling detail to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonChannelSettingsPatch(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonChannelSettingsPatch: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # ChannelSettings | Data handling detail to apply
    -
    -try: 
    -    api_response = api_instance.daemon_channel_settings_patch(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonChannelSettingsPatch: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonChannelSettingsPut

    -

    -
    -
    -
    -

    -

    Apply channel handling details unconditionally (force)

    -

    -
    -
    /sdrdaemon/channel/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PUT "http://localhost/sdrdaemon/channel/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        ChannelSettings body = ; // ChannelSettings | Channel handling details to apply
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        ChannelSettings body = ; // ChannelSettings | Channel handling details to apply
    -        try {
    -            ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    ChannelSettings *body = ; // Channel handling details to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonChannelSettingsPutWith:body
    -              completionHandler: ^(ChannelSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {ChannelSettings} Channel handling details to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonChannelSettingsPut(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonChannelSettingsPutExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new ChannelSettings(); // ChannelSettings | Channel handling details to apply
    -
    -            try
    -            {
    -                ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsPut: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // ChannelSettings | Channel handling details to apply
    -
    -try {
    -    $result = $api_instance->daemonChannelSettingsPut($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonChannelSettingsPut: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::ChannelSettings->new(); # ChannelSettings | Channel handling details to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonChannelSettingsPut(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonChannelSettingsPut: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # ChannelSettings | Channel handling details to apply
    -
    -try: 
    -    api_response = api_instance.daemon_channel_settings_put(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonChannelSettingsPut: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonDeviceReportGet

    -

    -
    -
    -
    -

    -

    get the device report

    -

    -
    -
    /sdrdaemon/device/report
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/device/report"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceReport result = apiInstance.daemonDeviceReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceReport result = apiInstance.daemonDeviceReportGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceReportGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonDeviceReportGetWithCompletionHandler: 
    -              ^(DeviceReport output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonDeviceReportGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonDeviceReportGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceReport result = apiInstance.daemonDeviceReportGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonDeviceReportGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonDeviceReportGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDeviceReportGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonDeviceReportGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDeviceReportGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_device_report_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDeviceReportGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return device report

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonDeviceSettingsGet

    -

    -
    -
    -
    -

    -

    Get device settings

    -

    -
    -
    /sdrdaemon/device/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/device/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonDeviceSettingsGetWithCompletionHandler: 
    -              ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonDeviceSettingsGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonDeviceSettingsGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonDeviceSettingsGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonDeviceSettingsGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDeviceSettingsGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonDeviceSettingsGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDeviceSettingsGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_device_settings_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDeviceSettingsGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success returns current settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonDeviceSettingsPatch

    -

    -
    -
    -
    -

    -

    Apply settings differentially (no force)

    -

    -
    -
    /sdrdaemon/device/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PATCH "http://localhost/sdrdaemon/device/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPatch");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    DeviceSettings *body = ; // Device settings to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonDeviceSettingsPatchWith:body
    -              completionHandler: ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {DeviceSettings} Device settings to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonDeviceSettingsPatch(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonDeviceSettingsPatchExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonDeviceSettingsPatch(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsPatch: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // DeviceSettings | Device settings to apply
    -
    -try {
    -    $result = $api_instance->daemonDeviceSettingsPatch($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDeviceSettingsPatch: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonDeviceSettingsPatch(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDeviceSettingsPatch: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # DeviceSettings | Device settings to apply
    -
    -try: 
    -    api_response = api_instance.daemon_device_settings_patch(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDeviceSettingsPatch: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonDeviceSettingsPut

    -

    -
    -
    -
    -

    -

    Apply all settings unconditionally (force)

    -

    -
    -
    /sdrdaemon/device/settings
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PUT "http://localhost/sdrdaemon/device/settings"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        DeviceSettings body = ; // DeviceSettings | Device settings to apply
    -        try {
    -            DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDeviceSettingsPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    DeviceSettings *body = ; // Device settings to apply
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonDeviceSettingsPutWith:body
    -              completionHandler: ^(DeviceSettings output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {DeviceSettings} Device settings to apply
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonDeviceSettingsPut(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonDeviceSettingsPutExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new DeviceSettings(); // DeviceSettings | Device settings to apply
    -
    -            try
    -            {
    -                DeviceSettings result = apiInstance.daemonDeviceSettingsPut(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonDeviceSettingsPut: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // DeviceSettings | Device settings to apply
    -
    -try {
    -    $result = $api_instance->daemonDeviceSettingsPut($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDeviceSettingsPut: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::DeviceSettings->new(); # DeviceSettings | Device settings to apply
    -
    -eval { 
    -    my $result = $api_instance->daemonDeviceSettingsPut(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDeviceSettingsPut: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # DeviceSettings | Device settings to apply
    -
    -try: 
    -    api_response = api_instance.daemon_device_settings_put(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDeviceSettingsPut: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - On success returns new settings values

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Invalid device set index or device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonInstanceLoggingGet

    -

    -
    -
    -
    -

    -

    Get logging information for this instance

    -

    -
    -
    /sdrdaemon/logging
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/logging"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            LoggingInfo result = apiInstance.daemonInstanceLoggingGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceLoggingGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            LoggingInfo result = apiInstance.daemonInstanceLoggingGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceLoggingGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonInstanceLoggingGetWithCompletionHandler: 
    -              ^(LoggingInfo output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonInstanceLoggingGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonInstanceLoggingGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                LoggingInfo result = apiInstance.daemonInstanceLoggingGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonInstanceLoggingGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonInstanceLoggingGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonInstanceLoggingGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonInstanceLoggingGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonInstanceLoggingGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_instance_logging_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonInstanceLoggingGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - Success

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonInstanceLoggingPut

    -

    -
    -
    -
    -

    -

    Change logging parmeters for this instance

    -

    -
    -
    /sdrdaemon/logging
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X PUT "http://localhost/sdrdaemon/logging"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        LoggingInfo body = ; // LoggingInfo | Logging information
    -        try {
    -            LoggingInfo result = apiInstance.daemonInstanceLoggingPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceLoggingPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        LoggingInfo body = ; // LoggingInfo | Logging information
    -        try {
    -            LoggingInfo result = apiInstance.daemonInstanceLoggingPut(body);
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceLoggingPut");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    LoggingInfo *body = ; // Logging information
    -
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonInstanceLoggingPutWith:body
    -              completionHandler: ^(LoggingInfo output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var body = ; // {LoggingInfo} Logging information
    -
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonInstanceLoggingPut(body, callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonInstanceLoggingPutExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -            var body = new LoggingInfo(); // LoggingInfo | Logging information
    -
    -            try
    -            {
    -                LoggingInfo result = apiInstance.daemonInstanceLoggingPut(body);
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonInstanceLoggingPut: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // LoggingInfo | Logging information
    -
    -try {
    -    $result = $api_instance->daemonInstanceLoggingPut($body);
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonInstanceLoggingPut: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::LoggingInfo->new(); # LoggingInfo | Logging information
    -
    -eval { 
    -    my $result = $api_instance->daemonInstanceLoggingPut(body => $body);
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonInstanceLoggingPut: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -body =  # LoggingInfo | Logging information
    -
    -try: 
    -    api_response = api_instance.daemon_instance_logging_put(body)
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonInstanceLoggingPut: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - -
    Body parameters
    - - - - - - - - - -
    NameDescription
    body * - - - -
    -
    - - - -

    Responses

    -

    Status: 200 - Return new data on success

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid data

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonInstanceSummary

    -

    -
    -
    -
    -

    -

    get SDRdaemon summary information

    -

    -
    -
    /sdrdaemon
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DaemonSummaryResponse result = apiInstance.daemonInstanceSummary();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceSummary");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DaemonSummaryResponse result = apiInstance.daemonInstanceSummary();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonInstanceSummary");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonInstanceSummaryWithCompletionHandler: 
    -              ^(DaemonSummaryResponse output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonInstanceSummary(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonInstanceSummaryExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DaemonSummaryResponse result = apiInstance.daemonInstanceSummary();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonInstanceSummary: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonInstanceSummary();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonInstanceSummary: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonInstanceSummary();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonInstanceSummary: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_instance_summary()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonInstanceSummary: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - Success

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonRunDelete

    -

    -
    -
    -
    -

    -

    stop device

    -

    -
    -
    /sdrdaemon/run
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X DELETE "http://localhost/sdrdaemon/run"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunDelete();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunDelete");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunDelete();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunDelete");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonRunDeleteWithCompletionHandler: 
    -              ^(DeviceState output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonRunDelete(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonRunDeleteExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceState result = apiInstance.daemonRunDelete();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonRunDelete: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonRunDelete();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonRunDelete: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonRunDelete();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonRunDelete: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_run_delete()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonRunDelete: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return state before change

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set index

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonRunGet

    -

    -
    -
    -
    -

    -

    get device run status

    -

    -
    -
    /sdrdaemon/run
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X GET "http://localhost/sdrdaemon/run"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunGet();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunGet");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonRunGetWithCompletionHandler: 
    -              ^(DeviceState output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonRunGet(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonRunGetExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceState result = apiInstance.daemonRunGet();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonRunGet: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonRunGet();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonRunGet: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonRunGet();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonRunGet: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_run_get()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonRunGet: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return current state

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set index

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -

    daemonRunPost

    -

    -
    -
    -
    -

    -

    start device

    -

    -
    -
    /sdrdaemon/run
    -

    -

    Usage and SDK Samples

    -

    - - -
    -
    -
    curl -X POST "http://localhost/sdrdaemon/run"
    -
    -
    -
    import SWGSDRangel.*;
    -import SWGSDRangel.auth.*;
    -import SWGSDRangel.model.*;
    -import SWGSDRangel.api.DaemonApi;
    -
    -import java.io.File;
    -import java.util.*;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunPost();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunPost");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    import SWGSDRangel.api.DaemonApi;
    -
    -public class DaemonApiExample {
    -
    -    public static void main(String[] args) {
    -        DaemonApi apiInstance = new DaemonApi();
    -        try {
    -            DeviceState result = apiInstance.daemonRunPost();
    -            System.out.println(result);
    -        } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonRunPost");
    -            e.printStackTrace();
    -        }
    -    }
    -}
    -
    - -
    -
    
    -DaemonApi *apiInstance = [[DaemonApi alloc] init];
    -
    -[apiInstance daemonRunPostWithCompletionHandler: 
    -              ^(DeviceState output, NSError* error) {
    -                            if (output) {
    -                                NSLog(@"%@", output);
    -                            }
    -                            if (error) {
    -                                NSLog(@"Error: %@", error);
    -                            }
    -                        }];
    -
    -
    - -
    -
    var SdRangel = require('sd_rangel');
    -
    -var api = new SdRangel.DaemonApi()
    -
    -var callback = function(error, data, response) {
    -  if (error) {
    -    console.error(error);
    -  } else {
    -    console.log('API called successfully. Returned data: ' + data);
    -  }
    -};
    -api.daemonRunPost(callback);
    -
    -
    - - -
    -
    using System;
    -using System.Diagnostics;
    -using SWGSDRangel.Api;
    -using SWGSDRangel.Client;
    -using SWGSDRangel.Model;
    -
    -namespace Example
    -{
    -    public class daemonRunPostExample
    -    {
    -        public void main()
    -        {
    -            
    -            var apiInstance = new DaemonApi();
    -
    -            try
    -            {
    -                DeviceState result = apiInstance.daemonRunPost();
    -                Debug.WriteLine(result);
    -            }
    -            catch (Exception e)
    -            {
    -                Debug.Print("Exception when calling DaemonApi.daemonRunPost: " + e.Message );
    -            }
    -        }
    -    }
    -}
    -
    -
    - -
    -
    <?php
    -require_once(__DIR__ . '/vendor/autoload.php');
    -
    -$api_instance = new Swagger\Client\Api\DaemonApi();
    -
    -try {
    -    $result = $api_instance->daemonRunPost();
    -    print_r($result);
    -} catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonRunPost: ', $e->getMessage(), PHP_EOL;
    -}
    -?>
    -
    - -
    -
    use Data::Dumper;
    -use SWGSDRangel::Configuration;
    -use SWGSDRangel::DaemonApi;
    -
    -my $api_instance = SWGSDRangel::DaemonApi->new();
    -
    -eval { 
    -    my $result = $api_instance->daemonRunPost();
    -    print Dumper($result);
    -};
    -if ($@) {
    -    warn "Exception when calling DaemonApi->daemonRunPost: $@\n";
    -}
    -
    - -
    -
    from __future__ import print_statement
    -import time
    -import swagger_sdrangel
    -from swagger_sdrangel.rest import ApiException
    -from pprint import pprint
    -
    -# create an instance of the API class
    -api_instance = swagger_sdrangel.DaemonApi()
    -
    -try: 
    -    api_response = api_instance.daemon_run_post()
    -    pprint(api_response)
    -except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonRunPost: %s\n" % e)
    -
    -
    - -

    Parameters

    - - - - - - -

    Responses

    -

    Status: 200 - On success return state before change

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 400 - Invalid device set index

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 404 - Device not found

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 500 - Error

    - - - -
    -
    -
    - -
    - -
    -
    - -

    Status: 501 - Function not implemented

    - - - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -

    DeviceSet

    @@ -28708,7 +23066,7 @@ except ApiException as e:
    - Generated 2018-09-11T14:35:37.698+02:00 + Generated 2018-09-11T14:47:33.212+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp index 3998788e9..22fe7a6b0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h index f26f4e54c..2828e24d3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp index 8e8e8a86c..7086ebf7f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h index 2dd2bc5d0..7fc53a24a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp index 5cf0b3b60..ac498660a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModReport.h b/swagger/sdrangel/code/qt5/client/SWGAMModReport.h index cf130b999..adad48eda 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp index 591bc8368..0a11b3d6d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h index 8be0e9d5f..f0341fb30 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp index 5215e8125..7bc46458e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModReport.h b/swagger/sdrangel/code/qt5/client/SWGATVModReport.h index c22fe6661..93095afa0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGATVModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp index 4a061e46f..1674ec935 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h index 8152fe528..4f2a73def 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp index 9a5bf3883..894790830 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h index 97133d8f2..abea9185d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp index 4881e5b2d..849275b09 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h index 8176e770e..f3ba9eca8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyHFSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp index 8b2774de8..e45fd3e76 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h index 4ca4111c1..bffa6357e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspyReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp index 7cb8f115d..63ea48463 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h index ea3675cc8..ea53bc6f8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAirspySettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp index 2a5684d85..dd9e939ff 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h index 42288ff21..43ff1485a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp index 555630b30..b23506822 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h index 257b5e542..deff4ebe6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp index fe4279699..3ee2364e7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h index 7b3a8a166..e0b41287b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp index fcc1acf27..2f037dbb5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h index 976270ba3..53b53cd04 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp index f2eaf2524..a2dc94f50 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h index 487581677..aa43ab3e2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBFMDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp b/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp index a4b2c71f0..8ea2b78bb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBandwidth.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBandwidth.h b/swagger/sdrangel/code/qt5/client/SWGBandwidth.h index 7d48a72fa..9df17c8b5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBandwidth.h +++ b/swagger/sdrangel/code/qt5/client/SWGBandwidth.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp index b489e35cd..23de49522 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h index 65d92c4b9..e70e6a6cc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp index 8a4878520..c4d008fb5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h index 401f9d714..eb3c03ac8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.cpp index a588c970f..274ffacd0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h index 9fc1f2b7b..5bcc43b99 100644 --- a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannel.cpp b/swagger/sdrangel/code/qt5/client/SWGChannel.cpp index 4879beebd..5e602f877 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannel.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannel.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannel.h b/swagger/sdrangel/code/qt5/client/SWGChannel.h index 6f9ada0ab..e29dcf8f8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannel.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannel.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.cpp index e4dc497e3..6e538b903 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h index 61dc46a5c..3ed496d67 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index f2a5b33ea..451ed988b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index c090b4e2f..6537329dc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index ab9d493c4..b9a8f0e4f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index c6c536674..6d8a43592 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp index f83ccc9ef..cfe6bf496 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h index 6a126c79f..ea469f9af 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp index c1afd4e3c..e936aad4c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h index b70312ee0..0dc82e771 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp index 650a88e8c..6687c6f2f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h index fac77a4bb..5e757b40a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDSDDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.cpp b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.cpp index d30023cae..0d678caed 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h index a6605987f..1e5b7bd19 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.cpp index cd514ee5c..79d431269 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h index ad95fe489..f69317b80 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp deleted file mode 100644 index f1498b845..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp +++ /dev/null @@ -1,775 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -#include "SWGDaemonApi.h" -#include "SWGHelpers.h" -#include "SWGModelFactory.h" - -#include -#include - -namespace SWGSDRangel { - -SWGDaemonApi::SWGDaemonApi() {} - -SWGDaemonApi::~SWGDaemonApi() {} - -SWGDaemonApi::SWGDaemonApi(QString host, QString basePath) { - this->host = host; - this->basePath = basePath; -} - -void -SWGDaemonApi::daemonChannelReportGet() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/channel/report"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonChannelReportGetCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonChannelReportGetCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGChannelReport* output = static_cast(create(json, QString("SWGChannelReport"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonChannelReportGetSignal(output); - } else { - emit daemonChannelReportGetSignalE(output, error_type, error_str); - emit daemonChannelReportGetSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonChannelSettingsGet() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/channel/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonChannelSettingsGetCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonChannelSettingsGetCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGChannelSettings* output = static_cast(create(json, QString("SWGChannelSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonChannelSettingsGetSignal(output); - } else { - emit daemonChannelSettingsGetSignalE(output, error_type, error_str); - emit daemonChannelSettingsGetSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonChannelSettingsPatch(SWGChannelSettings& body) { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/channel/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "PATCH"); - - - - QString output = body.asJson(); - input.request_body.append(output); - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonChannelSettingsPatchCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonChannelSettingsPatchCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGChannelSettings* output = static_cast(create(json, QString("SWGChannelSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonChannelSettingsPatchSignal(output); - } else { - emit daemonChannelSettingsPatchSignalE(output, error_type, error_str); - emit daemonChannelSettingsPatchSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonChannelSettingsPut(SWGChannelSettings& body) { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/channel/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "PUT"); - - - - QString output = body.asJson(); - input.request_body.append(output); - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonChannelSettingsPutCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonChannelSettingsPutCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGChannelSettings* output = static_cast(create(json, QString("SWGChannelSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonChannelSettingsPutSignal(output); - } else { - emit daemonChannelSettingsPutSignalE(output, error_type, error_str); - emit daemonChannelSettingsPutSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonDeviceReportGet() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/device/report"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonDeviceReportGetCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonDeviceReportGetCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceReport* output = static_cast(create(json, QString("SWGDeviceReport"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonDeviceReportGetSignal(output); - } else { - emit daemonDeviceReportGetSignalE(output, error_type, error_str); - emit daemonDeviceReportGetSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonDeviceSettingsGet() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/device/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonDeviceSettingsGetCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonDeviceSettingsGetCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonDeviceSettingsGetSignal(output); - } else { - emit daemonDeviceSettingsGetSignalE(output, error_type, error_str); - emit daemonDeviceSettingsGetSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonDeviceSettingsPatch(SWGDeviceSettings& body) { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/device/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "PATCH"); - - - - QString output = body.asJson(); - input.request_body.append(output); - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonDeviceSettingsPatchCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonDeviceSettingsPatchCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonDeviceSettingsPatchSignal(output); - } else { - emit daemonDeviceSettingsPatchSignalE(output, error_type, error_str); - emit daemonDeviceSettingsPatchSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonDeviceSettingsPut(SWGDeviceSettings& body) { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/device/settings"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "PUT"); - - - - QString output = body.asJson(); - input.request_body.append(output); - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonDeviceSettingsPutCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonDeviceSettingsPutCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceSettings* output = static_cast(create(json, QString("SWGDeviceSettings"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonDeviceSettingsPutSignal(output); - } else { - emit daemonDeviceSettingsPutSignalE(output, error_type, error_str); - emit daemonDeviceSettingsPutSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonInstanceLoggingGet() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/logging"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonInstanceLoggingGetCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonInstanceLoggingGetCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGLoggingInfo* output = static_cast(create(json, QString("SWGLoggingInfo"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonInstanceLoggingGetSignal(output); - } else { - emit daemonInstanceLoggingGetSignalE(output, error_type, error_str); - emit daemonInstanceLoggingGetSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonInstanceLoggingPut(SWGLoggingInfo& body) { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/logging"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "PUT"); - - - - QString output = body.asJson(); - input.request_body.append(output); - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonInstanceLoggingPutCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonInstanceLoggingPutCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGLoggingInfo* output = static_cast(create(json, QString("SWGLoggingInfo"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonInstanceLoggingPutSignal(output); - } else { - emit daemonInstanceLoggingPutSignalE(output, error_type, error_str); - emit daemonInstanceLoggingPutSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonInstanceSummary() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonInstanceSummaryCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonInstanceSummaryCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDaemonSummaryResponse* output = static_cast(create(json, QString("SWGDaemonSummaryResponse"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonInstanceSummarySignal(output); - } else { - emit daemonInstanceSummarySignalE(output, error_type, error_str); - emit daemonInstanceSummarySignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonRunDelete() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/run"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "DELETE"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonRunDeleteCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonRunDeleteCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceState* output = static_cast(create(json, QString("SWGDeviceState"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonRunDeleteSignal(output); - } else { - emit daemonRunDeleteSignalE(output, error_type, error_str); - emit daemonRunDeleteSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonRunGet() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/run"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "GET"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonRunGetCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonRunGetCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceState* output = static_cast(create(json, QString("SWGDeviceState"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonRunGetSignal(output); - } else { - emit daemonRunGetSignalE(output, error_type, error_str); - emit daemonRunGetSignalEFull(worker, error_type, error_str); - } -} - -void -SWGDaemonApi::daemonRunPost() { - QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/run"); - - - - SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); - SWGHttpRequestInput input(fullPath, "POST"); - - - - - - foreach(QString key, this->defaultHeaders.keys()) { - input.headers.insert(key, this->defaultHeaders.value(key)); - } - - connect(worker, - &SWGHttpRequestWorker::on_execution_finished, - this, - &SWGDaemonApi::daemonRunPostCallback); - - worker->execute(&input); -} - -void -SWGDaemonApi::daemonRunPostCallback(SWGHttpRequestWorker * worker) { - QString msg; - QString error_str = worker->error_str; - QNetworkReply::NetworkError error_type = worker->error_type; - - if (worker->error_type == QNetworkReply::NoError) { - msg = QString("Success! %1 bytes").arg(worker->response.length()); - } - else { - msg = "Error: " + worker->error_str; - } - - - QString json(worker->response); - SWGDeviceState* output = static_cast(create(json, QString("SWGDeviceState"))); - worker->deleteLater(); - - if (worker->error_type == QNetworkReply::NoError) { - emit daemonRunPostSignal(output); - } else { - emit daemonRunPostSignalE(output, error_type, error_str); - emit daemonRunPostSignalEFull(worker, error_type, error_str); - } -} - - -} diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h deleted file mode 100644 index 10f9ccad2..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h +++ /dev/null @@ -1,123 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -#ifndef _SWG_SWGDaemonApi_H_ -#define _SWG_SWGDaemonApi_H_ - -#include "SWGHttpRequest.h" - -#include "SWGChannelReport.h" -#include "SWGChannelSettings.h" -#include "SWGDaemonSummaryResponse.h" -#include "SWGDeviceReport.h" -#include "SWGDeviceSettings.h" -#include "SWGDeviceState.h" -#include "SWGErrorResponse.h" -#include "SWGLoggingInfo.h" - -#include - -namespace SWGSDRangel { - -class SWGDaemonApi: public QObject { - Q_OBJECT - -public: - SWGDaemonApi(); - SWGDaemonApi(QString host, QString basePath); - ~SWGDaemonApi(); - - QString host; - QString basePath; - QMap defaultHeaders; - - void daemonChannelReportGet(); - void daemonChannelSettingsGet(); - void daemonChannelSettingsPatch(SWGChannelSettings& body); - void daemonChannelSettingsPut(SWGChannelSettings& body); - void daemonDeviceReportGet(); - void daemonDeviceSettingsGet(); - void daemonDeviceSettingsPatch(SWGDeviceSettings& body); - void daemonDeviceSettingsPut(SWGDeviceSettings& body); - void daemonInstanceLoggingGet(); - void daemonInstanceLoggingPut(SWGLoggingInfo& body); - void daemonInstanceSummary(); - void daemonRunDelete(); - void daemonRunGet(); - void daemonRunPost(); - -private: - void daemonChannelReportGetCallback (SWGHttpRequestWorker * worker); - void daemonChannelSettingsGetCallback (SWGHttpRequestWorker * worker); - void daemonChannelSettingsPatchCallback (SWGHttpRequestWorker * worker); - void daemonChannelSettingsPutCallback (SWGHttpRequestWorker * worker); - void daemonDeviceReportGetCallback (SWGHttpRequestWorker * worker); - void daemonDeviceSettingsGetCallback (SWGHttpRequestWorker * worker); - void daemonDeviceSettingsPatchCallback (SWGHttpRequestWorker * worker); - void daemonDeviceSettingsPutCallback (SWGHttpRequestWorker * worker); - void daemonInstanceLoggingGetCallback (SWGHttpRequestWorker * worker); - void daemonInstanceLoggingPutCallback (SWGHttpRequestWorker * worker); - void daemonInstanceSummaryCallback (SWGHttpRequestWorker * worker); - void daemonRunDeleteCallback (SWGHttpRequestWorker * worker); - void daemonRunGetCallback (SWGHttpRequestWorker * worker); - void daemonRunPostCallback (SWGHttpRequestWorker * worker); - -signals: - void daemonChannelReportGetSignal(SWGChannelReport* summary); - void daemonChannelSettingsGetSignal(SWGChannelSettings* summary); - void daemonChannelSettingsPatchSignal(SWGChannelSettings* summary); - void daemonChannelSettingsPutSignal(SWGChannelSettings* summary); - void daemonDeviceReportGetSignal(SWGDeviceReport* summary); - void daemonDeviceSettingsGetSignal(SWGDeviceSettings* summary); - void daemonDeviceSettingsPatchSignal(SWGDeviceSettings* summary); - void daemonDeviceSettingsPutSignal(SWGDeviceSettings* summary); - void daemonInstanceLoggingGetSignal(SWGLoggingInfo* summary); - void daemonInstanceLoggingPutSignal(SWGLoggingInfo* summary); - void daemonInstanceSummarySignal(SWGDaemonSummaryResponse* summary); - void daemonRunDeleteSignal(SWGDeviceState* summary); - void daemonRunGetSignal(SWGDeviceState* summary); - void daemonRunPostSignal(SWGDeviceState* summary); - - void daemonChannelReportGetSignalE(SWGChannelReport* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonChannelSettingsGetSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonChannelSettingsPatchSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonChannelSettingsPutSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDeviceReportGetSignalE(SWGDeviceReport* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDeviceSettingsGetSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDeviceSettingsPatchSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDeviceSettingsPutSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonInstanceLoggingGetSignalE(SWGLoggingInfo* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonInstanceLoggingPutSignalE(SWGLoggingInfo* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonInstanceSummarySignalE(SWGDaemonSummaryResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonRunDeleteSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonRunGetSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonRunPostSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); - - void daemonChannelReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonChannelSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonChannelSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonChannelSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDeviceReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDeviceSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDeviceSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDeviceSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonInstanceLoggingGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonInstanceLoggingPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonInstanceSummarySignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonRunDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonRunGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonRunPostSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - -}; - -} -#endif diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp index e9f21b8e3..225467eaf 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h index 9af9d232c..0acd07f37 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp index 91d991f52..83b412b60 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h index 5d9e63d44..39e0d2872 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.cpp index 006eaf2be..6d1846988 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.h index a9b0aa714..ca4ee7741 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.cpp deleted file mode 100644 index e2e81ae9d..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGDaemonSummaryResponse.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGDaemonSummaryResponse::SWGDaemonSummaryResponse(QString* json) { - init(); - this->fromJson(*json); -} - -SWGDaemonSummaryResponse::SWGDaemonSummaryResponse() { - version = nullptr; - m_version_isSet = false; - qt_version = nullptr; - m_qt_version_isSet = false; - dsp_rx_bits = 0; - m_dsp_rx_bits_isSet = false; - dsp_tx_bits = 0; - m_dsp_tx_bits_isSet = false; - pid = 0; - m_pid_isSet = false; - appname = nullptr; - m_appname_isSet = false; - architecture = nullptr; - m_architecture_isSet = false; - os = nullptr; - m_os_isSet = false; - logging = nullptr; - m_logging_isSet = false; - sampling_device = nullptr; - m_sampling_device_isSet = false; -} - -SWGDaemonSummaryResponse::~SWGDaemonSummaryResponse() { - this->cleanup(); -} - -void -SWGDaemonSummaryResponse::init() { - version = new QString(""); - m_version_isSet = false; - qt_version = new QString(""); - m_qt_version_isSet = false; - dsp_rx_bits = 0; - m_dsp_rx_bits_isSet = false; - dsp_tx_bits = 0; - m_dsp_tx_bits_isSet = false; - pid = 0; - m_pid_isSet = false; - appname = new QString(""); - m_appname_isSet = false; - architecture = new QString(""); - m_architecture_isSet = false; - os = new QString(""); - m_os_isSet = false; - logging = new SWGLoggingInfo(); - m_logging_isSet = false; - sampling_device = new SWGSamplingDevice(); - m_sampling_device_isSet = false; -} - -void -SWGDaemonSummaryResponse::cleanup() { - if(version != nullptr) { - delete version; - } - if(qt_version != nullptr) { - delete qt_version; - } - - - - if(appname != nullptr) { - delete appname; - } - if(architecture != nullptr) { - delete architecture; - } - if(os != nullptr) { - delete os; - } - if(logging != nullptr) { - delete logging; - } - if(sampling_device != nullptr) { - delete sampling_device; - } -} - -SWGDaemonSummaryResponse* -SWGDaemonSummaryResponse::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGDaemonSummaryResponse::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&version, pJson["version"], "QString", "QString"); - - ::SWGSDRangel::setValue(&qt_version, pJson["qtVersion"], "QString", "QString"); - - ::SWGSDRangel::setValue(&dsp_rx_bits, pJson["dspRxBits"], "qint32", ""); - - ::SWGSDRangel::setValue(&dsp_tx_bits, pJson["dspTxBits"], "qint32", ""); - - ::SWGSDRangel::setValue(&pid, pJson["pid"], "qint32", ""); - - ::SWGSDRangel::setValue(&appname, pJson["appname"], "QString", "QString"); - - ::SWGSDRangel::setValue(&architecture, pJson["architecture"], "QString", "QString"); - - ::SWGSDRangel::setValue(&os, pJson["os"], "QString", "QString"); - - ::SWGSDRangel::setValue(&logging, pJson["logging"], "SWGLoggingInfo", "SWGLoggingInfo"); - - ::SWGSDRangel::setValue(&sampling_device, pJson["samplingDevice"], "SWGSamplingDevice", "SWGSamplingDevice"); - -} - -QString -SWGDaemonSummaryResponse::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGDaemonSummaryResponse::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(version != nullptr && *version != QString("")){ - toJsonValue(QString("version"), version, obj, QString("QString")); - } - if(qt_version != nullptr && *qt_version != QString("")){ - toJsonValue(QString("qtVersion"), qt_version, obj, QString("QString")); - } - if(m_dsp_rx_bits_isSet){ - obj->insert("dspRxBits", QJsonValue(dsp_rx_bits)); - } - if(m_dsp_tx_bits_isSet){ - obj->insert("dspTxBits", QJsonValue(dsp_tx_bits)); - } - if(m_pid_isSet){ - obj->insert("pid", QJsonValue(pid)); - } - if(appname != nullptr && *appname != QString("")){ - toJsonValue(QString("appname"), appname, obj, QString("QString")); - } - if(architecture != nullptr && *architecture != QString("")){ - toJsonValue(QString("architecture"), architecture, obj, QString("QString")); - } - if(os != nullptr && *os != QString("")){ - toJsonValue(QString("os"), os, obj, QString("QString")); - } - if((logging != nullptr) && (logging->isSet())){ - toJsonValue(QString("logging"), logging, obj, QString("SWGLoggingInfo")); - } - if((sampling_device != nullptr) && (sampling_device->isSet())){ - toJsonValue(QString("samplingDevice"), sampling_device, obj, QString("SWGSamplingDevice")); - } - - return obj; -} - -QString* -SWGDaemonSummaryResponse::getVersion() { - return version; -} -void -SWGDaemonSummaryResponse::setVersion(QString* version) { - this->version = version; - this->m_version_isSet = true; -} - -QString* -SWGDaemonSummaryResponse::getQtVersion() { - return qt_version; -} -void -SWGDaemonSummaryResponse::setQtVersion(QString* qt_version) { - this->qt_version = qt_version; - this->m_qt_version_isSet = true; -} - -qint32 -SWGDaemonSummaryResponse::getDspRxBits() { - return dsp_rx_bits; -} -void -SWGDaemonSummaryResponse::setDspRxBits(qint32 dsp_rx_bits) { - this->dsp_rx_bits = dsp_rx_bits; - this->m_dsp_rx_bits_isSet = true; -} - -qint32 -SWGDaemonSummaryResponse::getDspTxBits() { - return dsp_tx_bits; -} -void -SWGDaemonSummaryResponse::setDspTxBits(qint32 dsp_tx_bits) { - this->dsp_tx_bits = dsp_tx_bits; - this->m_dsp_tx_bits_isSet = true; -} - -qint32 -SWGDaemonSummaryResponse::getPid() { - return pid; -} -void -SWGDaemonSummaryResponse::setPid(qint32 pid) { - this->pid = pid; - this->m_pid_isSet = true; -} - -QString* -SWGDaemonSummaryResponse::getAppname() { - return appname; -} -void -SWGDaemonSummaryResponse::setAppname(QString* appname) { - this->appname = appname; - this->m_appname_isSet = true; -} - -QString* -SWGDaemonSummaryResponse::getArchitecture() { - return architecture; -} -void -SWGDaemonSummaryResponse::setArchitecture(QString* architecture) { - this->architecture = architecture; - this->m_architecture_isSet = true; -} - -QString* -SWGDaemonSummaryResponse::getOs() { - return os; -} -void -SWGDaemonSummaryResponse::setOs(QString* os) { - this->os = os; - this->m_os_isSet = true; -} - -SWGLoggingInfo* -SWGDaemonSummaryResponse::getLogging() { - return logging; -} -void -SWGDaemonSummaryResponse::setLogging(SWGLoggingInfo* logging) { - this->logging = logging; - this->m_logging_isSet = true; -} - -SWGSamplingDevice* -SWGDaemonSummaryResponse::getSamplingDevice() { - return sampling_device; -} -void -SWGDaemonSummaryResponse::setSamplingDevice(SWGSamplingDevice* sampling_device) { - this->sampling_device = sampling_device; - this->m_sampling_device_isSet = true; -} - - -bool -SWGDaemonSummaryResponse::isSet(){ - bool isObjectUpdated = false; - do{ - if(version != nullptr && *version != QString("")){ isObjectUpdated = true; break;} - if(qt_version != nullptr && *qt_version != QString("")){ isObjectUpdated = true; break;} - if(m_dsp_rx_bits_isSet){ isObjectUpdated = true; break;} - if(m_dsp_tx_bits_isSet){ isObjectUpdated = true; break;} - if(m_pid_isSet){ isObjectUpdated = true; break;} - if(appname != nullptr && *appname != QString("")){ isObjectUpdated = true; break;} - if(architecture != nullptr && *architecture != QString("")){ isObjectUpdated = true; break;} - if(os != nullptr && *os != QString("")){ isObjectUpdated = true; break;} - if(logging != nullptr && logging->isSet()){ isObjectUpdated = true; break;} - if(sampling_device != nullptr && sampling_device->isSet()){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.h deleted file mode 100644 index 5444e7160..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.h +++ /dev/null @@ -1,115 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGDaemonSummaryResponse.h - * - * Summarized information about this SDRdaemon instance - */ - -#ifndef SWGDaemonSummaryResponse_H_ -#define SWGDaemonSummaryResponse_H_ - -#include - - -#include "SWGLoggingInfo.h" -#include "SWGSamplingDevice.h" -#include - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGDaemonSummaryResponse: public SWGObject { -public: - SWGDaemonSummaryResponse(); - SWGDaemonSummaryResponse(QString* json); - virtual ~SWGDaemonSummaryResponse(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGDaemonSummaryResponse* fromJson(QString &jsonString) override; - - QString* getVersion(); - void setVersion(QString* version); - - QString* getQtVersion(); - void setQtVersion(QString* qt_version); - - qint32 getDspRxBits(); - void setDspRxBits(qint32 dsp_rx_bits); - - qint32 getDspTxBits(); - void setDspTxBits(qint32 dsp_tx_bits); - - qint32 getPid(); - void setPid(qint32 pid); - - QString* getAppname(); - void setAppname(QString* appname); - - QString* getArchitecture(); - void setArchitecture(QString* architecture); - - QString* getOs(); - void setOs(QString* os); - - SWGLoggingInfo* getLogging(); - void setLogging(SWGLoggingInfo* logging); - - SWGSamplingDevice* getSamplingDevice(); - void setSamplingDevice(SWGSamplingDevice* sampling_device); - - - virtual bool isSet() override; - -private: - QString* version; - bool m_version_isSet; - - QString* qt_version; - bool m_qt_version_isSet; - - qint32 dsp_rx_bits; - bool m_dsp_rx_bits_isSet; - - qint32 dsp_tx_bits; - bool m_dsp_tx_bits_isSet; - - qint32 pid; - bool m_pid_isSet; - - QString* appname; - bool m_appname_isSet; - - QString* architecture; - bool m_architecture_isSet; - - QString* os; - bool m_os_isSet; - - SWGLoggingInfo* logging; - bool m_logging_isSet; - - SWGSamplingDevice* sampling_device; - bool m_sampling_device_isSet; - -}; - -} - -#endif /* SWGDaemonSummaryResponse_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.cpp index 17afc28dc..185ace67f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h index 8529dbdd8..92e4fe974 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index 612f07eed..54fb7b865 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index 987195af4..65d3ada4a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.cpp index 987eb2368..5e7a90b9f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h index 52deff57e..ed2d57806 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp index 12dda6ba1..8947c6bb0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h index 303a9b59f..72a151862 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.cpp index b2b344897..53ec12ead 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h index 3b5d7d70a..19b3fc539 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 7c5e91e85..ae80deceb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index af0ab758c..e632782b9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceState.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceState.cpp index 37dadcafb..0ce157479 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceState.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceState.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceState.h b/swagger/sdrangel/code/qt5/client/SWGDeviceState.h index 6a3cc01a6..70e537bbd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceState.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceState.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.cpp index eeb0f1023..71c4333d4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h index 6a0b6ef51..cdb29ed93 100644 --- a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp index 51cf6ca3b..f1d75d109 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h index dfed662f2..d3ed6060c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProPlusSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp index b5d88781f..91ecded40 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h index b22ac03d2..f53a391c9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFCDProSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp index c326c7edc..0d3d87d25 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h index 76fae2ffe..7a92948b0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp index 7167cc15d..368ae008f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h index cd6c5e34e..00b87f695 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp b/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp index df7242562..984912dd2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFrequency.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequency.h b/swagger/sdrangel/code/qt5/client/SWGFrequency.h index 9d3574268..0c45dec60 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFrequency.h +++ b/swagger/sdrangel/code/qt5/client/SWGFrequency.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp index bb00f3945..eda09d39b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h index 5f9be8932..a6da06ed1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h +++ b/swagger/sdrangel/code/qt5/client/SWGFrequencyBand.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGGain.cpp b/swagger/sdrangel/code/qt5/client/SWGGain.cpp index 33c3d492a..b9c24118d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGain.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGGain.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGGain.h b/swagger/sdrangel/code/qt5/client/SWGGain.h index 13044042c..d92210dec 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGain.h +++ b/swagger/sdrangel/code/qt5/client/SWGGain.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp index 915407316..4cfce55d9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h index dd1671722..03fa6f021 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.cpp index fe3afd9e4..e57284602 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h index 146a8574b..c5526b8b1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp b/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp index 5d4260ea4..1b5760c58 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHelpers.h b/swagger/sdrangel/code/qt5/client/SWGHelpers.h index 1ff0c05de..6569729f7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHelpers.h +++ b/swagger/sdrangel/code/qt5/client/SWGHelpers.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHttpRequest.cpp b/swagger/sdrangel/code/qt5/client/SWGHttpRequest.cpp index 7368167aa..d85335e38 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHttpRequest.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHttpRequest.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGHttpRequest.h b/swagger/sdrangel/code/qt5/client/SWGHttpRequest.h index b1f6b28e2..22b4a9fab 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHttpRequest.h +++ b/swagger/sdrangel/code/qt5/client/SWGHttpRequest.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp index bfce11328..cb53b9b89 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h index 78b6721ad..972ca1eca 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.cpp index b2ac5abb9..870db5ba7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h index 72b151503..0720fd7fb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.cpp index 488bec7bd..8d5a2768b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h index 4d822f691..6b0ef4768 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.cpp index 118cf5c5c..6ab7496f4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h index f4b552195..99839f8d5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp index e93b6122a..e54707350 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h index 7eed38549..717628e6f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp index d78eb30bb..db31ce7e3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h index 4b4f4f9a5..15f67a3c8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp index 97ceaab9e..b204931c6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h index 6cbcfb5b8..65cc56464 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp index 117ce0acd..5692c0051 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h index 2af5e4871..bab1e58c5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.cpp b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.cpp index f19bf923f..b53e914f0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h index aeaabe484..4390b1e03 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h +++ b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.cpp b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.cpp index e4576b8a8..db5c5bcba 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h index 6af2e76fc..ce7e54e46 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h +++ b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index a4e482e76..b5fc18c1c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com @@ -45,7 +45,6 @@ #include "SWGDaemonSinkSettings.h" #include "SWGDaemonSourceReport.h" #include "SWGDaemonSourceSettings.h" -#include "SWGDaemonSummaryResponse.h" #include "SWGDeviceListItem.h" #include "SWGDeviceReport.h" #include "SWGDeviceSet.h" @@ -211,9 +210,6 @@ namespace SWGSDRangel { if(QString("SWGDaemonSourceSettings").compare(type) == 0) { return new SWGDaemonSourceSettings(); } - if(QString("SWGDaemonSummaryResponse").compare(type) == 0) { - return new SWGDaemonSummaryResponse(); - } if(QString("SWGDeviceListItem").compare(type) == 0) { return new SWGDeviceListItem(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp index ddff665c1..1e9594ba1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h index 988b64b27..60eae67ba 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp index dd975c901..e6b7faf5b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h index cdbfccf15..5f0b12628 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp index a84b6650b..26584eab7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h index b747f49e6..dc6b0c06f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp index 83bb3d27e..0f90cc7ec 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h index 7eac3f51f..7a22248ff 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGObject.h b/swagger/sdrangel/code/qt5/client/SWGObject.h index 18552b17c..9110c5560 100644 --- a/swagger/sdrangel/code/qt5/client/SWGObject.h +++ b/swagger/sdrangel/code/qt5/client/SWGObject.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp index f56442b13..e3491d595 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h index 78d6ec47c..da0574f42 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp index eeabc3e31..071bb81e5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h index 7fb1eafd6..04403bceb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGPerseusSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp index 8721928ee..99a8c945e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h index 8684781df..1bdeb4504 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp index 613fb8791..8502ca139 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h index 78236feb8..10a0136e5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrInputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp index 159076dcb..2dba8c522 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h index 3beb113da..9530ccb52 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp index 7bfafbafe..f012edd6f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h index 43e0d0eaf..416904ba6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGPlutoSdrOutputSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetExport.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetExport.cpp index 0baa05f0b..ab6832efc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetExport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetExport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetExport.h b/swagger/sdrangel/code/qt5/client/SWGPresetExport.h index ddfe8b92b..f12664b5e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetExport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetExport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.cpp index ea00119f8..9ac0922a4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h index 1f365b9e0..192fb94e3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.cpp index e3674c6c7..909ae9ed1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h index 167c8e3a2..068faf6ac 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetImport.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetImport.cpp index 8382546cb..22ab35e98 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetImport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetImport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetImport.h b/swagger/sdrangel/code/qt5/client/SWGPresetImport.h index 637ac7ba4..c1fdd3b70 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetImport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetImport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetItem.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetItem.cpp index 66bdda5f8..1d82dfddc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetItem.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetItem.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetItem.h b/swagger/sdrangel/code/qt5/client/SWGPresetItem.h index 6c1bb8ac2..fb36bed26 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetItem.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.cpp b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.cpp index 446d7afe8..72cf9fcff 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h index fec28f18c..82a5b832f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresets.cpp b/swagger/sdrangel/code/qt5/client/SWGPresets.cpp index a38ff17c0..cd858dbb9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresets.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGPresets.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGPresets.h b/swagger/sdrangel/code/qt5/client/SWGPresets.h index 812220bc6..667452f15 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresets.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresets.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp index 513f8452b..95e2745f4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport.h b/swagger/sdrangel/code/qt5/client/SWGRDSReport.h index 3d6c769e9..a7786b816 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRDSReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp index c20953b74..29662225b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h index b8910a25d..52477339c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h +++ b/swagger/sdrangel/code/qt5/client/SWGRDSReport_altFrequencies.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp index cb34b7f97..9be0c8e65 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h index 9c9cccb0b..eb3496d5a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp index 62e4cbd90..7a72edc84 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h index 5198b9063..e2d56d103 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp index c2d7ef6f4..f0e5989d7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h index 6a1842f94..e7b05d5f4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp index b5e29c426..5eb8bfe52 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h index 1ac89b277..3206b625f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRPlaySettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp index d07ed8142..d6131d331 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h index 81dec0d76..a053c2054 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp index 48b33a891..11a96e9f8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h index 14a3198ad..fe0142428 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp index 94a6fd286..e2ffea067 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h index 401865ad4..bb5de79e4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp index 5f3f904b7..8511f3479 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h index 15bc36449..99f6e3f0d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp index d1aa6351f..8e8d7d64d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h index 2b1923def..b13188b8f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp index da8df7dd3..f71d8d048 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h index 2e226b1d2..c1ddd81c0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp index 94283e2f0..2c9138451 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h index a934b42a3..ab90b8ae5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp index b4c3b5a80..ffe4ee604 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h index 46a0fa06e..6340289a9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp b/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp index 56f85fe9f..dc6bf97bc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSampleRate.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSampleRate.h b/swagger/sdrangel/code/qt5/client/SWGSampleRate.h index 29ef96967..de921c75d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSampleRate.h +++ b/swagger/sdrangel/code/qt5/client/SWGSampleRate.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.cpp index a493eb3cf..3e9059a66 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h index f7610c397..cd66b8533 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.cpp b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.cpp index b62359a1b..6f5762b18 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h index 77fc9898b..441a0b2e3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp index 7e5dbe029..162eff398 100644 --- a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h index dcaad4efc..9bf4bc08a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGTestSourceSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp index 2b9c8420a..89fd688b5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h index b339e8406..535c38ef1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp index 480e7ef05..b6af36abc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h index 44245aaf7..07cc67639 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp index bf1ac735a..d05b9de59 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h index 1c44aec8e..2891943e1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp index 4bcc5a415..4233698db 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h index 25f2710c5..356465bc7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp index 7744ceb65..4184c1f4e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h index fbd0ab711..f867afe6d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp index b8e6dc836..653d4d60d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h index 65efcc76b..5dba40fa5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGWFMDemodSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp index 4ba4400f5..3cc4dde0a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h index ff1741982..a5cb7083b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp index d2a219d46..74fcbdc0b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h index f315ede70..10106245a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h @@ -1,6 +1,6 @@ /** * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- * * OpenAPI spec version: 4.1.0 * Contact: f4exb06@gmail.com From 3a32cf156cf28dcdb8dbea71672c69273035a430 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 16:32:14 +0200 Subject: [PATCH 732/956] Rename DaemonSrc to DaemonSource --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 98 +++++++++---------- plugins/channeltx/daemonsrc/daemonsrc.h | 28 +++--- plugins/channeltx/daemonsrc/daemonsrcgui.cpp | 82 ++++++++-------- plugins/channeltx/daemonsrc/daemonsrcgui.h | 18 ++-- plugins/channeltx/daemonsrc/daemonsrcgui.ui | 4 +- .../channeltx/daemonsrc/daemonsrcplugin.cpp | 24 ++--- plugins/channeltx/daemonsrc/daemonsrcplugin.h | 4 +- .../channeltx/daemonsrc/daemonsrcsettings.cpp | 8 +- .../channeltx/daemonsrc/daemonsrcsettings.h | 4 +- .../channeltx/daemonsrc/daemonsrcthread.cpp | 40 ++++---- plugins/channeltx/daemonsrc/daemonsrcthread.h | 6 +- sdrbase/webapi/webapirequestmapper.cpp | 2 +- 12 files changed, 159 insertions(+), 159 deletions(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index 318188243..8c13d760e 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -33,15 +33,15 @@ #include "daemonsrcthread.h" #include "daemonsrc.h" -MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgSampleRateNotification, Message) -MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgConfigureDaemonSrc, Message) -MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgQueryStreamData, Message) -MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgReportStreamData, Message) +MESSAGE_CLASS_DEFINITION(DaemonSource::MsgSampleRateNotification, Message) +MESSAGE_CLASS_DEFINITION(DaemonSource::MsgConfigureDaemonSource, Message) +MESSAGE_CLASS_DEFINITION(DaemonSource::MsgQueryStreamData, Message) +MESSAGE_CLASS_DEFINITION(DaemonSource::MsgReportStreamData, Message) -const QString DaemonSrc::m_channelIdURI = "sdrangel.channeltx.daemonsrc"; -const QString DaemonSrc::m_channelId ="DaemonSrc"; +const QString DaemonSource::m_channelIdURI = "sdrangel.channeltx.daemonsrc"; +const QString DaemonSource::m_channelId ="DaemonSource"; -DaemonSrc::DaemonSrc(DeviceSinkAPI *deviceAPI) : +DaemonSource::DaemonSource(DeviceSinkAPI *deviceAPI) : ChannelSourceAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_sourceThread(0), @@ -62,7 +62,7 @@ DaemonSrc::DaemonSrc(DeviceSinkAPI *deviceAPI) : m_deviceAPI->addChannelAPI(this); } -DaemonSrc::~DaemonSrc() +DaemonSource::~DaemonSource() { m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); @@ -70,32 +70,32 @@ DaemonSrc::~DaemonSrc() delete m_channelizer; } -void DaemonSrc::pull(Sample& sample) +void DaemonSource::pull(Sample& sample) { m_dataReadQueue.readSample(sample); } -void DaemonSrc::pullAudio(int nbSamples __attribute__((unused))) +void DaemonSource::pullAudio(int nbSamples __attribute__((unused))) { } -void DaemonSrc::start() +void DaemonSource::start() { - qDebug("DaemonSrc::start"); + qDebug("DaemonSource::start"); if (m_running) { stop(); } - m_sourceThread = new DaemonSrcThread(&m_dataQueue); + m_sourceThread = new DaemonSourceThread(&m_dataQueue); m_sourceThread->startStop(true); m_sourceThread->dataBind(m_settings.m_dataAddress, m_settings.m_dataPort); m_running = true; } -void DaemonSrc::stop() +void DaemonSource::stop() { - qDebug("DaemonSrc::stop"); + qDebug("DaemonSource::stop"); if (m_sourceThread != 0) { @@ -107,22 +107,22 @@ void DaemonSrc::stop() m_running = false; } -void DaemonSrc::setDataLink(const QString& dataAddress, uint16_t dataPort) +void DaemonSource::setDataLink(const QString& dataAddress, uint16_t dataPort) { - DaemonSrcSettings settings = m_settings; + DaemonSourceSettings settings = m_settings; settings.m_dataAddress = dataAddress; settings.m_dataPort = dataPort; - MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(settings, false); + MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(settings, false); m_inputMessageQueue.push(msg); } -bool DaemonSrc::handleMessage(const Message& cmd) +bool DaemonSource::handleMessage(const Message& cmd) { if (UpChannelizer::MsgChannelizerNotification::match(cmd)) { UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "DaemonSrc::handleMessage: MsgChannelizerNotification:" + qDebug() << "DaemonSource::handleMessage: MsgChannelizerNotification:" << " basebandSampleRate: " << notif.getBasebandSampleRate() << " outputSampleRate: " << notif.getSampleRate() << " inputFrequencyOffset: " << notif.getFrequencyOffset(); @@ -135,10 +135,10 @@ bool DaemonSrc::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureDaemonSrc::match(cmd)) + else if (MsgConfigureDaemonSource::match(cmd)) { - MsgConfigureDaemonSrc& cfg = (MsgConfigureDaemonSrc&) cmd; - qDebug() << "MsgConfigureDaemonSrc::handleMessage: MsgConfigureDaemonSrc"; + MsgConfigureDaemonSource& cfg = (MsgConfigureDaemonSource&) cmd; + qDebug() << "MsgConfigureDaemonSource::handleMessage: MsgConfigureDaemonSource"; applySettings(cfg.getSettings(), cfg.getForce()); return true; @@ -170,31 +170,31 @@ bool DaemonSrc::handleMessage(const Message& cmd) return false; } -QByteArray DaemonSrc::serialize() const +QByteArray DaemonSource::serialize() const { return m_settings.serialize(); } -bool DaemonSrc::deserialize(const QByteArray& data __attribute__((unused))) +bool DaemonSource::deserialize(const QByteArray& data __attribute__((unused))) { if (m_settings.deserialize(data)) { - MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(m_settings, true); + MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(m_settings, true); m_inputMessageQueue.push(msg); return true; } else { m_settings.resetToDefaults(); - MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(m_settings, true); + MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(m_settings, true); m_inputMessageQueue.push(msg); return false; } } -void DaemonSrc::applySettings(const DaemonSrcSettings& settings, bool force) +void DaemonSource::applySettings(const DaemonSourceSettings& settings, bool force) { - qDebug() << "DaemonSrc::applySettings:" + qDebug() << "DaemonSource::applySettings:" << " m_dataAddress: " << settings.m_dataAddress << " m_dataPort: " << settings.m_dataPort << " force: " << force; @@ -216,11 +216,11 @@ void DaemonSrc::applySettings(const DaemonSrcSettings& settings, bool force) m_settings = settings; } -void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unused))) +void DaemonSource::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unused))) { if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) { - qWarning("DaemonSrc::handleDataBlock: incomplete data block: not processing"); + qWarning("DaemonSource::handleDataBlock: incomplete data block: not processing"); } else { @@ -242,12 +242,12 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu } } - //qDebug("DaemonSrc::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); + //qDebug("DaemonSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); // Need to use the CM256 recovery if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) { - qDebug("DaemonSrc::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); + qDebug("DaemonSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); CM256::cm256_encoder_params paramsCM256; paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes @@ -267,7 +267,7 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode { - qWarning() << "DaemonSrc::handleDataBlock: decode CM256 error:" + qWarning() << "DaemonSource::handleDataBlock: decode CM256 error:" << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; } @@ -298,7 +298,7 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu { if (!(m_currentMeta == *metaData)) { - printMeta("DaemonSrc::handleDataBlock", metaData); + printMeta("DaemonSource::handleDataBlock", metaData); if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) { @@ -311,7 +311,7 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu } else { - qWarning() << "DaemonSrc::handleDataBlock: recovered meta: invalid CRC32"; + qWarning() << "DaemonSource::handleDataBlock: recovered meta: invalid CRC32"; } } @@ -319,7 +319,7 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu } } -void DaemonSrc::handleData() +void DaemonSource::handleData() { SDRDaemonDataBlock* dataBlock; @@ -328,7 +328,7 @@ void DaemonSrc::handleData() } } -void DaemonSrc::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) +void DaemonSource::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) { qDebug().noquote() << header << ": " << "|" << metaData->m_centerFrequency @@ -342,16 +342,16 @@ void DaemonSrc::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) << "|"; } -uint32_t DaemonSrc::calculateDataReadQueueSize(int sampleRate) +uint32_t DaemonSource::calculateDataReadQueueSize(int sampleRate) { // scale for 20 blocks at 48 kS/s. Take next even number. uint32_t maxSize = sampleRate / 2400; maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; - qDebug("DaemonSrc::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); + qDebug("DaemonSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); return maxSize; } -int DaemonSrc::webapiSettingsGet( +int DaemonSource::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { @@ -361,13 +361,13 @@ int DaemonSrc::webapiSettingsGet( return 200; } -int DaemonSrc::webapiSettingsPutPatch( +int DaemonSource::webapiSettingsPutPatch( bool force, const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - DaemonSrcSettings settings = m_settings; + DaemonSourceSettings settings = m_settings; if (channelSettingsKeys.contains("dataAddress")) { settings.m_dataAddress = *response.getDaemonSourceSettings()->getDataAddress(); @@ -389,13 +389,13 @@ int DaemonSrc::webapiSettingsPutPatch( settings.m_title = *response.getDaemonSourceSettings()->getTitle(); } - MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(settings, force); + MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(settings, force); m_inputMessageQueue.push(msg); - qDebug("DaemonSrc::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + qDebug("DaemonSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); if (m_guiMessageQueue) // forward to GUI if any { - MsgConfigureDaemonSrc *msgToGUI = MsgConfigureDaemonSrc::create(settings, force); + MsgConfigureDaemonSource *msgToGUI = MsgConfigureDaemonSource::create(settings, force); m_guiMessageQueue->push(msgToGUI); } @@ -404,7 +404,7 @@ int DaemonSrc::webapiSettingsPutPatch( return 200; } -int DaemonSrc::webapiReportGet( +int DaemonSource::webapiReportGet( SWGSDRangel::SWGChannelReport& response, QString& errorMessage __attribute__((unused))) { @@ -414,7 +414,7 @@ int DaemonSrc::webapiReportGet( return 200; } -void DaemonSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSrcSettings& settings) +void DaemonSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSourceSettings& settings) { if (response.getDaemonSourceSettings()->getDataAddress()) { *response.getDaemonSourceSettings()->getDataAddress() = settings.m_dataAddress; @@ -432,7 +432,7 @@ void DaemonSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& res } } -void DaemonSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +void DaemonSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { struct timeval tv; gettimeofday(&tv, 0); diff --git a/plugins/channeltx/daemonsrc/daemonsrc.h b/plugins/channeltx/daemonsrc/daemonsrc.h index efbd2bbed..1558b0f48 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.h +++ b/plugins/channeltx/daemonsrc/daemonsrc.h @@ -33,30 +33,30 @@ class ThreadedBasebandSampleSource; class UpChannelizer; class DeviceSinkAPI; -class DaemonSrcThread; +class DaemonSourceThread; class SDRDaemonDataBlock; -class DaemonSrc : public BasebandSampleSource, public ChannelSourceAPI { +class DaemonSource : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: - class MsgConfigureDaemonSrc : public Message { + class MsgConfigureDaemonSource : public Message { MESSAGE_CLASS_DECLARATION public: - const DaemonSrcSettings& getSettings() const { return m_settings; } + const DaemonSourceSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureDaemonSrc* create(const DaemonSrcSettings& settings, bool force) + static MsgConfigureDaemonSource* create(const DaemonSourceSettings& settings, bool force) { - return new MsgConfigureDaemonSrc(settings, force); + return new MsgConfigureDaemonSource(settings, force); } private: - DaemonSrcSettings m_settings; + DaemonSourceSettings m_settings; bool m_force; - MsgConfigureDaemonSrc(const DaemonSrcSettings& settings, bool force) : + MsgConfigureDaemonSource(const DaemonSourceSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -176,8 +176,8 @@ public: { } }; - DaemonSrc(DeviceSinkAPI *deviceAPI); - ~DaemonSrc(); + DaemonSource(DeviceSinkAPI *deviceAPI); + ~DaemonSource(); virtual void destroy() { delete this; } @@ -218,12 +218,12 @@ private: ThreadedBasebandSampleSource* m_threadedChannelizer; UpChannelizer* m_channelizer; SDRDaemonDataQueue m_dataQueue; - DaemonSrcThread *m_sourceThread; + DaemonSourceThread *m_sourceThread; CM256 m_cm256; CM256 *m_cm256p; bool m_running; - DaemonSrcSettings m_settings; + DaemonSourceSettings m_settings; CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) SDRDaemonMetaDataFEC m_currentMeta; @@ -233,11 +233,11 @@ private: uint32_t m_nbCorrectableErrors; //!< count of correctable errors in number of blocks uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks - void applySettings(const DaemonSrcSettings& settings, bool force = false); + void applySettings(const DaemonSourceSettings& settings, bool force = false); void handleDataBlock(SDRDaemonDataBlock *dataBlock); void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); uint32_t calculateDataReadQueueSize(int sampleRate); - void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSrcSettings& settings); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSourceSettings& settings); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); private slots: diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp index 3823a9869..b74fd5f04 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp @@ -23,48 +23,48 @@ #include "ui_daemonsrcgui.h" #include "daemonsrcgui.h" -DaemonSrcGUI* DaemonSrcGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) +DaemonSourceGUI* DaemonSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) { - DaemonSrcGUI* gui = new DaemonSrcGUI(pluginAPI, deviceUISet, channelTx); + DaemonSourceGUI* gui = new DaemonSourceGUI(pluginAPI, deviceUISet, channelTx); return gui; } -void DaemonSrcGUI::destroy() +void DaemonSourceGUI::destroy() { delete this; } -void DaemonSrcGUI::setName(const QString& name) +void DaemonSourceGUI::setName(const QString& name) { setObjectName(name); } -QString DaemonSrcGUI::getName() const +QString DaemonSourceGUI::getName() const { return objectName(); } -qint64 DaemonSrcGUI::getCenterFrequency() const { +qint64 DaemonSourceGUI::getCenterFrequency() const { return 0; } -void DaemonSrcGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +void DaemonSourceGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) { } -void DaemonSrcGUI::resetToDefaults() +void DaemonSourceGUI::resetToDefaults() { m_settings.resetToDefaults(); displaySettings(); applySettings(true); } -QByteArray DaemonSrcGUI::serialize() const +QByteArray DaemonSourceGUI::serialize() const { return m_settings.serialize(); } -bool DaemonSrcGUI::deserialize(const QByteArray& data) +bool DaemonSourceGUI::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { displaySettings(); @@ -76,26 +76,26 @@ bool DaemonSrcGUI::deserialize(const QByteArray& data) } } -bool DaemonSrcGUI::handleMessage(const Message& message) +bool DaemonSourceGUI::handleMessage(const Message& message) { - if (DaemonSrc::MsgSampleRateNotification::match(message)) + if (DaemonSource::MsgSampleRateNotification::match(message)) { - DaemonSrc::MsgSampleRateNotification& notif = (DaemonSrc::MsgSampleRateNotification&) message; + DaemonSource::MsgSampleRateNotification& notif = (DaemonSource::MsgSampleRateNotification&) message; m_channelMarker.setBandwidth(notif.getSampleRate()); return true; } - else if (DaemonSrc::MsgConfigureDaemonSrc::match(message)) + else if (DaemonSource::MsgConfigureDaemonSource::match(message)) { - const DaemonSrc::MsgConfigureDaemonSrc& cfg = (DaemonSrc::MsgConfigureDaemonSrc&) message; + const DaemonSource::MsgConfigureDaemonSource& cfg = (DaemonSource::MsgConfigureDaemonSource&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); blockApplySettings(false); return true; } - else if (DaemonSrc::MsgReportStreamData::match(message)) + else if (DaemonSource::MsgReportStreamData::match(message)) { - const DaemonSrc::MsgReportStreamData& report = (DaemonSrc::MsgReportStreamData&) message; + const DaemonSource::MsgReportStreamData& report = (DaemonSource::MsgReportStreamData&) message; ui->sampleRate->setText(QString("%1").arg(report.get_sampleRate())); QString nominalNbBlocksText = QString("%1/%2") .arg(report.get_nbOriginalBlocks() + report.get_nbFECBlocks()) @@ -152,9 +152,9 @@ bool DaemonSrcGUI::handleMessage(const Message& message) } } -DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx __attribute__((unused)), QWidget* parent) : +DaemonSourceGUI::DaemonSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx __attribute__((unused)), QWidget* parent) : RollupWidget(parent), - ui(new Ui::DaemonSrcGUI), + ui(new Ui::DaemonSourceGUI), m_pluginAPI(pluginAPI), m_deviceUISet(deviceUISet), m_countUnrecoverable(0), @@ -171,7 +171,7 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); - m_daemonSrc = (DaemonSrc*) channelTx; + m_daemonSrc = (DaemonSource*) channelTx; m_daemonSrc->setMessageQueueToGUI(getInputMessageQueue()); connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); @@ -185,7 +185,7 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb m_settings.setChannelMarker(&m_channelMarker); - m_deviceUISet->registerTxChannelInstance(DaemonSrc::m_channelIdURI, this); + m_deviceUISet->registerTxChannelInstance(DaemonSource::m_channelIdURI, this); m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); @@ -198,30 +198,30 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb applySettings(true); } -DaemonSrcGUI::~DaemonSrcGUI() +DaemonSourceGUI::~DaemonSourceGUI() { m_deviceUISet->removeTxChannelInstance(this); delete m_daemonSrc; delete ui; } -void DaemonSrcGUI::blockApplySettings(bool block) +void DaemonSourceGUI::blockApplySettings(bool block) { m_doApplySettings = !block; } -void DaemonSrcGUI::applySettings(bool force) +void DaemonSourceGUI::applySettings(bool force) { if (m_doApplySettings) { setTitleColor(m_channelMarker.getColor()); - DaemonSrc::MsgConfigureDaemonSrc* message = DaemonSrc::MsgConfigureDaemonSrc::create(m_settings, force); + DaemonSource::MsgConfigureDaemonSource* message = DaemonSource::MsgConfigureDaemonSource::create(m_settings, force); m_daemonSrc->getInputMessageQueue()->push(message); } } -void DaemonSrcGUI::displaySettings() +void DaemonSourceGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(0); @@ -239,17 +239,17 @@ void DaemonSrcGUI::displaySettings() blockApplySettings(false); } -void DaemonSrcGUI::leaveEvent(QEvent*) +void DaemonSourceGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); } -void DaemonSrcGUI::enterEvent(QEvent*) +void DaemonSourceGUI::enterEvent(QEvent*) { m_channelMarker.setHighlighted(true); } -void DaemonSrcGUI::handleSourceMessages() +void DaemonSourceGUI::handleSourceMessages() { Message* message; @@ -262,11 +262,11 @@ void DaemonSrcGUI::handleSourceMessages() } } -void DaemonSrcGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) +void DaemonSourceGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { } -void DaemonSrcGUI::onMenuDialogCalled(const QPoint &p) +void DaemonSourceGUI::onMenuDialogCalled(const QPoint &p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); dialog.move(p); @@ -281,13 +281,13 @@ void DaemonSrcGUI::onMenuDialogCalled(const QPoint &p) applySettings(); } -void DaemonSrcGUI::on_dataAddress_returnPressed() +void DaemonSourceGUI::on_dataAddress_returnPressed() { m_settings.m_dataAddress = ui->dataAddress->text(); applySettings(); } -void DaemonSrcGUI::on_dataPort_returnPressed() +void DaemonSourceGUI::on_dataPort_returnPressed() { bool dataOk; int dataPort = ui->dataPort->text().toInt(&dataOk); @@ -304,7 +304,7 @@ void DaemonSrcGUI::on_dataPort_returnPressed() applySettings(); } -void DaemonSrcGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +void DaemonSourceGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) { m_settings.m_dataAddress = ui->dataAddress->text(); @@ -319,7 +319,7 @@ void DaemonSrcGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused applySettings(); } -void DaemonSrcGUI::on_eventCountsReset_clicked(bool checked __attribute__((unused))) +void DaemonSourceGUI::on_eventCountsReset_clicked(bool checked __attribute__((unused))) { m_countUnrecoverable = 0; m_countRecovered = 0; @@ -328,7 +328,7 @@ void DaemonSrcGUI::on_eventCountsReset_clicked(bool checked __attribute__((unuse displayEventTimer(); } -void DaemonSrcGUI::displayEventCounts() +void DaemonSourceGUI::displayEventCounts() { QString nstr = QString("%1").arg(m_countUnrecoverable, 3, 10, QChar('0')); ui->eventUnrecText->setText(nstr); @@ -336,7 +336,7 @@ void DaemonSrcGUI::displayEventCounts() ui->eventRecText->setText(nstr); } -void DaemonSrcGUI::displayEventStatus(int recoverableCount, int unrecoverableCount) +void DaemonSourceGUI::displayEventStatus(int recoverableCount, int unrecoverableCount) { if (unrecoverableCount == 0) @@ -353,7 +353,7 @@ void DaemonSrcGUI::displayEventStatus(int recoverableCount, int unrecoverableCou } } -void DaemonSrcGUI::displayEventTimer() +void DaemonSourceGUI::displayEventTimer() { int elapsedTimeMillis = m_time.elapsed(); QTime recordLength(0, 0, 0, 0); @@ -362,11 +362,11 @@ void DaemonSrcGUI::displayEventTimer() ui->eventCountsTimeText->setText(s_time); } -void DaemonSrcGUI::tick() +void DaemonSourceGUI::tick() { if (++m_tickCount == 20) // once per second { - DaemonSrc::MsgQueryStreamData *msg = DaemonSrc::MsgQueryStreamData::create(); + DaemonSource::MsgQueryStreamData *msg = DaemonSource::MsgQueryStreamData::create(); m_daemonSrc->getInputMessageQueue()->push(msg); displayEventTimer(); @@ -375,6 +375,6 @@ void DaemonSrcGUI::tick() } } -void DaemonSrcGUI::channelMarkerChangedByCursor() +void DaemonSourceGUI::channelMarkerChangedByCursor() { } diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.h b/plugins/channeltx/daemonsrc/daemonsrcgui.h index 4d720e3b4..baf656ac9 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.h +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.h @@ -29,17 +29,17 @@ class PluginAPI; class DeviceUISet; class BasebandSampleSource; -class DaemonSrc; +class DaemonSource; namespace Ui { - class DaemonSrcGUI; + class DaemonSourceGUI; } -class DaemonSrcGUI : public RollupWidget, public PluginInstanceGUI { +class DaemonSourceGUI : public RollupWidget, public PluginInstanceGUI { Q_OBJECT public: - static DaemonSrcGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); + static DaemonSourceGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); virtual void destroy(); void setName(const QString& name); @@ -57,14 +57,14 @@ public slots: void channelMarkerChangedByCursor(); private: - Ui::DaemonSrcGUI* ui; + Ui::DaemonSourceGUI* ui; PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; ChannelMarker m_channelMarker; - DaemonSrcSettings m_settings; + DaemonSourceSettings m_settings; bool m_doApplySettings; - DaemonSrc* m_daemonSrc; + DaemonSource* m_daemonSrc; MessageQueue m_inputMessageQueue; uint32_t m_countUnrecoverable; @@ -77,8 +77,8 @@ private: QTime m_time; uint32_t m_tickCount; - explicit DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); - virtual ~DaemonSrcGUI(); + explicit DaemonSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); + virtual ~DaemonSourceGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.ui b/plugins/channeltx/daemonsrc/daemonsrcgui.ui index 3b4ee8bdd..9baae1bf1 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.ui +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.ui @@ -1,7 +1,7 @@ - DaemonSrcGUI - + DaemonSourceGUI + 0 diff --git a/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp b/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp index 83d2f9ab8..d1ce2307b 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp @@ -23,7 +23,7 @@ #include "daemonsrc.h" #include "daemonsrcplugin.h" -const PluginDescriptor DaemonSrcPlugin::m_pluginDescriptor = { +const PluginDescriptor DaemonSourcePlugin::m_pluginDescriptor = { QString("Daemon channel source"), QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), @@ -32,47 +32,47 @@ const PluginDescriptor DaemonSrcPlugin::m_pluginDescriptor = { QString("https://github.com/f4exb/sdrangel") }; -DaemonSrcPlugin::DaemonSrcPlugin(QObject* parent) : +DaemonSourcePlugin::DaemonSourcePlugin(QObject* parent) : QObject(parent), m_pluginAPI(0) { } -const PluginDescriptor& DaemonSrcPlugin::getPluginDescriptor() const +const PluginDescriptor& DaemonSourcePlugin::getPluginDescriptor() const { return m_pluginDescriptor; } -void DaemonSrcPlugin::initPlugin(PluginAPI* pluginAPI) +void DaemonSourcePlugin::initPlugin(PluginAPI* pluginAPI) { m_pluginAPI = pluginAPI; // register source - m_pluginAPI->registerTxChannel(DaemonSrc::m_channelIdURI, DaemonSrc::m_channelId, this); + m_pluginAPI->registerTxChannel(DaemonSource::m_channelIdURI, DaemonSource::m_channelId, this); } #ifdef SERVER_MODE -PluginInstanceGUI* DaemonSrcPlugin::createTxChannelGUI( +PluginInstanceGUI* DaemonSourcePlugin::createTxChannelGUI( DeviceUISet *deviceUISet __attribute__((unused)), BasebandSampleSource *txChannel __attribute__((unused))) { return 0; } #else -PluginInstanceGUI* DaemonSrcPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) +PluginInstanceGUI* DaemonSourcePlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { - return DaemonSrcGUI::create(m_pluginAPI, deviceUISet, txChannel); + return DaemonSourceGUI::create(m_pluginAPI, deviceUISet, txChannel); } #endif -BasebandSampleSource* DaemonSrcPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) +BasebandSampleSource* DaemonSourcePlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { - return new DaemonSrc(deviceAPI); + return new DaemonSource(deviceAPI); } -ChannelSourceAPI* DaemonSrcPlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +ChannelSourceAPI* DaemonSourcePlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) { - return new DaemonSrc(deviceAPI); + return new DaemonSource(deviceAPI); } diff --git a/plugins/channeltx/daemonsrc/daemonsrcplugin.h b/plugins/channeltx/daemonsrc/daemonsrcplugin.h index 9e353e38e..7477f2193 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcplugin.h +++ b/plugins/channeltx/daemonsrc/daemonsrcplugin.h @@ -23,13 +23,13 @@ class DeviceUISet; class BasebandSampleSource; -class DaemonSrcPlugin : public QObject, PluginInterface { +class DaemonSourcePlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) Q_PLUGIN_METADATA(IID "sdrangel.channeltx.daemonsrc") public: - explicit DaemonSrcPlugin(QObject* parent = 0); + explicit DaemonSourcePlugin(QObject* parent = 0); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp b/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp index 62e8a4dcf..411282c74 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp @@ -22,12 +22,12 @@ #include "settings/serializable.h" #include "daemonsrcsettings.h" -DaemonSrcSettings::DaemonSrcSettings() +DaemonSourceSettings::DaemonSourceSettings() { resetToDefaults(); } -void DaemonSrcSettings::resetToDefaults() +void DaemonSourceSettings::resetToDefaults() { m_dataAddress = "127.0.0.1"; m_dataPort = 9090; @@ -35,7 +35,7 @@ void DaemonSrcSettings::resetToDefaults() m_title = "Daemon source"; } -QByteArray DaemonSrcSettings::serialize() const +QByteArray DaemonSourceSettings::serialize() const { SimpleSerializer s(1); s.writeString(1, m_dataAddress); @@ -46,7 +46,7 @@ QByteArray DaemonSrcSettings::serialize() const return s.final(); } -bool DaemonSrcSettings::deserialize(const QByteArray& data) +bool DaemonSourceSettings::deserialize(const QByteArray& data) { SimpleDeserializer d(data); diff --git a/plugins/channeltx/daemonsrc/daemonsrcsettings.h b/plugins/channeltx/daemonsrc/daemonsrcsettings.h index a1fd83594..df1ab6736 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcsettings.h +++ b/plugins/channeltx/daemonsrc/daemonsrcsettings.h @@ -22,7 +22,7 @@ class Serializable; -struct DaemonSrcSettings +struct DaemonSourceSettings { QString m_dataAddress; //!< Listening (local) data address uint16_t m_dataPort; //!< Listening data port @@ -31,7 +31,7 @@ struct DaemonSrcSettings Serializable *m_channelMarker; - DaemonSrcSettings(); + DaemonSourceSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } QByteArray serialize() const; diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp index d73c501cf..27d69d65f 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp @@ -24,10 +24,10 @@ #include "daemonsrcthread.h" -MESSAGE_CLASS_DEFINITION(DaemonSrcThread::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(DaemonSrcThread::MsgDataBind, Message) +MESSAGE_CLASS_DEFINITION(DaemonSourceThread::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(DaemonSourceThread::MsgDataBind, Message) -DaemonSrcThread::DaemonSrcThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : +DaemonSourceThread::DaemonSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : QThread(parent), m_running(false), m_dataQueue(dataQueue), @@ -38,26 +38,26 @@ DaemonSrcThread::DaemonSrcThread(SDRDaemonDataQueue *dataQueue, QObject* parent) connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); } -DaemonSrcThread::~DaemonSrcThread() +DaemonSourceThread::~DaemonSourceThread() { - qDebug("DaemonSrcThread::~DaemonSrcThread"); + qDebug("DaemonSourceThread::~DaemonSourceThread"); } -void DaemonSrcThread::startStop(bool start) +void DaemonSourceThread::startStop(bool start) { MsgStartStop *msg = MsgStartStop::create(start); m_inputMessageQueue.push(msg); } -void DaemonSrcThread::dataBind(const QString& address, uint16_t port) +void DaemonSourceThread::dataBind(const QString& address, uint16_t port) { MsgDataBind *msg = MsgDataBind::create(address, port); m_inputMessageQueue.push(msg); } -void DaemonSrcThread::startWork() +void DaemonSourceThread::startWork() { - qDebug("DaemonSrcThread::startWork"); + qDebug("DaemonSourceThread::startWork"); m_startWaitMutex.lock(); m_socket = new QUdpSocket(this); start(); @@ -66,18 +66,18 @@ void DaemonSrcThread::startWork() m_startWaitMutex.unlock(); } -void DaemonSrcThread::stopWork() +void DaemonSourceThread::stopWork() { - qDebug("DaemonSrcThread::stopWork"); + qDebug("DaemonSourceThread::stopWork"); delete m_socket; m_socket = 0; m_running = false; wait(); } -void DaemonSrcThread::run() +void DaemonSourceThread::run() { - qDebug("DaemonSrcThread::run: begin"); + qDebug("DaemonSourceThread::run: begin"); m_running = true; m_startWaiter.wakeAll(); @@ -87,11 +87,11 @@ void DaemonSrcThread::run() } m_running = false; - qDebug("DaemonSrcThread::run: end"); + qDebug("DaemonSourceThread::run: end"); } -void DaemonSrcThread::handleInputMessages() +void DaemonSourceThread::handleInputMessages() { Message* message; @@ -100,7 +100,7 @@ void DaemonSrcThread::handleInputMessages() if (MsgStartStop::match(*message)) { MsgStartStop* notif = (MsgStartStop*) message; - qDebug("DaemonSrcThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + qDebug("DaemonSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); if (notif->getStartStop()) { startWork(); @@ -113,7 +113,7 @@ void DaemonSrcThread::handleInputMessages() else if (MsgDataBind::match(*message)) { MsgDataBind* notif = (MsgDataBind*) message; - qDebug("DaemonSrcThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); + qDebug("DaemonSourceThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); if (m_socket) { @@ -125,7 +125,7 @@ void DaemonSrcThread::handleInputMessages() } } -void DaemonSrcThread::readPendingDatagrams() +void DaemonSourceThread::readPendingDatagrams() { SDRDaemonSuperBlock superBlock; qint64 size; @@ -158,7 +158,7 @@ void DaemonSrcThread::readPendingDatagrams() if (superBlock.m_header.m_frameIndex != frameIndex) { - //qDebug("DaemonSrcThread::readPendingDatagrams: push frame %u", frameIndex); + //qDebug("DaemonSourceThread::readPendingDatagrams: push frame %u", frameIndex); m_dataQueue->push(m_dataBlocks[dataBlockIndex]); m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; @@ -181,7 +181,7 @@ void DaemonSrcThread::readPendingDatagrams() } else { - qWarning("DaemonSrcThread::readPendingDatagrams: wrong super block size not processing"); + qWarning("DaemonSourceThread::readPendingDatagrams: wrong super block size not processing"); } } } diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.h b/plugins/channeltx/daemonsrc/daemonsrcthread.h index dc11e1a76..a5371bed7 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcthread.h +++ b/plugins/channeltx/daemonsrc/daemonsrcthread.h @@ -29,7 +29,7 @@ class SDRDaemonDataQueue; class SDRDaemonDataBlock; class QUdpSocket; -class DaemonSrcThread : public QThread { +class DaemonSourceThread : public QThread { Q_OBJECT public: class MsgStartStop : public Message { @@ -74,8 +74,8 @@ public: } }; - DaemonSrcThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); - ~DaemonSrcThread(); + DaemonSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); + ~DaemonSourceThread(); void startStop(bool start); void dataBind(const QString& address, uint16_t port); diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 12bfa4dc9..1d3b52535 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2159,7 +2159,7 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } - else if (*channelType == "DaemonSrc") + else if (*channelType == "DaemonSource") { if (channelSettings.getTx() != 0) { From 0a42e2b0ac73e401cc49a14043062602774eb0b5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 16:50:55 +0200 Subject: [PATCH 733/956] Rename daemonsrc to daemonsource --- plugins/channeltx/CMakeLists.txt | 2 +- .../CMakeLists.txt | 22 +++++++++---------- .../daemonsource.cpp} | 4 ++-- .../daemonsource.h} | 2 +- .../daemonsourcegui.cpp} | 6 ++--- .../daemonsourcegui.h} | 2 +- .../daemonsourcegui.ui} | 0 .../daemonsourceplugin.cpp} | 6 ++--- .../daemonsourceplugin.h} | 0 .../daemonsourcesettings.cpp} | 4 +--- .../daemonsourcesettings.h} | 0 .../daemonsourcethread.cpp} | 2 +- .../daemonsourcethread.h} | 0 pluginssrv/channeltx/CMakeLists.txt | 2 +- .../CMakeLists.txt | 18 +++++++-------- 15 files changed, 34 insertions(+), 36 deletions(-) rename plugins/channeltx/{daemonsrc => daemonsource}/CMakeLists.txt (77%) rename plugins/channeltx/{daemonsrc/daemonsrc.cpp => daemonsource/daemonsource.cpp} (99%) rename plugins/channeltx/{daemonsrc/daemonsrc.h => daemonsource/daemonsource.h} (99%) rename plugins/channeltx/{daemonsrc/daemonsrcgui.cpp => daemonsource/daemonsourcegui.cpp} (99%) rename plugins/channeltx/{daemonsrc/daemonsrcgui.h => daemonsource/daemonsourcegui.h} (99%) rename plugins/channeltx/{daemonsrc/daemonsrcgui.ui => daemonsource/daemonsourcegui.ui} (100%) rename plugins/channeltx/{daemonsrc/daemonsrcplugin.cpp => daemonsource/daemonsourceplugin.cpp} (96%) rename plugins/channeltx/{daemonsrc/daemonsrcplugin.h => daemonsource/daemonsourceplugin.h} (100%) rename plugins/channeltx/{daemonsrc/daemonsrcsettings.cpp => daemonsource/daemonsourcesettings.cpp} (97%) rename plugins/channeltx/{daemonsrc/daemonsrcsettings.h => daemonsource/daemonsourcesettings.h} (100%) rename plugins/channeltx/{daemonsrc/daemonsrcthread.cpp => daemonsource/daemonsourcethread.cpp} (99%) rename plugins/channeltx/{daemonsrc/daemonsrcthread.h => daemonsource/daemonsourcethread.h} (100%) rename pluginssrv/channeltx/{daemonsrc => daemonsource}/CMakeLists.txt (64%) diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index 75e3cb345..c1586843c 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -8,7 +8,7 @@ add_subdirectory(udpsink) find_package(CM256cc) if(CM256CC_FOUND) - add_subdirectory(daemonsrc) + add_subdirectory(daemonsource) endif(CM256CC_FOUND) find_package(OpenCV) diff --git a/plugins/channeltx/daemonsrc/CMakeLists.txt b/plugins/channeltx/daemonsource/CMakeLists.txt similarity index 77% rename from plugins/channeltx/daemonsrc/CMakeLists.txt rename to plugins/channeltx/daemonsource/CMakeLists.txt index 0353c457f..bbea60488 100644 --- a/plugins/channeltx/daemonsrc/CMakeLists.txt +++ b/plugins/channeltx/daemonsource/CMakeLists.txt @@ -3,23 +3,23 @@ project(daemonsrc) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(daemonsrc_SOURCES - daemonsrc.cpp - daemonsrcthread.cpp - daemonsrcgui.cpp - daemonsrcplugin.cpp - daemonsrcsettings.cpp + daemonsource.cpp + daemonsourcethread.cpp + daemonsourcegui.cpp + daemonsourceplugin.cpp + daemonsourcesettings.cpp ) set(daemonsrc_HEADERS - daemonsrc.h - daemonsrcthread.h - daemonsrcgui.h - daemonsrcplugin.h - daemonsrcsettings.h + daemonsource.h + daemonsourcethread.h + daemonsourcegui.h + daemonsourceplugin.h + daemonsourcesettings.h ) set(daemonsrc_FORMS - daemonsrcgui.ui + daemonsourcegui.ui ) include_directories( diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsource/daemonsource.cpp similarity index 99% rename from plugins/channeltx/daemonsrc/daemonsrc.cpp rename to plugins/channeltx/daemonsource/daemonsource.cpp index 8c13d760e..b336fb7c5 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsource/daemonsource.cpp @@ -30,8 +30,8 @@ #include "dsp/upchannelizer.h" #include "dsp/threadedbasebandsamplesource.h" -#include "daemonsrcthread.h" -#include "daemonsrc.h" +#include "daemonsourcethread.h" +#include "daemonsource.h" MESSAGE_CLASS_DEFINITION(DaemonSource::MsgSampleRateNotification, Message) MESSAGE_CLASS_DEFINITION(DaemonSource::MsgConfigureDaemonSource, Message) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.h b/plugins/channeltx/daemonsource/daemonsource.h similarity index 99% rename from plugins/channeltx/daemonsrc/daemonsrc.h rename to plugins/channeltx/daemonsource/daemonsource.h index 1558b0f48..25237fa25 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.h +++ b/plugins/channeltx/daemonsource/daemonsource.h @@ -25,7 +25,7 @@ #include "channel/channelsourceapi.h" #include "util/message.h" -#include "daemonsrcsettings.h" +#include "daemonsourcesettings.h" #include "channel/sdrdaemondataqueue.h" #include "channel/sdrdaemondatablock.h" #include "channel/sdrdaemondatareadqueue.h" diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsource/daemonsourcegui.cpp similarity index 99% rename from plugins/channeltx/daemonsrc/daemonsrcgui.cpp rename to plugins/channeltx/daemonsource/daemonsourcegui.cpp index b74fd5f04..10ba93ba5 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp +++ b/plugins/channeltx/daemonsource/daemonsourcegui.cpp @@ -19,9 +19,9 @@ #include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" -#include "daemonsrc.h" -#include "ui_daemonsrcgui.h" -#include "daemonsrcgui.h" +#include "daemonsource.h" +#include "ui_daemonsourcegui.h" +#include "daemonsourcegui.h" DaemonSourceGUI* DaemonSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) { diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.h b/plugins/channeltx/daemonsource/daemonsourcegui.h similarity index 99% rename from plugins/channeltx/daemonsrc/daemonsrcgui.h rename to plugins/channeltx/daemonsource/daemonsourcegui.h index baf656ac9..f77c10404 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.h +++ b/plugins/channeltx/daemonsource/daemonsourcegui.h @@ -24,7 +24,7 @@ #include "gui/rollupwidget.h" #include "util/messagequeue.h" -#include "daemonsrcsettings.h" +#include "daemonsourcesettings.h" class PluginAPI; class DeviceUISet; diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.ui b/plugins/channeltx/daemonsource/daemonsourcegui.ui similarity index 100% rename from plugins/channeltx/daemonsrc/daemonsrcgui.ui rename to plugins/channeltx/daemonsource/daemonsourcegui.ui diff --git a/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp b/plugins/channeltx/daemonsource/daemonsourceplugin.cpp similarity index 96% rename from plugins/channeltx/daemonsrc/daemonsrcplugin.cpp rename to plugins/channeltx/daemonsource/daemonsourceplugin.cpp index d1ce2307b..805ad8e89 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp +++ b/plugins/channeltx/daemonsource/daemonsourceplugin.cpp @@ -18,10 +18,10 @@ #include "plugin/pluginapi.h" #ifndef SERVER_MODE -#include "daemonsrcgui.h" +#include "daemonsourcegui.h" #endif -#include "daemonsrc.h" -#include "daemonsrcplugin.h" +#include "daemonsource.h" +#include "daemonsourceplugin.h" const PluginDescriptor DaemonSourcePlugin::m_pluginDescriptor = { QString("Daemon channel source"), diff --git a/plugins/channeltx/daemonsrc/daemonsrcplugin.h b/plugins/channeltx/daemonsource/daemonsourceplugin.h similarity index 100% rename from plugins/channeltx/daemonsrc/daemonsrcplugin.h rename to plugins/channeltx/daemonsource/daemonsourceplugin.h diff --git a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp b/plugins/channeltx/daemonsource/daemonsourcesettings.cpp similarity index 97% rename from plugins/channeltx/daemonsrc/daemonsrcsettings.cpp rename to plugins/channeltx/daemonsource/daemonsourcesettings.cpp index 411282c74..e55462390 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp +++ b/plugins/channeltx/daemonsource/daemonsourcesettings.cpp @@ -14,13 +14,11 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "daemonsrcsettings.h" - #include #include "util/simpleserializer.h" #include "settings/serializable.h" -#include "daemonsrcsettings.h" +#include "daemonsourcesettings.h" DaemonSourceSettings::DaemonSourceSettings() { diff --git a/plugins/channeltx/daemonsrc/daemonsrcsettings.h b/plugins/channeltx/daemonsource/daemonsourcesettings.h similarity index 100% rename from plugins/channeltx/daemonsrc/daemonsrcsettings.h rename to plugins/channeltx/daemonsource/daemonsourcesettings.h diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp b/plugins/channeltx/daemonsource/daemonsourcethread.cpp similarity index 99% rename from plugins/channeltx/daemonsrc/daemonsrcthread.cpp rename to plugins/channeltx/daemonsource/daemonsourcethread.cpp index 27d69d65f..4feb60ef4 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp +++ b/plugins/channeltx/daemonsource/daemonsourcethread.cpp @@ -22,7 +22,7 @@ #include "channel/sdrdaemondataqueue.h" #include "channel/sdrdaemondatablock.h" -#include "daemonsrcthread.h" +#include "daemonsourcethread.h" MESSAGE_CLASS_DEFINITION(DaemonSourceThread::MsgStartStop, Message) MESSAGE_CLASS_DEFINITION(DaemonSourceThread::MsgDataBind, Message) diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.h b/plugins/channeltx/daemonsource/daemonsourcethread.h similarity index 100% rename from plugins/channeltx/daemonsrc/daemonsrcthread.h rename to plugins/channeltx/daemonsource/daemonsourcethread.h diff --git a/pluginssrv/channeltx/CMakeLists.txt b/pluginssrv/channeltx/CMakeLists.txt index 512390995..76c3291d2 100644 --- a/pluginssrv/channeltx/CMakeLists.txt +++ b/pluginssrv/channeltx/CMakeLists.txt @@ -8,7 +8,7 @@ add_subdirectory(udpsink) find_package(CM256cc) if(CM256CC_FOUND) - add_subdirectory(daemonsrc) + add_subdirectory(daemonsource) endif() find_package(OpenCV) diff --git a/pluginssrv/channeltx/daemonsrc/CMakeLists.txt b/pluginssrv/channeltx/daemonsource/CMakeLists.txt similarity index 64% rename from pluginssrv/channeltx/daemonsrc/CMakeLists.txt rename to pluginssrv/channeltx/daemonsource/CMakeLists.txt index 2121a9fee..a8cc94b39 100644 --- a/pluginssrv/channeltx/daemonsrc/CMakeLists.txt +++ b/pluginssrv/channeltx/daemonsource/CMakeLists.txt @@ -1,20 +1,20 @@ project(daemonsrc) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -set(PLUGIN_PREFIX "../../../plugins/channeltx/daemonsrc") +set(PLUGIN_PREFIX "../../../plugins/channeltx/daemonsource") set(daemonsrc_SOURCES - ${PLUGIN_PREFIX}/daemonsrc.cpp - ${PLUGIN_PREFIX}/daemonsrcthread.cpp - ${PLUGIN_PREFIX}/daemonsrcplugin.cpp - ${PLUGIN_PREFIX}/daemonsrcsettings.cpp + ${PLUGIN_PREFIX}/daemonsource.cpp + ${PLUGIN_PREFIX}/daemonsourcethread.cpp + ${PLUGIN_PREFIX}/daemonsourceplugin.cpp + ${PLUGIN_PREFIX}/daemonsourcesettings.cpp ) set(daemonsrc_HEADERS - ${PLUGIN_PREFIX}/daemonsrc.h - ${PLUGIN_PREFIX}/daemonsrcthread.h - ${PLUGIN_PREFIX}/daemonsrcplugin.h - ${PLUGIN_PREFIX}/daemonsrcsettings.h + ${PLUGIN_PREFIX}/daemonsource.h + ${PLUGIN_PREFIX}/daemonsourcethread.h + ${PLUGIN_PREFIX}/daemonsourceplugin.h + ${PLUGIN_PREFIX}/daemonsourcesettings.h ) include_directories( From 87b9cff3c29c678e15e65b269c03d9e76b7f4969 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 17:05:16 +0200 Subject: [PATCH 734/956] Rename libdaemonsrc to libdaemonsource --- .../channelrx/daemonsink/daemonsinkplugin.cpp | 2 +- plugins/channeltx/daemonsource/CMakeLists.txt | 24 +++++++++---------- .../channeltx/daemonsource/CMakeLists.txt | 18 +++++++------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsinkplugin.cpp b/plugins/channelrx/daemonsink/daemonsinkplugin.cpp index 54d6c3333..e65dfae0d 100644 --- a/plugins/channelrx/daemonsink/daemonsinkplugin.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkplugin.cpp @@ -25,7 +25,7 @@ #include "daemonsink.h" const PluginDescriptor DaemonSinkPlugin::m_pluginDescriptor = { - QString("Daemon Channel Sink"), + QString("Daemon channel Sink"), QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), diff --git a/plugins/channeltx/daemonsource/CMakeLists.txt b/plugins/channeltx/daemonsource/CMakeLists.txt index bbea60488..8caf6ef74 100644 --- a/plugins/channeltx/daemonsource/CMakeLists.txt +++ b/plugins/channeltx/daemonsource/CMakeLists.txt @@ -1,8 +1,8 @@ -project(daemonsrc) +project(daemonsource) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -set(daemonsrc_SOURCES +set(daemonsource_SOURCES daemonsource.cpp daemonsourcethread.cpp daemonsourcegui.cpp @@ -10,7 +10,7 @@ set(daemonsrc_SOURCES daemonsourcesettings.cpp ) -set(daemonsrc_HEADERS +set(daemonsource_HEADERS daemonsource.h daemonsourcethread.h daemonsourcegui.h @@ -18,7 +18,7 @@ set(daemonsrc_HEADERS daemonsourcesettings.h ) -set(daemonsrc_FORMS +set(daemonsource_FORMS daemonsourcegui.ui ) @@ -34,15 +34,15 @@ add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_PLUGIN) add_definitions(-DQT_SHARED) -qt5_wrap_ui(daemonsrc_FORMS_HEADERS ${daemonsrc_FORMS}) +qt5_wrap_ui(daemonsource_FORMS_HEADERS ${daemonsource_FORMS}) -add_library(daemonsrc SHARED - ${daemonsrc_SOURCES} - ${daemonsrc_HEADERS_MOC} - ${daemonsrc_FORMS_HEADERS} +add_library(daemonsource SHARED + ${daemonsource_SOURCES} + ${daemonsource_HEADERS_MOC} + ${daemonsource_FORMS_HEADERS} ) -target_link_libraries(daemonsrc +target_link_libraries(daemonsource ${QT_LIBRARIES} ${CM256CC_LIBRARIES} sdrbase @@ -50,6 +50,6 @@ target_link_libraries(daemonsrc swagger ) -target_link_libraries(daemonsrc Qt5::Core Qt5::Widgets Qt5::Network) +target_link_libraries(daemonsource Qt5::Core Qt5::Widgets Qt5::Network) -install(TARGETS daemonsrc DESTINATION lib/plugins/channeltx) +install(TARGETS daemonsource DESTINATION lib/plugins/channeltx) diff --git a/pluginssrv/channeltx/daemonsource/CMakeLists.txt b/pluginssrv/channeltx/daemonsource/CMakeLists.txt index a8cc94b39..27eb29836 100644 --- a/pluginssrv/channeltx/daemonsource/CMakeLists.txt +++ b/pluginssrv/channeltx/daemonsource/CMakeLists.txt @@ -1,16 +1,16 @@ -project(daemonsrc) +project(daemonsource) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(PLUGIN_PREFIX "../../../plugins/channeltx/daemonsource") -set(daemonsrc_SOURCES +set(daemonsource_SOURCES ${PLUGIN_PREFIX}/daemonsource.cpp ${PLUGIN_PREFIX}/daemonsourcethread.cpp ${PLUGIN_PREFIX}/daemonsourceplugin.cpp ${PLUGIN_PREFIX}/daemonsourcesettings.cpp ) -set(daemonsrc_HEADERS +set(daemonsource_HEADERS ${PLUGIN_PREFIX}/daemonsource.h ${PLUGIN_PREFIX}/daemonsourcethread.h ${PLUGIN_PREFIX}/daemonsourceplugin.h @@ -29,18 +29,18 @@ add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_PLUGIN) add_definitions(-DQT_SHARED) -add_library(daemonsrcsrv SHARED - ${daemonsrc_SOURCES} - ${daemonsrc_HEADERS_MOC} +add_library(daemonsourcesrv SHARED + ${daemonsource_SOURCES} + ${daemonsource_HEADERS_MOC} ) -target_link_libraries(daemonsrcsrv +target_link_libraries(daemonsourcesrv ${QT_LIBRARIES} ${CM256CC_LIBRARIES} sdrbase swagger ) -target_link_libraries(daemonsrcsrv Qt5::Core Qt5::Network) +target_link_libraries(daemonsourcesrv Qt5::Core Qt5::Network) -install(TARGETS daemonsrcsrv DESTINATION lib/pluginssrv/channeltx) +install(TARGETS daemonsourcesrv DESTINATION lib/pluginssrv/channeltx) From f58cc3cc07c004f314cf94fd3e44727dd9ee3de0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 22:36:16 +0200 Subject: [PATCH 735/956] Renamed UDPSink to UDPSource --- plugins/channelrx/demodbfm/rdsdemod.h | 2 - plugins/channeltx/udpsink/CMakeLists.txt | 50 +++--- .../udpsink/{udpsink.cpp => udpsource.cpp} | 94 +++++------ .../udpsink/{udpsink.h => udpsource.h} | 32 ++-- .../{udpsinkgui.cpp => udpsourcegui.cpp} | 155 +++++++++--------- .../udpsink/{udpsinkgui.h => udpsourcegui.h} | 28 ++-- .../{udpsinkgui.ui => udpsourcegui.ui} | 8 +- .../{udpsinkmsg.cpp => udpsourcemsg.cpp} | 4 +- .../udpsink/{udpsinkmsg.h => udpsourcemsg.h} | 10 +- ...{udpsinkplugin.cpp => udpsourceplugin.cpp} | 34 ++-- .../{udpsinkplugin.h => udpsourceplugin.h} | 4 +- ...sinksettings.cpp => udpsourcesettings.cpp} | 11 +- ...{udpsinksettings.h => udpsourcesettings.h} | 12 +- ...udphandler.cpp => udpsourceudphandler.cpp} | 45 ++--- ...sinkudphandler.h => udpsourceudphandler.h} | 12 +- pluginssrv/channeltx/udpsink/CMakeLists.txt | 38 ++--- sdrbase/webapi/webapirequestmapper.cpp | 2 +- 17 files changed, 272 insertions(+), 269 deletions(-) rename plugins/channeltx/udpsink/{udpsink.cpp => udpsource.cpp} (88%) rename plugins/channeltx/udpsink/{udpsink.h => udpsource.h} (91%) rename plugins/channeltx/udpsink/{udpsinkgui.cpp => udpsourcegui.cpp} (76%) rename plugins/channeltx/udpsink/{udpsinkgui.h => udpsourcegui.h} (83%) rename plugins/channeltx/udpsink/{udpsinkgui.ui => udpsourcegui.ui} (99%) rename plugins/channeltx/udpsink/{udpsinkmsg.cpp => udpsourcemsg.cpp} (92%) rename plugins/channeltx/udpsink/{udpsinkmsg.h => udpsourcemsg.h} (91%) rename plugins/channeltx/udpsink/{udpsinkplugin.cpp => udpsourceplugin.cpp} (66%) rename plugins/channeltx/udpsink/{udpsinkplugin.h => udpsourceplugin.h} (95%) rename plugins/channeltx/udpsink/{udpsinksettings.cpp => udpsourcesettings.cpp} (95%) rename plugins/channeltx/udpsink/{udpsinksettings.h => udpsourcesettings.h} (90%) rename plugins/channeltx/udpsink/{udpsinkudphandler.cpp => udpsourceudphandler.cpp} (85%) rename plugins/channeltx/udpsink/{udpsinkudphandler.h => udpsourceudphandler.h} (93%) diff --git a/plugins/channelrx/demodbfm/rdsdemod.h b/plugins/channelrx/demodbfm/rdsdemod.h index c10c7527a..5a7ea24f6 100644 --- a/plugins/channelrx/demodbfm/rdsdemod.h +++ b/plugins/channelrx/demodbfm/rdsdemod.h @@ -75,8 +75,6 @@ private: int m_srate; - //UDPSink m_udpDebug; // UDP debug - static const Real m_pllBeta; static const Real m_fsc; }; diff --git a/plugins/channeltx/udpsink/CMakeLists.txt b/plugins/channeltx/udpsink/CMakeLists.txt index 760a55c14..9a2ab1e02 100644 --- a/plugins/channeltx/udpsink/CMakeLists.txt +++ b/plugins/channeltx/udpsink/CMakeLists.txt @@ -1,27 +1,27 @@ -project(udpsink) +project(udpsource) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -set(udpsink_SOURCES - udpsink.cpp - udpsinkgui.cpp - udpsinkplugin.cpp - udpsinkudphandler.cpp - udpsinkmsg.cpp - udpsinksettings.cpp +set(udpsource_SOURCES + udpsource.cpp + udpsourcegui.cpp + udpsourceplugin.cpp + udpsourceudphandler.cpp + udpsourcemsg.cpp + udpsourcesettings.cpp ) -set(udpsink_HEADERS - udpsink.h - udpsinkgui.h - udpsinkplugin.h - udpsinkudphandler.h - udpsinkmsg.h - udpsinksettings.h +set(udpsource_HEADERS + udpsource.h + udpsourcegui.h + udpsourceplugin.h + udpsourceudphandler.h + udpsourcemsg.h + udpsourcesettings.h ) -set(udpsink_FORMS - udpsinkgui.ui +set(udpsource_FORMS + udpsourcegui.ui ) include_directories( @@ -35,21 +35,21 @@ add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_PLUGIN) add_definitions(-DQT_SHARED) -qt5_wrap_ui(udpsink_FORMS_HEADERS ${udpsink_FORMS}) +qt5_wrap_ui(udpsource_FORMS_HEADERS ${udpsource_FORMS}) -add_library(modudpsink SHARED - ${udpsink_SOURCES} - ${udpsink_HEADERS_MOC} - ${udpsink_FORMS_HEADERS} +add_library(udpsource SHARED + ${udpsource_SOURCES} + ${udpsource_HEADERS_MOC} + ${udpsource_FORMS_HEADERS} ) -target_link_libraries(modudpsink +target_link_libraries(udpsource ${QT_LIBRARIES} sdrbase sdrgui swagger ) -target_link_libraries(modudpsink Qt5::Core Qt5::Widgets Qt5::Network) +target_link_libraries(udpsource Qt5::Core Qt5::Widgets Qt5::Network) -install(TARGETS modudpsink DESTINATION lib/plugins/channeltx) +install(TARGETS udpsource DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsource.cpp similarity index 88% rename from plugins/channeltx/udpsink/udpsink.cpp rename to plugins/channeltx/udpsink/udpsource.cpp index 374d5cf2d..0f3a89bc9 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsource.cpp @@ -26,18 +26,18 @@ #include "dsp/dspcommands.h" #include "util/db.h" -#include "udpsinkmsg.h" -#include "udpsink.h" +#include "udpsource.h" +#include "udpsourcemsg.h" -MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSink, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSinkSpectrum, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgResetReadIndex, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgConfigureUDPSink, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgUDPSinkSpectrum, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgResetReadIndex, Message) -const QString UDPSink::m_channelIdURI = "sdrangel.channeltx.udpsink"; -const QString UDPSink::m_channelId = "UDPSink"; +const QString UDPSource::m_channelIdURI = "sdrangel.channeltx.udpsource"; +const QString UDPSource::m_channelId = "UDPSource"; -UDPSink::UDPSink(DeviceSinkAPI *deviceAPI) : +UDPSource::UDPSource(DeviceSinkAPI *deviceAPI) : ChannelSourceAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_basebandSampleRate(48000), @@ -80,7 +80,7 @@ UDPSink::UDPSink(DeviceSinkAPI *deviceAPI) : m_deviceAPI->addChannelAPI(this); } -UDPSink::~UDPSink() +UDPSource::~UDPSource() { m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); @@ -90,18 +90,18 @@ UDPSink::~UDPSink() delete[] m_SSBFilterBuffer; } -void UDPSink::start() +void UDPSource::start() { m_udpHandler.start(); applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); } -void UDPSink::stop() +void UDPSource::stop() { m_udpHandler.stop(); } -void UDPSink::pull(Sample& sample) +void UDPSource::pull(Sample& sample) { if (m_settings.m_channelMute) { @@ -147,9 +147,9 @@ void UDPSink::pull(Sample& sample) sample.m_imag = (FixReal) ci.imag(); } -void UDPSink::modulateSample() +void UDPSource::modulateSample() { - if (m_settings.m_sampleFormat == UDPSinkSettings::FormatSnLE) // Linear I/Q transponding + if (m_settings.m_sampleFormat == UDPSourceSettings::FormatSnLE) // Linear I/Q transponding { Sample s; @@ -173,7 +173,7 @@ void UDPSink::modulateSample() m_modSample.imag(0.0f); } } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFM) + else if (m_settings.m_sampleFormat == UDPSourceSettings::FormatNFM) { qint16 t; readMonoSample(t); @@ -196,7 +196,7 @@ void UDPSink::modulateSample() m_modSample.imag(0.0f); } } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAM) + else if (m_settings.m_sampleFormat == UDPSourceSettings::FormatAM) { qint16 t; readMonoSample(t); @@ -217,7 +217,7 @@ void UDPSink::modulateSample() m_modSample.imag(0.0f); } } - else if ((m_settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB)) + else if ((m_settings.m_sampleFormat == UDPSourceSettings::FormatLSB) || (m_settings.m_sampleFormat == UDPSourceSettings::FormatUSB)) { qint16 t; Complex c, ci; @@ -235,7 +235,7 @@ void UDPSink::modulateSample() ci.real((t / SDR_TX_SCALEF) * m_settings.m_gainOut); ci.imag(0.0f); - n_out = m_SSBFilter->runSSB(ci, &filtered, (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB)); + n_out = m_SSBFilter->runSSB(ci, &filtered, (m_settings.m_sampleFormat == UDPSourceSettings::FormatUSB)); if (n_out > 0) { @@ -279,7 +279,7 @@ void UDPSink::modulateSample() } } -void UDPSink::calculateLevel(Real sample) +void UDPSource::calculateLevel(Real sample) { if (m_levelCalcCount < m_levelNbSamples) { @@ -298,7 +298,7 @@ void UDPSink::calculateLevel(Real sample) } } -void UDPSink::calculateLevel(Complex sample) +void UDPSource::calculateLevel(Complex sample) { Real t = std::abs(sample); @@ -318,12 +318,12 @@ void UDPSink::calculateLevel(Complex sample) } } -bool UDPSink::handleMessage(const Message& cmd) +bool UDPSource::handleMessage(const Message& cmd) { if (UpChannelizer::MsgChannelizerNotification::match(cmd)) { UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "UDPSink::handleMessage: MsgChannelizerNotification"; + qDebug() << "UDPSource::handleMessage: MsgChannelizerNotification"; applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset()); @@ -332,7 +332,7 @@ bool UDPSink::handleMessage(const Message& cmd) else if (MsgConfigureChannelizer::match(cmd)) { MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug() << "UDPSink::handleMessage: MsgConfigureChannelizer:" + qDebug() << "UDPSource::handleMessage: MsgConfigureChannelizer:" << " sampleRate: " << cfg.getSampleRate() << " centerFrequency: " << cfg.getCenterFrequency(); @@ -345,15 +345,15 @@ bool UDPSink::handleMessage(const Message& cmd) else if (MsgConfigureUDPSink::match(cmd)) { MsgConfigureUDPSink& cfg = (MsgConfigureUDPSink&) cmd; - qDebug() << "UDPSink::handleMessage: MsgConfigureUDPSink"; + qDebug() << "UDPSource::handleMessage: MsgConfigureUDPSink"; applySettings(cfg.getSettings(), cfg.getForce()); return true; } - else if (UDPSinkMessages::MsgSampleRateCorrection::match(cmd)) + else if (UDPSourceMessages::MsgSampleRateCorrection::match(cmd)) { - UDPSinkMessages::MsgSampleRateCorrection& cfg = (UDPSinkMessages::MsgSampleRateCorrection&) cmd; + UDPSourceMessages::MsgSampleRateCorrection& cfg = (UDPSourceMessages::MsgSampleRateCorrection&) cmd; Real newSampleRate = m_actualInputSampleRate + cfg.getCorrectionFactor() * m_actualInputSampleRate; // exclude values too way out nominal sample rate (20%) @@ -378,7 +378,7 @@ bool UDPSink::handleMessage(const Message& cmd) if (m_sampleRateAvgCounter == m_sampleRateAverageItems) { float avgRate = m_sampleRateSum / m_sampleRateAverageItems; - qDebug("UDPSink::handleMessage: MsgSampleRateCorrection: corr: %+.6f new rate: %.0f: avg rate: %.0f", + qDebug("UDPSource::handleMessage: MsgSampleRateCorrection: corr: %+.6f new rate: %.0f: avg rate: %.0f", cfg.getCorrectionFactor(), m_actualInputSampleRate, avgRate); @@ -388,7 +388,7 @@ bool UDPSink::handleMessage(const Message& cmd) } // else // { -// qDebug("UDPSink::handleMessage: MsgSampleRateCorrection: corr: %+.6f new rate: %.0f", +// qDebug("UDPSource::handleMessage: MsgSampleRateCorrection: corr: %+.6f new rate: %.0f", // cfg.getCorrectionFactor(), // m_actualInputSampleRate); // } @@ -407,7 +407,7 @@ bool UDPSink::handleMessage(const Message& cmd) { MsgUDPSinkSpectrum& spc = (MsgUDPSinkSpectrum&) cmd; m_spectrumEnabled = spc.getEnabled(); - qDebug() << "UDPSink::handleMessage: MsgUDPSinkSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; + qDebug() << "UDPSource::handleMessage: MsgUDPSinkSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; return true; } @@ -417,7 +417,7 @@ bool UDPSink::handleMessage(const Message& cmd) m_udpHandler.resetReadIndex(); m_settingsMutex.unlock(); - qDebug() << "UDPSink::handleMessage: MsgResetReadIndex"; + qDebug() << "UDPSource::handleMessage: MsgResetReadIndex"; return true; } @@ -438,21 +438,21 @@ bool UDPSink::handleMessage(const Message& cmd) } } -void UDPSink::setSpectrum(bool enabled) +void UDPSource::setSpectrum(bool enabled) { Message* cmd = MsgUDPSinkSpectrum::create(enabled); getInputMessageQueue()->push(cmd); } -void UDPSink::resetReadIndex() +void UDPSource::resetReadIndex() { Message* cmd = MsgResetReadIndex::create(); getInputMessageQueue()->push(cmd); } -void UDPSink::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) +void UDPSource::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { - qDebug() << "UDPSink::applyChannelSettings:" + qDebug() << "UDPSource::applyChannelSettings:" << " basebandSampleRate: " << basebandSampleRate << " outputSampleRate: " << outputSampleRate << " inputFrequencyOffset: " << inputFrequencyOffset; @@ -480,9 +480,9 @@ void UDPSink::applyChannelSettings(int basebandSampleRate, int outputSampleRate, m_inputFrequencyOffset = inputFrequencyOffset; } -void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) +void UDPSource::applySettings(const UDPSourceSettings& settings, bool force) { - qDebug() << "UDPSink::applySettings:" + qDebug() << "UDPSource::applySettings:" << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << " m_sampleFormat: " << settings.m_sampleFormat << " m_inputSampleRate: " << settings.m_inputSampleRate @@ -575,12 +575,12 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) m_settings = settings; } -QByteArray UDPSink::serialize() const +QByteArray UDPSource::serialize() const { return m_settings.serialize(); } -bool UDPSink::deserialize(const QByteArray& data) +bool UDPSource::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { @@ -597,7 +597,7 @@ bool UDPSink::deserialize(const QByteArray& data) } } -int UDPSink::webapiSettingsGet( +int UDPSource::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { @@ -607,17 +607,17 @@ int UDPSink::webapiSettingsGet( return 200; } -int UDPSink::webapiSettingsPutPatch( +int UDPSource::webapiSettingsPutPatch( bool force, const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - UDPSinkSettings settings = m_settings; + UDPSourceSettings settings = m_settings; bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("sampleFormat")) { - settings.m_sampleFormat = (UDPSinkSettings::SampleFormat) response.getUdpSinkSettings()->getSampleFormat(); + settings.m_sampleFormat = (UDPSourceSettings::SampleFormat) response.getUdpSinkSettings()->getSampleFormat(); } if (channelSettingsKeys.contains("inputSampleRate")) { settings.m_inputSampleRate = response.getUdpSinkSettings()->getInputSampleRate(); @@ -678,7 +678,7 @@ int UDPSink::webapiSettingsPutPatch( if (frequencyOffsetChanged) { - UDPSink::MsgConfigureChannelizer *msgChan = UDPSink::MsgConfigureChannelizer::create( + UDPSource::MsgConfigureChannelizer *msgChan = UDPSource::MsgConfigureChannelizer::create( settings.m_inputSampleRate, settings.m_inputFrequencyOffset); m_inputMessageQueue.push(msgChan); @@ -698,7 +698,7 @@ int UDPSink::webapiSettingsPutPatch( return 200; } -int UDPSink::webapiReportGet( +int UDPSource::webapiReportGet( SWGSDRangel::SWGChannelReport& response, QString& errorMessage __attribute__((unused))) { @@ -708,7 +708,7 @@ int UDPSink::webapiReportGet( return 200; } -void UDPSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings) +void UDPSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSourceSettings& settings) { response.getUdpSinkSettings()->setSampleFormat((int) settings.m_sampleFormat); response.getUdpSinkSettings()->setInputSampleRate(settings.m_inputSampleRate); @@ -742,7 +742,7 @@ void UDPSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respo } } -void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +void UDPSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { response.getUdpSinkReport()->setInputPowerDb(CalcDb::dbPower(getInMagSq())); response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); diff --git a/plugins/channeltx/udpsink/udpsink.h b/plugins/channeltx/udpsink/udpsource.h similarity index 91% rename from plugins/channeltx/udpsink/udpsink.h rename to plugins/channeltx/udpsink/udpsource.h index 12497aaa2..47c7d1f27 100644 --- a/plugins/channeltx/udpsink/udpsink.h +++ b/plugins/channeltx/udpsink/udpsource.h @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINK_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINK_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCE_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCE_H_ #include @@ -28,14 +28,14 @@ #include "dsp/fftfilt.h" #include "util/message.h" -#include "udpsinkudphandler.h" -#include "udpsinksettings.h" +#include "udpsourcesettings.h" +#include "udpsourceudphandler.h" class DeviceSinkAPI; class ThreadedBasebandSampleSource; class UpChannelizer; -class UDPSink : public BasebandSampleSource, public ChannelSourceAPI { +class UDPSource : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: @@ -43,19 +43,19 @@ public: MESSAGE_CLASS_DECLARATION public: - const UDPSinkSettings& getSettings() const { return m_settings; } + const UDPSourceSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureUDPSink* create(const UDPSinkSettings& settings, bool force) + static MsgConfigureUDPSink* create(const UDPSourceSettings& settings, bool force) { return new MsgConfigureUDPSink(settings, force); } private: - UDPSinkSettings m_settings; + UDPSourceSettings m_settings; bool m_force; - MsgConfigureUDPSink(const UDPSinkSettings& settings, bool force) : + MsgConfigureUDPSink(const UDPSourceSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -86,8 +86,8 @@ public: { } }; - UDPSink(DeviceSinkAPI *deviceAPI); - virtual ~UDPSink(); + UDPSource(DeviceSinkAPI *deviceAPI); + virtual ~UDPSource(); virtual void destroy() { delete this; } void setSpectrumSink(BasebandSampleSink* spectrum) { m_spectrum = spectrum; } @@ -183,7 +183,7 @@ private: int m_basebandSampleRate; Real m_outputSampleRate; int m_inputFrequencyOffset; - UDPSinkSettings m_settings; + UDPSourceSettings m_settings; Real m_squelch; @@ -206,7 +206,7 @@ private: MovingAverage m_movingAverage; MovingAverage m_inMovingAverage; - UDPSinkUDPHandler m_udpHandler; + UDPSourceUDPHandler m_udpHandler; Real m_actualInputSampleRate; //!< sample rate with UDP buffer skew compensation double m_sampleRateSum; int m_sampleRateAvgCounter; @@ -232,12 +232,12 @@ private: static const int m_ssbFftLen = 1024; void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const UDPSinkSettings& settings, bool force = false); + void applySettings(const UDPSourceSettings& settings, bool force = false); void modulateSample(); void calculateLevel(Real sample); void calculateLevel(Complex sample); - void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSourceSettings& settings); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); inline void calculateSquelch(double value) @@ -318,4 +318,4 @@ private: -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINK_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCE_H_ */ diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsourcegui.cpp similarity index 76% rename from plugins/channeltx/udpsink/udpsinkgui.cpp rename to plugins/channeltx/udpsink/udpsourcegui.cpp index 1d7b8da74..3e8edbea3 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsourcegui.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "udpsourcegui.h" + #include "device/devicesinkapi.h" #include "device/deviceuiset.h" #include "dsp/spectrumvis.h" @@ -24,53 +26,52 @@ #include "plugin/pluginapi.h" #include "mainwindow.h" -#include "udpsinkgui.h" -#include "ui_udpsinkgui.h" +#include "ui_udpsourcegui.h" -UDPSinkGUI* UDPSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) +UDPSourceGUI* UDPSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) { - UDPSinkGUI* gui = new UDPSinkGUI(pluginAPI, deviceUISet, channelTx); + UDPSourceGUI* gui = new UDPSourceGUI(pluginAPI, deviceUISet, channelTx); return gui; } -void UDPSinkGUI::destroy() +void UDPSourceGUI::destroy() { delete this; } -void UDPSinkGUI::setName(const QString& name) +void UDPSourceGUI::setName(const QString& name) { setObjectName(name); } -QString UDPSinkGUI::getName() const +QString UDPSourceGUI::getName() const { return objectName(); } -qint64 UDPSinkGUI::getCenterFrequency() const { +qint64 UDPSourceGUI::getCenterFrequency() const { return m_channelMarker.getCenterFrequency(); } -void UDPSinkGUI::setCenterFrequency(qint64 centerFrequency) +void UDPSourceGUI::setCenterFrequency(qint64 centerFrequency) { m_channelMarker.setCenterFrequency(centerFrequency); applySettings(); } -void UDPSinkGUI::resetToDefaults() +void UDPSourceGUI::resetToDefaults() { m_settings.resetToDefaults(); displaySettings(); applySettings(true); } -QByteArray UDPSinkGUI::serialize() const +QByteArray UDPSourceGUI::serialize() const { return m_settings.serialize(); } -bool UDPSinkGUI::deserialize(const QByteArray& data) +bool UDPSourceGUI::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { @@ -83,11 +84,11 @@ bool UDPSinkGUI::deserialize(const QByteArray& data) } } -bool UDPSinkGUI::handleMessage(const Message& message) +bool UDPSourceGUI::handleMessage(const Message& message) { - if (UDPSink::MsgConfigureUDPSink::match(message)) + if (UDPSource::MsgConfigureUDPSink::match(message)) { - const UDPSink::MsgConfigureUDPSink& cfg = (UDPSink::MsgConfigureUDPSink&) message; + const UDPSource::MsgConfigureUDPSink& cfg = (UDPSource::MsgConfigureUDPSink&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -100,7 +101,7 @@ bool UDPSinkGUI::handleMessage(const Message& message) } } -void UDPSinkGUI::handleSourceMessages() +void UDPSourceGUI::handleSourceMessages() { Message* message; @@ -113,9 +114,9 @@ void UDPSinkGUI::handleSourceMessages() } } -UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : +UDPSourceGUI::UDPSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), - ui(new Ui::UDPSinkGUI), + ui(new Ui::UDPSourceGUI), m_pluginAPI(pluginAPI), m_deviceUISet(deviceUISet), m_tickCount(0), @@ -129,9 +130,9 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS setAttribute(Qt::WA_DeleteOnClose, true); m_spectrumVis = new SpectrumVis(SDR_TX_SCALEF, ui->glSpectrum); - m_udpSink = (UDPSink*) channelTx; //new UDPSink(m_deviceUISet->m_deviceSinkAPI); - m_udpSink->setSpectrumSink(m_spectrumVis); - m_udpSink->setMessageQueueToGUI(getInputMessageQueue()); + m_udpSource = (UDPSource*) channelTx; + m_udpSource->setSpectrumSink(m_spectrumVis); + m_udpSource->setMessageQueueToGUI(getInputMessageQueue()); ui->fmDeviation->setEnabled(false); ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); @@ -161,7 +162,7 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only - m_deviceUISet->registerTxChannelInstance(UDPSink::m_channelIdURI, this); + m_deviceUISet->registerTxChannelInstance(UDPSource::m_channelIdURI, this); m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); @@ -170,43 +171,43 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); - connect(m_udpSink, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int))); + connect(m_udpSource, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int))); displaySettings(); applySettings(true); } -UDPSinkGUI::~UDPSinkGUI() +UDPSourceGUI::~UDPSourceGUI() { m_deviceUISet->removeTxChannelInstance(this); - delete m_udpSink; // TODO: check this: when the GUI closes it has to delete the modulator + delete m_udpSource; // TODO: check this: when the GUI closes it has to delete the modulator delete m_spectrumVis; delete ui; } -void UDPSinkGUI::blockApplySettings(bool block) +void UDPSourceGUI::blockApplySettings(bool block) { m_doApplySettings = !block; } -void UDPSinkGUI::applySettings(bool force) +void UDPSourceGUI::applySettings(bool force) { if (m_doApplySettings) { - UDPSink::MsgConfigureChannelizer *msgChan = UDPSink::MsgConfigureChannelizer::create( + UDPSource::MsgConfigureChannelizer *msgChan = UDPSource::MsgConfigureChannelizer::create( m_settings.m_inputSampleRate, m_settings.m_inputFrequencyOffset); - m_udpSink->getInputMessageQueue()->push(msgChan); + m_udpSource->getInputMessageQueue()->push(msgChan); - UDPSink::MsgConfigureUDPSink* message = UDPSink::MsgConfigureUDPSink::create( m_settings, force); - m_udpSink->getInputMessageQueue()->push(message); + UDPSource::MsgConfigureUDPSink* message = UDPSource::MsgConfigureUDPSink::create( m_settings, force); + m_udpSource->getInputMessageQueue()->push(message); ui->applyBtn->setEnabled(false); ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); } } -void UDPSinkGUI::displaySettings() +void UDPSourceGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); @@ -258,29 +259,29 @@ void UDPSinkGUI::displaySettings() blockApplySettings(false); } -void UDPSinkGUI::channelMarkerChangedByCursor() +void UDPSourceGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettings(); } -void UDPSinkGUI::on_deltaFrequency_changed(qint64 value) +void UDPSourceGUI::on_deltaFrequency_changed(qint64 value) { m_settings.m_inputFrequencyOffset = value; m_channelMarker.setCenterFrequency(value); applySettings(); } -void UDPSinkGUI::on_sampleFormat_currentIndexChanged(int index) +void UDPSourceGUI::on_sampleFormat_currentIndexChanged(int index) { - if (index == (int) UDPSinkSettings::FormatNFM) { + if (index == (int) UDPSourceSettings::FormatNFM) { ui->fmDeviation->setEnabled(true); } else { ui->fmDeviation->setEnabled(false); } - if (index == (int) UDPSinkSettings::FormatAM) { + if (index == (int) UDPSourceSettings::FormatAM) { ui->amModPercent->setEnabled(true); } else { ui->amModPercent->setEnabled(false); @@ -292,14 +293,14 @@ void UDPSinkGUI::on_sampleFormat_currentIndexChanged(int index) ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_localUDPAddress_editingFinished() +void UDPSourceGUI::on_localUDPAddress_editingFinished() { m_settings.m_udpAddress = ui->localUDPAddress->text(); ui->applyBtn->setEnabled(true); ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_localUDPPort_editingFinished() +void UDPSourceGUI::on_localUDPPort_editingFinished() { bool ok; quint16 udpPort = ui->localUDPPort->text().toInt(&ok); @@ -315,7 +316,7 @@ void UDPSinkGUI::on_localUDPPort_editingFinished() ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSourceGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; Real inputSampleRate = ui->sampleRate->text().toDouble(&ok); @@ -331,7 +332,7 @@ void UDPSinkGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unu ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSourceGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; Real rfBandwidth = ui->rfBandwidth->text().toDouble(&ok); @@ -352,7 +353,7 @@ void UDPSinkGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((un ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSourceGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; int fmDeviation = ui->fmDeviation->text().toInt(&ok); @@ -368,7 +369,7 @@ void UDPSinkGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((un ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_amModPercent_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSourceGUI::on_amModPercent_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; int amModPercent = ui->amModPercent->text().toInt(&ok); @@ -385,21 +386,21 @@ void UDPSinkGUI::on_amModPercent_textEdited(const QString& arg1 __attribute__((u ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_gainIn_valueChanged(int value) +void UDPSourceGUI::on_gainIn_valueChanged(int value) { m_settings.m_gainIn = value / 10.0; ui->gainInText->setText(tr("%1").arg(m_settings.m_gainIn, 0, 'f', 1)); applySettings(); } -void UDPSinkGUI::on_gainOut_valueChanged(int value) +void UDPSourceGUI::on_gainOut_valueChanged(int value) { m_settings.m_gainOut = value / 10.0; ui->gainOutText->setText(tr("%1").arg(m_settings.m_gainOut, 0, 'f', 1)); applySettings(); } -void UDPSinkGUI::on_squelch_valueChanged(int value) +void UDPSourceGUI::on_squelch_valueChanged(int value) { m_settings.m_squelchEnabled = (value != -100); m_settings.m_squelch = value * 1.0; @@ -413,20 +414,20 @@ void UDPSinkGUI::on_squelch_valueChanged(int value) applySettings(); } -void UDPSinkGUI::on_squelchGate_valueChanged(int value) +void UDPSourceGUI::on_squelchGate_valueChanged(int value) { m_settings.m_squelchGate = value / 100.0; ui->squelchGateText->setText(tr("%1").arg(roundf(value * 10.0), 0, 'f', 0)); applySettings(); } -void UDPSinkGUI::on_channelMute_toggled(bool checked) +void UDPSourceGUI::on_channelMute_toggled(bool checked) { m_settings.m_channelMute = checked; applySettings(); } -void UDPSinkGUI::on_applyBtn_clicked() +void UDPSourceGUI::on_applyBtn_clicked() { if (m_rfBandwidthChanged) { @@ -439,32 +440,32 @@ void UDPSinkGUI::on_applyBtn_clicked() applySettings(); } -void UDPSinkGUI::on_resetUDPReadIndex_clicked() +void UDPSourceGUI::on_resetUDPReadIndex_clicked() { - m_udpSink->resetReadIndex(); + m_udpSource->resetReadIndex(); } -void UDPSinkGUI::on_autoRWBalance_toggled(bool checked) +void UDPSourceGUI::on_autoRWBalance_toggled(bool checked) { m_settings.m_autoRWBalance = checked; applySettings(); } -void UDPSinkGUI::on_stereoInput_toggled(bool checked) +void UDPSourceGUI::on_stereoInput_toggled(bool checked) { m_settings.m_stereoInput = checked; applySettings(); } -void UDPSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown) +void UDPSourceGUI::onWidgetRolled(QWidget* widget, bool rollDown) { - if ((widget == ui->spectrumBox) && (m_udpSink != 0)) + if ((widget == ui->spectrumBox) && (m_udpSource != 0)) { - m_udpSink->setSpectrum(rollDown); + m_udpSource->setSpectrum(rollDown); } } -void UDPSinkGUI::onMenuDialogCalled(const QPoint &p) +void UDPSourceGUI::onMenuDialogCalled(const QPoint &p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); dialog.move(p); @@ -479,20 +480,20 @@ void UDPSinkGUI::onMenuDialogCalled(const QPoint &p) applySettings(); } -void UDPSinkGUI::leaveEvent(QEvent*) +void UDPSourceGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); } -void UDPSinkGUI::enterEvent(QEvent*) +void UDPSourceGUI::enterEvent(QEvent*) { m_channelMarker.setHighlighted(true); } -void UDPSinkGUI::tick() +void UDPSourceGUI::tick() { - m_channelPowerAvg(m_udpSink->getMagSq()); - m_inPowerAvg(m_udpSink->getInMagSq()); + m_channelPowerAvg(m_udpSource->getMagSq()); + m_inPowerAvg(m_udpSource->getInMagSq()); if (m_tickCount % 4 == 0) { @@ -502,13 +503,13 @@ void UDPSinkGUI::tick() ui->inputPower->setText(tr("%1").arg(inPowDb, 0, 'f', 1)); } - int32_t bufferGauge = m_udpSink->getBufferGauge(); + int32_t bufferGauge = m_udpSource->getBufferGauge(); ui->bufferGaugeNegative->setValue((bufferGauge < 0 ? -bufferGauge : 0)); ui->bufferGaugePositive->setValue((bufferGauge < 0 ? 0 : bufferGauge)); QString s = QString::number(bufferGauge, 'f', 0); ui->bufferRWBalanceText->setText(tr("%1").arg(s)); - if (m_udpSink->getSquelchOpen()) { + if (m_udpSource->getSquelchOpen()) { ui->channelMute->setStyleSheet("QToolButton { background-color : green; }"); } else { ui->channelMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); @@ -517,32 +518,32 @@ void UDPSinkGUI::tick() m_tickCount++; } -void UDPSinkGUI::setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampleFormat) +void UDPSourceGUI::setSampleFormatIndex(const UDPSourceSettings::SampleFormat& sampleFormat) { switch(sampleFormat) { - case UDPSinkSettings::FormatSnLE: + case UDPSourceSettings::FormatSnLE: ui->sampleFormat->setCurrentIndex(0); ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); ui->stereoInput->setEnabled(false); break; - case UDPSinkSettings::FormatNFM: + case UDPSourceSettings::FormatNFM: ui->sampleFormat->setCurrentIndex(1); ui->fmDeviation->setEnabled(true); ui->stereoInput->setEnabled(true); break; - case UDPSinkSettings::FormatLSB: + case UDPSourceSettings::FormatLSB: ui->sampleFormat->setCurrentIndex(2); ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; - case UDPSinkSettings::FormatUSB: + case UDPSourceSettings::FormatUSB: ui->sampleFormat->setCurrentIndex(3); ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; - case UDPSinkSettings::FormatAM: + case UDPSourceSettings::FormatAM: ui->sampleFormat->setCurrentIndex(4); ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); @@ -556,38 +557,38 @@ void UDPSinkGUI::setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampl } } -void UDPSinkGUI::setSampleFormat(int index) +void UDPSourceGUI::setSampleFormat(int index) { switch(index) { case 0: - m_settings.m_sampleFormat = UDPSinkSettings::FormatSnLE; + m_settings.m_sampleFormat = UDPSourceSettings::FormatSnLE; ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); ui->stereoInput->setEnabled(false); break; case 1: - m_settings.m_sampleFormat = UDPSinkSettings::FormatNFM; + m_settings.m_sampleFormat = UDPSourceSettings::FormatNFM; ui->fmDeviation->setEnabled(true); ui->stereoInput->setEnabled(true); break; case 2: - m_settings.m_sampleFormat = UDPSinkSettings::FormatLSB; + m_settings.m_sampleFormat = UDPSourceSettings::FormatLSB; ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; case 3: - m_settings.m_sampleFormat = UDPSinkSettings::FormatUSB; + m_settings.m_sampleFormat = UDPSourceSettings::FormatUSB; ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; case 4: - m_settings.m_sampleFormat = UDPSinkSettings::FormatAM; + m_settings.m_sampleFormat = UDPSourceSettings::FormatAM; ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; default: - m_settings.m_sampleFormat = UDPSinkSettings::FormatSnLE; + m_settings.m_sampleFormat = UDPSourceSettings::FormatSnLE; ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); ui->stereoInput->setEnabled(false); diff --git a/plugins/channeltx/udpsink/udpsinkgui.h b/plugins/channeltx/udpsink/udpsourcegui.h similarity index 83% rename from plugins/channeltx/udpsink/udpsinkgui.h rename to plugins/channeltx/udpsink/udpsourcegui.h index f8190df50..6658da30d 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.h +++ b/plugins/channeltx/udpsink/udpsourcegui.h @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINKGUI_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINKGUI_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEGUI_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEGUI_H_ #include #include @@ -25,8 +25,8 @@ #include "util/messagequeue.h" #include "util/movingaverage.h" -#include "udpsink.h" -#include "udpsinksettings.h" +#include "udpsource.h" +#include "udpsourcesettings.h" class PluginAPI; class DeviceUISet; @@ -34,14 +34,14 @@ class BasebandSampleSource; class SpectrumVis; namespace Ui { - class UDPSinkGUI; + class UDPSourceGUI; } -class UDPSinkGUI : public RollupWidget, public PluginInstanceGUI { +class UDPSourceGUI : public RollupWidget, public PluginInstanceGUI { Q_OBJECT public: - static UDPSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); + static UDPSourceGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); virtual void destroy(); void setName(const QString& name); @@ -58,30 +58,30 @@ public slots: void channelMarkerChangedByCursor(); private: - Ui::UDPSinkGUI* ui; + Ui::UDPSourceGUI* ui; PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; SpectrumVis* m_spectrumVis; - UDPSink* m_udpSink; + UDPSource* m_udpSource; MovingAverageUtil m_channelPowerAvg; MovingAverageUtil m_inPowerAvg; uint32_t m_tickCount; ChannelMarker m_channelMarker; // settings - UDPSinkSettings m_settings; + UDPSourceSettings m_settings; bool m_rfBandwidthChanged; bool m_doApplySettings; MessageQueue m_inputMessageQueue; - explicit UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = NULL); - virtual ~UDPSinkGUI(); + explicit UDPSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = NULL); + virtual ~UDPSourceGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); void setSampleFormat(int index); - void setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampleFormat); + void setSampleFormatIndex(const UDPSourceSettings::SampleFormat& sampleFormat); void leaveEvent(QEvent*); void enterEvent(QEvent*); @@ -110,4 +110,4 @@ private slots: void tick(); }; -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINKGUI_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEGUI_H_ */ diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsink/udpsourcegui.ui similarity index 99% rename from plugins/channeltx/udpsink/udpsinkgui.ui rename to plugins/channeltx/udpsink/udpsourcegui.ui index 7ce8502c6..245163d9e 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsink/udpsourcegui.ui @@ -1,7 +1,7 @@ - UDPSinkGUI - + UDPSourceGUI + 0 @@ -29,13 +29,13 @@
    - UDP Sample Sink + UDP Source Sink -1 - UDP Sample Sink + UDP Source Sink diff --git a/plugins/channeltx/udpsink/udpsinkmsg.cpp b/plugins/channeltx/udpsink/udpsourcemsg.cpp similarity index 92% rename from plugins/channeltx/udpsink/udpsinkmsg.cpp rename to plugins/channeltx/udpsink/udpsourcemsg.cpp index 87f3cdbb4..1645f3172 100644 --- a/plugins/channeltx/udpsink/udpsinkmsg.cpp +++ b/plugins/channeltx/udpsink/udpsourcemsg.cpp @@ -14,7 +14,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "udpsinkmsg.h" +#include "udpsourcemsg.h" -MESSAGE_CLASS_DEFINITION(UDPSinkMessages::MsgSampleRateCorrection, Message) +MESSAGE_CLASS_DEFINITION(UDPSourceMessages::MsgSampleRateCorrection, Message) diff --git a/plugins/channeltx/udpsink/udpsinkmsg.h b/plugins/channeltx/udpsink/udpsourcemsg.h similarity index 91% rename from plugins/channeltx/udpsink/udpsinkmsg.h rename to plugins/channeltx/udpsink/udpsourcemsg.h index efe062461..2df91e5a5 100644 --- a/plugins/channeltx/udpsink/udpsinkmsg.h +++ b/plugins/channeltx/udpsink/udpsourcemsg.h @@ -14,15 +14,15 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINKMSG_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINKMSG_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEMSG_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEMSG_H_ #include "util/message.h" /** - * Message(s) used to communicate back from UDPSinkUDPHandler to UDPSink + * Message(s) used to communicate back from UDPSinkUDPHandler to UDPSource */ -class UDPSinkMessages +class UDPSourceMessages { public: class MsgSampleRateCorrection : public Message { @@ -50,4 +50,4 @@ public: }; -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINKMSG_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEMSG_H_ */ diff --git a/plugins/channeltx/udpsink/udpsinkplugin.cpp b/plugins/channeltx/udpsink/udpsourceplugin.cpp similarity index 66% rename from plugins/channeltx/udpsink/udpsinkplugin.cpp rename to plugins/channeltx/udpsink/udpsourceplugin.cpp index 5d3b70ea0..2727ae797 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.cpp +++ b/plugins/channeltx/udpsink/udpsourceplugin.cpp @@ -15,66 +15,66 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "udpsinkplugin.h" +#include "udpsourceplugin.h" #include #include "plugin/pluginapi.h" #ifndef SERVER_MODE -#include "udpsinkgui.h" +#include "udpsourcegui.h" #endif -#include "udpsink.h" +#include "udpsource.h" -const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { - QString("UDP Channel Sink"), - QString("4.0.7"), +const PluginDescriptor UDPSourcePlugin::m_pluginDescriptor = { + QString("UDP Channel Source"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, QString("https://github.com/f4exb/sdrangel") }; -UDPSinkPlugin::UDPSinkPlugin(QObject* parent) : +UDPSourcePlugin::UDPSourcePlugin(QObject* parent) : QObject(parent), m_pluginAPI(0) { } -const PluginDescriptor& UDPSinkPlugin::getPluginDescriptor() const +const PluginDescriptor& UDPSourcePlugin::getPluginDescriptor() const { return m_pluginDescriptor; } -void UDPSinkPlugin::initPlugin(PluginAPI* pluginAPI) +void UDPSourcePlugin::initPlugin(PluginAPI* pluginAPI) { m_pluginAPI = pluginAPI; // register TCP Channel Source - m_pluginAPI->registerTxChannel(UDPSink::m_channelIdURI, UDPSink::m_channelId, this); + m_pluginAPI->registerTxChannel(UDPSource::m_channelIdURI, UDPSource::m_channelId, this); } #ifdef SERVER_MODE -PluginInstanceGUI* UDPSinkPlugin::createTxChannelGUI( +PluginInstanceGUI* UDPSourcePlugin::createTxChannelGUI( DeviceUISet *deviceUISet __attribute__((unused)), BasebandSampleSource *txChannel __attribute__((unused))) { return 0; } #else -PluginInstanceGUI* UDPSinkPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) +PluginInstanceGUI* UDPSourcePlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { - return UDPSinkGUI::create(m_pluginAPI, deviceUISet, txChannel); + return UDPSourceGUI::create(m_pluginAPI, deviceUISet, txChannel); } #endif -BasebandSampleSource* UDPSinkPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) +BasebandSampleSource* UDPSourcePlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { - return new UDPSink(deviceAPI); + return new UDPSource(deviceAPI); } -ChannelSourceAPI* UDPSinkPlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +ChannelSourceAPI* UDPSourcePlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) { - return new UDPSink(deviceAPI); + return new UDPSource(deviceAPI); } diff --git a/plugins/channeltx/udpsink/udpsinkplugin.h b/plugins/channeltx/udpsink/udpsourceplugin.h similarity index 95% rename from plugins/channeltx/udpsink/udpsinkplugin.h rename to plugins/channeltx/udpsink/udpsourceplugin.h index 2932f7055..7e5cfc4b9 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.h +++ b/plugins/channeltx/udpsink/udpsourceplugin.h @@ -24,13 +24,13 @@ class DeviceUISet; class BasebandSampleSource; -class UDPSinkPlugin : public QObject, PluginInterface { +class UDPSourcePlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) Q_PLUGIN_METADATA(IID "sdrangel.channeltx.udpsink") public: - explicit UDPSinkPlugin(QObject* parent = 0); + explicit UDPSourcePlugin(QObject* parent = 0); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/channeltx/udpsink/udpsinksettings.cpp b/plugins/channeltx/udpsink/udpsourcesettings.cpp similarity index 95% rename from plugins/channeltx/udpsink/udpsinksettings.cpp rename to plugins/channeltx/udpsink/udpsourcesettings.cpp index cc8cdaf83..88cda9491 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.cpp +++ b/plugins/channeltx/udpsink/udpsourcesettings.cpp @@ -14,21 +14,22 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "udpsourcesettings.h" + #include #include "dsp/dspengine.h" #include "util/simpleserializer.h" #include "settings/serializable.h" -#include "udpsinksettings.h" -UDPSinkSettings::UDPSinkSettings() : +UDPSourceSettings::UDPSourceSettings() : m_channelMarker(0), m_spectrumGUI(0) { resetToDefaults(); } -void UDPSinkSettings::resetToDefaults() +void UDPSourceSettings::resetToDefaults() { m_sampleFormat = FormatSnLE; m_inputSampleRate = 48000; @@ -51,7 +52,7 @@ void UDPSinkSettings::resetToDefaults() m_title = "UDP Sample Sink"; } -QByteArray UDPSinkSettings::serialize() const +QByteArray UDPSourceSettings::serialize() const { SimpleSerializer s(1); s.writeS32(2, m_inputFrequencyOffset); @@ -82,7 +83,7 @@ QByteArray UDPSinkSettings::serialize() const return s.final(); } -bool UDPSinkSettings::deserialize(const QByteArray& data) +bool UDPSourceSettings::deserialize(const QByteArray& data) { SimpleDeserializer d(data); diff --git a/plugins/channeltx/udpsink/udpsinksettings.h b/plugins/channeltx/udpsink/udpsourcesettings.h similarity index 90% rename from plugins/channeltx/udpsink/udpsinksettings.h rename to plugins/channeltx/udpsink/udpsourcesettings.h index 9442fe627..4a7be431f 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.h +++ b/plugins/channeltx/udpsink/udpsourcesettings.h @@ -14,16 +14,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINKSETTINGS_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINKSETTINGS_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCESETTINGS_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCESETTINGS_H_ #include #include #include +#include "dsp/dsptypes.h" + class Serializable; -struct UDPSinkSettings +struct UDPSourceSettings { enum SampleFormat { FormatSnLE, @@ -59,7 +61,7 @@ struct UDPSinkSettings Serializable *m_channelMarker; Serializable *m_spectrumGUI; - UDPSinkSettings(); + UDPSourceSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } @@ -70,4 +72,4 @@ struct UDPSinkSettings -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINKSETTINGS_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCESETTINGS_H_ */ diff --git a/plugins/channeltx/udpsink/udpsinkudphandler.cpp b/plugins/channeltx/udpsink/udpsourceudphandler.cpp similarity index 85% rename from plugins/channeltx/udpsink/udpsinkudphandler.cpp rename to plugins/channeltx/udpsink/udpsourceudphandler.cpp index f017e0a3f..5422002ef 100644 --- a/plugins/channeltx/udpsink/udpsinkudphandler.cpp +++ b/plugins/channeltx/udpsink/udpsourceudphandler.cpp @@ -14,16 +14,17 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "udpsourceudphandler.h" + #include #include #include -#include "udpsinkmsg.h" -#include "udpsinkudphandler.h" +#include "udpsourcemsg.h" -MESSAGE_CLASS_DEFINITION(UDPSinkUDPHandler::MsgUDPAddressAndPort, Message) +MESSAGE_CLASS_DEFINITION(UDPSourceUDPHandler::MsgUDPAddressAndPort, Message) -UDPSinkUDPHandler::UDPSinkUDPHandler() : +UDPSourceUDPHandler::UDPSourceUDPHandler() : m_dataSocket(0), m_dataAddress(QHostAddress::LocalHost), m_remoteAddress(QHostAddress::LocalHost), @@ -46,12 +47,12 @@ UDPSinkUDPHandler::UDPSinkUDPHandler() : connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages())); } -UDPSinkUDPHandler::~UDPSinkUDPHandler() +UDPSourceUDPHandler::~UDPSourceUDPHandler() { delete[] m_udpBuf; } -void UDPSinkUDPHandler::start() +void UDPSourceUDPHandler::start() { qDebug("UDPSinkUDPHandler::start"); @@ -77,7 +78,7 @@ void UDPSinkUDPHandler::start() } } -void UDPSinkUDPHandler::stop() +void UDPSourceUDPHandler::stop() { qDebug("UDPSinkUDPHandler::stop"); @@ -94,7 +95,7 @@ void UDPSinkUDPHandler::stop() } } -void UDPSinkUDPHandler::dataReadyRead() +void UDPSourceUDPHandler::dataReadyRead() { while (m_dataSocket->hasPendingDatagrams() && m_dataConnected) { @@ -127,7 +128,7 @@ void UDPSinkUDPHandler::dataReadyRead() } } -void UDPSinkUDPHandler::moveData(char *blk) +void UDPSourceUDPHandler::moveData(char *blk) { memcpy(m_udpBuf[m_writeFrameIndex], blk, m_udpBlockSize); @@ -138,7 +139,7 @@ void UDPSinkUDPHandler::moveData(char *blk) } } -void UDPSinkUDPHandler::readSample(qint16 &t) +void UDPSourceUDPHandler::readSample(qint16 &t) { if (m_readFrameIndex == m_writeFrameIndex) // block until more writes { @@ -151,7 +152,7 @@ void UDPSinkUDPHandler::readSample(qint16 &t) } } -void UDPSinkUDPHandler::readSample(AudioSample &a) +void UDPSourceUDPHandler::readSample(AudioSample &a) { if (m_readFrameIndex == m_writeFrameIndex) // block until more writes { @@ -165,7 +166,7 @@ void UDPSinkUDPHandler::readSample(AudioSample &a) } } -void UDPSinkUDPHandler::readSample(Sample &s) +void UDPSourceUDPHandler::readSample(Sample &s) { if (m_readFrameIndex == m_writeFrameIndex) // block until more writes { @@ -179,7 +180,7 @@ void UDPSinkUDPHandler::readSample(Sample &s) } } -void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) +void UDPSourceUDPHandler::advanceReadPointer(int nbBytes) { if (m_readIndex < m_udpBlockSize - 2*nbBytes) { @@ -209,7 +210,7 @@ void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) float dd = d - m_d; // derivative float c = (d / 15.0) + (dd / 20.0); // damping and scaling c = c < -0.05 ? -0.05 : c > 0.05 ? 0.05 : c; // limit - UDPSinkMessages::MsgSampleRateCorrection *msg = UDPSinkMessages::MsgSampleRateCorrection::create(c, d); + UDPSourceMessages::MsgSampleRateCorrection *msg = UDPSourceMessages::MsgSampleRateCorrection::create(c, d); if (m_autoRWBalance && m_feedbackMessageQueue) { m_feedbackMessageQueue->push(msg); @@ -222,13 +223,13 @@ void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) } } -void UDPSinkUDPHandler::configureUDPLink(const QString& address, quint16 port) +void UDPSourceUDPHandler::configureUDPLink(const QString& address, quint16 port) { Message* msg = MsgUDPAddressAndPort::create(address, port); m_inputMessageQueue.push(msg); } -void UDPSinkUDPHandler::applyUDPLink(const QString& address, quint16 port) +void UDPSourceUDPHandler::applyUDPLink(const QString& address, quint16 port) { qDebug("UDPSinkUDPHandler::configureUDPLink: %s:%d", address.toStdString().c_str(), port); bool addressOK = m_dataAddress.setAddress(address); @@ -245,7 +246,7 @@ void UDPSinkUDPHandler::applyUDPLink(const QString& address, quint16 port) start(); } -void UDPSinkUDPHandler::resetReadIndex() +void UDPSourceUDPHandler::resetReadIndex() { m_readFrameIndex = (m_writeFrameIndex + (m_nbUDPFrames/2)) % m_nbUDPFrames; m_rwDelta = m_nbUDPFrames/2; @@ -253,7 +254,7 @@ void UDPSinkUDPHandler::resetReadIndex() m_d = 0.0f; } -void UDPSinkUDPHandler::resizeBuffer(float sampleRate) +void UDPSourceUDPHandler::resizeBuffer(float sampleRate) { int halfNbFrames = std::max((sampleRate / 375.0), (m_minNbUDPFrames / 2.0)); qDebug("UDPSinkUDPHandler::resizeBuffer: nb_frames: %d", 2*halfNbFrames); @@ -271,7 +272,7 @@ void UDPSinkUDPHandler::resizeBuffer(float sampleRate) resetReadIndex(); } -void UDPSinkUDPHandler::handleMessages() +void UDPSourceUDPHandler::handleMessages() { Message* message; @@ -284,11 +285,11 @@ void UDPSinkUDPHandler::handleMessages() } } -bool UDPSinkUDPHandler::handleMessage(const Message& cmd) +bool UDPSourceUDPHandler::handleMessage(const Message& cmd) { - if (UDPSinkUDPHandler::MsgUDPAddressAndPort::match(cmd)) + if (UDPSourceUDPHandler::MsgUDPAddressAndPort::match(cmd)) { - UDPSinkUDPHandler::MsgUDPAddressAndPort& notif = (UDPSinkUDPHandler::MsgUDPAddressAndPort&) cmd; + UDPSourceUDPHandler::MsgUDPAddressAndPort& notif = (UDPSourceUDPHandler::MsgUDPAddressAndPort&) cmd; applyUDPLink(notif.getAddress(), notif.getPort()); return true; } diff --git a/plugins/channeltx/udpsink/udpsinkudphandler.h b/plugins/channeltx/udpsink/udpsourceudphandler.h similarity index 93% rename from plugins/channeltx/udpsink/udpsinkudphandler.h rename to plugins/channeltx/udpsink/udpsourceudphandler.h index e75cca5b3..13b3faf60 100644 --- a/plugins/channeltx/udpsink/udpsinkudphandler.h +++ b/plugins/channeltx/udpsink/udpsourceudphandler.h @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINKUDPHANDLER_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINKUDPHANDLER_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEUDPHANDLER_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEUDPHANDLER_H_ #include #include @@ -27,12 +27,12 @@ #include "util/message.h" #include "util/messagequeue.h" -class UDPSinkUDPHandler : public QObject +class UDPSourceUDPHandler : public QObject { Q_OBJECT public: - UDPSinkUDPHandler(); - virtual ~UDPSinkUDPHandler(); + UDPSourceUDPHandler(); + virtual ~UDPSourceUDPHandler(); void start(); void stop(); @@ -120,4 +120,4 @@ private slots: -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINKUDPHANDLER_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEUDPHANDLER_H_ */ diff --git a/pluginssrv/channeltx/udpsink/CMakeLists.txt b/pluginssrv/channeltx/udpsink/CMakeLists.txt index 5f81e4025..be2cde116 100644 --- a/pluginssrv/channeltx/udpsink/CMakeLists.txt +++ b/pluginssrv/channeltx/udpsink/CMakeLists.txt @@ -1,22 +1,22 @@ -project(udpsink) +project(udpsource) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(PLUGIN_PREFIX "../../../plugins/channeltx/udpsink") -set(udpsink_SOURCES - ${PLUGIN_PREFIX}/udpsink.cpp - ${PLUGIN_PREFIX}/udpsinkplugin.cpp - ${PLUGIN_PREFIX}/udpsinkudphandler.cpp - ${PLUGIN_PREFIX}/udpsinkmsg.cpp - ${PLUGIN_PREFIX}/udpsinksettings.cpp +set(udpsource_SOURCES + ${PLUGIN_PREFIX}/udpsource.cpp + ${PLUGIN_PREFIX}/udpsourceplugin.cpp + ${PLUGIN_PREFIX}/udpsourceudphandler.cpp + ${PLUGIN_PREFIX}/udpsourcemsg.cpp + ${PLUGIN_PREFIX}/udpsourcesettings.cpp ) -set(udpsink_HEADERS - ${PLUGIN_PREFIX}/udpsink.h - ${PLUGIN_PREFIX}/udpsinkplugin.h - ${PLUGIN_PREFIX}/udpsinkudphandler.h - ${PLUGIN_PREFIX}/udpsinkmsg.h - ${PLUGIN_PREFIX}/udpsinksettings.h +set(udpsource_HEADERS + ${PLUGIN_PREFIX}/udpsource.h + ${PLUGIN_PREFIX}/udpsourceplugin.h + ${PLUGIN_PREFIX}/udpsourceudphandler.h + ${PLUGIN_PREFIX}/udpsourcemsg.h + ${PLUGIN_PREFIX}/udpsourcesettings.h ) include_directories( @@ -29,17 +29,17 @@ add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_PLUGIN) add_definitions(-DQT_SHARED) -add_library(modudpsinksrv SHARED - ${udpsink_SOURCES} - ${udpsink_HEADERS_MOC} +add_library(udpsourcesrv SHARED + ${udpsource_SOURCES} + ${udpsource_HEADERS_MOC} ) -target_link_libraries(modudpsinksrv +target_link_libraries(udpsourcesrv ${QT_LIBRARIES} sdrbase swagger ) -target_link_libraries(modudpsinksrv Qt5::Core Qt5::Network) +target_link_libraries(udpsourcesrv Qt5::Core Qt5::Network) -install(TARGETS modudpsinksrv DESTINATION lib/pluginssrv/channeltx) +install(TARGETS udpsourcesrv DESTINATION lib/pluginssrv/channeltx) diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 1d3b52535..82edc9cbf 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2208,7 +2208,7 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } - else if (*channelType == "UDPSink") + else if (*channelType == "UDPSource") { if (channelSettings.getTx() != 0) { From 52d4baf755b40816aa0efd823f2e8daf6da08d6e Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 23:18:00 +0200 Subject: [PATCH 736/956] REST API: renamed UDPSink to UDPSource --- .../sdrangel/api/swagger/include/{UDPSink.yaml => UDPSource.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename swagger/sdrangel/api/swagger/include/{UDPSink.yaml => UDPSource.yaml} (100%) diff --git a/swagger/sdrangel/api/swagger/include/UDPSink.yaml b/swagger/sdrangel/api/swagger/include/UDPSource.yaml similarity index 100% rename from swagger/sdrangel/api/swagger/include/UDPSink.yaml rename to swagger/sdrangel/api/swagger/include/UDPSource.yaml From 6f368d123bdfa53fb79c25030ea0be2ccf878b4d Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 23:22:59 +0200 Subject: [PATCH 737/956] renamed udpsink directory to udpsource --- plugins/channeltx/CMakeLists.txt | 2 +- .../{udpsink => udpsource}/CMakeLists.txt | 0 .../{udpsink => udpsource}/readme.md | 0 .../{udpsink => udpsource}/udpsink.pro | 0 .../{udpsink => udpsource}/udpsource.cpp | 104 +++++++++--------- .../{udpsink => udpsource}/udpsource.h | 0 .../{udpsink => udpsource}/udpsourcegui.cpp | 0 .../{udpsink => udpsource}/udpsourcegui.h | 0 .../{udpsink => udpsource}/udpsourcegui.ui | 0 .../{udpsink => udpsource}/udpsourcemsg.cpp | 0 .../{udpsink => udpsource}/udpsourcemsg.h | 0 .../udpsourceplugin.cpp | 0 .../{udpsink => udpsource}/udpsourceplugin.h | 0 .../udpsourcesettings.cpp | 0 .../udpsourcesettings.h | 0 .../udpsourceudphandler.cpp | 18 +-- .../udpsourceudphandler.h | 0 pluginssrv/channeltx/CMakeLists.txt | 2 +- .../{udpsink => udpsource}/CMakeLists.txt | 2 +- sdrbase/resources/res.qrc | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 18 +-- .../include/{UDPSink.yaml => UDPSource.yaml} | 8 +- .../resources/webapi/doc/swagger/swagger.yaml | 8 +- sdrbase/webapi/webapirequestmapper.cpp | 12 +- .../api/swagger/include/UDPSource.yaml | 8 +- swagger/sdrangel/api/swagger/swagger.yaml | 8 +- swagger/sdrangel/code/html2/index.html | 18 +-- .../code/qt5/client/SWGChannelReport.cpp | 32 +++--- .../code/qt5/client/SWGChannelReport.h | 10 +- .../code/qt5/client/SWGChannelSettings.cpp | 32 +++--- .../code/qt5/client/SWGChannelSettings.h | 10 +- .../code/qt5/client/SWGModelFactory.h | 12 +- ...PSinkReport.cpp => SWGUDPSourceReport.cpp} | 44 ++++---- ...WGUDPSinkReport.h => SWGUDPSourceReport.h} | 20 ++-- ...kSettings.cpp => SWGUDPSourceSettings.cpp} | 100 ++++++++--------- ...PSinkSettings.h => SWGUDPSourceSettings.h} | 20 ++-- swagger/sdrangel/examples/tx_test.py | 26 ++--- 37 files changed, 258 insertions(+), 258 deletions(-) rename plugins/channeltx/{udpsink => udpsource}/CMakeLists.txt (100%) rename plugins/channeltx/{udpsink => udpsource}/readme.md (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsink.pro (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsource.cpp (85%) rename plugins/channeltx/{udpsink => udpsource}/udpsource.h (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourcegui.cpp (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourcegui.h (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourcegui.ui (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourcemsg.cpp (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourcemsg.h (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourceplugin.cpp (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourceplugin.h (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourcesettings.cpp (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourcesettings.h (100%) rename plugins/channeltx/{udpsink => udpsource}/udpsourceudphandler.cpp (90%) rename plugins/channeltx/{udpsink => udpsource}/udpsourceudphandler.h (100%) rename pluginssrv/channeltx/{udpsink => udpsource}/CMakeLists.txt (94%) rename sdrbase/resources/webapi/doc/swagger/include/{UDPSink.yaml => UDPSource.yaml} (94%) rename swagger/sdrangel/code/qt5/client/{SWGUDPSinkReport.cpp => SWGUDPSourceReport.cpp} (83%) rename swagger/sdrangel/code/qt5/client/{SWGUDPSinkReport.h => SWGUDPSourceReport.h} (88%) rename swagger/sdrangel/code/qt5/client/{SWGUDPSinkSettings.cpp => SWGUDPSourceSettings.cpp} (83%) rename swagger/sdrangel/code/qt5/client/{SWGUDPSinkSettings.h => SWGUDPSourceSettings.h} (92%) diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index c1586843c..3b24dfce0 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(modam) add_subdirectory(modnfm) add_subdirectory(modssb) add_subdirectory(modwfm) -add_subdirectory(udpsink) +add_subdirectory(udpsource) find_package(CM256cc) if(CM256CC_FOUND) diff --git a/plugins/channeltx/udpsink/CMakeLists.txt b/plugins/channeltx/udpsource/CMakeLists.txt similarity index 100% rename from plugins/channeltx/udpsink/CMakeLists.txt rename to plugins/channeltx/udpsource/CMakeLists.txt diff --git a/plugins/channeltx/udpsink/readme.md b/plugins/channeltx/udpsource/readme.md similarity index 100% rename from plugins/channeltx/udpsink/readme.md rename to plugins/channeltx/udpsource/readme.md diff --git a/plugins/channeltx/udpsink/udpsink.pro b/plugins/channeltx/udpsource/udpsink.pro similarity index 100% rename from plugins/channeltx/udpsink/udpsink.pro rename to plugins/channeltx/udpsource/udpsink.pro diff --git a/plugins/channeltx/udpsink/udpsource.cpp b/plugins/channeltx/udpsource/udpsource.cpp similarity index 85% rename from plugins/channeltx/udpsink/udpsource.cpp rename to plugins/channeltx/udpsource/udpsource.cpp index 0f3a89bc9..9e887963b 100644 --- a/plugins/channeltx/udpsink/udpsource.cpp +++ b/plugins/channeltx/udpsource/udpsource.cpp @@ -18,7 +18,7 @@ #include "SWGChannelSettings.h" #include "SWGChannelReport.h" -#include "SWGUDPSinkReport.h" +#include "SWGUDPSourceReport.h" #include "device/devicesinkapi.h" #include "dsp/upchannelizer.h" @@ -601,8 +601,8 @@ int UDPSource::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - response.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); - response.getUdpSinkSettings()->init(); + response.setUdpSourceSettings(new SWGSDRangel::SWGUDPSourceSettings()); + response.getUdpSourceSettings()->init(); webapiFormatChannelSettings(response, m_settings); return 200; } @@ -617,63 +617,63 @@ int UDPSource::webapiSettingsPutPatch( bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("sampleFormat")) { - settings.m_sampleFormat = (UDPSourceSettings::SampleFormat) response.getUdpSinkSettings()->getSampleFormat(); + settings.m_sampleFormat = (UDPSourceSettings::SampleFormat) response.getUdpSourceSettings()->getSampleFormat(); } if (channelSettingsKeys.contains("inputSampleRate")) { - settings.m_inputSampleRate = response.getUdpSinkSettings()->getInputSampleRate(); + settings.m_inputSampleRate = response.getUdpSourceSettings()->getInputSampleRate(); } if (channelSettingsKeys.contains("inputFrequencyOffset")) { - settings.m_inputFrequencyOffset = response.getUdpSinkSettings()->getInputFrequencyOffset(); + settings.m_inputFrequencyOffset = response.getUdpSourceSettings()->getInputFrequencyOffset(); frequencyOffsetChanged = true; } if (channelSettingsKeys.contains("rfBandwidth")) { - settings.m_rfBandwidth = response.getUdpSinkSettings()->getRfBandwidth(); + settings.m_rfBandwidth = response.getUdpSourceSettings()->getRfBandwidth(); } if (channelSettingsKeys.contains("lowCutoff")) { - settings.m_lowCutoff = response.getUdpSinkSettings()->getLowCutoff(); + settings.m_lowCutoff = response.getUdpSourceSettings()->getLowCutoff(); } if (channelSettingsKeys.contains("fmDeviation")) { - settings.m_fmDeviation = response.getUdpSinkSettings()->getFmDeviation(); + settings.m_fmDeviation = response.getUdpSourceSettings()->getFmDeviation(); } if (channelSettingsKeys.contains("amModFactor")) { - settings.m_amModFactor = response.getUdpSinkSettings()->getAmModFactor(); + settings.m_amModFactor = response.getUdpSourceSettings()->getAmModFactor(); } if (channelSettingsKeys.contains("channelMute")) { - settings.m_channelMute = response.getUdpSinkSettings()->getChannelMute() != 0; + settings.m_channelMute = response.getUdpSourceSettings()->getChannelMute() != 0; } if (channelSettingsKeys.contains("gainIn")) { - settings.m_gainIn = response.getUdpSinkSettings()->getGainIn(); + settings.m_gainIn = response.getUdpSourceSettings()->getGainIn(); } if (channelSettingsKeys.contains("gainOut")) { - settings.m_gainOut = response.getUdpSinkSettings()->getGainOut(); + settings.m_gainOut = response.getUdpSourceSettings()->getGainOut(); } if (channelSettingsKeys.contains("squelch")) { - settings.m_squelch = response.getUdpSinkSettings()->getSquelch(); + settings.m_squelch = response.getUdpSourceSettings()->getSquelch(); } if (channelSettingsKeys.contains("squelchGate")) { - settings.m_squelchGate = response.getUdpSinkSettings()->getSquelchGate(); + settings.m_squelchGate = response.getUdpSourceSettings()->getSquelchGate(); } if (channelSettingsKeys.contains("squelchEnabled")) { - settings.m_squelchEnabled = response.getUdpSinkSettings()->getSquelchEnabled() != 0; + settings.m_squelchEnabled = response.getUdpSourceSettings()->getSquelchEnabled() != 0; } if (channelSettingsKeys.contains("autoRWBalance")) { - settings.m_autoRWBalance = response.getUdpSinkSettings()->getAutoRwBalance() != 0; + settings.m_autoRWBalance = response.getUdpSourceSettings()->getAutoRwBalance() != 0; } if (channelSettingsKeys.contains("stereoInput")) { - settings.m_stereoInput = response.getUdpSinkSettings()->getStereoInput() != 0; + settings.m_stereoInput = response.getUdpSourceSettings()->getStereoInput() != 0; } if (channelSettingsKeys.contains("rgbColor")) { - settings.m_rgbColor = response.getUdpSinkSettings()->getRgbColor(); + settings.m_rgbColor = response.getUdpSourceSettings()->getRgbColor(); } if (channelSettingsKeys.contains("udpAddress")) { - settings.m_udpAddress = *response.getUdpSinkSettings()->getUdpAddress(); + settings.m_udpAddress = *response.getUdpSourceSettings()->getUdpAddress(); } if (channelSettingsKeys.contains("udpPort")) { - settings.m_udpPort = response.getUdpSinkSettings()->getUdpPort(); + settings.m_udpPort = response.getUdpSourceSettings()->getUdpPort(); } if (channelSettingsKeys.contains("title")) { - settings.m_title = *response.getUdpSinkSettings()->getTitle(); + settings.m_title = *response.getUdpSourceSettings()->getTitle(); } if (frequencyOffsetChanged) @@ -702,51 +702,51 @@ int UDPSource::webapiReportGet( SWGSDRangel::SWGChannelReport& response, QString& errorMessage __attribute__((unused))) { - response.setUdpSinkReport(new SWGSDRangel::SWGUDPSinkReport()); - response.getUdpSinkReport()->init(); + response.setUdpSourceReport(new SWGSDRangel::SWGUDPSourceReport()); + response.getUdpSourceReport()->init(); webapiFormatChannelReport(response); return 200; } void UDPSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSourceSettings& settings) { - response.getUdpSinkSettings()->setSampleFormat((int) settings.m_sampleFormat); - response.getUdpSinkSettings()->setInputSampleRate(settings.m_inputSampleRate); - response.getUdpSinkSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); - response.getUdpSinkSettings()->setRfBandwidth(settings.m_rfBandwidth); - response.getUdpSinkSettings()->setLowCutoff(settings.m_lowCutoff); - response.getUdpSinkSettings()->setFmDeviation(settings.m_fmDeviation); - response.getUdpSinkSettings()->setAmModFactor(settings.m_amModFactor); - response.getUdpSinkSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); - response.getUdpSinkSettings()->setGainIn(settings.m_gainIn); - response.getUdpSinkSettings()->setGainOut(settings.m_gainOut); - response.getUdpSinkSettings()->setSquelch(settings.m_squelch); - response.getUdpSinkSettings()->setSquelchGate(settings.m_squelchGate); - response.getUdpSinkSettings()->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); - response.getUdpSinkSettings()->setAutoRwBalance(settings.m_autoRWBalance ? 1 : 0); - response.getUdpSinkSettings()->setStereoInput(settings.m_stereoInput ? 1 : 0); - response.getUdpSinkSettings()->setRgbColor(settings.m_rgbColor); + response.getUdpSourceSettings()->setSampleFormat((int) settings.m_sampleFormat); + response.getUdpSourceSettings()->setInputSampleRate(settings.m_inputSampleRate); + response.getUdpSourceSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getUdpSourceSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getUdpSourceSettings()->setLowCutoff(settings.m_lowCutoff); + response.getUdpSourceSettings()->setFmDeviation(settings.m_fmDeviation); + response.getUdpSourceSettings()->setAmModFactor(settings.m_amModFactor); + response.getUdpSourceSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getUdpSourceSettings()->setGainIn(settings.m_gainIn); + response.getUdpSourceSettings()->setGainOut(settings.m_gainOut); + response.getUdpSourceSettings()->setSquelch(settings.m_squelch); + response.getUdpSourceSettings()->setSquelchGate(settings.m_squelchGate); + response.getUdpSourceSettings()->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); + response.getUdpSourceSettings()->setAutoRwBalance(settings.m_autoRWBalance ? 1 : 0); + response.getUdpSourceSettings()->setStereoInput(settings.m_stereoInput ? 1 : 0); + response.getUdpSourceSettings()->setRgbColor(settings.m_rgbColor); - if (response.getUdpSinkSettings()->getUdpAddress()) { - *response.getUdpSinkSettings()->getUdpAddress() = settings.m_udpAddress; + if (response.getUdpSourceSettings()->getUdpAddress()) { + *response.getUdpSourceSettings()->getUdpAddress() = settings.m_udpAddress; } else { - response.getUdpSinkSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + response.getUdpSourceSettings()->setUdpAddress(new QString(settings.m_udpAddress)); } - response.getUdpSinkSettings()->setUdpPort(settings.m_udpPort); + response.getUdpSourceSettings()->setUdpPort(settings.m_udpPort); - if (response.getUdpSinkSettings()->getTitle()) { - *response.getUdpSinkSettings()->getTitle() = settings.m_title; + if (response.getUdpSourceSettings()->getTitle()) { + *response.getUdpSourceSettings()->getTitle() = settings.m_title; } else { - response.getUdpSinkSettings()->setTitle(new QString(settings.m_title)); + response.getUdpSourceSettings()->setTitle(new QString(settings.m_title)); } } void UDPSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { - response.getUdpSinkReport()->setInputPowerDb(CalcDb::dbPower(getInMagSq())); - response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); - response.getUdpSinkReport()->setSquelch(m_squelchOpen ? 1 : 0); - response.getUdpSinkReport()->setBufferGauge(getBufferGauge()); - response.getUdpSinkReport()->setChannelSampleRate(m_outputSampleRate); + response.getUdpSourceReport()->setInputPowerDb(CalcDb::dbPower(getInMagSq())); + response.getUdpSourceReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getUdpSourceReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getUdpSourceReport()->setBufferGauge(getBufferGauge()); + response.getUdpSourceReport()->setChannelSampleRate(m_outputSampleRate); } diff --git a/plugins/channeltx/udpsink/udpsource.h b/plugins/channeltx/udpsource/udpsource.h similarity index 100% rename from plugins/channeltx/udpsink/udpsource.h rename to plugins/channeltx/udpsource/udpsource.h diff --git a/plugins/channeltx/udpsink/udpsourcegui.cpp b/plugins/channeltx/udpsource/udpsourcegui.cpp similarity index 100% rename from plugins/channeltx/udpsink/udpsourcegui.cpp rename to plugins/channeltx/udpsource/udpsourcegui.cpp diff --git a/plugins/channeltx/udpsink/udpsourcegui.h b/plugins/channeltx/udpsource/udpsourcegui.h similarity index 100% rename from plugins/channeltx/udpsink/udpsourcegui.h rename to plugins/channeltx/udpsource/udpsourcegui.h diff --git a/plugins/channeltx/udpsink/udpsourcegui.ui b/plugins/channeltx/udpsource/udpsourcegui.ui similarity index 100% rename from plugins/channeltx/udpsink/udpsourcegui.ui rename to plugins/channeltx/udpsource/udpsourcegui.ui diff --git a/plugins/channeltx/udpsink/udpsourcemsg.cpp b/plugins/channeltx/udpsource/udpsourcemsg.cpp similarity index 100% rename from plugins/channeltx/udpsink/udpsourcemsg.cpp rename to plugins/channeltx/udpsource/udpsourcemsg.cpp diff --git a/plugins/channeltx/udpsink/udpsourcemsg.h b/plugins/channeltx/udpsource/udpsourcemsg.h similarity index 100% rename from plugins/channeltx/udpsink/udpsourcemsg.h rename to plugins/channeltx/udpsource/udpsourcemsg.h diff --git a/plugins/channeltx/udpsink/udpsourceplugin.cpp b/plugins/channeltx/udpsource/udpsourceplugin.cpp similarity index 100% rename from plugins/channeltx/udpsink/udpsourceplugin.cpp rename to plugins/channeltx/udpsource/udpsourceplugin.cpp diff --git a/plugins/channeltx/udpsink/udpsourceplugin.h b/plugins/channeltx/udpsource/udpsourceplugin.h similarity index 100% rename from plugins/channeltx/udpsink/udpsourceplugin.h rename to plugins/channeltx/udpsource/udpsourceplugin.h diff --git a/plugins/channeltx/udpsink/udpsourcesettings.cpp b/plugins/channeltx/udpsource/udpsourcesettings.cpp similarity index 100% rename from plugins/channeltx/udpsink/udpsourcesettings.cpp rename to plugins/channeltx/udpsource/udpsourcesettings.cpp diff --git a/plugins/channeltx/udpsink/udpsourcesettings.h b/plugins/channeltx/udpsource/udpsourcesettings.h similarity index 100% rename from plugins/channeltx/udpsink/udpsourcesettings.h rename to plugins/channeltx/udpsource/udpsourcesettings.h diff --git a/plugins/channeltx/udpsink/udpsourceudphandler.cpp b/plugins/channeltx/udpsource/udpsourceudphandler.cpp similarity index 90% rename from plugins/channeltx/udpsink/udpsourceudphandler.cpp rename to plugins/channeltx/udpsource/udpsourceudphandler.cpp index 5422002ef..a341d8f9c 100644 --- a/plugins/channeltx/udpsink/udpsourceudphandler.cpp +++ b/plugins/channeltx/udpsource/udpsourceudphandler.cpp @@ -54,7 +54,7 @@ UDPSourceUDPHandler::~UDPSourceUDPHandler() void UDPSourceUDPHandler::start() { - qDebug("UDPSinkUDPHandler::start"); + qDebug("UDPSourceUDPHandler::start"); if (!m_dataSocket) { @@ -66,13 +66,13 @@ void UDPSourceUDPHandler::start() if (m_dataSocket->bind(m_dataAddress, m_dataPort)) { - qDebug("UDPSinkUDPHandler::start: bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); + qDebug("UDPSourceUDPHandler::start: bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead())); // , Qt::QueuedConnection gets stuck since Qt 5.8.0 m_dataConnected = true; } else { - qWarning("UDPSinkUDPHandler::start: cannot bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); + qWarning("UDPSourceUDPHandler::start: cannot bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); m_dataConnected = false; } } @@ -80,7 +80,7 @@ void UDPSourceUDPHandler::start() void UDPSourceUDPHandler::stop() { - qDebug("UDPSinkUDPHandler::stop"); + qDebug("UDPSourceUDPHandler::stop"); if (m_dataConnected) { @@ -104,7 +104,7 @@ void UDPSourceUDPHandler::dataReadyRead() if (bytesRead < 0) { - qWarning("UDPSinkUDPHandler::dataReadyRead: UDP read error"); + qWarning("UDPSourceUDPHandler::dataReadyRead: UDP read error"); } else { @@ -199,7 +199,7 @@ void UDPSourceUDPHandler::advanceReadPointer(int nbBytes) m_rwDelta = m_writeFrameIndex; // raw R/W delta estimate int nbUDPFrames2 = m_nbUDPFrames/2; float d = (m_rwDelta - nbUDPFrames2)/(float) m_nbUDPFrames; - //qDebug("UDPSinkUDPHandler::advanceReadPointer: w: %02d d: %f", m_writeIndex, d); + //qDebug("UDPSourceUDPHandler::advanceReadPointer: w: %02d d: %f", m_writeIndex, d); if ((d < -0.45) || (d > 0.45)) { @@ -231,12 +231,12 @@ void UDPSourceUDPHandler::configureUDPLink(const QString& address, quint16 port) void UDPSourceUDPHandler::applyUDPLink(const QString& address, quint16 port) { - qDebug("UDPSinkUDPHandler::configureUDPLink: %s:%d", address.toStdString().c_str(), port); + qDebug("UDPSourceUDPHandler::configureUDPLink: %s:%d", address.toStdString().c_str(), port); bool addressOK = m_dataAddress.setAddress(address); if (!addressOK) { - qWarning("UDPSinkUDPHandler::configureUDPLink: invalid address %s. Set to localhost.", address.toStdString().c_str()); + qWarning("UDPSourceUDPHandler::configureUDPLink: invalid address %s. Set to localhost.", address.toStdString().c_str()); m_dataAddress = QHostAddress::LocalHost; } @@ -257,7 +257,7 @@ void UDPSourceUDPHandler::resetReadIndex() void UDPSourceUDPHandler::resizeBuffer(float sampleRate) { int halfNbFrames = std::max((sampleRate / 375.0), (m_minNbUDPFrames / 2.0)); - qDebug("UDPSinkUDPHandler::resizeBuffer: nb_frames: %d", 2*halfNbFrames); + qDebug("UDPSourceUDPHandler::resizeBuffer: nb_frames: %d", 2*halfNbFrames); if (2*halfNbFrames > m_nbAllocatedUDPFrames) { diff --git a/plugins/channeltx/udpsink/udpsourceudphandler.h b/plugins/channeltx/udpsource/udpsourceudphandler.h similarity index 100% rename from plugins/channeltx/udpsink/udpsourceudphandler.h rename to plugins/channeltx/udpsource/udpsourceudphandler.h diff --git a/pluginssrv/channeltx/CMakeLists.txt b/pluginssrv/channeltx/CMakeLists.txt index 76c3291d2..b4541996d 100644 --- a/pluginssrv/channeltx/CMakeLists.txt +++ b/pluginssrv/channeltx/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(modam) add_subdirectory(modnfm) add_subdirectory(modssb) add_subdirectory(modwfm) -add_subdirectory(udpsink) +add_subdirectory(udpsource) find_package(CM256cc) if(CM256CC_FOUND) diff --git a/pluginssrv/channeltx/udpsink/CMakeLists.txt b/pluginssrv/channeltx/udpsource/CMakeLists.txt similarity index 94% rename from pluginssrv/channeltx/udpsink/CMakeLists.txt rename to pluginssrv/channeltx/udpsource/CMakeLists.txt index be2cde116..767104379 100644 --- a/pluginssrv/channeltx/udpsink/CMakeLists.txt +++ b/pluginssrv/channeltx/udpsource/CMakeLists.txt @@ -1,7 +1,7 @@ project(udpsource) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -set(PLUGIN_PREFIX "../../../plugins/channeltx/udpsink") +set(PLUGIN_PREFIX "../../../plugins/channeltx/udpsource") set(udpsource_SOURCES ${PLUGIN_PREFIX}/udpsource.cpp diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 45afba2ea..a64c82c95 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -30,7 +30,7 @@ webapi/doc/swagger/include/SSBMod.yaml webapi/doc/swagger/include/Structs.yaml webapi/doc/swagger/include/TestSource.yaml - webapi/doc/swagger/include/UDPSink.yaml + webapi/doc/swagger/include/UDPSource.yaml webapi/doc/swagger/include/UDPSrc.yaml webapi/doc/swagger/include/WFMDemod.yaml webapi/doc/swagger/include/WFMMod.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index c18e79645..5fd2008f2 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1422,8 +1422,8 @@ margin-bottom: 20px; "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" }, - "UDPSinkReport" : { - "$ref" : "#/definitions/UDPSinkReport" + "UDPSourceReport" : { + "$ref" : "#/definitions/UDPSourceReport" }, "UDPSrcReport" : { "$ref" : "#/definitions/UDPSrcReport" @@ -1482,8 +1482,8 @@ margin-bottom: 20px; "SSBDemodSettings" : { "$ref" : "#/definitions/SSBDemodSettings" }, - "UDPSinkSettings" : { - "$ref" : "#/definitions/UDPSinkSettings" + "UDPSourceSettings" : { + "$ref" : "#/definitions/UDPSourceSettings" }, "UDPSrcSettings" : { "$ref" : "#/definitions/UDPSrcSettings" @@ -3713,7 +3713,7 @@ margin-bottom: 20px; }, "description" : "TestSource" }; - defs.UDPSinkReport = { + defs.UDPSourceReport = { "properties" : { "channelPowerDB" : { "type" : "number", @@ -3737,9 +3737,9 @@ margin-bottom: 20px; "type" : "integer" } }, - "description" : "UDPSink" + "description" : "UDPSource" }; - defs.UDPSinkSettings = { + defs.UDPSourceSettings = { "properties" : { "sampleFormat" : { "type" : "integer" @@ -3809,7 +3809,7 @@ margin-bottom: 20px; "type" : "string" } }, - "description" : "UDPSink" + "description" : "UDPSource" }; defs.UDPSrcReport = { "properties" : { @@ -23066,7 +23066,7 @@ except ApiException as e:
    - Generated 2018-09-11T14:47:33.212+02:00 + Generated 2018-09-11T22:44:27.082+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml b/sdrbase/resources/webapi/doc/swagger/include/UDPSource.yaml similarity index 94% rename from sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml rename to sdrbase/resources/webapi/doc/swagger/include/UDPSource.yaml index 16da1401d..46e8b680b 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/UDPSource.yaml @@ -1,5 +1,5 @@ -UDPSinkSettings: - description: UDPSink +UDPSourceSettings: + description: UDPSource properties: sampleFormat: type: integer @@ -50,8 +50,8 @@ UDPSinkSettings: title: type: string -UDPSinkReport: - description: UDPSink +UDPSourceReport: + description: UDPSource properties: channelPowerDB: description: power transmitted in channel (dB) diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index a59d3def7..d659f35c1 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1872,8 +1872,8 @@ definitions: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: $ref: "/doc/swagger/include/SSBDemod.yaml#/SSBDemodSettings" - UDPSinkSettings: - $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkSettings" + UDPSourceSettings: + $ref: "/doc/swagger/include/UDPSource.yaml#/UDPSourceSettings" UDPSrcSettings: $ref: "/doc/swagger/include/UDPSrc.yaml#/UDPSrcSettings" WFMDemodSettings: @@ -1911,8 +1911,8 @@ definitions: $ref: "/doc/swagger/include/DaemonSource.yaml#/DaemonSourceReport" SSBModReport: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" - UDPSinkReport: - $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkReport" + UDPSourceReport: + $ref: "/doc/swagger/include/UDPSource.yaml#/UDPSourceReport" UDPSrcReport: $ref: "/doc/swagger/include/UDPSrc.yaml#/UDPSrcReport" WFMDemodReport: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 82edc9cbf..6e38970c1 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2212,10 +2212,10 @@ bool WebAPIRequestMapper::validateChannelSettings( { if (channelSettings.getTx() != 0) { - QJsonObject udpSinkSettingsJsonObject = jsonObject["UDPSinkSettings"].toObject(); - channelSettingsKeys = udpSinkSettingsJsonObject.keys(); - channelSettings.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); - channelSettings.getUdpSinkSettings()->fromJsonObject(udpSinkSettingsJsonObject); + QJsonObject udpSourceSettingsJsonObject = jsonObject["UDPSourceSettings"].toObject(); + channelSettingsKeys = udpSourceSettingsJsonObject.keys(); + channelSettings.setUdpSourceSettings(new SWGSDRangel::SWGUDPSourceSettings()); + channelSettings.getUdpSourceSettings()->fromJsonObject(udpSourceSettingsJsonObject); return true; } else { @@ -2415,7 +2415,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setDaemonSourceSettings(0); channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); - channelSettings.setUdpSinkSettings(0); + channelSettings.setUdpSourceSettings(0); channelSettings.setUdpSrcSettings(0); channelSettings.setWfmDemodSettings(0); channelSettings.setWfmModSettings(0); @@ -2435,7 +2435,7 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan channelReport.setDaemonSourceReport(0); channelReport.setSsbDemodReport(0); channelReport.setSsbModReport(0); - channelReport.setUdpSinkReport(0); + channelReport.setUdpSourceReport(0); channelReport.setUdpSrcReport(0); channelReport.setWfmDemodReport(0); channelReport.setWfmModReport(0); diff --git a/swagger/sdrangel/api/swagger/include/UDPSource.yaml b/swagger/sdrangel/api/swagger/include/UDPSource.yaml index 16da1401d..46e8b680b 100644 --- a/swagger/sdrangel/api/swagger/include/UDPSource.yaml +++ b/swagger/sdrangel/api/swagger/include/UDPSource.yaml @@ -1,5 +1,5 @@ -UDPSinkSettings: - description: UDPSink +UDPSourceSettings: + description: UDPSource properties: sampleFormat: type: integer @@ -50,8 +50,8 @@ UDPSinkSettings: title: type: string -UDPSinkReport: - description: UDPSink +UDPSourceReport: + description: UDPSource properties: channelPowerDB: description: power transmitted in channel (dB) diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index eb8269926..a126ae966 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1872,8 +1872,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: $ref: "http://localhost:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodSettings" - UDPSinkSettings: - $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkSettings" + UDPSourceSettings: + $ref: "http://localhost:8081/api/swagger/include/UDPSource.yaml#/UDPSourceSettings" UDPSrcSettings: $ref: "http://localhost:8081/api/swagger/include/UDPSrc.yaml#/UDPSrcSettings" WFMDemodSettings: @@ -1911,8 +1911,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/DaemonSource.yaml#/DaemonSourceReport" SSBModReport: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" - UDPSinkReport: - $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkReport" + UDPSourceReport: + $ref: "http://localhost:8081/api/swagger/include/UDPSource.yaml#/UDPSourceReport" UDPSrcReport: $ref: "http://localhost:8081/api/swagger/include/UDPSrc.yaml#/UDPSrcReport" WFMDemodReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index c18e79645..5fd2008f2 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1422,8 +1422,8 @@ margin-bottom: 20px; "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" }, - "UDPSinkReport" : { - "$ref" : "#/definitions/UDPSinkReport" + "UDPSourceReport" : { + "$ref" : "#/definitions/UDPSourceReport" }, "UDPSrcReport" : { "$ref" : "#/definitions/UDPSrcReport" @@ -1482,8 +1482,8 @@ margin-bottom: 20px; "SSBDemodSettings" : { "$ref" : "#/definitions/SSBDemodSettings" }, - "UDPSinkSettings" : { - "$ref" : "#/definitions/UDPSinkSettings" + "UDPSourceSettings" : { + "$ref" : "#/definitions/UDPSourceSettings" }, "UDPSrcSettings" : { "$ref" : "#/definitions/UDPSrcSettings" @@ -3713,7 +3713,7 @@ margin-bottom: 20px; }, "description" : "TestSource" }; - defs.UDPSinkReport = { + defs.UDPSourceReport = { "properties" : { "channelPowerDB" : { "type" : "number", @@ -3737,9 +3737,9 @@ margin-bottom: 20px; "type" : "integer" } }, - "description" : "UDPSink" + "description" : "UDPSource" }; - defs.UDPSinkSettings = { + defs.UDPSourceSettings = { "properties" : { "sampleFormat" : { "type" : "integer" @@ -3809,7 +3809,7 @@ margin-bottom: 20px; "type" : "string" } }, - "description" : "UDPSink" + "description" : "UDPSource" }; defs.UDPSrcReport = { "properties" : { @@ -23066,7 +23066,7 @@ except ApiException as e:
    - Generated 2018-09-11T14:47:33.212+02:00 + Generated 2018-09-11T22:44:27.082+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 451ed988b..8bf3a9d8a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -52,8 +52,8 @@ SWGChannelReport::SWGChannelReport() { m_daemon_source_report_isSet = false; ssb_mod_report = nullptr; m_ssb_mod_report_isSet = false; - udp_sink_report = nullptr; - m_udp_sink_report_isSet = false; + udp_source_report = nullptr; + m_udp_source_report_isSet = false; udp_src_report = nullptr; m_udp_src_report_isSet = false; wfm_demod_report = nullptr; @@ -92,8 +92,8 @@ SWGChannelReport::init() { m_daemon_source_report_isSet = false; ssb_mod_report = new SWGSSBModReport(); m_ssb_mod_report_isSet = false; - udp_sink_report = new SWGUDPSinkReport(); - m_udp_sink_report_isSet = false; + udp_source_report = new SWGUDPSourceReport(); + m_udp_source_report_isSet = false; udp_src_report = new SWGUDPSrcReport(); m_udp_src_report_isSet = false; wfm_demod_report = new SWGWFMDemodReport(); @@ -138,8 +138,8 @@ SWGChannelReport::cleanup() { if(ssb_mod_report != nullptr) { delete ssb_mod_report; } - if(udp_sink_report != nullptr) { - delete udp_sink_report; + if(udp_source_report != nullptr) { + delete udp_source_report; } if(udp_src_report != nullptr) { delete udp_src_report; @@ -187,7 +187,7 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport"); - ::SWGSDRangel::setValue(&udp_sink_report, pJson["UDPSinkReport"], "SWGUDPSinkReport", "SWGUDPSinkReport"); + ::SWGSDRangel::setValue(&udp_source_report, pJson["UDPSourceReport"], "SWGUDPSourceReport", "SWGUDPSourceReport"); ::SWGSDRangel::setValue(&udp_src_report, pJson["UDPSrcReport"], "SWGUDPSrcReport", "SWGUDPSrcReport"); @@ -247,8 +247,8 @@ SWGChannelReport::asJsonObject() { if((ssb_mod_report != nullptr) && (ssb_mod_report->isSet())){ toJsonValue(QString("SSBModReport"), ssb_mod_report, obj, QString("SWGSSBModReport")); } - if((udp_sink_report != nullptr) && (udp_sink_report->isSet())){ - toJsonValue(QString("UDPSinkReport"), udp_sink_report, obj, QString("SWGUDPSinkReport")); + if((udp_source_report != nullptr) && (udp_source_report->isSet())){ + toJsonValue(QString("UDPSourceReport"), udp_source_report, obj, QString("SWGUDPSourceReport")); } if((udp_src_report != nullptr) && (udp_src_report->isSet())){ toJsonValue(QString("UDPSrcReport"), udp_src_report, obj, QString("SWGUDPSrcReport")); @@ -383,14 +383,14 @@ SWGChannelReport::setSsbModReport(SWGSSBModReport* ssb_mod_report) { this->m_ssb_mod_report_isSet = true; } -SWGUDPSinkReport* -SWGChannelReport::getUdpSinkReport() { - return udp_sink_report; +SWGUDPSourceReport* +SWGChannelReport::getUdpSourceReport() { + return udp_source_report; } void -SWGChannelReport::setUdpSinkReport(SWGUDPSinkReport* udp_sink_report) { - this->udp_sink_report = udp_sink_report; - this->m_udp_sink_report_isSet = true; +SWGChannelReport::setUdpSourceReport(SWGUDPSourceReport* udp_source_report) { + this->udp_source_report = udp_source_report; + this->m_udp_source_report_isSet = true; } SWGUDPSrcReport* @@ -440,7 +440,7 @@ SWGChannelReport::isSet(){ if(ssb_demod_report != nullptr && ssb_demod_report->isSet()){ isObjectUpdated = true; break;} if(daemon_source_report != nullptr && daemon_source_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} - if(udp_sink_report != nullptr && udp_sink_report->isSet()){ isObjectUpdated = true; break;} + if(udp_source_report != nullptr && udp_source_report->isSet()){ isObjectUpdated = true; break;} if(udp_src_report != nullptr && udp_src_report->isSet()){ isObjectUpdated = true; break;} if(wfm_demod_report != nullptr && wfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_report != nullptr && wfm_mod_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 6537329dc..a4029c8b1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -32,7 +32,7 @@ #include "SWGNFMModReport.h" #include "SWGSSBDemodReport.h" #include "SWGSSBModReport.h" -#include "SWGUDPSinkReport.h" +#include "SWGUDPSourceReport.h" #include "SWGUDPSrcReport.h" #include "SWGWFMDemodReport.h" #include "SWGWFMModReport.h" @@ -92,8 +92,8 @@ public: SWGSSBModReport* getSsbModReport(); void setSsbModReport(SWGSSBModReport* ssb_mod_report); - SWGUDPSinkReport* getUdpSinkReport(); - void setUdpSinkReport(SWGUDPSinkReport* udp_sink_report); + SWGUDPSourceReport* getUdpSourceReport(); + void setUdpSourceReport(SWGUDPSourceReport* udp_source_report); SWGUDPSrcReport* getUdpSrcReport(); void setUdpSrcReport(SWGUDPSrcReport* udp_src_report); @@ -144,8 +144,8 @@ private: SWGSSBModReport* ssb_mod_report; bool m_ssb_mod_report_isSet; - SWGUDPSinkReport* udp_sink_report; - bool m_udp_sink_report_isSet; + SWGUDPSourceReport* udp_source_report; + bool m_udp_source_report_isSet; SWGUDPSrcReport* udp_src_report; bool m_udp_src_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index b9a8f0e4f..e360c0fc5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -54,8 +54,8 @@ SWGChannelSettings::SWGChannelSettings() { m_ssb_mod_settings_isSet = false; ssb_demod_settings = nullptr; m_ssb_demod_settings_isSet = false; - udp_sink_settings = nullptr; - m_udp_sink_settings_isSet = false; + udp_source_settings = nullptr; + m_udp_source_settings_isSet = false; udp_src_settings = nullptr; m_udp_src_settings_isSet = false; wfm_demod_settings = nullptr; @@ -96,8 +96,8 @@ SWGChannelSettings::init() { m_ssb_mod_settings_isSet = false; ssb_demod_settings = new SWGSSBDemodSettings(); m_ssb_demod_settings_isSet = false; - udp_sink_settings = new SWGUDPSinkSettings(); - m_udp_sink_settings_isSet = false; + udp_source_settings = new SWGUDPSourceSettings(); + m_udp_source_settings_isSet = false; udp_src_settings = new SWGUDPSrcSettings(); m_udp_src_settings_isSet = false; wfm_demod_settings = new SWGWFMDemodSettings(); @@ -145,8 +145,8 @@ SWGChannelSettings::cleanup() { if(ssb_demod_settings != nullptr) { delete ssb_demod_settings; } - if(udp_sink_settings != nullptr) { - delete udp_sink_settings; + if(udp_source_settings != nullptr) { + delete udp_source_settings; } if(udp_src_settings != nullptr) { delete udp_src_settings; @@ -196,7 +196,7 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ssb_demod_settings, pJson["SSBDemodSettings"], "SWGSSBDemodSettings", "SWGSSBDemodSettings"); - ::SWGSDRangel::setValue(&udp_sink_settings, pJson["UDPSinkSettings"], "SWGUDPSinkSettings", "SWGUDPSinkSettings"); + ::SWGSDRangel::setValue(&udp_source_settings, pJson["UDPSourceSettings"], "SWGUDPSourceSettings", "SWGUDPSourceSettings"); ::SWGSDRangel::setValue(&udp_src_settings, pJson["UDPSrcSettings"], "SWGUDPSrcSettings", "SWGUDPSrcSettings"); @@ -259,8 +259,8 @@ SWGChannelSettings::asJsonObject() { if((ssb_demod_settings != nullptr) && (ssb_demod_settings->isSet())){ toJsonValue(QString("SSBDemodSettings"), ssb_demod_settings, obj, QString("SWGSSBDemodSettings")); } - if((udp_sink_settings != nullptr) && (udp_sink_settings->isSet())){ - toJsonValue(QString("UDPSinkSettings"), udp_sink_settings, obj, QString("SWGUDPSinkSettings")); + if((udp_source_settings != nullptr) && (udp_source_settings->isSet())){ + toJsonValue(QString("UDPSourceSettings"), udp_source_settings, obj, QString("SWGUDPSourceSettings")); } if((udp_src_settings != nullptr) && (udp_src_settings->isSet())){ toJsonValue(QString("UDPSrcSettings"), udp_src_settings, obj, QString("SWGUDPSrcSettings")); @@ -405,14 +405,14 @@ SWGChannelSettings::setSsbDemodSettings(SWGSSBDemodSettings* ssb_demod_settings) this->m_ssb_demod_settings_isSet = true; } -SWGUDPSinkSettings* -SWGChannelSettings::getUdpSinkSettings() { - return udp_sink_settings; +SWGUDPSourceSettings* +SWGChannelSettings::getUdpSourceSettings() { + return udp_source_settings; } void -SWGChannelSettings::setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings) { - this->udp_sink_settings = udp_sink_settings; - this->m_udp_sink_settings_isSet = true; +SWGChannelSettings::setUdpSourceSettings(SWGUDPSourceSettings* udp_source_settings) { + this->udp_source_settings = udp_source_settings; + this->m_udp_source_settings_isSet = true; } SWGUDPSrcSettings* @@ -463,7 +463,7 @@ SWGChannelSettings::isSet(){ if(daemon_source_settings != nullptr && daemon_source_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_demod_settings != nullptr && ssb_demod_settings->isSet()){ isObjectUpdated = true; break;} - if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} + if(udp_source_settings != nullptr && udp_source_settings->isSet()){ isObjectUpdated = true; break;} if(udp_src_settings != nullptr && udp_src_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_demod_settings != nullptr && wfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_settings != nullptr && wfm_mod_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 6d8a43592..ee2e3e92c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -33,7 +33,7 @@ #include "SWGNFMModSettings.h" #include "SWGSSBDemodSettings.h" #include "SWGSSBModSettings.h" -#include "SWGUDPSinkSettings.h" +#include "SWGUDPSourceSettings.h" #include "SWGUDPSrcSettings.h" #include "SWGWFMDemodSettings.h" #include "SWGWFMModSettings.h" @@ -96,8 +96,8 @@ public: SWGSSBDemodSettings* getSsbDemodSettings(); void setSsbDemodSettings(SWGSSBDemodSettings* ssb_demod_settings); - SWGUDPSinkSettings* getUdpSinkSettings(); - void setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings); + SWGUDPSourceSettings* getUdpSourceSettings(); + void setUdpSourceSettings(SWGUDPSourceSettings* udp_source_settings); SWGUDPSrcSettings* getUdpSrcSettings(); void setUdpSrcSettings(SWGUDPSrcSettings* udp_src_settings); @@ -151,8 +151,8 @@ private: SWGSSBDemodSettings* ssb_demod_settings; bool m_ssb_demod_settings_isSet; - SWGUDPSinkSettings* udp_sink_settings; - bool m_udp_sink_settings_isSet; + SWGUDPSourceSettings* udp_source_settings; + bool m_udp_source_settings_isSet; SWGUDPSrcSettings* udp_src_settings; bool m_udp_src_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index b5fc18c1c..6adb9b75a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -105,8 +105,8 @@ #include "SWGSamplingDevice.h" #include "SWGSuccessResponse.h" #include "SWGTestSourceSettings.h" -#include "SWGUDPSinkReport.h" -#include "SWGUDPSinkSettings.h" +#include "SWGUDPSourceReport.h" +#include "SWGUDPSourceSettings.h" #include "SWGUDPSrcReport.h" #include "SWGUDPSrcSettings.h" #include "SWGWFMDemodReport.h" @@ -390,11 +390,11 @@ namespace SWGSDRangel { if(QString("SWGTestSourceSettings").compare(type) == 0) { return new SWGTestSourceSettings(); } - if(QString("SWGUDPSinkReport").compare(type) == 0) { - return new SWGUDPSinkReport(); + if(QString("SWGUDPSourceReport").compare(type) == 0) { + return new SWGUDPSourceReport(); } - if(QString("SWGUDPSinkSettings").compare(type) == 0) { - return new SWGUDPSinkSettings(); + if(QString("SWGUDPSourceSettings").compare(type) == 0) { + return new SWGUDPSourceSettings(); } if(QString("SWGUDPSrcReport").compare(type) == 0) { return new SWGUDPSrcReport(); diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSourceReport.cpp similarity index 83% rename from swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp rename to swagger/sdrangel/code/qt5/client/SWGUDPSourceReport.cpp index 89fd688b5..9cdeca802 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSourceReport.cpp @@ -11,7 +11,7 @@ */ -#include "SWGUDPSinkReport.h" +#include "SWGUDPSourceReport.h" #include "SWGHelpers.h" @@ -22,12 +22,12 @@ namespace SWGSDRangel { -SWGUDPSinkReport::SWGUDPSinkReport(QString* json) { +SWGUDPSourceReport::SWGUDPSourceReport(QString* json) { init(); this->fromJson(*json); } -SWGUDPSinkReport::SWGUDPSinkReport() { +SWGUDPSourceReport::SWGUDPSourceReport() { channel_power_db = 0.0f; m_channel_power_db_isSet = false; input_power_db = 0.0f; @@ -40,12 +40,12 @@ SWGUDPSinkReport::SWGUDPSinkReport() { m_channel_sample_rate_isSet = false; } -SWGUDPSinkReport::~SWGUDPSinkReport() { +SWGUDPSourceReport::~SWGUDPSourceReport() { this->cleanup(); } void -SWGUDPSinkReport::init() { +SWGUDPSourceReport::init() { channel_power_db = 0.0f; m_channel_power_db_isSet = false; input_power_db = 0.0f; @@ -59,7 +59,7 @@ SWGUDPSinkReport::init() { } void -SWGUDPSinkReport::cleanup() { +SWGUDPSourceReport::cleanup() { @@ -67,8 +67,8 @@ SWGUDPSinkReport::cleanup() { } -SWGUDPSinkReport* -SWGUDPSinkReport::fromJson(QString &json) { +SWGUDPSourceReport* +SWGUDPSourceReport::fromJson(QString &json) { QByteArray array (json.toStdString().c_str()); QJsonDocument doc = QJsonDocument::fromJson(array); QJsonObject jsonObject = doc.object(); @@ -77,7 +77,7 @@ SWGUDPSinkReport::fromJson(QString &json) { } void -SWGUDPSinkReport::fromJsonObject(QJsonObject &pJson) { +SWGUDPSourceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); ::SWGSDRangel::setValue(&input_power_db, pJson["inputPowerDB"], "float", ""); @@ -91,7 +91,7 @@ SWGUDPSinkReport::fromJsonObject(QJsonObject &pJson) { } QString -SWGUDPSinkReport::asJson () +SWGUDPSourceReport::asJson () { QJsonObject* obj = this->asJsonObject(); @@ -102,7 +102,7 @@ SWGUDPSinkReport::asJson () } QJsonObject* -SWGUDPSinkReport::asJsonObject() { +SWGUDPSourceReport::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(m_channel_power_db_isSet){ obj->insert("channelPowerDB", QJsonValue(channel_power_db)); @@ -124,58 +124,58 @@ SWGUDPSinkReport::asJsonObject() { } float -SWGUDPSinkReport::getChannelPowerDb() { +SWGUDPSourceReport::getChannelPowerDb() { return channel_power_db; } void -SWGUDPSinkReport::setChannelPowerDb(float channel_power_db) { +SWGUDPSourceReport::setChannelPowerDb(float channel_power_db) { this->channel_power_db = channel_power_db; this->m_channel_power_db_isSet = true; } float -SWGUDPSinkReport::getInputPowerDb() { +SWGUDPSourceReport::getInputPowerDb() { return input_power_db; } void -SWGUDPSinkReport::setInputPowerDb(float input_power_db) { +SWGUDPSourceReport::setInputPowerDb(float input_power_db) { this->input_power_db = input_power_db; this->m_input_power_db_isSet = true; } qint32 -SWGUDPSinkReport::getSquelch() { +SWGUDPSourceReport::getSquelch() { return squelch; } void -SWGUDPSinkReport::setSquelch(qint32 squelch) { +SWGUDPSourceReport::setSquelch(qint32 squelch) { this->squelch = squelch; this->m_squelch_isSet = true; } qint32 -SWGUDPSinkReport::getBufferGauge() { +SWGUDPSourceReport::getBufferGauge() { return buffer_gauge; } void -SWGUDPSinkReport::setBufferGauge(qint32 buffer_gauge) { +SWGUDPSourceReport::setBufferGauge(qint32 buffer_gauge) { this->buffer_gauge = buffer_gauge; this->m_buffer_gauge_isSet = true; } qint32 -SWGUDPSinkReport::getChannelSampleRate() { +SWGUDPSourceReport::getChannelSampleRate() { return channel_sample_rate; } void -SWGUDPSinkReport::setChannelSampleRate(qint32 channel_sample_rate) { +SWGUDPSourceReport::setChannelSampleRate(qint32 channel_sample_rate) { this->channel_sample_rate = channel_sample_rate; this->m_channel_sample_rate_isSet = true; } bool -SWGUDPSinkReport::isSet(){ +SWGUDPSourceReport::isSet(){ bool isObjectUpdated = false; do{ if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h b/swagger/sdrangel/code/qt5/client/SWGUDPSourceReport.h similarity index 88% rename from swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h rename to swagger/sdrangel/code/qt5/client/SWGUDPSourceReport.h index 535c38ef1..45919d544 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSourceReport.h @@ -11,13 +11,13 @@ */ /* - * SWGUDPSinkReport.h + * SWGUDPSourceReport.h * - * UDPSink + * UDPSource */ -#ifndef SWGUDPSinkReport_H_ -#define SWGUDPSinkReport_H_ +#ifndef SWGUDPSourceReport_H_ +#define SWGUDPSourceReport_H_ #include @@ -28,18 +28,18 @@ namespace SWGSDRangel { -class SWG_API SWGUDPSinkReport: public SWGObject { +class SWG_API SWGUDPSourceReport: public SWGObject { public: - SWGUDPSinkReport(); - SWGUDPSinkReport(QString* json); - virtual ~SWGUDPSinkReport(); + SWGUDPSourceReport(); + SWGUDPSourceReport(QString* json); + virtual ~SWGUDPSourceReport(); void init(); void cleanup(); virtual QString asJson () override; virtual QJsonObject* asJsonObject() override; virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGUDPSinkReport* fromJson(QString &jsonString) override; + virtual SWGUDPSourceReport* fromJson(QString &jsonString) override; float getChannelPowerDb(); void setChannelPowerDb(float channel_power_db); @@ -79,4 +79,4 @@ private: } -#endif /* SWGUDPSinkReport_H_ */ +#endif /* SWGUDPSourceReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSourceSettings.cpp similarity index 83% rename from swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp rename to swagger/sdrangel/code/qt5/client/SWGUDPSourceSettings.cpp index b6af36abc..bd7a26122 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSourceSettings.cpp @@ -11,7 +11,7 @@ */ -#include "SWGUDPSinkSettings.h" +#include "SWGUDPSourceSettings.h" #include "SWGHelpers.h" @@ -22,12 +22,12 @@ namespace SWGSDRangel { -SWGUDPSinkSettings::SWGUDPSinkSettings(QString* json) { +SWGUDPSourceSettings::SWGUDPSourceSettings(QString* json) { init(); this->fromJson(*json); } -SWGUDPSinkSettings::SWGUDPSinkSettings() { +SWGUDPSourceSettings::SWGUDPSourceSettings() { sample_format = 0; m_sample_format_isSet = false; input_sample_rate = 0.0f; @@ -68,12 +68,12 @@ SWGUDPSinkSettings::SWGUDPSinkSettings() { m_title_isSet = false; } -SWGUDPSinkSettings::~SWGUDPSinkSettings() { +SWGUDPSourceSettings::~SWGUDPSourceSettings() { this->cleanup(); } void -SWGUDPSinkSettings::init() { +SWGUDPSourceSettings::init() { sample_format = 0; m_sample_format_isSet = false; input_sample_rate = 0.0f; @@ -115,7 +115,7 @@ SWGUDPSinkSettings::init() { } void -SWGUDPSinkSettings::cleanup() { +SWGUDPSourceSettings::cleanup() { @@ -141,8 +141,8 @@ SWGUDPSinkSettings::cleanup() { } } -SWGUDPSinkSettings* -SWGUDPSinkSettings::fromJson(QString &json) { +SWGUDPSourceSettings* +SWGUDPSourceSettings::fromJson(QString &json) { QByteArray array (json.toStdString().c_str()); QJsonDocument doc = QJsonDocument::fromJson(array); QJsonObject jsonObject = doc.object(); @@ -151,7 +151,7 @@ SWGUDPSinkSettings::fromJson(QString &json) { } void -SWGUDPSinkSettings::fromJsonObject(QJsonObject &pJson) { +SWGUDPSourceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&sample_format, pJson["sampleFormat"], "qint32", ""); ::SWGSDRangel::setValue(&input_sample_rate, pJson["inputSampleRate"], "float", ""); @@ -193,7 +193,7 @@ SWGUDPSinkSettings::fromJsonObject(QJsonObject &pJson) { } QString -SWGUDPSinkSettings::asJson () +SWGUDPSourceSettings::asJson () { QJsonObject* obj = this->asJsonObject(); @@ -204,7 +204,7 @@ SWGUDPSinkSettings::asJson () } QJsonObject* -SWGUDPSinkSettings::asJsonObject() { +SWGUDPSourceSettings::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(m_sample_format_isSet){ obj->insert("sampleFormat", QJsonValue(sample_format)); @@ -268,198 +268,198 @@ SWGUDPSinkSettings::asJsonObject() { } qint32 -SWGUDPSinkSettings::getSampleFormat() { +SWGUDPSourceSettings::getSampleFormat() { return sample_format; } void -SWGUDPSinkSettings::setSampleFormat(qint32 sample_format) { +SWGUDPSourceSettings::setSampleFormat(qint32 sample_format) { this->sample_format = sample_format; this->m_sample_format_isSet = true; } float -SWGUDPSinkSettings::getInputSampleRate() { +SWGUDPSourceSettings::getInputSampleRate() { return input_sample_rate; } void -SWGUDPSinkSettings::setInputSampleRate(float input_sample_rate) { +SWGUDPSourceSettings::setInputSampleRate(float input_sample_rate) { this->input_sample_rate = input_sample_rate; this->m_input_sample_rate_isSet = true; } qint64 -SWGUDPSinkSettings::getInputFrequencyOffset() { +SWGUDPSourceSettings::getInputFrequencyOffset() { return input_frequency_offset; } void -SWGUDPSinkSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { +SWGUDPSourceSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { this->input_frequency_offset = input_frequency_offset; this->m_input_frequency_offset_isSet = true; } float -SWGUDPSinkSettings::getRfBandwidth() { +SWGUDPSourceSettings::getRfBandwidth() { return rf_bandwidth; } void -SWGUDPSinkSettings::setRfBandwidth(float rf_bandwidth) { +SWGUDPSourceSettings::setRfBandwidth(float rf_bandwidth) { this->rf_bandwidth = rf_bandwidth; this->m_rf_bandwidth_isSet = true; } float -SWGUDPSinkSettings::getLowCutoff() { +SWGUDPSourceSettings::getLowCutoff() { return low_cutoff; } void -SWGUDPSinkSettings::setLowCutoff(float low_cutoff) { +SWGUDPSourceSettings::setLowCutoff(float low_cutoff) { this->low_cutoff = low_cutoff; this->m_low_cutoff_isSet = true; } qint32 -SWGUDPSinkSettings::getFmDeviation() { +SWGUDPSourceSettings::getFmDeviation() { return fm_deviation; } void -SWGUDPSinkSettings::setFmDeviation(qint32 fm_deviation) { +SWGUDPSourceSettings::setFmDeviation(qint32 fm_deviation) { this->fm_deviation = fm_deviation; this->m_fm_deviation_isSet = true; } float -SWGUDPSinkSettings::getAmModFactor() { +SWGUDPSourceSettings::getAmModFactor() { return am_mod_factor; } void -SWGUDPSinkSettings::setAmModFactor(float am_mod_factor) { +SWGUDPSourceSettings::setAmModFactor(float am_mod_factor) { this->am_mod_factor = am_mod_factor; this->m_am_mod_factor_isSet = true; } qint32 -SWGUDPSinkSettings::getChannelMute() { +SWGUDPSourceSettings::getChannelMute() { return channel_mute; } void -SWGUDPSinkSettings::setChannelMute(qint32 channel_mute) { +SWGUDPSourceSettings::setChannelMute(qint32 channel_mute) { this->channel_mute = channel_mute; this->m_channel_mute_isSet = true; } float -SWGUDPSinkSettings::getGainIn() { +SWGUDPSourceSettings::getGainIn() { return gain_in; } void -SWGUDPSinkSettings::setGainIn(float gain_in) { +SWGUDPSourceSettings::setGainIn(float gain_in) { this->gain_in = gain_in; this->m_gain_in_isSet = true; } float -SWGUDPSinkSettings::getGainOut() { +SWGUDPSourceSettings::getGainOut() { return gain_out; } void -SWGUDPSinkSettings::setGainOut(float gain_out) { +SWGUDPSourceSettings::setGainOut(float gain_out) { this->gain_out = gain_out; this->m_gain_out_isSet = true; } float -SWGUDPSinkSettings::getSquelch() { +SWGUDPSourceSettings::getSquelch() { return squelch; } void -SWGUDPSinkSettings::setSquelch(float squelch) { +SWGUDPSourceSettings::setSquelch(float squelch) { this->squelch = squelch; this->m_squelch_isSet = true; } float -SWGUDPSinkSettings::getSquelchGate() { +SWGUDPSourceSettings::getSquelchGate() { return squelch_gate; } void -SWGUDPSinkSettings::setSquelchGate(float squelch_gate) { +SWGUDPSourceSettings::setSquelchGate(float squelch_gate) { this->squelch_gate = squelch_gate; this->m_squelch_gate_isSet = true; } qint32 -SWGUDPSinkSettings::getSquelchEnabled() { +SWGUDPSourceSettings::getSquelchEnabled() { return squelch_enabled; } void -SWGUDPSinkSettings::setSquelchEnabled(qint32 squelch_enabled) { +SWGUDPSourceSettings::setSquelchEnabled(qint32 squelch_enabled) { this->squelch_enabled = squelch_enabled; this->m_squelch_enabled_isSet = true; } qint32 -SWGUDPSinkSettings::getAutoRwBalance() { +SWGUDPSourceSettings::getAutoRwBalance() { return auto_rw_balance; } void -SWGUDPSinkSettings::setAutoRwBalance(qint32 auto_rw_balance) { +SWGUDPSourceSettings::setAutoRwBalance(qint32 auto_rw_balance) { this->auto_rw_balance = auto_rw_balance; this->m_auto_rw_balance_isSet = true; } qint32 -SWGUDPSinkSettings::getStereoInput() { +SWGUDPSourceSettings::getStereoInput() { return stereo_input; } void -SWGUDPSinkSettings::setStereoInput(qint32 stereo_input) { +SWGUDPSourceSettings::setStereoInput(qint32 stereo_input) { this->stereo_input = stereo_input; this->m_stereo_input_isSet = true; } qint32 -SWGUDPSinkSettings::getRgbColor() { +SWGUDPSourceSettings::getRgbColor() { return rgb_color; } void -SWGUDPSinkSettings::setRgbColor(qint32 rgb_color) { +SWGUDPSourceSettings::setRgbColor(qint32 rgb_color) { this->rgb_color = rgb_color; this->m_rgb_color_isSet = true; } QString* -SWGUDPSinkSettings::getUdpAddress() { +SWGUDPSourceSettings::getUdpAddress() { return udp_address; } void -SWGUDPSinkSettings::setUdpAddress(QString* udp_address) { +SWGUDPSourceSettings::setUdpAddress(QString* udp_address) { this->udp_address = udp_address; this->m_udp_address_isSet = true; } qint32 -SWGUDPSinkSettings::getUdpPort() { +SWGUDPSourceSettings::getUdpPort() { return udp_port; } void -SWGUDPSinkSettings::setUdpPort(qint32 udp_port) { +SWGUDPSourceSettings::setUdpPort(qint32 udp_port) { this->udp_port = udp_port; this->m_udp_port_isSet = true; } QString* -SWGUDPSinkSettings::getTitle() { +SWGUDPSourceSettings::getTitle() { return title; } void -SWGUDPSinkSettings::setTitle(QString* title) { +SWGUDPSourceSettings::setTitle(QString* title) { this->title = title; this->m_title_isSet = true; } bool -SWGUDPSinkSettings::isSet(){ +SWGUDPSourceSettings::isSet(){ bool isObjectUpdated = false; do{ if(m_sample_format_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGUDPSourceSettings.h similarity index 92% rename from swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h rename to swagger/sdrangel/code/qt5/client/SWGUDPSourceSettings.h index 07cc67639..98e56a9e1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSourceSettings.h @@ -11,13 +11,13 @@ */ /* - * SWGUDPSinkSettings.h + * SWGUDPSourceSettings.h * - * UDPSink + * UDPSource */ -#ifndef SWGUDPSinkSettings_H_ -#define SWGUDPSinkSettings_H_ +#ifndef SWGUDPSourceSettings_H_ +#define SWGUDPSourceSettings_H_ #include @@ -29,18 +29,18 @@ namespace SWGSDRangel { -class SWG_API SWGUDPSinkSettings: public SWGObject { +class SWG_API SWGUDPSourceSettings: public SWGObject { public: - SWGUDPSinkSettings(); - SWGUDPSinkSettings(QString* json); - virtual ~SWGUDPSinkSettings(); + SWGUDPSourceSettings(); + SWGUDPSourceSettings(QString* json); + virtual ~SWGUDPSourceSettings(); void init(); void cleanup(); virtual QString asJson () override; virtual QJsonObject* asJsonObject() override; virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGUDPSinkSettings* fromJson(QString &jsonString) override; + virtual SWGUDPSourceSettings* fromJson(QString &jsonString) override; qint32 getSampleFormat(); void setSampleFormat(qint32 sample_format); @@ -164,4 +164,4 @@ private: } -#endif /* SWGUDPSinkSettings_H_ */ +#endif /* SWGUDPSourceSettings_H_ */ diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index 37ef3b3b4..f8445a5c9 100755 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -227,19 +227,19 @@ def setupChannel(options): settings["SSBModSettings"]["toneFrequency"] = 600 settings["SSBModSettings"]["bandwidth"] = 1000 settings["SSBModSettings"]["lowCut"] = 300 - elif options.channel_id == "UDPSink": - settings["UDPSinkSettings"]["title"] = "Test UDP Sink" - settings["UDPSinkSettings"]["inputFrequencyOffset"] = options.channel_freq - settings["UDPSinkSettings"]["rfBandwidth"] = 12500 - settings["UDPSinkSettings"]["fmDeviation"] = 6000 - settings["UDPSinkSettings"]["autoRWBalance"] = 1 - settings["UDPSinkSettings"]["stereoInput"] = 0 - settings["UDPSinkSettings"]["udpAddress"] = "127.0.0.1" - settings["UDPSinkSettings"]["udpPort"] = 9998 - settings["UDPSinkSettings"]["inputSampleRate"] = 48000 - settings["UDPSinkSettings"]["sampleFormat"] = 1 # FormatNFM - settings["UDPSinkSettings"]["gainIn"] = 2.5 - settings["UDPSinkSettings"]["gainOut"] = 2.8 + elif options.channel_id == "UDPSource": + settings["UDPSourceSettings"]["title"] = "Test UDP Source" + settings["UDPSourceSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["UDPSourceSettings"]["rfBandwidth"] = 12500 + settings["UDPSourceSettings"]["fmDeviation"] = 6000 + settings["UDPSourceSettings"]["autoRWBalance"] = 1 + settings["UDPSourceSettings"]["stereoInput"] = 0 + settings["UDPSourceSettings"]["udpAddress"] = "127.0.0.1" + settings["UDPSourceSettings"]["udpPort"] = 9998 + settings["UDPSourceSettings"]["inputSampleRate"] = 48000 + settings["UDPSourceSettings"]["sampleFormat"] = 1 # FormatNFM + settings["UDPSourceSettings"]["gainIn"] = 2.5 + settings["UDPSourceSettings"]["gainOut"] = 2.8 elif options.channel_id == "WFMMod": settings["WFMModSettings"]["title"] = "Test WFM" settings["WFMModSettings"]["inputFrequencyOffset"] = options.channel_freq From f8e90827acb319c0d7065a47a5a1c100eb2708ab Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 23:45:56 +0200 Subject: [PATCH 738/956] Renamed the UDPSink from sdrbase utils to UDPSinkUtil --- plugins/channelrx/demoddsd/dsddemod.h | 1 - plugins/channelrx/udpsrc/udpsrc.cpp | 6 +++--- plugins/channelrx/udpsrc/udpsrc.h | 8 ++++---- sdrbase/util/{udpsink.h => udpsinkutil.h} | 10 +++++----- 4 files changed, 12 insertions(+), 13 deletions(-) rename sdrbase/util/{udpsink.h => udpsinkutil.h} (94%) diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index f5d906723..207a05f60 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -33,7 +33,6 @@ #include "dsp/afsquelch.h" #include "audio/audiofifo.h" #include "util/message.h" -#include "util/udpsink.h" #include "util/doublebufferfifo.h" #include "dsddemodsettings.h" diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index 25689e017..3cc35f112 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -62,9 +62,9 @@ UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : { setObjectName(m_channelId); - m_udpBuffer16 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); - m_udpBufferMono16 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); - m_udpBuffer24 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); + m_udpBuffer16 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); + m_udpBufferMono16 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); + m_udpBuffer24 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); m_audioSocket = new QUdpSocket(this); m_udpAudioBuf = new char[m_udpAudioPayloadSize]; diff --git a/plugins/channelrx/udpsrc/udpsrc.h b/plugins/channelrx/udpsrc/udpsrc.h index e582a8845..74dc18cb4 100644 --- a/plugins/channelrx/udpsrc/udpsrc.h +++ b/plugins/channelrx/udpsrc/udpsrc.h @@ -30,7 +30,7 @@ #include "dsp/movingaverage.h" #include "dsp/agc.h" #include "dsp/bandpass.h" -#include "util/udpsink.h" +#include "util/udpsinkutil.h" #include "util/message.h" #include "audio/audiofifo.h" @@ -197,9 +197,9 @@ protected: fftfilt* UDPFilter; SampleVector m_sampleBuffer; - UDPSink *m_udpBuffer16; - UDPSink *m_udpBufferMono16; - UDPSink *m_udpBuffer24; + UDPSinkUtil *m_udpBuffer16; + UDPSinkUtil *m_udpBufferMono16; + UDPSinkUtil *m_udpBuffer24; AudioVector m_audioBuffer; uint m_audioBufferFill; diff --git a/sdrbase/util/udpsink.h b/sdrbase/util/udpsinkutil.h similarity index 94% rename from sdrbase/util/udpsink.h rename to sdrbase/util/udpsinkutil.h index 17ed66a2b..04bf4f820 100644 --- a/sdrbase/util/udpsink.h +++ b/sdrbase/util/udpsinkutil.h @@ -26,10 +26,10 @@ #include template -class UDPSink +class UDPSinkUtil { public: - UDPSink(QObject *parent, unsigned int udpSize) : + UDPSinkUtil(QObject *parent, unsigned int udpSize) : m_udpSize(udpSize), m_udpSamples(udpSize/sizeof(T)), m_address(QHostAddress::LocalHost), @@ -41,7 +41,7 @@ public: m_socket = new QUdpSocket(parent); } - UDPSink(QObject *parent, unsigned int udpSize, unsigned int port) : + UDPSinkUtil(QObject *parent, unsigned int udpSize, unsigned int port) : m_udpSize(udpSize), m_udpSamples(udpSize/sizeof(T)), m_address(QHostAddress::LocalHost), @@ -53,7 +53,7 @@ public: m_socket = new QUdpSocket(parent); } - UDPSink (QObject *parent, unsigned int udpSize, QHostAddress& address, unsigned int port) : + UDPSinkUtil (QObject *parent, unsigned int udpSize, QHostAddress& address, unsigned int port) : m_udpSize(udpSize), m_udpSamples(udpSize/sizeof(T)), m_address(address), @@ -65,7 +65,7 @@ public: m_socket = new QUdpSocket(parent); } - ~UDPSink() + ~UDPSinkUtil() { delete[] m_sampleBuffer; delete m_socket; From 06acb17e3a82ff96b8ee26630b67d8437748697d Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 00:08:47 +0200 Subject: [PATCH 739/956] Renamed UDPSrc to UDPSink --- plugins/channelrx/udpsrc/CMakeLists.txt | 18 +- .../udpsrc/{udpsrc.cpp => udpsink.cpp} | 96 +++++----- .../channelrx/udpsrc/{udpsrc.h => udpsink.h} | 30 +-- .../udpsrc/{udpsrc.pro => udpsink.pro} | 20 +- .../udpsrc/{udpsrcgui.cpp => udpsinkgui.cpp} | 179 +++++++++--------- .../udpsrc/{udpsrcgui.h => udpsinkgui.h} | 24 +-- .../udpsrc/{udpsrcgui.ui => udpsinkgui.ui} | 8 +- .../{udpsrcplugin.cpp => udpsinkplugin.cpp} | 35 ++-- .../{udpsrcplugin.h => udpsinkplugin.h} | 4 +- ...udpsrcsettings.cpp => udpsinksettings.cpp} | 10 +- .../{udpsrcsettings.h => udpsinksettings.h} | 4 +- pluginssrv/channelrx/udpsrc/CMakeLists.txt | 12 +- 12 files changed, 219 insertions(+), 221 deletions(-) rename plugins/channelrx/udpsrc/{udpsrc.cpp => udpsink.cpp} (89%) rename plugins/channelrx/udpsrc/{udpsrc.h => udpsink.h} (91%) rename plugins/channelrx/udpsrc/{udpsrc.pro => udpsink.pro} (79%) rename plugins/channelrx/udpsrc/{udpsrcgui.cpp => udpsinkgui.cpp} (74%) rename plugins/channelrx/udpsrc/{udpsrcgui.h => udpsinkgui.h} (86%) rename plugins/channelrx/udpsrc/{udpsrcgui.ui => udpsinkgui.ui} (99%) rename plugins/channelrx/udpsrc/{udpsrcplugin.cpp => udpsinkplugin.cpp} (67%) rename plugins/channelrx/udpsrc/{udpsrcplugin.h => udpsinkplugin.h} (95%) rename plugins/channelrx/udpsrc/{udpsrcsettings.cpp => udpsinksettings.cpp} (95%) rename plugins/channelrx/udpsrc/{udpsrcsettings.h => udpsinksettings.h} (98%) diff --git a/plugins/channelrx/udpsrc/CMakeLists.txt b/plugins/channelrx/udpsrc/CMakeLists.txt index c73e0f38b..6c33f3596 100644 --- a/plugins/channelrx/udpsrc/CMakeLists.txt +++ b/plugins/channelrx/udpsrc/CMakeLists.txt @@ -3,21 +3,21 @@ project(udpsrc) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(udpsrc_SOURCES - udpsrc.cpp - udpsrcgui.cpp - udpsrcplugin.cpp - udpsrcsettings.cpp + udpsink.cpp + udpsinkgui.cpp + udpsinkplugin.cpp + udpsinksettings.cpp ) set(udpsrc_HEADERS - udpsrc.h - udpsrcgui.h - udpsrcplugin.h - udpsrcsettings.h + udpsink.h + udpsinkgui.h + udpsinkplugin.h + udpsinksettings.h ) set(udpsrc_FORMS - udpsrcgui.ui + udpsinkgui.ui ) include_directories( diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsink.cpp similarity index 89% rename from plugins/channelrx/udpsrc/udpsrc.cpp rename to plugins/channelrx/udpsrc/udpsink.cpp index 3cc35f112..b6976189e 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsink.cpp @@ -30,18 +30,18 @@ #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" -#include "udpsrc.h" +#include "udpsink.h" -const Real UDPSrc::m_agcTarget = 16384.0f; +const Real UDPSink::m_agcTarget = 16384.0f; -MESSAGE_CLASS_DEFINITION(UDPSrc::MsgConfigureUDPSrc, Message) -MESSAGE_CLASS_DEFINITION(UDPSrc::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(UDPSrc::MsgUDPSrcSpectrum, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSrc, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSrcSpectrum, Message) -const QString UDPSrc::m_channelIdURI = "sdrangel.channel.udpsrc"; -const QString UDPSrc::m_channelId = "UDPSrc"; +const QString UDPSink::m_channelIdURI = "sdrangel.channel.udpsrc"; +const QString UDPSink::m_channelId = "UDPSrc"; -UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : +UDPSink::UDPSink(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_inputSampleRate(48000), @@ -112,7 +112,7 @@ UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : m_deviceAPI->addChannelAPI(this); } -UDPSrc::~UDPSrc() +UDPSink::~UDPSink() { delete m_audioSocket; delete m_udpBuffer24; @@ -127,13 +127,13 @@ UDPSrc::~UDPSrc() delete UDPFilter; } -void UDPSrc::setSpectrum(MessageQueue* messageQueue, bool enabled) +void UDPSink::setSpectrum(MessageQueue* messageQueue, bool enabled) { Message* cmd = MsgUDPSrcSpectrum::create(enabled); messageQueue->push(cmd); } -void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) +void UDPSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) { Complex ci; fftfilt::cmplx* sideband; @@ -153,10 +153,10 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: double agcFactor = 1.0; if ((m_settings.m_agc) && - (m_settings.m_sampleFormat != UDPSrcSettings::FormatNFM) && - (m_settings.m_sampleFormat != UDPSrcSettings::FormatNFMMono) && - (m_settings.m_sampleFormat != UDPSrcSettings::FormatIQ16) && - (m_settings.m_sampleFormat != UDPSrcSettings::FormatIQ24)) + (m_settings.m_sampleFormat != UDPSinkSettings::FormatNFM) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatNFMMono) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatIQ16) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatIQ24)) { agcFactor = m_agc.feedAndGetValue(ci); inMagSq = m_agc.getMagSq(); @@ -176,7 +176,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: calculateSquelch(m_inMagsq); - if (m_settings.m_sampleFormat == UDPSrcSettings::FormatLSB) // binaural LSB + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatLSB) // binaural LSB { ci *= agcFactor; int n_out = UDPFilter->runSSB(ci, &sideband, false); @@ -192,7 +192,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } } - if (m_settings.m_sampleFormat == UDPSrcSettings::FormatUSB) // binaural USB + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB) // binaural USB { ci *= agcFactor; int n_out = UDPFilter->runSSB(ci, &sideband, true); @@ -208,19 +208,19 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatNFM) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFM) { Real discri = m_squelchOpen ? m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_gain : 0; udpWriteNorm(discri, discri); m_outMovingAverage.feed(discri*discri); } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatNFMMono) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFMMono) { Real discri = m_squelchOpen ? m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_gain : 0; udpWriteNormMono(discri); m_outMovingAverage.feed(discri*discri); } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatLSBMono) // Monaural LSB + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) // Monaural LSB { ci *= agcFactor; int n_out = UDPFilter->runSSB(ci, &sideband, false); @@ -235,7 +235,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatUSBMono) // Monaural USB + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono) // Monaural USB { ci *= agcFactor; int n_out = UDPFilter->runSSB(ci, &sideband, true); @@ -250,14 +250,14 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatAMMono) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMMono) { Real amplitude = m_squelchOpen ? sqrt(inMagSq) * agcFactor * m_settings.m_gain : 0; FixReal demod = (FixReal) amplitude; udpWriteMono(demod); m_outMovingAverage.feed((amplitude/SDR_RX_SCALEF)*(amplitude/SDR_RX_SCALEF)); } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatAMNoDCMono) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMNoDCMono) { if (m_squelchOpen) { @@ -274,7 +274,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: m_outMovingAverage.feed(0); } } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatAMBPFMono) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMBPFMono) { if (m_squelchOpen) { @@ -320,17 +320,17 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: m_settingsMutex.unlock(); } -void UDPSrc::start() +void UDPSink::start() { m_phaseDiscri.reset(); applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); } -void UDPSrc::stop() +void UDPSink::stop() { } -bool UDPSrc::handleMessage(const Message& cmd) +bool UDPSink::handleMessage(const Message& cmd) { if (DownChannelizer::MsgChannelizerNotification::match(cmd)) { @@ -392,7 +392,7 @@ bool UDPSrc::handleMessage(const Message& cmd) } } -void UDPSrc::audioReadyRead() +void UDPSink::audioReadyRead() { while (m_audioSocket->hasPendingDatagrams()) { @@ -460,7 +460,7 @@ void UDPSrc::audioReadyRead() //qDebug("UDPSrc::audioReadyRead: done"); } -void UDPSrc::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) +void UDPSink::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "UDPSrc::applyChannelSettings:" << " inputSampleRate: " << inputSampleRate @@ -484,7 +484,7 @@ void UDPSrc::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, m_inputFrequencyOffset = inputFrequencyOffset; } -void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) +void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) { qDebug() << "UDPSrc::applySettings:" << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset @@ -514,10 +514,10 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.0); m_sampleDistanceRemain = m_inputSampleRate / settings.m_outputSampleRate; - if ((settings.m_sampleFormat == UDPSrcSettings::FormatLSB) || - (settings.m_sampleFormat == UDPSrcSettings::FormatLSBMono) || - (settings.m_sampleFormat == UDPSrcSettings::FormatUSB) || - (settings.m_sampleFormat == UDPSrcSettings::FormatUSBMono)) + if ((settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono)) { m_squelchGate = settings.m_outputSampleRate * 0.05; } @@ -555,10 +555,10 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { - if ((settings.m_sampleFormat == UDPSrcSettings::FormatLSB) || - (settings.m_sampleFormat == UDPSrcSettings::FormatLSBMono) || - (settings.m_sampleFormat == UDPSrcSettings::FormatUSB) || - (settings.m_sampleFormat == UDPSrcSettings::FormatUSBMono)) + if ((settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono)) { m_squelchGate = settings.m_outputSampleRate * 0.05; } @@ -620,12 +620,12 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_settings = settings; } -QByteArray UDPSrc::serialize() const +QByteArray UDPSink::serialize() const { return m_settings.serialize(); } -bool UDPSrc::deserialize(const QByteArray& data) +bool UDPSink::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { @@ -642,7 +642,7 @@ bool UDPSrc::deserialize(const QByteArray& data) } } -int UDPSrc::webapiSettingsGet( +int UDPSink::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { @@ -652,20 +652,20 @@ int UDPSrc::webapiSettingsGet( return 200; } -int UDPSrc::webapiSettingsPutPatch( +int UDPSink::webapiSettingsPutPatch( bool force, const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - UDPSrcSettings settings = m_settings; + UDPSinkSettings settings = m_settings; bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("outputSampleRate")) { settings.m_outputSampleRate = response.getUdpSrcSettings()->getOutputSampleRate(); } if (channelSettingsKeys.contains("sampleFormat")) { - settings.m_sampleFormat = (UDPSrcSettings::SampleFormat) response.getUdpSrcSettings()->getSampleFormat(); + settings.m_sampleFormat = (UDPSinkSettings::SampleFormat) response.getUdpSrcSettings()->getSampleFormat(); } if (channelSettingsKeys.contains("inputFrequencyOffset")) { @@ -723,7 +723,7 @@ int UDPSrc::webapiSettingsPutPatch( if (frequencyOffsetChanged) { - UDPSrc::MsgConfigureChannelizer *msgChan = UDPSrc::MsgConfigureChannelizer::create( + UDPSink::MsgConfigureChannelizer *msgChan = UDPSink::MsgConfigureChannelizer::create( (int) settings.m_outputSampleRate, (int) settings.m_inputFrequencyOffset); m_inputMessageQueue.push(msgChan); @@ -744,7 +744,7 @@ int UDPSrc::webapiSettingsPutPatch( return 200; } -int UDPSrc::webapiReportGet( +int UDPSink::webapiReportGet( SWGSDRangel::SWGChannelReport& response, QString& errorMessage __attribute__((unused))) { @@ -754,7 +754,7 @@ int UDPSrc::webapiReportGet( return 200; } -void UDPSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSrcSettings& settings) +void UDPSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings) { response.getUdpSrcSettings()->setOutputSampleRate(settings.m_outputSampleRate); response.getUdpSrcSettings()->setSampleFormat((int) settings.m_sampleFormat); @@ -788,7 +788,7 @@ void UDPSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon } } -void UDPSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { response.getUdpSrcReport()->setChannelPowerDb(CalcDb::dbPower(getInMagSq())); response.getUdpSrcReport()->setOutputPowerDb(CalcDb::dbPower(getMagSq())); diff --git a/plugins/channelrx/udpsrc/udpsrc.h b/plugins/channelrx/udpsrc/udpsink.h similarity index 91% rename from plugins/channelrx/udpsrc/udpsrc.h rename to plugins/channelrx/udpsrc/udpsink.h index 74dc18cb4..a1e6a07b3 100644 --- a/plugins/channelrx/udpsrc/udpsrc.h +++ b/plugins/channelrx/udpsrc/udpsink.h @@ -34,14 +34,14 @@ #include "util/message.h" #include "audio/audiofifo.h" -#include "udpsrcsettings.h" +#include "udpsinksettings.h" class QUdpSocket; class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; -class UDPSrc : public BasebandSampleSink, public ChannelSinkAPI { +class UDPSink : public BasebandSampleSink, public ChannelSinkAPI { Q_OBJECT public: @@ -49,19 +49,19 @@ public: MESSAGE_CLASS_DECLARATION public: - const UDPSrcSettings& getSettings() const { return m_settings; } + const UDPSinkSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureUDPSrc* create(const UDPSrcSettings& settings, bool force) + static MsgConfigureUDPSrc* create(const UDPSinkSettings& settings, bool force) { return new MsgConfigureUDPSrc(settings, force); } private: - UDPSrcSettings m_settings; + UDPSinkSettings m_settings; bool m_force; - MsgConfigureUDPSrc(const UDPSrcSettings& settings, bool force) : + MsgConfigureUDPSrc(const UDPSinkSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -92,8 +92,8 @@ public: { } }; - UDPSrc(DeviceSourceAPI *deviceAPI); - virtual ~UDPSrc(); + UDPSink(DeviceSourceAPI *deviceAPI); + virtual ~UDPSink(); virtual void destroy() { delete this; } void setSpectrum(BasebandSampleSink* spectrum) { m_spectrum = spectrum; } @@ -178,7 +178,7 @@ protected: int m_inputSampleRate; int m_inputFrequencyOffset; - UDPSrcSettings m_settings; + UDPSinkSettings m_settings; QUdpSocket *m_audioSocket; @@ -230,9 +230,9 @@ protected: QMutex m_settingsMutex; void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = true); - void applySettings(const UDPSrcSettings& settings, bool force = false); + void applySettings(const UDPSinkSettings& settings, bool force = false); - void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSrcSettings& settings); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); inline void calculateSquelch(double value) @@ -297,9 +297,9 @@ protected: { if (SDR_RX_SAMP_SZ == 16) { - if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ16) { + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ16) { m_udpBuffer16->write(Sample16(real, imag)); - } else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ24) { + } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ24) { m_udpBuffer24->write(Sample24(real<<8, imag<<8)); } else { m_udpBuffer16->write(Sample16(real, imag)); @@ -307,9 +307,9 @@ protected: } else if (SDR_RX_SAMP_SZ == 24) { - if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ16) { + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ16) { m_udpBuffer16->write(Sample16(real>>8, imag>>8)); - } else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatIQ24) { + } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ24) { m_udpBuffer24->write(Sample24(real, imag)); } else { m_udpBuffer16->write(Sample16(real>>8, imag>>8)); diff --git a/plugins/channelrx/udpsrc/udpsrc.pro b/plugins/channelrx/udpsrc/udpsink.pro similarity index 79% rename from plugins/channelrx/udpsrc/udpsrc.pro rename to plugins/channelrx/udpsrc/udpsink.pro index e8843d37b..534f2f6cc 100644 --- a/plugins/channelrx/udpsrc/udpsrc.pro +++ b/plugins/channelrx/udpsrc/udpsink.pro @@ -9,7 +9,7 @@ CONFIG += plugin QT += core gui widgets multimedia network opengl -TARGET = udpsrc +TARGET = udpsink DEFINES += USE_SSE2=1 QMAKE_CXXFLAGS += -msse2 @@ -26,17 +26,17 @@ INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -SOURCES += udpsrc.cpp\ - udpsrcgui.cpp\ - udpsrcplugin.cpp\ - udpsrcsettings.cpp +SOURCES += udpsink.cpp\ + udpsinkgui.cpp\ + udpsinkplugin.cpp\ + udpsinksettings.cpp -HEADERS += udpsrc.h\ - udpsrcgui.h\ - udpsrcplugin.h\ - udpsrcsettings.h +HEADERS += udpsink.h\ + udpsinkgui.h\ + udpsinkplugin.h\ + udpsinksettings.h -FORMS += udpsrcgui.ui +FORMS += udpsinkgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsrc/udpsinkgui.cpp similarity index 74% rename from plugins/channelrx/udpsrc/udpsrcgui.cpp rename to plugins/channelrx/udpsrc/udpsinkgui.cpp index a3332091f..c38e01db7 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsrc/udpsinkgui.cpp @@ -15,8 +15,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "udpsrcgui.h" - #include "device/devicesourceapi.h" #include "device/deviceuiset.h" #include "plugin/pluginapi.h" @@ -25,44 +23,45 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "gui/basicchannelsettingsdialog.h" -#include "ui_udpsrcgui.h" +#include "ui_udpsinkgui.h" #include "mainwindow.h" -#include "udpsrc.h" +#include "udpsink.h" +#include "udpsinkgui.h" -UDPSrcGUI* UDPSrcGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +UDPSinkGUI* UDPSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { - UDPSrcGUI* gui = new UDPSrcGUI(pluginAPI, deviceUISet, rxChannel); + UDPSinkGUI* gui = new UDPSinkGUI(pluginAPI, deviceUISet, rxChannel); return gui; } -void UDPSrcGUI::destroy() +void UDPSinkGUI::destroy() { delete this; } -void UDPSrcGUI::setName(const QString& name) +void UDPSinkGUI::setName(const QString& name) { setObjectName(name); } -qint64 UDPSrcGUI::getCenterFrequency() const +qint64 UDPSinkGUI::getCenterFrequency() const { return m_channelMarker.getCenterFrequency(); } -void UDPSrcGUI::setCenterFrequency(qint64 centerFrequency) +void UDPSinkGUI::setCenterFrequency(qint64 centerFrequency) { m_channelMarker.setCenterFrequency(centerFrequency); applySettings(); } -QString UDPSrcGUI::getName() const +QString UDPSinkGUI::getName() const { return objectName(); } -void UDPSrcGUI::resetToDefaults() +void UDPSinkGUI::resetToDefaults() { m_settings.resetToDefaults(); displaySettings(); @@ -70,12 +69,12 @@ void UDPSrcGUI::resetToDefaults() applySettings(true); } -QByteArray UDPSrcGUI::serialize() const +QByteArray UDPSinkGUI::serialize() const { return m_settings.serialize(); } -bool UDPSrcGUI::deserialize(const QByteArray& data) +bool UDPSinkGUI::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { @@ -89,11 +88,11 @@ bool UDPSrcGUI::deserialize(const QByteArray& data) } } -bool UDPSrcGUI::handleMessage(const Message& message ) +bool UDPSinkGUI::handleMessage(const Message& message ) { - if (UDPSrc::MsgConfigureUDPSrc::match(message)) + if (UDPSink::MsgConfigureUDPSrc::match(message)) { - const UDPSrc::MsgConfigureUDPSrc& cfg = (UDPSrc::MsgConfigureUDPSrc&) message; + const UDPSink::MsgConfigureUDPSrc& cfg = (UDPSink::MsgConfigureUDPSrc&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -106,7 +105,7 @@ bool UDPSrcGUI::handleMessage(const Message& message ) } } -void UDPSrcGUI::handleSourceMessages() +void UDPSinkGUI::handleSourceMessages() { Message* message; @@ -119,32 +118,32 @@ void UDPSrcGUI::handleSourceMessages() } } -void UDPSrcGUI::channelMarkerChangedByCursor() +void UDPSinkGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettingsImmediate(); } -void UDPSrcGUI::channelMarkerHighlightedByCursor() +void UDPSinkGUI::channelMarkerHighlightedByCursor() { setHighlighted(m_channelMarker.getHighlighted()); } -void UDPSrcGUI::tick() +void UDPSinkGUI::tick() { if (m_tickCount % 4 == 0) { // m_channelPowerAvg.feed(m_udpSrc->getMagSq()); // double powDb = CalcDb::dbPower(m_channelPowerAvg.average()); - double powDb = CalcDb::dbPower(m_udpSrc->getMagSq()); + double powDb = CalcDb::dbPower(m_udpSink->getMagSq()); ui->channelPower->setText(QString::number(powDb, 'f', 1)); - m_inPowerAvg.feed(m_udpSrc->getInMagSq()); + m_inPowerAvg.feed(m_udpSink->getInMagSq()); double inPowDb = CalcDb::dbPower(m_inPowerAvg.average()); ui->inputPower->setText(QString::number(inPowDb, 'f', 1)); } - if (m_udpSrc->getSquelchOpen()) { + if (m_udpSink->getSquelchOpen()) { ui->squelchLabel->setStyleSheet("QLabel { background-color : green; }"); } else { ui->squelchLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); @@ -153,12 +152,12 @@ void UDPSrcGUI::tick() m_tickCount++; } -UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : +UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : RollupWidget(parent), - ui(new Ui::UDPSrcGUI), + ui(new Ui::UDPSinkGUI), m_pluginAPI(pluginAPI), m_deviceUISet(deviceUISet), - m_udpSrc(0), + m_udpSink(0), m_channelMarker(this), m_channelPowerAvg(4, 1e-10), m_inPowerAvg(4, 1e-10), @@ -172,9 +171,9 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam setAttribute(Qt::WA_DeleteOnClose, true); m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); - m_udpSrc = (UDPSrc*) rxChannel; //new UDPSrc(m_deviceUISet->m_deviceSourceAPI); - m_udpSrc->setSpectrum(m_spectrumVis); - m_udpSrc->setMessageQueueToGUI(getInputMessageQueue()); + m_udpSink = (UDPSink*) rxChannel; //new UDPSrc(m_deviceUISet->m_deviceSourceAPI); + m_udpSink->setSpectrum(m_spectrumVis); + m_udpSink->setMessageQueueToGUI(getInputMessageQueue()); ui->fmDeviation->setEnabled(false); @@ -211,7 +210,7 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_settings.setChannelMarker(&m_channelMarker); m_settings.setSpectrumGUI(ui->spectrumGUI); - m_deviceUISet->registerRxChannelInstance(UDPSrc::m_channelIdURI, this); + m_deviceUISet->registerRxChannelInstance(UDPSink::m_channelIdURI, this); m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); @@ -226,20 +225,20 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam applySettings(true); } -UDPSrcGUI::~UDPSrcGUI() +UDPSinkGUI::~UDPSinkGUI() { m_deviceUISet->removeRxChannelInstance(this); - delete m_udpSrc; // TODO: check this: when the GUI closes it has to delete the demodulator + delete m_udpSink; // TODO: check this: when the GUI closes it has to delete the demodulator delete m_spectrumVis; delete ui; } -void UDPSrcGUI::blockApplySettings(bool block) +void UDPSinkGUI::blockApplySettings(bool block) { m_doApplySettings = !block; } -void UDPSrcGUI::displaySettings() +void UDPSinkGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); @@ -290,41 +289,41 @@ void UDPSrcGUI::displaySettings() ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); } -void UDPSrcGUI::setSampleFormatIndex(const UDPSrcSettings::SampleFormat& sampleFormat) +void UDPSinkGUI::setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampleFormat) { switch(sampleFormat) { - case UDPSrcSettings::FormatIQ16: + case UDPSinkSettings::FormatIQ16: ui->sampleFormat->setCurrentIndex(0); break; - case UDPSrcSettings::FormatIQ24: + case UDPSinkSettings::FormatIQ24: ui->sampleFormat->setCurrentIndex(1); break; - case UDPSrcSettings::FormatNFM: + case UDPSinkSettings::FormatNFM: ui->sampleFormat->setCurrentIndex(2); break; - case UDPSrcSettings::FormatNFMMono: + case UDPSinkSettings::FormatNFMMono: ui->sampleFormat->setCurrentIndex(3); break; - case UDPSrcSettings::FormatLSB: + case UDPSinkSettings::FormatLSB: ui->sampleFormat->setCurrentIndex(4); break; - case UDPSrcSettings::FormatUSB: + case UDPSinkSettings::FormatUSB: ui->sampleFormat->setCurrentIndex(5); break; - case UDPSrcSettings::FormatLSBMono: + case UDPSinkSettings::FormatLSBMono: ui->sampleFormat->setCurrentIndex(6); break; - case UDPSrcSettings::FormatUSBMono: + case UDPSinkSettings::FormatUSBMono: ui->sampleFormat->setCurrentIndex(7); break; - case UDPSrcSettings::FormatAMMono: + case UDPSinkSettings::FormatAMMono: ui->sampleFormat->setCurrentIndex(8); break; - case UDPSrcSettings::FormatAMNoDCMono: + case UDPSinkSettings::FormatAMNoDCMono: ui->sampleFormat->setCurrentIndex(9); break; - case UDPSrcSettings::FormatAMBPFMono: + case UDPSinkSettings::FormatAMBPFMono: ui->sampleFormat->setCurrentIndex(10); break; default: @@ -333,94 +332,94 @@ void UDPSrcGUI::setSampleFormatIndex(const UDPSrcSettings::SampleFormat& sampleF } } -void UDPSrcGUI::setSampleFormat(int index) +void UDPSinkGUI::setSampleFormat(int index) { switch(index) { case 0: - m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ16; + m_settings.m_sampleFormat = UDPSinkSettings::FormatIQ16; ui->fmDeviation->setEnabled(false); break; case 1: - m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ24; + m_settings.m_sampleFormat = UDPSinkSettings::FormatIQ24; ui->fmDeviation->setEnabled(false); break; case 2: - m_settings.m_sampleFormat = UDPSrcSettings::FormatNFM; + m_settings.m_sampleFormat = UDPSinkSettings::FormatNFM; ui->fmDeviation->setEnabled(true); break; case 3: - m_settings.m_sampleFormat = UDPSrcSettings::FormatNFMMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatNFMMono; ui->fmDeviation->setEnabled(true); break; case 4: - m_settings.m_sampleFormat = UDPSrcSettings::FormatLSB; + m_settings.m_sampleFormat = UDPSinkSettings::FormatLSB; ui->fmDeviation->setEnabled(false); break; case 5: - m_settings.m_sampleFormat = UDPSrcSettings::FormatUSB; + m_settings.m_sampleFormat = UDPSinkSettings::FormatUSB; ui->fmDeviation->setEnabled(false); break; case 6: - m_settings.m_sampleFormat = UDPSrcSettings::FormatLSBMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatLSBMono; ui->fmDeviation->setEnabled(false); break; case 7: - m_settings.m_sampleFormat = UDPSrcSettings::FormatUSBMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatUSBMono; ui->fmDeviation->setEnabled(false); break; case 8: - m_settings.m_sampleFormat = UDPSrcSettings::FormatAMMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatAMMono; ui->fmDeviation->setEnabled(false); break; case 9: - m_settings.m_sampleFormat = UDPSrcSettings::FormatAMNoDCMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatAMNoDCMono; ui->fmDeviation->setEnabled(false); break; case 10: - m_settings.m_sampleFormat = UDPSrcSettings::FormatAMBPFMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatAMBPFMono; ui->fmDeviation->setEnabled(false); break; default: - m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ16; + m_settings.m_sampleFormat = UDPSinkSettings::FormatIQ16; ui->fmDeviation->setEnabled(false); break; } } -void UDPSrcGUI::applySettingsImmediate(bool force) +void UDPSinkGUI::applySettingsImmediate(bool force) { if (m_doApplySettings) { - UDPSrc::MsgConfigureUDPSrc* message = UDPSrc::MsgConfigureUDPSrc::create( m_settings, force); - m_udpSrc->getInputMessageQueue()->push(message); + UDPSink::MsgConfigureUDPSrc* message = UDPSink::MsgConfigureUDPSrc::create( m_settings, force); + m_udpSink->getInputMessageQueue()->push(message); } } -void UDPSrcGUI::applySettings(bool force) +void UDPSinkGUI::applySettings(bool force) { if (m_doApplySettings) { - UDPSrc::MsgConfigureChannelizer* channelConfigMsg = UDPSrc::MsgConfigureChannelizer::create( + UDPSink::MsgConfigureChannelizer* channelConfigMsg = UDPSink::MsgConfigureChannelizer::create( m_settings.m_outputSampleRate, m_channelMarker.getCenterFrequency()); - m_udpSrc->getInputMessageQueue()->push(channelConfigMsg); + m_udpSink->getInputMessageQueue()->push(channelConfigMsg); - UDPSrc::MsgConfigureUDPSrc* message = UDPSrc::MsgConfigureUDPSrc::create( m_settings, force); - m_udpSrc->getInputMessageQueue()->push(message); + UDPSink::MsgConfigureUDPSrc* message = UDPSink::MsgConfigureUDPSrc::create( m_settings, force); + m_udpSink->getInputMessageQueue()->push(message); ui->applyBtn->setEnabled(false); ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); } } -void UDPSrcGUI::on_deltaFrequency_changed(qint64 value) +void UDPSinkGUI::on_deltaFrequency_changed(qint64 value) { m_channelMarker.setCenterFrequency(value); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettings(); } -void UDPSrcGUI::on_sampleFormat_currentIndexChanged(int index) +void UDPSinkGUI::on_sampleFormat_currentIndexChanged(int index) { if ((index == 1) || (index == 2)) { ui->fmDeviation->setEnabled(true); @@ -434,14 +433,14 @@ void UDPSrcGUI::on_sampleFormat_currentIndexChanged(int index) ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_outputUDPAddress_editingFinished() +void UDPSinkGUI::on_outputUDPAddress_editingFinished() { m_settings.m_udpAddress = ui->outputUDPAddress->text(); ui->applyBtn->setEnabled(true); ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_outputUDPPort_editingFinished() +void UDPSinkGUI::on_outputUDPPort_editingFinished() { bool ok; quint16 udpPort = ui->outputUDPPort->text().toInt(&ok); @@ -457,7 +456,7 @@ void UDPSrcGUI::on_outputUDPPort_editingFinished() ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_inputUDPAudioPort_editingFinished() +void UDPSinkGUI::on_inputUDPAudioPort_editingFinished() { bool ok; quint16 udpPort = ui->inputUDPAudioPort->text().toInt(&ok); @@ -473,7 +472,7 @@ void UDPSrcGUI::on_inputUDPAudioPort_editingFinished() ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSinkGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; Real outputSampleRate = ui->sampleRate->text().toDouble(&ok); @@ -492,7 +491,7 @@ void UDPSrcGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unus ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSinkGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; Real rfBandwidth = ui->rfBandwidth->text().toDouble(&ok); @@ -513,7 +512,7 @@ void UDPSrcGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unu ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSinkGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; int fmDeviation = ui->fmDeviation->text().toInt(&ok); @@ -532,7 +531,7 @@ void UDPSrcGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unu ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_applyBtn_clicked() +void UDPSinkGUI::on_applyBtn_clicked() { if (m_rfBandwidthChanged) { @@ -545,39 +544,39 @@ void UDPSrcGUI::on_applyBtn_clicked() applySettings(); } -void UDPSrcGUI::on_audioActive_toggled(bool active) +void UDPSinkGUI::on_audioActive_toggled(bool active) { m_settings.m_audioActive = active; applySettingsImmediate(); } -void UDPSrcGUI::on_audioStereo_toggled(bool stereo) +void UDPSinkGUI::on_audioStereo_toggled(bool stereo) { m_settings.m_audioStereo = stereo; applySettingsImmediate(); } -void UDPSrcGUI::on_agc_toggled(bool agc) +void UDPSinkGUI::on_agc_toggled(bool agc) { m_settings.m_agc = agc; applySettingsImmediate(); } -void UDPSrcGUI::on_gain_valueChanged(int value) +void UDPSinkGUI::on_gain_valueChanged(int value) { m_settings.m_gain = value / 10.0; ui->gainText->setText(tr("%1").arg(value/10.0, 0, 'f', 1)); applySettingsImmediate(); } -void UDPSrcGUI::on_volume_valueChanged(int value) +void UDPSinkGUI::on_volume_valueChanged(int value) { m_settings.m_volume = value; ui->volumeText->setText(QString("%1").arg(value)); applySettingsImmediate(); } -void UDPSrcGUI::on_squelch_valueChanged(int value) +void UDPSinkGUI::on_squelch_valueChanged(int value) { m_settings.m_squelchdB = value; @@ -595,22 +594,22 @@ void UDPSrcGUI::on_squelch_valueChanged(int value) applySettingsImmediate(); } -void UDPSrcGUI::on_squelchGate_valueChanged(int value) +void UDPSinkGUI::on_squelchGate_valueChanged(int value) { m_settings.m_squelchGate = value; ui->squelchGateText->setText(tr("%1").arg(value*10.0, 0, 'f', 0)); applySettingsImmediate(); } -void UDPSrcGUI::onWidgetRolled(QWidget* widget, bool rollDown) +void UDPSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown) { - if ((widget == ui->spectrumBox) && (m_udpSrc != 0)) + if ((widget == ui->spectrumBox) && (m_udpSink != 0)) { - m_udpSrc->setSpectrum(m_udpSrc->getInputMessageQueue(), rollDown); + m_udpSink->setSpectrum(m_udpSink->getInputMessageQueue(), rollDown); } } -void UDPSrcGUI::onMenuDialogCalled(const QPoint &p) +void UDPSinkGUI::onMenuDialogCalled(const QPoint &p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); dialog.move(p); @@ -627,12 +626,12 @@ void UDPSrcGUI::onMenuDialogCalled(const QPoint &p) applySettingsImmediate(); } -void UDPSrcGUI::leaveEvent(QEvent*) +void UDPSinkGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); } -void UDPSrcGUI::enterEvent(QEvent*) +void UDPSinkGUI::enterEvent(QEvent*) { m_channelMarker.setHighlighted(true); } diff --git a/plugins/channelrx/udpsrc/udpsrcgui.h b/plugins/channelrx/udpsrc/udpsinkgui.h similarity index 86% rename from plugins/channelrx/udpsrc/udpsrcgui.h rename to plugins/channelrx/udpsrc/udpsinkgui.h index a35ac9324..eec9ddd07 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.h +++ b/plugins/channelrx/udpsrc/udpsinkgui.h @@ -25,23 +25,23 @@ #include "dsp/movingaverage.h" #include "util/messagequeue.h" -#include "udpsrc.h" -#include "udpsrcsettings.h" +#include "udpsink.h" +#include "udpsinksettings.h" class PluginAPI; class DeviceUISet; -class UDPSrc; +class UDPSink; class SpectrumVis; namespace Ui { - class UDPSrcGUI; + class UDPSinkGUI; } -class UDPSrcGUI : public RollupWidget, public PluginInstanceGUI { +class UDPSinkGUI : public RollupWidget, public PluginInstanceGUI { Q_OBJECT public: - static UDPSrcGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + static UDPSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); virtual void destroy(); void setName(const QString& name); @@ -60,11 +60,11 @@ public slots: void channelMarkerHighlightedByCursor(); private: - Ui::UDPSrcGUI* ui; + Ui::UDPSinkGUI* ui; PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; - UDPSrc* m_udpSrc; - UDPSrcSettings m_settings; + UDPSink* m_udpSink; + UDPSinkSettings m_settings; ChannelMarker m_channelMarker; MovingAverage m_channelPowerAvg; MovingAverage m_inPowerAvg; @@ -78,15 +78,15 @@ private: // RF path SpectrumVis* m_spectrumVis; - explicit UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); - virtual ~UDPSrcGUI(); + explicit UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~UDPSinkGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); void applySettingsImmediate(bool force = false); void displaySettings(); void setSampleFormat(int index); - void setSampleFormatIndex(const UDPSrcSettings::SampleFormat& sampleFormat); + void setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampleFormat); void leaveEvent(QEvent*); void enterEvent(QEvent*); diff --git a/plugins/channelrx/udpsrc/udpsrcgui.ui b/plugins/channelrx/udpsrc/udpsinkgui.ui similarity index 99% rename from plugins/channelrx/udpsrc/udpsrcgui.ui rename to plugins/channelrx/udpsrc/udpsinkgui.ui index b0d114948..8071b8c59 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.ui +++ b/plugins/channelrx/udpsrc/udpsinkgui.ui @@ -1,7 +1,7 @@ - UDPSrcGUI - + UDPSinkGUI + 0 @@ -29,10 +29,10 @@ - UDP Sample Source + UDP Sample Sink - UDP Sample Source + UDP Sample Sink diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.cpp b/plugins/channelrx/udpsrc/udpsinkplugin.cpp similarity index 67% rename from plugins/channelrx/udpsrc/udpsrcplugin.cpp rename to plugins/channelrx/udpsrc/udpsinkplugin.cpp index e31fc580e..b95e8f087 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.cpp +++ b/plugins/channelrx/udpsrc/udpsinkplugin.cpp @@ -15,65 +15,64 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "udpsrcplugin.h" - #include #include "plugin/pluginapi.h" #ifndef SERVER_MODE -#include "udpsrcgui.h" +#include "udpsinkgui.h" #endif -#include "udpsrc.h" +#include "udpsink.h" +#include "udpsinkplugin.h" -const PluginDescriptor UDPSrcPlugin::m_pluginDescriptor = { - QString("UDP Channel Source"), - QString("4.0.7"), +const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { + QString("UDP Channel Sink"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, QString("https://github.com/f4exb/sdrangel") }; -UDPSrcPlugin::UDPSrcPlugin(QObject* parent) : +UDPSinkPlugin::UDPSinkPlugin(QObject* parent) : QObject(parent), m_pluginAPI(0) { } -const PluginDescriptor& UDPSrcPlugin::getPluginDescriptor() const +const PluginDescriptor& UDPSinkPlugin::getPluginDescriptor() const { return m_pluginDescriptor; } -void UDPSrcPlugin::initPlugin(PluginAPI* pluginAPI) +void UDPSinkPlugin::initPlugin(PluginAPI* pluginAPI) { m_pluginAPI = pluginAPI; // register TCP Channel Source - m_pluginAPI->registerRxChannel(UDPSrc::m_channelIdURI, UDPSrc::m_channelId, this); + m_pluginAPI->registerRxChannel(UDPSink::m_channelIdURI, UDPSink::m_channelId, this); } #ifdef SERVER_MODE -PluginInstanceGUI* UDPSrcPlugin::createRxChannelGUI( +PluginInstanceGUI* UDPSinkPlugin::createRxChannelGUI( DeviceUISet *deviceUISet __attribute__((unused)), BasebandSampleSink *rxChannel __attribute__((unused))) { return 0; } #else -PluginInstanceGUI* UDPSrcPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +PluginInstanceGUI* UDPSinkPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { - return UDPSrcGUI::create(m_pluginAPI, deviceUISet, rxChannel); + return UDPSinkGUI::create(m_pluginAPI, deviceUISet, rxChannel); } #endif -BasebandSampleSink* UDPSrcPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) +BasebandSampleSink* UDPSinkPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { - return new UDPSrc(deviceAPI); + return new UDPSink(deviceAPI); } -ChannelSinkAPI* UDPSrcPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) +ChannelSinkAPI* UDPSinkPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) { - return new UDPSrc(deviceAPI); + return new UDPSink(deviceAPI); } diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.h b/plugins/channelrx/udpsrc/udpsinkplugin.h similarity index 95% rename from plugins/channelrx/udpsrc/udpsrcplugin.h rename to plugins/channelrx/udpsrc/udpsinkplugin.h index 9c3ca2de6..c8b360392 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.h +++ b/plugins/channelrx/udpsrc/udpsinkplugin.h @@ -24,13 +24,13 @@ class DeviceUISet; class BasebandSampleSink; -class UDPSrcPlugin : public QObject, PluginInterface { +class UDPSinkPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) Q_PLUGIN_METADATA(IID "sdrangel.demod.udpsrc") public: - explicit UDPSrcPlugin(QObject* parent = 0); + explicit UDPSinkPlugin(QObject* parent = 0); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/channelrx/udpsrc/udpsrcsettings.cpp b/plugins/channelrx/udpsrc/udpsinksettings.cpp similarity index 95% rename from plugins/channelrx/udpsrc/udpsrcsettings.cpp rename to plugins/channelrx/udpsrc/udpsinksettings.cpp index 6a1bdf856..5e84f3d73 100644 --- a/plugins/channelrx/udpsrc/udpsrcsettings.cpp +++ b/plugins/channelrx/udpsrc/udpsinksettings.cpp @@ -19,16 +19,16 @@ #include "dsp/dspengine.h" #include "util/simpleserializer.h" #include "settings/serializable.h" -#include "udpsrcsettings.h" +#include "udpsinksettings.h" -UDPSrcSettings::UDPSrcSettings() : +UDPSinkSettings::UDPSinkSettings() : m_channelMarker(0), m_spectrumGUI(0) { resetToDefaults(); } -void UDPSrcSettings::resetToDefaults() +void UDPSinkSettings::resetToDefaults() { m_outputSampleRate = 48000; m_sampleFormat = FormatIQ16; @@ -51,7 +51,7 @@ void UDPSrcSettings::resetToDefaults() m_title = "UDP Sample Source"; } -QByteArray UDPSrcSettings::serialize() const +QByteArray UDPSinkSettings::serialize() const { SimpleSerializer s(1); s.writeS32(2, m_inputFrequencyOffset); @@ -85,7 +85,7 @@ QByteArray UDPSrcSettings::serialize() const } -bool UDPSrcSettings::deserialize(const QByteArray& data) +bool UDPSinkSettings::deserialize(const QByteArray& data) { SimpleDeserializer d(data); diff --git a/plugins/channelrx/udpsrc/udpsrcsettings.h b/plugins/channelrx/udpsrc/udpsinksettings.h similarity index 98% rename from plugins/channelrx/udpsrc/udpsrcsettings.h rename to plugins/channelrx/udpsrc/udpsinksettings.h index b268b47c4..c825ead71 100644 --- a/plugins/channelrx/udpsrc/udpsrcsettings.h +++ b/plugins/channelrx/udpsrc/udpsinksettings.h @@ -23,7 +23,7 @@ class Serializable; -struct UDPSrcSettings +struct UDPSinkSettings { enum SampleFormat { FormatIQ16, @@ -65,7 +65,7 @@ struct UDPSrcSettings Serializable *m_channelMarker; Serializable *m_spectrumGUI; - UDPSrcSettings(); + UDPSinkSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } diff --git a/pluginssrv/channelrx/udpsrc/CMakeLists.txt b/pluginssrv/channelrx/udpsrc/CMakeLists.txt index 36e3d15de..1c92525e1 100644 --- a/pluginssrv/channelrx/udpsrc/CMakeLists.txt +++ b/pluginssrv/channelrx/udpsrc/CMakeLists.txt @@ -4,15 +4,15 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(PLUGIN_PREFIX "../../../plugins/channelrx/udpsrc") set(udpsrc_SOURCES - ${PLUGIN_PREFIX}/udpsrc.cpp - ${PLUGIN_PREFIX}/udpsrcplugin.cpp - ${PLUGIN_PREFIX}/udpsrcsettings.cpp + ${PLUGIN_PREFIX}/udpsink.cpp + ${PLUGIN_PREFIX}/udpsinkplugin.cpp + ${PLUGIN_PREFIX}/udpsinksettings.cpp ) set(udpsrc_HEADERS - ${PLUGIN_PREFIX}/udpsrc.h - ${PLUGIN_PREFIX}/udpsrcplugin.h - ${PLUGIN_PREFIX}/udpsrcsettings.h + ${PLUGIN_PREFIX}/udpsink.h + ${PLUGIN_PREFIX}/udpsinkplugin.h + ${PLUGIN_PREFIX}/udpsinksettings.h ) include_directories( From fed7f72da130a85f264b7e2498d49c9c4d9a8a8b Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 00:36:30 +0200 Subject: [PATCH 740/956] REST API: Renamed UDPSrc to UDPSink --- ReadmeDeveloper.md | 23 +- plugins/channelrx/udpsrc/udpsink.cpp | 142 ++++++------ plugins/channelrx/udpsrc/udpsinkgui.cpp | 2 +- sdrbase/resources/res.qrc | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 210 +++++++++--------- .../include/{UDPSrc.yaml => UDPSink.yaml} | 6 +- .../resources/webapi/doc/swagger/swagger.yaml | 8 +- sdrbase/webapi/webapirequestmapper.cpp | 14 +- .../include/{UDPSrc.yaml => UDPSink.yaml} | 6 +- swagger/sdrangel/api/swagger/swagger.yaml | 8 +- swagger/sdrangel/code/html2/index.html | 210 +++++++++--------- .../code/qt5/client/SWGChannelReport.cpp | 32 +-- .../code/qt5/client/SWGChannelReport.h | 10 +- .../code/qt5/client/SWGChannelSettings.cpp | 32 +-- .../code/qt5/client/SWGChannelSettings.h | 10 +- .../code/qt5/client/SWGModelFactory.h | 16 +- ...GUDPSrcReport.cpp => SWGUDPSinkReport.cpp} | 40 ++-- .../{SWGUDPSrcReport.h => SWGUDPSinkReport.h} | 18 +- ...SrcSettings.cpp => SWGUDPSinkSettings.cpp} | 100 ++++----- ...GUDPSrcSettings.h => SWGUDPSinkSettings.h} | 20 +- swagger/sdrangel/examples/rx_test.py | 14 +- 21 files changed, 461 insertions(+), 462 deletions(-) rename sdrbase/resources/webapi/doc/swagger/include/{UDPSrc.yaml => UDPSink.yaml} (96%) rename swagger/sdrangel/api/swagger/include/{UDPSrc.yaml => UDPSink.yaml} (96%) rename swagger/sdrangel/code/qt5/client/{SWGUDPSrcReport.cpp => SWGUDPSinkReport.cpp} (84%) rename swagger/sdrangel/code/qt5/client/{SWGUDPSrcReport.h => SWGUDPSinkReport.h} (88%) rename swagger/sdrangel/code/qt5/client/{SWGUDPSrcSettings.cpp => SWGUDPSinkSettings.cpp} (83%) rename swagger/sdrangel/code/qt5/client/{SWGUDPSrcSettings.h => SWGUDPSinkSettings.h} (92%) diff --git a/ReadmeDeveloper.md b/ReadmeDeveloper.md index 9fd3e31ac..09785d3a1 100644 --- a/ReadmeDeveloper.md +++ b/ReadmeDeveloper.md @@ -62,8 +62,7 @@ At present the following plugins are available: - `NFMDemodXxx` classes in `plugins/channelrx/demodnfm`: Narrowband FM demodulator with audio output. - `SSBDemodXxx` classes in `plugins/channelrx/demodssb`: SSB/DSB/CW demodulator with audio output. - `WFMDemodXxx` classes in `plugins/channelrx/demodwfm`: Wideband FM demodulator with audio output. This is a basic demodulator. - - `TCPSrcXxx` classes in `plugins/channelrx/tcpsrc`: Sends channel I/Q samples via TCP - - `UDPSrcXxx` classes in `plugins/channelrx/udpsrc`: Sends channel I/Q or FM demodulated samples via UDP + - `UDPSinkXxx` classes in `plugins/channelrx/udpsink`: Sends channel I/Q or FM demodulated samples via UDP

    Channel transmitter (Tx) plugins

    @@ -104,11 +103,11 @@ The `plugins` subdirectory contains the associated plugins used to manage device - `xxxanalyzergui.h/cpp` : Analyzer GUI - `xxxanalyzerplugin.h/cpp` : Analyzer plugin manager - `xxxanalyzer.pro` : Qt .pro file for Windows/Android build - - `xxxsrc` : Interface to the outside (e.g xxx = udp): - - `xxxsrc.h/cpp` : Interface core - - `xxxsrcgui.h/cpp` : Interface GUI - - `xxxsrcplugin/h/cpp` : Interface plugin manager - - `xxxsrc.pro` : Qt .pro file for Windows/Android build + - `xxxsink` : Interface to the outside (e.g xxx = udp): + - `xxxsink.h/cpp` : Interface core + - `xxxsinkgui.h/cpp` : Interface GUI + - `xxxsinkplugin/h/cpp` : Interface plugin manager + - `xxxsink.pro` : Qt .pro file for Windows/Android build - Transmitter functions (Tx): - `samplesink`: Device managers: @@ -130,11 +129,11 @@ The `plugins` subdirectory contains the associated plugins used to manage device - `xxxgeneratorgui.h/cpp` : Generator GUI - `xxxgeneratorplugin.h/cpp` : Generator plugin manager - `xxxgenerator.pro` : Qt .pro file for Windows/Android build - - `xxxsink` : Interface to the outside (e.g xxx = udp): - - `xxxsink.h/cpp` : Interface core - - `xxxsinkgui.h/cpp` : Interface GUI - - `xxxsinklugin/h/cpp` : Interface plugin manager - - `xxxsink.pro` : Qt .pro file for Windows/Android build + - `xxxsource` : Interface to the outside (e.g xxx = udp): + - `xxxsource.h/cpp` : Interface core + - `xxxsourcegui.h/cpp` : Interface GUI + - `xxxsourceplugin/h/cpp` : Interface plugin manager + - `xxxsource.pro` : Qt .pro file for Windows/Android build

    Device interface and GUI lifecycle

    diff --git a/plugins/channelrx/udpsrc/udpsink.cpp b/plugins/channelrx/udpsrc/udpsink.cpp index b6976189e..1e2e2a235 100644 --- a/plugins/channelrx/udpsrc/udpsink.cpp +++ b/plugins/channelrx/udpsrc/udpsink.cpp @@ -19,9 +19,9 @@ #include #include "SWGChannelSettings.h" -#include "SWGUDPSrcSettings.h" +#include "SWGUDPSinkSettings.h" #include "SWGChannelReport.h" -#include "SWGUDPSrcReport.h" +#include "SWGUDPSinkReport.h" #include "dsp/dspengine.h" #include "util/db.h" @@ -38,8 +38,8 @@ MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSrc, Message) MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSrcSpectrum, Message) -const QString UDPSink::m_channelIdURI = "sdrangel.channel.udpsrc"; -const QString UDPSink::m_channelId = "UDPSrc"; +const QString UDPSink::m_channelIdURI = "sdrangel.channel.udpsink"; +const QString UDPSink::m_channelId = "UDPSink"; UDPSink::UDPSink(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), @@ -90,12 +90,12 @@ UDPSink::UDPSink(DeviceSourceAPI *deviceAPI) : if (m_audioSocket->bind(QHostAddress::LocalHost, m_settings.m_audioPort)) { - qDebug("UDPSrc::UDPSrc: bind audio socket to port %d", m_settings.m_audioPort); + qDebug("UDPSink::UDPSink: bind audio socket to port %d", m_settings.m_audioPort); connect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead()), Qt::QueuedConnection); } else { - qWarning("UDPSrc::UDPSrc: cannot bind audio port"); + qWarning("UDPSink::UDPSink: cannot bind audio port"); } m_agc.setClampMax(SDR_RX_SCALED*SDR_RX_SCALED); @@ -310,7 +310,7 @@ void UDPSink::feed(const SampleVector::const_iterator& begin, const SampleVector } } - //qDebug() << "UDPSrc::feed: " << m_sampleBuffer.size() * 4; + //qDebug() << "UDPSink::feed: " << m_sampleBuffer.size() * 4; if((m_spectrum != 0) && (m_spectrumEnabled)) { @@ -335,7 +335,7 @@ bool UDPSink::handleMessage(const Message& cmd) if (DownChannelizer::MsgChannelizerNotification::match(cmd)) { DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "UDPSrc::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << notif.getSampleRate() + qDebug() << "UDPSink::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << notif.getSampleRate() << " frequencyOffset: " << notif.getFrequencyOffset(); applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); @@ -346,7 +346,7 @@ bool UDPSink::handleMessage(const Message& cmd) else if (MsgConfigureChannelizer::match(cmd)) { MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug() << "UDPSrc::handleMessage: MsgConfigureChannelizer:" + qDebug() << "UDPSink::handleMessage: MsgConfigureChannelizer:" << " sampleRate: " << cfg.getSampleRate() << " centerFrequency: " << cfg.getCenterFrequency(); @@ -359,7 +359,7 @@ bool UDPSink::handleMessage(const Message& cmd) else if (MsgConfigureUDPSrc::match(cmd)) { MsgConfigureUDPSrc& cfg = (MsgConfigureUDPSrc&) cmd; - qDebug("UDPSrc::handleMessage: MsgConfigureUDPSrc"); + qDebug("UDPSink::handleMessage: MsgConfigureUDPSrc"); applySettings(cfg.getSettings(), cfg.getForce()); @@ -371,7 +371,7 @@ bool UDPSink::handleMessage(const Message& cmd) m_spectrumEnabled = spc.getEnabled(); - qDebug() << "UDPSrc::handleMessage: MsgUDPSrcSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; + qDebug() << "UDPSink::handleMessage: MsgUDPSrcSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; return true; } @@ -398,7 +398,7 @@ void UDPSink::audioReadyRead() { qint64 pendingDataSize = m_audioSocket->pendingDatagramSize(); qint64 udpReadBytes = m_audioSocket->readDatagram(m_udpAudioBuf, pendingDataSize, 0, 0); - //qDebug("UDPSrc::audioReadyRead: %lld", udpReadBytes); + //qDebug("UDPSink::audioReadyRead: %lld", udpReadBytes); if (m_settings.m_audioActive) { @@ -418,7 +418,7 @@ void UDPSink::audioReadyRead() if (res != m_audioBufferFill) { - qDebug("UDPSrc::audioReadyRead: (stereo) lost %u samples", m_audioBufferFill - res); + qDebug("UDPSink::audioReadyRead: (stereo) lost %u samples", m_audioBufferFill - res); } m_audioBufferFill = 0; @@ -440,7 +440,7 @@ void UDPSink::audioReadyRead() if (res != m_audioBufferFill) { - qDebug("UDPSrc::audioReadyRead: (mono) lost %u samples", m_audioBufferFill - res); + qDebug("UDPSink::audioReadyRead: (mono) lost %u samples", m_audioBufferFill - res); } m_audioBufferFill = 0; @@ -450,19 +450,19 @@ void UDPSink::audioReadyRead() if (m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 0) != m_audioBufferFill) { - qDebug("UDPSrc::audioReadyRead: lost samples"); + qDebug("UDPSink::audioReadyRead: lost samples"); } m_audioBufferFill = 0; } } - //qDebug("UDPSrc::audioReadyRead: done"); + //qDebug("UDPSink::audioReadyRead: done"); } void UDPSink::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { - qDebug() << "UDPSrc::applyChannelSettings:" + qDebug() << "UDPSink::applyChannelSettings:" << " inputSampleRate: " << inputSampleRate << " inputFrequencyOffset: " << inputFrequencyOffset; @@ -486,7 +486,7 @@ void UDPSink::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) { - qDebug() << "UDPSrc::applySettings:" + qDebug() << "UDPSink::applySettings:" << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << " m_audioActive: " << settings.m_audioActive << " m_audioStereo: " << settings.m_audioStereo @@ -602,11 +602,11 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) if (m_audioSocket->bind(QHostAddress::LocalHost, settings.m_audioPort)) { connect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead()), Qt::QueuedConnection); - qDebug("UDPSrc::handleMessage: audio socket bound to port %d", settings.m_audioPort); + qDebug("UDPSink::handleMessage: audio socket bound to port %d", settings.m_audioPort); } else { - qWarning("UDPSrc::handleMessage: cannot bind audio socket"); + qWarning("UDPSink::handleMessage: cannot bind audio socket"); } } @@ -646,8 +646,8 @@ int UDPSink::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - response.setUdpSrcSettings(new SWGSDRangel::SWGUDPSrcSettings()); - response.getUdpSrcSettings()->init(); + response.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); + response.getUdpSinkSettings()->init(); webapiFormatChannelSettings(response, m_settings); return 200; } @@ -662,63 +662,63 @@ int UDPSink::webapiSettingsPutPatch( bool frequencyOffsetChanged = false; if (channelSettingsKeys.contains("outputSampleRate")) { - settings.m_outputSampleRate = response.getUdpSrcSettings()->getOutputSampleRate(); + settings.m_outputSampleRate = response.getUdpSinkSettings()->getOutputSampleRate(); } if (channelSettingsKeys.contains("sampleFormat")) { - settings.m_sampleFormat = (UDPSinkSettings::SampleFormat) response.getUdpSrcSettings()->getSampleFormat(); + settings.m_sampleFormat = (UDPSinkSettings::SampleFormat) response.getUdpSinkSettings()->getSampleFormat(); } if (channelSettingsKeys.contains("inputFrequencyOffset")) { - settings.m_inputFrequencyOffset = response.getUdpSrcSettings()->getInputFrequencyOffset(); + settings.m_inputFrequencyOffset = response.getUdpSinkSettings()->getInputFrequencyOffset(); frequencyOffsetChanged = true; } if (channelSettingsKeys.contains("rfBandwidth")) { - settings.m_rfBandwidth = response.getUdpSrcSettings()->getRfBandwidth(); + settings.m_rfBandwidth = response.getUdpSinkSettings()->getRfBandwidth(); } if (channelSettingsKeys.contains("fmDeviation")) { - settings.m_fmDeviation = response.getUdpSrcSettings()->getFmDeviation(); + settings.m_fmDeviation = response.getUdpSinkSettings()->getFmDeviation(); } if (channelSettingsKeys.contains("channelMute")) { - settings.m_channelMute = response.getUdpSrcSettings()->getChannelMute() != 0; + settings.m_channelMute = response.getUdpSinkSettings()->getChannelMute() != 0; } if (channelSettingsKeys.contains("gain")) { - settings.m_gain = response.getUdpSrcSettings()->getGain(); + settings.m_gain = response.getUdpSinkSettings()->getGain(); } if (channelSettingsKeys.contains("squelchDB")) { - settings.m_squelchdB = response.getUdpSrcSettings()->getSquelchDb(); + settings.m_squelchdB = response.getUdpSinkSettings()->getSquelchDb(); } if (channelSettingsKeys.contains("squelchGate")) { - settings.m_squelchGate = response.getUdpSrcSettings()->getSquelchGate(); + settings.m_squelchGate = response.getUdpSinkSettings()->getSquelchGate(); } if (channelSettingsKeys.contains("squelchEnabled")) { - settings.m_squelchEnabled = response.getUdpSrcSettings()->getSquelchEnabled() != 0; + settings.m_squelchEnabled = response.getUdpSinkSettings()->getSquelchEnabled() != 0; } if (channelSettingsKeys.contains("agc")) { - settings.m_agc = response.getUdpSrcSettings()->getAgc() != 0; + settings.m_agc = response.getUdpSinkSettings()->getAgc() != 0; } if (channelSettingsKeys.contains("audioActive")) { - settings.m_audioActive = response.getUdpSrcSettings()->getAudioActive() != 0; + settings.m_audioActive = response.getUdpSinkSettings()->getAudioActive() != 0; } if (channelSettingsKeys.contains("audioStereo")) { - settings.m_audioStereo = response.getUdpSrcSettings()->getAudioStereo() != 0; + settings.m_audioStereo = response.getUdpSinkSettings()->getAudioStereo() != 0; } if (channelSettingsKeys.contains("volume")) { - settings.m_volume = response.getUdpSrcSettings()->getVolume(); + settings.m_volume = response.getUdpSinkSettings()->getVolume(); } if (channelSettingsKeys.contains("udpAddress")) { - settings.m_udpAddress = *response.getUdpSrcSettings()->getUdpAddress(); + settings.m_udpAddress = *response.getUdpSinkSettings()->getUdpAddress(); } if (channelSettingsKeys.contains("udpPort")) { - settings.m_udpPort = response.getUdpSrcSettings()->getUdpPort(); + settings.m_udpPort = response.getUdpSinkSettings()->getUdpPort(); } if (channelSettingsKeys.contains("audioPort")) { - settings.m_audioPort = response.getUdpSrcSettings()->getAudioPort(); + settings.m_audioPort = response.getUdpSinkSettings()->getAudioPort(); } if (channelSettingsKeys.contains("rgbColor")) { - settings.m_rgbColor = response.getUdpSrcSettings()->getRgbColor(); + settings.m_rgbColor = response.getUdpSinkSettings()->getRgbColor(); } if (channelSettingsKeys.contains("title")) { - settings.m_title = *response.getUdpSrcSettings()->getTitle(); + settings.m_title = *response.getUdpSinkSettings()->getTitle(); } if (frequencyOffsetChanged) @@ -732,7 +732,7 @@ int UDPSink::webapiSettingsPutPatch( MsgConfigureUDPSrc *msg = MsgConfigureUDPSrc::create(settings, force); m_inputMessageQueue.push(msg); - qDebug("UDPSrc::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + qDebug("getUdpSinkSettings::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); if (m_guiMessageQueue) // forward to GUI if any { MsgConfigureUDPSrc *msgToGUI = MsgConfigureUDPSrc::create(settings, force); @@ -748,50 +748,50 @@ int UDPSink::webapiReportGet( SWGSDRangel::SWGChannelReport& response, QString& errorMessage __attribute__((unused))) { - response.setUdpSrcReport(new SWGSDRangel::SWGUDPSrcReport()); - response.getUdpSrcReport()->init(); + response.setUdpSinkReport(new SWGSDRangel::SWGUDPSinkReport()); + response.getUdpSinkReport()->init(); webapiFormatChannelReport(response); return 200; } void UDPSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings) { - response.getUdpSrcSettings()->setOutputSampleRate(settings.m_outputSampleRate); - response.getUdpSrcSettings()->setSampleFormat((int) settings.m_sampleFormat); - response.getUdpSrcSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); - response.getUdpSrcSettings()->setRfBandwidth(settings.m_rfBandwidth); - response.getUdpSrcSettings()->setFmDeviation(settings.m_fmDeviation); - response.getUdpSrcSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); - response.getUdpSrcSettings()->setGain(settings.m_gain); - response.getUdpSrcSettings()->setSquelchDb(settings.m_squelchdB); - response.getUdpSrcSettings()->setSquelchGate(settings.m_squelchGate); - response.getUdpSrcSettings()->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); - response.getUdpSrcSettings()->setAgc(settings.m_agc ? 1 : 0); - response.getUdpSrcSettings()->setAudioActive(settings.m_audioActive ? 1 : 0); - response.getUdpSrcSettings()->setAudioStereo(settings.m_audioStereo ? 1 : 0); - response.getUdpSrcSettings()->setVolume(settings.m_volume); + response.getUdpSinkSettings()->setOutputSampleRate(settings.m_outputSampleRate); + response.getUdpSinkSettings()->setSampleFormat((int) settings.m_sampleFormat); + response.getUdpSinkSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getUdpSinkSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getUdpSinkSettings()->setFmDeviation(settings.m_fmDeviation); + response.getUdpSinkSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getUdpSinkSettings()->setGain(settings.m_gain); + response.getUdpSinkSettings()->setSquelchDb(settings.m_squelchdB); + response.getUdpSinkSettings()->setSquelchGate(settings.m_squelchGate); + response.getUdpSinkSettings()->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); + response.getUdpSinkSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getUdpSinkSettings()->setAudioActive(settings.m_audioActive ? 1 : 0); + response.getUdpSinkSettings()->setAudioStereo(settings.m_audioStereo ? 1 : 0); + response.getUdpSinkSettings()->setVolume(settings.m_volume); - if (response.getUdpSrcSettings()->getUdpAddress()) { - *response.getUdpSrcSettings()->getUdpAddress() = settings.m_udpAddress; + if (response.getUdpSinkSettings()->getUdpAddress()) { + *response.getUdpSinkSettings()->getUdpAddress() = settings.m_udpAddress; } else { - response.getUdpSrcSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + response.getUdpSinkSettings()->setUdpAddress(new QString(settings.m_udpAddress)); } - response.getUdpSrcSettings()->setUdpPort(settings.m_udpPort); - response.getUdpSrcSettings()->setAudioPort(settings.m_audioPort); - response.getUdpSrcSettings()->setRgbColor(settings.m_rgbColor); + response.getUdpSinkSettings()->setUdpPort(settings.m_udpPort); + response.getUdpSinkSettings()->setAudioPort(settings.m_audioPort); + response.getUdpSinkSettings()->setRgbColor(settings.m_rgbColor); - if (response.getUdpSrcSettings()->getTitle()) { - *response.getUdpSrcSettings()->getTitle() = settings.m_title; + if (response.getUdpSinkSettings()->getTitle()) { + *response.getUdpSinkSettings()->getTitle() = settings.m_title; } else { - response.getUdpSrcSettings()->setTitle(new QString(settings.m_title)); + response.getUdpSinkSettings()->setTitle(new QString(settings.m_title)); } } void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { - response.getUdpSrcReport()->setChannelPowerDb(CalcDb::dbPower(getInMagSq())); - response.getUdpSrcReport()->setOutputPowerDb(CalcDb::dbPower(getMagSq())); - response.getUdpSrcReport()->setSquelch(m_squelchOpen ? 1 : 0); - response.getUdpSrcReport()->setInputSampleRate(m_inputSampleRate); + response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getInMagSq())); + response.getUdpSinkReport()->setOutputPowerDb(CalcDb::dbPower(getMagSq())); + response.getUdpSinkReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getUdpSinkReport()->setInputSampleRate(m_inputSampleRate); } diff --git a/plugins/channelrx/udpsrc/udpsinkgui.cpp b/plugins/channelrx/udpsrc/udpsinkgui.cpp index c38e01db7..dc457b89b 100644 --- a/plugins/channelrx/udpsrc/udpsinkgui.cpp +++ b/plugins/channelrx/udpsrc/udpsinkgui.cpp @@ -264,7 +264,7 @@ void UDPSinkGUI::displaySettings() ui->squelch->setValue(m_settings.m_squelchdB); ui->squelchText->setText(tr("%1").arg(ui->squelch->value()*1.0, 0, 'f', 0)); - qDebug("UDPSrcGUI::deserialize: m_squelchGate: %d", m_settings.m_squelchGate); + qDebug("UDPSinkGUI::deserialize: m_squelchGate: %d", m_settings.m_squelchGate); ui->squelchGate->setValue(m_settings.m_squelchGate); ui->squelchGateText->setText(tr("%1").arg(m_settings.m_squelchGate*10.0, 0, 'f', 0)); diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index a64c82c95..51145dc6a 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -31,7 +31,7 @@ webapi/doc/swagger/include/Structs.yaml webapi/doc/swagger/include/TestSource.yaml webapi/doc/swagger/include/UDPSource.yaml - webapi/doc/swagger/include/UDPSrc.yaml + webapi/doc/swagger/include/UDPSink.yaml webapi/doc/swagger/include/WFMDemod.yaml webapi/doc/swagger/include/WFMMod.yaml webapi/doc/swagger-ui/swagger-ui.js.map diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 5fd2008f2..c6de6f988 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1425,8 +1425,8 @@ margin-bottom: 20px; "UDPSourceReport" : { "$ref" : "#/definitions/UDPSourceReport" }, - "UDPSrcReport" : { - "$ref" : "#/definitions/UDPSrcReport" + "UDPSinkReport" : { + "$ref" : "#/definitions/UDPSinkReport" }, "WFMDemodReport" : { "$ref" : "#/definitions/WFMDemodReport" @@ -1485,8 +1485,8 @@ margin-bottom: 20px; "UDPSourceSettings" : { "$ref" : "#/definitions/UDPSourceSettings" }, - "UDPSrcSettings" : { - "$ref" : "#/definitions/UDPSrcSettings" + "UDPSinkSettings" : { + "$ref" : "#/definitions/UDPSinkSettings" }, "WFMDemodSettings" : { "$ref" : "#/definitions/WFMDemodSettings" @@ -3712,6 +3712,106 @@ margin-bottom: 20px; } }, "description" : "TestSource" +}; + defs.UDPSinkReport = { + "properties" : { + "outputPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power received in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "inputSampleRate" : { + "type" : "integer" + } + }, + "description" : "UDPSink" +}; + defs.UDPSinkSettings = { + "properties" : { + "outputSampleRate" : { + "type" : "number", + "format" : "float" + }, + "sampleFormat" : { + "type" : "integer" + }, + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "integer" + }, + "channelMute" : { + "type" : "integer", + "description" : "channel mute (1 if muted else 0)" + }, + "gain" : { + "type" : "number", + "format" : "float" + }, + "squelchDB" : { + "type" : "integer", + "description" : "power dB" + }, + "squelchGate" : { + "type" : "integer", + "description" : "100ths seconds" + }, + "squelchEnabled" : { + "type" : "integer", + "description" : "squelch enable (1 if enabled else 0)" + }, + "agc" : { + "type" : "integer", + "description" : "AGC enable (1 if enabled else 0)" + }, + "audioActive" : { + "type" : "integer", + "description" : "Audio return enable (1 if enabled else 0)" + }, + "audioStereo" : { + "type" : "integer", + "description" : "Audio return stereo (1 if stereo else 0)" + }, + "volume" : { + "type" : "integer" + }, + "udpAddress" : { + "type" : "string", + "description" : "destination UDP address (remote)" + }, + "udpPort" : { + "type" : "integer", + "format" : "uint16", + "description" : "destination UDP port (remote)" + }, + "audioPort" : { + "type" : "integer", + "format" : "uint16", + "description" : "audio return UDP port (local)" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + } + }, + "description" : "UDPSink" }; defs.UDPSourceReport = { "properties" : { @@ -3810,106 +3910,6 @@ margin-bottom: 20px; } }, "description" : "UDPSource" -}; - defs.UDPSrcReport = { - "properties" : { - "outputPowerDB" : { - "type" : "number", - "format" : "float", - "description" : "power transmitted in channel (dB)" - }, - "channelPowerDB" : { - "type" : "number", - "format" : "float", - "description" : "power received in channel (dB)" - }, - "squelch" : { - "type" : "integer", - "description" : "squelch status (1 if open else 0)" - }, - "inputSampleRate" : { - "type" : "integer" - } - }, - "description" : "UDPSink" -}; - defs.UDPSrcSettings = { - "properties" : { - "outputSampleRate" : { - "type" : "number", - "format" : "float" - }, - "sampleFormat" : { - "type" : "integer" - }, - "inputFrequencyOffset" : { - "type" : "integer", - "format" : "int64" - }, - "rfBandwidth" : { - "type" : "number", - "format" : "float" - }, - "fmDeviation" : { - "type" : "integer" - }, - "channelMute" : { - "type" : "integer", - "description" : "channel mute (1 if muted else 0)" - }, - "gain" : { - "type" : "number", - "format" : "float" - }, - "squelchDB" : { - "type" : "integer", - "description" : "power dB" - }, - "squelchGate" : { - "type" : "integer", - "description" : "100ths seconds" - }, - "squelchEnabled" : { - "type" : "integer", - "description" : "squelch enable (1 if enabled else 0)" - }, - "agc" : { - "type" : "integer", - "description" : "AGC enable (1 if enabled else 0)" - }, - "audioActive" : { - "type" : "integer", - "description" : "Audio return enable (1 if enabled else 0)" - }, - "audioStereo" : { - "type" : "integer", - "description" : "Audio return stereo (1 if stereo else 0)" - }, - "volume" : { - "type" : "integer" - }, - "udpAddress" : { - "type" : "string", - "description" : "destination UDP address (remote)" - }, - "udpPort" : { - "type" : "integer", - "format" : "uint16", - "description" : "destination UDP port (remote)" - }, - "audioPort" : { - "type" : "integer", - "format" : "uint16", - "description" : "audio return UDP port (local)" - }, - "rgbColor" : { - "type" : "integer" - }, - "title" : { - "type" : "string" - } - }, - "description" : "UDPSrc" }; defs.WFMDemodReport = { "properties" : { @@ -23066,7 +23066,7 @@ except ApiException as e:
    - Generated 2018-09-11T22:44:27.082+02:00 + Generated 2018-09-12T00:14:34.357+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/UDPSrc.yaml b/sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml similarity index 96% rename from sdrbase/resources/webapi/doc/swagger/include/UDPSrc.yaml rename to sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml index 0cdf81093..0e81847cb 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/UDPSrc.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml @@ -1,5 +1,5 @@ -UDPSrcSettings: - description: UDPSrc +UDPSinkSettings: + description: UDPSink properties: outputSampleRate: type: number @@ -56,7 +56,7 @@ UDPSrcSettings: title: type: string -UDPSrcReport: +UDPSinkReport: description: UDPSink properties: outputPowerDB: diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index d659f35c1..d2fbbaa23 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1874,8 +1874,8 @@ definitions: $ref: "/doc/swagger/include/SSBDemod.yaml#/SSBDemodSettings" UDPSourceSettings: $ref: "/doc/swagger/include/UDPSource.yaml#/UDPSourceSettings" - UDPSrcSettings: - $ref: "/doc/swagger/include/UDPSrc.yaml#/UDPSrcSettings" + UDPSinkSettings: + $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkSettings" WFMDemodSettings: $ref: "/doc/swagger/include/WFMDemod.yaml#/WFMDemodSettings" WFMModSettings: @@ -1913,8 +1913,8 @@ definitions: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" UDPSourceReport: $ref: "/doc/swagger/include/UDPSource.yaml#/UDPSourceReport" - UDPSrcReport: - $ref: "/doc/swagger/include/UDPSrc.yaml#/UDPSrcReport" + UDPSinkReport: + $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkReport" WFMDemodReport: $ref: "/doc/swagger/include/WFMDemod.yaml#/WFMDemodReport" WFMModReport: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 6e38970c1..92b424c3d 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2222,14 +2222,14 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } - else if (*channelType == "UDPSrc") + else if (*channelType == "UDPSink") { if (channelSettings.getTx() == 0) { - QJsonObject udpSrcSettingsJsonObject = jsonObject["UDPSrcSettings"].toObject(); - channelSettingsKeys = udpSrcSettingsJsonObject.keys(); - channelSettings.setUdpSrcSettings(new SWGSDRangel::SWGUDPSrcSettings()); - channelSettings.getUdpSrcSettings()->fromJsonObject(udpSrcSettingsJsonObject); + QJsonObject udpSinkSettingsJsonObject = jsonObject["UDPSinkSettings"].toObject(); + channelSettingsKeys = udpSinkSettingsJsonObject.keys(); + channelSettings.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); + channelSettings.getUdpSinkSettings()->fromJsonObject(udpSinkSettingsJsonObject); return true; } else { @@ -2416,7 +2416,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); channelSettings.setUdpSourceSettings(0); - channelSettings.setUdpSrcSettings(0); + channelSettings.setUdpSinkSettings(0); channelSettings.setWfmDemodSettings(0); channelSettings.setWfmModSettings(0); } @@ -2436,7 +2436,7 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan channelReport.setSsbDemodReport(0); channelReport.setSsbModReport(0); channelReport.setUdpSourceReport(0); - channelReport.setUdpSrcReport(0); + channelReport.setUdpSinkReport(0); channelReport.setWfmDemodReport(0); channelReport.setWfmModReport(0); } diff --git a/swagger/sdrangel/api/swagger/include/UDPSrc.yaml b/swagger/sdrangel/api/swagger/include/UDPSink.yaml similarity index 96% rename from swagger/sdrangel/api/swagger/include/UDPSrc.yaml rename to swagger/sdrangel/api/swagger/include/UDPSink.yaml index 0cdf81093..0e81847cb 100644 --- a/swagger/sdrangel/api/swagger/include/UDPSrc.yaml +++ b/swagger/sdrangel/api/swagger/include/UDPSink.yaml @@ -1,5 +1,5 @@ -UDPSrcSettings: - description: UDPSrc +UDPSinkSettings: + description: UDPSink properties: outputSampleRate: type: number @@ -56,7 +56,7 @@ UDPSrcSettings: title: type: string -UDPSrcReport: +UDPSinkReport: description: UDPSink properties: outputPowerDB: diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index a126ae966..497a6619b 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1874,8 +1874,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodSettings" UDPSourceSettings: $ref: "http://localhost:8081/api/swagger/include/UDPSource.yaml#/UDPSourceSettings" - UDPSrcSettings: - $ref: "http://localhost:8081/api/swagger/include/UDPSrc.yaml#/UDPSrcSettings" + UDPSinkSettings: + $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkSettings" WFMDemodSettings: $ref: "http://localhost:8081/api/swagger/include/WFMDemod.yaml#/WFMDemodSettings" WFMModSettings: @@ -1913,8 +1913,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" UDPSourceReport: $ref: "http://localhost:8081/api/swagger/include/UDPSource.yaml#/UDPSourceReport" - UDPSrcReport: - $ref: "http://localhost:8081/api/swagger/include/UDPSrc.yaml#/UDPSrcReport" + UDPSinkReport: + $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkReport" WFMDemodReport: $ref: "http://localhost:8081/api/swagger/include/WFMDemod.yaml#/WFMDemodReport" WFMModReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 5fd2008f2..c6de6f988 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1425,8 +1425,8 @@ margin-bottom: 20px; "UDPSourceReport" : { "$ref" : "#/definitions/UDPSourceReport" }, - "UDPSrcReport" : { - "$ref" : "#/definitions/UDPSrcReport" + "UDPSinkReport" : { + "$ref" : "#/definitions/UDPSinkReport" }, "WFMDemodReport" : { "$ref" : "#/definitions/WFMDemodReport" @@ -1485,8 +1485,8 @@ margin-bottom: 20px; "UDPSourceSettings" : { "$ref" : "#/definitions/UDPSourceSettings" }, - "UDPSrcSettings" : { - "$ref" : "#/definitions/UDPSrcSettings" + "UDPSinkSettings" : { + "$ref" : "#/definitions/UDPSinkSettings" }, "WFMDemodSettings" : { "$ref" : "#/definitions/WFMDemodSettings" @@ -3712,6 +3712,106 @@ margin-bottom: 20px; } }, "description" : "TestSource" +}; + defs.UDPSinkReport = { + "properties" : { + "outputPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power received in channel (dB)" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + }, + "inputSampleRate" : { + "type" : "integer" + } + }, + "description" : "UDPSink" +}; + defs.UDPSinkSettings = { + "properties" : { + "outputSampleRate" : { + "type" : "number", + "format" : "float" + }, + "sampleFormat" : { + "type" : "integer" + }, + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "integer" + }, + "channelMute" : { + "type" : "integer", + "description" : "channel mute (1 if muted else 0)" + }, + "gain" : { + "type" : "number", + "format" : "float" + }, + "squelchDB" : { + "type" : "integer", + "description" : "power dB" + }, + "squelchGate" : { + "type" : "integer", + "description" : "100ths seconds" + }, + "squelchEnabled" : { + "type" : "integer", + "description" : "squelch enable (1 if enabled else 0)" + }, + "agc" : { + "type" : "integer", + "description" : "AGC enable (1 if enabled else 0)" + }, + "audioActive" : { + "type" : "integer", + "description" : "Audio return enable (1 if enabled else 0)" + }, + "audioStereo" : { + "type" : "integer", + "description" : "Audio return stereo (1 if stereo else 0)" + }, + "volume" : { + "type" : "integer" + }, + "udpAddress" : { + "type" : "string", + "description" : "destination UDP address (remote)" + }, + "udpPort" : { + "type" : "integer", + "format" : "uint16", + "description" : "destination UDP port (remote)" + }, + "audioPort" : { + "type" : "integer", + "format" : "uint16", + "description" : "audio return UDP port (local)" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + } + }, + "description" : "UDPSink" }; defs.UDPSourceReport = { "properties" : { @@ -3810,106 +3910,6 @@ margin-bottom: 20px; } }, "description" : "UDPSource" -}; - defs.UDPSrcReport = { - "properties" : { - "outputPowerDB" : { - "type" : "number", - "format" : "float", - "description" : "power transmitted in channel (dB)" - }, - "channelPowerDB" : { - "type" : "number", - "format" : "float", - "description" : "power received in channel (dB)" - }, - "squelch" : { - "type" : "integer", - "description" : "squelch status (1 if open else 0)" - }, - "inputSampleRate" : { - "type" : "integer" - } - }, - "description" : "UDPSink" -}; - defs.UDPSrcSettings = { - "properties" : { - "outputSampleRate" : { - "type" : "number", - "format" : "float" - }, - "sampleFormat" : { - "type" : "integer" - }, - "inputFrequencyOffset" : { - "type" : "integer", - "format" : "int64" - }, - "rfBandwidth" : { - "type" : "number", - "format" : "float" - }, - "fmDeviation" : { - "type" : "integer" - }, - "channelMute" : { - "type" : "integer", - "description" : "channel mute (1 if muted else 0)" - }, - "gain" : { - "type" : "number", - "format" : "float" - }, - "squelchDB" : { - "type" : "integer", - "description" : "power dB" - }, - "squelchGate" : { - "type" : "integer", - "description" : "100ths seconds" - }, - "squelchEnabled" : { - "type" : "integer", - "description" : "squelch enable (1 if enabled else 0)" - }, - "agc" : { - "type" : "integer", - "description" : "AGC enable (1 if enabled else 0)" - }, - "audioActive" : { - "type" : "integer", - "description" : "Audio return enable (1 if enabled else 0)" - }, - "audioStereo" : { - "type" : "integer", - "description" : "Audio return stereo (1 if stereo else 0)" - }, - "volume" : { - "type" : "integer" - }, - "udpAddress" : { - "type" : "string", - "description" : "destination UDP address (remote)" - }, - "udpPort" : { - "type" : "integer", - "format" : "uint16", - "description" : "destination UDP port (remote)" - }, - "audioPort" : { - "type" : "integer", - "format" : "uint16", - "description" : "audio return UDP port (local)" - }, - "rgbColor" : { - "type" : "integer" - }, - "title" : { - "type" : "string" - } - }, - "description" : "UDPSrc" }; defs.WFMDemodReport = { "properties" : { @@ -23066,7 +23066,7 @@ except ApiException as e:
    - Generated 2018-09-11T22:44:27.082+02:00 + Generated 2018-09-12T00:14:34.357+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 8bf3a9d8a..2ae53f5e1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -54,8 +54,8 @@ SWGChannelReport::SWGChannelReport() { m_ssb_mod_report_isSet = false; udp_source_report = nullptr; m_udp_source_report_isSet = false; - udp_src_report = nullptr; - m_udp_src_report_isSet = false; + udp_sink_report = nullptr; + m_udp_sink_report_isSet = false; wfm_demod_report = nullptr; m_wfm_demod_report_isSet = false; wfm_mod_report = nullptr; @@ -94,8 +94,8 @@ SWGChannelReport::init() { m_ssb_mod_report_isSet = false; udp_source_report = new SWGUDPSourceReport(); m_udp_source_report_isSet = false; - udp_src_report = new SWGUDPSrcReport(); - m_udp_src_report_isSet = false; + udp_sink_report = new SWGUDPSinkReport(); + m_udp_sink_report_isSet = false; wfm_demod_report = new SWGWFMDemodReport(); m_wfm_demod_report_isSet = false; wfm_mod_report = new SWGWFMModReport(); @@ -141,8 +141,8 @@ SWGChannelReport::cleanup() { if(udp_source_report != nullptr) { delete udp_source_report; } - if(udp_src_report != nullptr) { - delete udp_src_report; + if(udp_sink_report != nullptr) { + delete udp_sink_report; } if(wfm_demod_report != nullptr) { delete wfm_demod_report; @@ -189,7 +189,7 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&udp_source_report, pJson["UDPSourceReport"], "SWGUDPSourceReport", "SWGUDPSourceReport"); - ::SWGSDRangel::setValue(&udp_src_report, pJson["UDPSrcReport"], "SWGUDPSrcReport", "SWGUDPSrcReport"); + ::SWGSDRangel::setValue(&udp_sink_report, pJson["UDPSinkReport"], "SWGUDPSinkReport", "SWGUDPSinkReport"); ::SWGSDRangel::setValue(&wfm_demod_report, pJson["WFMDemodReport"], "SWGWFMDemodReport", "SWGWFMDemodReport"); @@ -250,8 +250,8 @@ SWGChannelReport::asJsonObject() { if((udp_source_report != nullptr) && (udp_source_report->isSet())){ toJsonValue(QString("UDPSourceReport"), udp_source_report, obj, QString("SWGUDPSourceReport")); } - if((udp_src_report != nullptr) && (udp_src_report->isSet())){ - toJsonValue(QString("UDPSrcReport"), udp_src_report, obj, QString("SWGUDPSrcReport")); + if((udp_sink_report != nullptr) && (udp_sink_report->isSet())){ + toJsonValue(QString("UDPSinkReport"), udp_sink_report, obj, QString("SWGUDPSinkReport")); } if((wfm_demod_report != nullptr) && (wfm_demod_report->isSet())){ toJsonValue(QString("WFMDemodReport"), wfm_demod_report, obj, QString("SWGWFMDemodReport")); @@ -393,14 +393,14 @@ SWGChannelReport::setUdpSourceReport(SWGUDPSourceReport* udp_source_report) { this->m_udp_source_report_isSet = true; } -SWGUDPSrcReport* -SWGChannelReport::getUdpSrcReport() { - return udp_src_report; +SWGUDPSinkReport* +SWGChannelReport::getUdpSinkReport() { + return udp_sink_report; } void -SWGChannelReport::setUdpSrcReport(SWGUDPSrcReport* udp_src_report) { - this->udp_src_report = udp_src_report; - this->m_udp_src_report_isSet = true; +SWGChannelReport::setUdpSinkReport(SWGUDPSinkReport* udp_sink_report) { + this->udp_sink_report = udp_sink_report; + this->m_udp_sink_report_isSet = true; } SWGWFMDemodReport* @@ -441,7 +441,7 @@ SWGChannelReport::isSet(){ if(daemon_source_report != nullptr && daemon_source_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} if(udp_source_report != nullptr && udp_source_report->isSet()){ isObjectUpdated = true; break;} - if(udp_src_report != nullptr && udp_src_report->isSet()){ isObjectUpdated = true; break;} + if(udp_sink_report != nullptr && udp_sink_report->isSet()){ isObjectUpdated = true; break;} if(wfm_demod_report != nullptr && wfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_report != nullptr && wfm_mod_report->isSet()){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index a4029c8b1..b7e529bbd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -32,8 +32,8 @@ #include "SWGNFMModReport.h" #include "SWGSSBDemodReport.h" #include "SWGSSBModReport.h" +#include "SWGUDPSinkReport.h" #include "SWGUDPSourceReport.h" -#include "SWGUDPSrcReport.h" #include "SWGWFMDemodReport.h" #include "SWGWFMModReport.h" #include @@ -95,8 +95,8 @@ public: SWGUDPSourceReport* getUdpSourceReport(); void setUdpSourceReport(SWGUDPSourceReport* udp_source_report); - SWGUDPSrcReport* getUdpSrcReport(); - void setUdpSrcReport(SWGUDPSrcReport* udp_src_report); + SWGUDPSinkReport* getUdpSinkReport(); + void setUdpSinkReport(SWGUDPSinkReport* udp_sink_report); SWGWFMDemodReport* getWfmDemodReport(); void setWfmDemodReport(SWGWFMDemodReport* wfm_demod_report); @@ -147,8 +147,8 @@ private: SWGUDPSourceReport* udp_source_report; bool m_udp_source_report_isSet; - SWGUDPSrcReport* udp_src_report; - bool m_udp_src_report_isSet; + SWGUDPSinkReport* udp_sink_report; + bool m_udp_sink_report_isSet; SWGWFMDemodReport* wfm_demod_report; bool m_wfm_demod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index e360c0fc5..48a85b40f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -56,8 +56,8 @@ SWGChannelSettings::SWGChannelSettings() { m_ssb_demod_settings_isSet = false; udp_source_settings = nullptr; m_udp_source_settings_isSet = false; - udp_src_settings = nullptr; - m_udp_src_settings_isSet = false; + udp_sink_settings = nullptr; + m_udp_sink_settings_isSet = false; wfm_demod_settings = nullptr; m_wfm_demod_settings_isSet = false; wfm_mod_settings = nullptr; @@ -98,8 +98,8 @@ SWGChannelSettings::init() { m_ssb_demod_settings_isSet = false; udp_source_settings = new SWGUDPSourceSettings(); m_udp_source_settings_isSet = false; - udp_src_settings = new SWGUDPSrcSettings(); - m_udp_src_settings_isSet = false; + udp_sink_settings = new SWGUDPSinkSettings(); + m_udp_sink_settings_isSet = false; wfm_demod_settings = new SWGWFMDemodSettings(); m_wfm_demod_settings_isSet = false; wfm_mod_settings = new SWGWFMModSettings(); @@ -148,8 +148,8 @@ SWGChannelSettings::cleanup() { if(udp_source_settings != nullptr) { delete udp_source_settings; } - if(udp_src_settings != nullptr) { - delete udp_src_settings; + if(udp_sink_settings != nullptr) { + delete udp_sink_settings; } if(wfm_demod_settings != nullptr) { delete wfm_demod_settings; @@ -198,7 +198,7 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&udp_source_settings, pJson["UDPSourceSettings"], "SWGUDPSourceSettings", "SWGUDPSourceSettings"); - ::SWGSDRangel::setValue(&udp_src_settings, pJson["UDPSrcSettings"], "SWGUDPSrcSettings", "SWGUDPSrcSettings"); + ::SWGSDRangel::setValue(&udp_sink_settings, pJson["UDPSinkSettings"], "SWGUDPSinkSettings", "SWGUDPSinkSettings"); ::SWGSDRangel::setValue(&wfm_demod_settings, pJson["WFMDemodSettings"], "SWGWFMDemodSettings", "SWGWFMDemodSettings"); @@ -262,8 +262,8 @@ SWGChannelSettings::asJsonObject() { if((udp_source_settings != nullptr) && (udp_source_settings->isSet())){ toJsonValue(QString("UDPSourceSettings"), udp_source_settings, obj, QString("SWGUDPSourceSettings")); } - if((udp_src_settings != nullptr) && (udp_src_settings->isSet())){ - toJsonValue(QString("UDPSrcSettings"), udp_src_settings, obj, QString("SWGUDPSrcSettings")); + if((udp_sink_settings != nullptr) && (udp_sink_settings->isSet())){ + toJsonValue(QString("UDPSinkSettings"), udp_sink_settings, obj, QString("SWGUDPSinkSettings")); } if((wfm_demod_settings != nullptr) && (wfm_demod_settings->isSet())){ toJsonValue(QString("WFMDemodSettings"), wfm_demod_settings, obj, QString("SWGWFMDemodSettings")); @@ -415,14 +415,14 @@ SWGChannelSettings::setUdpSourceSettings(SWGUDPSourceSettings* udp_source_settin this->m_udp_source_settings_isSet = true; } -SWGUDPSrcSettings* -SWGChannelSettings::getUdpSrcSettings() { - return udp_src_settings; +SWGUDPSinkSettings* +SWGChannelSettings::getUdpSinkSettings() { + return udp_sink_settings; } void -SWGChannelSettings::setUdpSrcSettings(SWGUDPSrcSettings* udp_src_settings) { - this->udp_src_settings = udp_src_settings; - this->m_udp_src_settings_isSet = true; +SWGChannelSettings::setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings) { + this->udp_sink_settings = udp_sink_settings; + this->m_udp_sink_settings_isSet = true; } SWGWFMDemodSettings* @@ -464,7 +464,7 @@ SWGChannelSettings::isSet(){ if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_demod_settings != nullptr && ssb_demod_settings->isSet()){ isObjectUpdated = true; break;} if(udp_source_settings != nullptr && udp_source_settings->isSet()){ isObjectUpdated = true; break;} - if(udp_src_settings != nullptr && udp_src_settings->isSet()){ isObjectUpdated = true; break;} + if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_demod_settings != nullptr && wfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_settings != nullptr && wfm_mod_settings->isSet()){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index ee2e3e92c..0c7a18dfc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -33,8 +33,8 @@ #include "SWGNFMModSettings.h" #include "SWGSSBDemodSettings.h" #include "SWGSSBModSettings.h" +#include "SWGUDPSinkSettings.h" #include "SWGUDPSourceSettings.h" -#include "SWGUDPSrcSettings.h" #include "SWGWFMDemodSettings.h" #include "SWGWFMModSettings.h" #include @@ -99,8 +99,8 @@ public: SWGUDPSourceSettings* getUdpSourceSettings(); void setUdpSourceSettings(SWGUDPSourceSettings* udp_source_settings); - SWGUDPSrcSettings* getUdpSrcSettings(); - void setUdpSrcSettings(SWGUDPSrcSettings* udp_src_settings); + SWGUDPSinkSettings* getUdpSinkSettings(); + void setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings); SWGWFMDemodSettings* getWfmDemodSettings(); void setWfmDemodSettings(SWGWFMDemodSettings* wfm_demod_settings); @@ -154,8 +154,8 @@ private: SWGUDPSourceSettings* udp_source_settings; bool m_udp_source_settings_isSet; - SWGUDPSrcSettings* udp_src_settings; - bool m_udp_src_settings_isSet; + SWGUDPSinkSettings* udp_sink_settings; + bool m_udp_sink_settings_isSet; SWGWFMDemodSettings* wfm_demod_settings; bool m_wfm_demod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 6adb9b75a..b1d5e989f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -105,10 +105,10 @@ #include "SWGSamplingDevice.h" #include "SWGSuccessResponse.h" #include "SWGTestSourceSettings.h" +#include "SWGUDPSinkReport.h" +#include "SWGUDPSinkSettings.h" #include "SWGUDPSourceReport.h" #include "SWGUDPSourceSettings.h" -#include "SWGUDPSrcReport.h" -#include "SWGUDPSrcSettings.h" #include "SWGWFMDemodReport.h" #include "SWGWFMDemodSettings.h" #include "SWGWFMModReport.h" @@ -390,18 +390,18 @@ namespace SWGSDRangel { if(QString("SWGTestSourceSettings").compare(type) == 0) { return new SWGTestSourceSettings(); } + if(QString("SWGUDPSinkReport").compare(type) == 0) { + return new SWGUDPSinkReport(); + } + if(QString("SWGUDPSinkSettings").compare(type) == 0) { + return new SWGUDPSinkSettings(); + } if(QString("SWGUDPSourceReport").compare(type) == 0) { return new SWGUDPSourceReport(); } if(QString("SWGUDPSourceSettings").compare(type) == 0) { return new SWGUDPSourceSettings(); } - if(QString("SWGUDPSrcReport").compare(type) == 0) { - return new SWGUDPSrcReport(); - } - if(QString("SWGUDPSrcSettings").compare(type) == 0) { - return new SWGUDPSrcSettings(); - } if(QString("SWGWFMDemodReport").compare(type) == 0) { return new SWGWFMDemodReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp similarity index 84% rename from swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp rename to swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp index d05b9de59..5e96bd1a6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp @@ -11,7 +11,7 @@ */ -#include "SWGUDPSrcReport.h" +#include "SWGUDPSinkReport.h" #include "SWGHelpers.h" @@ -22,12 +22,12 @@ namespace SWGSDRangel { -SWGUDPSrcReport::SWGUDPSrcReport(QString* json) { +SWGUDPSinkReport::SWGUDPSinkReport(QString* json) { init(); this->fromJson(*json); } -SWGUDPSrcReport::SWGUDPSrcReport() { +SWGUDPSinkReport::SWGUDPSinkReport() { output_power_db = 0.0f; m_output_power_db_isSet = false; channel_power_db = 0.0f; @@ -38,12 +38,12 @@ SWGUDPSrcReport::SWGUDPSrcReport() { m_input_sample_rate_isSet = false; } -SWGUDPSrcReport::~SWGUDPSrcReport() { +SWGUDPSinkReport::~SWGUDPSinkReport() { this->cleanup(); } void -SWGUDPSrcReport::init() { +SWGUDPSinkReport::init() { output_power_db = 0.0f; m_output_power_db_isSet = false; channel_power_db = 0.0f; @@ -55,15 +55,15 @@ SWGUDPSrcReport::init() { } void -SWGUDPSrcReport::cleanup() { +SWGUDPSinkReport::cleanup() { } -SWGUDPSrcReport* -SWGUDPSrcReport::fromJson(QString &json) { +SWGUDPSinkReport* +SWGUDPSinkReport::fromJson(QString &json) { QByteArray array (json.toStdString().c_str()); QJsonDocument doc = QJsonDocument::fromJson(array); QJsonObject jsonObject = doc.object(); @@ -72,7 +72,7 @@ SWGUDPSrcReport::fromJson(QString &json) { } void -SWGUDPSrcReport::fromJsonObject(QJsonObject &pJson) { +SWGUDPSinkReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&output_power_db, pJson["outputPowerDB"], "float", ""); ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); @@ -84,7 +84,7 @@ SWGUDPSrcReport::fromJsonObject(QJsonObject &pJson) { } QString -SWGUDPSrcReport::asJson () +SWGUDPSinkReport::asJson () { QJsonObject* obj = this->asJsonObject(); @@ -95,7 +95,7 @@ SWGUDPSrcReport::asJson () } QJsonObject* -SWGUDPSrcReport::asJsonObject() { +SWGUDPSinkReport::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(m_output_power_db_isSet){ obj->insert("outputPowerDB", QJsonValue(output_power_db)); @@ -114,48 +114,48 @@ SWGUDPSrcReport::asJsonObject() { } float -SWGUDPSrcReport::getOutputPowerDb() { +SWGUDPSinkReport::getOutputPowerDb() { return output_power_db; } void -SWGUDPSrcReport::setOutputPowerDb(float output_power_db) { +SWGUDPSinkReport::setOutputPowerDb(float output_power_db) { this->output_power_db = output_power_db; this->m_output_power_db_isSet = true; } float -SWGUDPSrcReport::getChannelPowerDb() { +SWGUDPSinkReport::getChannelPowerDb() { return channel_power_db; } void -SWGUDPSrcReport::setChannelPowerDb(float channel_power_db) { +SWGUDPSinkReport::setChannelPowerDb(float channel_power_db) { this->channel_power_db = channel_power_db; this->m_channel_power_db_isSet = true; } qint32 -SWGUDPSrcReport::getSquelch() { +SWGUDPSinkReport::getSquelch() { return squelch; } void -SWGUDPSrcReport::setSquelch(qint32 squelch) { +SWGUDPSinkReport::setSquelch(qint32 squelch) { this->squelch = squelch; this->m_squelch_isSet = true; } qint32 -SWGUDPSrcReport::getInputSampleRate() { +SWGUDPSinkReport::getInputSampleRate() { return input_sample_rate; } void -SWGUDPSrcReport::setInputSampleRate(qint32 input_sample_rate) { +SWGUDPSinkReport::setInputSampleRate(qint32 input_sample_rate) { this->input_sample_rate = input_sample_rate; this->m_input_sample_rate_isSet = true; } bool -SWGUDPSrcReport::isSet(){ +SWGUDPSinkReport::isSet(){ bool isObjectUpdated = false; do{ if(m_output_power_db_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h similarity index 88% rename from swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h rename to swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h index 2891943e1..e9569bf37 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h @@ -11,13 +11,13 @@ */ /* - * SWGUDPSrcReport.h + * SWGUDPSinkReport.h * * UDPSink */ -#ifndef SWGUDPSrcReport_H_ -#define SWGUDPSrcReport_H_ +#ifndef SWGUDPSinkReport_H_ +#define SWGUDPSinkReport_H_ #include @@ -28,18 +28,18 @@ namespace SWGSDRangel { -class SWG_API SWGUDPSrcReport: public SWGObject { +class SWG_API SWGUDPSinkReport: public SWGObject { public: - SWGUDPSrcReport(); - SWGUDPSrcReport(QString* json); - virtual ~SWGUDPSrcReport(); + SWGUDPSinkReport(); + SWGUDPSinkReport(QString* json); + virtual ~SWGUDPSinkReport(); void init(); void cleanup(); virtual QString asJson () override; virtual QJsonObject* asJsonObject() override; virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGUDPSrcReport* fromJson(QString &jsonString) override; + virtual SWGUDPSinkReport* fromJson(QString &jsonString) override; float getOutputPowerDb(); void setOutputPowerDb(float output_power_db); @@ -73,4 +73,4 @@ private: } -#endif /* SWGUDPSrcReport_H_ */ +#endif /* SWGUDPSinkReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp similarity index 83% rename from swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp rename to swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp index 4233698db..e12aafd90 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp @@ -11,7 +11,7 @@ */ -#include "SWGUDPSrcSettings.h" +#include "SWGUDPSinkSettings.h" #include "SWGHelpers.h" @@ -22,12 +22,12 @@ namespace SWGSDRangel { -SWGUDPSrcSettings::SWGUDPSrcSettings(QString* json) { +SWGUDPSinkSettings::SWGUDPSinkSettings(QString* json) { init(); this->fromJson(*json); } -SWGUDPSrcSettings::SWGUDPSrcSettings() { +SWGUDPSinkSettings::SWGUDPSinkSettings() { output_sample_rate = 0.0f; m_output_sample_rate_isSet = false; sample_format = 0; @@ -68,12 +68,12 @@ SWGUDPSrcSettings::SWGUDPSrcSettings() { m_title_isSet = false; } -SWGUDPSrcSettings::~SWGUDPSrcSettings() { +SWGUDPSinkSettings::~SWGUDPSinkSettings() { this->cleanup(); } void -SWGUDPSrcSettings::init() { +SWGUDPSinkSettings::init() { output_sample_rate = 0.0f; m_output_sample_rate_isSet = false; sample_format = 0; @@ -115,7 +115,7 @@ SWGUDPSrcSettings::init() { } void -SWGUDPSrcSettings::cleanup() { +SWGUDPSinkSettings::cleanup() { @@ -141,8 +141,8 @@ SWGUDPSrcSettings::cleanup() { } } -SWGUDPSrcSettings* -SWGUDPSrcSettings::fromJson(QString &json) { +SWGUDPSinkSettings* +SWGUDPSinkSettings::fromJson(QString &json) { QByteArray array (json.toStdString().c_str()); QJsonDocument doc = QJsonDocument::fromJson(array); QJsonObject jsonObject = doc.object(); @@ -151,7 +151,7 @@ SWGUDPSrcSettings::fromJson(QString &json) { } void -SWGUDPSrcSettings::fromJsonObject(QJsonObject &pJson) { +SWGUDPSinkSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&output_sample_rate, pJson["outputSampleRate"], "float", ""); ::SWGSDRangel::setValue(&sample_format, pJson["sampleFormat"], "qint32", ""); @@ -193,7 +193,7 @@ SWGUDPSrcSettings::fromJsonObject(QJsonObject &pJson) { } QString -SWGUDPSrcSettings::asJson () +SWGUDPSinkSettings::asJson () { QJsonObject* obj = this->asJsonObject(); @@ -204,7 +204,7 @@ SWGUDPSrcSettings::asJson () } QJsonObject* -SWGUDPSrcSettings::asJsonObject() { +SWGUDPSinkSettings::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(m_output_sample_rate_isSet){ obj->insert("outputSampleRate", QJsonValue(output_sample_rate)); @@ -268,198 +268,198 @@ SWGUDPSrcSettings::asJsonObject() { } float -SWGUDPSrcSettings::getOutputSampleRate() { +SWGUDPSinkSettings::getOutputSampleRate() { return output_sample_rate; } void -SWGUDPSrcSettings::setOutputSampleRate(float output_sample_rate) { +SWGUDPSinkSettings::setOutputSampleRate(float output_sample_rate) { this->output_sample_rate = output_sample_rate; this->m_output_sample_rate_isSet = true; } qint32 -SWGUDPSrcSettings::getSampleFormat() { +SWGUDPSinkSettings::getSampleFormat() { return sample_format; } void -SWGUDPSrcSettings::setSampleFormat(qint32 sample_format) { +SWGUDPSinkSettings::setSampleFormat(qint32 sample_format) { this->sample_format = sample_format; this->m_sample_format_isSet = true; } qint64 -SWGUDPSrcSettings::getInputFrequencyOffset() { +SWGUDPSinkSettings::getInputFrequencyOffset() { return input_frequency_offset; } void -SWGUDPSrcSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { +SWGUDPSinkSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { this->input_frequency_offset = input_frequency_offset; this->m_input_frequency_offset_isSet = true; } float -SWGUDPSrcSettings::getRfBandwidth() { +SWGUDPSinkSettings::getRfBandwidth() { return rf_bandwidth; } void -SWGUDPSrcSettings::setRfBandwidth(float rf_bandwidth) { +SWGUDPSinkSettings::setRfBandwidth(float rf_bandwidth) { this->rf_bandwidth = rf_bandwidth; this->m_rf_bandwidth_isSet = true; } qint32 -SWGUDPSrcSettings::getFmDeviation() { +SWGUDPSinkSettings::getFmDeviation() { return fm_deviation; } void -SWGUDPSrcSettings::setFmDeviation(qint32 fm_deviation) { +SWGUDPSinkSettings::setFmDeviation(qint32 fm_deviation) { this->fm_deviation = fm_deviation; this->m_fm_deviation_isSet = true; } qint32 -SWGUDPSrcSettings::getChannelMute() { +SWGUDPSinkSettings::getChannelMute() { return channel_mute; } void -SWGUDPSrcSettings::setChannelMute(qint32 channel_mute) { +SWGUDPSinkSettings::setChannelMute(qint32 channel_mute) { this->channel_mute = channel_mute; this->m_channel_mute_isSet = true; } float -SWGUDPSrcSettings::getGain() { +SWGUDPSinkSettings::getGain() { return gain; } void -SWGUDPSrcSettings::setGain(float gain) { +SWGUDPSinkSettings::setGain(float gain) { this->gain = gain; this->m_gain_isSet = true; } qint32 -SWGUDPSrcSettings::getSquelchDb() { +SWGUDPSinkSettings::getSquelchDb() { return squelch_db; } void -SWGUDPSrcSettings::setSquelchDb(qint32 squelch_db) { +SWGUDPSinkSettings::setSquelchDb(qint32 squelch_db) { this->squelch_db = squelch_db; this->m_squelch_db_isSet = true; } qint32 -SWGUDPSrcSettings::getSquelchGate() { +SWGUDPSinkSettings::getSquelchGate() { return squelch_gate; } void -SWGUDPSrcSettings::setSquelchGate(qint32 squelch_gate) { +SWGUDPSinkSettings::setSquelchGate(qint32 squelch_gate) { this->squelch_gate = squelch_gate; this->m_squelch_gate_isSet = true; } qint32 -SWGUDPSrcSettings::getSquelchEnabled() { +SWGUDPSinkSettings::getSquelchEnabled() { return squelch_enabled; } void -SWGUDPSrcSettings::setSquelchEnabled(qint32 squelch_enabled) { +SWGUDPSinkSettings::setSquelchEnabled(qint32 squelch_enabled) { this->squelch_enabled = squelch_enabled; this->m_squelch_enabled_isSet = true; } qint32 -SWGUDPSrcSettings::getAgc() { +SWGUDPSinkSettings::getAgc() { return agc; } void -SWGUDPSrcSettings::setAgc(qint32 agc) { +SWGUDPSinkSettings::setAgc(qint32 agc) { this->agc = agc; this->m_agc_isSet = true; } qint32 -SWGUDPSrcSettings::getAudioActive() { +SWGUDPSinkSettings::getAudioActive() { return audio_active; } void -SWGUDPSrcSettings::setAudioActive(qint32 audio_active) { +SWGUDPSinkSettings::setAudioActive(qint32 audio_active) { this->audio_active = audio_active; this->m_audio_active_isSet = true; } qint32 -SWGUDPSrcSettings::getAudioStereo() { +SWGUDPSinkSettings::getAudioStereo() { return audio_stereo; } void -SWGUDPSrcSettings::setAudioStereo(qint32 audio_stereo) { +SWGUDPSinkSettings::setAudioStereo(qint32 audio_stereo) { this->audio_stereo = audio_stereo; this->m_audio_stereo_isSet = true; } qint32 -SWGUDPSrcSettings::getVolume() { +SWGUDPSinkSettings::getVolume() { return volume; } void -SWGUDPSrcSettings::setVolume(qint32 volume) { +SWGUDPSinkSettings::setVolume(qint32 volume) { this->volume = volume; this->m_volume_isSet = true; } QString* -SWGUDPSrcSettings::getUdpAddress() { +SWGUDPSinkSettings::getUdpAddress() { return udp_address; } void -SWGUDPSrcSettings::setUdpAddress(QString* udp_address) { +SWGUDPSinkSettings::setUdpAddress(QString* udp_address) { this->udp_address = udp_address; this->m_udp_address_isSet = true; } qint32 -SWGUDPSrcSettings::getUdpPort() { +SWGUDPSinkSettings::getUdpPort() { return udp_port; } void -SWGUDPSrcSettings::setUdpPort(qint32 udp_port) { +SWGUDPSinkSettings::setUdpPort(qint32 udp_port) { this->udp_port = udp_port; this->m_udp_port_isSet = true; } qint32 -SWGUDPSrcSettings::getAudioPort() { +SWGUDPSinkSettings::getAudioPort() { return audio_port; } void -SWGUDPSrcSettings::setAudioPort(qint32 audio_port) { +SWGUDPSinkSettings::setAudioPort(qint32 audio_port) { this->audio_port = audio_port; this->m_audio_port_isSet = true; } qint32 -SWGUDPSrcSettings::getRgbColor() { +SWGUDPSinkSettings::getRgbColor() { return rgb_color; } void -SWGUDPSrcSettings::setRgbColor(qint32 rgb_color) { +SWGUDPSinkSettings::setRgbColor(qint32 rgb_color) { this->rgb_color = rgb_color; this->m_rgb_color_isSet = true; } QString* -SWGUDPSrcSettings::getTitle() { +SWGUDPSinkSettings::getTitle() { return title; } void -SWGUDPSrcSettings::setTitle(QString* title) { +SWGUDPSinkSettings::setTitle(QString* title) { this->title = title; this->m_title_isSet = true; } bool -SWGUDPSrcSettings::isSet(){ +SWGUDPSinkSettings::isSet(){ bool isObjectUpdated = false; do{ if(m_output_sample_rate_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h similarity index 92% rename from swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h rename to swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h index 356465bc7..7f06915e2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGUDPSrcSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h @@ -11,13 +11,13 @@ */ /* - * SWGUDPSrcSettings.h + * SWGUDPSinkSettings.h * - * UDPSrc + * UDPSink */ -#ifndef SWGUDPSrcSettings_H_ -#define SWGUDPSrcSettings_H_ +#ifndef SWGUDPSinkSettings_H_ +#define SWGUDPSinkSettings_H_ #include @@ -29,18 +29,18 @@ namespace SWGSDRangel { -class SWG_API SWGUDPSrcSettings: public SWGObject { +class SWG_API SWGUDPSinkSettings: public SWGObject { public: - SWGUDPSrcSettings(); - SWGUDPSrcSettings(QString* json); - virtual ~SWGUDPSrcSettings(); + SWGUDPSinkSettings(); + SWGUDPSinkSettings(QString* json); + virtual ~SWGUDPSinkSettings(); void init(); void cleanup(); virtual QString asJson () override; virtual QJsonObject* asJsonObject() override; virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGUDPSrcSettings* fromJson(QString &jsonString) override; + virtual SWGUDPSinkSettings* fromJson(QString &jsonString) override; float getOutputSampleRate(); void setOutputSampleRate(float output_sample_rate); @@ -164,4 +164,4 @@ private: } -#endif /* SWGUDPSrcSettings_H_ */ +#endif /* SWGUDPSinkSettings_H_ */ diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 5079fd5ec..10b149872 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -305,13 +305,13 @@ def setupChannel(deviceset_url, options): settings["DSDDemodSettings"]["enableCosineFiltering"] = 1 settings["DSDDemodSettings"]["pllLock"] = 1 settings["DSDDemodSettings"]["title"] = "Channel %d" % i - elif options.channel_id == "UDPSrc": - settings["UDPSrcSettings"]["inputFrequencyOffset"] = options.channel_freq - settings["UDPSrcSettings"]["rfBandwidth"] = options.rf_bw - settings["UDPSrcSettings"]["volume"] = options.volume - settings["UDPSrcSettings"]["squelchDB"] = options.squelch_db - settings["UDPSrcSettings"]["channelMute"] = 0 - settings["UDPSrcSettings"]["title"] = "Channel %d" % i + elif options.channel_id == "UDPSink": + settings["UDPSinkSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["UDPSinkSettings"]["rfBandwidth"] = options.rf_bw + settings["UDPSinkSettings"]["volume"] = options.volume + settings["UDPSinkSettings"]["squelchDB"] = options.squelch_db + settings["UDPSinkSettings"]["channelMute"] = 0 + settings["UDPSinkSettings"]["title"] = "Channel %d" % i elif options.channel_id == "DaemonSink": settings["DaemonSinkSettings"]["title"] = "Channel %d" % i if options.daemon_address: From 3497cb92ee55150e3d59da6fafb7861bb63a076d Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 00:40:22 +0200 Subject: [PATCH 741/956] Renamed udpsrc folder to udpsink --- plugins/channelrx/CMakeLists.txt | 2 +- plugins/channelrx/{udpsrc => udpsink}/CMakeLists.txt | 0 plugins/channelrx/{udpsrc => udpsink}/readme.md | 0 plugins/channelrx/{udpsrc => udpsink}/udpsink.cpp | 0 plugins/channelrx/{udpsrc => udpsink}/udpsink.h | 0 plugins/channelrx/{udpsrc => udpsink}/udpsink.pro | 0 plugins/channelrx/{udpsrc => udpsink}/udpsinkgui.cpp | 0 plugins/channelrx/{udpsrc => udpsink}/udpsinkgui.h | 0 plugins/channelrx/{udpsrc => udpsink}/udpsinkgui.ui | 0 plugins/channelrx/{udpsrc => udpsink}/udpsinkplugin.cpp | 0 plugins/channelrx/{udpsrc => udpsink}/udpsinkplugin.h | 0 plugins/channelrx/{udpsrc => udpsink}/udpsinksettings.cpp | 0 plugins/channelrx/{udpsrc => udpsink}/udpsinksettings.h | 0 pluginssrv/channelrx/CMakeLists.txt | 2 +- pluginssrv/channelrx/{udpsrc => udpsink}/CMakeLists.txt | 2 +- 15 files changed, 3 insertions(+), 3 deletions(-) rename plugins/channelrx/{udpsrc => udpsink}/CMakeLists.txt (100%) rename plugins/channelrx/{udpsrc => udpsink}/readme.md (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsink.cpp (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsink.h (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsink.pro (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsinkgui.cpp (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsinkgui.h (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsinkgui.ui (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsinkplugin.cpp (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsinkplugin.h (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsinksettings.cpp (100%) rename plugins/channelrx/{udpsrc => udpsink}/udpsinksettings.h (100%) rename pluginssrv/channelrx/{udpsrc => udpsink}/CMakeLists.txt (93%) diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 8dcad701a..e16372740 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -5,7 +5,7 @@ add_subdirectory(demodam) add_subdirectory(demodbfm) add_subdirectory(demodnfm) add_subdirectory(demodssb) -add_subdirectory(udpsrc) +add_subdirectory(udpsink) add_subdirectory(demodwfm) add_subdirectory(chanalyzer) add_subdirectory(demodatv) diff --git a/plugins/channelrx/udpsrc/CMakeLists.txt b/plugins/channelrx/udpsink/CMakeLists.txt similarity index 100% rename from plugins/channelrx/udpsrc/CMakeLists.txt rename to plugins/channelrx/udpsink/CMakeLists.txt diff --git a/plugins/channelrx/udpsrc/readme.md b/plugins/channelrx/udpsink/readme.md similarity index 100% rename from plugins/channelrx/udpsrc/readme.md rename to plugins/channelrx/udpsink/readme.md diff --git a/plugins/channelrx/udpsrc/udpsink.cpp b/plugins/channelrx/udpsink/udpsink.cpp similarity index 100% rename from plugins/channelrx/udpsrc/udpsink.cpp rename to plugins/channelrx/udpsink/udpsink.cpp diff --git a/plugins/channelrx/udpsrc/udpsink.h b/plugins/channelrx/udpsink/udpsink.h similarity index 100% rename from plugins/channelrx/udpsrc/udpsink.h rename to plugins/channelrx/udpsink/udpsink.h diff --git a/plugins/channelrx/udpsrc/udpsink.pro b/plugins/channelrx/udpsink/udpsink.pro similarity index 100% rename from plugins/channelrx/udpsrc/udpsink.pro rename to plugins/channelrx/udpsink/udpsink.pro diff --git a/plugins/channelrx/udpsrc/udpsinkgui.cpp b/plugins/channelrx/udpsink/udpsinkgui.cpp similarity index 100% rename from plugins/channelrx/udpsrc/udpsinkgui.cpp rename to plugins/channelrx/udpsink/udpsinkgui.cpp diff --git a/plugins/channelrx/udpsrc/udpsinkgui.h b/plugins/channelrx/udpsink/udpsinkgui.h similarity index 100% rename from plugins/channelrx/udpsrc/udpsinkgui.h rename to plugins/channelrx/udpsink/udpsinkgui.h diff --git a/plugins/channelrx/udpsrc/udpsinkgui.ui b/plugins/channelrx/udpsink/udpsinkgui.ui similarity index 100% rename from plugins/channelrx/udpsrc/udpsinkgui.ui rename to plugins/channelrx/udpsink/udpsinkgui.ui diff --git a/plugins/channelrx/udpsrc/udpsinkplugin.cpp b/plugins/channelrx/udpsink/udpsinkplugin.cpp similarity index 100% rename from plugins/channelrx/udpsrc/udpsinkplugin.cpp rename to plugins/channelrx/udpsink/udpsinkplugin.cpp diff --git a/plugins/channelrx/udpsrc/udpsinkplugin.h b/plugins/channelrx/udpsink/udpsinkplugin.h similarity index 100% rename from plugins/channelrx/udpsrc/udpsinkplugin.h rename to plugins/channelrx/udpsink/udpsinkplugin.h diff --git a/plugins/channelrx/udpsrc/udpsinksettings.cpp b/plugins/channelrx/udpsink/udpsinksettings.cpp similarity index 100% rename from plugins/channelrx/udpsrc/udpsinksettings.cpp rename to plugins/channelrx/udpsink/udpsinksettings.cpp diff --git a/plugins/channelrx/udpsrc/udpsinksettings.h b/plugins/channelrx/udpsink/udpsinksettings.h similarity index 100% rename from plugins/channelrx/udpsrc/udpsinksettings.h rename to plugins/channelrx/udpsink/udpsinksettings.h diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index f0b816890..d34907866 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -15,4 +15,4 @@ endif(CM256CC_FOUND) add_subdirectory(demodnfm) add_subdirectory(demodssb) add_subdirectory(demodwfm) -add_subdirectory(udpsrc) +add_subdirectory(udpsink) diff --git a/pluginssrv/channelrx/udpsrc/CMakeLists.txt b/pluginssrv/channelrx/udpsink/CMakeLists.txt similarity index 93% rename from pluginssrv/channelrx/udpsrc/CMakeLists.txt rename to pluginssrv/channelrx/udpsink/CMakeLists.txt index 1c92525e1..81aa1c5fd 100644 --- a/pluginssrv/channelrx/udpsrc/CMakeLists.txt +++ b/pluginssrv/channelrx/udpsink/CMakeLists.txt @@ -1,7 +1,7 @@ project(udpsrc) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -set(PLUGIN_PREFIX "../../../plugins/channelrx/udpsrc") +set(PLUGIN_PREFIX "../../../plugins/channelrx/udpsink") set(udpsrc_SOURCES ${PLUGIN_PREFIX}/udpsink.cpp From 7252e886de74fa1985e48957408d025ca95b6949 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 00:43:15 +0200 Subject: [PATCH 742/956] Renamed libdemodudpsrc folder to libudpsink --- plugins/channelrx/udpsink/CMakeLists.txt | 24 ++++++++++----------- pluginssrv/channelrx/udpsink/CMakeLists.txt | 18 ++++++++-------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/plugins/channelrx/udpsink/CMakeLists.txt b/plugins/channelrx/udpsink/CMakeLists.txt index 6c33f3596..eabe5e963 100644 --- a/plugins/channelrx/udpsink/CMakeLists.txt +++ b/plugins/channelrx/udpsink/CMakeLists.txt @@ -1,22 +1,22 @@ -project(udpsrc) +project(udpsink) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -set(udpsrc_SOURCES +set(udpsink_SOURCES udpsink.cpp udpsinkgui.cpp udpsinkplugin.cpp udpsinksettings.cpp ) -set(udpsrc_HEADERS +set(udpsink_HEADERS udpsink.h udpsinkgui.h udpsinkplugin.h udpsinksettings.h ) -set(udpsrc_FORMS +set(udpsink_FORMS udpsinkgui.ui ) @@ -31,20 +31,20 @@ add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_PLUGIN) add_definitions(-DQT_SHARED) -qt5_wrap_ui(udpsrc_FORMS_HEADERS ${udpsrc_FORMS}) +qt5_wrap_ui(udpsink_FORMS_HEADERS ${udpsink_FORMS}) -add_library(demodudpsrc SHARED - ${udpsrc_SOURCES} - ${udpsrc_HEADERS_MOC} - ${udpsrc_FORMS_HEADERS} +add_library(udpsink SHARED + ${udpsink_SOURCES} + ${udpsink_HEADERS_MOC} + ${udpsink_FORMS_HEADERS} ) -target_link_libraries(demodudpsrc +target_link_libraries(udpsink ${QT_LIBRARIES} sdrbase sdrgui ) -target_link_libraries(demodudpsrc Qt5::Core Qt5::Widgets Qt5::Network) +target_link_libraries(udpsink Qt5::Core Qt5::Widgets Qt5::Network) -install(TARGETS demodudpsrc DESTINATION lib/plugins/channelrx) +install(TARGETS udpsink DESTINATION lib/plugins/channelrx) diff --git a/pluginssrv/channelrx/udpsink/CMakeLists.txt b/pluginssrv/channelrx/udpsink/CMakeLists.txt index 81aa1c5fd..2f1db0788 100644 --- a/pluginssrv/channelrx/udpsink/CMakeLists.txt +++ b/pluginssrv/channelrx/udpsink/CMakeLists.txt @@ -1,15 +1,15 @@ -project(udpsrc) +project(udpsink) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(PLUGIN_PREFIX "../../../plugins/channelrx/udpsink") -set(udpsrc_SOURCES +set(udpsink_SOURCES ${PLUGIN_PREFIX}/udpsink.cpp ${PLUGIN_PREFIX}/udpsinkplugin.cpp ${PLUGIN_PREFIX}/udpsinksettings.cpp ) -set(udpsrc_HEADERS +set(udpsink_HEADERS ${PLUGIN_PREFIX}/udpsink.h ${PLUGIN_PREFIX}/udpsinkplugin.h ${PLUGIN_PREFIX}/udpsinksettings.h @@ -26,16 +26,16 @@ add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_PLUGIN) add_definitions(-DQT_SHARED) -add_library(demodudpsrcsrv SHARED - ${udpsrc_SOURCES} - ${udpsrc_HEADERS_MOC} +add_library(udpsinksrv SHARED + ${udpsink_SOURCES} + ${udpsink_HEADERS_MOC} ) -target_link_libraries(demodudpsrcsrv +target_link_libraries(udpsinksrv ${QT_LIBRARIES} sdrbase ) -target_link_libraries(demodudpsrcsrv Qt5::Core Qt5::Network) +target_link_libraries(udpsinksrv Qt5::Core Qt5::Network) -install(TARGETS demodudpsrcsrv DESTINATION lib/pluginssrv/channelrx) +install(TARGETS udpsinksrv DESTINATION lib/pluginssrv/channelrx) From bdb0e50c5fcd588f9d50242b92f0fffe0e965dbf Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 00:49:55 +0200 Subject: [PATCH 743/956] UDP source and sink: updated documentation --- doc/img/UDPsink_plugin.png | Bin 85787 -> 69441 bytes doc/img/UDPsink_plugin.xcf | Bin 250451 -> 204165 bytes ..._plugin_agc.png => UDPsink_plugin_agc.png} | Bin ..._plugin_agc.xcf => UDPsink_plugin_agc.xcf} | Bin ...rc_plugin_sq.png => UDPsink_plugin_sq.png} | Bin ...rc_plugin_sq.xcf => UDPsink_plugin_sq.xcf} | Bin doc/img/UDPsource_plugin.png | Bin 0 -> 85787 bytes doc/img/UDPsource_plugin.xcf | Bin 0 -> 250451 bytes doc/img/UDPsrc_plugin.png | Bin 69441 -> 0 bytes doc/img/UDPsrc_plugin.xcf | Bin 204165 -> 0 bytes plugins/channelrx/udpsink/readme.md | 10 +++++----- plugins/channeltx/udpsource/readme.md | 6 +++--- 12 files changed, 8 insertions(+), 8 deletions(-) rename doc/img/{UDPsrc_plugin_agc.png => UDPsink_plugin_agc.png} (100%) rename doc/img/{UDPsrc_plugin_agc.xcf => UDPsink_plugin_agc.xcf} (100%) rename doc/img/{UDPsrc_plugin_sq.png => UDPsink_plugin_sq.png} (100%) rename doc/img/{UDPsrc_plugin_sq.xcf => UDPsink_plugin_sq.xcf} (100%) create mode 100644 doc/img/UDPsource_plugin.png create mode 100644 doc/img/UDPsource_plugin.xcf delete mode 100644 doc/img/UDPsrc_plugin.png delete mode 100644 doc/img/UDPsrc_plugin.xcf diff --git a/doc/img/UDPsink_plugin.png b/doc/img/UDPsink_plugin.png index 8dfbc5312b978dd86afc9a15f3203500d64e87b0..cc4a913cf4333d6477afa96fa0be8c47a30a9ad9 100644 GIT binary patch literal 69441 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 literal 85787 zcmb5V1yogSxGoBafFK~Dq#`JSq=a-xcZh_vlt@cAh?GbPNJ$BZba$74ba!_*(sACk z_dfUDJ?E8EpjR zkdPiDNr?(6J56jQIJzl|U6ki{|K=RPN{&|ig^?_lWrvJR_W0*PAL`QW`(#SDU*AiU ziuf5SMBy_R`mENEN_tIBSeS;=2UW6Yyrld6te`{j`#}uKJI_yAj0Memy7+D1AIG2d z@Ut0*p!88_39p~&^^()+d_!%UPSOeR3TIy#1E)=PXF=ZfBo|R^~L}G zj{nJJ{_7q8{o4QiGXML7|G)0$zrW*u^1=Uu`~Uy(!T*!{x6#+p!CcthW~~4H7~}Ka zkf@kg06H^bZP}UwRD$P^{mYH;g<+=uY9kF2 zI+-8OHRp~v#R)D9@;iL$YK+#{sQ6u6l}A!8I1qR-aGDnH^btk-(TzMGS7XYlm`$27 zD}KyH(BMPNzY0_s!j$8QC0B*p*CW4j*BI)0HM{B?fJ+ zrFQ?UHRrBYY<@Ry^NgOgp6v_4zQK#ba4W=8m7CO1tgI7q3B*ZAn;eHbLd=`jK zbjoVSE>?BK^C)7?;=OL6AJ~*fk7vzD>sHr&(0W-?CQ0WQtX%o1!7^dy$4f7>M`X-< ztFJBxuafw@vpXF6>F2h+@$B zPJ^7Qs8usL&FS~&!l|{;-H=PzFm!aAB^znf z7cMD2{`nS~r1$4tTJqsOenCgy9po`wa#SADg@0&yB%+-37ySxk=C^*J%#zg{L@a5A zO^{v@wkV6ZbR%uLZP3l^-8`R5Ot#&_RAiHhmu&v~S90!FWeutjE!XB~zC`+9f32${ z5+SB+s=3=0DHK^r(SZrnd+CiHpXYB98yH2LZh3QBB0G@{ z@P#u5Dkl^bv%OUOqkYe(36GHQPa?nT+DxshFM;F!++kDG z<5u;4;*eDd_HT*Z7m2s;3Jca)#)ev`c>CYIcRT2X)8Pg7x(Di|uScE0{fx-&8TwAg zNv&)7t`WR;OIOZl+bUlVccFJ>izlCWD*t#k?ltx$JKeuhyeiRJm@v-9jWb^*{!<@# z2$dK!KsfpDziJFOu{6?;PbLe^Uj1tuyP`NBUX&{$&;B0xQvk>69pQ_Ky_0k_h07YD zoY8Pwmf47JwdSMP`th3S1{r$F^HL^$9(40=l)d`|N6jbsDV{EkN=!0B_Y#A0Y_ud-0)l20q2#VPkrm2W)1SC&L=!V*gjf+_%{pOPIjjI*X{1>XVht7UACjsBr)V= z&xhiEdg}d6j6~G&PT$M8*AKn>j0Dhcu=?`v(QF`13EgC9eW$F=Yj6`eW$-3OClRf? zf0pm&NzUKjJMW2fO3cI5Q~75T$IX~n?ypeogE`cJ6Q(StSASb#9 z8^+~h5o6J+U!NcI{buZynezVpxo_R>Xs!Q=akVbab4ShLnFK=LPy?f#kg86ofb8{s z@eN704oe>%C+WqT+lAxOseBsN%I?qqI?I6V^%!G0G*?Q~dHi_UjcBNLzm*Iw<;}Rk zcvQZru>7e*^x0I}fuqaA=ggYJQD&ZNSJJ!1zxMH4yT~F|4?}1u;+Hbc`oB`{K)YCu zuPbwh`t{Wj%S!d+ZGv%i((|3=SR27PlPM1lX02sEsN%G0R92&E^)D(vmtQO^%{^4$ z6}S>EIgqvbr@b(x5R(RU_GF~*BG%01D6^*SGL`QD0QjH~u$!*qI7|Kh9P_;0-V4yAd=B@BeKKkByHnlPTn?vN<0ZA~7&9ye;{Fw>gq)Fz`!? zmJpMa(=tdko3ZH7MN8tp0LtO~M5$Rkm-T0yhdl4zM_mLSva+&rSudcD=B?|7tt>D5 zMnn+66`nkQe&>@xTS)8}VlL*J{TFw3xt)I>Tx-^`EY zvZmtT;4mLAx?QT#J=f?h=eKt8Z&PA4PdgwuI8CS4B_=M8&1#0vfTm$)Ch_^3-+fss z^gFZlz3_nRQ|=7Gx_+x{uCaPZ{HH% zLdRL!%&%WbaGaT63>AFe?1z1{d9v$K8c4+4!y=lL0MEhda`d4mNl;<3%p#VGla=>o=P2zXLE8e&8X#SCr_o0J5}SRK3O4;6H6`apYv&HcxRM@y(@%3h2Y$Wl%5rQ*nPMJHJj^ zQSn}Ka!oT%IXn&Fa@w88m*r-Fn+j{WV=A!`p(# zZTo9$AL_1-HIb&Kreb*P=>~tx8BA4JJGr_J{*oeaIoYw$U)x!QA@UZs+pgz`)x|8)|yByk8onrVJV-U;6qKk9TL8)ye>j-)_rEA++0`D1FvE zYGPuNqg~CR7Upp>?U?yG6)h?%>h$zldx8n}c$;&1Bg#IikX`j>XRJ=vD=I<=sztaWfOVpP|2ns{Y(Rie~% zSdo?x<}o5MQRi3o6@mop&vl~OuJ#ype1G1$TbZnyy?=CMxWCX+QB{?ql! zH4?d?N4!|z0~VtWLN_-z#q8mDKId>)kT8W!o;Me|9#{X=y1Khb_+8j(6cV4p-U1wy zs+zq%S-wk z@jG7s5h*Wkr&F)_qtgxE!V{FJv8{$9*=@~Bhj(PJs+YTT!% zr_a-=l^)JgAz}Y;k6tzBTlMSztT3Hp6B7r+YP$1VTeAB4RGydn=oiP^5nu6Om5a`G zva022&6keqM0`d3x#*mMfx&Y0{r~(OSpu(Pl&&iL!U@HL(2E(%(DH%~0p@R~cscDE zeO|FA*gI5dzfG_FR^FHUBhS=pI^lbCFQ61(xv2zg*s6JGCodG`7GcypR{q%a3lpx2 zD#Uln+P3}z9p3Plnen^prJD+SbmnuRdvsqLk}z!_zpDO*XdPqOrI9!(lCH!e`$~OkFEpQdR56 zOwSYMJRgO}=e^)AKTw(pFJ|OsF#BzwQf%iyr(_iq;UMzk2_;QSX-F1Tj(QmH#vRld zzUT+s{I_dQgvt#A#x-F%;`FAainS1Ohv9c}rS-`kAoe^R6f-iv6=(WFy05qr{#6NNKI zJ;8#7j(3{!%CYElK(&N6_Nx+IqyhtvOLdU&WS~V$!F``}vE{|KY1PyYIcI!+DlZG9 z589>dqMzO}C<}9OM;*#*1AbNobdGEzCr4EPAFO$KJ z(l;6@D7qt1lxPXR3Xk6((J}9=&y}~##oze!i%-N$(6=|>Qwo{yr#1|?{uQD@W16x0Gn|z>2L2xx=lAEI zJ?qwr+qaGg4N#IOWVWH$6+98RespbKQ`m1P`ZELL2bPuHOD=ClX@%P8ue=KbFUJZL zZYoQXmkv)34)k}XDxv5_4ri+!LZ@9`ZxP(4ef7-BA*aWTS-E9=Cfw|G0K?>?)=Dz# z+ePjV&d!IelN~!d*zP?&DVd2J5Ywz%M6ld{iw;|l!LYxc`frb9tN z8LO~1$g@sJNQh-O!H13}l0IlX*MR)KH}UF^GY=P6f#;3K;_RI}cMxp^Q02;Q#e%txVsi;`J-+L)f?6E6`r3-9bOX8e&Cb#4c}$<=xx8Csf=*)S2L|?ij8603K{Fp zeyMvkYRlY|_-`(;uUzE0y{t^L6gw$~E^31y=U2`oqs}Pny*Z(roE+JZdO+Y>6(7Br zw5yircf?-$T!>ej25qWDyRW9iRvuG5CuN+)u@YBttk!?XkcIn$aV33r9xKYdl+QF6ntI*-l# zHO8z3&J(!^WK6M2Mk}ew_pHV}-43y{)$)JCux^bN zdi!D#t*p@+hXa=QEf)un$tWyIW{VCzy!UNb*iWCkx7Sa)oQVyyDME6T%c2-KF0TZg zbS?$W7k1x8C5y!-25HOE4&(OTdSzy$n{$TykmtML&1FQX*(g2%K{Ism)72zTn_z&% znp)c0X9u0k2x7y+!g5xZnhJd8@@P;26&=0!Lq8Qcxy_Df`mkxgY3Wq8W9Z!6JJaE; z$m+69yEWFMBvCmk`3Lg9ey4~3l4_c1dZfvwNrnI9i&O|D4EdKo80KB(9}7M{e$F32 zD2EyHAmnK}4cTD75n*@f!6JLbqdT^q54=2oA8iZ=Hv8iiTTF5wuqvN`{f6>Hxm9n4 z_2O8O5!Oqy5e8MA8ik=WDkZh2H9xNgqJaH zmzWIt!6+hjo6?&%07&`Yzkh#vaj{BzGms_&YbG!wBO{!O;xbqQV1Brw%wYoPlCBvSYD32pfyVs_wn~<^SpM0RjywA9=kh^ zvv-i>d?n6`%jJkxyUI=vfn6;u9biz``;u<~(D%$ir+7f*msCxWxtbjj=>A>&n-ATpb)O1xn9lS8dh?^A@%PP29@q zhBhS1C)qmKPAY?CREVMTdc`aw>_10}_U5eD69WU|@#DvzV2JPG<9|*_m}aX?vg3JZ zzd6Eq|D?>F_v*O%`6^?l;iTO{?LGM#qNa7n(2wigvyw-a!jT3t84h+rg)5stZSr+$ zV_up)V6MMp1=zjN$z0#XW(mM@yxNf&fUhx761R)(axQM}*4zG>gsG;aq}-aSB%q<88Oha*&aX)Io-TJ7{F8epSc`ODbIfY1x;XSg>O`tn zQX(RmlK)9UP|)2Sn(0-=CCU*k9TEO?my%=i?vOa%ujXIsqHx_V1iCOk1*SPM8v5Ql zg@rvoH}^_X5`!-C>tb8z;eZS?zq;7{{(H5lxpjEb@||I^>~Yto&Ec-Ur(W_d>Ht3=lGaA9Ss(CrNgl{&uZyDlBhKl5ex!GEebtI(33@;f>C3OW9%pF{CJ25RrpJS0V3GM} zYHJpYRVv*woKuCzk z%EHd2I4e(Zh`-r3Wvw|*zp*8EoaOqbiBx2~>%{;|9%5EIW!|z1$%7aHcb>s?1y1e< zj~?l~yMVeokfUCV(h?jT+yehC#RLXo6&4m&wkk+V>vu*m)*SZmj!h)~oeV@5rtm4+ zXri03+~XL2m}Pr@ZQ}lEfZei+Bf-vs$39_0X*`cLn<-~OX)BF<{l1@+Nb<1l9B0W9 zPPZDdml|E99q8e>f|q?xThP=H>c;uM&DCntBWn~v_jgYwhHPJ6^Pik_Zt=J?>(0NX zHbW{XWnYZ;<~&=TlS8U{`R`TwEtR6*Z&C9z4u{w6ZsVU8y@jghj<4f!W^{RZNzChD z9wyisXU{{=D-l9I4;>n5Y;0_=jly%O@^SXl0*f%|G>>zY!_d!LlJ)rvhZd2}KfX%i zQNG>1^f}x$U-q&Jr6M?;It4W)hToL~LGEFl2NAP;E;jBBkBPx(7%MawFEl_ex13&R z@VXU5A(#kKk1bSNmwy|+wYB`KJqev*mLSf+Cd<>VF0z^x0Dk;owvIoJ!=j^~gFaH) z+`__nIZ{nhQu6V0?&pryy*AnA^>qma{(4%$)u!iTaZJXSfh18U2zf0);DR`}E;`FF)3d^l@V^~sJnu?vBP)0^3mFpp_Z;+-1BO91!ANAz>p^?wY zn=6HIjo+RdaliUzdY{vbY5Mx4a*ZypOtdZS+mEQk^zA_mov>BSS6nLZ)bCzB-CxYnsr}Is zNMvAU)(HYw42Q)J7Pr2iLh<~rQJUqJctk{P5)|%#VA{H%pFtl)i+CnS#l*xk>bf7H zAfUG~lzA7L`st|?4uwGc6P=YO-=(Cav}#=SEw%%nJ#7A?M98n^y;-Ro5*QP{7th2)fgn2{)@lTT3T9Oj;bBq2DKgvDvzsk zj+62B|6~rX?Zxx*Ds0R;np+vjAM*5UaX&N)mvETOVb9#MY6HzF-RQ2Zy}i-j zZhz5k_rRA@#vLn7JfG;@A1qq0 zJ8q>iy-WP@sw(o9X|K3wNx$HYC53(7ST7|l?U$cFr?mT_K>WCbLbXmFYQ54DOvXvf zqA!}K2a8iR_rQrjr;{&4FpoLjeqKu)d66i*<|u>RdFOts_iU{hC-M#R%_yD6@wl9s+O+XzVH48)PtmPcXsQA zKYYzhx^?CQ?~*{t+eN#4ZoQR9QK=Z?bx8l5t6qO%yqdQSSzBrREpNQybdt|TrJ!ks zY)I-vzb{6Nxk_BplQfa&>^9vY6x}G=`zX3UT=>p(UilJ_v~SECdFXncK6@a2JHI@; z=O@l^C287%w+c<=!&HwPi(`7_=(ZN!p)-#B?XDrA;9%Dv82cV`t}k zpM||SHud5gQnO=S`tOVl-y7Iu@9@3RKfAWR#PKArf1}r-sZ@3#P)YLyXui#x2mWhV zT*s47Oa6BjKvee|Md?qfq}dOW(kKRw!S4EP*)_ab)X(-3E{}Gb89RE%8k+_ZBa;S36ajBpOSmybnv*1dOsI~CO-BDUbcCNFDyxM6`|02iSR|~cB_k-*o6E+L4qnR5SO&As! zDs0X%14shI!V_y_`5girlx>tzHf%P(M`fVvjt zxJ!HEE1Cb_H_ABA1-EnEi{H#8-^`2;21%DbL~5hzZthi6$xBeeJEeK$x7JctVcsJ0 zAn`$4b-HzpYZb^kPw?;ZE^?fx^iQc#3l?Fx?S~~M%DYe=SIH|VFqa?%5lxWb@J@{x z_I$@p*s_rfCtP_o-?Vk&oJgyVX~x;|YjpQ_;$j+JFdURB{&+pxmT0+TDkj zExp(k?DUBGcVsmJ7)gb>G{^f~r5#>N*HX%b?J=h}k-yVi#_zUL=GJ=ldS{pJim!7p zS8sdzZdQfhQ_6qsj(=@RIvxv%2~nFR^v|>nqe)Dn4n1Gzn$+wUO=vyD9{LZiKB4hE zQAoY& z`}5lHOcffk-S=X4C7iyL)5l9xX=2L?PH#VXohPAduArU<0OCr?dw=W{-5NhXt zD8JPFo=-5+4jv&SzPg%y*%hmU$H_K_Z99C_d}QQQ3X$0ofvA=*oT36lu zBX>saGu$pKCQZL_uY34L+H{eq(%80bSrW6V6Fu0|-(APe?3G)6 zmKxpWt7OHW%5;Ur#ROi=tirfLA3pY{snrtwcr_&F8mb_dh#g?Xa7-TJ>I^%c~*3^F+( zpWml>!*5RGF=&g9sFGzzs+a%O7u0y<$lgSmld@8(|5mj25qDBW@x1GQH5<7t&q#b*=QR539!J{%<(_kQ zfAqnby->!NKWZjYb`e5PtB1u5HiN_lo|x-5@AK7%NaNA``rJW6y1Od5bt#bfa^h!k zS@S}V^A;{+%Xx=DX1r1eA9@lNpmO7dKLO5uH^4wk%tqzuq%%H>a=IiGcyxZrA5UaQ zXSS23A0V$m{D64P^2jA1A<*9+T_W^RBt;srROW2(*hyK$Kn5p|tJ;fl@i)cyL;Z5Y zI`F(ah5eq(3W{|V8Fg|w?LAgiRZUB&0QY|`TGwMfzy4Yun(8w>M$IzAvx8-Fu8*Ji zU5{VbuE>&;U%Qt%FLy)&E{}0T@DgL4hdg%g01=S$yL<=p=#iu23E$$x#00DBu{kv4 zE@(PVi@}@+L7OWpt)FjWjg^`a0d942a$3?{$x_KKvRhXI4ml2thu`Js#U0T9je$BM zAbLZ?D>rv{xgImPo2~I;fA@=H7LQ9OLQFBQ!3T+nwDmVga~|_IAMnSB>hE8(-`NVB zOp`m`d(fOnTkD5ScF>F~i0G@=R~KjdZ4~>5gMuj|g$5FfrB^`t@d*hH58|?v=^ik= zRa~%F1YH5)5-2KuYu~>)xw)nQr(y|wMDy9Y>9--W0;hAgn6xTh0hl{n>27XnY9dyx zcCaY3obG@Q&JBZTF;T)&xII(L+ZOT=#)B+JvmA$+IbWYSePkxM!b|3rNx*RAtqR7i zFQeZ#D%@vpW%pg#uzhKWe#FPmzkhfrl07^C2CIJilk(dPXLtkzBClQ{0n6>dTiM?3 z1{;c)&*?>~MCfl29b|v7xBp@>8O=-Bw_W&i2Zx;No0$MuOu&9dPDm?V1hB=BNBrRHym{nV7#q7k?Kq3~;DLB=B0um6 zG_YQOgQMB%cNk|?rw1l1SW%Hc;R-&^3mf>G!@2|m>cz*oS2He~j4(=VpO9~1elJsG zH+;M18A#{J+(Nhi=sk%>4*u@ze`6=-AB#UbCNln~ciR?9k*<&=aI`y{MB#afmwPNX3#9l8S9K4I)7+45~!2m7=zZ2 z!Qvts8l>O_f=zn|z<8O}Y@(UKbd~+$!E)!;bTv_=bPN2;WF#lHFu9AV35Mn=$Qe)! ztaqlk!EF~y5^%SXy8xfte5xX>prAmjnb%=U5@tO3kpmd_2>pYa+84-;&C%Lxg9%vB z2qPV_{fCB>NAh(;V`E9KudiEQf6?{4ii8W}-M=pe#_rYmKYGOp;r=-nBO0Wz+_o^& zT_HbVsjONWyzAZd=4QwHT#U@j%<=K@Z_6zo1_T6vE(}kIAgglxQn^vzzY{uajR~2W z(%EkeA=?03oe7kQW-@~7f)6~mzOMdjd3Pk23~WzW(QQjk;8a#P?Nb@|#8U$22Pto1 zXD6h%xY)X62i(kM7zJ2Qh2YXc@CxG<40EPVEzjX9x<_`tn6Lp+9P}Q2hRMk|cuF?M9jz+IU3oV* zK5Q}$cBzXNB3;9ku2|Rtf(ps58jxY=LJ0N@laM(G2cuH{N1jey^LY$_U0UW@Opkk}_}A zH@dbce6`evtFz;+nbw}x*9n4O@{dteLKxEkBHxzZgjEj`37f?P`{mVD@FO#w84pmb z3Q9|%Wowg^-je#!Qh4KQM?}f|J8WnwYY3WNC(N2~Gv;t5fU{XJfVTii?{^N24Lf74iMMA$}q^f8;a=56=(a^#>Z63Y~n~k)Zr&R=^+Q?XDYrLhB z;Z{Q#cq&=Sdb=~VphVh&NN2D+HabeArly8+>lR3~(4zKEWOKv^@A$i7YyD zYFawsbSV`p=vilddJQ@NEF6ElC*to+Or)T+m;k3A85u!Bq)tE{?}Ii2hPN{`zK^!H zYpS7uVLlK!1h?%L78}+qZA|I4qLa`o_2-EVbK}@8-}a7g~E1*|C!%gf(w; z+|#wx(6L%qPMIhM783P?<71AVM$i2gqOLg3P%=)-rC5X@5u9=k?Mw;EI~Dy2sE57K zDk_{0--i^S<31{I+|@xO7@*7$3%GGXy)9&!>gmCW+?Xxbc<)gT^U~A9BoP<>?c1l? zTKBxN*yEO3slXnkr249>x8V~ZqQ;9pJrju~RS%tBoI4I=m4A-|EQpljf|b(zD&nph zPNV6S@FTZLBB=vlmh<35w~~2+1SZZr-Q67wS&Rth`kcgq>S|sHn7kxms(~8{iL0)F zMH6eiwhs`Z(&ZSeg{{fxlPC1;?d{y8w_IH>$Ww5MYro{|Z-X;m6W9dO0>V53Yv~>6 zsBU|W=vy)*?BEy#bd=}|awIr>7!fR}{`juTVrD(~cL(z3H35mn)^4SB( zSnPu*5D^yE2 z8Dyt+P=969SQhN=u6YsRjcY?#&_H6~|JIV2xakug=dxLPEs*doANkk|wV- zztgfmM=~7aStlBk%k`RU3VG{Cfs#g@wUhK0n-WDgMs=x1NL?$D=Q)TyRrZ_WpoavB z9Etejq(QD|C|@^eZ0zl#T#VF6y@9^IjgN6pHi>IhONpK^{9z2I)gw?qyIGiHOAl@K z<{I5@pTa#q6B!1%6clM|D2iJCauMO-vLpD0IFt`=Bbrd{$xJF}F?T5h$gQlbBIetk zNQAsF{Cls;ZoLU)3o$5A2pc6xrxV1(HRy@?H3yia!L5Ietxd2Ldc9q0kusd$ad233 zvUK*nJ6uD1dE%*uv8%mS0-1{O8fV+QK{^m^7_=(B!5l8V)ziT*v79D_&kCUs>;XgO z6fDIL(^W6i6*gy$KpeWYh>;ArBPc{5oB!Y#R$$YqwDCp5CM6y;8!Mp1!^6uMHU*HD zp}d1bKo)xh znJ{YIy`dLJ11c;E3CghGz&W@}%Kn8?#1HY%Xm^Pl0I02j38AN_2feJyVH6uM<7h@5^G zXJ_+kYm(6DVnO{SV%Av#H4jNSUnc>eCE_=5S0p@kw8+TFx8*gRTm~!-VrV#Xyd=w+ zPR&efYigYSj~vqSIASp_0$4E^$_V$w>jD65GWZ)C;0a>kG)Znv`-^R`R^$t##t(W(B4#-*ta3AucpD?_6R+eQ z2+EswZhz!Q?dtk>(TO`OXw&Sgt&rdbplGblz4~<*>m5u?{ez`;2A!H11%Wdkm||AD zHN}O+MQ*p#g7$m!@!YmCf}R4;pFh{C_ekjL>!W&pzBk`2N183*e!f?Ka}^3I=HW(m z{=(YYUxd;(59Q2m>vZ?}B7w)AiiIT%O4W*_>;FUBynnw1l9Xq#xIa2Nt^&A`O%j;? zqq#O!S-f94LoE}v2XGbP1wsr8f=hFV*yV5Rm>k0%H*>f@`KQ#>{SY#QCL9zT2~M;( z8g^4h$5((lY?f185&ix!A#p3+ajE?uFmP~IAc)86r;@4U3mtKBVWA27dn~_e+3QQJ z@Up$5u~whdW79vuJ%eCnQL5ev`^9tqvAXsePkwT24<$YA{|tY%+Y*z%gFOg!As#CE zDld`l(63*=AoitivqKu}7eic20L8FgF-`iu$IX=^+-HUR1qU!&P9}^gV0?gb-UFS2 zPFMgP%Yd7Wm6aGGga9^{0K`qzI7dK=fFzv^&eT#1`kL ziczA4(>A*=~#?)ncJJd#( zME)kW&(qIZB)-d++mS}B-_(6w{St57=KKC|M5G9cA+yUFzcfG13|4!wL%-f@_} zL>Qv{GEeZRjcP+F@yuBvPb+J)rLoaVI`UeZfs}O*HM0JYgU4an%UDoUu$(P^3M1>S zq=NTY+{bsfyUfGNE3iKA$!6HCa`jp`p|56N6v}=PO&tid z$+dNkZ`7sI_bWxx<;y=`iwdUQ$m|NNv_QBpg^S`19%{1eyNk#1Jk>NZV#-K_BqU=L z8X;H7%St3vdx%V(e~AY3PdaoX$hZYYpC=q0u876zkcccV`pfe0UCh3sJu@rK8FAHM zys^1q`j@iv=X)fMm`=6s*9Ic#Z@e3V_pAENLpstcGyETqW<5@bG5!)68Mz;3hp;~E^Z!=6uzYt6_tSD|Z{<@9JY0DtnZdaBDk3i|Ti$8qZH(q86pdkpluE0fmqTTa z^6%Uacd6QCGL`jzzj%_Vksw18= zp*GfoB{{gL+3`qn1$*ypHk(oc-RRfh? z>S+}#<=%~aexzWXoRrb->$)^1mXyCFnApP?RQc;3!rIvGWZ0}7}6>!1WW{p(b5Y^tSggR~}u>Xi_C zwfUL4jp^+J^+(L?^3CBv&37_o+bVttiUT!J>z_X#L%P8hdi87~#9s>?wly9w z>&QaSL%wPXgWp2)wH+%l9}oZe^Cx+d?BrBIQ4!UP7vG>WE@Q+&FA)+Fa&q~>l!1_3 z;NL@e+L}8>Ho{O+oe#S?5qSadURxJWc4v)&rUSzobGnM+Rrosvy=Y|*xGumod&ms; z$}ra&-u2w6T&-|D$!j-;+{`l*1WZOmoC*qFKnnok=qsgJV85vf>#7~d2;4+)fCbQ4 zZig*mPRprxaNkbO&i7qold?1Q+8lA$203nzO7r9}BwMO5vWaJ$I=IzL-CJlB1EY#If}a3~aws0&Y_X z&4c_FpuI%b9q#yq1n1MeWU0!OloW)WO2%Q1)G$5$P(eWf@&HClDM7A#pnpJW{I;vB zD0jyTxAw-3lls9xCvOAlhX@i; z(C=aTel9dMfxdb(vk+&3a1L#k3A&zT( zUce!i;_BU`+g7a++5vpj0EDmr*0rRF1@?WgVd|2RlQZg15&P5H3ZVoxjgk*S5V63r zvqEe|h%rGFVHcFmM(~j3njCkgOl)?qVc5CNVT9#Cs0Aep?J*=qKsaHEN0@L{gK$Cle@-V{w^*qeV>Az9kO1CpsV100w z!3WL`l1hkYspa=vo*llaGiPxs(mu!YI8fR5!@sCJ^A z&pIy%(==IXev8U`yj6Ca-~ku%p7^TaYsLM!q#!jN28~^QGbGNmcKG;f)B@ zz%u7^JN*Vh29RFGBe;OJi=`>eZ*F$Nl7o=mEx>E3uzwaBqwj%W4||$(v;e@c7XSL^ zOD0$wH77H!B^HxWP;(*x@U2??h>1Z+ZV15wRstM!yTTGVi!*QEbMOrv>F$Yi2ZZ;AGh)^+8i5k)pz!rG?Xe#p@1@&yC2Ly|nNrGUmFI6#O~V`HP;))*d~(Fu=;2>9~lTXi<-I2@MD>&}XlAZ3ntWL>yX=f*rNOv!Oa%@FB(H{8v%6 zswE$yJYBq>+qpaXWZuoG|MykD1Ojq+Lk9g5< zK(`dQ*eWI$39M{!hrWLY2S+@8a32o*A$%W@a4>LjIl0^5bBbVEze`htyymu${BC~z z41^+xQutU$=Rr5|fM}mvetXkbkJqe_%KEL4gwp`xqI$*0WGN>Q_dg(9n_c8vXvDj{ zIfw-hC5L1Yj(Yi(&$wW*v9TfQ6o|%^cI)^EwfGVJ-@O_sZxhR&4x(4fEB`wS5J?XO z2-Z`J(0|cqmK$@L@nu z5PPfj-{TDxbaeC~Q~N?4h1op&chg(`c5VW}BAqwd%4cn_&5+D$dSWZup-Fqbko)N#CDdLcfFIl|eZ_(HAb3MX z;C?V<6dFBx&s|*HubpnKCPTBFNkBm0Xmd0a1n67uK-WeHNbBiILH{xa*AZDL28JMz z*jPZ{(CU0lC8XxiQYIodz`340(fPCaCg7Js63j49Z4|U z47Mnky@r5mo^4Ayu#;lL?re3bwpqqRD3GAhAhDZ|eS@tCv>YU$p&WGrfW`)zL!iUp zJmULMvOSO%$YKF#1n^;{&9Zc%jtoF+mUmyVDC%M**>tgg*qvVe#J(XiZ|>Yyl9UZ^ zTN0g1`gEOebVJm(Xbo!hRK4d-=D=UG^t>jgy`7yML|6`T!L~Qo?f`z)`%}^3gIk@z z5`j~PaqK^0ZNosQO0}A;11N?EkQ{VzZtDdfDS>gai5&XnV$KFQtml8P{&XP_0Sa$p zJ&0kSf$iSf+ZzgJ>?VJ?f!tG3Q4z!50HvJ|3@z6U9OGx8YPy}Tr$d}PRVmZP>4$o) z3+R_MwY4N1=ER_HD3sjKkWXKPdZgF=ep@SES(p6r;?8tQ2|FCX^wG#g)DlFL zHCVhE6cXooU!uK(16HQDfY2Ba#~nfNqTjo$25JIAcjNSQ0+4=aQGRgzaD9Cp_BRNW z60nqD^E82>0?_y4S|4TVJ1dZkATd@1+Zj|ElK)dODty<7a(=QK>hJGwE&2|cGx(+f zpcmOJ{lybFTOt5)Q77kcHx@wASL8Qx>AQX>{h{Pqf!~7_#pbXi1JM+_v33M~1bZWU zI>7wE1BVqxdv(&483P_2V#h47!C^Y2WM!8?r~)`Zx@hQ5XabcP2_YDQu?4aS;;bFS z)~P`df#Puh68-9AAG2mz5SUKD5ab5XP7$$bhpm`^ZwM*@t%}rNgbm>-GuNG_1T=Vi zbR=gSGZRUp!*XsO9*%^{(+8(N;XsL%8#yaIG&R|n7sznV4Kd#0sVtU87f<1wsxNGW z0m#ggkE-c3WkbB8px`HTSGaF56tN9XVeE+6KYVMSxirkA8wCJrw|%!Y1tRwd1lsgiB7%Km+-jp zPEo=uT|74RXmfF9p`$>lG6diZS}ycj%@4RbX-b7o`wMOs(0!tJ@6CZoq+aLd98&P- z&mT97L#)mp*WV~bY_K&;!QVhL$}{^L+|~BUXFv*$)`3k=RzI4p77F#Yzr^HC1f{F3 ztu5lHDB`R#@Q@1E@;xhk1m*;}7-F6&uTTUbeg9iN;j8%J%{cF2MMl4b)eZI>!2jP3ws2Z?^fzbM55T+!e+U%Du6Dp- zg?wt;!~7@ozo(ZLcWb!Z+hbqdn9sQDNED-qhrdyAs~*;VM1^;{aL3I#sD6?l3+C&v zPXu>-t2pSTW&zjUTTv@;`l&r)rPWFm$5Cf~fQAc^M1(zM!ltzhXGWoJgVSPN*ur0L zYzuS$^)QoPh+(a=43W*Z+ZbwpuWMt&ia0BYsfg-g-;tu0&JgGBa_iDK4>zD)K3s@u zPb~FcL)H!JeJ>)q;s$SLr)O+z5umicdqMny6mhD!7!HoZvpkE{Xd&L$k<)SEiL8zz zmF!gBZ)0IR^xS*8@j1{i1kO=nsN}xBM?%sLBU9<}kB*z0yDLdB37$!<9nHMF%;lBZ zZ4Z(5G5fwqZFKFlg}??PSxNL5mNN5RH51nH_E@M_urMw7x^5?@z_6o%)y~Cx|K|R; zgoJL`1d+YaH|U~gP$YhuQJQ69MMig2`4oy^x}D&xRL z*O$&NC4XxSTs;au+pDt5^k+3{rmnQ}5lSE)hnR^3C@;d84MmRh?YQ-+MY(^cIRJ;{a+5kST{exK94S ze3~ZU;{E7N$Q=R|SLhIkmHk9l6m|jz|0wtQ7#nJNSpJN{i;Q*|y_0Kg?>; zkWea<;%}Bv7Rtw&o=xH=?XT>S?9yFam<|=N3g}eqhG=smasoOr$Dxo^*C5F<5yP)m#SDS_K(a&E0cqd4z)VwE;^kHOukv4t2VFF(D7v z+_!Fmx@zao)5|Wwasv7LfxU~r&?Ncqk^7&A={K$#ReEzJ1zon`(N0nOj9vJttZj2; z*@j%SNKP?xi_58IDm#@azD|uaKRO`#r}^p2}+P;V#w{S$>6*A?Z! zn)*xIXCyR+KVn|$6#82^24glQND%cQYrndXHmFhk*5D7~^Zmoci|)Tq_JkW%ZLLTC zF|R8W}~N^UdVO z?^&8$#MwyL@!E{|4^};Lj#|n!<(rmEkeKt$3RcZ->Ff;L+A=r#Up&1BIF{}IKdz!g zQH07$g)&kZkyRu`r9{dsBO!awJEDw`tcGYwvO=4EKHu-xIwxG!)tRtKN=i#hN2FiT3~rn8e7ll$Z(*%Yr~lHcYaNqzHq9md z)?e2Syt+-*8R*wjfAji)i)wx0T(c^$ zNS3_!?wVyk)s*TPj{Gp6e57n7E|3+tsqvr6%WF15C!HrQV7k8X>E`LkUC#LP`->$^ zGZsaChR2^()zuB4&IM*2-USVd`FD_s4;Z;an(k0vj$(Ckc1}RKFIcCn{vQQpkI_$T zZ*afPEU&o!WFA}I8oTzQuUq{-{*Jo^XO&A*{cPerv~CLZ=mrP-%!~HERM>AwBX7>S zIA^>xaAo(nIpsy{wnf?PmTr;%B#PE4?l4?iet{JX;RR?RjiCjQV9dis0;dI0PWFY2 zTNve`M%QdlX8T9wG7zS`g!w(!=Fdip*5uO0#>Sx~kmdvihZ(dkR+gPDnk3WsLd=YC zyZHwM@SqijNijml_REX$YQ6iPT_hY8;;%*C&~_&>0|4%Q*0wAfYzHU$Vh#NqAKdl8 z9X9OPy}Jr6@mDZ@jo1?@iF4-SgtHDKh0xRrz(xXW1C00pKK<`$2J(Z~;Yqk#1OHi4 z!BiPN@5;-lcwg0w!j*qXD5Kn_ro-wl6FN^wi>hd6Th&7+cpm5i z*|fIkye$?$Md=FqLbVmVCo9UC+80V0G21XQGMAKjgU!7{QDFvz}R$I>rk_Q8$0|39_;K3t@fF-BV2-XM`8 zA`asXNKf5-r(od3hb!^nYq6gbaRh)%6F~BkhvnC=_?qKda-4MV$7D*xrC`hs$#!0@ zD^@Eu9H?o$!Qm;Lb)jWQ8AC1s#GHK_Mj+yqgvJKleq)qx;9Hwx2O~p6@fa50 z0aFR8Z+V`VSBMU9n}ZO=nE(c%@>u%wkVDSnDOz7UTyh8#zQF=giI#T5bx{9T-(UCx z5+NRoLd^V!KPDQakTf7>WH%x*fKd{WB8O%TPfG9>>NXE4gQJ#oojLfBk(&>2CRp1J zj-D@;?D)ak)l8Y8O1S&&MV``ts@0nb{RdhRJTgQR1kN8WEo3YQisss*otlrcERx zIl9S_kpOfz0wK>gvOq zT7`wq9`N4)@YZs>1hr>_?^fogwgb`4d(xEt6gK1vyxw5QP+GI~yX(=MNb3@>${j9o z9k)`L8agJjJyLHJ-AZ$s>?TTPz}dH@!0w?D#S~+x)cez19Ez-CnCgsxqB#H)#~_vg zd~d%&KQQEB;xA?{54diCCQ^NesP#p+F91ZK&Sp(-Xz*U%qo81GQ<36=7846#YHI2g z%3zE-vFUVIbf&inwtICpK#kEet_b; z9-+~j?>Jh45!*i`un9F3#|1uSRQabD_#g-I zOGqFPL_*CPBj@>hD4vY`0^)t6`Hwhz6%vXi*e~^uZAHN z=v*L<5wu$Yyley89s%e=XG~WWc3X<7dEf;^G-!X4Xtw-&^=6mMs*%>_RyOw{0ml`07KNMbI)ChHV6VTbg z(yDRCw3d;G_-*#w+2qMTe_mg|-3rOZA^oJtNYsAQz9eC1`J& z?uZeq8;bJ7BF%t3fLt`fT<8L*6k8sBoY%?-=jaTlpQT){mNfG;5hLS<=mz7E@YI#gg1h)w)3fA<;F@Yq-#-aMhS zLrWJ(^3TfANw%zYPG;BOy|pmbUa;`n0=C^f_e)FA0j0_$0p0^))-EQrAx3ZFm1lmZ zad(l*eMZWI+g-g6Pu%*M^s~gLQDw^sk?%R)-5*CU+UIM2R1=x!En4{}2$DKpqk{a6 z2!S9|+D=g ztpz{0E9h_ViSC=g9PfxdXA0(LZSz4vT9tC0d1siuj_!g$KOtciFca#pySX5GUQMkM zb#!jdVa~1aVxt;nAhflgcS?j;`iN6MQ}LGxj;bDP4;Ak0ED2HBsrIM_f7A@6vZSZ9FsBcF{VNFq2@rJ{$+h zoeDk#JgR%Giy#?Ed(5DhHb z*FVhz0t5RZwe$%`CfhKgM>AsP9Hh+h#1q#maiktX%JH)AuAT5j06G8yszIVQUP+^KbX>$6@vja1-n% znioW%iA)s$L^DELPBgvHbmD2>j0Cy=BQ-wn;Y$a9x97!?b?Dv&6$NvT?cRp80H&>{ zZS(&kDkc}>fDHs36!LOlq6lO4t;3+MUo-BAP_8o&x0!O24{=ra| zM7#J^vT-VJPt{_M$+n`LjU>dXJ>+DQ844rS26xC=V0)YI|7l=}FrjVO{y|*lZ z8@-WkNCR=NQ{xB#L#zmv$~@>So{O0&fE5Oz@+qAu1^W5IX7=MQQ#Xm}^69Z5M+PpJ z-VMMfa$)qqU{m*8JP2#+x65>NbV-kWqbxAtZr5SX7u_GA`fNeSF18vff~JF%Lj;Y{ zP+~&tepn2kKD~k^j@<+Pwwpla5oI}DvWb!4{)b22WEbq4catU7>N;hO_n%7EurahS zk}%gaU}a9v&ky4FsPEOm)?iN{c-11hL5s4zf2uT6$VT`%3ruG8{ zJYrx43qbG-km3*$T9v!DyOevmv1}k0o#|Xe;sPNi1$Vf)myp)L9Y)Z0=t_#nGpd8y z&z?Qgg=iej>n?2V=nDwGEO^k$Qw>IYb_~nLf6DxLM`Ig@1>Nq)qNq>fx?e$e0Syc) zzsg!SW-iDj;MG@{ena~E`k{($Hoy)+Oewe7}yicolS)T^mRbAfJ^x;OuIp7f zq3H!J$TT3-RTR$mmb3)DRT4y^fBet z)#twz?k+rlMMOY#h$cFU{sIy=Tc^bit^WU~1#kii2731U9Wh|3f8Z8Ht$=3ei>+0P zhAi*$p0w6gp|Z|}E3;8)^O3Yk27)~yK6bM1w%XihWs~Eqx(R!CU4x~y!Iji@ObPmU z8ZX62p2o~sJ=x;j+MMq1WIGf>uTZoa!m3{*K+g>{&M;y$Mz&m)6hHZro#r#oEGK^4%^&c8E+)7 zIok?5SoYuD*TOS%A)KcBMtpW>feuT(bdC%)1*-bAE+e#OMrg8?PBvmyxI%`e+VlO; zCcn1Pc~_Y-n#xkc_)&+Gcq)6mR)x8^xONAh#p?R;O#EF7UhTiLN8P_98*smVY=O!>i7J1Is=w!)HLOHVTTGZeIa9B_3gMdx(?Tmx z?X{%%bn9e|8S{JPVOP@H;Ej2rTsNDKo(q39+tZfyUN8LM z?`f?pq4mJOx0ok5TCeumov{09?&wFKf8>>7JYeTXC7})lQCj{yF}z{(f^%GRn`Pf~ zgnyM}yYlWVdc#8f=hEywik9}zuETqHBsV*5;NI!NzA|p}$g%Q-4AaX= zN;3T=UH_aTky<-A?nN<*`%>9c=Q$Ob$*}KleBXQOn}5kL&-A1NQ|N7ts`@swVPsK{ zr?JF2c0ao+XpN>QV!q9p&qjCCNz|Hul)_oEBvLZ885%QMeiiXcsYnQ*Q0*YmmUtC{C%QP{!sLYT4j#1 zW+mM&m)CYP-R_g=Xlm`1zC2I(2Z0G;K!HC4`m0`yhqp24J>rnc_&wYXl;H*?$=r`bCCeRRAL64&0U=`NZ7@wWL{FzNvVoSubYnHY!@C0;rf?+?f-p*m- zX>ZT1NnvtMNZh;3ZlTq?VS&qMN|D9hl!x<``$M`fz4T?7-t?43_r9L`@aMw1R}wXy zDr@cSdo<)XQUHgss4)&LnLE6{ms`)KrT(WQ!ajv0 zZ2e>p_nB(`8?fv)H4Wtzz)lOr;dQHx*5=f{o!!6hscauG+WPc2m8W;ZQ_f(kjr`u$ zryMivqQd{9h@Qh7c^RE2%)SgY>JmySe;n`4j1W~KHX zv{=BhOpLOCMTDdJtHiRNy0zR2djC=^S^1b+tju{dsk36cYUqBdy>i>5VQC$dU+5rs zYJS&;z4uf$3CLfbr;56t_|Pb#A2-hxNLx_E7XWmK{1Zji9@_Fico)GXg8=JAJUA?L zL}E6q_nbvVMWE}#!ov@I)H`#KB*->sUYhXDc;{+T$S(s&8M5Q$raVt`Y1>6rN2#^0 z?b`Cgg1j0GgpeIV3G{lGuwV(BB6g)gyf!h4INb^pbE_us-Z=F;7rdU7x_8w8%mrR~by>jzFl+cX}0R4{b z4aTSv1~inw$R3?NfJQj0I^RdA7FgS{yT6Xkxy|~hv5p>FWXeS?C)7JYc(K&fhmJKR zTzIAcl7bL81B};la~cCDa|kH5@3kZoz?B|Y7!s5{4Ba(8Sru+SFs@!}auxypVyX*@K#gS}Cte#y;`o*bLb7PR(qA?@dZ@#o%u zxvc_9qM7lES1H9{#sJPFVAqd1=@na7NsS4o%5MNUuVA#mOl$5+yRAQ&rO!`UK z7z_2&XfJwgj0g``1gnHwpy{SL=jQqB$PKsuQ>Fv+q`%mKSr;bqDj>eV7;j-5B`g5Y z0>8W7=HTpX3dsXu1_Q3o%*=cr2qs3KeW9`oyP+~yO7yzw*x1tq zi!a?frDTTpo=a=s{Ce}~p`ceW(#|16_3HuFy`!V5xCv62*^M!lFvNmiZ5$X{(9E{! zy9fUdG4c-$9mcml?_5NQm<5f=%*t8;#kl3a_yOytfRkH9i+m+E1vO1g+{xEu_jX=% z=5t9FNhr?uK5{nnvK%z!gf`_eEPjYV``c42PcUaPG933XwG-NJw}r6)EP2c@#~(6q zeq3O1qPleHQca*u>pB~6kA&s#Z}{A1h@?bLz%A6z8llY(`+Kt2t%?H>F&&IH1t_LO+dv-#)6e4*H{!UuwU+6SL~PZd*hRh`OSur}!6T4`8uA zObD8Jjk8}NhYOXbJVmAP0bH35?;T2HC^U8h*2lJTZ4-?y37`*IZLoW4W;f2i{5n6G zf90Xho3g<=07Ugj)3C9%<>BLdKxCo;i>9)f|ECG~;~VnXg`Rbh_O9-|IW&m&hmi$am8xU#^IP|@!K$BXNRES zYf8MzzcA6IGd{C;i8+haXJc>L)uXOFXE+i!Z|s>f<2L`|z)LPyZ5B6ZKs{mo-Gyv` zv)8XbHNJL)*yDOXe<_=>LF$8>qhvg7awU==RIXa{dSWl^7pC++WEe8(bSp zI(zo)^nPDHetwL$oP)C(^%$Z-G3SM zs`i|u+k0~A@D9DXl(Vg+)k+r|dA;X4nsnQDsUB_{{^;-P6)BT)F7}^SP-w-ohs=B; z483=gM>+GI`YYCj`XiB3i)gh#%kjjY=IBYq&3{16x(&V;tZWcfs71X)Lk~)Cmf-Q% ztHSyw;PZ>6)7R&t60ZQez}VQMvu}B% zrC-FvaQtgcsYOG!49*9e5}`{3BpV4<<4wy5WYg5en8wr7mht>F!4D(1Hg}cK2m(1E zxGd-uRrDYD`4PcC6UFOZgjyFx>S1>F3BUmnxB#)~f#!vaM`ccI@-o=W*nPFA&5@Sg z>+6B0Wvkp!dY{W*9=Yy6y>szd?b?mvv?0#ohd^4I6V$i~WhRC`7l?w&;aW2 zx3!)ls2G?$wiw;?yUt_3N@3fnd%~+qsGEr+NPw;iE{M0Zv_Ou&4dyNa#sFuUiKiZ& z5)0`uN;;ofY#lZnA}I-HW)why;isMXq}ZgkC!3%5=f^7n!b{}5pDBw0cjy(Q=2<=5 z-;{V-o_*@RL!a}h&FUb%Xk4h#(5$UQ&&YzI{gbP;*>6Qqkq!rqEoyoJ5A-Xpig^j< zt`D0eAr2rAT4gR*Uvwn`tnnuHPEPO9A|RFVH=k~1rhWTb1E#JHStr0b|Dm9h#4#iI zdt}}bqYx2|3DMz~7qj2YS@#@4RV7Szab!tJdH$eV! z=liP-g#X3(co>cq!BHLPkPZk6)}HmKIbNf!sWj6TMJ{X9p3J1qaP2b~5x9mf7W2)~$tg$T1f03b2 ztu&LpGGKj0vHroDS+{^sGd;O$;zw?Wo_WzeTHZH`_MDd&UO!GrxI6ge%un+y+WdjZ z@jNZZ!;vLgtRhP{l0p^gR|f+k$jQGvk^-30#23j=K)rMbUArn?E&o(y6*G+1G+WAL zsjVYaq7~OVvjjVRoQ*#RXS&|8(@~e?7uVb&u;Q8=9-Nc0H8Nm=vrZ@Lb1x^wkIDE9 zyK#iJ04+mB6E$NLS>52cg3ay=1*?-qj;yW+%}tsXY!C&Q+@KO_EouOKZ$V0zJB zqdUt1{g}BL#D4U4i6-ebaKEq{7ba> zy`3Exl-$#e?e9f=-lySeI8Ilv^=f)+w1|Yso*zmZr)VQj_v-w=W_tEX1kFX+oCK*0 zoF*adqk{~jSl@QR-e}MG{7`hvkIot`%k?VkDQK&kPKILRooR;?myT|eQEt|9e)fM4RrpC*Ylj1*e>@EwW(t4D} ztR((5i*woGMcyrTWPQxNmuvRg^JRbFqIfx+lo0=!bNAb(s_Zo;*IT9s?kSY_qP*_j zw)t?IiVu`}VKrGF2m+Xx79e6E9H?;D1^(Bsp}W1^Y8f7Y@-u^~f{dQ$$1TNHB<_~p z-()bDdhMl6YtEAx8^4yY4yl|$_T%c|HDYn0jO*#=M*8l@4XH1lu)EvhN-3Ffsrtf83$mFx)@$4O>K|rm?aSF(u&!{Jy;F)0 z6k_uHSnqZH4{n9}JMa5I^ATp3a%NYSZjj3D+k8-c5L_cVAC}Uoh7Kj`Wip{{fq)({ zL$1RwopWFdDWbZta&i! z>HW9s6@_k2WeJdBl6~P-E^n!4zpFyB&&wh1PC>{ z>5iN%^&rZ$2XI1y-wF7u+`M@1YYftY77t*mldHY;FAAE9%#IGa!hW_Ut*2 z^m5gx2ph`c_>mQ|5bULuqVTx_i9ZF=Ay8H);ibOaQ!B~aGPd7vSy>WTrF7;!vhH?^ zp;umf?KqqK-9tR*=fRGw2lYjF+xG76G*^j7uBG&GD`|zcxd#%PO0$xdCmS~1uFP*f z`FpF+W!H8@#9;^_vdKYyy!SOdoD(F6?g{G7Bl-nZcvi;fJ<^Yu^{M~h{yf$^%-z&@ zfnWHr_g%&E21-uzGELtkXNYO%6`x}6GY`ubnG4L1#QK|=lV&zD7>t15i7>jgPK%)zBLG&DfVfd4S(cRa1F^QuJW z3nWTm&;}7XY%9lHZcaW|yVLrbUpDTULdr_8ZByRWBrhZLpVqpsL*AUwe#*{y+P%9{ z`0!@OkPX>7Y_z{+XBVa~CmQ3lIYr}$W$THhivmdxGv9r9{NZxsc1^jk0yt}~+}Znj zCeq@3E^vMVKtpmPkzEIHKXz=@II$6!&kY3N)FSWs2H)Zy(loVT0%KXigx({UNoBqx zvSqKTdu#@O#Et2kC@!nn-%;P8&+{xaU&Uqe<2^;m_LRYxi^~e`|Ykw-SH(EGe;f6sSiQgaSw*GjFylTqD4NIczpkq-{;Pt zor>L0H*u3JKpE^1&{{1W*fE@u{po^NPLHHw%eb{_dq9bn{zPaY0E zo5cGzV!tlZCXR2fc3l{~e=@WBuTM?mR>f;>iK&0yI95g&otZpnkxI|7Z;FTWX2q|- z+~vm%ehFqhx~9&-chG)qsc<{&vT*2SxWRiKKZm$8b|TNhf4^ex7;&id3Ie9?_e?17 z9)tR`kKrZGlQ)U?#=#O!+Ixdy7B3f?w292Bcgo#igW(_gL7}Pm8wrInQoF`!iho2m zv-q0VpyRl6-D^1QZScX-GQ&Bx8d5}y;KJh>l5NE=n%62JrQCA;^?yXW)$@EV2R9j; zhG*n^G^7k3+Q9Z^!oP&xhj5@V@5_0T+EH+A~(4dOH}>Y|TLBM(>K8Pl9z z8dlP(^tZ6sWR;R(Yj*QY{DWVI#b@5pKaIS*$RQ=Q<3!2>h3Ep zqbb?6+qMa*{|16_?9TUngZ3~{5!fceAz?w;LmbaW9EJm%mdZ0W4E941rMw2#1i%P$ z*U}_uy%t$aHRI2rBQ{1xD3rpwv0|?kj(tn2mK?gUV#`L>&yv?WBA-+=)GO9G*}ymI z8)W=$-^R&9M2>?87*4+mAvGjIjc2cKZAU_e0YNH(;;lltfv@`&G|y0=B&gFBK)(qc zl_|!80vrN};zOwWo9-lkU$wKd`#9x}+uz0(VY7TSYik?p=2J>}xZoQnB?;UN-tn0< z(i(l%$@PgWOq%dsRzX2Yg%Vvro~iW!Q$!r!qLPO?3X(Xo5hH*hSOcW#h5*+jR5FnN z*xK3kq2Rm&*9iZk4TP|AXlIT!{1U}4GBNQ~+*yW)Qz%sJQ}s{c>!O2J0c3}_MJ z2_-07z%;?XrL`yH!;MTnAk1%YbpS0Bv?SrD9$Y0yzqEA6#5C+D zXw~99(9B5zXBlMEQJH$oYE#1nIrUl;~g8$&fRD<#u+Fw%$#UzGZ zF^&G`nn$uN{`u7+dTcUVOZ;~pAjH79FmJp(?Z3yaPUMGb>AU)2y%1MD6UQhJcSu7( z)7#Z%{I@vn;WH?*aR16Xq^%#_BYqyFi>To@TDg_K2jnPAMg;|R*BWtM;M9Bvk1RIX z2Uv&%a!rLBSV81#KyqD0pk2gqEdX$qaEl1M6T-CKYW_H5NKNNKQ0DsCPf zAp8SF*zNgCluCPoa`-#%1|Kq=45ChTAbmTC>5vFmL4J(7+0nb*18%L5a&{XSWwBsM{Guz4$^9G9z}}h9jKH_ zk#vFFGiiEOAwtu7-X5rTxuLulSrRApB!DXbNct3j-6x17@uI|)zy*2*j5u(<7#2Vx z=Je7f26TJ6MIP-Fg@EU-rRqLQDAl;GABTn$<2@mICM4In_WY0~5D+g~f7d5|b|t_TG8?@m=H zO>*7l&AnX_Uqp~~8&9F*`o9xG`@zCdU*5~XiYs#(Uj|`S4ru`R_Cu8bVcyY0AuBXA*CBuGG%1HZI15lliy!h49fiIuSY z=OOnDM-uKxSY!Cpr&R=piK!#PGwK!(xcL45X#u#o)!f`pqA~*el91yAKKuogxjn#sR=o zgc*f!n_o*p3<_+Grf{>_;HWTOfVVUBLndZ<_ZL z0s({NMD{|AWEe!w#Mx#5fT;+96_JN2zc$AVIt3mr;>^(vx2?%A;U5KX2!K=-jfq{*W-S4<-GgYO}Uxd%>=eZ zzjN3B5c7fRs^$_8YtrPuQCpe^AGzyG|W8x53PUTX?!YwYCT zioWPIS#o@`(Tqg8nMmtlQZih%bA^?u{UREs9Z}yZXPp%`e*WlId{MYukT_0NGgM&G z*!fp&kfOf6dNaPTk&4sd6^jqaMRp{1f+4^{Jy<|TRSb-fVRF+KOy$&8Lwc9sAC8+%Ske5cP`EGQ3>TVAkTREc=a#TARTOrWo? z8QN6=qlycUwfS&aI&C=&#dD9o;y=pymwNX-gkX7lpEA+@F}b!l4EmnRvSV}eXES>* zvaKM$+F58=k7NVy79#@qaPvAGL{9;=fhDo>t)1wiCM%&o60VNai)GBn%>t*hbE zRM>xOMPYu|QfCg8NH>L~!&_aqdHY0JOG^;CoI|UC;LAiCe>DuiDZ$fj-`+IK?O`(SSnUWtoq=(6ldy}%3+Ufw-wYH2cNfRztt5vPWK zh}mc@U>rApJT0`RJGsdymY&<-{BcoXVHPS85o?s!t@pB>x2s=uRBypW^_33%>gz_@ z=wC8+I*yj?>q=Q&7r1g;Ki4kchX$wR@6MDldIqb?#H&~S_v0TAOnp?4u<+WzMcqkd z&}-Ivj<@y#P_I14B(Ml(hye*&7@zDPNq9K3wpbhgek()c+=%xR+}w>KzhDeMfAFC0 z@V9_kK3DRED<_-4f7~s$>!1& zt&U%W1m>cP3sXzNmgDxf9~nQ`r{V0ab)NFy(ox)RLpOQ`!If;{PrE z|NAEKMU4Z-l>Gl5iOXBdajNxBJT4!1n{XP*35{`8YvA;C90l_6)>bn-gT#E}{J76Q zt?;vnEd%io(40P`Xy;tbW^Bv$!_z?HPn5}Rn$yte5bKoqO)9m1^m=p`!#mVPssb90 zzdVA^xE%Ya`hT;MGnGYYa6q+?n5CEA%Kx6r-d7jmxXy)gDUWUblzzQ`d8^y4)7EZt zl26j+@p*_hFnI7^)RDQFkPG}_lbEdVT3alrBTU`?@9pr@?N!c}tYR^7)4Bdjmgapv zvn+h;#OJnoK|bTWGFQUn_iQ_(+J1bz_iBvB1Lx#l7)7Pyz5egh1h#luH8N#CYVWYv zeVtT%G|gkAfsMMd^hU<19RdlT9{MirvS4t#`AV=h+MrfzEJfHhs7j~7fTM1ByU^zo z_}J;VD4!HvT-4LQK`m|}VKn3y;5^Y^4g-zVLEIA7f_?rbUhdCzY|niD5ao!EO1m{T zp{ggFgONTz;an6M3q@k2u7@!~fd#)za0_x#kciCpV!Yd3TA&iT@Y03x;+@=a)!ZZw zv}dIA@3)pS^^re0h18C3khi~mA#79207Yk4^8LYGd~?fPLGwF&WciCnS~i)G_3t|2+JlcTq&slL6}j)kIEjG%s>KHWznz9CYD=2UHz zr6lC0BO_)izfl&xf7E2J7JGeJ@aORdmW5Q02JJP!&am6Bf9dRBGHk;8Aag>m?I|-` zcNNVYg7>&ZU1uE52D@#b0z-!``fhW=yMqwP^6t^{^J~{Xsrs8-WVCb2r$Os-grqzD zH_7MoI?d&G>1y(yL_H)H3LVth*EmKndQ$aPg(;F3I`kXjD8-(%!Mn51X@Siq@Nqy$7^X(p=TGDlN0dp(2!a9wQ2eNvE6eYeqN%AImv=!DlWo0 zdo^UsmhAq4>O<9aDvz?VUiK`x+$uBPnbWD^{5qZ1{j@A&u3|rHWM?LW9izLyk_bM4 zD?0w8m|M#6?il5M-49^yft4eeiag&I_wAc*Z%X$2{-L1;(Z)w_^kwA-b1vr!Ep*0? zF9$r%x_+I)+IrFJj{;lS*Kpcx4q55ngnI<5HNMpN&s#T6W@{_-YwPsn_EXoz?0M_b z*D-7HDW;a`YVpBBA>w{+|NDFXG%j54So^4u?vi+IG^xDIFOKT=!$*;u!4o<^o=0vbN-BMM;xjTFvWAOGi-B6S&-#bs{cw(rjU!+ znfks~g?TMY>=O$1>7?w~zoejfdVf1hI8Koaz~AbL>A!!k%FLWQQT;*X;J<~6d%~;+ z6SOw0rP-`LeSz**Sgm#jK8!E0i#ENre0Rj}n6&sCvhXF1)2DaP>+j&GQg4*yt(|$( zX#GgSn)cdi|JQ|r3Sm|wtJ3su5!w7_T3gLcvW^7(rplOmyi55yy?t}7XXleRKfSMZ zjeQaPxabNrj}54EGML_V8Wlp`^Y^3Jwm_zn64|}G#Pw_&#}Dl~A!cP;^^!Ykdg1Yo zI2kVnzw%w5ta@)Wv;I6R;;eWoZ-oX0^Ux++k?QH@9bB>Ru6OeA$kiNAF5mg7Il1-P zp2Ow^>Je>cc(K^DmGk2mXeW-4d0U)>6SGGKOoZ$vJQjPuKkiPr4lED_>& zTs|{;ID66YmdH55r46dB+J{mqFQh2OF8sAUBPD0|F0OuY z=7mM9^sh$+zJ5M#7DC(4t8!+FaVRKw+^ry|gl_e@PJH~J_?J2{!u#*a%6Aor9BrE> z`Lqs%+_!p}`cCi*%P@_8#O_&z^6TsC5y76S(#1#IM_0mErCa5w*Bx5cMYFr}tmyH3 z6e3GcnqQwtv|<(F(GwQ=N*)Rr_BtK)W95ioQlY?+aqT^al1A>EXp&}_twyQN(+JR85#BJwU_|Fy-)k-NNL)A!l_=M$$4{a$bW&3EAOo%PbFL+G$z`}#iDzlI1 zkccb3!!fA*v9!$vFA)}Z5w_9(sA&Irjs2?E+d8?%4dZEl#`Etl&a=9)x6Q{<3|*@$arUFT*YHfH?{wQUi&5$$Z7buK!T2!uY7Nzsqm4A;RDl_HKMt0&?3#Oc zJidO0DYI_Rmv1{n1cwjXdNHdgOqhk6Xoe;?hWEG>l@EVI(2e`KSFd=4Ey5V+^ZC^_ z?ORmwk8)U6y1(m8uix=6uY&9gUQOh6cDee=ep=pY?3i#Po&;L zw>xV0T^rYJPFHBQX@u=&s;lCriOV>_VY=*ELoB$NBRfN!@vqo{f~4;cSscd*(Q`)lSns+mlvG z`4%@O+McJWnI3!bwq{BFik$g~NH%AJhVGDYw+tuA>U4Q@=y_lgfwuh_EA z+A1u6X!X=pZ+PN@eA&@hyPc1Umx8o(fzAqZXRkL+*tA`xi`rAHWx7{novX4Sb%EBS zcau@~Y^Pl|<)cP|xE~+EiVND}&hH28Z zF`nPACWN`O(sd|=QmnB%L||HLHs|edPSkOm80YV0bW7~4wUWQCnl)?DFZcgWKN8?z zwp}JN%WFc{%Wq#O-Eh*@wZ!E1x2s&0XQG}}v;`j4ukS3QeIw$s??o2@cuFxbSa^=g(58gJPmCJMOm&2FZGnD3R%hvwprWL*SW0^W3 z6vEW-L^AbDa`ZP_;gmJEZys5Z9vt2VK_j{r9nA_Z0zA3owL`Nm^GTDQK7(H3Mk|Km z$>~vf<(Ve&q+>&WY}@kP;^H3{KW9j@+UzJhy7t$r@!+3%^Ptt7g~ql%_O{=P+7D{k zdzPG?n6x*LjQS)6S3mU1pBxEgnzNhX9-mvdkY*oqPcGzp^xEgpiTtr#PvP-(dY0HP zJdeule+H8BBtO2A|M}6a@^F8e*LHUTn&>!>}(>FVH)d-GD}Fmj9+ccy~gaY z$GC0=PTvYYKk1ncv2Q#0DT(=){&e6#Aj?{y*1<}qZ_T{KZPRmJX(_$(hN3Piil9#s zhH(!$m3iBsVGfppHl}}Cvx!NH-fHFjSR~b7dgd-FR`gdDxd`@JUn-OR_}ZYbv2d{= zZfebKIHz^N^vSO;2PZ6q-yvmidaN(;_*C4bzoTxptD`AJE_2$-!O{IoRX-OU3m@Mn z?y1Z%uIzEiGP3%&#Nr)g8E4FH+4gHMH=emd;&YW~({@@MImFp|=hH^Y`vVp6n>|yg zzx4C1web2TWcD=u>TWR`vYnT*^oj8+&-|(ya>W0?Wp%bvWQ3Ba`&aFR;QH+LEpnQo z`{?mz=|6S#eivF&rL2iTY?tO7`zqHt8qwZDV*u(p^R@@aZGc6yLm2jeMx5?5vb8xUtUVus&jLEUKwlGiL;3X zN;i5q&9hA}n^G9kWk4<$oU1?1I9BO3HeAuawXM&!tv@nua(Mi#+?_M}1v8OpKUUZn z2dnC5xKe1=w@tla>#81>TfEWiU^|!%ygZt%c97^GK)h(ATfE|>EEl18&9ZcA!xd-yM}j1wt86T=G~amu^O zwY=O%R1F#u|ITh??-D^9N|>zibe*i2#}7T!Q8<*!I8nKYtaQMgDUd9Ksac1ZQshCp(*Nh&| zQkA-R4RTk{cf^frQpH`AiH`FOa4sC}@)$FUGm&X6SjbXK^>7{4Aw_NB zE~s0cz3Md*?X|sU(fW7#luRgT*4oR@ebUokHCkPc{3moarDsLHUCM~2pgek$#EERU z9^?83MT%c$7YuH3snzj%)K*L(MX`Ck#Jji#TZDJq`f~QHd+}m7zvZgkb(*R6GW+HB z(7t}|;)f^#*;6BGq<`l-ie{w^Y8ltQhfa<6P(qiVr@3x5Sj|f7<0s9SS{ZJb?3ZM) zc57Lj$%yW%Nn87!zZBfF#2LFzX8SE=zo{&h&pDh#=_+3BnJ->nET(+E_AS3%zIckh zIGHr4HQ`let>(;9@?v^c*K46)j(g>M;YwE88ik*{P_(#~PH9_TA?44kJA1Y*{sRfP z7_&FJc=?6ezscp;wk|RIp>Zd1sh}4dR1WJe*QE7_#}zQSLAK4Ot$nd0hii)8x@n_D;9a@lMjZr&B}cO_XE4%HkER$y0~Rttsz?KHB3Z#RmO# zV!oX@JoFUzWNnu7?TCY&UzC*-_34r>=i=zaqD5J)N7T}$xbI8uW$fKkijOLvPhX$K zC3($%ceROo?fidN&6%`lQ8mkvvk>S~nd0L7wy%w}wq9_Y&)U7CQO|kZG=r|U{=x7} zCaK8i$0TFw_vZR-jkEt()cE}sL zuQo^aq-F2#uD^#Hyqfl?x^bHu*_P5fLHY{Zens`tO%}NcCQ{Ub*Gf$L|%0t4gY%P$nXOPWP?|_I1^@p4+7)84@&_ zcjk)kaJBlj#z9T0aXNnhYW{C%<9EdlzsTJ`Htp;+obA%j-cv?zuw*E4LT!p!e)=kT z!Mb>Oh=nfXD;9+{M84H8ddMq+f_L*$Er#p1)4ot5g?r+xhSRcKqx>FTi_YtRYZllF zdX-ihDPv#V+@;&Nm#fxRG}BfpUU*&bT1n`kygRm>8@F06*kLgH+`474d-ZX5cf&k4 zxpX}R9;daKXCjS*rUqV=4$^8#_)z5u(rmVVJLRhUeDS|VPb;lBdHJ8koD;(bFv&J9%Rplf1EQPHdYK+nU(6ZQFcfYhv5Z#4~gA`@VBd-Fxets`LM@r=Hr? zdv*8f)!qL$0Px>u{ufI8Z$aY!cjkW~$^RQj{!eHAKWOuR{-J)Wh3ju$g+HH8Pp3gh zfZpDW!(TK$n*Rc`pSO2csJ>43vmSqSzg9hrOc}V-&H3ygzV`0=2>l1${{x8)!KVKM z(_`UQ-QG_Beg5vbaoZidxk33VEB4&_-5&q0|5=Q6_g|mX7}%}g_Isz7UOfD&)_-}| zuXi^E_#6NT8ux!WQN$Gmv1$>UI{{0bH?UWfUko(R=E!xBg|HIPdF!jn54swnnBxw4 z6#eDUuA(&7Z~YZy4H%|DS_KkRsOqSGOfIo0h(9G}qvr!ky zA0n+7(kxLTu|ts#=DB*~E{JVB+MMD?MoWMiqNOFodi#Ar)BxQ8lQ12Be~~>Q;j$>! zVK~nuL!~ZGsm=whcp+R+PE}~zy;AL*OcKkTas7NsEy#o2Dyv~5zZp$O>9ad7IRK_4_i*mo)Tx;C|66x58ypCz_8ewn4tY~b4A!f`A?=Gzt` zbt*{Rjirxcjhvq&vQ4X$RTWx>&}pZ{T)DT{0qY-EOmx93?8QSPf42II;jAh6L59Dy z!c|t4($imIyJOy4DO{L>eg9M`^g-|sL=KF#BJs2p>7|GRP82Z|qL;_L+)gB?@&qf) z+%$B3Pz|C8v=UW>L7Nw5|;?|^p3(hF@Ntb+K77NY}v52X#{!}j|SnT609IR(D zh_OJU4dFB4P2sEY=(Q_h=vRaXNlS_B+dfDu2BtUCa7xTR=D9)wM4<@3eY|EuNuss< zAsV#zUnf1yGG|^{Lo`9!;DrD$s+P;{dGmHFft*zT%U3doFMz&TNCX{|c;n zsjolc;{OKuTA?y-Sd-yP!8^4{4xF~qfCW;3I2;SC0Y9_TQDK!<_qxcR9?*FcQBmLq z!%+5wb!1K?v_%)O4zkVXkqfQGSu9)OUh4qS=-%LRGH0*}%g!+>l54z8@y;Pm;I6(D zd#%jLU0CL38lt;6k!T&v3`ToXe+b!`?~Xnr5xcQ!-jzD{AXJefjfoi(y!P7ntkLom z=%x*uBl5<+gfK=MTHoeF)9;*ER8EN3e#VX%P*^jI?+Z-N zOVW5mOWiCR12-HHbZUMK#OY5b% zw)@^#1}_2Dr|fo#dv$sxRxzkn`glU^P~~a=PHo7qf-!l065`GLkKkRWeXOw93*qdR zqgbDK$Kd~`7N7!|p+bY5q#pfvs#3zqk>Kr;og6hfjwZ&QYAlUK9Fd1B4()F&+#7EI zEvk9rgV{f97X>OpO)D3BXdYB>b>u{JI~xX82;y-av`Vym$KQ}JTNq`)hDwAmkcKV7M>0A$kR*`Ne31_Jq0)90nH{e~5YzOGQj~BV z6NH-u?b%0zAOYhvMuV10)+3q%)^ZQwDOovwth0`E&_^~7d=*qPjo@{5hz1VBv$w)A zONj}F+mpCLdm)HGslzKnRRyhMCiU^#nC1bu!w0|P?)xG+;Y>oib~!2x4l>r%kg$SF z4HhMw0uejo_q&*LdN9KZah&_5;X1#H-O3Yw$leWXVa=BXF$V|F1>g6Nu#XsC;VIGh zPlgbAnTZENd1`7Sps_ibp9_m)f|Nj|s%JZ*HNWWt`GYzz$@Una#Ylq$Wyo$9+2IMH z&Tbe>{3dCG{(vJ{u#VaztBZH~;Y+3)w~1_S!$K!_6(XI~gfc|qYjgw2h;0Jz<^Wfq z5^LqSGqu}Ju>4GKns$pE7^L8&k7s?s$343g`^d}V0dpDT{LiaLZXteQSeIE81VJzV zU{!+j_1s}!N1<-=SKao#p>$D$M&$-<&?z>mPgYatZ`4^TAO4IM)*jga6@>8FYcyCL5|d}`IW!WxGe08cH!TfwMNi|m~n zU4!tgov6iV%Umjs+a*Qjg)HLAFhj zHBX`wyH&apvO0D%;-gBoWUrh%wpS+~5gj(#-U~zds(o|+q zhcEUgPX_~`ZN%6}y}1DvS_+x#7mtx7i(B$MvM7&Hp})or(1+$L53y+-{gXTD{Mljy z)!?9aO`KyGPzMmu`9plgb@vMKKun0s&nDyl;us~hRK!GUmNOV(dm3AeQo zvKC<^7{09DSW>NDIMNol777X_S@B(u7$rW9nID}MsJGNQ9ethXlb#OIJdP{Mi4dRcQdd!Yi30kf#1C|DI}?f z8pG024&_f*4cH9?rFxXxC4GaMTft}&L zr$&vm=$zxjyhQm)j)6-NJGq7&`3- zpEtDfc!AbHeAudx-mO40-P!F-jOP~=vGO6ir)T)Jwc|aIHm<&zaz_Fov$m_!41Tj` z&>;u2;KVce4?Y?#eZvMW#DXZ`11`CIqf1mTbFyd0lJKEy$MAw9bI{-`^EaZ|W1dGr zBo8Cr6kj+l9L*jZ5JNQh(-^ZpR^eV-LMZBT!zPG1s271acW7$Gn0Ug6HikeTa!&=g zS`%y1#ez=O+zp#ePy`oFOh|9k1)y#tjWqsAq9J*JX05ArK0$avWueeT{+d?U${aw? z^;Bxk$lC{Z$jtm?26E;I`0`|N0;WaEQ}k~boJ&;ER@iBO}p+NI;&sU zPbzrC5=S?7xpIf18Pan*#S{Gf8QKdaWtIJNIR41T#uJjU;yuA)Q={%{gL4>0 z=4&q=;5Z{ATUk^mxgIs6p|~k^OJm?C-oBl~2TsqW{_RO}zHiLA>*RkZ&#@o#nwfG( zw8^Ulx=!h>t<1T{!;Gp6&zl0`5Z@H>SdM`wV_uV5-@?)b#glQg0%j~Gknm@b*!53I5eVid75SzFA4Kv!F;DM z2BS{+;W(qFhs$MJJ<@yHae&|k+1X|v%fX5fw$$dVzb)DYR?jrekSn$pdLEdwX+t(O z?54GH!1-G<#-=~%vV;4fWl*-I)g^8OPo5LoYuIf(HZRb5qD4A~g5dw0{DImdqT5O*PCi zrf@##WZ%Wf?D5e$mi&|jIhYBeov-Mz9HjKl4d_qnVvTC$zyFYaR3If|va>AF4)c8w zBh#iAm#&)}x_;B+cbdAS_X=i(5f@DMvBBRLz<<=7=$w?xlh2+Hl=KYJ2+K?7uVMWh zwH-@z;k4N8;+C_wm35J0#P(N>1l1AjhsyS8;;?FLbe;91YW+TwMGgNqNb)Jz*4FhO zn!V>^yWjgW?hRyCQD%{3^O#K?qMos)ok)ys)W>}9;v!dl%s0Mk(&n&neii&dAurwKJ}x*y0h*y+l4%)m7WSa3+DhWg2E}E58)dIkK!&Ru^Tzs zQ60sN%UtqgkpGw&Hmvg&d+IRdF2+mk<)l+Ki=-Spdz>S`aPv*$LC;^#;<>7ZZ4>6Aj9~d&fhmk2a#Fh{0|^%Pf2sM;=C%vO zUH(upp7-7>k2y%%$WNwZ(OwIUQo1f<#+5sPa>Ky0Q^9mv+UJF$7^kz zef}sKsL^M}Hi0Qbd%c&#%vz@%&P3ykC|d9?I(zyyO2C?a)NY>Ub0S?)oG)fLm|3Ui zq9P+oE2D?mEP67~^gs!J?4vx@Qz6$>eLL6yC{CmS_k!ORe@3Q=5X-=C_tPfwo;-0e zUEn=v@*vFoXAb;Wk+fWHo&GU~_Fjn~ChJldI`WhNv)DPIZ$+vLH)Fw8j70AhUf4ct({U zvQ@$WC1J3o&bM|puL}=5Ncl7>ULNvxp;4`S4#FW5g>CpFq9Ij7sq`vBX=^?y85t>v zb!bS&ng>R8bT=kCk?`1|TIl99k#=!GJmgSTg_|6WSCVz-xZtIJjTJ-VDSs&++Lv&m zMS*8^nMf~vsQReUsUdWkJ=&E0vE-_FsG`{4x0e1dJ4_%yz7|tuD{d!s)#NI?pvW<2mnqRwVp50kP@%9W{@k% z9uXQ}%j<@>-5`rG6|$ z%62gG`1xk;NGQMDfmEE9%+liw2B2a)L5|V&G*B$tl=8sZaU{_EcvXMvc~9fH?3-z+~B%skI5VDw6)fh{4}u^qLJ>PHO&_b!*pB4+IYaBnsvch@FB_U zQ(&U}$?Jy~60F2}@_}aSM!G6mzYsQjWP^gSxG2uAky)6{okA(^y9TR)QFsrB7jFVy zjDhN207nVlV6{J7X8V!?H>;p|@DK;@g*7U1%O@I5fuV5mEY7kR>sy{Et|=ugTC~d& z>DjndFT}nKiHjqY6tdd#5R;py9Kbtz8NStz*pcWt@hMCz7c}pAb`oyfVxG~+>;=ff zQvTv7BRZ}z`I{IrHv=cek;Obe(Q|l!pl48=7T|Xip`@(z6<~qc9t42|83MX~!SS^aLV8fWeJg zXhYWucT|<;y`4uvWfusEBtNY`Xz=ZPD_gjUeEp&v{kyKR34*HJ_DiYWgcX$mj^=3S zrSO3m8yGDR2uh*uOl970z$!aEIgs?wag?z%bwbV(D+pBO(2Lo_RxQq3o?jq$3?4D| zbHNkNkhSF&1n;e!sDq(Z<-5fK&FjGOrkfMmkT|#ocDYkZ(+UnCI}?_dqNunYiv0-N z#1q>o2b!v#f?=6?`pZ}9s*hB_;JK<8Yy}T)#5~rjX>Ie= zbhbho_NzPnAR+!UJNPM*FxM^WqXynM9w#ed3zri zw1f0jY~ERbLNG}BF8{|L89=LbFR^>U$KZwd(?=-@hb(ZjX}Q*`#Y1?7*Akjnjx_5a z6fbtEpa=aLbN>BImV&!6ezjjN7g;V(X4KMiV6Y%~HDBe2pAwdTlazck<9mY>xVxhC zgZYavh(H2rkZ>i@HzE)5(udnXnlD#YR9|J>hYsD#fiBP!>{BPC#k#;r&cw(RQ=4JT zv(%~oG<;L!#Kh{IgGMuHGlgQZ#xNSB;nM&&csuD=0fnujcd*?@(|XysB}N^POjaV3 zvb-<=H!T=rgLZcLEwU9CbfbqE{9UZ9^rW}jI)u}aEHz~r;HAEFm^_4X#*53^PWWbf$mtjL(L=G5psTTeAP(iWIqvWWjxlb;*w>p>a?F~1 z^~%GDE^I1P4gJX!xK5D!<#-O(CK0|${=zlSW>4Mvne*FysOlZi^g^J)P{5B$#K8!Ngx1mm;BwY;_k*e9ALbUQ`IUD$k%lQEc zSwzy{P4ePLD9yPtf+;6grq|nR!zIoJtp}o-C`WQ!S*_XciPJaQr)ce30rQguujq{P z?xUD%B`AHy%GF6@UBHrChq#ixXTVYT9JEpR*DP&I7^$^@Sic1Ow+Pq zPxjD3@ajYyB>V;tQ*guS8RQVxrfGX2&#=K$5idPg2J-xb63wtl9tdwj{ zB;E0d(4tz{`sE-bu;Qn6k{XWRAZIk6@CBPub1$zd@yrqbxw_&odReIll9;Co1-HQD`z0@5BU8!`2sA3G}ex zl-$P~Dy^VMdx#JpnH@@Ewx=R?+~-KPDt*g7QK^|^ zYo#&cqtMz+wuWyHyT^YTMJHb%CPXT1PUt~kN`KRUZgTiZ*7~XrR!aB#q(z08`=wAQ z-C3|diAOyn<~@%SIRo>kkftRRD33_KP;r2&p`+EotUaKi*dt-UfJLLquzHzU#vu*X ztA~01I}YwoX4+qj;CZ#7mb*%H4HLB2L&>ivholjP>bjrI$9#d0AG`O9*7nJmaJ<>a7V?>TN^~@FCS+qZ3%mMie{09vX)e3^faqBF<4( ziW`9@_B6>A7xeUTj%!AALXKl&`4=Qw6X2`FU5Mn97zJlfw?rV{=mJA*+A??Mdyb|?(YG^f)~Z& z>fCCjlV-s}@<5Sn=`ge7euM z=iVDMfs^c+@Upd|t}ER>Oet1my_JA`C6<|3<(O7}QFVq6-`Rj`m<>c(rs9udi1k+} ze|9M^XmwuF8-?*?{U;NmFdV8#WggU6Djp`H49V}vmaucxBWwjk$>XaS(8`%5;{}`( zekw`hN0hEFr9De?RDd82>lVIF=_@)iK~&8a_hY@mg9gl+sAD4cawU(E%R_c&!Fu+| zBCC!iPuA6?I+~d6P`EdXbt=G_I`gBr@!A!F(88!hvZYl_u@1b*aNqoaJ7Xa%P9^6V zG^^8m60-~fwaSF+3lst!*xc(UB+n>+NTix$(!NItgn?PX;u)>lXn0Qv90C7^5pU;o*N zGwIdQfuINTUkpgur-_JxsFxGnsol+@p;fq(@0AHGv`l1)J+8yQI!-J1kDnuAita%t zKCDD-p%o#T3bhY9{+YtwG=y5Thfc=NT8{S;ru)ejz>p5^wnpfsZMH(P4n2rBWDx8G zoRTeKjHZ0l1+3JnRkEJ1B^8eM#$g|;l$n)j%!y!QbeQMzyUx@~A9MGSaESN6Am-yh zr`}TmWr*kZ1~+N+1`x1Lb1{WTttnaqKcx{@)$m}gDlx6eA4XG#`(+CRag}Nks_u7uRG9ipAfs7wz*#6q>7py- z412s1(G0t060jEQ-Vv5d?>kd6f7w!IVj3d~O7$O}y5g@I&L>orI9pxmq26jJ7Qo*{ z5r6gx;`JxWURoVHR8PuKdD};hLGFVmEyU|SW*fOOM4M{yq2|3Z9@zF1d^qn)-I2G49 zL788?XZDk}8^J|W)3uIbGoC%7{X&CF$d4*tyVBc>>Zlxs;dOyT4iZ2ES=MK4ii4e; z6+r8)&Qlz8F?;#Wlco=o^~5(d*Fv{Q2LY)%nJu$;-V{bpg@i=~h420Y z8|G-U=d||RQdB%{3nGQ{>XeOc{iwzlSJBHPx3V7NylE=qe~=?$oA^4Qbn+Cvu6kv98%_66F54E= zd@&x58wULJ$U6YL9oB#)%}(e7u{egF)S-P$?Uw}U?`UwBLquNYjl8-dubmYG_8GIN zmvB~LO0YX28khvcsh?B0o9H}S3PmYX)hf=VmjhX2=Nqa9dk63c7Cl@uLPDSMOW*^~ zQmxZe<*QUDU683jMhx!PK^UMwg*mTTK%kZ<`|oXq{XOHkWNB?Y6xnccenX=NCgZ3? znU<-z80-~cO&6gcb70y8ICOKAIuNl|ofy(`8Uf`&A6zgIRd9;a zDB=_ueJk4)D=lU0pD^x&Fo4i6d7CXl$ML<&_Qk;o!no_8-U;MD_K%uW#l+u^l!qt} ziWc4uPi$#Zf3#C8N6d?MPwB~!hTliHs*gymlPCG4O!}3w-n6bx4j}~8mvt$lRlD{UY7xT{!)6}Mn`lxT~hpVswYoC%>gWr; z!e)Wi5Lf#@D#F0C|EYzeuuY{5Ac+mS{$dDh0)@b1&f6mRvMf3bekrD0s+WH5qB!UR)Wv4SUME z_CGLT+4GT>7hz>I5?N2=k;RIvk-(dfhW4l(LnWeV<6eiXmkfy?jCx_s`mZ@bE?7uh zmOey@xXq!=TYMVB*Ibo@y6GS8ut}69i7fYek$7DL;Zlz7Snq=JDav{HBt~nAIXQj1G6LGx4m~1(~tB z=ytIN;Z?@u8pN(rwFoLUL-Q<@g1B*Y#CuEu-QX^fH>wf}mGYpGD6OyQGA2JUH1IcA zErjwge6kd;h8su3f^)_?hUqVxEM@HRBBKA!z3e4+%C39XxLJQ{ zp-&V4VwN@)?1g+WW2wtuvXrkNT#*90EkG}`=Drv$6x$N62{7d4D@Q)KjK8yYQyU$! ze1okf|J)W$VQYuNw6D$3Gf3}=B8O3IHuat^$C(3T<7Te4e?&@3W=1YC-8t5c5w4e8 z>7(c*Qa0S$TZ*73K*+_eh1;7f?PbT5aO=lBZUav#%B+#6pK}x zuJI6d0F@*(Y&fdpY(igFV3JHBtH4xK%jmmn}d6vG^6nFb(%Qs zbS2m299loifD%e}ZNC_AKEWFP_R-_G??sjnoGh}0c7og>6l!3Yk|aLVg*_!}s*%o? zruTy^NbqIYZF4i-tV%=7K@qoMi8u^hr#VV*Bl*gYDmuNzjg%}=a-*JiTR^8hMM6mf zMR=KAf_ze*9|PP_%4FinowAais6e^LvLKV`xfF?^&PoL^H^+@8V+<+@GX6Q);mX}< zOSUsW1ixkYX7(0=5e)5CZP}C4E828I!b!Ds>Y@d|ABb1uih%7e6DKAGi)lZYiQ-Dc zvcX%&E@Y(}USzk%xZei%K7nZ{^|f{GtkrV?f+T(!ZkCJq7n+De`eNk>NF$=aUi3l& zz~M{iC#yK>d}-X-XiGDx@dHIcOx2tG=~AhOzE(QNl|YN>$Zr%e(pV_xyDc{-9pI~< zMF_yg^Ft|$pffv?W2;*3kW!OagW)uVLZh^<#6&D}`u876>vIaKTYjCfa)JR)UAG?o^!PtMgUr-9EAttyOv|6#j`zp+I zwPk@CLTNE-yQzu>(%O@KR=$ZDjJn98ciL3oYvy9eW43g3OB}N`xLi!)6)<(EFU&!R z7^>^QZb4+jdfwzZq6jej zQVEn4pPJp&GX^QXGlos$;`ffQZKQQ zO)K$@pfe#$$K5={<4DEc$&yQGsZ`jm3i^{H^F{%hzi!KeAE>#W>?pSS2tJw`1!cM7<)N1K$ zM7`j#{U+*qsMd1j^vzC#jS5lliPkvAiSlpP7pDTFZJbOcRZ!`KGbfnBKc$J^_aehtAsDFvg-mNRF z={=Z^#*M%I%55)^{hJa}j}F-CjQ?=XJB-oPGvxPy$3&2pgS4NFkzo_Qw4DWea>pTX zPh9R5f}_T6H^L3b_+>I8R1aI&?q|+(pUmQ`PV(36)X~x#oKViU@7Q(F4f-I+AII|*BQpCx3+9hMX{Bw>X=!sZ`{VahFavwBBC+yUEv|p7cuMU@;h3ht>yLsX1&A19H$IRu8qH@B<}S7z z&6KcX55t{VuVqV5=vA99k9^7VhZZc@JA^E8m{#H18!QG=HdzIxT$(BpNAPCooQgc95%9a|Uuf zbVgeSVZ@=HkN0rLi3C0Glm;%u30tV%E}78Tr>L!((RDdu4pDa{DM#8fSo;euCc zf35EpnlrpQGKyMqCiqApx?IW?xGkjT0jj&t&f&G0%`cY{0LsO(3GOH#{o#1;Mu>}U zUS&6ou%SbSH!Yt-82E}n&MvBGnSIHuJjiGnG!(|<+d5&TqK|H0TZH^9(F%V4dP&Ic z%CUykyZLEjWv=OHFq%qT8vL1Kt8|QoAlM%$$uctA#96=xokN<}VFlPymC@3(qWn$8 zJH=TEYMsvSJJzG2a}y;eitWaFG;n6EJlK#r{Yh_XAFO{{wK1v2#T$VbnFQykpJ5g( zDSu%?&zo>3le*da2vuFpbeU5<3J@|8`2=%n<)J0N9TzJAOC1w|UWh5$N5k{^Hu&G% z5R_eq^in#PCDZb9Ezv*<6p?v}nR8a;Q30A_#L-1Nx{TyYxBz83vOqo^X?J9~TaWV4 z=5KhjY=4L_(Gj(|q$6mti0>ez+ICG+N+e~WdcWEVkH*u(gU}D{U98araUq`& z;!?n^hA?$BXG@WlE1#L|+po@Rk?t8HXKSb3_$kiPhpo0A{jFwgwp^#WT$k~yOH=01 z%`JIHQ%$TmB*D1yw5_JM41auk$#)Blgso)0-uwpILIh~Uf-S#~3-FnQ@}zdWikTv( zCGyK!Ld+WYy5t!-{Fgl$S^F0ky9KgJ?*UD`4vRBwL9k z@WwlyM!6M&>-nQbl=KG7VPPjjlg~%&uhC+|h67K9?24(xHZMKxOwqzCg2D$`>?}7;YY6m3#I?n&3|%bh z+*3g$0wKfkYMix|YnUpErGlVnA8v+tyXap=-yV!~M^Mfpo#W{xyE)PXqryUAZhbU$ z34&t}u0w}Z=l20CWrMgnZ^~ZaqpdJkh4xim0Qb;s?7~K>N>j8=#|;&JJ6e2<%0Vv6 zk*FCzn-@*=+N;Hc%EnM!m^swM+LQU6L`7=i@M5@68J&p~=4=mz7hzx0?m?(l>tsis z{?cSCk{r#<{32Fg!EsL>UmhsY~)erZd4df35ym&eK!JX^b>OW~jp$(0Zgi<+LD6Z1$gA zJK{zwtJsyg3@97tEMdv35NuE6+h17657A#}jTyMA#8p=*f}`WO6;_3GKNM5+aOYTo zAL>ltdRfE*rTJ}f?eGC6rcAV=)}@5OmSQs-Y&G?Lf1tiX*U?GI9yvDtQ2keI@M+tx z*ywu9RD0!(QjXjeO937BDC}Gmfev8l5T zp8*wBN*d?{`lPm9jtxQha#rf{$CF7VT_`io`_R0^#gBdbKd)trawuvQMJ9fG`y?ry ztvFslT;-2M>s+)77>7`gRY;nQ&i;kTHJAZuCr~2(>XDL{EzdsSz@H23a-$io=3YQj zAFHqwJ?m&vv04cx9l54i6F=GBy&IVA6OI{z!q>tMxBR`YBWG>DXdyz#-=LI*}cA%J11Xv=kJ^ZiM5=rNrI-Y+zO2+s7q%`{+Aiu{7rO%Cmd`v8wpR((bS0yo$&) z0=UmDe2Xa*5*5J2`cEDO_^mlU$1tLon)d&=%U|*O9v0W1d6JWV786(@w*=)}js%#j zn)>#UuE%JVGaf;1IKj|61$etbJk!}*h7*4dRv2s24ia`anhiQQ3^d_SI!Y`L?AWg*Ms?0H7N+mLL;JT-8v>lV+SM=< z#@6m(s@p1~z`BaEka^hUZHqk^U{H~RMN&h)% zy{T-|9#ObF`iO#=26=<>EQvJUH2i^TACf;VcD8Lf6XN3tQ1a;rCu2WIZ{YpXQd86G zvfm@v@D<$5(#WQJbA;?5{17TR*RM)y6vQU^S4Sr&f_=FbYr0YcD1@Ipj9rLL$IpgF zy|I!3>{`)WgblP!&|TJ_?v}crspxX#nJ1=l_UR^`3dZFtr{<}ZUh8n9PH**^!<7dG zgNLnrYtR-YHV#!`bIpnBYICxKUQJuxV@EMJ-S4C_Ki})ZQa?B9rgA$s!K)gsvTCT6 zS=par}5^Ne3As{x%S^**Iuv6RU+#ZOU+$Hp} zjXjw4y&z}RCf0$-bhXThQCUE@Q^Y4`b#vK{2e%fC?U`8?8*GJM7eH{N-MIhK$#Q$~ zh@oppw`rEJ+o|$e@1kZu?t-~wTNe)1P;5PelW06Y-|lBnCM!JCVHK}PRi-pgt# z=F2`?6$r~%`Vh4(Ju8a99=DY(sA~YCE0;m~Bw78Fqj;+jtR3M@+=Yw(>5f19&mO{8AV3iR5(X495YdLx8^+tc9jbr5>!;_C zSYy940Vo?cT5)}@W5paqwISx)(PMiJozjsAeb+voKWETVWs{}f+M!y0EO&f{;Nw{w zHnTF7H<iuEiZm z?D9jb?dy;(!EP;HyhEisqk3yG{3_B_K+m1rZdcK?3CL5di$ob4&;K-SzG=OjrBppZ z9lMsa{PL9k1mog0?NUBks#riBQp7`&kLEkw87y*Ze$(5lsw5fZaaz$+r8Qu~<#Q#@ z#2sam?QC}dq2^1?0Ib|x4Bn?2mTf?+=?Qk&T-#6Eag;C^JSa$NKX$N^_@Yn(R+X8X z1%}y^HO1rP`L&XkIlMHI91ydNs!7T1g0Bz5`1m(LZS+yg30p00Tb9w=+($>;fk8N% zbu_f$^Yu>IVIrhCdC_9YtyaE`z_d_V%-hHsoqlNqdDG-o4tc7JL)z;DD88OQXc`UF zy#l58fY;XeORP0}=^dRUuI~&DaE=V=i<@k4saAYt4}Dv$XTv@ySWs;e8XZsufu_}3 zNh#P~R!i};?wu0yL-wo75hZjQgD1?Cj-+=xJg*)oe{EjzQ5@DKb!)mPWmP4%v$Stw%#K0P3Nj%Hcg9xi{%x*^h~Avu9daWe;SGo!M&zA>+dWmGU$PH z>DIeceQWzaPo}MD~jJ%?j4ENR1Vv`$mJZKQ( zDQym)48OvJ*FypMLp1MF^4~BIMH2em!`sO0oT2HcQ1GyHV<7S;TKw)JF~l)NC&|^G z%oi&Ssb95J${8vfcxl;7ZA8}9#`F5BM0$%wW!6UNom%D*L~7!0a;UcJ5{QITpjrua z-PTpJ**FV6@(U(uqJ^FJw}2`?X>noRxrS~$Y+~~tB3apqoJ+HooqJl?kDI2nJI;%A z1#O+P){DQmix3vKN=dMO4cc_cXVYvcFC}Or`?rk5qQm40)Ef#EarmLYadCGGkU44~ zeRDC9nsy9F-=JpFrV{MSUAnP%r1xaPeqLyU`q80atS;PN<&_z^TY5U#XmCqZW*m7) z{S=gC3^vEfxqg6o>RD|u>Z)M3%v~(ou&1=KHi&ZT1KZR!Sm~FfpTFa&cm!HE8{DD^ zFH4QUYDUN#g%H{mH)*+iuaXnG9>BWmt*fg0>3i5T>CDR*bC{Ic3UYVp6PWgFhF{kO zN~@xFAqVOvuQ1&);FKfaAz_zvGpG|U-4lpT4c75X7V8os8rbXBJP70=Z&DUuV28BD z{#f;Gb#KT`YQh%LH95U%MzD@&SG6Dv7qq$t)f9fz;@G~}Rn3GLz30#)I`ydOz5mVQ zR&XMSPt#S#25-|>PA6_tZBnqSq6fJUZPY@VFAhexxfX@w?KX@LwDL#Y%j(D%$Vj?} z@coHW&^p*aW<>dzMZS9Pun#1<`f5+s6x+xp3U5NAFt}Or)FuDo$q;&qhPq}ecLhg< z{-pT&i5sm>v*p7|xm57;8VqJ@1NaQkxg&th;B#$f@|63!Z@Tw5p2&&E3lE4IJV%#k z^2BIw&~M%le~1*Y(4l}{*d}kl@ME;lM=besWl!kyGR2L|1rL4tb|XJHr(FE%yYp_W zNAfWj*(fm*zLiI#R0iH+*`k|++-A=&hvHv<+KjSdJniZ`#m~9$c7=8wp3kC-+!!75 zdzr#}Y)-qJQC^kR=bkvn7sY&_A-5qX7d>`IB~4Ia#n^BqMSAGq1gR(GwRlxaeq8We zsg)DTJbPre4BlEJa`IZ+PL-3RdXoZyX|ReYsAQdspdUW-M{ z240>15>nmxV}v^&RVDt_=;`mThh7KYo3s}A#7x>c2-K(VCO4D^a!Sc57|v62M#su0 zx00NM7$df6_#=t$YZ6}tfTmjAJ zCDw@<)bfjV7dhfrISUKXuh)9F^iXN5J{~*--A^DUL0pX%ntRdP&%)KTYb|71?3ULt z6b+(mTCZJgTwQ08M-I%Lg>y`qk6)VS-=AZZ=f>c4XZ>upe~m*!@0l>x{vL4q?>j?{ z=mB&7(>`yojUjF=<>S3x_B3r{Gh7ja!Xf&G<3L%Z{zX?{*HMMKivG*l7!?oj%^m44 zaX-8^p*7<{*sA%6po?WEbi8g6|GzkPAkRVVfl zTJ}glGbeMn7faEEAILZ=x1;0Nv?g@gwh~9H#jXv`>RiQE%#M**rZ)SoJno`#FMs$m z76ZgXO{~5TdpKvQYSYYfigdPlM>+q~y^id~Mp32RB<%qF^UfGl-7OHU(XSH!LYpm%@3{4IlBXsRJ6GM@?-Uox*6x)LV8FV(W0iAZ;eJmB|J}& zhd=a~_{^>uy0c+Biqn6Mvz&T~v-`!}b zJmA+%jx@@;1Fs%XynJL;z$FlqvfLXi2(D?GP|~BxoIGZY1&mp{uoBqqREfHtU8q|wtCBg+oO_Rt6d{kis(E*}T0cU?)5cpkUIghCjZ72S)rE>&BCyhh#FycNW8BR3 zCNxz8dnk(>CCeAm1hBZnv)&riaafWlwbL5p@iECLkG{-Q4`#8=RX(t9!?X4B|E z)OoMsPLG!x|M#%QY2^)_5yu`JOY6Syj(W5q)=>M>&P#WbDsY$%H7|UZPiNAyf$;n& z-12)eouOk>T>a`>9zL{-*CT`jo)z*R4+bEsJ;z=T>Flh@+4FTlhcuS>c$^v+?$*sc z>L<9jdysKvSBJTrHt$}Vr@HXIgUIwb`#_8SUGlE}{q7&$&ljOrPWzrRq?H7ZI|g09 zo5c}(hKSE3Yayd~!^d3z8654XOH1s3)QIV-LjMl{pFm*0;IkxQU`Nb%A%FKI<;O|F zX45v0lP^3(Wq#vu4f6|4FJuOQi7POqAqQgDhVfhBMY5K9#Onx-Zs6*n0GCk?YeP=+ zvlPxdBB42|HMSKRmd3ULkS%ll22FMUOy;TRl5N`{`$NrbTdtG^vqH}dmS*JZ$^gbJ zq47&wBsg^=P5Q{!^XlIr$P1%~Ug??lJgouOH*3Ds`O8FW*0$6_at*N!X#Ez(c2J8f z%k%Y~ZEMu*ZY@>@>}ucH4l|NVq4Cx#*V?r#A?cmKOxB=nE5o0G8Nqd2gSdCiEPQn@ z4cnM&yE{+Q$QC%B_OX7>9@(zB4zsT6Tq<+|N604BIlu9r|2O>Rum9KouiM~%2K^$T{Kt7roS<5M82Q84O4TrTTGs{BGDC%oF z#|J-xWTp4=M2jZw!G2|EHcKdsqe-?}SgsH02Ev|%W18CRvlK%ewchUxVZYWsUZ7*P zhcb9&3atU`<`8srj68wv2zEzo9aB+(CWbE7P=Ei z_Bw8V2=g1A56jSN_6!AYq3b-XVSEhBnXY%(7dxHRh&gMsfw%?xy~u%VN9P-M<%r7~ zCPQOv8ufh-1x(to+)1&o;@JCC<2=QyX0m#MFZj3*KmPoPZTk)RdQ5ZZ;KzHM{w3v$4~fpd!Vj)l?_*rMX4J|_ss`Uc z5E`J>U2=5%hS$$NB5z;Q?yop(Mx=|!w2oeISR7%c9j^0$-=3HF{VSey3mO`VJmj5>cU$fl_ zH0i=D0PhqESHV=;f!iHCaN*c&x7o@WOQN^Bw;*S5(8+fm;62v-@bDSB_OB{$p zA_kt%C4@2#;A$&@rqI)~mt}H%MGn2bp8Zx!bKI*1OwVAolC#ZHIsN`b6t86_%G$*Y zT81Gp*}!0`_ky;9&g~r912}ATpW-fTXIke?1KAF`V_4m4jy{aE)>;Edc3M|a2lk8m z^;;zRe7gWIlpO7yJxaDZZrp+0LhscLWLt=OvO#XB{n9lAUD=STgjOF8OWB*cke!Hn zI<^-oP)Ur9D^dj*aUTvV@!Qvxxa?bf-HT7scfR7CgZ~=7Y#Y2tCe`swgRC1ZEHJ_jMw>HegYO&8me5eYwiU^jIe4+5ppCT%%R(IZo+r(9H`M6@Lr+^( zde62Yh-93aTF0>sL!K8Iqin7%-78?QZ8>|-SM(kkPVTzVK4l55t_)$8>N*@pk$l+J zcZ<6EK9-@01O5H*Tx?#QKsDx`E&h796md-u!({;v8nKPzJhWQkR3(e}UCPGWwCH&)^GOp5v(Oma^H_}*D*3vd*2LHx~;}||j;QK9D zdocb2LJNC8!cIoiohwdHulWzAMx)*Ok{`?*n%*@qKxMi7)zEYrJ83*h|TAQ8+b=$lb7(3B`XV$c10_)OL?p$Bz@uK@mgzd<- z__3kvMxW=Iu?_EB)H?_Nb$mIFGK<<6fTmV9;74+{o+k&N=5k(5Evm>Ukl_hp1p!G4 z?UoE@6pPb2FC{8b7y=o`(!bjs?Xy2X%P@QSru>!_5|F4Wk+?qUGTOl5kjk^{{$=(s|zJ->zN zSnO(zXY}YRcvQ(z>_Rb&ofoj^z{QoA_Qq?NO;r-@&gFFWLyZ8&Yp4x5{H_K4x6-6P zgqo2)egnG!w6>zFwHkO1{F7@)4DrY=RvEal@tlU<#b>l!i??qd(6(Rmal7L8SMYz` zSY-Q_Fa+L#MPlLiFR|^0?XJbIp04@+;J_a&;fE`Dzhj6KQE%S@KHCbg_e)O}Jc!_r z<~GmmitD{gvpOd2T*2js6hVURe#W1!D;~x0y#f63t2P(sueiEACFm|#H!0Pdk91%5 zr5x<>XUgJ}4lFxrZ5%q#ztz}S?WodG8(P-FQ_$n(Dz|slGN3b;P>Wkbe+`LYg1hV( z(wIu5H9-cCJ=1csVCg{@*jiE}tSk{TrJ2gbwXHFL&RAms>qO4Rtsn(|(4;0H$Wu~Og|bl~bWJbVnZm3ZMT3tnGCE0z+XV?!LmdJFx&Y(Ud!t4)!b9WrA`A$cr zbK8a}g6&Rr%XM|kc3Xc#6L{?QTK`p}eL`Pc5@B1;yew2*t7-)_mIX;7`rg{?Bduf; z+Ta*9Kv$Q1pYpvfQ^__^yFlcMK6mEqctrQ|3{bIjd1uk-uZz?=v#O6G;laSm)_R`m89#LyuXIf1NG& zj8IOaQ?Ax&s)uq`)=m z!26G79NIpd9g72d5bHkneRveBiZSw_=fkNB4?G1yA05F(0LKlSnR(w<1K*G25NzWh zyh6?+NL5bWAo}3KlT)vO=~6VPz9IB!Y{`k{fo7e(M8`X{;N-aihTavNegV@PIl9(b zV?xnh+yLL&!{*p90eUdLg6<7g<1py0dDw>cI}InNFF1}WmIb^{Aa*R=Acd;I9=stQ zzvQ@G5cRM4{@I5A2!uT{-NR2le4=voMk^Y}Z;Sm66WeFO|vO0eE&R zE%K^X6uw=+W}$t84!pUAY;zy|A9nEUpndPaIGdTW8rJ~t_Io&Nv^~X%;znVD&*s{4ZnL^@T09{WX~guH-*J0fK}Ro^Uv||$(oX7X7Mj)4bOIptG?X9 z+f{^>w`jH_5tl?ek+-z=`rf;tobvl`1t=T}aV|R71FX))Vi@fe+^oisK7`}1^t}Fg zA3ky6whxmvyd5hDdozY!8fH0(edgUUf*bK<_4U$ z-@>OmJ@=iV)d-|Lt`OF_zRsm~hY z%wJ!sEhHI9_22hPeWAFg%3r$Haj}$*?{@+y)+T{CtYjZ-N46l@L%LPqQP%?CZEp4Z zS0))atYw$1M(vILR=9%Ns8_ywy>swiyO-Zm1e+BqJ2$597MRj`RVunKOcYz0zPInG zoQ5fjyOE4kX%Md79ZlrV_0prz>|?40PN?jmY7FqjR**ogh3G{xNU2d^^K7V0&%G=R zD900;+(wHwXzM(Q8Pc|Gz2})yo^N{Tsck@dsgBc%CHZRE+Quqngm$@%M6a!qrG-j_ zd9D)TAv0}@R8GC$f$Np-&}agitv=7ju-w3; z4_hNcyW2yrBZZcAA|vQKF!oeAxJh+w09R83WX$2DhM6T4cWVQe=WzBy<>c!#7%UBy zuUG=Vvhc(I;^6V}9`G-(zBZ2D~H(ZvW*k!!Q4YfBln3 z*yCsXy_1R`tzee~*7oTJ9`@kXM%ADyQ}DD-m1Y0cb9njyUR+5iDa~QEgz-q#rR$|CU%jrL z%~qss4ygj=C=iY#&6R!LE=*Ofb>DB_B?i%s0@^)V|LNWF3I)bAsyeh{1-o@^+G&Lx zx8n-Y--;z;Tvw*IAFJI~)hem`F&qwB(~c!mABDQ+JE~hZGD!LQzEzl*HSW0TrH7%` zSY2t&SFx&aRV{%W-&YV-=J&XwFw~lF(3*E66OY?!r^T^?lEzHixPkV`bA5l$hdRDj zo_zOuSEBzK*688Ui6ZOi7|xEM$@QM^svmZ71QhV_1ja*Ua*t1CBqk%cI2Ksx(Xked z=W5$JgOiDl>i`drWE97JcyO#^K0Q~j%=h5&v5tK@g_EfSUmhI+xe)3PP6UE`d@Lj9 z+3@&C?_C^ezw!X@o#=S27#>X2%Y1UIW5f}>f1+cbjpK zTzGFPdDm$VPKR3bk4O4m?MUFbPOR;w1Kpda0|hLDmfrUq_~=-D_;Cxq`A}KJFFk}` z`UuW?@VC$5drx#9zI!1xzDW#^JMedU@Hm1K2fh)*SqvuuJUS>-KlSyuWy`IO`|;!sD0p`wPA^$vBSSw43qdy-)dS2R~leEHcA6 ze)W;!!4C|s3KWp6PT=(e>a@qRonpIgobs&?KclMQSB@&W-4z$dSA6Hu8!kEp|MVPw zY25J9pkf-qw+0R09zdrzCmdXff_OBCc%gd|@8IDVus(s&OjM{|2CIQ&cZY8Uv`c!> zy)ympmgrQ6iIn~>t|bfG+3UY;01pf3dq@6@2s|GjT0 zQ2Wj!_+Y4K;prKCGBqGq7d|=Bb96CO34YRr5090>o)0A2QdjU~D8&86F`NyRiJlKs zM(@P%XawaR-al1Kt194w6FuukLpU94O+PtOkPwH$l3>>U@kpin_l~s&#sjVICYL6B zJC;*@d?Y(?b|hP21COS1_T!<}|DC<*wN=W0bPQD5=V%0VYSy_0!+}|!89Y3Oe%IKJ z6Rq{{$9vqocep~ja$>HHC40;MEo}B9Y9F24?N*y!s6wEw0Hpe-UPy1r2F{eYS?Emk7 z7v5~u23V}c46{t(y`CJ&+6bgKmP)x%ajz8CbKx|Fw=oRXa5UF;UIE=b3~yDYZ<)FF z+fxC=a$jZX#)8?ydx4?OT(jTdPFd>wR3t3N*RXEGb_yNM!uP|Hjwk=*S@5ev6=FW= z!md&e-m~E^Zd6gQEcMaHp3Q?6*jOC5YB}=BB@AD~^p+;k_DutXM$7)<6%;X^tu}e& zsPz9&PIOLySt{1Oz_z)~bUm8_er=-d_a!`TgO?0(+!V{-DcDTfs#u{d zg5%eQhrG~dn+Ys01cdVs2Df6Xm9go=)6WbT&sGI0I!NNRaMZhh)IerrT>Gi;54ocY z^pRqmZ>df5d@Ig`rvrGK!N7Vg+Kd3W0ox+ zE*Efgsy_a(qqXy50pI=xY*MwpPLAN!O3pvc1ql7<1NiAnWykFZ-mc)m1-!n6^-eb9 zr>~*k)AzkzYt6r!>Anp6@XXkOw>zXbe$sX131ZUor=)aDyK@8rp3_;`x+2mL%1vj-quT})tx2ANK%W@9- zhFrpo%y>48dg>8-4(tMDBfhIe89Iv2?@H|r24x?~SZr4^Li3fJ{UH&ecRW<)61uR> zBorR@4LB!~LGo=g+GBcn;ZS|Vz*SH3%g^Ebv5e5wTlMj*2Koj;T$C!u`L@dH(~jOd z6guz7jN{}bj9LQSwH!HRP&J_&!Sxp2pDKXqTWSM17F=|dowq7+2Uw+iw23Okb&QJeX zZ{fdu06$uJjKhMZ5AzJ33=a$)mzLe|hu0RzPK)n8So1b%82IpJ;~4>!m$044dEUJW z7rJjC8A0oYi&&4(FAnv!|EEtKzCD0%^=kH|`tE;s4Cj#^x&QPO{@2%V975j%YKMRL z;U&L&ZLtpzq@R5srY|7v!h8b5nM8$-KBxZKufp*wXy3s06ecfWcA-qZ|3>#}{z%tZ z*{T9$E$ClE?W@YtSSrESo;1z(A#5FZ9>9gC(YUCTrh7XRwqk+;t}iN6HZS0z1J+hx z%FqzlJRE3*a`faLkw4Gi*S@7^@#IU>Pmj|9xIERo~@ELPg5DYckC|J>jLS|aP{OyGxq zrF+mD!}3aK<2-}n0FyJAU+cSey6{(*Fg_Oua8<*+Edr3UQyI?HEwuXr_^hts=mIut z^;6$o8cTAY*-X34iBU8O1lw;n_~)@sSHJ4YxpS!yy3Q zpzUA04>xG!ZnV=FTy2TGru3C@b~fg|E?|2k0-VYg3C?-~gG+((!h`8qZfSNCsQ4X^*|W6F!a;3S5tL=vt){n59vPL~j_8BA9E(Lz3HQ)oi4 z?98ZF4`{HUyS=Z`4`=Y=Z(+ap$9StJTn<0SwuD`Ld9d&-ho5|PPSw7`b_zzVf`9R| zV=hkLk{2GnSI~ChM+=*AXz}*NBmBXPbk|crQ@T(@u(Iux;rWga*V3oNaW!3m3K_q&2jV!~XJG*<<6u(o|tq75p#}y1c4Y zIkJGixDx)v_u=-mj)XX2}pZ%27mM;GwbbW z{obCza;~-1o4}i|w0_D;L^9n8Jb$L+`;pdfG|+m!c_U(*D%bd&*7_1R$NEj9mX1p1cQ_S7O4mEjXG8Dcw{e`?$*(K7Oj} z>l+>Suoe&z1ho796X6zqF@n_7nAdp$pC@qQD*J65SjRA3z{K1~yU}Ma%_x+gz}e>_ zc3GUO4;>`R)-V1F77sPTwLXH$bM>0tQuE+hEM&cPN;-H=`t}KA5%%DcuruS=f9(f6 zKPUmLfUEfsj@L|2UXT|q@3jp(uLU1>U^B`2?VD@n={45}2fGm2j014=Q^NC-vljg0 z4?kyP{9#(dXR{W6d%9!Rd&cL_A7T$*(bP7>=_S8B0*iv*yMcY~k}Xeookvjkq=z`;IDC*QuV@hhw4xm&ilC$QxBF;uka?&q$3|L@whA~6-1OcAGL8@q;j^Vvb)sm$KFiFZBtudjb+ zB#BPk*f7-9qO7!R8I^Wl20D`$QN6=x_$`V8v2=LroCKz=cNs^ z7(TLKLs!H3JnF9XyKqz;=rOH85h+HQy43^^~ zt+S}B3V`jZ&yJ}?gpt;C)ROJ6ELG5&%1pnQshQqTYvIlgG?mV6d$Jw9p}|B1I_{l~ z!qPTzPw&Ou`;pnv)Ko;GdmGj2>3FuMdqyRDWI6AA#k&mtYxp|4P%8Fy8T>&d-jyHDY))D={qB~@bj!0@o0I8|*oF_f zVy^pS0Dm;s)Zf3la_G7a(@+FG10SlS&n8&l+q$*uLM`aXQ4sNGOK|IeC+~m8t3B*< zhwDSd57$z!+myQPxAPJ9z6EUO#M2?nwJ)fBofU+&&L>?T&dN2G)JA_9=1r29?jp`MZ^bBs7dUm&o zra^2=09eqJ}{p%2|h`d-+EMK1vF zfqM3Lw!?Lxnd;iZK-G*qgQhQ3yzjy3Be;5{R$n;PJ!wh>Jnh)n05d}T?)A>Wf9+oL zxj-$4jVW7gBtUW3i??hETX=UYbs4N{Wg4!pNZxWp^H}V);F?lVe_hGxQfuD!&@p|u z6c&k`)*U!*w^tdwGGsb+4eO0iw$yTbH)d8fT`TL(a*}w@3&T6TB)aj6POJ8 zDxYpPno`Je1wS3btaW7A!+vrr<9yapxo}_`K@(sfK<3C>wH=M&B{rlDya)u81ellb zC|Cb?I)j_BvPjE0-E`a;?0?TNVS&;Jq@k0D+ONuRb9IINE3q>4}@cIzW; zd-Fa8Emu_l03ZNKL_t&>KPTTzp>p}ETHts&4K?R27{@iU)S}o8Njh`-b(dt(;!Ee6 zmnK2|!x>yHI~)zR48n@}S0B)Q_!8LDbpd}z?85I}8J`fyZ+*If7YF!-9(?6)xJ)1| zEhs|T?L8o#r9}p-#096Iwme9C5bWUS3%LG3fW+2P==g140n^cydh6jq41bNS>4DPl z1DsvT`Fbh5dPm`uZlEevlBmt~vd}Xf8ZheC(RxnSTJvQo=b2?Hr_XBj!0%?(iyU?vqwtu?X{LoR zEtac{{PtSed$Q9yU$69>+qN(|G-`Xb%qStqE)3nXES-&eXzbnyyOj$P z%5cd_BQ{vj9>Zj+{@>|CmHWH^&d#*mUr>Ufi> zBL353eP7GcF%Jig5cY;LOpi`fH5iUza;$rFJb?9F{xJz|R)2O2uH8-^q=Z>fa7sW$S-< z4X=;nEW(3v@By^9>fL|wG3+P0wjdMv&CLgLKAQ9qE27AI7V`ux5P{iwtWH+<)V&-q(VU*Lv2{BUPR;Tj_YTSI#-XYCZ~!LKF)9_Cyg z$+3WJgOO}Yx`y)$wbH`28PA<-g62*z zIEK-Q&NH3Jeg!Q!ejs4q_(Y}urh?-mz1JCt3%|-?dZLP^=NtWsQs38mhoXPy;J=nV zc*l&y7y*X#V0z$2_02b|bzxU%+rrR8)&`VPRRXFxp|ZocYkK64O8J}lJzy>?48`o$ zFxpv0^(l}+@I5){y|OSIPv!KWEi`PF8z7v~mA+%B8lS=9)+kuDG+!RH3^ipT!;vKS zy4Ah|lL^chMwhg$?AmJN%y$PF!Xh(Nmx0QFPv6%lP3Wt{xJqOg>RKhrriSO2%4k+M z%Dy@wyt-0(v=_-hhn@nUO)6u(%~aCw25MYK7X1Ea+BYxNfW9s?C;haqK%#3y--bo5 zdi2p9sLO`$O%0m9QV%(a1fp9G^&559nrAOvjdg*DaoRU9dZEnr_6XwTJ^#FafKT=k zU`bn?^bQCX#5>m zHI0|ZagO&iu|Q6B0}L!0Kh-RJ)`s1frm^7sC#+MOr`?JluHa!m=aa{8Ne^A@&NaRb zpUq-!w=wHiAJA-%`EuJ}+YRlYAaW5k?R+HXzUnK0>An?LK$yY!CG=mbqS0Ez;I*>( z#)9)Nbx(dLR)u7mLmG*nzyh95;R-!x?;X^J`pcF23;{4T432H17qGWPnS3b3x3I}H zxS#GM9el{)&wr%KPF9H|$#r1d){T1hMkA_|w%Sq;pQ>e*CNR5F1?lWeybpe$=Y6q; z(a=<|ZY8o5n0DDA5ffy8Y($+*%j$NcEZB2}-0zG`wXHN+d?p-A-KZMY8R{AWqtLeB zE12+I^~#gAp6|$v+~!6MCd<_mcOBV}{#5(i$+%X9K2K5wA#R{x_wKd05*Ehy3|_`C z(Yc|{Q6#bxrTK1orhwiHH5%vmhDFad0Kmimf8V{{mFT~2uhCQsdU&LMS#4C_l1ybi z3mFJMR5sw)Dh1{RjE?2(s#@jdeF-8h^l?ks&|;_Tx^LLFw#I;WjIhR?5>~IRvip3eai?x5QXJP2amOx|1K6i3 zw-%MeX1+4~@Vi0{=qIP}AcSQk&3Dh0^RAK9s9%UNEegR&1^5aJI5?WS_G&n}QZLx9 z)sH`faQ<4T`OT@3JvF2)NmnIoe-DE3e%S=(N{ieh$@!zhw%Cs;sWas z-$(q74Yw0GE}?bcG{_uO)(@Wkz*)ZSxe0Q4j=@u@NhHhueJhAX?_;{M|at~$n zCFd=bUbh8|;4oWyqLmtD_q0bGuVFw zH>H_nFEnYs&`+x&`sd`0XUg{t4kyCdT1%TpJ!q_d_RKANH~rLi59(Cis|mr zQ$zo1sclu&nj?QOQJ+2XH52~iOxvsu>N$^36&P#}>M1*xv=ElNdjY4XW_n|NP-Z@8 ztD;nvT3bhBJ@e~CYsz=E-YT=sr)L6_COIUg`fz-F-zI4H4F~>6Yh|4%3vY*V%$~1p zPt3JvrLH{+z&}+VezBE3@?4{A+0nD#J~w6hQVB&ThUkIe+23m)$JRQFyJE6zwM}aw zhta8e{-sf>+^5Fw^|epf7U5T?_s&q ze_q{XW%2z&vu%@2$7UwAG6-I@fbR^|!1UGb2rNKjHkM@~`{-U3u0k2|peJLLSB4MU zWbu}zAfXfKI_@MN8|0K5^m!DR3V=D^Hr2ITmZ1anjiQ#(wB97Tw#b#?iF|#ZNms`_ zWcOq~PAl9b76VVYCtHa8maW;XwW0leJ7u`B2PCWH4aWh;?w zkk%>u@=!h7sw2*BuY$0V<8GIRT*XrPI14mSUb=9xfY~veyb__y_zUXYG2EU@NM(L3 z5Z_>>GPPH#=iXk(!N2&Xl-F7_uCGs+jIU_B@R!Sm(1rJVHEAU<+hy0?*EBXozwer>h!5W8U(HB7kRFGkHfvyVLR_c#h zfjSf^zAY$%tr3;kGKN6rtT4yEqJm-1#KKtyw zzk7f8cjzV7x{a6#n+E#u`v&=+!4^6)Rs6_<a!lD<6Ffk3-T8>EMVOt4erh#EZ5X70NRauaX7_35fGGMSGhJXEOczP5< zKL#xmuwTho$JZnRq>(ZyZ)fA;{K`UDhvWwBIELPuW00DCbQQ!a9G zPZnt>0pn@$i;sD*&KCDaBFPjuVT!9jj}3!9oZ`S*7miZ)6a!W0R{>aRp#)g+LeT;R zq4cM&wDh%fLKX$Zn%ae{EQ!gO2>;ZS9O=1~2=8{~<`+cGWGkt-61u+1pr(X%7)ifN zIP%OelsUmtW8ZLOY{VQH3-tt3t|y-dm6BOyovX((s;Y6{)}m59tGOZWh`VPkXeP)B z!{*}Mwv?M|pxoO~T1KZim(({a7&698Qw-+nhQNj)_n2mL-peGL$!a1se`GVw`9z}y z{Qhv+WuM~Wi$BUqC!NB*_x>}V{`AMDT|f8S^SSZH+vw|Cz;A!MnpLYl!r zUR3<1CFg1NXFEyxe@*$Q*R$K1hLmHcnpvytxV#rj%QLG5QLlx*GNeJy^_(g9IB`+g zB~@C<;)L8^Pd{s#;+tPjhT(hCF7Jy!buXXLkt#e+(J9W)Mjb{*mj6Nv5@Vr zE)?H;BZ88HVf)P;e7qv=X+XI7jco2)MgDMe?q0vE9JG6)&hS27#tknrklRZd*6G(F4q<=T*UwR zKmW?ARUhGzM}Ef5H-CdW@4TDy&igX}-v9oK_}Irj$$Q^>4&&ov{P@TJ$>%i0{e(z*GZ{W*W%Ikb!4d z%7w%iAs*BNKp652!;t|MRv=W@>w#^8sywqEG{STsD9A(+_>#z_{>=J>6BcA53u?{X zeWcFkf)K)@1O@TRGWa83gt!%#a)LkxyEh>NIPj-D)0G8`os>v+c3Rz|0^3w3<=-HX zdd6F1Vha+ou&5jScvbbCA{FezlKM`n03milz3;&Iu$&{dWW;T#t4matwheiuB8@o` z6sOWDYTH@>ouvABPbO`|Pzw???uFq4Hg63G&NtUxfbh$uNub;GI9Hi-Aqsf=ch#)wr3)7zJ)Fc?aN!v#6nd2cPq?y%&_u6uA0mK+TO%ARGgB#T4W7whegcF_)1 z%i>Pzs3WVCF#db8r;$5uGX)}9AQ+Ia5b9qfbUCSD=VSKdnm4MfjX{p%PLTotpZ?0G6usM z7;!PY6JJoZ;ZO_xMqrwHu$zin)F+x_ujV%An=q6)%#6viiPXj-kh1G}<-e}5z;QE)3nalXd}CQ<)=Trj|U%ofQgCmU9Mel!C&y~vrlo~eLrAwa)PgX<%_)S zZEt7MqQwAw-~%7xuDkw`7himifq|{udh3mR@Pi));7xD(BNi`S!q>ibErmj!fBL6y zGc>e=^UizUZdCM%>8s|*7lXQ>CIbYfUpFkZTK1beS3+5dH;B|d2{q6xG1dzMSrrq> zz)7|?SK3p{K^et3>Ew9)b8Jrx+k(y zH@_k~9Fz+l$j+l4>5ixwT`#RHZM}*Z{&=n!?!{FApjcW0WwiCmefeQ^?irUe!=o2T zOo2*8><4!25aB*PA;EPlOW8KGtGwS;Ip-fO!KPu@JS^>Rs}#G2@?)-r5-6u51u4Ye z`00&MOg6Kn1&+AB^=ua(_s*@584)+Jp`%2I4fRQA+GG(XvXX5rRfQLo!JXyFZZ&j> zvJ(UY$5O^43~Yj-V?|glSR?V{0|(j$h`fZH^c1_m^2qO4j2DH(Em)lO>CIMYaUz`7 z0s2pPmP}eApS3vPbgid<(Q6onK|0}b>Vk1%CbYx@0xwRvoQ6U#QN9Z%`-!^Of#qSo z@z;du5dy11G}%uOns{CecchOfRe<&hc;RSqb!*uUW8Gp{7n*V|9$F$$su}1UfYCl# z+$6`vC^24wR7qm=+X1uI)%Q{Fl3uX za;Qy2Tm_V=Yw7C3JMvX-iL8vp`g{}mVt89$1XwW{=T@RQhb5;3>H-y_T4@{V8oDTG z2Dl-V#doB%kuXI=p?%=BfS+NOEjyX#p8I{{@9o zp&-pE(Vpz&lofR#0@8|!N*Z+rR?Vx`cGwi8fva{1rmWbVWK(5kIwE|dNNEZ@nYijJ zdm?#eJgIgXD1UwxwQj1~uU1qm^FW2oHJis2meV{cz>sq6MAL`Xbi{AgR&IA*4ca@J zp?HgVnOt+7Fgn!qHx7_UV{T0hd$Mg})Durito$I5v%_3k>;_UQ(M38A3p>Ou?}RRy zoKi7ZoRiFAwgnjv7U!X@1eq%QSrXa{kS;=AEnutwM!F?vO=v@@4MzH9_qnY{0<#!u zG7D_6Y3kHD^yi%a>o0Y|l_*G= z@v@xR4jl{CEFrcWDFks@n3OspH7WsZA`?0{LSF@*KMp1qLYNS(hvh-OO|%`UN!is` zTcC9WCc9y1i6rc)XXNwaB5=prWO0!6pnpPT{0rs(nVKA@bZ121AFBdBwWj5)xIHTg zUy_cf;}`qWUal}S;j*LS@2xx-Xi zIkzO`)#m}zlJ}rFKJEHEU^Exprrq3GTe0SR6RCN?o~i*r7CWYzdqD2@)sZ7n(-SAC zHp_8KGWP4cdPAw}4K1FufS+N~=?sO!^mCtlev(Y4Ww+P!d5H;~$z;f6GUW4nzIPgi zr2_Sm(_|{1*CD(>ijFAR{5&T z`VIkeittve@CY@K)y~1IG!66WyMDJI^|h3WE|zL)Br~n zMKH!y0*h3xxd=%sEk`0P&m13;T~KjU>YYf+nZECdTVSOqzVVfcOw^vQB7&)1Nma!j zFIHuOADxih-qsPdOAqACcBHIa5aZ%b)#At*?{EZL5-<>l7fTSe!S9MHtg5YyjT~|1 z%6EwXH$#cq)-nc1ZIB3ejtXj%o0RNmo!g;v40d!&mbC07_(@UU=R4IR(UIM-5n#RH zIq=d_UdymNFO0xT)7L#pi)GUqgCQ4&s_^>>=t+pq#*ZXQ9m*Cn8;=-vp(PgbNr2}~%aMd%w5{2uXhFV-NdQWcvj zt76I#!?|P(w&%s`qFR%btLeD>J31j+s(K)$Vi`uai@-}HB@U=$sp0XfUW35(FXY&&xEUlP~J3G-7u5lHlDz0|9h9hIvQ*LU8A#GvB z)qFBVD~GaqiIhf1dSgf9FqJEs` zSDImG!=R~2FrB6#w-U`ofnhZ7G0bK{n$Xh%NuR;bETvMBmX_?a_p;d>#bRN%*Rxr1 z<0ux36pKZ&*^+tm3c`exu_4;m<-g6ay@fZ(g~2d1);o`R4ThDR0|NLZVW>eWy@)n z71xZLr->FK}~#iBXw38Pph4c-Hd>hQSq&-YL#DeA=@gGX=y@Ko0cW6eUUBa z^6e^gZFxe<_dM}=Hw-yLM% z!Q(0sNY^HbTOV3rR;50EOft~f72@ehT)PV6N734|naO+`W_pCM)JY<@gX-W3kSt@^ zH6qi)j)y!k0{IPZz-=8QZ`atqrH7hR#2x5hTe=Kvpy0vefBzAM(_dk5LqCP=CMfh$ zGJOVH#_4G-Nf_}&k=nrVV1nJX9>0}$bqEIfCEaVmgp|>peRWVA-}faF zEWrtG!3pjXEFoC%;O-LK8DwyRy9N&g*Wm6D96|^>3>qZZ;5NuMpRfF}Ra?8irE03C zdtSeO{rbLp&pG#Y(-5?48#EvF6GFR`6-0YZzurny+7QJgJ!mH*G+sC$;rA}SCCAf3 z^Ls%H#iCS%ZFhO7E*s;K{^z8a&uNtg6)cAuB%_i&b(Xb%7i_ijQlSnmk_Kan1X0PO z96!|mreHD0Rt(nbefb@$y-E6k(lA>ita2~+mUkb~u21!ctY++D+qA8rTiW=j=*N+Y zh&0m~)MBkPS2GvVJde$mA7J%QD+F_!UK$qS+)9z-Hm{X}5Bd1xxd}r{iit4aY3ADK zo%-#|hK5ZWpfk8mY>LDZ9rN~%adrE*2Ccrkjq2;@;BUE!Ux| z^xNhtcsCa38x~I1VJhTA%1#7n7*CL=rI-s(9DgsaatUqu6sze?$eK9Mz)yIB`e8lmr`k#P#&R zqh$%OMlQN!#pQ4vF&t0&k;(VudIp3cJxO=V`G_U-^gmi1M*Van%an)cHSo>kzEGyC zR&J%}3=zDmWUTf)NjeY}LEX}8)g0YhF2@d135w z$P2V#aj?aW3BQk~+lFH(;13mweb94)Y)Guy_2D;OR~Gg>f*#fPl>LhO*XcSsKsNGPd!%2IJV>~N5tVaw>IwZhDi3Q@QkDFVes3}X#&6b z3Fn%RA~!2|uOo`}#8elz_xAkNTMuVJ*B<-uAK0DZ%^F!lUKddCS3YoVMeIRCphO_2 z17x8o)<#b!O`0~b=gJSfy3+;s?@84*8uH%hjDNhXF?PO~oj-%DR$HBI$0FSAcl*cJ z_#F!PpC(gtadS^vOTbZ7564*kQwwm^1?ND>^}Bp6;JExxH|)*e)EAt02x4NLcRK+O z@aojk5<{%cVg>ZX(q1eDj82o$K(+R9xw8F`($^OKnPfdd=<+G5{P)FV)LSwHik)%t z&iSY*HOnVlse~1-HD&r&aLvX~T!q#~{y?Vy;0xqa_)rZa$ZzJ@`Hb?{|J$6l#=$kCh32-H$h zaYiormCnWNzy=tcyu`2^s~Cm53{^h&creuIuyHJo*-~NtD9ZgyQpwu0LA0xCdo88u zir5e{(h7zPd^t!*k&KR-BjSvtS+)naE>2efy=_*2D7$Wugv{-;e402NO zx|H!G+46KN_8Y#@#;VwQREd8i{Qj~SO1)W1?^4;0tyB`7oW$=tsv zDK()(Mo!3;&8Y4b7WEtZ)oIz26VFPj5H8&T97WrakIWu#eNu)UdMBDCE!N^W3$w>J zyXq*GhAhI5iY=y-SR#B@l}0$k8wLlR6^PmO)i#;^>uYE z`7z6B*~rU`ZHi4V>`u$c=klLflrBq_lpXrn2&%+NW2PpH*|&9)ZlBY2-+EmZY-baC z@lcb>oK-?u1wXqB4KCF92doJ=IP9I}80YW^mMvo?3g1&z70)C22i)B4bPoU~Jbw-6 zd_3)6_J>!2|0?8JkEJPr&%aSW$!jaeA>HS{qRI2_n*-q5X&hp?Xw(KuY($$w5?$%~ zPWPGTTU>u?9`=wNyYJiWmxX?WVeMb{pslp)+xZDQsqJXR)w!=OrvC;>I}5QuziHd9 z?3CUeT+};P_^(8_Gz|Vyf7UwFV8EZk=_^KAxXw3qcId^6S#8S4eg&vzTL=T_UsG!KU+srsK9wf>FI(pn0% z#mKHKRW>Y=W+tL%^z34eblG4*_)vr@RN${z_Q^1{oW7#09MdFDKHTc-sSi1en$Fir zCtkBe%3FM$p_q4lxLAEimR$T41e>jvD7E%i8yiQKQncU0-q7dhlSJ+LbyRrI8Q2m> z!_8#R?CS{{>tohUgYp|V7V)lCqL*4XWRI}VCDTdfYCDmMu;nlLL~sv#EQl=_(akKm z+KUd7-v1`x69`fyF==>TCusqf3c|Xg&|MBinG@=0AaO-3wmMlT%d^kayiL^dTORG&z-^J6xD@;u)Xh-6C0 z2&C-%-iSIxw;mj?To!s+tkZ`RANjE;BVje5k})WxfaIBt?#ocy#68F_n7KSFpI*XX zRuP{TqbvQ((1ND;63gZz)HRu30p=JCg?6qnDfu5C0m$EW-W96IjYa# znljs-iDuyO$Ap!fY%Iw)`x#dAO55De4}7p%GnTKZJlPj4EJoqa12u7c%;%6-m>s^E zme>1Q+F1-{>B#|*0zL}pyBS$d52sW$64z_UqQJ+u!+(t3cq>i6s|{jjH(zZ!7}b0j z;{B}%b0{UC-BbWgOQ-owd#24*?@kc(i58 zw324ogBi=Be3h`!zHosZA!$c#=)ti=y^WY%Ih%GISwxx|WcIhkOBF$;l(&A9b$=Aa z(h@L+)IvH07(6{KN)ZHuo}VTrph(f8oRkWeX^s{MRB__IB|xmtkGixLah*_2M8UF1s%gO&cIqfy&KF$}pH?)t%lMfYTDAG}+o?Y&Too(-;O**{ z1LkO(U6v@|h2?LDP>+A>)*beFHA@+~N!}cW&6rq!mEMYhEhAQGemsG3t?w3)EZhiq z9e(G-pYa+CxBuw*<3@Qn^=m>9_?f3N5mu3wS3Y?hXT)PC>D#85_3H~{>fK67wwG8T z)ZvNJNh)G#=_yTg>_5}O6<;B>q=hXLO$)S;4tp0kIZV?a^e2H|w)D zT<|q1u=3k4SmrOPRv_L*qa{F42dQqrpWe~1%hHXooDVQ0s*I*Ke5d0tMa6GnPAA$N zX^Npwa6$f9<3w&N#|nLF1r_7NJK4I00d4_U6-erlc9ORK8e{>?lM!20d$!Jn(< zHaqnliSQ+zs>pqhoxi|yOF{!2<&Pk{GTG>;Jr*Gg(>2!g+_xBh@2igYtu6gETNhq8 z7VM0&h-z@W31Z@`SrQ#OW3^q+NBS8Ww5(p!UV?Pw)mZ#d#ZX$?Ewy|)ec!Z{$0l_) zu9PJY(4tHzw;VTH&8~?QB3PiBUHwhJ=t+PzW244SL_xTy(7Ha${#A*vK+sBIbY+A# zCVrT7w}}QLR1F7DG&fyZ*;twFgf%zxA=`acxQJb{$r>psG3vP>hyThfA3c+4ze_>2 zigHw-U}B|hnM*BIvxWgY&4rm>`a604!D!p4E0x$X6QUXmsgS8g<#uv1ZG!r@4J+a^ z%jusVllEoM4fns!gu4_`ep*y^Jt4b0@Z_ew=+)~$^)XKVELp(piE@}F-ScDYyK`2{ z?P7%0s|@WuxA$Etkd|M^g+=~hG-2+JYMz@i^hjhmguFCqUkg5rMSGZ zH%&kgcb=8WB;pIf-<)uLcU$?b!6A<4wz72Gt?Aos^J#6(3!`Qhze#q50Ibv{33N+? z8zXgO)jV?1W}DJ&U)zs+0wJr{wjn=Bq8qn$uz$Baf6t!=ZY|NWDIdPijL|l}_YZ<- z^v~a)c1C4}_ia>1aw2u({AQvAbLPuw@-2_TRH!L8{cs z66ENu8YUQ-nEvkWepM^OjUK$*HyW#xV@wu`3$e-c%9YJmeT9yMps1epg_0ZT!NeUH zIOcx_eg(n$Qha#bi{pRXHrl#d5bf!`EsntsDQ;{`-5N`G+lXZPH9sG2sT&g;TkEnZ z1F#5PR5UbFYHBC+0QR-`uQ?quPuIcM2F0$Nuqub{O)ci_Y`F?AK_I$~wuDU%=9!GO zIx);qaz$$AS66P^8P=!QoAGC7d5_saL5OT)Z_K8y9H7GdZl-Fp-LZ~>P)Av<_QRUM zsV!GFApjC}Fz{Oc4BUp`SHKihcR%>}-|rir?HfO4k~?+Oo-E53Nvf#e zuxeKqc6aB!LO=F1hONanP~PQ$Rw7~W>#lf9-HzkF7k8Ty4|KUZE)O?5GfPXOS6#q= z6k>rb+0|@4z+pSBJGogGF#nw#kZV7d;CGkJA3uCoZ|9m@F6Q)gn;oIq%cKV2lfjOQ zDyO3M_AI;V)-Zm%IRs~5Zcl-UPXKnvz)C%59R2+Kz1;E^c#isx=?@(?ug2}S1spG}&pFmhbEKQkmTPKMXcjQjQpML>juFYo z%8qQgl7#gR52GU^BcESfd@R#oP9I}4Y)cu8Cgv3tjoI;DxJ>k5aiFpZ@dMRlIkrWr z0Q?brJCkQMT_F9G(}QeUU+pwD^X&B5HSIOmbWbF!h>tYR8;$k!^mN!k7VVqk1MW8O zPSd+BfYSr8CF^QjGu%teR`L4nr)*Aaeq1dz7 zD|Kwj!dovDX9lkB0Ulga6MuB%`1~_ca&ode2s8tMC={vDMA7zf6&i5Vn+-l+T3SMg zYccE)56N?#;8S87E6`z6h8w(;&IX^3JS7?FiK0z28Lw6YB)V^BYY=vKcY)XFz)3ng zJEcAj)c9Yp9(6xH*jNGvyjZDI8=U3TmEkri9FFpW=;zXsypt1WO~>hTiTl4QH^Z{x zlCrXB-S>x1z=yOS0HQah4D2~g?7z$emGSo z@%^z$lfJ$_xk5U}R)z6B%g2|sJ{Ptet$*-=MYPj7du@NwJfkcU8%r)&XEVcyK;^Sb z^RVso5atW(1l7#PrFi;w>IF-jcSzCpVlZWMIlMC=K{X{g9pv;+q<*{}0?Yt~*dTDf zZAN^jjf0YsGB(E-_nGW(4x{jL6rdvKIe4|Cq9WQl*E_5v*T*(}%6_>e@ySYGp#y1# zW!Euw(X(-0K=T!cFHW8QR#$NuV{b;-kbRSy%Bt;pcQSOg#Tk%c+xWbvc8F>W;CPYy zZOh=ZiKle%8msZm^I{uUIPg5naX${PkkIhnY{gCd<24RK8i(QNki>&a*YRggzFaXb zt{%TDC?MG}2}0dxj8N`owT;CbP$1LO)7OvGEY_gA18a$IRaLRo)zvmI92^{&=dUj| zKEA%K8&Rw+S1Z2Wrz1=|N&zBY4(ApZM+OF@HbL0Ifwi^VZ$w0PIs@~n9_D6e2ak`P zH(~ktG{8=7O!J!g?Wj!OrlG(4gDw=ex3|Zl@m!eX4*C_d$9{RhrCIh{ zWx{+iRktO_az1e!ywojIqmz{0D3_2MkewvS1!IcH{9)#ob85MPwR0N%F zf3YkIlEhb!_(AcWQ^K~Ru5 zI@0OQ9Y?pO*h?3ZFd)8wK!SjeiM6J9P2r+-?kh6pVVKU82Tl^~{HxS%00(Meez4Yb z^Z|x3lHPUB0E7=03hSDkUJt?r?jefzzP|7VyGFgLlgC%+#rKq9rgTF6#9hZIU61!L zw^>bXm-~x(!O=>I?v=!S&KW-MA(55d=B^9&F2J9j#DO}@<2|xRS4MB%*OY}tfmXbC z{PXq?|28i|ynV&G*HWy>Hujvk(F3lKN>Dl@Jc_VRoH=SDGP^Lt0-;g5=^?#abBY&GhFxm`g)XQ?${0Hrn6`oS-4D1C~BG`0>nf!aY?VZaI5I5=#TS5)_% zi3091>Bog$)em6#4DU!hdOw|@>nsPhyT%X!-vlI3-G{rW$Lz>RjINVdr`>lCZ*KKl z4YpwqN3fQ?@(O2)@J74&Q>VvECoIjY+-k}XS%o2G1vtThPkyj|@{Jua;r)?-jJolN zMH0|bjrnD9*D@)Ty3Z$$3z!PdP<00V z=}B}1;H8>?`+ZDoY$QHOh*6sZgo9@zGCH^LQ7e(|VrrlA4zKO@cSo0I`-6jf`(ICy zI*5ZY=(yO1hnu_iTfF#aFv^Qv7%Y$R56EBHu%)@7!5QMvtR#~A=9k6=fV9s;D^nc$hDJ{eD8Q2IWo^04jjD8@8&)dI6fqXGkCW$dU@2?zVaQuo&Wy+X$IN zdguURuLV+9S2UV1H8m-zX@3}&P8whv?j9bs9{Y1L^uNkvJT6x6 z*`&Poe-RwvN@5wkQW%xGV9*nK{Vxy7K>I}4Y1Lh7ZCkFv;#TKBVu9b!MaID&t^hq5 zmHPO$3x9H?%{rMe%rq!D6XcNxyI9qS?s(+Q1C&O%;fZg!{`$bgkGgG8AfDLy4;M+b zN&W$@7BR{=DjX9Y;~7Y@`C_*d`%$V&ZG)JTn6#YB@aOnN+tk%9tmx>hZOX18aGv58 zvDJQ&hixpg{;zEPBHLIR`{v)0Jn@)4DrX=MR(zO1os;!}OKlN*Qa5e=sHB4Atn%or z^TD?oes_3A#=d4v@xL*VPuDt=g(_QMwu*n43kfAub#-)>zUe-`R<^nudmw$7G}!z zq6OfyPTJtY0tvaGE>-DRED+Sbs-dEnm%-%ay5H8yW4>$LMdKyg!`ZFulsHl4cl#)g$ zOk3bwCEA@LosY;?sQZtzO;o!JaT!ki!+*TtCn4?(@5MI;NNd;A`O{b9^*dU=eO{8I zPTcHu7Su4uB{Q(A&PZ(Q&J{P_Q82#67hHDk=g1QQ{2`*(LG2K>a$X*?TPe};S+)U( ztgo*~g;rH25g!nLZsG91FxU!Kyun}LwW>t@)l z>r*@6!6i50-F`URw*jx%9HHhJ@P0)r-2KBtlr_vXG2YPGsHmXMO>&+ zc56xDr&}y6KrgxYGzZEVp+pV{b1}g4ZK00RC!0Alz&nOCj~R7RUsL@=%|^eIGpw1l z1uRhPDA-!PXMTSLiUU-)H#GJBvOI=KEH$_FC+U)Bebx&#j>6O7ccw(ALr1=QMZZ*( zE@csPWWibWVs(Vo(CL|DHgtqR6Ln~i3yW3|@m*>xL*6UIe7d^jSR~s1P*Bifi8YPvx3Dpme-ZZ4Hp zwZVsg0Eu_FYw!m=ZLRyRd+$Rv0Y>+33<*a(>=12UuDUVLo8?IlXx_Zyte>?Jva)a> z#^uGFALGI?S+@JADCt+!wNR2oL_FFxIDZsf(@ zDfc{y6t~iqUTVhnE6ob6k5yV%Uz(h)X*4;~b=%xuM-y{ed3ljcosroV(A8EVGc5X0 zu!*IvtXIW&QL>4dQf5H~lb|lNpKG-vwm;=X2FAV}10NjY*)O91^;Yv9d(JhEj;vty z`b9)WLsQf74d-$wp7gTMjNo58*q??+zc-wW$#+vPFn(p_F=*+aIpuC~u94vCu4U-B zgkaqW+03lBb_Eh3%fFCE_Uy+jyqVmSm zcOP#$bK5`&kOW>YbJRT^^}JPLJrNUUa<7$#SwBy~V@A~CN=5Jsq#mrBJ0|gQOX~Xo zi__$$F+3BOmX9fZU7`XMrxh?u2GKxvwz-j4#+K~&5B~kH&e^n z{1q`F?>Giyid)PgeA~W7FWEJ1M}0=4VgE3Hx~b#iE&-?kp{8=MX+O-zx`{mZW-LKvHhfLbnT+Jhs(xcRE(4!2*7SukK{pRp&Q063%;A7gi!KG@Fw!zJr&o zl{N?C@;=AtKrlFdQ)l58{W`Xa20f(p+X0dQF~-T1i(U%wlEZ|u3l&;qASVit14|;4AtVJu+b@xgr}gq$J2Q4 zj71Cy8;*Eogup6N;vb#iE|ODrb89k@A5Jb?n3zL2Pnd#$fX($m>YauuiB1~-glfMF z1t3>$xn_8Yb}3kwfvUMMPecI;<|qt4zw%~c(sIy@J2&9LyX&&C<^;a_mAFCqWRx`uO zW?*uu#NMBt1YD7{$f_Rky65M*vE3m52}aEt`B}FuO}DZ++w;qdliaf@Yk(fJ8MGuk zxlZ#qVN^u|MPx$EiSM>e7pzktkjna+BL#kwLqZX4q<9;IY$W53kyM|d|=s=UjSDhOx+r(uE}L9VT$ z=9Z03O#*DPfb;b*1SAMRTgWdC5VNElM&RbICGJdig(Hz%e5Jsxi6uDl3cdW?8MD}l z`;Nae!E4`j{1+$2H|HN{e;&ju0KsPYWh0mz|A4XT;eO`QQ(T?R^<7mH`Bl|ojv@fS zhqrqKuoP)s9Z*N4q!gB3<;Dn|sX$uKd8Ng{rm@V~`lfSAy!90VeYWd+6*b#{tb?-l z^yNP9jzY$m>=#v-dcWe1%~*;_lUs!pH}e~*rXY#$DM~+3+w|n=#}ky18Rd^NZPZ?q zSCZS%RMMBVVN21Bf4|&Qz3R>2fT>Ob2#p#QP>lo}mVT>RXBCfty5WX>T=ZEJr@*6L zVZ4(E>qvPLzeYwz47D0- zN-CQ>w%DIoDS-Hb0fj9WSRMkC9H8dlmDRe-aQA}G8vC^|o_UO2UGGBIWx4K1Hk0)u zw&u(ia->d32C(jqBp7*5kZv|StsrdnpFW`z3*$pkTY6sMBFhAVG%Dx~(xVO2A=^3< z)MR7@8F>twfbp%3XVI6QAItJ-}ft>*St@L)w6ltmQ$Q!nSL zAOpsCgABlP4;Lh1Bi0YAHFY$u>#>vxrFaaZUs?m1clzDxm#=(i({pv~Lyq-VZ)%a5W=D8Lz}rzpuZi4V7x4j6r1!nY#wg)Hw00TJGtzQuj^ zB*+QH@hVC4X^QEp)VI{rS|=2 zb|iz^++c0&7D@t%8I`817@x@cBQ%LdLa+&iP!~spVzPG7<@>_w3Zd3)8 zgl>q4;#~K02M0Ijgxu?SirF)wI3`-LK^NDVVs`LleY)GtPPNHc)1isX9$_G5dyCE0*09UF}OXV!Bsee`634wGKiWX3E76RkSc5B;CIuX}uXsduf!D|P|w zR1P{0$9Vt#z3AnFdB@F!Q?Xvv={iop7boy0`OD`h$`bePPf8;o2sbEyQDnpj4n(8! z`Z{fT$uN2HyAII&p4_cyFc22HPuj<>5Jua3AtHl#;d#K6le_bN!Thd{@_;cn3 z#9Fv{RObp`rY*kcD#lIN!I_^RWM5aJfv{g-p~VHcm@>qR*d$UF6AN=H=x47-a=k=v zLmGEBO9DNQ-!F{xU>s8NA4<;1rPoBJCIsHxavEQg65Xhswqww(`l22TVLtPYRlEDa zr%!IGq%#0WY)pCm8^TRpaXMrwDk>iDev7a3*w+H09y>exlb~zhGDuiy(3%7w0-L=9 z3!V=O3NJv*E<+8!qXXnF>fLnXHh%&NYq@5{?%^Rr-V_Y~vrX}+Es%!EZil_zn0ZL^ zcvW!*V{eak(tA`6n6hj6CN5(Wytg|RL(EvJQIR4qJszoDIxD)|p>kJ9neBF&{3Uu- z-sVKs$`371dX5r-f6aSMXw88B?>NqETvu0x#EWaDwFBYrAyeBu+NmHzuN!Y2SJCWy z4w5D8$A|Yf?OhcC`?|-m0m1c?xlryUfjb`0BE>{cqc@j+4LSEzlcFvdO*`+OCHml@ z?nxjfs?+iKVYa{7cT@cJiDrEuQUt^GLwK!x97-Ae{y4otEPYU-h07d`Bh$zhQj$OU zh)%Xe0%JzZl#UqoHnzV?`Ax>r*;DjDdf@7Hp#YRlAUEW@@ZA)-)gaES7Rbq&g25f8 z33%~CHcn1VMA0sQF2nuiP1KHeY+N%ZYhTXTs8u~6nXshFe>115tOL~T5A^t7iiI~V z4_P_UI`0OD(XM;_cJNY>+E_Tbg}ZWJy`+1~S;{SNiho$7-HYQDUH=&pYkWK)ytw0<7O3QNi}X3+e!Vng=Aatx%DuaeL0L&dtb1I`hPww zBFo?TSl;?41S$2JnUGGUNTXP-JU^pTSOZwp$%Y-4y7thvRO*N0a5lY$2moUPmL&}& zNviiZgs^YGxliI<4>ArH56`b%wjS{v&J}%K597%jwYi`J9?tuz`1M$F+%TbcSS8=X zu^XSdSCN+Q!?{pVlkOb|uj6jOD%Ny-}{|xt`g7 ztoQ??u|8|AK{H_{Po}bU{sOS?-^)H}GX|9=n@TIBJ!?-s=WG4Ka;$1NZIX=hCS4cA=oij~U3x zUVnn@BpXq9g?kHkHhhSTo-_BoCT0FeM;^~jHH+?tT**?XW>&%z&2sAp?VZw~UP+&# z9u?Mjaozt(6P*jl9smhGAP3P?#|Dr`022U0oS7~W;K4EzW&Db~=z$eS@Z`Ws5lo5e2^)Zp8Tc*pU1AgXv|R!=vFe4klTl&;E$N3Au}*X~w~ z?Z{sW-8ebc*E#zxh!3{S(7xM`uJU`y>;5vXMY@I}en=**fL{<_9y%IV8i}c|u$HES z(QGlk2)!jZC#`%`sx{TDV(FNB-q1Ir1|LeXVn#DqX`w?HHA%s-*UIe`4*=wPB{&?& z8$q@@JN+I^*lnLYI?A0X{hxiUGr+6I67B=(M#pEWRB>=#KCGmB!W0^3G1*Q`hUi z(3_y>ANAwY8r-b8l?WhB##f}1V2&|9rKi+t5`)6-!9jL%x8=VDL~!1X0jU4`_28H^ zpmNbbrUjd6f0aVb0o`mpJ&ME%>_7V?OmY58m>PeAJz>HA){WD>$649&Pe9tXk<4zc zveyGDd_(?Y^8{MGwBrVKQNz?+10=IQtXsmmSN6YNBASNeolCy! z^RkJ6`#r{KJ8I>qP>jADL$_$ z2js*|IwMaCPmybFSqB~_RB*UI)cmP9qFy`K(Z+Pk<$VW_>-FmfoOUPo{ThP1nMcF5 z#1#(zbKB;l&VQ;O=vd>)Hx5wx!GD<8!=WJFg;i!+x0J3fE0dU9#o;Zd3TJT1R2F=+ z4ehCX!-|-STJdBwVxlUZfs}(^r4kJd4JR4~(s}-`Di7?Ge(9d9+mhc;r5M}Co8K3E z|6TPV@~;Mf8MNTgZdg$PEJL`qBuEJ;YhB~+>DRH>nhpi%`jXKxXdS5c|4qF7OB zK_Ln#kYE>lMGYu|QUua-x!wK#&(7}N?&S~?AmV2(zkg=tnLax^&pgkRoiydv+k)n0 zObnVhW!ePBn5B#{YiC&K*95Rw@b3zsUiT*%Bj9%>;0<&EnyPjRcNM~44YTniBpW*A z*0GZ&gig9+%6O0%{4MN;nX~SkHX&%{jA>KGxAnha;-rij(`MWebc14q-Z5$1*r4zm z!|H;^W6^uQB@J~I!*fJYq*?10XV7{Hn*v9URefSt-pN?g0_ff|7 zu4in2W5#|*yC2)nSlO%05|GWju6S1|fLDtQwIC=1JvVwj%qOU0=OCEO7oZ1>Lb7_Q zodVQX?JyWUXbM9=e?YSb80?oB>Q`lQ+D%|WdvOb?x{BsgyD%n706r`$0iW7d=z z(}SkXx&!gtyR)k&YF*9MYY=?u?3uHsOuRE_+JuR-;2bw&Ug)he#!mNlR0buA(avkvOU|_JW`x$Vlxx4*UYRS) zcJ$gxyV3M!jvMCf53_s@BxX0U#h$fj_ z5z6b&6Xqo@zpi+J+5|2#n#Q^7(?HRQd(nU2@3>trG8)qt zqh?qj5CR}<7)DI`X<_QM!B~XBrd}@~9RMG!Hyd~mcm{X{SO@F?z5{*(N-!4s09OHR z00HO)qyrOx*}#LqGr%htAESVq0Xy(AuoF0r)*KFe&sames15OQHbQwDVE}A&J%9#o zg2D012N-MiB#;eY^lp|5;AL-i0OR9pjI|7BEGQNj03eT`X~11T7Gu|?G1lfs#%=%y zxS^7+{{YTC!E`%ct_ui02~vD*z}l{P+IAoEOP$G?THwP$QKhQxCp zJnKT%LbECys)pRDE}F{IH8x#+?pzncIH#$bt7s!?-CX6;oI_mfIf|Bk-CX5D&!Mz8 zt8T7xZldcYIQy!btJFk?;5JLEo2y)yIal3WMH^A;<|=h_6?Q>&D;Jvg|I^ab-mJR0 zO5I%L0_4J zYFAevinc)g_xu0TrYAKoZ>Gh|@D%E%H<#}8#vVhPzB1Xp*ur6}!On$$&Lrcamh={V z`_hHsQ&;Ri zQ7Ori;zPTh{h71K;H<|SM;%6gU!>ra#C$yh5iF9lUnd|+A@*{ck}NknD-2F=l)_n@ zU#v%=jno#UybdKz?v^U4Mx{!PwC&Zs@?lfp!=%shEP2?}?ZwJ==I7<-=jH99u=&_b zj{VGj`O+>*3{FSh?j89Qt~5XI)Aj3j=jG=qKB#;?D!arg5E zw^zP1Z})ngfEXX{LgqRFDZ*`ZZa4U%?s>a*KEGqP76o<9w@_($p4?gXSsn_Rw|l!% zAztNP`RBf`98D6j@T=0oeamh{3Vbe3o6D6>;T(#>X*6|nIof9TEg`-QI=?O-(0ysw zIBIjHe9}ZhjRS6cNq*WyR65B|bI|^3(iB-C7A=BdAR_wZy+49sr&OUIsn@wgcY+hk#F20Fgi+Uvz#x7X?ZV zH0~f5iyemyA}~ilJ6&Q1l`PZu$GRc;-_DY1Qx8CS8T0^T$L~CK0TDrzNNvqu_ad)@ z0OnLQCslr!7b^BJm^IS{L|i($0K;+WE)h{eWJmp^^@Y|2B;3H8Pm}wp4HII`v;-Wz zM5n=7<6ha25EVeyUA}VFDjd0I<=Lz7m%W_faC0_9;%vx%+0qK6L{3Nc$`#oZ43_8Z z%d;(@d1XT&&aTg#E8#=IROhqVtBlT-*(-G?&TOw(mHoW#_9OSIWlEkD+m}_YL@5{~ zKqWOF#k~Sugu5Y|!haR32R9_&YzV^H(n=Joij&u{SGH%}N2TN?NtKi>bR8t-`at*L zq+%I$A7zqQc0f^WkO#^qqq3x@FgT0NPDvJZU*&+hbXa$zvN*@?P;#ZjP+7R0jHF(2 zqq3q54Gb!)xKUZfjmpB0gjPRUDx%7|J5gC2mG32=+aYp1>&WMSs`%v{0Y%Z|dbJV6 z6cksZ>9T&=jxjC$!gt8~bWf7j(HPgtW$vV_4*9z8X9M~vp7^Q z5myIPP7s}mXdqr9qM^jM$2ra2_&ifJdKT^nkO`SLELVX?R!C<9t2iA2>QW67#9XX#-^VZruS{o3)_Jw zfo$MiAQ$)&H~^ettPcYk13^GI5Dz2)V}RQLJMbis4ZI8F0$&0LfK!a!#DK<(^?e%n zk+J?`fZG5&@Fb88ybI(4Ujhe!Q;g9NHJ~vN1cU?eKoT$pxDBuaPXgJ%yFf1RC2#;Z z#n?awGzNlza3CH?0>%Kh0e0X?ARBlW$OXOx4gjanR*Zh{<>TND^$!I*^R28*SA)U26B)*_oDKW2Tir zFOpV3MR4`|>g?lPP2SJyjsbrazAk@#tzOx8_*ZRpG5p-yzXAJ)FM4+jB0gVRlQ}fk z85g}fz^`V4ZpTYU(ABT93%~IqFM21@b$|404O|64&`oMX1pHK5l0Xg*f*-EY;x~zr z#6@x+fut>cz1v$`_|{}HlFKxC>_Xc~rn3TE>U7yPY0lHywYR>CjS?y=*Qi52O6Xd) zMlJdJHHAQ#6;OzaVW;J1)B)?~R05YBb-=27b(Wy@JI*v|kJg|01JXn|RUSo1?S?b% zLvkU1IDe0uR+RaJ)Jkf0S69;PNd33A1}Dmkex4-FcHE2pr#{B*niNSdUh3Pp0&+g^ z5U?0nX%3V217m4N0S9C0{s0ELbflTy73c>H2c`n^fyaQQz#1S2*bV#u90eSV4fY3G z0PTRTKtEtOV?%ysY-liJ!`1*fz;56N;3(i=Y`8zr0%!+x1^NNQfvLcJ;4xq+um;Eh zb^|{EM*#<8Bm99DKs%r-&<_|6Oa0D-5O?;muNq>6; ziID!Nk!sL@lxz0`oexc#R8YGnFM+vaN^yA$*)P~CaERQDxo~dJ%=7a$rbG5jzwZ?( zG9(`^9mt`EtNC7f8d`cv4m5kW+F3A(W=&ZzNs_v8-QO%pHf34~^de~mRL z-_MI7&HPoGb@|sxnucXnu;}w#QVkCxK3_|dIc0nGd&tkzWUejfn26BCt(x1b!!I34 zv-Xx$M=d}*(a#`hj#|V?8g0}na-U&I)tia-m8&eP#FdND=6p@nhxuene09{YrdqlJ zYn}@MzLE)1CL6aYlheW%S808dQ!UxjGNLZaro~u|im=ZR^%)ZtVbh74j`i4c2?Ayp(G2jFWYLY7da(+wpTWo7gZdxAmDK+7UJvyT8iCI0acM4S8997 z5?^H11#|2-cFL0TS8M@yQm*O(DV!oz5$mL8D&5nK9qew@qeMOGPF+LPHSSc;VLhko zp$tlK59r0^vc9=$BqZ|cA5f~V$^GZHitEkF{p>$i{A|aLbA6GMBxdO?YQHxPRDB({ z^d|XqpL%^x%e~QBE?F6!?In30wZc64MR%X?oz*I~W^(9{`zs{EGls z#`sTwuYq5HLdGUofy;r`Km^boNCCzHcL4VRi-8w`_ZXY_6@W|hCt+95L9ZqGHFQDYxqjq~s=XMGYIr zEZNnCmNw$a)dj`nWr?$kxiX8zl;* zUGL#)*s8rc(_&R(R_(L!QPPR~tKMjj<%h(Td;TdC;Ospu^D^Ia=tC5$+WiB)D)Ta* zI?h5Jqc)*Fd!-Yqo$YVUzX9rBn=oiIYPd<9&b!f4s|D~5iVlKNYqMaiFtN3Or&%y9 z`?@PM8iB??>{j*LTSeNpx1uj4eMYB_5nZCY;EP+45z(DHM!?dsb5vyKP8WKM_>BH& z8v1FE6BF|pqd7^V&6q*%u%yeGmjZkltAAorV&b592KhV*()xfTpCs&>hP8q`(i4*` zu#?-8Bqfs2mzb0UX^bea_9aSRonlR@7??iL6+%o(O!B696oHbh9He>rGpmwteO8J1 zT8Og50eul7FRLbVVDiAj@aR60!RfEmt)Xl zAfxV18TyMi3@QwK`OLPZ_~cVv4CmXi2Y$M|Q%?*#(0#wh%u&k{Q9kJC+jgCN>OTgW zw=6ma={3NsG;n+Q@wYmnX?Q`k7T_ke5DxV<#g=nj$fu!L|HxT*G`+Q#f?nb}DhU70 z%;Bb9WDGYmu~vWg7GO63J;B|-n8VEWXKXe`^x5rzu0TIvI4~8M4?G4e1=avLz;56N z;3(i=Y>q$B0%!+xWo#}u$DKC-I8lD*O#t*EcTNW80go^?A4B4ThCnOeMxYxo2p9!S z2krqD0xN;Hfi1x2z|X*+fW+8c4S`m`jX*bG5HJdu4%`DQ1XcoX16zR4fuDgt0SRr; z5NHM52y_Dm0i%HFz&*f1U?uQ2wNJMoQHS9_})b3sNp;`zVV%i4Cdjg(}8)HH@^zX`$0)zW>TdJ z#(qln%m&|3xGGgJ_Cd0%;i^;tetQ2RI}dkmRcOMr?xXi7t#>JVEE;@$(Vy~p^O*DO zWp(+VC!cq3|Ju|xP+k{W;}8`@Q=i(J-~I=AVLi*E#-TOnm|tS*?%G0eP8x@(OG)FP zf4cr6A4<>s2+c%4gT^5f%Y@2LrDYx@SEl7x7M2aEPyrVGS6QEPYW>3Gvq|?o-~Xljmk)ZPbvSRtorKe<1*YkuGIo6&mY)mq4O%My zW*|CG%8jYSP2@if)YAXT&$RNn=2`i_2BOn@-a%@SgY!@_Js+p{6U9U=e!@sCeMH{W zu3r1i^25fUhc%VKVI#GiQ?JJPcUQ8dyC{Knn+*y9C=&WOsii7==>ehG2!FG{as2(R zdey5d@k5}ky%CY}qMs)nY>?uk{JZ~6ZZ}H&_kW4|NCN@vSaa~2kOeFU-UL1b@_`?LDwcB)u8b**B(L_hc#9QwGjCI-413~dh2EJ%$kjh*lUPPihss$ zpP&7LcZ#pv78Zl0Tr|o>HO~9>JcsUnZk^k8c%EK}%!UjaiF4_f=1$GyaeiF=E~WQL zb9YtxFr`eb_1E1Zvtis>9zKPr{&Q7?=Z3N8Kb)S!^E&T7@mE7=UH+FAd-|sf*O5Uq z%cv#cS~;Ka9P;xtm23rijfCsSOGmh_y@T+JsHx~@5Uv;9$VnP)Q8#kC1WvlmTisZ1 zuwn_*b!%DW0ZA?=c@-P%o!D_t%e7=BF_f%WOURXEeutSK4}tY(kd+X+wGutbi!L~; zl%+MQT?qlzFvzev(Fa_jVpu+eVg)EzzLZBLz?pL0iZbTml69hDi8VJrf17G|?#5l| zD424%9ao~0mDW2bG`Bl<=I2uhKt>vx)k62D6BT9rEI&^vBkW3YC>U8O39|B%h2@iy z;x9#7FKS-8{!J5S;iPhMCmQi}`3~KQLi!cT*Bac;0rFQUGY^fzu8{4z6Q%SzCWjf^ z&UpD}RDg#@X&1{2jm}@N*HiOFnu2`PYH&Lrl9!`yJhBgS?vn@T&gzPyk<`a(Y5bt9 zl%J>Z<7&;5vBgTE4D01k*exEx*fJ>VmIVS3N0&hyrJp9Q{`X(PfPIWDxrMQ3v3CC)@_z0bAPj&Q{9Gb1 z8khlO0*ioEz&pTJ;0vGtI0?v%J>Lkp1_%ReKq4?2m;q!0i-1+YJHS@p3!nfv3CN5s zZ3J8cgaI}n5f}~305X9^z$)M!U@PziPyn0+WNN94IUF{ZRSbJ9BPI78H)JipIoFUQR za&^S1t%YdM`S6mMOQ;l^w-!Wd`9=d!V-K6B&z)vWi|m=+Gtg4VB_A#wyj^SlT#RXv zUCBjxCb_H;2|uB%5edV(aoOLrO6~qkLYY<)y=YoN6`9R@x_Q1&V}hr#;$qVNzmG#_ zYrDGoyY)egb+sw?UFZeXEs!NJxv}9*kQiODAh=e6XzM`mo(M!ZyCNkw8DC0|} zVX{k3M7`*9ULwRlk_u6=oq73WlH6GK3BHE4BX?K6;)jai>sh%}G59HxA1VebM7+=P zta;K-H5k0|@~!zIz6*wmAwUHsvt~!W1$H&UE~>>YSro_ND^yeY6=I8HF@;qnR68on+zICj8J8gF7VjH%64{|7vlDqKv`EfrPiv!F z4MfO|fmHcGxx`3)LEC!m`WP1$a(gP9tT)TF37)5l74KJRX|%YbfmH52_kR^9-fXWI zsJilK7wZ4DHMUV+^z#E~QwaTV*jcgD3#$IIAq&z^lQqdd`xs+u8UYvs*MtCW^5~0r zpo&{C|8-UM%eZUc*jIo#efM!4lV=r%J>=iGdKVz>#o_iJY z+^dl1RwM7#*8pLF4M+q=12ce3U=gqicn8=Dd;t^yCjptU*BSxW0AYX)NCZX$Gk{EB z5wHq)2iOXH0Tciy0hzJa8v)kJ=uOyPV=#&M~LQB_!mMk6a9>x!l6#9%EYV4f5o-Bu}=rUqOOoThvT7X!yvr z2ZPQB5_g%RcHO)r<`OE!*5pZ?frZqEXQi0&iu4CN>Z#BS_0>*W<8&z#7; zajZ`MGRt3QlUFaae_i{V+rKtt-;1%KSO}u&LoLk_l{HTsJ=_+bGJgeTvhSRlh|*}z3^QrYzL;ps>F{&$SD)-FS(lCO7g8v}ep$`0~iJF(IZW!+<{ogEVzB8DyjR><5TP!x> zZ0trwMs|gH_ddqnT@1Vkya#*&d=2~p6ygE~D{wi`8i)Y811Z3xz^A}o;4n~%qZ4S4 z_nHF1jJ^LXW9txi9pbJ-+;xb%4sq8Z?z(Zn9l(9SV&FyKJ>V1IYv324kg@ev;Buff z5CL=tQh;&59l(9SV&FyKJ>V1IYv324kg*L`;Buff5CL=tQh;&59l(9SV&FyKJ>V1I zYv3245N&0pw*7x8YBow`9dImxBa>YELL|97EafT9&?ufY&c+&(icE4FR?Nulam9&_ z-{Z|FU`2=C5c zrdbQ`Ze9L=!DlYD_9wBdHgTbsKsyH!TgO~QnM;v6xt?mLt5xc&b{LGdm1*-We}g^1 zV8;n0Jq%h!w>N^F(xoL(JK$;V)&%q#iGZE|jUr$UH5dJ`x}hzB=Fml8^iyd$%Dmhx z0zwd?!<{6EVKEE>8n^o~$1)t+)E)RXvFh+;6)RUQW2y@h)^f;kniB$4`l?mW8{AHO zMHx$B{Ikg3tbR6@2eUQPU!}X7SpCEN%XlOd#gt^3iiu6P_&Ts^M=?urI+T0hYe$kZ zUv;3EBn7H26tf&VP&FrtxrPpYy4+3*jAE(_z*utilldBMeZ@ox6)$&FTYvq_{jU(h z-cSm{Wisq99Z_L$`!Yx1$-B&ijeEQe1T1n+t_bC&)dj)piWjIA;3UX))@@jiH3q|d zPqQmJs1?(KN##oAG$&P%+v_Wp{A+u4MZwP0|7vSEp}gqlX*i)FNBw;i$1TQH$B)J` z_Gw1|exLRQ(#>JEJi*wO6~J4-N5CH7C*TAiGPbn=&=TkcAg!&7fK|Xdz*gW3pn$QD z6M=^r`{XoZ+c2bV!+X2!S^#-(O8}Aq#NT#1a4+x_@B*+F_!#&KI0&3(Y`X<$0$dAp z1QLK`U@UMua4+x_@B*+F_!#&KI0&3(Y=;GC0$dAp1QLK`U@UMua4+x_@B*+F_!#&K zI0&3Z8(DxRz_rv?e>qLL7}K(IVf~H%tBY~4DdI&YXhsoEDR(0~%VhTKOm50#+Uz!2 z^J|qI#0W%hCBqJoy(MkMyN0!-9fa3ZzXclPnkTRxT8Rs8By^pxG;t=u?Lr7|o&+~6 z{Lxld8cr@9`R%N7o6-v~h${EEmkSLWlrOzUl}AYQ+q z_P6tu`FNkz`fe-KbP|dZ+Kn)c-uUT zd>4dvskJ|53IAyEuu~AR`dXULDJz6yke{ddTtm=nBp!CUbi~8jI~BPz9L+^PgLt^J zCnssNooVED?U2QamqYT_cAC<`ND(p=mx>^DRqQ`O`(hOWF1{^Nt@W7WsD6nKt=c&y zkzcVx4&1l1Ux|TWg~~b`rHAO{u9PZND_zOsEY2_1gYhEpeXuB8B^9nzkqI?1%(V>` zBHm8gPMU{3tB@KY7y0E&+V<7b{Jc-suiuSLtW=hKWWPlF`#`fwN0)?&l1S+~3PyXNiM`TP!jzpGLJo-WGvSspUa+r3>W8%mFJ zNau8rvMgfZSEYsfmfeVmngECA;v9;AN)>HKL98cIz?Arvh_tyKb|T;BV0`3aqXB!lE5MrW{W+Sj@%v8ne}1HO^z0%xsg zDC+Tc26tK4RR=1Izka;AUY+X7m&)eQS{jek*TwUAM5-?Jm$!mjjPhmP$BgZL2v`h2 zVX_w&^UzNVlMgvEA9Ca#aELvSBlm;?alk-eBycNmH}C|o0(cAfh_Nq#uevez4cg!v zEH8c&40Hi{1A~Eyz#QNqUw!;!y})6h6z$;)GzEfzEk*Em25+4B$zr5CD3OlnN1-j#8oawjF=}AexJQ2BpIHSo~FfD((B%$z@)1#yziC zTgK`#-&YK=K3iHrl5n;+b7Zf4-o8AWuNOncq+5l_2i9j@`-)fM6VOD(S}-;tKd;!a zth!O^h15!jVcXy7KiE?BI607GZ9tf86wr& zWN93#sTH7dfhh$fMj4Sa651>@D%U@uon0j_zE}( zoM!B>1!w|X3v>h$fMj4Sa651>@D%U@uon0j_zE}(oJQR(Koj6vpd*j~Bm-lC+ktz5 zr+^oLwZO;JR{uyXfiLd%tl_1w7cX8W%Y|%ywp&Nfe(mh}Tv_Wmu2wQc7O>|Htsa?0`cE$F*iyk%SVq!Q^{y510R7WUZ+p zXoHJCi=YiIx`A&KhT1E0Wk~1#&~BmeIFqh()M|qmpMv=TDNikv<(-`yUTUK3*;Ee~ z+P|*;XWfBzQD;<_TKoUgRVf{VXcAmok`BrEqXAEn4$*3)O6hp%s8VWgf5snQqPgg2 zP^J8Fh?6whAI0Q$?a%OG4(xHqoQ10mym8sIawb}GIxrz7Vm+*H5V5AF4-^fg&CpCu z+u+(}f!oRGwlVCbM}D|u%p12!lj+FYy+gfI8ksA^nwrR5xn)c<&v~~5lu0+t+o`k< zpPzU$Y9r0=_|D#5X(vv==>gsIV%SM(qp4Di_UfuKI#MU7trwi~qMxT1+=qM7|Kkg} z-Kfeq{vKmxShz2{4}gE!QgfISKQngXPe5YqPi*G;vlVb7fXxhl4gy9ocIp$xPUCFR z=@iC_)&rjcdx671DPzTGhvKF{Fwh0)4Gab*0&{?efF;1IzrJ3F2sKY8 z+gAN?-F;m4GU>exlz{J@4Fk0pSo2|XsD+OM3XdirSvN7>33FiXHAz|-7I^DBQA z)w-NneC}tK`Z45}$?g-x@UY;n!44rjdfYA3qNh~~VdEwa?K;#c1Wz3jGlY!6F@s@D zNsmca3~PGnf%1&>^d}CakHIa=#SR!p2dAgsTvnd$o$fp!PfNF?JHM6Zr8lMslhdqe z#RWz0r=?{Tq>YxR53>v_fqD3zw6y;oE>H7LD=j$udRp3)W94(x8mA$`q*QBaalz`c z38`5HsiWj6aF&jeSAUe6`t_^jsotpv3tl^tn!5V!@;Rv^HlIB4*5K47Uv52kC^HpK zA$VenHKn*<;YS-&9xq54DNly8bfkRisUZ`N&n-{!PB~bx@P|87ew$uCJ7wdSqb3~4 zPFW(}neyNtDRA=GM+z2BDx0jtE-EZsi$uolc;cmZXREQ7Zk+dH3SyVcO-Wf;kg{ZN zN=lkE5>6p_Lb5gaNWl}y8$NohAo*r_?)dRH!x;O}o?mX8RhI0Xe7xX^Q71PqOD>x= zOdgw@Joj|+GrN*4$z_i5a3gw>HL18DD`{NW%LPdzoI_iDiy)a2ukJEYqSpDY`XLRHBQB?v+@C*h4p; zI`MXjEA~({d&Z!cL3noK2F478F?K-A0M)1zLI)4;-?cybRj2gPT}N94F)S1v&L-4# zzPip=*ZJy3zJKA!=Nk`6(Jr(P7Q?~?FNFoy4{kTD@|JPY;eth|RCsZ{Y3+wPCyomP z!AU&1{?HCX#Hqn>ieSzDLpluBU4#7xcSskfaK9<(kp{aZy<+Hw-zyG24yN28O`Ocb zOir_;6%OAxFXP$D>1k;S6AeifCvne7slKUYzdo6os<6ivDbn*>y|+rwrg)`@6M2Y< zDLyIAv5wopTt)D}z!YiCR_j)24fv}oh<8fK;IB8Oq;O7mb#mngTef_V?2{}`;E7B~ z_DU{J+qp9>IeDwX%~~c|a{Q8XZ?M10lpXnLU~!J)3cm_T6vy+p9w}px|DX;7#c|v5BTdz^J|r!qQSdT*yX;M#(1 z(`-X+Lu`Zn)5R0B)4kHg#b2g(NO!K6?}qHcNfpx{Nl!mIKRr0zkv3<4`CSlE#1pg9 z{L;ju^V34oDu1}Md;!FeV<%P}F8Xk2>Qi5&t`|=peP;T;;-W37*Q7e0|0tz=0Y<`O zCw>?+>igGIp8O&u<>*onEx$@`6#RzTTcJ-Wb!r16{k<0l!{g)c_kGde=I5KzTc7-eV&x` z`y3iClQW%Tl3FEM$2iL$9D?ck(c|Bb9I^NH#D$+HCjM_>V&bEt5@$F@CSIE;md%4m zaP0Vs--yN8xJOfv<;X6sRpSMFxN?Koa)4^22&T; zB*uwJ3|>#yl!XYGmuT}7C*~(fzRFyLtci^iotSl~#-PT7oS04wY&_73dBlLm1Du#Z zs7C)r{VQqGFuKv`N}4PLH&P}ER|$1hxD=j3rnLbn?WO|S6#xS4q|n39f(`kWFS(ty zi3sl)85$lH8X<&6bm|!1x%23bLZ{C2gow_gXJ*_nW7dtgO`k+zYfBqQ$07)xw1Jvt z66L?_Rw|UCGG3tJCWtGtfaidhP5zb!cQVU$6PP7pC9~KDF-woHn5Ab|X1NLRlHeY9 zlv(b;*WngmOGxH5%(5_rSr)frmgjpi%iHfW%f`*jvTZrDd>hFu`)_8JL+>)n@lTnh zqAf6&S-p#xwb>kCBeS-6npv;Co>{|Z01KG4$92p)wgnK(tP`d(>$G;vdME7jA7s{t zpJmpi+0459Zf4C!8mr%D){Qc=?zn?lzeK$6*DxziKUj;uXI61P^Rh-VFQ1p0SH16< zSHO1W)$j%8)#O#GYeI8p#LGM7XxY&`V$UPjwP@C&Wpl6pR5pM4fmS&1UEX|W5WaDp zC*$(|yeC;0t7tB_U{8OX;hQ0TvY6e-9OTZ+sF#5cx~ndkT=Io=={YF*Klusezikon zD5w0{VqU;SD4>%F*h$}9zM(}-3#qvrCyUKxAyz<11!2N(Vf5!Eg$e*A(gCmk;{0ro@ZmE^r}9LKdlFv=LZsMTC>bFLkM9#6{bocLB;h6C z5>hKpZJ~A*eRt#u-yJ<*IZ%0Gf+4np3XqFLPgeA|$tdiseL~ENUym!%D;AZ?a_J($ zPjJjWDQw9i5pbZ=o|)91dfd{A{#w>n!I_G<-a_RmSw4jb2L~=sk4f6OU6}UA=q~Z| zBICsVf-t=x8ufMMtkr#TYL}io_PnB^c@(?*`-dxzufItU?);(T==v_g@{>iwx^6E# zVv61UqrxAb`%1X)*OH@%?IVbXe-k#&-px0LSVNrT^b66#^===~-q|~_cYA+s__f!3 z`i2FBIr|3o4fE%QUzp}Iuya6X=fJ>$o&CAt*IDyPiVTQ!CIu!%`g6lCQu9fU3W#zh z2PQ}PbHgu6^GS&gh;~BT5$(?nzi7=TEhZqwnHHE9*}u@ux>aPBUSMGq2Rd<_nfrN;%t6{QEJ$HhG*zkKKCWy9hu zaZYmk#Zh=Kp>jac7k5#FoQIVNLu>)Iq9K7pYzZf~*=+qw*V-&LC%OG>JVNo3b@zXU zK2~=6@L;7#L*oPD%ZCOIjh`ex6mN+acO1cgClUPOd6d$F)4P}T6@-s?7Q+$(5>5;Y z9G38aJSCxj!s}A+gamT>B~WmyP%`$(&eprGvLFzluGEnLTo08L0@~Gv787t&uV@?*KIHqvCAjFo8)QUF5 zX0=J5;ZhRmVc5#4-Vex=(FA3$Nj+^gS35wXGw=7<7)y*}<7-;v^f+srH1Zb9Ez%&^ z%Bhtn$Pb{w#qCGT&2AB-?HI!%%Phw*Fl)j6Um5yMIDZllR)R-7F z480B1b&9C#L@$zGoU^BFaI}T@A!~H`W_fOOv>HzDe3mH3)4x0RPL8tjUI=5eJSU1p zfupq|qO*c`x%>?z7^ul|HL{vVC5t|DvP$m{E_5;CSWDXWowi=QzqM z9zP|A$ra7BmddAyxm0Y!P}FEEnyX}y4cAREuPb}6g{vYAAO9S3463t=-Q6y09K8jb zq8t?KIGCQ}xEklU66eSAAF=|TlI5@!l|ObGhe5Tx;>!C^{ki@VEesmIALZmMYks_X_NRu+uNEOfUDh9|W-o8>EJ*h-~ zCDI(@*kd7ImgF?pj0iuL|G20DHRCF<-`@}3y|?VAUj5~yC*;z2JrcG4iQ}?dX>VP> z9hwdg%z8zycaf>6L_6~5m>|FZ+-dDO2_uJGPxA#I)>_qd3&?uTmbPDVg5@H)Zc4WWyG>;%B%_DfY zNSa4P21e36f()8RaG$O;kLViMmF5v-&^&_sbfb9$rY$s&AcN)++$VwN5eb0_G>;&I z<`LYdJIy07g`s%_88naJK0Rn2(Ic=2%_GR5c?9?AMe~SWfxT!RK?cnuvP|=cUNnz@ z7tJFUto!)2ku;AWH_apH`MS*`deb}tv!=MXc{?7O^wQR3Vrb;1c?9)D_j$xkG>^C` z@FrVA&N7>=&(=jWk03YABPc?Zc|>2DM_~FDKVrx2G>=&R0nH=yNArk& zf&CJ0+c7fXri4eg_ew}0C(R@1^>m*{44`?$fWQGg=IkD#RI?MSzOIPBgX1B^L&m`B`1^N5u@EIXvzX&yoKp0Q&D%_AP! z-os{dwS%X5L~oi$q>i+Vl={*k~AO zAUEa_qi!F7d4#L$6j7gvp0nfr@h|2Mq=@7I5ltKVvuMb;P5rNNNyZIZamm=PiKDf>kvLlGz}6ul9ph}>y0j&ZMo!{rJX|nwwBW#C;%H(iiKDd*Y)c%C4B}|qCzLo^XkaLDG%|>zai4JFXyJk3#L>tgj>dgD5=ZM8*pWCI z8N|`JPXuwah`Mz5zk zM~fkj784lLW2Ak+_{^lS_M3Y2ASZD&CA=F)izbfNvzMip)SWmQB|XC4fAn1g#@Hc) zyK?ft(ISbX-DTgf8O};A}Z>dLpmVaxh zTZqM@3d<7WNSbrc^jN&Oo-|zkAyW7D7L#gA)o|&C)H}>#(r~GYF5Qs&cD9%lU8=rI zH>81)7L&e9ReR}%loVw#sl8O~mu^VOn#w}eez_Y`il+Nem0<3MM9MFB9astJ#A0XU z9FaH0dR6gNLi@)`Y3OY3+OQHuJNA0Pdu2IsUR8V*U73HJl#ZV6t`;jHb&w66|8$Pc ztBS9pH1oGf^yYh3k9oz5r#HuYox#^XUK*NU@u(>CNbMvFz&@vtsWnMN>X&8^5)G&A$8v3BS8tssxR!g2gzvRpL^W&nUJ!{#ZJ)0Zt6)mM` z-Q1vHs~~M})cvxzT+uTsiW~+{MHP%OhO}>9Q7jpwOqS(E-HG+ygH1h;Gh&{Q;^HI zBS`6pMY6msQC`-UQb^096oeKS1I1?Bpvx-!Jm|h+_PZ>9b|sHqLeUq>atS+PjC}*e zj!^PXc}l)aE}EXE<@)v2@N)zq+9EyI!&<(|@RH5Z#a>iP;#`AgVS* zH>9q_Yz*2ERVktyQa55Y2BnCq8_^9Zf!;!cZbVg)=!Vo?lWSEKiMt{7&;(>vQ{rw& zq#I^rVI%X5&$l0%o(B z9)@RMdK$!RJn2-xY&Jhl%*K-^m<=ghz-)REvvJe498%oq9=tKiw5CzUAd_NvF}b8*8f6TkDTWu5 zOKMA_j6p)h@M3aFp)|@E1XT<#CYKaW&2NxbF}#>uQb!tP3}P!r8Iwzjpizc|77Q<* z42h&xMOZZmuO$c>8#`$7T>E4iWyodlRoob5I@2iQCdQU1?s2mx+&!5_88=@=$VI;v zXp|wj%^=T~klPk-vn`lHql}xcBJJX3ilkA-qg;zoX2BF1Wjy*~l!>DE-h+gT_kO`t z8f85A;k}QhQO1Lyi&195R2pSG_+gZZ(Qa!t9p`B82(%23_iC0vX$O2c^cq=unU z#-ntLQDy->49~vwG-#BezTqzCVw71hiAEVuo)~3F2*xPWnMN5m`S+0G?w*u%*Nmj3 zuF=tM-l}-)B=H!dOoZ0WF_37IaRqhss8qYRy&^6uiW~+{qu7j5rXvZ?O`5j4s^OKo z;Ov#Fi^Lt6lq573ip`ri+=XCwx7dv$m6$ekxJkcmPO&@15M3KQbn#d0&3*I@9=hBs z_T@hM1`l1}6$f%3eS?QC=~|MSAQydmhc4iX$vlR>y+app#T4$NZ|~40TQN=bp*P&L zt>Z7;JW{cFZ-={d>+TlQDW_ud{tkD+*4-@*Ng@qr&O%a9fAAC(Q5A06J*o(}RS4ox z_cXq$x&y>*Lx|Doik*iNEcx=ws{29Qwucy%VDwfDb(W2F38+Xmi-H-D|4#8 zMJUhKH-;4H8$jg3&iej z>MzCwzWD7+^F|VPiP+t(_1~&;Ed~YCE)#>BzYZkr`n=)uX24s%Z~MIC^KQUepZBcq zV-g~lAD8#UHtw)Vj+Dy^k6p3ax$TPIU>Be6O|shHWn`b-n8d-WA0hkVWb8L6EByVc zR~_Y_9FV)nWhdm%yEnP=rk`*Yu8d1io<<$hlCQfo^Pp2fIr#?r!lWSC7@k9rN;V6=|jl zfrfUi*Yh;Ks=8wAwt#GOx?*eNPL=gzx8-D`lgBoVJ5?5u-J~3&)0MeC?r5S0Irn#^pp84`#pEhdPZa_^IjsS;amTd2>@NPeyQ#kz6QFU&wD9aM9J#x-{##YV z!8l`Dl{UEf>p+5!X-dK}Jq-?~)iZY?#@#KpqL-$`w36l~y|_8W*8DZoSJreXMhxaY z`pTLvzKCtPkG`^|%PnFk_t96@bb&<-=RW$%nl7n`9l4LbvZjkDVg&c8Dw~L%Ri7$i z2L=Pv3Y)>rBNdyM)!Zczcef}|PQ_+X#9jDschk74bt`RfFt4;#5kjgE#3=VPzN)(T z=C&4Rbh=_|gF}_2IJcEKqm#!r4GvY-?D8t|iuVw~(RbSkxj!a*g^&HO?8tLn1R?FKik~K;w2tTgEO{$hXel`Q zJUm$ma(d;oJ`?}EUx@tqmH0;ryVEv?`6q_;-@jD2MsRjV5FerB&3A+`72O2kz`f+X zW4RzKIE>>1&V6#ca3v4rEmU;*{&C^H{WQpza;U!%v2=fmMt%QP@K5}x5AUu3DJtig^b$uc3e$=)9%TdCDW9c`2zZA8xd@0$4 zD+F)ZX%l{hBK+8P6#wgrpC<~!C>e2|KUMN}G|quOEYOV5`o8Z60qcC$`)ml<=(EW; z$9pp#gn0^A_aJKFK`_%>;9`+=jb96mFZ*8Js!8+4RfZrxro1E4<`4Zp^!dnVi_g}} za(zDb`NU_NWqXy^ga>A(q+HLD`^sL8TQ$16VU>)$uNEpNe_rw{#Cpf{{pEW{V6Tj{ z5HGJZN@ycgj=B4ZUuj54ET0>jbu<=(iMVhhlvPp}Aw&>ok)QbRcl7`GY{hef5TMYn zHv1l13g3!_r;hG@tP72MgUW6Zv>@dv6|ur0^AVigPLCyz$G%n~Z-yT3aAeH9Lor(bZ4un(yq*cX{*6WarM8!kb%~TN?T| zs0e&?Pk8g@EPz#5nmzg=Y!)W2ql2D>dAN}Knw_Aqb#hLIcgE=)`90P`CPH2gDAtTU zIr7Fll_X~nEn~C1F+pfG8$%FJMn#ESU%z1&dlDDzxZ%B|SkZrI=BtK_W$)Nc-z~V*gV@MGpI8j+&St z9MuwAzgA65xKjpgT~y4-ql;A1H*5D%VzPMjl_-!rwvQw?XU)-tvz#->T#qh{oBsDy zJUeW58$3qrcd2-N#Aw)1o)S85R(QNbmLHx8id=$Dqjce7d1)3Aj`J>DiY|PdlPvN{ zLm$>?mW+=G59-!xC+0NyxT_PZML}`ZPOKIe zH7ceWl^rjZd8`IJ=cNsnR!&BRvKnP6ofuYn@)y^jPK+F=6JOknTvaKjjMPr7SzVo2 zr?@&XNL4y9XzrbuV!Cx=B{6d+R!DB0m}qXD7$o;jOrhMLhm^%TF?0GiY^smL*90`f z*rbfZ*EEF9JP!NQuxlKL84=9mutGAA!z#%*4yz>dIP4-C#$he8-iL9rfpV1%U05ZV z$6+OuaU3Rsc^oDRjl(dNahTkVn9_w|r6#+$26bWNKwbFaX5^|$Ic21FVa@95!aBv( zg+Z#)g+X)g!W7f33oD74yRbrX>%v5H>%t(pcVP*G2O=^SSo{5djfSZ0(8g=Z0T|7N=i$K)4fog~I*0;TNv?^a%|Jb@mDD6Y9?m zzfjHR%xChuhXn8g{QmCe_`9_ZXk9{A0JM%7JGGrtXEVi{0S3O$*9gXf$U#NWid*@m( z;L@piMJLxmu-HQ))lcAzlpP%}5KrGl`WihBe7exZ36*nZu2W*(pE)r7>vg#10^*jq zdzmZhl5bz6Ya))Ne^avK?U!-2#IH(6+wgkcZ#PqttJl*1JAXl9&VFK&9`Vq@tl?r` zA+q4vs9P#t79fHtopHm_{z4a5yjQm%p7_ic^dCjF3WvvveT1JE3nL4mQ2O{~0fL#* zV>i4g)KfA;157)eM&S?Kug5BVYo{PA`AN8))LD*KUlt&%!GdnRVUZdS#hdu&1jKNT zE3>H)Eka35Vd;qzh4RReCn&;3WVo3u==~cOsxdLY$T&Hbp2FMf=|9Rz2M>w{-DJ7x zSpI8~2ksP-N>D?Hc=U!Kp07|674!Q0F;T+k({s?ENFk|;7AvWT{<1HtJ{Z+;jdFJ?z}3P>d;!f zwDZbo+@;*udDT$vp!+(T4$~Zv;2Wmk7mqK zquh&(PNoz+^5@BWCKVLDC?qRawxBEE(rrts^A>qTs!ytOlspkGasLwfZ-{w1MSLap zj*{HogHDeW)Iv^7v7|T#AKP*NDBQ7CI8vUFl9DdIeB$JKCEl?gN=}{nMo5w31fg)c zAZ&fW70;SdF>(E2=NFTZ0*^RR{yt;E&q~Cy9~SlR-$zI(!t*LUiYr|3DAgP%;Hoa? zxIJIv;_SkkwOEa+T*g9;N^wg^eDsIo z&#hw;6KT`)ki^uDC8s_>R1844G`r~_ttX5d*mR&~A3LDwfU0)oCeo|=a}OuorhV0D za-bXH5>nm`jop*mTM1&jkl+^Rr2JrLJn@Y9z7}95SU*Z|$BA!9HsyyS|PM=f5X(8QthJM1@IR5`N&)|ow zYs52@s5Rjk6tM<8gAP+ypJ!048u1Le%%Mg+gCf?PXDD|_)R<>zJ;T^b&fV_f8CfQt zK`xzVkVE4cs$Jn3+|GFhHF|Y;28FLC&)`vN!ZTbktH(1aUX6GLMXUkOaFw%KJi|R& zHF$=5)N1ey{`5J|;C9Y4xSjJ1ce~Cr$V2BDSq7frsXM!P20wZkMmz)KE%6L8h-Yx0 zGoK>vKs=*EUCQ9sGtB4CGx#y)8u1JzYE5_sMXUkOpySHb=NS~MMm&SAOQ;dgpolf+8OkjUHRc&w z&oK6qbGN&A1~c&ta_KyS92(D1?F!G}cFr@X(W}EVD10?}29Ht`p5cmFJ)S}FYQ!@r zVhwnPtDM#18Sc@l!86>WR)c5or_Xr?w{xDs?VM-0+jX8n9y-rp2A<)mJ8L|{%2iRS zM#|lg63=>0J;GwrPpN7u-H--^TTE&yRa>PSQom4(Nn52Vt#m``bJo-L*tJY2v52R5 zGNkS}GG$g_6_W}pB=Ob-^Tu~-ZFTch^jma6Kue38VylQ0TOAh+?{U-2yThvZDrztP zmQr+5x1>yKcfm39*NUC}pfz#WHkKWGj4XVw6Sg*`>?%1-*FhfQI{^gS+LNbow}HUv z1Iy=k|6mUyRJ`?h@9`y*G1RMjpuB}2A`4!Mn^1DIpluX*JHPj!pR%BABC@a;7peA> z2H|BXk1UxY2ycA=g_E*33OlT3-7Tca*c-L!H9^@*a2#6+j^Bo@M*CH|u#R*`;)&ZR zexlq1UV9Dk*W_T66vfY1<72Y{!U_+Ik(@4UQT7lVTtM-M$xvh6bwG>nEfhrk@$V+^7aw-r|&-X%{yBJLEV|q{rzKymY&A0Cn5`r_h4I=sMZ>H z*}7^CrNGYrqWzRz3tmvSeTrs%b0>6ivutY-YtrKgo!4V@5n`=O69jeh61pCxCf?(TJcKk848+25H0KGx6O+ZTtOA9OVhJak8 z?s&NEkIJ8B!R_E4nbu6hE&aB1$9#O5ZECtTU3v^@Nb_NHj*<7LThc2xuB-a(wkGM0 zsrWG4lr(Fa_}H;w)?rdQtVN^cqDRuwHmrk}`?uSgq(N!KztAQ=dNkE9wP=+5NhDK&2YK($*x!@unVDDzp|#!|KPak!2 z;*B(rnmqNx?=)1Jv^_=SYCoz^RLrn3ZL9byx*!as?dk^7x`DKAApO@3q@=XK(CNvL z!e}5h9V|Dyq)-}24Vny$peC2pfd*28LIWeH$tAU;fz+VazzAw`Ng-!FEKdWeLCc{k zJ9NX#P18|KnvUzkyLO9~u+(+MLqD$;zAMT@n z`k~6r098eGevGixxXr{Yv724|;BKjL(}{TtirFPbQ;9+$l@Atvf8$ZXqtFe(?3v%o zz8#DG?9$B;(+Um`@+e~8EZkNyCT7WrF4)vw_L?9JF5trOVRvErHQ_M`<9*~_6fhs6 z;!8Z>5qGIMU%2U~FlbzNzDdrt36K1e5;@;72$?#& z9PNv6?&54A7iWdY7yc;wX&#+582LSI)EIS}kZFxIh`9JHUAisJmI^b)mTXIc=|Pmb zJXj2Ahi`C+u0?NMj1uE{&pPu-Zhg^P7o5a69!FpF)+Ht}hWqG?-nytn=N0L0i!+}x z*B8BY;fSA1FQq%rwMB2UG-P)1qwl50C2+HdWOj+|s6=%=nCox;DLuIAG?qVh(RGn; zmE*?x5}ZD96+7_Ws4v0kGgjU!MAs6WK2;S%c$_mIh1Qqg^jRrCK3z(89BWH(=9#G3 z#m{G#8W-fu(^IocY+l!c$3hYLPwK&$-lE{dm&k;!LM44?45pJlBo+fFJ_!cXiO+iB z$2Mfox|Oy!%{FqJQ0Yo=9j z{vgX~g7W!ma@-%4Dkz`3Cda)($_<9kL(&JVRdU-D*GD2XGA_<+r++GvzE!P+?UK}9 z|9CZzoG2(CtM=d+Bq(2`M&XoiQNvWeJnb?C{_Sa0kAHL;7Ue^dne20A$6;f`XTx=S ze9wfs#=TdlYh0y6|I>P|*LY={;(vm%h~4hm|6}hx;N&Qd#o?LFyBBsg?`+OF@6P6& zPp5Eq%0lUsi54JXZ1XIEFcKhxZ7}9xKO6|}0XB}vV3G|OM6kieCWFX9fFyKECtdie zXD{vP#KW@v{`j+gpuOqos*crF)!kJ!3Q}RBhU}z@gg>_9{?Oks+%PyNjXcwv*}?VNnlmco$UD_Ya;2AD(^bz@HaW)MH#^3yUR(Hn7?(zC5 z__iV7Q>oo&k5s>F1NFcbirV}v1z$U)2M;F?o_~1v?!Ke%k+*#p2K(dPi^%)I>t-$V z|1kQxAw7V*zxijl8CxIU2KQdq-%Dlw^elxwcL-mbumR4H5rROz{q>zb^1@w7&Dx_B zx$ga}|5-Ez=nIE*3Ge6o&Vv2bfoGRMOoT^Xd*(*CA48u!Y)k>0Jcvd>eeJu4URVPq z9=>u<-%T*(8Ok^n>5S7;gSrM?29hMfzyfS!m9|aT+kXQY_V2y7LV@2LV|e$q6v&su zPcNdD9fm#BPv{@ya0i`akjK9K?6F&+X@}-M{K?0+J4o`YfO4P?WI6LsKZgQ#fF1Yl z7zIM9c-yN~?BEKDdTkf_*)&$d%4N5s5)Phwb|mxE9Z=vsDQb0J?3Qhr zJ+WJMOvGi8yBF+DlVq(YvF4*MkzaXY*#}Dj`MyrP^F!$I#Z+p;ff-b?fAMn*sAm^H z_qB<*Omd+6qgl}GzD>%d9c7RMGd`Y6 za>%|fKLw;nrT3oQK->dEoVjoi=;(WU9s=KsJs&qHnL)m;HQe|=1})jKccsCg2O3V; z=e&C<8eZy#XBx=;FvCZ8p>6HqGlt<4hvlLBMzM#s!-NiehrAX3^c^J08+RXl{R+)V z(;!^_6*iq>h^H9hDTa88Ax_|Hrx+qFq)%yxr!>S<8saGp@mA1d|E)E|Jh}-{Kqy}F z*pvM)bi@AO=(1f$eitM|WZ(6+3qP@wk;7~4w>=8!{brJUWj(nCwyA-kC>bG-{`@-f z_WuHu!QD$x@}?h=cYOmTUtKpjH%RU`lH|*P{U~4$M#&)AcdhBXPaGt5sMm5UD*o70 z{Vz7^f4%>q5V;BeC7FZG6tZIbyf+Xu?Nj5}AcMbaqEC(E*mC;&#_^vQ56Q-(FGAr& z8UhF@_=-WJ3!R}1lda^CeeB8hIe!{yX$v`MK+Q*eY3dk?m;vva|1?4Tdv(3QD-4$N z=p+e6a02R@DlpDJ;N|!bLj~hQynT$i=qH-GKF3cqb!{ids0;ei)G#|>Z;#J^Y9 z$Egd2Rs!mpDrU_;;N|!bLj~hQjEzwj{X|pO=lF@HE^M5-pf61wL)3NL05(DVdv$%B zx<;cOLC8?J9#Sh%+zTzbBGQsW^powxPYOCx&&Xl?(A{uiJ#=tfS;on}(OaS?`=L~T zLy=ex_7;cV1^21!!9#G&i$eX36!6yFU!;$SM|A7)JEQAuG9;I!`H*jz!TRU0(YcFq@a=758eIlyN}Ez8@hWR z=~#*P14j00UU37R^B=j0O}MDWKo@ z*e8eovYMWMdb0xaKYC^KRAc@da{wN0b*S|q6hX~vgcYKT_zVO$kucE(lLpPqsDC7) zT;%%uxBSBWRM2l@!?k~qYNc`E3r1gkjB2Bf1yk$RpUXV=*!p#;APV0L<-&kH@_lIf z!1}!j6dMWvTd8AV-fq7__)rc$3Oewi?acYy`2%NKGetoWH3Fb}4`!%8ev4Xs0NtJa_XxDD0e!|i z?!19Btoe$df}oky;WxfIb6^^^eiyX+=(YDXm%VNdch0~%YoR8nHK6xixAVHksOF*v zQ`ttl*MJw=z?!jkGpW7P2R?w_#a{hha>apVsOVM-#$y%uhYhT);llkADJJRO~1#=whnsyBeVIp(8CZp zoEb))OH)VTms)!R?}nkZD7Y4cNHS9Wo#^GRVczf%8lgiQo;|Yn(bXt?l94(>=Pft@ zBm2pL1u#Pgw{;dT_-P|^0A&IxJU9r^E{_a82$#S6E{EB9BTD`H2RjcxGo6Y*xTg>9 zrKw-v_V{a%?CHDa`M0k|bzJmX-;-U`2!6RA;rExptr5IES=95t{%~pP*T0|=n{(-( z{T{I2tx-4qg6bK*hN|zMMV;~ClGs(xM-ejcuKzv?tatF}f1`%1?+4=The&1A885-S z@9sPZ$o~b-lP^&{gAf_|j~l4_z72p!Kkh-;a8N!7$U}pO@*7pZ2fe0x-=bnS?)u<8 z2x32yq@zO}h1>kU?tUGA{piQ58|eL49~vG$gkqu&q(0dOf8K$fHww7;xhgeniUJ-$ zVRq<&V^PQU4G-^w{_fgx@Um%z#;BsozYaHzKb(ZSA1XiVgJseDg)vk&AYa(mcOi85 z^^MOS+4uWZqxI$7|3achG|d>-*hzsPGs=*^Z)WkBCV0+z^rU6D=aA ze)?NPB#K~i@*=Vk{{)3DI~fr<9zEq`MPyS6CnX|L2`4Wi5%x)o$nnOXtcV;h;iN_6 zcoCl>BBv%ZAjfecq;)q0HzFdpPY{t)1*Jhu^2F~ai^%QNiA7{1{?lg=k&Q4@|Eq{Z zU{i@mFp~*SAtBKKPAMS=!r!~)X4cIE-)WYSH2hRDa?p0ijqDo-zGZ6?l87fwAta$O zx7^9TbKnQICLxKSrxcP=Y6>AqLw^P#ITj@iq{F8RNfejpcp=#cP&83Uo_OY`qKhGN zduDrsT#f?KOd%xUniIWzR zH1bIb$?Zr99ublaGQuVZN%Y(hlJpbJpQe8LTZAME*>dtivJvp*WQF8-xR{d_l1(L? zl#oOvoV<`k*e5L{#~XjLLUO!>lNOTWMSO~ooSMu=g=8~fjtEI?f{>gl3J+q2Cw@O! zNMfHNBpU&*K7){K#MSy=g(L!-N=U*!(rcMQN}>^*T1r}t(FiLt5NMW?H2l<3veYQP zNlKz%XH!T?XpJM$$?hDeG>b_DKBbrpxTX-3H1uZ>lVgG5{#P-HLNOgLCL1xCCW^@u z&;3-CF+^e+tU)kGfo!G_lkoC{ViNLBEGB94`8&iUjeoLYl3sl$Ehf?I{T*Twq5mCX z64As-i%A;!B*i2)K};etYTVopf?+|L96;6tX>t&32;diN2jCY>2F9MjIv_<3Ae(>` zIS8fzoACRQ1pwnFs(;qKA02#h0otcGKEB7L`eWaEt^cD(;XvY(71z$~_zAF%k#Fun z-j27C-k80cfYy5T>2_x4t*7({u~BN=f+p_ zAS-^&u44yw(q2rt;T$#8Pi4>##Nz^IsQrIlN6A1%>7|tDC4BS`xke$Mg~nS@-Q&Qi z=`7k?ib?Ii^zpl&e-RFttH0Uv9{RBLp0B=o@Qu~C4Sw|Q7C4xBiwihJ$-9>I#~CAVb7}A%pQp32qD9Uze`2O z3)sCV`}8|sPX6d&D1b#B0{<-Jo^a{&P)Og?7tKEeF}b$=ntI?mDE$D9K9r@8T}JBv zAw_Km)9r=30UbsHxh7nSJQ|MO_l@;O(E-dm4^e;p3Ly8>$Oj;6aJ+IjfVuRy(4;=V z=21t%FX2-9FC%v{!9v``(A*X~(ANl2hU2q&)z3ZdCznlXu z5xWk)4&Ej2_iY1wkXaYK`TT7!HlepsgU|uUR{b#-c8osw7oaCwED zT37$h>qJjuolOKM# zU^Zj+(45)K*<%H+>EZModgs5+8(i5l`{?C8?4H4=@9N?AjOC-xjC}OP!QSpSf7rcn z_>0}$x9r{fgIQk!*sE`KDztquCANk=;}VYt*dLn@&|Tw{qa{_{H_TwtgZud z`ws8j+c$iE*B>70Vs!Q2y`$>~yXV5wP7vH_- z=-ao>o5`Fx2D)N~dB#|xce;7{R+RbQ#cF4>!*;YO|wVV9NzV~&Ye%HrbS$0g7T_rJA-h8(#k1V~#0 za_kI1T-gU_+8?YT;gPywtICa&Zk{XB}8XQE#3z3HAuwB|}rkZj!~e zeZ%ENAE&7=fCfqTX{1=#a!j~L7E}Aqpr{|c{`4I$PX;>{o_LQOn6&E;O}IPW#E? z371&^Pgm#mQ>*io0Ck~bivL>y3McBHNq`EI|LOXKFPP_HZUS&_0~+nhv(9cm``B4469Ev~lha4?UFUyg1)W8!x-TcwqdF*+| z7T)#PX<+p>hB1#n@7RK$JiUA#bL1VMtX(^A423oCXz$Mt{dI8@4sYJkWp^BYb!9Ws z+|wJaIDIZ_?$MvU{rx9To6DsmC85@yK9@K5=pDQN^O+TMnIkWc6fRt|X3jcz@+ z=A8chTOQn1Te)))*x3!zuLt2ZYtEr{H{NppTeD{Wp%*mqM#hoC7gw(yMO{04_GNqi zy#GtARXZ0#nE1i9jb^N!&6<4(Wo%~8{ljw5&Kns=3Ky(eHJgUn4<9C+z4a;O-Al2l zP@j?id^I+w33cPjl|2x_2{m|4PtRq4Fx-bw7l5fBir|F$xu%EJbLg@^>+b^8rlCP_9jMlcsOK+VK8D)WwdK7XA6~>+wR0ZW9FEo6#p*hA$$L9K*utIj)DqY+G%}79 z&Re!@)~ZH>SIwHW=9VAd_tK1|kIkLMr9)4l2BR&-p*6SMaqnyMW<9kSHV}g}{E@=B zOP9`E+2|D7N*uj;*UdZU%;eIMsSx7InY@`tzyJ34|8N>?B?f6o8fnIg2}ld?IQ*C6 zkmlcVz6E<-rGQ0F@rthSl5ondS-Cx&{wFU6*KrVj?KLDnfWuABkztBzOZ=l zC`#{i&h&xBXU>?;rGsQ4jNa*j=>rSTo;jU4^6E(8oYPKQG|x*<(eh4l=iu^2edEtZ zimTQwnhPeFBRd_U5n^j{sy{nzUpa&)yvVD%>oLT!jGB!KmN$QC8~MnwM&F zbPqC?J9>+PL$l@d^~Y$|=xGuIa%|L|Wa{NhQZcIY=J@En2Q)@^iIWGN7((C4?K_uR z3KokaHy@BG`u?sX5XEikAwk1L`-#%qm{W|nLAIwD@hL`piV^=q81ZJ{FvtCyiV<(V z;r0Hh8S$2b4{SX#BmUO#Wt&gTh_`(5*PBnwh&NyL_{S$^#G6mZh&P{z5nuhgYcD+^ zBi{D(-T}mjj~kBBA)*;Ey<otr z;j8+VI~#CtRm5$&Dv}gG@leO^)?>d|k72$MAX@`y?3hbZ`NT$%JMPDxnvG$?=90#) zha|O6+!WURAzhMpv{~6kvnJjQfmNC5H^0J8ds!#=F#>CLE|gJy@7<)t@GZL=eM3H* z6R(0~6c;Twqu^ia7jky*o#@>b|&B-S0z{thXzZ`vS2Ks9HH;KMBPeEUY z-}=F3?&c}zYun8?(izS4#oB!2w+BA?1?Y9?E-*YLbzQz0-+cJTd#?W4^9T%11kg1H zyUBBhrmw+QuHO9O-J3UmrvZi>b~sHAJ2Zi9fBCh|@7}g~^VX3%Z@H7xU*rf<>3IZF+ znM7bCZ-Ky?CWR(2T9f>jBe4H_1_o#KpM`-9prd+rliB-JENswLY83aWJPaARbDE6Y zr(|MqJUeEc5wH$wSl!QYvAviz?zoTgS)q`4y@k&?tiaZwez>nzxf;- zD(}ARlD@U8R$cSXD(9-*U-;;Xm5=Q`Z{-~?tX#SKYb)RS*2z3{!|uH_$o<-oROj~>{x;=UJ_E&Je-Wy`L6>&V(= zu4NzXy=CdrM|Zq<=s7rs{ovxIOIIFxbg6&o;G?%Jw8Cs8l-={xB7x`m*^O?|$TwyT4yZ`65&h ztd!6A{X?vWjy`fm$s0x=4T^pLqyE3;TLd~tHP#)EP`ujiH|JLQ`y!6{o1Iows9rxYy zn|purn|tp0^^RZd*inPC)Dh4ln}+Ym_8vS7iTKyIAlUN5cmMi$%2}OVKz+J!k>774)xC|%?>d2fi zxWPMjfUU~MwYz@vU^JD;{r2ZS{mGAiaaRq%l+=Mg-0IW1peP*~DKE9(S6KVdj-TIg$IpKGyLCnQTA-im zi+GcT|Gewrhko^+Mf4%op$O%t4u!qmRJByDrMwL9PzYvtFyixaQD%f14Ey2jAP~YP zT+$@qPpyqpYva_~n6Ne`=q9Ju#;LV&YHdtd8?;WvyXe?g&hIWDnFhfWPv@OJu<^X| zNNOE_-Qbr-v)1z0o*=P?zh>~lQLxqg)q`IgO|0Ut8oXdMv68=XaN}rV1%JihhS5YX zzjr*boWFeV{L!3c{AGjZjV6%FbncXiCHy6W>qm1I^A`_(VKi|X|Fpq#MiYy8iw4%M zDvtE-Y%VeZ>CBBJQ3$eP4sG#{3n~fMrziCil$D`Jp!l&{;C)c^yi^LJAB|uC!mA!o zZTiQqeD}hex8At zaf+Rs7^hgs8K+oCj8iNm#wivO;}i>taf*e+IK@I@oMIs{PO*~{;}i=y;}i>taf*e+ z1d4^!M2dyvIK@H&4cXuS=Xi>RGAC0kyqZL@@a8y*g?GnMEWA68V(E9y6br8=Q!KnX zj$#q!Kcv^&h1+S0{f8Ae-mD=MEpr0JLeR$v6bq?wiiN~D#ZFF)Q!M0+Q!FILDHamr z6bp%QiiN~D#X@47Vj(e3v5*+2*vW};iiMnUiiN~D#X@2N#X@Q##X@qNVj+PB>F@t@ zJjFtplPMNnO`=$Ma~#FOyW=Pp-W^A=^t)z?g;$d)7G51ku?X`Y(rZkJ(G>d+D{#D7 zL!%KmQ5=Dh*oDTl5uR73SB|}zBC;V)B_YHB3N81~{>#FSwtVls7gl3z?C1rrynQtr zGmq5F!uqe;A&tMC#+X=t&r5FAqZf|lF|i}ex3d^_^>T4C=*={yx|D^@e!;GI=)&V5TK?<37gu9ga32rDzWCbP-(WXG zkP{31mr0CGDhnGreIH}rP_VgI6Q_3Y^zZR--Vojl0TVDj!GovYfN>@cMAP*2pZ?nO z53W8Qe|(Pn11VG2E31yHVRVYQ*x(s=AD25iyO6v3H_f@tGuvZo&pfyOIEV=|d*w^J zpSgh94DmUd%%(71G=JDm^M|JQG?y5C)8GpzDi-1o|IEK*q}0S8P>mCbXd-_=c}?Uq zkw1)Nj?O&5HS>p&{BhbjoJ{Ic5KR-@#2-d-$7lFB{xAYDKF7!Lhe>5j;tyl>H)eMde?Ym-Gdr0- zj6h77*~$C?L41y8wkbXl%^xtDKQz6k`NQa&246tofDnK9XZ{6JYQ6Q~yJsT(;Xiio zzrLYApc;WceCKzv)c!nD5$3<|)BJfetsj!W2V3-3g@R5G7RkyX#8^HGtg9@?m1*AfJT*feR7_0yQM5q&vft;X}i6Bg99`>C^ zD>h@;{MTtAF$MvIbp8F{cbdmE+gaFxcb&>VZaxm8^}i3kcP55KhCzk+>PP$kv!Oza zm4OK9UoSB>ufQVKql>S|k8VB3~UF1zI7ty{KiMcVu>CeFZEIL74S7z<fB=X$7=Q`cIFrF-;A{b#uMz1iA#KoWo#oI4yh@ zgWJvuSTznqu$VMCeDkF=2nT0#S$rmk#lgAo+#;~p74A^dpOx4mSvBep{uYzX=PJ*Gz>j5k>f0S2uUumw1DiPfTWsv@SDnLsp<@wd|T zz`A7c8SEA|8^_r^kyPZ8M7;SbBajNt&Mehr=^X?X)(GwXg3$E-<))sy%m%(a*k(8Mt6+b9j6XpMwi{ES@Fpw>bSX14)g} zQ8S_b0|>@wWAHGZkc+eVtpZux7IU}vB%>i`F&}~k;rJV9g|Hboi-9xPK;?uv>~dQa z6}yu%hVub5pJoe==KPWT8mbv?@Tp{f?51py3FF6^*kM(h(V&^>A_`h`Kqu|5ulaq$+RpdA;sL4#m-jvD$tj1B+*2_OU@ zOkBp3@NpjVINrQ<@8w&!U9t7@E3dfX>)W<&yZnl++wtuOuGxO&)!VPU=Id8|b1TTO zT{4MGD3=IjIuW4|$q7Oz6w3*V(G;*+%zB+&A=CI&5fOU1DWJ7bX`ekFc2a4ZP%nI)kSYi<D(H@aftTBj4i9)5;8x%5)MV^lNb%BT^Z)p_nI`Eu>18SUT+=>6uupAoXgQMlFLHWpbI#l6EIc9aK7Bfh z(v?Y;>amKYR5n3fgM>(k3*XX7l zgj%B0l0JpQpwnqHZdbNm&R0YEbhQjXuL4mhRdPZq5=xbN(oMJ&4!I?0_SLeLV%?K2 zRO6^oBA^P1SR_}-6iTPesapZXgUzj)FQ=HCQ(l!ARsWgTq#luwIZoh zr4y+|Mt43H4-~4Yj*>spQD{_0h)IoDCAZ5pdX-Tj6WP^qhtpjx=Id!PT0@g^OeBO> z2LL?M*JkZWND=%i|?DnR*Co=80w4}0_F2E|EX9m4-&y+}kr7YsT@ zFmBHKL-}O7K<27Z)NGN25Ge?)QlWuLNr_aeR(UGPVk#P-qEskd_R#Yyg=MXTVbIA` zBDqkhmdSOdTqu?CL}S5VGFXoS6fW8&loCQ%*$R=CuGt{c>Vn~9$`%e%!BDs{DIjO`#6-=eko{m!yna|>(R0^R% z_=cDu(ZmokSXLsXRH!mZ{9e*-v8in-g9)t@;3L4W)TCGq%mA?!l}07Asr^QSIuZ?N z8-y#?h@>)w1WM8h36WYXmr4~5o5$_CSx%SG-uiEmpZI^xRlCNEQ*_KPTY~6h6C12aNWizPcn=ambG5lPz z>EbPyApM-b=~b1OEmjE$mz=-BD^|E9t#Xk-NVK#00-i=G=eH4{T4A5i;s!}J4x7sX z4FTt4uNZ|~38@x(05}Doj38+~l{mNkwh2cfhfIOcqFBP^no+^ur4d_M|Cb za7pFvs7|?&6#HE+sm9LFD#c!vAusiX4OUVm%cCWb&=d(fQLCQ7gb~sWP1lUvlqQT&#RFEgLBnIQKvm(gKn3Q427t6>KK4Y^CbJja>7b6-s8jf?CXdg-@mVZs zDsBi;X|s>?x4>=+jSU}$l+Q%_BWN!6!VH5}*==%l+SD7#wop=TEUKA_ps^V8xExWB z)70uzwDXuq&Fq6+3Jb;wRC5>}9`^Xmkj|J7w$7BvH|WI%pUCPF^GacJC1)>q^B#>_ zt+tLU>I_s9Iag#+I5U9(DMrP$6Y9s#OL;%ZD8lpWOl* zI~-^b<`Q9=rWy4GN2W`nJujrNbR^7fkF4%2c6r>De6Ax9kxTp{iIm0XN(F2J=Q7wZ zx%J+5+W(ENm77S)^?KIF+phBX++{+SoT=&@JWpEq*Cf=HP{0h8|?n#WeaJJgoIos3sxtK-NNJm9z4>6WVSi1aaDSnsdb~x zX7TAP8d;0d=5o1hg^;K2kXrpQA&<}Ia+$5L4HobPuuehe=V8ytGAf(S9O_W$H##+* zxHE2(Ny`p?KCSdbgGPIsDd<)cLVhcc%LjVk00V<{4NU9_NkZq58*{TYyIwz%FeP;LlF>}HNb6h;RzHHWXlHkxD(dr~Wec|e21 zz}kTUBC3EofsTZRHj~h21opTgqPN%ye~aB=5h^IV%hlpCh|(?E5D$ij0UQwaU$m@1 zQ-e@tZ3faP(%Z!54Jn&Z->z#H=#_$4L8)qyn2oZKL1hy#*-#Y=w#en_#ga)aN8ETvDiH+X>}{ zluWM`Cd-t6$S5Y=Ip$3yH-;Xr00AK#Td<&Q`fpE0D`!p=_XJ8j=u* z*?dwVHEHn{5#Sn$mR6u3s0m@9-5Vqtl|Drhe5FlhG}Z2>4JtBHur>TjdI!ku)gz8^c1aOszJHgbFs(D`2*8nM9j}5CE43cvxdF zKhRo)2YXN+_9wze$|VxLg-L7-N^qW#p%4lcQc0TCuH&>2oK}U1OK*9Bkk~N5&{2E< zw&8&Kq%tNkXyrO9$GFjG;!4?Ufmp$^xPk(e4TpV!Os!Kgxj;tHY8D4r3)(wFy*%s> zCcF`J>)*WQTi^KlH?R8ERok!n=C-T1f8$%*FWE4FXF^6T3#hut!N z>*Ich${w)U5*~voiRD!jv?IV@{3Fr0{smX06iZk9u4#1MKl&gSHn`YOxgbe1%X2$~C08!krO? zLGO=-6Aqy%ZAwMlMz7cyum=+sy*EI)voV+6OA?^7!!|-GgK{KFNXbQj`dHZL^aca* zbWmlC*^AK#9DZ7JCV$=&Nl=AII%;q`EDBOc5DKM4PUzrA1hvCnkt2MJ%7haxcd`)h z8h!S3BH;|i3}v@7;R}_m$!sJFQb(gx!?qk$SJ=du2nFcppvVI5&S;IwScA!&Ct@@y zsERA<54fwbU^MG1*@G2mP}CyQlL`_xb{d5gw!l*Og|UO)rYrt%FcD2BB1SWrs+eOz zN2um>*RzR4DV>joA_1pDC)CMwGPPN!0u3IN^#nnu29L;puVeM-w4z5crNOWmg1#!$YuoDK9vX? z6IP2_4troy1&1LTp%QjoFuqV{JsXM?Go}ug#}u+g(zaN_sV--!QaM&hm6Kk-IT3Z* zVe6&Q$}|$#*uxH32}2E-nSwu_jYVrFttk@UFuyq)`cH zE(WzuVUWsXu<=8fu8cPiTkx_^X)bu;xtOB_|A#aALbMR8#T}WL&TDlmEgC{2F~9)J zN!a}W>*EP`T9c7vEl?`ieb#)SPzf8$5mP--$RyI)Y`GAQ1Oj1eP;J!6bSi^RqLz>f zphqP=qoH~vTuqc~X1^`#jaCXUx~_VnQcESPm0~TE_gFI)hfAr~x{NBjSR+#!6mnPu zh#1P1d>W1&>RGEbYtNLE$yhv;PvuLASg}~EWJ*DOPOWuHZ91Px4W}b2*c`(F0F7G8 z6^fa3wU!E6isno$?Z{E~WIB)_5%Hj7qf_CKqajfk}F^ zxsumyE}8rt0d3Y}$z{@oXe^vfmy%R6uZ2B|QY$i=2$7k9gE6%Xm>xoP!LgJt5ieR@ zrjjdEO?kq$Ko-915YKz5d^r~m#QmgICsm4NO0irACqE)BDTK{0tkq!FozDge^@!1! zwnYkgpEC~k3zRz*=>c;R*rtNN?DX2qp{3 z0_Aqr;`(yYo62PTQ6rq7`7IWMQK!-p8YwA88*LG+bgu9TM8cXvUo= z=}VPzp^}JKLct8swMnm#8VydlRwYtOR4Snot(wQ2sbJ0@FGM0TquX1^=R(O)qU_Jt zlF>YsDmi_y*8NtARbwDsQU#ov0pY`m5zUnzv$^8oOsSj?*o|>RrI2*T0^SUjtw$XR zZ?b5Px&juxNNX|z^Hq~lDLN~Fb2T^&;Rx~66X{s0oJzTZ#&j@~@C7_cyWbnBQQ5LJ zmvkoG4wqk~mKoseRBF&G^)iVTcneL5!Ehj9DTdQ(pDE*vQ;LKm7uFgql|;HKk5Ha? zxZt;va7-YQ%4GyF3#mj(N`Qp}Ia$Lx3RYe@5I5z*(YQThq7qS?Js(d}hGf`L)&z4} zwHnUQbc7uElNc5&5EG5+Hbs09dno3Nm`X7!YzpIpQ;!%F%$=;kO60RtC#dTr>NhmcgI~-z3pcW;ZTEc%Kp^ktt@u?Egl;p|5Y!cu{lsW0mahdjZUI%Eij zwJx<9cD<5lGHvitVz{kydIN5WMlT{jPQlr*P$-d$l`!6jOHk%;IAHQc%zC|AVKl&T zn#rS1Ivg^+k`zk}ApAj^!gE5Bd$x7xVNm`f!lA9F{!AExCTARDyAiqOtZZNTzj^4u5nsbE=+pxY*uTdh&>mJm zFXOH-h+ZIt=wY3HiJO>HvzqjJE937GBjqnd|ZN!9b5CzN7uj+@rbVvvX}+?l7?RnR0Vwui;L;ySozoZ#Fu z31a9%@O|mKbfR-l6U2!J>hqj4I*IHt-=3yUjnCqk<$hEC=xiXPeJJAiX&SZHpAu`- zRDX(0KJCRbbp|-_G-Wi;-iU$zB#4Q#H)5dQ1o3&EqtDnjuzhD;@yz>=PKTKoS&>a1 zY+&Hb+a8iF-ED=F_|Zk#1i`}wAD@ddM;2vMCKnr!ec*!J2~IW>CY*x}aDIL@+=d+Q zV}m#c5EAR=o8j)`1n4a681o0Wa}c{2S)fgoGqH~u-`yePfg$3A6*>uINVSi-ukY-M zE3^sXGmhibV1I)b$LN|mo-v|A#+TIS9E=en${t-&qjNB2=48e=5pgnOY|5L&7$@e9 zj(byu%}Z;E?Q9s6K@p#5Kaa5Zav5_I5sF_5@Q_4f{clb5oI*b-ektufS5RYlNlp|_?MZm6F2nJ7$fT9r!z(baYDw3 zGCzeeqU=v$j3^rj>4c0C<$nreMA`ofV{CwY8e>EdCmy=bkb^w15E$64HGHK`oPfTb z^~Yo1LmFely9?3x7#`&I%>3c^kO&xY@M_8i^8W^%IpW|oW#++A2QO^A+)>1Xqn=$; z<$VfX|Bi+)S5t-b(z@pRpfOJH<(dRBv>APkWBWv3t|o{R57g&5XLJ&QF^;%$HFauy z7RMa9n({|y1Nm?vjd5)HM>9sta>N*K z`5l2Y#tAEQ63CEdm^s|HVd4sHg7}Q%I2G6?0>eiB3DI98vYu(UaC`vPT*zt-OikGw zjhwF#bF>^|8@Q9~r|njd9SaBNfqfkQ2?OH_#43Z)qyl4hC4hjVR>PhZ>~X+}2(0$N z4+^}(IC8F1$Ydf1k6p;z2N?(f2sou6TT*1D*`iPx^)WNi?gJPce*+?gs*>kxXh2n5C*42m$kjR}O)!fwaG#j2e!%vQJy z6_1O8dXRGXwynW4nTfj2?q1ok|f=HmqMuKVeL1+iiB)^@j4vN?k zTr$8S1&ETMyFkv+2t=-JV3@@MD?bLatwpMr@>DvoZ37a3Q3JOt7+VH1;0C)qPJ64~ zEow8Rg|n6~TeD)x8cc(|ik2FoG?IaM z&=W2NJEPf5&QM=enY&QEXw{l|r!Sd>pujj=U{)AiUR^!no8IB9E=v0s&vEuFoi%OA zym^(=md}3%GviAVUA-%3ELgUD2|ey?oW&Eh%XLmWSM7B>YKcy^sM8uZgl9){xm0OB zJr*rsvG0)JWQ)dPG4Tnp!0q4^=ao7gv$|J%>-JR82cd^v!ol5;Ze?ce(wIhjnryxmoQp%8W}_4ug+G_=#Fo3DX!G(Gw100W9bNRR|*%8)6qB9E9 z=gycre^F)8H2ty}sQtiKnF5vA5zTtF)ne6NFT1KUYtv`XTF~p8Kex*{_p}%)j?ZSx z+8I`pj8u9n9hq82J%2{9SesEht!Mch*RloW$c#p{oHnsqpwtx2{+O~DPE8BXN#y6v zm|mJ+U(!2s?)>gWxznIp9Dj|&6f(f1K&@BU%{nkmGo?E_i!)=nxif6tr4`eAdg-}` zb=1NUs&zKG(`Wa^z_tqv+&ibmXU&=^Iepr6dTbfs_X>`~3h<)SwYSQH8mn9rGGt`w zg0>v%oo}F&JGO-v!RnV2&$b zwsF`zt^m$!TDaUc0S_dHQcM`^5@3CM5f=b=OIXIh1i(lc_{x5k1aBj(s!X8fwc1gY zh~|8WU$JL}H~jm2wkm#71N6B&{wkZ&pYbKuyU0xz0^t^Fi<%t8&0BYvxQOj(`~hSPP>CYXR#%tJdgt z+l+ZnqV6pB%vd-dI(QVZRv20Q9R`~XEEL?P+Z3eD?lQ|A2DL2?##S0cipVBd$Zk=! zY8)z!JJ=d7nEcf`RhgkLOi$MXJu^Ga_XZ!jgqE zXQXP2yO$s^s1ckz_^s_NEfNtQTsvh}rOptsk#287qL(L2sXP*5eBjgK77$PZWh;wI z5G@*8cfKVUs~B3cU>Aw>j9p0n@Yu{Y9!~@w<1Def-957qlO;RTDzC3nNx(3GXa~U# zYn%aw{WwO5ZPr{Rl+c(0?Pf6QaT~zj6It#c*C`b^SINP7QY9kQ0T1c5D}!XjLc4K+ zHIGyy;=z;Ye|Q%40R156W}*R zMuSXku*eLg$ze5_RTghVQ%z-Sg>ooVrG4Rq8j)BewI~!CyF=p9kz$t{{1WROm7K>} zYB;(PVkHc+K?T-P5<;n!+emQbv&nMxVzt^~O$DJr4EbAPsZ=B~OVmQrsDjSvC02X5 z>YzZ6WyvK!_EF-|+N+Pps)FfD!ip(al!|aQtsZcKO52xu76e3qcl?*KIG$NUn zFoFZJiKKj`LcN}j##4=MfkmA{sugR&n^H`u6&jsHrS!%l$nCTg%g6MnF111{l$k_e zais(|W~oBq4$3ltY$2I)TPWy`@C|SQ2AeY=4)6g4PiBQgDG{5B-lQVowFdNb$3@^k zC{vJ1urd=NpFOa~mWd6ee5#|aj40^&l&XQm#0C*ExKhb98ZeeplQxqnmW*lLN*h%4 z9{6F>u6;5nfdr#g!eG?~G7hN=cMV8<$dM2iy$*hVAZEdvnvj~oW{U))Jh9Ada)(?x ze@Lll3=YhrPz5!q2`CFJkcC#2)f`URO_8D}0r3~W*>D#u90>su0Ek0PXcbbCS!&no z9Klf1;djLXRiR4wGGy6JzM$1q4a8E_a2}K zlZm8qQB)k57m9>-U(lB+W>dLZwj*CISAw-dty1(C646R9S4R^J^J1Vpkz_jDkxO^f z67?dOe3uf{Y)3s?E>{cL8iE2xQJu_f3M72dK*evZ2U62)9ThMh&D4syWIC5Apr(Pl zrCzOe`J8@jxEjlL6n)W3N4-|A=X1q6=;tNS?P&T{!}_pVJ)uNg6-Xzc@zZ41db9wJ z;rVi<2rkd0@FhYh1s4l_+$r%J1JNj@w>u2UxT;dD)Z_J9EZq?=Wzyg?`yzN@tMtlD z+T*j>qjtI1CW+@XnQ)9s0BA5@t#$ZPu?|$EL@w2VoPeENC@Qyz?Gc|Vobr_-*;+jx zu4ZF}s5D(iW2aRT2Ajeia7QVNQ(_DST_R_uT6I?|g=#LG1t(~F#t0!;(W{Mmg)$~~ zn=KYwFleim@&#bdB{0mb=Syk2yJo8)EDgo7wnEGkiiD#He@7)>sF%~xLaA2C(IYGs zYP52Dm~x19VJjF@2Hlj`k1` zl>DV?2CAy(E16;}Q>>=zH3TQpn4ErtFHoVfO9h&oW}fco>5N;OieBx)5}2mpgro7CX^8}lU6F>ugNBr1h)y(3>Q z)vTdlg)YE@p3+a3aaG8>3_0=aO>UaIMtxs8>J&;pKsPp$c#~0fJE>lS-9RDJfTgb*@|~Gf9=C+TuwkLf*6^ zU!=(m%tsAE4Iv^$gcigEcvb5xdRrh6q5`S3KSa}{Or(J|Ema#NI`9gyE4=Zz6JqIu z_kKlD%awCT&VUoVQX1uPN!SE^x4qM;b97`;#}G%Q-d9 ze86`>HRb_+V{C*v)ndM|MZguXKwX5`6Vi52klVn=8RI~V9nf>Z*BOo=(EmK_4T)Z;VT&ymk9I@I z6eLwVlF5@3dNyv0w-5#aE7e;V~tgvtsE``Vidt~Q9A_ML8ocpk_{&u;DWBt+qpsrCL*<*pHy;u(y*MT z%xD7wo^O7oG}p%Fu^Fvx?VzuV`2_f&qm2U84@Y^}A7e8VW>+?&;|b5t3Plco4m`r9 zdt$Oy`>a@THmQL9M;jb%NZMIAzYV;>k-O;-2Zr}?&peUbGb=&yigXKF zgFV0$U_zFml?P{7Ob#4HF|jsmXEf#YB=t_l%^vrb5 zIp?mNqn!X?JF4bC6a@LVy7A+2A$V!pI0L5XOLV03#ukg`}0pHXk;@@DV-? zLIeq=UG46y{`Xe*%+733^#2=v_Cf4)Rh@gQ>V{M2oO|B)>gB!FfWj^?r9yPhG}t%L z)zRODa3y31TkvmNFTni>s*=#vW9d_`s@-1074)yJ`PXpOpJt+%s_ z1BV981n_rTxxG9D_6X1v#XDF(H;so?y-KsDbkQZ$f#OIZjDRa042t_r{?VKZdHcVI z-__cN-T9ThLQ{5DBXOZ!0A zKv!2M=E@+VYK}WTN9R`hmDtBqRT-Q6gB;u|DSE*J@0Tc4chB|sW2$L2b zkVM}Q#BD;LDGK9KUJu0neiXkvi#<9V*j=>HCL!5lzxAo0(@m% zy^`*ZwpL))5szjtw#u?51{Yo!UK9+fl1-uezqUa<@#T zGF4|en97dp6M$P%{RTS^Zf0*k9`>>6+SHZ<>8%S(Q)AiLnZ>QUW~vpdwKg^FC{z<$ zWMDnv@NHAk^9?sAXu%oco*Wio==Z(}_=zjaTtw6bmc z;%s_hZp*BBXL-xcx$u&+TB=u4<)PF{aeOf{R-G9gj}{_*?yy%N)=D|8gHViZti#2n zh55xLOL5z-Jr``ByJ)sl8g*7?i<7iYT1pA+-mwTmmK`8e&^eXr_7TZIr(zH^To@vF z>u9JmqIa7ne9B9PcvJ;E6K2<#XTs)6a`N*@USzahinqCW0}@!&dW~sBIn1??#S^fP zupZHgv?J*W>*$#2!dSe(pY-HJ-M(I*-fA;U*2mM9@r7M8{c5j5XHgGv)H1oLTd&FK z`axedfKmFejrN4~aiKLjbD^(P8*@5`k~)vZ(e9>vhPlb{ylZNHdqHLGfKS!mA{I(I z21lHFe7M{Cd&mmRqr?3kxiKFe%@r<6xTiURj!_`h!LB!=_jPQqSwp2lecZ$6inaXy zRsr8;?CsKzh`M1L5KR5y7{?`vxP5_2@l6hDi^Z09h1H6nGi4eZPb^eK=BaGl73~!a z3<*0Csgn$MbP5Em1O1qf=fkp^DWQqo2XvEEgH?r0iH5YiO&`7)1r8BG8W?n zof3gVJD^k;`6FUIXMjHh1x8kozE+%=tCts-b{t%~u(tP2x#_9d>O?W&t5Va+DwQwt zQhdMKsF$07nKuj$z}W8OfRhBX`Oynf`swZ4R<>?iUa4)X@2u^bDQ&4vN4HnwDHrv{q|+0V>5Pd#>hVOpI-8a^(%aEZ#@XSm%Uc$wmv`)$+rGOtu{=A!vNE=7Y9>25 zH(HpQoT$!Zs=-{j6sQ$^fxI;;)50`$n)tn3kn^z~-CCdAZ?4pLZ{JeeI=-_yySuu$ zwYDu#nVc=vrb@~Av2By?x$!``VwaT6<00#$r=K!)b`3OId!Z0b7w2}?S0=~ii}Ty( z7N)jtnJJD>Y;jG`Y^ximQb|wMRSt(|qob2Xok1n=SQVbm0Ri*novCT-mBn(PK6&vz z@ATYaI9FQG+3S({I_^A{akvb5UN92!T0|ZJ;HUCl71%n-_q&7j_!M~C6oPQRIChaL zM9VGkCsf{2v9`QCPz%_SvABwF4cny=gTbVdn{~WCPDckQL!igL7Kmt^<^W}iNG?r| zc@rVNg(o+-Xl#kgwGX6ow(AiaTQJ>*q5IT&%+Yx=U3~&v$y#bpdw*9)S64@SUpw@F7x*yny+J>H&7|PUL*DVp z?c)b$c5lnZb-J!$sjeWfTHteX+d8S<* z&c!_!%vP%lmD-qLDpe?WCPJ=kLK1dq1PYzeHlk9<)G{#$Z}1{?o`!UIf|s%5%Z%NIv;^~qFB;`F-tl$HvMbX^0MVZLmbQ1zhjV|l68 zNtI{IJ9b~ZuteAVcD*Q?N;&N^p;jwW3Phsb5fD^%xA*i7^ux*mIV4C^h#}E|){ zvjoEmPa+63kqU;rnbadh6@lBqZn_Zlz7wV;z zxqbDWd#0wwcP_-YjFxMYV^gJUp)yg*jg6KpiBWydAxRn~Znc5S<#XYAkq^vNwZ2pi zPgl3TY1hKkXt`WXPFqVskh%?u%tpCNsC4oa{O%!M2fw#{sJjC^KdtRpV<$a(SeHvG zY7>fL(Q>KL0cybsmvPK1)axvEg`GF*Hkp&--SYlctReLEbak=1Iq>U1rG!qxM9>Cu z6i;BQQlU%1hU2#yWp?oGCH$FwsoXk2@#EE^fY;eM2qK(raALKA!<7sQkl@IYUWL+@ zk9+A$jY?lQ?*w(ELlseatqQqD8Syzw34DK%Pte-e);iDyYD+vX85S5&u8=cg_xmj# zo6_Mi>w?jc%%zr!ooa>M?ocZ1Mje5jg9#59;UNik7^rnZzy|1TkOr3C70g5;igY4m zc1L}Fze}ZadIOZluawz!Zj;5pmkWeaV5%iT0g%cF{}T-C*W?DZiDv=g-J%LO9447g zrIag2_(GvUJp^(q^{{481_nAIINLZwa=sK8dqRiN#rapWTkjCTO%FTOF;C3llsote ztHCWE^3zJKTdY!vu;9VOktJN7oWpJ9o*V*9{&7dhJq&6d#6h%?bj)Q{yTqEPA}x`G zGQLDMsFKJ)jHw1XSf`NVcA#$L18vNJ&fS@SNE?mO7D}6h;x)#tVtd*ZRmvkN1VQ}* zxtNC_p+q1s$P9$&4gmB)Fz1r@q;obZm`=OodTm-~2!=CZkGBv=Sk?YqBAc|^c*6p% zLM$|Cd0d5tKO~ic1`jZKfb&CXgE^c}(o(CI9t?W)IlsY`5=ZsMOg>7d{bq|%;8Y<* zIc$-tK=?5!tIOwuUYgE;Od*^`OxZi)(SzZ^BIOSn zv3Q^pia?adM@e)ko$&^}0ovfnYfUk$BN>SfA(ZKJg(HQ0+85CoO&p!TqsGFEm@gFz zK%9UTDEzOJb2y$1hm)S*a6Yaogz~WYqEf7W_=1sWE}zcYl>o(?uc;t*o= z9I~=RknV?bSzCg#yTsY7B5yBvQW?JstOBlxHJD9e*+;L@$s}4iU#64|at%WY8CR$p zCR`;$DEwHi;Eg#`afipFN+C>PiNotn)5?(87x3kiFyrvaQpuHav@Tz-GRwd!gWf^e z4$jYm*_2o92~jzNI&ROHvnh|4N+pwE459P6n2~M#jaC5WN*UO$2_FF1RR`r*M`H$Y zIG*vSGpTsmsR1`?+~*HPF|N7&8b7h6KNG%xlGC*mKgXPti%Y(N>Uf+8=17v zzzcX2nY1dCP5Gl@heMv#W3yyuESaI5MyXQF2XQKTmBUqWhem|rK}=uH;*8mX0&ZGi z(zz6|fG+@QLePa^3?L{e!#d00h?pbCY7$ERQJ>JMXqzn*ORH0K9IHG(idxh$rAlG2 z2W%d*LoAXEss=$lfYqfTsX#0c5`GCn3Ghu=fd)q`oh`()8Gl}trh_~)6||U;&g5{I zje0O1gI8!!g8O3CPBen|#Ir+3xV*F{G@8w)T>4xxMhEC<)JfSa3ckvrhYcWCL#3z& zm4ldRBUq)x>MJ+{#Dq)W@o=^fj$pYV8rP=+=}aOPA58^Q!y!>Pt~N?V!v-C+CrAKL z5;VWVptcxPa8OSt=Ua(PArni}wpdu6OWQ&@gyFH0nTV;}CcjCnbjk#Lu2jq&R)8K9 zWEPP9e~pO{bHoL@Jx1 ztyI+QOSrvmwWHlC-Gcw($#6!Hb6 zxQpyBc)dT?RA2 zVV25mW`RyCA|HuIc_aCFE*YUi2}dqtF@O`;tFw5Jd63KnGbSv@rPIbl)M<%Oh#Lk> zW`|#=vzXBAVl@28WP+XeRxTR!7^CK_Q;)UQcrumB&{)^Z;@&A!)|`OZ;|#j2&VXKr zMIVWdd{o&a5uuIqBTLqs4upfza3qusC%jn-UkKbWkikHsbJ=Vnok(S}?CkGz>7WIp zITiPXDYF?#6g(7v6bxAL3_^a%fG1msBoT-mr84Op){hG5JYGAS#R651&W_TFTqy1G zVTeV8R<#kIXOxbm@^%aiZy=V56@2)_Wn$56D1k|xL#b>cAIe1&1sq3gd3Rk&*Z>*9ii!DGK~dXpWkbVhdgnq z#zt(BR?h#WvoNpXSvplvIpUc_961PaZv>iyJTW`>t8grk*CnyS7O=)F7N^Ut_JRR= zSRsXuc`O|Xgk7d&AwO!cCBdzf&gWdnCy9h3qHF@o?HS4(!pATbKySK(ev4Ws0r5LX zt5B9s`JzEA2BjRzOg^4TnnNbHKkABP!%lEk1+5t`xSYbCFlDv5kQCz(Nyy3>6k`Vz zOiYx_I`i3l$bxli=*Dm&K;=@Ic#tw!9elDv9m)oxNw7HE+yP_2qX)s1#xe}E0_|tgAv)j_D_vHe#vm}s#1^>@Mvs6;*yRZCluXQ1iE$`b41og;D!q;KwV>PR zPU3kT1!L6X$R;Vj+yu5aD_G-vMuSjdFe>y&+fhnKKtK*|8st21v3AzQIh;wslt>4B z);JY+BYl_l2P0rWOXqUYbRtS8oyiDgad`u12$##_u**dXxlqZ)HyaihRCFSV)$MpN zOZ&l??nciBqjU~~ER)aCX*!SkB0(yZ@`9V4ayiu|p-xLU?%O%+9L(!<+8;=o>|Q?= zjie)VHk-@lGFa)%Gx{}!OqZB7?xRySzepQ4S)CZ%_+Z0)E7*OpST33Hdo*+e!#o_Y zn{p*m>3}B? zvid!CyFOr1ct$|91zTQ#gry-kAcP@bK)e^2=8;egv}u`m)D+Ccoz4K2M%sfZ9te6U zcP#D+`2Aj)+KgNqWHc$HFr{GQz+i=u3^kLD`NH9-35{V5gu1Z-+&m?^5WFQzc zxOIA~VpxtOTp3|*N463V*)cE^Mlmt{uCOoViRHD9NFotT7gC8J=6^PWRG?VU;pKa@ z60>GRAP48%pi~ORA;#K#gbsy3xRs@oSjz`hc{-F1W%KE9JQ0IClfw6!PKLaRXgm<} zxqMcS(XBKaB{DH`X&TBik&GwObSf8&*;14z3Bv(w=eTVun-1l2m`hpc15l|`*{D4O zT6~XRZ?GD8#P$nH zp>RZ?L@pUA?8|15<*TsKghD3c%5_?h ze(_0`OFQT5zGOb8GK7(vpz;UpiLl=l!FLA~`clxR=i`=4CT}rIWd@B3yBUZ^KzOa; zfg5=c3c!VUD7@2DJfjMD=u|rD^##Eu=#Ke3Zl5a^k7n`F)vJ`l0;FYWuY%fQPsJK5)26Eg zjZOG_Q2FV*tFJLsGzdsGl{$@`;`F_f8!xnxo#OPplZnI|Zq)yg3b5tv-+u2m-#vh( z&a;s*VL#)5V$l18EY_>ncz`xF9Xf0Ho4d)?vym~O(zpHi_uuB@`q}B2AkZhN{7n}T zSYt{<7T~5)^1ip6vpBl>{Z}(LZ?L7cvJ_V|hB&&kXOEIQ{;T>%CMHdD0^n-bO+S6& z>w8!mS(w(YlI^2OkG|HwuJu{LZr0#pJ-5bX)xuQiW4#FKDi8CUVjU$+RrAb&mvw~j zQn7Y1M>f{epLqJ|w`0Yg4CbbYyU9JpW}RqMfZEsCrW&=oSj-?jc7SXR{W@NNyzm<) zT6w({ysSk?3B~@Zznos16j=3HgIocZ6u5NlitAnHT5xc%9TGq`qN8@ zopKfmAn25citGQcQDO|7jR6RqHS)s?$knq^0HM;qdh*173UK}GBtQ`8m(6^rn+Tkh z1_&Mb_g9>=IIe#5pO~9BIJz2k3Yiw@(%ysWq4INEHuC=vOQd(1XF`TjxHM(&@r zt7}v1wf-eKr7FDUwY}? zuu}*HQd5NJ6vB7}JEc(p(J9Sg+o&CO3Sl%lb_whha{Jf%0wLBF-S1*yH-;9Pbxik_ z-#i{-1z1O;kH4pX<>wrhpLNvp1c++BF^97!38mKRr~aD!yo>z&yAN+X>t;RMFtnZk z=IZODz>AN+t9$Y1gQ(@j7X96gs!v8(SFj)WAG;~1`KE?PkA5$PM?9MN_YV&2d5S#a zQQr}4KK6qp@=o6+uXp|X$wxt$ zXUL%E8Xe(cJ=0bCS8P<;((DfxtM%z453*Q|KDmPZEEpMnik(`KGl|_qyL*AuY3FxE z?4zZm(X6I>ZF-*E1!t4pa=f`G+!G+o09dZC)ox0~D2qTXahR2S5yjH5e#yXxkj~tW zJX`D$=allgxICVzzlSFWO$K;@TH8DNkR3NPEY=&utz3!5W^$Q4?g;J1Dmv&P2x&`q zUuQpuuab(ya*NYr)nmV&Y%H5EmU79-sq*A>j#Sm%3*wPsxk@^ua|RM&DxNJ)j!onz z%gF1dE9E%{gxIY=WB2tA@ytq{#Y+c~ev-_TvW`?Cog5D(W+tZ-q$cDOcl7p*@a0Ms z_P=q3v(DUPl8UEHiAcCmDrRZs$=ld%eZ8D6l~Jy<(>`-PhSUP&1;xE_%cKwR2tt0* z!tTJ6_w}ol3Y9(LcSRHNiSXEXDl?X-m?w)R@<{CCZN1%Ga3%d(CR8cSvAEk$SR|@r zg$Yk_IyyF1W~6@B+TA0>8oCTTQdBAvj`#}WrD{GFO4KW^$|&KZISIsH+s}Hsd&&M= zLY3YZMfO5IR{`HkYIHPKrQ_p83q$V#C|i5)hzRShN{hwkwfku#IpCqA<$7espQ_Yj z%qzCHws#>JrALU}7j+Vi+Z=F)QlsfoalDo+#inWydX9m5fn^8;RlV%q-*xqH`iBNY zDviM^HG7p#i_OhE{xe{F!EkTEumaNu2Kv)r{1_2S#C-xv&KVY|FzS8=Ql<`ov9RAN z=&XPRYwr_iHCmlqnZs7!ay=u`!tMp4vITk95Nm1c?~p15I`tsm(=8h`l3Lg;zW^5& zkwBU;$qVmb_4V+Fd)o$v2m0EC!w`82kAk#qjBoIkA*YxiS)Utd1tfv11oE+;rDCmi z5Dd!`AV|Y?@R=bIzLO2=5TSBFF|2C#NJl?*sTT;r>mv~HcoMN(q_tQakvN^ECSrNg z(k<=1Tx`UqwV1RzlRTU35!b>bK7h(kA6^i+!Z+3o}NMFh>JJu@@m3h-^k{sL*o;B#VUjBeLql)+~t{Ad) zfcYo6r`Q4uOe1{0m@gUD@WV*HqC*zSioFi4jwaHbq-v7g;gL)+tPo+>IE~gAbC~1? z>~*YGT3mz~08}L+vC04#lELEf+XM!iCx|q`D3U`$>2Lrf9stb`DO6Ie-s*MP9qv#p zj?Y0VgT!oPfZ*Zq|4e~_)?<=LRVKIBW$;H6*%X~YwqCxF1Nlyt5Z)vS%fKIaBiOIc zWC?kw5HgjJXo=LdXL2DVb><47`N*al)Hy?3fk>?|ncW^wEFMSp3TP*gIfAs$R0`<- z48t?P1^Q>NI+#v@6$6zd_oBn90PDRfo63y&`$AS+H|*&QauWE0s2}1wyp(8)O*?6$+KrX|*Gz3F$fcG%_BM1p9oBr0S)S857ikP+2(YpY_vmbw0q%DvbjI!amCQVQh`9kmt)IbvC2YGK2Sa6tY&1w5gMi8 zkzv&^ZwQr)U{ALZffn0aTl|#T5|z7jK`%tuBfk(RK(K+m3J1jy<%&2esl^DOl0mEB zSxuxCE|{Uf-ZLcN$ppZL@W3-QtmO|YMFx*xP{}Y+Vb3=VVX!ulJr6m!6ZS47%ugD- zM{6^An1P20Bhu;(I-^l%GGcU_ELJ=AN%e$c;h;Yni!cNcBO(O>&ig{h9(VcNBnc~; zL;_+mgJw^|;7Hp@ub@*@X37}|N5X!e+hU_EG%`KWAL&TcpG;&D8HQwJP{>uNz%T|a z7a;{Kq5xb-f$W$9T7qdf?0>X^e~LgVcLc44YJ z%x#foW>SJ%#*+vHVy;jOTqIsLw?K^^W+#N zrkF(=i&JzwgGqy-7fT>N65A{$kqJ$zgZdtp$y6Rq&_TIy?ok90C3v5=gP1qDrJxn)DWz-fi}+eMBC5_Q%B8XgE4>Hs&wLhK&c>R< znP(mAz2?_H*ue_2Ui{FVBY|H=h!@uS%NUFG;g6A@il@0O*0u-8PctF)sD0zKv#lt? z`q0OS1NPxBa^1g(;McLk{=U|M zfke=%)4~SsXm1B6I1EN$#|bOSH!OZvIbC&R=8_jKrRSy$(NIP*VCs```h^1{ioRC0 za;UGD1=u5s5OxR{7KD7`YyE}snVEX2x@&4>Uu{}lEZ62_bV3$GMr8oZGBydIZaQ9T zA5uO$*?nA2PbY|R8RTQSlG(DdYjSFN7D(jf#8PZ-sgN5lR>0LXF`Co`om9xm(~kh+ zIWRN?;^5x)Hn3B`MtOX;l3dujuj<=6xn=8tl`ZvBadEr5G*@$GE0fWDB&4Lm8lzdI zQJX|^A$Ip2?&Sc%NO&n8pV?XhM4n3SOcxg~T3MQ$t5tT4*C(galjU+UHLmk`ZGLme zBDD+DDj^7wuzzP?FF?~Up5`B)ncX)^m$nraX5Tzt1(316tzMp(^|~sF7(HgSB#knS z#EE%?K`sUB0*E`VG1vUm?j~8oGi_`Uq#XZjHiHqaA7V2}Bda^v{4~*w5v5XU+ z=RuQ0p%>`@Rpa-L4E6S6=XbOeUKfl3vs<4H1w(?o(EfyXFNVV`udptHLhW0Ab=W3}q?==8p&iS3KawM%!+?kLahnyD;$C-ceb zq-Hcf8c+p%zCci5HEZ;${t=#_4<1qDzK<7kv*YOtw$IEjZQpWmc7Cz6z3iBo&Xj|b zp-C-J8Aw{yDUD{XP$1#@&#OJjgYIKzPney>9aJhu#(R30`Z z2BR*_><++a5lA3I0#g8!&J;=}&B=gXrn*?KP>6ti9rgH(TAe_ucbP@lf?2`A9`j%s zA#m*u!YB;V|Ux`ffr5gdjPg5;~4CNXmXASIW7U zGVaQJecP`2#r*Vqt&*$cXxbW%PztBrXy%K$dt3QJu^g0)eQn(xEj?Xmp~Lf2bbc`u zo}QatCK%~$3rjm@S4xY8$+>B>w+1S(unZJi*fKrh^Z4Wv;b4C!_+%Q8-@{v{0+l&$ zrn;P1-hN=qjsulatWqAI%hzY?;dCtQx9Eow$yl+R$)sZ;janwdKG(y2U=PJie{^<6 zUItOJtDH#e-8VCOaIqF0k6Nb}GSge5Ngt)tD6BBZCo>tZ-l(zaj1HR`UDgR29Q5s@ zGiBdQU}=gnj?&v_7A6kvovv34{@9jCte(k?=bWP{-&i71p)x_Q!)KzT5xs42xOWhg ztEBYY%*;}LSGAt2RTjpl<`=gWV{^%wbTvswM-kA8r^~5yxi~sr%@?y1+K7s>dBn02 zNe_ArrLzmuqf5J&_N?rhFN3?HR!zp|Qe)YYyO_((rUMh1Tq$3kiABn#@?<5HOBg(E zv4YR(f_g;h(oB79sg~G2y}LeET%IXS6syxylc}*-ZLU6BOB5@G%A}`En>`aZmc%9I9aY*3$@XZzi6;Id^wLI z=`=a4R6LAY#ktAZt?8BRh1un~t>rD#u`Q*@WT9FuCF}Z$d1qoQuGLW^ zv5+#V@>oDJC*}@-?0`J@%(mI&Okozh)mxV0RXprueR5&C6rVJQ&F*A2F}*ENjTi&M zu*&C%h4}{IkdUt%k@MjeqqIBfNjN=Jv@o%)Qq7hs`LR;e9mtR7EN-D>+%dN>3-qqR zEwoy^`ba>o9NfN`p&A4&S4JwwO}6y^wq zfeRmHFfR$MSSRbdYKOTJ_SUPjwM!?q?yOf+QE$j$;fP?_DS%2C;dWs@LWe*X^y2^Q z7cq|;A0raP{(s+kiuuzvDji8CQW|TD)&eytPJ|P=Xv7mx#bfetCXoxrwI+og{)~#R zh0DVe!DI#BEHiJ=XQt5!!76LN!SSkV_ z5`aupE*Cchqj^N8{D855?GkN}E%>7GFda&Q#K25>ZBDY+xlyAQV*hnuR1?50IS~Q~ z<)cXj4U}yzi9mTi9t)#cF?XS(m?3t;63K_!HC;$^IJ?VtHNwjsbt`Q1T+b#COVT%Mq@s|J&`K7B4#{y zfX-qeA(%#0g@8Mfae2Y|sKs9Jps>^6Ki;qqU>l(D(J)DO3Xq&U07W5pE{rx$&_GRQ z^KJm?W63;1HWA7j@LC;Cwb7z8DK#=c^BELA;w5n^ob>{D8*zox!E`1V0F5cYP1!U6 zg!you1~@)$jt6aC?3(Vd2^CTy7aO^wxgN`;VPZIgi9jsmRZ%pEB*E((k0b5^*6l(* zpKvDVL?USohbX^SYqHvuTEqN!-K-56f@jRT#Gy*nBKvaD+@P}@{ z7q~8?&4L{xq%x603?fvr-F*j#9sN3RzP_*`L+SJOOe|8c6+-T$#z$G~A-m3Lav>-z zk}CKD0XD4XfxjA)2rLZ*jb(QrFqgtC_9@dApfi)Pf>9MUTCH}M$11njz$GN+3<|hn z3HE;%h_IPEN8H#u{&xtfp?wkoL~MO-w;k~BLVz7$`+jC~`T;I<`D`AY9FzfErB*N~ zmI#312RI$m7q<G zf6rmJn3E|4$|5Q{9|!=%E0Ejua+O%Bm15v(B?=LrJHiJQsQ~zLHs>h174b-kTGk_Zw34{6$f4x zV01r-T;m~&0X7w*Lefdf>vFoZh)~Ex1kU(qB4y3R0;zB!6LmY%v>}>AQC}L|43Q{M z@lML>&jNOf%O0Nx53P&`F~!mCPoCai~@rnBSBy zkfaM>wTNaDp%8H7=9E9IHz~EE2DUwsN`>v-ERbzEx&T_0U_OX148lVoTgfAu6abzd z^?-T=e7?SvoestAE>J82sE2z);YC930RETAAshxMe>|B^C38_wuEr6bjAj9XP9?z- zfDkL4Nn%$HyT_`Q3kb~kzc41^p`_EF_9P2Ye98*3EaGaJTqK=fCw`Gm0vb<4-KVG! zVAIJ|F5*r3%znFKM9#c8MkG+4kvLK9{vecXB9aIt&{6>V;`HaHfjO4 zb<`hnc)eJ|WKhcKV8{sq*F+TP^I$xjh~~nnFleap5}7=D4g9~cT*~32^(oL-cw-@J zK<7}am0+z0N*R2Zz&;}|OQYj3*yuunkN~7|frJ~F<#ZZ1r2`2Y8jQf#eKu%opU!O_ z1_whc=VxAj2-W-J*+K|kDgb{AnWT-*04X1{IP(#jP5&yL!F$<)K}_9*0}hZg0KBDs z7#~)^`V~q_3M zaUK^743r&gIKhNEk59ALV=$X#3NZaC`5HM-A>_!15!3=UQNab?79nEHQs9L#y&eNM zX4L75<$|;tloj~C_^dIFOfN+^SF4xkq+qES0qF>+cfd%4KrQ$u28V?Wgfw`3qcMLf zo60Bwj!c2hA(Hg_bykB)?=tGZAT3cS5J?>d$XCthf;^dn7%ll!bTDZGu(0|*|NO7^ zk@(cd9^!?cp_%y9Gf5Wf>Q9rOs+WdXtldmd%JvfCQ?oxf?QAQGus-%_5}&&Iz9H&q zn#8C2u6zF1`uWJH5~$-ptwM@GGj!%+wLN_l+fp>cXTM&L)BWi_9@9;DJkh^A@sgCZ zUZcOzdV<{v=FD2hp*03^g4NpWy|v@DE?hfa>&La@)32}fC}};xOB%FZvt!YEFCFb; zt#$6N|I9(5T`USd#{7mX>|=%RJaL`RG zVZBH)v9EqFx?}Z6IhLDslu5?^=eLs+->kA+tRsy~>~#_6dNTIW3-3TJe~_~rtUs=$ zW6OR$*8BH&;J!@mes;c%|G#Ks&dEb?bpNd3ssB9>zcwS*^8JoY z{`kn<5;7y!68_{b%nW||K{6wd@%QGhvRLAiN-`rz&fh_B#6QLJrM_z(Br}3!{vAQ)-{en@-XmpZ#Cag@ z8Z_>Wp;G?>k-g`ExCF%h#=xk5$;HnDamo1kZ$hR11=h0xh)a^#UoYtHjhBm1$SYrMmjm#Z}NdKl$dWq`G=wOSQMUc6Dc^rGl#! zcI7)yURwF&kN?jPFR4^kKl{@s-f{cSe{n(O^AG*oZ@zlzfBfe5O68k>Q~B-}ANr45 zE0qg=^wY2YfO(BoeWUyzo@77yoz+V#OO?u-{}bh}uDti?dn%uNsPg$=E=>L8+cUGj z-^Y}{vr_%lzRFMDSGjoY{yWMo<<+O&RA!g|{mDzpmw)^F-#b<=ug;cNo-da#`(gR> zUnrOF`DD5L?Y}C2^F8H%`|EP~>wh)>0;Es;xcLA(N-d?;r!Fe7OQeEl-c*`-sZ?66 zmX@9>l`i>l>2o)gN_Ty%RQl$-$$$TLMXB_)ch9{5=@b9itYCYwrMUXk!6Li(ohL7@ z9+@fr{Y%B-YNfdJRIzyRkBgtXu~@w8W5wb({+j&vZ|^J?AOGv(PyV)e(b^kqDqp_|eZFS$lz%N$2FzsmaORzimFywh8CN(ZBx5kAHGS^ThHvHNJMYQ@>Bx zrsu}IV~q_^S^;{KyA$HYe44#+fcgvWq&Fe$(NH9(;)X z(2)o4zxUxAMroPvde8lfwP=!$ahdL1}=T*?tAXJmwhk$o)=K!j=OJ3;U?|pzCc>L zgE~fE^Way$a(C<9&wu5vFW>p4e|a!xv!m2GPB+RN&)jj(m+xZV^~}9@q0Aj$y8YHi zN9{J0`Ym(etB*f)+WAJYC$o3mb?2S;J@mx~NTJ(qz4^f zH{bMyJ9Eu)xbVodj}Dd|xoy*hM!Da5Y7gIbf6gCwe&Vj1Z@Te@yFVJTF>ipGkqtm*LPZ$^hVem8XZ znZM7a!yA(4(&3G(uV066EdP3Rc%%Fq(BaJrUcU}+RIo`0za|}imh!Jjhd0V^(BX{} z8+3T%L_=?%{(D`AlMCxQ`~w?w__+t;nFIFabod9TSEIu}044nDbod9T*QUcia1L#K zZXHfaoJC!q^(4{ZAAl}?Jvy9}JBPA9$LYqsUWX1Rw|YG~oGJG@bU0J)b?9(X?zQXi z*9RtTU|?T62=#2`$dq6)I{e)Kp3>p0O*)($t?O`du%^SCzZo6g_}$RqXZ}8y4sS@F zONTeEzJ49PvHa`N;f?ZdK!-Ogc>OxOQNioi;b$rTnsj)h{01G~II%&8H%>Hk`02mb zbvU`OuESXybojXkz>@S8>~~QH{i?%BR3IW2{d)cWL+$?bKnG{F#GGqnbo~|91@i|Fk_rvKbjtxHcqYXX?pUDpZn$8%H_BH_BWT6%Wp49OYFNlOY4_QZKdaa`PNeDtv~thb*0kf zMSXE~d!yv`Vq5XKU%sVSe9M1Kee2fZMVl_Q6rcIR?@s*jceq%rK6b~{q<(UBrBQBW za$wRpvAV5sv~6Nw!Z^OV+&Eeu_iy+;Fm4z-F*j`^%}!kZ{xQSW6H}f>H^BSv-->Rt zIY_s`|L@md!4@$csL=~Z$F1x?pgtgI^q8jJzQM(ZOOAfi&a|H9)WfLK@0;nF0s9BL zm)lM9&?*?fQe=y~ug*o@SrTm&d19zh|mv3X<5kx1*m(PQ6H- z#j_`0-`%yI8W=;^*oBilK<-^TMxE)>pOR$vyeMqbF`49$ZCHYjo$k%IpyFNCo@xv0 z1$IxLvnAF!OmxFz5;5H&a2T%YivN;m>gn5`{}$r6-{( z9@^`jx%Z23^iR3$YlWWb?)pD6z3Jisc7IvMKPrABZ!1oP!t`N&p8KPSy8mMyvXM7djFK< z)oBXr1#d2zUWKMO%EdOWZ_W<^D)WkpqLp<-zxS=V57jLID83p!!5b|`BbjnS@GA9$ zp;!t9>Cu`V2%cA?C!XsMFXsIAV0t3h_iEGxoApP_#Awtd@BnQt#jJl7N}@}cz}`2~ zF1ZlZ#9<+gBN(V7@~Ze-`}x8HEx zO}E~<$iqhSuV_s?!x^xD^j~hd_HEbSeA|^;z+7I@n0Sgac)c1cjGwB))^9)Sv#7ZCM%?8xEse|bxzf&rn_+*mSwF< zHC>G3FbAs@<5_(Q%Nb6@aae;W$jVa>=s6Rlu22sYo@wgJUqHilw5rd3c;0 zPEt)5;JAKea&m%dx&X)Z+a@N+a^D#)z;XTZ_&C=4nl8X`;sRjBujv6CC+mM?(T{Ne zPQU@6u3)`HozSoQaoP<&UAzAH{*^V4j&Te&AGaMiUf<#Xoe#7ss znbmy_&xN=RY}S#knI$2M#RX!W)w|(Z#|(Rei`X;G79N?r{iZ1)-y|2d341qO>zHP5 z=+B-h)VaH|@(vaYTehRl4c9uRu=>63PWIFo$@qEA z%-7w}o+@g*d_sKJn&gIS9aZ)QC$p#WJge=KwB(*O$qm=KHvA@Xows*c&|Cp5k+?!OyY=|H03?>GSU4FmJ| z$10aSfn2-Q|NGX8qhVbhyRlNf>%)=JgqS4u8el`Z%hAN$@v z6tDb`Vz78@?~i`_pZkjbVtg__fm@Hq$KqS#i1xB5Ib-6rQ7656zFu~qVbdXwH}>JH z_*co-jX3XzuRO#*q*yoHt~w+*)XJD^rMln@bM2}_J%?6@dV5-Wj`z2A4i2yfT8Y6n z`q^uXXBcdZtf&+G1;_h9)`IXvD>2!Y#^x_xGji5VHbxe)q88TCu2#sh1`zmXghQWP z6JGxk5aAP`$3cxpyIVUtJEfvEY1#E>m*#*&h0Q)H`rwBo-ko7A9iZ-mIpGa!((B&` zrtZOZM*4%#KKqenem`;|VZ&V+7T&xjz5bf~pOCt69UJ;IKY;&z?<&9P_Ze9^Cu5;Y)XDNPobT8@QUq zd7p=M?Z@A?$&@3)&+`m=P#Sk>4TOGmEz0{RJJcI1RHw%J+6onK|EbYVJVt)~)M(#V z7t|Z0eP&5?#%SMP@2Nju%7b%J$8SG1+9$to7Waga{i}<2EKfTnou@`S9F%vi4V(4R z&d9!f_wFq2@6N>{uV`{WnYCF0uK_&10R2u8;MW7G|ukyDZ=f+~~3}vdys`3T2qF&iNdR zcRiQ&@o!$dX{-~`)owy!bQELVY^pCIuNQvH=is*lST|p{*>7Q_T|!8|*rhdcEOXob z90t3^xIwXFuv@gwaD*2*HQ0?#O3hcF8th@UK;1fXraci+_o$y2sdQ$$N!8gc6tD%Y z(`PZ{88M;O8w7J*PuCCvNv%`s#_0OkCQ{D`%=RD{u8GtD(d6r5>m#)xW^@GOK1)yc z2se1~xy42d?qEC+xbp8b{{3&qfL^a{U28M7@lCEs-1iU9K6~Z-N*-!>U2I(eGh#fg z+2N^^K|HBao>S~(S0}skSjYMR#s>l1Ijh4xn{0ei;i=I}v@e@~M8GHiJI&H(B>Rro zh@MVb4Uoj+oo4G3nN{p0&*mH%aNqIepgSJKs9k@Jb$g%4J^O*ezyN1}XRxVUG2hy7 zZQT&oap_w&2hR38_qVQp_K6~N)!WuT`+H~HGi@7UE$->I^)B7KRPUZ@Tkl1(RDYaV zs%O`pJ@l(5ADge$8h<3Uw)Ng2%l5x(EZZ+Ub5ZsFo2tz}lIrI5_&<CT=Eatl>+RhTmTSX+#J>I$&$zvAtU1P0lp*$vq$h8$6yS|w3V$8b%{YfuU z#9`ebmgl9e>c49B!((K*ANHM={65rw=){#D_L2kvV&iEKtzP-zLj#9auWH!RR~^zH zI(ZcgxV{aidJeI>pYQD(-TL6o`@(}g-7Vd{hmK!`CE`Q9o29@(btJ$0kne z_B|K)v0}|;r22c?&yaeC-P#Fl}zcHXmw$5M7+&`6(#YFP=_noHd)%#B%ORL_$N#ASht2?*V2kPr_`MSP-avxCv z_0|22MkJQdQ+wWi!P`E4!>wPu`5)hX@Y1FFG|rvePo~Ibv0ay3`mRsic;jt1{lj}N z!_rF`V#oJ)>q7P3&2ksL_42p=YLvO#RTx8^ix?4||tIn=m{LXiO{`%`b zf8z(=cW{0Vawqo_b+GyIw(i>Zmdg))>egF6chlAHd)p=37_l?oc6xF5!FPW6+8b~A z>ykxr3Dtq7h-qYn4FFW|I zf4J_Z8*jS$J(uiSu8g5R*8cOl;V)4)7%jvOfO`PlV6;T~BFDo{tp$ca!$nX>PT*MG zuRcLJjl?`4eu7rLN;%a7>eXfX)OY4`#jm@UES++p<6i8v$ok=t%N zUCwROjbM8h$PMX?o*2!@ed3+-r^|Vq23}teaQzFztosf&zAOV2ckL5H(ng0Ef1-yvPSB$>+@^PN9EqbdtN|8Qim@WxMZMTS zk4|$puGN$4%yB1ml;B1WfAbqt+@=e{EZGwQ=D348Lg1p0JiLGZ1_>Yx9O@d%l|}#Ic}q#Z-77_EVx~8K2A?GW|sen zG~=Q`x&i&X|KSS{R@%>=EU-a(E!q6U-H$$6?l@Zx0TE_An>Okg1NHoJ%a>2yS?WAJ zvw+Ak-c1{IoTQZB(R#XN5^V*dTJ0WRKteg)iRDzI|w21PdudggM6D2w?Np z+t|0UZynm0mq0|AW8BCS``ZU@X}N`c^U%gz1tP&5w^PUM*?+$8CiYG28;6=+53>}y zLE;6Et#Hl5|8mRq*N=18MUd7&M3`fAw9}T|e&Yj=-aj_D<{+)=m&S(1R+sbu6TIlO zxl`HEt=x5QiR3{vMZ2hzE*smnYIo`}w&6X2U4i693^s%@NL_Z|Pz6kabVFhQ?i4a5 z9$_X#BSm6(JzKgFKxagnsqW282%Kst$h|X-{D&Uu7#aVKlO*{8J3+o36P_K0f76@; zM;mjXSr7@+ka@?NOmiL_Y2v3FLL^rjLhmF(5meNi3+vG7=0h|RrLk>uQ>-~5))CVi z#MohWnv>$WQ?TY!a?JaDqVewgPR41x{F*o>NE6N65GQ%hjQBrp5^qkH7fzv=&#Tk( z>XcYnB%_NcvGeNm40Xz(PBx)K8xqZ+K-b|z=XL6Nol2e8sg3XO3+GML^Cs#mnyApJ zjC0>a^{v~fjBiiYdCu$9^E!2-PW=-j`J@IYLYMiP{v1UChMlU1dI3M1L{weW^Ei04 zNjKF=J%^)*zuF{}>Y$#*Vd1N5d{OPpF?IbzO**J{>KR-gdvKlWsf{`D@V)CaPK^^^ zImOWQ|Fn0lu~A%Cc-G^6c-Qv&v3@KpHemC{*k(N#8?d3qCd9j@U;%-u*kGKb!QeDo zLQqi*b|9s8T3(Jy9t3b`@QXkkN@x96M@-P<9x?k;X6qG_EBShnYkBg_ zxsnXrsd7gv><>No(Ox$kwC$~@|8UNeM;(x`3%mR5p_19S#l?*~L01PewG)6}FP)rK~ z!1_c1z|;uqgZB@z6caP9o5^{SU?p3ngId85Ci~A z6aY+}0AT9`09!8rT3#mrIP?O5Bj{^^CrAMB1PK5hy#V0Q3jhwi0LUQ<0Jb0iSfT)6 zY676PCIA>*0BDSi@S&I%1c3F40)VL#0BoHAVCw|{hh6}1=mh}B17KRK6H66QFpGWe zo!r?~9}3)~?jrere{}T`Rol|d{+~{sZx_BF6L+7z@VmA)!L2Uy z1)uAF|K!QQS7=uJUd(_8yi~kw0O#!+>eRRT9a}?PsMUeK5z0;fJh*lMSVLXBbf#6C z6z4}rRFKU}cSb6AF*h!=i-s7XZDpTlSHg0VfM{k>(IQ^$_7ZAs^*dYkD)W>+XV#Tp zD*1}R*`Ia%(m>uoowU=*3#E;ob1MBdTFz;i(Q!^C>_*Bt6~-GW=TvG>eGqYf&YN&e zc7M*B6p?eW2%wK~Ub%*Gp1yT0=X_FyuPLvIbIvROjP^LEQca`foR%3K=TvfPq?}VB ztC4a}rLl(RTpE&dNyoWZw9Cpkk4|NrCt6cE=Sk64{Ll|^&W`ZTQ=Oe1R6j)>J*a|8 zRhhNy=9D!HESAX*TTB8XO#3ZyF=dvrJBs$S%+PvUyX>cBHh8Vxp3B!)8zNHVFk_NI ziuO2+UW@m-qCGwf3?5YfhAnV(u_UF~@xa6wFO~fBvtMBWLnzf$tY^K)>ghqSoH32U z@4}Of38f$3@>o1!p=?vdp32hnQaOlR+;Gryx3p5MPa8MP{ApWSg#|+e4sIkNry+20 zp;E{OgP}?4@g1mtq9Ds|jhY)0?srJxt}yrlwY5xT{NXh9W-5_Vj!TB6 z2WpzF=kldR=NCh2f!bRZp$!Sz_pg+stWPdXH`boY{q2#DbbU=;{=}Nr%mh{cDI^t9 zoEc?~UH6yw;`Tvgfh2yD$G{$7Q}4>s$wJ$#KILYDO4nJ&(!L6zz*oBV%$pu+8!P%J zYXQP|a;>$Eg68yGX>sL4Fr@VnxX|-5SdU;fv_9W12TW7{=NvH2$j}MWzRWcnCmdU| z2ZfKuh&o}JU$ViuAj1|JZlcUD+1n$z4oOnco~lOc$vM*ewnE|PXZ;<V1jWlyY|WtPp+ z(^j$t?E6oJ z8*4JhPW7xTP0?7O6t(~hJi*P+f{+2H4hblzlE{YyCI`7p6Q?!|P@Lb5Q|}&F=}t;E zMNY^m%*F0&-L#Q_b=!U7?Grom9O#x^ki)yhTBsO@zFYj*Ii(x7Zc2=tfSeZWs&m=;69{J=I^mtv)y;c=L{J z^+#ITJ6aFb*VeB4kr&5MU(GYygF}q(Y}vJ=vANUGdAsfSo)~pc-cQdnz2^_x^n3auPFH{QIG2HJSpAa1GI5lo8(lHS3Xo{LN zdENpZ2b$rE3ht*k(_-ut&T31XnEQjrD4ex#{;cGXe)FeJjIv;TvdNy1o>Q=x^_wy# zBpfq$dS;vn;h3@%X`eKc#h>1r9}-qOSnTb6!gOiR_3^xX&)LE4kE5b1AH+RTPNSg^ zaa2sn;mTKmSqvV?MAtm%x10JcL_elcqjHn(J=ny>(A~8A4c?8ebFj8O>e~Dvd$-hi z3*Jk;zA;lQ9}CuF8A)`%;J>^H#ObLKdj3lrH9`;HI&L2LMGcS8qpzObMSLd-V6iTt z1oY3!qQjoWD2|8$cJO~E8DLMQ2W5aBWRIKyCW4#7P4>6-;Or9SQlpf<#$$MoRPHoh zndNj2*arG99h>Ex;tbkC%;O6)gm|<^4sl`^wmRvDM|-fec|F`|!_+2rsjW@y!WiPo zhL3OI+6umfYqzF5olUl;eqTj;=oVt4RlkMg5VsJvu!XS1Erh9UA+@zFgfYa_vz2dQ zYBb-%RBICZDaL+lbP~2ulBCPPzX5t}1n|R^>@^#I^Lf*7TavWZSU5YSUBC z)o$HFy(JTXi%e+%!0+y|Hg${exA7*So-OlJ+bXniF46p`fT^{Se;QP22JP=LhriuqJq&Mkd7`;6h^wuIW0(kuRU0p3ge?Nq$8@d|y_Q55+JsjCj{k(|0v$(=1rr#A=ySLp1eo`uNGU-FpGg)P8L&)5h_9=8%$ z4{Qdu^N#+llwFD>yYheqz#^a+SPFQ7&Y%0A+|=SeNUkz*b;8fLs^X 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%0DpOB`4qnV#^1<7Ujzss? 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

    DF2BcP11FB#>eFZ)2Cv2F(V9_vVl$6 zz@M3qCzFYAZ90?4WHW^HaXA-GMZ5lKcS)?3*!!9o5*aJehFCVSVfK;)@ zQgfCd87xWgPDNQ;tSf$UZ0I;FmP3Nt;8zj$TR}3%Ayeq#6a+o@i{Ujs*m`Y zoHG=x%Gaf;GT~${nocD^f`PoHR6||1TJLp8y#cwyVf26>)f+NPooa-U+*+iznD{~y z5cR_zcRb*aW|84kR}GR#B6b_L_@uETFa(g4bpwpyrdld?e1&%FVw-S}yXtL^g zLZwA3$AyBJ_jPp9P$752*Dbv2m6dj zCNUbsI<*RnvpSIi@M%0Xh{(a)8jJaZig+fH2>H}ro5tl0SeauukL z<%)$I4Ob{eO&UkW<)gHV%atQBEnlCDrd&?1&k~FVT?n2y<>1J1Xgpez19e+=71w~I zUcOu}muryCEYc%6Rv_aDWk8?eMr&%rU{}m%0*OGVI_XI|V*vp&Aw32q@)C6#q0A!H z0@p;y0Un52pjWV!D9B=~xjZgciR!zW+L~}KjoiLyCY1>IZ0>;FAVs++Xd!e;vr28X z$h9(+TB70c2=ph4&;XStS4dQV!s1J~yd2agmanVI>HRe^yV0!)>%B&Y9z+%(qKHPpuKDg`hf8$)p-CialkhljSSKe{eGXLx(bg1R4CxNjd*L z_LfpYz>PQoJgoHv94t;D>ctB33ZF#;3#)%@oHc%JtRL7|l|k-u9IOC}#aRl1RR(#t zqG1Fai&qGRF@o14ncU3{plX>$ySp)kr*_{MH3iFppGNC@RGW1V5>n`Pn<~y*su-V5p;m2oxe}^x((Q&Y%78 z?RRpX4&Kce+cgx$t&*tw(c5pm_Z@#~EpJ7o+*)#s!Zt1V+sOPoZ@l^DkL!Ky^v{*g zT|=QIzyPPPM)>o#bu+l8;yzVha)uTfvS@y1WvN^dK+y9SrI zjU2cDutf`gntScFAN=6eqE}Z!XhEp8l?W9gakSvI>ZjiN{wu|=+=9WnegAJm$KHPV z~dHfykzVzZt)R%t$Vl(e%-0hkP{Pskqu#MCBllT1S zg`yWI&;Psv@B%`XM8xKURhuCYvG{0Vp)^^{K-IROQYfk5V~fEPa5$9$VFeExOu-2( zmHk}c{02N<(C1^#<*qA()urOIIVHW5*=^ioLM>lJkKBn4B zb4uCn5~G3UE>*Z_PC3h6qBqi=OtFJzmovR|y{W{>=G$r3)8w}r_);yv6hvToQp${6 zsR}_RzKSW43$%P}XvGp;g-puP;I+w+3HX{ysf?{%A{A4U^klEq4VFp0-mU=5cA1z- z2VR!OWOOESU*D$0r_CJ zP{`@nKyG3gSym#@RB7l}xf&mc-S!E?y(E}(}Wl<52itqyu$)wZ@P+vRj zCn>j*r!fjRaxuS1q5|W(T*59@ssyF*rz&~)S0+(#S@12Ge1#l~yRu4#L?R$v|Duco zx>q1^l>z;#7?4(o9U^U%$pC9|Nddcy0>~lU5ai(B00%9#_!9&Lp&pCmPN~X@)f6Eg zE1?V}7Q2g8MMx~vP)j8gX?d}d%H>m(#idd(3nLkj!^66CnUo?bC&2R7~YkEA-`bDUDrBmx3jdt}Ul2XqAN@%atqOVyl%Bkx(mV zp{!m+>fZ%Ym57Z{kbtd{ap6RWSyGvp#|N4xNX^6*;(|VDR3NHX!S6#bR-uMYDGBry zaN7YYKyc?Jav&4{dmZ5GK>ftg2oSrZKvKsnD=)4@NPV;~Mh#9`&FaD5EwBWO-pcBwO zvfhkzV~Ce6VKCtn-GGBdR)oR#j6OIpL1#kT2Q4MkLRY|L70yb)tqKZWEK&&U}|Y4t^gqFxMOrG_7IpSM0C)%oC#zpIu@S`6oOd$gwd3M3mcIi zfLQ_63LOyk!4E|o4lw+bDvJwiNPAf+A8=*id{R5mC znvbtQ-2#jlVJH1(#0nYs#WXM^mxCD@4a^PM0yJ4HHkZccFj{psCu)H0POSw@hz7e$Z!ue~T5u(r40Z?Eu<2|U$xFr> zvx2kPXEO%zV$}s~20swV^?sAhr}x>-L8IGk2$-!tP&%9aMz_ZhFxmVDUx8AQt3t^u zSFe%F#5x04tyaqT1|ydz&}*>n1ojQ1o-YB~AXlT3@(@-Q0Etk^)fMO?5fspx6k084 zV>LRNPNOu))p|@p!Jw!IsJar&u?Ce!E7NFHdhC{iT~R036o_htRSrXyFcMH#X?#gR z+@VWILLot134p6OFs;PFurRK1!~`+7wn`EVi{m;YVAu-;tRbrlWvvcJSm*J20Nmx% zdyqw?ad~V3z*^}8q-@FUHv3Hum)32^?{vFO0b7A5+y@k^Xab;B;Y1)9^?EQ>b!cS3 zv_gqpEEY;cBB)sdbdgi9cB*Vnw=Wn%VP=7llx<{lwQRKnAXXv`N5{p6N{D0zZUM+$ z!Ks}R_DGN6m{`4#~60IdqpRXTyd$`Q!`0grSj9S>qs z3CD=%#!=r(&g!-ptp2dg;Bi^FE}vJTM^cc)i={l9$7!%&L+$dKjSja(XY+daW|v#5 zaJsDbpzHsxHiOO=AU0~DR3O%g1c1-M6>EWl#+QIk7g#-NM0B`XtgFbhBC)7QbWO(R z7S77`sfB*F&C2odG+wqxDfICzR<@s~bg{93=H*(f9IsgJX4{o)FWYS4xTOjg+fh&n zgI8*{NgXP4NXm7pokFccVHX+0YME7H5-40;3s)afOI>2UK;}k*jRXth9w;OO0Uqt9)zyF5;#%k8vUy>2rgx}19F1L*oO$XMM3 ze+q%;kKBNv0xA{Ysl4tY_wRjfs+;QhM;rm-1ii|IuYj-uoWI*u&?k*j#nY?BN|8zh zrcqdqLMB#9HF7@ccfs6wcYFA*Y!DwYa>b`N|pK2OCHNci~Gavl(-Bpkj(fHPYC2MGJ*Vq*YO9h=GePGdF$P08?apx{57^c^*?#@jdvc) zJH0`Ap&=c7`kmKaeQ&$ZiH3iLgtzg>uf6)p8!u!bLMyaPz5CXyKluJ1{W$M*-E8^k z>pytq`!B!v(*~ClE&qaEKS0AvFFya{UXL>X)&bJ={qMhwmKWa5I+vT!@WS)YefJ0X zr6zJZ((vpvZ(NAMV_P~M*?#)FujZVn+oc8nK%PvnECS*H1LvuAgYSO+V3cn|_{> zQly3P^A7~Y|Nbn^L@{ySzt+dc=&-Dx_`X6vv9(e^vAIe=vAtS9(Xd)S(Quo7qUARI zM9XdZiI&^-6HT}4Cz@{8Pc+@8pJ=&FKT&l-Jj*36)2&T`rsa*kHGwsE+PLoQTHokz zyt(VT+uymxzooDR3LBw3trsSA{D`pv1Yb(>$xkwGnROE8Nm+=t7f;JN9AqX zWlLh^!G9&IXYalHlXq{v>6o8uDP0o9IviO@`|&%}cRv2{+c)2X<#%cSp37^zV+rCH{YbC!8`)T?-O|F_ThzCxZ~hit zZoX{kU$ld^bf%Kvr%Q`)#c%xP_1Bi)$n-lmFL%1N829V9UPGtl*ET))%fc$%Pk#Q+ zs`ll6zbGus{qUXd{fP8hez((tg&l8SY&!R&+myDTA|=6Jkj1(mJ`?c_|9GVbfR5Pl z_Jhu_^ZeT@cNE|HdA}g5ct6UAlJ2XoukaJl5j)=N3ZG>g`5xQ;*8HX-Bk4Q;BNp%mCZG5$>w6t zBIoY|#5cUX>86r1g1`6TXg3fGDR%zejdkzU+c)j3B6tJXL&eS;Zhw%o;(Phh<6;Mk z0%H8DT-sb|=|3;9ncaBeL@!k5Q?{%?_g1H=LukCs>-Z;nSx8#l+J z&ux4*9+6$2jYp^89xjhZcSSf_xXNY8FCC9g;SS!#cyuaw*W=MCk{9rG#-mfvkgqu& zkuG-_dhXa*P_$Ff+;21~xWIQ4rzrlF4)a`4J zN91{b?eU0o`kLbr(cQZqkBCitZt(e{@rc;j>hb4}vGH#hY6*+ZtJX+$>yEz^$ZQLA>KDY7Nctm!6HXcz{jz?e4GUZS2W#85AdM`9KOfcs=TvNqk=YATR%Zf&Y;vaY$&x3tx_CcNT%GN^t7x=rx!&ntBK5?itzrvEK;dVC1QfHj3ey3qtdItYH0PVCkJ@p`691>J zr)0&1tgOHSs_&-V<^h$UeYBg=O#_etlM8}E8l|ke_;a%_I_ciU=%N*UN;ge`Qjk_u z)>U*T-v|C1G9MY8kOV7T2`aIQ$~vicbb1J&>5>dGD<^ z-dHi?BJzXnjP|19-?ES|Qc_&Z1aMz*S^F=7wXeMOza;#=mwvQzs&z}ATKo}>i4Mp- zDv_cF4?X@Vn0xK#lipOM{k>I_DZ;}#(8g$^0&N6IKn#G@QErrR@VBh(pMup-{rrS0 z?Hzn)RfwR_rpulCdb~3d=zi;atD*?EdVRnYlDr!( zlZ(6}CNL7v=NCatpuYRY58wFyiz~wnu_1^o1Ue)leuZHuA6Nd`j+b|K<#O z6n%cgAe!2@hOy=Y++(_)AY9NSbYbI9f4io1P4VZ)45F^RjrvAJrB}_MN|x+T*9Zey zo->s$c?P;Im~x(WjI_Z22y(*Bf(_0=X`T=YAtX#2@d z`CZz$V`4{(^=@qZ6K?CejvaLbgZisYyqNN_X=EXPrpHdeY3|0tg$*rLiQ2XdNI>t- z!p$wMv1q!X!$gP}@5aJEs*G>Shh5Q{rYHeu{AvTIQvTK6)KKLW`>V1w3YhENn0J{Z zgMu4HnJHY;=mR#+SDW_-&PW}K{P;APKiir#+>LdAFnj(`PtwC?u&l|Z_MBOCH^%+Q z#RqHsN>D>eBdNXjlH?vq5Y#q6D_ZH(T$t^DaT;*iP$vuml`+sQ^oH51t*ur{fhT@G4~Tr{FOIe%yYQK+^>9tvp?aD zz4pdSQ!I8d`>S5z>`%gvJo3`1tD%DISG~cD*&mNTa^R)W$LFN%BKB9CH}|)pmtTJF znHRtNSQWR3`_(US-baGLmtTJ7JI}rN;wB;TNWR*Se`p%0lJhN6d_3h_hJZ@rhmvuYa@&G)i3W4&Q2bA`PnI*R5SMc%invxKXy0f?bh@@|4^RC71Vy``GFv-w+|dTcS{`JZau1@}CB(>=cq zUt>*Zg-3qNJHHOEqA|GAIo}do>6)8@pE>5i1C)OYQdi*d7z*FvV+2ez)2HKw+9Y^L zg<2)u%~~aRMvZ4L(k@bORVEb*mJG{VfwSk1(~cLdK|As<) zG%bJgmgMrjJ4u$l;FrCAXURe}(=D(4BWlHSa@nguqgNw4oXT9@SvcqlbRkCTha%&P za{G^&g3fnELgY{~m-l{N_9F`WNs8b*T*~D?ox$7eHqjM;LyVS>D4ZuLoJ(QKbKgEf zM3;9Kj=BPNh|&B3h4U1pbjP^ic?gNQ>gXl$1XBn zjZreM*T6l$HCMfZH2Yj9h2r_EcWm^@7sKDa^5fg3SJ)1`@I(J6{P6NxcV#Je^Phf? zcON|STQlFnu^VsnE?rk|qiOj&*+X4vYK?~FttB&{t~9+ZhNZ1#3;N6wO|ZPBoD*Vs zpSkM)9_EgTi^u)N_Fl06t)pYbV_#(P?yZA8w4OU#{AdrI>{(&)rIZ`D+4>lfS+-{~ zeJ_z*zun9WLd&L1X6`u9Z3bSDSvF!aWnY$AZoO-nWy2-&^X20#w-%1GY_eodzAUrc zde<_`R!Sz`rPLezaunfwX1j&sEL-OQCfcQ<8y1C#w!&ubAVayqDhzz-+ zCGTi`EA;tu_IJw^3Ie}2z`YC7G#!9?al^x*D}kdc4q@D9B0|o?reZ}E%OxySUAqI8Qs|c?;!Iz11!ia zo7$ZXupqN+YIiokg3Pk1-Pr)|T4veQ?reaCFV=h5?;;s6vza;*GP7B*qMfRCrN+{Q9}G0TPiW@7~by z8y9bR67u83&1~%v-*Pio>|-3i!DBLt7{9A5F6FRT$BU1{(QJ6~YRfH0bG57#&{?$K zRnX{g7l>#iDee|;HrO!Q!tEL@?*Lg*X3!sV+Er!>*N)CX?nvS3StV2s~ z*oPv+<8s^gDg|vnSaLd7ixz#N|1bacZ~sM0h3H3=%F7h~Lu|_FH;0KMxk~Z|xLR;? z&EJ1TUw9B7QCOEL?0fx`s}Jt}obZQ2vk3sSWd<|qUUB3Ph;ZrHHto089YgR=R&*R% zu&gbN;~y4NrBzO51*!O4l2=@yP_oDRPcohkQ4-IlZkHF(EBVPBAl;Up^are?EkOGH z`FE}K!K;DG=U%>Do?d}^j!dth1eLnvdUpL8s&>oL_R3OD*PBmrkKQIyoXp-cdzu8RrTB$Te7&Z9!AfHt#vi&s8K{GR@GD7L#A)v{cQTSTK3TFKwpW0}jkK3kRo|>&b?&9S3 zzD~d8>=~hXY)N+IH12bASF}U=a~7UMtHMfgp)@*}{3fURu0&et+r37YQG!==Q zyH6h;`(i1P)tg9#E`9jnxefhaEG0D9-2NV7FwNR8l=?l8CrUpBq{5Q1=m>;m5hhLy zGI3hXZSd4;Ev1n=bv|6y@6DN83R6CZ=x-@xOC33^SMI7}LmhY8UqrTJ^ zUfhSL>RBu{OK5g#y~!_@dhw<6oqn;Tq(U3`l4Bix>dB`TqrfR%J{Fg-rLF#vAM)j) z#%=SXq6+PoTvu`V$1Ho4$yB=|I`s;D3$4;bq3#a4YPzvPmp}8)=qsF1ZxelmD|ss( zNme<%YiP@H5t1$7X{4=+i+HvGuJiwG@7#l;y6!lBFPHZ|cn4Oj2qMr{LDZEfZ*3cV z#S$CEcNRq6AVGr)vSQSoNLnS47#~&9C;}3sok?esaV%+OYGSR~b~>F-|L8cGPX5Rr z4E2F^Vd?jrd+zSNXN9GIXlK&w%xCuAv){dsv%m8@XV2qzU9$z|(RN;ltM*F7R)**=`F_RgFnD$*5QD2iwG;hdTX=ZNI%%l^Gh0$Cl}AZ;0^FJoGazX*_?pHUneeB2=PbOy}pJ`j@EFZz#EaydourG%|1Ti3-Q{6`u=(nu!#)Tc}IyuX6EPp;b@f!z6rdOZ-tfVkJl>WwuOcNB% z9g9r1NG|L6bmf`=c0&IEV~nRrih1q$r+aFm5XwZDuCgUHBY7GMa(nFs4-u& zRm^3B@6WQtxLa~4GilWZt}&NTJyum*WYkn}iAByGMIoyCFR2IxM^keTRW#S`yU_jN zudbargr|oWtwT);%hGDfcI`fJuBWHJ_r13oE7z|tgvKER&0bQZZD9=6?`_(9=6qLw zcgNA162vzrI)n%>;B}Vr`hD+Qxz>ICytS^j^yQxt-R|Y7@w{$FS@oVnC;G0O?LJ{^ zt}k3ebTQ#U60cjkp`@YZ(3PuKuk`%<=*~?Wp_7Y{_BQZbxIIm_b079y?tTC0zFozT zdEDx{E#*5~j-Bu6`=I;OFPfT4p+P_yiOZ196&3s2+pqR_UA)$D>W%u%-heiyVlVRDX!f67O&gV zxbwh!UA;ZM?;qY(W_e{LJziMHm0=Y3M!+T7OB zC-#l>Ts+cJwVnR@j^e6jj}x!0Zm=Hj>BVE@4m7=9x|*K&)ve_X{E1&F-QL`Gx&Kmc zugzLpzW!(UAL0H_p>2H;+2O{@i8DSD>vYg>ag=h~$Q; zPUz7Mm^L>*Cm$MzF{$Qkg0>|vH0y=vC_fQ8(LZHjUVbjoHN4Z`&+F#TolTxv3SIc@ zlm$6?MAv+}WfQMkFfTbukC0zzOzR>ObCwZZLB{k*PB)U3JtsB-p~X>)J<$axEg~m& z&Wsb|wD&QBhw30aszHw+Nly&HGm|o)LmAU#$jFbfGv>tl>4`}6AB{6Dgvdv93fxJD z7N;eL`TOAF(ADDey;<4h;*#Sd0(o6-&OCEu5OjXNVbc=gmk{0Jv>741Zc*BdS&G2;j|MH5iHr_qA*t*lC&v9Woe*1 zKhBw&6h@nrSQknb5r)cIgwDhyIdcr15{HnSXmeVHztFx9dFb0{cCIul(K(e7Z-aSE z?pGu|AZ(x;X{^Jja=kph9c+GrC59}K3~l3?ZOyPtF%ykH+5sPEeoR*xDqqAv7c~17 z4efvrFptPf44H@wbV1ZYM0ggLj&{KNn;*%Gc?Ow^^t3}|RaCSK9;@@@6@}tWT^R8lWupF_}>+kxw(0HNE|C8!KK_&<&`gxK6qTk z0df`DxK`RZk0?~~(m|h(IbAE!<+ybZT#EwPU6H{s9Pgl_qn-4=<_BbTAYBBg=rACr z*3pi7=v-_5R62S?r{%k^a^~jY_7%Bnr5}@%?s+Odqmk3@dmP=hz>mpEPd%=J^puFX zxGUO)Pjt$PKBgnR_0SzAx;*Gu!Ffogp^`OxUSvw)VKUNZkE0z?j>byRBUXZ#N*OkT zb;*TCwr5gq*oZYNH)E+QVE(kou)%6pWaQ;;@+h@cT*Df(-y$z?*X&ffK^0dphRD3M z+Xs<)RlcBDp5?Cb1#Eat>kC+K=%l`YX~>{Ae#QxW0TX%g1x)4D7pQcq;yr;cVB)F! z0`>*3zCf#+*cULJS6`s$y!ir^ZYsWj$*1ZInB1!`U^;KUz>7@r1x)6}7cd+3@CC9x zd-{UWRNCs+Is*slPuls3Ipr#EKpWoQE^k1)ZZRn0@&J6A?a&N#yUc3QQdG!V=op*1*#8dYM>n9PeeU^eRE4P<+Mnm3^K@97O_InjCp`VZlOHW&ek0Hq2* z;Aeh>1Xp~40P|z)x1Qs0y(KNIZZby0cKk>VFahi-ceC{x*5sDpxcQ zId0XUD_(~jx9X-l9ETjY>V`Yoh8(x*6B2zx6SqpeW=jq&rCyaaVcaS%I0Xr1#f6<9 zajUcuB}j^Txs!al|54`O!Y^VpJK8$&ZZJP-yOU}%jqArpZ*``czHj190uhb0CV>RC zNnlrF64-Z}1a{mefgRZ-ut7}1``hH9qSPjVV`)qR`&yGgg4QIEz?lS6IFmpMXA(%! zEW|@7GbHgQfh67}$e}brMuw5XnFLZelR(OI$@~V{BsOl7O=9EjM3d=l{oA7r+Y+B* z5(pxsy(%QAO#-_blfb^)B(UQ)3GB!wfem63-rpvV&ZIU897|&o*w>l_60|0P1kNOo z!kGk8IFmpMZxTr2O#(^0NsvQnO#&&LNg#zY38Xxi%nz1LBF-qAL|kw*je$5CY>b9U z2!ikhcnHX=6zrp0f-oQN+48pd7~$q$#x@3@1l>|gNmbeFb-Qb~lTR^g2P33{Fb)FQ zHj&|J3^W~Y0+x)rw(*E%*EYKAT-#Wy=GsQzt7|(1+vK>)3w@)m&m!42g6*@HbgNL_ za$PbOgx%ULNPipzcfjAkKQ$WZbA0x>9oz%Mg7k$5!azJo2l=27SU^2!1?`{{41hsk z2lv3RAl(*07>EbyARiP03#bRJpdECA0Wb*c;2szjq(6ut48((UkPixh1=NF9&<;Am z02l;za1RU%(j5_mfq0M(@jb+t2BTN8(BmsGzffas>CFQt|w$?{9Oi&@mJD-Z?g?ny!V(=3n%GQmr-kZGhZ zzY?S`zXsocA#fjz00$TsBs&&e+c7|A*8zlMu;Xb-_Gl0b;sB0qPX?HpV#hh`3qS_I zx$L=M8F&e-1Z%)rupVpz+dv5@16807G=N6X1gzjqU;}O77u6|b_+oEf%c7y7qtMW_ zM?<@ZCiYrT-eGJ#&fZ08n7VbkCsgu<1SgcJnHsJ@S{EuO~47^QR@r|MBGi=Y#*Rcl`gC z`9B8CtEy!{S*oe6O)Mu3276oG%h%Vxj6^JiDYQkO;Z}xWjJRC7Lq-P7gwT^9A2lnM z_AqLK@COgNU-P1p8P_G8nhH)M_@={HA19dz4PpQO6Laoy$MhmCsXfI8_W?fbaPs>p z?f0k6Krne0m)3wu{IrDx06w`5&&{(~2fbLHY``73^9xj$bU5a7$#iL^W ziKBI&tf$C47u>1+KupZ3v{1;9YQb9F6Iq#sHcuMCNq3xS7}MUTYGfABd+oG`zq*mXOgir{Hg0!+?gwoAl>2id;Hyv5xk-{i@)mUHRp90iB)d3 zeivIJb*(Q;@t!kbq#wACqi=X59JmW~vuNR22)PSScx1dB*O5Om_SpJF?}7jKuJV-^ z61op@gzB%I8b{>jpKhCBWs`?3vQ#^x(lYIpN}5cQViY)KP2>!W?hu4={4QV~`?Qvp z@m8hU=-_s+TCo*zd`7%K=}Y981!JO8c#r@$s0cYG-}g^{J_;qvPY* zFoOwcWzy_2&JxpDrdGNrv^y^uw2CxFo}h^Zhv(4BV64Q{hxYuzzjy0y#@34nYz<7+ z)yB`ChEC3JI1#?>)Y81Bj5dIowZG%_ulj=ZrT#q;_jOrUnq=yk8|+taELa4&qcN?B zvW>0sP>uRklK;hgnvEL5UfILCiHnC$ad29L)hAkifo{a_Cg>4&Qag>#-RDS3RoBmcZ+W1tW1gWiom<9z>Tp8u`J-VYhdJ(nRVVFae)b|-DgL=ICj*NHwdSit zd7o`2592yVS1L^m6Sjp_F_XUK;_o%QHCJvV$2_qd#H}c=ap6vkxmdwG!{7gFkMqIl z4-PROO?E;AKhDP+q;hKsNi8h>@lgzNHm7; z!C}I`%dM;G!@iZP)fQ}ZV$}L|x|S~JBNz$?$&*-_PO&wn|d_@mSP?l)iE+@)S8 zk{fle*h&xKzICkrqVJnI#fF80jrFhksQSdAb*1|62b))YgVcrgo!g~)cWQO1*SvVm z$RmW~b5hi#^Zwhdo3wRRUIpm&8Sk7_2qP49xI2xWNTMw!cyFMV$_gV9@)yhmrtFKSzRHoj_nDg0+ql|ud2L6s~yhW|xMPi3HnLx0B{ z*-Y}~`1MBH>)Qqwv3OHXE#2an=Ny)u4|+^L1 z%&5p`?pKsMN!&x9oY(7V6xQOZ2w!*qw>M4eKksvet(Pw?eb9ND{ErjnnPwf+iM803 zpyU`DX#wAw6Kj`5)JW~HJ;h(=8J0Sg^Q|tYogP2Im9z(DD|@$p2G29he7mTU-FLh; z(@W%g59hJe$Z)QvIrHm;K{}_M34g-Ylpul0{E`#ul>Oujtcv@)Mo!Y@=ei;s2k%q% zmnYmYDg<7MWPgfTxfAWZHgB9uqWBkog8xA%HxX_$ zfq$ZeeF^W)arZmtl(AiX7s#tUcreZn3=;(+Ltaw420CL;j(xI`c1fzK%I?MAs6F$t zj>_74Ry2@~x{~)kfk42f|1Nb(jJNLgIHAXv zjlc8Iu?22`>x0v;4>*<31w;C~3dq&wN@fN|{8A|+KaOvv<5sfh4&eR^Q)>U{n>~RC z?NH?Lw&J7>uC0VarMbl;Fu zg|s=vEm6wDujw-3Q@$Brai+d)$;8NRG=O7ok5Z>3Cn9R{yG?Wbk>-4B@bXeuOqOb~ z_~z69V4ritqkoyXzf_Bja)J&H50|n_r>-QzR}8qz;>m6M?^{GfOR@2=a+$?_K|w*T zmf3}crjvs;!_k7&f?SD6Mw^wtpQHs`i}JQ|YcC!!GD;c6!!6vKyC@2vgn3EgFB{%`nIIJ>*pLBV4eFqEyP ze_D?gDAJg(+gj$Lg34eM6%}m{r*DP#`vnJM&-@8_i1nP3$8O;>0e{`pRQ$}0R+?0t zxPie_HvR6Vi3ti#O-;0#%L`|bfV+aLeW^PK2ag#UgWS$fNbx%Cx2EC_H^y(1l8VJ~ z+cI--=(Plpieo2AMzaQxbKW2zAlRHJ{|vh`w`%wL`6(*hX0;=IULpZ^78syX>X3)d-0|W9Iy$I;Fbbqll+vsaDQ5F&w7AC1pOG}IV>`OpRwocA&aP!@} zcLAhqSONk9OFap}p`n|_UglLhSg?PtVq>TD8XWEQ2xKVdKM1E&a9ZMx=d$*JfkxX# zO}o#ptb8dh=0Jl6q~G@a@uMeCZV?jJ`{0w~aX%rZlTS1n{0&>s^YdphDX9oD23R9^ zFKIOOd*ZX7MRa`e^Sf54-_uQYA3Hefw6&RC~7jy7JGTH_y$dI)^pgv%joqx}S$HqC6-m zDcLrhsgeSNgK7Brsj#uJQ!!>{wpZr2+uGWmX;eiu`;*9(Tj{}ymawp3c1CT6tHKwP zX`m&Xw!WMX7Qpk#3JncChMx+ER+3)%$`%_bbar*YW=MiB9?N5Y!y}NKb9b|HOEy=7 z54yLaqGFNp@Qq^CuFu#6up(1+n!k#>pth!4dwU7x`t{ddZHyNB)_=y9`z=pPK|$fL z*TosmY;SK*r<4;Qo;n9p`!g+VY4x`ovX^*K4|?`j`$;*?Vt4Hoo0^+vXJ+cE9QX2y ziyuFE;!{`m5+<3Tt~W(An#Z1Tz9q1+x!Jp{>_rf@KzEJn=^IJO56xeRvs4PPUR7bcb|~){&uLBt@6L5NgG3a`FzS6{Cgv!9)T7 zsXiL&JJQ1ibUQ#A1BGRrv{GYc5`YCBR3g`TWS8fwMgF^Z5XKlLz@;WQ; zn4P%qs{eWF-j`*KpN8`EWT-I~D?Q9v7EhrpF3VmYgJqGXvNH0?V-;U4?b~)jj79hE zrj%6<2WeYGGM5*4Zoe3hMP7H8#J+(Si+pmG3;V$wBaYJtWewybnyi`lV{!7IJZ)0V zDUi{z%%yylLXnn{^-5Zcl1$#DhpDAG?rx-rRg$%zq}s<8WsZh{5vH;48;m`~txyGj z9^xJg1yk|i;Ny2K%L^sk+Y~ILP?I(3U?zG!kx)=b(;pF>-=)%2{j;w^P|vLS1LJSH z!-%3B{Z?~qvk!(!ZQS72L}3E#`((BYyv+ekfc&g_S< zaqRC;JS>JPKCA0RFTZ~}FaGl(M|jG4{S8N2+CQ4HCH`8+gw5tJuNg%ieWWAN<(>8{ zB=%}}$dtz5&y@6Fv!~66k8jT**}YM4m)WPiI{IJk{oAXK{` z6B{QNecX|{YzCKGn-F#gAE+)Ncgq)gRuJ(scbsKi=?WxcVa|H>YKqTX?~X z2&S%_(9-CF!>rzTbHVZJ-mjv!`$rPS0}irvqaIH@%y;3)ads8VlEV<<^YtrR_Nc7n zdF6wbBo)U!QE4B{IpxrJZ=%0HRGRFGn+@50Y zv5#3E2dYrBHw{kx^DFTVH?!vCG2&9QUZ3N;CD$jj_>D>T#k6M;!;O*AQ5NSz6T8g` zF)gio#Po`tHU!x!g+5UI3ji-*-JmeAa8vj#uMOCRg4^Z`AhEtY?M8}z@)s8O+}zyO zr)v{(Yfk<}Q@!fKHzUW^wb$mkV`(pAESn!t@#bLiHu{?ayIYt&pPHIpKQ|p~nrrrF z^XB5=*=r;a$g^3M9V<2O38LhQV$+vOE8%E@tq8n;O9t%xSQcze}ThGJi6dCqE z30-zO_%}#%BZ+@4qIYUw!0RGce*Uj}$g`cwhLD-3PqMiD@2vD!hzFH;M{mx>Dcxn} z{6@{``z*jS_fd&{>J#Q)->m|OC!%#m0Th!SR`fkZ_+COX^d(+m#gUs zE4?Y$(=L1X=G6yx-dEZwv@iKmyUnbR7TTN_v$N3+KRAqAU>l5qJi9N*8KhF7w1fbs4KI@!y5B5^`A|~O9BDc(4V#jQ%h$mX5W2oa<`_YMm|%K zL6Z+P);$zf$og=mwz9pZ>?gaFMoqA2_VweXHh%L8Rfb@1?V@G1za`C|0DI(H&bOHO zDv&U!HbF-(x8I_ok&gc`UE^AHH0RH~HPVGoLPFwpyht)PH+M9BdC>|o(f00cJ8KSn zd?Cyh*fKJ| zR@e3CDuASjJ5Qi(S<=&?_f*(L#YBJm{=2QO#2_`sHi{({DhNNpR_!H!R}3e1T| zz?8lcQ#JU~*EcleGUSobFqy0%_V_oLX+7V9f9uw*FkK-bp}brqoGRT~-TstgNPX`|jO0s;Xq7!BmTbN}7wM({3H- z+agri5vBR;*!S+MH2#(1SXo_d_@$TbC()NE`^najjjAd2gP~ejpF+kT*3P@#u%Qx~ z5(Php2@9evwujFHc0$Wi&i91ElPH@J_1qM>I)l76_FdKwI6%%|3^8OXY*r;5zXk?2 z_w})^&Aye8_&l$xs~hgHD>sp1WH%#zD)vk6cE<5=X>Ki&YiAuybZXT+h%3^wPk5(Db2I>-eu$d zzP>Wo(~^3Y@lx~Uar0W?Y(=?hCtHL$!h-SOaO{sBtc^^q;$Y87S9`cBeWa z$_4rlBUJN9paW=7RNG*GC5TUOkl~XBYIA0Vm0wHyCpx91BTklKI3{UFx>4M2&X9kN z7O+pqx7gpEOnQ>bmgDL7Yx@U{+WvRI=A|uM0B1)Bk;2wQ!zE#K**tBAW_NkM<&M>u zx{B%!bvWH9-8nq`xbipY5zSf?%l-T4^z`(f=i9?*Lrq^%Q&XE;xJ0w+g*oi{SDHAUxyP$B*W@P|`(h`<m+#l6S%@%7fFFL|a#l^*Qre+*a zoA32bXyRbP^v6X25&I=3-cv0y5NvAWa8wmR>1v_gOQxiMQrtVW}S< zCkG;@Hx10g!p7#O*<^(@!zO8|U-je&YmIk7wd^#1$2{v;>!X#b=MvT&wa1z%Yhh@N z-pyKiNg|ovQC?vtrU`Z(xi+?QISku2xc^*m#||A=$BOAUH#a4Ju+t9ve~VwRb#!oe zs%GY04Sah4-=IINbV6d{)b(1c#p^jaIau_m#)H4ZH>#Gy>6Lz|6sFbZLuEZ)DxP-B z;2hQ<#a1O&I8T3fVdU}4kx{e*=n)GChj6N_wwamPwksxZ8xs=~3tL+~7^PS~=ZHdm z+u$C>Eaecwhayj}Y;J4>0!`3s538S?j6+R12V-EzpWRKatrd71^SrrP7^PPU9A97c zTCQvcGmOc^L@ZJf$}H!3pw>P!6-lm`k zC@(L6Ac>BSZnHVTH5{z=Vel@~gj)#5#V{4e*N~m)AjhhmS~pS73FWpNqGdB)r_J*3 z*^dbcZi$IYbqyUl@{aOKD=}?i#+#x4w=%)N;oW8S00tKd{5uzA)Y38zGKICZbu*9q zd?4q_bgclj`xy%X6>l9p9w_q4$cS#aCxPbKvjBKgA@pq6D<0X|*?kU5FbDu}c7c+E zgaVt4=PsAkBM{@C0J*z3KP?3A>%x_uCxU@a zDJUc_FV4}Cj}0W_c<}hKHwYbUreo}jP;u0z+)hpJFsQzUvI8ucHAOVIWo+yRkNsvI zw2PrE#{zvyDynWkThUzBPyGD+)S-3A#axUT=kCD&NxcSNzJAp~ZB9bp3?ks>n?Bw; z6GJ_nn7(YN_LZ5!`cZ>3O)wnUnsJM9^Y|?^3rhY--Z=Hc zj9~P*^Y{DPD8m1aJABvXrY0{^wvSIwK?@3D(lBe`bW3I8H|S86TgY zpHHE%hAJ!)NahVFql2mTLxa~f=uWS8;h-dYkop`Xx zkOj%K@#@!do(*T%nrZEl(|9g^gQmt1Rbi9CyWM9v*RMA;K0KA43cAbkU_5VcxrcnD zJC-XH6dis!epsQcFp~v%F?`O?5gjCQZ)+uEyx_quy36x)FMnq@_eU+}>C_VZZO1Vk znyv?fY>t~fbMK7U^Q?M^MeK4rp!Rb)pQBf+DG8=}3LI*3ym?3?U7{vOGFH3(M~Png z)*a%6tyS&_rofm?e6){G=qTF58`gv;x0UM3dD>Mm5IkDa<(Cyte+fM)yw(?wR8|XR zB!z!GZ%*SJYdoC8Vlh*v&at1UrjG!}cQQ$T`p#VMo^cnM;xQ86rzOqt@|>P#KD95S zd>rqb4=s;BG(EhxP~y^gvfel<`jzDC>dt@FkqK>gv?hLs2B_H<)b&j|?LScqo_fZztR_=J2ju%} zMwxRt$wcd_zkiv3_J|QXndHxN+M7D+6W6-tkG#GMTG~nNDNO7+OI0WSF|$-L`wt}p zk|%R>Zt4rra@m_gepXU?-S2tr^BGizFw>sH#HPwdbIFI1 z>q6SI8a!CLHVw&Tag~-2=i+fe0x;R@P`r8FU!+*qK>o_>oSzTiVOcpjv9}6*p_7GU z{-$kth)7}SF21WXl41So*ZNH{o8A6epXnC$#&ZXTMFxeh)wZ;9bYU#51!32g(u_7+ z(<~mc@+*~ihvfK)d&dQG=`nQWG0Ua&3c1LVl~Oxzv!DO-*fN8M zm?2`%0=B{itQ7v^kAt`rh{tnH=mrW4lnXEG#GSYVTZ3=v9|V~dEhDvQ1pko6mIM>jc+JiLqpZ74}lHuCkv;?kX~hD3pp|CLB0e_7kzXmVKFX?Jl^w|&XiJ|Oo$ zS%3?76``j0W@dt9!h#M@ZiU`*gsqw1mMfmx(A0F5o<4Je^eVN-%(-LarwY@=oX8M| z&E_i9L(p}K!Yb^DLjBQ29L?G|euT; zl+M(tqasK!f38s1d%#^eu4j2^Ij!CtI{iql&!?C{WT})P|7);$Eh!U-6tt+o+8cKQ z%1EZCdJ6rWZ!)$gD;R@*WZT?!rOOXrMSr2%(;1_kWTF0RhQ0n~g3ue97V7 zd)k2@(y{3@eKBc_WYU!Ch+s%vLh=aQ(tPtz=HfBCP6Qjxj&B|OFWpS8lU_r(3IM)& z0Ja~`j&`uL5dnF&!FzqYG#CgEi(%iRtnBRYZ4e{@CK}{rzps2gUZh%T*3}Y7&SkT5 zQ{a66_Tk~-D3!6HVOtoj+zW^8d%*7!0m8#$Ahtq@8wJt>kXt@WnWfd*&enDjnEXfR z4o4>^ec*R|wg7eS;%J@#P>OP~5ix+<=YNJlq#Z3bCUHJo*FM^w{U!ffy%m4EnM8AY zDaL%sSrr2dPYVo63SNhA05Z4d{F%gcm0k{HDg|r09V5M_(0)q;6kDIl%FTE3K%*Aj zxLl5yL2p(W3e~R%-4`l1Uubq=AzLKl`-axmR;^2#1Q$e!EO$9(1$?N~{V?ubipUNO z4M6z@U=hr0Y;p}o1HbCwx@C47WEU3~?8ZZ#btP~q3N98ZNGP?>Z?0<&jd~)5Fx^qcdJ&y1zCYeE*ft z%)fu_Kt1uzPT-U1#zCGN0ZT7p^#wOKtL;B|=$uWNxi#V+K0K03`=A-0;C4a_gYXl$ z8sd!v3tYUAOZgu3zhri^EnGYsVMC7|J@Ntwz0<`x&1|=>3=BAh-9q4O8w1d7s_W_D z`bhp~F6*VIl(~R`X6ELEwx+5bQIqyy$~*=Nte7(=l`iamhl1bbg=W7I7zQv4GjJ!# z@LpslKw#HXBk-zf?CcBxvTL=!@(w^gA}cDFn2;J82J?R;qM`~)Pp5&NtP4Z|u--Kq znslKql>M}8D*#jDyj)mzE+?z$cvQT1IXOA)HpZF(oQ69MWUFyv;oYrMGq3grb_DYF zQopo-!oL;JGoT43gY}Z7Uj7}hppI^eb4aAWKdwIs(??Ku*$w`BzDV@4 zy=}}Jz=K!(wfmP@FCmH`qJEM-&($ej^c=8-<;9t!i>vGEM0ug0^5Aa;l^PeWQ`D9| zI5t=s))m-!mh&Qqo0A=25aBmGk&f?-V$nrBteEGf$)2X(&-zg))H1Mx*P{9tGJjzD zM)GyB!K_&VXX)&CIbk4IQ}dd%nAm+TuIPn@5A6{Q{vbQS?e(xynwy(r|};sbr)4g89j)zHwe`8Oc8 zV+Rzv{rh7Hkl7aiLV>*V#;+2lM!m)*8YT+-nI=H0GMR%8PEK?w1ja*IvjFNKMgt(_ z6@V&hV$PSvMuTs}#UJqU@@n>5+1WjTg=4+k9TFQGkekbdxGrD|_024@1qB6#(JKYP zmc2zzE+Hw2CmKXC3m=Nrn))B5;tzmFxKQ%Pt{>vc{b_HHK@{0Rq4Y-!^sp$Kj`x=L zz$2uqRVy)RgIPw80=T!dv;>7=Zf#8hZo(>DA4UZVBh-S(`U{}^)`#mVQt`ZER#vR; zXFJ$Xd%~DBHAs)u3P0i_c$Swp7B1Tl&KDXPyTMEFlaPzDu&{tN`4uoMog*x2o^!v=QYHG@?XzYfLRIbb2 zaY!wL;csee!~jh}c*gB$78`WT*Ohh~M~E@gG!3PNRxXX%^~44MbzP$`L55Opyx;_2 zX1%_jV*4w-pMU-OwcKqCIgoMyJSEGPOP!=buL>{T=mS#5Yl0c5oTznA02x7l^o1%w zWD4qAx4tjtm)AEoepbv@rA@i35|Xd&N|CK7k*!)>Pz<6Ww^1>-l4U6vQmxwkRpQ2= z&WwzVY&YOCX?^@C0+-~n-=qNS{|$>Nq1^TKIUymTWE3+x6y9dJuI`Slk^F~XdrVZ= zN@QFj1p&4WlDq*@%xb@>E*{Bv-c%$24k9R1t1x_%mG;YUf6_&?3crcKHT=WQfSu=h zY6tTp3bq;x8(WHIty?Uw<9Db@=rZY2lpqDAQ@8`!p;P$n6&{Wh`6YNYupx4#Cm%rJZU{H7avr?_D0&Vx;~5hY*#!a93aulTOL6-}2N zVYpNi3|6_r&I5=KC^;3wMO%i3A`~(eLn0zXK=`~o+YA0=K`r1G3!YS8U!QWd6Gwi2 zKGx3GM0qGo|3+@(3 zR^D-Oakt!7{@w>m+BYx|A4>HCRN&Xyin<>^9yXQrH=l3MHXg?h!S23>gQIo4y9gCF z3N$~2FB$G%_XrCk0x#>eygZR^O8|6>U|8Yg0kT|NT(s7k({85>Y#%#iypF<^G-emt zLa$!E`VDMi_%uHdR~j21YVPt_ExZA_6*1gdy@iE^^PN#<$lpQ2Jf8VXaCElU6K(;* zf{qTAYX$eYIsBNFmBm^t*4x_)P5p_)xXmfVU*7(S*PQnLa`TOt2QIeO>S@J!v6^4m z=j&M6UfWtDEDtqhSNc+MTZ1U&oa$k8<*Td_-R;2xyrorEr)JoyUf$lMcsr9I^YsTt zlR$ZsAzi9M0#I#W7zJe&?`dZ66U$^S~UsbG^LNxP&ZArVzqkA1MP3Y+$+=Vh5` zg{z{e=XUG_cZ&HNdLqAdqRq-ho#s#bu2g7mv?>Xt_k7l)m~+)Y2IgaDR6Ph3tX-rZ*MAnZ=E zD(!d8-d$@YUVA4gbT-^5r^DXD4blX2_wMCMwPGW0@zi8kJyqw2HkUdC(e(PbTXLwS@vT~<^18y6nax^eU66RC%MzwWxJj=_ujEc!X`-dMuUTF_} z0k#d2*^o-$hsPY#lpdzp%NCG*zg$TP{w14w9SwF#5=hDpj*i*@xe@s@LoOYMfB-uY zMz&qt0+uKgTT<|Go($GXk z^0YtS0u3n$>Jw;dpc;mNTwryWWu}@2qa>fBPD)NruHm>yJmt7d2~gyFW?2lUc@AFN zvb`$ojZ|1eWfc|4ikZl|92?{ANK(3QZfRrl0hCMmY*k_qqSV#ZS4Iod1zVpz1oHv` zLxAy=%S*{Qj9&ZSVE_>g2`?Z!fU@-4x0{P01)zKHz`~%neF@!{fA#y+p$Q4)v z#tftvCdw>7gP$P>rT*k_Ljs;9)<-Zw6*zZ*qpm>>tT~#)g+nhS|rffmoLpVbd10`&2M3;s*1jy@5cLm&dx2pDT5K!|k z04&66w2@1f(t{cUITpA>VsOL~xT&dKwmTudOG!yRQ!T=Qr33C})C)_Uw388wm8q+XOBAmoD@@@-ZtfpYpnk9$URjxXTcgu_ zeJD%iGgw35;I`Qjnw;#cYS=nW$;rx=IV&yC!Ui=5yUY)t{JBhyI+xi*Sv~aJ@Md># z#0Gx7`wTM-&A0_BQTX0~0XrTU`xhWW7C;6-k!$fl#|R592Xy2g%zyI5y!Kp?l@gAifjo@%`HYgq#gOXdlile?=^PIl&}t1Q=?Ty$@4E-btr zS+U?9;-D>Rz%yl4K3>SQp1I{%>Vhr@0Zri3t(99fYLE744jRka99%orj|tbR1;`d1 zTZ+ivnw<7FpYVb_i=U2lt35|TsDkJzdY=0e|_LiJ{UXiq4 z6(G%vntL1I&yysJ*Pv@F^py0GlDSj>VS<_^&G5AQueH?iCTq?b5GXho0>4-qKFW^0 z7xu;y^{Bieo#-`TQ$vH0BN3PA*JB(jbU){vjdMhUP|-Ehj$g%h5T4Wp|A3Y-ce=~n zl1XrH8HAe`*zNdC3pqJBR+l8F)U;Q7M=jPS&OH9G0$tZ*WreaA=&mmD~8YQ^W(aL0aQ=gePiuxy3+m-EJ5Ecy_N&Xn|- zCgxeNvHx4Qgpb|TWyVUM95%5%wcM$sSV}ChnH+z=xp_57Moe5%c1l-MJ+G+f>YwmB z68#Ojo0ikmS3}*pDvzGv(^Ad3J!tw-w-@n(izi7jQ%qd^`%of|uD13kk(B$q&n9Nx zMT0+7?&Iz7)`-XFUF)JV6&MxThVwQ^)}=tD$=;|=+oZ{LS?mvojqnymNY#a~4v%cNQJI?rv~7GlcVzKXKfYMreTn}_;E!_53w z%Xz1nB!N2~oL^@5i!)vx6zI{rV7O`P?inna1E4}kS)$td?EJiL-ucDp7M3>dooCDr zq6JWRKu1|ZTu7GpP)i|FYT9|LTCxP2 zfNB9kdJ64D&|E>xxGC2!TGOMX=^DOR1ESC}T`pq&0?nI)oqBeZGK=Z~)(So@L4AEa z;vYHgEs;v%;#2WPxD(x5m z+*n}<(EM(RF14++(T@50CsGSnVM4&^cDqWW_u5}wyfb^^?{YJ*nQf>->{pRs+J7`5 zkXNoZTf4hRh>0bEw>s*Bf#hmI1nJGy6hQKxpYtG_`EMvYbKQ8Xh!&E5JuDz3NH{pW zfN7O=8y_8o(B4Z~S=k`F$9f%I9P*iizvSr&Ib{)DS+bJ`U}86O{C1qK<&dm*4el6zf(YmJ3+YY8&1Fa^d8ff;SE zE7AU&^F%OOkm*R?eb7X@i?*8lCaQ~&AS?9re8c`UNo^qPpvL)v?gP!4;I~q4&cfq3 zD9>_$*YtR-MV6@`YXn0eA|rDX=!-sdEzo^rEy)00guP1wo@8-)XnNnveY5pAxgY5<1sy%Jc$P&Ci;K}>zqql)LAVL!&szDpw-r3QHCtP;h zXxh6tb((Mh%zJ_E0eVBLwLecCFelikd=TI0Dd@#|BPP}shT6}AFyhSOV(R+d?ye4q zn_RHTU`_pm#n@g0A;Q}?Z)$1jYirj-z(l3s!!-zp;sQDZ2RdcY$Z;`T2|)^oLnm-? z^boX`MQ;*~KjGQ=Uz}xSZ-GcS3(*%|hiwnY9wJ{52FC1W4-gCG8kghS&39+uhWV z2XH)xbUhQEg?vb-0(qWp>kSyy@I_r+AP{=+LN6a5tJS{803SUoE1lE!KpB!P+2`(0 z6vX%~pV816&4vhHpt?;|J4eFSBlZA#Z+)_FSYk3N1l$cc17Ftf@31tKnj9kc7t z6)m{0=6I6?@)G}xb>3*NxV+@YuoiuF*{x-CMYN}Zvv%Qf!aFtfF<@H2g!quKhPY7! z2 vZJwrzha=**F32OWIN1P?Ac{>o$f=$2r}H3)Sugw{bOKU}fTy<49o^5*Q6UdS zkx)unS{mT9nf3Kfs0!WW=2ao{XBNuJcR^=DV$tB`|E;h!M);O-ZgtvE6a-In%*}hg z5I)Sy%hQ4>X=-Xhg4<~ShI4yB>PAwfh=oi3JOCgD&Wv9PrZDhmHjcwpxV1u1QJ&=v z_NzDAAc7%KWkl}?TKk)X0ql1HgbV2a>O_FxF6Rfs2JB=t7bnIV?&r2h&RvEig7LjL zBx$3;0=$3!KAOw%Vk8A9B2d(Hb##P4Wc#{K7OY?Kq&&(7f)eR~G6CtVMUQ;~9&HI~ zTj~VDuVDIAKs`b1TF6<(FRuf$Y|n?9AlF_77sq-S4muQ2Rap>-%%-Ynf5(beO_dlpZg_8&tF2AnXI^@L zx0j@SeC4p%{Q5=hOMfiK&~6Z1N2;AULCRCCdis|y1>_%{@87>ez5$%FK!BrGmlv*p ziMcH2uG;<^Xd7mQ{aoMC;s2GG{v#>B^^&CCF9z2d<~WENLx1oE&32tA!bUnryqfAhy1-5gsYT+g6eY za>P8W*XYA(xIGC%$0e$Q3|a)0L+M`|&ZXCZ$59l+0=z*`OF&qGOG0`FOSQx#dM$(& z4j{n(6np!25jMJT31R1&F)QHsD;OA~AeVqB3cI8AZ_4hW;^sNXZ*s8hB}+h=PKUj9 z2D@5(1oAfZn@d^=GHrG>ecQ+{_RtE#G68O$VsJycz=1?~cZaUtXQ z8F=q5G(uX_Paw-7;+Q3p)@v}}i$!IFMFmuZ0cB$;kyNR?^x)L*LR`k=M+ zn*l#ZNAgLXijLAmk;hRyBAlHpiw_+Z8<(Y*p^@tK#Pf;2ih4(J`}XbdY1r3TJ*+fO zp3s@IIZ+ktoj`H2-<;5fLWx8>5)%_8TiwBF5Rbog`!<{70rm}wMBUaPAgOG?hxMLv z()7H|9JB%v4IsKN^nYOXSdEF0;#O8wwOZ~5Z}`g>97Iw*F*oXgub>G8(8gO*v07WU8qQBPN9n}AD?GYOMQw&&{ zbhB`mZSo&S-%dW$tcitc5rZI^?b%mIEbkKr_E!_#qoUzQG_9_?3vDLTHI$$=Ae!*{ z`Z^vr0R%j-JQFOHU63coOYngO^YZb*6>dQ3Uj%m^iR#0H2t9=jHVR@2j1-hfy3>Lm z0`62uO0Y-ZvJ(hMnagoOfq<(UifF805=(M>Y4-$j`uLy%Tf zslHMgMb8NR$2&I1-|~5cNLV zke%%FG+^dVAi3xmSb2~r^&y~^0)xSSpVvv_edzw$F0A={rvoG82+Zd^Fj^O>uDv=UAdU9 zsx?^5gg{W8HKd)XF6S!6%MJad1Sf0Vso^6f-@e7Kx?J9w=x`GN4aJr9I57vgy)_ZlFF zK)~abi5T7oUxO2)eko zc2a;tH3%_T8r45obXzReM_2%Q{xvFoL_QxJ z5d=LaT$HBNWV8Wl-ReNbZP<%0z)C3ioaC2$HStgWCkrsX_l?SQ|JCk=Fuf*(=xa@V zQ5AJx#h7*OsF!07($`n9RBShFO5s8vzs`+uM|s zaT(S(Z{OBs#@~MdY!5LlVTWzQ&IeTH^DOujN!5aSL<*n<$WaSq1Yy`l!WKMfks4oim2q!lfhY@ zmRmH1Lt`MWs~*FWGrz;6(Fr~R;{4JovlGf?(v?`{z9ytOLr*1ocGAtaA-k z6)Hkwb0?iQERPO{*EMS{PwoHa$}OGe@BB5o8GF5r;<+J?3tQ7Rz8$>ZEAU3E?)y+7 zhQIZ3ufluy8Aq2ovjp1z{#M8|5te_d3e5sQ_P5o^T`PRt;7_?zKh;7*+xdz=R7!+& z>bE#wwhFZ0-Kbm?MFncx)Q;%5#)E8*g>ul^(tUBcay5|`edp1}l_+k!nhUfDGoSit zKCa2EqK>D0_8u3vZF{iX{!;$t7BsnXMD1&SXKiUZ5Z&?IWT!&W3g3T(BLvlRT{vuC zbmr@ri!;!1cKowE)l$+ztuM@=xb$RgddfV?7|vW-ddm>{0$aYz4 z<~%^P5UC0O8h8EoX4Ci#?bmjCvt1) zAoSB+L{;!VE}cOCOkhTuX-E78Vn}Nx-$hc!SlEA4VoZc6R*XCxmifaMe-OIEeua>| z{YHwncfIcKmf!D>88=&Y-`c@xoBEs9xO~_1patz z>b(H3r{uSj+kd}Vk7n$oR&$9GGk z{?~`ii|-}->4$WxJ=QJG7UZ zj3Qi&9gTY#uM?!Z3h$auNYbu-Y=?~$))S(-_onW9PLx$o&+Frp_wSP7U}k%(F)P4J zq18UUUr^kwI`jl{l)%>em%2&Tln>ND}RlfZeIht`hcBJrtqs&|1LMAz~^#)+m2 z%jtInG-?)^*@4lD<(>W>GtnAz-sivBA`;EX=Q6_*bjDQ4vKca&{=+fOg`#RTBa3In%18P1{pOij&SmFf#^<`XE& zM6=EDXmS-Wi4U{iIf#<~**tmHJ{_gWCA&cHWOcCgNC_3j!;js|eufXx*{B~s00=Am z-2D+Z1F--ZAZ|2E!!0dXpqHCX*PJ&Euk*Vc-30jxQZxIMiRlt+mf$5m)vV!#N)+Aa zcC>x}W10%5T-uW~AVF`0iLJJ7HZ{rAxHuMFE8lP5Q?0x%v;3i(PqXP9XUN(8*$FXa zfZ~J%SLUz%3e4xWZ)Mbk`Y0Lf34U6PGm#ecELo7?y0li`d*myh(xjGS_1!#S*^I(sWy&+Ln_?)TF<>pblR>u1H$QLe4-BL;Ot0O zZnKFOn`QGs4~>A_sUL1lQ=bB|t!_A}J>M`#ViZU+%>BZJjMGeA`~1EAWHYqEkrGpK zII5AWC2czRn;85YB%h05W?ZH!nHr0ig-Lhz?@;Q+c%Geni{VI#L;fh3_Wb$YsL5?8 zU*NF!pT)s}bwHGPF69a0W$`)f&wy#(v}&X19&5BDOJ`KQ6+C}l z1nl+lY*_$;Q@K*{p+0VxK}(~2M!fl$KK$tV8c)BRTa@Whsk4ywsd@H3tI3VRyNn%v z`55(yz;7$}62m{iqwoA%80Mt|j?>hmkK7#^FLGWXytW8XP(uN^S=lGhTM$gSySrNk zctn@dtXLJ1Z@wnOBHI`_pmvx+2f3|UTvpT0?)l|$k9!%sT_s-!GnjW5hpB4&3Y-aj zjP@A|UkAYQe~t(D$s+M*v)XgJc()Tn8*a+n>O(TXx8P{{LilSN9It+`jwx=m8ou2; z9GVjz-}#!Ll~Fc5HG#vUaoE4g_0W(c#=`7?lB9gt%l&*!GuCk_dct8o@ah#}>i|yU zp#VS)y+FeypWMd8#5~(>BtQ~XEaU}~X8I7OgQ|uZ(rGPNursGRiCR@{DI3b+vcH|Bf@VxoHy`uce0YsysYAiSOmjs3G(?<6$%<2@8RdzcXb>zTO|8szmBH%g~J0+?M`Tx0=B02VOTOAuVR<^$M{P_>tV{i_RsoKsLpJwQ_x z>#fEbv~uaFa9syO30z{zS&Jyxkc!IoEu{L)5Xrlai;Em7=bW&>kmfre;*JXf5SG29O^8-M-35!O2$pn5%7<#Z7gC)ihs|$;ON)mia$eXbt{~7>1;h!ks@FS_vPZyU1obfqgr9s{m^%3PsH_oqhfVGB z4V1m@;o1w15;vH_g@pwR0CQ}hCu~-%G0wiRlbIGy@P*KNV*jen8lrju-b-E3xJ_k- zjhkL~F>Mr;wD@I9I=#My>HY%Abb{3Fj!Mmbw87oXsXNn3Qr;qO1@%2dx@La2Ht!B@ z4Um;%UHLS!Jk$Liwg&Kz;B#tbt(U2!RxrW{+oe~`ngNZ0@1M{$xJLr;?{w=uG1UQ& z=P&BV*(!SbiMYA(dwYAw+778G%k`{OrQK@v>WVes!ZAX^q#z&q_pkld6@%_yyxIuT9x z$b}zC?|~E11+Y;@Aso{2I?rPqLD@xQ7LDArU85+{UKPF~o^1jq;5`s#_k5FSm z5)1-cC$HFEh0!L-6I>v`7g^H%JN6A!n@T{r*~ke&nv323 zIpDXY5$mlPO03S)!xdQ^2HaF}7c19`b`5SGub*318gkK{mvrSRxH_yO;;|WoB!S%q z9ceo3xUBI_uIWK2h2X+!j>ZO{)6yxHJ`KS54D4{*w*TKt&dg1{mZ~5o&mC6tZ<#$(6szE&b zqGsV0UdSnkOcAK1tgP$=R7AL8?WKpH<6#0r3j&@wiu40Ua{>fb%*g~ycW~P}22^JQ z#6E~X83d0tGdn7VDD^YZQ`=Nz>r9)UnrB|zeNkOc7RS!Y8k>^h^DCkLiE1K`re}B4 zbV%W6dZBEslG{o{zZq|typa>9y<4jv2Fv6}jnk8+^Bpl2Rn?w>ft6uZz7+_B-|U5- z+@Zb&(-5|o^74x!ZK8n~`37Fr;F%Z2KUO`TM<@z@9Q!=b_{C4Z-c$nhXvF(fb~fv; zs^@)x8EfmlL$uO>xgb+#{4e$tb#!l%;OP9Ae)su7Z)vTX7hOx<*QWD!Ym)X{&%-iNZOfU|N22awy-bi z-yJf+qys(2!*UBR5tJ*x^%~;8AWZlJX2VXMgRsT&LCGiGu}qdvpYB1X(*L8Rsjhxg zN~#%cfr0oX#1(_?LAawE(3#qsC@5edlSO9ggsT3fx_n=N_o9CUG6VGL#MM&+2r~m% z169<`Rr4`OfUvZA&DdIMh1%Y4P|^+FJ`3-fIbPdaxg|xLiQtaNJdXHyG8AOJB0#?) zgI~zt8q0NpZ}u4|8NU0t5PF|3cgMkqi_APuqOec0DZdmdz8v-hnHCqP#nxkOX^n#} z8FF~_jJuJ2Qjg_gRur!VpxwE7kv*)oUlr+|f#;Cp)~yKjcmTV&MDRdq-u06~Jc*Ns z=P4+@L1_|!ATBc=b3)G)>&g9}`x6^%w{>wl##)jd-oy9&ykEy52!DMW9Z|pfBjiC)Os1TAR-`7IRS)01AtLviVrA?%y&)WY+aBD?J^()rBL$0 zfh!>2*Z$Uls#5}y-6Bz3FaI6DO0f`bK+n&c)W|72BBQ_%H~slwyslJ$=8Xbx-8%Dk z$#Z*zRGhALxgqY!54-;pAnLh!y$EswiaUFnj6p@nU6_h}$jv~KYA-p%&DEtLk&yPs zw*>{0gzTswpd+Fm%J|vQSBB+v&)XLGH#RoJ0c8UFToTZ#kA+k0ri;8^IMmoKMe){! zL(B^SUC=klL5X8pxEoP6e{^)8!XjHg^e!teuLWh!=|UKT#c=_k=c!KLZmheC7B%dv z!1~dXT}rJdFyHxxF6t zg)Z3qLFBdA;L;EWnDLpxb{v^lTYkY6TnsiX1cPi-tXg zbg$JL$Xpp99Bv5NQh-Dh(iHo`~h=;+;?Z+KsaJT?)*ao8tYNc}Mr5v{?d_@0;cA zJ@^gO!+>(r{5ii2n>ljD54D563O5pBc17E{v7uov^fa|lqya-^_UF_WF`)n(N3E-i zAZehw*{QvR9>DQ%On(GQCpf?X&~+L=eJ$1kgw=Jp44?>j*vJ*>+N-)FHZHq5`NIii z`31MxtoGlTUx!c7S&yZD-p_AtS--bpds^{6_OJ2j8F>i8i1e<+DTM$dCiu7wqo{V3 zy0^FY1dJxp&oU%45Jv`W)c_($g6~J$XHYkRGV3+x`184@w1A5xhWMU*>HSlz_`%}! z1QH^JiM_~}kM|{tUS`&{W4Y4PWoT}_LTTtJXvLEMtKDAg?2+Cq9YsDwDj)j{t~!q$-dEe0YU46`y^sXO1P zFK)Xs=U004=muoyx50|fm(IN$hboyJ`qDFcm!`2LbH7H?0mIu<~VNgEZ~Nk z7^@yXMF=rF{V|iXvvZ#Dj;knFC!=C1C?N1~?TL=dlt(q~(-xRq0v&VlPjym_+Ln3u zmoAr15*Hnp54%}nbZUKJ^%NNDnkqem@@kBzPW$b+w z3L42UHwKwCuKA|>Y!*`fI5f@A0+*ZiR18ZYt7h!u_ zMEtOB)`%Vbz8~F1w0yFAtufLHmiF2YY1P+>{LK90DUJvo3<~vtvs31vh_^lcw6)2y z7m$lfEdB=tV%ridTsE?ZRQlr`Iej~SClLZ~gOjA2Pb2ILxaLCSA8u~NvP)6C*!A{Y zxMHPIRvImRt4{~P1IlC;0fD{ngx*oCTE!_ogOWf1=o?ITwZE=tTK8PS?Oy(p@**!? z)zo({{1}9!6(bH7IyB3Yzd78=h*}gAI@T~`-uo7!V;H)3c}1qKeSIC`auE3S{jq-Z zeTk7nZ;2PxDWH1wUaJ9oBz$VSXx`8NPKXg`3-IUixp-vU97Slu3d6M0mgxzwxI%*9 z*iNF^S;MDMj$Q>6vcH-h+_{A}IKKMtw~&pyU5VT9=2(<>)yt6b%~ew2l!12^t%cTk z6zc&j_wL;=qI_)2PTuq}7#13}P}E0o;vO5__olTGE2$b=I6k#on>FYcVJ4!^Sr@qe z`=7zf(Yzj?n9rD&N9{lQ1D)Fr3&?U(JEB!MuD7i?YsU{CEjy0mF4vOF?MQSeX3e%) z3e&lLzetFf`!=?{Wm-ILXA?-wT&zc0P>3ei-DO6+t8B5Q-zoAB=Tt86mx>YjOg)Yz zVMgo|-O$B@BZ?8zrUtV3dJVkV7*PqU(W7ZM%ffr1gUycQxDgTiPv67nu4gFIH=plR z-rU~4iU_%s>Y*8GnV);wQmQr^c7|p&scE^L%;OLuSBPg-VedJ9v&TesksoL4Hp#?I zvxGA=uFW2+#s*R{vK-1<_SQtyGET$$t?ZbMk8(_z;avnX)DEAmFYx-mi+c&O+P)KG ztwsMGbGWqp4nL3a`0r64cI8X+ArHFAj5`XmI1$%>eI{)Wk#}A@i9LRuqKOy1J+a5PapYSVHMO>eK*>tY- zMM~OaRQU?IZ!c3dwX>Pxk>SQozcWcn9p+<7d48{{((U3XTAJS!R^>P9XEZn{WVI;{ zyd{8rg^G~?3tcp=Xf|4i5YtU|$2tx5!!boh(OQs(e&fvivtQ|XIH9s~%yI2Mm8%R3 zApsL$Z6xuW>&qUUevMccilMz1Ur@kpFRbl8x#Ar6;X@Y$KxIafmJL^n+ew_CJtf~c zW>35Qd*rAdus(xncsE$^9U#+{(HSYjPqzE$L8jyg-*pVRA z4REgp-0wLVtaNqvPJQh`j3iwCE|&%jF)yJ>+W$u5F#3y;@BH|H#eH(^9n4!nde82j z9wu6YdhtKbZI1{fD4bq?6LPY@fTh$BiukB(;cN!?OyMjpp_`VX}4HmKvoL3V5{ zKB}6e&T|#$RK7PQQ>aEwr#^KG4vM!!M`QX`c3A$J38GdwQ4W{dU`Ma$H4KI6U1LlDwyX1>}v9rScKzLE@7QK4B` z?LPk?&&0*Y2Mw;>FCNbr4i11~2APQg<#FqZvl9$T*zXq{hJ-P?MhzqVR#r7+NW~7a2f;(UH~`8&zu@?c#oerUj8( z)n5HOx3f2zDHk~X(EHrw*BbxxwFg*@VmN78OKoh|$RfKv>e!~eHwyul3-#Hl4Qpr+ z2ZJpg4ZcJDb`_r7IUG=lzu$mza8|;OG$RR(T|V=%600l+1$cc)?PnWq1WGe%Jr!#I zglM?B2L=j1@`z0Tb!B~`{@iKYtSJ9P>Uxk{%Hu6pnVpELx{WogLasBALwl`fhOGna zB=wIYhzjVv4VZNVEiDTQATJBh(B?od0d@t%ptlcz+h~mgxg-H8pV#-_W)qGguT6_% zBu0gp&d@|?qg5z-e&fGZV4;dyDM24Yz+e~zv2_K-S${0keW3C`1C9pd{2xH7Oa1Ek zg&Y5!ZGu&`>f5y)q>C%-<6VM}v4UElbOo>?b0BX^ub{+w44n-K<>OOQ`e4hDZ(aqD z?Zfh10cdU>REEGIOHORNgV+z5J{X-lNMmtDZ`R zuwVwL@Fw805Et-zoYlRwUk9H<8;lHA`U`$P2KNkDSRnCwZ_a=HVHZ~<8O!Oq2P;{` z%-!a>L1=IXpzm=KO@lDElzb5)o0<4+UMW527vu30`BY3pA<}rU7BN-US_UE56G*QK z_L`|=o70X~Pokx=#ewnK}k4jrS{PVE|EHMA)2wRJG==!!IIGk=a@JI3dg zDcB)D=ov1@tayh+ZVLM8#|73m<%z$6Or^xIP;t5mP6md9bg-rYOi<>y&^B=f_c$&o z2}C5*kB<@18LHApFDLTb(j|NV{-A-z^)VchIJmPxE`*aYsQ>l3PTE~?lL4~h3BDf$ z=mrr|ap@q9?bT0(3WEo{GXc^+oBAzIHB znl3v_z5mmxdGK&AP4bt&4fONS!5G63DdZdWr~nKooxs@AvbccT{DjQOEgVK=3JNAT zim0)YePt}wSKN6i9k{h+zvV6lIFJ@cNROrH>iM5(m%&U9JaDG0!UV2Jb>nEag0J|r z$rRRIlGm?j`#lxo&jl7BR<3}g%NkHQh&(fBmIVJ#3xF7mfzN(I?EQa&xlFBa5Sjjk zSC15%HBhM92w-yxe(X zmy(&O6|#68^>Z~9l|1vMJ4tGQMspkmfM!61mZ%8`g^4nxSB2gv9YS*X)4und*mGcc zxE7d|#SAt#4M;Betj8*Tv(o3sp63@e*~zx)gu$izT0(1U_pYZ@SXebDRVdlEmYMX` zoF=49KCB8O-B#qc8{j^AyNznTAK7Ec@1LZ~1Mc;7)l<;?B@S3Y5|7LxA;5Z)cLsZpvEO58PVu&O9uZJI z)^KczYHZK~c!F6AvDTOtA18JtlfOK$>{g>RJz7xO-feD8IEMgvi<>(eIw3e=K4h>j zjIBlK812BGngzA2$oW>e-L!*M77-e~lC6m!iwVkv%V@c;y!P)(^&_R5u}}(|dM;ps zk0%Z93sFqhr!kdc4haU{0-O*+K_Q{awwCt^3HL$E1KBugro0(;KLLYeD!dJV0?>c4 z-qcMK|FyKIqa}ZK`Ont+@ZDL%%cI@BgVVjNI%^h+0((`D6FN0h=ah9CBer5f|2Ox7 z*M6K2%vw&CPZ;TGjQXe?(_Wn$6Q5r=UIH-oQ1ayC^yhlW8z-0k(uutdrhMR|S%#bV z1ui9JcOw)SU}Z%Bt}#y{ZA#y_6X00ufKVWCodAjSFg1XncswdE4xUQK z;@}|RtZI`5gDd}a0)e1G(DxKdZQ1LV{#cs4{f|3;_A8rhhS;x{^Dr79w>mxz_6qab z2e6+*Hz5z5mT}LkM#$3N#s8`SVn#pm5m=FhJx`)~M9Z;181OFvS)6cgi4}hQfP~gS zN~_)Yl5zsxY19*FRc)V=+^hI6`f6s3V?hY8EgN5}-A`)=WU8Ala3@G7C}3ph`V%n?IIX35l!12}4ii-J%MecrotmLWw18=f;+F>_lnpDN z&=d3W067OL3K`Rj0$7(AWUfLw%5^3`|GJ~yQl$@(Ssyv@u!2z&dMIeNMn*<%2v}es z`*FeXBscv{`hbtKhLdEyibvej!NvEy)qn06#Y)aiozN%vXsosmFg#elW%*!Gx$9E$ zZiwVuxivA3u)R6VBLIVP0{9(<^7Z*Z0%Z1h_yv1Q@>NKRrD~#zR*?A_>xWVxZ!xfq!CYy?J`H$%&1$lpMPD&|CW3AO-lB|{ML@c& z@F|Y3=_Ckz5A;J#ATU?c&>-@dcEW~grFHkX9L6%hom2%N0}=@`LK%^2LjMUd)W2gm za%--x6;y>5r6c6*8kR8eYufL42Dz^QcEv(*478D)2yM~7Ns+6w-s92KZtHt4Jr&ju z7I_x()6AUahR;zFs~^)_=cEtB+`SCvTV5KvlIVT6W_;_M))NY;L?Hiwg87u28;&>` zfc)E6emIz3V_cL-?yT-uKhpqM3+OH|i^uzTs;Aqamx%z>0w`fm@pQcJuCNMH2EKu$ z!%2j;^xT^p7X36(P||=s5%~-d55GY_15l zS?W03_5Yf(#2M&aLGx9r?X7;EX0F-8uMNQu59rjVCQt&%)#|=3*7f(R9S!!P3wT)` ze(IV)g)_+O2hmCs}=Y9Nx&tYeBPx0zAni4K>$5ea7wDlCsH$E2bIxo zV9Ptn$nfvsK9S3MgENpB@F18+@5i&r;vnzx>p+@-=Bf9iB?a;-8%i8W}_ z{tIkzu%@6j&&3Qqd%c1Y{%cBCx6_>eN3J%@Y`~SDu>AxH%W*>7*PZ#&S2MA0Mr@21 zMYaiRMB_yz6uflkDIRn;294DlJ#le^WBBVd*Gbf~`1Mf!Klc!fI-I7wDp-ytF zHNJp_hv-04Q)!KP_4^;1YEuAY_y!JDKm~7Seh9yoC_lw#Z2RfU%Pw$M0hU9oRF}_ES(bloe=FbX36O|8RBAL(I zp%kJMZ6r!_cFsQfPFOR&=@jQP_`QwI@%E3uICv8twoCRuD!nzETN@vK_MRonx$B)B zJ}8hm;O~WgQ^PAgpWJzO=#ekPGx(xj#@MG=t$o6L>}|)+M`^lGMu@J`>RmQExv!wq zQIc62wYDVo@U55c*kxH&kCSW`?UUp!DL2oeqMOIwsyl~_7wTD6C873JsqxlL?pw*; zPV^?G>%Tsxhf4+C9YQr(-el7DS(YqVNV2iyNDYyrYfV2V2YiKnW#aqR_hCgD*~L4r zSUqqfL|KmA*m?Fec$J(##KtbqI+j>X7*6JsvD4x4f0^-Ts}{dExU^h@sus5!91_BM z{3@tuSJ^04PNKyiu~8su@TcCTxOYOb_OC4Vn>;@A8dBF>>o~WUie6Vyq)Xr^juc@m zjtF>1MQ}~1t;Q5T;hHdRT%Ns&U)OsKhu}(yM^j+f1hlN{j<2Nv@|cq)K$%6esu0Bn zQaVDRU3hmq8Hf$UT@Aet_uTM93>uezK62Z_W{2LPSExQu(2l;nt*Uver%L~EsOYLE z2}K)NJcG6v*eDeePH!ILYWgY{OoaT7)Om_6@CXxEY;gh3h9oEm?e1yOU_ zsf!L_y@BmO0Bs1H-F0qmYg?57|I_=QJO-UP$6Ohw`K?)*PjerJo^{<8HoY{h+_v}H zRGvBzIP$ZDGu^vcZLS%!S94n|erj7!c?Uvl&b3-8NX-przw;ptF;t~ZZLfm8_VpzL zOyh?N^De~IMWB5LVS2|&u`v6{&TMk$~85BKH)UGmiACeKz=`Q23lI=!6`5oQv^gH zKQ>$;m9kiUQPR*@1%b~>5I%3q?(tuUFvxJ#uBzSfVuo=O4?08!Q&JM!Mep-k((f!b zF0K{oPX}n{5LFK{GG*%w5U4JMYJpkNp38BHFauS|KER>rl#Q?PfDAD^#wpTgLWd0D zX}HKl%>VLGpG_^X1!i-pfMlVkuWuoPESQ0}*JVC;;0bvE9Nj0=W>BK$eNdHEy?G5`_*2Vhbo8<(o(@Ne+D zBiT2BO&uA4;0tOA5E&Qt8V+MGK?%bObr$e%!@!NgQsk_Jx(FXp0wdK@1bg~CIJ-Mu zRHGw9azHtA16Cx`?Em;E17a2Q|4d&{Vcmx5C;#9`oMFvq>*#bm)Rd41l?Wtl21MEe zx~0<1aszI}_lSs~6i34XuFmI@fc*$KpPJ*Re$$^O_5p+eQ2v-fJ=FOeIND#06#?lU zZxSv4{rgPY3NcYb%9)RgYYE0yvtB3AGrUHCbqXXj!h7?;I3re|!#NZUC_!-F1|={q z1{z`tO!YgOEbVE7Msd$m99jUY!Sn&>P4sQ1fC)zd@%N1>;XLJV(chGml%+5g2hbeC z763w!h8cVbss$$A-l9;q+}o^f;hX`<_&3%=SYBmN zn%8;kA`{*2Z5EUxFMu?3ap8;DJiU*1=n~Grcr}R3dy!*?%Doie1KUA)UENt|HB=Hl zC+{2rbQnFp`4m1J5FKRJXHY96;wdaB|1Mf+I*}!K^(|w~EUMO(_|5p>|JK&KXMdFK z0p8MXeZ-5`XgUZwFHUtVNT=ZE47+&(e*OuE;&Gb%je?Pl8lbH?)~Ed$+%8LlpDf$=~`4qThOAMinE1|`yy&DRP~`d@?KwH0Q6Mc43&(-TAIx!zrn z%W*Wd_E+~LVEs~zF5J&Lqi0=K^1HFKRGp)Av^f$K*=x~}@)*jmdkPq!_JIPk_CY;_ z;RQz#uu(W*rikIZ^HCp0#?v88WG$Q-2o4ZKB=lV4y+X@tYvqccmXL~y_EH*Cu`>9>ARPGA zi~2XnmIa?V(1Kv@%A40Vqk}&J-w)6Z3O}IPhV`!i+Koj}DTpXp9Ej@<9*0qHXx++z zbc_HdsX>JgG_<9)wF^L>We{2oQh-Jmc=m%4v4|$RZ$-)Z&Z*!p2<%mRRYv^s>*q~=M=eL8R3IW-H5uY$-Wcp(r_IT^x zX8T9ZJ7)hxdg`0r-IWJ(oyrc1ruZHP9INA&hnqpgGg6|kVDMKu!wz5idpZFkFi?IK z?p+5l=^|giDLx!@`-=14c6H@loGaL+;s6|ogSrt;W68b#6qH%W7#ZSvNOf#1EWwb$ zqG5eOlZKeYfjci1MuIJLa!b(DWI@0M!gtA46cqt%kYI**$^khC#pfU39Z}jg{ozf> zC=`%C;NjsxaOH#^_&@Lw$p}cNgSc)7l6!TB0b&M7WDN;y+wOk>3P4cyQ$6~S%QS^J z$jRYyB1-|9u~ml;y&gi?u?<1XC0vjNeZypc=IrB<=e))>B)Yn_0FXQ#w7U*U35eGM za3L}pZd8U>J{ga#v~sTuePYXrOQSAt^sxS~>1Obd{-hLZb>HB^S#LpqdO;#5W->?0J{GOyDpGPe$VjBoG4&5)Ii z_qptB6<8-DV<+$2p!<`W;_@JRTXmOl4jUI&d4~|YI@N{9T8JbJC*t8Xsen7D%jwcR zWG7y@YwS0BzQdBmGJ=yyPQquGFa+4x{~UFZHcVcdwiZyz%P-rtpx%}Rgb?l0L) zMbUDPaQByMna@qQ5Ui}(%l^Id?Dc7GhHiooTX1nBzH6|jRj``GPitxylusp@sPDM^ z_wOSlwYBh%Zpgz4Du@5^BD7cL;}#p|wBgnN{=&p`^Y|>`wQ1ZXJMw=_+G5K@f24pa z6WvIcCZ6>8-^Hj@-Qf!CVc*DRS4GNC_{ydF|9?PFqg5*AG;sk|8*SQ~e}09HoaYMv zU7p26E%r=DqiCo4{FgFin*SX->+g)^Cfvu@q>eeNdeG9b>EHP=|L=pZSdZPiN(Ss& zH0E*V1t$6a_sNG*(=k+c(HjHtNrb)sJ6GI@Ctb?N?;UW(F~!mB-(LRrZYC$&gnG1w zXvdZm-t+%HTy}Ak{n#h4sD(o#6`naZ`z^otI}6|7z0oo{C|RirbzdE^){R zwoX4KR`{eXDfHiG#!u(Be+s;j8Lj`7cFy$L-VU%<4glfUz@&*!3YQF!z?T-IeDo&W zyJH`NhS&g$c<26j(eu+jvpOK1i2HK>clz-@ZY8PnOB;vYuZRW~BG|uZ85p!2&lOv? z26-(nzs@VW3@05&hV_)#j1QY0kM-^majgV*<15Vb8!2oaFgJLZxeX`@oKRpf6hY6 zZ^T4~7OZ?xo!GiU*0o*Gwk!Mxi!m!moA|}ub%KxM5l?L~+8+O)ktU3CTZnZx5p_~) zzD8iRom%-k7_~HwdT1w<`MbU~iV#MmX+1Wn6K|x(G?wt^SSI=@NWj z^ov!O-{fj87yT^RvV3RzCZ-y7-c{71k2d9TqR-g=lGt2wek(5R3d zNyLmQ*^34G4rI&L6*$U9+AVA73EHtmS<1(_5i89DY)k@O?17UUY-pm5 z?$;^p_0*kzn*RN$@2jXIKw9#QoJ==V8s)${90Rj9rO;eB$}M6~Hj?P9qc&7mNp1K$ zyGE)aBZfcm1Q~JiklnkRqv7rv$p=M(w#oP%4!W4lMq<^m&#sM;rQXq&MuJ=AeG|6~ zmYJriQinN)8e@&`64m$ZL0n1)MbX1|+u)29;4^ccvn zoe72Ooz;2DD=1$?O4EkWlyAun&|AM8mFrfc;`B;G(__hI#p@B5xRLzj?QZ)v$@0!# z#IoXJF1ks3 z`!==9TH0~fFBK(P)-3wlVnR61QO`Kn9i@H7?%a~h%Zq99$5OvNenW_+5pC5|sRZ9* z)UN7~YD0EdqdHiEXm~631~HSmyhBv5Cl@Olj?D}W30b2uv3veRPF4^f#~n1`a<)^W z&g>R@3XHA=>`}u*Z?4ocI#w2GXqg*2q&WHShOM0fg-$Gqw82PbZ?lU=_W;!t^QcOHTQFgM( zXd-!wk*S?EQRGF=aVVXNOY}BE_?3vvuiV&Fw3?O;%B}+}jJ=eD`C-;$wnu*yB|B(B z4d-Z3ip^ZAqNG20PBdfND=0+=GZSBTv&i~*y@if~Iw-fCE^_tB{P@<6_M1>)ru|wb zUQ)D9iwBt_?^OT9&CAq@Lqa5%-I1sm{EAgQ)ww>zl4pDjWOMvm9Vf;l$xG5NngMc> zx5yNEI~CKqv!b0MO-b}e#Z0Y9+<%)Zkr-%|du-mpzEjSodmhwQ%2N>ZhVE)KiSXpII=7$s~Fc%1C(-4onCg`6kj&M{lSXsz9O4NC+@%C=P$ zhY{SS$VkQ+!+~R$ei|1nzVnH!Atax%aY^{v^V@weuk zZ0XlT-~6E{l~nLgc1fLM8J(Z~SdzBs7Su5#qf{ZS$HKuwndcNJSfI6vm4Yivx=?Dm zO4>ndO*-tyNbqKB}az4-$iLKzEj z&ma;9GIEp;n!ZM_SJMB;#IUu7wOr3h;v(rIuo{Ta!bUB=9!Zgwh$BCC2ziYC{WKIT zM4(dFG^wq)fg3byyn|aTh;xg&Wr<|dP2q8H%(K!JHo+S4#Q_4_?=Rjgw&sN~SO2Jd z=OyPu9=moezDWbSh*4yE(KUXl!y{E$Wa%_$eym|7A))m>H_qkYZ6(!fd0z4_4=74w zhGP>sIFOA+_T5+h1(TQ2!QzxH8TlJ&rH=lT0ShwneY=G~R30_{>`UUHsn4gl7(#1e z1NZpyUg;mK>|Ir+e|i8xuO2QmDHC-Zxo}~n6VeFri8aNFbS=)Diu2M@&a*i< z_79<v`kEKDTIp+#{TMP;}Pz9pC&XdXy3lYA-^y3 zF|;8RBNJQP0?nercJ1Tjyc?DP!N#2)iLA6J*DR3hFY_{KOR*jYi`TdN^C+t;J*?lX zcSJ~F;&I$cKSo9+V{Jwj4W&UU z!MCT3LyW&D#2EW{yYESAq#E&K(8o-87&6yPTP(&>^QCULp@!cL*Q#|icm_Kl(>q2m z@{$wpt?iNDhij=CPQEEtru_=9y$!ed96 z#|w)=qi&gAy*Tf#-4*3y&FC)%gf?Fd38_eVsYbJpI3`mHEFPGb2hQ=?ukHlKMx&#~ zd9r=Sc_C#$82gT+;X8iXqS$X~Yav{|vksAyPfC_CTuyio^eJV!73e5@LMiA8aFzcB zKJ@IOQ75q`$u1a)*xt5!%+_6kKOmjD%DC=|+cTF--UUyxF#ShmR9hwM@Y7<7Fle5T zt<=n=;fG{mMMNMqsk>LFW2_;BP-HQ5DBtuLV0s`@8!nuBcf;h^w+VN0YTn;+w0bCWpiEs_M_P4|Wg#+8l}$s0zt4;Rk_+Y0*S7lE zP4o{+8EVer6I+YZL64usf4SFrDvx988>H2_Nx?%`h=)4BWmb$+?3wT$w|D~e25xLT z^XTbKUY=>bY}72gP@`mGy&9A!cG{U1?8n6)O^mV_uA9769l?^T zcv~sMLb#aq|Fi&mLyCV}6y;v{V9K0@K3x4qUj zi`9n}o<137A2WOXew~ zEChTU$EEZ;aF=Jn&I&1BzwrD3Na(N} ztz!z~W#`TC=We2z?};&)gmCeC?}Xh=aeUP`D*KC?J)HB!jW-ymu-XD2ZpFT13^Dz{ z&|A?a5Q*0GY*k!-V!EBQv>2_rBqxVWT7h%fi*c6Q{t}P*>GM?7vc-t4QL=JjS(-!QafikR5g`~EV&C5t(JlKsKm=W>p~5M~RG{iP~F zqTP~39&I-p_}=L|M`Yhhy__?Rhk+Vad_f!OVD`0aTAh!Ns+6S34~hR^hhXgJh0@JI zZA?Lc|IWK~HEEX^@7zbOe6U_t)?4#VkbXh~kD=+AxiszIE+zd5X(3wrt>lU>Ez=?1 zH}@E>JA$s&$HmXcAK%k*)t~p3lKdo9~7DDu^0*qe^S^tT%-zq zG%>(pC@)g1-w{+VM0^yePm(ppi^IpFt@J}finTMCfoDNYE4L+0lJxeqANLzDRi7u04XuU6{2`9AI7MKfk>`lwX4fa(rYO0N+Qh{Qokl}6S{-`nFmLlve{aRJeHYkJRT1W^Yl(q1O8zmr7DgtW$}ZGqZ-rvJ`K5$m!ZD0D%k1+1*G-{EHd&_VR`R@ z)Q3}t?Wr8?syw1N?U!+_w57lFGj;3hb~xw1xBQlU&B1Mn)zLFlv02Lx$ws9&-?S1IHZ?r@+oU1hA>3A z`1EJ1O}SR>=A_SqB-QgX?_GS&OJod1Po^^sP zpnZG5RecEfh&yfT7$UF!9mABDuE<1SNk z%W2W~wYL*B3Wkh8q_T_(OD5SdMreEb0Cxq7Ja1{*AWz!?8miKdhBf?|SUSGc3uFI< zdk#M$YTVO=YfGNpkIceTtkaU7#&0B*H7#MaaoRSd_i|~d5pI;9&WLRm&o}Aji-0Yn zvLghtQ9;*dM4I9Pp>1twO;`Rm@u~V**sXOcrvUo)l{DM>ls6ouzevKNQrmbvM2^~# zoQv^EDZZw2g6_oFSor(O0Tx#81U;h40yU-Hra(un@!yHpI1eQxJbnA zQoNhKO%_s7gmT^|*I&!d*!^oe>@9(lhIg7{Tlw{nF24rM2hUe-JuReq=J7>qd-sfd z_&IaY?7~a5W)j@j-V=tbMV-5pCnj@v&fCs<9`y9?W&+^n*1e`N;3ESLP5FPa(6) zW?`lMO}4GIjv2#jzfH>HKJ$_ML4#%r&cqv%oSTKX&nU;m=@tPmxJJj{Y*~CLz{>&e|c48JfpeJ*L4t<<+zO zpnKXh;^FJ4jmj=3BNj6^OI4L<$u+nteSI4UQF2B|jaD7sJ}#fkw{P}7tTy)9eg|yH zaAy0d_~9r%Skl0-#gRc9;hf9F`89^cu=IMcHmip~C+^0m#Wk|RL4Dp8n*KH{I$W6~ zBi46&SWGufmD=iwq`9JE%kGmZjsLX%CExEAZ#8&~C1+KmWuP<@;nX(L*p2OE#lb-) zd?pn&KmDGBKayWKMccFg!-kwv`YjESU#AU@M@bg@AM-32l(C$M^6+>bK7L5bDoo~n zvh2C=HYbZR8rP1Yv4(hof8ycCf?VT1Ql30=T)Zm`$~bjRYtHH+NQ+9&Qnb@AuA#X5 zpxwOLi2?y;(fMA!JxFjjEt+Armxi7mIluytkRp1+*{rl#sGV#wog|A-wmtC9y<1q~ ze{oPR$d3(1YRCEqsVi+_r7^k(Xo>XQBV>x--+#qUVMxqr+%&VQ9ScaD{J07=H*KIk$0ogGbL{Cr{gC3&6T<48|7HQBpFlfUl7_`}iO;h%79 z#L38n-GXaVa}N}F2J7_tX4a@OEldYm2R5kWb`E44bS-nDR&)5=@v}zl_I_)pTosWm zT-Tous=#1UPC59|#?~#C87DGhDfafByhw^Ve$wBQ&<=(O>96?z4)A%TS(&4kh-EU- z_b=LlrM!7x2LAbdRj#8ZIZe;O8(JpI|IJ2s?sd?7Fug$QkCe(qLBqoHXVG5rL;F2; zc_-I1`@&6->MKk}gw_hs)7JpqE-x#95i~Tvh`JTryF}7(x9EMgZV_N*{s6Z4I*(Dh zvaBrg2jk(W`W^4UK}jQy4C;v92ld~zcniZSYhK*h^^(W_;`yhx!gnLmk^66bMSStM z;BYO{kS%xo`WjQd?&22q?8gZ*xhwd1Lb^2?Z(lu6#j@$KXzXFkev*cE({zH8qO08I zw@(yme0=DWSzdt1nBNx4^%3sk2evakn1Amba%rNQX(O85h2Q+r^HWKCikd~dxlV_3 z0`8Xb%JP=mdqvW~n!8NRf8_bQ?7rYPCSH6D8e)ErDS@q?Y8m}s!6{_k7UFY@L!`x_ zd3$BXopRZ+!T0NHEwB+k_|O%-j)r_#SIw&UnG<_KA<0q?1f+J(v#6d<*5P>V7MTi#_>S|`d z8U6TnIG!t8S}wlXJk{E#k*FnBgOBoGCT5pNOSfb)5_L|DEy_sq_rKtvo%*rak6FPTwlgYS{C| z7>(QX7*MF&9{XS8?JVcGbA%A!fgrOZW`uiYIkTKgSZ5215g(a_egOckr8O%&^=K${zxHO^!gt$Yol_=Kh2y_ef8-c>aPcBLG1|>vel#MfoMmLYBN=w0 zN4XF~^7DcsYxb{}sg8noFN7Gx<&+)g!!4i3_6|#ky)IXd;k8xB$cL#s%Es_LBJ2v< zO_?F#g2^hkNUeHomaHHn5$^Drf`Kj*Nturd^4T#$Ngs1D9t_L3CMiUeR4e5k{7qev zBd}nw3eMf6CrsjUiuyZc#F*{k!mWfXhaMI$wy-E>XfeVG?|DK|hBSu`DNsF-fV_ zJM8yF)#VF045QuNoLrhR2}~8rjuk$Xo38ES;w-x7C%b=iR$1W0JCVv2KD87yeEYG~ z_$^sczM;`QHkRcm98!W`9ZVz?M{bR#6+;{UG8*p^SMo>t1Tlp@;b$%>teK(_;71clTU!_pb_5 zbAV-*|20#ISels*g10usoSgxl+@)su?s@5&w`(eM)Z}Wn=%s~Rq;wi*{*K+7_q4@! zH6FpTCU|l2XH?(c%Ws!26(;I&*BTi#*j%J1J0t{nZ`S2>R2-ygt`;+JFNv`6Jb!!C zSG)Uh!#1Jgq{`<%7 zVx4Ez%2Pcnu6#qy?O!*6|Kvu`^y)gv8H*!+V)Q}c!|N_-Nw*1{bQfElze;e(O;c!n zd3B=RE1@|cOMj7D!{sZ1Qe7r7VYP0q>lxqnSC&g=7MSMj2=4m-k6HOF#4v@u0D z4H+@g>9G%s9_Fp4TmLR*h#p;PYUXo}R-;J`{=M0R-iVQFW9m~Dm_RPlzc5{O8$VQ_ zP?^d~ukl{MxX{DB=_^~3e)OAoD)NV3whd@#9ON!01%|V2Z3;V7R1TBX8?6xze@8}r zNHUHp&KEY0qWPYB3I{ZP%LzcwaT_BHC^j7Z|`Zr zk)7(-4Rnf<=c=lvT`Nrnyk@Cn{z$a%JYmHVywL1=*TU1o?ybSf0 zl=g4QzgH;r@lI?p#v)5Sc{Dz4YnXJ*FetCk|NJ-`xPj3S89w`$F?zw%jPvqM(YMZ5 zx`Mr9qkr$a_zy&@U^TIX2ISa@qH}r=T-uo%U+n9|oR&uK`lEH!jPh)I(&^)4bp4Zi zWZ=tJ5+sTrb!BRKrEBJ+C6MQ!7?(@ltua;{ncZhh+OQPKu`6{qwB1BxntYFqeEat8 z_r+uHnNMa|W{6@Ih-u}g?705yn|vPsz~eb`%hTK|}+;^@h+FXNL-lSZLoim;VE|iA7ra>!F4XNSt73l{} z{v5A^y;q__aV307FUtIWfL7?gEkZ<}Tg%t{Ky~osGsjlz`wKWwt9!i^S2Hi=zHs<{4#Hv3vuKy|67m4jI%-=mbU%Tf2Ca=9{h-&eWOk6 zr+I8UYtT&7ukAd^{@W9?x4Wi;OwBd|h{tBp&4MKH=u`t7MT;u?GspO&2=r~Pv^AG^ zZ&*vteMp!cenQ{!#|k~l-v~B_KMZ32?+|^h?%uWPP1#KD_PFOTV_#nI z$vR1SFy*bUmeliLmwu%Q%S`{j^0{|>eF(Aq#>Q@aPV5S`y)Q?jUtu23oHSohuvsF0 zjVSEF_y^u;YE|XktfeV2oep?PL&i04b&KDtOL5V)L&JN7=OtyhG9T9zQ2VjGXVwke zPFyox{?M09nyAVB`ulQ&pC8==@?4t3BZt8?;)4zdh8K_astRfY*6$Dg@o7HhR#d!| zak;g2HM$*e`XL~6+*Him+PYinA}oUi2kpG4)Kj9gUe)blSamjc=|hoQbU<=lGarTE zRNd@`U8D5jZ)Sl{dgzniieBXTH`QWY(=)Fme4|^LVwg>^|17dPNUr_H zDy`9He&%b1ck(sl*x@;Ck_Sn`p;ps6Pb0Q%C~r-9VAqHD6u2z|Uf<=V{&u_QWycvL zM1I(U>6|$}5b-x4;4&QpD0Lp2375O;=s0@zatRjIti1*8k9jAX+$IOe^lrbYwgS_0Uow4n5kJ##^`EJpE-$Xrr{4*Lo?J-lKof)Q zkr;{}io?$LPQZWDrh7PlA@>maCFRSCV_{ZaU?Qv4Dv534n;3cDX@dq!emL${2IF|A z8G@UU()rp0Ou*%Z`_S&toSbiP( za02Ps9kU8l!T7Og(ln^xL*#TDVfAkbTtLC3-#kB&+oA~GiMw* zT!80VEBcrs%VH~pWt{%pGdHiG8#c}@a?UIIv+ie%EBuAfqV$0pl-PdNHJ1Jf0~_J_ z>81cIg10q2F2D0ARm==_4y#w9480SGFlrXkO!uB4t2aYUzrN9_EXiYoygZij+e?70r)vh54cI_T zyr(7w&H4K2VcwF4c(3oqP;UBFHf>mU5jPh{Kx66yN&kbd;&qdJ;=lD-?fDO-Xwzb)hYIhWBf3Er z1Tq$v6;Rdwnx&u{@!}t*Rd=_d7eXw>@XNt_0>%4!q(dfJYblbcb+$hA81rwv>~;0Z zouQ#$pSa+*pCb0~mLIP9ErrB+u~_EY^& zuC6nCU!ebO&VCEA_z@lKwzTqNJoaO0#nQV*yWdv9uFdNzdi|plLC*oO&lDC!UgY7rZKoZRxbH($2E)wn&b0?|n}_al!ThC8Na?$imqb~AVreL{9|Zn~OYW!rYZ9qFuR zqy=Z-L%N7Bhu%2{$B_x5b;hQ|B%yh)&l~CLcqGT&583|m^jb3F5#4iWlqpyleo(v? znaC2LK3?nY>TiTCDW(^`s!QI%p8VvCMjxe|QZdaeDXC?^m;In`mFCWC;@?R0W?ed{ zrTM=4W2JRZCg3)a^e8d+{n^9!l5qQ(V(nsC3O@IBq4u_15m*6i(tIC9_w8W`WB-}8 zR|4f$Q~2>Ps#MUh6%=uai9bt(WN6eAnMlH}kyl2iSv_QJb%%R>G(E71p^frB+Y?bF zrdizwS3FIh!2cJQR#!jKlZk%k_1n9InhR;kH70gj!k{I>cE;di#e_Wy%Iuo$y=zY)ovctDR7hKVS8F{($J()Ubp$EK59k1M=7<#fM0|8=djTzfr7J9JEw z7AOI8gJfNd11HqPILy18aBbfZxToUwILsvGP_T91mlzHl)JscsWSsw7ppoclPa8;& z(EM7A$TrZ-P3)vY;Iz2|SK=4bI*C)7%KB5{*70=KAz~A>*L8;e$&7=wRVgim{o!@Q zPOc}P-%j4(Op5nJAbxr>b%ZgkeoEfA#JiAzky+iMhTFTODz2~9SM0WOBEkrY4jl9A zXBdkM4iWV<%V}=ooYU0BFSTfUU)R~%>R@UglV58kXILe9b)d847mQn_4PDf`uj^^Q z5KSb}uIMYb81@9-2s3&rLy0JKU&Qik&rB2zD{B?Bo1dPRyrV5m{Gj=U`ImLFtyR_w z5mD}69*v*Eu?wryek%^u>|4|7!Hd9|OaDUw2V+@%O=UDrQQX_A09u4dhGYAuVKquz ztyph?s}JL^iz-#oRs=rqbEG`3FjFWWiZ(7KD1B~66LpTKWH^*gM~+{fH8}l~d@N~w z{b=r}dANNdijd~oZW244kZDvb(q@jTDPu$vII@$&90@O;WqT20Kn*Jz`qB3e}zt5Z7*s)gO|#`9nHQ;8YfeJBRci7m3+Omf((Y*gW5rUxtM? zlf8R5gXyK>Ve4}GhtLSweIu+UjTrJcuWM8aVV9qlLM&FKqG! z6$)rt$v7yblu}-KduKa9h3H$3q377kQ3zMHd=I`e&JUe6cJ}anpa@X~KFx%v>sxWF7t?}~rH2Py3yGFmW&_oX_a%5Dg=EpvKmh2N z2upRROwoyso)buMym(K!?1T?dy)mtY80>i6coJ)CMW|KVUHX>wuW) zT>R75XlM$B>a?2_jO(&u@-iRU^Epy^ldzF+c~&%7Wq$7v(hZT}>`)naZ!sK~)K0^; zCwE1FsB;x`X05Zsz|e!cVpbpe=Lxf1h8{O;B2pK4VFr&EM4a_H#yo^kAg}`I?_9;s z@wLkvcgqXip2+(5>4S~qNdw%1lPM)8$4+MJ?#-R|g(m{;bYHI@dP@sg?$4RbZ?=cd zDeKcae^L#BMDMAf&O+7TY}@|mr z$AuK=&A435;z!!%@d^vC8fjME<64)f+Ecu-4=Js6bqS!wrvoo#P|6Ch=FN;) z*$`HKW#z~&jhrE#AG`qEukkDY*4{7myFn@8mi=MJ<#SHW zELL)$#YU-Xhd*;$v9I*AtE3arQ|^1A(q&hElEhqUG4tG$U=b7~{#@INpKMfZgBG!W8Fd*g%3l__Fxc_YP`(=Hqg@y)>Ujy%Wf<1Bf!`OCqq|MTmat=5 zk#jGb2Rs}qw@;wE5Y(pW-eyyFr1(+TPNGBU_$FNo?}uDa?oOF?M}*uWgJ}k-%_y*y zS(f@?Syt}c7=gnoq9EFDEjNsf-`usA!P`+^I%$6Bi&^;Xn#8hjTrvFGGXbcn^5;NeDF z?DVbKt@v=-)yM&^IT8oTM_}c&kAkpHIoySVc0mpR(b0NB?3Yjjw>9QDbm6ze$jPfE+`zR|V$*&LR!lv-8EFS6n4bS(6F~X z_Yc5j#kSvBmpd1}7*^jBG&ir(_=c&`+>>5&4KS3zc2VK86LAr0iw&c_RnOA{S)=qi zm8tJTGyIc;G{+P6cRu=Vx)Il1`IZG|qLKq|@P|@*ux!zB&yu>e-x7e9oONJ8R_BKZ z^Pw71D-lK_t*8g-3{6{5fj1XR6vP{**?)yN#)uVCmikfi5)J#)NfGss+*pZoBZVIg zqe>y&@`Dzt0;%84WX^_ft(k)sV>gb&Fcs9-wZrA;2{>I2ew|)i(CMBJcuNel&xNuq zo3;+=nixwdTrf}Nv4&=&xkDan?Ce4~FFNwkqFhpNAB_cjAqmj*e>l+atY3E`g?^)y?C7AFIfU{pncN zQfg}4TLPSb!ASe0i^P_b0@P7Q#jsC_AH)4*n;O70ohb>086j=r00uYo)G5FUb&W$} zR_LXT!5ko){6<pYEAaAVkr;_-HU zS(>*?uP&6)KMA>_KmXDW9fEF~qxxtl$Zp(ER8Wy~CtvGRY*rIAs??VAQwrIg75zP(H;XS}#O_y?16F)F>D#A`NwmLFct^e#|u zb^BwS=wq3Frna9F)&n|&L^rd)nC|?m5^^8CTTX3O-=jM5ks*o`+1py`W#3Xp&9pu0 zM(pXcGNoJ3S^shTd{R%$J|lze{)gUmKB~ar6>zJOh`8AIu`%}(vlaNq;Qi|>@YXHh z%6r-8ocib@b}p@EAO0Q{tC?g2`&+wkA-qS-vNy#q@QDvBjkD|U)*VqD+UrnQ3Q}$O z1-UjJWoou z@J-Rxj&@4Pp98vK<8ni*!BCcs25Dw8Ar+$KATOx3`z~-6z*<89Y;D@%htejx9?M3J z;Mqv#Oh~M4c37X2nk^kRwDKlDRk*JpLdPJi;Ok+-6}S!h$N(os6u}!o*5ILt;h|LH z4=O7>VTlO3ys*QV#WxhFEli_Hr5d3xn%dByzUqYS0bKuoA;w2+9ao~?SFraJpq&sW zkwE*)&2Mo*N&t_ve-=!iCs3P)HDLeO>!_H4_3ajs|2@3K?;gZAwiV{x=191QWWVCZ zS{TRk!3xMbJpU^wYsAILLg;%wH2l_1U`^O)*BeBWU-6=glt$wo0t5NVy4r=nU2`uz zEFCzzWX59(PvkQ!qvxfxn1%n7I&#D+=^0VQ7d1c2$=!9fh|LOB?t6|Vs*Hhg3eR1z zNZJje{!1tn;>DNGLBX!9V1vG9ij~a4uLhBCee?Gn-V%uUe0~Bc2ioVJ;7gGpJq@JG zU<=(~3XMiFUtV8*0+*jd4W3zIO#)0Ac6yv*Na>xc-3^Pa7VdJgHf~iCS2?W-QZo*f zoRTwow!+|lhJRIGu7E|y6Z4vG|>NBGzBn_b%>Igo=bDxhfPPW0j9Qd1Ok zqdt#UdGd|$OH+7!x|JKXdKw`ATUantmVvMdLX_~y(;{!x(hl`@zV>sW9kw7(Des@S+-dXiw$sX__{eks15uE2=&2r%@)eMeEF ziVt(NfJvo*-}^!IOBkZ3EF`}YdI(YAc~2KKA!-1ptNkfD>fdi43nq? zdRvh?hW2ex1r)SA!cvMn;rz`+I#-=r$1J(!cT{?R<_HL0z^1DmmI*f-hwl;!d+_;xDpoFyhI$L&3 z!M8hLA2f5W?BRvHwb1RgM7aX9dH8h)Y`|@~D^fdoh?;g~A!<%g1IlJ*u($7P6)SJ; z4Djhh<8Ztgrgne>VK0qa74`$^+=hn>0E|8^b14Y{Rp*Co>s3$bVXkgcHcK@*t| z|MW!Masy{V*#*TzeWyf{Ohq}Yu>Sc!1w62Jm&M9nt~cCv`1zeB^&dRGFOVGh$WEeC zvycee5FfJ5qdJ_v{ZEaPo`+g~I1jD+(rMpN{#p;Ln9yy|6*x`SoV6(|-ns!MoStuc zBR?*2kap_;-ZJ<4XCXSi`5xFPISj_}Lk+9u!T3N*TkojvekcDy+!48NLaKq7_J+|H zli@fdzR-?pFuo&8s3BPq*UBdGG2a0faK}`K3`=Depcd`}t^2s+i_%SyHNDdo(n+l{ z)N7%7^ttHc=&oLE=c9l}$uhcw3QEG+t{r+|0JRn7`VmbuWuXa3xt}W^Tjl*R|5UE7FI|i_mpxaQ!{2JocD+cPi1spNv~_eWUVc*x`h9*V ze%>4G-5$`hXEOS~4}VgBJZ^t%zie&;aUT=!TH@BgspBsyNC`z9`jewheXeq)4+PR_ z^l}f$Z!he)Pe47yRs@39ed^dkwy-`fT(0*N2MxGk?}$iFDo{SBIJgahF2T&xVSb;- zxDrvbc{-s`<9A{73Hdn&t{W@vpGPkzjTMnDmd;m^Dk*tSo_INMJ!A?!>h+^k#gw^)KDe}`XzZV9T z3=`isDdEdwW#3bpw4moptHz1cjI#-_s!|!q4&cN)X^ev+W+zHKMFeNVIK5` z+^O7$0atElk!|A`Bs{Qy>4>ZWklL6wxjT`;zv8eNa)b08#`IO7c97({Ll$P2RQ*4L&5E#Vdr_YUT2qd)DCHbEir9{ zq?i>x++x%W(HnrfL;?TF)YLrUd4fYUWy;bh6fRNoXLJ{e6Y#~gc%=6AdWumY!iAX8 znKRZ1vl-^G)X_*{E!et@rq^2c_;E7&?M5_CUGxNPX{`fC=#|B`W@OHt_m`CaLdYSV z2CS{o4b1;VeVRYsF(LH{i}O3jf0Lk)eruKEj=;uF&|r9lt*26aVD| zwyuEpwQe?W-H&@dq%a{iUp1(-q7Up4-&Ufc9b`6b!{&ZhIo< z`09$b$$~LIv3m}22wWU#V~nVi(-6+2^~LFhrpDgucRm@~iM?eo7X8S*PhO=_L0)Nh zN31~R#_wOnZgYZoLUFU#2ir?1cw$Jk(K9*&X(#`@%;%n4AM>yQwW2_w4#d5P9))Fx ziY@;~a(UcH6=r=Fs7~-f3T|$VZ6Bgh!jC@=a>_#TJCe9m-`;m);m&Qr&J+@3+`kXk zm_v76(Iyx;pghIyo-IEJdU!_`(F^i7!5k4rHiYLxc{PLCPrG*Y1}Go~I~W9$_JB?6 zhiCh>t*bh`6{C@KSEOGBr#}Ylg`GYbfpdlPULs=SW&ZT9g@$EYT)QFx`TOK|UJ!So zVPjR4_IXhVqlx`qQpJ3@;BQ3kPA0Cd&t3SH?EbwY zLyxi%f!kekazJ-e;5cLVhg7agNj_NYu=lhbY6;3YFH6=~vIA?7w;p2di*B&#uTXL4 z!ulFTG)sf>n;}y9MY({wTHEmLkQedk>kKOMpKN$A$D6p9glimpI|Od^Pe zjQ1<*AO_>*)9XdP>k%zWzB*x1zQAm;HfIP6;C}gZWNEjzaZY<~m#-5W?tPd?ZWF>mv8V=JO z*mu{!`MbcY*&O5h{7HE^G|T(LYRm0pjC>CSG-Z-hYKvwf`xVG&c9X@PNi!W)f)U@Y z2ROTYArxCVlxwcVCuFd1HEAE5bcTg$`(&lp?C<`tjQ zYh(6FWqV|LWgJZDJvrx~Q=*5yD(_$BDUv?kRw6HPy+=YsQfblIO&9v6t;gAr+OqSS zaF_~!^jvO1vX({VtuJ`Ahsvy%>{!zMNpu$g@c`Y$jeXPNQrzNzwBtv-cO@7sf;$zr z8y+KZQI|imJbCa~ zCL@tpTSA?$zzq@M4&}UVVge|E(n!;B3s)r6);^sH11u`Jydb8j7rC0m*9Iz>Aeum_ zL#37a3z1tsCqRveQd<}+=i_Rb!_%DPOu$B!b{D7usjpqG@?2c$+I1v~T6`x4a051L z3fv~kLz+7-%mGHRv$uJEtSH0h9}Kj|>OL;Th;7CAXv2Xa!Q6t6xFqutogrEMS7?Ea zcoC)~8pf#MI7M4t7)UO<-p7YO;nY@o*m?1(b2Q9|{7v}9wgo$eG~M;hI-jOhoa1;R zyu38Y*8~NS5xFJ@OMt@vcYr1IP3RukoC1~B_%jWG+|aAX3_p)aQv1fPs6w0i*qA{Dlx z^kE+M1j}6*`j>K9Trvx5pLDvNM}kGMmIx-HlVX+8*vO`?pqNG zlzRK0MZk_B?AqD&Kn(x!cC^fQYETy@xW~ zfoaDSNk{LYKD7*a%83$=XqvKvk6)vxr?v2G-)rVdU<)%PH&mpl%5^!Dnfsc!}|i!J|@A5OlN9njHiidM4vZBA3lRI{6C>`vZJ!( z40Q(rNUV`A4)Dx#N=8B>RL(vRz0ANLG1>$)mI~BoZ67JaEgV2B8JtgA-$l;cCH_nm zy^C`7NMEVCGT+8T8ZZlqOh)`Nc2tRhst#{`IwLeR<{!2u&rMEMTS0?q$eHA`Y0H2i z7jt2qQoh_N6#uJ7JM;qEs}Vp>`#YRX(;)&WoVKe85-?4t9dLnGlM_U*Q(?fR@;oG6 zbnad8go;T!W%Dh|O2#dqlG$iGhQiYpJatNrrd237eCdGr^H$)cgY>@Wopf^79bC%f zVbx)Y-J!b~9JYOyunl97IBsW_P5X~)CIL?4$im*0*G#TWK3m3#TM)BF59Pcu<#llq z^X9z!Kvf_M4v0hjue&z6>x$bI`K7W#<%~O0d#_`pqZ2|^?0Z~x&}7ZaXBLwFsXDR) z&$`Cd6W?^7tk~bCQk}@i>1vyg7xH;yGG~c*YPZFD(#mlge~uqc2p(4>8|IDRe17m7 zOdNUmeQ}G1%E5~&%AS_yr4D@83n@Pz!nxG1z9U{djWT6b3tNB}ik9Ewy6?OOC0*9) z1-t95_qLK32+oI%+o5~6BE(4l-m0PtPB5Qg`BXeo8Fg?F>4u4c;eSU{(;|bRS;Jfl*nyYwjK&evq;4QrT+h=TDXLs$5|&T0Nh8^5+_<3j zFEW{2vou_co^8TFbB4-%F>G)G`Ib$(GzS6)(V;eNc!#$R9k4I86*#4-73|G`@qoIU zOv{fxa6^PLRM%VRbrOR@gu3HsfVl!AmiBbe(~2jDICE?{rl;9gRQlLNxXf<;7>6hg z#uaE|K4G^J&zTQrxU$@i-L4H+*f9|>B%n79Va8vgI_W$dM7t)KoxOIW{l6m~ob{b{ zx9uC2`Cl!xN|dV*a@W97rG(NwS%oX;_(`&F`qu}Lmp1-TMws7Y^S0)GuDTo*(*;-} zOCqkoaRmG2=qZHgl=qv1>R1Scbu%`1>pkQN{ve$+w18|mb98`eb3D>C zQKzqG1p{#ItYUKeXdwRT7R=gS5br!hh=vJ-P418>pC*gU3&?ZHMiRf4DA9jtSolF* zW5b%Y7r8Pn>K$t8>@8GgV+5A6@-IvJgr`1xdta*sihcYwc+Xwk;2(TAtG%)5eGIs+ z{aF0G(=)gm?|rBDJr)ay;*Nc>_&jfYJ}~&uDJtif_go)D#!?S=i#U;04M!ogW~}5` zyi^yjTT3aMd{ToON3i2lTtqd9;`lcC#LiD8R(@_Nx8JMXN28*%DvOc`_sG_!qAcX| zbqQ|SNFDgpI^@WszOHKlWuMRPYk{De_~;>9W>NP`HmOxR(Q7i13Ry5}w>bf(kH5Yg zOHi7q)$IM!BE6)un5oMd0=H9T0u{HSbvR%D=!NxR^+Kf3xCXIqcF~xIdAF#xN^QKY=Z~z3jk#@+t~?B~1HWlHL&Z-h2g>l|1#>_$Uo{ zt-ZMu`ZV%CE%pEL)P41B>+^$jmxA=A^HJ3#Um8p5_dz(Cc%>%{W{udNsPbFy@-TVkm0HeESfBZj4&U`Bs27#)gj zTV2keiTY%4oNSr~2Wd|$`JXo_rPx&FiKpF_x1b8|m`^%1-NIWhgN&~qIc zFoDSIo9Of84gxOxV{+fxQCAdWpq1#n4dk)Rs8oyR_cZqu#vxE(SUBw+yCcX~*C8eL zZ1ms{=5%Lo_(Ow3RnEA=$UhFa*zfWQ#~l!JDMJh{Ndye{tO2%-!!1c>m!?Slm zXPE_}`SuibFG%wG6P4<<8+z2cclVDuGkE`dbhRA-s@Bf!aS73^ojv;dack{ZK;Y|6 zgOc8}=T#7IS1sJO2qHK&?(QpgTQlT7`I7)(>{egQ&ef09o#=wQ%(B03DQr{s;ZduC zvvpnS`kJaOz2N=R8L-|VxFK8QOuD?|Twd;YN4M}=w+$^6oJmskPw8t+KYc3vqN|1b zEiN9`UwOolTA}EAIA~POls_|S!^z0As-Z6rl{!2u?}&~q8ny!lwpitS9zV+y1DwjQ z&D{HNd}HBqQZRLSWeIe~F|_xhhZv0<34Q9kFFE)HIRw1%CHhorSg;dp^$HQ8fR!yX zw(F!F6Mx^FzYQg+bT?ds44|!Y2^1c?ZAhA7!nav3gKtduU>>rrL=>A0jiV?o7op+` zDuwedruArJ`H}s_wZePrVey9sTdZ5MI8Q>l*#BVx%(?0*RC1ReaEs8G^45PWQqZik zc$hi6K(@`R{=VpRv_!JwSHusIAs5{~ujuvT4~kK5bCFYa=S*o3;6Th^P0Vu;gtagl z!%wr5W5kL?!hwE*j?_2hMb{NO{qMHbn~?bE=fB6*^V^agB*pn0cG`2LM_aLHJY8{R zGJ*}6fQVq!zMCNV?v)c!J6$8a-y|6wceYTqQW2zZ2Vz}8@40nCELh%6K_8cJH zQg1i@w$Mz&=7tQFVC9r)gPv3>nRMLq(UIc*yAtHx|7gvQh&l4|{kZFuz?1a74i3m> z81Ff20TuR!0F_W&0KS0=0)D^TXg4c#k6JEdLHEkZZgJwtf#cOlt4*iB*=)eJbyH}q zMp>wEcFHubTUA~-_fm<;wW4A5AhP?MflfI0LOty{x1vYcS9h#1SF?fllCp3^ zU#>*6Ab>oxsQam2sVKI%eI`5zYQoj7ZieWK`ikk@U z#TH2$#VVs_H9z_AGnW`)f2B*?e#3E^gsISaLN#O$>xO)3n8uXo(E{%&lzw05>*(d1 zF>T*S#@w6y%-7!2sCbZ?=-=2^0k9eyh(zav|`PM5&$pq+p$d)nRhihry7xnmAD zSI%{D50vL9NVGyLTzo9&m`O9PJulw+_1tY{rq26f?`tF2M#p03=X34**zohYh0r8u zg46tW=%C5>N~M^+KRN_N&;v+z=?z{KL8}%7D=WVI2X0%?Ok-Dq@*>A@_O*03=j?m7uzN#AG?8q5wY2M(AL7y~b!`b-0MP(< z2wn9uGqSUe+13SNfs4`D9;9-_p@wUqGcsfDR%1ydHT~A^y?o{E$T|D|*pAvrS5-2@ z6S(JQiSh+8Uq@JFQLv*Nhe1lP36tAk*2n8>mfP1Z7coyo=n}z+AX#{wFP>yZ-ulx0$$;y zK^t{V5JUu%O>mD9aYj^V8t^~`g2roT*q{H14P!~o*!OP=lnr^?vIlKewBSeQZHJF) z(tfN8_dI#!-zh5a3XZzkL6}HomX_@G4GjE^8TcOBSof@7Uj>`m9%AfYRpwEzWG(o# zKq#0^qtGp#zo=f}&61*ElQFQXiMfmbI{-GtQ@0poqzGwR;Z_Dab3C9-x>aA!`@V%V zhw+_tg>Y~tp??y9YDg3{;Xze*-GugBNb5=aK8%%<+fUDi*ICB~i2Qtox@0uO7%_kR z?4zexd?G%@P}$N*)n<@AoK+3=y$P+| zrylFxNU!|4%-pzOAkemr@wzwMbo#SN@=z zh`HJ3iqnZ8ST7n;pT7|2L=~6Bua54}+Q<^~cStR#Gm-h`YYQ;x`n_90pU(vEsb@3M z{meUCNZJYu0FuFhP+)zmMM{6A0C&cz-(jd^TaKQ3WVcRo+2xSJ;tj~twTw&>z>N*B zTMCS2-ZgBj)_*pw;cO#Y7@(4wbmgNIolpqLnKBYorO!T#Sh!PLjUfk>nQN%4^Bk3awUE;WINjcWT^94GgK2?hI-44+Shx*;7zAAfmek8-bMH~m{6d=PmS6foOT zqxR!U*FHbcZh3WARLb)hn#nx-iQgjv6n~CY7`P1Z(`hdkw2FS9UZ{VK=92nK+ktN zI{85l{zw+r+k|Hoyvr34gAJ|(ef$h)T-Ee>P%?LyouP7;s4MVHEr9;w4XN*`ezhF2 zOQudq3R}0TQbHcvjAvOZTv+Zb(O8UL-zw~!VRFZnQr1ey@uR2K)C?FTmVk(}(egEVw< zZheL$=iXcrp2$Oi6v%B_R|CzN6}d;?8(our#UESJh;biSC4Q6j%n7aUrf`KOX|YCv z2_#0GGY?uRv?&LO!(L?Ctw7L_12k5exMR$sNi#Z?ik#nK{Ei*ymUA}i0IO}>ytZ0<|i$l682fV`znbEqefl2lExG1OI}@Guvr;^|9hjqdxV85ONeH~lM1DQ1``hTRYj>vVa1J+$W zlu>M9K2!b)@ViTM8xP*ul4dQwg3+fil-@;Ij~;R|hDV^J`ap9-rFX}btWmFaXbjs9 zego0lb@lSjFbSjsxDg6{3FDPY-UR_l5q`9eQl>~JxE5qj53Hg)$H!vjX>$#%kSk0U z+z|b#0<%pZEc_1p7pMHcgD)AN8X-8cqaLU60Lt?-z&~E}*<5egm_2+|+$=H=e6ixD z2sJrjB$`%9Q=N0K{}?p#|3bnL!>U-9?HF;P^*{L$@^PY8fE#I>o)tE*JwKUP#J9C2 z*J|dj@8mx_zlhhUoUj4x94sJz%C?Uj6c@NGhdDROiy5SD~K#t9b?2gDeZRl1c$MRCmc|7sfA6c+>a2>HZ(|RlVeY^6i2iEx$p06(G z9*d^flC&YQtcWNC43OGj6BwFSH1jiM^v5W^jJDQ8)|}CKv%hv~RF>`VqMm6b9=Ue_ z=N1$Hd&N_i&dQmn>Z(tW6fj~ijLAxrt#fMh8CSAFg43q*s;Khxx?farxd0*lK6&ZK z<B?=5Q;?L=bo6;d$X~K;mPDK$8jRYGxW9xz$gH0izGC;3XzzduVpuSu4 zv`k*;U`pkQ(;iqBgoX(tzic19_>vVZ^yjW7h0VB1LEmDA%G*UG4pWrcup_G)VXlS0 zdj+<{=_Jf^&l|vd^3Z%0=S55HCYj;xV0G@pS`$V`Mb<>|gQJ!^W!Ik*=GXO8!Qp6z9vP zg8z3b0uH!T!-^dXnHYN5NlKb=wsW%PmBV1`cc-d`r)yAYzlKW|W4x;bFb9hhrXX=`wk-f+d-sOqJY5A9Y23z{9 z|Bxvt+fOgBpF55GPa7PMk0tN@q`ry0_o*MLR~E+wh=%umArs$cTRv(T~RyB&xeQnY8WrTX^|k*0Mf z^Kw`Du3pMCuQ2F;%Sx>5Tog||nZs=;jyV>tazY~)GR3;79`0?fhari_>t=ciu`VfP zn{(k0aD`aMo>zi>#1ihc6_kO7RaIizBjq&=`9e7Ds8p=8S}6>#0(wHjpiPn;J%2i^ zM8eTn+>=Lx(E((002RspAF`9*>hsEW z4kN;0dKQ;{cFPxV3{QCGav1uQ+=KiOsND7pvR1VxsdgM_DYnJAN@1uCz8Np^nkL9_ zF?4?oX)%|LQ>#0@a>Tt zcL^w{kXi=#Z}eKcC->TVZ=3!B{H>MqtJ0#>o7w(bwaW^iBHW7tZkOeEJV!j!UL?5I zI9Ovd=8-QOQ6<+kv~LT@WO^YVRNM_zXJpKk%%y z{eDi-*izrh3~Df;T3bUt*7H|K`f6NA*BtwZrFKjpzhc(mA87@h3P&MDR!>yNkxGQ zLw|JRz+)*=VrBxZwP2vWLqtL*+!=9bAO4){K2`j#?0VSK`rG(h3PXFU&z0WTIP;bE zMy{tZe!v|U?#yO)1T^B-L~D;5pAlYz>8Oy+xN=!->oR1mxYtIa%(qc$q8kFP0h&Y| zU)M)1_di&LeU5ELq~6|9o?(6Cqy%zgNqnhxf|+LNy;$ge)fL z8qGjY_T81fwJ(}#6$gO0zmfLU<)`(&%M4m^gd4?t03Ei(HJfBAd*q*48E-tZ{CTa; zg0i;W!_*c#|Lfp4e3GfVmuZ8R^}7}W1A|6$Y(2e3&9-Y}Y!5Rv&v%-MN~74Jh$s5^ zu`ym}%W(~63TjC3;S{%HvLr^Mh%~6ia&hygNcycK2?L1L-_;}FlVMrRjZdyf1b)YE zMq!W_Dcrx+3+h@+pn<~S)WJb&WF*(I%q90~Dg)hz^W=TWMH2<{62~%w83+@Nd8g9% zy!0XYU~9%RdSOdNa4rE_J*{@pk3F_2~w?mfcneTMj0L38msn)D6H%EbcKrT zHC1*#HC3eHR7m$y$_G`mPcO<7FZ%Sizgoqg=i%Y4Y{HZsaWP@SDFuD#XyMJAWzg?~ zoSVYz#^r^ig_)A{YxhO-As`{+WuyL(C&aJ5wAeL3&j|NaT8XqHg^pktN`AW(GZ@p2 z^2L(;bremxSI>aYo!96tq3jfZEI+>=%XeAO?FWNv@D4~Tks(j$bJO$PF0Hh|iNf&< zJFDqLn7;G8@?ri9>Umn+>Ott>yB7f>y8$=AtL>6%Nqo7TxT*b%qMu2iF7J06G`*c) zjrD0IM$P24Y-+G1g@#v{cH|Nn8;vNU$$Qn!dc8VCveuR!Bd&*yS>I~03Zq@}ZAB`f z4LKxr&ROfMmBi<3`U7EYe2-~H$tca`ww^T;@8Q;8i;>*5YgSb;>Pg^>`INAS`&HB- zp150}w#vO4{!BLYGfI*Zr#`q)XIv~7q}>?NKoX9}KDO}vB_{P={~DvL3`hblb)MXt z@$q(eZoh3!uWvoNFV&J7j;FBdv?OUY9XEnaq|Nr5rRoLQji((gs7~+fh6jvSs0)Dl zb7G`00R>yPUK#B;#!xVyX=o#jWN$Gc&}rDH3M86<4B|i9(!P03x%SD7Zr{Mj_+^X< z+tBaNVazh(f6cR+8td?L(Oo*-o3-FhNcCodZe*MJD-G3NJ;(~@=qFudrf~t=(9-jj z`RwzB6$$H)6|=dRi}u7v_`yyazVgaSG-V6^3(6=Sdud)Xx-&kl6y4hCa{Q05s-He| zgp1+0epe&AGtn}7e^d(F*H@W{&qoYtiDdFCA^z>A0mz`XiPB81KFjN%_9x~OXStv6 zW4TrI`q6f(b4)d|MS;PH|QaS^+S{~myMVrCev(m=KvA_L! z3))wW`Sx zWi?b@1!9tD3F)j=l#}{xz1lL@FP}W;bI|LhqZp!TDn2L(zq*sKgnPmZyF8HvOQaUZ z?6cb}F6<1MJOG-HsnL@eVPbE2UqD15Ga0l5>Bpeh8WiT5B;p6rt|j*hbT~a{*PQVP z6(TWUj2mik@-@QxMZTGFndS#b%U`MkeszLI^o8^o7xU8)Yr0!4q5G;HFKKKhBjLjB z3wc*e9~GgaS;=-f&P|-ucZZg*Z*yGtBfs`qkv?dk9}G%S7|Z z&Ojz67VH6kD-OI*()-uzz!1v(FIeEH!55G9nd=fSQS$dgKUK4*wy-7s=qXc0CfMmv zVeq<{R&I<)UXaB!8N#R@DSYHEm(jMv~dsa4Ky;F@67q< z%&h-nF6M4}EogYVU)fgm)Kj&qc&smy)N%1#B~zsxqVKDWjf}_~o}r1qXrw^>xaXNy z^Jko(rj}g7>Q~N)mICq*o)_Kn@osrD%%M^!roX?A|G|8U#mS)QsU#4m(O>_K;Z)3y zY0$@|2&o2prQgs6V*zmlBZm%U?x&98GbtBQDpsq%kW)^ID&yFvoq@smUgR^0)gKX1 zWL3dlqx4IcFumOG-Y$ZtGNF!3Gam(s&wEuSI{Ra`cWW+{b2k~nbJxo0kqL}T1(vEQ zv#={%=wx?{G;@vH$tBGq1XmQ~x`&KpXdB04Dr?G_6rE29SHk#t=ds;?Ju{&d{)6%| zcwabY1uN?Kl-wV4?9Pm?G1vdTP$q6oOEG37&Q zjh|GIXxoT>reP;mq$REJD!j*UCN?n3X<*zt3+TaC?NEsoyfpK2*4D4W?OWyRt7%t> z+3}%3$dw2{Cw}Mh;`_j(-e4LM?Q^E#)MciPT_Tk-YlAnUjh0$kv8Bk>Eq(WXPtZY( z1VxI3>(zegK`6ggxtFc@v-YPSDfYx!D7%;R0}2tjo7YI8r)qA-Xn(9v66~VGK7V^g zqk5V({+tq1fu8P_s?Mn2FM30{Q9mT|j2{VaJ_dbzF)uzBA~N8Qr0&??q4A}jc_gZ* zk|A|??uJLH`%tUU?Eq$Zj1l(g$g?_&+%BfFHiz#M z3LMWEQ+RkwE@`BUC$5QUFXgYOu+En9(54p!R(0ZGZwaCU+jJ7|jzsAd9k?JAMDC+} z@5~MAYG%&FKCSJlF9&u1F9c}pDn zky0;Ewx%~;X~WRIFx0`9NK4_n1cRZsYVLl47ZF8@AuhslaMq9BG9`gZQ^EyGdJpyn zo}mu}V*N=_+?ZxrjcVj&G?TLBsP3`XQGe7835`(fwM;UX(Q-{etno3fw9s!cjPRSRmQ2KI~ zQ2PNZqr-X@dRS!{stg)7WYadAApXLQ>&qx4*LzcVq;%+aXBswozX@~4ufP;0*kTFC zYyz&FXfsVf3_DBBl^)LY#qed_H0pT})y4_Ey}}oC{!cy|6GviGU&=H1k2I6eHM%K= zgvh12+tgv-=&-ayXv*77#a?5MVpL}fCr2Pjo_yD9KohaDa4n!`JPdd2^cAY-|LIPa zPfWw9L-;C8=AEKN5(BJ5T;h0$$HMMQKkkTif&^0($|&WA{9v;gtQylMxUv|P>vxR9 z%1)rQm6h09AIbahnR-eYh*r(Fo}`6+reV$=#dISf4LxiQjF;d0avC%_J2eZ@KMfP5k@)`-ARF#_-d$@0xn=FC*AbZ+Uxr58bi9 z(JyC|d!{KLxX8WAPGTgf8Db|d4U{6ane%Xr z#!>K7O!ftO6houVSeCe59`!`05LJ6zcIB8IR}`>M2p1=gM0j0XpNl_-CY*@SUsHzg znV}2nji3Nu@D-Ld=QoZt2Pna-IKBG!_4lTv-)OZ@9eD&gjCvxKlL$+tXNQwU4q>p3 zD&t|#wWKSmeC$}9TuQ|t9({ipVNz)iQ+O_ytE2!0;g$sjJ{>D$npvBwF(?NyVURZG zLwqNjjMQ|}I%Y!dfv$EqXOF_Xa$vvnAQ;Hnr0rfz6*>&u_}UTPl$NmZveTXYGB z-m=AAuyB7H_!5P5QffCMKFL1vDKJS`dV0etqCDkx7&C#y;5&S<5Ifb_=bpIJlq6;> z^dw3Wf(SqI%_ybd@olkw9JU)3dSL_?+#kHFKh@g0!V`F26!EvGa|bX`Jpn<)9jmuQ==fE&&hs)Xb*r@x$k^?5q^Yeh&;Kh!m#V~}bYE{#sSnk8A@C+iF2j{&w@o=haryA4` zCdu{QLi!{B^Mjr zpd6l+sWh)b%eUF^6-m86^iWMP+E)f0gKWZideCSjY;zdNE*##V8t=(f6TKmmY*NL2 zRJNG;mC+-#Lh93XR@z4aM4Ne|>6u&#%G9 zO4Y)OE%yapTmBl!=&(T?g=f#r(V7T1L;w=vhxDDcH|+2gvNe_weCvA#Yv#|=Bki@$gh|PN#{#5wG&5gsO-^*?^P%SB5RSJg?Diux z16wgf$IZ0l&sd?q20C%vxPHu>=SZxFY;UgavnaQby)4&%jY9Z~O-8yQJ^Xj>1B(_f zm&znjbgeBu#qk_<(Z7#09oiSL943R_Q$>1x&|3PX%zSDAl?-Gmw!pXPs;A*5Rt~F= z?$bH3k5)IOU9yVoPY@Ag)t)bXi-hQarFCPNhf-kiRb)%t7-fj#8Z(PQ@3NjQAjU`m ziB?jMbXPXEW?5Om?5vKyQn3{&o#{EU77qK)JI`6~u&0%U+Jg7%?=`gezT`0G8&PdR za!tdv80JOC*^X+~sSXPZ4iw*iSa2Qv%I6b8pszDFU50mmHBjfZtC|g2zgE|CXDLy; zsB|#S0&y9bqyrObdD0_JJyDk?bTqkIf@EQ_uTXVeOtalNX|0Y#pSzEd#8kg>kPEeb z0pWhu#=1fJ0;;Be85k1F2aBZ1XH=GzrFlK$FF5h7GxW5bglb%B(9e)7yp-$iuLeDR}qtWrB)_e73c$${Yd8 z+_t)BWuxpm_&8q1Vu9@%K|SW7LAe@n7Vh3X)jal<^jtU2IvKl;0y3{6MA8`5-zUtN z%)1Xi4EWxr9C;t%2L~gizJU@Rw!_R@PltvDPk*wxyaoYOZJ`~o7Nt{l-_Q662zR#| zbR8?@aG`(n%Ys`LLmRmb=GwF=R&wXZOt+`^yJv#D>8&L^C z<$SxtDz=ntUqQKY*~O6=biLw1VsE>T zYrpw=yA+4&o4S~5gV}WuL>Q9%;;0dV!Z8qdW@nMB@P9s~s-2?e^_mO0|5;p7g2Ict z(rA&U92M5ME&g2sAz5ofkpF}K2d(;sL?H|Gf+*}L4LaHP{=^V}XPnynUJ3mll9V13 z&prcK*6x|*b=q*z0jkj|O=~K!zC;&rCzn`H&t`i>JiF29@tecKuM?36L18 zFUhas6ze-E;N7%Xc53=yi`BglUkmor$gv(rtkaKStm@B$Enos%P!rpN1nFqH9yiEN5O19Cnb*?u9Q?& zx%Or%HqAvm4U)G5klWmzK% zYP8^~Fu3_>Twc*tp9;yVP7f@~)+*YHDkpYHykeX5;+mJ`Uu#)uiJjD;xR@jPtl(GS zUk*Tq=YVSmOu^@>=Z(N-Lc@uFTYq>BXy^=ubEM7VZQK>&Od*_VQZpEQT){KVPDxri z4eDkG<&Rgu0Kq{XBl^l0Hr@M-{QCRl?;L33zb;jDm+$BAQW-%)Wb1NyT$osWkz?uZ zLZ$nEPpCEj$bbEL&1j8JK{LBQ`C*;IVo){|@*%I+&dx4r#Cnk5xmMH@XT&OrXOG%% zjwt0tKx&kAL3(C*E2zbJcOaD@k@AMcB3$A=G-IDbl=vN~= z>B4Ux0)x@H>wdaAuNmoKAvkc?GQTiqqSZ#ikVE2n$Nhy!##g(+-(jv3tspvmCb%kNY3?oZbsj?t)5o$Jy5`R1~`bq=2`B% zkD`9iBwSrzcVMkge+?AJeqzuciOJfsj|$3e%eiF@DEWGbIBk>lh-0x(<42stXD@*A z)7}c&Uvue!G!fSP{(%wXDLh&_*c%gqqBkwfQV@Xv8ujabzV;t1k2g!@w7AlI`~%N%^Gn*Quczp025s)JHX70G% zT(@4-S{|Xsgw+;jbN#vkTs}dE;bfskZruTQt~T)_z?!KM*Cwn~sKz+*4U%l{7u2ch2>2 zjHMooC3OBUrN6hcgNlsge|+3yu2g*FylBx3N<{1Gu4*rxCb8cpSxB3kZ&NxQId2I+ zwY0J_zuXsSJ^zgcHP*~M8p4!m@oSP~A9W4nVZw2YIcsbofrv3A*;F5CQ3(k{lC zx@LF3g>5#nvqI2uHD;+Q5+?Cvz=0h-K!Qq?8Go`l?ASXSY%$g=Z#~sYOG^_t>20XA zU+r6P8vAVtOP8g=?p=3)TRY(olQ5c}nX4~_4Jq}RQhR&$g}+ySiiFigwo{C^f}#aaTkQ9yWv@2k*@EmmA&{c4Qb&)I zUGXyrS3irK9(9p=z4-iouVab8#g(8(UGMLs_b7gHUe!mvqsY10P-|vt>6~jj!%6Sz z_N=6*Oq*%d_jVx9*oFAp(rNsGvcn7zh3DhO9;7pW)%gDrAcY&85r8xRQVEba;r~dd za_|rRBOmhk4ova?`0&3y_()p)uQUE1U52!IHsmyUZ+CZ%h0AfpAoIbcW>N^;7pj6YL&#dr~PT^*--YZh6yNpPwFfwM(^2 zw61MU<#L><8yI$O;&&M{J<@j_pcHk&uqmD!9u#b-H{l!vmDdLf3>jfialLl0Yef;0 zHa;yoExY)RrEYeU``c@cBuP8FqedOJ_(68xdoK`luUX~=PPVH(+a4RJx4F5wIWJd& zYk^VjwM}?TdZgc8sIex?&|t4?nTv~y*Vfi5vnGf28Ntr=hO4D%6h_de5Q+5@1Vv6QPT9|8ix=T&uwY+5ci$)m5+>A4s6zMpyZE_hD`V5jbL z)=67iX80^xfi@7}AYsO*Mi*Bp#?{r;&2X8w1f^nB0p6#!UTP&8t}Z3Z#<|q@38+6~T_L#mwrs_5ujCq6gGn?RY<2INeDAk?w+hp3jV^Yl zklP~F^}Tt(+WJYkC)CWw<&On)QHIBEacx6`(?Q(~P;;noTcha%$im0)RLs8_sP2h5K;Y2uzRd?ugbC_=b26=rJ;(2yHsdTo<=9>zNN-6#D zYiebq}>sH^+c+*6JQ+v_wwdn0}MmiF-|ee!Sy*!)m& zJ)fgzhitWFtBFsSVgIDia?M@O+iYoWPR7&oO;1k`ItIqX(vqT-6!NBHDmyxg{}+|w z-Z}H^$6@KLO>f&vgR#E*K@A)%hfS^PFOR<4)is{|dZSrmh5;vXe-hnv)DGi6sGS6p zc)+kScb$*Vdat&@eIy*Z*7Mnjl%YMj+_S&EZ%iy>d zN6CIh*A*TlSMm;(B6AM}Q0vh%7xj5>I(F0yC`RMHaJ&U?=px)m8c)P9YYX~-!)&D? z!ok4-5XM44Wy&F!2`^v&a&;Wx7}!6p!(&oA-pPw@FHijgI#d6Q%#@P|Y<*#?w4UPx zyMb02CNQx~+IV<)I9v~P+Ha;Ru!AXwlUUZ{n%svxLzF&vzB%c|(QL4N2@D`) z;4LHHs>!}%1|{m3$@J4%FD^aG5GpR*!zBETqlH{X3_97-{V7%=^lzy|t_PKX5jby$_j`qD> z2RSSBzjx@-<2J5^Gx2XGr~)-&)%dqCy%BT?&@4_(EBn~As2_E0KYNMZvofu1x2fK7gSWW2c-td# ziy(6MSIHeLPyw*Oufg`Hq5wTR02tXUKYhwAE3+9ngVftVOhY)9z^K`8CaOCwdqcr) zA|<8NOBag0vbws&uC6W*&r9!YAaFpYlA_i|&~H{?yugVU142SVwe|Hzg#DnymbP*C$x?*|^@X8Jm7sYILCH zG8-|lw~zVtOEJrBE$n9dYr=y8-xBPwC0dTPDewd1)3?PR=GRM2ea#SCI0qW)>wyL0 z6$C1{rzSF~D>l`;ecQ9K%bxUrv|QGgoIy?cv#gh{kn@%>|BNiMqcaZ^Q!h|^u6r-j zWse;>SG?;+Ll*tA*9>G4s5Q2AV=hlnnAmW;RJv$8M$mrz6i_X;k0U35s1X(xU3ZC$s5h+z7|O>(<3Vkzqz6AQR}UXmb}OJeRi6BHZ+uA|(%??{g+|vug*7>dHYs1*l1@6u#U5PCZua;g)<+Wh%>t@2VGDj|b2_huHv~=Io zULL|x670$la7y7a{}uQP-?roK(wn&KXb;PKz~j4G3A0{nN=bQhc*h;2V>3kLb~V8| zeqGn@?(VKst1g^tOfVkgAxmz(JFqjqySZ4$N4r%nYY=L?AK@B1u8Q`K?7)2Ny6ZL2<BRY{64C%0gDz?hBv91yKxGc{}*7vytrHmz-lkI%5YtBZ)uyp zL==7dI&*9B$cL{e!x0q>M9WK>efs?>jYwb*K|Y&r#@g2<+s@axcoB2vZpsW$C_H^U76e(2zy{PvmEGQJ>eC4YVh+!h)395yjpr zSY@TUE%?LLzbf$)Sz2;=;UF24Hgc)FYSlHN1=^rC1GbU=( zn2jnHa{Lz%1-C6w3A5&XV;hhQZV3dG)yZd4WPo{flffE)J90t|{|SWuD-96AYO$dj z%OzvwFwLnmD{EquCCGw;|z$4VTts7~31$F!uAo&zfMn^Ki4g;4O4V~vTRQZx&(H4*=n=AK(+hf_$rke4*K^C;}h5*5Xtm8FWmoo2N zvTL^Z4CXPf+XvGlsS_<%!Sxo-te+Z(C;CE7vzrgzy>}p2XC9_Xl?QVGz^7(bu;4C) z64sl{9U1jXbXeo1X?FJZ(rcmi78apNN%%vXrzrkZFVOqaWyn~0s z?$n2uSur$_JNE!Uu0X}I0p`QjyptQo=J{mlLOn*hyTTVmy9LX34myx6Pft z+=_s5UCvA&@egI<*+`o-ns`lrgP9z;3LjUGWncDnjr$7T(Jx6XDc7`-!Gd-oh3WMs zol+V6ZM?V5YMa;a5y|zuDtiYs&N{Qxn2CglNcz0$k#ckTN-adIa{4#ed<*tHirbmM zvVar3zgkBwT$J}0{s5MAJOFyOwq_H#lEzP{0u80r)d~Jv6Xep$dq=r(`kp3gb}D+f zdUE9U49y+-bnKLE*$Znc&6kqidf|Wv8gw#(I6W}|-#OUX_XQ2t&r~|=FBlkr1?seH zeka}a#v87}y2?bGo++`sX*69XU!x1g4Pn0ZQtNF?`QjKPVM*9i`^^7~QC0BXA*ths zd@7-wJ6Qqxw|zZPcEK946!dZ2otIB!kq8YF!>x>8vu-wu@-IBue5;jg)x{qqMjkD} z24ieTu3jV^d2C-e8tlJJq&4@YlweXs(L4U@jd+3m${vkoX9f~pYq;4WB&Y8>Ryx&U zL6nMz&!%fL`w!9|NY{6sT-LMFTYq;QjhCcou#Oe*Ah0JGM;t-$A#KwunF2V!CG$=( zxVpi$%UdK+2WKP`>*0DmK>PB}un~U^&-h9W;|C@pC8BT^`SZg+vSOTXJ*xv2N?lK7 zAtZhkO4i$PjDD(p>=@mjp4_7Dg_HR_c_QW9N_-O-j8cI9VSOd&%hTyA_f2wXhvmgH zgA}%xg)gXf3Q&T}lFt;qe zgL0nJc5eZfW?tIb4k9+&7uE<3W(Fo(N6t%8ri;nOtwZh9C0}qZ^FXEpQ~egyCHw9! zHrPD_>Ix!Z4m>5*Atm8Y;~sW)h4@BGP9{&%zwxZ5D{QRmSZ8DTDVC{Mm}cx~2we7Z zL{F&mThPl+V!mS5&U|CS3@P}gVNJp9Elu#volu9o_2ldmd9-Jv2p9CkmJhiGYbq5+ z$!?_&Y^g+9(}rSXSKRNw_*rOj0wKD*01x$iUTn6sc&%sUE3*|9++>$)40-3Vh`&YF zlBQl_M^p2etmm%cx#DkLoF{u<`io~*#Ahopf2-ynCv6(1P$DIrE8{;0b`SCO0>S+Y zYV@YR1E~@s{}W=Jjj-^++xk?3?wdH>}O-(>@qa zXsvw9)`Wuq4}hb{4?oFWtX4u8EJ_$`OuT)GVPPV{|J&e~Fw9!5OiNNp2`g*+psGv$ zyzcj3?mi=jh4Kpza|@)mkiit)@z}+;W8_*p`x?U~74_@R?U`uCK% z2CUNV??ST5OA_sBeyW@}kiqj;lxA;-dLjmnt>Ar0OYJ3UWGaY`p%|cqx2Ar64zp((LT(gE%)XDW4C1>m*x z)@ym$*f{mO715*a-wvb8TG@zKDg+ifV%J{#^u05F5>NAKysdEB+Cs+{h2u3e&Hc*$ zb*#Im+!`O|qv__}5HvvhWM0MavLm|*V!K$8z9~o0b0LWmM9qpqwbMlI*oOr)(e^Sl z&VEF=g&X310EszAgmNQtawz>NzO9EU*57#$dLFyxqo8CyCyfYmDMY9wF^c)_p9x17 ze5B2-LAi*RjN?d|E|yKrSo!-)RIQBN5?$7h7cQ7$9qCpv=H zObBBm3lZns94<_K1^IrX>KsnssWPfF92};tjD+Y1gA|KvS4PcZ>z^h$&+&v)sDjuQ z`Or5Stb{3}iP%m{vTq|>j+#Q)(VKvQ2R%k{qP;{*PC{R&rS?0IpcTrBDXI`PIfD>K9GBS=QtqUMepgXF6B@pidv76rv;F^ zcl{(2w7Cb>A0+&+w6m+QJDFDvm^;w|P!y2keihyaR)P<=XOG{@=+`@{1JrjT;4kR$ zBe=P_pGWjvHTc5#NC(^(XXeSxiI$-!-dJ37l4YOaefqoEV7<542Ym#`CBi-|-_&>r zEv~s#POQ(#I`r&tHWSPUk)jNr6C!D&Bjk2)priLtm}xqvnBAR34Fo1Hay%0c$YP;2 zzz#-wo+xC-kvI86L|7T<7e?ElaP7oAwE~rriHW&E&tGq%fi25vHO>4;j>Jllp%kJN z!Wa4(5)n@KLOJwGLuXgiOa57mS(y78+@vJsca&n{VQxr7D?zlqHbCy^Oa1{yN zOmqWfq#^#|i(UKTpSOhjP6VUd+l7IJD_WpJnJN!}>TpBHoeg6mYEhO4BZ02-iy$%h zv9o@D%Si58KveyqraU;P18y6T7#GKN!2!AWL-;GXB>TSw$&R-t1FDRP{Rdi2I^~Cy zp9qnrJ|#|1t9dO~q;Khu)m?567#bOo-z&IIpbf= zUMDp6hz{Ck0S*rwhkNFAb#<|ezi^iW5vygS9y;=xQk4i$b(BjEj3TCclZE1+Lb%yc z26>5Sa45f82Dou<)Vgvz{VhWCax?tOJ}ZDZB;n#;I9FMN*ZpZD&8mD=!OjB*4zMX0 zM|T@|gqfDoErf~~T$x41InQu1>0U*Oc2U-1w2nGy+LacRRY`ZFTksfEJ}Z%rcN{eM zY)36BAXBjWmavmwyA1QvoAhY!<-c^bDm|`7hmZGSpiLUB$W<2>zySc;ANfGvC<D-i_AIM5 zz=W9sGxhc!+5t%asMT&Xw$h4^ho3fDD`w^ieuuqNY0BHN5_x}ldP5mZ&|N=3L^%JJ zxdo}m_EqEnh3!gRM^JDV!5~3>r5&rmx)KoFA5Bo4ag?7n`w@<#-AWdS`BY{`t5Y#qJ$J8#&Rk`&1J*w2q3t(rA z0SfBXg7c&ide6pW_5-6;4`XZ9Q*54Y2^6k995bP-bwpo3HzK2tKheMJ8Nl0Fluww< zk*fQD#CRS|=DU#gZ3jLPmmkmVGw&ZGY-|VrS#7&(pnP)f7yP&<$U(1;o~n|9Enq$5 z_g?^?F(L*@r;$>5b=VlqnJQmxHcA{E5&|8vuDh!P2o3l#FcW2iWDCTKKdk8LbNRlfa1YX;Tvqmnq{Tu(E;RQ6Lf#^IEK>g z+lb$-DiP{^#IJpcQ;`!PKQKtGo^jl^2AT0kMu!`S42`iorTcabmrf6D*@xgQIT==x zZ;>%Yr)$ek-=d_YVMe)s&Q5_sNqN`HNX4Z+T1I2{X6#nDvYRr7D;nc4((Atvy$iuI z3pKsrpQcI4X(BUeFr1=DIxrO-V1LPw-SCv%q$dKumDA+((~A*Dq#?_ly*Q!6!z<*f z)FHELpuu)W?w)@uDhe4O_svjM6abCx22_XPBPRg5MBs0Je99YFR$#Ci6B~e;1@$KL zI_~l?o=b1+gXovk)Wi?7b!3E)d4vLrFVp$+=lAruE}!<-9m?+S?>Q7{AP*fKGxHVf z-_5uAPePOD_-Fek*~qc*vNaFM|3W?NjW9)5Uunro#VI{BS-XF-$>nnp9#zXQFu2a2 zRpkBsG9k9@kWPxr6~_(Hjcc4($QlTH^~wFxW3!uUW>IndCrbsb?9-v=+9B z+sq_p&YekzVHZh9cG==~u=maxUYK+)Y{r$I7*-y zZBMu5Jc<0p;PUMjpK+GRQR`WM+dqoY{8058EuA(XAV3fh0M-kQvB2szHm2rVa-#;` z`&U3aP(Jpi{Oq7fTz~(&N79MoFOL53Ld|*|AScVpzDh>mPfSdR3TH1fH2-$fWlA2N znwfJY7_`){s1wOMZP{YmRNE8zn^qP6DLdpnY5wnq48K*;p+wX^BY3A{yiYvIpO&ca zgUd2LZ%wAuC>8L-sBwg0qZG!NUx<&N*mK)s#=YH=nvnM~;q^s2uINmSH`vM=^e4ar z+3GggIx~z{MqEY5@UOx2y`gENjjQ695mDeIB5!ac{h10o964EEw8bjRnWP|DQBzZ@26O)?-?BWQ1vwq(6W5zKCQP0~1e zW#{8N4ZZ63BugK6iF;ZO=cA`j6T<^_g+D^Dm3jgZMEAl(iF9G87Jeu|v0YDP_F8jv zJAZh|;x&fPf0k*#6m%Q_!B|M^GREqmEB_+m$>epcg5q{IXGL4TFa7{ETkGhE`*YMD zaR>2q|2z1#(Mf6hHiDIECZXpo>Bd)fEm^M2pYB6FS%^!gn+gs?km`gXO7ExHaoT+s z!;PY;olG?a8r1f>;dPAezA28a(bcgfs&W|qu0Rh`sg4c@h$)kOfk>_)h-8G%)`L32DV1d0xmwu6o|2Yev`<^=SAyv zmqj)F5bf8z6rLr0txxHW)<*PJ^iokPLMkeh;EOHV1v#Ev|7qGj#kId=)l0{FNqr7K zBk7xsD#%EXSy|n2ywTKpOw;N_5TrEbKL^2UpG9tv2=av#AGb&mHF%hd^qQV@|I!@| z$usVE8N-l5t~OJV>s;|^PK!3ae&DEQVN`pnw)urrkg32|u=<&)-|*hiu^%>J!QEc$ zK-FRfp!JN&L;#oZMyR3a4C~LgV}r{a#>0@%&S(v#ev@IC)j z1zz+3z_0*|19U*Uc{gHKbMaQ(18wV3(~-|Z|YUMuDlX+%73MrJxY=mF|Pt5 zp%U8Gy7N5U3ZUa$M&v5XJB7=%ZY%keWOT#Bca}DFAUa{lc5nnLV7Wy>ehuvxKWrh| zx2g@7ku@(uY)_eZZhs9KBAwiIkTsXlt^hZ$#!>oQ_1{KlJw|VXZ~r@d>;EdI>!0|H|2@#_ f|J%!8K7SC&>^pe9;1Dnf2VOFgiW22w2LAs8Ynz_7 diff --git a/doc/img/UDPsrc_plugin.xcf b/doc/img/UDPsrc_plugin.xcf deleted file mode 100644 index 5c6042f25d41e9dd7f887931a858d5c4453e3dd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 204165 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%0A+|=SeNUkz*b;8fLs^X zE&w+puD!r3zF82}>?0Ym|@KmyUDP source plugin +

    UDP sink plugin

    Introduction

    -By "source" one should understand a source of samples for the outside of SDRangel application. An UDP connection is established from the plugin to the given address and port and samples are directed to it. +By "sink" one should understand a sink for samples coming from the device baseband. An UDP connection is established from the plugin to the given address and port and samples are directed to it. The UDP block size or UDP payload size is fixed at 512 bytes. @@ -12,7 +12,7 @@ This plugin is available for Linux and Mac O/S only.

    Interface

    -![UDP Source plugin GUI](../../../doc/img/UDPsrc_plugin.png) +![UDP Sink plugin GUI](../../../doc/img/UDPsink_plugin.png)

    1: Frequency shift from center frequency of reception

    @@ -68,7 +68,7 @@ This is the maximum expected FM deviation in Hz for NFM demodulated samples. The

    9: AGC and audio feedback control

    -![UDP Source plugin GUI AGC](../../../doc/img/UDPsrc_plugin_agc.png) +![UDP Sink plugin GUI AGC](../../../doc/img/UDPsink_plugin_agc.png)

    9.1: Toggle AGC

    @@ -106,7 +106,7 @@ This gain is applied to the samples just before they are sent via UDP. The gain

    13: Squelch

    -![UDP Source plugin GUI Squelch](../../../doc/img/UDPsrc_plugin_sq.png) +![UDP Sink plugin GUI Squelch](../../../doc/img/UDPsink_plugin_sq.png)

    13.1: Squelch indicator

    diff --git a/plugins/channeltx/udpsource/readme.md b/plugins/channeltx/udpsource/readme.md index 792a51540..69ddd8b1b 100644 --- a/plugins/channeltx/udpsource/readme.md +++ b/plugins/channeltx/udpsource/readme.md @@ -1,8 +1,8 @@ -

    UDP sink plugin

    +

    UDP Source plugin

    Introduction

    -By "sink" one should understand a sink of samples for the outside of SDRangel application. An external application establishes an UDP connection to the plugin at the given address and port and samples are directed to it. In fact it can also come from SDRangel itself using the UDP source plugin +By "source" one should understand a source of samples that feed the baseband of the transmitting device. An external application establishes an UDP connection to the plugin at the given address and port and samples are directed to it. In fact it can also come from SDRangel itself using the UDP source plugin The UDP block size or UDP payload size is optimized for 512 bytes but other sizes are acceptable. @@ -10,7 +10,7 @@ This plugin is available for Linux and Mac O/S only.

    Interface

    -![UDP Sink plugin GUI](../../../doc/img/UDPsink_plugin.png) +![UDP Source plugin GUI](../../../doc/img/UDPsource_plugin.png)

    1: Frequency shift from center frequency of reception

    From d084d6ff5b3597ffb819972a37c7d112d0ce7f14 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 01:21:51 +0200 Subject: [PATCH 744/956] Cosmetic changes --- plugins/channelrx/udpsink/udpsinksettings.cpp | 2 +- plugins/channeltx/daemonsource/daemonsource.cpp | 2 +- plugins/channeltx/udpsource/udpsourcesettings.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/channelrx/udpsink/udpsinksettings.cpp b/plugins/channelrx/udpsink/udpsinksettings.cpp index 5e84f3d73..fdf24a6d8 100644 --- a/plugins/channelrx/udpsink/udpsinksettings.cpp +++ b/plugins/channelrx/udpsink/udpsinksettings.cpp @@ -48,7 +48,7 @@ void UDPSinkSettings::resetToDefaults() m_udpPort = 9998; m_audioPort = 9997; m_rgbColor = QColor(225, 25, 99).rgb(); - m_title = "UDP Sample Source"; + m_title = "UDP Sample Sink"; } QByteArray UDPSinkSettings::serialize() const diff --git a/plugins/channeltx/daemonsource/daemonsource.cpp b/plugins/channeltx/daemonsource/daemonsource.cpp index b336fb7c5..267095de7 100644 --- a/plugins/channeltx/daemonsource/daemonsource.cpp +++ b/plugins/channeltx/daemonsource/daemonsource.cpp @@ -38,7 +38,7 @@ MESSAGE_CLASS_DEFINITION(DaemonSource::MsgConfigureDaemonSource, Message) MESSAGE_CLASS_DEFINITION(DaemonSource::MsgQueryStreamData, Message) MESSAGE_CLASS_DEFINITION(DaemonSource::MsgReportStreamData, Message) -const QString DaemonSource::m_channelIdURI = "sdrangel.channeltx.daemonsrc"; +const QString DaemonSource::m_channelIdURI = "sdrangel.channeltx.daemonsource"; const QString DaemonSource::m_channelId ="DaemonSource"; DaemonSource::DaemonSource(DeviceSinkAPI *deviceAPI) : diff --git a/plugins/channeltx/udpsource/udpsourcesettings.cpp b/plugins/channeltx/udpsource/udpsourcesettings.cpp index 88cda9491..c7acbd0a5 100644 --- a/plugins/channeltx/udpsource/udpsourcesettings.cpp +++ b/plugins/channeltx/udpsource/udpsourcesettings.cpp @@ -49,7 +49,7 @@ void UDPSourceSettings::resetToDefaults() m_udpAddress = "127.0.0.1"; m_udpPort = 9998; m_rgbColor = QColor(225, 25, 99).rgb(); - m_title = "UDP Sample Sink"; + m_title = "UDP Sample Source"; } QByteArray UDPSourceSettings::serialize() const From 5771ef4783e222d75be5c728f58923acec85e708 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 15:30:53 +0200 Subject: [PATCH 745/956] Foolproof AudioFifo to try fix issue #210. Consequently removed useless timeout parameter on read and write methods --- plugins/channelrx/demodam/amdemod.cpp | 4 +- plugins/channelrx/demodam/amdemodplugin.cpp | 2 +- plugins/channelrx/demodbfm/bfmdemod.cpp | 4 +- plugins/channelrx/demodbfm/bfmplugin.cpp | 2 +- plugins/channelrx/demoddsd/dsddemod.cpp | 4 +- plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- plugins/channelrx/demodnfm/nfmdemod.cpp | 4 +- plugins/channelrx/demodnfm/nfmplugin.cpp | 2 +- plugins/channelrx/demodssb/ssbdemod.cpp | 4 +- plugins/channelrx/demodssb/ssbplugin.cpp | 2 +- plugins/channelrx/demodwfm/wfmdemod.cpp | 4 +- plugins/channelrx/demodwfm/wfmplugin.cpp | 2 +- plugins/channelrx/udpsink/udpsink.cpp | 6 +- plugins/channeltx/modam/ammod.cpp | 2 +- plugins/channeltx/modam/ammodplugin.cpp | 2 +- plugins/channeltx/modnfm/nfmmod.cpp | 2 +- plugins/channeltx/modnfm/nfmmodplugin.cpp | 2 +- plugins/channeltx/modssb/ssbmod.cpp | 2 +- plugins/channeltx/modssb/ssbmodplugin.cpp | 2 +- plugins/channeltx/modwfm/wfmmod.cpp | 2 +- plugins/channeltx/modwfm/wfmmodplugin.cpp | 2 +- sdrbase/audio/audiofifo.cpp | 116 ++---------------- sdrbase/audio/audiofifo.h | 9 +- sdrbase/audio/audioinput.cpp | 2 +- sdrbase/audio/audiooutput.cpp | 2 +- sdrbase/dsp/dvserialworker.cpp | 2 +- 26 files changed, 47 insertions(+), 142 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index 50c1caa88..29a01559f 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -136,7 +136,7 @@ void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector if (m_audioBufferFill > 0) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -263,7 +263,7 @@ void AMDemod::processOneSample(Complex &ci) if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index 76a6b95bb..28a4b640c 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("4.0.6"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index f17ae25d3..1d056771d 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -246,7 +246,7 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if(res != m_audioBufferFill) { qDebug("BFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill); @@ -262,7 +262,7 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_audioBufferFill > 0) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { qDebug("BFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); diff --git a/plugins/channelrx/demodbfm/bfmplugin.cpp b/plugins/channelrx/demodbfm/bfmplugin.cpp index 0d813d9ef..b6af176e5 100644 --- a/plugins/channelrx/demodbfm/bfmplugin.cpp +++ b/plugins/channelrx/demodbfm/bfmplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor BFMPlugin::m_pluginDescriptor = { QString("Broadcast FM Demodulator"), - QString("4.0.6"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index edce8becd..04a9169e3 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -296,7 +296,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (nbAudioSamples > 0) { if (!m_settings.m_audioMute) { - m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples, 10); + m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples); } m_dsdDecoder.resetAudio1(); @@ -311,7 +311,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (nbAudioSamples > 0) { if (!m_settings.m_audioMute) { - m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples, 10); + m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples); } m_dsdDecoder.resetAudio2(); diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index 32c365ae0..d10957be0 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("4.0.6"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 29c1de46f..85a6e9263 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -302,7 +302,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -318,7 +318,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_audioBufferFill > 0) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index e23e7fa6b..97ef60aae 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("4.0.6"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 82749468c..0b0aa6b21 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -253,7 +253,7 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -265,7 +265,7 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } } - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp index 04281096d..9a9ab8e9c 100644 --- a/plugins/channelrx/demodssb/ssbplugin.cpp +++ b/plugins/channelrx/demodssb/ssbplugin.cpp @@ -10,7 +10,7 @@ const PluginDescriptor SSBPlugin::m_pluginDescriptor = { QString("SSB Demodulator"), - QString("4.0.6"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index e4354a331..741a16706 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -152,7 +152,7 @@ void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if(m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { qDebug("WFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill); @@ -168,7 +168,7 @@ void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_audioBufferFill > 0) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { qDebug("WFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); diff --git a/plugins/channelrx/demodwfm/wfmplugin.cpp b/plugins/channelrx/demodwfm/wfmplugin.cpp index 8d2836832..55c366b16 100644 --- a/plugins/channelrx/demodwfm/wfmplugin.cpp +++ b/plugins/channelrx/demodwfm/wfmplugin.cpp @@ -10,7 +10,7 @@ const PluginDescriptor WFMPlugin::m_pluginDescriptor = { QString("WFM Demodulator"), - QString("4.0.6"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/udpsink/udpsink.cpp b/plugins/channelrx/udpsink/udpsink.cpp index 1e2e2a235..945c35a88 100644 --- a/plugins/channelrx/udpsink/udpsink.cpp +++ b/plugins/channelrx/udpsink/udpsink.cpp @@ -414,7 +414,7 @@ void UDPSink::audioReadyRead() if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -436,7 +436,7 @@ void UDPSink::audioReadyRead() if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -448,7 +448,7 @@ void UDPSink::audioReadyRead() } } - if (m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 0) != m_audioBufferFill) + if (m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill) != m_audioBufferFill) { qDebug("UDPSink::audioReadyRead: lost samples"); } diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index 8fed8d862..6fe798e1c 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -150,7 +150,7 @@ void AMMod::pullAudio(int nbSamples) m_audioBuffer.resize(nbAudioSamples); } - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbAudioSamples, 10); + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbAudioSamples); m_audioBufferFill = 0; } diff --git a/plugins/channeltx/modam/ammodplugin.cpp b/plugins/channeltx/modam/ammodplugin.cpp index 2aa1bd0f5..23d880d4e 100644 --- a/plugins/channeltx/modam/ammodplugin.cpp +++ b/plugins/channeltx/modam/ammodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor AMModPlugin::m_pluginDescriptor = { QString("AM Modulator"), - QString("4.0.7"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp index 6dc2c7b1e..894d4ffb5 100644 --- a/plugins/channeltx/modnfm/nfmmod.cpp +++ b/plugins/channeltx/modnfm/nfmmod.cpp @@ -155,7 +155,7 @@ void NFMMod::pullAudio(int nbSamples) m_audioBuffer.resize(nbSamplesAudio); } - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio, 10); + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio); m_audioBufferFill = 0; } diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp index 4b628a709..8f231d52c 100644 --- a/plugins/channeltx/modnfm/nfmmodplugin.cpp +++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor NFMModPlugin::m_pluginDescriptor = { QString("NFM Modulator"), - QString("4.0.7"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index 17eb07625..2efda0df5 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -179,7 +179,7 @@ void SSBMod::pullAudio(int nbSamples) m_audioBuffer.resize(nbSamplesAudio); } - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio, 10); + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio); m_audioBufferFill = 0; } diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp index f8b146068..cfd279871 100644 --- a/plugins/channeltx/modssb/ssbmodplugin.cpp +++ b/plugins/channeltx/modssb/ssbmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor SSBModPlugin::m_pluginDescriptor = { QString("SSB Modulator"), - QString("4.0.7"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index d8929546f..916781f0d 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -177,7 +177,7 @@ void WFMMod::pullAudio(int nbSamples) m_audioBuffer.resize(nbSamplesAudio); } - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio, 10); + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio); m_audioBufferFill = 0; } diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp index afa3545d6..b526dc703 100644 --- a/plugins/channeltx/modwfm/wfmmodplugin.cpp +++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor WFMModPlugin::m_pluginDescriptor = { QString("WFM Modulator"), - QString("4.0.7"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/audio/audiofifo.cpp b/sdrbase/audio/audiofifo.cpp index 2cade6829..26ca348dc 100644 --- a/sdrbase/audio/audiofifo.cpp +++ b/sdrbase/audio/audiofifo.cpp @@ -52,9 +52,6 @@ AudioFifo::~AudioFifo() m_fifo = 0; } - m_writeWaitCondition.wakeOne(); - m_readWaitCondition.wakeOne(); - m_size = 0; } @@ -65,68 +62,27 @@ bool AudioFifo::setSize(uint32_t numSamples) return create(numSamples); } -uint AudioFifo::write(const quint8* data, uint32_t numSamples, int timeout_ms) +uint AudioFifo::write(const quint8* data, uint32_t numSamples) { - QTime time; uint32_t total; uint32_t remaining; uint32_t copyLen; - if(m_fifo == 0) - { + if (m_fifo == 0) { return 0; } - time.start(); m_mutex.lock(); - if(timeout_ms == 0) - { - total = MIN(numSamples, m_size - m_fill); - } - else - { - total = numSamples; - } - + total = MIN(numSamples, m_size - m_fill); remaining = total; - while (remaining > 0) + while (remaining != 0) { if (isFull()) { - if (time.elapsed() < timeout_ms) - { - m_writeWaitLock.lock(); - m_mutex.unlock(); - int ms = timeout_ms - time.elapsed(); - - if(ms < 1) - { - ms = 1; - } - - bool ok = m_writeWaitCondition.wait(&m_writeWaitLock, ms); - m_writeWaitLock.unlock(); - - if(!ok) - { - return total - remaining; - } - - m_mutex.lock(); - - if(m_fifo == 0) - { - m_mutex.unlock(); - return 0; - } - } - else - { - m_mutex.unlock(); - return total - remaining; - } + m_mutex.unlock(); + return total - remaining; // written so far } copyLen = MIN(remaining, m_size - m_fill); @@ -137,75 +93,33 @@ uint AudioFifo::write(const quint8* data, uint32_t numSamples, int timeout_ms) m_fill += copyLen; data += copyLen * m_sampleSize; remaining -= copyLen; - m_readWaitCondition.wakeOne(); } m_mutex.unlock(); return total; } -uint AudioFifo::read(quint8* data, uint32_t numSamples, int timeout_ms) +uint AudioFifo::read(quint8* data, uint32_t numSamples) { - QTime time; uint32_t total; uint32_t remaining; uint32_t copyLen; - if(m_fifo == 0) - { + if (m_fifo == 0) { return 0; } - time.start(); m_mutex.lock(); - if(timeout_ms == 0) - { - total = MIN(numSamples, m_fill); - } - else - { - total = numSamples; - } - + total = MIN(numSamples, m_fill); remaining = total; - while(remaining > 0) + while (remaining != 0) { - if(isEmpty()) + if (isEmpty()) { - if(time.elapsed() < timeout_ms) - { - m_readWaitLock.lock(); - m_mutex.unlock(); - int ms = timeout_ms - time.elapsed(); - - if(ms < 1) - { - ms = 1; - } - - bool ok = m_readWaitCondition.wait(&m_readWaitLock, ms); - m_readWaitLock.unlock(); - - if(!ok) - { - return total - remaining; - } - - m_mutex.lock(); - - if(m_fifo == 0) - { - m_mutex.unlock(); - return 0; - } - } - else - { - m_mutex.unlock(); - return total - remaining; - } + m_mutex.unlock(); + return total - remaining; // read so far } copyLen = MIN(remaining, m_fill); @@ -217,7 +131,6 @@ uint AudioFifo::read(quint8* data, uint32_t numSamples, int timeout_ms) m_fill -= copyLen; data += copyLen * m_sampleSize; remaining -= copyLen; - m_writeWaitCondition.wakeOne(); } m_mutex.unlock(); @@ -236,7 +149,6 @@ uint AudioFifo::drain(uint32_t numSamples) m_head = (m_head + numSamples) % m_size; m_fill -= numSamples; - m_writeWaitCondition.wakeOne(); return numSamples; } @@ -247,8 +159,6 @@ void AudioFifo::clear() m_fill = 0; m_head = 0; m_tail = 0; - - m_writeWaitCondition.wakeOne(); } bool AudioFifo::create(uint32_t numSamples) diff --git a/sdrbase/audio/audiofifo.h b/sdrbase/audio/audiofifo.h index defdd3658..bab5d0177 100644 --- a/sdrbase/audio/audiofifo.h +++ b/sdrbase/audio/audiofifo.h @@ -34,8 +34,8 @@ public: bool setSize(uint32_t numSamples); - uint32_t write(const quint8* data, uint32_t numSamples, int timeout_ms = INT_MAX); - uint32_t read(quint8* data, uint32_t numSamples, int timeout_ms = INT_MAX); + uint32_t write(const quint8* data, uint32_t numSamples); + uint32_t read(quint8* data, uint32_t numSamples); uint32_t drain(uint32_t numSamples); void clear(); @@ -58,11 +58,6 @@ private: uint32_t m_head; uint32_t m_tail; - QMutex m_writeWaitLock; - QMutex m_readWaitLock; - QWaitCondition m_writeWaitCondition; - QWaitCondition m_readWaitCondition; - bool create(uint32_t numSamples); }; diff --git a/sdrbase/audio/audioinput.cpp b/sdrbase/audio/audioinput.cpp index d94b2ffbf..17362b640 100644 --- a/sdrbase/audio/audioinput.cpp +++ b/sdrbase/audio/audioinput.cpp @@ -181,7 +181,7 @@ qint64 AudioInput::writeData(const char *data, qint64 len) for (std::list::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) { - (*it)->write(reinterpret_cast(data), len/4, 10); + (*it)->write(reinterpret_cast(data), len/4); } return len; diff --git a/sdrbase/audio/audiooutput.cpp b/sdrbase/audio/audiooutput.cpp index c7ddf62e0..d6d2d107d 100644 --- a/sdrbase/audio/audiooutput.cpp +++ b/sdrbase/audio/audiooutput.cpp @@ -241,7 +241,7 @@ qint64 AudioOutput::readData(char* data, qint64 maxLen) for (std::list::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) { // use outputBuffer as temp - yes, one memcpy could be saved - unsigned int samples = (*it)->read((quint8*) data, samplesPerBuffer, 1); + unsigned int samples = (*it)->read((quint8*) data, samplesPerBuffer); const qint16* src = (const qint16*) data; std::vector::iterator dst = m_mixBuffer.begin(); diff --git a/sdrbase/dsp/dvserialworker.cpp b/sdrbase/dsp/dvserialworker.cpp index 6c5e28e4c..e72f0f5db 100644 --- a/sdrbase/dsp/dvserialworker.cpp +++ b/sdrbase/dsp/dvserialworker.cpp @@ -118,7 +118,7 @@ void DVSerialWorker::handleInputMessages() if (audioFifo) { - uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { From da963179fbc140048dfc62aefde51063f903ceaf Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 15:46:42 +0200 Subject: [PATCH 746/956] UDP source and sink: align message names --- debian/changelog | 54 ++++++++++---------- plugins/channelrx/udpsink/udpsink.cpp | 26 +++++----- plugins/channelrx/udpsink/udpsink.h | 16 +++--- plugins/channelrx/udpsink/udpsinkgui.cpp | 8 +-- plugins/channeltx/udpsource/udpsource.cpp | 26 +++++----- plugins/channeltx/udpsource/udpsource.h | 16 +++--- plugins/channeltx/udpsource/udpsourcegui.cpp | 6 +-- 7 files changed, 77 insertions(+), 75 deletions(-) diff --git a/debian/changelog b/debian/changelog index d7342e7c5..415cc7da9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,17 +1,19 @@ sdrangel (4.1.0-1) unstable; urgency=medium - * Integrated SDRdaemon + * Integrated SDRdaemon with a pair of new channel plugins + * Exchanged UDP sink and source names for better consistency + * Fixed AudioFifo to prevent deadlocks. Fixes issue #210 + + -- Edouard Griffiths, F4EXB Sun, 16 Sep 2018 21:14:18 +0200 - -- Edouard Griffiths, F4EXB Sun, 02 Sep 2018 21:14:18 +0200 - sdrangel (4.0.7-1) unstable; urgency=medium * Scope: removed old scope objects - * Web API: reduced HTTP server debug messages + * Web API: reduced HTTP server debug messages * Sink plugins: corrected name getters and setters -- Edouard Griffiths, F4EXB Sun, 19 Aug 2018 21:14:18 +0200 - + sdrangel (4.0.6-1) unstable; urgency=medium * Web API: RTL-SDR: fixed RF bandwidth setting @@ -21,7 +23,7 @@ sdrangel (4.0.6-1) unstable; urgency=medium * SSB modulator: fixed sample not reset when no modulation is present -- Edouard Griffiths, F4EXB Tue, 07 Aug 2018 19:14:18 +0200 - + sdrangel (4.0.5-1) unstable; urgency=medium * Web API: handle pre-flight requests @@ -35,20 +37,20 @@ sdrangel (4.0.4-1) unstable; urgency=medium * Fix preset group delete not removing presets from the preset window -- Edouard Griffiths, F4EXB Wed, 18 Jul 2018 19:14:18 +0200 - + sdrangel (4.0.3-1) unstable; urgency=medium * Spectrum: linear mode for spectrum * Scope: fixed power display overlay -- Edouard Griffiths, F4EXB Sun, 08 Jul 2018 15:14:18 +0200 - + sdrangel (4.0.2-1) unstable; urgency=medium * Spectrum: added averaging -- Edouard Griffiths, F4EXB Sun, 01 Jul 2018 21:14:18 +0200 - + sdrangel (4.0.1-1) unstable; urgency=medium * DSD demod: added NXDN support @@ -57,7 +59,7 @@ sdrangel (4.0.1-1) unstable; urgency=medium * Scope: new magnitude squared projection mainly for radioastronomy -- Edouard Griffiths, F4EXB Sat, 23 Jun 2018 09:14:18 +0200 - + sdrangel (4.0.0-1) unstable; urgency=medium * Finalization of REST API and server instance @@ -110,7 +112,7 @@ sdrangel (3.14.3-1) unstable; urgency=medium * LimeSDR: implemented transverter dialog (issue #157) * UDP source and sink: make sure audio samples are always on 16 bits * UDP source and sink: dialog elements for address and port - * Reviewed FFT destruction in many channel sources and sinks (issue #159) + * Reviewed FFT destruction in many channel sources and sinks (issue #159) -- Edouard Griffiths, F4EXB Fri, 20 Apr 2018 20:14:18 +0200 @@ -145,7 +147,7 @@ sdrangel (3.14.0-1) unstable; urgency=medium sdrangel (3.13.1-1) unstable; urgency=medium * Web API: settings and report enry points for AM demod and AirspyHF - * Web API: client Python script scanner example + * Web API: client Python script scanner example * LimeSDR: fixed channelA/B frequency setting with latest LimeSuite -- Edouard Griffiths, F4EXB Sun, 25 Mar 2018 06:14:18 +0100 @@ -165,7 +167,7 @@ sdrangel (3.12.0-1) unstable; urgency=medium * 24 bit Rx DSP Debian builds * DC and IQ correction fixes * AirspyHF: fall back to official library support - * Test source: implemented phase imbalance + * Test source: implemented phase imbalance -- Edouard Griffiths, F4EXB Sun, 11 Feb 2018 12:14:18 +0100 @@ -178,7 +180,7 @@ sdrangel (3.11.1-1) unstable; urgency=medium sdrangel (3.11.0-1) unstable; urgency=medium * AirspyHF: support - * Refactored 8 bit samples shifting during decimation (RTL-SDR and HackRF Rx) + * Refactored 8 bit samples shifting during decimation (RTL-SDR and HackRF Rx) * RTL-SDR: implemented RF filter control (tuner bandwidth) * Airspy, BladeRF, HackRF, PlutoSDR, RTLSDR, SDRPlay: fix for no decimation * Test source input plugin for test of software internals @@ -202,7 +204,7 @@ sdrangel (3.10.0-1) unstable; urgency=medium * AM, SSB demodulators and SSB modulator: fix sample rate handling * Enhancements to presets processing and GUI * Improved build and system info logging - * Web API: added function to set device set focus (GUI only) + * Web API: added function to set device set focus (GUI only) -- Edouard Griffiths, F4EXB Sun, 07 Jan 2018 09:14:18 +0100 @@ -219,7 +221,7 @@ sdrangel (3.9.0-1) unstable; urgency=medium * Server: proof of concept * DSD demodulator: added optional high pass filter on audio (uese dsdcc v1.7.3) - * Down/Up channelizers: enqeue MsgChannelizerNotification to sample sink/source + * Down/Up channelizers: enqeue MsgChannelizerNotification to sample sink/source * Separate channel sample rate and offset frequency this data from settings * Use specific method to apply channelizer sample rate and frequency offset changes @@ -342,14 +344,14 @@ sdrangel (3.7.1-1) unstable; urgency=medium sdrangel (3.7.0-1) unstable; urgency=medium - * PlutoSDR: Rx support + * PlutoSDR: Rx support * GUI segregation: preliminary works -- Edouard Griffiths, F4EXB Thu, 17 Sep 2017 23:14:18 +0200 sdrangel (3.6.1-1) unstable; urgency=medium - * Basic channel settings dialog with title+color update and UDP parameters + * Basic channel settings dialog with title+color update and UDP parameters * Applied to UDPSink, UDPSource, DSDDemod, AMDemod, BFMDemod, NFMDemod * DSD, AM, NFM, BFM demods: added possibility to send AF via UDP @@ -388,7 +390,7 @@ sdrangel (3.5.3-1) unstable; urgency=medium sdrangel (3.5.2-1) unstable; urgency=medium * HackRF: stop Rx before start Tx automatically and vice versa - * HackRF: added option on Rx to drive Tx frequency change + * HackRF: added option on Rx to drive Tx frequency change * SSB mod and demod: make UI displays consistent with DSB, USB and LSB modes -- Edouard Griffiths, F4EXB Sat, 22 Jul 2017 09:14:18 +0200 @@ -411,13 +413,13 @@ sdrangel (3.5.0-1) unstable; urgency=medium * Changed frequency thumbweels color scheme * Activated compiler warnings and fixed warnings * Lots of little GUI fixes - + -- Edouard Griffiths, F4EXB Mon, 11 Jun 2017 19:14:18 +0200 sdrangel (3.4.5-1) unstable; urgency=medium - * Removed default constuctors in Moving average and AGC classes - + * Removed default constuctors in Moving average and AGC classes + -- Edouard Griffiths, F4EXB Mon, 11 May 2017 21:14:18 +0100 sdrangel (3.4.4-1) unstable; urgency=medium @@ -427,14 +429,14 @@ sdrangel (3.4.4-1) unstable; urgency=medium * LimeSDR: Windows 64 build * LimeSDR: integrated Debian build * cmake modules: search lib64 libraries - + -- Edouard Griffiths, F4EXB Mon, 08 May 2017 21:14:18 +0100 sdrangel (3.4.3-1) unstable; urgency=medium * DSD demod: use version 1.7.1 of dsdcc with PLL for symbol synchronization as an option * LimeSDR: fixed antenna selection in both input and output plugins - + -- Edouard Griffiths, F4EXB Mon, 08 May 2017 23:14:18 +0100 sdrangel (3.4.2-1) unstable; urgency=medium @@ -442,7 +444,7 @@ sdrangel (3.4.2-1) unstable; urgency=medium * DSD demod: use version 1.7.0 of dsdcc with PLL for symbol synchronization * DSD demod: kernel >= 4.4.52 workaround for SerialDV * Code cleanup: cppchack and Eclipse warnings - + -- Edouard Griffiths, F4EXB Wed, 26 Apr 2017 23:14:18 +0100 sdrangel (3.4.1-1) unstable; urgency=medium @@ -451,7 +453,7 @@ sdrangel (3.4.1-1) unstable; urgency=medium * HackRF support: fixed start/stop sequence * WFM Demod enhancement * CW Keyer: specifiy char signedness to fix error with some compilers - + -- Edouard Griffiths, F4EXB Wed, 26 Apr 2017 23:14:18 +0100 sdrangel (3.4.0-1) unstable; urgency=medium diff --git a/plugins/channelrx/udpsink/udpsink.cpp b/plugins/channelrx/udpsink/udpsink.cpp index 945c35a88..040c054df 100644 --- a/plugins/channelrx/udpsink/udpsink.cpp +++ b/plugins/channelrx/udpsink/udpsink.cpp @@ -34,9 +34,9 @@ const Real UDPSink::m_agcTarget = 16384.0f; -MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSrc, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSource, Message) MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSrcSpectrum, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSinkSpectrum, Message) const QString UDPSink::m_channelIdURI = "sdrangel.channel.udpsink"; const QString UDPSink::m_channelId = "UDPSink"; @@ -129,7 +129,7 @@ UDPSink::~UDPSink() void UDPSink::setSpectrum(MessageQueue* messageQueue, bool enabled) { - Message* cmd = MsgUDPSrcSpectrum::create(enabled); + Message* cmd = MsgUDPSinkSpectrum::create(enabled); messageQueue->push(cmd); } @@ -356,22 +356,22 @@ bool UDPSink::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureUDPSrc::match(cmd)) + else if (MsgConfigureUDPSource::match(cmd)) { - MsgConfigureUDPSrc& cfg = (MsgConfigureUDPSrc&) cmd; - qDebug("UDPSink::handleMessage: MsgConfigureUDPSrc"); + MsgConfigureUDPSource& cfg = (MsgConfigureUDPSource&) cmd; + qDebug("UDPSink::handleMessage: MsgConfigureUDPSource"); applySettings(cfg.getSettings(), cfg.getForce()); return true; } - else if (MsgUDPSrcSpectrum::match(cmd)) + else if (MsgUDPSinkSpectrum::match(cmd)) { - MsgUDPSrcSpectrum& spc = (MsgUDPSrcSpectrum&) cmd; + MsgUDPSinkSpectrum& spc = (MsgUDPSinkSpectrum&) cmd; m_spectrumEnabled = spc.getEnabled(); - qDebug() << "UDPSink::handleMessage: MsgUDPSrcSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; + qDebug() << "UDPSink::handleMessage: MsgUDPSinkSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; return true; } @@ -629,14 +629,14 @@ bool UDPSink::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { - MsgConfigureUDPSrc *msg = MsgConfigureUDPSrc::create(m_settings, true); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); m_inputMessageQueue.push(msg); return true; } else { m_settings.resetToDefaults(); - MsgConfigureUDPSrc *msg = MsgConfigureUDPSrc::create(m_settings, true); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); m_inputMessageQueue.push(msg); return false; } @@ -729,13 +729,13 @@ int UDPSink::webapiSettingsPutPatch( m_inputMessageQueue.push(msgChan); } - MsgConfigureUDPSrc *msg = MsgConfigureUDPSrc::create(settings, force); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(settings, force); m_inputMessageQueue.push(msg); qDebug("getUdpSinkSettings::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); if (m_guiMessageQueue) // forward to GUI if any { - MsgConfigureUDPSrc *msgToGUI = MsgConfigureUDPSrc::create(settings, force); + MsgConfigureUDPSource *msgToGUI = MsgConfigureUDPSource::create(settings, force); m_guiMessageQueue->push(msgToGUI); } diff --git a/plugins/channelrx/udpsink/udpsink.h b/plugins/channelrx/udpsink/udpsink.h index a1e6a07b3..5923226b0 100644 --- a/plugins/channelrx/udpsink/udpsink.h +++ b/plugins/channelrx/udpsink/udpsink.h @@ -45,23 +45,23 @@ class UDPSink : public BasebandSampleSink, public ChannelSinkAPI { Q_OBJECT public: - class MsgConfigureUDPSrc : public Message { + class MsgConfigureUDPSource : public Message { MESSAGE_CLASS_DECLARATION public: const UDPSinkSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureUDPSrc* create(const UDPSinkSettings& settings, bool force) + static MsgConfigureUDPSource* create(const UDPSinkSettings& settings, bool force) { - return new MsgConfigureUDPSrc(settings, force); + return new MsgConfigureUDPSource(settings, force); } private: UDPSinkSettings m_settings; bool m_force; - MsgConfigureUDPSrc(const UDPSinkSettings& settings, bool force) : + MsgConfigureUDPSource(const UDPSinkSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -136,21 +136,21 @@ public slots: void audioReadyRead(); protected: - class MsgUDPSrcSpectrum : public Message { + class MsgUDPSinkSpectrum : public Message { MESSAGE_CLASS_DECLARATION public: bool getEnabled() const { return m_enabled; } - static MsgUDPSrcSpectrum* create(bool enabled) + static MsgUDPSinkSpectrum* create(bool enabled) { - return new MsgUDPSrcSpectrum(enabled); + return new MsgUDPSinkSpectrum(enabled); } private: bool m_enabled; - MsgUDPSrcSpectrum(bool enabled) : + MsgUDPSinkSpectrum(bool enabled) : Message(), m_enabled(enabled) { } diff --git a/plugins/channelrx/udpsink/udpsinkgui.cpp b/plugins/channelrx/udpsink/udpsinkgui.cpp index dc457b89b..eed9fcb54 100644 --- a/plugins/channelrx/udpsink/udpsinkgui.cpp +++ b/plugins/channelrx/udpsink/udpsinkgui.cpp @@ -90,9 +90,9 @@ bool UDPSinkGUI::deserialize(const QByteArray& data) bool UDPSinkGUI::handleMessage(const Message& message ) { - if (UDPSink::MsgConfigureUDPSrc::match(message)) + if (UDPSink::MsgConfigureUDPSource::match(message)) { - const UDPSink::MsgConfigureUDPSrc& cfg = (UDPSink::MsgConfigureUDPSrc&) message; + const UDPSink::MsgConfigureUDPSource& cfg = (UDPSink::MsgConfigureUDPSource&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -391,7 +391,7 @@ void UDPSinkGUI::applySettingsImmediate(bool force) { if (m_doApplySettings) { - UDPSink::MsgConfigureUDPSrc* message = UDPSink::MsgConfigureUDPSrc::create( m_settings, force); + UDPSink::MsgConfigureUDPSource* message = UDPSink::MsgConfigureUDPSource::create( m_settings, force); m_udpSink->getInputMessageQueue()->push(message); } } @@ -404,7 +404,7 @@ void UDPSinkGUI::applySettings(bool force) m_settings.m_outputSampleRate, m_channelMarker.getCenterFrequency()); m_udpSink->getInputMessageQueue()->push(channelConfigMsg); - UDPSink::MsgConfigureUDPSrc* message = UDPSink::MsgConfigureUDPSrc::create( m_settings, force); + UDPSink::MsgConfigureUDPSource* message = UDPSink::MsgConfigureUDPSource::create( m_settings, force); m_udpSink->getInputMessageQueue()->push(message); ui->applyBtn->setEnabled(false); diff --git a/plugins/channeltx/udpsource/udpsource.cpp b/plugins/channeltx/udpsource/udpsource.cpp index 9e887963b..38b25e946 100644 --- a/plugins/channeltx/udpsource/udpsource.cpp +++ b/plugins/channeltx/udpsource/udpsource.cpp @@ -29,9 +29,9 @@ #include "udpsource.h" #include "udpsourcemsg.h" -MESSAGE_CLASS_DEFINITION(UDPSource::MsgConfigureUDPSink, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgConfigureUDPSource, Message) MESSAGE_CLASS_DEFINITION(UDPSource::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(UDPSource::MsgUDPSinkSpectrum, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgUDPSourceSpectrum, Message) MESSAGE_CLASS_DEFINITION(UDPSource::MsgResetReadIndex, Message) const QString UDPSource::m_channelIdURI = "sdrangel.channeltx.udpsource"; @@ -342,10 +342,10 @@ bool UDPSource::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureUDPSink::match(cmd)) + else if (MsgConfigureUDPSource::match(cmd)) { - MsgConfigureUDPSink& cfg = (MsgConfigureUDPSink&) cmd; - qDebug() << "UDPSource::handleMessage: MsgConfigureUDPSink"; + MsgConfigureUDPSource& cfg = (MsgConfigureUDPSource&) cmd; + qDebug() << "UDPSource::handleMessage: MsgConfigureUDPSource"; applySettings(cfg.getSettings(), cfg.getForce()); @@ -403,11 +403,11 @@ bool UDPSource::handleMessage(const Message& cmd) return true; } - else if (MsgUDPSinkSpectrum::match(cmd)) + else if (MsgUDPSourceSpectrum::match(cmd)) { - MsgUDPSinkSpectrum& spc = (MsgUDPSinkSpectrum&) cmd; + MsgUDPSourceSpectrum& spc = (MsgUDPSourceSpectrum&) cmd; m_spectrumEnabled = spc.getEnabled(); - qDebug() << "UDPSource::handleMessage: MsgUDPSinkSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; + qDebug() << "UDPSource::handleMessage: MsgUDPSourceSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; return true; } @@ -440,7 +440,7 @@ bool UDPSource::handleMessage(const Message& cmd) void UDPSource::setSpectrum(bool enabled) { - Message* cmd = MsgUDPSinkSpectrum::create(enabled); + Message* cmd = MsgUDPSourceSpectrum::create(enabled); getInputMessageQueue()->push(cmd); } @@ -584,14 +584,14 @@ bool UDPSource::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { - MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(m_settings, true); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); m_inputMessageQueue.push(msg); return true; } else { m_settings.resetToDefaults(); - MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(m_settings, true); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); m_inputMessageQueue.push(msg); return false; } @@ -684,12 +684,12 @@ int UDPSource::webapiSettingsPutPatch( m_inputMessageQueue.push(msgChan); } - MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(settings, force); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(settings, force); m_inputMessageQueue.push(msg); if (m_guiMessageQueue) // forward to GUI if any { - MsgConfigureUDPSink *msgToGUI = MsgConfigureUDPSink::create(settings, force); + MsgConfigureUDPSource *msgToGUI = MsgConfigureUDPSource::create(settings, force); m_guiMessageQueue->push(msgToGUI); } diff --git a/plugins/channeltx/udpsource/udpsource.h b/plugins/channeltx/udpsource/udpsource.h index 47c7d1f27..809834f82 100644 --- a/plugins/channeltx/udpsource/udpsource.h +++ b/plugins/channeltx/udpsource/udpsource.h @@ -39,23 +39,23 @@ class UDPSource : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: - class MsgConfigureUDPSink : public Message { + class MsgConfigureUDPSource : public Message { MESSAGE_CLASS_DECLARATION public: const UDPSourceSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureUDPSink* create(const UDPSourceSettings& settings, bool force) + static MsgConfigureUDPSource* create(const UDPSourceSettings& settings, bool force) { - return new MsgConfigureUDPSink(settings, force); + return new MsgConfigureUDPSource(settings, force); } private: UDPSourceSettings m_settings; bool m_force; - MsgConfigureUDPSink(const UDPSourceSettings& settings, bool force) : + MsgConfigureUDPSource(const UDPSourceSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -139,21 +139,21 @@ signals: void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples); private: - class MsgUDPSinkSpectrum : public Message { + class MsgUDPSourceSpectrum : public Message { MESSAGE_CLASS_DECLARATION public: bool getEnabled() const { return m_enabled; } - static MsgUDPSinkSpectrum* create(bool enabled) + static MsgUDPSourceSpectrum* create(bool enabled) { - return new MsgUDPSinkSpectrum(enabled); + return new MsgUDPSourceSpectrum(enabled); } private: bool m_enabled; - MsgUDPSinkSpectrum(bool enabled) : + MsgUDPSourceSpectrum(bool enabled) : Message(), m_enabled(enabled) { } diff --git a/plugins/channeltx/udpsource/udpsourcegui.cpp b/plugins/channeltx/udpsource/udpsourcegui.cpp index 3e8edbea3..fefa33893 100644 --- a/plugins/channeltx/udpsource/udpsourcegui.cpp +++ b/plugins/channeltx/udpsource/udpsourcegui.cpp @@ -86,9 +86,9 @@ bool UDPSourceGUI::deserialize(const QByteArray& data) bool UDPSourceGUI::handleMessage(const Message& message) { - if (UDPSource::MsgConfigureUDPSink::match(message)) + if (UDPSource::MsgConfigureUDPSource::match(message)) { - const UDPSource::MsgConfigureUDPSink& cfg = (UDPSource::MsgConfigureUDPSink&) message; + const UDPSource::MsgConfigureUDPSource& cfg = (UDPSource::MsgConfigureUDPSource&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -199,7 +199,7 @@ void UDPSourceGUI::applySettings(bool force) m_settings.m_inputFrequencyOffset); m_udpSource->getInputMessageQueue()->push(msgChan); - UDPSource::MsgConfigureUDPSink* message = UDPSource::MsgConfigureUDPSink::create( m_settings, force); + UDPSource::MsgConfigureUDPSource* message = UDPSource::MsgConfigureUDPSource::create( m_settings, force); m_udpSource->getInputMessageQueue()->push(message); ui->applyBtn->setEnabled(false); From 05072ce4b99a48f1523d2b7c7543d586ac49dcb6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 12 Sep 2018 15:50:40 +0200 Subject: [PATCH 747/956] Windows buiid: fixed UDP channel plugins pro files --- .../udpsource/{udpsink.pro => udpsource.pro} | 28 +++++++++---------- sdrangel.windows.pro | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) rename plugins/channeltx/udpsource/{udpsink.pro => udpsource.pro} (71%) diff --git a/plugins/channeltx/udpsource/udpsink.pro b/plugins/channeltx/udpsource/udpsource.pro similarity index 71% rename from plugins/channeltx/udpsource/udpsink.pro rename to plugins/channeltx/udpsource/udpsource.pro index 6d93b1ddb..835fb8fe8 100644 --- a/plugins/channeltx/udpsource/udpsink.pro +++ b/plugins/channeltx/udpsource/udpsource.pro @@ -9,7 +9,7 @@ CONFIG += plugin QT += core gui widgets multimedia opengl network -TARGET = udpsink +TARGET = udpsource DEFINES += USE_SSE2=1 QMAKE_CXXFLAGS += -msse2 @@ -26,21 +26,21 @@ INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -SOURCES += udpsink.cpp\ - udpsinkgui.cpp\ - udpsinkplugin.cpp\ - udpsinkmsg.cpp\ - udpsinkudphandler.cpp\ - udpsinksettings.cpp +SOURCES += udpsource.cpp\ + udpsourcegui.cpp\ + udpsourceplugin.cpp\ + udpsourcemsg.cpp\ + udpsourceudphandler.cpp\ + udpsourcesettings.cpp -HEADERS += udpsink.h\ - udpsinkgui.h\ - udpsinkplugin.h\ - udpsinkmsg.h\ - udpsinkudphandler.h\ - udpsinksettings.h +HEADERS += udpsource.h\ + udpsourcegui.h\ + udpsourceplugin.h\ + udpsourcemsg.h\ + udpsourceudphandler.h\ + udpsourcesettings.h -FORMS += udpsinkgui.ui +FORMS += udpsourcegui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index f73b451ea..344da94a0 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -54,12 +54,12 @@ SUBDIRS += plugins/channelrx/demodlora SUBDIRS += plugins/channelrx/demodnfm SUBDIRS += plugins/channelrx/demodssb SUBDIRS += plugins/channelrx/demodwfm -SUBDIRS += plugins/channelrx/udpsrc +SUBDIRS += plugins/channelrx/udpsource SUBDIRS += plugins/channeltx/modam SUBDIRS += plugins/channeltx/modnfm SUBDIRS += plugins/channeltx/modssb SUBDIRS += plugins/channeltx/modwfm -SUBDIRS += plugins/channeltx/udpsink +SUBDIRS += plugins/channeltx/udpsource # Main app must be last SUBDIRS += app From 5e588ae09eacfd005bf019516492e2dbae45f404 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 13 Sep 2018 00:31:49 +0200 Subject: [PATCH 748/956] SDRDaemon: cleanup on UDP Tx side to use sdrdaemondatablock.h definitions and Rx sample size --- plugins/channelrx/daemonsink/daemonsink.cpp | 9 +- plugins/channelrx/daemonsink/daemonsink.h | 1 - .../channelrx/daemonsink/daemonsinkthread.cpp | 2 + .../channelrx/daemonsink/daemonsinkthread.h | 1 + .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 3 +- .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 112 +++++---------- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 127 +++++++++--------- sdrbase/channel/sdrdaemondatablock.h | 8 +- 8 files changed, 114 insertions(+), 149 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 2b69618dc..ac5dfd02e 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -52,7 +52,6 @@ DaemonSink::DaemonSink(DeviceSourceAPI *deviceAPI) : m_dataBlock(0), m_centerFrequency(0), m_sampleRate(48000), - m_sampleBytes(SDR_RX_SAMP_SZ <= 16 ? 2 : 4), m_nbBlocksFEC(0), m_txDelay(35), m_dataAddress("127.0.0.1"), @@ -115,7 +114,7 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec metaData.m_centerFrequency = m_centerFrequency; metaData.m_sampleRate = m_sampleRate; - metaData.m_sampleBytes = m_sampleBytes & 0xF; + metaData.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); metaData.m_sampleBits = SDR_RX_SAMP_SZ; metaData.m_nbOriginalBlocks = SDRDaemonNbOrginalBlocks; metaData.m_nbFECBlocks = m_nbBlocksFEC; @@ -133,6 +132,8 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec 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; memcpy((void *) &superBlock.m_protectedBlock, (const void *) &metaData, sizeof(SDRDaemonMetaDataFEC)); if (!(metaData == m_currentMetaFEC)) @@ -154,7 +155,7 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec } // block zero // handle different sample sizes... - int samplesPerBlock = SDRDaemonNbBytesPerBlock / sizeof(Sample); + int samplesPerBlock = SDRDaemonNbBytesPerBlock / (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)], @@ -173,6 +174,8 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec 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 == SDRDaemonNbOrginalBlocks - 1) // frame complete diff --git a/plugins/channelrx/daemonsink/daemonsink.h b/plugins/channelrx/daemonsink/daemonsink.h index fcadb413a..b50986c48 100644 --- a/plugins/channelrx/daemonsink/daemonsink.h +++ b/plugins/channelrx/daemonsink/daemonsink.h @@ -144,7 +144,6 @@ private: uint64_t m_centerFrequency; uint32_t m_sampleRate; - uint8_t m_sampleBytes; int m_nbBlocksFEC; int m_txDelay; QString m_dataAddress; diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.cpp b/plugins/channelrx/daemonsink/daemonsinkthread.cpp index 423e17454..ba0cc1ab8 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkthread.cpp @@ -132,6 +132,8 @@ void DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) txBlockx[i].m_header.m_frameIndex = frameIndex; txBlockx[i].m_header.m_blockIndex = i; + txBlockx[i].m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + txBlockx[i].m_header.m_sampleBits = SDR_RX_SAMP_SZ; descriptorBlocks[i].Block = (void *) &(txBlockx[i].m_protectedBlock); descriptorBlocks[i].Index = txBlockx[i].m_header.m_blockIndex; } diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.h b/plugins/channelrx/daemonsink/daemonsinkthread.h index 5e468f7a9..e87f31ce8 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.h +++ b/plugins/channelrx/daemonsink/daemonsinkthread.h @@ -69,6 +69,7 @@ private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; bool m_running; + uint8_t m_sampleBytes; CM256 m_cm256; CM256 *m_cm256p; diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index d39d3b406..c2607530f 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -38,6 +38,7 @@ #include "device/devicesinkapi.h" #include "device/deviceuiset.h" +#include "channel/sdrdaemondatablock.h" #include "udpsinkfec.h" #include "sdrdaemonsinkgui.h" @@ -215,7 +216,7 @@ void SDRdaemonSinkGui::updateSampleRate() void SDRdaemonSinkGui::updateTxDelayTooltip() { - int samplesPerBlock = UDPSinkFEC::bytesPerBlock / (SDR_TX_SAMP_SZ <= 16 ? 4 : 8); + int samplesPerBlock = SDRDaemonNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8); double delay = ((127*samplesPerBlock*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(delay*1e6, 'f', 0))); } diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index b8fd17009..77984afe2 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -29,8 +29,6 @@ MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) UDPSinkFEC::UDPSinkFEC() : m_sampleRate(48000), - m_sampleBytes(SDR_TX_SAMP_SZ <= 16 ? 2 : 4), - m_sampleBits(SDR_TX_SAMP_SZ), m_nbSamples(0), m_nbBlocksFEC(0), m_txDelayRatio(0.0), @@ -40,8 +38,8 @@ UDPSinkFEC::UDPSinkFEC() : m_frameCount(0), m_sampleIndex(0) { - memset((char *) m_txBlocks, 0, 4*256*sizeof(SuperBlock)); - memset((char *) &m_superBlock, 0, sizeof(SuperBlock)); + memset((char *) m_txBlocks, 0, 4*256*sizeof(SDRDaemonSuperBlock)); + memset((char *) &m_superBlock, 0, sizeof(SDRDaemonSuperBlock)); m_currentMetaFEC.init(); m_bufMeta = new uint8_t[m_udpSize]; m_buf = new uint8_t[m_udpSize]; @@ -74,7 +72,7 @@ void UDPSinkFEC::setTxDelay(float txDelayRatio) // divided by sample rate gives the frame process time // divided by the number of actual blocks including FEC blocks gives the block (i.e. UDP block) process time m_txDelayRatio = txDelayRatio; - int samplesPerBlock = bytesPerBlock / (m_sampleBytes*2); + int samplesPerBlock = SDRDaemonNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8); double delay = ((127*samplesPerBlock*txDelayRatio) / m_sampleRate)/(128 + m_nbBlocksFEC); m_txDelay = delay * 1e6; qDebug() << "UDPSinkFEC::setTxDelay: txDelay: " << txDelayRatio << " m_txDelay: " << m_txDelay << " us"; @@ -113,14 +111,14 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk if (m_txBlockIndex == 0) // Tx block index 0 is a block with only meta data { struct timeval tv; - MetaDataFEC metaData; + SDRDaemonMetaDataFEC metaData; gettimeofday(&tv, 0); metaData.m_centerFrequency = 0; // frequency not set by stream metaData.m_sampleRate = m_sampleRate; - metaData.m_sampleBytes = m_sampleBytes & 0xF; - metaData.m_sampleBits = m_sampleBits; + metaData.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + metaData.m_sampleBits = SDR_RX_SAMP_SZ; metaData.m_nbOriginalBlocks = m_nbOriginalBlocks; metaData.m_nbFECBlocks = m_nbBlocksFEC; metaData.m_tv_sec = tv.tv_sec; @@ -133,9 +131,11 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk memset((char *) &m_superBlock, 0, sizeof(m_superBlock)); - m_superBlock.header.frameIndex = m_frameCount; - m_superBlock.header.blockIndex = m_txBlockIndex; - memcpy((char *) &m_superBlock.protectedBlock, (const char *) &metaData, sizeof(MetaDataFEC)); + 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; + memcpy((char *) &m_superBlock.m_protectedBlock, (const char *) &metaData, sizeof(SDRDaemonMetaDataFEC)); if (!(metaData == m_currentMetaFEC)) { @@ -157,74 +157,28 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk m_txBlockIndex = 1; // next Tx block with data } - int samplesPerBlock = bytesPerBlock / (m_sampleBytes*2); + int samplesPerBlock = SDRDaemonNbBytesPerBlock / (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 { - if (sizeof(Sample) == m_sampleBytes*2) // can do direct copy if sample sizes are equal - { - memcpy((char *) &m_superBlock.protectedBlock.m_buf[m_sampleIndex*m_sampleBytes*2], - (const char *) &(*it), - inRemainingSamples * sizeof(Sample)); - } - else if ((sizeof(Sample) == 8) && (m_sampleBytes == 2)) // modulators produce 16 bit samples - { - for (int is = 0; is < inRemainingSamples; is++) - { - int16_t *rp = (int16_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2]); - int16_t *ip = (int16_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2+2]); - *rp = (it+is)->m_real & 0xFFFF; - *ip = (it+is)->m_imag & 0xFFFF; - } - } - else if ((sizeof(Sample) == 4) && (m_sampleBytes == 4)) // use 16 bit samples for Tx - { - for (int is = 0; is < inRemainingSamples; is++) - { - int32_t *rp = (int32_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2]); - int32_t *ip = (int32_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2+4]); - *rp = (it+is)->m_real; - *ip = (it+is)->m_imag; - } - } - + memcpy((char *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], + (const char *) &(*it), + 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 { - if (sizeof(Sample) == m_sampleBytes*2) // can do direct copy if sample sizes are equal - { - memcpy((char *) &m_superBlock.protectedBlock.m_buf[m_sampleIndex*m_sampleBytes*2], - (const char *) &(*it), - (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); - } - else if ((sizeof(Sample) == 8) && (m_sampleBytes == 2)) // modulators produce 16 bit samples - { - for (int is = 0; is < samplesPerBlock - m_sampleIndex; is++) - { - int16_t *rp = (int16_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2]); - int16_t *ip = (int16_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2+2]); - *rp = (it+is)->m_real & 0xFFFF; - *ip = (it+is)->m_imag & 0xFFFF; - } - } - else if ((sizeof(Sample) == 4) && (m_sampleBytes == 4)) // use 16 bit samples for Tx - { - for (int is = 0; is < samplesPerBlock - m_sampleIndex; is++) - { - int32_t *rp = (int32_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2]); - int32_t *ip = (int32_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2+4]); - *rp = (it+is)->m_real; - *ip = (it+is)->m_imag; - } - } - + memcpy((char *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], + (const char *) &(*it), + (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); it += samplesPerBlock - m_sampleIndex; m_sampleIndex = 0; - m_superBlock.header.frameIndex = m_frameCount; - m_superBlock.header.blockIndex = m_txBlockIndex; + 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_txBlocks[m_txBlocksIndex][m_txBlockIndex] = m_superBlock; if (m_txBlockIndex == m_nbOriginalBlocks - 1) // frame complete @@ -263,7 +217,7 @@ UDPSinkFECWorker::~UDPSinkFECWorker() m_inputMessageQueue.clear(); } -void UDPSinkFECWorker::pushTxFrame(UDPSinkFEC::SuperBlock *txBlocks, +void UDPSinkFECWorker::pushTxFrame(SDRDaemonSuperBlock *txBlocks, uint32_t nbBlocksFEC, uint32_t txDelay, uint16_t frameIndex) @@ -320,11 +274,11 @@ void UDPSinkFECWorker::handleInputMessages() } } -void UDPSinkFECWorker::encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay) +void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay) { CM256::cm256_encoder_params cm256Params; //!< Main interface with CM256 encoder CM256::cm256_block descriptorBlocks[256]; //!< Pointers to data for CM256 encoder - UDPSinkFEC::ProtectedBlock fecBlocks[256]; //!< FEC data + SDRDaemonProtectedBlock fecBlocks[256]; //!< FEC data if ((nbBlocksFEC == 0) || !m_cm256Valid) { @@ -339,7 +293,7 @@ void UDPSinkFECWorker::encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint1 } else { - cm256Params.BlockBytes = sizeof(UDPSinkFEC::ProtectedBlock); + cm256Params.BlockBytes = sizeof(SDRDaemonProtectedBlock); cm256Params.OriginalCount = UDPSinkFEC::m_nbOriginalBlocks; cm256Params.RecoveryCount = nbBlocksFEC; @@ -348,13 +302,15 @@ void UDPSinkFECWorker::encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint1 for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; ++i) { if (i >= cm256Params.OriginalCount) { - memset((char *) &txBlockx[i].protectedBlock, 0, sizeof(UDPSinkFEC::ProtectedBlock)); + memset((char *) &txBlockx[i].m_protectedBlock, 0, sizeof(SDRDaemonProtectedBlock)); } - txBlockx[i].header.frameIndex = frameIndex; - txBlockx[i].header.blockIndex = i; - descriptorBlocks[i].Block = (void *) &(txBlockx[i].protectedBlock); - descriptorBlocks[i].Index = txBlockx[i].header.blockIndex; + txBlockx[i].m_header.m_frameIndex = frameIndex; + txBlockx[i].m_header.m_blockIndex = i; + txBlockx[i].m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + txBlockx[i].m_header.m_sampleBits = SDR_RX_SAMP_SZ; + descriptorBlocks[i].Block = (void *) &(txBlockx[i].m_protectedBlock); + descriptorBlocks[i].Index = txBlockx[i].m_header.m_blockIndex; } // Encode FEC blocks @@ -367,7 +323,7 @@ void UDPSinkFECWorker::encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint1 // Merge FEC with data to transmit for (int i = 0; i < cm256Params.RecoveryCount; i++) { - txBlockx[i + cm256Params.OriginalCount].protectedBlock = fecBlocks[i]; + txBlockx[i + cm256Params.OriginalCount].m_protectedBlock = fecBlocks[i]; } // Transmit all blocks diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index f613fb7ed..172e07312 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -31,6 +31,7 @@ #include "util/CRC64.h" #include "util/messagequeue.h" #include "util/message.h" +#include "channel/sdrdaemondatablock.h" #include "UDPSocket.h" @@ -42,52 +43,52 @@ class UDPSinkFEC : public QObject public: static const uint32_t m_udpSize = 512; //!< Size of UDP block in number of bytes static const uint32_t m_nbOriginalBlocks = 128; //!< Number of original blocks in a protected block sequence -#pragma pack(push, 1) - struct MetaDataFEC - { - uint32_t m_centerFrequency; //!< 4 center frequency in kHz - uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample - uint8_t m_sampleBits; //!< 10 number of effective bits per sample - uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data - uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC - uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing - uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing - uint32_t m_crc32; //!< 24 CRC32 of the above - - bool operator==(const MetaDataFEC& rhs) - { - return (memcmp((const char *) this, (const char *) &rhs, 12) == 0); // Only the 12 first bytes are relevant - } - - void init() - { - memset((char *) this, 0, sizeof(MetaDataFEC)); - m_nbFECBlocks = -1; - } - }; - - struct Header - { - uint16_t frameIndex; - uint8_t blockIndex; - uint8_t filler; - uint32_t filler2; - }; - - static const int bytesPerBlock = m_udpSize - sizeof(Header); - - struct ProtectedBlock - { - uint8_t m_buf[bytesPerBlock]; - }; - - struct SuperBlock - { - Header header; - ProtectedBlock protectedBlock; - }; -#pragma pack(pop) +//#pragma pack(push, 1) +// struct MetaDataFEC +// { +// uint32_t m_centerFrequency; //!< 4 center frequency in kHz +// uint32_t m_sampleRate; //!< 8 sample rate in Hz +// uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample +// uint8_t m_sampleBits; //!< 10 number of effective bits per sample +// uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data +// uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC +// uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing +// uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing +// uint32_t m_crc32; //!< 24 CRC32 of the above +// +// bool operator==(const MetaDataFEC& rhs) +// { +// return (memcmp((const char *) this, (const char *) &rhs, 12) == 0); // Only the 12 first bytes are relevant +// } +// +// void init() +// { +// memset((char *) this, 0, sizeof(MetaDataFEC)); +// m_nbFECBlocks = -1; +// } +// }; +// +// struct Header +// { +// uint16_t frameIndex; +// uint8_t blockIndex; +// uint8_t filler; +// uint32_t filler2; +// }; +// +// static const int bytesPerBlock = m_udpSize - sizeof(Header); +// +// struct ProtectedBlock +// { +// uint8_t m_buf[bytesPerBlock]; +// }; +// +// struct SuperBlock +// { +// Header header; +// ProtectedBlock protectedBlock; +// }; +//#pragma pack(pop) /** * Construct UDP sink @@ -127,8 +128,6 @@ private: std::string m_error; uint32_t m_sampleRate; //!< sample rate in Hz - uint8_t m_sampleBytes; //!< number of bytes per sample - uint8_t m_sampleBits; //!< number of effective bits per sample uint32_t m_nbSamples; //!< total number of samples sent int the last frame QHostAddress m_ownAddress; @@ -137,16 +136,16 @@ private: uint8_t* m_bufMeta; uint8_t* m_buf; - MetaDataFEC m_currentMetaFEC; //!< Meta data for current frame - uint32_t m_nbBlocksFEC; //!< Variable number of FEC blocks - float m_txDelayRatio; //!< Delay in ratio of nominal frame period - uint32_t m_txDelay; //!< Delay in microseconds (usleep) between each sending of an UDP datagram - SuperBlock m_txBlocks[4][256]; //!< UDP blocks to send with original data + FEC - SuperBlock m_superBlock; //!< current super block being built - int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row - int m_txBlocksIndex; //!< Current index of Tx blocks row - uint16_t m_frameCount; //!< transmission frame count - int m_sampleIndex; //!< Current sample index in protected block data + SDRDaemonMetaDataFEC m_currentMetaFEC; //!< Meta data for current frame + uint32_t m_nbBlocksFEC; //!< Variable number of FEC blocks + float m_txDelayRatio; //!< Delay in ratio of nominal frame period + uint32_t m_txDelay; //!< Delay in microseconds (usleep) between each sending of an UDP datagram + SDRDaemonSuperBlock m_txBlocks[4][256]; //!< UDP blocks to send with original data + FEC + SDRDaemonSuperBlock m_superBlock; //!< current super block being built + int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row + int m_txBlocksIndex; //!< Current index of Tx blocks row + uint16_t m_frameCount; //!< transmission frame count + int m_sampleIndex; //!< Current sample index in protected block data QThread *m_udpThread; UDPSinkFECWorker *m_udpWorker; @@ -161,13 +160,13 @@ public: { MESSAGE_CLASS_DECLARATION public: - UDPSinkFEC::SuperBlock *getTxBlocks() const { return m_txBlockx; } + SDRDaemonSuperBlock *getTxBlocks() const { return m_txBlockx; } uint32_t getNbBlocsFEC() const { return m_nbBlocksFEC; } uint32_t getTxDelay() const { return m_txDelay; } uint16_t getFrameIndex() const { return m_frameIndex; } static MsgUDPFECEncodeAndSend* create( - UDPSinkFEC::SuperBlock *txBlocks, + SDRDaemonSuperBlock *txBlocks, uint32_t nbBlocksFEC, uint32_t txDelay, uint16_t frameIndex) @@ -176,13 +175,13 @@ public: } private: - UDPSinkFEC::SuperBlock *m_txBlockx; + SDRDaemonSuperBlock *m_txBlockx; uint32_t m_nbBlocksFEC; uint32_t m_txDelay; uint16_t m_frameIndex; MsgUDPFECEncodeAndSend( - UDPSinkFEC::SuperBlock *txBlocks, + SDRDaemonSuperBlock *txBlocks, uint32_t nbBlocksFEC, uint32_t txDelay, uint16_t frameIndex) : @@ -218,7 +217,7 @@ public: UDPSinkFECWorker(); ~UDPSinkFECWorker(); - void pushTxFrame(UDPSinkFEC::SuperBlock *txBlocks, + void pushTxFrame(SDRDaemonSuperBlock *txBlocks, uint32_t nbBlocksFEC, uint32_t txDelay, uint16_t frameIndex); @@ -237,7 +236,7 @@ private slots: void handleInputMessages(); private: - void encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay); + void encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay); volatile bool m_running; CM256 m_cm256; //!< CM256 library object diff --git a/sdrbase/channel/sdrdaemondatablock.h b/sdrbase/channel/sdrdaemondatablock.h index 75d2926b6..1b877f8f1 100644 --- a/sdrbase/channel/sdrdaemondatablock.h +++ b/sdrbase/channel/sdrdaemondatablock.h @@ -38,7 +38,7 @@ struct SDRDaemonMetaDataFEC { uint32_t m_centerFrequency; //!< 4 center frequency in kHz uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_sampleBytes; //!< 9 4 LSB: number of bytes per sample (2 or 3) + uint8_t m_sampleBytes; //!< 9 4 LSB: number of bytes per sample (2 or 4) uint8_t m_sampleBits; //!< 10 number of effective bits per sample (deprecated) uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC @@ -69,13 +69,17 @@ struct SDRDaemonHeader { uint16_t m_frameIndex; uint8_t m_blockIndex; + uint8_t m_sampleBytes; //!< number of bytes per sample (2 or 4) for this block + uint8_t m_sampleBits; //!< number of bits per sample uint8_t m_filler; - uint32_t m_filler2; + uint16_t m_filler2; void init() { m_frameIndex = 0; m_blockIndex = 0; + m_sampleBytes = 2; + m_sampleBits = 16; m_filler = 0; m_filler2 = 0; } From e78ee1b946ccc5880bcb34f43bf4c23ca789c743 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 13 Sep 2018 02:33:56 +0200 Subject: [PATCH 749/956] Make SDRDaemonSink -> DaemonSource work in all 16 / 24 bit samples combination --- .../channeltx/daemonsource/daemonsource.cpp | 3 +- sdrbase/channel/sdrdaemondatareadqueue.cpp | 17 ++++---- sdrbase/channel/sdrdaemondatareadqueue.h | 39 +++++++++++-------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/plugins/channeltx/daemonsource/daemonsource.cpp b/plugins/channeltx/daemonsource/daemonsource.cpp index 267095de7..d23e1e100 100644 --- a/plugins/channeltx/daemonsource/daemonsource.cpp +++ b/plugins/channeltx/daemonsource/daemonsource.cpp @@ -46,7 +46,6 @@ DaemonSource::DaemonSource(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_sourceThread(0), m_running(false), - m_dataReadQueue(SDR_TX_SAMP_SZ <= 16 ? 4 : 8), m_nbCorrectableErrors(0), m_nbUncorrectableErrors(0) { @@ -72,7 +71,7 @@ DaemonSource::~DaemonSource() void DaemonSource::pull(Sample& sample) { - m_dataReadQueue.readSample(sample); + m_dataReadQueue.readSample(sample, true); // true is scale for Tx } void DaemonSource::pullAudio(int nbSamples __attribute__((unused))) diff --git a/sdrbase/channel/sdrdaemondatareadqueue.cpp b/sdrbase/channel/sdrdaemondatareadqueue.cpp index c71158771..601a5f12a 100644 --- a/sdrbase/channel/sdrdaemondatareadqueue.cpp +++ b/sdrbase/channel/sdrdaemondatareadqueue.cpp @@ -25,12 +25,12 @@ const uint32_t SDRDaemonDataReadQueue::MinimumMaxSize = 10; -SDRDaemonDataReadQueue::SDRDaemonDataReadQueue(uint32_t sampleSize) : - m_sampleSize(sampleSize), +SDRDaemonDataReadQueue::SDRDaemonDataReadQueue() : m_dataBlock(0), m_maxSize(MinimumMaxSize), m_blockIndex(1), m_sampleIndex(0), + m_sampleCount(0), m_full(false) {} @@ -86,7 +86,7 @@ void SDRDaemonDataReadQueue::setSize(uint32_t size) } } -void SDRDaemonDataReadQueue::readSample(Sample& s) +void SDRDaemonDataReadQueue::readSample(Sample& s, bool scaleForTx) { // depletion/repletion state if (m_dataBlock == 0) @@ -96,7 +96,7 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) qDebug("SDRDaemonDataReadQueue::readSample: initial pop new block: queue size: %u", length()); m_blockIndex = 1; m_dataBlock = m_dataReadQueue.takeFirst(); - convertDataToSample(s, m_blockIndex, m_sampleIndex); + convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); m_sampleIndex++; m_sampleCount++; } @@ -108,11 +108,12 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) return; } - uint32_t samplesPerBlock = SDRDaemonNbBytesPerBlock / m_sampleSize; + int sampleSize = m_dataBlock->m_superBlocks[m_blockIndex].m_header.m_sampleBytes * 2; + uint32_t samplesPerBlock = SDRDaemonNbBytesPerBlock / sampleSize; if (m_sampleIndex < samplesPerBlock) { - convertDataToSample(s, m_blockIndex, m_sampleIndex); + convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); m_sampleIndex++; m_sampleCount++; } @@ -123,7 +124,7 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) if (m_blockIndex < SDRDaemonNbOrginalBlocks) { - convertDataToSample(s, m_blockIndex, m_sampleIndex); + convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); m_sampleIndex++; m_sampleCount++; } @@ -144,7 +145,7 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) //qDebug("SDRDaemonDataReadQueue::readSample: pop new block: queue size: %u", length()); m_blockIndex = 1; m_dataBlock = m_dataReadQueue.takeFirst(); - convertDataToSample(s, m_blockIndex, m_sampleIndex); + convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); m_sampleIndex++; m_sampleCount++; } diff --git a/sdrbase/channel/sdrdaemondatareadqueue.h b/sdrbase/channel/sdrdaemondatareadqueue.h index c20a8f9c6..ce120f8d2 100644 --- a/sdrbase/channel/sdrdaemondatareadqueue.h +++ b/sdrbase/channel/sdrdaemondatareadqueue.h @@ -31,12 +31,12 @@ class Sample; class SDRDaemonDataReadQueue { public: - SDRDaemonDataReadQueue(uint32_t sampleSize); + SDRDaemonDataReadQueue(); ~SDRDaemonDataReadQueue(); void push(SDRDaemonDataBlock* dataBlock); //!< push block on the queue SDRDaemonDataBlock* pop(); //!< Pop block from the queue - void readSample(Sample& s); //!< Read sample from queue + void readSample(Sample& s, bool scaleForTx = false); //!< Read sample from queue possibly scaling to Tx size uint32_t length() const { return m_dataReadQueue.size(); } //!< Returns queue length uint32_t size() const { return m_maxSize; } //!< Returns queue size (max length) void setSize(uint32_t size); //!< Sets the queue size (max length) @@ -45,7 +45,6 @@ public: static const uint32_t MinimumMaxSize; private: - uint32_t m_sampleSize; QQueue m_dataReadQueue; SDRDaemonDataBlock *m_dataBlock; uint32_t m_maxSize; @@ -54,25 +53,33 @@ private: uint32_t m_sampleCount; //!< use a counter capped below 2^31 as it is going to be converted to an int in the web interface bool m_full; //!< full condition was hit - inline void convertDataToSample(Sample& s, uint32_t blockIndex, uint32_t sampleIndex) + inline void convertDataToSample(Sample& s, uint32_t blockIndex, uint32_t sampleIndex, bool scaleForTx) { - if (sizeof(Sample) == m_sampleSize) + int sampleSize = m_dataBlock->m_superBlocks[blockIndex].m_header.m_sampleBytes * 2; // I/Q sample size in data block + int samplebits = m_dataBlock->m_superBlocks[blockIndex].m_header.m_sampleBits; // I or Q sample size in bits + int32_t iconv, qconv; + + if (sizeof(Sample) == sampleSize) // generally 16->16 or 24->24 bits { - s = *((Sample*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize])); + s = *((Sample*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize])); } - else if ((sizeof(Sample) == 4) && (m_sampleSize == 8)) + else if ((sizeof(Sample) == 4) && (sampleSize == 8)) // generally 24->16 bits { - int32_t rp = *( (int32_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize]) ); - int32_t ip = *( (int32_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize+4]) ); - s.setReal(rp>>8); - s.setImag(ip>>8); + iconv = ((int32_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize]))[0]; + qconv = ((int32_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize+4]))[0]; + iconv >>= scaleForTx ? (SDR_TX_SAMP_SZ-SDR_RX_SAMP_SZ) : (samplebits-SDR_RX_SAMP_SZ); + qconv >>= scaleForTx ? (SDR_TX_SAMP_SZ-SDR_RX_SAMP_SZ) : (samplebits-SDR_RX_SAMP_SZ); + s.setReal(iconv); + s.setImag(qconv); } - else if ((sizeof(Sample) == 8) && (m_sampleSize == 4)) + else if ((sizeof(Sample) == 8) && (sampleSize == 4)) // generally 16->24 bits { - int32_t rp = *( (int16_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize]) ); - int32_t ip = *( (int16_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize+2]) ); - s.setReal(rp<<8); - s.setImag(ip<<8); + iconv = ((int16_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize]))[0]; + qconv = ((int16_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize+2]))[0]; + iconv <<= scaleForTx ? (SDR_TX_SAMP_SZ-samplebits) : (SDR_RX_SAMP_SZ-samplebits); + qconv <<= scaleForTx ? (SDR_TX_SAMP_SZ-samplebits) : (SDR_RX_SAMP_SZ-samplebits); + s.setReal(iconv); + s.setImag(qconv); } else { From 0a2329ffe72d8606128e7e850de23fb1dfde9ce3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 13 Sep 2018 09:18:58 +0200 Subject: [PATCH 750/956] SDRDaemonSource: use global SDR data blocks definitions --- .../sdrdaemonsource/sdrdaemonsourcebuffer.cpp | 38 ++++---- .../sdrdaemonsource/sdrdaemonsourcebuffer.h | 90 +++++-------------- .../sdrdaemonsourceudphandler.cpp | 8 +- .../sdrdaemonsourceudphandler.h | 2 +- 4 files changed, 45 insertions(+), 93 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp index d3bafe007..ca30dfc14 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp @@ -50,8 +50,8 @@ SDRdaemonSourceBuffer::SDRdaemonSourceBuffer() : m_tvOut_sec = 0; m_tvOut_usec = 0; m_readNbBytes = 1; - m_paramsCM256.BlockBytes = sizeof(ProtectedBlock); // never changes - m_paramsCM256.OriginalCount = m_nbOriginalBlocks; // never changes + m_paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + m_paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes if (!m_cm256.isInitialized()) { m_cm256_OK = false; @@ -81,7 +81,7 @@ void SDRdaemonSourceBuffer::initDecodeAllSlots() m_decoderSlots[i].m_decoded = false; m_decoderSlots[i].m_metaRetrieved = false; resetOriginalBlocks(i); - memset((void *) m_decoderSlots[i].m_recoveryBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); + memset((void *) m_decoderSlots[i].m_recoveryBlocks, 0, SDRDaemonNbOrginalBlocks * sizeof(SDRDaemonProtectedBlock)); } } @@ -118,7 +118,7 @@ void SDRdaemonSourceBuffer::initDecodeSlot(int slotIndex) m_decoderSlots[slotIndex].m_metaRetrieved = false; resetOriginalBlocks(slotIndex); - memset((void *) m_decoderSlots[slotIndex].m_recoveryBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); + memset((void *) m_decoderSlots[slotIndex].m_recoveryBlocks, 0, SDRDaemonNbOrginalBlocks * sizeof(SDRDaemonProtectedBlock)); } void SDRdaemonSourceBuffer::initReadIndex() @@ -189,8 +189,8 @@ void SDRdaemonSourceBuffer::checkSlotData(int slotIndex) void SDRdaemonSourceBuffer::writeData(char *array) { - SuperBlock *superBlock = (SuperBlock *) array; - int frameIndex = superBlock->header.frameIndex; + SDRDaemonSuperBlock *superBlock = (SDRDaemonSuperBlock *) array; + int frameIndex = superBlock->m_header.m_frameIndex; int decoderIndex = frameIndex % nbDecoderSlots; // frame break @@ -213,9 +213,9 @@ void SDRdaemonSourceBuffer::writeData(char *array) // Block processing - if (m_decoderSlots[decoderIndex].m_blockCount < m_nbOriginalBlocks) // not enough blocks to decode -> store data + if (m_decoderSlots[decoderIndex].m_blockCount < SDRDaemonNbOrginalBlocks) // not enough blocks to decode -> store data { - int blockIndex = superBlock->header.blockIndex; + int blockIndex = superBlock->m_header.m_blockIndex; int blockCount = m_decoderSlots[decoderIndex].m_blockCount; int recoveryCount = m_decoderSlots[decoderIndex].m_recoveryCount; m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Index = blockIndex; @@ -225,14 +225,14 @@ void SDRdaemonSourceBuffer::writeData(char *array) m_decoderSlots[decoderIndex].m_metaRetrieved = true; } - if (blockIndex < m_nbOriginalBlocks) // original data + if (blockIndex < SDRDaemonNbOrginalBlocks) // original data { - m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) storeOriginalBlock(decoderIndex, blockIndex, superBlock->protectedBlock); + m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) storeOriginalBlock(decoderIndex, blockIndex, superBlock->m_protectedBlock); m_decoderSlots[decoderIndex].m_originalCount++; } else // recovery data { - m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount] = superBlock->protectedBlock; + m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount] = superBlock->m_protectedBlock; m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) &m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount]; m_decoderSlots[decoderIndex].m_recoveryCount++; } @@ -240,14 +240,14 @@ void SDRdaemonSourceBuffer::writeData(char *array) m_decoderSlots[decoderIndex].m_blockCount++; - if (m_decoderSlots[decoderIndex].m_blockCount == m_nbOriginalBlocks) // ready to decode + if (m_decoderSlots[decoderIndex].m_blockCount == SDRDaemonNbOrginalBlocks) // ready to decode { m_decoderSlots[decoderIndex].m_decoded = true; if (m_cm256_OK && (m_decoderSlots[decoderIndex].m_recoveryCount > 0)) // recovery data used => need to decode FEC { - m_paramsCM256.BlockBytes = sizeof(ProtectedBlock); // never changes - m_paramsCM256.OriginalCount = m_nbOriginalBlocks; // never changes + m_paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + m_paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes if (m_decoderSlots[decoderIndex].m_metaRetrieved) { m_paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; @@ -269,13 +269,13 @@ void SDRdaemonSourceBuffer::writeData(char *array) for (int ir = 0; ir < m_decoderSlots[decoderIndex].m_recoveryCount; ir++) // restore missing blocks { - int recoveryIndex = m_nbOriginalBlocks - m_decoderSlots[decoderIndex].m_recoveryCount + ir; + int recoveryIndex = SDRDaemonNbOrginalBlocks - m_decoderSlots[decoderIndex].m_recoveryCount + ir; int blockIndex = m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Index; - ProtectedBlock *recoveredBlock = (ProtectedBlock *) m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Block; + SDRDaemonProtectedBlock *recoveredBlock = (SDRDaemonProtectedBlock *) m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Block; if (blockIndex == 0) // first block with meta { - MetaDataFEC *metaData = (MetaDataFEC *) recoveredBlock; + SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) recoveredBlock; boost::crc_32_type crc32; crc32.process_bytes(metaData, 20); @@ -300,7 +300,7 @@ void SDRdaemonSourceBuffer::writeData(char *array) if (m_decoderSlots[decoderIndex].m_metaRetrieved) // block zero with its meta data has been received { - MetaDataFEC *metaData = getMetaData(decoderIndex); + SDRDaemonMetaDataFEC *metaData = getMetaData(decoderIndex); if (!(*metaData == m_currentMeta)) { @@ -435,7 +435,7 @@ uint8_t *SDRdaemonSourceBuffer::readData(int32_t length) } } -void SDRdaemonSourceBuffer::printMeta(const QString& header, MetaDataFEC *metaData) +void SDRdaemonSourceBuffer::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) { qDebug() << header << ": " << "|" << metaData->m_centerFrequency diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h index 5ac67e9fa..615cb2f5b 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h @@ -22,6 +22,7 @@ #include #include "cm256.h" #include "util/movingaverage.h" +#include "channel/sdrdaemondatablock.h" #define SDRDAEMONSOURCE_UDPSIZE 512 // UDP payload size @@ -31,54 +32,6 @@ class SDRdaemonSourceBuffer { public: -#pragma pack(push, 1) - struct MetaDataFEC - { - uint32_t m_centerFrequency; //!< 4 center frequency in kHz - uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample - uint8_t m_sampleBits; //!< 10 number of effective bits per sample - uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data - uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC - uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing - uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing - uint32_t m_crc32; //!< 24 CRC32 of the above - - bool operator==(const MetaDataFEC& rhs) - { - return (memcmp((const char *) this, (const char *) &rhs, 12) == 0); // Only the 12 first bytes are relevant - } - - void init() - { - memset((char *) this, 0, sizeof(MetaDataFEC)); - m_sampleBits = 16; // assume 16 bits samples to start with - m_sampleBytes = 2; - } - }; - - struct Header - { - uint16_t frameIndex; - uint8_t blockIndex; - uint8_t filler; - uint32_t filler2; - }; - - static const int framesSize = SDRDAEMONSOURCE_NBDECODERSLOTS * (SDRDAEMONSOURCE_NBORIGINALBLOCKS - 1) * (SDRDAEMONSOURCE_UDPSIZE - sizeof(Header)); - - struct ProtectedBlock - { - uint8_t buf[SDRDAEMONSOURCE_UDPSIZE - sizeof(Header)]; - }; - - struct SuperBlock - { - Header header; - ProtectedBlock protectedBlock; - }; -#pragma pack(pop) - SDRdaemonSourceBuffer(); ~SDRdaemonSourceBuffer(); @@ -88,7 +41,7 @@ public: uint8_t *readData(int32_t length); //!< Read data from buffer // meta data - const MetaDataFEC& getCurrentMeta() const { return m_currentMeta; } + const SDRDaemonMetaDataFEC& getCurrentMeta() const { return m_currentMeta; } // samples timestamp uint32_t getTVOutSec() const { return m_tvOut_sec; } @@ -152,8 +105,7 @@ public: } } - static const int m_udpPayloadSize = SDRDAEMONSOURCE_UDPSIZE; - static const int m_nbOriginalBlocks = SDRDAEMONSOURCE_NBORIGINALBLOCKS; + static const int framesSize = SDRDAEMONSOURCE_NBDECODERSLOTS * (SDRDaemonNbOrginalBlocks - 1) * SDRDaemonNbBytesPerBlock; private: static const int nbDecoderSlots = SDRDAEMONSOURCE_NBDECODERSLOTS; @@ -161,24 +113,24 @@ private: #pragma pack(push, 1) struct BufferFrame { - ProtectedBlock m_blocks[m_nbOriginalBlocks - 1]; + SDRDaemonProtectedBlock m_blocks[SDRDaemonNbOrginalBlocks - 1]; }; #pragma pack(pop) struct DecoderSlot { - ProtectedBlock m_blockZero; //!< First block of a frame. Has meta data. - ProtectedBlock m_originalBlocks[m_nbOriginalBlocks]; //!< Original blocks retrieved directly or by later FEC - ProtectedBlock m_recoveryBlocks[m_nbOriginalBlocks]; //!< Recovery blocks (FEC blocks) with max size - CM256::cm256_block m_cm256DescriptorBlocks[m_nbOriginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) - int m_blockCount; //!< number of blocks received for this frame - int m_originalCount; //!< number of original blocks received - int m_recoveryCount; //!< number of recovery blocks received - bool m_decoded; //!< true if decoded - bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved + SDRDaemonProtectedBlock m_blockZero; //!< First block of a frame. Has meta data. + SDRDaemonProtectedBlock m_originalBlocks[SDRDaemonNbOrginalBlocks]; //!< Original blocks retrieved directly or by later FEC + SDRDaemonProtectedBlock m_recoveryBlocks[SDRDaemonNbOrginalBlocks]; //!< Recovery blocks (FEC blocks) with max size + CM256::cm256_block m_cm256DescriptorBlocks[SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) + int m_blockCount; //!< number of blocks received for this frame + int m_originalCount; //!< number of original blocks received + int m_recoveryCount; //!< number of recovery blocks received + bool m_decoded; //!< true if decoded + bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved }; - MetaDataFEC m_currentMeta; //!< Stored current meta data + SDRDaemonMetaDataFEC m_currentMeta; //!< Stored current meta data CM256::cm256_encoder_params m_paramsCM256; //!< CM256 decoder parameters block DecoderSlot m_decoderSlots[nbDecoderSlots]; //!< CM256 decoding control/buffer slots BufferFrame m_frames[nbDecoderSlots]; //!< Samples buffer @@ -213,7 +165,7 @@ private: CM256 m_cm256; //!< CM256 library bool m_cm256_OK; //!< CM256 library initialized OK - inline ProtectedBlock* storeOriginalBlock(int slotIndex, int blockIndex, const ProtectedBlock& protectedBlock) + inline SDRDaemonProtectedBlock* storeOriginalBlock(int slotIndex, int blockIndex, const SDRDaemonProtectedBlock& protectedBlock) { if (blockIndex == 0) { // m_decoderSlots[slotIndex].m_originalBlocks[0] = protectedBlock; @@ -228,7 +180,7 @@ private: } } - inline ProtectedBlock& getOriginalBlock(int slotIndex, int blockIndex) + inline SDRDaemonProtectedBlock& getOriginalBlock(int slotIndex, int blockIndex) { if (blockIndex == 0) { // return m_decoderSlots[slotIndex].m_originalBlocks[0]; @@ -239,17 +191,17 @@ private: } } - inline MetaDataFEC *getMetaData(int slotIndex) + inline SDRDaemonMetaDataFEC *getMetaData(int slotIndex) { // return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_originalBlocks[0]; - return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_blockZero; + return (SDRDaemonMetaDataFEC *) &m_decoderSlots[slotIndex].m_blockZero; } inline void resetOriginalBlocks(int slotIndex) { // memset((void *) m_decoderSlots[slotIndex].m_originalBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); - memset((void *) &m_decoderSlots[slotIndex].m_blockZero, 0, sizeof(ProtectedBlock)); - memset((void *) m_frames[slotIndex].m_blocks, 0, (m_nbOriginalBlocks - 1) * sizeof(ProtectedBlock)); + memset((void *) &m_decoderSlots[slotIndex].m_blockZero, 0, sizeof(SDRDaemonProtectedBlock)); + memset((void *) m_frames[slotIndex].m_blocks, 0, (SDRDaemonNbOrginalBlocks - 1) * sizeof(SDRDaemonProtectedBlock)); } void initDecodeAllSlots(); @@ -258,7 +210,7 @@ private: void checkSlotData(int slotIndex); void initDecodeSlot(int slotIndex); - static void printMeta(const QString& header, MetaDataFEC *metaData); + static void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); }; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index 1eeebce59..57d88fdb0 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -56,7 +56,7 @@ SDRdaemonSourceUDPHandler::SDRdaemonSourceUDPHandler(SampleSinkFifo *sampleFifo, m_throttleToggle(false), m_autoCorrBuffer(true) { - m_udpBuf = new char[SDRdaemonSourceBuffer::m_udpPayloadSize]; + m_udpBuf = new char[SDRDaemonUdpSize]; #ifdef USE_INTERNAL_TIMER #warning "Uses internal timer" @@ -167,7 +167,7 @@ void SDRdaemonSourceUDPHandler::dataReadyRead() qint64 pendingDataSize = m_dataSocket->pendingDatagramSize(); m_udpReadBytes += m_dataSocket->readDatagram(&m_udpBuf[m_udpReadBytes], pendingDataSize, &m_remoteAddress, 0); - if (m_udpReadBytes == SDRdaemonSourceBuffer::m_udpPayloadSize) { + if (m_udpReadBytes == SDRDaemonUdpSize) { processData(); m_udpReadBytes = 0; } @@ -177,7 +177,7 @@ void SDRdaemonSourceUDPHandler::dataReadyRead() void SDRdaemonSourceUDPHandler::processData() { m_sdrDaemonBuffer.writeData(m_udpBuf); - const SDRdaemonSourceBuffer::MetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); + const SDRDaemonMetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); bool change = false; m_tv_sec = m_sdrDaemonBuffer.getTVOutSec(); @@ -263,7 +263,7 @@ void SDRdaemonSourceUDPHandler::tick() m_readLengthSamples += m_sdrDaemonBuffer.getRWBalanceCorrection(); } - const SDRdaemonSourceBuffer::MetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); + const SDRDaemonMetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); m_readLength = m_readLengthSamples * (metaData.m_sampleBytes & 0xF) * 2; if (SDR_RX_SAMP_SZ == metaData.m_sampleBits) // same sample size diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h index 44c26bbc8..6abc6b03f 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h @@ -43,7 +43,7 @@ public: void stop(); void configureUDPLink(const QString& address, quint16 port); void getRemoteAddress(QString& s) const { s = m_remoteAddress.toString(); } - int getNbOriginalBlocks() const { return SDRdaemonSourceBuffer::m_nbOriginalBlocks; } + int getNbOriginalBlocks() const { return SDRDaemonNbOrginalBlocks; } bool isStreaming() const { return m_masterTimerConnected; } int getSampleRate() const { return m_samplerate; } int getCenterFrequency() const { return m_centerFrequency * 1000; } From a07f01b0211e9829dc87c0d4b7cd1ebf22e9bb9e Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 13 Sep 2018 14:58:35 +0200 Subject: [PATCH 751/956] SDRDaemonSource: allow zero frequency and correct some calculations based on meta data --- .../sdrdaemonsource/sdrdaemonsourcebuffer.cpp | 89 +++---------------- .../sdrdaemonsource/sdrdaemonsourcebuffer.h | 1 - .../sdrdaemonsourceudphandler.cpp | 2 +- 3 files changed, 12 insertions(+), 80 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp index ca30dfc14..c1cfe6476 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp @@ -182,6 +182,7 @@ void SDRdaemonSourceBuffer::checkSlotData(int slotIndex) if (!m_decoderSlots[slotIndex].m_decoded) { qDebug() << "SDRdaemonSourceBuffer::checkSlotData: incomplete frame:" + << " slotIndex: " << slotIndex << " m_blockCount: " << m_decoderSlots[slotIndex].m_blockCount << " m_recoveryCount: " << m_decoderSlots[slotIndex].m_recoveryCount; } @@ -258,12 +259,16 @@ void SDRdaemonSourceBuffer::writeData(char *array) if (m_cm256.cm256_decode(m_paramsCM256, m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks)) // CM256 decode { qDebug() << "SDRdaemonSourceBuffer::writeData: decode CM256 error:" + << " decoderIndex: " << decoderIndex + << " m_blockCount: " << m_decoderSlots[decoderIndex].m_blockCount << " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount << " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount; } else { qDebug() << "SDRdaemonSourceBuffer::writeData: decode CM256 success:" + << " decoderIndex: " << decoderIndex + << " m_blockCount: " << m_decoderSlots[decoderIndex].m_blockCount << " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount << " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount; @@ -296,7 +301,7 @@ void SDRdaemonSourceBuffer::writeData(char *array) qDebug() << "SDRdaemonSourceBuffer::writeData: recovered block #" << blockIndex; } // restore missing blocks } // CM256 decode - } // revovery + } // recovery if (m_decoderSlots[decoderIndex].m_metaRetrieved) // block zero with its meta data has been received { @@ -304,12 +309,13 @@ void SDRdaemonSourceBuffer::writeData(char *array) if (!(*metaData == m_currentMeta)) { - int sampleRate = metaData->m_sampleRate; + uint32_t sampleRate = metaData->m_sampleRate; - if (sampleRate > 0) { - m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * m_currentMeta.m_sampleBytes * 2); + if (sampleRate != 0) + { + m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * metaData->m_sampleBytes * 2); m_balCorrLimit = sampleRate / 1000; // +/- 1 ms correction max per read - m_readNbBytes = (sampleRate * m_currentMeta.m_sampleBytes * 2) / 20; + m_readNbBytes = (sampleRate * metaData->m_sampleBytes * 2) / 20; } printMeta("SDRdaemonSourceBuffer::writeData: new meta", metaData); // print for change other than timestamp @@ -320,79 +326,6 @@ void SDRdaemonSourceBuffer::writeData(char *array) } // decode } -void SDRdaemonSourceBuffer::writeData0(char *array __attribute__((unused)), uint32_t length __attribute__((unused))) -{ -// Kept as comments for the out of sync blocks algorithms -// assert(length == m_udpPayloadSize); -// -// bool dataAvailable = false; -// SuperBlock *superBlock = (SuperBlock *) array; -// int frameIndex = superBlock->header.frameIndex; -// int decoderIndex = frameIndex % nbDecoderSlots; -// int blockIndex = superBlock->header.blockIndex; -// -//// qDebug() << "SDRdaemonSourceBuffer::writeData:" -//// << " frameIndex: " << frameIndex -//// << " decoderIndex: " << decoderIndex -//// << " blockIndex: " << blockIndex; -// -// if (m_frameHead == -1) // initial state -// { -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// initReadIndex(); // reset read index -// initDecodeAllSlots(); // initialize all slots -// } -// else -// { -// int frameDelta = m_frameHead - frameIndex; -// -// if (frameDelta < 0) -// { -// if (-frameDelta < nbDecoderSlots) // new frame head not too new -// { -// //qDebug() << "SDRdaemonSourceBuffer::writeData: new frame head (1): " << frameIndex << ":" << frameDelta << ":" << decoderIndex; -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// checkSlotData(decoderIndex); -// dataAvailable = true; -// initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot -// } -// else if (-frameDelta <= 65536 - nbDecoderSlots) // loss of sync start over -// { -// //qDebug() << "SDRdaemonSourceBuffer::writeData: loss of sync start over (1)" << frameIndex << ":" << frameDelta << ":" << decoderIndex; -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// initReadIndex(); // reset read index -// initDecodeAllSlots(); // re-initialize all slots -// } -// } -// else -// { -// if (frameDelta > 65536 - nbDecoderSlots) // new frame head not too new -// { -// //qDebug() << "SDRdaemonSourceBuffer::writeData: new frame head (2): " << frameIndex << ":" << frameDelta << ":" << decoderIndex; -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// checkSlotData(decoderIndex); -// dataAvailable = true; -// initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot -// } -// else if (frameDelta >= nbDecoderSlots) // loss of sync start over -// { -// //qDebug() << "SDRdaemonSourceBuffer::writeData: loss of sync start over (2)" << frameIndex << ":" << frameDelta << ":" << decoderIndex; -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// initReadIndex(); // reset read index -// initDecodeAllSlots(); // re-initialize all slots -// } -// } -// } -// -// // decoderIndex should now be correctly set -// -} - uint8_t *SDRdaemonSourceBuffer::readData(int32_t length) { uint8_t *buffer = (uint8_t *) m_frames; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h index 615cb2f5b..405ad38ce 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h @@ -37,7 +37,6 @@ public: // R/W operations void writeData(char *array); //!< Write data into buffer. - void writeData0(char *array, uint32_t length); //!< Write data into buffer. uint8_t *readData(int32_t length); //!< Read data from buffer // meta data diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index 57d88fdb0..399ca525e 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -195,7 +195,7 @@ void SDRdaemonSourceUDPHandler::processData() change = true; } - if (change && (m_samplerate != 0) && (m_centerFrequency != 0)) + if (change && (m_samplerate != 0)) { qDebug("SDRdaemonSourceUDPHandler::processData: m_samplerate: %u m_centerFrequency: %u kHz", m_samplerate, m_centerFrequency); From 39c553d763ded7b0b39566964f9290563ea56f64 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 13 Sep 2018 17:34:45 +0200 Subject: [PATCH 752/956] DSD demod: fixed highpass filter display in the UI --- plugins/channelrx/demoddsd/dsddemodgui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 8c37b095b..e299c5b5b 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -432,6 +432,7 @@ void DSDDemodGUI::displaySettings() ui->tdmaStereoSplit->setChecked(m_settings.m_tdmaStereo); ui->audioMute->setChecked(m_settings.m_audioMute); ui->symbolPLLLock->setChecked(m_settings.m_pllLock); + ui->highPassFilter->setChecked(m_settings.m_highPassFilter); ui->baudRate->setCurrentIndex(DSDDemodBaudRates::getRateIndex(m_settings.m_baudRate)); From 840bfcd01070cab231c8f4eb6824a2d7929eaad4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 13 Sep 2018 18:31:27 +0200 Subject: [PATCH 753/956] Audio dialog doc: fixed numbering typo --- sdrgui/audio.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/sdrgui/audio.md b/sdrgui/audio.md index b17d21fb8..1b17e4653 100644 --- a/sdrgui/audio.md +++ b/sdrgui/audio.md @@ -24,9 +24,9 @@ In this column there are two indicators: - Copy audio to UDP: unchecked (false) - UDP copy channel mode: mono left channel (Left) - Use RTP protocol: unchecked (false) - + A unset indicator is marked with an underscore character: `_` - +

    1.2 Device name

    This is the device name defined in the system. In Linux when you define virtual devices (null sinks) with Pulseaudio this is the name you have given when defining the device. @@ -41,7 +41,7 @@ The device currently selected is marked in the selection color (orange). The par

    1.5 Sample rate

    -This is the device sample rate in samples per second (S/s). +This is the device sample rate in samples per second (S/s).

    1.6 Reset values to defaults

    @@ -62,10 +62,10 @@ Use this button to activate or de-activate the copy of the audio stream to UDP s

    1.10 UDP copy channel mode

    - `Left`: UDP stream is mono (1 channel) and the left audio channel is copied - - `Right`: UDP stream is mono (1 channel) and the right audio channel is copied + - `Right`: UDP stream is mono (1 channel) and the right audio channel is copied - `Mixed`: UDP stream is mono (1 channel) and the mix of left and right audio channels is copied - `Stereo`: UDP stream is stereo (2 channels) and audio channels are copied to their UDP channel counterparts respectively - +

    1.11 Use RTP protocol over UDP

    Check this box to activate the RTP protocol over UDP. RTP parameters are as follows: @@ -75,12 +75,12 @@ Check this box to activate the RTP protocol over UDP. RTP parameters are as foll - Sample format: 16 bit integer signed (S16LE) - Channels: 1 for mono (Left, Right and Mixed copy channels mode); 2 for stereo (Stereo copy channels mode) - Address and port: destination address and port (local on the client machine) - + You may read the RTP stream using a SDP file (extension `.sdp`) that can be read with any program supporting SDP files (VLC, MX player, ffmpeg, ...). For a mono 48000 S/s stream at address `192.168.0.34:9998` the contents of the file would be as follows: ``` c=IN IP4 192.168.0.34 -m=audio 9998 RTP/AVP 96 +m=audio 9998 RTP/AVP 96 a=rtpmap:96 L16/48000/1 ``` @@ -90,15 +90,15 @@ a=rtpmap:96 L16/48000/1 Use this button to keep only the visible devices in the devices registrations. The devices registrations with custom parameters are kept in the preferences using the device names. This button makes some tidying up when devices are permanently removed. -

    1.14 Unregister device

    +

    1.13 Unregister device

    Use this button to remove the device from the devices registrations returning it to the unregistered state. Therefore when associated to an output stream or selected it will initially take default values and appear with the `D` indicator in the list. -

    1.15 OK button

    +

    1.14 OK button

    Use this button to confirm your changes and close dialog. Note that you can change parameters of only one device at a time. -

    1.16 Cancel button

    +

    1.15 Cancel button

    Use this button to dismiss your changes and close dialog. @@ -119,9 +119,9 @@ In this column there are two indicators: - `D`: the device is unregistered so if you associate an input stream to it it will be registered with default values. Default values are: - Sample rate: 48000 S/s - Volume: 0.15 - + A unset indicator is marked with an underscore character: `_` - +

    2.2 Device name

    This is the device name defined in the system. In Linux when you define virtual devices (null sinks) with Pulseaudio an input device is automatically created with the `.monitor` extension. @@ -136,7 +136,7 @@ The device currently selected is marked in the selection color (orange). The par

    2.5 Sample rate

    -This is the device sample rate in samples per second (S/s). +This is the device sample rate in samples per second (S/s).

    2.6 Input volume

    @@ -177,9 +177,9 @@ In this column there are two indicators: - `S`: for system default device. This is the device that is defined as system default. You may configure it directly or via the ` System default device` entry.
    ☞ Note that (at least in Linux) you may affect different parameters to one or the other. - `D`: the device is unregistered so if you associate an input stream to it it will be registered with default values. Default values depend on the input or output nature and are listed in the 2.1 and 1.1 sections respectively. - + A unset indicator is marked with an underscore character: `_` - +

    3.2 Device name

    This is the device name defined in the system. @@ -209,5 +209,5 @@ Use this button to confirm your selection and close dialog. Use this button to dismiss your selection and close dialog. - + From c39ea3e4b754535f0be0458184e138d14aa8d984 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 15 Sep 2018 10:32:40 +0200 Subject: [PATCH 754/956] SDRDaemonSource: sends API info request when pushing API set button --- plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp | 4 ++++ .../samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 008b70319..2607ff590 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -320,6 +320,10 @@ void SDRdaemonSourceGui::on_apiApplyButton_clicked(bool checked __attribute__((u } sendSettings(); + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); } void SDRdaemonSourceGui::on_dataApplyButton_clicked(bool checked __attribute__((unused))) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index c4147fbeb..1c6d95891 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -224,11 +224,11 @@ void SDRdaemonSourceInput::applySettings(const SDRdaemonSourceSettings& settings settings.m_iqCorrection ? "true" : "false"); } - if (force || (m_settings.m_dataAddress != settings.m_dataAddress) || (m_settings.m_dataPort != settings.m_dataPort)) - { +// if (force || (m_settings.m_dataAddress != settings.m_dataAddress) || (m_settings.m_dataPort != settings.m_dataPort)) +// { m_SDRdaemonUDPHandler->configureUDPLink(settings.m_dataAddress, settings.m_dataPort); m_SDRdaemonUDPHandler->getRemoteAddress(remoteAddress); - } +// } mutexLocker.unlock(); m_settings = settings; From db77414aa05074db2b6f1d3143af857fe665e278 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 15 Sep 2018 10:33:04 +0200 Subject: [PATCH 755/956] SDRDaemonSource: updated documentation --- Readme.md | 4 - doc/img/SDRdaemonSource_plugin.png | Bin 36683 -> 29744 bytes doc/img/SDRdaemonSource_plugin_02.png | Bin 5841 -> 6121 bytes doc/img/SDRdaemonSource_plugin_04.png | Bin 8841 -> 7957 bytes doc/img/SDRdaemonSource_plugin_05.png | Bin 5983 -> 5458 bytes doc/img/SDRdaemonSource_plugin_06.png | Bin 0 -> 5057 bytes doc/img/SDRdaemonSource_plugin_06.xcf | Bin 0 -> 15754 bytes .../samplesource/sdrdaemonsource/readme.md | 82 +++++++----------- sdrsrv/readme.md | 6 +- 9 files changed, 36 insertions(+), 56 deletions(-) create mode 100644 doc/img/SDRdaemonSource_plugin_06.png create mode 100644 doc/img/SDRdaemonSource_plugin_06.xcf diff --git a/Readme.md b/Readme.md index 54c920605..a497a81c1 100644 --- a/Readme.md +++ b/Readme.md @@ -211,8 +211,6 @@ The [Test source plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/s

    SDRdaemon receiver input

    -Linux only. - The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of an instance of SDRangel running the Daemon Sink channel plugin. On the "Data" line you must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiving center frequency. The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. However it is used just to display basic information about the remote. The data blocks transmitted via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. @@ -225,8 +223,6 @@ Note that this plugin does not require any of the hardware support libraries nor

    SDRdaemon transmitter output

    -Linux only. - The [SDRdaemon sink output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) is the client side of and instance of SDRangel running the Daemon Source channel plugin. On the "Data" line you must specify the distant address and UDP port to which the plugin connects and samples from the SDRangel application will flow into the transmitter server (default is `127.0.0.1`port `9090`). The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. The API is pinged regularly to retrieve the status of the data blocks queue and allow rate control to stabilize the queue length. Therefore it is important to connect to the API properly (The status line must return "API OK" and the API label should be lit in green). The data blocks sent via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. diff --git a/doc/img/SDRdaemonSource_plugin.png b/doc/img/SDRdaemonSource_plugin.png index f435841f666d75b027df1f109e784c10347a3410..60ad568375a4f874f145914bb2c3b8bb44df98e6 100644 GIT binary patch literal 29744 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 literal 36683 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 diff --git a/doc/img/SDRdaemonSource_plugin_02.png b/doc/img/SDRdaemonSource_plugin_02.png index a9b63f0769acf9149892925ca125db32e58b57f5..5e350cecd31d7ca716927e4a1ca170b22aa205e8 100644 GIT binary patch literal 6121 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 Pn7Av+tIK_VY8w0>nWGsc literal 5841 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 diff --git a/doc/img/SDRdaemonSource_plugin_04.png b/doc/img/SDRdaemonSource_plugin_04.png index 8068428195089e0661846bda46dde4ee5801c647..2c2fd4bf6bcbb6fb5b2193f879c3769c440706b7 100644 GIT binary patch literal 7957 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 W$xY)NchDh%B>q80wCKII&wl{uFrEGY diff --git a/doc/img/SDRdaemonSource_plugin_05.png b/doc/img/SDRdaemonSource_plugin_05.png index 5a07c3a821be344d95e3d617703dd2d90ec4c242..a5ec1f341ed04606955514156c1f22542257d912 100644 GIT binary patch literal 5458 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 TP!_a|02{5))e(?zO*8ucgM^Y8 literal 0 HcmV?d00001 diff --git a/plugins/samplesource/sdrdaemonsource/readme.md b/plugins/samplesource/sdrdaemonsource/readme.md index 91a27b9c7..465ced28c 100644 --- a/plugins/samplesource/sdrdaemonsource/readme.md +++ b/plugins/samplesource/sdrdaemonsource/readme.md @@ -8,6 +8,8 @@ Forward Error Correction with a Cauchy MDS block erasure codec is used to preven Please note that there is no provision for handling out of sync UDP blocks. It is assumed that frames and block numbers always increase with possible blocks missing. Such out of sync situation has never been encountered in practice. +The distant SDRangel instance that sends the data stream is controlled via its REST API using a separate control software for example [SDRangelcli](https://github.com/f4exb/sdrangelcli) +

    Build

    The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. @@ -41,7 +43,7 @@ Stream I/Q sample rate in kS/s

    2: Auto correction options and stream status

    -![SDR Daemon source input AC and stream1 GUI](../../../doc/img/SDRdaemonSource_plugin_02.png) +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_02.png)

    2.1: Auto correction options

    @@ -76,20 +78,22 @@ There are two gauges separated by a dot in the center. Ideally these gauges shou The system tries to compensate read / write unbalance however at start or when a large stream disruption has occurred a delay of a few tens of seconds is necessary before read / write reaches equilibrium. -

    4: Forward Error Correction setting and status

    +

    4: Data stream status

    -![SDR Daemon source input FEC GUI](../../../doc/img/SDRdaemonSource_plugin_04.png) +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_04.png) -

    4.1: Desired number of FEC blocks per frame

    +

    4.1: Sample size

    -This is the number of FEC blocks per frame set by the user. A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. - -Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered. +This is the size in bits of a I or Q sample sent in the stream by the distant server.

    4.2: Total number of frames and number of FEC blocks

    This is the total number of frames and number of FEC blocks separated by a slash '/' as sent in the meta data block thus acknowledged by the distant server. When you set the number of FEC blocks with (4.1) the effect may not be immediate and this information can be used to monitor when it gets effectively set in the distant server. +A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. + +Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered. +

    4.3: Stream status

    The color of the icon indicates stream status: @@ -124,68 +128,46 @@ This counter counts the unrecoverable error conditions found (i.e. 4.4 between 1 This HH:mm:ss time display shows the time since the reset events counters button (4.6) was pushed. -

    5: Network parameters

    +

    5: Distant server API address and port

    -![SDR Daemon status3 GUI](../../../doc/img/SDRdaemonSource_plugin_05.png) +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_05.png) -

    5.1: Local interface IP address

    +

    5.1: API connection indicator

    -Address of the network interface on the local (your) machine to which the SDRdaemon Rx server sends samples to. +The "API" label is lit in green when the connection is successful -

    5.2: Local data port

    +

    5.2: API IP address

    -UDP port on the local (your) machine to which the SDRdaemon Rx server sends samples to. +IP address of the distant SDRangel instance REST API -

    5.3 Distant configuration port

    +

    5.3: API port

    -TCP port on the distant machine hosting the SDRdaemon Rx instance to send control messages to. The IP address of the host where the SDRdaemon instance runs is guessed from the address sending the data blocks hence the distant address does not need to be specified. +Port of the distant SDRangel instance REST API

    5.4: Validation button

    -When the return key is hit within the address (5.1), data port (5.2) or configuration port (5.3) boxes the changes are effective immediately. You can also use this button to set again these values. +When the return key is hit within the address (5.2) or port (5.3) the changes are effective immediately. You can also use this button to set again these values. Clicking on this button will send a request to the API to get the distant SDRangel instance information that is displayed in the API message box (8) -

    6: Desired center frequency

    +

    6: Local data address and port

    -This is the center frequency sent to the distant device. This becomes reflected in the main frequency dial (1.1) only when it gets acknowledged by the distant server and this frequency is sent back in the frames meta data. +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_06.png) -Use the wheels to adjust the frequency. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 0 Hz and the maximum value is 9.9 GHz. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. +

    6.1: Data IP address

    -

    7: Delay between UDP blocks transmission

    +IP address of the local network interface the distant SDRangel instance sends the data to -This sets the minimum delay between transmission of an UDP block (send datagram) and the next. This allows throttling of the UDP transmission that is otherwise uncontrolled and causes network congestion. +

    6.2: Data port

    -The value is a percentage of the nominal time it takes to process a block of samples corresponding to one UDP block (512 bytes). This is calculated as follows: +Local port the distant SDRangel instance sends the data to - - Sample rate on the network: _SR_ - - Delay percentage: _d_ - - Number of FEC blocks: _F_ - - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 8 bytes header (1 sample for 24 bit and 2 samples for 16 bit) thus there are 126 (16 bit) or 63 (24 bit) samples remaining effectively. - -Formula (16 bit): ((127 ✕ 126 ✕ _d_) / _SR_) / (128 + _F_) -Formula (24 bit): ((127 ✕ 63 ✕ _d_) / _SR_) / (128 + _F_) thus half the above +

    6.3: Validation button

    -

    8: Desired distant device sample rate

    +When the return key is hit within the address (5.2) or port (5.3) the changes are effective immediately. You can also use this button to set again these values. -This is the device sample rate sent to the distant device. It will be divided in the distant server by the decimation factor set with (9) to give the actual sample rate over the network. This becomes effective and displayed in (1.4) only when it gets acknowledged by the distant server and this sample rate is sent back in the frames meta data. +

    7: Status message

    -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 32 kS/s and the maximum value is 9.9 MS/s. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. +The API status is displayed in this box. It shows "API OK" when the connection is successful and reply is OK -

    9: Desired distant decimation factor

    +

    8: API information

    -This is the decimation factor to be set in the distant server downsampler. The hardware device sample rate is divided by this factor before the I/Q blocks are sent over the network. The actual network sample rate becomes effective and displayed in (1.4) only when it gets acknowledged by the distant server and this sample rate is sent back in the frames meta data. - -

    10: Center frequency position

    - -The center frequency in the passband will be set either: - - - below the local oscillator (NCO) or infradyne. Actually -1/4th the bandwidth. - - above the local oscillator (NCO) or supradyne. Actually +1/4th the bandwidth. - - centered on the local oscillator or zero IF. - -

    11: Other parameters hardware specific

    - -These are the parameters that are specific to the hardware attached to the distant SDRdaemon instance. You have to know which device is attached to send the proper parameters. Please refer to the SDRdaemon documentation or its line help to get information on these parameters. - -

    12: Send data to the distant SDRdaemon Rx instance

    - -When any of the parameters change they get immediately transmitted to the distant server over the TCP link. You can however use this button to send again the complete configuration. This is handy if for some reason you are unsure of the parameters set in the distant server. +This is the information returned by the API and is the distance SDRangel instance information if transaction is successful diff --git a/sdrsrv/readme.md b/sdrsrv/readme.md index f93b6fb92..80fbf8d6f 100644 --- a/sdrsrv/readme.md +++ b/sdrsrv/readme.md @@ -16,19 +16,21 @@ The main motivations are: - Rx channels: - AM demodulator - BFM (Broadcast FM) demodulator + - Daemon sink - DSD (Digital Vouice) demodulator - NFM (Narrowband FM) demodulator - SSB demodulator - WFM (Wideband FM) demodulator - - UDP source + - UDP sink - Tx channels: - AM modulator - ATV modulator + - Daemon source - NFM (Narrowband FM) modulator - SSB modulator - WFM (Wideband FM) modulator - - UDP sink + - UDP source - Sample sources: - Airspy From 69cb73900d42916dbf186ba23b165d53a5366112 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 15 Sep 2018 11:42:28 +0200 Subject: [PATCH 756/956] SDRDaemonSink: updated documentation --- doc/img/SDRdaemonSink_plugin.png | Bin 26007 -> 34890 bytes doc/img/SDRdaemonSink_plugin.xcf | Bin 101665 -> 127053 bytes doc/img/SDRdaemonSink_plugin_04.png | Bin 4908 -> 0 bytes doc/img/SDRdaemonSink_plugin_04.xcf | Bin 20183 -> 0 bytes doc/img/SDRdaemonSink_plugin_05.png | Bin 0 -> 7435 bytes doc/img/SDRdaemonSink_plugin_05.xcf | Bin 0 -> 29900 bytes doc/img/SDRdaemonSink_plugin_06.png | Bin 8319 -> 8175 bytes doc/img/SDRdaemonSink_plugin_06.xcf | Bin 33385 -> 31545 bytes doc/img/SDRdaemonSink_plugin_07.png | Bin 6105 -> 0 bytes doc/img/SDRdaemonSink_plugin_07.xcf | Bin 20668 -> 0 bytes plugins/samplesink/sdrdaemonsink/readme.md | 108 +++++++++++------- .../samplesource/sdrdaemonsource/readme.md | 4 +- 12 files changed, 72 insertions(+), 40 deletions(-) delete mode 100644 doc/img/SDRdaemonSink_plugin_04.png delete mode 100644 doc/img/SDRdaemonSink_plugin_04.xcf create mode 100644 doc/img/SDRdaemonSink_plugin_05.png create mode 100644 doc/img/SDRdaemonSink_plugin_05.xcf delete mode 100644 doc/img/SDRdaemonSink_plugin_07.png delete mode 100644 doc/img/SDRdaemonSink_plugin_07.xcf diff --git a/doc/img/SDRdaemonSink_plugin.png b/doc/img/SDRdaemonSink_plugin.png index e6ffbdf9c0abf3c53a827d86653604b435b3d6b6..f2d1d4fb7aee7b53ad3a9dd29189ddfce0a20dfa 100644 GIT binary patch literal 34890 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 literal 26007 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
    iVzwaI+(#nprfPH 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 E2bc25(*OVf literal 101665 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

    NSqNIWaZMte)D#(y%)Hl1pU7Xv` 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 diff --git a/doc/img/SDRdaemonSink_plugin_04.png b/doc/img/SDRdaemonSink_plugin_04.png deleted file mode 100644 index 356d141a42fe216f4e1fde2c60d690e32bcfe563..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4908 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 diff --git a/doc/img/SDRdaemonSink_plugin_04.xcf b/doc/img/SDRdaemonSink_plugin_04.xcf deleted file mode 100644 index d7e9e9360fd725545234496a088176800122954b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20183 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 diff --git a/doc/img/SDRdaemonSink_plugin_05.png b/doc/img/SDRdaemonSink_plugin_05.png new file mode 100644 index 0000000000000000000000000000000000000000..51b0180dc30d61515f62ed32f1c9c56b7e605b85 GIT binary patch literal 7435 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 literal 0 HcmV?d00001 diff --git a/doc/img/SDRdaemonSink_plugin_06.png b/doc/img/SDRdaemonSink_plugin_06.png index c69e557f0ffae2f23d9c2fc66aac7b30ceefd140..792aa82e76e25555296fb9f0ec6169a018c29caf 100644 GIT binary patch literal 8175 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 ANB{r; diff --git a/doc/img/SDRdaemonSink_plugin_06.xcf b/doc/img/SDRdaemonSink_plugin_06.xcf index 732c630d2450ee4e50d52ab6d953ba776f32510a..4d161ca7e461b62f51af4da3c7101a5e8eb1414e 100644 GIT binary patch literal 31545 zcmeHQ33yaRwyy3@SVIWAI4%JZAqymxgalcV5E4LPRAd(i1BBSh_8_dzm>x&Oab|qZ zCWR*n>kU-Y%d++AkOOEpd9 zTA+L%+#4mL+Q2cBCQL|}m;{g4z_=81{E!CG5j&T_|8d4ba=nv;CZ6UV%oFqPydjT<*LA<3yA zNmpbk99K~q2lX;sWvos#V_s)5B?lR6^)6$Nv}df%pBZcaFk=Dp7z^&tSU85h+hI&n zW5(j580){Du^DZZN@nd+D{F;2O_>4k@D2CHjTz|!R~>++RRZd%X)0G=P2mDUd5~n9kWoq6?1*3lDZ$ z8QIip)21ew$IbE`pD->7Rz?MoOtCJNlXaQhl9FOk(*~-w4ocKz4r|InIa!A}7NuA* z^(HHlOHnZ;HNBcb$IcQc8Bk4>JS9;ieI!>3iVBil&S=04BJ_};s9h3BJ0uOFSRf~% z-3`=01&|aZhnyruMKMcFI~dq~su%cGrJ~_;(-WrP0UZ?-PF7b#D+)CwU+z|v-)Wgp=cbZ&s*J~_E><3|oxlO$ zC)fPCNyx`)Rre#{b6^9o8;jT#EQ(ej9moQ{15N|iF#@wu&k$f7FdcXYm=7!mHc|b_ zKKB8Q;#gO!rwdG0&Wzwy?Ve0ENvKH&lZ2W;SM5$UiBXntizgE;QaI$*8`)WwCD`J? zljF4LU^2V=M50|LC1Wc}Zm^q)Ru){14_Q#O=$wT3)i3 zya80pW^Qd`dVB0DDQzeV*8Q}c>e{5vyj^WyejrGDGRJC~qKjB^gAvs*$%;lS*HdNt zEN{_Kc*I7qWQP9TM&w`^_I172VVLOBrW3>FUC#gY{*elld60FO zm?xS<@v7`7dNQF{luVK+sAQ2SiS~1afxM{N~n%)+j9&#;&xI~&8M#9*!F3h1GXH6LpLf@kZrT=-?SIi zX3HFOZa?Z&RYT|Y=NNOu{c?+1ci>ao=ryHMzuJ=(H+k=P(b2cO(J5v9_h7=~r)XMd z>R3hDlC68ahi99eJQVLKUGPEw6)_WYyes_?W1x{mYo+dgxm;u$1+6rmW4pARs!rBb zQ;;EDdCt3Od~r!r^vzlFV=0NoV4l=l&mE<4V|3gCpS<6mYt$sZxX8!J#p}&zn*OQI zmONR>b9)L3_6&0BQ(a%27Q>oa@u(^Ja<}5a*VeA47A2+k#hY63bdLol1AlbQrx;W- ztmw^rfgm6X!0ShI`0mXQ0Y3wXlr$F+H1Gxl1Uh~L9H3LMTvfHT z^5Ia2Mn?x~?k>>r7W1YJK-t}O&dHyHtlK%PI6(TjZ();tLFioFNz_Ke{arB+SF@3A ziJGOos;o=5PV4Q1(#c+1N%oGqtG5+OlPpbdvkC5z@;ttt5beR80X1pyDMfDkLia6MSz z2UhC`iqcM|>5XAm6zxDIjSS1>u}tMi4JhvnW}3U36T(DNy8< z80bVPYM~@LAyP9s7K)2P>2Eh>uU(Nx?ASvRQ&@8e54O_o@6<)$VWPTx#-6?>y@uV*oQ%JR$JIOEwXXT!KV=Q)tHD72S~e}WCJGT zA9GUWGEJ;-&YwDawHS0tn&B)1^%v7H>~ElB6*Zq_%P;tLc9}labu7ckkZb5wnwpX? zcZ(E#RjFQ&sNa9+9m_{b@O9bCfEk#k6x8w`9gZ(D!GClX@K<0VV~-sHE&@f2J&yJs z$J6}yQ$RQ(40y-%{RJpMU7kP-;7K41hy?~y{dePrIAt6Co;_l1>jIOtIUc~aKvnL| zvH77c33cgUl28Zes@;{k;6$`0r>+e-qpiuW)29^Aw3+JQwNXvHnR>t9bhC=1uXhqD zeHTumCflfrb8!1PbDisCuXzk@RBMiIJ*WEle*~T!wV|wQH8nfcoBb@j|0v(I>FrEs zd#XJqdwo=KcFWKHgY5W)`qJ`IQ)?>LH`W?(x2ED5sh;B(`rtTzH5ZMuIF4l8~B#$X)-(YhuV6GB}%pKW4#jt495 zm;0~_%fyPj&%p!H7c(*8lU2;X9D1gaiLGh6COco9!SQwF;LLztq;j&lK3^!=a80dG z)D~Ut)+Yq{w5$C=C8havm^Riv7e}N13-}WNzm6^~|EU3tJq3U8sR;n`JNyv=7`&Br z{2Q!Ito)h zQd5T|le`V`I^0=QhJsR%Coc$fz?Z9M2_;9a9<)(!iVNonzTzHKH6U}l@}6iqiup*L zbSU4&%IHptybbbBP`BRX`}# z^vVXTVxFt*B_l|VI_`J?m4VjVUIRq;6rJyv(YTIkmW9mI)of*3CN;dOI2?LAq;#@Z zXR4!0!=blDN>d#)gJxW*mx%Jg(4xUh5T8u3E+hxi zDu*O-Ms*2-SaN)Gi8W=>`=8LMQ(_nA0vg3eT0&YXC9Omtr-EzWi9Cg+HOaAQ=$PR1P7iBu=ZImc>x^ zg0l`?d^F8@c$VU$zh1R#?LZGDKC0;i|#3&0^IWM z)Wv*%sHU+BBhvc+uAbjYX@QdoFTJhwg_Fc9gXfc@%699P8q5ohk?C&^x1uwb_yy!0QqPxJ0?gB5m%W>ceU`O2zf!|U6 zcjVtR3SnM2%BqLS3h(2Vs0Tj^GB|*!N9Ep;A_MQCfrXPx(E!rwP4R^5f`50Oa9yRl z*h0dr^>wJME0kAXb_>IBsJ#r}SL*C3GNH8l*TcIdJX_~2_#Sz-7%{y+;VzA?R@I~B zmCS3!fzsQAmz7>9AYbXgxDK4&KD^-ak^J3FvgVSxzh_y13~GNdAtyueT>kEAscJ`}LS+c1}CgtWxn%`&HeV9%PS|`_l4OQ)~V|(bhwN zyEUJFr1da>^&OF;D*T{|KR6z1%|&s$w!{$8rHkTr^;1|Huj|u9mql^$-STziyXD_7 z%Cd{6xK_Wyn;_C47CQ=ZonKAE#iKy4>g_qy@3(OdF-ugx{6H^< zc>I9pCh_T~O8Dtdu_%^c&Z!BzT@;J3#;dL0;zv_@x2`OP3qLknK1Q0en_9%_O+;9; zNIxVEImP?F-h^!BP$_iRB@f^XDHWqLlsUExISt({IXQdenxK0I&ViEd2scrrcrWRW zldAT;bp8~&qY0!@UOAR3=GZ)KqIO_aPeFI};OiZn5=B(i=hdB;^1q+$1x1u44Q}ZI za`OJ&5PE8?!nyOK9%|J87wI)86At50>F;{-S!t}3ME6YDC(FLJM;A?>Uk`G&f^ODd z_ge_MtTcgpi+vXZ&~1f=HcuQs`y{%qsbCNL_4s4xzDA4b^t-W@&K99}IZk!`P)>GR zQ)?2nMVGrZsh*NWpWWvwX}urXeFe_+!&i(h!jD-^uK7JsUk^N-J)UB$=Py73W09Uf z3*bp042Xqn41gm|(PMzAz+Zq5fn`7jkPZABI7jv0)|2t-&)P_jy1*p0#^d%Va4+vT z@=0yk(3TD+8`=PUZ|~~yQnUvj)96DYDEg%R{Q)_=sw$52^PDoyI9w zcfM(5yej&wSWxb%4cywMTx~G9;SUUL-xu4cXlr_VY-6LgX`TjIP3;-vbcF+0OA_X-?>gGEQA4)F_2f+)X{TIepEFoa*|T z!DMSSwQi_(?RMROZ>U{yHIx;gj;zvL4o_SsBX5x!loD%k_FlkaNN{wC8?U6gd<5x?QnGz|nQyN-bIR2R!a} z1T^`sckdWrbb{FJ&BAqjT+NEU2}d1w91~Q=P;Xw|hzn93_r^Z7(Q$vzvXCXZTBvNc z+PQ*c6H^QeDyeB%JxV(bi}EmZBE#P0IyRM$rQQZ9o$S>Om0;1n6T{NV=naz6R7oie z{vKLZ`LXMY?>uGGb(*qf*h**>Hc!u4RZ#u~=z(%k4bNj0eGH)A^EY}vek%P@vHsd0 zg{lK4`Hm7maRy3LL7d!@)41J>KVpS13h-qZ;LcZ-9V=>xu>pz7-KjM688DZz>G+xI z48&$8f5 z%eK_N4(%Bb#DlpbW=PgbZ_Jt6F*NY^*;}Y= z`kHT^@h3e56!qZ6vAb9Bk9TB1>etvUTlYr=^7I`jwqfm>FSB0hC zoqut2G!>2hV*BPD^D?(?+_)hfC0A$1_&JMu@RDfj(a;5Z(Rgv#J3G_Ux35n(q~Bb- zX7!h=)@_O_D+x{i)Q|Hhss9^CP`EVs#q70f)*99t))b&mK^A z+|aJqW**rR~uIwzAQk=mFqU6-aw4O)%hi*Qb|eQxNW(4-@n57u%oEi9uT{1 z$11}rLz*GAXyv+{{e%4cg1A4=4f5-^I6GtY%9W|>%#sqk9p`{o8Dxcuhia*SSHeIo@Dxdb2O`nd9 zYaC~feY#0Y-r6P4;nlTgK$8GR&#qqOas<{ppjm*>n^oY)26zM%2EO>w!IS&{{%jx! zA+}GM*q@)nI{&7 z`5XN0Pp2MvT`gFbI||OtfCU#V{fN)~`ScHCot;((fW`P3{p`W>PL0OmZ%;ZkHxRTC z!+2eOJ#@@Z5u6_#KKg1LpLnAis?m>{pooTAPd-PY@u&V7q>6g+f<9M5=I-M>Od5E{ zQfTnP;nz5SYaM^_>I;14*{}+ty*e5@+Gp)O{l$FKeEXNDcg+AT^depJLXrym@SW2+ ze_yzsKdwSANm5*OuKqc3l`2!8=l^F`m%K*IsfmYk^F^%mn)GS z)gdHXFN<#8IBMpnhI8KK^RwqpS}Jt2tliWOFQ3uNx>~isqMkB7C2Q!|@Khoc71O{F zaN6b81-FGvue4x$V|%5>W2So4~(+Ae4XB<3pI31+0v0||eX{I#$*Jlo{PBWz0 z*M5I$Cum`V84tqko_BLbsxj5RD(CFBR8y)w>+GI1&_V`@-tKufuUlbUVNW}FCS!$Z zg?;PUeW{>@b!J}a^KMRGZd`6pJ$PpQa?^7Amb3d-f)>){25-^z5Pi zrv3Ks&K}tbT3Ab>w|U;pYnB<8iQm!l{vA@n8Z*D$^KM@Knej7-7|%IQ{d9ut_d76z z)g*bF$KAY&V&dOI#-91xtyJ(J z7{ZE*1;1EmT&NU0b?BH}5DejqiUmJkU|b;DXew+-37?lQ;YeJPGQY`uN6M1Ka*vf` z`0T}>Hu==Cc=j;l=pOcAiS$S(4y*XE*Y5DOJACa9PkzDw@g1IAd*ePXepX^EafmY~ z<910{#h=YSeQHGn*P>Hzp1yHxJbZ*3ahraZW}M`NMWOj0O4l%VUszJ!IDQ-2C-@ z`OuR*^w(uCPJ!)2WRe$}O4 zKntI(rM4!XMGL?6moQ8dE^{@u#pj%Sh8(Qe*`(-DsoS@SC@UtAnC(3hTd)&ix#4*h z9{T0iTn%##{w z=FlqGZfLYAT6m=-+7Ke0! z5q~wG#K)ADiO>Cp#Q(KJyALX%N=N09Ja~1{@Q^4|lrX9!$`B>Io)_g2B@B@p{(x3< z`+Iw|`^$MGIkbc%WuEwstoM*eQ)F@PlHrk&|M)i2BT|)qXAen#v|YQuD7PDboup-+ zP&5<+iq;`hvSml?ptEz!q!TWY^p*wky>QEdh?3uPKF3DFGE2Co2oLV*(X-^4%xjme zkw^)HN%3Dp&%YNs_U7~Ijc06)=y&=}h?dBl_hqFA_wJ$!lygA`NIrw4ZT=`IW;zN2HGXS;cKb3EIpBT)f?eFl0DboA*I0Am&W zJCfmc48XT2?eLybYs$5zTx-g;t8(qC`~a=W@=IhEzvgCZzu~gwd+>afj@y1UcU@O( zCRc8jmXx^J+w;n8`bMsC`2MvKG5`6PUT#9gKAhirbJzC$qV@f0GE+|vp45U zw-75zdYbcFZtrDAR`jnOe8?+Yb^`>Y@x~Q)wjGIMk1A>dbXD7DLT zm#5*KuDH8|ZDsxrtdNC&<=v&eoWFa5h`ee1;M#}*Kb<9Fc?R#7{oBEXhiM^AlLLRt zR#8U&c)s(4us`ID08#W77QZ6}=V#*8Wd-p6qDjMPIwHCfXiUc|aSa0KI450x01g$? z<@~t-9V2bnD}K!DiBl#`n>d#8YU;EpwMCb^)26DLls)4|`qZh6)21`<3)xvqfK*_k zYyQl^jLl2{rU7$+dB7I{%FX-+_!0O8D1fXd&;oc82m@k)!9W5q4VVMWqx#DoHJ0u< z?Rq@<-q-JXR9g9$1IyaPDI<&i@1@;yTK$J3$UKh$(KM#GCIG&az=dCzX#c#K(>xo$ zm@r=bhiM1IPko(=Vfy7uGt*>HcXBA&8|6rrEFOcQJqg-x@rbQnvtxAD2L9|ZjJ?qU zXafW)g)|LkXCr?vkOSb1^KAV7dNzJPGy6Dj3OEN`1g-!$_dFZF*O^@e*a3{j8zulx jz#9z#JhX4%w>z@6kp)ylBR4#)9OdCuzB?q|(YXH)+uPIr literal 33385 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 diff --git a/doc/img/SDRdaemonSink_plugin_07.xcf b/doc/img/SDRdaemonSink_plugin_07.xcf deleted file mode 100644 index bc4d995da2bc076dc9f997925e4be2ff2dd7f671..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20668 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 E0p;on+yDRo diff --git a/plugins/samplesink/sdrdaemonsink/readme.md b/plugins/samplesink/sdrdaemonsink/readme.md index d04f4cca9..82db57950 100644 --- a/plugins/samplesink/sdrdaemonsink/readme.md +++ b/plugins/samplesink/sdrdaemonsink/readme.md @@ -2,10 +2,14 @@

    Introduction

    -This output sample sink plugin sends its samples over tbe network to a SDRdaemon transmitter server using UDP connection. SDRdaemon refers to the SDRdaemon utility `sdrdaemontx`found in [this](https://github.com/f4exb/sdrdaemon) Github repository. +This output sample sink plugin sends its samples over tbe network to a SDRangel instance's Daemon source channel using UDP connection. Forward Error Correction with a Cauchy MDS block erasure codec is used to prevent block loss. This can make the UDP transmission more robust particularly over WiFi links. +The distant SDRangel instance to which the data stream is sent is controlled via its REST API using a separate control software for example [SDRangelcli](https://github.com/f4exb/sdrangelcli) + +The sample size used in the I/Q stream is the Rx sample size of the local instance. Possible conversion takes place in the distant Daemon source channel plugin to match the Rx sample size of the distant instance. Best performace is obtained when both instances use the same sample size. +

    Build

    The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. @@ -23,25 +27,25 @@ Device start / stop button.

    2: Stream sample rate

    -Stream I/Q sample rate in kS/s over the network. +I/Q sample rate in kS/s of the stream that is sent over the network.

    3: Frequency

    -This is the center frequency in kHz sent to the remote device. +This is the center frequency in kHz of the remote instance device. -

    4: Sample rate

    +

    4: Remote baseband sample rate

    -![SDR Daemon sink output sample rate GUI](../../../doc/img/SDRdaemonSink_plugin_04.png) +This is the remote instance baseband sample rate. It can be a power of two multiple of the stream sample rate (2) but it will not work for other values. -

    4.1: Network stream sample rate

    +

    5: Stream controls and API destination

    + +![SDR Daemon sink output sample rate GUI](../../../doc/img/SDRdaemonSink_plugin_05.png) + +

    5.1: Network stream sample rate

    This is the I/Q stream sample rate transmitted via UDP over the network -

    4.2: Remote interpolation factor

    - -This is the interpolation set in the remote `sdrdaemontx` server instance. - -

    5: Delay between UDP blocks transmission

    +

    5.2: Delay between UDP blocks transmission

    This sets the minimum delay between transmission of an UDP block (send datagram) and the next. This allows throttling of the UDP transmission that is otherwise uncontrolled and causes network congestion. @@ -54,6 +58,14 @@ The value is a percentage of the nominal time it takes to process a block of sam Formula: ((127 ✕ 126 ✕ _d_) / _SR_) / (128 + _F_) +

    5.3: remote instance device set index

    + +This is the device set index in the remote instance to which the stream is connected to. Use this value to properly address the API to get status. + +

    5.4: remote instance channel index

    + +This is the channel index of the Daemon source in the remote instance to which the stream is connected to. Use this value to properly address the API to get status. +

    6: Forward Error Correction setting and status

    ![SDR Daemon sink output FEC GUI](../../../doc/img/SDRdaemonSink_plugin_06.png) @@ -62,65 +74,83 @@ Formula: ((127 ✕ 126 ✕ _d_) / _SR_) / (128 + _F_) This sets the number of FEC blocks per frame. A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. The two numbers next are the total number of blocks and the number of FEC blocks separated by a slash (/). -

    6.2: Distant transmitter queue length

    - -This is the samples queue length reported from the distant transmitter. This is a number of vectors of 127 ✕ 127 ✕ _I_ samples where _I_ is the interpolation factor. This corresponds to a block of 127 ✕ 126 samples sent over the network. This numbers serves to throttle the sample generator so that the queue length is close to 8 vectors. - -

    6.3: Stream status

    +

    6.2: Stream status

    The color of the icon indicates stream status: - Green: all original blocks have been received for all frames during the last polling timeframe (ex: 134) - No color: some original blocks were reconstructed from FEC blocks for some frames during the last polling timeframe (ex: between 128 and 133) - Red: some original blocks were definitely lost for some frames during the last polling timeframe (ex: less than 128) + - Blue: stream is idle -

    6.4: Frames recovery status

    +

    6.3: Remote stream rate

    -These are two numbers separated by a slash (/): +This is the remote stream rate calculated from the samples counter between two consecutive API polls. It is normal for it to oscillate moderately around the nominal stream rate (2). - - first: minimum total number of blocks per frame during the last polling period. If all blocks were received for all frames then this number is the nominal number of original blocks plus FEC blocks (Green lock icon). In our example this is 128+6 = 134. - - second: maximum number of FEC blocks used for original blocks recovery during the last polling timeframe. Ideally this should be 0 when no blocks are lost but the system is able to correct lost blocks up to the nominal number of FEC blocks (Neutral lock icon). - -

    6.5: Reset events counters

    +

    6.4: Reset events counters

    This push button can be used to reset the events counters (6.6 and 6.7) and reset the event counts timer (6.8) -

    6.6: Unrecoverable error events counter

    +

    6.5: Unrecoverable error events counter

    This counter counts the unrecoverable error conditions found (i.e. 6.4 lower than 128) since the last counters reset. -

    6.7: Recoverable error events counter

    +

    6.6: Recoverable error events counter

    This counter counts the unrecoverable error conditions found (i.e. 6.4 between 128 and 128 plus the number of FEC blocks) since the last counters reset. -

    6.8: events counters timer

    +

    6.7: events counters timer

    This HH:mm:ss time display shows the time since the reset events counters button (4.6) was pushed. -

    7: Network parameters

    +

    7: Distant transmitter queue length gauge

    -![SDR Daemon sink output network GUI](../../../doc/img/SDRdaemonSink_plugin_07.png) +This is ratio of the reported number of data frame blocks in the remote queue over the total number of blocks in the queue. -

    7.1: Distant interface IP address

    +

    8: Distant transmitter queue length status

    -Address of the network interface on the distance (server) machine where the SDRdaemon Tx server runs and receives samples. +This is the detail of the ratio shown in the gauge. Each frame block is a block of 127 ✕ 126 samples (16 bit I or Q samples) or 127 ✕ 63 samples (24 bit I or Q samples). -

    7.2: Distant data port

    +

    9: Distant server API address and port

    -UDP port on the distant (server) machine where the SDRdaemon Tx server runs and receives samples. +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_05.png) -

    7.3 Distant configuration port

    +

    9.1: API connection indicator

    -TCP port on the distant machine hosting the SDRdaemon Tx instance to send control messages to and receive status messages from. +The "API" label is lit in green when the connection is successful -

    7.4: Validation button

    +

    9.2: API IP address

    -When the return key is hit within the address (7.1), data port (7.2) or configuration port (7.3) boxes the changes are effective immediately. You can also use this button to set again these values. +IP address of the distant SDRangel instance REST API -

    8: Other parameters hardware specific

    +

    9.3: API port

    -These are the parameters that are specific to the hardware attached to the distant SDRdaemon instance. You have to know which device is attached to send the proper parameters. Please refer to the SDRdaemon documentation or its line help to get information on these parameters. +Port of the distant SDRangel instance REST API -

    9: Send data to the distant SDRdaemon Rx instance

    +

    9.4: Validation button

    -When any of the parameters change they get immediately transmitted to the distant server over the TCP link. You can however use this button to send again the complete configuration. This is handy if for some reason you are unsure of the parameters set in the distant server. +When the return key is hit within the address (9.2) or port (9.3) the changes are effective immediately. You can also use this button to set again these values. Clicking on this button will send a request to the API to get the distant SDRangel instance information that is displayed in the API message box (8) + +

    10: Local data address and port

    + +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_06.png) + +

    10.1: Data IP address

    + +IP address of the local network interface the distant SDRangel instance sends the data to + +

    10.2: Data port

    + +Local port the distant SDRangel instance sends the data to + +

    10.3: Validation button

    + +When the return key is hit within the address (10.2) or port (10.3) the changes are effective immediately. You can also use this button to set again these values. + +

    11: Status message

    + +The API status is displayed in this box. It shows "API OK" when the connection is successful and reply is OK + +

    12: API information

    + +This is the information returned by the API and is the distance SDRangel instance information if transaction is successful diff --git a/plugins/samplesource/sdrdaemonsource/readme.md b/plugins/samplesource/sdrdaemonsource/readme.md index 465ced28c..337165ce7 100644 --- a/plugins/samplesource/sdrdaemonsource/readme.md +++ b/plugins/samplesource/sdrdaemonsource/readme.md @@ -2,7 +2,7 @@

    Introduction

    -This input sample source plugin gets its samples over tbe network from a SDRdaemon receiver server using UDP connection. SDRdaemon refers to the SDRdaemon utility `sdrdaemonrx`found in [this](https://github.com/f4exb/sdrdaemon) Github repository. +This input sample source plugin gets its samples over tbe network from a SDRangel instance's Daemon channel sink using UDP connection. Forward Error Correction with a Cauchy MDS block erasure codec is used to prevent block loss. This can make the UDP transmission more robust particularly over WiFi links. @@ -10,6 +10,8 @@ Please note that there is no provision for handling out of sync UDP blocks. It i The distant SDRangel instance that sends the data stream is controlled via its REST API using a separate control software for example [SDRangelcli](https://github.com/f4exb/sdrangelcli) +A sample size conversion takes place if the stream sample size sent by the distant instance and the Rx sample size of the local instance do not match (i.e. 16 to 24 bits or 24 to 16 bits). Best performace is obtained when both instances use the same sample size. +

    Build

    The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. From 94befa3e4ec60b336c9890f38ff46178416f2a58 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 15 Sep 2018 20:24:49 +0200 Subject: [PATCH 757/956] SDRDaemonSource: updated documentation --- doc/img/DaemonSink.png | Bin 0 -> 13192 bytes doc/img/DaemonSink.xcf | Bin 0 -> 38235 bytes doc/img/DaemonSource.png | Bin 0 -> 18222 bytes doc/img/DaemonSource.xcf | Bin 0 -> 52368 bytes doc/img/DaemonSource_5.png | Bin 0 -> 7298 bytes doc/img/DaemonSource_5.xcf | Bin 0 -> 28074 bytes plugins/channelrx/daemonsink/readme.md | 46 +++++++++++++ plugins/channeltx/daemonsource/readme.md | 79 +++++++++++++++++++++++ 8 files changed, 125 insertions(+) create mode 100644 doc/img/DaemonSink.png create mode 100644 doc/img/DaemonSink.xcf create mode 100644 doc/img/DaemonSource.png create mode 100644 doc/img/DaemonSource.xcf create mode 100644 doc/img/DaemonSource_5.png create mode 100644 doc/img/DaemonSource_5.xcf create mode 100644 plugins/channelrx/daemonsink/readme.md create mode 100644 plugins/channeltx/daemonsource/readme.md diff --git a/doc/img/DaemonSink.png b/doc/img/DaemonSink.png new file mode 100644 index 0000000000000000000000000000000000000000..1a6f1afcd98f6ff4a58226ef2d39a600f7729627 GIT binary patch literal 13192 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 literal 0 HcmV?d00001 diff --git a/doc/img/DaemonSink.xcf b/doc/img/DaemonSink.xcf new file mode 100644 index 0000000000000000000000000000000000000000..ba60ba5e907d66428a51913887daed4eb2922d48 GIT binary patch literal 38235 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 literal 0 HcmV?d00001 diff --git a/doc/img/DaemonSource.png b/doc/img/DaemonSource.png new file mode 100644 index 0000000000000000000000000000000000000000..1adf4ac079c8fa7ec0554adbeda5119bb159fec6 GIT binary patch literal 18222 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 literal 0 HcmV?d00001 diff --git a/doc/img/DaemonSource_5.png b/doc/img/DaemonSource_5.png new file mode 100644 index 0000000000000000000000000000000000000000..723242bddf8080af98398eac12aecd402318f0b5 GIT binary patch literal 7298 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 literal 0 HcmV?d00001 diff --git a/doc/img/DaemonSource_5.xcf b/doc/img/DaemonSource_5.xcf new file mode 100644 index 0000000000000000000000000000000000000000..fee564fec3703dab53d32d437dd2ed574f0ffcb0 GIT binary patch literal 28074 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 sink channel plugin + +

    Introduction

    + +This plugin sends I/Q samples from the baseband via UDP to a distant network end point. It can use FEC protection to prevent possible data loss inherent to UDP protocol. + +It is present only in Linux binary releases. + +

    Build

    + +The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. + +

    Interface

    + +![Daemon sink channel plugin GUI](../../../doc/img/DaemonSink.png) + +

    1: Distant address

    + +IP address of the distant network interface from where the I/Q samples are sent via UDP + +

    2: Data distant port

    + +Distant port to which the I/Q samples are sent via UDP + +

    3: Validation button

    + +When the return key is hit within the address (1) or port (2) the changes are effective immediately. You can also use this button to set again these values. + +

    4: Desired number of FEC blocks per frame

    + +This sets the number of FEC blocks per frame. A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. The two numbers next are the total number of blocks and the number of FEC blocks separated by a slash (/). + +

    5: Delay between UDP blocks transmission

    + +This sets the minimum delay between transmission of an UDP block (send datagram) and the next. This allows throttling of the UDP transmission that is otherwise uncontrolled and causes network congestion. + +The value is a percentage of the nominal time it takes to process a block of samples corresponding to one UDP block (512 bytes). This is calculated as follows: + + - Sample rate on the network: _SR_ + - Delay percentage: _d_ + - Number of FEC blocks: _F_ + - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 8 bytes header (2 samples) thus there are 126 samples remaining effectively. This gives the constant 127*126 = 16002 samples per frame in the formula + +Formula: ((127 ✕ 126 ✕ _d_) / _SR_) / (128 + _F_) + +The percentage appears first at the right of the dial button and then the actual delay value in microseconds. \ No newline at end of file diff --git a/plugins/channeltx/daemonsource/readme.md b/plugins/channeltx/daemonsource/readme.md new file mode 100644 index 000000000..8a68a4db1 --- /dev/null +++ b/plugins/channeltx/daemonsource/readme.md @@ -0,0 +1,79 @@ +

    Daemon source channel plugin

    + +

    Introduction

    + +This plugin receives I/Q samples from UDP and copies them to the baseband to be transmitted by the sink output device. It uses SDRDaemon format and possible FEC protection. + +It is present only in Linux binary releases. + +

    Build

    + +The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. + +

    Interface

    + +![Daemon source channel plugin GUI](../../../doc/img/DaemonSource.png) + +

    1: Data local address

    + +IP address of the local network interface from where the I/Q samples are fetched via UDP + +

    2: Data local port

    + +Local port from where the I/Q samples are fetched via UDP + +

    3: Validation button

    + +When the return key is hit within the address (1) or port (2) the changes are effective immediately. You can also use this button to set again these values. + +

    4: Stream sample rate

    + +Stream sample rate as specified in the stream meta data + +

    5: Stream status

    + +![Daemon source channel plugin GUI](../../../doc/img/DaemonSource_5.png) + +

    5.1: Total number of frames and number of FEC blocks

    + +This is the total number of frames and number of FEC blocks separated by a slash '/' as sent in the meta data block thus acknowledged by the distant server. When you set the number of FEC blocks with (4.1) the effect may not be immediate and this information can be used to monitor when it gets effectively set in the distant server. + +A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. + +Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered. + +

    5.2: Stream status

    + +The color of the icon indicates stream status: + + - Green: all original blocks have been received for all frames during the last polling timeframe (ex: 136) + - No color: some original blocks were reconstructed from FEC blocks for some frames during the last polling timeframe (ex: between 128 and 135) + - Red: some original blocks were definitely lost for some frames during the last polling timeframe (ex: less than 128) + +

    5.3: Actual stream sample rate

    + +This is the sample rate calculated using the counter of samples between two consecutive polls + +

    5.4: Reset events counters

    + +This push button can be used to reset the events counters (5.5 and 5.6) and reset the event counts timer (5.7) + +

    5.5: Unrecoverable error events counter

    + +This counter counts the unrecoverable error conditions found (i.e. 4.4 lower than 128) since the last counters reset. + +

    5.6: Recoverable error events counter

    + +This counter counts the unrecoverable error conditions found (i.e. 4.4 between 128 and 128 plus the number of FEC blocks) since the last counters reset. + +

    5.7: events counters timer

    + +This HH:mm:ss time display shows the time since the reset events counters button (5.4) was pushed. + +

    6: Transmitter queue length gauge

    + +This is ratio of the reported number of data frame blocks in the remote queue over the total number of blocks in the queue. + +

    7: Transmitter queue length status

    + +This is the detail of the ratio shown in the gauge. Each frame block is a block of 127 ✕ 126 samples (16 bit I or Q samples) or 127 ✕ 63 samples (24 bit I or Q samples). From a2a07a112172a07a0828652acd1a4d55c223e2cc Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 15 Sep 2018 21:50:12 +0200 Subject: [PATCH 758/956] SDRdaemon: code cleanup --- .../samplesink/sdrdaemonsink/UDPSocket.cpp | 19 +------- .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 22 --------- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 46 ------------------- .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 7 +-- 4 files changed, 3 insertions(+), 91 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/UDPSocket.cpp b/plugins/samplesink/sdrdaemonsink/UDPSocket.cpp index fe111cbef..dabb54e17 100644 --- a/plugins/samplesink/sdrdaemonsink/UDPSocket.cpp +++ b/plugins/samplesink/sdrdaemonsink/UDPSocket.cpp @@ -121,7 +121,6 @@ void CSocket::BindLocalAddressAndPort( const string &localAddress, unsigned shor void CSocket::FillAddr( const string & localAddress, unsigned short localPort, sockaddr_in& localAddr ) { - ////cout<<"\n Inside Fille addr:"<h_addr_list[0]); localAddr.sin_port = htons(localPort); // Assign port in network byte order - ////cout<<"\n returning from Fille addr"; } unsigned long int CSocket::GetReadBufferSize() @@ -175,19 +173,14 @@ void CSocket::SetNonBlocking( bool bBlocking ) void CSocket::ConnectToHost( const string &foreignAddress, unsigned short foreignPort ) { - //cout<<"\nstart Connect to host"; // Get the address of the requested host sockaddr_in destAddr; - //cout<<"\ninside Connect to host"; FillAddr(foreignAddress, foreignPort, destAddr); - //cout<<"trying to connect to host"; // Try to connect to the given port if (::connect(m_sockDesc, (sockaddr *) &destAddr, sizeof(destAddr)) < 0) { throw CSocketException("Connect failed (connect())", true); } - //cout<<"\n after connecting"; - } void CSocket::Send( const void *buffer, int bufferLen ) @@ -249,7 +242,6 @@ int CSocket::OnDataRead(unsigned long timeToWait) { /* master file descriptor list */ fd_set master; - //struct timeval *ptimeout = NULL; /* temp file descriptor list for select() */ fd_set read_fds; @@ -268,8 +260,8 @@ int CSocket::OnDataRead(unsigned long timeToWait) /* copy it */ read_fds = master; - //cout<<"Waiting for select"; int nRet; + if (timeToWait == ULONG_MAX) { nRet = select(fdmax+1, &read_fds, NULL, NULL, NULL); @@ -300,13 +292,6 @@ void CSocket::SetBindToDevice( const string& sInterface ) struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", sInterface.c_str()); - //Todo:SO_BINDTODEVICE not declared error comes in CygWin, need to compile in Linux. - /*int nRet = ::setsockopt(m_sockDesc, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(ifr)); - - if (nRet < 0) - { - throw CSocketException("Error in binding to device ", true); - }*/ } UDPSocket::UDPSocket():CSocket(UdpSocket,IPv4Protocol) @@ -346,10 +331,8 @@ void UDPSocket::DisconnectFromHost() void UDPSocket::SendDataGram( const void *buffer, int bufferLen, const string &foreignAddress, unsigned short foreignPort ) { - //cout<<"Befor Fill addr"; sockaddr_in destAddr; FillAddr(foreignAddress, foreignPort, destAddr); - //cout<<"Befor socket send"; // Write out the whole buffer as a single message. if (sendto(m_sockDesc, (void *) buffer, bufferLen, 0,(sockaddr *) &destAddr, sizeof(destAddr)) != bufferLen) { diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index 77984afe2..404644728 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -100,7 +100,6 @@ void UDPSinkFEC::setRemoteAddress(const QString& address, uint16_t port) void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunkSize) { - //qDebug("UDPSinkFEC::write(: %u samples", sampleChunkSize); const SampleVector::iterator end = begin + sampleChunkSize; SampleVector::iterator it = begin; @@ -186,10 +185,7 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk int nbBlocksFEC = m_nbBlocksFEC; int txDelay = m_txDelay; - //qDebug("UDPSinkFEC::write: push frame to worker: %u", m_frameCount); m_udpWorker->pushTxFrame(m_txBlocks[m_txBlocksIndex], nbBlocksFEC, txDelay, m_frameCount); - //m_txThread = new std::thread(transmitUDP, this, m_txBlocks[m_txBlocksIndex], m_frameCount, nbBlocksFEC, txDelay, m_cm256Valid); - //transmitUDP(this, m_txBlocks[m_txBlocksIndex], m_frameCount, m_nbBlocksFEC, m_txDelay, m_cm256Valid); m_txBlocksIndex = (m_txBlocksIndex + 1) % 4; m_txBlockIndex = 0; @@ -282,8 +278,6 @@ void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t if ((nbBlocksFEC == 0) || !m_cm256Valid) { -// qDebug("UDPSinkFECWorker::encodeAndTransmit: transmit frame without FEC to %s:%d", m_remoteAddress.toStdString().c_str(), m_remotePort); - for (unsigned int i = 0; i < UDPSinkFEC::m_nbOriginalBlocks; i++) { m_socket.SendDataGram((const void *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); @@ -328,8 +322,6 @@ void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t // Transmit all blocks -// qDebug("UDPSinkFECWorker::encodeAndTransmit: transmit frame with FEC to %s:%d", m_remoteAddress.toStdString().c_str(), m_remotePort); - for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) { #ifdef SDRDAEMON_PUNCTURE @@ -337,22 +329,8 @@ void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t continue; } #endif -// std::cerr << "UDPSinkFEC::transmitUDP:" -// << " i: " << i -// << " frameIndex: " << (int) m_txBlocks[i].header.frameIndex -// << " blockIndex: " << (int) m_txBlocks[i].header.blockIndex -// << " i.q:"; -// -// for (int j = 0; j < 10; j++) -// { -// std::cerr << " " << (int) m_txBlocks[i].protectedBlock.m_samples[j].m_real -// << "." << (int) m_txBlocks[i].protectedBlock.m_samples[j].m_imag; -// } -// -// std::cerr << std::endl; m_socket.SendDataGram((const void *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); - //m_udpSocket->writeDatagram((const char *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress, m_remotePort); usleep(txDelay); } } diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index 172e07312..308e5db39 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -43,52 +43,6 @@ class UDPSinkFEC : public QObject public: static const uint32_t m_udpSize = 512; //!< Size of UDP block in number of bytes static const uint32_t m_nbOriginalBlocks = 128; //!< Number of original blocks in a protected block sequence -//#pragma pack(push, 1) -// struct MetaDataFEC -// { -// uint32_t m_centerFrequency; //!< 4 center frequency in kHz -// uint32_t m_sampleRate; //!< 8 sample rate in Hz -// uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample -// uint8_t m_sampleBits; //!< 10 number of effective bits per sample -// uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data -// uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC -// uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing -// uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing -// uint32_t m_crc32; //!< 24 CRC32 of the above -// -// bool operator==(const MetaDataFEC& rhs) -// { -// return (memcmp((const char *) this, (const char *) &rhs, 12) == 0); // Only the 12 first bytes are relevant -// } -// -// void init() -// { -// memset((char *) this, 0, sizeof(MetaDataFEC)); -// m_nbFECBlocks = -1; -// } -// }; -// -// struct Header -// { -// uint16_t frameIndex; -// uint8_t blockIndex; -// uint8_t filler; -// uint32_t filler2; -// }; -// -// static const int bytesPerBlock = m_udpSize - sizeof(Header); -// -// struct ProtectedBlock -// { -// uint8_t m_buf[bytesPerBlock]; -// }; -// -// struct SuperBlock -// { -// Header header; -// ProtectedBlock protectedBlock; -// }; -//#pragma pack(pop) /** * Construct UDP sink diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 1c6d95891..2718bed46 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -224,11 +224,8 @@ void SDRdaemonSourceInput::applySettings(const SDRdaemonSourceSettings& settings settings.m_iqCorrection ? "true" : "false"); } -// if (force || (m_settings.m_dataAddress != settings.m_dataAddress) || (m_settings.m_dataPort != settings.m_dataPort)) -// { - m_SDRdaemonUDPHandler->configureUDPLink(settings.m_dataAddress, settings.m_dataPort); - m_SDRdaemonUDPHandler->getRemoteAddress(remoteAddress); -// } + m_SDRdaemonUDPHandler->configureUDPLink(settings.m_dataAddress, settings.m_dataPort); + m_SDRdaemonUDPHandler->getRemoteAddress(remoteAddress); mutexLocker.unlock(); m_settings = settings; From f3253ffccc0007fe784987ab24079326f55b9e08 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 15 Sep 2018 22:30:58 +0200 Subject: [PATCH 759/956] SDRDaemonSink: Windows build --- .../sdrdaemonsink/sdrdaemonsink.pro | 62 +++++++++++++++++++ sdrangel.windows.pro | 3 +- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro b/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro new file mode 100644 index 000000000..094b3d2a8 --- /dev/null +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro @@ -0,0 +1,62 @@ +#-------------------------------------------------------- +# +# Pro file for Windows builds with Qt Creator +# +#-------------------------------------------------------- + +TEMPLATE = lib +CONFIG += plugin + +QT += core gui widgets multimedia network opengl + +TARGET = outputsdrdaemonsink + +CONFIG(MINGW32):LIBCM256CCSRC = "D:\softs\cm256cc" +CONFIG(MINGW64):LIBCM256CCSRC = "D:\softs\cm256cc" +CONFIG(macx):LIBCM256CCSRC = "../../../../deps/cm256cc" + +INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../sdrbase +INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client +macx:INCLUDEPATH += /opt/local/include +INCLUDEPATH += $$LIBCM256CCSRC + +DEFINES += USE_SSE2=1 +QMAKE_CXXFLAGS += -msse2 +DEFINES += USE_SSSE3=1 +QMAKE_CXXFLAGS += -mssse3 +DEFINES += USE_SSE4_1=1 +QMAKE_CXXFLAGS += -msse4.1 +QMAKE_CXXFLAGS += -std=c++11 + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" + +SOURCES += sdrdaemonsinkthread.cpp\ +sdrdaemonsinkgui.cpp\ +sdrdaemonsinkoutput.cpp\ +sdrdaemonsinksettings.cpp\ +sdrdaemonsinkplugin.cpp + +HEADERS += sdrdaemonsinkthread.h\ +sdrdaemonsinkgui.h\ +sdrdaemonsinkoutput.h\ +sdrdaemonsinksettings.h\ +sdrdaemonsinkplugin.h + +FORMS += sdrdaemonsinkgui.ui + +LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase +LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger +LIBS += -L../../../cm256cc/$${build_subdir} -lcm256cc + +RESOURCES = ../../../sdrgui/resources/res.qrc + +CONFIG(MINGW32):DEFINES += USE_INTERNAL_TIMER=1 + diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index 344da94a0..ab6083f22 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -37,7 +37,7 @@ SUBDIRS += plugins/samplesource/filesource SUBDIRS += plugins/samplesource/hackrfinput SUBDIRS += plugins/samplesource/limesdrinput SUBDIRS += plugins/samplesource/plutosdrinput -CONFIG(MINGW64)SUBDIRS += plugins/samplesource/sdrdaemonsource +SUBDIRS += plugins/samplesource/sdrdaemonsource SUBDIRS += plugins/samplesource/rtlsdr SUBDIRS += plugins/samplesource/testsource SUBDIRS += plugins/samplesink/filesink @@ -45,6 +45,7 @@ SUBDIRS += plugins/samplesink/bladerfoutput SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput +SUBDIRS += plugins/samplesink/sdrdaemonsink SUBDIRS += plugins/channelrx/chanalyzer SUBDIRS += plugins/channelrx/demodam SUBDIRS += plugins/channelrx/demodatv From f86afff3e8ae66a737a371cc272dde905d0911ed Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 16 Sep 2018 01:30:43 +0200 Subject: [PATCH 760/956] Fixed Debian build --- plugins/channelrx/daemonsink/CMakeLists.txt | 45 +++++++++++++------ plugins/channeltx/CMakeLists.txt | 5 +++ plugins/channeltx/daemonsource/CMakeLists.txt | 45 +++++++++++++------ .../sdrdaemonsink/sdrdaemonsinkgui.h | 1 - 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/plugins/channelrx/daemonsink/CMakeLists.txt b/plugins/channelrx/daemonsink/CMakeLists.txt index 3375cb799..9b7d04c38 100644 --- a/plugins/channelrx/daemonsink/CMakeLists.txt +++ b/plugins/channelrx/daemonsink/CMakeLists.txt @@ -22,13 +22,6 @@ set(daemonsink_FORMS daemonsinkgui.ui ) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CM256CC_INCLUDE_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client -) - #include(${QT_USE_FILE}) add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_PLUGIN) @@ -42,13 +35,39 @@ add_library(daemonsink SHARED ${daemonsink_FORMS_HEADERS} ) -target_link_libraries(daemonsink - ${QT_LIBRARIES} - ${CM256CC_LIBRARIES} - sdrbase - sdrgui - swagger +if (BUILD_DEBIAN) +target_include_directories(daemonsink PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBCM256CCSRC} ) +else (BUILD_DEBIAN) +target_include_directories(daemonsink PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CM256CC_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +if (BUILD_DEBIAN) +target_link_libraries(daemonsink + ${QT_LIBRARIES} + cm256cc + sdrbase + sdrgui + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(daemonsink + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrgui + swagger +) +endif (BUILD_DEBIAN) target_link_libraries(daemonsink Qt5::Core Qt5::Widgets) diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index 3b24dfce0..7c4977aef 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -15,3 +15,8 @@ find_package(OpenCV) if (OpenCV_FOUND) add_subdirectory(modatv) endif() + +if (BUILD_DEBIAN) + add_subdirectory(daemonsource) +endif (BUILD_DEBIAN) + diff --git a/plugins/channeltx/daemonsource/CMakeLists.txt b/plugins/channeltx/daemonsource/CMakeLists.txt index 8caf6ef74..682f1953a 100644 --- a/plugins/channeltx/daemonsource/CMakeLists.txt +++ b/plugins/channeltx/daemonsource/CMakeLists.txt @@ -22,13 +22,6 @@ set(daemonsource_FORMS daemonsourcegui.ui ) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CM256CC_INCLUDE_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client -) - #include(${QT_USE_FILE}) add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_PLUGIN) @@ -42,13 +35,39 @@ add_library(daemonsource SHARED ${daemonsource_FORMS_HEADERS} ) -target_link_libraries(daemonsource - ${QT_LIBRARIES} - ${CM256CC_LIBRARIES} - sdrbase - sdrgui - swagger +if (BUILD_DEBIAN) +target_include_directories(daemonsource PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBCM256CCSRC} ) +else (BUILD_DEBIAN) +target_include_directories(daemonsource PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CM256CC_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +if (BUILD_DEBIAN) +target_link_libraries(daemonsource + ${QT_LIBRARIES} + cm256cc + sdrbase + sdrgui + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(daemonsource + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrgui + swagger +) +endif (BUILD_DEBIAN) target_link_libraries(daemonsource Qt5::Core Qt5::Widgets Qt5::Network) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 186a012f6..314be9314 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -137,7 +137,6 @@ private: private slots: void handleInputMessages(); - void on_centerFrequency_changed(quint64 value); void on_sampleRate_changed(quint64 value); void on_txDelay_valueChanged(int value); void on_nbFECBlocks_valueChanged(int value); From 03441bdd6c625e4d688c91d32805e198e3718d19 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 15 Sep 2018 23:55:41 +0000 Subject: [PATCH 761/956] Fixed Debian build on Bionic --- plugins/channelrx/daemonsink/CMakeLists.txt | 9 +++++++++ plugins/channeltx/daemonsource/CMakeLists.txt | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/plugins/channelrx/daemonsink/CMakeLists.txt b/plugins/channelrx/daemonsink/CMakeLists.txt index 9b7d04c38..8507daf6c 100644 --- a/plugins/channelrx/daemonsink/CMakeLists.txt +++ b/plugins/channelrx/daemonsink/CMakeLists.txt @@ -2,6 +2,15 @@ project(daemonsink) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +if (HAS_SSSE3) + message(STATUS "DaemonSink: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "DaemonSink: use Neon SIMD" ) +else() + message(STATUS "DaemonSink: Unsupported architecture") + return() +endif() + set(daemonsink_SOURCES daemonsink.cpp daemonsinkgui.cpp diff --git a/plugins/channeltx/daemonsource/CMakeLists.txt b/plugins/channeltx/daemonsource/CMakeLists.txt index 682f1953a..f8e0522de 100644 --- a/plugins/channeltx/daemonsource/CMakeLists.txt +++ b/plugins/channeltx/daemonsource/CMakeLists.txt @@ -2,6 +2,15 @@ project(daemonsource) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +if (HAS_SSSE3) + message(STATUS "DaemonSource: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "DaemonSource: use Neon SIMD" ) +else() + message(STATUS "DaemonSource: Unsupported architecture") + return() +endif() + set(daemonsource_SOURCES daemonsource.cpp daemonsourcethread.cpp From 82b25f2f9dd5bc3a1559afb0968862b28321ac40 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 16 Sep 2018 10:02:54 +0200 Subject: [PATCH 762/956] Fixed Windows build --- plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro | 9 +++++---- plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro | 9 +++++---- sdrangel.windows.pro | 3 +-- windows.install.bat | 7 +++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro b/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro index 094b3d2a8..e6618fbf8 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro @@ -11,11 +11,12 @@ QT += core gui widgets multimedia network opengl TARGET = outputsdrdaemonsink -CONFIG(MINGW32):LIBCM256CCSRC = "D:\softs\cm256cc" -CONFIG(MINGW64):LIBCM256CCSRC = "D:\softs\cm256cc" +CONFIG(MINGW32):LIBCM256CCSRC = "C:\softs\cm256cc" +CONFIG(MINGW64):LIBCM256CCSRC = "C:\softs\cm256cc" CONFIG(macx):LIBCM256CCSRC = "../../../../deps/cm256cc" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client @@ -33,8 +34,8 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" SOURCES += sdrdaemonsinkthread.cpp\ diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro b/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro index b8bffdecc..4bb075cc3 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro @@ -11,11 +11,12 @@ QT += core gui widgets multimedia network opengl TARGET = inputsdrdaemonsource -CONFIG(MINGW32):LIBCM256CCSRC = "D:\softs\cm256cc" -CONFIG(MINGW64):LIBCM256CCSRC = "D:\softs\cm256cc" +CONFIG(MINGW32):LIBCM256CCSRC = "C:\softs\cm256cc" +CONFIG(MINGW64):LIBCM256CCSRC = "C:\softs\cm256cc" CONFIG(macx):LIBCM256CCSRC = "../../../../deps/cm256cc" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client @@ -33,8 +34,8 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" SOURCES += sdrdaemonsourcebuffer.cpp\ diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index ab6083f22..f8b6b5e91 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -45,7 +45,6 @@ SUBDIRS += plugins/samplesink/bladerfoutput SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput -SUBDIRS += plugins/samplesink/sdrdaemonsink SUBDIRS += plugins/channelrx/chanalyzer SUBDIRS += plugins/channelrx/demodam SUBDIRS += plugins/channelrx/demodatv @@ -55,7 +54,7 @@ SUBDIRS += plugins/channelrx/demodlora SUBDIRS += plugins/channelrx/demodnfm SUBDIRS += plugins/channelrx/demodssb SUBDIRS += plugins/channelrx/demodwfm -SUBDIRS += plugins/channelrx/udpsource +SUBDIRS += plugins/channelrx/udpsink SUBDIRS += plugins/channeltx/modam SUBDIRS += plugins/channeltx/modnfm SUBDIRS += plugins/channeltx/modssb diff --git a/windows.install.bat b/windows.install.bat index 481820ebc..a710d6db2 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -38,7 +38,6 @@ mkdir %2\plugins\channeltx mkdir %2\plugins\samplesource mkdir %2\plugins\samplesink copy plugins\channelrx\chanalyzer\%1\chanalyzer.dll %2\plugins\channelrx -REM copy plugins\channelrx\chanalyzerng\%1\chanalyzerng.dll %2\plugins\channelrx copy plugins\channelrx\demodam\%1\demodam.dll %2\plugins\channelrx copy plugins\channelrx\demodatv\%1\demodatv.dll %2\plugins\channelrx copy plugins\channelrx\demodbfm\%1\demodbfm.dll %2\plugins\channelrx @@ -47,14 +46,13 @@ copy plugins\channelrx\demodlora\%1\demodlora.dll %2\plugins\channelrx copy plugins\channelrx\demodnfm\%1\demodnfm.dll %2\plugins\channelrx copy plugins\channelrx\demodssb\%1\demodssb.dll %2\plugins\channelrx copy plugins\channelrx\demodwfm\%1\demodwfm.dll %2\plugins\channelrx -REM copy plugins\channelrx\tcpsrc\%1\tcpsrc.dll %2\plugins\channelrx -REM copy plugins\channelrx\udpsrc\%1\udpsrc.dll %2\plugins\channelrx +copy plugins\channelrx\udpsink\%1\udpsink.dll %2\plugins\channelrx copy plugins\channeltx\modam\%1\modam.dll %2\plugins\channeltx REM copy plugins\channeltx\modatv\%1\modatv.dll %2\plugins\channeltx copy plugins\channeltx\modnfm\%1\modnfm.dll %2\plugins\channeltx copy plugins\channeltx\modssb\%1\modssb.dll %2\plugins\channeltx copy plugins\channeltx\modwfm\%1\modwfm.dll %2\plugins\channeltx -REM copy plugins\channeltx\udpsink\%1\udpsink.dll %2\plugins\channeltx +copy plugins\channeltx\udpsource\%1\udpsource.dll %2\plugins\channeltx copy plugins\samplesource\filesource\%1\inputfilesource.dll %2\plugins\samplesource copy plugins\samplesource\testsource\%1\inputtestsource.dll %2\plugins\samplesource copy plugins\samplesource\rtlsdr\%1\inputrtlsdr.dll %2\plugins\samplesource @@ -64,6 +62,7 @@ copy plugins\samplesource\airspyhf\%1\inputairspyhf.dll %2\plugins\samplesource copy plugins\samplesource\bladerfinput\%1\inputbladerf.dll %2\plugins\samplesource copy plugins\samplesource\limesdrinput\%1\inputlimesdr.dll %2\plugins\samplesource copy plugins\samplesource\plutosdrinput\%1\inputplutosdr.dll %2\plugins\samplesource +copy plugins\samplesource\sdrdaemonsource\%1\inputsdrdaemonsource.dll %2\plugins\samplesource copy plugins\samplesink\filesink\%1\outputfilesink.dll %2\plugins\samplesink copy plugins\samplesink\bladerfoutput\%1\outputbladerf.dll %2\plugins\samplesink copy plugins\samplesink\hackrfoutput\%1\outputhackrf.dll %2\plugins\samplesink From 2d003dfebf723d13333f8d928ab18ff15188b40b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 16 Sep 2018 11:00:36 +0200 Subject: [PATCH 763/956] Fixed Windows install script --- windows.install.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/windows.install.bat b/windows.install.bat index a710d6db2..6e1b47e72 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -16,6 +16,7 @@ copy httpserver\%1\httpserver.dll %2 copy qrtplib\%1\qrtplib.dll %2 copy swagger\%1\swagger.dll %2 copy logging\%1\logging.dll %2 +copy cm256cc\%1\cm256cc.dll %2 copy libhackrf\%1\libhackrf.dll %2 copy librtlsdr\%1\librtlsdr.dll %2 copy libairspy\%1\libairspy.dll %2 From 6406a0ba50c0558f1de09726bef2c3ce3fde5d15 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 16 Sep 2018 11:23:24 +0200 Subject: [PATCH 764/956] SDRDaemonSink: separate files for UDPSinkFECWorker --- .../samplesink/sdrdaemonsink/CMakeLists.txt | 2 + .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 140 +-------------- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 103 ----------- .../sdrdaemonsink/udpsinkfecworker.cpp | 161 ++++++++++++++++++ .../sdrdaemonsink/udpsinkfecworker.h | 124 ++++++++++++++ 5 files changed, 288 insertions(+), 242 deletions(-) create mode 100644 plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp create mode 100644 plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h diff --git a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt index a2e7e9e29..d6e7e4c2c 100644 --- a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt +++ b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt @@ -18,6 +18,7 @@ set(sdrdaemonsink_SOURCES sdrdaemonsinksettings.cpp sdrdaemonsinkthread.cpp udpsinkfec.cpp + udpsinkfecworker.cpp UDPSocket.cpp ) @@ -28,6 +29,7 @@ set(sdrdaemonsink_HEADERS sdrdaemonsinksettings.h sdrdaemonsinkthread.h udpsinkfec.h + udpsinkfecworker.h UDPSocket.h ) diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index 404644728..8916fa443 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -22,9 +22,7 @@ #include #include "udpsinkfec.h" - -MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgUDPFECEncodeAndSend, Message) -MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) +#include "udpsinkfecworker.h" UDPSinkFEC::UDPSinkFEC() : @@ -199,139 +197,3 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk } } -UDPSinkFECWorker::UDPSinkFECWorker() : - m_running(false), - m_remotePort(9090) -{ - m_cm256Valid = m_cm256.isInitialized(); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::DirectConnection); -} - -UDPSinkFECWorker::~UDPSinkFECWorker() -{ - disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); - m_inputMessageQueue.clear(); -} - -void UDPSinkFECWorker::pushTxFrame(SDRDaemonSuperBlock *txBlocks, - uint32_t nbBlocksFEC, - uint32_t txDelay, - uint16_t frameIndex) -{ - //qDebug("UDPSinkFECWorker::pushTxFrame. %d", m_inputMessageQueue.size()); - m_inputMessageQueue.push(MsgUDPFECEncodeAndSend::create(txBlocks, nbBlocksFEC, txDelay, frameIndex)); -} - -void UDPSinkFECWorker::setRemoteAddress(const QString& address, uint16_t port) -{ - m_inputMessageQueue.push(MsgConfigureRemoteAddress::create(address, port)); -} - -void UDPSinkFECWorker::process() -{ - m_running = true; - - qDebug("UDPSinkFECWorker::process: started"); - - while (m_running) - { - usleep(250000); - } - - qDebug("UDPSinkFECWorker::process: stopped"); - emit finished(); -} - -void UDPSinkFECWorker::stop() -{ - m_running = false; -} - -void UDPSinkFECWorker::handleInputMessages() -{ - Message* message; - - while ((message = m_inputMessageQueue.pop()) != 0) - { - if (MsgUDPFECEncodeAndSend::match(*message)) - { - MsgUDPFECEncodeAndSend *sendMsg = (MsgUDPFECEncodeAndSend *) message; - encodeAndTransmit(sendMsg->getTxBlocks(), sendMsg->getFrameIndex(), sendMsg->getNbBlocsFEC(), sendMsg->getTxDelay()); - } - else if (MsgConfigureRemoteAddress::match(*message)) - { - qDebug("UDPSinkFECWorker::handleInputMessages: %s", message->getIdentifier()); - MsgConfigureRemoteAddress *addressMsg = (MsgConfigureRemoteAddress *) message; - m_remoteAddress = addressMsg->getAddress(); - m_remotePort = addressMsg->getPort(); - } - - delete message; - } -} - -void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay) -{ - 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 - - if ((nbBlocksFEC == 0) || !m_cm256Valid) - { - for (unsigned int i = 0; i < UDPSinkFEC::m_nbOriginalBlocks; i++) - { - m_socket.SendDataGram((const void *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); - //m_udpSocket->writeDatagram((const char *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress, m_remotePort); - usleep(txDelay); - } - } - else - { - cm256Params.BlockBytes = sizeof(SDRDaemonProtectedBlock); - cm256Params.OriginalCount = UDPSinkFEC::m_nbOriginalBlocks; - cm256Params.RecoveryCount = nbBlocksFEC; - - - // Fill pointers to data - for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; ++i) - { - if (i >= cm256Params.OriginalCount) { - memset((char *) &txBlockx[i].m_protectedBlock, 0, sizeof(SDRDaemonProtectedBlock)); - } - - txBlockx[i].m_header.m_frameIndex = frameIndex; - txBlockx[i].m_header.m_blockIndex = i; - txBlockx[i].m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); - txBlockx[i].m_header.m_sampleBits = SDR_RX_SAMP_SZ; - 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)) - { - qDebug("UDPSinkFECWorker::encodeAndTransmit: CM256 encode failed. No transmission."); - return; - } - - // 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 - - for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) - { -#ifdef SDRDAEMON_PUNCTURE - if (i == SDRDAEMON_PUNCTURE) { - continue; - } -#endif - - m_socket.SendDataGram((const void *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); - usleep(txDelay); - } - } -} diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index 308e5db39..2aea27916 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -25,16 +25,10 @@ #include #include -#include "cm256.h" - #include "dsp/dsptypes.h" #include "util/CRC64.h" -#include "util/messagequeue.h" -#include "util/message.h" #include "channel/sdrdaemondatablock.h" -#include "UDPSocket.h" - class UDPSinkFECWorker; class UDPSinkFEC : public QObject @@ -105,101 +99,4 @@ private: UDPSinkFECWorker *m_udpWorker; }; - -class UDPSinkFECWorker : public QObject -{ - Q_OBJECT -public: - class MsgUDPFECEncodeAndSend : public Message - { - MESSAGE_CLASS_DECLARATION - public: - SDRDaemonSuperBlock *getTxBlocks() const { return m_txBlockx; } - uint32_t getNbBlocsFEC() const { return m_nbBlocksFEC; } - uint32_t getTxDelay() const { return m_txDelay; } - uint16_t getFrameIndex() const { return m_frameIndex; } - - static MsgUDPFECEncodeAndSend* create( - SDRDaemonSuperBlock *txBlocks, - uint32_t nbBlocksFEC, - uint32_t txDelay, - uint16_t frameIndex) - { - return new MsgUDPFECEncodeAndSend(txBlocks, nbBlocksFEC, txDelay, frameIndex); - } - - private: - SDRDaemonSuperBlock *m_txBlockx; - uint32_t m_nbBlocksFEC; - uint32_t m_txDelay; - uint16_t m_frameIndex; - - MsgUDPFECEncodeAndSend( - SDRDaemonSuperBlock *txBlocks, - uint32_t nbBlocksFEC, - uint32_t txDelay, - uint16_t frameIndex) : - m_txBlockx(txBlocks), - m_nbBlocksFEC(nbBlocksFEC), - m_txDelay(txDelay), - m_frameIndex(frameIndex) - {} - }; - - class MsgConfigureRemoteAddress : public Message - { - MESSAGE_CLASS_DECLARATION - public: - const QString& getAddress() const { return m_address; } - uint16_t getPort() const { return m_port; } - - static MsgConfigureRemoteAddress* create(const QString& address, uint16_t port) - { - return new MsgConfigureRemoteAddress(address, port); - } - - private: - QString m_address; - uint16_t m_port; - - MsgConfigureRemoteAddress(const QString& address, uint16_t port) : - m_address(address), - m_port(port) - {} - }; - - UDPSinkFECWorker(); - ~UDPSinkFECWorker(); - - void pushTxFrame(SDRDaemonSuperBlock *txBlocks, - uint32_t nbBlocksFEC, - uint32_t txDelay, - uint16_t frameIndex); - void setRemoteAddress(const QString& address, uint16_t port); - void stop(); - - MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication - -signals: - void finished(); - -public slots: - void process(); - -private slots: - void handleInputMessages(); - -private: - void encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay); - - volatile bool m_running; - CM256 m_cm256; //!< CM256 library object - bool m_cm256Valid; //!< true if CM256 library is initialized correctly - UDPSocket m_socket; - QString m_remoteAddress; - uint16_t m_remotePort; -}; - - - #endif /* PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFEC_H_ */ diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp new file mode 100644 index 000000000..7ced93f6d --- /dev/null +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp @@ -0,0 +1,161 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// 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 "udpsinkfecworker.h" + +MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgUDPFECEncodeAndSend, Message) +MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) + +UDPSinkFECWorker::UDPSinkFECWorker() : + m_running(false), + m_remotePort(9090) +{ + m_cm256Valid = m_cm256.isInitialized(); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::DirectConnection); +} + +UDPSinkFECWorker::~UDPSinkFECWorker() +{ + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_inputMessageQueue.clear(); +} + +void UDPSinkFECWorker::pushTxFrame(SDRDaemonSuperBlock *txBlocks, + uint32_t nbBlocksFEC, + uint32_t txDelay, + uint16_t frameIndex) +{ + //qDebug("UDPSinkFECWorker::pushTxFrame. %d", m_inputMessageQueue.size()); + m_inputMessageQueue.push(MsgUDPFECEncodeAndSend::create(txBlocks, nbBlocksFEC, txDelay, frameIndex)); +} + +void UDPSinkFECWorker::setRemoteAddress(const QString& address, uint16_t port) +{ + m_inputMessageQueue.push(MsgConfigureRemoteAddress::create(address, port)); +} + +void UDPSinkFECWorker::process() +{ + m_running = true; + + qDebug("UDPSinkFECWorker::process: started"); + + while (m_running) + { + usleep(250000); + } + + qDebug("UDPSinkFECWorker::process: stopped"); + emit finished(); +} + +void UDPSinkFECWorker::stop() +{ + m_running = false; +} + +void UDPSinkFECWorker::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgUDPFECEncodeAndSend::match(*message)) + { + MsgUDPFECEncodeAndSend *sendMsg = (MsgUDPFECEncodeAndSend *) message; + encodeAndTransmit(sendMsg->getTxBlocks(), sendMsg->getFrameIndex(), sendMsg->getNbBlocsFEC(), sendMsg->getTxDelay()); + } + else if (MsgConfigureRemoteAddress::match(*message)) + { + qDebug("UDPSinkFECWorker::handleInputMessages: %s", message->getIdentifier()); + MsgConfigureRemoteAddress *addressMsg = (MsgConfigureRemoteAddress *) message; + m_remoteAddress = addressMsg->getAddress(); + m_remotePort = addressMsg->getPort(); + } + + delete message; + } +} + +void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay) +{ + 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 + + if ((nbBlocksFEC == 0) || !m_cm256Valid) + { + for (unsigned int i = 0; i < SDRDaemonNbOrginalBlocks; i++) + { + m_socket.SendDataGram((const void *) &txBlockx[i], (int) SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); + //m_udpSocket->writeDatagram((const char *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress, m_remotePort); + 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((char *) &txBlockx[i].m_protectedBlock, 0, sizeof(SDRDaemonProtectedBlock)); + } + + txBlockx[i].m_header.m_frameIndex = frameIndex; + txBlockx[i].m_header.m_blockIndex = i; + txBlockx[i].m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + txBlockx[i].m_header.m_sampleBits = SDR_RX_SAMP_SZ; + 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)) + { + qDebug("UDPSinkFECWorker::encodeAndTransmit: CM256 encode failed. No transmission."); + return; + } + + // 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 + + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) + { +#ifdef SDRDAEMON_PUNCTURE + if (i == SDRDAEMON_PUNCTURE) { + continue; + } +#endif + + m_socket.SendDataGram((const void *) &txBlockx[i], (int) SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); + usleep(txDelay); + } + } +} + + + + diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h new file mode 100644 index 000000000..6ff53d30f --- /dev/null +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h @@ -0,0 +1,124 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 // +// // +// 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 PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ +#define PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ + +#include + +#include "cm256.h" + +#include "util/messagequeue.h" +#include "util/message.h" +#include "channel/sdrdaemondatablock.h" + +#include "UDPSocket.h" + +class UDPSinkFECWorker : public QObject +{ + Q_OBJECT +public: + class MsgUDPFECEncodeAndSend : public Message + { + MESSAGE_CLASS_DECLARATION + public: + SDRDaemonSuperBlock *getTxBlocks() const { return m_txBlockx; } + uint32_t getNbBlocsFEC() const { return m_nbBlocksFEC; } + uint32_t getTxDelay() const { return m_txDelay; } + uint16_t getFrameIndex() const { return m_frameIndex; } + + static MsgUDPFECEncodeAndSend* create( + SDRDaemonSuperBlock *txBlocks, + uint32_t nbBlocksFEC, + uint32_t txDelay, + uint16_t frameIndex) + { + return new MsgUDPFECEncodeAndSend(txBlocks, nbBlocksFEC, txDelay, frameIndex); + } + + private: + SDRDaemonSuperBlock *m_txBlockx; + uint32_t m_nbBlocksFEC; + uint32_t m_txDelay; + uint16_t m_frameIndex; + + MsgUDPFECEncodeAndSend( + SDRDaemonSuperBlock *txBlocks, + uint32_t nbBlocksFEC, + uint32_t txDelay, + uint16_t frameIndex) : + m_txBlockx(txBlocks), + m_nbBlocksFEC(nbBlocksFEC), + m_txDelay(txDelay), + m_frameIndex(frameIndex) + {} + }; + + class MsgConfigureRemoteAddress : public Message + { + MESSAGE_CLASS_DECLARATION + public: + const QString& getAddress() const { return m_address; } + uint16_t getPort() const { return m_port; } + + static MsgConfigureRemoteAddress* create(const QString& address, uint16_t port) + { + return new MsgConfigureRemoteAddress(address, port); + } + + private: + QString m_address; + uint16_t m_port; + + MsgConfigureRemoteAddress(const QString& address, uint16_t port) : + m_address(address), + m_port(port) + {} + }; + + UDPSinkFECWorker(); + ~UDPSinkFECWorker(); + + void pushTxFrame(SDRDaemonSuperBlock *txBlocks, + uint32_t nbBlocksFEC, + uint32_t txDelay, + uint16_t frameIndex); + void setRemoteAddress(const QString& address, uint16_t port); + void stop(); + + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + +signals: + void finished(); + +public slots: + void process(); + +private slots: + void handleInputMessages(); + +private: + void encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay); + + volatile bool m_running; + CM256 m_cm256; //!< CM256 library object + bool m_cm256Valid; //!< true if CM256 library is initialized correctly + UDPSocket m_socket; + QString m_remoteAddress; + uint16_t m_remotePort; +}; + +#endif /* PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ */ From 0d115ac342dc4cb03db2dcffabaefc06d3971ba7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 16 Sep 2018 20:50:56 +0200 Subject: [PATCH 765/956] DaemonSinkThread: removed useless member --- plugins/channelrx/daemonsink/daemonsinkthread.h | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.h b/plugins/channelrx/daemonsink/daemonsinkthread.h index e87f31ce8..5e468f7a9 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.h +++ b/plugins/channelrx/daemonsink/daemonsinkthread.h @@ -69,7 +69,6 @@ private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; bool m_running; - uint8_t m_sampleBytes; CM256 m_cm256; CM256 *m_cm256p; From 1e02b85702d76ffa64dca1b71733de5ab1452a0b Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 17 Sep 2018 03:33:18 +0200 Subject: [PATCH 766/956] SDRDaemonSink: make UDP worker a QThread --- .../sdrdaemonsink/sdrdaemonsinkthread.cpp | 2 + .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 47 +++++++----- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 7 +- .../sdrdaemonsink/udpsinkfecworker.cpp | 73 +++++++++++++------ .../sdrdaemonsink/udpsinkfecworker.h | 41 ++++++++--- 5 files changed, 119 insertions(+), 51 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp index 9c9d01816..bbe966cb8 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp @@ -48,6 +48,7 @@ SDRdaemonSinkThread::~SDRdaemonSinkThread() void SDRdaemonSinkThread::startWork() { qDebug() << "SDRdaemonSinkThread::startWork: "; + m_udpSinkFEC.start(); m_maxThrottlems = 0; m_startWaitMutex.lock(); m_elapsedTimer.start(); @@ -62,6 +63,7 @@ void SDRdaemonSinkThread::stopWork() qDebug() << "SDRdaemonSinkThread::stopWork"; m_running = false; wait(); + m_udpSinkFEC.stop(); } void SDRdaemonSinkThread::setSamplerate(int samplerate) diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index 8916fa443..cd1b6fb0f 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -34,33 +34,39 @@ UDPSinkFEC::UDPSinkFEC() : m_txBlockIndex(0), m_txBlocksIndex(0), m_frameCount(0), - m_sampleIndex(0) + m_sampleIndex(0), + m_udpWorker(0), + m_remoteAddress("127.0.0.1"), + m_remotePort(9090) { memset((char *) m_txBlocks, 0, 4*256*sizeof(SDRDaemonSuperBlock)); memset((char *) &m_superBlock, 0, sizeof(SDRDaemonSuperBlock)); m_currentMetaFEC.init(); m_bufMeta = new uint8_t[m_udpSize]; m_buf = new uint8_t[m_udpSize]; - m_udpThread = new QThread(); - m_udpWorker = new UDPSinkFECWorker(); - - m_udpWorker->moveToThread(m_udpThread); - - connect(m_udpThread, SIGNAL(started()), m_udpWorker, SLOT(process())); - connect(m_udpWorker, SIGNAL(finished()), m_udpThread, SLOT(quit())); - - m_udpThread->start(); } UDPSinkFEC::~UDPSinkFEC() { - m_udpWorker->stop(); - m_udpThread->wait(); - delete[] m_buf; delete[] m_bufMeta; - delete m_udpWorker; - delete m_udpThread; +} + +void UDPSinkFEC::start() +{ + m_udpWorker = new UDPSinkFECWorker(); + m_udpWorker->setRemoteAddress(m_remoteAddress, m_remotePort); + m_udpWorker->startStop(true); +} + +void UDPSinkFEC::stop() +{ + if (m_udpWorker) + { + m_udpWorker->startStop(false); + m_udpWorker->deleteLater(); + m_udpWorker = 0; + } } void UDPSinkFEC::setTxDelay(float txDelayRatio) @@ -93,7 +99,12 @@ void UDPSinkFEC::setSampleRate(uint32_t sampleRate) void UDPSinkFEC::setRemoteAddress(const QString& address, uint16_t port) { qDebug() << "UDPSinkFEC::setRemoteAddress: address: " << address << " port: " << port; - m_udpWorker->setRemoteAddress(address, port); + m_remoteAddress = address; + m_remotePort = port; + + if (m_udpWorker) { + m_udpWorker->setRemoteAddress(m_remoteAddress, m_remotePort); + } } void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunkSize) @@ -183,7 +194,9 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk int nbBlocksFEC = m_nbBlocksFEC; int txDelay = m_txDelay; - m_udpWorker->pushTxFrame(m_txBlocks[m_txBlocksIndex], nbBlocksFEC, txDelay, m_frameCount); + if (m_udpWorker) { + m_udpWorker->pushTxFrame(m_txBlocks[m_txBlocksIndex], nbBlocksFEC, txDelay, m_frameCount); + } m_txBlocksIndex = (m_txBlocksIndex + 1) % 4; m_txBlockIndex = 0; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index 2aea27916..36055522b 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -23,7 +23,6 @@ #include #include #include -#include #include "dsp/dsptypes.h" #include "util/CRC64.h" @@ -46,6 +45,9 @@ public: /** Destroy UDP sink */ ~UDPSinkFEC(); + void start(); + void stop(); + /** * Write IQ samples */ @@ -95,8 +97,9 @@ private: uint16_t m_frameCount; //!< transmission frame count int m_sampleIndex; //!< Current sample index in protected block data - QThread *m_udpThread; UDPSinkFECWorker *m_udpWorker; + QString m_remoteAddress; + uint16_t m_remotePort; }; #endif /* PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFEC_H_ */ diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp index 7ced93f6d..57693afb8 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp @@ -18,6 +18,7 @@ MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgUDPFECEncodeAndSend, Message) MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) +MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgStartStop, Message) UDPSinkFECWorker::UDPSinkFECWorker() : m_running(false), @@ -29,8 +30,45 @@ UDPSinkFECWorker::UDPSinkFECWorker() : UDPSinkFECWorker::~UDPSinkFECWorker() { - disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); - m_inputMessageQueue.clear(); +} + +void UDPSinkFECWorker::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void UDPSinkFECWorker::startWork() +{ + qDebug("UDPSinkFECWorker::startWork"); + m_startWaitMutex.lock(); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void UDPSinkFECWorker::stopWork() +{ + qDebug("UDPSinkFECWorker::stopWork"); + m_running = false; + wait(); +} + +void UDPSinkFECWorker::run() +{ + m_running = true; + m_startWaiter.wakeAll(); + + qDebug("UDPSinkFECWorker::process: started"); + + while (m_running) + { + sleep(1); + } + m_running = false; + + qDebug("UDPSinkFECWorker::process: stopped"); } void UDPSinkFECWorker::pushTxFrame(SDRDaemonSuperBlock *txBlocks, @@ -47,26 +85,6 @@ void UDPSinkFECWorker::setRemoteAddress(const QString& address, uint16_t port) m_inputMessageQueue.push(MsgConfigureRemoteAddress::create(address, port)); } -void UDPSinkFECWorker::process() -{ - m_running = true; - - qDebug("UDPSinkFECWorker::process: started"); - - while (m_running) - { - usleep(250000); - } - - qDebug("UDPSinkFECWorker::process: stopped"); - emit finished(); -} - -void UDPSinkFECWorker::stop() -{ - m_running = false; -} - void UDPSinkFECWorker::handleInputMessages() { Message* message; @@ -85,6 +103,17 @@ void UDPSinkFECWorker::handleInputMessages() m_remoteAddress = addressMsg->getAddress(); m_remotePort = addressMsg->getPort(); } + else 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/samplesink/sdrdaemonsink/udpsinkfecworker.h b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h index 6ff53d30f..0a9aa7a48 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h @@ -17,7 +17,9 @@ #ifndef PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ #define PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ -#include +#include +#include +#include #include "cm256.h" @@ -27,7 +29,7 @@ #include "UDPSocket.h" -class UDPSinkFECWorker : public QObject +class UDPSinkFECWorker : public QThread { Q_OBJECT public: @@ -89,31 +91,50 @@ 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) + { } + }; + UDPSinkFECWorker(); ~UDPSinkFECWorker(); + void startStop(bool start); + void pushTxFrame(SDRDaemonSuperBlock *txBlocks, uint32_t nbBlocksFEC, uint32_t txDelay, uint16_t frameIndex); void setRemoteAddress(const QString& address, uint16_t port); - void stop(); MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication -signals: - void finished(); - -public slots: - void process(); - private slots: void handleInputMessages(); private: + void startWork(); + void stopWork(); + void run(); void encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay); - volatile bool m_running; + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; CM256 m_cm256; //!< CM256 library object bool m_cm256Valid; //!< true if CM256 library is initialized correctly UDPSocket m_socket; From aac6d0962289db8cf4c4d6f7900d97bfc75b651a Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 17 Sep 2018 03:42:08 +0200 Subject: [PATCH 767/956] SDRDaemonSink: use QUdpSocket --- .../samplesink/sdrdaemonsink/udpsinkfecworker.cpp | 15 +++++++++++---- .../samplesink/sdrdaemonsink/udpsinkfecworker.h | 5 +++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp index 57693afb8..3b69111f6 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "udpsinkfecworker.h" MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgUDPFECEncodeAndSend, Message) @@ -25,7 +27,7 @@ UDPSinkFECWorker::UDPSinkFECWorker() : m_remotePort(9090) { m_cm256Valid = m_cm256.isInitialized(); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::DirectConnection); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); } UDPSinkFECWorker::~UDPSinkFECWorker() @@ -42,6 +44,7 @@ void UDPSinkFECWorker::startWork() { qDebug("UDPSinkFECWorker::startWork"); m_startWaitMutex.lock(); + m_udpSocket = new QUdpSocket(this); start(); while(!m_running) m_startWaiter.wait(&m_startWaitMutex, 100); @@ -51,6 +54,8 @@ void UDPSinkFECWorker::startWork() void UDPSinkFECWorker::stopWork() { qDebug("UDPSinkFECWorker::stopWork"); + delete m_udpSocket; + m_udpSocket = 0; m_running = false; wait(); } @@ -102,6 +107,7 @@ void UDPSinkFECWorker::handleInputMessages() MsgConfigureRemoteAddress *addressMsg = (MsgConfigureRemoteAddress *) message; m_remoteAddress = addressMsg->getAddress(); m_remotePort = addressMsg->getPort(); + m_remoteHostAddress.setAddress(addressMsg->getAddress()); } else if (MsgStartStop::match(*message)) { @@ -129,8 +135,8 @@ void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t { for (unsigned int i = 0; i < SDRDaemonNbOrginalBlocks; i++) { - m_socket.SendDataGram((const void *) &txBlockx[i], (int) SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); - //m_udpSocket->writeDatagram((const char *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress, m_remotePort); + //m_socket.SendDataGram((const void *) &txBlockx[i], SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); + m_udpSocket->writeDatagram((const char *) &txBlockx[i], SDRDaemonUdpSize, m_remoteHostAddress, m_remotePort); usleep(txDelay); } } @@ -179,7 +185,8 @@ void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t } #endif - m_socket.SendDataGram((const void *) &txBlockx[i], (int) SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); + //m_socket.SendDataGram((const void *) &txBlockx[i], SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); + m_udpSocket->writeDatagram((const char *) &txBlockx[i], SDRDaemonUdpSize, m_remoteHostAddress, m_remotePort); usleep(txDelay); } } diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h index 0a9aa7a48..3fcbb6b01 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "cm256.h" @@ -29,6 +30,8 @@ #include "UDPSocket.h" +class QUdpSocket; + class UDPSinkFECWorker : public QThread { Q_OBJECT @@ -138,8 +141,10 @@ private: CM256 m_cm256; //!< CM256 library object bool m_cm256Valid; //!< true if CM256 library is initialized correctly UDPSocket m_socket; + QUdpSocket *m_udpSocket; QString m_remoteAddress; uint16_t m_remotePort; + QHostAddress m_remoteHostAddress; }; #endif /* PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ */ From 7bbb0b426d1f5c0076930e2c318f1ef80f73b115 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 17 Sep 2018 03:53:46 +0200 Subject: [PATCH 768/956] SDRDaemonSink: use QUdpSocket: cleanup --- .../samplesink/sdrdaemonsink/CMakeLists.txt | 2 -- .../sdrdaemonsink/sdrdaemonsinksettings.cpp | 2 +- .../sdrdaemonsink/udpsinkfecworker.cpp | 33 +++++++++++-------- .../sdrdaemonsink/udpsinkfecworker.h | 3 -- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt index d6e7e4c2c..af5f6031f 100644 --- a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt +++ b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt @@ -19,7 +19,6 @@ set(sdrdaemonsink_SOURCES sdrdaemonsinkthread.cpp udpsinkfec.cpp udpsinkfecworker.cpp - UDPSocket.cpp ) set(sdrdaemonsink_HEADERS @@ -30,7 +29,6 @@ set(sdrdaemonsink_HEADERS sdrdaemonsinkthread.h udpsinkfec.h udpsinkfecworker.h - UDPSocket.h ) set(sdrdaemonsink_FORMS diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp index 7cc5d4548..8467b259d 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp @@ -26,7 +26,7 @@ void SDRdaemonSinkSettings::resetToDefaults() { m_centerFrequency = 435000*1000; m_sampleRate = 48000; - m_txDelay = 0.5; + m_txDelay = 0.35; m_nbFECBlocks = 0; m_apiAddress = "127.0.0.1"; m_apiPort = 9091; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp index 3b69111f6..7618c4421 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp @@ -24,6 +24,7 @@ MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgStartStop, Message) UDPSinkFECWorker::UDPSinkFECWorker() : m_running(false), + m_udpSocket(0), m_remotePort(9090) { m_cm256Valid = m_cm256.isInitialized(); @@ -133,11 +134,14 @@ void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t if ((nbBlocksFEC == 0) || !m_cm256Valid) { - for (unsigned int i = 0; i < SDRDaemonNbOrginalBlocks; i++) + if (m_udpSocket) { - //m_socket.SendDataGram((const void *) &txBlockx[i], SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); - m_udpSocket->writeDatagram((const char *) &txBlockx[i], SDRDaemonUdpSize, m_remoteHostAddress, m_remotePort); - usleep(txDelay); + for (unsigned int i = 0; i < SDRDaemonNbOrginalBlocks; i++) + { + //m_socket.SendDataGram((const void *) &txBlockx[i], SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); + m_udpSocket->writeDatagram((const char *) &txBlockx[i], SDRDaemonUdpSize, m_remoteHostAddress, m_remotePort); + usleep(txDelay); + } } } else @@ -176,18 +180,19 @@ void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t } // Transmit all blocks - - for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) + if (m_udpSocket) { -#ifdef SDRDAEMON_PUNCTURE - if (i == SDRDAEMON_PUNCTURE) { - continue; - } -#endif + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) + { + #ifdef SDRDAEMON_PUNCTURE + if (i == SDRDAEMON_PUNCTURE) { + continue; + } + #endif - //m_socket.SendDataGram((const void *) &txBlockx[i], SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); - m_udpSocket->writeDatagram((const char *) &txBlockx[i], SDRDaemonUdpSize, m_remoteHostAddress, m_remotePort); - usleep(txDelay); + m_udpSocket->writeDatagram((const char *) &txBlockx[i], SDRDaemonUdpSize, m_remoteHostAddress, m_remotePort); + usleep(txDelay); + } } } } diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h index 3fcbb6b01..32580aa3f 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h @@ -28,8 +28,6 @@ #include "util/message.h" #include "channel/sdrdaemondatablock.h" -#include "UDPSocket.h" - class QUdpSocket; class UDPSinkFECWorker : public QThread @@ -140,7 +138,6 @@ private: bool m_running; CM256 m_cm256; //!< CM256 library object bool m_cm256Valid; //!< true if CM256 library is initialized correctly - UDPSocket m_socket; QUdpSocket *m_udpSocket; QString m_remoteAddress; uint16_t m_remotePort; From a5ebd0198bf1e06e82d54847593084a8702f94eb Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 17 Sep 2018 04:08:35 +0200 Subject: [PATCH 769/956] SDRDaemonSink: fixed server CMake file --- pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt index d5f794ba3..ace22f7d2 100644 --- a/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt +++ b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt @@ -18,7 +18,7 @@ set(sdrdaemonsink_SOURCES ${PLUGIN_PREFIX}/sdrdaemonsinksettings.cpp ${PLUGIN_PREFIX}/sdrdaemonsinkthread.cpp ${PLUGIN_PREFIX}/udpsinkfec.cpp - ${PLUGIN_PREFIX}/UDPSocket.cpp + ${PLUGIN_PREFIX}/udpsinkfecworker.cpp ) set(sdrdaemonsink_HEADERS @@ -27,7 +27,7 @@ set(sdrdaemonsink_HEADERS ${PLUGIN_PREFIX}/sdrdaemonsinksettings.h ${PLUGIN_PREFIX}/sdrdaemonsinkthread.h ${PLUGIN_PREFIX}/udpsinkfec.h - ${PLUGIN_PREFIX}/UDPSocket.h + ${PLUGIN_PREFIX}/udpsinkfecworker.h ) if (BUILD_DEBIAN) From 1894f0ba02283714994fde764cb74833b693d938 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 17 Sep 2018 09:24:05 +0200 Subject: [PATCH 770/956] Windows build: remove SDRDaemon plugins --- plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro | 12 ++++++++---- sdrangel.windows.pro | 6 +++--- windows.install.bat | 5 +++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro b/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro index e6618fbf8..51a2d307a 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro @@ -34,21 +34,25 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -CONFIG(MINGW32):INCLUDEPATH += "C:\boost_1_66_0" -CONFIG(MINGW64):INCLUDEPATH += "C:\boost_1_66_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" SOURCES += sdrdaemonsinkthread.cpp\ sdrdaemonsinkgui.cpp\ sdrdaemonsinkoutput.cpp\ sdrdaemonsinksettings.cpp\ -sdrdaemonsinkplugin.cpp +sdrdaemonsinkplugin.cpp\ +udpsinkfec.cpp\ +udpsinkfecworker.cpp HEADERS += sdrdaemonsinkthread.h\ sdrdaemonsinkgui.h\ sdrdaemonsinkoutput.h\ sdrdaemonsinksettings.h\ -sdrdaemonsinkplugin.h +sdrdaemonsinkplugin.h\ +udpsinkfec.h\ +udpsinkfecworker.h FORMS += sdrdaemonsinkgui.ui diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index f8b6b5e91..68363a6ee 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -22,14 +22,13 @@ SUBDIRS += libairspyhf SUBDIRS += libbladerf SUBDIRS += libhackrf SUBDIRS += libiio -#SUBDIRS += libsqlite3 SUBDIRS += liblimesuite SUBDIRS += libperseus SUBDIRS += librtlsdr SUBDIRS += devices SUBDIRS += mbelib SUBDIRS += dsdcc -SUBDIRS += cm256cc +#SUBDIRS += cm256cc SUBDIRS += plugins/samplesource/airspy SUBDIRS += plugins/samplesource/airspyhf SUBDIRS += plugins/samplesource/bladerfinput @@ -37,7 +36,7 @@ SUBDIRS += plugins/samplesource/filesource SUBDIRS += plugins/samplesource/hackrfinput SUBDIRS += plugins/samplesource/limesdrinput SUBDIRS += plugins/samplesource/plutosdrinput -SUBDIRS += plugins/samplesource/sdrdaemonsource +#SUBDIRS += plugins/samplesource/sdrdaemonsource SUBDIRS += plugins/samplesource/rtlsdr SUBDIRS += plugins/samplesource/testsource SUBDIRS += plugins/samplesink/filesink @@ -45,6 +44,7 @@ SUBDIRS += plugins/samplesink/bladerfoutput SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput +#SUBDIRS += plugins/samplesink/sdrdaemonsink SUBDIRS += plugins/channelrx/chanalyzer SUBDIRS += plugins/channelrx/demodam SUBDIRS += plugins/channelrx/demodatv diff --git a/windows.install.bat b/windows.install.bat index 6e1b47e72..eb0ee05a2 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -16,7 +16,7 @@ copy httpserver\%1\httpserver.dll %2 copy qrtplib\%1\qrtplib.dll %2 copy swagger\%1\swagger.dll %2 copy logging\%1\logging.dll %2 -copy cm256cc\%1\cm256cc.dll %2 +REM copy cm256cc\%1\cm256cc.dll %2 copy libhackrf\%1\libhackrf.dll %2 copy librtlsdr\%1\librtlsdr.dll %2 copy libairspy\%1\libairspy.dll %2 @@ -63,9 +63,10 @@ copy plugins\samplesource\airspyhf\%1\inputairspyhf.dll %2\plugins\samplesource copy plugins\samplesource\bladerfinput\%1\inputbladerf.dll %2\plugins\samplesource copy plugins\samplesource\limesdrinput\%1\inputlimesdr.dll %2\plugins\samplesource copy plugins\samplesource\plutosdrinput\%1\inputplutosdr.dll %2\plugins\samplesource -copy plugins\samplesource\sdrdaemonsource\%1\inputsdrdaemonsource.dll %2\plugins\samplesource +REM copy plugins\samplesource\sdrdaemonsource\%1\inputsdrdaemonsource.dll %2\plugins\samplesource copy plugins\samplesink\filesink\%1\outputfilesink.dll %2\plugins\samplesink copy plugins\samplesink\bladerfoutput\%1\outputbladerf.dll %2\plugins\samplesink copy plugins\samplesink\hackrfoutput\%1\outputhackrf.dll %2\plugins\samplesink copy plugins\samplesink\limesdroutput\%1\outputlimesdr.dll %2\plugins\samplesink copy plugins\samplesink\plutosdroutput\%1\outputplutosdr.dll %2\plugins\samplesink +REM copy plugins\samplesink\sdrdaemonsink\%1\outputsdrdaemonsink.dll %2\plugins\samplesink From d2a740425b1cea2fd4a0f63413c67719ed539781 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 18 Sep 2018 00:08:15 +0200 Subject: [PATCH 771/956] SDRDaemonSink: always set center frequency on API report analysis --- Readme.md | 4 ++++ plugins/samplesink/sdrdaemonsink/readme.md | 2 ++ plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp | 9 +++++---- plugins/samplesource/sdrdaemonsource/readme.md | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index a497a81c1..54c920605 100644 --- a/Readme.md +++ b/Readme.md @@ -211,6 +211,8 @@ The [Test source plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/s

    SDRdaemon receiver input

    +Linux only. + The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of an instance of SDRangel running the Daemon Sink channel plugin. On the "Data" line you must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiving center frequency. The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. However it is used just to display basic information about the remote. The data blocks transmitted via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. @@ -223,6 +225,8 @@ Note that this plugin does not require any of the hardware support libraries nor

    SDRdaemon transmitter output

    +Linux only. + The [SDRdaemon sink output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) is the client side of and instance of SDRangel running the Daemon Source channel plugin. On the "Data" line you must specify the distant address and UDP port to which the plugin connects and samples from the SDRangel application will flow into the transmitter server (default is `127.0.0.1`port `9090`). The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. The API is pinged regularly to retrieve the status of the data blocks queue and allow rate control to stabilize the queue length. Therefore it is important to connect to the API properly (The status line must return "API OK" and the API label should be lit in green). The data blocks sent via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. diff --git a/plugins/samplesink/sdrdaemonsink/readme.md b/plugins/samplesink/sdrdaemonsink/readme.md index 82db57950..e8236643a 100644 --- a/plugins/samplesink/sdrdaemonsink/readme.md +++ b/plugins/samplesink/sdrdaemonsink/readme.md @@ -10,6 +10,8 @@ The distant SDRangel instance to which the data stream is sent is controlled via The sample size used in the I/Q stream is the Rx sample size of the local instance. Possible conversion takes place in the distant Daemon source channel plugin to match the Rx sample size of the distant instance. Best performace is obtained when both instances use the same sample size. +It is present only in Linux binary releases. +

    Build

    The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index f47b42b48..0403c80c2 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -474,14 +474,15 @@ void SDRdaemonSinkOutput::networkManagerFinished(QNetworkReply *reply) void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) { - if (!m_sdrDaemonSinkThread) { - return; - } - if (jsonObject.contains("DaemonSourceReport")) { QJsonObject report = jsonObject["DaemonSourceReport"].toObject(); m_centerFrequency = report["deviceCenterFreq"].toInt() * 1000; + + if (!m_sdrDaemonSinkThread) { + return; + } + int queueSize = report["queueSize"].toInt(); queueSize = queueSize == 0 ? 10 : queueSize; int queueLength = report["queueLength"].toInt(); diff --git a/plugins/samplesource/sdrdaemonsource/readme.md b/plugins/samplesource/sdrdaemonsource/readme.md index 337165ce7..50313c04c 100644 --- a/plugins/samplesource/sdrdaemonsource/readme.md +++ b/plugins/samplesource/sdrdaemonsource/readme.md @@ -12,6 +12,8 @@ The distant SDRangel instance that sends the data stream is controlled via its R A sample size conversion takes place if the stream sample size sent by the distant instance and the Rx sample size of the local instance do not match (i.e. 16 to 24 bits or 24 to 16 bits). Best performace is obtained when both instances use the same sample size. +It is present only in Linux binary releases. +

    Build

    The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. From d5969312090e9c2d9a960f65b72611bf777ed498 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 18 Sep 2018 23:12:00 +0200 Subject: [PATCH 772/956] LibbladeRF2: make bladeRF1 work --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 ++++++ devices/bladerf/devicebladerf.cpp | 14 ++++++++------ plugins/samplesink/bladerfoutput/bladerfoutput.cpp | 2 +- .../bladerfoutput/bladerfoutputplugin.cpp | 2 +- plugins/samplesource/bladerfinput/bladerfinput.cpp | 6 ++++-- .../bladerfinput/bladerfinputplugin.cpp | 2 +- sdrbase/dsp/dspdevicesinkengine.cpp | 2 +- 10 files changed, 25 insertions(+), 15 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 549e95fe1..464499d50 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.1.0"); + QCoreApplication::setApplicationVersion("4.2.0"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index f941b7150..61eca0e2b 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.1.0"); + QCoreApplication::setApplicationVersion("4.2.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index dda2961ee..6230ef960 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.1.0"); + QCoreApplication::setApplicationVersion("4.2.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 415cc7da9..66bc4c1fa 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (4.2.0-1) unstable; urgency=medium + + * LibbladeRF 2.0 support with BladeRF Micro + + -- Edouard Griffiths, F4EXB Sun, 14 Oct 2018 21:14:18 +0200 + sdrangel (4.1.0-1) unstable; urgency=medium * Integrated SDRdaemon with a pair of new channel plugins diff --git a/devices/bladerf/devicebladerf.cpp b/devices/bladerf/devicebladerf.cpp index ff88ed756..f650f4015 100644 --- a/devices/bladerf/devicebladerf.cpp +++ b/devices/bladerf/devicebladerf.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include #include #include "devicebladerf.h" @@ -24,7 +26,7 @@ bool DeviceBladeRF::open_bladerf(struct bladerf **dev, const char *serial) if ((*dev = open_bladerf_from_serial(serial)) == 0) { - fprintf(stderr, "DeviceBladeRF::open_bladerf: could not open BladeRF\n"); + qCritical("DeviceBladeRF::open_bladerf: could not open BladeRF"); return false; } @@ -32,13 +34,13 @@ bool DeviceBladeRF::open_bladerf(struct bladerf **dev, const char *serial) if (fpga_loaded < 0) { - fprintf(stderr, "DeviceBladeRF::open_bladerf: failed to check FPGA state: %s\n", - bladerf_strerror(fpga_loaded)); + qCritical("DeviceBladeRF::open_bladerf: failed to check FPGA state: %s", + bladerf_strerror(fpga_loaded)); return false; } else if (fpga_loaded == 0) { - fprintf(stderr, "BladerfOutput::start: the device's FPGA is not loaded.\n"); + qCritical("BladerfOutput::start: the device's FPGA is not loaded."); return false; } @@ -69,12 +71,12 @@ struct bladerf *DeviceBladeRF::open_bladerf_from_serial(const char *serial) if (status == BLADERF_ERR_NODEV) { - fprintf(stderr, "DeviceBladeRF::open_bladerf_from_serial: No devices available with serial=%s\n", serial); + qCritical("DeviceBladeRF::open_bladerf_from_serial: No devices available with serial %s", serial); return 0; } else if (status != 0) { - fprintf(stderr, "DeviceBladeRF::open_bladerf_from_serial: Failed to open device with serial=%s (%s)\n", + qCritical("DeviceBladeRF::open_bladerf_from_serial: Failed to open device with serial %s (%s)", serial, bladerf_strerror(status)); return 0; } diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp index a695b67dc..9f279bc04 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp @@ -103,7 +103,7 @@ bool BladerfOutput::openDevice() } // TODO: adjust USB transfer data according to sample rate - if ((res = bladerf_sync_config(m_dev, BLADERF_MODULE_TX, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) + if ((res = bladerf_sync_config(m_dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) { qCritical("BladerfOutput::start: bladerf_sync_config with return code %d", res); return false; diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp index 92dab83a8..2dc8e7df4 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor BladerfOutputPlugin::m_pluginDescriptor = { QString("BladeRF Output"), - QString("3.14.5"), + QString("4.2.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 419f3935e..78c225a3a 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -112,7 +112,7 @@ bool BladerfInput::openDevice() } // TODO: adjust USB transfer data according to sample rate - if ((res = bladerf_sync_config(m_dev, BLADERF_MODULE_RX, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) + if ((res = bladerf_sync_config(m_dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) { qCritical("BladerfInput::start: bladerf_sync_config with return code %d", res); return false; @@ -136,7 +136,9 @@ bool BladerfInput::start() { // QMutexLocker mutexLocker(&m_mutex); - if (!m_dev) { + if (!m_dev) + { + qDebug("BladerfInput::start: no device handle"); return false; } diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index 6cf31a871..b9a79a471 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { QString("BladeRF Input"), - QString("4.0.0"), + QString("4.2.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/dsp/dspdevicesinkengine.cpp b/sdrbase/dsp/dspdevicesinkengine.cpp index a890f1875..aa6b4246f 100644 --- a/sdrbase/dsp/dspdevicesinkengine.cpp +++ b/sdrbase/dsp/dspdevicesinkengine.cpp @@ -362,7 +362,7 @@ DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoRunning() if(!m_deviceSampleSink->start()) { - return gotoError("DSPDeviceSinkEngine::gotoRunning: Could not start sample source"); + return gotoError("DSPDeviceSinkEngine::gotoRunning: Could not start sample sink"); } for(BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); it++) From 8433f63a9b6fa6c82535fb3f1293b6f088ae4bef Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 19 Sep 2018 05:26:18 +0200 Subject: [PATCH 773/956] LibbladeRF2: migrate devices/bladerf to devices/bladerf1 --- devices/CMakeLists.txt | 4 +-- devices/{bladerf => bladerf1}/CMakeLists.txt | 30 +++++++++---------- .../devicebladerf1.cpp} | 7 +++-- .../devicebladerf1.h} | 2 +- .../devicebladerf1param.h} | 10 +++---- .../devicebladerf1shared.cpp} | 8 ++--- .../devicebladerf1shared.h} | 2 +- .../devicebladerf1values.cpp} | 14 ++++----- .../devicebladerf1values.h} | 8 ++--- .../samplesink/bladerfoutput/CMakeLists.txt | 4 +-- .../bladerfoutput/bladerfoutput.cpp | 14 ++++----- .../samplesink/bladerfoutput/bladerfoutput.h | 7 ++--- .../bladerfoutput/bladerfoutputgui.cpp | 3 +- .../samplesource/bladerfinput/CMakeLists.txt | 4 +-- .../bladerfinput/bladerfinput.cpp | 4 +-- .../samplesource/bladerfinput/bladerfinput.h | 7 +++-- .../samplesink/bladerfoutput/CMakeLists.txt | 4 +-- .../samplesource/bladerfinput/CMakeLists.txt | 4 +-- 18 files changed, 69 insertions(+), 67 deletions(-) rename devices/{bladerf => bladerf1}/CMakeLists.txt (51%) rename devices/{bladerf/devicebladerf.cpp => bladerf1/devicebladerf1.cpp} (95%) rename devices/{bladerf/devicebladerf.h => bladerf1/devicebladerf1.h} (98%) rename devices/{bladerf/devicebladerfparam.h => bladerf1/devicebladerf1param.h} (89%) rename devices/{bladerf/devicebladerfshared.cpp => bladerf1/devicebladerf1shared.cpp} (80%) rename devices/{bladerf/devicebladerfshared.h => bladerf1/devicebladerf1shared.h} (97%) rename devices/{bladerf/devicebladerfvalues.cpp => bladerf1/devicebladerf1values.cpp} (80%) rename devices/{bladerf/devicebladerfvalues.h => bladerf1/devicebladerf1values.h} (88%) diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt index 66429d0c1..1545c534d 100644 --- a/devices/CMakeLists.txt +++ b/devices/CMakeLists.txt @@ -3,7 +3,7 @@ project(devices) find_package(LibUSB) if (BUILD_DEBIAN) - add_subdirectory(bladerf) + add_subdirectory(bladerf1) add_subdirectory(hackrf) add_subdirectory(limesdr) add_subdirectory(perseus) @@ -11,7 +11,7 @@ if (BUILD_DEBIAN) else(BUILD_DEBIAN) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) - add_subdirectory(bladerf) + add_subdirectory(bladerf1) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) find_package(LibHACKRF) diff --git a/devices/bladerf/CMakeLists.txt b/devices/bladerf1/CMakeLists.txt similarity index 51% rename from devices/bladerf/CMakeLists.txt rename to devices/bladerf1/CMakeLists.txt index 1d698cbdd..ddb327d5f 100644 --- a/devices/bladerf/CMakeLists.txt +++ b/devices/bladerf1/CMakeLists.txt @@ -1,16 +1,16 @@ -project(bladerfdevice) +project(bladerf1device) -set(bladerfdevice_SOURCES - devicebladerf.cpp - devicebladerfvalues.cpp - devicebladerfshared.cpp +set(bladerf1device_SOURCES + devicebladerf1.cpp + devicebladerf1values.cpp + devicebladerf1shared.cpp ) -set(bladerfdevice_HEADERS - devicebladerf.h - devicebladerfvalues.h - devicebladerfparam.h - devicebladerfshared.h +set(bladerf1device_HEADERS + devicebladerf1.h + devicebladerf1values.h + devicebladerf1param.h + devicebladerf1shared.h ) if (BUILD_DEBIAN) @@ -31,20 +31,20 @@ endif (BUILD_DEBIAN) #add_definitions(${QT_DEFINITIONS}) #add_definitions(-DQT_SHARED) -add_library(bladerfdevice SHARED - ${bladerfdevice_SOURCES} +add_library(bladerf1device SHARED + ${bladerf1device_SOURCES} ) if (BUILD_DEBIAN) -target_link_libraries(bladerfdevice +target_link_libraries(bladerf1device bladerf sdrbase ) else (BUILD_DEBIAN) -target_link_libraries(bladerfdevice +target_link_libraries(bladerf1device ${LIBBLADERF_LIBRARIES} sdrbase ) endif (BUILD_DEBIAN) -install(TARGETS bladerfdevice DESTINATION lib) +install(TARGETS bladerf1device DESTINATION lib) diff --git a/devices/bladerf/devicebladerf.cpp b/devices/bladerf1/devicebladerf1.cpp similarity index 95% rename from devices/bladerf/devicebladerf.cpp rename to devices/bladerf1/devicebladerf1.cpp index f650f4015..14ab43374 100644 --- a/devices/bladerf/devicebladerf.cpp +++ b/devices/bladerf1/devicebladerf1.cpp @@ -14,13 +14,14 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "../bladerf1/devicebladerf1.h" + #include #include #include -#include "devicebladerf.h" -bool DeviceBladeRF::open_bladerf(struct bladerf **dev, const char *serial) +bool DeviceBladeRF1::open_bladerf(struct bladerf **dev, const char *serial) { int fpga_loaded; @@ -47,7 +48,7 @@ bool DeviceBladeRF::open_bladerf(struct bladerf **dev, const char *serial) return true; } -struct bladerf *DeviceBladeRF::open_bladerf_from_serial(const char *serial) +struct bladerf *DeviceBladeRF1::open_bladerf_from_serial(const char *serial) { int status; struct bladerf *dev; diff --git a/devices/bladerf/devicebladerf.h b/devices/bladerf1/devicebladerf1.h similarity index 98% rename from devices/bladerf/devicebladerf.h rename to devices/bladerf1/devicebladerf1.h index 09751ea93..f8a9d1cad 100644 --- a/devices/bladerf/devicebladerf.h +++ b/devices/bladerf1/devicebladerf1.h @@ -21,7 +21,7 @@ #include "export.h" -class DEVICES_API DeviceBladeRF +class DEVICES_API DeviceBladeRF1 { public: static bool open_bladerf(struct bladerf **dev, const char *serial); diff --git a/devices/bladerf/devicebladerfparam.h b/devices/bladerf1/devicebladerf1param.h similarity index 89% rename from devices/bladerf/devicebladerfparam.h rename to devices/bladerf1/devicebladerf1param.h index 8bd4349ec..ad76a67f9 100644 --- a/devices/bladerf/devicebladerfparam.h +++ b/devices/bladerf1/devicebladerf1param.h @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef DEVICES_BLADERF_DEVICEBLADERFPARAM_H_ -#define DEVICES_BLADERF_DEVICEBLADERFPARAM_H_ +#ifndef DEVICES_BLADERF1_DEVICEBLADERF1PARAM_H_ +#define DEVICES_BLADERF1_DEVICEBLADERF1PARAM_H_ #include @@ -23,16 +23,16 @@ * This structure is owned by each of the parties sharing the same physical device * It allows exchange of information on the common resources */ -struct DeviceBladeRFParams +struct DeviceBladeRF1Params { struct bladerf *m_dev; //!< device handle if the party has ownership else 0 bool m_xb200Attached; //!< true if XB200 is attached and owned by the party - DeviceBladeRFParams() : + DeviceBladeRF1Params() : m_dev(0), m_xb200Attached(false) { } }; -#endif /* DEVICES_BLADERF_DEVICEBLADERFPARAM_H_ */ +#endif /* DEVICES_BLADERF1_DEVICEBLADERF1PARAM_H_ */ diff --git a/devices/bladerf/devicebladerfshared.cpp b/devices/bladerf1/devicebladerf1shared.cpp similarity index 80% rename from devices/bladerf/devicebladerfshared.cpp rename to devices/bladerf1/devicebladerf1shared.cpp index 636318a00..361e430ec 100644 --- a/devices/bladerf/devicebladerfshared.cpp +++ b/devices/bladerf1/devicebladerf1shared.cpp @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "devicebladerfshared.h" +#include "../bladerf1/devicebladerf1shared.h" -const float DeviceBladeRFShared::m_sampleFifoLengthInSeconds = 0.25; -const int DeviceBladeRFShared::m_sampleFifoMinSize = 75000; // 300 kS/s knee -const int DeviceBladeRFShared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32 +const float DeviceBladeRF1Shared::m_sampleFifoLengthInSeconds = 0.25; +const int DeviceBladeRF1Shared::m_sampleFifoMinSize = 75000; // 300 kS/s knee +const int DeviceBladeRF1Shared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32 diff --git a/devices/bladerf/devicebladerfshared.h b/devices/bladerf1/devicebladerf1shared.h similarity index 97% rename from devices/bladerf/devicebladerfshared.h rename to devices/bladerf1/devicebladerf1shared.h index 2fe2d7f89..cc893c77a 100644 --- a/devices/bladerf/devicebladerfshared.h +++ b/devices/bladerf1/devicebladerf1shared.h @@ -20,7 +20,7 @@ #include "util/message.h" #include "export.h" -class DEVICES_API DeviceBladeRFShared +class DEVICES_API DeviceBladeRF1Shared { public: static const float m_sampleFifoLengthInSeconds; diff --git a/devices/bladerf/devicebladerfvalues.cpp b/devices/bladerf1/devicebladerf1values.cpp similarity index 80% rename from devices/bladerf/devicebladerfvalues.cpp rename to devices/bladerf1/devicebladerf1values.cpp index fb44ad6c2..522953ae0 100644 --- a/devices/bladerf/devicebladerfvalues.cpp +++ b/devices/bladerf1/devicebladerf1values.cpp @@ -14,11 +14,11 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "devicebladerfvalues.h" +#include "../bladerf1/devicebladerf1values.h" -unsigned int DeviceBladeRFBandwidths::m_nb_halfbw = 16; -unsigned int DeviceBladeRFBandwidths::m_halfbw[] = { +unsigned int DeviceBladeRF1Bandwidths::m_nb_halfbw = 16; +unsigned int DeviceBladeRF1Bandwidths::m_halfbw[] = { 750, 875, 1250, @@ -36,7 +36,7 @@ unsigned int DeviceBladeRFBandwidths::m_halfbw[] = { 10000, 14000}; -unsigned int DeviceBladeRFBandwidths::getBandwidth(unsigned int bandwidth_index) +unsigned int DeviceBladeRF1Bandwidths::getBandwidth(unsigned int bandwidth_index) { if (bandwidth_index < m_nb_halfbw) { @@ -48,7 +48,7 @@ unsigned int DeviceBladeRFBandwidths::getBandwidth(unsigned int bandwidth_index) } } -unsigned int DeviceBladeRFBandwidths::getBandwidthIndex(unsigned int bandwidth) +unsigned int DeviceBladeRF1Bandwidths::getBandwidthIndex(unsigned int bandwidth) { for (unsigned int i=0; i < m_nb_halfbw; i++) { @@ -61,9 +61,9 @@ unsigned int DeviceBladeRFBandwidths::getBandwidthIndex(unsigned int bandwidth) return 0; } -unsigned int DeviceBladeRFBandwidths::getNbBandwidths() +unsigned int DeviceBladeRF1Bandwidths::getNbBandwidths() { - return DeviceBladeRFBandwidths::m_nb_halfbw; + return DeviceBladeRF1Bandwidths::m_nb_halfbw; } diff --git a/devices/bladerf/devicebladerfvalues.h b/devices/bladerf1/devicebladerf1values.h similarity index 88% rename from devices/bladerf/devicebladerfvalues.h rename to devices/bladerf1/devicebladerf1values.h index 2498e6cd6..83afa961b 100644 --- a/devices/bladerf/devicebladerfvalues.h +++ b/devices/bladerf1/devicebladerf1values.h @@ -14,12 +14,12 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ -#define DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ +#ifndef DEVICES_BLADERF1_DEVICEBLADERF1VALUES_H_ +#define DEVICES_BLADERF1_DEVICEBLADERF1VALUES_H_ #include "export.h" -class DEVICES_API DeviceBladeRFBandwidths { +class DEVICES_API DeviceBladeRF1Bandwidths { public: static unsigned int getBandwidth(unsigned int bandwidth_index); static unsigned int getBandwidthIndex(unsigned int bandwidth); @@ -29,4 +29,4 @@ private: static unsigned int m_nb_halfbw; }; -#endif /* DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ */ +#endif /* DEVICES_BLADERF1_DEVICEBLADERF1VALUES_H_ */ diff --git a/plugins/samplesink/bladerfoutput/CMakeLists.txt b/plugins/samplesink/bladerfoutput/CMakeLists.txt index 719a7442e..6b3d35250 100644 --- a/plugins/samplesink/bladerfoutput/CMakeLists.txt +++ b/plugins/samplesink/bladerfoutput/CMakeLists.txt @@ -62,7 +62,7 @@ target_link_libraries(outputbladerf sdrbase sdrgui swagger - bladerfdevice + bladerf1device ) else (BUILD_DEBIAN) target_link_libraries(outputbladerf @@ -71,7 +71,7 @@ target_link_libraries(outputbladerf sdrbase sdrgui swagger - bladerfdevice + bladerf1device ) endif (BUILD_DEBIAN) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp index 9f279bc04..de47352e2 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp @@ -26,9 +26,9 @@ #include "dsp/dspengine.h" #include "device/devicesinkapi.h" #include "device/devicesourceapi.h" -#include "bladerf/devicebladerfshared.h" - #include "bladerfoutput.h" + +#include "../../../devices/bladerf1/devicebladerf1shared.h" #include "bladerfoutputthread.h" MESSAGE_CLASS_DEFINITION(BladerfOutput::MsgConfigureBladerf, Message) @@ -74,7 +74,7 @@ bool BladerfOutput::openDevice() if (m_deviceAPI->getSourceBuddies().size() > 0) { DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; - DeviceBladeRFParams *buddySharedParams = (DeviceBladeRFParams *) sourceBuddy->getBuddySharedPtr(); + DeviceBladeRF1Params *buddySharedParams = (DeviceBladeRF1Params *) sourceBuddy->getBuddySharedPtr(); if (buddySharedParams == 0) { @@ -93,7 +93,7 @@ bool BladerfOutput::openDevice() } else { - if (!DeviceBladeRF::open_bladerf(&m_dev, qPrintable(m_deviceAPI->getSampleSinkSerial()))) + if (!DeviceBladeRF1::open_bladerf(&m_dev, qPrintable(m_deviceAPI->getSampleSinkSerial()))) { qCritical("BladerfOutput::start: could not open BladeRF %s", qPrintable(m_deviceAPI->getSampleSinkSerial())); return false; @@ -318,13 +318,13 @@ bool BladerfOutput::applySettings(const BladeRFOutputSettings& settings, bool fo if (settings.m_log2Interp >= 5) { - fifoSize = DeviceBladeRFShared::m_sampleFifoMinSize32; + fifoSize = DeviceBladeRF1Shared::m_sampleFifoMinSize32; } else { fifoSize = std::max( - (int) ((settings.m_devSampleRate/(1< -#include "bladerf/devicebladerf.h" -#include "bladerf/devicebladerfparam.h" - #include #include +#include "../../../devices/bladerf1/devicebladerf1.h" +#include "../../../devices/bladerf1/devicebladerf1param.h" #include "bladerfoutputsettings.h" class DeviceSinkAPI; @@ -140,7 +139,7 @@ private: struct bladerf* m_dev; BladerfOutputThread* m_bladerfThread; QString m_deviceDescription; - DeviceBladeRFParams m_sharedParams; + DeviceBladeRF1Params m_sharedParams; bool m_running; }; diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp index 86ecc7461..6f120df64 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp @@ -27,7 +27,8 @@ #include "device/devicesinkapi.h" #include "device/deviceuiset.h" #include "bladerfoutputgui.h" -#include "bladerf/devicebladerfvalues.h" + +#include "../../../devices/bladerf1/devicebladerf1values.h" BladerfOutputGui::BladerfOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : QWidget(parent), diff --git a/plugins/samplesource/bladerfinput/CMakeLists.txt b/plugins/samplesource/bladerfinput/CMakeLists.txt index 943e3a7df..a39e263fe 100644 --- a/plugins/samplesource/bladerfinput/CMakeLists.txt +++ b/plugins/samplesource/bladerfinput/CMakeLists.txt @@ -62,7 +62,7 @@ target_link_libraries(inputbladerf sdrbase sdrgui swagger - bladerfdevice + bladerf1device ) else (BUILD_DEBIAN) target_link_libraries(inputbladerf @@ -71,7 +71,7 @@ target_link_libraries(inputbladerf sdrbase sdrgui swagger - bladerfdevice + bladerf1device ) endif (BUILD_DEBIAN) diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 78c225a3a..14ad45706 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -83,7 +83,7 @@ bool BladerfInput::openDevice() if (m_deviceAPI->getSinkBuddies().size() > 0) { DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; - DeviceBladeRFParams *buddySharedParams = (DeviceBladeRFParams *) sinkBuddy->getBuddySharedPtr(); + DeviceBladeRF1Params *buddySharedParams = (DeviceBladeRF1Params *) sinkBuddy->getBuddySharedPtr(); if (buddySharedParams == 0) { @@ -102,7 +102,7 @@ bool BladerfInput::openDevice() } else { - if (!DeviceBladeRF::open_bladerf(&m_dev, qPrintable(m_deviceAPI->getSampleSourceSerial()))) + if (!DeviceBladeRF1::open_bladerf(&m_dev, qPrintable(m_deviceAPI->getSampleSourceSerial()))) { qCritical("BladerfInput::start: could not open BladeRF %s", qPrintable(m_deviceAPI->getSampleSourceSerial())); return false; diff --git a/plugins/samplesource/bladerfinput/bladerfinput.h b/plugins/samplesource/bladerfinput/bladerfinput.h index 74254a5a4..df4365350 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.h +++ b/plugins/samplesource/bladerfinput/bladerfinput.h @@ -22,8 +22,9 @@ #include #include -#include "bladerf/devicebladerf.h" -#include "bladerf/devicebladerfparam.h" + +#include "../../../devices/bladerf1/devicebladerf1.h" +#include "../../../devices/bladerf1/devicebladerf1param.h" #include "bladerfinputsettings.h" class DeviceSourceAPI; @@ -144,7 +145,7 @@ private: struct bladerf* m_dev; BladerfInputThread* m_bladerfThread; QString m_deviceDescription; - DeviceBladeRFParams m_sharedParams; + DeviceBladeRF1Params m_sharedParams; bool m_running; FileRecord *m_fileSink; //!< File sink to record device I/Q output }; diff --git a/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt b/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt index a4f4d0cc9..6a18571f9 100644 --- a/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt +++ b/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt @@ -51,7 +51,7 @@ target_link_libraries(outputbladerfsrv bladerf sdrbase swagger - bladerfdevice + bladerf1device ) else (BUILD_DEBIAN) target_link_libraries(outputbladerfsrv @@ -59,7 +59,7 @@ target_link_libraries(outputbladerfsrv ${LIBBLADERF_LIBRARIES} sdrbase swagger - bladerfdevice + bladerf1device ) endif (BUILD_DEBIAN) diff --git a/pluginssrv/samplesource/bladerfinput/CMakeLists.txt b/pluginssrv/samplesource/bladerfinput/CMakeLists.txt index 78ed1c820..771ba0936 100644 --- a/pluginssrv/samplesource/bladerfinput/CMakeLists.txt +++ b/pluginssrv/samplesource/bladerfinput/CMakeLists.txt @@ -51,7 +51,7 @@ target_link_libraries(inputbladerfsrv bladerf sdrbase swagger - bladerfdevice + bladerf1device ) else (BUILD_DEBIAN) target_link_libraries(inputbladerfsrv @@ -59,7 +59,7 @@ target_link_libraries(inputbladerfsrv ${LIBBLADERF_LIBRARIES} sdrbase swagger - bladerfdevice + bladerf1device ) endif (BUILD_DEBIAN) From bf726e16b1e659b00a7bed992d51cfc9eff36544 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 19 Sep 2018 05:54:07 +0200 Subject: [PATCH 774/956] LibbladeRF2: migrate REST API from bladerf to bladerf1 --- .../bladerfoutput/bladerfoutput.cpp | 40 ++++----- .../bladerfoutput/bladerfoutputplugin.cpp | 6 +- .../bladerfoutput/bladerfoutputplugin.h | 4 +- .../bladerfinput/bladerfinput.cpp | 64 +++++++-------- .../bladerfinput/bladerfinputplugin.cpp | 6 +- .../bladerfinput/bladerfinputplugin.h | 4 +- sdrbase/resources/res.qrc | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 16 ++-- .../include/{BladeRF.yaml => BladeRF1.yaml} | 4 +- .../resources/webapi/doc/swagger/swagger.yaml | 10 +-- sdrbase/webapi/webapirequestmapper.cpp | 16 ++-- .../include/{BladeRF.yaml => BladeRF1.yaml} | 4 +- swagger/sdrangel/api/swagger/swagger.yaml | 10 +-- swagger/sdrangel/code/html2/index.html | 16 ++-- .../code/qt5/client/SWGAMDemodReport.cpp | 2 +- .../code/qt5/client/SWGAMDemodReport.h | 2 +- .../code/qt5/client/SWGAMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGAMDemodSettings.h | 2 +- .../code/qt5/client/SWGAMModReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGAMModReport.h | 2 +- .../code/qt5/client/SWGAMModSettings.cpp | 2 +- .../code/qt5/client/SWGAMModSettings.h | 2 +- .../code/qt5/client/SWGATVModReport.cpp | 2 +- .../code/qt5/client/SWGATVModReport.h | 2 +- .../code/qt5/client/SWGATVModSettings.cpp | 2 +- .../code/qt5/client/SWGATVModSettings.h | 2 +- .../code/qt5/client/SWGAirspyHFReport.cpp | 2 +- .../code/qt5/client/SWGAirspyHFReport.h | 2 +- .../code/qt5/client/SWGAirspyHFSettings.cpp | 2 +- .../code/qt5/client/SWGAirspyHFSettings.h | 2 +- .../code/qt5/client/SWGAirspyReport.cpp | 2 +- .../code/qt5/client/SWGAirspyReport.h | 2 +- .../code/qt5/client/SWGAirspySettings.cpp | 2 +- .../code/qt5/client/SWGAirspySettings.h | 2 +- .../code/qt5/client/SWGAudioDevices.cpp | 2 +- .../code/qt5/client/SWGAudioDevices.h | 2 +- .../code/qt5/client/SWGAudioInputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioInputDevice.h | 2 +- .../code/qt5/client/SWGAudioOutputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioOutputDevice.h | 2 +- .../code/qt5/client/SWGBFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGBFMDemodReport.h | 2 +- .../code/qt5/client/SWGBFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGBFMDemodSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.cpp | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.h | 2 +- ...tings.cpp => SWGBladeRF1InputSettings.cpp} | 82 +++++++++---------- ...tSettings.h => SWGBladeRF1InputSettings.h} | 20 ++--- ...ings.cpp => SWGBladeRF1OutputSettings.cpp} | 62 +++++++------- ...Settings.h => SWGBladeRF1OutputSettings.h} | 20 ++--- .../code/qt5/client/SWGCWKeyerSettings.cpp | 2 +- .../code/qt5/client/SWGCWKeyerSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGChannel.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGChannel.h | 2 +- .../code/qt5/client/SWGChannelListItem.cpp | 2 +- .../code/qt5/client/SWGChannelListItem.h | 2 +- .../code/qt5/client/SWGChannelReport.cpp | 2 +- .../code/qt5/client/SWGChannelReport.h | 2 +- .../code/qt5/client/SWGChannelSettings.cpp | 2 +- .../code/qt5/client/SWGChannelSettings.h | 2 +- .../code/qt5/client/SWGChannelsDetail.cpp | 2 +- .../code/qt5/client/SWGChannelsDetail.h | 2 +- .../code/qt5/client/SWGDSDDemodReport.cpp | 2 +- .../code/qt5/client/SWGDSDDemodReport.h | 2 +- .../code/qt5/client/SWGDSDDemodSettings.cpp | 2 +- .../code/qt5/client/SWGDSDDemodSettings.h | 2 +- .../code/qt5/client/SWGDVSeralDevices.cpp | 2 +- .../code/qt5/client/SWGDVSeralDevices.h | 2 +- .../code/qt5/client/SWGDVSerialDevice.cpp | 2 +- .../code/qt5/client/SWGDVSerialDevice.h | 2 +- .../code/qt5/client/SWGDaemonSinkSettings.cpp | 2 +- .../code/qt5/client/SWGDaemonSinkSettings.h | 2 +- .../code/qt5/client/SWGDaemonSourceReport.cpp | 2 +- .../code/qt5/client/SWGDaemonSourceReport.h | 2 +- .../qt5/client/SWGDaemonSourceSettings.cpp | 2 +- .../code/qt5/client/SWGDaemonSourceSettings.h | 2 +- .../code/qt5/client/SWGDeviceListItem.cpp | 2 +- .../code/qt5/client/SWGDeviceListItem.h | 2 +- .../code/qt5/client/SWGDeviceReport.cpp | 2 +- .../code/qt5/client/SWGDeviceReport.h | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.h | 2 +- .../code/qt5/client/SWGDeviceSetApi.cpp | 2 +- .../code/qt5/client/SWGDeviceSetApi.h | 2 +- .../code/qt5/client/SWGDeviceSetList.cpp | 2 +- .../code/qt5/client/SWGDeviceSetList.h | 2 +- .../code/qt5/client/SWGDeviceSettings.cpp | 66 +++++++-------- .../code/qt5/client/SWGDeviceSettings.h | 22 ++--- .../code/qt5/client/SWGDeviceState.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceState.h | 2 +- .../code/qt5/client/SWGErrorResponse.cpp | 2 +- .../code/qt5/client/SWGErrorResponse.h | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.h | 2 +- .../code/qt5/client/SWGFCDProSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProSettings.h | 2 +- .../code/qt5/client/SWGFileSourceReport.cpp | 2 +- .../code/qt5/client/SWGFileSourceReport.h | 2 +- .../code/qt5/client/SWGFileSourceSettings.cpp | 2 +- .../code/qt5/client/SWGFileSourceSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.cpp | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.h | 2 +- .../code/qt5/client/SWGFrequencyBand.cpp | 2 +- .../code/qt5/client/SWGFrequencyBand.h | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.h | 2 +- .../qt5/client/SWGHackRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFInputSettings.h | 2 +- .../qt5/client/SWGHackRFOutputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFOutputSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGHelpers.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGHelpers.h | 2 +- .../code/qt5/client/SWGHttpRequest.cpp | 2 +- .../sdrangel/code/qt5/client/SWGHttpRequest.h | 2 +- .../code/qt5/client/SWGInstanceApi.cpp | 2 +- .../sdrangel/code/qt5/client/SWGInstanceApi.h | 2 +- .../client/SWGInstanceChannelsResponse.cpp | 2 +- .../qt5/client/SWGInstanceChannelsResponse.h | 2 +- .../qt5/client/SWGInstanceDevicesResponse.cpp | 2 +- .../qt5/client/SWGInstanceDevicesResponse.h | 2 +- .../qt5/client/SWGInstanceSummaryResponse.cpp | 2 +- .../qt5/client/SWGInstanceSummaryResponse.h | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.h | 2 +- .../qt5/client/SWGLimeSdrInputSettings.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputSettings.h | 2 +- .../qt5/client/SWGLimeSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrOutputReport.h | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.h | 2 +- .../qt5/client/SWGLocationInformation.cpp | 2 +- .../code/qt5/client/SWGLocationInformation.h | 2 +- .../code/qt5/client/SWGLoggingInfo.cpp | 2 +- .../sdrangel/code/qt5/client/SWGLoggingInfo.h | 2 +- .../code/qt5/client/SWGModelFactory.h | 14 ++-- .../code/qt5/client/SWGNFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGNFMDemodReport.h | 2 +- .../code/qt5/client/SWGNFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGNFMDemodSettings.h | 2 +- .../code/qt5/client/SWGNFMModReport.cpp | 2 +- .../code/qt5/client/SWGNFMModReport.h | 2 +- .../code/qt5/client/SWGNFMModSettings.cpp | 2 +- .../code/qt5/client/SWGNFMModSettings.h | 2 +- swagger/sdrangel/code/qt5/client/SWGObject.h | 2 +- .../code/qt5/client/SWGPerseusReport.cpp | 2 +- .../code/qt5/client/SWGPerseusReport.h | 2 +- .../code/qt5/client/SWGPerseusSettings.cpp | 2 +- .../code/qt5/client/SWGPerseusSettings.h | 2 +- .../qt5/client/SWGPlutoSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrInputReport.h | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.h | 2 +- .../qt5/client/SWGPlutoSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrOutputReport.h | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.h | 2 +- .../code/qt5/client/SWGPresetExport.cpp | 2 +- .../code/qt5/client/SWGPresetExport.h | 2 +- .../code/qt5/client/SWGPresetGroup.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetGroup.h | 2 +- .../code/qt5/client/SWGPresetIdentifier.cpp | 2 +- .../code/qt5/client/SWGPresetIdentifier.h | 2 +- .../code/qt5/client/SWGPresetImport.cpp | 2 +- .../code/qt5/client/SWGPresetImport.h | 2 +- .../code/qt5/client/SWGPresetItem.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetItem.h | 2 +- .../code/qt5/client/SWGPresetTransfer.cpp | 2 +- .../code/qt5/client/SWGPresetTransfer.h | 2 +- .../sdrangel/code/qt5/client/SWGPresets.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGPresets.h | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.h | 2 +- .../client/SWGRDSReport_altFrequencies.cpp | 2 +- .../qt5/client/SWGRDSReport_altFrequencies.h | 2 +- .../code/qt5/client/SWGRtlSdrReport.cpp | 2 +- .../code/qt5/client/SWGRtlSdrReport.h | 2 +- .../code/qt5/client/SWGRtlSdrSettings.cpp | 2 +- .../code/qt5/client/SWGRtlSdrSettings.h | 2 +- .../code/qt5/client/SWGSDRPlayReport.cpp | 2 +- .../code/qt5/client/SWGSDRPlayReport.h | 2 +- .../code/qt5/client/SWGSDRPlaySettings.cpp | 2 +- .../code/qt5/client/SWGSDRPlaySettings.h | 2 +- .../qt5/client/SWGSDRdaemonSinkReport.cpp | 2 +- .../code/qt5/client/SWGSDRdaemonSinkReport.h | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.h | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.h | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.h | 2 +- .../code/qt5/client/SWGSSBDemodReport.cpp | 2 +- .../code/qt5/client/SWGSSBDemodReport.h | 2 +- .../code/qt5/client/SWGSSBDemodSettings.cpp | 2 +- .../code/qt5/client/SWGSSBDemodSettings.h | 2 +- .../code/qt5/client/SWGSSBModReport.cpp | 2 +- .../code/qt5/client/SWGSSBModReport.h | 2 +- .../code/qt5/client/SWGSSBModSettings.cpp | 2 +- .../code/qt5/client/SWGSSBModSettings.h | 2 +- .../code/qt5/client/SWGSampleRate.cpp | 2 +- .../sdrangel/code/qt5/client/SWGSampleRate.h | 2 +- .../code/qt5/client/SWGSamplingDevice.cpp | 2 +- .../code/qt5/client/SWGSamplingDevice.h | 2 +- .../code/qt5/client/SWGSuccessResponse.cpp | 2 +- .../code/qt5/client/SWGSuccessResponse.h | 2 +- .../code/qt5/client/SWGTestSourceSettings.cpp | 2 +- .../code/qt5/client/SWGTestSourceSettings.h | 2 +- .../code/qt5/client/SWGUDPSinkReport.cpp | 2 +- .../code/qt5/client/SWGUDPSinkReport.h | 2 +- .../code/qt5/client/SWGUDPSinkSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSinkSettings.h | 2 +- .../code/qt5/client/SWGUDPSourceReport.cpp | 2 +- .../code/qt5/client/SWGUDPSourceReport.h | 2 +- .../code/qt5/client/SWGUDPSourceSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSourceSettings.h | 2 +- .../code/qt5/client/SWGWFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGWFMDemodReport.h | 2 +- .../code/qt5/client/SWGWFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGWFMDemodSettings.h | 2 +- .../code/qt5/client/SWGWFMModReport.cpp | 2 +- .../code/qt5/client/SWGWFMModReport.h | 2 +- .../code/qt5/client/SWGWFMModSettings.cpp | 2 +- .../code/qt5/client/SWGWFMModSettings.h | 2 +- 222 files changed, 445 insertions(+), 445 deletions(-) rename sdrbase/resources/webapi/doc/swagger/include/{BladeRF.yaml => BladeRF1.yaml} (94%) rename swagger/sdrangel/api/swagger/include/{BladeRF.yaml => BladeRF1.yaml} (94%) rename swagger/sdrangel/code/qt5/client/{SWGBladeRFInputSettings.cpp => SWGBladeRF1InputSettings.cpp} (81%) rename swagger/sdrangel/code/qt5/client/{SWGBladeRFInputSettings.h => SWGBladeRF1InputSettings.h} (89%) rename swagger/sdrangel/code/qt5/client/{SWGBladeRFOutputSettings.cpp => SWGBladeRF1OutputSettings.cpp} (80%) rename swagger/sdrangel/code/qt5/client/{SWGBladeRFOutputSettings.h => SWGBladeRF1OutputSettings.h} (87%) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp index de47352e2..3fc74365a 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp @@ -538,23 +538,23 @@ int BladerfOutput::webapiSettingsGet( SWGSDRangel::SWGDeviceSettings& response, QString& errorMessage __attribute__((unused))) { - response.setBladeRfOutputSettings(new SWGSDRangel::SWGBladeRFOutputSettings()); - response.getBladeRfOutputSettings()->init(); + response.setBladeRf1OutputSettings(new SWGSDRangel::SWGBladeRF1OutputSettings()); + response.getBladeRf1OutputSettings()->init(); webapiFormatDeviceSettings(response, m_settings); return 200; } void BladerfOutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRFOutputSettings& settings) { - response.getBladeRfOutputSettings()->setCenterFrequency(settings.m_centerFrequency); - response.getBladeRfOutputSettings()->setDevSampleRate(settings.m_devSampleRate); - response.getBladeRfOutputSettings()->setVga1(settings.m_vga1); - response.getBladeRfOutputSettings()->setVga2(settings.m_vga2); - response.getBladeRfOutputSettings()->setBandwidth(settings.m_bandwidth); - response.getBladeRfOutputSettings()->setLog2Interp(settings.m_log2Interp); - response.getBladeRfOutputSettings()->setXb200(settings.m_xb200 ? 1 : 0); - response.getBladeRfOutputSettings()->setXb200Path((int) settings.m_xb200Path); - response.getBladeRfOutputSettings()->setXb200Filter((int) settings.m_xb200Filter); + response.getBladeRf1OutputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf1OutputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf1OutputSettings()->setVga1(settings.m_vga1); + response.getBladeRf1OutputSettings()->setVga2(settings.m_vga2); + response.getBladeRf1OutputSettings()->setBandwidth(settings.m_bandwidth); + response.getBladeRf1OutputSettings()->setLog2Interp(settings.m_log2Interp); + response.getBladeRf1OutputSettings()->setXb200(settings.m_xb200 ? 1 : 0); + response.getBladeRf1OutputSettings()->setXb200Path((int) settings.m_xb200Path); + response.getBladeRf1OutputSettings()->setXb200Filter((int) settings.m_xb200Filter); } int BladerfOutput::webapiSettingsPutPatch( @@ -566,31 +566,31 @@ int BladerfOutput::webapiSettingsPutPatch( BladeRFOutputSettings settings = m_settings; if (deviceSettingsKeys.contains("centerFrequency")) { - settings.m_centerFrequency = response.getBladeRfOutputSettings()->getCenterFrequency(); + settings.m_centerFrequency = response.getBladeRf1OutputSettings()->getCenterFrequency(); } if (deviceSettingsKeys.contains("devSampleRate")) { - settings.m_devSampleRate = response.getBladeRfOutputSettings()->getDevSampleRate(); + settings.m_devSampleRate = response.getBladeRf1OutputSettings()->getDevSampleRate(); } if (deviceSettingsKeys.contains("vga1")) { - settings.m_vga1 = response.getBladeRfOutputSettings()->getVga1(); + settings.m_vga1 = response.getBladeRf1OutputSettings()->getVga1(); } if (deviceSettingsKeys.contains("vga2")) { - settings.m_vga2 = response.getBladeRfOutputSettings()->getVga2(); + settings.m_vga2 = response.getBladeRf1OutputSettings()->getVga2(); } if (deviceSettingsKeys.contains("bandwidth")) { - settings.m_bandwidth = response.getBladeRfOutputSettings()->getBandwidth(); + settings.m_bandwidth = response.getBladeRf1OutputSettings()->getBandwidth(); } if (deviceSettingsKeys.contains("log2Interp")) { - settings.m_log2Interp = response.getBladeRfOutputSettings()->getLog2Interp(); + settings.m_log2Interp = response.getBladeRf1OutputSettings()->getLog2Interp(); } if (deviceSettingsKeys.contains("xb200")) { - settings.m_xb200 = response.getBladeRfOutputSettings()->getXb200() == 0 ? 0 : 1; + settings.m_xb200 = response.getBladeRf1OutputSettings()->getXb200() == 0 ? 0 : 1; } if (deviceSettingsKeys.contains("xb200Path")) { - settings.m_xb200Path = static_cast(response.getBladeRfOutputSettings()->getXb200Path()); + settings.m_xb200Path = static_cast(response.getBladeRf1OutputSettings()->getXb200Path()); } if (deviceSettingsKeys.contains("xb200Filter")) { - settings.m_xb200Filter = static_cast(response.getBladeRfOutputSettings()->getXb200Filter()); + settings.m_xb200Filter = static_cast(response.getBladeRf1OutputSettings()->getXb200Filter()); } MsgConfigureBladerf *msg = MsgConfigureBladerf::create(settings, force); diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp index 2dc8e7df4..82bd8574f 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp @@ -29,7 +29,7 @@ #endif const PluginDescriptor BladerfOutputPlugin::m_pluginDescriptor = { - QString("BladeRF Output"), + QString("BladeRF1 Output"), QString("4.2.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), @@ -37,8 +37,8 @@ const PluginDescriptor BladerfOutputPlugin::m_pluginDescriptor = { QString("https://github.com/f4exb/sdrangel") }; -const QString BladerfOutputPlugin::m_hardwareID = "BladeRF"; -const QString BladerfOutputPlugin::m_deviceTypeID = BLADERFOUTPUT_DEVICE_TYPE_ID; +const QString BladerfOutputPlugin::m_hardwareID = "BladeRF1"; +const QString BladerfOutputPlugin::m_deviceTypeID = BLADERF1OUTPUT_DEVICE_TYPE_ID; BladerfOutputPlugin::BladerfOutputPlugin(QObject* parent) : QObject(parent) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.h b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.h index a2e057995..1ba913aac 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.h +++ b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.h @@ -22,12 +22,12 @@ class PluginAPI; -#define BLADERFOUTPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerfoutput" +#define BLADERF1OUTPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf1output" class BladerfOutputPlugin : public QObject, public PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID BLADERFOUTPUT_DEVICE_TYPE_ID) + Q_PLUGIN_METADATA(IID BLADERF1OUTPUT_DEVICE_TYPE_ID) public: explicit BladerfOutputPlugin(QObject* parent = NULL); diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index 14ad45706..97308c2f5 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -611,32 +611,32 @@ int BladerfInput::webapiSettingsGet( SWGSDRangel::SWGDeviceSettings& response, QString& errorMessage __attribute__((unused))) { - response.setBladeRfInputSettings(new SWGSDRangel::SWGBladeRFInputSettings()); - response.getBladeRfInputSettings()->init(); + response.setBladeRf1InputSettings(new SWGSDRangel::SWGBladeRF1InputSettings()); + response.getBladeRf1InputSettings()->init(); webapiFormatDeviceSettings(response, m_settings); return 200; } void BladerfInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRFInputSettings& settings) { - response.getBladeRfInputSettings()->setCenterFrequency(settings.m_centerFrequency); - response.getBladeRfInputSettings()->setDevSampleRate(settings.m_devSampleRate); - response.getBladeRfInputSettings()->setLnaGain(settings.m_lnaGain); - response.getBladeRfInputSettings()->setVga1(settings.m_vga1); - response.getBladeRfInputSettings()->setVga2(settings.m_vga2); - response.getBladeRfInputSettings()->setBandwidth(settings.m_bandwidth); - response.getBladeRfInputSettings()->setLog2Decim(settings.m_log2Decim); - response.getBladeRfInputSettings()->setFcPos((int) settings.m_fcPos); - response.getBladeRfInputSettings()->setXb200(settings.m_xb200 ? 1 : 0); - response.getBladeRfInputSettings()->setXb200Path((int) settings.m_xb200Path); - response.getBladeRfInputSettings()->setXb200Filter((int) settings.m_xb200Filter); - response.getBladeRfInputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); - response.getBladeRfInputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getBladeRf1InputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf1InputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf1InputSettings()->setLnaGain(settings.m_lnaGain); + response.getBladeRf1InputSettings()->setVga1(settings.m_vga1); + response.getBladeRf1InputSettings()->setVga2(settings.m_vga2); + response.getBladeRf1InputSettings()->setBandwidth(settings.m_bandwidth); + response.getBladeRf1InputSettings()->setLog2Decim(settings.m_log2Decim); + response.getBladeRf1InputSettings()->setFcPos((int) settings.m_fcPos); + response.getBladeRf1InputSettings()->setXb200(settings.m_xb200 ? 1 : 0); + response.getBladeRf1InputSettings()->setXb200Path((int) settings.m_xb200Path); + response.getBladeRf1InputSettings()->setXb200Filter((int) settings.m_xb200Filter); + response.getBladeRf1InputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getBladeRf1InputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); - if (response.getBladeRfInputSettings()->getFileRecordName()) { - *response.getBladeRfInputSettings()->getFileRecordName() = settings.m_fileRecordName; + if (response.getBladeRf1InputSettings()->getFileRecordName()) { + *response.getBladeRf1InputSettings()->getFileRecordName() = settings.m_fileRecordName; } else { - response.getBladeRfInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + response.getBladeRf1InputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); } } @@ -649,46 +649,46 @@ int BladerfInput::webapiSettingsPutPatch( BladeRFInputSettings settings = m_settings; if (deviceSettingsKeys.contains("centerFrequency")) { - settings.m_centerFrequency = response.getBladeRfInputSettings()->getCenterFrequency(); + settings.m_centerFrequency = response.getBladeRf1InputSettings()->getCenterFrequency(); } if (deviceSettingsKeys.contains("devSampleRate")) { - settings.m_devSampleRate = response.getBladeRfInputSettings()->getDevSampleRate(); + settings.m_devSampleRate = response.getBladeRf1InputSettings()->getDevSampleRate(); } if (deviceSettingsKeys.contains("lnaGain")) { - settings.m_lnaGain = response.getBladeRfInputSettings()->getLnaGain(); + settings.m_lnaGain = response.getBladeRf1InputSettings()->getLnaGain(); } if (deviceSettingsKeys.contains("vga1")) { - settings.m_vga1 = response.getBladeRfInputSettings()->getVga1(); + settings.m_vga1 = response.getBladeRf1InputSettings()->getVga1(); } if (deviceSettingsKeys.contains("vga2")) { - settings.m_vga2 = response.getBladeRfInputSettings()->getVga2(); + settings.m_vga2 = response.getBladeRf1InputSettings()->getVga2(); } if (deviceSettingsKeys.contains("bandwidth")) { - settings.m_bandwidth = response.getBladeRfInputSettings()->getBandwidth(); + settings.m_bandwidth = response.getBladeRf1InputSettings()->getBandwidth(); } if (deviceSettingsKeys.contains("log2Decim")) { - settings.m_log2Decim = response.getBladeRfInputSettings()->getLog2Decim(); + settings.m_log2Decim = response.getBladeRf1InputSettings()->getLog2Decim(); } if (deviceSettingsKeys.contains("fcPos")) { - settings.m_fcPos = static_cast(response.getBladeRfInputSettings()->getFcPos()); + settings.m_fcPos = static_cast(response.getBladeRf1InputSettings()->getFcPos()); } if (deviceSettingsKeys.contains("xb200")) { - settings.m_xb200 = response.getBladeRfInputSettings()->getXb200() == 0 ? 0 : 1; + settings.m_xb200 = response.getBladeRf1InputSettings()->getXb200() == 0 ? 0 : 1; } if (deviceSettingsKeys.contains("xb200Path")) { - settings.m_xb200Path = static_cast(response.getBladeRfInputSettings()->getXb200Path()); + settings.m_xb200Path = static_cast(response.getBladeRf1InputSettings()->getXb200Path()); } if (deviceSettingsKeys.contains("xb200Filter")) { - settings.m_xb200Filter = static_cast(response.getBladeRfInputSettings()->getXb200Filter()); + settings.m_xb200Filter = static_cast(response.getBladeRf1InputSettings()->getXb200Filter()); } if (deviceSettingsKeys.contains("dcBlock")) { - settings.m_dcBlock = response.getBladeRfInputSettings()->getDcBlock() != 0; + settings.m_dcBlock = response.getBladeRf1InputSettings()->getDcBlock() != 0; } if (deviceSettingsKeys.contains("iqCorrection")) { - settings.m_iqCorrection = response.getBladeRfInputSettings()->getIqCorrection() != 0; + settings.m_iqCorrection = response.getBladeRf1InputSettings()->getIqCorrection() != 0; } if (deviceSettingsKeys.contains("fileRecordName")) { - settings.m_fileRecordName = *response.getBladeRfInputSettings()->getFileRecordName(); + settings.m_fileRecordName = *response.getBladeRf1InputSettings()->getFileRecordName(); } MsgConfigureBladerf *msg = MsgConfigureBladerf::create(settings, force); diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index b9a79a471..e6c979d74 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -29,7 +29,7 @@ #endif const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { - QString("BladeRF Input"), + QString("BladeRF1 Input"), QString("4.2.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), @@ -37,8 +37,8 @@ const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { QString("https://github.com/f4exb/sdrangel") }; -const QString BlderfInputPlugin::m_hardwareID = "BladeRF"; -const QString BlderfInputPlugin::m_deviceTypeID = BLADERF_DEVICE_TYPE_ID; +const QString BlderfInputPlugin::m_hardwareID = "BladeRF1"; +const QString BlderfInputPlugin::m_deviceTypeID = BLADERF1INPUT_DEVICE_TYPE_ID; BlderfInputPlugin::BlderfInputPlugin(QObject* parent) : QObject(parent) diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.h b/plugins/samplesource/bladerfinput/bladerfinputplugin.h index d9fcb6599..83ea9429a 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.h +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.h @@ -24,12 +24,12 @@ class PluginAPI; class DeviceSourceAPI; class DeviceUISet; -#define BLADERF_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf" +#define BLADERF1INPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf1input" class BlderfInputPlugin : public QObject, public PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID BLADERF_DEVICE_TYPE_ID) + Q_PLUGIN_METADATA(IID BLADERF1INPUT_DEVICE_TYPE_ID) public: explicit BlderfInputPlugin(QObject* parent = NULL); diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 51145dc6a..9df5f8c67 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -8,7 +8,7 @@ webapi/doc/swagger/include/AMMod.yaml webapi/doc/swagger/include/ATVMod.yaml webapi/doc/swagger/include/BFMDemod.yaml - webapi/doc/swagger/include/BladeRF.yaml + webapi/doc/swagger/include/BladeRF1.yaml webapi/doc/swagger/include/CWKeyer.yaml webapi/doc/swagger/include/DSDDemod.yaml webapi/doc/swagger/include/FCDPro.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index c6de6f988..61e28cb46 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1221,7 +1221,7 @@ margin-bottom: 20px; }, "description" : "A bandwidth expressed in Hertz (Hz)" }; - defs.BladeRFInputSettings = { + defs.BladeRF1InputSettings = { "properties" : { "centerFrequency" : { "type" : "integer", @@ -1269,7 +1269,7 @@ margin-bottom: 20px; }, "description" : "BladeRF" }; - defs.BladeRFOutputSettings = { + defs.BladeRF1OutputSettings = { "properties" : { "centerFrequency" : { "type" : "integer", @@ -1929,11 +1929,11 @@ margin-bottom: 20px; "airspyHFSettings" : { "$ref" : "#/definitions/AirspyHFSettings" }, - "bladeRFInputSettings" : { - "$ref" : "#/definitions/BladeRFInputSettings" + "bladeRF1InputSettings" : { + "$ref" : "#/definitions/BladeRF1InputSettings" }, - "bladeRFOutputSettings" : { - "$ref" : "#/definitions/BladeRFOutputSettings" + "bladeRF1OutputSettings" : { + "$ref" : "#/definitions/BladeRF1OutputSettings" }, "fcdProSettings" : { "$ref" : "#/definitions/FCDProSettings" @@ -4198,7 +4198,7 @@ margin-bottom: 20px; diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF1.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF1.yaml index 9ad3cbfaa..a7f2c3872 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/BladeRF1.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF1.yaml @@ -1,5 +1,5 @@ BladeRF1InputSettings: - description: BladeRF + description: BladeRF1 properties: centerFrequency: type: integer @@ -32,7 +32,7 @@ BladeRF1InputSettings: type: string BladeRF1OutputSettings: - description: BladeRF + description: BladeRF1 properties: centerFrequency: type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml new file mode 100644 index 000000000..9b44f6c2d --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml @@ -0,0 +1,39 @@ +BladeRF2InputSettings: + description: BladeRF2 + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + bandwidth: + type: integer + gainMode: + type: integer + globalGain: + type: integer + biasTee: + type: integer + log2Decim: + type: integer + fcPos: + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + fileRecordName: + type: string + +BladeRF2InputReport: + description: BladeRF2 + properties: + frequencyRange: + $ref: "/doc/swagger/include/Structs.yaml#/Range" + sampleRateRange: + $ref: "/doc/swagger/include/Structs.yaml#/Range" + bandwidthRange: + $ref: "/doc/swagger/include/Structs.yaml#/Range" + globalGainRange: + $ref: "/doc/swagger/include/Structs.yaml#/Range" + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml b/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml index 58b6cf8ed..ccdedd7ba 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml @@ -31,4 +31,13 @@ Gain: properties: gainCB: type: integer - \ No newline at end of file + +Range: + description: An arbitrary range of values + properties: + min: + type: integer + max: + type: integer + step: + type: integer \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 876acee85..1e5a16dba 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1765,6 +1765,8 @@ definitions: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFSettings" bladeRF1InputSettings: $ref: "/doc/swagger/include/BladeRF1.yaml#/BladeRF1InputSettings" + bladeRF2InputSettings: + $ref: "/doc/swagger/include/BladeRF2.yaml#/BladeRF2InputSettings" bladeRF1OutputSettings: $ref: "/doc/swagger/include/BladeRF1.yaml#/BladeRF1OutputSettings" fcdProSettings: @@ -1816,6 +1818,8 @@ definitions: $ref: "/doc/swagger/include/Airspy.yaml#/AirspyReport" airspyHFReport: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFReport" + bladeRF2InputReport: + $ref: "/doc/swagger/include/BladeRF2.yaml#/BladeRF2InputReport" fileSourceReport: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceReport" limeSdrInputReport: diff --git a/swagger/sdrangel/api/swagger/include/BladeRF1.yaml b/swagger/sdrangel/api/swagger/include/BladeRF1.yaml index 9ad3cbfaa..a7f2c3872 100644 --- a/swagger/sdrangel/api/swagger/include/BladeRF1.yaml +++ b/swagger/sdrangel/api/swagger/include/BladeRF1.yaml @@ -1,5 +1,5 @@ BladeRF1InputSettings: - description: BladeRF + description: BladeRF1 properties: centerFrequency: type: integer @@ -32,7 +32,7 @@ BladeRF1InputSettings: type: string BladeRF1OutputSettings: - description: BladeRF + description: BladeRF1 properties: centerFrequency: type: integer diff --git a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml new file mode 100644 index 000000000..40eb4d52c --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml @@ -0,0 +1,39 @@ +BladeRF2InputSettings: + description: BladeRF2 + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + bandwidth: + type: integer + gainMode: + type: integer + globalGain: + type: integer + biasTee: + type: integer + log2Decim: + type: integer + fcPos: + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + fileRecordName: + type: string + +BladeRF2InputReport: + description: BladeRF2 + properties: + frequencyRange: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" + sampleRateRange: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" + bandwidthRange: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" + globalGainRange: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/Structs.yaml b/swagger/sdrangel/api/swagger/include/Structs.yaml index 58b6cf8ed..ccdedd7ba 100644 --- a/swagger/sdrangel/api/swagger/include/Structs.yaml +++ b/swagger/sdrangel/api/swagger/include/Structs.yaml @@ -31,4 +31,13 @@ Gain: properties: gainCB: type: integer - \ No newline at end of file + +Range: + description: An arbitrary range of values + properties: + min: + type: integer + max: + type: integer + step: + type: integer \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index f82fab20b..1598f5faf 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1765,6 +1765,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFSettings" bladeRF1InputSettings: $ref: "http://localhost:8081/api/swagger/include/BladeRF1.yaml#/BladeRF1InputSettings" + bladeRF2InputSettings: + $ref: "http://localhost:8081/api/swagger/include/BladeRF2.yaml#/BladeRF2InputSettings" bladeRF1OutputSettings: $ref: "http://localhost:8081/api/swagger/include/BladeRF1.yaml#/BladeRF1OutputSettings" fcdProSettings: @@ -1816,6 +1818,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/Airspy.yaml#/AirspyReport" airspyHFReport: $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFReport" + bladeRF2InputReport: + $ref: "http://localhost:8081/api/swagger/include/BladeRF2.yaml#/BladeRF2InputReport" fileSourceReport: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceReport" limeSdrInputReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 61e28cb46..941d139fd 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1267,7 +1267,7 @@ margin-bottom: 20px; "type" : "string" } }, - "description" : "BladeRF" + "description" : "BladeRF1" }; defs.BladeRF1OutputSettings = { "properties" : { @@ -1300,7 +1300,63 @@ margin-bottom: 20px; "type" : "integer" } }, - "description" : "BladeRF" + "description" : "BladeRF1" +}; + defs.BladeRF2InputReport = { + "properties" : { + "frequencyRange" : { + "$ref" : "#/definitions/Range" + }, + "sampleRateRange" : { + "$ref" : "#/definitions/Range" + }, + "bandwidthRange" : { + "$ref" : "#/definitions/Range" + }, + "globalGainRange" : { + "$ref" : "#/definitions/Range" + } + }, + "description" : "BladeRF2" +}; + defs.BladeRF2InputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "bandwidth" : { + "type" : "integer" + }, + "gainMode" : { + "type" : "integer" + }, + "globalGain" : { + "type" : "integer" + }, + "biasTee" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + }, + "fileRecordName" : { + "type" : "string" + } + }, + "description" : "BladeRF2" }; defs.CWKeyerSettings = { "properties" : { @@ -1838,6 +1894,9 @@ margin-bottom: 20px; "airspyHFReport" : { "$ref" : "#/definitions/AirspyHFReport" }, + "bladeRF2InputReport" : { + "$ref" : "#/definitions/BladeRF2InputReport" + }, "fileSourceReport" : { "$ref" : "#/definitions/FileSourceReport" }, @@ -1932,6 +1991,9 @@ margin-bottom: 20px; "bladeRF1InputSettings" : { "$ref" : "#/definitions/BladeRF1InputSettings" }, + "bladeRF2InputSettings" : { + "$ref" : "#/definitions/BladeRF2InputSettings" + }, "bladeRF1OutputSettings" : { "$ref" : "#/definitions/BladeRF1OutputSettings" }, @@ -3147,6 +3209,20 @@ margin-bottom: 20px; "format" : "float" } } +}; + defs.Range = { + "properties" : { + "min" : { + "type" : "integer" + }, + "max" : { + "type" : "integer" + }, + "step" : { + "type" : "integer" + } + }, + "description" : "An arbitrary range of values" }; defs.RtlSdrReport = { "properties" : { @@ -23066,7 +23142,7 @@ except ApiException as e:
    - Generated 2018-09-19T05:34:44.404+02:00 + Generated 2018-09-22T10:27:51.856+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF1InputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF1InputSettings.h index fe54e9eeb..42b6c6aa0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF1InputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF1InputSettings.h @@ -13,7 +13,7 @@ /* * SWGBladeRF1InputSettings.h * - * BladeRF + * BladeRF1 */ #ifndef SWGBladeRF1InputSettings_H_ diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF1OutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF1OutputSettings.h index 1c9f68472..bcafbd169 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF1OutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF1OutputSettings.h @@ -13,7 +13,7 @@ /* * SWGBladeRF1OutputSettings.h * - * BladeRF + * BladeRF1 */ #ifndef SWGBladeRF1OutputSettings_H_ diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.cpp new file mode 100644 index 000000000..b641d2a1c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.cpp @@ -0,0 +1,177 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBladeRF2InputReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBladeRF2InputReport::SWGBladeRF2InputReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBladeRF2InputReport::SWGBladeRF2InputReport() { + frequency_range = nullptr; + m_frequency_range_isSet = false; + sample_rate_range = nullptr; + m_sample_rate_range_isSet = false; + bandwidth_range = nullptr; + m_bandwidth_range_isSet = false; + global_gain_range = nullptr; + m_global_gain_range_isSet = false; +} + +SWGBladeRF2InputReport::~SWGBladeRF2InputReport() { + this->cleanup(); +} + +void +SWGBladeRF2InputReport::init() { + frequency_range = new SWGRange(); + m_frequency_range_isSet = false; + sample_rate_range = new SWGRange(); + m_sample_rate_range_isSet = false; + bandwidth_range = new SWGRange(); + m_bandwidth_range_isSet = false; + global_gain_range = new SWGRange(); + m_global_gain_range_isSet = false; +} + +void +SWGBladeRF2InputReport::cleanup() { + if(frequency_range != nullptr) { + delete frequency_range; + } + if(sample_rate_range != nullptr) { + delete sample_rate_range; + } + if(bandwidth_range != nullptr) { + delete bandwidth_range; + } + if(global_gain_range != nullptr) { + delete global_gain_range; + } +} + +SWGBladeRF2InputReport* +SWGBladeRF2InputReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBladeRF2InputReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&frequency_range, pJson["frequencyRange"], "SWGRange", "SWGRange"); + + ::SWGSDRangel::setValue(&sample_rate_range, pJson["sampleRateRange"], "SWGRange", "SWGRange"); + + ::SWGSDRangel::setValue(&bandwidth_range, pJson["bandwidthRange"], "SWGRange", "SWGRange"); + + ::SWGSDRangel::setValue(&global_gain_range, pJson["globalGainRange"], "SWGRange", "SWGRange"); + +} + +QString +SWGBladeRF2InputReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBladeRF2InputReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if((frequency_range != nullptr) && (frequency_range->isSet())){ + toJsonValue(QString("frequencyRange"), frequency_range, obj, QString("SWGRange")); + } + if((sample_rate_range != nullptr) && (sample_rate_range->isSet())){ + toJsonValue(QString("sampleRateRange"), sample_rate_range, obj, QString("SWGRange")); + } + if((bandwidth_range != nullptr) && (bandwidth_range->isSet())){ + toJsonValue(QString("bandwidthRange"), bandwidth_range, obj, QString("SWGRange")); + } + if((global_gain_range != nullptr) && (global_gain_range->isSet())){ + toJsonValue(QString("globalGainRange"), global_gain_range, obj, QString("SWGRange")); + } + + return obj; +} + +SWGRange* +SWGBladeRF2InputReport::getFrequencyRange() { + return frequency_range; +} +void +SWGBladeRF2InputReport::setFrequencyRange(SWGRange* frequency_range) { + this->frequency_range = frequency_range; + this->m_frequency_range_isSet = true; +} + +SWGRange* +SWGBladeRF2InputReport::getSampleRateRange() { + return sample_rate_range; +} +void +SWGBladeRF2InputReport::setSampleRateRange(SWGRange* sample_rate_range) { + this->sample_rate_range = sample_rate_range; + this->m_sample_rate_range_isSet = true; +} + +SWGRange* +SWGBladeRF2InputReport::getBandwidthRange() { + return bandwidth_range; +} +void +SWGBladeRF2InputReport::setBandwidthRange(SWGRange* bandwidth_range) { + this->bandwidth_range = bandwidth_range; + this->m_bandwidth_range_isSet = true; +} + +SWGRange* +SWGBladeRF2InputReport::getGlobalGainRange() { + return global_gain_range; +} +void +SWGBladeRF2InputReport::setGlobalGainRange(SWGRange* global_gain_range) { + this->global_gain_range = global_gain_range; + this->m_global_gain_range_isSet = true; +} + + +bool +SWGBladeRF2InputReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(frequency_range != nullptr && frequency_range->isSet()){ isObjectUpdated = true; break;} + if(sample_rate_range != nullptr && sample_rate_range->isSet()){ isObjectUpdated = true; break;} + if(bandwidth_range != nullptr && bandwidth_range->isSet()){ isObjectUpdated = true; break;} + if(global_gain_range != nullptr && global_gain_range->isSet()){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.h new file mode 100644 index 000000000..0f8b5d5d9 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.h @@ -0,0 +1,77 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBladeRF2InputReport.h + * + * BladeRF2 + */ + +#ifndef SWGBladeRF2InputReport_H_ +#define SWGBladeRF2InputReport_H_ + +#include + + +#include "SWGRange.h" + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBladeRF2InputReport: public SWGObject { +public: + SWGBladeRF2InputReport(); + SWGBladeRF2InputReport(QString* json); + virtual ~SWGBladeRF2InputReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBladeRF2InputReport* fromJson(QString &jsonString) override; + + SWGRange* getFrequencyRange(); + void setFrequencyRange(SWGRange* frequency_range); + + SWGRange* getSampleRateRange(); + void setSampleRateRange(SWGRange* sample_rate_range); + + SWGRange* getBandwidthRange(); + void setBandwidthRange(SWGRange* bandwidth_range); + + SWGRange* getGlobalGainRange(); + void setGlobalGainRange(SWGRange* global_gain_range); + + + virtual bool isSet() override; + +private: + SWGRange* frequency_range; + bool m_frequency_range_isSet; + + SWGRange* sample_rate_range; + bool m_sample_rate_range_isSet; + + SWGRange* bandwidth_range; + bool m_bandwidth_range_isSet; + + SWGRange* global_gain_range; + bool m_global_gain_range_isSet; + +}; + +} + +#endif /* SWGBladeRF2InputReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp new file mode 100644 index 000000000..60e5492a4 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp @@ -0,0 +1,318 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBladeRF2InputSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBladeRF2InputSettings::SWGBladeRF2InputSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBladeRF2InputSettings::SWGBladeRF2InputSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + bandwidth = 0; + m_bandwidth_isSet = false; + gain_mode = 0; + m_gain_mode_isSet = false; + global_gain = 0; + m_global_gain_isSet = false; + bias_tee = 0; + m_bias_tee_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + file_record_name = nullptr; + m_file_record_name_isSet = false; +} + +SWGBladeRF2InputSettings::~SWGBladeRF2InputSettings() { + this->cleanup(); +} + +void +SWGBladeRF2InputSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + bandwidth = 0; + m_bandwidth_isSet = false; + gain_mode = 0; + m_gain_mode_isSet = false; + global_gain = 0; + m_global_gain_isSet = false; + bias_tee = 0; + m_bias_tee_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; + file_record_name = new QString(""); + m_file_record_name_isSet = false; +} + +void +SWGBladeRF2InputSettings::cleanup() { + + + + + + + + + + + if(file_record_name != nullptr) { + delete file_record_name; + } +} + +SWGBladeRF2InputSettings* +SWGBladeRF2InputSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBladeRF2InputSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain_mode, pJson["gainMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&global_gain, pJson["globalGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&bias_tee, pJson["biasTee"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&fc_pos, pJson["fcPos"], "qint32", ""); + + ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); + + ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); + +} + +QString +SWGBladeRF2InputSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBladeRF2InputSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_dev_sample_rate_isSet){ + obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); + } + if(m_bandwidth_isSet){ + obj->insert("bandwidth", QJsonValue(bandwidth)); + } + if(m_gain_mode_isSet){ + obj->insert("gainMode", QJsonValue(gain_mode)); + } + if(m_global_gain_isSet){ + obj->insert("globalGain", QJsonValue(global_gain)); + } + if(m_bias_tee_isSet){ + obj->insert("biasTee", QJsonValue(bias_tee)); + } + if(m_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_fc_pos_isSet){ + obj->insert("fcPos", QJsonValue(fc_pos)); + } + if(m_dc_block_isSet){ + obj->insert("dcBlock", QJsonValue(dc_block)); + } + if(m_iq_correction_isSet){ + obj->insert("iqCorrection", QJsonValue(iq_correction)); + } + if(file_record_name != nullptr && *file_record_name != QString("")){ + toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGBladeRF2InputSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGBladeRF2InputSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getDevSampleRate() { + return dev_sample_rate; +} +void +SWGBladeRF2InputSettings::setDevSampleRate(qint32 dev_sample_rate) { + this->dev_sample_rate = dev_sample_rate; + this->m_dev_sample_rate_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getBandwidth() { + return bandwidth; +} +void +SWGBladeRF2InputSettings::setBandwidth(qint32 bandwidth) { + this->bandwidth = bandwidth; + this->m_bandwidth_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getGainMode() { + return gain_mode; +} +void +SWGBladeRF2InputSettings::setGainMode(qint32 gain_mode) { + this->gain_mode = gain_mode; + this->m_gain_mode_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getGlobalGain() { + return global_gain; +} +void +SWGBladeRF2InputSettings::setGlobalGain(qint32 global_gain) { + this->global_gain = global_gain; + this->m_global_gain_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getBiasTee() { + return bias_tee; +} +void +SWGBladeRF2InputSettings::setBiasTee(qint32 bias_tee) { + this->bias_tee = bias_tee; + this->m_bias_tee_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getLog2Decim() { + return log2_decim; +} +void +SWGBladeRF2InputSettings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getFcPos() { + return fc_pos; +} +void +SWGBladeRF2InputSettings::setFcPos(qint32 fc_pos) { + this->fc_pos = fc_pos; + this->m_fc_pos_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getDcBlock() { + return dc_block; +} +void +SWGBladeRF2InputSettings::setDcBlock(qint32 dc_block) { + this->dc_block = dc_block; + this->m_dc_block_isSet = true; +} + +qint32 +SWGBladeRF2InputSettings::getIqCorrection() { + return iq_correction; +} +void +SWGBladeRF2InputSettings::setIqCorrection(qint32 iq_correction) { + this->iq_correction = iq_correction; + this->m_iq_correction_isSet = true; +} + +QString* +SWGBladeRF2InputSettings::getFileRecordName() { + return file_record_name; +} +void +SWGBladeRF2InputSettings::setFileRecordName(QString* file_record_name) { + this->file_record_name = file_record_name; + this->m_file_record_name_isSet = true; +} + + +bool +SWGBladeRF2InputSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_gain_mode_isSet){ isObjectUpdated = true; break;} + if(m_global_gain_isSet){ isObjectUpdated = true; break;} + if(m_bias_tee_isSet){ isObjectUpdated = true; break;} + if(m_log2_decim_isSet){ isObjectUpdated = true; break;} + if(m_fc_pos_isSet){ isObjectUpdated = true; break;} + if(m_dc_block_isSet){ isObjectUpdated = true; break;} + if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h new file mode 100644 index 000000000..1a24a82f8 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h @@ -0,0 +1,119 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBladeRF2InputSettings.h + * + * BladeRF2 + */ + +#ifndef SWGBladeRF2InputSettings_H_ +#define SWGBladeRF2InputSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBladeRF2InputSettings: public SWGObject { +public: + SWGBladeRF2InputSettings(); + SWGBladeRF2InputSettings(QString* json); + virtual ~SWGBladeRF2InputSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBladeRF2InputSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getDevSampleRate(); + void setDevSampleRate(qint32 dev_sample_rate); + + qint32 getBandwidth(); + void setBandwidth(qint32 bandwidth); + + qint32 getGainMode(); + void setGainMode(qint32 gain_mode); + + qint32 getGlobalGain(); + void setGlobalGain(qint32 global_gain); + + qint32 getBiasTee(); + void setBiasTee(qint32 bias_tee); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + qint32 getFcPos(); + void setFcPos(qint32 fc_pos); + + qint32 getDcBlock(); + void setDcBlock(qint32 dc_block); + + qint32 getIqCorrection(); + void setIqCorrection(qint32 iq_correction); + + QString* getFileRecordName(); + void setFileRecordName(QString* file_record_name); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 dev_sample_rate; + bool m_dev_sample_rate_isSet; + + qint32 bandwidth; + bool m_bandwidth_isSet; + + qint32 gain_mode; + bool m_gain_mode_isSet; + + qint32 global_gain; + bool m_global_gain_isSet; + + qint32 bias_tee; + bool m_bias_tee_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + qint32 fc_pos; + bool m_fc_pos_isSet; + + qint32 dc_block; + bool m_dc_block_isSet; + + qint32 iq_correction; + bool m_iq_correction_isSet; + + QString* file_record_name; + bool m_file_record_name_isSet; + +}; + +} + +#endif /* SWGBladeRF2InputSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index 5a23ae3a7..a153e10a1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -36,6 +36,8 @@ SWGDeviceReport::SWGDeviceReport() { m_airspy_report_isSet = false; airspy_hf_report = nullptr; m_airspy_hf_report_isSet = false; + blade_rf2_input_report = nullptr; + m_blade_rf2_input_report_isSet = false; file_source_report = nullptr; m_file_source_report_isSet = false; lime_sdr_input_report = nullptr; @@ -72,6 +74,8 @@ SWGDeviceReport::init() { m_airspy_report_isSet = false; airspy_hf_report = new SWGAirspyHFReport(); m_airspy_hf_report_isSet = false; + blade_rf2_input_report = new SWGBladeRF2InputReport(); + m_blade_rf2_input_report_isSet = false; file_source_report = new SWGFileSourceReport(); m_file_source_report_isSet = false; lime_sdr_input_report = new SWGLimeSdrInputReport(); @@ -106,6 +110,9 @@ SWGDeviceReport::cleanup() { if(airspy_hf_report != nullptr) { delete airspy_hf_report; } + if(blade_rf2_input_report != nullptr) { + delete blade_rf2_input_report; + } if(file_source_report != nullptr) { delete file_source_report; } @@ -157,6 +164,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&airspy_hf_report, pJson["airspyHFReport"], "SWGAirspyHFReport", "SWGAirspyHFReport"); + ::SWGSDRangel::setValue(&blade_rf2_input_report, pJson["bladeRF2InputReport"], "SWGBladeRF2InputReport", "SWGBladeRF2InputReport"); + ::SWGSDRangel::setValue(&file_source_report, pJson["fileSourceReport"], "SWGFileSourceReport", "SWGFileSourceReport"); ::SWGSDRangel::setValue(&lime_sdr_input_report, pJson["limeSdrInputReport"], "SWGLimeSdrInputReport", "SWGLimeSdrInputReport"); @@ -205,6 +214,9 @@ SWGDeviceReport::asJsonObject() { if((airspy_hf_report != nullptr) && (airspy_hf_report->isSet())){ toJsonValue(QString("airspyHFReport"), airspy_hf_report, obj, QString("SWGAirspyHFReport")); } + if((blade_rf2_input_report != nullptr) && (blade_rf2_input_report->isSet())){ + toJsonValue(QString("bladeRF2InputReport"), blade_rf2_input_report, obj, QString("SWGBladeRF2InputReport")); + } if((file_source_report != nullptr) && (file_source_report->isSet())){ toJsonValue(QString("fileSourceReport"), file_source_report, obj, QString("SWGFileSourceReport")); } @@ -279,6 +291,16 @@ SWGDeviceReport::setAirspyHfReport(SWGAirspyHFReport* airspy_hf_report) { this->m_airspy_hf_report_isSet = true; } +SWGBladeRF2InputReport* +SWGDeviceReport::getBladeRf2InputReport() { + return blade_rf2_input_report; +} +void +SWGDeviceReport::setBladeRf2InputReport(SWGBladeRF2InputReport* blade_rf2_input_report) { + this->blade_rf2_input_report = blade_rf2_input_report; + this->m_blade_rf2_input_report_isSet = true; +} + SWGFileSourceReport* SWGDeviceReport::getFileSourceReport() { return file_source_report; @@ -388,6 +410,7 @@ SWGDeviceReport::isSet(){ if(m_tx_isSet){ isObjectUpdated = true; break;} if(airspy_report != nullptr && airspy_report->isSet()){ isObjectUpdated = true; break;} if(airspy_hf_report != nullptr && airspy_hf_report->isSet()){ isObjectUpdated = true; break;} + if(blade_rf2_input_report != nullptr && blade_rf2_input_report->isSet()){ isObjectUpdated = true; break;} if(file_source_report != nullptr && file_source_report->isSet()){ isObjectUpdated = true; break;} if(lime_sdr_input_report != nullptr && lime_sdr_input_report->isSet()){ isObjectUpdated = true; break;} if(lime_sdr_output_report != nullptr && lime_sdr_output_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index e536e7275..068ea5a1a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -24,6 +24,7 @@ #include "SWGAirspyHFReport.h" #include "SWGAirspyReport.h" +#include "SWGBladeRF2InputReport.h" #include "SWGFileSourceReport.h" #include "SWGLimeSdrInputReport.h" #include "SWGLimeSdrOutputReport.h" @@ -66,6 +67,9 @@ public: SWGAirspyHFReport* getAirspyHfReport(); void setAirspyHfReport(SWGAirspyHFReport* airspy_hf_report); + SWGBladeRF2InputReport* getBladeRf2InputReport(); + void setBladeRf2InputReport(SWGBladeRF2InputReport* blade_rf2_input_report); + SWGFileSourceReport* getFileSourceReport(); void setFileSourceReport(SWGFileSourceReport* file_source_report); @@ -112,6 +116,9 @@ private: SWGAirspyHFReport* airspy_hf_report; bool m_airspy_hf_report_isSet; + SWGBladeRF2InputReport* blade_rf2_input_report; + bool m_blade_rf2_input_report_isSet; + SWGFileSourceReport* file_source_report; bool m_file_source_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index b69310d19..8f581f8ec 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -38,6 +38,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_airspy_hf_settings_isSet = false; blade_rf1_input_settings = nullptr; m_blade_rf1_input_settings_isSet = false; + blade_rf2_input_settings = nullptr; + m_blade_rf2_input_settings_isSet = false; blade_rf1_output_settings = nullptr; m_blade_rf1_output_settings_isSet = false; fcd_pro_settings = nullptr; @@ -88,6 +90,8 @@ SWGDeviceSettings::init() { m_airspy_hf_settings_isSet = false; blade_rf1_input_settings = new SWGBladeRF1InputSettings(); m_blade_rf1_input_settings_isSet = false; + blade_rf2_input_settings = new SWGBladeRF2InputSettings(); + m_blade_rf2_input_settings_isSet = false; blade_rf1_output_settings = new SWGBladeRF1OutputSettings(); m_blade_rf1_output_settings_isSet = false; fcd_pro_settings = new SWGFCDProSettings(); @@ -137,6 +141,9 @@ SWGDeviceSettings::cleanup() { if(blade_rf1_input_settings != nullptr) { delete blade_rf1_input_settings; } + if(blade_rf2_input_settings != nullptr) { + delete blade_rf2_input_settings; + } if(blade_rf1_output_settings != nullptr) { delete blade_rf1_output_settings; } @@ -208,6 +215,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&blade_rf1_input_settings, pJson["bladeRF1InputSettings"], "SWGBladeRF1InputSettings", "SWGBladeRF1InputSettings"); + ::SWGSDRangel::setValue(&blade_rf2_input_settings, pJson["bladeRF2InputSettings"], "SWGBladeRF2InputSettings", "SWGBladeRF2InputSettings"); + ::SWGSDRangel::setValue(&blade_rf1_output_settings, pJson["bladeRF1OutputSettings"], "SWGBladeRF1OutputSettings", "SWGBladeRF1OutputSettings"); ::SWGSDRangel::setValue(&fcd_pro_settings, pJson["fcdProSettings"], "SWGFCDProSettings", "SWGFCDProSettings"); @@ -271,6 +280,9 @@ SWGDeviceSettings::asJsonObject() { if((blade_rf1_input_settings != nullptr) && (blade_rf1_input_settings->isSet())){ toJsonValue(QString("bladeRF1InputSettings"), blade_rf1_input_settings, obj, QString("SWGBladeRF1InputSettings")); } + if((blade_rf2_input_settings != nullptr) && (blade_rf2_input_settings->isSet())){ + toJsonValue(QString("bladeRF2InputSettings"), blade_rf2_input_settings, obj, QString("SWGBladeRF2InputSettings")); + } if((blade_rf1_output_settings != nullptr) && (blade_rf1_output_settings->isSet())){ toJsonValue(QString("bladeRF1OutputSettings"), blade_rf1_output_settings, obj, QString("SWGBladeRF1OutputSettings")); } @@ -373,6 +385,16 @@ SWGDeviceSettings::setBladeRf1InputSettings(SWGBladeRF1InputSettings* blade_rf1_ this->m_blade_rf1_input_settings_isSet = true; } +SWGBladeRF2InputSettings* +SWGDeviceSettings::getBladeRf2InputSettings() { + return blade_rf2_input_settings; +} +void +SWGDeviceSettings::setBladeRf2InputSettings(SWGBladeRF2InputSettings* blade_rf2_input_settings) { + this->blade_rf2_input_settings = blade_rf2_input_settings; + this->m_blade_rf2_input_settings_isSet = true; +} + SWGBladeRF1OutputSettings* SWGDeviceSettings::getBladeRf1OutputSettings() { return blade_rf1_output_settings; @@ -543,6 +565,7 @@ SWGDeviceSettings::isSet(){ if(airspy_settings != nullptr && airspy_settings->isSet()){ isObjectUpdated = true; break;} if(airspy_hf_settings != nullptr && airspy_hf_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf1_input_settings != nullptr && blade_rf1_input_settings->isSet()){ isObjectUpdated = true; break;} + if(blade_rf2_input_settings != nullptr && blade_rf2_input_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf1_output_settings != nullptr && blade_rf1_output_settings->isSet()){ isObjectUpdated = true; break;} if(fcd_pro_settings != nullptr && fcd_pro_settings->isSet()){ isObjectUpdated = true; break;} if(fcd_pro_plus_settings != nullptr && fcd_pro_plus_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index cdbcd9627..9ed650e38 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -26,6 +26,7 @@ #include "SWGAirspySettings.h" #include "SWGBladeRF1InputSettings.h" #include "SWGBladeRF1OutputSettings.h" +#include "SWGBladeRF2InputSettings.h" #include "SWGFCDProPlusSettings.h" #include "SWGFCDProSettings.h" #include "SWGFileSourceSettings.h" @@ -76,6 +77,9 @@ public: SWGBladeRF1InputSettings* getBladeRf1InputSettings(); void setBladeRf1InputSettings(SWGBladeRF1InputSettings* blade_rf1_input_settings); + SWGBladeRF2InputSettings* getBladeRf2InputSettings(); + void setBladeRf2InputSettings(SWGBladeRF2InputSettings* blade_rf2_input_settings); + SWGBladeRF1OutputSettings* getBladeRf1OutputSettings(); void setBladeRf1OutputSettings(SWGBladeRF1OutputSettings* blade_rf1_output_settings); @@ -143,6 +147,9 @@ private: SWGBladeRF1InputSettings* blade_rf1_input_settings; bool m_blade_rf1_input_settings_isSet; + SWGBladeRF2InputSettings* blade_rf2_input_settings; + bool m_blade_rf2_input_settings_isSet; + SWGBladeRF1OutputSettings* blade_rf1_output_settings; bool m_blade_rf1_output_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index aaf1bb2df..d49a92e09 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -32,6 +32,8 @@ #include "SWGBandwidth.h" #include "SWGBladeRF1InputSettings.h" #include "SWGBladeRF1OutputSettings.h" +#include "SWGBladeRF2InputReport.h" +#include "SWGBladeRF2InputSettings.h" #include "SWGCWKeyerSettings.h" #include "SWGChannel.h" #include "SWGChannelListItem.h" @@ -89,6 +91,7 @@ #include "SWGPresets.h" #include "SWGRDSReport.h" #include "SWGRDSReport_altFrequencies.h" +#include "SWGRange.h" #include "SWGRtlSdrReport.h" #include "SWGRtlSdrSettings.h" #include "SWGSDRPlayReport.h" @@ -171,6 +174,12 @@ namespace SWGSDRangel { if(QString("SWGBladeRF1OutputSettings").compare(type) == 0) { return new SWGBladeRF1OutputSettings(); } + if(QString("SWGBladeRF2InputReport").compare(type) == 0) { + return new SWGBladeRF2InputReport(); + } + if(QString("SWGBladeRF2InputSettings").compare(type) == 0) { + return new SWGBladeRF2InputSettings(); + } if(QString("SWGCWKeyerSettings").compare(type) == 0) { return new SWGCWKeyerSettings(); } @@ -342,6 +351,9 @@ namespace SWGSDRangel { if(QString("SWGRDSReport_altFrequencies").compare(type) == 0) { return new SWGRDSReport_altFrequencies(); } + if(QString("SWGRange").compare(type) == 0) { + return new SWGRange(); + } if(QString("SWGRtlSdrReport").compare(type) == 0) { return new SWGRtlSdrReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGRange.cpp b/swagger/sdrangel/code/qt5/client/SWGRange.cpp new file mode 100644 index 000000000..26805098b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRange.cpp @@ -0,0 +1,148 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGRange.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRange::SWGRange(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRange::SWGRange() { + min = 0; + m_min_isSet = false; + max = 0; + m_max_isSet = false; + step = 0; + m_step_isSet = false; +} + +SWGRange::~SWGRange() { + this->cleanup(); +} + +void +SWGRange::init() { + min = 0; + m_min_isSet = false; + max = 0; + m_max_isSet = false; + step = 0; + m_step_isSet = false; +} + +void +SWGRange::cleanup() { + + + +} + +SWGRange* +SWGRange::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRange::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&min, pJson["min"], "qint32", ""); + + ::SWGSDRangel::setValue(&max, pJson["max"], "qint32", ""); + + ::SWGSDRangel::setValue(&step, pJson["step"], "qint32", ""); + +} + +QString +SWGRange::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRange::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_min_isSet){ + obj->insert("min", QJsonValue(min)); + } + if(m_max_isSet){ + obj->insert("max", QJsonValue(max)); + } + if(m_step_isSet){ + obj->insert("step", QJsonValue(step)); + } + + return obj; +} + +qint32 +SWGRange::getMin() { + return min; +} +void +SWGRange::setMin(qint32 min) { + this->min = min; + this->m_min_isSet = true; +} + +qint32 +SWGRange::getMax() { + return max; +} +void +SWGRange::setMax(qint32 max) { + this->max = max; + this->m_max_isSet = true; +} + +qint32 +SWGRange::getStep() { + return step; +} +void +SWGRange::setStep(qint32 step) { + this->step = step; + this->m_step_isSet = true; +} + + +bool +SWGRange::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_min_isSet){ isObjectUpdated = true; break;} + if(m_max_isSet){ isObjectUpdated = true; break;} + if(m_step_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGRange.h b/swagger/sdrangel/code/qt5/client/SWGRange.h new file mode 100644 index 000000000..65be2f30b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRange.h @@ -0,0 +1,70 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGRange.h + * + * An arbitrary range of values + */ + +#ifndef SWGRange_H_ +#define SWGRange_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRange: public SWGObject { +public: + SWGRange(); + SWGRange(QString* json); + virtual ~SWGRange(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRange* fromJson(QString &jsonString) override; + + qint32 getMin(); + void setMin(qint32 min); + + qint32 getMax(); + void setMax(qint32 max); + + qint32 getStep(); + void setStep(qint32 step); + + + virtual bool isSet() override; + +private: + qint32 min; + bool m_min_isSet; + + qint32 max; + bool m_max_isSet; + + qint32 step; + bool m_step_isSet; + +}; + +} + +#endif /* SWGRange_H_ */ From f7af0a4ac283aa2d6e92ff8f86d225a0cf38a56a Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 23 Sep 2018 19:56:24 +0200 Subject: [PATCH 785/956] BladerRF2 input support (2) --- Readme.md | 26 +- devices/bladerf2/CMakeLists.txt | 2 + devices/bladerf2/devicebladerf2.cpp | 198 ++++- devices/bladerf2/devicebladerf2.h | 18 +- devices/bladerf2/devicebladerf2shared.cpp | 28 + devices/bladerf2/devicebladerf2shared.h | 62 ++ ...ut_plugin.png => BladeRF1Input_plugin.png} | Bin ...ut_plugin.xcf => BladeRF1Input_plugin.xcf} | Bin ...t_plugin.png => BladeRF1Output_plugin.png} | Bin ...t_plugin.xcf => BladeRF1Output_plugin.xcf} | Bin ...g => BladeRF1Output_plugin_fifodly_32.png} | Bin ...> BladeRF1Output_plugin_fifodly_other.png} | Bin plugins/samplesink/bladerf1output/readme.md | 16 +- plugins/samplesource/CMakeLists.txt | 2 + plugins/samplesource/bladerf1input/readme.md | 10 +- .../samplesource/bladerf2input/CMakeLists.txt | 79 ++ .../bladerf2input/bladerf2input.cpp | 220 ++++++ .../bladerf2input/bladerf2input.h | 154 ++++ .../bladerf2input/bladerf2inputgui.ui | 727 ++++++++++++++++++ .../bladerf2input/bladerf2inputplugin.cpp | 25 +- .../bladerf2input/bladerf2inputthread.cpp | 264 +++++++ .../bladerf2input/bladerf2inputthread.h | 90 +++ .../limesdrinput/limesdrinput.cpp | 14 + 23 files changed, 1904 insertions(+), 31 deletions(-) create mode 100644 devices/bladerf2/devicebladerf2shared.cpp create mode 100644 devices/bladerf2/devicebladerf2shared.h rename doc/img/{BladeRFInput_plugin.png => BladeRF1Input_plugin.png} (100%) rename doc/img/{BladeRFInput_plugin.xcf => BladeRF1Input_plugin.xcf} (100%) rename doc/img/{BladeRFOutput_plugin.png => BladeRF1Output_plugin.png} (100%) rename doc/img/{BladeRFOutput_plugin.xcf => BladeRF1Output_plugin.xcf} (100%) rename doc/img/{BladeRFOutput_plugin_fifodly_32.png => BladeRF1Output_plugin_fifodly_32.png} (100%) rename doc/img/{BladeRFOutput_plugin_fifodly_other.png => BladeRF1Output_plugin_fifodly_other.png} (100%) create mode 100644 plugins/samplesource/bladerf2input/CMakeLists.txt create mode 100644 plugins/samplesource/bladerf2input/bladerf2input.cpp create mode 100644 plugins/samplesource/bladerf2input/bladerf2input.h create mode 100644 plugins/samplesource/bladerf2input/bladerf2inputgui.ui create mode 100644 plugins/samplesource/bladerf2input/bladerf2inputthread.cpp create mode 100644 plugins/samplesource/bladerf2input/bladerf2inputthread.h diff --git a/Readme.md b/Readme.md index fe8969996..4a08d8ce5 100644 --- a/Readme.md +++ b/Readme.md @@ -36,7 +36,7 @@ Since version 2 SDRangel can integrate more than one hardware device running con Since version 3 transmission or signal generation is supported for BladeRF, HackRF (since version 3.1), LimeSDR (since version 3.4) and PlutoSDR (since version 3.7.8) using a sample sink plugin. These plugins are: - - [BladeRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) + - [BladeRF1 output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) - [HackRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/hackrfoutput) - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) - [PlutoSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/plutosdroutput) @@ -96,15 +96,31 @@ It is recommended to add `-DRX_SAMPLE_24BIT=ON` on the cmake command line to act ☞ From version 3.12.0 the Linux binaries are built with the 24 bit Rx option. -

    BladeRF

    +

    BladeRF classic (v.1)

    -[BladeRF](https://www.nuand.com/) is supported through the libbladerf library that should be installed in your system for proper build of the software and operation support. Add `libbladerf-dev` to the list of dependencies to install. +Linux only. -If you use your own location for libbladeRF install directory you need to specify library and include locations. Example with `/opt/install/libbladerf` with the following defines on `cmake` command line: +[BladeRF1](https://www.nuand.com/bladerf-1) is supported through the libbladeRF library that should be installed in your system for proper build of the software and operation support. Add `libbladerf-dev` to the list of dependencies to install. Note that libbladeRF v2 is used since version 4.2.0 (git tag 2018.08). + +If you compile and use your own location for libbladeRF install directory you need to specify library and include locations. Example with `/opt/install/libbladerf` with the following defines on `cmake` command line: `-DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so -DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include` -☞ Please note that if you use your own library the FPGA image `hostedx40.rbf` or `hostedx115.rbf` shoud be placed in e.g. `/opt/install/libbladeRF/share/Nuand/bladeRF` +☞ Please note that if you use your own library the FPGA image `hostedx40.rbf` or `hostedx115.rbf` shoud be placed in e.g. `/opt/install/libbladeRF/share/Nuand/bladeRF` unless you have flashed the FPGA image inside the BladeRF. + +The plugins used to support BladeRF classic are specific to this version of the BladeRF: + - [BladeRF1 input](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/bladerf1input) + - [BladeRF1 output](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) + +

    BladeRF micro (v.2)

    + +Linux only. From version 4.2.0 + +[BladeRF 2 micro](https://www.nuand.com/bladerf-2-0-micro/) is also supported using libbladeRF library that should be installed and configured in the same way as for BladeRF1. + +The plugins used to support BladeRF 2 micro are specific to this version of the BladeRF: + - [BladeRF2 input](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/bladerf2input) + - [BladeRF2 output](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf2output)

    FunCube Dongle

    diff --git a/devices/bladerf2/CMakeLists.txt b/devices/bladerf2/CMakeLists.txt index f92ad7686..2b0c96490 100644 --- a/devices/bladerf2/CMakeLists.txt +++ b/devices/bladerf2/CMakeLists.txt @@ -2,10 +2,12 @@ project(bladerf2device) set(bladerf2device_SOURCES devicebladerf2.cpp + devicebladerf2shared.cpp ) set(bladerf2device_HEADERS devicebladerf2.h + devicebladerf2shared.h ) if (BUILD_DEBIAN) diff --git a/devices/bladerf2/devicebladerf2.cpp b/devices/bladerf2/devicebladerf2.cpp index a82e9f493..7b7e4a772 100644 --- a/devices/bladerf2/devicebladerf2.cpp +++ b/devices/bladerf2/devicebladerf2.cpp @@ -22,7 +22,11 @@ #include "devicebladerf2.h" DeviceBladeRF2::DeviceBladeRF2() : - m_dev(0) + m_dev(0), + m_nbRxChannels(0), + m_nbTxChannels(0), + m_rxOpen(0), + m_txOpen(0) {} DeviceBladeRF2::~DeviceBladeRF2() @@ -32,6 +36,14 @@ DeviceBladeRF2::~DeviceBladeRF2() bladerf_close(m_dev); m_dev = 0; } + + if (m_rxOpen) { + delete[] m_rxOpen; + } + + if (m_txOpen) { + delete[] m_txOpen; + } } bool DeviceBladeRF2::open(const char *serial) @@ -58,6 +70,12 @@ bool DeviceBladeRF2::open(const char *serial) return false; } + m_nbRxChannels = bladerf_get_channel_count(m_dev, BLADERF_RX); + m_nbTxChannels = bladerf_get_channel_count(m_dev, BLADERF_TX); + + m_rxOpen = new bool[m_nbRxChannels]; + m_txOpen = new bool[m_nbTxChannels]; + return true; } @@ -100,7 +118,157 @@ struct bladerf *DeviceBladeRF2::open_bladerf_from_serial(const char *serial) } } -void DeviceBladeRF2::getFrequencyRangeRx(int& min, int& max, int& step) +bool DeviceBladeRF2::openRx(int channel) +{ + if (!m_dev) { + return false; + } + + if ((channel < 0) || (channel >= m_nbRxChannels)) + { + qCritical("DeviceBladeRF2::openRx: invalid Rx channel index %d", channel); + return false; + } + + int status; + + if (!m_rxOpen[channel]) + { + status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_RX(channel), true); + + if (status < 0) + { + qCritical("DeviceBladeRF2::openRx: Failed to enable Rx channel %d: %s", + channel, bladerf_strerror(status)); + return false; + } + else + { + qDebug("DeviceBladeRF2::openRx: Rx channel %d enabled", channel); + m_rxOpen[channel] = true; + return true; + } + } + else + { + qCritical("DeviceBladeRF2::openRx: Rx channel %d already opened", channel); + return false; + } +} + +bool DeviceBladeRF2::openTx(int channel) +{ + if (!m_dev) { + return false; + } + + if ((channel < 0) || (channel >= m_nbTxChannels)) + { + qCritical("DeviceBladeRF2::openTx: invalid Tx channel index %d", channel); + return false; + } + + int status; + + if (!m_txOpen[channel]) + { + status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(0), true); + + if (status < 0) + { + qCritical("DeviceBladeRF2::openTx: Failed to enable Tx channel %d: %s", + channel, bladerf_strerror(status)); + return false; + } + else + { + qDebug("DeviceBladeRF2::openTx: Tx channel %d enabled", channel); + m_txOpen[channel] = true; + return true; + } + } + else + { + qCritical("DeviceBladeRF2::openTx: Tx channel %d already opened", channel); + return false; + } +} + +void DeviceBladeRF2::closeRx(int channel) +{ + if (!m_dev) { + return; + } + + if ((channel < 0) || (channel >= m_nbRxChannels)) + { + qCritical("DeviceBladeRF2::closeRx: invalid Rx channel index %d", channel); + return; + } + + if (m_rxOpen[channel]) + { + for (int i = 0; i < m_nbRxChannels; i++) + { + if ((i != channel) && (m_rxOpen[i])) + { + qDebug("DeviceBladeRF2::closeRx: not closing channel %d as %d is still open", channel, i); + } + } + + int status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_RX(channel), false); + m_rxOpen[channel] = false; + + if (status < 0) { + qCritical("DeviceBladeRF2::closeRx: cannot close channel %d: %s", channel, bladerf_strerror(status)); + } else { + qDebug("DeviceBladeRF2::closeRx: channel %d closed", channel); + } + } + else + { + qCritical("DeviceBladeRF2::closeRx: Rx channel %d already closed", channel); + } +} + +void DeviceBladeRF2::closeTx(int channel) +{ + if (!m_dev) { + return; + } + + if ((channel < 0) || (channel >= m_nbTxChannels)) + { + qCritical("DeviceBladeRF2::closeTx: invalid Tx channel index %d", channel); + return; + } + + if (m_txOpen[channel]) + { + for (int i = 0; i < m_nbTxChannels; i++) + { + if ((i != channel) && (m_txOpen[i])) + { + qDebug("DeviceBladeRF2::closeTx: not closing channel %d as %d is still open", channel, i); + } + } + + int status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(channel), false); + m_txOpen[channel] = false; + + if (status < 0) { + qCritical("DeviceBladeRF2::closeTx: cannot close channel %d: %s", channel, bladerf_strerror(status)); + } else { + qDebug("DeviceBladeRF2::closeTx: channel %d closed", channel); + } + } + else + { + qCritical("DeviceBladeRF2::closeTx: Rx channel %d already closed", channel); + } +} + +void DeviceBladeRF2::getFrequencyRangeRx(uint64_t& min, uint64_t& max, int& step) { if (m_dev) { @@ -123,7 +291,7 @@ void DeviceBladeRF2::getFrequencyRangeRx(int& min, int& max, int& step) } } -void DeviceBladeRF2::getFrequencyRangeTx(int& min, int& max, int& step) +void DeviceBladeRF2::getFrequencyRangeTx(uint64_t& min, uint64_t& max, int& step) { if (m_dev) { @@ -284,3 +452,27 @@ void DeviceBladeRF2::getGlobalGainRangeTx(int& min, int& max, int& step) } } } + +void DeviceBladeRF2::setBiasTeeRx(bool enable) +{ + if (m_dev) + { + int status = bladerf_set_bias_tee(m_dev, BLADERF_CHANNEL_RX(0), enable); + + if (status < 0) { + qCritical("DeviceBladeRF2::setBiasTeeRx: Failed to set Rx bias tee: %s", bladerf_strerror(status)); + } + } +} + +void DeviceBladeRF2::setBiasTeeTx(bool enable) +{ + if (m_dev) + { + int status = bladerf_set_bias_tee(m_dev, BLADERF_CHANNEL_TX(0), enable); + + if (status < 0) { + qCritical("DeviceBladeRF2::setBiasTeeTx: Failed to set Tx bias tee: %s", bladerf_strerror(status)); + } + } +} diff --git a/devices/bladerf2/devicebladerf2.h b/devices/bladerf2/devicebladerf2.h index 76e8c4815..30b2cf6a8 100644 --- a/devices/bladerf2/devicebladerf2.h +++ b/devices/bladerf2/devicebladerf2.h @@ -31,17 +31,31 @@ public: bool open(const char *serial); void close(); - void getFrequencyRangeRx(int& min, int& max, int& step); - void getFrequencyRangeTx(int& min, int& max, int& step); + bool openRx(int channel); + bool openTx(int channel); + void closeRx(int channel); + void closeTx(int channel); + + void getFrequencyRangeRx(uint64_t& min, uint64_t& max, int& step); + void getFrequencyRangeTx(uint64_t& min, uint64_t& max, int& step); void getSampleRateRangeRx(int& min, int& max, int& step); void getSampleRateRangeTx(int& min, int& max, int& step); void getBandwidthRangeRx(int& min, int& max, int& step); void getBandwidthRangeTx(int& min, int& max, int& step); void getGlobalGainRangeRx(int& min, int& max, int& step); void getGlobalGainRangeTx(int& min, int& max, int& step); + void setBiasTeeRx(bool enable); + void setBiasTeeTx(bool enable); + + static const unsigned int blockSize = (1<<14); private: bladerf *m_dev; + int m_nbRxChannels; + int m_nbTxChannels; + bool *m_rxOpen; + bool *m_txOpen; + static struct bladerf *open_bladerf_from_serial(const char *serial); }; diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp new file mode 100644 index 000000000..4bb577367 --- /dev/null +++ b/devices/bladerf2/devicebladerf2shared.cpp @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "devicebladerf2shared.h" + +DeviceBladeRF2Shared::DeviceBladeRF2Shared() : + m_dev(0), + m_channel(-1) +{} + +DeviceBladeRF2Shared::~DeviceBladeRF2Shared() +{} + + + diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h new file mode 100644 index 000000000..0f6378191 --- /dev/null +++ b/devices/bladerf2/devicebladerf2shared.h @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ +#define DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ + +#include "devicebladerf2.h" + +class SampleSinkFifo; +class SampleSourceFifo; + +/** + * Structure shared by a buddy with other buddies + */ +class DEVICES_API DeviceBladeRF2Shared +{ +public: + class InputThreadInterface + { + public: + virtual void startWork() = 0; + virtual void stopWork() = 0; + virtual bool isRunning() const = 0; + virtual void setFifo(unsigned int channel, SampleSinkFifo *fifo) = 0; + virtual SampleSinkFifo *getFifo(unsigned int channel) = 0; + }; + + class OutputThreadInterface + { + public: + virtual void startWork() = 0; + virtual void stopWork() = 0; + virtual bool isRunning() = 0; + virtual void setFifo(unsigned int channel, SampleSourceFifo *fifo) = 0; + virtual SampleSourceFifo *getFifo(unsigned int channel) = 0; + }; + + DeviceBladeRF2Shared(); + ~DeviceBladeRF2Shared(); + + DeviceBladeRF2 *m_dev; + int m_channel; //!< allocated channel (-1 if none) + InputThreadInterface *m_inputThread; //!< The SISO/MIMO input thread + OutputThreadInterface *m_outputThread; //!< The SISO/MIMO output thread +}; + + + +#endif /* DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ */ diff --git a/doc/img/BladeRFInput_plugin.png b/doc/img/BladeRF1Input_plugin.png similarity index 100% rename from doc/img/BladeRFInput_plugin.png rename to doc/img/BladeRF1Input_plugin.png diff --git a/doc/img/BladeRFInput_plugin.xcf b/doc/img/BladeRF1Input_plugin.xcf similarity index 100% rename from doc/img/BladeRFInput_plugin.xcf rename to doc/img/BladeRF1Input_plugin.xcf diff --git a/doc/img/BladeRFOutput_plugin.png b/doc/img/BladeRF1Output_plugin.png similarity index 100% rename from doc/img/BladeRFOutput_plugin.png rename to doc/img/BladeRF1Output_plugin.png diff --git a/doc/img/BladeRFOutput_plugin.xcf b/doc/img/BladeRF1Output_plugin.xcf similarity index 100% rename from doc/img/BladeRFOutput_plugin.xcf rename to doc/img/BladeRF1Output_plugin.xcf diff --git a/doc/img/BladeRFOutput_plugin_fifodly_32.png b/doc/img/BladeRF1Output_plugin_fifodly_32.png similarity index 100% rename from doc/img/BladeRFOutput_plugin_fifodly_32.png rename to doc/img/BladeRF1Output_plugin_fifodly_32.png diff --git a/doc/img/BladeRFOutput_plugin_fifodly_other.png b/doc/img/BladeRF1Output_plugin_fifodly_other.png similarity index 100% rename from doc/img/BladeRFOutput_plugin_fifodly_other.png rename to doc/img/BladeRF1Output_plugin_fifodly_other.png diff --git a/plugins/samplesink/bladerf1output/readme.md b/plugins/samplesink/bladerf1output/readme.md index 5b724dec5..fde65d313 100644 --- a/plugins/samplesink/bladerf1output/readme.md +++ b/plugins/samplesink/bladerf1output/readme.md @@ -1,20 +1,22 @@ -

    BladeRF output plugin

    +

    BladeRF classic (v1) output plugin

    Introduction

    -This output sample sink plugin sends its samples to a [BladeRF device](https://www.nuand.com/). +This output sample sink plugin sends its samples to a [BladeRF1 device](https://www.nuand.com/bladerf-1). -Warning to Windows users: concurrent use of Rx and Tx does not work correctly hence full duplex is not fully operational. For best results use BladeRF as a half duplex device like HackRF i.e. do not run Tx and Rx concurrently. +Warning to Windows users: concurrent use of Rx and Tx does not work correctly hence full duplex is not fully operational. For best results use BladeRF as a half duplex device like HackRF i.e. do not run Tx and Rx concurrently. Anyway from version 4.2.0 using LibbladeRF v.2 this is available in Linux distributions only.

    Build

    The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. -The BladeRF Host library is also provided by many Linux distributions and is built in the SDRangel binary releases. +Note that since version 4.2.0 the libbladeRF v2 (specifically the git tag 2018.08) is used. + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases.

    Interface

    -![BladeRF output plugin GUI](../../../doc/img/BladeRFOutput_plugin.png) +![BladeRF1 output plugin GUI](../../../doc/img/BladeRF1Output_plugin.png)

    1: Start/Stop

    @@ -32,11 +34,11 @@ Transmission latency depends essentially in the delay in the sample FIFO. The FI For interpolation by 32 the size is fixed at 150000 samples, Delay is 150000 / B where B is the baseband sample rate. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 500 kS/s: -![BladeRF output plugin FIFO delay 32](../../../doc/img/BladeRFOutput_plugin_fifodly_32.png) +![BladeRF1 output plugin FIFO delay 32](../../../doc/img/BladeRF1Output_plugin_fifodly_32.png) For lower interpolation rates the size is calculated to give a fixed delay of 250 ms or 75000 samples whichever is bigger. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 400 kS/s. The 250 ms delay is reached at 300 kS/s: -![BladeRF output plugin FIFO delay other](../../../doc/img/BladeRFOutput_plugin_fifodly_other.png) +![BladeRF1 output plugin FIFO delay other](../../../doc/img/BladeRF1Output_plugin_fifodly_other.png)

    3: Frequency

    diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index d4698339d..c3c54d52a 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -26,6 +26,7 @@ endif(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) if(LIBUSB_FOUND AND UNIX) @@ -80,6 +81,7 @@ if (BUILD_DEBIAN) add_subdirectory(airspy) add_subdirectory(airspyhf) add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) add_subdirectory(hackrfinput) add_subdirectory(limesdrinput) add_subdirectory(perseus) diff --git a/plugins/samplesource/bladerf1input/readme.md b/plugins/samplesource/bladerf1input/readme.md index 2f0e8f01e..cdfe764a2 100644 --- a/plugins/samplesource/bladerf1input/readme.md +++ b/plugins/samplesource/bladerf1input/readme.md @@ -1,18 +1,20 @@ -

    BladeRF input plugin

    +

    BladeRF classic (v1) input plugin

    Introduction

    -This input sample source plugin gets its samples from a [BladeRF device](https://www.nuand.com/). +This input sample source plugin gets its samples from a [BladeRF1 device](https://www.nuand.com/bladerf-1). From version 4.2.0 using LibbladeRF v.2 this is available in Linux distributions only.

    Build

    The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. -The BladeRF Host library is also provided by many Linux distributions and is built in the SDRangel binary releases. +Note that since version 4.2.0 the libbladeRF v2 (specifically the git tag 2018.08) is used. + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases.

    Interface

    -![BladeRF input plugin GUI](../../../doc/img/BladeRFInput_plugin.png) +![BladeRF1 input plugin GUI](../../../doc/img/BladeRF1Input_plugin.png)

    1: Common stream parameters

    diff --git a/plugins/samplesource/bladerf2input/CMakeLists.txt b/plugins/samplesource/bladerf2input/CMakeLists.txt new file mode 100644 index 000000000..6594e1cff --- /dev/null +++ b/plugins/samplesource/bladerf2input/CMakeLists.txt @@ -0,0 +1,79 @@ +project(bladerf2input) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(bladerf2input_SOURCES + #bladerf2inputgui.cpp + bladerf2input.cpp + #bladerf2inputplugin.cpp + bladerf2inputsettings.cpp + bladerf2inputthread.cpp +) + +set(bladerf2input_HEADERS + #bladerf2inputgui.h + bladerf2input.h + #bladerf2inputplugin.h + bladerf2inputsettings.h + bladerf2inputthread.h +) + +set(bladerf2input_FORMS + bladerf2inputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(bladerf2input_FORMS_HEADERS ${bladerf2input_FORMS}) + +add_library(inputbladerf2 SHARED + ${bladerf2input_SOURCES} + ${bladerf2input_HEADERS_MOC} + ${bladerf2input_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputbladerf2 + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(inputbladerf2 + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + sdrgui + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputbladerf2 Qt5::Core Qt5::Widgets) + +install(TARGETS inputbladerf2 DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp new file mode 100644 index 000000000..ebbee96a3 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -0,0 +1,220 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "SWGDeviceSettings.h" +#include "SWGBladeRF2InputSettings.h" +#include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGBladeRF2InputReport.h" + +#include "device/devicesourceapi.h" +#include "device/devicesinkapi.h" +#include "dsp/dspcommands.h" +#include "dsp/filerecord.h" +#include "dsp/dspengine.h" + +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2/devicebladerf2.h" +#include "bladerf2input.h" + + +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgConfigureBladeRF2, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgFileRecord, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgStartStop, Message) + +BladeRF2Input::BladeRF2Input(DeviceSourceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_deviceDescription("BladeRF2Input"), + m_running(false) +{ + openDevice(); + + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); + m_deviceAPI->addSink(m_fileSink); +} + +BladeRF2Input::~BladeRF2Input() +{ + if (m_running) { + stop(); + } + + m_deviceAPI->removeSink(m_fileSink); + delete m_fileSink; + closeDevice(); +} + +void BladeRF2Input::destroy() +{ + delete this; +} + +bool BladeRF2Input::openDevice() +{ + if (!m_sampleFifo.setSize(96000 * 4)) + { + qCritical("BladeRF2Input::openDevice: could not allocate SampleFifo"); + return false; + } + else + { + qDebug("BladeRF2Input::openDevice: allocated SampleFifo"); + } + + // look for Rx buddies and get reference to the device object + // if there is a channel left take the first available + if (m_deviceAPI->getSourceBuddies().size() > 0) // look source sibling first + { + qDebug("BladeRF2Input::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Input::openDevice: the source buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Input::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (requestedChannel == deviceBladeRF2Shared->m_channel) + { + qCritical("BladeRF2Input::openDevice: channel %u already in use", requestedChannel); + return false; + } + + if (!device->openRx(requestedChannel)) + { + qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); + return false; + } + else + { + m_deviceShared.m_channel = requestedChannel; + qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); + } + } + // look for Tx buddies and get reference to the device object + // allocate the Rx channel unconditionally + else if (m_deviceAPI->getSinkBuddies().size() > 0) // then sink + { + qDebug("BladeRF2Input::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Input::openDevice: the sink buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Input::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (!device->openRx(requestedChannel)) + { + qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); + return false; + } + else + { + m_deviceShared.m_channel = requestedChannel; + qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); + } + } + // There are no buddies then create the first BladeRF2 device + // allocate the Rx channel unconditionally + else + { + qDebug("BladeRF2Input::openDevice: open device here"); + + m_deviceShared.m_dev = new DeviceBladeRF2(); + char serial[256]; + strcpy(serial, qPrintable(m_deviceAPI->getSampleSourceSerial())); + + if (!m_deviceShared.m_dev->open(serial)) + { + qCritical("BladeRF2Input::openDevice: cannot open BladeRF2 device"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (!m_deviceShared.m_dev->openRx(requestedChannel)) + { + qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); + return false; + } + else + { + m_deviceShared.m_channel = requestedChannel; + qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); + } + } + + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void BladeRF2Input::closeDevice() +{ + if (m_deviceShared.m_dev == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + + m_deviceShared.m_channel = -1; + + // No buddies so effectively close the device + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + m_deviceShared.m_dev->close(); + delete m_deviceShared.m_dev; + m_deviceShared.m_dev = 0; + } +} + +void BladeRF2Input::init() +{ + applySettings(m_settings, true); +} + diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h new file mode 100644 index 000000000..5798caf25 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -0,0 +1,154 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ + +#include +#include +#include + +#include "dsp/devicesamplesource.h" +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2inputsettings.h" + +class DeviceSourceAPI; +class LimeSDRInputThread; +class FileRecord; + +class BladeRF2Input : public DeviceSampleSource +{ +public: + class MsgConfigureBladeRF2 : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const BladeRF2InputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureBladeRF2* create(const BladeRF2InputSettings& settings, bool force) + { + return new MsgConfigureBladeRF2(settings, force); + } + + private: + BladeRF2InputSettings m_settings; + bool m_force; + + MsgConfigureBladeRF2(const BladeRF2InputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgFileRecord : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgFileRecord* create(bool startStop) { + return new MsgFileRecord(startStop); + } + + protected: + bool m_startStop; + + MsgFileRecord(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + 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) + { } + }; + + BladeRF2Input(DeviceSourceAPI *deviceAPI); + virtual ~BladeRF2Input(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + +private: + DeviceSourceAPI *m_deviceAPI; + QMutex m_mutex; + BladeRF2InputSettings m_settings; + QString m_deviceDescription; + bool m_running; + DeviceBladeRF2Shared m_deviceShared; + FileRecord *m_fileSink; //!< File sink to record device I/Q output + + bool openDevice(); + void closeDevice(); + bool applySettings(const BladeRF2InputSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); +}; + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui new file mode 100644 index 000000000..527110d64 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui @@ -0,0 +1,727 @@ + + + Bladerf2InputGui + + + + 0 + 0 + 310 + 265 + + + + + 0 + 0 + + + + + 310 + 250 + + + + + Liberation Sans + 9 + + + + BladeRF2 + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + Toggle record I/Q samples from device + + + + + + + :/record_off.png + :/record_on.png:/record_off.png + + + + + + + + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + + + Automatic IQ imbalance correction + + + IQ + + + + + + + Automatic DC offset removal + + + DC + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Auto + + + + + + + xb200 + + + + + + + XB200 board mode + + + None + + + 0 + + + 5 + + + + None + + + + + Bypass + + + + + Auto 1dB + + + + + Auto 3dB + + + + + Custom + + + + + 50M + + + + + 144M + + + + + 222M + + + + + + + + + + Qt::Horizontal + + + + + + + 2 + + + 2 + + + + + + 0 + 0 + + + + SR + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + + + + + S/s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Dec + + + + + + + + 50 + 16777215 + + + + Decimation factor + + + 0 + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + + + 3 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + BW + + + + + + + Fp + + + + + + + Relative position of device center frequency + + + + Inf + + + + + Sup + + + + + Cen + + + + + + + + + 0 + 0 + + + + LNA + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 40 + 16777215 + + + + + 0 + + + + + 3 + + + + + 6 + + + + + + + + dB + + + + + + + kHz + + + + + + + + 70 + 16777215 + + + + IF bandwidth in kHz + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + 3 + + + + + Amplifier before filtering gain (dB) + + + 5 + + + 30 + + + 1 + + + 20 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 20 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + VGA1 + + + + + + + + + Qt::Horizontal + + + + + + + 3 + + + + + VGA2 + + + + + + + Amplifier before ADC gain (dB) + + + 30 + + + 3 + + + 3 + + + 9 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 9 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + + + + + ValueDial + QWidget +
    gui/valuedial.h
    + 1 +
    + + ButtonSwitch + QToolButton +
    gui/buttonswitch.h
    +
    +
    + + + + +
    diff --git a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp index 340069cda..db6b282c4 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp @@ -85,17 +85,22 @@ PluginInterface::SamplingDevices Blderf2InputPlugin::enumSampleSources() if (strcmp(boardName, "bladerf2") == 0) { - QString displayedName(QString("BladeRF2[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); + unsigned int nbRxChannels = bladerf_get_channel_count(dev, BLADERF_RX); - result.append(SamplingDevice(displayedName, - m_hardwareID, - m_deviceTypeID, - QString(devinfo[i].serial), - i, - PluginInterface::SamplingDevice::PhysicalDevice, - true, - 1, - 0)); + for (int j = 0; j < nbRxChannels; j++) + { + qDebug("Blderf2InputPlugin::enumSampleSources: device #%d (%s) channel %u", i, devinfo[i].serial, j); + QString displayedName(QString("BladeRF2[%1:%2] %3").arg(devinfo[i].instance).arg(j).arg(devinfo[i].serial)); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + true, + 1, + j)); + } } bladerf_close(dev); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp new file mode 100644 index 000000000..c84e02fc3 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -0,0 +1,264 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "bladerf2inputthread.h" + +Bladerf2InputThread::Bladerf2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_nbChannels(nbRxChannels) +{ + m_channels = new Channel[nbRxChannels]; + m_buf = new qint16[2*DeviceBladeRF2::blockSize*nbRxChannels]; +} + +Bladerf2InputThread::~Bladerf2InputThread() +{ + if (m_running) { + stopWork(); + } + + delete[] m_buf; + delete[] m_channels; +} + +void Bladerf2InputThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void Bladerf2InputThread::stopWork() +{ + m_running = false; + wait(); +} + +void Bladerf2InputThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if (nbFifos > 0) + { + int status; + + if (nbFifos > 1) { + status = bladerf_sync_config(m_dev, BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + } else { + status = bladerf_sync_config(m_dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + } + + if (status < 0) + { + qCritical("Bladerf2InputThread::run: cannot configure streams: %s", bladerf_strerror(status)); + } + else + { + while (m_running) + { + res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize, NULL, 10000); + + if (res < 0) + { + qCritical("BladerfThread::run sync Rx error: %s", bladerf_strerror(res)); + break; + } + + if (nbFifos > 1) { + callbackMI(m_buf, DeviceBladeRF2::blockSize); + } else { + callbackSI(m_buf, 2*DeviceBladeRF2::blockSize); + } + } + } + } + else + { + qWarning("Bladerf2InputThread::run: no sample FIFOs registered. Aborting"); + } + + + m_running = false; +} + +unsigned int Bladerf2InputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void Bladerf2InputThread::setLog2Decimation(unsigned int channel, unsigned int log2_decim) +{ + if ((channel >= 0) && (channel < m_nbChannels)) { + m_channels[channel].m_log2Decim = log2_decim; + } +} + +void Bladerf2InputThread::setFcPos(unsigned int channel, int fcPos) +{ + if ((channel >= 0) && (channel < m_nbChannels)) { + m_channels[channel].m_fcPos = fcPos; + } +} + +void Bladerf2InputThread::setFifo(unsigned int channel, SampleSinkFifo *sampleFifo) +{ + if ((channel >= 0) && (channel < m_nbChannels)) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSinkFifo *Bladerf2InputThread::getFifo(unsigned int channel) +{ + if ((channel >= 0) && (channel < m_nbChannels)) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void Bladerf2InputThread::callbackMI(const qint16* buf, qint32 samplesPerChannel) +{ + // TODO: write a set of decimators that can take interleaved samples in input directly + int status = bladerf_deinterleave_stream_buffer(BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11 , samplesPerChannel*m_nbChannels, (void *) buf); + + if (status < 0) + { + qCritical("Bladerf2InputThread::callbackMI: cannot de-interleave buffer: %s", bladerf_strerror(status)); + return; + } + + for (unsigned int channel = 0; channel < m_nbChannels; channel++) + { + if (m_channels[channel].m_sampleFifo) { + callbackSI(&buf[2*samplesPerChannel*channel], 2*samplesPerChannel, channel); + } + } +} + +void Bladerf2InputThread::callbackSI(const qint16* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} + diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.h b/plugins/samplesource/bladerf2input/bladerf2inputthread.h new file mode 100644 index 000000000..23c929f0b --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.h @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ + +// BladerRF2 is a SISO/MIMO device with a single stream supporting one or two Rx +// Therefore only one thread can be allocated for the Rx side +// All FIFOs must be registered before calling startWork() else SISO/MIMO switch will not work properly +// with unpredicatable results + +#include +#include +#include + +#include + +#include "bladerf2/devicebladerf2shared.h" +#include "dsp/samplesinkfifo.h" +#include "dsp/decimators.h" + +class Bladerf2InputThread : public QThread, public DeviceBladeRF2Shared::InputThreadInterface { + Q_OBJECT + +public: + Bladerf2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent = NULL); + virtual ~Bladerf2InputThread(); + + virtual void startWork(); + virtual void stopWork(); + virtual bool isRunning() const { return m_running; } + void setLog2Decimation(unsigned int channel, unsigned int log2_decim); + void setFcPos(unsigned int channel, int fcPos); + virtual void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); + virtual SampleSinkFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleVector m_convertBuffer; + SampleSinkFifo* m_sampleFifo; + unsigned int m_log2Decim; + int m_fcPos; + Decimators m_decimators; + + Channel() : + m_sampleFifo(0), + m_log2Decim(0), + m_fcPos(0) + {} + + ~Channel() + { + if (m_sampleFifo) { + delete[] m_sampleFifo; + } + } + }; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + struct bladerf* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Rx channels + qint16 *m_buf; //!< Full buffer for SISO or MIMO operation + unsigned int m_nbChannels; + + void run(); + unsigned int getNbFifos(); + void callbackSI(const qint16* buf, qint32 len, unsigned int channel = 0); + void callbackMI(const qint16* buf, qint32 samplesPerChannel); +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ */ diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 8a7aa88fe..f623e31e0 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -103,6 +103,13 @@ bool LimeSDRInput::openDevice() DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; //m_deviceShared = *((DeviceLimeSDRShared *) sourceBuddy->getBuddySharedPtr()); // copy shared data DeviceLimeSDRShared *deviceLimeSDRShared = (DeviceLimeSDRShared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceLimeSDRShared == 0) + { + qCritical("LimeSDRInput::openDevice: the source buddy shared pointer is null"); + return false; + } + m_deviceShared.m_deviceParams = deviceLimeSDRShared->m_deviceParams; DeviceLimeSDRParams *deviceParams = m_deviceShared.m_deviceParams; // get device parameters @@ -152,6 +159,13 @@ bool LimeSDRInput::openDevice() DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; //m_deviceShared = *((DeviceLimeSDRShared *) sinkBuddy->getBuddySharedPtr()); // copy parameters DeviceLimeSDRShared *deviceLimeSDRShared = (DeviceLimeSDRShared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceLimeSDRShared == 0) + { + qCritical("LimeSDRInput::openDevice: the sink buddy shared pointer is null"); + return false; + } + m_deviceShared.m_deviceParams = deviceLimeSDRShared->m_deviceParams; if (m_deviceShared.m_deviceParams == 0) From 945d30d91b8df0d788aabe1e947dd531f29bd3ed Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 24 Sep 2018 02:01:10 +0200 Subject: [PATCH 786/956] BladerRF2 input support (3) --- devices/bladerf2/devicebladerf2.h | 2 + devices/bladerf2/devicebladerf2shared.cpp | 4 +- devices/bladerf2/devicebladerf2shared.h | 8 + .../bladerf2input/bladerf2input.cpp | 381 ++++++++++++++++++ .../bladerf2input/bladerf2inputthread.cpp | 34 +- .../bladerf2input/bladerf2inputthread.h | 7 +- 6 files changed, 425 insertions(+), 11 deletions(-) diff --git a/devices/bladerf2/devicebladerf2.h b/devices/bladerf2/devicebladerf2.h index 30b2cf6a8..70df89f17 100644 --- a/devices/bladerf2/devicebladerf2.h +++ b/devices/bladerf2/devicebladerf2.h @@ -31,6 +31,8 @@ public: bool open(const char *serial); void close(); + bladerf *getDev() { return m_dev; } + bool openRx(int channel); bool openTx(int channel); void closeRx(int channel); diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp index 4bb577367..8d8c00eea 100644 --- a/devices/bladerf2/devicebladerf2shared.cpp +++ b/devices/bladerf2/devicebladerf2shared.cpp @@ -18,7 +18,9 @@ DeviceBladeRF2Shared::DeviceBladeRF2Shared() : m_dev(0), - m_channel(-1) + m_channel(-1), + m_inputThread(0), + m_outputThread(0) {} DeviceBladeRF2Shared::~DeviceBladeRF2Shared() diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index 0f6378191..c72524a26 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -31,9 +31,15 @@ public: class InputThreadInterface { public: + virtual ~InputThreadInterface() = 0; virtual void startWork() = 0; virtual void stopWork() = 0; virtual bool isRunning() const = 0; + virtual unsigned int getNbChannels() const = 0; + virtual void setLog2Decimation(unsigned int channel, unsigned int log2_decim) = 0; + virtual unsigned int getLog2Decimation(unsigned int channel) const = 0; + virtual void setFcPos(unsigned int channel, int fcPos) = 0; + virtual int getFcPos(unsigned int channel) const = 0; virtual void setFifo(unsigned int channel, SampleSinkFifo *fifo) = 0; virtual SampleSinkFifo *getFifo(unsigned int channel) = 0; }; @@ -41,9 +47,11 @@ public: class OutputThreadInterface { public: + virtual ~OutputThreadInterface() = 0; virtual void startWork() = 0; virtual void stopWork() = 0; virtual bool isRunning() = 0; + virtual unsigned int getNbChannels() const = 0; virtual void setFifo(unsigned int channel, SampleSourceFifo *fifo) = 0; virtual SampleSourceFifo *getFifo(unsigned int channel) = 0; }; diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index ebbee96a3..d14df5a8d 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -30,6 +30,7 @@ #include "bladerf2/devicebladerf2shared.h" #include "bladerf2/devicebladerf2.h" +#include "bladerf2inputthread.h" #include "bladerf2input.h" @@ -218,3 +219,383 @@ void BladeRF2Input::init() applySettings(m_settings, true); } +bool BladeRF2Input::start() +{ + if (!m_deviceShared.m_dev) + { + qDebug("BladerfInput::start: no device object"); + return false; + } + + Bladerf2InputThread *bladerf2InputThread = 0; + bool needsStart = false; + + // find thread allocated by a buddy + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + bladerf2InputThread = (Bladerf2InputThread*) ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_inputThread; + + if (bladerf2InputThread) { + break; + } + } + + if (bladerf2InputThread) // if thread was allocated by a buddy + { + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sourceBuddy->getBuddySharedPtr(); + bladerf2InputThread = (Bladerf2InputThread*) deviceBladeRF2Shared->m_inputThread; + int nbOriginalChannels = bladerf2InputThread->getNbChannels(); + + if (m_deviceShared.m_channel+1 > nbOriginalChannels) // expansion + { + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels]; + int *fcPoss = new int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) { // save original FIFO references and data + fifos[i] = bladerf2InputThread->getFifo(i); + log2Decims[i] = bladerf2InputThread->getLog2Decimation(i); + fcPoss[i] = bladerf2InputThread->getFcPos(i); + } + + bladerf2InputThread->stopWork(); + delete bladerf2InputThread; + bladerf2InputThread = new Bladerf2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); + + for (int i = 0; i < nbOriginalChannels; i++) { // restore original FIFO references + bladerf2InputThread->setFifo(i, fifos[i]); + bladerf2InputThread->setLog2Decimation(i, log2Decims[i]); + bladerf2InputThread->setFcPos(i, fcPoss[i]); + } + + // propagate new thread address to buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_inputThread = bladerf2InputThread; + } + + needsStart = true; + } + } + else // first allocation + { + bladerf2InputThread = new Bladerf2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); + needsStart = true; + } + + bladerf2InputThread->setFifo(m_deviceShared.m_channel, &m_sampleFifo); + bladerf2InputThread->setLog2Decimation(m_deviceShared.m_channel, m_settings.m_log2Decim); + bladerf2InputThread->setFcPos(m_deviceShared.m_channel, (int) m_settings.m_fcPos); + m_deviceShared.m_inputThread = bladerf2InputThread; + + if (needsStart) { + bladerf2InputThread->startWork(); + } + + applySettings(m_settings, true); + + qDebug("BladerfInput::startInput: started"); + m_running = true; + + return true; +} + +void BladeRF2Input::stop() +{ + if (!m_running) { + return; + } + + int nbOriginalChannels = m_deviceShared.m_inputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SI mode + { + m_deviceShared.m_inputThread->stopWork(); + delete m_deviceShared.m_inputThread; + m_deviceShared.m_inputThread = 0; + m_running = false; + } + else if (m_deviceShared.m_channel == nbOriginalChannels - 1) // remove last MI channel => reduce + { + m_deviceShared.m_inputThread->stopWork(); + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; + int *fcPoss = new int[nbOriginalChannels-1]; + + for (int i = 0; i < nbOriginalChannels-1; i++) { // save original FIFO references + fifos[i] = m_deviceShared.m_inputThread->getFifo(i); + log2Decims[i] = m_deviceShared.m_inputThread->getLog2Decimation(i); + fcPoss[i] = m_deviceShared.m_inputThread->getFcPos(i); + } + + delete m_deviceShared.m_inputThread; + m_deviceShared.m_inputThread = new Bladerf2InputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); + + for (int i = 0; i < nbOriginalChannels-1; i++) { // restore original FIFO references + m_deviceShared.m_inputThread->setFifo(i, fifos[i]); + m_deviceShared.m_inputThread->setLog2Decimation(i, log2Decims[i]); + m_deviceShared.m_inputThread->setFcPos(i, fcPoss[i]); + } + + // propagate new thread address to buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_inputThread = m_deviceShared.m_inputThread; + } + + m_deviceShared.m_inputThread->startWork(); + } + else + { + m_deviceShared.m_inputThread->setFifo(m_deviceShared.m_channel, 0); // remove FIFO + } +} + +QByteArray BladeRF2Input::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2Input::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureBladeRF2* message = MsgConfigureBladeRF2::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureBladeRF2* messageToGUI = MsgConfigureBladeRF2::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& BladeRF2Input::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int BladeRF2Input::getSampleRate() const +{ + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } +} + +bool BladeRF2Input::handleMessage(const Message& message) +{ + if (MsgConfigureBladeRF2::match(message)) + { + MsgConfigureBladeRF2& conf = (MsgConfigureBladeRF2&) message; + qDebug() << "BladeRF2Input::handleMessage: MsgConfigureBladeRF2"; + + if (!applySettings(conf.getSettings(), conf.getForce())) + { + qDebug("BladeRF2Input::handleMessage: MsgConfigureBladeRF2 config error"); + } + + return true; + } + else if (MsgFileRecord::match(message)) + { + MsgFileRecord& conf = (MsgFileRecord&) message; + qDebug() << "BladeRF2Input::handleMessage: MsgFileRecord: " << conf.getStartStop(); + + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + + m_fileSink->startRecording(); + } + else + { + m_fileSink->stopRecording(); + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "BladeRF2Input::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initAcquisition()) + { + m_deviceAPI->startAcquisition(); + } + } + else + { + m_deviceAPI->stopAcquisition(); + } + + return true; + } + else + { + return false; + } +} + +bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool force) +{ + bool forwardChange = false; + + struct bladerf *dev = m_deviceShared.m_dev->getDev(); + qDebug() << "BladeRF2Input::applySettings: m_dev: " << dev; + + if ((m_settings.m_dcBlock != settings.m_dcBlock) || + (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); + } + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + forwardChange = true; + + if (dev != 0) + { + unsigned int actualSamplerate; + int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), settings.m_devSampleRate, &actualSamplerate); + + if (status < 0) + { + qCritical("BladeRF2Input::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2Input::applySettings: bladerf_set_sample_rate(BLADERF_MODULE_RX) actual sample rate is " << actualSamplerate; + } + } + } + + if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) + { + if (dev != 0) + { + unsigned int actualBandwidth; + int status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), settings.m_bandwidth, &actualBandwidth); + + if(status < 0) + { + qCritical("BladeRF2Input::applySettings: could not set bandwidth: %d: %s", + settings.m_bandwidth, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2Input::applySettings: bladerf_set_bandwidth(BLADERF_MODULE_RX) actual bandwidth is " << actualBandwidth; + } + } + } + + if ((m_settings.m_fcPos != settings.m_fcPos) || force) + { + if (m_deviceShared.m_inputThread != 0) + { + m_deviceShared.m_inputThread->setFcPos(m_deviceShared.m_channel, (int) settings.m_fcPos); + qDebug() << "BladeRF2Input::applySettings: set fc pos (enum) to " << (int) settings.m_fcPos; + } + } + + if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + forwardChange = true; + + if (m_deviceShared.m_inputThread != 0) + { + m_deviceShared.m_inputThread->setLog2Decimation(m_deviceShared.m_channel, settings.m_log2Decim); + qDebug() << "BladeRF2Input::applySettings: set decimation to " << (1<handleMessage(*notif); // forward to file sink + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } + + m_settings = settings; + + qDebug() << "BladeRF2Input::applySettings: " + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_bandwidth: " << m_settings.m_bandwidth + << " m_log2Decim: " << m_settings.m_log2Decim + << " m_fcPos: " << m_settings.m_fcPos + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_dcBlock: " << m_settings.m_dcBlock + << " m_iqCorrection: " << m_settings.m_iqCorrection; + + return true; +} diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp index c84e02fc3..048576800 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -63,11 +63,11 @@ void Bladerf2InputThread::run() unsigned int nbFifos = getNbFifos(); - if (nbFifos > 0) + if ((m_nbChannels > 0) && (nbFifos > 0)) { int status; - if (nbFifos > 1) { + if (m_nbChannels > 1) { status = bladerf_sync_config(m_dev, BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); } else { status = bladerf_sync_config(m_dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); @@ -99,7 +99,7 @@ void Bladerf2InputThread::run() } else { - qWarning("Bladerf2InputThread::run: no sample FIFOs registered. Aborting"); + qWarning("Bladerf2InputThread::run: no channels or FIFO allocated. Aborting"); } @@ -110,7 +110,7 @@ unsigned int Bladerf2InputThread::getNbFifos() { unsigned int fifoCount = 0; - for (int i = 0; i < m_nbChannels; i++) + for (unsigned int i = 0; i < m_nbChannels; i++) { if (m_channels[i].m_sampleFifo) { fifoCount++; @@ -122,28 +122,46 @@ unsigned int Bladerf2InputThread::getNbFifos() void Bladerf2InputThread::setLog2Decimation(unsigned int channel, unsigned int log2_decim) { - if ((channel >= 0) && (channel < m_nbChannels)) { + if (channel < m_nbChannels) { m_channels[channel].m_log2Decim = log2_decim; } } +unsigned int Bladerf2InputThread::getLog2Decimation(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_log2Decim; + } else { + return 0; + } +} + void Bladerf2InputThread::setFcPos(unsigned int channel, int fcPos) { - if ((channel >= 0) && (channel < m_nbChannels)) { + if (channel < m_nbChannels) { m_channels[channel].m_fcPos = fcPos; } } +int Bladerf2InputThread::getFcPos(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_fcPos; + } else { + return 0; + } +} + void Bladerf2InputThread::setFifo(unsigned int channel, SampleSinkFifo *sampleFifo) { - if ((channel >= 0) && (channel < m_nbChannels)) { + if (channel < m_nbChannels) { m_channels[channel].m_sampleFifo = sampleFifo; } } SampleSinkFifo *Bladerf2InputThread::getFifo(unsigned int channel) { - if ((channel >= 0) && (channel < m_nbChannels)) { + if (channel < m_nbChannels) { return m_channels[channel].m_sampleFifo; } else { return 0; diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.h b/plugins/samplesource/bladerf2input/bladerf2inputthread.h index 23c929f0b..ba2718a93 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.h @@ -42,8 +42,11 @@ public: virtual void startWork(); virtual void stopWork(); virtual bool isRunning() const { return m_running; } - void setLog2Decimation(unsigned int channel, unsigned int log2_decim); - void setFcPos(unsigned int channel, int fcPos); + virtual unsigned int getNbChannels() const { return m_nbChannels; } + virtual void setLog2Decimation(unsigned int channel, unsigned int log2_decim); + virtual unsigned int getLog2Decimation(unsigned int channel) const; + virtual void setFcPos(unsigned int channel, int fcPos); + virtual int getFcPos(unsigned int channel) const; virtual void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); virtual SampleSinkFifo *getFifo(unsigned int channel); From df505fcdef31ef893a3d55f73733cd05a351a6b9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 24 Sep 2018 08:43:16 +0200 Subject: [PATCH 787/956] BladerRF2 input support (4) --- devices/bladerf2/devicebladerf2shared.cpp | 2 + devices/bladerf2/devicebladerf2shared.h | 30 +++++++ .../bladerf2input/bladerf2input.cpp | 85 +++++++++++++++++-- .../limesdrinput/limesdrinput.cpp | 18 ++-- 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp index 8d8c00eea..5068f816e 100644 --- a/devices/bladerf2/devicebladerf2shared.cpp +++ b/devices/bladerf2/devicebladerf2shared.cpp @@ -16,6 +16,8 @@ #include "devicebladerf2shared.h" +MESSAGE_CLASS_DEFINITION(DeviceBladeRF2Shared::MsgReportBuddyChange, Message) + DeviceBladeRF2Shared::DeviceBladeRF2Shared() : m_dev(0), m_channel(-1), diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index c72524a26..e8c5761a2 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -17,6 +17,7 @@ #ifndef DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ #define DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ +#include "util/message.h" #include "devicebladerf2.h" class SampleSinkFifo; @@ -56,6 +57,35 @@ public: virtual SampleSourceFifo *getFifo(unsigned int channel) = 0; }; + class MsgReportBuddyChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getBiasTee() const { return m_biasTee; } + bool getRxElseTx() const { return m_rxElseTx; } + + static MsgReportBuddyChange* create( + bool biasTee, + bool rxElseTx) + { + return new MsgReportBuddyChange( + biasTee, + rxElseTx); + } + + private: + bool m_biasTee; + bool m_rxElseTx; //!< tells which side initiated the message + + MsgReportBuddyChange( + bool biasTee, + bool rxElseTx) : + Message(), + m_biasTee(biasTee), + m_rxElseTx(rxElseTx) + { } + }; + DeviceBladeRF2Shared(); ~DeviceBladeRF2Shared(); diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index d14df5a8d..3669ac4da 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -431,6 +431,24 @@ bool BladeRF2Input::handleMessage(const Message& message) return true; } + else if (DeviceBladeRF2Shared::MsgReportBuddyChange::match(message)) + { + DeviceBladeRF2Shared::MsgReportBuddyChange& report = (DeviceBladeRF2Shared::MsgReportBuddyChange&) message; + + if (report.getRxElseTx()) + { + m_settings.m_biasTee = report.getBiasTee(); + } + + if (getMessageQueueToGUI()) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *reportToGUI = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + m_settings.m_biasTee, true); + getMessageQueueToGUI()->push(reportToGUI); + } + + return true; + } else if (MsgFileRecord::match(message)) { MsgFileRecord& conf = (MsgFileRecord&) message; @@ -480,7 +498,9 @@ bool BladeRF2Input::handleMessage(const Message& message) bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool force) { - bool forwardChange = false; + bool forwardChangeOwnDSP = false; + bool forwardChangeRxDSP = false; + bool forwardChangeAllDSP __attribute__((unused)) = false; struct bladerf *dev = m_deviceShared.m_dev->getDev(); qDebug() << "BladeRF2Input::applySettings: m_dev: " << dev; @@ -493,7 +513,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) { - forwardChange = true; + forwardChangeOwnDSP = true; if (dev != 0) { @@ -542,7 +562,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) { - forwardChange = true; + forwardChangeOwnDSP = true; if (m_deviceShared.m_inputThread != 0) { @@ -563,7 +583,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo (DeviceSampleSource::fcPos_t) settings.m_fcPos, settings.m_devSampleRate); - forwardChange = true; + forwardChangeOwnDSP = true; if (dev != 0) { @@ -578,7 +598,43 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo } } - if (forwardChange) + if ((m_settings.m_biasTee != settings.m_biasTee) || force) + { + m_deviceShared.m_dev->setBiasTeeRx(settings.m_biasTee); + forwardChangeRxDSP = true; + } + + if ((m_settings.m_globalGain != settings.m_globalGain) || force) + { + if (dev) + { + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), settings.m_globalGain); + + if (status < 0) { + qWarning("BladeRF2Input::applySettings: bladerf_set_gain(%d) failed: %s", + settings.m_globalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2Input::applySettings: bladerf_set_gain(%d)", settings.m_globalGain); + } + } + } + + if ((m_settings.m_gainMode != settings.m_gainMode) || force) + { + if (dev) + { + int status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), (bladerf_gain_mode) settings.m_gainMode); + + if (status < 0) { + qWarning("BladeRF2Input::applySettings: bladerf_set_gain_mode(%d) failed: %s", + settings.m_gainMode, bladerf_strerror(status)); + } else { + qDebug("BladeRF2Input::applySettings: bladerf_set_gain_mode(%d)", settings.m_gainMode); + } + } + } + + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<getDeviceEngineInputMessageQueue()->push(notif); } + if (forwardChangeRxDSP) + { + // send to source buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator itSource = sourceBuddies.begin(); + + for (; itSource != sourceBuddies.end(); ++itSource) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_biasTee, true); + (*itSource)->getSampleSourceInputMessageQueue()->push(report); + } + } + m_settings = settings; qDebug() << "BladeRF2Input::applySettings: " @@ -594,8 +664,11 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo << " m_log2Decim: " << m_settings.m_log2Decim << " m_fcPos: " << m_settings.m_fcPos << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_globalGain: " << m_settings.m_globalGain + << " m_gainMode: " << m_settings.m_gainMode << " m_dcBlock: " << m_settings.m_dcBlock - << " m_iqCorrection: " << m_settings.m_iqCorrection; + << " m_iqCorrection: " << m_settings.m_iqCorrection + << " m_biasTee: " << m_settings.m_biasTee; return true; } diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index f623e31e0..45add7cdd 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -590,9 +590,12 @@ bool LimeSDRInput::handleMessage(const Message& message) m_settings.m_centerFrequency + ncoShift); m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); - DeviceLimeSDRShared::MsgReportBuddyChange *reportToGUI = DeviceLimeSDRShared::MsgReportBuddyChange::create( - m_settings.m_devSampleRate, m_settings.m_log2HardDecim, m_settings.m_centerFrequency, true); - getMessageQueueToGUI()->push(reportToGUI); + if (getMessageQueueToGUI()) + { + DeviceLimeSDRShared::MsgReportBuddyChange *reportToGUI = DeviceLimeSDRShared::MsgReportBuddyChange::create( + m_settings.m_devSampleRate, m_settings.m_log2HardDecim, m_settings.m_centerFrequency, true); + getMessageQueueToGUI()->push(reportToGUI); + } return true; } @@ -603,9 +606,12 @@ bool LimeSDRInput::handleMessage(const Message& message) m_settings.m_extClock = report.getExtClock(); m_settings.m_extClockFreq = report.getExtClockFeq(); - DeviceLimeSDRShared::MsgReportClockSourceChange *reportToGUI = DeviceLimeSDRShared::MsgReportClockSourceChange::create( - m_settings.m_extClock, m_settings.m_extClockFreq); - getMessageQueueToGUI()->push(reportToGUI); + if (getMessageQueueToGUI()) + { + DeviceLimeSDRShared::MsgReportClockSourceChange *reportToGUI = DeviceLimeSDRShared::MsgReportClockSourceChange::create( + m_settings.m_extClock, m_settings.m_extClockFreq); + getMessageQueueToGUI()->push(reportToGUI); + } return true; } From 7dfe094364d510d4f3f01e9e6de2344b795de9fb Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 24 Sep 2018 17:32:40 +0200 Subject: [PATCH 788/956] BladerRF2 input support (5). Global notification of changes to buddies --- devices/bladerf2/devicebladerf2shared.h | 17 +-- .../bladerf2input/bladerf2input.cpp | 129 ++++++++++++++++-- 2 files changed, 119 insertions(+), 27 deletions(-) diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index e8c5761a2..6e132887a 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -61,27 +61,18 @@ public: MESSAGE_CLASS_DECLARATION public: - bool getBiasTee() const { return m_biasTee; } bool getRxElseTx() const { return m_rxElseTx; } - static MsgReportBuddyChange* create( - bool biasTee, - bool rxElseTx) + static MsgReportBuddyChange* create(bool rxElseTx) { - return new MsgReportBuddyChange( - biasTee, - rxElseTx); + return new MsgReportBuddyChange(rxElseTx); } private: - bool m_biasTee; - bool m_rxElseTx; //!< tells which side initiated the message + bool m_rxElseTx; //!< tells which side initiated the message - MsgReportBuddyChange( - bool biasTee, - bool rxElseTx) : + MsgReportBuddyChange(bool rxElseTx) : Message(), - m_biasTee(biasTee), m_rxElseTx(rxElseTx) { } }; diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 3669ac4da..09acee900 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -434,17 +434,97 @@ bool BladeRF2Input::handleMessage(const Message& message) else if (DeviceBladeRF2Shared::MsgReportBuddyChange::match(message)) { DeviceBladeRF2Shared::MsgReportBuddyChange& report = (DeviceBladeRF2Shared::MsgReportBuddyChange&) message; + struct bladerf *dev = m_deviceShared.m_dev->getDev(); + BladeRF2InputSettings settings = m_settings; + int status; + int tmp_int; + unsigned int tmp_uint; + uint64_t tmp_uint64; + bool tmp_bool; + bladerf_gain_mode tmp_gain_mode; - if (report.getRxElseTx()) - { - m_settings.m_biasTee = report.getBiasTee(); - } + // evaluate changes that may have been introduced by changes in a buddy - if (getMessageQueueToGUI()) + if (dev) // The BladeRF device must have been open to do so { - DeviceBladeRF2Shared::MsgReportBuddyChange *reportToGUI = DeviceBladeRF2Shared::MsgReportBuddyChange::create( - m_settings.m_biasTee, true); - getMessageQueueToGUI()->push(reportToGUI); + if (report.getRxElseTx()) // Rx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth + { + status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_uint); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); + } else { + settings.m_devSampleRate = tmp_uint; + } + + status = bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_uint64); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_frequency error: %s", bladerf_strerror(status)); + } else { + settings.m_centerFrequency = tmp_uint64; + } + + status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_uint); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_bandwidth error: %s", bladerf_strerror(status)); + } else { + settings.m_bandwidth = tmp_uint; + } + + status = bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_gain_mode); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_gain_mode error: %s", bladerf_strerror(status)); + } else { + settings.m_gainMode = (int) tmp_gain_mode; + } + + status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_int); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_gain error: %s", bladerf_strerror(status)); + } else { + settings.m_globalGain = tmp_int; + } + + status = bladerf_get_bias_tee(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_bool); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_bias_tee error: %s", bladerf_strerror(status)); + } else { + settings.m_biasTee = tmp_bool; + } + } + else // Tx buddy change: check for sample rate change only + { + status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_uint); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); + } else { + settings.m_devSampleRate = tmp_uint; + } + } + + // change DSP settings if buddy change introduced a change in center frequency or base rate + if ((settings.m_centerFrequency != m_settings.m_centerFrequency) || (settings.m_devSampleRate != m_settings.m_devSampleRate)) + { + int sampleRate = settings.m_devSampleRate/(1<handleMessage(*notif); // forward to file sink + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } + + m_settings = settings; // acknowledge the new settings + + // propagate settings to GUI if any + if (getMessageQueueToGUI()) + { + MsgConfigureBladeRF2 *reportToGUI = MsgConfigureBladeRF2::create(m_settings, false); + getMessageQueueToGUI()->push(reportToGUI); + } } return true; @@ -499,8 +579,8 @@ bool BladeRF2Input::handleMessage(const Message& message) bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool force) { bool forwardChangeOwnDSP = false; - bool forwardChangeRxDSP = false; - bool forwardChangeAllDSP __attribute__((unused)) = false; + bool forwardChangeRxBuddies = false; + bool forwardChangeTxBuddies = false; struct bladerf *dev = m_deviceShared.m_dev->getDev(); qDebug() << "BladeRF2Input::applySettings: m_dev: " << dev; @@ -514,6 +594,8 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) { forwardChangeOwnDSP = true; + forwardChangeRxBuddies = true; + forwardChangeTxBuddies = true; if (dev != 0) { @@ -534,6 +616,8 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) { + forwardChangeRxBuddies = true; + if (dev != 0) { unsigned int actualBandwidth; @@ -584,6 +668,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo settings.m_devSampleRate); forwardChangeOwnDSP = true; + forwardChangeRxBuddies = true; if (dev != 0) { @@ -600,12 +685,14 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if ((m_settings.m_biasTee != settings.m_biasTee) || force) { + forwardChangeRxBuddies = true; m_deviceShared.m_dev->setBiasTeeRx(settings.m_biasTee); - forwardChangeRxDSP = true; } if ((m_settings.m_globalGain != settings.m_globalGain) || force) { + forwardChangeRxBuddies = true; + if (dev) { int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), settings.m_globalGain); @@ -621,6 +708,8 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if ((m_settings.m_gainMode != settings.m_gainMode) || force) { + forwardChangeRxBuddies = true; + if (dev) { int status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), (bladerf_gain_mode) settings.m_gainMode); @@ -642,7 +731,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); } - if (forwardChangeRxDSP) + if (forwardChangeRxBuddies) { // send to source buddies const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); @@ -650,12 +739,24 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo for (; itSource != sourceBuddies.end(); ++itSource) { - DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( - settings.m_biasTee, true); + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create(true); (*itSource)->getSampleSourceInputMessageQueue()->push(report); } } + if (forwardChangeTxBuddies) + { + // send to sink buddies + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator itSink = sinkBuddies.begin(); + + for (; itSink != sinkBuddies.end(); ++itSink) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create(true); + (*itSink)->getSampleSinkInputMessageQueue()->push(report); + } + } + m_settings = settings; qDebug() << "BladeRF2Input::applySettings: " From cdeb6e6c42e527cdc28f01b92a9b59e0b0d26d37 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 24 Sep 2018 17:59:52 +0200 Subject: [PATCH 789/956] BladerRF2 input support (6). REST API --- .../bladerf2input/bladerf2input.cpp | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 09acee900..1b9709471 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -773,3 +773,161 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo return true; } + +int BladeRF2Input::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf2InputSettings(new SWGSDRangel::SWGBladeRF2InputSettings()); + response.getBladeRf2InputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int BladeRF2Input::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + BladeRF2InputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getBladeRf2InputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getBladeRf2InputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getBladeRf2InputSettings()->getBandwidth(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getBladeRf2InputSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) { + settings.m_fcPos = static_cast(response.getBladeRf2InputSettings()->getFcPos()); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getBladeRf2InputSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getBladeRf2InputSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("biasTee")) { + settings.m_biasTee = response.getBladeRf2InputSettings()->getBiasTee() != 0; + } + if (deviceSettingsKeys.contains("gainMode")) { + settings.m_gainMode = response.getBladeRf2InputSettings()->getGainMode(); + } + if (deviceSettingsKeys.contains("globalGain")) { + settings.m_globalGain = response.getBladeRf2InputSettings()->getGlobalGain(); + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getBladeRf1InputSettings()->getFileRecordName(); + } + + MsgConfigureBladeRF2 *msg = MsgConfigureBladeRF2::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBladeRF2 *msgToGUI = MsgConfigureBladeRF2::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int BladeRF2Input::webapiReportGet(SWGSDRangel::SWGDeviceReport& response, QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf2InputReport(new SWGSDRangel::SWGBladeRF2InputReport()); + response.getBladeRf2InputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void BladeRF2Input::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings) +{ + response.getBladeRf2InputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf2InputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf2InputSettings()->setBandwidth(settings.m_bandwidth); + response.getBladeRf2InputSettings()->setLog2Decim(settings.m_log2Decim); + response.getBladeRf2InputSettings()->setFcPos((int) settings.m_fcPos); + response.getBladeRf2InputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getBladeRf2InputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getBladeRf2InputSettings()->setBiasTee(settings.m_biasTee ? 1 : 0); + response.getBladeRf2InputSettings()->setGainMode(settings.m_gainMode); + response.getBladeRf2InputSettings()->setGlobalGain(settings.m_globalGain); + + if (response.getBladeRf2InputSettings()->getFileRecordName()) { + *response.getBladeRf2InputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getBladeRf2InputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +void BladeRF2Input::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + DeviceBladeRF2 *device = m_deviceShared.m_dev; + + if (device) + { + int min, max, step; + uint64_t f_min, f_max; + + device->getBandwidthRangeRx(min, max, step); + + response.getBladeRf2InputReport()->setBandwidthRange(new SWGSDRangel::SWGRange); + response.getBladeRf2InputReport()->getBandwidthRange()->setMin(min); + response.getBladeRf2InputReport()->getBandwidthRange()->setMax(max); + response.getBladeRf2InputReport()->getBandwidthRange()->setStep(step); + + device->getFrequencyRangeRx(f_min, f_max, step); + + response.getBladeRf2InputReport()->setFrequencyRange(new SWGSDRangel::SWGRange); + response.getBladeRf2InputReport()->getFrequencyRange()->setMin(f_min); + response.getBladeRf2InputReport()->getFrequencyRange()->setMax(f_max); + response.getBladeRf2InputReport()->getFrequencyRange()->setStep(step); + + device->getGlobalGainRangeRx(min, max, step); + + response.getBladeRf2InputReport()->setGlobalGainRange(new SWGSDRangel::SWGRange); + response.getBladeRf2InputReport()->getGlobalGainRange()->setMin(min); + response.getBladeRf2InputReport()->getGlobalGainRange()->setMax(max); + response.getBladeRf2InputReport()->getGlobalGainRange()->setStep(step); + + device->getSampleRateRangeRx(min, max, step); + + response.getBladeRf2InputReport()->setSampleRateRange(new SWGSDRangel::SWGRange); + response.getBladeRf2InputReport()->getSampleRateRange()->setMin(min); + response.getBladeRf2InputReport()->getSampleRateRange()->setMax(max); + response.getBladeRf2InputReport()->getSampleRateRange()->setStep(step); + } +} + +int BladeRF2Input::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int BladeRF2Input::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + m_guiMessageQueue->push(msgToGUI); + } + + return 200; +} \ No newline at end of file From b20feec1fd2c40db0b7f4f3d81c14d811c6a7d5a Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 25 Sep 2018 00:38:38 +0200 Subject: [PATCH 790/956] BladerRF2 input support (7). GUI and Plugin --- devices/bladerf2/devicebladerf2.cpp | 9 + .../bladerf1input/bladerf1inputgui.cpp | 6 +- .../samplesource/bladerf2input/CMakeLists.txt | 8 +- .../bladerf2input/bladerf2input.cpp | 30 +- .../bladerf2input/bladerf2input.h | 5 + .../bladerf2input/bladerf2inputgui.cpp | 333 +++++++++++++++ .../bladerf2input/bladerf2inputgui.h | 93 +++++ .../bladerf2input/bladerf2inputgui.ui | 382 +++++------------- .../bladerf2input/bladerf2inputplugin.cpp | 6 +- udev-rules/88-nuand.rules | 3 + 10 files changed, 573 insertions(+), 302 deletions(-) create mode 100644 plugins/samplesource/bladerf2input/bladerf2inputgui.cpp create mode 100644 plugins/samplesource/bladerf2input/bladerf2inputgui.h diff --git a/devices/bladerf2/devicebladerf2.cpp b/devices/bladerf2/devicebladerf2.cpp index 7b7e4a772..63d7c4c8c 100644 --- a/devices/bladerf2/devicebladerf2.cpp +++ b/devices/bladerf2/devicebladerf2.cpp @@ -79,6 +79,15 @@ bool DeviceBladeRF2::open(const char *serial) return true; } +void DeviceBladeRF2::close() +{ + if (m_dev) + { + bladerf_close(m_dev); + m_dev = 0; + } +} + struct bladerf *DeviceBladeRF2::open_bladerf_from_serial(const char *serial) { int status; diff --git a/plugins/samplesource/bladerf1input/bladerf1inputgui.cpp b/plugins/samplesource/bladerf1input/bladerf1inputgui.cpp index 220b3d33c..3865fedce 100644 --- a/plugins/samplesource/bladerf1input/bladerf1inputgui.cpp +++ b/plugins/samplesource/bladerf1input/bladerf1inputgui.cpp @@ -14,8 +14,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "bladerf1inputgui.h" - #include #include @@ -26,9 +24,11 @@ #include "gui/glspectrum.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" -#include +#include "device/devicesourceapi.h" #include "device/deviceuiset.h" +#include "bladerf1inputgui.h" + Bladerf1InputGui::Bladerf1InputGui(DeviceUISet *deviceUISet, QWidget* parent) : QWidget(parent), ui(new Ui::Bladerf1InputGui), diff --git a/plugins/samplesource/bladerf2input/CMakeLists.txt b/plugins/samplesource/bladerf2input/CMakeLists.txt index 6594e1cff..75d330454 100644 --- a/plugins/samplesource/bladerf2input/CMakeLists.txt +++ b/plugins/samplesource/bladerf2input/CMakeLists.txt @@ -3,17 +3,17 @@ project(bladerf2input) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(bladerf2input_SOURCES - #bladerf2inputgui.cpp + bladerf2inputgui.cpp bladerf2input.cpp - #bladerf2inputplugin.cpp + bladerf2inputplugin.cpp bladerf2inputsettings.cpp bladerf2inputthread.cpp ) set(bladerf2input_HEADERS - #bladerf2inputgui.h + bladerf2inputgui.h bladerf2input.h - #bladerf2inputplugin.h + bladerf2inputplugin.h bladerf2inputsettings.h bladerf2inputthread.h ) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 1b9709471..296fe3e44 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -417,6 +417,34 @@ void BladeRF2Input::setCenterFrequency(qint64 centerFrequency) } } +void BladeRF2Input::getFrequencyRange(uint64_t& min, uint64_t& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getFrequencyRangeRx(min, max, step); + } +} + +void BladeRF2Input::getSampleRateRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getSampleRateRangeRx(min, max, step); + } +} + +void BladeRF2Input::getBandwidthRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getBandwidthRangeRx(min, max, step); + } +} + +void BladeRF2Input::getGlobalGainRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getGlobalGainRangeRx(min, max, step); + } +} + bool BladeRF2Input::handleMessage(const Message& message) { if (MsgConfigureBladeRF2::match(message)) @@ -930,4 +958,4 @@ int BladeRF2Input::webapiRun( } return 200; -} \ No newline at end of file +} diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h index 5798caf25..8966c378d 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.h +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -110,6 +110,11 @@ public: virtual quint64 getCenterFrequency() const; virtual void setCenterFrequency(qint64 centerFrequency); + void getFrequencyRange(uint64_t& min, uint64_t& max, int& step); + void getSampleRateRange(int& min, int& max, int& step); + void getBandwidthRange(int& min, int& max, int& step); + void getGlobalGainRange(int& min, int& max, int& step); + virtual bool handleMessage(const Message& message); virtual int webapiSettingsGet( diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp new file mode 100644 index 000000000..3fb1020e9 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -0,0 +1,333 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "ui_bladerf2inputgui.h" +#include "gui/colormapper.h" +#include "gui/glspectrum.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/devicesourceapi.h" +#include "device/deviceuiset.h" + +#include "bladerf2inputgui.h" + +BladeRF2InputGui::BladeRF2InputGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::Bladerf2InputGui), + m_deviceUISet(deviceUISet), + m_forceSettings(true), + m_doApplySettings(true), + m_settings(), + m_sampleSource(0), + m_sampleRate(0), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) +{ + m_sampleSource = (BladeRF2Input*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); + int max, min, step; + uint64_t f_min, f_max; + + ui->setupUi(this); + + m_sampleSource->getFrequencyRange(f_min, f_max, step); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + + m_sampleSource->getSampleRateRange(min, max, step); + ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); + ui->sampleRate->setValueRange(8, min, max); + + m_sampleSource->getBandwidthRange(min, max, step); + ui->bandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); + ui->bandwidth->setValueRange(6, min/1000, max/1000); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); + + sendSettings(); +} + +BladeRF2InputGui::~BladeRF2InputGui() +{ + delete ui; +} + +void BladeRF2InputGui::destroy() +{ + delete this; +} + +void BladeRF2InputGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString BladeRF2InputGui::getName() const +{ + return objectName(); +} + +void BladeRF2InputGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 BladeRF2InputGui::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +void BladeRF2InputGui::setCenterFrequency(qint64 centerFrequency) +{ + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray BladeRF2InputGui::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2InputGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool BladeRF2InputGui::handleMessage(const Message& message) +{ + if (BladeRF2Input::MsgConfigureBladeRF2::match(message)) + { + const BladeRF2Input::MsgConfigureBladeRF2& cfg = (BladeRF2Input::MsgConfigureBladeRF2&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (BladeRF2Input::MsgStartStop::match(message)) + { + BladeRF2Input::MsgStartStop& notif = (BladeRF2Input::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void BladeRF2InputGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("BladeRF2InputGui::handleInputMessages: message: %s", message->getIdentifier()); + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("BladeRF2InputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void BladeRF2InputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateLabel->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); +} + +void BladeRF2InputGui::displaySettings() +{ + blockApplySettings(true); + + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + ui->sampleRate->setValue(m_settings.m_devSampleRate); + ui->bandwidth->setValue(m_settings.m_bandwidth / 1000); + + ui->dcOffset->setChecked(m_settings.m_dcBlock); + ui->iqImbalance->setChecked(m_settings.m_iqCorrection); + + ui->decim->setCurrentIndex(m_settings.m_log2Decim); + ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); + + ui->gain->setValue(m_settings.m_globalGain); + + blockApplySettings(false); +} + +void BladeRF2InputGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +void BladeRF2InputGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void BladeRF2InputGui::on_sampleRate_changed(quint64 value) +{ + m_settings.m_devSampleRate = value; + sendSettings(); +} + +void BladeRF2InputGui::on_dcOffset_toggled(bool checked) +{ + m_settings.m_dcBlock = checked; + sendSettings(); +} + +void BladeRF2InputGui::on_iqImbalance_toggled(bool checked) +{ + m_settings.m_iqCorrection = checked; + sendSettings(); +} + +void BladeRF2InputGui::on_bandwidth_changed(quint64 value) +{ + m_settings.m_bandwidth = value * 1000; + sendSettings(); +} + +void BladeRF2InputGui::on_decim_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) + return; + m_settings.m_log2Decim = index; + sendSettings(); +} + +void BladeRF2InputGui::on_fcPos_currentIndexChanged(int index) +{ + if (index == 0) { + m_settings.m_fcPos = BladeRF2InputSettings::FC_POS_INFRA; + sendSettings(); + } else if (index == 1) { + m_settings.m_fcPos = BladeRF2InputSettings::FC_POS_SUPRA; + sendSettings(); + } else if (index == 2) { + m_settings.m_fcPos = BladeRF2InputSettings::FC_POS_CENTER; + sendSettings(); + } +} + +void BladeRF2InputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + BladeRF2Input::MsgStartStop *message = BladeRF2Input::MsgStartStop::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void BladeRF2InputGui::on_record_toggled(bool checked) +{ + if (checked) { + ui->record->setStyleSheet("QToolButton { background-color : red; }"); + } else { + ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + + BladeRF2Input::MsgFileRecord* message = BladeRF2Input::MsgFileRecord::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); +} + +void BladeRF2InputGui::updateHardware() +{ + if (m_doApplySettings) + { + qDebug() << "BladeRF2InputGui::updateHardware"; + BladeRF2Input::MsgConfigureBladeRF2* message = BladeRF2Input::MsgConfigureBladeRF2::create(m_settings, m_forceSettings); + m_sampleSource->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void BladeRF2InputGui::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void BladeRF2InputGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceSourceAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DSPDeviceSourceEngine::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DSPDeviceSourceEngine::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DSPDeviceSourceEngine::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DSPDeviceSourceEngine::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSourceAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.h b/plugins/samplesource/bladerf2input/bladerf2inputgui.h new file mode 100644 index 000000000..f52c82022 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTGUI_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTGUI_H_ + +#include +#include +#include + +#include "util/messagequeue.h" + +#include "bladerf2input.h" + +class DeviceUISet; + +namespace Ui { + class Bladerf2InputGui; +} + +class BladeRF2InputGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit BladeRF2InputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~BladeRF2InputGui(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::Bladerf2InputGui* ui; + + DeviceUISet* m_deviceUISet; + bool m_forceSettings; + bool m_doApplySettings; + BladeRF2InputSettings m_settings; + QTimer m_updateTimer; + QTimer m_statusTimer; + std::vector m_gains; + BladeRF2Input* m_sampleSource; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + + void displaySettings(); + void sendSettings(); + void updateSampleRateAndFrequency(); + void blockApplySettings(bool block); + +private slots: + void handleInputMessages(); + void on_centerFrequency_changed(quint64 value); + void on_sampleRate_changed(quint64 value); + void on_dcOffset_toggled(bool checked); + void on_iqImbalance_toggled(bool checked); + void on_biasTee_toggled(bool checked); + void on_bandwidth_changed(quint64 value); + void on_decim_currentIndexChanged(int index); + void on_fcPos_currentIndexChanged(int index); + void on_gainMode_currentIndexChanged(int index); + void on_gain_valueChanged(int value); + void on_startStop_toggled(bool checked); + void on_record_toggled(bool checked); + void updateHardware(); + void updateStatus(); + +}; + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTGUI_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui index 527110d64..b39e828d9 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui @@ -7,7 +7,7 @@ 0 0 310 - 265 + 250

    DF2BcP11FB#>eFZ)2Cv2F(V9_vVl$6 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%0DpOB`4qnV#^1<7Ujzss? z)XEZdEZdj-l%WBPKe(v*U;+(H&=3LnCh;D!v7#?=SWfc8&Q+tDjGuF^)1JZ#cbyjF z2AbB36+3KyoznuKAp-%avpb5p8(EKI&au+G2qOf}mFAvC5Rk__kRXJa5^4|hWHKk|quzSQY$dyoc``?{#d-DI})Kc#Z52igv9F2O5SVj7k@xn?P83qca#2fH>xGkaFo+CT*XT#T#Qgi z>l6Iz4*Crz$E!}|CGn^XCz`pLmGoS5%6jDOrdl$~E8mOslvGg@NKx^hrzS8;wZs=S zAal}BFW;v?rcSfqOB;|yCt?lI#OAJU#-=bfb}D!Q%md5829N{xf#aZ%v2os@0SE>n zNCd+`3YZFB0Q10dumR+Nec(7KWNf@QXaIs4OWn=b!$`}+!x{S*(l!CpcoUG$32i|% z=m%goAr(vqGr@eY5@dqi-~jj&SQvYxCTIfMf@shW3;?zGpTL6h zsR^2Zwjdhx1H(Zom=0!w`Cui;1iQfj@F$gJb$_yo%Rt|kB6UUk=Y~V9CH5cbxq2DS zDgWuiYbmAU*-fXIFlMdiYR3rl{`BFs%;PapnIJZd7u;%UP80RFB8SR_no-*C(_Cp# zuhj%``NEY`&6UVr;Q&t{YZ)*<*r;!IOEZi z=zNVy_qhvq5i#W`0ZQ&k;YT3C<=&_tu7F8|c8pV1QGtCR7& zTt6~?r;b&%8Q@W?Qhqua|1mrs{HK^b)`E12Ne~h_m^i0R0X3N+2?8qcqNV>yv<(0v zDfG(L03T-UroAgT=;Jh-!)nJsDXV9bUK zJP8z*7psY_0!AdoRqdy^ff$Sg$nN!OIai5EiCIu_kO6sPK@DceLJol!whA~lWeJK_ z*Kb17z{G;1H%WK^9vDLvV935`wL`g*qb~C~#YFYsw*FHzIZcdn9UN z&q7%>a>(7_w((656-B{Q*b?Q8k3zs5H*Cj(kFw^rsg@2@CKyj~j$h!QhZ2c) zNa&4fy690vnim?!pjgx(-1X%hyy!xYe^s^Z6c_zGwe6y8MgOOMBCGX-7gK-4r$J|f z1>k+)=H>~sXHQ`B*b_a$P%s)y0?&cDU>W!XdN5KWgrWrte5CkGXPcRgW29v;Z zU@ln3*poKKp1KRHX6$L);2EPp8o-G-Gmy3!OTZei4eSMnz!~5`ce)N}0Xl*nU=SDu z(!dNb8!Q29z&5ZK90F&6gE3Pb&;oP>J-{F^3Z#J7 zf*xQH7zNV63@Yym3(wUZ)$)}g?ug5-6T=kdpVZlfitd>3vyO`HsPN=pAHIKx8Z*WeYMMIJSV&C>@XAFV67hk#+#r{4=;GR~ zvA=XWvaw%8%YvFA%ATCpcp556O7~W-{GM}lmSU#d4Q>{qm?`C?8#aM|Z3x8~#tjv2 zi=;s21VeNc+>=z+zdn3Yr~53$6Bf@}WI?Bam@BwPsjPo}_@vfi4uMVsF;{TUQd$4{ z@JX%4+?+;XZe`7B07<(v{f8HlT>42W`vl+0)D&x3P%|VCGMO*m?z^}3<$YIH)O~gQ zuJ784mS=hWUvK4aG~M8XQk?^-&ss%-i?9Q6eF}Vm`=As_P**o7)%kiFl;V3_I^kf7 zYkZ>Q^K(#M^a}!*1*Yy-2rerVRQRS<#+)-KR z=}i|yUPiw@j-={FY`9_m99(pU@Ls>qUg=dALqroO{o!sDB9+;tk~z4cSAzjRNSigc z@)8#0>r^{8v*K;ZwHh>(k4;%-DjJ@s+8h~IRf9?+k-jGtb6(XAqFLM z_?F0bdH!Jw3Vh4y>4C%M$-k+*|ClMg4l?1lnHkJot{Uzidyx!R3HcXD7VV4|88UHA zFO|_i1YGh_aD8;rYsWLVzr~xkS4)R2dvbsK( z_Ty(CgN1tjX{d|G@h-etM;s4%#MpkP{hTFH7GG_i0 z6f(9HSy)=z)y+Gn8GFah*nF7JZw_!4#r*DIAQ%ZA0ndO}!D8?c_zZjveg&t2ov{VA zL373y)&ixBE%FBepgo8I{lN&v7W;urup1lzNZVoyV@qm+CZH{d2K~TrkP4=QnP5Ix z2{OTMZ~*)XER1E;1WiC&5Dof);UE=E2Q$HZuo7f~-QWQD6If6_H9-^57DR)7U^qwx z)4@zIAFKqKU^h5GW%-Z4M=SbXF0X(0_jq~z*ILYia9)g_O9>oKI|#hwHxGVw=yzmX zT&)Vriy4HAKvOf4I0BIhM?4pIIuTMdc-jMZ+7M8yL1m%%i zDz2-$`Pn9_Hm=o)*D(sV$wiW@uMeb7uf2@idc3##`%h|eOUZ3g&bk`N)Z4Dr1GQFg znXopNPF(I;uHfCfZ1~mjU-}+Ym%pnFeZ$L;;ephLsH%?S^N8zWQ|ldJcjYo<`1K=0 zR^1+tWryfy;ir=!mtByJRI_&tq}M+9pltED30pfbeVlZYx}^gL{K=nvfE2}dTF`eJ zFu~2g-%y?_ea*oD>L}NAHb<7RHr&AffVpP7f8WXUFm}j zNI@1-z^`fw_|^zzhXnG^w?-g#`Kt|xT~;7=zGVTiU)7a9&w$uN5IeuBu`3^DKs>Ur ziTq<7zn6p*$R{hpMphsNe8L_nxT-6Ca{(!kkOF?C6kJqpq>-8`d)z55`gxKYA(yGY z3LqIZk3_J^vw$)3)I`9Sf4*Ukr zGxm`HZUQYqI7k3PKr(m?JPTe2OTjwu1^5p92F^3KMgTW~mLMD?fFU3mJO-WxuY;vv z9ryx#2Yv(RQC0%D3A6;^AOQ>k$>1^YER}bK`$CuNE4ka>XnluThgo1!3m3{R0ER&1 zvZ%NjgdX_IfYHo5Os}2I;LfE!$pxyfgMnF!I}Zh@uXBMRvb*wBnrstQ zGuP_J>m&u$&_fpVb%@mIwU3j#GFi~q8B#0m%47j{S{)&^?C#34(2A+mLaQfgt&!!@ z->>;yuJpFJoVBbv{z`ryUT@`p!^^3i0;$hZRdQ-w#6`=JJ#}2$!MrJ;i4c;t(3!l# zdCyMQlgwIGOTgF7MH$ggC$p}5Up7+B)@>yHMV5ejF}rCA)_~iW%te{YE5LC^o!OD0 zXszZYi_Ns=y9i6M&9i}Xxi(_zfG&{3nA(`sea&x!y$aEeLPF_OJ-3}wu zVJ-{35w2&qp2Jnr;9Ppnmx^aBLDtNRk#@?Lui2o_7ik?Pv(bzm%w^VzV_*3ZnkPO; z4!M{KxtG4E;E+#D)O{L)zP?~PhBewYij@nT$T&?9Ba4{$bv3n;sny-|FEahh-Sk5; z{m|Xivs=&MdPoCL?h!4yw4@xR;*vFP!n1#bXCLe7v9LT*y0SwAyjY2^>5%1+33~yQ zjk@zdyU)AOMOsUawkJR48ZoTG>uAOnzYQ_<|iqXY(g=Y z?B*w5@+TXA>4LkB9eIU~f!`~acrNbe;_+x{d)FRZ&V$+|%!r11Tpb2OTqCr0} z9HfHjU?!LkRx+0PGENH_2c7^gfw#d2U=!HM*p{)3ZS@9t&1?+@7_-=#2!;WqZR=F< z0+;uO^A!DC;g9acNh#(OR11VrCcmd1<%fSYa1NMRAppda`-k<>p1|moV z!$1m{3SI#7z;dtwFPd45PBF^nR!Nn1j3idh3@_`f3$0e>1kl z>$v>dabMXVwzB){a8BMr{_{A=EBk|NcI`LA`^dK!vVV-(r~^s6?9zB6A2W!-$tX@P zmLhCS7Wp`JWJOfj2r*?nUyDiJ?+pFa6 zRB8L=p>|ZqUxn|G>#h8arWD|T29TA-!x%E{t0Ewh$IX&wZYN#m$GB{yMGSRV4k!FTg= z^62w`CUTA?$o8G|#XfvNP}UaFYUmGPwu^#>z4P0DdlVCVOwi>Ufyfs=?}sk|;=kg= zu?Jzor>sY|b|FgssQ?NT*}~dr?iAVD2)|tIjjspn+_)PF_)^i+#|h-m7~&&=jr>9k29su zI@IW}O^QRzsykbO`jI{wp3zK;)Q|e@)0q~1$v@kdr4xw}QBND)-Bmhi7{~9$Z7VqY zdRN_MZmiNylc>1mD6l5m=O@Xe>EY$maG$mtq;rjRX82Blb&!@RFJ>9|RmmyL-Kr<= zsU;v^se~ZnHoT~a+X+%%l{IY?75{l^+FGiXjO8?w)p`*(C!Vpd&j2evwgQoSZ+%xc zJ7zJqV-dh9cssTN>;T#E3pfRAjP0xint=`=77PIQg9+eiFbgaKtHD-~2Yz8}&me#k z>Gq@noJhB4Hdq4IfNfweI0ViB2V=Q)Knu_j^Z<{>rE zSJhP?cO;I-N2=&vBV`cxyO3DqLsPU}NGjH=sGZC%P`|9#{E&m%`5RD3?7j+d)NC+4VXC~mOw-yNV zs}e+`t{*|Pld^sH+Mh1=4fg)=pH2|{mc|z7rFk3Dn6A(CT>#f5|fK366NuMc8KAWT0VTh z_dz44nizgLS+)22qa{Z_?TylryE52KJ%LJK0>V^HicU!H1o{LnkdEsNZD*x=R1!By zXLP1TZ%Ff6c;EWA^oGvR<~8j%Dv{r&A!S;qHm_F;$h6>9y=l?QQf52)vi(bXrzcba z_=MiHL^f@P{l?Btv`Y(2HUo0j$R%CAxM)KpQ6 zNKx^hrxvl9YRT9SkQ?czmtX%6`d`z)li)?5tTs{I>>I$?zWc!h@HChO7J=1ZE64-C zfK$N6*pIb9GtdFVf&t)uFabObW-)dE^4)>G0P@{|Gr+;v&*&5U+yZn2J-{F^3Z#J< zU^Z9+)_`qbFE|9w00(0S>wp%ZBj^DJfl(k0%mA~&60inr1ADeCv4&&SKTfOOZGTNbbTv&YPQT`5q`Bll^k=Kv>U3C+|haW+4(N8CTAAVLgQq2y( zPI{+IinF`bMaeoP=Im$%G6{E>uRG$9N))w?zWEM09ma4WD{-Ry}sHnLMnkVof$2Qafr!?uD)J4Mb9J!PNOxS8lq*474JJOh)m$#j} zK*DJD6=~$3e8G>s(cGArBl`-tt+KTnih%ly?~=(RBNPefb<>GEW3twF(v|3EmCRx z8xt%}J-)?$2pN!FBeip-*?R3!sk^3yCsF##pwru4lolaxvS)8*+b<2!wB?mnBdLy6 zRr^74(a)2#N^OhsJNh44p-Q4CWp;E8W2eplD`TgT-qUWgt;b;gyB|Os^gH&M{N4@R z0qz42gD1g%zyk0g$O3!7&)_d$W$d^gV7f*xQH7zNV6 z3@{rk0c*fEuooNxXMh9cQwOvF9YGH;2#f-0UJCkj?4d!x-Q{L|aRjqqDrLH;P*^)~aB`n}XYs@6@D=%?wmZzBM%ERP5q!{kC z1B;!0DkYrCu2Yu|FWtBmcN%MYuCnf2oj)EcuVYwMPQwS?82|pbc9B1pUbQu)?2&I* z5PvpJ@N3CdGeKEOH*UFn8(IhTPETdax0ee~A2+El=f1M-tK+Y}|N1&WZE2NK5Aiuc zx5tKAk=Am5lc9ga-cZzWxMK4e|J{oJw<&5>V57-WufVQ>WL!o4XTgNH2w#9cG5@Ix zu^U6z6p*>PxB7|}Vt2iM;!C%4^JuG1c7-Q+g% zCC_bJd7xNAj#+g?YUPn4Uzp$ylP)>AjrH5%AN^hO)j)WW`?FXaFvyB|U{yND+0~8D zNt@dHcHXGD!9?=2(g9y&TlQmQw%t-1vaV{2&r0I!XY;ql=)7QjWhE7XH5RE=9Tw$F zS?y^i`*WQkMEC%jH4l(z9lh$*wp3mA?fmK$R*3iJ3%s{TnVM5nzV~S&yC7U=RO0o- z0n6#t?X?7zcW9t1JquOTv{6+2=c#FK`;PW8hivI#>$U zfiJ*!;5TrdF{=P>0xdx}NB~1XGI$I;3tk6H!8-5-_zwIA&ZDdZa1&?=!a)KU0+PXF z;8`lm|H^J+-5eH)VrURq&)$$A5`7 zy}JBeMRem0E~1Ko^a`)4ejH8Xn1C;E7g2A9*;R?C;{T$EYJUyIML%>wSp$ILw7*9N z_$g-gPf6pxMBCepSyBjn>XkmdOnT>)MAJo#-YOeK@lrC#aV z!lZXzxj0EuD4qUnSEM{7(_uNur=v;IMA|S*O_7rFlv7F5-((62)?xeWoaRCP6jZTd zhds(z99=|-ywb(dlwMgxqoAB>O1En6sxJMpct>|cq1qxdC7qA}RbTR6YrpsdlJmeU z%rr3aYgwF{6u_cbmE}Eequt+@4+E}Bh+f*bHnsg+z5u-m|*Av zdV{;bLtqN{H+U1g2R;S(Y@^{gC}e^WwbIxC1OpKyGQsOBCivixF&}(b%O?m#fSzC| z7!4+Y=fGUB415B<1mA<0ui7m7z##%N#Hp!7c2vxfG@%K;3&Ajgc=4= z9|VC2&=U*=qroKb9GDB1flt7f;CpZsTtL|vKz$GdB0x_t6pRLwz;j?OSOz`;Us6e4 zvinroGX@mim2^q!{={0F)9JrN{~^i+n|MO&X0te0;6E&~Sq~G7uW$ygAVH0g94DSm zA}WuWJ}hL@FCK4z){>J;Px@VcVp<;-3Xq2n=_(jeC>Ls1vQ0szMoJ5F1#@LwGPySH z3zI={Uv+r!~cRQ-Um%nTI-{=Bl zmq2>yS5ePT*nzl415Z6a)YhvLAiG>Y0%Vn~F&6y4Lvhj1DL@MTnAhMx*-Y>+B%O1O zF+?bbBtdN8tBJ8=RaW~lYZ11`Y4LC_eb1H_30rsP?oo_!u5hkx zzM?@o_uvF4MTd0m%*Ea>>eJVDDi<#!*(QE?+jCyQ!O2oo1i*#l<*s&YUyUk>YDt9Tv}aTNwN#oxhiT;C@TK*)Slo? zrTm0C4q2s_B?aq@G^J@tN@$A zF7OjL2}+pIxCUqp+JGp~7u*ZRgK6Mp@D5l3HiKQ@CvXy!Fri5e&=|AtOW1Z6r8nbu*c`s(s`Eq|lw z#$Ku2IgmQT~k2QAK_G!{&om&~6#7gN;osbV+?T0K0h2D8rN zli=jghk3!1d^j0Rs@pQmbFs#jzHeoH6EolP`fB)A7iU9BQ{_q|IH_#@+lWLppcE`n zEQ=S>0c7e@^0E`;$bi-1rLJwJ9p84{0c3W|!PB%7n*yWLx$_kz()E{ifEdlM+Z=ZJ zcrnu8tg(aK2b^8tGvbQ(5@$#jMM-Cv*$VOsG-q-WG^gCeATU9Wnd6 zq6_;Mc7!Ln7fgGu?Y4W7BvoIu=U=UTYIj*F9nlOi(sPUMkZO$a_;-9)dQ-Efp(NX{ zk0T*UDBK`_4l=A5z)Sr;d!<)30|Xf;{h`-}NG@xvGY2>HYB1mjX|v{rC-qc2oxZqR z#*xy3?_X)6Wv9FYA)Tq(R#B5gQSqNI{!`OKKU!H$YJbx3pAbm)_~{2i0^2j8Z8~@b zyba!UaU-;Rf(b2O0~mOlVUV+zP@#92g89024tvm;*AvTJSme1{?-wfrK)v3vLBr zAPx)$4}ghOmj9IlAy|=6+Z^sfc_v^zxpfUM9k0)HibX;!I`osTSEJU4Ra;VHfyn7^ z3MZM~A8{!cS+uVKbGZfk=O%TXDRE69Pd3j3b(w20mvsrPD>zARG&|Qg$ZHhR4UOek zbgL6uS8xK|=n@vKdQf8tA^ET&3n7VDyR`bpC)nk>(2%Jq(UPVnl#8{7My_3K5;?B5 zSmPdkwV30258TV=TI;LJ|N1Kb8(qwe2&C>r74<2FV8nGXF&BsFT%DL3aXpE-ox>x# zMt8-5+>sH{UBV-v2=5XV*`;&awtj6Mo;rC->e%Ul6H>-bfp+x7X`zoyd?+RGq3LPK zkED)KjHafgP6>Q8by~`V$uJq8nl>dhZCv2=6zJ$y$v>ez?LDWTPRwnuoXD$+x$T#c zgRmsXiW~@G0|yI(4M~u6lCTyz$v!v<-FZ?MCk-ADKPV|_FxNxe>F-ny(UgWHh)PM= z(>e&wxl7DL4;q{_5Q~J32(>h6FhZozy_sWhk^rX^xFN|g7&~D}>6_%}2a(7{DJL&1 zDJ%;r4mJvJ%*xCXvK*T!$z8?H8U%76Auw-&CG%mq!r=F=GZ%j;AJ&Z3$<^SV=`{h$EO{gt9pJwxGbvS`IYLXEm#xz_kbcl>UzVQIw8+VQj-P94dKCVBwt`SCLPVTm zw++V4kRwE>k4lcwSkj{=p!6xpF&0@>^}dqh7M*16qAjmb9YGIC6*Yhq75{k>s?pU} zenQy&vPv&hhnbkrtt;pWup*$_eJ*ZnYrC}KjS4`>KNKv&QQ+zrNp$H9x> zE$}|r2zG!U!3j{rgeV`-5QKoPpbxkkj0KN_7r|TLeXtSi06&5gpoj_4KA<580bM~K za5oqW9tSUix4`>gBiI3c1Sdcd%FqWi1R+$GH(Y1h7kfOCaPqtkV-nUHS0ehby^`eV zdc>NASale)5Njo(58EqCi!}tX>M({N)=ESlwpWN6E97B=sT;}f21&bE`3yaaR$8Qg zR*E;_fGN3UEPK@w>Y9F@DLIGPEGVg&j>@e+MwhTDo^)X(mV6-AEa)^UxBeKlaMd`p zSVLfp~l*hQ z*6T}bz1G7N?%`M8|Lg1i-{`{@;epg)tEyf*ZBoYpHy-yGP*47tsD#8qDrIab~q zu}wg}A`w@%H2^~*ZKmzw=fN8QZsXU3 z?cfJ+j0wGx0nB?HXF{Jb0Pg!d58%EJ%=^H7pX~td`y2!LOt{?$ZU(JDXV44W3C4iQ z;Cb){co(b(+rbau7|3TrUn95~v;v($FK{Oq115v#!5iRRupVp&KY(K(p9%ep;AYSY zbOyb^onQ=@44wyXfOo-qupRsWj)8oXlM&nuT7k}>7q}CQ0h7V=;0-Fve_Sm05t$!W z4C}d?S>np_qfdt)zWbg|>BCxLJy&<^GQ^=uIObWO4!=BQgD}zvnX`te3klLSP@2kx z8j5U_H_YWG<8s2tf2X}%0GKva%%F`Jq`MFkQ(VDaR~QOBzf7Fi8KtLT_SFf zVSsD(_5HuT?*9!p*dV-#&i-F=Kb{3g zbPA6Q?GzOnA%;eD4(}vJJrLfdOZYUgYnKNmkC`-aO2{x!lJF#~(Eb4V zC-gTf3N}=kQYYrCp{(w2O|HFLRFPM+~YdRAy9K-H}e#}s795Xas%nYq-GDAn0#kOFE zUJ2j>X6SvM86Z>``hCO2hTFY!;#D|+0G0zOw907CNsS83^Od~&I}*KGQ*|? z%&_|uqLS z`Z6=F3Sq_#w=&}w2~-y8?D;0Pi1&6$QIgQ4G~(-*TQzOew0RRlW1)%dy??jBY>v>x zvMZ3WH*+N%xt9AH>j)F8)RevX#TcJ4_AlqMP#BcLAa_iSG5Btos+CA9T??%)B1*_> zUs6K0&*4#7V8G_eX`REX5`3ky0&+;}+Cd;20-17VYca z6mL=t52HI?bXXK0*jsqv`|+ZWXdh$|1=0TOPWnH7BzI_q?|Ta*AE~h5Ua{NRw-b6! zOmT7A5TPL^`1st8!Yi?VE{a2?IMGYo|Ma2!+$8aUbmGJ#=PV}Ih@LXi+EsEVvZcXcj@QNP(dt~J7{atm5`h!BSj}ad|E{f~2 zyAL?LUwrk87z)84+CQ-JBh){Dx@f@4qqd!c#I5rXPU-2P?m>Jbijnr7V&Mo;d}Y5l z^M?@>gi*x775Gkz9oe(=%gGh@{dGh*QhGW?=i5pN$VQ-NE&bypBzDSvF=p{G*}G*< zkt7w(5q(AL)U)EYSqRZSke6sWm8j;o$kJbp+8`I6msomElFq@yZvz(%i%Ht`l{n$g z2fD^ji|lUiFN$f0qmf@{%xd1p-`SO7hVNe_n$Ij+Wj3{8(P~)>(g3u*r&aJ zdt0A?KJESFi%)yas$ZCYn5|zxzc4@f;uEG>4ea9I#WpZtU>866;?qU5N{aN4v?T>3 zMf%AXpGeJWNR)q+ZAie7C_nk)6Qx-VjrNbW4GkC??I&M+qBX0#V*F!lcLm%P<0oHy zVz`x`SUPauh1n=(JG(2^KbD-t3bEFCCoMnzE7lr^!dwOWemjasF}n_XOM%mvDA_ zTwMR6b#X$Rjr2ZoijRV~Hax!*m9O~xiMx5K?v3}4FS$41-uQ9Si}6CdJ?9Vnw~>KQ zyyBheR>9aHN{Ld*Tdnj-3*ALcau631$A@(s-0Z@d`)pxHw&52%>G; z$=Dbn#=3ch>U&ssLwCo>e+mC`41%hJ3OhymH_F}q)gNx9FNlt>(8QIVjaJHqsz5$f zf$nF&jET9u=wk%UtArt@h_j8z{ ze6vo86qX@LvpU0*?I6oXeCPIeuh(#Fu_OgFX@cl3;`1ciO+E{H4Tn#wrTMT;rxD<} zAGcXgbeVZ6zXqwuYqJzqr;%@uqm;X&6N0^NKLS<3y93<(-I7HG@t@u(7D{dLNw!A{ zd+nqJxjJ>CUr*`e{jHiejB@i7F`qn*8>4u#%#*X|FP&a3hyq()leTlEmNujun7AjYl`e27iH`v%`jbG4h0s0cC zSB_b7Ut*T0z61=ftS$FENYvCBmpL5t&SV ziLijMprH7KZV4mrxsCb~r00DJ*=1+yON2&IU!rqB=hm&`?n@qdM|PCHuZUNn>FF_aHmyoS`P+tPw7wSvUh4&?7s|4yxBm^W-UxF^Y zFCkm?q`n0DGSru#3-3$FR=ub%(JP=A^(E-S`x3HMBK0K_0}`n(K^NYam?i4_5{cB8 zfF1QEp4#xmijmZpAU*F(&~10?OZ1_>1bR^2yHCq`aooF~523di>3Ls*YN&f(qA&F& z`UdokOUPOn7kB&TbEq#tdft~HA7%Oy{irX2zF7Q-oX4myvFKCkOOOHYOOPM8zQi5W zm$)O~j)X^ZMke%4nE6#=LIP=dUxJ=o_rAmc>Prj=7|`pTk z4{m*lJE$*ldq1I{qc`;>DDY`H!%SO;KbteaB`yzriN4gASdt^;I3Aj`mSqp5z69yfmzbB6BKIX4*70LFh^t9yL%&;f zf^wY}ilFRQMo`8&2v{%sjp3=Rz{`!iU+v3{Rp{l$eP8v$0)kq%4(}eF#^l}Rc2%(o7UHHpQwrWc+x3&Ro z>E%Wj{&JJ8Lh0oe8W2h^H@fhbn{3sIUT&QNI?>CGF8t*tTZPlhEj%EcUT$>ZFE`mL zf?jSB0TJ|aqYHnz$yQzH<<=#j3%%Uv!e4I8<>l6eUT(0Xm)nSGGbRn7mmBH%%Z+Zk z+sjR)mzx+McJF?lDS7C`XKttFmGu1OMm5y^WqTgmPV69$s#d^m2Q|gp3w)AUH)3yRZJIR#kiJnW6}ka7{`od#;oKqtp29E;>JJS36G}7F)hC! zqsJjEp|v&X@fm5ccv3x$aQU@1x(sV6qg&d==rXdU3~*@|qs!oyGRCD{j4tC_$}pF9 zF}e(KDI;Cl#pp81r3`jy7o*ESmona^U5sVMxC&{^D|TYmA5vy)SzA6L<`-)$GtyPa zN4{1b{-ijods$mP4(8Y0SZ27ZkVbgoP=U{9#g(>0nz3k_=_VR4!*Gmb@ZP3dl9ydG0<9V>l7-#1t;c+AkC2^5K zE@O9iL%ElD90EhZdmidC#@Nqg#$3eG5M!fpFk_6d%=p*ud^~K(lTQ|Y`{a||qoXe| zJZ6-i`&ou%KJR+J=fB+heeVyvSJYbR_2Cf7KISERIZM9T%hDh94#D^D?Pg;`o(!#;g!hYoUu36+tY}#6AZEv(4 zp%?89e2#mp3+s~G-<3u*>{RULvkm!-;IR&UU8*G&r`@G_DJCzohWE1|tYf;^jEn8> z*F8hdGMnvjoL@!LEf+Sv^IOTT=C5d;i^%hw(mU~`T@;eecmD_LwEavj-j!js1Ma6n zy72YDM!qedvT42xxo^ojcxb6q2gN^Fiq-v(km44^&8m2t?>4p|k2Uld-Mj112ijAc z?`F@(9(bGYVW6&t?`F%#D|nmlHY`y{dTR5x%%e8n&6W>Z@HXG$ z&;;82EpJhq@6ndG`5p!+&=zlbi`sk-cD&8^Fh+qkf6Lp{=6kT?ZN7)W372@>1}=D; zPnmTelt7!$OTy!b1#j~`4o0BO-$Lc?d8mT7`BVwqha}MEZ+Vm2d{35W^J%<-xA|`4 z6u)!*9plDrdvDyh*yw0CYyB{W(&jUlz8)>2tYBZyP3)4zu(0@u_EB*m^xV7IbFqv+ z_a3A$JolZu$Hj!wbML{LKleJx3=h1E4$r+#IKu<)qQi5qlh5$LyXf%T>qIm>@Gd$$ z_c|#J54?*G&%I7i!vpW4!*fp^b3E{#E_kwGtQXS?Ngq3C{1c|}^xV7Iay@_U-GsG5 zt{*)$<(cvH+`HLwQI0?NB$VhRwnEa!#mB`xl}gXOn=O~;_;c@3YQuB?R4P699&Pz^ z??HCM^Z3-m^xS)}jp8Kc9(R1&~63;z}a{Re>6XAa6`kqNiPftur>J}aC zX04a-l;>Wm->^l4+P7#jo-tb!*+Q8kNvtcnOJ$eS+k22NH<|3xT6<6OqF;re$!YdJ z+zKxcbrpgpq}ls%E8QvtO+vE|2%2~%B-Io3YY;Ta%szzs(XBzy#4`I(Zlzm; zpvh$RyHqQBR$bN}TtYAVa$kUOIlX-t#pS*T;c|M*JxQXNHJb)gKYbnRb;_(rxV(Yo zUe_SXZ3Tl~O95OsA)K%my^d_`?7Gz~ij)L5 zLG2P+H3?J%f|zgK+Eo;t>lgC1^$YTP26ugdY_A4VfX-6T8rg%Iv~xmNR$o>`6$S9z`SUhV&p z&l>Nw-XHs~^ZrCwk48Zj0z5PdZryhGZoIqR9!6EytCf9Exs|8R zyuBN@^3;=;8zzt4czXi(<*5&^v`wBm@b+HZ7xm5DyY2Qw)k@!W_t;t2b?s$e?tSyi z>E$NUy=#6sy``^Og_LH&y^mg|(_Y5Ff(BUMvgCQ_u)9q@>b0t8U9;e!_wF_ssn=2f z?#+USe!g4RUawV?pl=qOz5aY{DpHmL-1P;ry&8zVS#TEu%Jloo7+CP4E7L5vb^G1L z0C&9wwXSTl;MVzfpVM^L%e9&}3(Tc+fTQZmb`IRS@$TJvcfGv@Jr{1B_)BZ;LHtp2 z>Cr3wdwU4Ca_P=1oqKy*ZspRKS9Fwd%%B2ghbm;97 z+=}{Y?!9??7u8DNmG{_rM?0X-$dIpkVu9!A&gF3b+XY?m~gP-h$dwwiR#_1>7fp-1TyumZyR0 z-O}jhhV{#c2wwM!*0h5qKaQZO=J9(AkBJ?{lEEcU#LoIFmgWT?eCAa$vcI&C`ktqz z(yZ_loyp$|G?S8{mMPKyk(Mb2?Zkq=B?UU2zV%z&)VN-ms^ycd+%;i~|5oqMytjFOUMt)C3-2$z zw_{~+x-s3gU#dE1F2Om=M75W^>b0mFP^V0Eh9^XOquMxXQ4~uX)sOC-=#?mnw%XA> zI(T;wv8=dv2cd)YuFTszbP&6Ug{*h`_Ho%iC$(>{>YgZiy!{h{#SqcH^gT?s7qVXM z+O0hiHX{F7v6g5Z^^N5eE$_DW$t*1RM64l}!c|Yq3E0kO^`xn4a(;4Y=-LAVM1RrJ z=fa#jhCV>(C|P1r>9|KELW+Q94S31^4LuEVyKC3lI5`A7ATT8Zd&KP4q=YiyjECi-F7^4?OLi_BQ~#Dv+?w;n`}Evz zAEE}9v#O0)y;N9g-F|A*(xvcPj;c1|!wezAx^&&rjEr(rwGk_q_$;Anv0{ z+oaMLbcS8GeG%OuB6{=TJKMBLAn*BzoebNwx##=o=q}jZOsNx~nwG$=xGg&huX%es zdG=_~<+JQay!d4o4yg%if(K`oZSsS+;lZhq=k>Ma?cq~{QBi717R$;J4?u;kQX%ao zD?U{yNiR)?3H2(}2d6+MST7k8I7V zhn-P)T%e@K<=S58aX}39xKuo%5j98HlZVmcg0QyK++y4G%-!@XK)0^uI@_j=$%<~R z*IL`=-#P4SjtS-#VUA@bew|ezP)!iz?s=_>L_DA_g zSFPGWhG!7`#-qnJZ9H>i)&7!|E7z-ryB&@_Yfl_s^R;8eiceI-&kBpSt^518mET+6 zfB$3EaP^5lKH6dZdijaJmoL{0Km7gps;!6L+hEUq@4dCE%N56dU-{nqE03Jtux!~! zWSEcGKREXLilzV0-gm%9Rb_F%nMv;uLNck7Hc2LJQeHC2q!D^efzU}v4@E?X2vPcv!s0PKCP5$i?)!)Dxrgi+**&I53EBbN2fzFN&#+WX`z62w-+uQX zOcv8465#%Czk2|-i|Jtk^lTU27S8DAHPWJH!rc8KW|^7^^TM6InsH+~#ZJv|4Ae`_ z2*6%y#saieGXk)Wnz04js2M8@%WH$<5v&Hi)QkY^rDg=6othz#y_z8isTo|EnxV80 z4qf2A!7aTvzwL$I8ha1$<085R~7ILALF)jidS1({Sg;o_8P zp_-(1*BY_XqIDB4PFhRW_(b|G44WO0=GW#3GyP`BU-VX z7ZIVaXxvzia~-`y9b|>LhqS`s8xk^O`J8nNLKGoJN;!pyCHHW}>SA`U7h*OH`r^eL zJBSYU&KV)ci|@YiGvm{cZ*7a;Z#n%O{E+{If@dYuF$vSzb>zLY z;KP-WkxA~LRV$xkRGSS`%l`cwqIzJ{zC^X_)B7nP#tG50!#93^?jh9T`Tg(zI3KPi z)5A{_&o$qX!*vopa0UOvx7+8MCi0SBXIlFb-7XVIj!)(@tR#Pal}L6HiJU)DX&TRe zeH$-%b#}bL^SZO3xu5rBW#C-M(z7#&;v0dY?eTYce%Dt#bLDhlBK_cgRI}@I(CiRt z6uhzSR4r(_s@jPNJ|6wKwv#8%nHMlOP8ZxbJ6CH#bwiByPXNg`M z{;DrNdHRq@*Ru!qnDIgWXs&DM_G)JK@X392_kuuHd#GF%^qX~->uE6TC&u4 zshBh-SJllPF=vF$&tYmxB|T%rrNOVdVTNqRx!LC)F0Z(D{fz288_o3qbZj|ueYHSgI(#kq497UE zB=yr}(~V2a^QJpaHC%>Mr%rFEZ*IAtFJaMYTu@eBsjReX z=83m9mzOF^&n+<5mX?;89z1#4mhx<=>7kqzt*?!nc6I?T*0HWsUfNlA;E3^~dT&%~3*9wFu4!52;^9f(tlgsnx;F)XMA`#Y>IF=Zz{`v0`S;@Ki=9-d{3iAymB?49Z z;T@AFkLOEU@Lb!D!`FuKC6-rP>{M*pb`~FAGR`wsRVnidunVZ(XbtfJ{J zw6btM5CVs(PlYniil9xImK^Ee+gsa%(;_COct|@$!TR3 zBPv)5BQiNr8j<~s*4D zsSGd#E;Ky)&9hM|+j%>sLP@Jqp_E0bhzUWd2nkWCNQr@z3O$p-DHWm^j8YNsFGZ-kh;Z;qg{Uq;saR_{Sf$dN^5T_BZ_0s`itzMBr6MFmrP3#1RVtKaRVv$g8>Q0k zAQz6CTFx^=smxiu;fjf*RA`qCluG@^A}AFBFPW4IBrz!!x-g|8WL@AObpk1sgrEdc zDs*8=MWE7`v;l+9UlB`FnUkTN7ho1L1UPD+JROsP=Gl2W9il?qW^l2ReUOPZ++Fa$0%Jo?SEQ7W9h zQlX?(sZh$IRK$d!RD^`6RHVc}N`;=u;FJnc3`VI4_?MzotdxUPDnvMVr9xDfpj522 z9IR65O?mN3r8nh3N=113qEZnOqEhLTuqqYGvMLqMR;lzIvGe3Biz&Kn?dqDe2zjqe<~qYEyI%gX7V?*+terc0!pil@A+lb% z%$w#Gl3#3)7w{(YSLB;n`F-cRljtV@h4vFS(9Og*PGUEXBi~i}kJcj;IPi~e9ak4V`%mC^KK|jjn$`yL4vpX107?h`NM`c`xwWnHd1(*f6CaG5 z_SJUYR1B<++pwc&QqMHJF&&w$i+KL=XHl53knI3E*SMaaVb0_E7a!r-Zo~7~ZTRzY zp5OWf{}DKwo+Ia!>Ev>vE;5fs#z7nENB_o`G3pOQYV1q|F2B{3xtXY6y_=}Nr4X+j zbIjQHwC)>;+JQf+`|dt{Rokb$w1x5M_eM|t!kj}k$bO=4RS0odABxD(#}6S{+Kg;emSoo^%YCU!baYj4bd8f5i10{#8b z6)hkGnAeXtm*^3DcRkNnPMWyuWIoS}8z*N>AAJ?}Q!4lqwBc#x51s!&_}p7R;raX* z+kd_RE$${b1$fNo(L?;6bDi(c0$AE(x%{W@uNrxSd=sCq&R?28JAYRG%>1&>BUhI# zG%qfbm-XBL+McV+9LkLI+x`uJ@f-8fGFh4NQ}gOF|FX_ii)U1r7tN5*Fx_xsj(ko} z*$jsn-M6-w>foYkYd*4n#*9TLIw&q8U zPM`kp6CJCj`%mvYFuQbtxdE;u)3)QKE~Q;HJy-1e9{!}Z=9>?%DgAL-$Cai2rJY|t zX2I$?CqLIS2d=794}4czWm;8Q+I;h#7gm2+Dq!CBb;*2leTlrpwC(2_c}-7giK67x z4-b}K917k7TSzi2MH1Magn^pbl~3$Wed;Mv~DeG-dq4=z8{D!c-KQ7-Igk{HfRtG$ns>enmdEpuNV62hnYO zH=X3W(OveI!&{xBbUI4dWQvX*#51cAgrki^NA&AhF28U--p;KfdUq@r2uB;wj_BaA zTp%26oI9eA$8v#i?C%Va2vpd)ctlT+lYA9EXJC{|myAd=8L*k`JPj(z@vz9X596>U6y_MF)kE z92CrQB##8dVSkrj2qIwTo`6)eaaOpcLO9xZC?FN>QV@bTY_^qHX49r3cIfc}ff;PJl~^XwrYVfdW?PA6_H3HOvTU}M zSf#2Cd4|- zTgCZpCf^~=R&gs$J#i~dC3SqKI5*5;^F5@+$(xSv66RqB`HMF5E=&T-@@M2v$F(%S z1T%GRV^QD90kigdTmKhGi$~x4n-iD{Shru`n7z#+uVorw%4JmjO$AJPmet=pz=ZRq z*!tGrBtQ%VV6(_;G5v+`0rnRDg;GL<0(;~BLMfAmYW0VE#QoVYc*K&y!)zLxw0Nqy zKa)o25c_CI_9v+AtQgUzF(t5vWoN>O293~T_V$aIGQ}{e{!AJ1jJKWTBHA&+$#8o+ z{>3@KoawU9Q16 zjZfx{^o=PviyDu>ebd#tf(La1-z9xKsul3;b>^7_PUPFg??E+U>sqI28xGiZ@>>2Z zedGyv`H#KKASwJge76YzUVr>DfnTfiIVPc)TmLPBrSB{W;DVN=U}%tUET)TEv})^Tj-gxYD->c`{GG*?ZzGo{>lIMUfZZtW+_mK|I-4)>;cEWgaO zNT6o1ogk(Y3y^kHnz>bjjra=QC;Q@w@m=;0&(a1AB8K#%`? zedw@A*LrxfZrZJdlS5O_9q?M2>MZ1k3fDi$g?vEd+-u8XMRYUw!pi z?71@4T8d`Gp<>ZiVl3}q@En{3>drH=GhO4T&Dd!r`+aJ`)l`}Zj};GoY4gJ$Ha7nIuU}S(51!VeHX*-j&&Lv# z**w*Ks;Nq%!f@Mq^vA}=<3Fsm4yP%mN}K#~hC`Px{bZ%haJ0$CV4k?BXlnI18q934 zx=%LFotY?(_2J?Ocdsy(rS*L+@NOk0W9W4!r&!*iWIo2`T{CdI_#sI$BuR!O$&e&r zx^hU84A~?@Hp$q}hHR4myEchpCvVcksAJT=xQ6@3`up-dG0He4F6#a-4oYKM>l-*0 z)pgyEZ68j-P0I~Gx7;79ROYq>f0X^2_DQ%>ZkAaDaq={>XU#0g8bED!KA#_!n73mi*bI&}|OTe^-a_umEHUmc&Td(R*eaHaAKH1ciK!NeMAN7xgdEKC0SrgxK+tDCcf>$`Ym!?C>NPpDIGk63XwR>`+j zM*UgrR2xc1Wh*r?==LLRpVgtrmK#sCJb(w6`$6aD!h`#!^Yv6@4E*%j$PfmINuyH6 zDo2Rp$H4t(up>#PYzw*joA!fMO63?jN^=cWX`r!my8WZMd__AxH~cvJc^dXMp14fe z8h+2j#Fbq+$}vY(GS$&w^xyy^>~!J&ulUT?DxUwknSKlc3nyjQ;l-dFErri7$%6I zc2NEl`UV0#Bv)pae*D(~M%>Rd;>_o2dA_zK^Pz3Ir!pTpAdoAR-601Kgn-StvE5-8 zC|uuoekz~cIsf2n{*(C!uNA1}%I@I5z8s1sd|F@%A9&#LKW)Idx?QI-_y=~G=tLUp z^TR<;yc45R1uM(O#T2~tD&EWi#^2f8)ushJzxUMZ*c5Z>BsS?V`kqwmv}nIZdBrzf z#~w+z&`30PHE+fwtbMT=U*R3o^RmEUVENSl&_sNzAwefe$RR-o4K*a_22iI%f^JCA z4XlNSEV|xCq>Xt6TV%+h8?xwzEV|#tqCt^g6C=$g;2Mvt`2Xvb)ZD(aP%m+z3D z!J7g+5NDKj(qq@Ozro=(rLb|=J_Q!@9=g-%PP#t0+i5pKL@wBO&o0MZxG7z+_dZ8# zs5ebtfGCm|!RfzMscySaBoqrw?WQ*6vx84aM7u^CGt)d^8&g)kOMZ7=nwP(rq+l=} z{+QlS93DZOKsK4Nt*zLC4^q`d3<+{sw&%Om>)_tz`8TYOz5mOYx zYe5N(7*J8i{)&tB5rBzx^li6{K6b~(+#mRkjlS(l%jiQ}>>Ac@C)r{A?=+o7E9)iv z5UI&3{E)B_en{C0KP0Wf4+-koUvaTM12C~Z1I{x1*c}^wf8aYd{5b3ILtE?`)^8`- zVf^njy@!)xezDWLW%jPdRpNOv-r=g&QK}p4aE;?%_uZ~ThH@`Z+mh0 z{)MYlsxYPGQn&GgO5}7%_f(;IH+H$ax2uik#V}W54NNbrgx~$R4RyE+KppzY};RCd={b~ z0u^1tnKc=j!6($39<#>Nmhkt4*w%Pw#!hSXFD~(ZWk#ii=AsgaAd&g&V%_1j!txt0 zNEh_GCd@xWkS?&NW0YCKn zWjnYeku@NU2`|bOh1Ygl;bj@yOn6yRqVQrdqa@)q$b?l?Nrb;Agb6R9;J4;w%wumc z3F3=P?9b20x6oWvLK0qr;E?Icl4lYS}4G3exi*iNb#n}ok%h+abmn9_%FBY3f5?+H$SVfgY_p5bl>|9rd|~8_Ir%i(e8gyE{7ET@FtYg9 zy4dz2F-wfUb^e1#C~s;dZ6adrf!bnW8@mt-7&$WPhQ95$0;!7_G$fFFv0O zq}fEGrsox!8MkRq zV>KG$(Dur)WB;hqXr6kInEbp*gw~f$-riZNd1*I6n;Qw5qUn0|4m-RQ&AAu%Xl{B> zqscchb_trZsaF+bukD(v8Pk4Q`eS0r37XE-_s_K-T29QCeBb%DSkGLlEc=Pj1n%rH z?^Nszz2Hq{&-<0R_CFDo6b^{vwQP8~@J7rT{FD57EE>3QMRd5q?Pt+0bYn8qL2jn&VKo23O)lkIeOz@y9jJPxET!#FXv?bTV z{>p0i>XXO*yzw9pf=Pe>cuhmoxK}=0hk4Bzb;#GZhfQhyP}+xUp=qpgt7_k3T3&S{ z5g~MB*1@U!x90ya8r{~eR_48N>VpTLgTvfN+ixAEsx?-+Rkk;MT3I<(r25nCXbk`M zLNY69U%TnGEks1SZY@MB7rHKNt2y6s_AI`KSq{v$sRy(8XVztXHxY?5YUO0DcI)wI zX}hhZiYqEyE81>vTd{1JNY~zkSbe4cK6D#vnR5G?jyDRF(l%QQ*%jrk<))ASRxT^I zf6P~ud-Z}WU*NUC9_RA;Ui0lC%W!TO5bG!_tZA6*IhTf`N;zCyaPQ8WRK4^vkIU$o zfz80pFT}v5NNV|9GTBLLP4||A#>eoP8H=gcaofrB`;%asd6@$=-OH9d^%xAS)1u9% zS^0DEsLyw@+(3=#ewHVhey`u5(KPT&nS|$I z5}v7s-Dl40i}8y*PD>`^>!{vJn`#Wx97{}j=*g5s$$Zo_9-oqTP-q0J#xPCKPHc#8 zzel5y49WICoj(64^@3(NH6_+!RAy-8W(B1#aO0_otu(%GHltgCIGB_m+SqOx*~THM zGbD9LWBe~jopwp;aGso1?40T&c91Q}ol||}4l?b<3KJSFwt}Y*3X?o7wvwk0$}e*{ z)kpS7OonNW4kUc6D1(wdoFsh)x`DjOH)=S}B7H78H6(q8Y@Yu~n`cP+T&(RgkkElO zBxFJQ_+9+1y~N4q)uT*f3J@tLdR_P0l_jGPJ~~Z@T+G z^~1ze&yqTyI@7*S>xR{tR*!-;-YG^8a^rjMs`aTwR_(A_(^aEj-*-qcgpl`XjjZOx zyRRMk?%=ALtJ|Lb`pCmIHG4m;`FKCzrf+xGxYn3f5sesRs3T*~sbkk7-jJOC&a|Ss z`km7&s{is?^&0b2)tCQ#L-k#sR`2|#x_W(UeYI<~=}IEB#3AZf_rtWjCPw=GkB?SW zz4KyKRh{XI)#fEtRnNRub=N0VbuDYF4*VUnAX6i?;H(&`=ye=+__vC;&l{><{;XnE&ui6d z&u^=^^WPP+3geI8KV9KoVOl|rJ0k@}TL0bD+*ep($IEX!SNZmV&)W`OQGR9HgJ(`Z zR9?RO-VgO+Dvx|A4i#u<6`gZhpEMZ`N z-uQ7_%cs}r_=W`%5VP3Ltv5`|uYGwEK848eFqZUZw*tY27O+Zlp%q32!}7bL6TcmA zo;-O4FO(y|TdIJ|kDl<|YCEt|f9^UAo33wc!E*lmb?3KE$*($fl?^iLdgZ^`J3rg3 zvjJKgEVjG9OC0>-BY8D*g!u6>{w6qGeE9B$4_m*-2cog~g2cl0H?f48KF`^B!CQB} zdyCopF*VkT*wgs^kR%wA1b;wDAj*Zl7mFmItUi)}fW0IEfqF{<5jG?VEFxn_5)9b{ ze@vTz-c=~fzLz0M@LSmg2H9lZl#;A=L}bkrK9%oI(Kjr=LUBd+@&N7Eaa~dx#RW}KFPrB)&$vvTLa_n$`DzEXF;acm9OpU4%c3LIQR?paG(O+#Xf~cu zFPY^$%eW*uw=lZUn82$A;NqFiGmVR*3&uu|HL4}dh8fN?j1AGF#z&9uj_37x0@u1y z=Tc)G<7!Gs<(;YY8hkIsSQDLsx^$-{&Mhx;Eb13j-r9JBbX*BxH(dUNsU zL~7;fyhU#|HNEy))2pw%^7>+ZVmd#J?|$k#^9GpoUAs@UKAwf|`<^a%6WOo6`pU~M zz4+oAqY{${>H3D|4KO)7uRb|v@)vvXDc`Q#eN8XD^s?+_*-Py&zHs2>yRuONOt`}g z?XA5$zxD{wPEI)R@{6y(|Hj{5eDV1M&mK519kD0*rM5R8~@yUjTr@* zRqsCaWI#=L(gs-;`=quPiyMo$ zE$M16HE{5t*tfMk#MrUeMm2fIPOqJ&9g|UC_W3$5OYqqA9k*`v+G@OYM>=-+(8uh2 zECFP9ZuQw}+DW^D0H5h&2_0(-UsUE}2^nh-SCn-uG@E;Veqts*cqB!Y-MQ6aYscQt z+q-{$>z1tw3-^W@iWwah<|VUNpSukoJrW|wn$$ai(!Q_r{XN_7dgn|D@fBcHoaX54yYUWtPk09LFp7;nAlwom?Pi(afK?x1fX6~FU% zv4a@#RjkKX|Jf?vYWhe*cg179bfFc?_oVfaS)GtsX?q`fQ1PJg4qK_!4XO2)yB+Q} z-iF6(5nJ6juKmyx4o?{G#mCZZSdEt%~z+q9&w^jb>rr2^bmdfCJKm0n9{JIwC5^1YVMAD*b0t*~$z zKzc0&O8ctz=T}xVK7ES~`T)|4&`lq{HQUv~cL3?Nbhdo9aZcAwy)gR`UxeAV(7b;3 z?EPoXJY`db0mK)>C(g4MHvIiXTm1gz7pw9|Z=n>^%M$vplV32p5~KPRVA0_j>G-CQ zwzmKqEnwRVu-L504wH>zdkHYDW=Mby39#QnfW^m>0E><9Pk_ZoY4tfCIo*0~R6I>9YBEP;9FY8$c~0ynEePa!fZ=0PmhSW z>BNa$HdPovfH8dH9FdUu#QnDT{R=Qw<=6g-QcQoA&`E$r4`MzQ$e+97i;nN{X@MI) zZ$G*_1Cyw}6RZIKY+>hh*-JZT@pmsBUw3LVVfC3|1@IlSdhj*X=GC-g@UbT`)#`VG z<mgKD&qT|gKRdf)Y3AHh>-eq5%k=sz zD!iYm79t*&VYb=6zPbI_T^aoMFu{A?B;0=HTEH#GNuA$mZgqMPV*n3s%%eYb{rGGd z@72%KAI6_!)9d`^e=IDRa{WF+>hti2@$GDSed`f=P+dz8pa1uKnvnND!|uWi`<+LC z*fF(jMb-^J=TV>aJIg)`%tN;$j_%2m|KxYPMe8QtG!k~dLxHg0eT3(;Cv5$Sf8cS# ze&+_l?sv}J3GCaBWCN>f?N=i6X2!gqAu*i)8R+@nyi<`s>5(t-ect7|ZTR{wc=t0d zeE1VI1?Ty(j~zFje0Dm|FZ<&BCktp$_A@frEL>*x|DDE$!`vYmF(e}}28LwBkc=3> zH#a0BpcRJfh#?szlV%~KmMWvKXwDNh0fW&Nd}j&P$TD06Lcn7*u1zbgAF|d zqw_zV;t~vw4-@G7A6_cud1;A6#(?X}<{dba$4kpAcxi!&0?uciU2KCYLR&LVE#<|< z6QS&-V<+wZd6cwDf;|f$Kiq!q-FZ|=VLjx(66BKR&Q!CDzgSkY#PY2RS5|(Fv43qZ6<`qmx+u-W@WS@>m2)jLwUW8`%xaenuxjdyh_n^%|W7>N7e4>^nLE z>}PZe&|aexz&@iBu)d>{sL?sU>&13-Qo(kkQv_H?rvR`Wog&0$bPj}LGdh9NXLJJA zXLJ&)-@8KwQ=TDsB1Gp&xco8SB1o zdA}GjO&I-{PNMG|gRe1}N8_yUq32JO>!1HCpS3YR=c%_qEqocO)chxY!nq=T@rebQ zHy^^+lvvk1+(irWT@O=5mUq$+eExo(UwjyL&GE^tAQrw3RcgxS`41cUmv;iO>trcW z<0CjGawVF`K`In|RQx7X=H1Od9mQS^FbA)oAWFSANoQ=B*m3-26Ac_~^ENX!$jVTaO=LorWFaM}EZK zAj_B#hlb@6t`DcYYJK&9DAG8z2{mbnIr%%>1aV#Ndyjrr%lS)zE`zwvn-7f8{gA_r zlt6)8hvJ7kj@xmE9W3n3FpjHg;$++i18bj*2d3tX1INvHAJ85HH{6DW6}?x60%dVG z-_eT#_1N*(VX5Ec^nrJ|?>qW=Eho1tAnJEa^@`la1P#GNLBoOrN--=iF_v8JcJ!z~ z+r7O%K?C4`f(Cg53mR6@Um<81{iO;T)-{(bXy}dq6@rGye}$mgPO{=s1q~yAeIEfqDxX5oVPfOwfqXzJjI?x+G{U zyn728%IHtf2=G3FhQL-q^V_F-MOkBlhG3$gVS)T4L34?5-*QPp3$)!_e}V?U0R;{6 z1{O4|qQ64WF#1asG^}ebSA5Vgy;RHd`KZ+n| z=;<**^LyVw(0Kg0^~e&zqM_39g5U9XKmEA^!FnMO37T_-9Jl*kJ6O1RIL9@R?ZO0& zh?!`HMKk9!8#_eIu%b(X#=^URE{}&>k1nyXXo!zh(EP@! zWHB-xqQcUbB`x9cVqk|e=cg1s4*JT7s>z&I z%A*>5srfEG!^+_J+`Nx|ygK{-H^9JwZxfI6ZaIVkT0UAgs|}k&wmr|kx(hWw&&Yf7 z*oSV#@@Y%PCph|h9OTY?C!OitLM#5=FW<7fi>5a}zRv$=EeQigd>&aQYvGvQYMfVU>!U{DlrG{7i6o&5l# zSa$XUtmc9PZ2Qmv8yaA*@dFJo3K?fVz$m<&{Q%=GIKa4}0X8(i`X68j$VT7EL%`X5 zvq6F2Y<*;$4aW9g#NhhJU{QpzDL+%c`UmN(eoq6IAY)j}I0QgoT9?lE_gf9_U*rVI z<$9W=ADIF6eZT(`xWSaxUz2{j()#NjDosVr(n>!v724MC{{+sY&&I27Qr8qIirVLz zYm16jw$I1CsfLAk6plLG-Sys`+wc7L1M%dLD{nF%vQQa`N>*h0M^@hE4Dpmciu&pLYAnd z*R1DcQV?<+ z1@3*G*ngFKpLYGXxDRHJ%0+I`F#BNL>Nns^%YuD2iG+KH6R_I5x^JF60q4O9TQmOW zZw9^n<03atZ1=%B!eF+QTqIw5j(s*Q(1Qn%9(3{ylcGH5@(?_fL|H~;~u4mC1`I+_gais)MW!a!X7xz^ghacRd~IPSf^ zRXAH3;gx%EIa;vMb-}lIPwiH&eYtZt9zOo@Dh0=>|JJt${y!ZYY%SlBIszSQfP4^M z1exI{t_lA*14mbbaJA;PafW8((x1Gk%|KU!lxsw?N!HZUY>*j@gePy3ak3_HqXP9~ zlcMQlbCUu8nwt$xO$Pi(#Ahf?fB50(Dr@R&wq_qCFzN&lRSRgRIqtMXjFOu$a8DtZ z>PAd(waGOg*Vt@e7vy3DbX&4bKvr-jKoy)3tSQsdCK-IHG6VXFaBNz_F_42D+@0&r zJ@Dg4D5^_%m1JDk#E*_WsTAI+{wdOCHhhE<&q{As@Uc?wJDwYm!jm&iCj2wf>vs9` z92amp6d*iG_B`FBdP=w^_Ud)v)!ThN@Jxrt3)q&eA{IpU3U=WYd|(|RelQ*N@RfF& zIEgPJGFTP*VQbmHB5_1Dg;(J3s#EsXzhcw_gkXf2o8Eh;eGh1;x08Fn|~rvM(UUMTCLHI3fI^7$>BG z#JEtxz+zm;A5e@_wpEP(f0gPZ#)Z}mB*vxO{>8YEJFpm+dahqFE@TcU#)a&G#JHv8 ze#N*Yw|_A%WDg+5h0NY!T!0D^vx&2cae@y{jEfY55aUE)BOe8;X#g>9i`$R9wHh*Z zhy#jo!n#;7Zp*QMF-{m47UKfO1;zMpywc9L+StDRf9&!g#+RG%)s=LPi+QW#(>%cr zV2YY54l%y`&!RQ{pPRfEowl`QC4>iIsdQwtyK~%U3vCp=klnWY=O5l*BI7FmO$z>z z8~Y-pKlXzhWIZ9qRb;)XsAE+$TgTIm z;*J_roEHB$6-QT$B?t5xAgB0gh4n*Q*^(N(7Hw8=h-Y^n>9p-7-%n_rGZjFPn&xTlaybt5LY z+T^N`Yt*UO1-Tdj-Ii<3K@HCie1m;pMyUHsG0#O%|{%uek-0y@XwO2_N4=h@a0!J(xXC>>$Y5 ztB4F%g?`vFd)&6@Gtx`B>NSqT5~p=978B%UUy(g7zL#OG#U6WKvu^5wJzhYv=>zG_ zr0)ExoDpPjX?21X{W${Pn(fP- z1wS?m#Fa2Hm@x0dq_PVWQ%p@2O(qL7AFhj77%*QwjT%@8F{vbq9*z~-iAk%$&`iS) zb3!zh>VcGdzkDdpv)IJ$l5qpsYs!AwbAfiHAg=PVZVWTp5 z#|#`WS+Q(dFGt6dA3(ruW-SG0T!T3UDr3S^6#NG8E}&f~o~%Au6CQX!IMhW}9M%?r zkJKlibiia^;6e=uW5uv1MlGRiBt#=nMg0KbB(#8XJqZFZRif2^hT$K^3Wgrwv3R&N za+~noA|w;(hiwK(qJ3C0485WEf`;kPdx2qs_i5<8uzfF7q%W|YgqZ@+7i55tzEHtR zQ9)m5RdN;dIrIhW2Qn96Oh8|#IJ(83PDQ4Piu8p_q3uSBQz^*SA&`@{8)>adrovmS zg1!J5=?gMN@aG78Yc})+_%S;LSHgt8AiG7T?Skn9<4K`4S(rgz5DOJ7s?(@}g%HLN zQS@-E&`y|DDl~)a73d4H2&gV-5cCBRIcR%SsE1tD1~M%fVq~g8U(gNAI_L{#%xGKD zP_&7V&?5>WbJ6|;0@RQ!FzN@Y1TI>V`pnc*nLr>TS|IAEpQu0~3&m5O3^-I9p)~>} z+6gue+IB{T0?^2A@F09V7&0KKTF@6-(ihY*sE&j76cvM-P%Ut!`pCeF*o!340z&Bm z5f#!ZGz(Y^8<2d*WZ zVvj>zWW`}^5pbkF0VVVWLy~jU0O$*_U{8!%LfJ@&wk8wm%q27ou|{0}*ACX$3n4R8EF2p(tkd&?CTzL|vOWOqNNRQi;S~VGs-(ND9G5V)l;} z8K{aTg`t&k1p{zmMT%%LgCBR2ft%5X%~Ul0Gn11l!`eiqHkf1ePq4pn#|$xyU$M!7 zmMmuAB7^FSTp@#4vWyg1RVa+{wE&R$O8_by+(Z)T4oL%a1(LA04Csm#l4cotgNELq zp*P6#!a#Ul${VC29mKRbbP(ARq;^!KgP^`xf@~AA98{#?m?@!>sXCat6G1Jcz@d~? zDnfz|B2tA4b{(_Qw4{TGrNAE|4T{r2B^pr7jDrp$8Wrgv6?6~~h#M7x!l+izK@3Pb z6*`E)NJ+yOut$UrLN%a?Q6I*V?q#a;fWeT2U4be>MKA`?K~w}W#;=abB9(8CPL`d3 z4jn}8WcX+@)e&}_XfO%|5Fcg>3KY;mKyy~17|@{zkmHsaSSlD?R8bP5#0WY_1s#NJ zN7XSZg#D3ei3U>N;6E8>WJ?eU`ij|^WWx$nEfN(f#;V2;$Sa7hfI=X%3q~i(By$b6 z56W^tIK*{Hjo-}Z&q@A2B_vC3|aOQA{BmHFo z$PCehjFL#Rb`UlFBO3%T0~byi7~8N)$nK(lWM_y@s9(zMY$98W2->0S1zS!|r8F@< zXrTcPHikg9CmBRoMzCg+eS}KLxi*GRERbMkr5s*aW)$>?!CGX4@v{MjlTdfYgC+53 z1s>FV!XQh5vhml1iW8U#vM{OxYjYD;8Z9WOa5Fs}1FUt1z>K68V8Fnj!oZI?dT+#*!1B=@u!U$#g`63nU@!JY6a1aTp$qx==K`w@-GqFF&_x00 zN@g@sJuT0NZoKe6m;r^IGpHKbgk(#*0~|sRSPs`ERhz0zkE6*pt5wLNJX92~ylBt` z6QzP44ON+PMZYOt0s|KV-eggtznnp&FvJ4TK}kY~-QG<7D}c!nM=g;zwNqcQ3aEiD zz{$rb<}iL2%8`17*|g|=#*Y;mBa9Y2FB;GCrcNpeIq?K=Mk}b@43fdjTygwoka-L= z=3wjl0}@9^(Fh!R0jUS66WJKCyk6AbLoXn`l|wHez30E>3y38_=5vDvB>PReC;Nom zGZzvxpbbE#f(8_IEi|ACwgCMj3ka%3MH-NFHQ7XD_0d0JJrKFtU(6-fAK6UMfMgrO z;30zrrNHu`H2A`0h&u%tF95*TW}7Wnu}VM#3iw0<4XEM>6B>}LjQ%iKi)=7{CJOo| z{NWCNctw$B9fMaCAhJ_L447pM12(9N6r9**?HL;sRG17>>2YXD14>z~LKfwz zbgGix*22^!`GegI$0WMT%Kn5nCD78ec zZC6ppQ3asqR05p5reY4@7tWD-g;~_-U&aqKR3A24@Vscq$hDGJ5E@WR8W7c|vVjW? z2m;oFDkQ)PKq$ZdepEIZ(BSwkss3EiILTmZt_GM7&7R}X#6yRi33HB-B!n$y!1Y$+3~<77jT2`5$_rK6S#tTi^%3Z@(mG8>fRs2)`if6Ov#BJ+vb zMp&MLfl9T*@-|_R;mImMg?6)6u;oR(2Zl}&#f5|Wk{A*)wc3E%qekSc#7Z@6R8)>! zeavG>MtN5=E8I`Oq6aDX0A08aVuDr!7q%DHY-LR_Ly4Cy)>#P)yqg$OCsCnb@4m>O zX3NP`LwOTM2%adltf`6f60jKt8V44lYq1O2V2$nD;7Yhyx+Q`UL;!n~x*N}4!C|Q! zO(Vu^`!>r*PvRk>M^G*qjw1lamYLVd~*l>gk|Dz4ATm_hx{u2h7Foa9xq7?F2 z3HA^@K@2YD5RDZ>%_5S;=i=GFrtv;=#?s1h|7eUAn}OZ3J%D`9~*txaS5AC-%Op(T1F1HcxMsfKbb zMhKoLwG4v+T7s|{20X{C1_C;=*`hK`AL<{J0C%L8w1nu!gq9#f7YpQQ8ZkD4v5=3R zBp)fr1a~E^LbIkqOHe0~4GBw7%b^$W3#~v_6}22arJ^*soymEPd}s-kjh5g^X~4kx zfBlE4Q?;NEnNlQ|kDwvVEz3%o!bAD2GAK2wCs3 zt%~$4!52LPR%EK7@vOFV`VOQgq%u)2fs_EW1CQ(|S(79F1R*WRJsG0}lN{9`>O*Pc+S9iL^{D7<<^!K_)1Eq%sUtZA{gy_{<1rh!VCz!1iQV zXdICMiv}7v^}!O2bWi(CEH5=-fe2J=jIt#24?A-i27}BMJByGv^sZybbg%)0$2at@ z554O{@A@BDCNKj@((A(Nc?XBhcN`2GNw2d6Y#SImu*t|2BvZ$t*I^UERwSbdJ1>QE zAeo%Z27c&1AX8lxsvdIHN;^^Im;y0E z)_ZJADMzU2Nc_^3HaQX@S@+D#dqOG`?G;D~KszPL@|9_!*U4%|&p_w0#dt;ly-xNo zHI7x8ZLI_W9kzj9$1hZDADZ(-L*tA7Xz8EyI@_tqEc=C)GHB$GUbo_dornmH&9IqX zrv-Y#62}R!XrM``Ojomdd!ogR1@yYLKw7{>5_+9Vg92wsJH75X6y!(@_hQDWtD%rj zgXjbn7K)9Zp;$?X6gJcegX{zrU5bq#I>gCdy`dZQ;ZP@BTqoEDsbazWDJ;)_{U(#^ zDnBEH$N8`G{(~^I|N05_Vy~~C5UBUpPefR+ub+s}zF$A-gAQwi9pbg{?)~)>%INRw zCjz|B*G~u>MK+Fw>!qi<4Gs=ifv7=H%ky?CKy@#%S<@tDTd{PkRniI*;%U@%0<#?du|CcN%k!;%xu!QA>Nhdj#tJ?ujt#yLZ&^r(18?dE1U1x8HK>jW_SS`L-Rm?%r{S zQYj)@U&o_{ii-JF3+Ixq8&9j%bXk>9A(aKGAD%tB5t@iI>`k@+t>G~!PD8p#l^$P-P76A)fGQ(t{zSj zp5=8uYH)FKc6M@caCUKac6Fu%ZUi(6@_=O-2S?6HCUfDO6gY>6;cJuY?8Ik7N*(MI8524(NFDB{M6?EabkyLu zgt+*4bwa!*AyN~ML~N`&Rw71hiP*##b&8sPB4ZPyQ^G#kn>XKh(=FHU*nZp1J8s;5({($p@cJ2DDwHvO!Zu#cb*4*jE z(@Tn{7nhb4O)n}bEh;T8E-kX=y2;!fy^DUhX0*!8XYyp2@DERh6rf%N_&TT z2L$>A4f7524)*s6^%)uLZIc_M@(vFQ4D<~S2nq-c3=Ikh^B0RC+KjN+)cDlEL|&B= zpQ4S*&?d%1DpS)bcXY-meL+sz*z}x&-0>;;@tOH4mQXWC4YBdDQSni+nxKTZ==h*` zO^7B`ZKZW_cJhsi8s-cG!^zc8t@Of;H8;R@WMpaz#HG*h5rOJ>jmmd~yEQjGTAPs` zAD@w#6qBw?RVQbrCO}9@waU`#vh^AI>}-8{R(6&?OQ*}svgYRM^t#MkU5-wd%j@(R zd8sK`I%{rLMp{NjI-kjB@Vt&s&qz&6w4u$+%t+5jPfyFxWu&EN040M@v(l!drlqB8 z)95!XMazCIxn~UFiJ_4}%0PeRNT>n70C_-{U$8{mW*C|16#?}l!xp>__y}!!0^%jm z&DX~Po;f+^BXe+fSNKVEu*V`iWu7O~JV$yadJgkU_a5mP<7xBw;??o7aq%(naWU~R zu?aEpnwUg!}-9b6n-9l#InUHrrOF*zO}9^of0P9r>my`31`#nWA; zaB+3yTpc|ed;|SPxcayZa~n3y%bAlq!SM`=Zn?XM8!EMtyWVG{yNvVm^6_#S85b;f zMjPCRMJnaa!G7+X2cn0K3{!%YLgwrf5a=P}e0=1HWaR1+5EP1LxjH%p2L`)v!{rJ` zS9g5FZP;*U%u;+Xd;(B2Uliqve=eM}98tiWePGsjI5>l;6UX^FJ8^yAWUQt4-DL=?STcd`29fo{|=)%ZyJ? z(q+f#)3Ws1th892ldBBo0)~{EBkV{g2S;c8xH&n>95;EmIJ#q`$$VU#+!W3paxYgG zPnnyigO97{Fb7W`Z(pAg!(2Sw$i{W|a3+J--P6O#!_D2(&D#SR-kvT#?(W_$!@Mx& zJbc_nc)9z!`wVj#Io#XVZTJYU5#GuG=aG=Z&aSXwVFo+9xVkvGKpr`}csjevU5_}q zy1TfcRA)DLS2t%jZ+ABjcP|%rUr!I2$42?b-Y%ZQynG$~X=FJ&lFjVGxyi7MkCE%f zp=E9gxtk1cE+sfOg#vc72j?kwa+79rJrq8Uo?hPW4&DmyVUELGd`CJD_ZZ>p z>VtQ{&1cvMcW;m3K0blN{DT5Q{UXCvp^oA4s>r}db+kG%Aw4NPJuWUWH6u+M9Tb(Q z^^Me~CkCfxBqpV&#fK;J8g-6FtBlW1=e4@AnYp>6M{D!=QCZ`1#^h#=pE!By%sJEM z&YD$GQCTxtpQzVnN5|%mPD~t?JZ7wZYD&)J?6C!-Cnsf&%^N#0wRpzZX=Acyq!iDY zF>&VPg(W3(DyqvX7FU)R*HkZBaz*|8dGkx=%qp3?uw>4HiS;!z8m2eYmDbnHYnW1B zJF~pLw4uJTe%|!U<}O-#*|H@KjmuXqyP|Q$(kqrOp1Ww#iYr!JdyV1hW$VbA-?C}L zRadOrbPX*1jaM&kG;G?8JHx8QtLSd?HS1SRUb}wNmQ9=1U$ye`)s2P?Yu2t@RlRP` z?6DJarj<;bGq-fg_{rlZ6wR15Yhr0>j(*bYSw*wT%Eo6+S~#P0_Uww%+|be~(-zF0 zTC!l)w2}o46%}PA^Cr)Zi47PT85ym`4oMoE?JYBl$Mr|stwa7@mZ-k+5DIsU2e|Uyy9^wv9VzZDRJ=$;c-!kQ3lfA5JJ!qUe#V>zQ=WzfcA+UxeHEB?*H_m! z)XHlAS%v(%`ik26g^R1^RMl);yLR>3HEUL{xT8PxS>AGvLUwzHmHCHySFf?A)xPDdR z@|$*S-n@RtjW=xHx?|Uko6EOusk-5&+jnfe{JQe@)|>P1mlu;+jo{Yu2sbwrTm=t(!Jpeck5GSFhW&rE%lB z4Of-W9IK?X7}x2=xK1xAnO-!#m?mH)MWxeFA>2f4SS(#1W*+s)C{&D|N|6XMUq*%30;#lg$d&BMvf$;rpf)6r#^hliJ&hnu6j zH!y}fdHT8gju;u}>FDO`KV0F$)a8BGycAv=-2<|7wtoKKiYi0cVcXyxYtoD~fw(z35{WCtNd#dw}Dks5xCWhRms zDG(;omu5r;d{&AKNG8$`d+)wy_dR#*-Mw$$?K^kez3-O0?!5QjeGl$^VDAHW-y^^0 zpzI-?#69yYId0z6b8V`@Z|{x%a+(_e82=!sDW&qtvmO z;>5&+Mn}ZOMr)$*PpyuL)kLYIqcw4{Fgl|(vGGxHacYe^E-Ee|AwE%-_(Do@Qfg#$ zKzL|mctAvWaF}0IWI%)}JaS|drb~fQ(E(9N_(w+vMTSJH{W0;1@{f%Tj1G;{1SCSR zL?_1vCI^I#2$6+sh7U}T6gJRnf%8xtB7?iUy_GCC|Y zYIta*e@s|pY(PXzG^!H|TNcx)a23jr3W>y=E?A|C2vY#|2@YujaU!O3{#Jod%0z$o2!-t3Y zgpBmTk7`)>NMF^k&>-({?;s@n{KI@ABYYx928NGRhx)67!&Lq;fe}%3KQdaaj0sZ5 z2gRwPHNnxUi0DvtOmujZDlSr;5Sb7cr&XsVC8hYM>f+Pmllc@rHBGP6>XR}ub^0uQ zT5?KOPBgE}(=N=;Cp#)9HzqYl zuT33Sn3XfCaP;V${0S4Z(4LvOdVQ`gJ3Bo$SD%-im7|9XEk7$WL!X&3x*&IS!KmEK z{G7b}?EG9Uo=IWe=)$~ldHJJ9j~g|1TxymsEh{fe2TL*o)84%7!nDlMxuf;E(K=l| zUofUHe|+wkd|h7F=)&B*tb)8zWAew1o;Z5U*s+BLg}J$fIqB%htjxp=O;%=FQkp(p zpO%JlQ&O|@a`ZV_*`w0+i8*FDp%(AAaG>R$bgZ`z<{8U{y~8Ov3`+ZktxyPNim7(s({dl zNVQ*Zl$&3S(ht&FhB=TT-ZLpUEXL2@*U6W2k@*1F31uPk!eKyeqinvL+=UBv9~l@H zq4tl`gbfRgh~YSI=i!*fVHU@^yTmyN!TH2^jr4}1;v9T9CohGMmuIZX(=#yM)7x`6{znCcdIk+sc}9B0d4>86_X-d2 z_EM@nhx>RA9|qJBu$cY5y}U<`@bL-96hIlDpiYR5V?T-TnZzWhY3{FyO^l6Ch)dGM zC&eWtXp&SC8i`Nq-!(NGV)Zx!@@@>!-lCwj))u{8azD8FED&q zWRP#vu+U&%wO>&9aCL|>W_U=rkJ?|U9;Oab#t)B-3XWHfQ2QqbskCa95>hlcC{h(1 z3U5_lRCsu(GCE2X5fl~}9HWX-hQ>xlMu)+f5D^_58XV^z8Lxu1mmC=w7MB<|0^WrQ zTzB2;KmQ56Cw`tIh3lR=arggW?>)fdy3RAt&(?PBmG7yapyQW{r+8F_Q=MJJNHs(+?#VRSB8)H=+b-d zjiA_i;eWsG`*+h|go&Tg*$d}zuQsw?q!1q`NR(lkAl7qSB$-~qocwq^$w}UxN={77 z&P`2^Ps~k@PpsZrnO`PlzGNVDn;b`D(l7u9P=}zp!-%VhOyWbgg{I<@KSTl=k4eOd zJ2VRH7J)_dlLdh}7$|xwov;xfL-|7dj0SvypkZUEVI;!vKAV%GP)g#2mH07T0I&i5 z7=lBS0)YU$K(502Fk+>NjzAxS?LnbJLEWOmyN`wnEyT_s79fq5e?g72`olyV8GLS3|P=cdh{F31D$@qHc%auXC0 z6&Fv)Cesli{1b=)mQam4e||}tfpC!L$t2H=NqoRp;F%LZqJ(*1laUAUD<;9q;qWCw zI^iKc!12X$eGS}MyYfE)BqU)fxJ((K zHG)c`lUL!tGemq2A;!I+6H+zPyrXNZF1L(Lr-%`1W8T`;dksJy!I6pVYK>aNmy2k0 zi5$sR7+i$D7!MW6IXrF}mzp#4rNd=8VWEg7Q>o-QDGG%}7ht%6j-&dDTt)C54k1$r zz(`_pODQa#p5XBm6tg#~lNg+0W&WeDS673rr@%22_*2no5|)WCk#kV~TP!MJG~kZY zWQ0*g$WcQG47ak4G^s)?Hj5~Vu%-XOJq4~HjB?(mD8m|w&_Gh|De#xNL#0Ey`~R7z zah!7V^Iv%8$uB(p%o9(3?wO|^e&(r{o_gttSDtz8>DQip<;CY-dF9p5fAPyNz4+2g z&%gS`=U)2Kt8cva`pa+p`B(nzwb%aqE3bd)Yk&RbU%c7e(b?46(bL%4gzxUw?)Lun zfsTRZp7y@Z*3Py>+rY@gKwsD3U{7ybB5|buXdlA#L~r9TLi4j{6UUFo0^v|JKO-Y0 zoaf!VC7zR3zIpq;?ArbLJNF(b+t=83q_wBHwxy$|yREUW?cmYQmd?X%?OnC?echwy zPLHiDO2rb5*r4IbxN5mauQVB58efJvD;z9|dR(zBdnyheD6XnK*4NoN&~U7)_CQB> zV{2Pob8|z>XmWmfctRo9S`0e1M5a<`RTjBJYx9^R)5#T>KXKHR|Z@YftnZ9qa5!boCE) z_YEaG$A-p_FU*c7mItRgMzHT5jNzV2jwgpFFP&dL_0Z77OmZwaFfouEnHn9N>gw<4 z>*yG1og7UJ_V${o~2ReHCj`epPP4pgXX>V>GOC*jDjSbFWL&Gzp$_z zI@;aa)HBdD+_$jYJ1{vlnpwEDZ1ev8`Q^K-qE(xY9H^*|`CYf!qUyR){dxox1Uxn;0zM}L1$Q~PjxccP~!IXIl?Y3%QBoJ~v&FJF7` z+|s4yy1E*GIY%0s>yES@ZbU^o(ooa6r@W@=P+Mz9bE2=SwP&!QXSi!*=sZ)k3&t5n^KG@ecI5{-f*WTIJhZjX>vaNA=aAIt>cVY?v z&GO=j#o?*s-27l?|LDy4Y~P9GZ0}I}{K=WsTaP~V=(YP7j*p!K9O>@r>`!*} zC);NyM#dMGXHTD`oE)dr&Mz)6%_f$X=aVBH6H|lT<6|c$7iSg+FE5T?{lbe6-+%SO z%+lQQ(#nbHfw|Qrni_>$*$45t6M<+%bkI(iehDT@m7UnzW zr<1dtvt#39E5md1u9{SJ&*=TVLJY*aLK|t*3lnqP?yEP-oYnecdC?%V!tn7fYf)zIazb{f_qbu0&T`aBthbwq(sfXTy=|w&2KUZ_ntE zj3TnS)d7#i?^9S*A$v}7Moz_^19f}z4%Jp}sy()^=5XuL0|(mLYMNUUJsqv>JuRKx zwf;@bN82Vwx_T$pn$6a)e{=JZ)2y|k{NgQa2Al4H@ zke%3Z>Tj-Hx_Ir+36EA$w|C*#>c1U$H(#N3VYmL(r@i^9+`V)*|2&Og)Y2w>ez?FhE~>iX|;I5OHJVH8+%*8_CJd$}dO_ zWCX0iw6NRe@Wm|tte7R}PWSp;@iendLeRt%ipPamCMBr%S^}QTkUupg-5)PVMRQe} zo067YP*9qin^O``wYZ(yXw((=d!3Q&D?5{bNO z7Qv9xXra`QDd);C6O1%VfLymX=1lSV+XipRtxQ7C7;5eiFJgUYgN<4EVJ7# zCPaK8*`G#tUCpHn#Uee|Ads;nVxiOEHhY}%AX}={7(p)J8byGE8CZoW=UB{CiNGRb zvpG}-3*BWJ;(3nO1|ZFl=?&O}X0y`}a@tHOMthpZniBAYTq>P`rV(kmQaV@3SE{5s z4M(O_aCBO(00X9c#9c)szLi;;Q&L!5l3$WvoS&Xikdc<36F>#dD@aXEQD%ix9X6@L z;|TZ+DM6Xtlw#3GRAvfc(zC_Vf}(;=rTKZ$(&FNblALVZ?5y;xKu$r1FP`ZS`okt| zW=^`rtu_SRTDi`wH#wy#9)&^dq!I!tUCt}c%E&4PqL!Z@50s{*rl2~MI} z9A3ZP6%4A~=?0@M>{U7~O07ev1Sk+R8dIrSo&tmHh_b?&nc2CK^qkaiJSUJB$;u35 zr=~h0r4DbzAM}Nl23N?Yj(`p$Rpj}cnNo+q=X7{=VFPDRBSDK9gWkkf0JQJ{J7_p6jnU&a znWHA3OW^Y;H5>_Dt3+Iip>%|-YzdX2aT*l@Da%NR=@yM21K?z|3zjMIu0+e0*#YMX zHAaDl#gp*~f=`a(zs)2I>#+}^+$|O8Y(g<1!w{yC0C9jpyoXvqL0TS$nkC}us2nLp z`Y$TJM2RTejyM@%B87o15rlxnBh1*|#~Lz31c#9L^RPswu*lT1YzgYEL?Gbvr4m_4 zER;sXQk7gL6Bz|O50@+Ey95%kgrVkhWqhvG%0>K+uoxKam-5oH;$Wo2F}55I2V6l9 zUS!dDT6S&$K-kP+Zdwq-aBjdr_E>?|6)@(xoq0J9DWAsVvZ-vVC1}PNs?(k3u*Cq+ zx#Pk7;;g*l!s6n*^v(H&mZIW#M!v`E)Hnm~9Di;=lx1+InN$33xzuVCt36UjEYp{r z8O@4&qyC)Orp*OKrJM3Ii}Lg0MFknr!mQk2T5et-kd+e9+gw0)!)|-XnH5P7aAZy! zFG(r83-V;#19vh$fpyY}Pe2;{z6a&}?)-bOO*-+x23`YXo9>tdQ47RFtpUogN6NW+nG(Qt6TzWQ> zZ{h%zAgLL@QQhvt*N-JYO+;cnlTdkte3pn{DPb(~KHiuF1sriCm@?iTq>$7W3XQC& zACu?~Fb^g13xP)Nh)~peuN3u!cwZOr-0t%EaE!kcVAO!-OcTn0R+5+tR^}+Q?mUyn zlW8SEpkEhcGVTo zjfeIw-M?^X=<1a=CDjn*;b8#6K?RH>WJ>T%f`v;+IK<)uGv!^cAK#L`IhYl1MyTOXZ6JqDRc5WM0fk5 z)TtK|K}AZ$y?>Mbc5Nu?)Y~%rt&biNTRhpxD@vz5yg$=PuD(W`TB|_};;pTZl6e6f;6 zlBMLg8?X|M!_u&rAzN`;-tb@UvTrYaZgjIqVd1ivDv5-^NCAPdXM)MsvR}U16OQmQ zIJy9jprP4Q-9AWTqv;S?t)h@FBebvY>DowT=hbIMBn+!jC@yjv*kTpfGI|;z(;0N_ z9lL9`>Iw<7Q-F~IIz@52hmfHKu*0Gol4pX8R6G9#uY|Uo7J1+Lp71UfQnsgcQUADPE!KPBs(s4ut zT_x1t?%`RPgp^GPXml!#62?Iy1*_xhld(gkjDpgtuEnCRo zXs4TUglHeJKhVhj-+14TGydzB{_3r7{oP-G^LyX<`ZvDu^}qi9xBvRqH{SZrTYvl3 ze|_Vd-}~0rzWI0G{@R;g{mVDL@mH^W`Sn*`|MFk_*_Yn<${R1g{_4vwyzt_yFFybB ztF!ah9(?@Ng_|#2zjE<`YmePJTXN&d;}1P_<=W~O9=kkq|0CB|mlqaVuihL<3|0^I zCnkFaCfgbSXY}?Zh7zq^1FNeK+<$&)_3G7?)5otb4UJBeTpyS_ab^76^o`4xk1yVQ z=*;P*)x_!RiPnK*%^kh zIdyt@U~Firzp<;X;%INj02zw6w|BJlwzN0aH#gLboV+n}ZSILju3eouJ$3EjE0;=c zU3~cB+2q8^`5T{q=>DtMt}o8b9-lsgs|5_IcVwimKhf6FBd;}$h%hdf>*nFI;~3)?*Jp^z2K|KK#^+ zFMe)i{M3c%*|Cv<`Q}9bbo1Qtg^7vz+1csw6UP(JzWjxoz@Bejx_RO1mB(*fzgB$h z+Re|My?OEar7MrU^u!~df9Xq)ug+Y$GC4OgKD^vM(AJ-voR}LMPAm*fPERdQzwp}i zGY>!h{6iNPuCAWH`N+-lC0DOL`tZ`#ORJZzKKuHE-lPoym7hY+J(!5V*oE7c>2ct%;Kfbzc4pDHMg>IajQO-J3{^4))f}4s;GTCOUdjv7$cZx#o`)*n)U}5% zFO*zay86KRYd6kbyS^}g=Jd+TPpqC=xOxh=yuP=u2_W`RcXwZN!|-_LV0&k4-viG* zHru;=<;K$D!t`8ner&qr>fq?&+{xwn)2l1X%O@9Ke0=%x#VZ$1&rgl@bhNkD0N?BG z=m*r>(mU4EJ^0dd&o5lL`RMA>>G9r^g9BZ|C0CnTh8O0hmzS3wymWr`(HEY&dG+DP zKX(lptnu;Q{^5y*;qmdI@v%YdW_ohs+1DRAb7S@L)WXsVkeXvtL!}qT%E#xX=T9c5 zFPyn?`RY^8Jb32*8_$0J#^TDEcT?% z?A+M&iOpwcW~XNoOJ~k3T{w05!h=t|^vIOD88LlP9Og zmO&GH^m7k9a&!6Gi3;_?CI-^ zGvf;<6DMcpPTstHrR3&?2hU6`o#{R~y*N2@>I;uwT}bo{B-@)B_U*0e={VNYcA#UR zt+lbOtsS)CqiapwdOBN#HXD#1LYuo_A3~UWFdu&31NeS-{yi`cq0I)0htTFOdIvw^ z#iTO%@jN`(KAwjA>_3htle5X>()8>Sn4-z~sp-k(+4&P^PMuyjb>_qZmlSTI?I;n97Cs#tBppk4Y6-bZ!%dekdg$QdUO!5A~^v26LPqgW;ZGc!Y*T> zF(-rOJ2V#7ct=AJI=>+d<(?-y=+(x9P#k2)ycF*^v6>rW2`CZ`c;<4mUMc3=a|}pE z>{CnS@`x6lb_qv7=L{8O;7b@#lvp1EiaHBH+qA?NNQ;@pC$JCxeS@!Xtde| zmK10T96746N^7wzn1s)!r%H7Zw_a&>Wkgc4{XU;w>-NUoW~IiH;m#^50OeRN6B2wb zn%%AfU;WGQc1KtzB-*bb*>0_c~JjVWT4I_G-M2d@y#hLDwJy zb}I3ahL;7*i};nquO&CLrpNd$MJ-aXbSbXPk~A}rPp?hq_IbRPpbtPyT9zE-dxUK$ zEMpqUXzc^KSS&!$!XUnj-W8KWm;1aCkSJtQi&1N^s-5ywmpfui3!#fCz*QDNfPj7^ z*>9$>{s%!*Dy?#kh(lGmm2{cZKr@PsHlE%KuE5(u#4t25XmMt(o~6fFH;>1nLjXX~ zG~{AzkWZ2E6og6V*9)aQzDS`Z)O>~r$AZ{Fh+Tcmz{kH*d9c-_&?H8UoNEAHM3G4( zqBUN@$0BsAKNKOtWup*<{~JUfxDcpRG~%BH1e?y|(zr^gkzr7?)pEVg!efbeQi+JK zBWsI^yoeu4LAWG$`Jvm)Uegl)h6#>1h4^QsKq(Z62!-6_H)n^WVq=;ns#lmaT3y&5 zBg;$JD1S#KWl^9}_|T%{%E_HmI5=t)XN$$a2?>owpz~$>G)|sRC6n0#7NgY@&cO;F z0S6@S+Q;Zqlc6s*$RJ1A@8H_vQ@R!`P&iTxVKW82Y8PE6OY-@?!ie)GgSDm_l_h4Fpx};8PI9NI}^r-odWvN|sQ@ zmTCEJ3twgsVK)lw=r(yV396SylY)&xr!yHW9V91sazGowxg#z^B^Jo3Off+tUqw{n zhjfC?Wr_?emYj|2AmY(95I8V!e5j8Q`2blPC(d%TETLJ%;j_hDDR?M+rCJP03!lal zO7$k4RiRWU#CVGtggS%XEad9=T=E!sR45#%LZRGbcPLZ6DZY^1K<(LgUavtw11HxFgo;SYtxSp5k%GB+?#&? zZ?_YK^F1C?7p<@G{y*jug!(>!&4Ki*BjNTxdm^Io-Ht|b7P%i=x zCN>%+exc%sph89@nQfXtRS=+H;V>LkK8$zN`)>8 zx-2SHqOi&FSkNgMBJKZV(QNE>htn)Bqchq#Zt*|94l7AH4L%SDtzOrPrQ)^-Hh5 z`ohaEzVggVPrv;1GoSzblTSVM`DdPfhVslfb@17zKlk(#pL_I?2OdH6botheM^7RK zyZ-P)*Y3ag!0Po26N8A-I@?kVVwp%H;sM>4^2I`qm?zl1AH3Vpt}UB4MGLfUn+~P@iA^`_ND-omy96s2~Z2qo_J$ov)?Tqi(lo541 zT(O9nlxC`oR-RHJ((+Uap^DB{&@imW=kU<+qR}DGqEpafa|j-kqX(-F9jiNhaQp7! z-Q}CJ%64rp2^DVMR#H%qmXn`X0M>F&UeJ>sblUx~lyHg*V_|-qO=5Selp?J~D;9Jg zuI=e;0{f#bG1A`D*VEdvx1pt>5!#nSbu~xp>#C0)tp)_JYxlmx2li~M*tWeeZ|f#( zwJ^hh0_t?rrIt7#klPP4xEl9PaMysl%4`w}3ixtfi-=q58;?#sf{& z2kQf(c3`Tfsj<7|*wN~?>hi|Mv8BGrx$(Zvv5v8kp^@%^?w*dO z@rj|yk;xt)H3JjqK@WBI^d|-n4DY*2?Y0MeeQJJmKO^F>55vmFqS; z>;_}lY7Y4|CYvu%(>u`6QQNkyVSn?kBTdx}RrN;>9;n#6uVQmmQPs|^`P;Vd2yfk$ zSFpJ_;wdSJf-IgH$_NG1N!?*usxR6+*4Ne7)!){3&Q&V-*w(|W)wr|^4 zdvHtso&yK>?LV@!a!+~j_S`*t?kg|I+fRTFWS`OA6t!Zl5TXXDSO)WS$yAhGq)z&xE9EDP< zwxRK8Jv<}oan<{4TMo4~SM1!=(6|o>RDE^bvBs9_#-qoYkJZ-I96NfX;qbA>Bgc+F zcGXZ_UkBM%V;vM*_=ePKVtHY8czEf=@m(jzlSAF8PhBmo1IeDF{k;$?_4afphX*El zC&$`HyC+84`kQ-NF|W0wZJ?*WuWxj4#MI2p^8E6Nsj1oJrJ0V=vHtdvv1A8$VhGM=kIyWg9zFr=Xyx48%BlDBVrOuI=fsyXMku_dYqHm;gvVC%7dbG8FVW59_BS-{K63cLfnEC!?Af<{N9BE`rJ&su-&eY0TWR^$@@?BIckF=h zsuCKjH33$66=YUB%FE(=x0MAp7nWvJ?64GPczi0WU+-k9d@_MTEfI3mVx63>;#RI)%wr}0DcBT#ZA zghYp-P6Q}29gi#F${2`v7!0n2#S#nF8p+Gw{Vmky#D}Br{_Mh9|Eam)TL;m^ez50X zgZj6|$^O!RTbq7>Kbi6SYkcthI6>_B zr}h5RH)_y-+WS8|>aUNJ{ioMSLd`aU7`y$mi|hR-%JR1sFHE1BJ-+~D-0F#wi|0;V z0oHTv>a{DEuU>%J#D$AjZd|^8xOxBe`!ChbymUJvxFK@9f>g8{FP;`@!uZ6#xEO1azX@%Wsi z1k2@%1tG}E$;r&gOv}!RThh_~MN>jvk1HkBAF#MQo)o9s52>Zz4XK2|1rCnN<<`L6 z0{U6IAs;M;O__P=kcmX{a`JOMu?QqAndn11Y(byTV+gw-h4I-PL6g>Oby^)(t5#{X zn5^J&s7)qAX;IPUqCjCDv_f%E12WIPFHK$K`Rj zY!;W(;f5;82yxG*!jjUGc=6_v;=GbjW-K#5#RFD@#**SP`*rRB0(39*O(v_`V71`G zX)`*FW{1J1Gg$!>7;MEQki0A38+mzrQNKu znXGoHTdA^1Z7P{lWHd>v616qAI4_z8In+2MR#+4d7h#+|D>a%Gi)ZKML%fm^PtDHC zgcKzQ7d0(2Gd(kmA^7xA)D}v2LeP-vhEYgf+~!FEiNddu*_|d!Ae@#ON=uDprsig5 zK`4_Ei(xw%X=!;`xrN}nl@xC(hB{|cZeFwqE;<<+hel;{xs{fHfiD)Qr8->On9Xg^ zu=&IJVW%;W8nTDe;B66$N3wG=^RgkQDlW|^F4=5|a`>yY{$S z1HqdO8;zNq7Hf(rJvHo%;J#Vy@$3{^7!}Ees;W~eby7trHOr}&O06Xl#($f5BZhrcvjFhyrP)be|cgG)& zWo2iX4e0&(GotZuaZXBBEI&O9;-{j5lA;o9thf-}uvke^&Zg4h-1N;w8Q|eS$d;WA zN0Wj~$cOVnV#onqCZ9i+TAbmJ1~O73S*eAa^RkPIazMAqF3ia*%7cE2Quy4alA@wb zr6u`=rNxE$;lje=to(Rpq1L8U>Vtl>C+f+8=E)z1dq{R(UOWQzc`lyKSbBCEd`Plm zS;bkYaR|Pm*}2eFjT3)u%gD`5%k*aE+rtW%IW5Z#_C$I?92|3~stQ4nNy`f5$J62| zu_Ewgvf|m1^ddy4(Bs9hqiA{-Y)VkMVi^vr-Ift@dIAM_gi>;2F>iWyW=bfK9Ze7W zz}tz3quG({a7tD#3pm_<$j~z>bKaPevjX1Gnh%&$Q>p>=9(1mD6I z)5IhSg?>4MF6I(|XMmW&RKvxhQ5h5opF)G8izA{+OcILZ>oSc*ArlICTBAV57l;KO ziAb-o7^Fs-O)699B~GzdBoT^2Mj3mLUUMHkd>RCfjgTSkAdtM zi}*sWU^ti-O3hAB&&Y{q#HTr<`b#kA2)h(vyu;)kqsl9{S>;}-(IK@dRSt>8ERWzlmR3}f zot|AB&o3h16{XoAh&i)!!a>mc!g{*B&7<- zxNl6j2<0*FkJ3t7612zq*UKE31MCS@DjS_>`#Xedg0E1aXgu zlS2HO{=fr>)$gqm1`=@NF%t5*N6QHj|3*2p*?Qs^v_nFFwT5_R!IV6JPc!{udTbzl^q zUY)^0`SsQRbEkP0m^W-Hi^}>HgG=Mkz$76>Nx;r%FqdJ%?u*GK{p8pT5+o-<2iP03 zh&KUHi6PnJ$z?X7fJH?W$NQ21l0aTH7Vuq-j;j*L1#*=}%+(4}^??ttX33&5gM^e$ zu_z`R)LR^&uqqXkCQw=c4032R4RBvTCwM3nY6?SVYv3le5{pYTpJ$?Snxs}z+i)T-rJ3n)7<J0@hl12Gc9+SpnvH1YZs2tMY1(g)8LR^>%p>6@{Vt|MNM3n<OFHWekPFxL3ZxUZoj~Ea6dDhzI@Egz>wtX% z8z(&yfyRN81$rfkpkV^QKbB08d)26gT)9Xh<4Z+ClYo@7a-?#R9NsUmTYy>{jyF)h zvFThcAYPzrd?xrO{Ivt#6i+op;vie6WSZRR87Yp8w1_t|#bfZK*#+ngiDVHCWcYrW z%&9fWEG~)4p*8cD$-P#V7v^j&EXj>-sqklQE!}S4dS6yiq-cvN9{0P8V=;%JIO@y_ zW`q@6GEHHBQP`ND5^_Q4S#+?icWh#sR2U65CE(SFUt|3}B#tmMH9p$Y zPue%)*HC+60#W1ibh5t*1b{D-Mf0<_@7}s|@BUpS+aZG5v48*evWl&R1v~fcs@T4( zJhP~5XXSl+_iimt-&R?=bMMab(k)q=wpXm}RcH-O6n!n6S(xSVMS{UtwAdbsIPxsk41dI`!%Cj8$K^Gf zy)H-4;!1Ueo%V<)5CAQ8ZLcMJb4sd8c9xaiS6s1mXRPqPg7WMfl2e_QU0ND1j%+UR z$8$@I;yH*)@(Uq_2Og1GbPId^upT~=^-%jzj#7^OTOH#3HH*nL=N4GF`~)*#=mOJ8 z(si;PFhV00jx4Zdc?N~wg%?W`{vB&1zZx1y`$>FI8$Vc|+eB&lIXRDXZdv=JG<|Rk z2tqSi_|2h_!I2U8P>v0aB}heCA^|9TXpKUh_c0<;?H9q8$L zo-Dd>YH@LT;rP^veT4m{QTJQlP8bQEuC0B(K|jnFt-HCK=1MC`C$ylPtGi@ zEKCheA0J-+Dp_=3s%^Y^remgMYH+R%;B;0oa&Ts{V_m)0)tFAwp5mrbKsU)QC0CU zSyVz{2&e)&jSue?s(@hdsC>Ax5b$W=QW#vk?9kwFSeRgPDI7Y5oFI7ANwTPj!IAP{ z+Q6qnaLZ+|MI0`hEugXCeZ^#nFaW^fGWmQKlfe~|uP82FatsEWd{eA9Qmfzp)ms&4 z(KaMF= z&b?L0-p*sst@U>LUtgEqkiDG;zbqzuI~ybR=U0>d+oy2>*sHt zy?Nut%`5j`zW>V2E0?d42*LH$E0?ZbUA}VR(xod`FHkN}u6_ur&Xo(7DVIOGa^~`1 zkypmxwmUr*FG#y?ufuEe+TC`W%VxLO-RSB$T{@55?nHQJu{rJdaG33OXzm@S$)XmW z1EyIPt6kx+>tIge&{$1otI}@Mm<<+(+y>MEollKLYq2WLW|LWAwW&<{lVnkw!lF@G z^hTS^q?4OfTB}^6RhraBi&~>pnpHZZUL{kT;2>#IDKvV6T4q#3in>A;b?IDIlgos+ zj@@i@87&SywqrNh>?YEE1#|<8NoTehEG8Q)2F+F+mr-lhpF+`}>Wx@cXRyFl#bnVM zj5><$yO5S@wFU!BE_DX23C;qf6N}!Y(cvWE0HKHSV{Nx^QIVL%2A$X}QCdZ21*}zc z1~{%L3?kTom_>TM*d&n|#0HhfAkyhXIt-qRjEm$Ey8~8(4}K&jkJXxD4%jV0y~}O! zX{>gWSr2y?o!Vf~sSIj^Nn}%69U`mN=oH!3_L`rUlaiASACBzoNCv!4((==DGPASO zQqr?hE%8X);euh7KbYop2ZJF`YS876g?zRP-CUBkrMPfY zdTD8-FgGiT*=Z>ydFhBnvVEKL!&&jtc%(QdH+q#U>Vvfo-rIh=&*y?er`HS(h)?hJ znPJf5^*StW6L2D*)$BF+e5!!m<23>bag*=g57cgp1ndY3=K~l|=)~}#WPgVvFXlS1y=#J}UXYmwbs0QM^8GQmhlZnBndvYz%E*Xh zz~TkUtc-N@0^@KAg^~?HcqmF1{V)TrC>d}qp`=q%KZ=t!qLj4nuK9?jrp4el3SSg_ zq>{FyFh_~O{RHNtY0(JTLwpYtpp(ELB^rT)Pb`&^dI#<(lvGOeHio?5ixLS(Qen3O zI~16YMko)L3|WDmfbnECx$4x{F-2U^q}f zWETs~X7a&(6I2pbm}54|5Ti@5NQ5^hXuuc=Hu6A)At@wQQ0)~cjj=X#CE-4)SxZUv zrcUS=hJ+qLSf~*OgkphCiHIDoC<1{;FOUk&0;|9w&aP1>YYZsDlB>%jG)wJ2LxRBZ!y7Wmwc-t(2D)$Pl;G9z_)|6{ z@{0W_3DRf12!G0kMAjb?-2PK0sLyf{KAjE8{=X>?&L5Kj=6`42oj-Pe0EmCrl?)x8 zb?zSFcAHUB#lZ}k4SxUoO9?0qz3cTmj+71-}PR@m0zbT63#FF z`z!ySRYV6NOK#ktwY(GLo%%oUBP+Ic5P!rMJ$5G?a;7@h)~Cfj3TM`~v_@JtCVvyE zZP=T-cVp^~vw2T+&nFYRqr2}-H0_G+`j01$?WFGf)qA)9^+&Jm+j;8?JNY~ByfwK~ zwe#M}jaAgDU;VVFYVY?~t8PAB#jm>a)fZ8(N^0fTe%e#H|DWb6Z$4c)`gi~E zPbVuYkAC$B|8%SJt?|k;->H4*%innCjlOTa^HgQ!)$jeo-}hEl-g)2+G9MHzcE`D1 z{GEUK@Xac+_@P~uhktmu@|!m+E2~lbk+1KseCX|iyT1SRJ^Oy#f?1XQ-$n6-%Dcsn zR!}Q$|FpYe&)05N+<3a;=o^3cx4#(J_t8$w{=qlC_m!O$6%RdKQStKCii)pKRt$gr z+kg9EMa7-H759plQ_FAvw5z=Q>|5nGo+|(9vlwU;5OV%+9MJR<-pV>QYlv^iSn5GO$bvg-NJYii}dFj&D%OET1YHw8GZa ziKs{mS`S;JMW|%?WXlMSXiOO5AqoW_n?p`;KeZHmU{dsr7cT-+d{d|!DWJ%-78E%J zq+FPd2q|nDMT)!&6wxPT)jAbRs8)+P8l9Y_Fen7Te(o;)O_ITf{`$vs7B&wmI+MbH z>*BqfKG?L85XcD~ovtI4To8HrN{aSw;U6ZcS_*PZnRr|ao57%9f=frDmAjWxA(rX2 zBBf5R6l(NP!06!vcz5ZKl4@85sx?}TTB}?8)2MZK(=z=L&Cf?kD#RWX#!om%iN$2l z$lL*R7H56)e@uJy(7ey{X@ z)?nev;x?0%Z45->cH#pQ>xX}L@Bdi?_LC%T8{h?qBPbY-zywa>qm3JPZv+2Hi-rS0 zHDkeBfsK?-Y}g-Sfc5td;19QOKTOgI7-fP?PL4t|Xf!JL3bead|9{!y{U%ACF6ATg zz;WCZ%E#p7hv-kNRh~bp5yUC~2xEgZGSdm%ZK%jecJ~PE#4nO`7ILOBm~?VEG(q+tjH0|NY~Q8x~MquC@Y@mg#7f9S_rd$|<*BfdV>Y%Zah`Bbw> zPJYsCl2iAZ&5eomW^;XFquKlqCjM}{u!Fh_2L`N=G{b5utM^AtZo?9gBfTC(b8-6AQnPZC!Yy>s>7dRPj$Ou z_7uM(>hPu5qn|95$z@inL<5?W#A1~zRTi`4UOJ~ACXH4DEQzcZ14dR%2KXOav}khg zZrPBfj;7199L~5rGewuJi)6~OK3STgkNAA4_E5kX@xU7yK2%Z1Crh(n*cD3yg)0Q! zrV(QZ$b|LDQl$El>qKS=oa6N}ky(UPUuabC?iZ2-iCn4_i4_vLNUD^Gq;ln_mU_iH zz1Sge0sJ9xW<A+bR%zMH@S)*?xfS}c)?H6pPJN)LR9CE|_DTxfCD z9Qxk5JG{6-M82Ewfh?_(Xhb5T3|_n%iBv07iA09qE4?lf{`tS&M{KZ?Yu-ZZaOaUl zeEu*g(c%6{Rbr7^f<3FnB8e2~(#TtVckk=M;-CNhUSfmh-1cW=`HL6U%F88k9K1xS zkQhV~vONGp*M>U(8 zhb7fwr3Q*giu&K==o6{nuMe;NyH_7A=BLdI?{coG8%Zt5)0g6W;hBqb5Q$|l$dXA! z8;6hP@^`HcaqrhD+JyTr-v4GUx{NGJ(0#Bl`9OIyL-12=#1%M9FGA- zl@|R{m0pLw8QB@T`y5HNQW-`&Wm1hyqmUwN06b{c4}GJxBv)K(E&mVwSZgMU_15w~ z|N2yGN&IeWNlt#!T9Q-uTFZ@z_11EIVxzVE4<`O_Yq`GiA8swzmw)Eg^3%mXLu%k|=9Ynf!ePO3s_+78?UaNcWZyIVtn6XxI_}9?$N3Q@aJbJ-hBStuJfZmHw&U zf?cegci!vWN!fYl-GQB&op+9{uSM3QUqAKEZ%20S9Qihuva0R?nxItOdAGkxQ+20t zeJ!#!{d&0ZUxuryhQCcN{oX<)y%K9yQYwG_Zr=t9!L0oF;%oHRK0aSbtGv^&Ua|q} z{o-GSD=UY;U0MC@h99pL`^meMcYl4SZv%Z`Ret=~Yt+|1evDl9sbW80FV_4|a}~eZ zT|uk315|=i@#}YcH(&>L#YfM6=}XU6Fe>gGUEkT!3VOxuw}&b!hJN%8G9LYM@0lO| z@Ju<-2ue9wY#naklvBRQD!*5xoL>I(w+GA12fzQ;!{uea+*3v?y8{4%Qugb2do~aT zS=pWX^&Qlg(aV1RcA~5-@vU9|^~v8`djnAT^xD+2Up#&LH~)4U>z7si~sS@doQjNf* zl9}0Dt5q!!h=DukAkY=cEmE6U;y^$yaY`|WYgY)}0-a9c607YzgVqq>D&5wA+z>EW z75?~vn6aHnUs+)W{X8o zItV3vxfqt{8mYh_lag8U0(7UD8{(M!^sq`CVmWQh5X<0Wgtfvj%jcxWgqk2TRj-V( zynaSpt_X88tg0MV$j{GJ8q<+gRcKSH4ML+viFBKCzEPq!a?DzVRc6)+99lBfhDL5f zYDYG>ITpBL2pxWhQJHC!gRd3-MWxB6anSj=k?gk z7DBJ3@EktBOBTw>jCunZnW-thxLj@u!RUiWN~>W$fLsdDxRcaOjNkJJu$5pc#=;yV zW8fm61T4=mhbDz{Dsl@jq=cMF=dVZhKwH-0b3iXN#Hi!M35c-4GbUjJM2(2w5k7)a z%|Zl^n35|%`YjG@1!yduL_#&xSt!Gm7T(wXjM0!$zKyFmAGq*eE_Dp+b6GiZ~TU2`3Uz)Hm{cB_1xCt3@IiFw+#qK#SXCNi3&vBY$4^kI3`S6_ z%tnI=S+ERNGm?9mkr2yjhl#A);&r%PMm&&I1}+;?i_$6d1s0cv1gJ=W4ze2qsup`+!D(@P!^99&VAXO%WBP3jeKPJ5N;6MWNMSOF~B`i2)pb&)z#y&r2x%A(pOX*1|39u zCHVkgO*#&igelk7q^@H&4*)>H0|Hq9lCx6q6RY5e@lB<0n0(}zWAYdbKClqI`OB+P_}0Ow6! zigDt{I0|wBxdf*UGz*so1_o3HX*+`RXMoiWx+Mihn|LEbj=%(bj8_K(E-v^6yu=0R zn{@HuaB$IhxN|Hv-qXl!%I4wr@i;6xpHFA!6OwC0xky*AY?96p_C(mOCtkK06xe=K&Blkpev-|#e=#wlYuNW zz<<$h0PJE@;WbPqVh6T|>MIb_nX+W=pZYcHqbmQu?blFmg3fLQzujdI_^oI`UF3wv z<@Z`)@Zhrt{Z43c{rYe);Ma!G#yTM52-zb(cdFVDjRp)hrxUVIWFfcvQe9D}JLU)m zT``wEYLER-_TB?JuItJctOBYKs({Kl=bUpw1r$()Ktv)j0|WsgXOILVm_;#(6iHEu zlof1c$!^PvmMu$myQh2R&-&}H?%&-ry?T0Pb+F}DwWB~e=c_w=mx zl8CBz-?{I-dheY3&OP^>z3qk&I8aR?vn#0axIHnABjStMyfIJE7E1<0K36IeOWHLG z1y=`FBe7P;Re``zq&EqK0)+MB|upnh{#U)9PJt>XGMH#a#p~(cDSz%R7 zS7Y;L>>d|>DIoBDF4QNxY&v)%KDFO%4_X6mec0^=V6F@B(0->X{g#(sA8(*a27(_e^M(E$n7ql!sPL2O^8M+pgpJ_&NvMCl`A;b_2)H)w|ekg~HFao7q90*3I#2 zl|iY?Eej}=0g=}%jB*?87sSTQdUZNE`g+e38tCNM0aV~T!ITo%{qX^kVHknH) z0Gygh9&st$V5*c5oHRvM&=RnDE#MUPdK@N~4?J)lx7G==N}I=Tb$Gl!oeLXwmaB1Hj}LMlKmPav z0dEjht=<3FtT=`c|567F5^kmg^7YzspZR6BB?;B5lIv(HBX_EX$5M% z2p?L$5|C3elSu@#GYFBJjy+ZhxSB}#4+PajSvZKVirENBAs{H?NI4ui+PD%nU(6L3 z#N<3Bo2TTW&RGTGPp$@GLmq-}kU$D9o1-D|Jp7aMWNZ$S=!IN42aJ5!P>NB+ssg`) zT!g*|o?#wAm&j`MScq??S(QF6q%QVyTMzH#xJ#JF&_lBX2nia|Rk5alo4W%M#)D!5W;Gim?VzF-%xT;hAQRjwFr@E6+A6)()a+CQOZi{X5HzyI3O-jLR(97G(5%W8LaYV@6;Mvv6+%9oWRX-OLg4`TWK}A$1gX3{g+eI?K^IS<)GGOE zty&@0X~lAI&4}a@$jgyIEl|qU5QeeIl`64HC09$-3jD8DOSQl$lc_*xAlK?;s3$O} zfbarxM624Onk?@bLn%P%OfyNXA!*c`AWi&QpO0ov&61G*XpH zpacJ{TyK_0Y=3ZR{rN^#oT&9J@m|R21P8ArTk+P?OocFP^3g!az`0k0#Ism*>{jPJT> zpsm%hB7%p8)|SxC0NtmiZ7r{~wghf&UdLv0XhqCzkve6qFxr%1m((3``m`!fBGgcC zUEAITfURvkHNCCl?Vzrj-7z{evt^`r?|A?Ene8+E+o~6)CN_^Q9yoJ;SJ3Q^JM+!L z>U_Rco@?r8YwYL+{@K9D;P!^@4IrwY*)l#eH!!`kuXkbp-hETUGdsqH$44heW)=>d zdFbkSzSXM1l%FXvm#HvrSv;W+B>^y>iWCdHube{ z86DmU!0#QK_RQ=V0w>+$eYW9PFLmG`AUmQ6n3-&+eP2&M(qiMtWzbx~3NQ9liAUL(e>#iq>apYO0&E zjZLj>J?$-)j`bTh=ek^-4be5d{hJ2+x9%7RYTf9*{d>0Up5DE?f8UDQpToo4NM8%IfxaS!r^?bxyF;L@7~zavpKqX!}^wiv9XEG z>EX#e<3j^u)B8rphxdTzaIAN1-|X1Fdn=SQv6AlyG&WZ^MADw-7GIUECf%`aW8bEZ zhQ9vE$&L+U>&K@1w@+*tpV%~g=;+++(Egq&;O!kga^l#7Pv+p-S6KA*joG+Sl3df- z(VULfY}~NE%h}z$d0=R8U|>`4P~YtQroFD2eS4;6r?!J4Z^xeb!vl-`Cyw5KUMyBy zluG!~VuRJ4(z)t$ZOu(>4Vk8Fb1v1|TD58I@Y;c`<9)q*_6}_tI5@d5H#RXeF+DhV zaCFa}U5Ac4xLk+I9bew1NONxp!Gx@9*rQZR!K7&b*$0XnsTRGa&6t) z)@|Ipp}wJIY-Hos_6eMNZpZlC?mauV?i!idGrRZv(T5+rcp|6`2c2Gx$81ix?NLL6 zqrEk_KD)-D?r!dAZ0+gnT-!FVd17R8!`$qVUHxNy8^%zRJU_E>bpM{SSI?Zk48AK( zIg<|e(NIojm0R3;Fq@^;)LEJva!qD`F4){w)6qLKux+G&Y5&bEmfDu?8d0iBQY5b*6NJAwzY9X-@xE>&v^gD=&sS3U8CdE z>!zj_ric3W?ml*82aAgguyU?m%&9C@R%nsmz){&vSoXB(K!Vm--Lgi~vU#$97&M!^ zcWj;BI5@XyPk-(1y*+@a+kJRq9@v=N5-wN6Di;fRSZLK*G!{2-sL~DxYPq|&_V)Dj z4fpkrZP-0Oxpf#siF51cH}*_yT@RARy)%mkis>wRQE3^EF5)p1{8CJgD>WFFKo=Ja zH)TP%g&W~K-BP90qovS<~has{*8AYzwERbr7I z5q+`FuNCE-vBpeQsG&7G)HXWbH$J?uXLjqh+06q3yEk`k9hg{{KfXA3QlV6^R0V{UT$_^yS~&atWa z-tBY4(_kb7^j#kqA{|^o37f?c@i^jAX^8}$FJGXO^IS@!Qe~AZQuSpj6DgZ3VE1dyTNN#Te z0N>EW$l$geo2GZ|8l2fN)!jAI+CQ~<%h=Yztvgq^qyPC{;lGS(!clKJ>WTw+D&UHx zs_JrHU|2nvNLNMD*+i|g21I~*mB(+0dm?;iFlF$YqLGNiYEQVBC?-&&{xqA&Wi9Dk zJngB?B&&0^v1GP7>jc)|tzX-^o?y2UPw zB?t_NfZHF@fu+!AH^m%@B+#O&5wuCyrE7rFmQJQ*=4!n*VfQ)%_8N{eXS7tyv}y#A z*)>s1&KS;w^SEGfzBZGui`3Uq>;5{GtIbwd1Jfv#$R*rfxiaWhrGzGt-;6k%G#%pV z4LmFiya!8(*Mzm1P(B{3kNJJ+bUv4?t50XDQn^?<;K!{sCv^$j5>Gl{;Aym4PXa@m znaL%DO7GTIXKQn{0Q|~UXVM9rhWWI^ zst>cl;)FDjMe1IURba64+#0`3YSg$wN{=s*t8(Sc*)R^0tIK7R*=#g)5_u&cJ7Jg@3}I+LjhW$Th*e<&1A zRr$htZA9QT@kAEAC1~|1)FL%KToJ?_pg5ffJ$dy`vojP;)CLmub#*vaL#{qwUsqdK zSCh>7a^Z+G78mmTsc^YX26#Qc!yF8iyM!WHSy9|#0pnX7kr!Rwm2g@b@(uoQZN47h zf+%j!)#cswd1o}>Ojn!z5vv>3-Zrz+Z>w_1DhkVsKPsZ=w7hIgZA-*sfm9?Ia@W-v z;{H%IFtF{Ec#0X)0efba;G zf4xqx5lHXuSQsHt2WrR{iw2^#wLnU#PsT$*Q^pB@Md3EeT`o)7Vv$-rQIWE!L}f5@ zcrLXi0K`SGhz7haBp1fQRtw-2!+~slF5h6yCv7pWRjZV!qvm8#mk>o#VnKD#8kLk- zH72RtBr#h8I*kML1_oCU|fb&%0bOv>POEg*)OID}q^4WAYoOQVqQGeKJGzZj5hr{M|m|fLX zKKSi?YcbPm6lxVNAe$j1s&IRp_MqNob_P-b8<5cI0L=#Agg~uZ9dmkRe0|ujN9De_ zsy60z$ka5ZrpW7c8od~4-9B^N9#7(gnd)Q=aC?z-b!|A328uL#gl*Pn7D4_1zzNlE zxz3;!v#0ceF9v&Zk$8SD{2!No%nBqs#isZ2hUjfdO_MyeulyHREg z0WnA9viqHeppqco*bFk6TQ4>{%|@ZyX>{UVx?G_|2&jZ^SE4qTPv|q>jyLvjsfXgeUCvMBSd+K&CEPlMDD$7KfAV zx9LJb@D?XSNwYm(K7mhxl?^MDLE$sm!X8~b8BTj*_0^G(-{$g% zRRObH9x|yc>8vpywkG|qa4;tOPyHsp<~rX^h!}nWA`VV=cUqo~zCQ&>|L*xJEIne! zZ<#HVvn6^akMJ-4Mebmni{$6gN~R-qFjel8zQ0RYmb4{vc8;DbE^ERa|_paT?EFsBFe9To8r@D zIl8cJbK0x%N7w}X?bnBU!iG|Qq#;tvmlM~aaPT6^FLi7~Y%7qL09#+HP!kjW#Rn-BSU*uzwK+vC;(Uf$ueoxkj zuslv7*AHYcld(z-^t>V=RV7hb%M<_(6H@-NFH1LNbz< zkqco^{=3*uk#da1_Rbbfn@V29G2ucu`Sz@D?V^HV0bxog?X%)ni9yoh7XV|D53?~z zC}KWE_5LD7R#GI8GXo7)Fa)qeIu3a7ej#f{)(j;OR^j*IOoiV@zWfT0j=dJFc~rku z#G$%9U>p#DLZ*j8bsJnp6#y}=3*Mi?#|LFTgV{?jjv}pwQsLvUA@^%)C#P6x3bja) zGeJ-^Zr6aEcL?W7;m|}ybU0nHxq`>1 zxEsV+n5p5ChmLjoB0wus0B&DM!nk?#Um?$;0=fp)9=cFi&bCGvfadrYQk%jpt>9{q zKEZ!~5{G|ZLzVGq$Qr_bBps-jj^qg?<(I`pd~ZZZm#N?1OP5jYUX2Dh8(=3bq9C`H znrySJ3&+K-_ZLf2b?caGJ1XfE9SJBBE+JNPTwraE^26^R64*KoTx+Wa3?qfUa?lk4 zBNP@S;YpBo!e!DF@6XdqxyUo%m^n!MAP&zp+^K@O2&8(0E0$Yhg%iL;Q{l(qfq=A# zqOPt_1vFYM*dD$v~(~; zR*6uI@@YBm{h?xtZAHW~W%~W-Sk+jnC)J(mIzUj(F5A6YjrywEp3 zJvy>=cw%O9bl29a-+lDd(eYy^@4xrz58nFD*>63Pd+gG^XP!&Bq;T)uqoJ&T9#J9+H#qc2}Re(q@Q z>fyzQFI{=;(mfY;9zJ~N{KIE2o;$c_Vq~^|Vsd25?3U?~vH6LCiJ7f^3xmPIwj-yG zA70ph?ELZR2QQ!L+uzllyP8>;K6>T8<1-iUIk0!nu~Wd>K5=s2{LW3&Q=8ZC-?Dvl zd|+T~-{j_r-R)aiYoC1P$@?EV^YFtLFP=Vje9K_xzTDNG;m&>M&z*bl{;Q`iJbeGt zS023op+`;~+dG9I+QQ__?9`^6JLl&1Z5^E5wtc+gd*6Qe;*m3teCwgdPM3%IQ-NT>RF{7oL9f^5uPtJN9lH80+0MF+D%Odth*E%h=rD)Rvu( zKl`lSr7pMDs@#3TRADcgT zJ!-#0nCcW(d4$XxH*W@_^y?VV??K6-Bd{Cy`Md;E!) zUwGi`(Z$@=BPT9A|Mc_Eo_OTq{PELgo_gx+sh!hfyYRebcO0DPADlZdzI}LPacr!+ zeQ18@x#u4|dtmO&g~u;G`mLu=cy||n>)6t zYqWpE;`HF$zGEXpySm3FN7l_uY#luN(9!!&Up)B0lTY0LQ?!B11vhVb_p1k_t zlixjm_|nOv#~;4((51c8`}Pjc^pB43m;)8W_^#cHW1II5&&_r3TzmYXv-j*hap34f zXD;4%;J!2Cr*e-UK6m2ynR6!}`qrh>#}`jMeBtzw1Hivu93I&*)VqGVe|9evYJ6ee zMAzihmfo{Z96o($??VsYKYe7^zUhTEGr6nT0mRsj_TP8%$z!`t?tbXf+0(}_Up{ir zw$a()F@&T0C&vay$7VP5&I~n;cAx(C%;e<@moJ{*x40h&j=g(xS2{-Lc1|8SaLiHw_-Uuz&x9M=qT_edf^RZ{2t4^ySA+M+;L!dckAo|;4AkH_w1ZQ2%eB3PWH_Y&ks!XPHx&XyloG-{1))5 zb{swP;3H>_ojjL&{Ltye2OhZp{@wfMhNceeBQ$=8b`Q^vG%hS|@0}W&9oas=b=Us+ zog>qI6Ie#szk7Uc?BK3Lk3R9hw;z7w?B(1O_guO9?GsNvdE(gq$;sjQ1G5VUb{?6X z*}rYg)WFu+_1ouq#%G3yrWdx2gGOZi-i6(}7e@A;Ir_vSXD>YgK>pm5XCHX#!s$y- z+abkP#=KeiHqo5+0?3}|K{HP2M-+FKQ%eOXLRSn^rq>})48kb$Hr&&>>1v>adz**p`C~J zjF0U&&^t3VJT$X=bZT~_bI-``-tAyj9UdL)+&n+KZen!p@~<7?-0R~t5L zo8C1tIlXO1d*A5z*zT@{^*c6?P7IF>&SCv$Ti@L1==j9^p6P8<>!P5B-M(XJ>(-9R zo!jarrf2(h=dNzvHa)&+Xnw49e)Gi8?$OzS(UIZl^|<8&(_8u`hqm|6Y@gUSJu}(8 zb!%()?C$=>KHzN5?ONElX?pSCJ-LVX?_HSRwtHrFY-H;UmMwPf7}?%Cvu!H|fay)W zp3oftnC?tN3#g55FQ3-NINnR_Z99fa@ZTJL#?PF9u%dm$89R7+avU&_* zOrfjv36s~Yv4;@#kITHCKurauv|P;t8m}3h{w&7A0x=~kHR9)#nJC3bwZOs1ui$Hx z39~hsWJdy^-wV5Vwko?gQB_lms+YOV6iSs5yC zr9dVyof|D_nghh~Ae2sXVxQxF+?rw2RZcWF2l*;wd&a_ebNv$DqNblRWHzSJYHQq z2b^|tdJyLk@~%&dWyHIrQ9i+3OU zsMJ9-@DocY*Go+n9Odtj44lAJJIzEKg-iAL6+2K-iQQe`^$Dj-~Zk@tL0}~~= z%lKe0Fm1wNQdwQKhmnXV*lM%DV^pTxC`^b$`N6*ik~Rsw$epjo`ExaPNpVzz3qy$@ zTyOADl4JcbEzVMhELPM?D2z%)E^1ZQf+WL9c>8dfm_PvIU19OyDFss@kiv1uvn3P0 z63TmcIz}6!!{#c#Bd+CG!oIN0QymYORie^jni?0Oim~NR@C-OK8ZV0~0j@fxxQ-Cg z!;2|bn4n?O1`rkxrY&NvEA34C3^lQALd_0vZFCvVfaGFz6pzYjW0ndi#oQ8xs?sCQ z8x%A;SL{hCbp~nH1PD<}OykpptqE%~<%JE*rq2@bqBxFAhByP-K{j2X%vJn&uV2zD|l3q(8Sl6 z!Ag?0`+$)t4j=;(At!#wridwFrBG5X2a*i@QacujkqN4B zI5oVG!Q}B7#V(7^>l1h-RY9rM;q|y`lUhT%%$sW*nj%A^Un*$JyR>i>`T9$Z}=Nc${J$K){;FDL3O zRR_AqXe)DddOSV!71e>>Zu*MqKyMfQ4*5DakgwzIq_3(7yjG|Pq;`CG{O^D9#~V62 zmOf-1^i>ss?=MvZZv6H1+P9xud-FrK_H*@r|4a(`&iq$E4gJHd56!Adzh4o7Cw>To zqHAk!ezZ&5SJ(CZiq!Qz{#ILC@3r=uA8pb$`l>>{e)b!w`jEBKS5@!* zGpXKN_u)j#51+mDp;;kN)bjpP!CuSJe|Z0A-7PmiWG(a+X++*;>hL1%vYb5rdaZobZ+bbE6~WiTAmRJf%M8`~@~7+4OC%2;mF z2`n6`esIlj)Y_Fsg+hg84-{3)k$hUoEaM4L z7Q!i~vRL#|8i(E0-IHG9uGOaX;IPuf*gn2dTCUV_9WVou1sXebc7CuKLj0mX4MU>({L})vaCI z(Nf=9wWdB->uZQsyMqdaH0BE0QW3wxpNK^HTDRGaXtK6guBz+rT;EjNT4(gPC!4A? zeviS3;(65Cl{3VmGN!VasiV>>sFI>G231Hyc5Wp?dQ=(%4k9Q=OUnkkH}!SY_N{H- zw4vP>Zvx|E&=A(Kxhg$M|5bcyWhJ##z@+gp{DNH=bP}Y129-r9sUhPvb;H{6!R@_m zEt`5bY};II3kIC|d`D}-F9In#9DG`^k*rgzVL!NL^F)~2P z2S!Kxwh#69)U9i6Tfd<*;B0GeZm$msJy-$t*%el8rY_-isLD-J&|(NoA_kjf29=FP z#H7!YaK2nr ziP|Ix#1$Ym54b%?sNRrz*rE)-bP((U%u=Ngn6cOYOl>QzD?id3m?j?6%_R8;6n zwA^BSDPK{fE@xP%VqTGk|*IKHhN^C{te4e75&1WcRY$KJ3G<8`)2v(+A1K<_f?)H1Kh zop88RLaRw?RM=%)i@_l0>co7d#v48+$g#8ynX~Vr}*wUrS4{A>aym zgFdZWX^DFLE~i;#Q5x(Thn`e@n6)~PvB|YMjTR8@3Qc=!uFdWO!&Q?j&~D57vEZzg zl{184r zL&;}KRR%too~aymIjXOSqlhXt@PQy%6q<=*EK>am4;@8X`KHE(HMMv>A`hCcx9TK{ zN~xp_03<@Dipds(?L>$&UNDXn(UI=LL@jACp-3mCfThKlkMwnQZ0pIS>S9`#7THU3 zzu2jxal!RbS*GTSD;UKzF0jd{sE9*_UO6f^@rjbwas;fAwo0LPQacx^^MUqYQ)9v! z3AnUszCtHr(@QHjU=*wXZ%R3zi?Te_FfvK)AqF2BeiOJ>NKGhe%Ba+iuGV^2uECRM zX{zi(mW*MnP_sl8NXX&xFr5-s(o4%xQ&x^@Vk&Ze=tUKkMYNJ~Mo}pW&1lTZ?od}p zJXYP*)6m@A+0>8$mjyUc^fP^rNh>yjvB8XgKBSb67#P`laQv~Q2*Q`j>0&8Q$Y(QAJy6E3V54xlk_q(z^Gq3oNghi1>)_2T zVU%oY_Zz%=eNw2>m5a2M$Y*ECS(;)YQ!Xe&1rCQ-#s}AKC69`-!Lnl9H~^@X1C$Q8 z5)NTW@e0@H4_*c`i)n5Dp}dSm%HtkiERe~htAof=sjbaNV|AHOGMUQ8z`p2;y4^`v z%#BP;mz$5;xRBcFbbItRx63Vec(CGbcW0yKfVCzElzxAp8uWPSx+Lfjec5z6W3H}A zfg&&)_N3AozdqrLM~#Hg))4i2gX(a=8aBCu!Fq39I$M`XSH&}#>e_U!y2|f1S|cbH zGFt;+XY*3MizUji2L*i=kJ$^#NtMMemVjT6YcxufVza3xTU}ij&ibo;saV{f)g(

    @@ -222,67 +222,59 @@
    - - + + - xb200 + kHz + + + + + + + + 0 + 0 + + + + BW - + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + + + - XB200 board mode + Bias Tee - - None + + BT - - 0 - - - 5 - - - - None - - - - - Bypass - - - - - Auto 1dB - - - - - Auto 3dB - - - - - Custom - - - - - 50M - - - - - 144M - - - - - 222M - - @@ -360,6 +352,35 @@
    + + + + Fp + + + + + + + Relative position of device center frequency + + + + Inf + + + + + Sup + + + + + Cen + + + + @@ -421,146 +442,43 @@ - + 3 - - + + Qt::Horizontal - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - BW - - - + + + + + - Fp + Gain - - - Relative position of device center frequency - - - - Inf - - - - - Sup - - - - - Cen - - - - - - - - - 0 - 0 - + + + + 25 + 0 + - LNA + 000 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - 40 - 16777215 - - - - - 0 - - - - - 3 - - - - - 6 - - - - - - - - dB - - - - - - - kHz - - - - - - - - 70 - 16777215 - - - - IF bandwidth in kHz - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -570,117 +488,6 @@
    - - - - 3 - - - - - Amplifier before filtering gain (dB) - - - 5 - - - 30 - - - 1 - - - 20 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 20 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - VGA1 - - - - - - - - - Qt::Horizontal - - - - - - - 3 - - - - - VGA2 - - - - - - - Amplifier before ADC gain (dB) - - - 30 - - - 3 - - - 3 - - - 9 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 9 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - @@ -698,13 +505,6 @@ - - - - Qt::Horizontal - - - diff --git a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp index db6b282c4..76a611e62 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp @@ -87,7 +87,7 @@ PluginInterface::SamplingDevices Blderf2InputPlugin::enumSampleSources() { unsigned int nbRxChannels = bladerf_get_channel_count(dev, BLADERF_RX); - for (int j = 0; j < nbRxChannels; j++) + for (unsigned int j = 0; j < nbRxChannels; j++) { qDebug("Blderf2InputPlugin::enumSampleSources: device #%d (%s) channel %u", i, devinfo[i].serial, j); QString displayedName(QString("BladeRF2[%1:%2] %3").arg(devinfo[i].instance).arg(j).arg(devinfo[i].serial)); @@ -128,7 +128,7 @@ PluginInstanceGUI* Blderf2InputPlugin::createSampleSourcePluginInstanceGUI( { if(sourceId == m_deviceTypeID) { - Bladerf2InputGui* gui = new Bladerf2InputGui(deviceUISet); + BladeRF2InputGui* gui = new BladeRF2InputGui(deviceUISet); *widget = gui; return gui; } @@ -143,7 +143,7 @@ DeviceSampleSource *Blderf2InputPlugin::createSampleSourcePluginInstanceInput(co { if (sourceId == m_deviceTypeID) { - Bladerf2Input *input = new Bladerf2Input(deviceAPI); + BladeRF2Input *input = new BladeRF2Input(deviceAPI); return input; } else diff --git a/udev-rules/88-nuand.rules b/udev-rules/88-nuand.rules index c8d182dd4..44add41f7 100644 --- a/udev-rules/88-nuand.rules +++ b/udev-rules/88-nuand.rules @@ -1,6 +1,9 @@ # Nuand bladeRF ATTR{idVendor}=="2cf0", ATTR{idProduct}=="5246", MODE="666", GROUP="plugdev" +# Nuand bladeRF2 +ATTR{idVendor}=="2cf0", ATTR{idProduct}=="5250", MODE="666", GROUP="plugdev" + # Nuand bladeRF, legacy VID/PID ATTR{idVendor}=="1d50", ATTR{idProduct}=="6066", MODE="666", GROUP="plugdev" From 47a4da4142858c009ab1c6caa30566eadcbbdfd4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 25 Sep 2018 08:45:57 +0200 Subject: [PATCH 791/956] BladerRF2 input support (8). Streams but thread issue --- devices/bladerf2/devicebladerf2shared.h | 4 ++-- .../samplesource/bladerf2input/bladerf2input.cpp | 13 +++++++++---- plugins/samplesource/bladerf2input/bladerf2input.h | 3 ++- .../bladerf2input/bladerf2inputthread.cpp | 7 +++++++ .../bladerf2input/bladerf2inputthread.h | 2 +- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index 6e132887a..2c04e26f9 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -32,7 +32,7 @@ public: class InputThreadInterface { public: - virtual ~InputThreadInterface() = 0; + virtual ~InputThreadInterface() {} virtual void startWork() = 0; virtual void stopWork() = 0; virtual bool isRunning() const = 0; @@ -48,7 +48,7 @@ public: class OutputThreadInterface { public: - virtual ~OutputThreadInterface() = 0; + virtual ~OutputThreadInterface() {} virtual void startWork() = 0; virtual void stopWork() = 0; virtual bool isRunning() = 0; diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 296fe3e44..035d0760d 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -42,7 +42,8 @@ BladeRF2Input::BladeRF2Input(DeviceSourceAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_settings(), m_deviceDescription("BladeRF2Input"), - m_running(false) + m_running(false), + m_thread(0) { openDevice(); @@ -313,11 +314,13 @@ void BladeRF2Input::stop() } int nbOriginalChannels = m_deviceShared.m_inputThread->getNbChannels(); + Bladerf2InputThread *bladerf2InputThread = 0; if (nbOriginalChannels == 1) // SI mode { m_deviceShared.m_inputThread->stopWork(); - delete m_deviceShared.m_inputThread; + bladerf2InputThread = (Bladerf2InputThread*) m_deviceShared.m_inputThread; + delete bladerf2InputThread; m_deviceShared.m_inputThread = 0; m_running = false; } @@ -334,8 +337,10 @@ void BladeRF2Input::stop() fcPoss[i] = m_deviceShared.m_inputThread->getFcPos(i); } - delete m_deviceShared.m_inputThread; - m_deviceShared.m_inputThread = new Bladerf2InputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); + bladerf2InputThread = (Bladerf2InputThread*) m_deviceShared.m_inputThread; + delete bladerf2InputThread; + bladerf2InputThread = new Bladerf2InputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); + m_deviceShared.m_inputThread = bladerf2InputThread; for (int i = 0; i < nbOriginalChannels-1; i++) { // restore original FIFO references m_deviceShared.m_inputThread->setFifo(i, fifos[i]); diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h index 8966c378d..6a91a690a 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.h +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -26,7 +26,7 @@ #include "bladerf2inputsettings.h" class DeviceSourceAPI; -class LimeSDRInputThread; +class BladeRF2InputThread; class FileRecord; class BladeRF2Input : public DeviceSampleSource @@ -147,6 +147,7 @@ private: QString m_deviceDescription; bool m_running; DeviceBladeRF2Shared m_deviceShared; + BladeRF2InputThread *m_thread; FileRecord *m_fileSink; //!< File sink to record device I/Q output bool openDevice(); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp index 048576800..490bb2da1 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -23,11 +23,18 @@ Bladerf2InputThread::Bladerf2InputThread(struct bladerf* dev, unsigned int nbRxC m_nbChannels(nbRxChannels) { m_channels = new Channel[nbRxChannels]; + + for (unsigned int i = 0; i < nbRxChannels; i++) { + m_channels[i].m_convertBuffer.resize(DeviceBladeRF2::blockSize, Sample{0,0}); + } + m_buf = new qint16[2*DeviceBladeRF2::blockSize*nbRxChannels]; } Bladerf2InputThread::~Bladerf2InputThread() { + qDebug("Bladerf2InputThread::~Bladerf2InputThread"); + if (m_running) { stopWork(); } diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.h b/plugins/samplesource/bladerf2input/bladerf2inputthread.h index ba2718a93..bbce54fff 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.h @@ -37,7 +37,7 @@ class Bladerf2InputThread : public QThread, public DeviceBladeRF2Shared::InputTh public: Bladerf2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent = NULL); - virtual ~Bladerf2InputThread(); + ~Bladerf2InputThread(); virtual void startWork(); virtual void stopWork(); From 5f2a4e8c8326cd957ea22462a00978a623c6b8c7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 25 Sep 2018 14:19:57 +0200 Subject: [PATCH 792/956] BladerRF2 input support. Trying to fix threading issue --- devices/bladerf2/devicebladerf2shared.cpp | 3 +- devices/bladerf2/devicebladerf2shared.h | 32 +--- .../bladerf2input/bladerf2input.cpp | 169 +++++++++++------- .../bladerf2input/bladerf2input.h | 3 + .../bladerf2input/bladerf2inputthread.cpp | 38 ++-- .../bladerf2input/bladerf2inputthread.h | 6 +- 6 files changed, 132 insertions(+), 119 deletions(-) diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp index 5068f816e..d33c1a3da 100644 --- a/devices/bladerf2/devicebladerf2shared.cpp +++ b/devices/bladerf2/devicebladerf2shared.cpp @@ -21,8 +21,7 @@ MESSAGE_CLASS_DEFINITION(DeviceBladeRF2Shared::MsgReportBuddyChange, Message) DeviceBladeRF2Shared::DeviceBladeRF2Shared() : m_dev(0), m_channel(-1), - m_inputThread(0), - m_outputThread(0) + m_source(0) {} DeviceBladeRF2Shared::~DeviceBladeRF2Shared() diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index 2c04e26f9..aaab9891e 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -22,6 +22,7 @@ class SampleSinkFifo; class SampleSourceFifo; +class BladeRF2Input; /** * Structure shared by a buddy with other buddies @@ -29,34 +30,6 @@ class SampleSourceFifo; class DEVICES_API DeviceBladeRF2Shared { public: - class InputThreadInterface - { - public: - virtual ~InputThreadInterface() {} - virtual void startWork() = 0; - virtual void stopWork() = 0; - virtual bool isRunning() const = 0; - virtual unsigned int getNbChannels() const = 0; - virtual void setLog2Decimation(unsigned int channel, unsigned int log2_decim) = 0; - virtual unsigned int getLog2Decimation(unsigned int channel) const = 0; - virtual void setFcPos(unsigned int channel, int fcPos) = 0; - virtual int getFcPos(unsigned int channel) const = 0; - virtual void setFifo(unsigned int channel, SampleSinkFifo *fifo) = 0; - virtual SampleSinkFifo *getFifo(unsigned int channel) = 0; - }; - - class OutputThreadInterface - { - public: - virtual ~OutputThreadInterface() {} - virtual void startWork() = 0; - virtual void stopWork() = 0; - virtual bool isRunning() = 0; - virtual unsigned int getNbChannels() const = 0; - virtual void setFifo(unsigned int channel, SampleSourceFifo *fifo) = 0; - virtual SampleSourceFifo *getFifo(unsigned int channel) = 0; - }; - class MsgReportBuddyChange : public Message { MESSAGE_CLASS_DECLARATION @@ -82,8 +55,7 @@ public: DeviceBladeRF2 *m_dev; int m_channel; //!< allocated channel (-1 if none) - InputThreadInterface *m_inputThread; //!< The SISO/MIMO input thread - OutputThreadInterface *m_outputThread; //!< The SISO/MIMO output thread + BladeRF2Input *m_source; }; diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 035d0760d..66e6b45fc 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -189,6 +189,7 @@ bool BladeRF2Input::openDevice() } } + m_deviceShared.m_source = this; m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API return true; } @@ -204,6 +205,7 @@ void BladeRF2Input::closeDevice() } m_deviceShared.m_channel = -1; + m_deviceShared.m_source = 0; // No buddies so effectively close the device @@ -220,6 +222,38 @@ void BladeRF2Input::init() applySettings(m_settings, true); } +BladeRF2InputThread *BladeRF2Input::findThread() +{ + if (m_thread == 0) // this does not own the thread + { + BladeRF2InputThread *bladerf2InputThread = 0; + + // find a buddy that has allocated the thread + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + BladeRF2Input *buddySource = ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source; + + if (buddySource) + { + bladerf2InputThread = buddySource->getThread(); + + if (bladerf2InputThread) { + break; + } + } + } + + return bladerf2InputThread; + } + else + { + return m_thread; // own thread + } +} + bool BladeRF2Input::start() { if (!m_deviceShared.m_dev) @@ -228,36 +262,21 @@ bool BladeRF2Input::start() return false; } - Bladerf2InputThread *bladerf2InputThread = 0; + BladeRF2InputThread *bladerf2InputThread = findThread(); bool needsStart = false; - // find thread allocated by a buddy - const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); - std::vector::const_iterator it = sourceBuddies.begin(); - - for (; it != sourceBuddies.end(); ++it) + if (bladerf2InputThread) // if thread is already allocated { - bladerf2InputThread = (Bladerf2InputThread*) ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_inputThread; - - if (bladerf2InputThread) { - break; - } - } - - if (bladerf2InputThread) // if thread was allocated by a buddy - { - DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; - DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sourceBuddy->getBuddySharedPtr(); - bladerf2InputThread = (Bladerf2InputThread*) deviceBladeRF2Shared->m_inputThread; int nbOriginalChannels = bladerf2InputThread->getNbChannels(); - if (m_deviceShared.m_channel+1 > nbOriginalChannels) // expansion + if (m_deviceShared.m_channel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread { SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels]; unsigned int *log2Decims = new unsigned int[nbOriginalChannels]; int *fcPoss = new int[nbOriginalChannels]; - for (int i = 0; i < nbOriginalChannels; i++) { // save original FIFO references and data + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { fifos[i] = bladerf2InputThread->getFifo(i); log2Decims[i] = bladerf2InputThread->getLog2Decimation(i); fcPoss[i] = bladerf2InputThread->getFcPos(i); @@ -265,20 +284,22 @@ bool BladeRF2Input::start() bladerf2InputThread->stopWork(); delete bladerf2InputThread; - bladerf2InputThread = new Bladerf2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); + m_thread = bladerf2InputThread; // take ownership - for (int i = 0; i < nbOriginalChannels; i++) { // restore original FIFO references + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { bladerf2InputThread->setFifo(i, fifos[i]); bladerf2InputThread->setLog2Decimation(i, log2Decims[i]); bladerf2InputThread->setFcPos(i, fcPoss[i]); } - // propagate new thread address to buddies + // remove old thread address from buddies (reset in all buddies) const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); std::vector::const_iterator it = sourceBuddies.begin(); for (; it != sourceBuddies.end(); ++it) { - ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_inputThread = bladerf2InputThread; + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->resetThread(); } needsStart = true; @@ -286,14 +307,14 @@ bool BladeRF2Input::start() } else // first allocation { - bladerf2InputThread = new Bladerf2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); + m_thread = bladerf2InputThread; // take ownership needsStart = true; } bladerf2InputThread->setFifo(m_deviceShared.m_channel, &m_sampleFifo); bladerf2InputThread->setLog2Decimation(m_deviceShared.m_channel, m_settings.m_log2Decim); bladerf2InputThread->setFcPos(m_deviceShared.m_channel, (int) m_settings.m_fcPos); - m_deviceShared.m_inputThread = bladerf2InputThread; if (needsStart) { bladerf2InputThread->startWork(); @@ -301,7 +322,7 @@ bool BladeRF2Input::start() applySettings(m_settings, true); - qDebug("BladerfInput::startInput: started"); + qDebug("BladeRF2Input::start: started"); m_running = true; return true; @@ -313,55 +334,70 @@ void BladeRF2Input::stop() return; } - int nbOriginalChannels = m_deviceShared.m_inputThread->getNbChannels(); - Bladerf2InputThread *bladerf2InputThread = 0; + BladeRF2InputThread *bladerf2InputThread = findThread(); - if (nbOriginalChannels == 1) // SI mode + if (bladerf2InputThread == 0) // no thread allocated { - m_deviceShared.m_inputThread->stopWork(); - bladerf2InputThread = (Bladerf2InputThread*) m_deviceShared.m_inputThread; - delete bladerf2InputThread; - m_deviceShared.m_inputThread = 0; - m_running = false; + return; } - else if (m_deviceShared.m_channel == nbOriginalChannels - 1) // remove last MI channel => reduce + + int nbOriginalChannels = bladerf2InputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SI mode => just stop and delete the thread { - m_deviceShared.m_inputThread->stopWork(); - SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; - unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; - int *fcPoss = new int[nbOriginalChannels-1]; - - for (int i = 0; i < nbOriginalChannels-1; i++) { // save original FIFO references - fifos[i] = m_deviceShared.m_inputThread->getFifo(i); - log2Decims[i] = m_deviceShared.m_inputThread->getLog2Decimation(i); - fcPoss[i] = m_deviceShared.m_inputThread->getFcPos(i); - } - - bladerf2InputThread = (Bladerf2InputThread*) m_deviceShared.m_inputThread; + bladerf2InputThread->stopWork(); delete bladerf2InputThread; - bladerf2InputThread = new Bladerf2InputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); - m_deviceShared.m_inputThread = bladerf2InputThread; + m_thread = 0; - for (int i = 0; i < nbOriginalChannels-1; i++) { // restore original FIFO references - m_deviceShared.m_inputThread->setFifo(i, fifos[i]); - m_deviceShared.m_inputThread->setLog2Decimation(i, log2Decims[i]); - m_deviceShared.m_inputThread->setFcPos(i, fcPoss[i]); - } - - // propagate new thread address to buddies + // remove old thread address from buddies (reset in all buddies) const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); std::vector::const_iterator it = sourceBuddies.begin(); for (; it != sourceBuddies.end(); ++it) { - ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_inputThread = m_deviceShared.m_inputThread; + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->resetThread(); + } + } + else if (m_deviceShared.m_channel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread + { + bladerf2InputThread->stopWork(); + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; + int *fcPoss = new int[nbOriginalChannels-1]; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references + { + fifos[i] = bladerf2InputThread->getFifo(i); + log2Decims[i] = bladerf2InputThread->getLog2Decimation(i); + fcPoss[i] = bladerf2InputThread->getFcPos(i); } - m_deviceShared.m_inputThread->startWork(); + delete bladerf2InputThread; + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); + m_thread = bladerf2InputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels-1; i++) // restore original FIFO references + { + bladerf2InputThread->setFifo(i, fifos[i]); + bladerf2InputThread->setLog2Decimation(i, log2Decims[i]); + bladerf2InputThread->setFcPos(i, fcPoss[i]); + } + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->resetThread(); + } + + bladerf2InputThread->startWork(); } - else + else // remove channel from existing thread { - m_deviceShared.m_inputThread->setFifo(m_deviceShared.m_channel, 0); // remove FIFO + bladerf2InputThread->setFifo(m_deviceShared.m_channel, 0); // remove FIFO } + + m_running = false; } QByteArray BladeRF2Input::serialize() const @@ -670,9 +706,11 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if ((m_settings.m_fcPos != settings.m_fcPos) || force) { - if (m_deviceShared.m_inputThread != 0) + BladeRF2InputThread *inputThread = findThread(); + + if (inputThread != 0) { - m_deviceShared.m_inputThread->setFcPos(m_deviceShared.m_channel, (int) settings.m_fcPos); + inputThread->setFcPos(m_deviceShared.m_channel, (int) settings.m_fcPos); qDebug() << "BladeRF2Input::applySettings: set fc pos (enum) to " << (int) settings.m_fcPos; } } @@ -680,10 +718,11 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) { forwardChangeOwnDSP = true; + BladeRF2InputThread *inputThread = findThread(); - if (m_deviceShared.m_inputThread != 0) + if (inputThread != 0) { - m_deviceShared.m_inputThread->setLog2Decimation(m_deviceShared.m_channel, settings.m_log2Decim); + inputThread->setLog2Decimation(m_deviceShared.m_channel, settings.m_log2Decim); qDebug() << "BladeRF2Input::applySettings: set decimation to " << (1< Date: Tue, 25 Sep 2018 14:31:57 +0200 Subject: [PATCH 793/956] BladerRF2 input support. Transfer thread ownership before closing the source if it has the allocated thread --- .../bladerf2input/bladerf2input.cpp | 27 ++++++++++++++++--- .../bladerf2input/bladerf2input.h | 3 ++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 66e6b45fc..7abdc14e4 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -204,6 +204,10 @@ void BladeRF2Input::closeDevice() stop(); } + if (m_thread) { // stills own the thread => transfer to a buddy + moveThreadToBuddy(); + } + m_deviceShared.m_channel = -1; m_deviceShared.m_source = 0; @@ -254,6 +258,23 @@ BladeRF2InputThread *BladeRF2Input::findThread() } } +void BladeRF2Input::moveThreadToBuddy() +{ + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + BladeRF2Input *buddySource = ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source; + + if (buddySource) + { + buddySource->setThread(m_thread); + m_thread = 0; // zero for others + } + } +} + bool BladeRF2Input::start() { if (!m_deviceShared.m_dev) @@ -299,7 +320,7 @@ bool BladeRF2Input::start() std::vector::const_iterator it = sourceBuddies.begin(); for (; it != sourceBuddies.end(); ++it) { - ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->resetThread(); + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); } needsStart = true; @@ -354,7 +375,7 @@ void BladeRF2Input::stop() std::vector::const_iterator it = sourceBuddies.begin(); for (; it != sourceBuddies.end(); ++it) { - ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->resetThread(); + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); } } else if (m_deviceShared.m_channel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread @@ -387,7 +408,7 @@ void BladeRF2Input::stop() std::vector::const_iterator it = sourceBuddies.begin(); for (; it != sourceBuddies.end(); ++it) { - ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->resetThread(); + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); } bladerf2InputThread->startWork(); diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h index 9448cdf2b..f2d8486ba 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.h +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -101,7 +101,7 @@ public: virtual bool start(); virtual void stop(); BladeRF2InputThread *getThread() { return m_thread; } - void resetThread() { m_thread = 0; } + void setThread(BladeRF2InputThread *thread) { m_thread = thread; } virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); @@ -155,6 +155,7 @@ private: bool openDevice(); void closeDevice(); BladeRF2InputThread *findThread(); + void moveThreadToBuddy(); bool applySettings(const BladeRF2InputSettings& settings, bool force = false); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); From 5ad52a4a1b8a0e31c043987410196db2d6de1bdb Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 25 Sep 2018 17:03:34 +0200 Subject: [PATCH 794/956] BladerRF2 input support. Populate gain modes --- devices/bladerf2/devicebladerf2.cpp | 30 ++++ devices/bladerf2/devicebladerf2.h | 1 + .../bladerf2input/bladerf2input.cpp | 32 +++- .../bladerf2input/bladerf2input.h | 4 + .../bladerf2input/bladerf2inputgui.cpp | 9 ++ .../bladerf2input/bladerf2inputgui.h | 2 + sdrbase/resources/webapi/doc/html2/index.html | 37 ++++- .../webapi/doc/swagger/include/BladeRF2.yaml | 11 +- .../webapi/doc/swagger/include/Structs.yaml | 30 +++- .../api/swagger/include/BladeRF2.yaml | 11 +- .../sdrangel/api/swagger/include/Structs.yaml | 30 +++- swagger/sdrangel/code/html2/index.html | 37 ++++- .../qt5/client/SWGBladeRF2InputReport.cpp | 37 ++++- .../code/qt5/client/SWGBladeRF2InputReport.h | 15 +- .../code/qt5/client/SWGFrequencyRange.cpp | 148 ++++++++++++++++++ .../code/qt5/client/SWGFrequencyRange.h | 70 +++++++++ .../code/qt5/client/SWGModelFactory.h | 8 + .../sdrangel/code/qt5/client/SWGNamedEnum.cpp | 129 +++++++++++++++ .../sdrangel/code/qt5/client/SWGNamedEnum.h | 65 ++++++++ 19 files changed, 675 insertions(+), 31 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGFrequencyRange.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGFrequencyRange.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGNamedEnum.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGNamedEnum.h diff --git a/devices/bladerf2/devicebladerf2.cpp b/devices/bladerf2/devicebladerf2.cpp index 63d7c4c8c..92f88f313 100644 --- a/devices/bladerf2/devicebladerf2.cpp +++ b/devices/bladerf2/devicebladerf2.cpp @@ -462,6 +462,36 @@ void DeviceBladeRF2::getGlobalGainRangeTx(int& min, int& max, int& step) } } +int DeviceBladeRF2::getGainModesRx(const bladerf_gain_modes **modes) +{ + if (m_dev) + { + int n = bladerf_get_gain_modes(m_dev, BLADERF_CHANNEL_RX(0), 0); + + if (n < 0) + { + qCritical("DeviceBladeRF2::getGainModesRx: Failed to get the number of Rx gain modes: %s", bladerf_strerror(n)); + return 0; + } + + int status = bladerf_get_gain_modes(m_dev, BLADERF_CHANNEL_RX(0), modes); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getGainModesRx: Failed to get Rx gain modes: %s", bladerf_strerror(status)); + return 0; + } + else + { + return n; + } + } + else + { + return 0; + } +} + void DeviceBladeRF2::setBiasTeeRx(bool enable) { if (m_dev) diff --git a/devices/bladerf2/devicebladerf2.h b/devices/bladerf2/devicebladerf2.h index 70df89f17..a19147909 100644 --- a/devices/bladerf2/devicebladerf2.h +++ b/devices/bladerf2/devicebladerf2.h @@ -46,6 +46,7 @@ public: void getBandwidthRangeTx(int& min, int& max, int& step); void getGlobalGainRangeRx(int& min, int& max, int& step); void getGlobalGainRangeTx(int& min, int& max, int& step); + int getGainModesRx(const bladerf_gain_modes**); void setBiasTeeRx(bool enable); void setBiasTeeTx(bool enable); diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 7abdc14e4..26512a1e1 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -16,6 +16,8 @@ #include +#include "libbladeRF.h" + #include "SWGDeviceSettings.h" #include "SWGBladeRF2InputSettings.h" #include "SWGDeviceState.h" @@ -507,6 +509,19 @@ void BladeRF2Input::getGlobalGainRange(int& min, int& max, int& step) } } +const bladerf_gain_modes *BladeRF2Input::getGainModes(int& nbGains) +{ + const bladerf_gain_modes *modes = 0; + + if (m_deviceShared.m_dev) { + nbGains = m_deviceShared.m_dev->getGainModesRx(&modes); + } else { + nbGains = 0; + } + + return modes; +} + bool BladeRF2Input::handleMessage(const Message& message) { if (MsgConfigureBladeRF2::match(message)) @@ -978,7 +993,7 @@ void BladeRF2Input::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& respo device->getFrequencyRangeRx(f_min, f_max, step); - response.getBladeRf2InputReport()->setFrequencyRange(new SWGSDRangel::SWGRange); + response.getBladeRf2InputReport()->setFrequencyRange(new SWGSDRangel::SWGFrequencyRange); response.getBladeRf2InputReport()->getFrequencyRange()->setMin(f_min); response.getBladeRf2InputReport()->getFrequencyRange()->setMax(f_max); response.getBladeRf2InputReport()->getFrequencyRange()->setStep(step); @@ -996,6 +1011,21 @@ void BladeRF2Input::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& respo response.getBladeRf2InputReport()->getSampleRateRange()->setMin(min); response.getBladeRf2InputReport()->getSampleRateRange()->setMax(max); response.getBladeRf2InputReport()->getSampleRateRange()->setStep(step); + + response.getBladeRf2InputReport()->setGainModes(new QList); + + int nbModes; + const bladerf_gain_modes *modes = getGainModes(nbModes); + + if (modes) + { + for (int i = 0; i < nbModes; modes++) + { + response.getBladeRf2InputReport()->getGainModes()->append(new SWGSDRangel::SWGNamedEnum); + response.getBladeRf2InputReport()->getGainModes()->back()->setName(new QString(modes[i].name)); + response.getBladeRf2InputReport()->getGainModes()->back()->setValue(modes[i].mode); + } + } } } diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h index f2d8486ba..0cd59b212 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.h +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -28,6 +28,7 @@ class DeviceSourceAPI; class BladeRF2InputThread; class FileRecord; +struct bladerf_gain_modes; class BladeRF2Input : public DeviceSampleSource { @@ -116,6 +117,7 @@ public: void getSampleRateRange(int& min, int& max, int& step); void getBandwidthRange(int& min, int& max, int& step); void getGlobalGainRange(int& min, int& max, int& step); + const bladerf_gain_modes *getGainModes(int& nbGains); virtual bool handleMessage(const Message& message); @@ -151,6 +153,8 @@ private: DeviceBladeRF2Shared m_deviceShared; BladeRF2InputThread *m_thread; FileRecord *m_fileSink; //!< File sink to record device I/Q output + bladerf_gain_modes **m_gainModes; + int m_nbGainModes; bool openDevice(); void closeDevice(); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp index 3fb1020e9..6307e3f78 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -58,6 +58,15 @@ BladeRF2InputGui::BladeRF2InputGui(DeviceUISet *deviceUISet, QWidget* parent) : ui->bandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); ui->bandwidth->setValueRange(6, min/1000, max/1000); + m_gainModes = m_sampleSource->getGainModes(m_nbGainModes); + + if (m_gainModes) + { + for (int i = 0; i < m_nbGainModes; i++) { + ui->gainMode->addItem(tr("%1").arg(m_gainModes[i].name)); + } + } + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(500); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.h b/plugins/samplesource/bladerf2input/bladerf2inputgui.h index f52c82022..3f6a0df99 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.h @@ -65,6 +65,8 @@ private: quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_lastEngineState; MessageQueue m_inputMessageQueue; + const struct bladerf_gain_modes *m_gainModes; + int m_nbGainModes; void displaySettings(); void sendSettings(); diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 941d139fd..52d4b1fa3 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1305,7 +1305,7 @@ margin-bottom: 20px; defs.BladeRF2InputReport = { "properties" : { "frequencyRange" : { - "$ref" : "#/definitions/Range" + "$ref" : "#/definitions/FrequencyRange" }, "sampleRateRange" : { "$ref" : "#/definitions/Range" @@ -1315,6 +1315,12 @@ margin-bottom: 20px; }, "globalGainRange" : { "$ref" : "#/definitions/Range" + }, + "gainModes" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/NamedEnum" + } } }, "description" : "BladeRF2" @@ -2250,6 +2256,22 @@ margin-bottom: 20px; } }, "description" : "A band of frequencies given its boudaries in Hertz (Hz)" +}; + defs.FrequencyRange = { + "properties" : { + "min" : { + "type" : "integer", + "format" : "int64" + }, + "max" : { + "type" : "integer", + "format" : "int64" + }, + "step" : { + "type" : "integer" + } + }, + "description" : "An frequency range with 64 bit support for min and max" }; defs.Gain = { "properties" : { @@ -2811,6 +2833,17 @@ margin-bottom: 20px; } }, "description" : "NFMMod" +}; + defs.NamedEnum = { + "properties" : { + "name" : { + "type" : "string" + }, + "value" : { + "type" : "integer" + } + }, + "description" : "Enumeration with name for values" }; defs.PerseusReport = { "properties" : { @@ -23142,7 +23175,7 @@ except ApiException as e:
    - Generated 2018-09-22T10:27:51.856+02:00 + Generated 2018-09-25T16:51:16.493+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml index 9b44f6c2d..39332634b 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml @@ -23,17 +23,20 @@ BladeRF2InputSettings: iqCorrection: type: integer fileRecordName: - type: string - + type: string + BladeRF2InputReport: description: BladeRF2 properties: frequencyRange: - $ref: "/doc/swagger/include/Structs.yaml#/Range" + $ref: "/doc/swagger/include/Structs.yaml#/FrequencyRange" sampleRateRange: $ref: "/doc/swagger/include/Structs.yaml#/Range" bandwidthRange: $ref: "/doc/swagger/include/Structs.yaml#/Range" globalGainRange: $ref: "/doc/swagger/include/Structs.yaml#/Range" - \ No newline at end of file + gainModes: + type: array + items: + $ref: "/doc/swagger/include/Structs.yaml#/NamedEnum" diff --git a/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml b/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml index ccdedd7ba..a8976c4a5 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/Structs.yaml @@ -3,19 +3,19 @@ SampleRate: properties: rate: type: integer - + Bandwidth: description: A bandwidth expressed in Hertz (Hz) properties: bandwidth: type: integer - + Frequency: description: A frequency expressed in Hertz (Hz) properties: frequency: type: integer - + FrequencyBand: description: A band of frequencies given its boudaries in Hertz (Hz) properties: @@ -38,6 +38,26 @@ Range: min: type: integer max: - type: integer + type: integer step: - type: integer \ No newline at end of file + type: integer + +FrequencyRange: + description: An frequency range with 64 bit support for min and max + properties: + min: + type: integer + format: int64 + max: + type: integer + format: int64 + step: + type: integer + +NamedEnum: + description: Enumeration with name for values + properties: + name: + type: string + value: + type: integer diff --git a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml index 40eb4d52c..c87dc8d81 100644 --- a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml +++ b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml @@ -23,17 +23,20 @@ BladeRF2InputSettings: iqCorrection: type: integer fileRecordName: - type: string - + type: string + BladeRF2InputReport: description: BladeRF2 properties: frequencyRange: - $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/FrequencyRange" sampleRateRange: $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" bandwidthRange: $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" globalGainRange: $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" - \ No newline at end of file + gainModes: + type: array + items: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/NamedEnum" diff --git a/swagger/sdrangel/api/swagger/include/Structs.yaml b/swagger/sdrangel/api/swagger/include/Structs.yaml index ccdedd7ba..a8976c4a5 100644 --- a/swagger/sdrangel/api/swagger/include/Structs.yaml +++ b/swagger/sdrangel/api/swagger/include/Structs.yaml @@ -3,19 +3,19 @@ SampleRate: properties: rate: type: integer - + Bandwidth: description: A bandwidth expressed in Hertz (Hz) properties: bandwidth: type: integer - + Frequency: description: A frequency expressed in Hertz (Hz) properties: frequency: type: integer - + FrequencyBand: description: A band of frequencies given its boudaries in Hertz (Hz) properties: @@ -38,6 +38,26 @@ Range: min: type: integer max: - type: integer + type: integer step: - type: integer \ No newline at end of file + type: integer + +FrequencyRange: + description: An frequency range with 64 bit support for min and max + properties: + min: + type: integer + format: int64 + max: + type: integer + format: int64 + step: + type: integer + +NamedEnum: + description: Enumeration with name for values + properties: + name: + type: string + value: + type: integer diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 941d139fd..52d4b1fa3 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1305,7 +1305,7 @@ margin-bottom: 20px; defs.BladeRF2InputReport = { "properties" : { "frequencyRange" : { - "$ref" : "#/definitions/Range" + "$ref" : "#/definitions/FrequencyRange" }, "sampleRateRange" : { "$ref" : "#/definitions/Range" @@ -1315,6 +1315,12 @@ margin-bottom: 20px; }, "globalGainRange" : { "$ref" : "#/definitions/Range" + }, + "gainModes" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/NamedEnum" + } } }, "description" : "BladeRF2" @@ -2250,6 +2256,22 @@ margin-bottom: 20px; } }, "description" : "A band of frequencies given its boudaries in Hertz (Hz)" +}; + defs.FrequencyRange = { + "properties" : { + "min" : { + "type" : "integer", + "format" : "int64" + }, + "max" : { + "type" : "integer", + "format" : "int64" + }, + "step" : { + "type" : "integer" + } + }, + "description" : "An frequency range with 64 bit support for min and max" }; defs.Gain = { "properties" : { @@ -2811,6 +2833,17 @@ margin-bottom: 20px; } }, "description" : "NFMMod" +}; + defs.NamedEnum = { + "properties" : { + "name" : { + "type" : "string" + }, + "value" : { + "type" : "integer" + } + }, + "description" : "Enumeration with name for values" }; defs.PerseusReport = { "properties" : { @@ -23142,7 +23175,7 @@ except ApiException as e:
    - Generated 2018-09-22T10:27:51.856+02:00 + Generated 2018-09-25T16:51:16.493+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.cpp index b641d2a1c..e68b32e38 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.cpp @@ -36,6 +36,8 @@ SWGBladeRF2InputReport::SWGBladeRF2InputReport() { m_bandwidth_range_isSet = false; global_gain_range = nullptr; m_global_gain_range_isSet = false; + gain_modes = nullptr; + m_gain_modes_isSet = false; } SWGBladeRF2InputReport::~SWGBladeRF2InputReport() { @@ -44,7 +46,7 @@ SWGBladeRF2InputReport::~SWGBladeRF2InputReport() { void SWGBladeRF2InputReport::init() { - frequency_range = new SWGRange(); + frequency_range = new SWGFrequencyRange(); m_frequency_range_isSet = false; sample_rate_range = new SWGRange(); m_sample_rate_range_isSet = false; @@ -52,6 +54,8 @@ SWGBladeRF2InputReport::init() { m_bandwidth_range_isSet = false; global_gain_range = new SWGRange(); m_global_gain_range_isSet = false; + gain_modes = new QList(); + m_gain_modes_isSet = false; } void @@ -68,6 +72,13 @@ SWGBladeRF2InputReport::cleanup() { if(global_gain_range != nullptr) { delete global_gain_range; } + if(gain_modes != nullptr) { + auto arr = gain_modes; + for(auto o: *arr) { + delete o; + } + delete gain_modes; + } } SWGBladeRF2InputReport* @@ -81,7 +92,7 @@ SWGBladeRF2InputReport::fromJson(QString &json) { void SWGBladeRF2InputReport::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&frequency_range, pJson["frequencyRange"], "SWGRange", "SWGRange"); + ::SWGSDRangel::setValue(&frequency_range, pJson["frequencyRange"], "SWGFrequencyRange", "SWGFrequencyRange"); ::SWGSDRangel::setValue(&sample_rate_range, pJson["sampleRateRange"], "SWGRange", "SWGRange"); @@ -89,6 +100,8 @@ SWGBladeRF2InputReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&global_gain_range, pJson["globalGainRange"], "SWGRange", "SWGRange"); + + ::SWGSDRangel::setValue(&gain_modes, pJson["gainModes"], "QList", "SWGNamedEnum"); } QString @@ -106,7 +119,7 @@ QJsonObject* SWGBladeRF2InputReport::asJsonObject() { QJsonObject* obj = new QJsonObject(); if((frequency_range != nullptr) && (frequency_range->isSet())){ - toJsonValue(QString("frequencyRange"), frequency_range, obj, QString("SWGRange")); + toJsonValue(QString("frequencyRange"), frequency_range, obj, QString("SWGFrequencyRange")); } if((sample_rate_range != nullptr) && (sample_rate_range->isSet())){ toJsonValue(QString("sampleRateRange"), sample_rate_range, obj, QString("SWGRange")); @@ -117,16 +130,19 @@ SWGBladeRF2InputReport::asJsonObject() { if((global_gain_range != nullptr) && (global_gain_range->isSet())){ toJsonValue(QString("globalGainRange"), global_gain_range, obj, QString("SWGRange")); } + if(gain_modes->size() > 0){ + toJsonArray((QList*)gain_modes, obj, "gainModes", "SWGNamedEnum"); + } return obj; } -SWGRange* +SWGFrequencyRange* SWGBladeRF2InputReport::getFrequencyRange() { return frequency_range; } void -SWGBladeRF2InputReport::setFrequencyRange(SWGRange* frequency_range) { +SWGBladeRF2InputReport::setFrequencyRange(SWGFrequencyRange* frequency_range) { this->frequency_range = frequency_range; this->m_frequency_range_isSet = true; } @@ -161,6 +177,16 @@ SWGBladeRF2InputReport::setGlobalGainRange(SWGRange* global_gain_range) { this->m_global_gain_range_isSet = true; } +QList* +SWGBladeRF2InputReport::getGainModes() { + return gain_modes; +} +void +SWGBladeRF2InputReport::setGainModes(QList* gain_modes) { + this->gain_modes = gain_modes; + this->m_gain_modes_isSet = true; +} + bool SWGBladeRF2InputReport::isSet(){ @@ -170,6 +196,7 @@ SWGBladeRF2InputReport::isSet(){ if(sample_rate_range != nullptr && sample_rate_range->isSet()){ isObjectUpdated = true; break;} if(bandwidth_range != nullptr && bandwidth_range->isSet()){ isObjectUpdated = true; break;} if(global_gain_range != nullptr && global_gain_range->isSet()){ isObjectUpdated = true; break;} + if(gain_modes->size() > 0){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.h index 0f8b5d5d9..4aeeb3fcb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputReport.h @@ -22,7 +22,10 @@ #include +#include "SWGFrequencyRange.h" +#include "SWGNamedEnum.h" #include "SWGRange.h" +#include #include "SWGObject.h" #include "export.h" @@ -42,8 +45,8 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGBladeRF2InputReport* fromJson(QString &jsonString) override; - SWGRange* getFrequencyRange(); - void setFrequencyRange(SWGRange* frequency_range); + SWGFrequencyRange* getFrequencyRange(); + void setFrequencyRange(SWGFrequencyRange* frequency_range); SWGRange* getSampleRateRange(); void setSampleRateRange(SWGRange* sample_rate_range); @@ -54,11 +57,14 @@ public: SWGRange* getGlobalGainRange(); void setGlobalGainRange(SWGRange* global_gain_range); + QList* getGainModes(); + void setGainModes(QList* gain_modes); + virtual bool isSet() override; private: - SWGRange* frequency_range; + SWGFrequencyRange* frequency_range; bool m_frequency_range_isSet; SWGRange* sample_rate_range; @@ -70,6 +76,9 @@ private: SWGRange* global_gain_range; bool m_global_gain_range_isSet; + QList* gain_modes; + bool m_gain_modes_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequencyRange.cpp b/swagger/sdrangel/code/qt5/client/SWGFrequencyRange.cpp new file mode 100644 index 000000000..e50e1faef --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFrequencyRange.cpp @@ -0,0 +1,148 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGFrequencyRange.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGFrequencyRange::SWGFrequencyRange(QString* json) { + init(); + this->fromJson(*json); +} + +SWGFrequencyRange::SWGFrequencyRange() { + min = 0L; + m_min_isSet = false; + max = 0L; + m_max_isSet = false; + step = 0; + m_step_isSet = false; +} + +SWGFrequencyRange::~SWGFrequencyRange() { + this->cleanup(); +} + +void +SWGFrequencyRange::init() { + min = 0L; + m_min_isSet = false; + max = 0L; + m_max_isSet = false; + step = 0; + m_step_isSet = false; +} + +void +SWGFrequencyRange::cleanup() { + + + +} + +SWGFrequencyRange* +SWGFrequencyRange::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGFrequencyRange::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&min, pJson["min"], "qint64", ""); + + ::SWGSDRangel::setValue(&max, pJson["max"], "qint64", ""); + + ::SWGSDRangel::setValue(&step, pJson["step"], "qint32", ""); + +} + +QString +SWGFrequencyRange::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGFrequencyRange::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_min_isSet){ + obj->insert("min", QJsonValue(min)); + } + if(m_max_isSet){ + obj->insert("max", QJsonValue(max)); + } + if(m_step_isSet){ + obj->insert("step", QJsonValue(step)); + } + + return obj; +} + +qint64 +SWGFrequencyRange::getMin() { + return min; +} +void +SWGFrequencyRange::setMin(qint64 min) { + this->min = min; + this->m_min_isSet = true; +} + +qint64 +SWGFrequencyRange::getMax() { + return max; +} +void +SWGFrequencyRange::setMax(qint64 max) { + this->max = max; + this->m_max_isSet = true; +} + +qint32 +SWGFrequencyRange::getStep() { + return step; +} +void +SWGFrequencyRange::setStep(qint32 step) { + this->step = step; + this->m_step_isSet = true; +} + + +bool +SWGFrequencyRange::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_min_isSet){ isObjectUpdated = true; break;} + if(m_max_isSet){ isObjectUpdated = true; break;} + if(m_step_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGFrequencyRange.h b/swagger/sdrangel/code/qt5/client/SWGFrequencyRange.h new file mode 100644 index 000000000..0967d0706 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGFrequencyRange.h @@ -0,0 +1,70 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGFrequencyRange.h + * + * An frequency range with 64 bit support for min and max + */ + +#ifndef SWGFrequencyRange_H_ +#define SWGFrequencyRange_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGFrequencyRange: public SWGObject { +public: + SWGFrequencyRange(); + SWGFrequencyRange(QString* json); + virtual ~SWGFrequencyRange(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGFrequencyRange* fromJson(QString &jsonString) override; + + qint64 getMin(); + void setMin(qint64 min); + + qint64 getMax(); + void setMax(qint64 max); + + qint32 getStep(); + void setStep(qint32 step); + + + virtual bool isSet() override; + +private: + qint64 min; + bool m_min_isSet; + + qint64 max; + bool m_max_isSet; + + qint32 step; + bool m_step_isSet; + +}; + +} + +#endif /* SWGFrequencyRange_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index d49a92e09..1ac604c66 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -60,6 +60,7 @@ #include "SWGFileSourceSettings.h" #include "SWGFrequency.h" #include "SWGFrequencyBand.h" +#include "SWGFrequencyRange.h" #include "SWGGain.h" #include "SWGHackRFInputSettings.h" #include "SWGHackRFOutputSettings.h" @@ -76,6 +77,7 @@ #include "SWGNFMDemodSettings.h" #include "SWGNFMModReport.h" #include "SWGNFMModSettings.h" +#include "SWGNamedEnum.h" #include "SWGPerseusReport.h" #include "SWGPerseusSettings.h" #include "SWGPlutoSdrInputReport.h" @@ -258,6 +260,9 @@ namespace SWGSDRangel { if(QString("SWGFrequencyBand").compare(type) == 0) { return new SWGFrequencyBand(); } + if(QString("SWGFrequencyRange").compare(type) == 0) { + return new SWGFrequencyRange(); + } if(QString("SWGGain").compare(type) == 0) { return new SWGGain(); } @@ -306,6 +311,9 @@ namespace SWGSDRangel { if(QString("SWGNFMModSettings").compare(type) == 0) { return new SWGNFMModSettings(); } + if(QString("SWGNamedEnum").compare(type) == 0) { + return new SWGNamedEnum(); + } if(QString("SWGPerseusReport").compare(type) == 0) { return new SWGPerseusReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGNamedEnum.cpp b/swagger/sdrangel/code/qt5/client/SWGNamedEnum.cpp new file mode 100644 index 000000000..2220a6bd7 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNamedEnum.cpp @@ -0,0 +1,129 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGNamedEnum.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGNamedEnum::SWGNamedEnum(QString* json) { + init(); + this->fromJson(*json); +} + +SWGNamedEnum::SWGNamedEnum() { + name = nullptr; + m_name_isSet = false; + value = 0; + m_value_isSet = false; +} + +SWGNamedEnum::~SWGNamedEnum() { + this->cleanup(); +} + +void +SWGNamedEnum::init() { + name = new QString(""); + m_name_isSet = false; + value = 0; + m_value_isSet = false; +} + +void +SWGNamedEnum::cleanup() { + if(name != nullptr) { + delete name; + } + +} + +SWGNamedEnum* +SWGNamedEnum::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGNamedEnum::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&name, pJson["name"], "QString", "QString"); + + ::SWGSDRangel::setValue(&value, pJson["value"], "qint32", ""); + +} + +QString +SWGNamedEnum::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGNamedEnum::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(name != nullptr && *name != QString("")){ + toJsonValue(QString("name"), name, obj, QString("QString")); + } + if(m_value_isSet){ + obj->insert("value", QJsonValue(value)); + } + + return obj; +} + +QString* +SWGNamedEnum::getName() { + return name; +} +void +SWGNamedEnum::setName(QString* name) { + this->name = name; + this->m_name_isSet = true; +} + +qint32 +SWGNamedEnum::getValue() { + return value; +} +void +SWGNamedEnum::setValue(qint32 value) { + this->value = value; + this->m_value_isSet = true; +} + + +bool +SWGNamedEnum::isSet(){ + bool isObjectUpdated = false; + do{ + if(name != nullptr && *name != QString("")){ isObjectUpdated = true; break;} + if(m_value_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGNamedEnum.h b/swagger/sdrangel/code/qt5/client/SWGNamedEnum.h new file mode 100644 index 000000000..82a762d07 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNamedEnum.h @@ -0,0 +1,65 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGNamedEnum.h + * + * Enumeration with name for values + */ + +#ifndef SWGNamedEnum_H_ +#define SWGNamedEnum_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGNamedEnum: public SWGObject { +public: + SWGNamedEnum(); + SWGNamedEnum(QString* json); + virtual ~SWGNamedEnum(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGNamedEnum* fromJson(QString &jsonString) override; + + QString* getName(); + void setName(QString* name); + + qint32 getValue(); + void setValue(qint32 value); + + + virtual bool isSet() override; + +private: + QString* name; + bool m_name_isSet; + + qint32 value; + bool m_value_isSet; + +}; + +} + +#endif /* SWGNamedEnum_H_ */ From 81ad05cb64d79a8a1bdeb538c59d36e97191b514 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 25 Sep 2018 23:43:52 +0200 Subject: [PATCH 795/956] BladerRF2 input support. Global gain handling --- .../bladerf2input/bladerf2input.cpp | 15 ++++++- .../bladerf2input/bladerf2input.h | 25 +++++++++++ .../bladerf2input/bladerf2inputgui.cpp | 44 +++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 26512a1e1..564735988 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -39,6 +39,7 @@ MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgConfigureBladeRF2, Message) MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgFileRecord, Message) MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgReportGainRange, Message) BladeRF2Input::BladeRF2Input(DeviceSourceAPI *deviceAPI) : m_deviceAPI(deviceAPI), @@ -688,7 +689,6 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo bool forwardChangeTxBuddies = false; struct bladerf *dev = m_deviceShared.m_dev->getDev(); - qDebug() << "BladeRF2Input::applySettings: m_dev: " << dev; if ((m_settings.m_dcBlock != settings.m_dcBlock) || (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) @@ -785,8 +785,18 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if (status < 0) { qWarning("BladeRF2Input::applySettings: bladerf_set_frequency(%lld) failed: %s", settings.m_centerFrequency, bladerf_strerror(status)); - } else { + } + else + { qDebug("BladeRF2Input::applySettings: bladerf_set_frequency(%lld)", settings.m_centerFrequency); + + if (getMessageQueueToGUI()) + { + int min, max, step; + getGlobalGainRange(min, max, step); + MsgReportGainRange *msg = MsgReportGainRange::create(min, max, step); + getMessageQueueToGUI()->push(msg); + } } } } @@ -803,6 +813,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if (dev) { +// qDebug("BladeRF2Input::applySettings: channel: %d gain: %d", m_deviceShared.m_channel, settings.m_globalGain); int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), settings.m_globalGain); if (status < 0) { diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h index 0cd59b212..c9ee6caba 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.h +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -94,6 +94,31 @@ public: { } }; + class MsgReportGainRange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMin() const { return m_min; } + int getMax() const { return m_max; } + int getStep() const { return m_step; } + + static MsgReportGainRange* create(int min, int max, int step) { + return new MsgReportGainRange(min, max, step); + } + + protected: + int m_min; + int m_max; + int m_step; + + MsgReportGainRange(int min, int max, int step) : + Message(), + m_min(min), + m_max(max), + m_step(step) + {} + }; + BladeRF2Input(DeviceSourceAPI *deviceAPI); virtual ~BladeRF2Input(); virtual void destroy(); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp index 6307e3f78..4f9b33ce4 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -62,11 +62,21 @@ BladeRF2InputGui::BladeRF2InputGui(DeviceUISet *deviceUISet, QWidget* parent) : if (m_gainModes) { + ui->gainMode->blockSignals(true); + for (int i = 0; i < m_nbGainModes; i++) { ui->gainMode->addItem(tr("%1").arg(m_gainModes[i].name)); } + + ui->gainMode->blockSignals(false); } + m_sampleSource->getGlobalGainRange(min, max, step); + ui->gain->setMinimum(min); + ui->gain->setMaximum(max); + ui->gain->setPageStep(step); + ui->gain->setSingleStep(step); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(500); @@ -143,8 +153,25 @@ bool BladeRF2InputGui::handleMessage(const Message& message) const BladeRF2Input::MsgConfigureBladeRF2& cfg = (BladeRF2Input::MsgConfigureBladeRF2&) message; m_settings = cfg.getSettings(); blockApplySettings(true); + int min, max, step; + m_sampleSource->getGlobalGainRange(min, max, step); + ui->gain->setMinimum(min); + ui->gain->setMaximum(max); + ui->gain->setPageStep(step); + ui->gain->setSingleStep(step); displaySettings(); blockApplySettings(false); + + return true; + } + else if (BladeRF2Input::MsgReportGainRange::match(message)) + { + const BladeRF2Input::MsgReportGainRange& cfg = (BladeRF2Input::MsgReportGainRange&) message; + ui->gain->setMinimum(cfg.getMin()); + ui->gain->setMaximum(cfg.getMax()); + ui->gain->setSingleStep(cfg.getStep()); + ui->gain->setPageStep(cfg.getStep()); + return true; } else if (BladeRF2Input::MsgStartStop::match(message)) @@ -211,6 +238,7 @@ void BladeRF2InputGui::displaySettings() ui->decim->setCurrentIndex(m_settings.m_log2Decim); ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); + ui->gainText->setText(tr("%1").arg(m_settings.m_globalGain)); ui->gain->setValue(m_settings.m_globalGain); blockApplySettings(false); @@ -274,6 +302,22 @@ void BladeRF2InputGui::on_fcPos_currentIndexChanged(int index) } } +void BladeRF2InputGui::on_gainMode_currentIndexChanged(int index) +{ + if (index < m_nbGainModes) + { + m_settings.m_gainMode = m_gainModes[index].mode; + sendSettings(); + } +} + +void BladeRF2InputGui::on_gain_valueChanged(int value) +{ + ui->gainText->setText(tr("%1").arg(value)); + m_settings.m_globalGain = value; + sendSettings(); +} + void BladeRF2InputGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) From 4634fb481dc7ff9971461b52d329c5a73e83efb3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 26 Sep 2018 01:54:23 +0200 Subject: [PATCH 796/956] BladerRF2 input support. Fixed gain modes handling. Cosmetic changes --- .../bladerf2input/bladerf2input.cpp | 41 +++++++++---------- .../bladerf2input/bladerf2input.h | 11 +++-- .../bladerf2input/bladerf2inputgui.cpp | 32 ++++++++------- .../bladerf2input/bladerf2inputgui.h | 2 - .../bladerf2input/bladerf2inputgui.ui | 31 ++++++++++---- 5 files changed, 67 insertions(+), 50 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 564735988..6cbbd4d77 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -50,6 +50,19 @@ BladeRF2Input::BladeRF2Input(DeviceSourceAPI *deviceAPI) : { openDevice(); + if (m_deviceShared.m_dev) + { + const bladerf_gain_modes *modes = 0; + int nbModes = m_deviceShared.m_dev->getGainModesRx(&modes); + + if (modes) + { + for (int i = 0; i < nbModes; i++) { + m_gainModes.push_back(GainMode{QString(modes[i].name), modes[i].mode}); + } + } + } + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -510,19 +523,6 @@ void BladeRF2Input::getGlobalGainRange(int& min, int& max, int& step) } } -const bladerf_gain_modes *BladeRF2Input::getGainModes(int& nbGains) -{ - const bladerf_gain_modes *modes = 0; - - if (m_deviceShared.m_dev) { - nbGains = m_deviceShared.m_dev->getGainModesRx(&modes); - } else { - nbGains = 0; - } - - return modes; -} - bool BladeRF2Input::handleMessage(const Message& message) { if (MsgConfigureBladeRF2::match(message)) @@ -1025,17 +1025,14 @@ void BladeRF2Input::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& respo response.getBladeRf2InputReport()->setGainModes(new QList); - int nbModes; - const bladerf_gain_modes *modes = getGainModes(nbModes); + const std::vector& modes = getGainModes(); + std::vector::const_iterator it = modes.begin(); - if (modes) + for (; it != modes.end(); ++it) { - for (int i = 0; i < nbModes; modes++) - { - response.getBladeRf2InputReport()->getGainModes()->append(new SWGSDRangel::SWGNamedEnum); - response.getBladeRf2InputReport()->getGainModes()->back()->setName(new QString(modes[i].name)); - response.getBladeRf2InputReport()->getGainModes()->back()->setValue(modes[i].mode); - } + response.getBladeRf2InputReport()->getGainModes()->append(new SWGSDRangel::SWGNamedEnum); + response.getBladeRf2InputReport()->getGainModes()->back()->setName(new QString(it->m_name)); + response.getBladeRf2InputReport()->getGainModes()->back()->setValue(it->m_value); } } } diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h index c9ee6caba..af61d5776 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.h +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -119,6 +119,12 @@ public: {} }; + struct GainMode + { + QString m_name; + int m_value; + }; + BladeRF2Input(DeviceSourceAPI *deviceAPI); virtual ~BladeRF2Input(); virtual void destroy(); @@ -142,7 +148,7 @@ public: void getSampleRateRange(int& min, int& max, int& step); void getBandwidthRange(int& min, int& max, int& step); void getGlobalGainRange(int& min, int& max, int& step); - const bladerf_gain_modes *getGainModes(int& nbGains); + const std::vector& getGainModes() { return m_gainModes; } virtual bool handleMessage(const Message& message); @@ -178,8 +184,7 @@ private: DeviceBladeRF2Shared m_deviceShared; BladeRF2InputThread *m_thread; FileRecord *m_fileSink; //!< File sink to record device I/Q output - bladerf_gain_modes **m_gainModes; - int m_nbGainModes; + std::vector m_gainModes; bool openDevice(); void closeDevice(); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp index 4f9b33ce4..2f7d0dabc 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -56,27 +56,27 @@ BladeRF2InputGui::BladeRF2InputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleSource->getBandwidthRange(min, max, step); ui->bandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); - ui->bandwidth->setValueRange(6, min/1000, max/1000); + ui->bandwidth->setValueRange(5, min/1000, max/1000); - m_gainModes = m_sampleSource->getGainModes(m_nbGainModes); + const std::vector& modes = m_sampleSource->getGainModes(); + std::vector::const_iterator it = modes.begin(); - if (m_gainModes) - { - ui->gainMode->blockSignals(true); + ui->gainMode->blockSignals(true); - for (int i = 0; i < m_nbGainModes; i++) { - ui->gainMode->addItem(tr("%1").arg(m_gainModes[i].name)); - } - - ui->gainMode->blockSignals(false); + for (; it != modes.end(); ++it) { + ui->gainMode->addItem(it->m_name); } + ui->gainMode->blockSignals(false); + m_sampleSource->getGlobalGainRange(min, max, step); ui->gain->setMinimum(min); ui->gain->setMaximum(max); ui->gain->setPageStep(step); ui->gain->setSingleStep(step); + ui->label_decim->setText(QString::fromUtf8("D\u2193")); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(500); @@ -238,7 +238,7 @@ void BladeRF2InputGui::displaySettings() ui->decim->setCurrentIndex(m_settings.m_log2Decim); ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); - ui->gainText->setText(tr("%1").arg(m_settings.m_globalGain)); + ui->gainText->setText(tr("%1 dB").arg(m_settings.m_globalGain)); ui->gain->setValue(m_settings.m_globalGain); blockApplySettings(false); @@ -304,16 +304,20 @@ void BladeRF2InputGui::on_fcPos_currentIndexChanged(int index) void BladeRF2InputGui::on_gainMode_currentIndexChanged(int index) { - if (index < m_nbGainModes) + const std::vector& modes = m_sampleSource->getGainModes(); + unsigned int uindex = index < 0 ? 0 : (unsigned int) index; + + if (uindex < modes.size()) { - m_settings.m_gainMode = m_gainModes[index].mode; + BladeRF2Input::GainMode mode = modes[index]; + m_settings.m_gainMode = mode.m_value; sendSettings(); } } void BladeRF2InputGui::on_gain_valueChanged(int value) { - ui->gainText->setText(tr("%1").arg(value)); + ui->gainText->setText(tr("%1 dB").arg(value)); m_settings.m_globalGain = value; sendSettings(); } diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.h b/plugins/samplesource/bladerf2input/bladerf2inputgui.h index 3f6a0df99..f52c82022 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.h @@ -65,8 +65,6 @@ private: quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_lastEngineState; MessageQueue m_inputMessageQueue; - const struct bladerf_gain_modes *m_gainModes; - int m_nbGainModes; void displaySettings(); void sendSettings(); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui index b39e828d9..68aad81a2 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui @@ -6,8 +6,8 @@ 0 0 - 310 - 250 + 350 + 200 @@ -18,8 +18,8 @@ - 310 - 250 + 350 + 200 @@ -265,6 +265,9 @@ PointingHandCursor + + RF bandwidth + @@ -330,6 +333,9 @@ PointingHandCursor + + Device sample rate + @@ -384,7 +390,7 @@ - Dec + D @@ -442,19 +448,26 @@ - + 3 + + Gain value + Qt::Horizontal - + + + Gain mode + + @@ -467,12 +480,12 @@ - 25 + 45 0 - 000 + 000 dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter From 07bd587f6ccd9df941c06cf6295e7c8cb4bc6d13 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 26 Sep 2018 03:07:55 +0200 Subject: [PATCH 797/956] BladeRF2 input: more debug messages. Fixed thread channel destructor: do not delete the sample FIFO that is not owned by the thread --- plugins/samplesource/bladerf2input/bladerf2input.cpp | 12 ++++++++++++ .../samplesource/bladerf2input/bladerf2inputthread.h | 6 +----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 6cbbd4d77..b24431207 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -304,10 +304,14 @@ bool BladeRF2Input::start() if (bladerf2InputThread) // if thread is already allocated { + qDebug("BladerfInput::start: thread is owned by a buddy"); + int nbOriginalChannels = bladerf2InputThread->getNbChannels(); if (m_deviceShared.m_channel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread { + qDebug("BladerfInput::start: expand channels. Re-allocate thread and take ownership"); + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels]; unsigned int *log2Decims = new unsigned int[nbOriginalChannels]; int *fcPoss = new int[nbOriginalChannels]; @@ -341,9 +345,14 @@ bool BladeRF2Input::start() needsStart = true; } + else + { + qDebug("BladerfInput::start: keep buddy thread"); + } } else // first allocation { + qDebug("BladerfInput::start: allocate thread and take ownership"); bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); m_thread = bladerf2InputThread; // take ownership needsStart = true; @@ -382,6 +391,7 @@ void BladeRF2Input::stop() if (nbOriginalChannels == 1) // SI mode => just stop and delete the thread { + qDebug("BladeRF2Input::stop: SI mode. Just stop and delete the thread"); bladerf2InputThread->stopWork(); delete bladerf2InputThread; m_thread = 0; @@ -396,6 +406,7 @@ void BladeRF2Input::stop() } else if (m_deviceShared.m_channel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread { + qDebug("BladeRF2Input::stop: MI mode. Reduce by deleting and re-creating the thread"); bladerf2InputThread->stopWork(); SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; @@ -431,6 +442,7 @@ void BladeRF2Input::stop() } else // remove channel from existing thread { + qDebug("BladeRF2Input::stop: MI mode. Thread not owned by source. Just remove FIFO reference"); bladerf2InputThread->setFifo(m_deviceShared.m_channel, 0); // remove FIFO } diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.h b/plugins/samplesource/bladerf2input/bladerf2inputthread.h index 02b29d9e4..ad033c768 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.h @@ -66,11 +66,7 @@ private: {} ~Channel() - { - if (m_sampleFifo) { - delete[] m_sampleFifo; - } - } + {} }; QMutex m_startWaitMutex; From 464a9fde0a4c89373184c0d9a99052a717c5880b Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 26 Sep 2018 03:20:24 +0200 Subject: [PATCH 798/956] BladeRF2 input: do not reference m_deviceShared.m_channel outside open/close device --- .../bladerf2input/bladerf2input.cpp | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index b24431207..8f27fd817 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -299,6 +299,7 @@ bool BladeRF2Input::start() return false; } + int requestedChannel = m_deviceAPI->getItemIndex(); BladeRF2InputThread *bladerf2InputThread = findThread(); bool needsStart = false; @@ -308,7 +309,7 @@ bool BladeRF2Input::start() int nbOriginalChannels = bladerf2InputThread->getNbChannels(); - if (m_deviceShared.m_channel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread { qDebug("BladerfInput::start: expand channels. Re-allocate thread and take ownership"); @@ -325,7 +326,7 @@ bool BladeRF2Input::start() bladerf2InputThread->stopWork(); delete bladerf2InputThread; - bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); m_thread = bladerf2InputThread; // take ownership for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references @@ -353,14 +354,14 @@ bool BladeRF2Input::start() else // first allocation { qDebug("BladerfInput::start: allocate thread and take ownership"); - bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), m_deviceShared.m_channel+1); + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); m_thread = bladerf2InputThread; // take ownership needsStart = true; } - bladerf2InputThread->setFifo(m_deviceShared.m_channel, &m_sampleFifo); - bladerf2InputThread->setLog2Decimation(m_deviceShared.m_channel, m_settings.m_log2Decim); - bladerf2InputThread->setFcPos(m_deviceShared.m_channel, (int) m_settings.m_fcPos); + bladerf2InputThread->setFifo(requestedChannel, &m_sampleFifo); + bladerf2InputThread->setLog2Decimation(requestedChannel, m_settings.m_log2Decim); + bladerf2InputThread->setFcPos(requestedChannel, (int) m_settings.m_fcPos); if (needsStart) { bladerf2InputThread->startWork(); @@ -380,6 +381,7 @@ void BladeRF2Input::stop() return; } + int requestedChannel = m_deviceAPI->getItemIndex(); BladeRF2InputThread *bladerf2InputThread = findThread(); if (bladerf2InputThread == 0) // no thread allocated @@ -404,7 +406,7 @@ void BladeRF2Input::stop() ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); } } - else if (m_deviceShared.m_channel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread + else if (requestedChannel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread { qDebug("BladeRF2Input::stop: MI mode. Reduce by deleting and re-creating the thread"); bladerf2InputThread->stopWork(); @@ -443,7 +445,7 @@ void BladeRF2Input::stop() else // remove channel from existing thread { qDebug("BladeRF2Input::stop: MI mode. Thread not owned by source. Just remove FIFO reference"); - bladerf2InputThread->setFifo(m_deviceShared.m_channel, 0); // remove FIFO + bladerf2InputThread->setFifo(requestedChannel, 0); // remove FIFO } m_running = false; @@ -565,9 +567,12 @@ bool BladeRF2Input::handleMessage(const Message& message) if (dev) // The BladeRF device must have been open to do so { + int requestedChannel = m_deviceAPI->getItemIndex(); + if (report.getRxElseTx()) // Rx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth { - status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_uint); + + status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); if (status < 0) { qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); @@ -575,7 +580,7 @@ bool BladeRF2Input::handleMessage(const Message& message) settings.m_devSampleRate = tmp_uint; } - status = bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_uint64); + status = bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint64); if (status < 0) { qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_frequency error: %s", bladerf_strerror(status)); @@ -583,7 +588,7 @@ bool BladeRF2Input::handleMessage(const Message& message) settings.m_centerFrequency = tmp_uint64; } - status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_uint); + status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); if (status < 0) { qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_bandwidth error: %s", bladerf_strerror(status)); @@ -591,7 +596,7 @@ bool BladeRF2Input::handleMessage(const Message& message) settings.m_bandwidth = tmp_uint; } - status = bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_gain_mode); + status = bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_gain_mode); if (status < 0) { qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_gain_mode error: %s", bladerf_strerror(status)); @@ -599,7 +604,7 @@ bool BladeRF2Input::handleMessage(const Message& message) settings.m_gainMode = (int) tmp_gain_mode; } - status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_int); + status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_int); if (status < 0) { qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_gain error: %s", bladerf_strerror(status)); @@ -607,7 +612,7 @@ bool BladeRF2Input::handleMessage(const Message& message) settings.m_globalGain = tmp_int; } - status = bladerf_get_bias_tee(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_bool); + status = bladerf_get_bias_tee(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_bool); if (status < 0) { qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_bias_tee error: %s", bladerf_strerror(status)); @@ -617,7 +622,7 @@ bool BladeRF2Input::handleMessage(const Message& message) } else // Tx buddy change: check for sample rate change only { - status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), &tmp_uint); + status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); if (status < 0) { qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); @@ -701,6 +706,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo bool forwardChangeTxBuddies = false; struct bladerf *dev = m_deviceShared.m_dev->getDev(); + int requestedChannel = m_deviceAPI->getItemIndex(); if ((m_settings.m_dcBlock != settings.m_dcBlock) || (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) @@ -717,7 +723,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if (dev != 0) { unsigned int actualSamplerate; - int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), settings.m_devSampleRate, &actualSamplerate); + int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_RX(requestedChannel), settings.m_devSampleRate, &actualSamplerate); if (status < 0) { @@ -738,7 +744,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if (dev != 0) { unsigned int actualBandwidth; - int status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(m_deviceShared.m_channel), settings.m_bandwidth, &actualBandwidth); + int status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(requestedChannel), settings.m_bandwidth, &actualBandwidth); if(status < 0) { @@ -758,7 +764,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if (inputThread != 0) { - inputThread->setFcPos(m_deviceShared.m_channel, (int) settings.m_fcPos); + inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); qDebug() << "BladeRF2Input::applySettings: set fc pos (enum) to " << (int) settings.m_fcPos; } } @@ -770,7 +776,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if (inputThread != 0) { - inputThread->setLog2Decimation(m_deviceShared.m_channel, settings.m_log2Decim); + inputThread->setLog2Decimation(requestedChannel, settings.m_log2Decim); qDebug() << "BladeRF2Input::applySettings: set decimation to " << (1< Date: Wed, 26 Sep 2018 08:54:40 +0200 Subject: [PATCH 799/956] BladeRF2 input: fix MI start/stop --- .../bladerf2input/bladerf2input.cpp | 68 ++++++------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 8f27fd817..bb9c722f4 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -119,24 +119,6 @@ bool BladeRF2Input::openDevice() } m_deviceShared.m_dev = device; - int requestedChannel = m_deviceAPI->getItemIndex(); - - if (requestedChannel == deviceBladeRF2Shared->m_channel) - { - qCritical("BladeRF2Input::openDevice: channel %u already in use", requestedChannel); - return false; - } - - if (!device->openRx(requestedChannel)) - { - qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); - return false; - } - else - { - m_deviceShared.m_channel = requestedChannel; - qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); - } } // look for Tx buddies and get reference to the device object // allocate the Rx channel unconditionally @@ -162,18 +144,6 @@ bool BladeRF2Input::openDevice() } m_deviceShared.m_dev = device; - int requestedChannel = m_deviceAPI->getItemIndex(); - - if (!device->openRx(requestedChannel)) - { - qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); - return false; - } - else - { - m_deviceShared.m_channel = requestedChannel; - qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); - } } // There are no buddies then create the first BladeRF2 device // allocate the Rx channel unconditionally @@ -190,21 +160,9 @@ bool BladeRF2Input::openDevice() qCritical("BladeRF2Input::openDevice: cannot open BladeRF2 device"); return false; } - - int requestedChannel = m_deviceAPI->getItemIndex(); - - if (!m_deviceShared.m_dev->openRx(requestedChannel)) - { - qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); - return false; - } - else - { - m_deviceShared.m_channel = requestedChannel; - qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); - } } + m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel m_deviceShared.m_source = this; m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API return true; @@ -224,7 +182,7 @@ void BladeRF2Input::closeDevice() moveThreadToBuddy(); } - m_deviceShared.m_channel = -1; + m_deviceShared.m_channel = -1; // publicly release channel m_deviceShared.m_source = 0; // No buddies so effectively close the device @@ -305,7 +263,7 @@ bool BladeRF2Input::start() if (bladerf2InputThread) // if thread is already allocated { - qDebug("BladerfInput::start: thread is owned by a buddy"); + qDebug("BladerfInput::start: thread is already allocated"); int nbOriginalChannels = bladerf2InputThread->getNbChannels(); @@ -363,7 +321,19 @@ bool BladeRF2Input::start() bladerf2InputThread->setLog2Decimation(requestedChannel, m_settings.m_log2Decim); bladerf2InputThread->setFcPos(requestedChannel, (int) m_settings.m_fcPos); - if (needsStart) { + if (needsStart) + { + qDebug("BladerfInput::start: enabling channel(s) and (re)sart buddy thread"); + + int nbChannels = bladerf2InputThread->getNbChannels(); + + for (int i = 0; i < nbChannels; i++) + { + if (!m_deviceShared.m_dev->openRx(i)) { + qCritical("BladeRF2Input::start: channel %u cannot be enabled", i); + } + } + bladerf2InputThread->startWork(); } @@ -384,8 +354,7 @@ void BladeRF2Input::stop() int requestedChannel = m_deviceAPI->getItemIndex(); BladeRF2InputThread *bladerf2InputThread = findThread(); - if (bladerf2InputThread == 0) // no thread allocated - { + if (bladerf2InputThread == 0) { // no thread allocated return; } @@ -405,6 +374,8 @@ void BladeRF2Input::stop() for (; it != sourceBuddies.end(); ++it) { ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); } + + m_deviceShared.m_dev->closeRx(0); // close the unique channel } else if (requestedChannel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread { @@ -440,6 +411,7 @@ void BladeRF2Input::stop() ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); } + m_deviceShared.m_dev->closeRx(requestedChannel); // close the last channel bladerf2InputThread->startWork(); } else // remove channel from existing thread From aa6834a6e97c83a5035deef2cdf3409572553566 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 26 Sep 2018 14:56:33 +0200 Subject: [PATCH 800/956] BladeRF2 input: added details about the start and stop stream methods --- .../bladerf2input/bladerf2input.cpp | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index bb9c722f4..133b922bc 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -251,6 +251,36 @@ void BladeRF2Input::moveThreadToBuddy() bool BladeRF2Input::start() { + // There is a single thread per physical device (Rx side). This thread is unique and referenced by a unique + // buddy in the group of source buddies associated with this physical device. + // + // This start method is responsible for managing the thread and channel enabling when the streaming of a Rx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following (just 1 in BladeRF 2 case) + // + // The BladeRF support library lets you work in two possible modes: + // - Single Input (SI) with only one channel streaming. This HAS to be channel 0. + // - Multiple Input (MI) with two channels streaming using interleaved samples. It MUST be in this configuration if channel 1 + // is used irrespective of what you actually do with samples coming from channel 0. When we will run with only channel 1 + // streaming from the client perspective the channel 0 will actually be enabled and streaming but its samples will + // just be disregarded. + // + // It manages the transition form SI where only one channel (the first or channel 0) should be running to the + // Multiple Input (MI) if the requested channel is 1. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply removes its FIFO reference + // so that the samples are not fed to the FIFO anymore and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SI mode) and 2 if channel 1 is requested (MI mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + if (!m_deviceShared.m_dev) { qDebug("BladerfInput::start: no device object"); @@ -347,6 +377,22 @@ bool BladeRF2Input::start() void BladeRF2Input::stop() { + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Rx channel is stopped + // + // If the thread is currently managing only one channel (SI mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MI mode) and we are removing the last channel. The transition + // from MI to SI or reduction of MI size is handled by stopping the thread, deleting it and creating a new one + // with one channel less. Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MI mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that it will not stream into this FIFO + // anymore. In this case the channel is not closed (disabled) so that other channels can continue with the + // same configuration. The device continues streaming on this channel but the samples are simply dropped (by + // removing FIFO reference). + if (!m_running) { return; } @@ -416,7 +462,7 @@ void BladeRF2Input::stop() } else // remove channel from existing thread { - qDebug("BladeRF2Input::stop: MI mode. Thread not owned by source. Just remove FIFO reference"); + qDebug("BladeRF2Input::stop: MI mode. Not changing MI configuration. Just remove FIFO reference"); bladerf2InputThread->setFifo(requestedChannel, 0); // remove FIFO } From 151f21f775d3b4ee1bb14e35743f42912e7af719 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 26 Sep 2018 14:58:55 +0200 Subject: [PATCH 801/956] BladeRF2 input: set the appropriate total number of Rx channels in device enumeration process --- plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp index 76a611e62..a3536a860 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp @@ -98,7 +98,7 @@ PluginInterface::SamplingDevices Blderf2InputPlugin::enumSampleSources() i, PluginInterface::SamplingDevice::PhysicalDevice, true, - 1, + nbRxChannels, j)); } } From 2373dcf19760c2ca966e5f2d54657cf4951fd0fb Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 26 Sep 2018 18:59:52 +0200 Subject: [PATCH 802/956] libbladerRF2: Windows support with OOT build of libbladeRF --- devices/devices.pro | 13 + libbladerf/libbladerf.pro | 192 ++++++---- libbladerf/mingw/common/include/host_config.h | 333 ++++++++++++++++++ .../libbladeRF/src/backend/backend_config.h | 88 +++++ .../mingw/libraries/libbladeRF/src/version.h | 12 + .../{bladerfinput.pro => bladerf1input.pro} | 10 +- sdrangel.windows.pro | 6 +- 7 files changed, 576 insertions(+), 78 deletions(-) create mode 100644 libbladerf/mingw/common/include/host_config.h create mode 100644 libbladerf/mingw/libraries/libbladeRF/src/backend/backend_config.h create mode 100644 libbladerf/mingw/libraries/libbladeRF/src/version.h rename plugins/samplesource/bladerf1input/{bladerfinput.pro => bladerf1input.pro} (80%) diff --git a/devices/devices.pro b/devices/devices.pro index 1dd90d844..35aa88312 100644 --- a/devices/devices.pro +++ b/devices/devices.pro @@ -18,6 +18,8 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 macx:QMAKE_LFLAGS += -F/Library/Frameworks +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" CONFIG(macx):LIBHACKRFSRC = "/opt/local/include" CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" @@ -32,6 +34,7 @@ CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" INCLUDEPATH += $$PWD INCLUDEPATH += ../exports INCLUDEPATH += ../sdrbase +INCLUDEPATH += $$LIBBLADERF/include INCLUDEPATH += $$LIBHACKRFSRC INCLUDEPATH += "C:\softs\boost_1_66_0" INCLUDEPATH += "C:\softs\libusb-1.0.20\include" @@ -52,6 +55,10 @@ INCLUDEPATH += $$LIBPERSEUSSRC CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug +!macx:SOURCES += bladerf1/devicebladerf1.cpp\ + bladerf1/devicebladerf1values.cpp\ + bladerf1/devicebladerf1shared.cpp + SOURCES += hackrf/devicehackrf.cpp\ hackrf/devicehackrfvalues.cpp\ hackrf/devicehackrfshared.cpp @@ -66,6 +73,11 @@ SOURCES += limesdr/devicelimesdr.cpp\ plutosdr/deviceplutosdrscan.cpp\ plutosdr/deviceplutosdrshared.cpp +!macx:HEADERS += bladerf1/devicebladerf1.h\ + bladerf1/devicebladerf1param.h\ + bladerf1/devicebladerf1values.h\ + bladerf1/devicebladerf1shared.h + HEADERS += hackrf/devicehackrf.h\ hackrf/devicehackrfparam.h\ hackrf/devicehackrfvalues.h\ @@ -83,6 +95,7 @@ HEADERS += plutosdr/deviceplutosdr.h\ LIBS += -L../sdrbase/$${build_subdir} -lsdrbase !macx { + LIBS += -L$$LIBBLADERF/lib -lbladeRF LIBS += -L../libhackrf/$${build_subdir} -llibhackrf LIBS += -L../liblimesuite/$${build_subdir} -lliblimesuite LIBS += -L../libiio/$${build_subdir} -llibiio diff --git a/libbladerf/libbladerf.pro b/libbladerf/libbladerf.pro index 4919c084f..9a86b9559 100644 --- a/libbladerf/libbladerf.pro +++ b/libbladerf/libbladerf.pro @@ -9,7 +9,7 @@ QT += core TEMPLATE = lib TARGET = libbladerf -DEFINES += BLADERF_OS_WINDOWS=1 +#DEFINES += BLADERF_OS_WINDOWS=1 CONFIG(MINGW32):LIBBLADERFSRC = "C:\softs\bladeRF" CONFIG(MINGW32):LIBBLADERFCOMMONSRC = "C:\softs\bladeRF\host\common" @@ -17,90 +17,142 @@ CONFIG(MINGW32):LIBBLADERFLIBSRC = "C:\softs\bladeRF\host\libraries\libbladeRF" CONFIG(MINGW64):LIBBLADERFSRC = "C:\softs\bladeRF" CONFIG(MINGW64):LIBBLADERFCOMMONSRC = "C:\softs\bladeRF\host\common" CONFIG(MINGW64):LIBBLADERFLIBSRC = "C:\softs\bladeRF\host\libraries\libbladeRF" +INCLUDEPATH += $$PWD/mingw/include +INCLUDEPATH += $$PWD/mingw/common/include +INCLUDEPATH += $$PWD/mingw/libraries/libbladeRF/src +INCLUDEPATH += $$PWD/mingw/libraries/libbladeRF/src/backend INCLUDEPATH += $$LIBBLADERFLIBSRC/include INCLUDEPATH += $$LIBBLADERFLIBSRC/src INCLUDEPATH += $$LIBBLADERFSRC/firmware_common INCLUDEPATH += $$LIBBLADERFSRC/fpga_common/include INCLUDEPATH += $$LIBBLADERFCOMMONSRC/include INCLUDEPATH += $$LIBBLADERFCOMMONSRC/include/windows -INCLUDEPATH += $$PWD/include -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.19\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.19\include\libusb-1.0" -SOURCES = $$LIBBLADERFLIBSRC/src/async.c\ - $$LIBBLADERFLIBSRC/src/bladerf_priv.c\ - $$LIBBLADERFLIBSRC/src/config.c\ - $$LIBBLADERFLIBSRC/src/device_identifier.c\ - $$PWD/src/file_ops.c\ - $$LIBBLADERFLIBSRC/src/flash_fields.c\ - $$LIBBLADERFLIBSRC/src/fx3_fw.c\ - $$LIBBLADERFLIBSRC/src/gain.c\ - $$LIBBLADERFLIBSRC/src/init_fini.c\ - $$LIBBLADERFLIBSRC/src/sync.c\ - $$LIBBLADERFLIBSRC/src/smb_clock.c\ - $$LIBBLADERFLIBSRC/src/tuning.c\ - $$LIBBLADERFLIBSRC/src/xb.c\ +SOURCES = $$LIBBLADERFCOMMONSRC/src/sha256.c\ + $$LIBBLADERFCOMMONSRC/src/dc_calibration.c\ + $$LIBBLADERFCOMMONSRC/src/parse.c\ + $$LIBBLADERFCOMMONSRC/src/devcfg.c\ + $$LIBBLADERFCOMMONSRC/src/conversions.c\ + $$LIBBLADERFCOMMONSRC/src/log.c\ + $$LIBBLADERFCOMMONSRC/src/str_queue.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/clock_gettime.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/getopt_long.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/mkdtemp.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/nanosleep.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/setenv.c\ + $$LIBBLADERFSRC/host/misc/dev/lms_freqsel/freqsel.c\ + $$LIBBLADERFSRC/fpga_common/src/lms.c\ + $$LIBBLADERFSRC/fpga_common/src/band_select.c\ + $$LIBBLADERFLIBSRC/src/helpers/interleave.c\ + $$LIBBLADERFLIBSRC/src/helpers/timeout.c\ + $$LIBBLADERFLIBSRC/src/helpers/wallclock.c\ + $$LIBBLADERFLIBSRC/src/helpers/configfile.c\ + $$LIBBLADERFLIBSRC/src/helpers/file.c\ + $$LIBBLADERFLIBSRC/src/helpers/version.c\ + $$LIBBLADERFLIBSRC/src/driver/fpga_trigger.c\ + $$LIBBLADERFLIBSRC/src/driver/si5338.c\ + $$LIBBLADERFLIBSRC/src/driver/dac161s055.c\ + $$LIBBLADERFLIBSRC/src/driver/fx3_fw.c\ + $$LIBBLADERFLIBSRC/src/driver/smb_clock.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/dac_core.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/util.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/platform.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361_api.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/adc_core.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361_conv.c\ + $$LIBBLADERFLIBSRC/src/driver/spi_flash.c\ + $$LIBBLADERFLIBSRC/src/driver/ina219.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/compatibility.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/capabilities.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/params.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/bladerf2.c\ + $$LIBBLADERFLIBSRC/src/board/board.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/flash.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/bladerf1.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/image.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/compatibility.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/calibration.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/capabilities.c\ + $$LIBBLADERFLIBSRC/src/expansion/xb100.c\ + $$LIBBLADERFLIBSRC/src/expansion/xb200.c\ + $$LIBBLADERFLIBSRC/src/expansion/xb300.c\ + $$LIBBLADERFLIBSRC/src/streaming/async.c\ + $$LIBBLADERFLIBSRC/src/streaming/sync_worker.c\ + $$LIBBLADERFLIBSRC/src/streaming/sync.c\ $$LIBBLADERFLIBSRC/src/bladerf.c\ - $$LIBBLADERFLIBSRC/src/capabilities.c\ - $$LIBBLADERFLIBSRC/src/dc_cal_table.c\ - $$LIBBLADERFLIBSRC/src/devinfo.c\ - $$LIBBLADERFLIBSRC/src/flash.c\ - $$LIBBLADERFLIBSRC/src/fpga.c\ - $$LIBBLADERFLIBSRC/src/fx3_fw_log.c\ - $$LIBBLADERFLIBSRC/src/image.c\ - $$LIBBLADERFLIBSRC/src/si5338.c\ - $$LIBBLADERFLIBSRC/src/sync_worker.c\ - $$LIBBLADERFLIBSRC/src/trigger.c\ - $$LIBBLADERFLIBSRC/src/version_compat.c\ + $$LIBBLADERFLIBSRC/src/init_fini.c\ + $$LIBBLADERFLIBSRC/src/backend/dummy/dummy.c\ $$LIBBLADERFLIBSRC/src/backend/backend.c\ - $$LIBBLADERFLIBSRC/src/backend/dummy.c\ - $$LIBBLADERFLIBSRC/src/backend/usb/libusb.c\ $$LIBBLADERFLIBSRC/src/backend/usb/usb.c\ + $$LIBBLADERFLIBSRC/src/backend/usb/libusb.c\ $$LIBBLADERFLIBSRC/src/backend/usb/nios_access.c\ $$LIBBLADERFLIBSRC/src/backend/usb/nios_legacy_access.c\ - $$LIBBLADERFSRC/fpga_common/src/band_select.c\ - $$LIBBLADERFSRC/fpga_common/src/lms.c\ - $$LIBBLADERFCOMMONSRC/src/conversions.c\ - $$LIBBLADERFCOMMONSRC/src/devcfg.c\ - $$LIBBLADERFCOMMONSRC/src/sha256.c\ - $$LIBBLADERFCOMMONSRC/src/parse.c + $$LIBBLADERFLIBSRC/src/devinfo.c -HEADERS = $$LIBBLADERFLIBSRC/src/async.h\ - $$LIBBLADERFLIBSRC/src/capabilities.h\ - $$LIBBLADERFLIBSRC/src/dc_cal_table.h\ - $$LIBBLADERFLIBSRC/src/devinfo.h\ - $$LIBBLADERFLIBSRC/src/flash.h\ - $$LIBBLADERFLIBSRC/src/fpga.h\ - $$LIBBLADERFLIBSRC/src/fx3_fw_log.h\ - $$LIBBLADERFLIBSRC/src/metadata.h\ - $$LIBBLADERFLIBSRC/src/sync.h\ - $$LIBBLADERFLIBSRC/src/smb_clock.h\ - $$LIBBLADERFLIBSRC/src/tuning.h\ - $$LIBBLADERFLIBSRC/src/xb.h\ - $$LIBBLADERFLIBSRC/src/bladerf_priv.h\ - $$LIBBLADERFLIBSRC/src/config.h\ - $$LIBBLADERFLIBSRC/src/device_identifier.h\ - $$LIBBLADERFLIBSRC/src/file_ops.h\ - $$LIBBLADERFLIBSRC/src/flash_fields.h\ - $$LIBBLADERFLIBSRC/src/fx3_fw.h\ - $$LIBBLADERFLIBSRC/src/gain.h\ - $$LIBBLADERFLIBSRC/src/si5338.h\ - $$LIBBLADERFLIBSRC/src/sync_worker.h\ - $$LIBBLADERFLIBSRC/src/trigger.h\ - $$LIBBLADERFLIBSRC/src/version_compat.h\ - $$LIBBLADERFLIBSRC/src/backend/backend.h\ - $$LIBBLADERFLIBSRC/src/backend/dummy.h\ - $$LIBBLADERFLIBSRC/src/backend/usb/usb.h\ - $$LIBBLADERFLIBSRC/src/backend/usb/nios_access.h\ - $$LIBBLADERFLIBSRC/src/backend/usb/nios_legacy_access.h\ - $$LIBBLADERFSRC/fpga_common/include/band_select.h\ - $$LIBBLADERFSRC/fpga_common/include/lms.h\ - $$LIBBLADERFCOMMONSRC/include/sha256.h\ +HEADERS = $$PWD/mingw/common/include/host_config.h\ + $$PWD/mingw/libraries/libbladeRF/src/version.h\ + $$PWD/mingw/libraries/libbladeRF/src/backend/backend_config.h\ + $$LIBBLADERFCOMMONSRC/include/thread.h\ $$LIBBLADERFCOMMONSRC/include/parse.h\ - $$PWD/include/host_config.h\ - $$PWD/include/backend/backend_config.h\ - $$PWD/include/version.h + $$LIBBLADERFCOMMONSRC/include/minmax.h\ + $$LIBBLADERFCOMMONSRC/include/rel_assert.h\ + $$LIBBLADERFCOMMONSRC/include/devcfg.h\ + $$LIBBLADERFCOMMONSRC/include/str_queue.h\ + $$LIBBLADERFCOMMONSRC/include/log.h\ + $$LIBBLADERFCOMMONSRC/include/dc_calibration.h\ + $$LIBBLADERFCOMMONSRC/include/sha256.h\ + $$LIBBLADERFCOMMONSRC/include/conversions.h\ + $$LIBBLADERFSRC/fpga_common/include/lms.h\ + $$LIBBLADERFSRC/fpga_common/include/band_select.h\ + $$LIBBLADERFLIBSRC/src/helpers/interleave.h\ + $$LIBBLADERFLIBSRC/src/helpers/wallclock.h\ + $$LIBBLADERFLIBSRC/src/helpers/timeout.h\ + $$LIBBLADERFLIBSRC/src/helpers/version.h\ + $$LIBBLADERFLIBSRC/src/helpers/configfile.h\ + $$LIBBLADERFLIBSRC/src/helpers/file.h\ + $$LIBBLADERFLIBSRC/src/driver/dac161s055.h\ + $$LIBBLADERFLIBSRC/src/driver/fpga_trigger.h\ + $$LIBBLADERFLIBSRC/src/driver/si5338.h\ + $$LIBBLADERFLIBSRC/src/driver/ina219.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/platform.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/util.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/dac_core.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/config.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/adc_core.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/common.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361_api.h\ + $$LIBBLADERFLIBSRC/src/driver/spi_flash.h\ + $$LIBBLADERFLIBSRC/src/driver/fx3_fw.h\ + $$LIBBLADERFLIBSRC/src/driver/smb_clock.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/capabilities.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/compatibility.h\ + $$LIBBLADERFLIBSRC/src/board/board.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/calibration.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/capabilities.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/compatibility.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/flash.h\ + $$LIBBLADERFLIBSRC/src/expansion/xb300.h\ + $$LIBBLADERFLIBSRC/src/expansion/xb100.h\ + $$LIBBLADERFLIBSRC/src/expansion/xb200.h\ + $$LIBBLADERFLIBSRC/src/streaming/sync.h\ + $$LIBBLADERFLIBSRC/src/streaming/sync_worker.h\ + $$LIBBLADERFLIBSRC/src/streaming/metadata.h\ + $$LIBBLADERFLIBSRC/src/streaming/format.h\ + $$LIBBLADERFLIBSRC/src/streaming/async.h\ + $$LIBBLADERFLIBSRC/src/backend/backend.h\ + $$LIBBLADERFLIBSRC/src/backend/dummy/dummy.h\ + $$LIBBLADERFLIBSRC/src/backend/usb/nios_legacy_access.h\ + $$LIBBLADERFLIBSRC/src/backend/usb/nios_access.h\ + $$LIBBLADERFLIBSRC/src/backend/usb/usb.h\ + $$LIBBLADERFLIBSRC/src/devinfo.h\ + $$LIBBLADERFLIBSRC/include/bladeRF2.h\ + $$LIBBLADERFLIBSRC/include/libbladeRF.h\ + $$LIBBLADERFLIBSRC/include/bladeRF1.h CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 diff --git a/libbladerf/mingw/common/include/host_config.h b/libbladerf/mingw/common/include/host_config.h new file mode 100644 index 000000000..96871ea0e --- /dev/null +++ b/libbladerf/mingw/common/include/host_config.h @@ -0,0 +1,333 @@ +/** + * @file host_config.h.in + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2013-2018 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef HOST_CONFIG_H__ +#define HOST_CONFIG_H__ + +#define BLADERF_OS_LINUX 0 +#define BLADERF_OS_FREEBSD 0 +#define BLADERF_OS_OSX 0 +#define BLADERF_OS_WINDOWS 1 +#define BLADERF_BIG_ENDIAN 0 + +#if !(BLADERF_OS_LINUX || BLADERF_OS_OSX || BLADERF_OS_WINDOWS || BLADERF_OS_FREEBSD) +# error "Build not configured for any supported operating systems" +#endif + +#if 1 < (BLADERF_OS_LINUX + BLADERF_OS_OSX + BLADERF_OS_WINDOWS + BLADERF_OS_FREEBSD) +#error "Build configured for multiple operating systems" +#endif + +#define HAVE_CLOCK_GETTIME 0 + +/******************************************************************************* + * Endianness conversions + * + * HOST_TO_LE16 16-bit host endianness to little endian conversion + * LE16_TO_HOST 16-bit little endian to host endianness conversion + * HOST_TO_BE16 16-bit host endianness to big endian conversion + * BE16_TO_HOST 16-bit big endian to host endianness conversion + * HOST_TO_LE32 32-bit host endianness to little endian conversion + * LE32_TO_HOST 32-bit little endian to host endianness conversion + * HOST_TO_BE32 32-bit host endianness to big endian conversion + * BE32_TO_HOST 32-bit big endian to host endianness conversion + * HOST_TO_BE64 64-bit host endianness to big endian conversion + * BE64_TO_HOST 64-bit big endian to host endianness conversion + ******************************************************************************/ + +/*----------------------- + * Linux + *---------------------*/ +#if BLADERF_OS_LINUX +#include + +#define HOST_TO_LE16(val) htole16(val) +#define LE16_TO_HOST(val) le16toh(val) +#define HOST_TO_BE16(val) htobe16(val) +#define BE16_TO_HOST(val) be16toh(val) + +#define HOST_TO_LE32(val) htole32(val) +#define LE32_TO_HOST(val) le32toh(val) +#define HOST_TO_BE32(val) htobe32(val) +#define BE32_TO_HOST(val) be32toh(val) + +#define HOST_TO_LE64(val) htole64(val) +#define LE64_TO_HOST(val) le64toh(val) +#define HOST_TO_BE64(val) be64toh(val) +#define BE64_TO_HOST(val) be64toh(val) + +/*----------------------- + * FREEBSD + *---------------------*/ +#elif BLADERF_OS_FREEBSD +#include + +#define HOST_TO_LE16(val) htole16(val) +#define LE16_TO_HOST(val) le16toh(val) +#define HOST_TO_BE16(val) htobe16(val) +#define BE16_TO_HOST(val) be16toh(val) + +#define HOST_TO_LE32(val) htole32(val) +#define LE32_TO_HOST(val) le32toh(val) +#define HOST_TO_BE32(val) htobe32(val) +#define BE32_TO_HOST(val) be32toh(val) + +#define HOST_TO_LE64(val) htole64(val) +#define LE64_TO_HOST(val) le64toh(val) +#define HOST_TO_BE64(val) be64toh(val) +#define BE64_TO_HOST(val) be64toh(val) + +/*----------------------- + * Apple OSX + *---------------------*/ +#elif BLADERF_OS_OSX +#include + +#define HOST_TO_LE16(val) OSSwapHostToLittleInt16(val) +#define LE16_TO_HOST(val) OSSwapLittleToHostInt16(val) +#define HOST_TO_BE16(val) OSSwapHostToBigInt16(val) +#define BE16_TO_HOST(val) OSSwapBigToHostInt16(val) + +#define HOST_TO_LE32(val) OSSwapHostToLittleInt32(val) +#define LE32_TO_HOST(val) OSSwapLittleToHostInt32(val) +#define HOST_TO_BE32(val) OSSwapHostToBigInt32(val) +#define BE32_TO_HOST(val) OSSwapBigToHostInt32(val) + +#define HOST_TO_LE64(val) OSSwapHostToLittleInt64(val) +#define LE64_TO_HOST(val) OSSwapLittleToHostInt64(val) +#define HOST_TO_BE64(val) OSSwapHostToBigInt64(val) +#define BE64_TO_HOST(val) OSSwapBigToHostInt64(val) + +/*----------------------- + * Windows + *---------------------*/ +#elif BLADERF_OS_WINDOWS +#include + +/* We'll assume little endian for Windows platforms: + * http://blogs.msdn.com/b/larryosterman/archive/2005/06/07/426334.aspx + */ +#define HOST_TO_LE16(val) (val) +#define LE16_TO_HOST(val) (val) +#define HOST_TO_BE16(val) _byteswap_ushort(val) +#define BE16_TO_HOST(val) _byteswap_ushort(val) + +#define HOST_TO_LE32(val) (val) +#define LE32_TO_HOST(val) (val) +#define HOST_TO_BE32(val) _byteswap_ulong(val) +#define BE32_TO_HOST(val) _byteswap_ulong(val) + + +#define HOST_TO_LE64(val) (val) +#define LE64_TO_HOST(val) (val) +#define HOST_TO_BE64(val) _byteswap_uint64(val) +#define BE64_TO_HOST(val) _byteswap_uint64(val) + +/*----------------------- + * Unsupported or bug + *---------------------*/ +#else +#error "Encountered an OS that we do not have endian wrappers for?" +#endif + +/******************************************************************************* + * Endianness conversions for constants + * + * We shouldn't be using the above items for assigning constants because the + * implementations may be functions [1]. + * + * [1] https://sourceware.org/bugzilla/show_bug.cgi?id=17679#c7 + ******************************************************************************/ + +#define BLADERF_BYTESWAP16(x) ((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8)) + +#define BLADERF_BYTESWAP32(x) ((((x) & 0xff000000) >> 24) | \ + (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | \ + (((x) & 0x000000ff) << 24) ) + +#define BLADERF_BYTESWAP64(x) ((((x) & 0xff00000000000000llu) >> 56) | \ + (((x) & 0x00ff000000000000llu) >> 40) | \ + (((x) & 0x0000ff0000000000llu) >> 24) | \ + (((x) & 0x000000ff00000000llu) >> 8) | \ + (((x) & 0x00000000ff000000llu) << 8) | \ + (((x) & 0x0000000000ff0000llu) << 24) | \ + (((x) & 0x000000000000ff00llu) << 40) | \ + (((x) & 0x00000000000000ffllu) << 56) | \ + +#if BLADERF_BIG_ENDIAN +# define HOST_TO_LE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define LE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) +# define HOST_TO_BE16_CONST(x) (x) +# define BE16_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define LE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) +# define HOST_TO_BE32_CONST(x) (x) +# define BE32_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define LE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +# define HOST_TO_BE64_CONST(x) (x) +# define BE64_TO_HOST_CONST(x) (x) +#else +# define HOST_TO_LE16_CONST(x) (x) +# define LE16_TO_HOST_CONST(x) (x) +# define HOST_TO_BE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define BE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) + +# define HOST_TO_LE32_CONST(x) (x) +# define LE32_TO_HOST_CONST(x) (x) +# define HOST_TO_BE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define BE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) + +# define HOST_TO_LE64_CONST(x) (x) +# define LE64_TO_HOST_CONST(x) (x) +# define HOST_TO_BE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define BE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +#endif + +/******************************************************************************* + * Miscellaneous Linux fixups + ******************************************************************************/ +#if BLADERF_OS_LINUX + +/* Added here just to keep this out of the other source files. We'll have + * a few Windows replacements for unistd.h items. */ +#include +#include + +/******************************************************************************* + * Miscellaneous FREEBSD fixups + ******************************************************************************/ +#elif BLADERF_OS_FREEBSD + +#include +#include +#include + +/******************************************************************************* + * Miscellaneous OSX fixups + ******************************************************************************/ +#elif BLADERF_OS_OSX + +#include +#include + +/******************************************************************************* + * Miscellaneous Windows fixups + ******************************************************************************/ +#elif BLADERF_OS_WINDOWS + +/* Rename a few functions and types */ +#include +#include +#include +#define usleep(x) Sleep((x+999)/1000) + +/* Changes specific to Microsoft Visual Studio and its C89-compliant ways */ +#if _MSC_VER +# define strtok_r strtok_s +# define strtoull _strtoui64 +#if _MSC_VER < 1900 +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#else +#define STDC99 +#endif +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# define fileno _fileno +# define strdup _strdup +# define access _access +# define chdir _chdir +# define getcwd _getcwd +# define rmdir _rmdir +# define unlink _unlink +# define mkdir(n, m) _mkdir(n) + +/* ssize_t lives elsewhere */ +# include +# define ssize_t SSIZE_T + +/* With msvc, inline is only available for C++. Otherwise we need to use __inline: + * http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx */ +# if !defined(__cplusplus) +# define inline __inline +# endif + +/* Visual studio 2012 compiler (v17.00.XX) doesn't have round functions */ +# if _MSC_VER <= 1700 +# include + static inline float roundf(float x) + { + return x >= 0.0f ? floorf(x + 0.5f) : ceilf(x - 0.5f); + } + static inline double round(double x) + { + return x >= 0.0 ? floor(x + 0.5) : ceil(x - 0.5); + } +# endif + +#endif // _MSC_VER + +#endif + +/* Windows (msvc) does not support C99 designated initializers. + * + * Therefore, the following macro should be used. However, note that you'll + * need to be sure to keep your elements in order to avoid breaking Windows + * portability! + * + * http://stackoverflow.com/questions/5440611/how-to-rewrite-c-struct-designated-initializers-to-c89-resp-msvc-c-compiler + * + * Here's a sample regexep you could use in vim to clean these up in your code: + * @\(\s\+\)\(\.[a-zA-Z0-9_]\+\)\s*=\s*\([a-zA-Z0-9_]\+\)\(,\)\?@\1FIELD_INIT(\2,\3)\4@gc + */ +#if _MSC_VER +# define FIELD_INIT(field, ...) __VA_ARGS__ +#else +# define FIELD_INIT(field, ...) field = __VA_ARGS__ +#endif // _MSC_VER + +/******************************************************************************* + * Miscellaneous utility macros + ******************************************************************************/ +#define ARRAY_SIZE(n) (sizeof(n) / sizeof(n[0])) + +/** + * GCC 7+ warns on implicit fallthroughs in switch statements + * (-Wimplicit-fallthrough), which we use in a couple places for simplicity. + * The statement attribute syntax used to suppress this warning is not + * supported by anything else. + */ +#if __GNUC__ >= 7 +#define EXPLICIT_FALLTHROUGH __attribute__((fallthrough)) +#else +#define EXPLICIT_FALLTHROUGH +#endif // __GNUC__ >= 7 + +#endif diff --git a/libbladerf/mingw/libraries/libbladeRF/src/backend/backend_config.h b/libbladerf/mingw/libraries/libbladeRF/src/backend/backend_config.h new file mode 100644 index 000000000..351d5d12b --- /dev/null +++ b/libbladerf/mingw/libraries/libbladeRF/src/backend/backend_config.h @@ -0,0 +1,88 @@ +/** + * @file backend_config.h + * + * @brief Compile-time backend selection + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 Nuand LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BACKEND_BACKEND_CONFIG_H_ +#define BACKEND_BACKEND_CONFIG_H_ + +#define ENABLE_BACKEND_USB +#define ENABLE_BACKEND_LIBUSB +/* #undef ENABLE_BACKEND_CYAPI */ +/* #undef ENABLE_BACKEND_DUMMY */ +/* #undef ENABLE_BACKEND_LINUX_DRIVER */ + +#include "backend/backend.h" +#include "backend/usb/usb.h" + +#ifdef ENABLE_BACKEND_DUMMY +extern const struct backend_fns backend_fns_dummy; +#define BACKEND_DUMMY &backend_fns_dummy, +#else +#define BACKEND_DUMMY +#endif + +#ifdef ENABLE_BACKEND_USB +extern const struct backend_fns backend_fns_usb; +#define BACKEND_USB &backend_fns_usb, + +#ifdef ENABLE_BACKEND_LIBUSB +extern const struct usb_driver usb_driver_libusb; +#define BACKEND_USB_LIBUSB &usb_driver_libusb, +#else +#define BACKEND_USB_LIBUSB +#endif + +#ifdef ENABLE_BACKEND_CYAPI +extern const struct usb_driver usb_driver_cypress; +#define BACKEND_USB_CYAPI &usb_driver_cypress, +#else +#define BACKEND_USB_CYAPI +#endif + +#define BLADERF_USB_BACKEND_LIST \ + { \ + BACKEND_USB_LIBUSB \ + BACKEND_USB_CYAPI \ + } + +#if !defined(ENABLE_BACKEND_LIBUSB) && !defined(ENABLE_BACKEND_CYAPI) +#error "No USB backends are enabled. One or more must be enabled." +#endif + +#else +#define BACKEND_USB +#endif + +#if !defined(ENABLE_BACKEND_USB) && !defined(ENABLE_BACKEND_DUMMY) +#error "No backends are enabled. One more more must be enabled." +#endif + +/* This list should be ordered by preference (highest first) */ +#define BLADERF_BACKEND_LIST \ + { \ + BACKEND_USB \ + BACKEND_DUMMY \ + } + +#endif diff --git a/libbladerf/mingw/libraries/libbladeRF/src/version.h b/libbladerf/mingw/libraries/libbladeRF/src/version.h new file mode 100644 index 000000000..480695256 --- /dev/null +++ b/libbladerf/mingw/libraries/libbladeRF/src/version.h @@ -0,0 +1,12 @@ +#ifndef VERSION_H_ +#define VERSION_H_ + +#define LIBBLADERF_VERSION "2.0.2-git-32058c47-dirty" + +// clang-format off +#define LIBBLADERF_VERSION_MAJOR 2 +#define LIBBLADERF_VERSION_MINOR 0 +#define LIBBLADERF_VERSION_PATCH 2 +// clang-format on + +#endif diff --git a/plugins/samplesource/bladerf1input/bladerfinput.pro b/plugins/samplesource/bladerf1input/bladerf1input.pro similarity index 80% rename from plugins/samplesource/bladerf1input/bladerfinput.pro rename to plugins/samplesource/bladerf1input/bladerf1input.pro index aabf50f1c..78b56e879 100644 --- a/plugins/samplesource/bladerf1input/bladerfinput.pro +++ b/plugins/samplesource/bladerf1input/bladerf1input.pro @@ -17,15 +17,15 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(MINGW64):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client INCLUDEPATH += ../../../devices -INCLUDEPATH += $$LIBBLADERFSRC +INCLUDEPATH += $$LIBBLADERF/include CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -42,12 +42,12 @@ HEADERS += bladerf1inputgui.h\ bladerf1inputsettings.h\ bladerf1inputthread.h -FORMS += bladerfinputgui.ui +FORMS += bladerf1inputgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui LIBS += -L../../../swagger/$${build_subdir} -lswagger -LIBS += -L../../../libbladerf/$${build_subdir} -llibbladerf +LIBS += -L$$LIBBLADERF/lib -lbladeRF LIBS += -L../../../devices/$${build_subdir} -ldevices RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index 1a02dfe36..eb23dbc19 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -19,7 +19,7 @@ SUBDIRS += fcdhid SUBDIRS += fcdlib SUBDIRS += libairspy SUBDIRS += libairspyhf -SUBDIRS += libbladerf +#SUBDIRS += libbladerf SUBDIRS += libhackrf SUBDIRS += libiio SUBDIRS += liblimesuite @@ -31,7 +31,7 @@ SUBDIRS += dsdcc #SUBDIRS += cm256cc SUBDIRS += plugins/samplesource/airspy SUBDIRS += plugins/samplesource/airspyhf -SUBDIRS += plugins/samplesource/bladerfinput +SUBDIRS += plugins/samplesource/bladerf1input SUBDIRS += plugins/samplesource/filesource SUBDIRS += plugins/samplesource/hackrfinput SUBDIRS += plugins/samplesource/limesdrinput @@ -40,7 +40,7 @@ SUBDIRS += plugins/samplesource/plutosdrinput SUBDIRS += plugins/samplesource/rtlsdr SUBDIRS += plugins/samplesource/testsource SUBDIRS += plugins/samplesink/filesink -SUBDIRS += plugins/samplesink/bladerf1output +#SUBDIRS += plugins/samplesink/bladerf1output SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput From 3a4324a7fb427cfb5472856694ed0cd8dedc7fa9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 27 Sep 2018 05:17:34 +0200 Subject: [PATCH 803/956] BladeRF2 input: fixed sample rate and center frequency rounding issues by sending these values in the report to buddy message --- devices/bladerf2/devicebladerf2shared.h | 23 +++++++-- .../bladerf2input/bladerf2input.cpp | 49 +++++-------------- 2 files changed, 30 insertions(+), 42 deletions(-) diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index aaab9891e..65b326069 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -34,18 +34,33 @@ public: MESSAGE_CLASS_DECLARATION public: - bool getRxElseTx() const { return m_rxElseTx; } + uint64_t getCenterFrequency() const { return m_centerFrequency; } + int getDevSampleRate() const { return m_devSampleRate; } + bool getRxElseTx() const { return m_rxElseTx; } - static MsgReportBuddyChange* create(bool rxElseTx) + static MsgReportBuddyChange* create( + uint64_t centerFrequency, + int devSampleRate, + bool rxElseTx) { - return new MsgReportBuddyChange(rxElseTx); + return new MsgReportBuddyChange( + centerFrequency, + devSampleRate, + rxElseTx); } private: + uint64_t m_centerFrequency; //!< Center frequency + int m_devSampleRate; //!< device/host sample rate bool m_rxElseTx; //!< tells which side initiated the message - MsgReportBuddyChange(bool rxElseTx) : + MsgReportBuddyChange( + uint64_t centerFrequency, + int devSampleRate, + bool rxElseTx) : Message(), + m_centerFrequency(centerFrequency), + m_devSampleRate(devSampleRate), m_rxElseTx(rxElseTx) { } }; diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 133b922bc..b83863ed1 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -575,11 +575,8 @@ bool BladeRF2Input::handleMessage(const Message& message) struct bladerf *dev = m_deviceShared.m_dev->getDev(); BladeRF2InputSettings settings = m_settings; int status; - int tmp_int; unsigned int tmp_uint; - uint64_t tmp_uint64; bool tmp_bool; - bladerf_gain_mode tmp_gain_mode; // evaluate changes that may have been introduced by changes in a buddy @@ -589,22 +586,8 @@ bool BladeRF2Input::handleMessage(const Message& message) if (report.getRxElseTx()) // Rx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth { - - status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); - - if (status < 0) { - qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); - } else { - settings.m_devSampleRate = tmp_uint; - } - - status = bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint64); - - if (status < 0) { - qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_frequency error: %s", bladerf_strerror(status)); - } else { - settings.m_centerFrequency = tmp_uint64; - } + settings.m_devSampleRate = report.getDevSampleRate(); + settings.m_centerFrequency = report.getCenterFrequency(); status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); @@ -614,22 +597,6 @@ bool BladeRF2Input::handleMessage(const Message& message) settings.m_bandwidth = tmp_uint; } - status = bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_gain_mode); - - if (status < 0) { - qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_gain_mode error: %s", bladerf_strerror(status)); - } else { - settings.m_gainMode = (int) tmp_gain_mode; - } - - status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_int); - - if (status < 0) { - qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_gain error: %s", bladerf_strerror(status)); - } else { - settings.m_globalGain = tmp_int; - } - status = bladerf_get_bias_tee(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_bool); if (status < 0) { @@ -645,7 +612,7 @@ bool BladeRF2Input::handleMessage(const Message& message) if (status < 0) { qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); } else { - settings.m_devSampleRate = tmp_uint; + settings.m_devSampleRate = tmp_uint+1; } } @@ -894,7 +861,10 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo for (; itSource != sourceBuddies.end(); ++itSource) { - DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create(true); + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_devSampleRate, + true); (*itSource)->getSampleSourceInputMessageQueue()->push(report); } } @@ -907,7 +877,10 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo for (; itSink != sinkBuddies.end(); ++itSink) { - DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create(true); + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_devSampleRate, + true); (*itSink)->getSampleSinkInputMessageQueue()->push(report); } } From 3e876141f4022baceaeaaa2215a38ad709dc47f4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 27 Sep 2018 05:44:20 +0200 Subject: [PATCH 804/956] BladeRF2 input: fixed auto/manual transition gain control --- .../bladerf2input/bladerf2input.cpp | 37 ++++++++++--------- .../bladerf2input/bladerf2inputgui.cpp | 18 +++++++++ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index b83863ed1..81106de21 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -810,24 +810,6 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo m_deviceShared.m_dev->setBiasTeeRx(settings.m_biasTee); } - if ((m_settings.m_globalGain != settings.m_globalGain) || force) - { - forwardChangeRxBuddies = true; - - if (dev) - { -// qDebug("BladeRF2Input::applySettings: channel: %d gain: %d", requestedChannel, settings.m_globalGain); - int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(requestedChannel), settings.m_globalGain); - - if (status < 0) { - qWarning("BladeRF2Input::applySettings: bladerf_set_gain(%d) failed: %s", - settings.m_globalGain, bladerf_strerror(status)); - } else { - qDebug("BladeRF2Input::applySettings: bladerf_set_gain(%d)", settings.m_globalGain); - } - } - } - if ((m_settings.m_gainMode != settings.m_gainMode) || force) { forwardChangeRxBuddies = true; @@ -845,6 +827,25 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo } } + if ((m_settings.m_globalGain != settings.m_globalGain) + || ((m_settings.m_gainMode != settings.m_gainMode) && (settings.m_gainMode == BLADERF_GAIN_MANUAL)) || force) + { + forwardChangeRxBuddies = true; + + if (dev) + { +// qDebug("BladeRF2Input::applySettings: channel: %d gain: %d", requestedChannel, settings.m_globalGain); + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(requestedChannel), settings.m_globalGain); + + if (status < 0) { + qWarning("BladeRF2Input::applySettings: bladerf_set_gain(%d) failed: %s", + settings.m_globalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2Input::applySettings: bladerf_set_gain(%d)", settings.m_globalGain); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<gainText->setText(tr("%1 dB").arg(m_settings.m_globalGain)); ui->gain->setValue(m_settings.m_globalGain); + if (m_settings.m_gainMode == BLADERF_GAIN_MANUAL) { + ui->gain->setEnabled(true); + } else { + ui->gain->setEnabled(false); + } + blockApplySettings(false); } @@ -310,6 +316,18 @@ void BladeRF2InputGui::on_gainMode_currentIndexChanged(int index) if (uindex < modes.size()) { BladeRF2Input::GainMode mode = modes[index]; + + if (m_settings.m_gainMode != mode.m_value) + { + if (mode.m_value == BLADERF_GAIN_MANUAL) + { + m_settings.m_globalGain = ui->gain->value(); + ui->gain->setEnabled(true); + } else { + ui->gain->setEnabled(false); + } + } + m_settings.m_gainMode = mode.m_value; sendSettings(); } From d5a33b7448127cb41c0c6853ab6a0f10a5c2b706 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 27 Sep 2018 06:05:41 +0200 Subject: [PATCH 805/956] BladeRF2 input: fixed channel 1 running on its own --- plugins/samplesource/bladerf2input/bladerf2inputthread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp index f6775cec4..25adc2e78 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -96,7 +96,7 @@ void BladeRF2InputThread::run() break; } - if (nbFifos > 1) { + if (m_nbChannels > 1) { callbackMI(m_buf, DeviceBladeRF2::blockSize); } else { callbackSI(m_buf, 2*DeviceBladeRF2::blockSize); From 7078cd868e0ee90c695746ccee0ef9fc7ff5d46a Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 27 Sep 2018 06:37:31 +0200 Subject: [PATCH 806/956] BladeRF2 input: do not re-create the thread if there are no more channels active --- .../bladerf2input/bladerf2input.cpp | 30 ++++++++++++++----- .../bladerf2input/bladerf2inputthread.cpp | 3 ++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 81106de21..b4821887b 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -385,7 +385,7 @@ void BladeRF2Input::stop() // // If the thread is currently managing many channels (MI mode) and we are removing the last channel. The transition // from MI to SI or reduction of MI size is handled by stopping the thread, deleting it and creating a new one - // with one channel less. Then the channel is closed (disabled). + // with one channel less if (and only if) there is still a channel active. // // If the thread is currently managing many channels (MI mode) but the channel being stopped is not the last // channel then the FIFO reference is simply removed from the thread so that it will not stream into this FIFO @@ -430,23 +430,34 @@ void BladeRF2Input::stop() SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; int *fcPoss = new int[nbOriginalChannels-1]; + bool stillActiveFIFO = false; for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references { fifos[i] = bladerf2InputThread->getFifo(i); + stillActiveFIFO = stillActiveFIFO || (bladerf2InputThread->getFifo(i) != 0); log2Decims[i] = bladerf2InputThread->getLog2Decimation(i); fcPoss[i] = bladerf2InputThread->getFcPos(i); } delete bladerf2InputThread; - bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); - m_thread = bladerf2InputThread; // take ownership + m_thread = 0; - for (int i = 0; i < nbOriginalChannels-1; i++) // restore original FIFO references + if (stillActiveFIFO) { - bladerf2InputThread->setFifo(i, fifos[i]); - bladerf2InputThread->setLog2Decimation(i, log2Decims[i]); - bladerf2InputThread->setFcPos(i, fcPoss[i]); + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); + m_thread = bladerf2InputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels-1; i++) // restore original FIFO references + { + bladerf2InputThread->setFifo(i, fifos[i]); + bladerf2InputThread->setLog2Decimation(i, log2Decims[i]); + bladerf2InputThread->setFcPos(i, fcPoss[i]); + } + } + else + { + qDebug("BladeRF2Input::stop: do not re-create thread as there are no more FIFOs active"); } // remove old thread address from buddies (reset in all buddies) @@ -458,7 +469,10 @@ void BladeRF2Input::stop() } m_deviceShared.m_dev->closeRx(requestedChannel); // close the last channel - bladerf2InputThread->startWork(); + + if (stillActiveFIFO) { + bladerf2InputThread->startWork(); + } } else // remove channel from existing thread { diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp index 25adc2e78..f9de1c623 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -22,6 +22,7 @@ BladeRF2InputThread::BladeRF2InputThread(struct bladerf* dev, unsigned int nbRxC m_dev(dev), m_nbChannels(nbRxChannels) { + qDebug("BladeRF2InputThread::BladeRF2InputThread"); m_channels = new Channel[nbRxChannels]; for (unsigned int i = 0; i < nbRxChannels; i++) { @@ -86,6 +87,7 @@ void BladeRF2InputThread::run() } else { + qDebug("BladeRF2InputThread::run: start running loop"); while (m_running) { res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize, NULL, 10000); @@ -102,6 +104,7 @@ void BladeRF2InputThread::run() callbackSI(m_buf, 2*DeviceBladeRF2::blockSize); } } + qDebug("BladeRF2InputThread::run: stop running loop"); } } else From 4448fd2ca3f377dbfecde3ea628d39150f846077 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 27 Sep 2018 08:57:37 +0200 Subject: [PATCH 807/956] BladeRF2 input. Added documentation --- doc/img/BladeRF2Input_plugin.png | Bin 0 -> 21758 bytes doc/img/BladeRF2Input_plugin.xcf | Bin 0 -> 94314 bytes plugins/samplesource/bladerf1input/readme.md | 4 +- plugins/samplesource/bladerf2input/readme.md | 95 +++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 doc/img/BladeRF2Input_plugin.png create mode 100644 doc/img/BladeRF2Input_plugin.xcf create mode 100644 plugins/samplesource/bladerf2input/readme.md diff --git a/doc/img/BladeRF2Input_plugin.png b/doc/img/BladeRF2Input_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..630de617feef96dbf8b9b85dd6363dc42fdb0961 GIT binary patch literal 21758 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 literal 0 HcmV?d00001 diff --git a/doc/img/BladeRF2Input_plugin.xcf b/doc/img/BladeRF2Input_plugin.xcf new file mode 100644 index 0000000000000000000000000000000000000000..0c27a72b1b5df411346e01a8a980ce90895116d2 GIT binary patch literal 94314 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 literal 0 HcmV?d00001 diff --git a/plugins/samplesource/bladerf1input/readme.md b/plugins/samplesource/bladerf1input/readme.md index cdfe764a2..67c644bbd 100644 --- a/plugins/samplesource/bladerf1input/readme.md +++ b/plugins/samplesource/bladerf1input/readme.md @@ -2,13 +2,13 @@

    Introduction

    -This input sample source plugin gets its samples from a [BladeRF1 device](https://www.nuand.com/bladerf-1). From version 4.2.0 using LibbladeRF v.2 this is available in Linux distributions only. +This input sample source plugin gets its samples from a [BladeRF1 device](https://www.nuand.com/bladerf-1) using LibbladeRF v.2. This is available in Linux distributions only.

    Build

    The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. -Note that since version 4.2.0 the libbladeRF v2 (specifically the git tag 2018.08) is used. +Note that libbladeRF v2 with git tag 2018.08 should be used (official release). The FPGA image v0.7.3 should be used accordingly. The FPGA .rbf file should be copied to the folder where the `sdrangel` binary resides. You can download FPGA images from [here](https://www.nuand.com/fpga_images/) The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases. diff --git a/plugins/samplesource/bladerf2input/readme.md b/plugins/samplesource/bladerf2input/readme.md new file mode 100644 index 000000000..721a4c8fa --- /dev/null +++ b/plugins/samplesource/bladerf2input/readme.md @@ -0,0 +1,95 @@ +

    BladeRF 2.0 micro (v2) input plugin

    + +

    Introduction

    + +This input sample source plugin gets its samples from a [BladeRF 2.0 micro device](https://www.nuand.com/bladerf-2) using LibbladeRF v.2. This is available since v4.2.0 in Linux distributions only. + +

    Build

    + +The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. + +Note that libbladeRF v2 with git tag 2018.08 should be used (official release). The FPGA image v0.7.3 should be used accordingly. The FPGA .rbf file should be copied to the folder where the `sdrangel` binary resides. You can download FPGA images from [here](https://www.nuand.com/fpga_images/) + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases. + +

    Interface

    + +![BladeRF2 input plugin GUI](../../../doc/img/BladeRF2Input_plugin.png) + +

    1: Common stream parameters

    + +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_01.png) + +

    1.1: Frequency

    + +This is the center frequency of reception in kHz. The center frequency is the same for all Rx channels. The GUI of the sibling channel if present is adjusted automatically. + +

    1.2: Start/Stop

    + +Device start / stop button. + + - Blue triangle icon: device is ready and can be started + - Green square icon: device is running and can be stopped + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + +

    1.3: Record

    + +Record baseband I/Q stream toggle button + +

    1.4: Stream sample rate

    + +Baseband I/Q sample rate in kS/s. This is the device sample rate (4) divided by the decimation factor (6). + +

    2: Auto correction options

    + +These buttons control the local DSP auto correction options: + + - **DC**: auto remove DC component + - **IQ**: auto make I/Q balance. The DC correction must be enabled for this to be effective. + +

    3: Rx filter bandwidth

    + +This is the Rx filter bandwidth in kHz. Minimum and maximum values are adjusted automatically. Normal range is from 200 kHz to 56 MHz. The Rx filter bandwidth is the same for all Rx channels. The GUI of the sibling channel if present is adjusted automatically. + +

    4: Bias tee control

    + +Use this toggle button to activate or de-activate the bias tee. Note that according to BladeRF v2 specs the bias tee is simultanously present on all Rx RF ports. The GUI of the sibling channel if present is adjusted automatically. + +

    5: Device sample rate

    + +This is the BladeRF device ADC sample rate in S/s. + +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +The ADC sample rate is the same for all Rx channels. The GUI of the sibling channel if present is adjusted automatically. + +

    6: Baseband center frequency position relative the the BladeRF Rx center frequency

    + +Possible values are: + + - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband. + +

    7: Decimation factor

    + +The I/Q stream from the BladeRF ADC is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. + +

    8: Gain mode

    + +This selects the gain processing in use. Values are fetched automatically from the device. Normal values are + + - **default**: AGC with default behavior + - **Manual**: Manual. Use control (9) to adjust gain + - **fast**: fast AGC + - **slow**: slow AGC + - **hybrid**: hybrid AGC + +

    9: Manual gain control

    + +Use this slider to adjust gain in manual mode. This control is disabled in non manual modes (all modes but manual). The minumum, maximum and step values are fetched automatically from the device and may vary depending on the center frequency. For frequencies around 400 MHz the gain varies from -16 to 60 dB in 1 dB steps. From e4ce6c21b2f2c899c5113b41fd48421a137439e1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 27 Sep 2018 10:41:55 +0200 Subject: [PATCH 808/956] libbladerRF2: Windows build for bladerf1output plugin --- .../{bladerfoutput.pro => bladerf1output.pro} | 8 ++++---- sdrangel.windows.pro | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename plugins/samplesink/bladerf1output/{bladerfoutput.pro => bladerf1output.pro} (82%) diff --git a/plugins/samplesink/bladerf1output/bladerfoutput.pro b/plugins/samplesink/bladerf1output/bladerf1output.pro similarity index 82% rename from plugins/samplesink/bladerf1output/bladerfoutput.pro rename to plugins/samplesink/bladerf1output/bladerf1output.pro index b3e2950e7..325942932 100644 --- a/plugins/samplesink/bladerf1output/bladerfoutput.pro +++ b/plugins/samplesink/bladerf1output/bladerf1output.pro @@ -17,15 +17,15 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(MINGW64):LIBBLADERFSRC = "C:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client INCLUDEPATH += ../../../devices -INCLUDEPATH += $$LIBBLADERFSRC +INCLUDEPATH += $$LIBBLADERF/include CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -47,7 +47,7 @@ FORMS += bladerf1outputgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui LIBS += -L../../../swagger/$${build_subdir} -lswagger -LIBS += -L../../../libbladerf/$${build_subdir} -llibbladerf +LIBS += -L$$LIBBLADERF/lib -lbladeRF LIBS += -L../../../devices/$${build_subdir} -ldevices RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index eb23dbc19..e198a12e0 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -40,7 +40,7 @@ SUBDIRS += plugins/samplesource/plutosdrinput SUBDIRS += plugins/samplesource/rtlsdr SUBDIRS += plugins/samplesource/testsource SUBDIRS += plugins/samplesink/filesink -#SUBDIRS += plugins/samplesink/bladerf1output +SUBDIRS += plugins/samplesink/bladerf1output SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput From 5c09985664458a4680f3e183577b8be75460072c Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 27 Sep 2018 10:50:33 +0200 Subject: [PATCH 809/956] libbladerRF2: Windows build for bladerf2input plugin --- devices/devices.pro | 6 +++ .../bladerf1input/bladerf1input.pro | 2 +- .../bladerf2input/bladerf2input.pro | 53 +++++++++++++++++++ .../bladerf2input/bladerf2inputgui.cpp | 6 +++ sdrangel.windows.pro | 1 + 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 plugins/samplesource/bladerf2input/bladerf2input.pro diff --git a/devices/devices.pro b/devices/devices.pro index 35aa88312..4d8ea539b 100644 --- a/devices/devices.pro +++ b/devices/devices.pro @@ -59,6 +59,9 @@ CONFIG(Debug):build_subdir = debug bladerf1/devicebladerf1values.cpp\ bladerf1/devicebladerf1shared.cpp +!macx:SOURCES += bladerf2/devicebladerf2.cpp\ + bladerf2/devicebladerf2shared.cpp + SOURCES += hackrf/devicehackrf.cpp\ hackrf/devicehackrfvalues.cpp\ hackrf/devicehackrfshared.cpp @@ -73,6 +76,9 @@ SOURCES += limesdr/devicelimesdr.cpp\ plutosdr/deviceplutosdrscan.cpp\ plutosdr/deviceplutosdrshared.cpp +!macx:HEADERS += bladerf2/devicebladerf2.h\ + bladerf2/devicebladerf2shared.h + !macx:HEADERS += bladerf1/devicebladerf1.h\ bladerf1/devicebladerf1param.h\ bladerf1/devicebladerf1values.h\ diff --git a/plugins/samplesource/bladerf1input/bladerf1input.pro b/plugins/samplesource/bladerf1input/bladerf1input.pro index 78b56e879..035afb49f 100644 --- a/plugins/samplesource/bladerf1input/bladerf1input.pro +++ b/plugins/samplesource/bladerf1input/bladerf1input.pro @@ -9,7 +9,7 @@ CONFIG += plugin QT += core gui widgets multimedia opengl -TARGET = inputbladerf +TARGET = inputbladerf1 DEFINES += USE_SSE2=1 QMAKE_CXXFLAGS += -msse2 diff --git a/plugins/samplesource/bladerf2input/bladerf2input.pro b/plugins/samplesource/bladerf2input/bladerf2input.pro new file mode 100644 index 000000000..e474a41ca --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2input.pro @@ -0,0 +1,53 @@ +#-------------------------------------------- +# +# Pro file for Windows builds with Qt Creator +# +#-------------------------------------------- + +TEMPLATE = lib +CONFIG += plugin + +QT += core gui widgets multimedia opengl + +TARGET = inputbladerf2 + +DEFINES += USE_SSE2=1 +QMAKE_CXXFLAGS += -msse2 +DEFINES += USE_SSE4_1=1 +QMAKE_CXXFLAGS += -msse4.1 +QMAKE_CXXFLAGS += -std=c++11 + +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" +INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports +INCLUDEPATH += ../../../sdrbase +INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client +INCLUDEPATH += ../../../devices +INCLUDEPATH += $$LIBBLADERF/include + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +SOURCES += bladerf2inputgui.cpp\ + bladerf2input.cpp\ + bladerf2inputplugin.cpp\ + bladerf2inputsettings.cpp\ + bladerf2inputthread.cpp + +HEADERS += bladerf2inputgui.h\ + bladerf2input.h\ + bladerf2inputplugin.h\ + bladerf2inputsettings.h\ + bladerf2inputthread.h + +FORMS += bladerf2inputgui.ui + +LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase +LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger +LIBS += -L$$LIBBLADERF/lib -lbladeRF +LIBS += -L../../../devices/$${build_subdir} -ldevices + +RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp index 1036b9d1a..34f44d6a7 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -280,6 +280,12 @@ void BladeRF2InputGui::on_iqImbalance_toggled(bool checked) sendSettings(); } +void BladeRF2InputGui::on_biasTee_toggled(bool checked) +{ + m_settings.m_biasTee = checked; + sendSettings(); +} + void BladeRF2InputGui::on_bandwidth_changed(quint64 value) { m_settings.m_bandwidth = value * 1000; diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index e198a12e0..a322cbc3c 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -32,6 +32,7 @@ SUBDIRS += dsdcc SUBDIRS += plugins/samplesource/airspy SUBDIRS += plugins/samplesource/airspyhf SUBDIRS += plugins/samplesource/bladerf1input +SUBDIRS += plugins/samplesource/bladerf2input SUBDIRS += plugins/samplesource/filesource SUBDIRS += plugins/samplesource/hackrfinput SUBDIRS += plugins/samplesource/limesdrinput From 2a1f0a0d0ea63e48e92671f1e754b26ffdbdfe1b Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 27 Sep 2018 23:38:23 +0200 Subject: [PATCH 810/956] BladeRF input: communicate Fc Pos change to buddy to align center frequencies --- devices/bladerf2/devicebladerf2shared.h | 6 ++++++ plugins/samplesource/bladerf2input/bladerf2input.cpp | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index 65b326069..946d0ea40 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -35,31 +35,37 @@ public: public: uint64_t getCenterFrequency() const { return m_centerFrequency; } + int getFcPos() const { return m_fcPos; } int getDevSampleRate() const { return m_devSampleRate; } bool getRxElseTx() const { return m_rxElseTx; } static MsgReportBuddyChange* create( uint64_t centerFrequency, + int fcPos, int devSampleRate, bool rxElseTx) { return new MsgReportBuddyChange( centerFrequency, + fcPos, devSampleRate, rxElseTx); } private: uint64_t m_centerFrequency; //!< Center frequency + int m_fcPos; //!< Center frequency position int m_devSampleRate; //!< device/host sample rate bool m_rxElseTx; //!< tells which side initiated the message MsgReportBuddyChange( uint64_t centerFrequency, + int fcPos, int devSampleRate, bool rxElseTx) : Message(), m_centerFrequency(centerFrequency), + m_fcPos(fcPos), m_devSampleRate(devSampleRate), m_rxElseTx(rxElseTx) { } diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index b4821887b..4faccd6a6 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -602,6 +602,13 @@ bool BladeRF2Input::handleMessage(const Message& message) { settings.m_devSampleRate = report.getDevSampleRate(); settings.m_centerFrequency = report.getCenterFrequency(); + settings.m_fcPos = (BladeRF2InputSettings::fcPos_t) report.getFcPos(); + + BladeRF2InputThread *inputThread = findThread(); + + if (inputThread) { + inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); + } status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); @@ -878,6 +885,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo { DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( settings.m_centerFrequency, + (int) settings.m_fcPos, settings.m_devSampleRate, true); (*itSource)->getSampleSourceInputMessageQueue()->push(report); @@ -894,6 +902,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo { DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( settings.m_centerFrequency, + (int) settings.m_fcPos, settings.m_devSampleRate, true); (*itSink)->getSampleSinkInputMessageQueue()->push(report); From 3ac55eacf3e8c4e40ad074c3003bdd113d5e0edc Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 28 Sep 2018 16:45:08 +0200 Subject: [PATCH 811/956] Windows build: upgrade to libusb 1.0.22. Modified install script for BladeRF --- devices/devices.pro | 230 +++++++++++++++++----------------- fcdhid/fcdhid.pro | 8 +- fcdlib/fcdlib.pro | 8 +- libairspy/libairspy.pro | 8 +- libairspyhf/libairspyhf.pro | 8 +- libbladerf/libbladerf.pro | 4 +- libhackrf/libhackrf.pro | 8 +- libiio/libiio.pro | 8 +- liblimesuite/liblimesuite.pro | 8 +- libperseus/libperseus.pro | 8 +- librtlsdr/librtlsdr.pro | 8 +- windows.install.bat | 7 +- 12 files changed, 157 insertions(+), 156 deletions(-) diff --git a/devices/devices.pro b/devices/devices.pro index 4d8ea539b..2d5037f8d 100644 --- a/devices/devices.pro +++ b/devices/devices.pro @@ -1,115 +1,115 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -QT += core gui multimedia opengl - -TEMPLATE = lib -TARGET = devices - -DEFINES += USE_SSE2=1 -QMAKE_CXXFLAGS += -msse2 -DEFINES += USE_SSSE3=1 -QMAKE_CXXFLAGS += -mssse3 -DEFINES += USE_SSE4_1=1 -QMAKE_CXXFLAGS += -msse4.1 -QMAKE_CXXFLAGS += -std=c++11 -macx:QMAKE_LFLAGS += -F/Library/Frameworks - -CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" -CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" -CONFIG(macx):LIBHACKRFSRC = "/opt/local/include" -CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" -CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" -CONFIG(macx):LIBLIMESUITESRC = "../../../LimeSuite-17.12.0" -CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" -CONFIG(MINGW32):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" -CONFIG(macx):LIBIIOSRC = "../../../libiio" -CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" - -INCLUDEPATH += $$PWD -INCLUDEPATH += ../exports -INCLUDEPATH += ../sdrbase -INCLUDEPATH += $$LIBBLADERF/include -INCLUDEPATH += $$LIBHACKRFSRC -INCLUDEPATH += "C:\softs\boost_1_66_0" -INCLUDEPATH += "C:\softs\libusb-1.0.20\include" -INCLUDEPATH += ../liblimesuite/srcmw -INCLUDEPATH += $$LIBLIMESUITESRC/src -INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 -INCLUDEPATH += $$LIBLIMESUITESRC/src/ConnectionRegistry -INCLUDEPATH += $$LIBLIMESUITESRC/src/FPGA_common -INCLUDEPATH += $$LIBLIMESUITESRC/src/GFIR -INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m -INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m_mcu -INCLUDEPATH += $$LIBLIMESUITESRC/src/Si5351C -INCLUDEPATH += $$LIBLIMESUITESRC/src/protocols -INCLUDEPATH += $$LIBLIMESUITESRC/external/cpp-feather-ini-parser -INCLUDEPATH += $$LIBPERSEUSSRC -!macx:INCLUDEPATH += $$LIBIIOSRC - -CONFIG(Release):build_subdir = release -CONFIG(Debug):build_subdir = debug - -!macx:SOURCES += bladerf1/devicebladerf1.cpp\ - bladerf1/devicebladerf1values.cpp\ - bladerf1/devicebladerf1shared.cpp - -!macx:SOURCES += bladerf2/devicebladerf2.cpp\ - bladerf2/devicebladerf2shared.cpp - -SOURCES += hackrf/devicehackrf.cpp\ - hackrf/devicehackrfvalues.cpp\ - hackrf/devicehackrfshared.cpp - -SOURCES += limesdr/devicelimesdr.cpp\ - limesdr/devicelimesdrparam.cpp\ - limesdr/devicelimesdrshared.cpp - -!macx:SOURCES += plutosdr/deviceplutosdr.cpp\ - plutosdr/deviceplutosdrbox.cpp\ - plutosdr/deviceplutosdrparams.cpp\ - plutosdr/deviceplutosdrscan.cpp\ - plutosdr/deviceplutosdrshared.cpp - -!macx:HEADERS += bladerf2/devicebladerf2.h\ - bladerf2/devicebladerf2shared.h - -!macx:HEADERS += bladerf1/devicebladerf1.h\ - bladerf1/devicebladerf1param.h\ - bladerf1/devicebladerf1values.h\ - bladerf1/devicebladerf1shared.h - -HEADERS += hackrf/devicehackrf.h\ - hackrf/devicehackrfparam.h\ - hackrf/devicehackrfvalues.h\ - hackrf/devicehackrfshared.h - -HEADERS += limesdr/devicelimesdr.h\ - limesdr/devicelimesdrparam.h\ - limesdr/devicelimesdrshared.h - -HEADERS += plutosdr/deviceplutosdr.h\ - plutosdr/deviceplutosdrbox.h\ - plutosdr/deviceplutosdrparams.h\ - plutosdr/deviceplutosdrscan.h\ - plutosdr/deviceplutosdrshared.h - -LIBS += -L../sdrbase/$${build_subdir} -lsdrbase -!macx { - LIBS += -L$$LIBBLADERF/lib -lbladeRF - LIBS += -L../libhackrf/$${build_subdir} -llibhackrf - LIBS += -L../liblimesuite/$${build_subdir} -lliblimesuite - LIBS += -L../libiio/$${build_subdir} -llibiio -} -macx { - LIBS -= -L../libbladerf/$${build_subdir} -llibbladerf - LIBS -= -L../libhackrf/$${build_subdir} -llibhackrf - LIBS += -L/opt/local/lib -lhackrf - LIBS += -L/usr/local/lib -lLimeSuite - LIBS += -framework iio -} +#-------------------------------------------------------- +# +# Pro file for Android and Windows builds with Qt Creator +# +#-------------------------------------------------------- + +QT += core gui multimedia opengl + +TEMPLATE = lib +TARGET = devices + +DEFINES += USE_SSE2=1 +QMAKE_CXXFLAGS += -msse2 +DEFINES += USE_SSSE3=1 +QMAKE_CXXFLAGS += -mssse3 +DEFINES += USE_SSE4_1=1 +QMAKE_CXXFLAGS += -msse4.1 +QMAKE_CXXFLAGS += -std=c++11 +macx:QMAKE_LFLAGS += -F/Library/Frameworks + +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(macx):LIBHACKRFSRC = "/opt/local/include" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(macx):LIBLIMESUITESRC = "../../../LimeSuite-17.12.0" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW32):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" +CONFIG(macx):LIBIIOSRC = "../../../libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" + +INCLUDEPATH += $$PWD +INCLUDEPATH += ../exports +INCLUDEPATH += ../sdrbase +INCLUDEPATH += $$LIBBLADERF/include +INCLUDEPATH += $$LIBHACKRFSRC +INCLUDEPATH += "C:\softs\boost_1_66_0" +INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +INCLUDEPATH += ../liblimesuite/srcmw +INCLUDEPATH += $$LIBLIMESUITESRC/src +INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 +INCLUDEPATH += $$LIBLIMESUITESRC/src/ConnectionRegistry +INCLUDEPATH += $$LIBLIMESUITESRC/src/FPGA_common +INCLUDEPATH += $$LIBLIMESUITESRC/src/GFIR +INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m +INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m_mcu +INCLUDEPATH += $$LIBLIMESUITESRC/src/Si5351C +INCLUDEPATH += $$LIBLIMESUITESRC/src/protocols +INCLUDEPATH += $$LIBLIMESUITESRC/external/cpp-feather-ini-parser +INCLUDEPATH += $$LIBPERSEUSSRC +!macx:INCLUDEPATH += $$LIBIIOSRC + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +!macx:SOURCES += bladerf1/devicebladerf1.cpp\ + bladerf1/devicebladerf1values.cpp\ + bladerf1/devicebladerf1shared.cpp + +!macx:SOURCES += bladerf2/devicebladerf2.cpp\ + bladerf2/devicebladerf2shared.cpp + +SOURCES += hackrf/devicehackrf.cpp\ + hackrf/devicehackrfvalues.cpp\ + hackrf/devicehackrfshared.cpp + +SOURCES += limesdr/devicelimesdr.cpp\ + limesdr/devicelimesdrparam.cpp\ + limesdr/devicelimesdrshared.cpp + +!macx:SOURCES += plutosdr/deviceplutosdr.cpp\ + plutosdr/deviceplutosdrbox.cpp\ + plutosdr/deviceplutosdrparams.cpp\ + plutosdr/deviceplutosdrscan.cpp\ + plutosdr/deviceplutosdrshared.cpp + +!macx:HEADERS += bladerf2/devicebladerf2.h\ + bladerf2/devicebladerf2shared.h + +!macx:HEADERS += bladerf1/devicebladerf1.h\ + bladerf1/devicebladerf1param.h\ + bladerf1/devicebladerf1values.h\ + bladerf1/devicebladerf1shared.h + +HEADERS += hackrf/devicehackrf.h\ + hackrf/devicehackrfparam.h\ + hackrf/devicehackrfvalues.h\ + hackrf/devicehackrfshared.h + +HEADERS += limesdr/devicelimesdr.h\ + limesdr/devicelimesdrparam.h\ + limesdr/devicelimesdrshared.h + +HEADERS += plutosdr/deviceplutosdr.h\ + plutosdr/deviceplutosdrbox.h\ + plutosdr/deviceplutosdrparams.h\ + plutosdr/deviceplutosdrscan.h\ + plutosdr/deviceplutosdrshared.h + +LIBS += -L../sdrbase/$${build_subdir} -lsdrbase +!macx { + LIBS += -L$$LIBBLADERF/lib -lbladeRF + LIBS += -L../libhackrf/$${build_subdir} -llibhackrf + LIBS += -L../liblimesuite/$${build_subdir} -lliblimesuite + LIBS += -L../libiio/$${build_subdir} -llibiio +} +macx { + LIBS -= -L../libbladerf/$${build_subdir} -llibbladerf + LIBS -= -L../libhackrf/$${build_subdir} -llibhackrf + LIBS += -L/opt/local/lib -lhackrf + LIBS += -L/usr/local/lib -lLimeSuite + LIBS += -framework iio +} diff --git a/fcdhid/fcdhid.pro b/fcdhid/fcdhid.pro index ecf5eb64a..5f2ef61ce 100644 --- a/fcdhid/fcdhid.pro +++ b/fcdhid/fcdhid.pro @@ -9,10 +9,10 @@ QT += core TEMPLATE = lib TARGET = fcdhid -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" CONFIG(MINGW32):DEFINES += MINGW32=1 -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" CONFIG(MINGW64):DEFINES += MINGW32=1 CONFIG(macx):INCLUDEPATH += "/opt/local/include" @@ -26,6 +26,6 @@ HEADERS = $$PWD/fcdhid.h\ $$PWD/hid-libusb.h\ $$PWD/hidapi.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -liconv -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 -liconv +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 -liconv +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 -liconv CONFIG(macx):LIBS += -L/opt/local/lib -lusb-1.0 -liconv diff --git a/fcdlib/fcdlib.pro b/fcdlib/fcdlib.pro index 949f78d5c..e75af59bf 100644 --- a/fcdlib/fcdlib.pro +++ b/fcdlib/fcdlib.pro @@ -9,8 +9,8 @@ QT += core TEMPLATE = lib TARGET = fcdlib -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" SOURCES = $$PWD/fcdtraits.cpp\ $$PWD/fcdproplusconst.cpp\ @@ -20,5 +20,5 @@ HEADERS = $$PWD/fcdtraits.h\ $$PWD/fcdproplusconst.h\ $$PWD/fcdproconst.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 diff --git a/libairspy/libairspy.pro b/libairspy/libairspy.pro index 5506a510e..4b95674d9 100644 --- a/libairspy/libairspy.pro +++ b/libairspy/libairspy.pro @@ -13,8 +13,8 @@ CONFIG(MINGW32):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" CONFIG(MINGW64):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" INCLUDEPATH += $$LIBAIRSPYSRC/src -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" SOURCES = $$LIBAIRSPYSRC/src/airspy.c\ $$LIBAIRSPYSRC/src/iqconverter_float.c\ @@ -26,8 +26,8 @@ HEADERS = $$LIBAIRSPYSRC/src/airspy.h\ $$LIBAIRSPYSRC/src/iqconverter_int16.h\ $$LIBAIRSPYSRC/src/filters.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libairspyhf/libairspyhf.pro b/libairspyhf/libairspyhf.pro index 9639c7900..e11678f0e 100644 --- a/libairspyhf/libairspyhf.pro +++ b/libairspyhf/libairspyhf.pro @@ -13,8 +13,8 @@ CONFIG(MINGW32):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" CONFIG(MINGW64):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" INCLUDEPATH += $$LIBAIRSPYHFSRC/src -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" SOURCES = $$LIBAIRSPYHFSRC/src/airspyhf.c\ $$LIBAIRSPYHFSRC/src/iqbalancer.c @@ -23,8 +23,8 @@ HEADERS = $$LIBAIRSPYHFSRC/src/airspyhf.h\ $$LIBAIRSPYHFSRC/src/airspyhf_commands.h\ $$LIBAIRSPYHFSRC/src/iqbalancer.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libbladerf/libbladerf.pro b/libbladerf/libbladerf.pro index 9a86b9559..423d497a2 100644 --- a/libbladerf/libbladerf.pro +++ b/libbladerf/libbladerf.pro @@ -154,8 +154,8 @@ HEADERS = $$PWD/mingw/common/include/host_config.h\ $$LIBBLADERFLIBSRC/include/libbladeRF.h\ $$LIBBLADERFLIBSRC/include/bladeRF1.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libhackrf/libhackrf.pro b/libhackrf/libhackrf.pro index 31bf07ada..d47e6ca02 100644 --- a/libhackrf/libhackrf.pro +++ b/libhackrf/libhackrf.pro @@ -13,15 +13,15 @@ CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" INCLUDEPATH += $$LIBHACKRFSRC/src -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" SOURCES = $$LIBHACKRFSRC/src/hackrf.c HEADERS = $$LIBHACKRFSRC/src/hackrf.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libiio/libiio.pro b/libiio/libiio.pro index 4ea08c18a..bbde28890 100644 --- a/libiio/libiio.pro +++ b/libiio/libiio.pro @@ -17,8 +17,8 @@ DEFINES += LIBIIO_EXPORTS=1 INCLUDEPATH += $$PWD/includemw INCLUDEPATH += $$LIBIIOSRC -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" # LibXml2 Windows distribution from: # http://xmlsoft.org/sources/win32/ @@ -49,8 +49,8 @@ HEADERS = $$LIBIIOSRC/debug.h\ $$LIBIIOSRC/iio-private.h\ $$PWD/includemw/iio-config.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(MINGW32):LIBS += -LC:\softs\libxml2-2.7.8.win32\bin -llibxml2 CONFIG(MINGW64):LIBS += -LC:\softs\libxml2-2.9.3-win32-x86_64\bin -llibxml2-2 diff --git a/liblimesuite/liblimesuite.pro b/liblimesuite/liblimesuite.pro index ccd696bd1..133c0b908 100644 --- a/liblimesuite/liblimesuite.pro +++ b/liblimesuite/liblimesuite.pro @@ -18,8 +18,8 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" #CONFIG(MINGW32):INCLUDEPATH += "..\libsqlite3\src" #CONFIG(MINGW64):INCLUDEPATH += "..\libsqlite3\src" @@ -88,8 +88,8 @@ HEADERS = $$LIBLIMESUITESRC/src/API/*.h\ $$LIBLIMESUITESRC/src/FPGA_common/*.h\ $$LIBLIMESUITESRC/src/HPM7/*.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 #CONFIG(MINGW32):LIBS += -L../libsqlite3/release -llibsqlite3 #CONFIG(MINGW64):LIBS += -L../libsqlite3/release -llibsqlite3 diff --git a/libperseus/libperseus.pro b/libperseus/libperseus.pro index ba418e6df..64d4eddbf 100644 --- a/libperseus/libperseus.pro +++ b/libperseus/libperseus.pro @@ -15,8 +15,8 @@ CONFIG(MINGW32):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" CONFIG(MINGW64):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" INCLUDEPATH += $$LIBPERSEUSSRC/src -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" SOURCES = fpga_data.c\ $$LIBPERSEUSSRC/fifo.c\ @@ -34,8 +34,8 @@ HEADERS = fpga_data.h\ $$LIBPERSEUSSRC/perseus-in.h\ $$LIBPERSEUSSRC/perseus-sdr.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/librtlsdr/librtlsdr.pro b/librtlsdr/librtlsdr.pro index 4f5fa1e48..b6e66249a 100644 --- a/librtlsdr/librtlsdr.pro +++ b/librtlsdr/librtlsdr.pro @@ -13,8 +13,8 @@ CONFIG(MINGW32):LIBRTLSDRSRC = "C:\softs\librtlsdr" CONFIG(MINGW64):LIBRTLSDRSRC = "C:\softs\librtlsdr" INCLUDEPATH += $$LIBRTLSDRSRC/include -CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" SOURCES = $$LIBRTLSDRSRC/src/librtlsdr.c\ $$LIBRTLSDRSRC/src/tuner_e4k.c\ @@ -37,8 +37,8 @@ HEADERS = $$LIBRTLSDRSRC/include/reg_field.h\ $$LIBRTLSDRSRC/src/getopt/getopt.h\ $$LIBRTLSDRSRC/src/convenience/convenience.h -CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/windows.install.bat b/windows.install.bat index b325c7c82..589ee9a50 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -1,4 +1,4 @@ -SET libusbdir="C:\softs\libusb-1.0.20\MinGW32" +SET libusbdir="C:\softs\libusb-1.0.22\MinGW32" SET opencvdir="C:\softs\opencv\build\mw32\install\x86\mingw\bin" SET libxml2dir="C:\softs\libxml2-2.7.8.win32" SET libiconvdir="C:\softs\iconv-1.9.2.win32" @@ -21,7 +21,7 @@ copy libhackrf\%1\libhackrf.dll %2 copy librtlsdr\%1\librtlsdr.dll %2 copy libairspy\%1\libairspy.dll %2 copy libairspyhf\%1\libairspyhf.dll %2 -copy libbladerf\%1\libbladerf.dll %2 +REM copy libbladerf\%1\libbladerf.dll %2 REM copy libsqlite3\%1\libsqlite3.dll %2 copy liblimesuite\%1\liblimesuite.dll %2 copy libiio\%1\libiio.dll %2 @@ -60,7 +60,8 @@ copy plugins\samplesource\rtlsdr\%1\inputrtlsdr.dll %2\plugins\samplesource copy plugins\samplesource\hackrfinput\%1\inputhackrf.dll %2\plugins\samplesource copy plugins\samplesource\airspy\%1\inputairspy.dll %2\plugins\samplesource copy plugins\samplesource\airspyhf\%1\inputairspyhf.dll %2\plugins\samplesource -copy plugins\samplesource\bladerfinput\%1\inputbladerf.dll %2\plugins\samplesource +copy plugins\samplesource\bladerf1input\%1\inputbladerf1.dll %2\plugins\samplesource +copy plugins\samplesource\bladerf2input\%1\inputbladerf2.dll %2\plugins\samplesource copy plugins\samplesource\limesdrinput\%1\inputlimesdr.dll %2\plugins\samplesource copy plugins\samplesource\plutosdrinput\%1\inputplutosdr.dll %2\plugins\samplesource REM copy plugins\samplesource\sdrdaemonsource\%1\inputsdrdaemonsource.dll %2\plugins\samplesource From 21bfd71331c513bef004d3b15952d4eb397c151c Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 29 Sep 2018 03:03:43 +0200 Subject: [PATCH 812/956] BladeRF2 input: added server plugin --- pluginssrv/samplesource/CMakeLists.txt | 2 + .../samplesource/bladerf2input/CMakeLists.txt | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 pluginssrv/samplesource/bladerf2input/CMakeLists.txt diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 231856323..8e23ea759 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -26,6 +26,7 @@ endif(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) if(LIBUSB_FOUND AND UNIX) @@ -80,6 +81,7 @@ if (BUILD_DEBIAN) add_subdirectory(airspy) add_subdirectory(airspyhf) add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) add_subdirectory(hackrfinput) add_subdirectory(limesdrinput) add_subdirectory(perseus) diff --git a/pluginssrv/samplesource/bladerf2input/CMakeLists.txt b/pluginssrv/samplesource/bladerf2input/CMakeLists.txt new file mode 100644 index 000000000..8341b6008 --- /dev/null +++ b/pluginssrv/samplesource/bladerf2input/CMakeLists.txt @@ -0,0 +1,68 @@ +project(bladerf2input) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/bladerf2input") + +set(bladerf2input_SOURCES + ${PLUGIN_PREFIX}/bladerf2input.cpp + ${PLUGIN_PREFIX}/bladerf2inputplugin.cpp + ${PLUGIN_PREFIX}/bladerf2inputsettings.cpp + ${PLUGIN_PREFIX}/bladerf2inputthread.cpp +) + +set(bladerf2input_HEADERS + ${PLUGIN_PREFIX}/bladerf2input.h + ${PLUGIN_PREFIX}/bladerf2inputplugin.h + ${PLUGIN_PREFIX}/bladerf2inputsettings.h + ${PLUGIN_PREFIX}/bladerf2inputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputbladerf2srv SHARED + ${bladerf2input_SOURCES} + ${bladerf2input_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputbladerf2srv + ${QT_LIBRARIES} + bladerf + sdrbase + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(inputbladerf2srv + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputbladerf2srv Qt5::Core) + +install(TARGETS inputbladerf2srv DESTINATION lib/pluginssrv/samplesource) From c27acf086ea472d4c8b05057e101fc8063454eb5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 29 Sep 2018 05:49:14 +0200 Subject: [PATCH 813/956] BladeRF output (1) and some cosmetic changes --- devices/bladerf2/devicebladerf2shared.h | 2 + plugins/samplesink/CMakeLists.txt | 2 + .../bladerf1output/bladerf1output.cpp | 3 +- .../bladerf1output/bladerf1outputthread.h | 1 - .../samplesink/bladerf2output/CMakeLists.txt | 80 ++ .../bladerf2output/bladerf2output.cpp | 991 ++++++++++++++++++ .../bladerf2output/bladerf2output.h | 176 ++++ .../bladerf2output/bladerf2outputgui.ui | 464 ++++++++ .../bladerf2output/bladerf2outputplugin.cpp | 157 +++ .../bladerf2output/bladerf2outputplugin.h | 54 + .../bladerf2output/bladerf2outputsettings.cpp | 85 ++ .../bladerf2output/bladerf2outputsettings.h | 40 + .../bladerf2output/bladerf2outputthread.cpp | 223 ++++ .../bladerf2output/bladerf2outputthread.h | 81 ++ .../bladerf2input/bladerf2input.cpp | 17 +- .../bladerf2input/bladerf2inputthread.cpp | 2 + .../bladerf2input/bladerf2inputthread.h | 23 +- sdrbase/resources/webapi/doc/html2/index.html | 58 +- .../webapi/doc/swagger/include/BladeRF2.yaml | 35 + .../resources/webapi/doc/swagger/swagger.yaml | 4 + .../api/swagger/include/BladeRF2.yaml | 35 + swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 58 +- .../qt5/client/SWGBladeRF2OutputReport.cpp | 204 ++++ .../code/qt5/client/SWGBladeRF2OutputReport.h | 86 ++ .../qt5/client/SWGBladeRF2OutputSettings.cpp | 232 ++++ .../qt5/client/SWGBladeRF2OutputSettings.h | 94 ++ .../code/qt5/client/SWGDeviceReport.cpp | 23 + .../code/qt5/client/SWGDeviceReport.h | 7 + .../code/qt5/client/SWGDeviceSettings.cpp | 23 + .../code/qt5/client/SWGDeviceSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + 32 files changed, 3253 insertions(+), 26 deletions(-) create mode 100644 plugins/samplesink/bladerf2output/CMakeLists.txt create mode 100644 plugins/samplesink/bladerf2output/bladerf2output.cpp create mode 100644 plugins/samplesink/bladerf2output/bladerf2output.h create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputgui.ui create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputplugin.h create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputsettings.h create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputthread.cpp create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputthread.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index 946d0ea40..fbb2c9c55 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -23,6 +23,7 @@ class SampleSinkFifo; class SampleSourceFifo; class BladeRF2Input; +class BladeRF2Output; /** * Structure shared by a buddy with other buddies @@ -77,6 +78,7 @@ public: DeviceBladeRF2 *m_dev; int m_channel; //!< allocated channel (-1 if none) BladeRF2Input *m_source; + BladeRF2Output *m_sink; }; diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index eab0b27a9..2ea30aa73 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -5,6 +5,7 @@ find_package(LibUSB) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) add_subdirectory(bladerf1output) + add_subdirectory(bladerf2output) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) find_package(LibHACKRF) @@ -29,6 +30,7 @@ endif(CM256CC_FOUND) if (BUILD_DEBIAN) add_subdirectory(bladerf1output) + add_subdirectory(bladerf2output) add_subdirectory(hackrfoutput) add_subdirectory(limesdroutput) add_subdirectory(plutosdroutput) diff --git a/plugins/samplesink/bladerf1output/bladerf1output.cpp b/plugins/samplesink/bladerf1output/bladerf1output.cpp index 0704443a1..75d15f593 100644 --- a/plugins/samplesink/bladerf1output/bladerf1output.cpp +++ b/plugins/samplesink/bladerf1output/bladerf1output.cpp @@ -23,12 +23,11 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" -#include "util/simpleserializer.h" #include "dsp/dspcommands.h" #include "dsp/dspengine.h" #include "device/devicesinkapi.h" #include "device/devicesourceapi.h" -#include "../../../devices/bladerf1/devicebladerf1shared.h" +#include "bladerf1/devicebladerf1shared.h" #include "bladerf1outputthread.h" MESSAGE_CLASS_DEFINITION(Bladerf1Output::MsgConfigureBladerf1, Message) diff --git a/plugins/samplesink/bladerf1output/bladerf1outputthread.h b/plugins/samplesink/bladerf1output/bladerf1outputthread.h index 42e9ee14d..c9b17d228 100644 --- a/plugins/samplesink/bladerf1output/bladerf1outputthread.h +++ b/plugins/samplesink/bladerf1output/bladerf1outputthread.h @@ -36,7 +36,6 @@ public: void startWork(); void stopWork(); void setLog2Interpolation(unsigned int log2_interp); - void setFcPos(int fcPos); bool isRunning() const { return m_running; } private: diff --git a/plugins/samplesink/bladerf2output/CMakeLists.txt b/plugins/samplesink/bladerf2output/CMakeLists.txt new file mode 100644 index 000000000..907d1d0da --- /dev/null +++ b/plugins/samplesink/bladerf2output/CMakeLists.txt @@ -0,0 +1,80 @@ +project(bladerf2output) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(bladerf2output_SOURCES +# bladerf2outputgui.cpp + bladerf2output.cpp +# bladerf2outputplugin.cpp + bladerf2outputsettings.cpp + bladerf2outputthread.cpp +) + +set(bladerf2output_HEADERS +# bladerf2outputgui.h + bladerf2output.h +# bladerf1soutputplugin.h + bladerf2outputsettings.h + bladerf2outputthread.h +) + +set(bladerf2output_FORMS + bladerf2outputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt4_wrap_cpp(bladerf2output_HEADERS_MOC ${bladerf2output_HEADERS}) +qt5_wrap_ui(bladerf2output_FORMS_HEADERS ${bladerf2output_FORMS}) + +add_library(outputbladerf2 SHARED + ${bladerf2output_SOURCES} + ${bladerf2output_HEADERS_MOC} + ${bladerf2output_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputbladerf2 + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(outputbladerf2 + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + sdrgui + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputbladerf2 Qt5::Core Qt5::Widgets) + +install(TARGETS outputbladerf2 DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp new file mode 100644 index 000000000..83a58081f --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -0,0 +1,991 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "SWGDeviceState.h" +#include "SWGDeviceSettings.h" +#include "SWGBladeRF2InputSettings.h" +#include "SWGDeviceReport.h" +#include "SWGBladeRF2OutputReport.h" + +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "device/devicesinkapi.h" +#include "device/devicesourceapi.h" +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2/devicebladerf2.h" + +#include "bladerf2outputthread.h" +#include "bladerf2output.h" + +MESSAGE_CLASS_DEFINITION(BladeRF2Output::MsgConfigureBladeRF2, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Output::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Output::MsgReportGainRange, Message) + +BladeRF2Output::BladeRF2Output(DeviceSinkAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_dev(0), + m_thread(0), + m_deviceDescription("BladeRF2Output"), + m_running(false) +{ + openDevice(); + + if (m_deviceShared.m_dev) + { + const bladerf_gain_modes *modes = 0; + int nbModes = m_deviceShared.m_dev->getGainModesRx(&modes); + + if (modes) + { + for (int i = 0; i < nbModes; i++) { + m_gainModes.push_back(GainMode{QString(modes[i].name), modes[i].mode}); + } + } + } +} + +BladeRF2Output::~BladeRF2Output() +{ + if (m_running) { + stop(); + } + + closeDevice(); +} + +void BladeRF2Output::destroy() +{ + delete this; +} + +bool BladeRF2Output::openDevice() +{ + m_sampleSourceFifo.resize(m_settings.m_devSampleRate/(1<<(m_settings.m_log2Interp <= 4 ? m_settings.m_log2Interp : 4))); + + // look for Tx buddies and get reference to the device object + if (m_deviceAPI->getSinkBuddies().size() > 0) // look sink sibling first + { + qDebug("BladeRF2Output::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Output::openDevice: the sink buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Output::openDevice: cannot get device pointer from Tx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + } + // look for Rx buddies and get reference to the device object + else if (m_deviceAPI->getSourceBuddies().size() > 0) // then source + { + qDebug("BladeRF2Output::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Output::openDevice: the source buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Output::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + } + // There are no buddies then create the first BladeRF2 device + else + { + qDebug("BladeRF2Output::openDevice: open device here"); + + m_deviceShared.m_dev = new DeviceBladeRF2(); + char serial[256]; + strcpy(serial, qPrintable(m_deviceAPI->getSampleSinkSerial())); + + if (!m_deviceShared.m_dev->open(serial)) + { + qCritical("BladeRF2Output::openDevice: cannot open BladeRF2 device"); + return false; + } + } + + m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel + m_deviceShared.m_sink = this; + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void BladeRF2Output::closeDevice() +{ + if (m_deviceShared.m_dev == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + + if (m_thread) { // stills own the thread => transfer to a buddy + moveThreadToBuddy(); + } + + m_deviceShared.m_channel = -1; // publicly release channel + m_deviceShared.m_sink = 0; + + // No buddies so effectively close the device + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + m_deviceShared.m_dev->close(); + delete m_deviceShared.m_dev; + m_deviceShared.m_dev = 0; + } +} + +void BladeRF2Output::init() +{ + applySettings(m_settings, true); +} + +BladeRF2OutputThread *BladeRF2Output::findThread() +{ + if (m_thread == 0) // this does not own the thread + { + BladeRF2OutputThread *BladeRF2OutputThread = 0; + + // find a buddy that has allocated the thread + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) + { + BladeRF2Output *buddySink = ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink; + + if (buddySink) + { + BladeRF2OutputThread = buddySink->getThread(); + + if (BladeRF2OutputThread) { + break; + } + } + } + + return BladeRF2OutputThread; + } + else + { + return m_thread; // own thread + } +} + +void BladeRF2Output::moveThreadToBuddy() +{ + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) + { + BladeRF2Output *buddySink = ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink; + + if (buddySink) + { + buddySink->setThread(m_thread); + m_thread = 0; // zero for others + } + } +} + +bool BladeRF2Output::start() +{ + // There is a single thread per physical device (Tx side). This thread is unique and referenced by a unique + // buddy in the group of sink buddies associated with this physical device. + // + // This start method is responsible for managing the thread and channel enabling when the streaming of a Tx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following (just 1 in BladeRF 2 case) + // + // The BladeRF support library lets you work in two possible modes: + // - Single Output (SO) with only one channel streaming. This HAS to be channel 0. + // - Multiple Output (MO) with two channels streaming using interleaved samples. It MUST be in this configuration if channel 1 + // is used. When we will run with only channel 1 streaming from the client perspective the channel 0 will actually be enabled + // and streaming but zero samples will be sent to it. + // + // It manages the transition form SO where only one channel (the first or channel 0) should be running to the + // Multiple Output (MO) if the requested channel is 1. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply removes its FIFO reference + // so that the samples are not taken from the FIFO anymore and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SO mode) and 2 if channel 1 is requested (MO mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + + if (!m_deviceShared.m_dev) + { + qDebug("BladeRF2Output::start: no device object"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + BladeRF2OutputThread *bladeRF2OutputThread = findThread(); + bool needsStart = false; + + if (bladeRF2OutputThread) // if thread is already allocated + { + qDebug("BladeRF2Output::start: thread is already allocated"); + + int nbOriginalChannels = bladeRF2OutputThread->getNbChannels(); + + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + { + qDebug("BladeRF2Output::start: expand channels. Re-allocate thread and take ownership"); + + SampleSourceFifo **fifos = new SampleSourceFifo*[nbOriginalChannels]; + unsigned int *log2Interps = new unsigned int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { + fifos[i] = bladeRF2OutputThread->getFifo(i); + log2Interps[i] = bladeRF2OutputThread->getLog2Interpolation(i); + } + + bladeRF2OutputThread->stopWork(); + delete bladeRF2OutputThread; + bladeRF2OutputThread = new BladeRF2OutputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); + m_thread = bladeRF2OutputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { + bladeRF2OutputThread->setFifo(i, fifos[i]); + bladeRF2OutputThread->setLog2Interpolation(i, log2Interps[i]); + } + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + needsStart = true; + } + else + { + qDebug("BladeRF2Output::start: keep buddy thread"); + } + } + else // first allocation + { + qDebug("BladeRF2Output::start: allocate thread and take ownership"); + bladeRF2OutputThread = new BladeRF2OutputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); + m_thread = bladeRF2OutputThread; // take ownership + needsStart = true; + } + + bladeRF2OutputThread->setFifo(requestedChannel, &m_sampleSourceFifo); + bladeRF2OutputThread->setLog2Interpolation(requestedChannel, m_settings.m_log2Interp); + + if (needsStart) + { + qDebug("BladeRF2Output::start: enabling channel(s) and (re)sart buddy thread"); + + int nbChannels = bladeRF2OutputThread->getNbChannels(); + + for (int i = 0; i < nbChannels; i++) + { + if (!m_deviceShared.m_dev->openTx(i)) { + qCritical("BladeRF2Output::start: channel %u cannot be enabled", i); + } + } + + bladeRF2OutputThread->startWork(); + } + + applySettings(m_settings, true); + + qDebug("BladeRF2Output::start: started"); + m_running = true; + + return true; +} + +void BladeRF2Output::stop() +{ + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Tx channel is stopped + // + // If the thread is currently managing only one channel (SO mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MO mode) and we are removing the last channel. The transition + // from MO to SO or reduction of MO size is handled by stopping the thread, deleting it and creating a new one + // with one channel less if (and only if) there is still a channel active. + // + // If the thread is currently managing many channels (MO mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that this FIFO will not be used anymore. + // In this case the channel is not closed (disabled) so that other channels can continue with the + // same configuration. The device continues streaming on this channel but the samples are set to all zeros. + + if (!m_running) { + return; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + BladeRF2OutputThread *bladeRF2OutputThread = findThread(); + + if (bladeRF2OutputThread == 0) { // no thread allocated + return; + } + + int nbOriginalChannels = bladeRF2OutputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SO mode => just stop and delete the thread + { + qDebug("BladeRF2Output::stop: SO mode. Just stop and delete the thread"); + bladeRF2OutputThread->stopWork(); + delete bladeRF2OutputThread; + m_thread = 0; + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + m_deviceShared.m_dev->closeTx(0); // close the unique channel + } + else if (requestedChannel == nbOriginalChannels - 1) // remove last MO channel => reduce by deleting and re-creating the thread + { + qDebug("BladeRF2Output::stop: MO mode. Reduce by deleting and re-creating the thread"); + bladeRF2OutputThread->stopWork(); + SampleSourceFifo **fifos = new SampleSourceFifo*[nbOriginalChannels-1]; + unsigned int *log2Interps = new unsigned int[nbOriginalChannels-1]; + bool stillActiveFIFO = false; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references + { + fifos[i] = bladeRF2OutputThread->getFifo(i); + stillActiveFIFO = stillActiveFIFO || (bladeRF2OutputThread->getFifo(i) != 0); + log2Interps[i] = bladeRF2OutputThread->getLog2Interpolation(i); + } + + delete bladeRF2OutputThread; + m_thread = 0; + + if (stillActiveFIFO) + { + bladeRF2OutputThread = new BladeRF2OutputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); + m_thread = bladeRF2OutputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels-1; i++) // restore original FIFO references + { + bladeRF2OutputThread->setFifo(i, fifos[i]); + bladeRF2OutputThread->setLog2Interpolation(i, log2Interps[i]); + } + } + else + { + qDebug("BladeRF2Output::stop: do not re-create thread as there are no more FIFOs active"); + } + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + m_deviceShared.m_dev->closeTx(requestedChannel); // close the last channel + + if (stillActiveFIFO) { + bladeRF2OutputThread->startWork(); + } + } + else // remove channel from existing thread + { + qDebug("BladeRF2Output::stop: MO mode. Not changing MO configuration. Just remove FIFO reference"); + bladeRF2OutputThread->setFifo(requestedChannel, 0); // remove FIFO + } + + m_running = false; +} + +QByteArray BladeRF2Output::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2Output::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureBladeRF2* message = MsgConfigureBladeRF2::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureBladeRF2* messageToGUI = MsgConfigureBladeRF2::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& BladeRF2Output::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int BladeRF2Output::getSampleRate() const +{ + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } +} + +void BladeRF2Output::getFrequencyRange(uint64_t& min, uint64_t& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getFrequencyRangeTx(min, max, step); + } +} + +void BladeRF2Output::getSampleRateRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getSampleRateRangeTx(min, max, step); + } +} + +void BladeRF2Output::getBandwidthRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getBandwidthRangeTx(min, max, step); + } +} + +void BladeRF2Output::getGlobalGainRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getGlobalGainRangeTx(min, max, step); + } +} + +bool BladeRF2Output::handleMessage(const Message& message) +{ + if (MsgConfigureBladeRF2::match(message)) + { + MsgConfigureBladeRF2& conf = (MsgConfigureBladeRF2&) message; + qDebug() << "BladeRF2Output::handleMessage: MsgConfigureBladeRF2"; + + if (!applySettings(conf.getSettings(), conf.getForce())) + { + qDebug("BladeRF2Output::handleMessage: MsgConfigureBladeRF2 config error"); + } + + return true; + } + else if (DeviceBladeRF2Shared::MsgReportBuddyChange::match(message)) + { + DeviceBladeRF2Shared::MsgReportBuddyChange& report = (DeviceBladeRF2Shared::MsgReportBuddyChange&) message; + struct bladerf *dev = m_deviceShared.m_dev->getDev(); + BladeRF2OutputSettings settings = m_settings; + int status; + unsigned int tmp_uint; + bool tmp_bool; + + // evaluate changes that may have been introduced by changes in a buddy + + if (dev) // The BladeRF device must have been open to do so + { + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (report.getRxElseTx()) // Rx buddy change: check for sample rate change only + { + status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_uint); + + if (status < 0) { + qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); + } else { + settings.m_devSampleRate = tmp_uint+1; + } + } + else // Tx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth + { + settings.m_devSampleRate = report.getDevSampleRate(); + settings.m_centerFrequency = report.getCenterFrequency(); + + status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_uint); + + if (status < 0) { + qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_bandwidth error: %s", bladerf_strerror(status)); + } else { + settings.m_bandwidth = tmp_uint; + } + + status = bladerf_get_bias_tee(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_bool); + + if (status < 0) { + qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_bias_tee error: %s", bladerf_strerror(status)); + } else { + settings.m_biasTee = tmp_bool; + } + } + + // change DSP settings if buddy change introduced a change in center frequency or base rate + if ((settings.m_centerFrequency != m_settings.m_centerFrequency) || (settings.m_devSampleRate != m_settings.m_devSampleRate)) + { + int sampleRate = settings.m_devSampleRate/(1<getDeviceEngineInputMessageQueue()->push(notif); + } + + m_settings = settings; // acknowledge the new settings + + // propagate settings to GUI if any + if (getMessageQueueToGUI()) + { + MsgConfigureBladeRF2 *reportToGUI = MsgConfigureBladeRF2::create(m_settings, false); + getMessageQueueToGUI()->push(reportToGUI); + } + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "BladeRF2Input::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initGeneration()) + { + m_deviceAPI->startGeneration(); + } + } + else + { + m_deviceAPI->stopGeneration(); + } + + return true; + } + else + { + return false; + } +} + +bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool force) +{ + bool forwardChangeOwnDSP = false; + bool forwardChangeRxBuddies = false; + bool forwardChangeTxBuddies = false; + + struct bladerf *dev = m_deviceShared.m_dev->getDev(); + int requestedChannel = m_deviceAPI->getItemIndex(); + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + forwardChangeOwnDSP = true; + forwardChangeRxBuddies = true; + forwardChangeTxBuddies = true; + + if (dev != 0) + { + unsigned int actualSamplerate; + int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), settings.m_devSampleRate, &actualSamplerate); + + if (status < 0) + { + qCritical("BladeRF2Output::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2Output::applySettings: bladerf_set_sample_rate(BLADERF_MODULE_RX) actual sample rate is " << actualSamplerate; + } + } + } + + if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) + { + forwardChangeTxBuddies = true; + + if (dev != 0) + { + unsigned int actualBandwidth; + int status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(requestedChannel), settings.m_bandwidth, &actualBandwidth); + + if(status < 0) + { + qCritical("BladeRF2Output::applySettings: could not set bandwidth: %d: %s", + settings.m_bandwidth, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2Output::applySettings: bladerf_set_bandwidth(BLADERF_MODULE_RX) actual bandwidth is " << actualBandwidth; + } + } + } + + if ((m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + forwardChangeOwnDSP = true; + BladeRF2OutputThread *outputThread = findThread(); + + if (outputThread != 0) + { + outputThread->setLog2Interpolation(requestedChannel, settings.m_log2Interp); + qDebug() << "BladeRF2Output::applySettings: set interpolation to " << (1<push(msg); + } + } + } + } + + if ((m_settings.m_biasTee != settings.m_biasTee) || force) + { + forwardChangeTxBuddies = true; + m_deviceShared.m_dev->setBiasTeeTx(settings.m_biasTee); + } + + if ((m_settings.m_gainMode != settings.m_gainMode) || force) + { + forwardChangeTxBuddies = true; + + if (dev) + { + int status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_TX(requestedChannel), (bladerf_gain_mode) settings.m_gainMode); + + if (status < 0) { + qWarning("BladeRF2Output::applySettings: bladerf_set_gain_mode(%d) failed: %s", + settings.m_gainMode, bladerf_strerror(status)); + } else { + qDebug("BladeRF2Output::applySettings: bladerf_set_gain_mode(%d)", settings.m_gainMode); + } + } + } + + if ((m_settings.m_globalGain != settings.m_globalGain) + || ((m_settings.m_gainMode != settings.m_gainMode) && (settings.m_gainMode == BLADERF_GAIN_MANUAL)) || force) + { + forwardChangeTxBuddies = true; + + if (dev) + { +// qDebug("BladeRF2Output::applySettings: channel: %d gain: %d", requestedChannel, settings.m_globalGain); + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(requestedChannel), settings.m_globalGain); + + if (status < 0) { + qWarning("BladeRF2Output::applySettings: bladerf_set_gain(%d) failed: %s", + settings.m_globalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2Output::applySettings: bladerf_set_gain(%d)", settings.m_globalGain); + } + } + } + + if (forwardChangeOwnDSP) + { + int sampleRate = settings.m_devSampleRate/(1<getDeviceEngineInputMessageQueue()->push(notif); + } + + if (forwardChangeRxBuddies) + { + // send to source buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator itSource = sourceBuddies.begin(); + + for (; itSource != sourceBuddies.end(); ++itSource) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + 0, + settings.m_devSampleRate, + false); + (*itSource)->getSampleSourceInputMessageQueue()->push(report); + } + } + + if (forwardChangeTxBuddies) + { + // send to sink buddies + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator itSink = sinkBuddies.begin(); + + for (; itSink != sinkBuddies.end(); ++itSink) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + 0, + settings.m_devSampleRate, + false); + (*itSink)->getSampleSinkInputMessageQueue()->push(report); + } + } + + m_settings = settings; + + qDebug() << "BladeRF2Output::applySettings: " + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_bandwidth: " << m_settings.m_bandwidth + << " m_log2Interp: " << m_settings.m_log2Interp + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_globalGain: " << m_settings.m_globalGain + << " m_gainMode: " << m_settings.m_gainMode + << " m_biasTee: " << m_settings.m_biasTee; + + return true; +} + +int BladeRF2Output::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf2OutputSettings(new SWGSDRangel::SWGBladeRF2OutputSettings()); + response.getBladeRf2OutputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int BladeRF2Output::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + BladeRF2OutputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getBladeRf2OutputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getBladeRf2OutputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getBladeRf2OutputSettings()->getBandwidth(); + } + if (deviceSettingsKeys.contains("log2Interp")) { + settings.m_log2Interp = response.getBladeRf2OutputSettings()->getLog2Interp(); + } + if (deviceSettingsKeys.contains("biasTee")) { + settings.m_biasTee = response.getBladeRf2OutputSettings()->getBiasTee() != 0; + } + if (deviceSettingsKeys.contains("gainMode")) { + settings.m_gainMode = response.getBladeRf2OutputSettings()->getGainMode(); + } + if (deviceSettingsKeys.contains("globalGain")) { + settings.m_globalGain = response.getBladeRf2OutputSettings()->getGlobalGain(); + } + + MsgConfigureBladeRF2 *msg = MsgConfigureBladeRF2::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBladeRF2 *msgToGUI = MsgConfigureBladeRF2::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int BladeRF2Output::webapiReportGet(SWGSDRangel::SWGDeviceReport& response, QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf2OutputReport(new SWGSDRangel::SWGBladeRF2OutputReport()); + response.getBladeRf2OutputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void BladeRF2Output::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2OutputSettings& settings) +{ + response.getBladeRf2OutputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf2OutputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf2OutputSettings()->setBandwidth(settings.m_bandwidth); + response.getBladeRf2OutputSettings()->setLog2Interp(settings.m_log2Interp); + response.getBladeRf2OutputSettings()->setBiasTee(settings.m_biasTee ? 1 : 0); + response.getBladeRf2OutputSettings()->setGainMode(settings.m_gainMode); + response.getBladeRf2OutputSettings()->setGlobalGain(settings.m_globalGain); +} + +void BladeRF2Output::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + DeviceBladeRF2 *device = m_deviceShared.m_dev; + + if (device) + { + int min, max, step; + uint64_t f_min, f_max; + + device->getBandwidthRangeTx(min, max, step); + + response.getBladeRf2OutputReport()->setBandwidthRange(new SWGSDRangel::SWGRange); + response.getBladeRf2OutputReport()->getBandwidthRange()->setMin(min); + response.getBladeRf2OutputReport()->getBandwidthRange()->setMax(max); + response.getBladeRf2OutputReport()->getBandwidthRange()->setStep(step); + + device->getFrequencyRangeTx(f_min, f_max, step); + + response.getBladeRf2OutputReport()->setFrequencyRange(new SWGSDRangel::SWGFrequencyRange); + response.getBladeRf2OutputReport()->getFrequencyRange()->setMin(f_min); + response.getBladeRf2OutputReport()->getFrequencyRange()->setMax(f_max); + response.getBladeRf2OutputReport()->getFrequencyRange()->setStep(step); + + device->getGlobalGainRangeTx(min, max, step); + + response.getBladeRf2OutputReport()->setGlobalGainRange(new SWGSDRangel::SWGRange); + response.getBladeRf2OutputReport()->getGlobalGainRange()->setMin(min); + response.getBladeRf2OutputReport()->getGlobalGainRange()->setMax(max); + response.getBladeRf2OutputReport()->getGlobalGainRange()->setStep(step); + + device->getSampleRateRangeTx(min, max, step); + + response.getBladeRf2OutputReport()->setSampleRateRange(new SWGSDRangel::SWGRange); + response.getBladeRf2OutputReport()->getSampleRateRange()->setMin(min); + response.getBladeRf2OutputReport()->getSampleRateRange()->setMax(max); + response.getBladeRf2OutputReport()->getSampleRateRange()->setStep(step); + + response.getBladeRf2OutputReport()->setGainModes(new QList); + + const std::vector& modes = getGainModes(); + std::vector::const_iterator it = modes.begin(); + + for (; it != modes.end(); ++it) + { + response.getBladeRf2OutputReport()->getGainModes()->append(new SWGSDRangel::SWGNamedEnum); + response.getBladeRf2OutputReport()->getGainModes()->back()->setName(new QString(it->m_name)); + response.getBladeRf2OutputReport()->getGainModes()->back()->setValue(it->m_value); + } + } +} + +int BladeRF2Output::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int BladeRF2Output::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + m_guiMessageQueue->push(msgToGUI); + } + + return 200; +} + diff --git a/plugins/samplesink/bladerf2output/bladerf2output.h b/plugins/samplesink/bladerf2output/bladerf2output.h new file mode 100644 index 000000000..f67e79165 --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2output.h @@ -0,0 +1,176 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUT_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUT_H_ + +#include +#include + +#include "dsp/devicesamplesink.h" +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2outputsettings.h" + +class DeviceSinkAPI; +class BladeRF2OutputThread; +struct bladerf_gain_modes; + +class BladeRF2Output : public DeviceSampleSink { +public: + class MsgConfigureBladeRF2 : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const BladeRF2OutputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureBladeRF2* create(const BladeRF2OutputSettings& settings, bool force) + { + return new MsgConfigureBladeRF2(settings, force); + } + + private: + BladeRF2OutputSettings m_settings; + bool m_force; + + MsgConfigureBladeRF2(const BladeRF2OutputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + 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) + { } + }; + + class MsgReportGainRange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMin() const { return m_min; } + int getMax() const { return m_max; } + int getStep() const { return m_step; } + + static MsgReportGainRange* create(int min, int max, int step) { + return new MsgReportGainRange(min, max, step); + } + + protected: + int m_min; + int m_max; + int m_step; + + MsgReportGainRange(int min, int max, int step) : + Message(), + m_min(min), + m_max(max), + m_step(step) + {} + }; + + struct GainMode + { + QString m_name; + int m_value; + }; + + BladeRF2Output(DeviceSinkAPI *deviceAPI); + virtual ~BladeRF2Output(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + BladeRF2OutputThread *getThread() { return m_thread; } + void setThread(BladeRF2OutputThread *thread) { m_thread = thread; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void getFrequencyRange(uint64_t& min, uint64_t& max, int& step); + void getSampleRateRange(int& min, int& max, int& step); + void getBandwidthRange(int& min, int& max, int& step); + void getGlobalGainRange(int& min, int& max, int& step); + const std::vector& getGainModes() { return m_gainModes; } + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + +private: + bool openDevice(); + void closeDevice(); + BladeRF2OutputThread *findThread(); + void moveThreadToBuddy(); + bool applySettings(const BladeRF2OutputSettings& settings, bool force); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2OutputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); + + DeviceSinkAPI *m_deviceAPI; + QMutex m_mutex; + BladeRF2OutputSettings m_settings; + struct bladerf* m_dev; + BladeRF2OutputThread* m_thread; + QString m_deviceDescription; + DeviceBladeRF2Shared m_deviceShared; + bool m_running; + std::vector m_gainModes; +}; + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUT_H_ */ diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui new file mode 100644 index 000000000..751a8db80 --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui @@ -0,0 +1,464 @@ + + + Bladerf2OutputGui + + + + 0 + 0 + 350 + 200 + + + + + 0 + 0 + + + + + 350 + 200 + + + + + Liberation Sans + 9 + + + + BladeRF2 + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + + + kHz + + + + + + + + 0 + 0 + + + + BW + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + RF bandwidth + + + + + + + Bias Tee + + + BT + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + 2 + + + 2 + + + + + + 0 + 0 + + + + SR + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Device sample rate + + + + + + + S/s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + I + + + + + + + + 50 + 16777215 + + + + Decimation factor + + + 0 + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + + + + + + 3 + + + + + Gain value + + + Qt::Horizontal + + + + + + + Gain mode + + + + + + + Gain + + + + + + + + 45 + 0 + + + + 000 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + ValueDial + QWidget +
    gui/valuedial.h
    + 1 +
    + + ButtonSwitch + QToolButton +
    gui/buttonswitch.h
    +
    +
    + + + + +
    diff --git a/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp new file mode 100644 index 000000000..5c950c6df --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 // +// // +// 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 "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "device/devicesourceapi.h" + +#include "bladerf2outputplugin.h" + +#ifdef SERVER_MODE +#include "bladerf2output.h" +#else +#include "bladerf2outputgui.h" +#endif + +const PluginDescriptor Bladerf2OutputPlugin::m_pluginDescriptor = { + QString("BladeRF2 Output"), + QString("4.2.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString Bladerf2OutputPlugin::m_hardwareID = "BladeRF2"; +const QString Bladerf2OutputPlugin::m_deviceTypeID = BLADERF2OUTPUT_DEVICE_TYPE_ID; + +Bladerf2OutputPlugin::Bladerf1OutputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& Bladerf2OutputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void Bladerf2OutputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSink(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices Bladerf2OutputPlugin::enumSampleSinks() +{ + SamplingDevices result; + struct bladerf_devinfo *devinfo = 0; + + int count = bladerf_get_device_list(&devinfo); + + if (devinfo) + { + for(int i = 0; i < count; i++) + { + struct bladerf *dev; + + int status = bladerf_open_with_devinfo(&dev, &devinfo[i]); + + if (status == BLADERF_ERR_NODEV) + { + qCritical("Bladerf2OutputPlugin::enumSampleSinks: No device at index %d", i); + continue; + } + else if (status != 0) + { + qCritical("Bladerf2OutputPlugin::enumSampleSinks: Failed to open device at index %d", i); + continue; + } + + const char *boardName = bladerf_get_board_name(dev); + + if (strcmp(boardName, "bladerf2") == 0) + { + unsigned int nbTxChannels = bladerf_get_channel_count(dev, BLADERF_TX); + + for (unsigned int j = 0; j < nbTxChannels; j++) + { + qDebug("Blderf2InputPlugin::enumSampleSinks: device #%d (%s) channel %u", i, devinfo[i].serial, j); + QString displayedName(QString("BladeRF2[%1:%2] %3").arg(devinfo[i].instance).arg(j).arg(devinfo[i].serial)); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + false, + nbTxChannels, + j)); + } + } + + bladerf_close(dev); + } + + bladerf_free_device_list(devinfo); // Valgrind memcheck + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* Bladerf2OutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* Bladerf2OutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sinkId == m_deviceTypeID) + { + Bladerf2OutputGui* gui = new Bladerf2OutputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSink* Bladerf2OutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) +{ + if(sinkId == m_deviceTypeID) + { + Bladerf2Output* output = new Bladerf2Output(deviceAPI); + return output; + } + else + { + return 0; + } +} + + + + diff --git a/plugins/samplesink/bladerf2output/bladerf2outputplugin.h b/plugins/samplesink/bladerf2output/bladerf2outputplugin.h new file mode 100644 index 000000000..0f46a254e --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.h @@ -0,0 +1,54 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTPLUGIN_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTPLUGIN_H_ + +#include +#include "plugin/plugininterface.h" + +class PluginAPI; + +#define BLADERF2OUTPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf2output" + +class Bladerf2OutputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID BLADERF2OUTPUT_DEVICE_TYPE_ID) + +public: + explicit Bladerf2OutputPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual SamplingDevices enumSampleSinks(); + + virtual PluginInstanceGUI* createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet); + + virtual DeviceSampleSink* createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI); + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTPLUGIN_H_ */ diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp new file mode 100644 index 000000000..111f6d06b --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "bladerf2outputsettings.h" + +#include +#include "util/simpleserializer.h" + + +BladeRF2OutputSettings::BladeRF2OutputSettings() +{ + resetToDefaults(); +} + +void BladeRF2OutputSettings::resetToDefaults() +{ + m_centerFrequency = 435000*1000; + m_devSampleRate = 3072000; + m_bandwidth = 1500000; + m_gainMode = 0; + m_globalGain = 0; + m_biasTee = false; + m_log2Interp = 0; +} + +QByteArray BladeRF2OutputSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_devSampleRate); + s.writeS32(2, m_bandwidth); + s.writeS32(3, m_gainMode); + s.writeS32(4, m_globalGain); + s.writeBool(5, m_biasTee); + s.writeU32(6, m_log2Interp); + + return s.final(); +} + +bool BladeRF2OutputSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + int intval; + + d.readS32(1, &m_devSampleRate); + d.readS32(2, &m_bandwidth); + d.readS32(3, &m_gainMode); + d.readS32(4, &m_globalGain); + d.readBool(5, &m_biasTee); + d.readU32(6, &m_log2Interp); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + + + diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h new file mode 100644 index 000000000..29132165a --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h @@ -0,0 +1,40 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTSETTINGS_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTSETTINGS_H_ + +#include +#include + +struct BladeRF2OutputSettings { + quint64 m_centerFrequency; + qint32 m_devSampleRate; + qint32 m_bandwidth; + int m_gainMode; + int m_globalGain; + bool m_biasTee; + quint32 m_log2Interp; + + BladeRF2OutputSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTSETTINGS_H_ */ diff --git a/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp new file mode 100644 index 000000000..87fd1cfdd --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp @@ -0,0 +1,223 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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/samplesourcefifo.h" + +#include "bladerf2outputthread.h" + +BladeRF2OutputThread::BladeRF2OutputThread(struct bladerf* dev, unsigned int nbTxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_nbChannels(nbTxChannels) +{ + qDebug("BladeRF2OutputThread::BladeRF2OutputThread"); + m_channels = new Channel[nbTxChannels]; + m_buf = new qint16[2*DeviceBladeRF2::blockSize*nbTxChannels]; +} + +BladeRF2OutputThread::~BladeRF2OutputThread() +{ + qDebug("BladeRF2OutputThread::~BladeRF2OutputThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_buf; + delete[] m_channels; +} + +void BladeRF2OutputThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void BladeRF2OutputThread::stopWork() +{ + m_running = false; + wait(); +} + +void BladeRF2OutputThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if ((m_nbChannels > 0) && (nbFifos > 0)) + { + int status; + + if (m_nbChannels > 1) { + status = bladerf_sync_config(m_dev, BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + } else { + status = bladerf_sync_config(m_dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + } + + if (status < 0) + { + qCritical("BladeRF2OutputThread::run: cannot configure streams: %s", bladerf_strerror(status)); + } + else + { + qDebug("BladeRF2OutputThread::run: start running loop"); + while (m_running) + { + if (m_nbChannels > 1) { + callbackMO(m_buf, DeviceBladeRF2::blockSize); + } else { + callbackSO(m_buf, 2*DeviceBladeRF2::blockSize); + } + + res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize, NULL, 10000); + + if (res < 0) + { + qCritical("BladeRF2OutputThread::run sync Rx error: %s", bladerf_strerror(res)); + break; + } + } + qDebug("BladeRF2OutputThread::run: stop running loop"); + } + } + else + { + qWarning("BladeRF2OutputThread::run: no channels or FIFO allocated. Aborting"); + } + + + m_running = false; +} + +unsigned int BladeRF2OutputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (unsigned int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void BladeRF2OutputThread::setLog2Interpolation(unsigned int channel, unsigned int log2_interp) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_log2Interp = log2_interp; + } +} + +unsigned int BladeRF2OutputThread::getLog2Interpolation(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_log2Interp; + } else { + return 0; + } +} + +void BladeRF2OutputThread::setFifo(unsigned int channel, SampleSourceFifo *sampleFifo) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSourceFifo *BladeRF2OutputThread::getFifo(unsigned int channel) +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void BladeRF2OutputThread::callbackMO(qint16* buf, qint32 samplesPerChannel) +{ + for (unsigned int channel = 0; channel < m_nbChannels; channel++) + { + if (m_channels[channel].m_sampleFifo) { + callbackSO(&buf[2*samplesPerChannel*channel], 2*samplesPerChannel, channel); + } else { + std::fill(&buf[2*samplesPerChannel*channel], &buf[2*samplesPerChannel*channel]+2*samplesPerChannel, 0); // fill with zero samples + } + } + + // TODO: write a set of interpolators that can write interleaved samples in output directly + int status = bladerf_interleave_stream_buffer(BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11 , samplesPerChannel*m_nbChannels, (void *) buf); + + if (status < 0) + { + qCritical("BladeRF2OutputThread::callbackMI: cannot interleave buffer: %s", bladerf_strerror(status)); + return; + } +} + +// Interpolate according to specified log2 (ex: log2=4 => decim=16) +void BladeRF2OutputThread::callbackSO(qint16* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTTHREAD_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTTHREAD_H_ + +#include +#include +#include +#include + +#include "bladerf2/devicebladerf2shared.h" +#include "dsp/interpolators.h" + +class SampleSinkFifo; + +class BladeRF2OutputThread : public QThread { + Q_OBJECT + +public: + BladeRF2OutputThread(struct bladerf* dev, unsigned int nbTxChannels, QObject* parent = 0); + ~BladeRF2OutputThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + unsigned int getNbChannels() const { return m_nbChannels; } + void setLog2Interpolation(unsigned int channel, unsigned int log2_interp); + unsigned int getLog2Interpolation(unsigned int channel) const; + void setFcPos(unsigned int channel, int fcPos); + int getFcPos(unsigned int channel) const; + void setFifo(unsigned int channel, SampleSourceFifo *sampleFifo); + SampleSourceFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleSourceFifo* m_sampleFifo; + unsigned int m_log2Interp; + Interpolators m_interpolators; + + Channel() : + m_sampleFifo(0), + m_log2Interp(0) + {} + + ~Channel() + {} + }; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + struct bladerf* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Rx channels + qint16 *m_buf; //!< Full buffer for SISO or MIMO operation + unsigned int m_nbChannels; + + void run(); + unsigned int getNbFifos(); + void callbackSO(qint16* buf, qint32 len, unsigned int channel = 0); + void callbackMO(qint16* buf, qint32 samplesPerChannel); +}; + + + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTTHREAD_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 4faccd6a6..0eba5ae8b 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -96,7 +96,6 @@ bool BladeRF2Input::openDevice() } // look for Rx buddies and get reference to the device object - // if there is a channel left take the first available if (m_deviceAPI->getSourceBuddies().size() > 0) // look source sibling first { qDebug("BladeRF2Input::openDevice: look in Rx buddies"); @@ -121,7 +120,6 @@ bool BladeRF2Input::openDevice() m_deviceShared.m_dev = device; } // look for Tx buddies and get reference to the device object - // allocate the Rx channel unconditionally else if (m_deviceAPI->getSinkBuddies().size() > 0) // then sink { qDebug("BladeRF2Input::openDevice: look in Tx buddies"); @@ -139,14 +137,13 @@ bool BladeRF2Input::openDevice() if (device == 0) { - qCritical("BladeRF2Input::openDevice: cannot get device pointer from Rx buddy"); + qCritical("BladeRF2Input::openDevice: cannot get device pointer from Tx buddy"); return false; } m_deviceShared.m_dev = device; } // There are no buddies then create the first BladeRF2 device - // allocate the Rx channel unconditionally else { qDebug("BladeRF2Input::openDevice: open device here"); @@ -283,7 +280,7 @@ bool BladeRF2Input::start() if (!m_deviceShared.m_dev) { - qDebug("BladerfInput::start: no device object"); + qDebug("BladeRF2Input::start: no device object"); return false; } @@ -293,13 +290,13 @@ bool BladeRF2Input::start() if (bladerf2InputThread) // if thread is already allocated { - qDebug("BladerfInput::start: thread is already allocated"); + qDebug("BladeRF2Input::start: thread is already allocated"); int nbOriginalChannels = bladerf2InputThread->getNbChannels(); if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread { - qDebug("BladerfInput::start: expand channels. Re-allocate thread and take ownership"); + qDebug("BladeRF2Input::start: expand channels. Re-allocate thread and take ownership"); SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels]; unsigned int *log2Decims = new unsigned int[nbOriginalChannels]; @@ -336,12 +333,12 @@ bool BladeRF2Input::start() } else { - qDebug("BladerfInput::start: keep buddy thread"); + qDebug("BladeRF2Input::start: keep buddy thread"); } } else // first allocation { - qDebug("BladerfInput::start: allocate thread and take ownership"); + qDebug("BladeRF2Input::start: allocate thread and take ownership"); bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); m_thread = bladerf2InputThread; // take ownership needsStart = true; @@ -353,7 +350,7 @@ bool BladeRF2Input::start() if (needsStart) { - qDebug("BladerfInput::start: enabling channel(s) and (re)sart buddy thread"); + qDebug("BladeRF2Input::start: enabling channel(s) and (re)sart buddy thread"); int nbChannels = bladerf2InputThread->getNbChannels(); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp index f9de1c623..d4d10e512 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "dsp/samplesinkfifo.h" + #include "bladerf2inputthread.h" BladeRF2InputThread::BladeRF2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent) : diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.h b/plugins/samplesource/bladerf2input/bladerf2inputthread.h index ad033c768..ad5886620 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.h @@ -29,9 +29,10 @@ #include #include "bladerf2/devicebladerf2shared.h" -#include "dsp/samplesinkfifo.h" #include "dsp/decimators.h" +class SampleSinkFifo; + class BladeRF2InputThread : public QThread { Q_OBJECT @@ -39,16 +40,16 @@ public: BladeRF2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent = NULL); ~BladeRF2InputThread(); - virtual void startWork(); - virtual void stopWork(); - virtual bool isRunning() const { return m_running; } - virtual unsigned int getNbChannels() const { return m_nbChannels; } - virtual void setLog2Decimation(unsigned int channel, unsigned int log2_decim); - virtual unsigned int getLog2Decimation(unsigned int channel) const; - virtual void setFcPos(unsigned int channel, int fcPos); - virtual int getFcPos(unsigned int channel) const; - virtual void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); - virtual SampleSinkFifo *getFifo(unsigned int channel); + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + unsigned int getNbChannels() const { return m_nbChannels; } + void setLog2Decimation(unsigned int channel, unsigned int log2_decim); + unsigned int getLog2Decimation(unsigned int channel) const; + void setFcPos(unsigned int channel, int fcPos); + int getFcPos(unsigned int channel) const; + void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); + SampleSinkFifo *getFifo(unsigned int channel); private: struct Channel diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 52d4b1fa3..30babd580 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1363,6 +1363,56 @@ margin-bottom: 20px; } }, "description" : "BladeRF2" +}; + defs.BladeRF2OutputReport = { + "properties" : { + "frequencyRange" : { + "$ref" : "#/definitions/FrequencyRange" + }, + "sampleRateRange" : { + "$ref" : "#/definitions/Range" + }, + "bandwidthRange" : { + "$ref" : "#/definitions/Range" + }, + "globalGainRange" : { + "$ref" : "#/definitions/Range" + }, + "gainModes" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/NamedEnum" + } + } + }, + "description" : "BladeRF2" +}; + defs.BladeRF2OutputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "bandwidth" : { + "type" : "integer" + }, + "gainMode" : { + "type" : "integer" + }, + "globalGain" : { + "type" : "integer" + }, + "biasTee" : { + "type" : "integer" + }, + "log2Interp" : { + "type" : "integer" + } + }, + "description" : "BladeRF2" }; defs.CWKeyerSettings = { "properties" : { @@ -1903,6 +1953,9 @@ margin-bottom: 20px; "bladeRF2InputReport" : { "$ref" : "#/definitions/BladeRF2InputReport" }, + "bladeRF2OutputReport" : { + "$ref" : "#/definitions/BladeRF2OutputReport" + }, "fileSourceReport" : { "$ref" : "#/definitions/FileSourceReport" }, @@ -2003,6 +2056,9 @@ margin-bottom: 20px; "bladeRF1OutputSettings" : { "$ref" : "#/definitions/BladeRF1OutputSettings" }, + "bladeRF2OutputSettings" : { + "$ref" : "#/definitions/BladeRF2OutputSettings" + }, "fcdProSettings" : { "$ref" : "#/definitions/FCDProSettings" }, @@ -23175,7 +23231,7 @@ except ApiException as e:
    - Generated 2018-09-25T16:51:16.493+02:00 + Generated 2018-09-29T05:36:53.044+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml index 39332634b..61f00b5d2 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml @@ -40,3 +40,38 @@ BladeRF2InputReport: type: array items: $ref: "/doc/swagger/include/Structs.yaml#/NamedEnum" + +BladeRF2OutputSettings: + description: BladeRF2 + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + bandwidth: + type: integer + gainMode: + type: integer + globalGain: + type: integer + biasTee: + type: integer + log2Interp: + type: integer + +BladeRF2OutputReport: + description: BladeRF2 + properties: + frequencyRange: + $ref: "/doc/swagger/include/Structs.yaml#/FrequencyRange" + sampleRateRange: + $ref: "/doc/swagger/include/Structs.yaml#/Range" + bandwidthRange: + $ref: "/doc/swagger/include/Structs.yaml#/Range" + globalGainRange: + $ref: "/doc/swagger/include/Structs.yaml#/Range" + gainModes: + type: array + items: + $ref: "/doc/swagger/include/Structs.yaml#/NamedEnum" diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 1e5a16dba..2a8ca35ba 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1769,6 +1769,8 @@ definitions: $ref: "/doc/swagger/include/BladeRF2.yaml#/BladeRF2InputSettings" bladeRF1OutputSettings: $ref: "/doc/swagger/include/BladeRF1.yaml#/BladeRF1OutputSettings" + bladeRF2OutputSettings: + $ref: "/doc/swagger/include/BladeRF2.yaml#/BladeRF2OutputSettings" fcdProSettings: $ref: "/doc/swagger/include/FCDPro.yaml#/FCDProSettings" fcdProPlusSettings: @@ -1820,6 +1822,8 @@ definitions: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFReport" bladeRF2InputReport: $ref: "/doc/swagger/include/BladeRF2.yaml#/BladeRF2InputReport" + bladeRF2OutputReport: + $ref: "/doc/swagger/include/BladeRF2.yaml#/BladeRF2OutputReport" fileSourceReport: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceReport" limeSdrInputReport: diff --git a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml index c87dc8d81..0198207d3 100644 --- a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml +++ b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml @@ -40,3 +40,38 @@ BladeRF2InputReport: type: array items: $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/NamedEnum" + +BladeRF2OutputSettings: + description: BladeRF2 + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + bandwidth: + type: integer + gainMode: + type: integer + globalGain: + type: integer + biasTee: + type: integer + log2Interp: + type: integer + +BladeRF2OutputReport: + description: BladeRF2 + properties: + frequencyRange: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/FrequencyRange" + sampleRateRange: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" + bandwidthRange: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" + globalGainRange: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" + gainModes: + type: array + items: + $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/NamedEnum" diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 1598f5faf..a9d37464d 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1769,6 +1769,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/BladeRF2.yaml#/BladeRF2InputSettings" bladeRF1OutputSettings: $ref: "http://localhost:8081/api/swagger/include/BladeRF1.yaml#/BladeRF1OutputSettings" + bladeRF2OutputSettings: + $ref: "http://localhost:8081/api/swagger/include/BladeRF2.yaml#/BladeRF2OutputSettings" fcdProSettings: $ref: "http://localhost:8081/api/swagger/include/FCDPro.yaml#/FCDProSettings" fcdProPlusSettings: @@ -1820,6 +1822,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFReport" bladeRF2InputReport: $ref: "http://localhost:8081/api/swagger/include/BladeRF2.yaml#/BladeRF2InputReport" + bladeRF2OutputReport: + $ref: "http://localhost:8081/api/swagger/include/BladeRF2.yaml#/BladeRF2OutputReport" fileSourceReport: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceReport" limeSdrInputReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 52d4b1fa3..30babd580 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1363,6 +1363,56 @@ margin-bottom: 20px; } }, "description" : "BladeRF2" +}; + defs.BladeRF2OutputReport = { + "properties" : { + "frequencyRange" : { + "$ref" : "#/definitions/FrequencyRange" + }, + "sampleRateRange" : { + "$ref" : "#/definitions/Range" + }, + "bandwidthRange" : { + "$ref" : "#/definitions/Range" + }, + "globalGainRange" : { + "$ref" : "#/definitions/Range" + }, + "gainModes" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/NamedEnum" + } + } + }, + "description" : "BladeRF2" +}; + defs.BladeRF2OutputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "bandwidth" : { + "type" : "integer" + }, + "gainMode" : { + "type" : "integer" + }, + "globalGain" : { + "type" : "integer" + }, + "biasTee" : { + "type" : "integer" + }, + "log2Interp" : { + "type" : "integer" + } + }, + "description" : "BladeRF2" }; defs.CWKeyerSettings = { "properties" : { @@ -1903,6 +1953,9 @@ margin-bottom: 20px; "bladeRF2InputReport" : { "$ref" : "#/definitions/BladeRF2InputReport" }, + "bladeRF2OutputReport" : { + "$ref" : "#/definitions/BladeRF2OutputReport" + }, "fileSourceReport" : { "$ref" : "#/definitions/FileSourceReport" }, @@ -2003,6 +2056,9 @@ margin-bottom: 20px; "bladeRF1OutputSettings" : { "$ref" : "#/definitions/BladeRF1OutputSettings" }, + "bladeRF2OutputSettings" : { + "$ref" : "#/definitions/BladeRF2OutputSettings" + }, "fcdProSettings" : { "$ref" : "#/definitions/FCDProSettings" }, @@ -23175,7 +23231,7 @@ except ApiException as e:
    - Generated 2018-09-25T16:51:16.493+02:00 + Generated 2018-09-29T05:36:53.044+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.cpp new file mode 100644 index 000000000..8ce6ae808 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.cpp @@ -0,0 +1,204 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBladeRF2OutputReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBladeRF2OutputReport::SWGBladeRF2OutputReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBladeRF2OutputReport::SWGBladeRF2OutputReport() { + frequency_range = nullptr; + m_frequency_range_isSet = false; + sample_rate_range = nullptr; + m_sample_rate_range_isSet = false; + bandwidth_range = nullptr; + m_bandwidth_range_isSet = false; + global_gain_range = nullptr; + m_global_gain_range_isSet = false; + gain_modes = nullptr; + m_gain_modes_isSet = false; +} + +SWGBladeRF2OutputReport::~SWGBladeRF2OutputReport() { + this->cleanup(); +} + +void +SWGBladeRF2OutputReport::init() { + frequency_range = new SWGFrequencyRange(); + m_frequency_range_isSet = false; + sample_rate_range = new SWGRange(); + m_sample_rate_range_isSet = false; + bandwidth_range = new SWGRange(); + m_bandwidth_range_isSet = false; + global_gain_range = new SWGRange(); + m_global_gain_range_isSet = false; + gain_modes = new QList(); + m_gain_modes_isSet = false; +} + +void +SWGBladeRF2OutputReport::cleanup() { + if(frequency_range != nullptr) { + delete frequency_range; + } + if(sample_rate_range != nullptr) { + delete sample_rate_range; + } + if(bandwidth_range != nullptr) { + delete bandwidth_range; + } + if(global_gain_range != nullptr) { + delete global_gain_range; + } + if(gain_modes != nullptr) { + auto arr = gain_modes; + for(auto o: *arr) { + delete o; + } + delete gain_modes; + } +} + +SWGBladeRF2OutputReport* +SWGBladeRF2OutputReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBladeRF2OutputReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&frequency_range, pJson["frequencyRange"], "SWGFrequencyRange", "SWGFrequencyRange"); + + ::SWGSDRangel::setValue(&sample_rate_range, pJson["sampleRateRange"], "SWGRange", "SWGRange"); + + ::SWGSDRangel::setValue(&bandwidth_range, pJson["bandwidthRange"], "SWGRange", "SWGRange"); + + ::SWGSDRangel::setValue(&global_gain_range, pJson["globalGainRange"], "SWGRange", "SWGRange"); + + + ::SWGSDRangel::setValue(&gain_modes, pJson["gainModes"], "QList", "SWGNamedEnum"); +} + +QString +SWGBladeRF2OutputReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBladeRF2OutputReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if((frequency_range != nullptr) && (frequency_range->isSet())){ + toJsonValue(QString("frequencyRange"), frequency_range, obj, QString("SWGFrequencyRange")); + } + if((sample_rate_range != nullptr) && (sample_rate_range->isSet())){ + toJsonValue(QString("sampleRateRange"), sample_rate_range, obj, QString("SWGRange")); + } + if((bandwidth_range != nullptr) && (bandwidth_range->isSet())){ + toJsonValue(QString("bandwidthRange"), bandwidth_range, obj, QString("SWGRange")); + } + if((global_gain_range != nullptr) && (global_gain_range->isSet())){ + toJsonValue(QString("globalGainRange"), global_gain_range, obj, QString("SWGRange")); + } + if(gain_modes->size() > 0){ + toJsonArray((QList*)gain_modes, obj, "gainModes", "SWGNamedEnum"); + } + + return obj; +} + +SWGFrequencyRange* +SWGBladeRF2OutputReport::getFrequencyRange() { + return frequency_range; +} +void +SWGBladeRF2OutputReport::setFrequencyRange(SWGFrequencyRange* frequency_range) { + this->frequency_range = frequency_range; + this->m_frequency_range_isSet = true; +} + +SWGRange* +SWGBladeRF2OutputReport::getSampleRateRange() { + return sample_rate_range; +} +void +SWGBladeRF2OutputReport::setSampleRateRange(SWGRange* sample_rate_range) { + this->sample_rate_range = sample_rate_range; + this->m_sample_rate_range_isSet = true; +} + +SWGRange* +SWGBladeRF2OutputReport::getBandwidthRange() { + return bandwidth_range; +} +void +SWGBladeRF2OutputReport::setBandwidthRange(SWGRange* bandwidth_range) { + this->bandwidth_range = bandwidth_range; + this->m_bandwidth_range_isSet = true; +} + +SWGRange* +SWGBladeRF2OutputReport::getGlobalGainRange() { + return global_gain_range; +} +void +SWGBladeRF2OutputReport::setGlobalGainRange(SWGRange* global_gain_range) { + this->global_gain_range = global_gain_range; + this->m_global_gain_range_isSet = true; +} + +QList* +SWGBladeRF2OutputReport::getGainModes() { + return gain_modes; +} +void +SWGBladeRF2OutputReport::setGainModes(QList* gain_modes) { + this->gain_modes = gain_modes; + this->m_gain_modes_isSet = true; +} + + +bool +SWGBladeRF2OutputReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(frequency_range != nullptr && frequency_range->isSet()){ isObjectUpdated = true; break;} + if(sample_rate_range != nullptr && sample_rate_range->isSet()){ isObjectUpdated = true; break;} + if(bandwidth_range != nullptr && bandwidth_range->isSet()){ isObjectUpdated = true; break;} + if(global_gain_range != nullptr && global_gain_range->isSet()){ isObjectUpdated = true; break;} + if(gain_modes->size() > 0){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.h new file mode 100644 index 000000000..e6ea9646e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.h @@ -0,0 +1,86 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBladeRF2OutputReport.h + * + * BladeRF2 + */ + +#ifndef SWGBladeRF2OutputReport_H_ +#define SWGBladeRF2OutputReport_H_ + +#include + + +#include "SWGFrequencyRange.h" +#include "SWGNamedEnum.h" +#include "SWGRange.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBladeRF2OutputReport: public SWGObject { +public: + SWGBladeRF2OutputReport(); + SWGBladeRF2OutputReport(QString* json); + virtual ~SWGBladeRF2OutputReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBladeRF2OutputReport* fromJson(QString &jsonString) override; + + SWGFrequencyRange* getFrequencyRange(); + void setFrequencyRange(SWGFrequencyRange* frequency_range); + + SWGRange* getSampleRateRange(); + void setSampleRateRange(SWGRange* sample_rate_range); + + SWGRange* getBandwidthRange(); + void setBandwidthRange(SWGRange* bandwidth_range); + + SWGRange* getGlobalGainRange(); + void setGlobalGainRange(SWGRange* global_gain_range); + + QList* getGainModes(); + void setGainModes(QList* gain_modes); + + + virtual bool isSet() override; + +private: + SWGFrequencyRange* frequency_range; + bool m_frequency_range_isSet; + + SWGRange* sample_rate_range; + bool m_sample_rate_range_isSet; + + SWGRange* bandwidth_range; + bool m_bandwidth_range_isSet; + + SWGRange* global_gain_range; + bool m_global_gain_range_isSet; + + QList* gain_modes; + bool m_gain_modes_isSet; + +}; + +} + +#endif /* SWGBladeRF2OutputReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp new file mode 100644 index 000000000..f28ca0d5c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp @@ -0,0 +1,232 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBladeRF2OutputSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBladeRF2OutputSettings::SWGBladeRF2OutputSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBladeRF2OutputSettings::SWGBladeRF2OutputSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + bandwidth = 0; + m_bandwidth_isSet = false; + gain_mode = 0; + m_gain_mode_isSet = false; + global_gain = 0; + m_global_gain_isSet = false; + bias_tee = 0; + m_bias_tee_isSet = false; + log2_interp = 0; + m_log2_interp_isSet = false; +} + +SWGBladeRF2OutputSettings::~SWGBladeRF2OutputSettings() { + this->cleanup(); +} + +void +SWGBladeRF2OutputSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + bandwidth = 0; + m_bandwidth_isSet = false; + gain_mode = 0; + m_gain_mode_isSet = false; + global_gain = 0; + m_global_gain_isSet = false; + bias_tee = 0; + m_bias_tee_isSet = false; + log2_interp = 0; + m_log2_interp_isSet = false; +} + +void +SWGBladeRF2OutputSettings::cleanup() { + + + + + + + +} + +SWGBladeRF2OutputSettings* +SWGBladeRF2OutputSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBladeRF2OutputSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain_mode, pJson["gainMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&global_gain, pJson["globalGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&bias_tee, pJson["biasTee"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_interp, pJson["log2Interp"], "qint32", ""); + +} + +QString +SWGBladeRF2OutputSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBladeRF2OutputSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_dev_sample_rate_isSet){ + obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); + } + if(m_bandwidth_isSet){ + obj->insert("bandwidth", QJsonValue(bandwidth)); + } + if(m_gain_mode_isSet){ + obj->insert("gainMode", QJsonValue(gain_mode)); + } + if(m_global_gain_isSet){ + obj->insert("globalGain", QJsonValue(global_gain)); + } + if(m_bias_tee_isSet){ + obj->insert("biasTee", QJsonValue(bias_tee)); + } + if(m_log2_interp_isSet){ + obj->insert("log2Interp", QJsonValue(log2_interp)); + } + + return obj; +} + +qint64 +SWGBladeRF2OutputSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGBladeRF2OutputSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGBladeRF2OutputSettings::getDevSampleRate() { + return dev_sample_rate; +} +void +SWGBladeRF2OutputSettings::setDevSampleRate(qint32 dev_sample_rate) { + this->dev_sample_rate = dev_sample_rate; + this->m_dev_sample_rate_isSet = true; +} + +qint32 +SWGBladeRF2OutputSettings::getBandwidth() { + return bandwidth; +} +void +SWGBladeRF2OutputSettings::setBandwidth(qint32 bandwidth) { + this->bandwidth = bandwidth; + this->m_bandwidth_isSet = true; +} + +qint32 +SWGBladeRF2OutputSettings::getGainMode() { + return gain_mode; +} +void +SWGBladeRF2OutputSettings::setGainMode(qint32 gain_mode) { + this->gain_mode = gain_mode; + this->m_gain_mode_isSet = true; +} + +qint32 +SWGBladeRF2OutputSettings::getGlobalGain() { + return global_gain; +} +void +SWGBladeRF2OutputSettings::setGlobalGain(qint32 global_gain) { + this->global_gain = global_gain; + this->m_global_gain_isSet = true; +} + +qint32 +SWGBladeRF2OutputSettings::getBiasTee() { + return bias_tee; +} +void +SWGBladeRF2OutputSettings::setBiasTee(qint32 bias_tee) { + this->bias_tee = bias_tee; + this->m_bias_tee_isSet = true; +} + +qint32 +SWGBladeRF2OutputSettings::getLog2Interp() { + return log2_interp; +} +void +SWGBladeRF2OutputSettings::setLog2Interp(qint32 log2_interp) { + this->log2_interp = log2_interp; + this->m_log2_interp_isSet = true; +} + + +bool +SWGBladeRF2OutputSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_gain_mode_isSet){ isObjectUpdated = true; break;} + if(m_global_gain_isSet){ isObjectUpdated = true; break;} + if(m_bias_tee_isSet){ isObjectUpdated = true; break;} + if(m_log2_interp_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h new file mode 100644 index 000000000..89daff30f --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h @@ -0,0 +1,94 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.2.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBladeRF2OutputSettings.h + * + * BladeRF2 + */ + +#ifndef SWGBladeRF2OutputSettings_H_ +#define SWGBladeRF2OutputSettings_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBladeRF2OutputSettings: public SWGObject { +public: + SWGBladeRF2OutputSettings(); + SWGBladeRF2OutputSettings(QString* json); + virtual ~SWGBladeRF2OutputSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBladeRF2OutputSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getDevSampleRate(); + void setDevSampleRate(qint32 dev_sample_rate); + + qint32 getBandwidth(); + void setBandwidth(qint32 bandwidth); + + qint32 getGainMode(); + void setGainMode(qint32 gain_mode); + + qint32 getGlobalGain(); + void setGlobalGain(qint32 global_gain); + + qint32 getBiasTee(); + void setBiasTee(qint32 bias_tee); + + qint32 getLog2Interp(); + void setLog2Interp(qint32 log2_interp); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 dev_sample_rate; + bool m_dev_sample_rate_isSet; + + qint32 bandwidth; + bool m_bandwidth_isSet; + + qint32 gain_mode; + bool m_gain_mode_isSet; + + qint32 global_gain; + bool m_global_gain_isSet; + + qint32 bias_tee; + bool m_bias_tee_isSet; + + qint32 log2_interp; + bool m_log2_interp_isSet; + +}; + +} + +#endif /* SWGBladeRF2OutputSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp index a153e10a1..2620b44b0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.cpp @@ -38,6 +38,8 @@ SWGDeviceReport::SWGDeviceReport() { m_airspy_hf_report_isSet = false; blade_rf2_input_report = nullptr; m_blade_rf2_input_report_isSet = false; + blade_rf2_output_report = nullptr; + m_blade_rf2_output_report_isSet = false; file_source_report = nullptr; m_file_source_report_isSet = false; lime_sdr_input_report = nullptr; @@ -76,6 +78,8 @@ SWGDeviceReport::init() { m_airspy_hf_report_isSet = false; blade_rf2_input_report = new SWGBladeRF2InputReport(); m_blade_rf2_input_report_isSet = false; + blade_rf2_output_report = new SWGBladeRF2OutputReport(); + m_blade_rf2_output_report_isSet = false; file_source_report = new SWGFileSourceReport(); m_file_source_report_isSet = false; lime_sdr_input_report = new SWGLimeSdrInputReport(); @@ -113,6 +117,9 @@ SWGDeviceReport::cleanup() { if(blade_rf2_input_report != nullptr) { delete blade_rf2_input_report; } + if(blade_rf2_output_report != nullptr) { + delete blade_rf2_output_report; + } if(file_source_report != nullptr) { delete file_source_report; } @@ -166,6 +173,8 @@ SWGDeviceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&blade_rf2_input_report, pJson["bladeRF2InputReport"], "SWGBladeRF2InputReport", "SWGBladeRF2InputReport"); + ::SWGSDRangel::setValue(&blade_rf2_output_report, pJson["bladeRF2OutputReport"], "SWGBladeRF2OutputReport", "SWGBladeRF2OutputReport"); + ::SWGSDRangel::setValue(&file_source_report, pJson["fileSourceReport"], "SWGFileSourceReport", "SWGFileSourceReport"); ::SWGSDRangel::setValue(&lime_sdr_input_report, pJson["limeSdrInputReport"], "SWGLimeSdrInputReport", "SWGLimeSdrInputReport"); @@ -217,6 +226,9 @@ SWGDeviceReport::asJsonObject() { if((blade_rf2_input_report != nullptr) && (blade_rf2_input_report->isSet())){ toJsonValue(QString("bladeRF2InputReport"), blade_rf2_input_report, obj, QString("SWGBladeRF2InputReport")); } + if((blade_rf2_output_report != nullptr) && (blade_rf2_output_report->isSet())){ + toJsonValue(QString("bladeRF2OutputReport"), blade_rf2_output_report, obj, QString("SWGBladeRF2OutputReport")); + } if((file_source_report != nullptr) && (file_source_report->isSet())){ toJsonValue(QString("fileSourceReport"), file_source_report, obj, QString("SWGFileSourceReport")); } @@ -301,6 +313,16 @@ SWGDeviceReport::setBladeRf2InputReport(SWGBladeRF2InputReport* blade_rf2_input_ this->m_blade_rf2_input_report_isSet = true; } +SWGBladeRF2OutputReport* +SWGDeviceReport::getBladeRf2OutputReport() { + return blade_rf2_output_report; +} +void +SWGDeviceReport::setBladeRf2OutputReport(SWGBladeRF2OutputReport* blade_rf2_output_report) { + this->blade_rf2_output_report = blade_rf2_output_report; + this->m_blade_rf2_output_report_isSet = true; +} + SWGFileSourceReport* SWGDeviceReport::getFileSourceReport() { return file_source_report; @@ -411,6 +433,7 @@ SWGDeviceReport::isSet(){ if(airspy_report != nullptr && airspy_report->isSet()){ isObjectUpdated = true; break;} if(airspy_hf_report != nullptr && airspy_hf_report->isSet()){ isObjectUpdated = true; break;} if(blade_rf2_input_report != nullptr && blade_rf2_input_report->isSet()){ isObjectUpdated = true; break;} + if(blade_rf2_output_report != nullptr && blade_rf2_output_report->isSet()){ isObjectUpdated = true; break;} if(file_source_report != nullptr && file_source_report->isSet()){ isObjectUpdated = true; break;} if(lime_sdr_input_report != nullptr && lime_sdr_input_report->isSet()){ isObjectUpdated = true; break;} if(lime_sdr_output_report != nullptr && lime_sdr_output_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h index 068ea5a1a..22987625f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceReport.h @@ -25,6 +25,7 @@ #include "SWGAirspyHFReport.h" #include "SWGAirspyReport.h" #include "SWGBladeRF2InputReport.h" +#include "SWGBladeRF2OutputReport.h" #include "SWGFileSourceReport.h" #include "SWGLimeSdrInputReport.h" #include "SWGLimeSdrOutputReport.h" @@ -70,6 +71,9 @@ public: SWGBladeRF2InputReport* getBladeRf2InputReport(); void setBladeRf2InputReport(SWGBladeRF2InputReport* blade_rf2_input_report); + SWGBladeRF2OutputReport* getBladeRf2OutputReport(); + void setBladeRf2OutputReport(SWGBladeRF2OutputReport* blade_rf2_output_report); + SWGFileSourceReport* getFileSourceReport(); void setFileSourceReport(SWGFileSourceReport* file_source_report); @@ -119,6 +123,9 @@ private: SWGBladeRF2InputReport* blade_rf2_input_report; bool m_blade_rf2_input_report_isSet; + SWGBladeRF2OutputReport* blade_rf2_output_report; + bool m_blade_rf2_output_report_isSet; + SWGFileSourceReport* file_source_report; bool m_file_source_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 8f581f8ec..d64fa15fe 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -42,6 +42,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_blade_rf2_input_settings_isSet = false; blade_rf1_output_settings = nullptr; m_blade_rf1_output_settings_isSet = false; + blade_rf2_output_settings = nullptr; + m_blade_rf2_output_settings_isSet = false; fcd_pro_settings = nullptr; m_fcd_pro_settings_isSet = false; fcd_pro_plus_settings = nullptr; @@ -94,6 +96,8 @@ SWGDeviceSettings::init() { m_blade_rf2_input_settings_isSet = false; blade_rf1_output_settings = new SWGBladeRF1OutputSettings(); m_blade_rf1_output_settings_isSet = false; + blade_rf2_output_settings = new SWGBladeRF2OutputSettings(); + m_blade_rf2_output_settings_isSet = false; fcd_pro_settings = new SWGFCDProSettings(); m_fcd_pro_settings_isSet = false; fcd_pro_plus_settings = new SWGFCDProPlusSettings(); @@ -147,6 +151,9 @@ SWGDeviceSettings::cleanup() { if(blade_rf1_output_settings != nullptr) { delete blade_rf1_output_settings; } + if(blade_rf2_output_settings != nullptr) { + delete blade_rf2_output_settings; + } if(fcd_pro_settings != nullptr) { delete fcd_pro_settings; } @@ -219,6 +226,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&blade_rf1_output_settings, pJson["bladeRF1OutputSettings"], "SWGBladeRF1OutputSettings", "SWGBladeRF1OutputSettings"); + ::SWGSDRangel::setValue(&blade_rf2_output_settings, pJson["bladeRF2OutputSettings"], "SWGBladeRF2OutputSettings", "SWGBladeRF2OutputSettings"); + ::SWGSDRangel::setValue(&fcd_pro_settings, pJson["fcdProSettings"], "SWGFCDProSettings", "SWGFCDProSettings"); ::SWGSDRangel::setValue(&fcd_pro_plus_settings, pJson["fcdProPlusSettings"], "SWGFCDProPlusSettings", "SWGFCDProPlusSettings"); @@ -286,6 +295,9 @@ SWGDeviceSettings::asJsonObject() { if((blade_rf1_output_settings != nullptr) && (blade_rf1_output_settings->isSet())){ toJsonValue(QString("bladeRF1OutputSettings"), blade_rf1_output_settings, obj, QString("SWGBladeRF1OutputSettings")); } + if((blade_rf2_output_settings != nullptr) && (blade_rf2_output_settings->isSet())){ + toJsonValue(QString("bladeRF2OutputSettings"), blade_rf2_output_settings, obj, QString("SWGBladeRF2OutputSettings")); + } if((fcd_pro_settings != nullptr) && (fcd_pro_settings->isSet())){ toJsonValue(QString("fcdProSettings"), fcd_pro_settings, obj, QString("SWGFCDProSettings")); } @@ -405,6 +417,16 @@ SWGDeviceSettings::setBladeRf1OutputSettings(SWGBladeRF1OutputSettings* blade_rf this->m_blade_rf1_output_settings_isSet = true; } +SWGBladeRF2OutputSettings* +SWGDeviceSettings::getBladeRf2OutputSettings() { + return blade_rf2_output_settings; +} +void +SWGDeviceSettings::setBladeRf2OutputSettings(SWGBladeRF2OutputSettings* blade_rf2_output_settings) { + this->blade_rf2_output_settings = blade_rf2_output_settings; + this->m_blade_rf2_output_settings_isSet = true; +} + SWGFCDProSettings* SWGDeviceSettings::getFcdProSettings() { return fcd_pro_settings; @@ -567,6 +589,7 @@ SWGDeviceSettings::isSet(){ if(blade_rf1_input_settings != nullptr && blade_rf1_input_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf2_input_settings != nullptr && blade_rf2_input_settings->isSet()){ isObjectUpdated = true; break;} if(blade_rf1_output_settings != nullptr && blade_rf1_output_settings->isSet()){ isObjectUpdated = true; break;} + if(blade_rf2_output_settings != nullptr && blade_rf2_output_settings->isSet()){ isObjectUpdated = true; break;} if(fcd_pro_settings != nullptr && fcd_pro_settings->isSet()){ isObjectUpdated = true; break;} if(fcd_pro_plus_settings != nullptr && fcd_pro_plus_settings->isSet()){ isObjectUpdated = true; break;} if(file_source_settings != nullptr && file_source_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 9ed650e38..047cd48e9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -27,6 +27,7 @@ #include "SWGBladeRF1InputSettings.h" #include "SWGBladeRF1OutputSettings.h" #include "SWGBladeRF2InputSettings.h" +#include "SWGBladeRF2OutputSettings.h" #include "SWGFCDProPlusSettings.h" #include "SWGFCDProSettings.h" #include "SWGFileSourceSettings.h" @@ -83,6 +84,9 @@ public: SWGBladeRF1OutputSettings* getBladeRf1OutputSettings(); void setBladeRf1OutputSettings(SWGBladeRF1OutputSettings* blade_rf1_output_settings); + SWGBladeRF2OutputSettings* getBladeRf2OutputSettings(); + void setBladeRf2OutputSettings(SWGBladeRF2OutputSettings* blade_rf2_output_settings); + SWGFCDProSettings* getFcdProSettings(); void setFcdProSettings(SWGFCDProSettings* fcd_pro_settings); @@ -153,6 +157,9 @@ private: SWGBladeRF1OutputSettings* blade_rf1_output_settings; bool m_blade_rf1_output_settings_isSet; + SWGBladeRF2OutputSettings* blade_rf2_output_settings; + bool m_blade_rf2_output_settings_isSet; + SWGFCDProSettings* fcd_pro_settings; bool m_fcd_pro_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 1ac604c66..a1526953b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -34,6 +34,8 @@ #include "SWGBladeRF1OutputSettings.h" #include "SWGBladeRF2InputReport.h" #include "SWGBladeRF2InputSettings.h" +#include "SWGBladeRF2OutputReport.h" +#include "SWGBladeRF2OutputSettings.h" #include "SWGCWKeyerSettings.h" #include "SWGChannel.h" #include "SWGChannelListItem.h" @@ -182,6 +184,12 @@ namespace SWGSDRangel { if(QString("SWGBladeRF2InputSettings").compare(type) == 0) { return new SWGBladeRF2InputSettings(); } + if(QString("SWGBladeRF2OutputReport").compare(type) == 0) { + return new SWGBladeRF2OutputReport(); + } + if(QString("SWGBladeRF2OutputSettings").compare(type) == 0) { + return new SWGBladeRF2OutputSettings(); + } if(QString("SWGCWKeyerSettings").compare(type) == 0) { return new SWGCWKeyerSettings(); } From d75a576f697f683a2c81d5250a95e5013729da43 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 29 Sep 2018 09:56:54 +0200 Subject: [PATCH 814/956] BladeRF output (2) --- .../bladerf1output/bladerf1outputgui.cpp | 3 - .../samplesink/bladerf2output/CMakeLists.txt | 8 +- .../bladerf2output/bladerf2outputgui.cpp | 365 ++++++++++++++++++ .../bladerf2output/bladerf2outputgui.h | 90 +++++ .../bladerf2output/bladerf2outputgui.ui | 6 +- .../bladerf2output/bladerf2outputplugin.cpp | 24 +- .../bladerf2output/bladerf2outputplugin.h | 4 +- .../bladerf2output/bladerf2outputsettings.cpp | 2 - pluginssrv/samplesink/CMakeLists.txt | 2 + .../samplesink/bladerf2output/CMakeLists.txt | 68 ++++ 10 files changed, 546 insertions(+), 26 deletions(-) create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputgui.cpp create mode 100644 plugins/samplesink/bladerf2output/bladerf2outputgui.h create mode 100644 pluginssrv/samplesink/bladerf2output/CMakeLists.txt diff --git a/plugins/samplesink/bladerf1output/bladerf1outputgui.cpp b/plugins/samplesink/bladerf1output/bladerf1outputgui.cpp index 5509da344..76562676f 100644 --- a/plugins/samplesink/bladerf1output/bladerf1outputgui.cpp +++ b/plugins/samplesink/bladerf1output/bladerf1outputgui.cpp @@ -61,9 +61,6 @@ Bladerf1OutputGui::Bladerf1OutputGui(DeviceUISet *deviceUISet, QWidget* parent) displaySettings(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceUISet->m_deviceSinkAPI->getDeviceUID()); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); } diff --git a/plugins/samplesink/bladerf2output/CMakeLists.txt b/plugins/samplesink/bladerf2output/CMakeLists.txt index 907d1d0da..620cb2965 100644 --- a/plugins/samplesink/bladerf2output/CMakeLists.txt +++ b/plugins/samplesink/bladerf2output/CMakeLists.txt @@ -3,17 +3,17 @@ project(bladerf2output) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(bladerf2output_SOURCES -# bladerf2outputgui.cpp + bladerf2outputgui.cpp bladerf2output.cpp -# bladerf2outputplugin.cpp + bladerf2outputplugin.cpp bladerf2outputsettings.cpp bladerf2outputthread.cpp ) set(bladerf2output_HEADERS -# bladerf2outputgui.h + bladerf2outputgui.h bladerf2output.h -# bladerf1soutputplugin.h + bladerf1soutputplugin.h bladerf2outputsettings.h bladerf2outputthread.h ) diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp new file mode 100644 index 000000000..560d22752 --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp @@ -0,0 +1,365 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "ui_bladerf2outputgui.h" +#include "gui/colormapper.h" +#include "gui/glspectrum.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/devicesinkapi.h" +#include "device/deviceuiset.h" + +#include "bladerf2outputgui.h" + +BladeRF2OutputGui::BladeRF2OutputGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::BladeRF2OutputGui), + m_deviceUISet(deviceUISet), + m_doApplySettings(true), + m_forceSettings(true), + m_settings(), + m_sampleRate(0), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) +{ + m_sampleSink = (BladeRF2Output*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); + int max, min, step; + uint64_t f_min, f_max; + + ui->setupUi(this); + + m_sampleSink->getFrequencyRange(f_min, f_max, step); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + + m_sampleSink->getSampleRateRange(min, max, step); + ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); + ui->sampleRate->setValueRange(8, min, max); + + m_sampleSink->getBandwidthRange(min, max, step); + ui->bandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); + ui->bandwidth->setValueRange(5, min/1000, max/1000); + + const std::vector& modes = m_sampleSink->getGainModes(); + std::vector::const_iterator it = modes.begin(); + + ui->gainMode->blockSignals(true); + + for (; it != modes.end(); ++it) { + ui->gainMode->addItem(it->m_name); + } + + ui->gainMode->blockSignals(false); + + m_sampleSink->getGlobalGainRange(min, max, step); + ui->gain->setMinimum(min); + ui->gain->setMaximum(max); + ui->gain->setPageStep(step); + ui->gain->setSingleStep(step); + + ui->label_decim->setText(QString::fromUtf8("I\u2191")); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSink->setMessageQueueToGUI(&m_inputMessageQueue); +} + +BladeRF2OutputGui::~BladeRF2OutputGui() +{ + delete ui; +} + +void BladeRF2OutputGui::destroy() +{ + delete this; +} + +void BladeRF2OutputGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString BladeRF2OutputGui::getName() const +{ + return objectName(); +} + +void BladeRF2OutputGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 BladeRF2OutputGui::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +void BladeRF2OutputGui::setCenterFrequency(qint64 centerFrequency) +{ + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray BladeRF2OutputGui::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2OutputGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool BladeRF2OutputGui::handleMessage(const Message& message) +{ + if (BladeRF2Output::MsgConfigureBladeRF2::match(message)) + { + const BladeRF2Output::MsgConfigureBladeRF2& cfg = (BladeRF2Output::MsgConfigureBladeRF2&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + int min, max, step; + m_sampleSink->getGlobalGainRange(min, max, step); + ui->gain->setMinimum(min); + ui->gain->setMaximum(max); + ui->gain->setPageStep(step); + ui->gain->setSingleStep(step); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (BladeRF2Output::MsgReportGainRange::match(message)) + { + const BladeRF2Output::MsgReportGainRange& cfg = (BladeRF2Output::MsgReportGainRange&) message; + ui->gain->setMinimum(cfg.getMin()); + ui->gain->setMaximum(cfg.getMax()); + ui->gain->setSingleStep(cfg.getStep()); + ui->gain->setPageStep(cfg.getStep()); + + return true; + } + else if (BladeRF2Output::MsgStartStop::match(message)) + { + BladeRF2Output::MsgStartStop& notif = (BladeRF2Output::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void BladeRF2OutputGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("BladeRF2OutputGui::handleInputMessages: message: %s", message->getIdentifier()); + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("BladeRF2OutputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void BladeRF2OutputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateLabel->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); +} + +void BladeRF2OutputGui::displaySettings() +{ + blockApplySettings(true); + + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + ui->sampleRate->setValue(m_settings.m_devSampleRate); + ui->bandwidth->setValue(m_settings.m_bandwidth / 1000); + + ui->interp->setCurrentIndex(m_settings.m_log2Interp); + + ui->gainText->setText(tr("%1 dB").arg(m_settings.m_globalGain)); + ui->gain->setValue(m_settings.m_globalGain); + + if (m_settings.m_gainMode == BLADERF_GAIN_MANUAL) { + ui->gain->setEnabled(true); + } else { + ui->gain->setEnabled(false); + } + + blockApplySettings(false); +} + +void BladeRF2OutputGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +void BladeRF2OutputGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void BladeRF2OutputGui::on_sampleRate_changed(quint64 value) +{ + m_settings.m_devSampleRate = value; + sendSettings(); +} + +void BladeRF2OutputGui::on_biasTee_toggled(bool checked) +{ + m_settings.m_biasTee = checked; + sendSettings(); +} + +void BladeRF2OutputGui::on_bandwidth_changed(quint64 value) +{ + m_settings.m_bandwidth = value * 1000; + sendSettings(); +} + +void BladeRF2OutputGui::on_interp_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) + return; + m_settings.m_log2Interp = index; + sendSettings(); +} + +void BladeRF2OutputGui::on_gainMode_currentIndexChanged(int index) +{ + const std::vector& modes = m_sampleSink->getGainModes(); + unsigned int uindex = index < 0 ? 0 : (unsigned int) index; + + if (uindex < modes.size()) + { + BladeRF2Output::GainMode mode = modes[index]; + + if (m_settings.m_gainMode != mode.m_value) + { + if (mode.m_value == BLADERF_GAIN_MANUAL) + { + m_settings.m_globalGain = ui->gain->value(); + ui->gain->setEnabled(true); + } else { + ui->gain->setEnabled(false); + } + } + + m_settings.m_gainMode = mode.m_value; + sendSettings(); + } +} + +void BladeRF2OutputGui::on_gain_valueChanged(int value) +{ + ui->gainText->setText(tr("%1 dB").arg(value)); + m_settings.m_globalGain = value; + sendSettings(); +} + +void BladeRF2OutputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + BladeRF2Output::MsgStartStop *message = BladeRF2Output::MsgStartStop::create(checked); + m_sampleSink->getInputMessageQueue()->push(message); + } +} + +void BladeRF2OutputGui::updateHardware() +{ + if (m_doApplySettings) + { + qDebug() << "BladeRF2OutputGui::updateHardware"; + BladeRF2Output::MsgConfigureBladeRF2* message = BladeRF2Output::MsgConfigureBladeRF2::create(m_settings, m_forceSettings); + m_sampleSink->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void BladeRF2OutputGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceSinkAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DSPDeviceSinkEngine::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DSPDeviceSinkEngine::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DSPDeviceSinkEngine::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DSPDeviceSinkEngine::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSinkAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.h b/plugins/samplesink/bladerf2output/bladerf2outputgui.h new file mode 100644 index 000000000..0fa4228bb --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.h @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTGUI_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTGUI_H_ + +#include +#include + +#include "plugin/plugininstancegui.h" +#include "util/messagequeue.h" + +#include "bladerf2output.h" + +class DeviceSampleSink; +class DeviceUISet; + +namespace Ui { + class BladeRF2OutputGui; +} + +class BladeRF2OutputGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit BladeRF2OutputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~BladeRF2OutputGui(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::BladeRF2OutputGui* ui; + + DeviceUISet* m_deviceUISet; + bool m_doApplySettings; + bool m_forceSettings; + BladeRF2OutputSettings m_settings; + QTimer m_updateTimer; + QTimer m_statusTimer; + BladeRF2Output* m_sampleSink; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void sendSettings(); + void updateSampleRateAndFrequency(); + +private slots: + void handleInputMessages(); + void on_centerFrequency_changed(quint64 value); + void on_biasTee_toggled(bool checked); + void on_sampleRate_changed(quint64 value); + void on_bandwidth_changed(quint64 value); + void on_interp_currentIndexChanged(int index); + void on_gainMode_currentIndexChanged(int index); + void on_gain_valueChanged(int value); + void on_startStop_toggled(bool checked); + void updateHardware(); + void updateStatus(); +}; + + + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTGUI_H_ */ diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui index 751a8db80..468db150f 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui @@ -1,7 +1,7 @@ - Bladerf2OutputGui - + BladeRF2OutputGui + 0 @@ -324,7 +324,7 @@
    - + 50 diff --git a/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp index 5c950c6df..09fc023a1 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp @@ -28,7 +28,7 @@ #include "bladerf2outputgui.h" #endif -const PluginDescriptor Bladerf2OutputPlugin::m_pluginDescriptor = { +const PluginDescriptor BladeRF2OutputPlugin::m_pluginDescriptor = { QString("BladeRF2 Output"), QString("4.2.0"), QString("(c) Edouard Griffiths, F4EXB"), @@ -37,25 +37,25 @@ const PluginDescriptor Bladerf2OutputPlugin::m_pluginDescriptor = { QString("https://github.com/f4exb/sdrangel") }; -const QString Bladerf2OutputPlugin::m_hardwareID = "BladeRF2"; -const QString Bladerf2OutputPlugin::m_deviceTypeID = BLADERF2OUTPUT_DEVICE_TYPE_ID; +const QString BladeRF2OutputPlugin::m_hardwareID = "BladeRF2"; +const QString BladeRF2OutputPlugin::m_deviceTypeID = BLADERF2OUTPUT_DEVICE_TYPE_ID; -Bladerf2OutputPlugin::Bladerf1OutputPlugin(QObject* parent) : +BladeRF2OutputPlugin::BladeRF2OutputPlugin(QObject* parent) : QObject(parent) { } -const PluginDescriptor& Bladerf2OutputPlugin::getPluginDescriptor() const +const PluginDescriptor& BladeRF2OutputPlugin::getPluginDescriptor() const { return m_pluginDescriptor; } -void Bladerf2OutputPlugin::initPlugin(PluginAPI* pluginAPI) +void BladeRF2OutputPlugin::initPlugin(PluginAPI* pluginAPI) { pluginAPI->registerSampleSink(m_deviceTypeID, this); } -PluginInterface::SamplingDevices Bladerf2OutputPlugin::enumSampleSinks() +PluginInterface::SamplingDevices BladeRF2OutputPlugin::enumSampleSinks() { SamplingDevices result; struct bladerf_devinfo *devinfo = 0; @@ -113,7 +113,7 @@ PluginInterface::SamplingDevices Bladerf2OutputPlugin::enumSampleSinks() } #ifdef SERVER_MODE -PluginInstanceGUI* Bladerf2OutputPlugin::createSampleSinkPluginInstanceGUI( +PluginInstanceGUI* BladeRF2OutputPlugin::createSampleSinkPluginInstanceGUI( const QString& sinkId __attribute__((unused)), QWidget **widget __attribute__((unused)), DeviceUISet *deviceUISet __attribute__((unused))) @@ -121,14 +121,14 @@ PluginInstanceGUI* Bladerf2OutputPlugin::createSampleSinkPluginInstanceGUI( return 0; } #else -PluginInstanceGUI* Bladerf2OutputPlugin::createSampleSinkPluginInstanceGUI( +PluginInstanceGUI* BladeRF2OutputPlugin::createSampleSinkPluginInstanceGUI( const QString& sinkId, QWidget **widget, DeviceUISet *deviceUISet) { if(sinkId == m_deviceTypeID) { - Bladerf2OutputGui* gui = new Bladerf2OutputGui(deviceUISet); + BladeRF2OutputGui* gui = new BladeRF2OutputGui(deviceUISet); *widget = gui; return gui; } @@ -139,11 +139,11 @@ PluginInstanceGUI* Bladerf2OutputPlugin::createSampleSinkPluginInstanceGUI( } #endif -DeviceSampleSink* Bladerf2OutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) +DeviceSampleSink* BladeRF2OutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) { if(sinkId == m_deviceTypeID) { - Bladerf2Output* output = new Bladerf2Output(deviceAPI); + BladeRF2Output* output = new BladeRF2Output(deviceAPI); return output; } else diff --git a/plugins/samplesink/bladerf2output/bladerf2outputplugin.h b/plugins/samplesink/bladerf2output/bladerf2outputplugin.h index 0f46a254e..ef10913de 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputplugin.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.h @@ -24,13 +24,13 @@ class PluginAPI; #define BLADERF2OUTPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf2output" -class Bladerf2OutputPlugin : public QObject, public PluginInterface { +class BladeRF2OutputPlugin : public QObject, public PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) Q_PLUGIN_METADATA(IID BLADERF2OUTPUT_DEVICE_TYPE_ID) public: - explicit Bladerf2OutputPlugin(QObject* parent = 0); + explicit BladeRF2OutputPlugin(QObject* parent = 0); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp index 111f6d06b..7ad09065c 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp @@ -62,8 +62,6 @@ bool BladeRF2OutputSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { - int intval; - d.readS32(1, &m_devSampleRate); d.readS32(2, &m_bandwidth); d.readS32(3, &m_gainMode); diff --git a/pluginssrv/samplesink/CMakeLists.txt b/pluginssrv/samplesink/CMakeLists.txt index 5fcece26d..ec27eee6c 100644 --- a/pluginssrv/samplesink/CMakeLists.txt +++ b/pluginssrv/samplesink/CMakeLists.txt @@ -5,6 +5,7 @@ find_package(LibUSB) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) add_subdirectory(bladerf1output) + add_subdirectory(bladerf2output) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) find_package(LibHACKRF) @@ -29,6 +30,7 @@ endif(CM256CC_FOUND) if (BUILD_DEBIAN) add_subdirectory(bladerf1output) + add_subdirectory(bladerf2output) add_subdirectory(hackrfoutput) add_subdirectory(limesdroutput) add_subdirectory(plutosdroutput) diff --git a/pluginssrv/samplesink/bladerf2output/CMakeLists.txt b/pluginssrv/samplesink/bladerf2output/CMakeLists.txt new file mode 100644 index 000000000..69bc590cf --- /dev/null +++ b/pluginssrv/samplesink/bladerf2output/CMakeLists.txt @@ -0,0 +1,68 @@ +project(bladerf2output) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesink/bladerf2output") + +set(bladerf2output_SOURCES + ${PLUGIN_PREFIX}/bladerf2output.cpp + ${PLUGIN_PREFIX}/bladerf2outputplugin.cpp + ${PLUGIN_PREFIX}/bladerf2outputsettings.cpp + ${PLUGIN_PREFIX}/bladerf2outputthread.cpp +) + +set(bladerf2output_HEADERS + ${PLUGIN_PREFIX}/bladerf2output.h + ${PLUGIN_PREFIX}/bladerf2outputplugin.h + ${PLUGIN_PREFIX}/bladerf2outputsettings.h + ${PLUGIN_PREFIX}/bladerf2outputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(outputbladerf2srv SHARED + ${bladerf2output_SOURCES} + ${bladerf2output_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputbladerf2srv + ${QT_LIBRARIES} + bladerf + sdrbase + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(outputbladerf2srv + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputbladerf2srv Qt5::Core) + +install(TARGETS outputbladerf2srv DESTINATION lib/pluginssrv/samplesink) From 5566dc6a7ee54a10734d0495601e720261421346 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 29 Sep 2018 10:53:44 +0200 Subject: [PATCH 815/956] BladeRF2 output: fixed gain handling --- .../bladerf2output/bladerf2output.cpp | 25 ------- .../bladerf2output/bladerf2output.h | 8 --- .../bladerf2output/bladerf2outputgui.cpp | 70 +++++-------------- .../bladerf2output/bladerf2outputgui.h | 1 - .../bladerf2output/bladerf2outputgui.ui | 13 +--- .../bladerf2output/bladerf2outputsettings.cpp | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 8 +-- .../webapi/doc/swagger/include/BladeRF2.yaml | 4 -- .../api/swagger/include/BladeRF2.yaml | 4 -- swagger/sdrangel/code/html2/index.html | 8 +-- .../qt5/client/SWGBladeRF2OutputReport.cpp | 27 ------- .../code/qt5/client/SWGBladeRF2OutputReport.h | 8 --- 12 files changed, 22 insertions(+), 156 deletions(-) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 83a58081f..fe1d38575 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -48,19 +48,6 @@ BladeRF2Output::BladeRF2Output(DeviceSinkAPI *deviceAPI) : m_running(false) { openDevice(); - - if (m_deviceShared.m_dev) - { - const bladerf_gain_modes *modes = 0; - int nbModes = m_deviceShared.m_dev->getGainModesRx(&modes); - - if (modes) - { - for (int i = 0; i < nbModes; i++) { - m_gainModes.push_back(GainMode{QString(modes[i].name), modes[i].mode}); - } - } - } } BladeRF2Output::~BladeRF2Output() @@ -948,18 +935,6 @@ void BladeRF2Output::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& resp response.getBladeRf2OutputReport()->getSampleRateRange()->setMin(min); response.getBladeRf2OutputReport()->getSampleRateRange()->setMax(max); response.getBladeRf2OutputReport()->getSampleRateRange()->setStep(step); - - response.getBladeRf2OutputReport()->setGainModes(new QList); - - const std::vector& modes = getGainModes(); - std::vector::const_iterator it = modes.begin(); - - for (; it != modes.end(); ++it) - { - response.getBladeRf2OutputReport()->getGainModes()->append(new SWGSDRangel::SWGNamedEnum); - response.getBladeRf2OutputReport()->getGainModes()->back()->setName(new QString(it->m_name)); - response.getBladeRf2OutputReport()->getGainModes()->back()->setValue(it->m_value); - } } } diff --git a/plugins/samplesink/bladerf2output/bladerf2output.h b/plugins/samplesink/bladerf2output/bladerf2output.h index f67e79165..3aa11401b 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.h +++ b/plugins/samplesink/bladerf2output/bladerf2output.h @@ -97,12 +97,6 @@ public: {} }; - struct GainMode - { - QString m_name; - int m_value; - }; - BladeRF2Output(DeviceSinkAPI *deviceAPI); virtual ~BladeRF2Output(); virtual void destroy(); @@ -126,7 +120,6 @@ public: void getSampleRateRange(int& min, int& max, int& step); void getBandwidthRange(int& min, int& max, int& step); void getGlobalGainRange(int& min, int& max, int& step); - const std::vector& getGainModes() { return m_gainModes; } virtual bool handleMessage(const Message& message); @@ -170,7 +163,6 @@ private: QString m_deviceDescription; DeviceBladeRF2Shared m_deviceShared; bool m_running; - std::vector m_gainModes; }; #endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUT_H_ */ diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp index 560d22752..446930cb5 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp @@ -46,33 +46,26 @@ BladeRF2OutputGui::BladeRF2OutputGui(DeviceUISet *deviceUISet, QWidget* parent) ui->setupUi(this); m_sampleSink->getFrequencyRange(f_min, f_max, step); + qDebug("BladeRF2OutputGui::BladeRF2OutputGui: getFrequencyRange: [%lu,%lu] step: %d", f_min, f_max, step); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); m_sampleSink->getSampleRateRange(min, max, step); + qDebug("BladeRF2OutputGui::BladeRF2OutputGui: getSampleRateRange: [%d,%d] step: %d", min, max, step); ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); ui->sampleRate->setValueRange(8, min, max); m_sampleSink->getBandwidthRange(min, max, step); + qDebug("BladeRF2OutputGui::BladeRF2OutputGui: getBandwidthRange: [%d,%d] step: %d", min, max, step); ui->bandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); ui->bandwidth->setValueRange(5, min/1000, max/1000); - const std::vector& modes = m_sampleSink->getGainModes(); - std::vector::const_iterator it = modes.begin(); - - ui->gainMode->blockSignals(true); - - for (; it != modes.end(); ++it) { - ui->gainMode->addItem(it->m_name); - } - - ui->gainMode->blockSignals(false); - m_sampleSink->getGlobalGainRange(min, max, step); - ui->gain->setMinimum(min); - ui->gain->setMaximum(max); - ui->gain->setPageStep(step); - ui->gain->setSingleStep(step); + qDebug("BladeRF2OutputGui::BladeRF2OutputGui: getGlobalGainRange: [%d,%d] step: %d", min, max, step); + ui->gain->setMinimum((min-max)/1000); + ui->gain->setMaximum(0); + ui->gain->setPageStep(1); + ui->gain->setSingleStep(1); ui->label_decim->setText(QString::fromUtf8("I\u2191")); @@ -152,10 +145,10 @@ bool BladeRF2OutputGui::handleMessage(const Message& message) blockApplySettings(true); int min, max, step; m_sampleSink->getGlobalGainRange(min, max, step); - ui->gain->setMinimum(min); - ui->gain->setMaximum(max); - ui->gain->setPageStep(step); - ui->gain->setSingleStep(step); + ui->gain->setMinimum((min-max)/1000); + ui->gain->setMaximum(0); + ui->gain->setPageStep(1); + ui->gain->setSingleStep(1); displaySettings(); blockApplySettings(false); @@ -164,10 +157,10 @@ bool BladeRF2OutputGui::handleMessage(const Message& message) else if (BladeRF2Output::MsgReportGainRange::match(message)) { const BladeRF2Output::MsgReportGainRange& cfg = (BladeRF2Output::MsgReportGainRange&) message; - ui->gain->setMinimum(cfg.getMin()); - ui->gain->setMaximum(cfg.getMax()); - ui->gain->setSingleStep(cfg.getStep()); - ui->gain->setPageStep(cfg.getStep()); + ui->gain->setMinimum((cfg.getMin()-cfg.getMax())/1000); + ui->gain->setMaximum(0); + ui->gain->setSingleStep(1); + ui->gain->setPageStep(1); return true; } @@ -234,12 +227,6 @@ void BladeRF2OutputGui::displaySettings() ui->gainText->setText(tr("%1 dB").arg(m_settings.m_globalGain)); ui->gain->setValue(m_settings.m_globalGain); - if (m_settings.m_gainMode == BLADERF_GAIN_MANUAL) { - ui->gain->setEnabled(true); - } else { - ui->gain->setEnabled(false); - } - blockApplySettings(false); } @@ -281,31 +268,6 @@ void BladeRF2OutputGui::on_interp_currentIndexChanged(int index) sendSettings(); } -void BladeRF2OutputGui::on_gainMode_currentIndexChanged(int index) -{ - const std::vector& modes = m_sampleSink->getGainModes(); - unsigned int uindex = index < 0 ? 0 : (unsigned int) index; - - if (uindex < modes.size()) - { - BladeRF2Output::GainMode mode = modes[index]; - - if (m_settings.m_gainMode != mode.m_value) - { - if (mode.m_value == BLADERF_GAIN_MANUAL) - { - m_settings.m_globalGain = ui->gain->value(); - ui->gain->setEnabled(true); - } else { - ui->gain->setEnabled(false); - } - } - - m_settings.m_gainMode = mode.m_value; - sendSettings(); - } -} - void BladeRF2OutputGui::on_gain_valueChanged(int value) { ui->gainText->setText(tr("%1 dB").arg(value)); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.h b/plugins/samplesink/bladerf2output/bladerf2outputgui.h index 0fa4228bb..c93c2f9fe 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.h @@ -78,7 +78,6 @@ private slots: void on_sampleRate_changed(quint64 value); void on_bandwidth_changed(quint64 value); void on_interp_currentIndexChanged(int index); - void on_gainMode_currentIndexChanged(int index); void on_gain_valueChanged(int value); void on_startStop_toggled(bool checked); void updateHardware(); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui index 468db150f..7facfdc9a 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui @@ -372,11 +372,11 @@
    - + 3 - + Gain value @@ -386,13 +386,6 @@ - - - - Gain mode - - - @@ -400,7 +393,7 @@ - + diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp index 7ad09065c..2a69e969b 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp @@ -31,7 +31,7 @@ void BladeRF2OutputSettings::resetToDefaults() m_devSampleRate = 3072000; m_bandwidth = 1500000; m_gainMode = 0; - m_globalGain = 0; + m_globalGain = -20; m_biasTee = false; m_log2Interp = 0; } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 30babd580..162f29506 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1377,12 +1377,6 @@ margin-bottom: 20px; }, "globalGainRange" : { "$ref" : "#/definitions/Range" - }, - "gainModes" : { - "type" : "array", - "items" : { - "$ref" : "#/definitions/NamedEnum" - } } }, "description" : "BladeRF2" @@ -23231,7 +23225,7 @@ except ApiException as e:
    - Generated 2018-09-29T05:36:53.044+02:00 + Generated 2018-09-29T10:10:59.386+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml index 61f00b5d2..5a6e73ed3 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml @@ -71,7 +71,3 @@ BladeRF2OutputReport: $ref: "/doc/swagger/include/Structs.yaml#/Range" globalGainRange: $ref: "/doc/swagger/include/Structs.yaml#/Range" - gainModes: - type: array - items: - $ref: "/doc/swagger/include/Structs.yaml#/NamedEnum" diff --git a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml index 0198207d3..3d5c16dc2 100644 --- a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml +++ b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml @@ -71,7 +71,3 @@ BladeRF2OutputReport: $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" globalGainRange: $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/Range" - gainModes: - type: array - items: - $ref: "http://localhost:8081/api/swagger/include/Structs.yaml#/NamedEnum" diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 30babd580..162f29506 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1377,12 +1377,6 @@ margin-bottom: 20px; }, "globalGainRange" : { "$ref" : "#/definitions/Range" - }, - "gainModes" : { - "type" : "array", - "items" : { - "$ref" : "#/definitions/NamedEnum" - } } }, "description" : "BladeRF2" @@ -23231,7 +23225,7 @@ except ApiException as e:
    - Generated 2018-09-29T05:36:53.044+02:00 + Generated 2018-09-29T10:10:59.386+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.cpp index 8ce6ae808..ef8e4b342 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.cpp @@ -36,8 +36,6 @@ SWGBladeRF2OutputReport::SWGBladeRF2OutputReport() { m_bandwidth_range_isSet = false; global_gain_range = nullptr; m_global_gain_range_isSet = false; - gain_modes = nullptr; - m_gain_modes_isSet = false; } SWGBladeRF2OutputReport::~SWGBladeRF2OutputReport() { @@ -54,8 +52,6 @@ SWGBladeRF2OutputReport::init() { m_bandwidth_range_isSet = false; global_gain_range = new SWGRange(); m_global_gain_range_isSet = false; - gain_modes = new QList(); - m_gain_modes_isSet = false; } void @@ -72,13 +68,6 @@ SWGBladeRF2OutputReport::cleanup() { if(global_gain_range != nullptr) { delete global_gain_range; } - if(gain_modes != nullptr) { - auto arr = gain_modes; - for(auto o: *arr) { - delete o; - } - delete gain_modes; - } } SWGBladeRF2OutputReport* @@ -100,8 +89,6 @@ SWGBladeRF2OutputReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&global_gain_range, pJson["globalGainRange"], "SWGRange", "SWGRange"); - - ::SWGSDRangel::setValue(&gain_modes, pJson["gainModes"], "QList", "SWGNamedEnum"); } QString @@ -130,9 +117,6 @@ SWGBladeRF2OutputReport::asJsonObject() { if((global_gain_range != nullptr) && (global_gain_range->isSet())){ toJsonValue(QString("globalGainRange"), global_gain_range, obj, QString("SWGRange")); } - if(gain_modes->size() > 0){ - toJsonArray((QList*)gain_modes, obj, "gainModes", "SWGNamedEnum"); - } return obj; } @@ -177,16 +161,6 @@ SWGBladeRF2OutputReport::setGlobalGainRange(SWGRange* global_gain_range) { this->m_global_gain_range_isSet = true; } -QList* -SWGBladeRF2OutputReport::getGainModes() { - return gain_modes; -} -void -SWGBladeRF2OutputReport::setGainModes(QList* gain_modes) { - this->gain_modes = gain_modes; - this->m_gain_modes_isSet = true; -} - bool SWGBladeRF2OutputReport::isSet(){ @@ -196,7 +170,6 @@ SWGBladeRF2OutputReport::isSet(){ if(sample_rate_range != nullptr && sample_rate_range->isSet()){ isObjectUpdated = true; break;} if(bandwidth_range != nullptr && bandwidth_range->isSet()){ isObjectUpdated = true; break;} if(global_gain_range != nullptr && global_gain_range->isSet()){ isObjectUpdated = true; break;} - if(gain_modes->size() > 0){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.h index e6ea9646e..03a946a29 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputReport.h @@ -23,9 +23,7 @@ #include "SWGFrequencyRange.h" -#include "SWGNamedEnum.h" #include "SWGRange.h" -#include #include "SWGObject.h" #include "export.h" @@ -57,9 +55,6 @@ public: SWGRange* getGlobalGainRange(); void setGlobalGainRange(SWGRange* global_gain_range); - QList* getGainModes(); - void setGainModes(QList* gain_modes); - virtual bool isSet() override; @@ -76,9 +71,6 @@ private: SWGRange* global_gain_range; bool m_global_gain_range_isSet; - QList* gain_modes; - bool m_gain_modes_isSet; - }; } From 53ff8f32bf07701695b06a8216038096196fd9c2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 29 Sep 2018 21:40:22 +0200 Subject: [PATCH 816/956] BladeRF2 output: fixed SO mode --- devices/bladerf2/devicebladerf2shared.cpp | 4 + devices/bladerf2/devicebladerf2shared.h | 4 + .../bladerf2output/bladerf2output.cpp | 60 ++++++++------ .../bladerf2output/bladerf2outputsettings.cpp | 5 +- .../bladerf2output/bladerf2outputsettings.h | 1 - .../bladerf2output/bladerf2outputthread.cpp | 81 +++++++++++-------- .../bladerf2input/bladerf2input.cpp | 4 +- sdrbase/dsp/samplesourcefifo.h | 2 +- .../api/swagger/include/BladeRF2.yaml | 2 - 9 files changed, 94 insertions(+), 69 deletions(-) diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp index d33c1a3da..7339a7021 100644 --- a/devices/bladerf2/devicebladerf2shared.cpp +++ b/devices/bladerf2/devicebladerf2shared.cpp @@ -18,6 +18,10 @@ MESSAGE_CLASS_DEFINITION(DeviceBladeRF2Shared::MsgReportBuddyChange, Message) +const float DeviceBladeRF2Shared::m_sampleFifoLengthInSeconds = 0.25; +const int DeviceBladeRF2Shared::m_sampleFifoMinSize = 75000; // 300 kS/s knee +const int DeviceBladeRF2Shared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32 + DeviceBladeRF2Shared::DeviceBladeRF2Shared() : m_dev(0), m_channel(-1), diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index fbb2c9c55..43474d7fa 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -79,6 +79,10 @@ public: int m_channel; //!< allocated channel (-1 if none) BladeRF2Input *m_source; BladeRF2Output *m_sink; + + static const float m_sampleFifoLengthInSeconds; + static const int m_sampleFifoMinSize; + static const int m_sampleFifoMinSize32; }; diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index fe1d38575..87a3f2331 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -645,6 +645,37 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool struct bladerf *dev = m_deviceShared.m_dev->getDev(); int requestedChannel = m_deviceAPI->getItemIndex(); + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + BladeRF2OutputThread *bladeRF2OutputThread = findThread(); + SampleSourceFifo *fifo = 0; + + if (bladeRF2OutputThread) + { + fifo = bladeRF2OutputThread->getFifo(requestedChannel); + bladeRF2OutputThread->setFifo(requestedChannel, 0); + } + + int fifoSize; + + if (settings.m_log2Interp >= 5) + { + fifoSize = DeviceBladeRF2Shared::m_sampleFifoMinSize32; + } + else + { + fifoSize = std::max( + (int) ((settings.m_devSampleRate/(1<setFifo(requestedChannel, &m_sampleSourceFifo); + } + } + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) { forwardChangeOwnDSP = true; @@ -663,7 +694,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool } else { - qDebug() << "BladeRF2Output::applySettings: bladerf_set_sample_rate(BLADERF_MODULE_RX) actual sample rate is " << actualSamplerate; + qDebug() << "BladeRF2Output::applySettings: bladerf_set_sample_rate: actual sample rate is " << actualSamplerate; } } } @@ -684,7 +715,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool } else { - qDebug() << "BladeRF2Output::applySettings: bladerf_set_bandwidth(BLADERF_MODULE_RX) actual bandwidth is " << actualBandwidth; + qDebug() << "BladeRF2Output::applySettings: bladerf_set_bandwidth: actual bandwidth is " << actualBandwidth; } } } @@ -736,25 +767,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool m_deviceShared.m_dev->setBiasTeeTx(settings.m_biasTee); } - if ((m_settings.m_gainMode != settings.m_gainMode) || force) - { - forwardChangeTxBuddies = true; - - if (dev) - { - int status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_TX(requestedChannel), (bladerf_gain_mode) settings.m_gainMode); - - if (status < 0) { - qWarning("BladeRF2Output::applySettings: bladerf_set_gain_mode(%d) failed: %s", - settings.m_gainMode, bladerf_strerror(status)); - } else { - qDebug("BladeRF2Output::applySettings: bladerf_set_gain_mode(%d)", settings.m_gainMode); - } - } - } - - if ((m_settings.m_globalGain != settings.m_globalGain) - || ((m_settings.m_gainMode != settings.m_gainMode) && (settings.m_gainMode == BLADERF_GAIN_MANUAL)) || force) + if ((m_settings.m_globalGain != settings.m_globalGain) || force) { forwardChangeTxBuddies = true; @@ -821,7 +834,6 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool << " m_log2Interp: " << m_settings.m_log2Interp << " m_devSampleRate: " << m_settings.m_devSampleRate << " m_globalGain: " << m_settings.m_globalGain - << " m_gainMode: " << m_settings.m_gainMode << " m_biasTee: " << m_settings.m_biasTee; return true; @@ -860,9 +872,6 @@ int BladeRF2Output::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("biasTee")) { settings.m_biasTee = response.getBladeRf2OutputSettings()->getBiasTee() != 0; } - if (deviceSettingsKeys.contains("gainMode")) { - settings.m_gainMode = response.getBladeRf2OutputSettings()->getGainMode(); - } if (deviceSettingsKeys.contains("globalGain")) { settings.m_globalGain = response.getBladeRf2OutputSettings()->getGlobalGain(); } @@ -895,7 +904,6 @@ void BladeRF2Output::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response.getBladeRf2OutputSettings()->setBandwidth(settings.m_bandwidth); response.getBladeRf2OutputSettings()->setLog2Interp(settings.m_log2Interp); response.getBladeRf2OutputSettings()->setBiasTee(settings.m_biasTee ? 1 : 0); - response.getBladeRf2OutputSettings()->setGainMode(settings.m_gainMode); response.getBladeRf2OutputSettings()->setGlobalGain(settings.m_globalGain); } diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp index 2a69e969b..b7f808c01 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp @@ -30,8 +30,7 @@ void BladeRF2OutputSettings::resetToDefaults() m_centerFrequency = 435000*1000; m_devSampleRate = 3072000; m_bandwidth = 1500000; - m_gainMode = 0; - m_globalGain = -20; + m_globalGain = -3; m_biasTee = false; m_log2Interp = 0; } @@ -42,7 +41,6 @@ QByteArray BladeRF2OutputSettings::serialize() const s.writeS32(1, m_devSampleRate); s.writeS32(2, m_bandwidth); - s.writeS32(3, m_gainMode); s.writeS32(4, m_globalGain); s.writeBool(5, m_biasTee); s.writeU32(6, m_log2Interp); @@ -64,7 +62,6 @@ bool BladeRF2OutputSettings::deserialize(const QByteArray& data) { d.readS32(1, &m_devSampleRate); d.readS32(2, &m_bandwidth); - d.readS32(3, &m_gainMode); d.readS32(4, &m_globalGain); d.readBool(5, &m_biasTee); d.readU32(6, &m_log2Interp); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h index 29132165a..333aa750d 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h @@ -24,7 +24,6 @@ struct BladeRF2OutputSettings { quint64 m_centerFrequency; qint32 m_devSampleRate; qint32 m_bandwidth; - int m_gainMode; int m_globalGain; bool m_biasTee; quint32 m_log2Interp; diff --git a/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp index 87fd1cfdd..f02ab126b 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp @@ -92,10 +92,10 @@ void BladeRF2OutputThread::run() if (m_nbChannels > 1) { callbackMO(m_buf, DeviceBladeRF2::blockSize); } else { - callbackSO(m_buf, 2*DeviceBladeRF2::blockSize); + callbackSO(m_buf, DeviceBladeRF2::blockSize); } - res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize, NULL, 10000); + res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize, 0, 10000); if (res < 0) { @@ -166,7 +166,7 @@ void BladeRF2OutputThread::callbackMO(qint16* buf, qint32 samplesPerChannel) for (unsigned int channel = 0; channel < m_nbChannels; channel++) { if (m_channels[channel].m_sampleFifo) { - callbackSO(&buf[2*samplesPerChannel*channel], 2*samplesPerChannel, channel); + callbackSO(&buf[2*samplesPerChannel*channel], samplesPerChannel, channel); } else { std::fill(&buf[2*samplesPerChannel*channel], &buf[2*samplesPerChannel*channel]+2*samplesPerChannel, 0); // fill with zero samples } @@ -182,42 +182,57 @@ void BladeRF2OutputThread::callbackMO(qint16* buf, qint32 samplesPerChannel) } } -// Interpolate according to specified log2 (ex: log2=4 => decim=16) +// Interpolate according to specified log2 (ex: log2=4 => decim=16). len is a number of samples (not a number of I or Q) void BladeRF2OutputThread::callbackSO(qint16* buf, qint32 len, unsigned int channel) { - SampleVector::iterator beginRead; - m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<getRWBalance(); + + if (bal < -0.25) { + qDebug("BladeRF2OutputThread::callbackSO: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("BladeRF2OutputThread::callbackSO: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1< Date: Sun, 30 Sep 2018 00:21:24 +0200 Subject: [PATCH 817/956] BladeRF2 input: fixed read size in MI mode --- plugins/samplesource/bladerf2input/bladerf2inputthread.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp index d4d10e512..1b9ebc63f 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -92,7 +92,11 @@ void BladeRF2InputThread::run() qDebug("BladeRF2InputThread::run: start running loop"); while (m_running) { - res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize, NULL, 10000); + if (m_nbChannels > 1) { + res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize*m_nbChannels, NULL, 10000); + } else { + res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize, NULL, 10000); + } if (res < 0) { From 2df27958b37f34b74055ee38fce0ff3ff13742c0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 30 Sep 2018 07:25:13 +0200 Subject: [PATCH 818/956] BladeRF output fixes but MO still not working --- .../bladerf2output/bladerf2outputthread.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp index f02ab126b..106d1ef91 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp @@ -75,9 +75,9 @@ void BladeRF2OutputThread::run() int status; if (m_nbChannels > 1) { - status = bladerf_sync_config(m_dev, BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + status = bladerf_sync_config(m_dev, BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11, 64, 16384, 32, 1500); } else { - status = bladerf_sync_config(m_dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + status = bladerf_sync_config(m_dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 1500); } if (status < 0) @@ -87,15 +87,19 @@ void BladeRF2OutputThread::run() else { qDebug("BladeRF2OutputThread::run: start running loop"); + while (m_running) { - if (m_nbChannels > 1) { + if (m_nbChannels > 1) + { callbackMO(m_buf, DeviceBladeRF2::blockSize); - } else { - callbackSO(m_buf, DeviceBladeRF2::blockSize); + res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize*m_nbChannels, 0, 1500); + } + else + { + callbackSO(m_buf, DeviceBladeRF2::blockSize); + res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize, 0, 1500); } - - res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize, 0, 10000); if (res < 0) { @@ -103,6 +107,7 @@ void BladeRF2OutputThread::run() break; } } + qDebug("BladeRF2OutputThread::run: stop running loop"); } } From b0d17193d6a7e33c57ba22de7d7939c4ad8a5972 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 30 Sep 2018 07:34:47 +0200 Subject: [PATCH 819/956] BladeRF output: fix open channel indicators array initialization so that SO Tx starts correctly --- devices/bladerf2/devicebladerf2.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/devices/bladerf2/devicebladerf2.cpp b/devices/bladerf2/devicebladerf2.cpp index 92f88f313..fc93bd5b5 100644 --- a/devices/bladerf2/devicebladerf2.cpp +++ b/devices/bladerf2/devicebladerf2.cpp @@ -16,6 +16,7 @@ #include #include +#include #include @@ -75,6 +76,8 @@ bool DeviceBladeRF2::open(const char *serial) m_rxOpen = new bool[m_nbRxChannels]; m_txOpen = new bool[m_nbTxChannels]; + std::fill(m_rxOpen, m_rxOpen + m_nbRxChannels, false); + std::fill(m_txOpen, m_txOpen + m_nbTxChannels, false); return true; } From b5c4b532aedd081297ee50f72067e7eae1b88903 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 30 Sep 2018 10:26:53 +0200 Subject: [PATCH 820/956] BladeRF2 input: implemented LO soft correction --- devices/bladerf2/devicebladerf2shared.h | 6 +++ .../bladerf2output/bladerf2output.cpp | 2 + .../bladerf2output/bladerf2outputgui.cpp | 2 - .../bladerf2output/bladerf2outputgui.ui | 2 +- .../bladerf2input/bladerf2input.cpp | 36 +++++++++---- .../bladerf2input/bladerf2input.h | 2 + .../bladerf2input/bladerf2inputgui.cpp | 9 ++++ .../bladerf2input/bladerf2inputgui.h | 1 + .../bladerf2input/bladerf2inputgui.ui | 53 ++++++++++++++++--- .../bladerf2input/bladerf2inputsettings.cpp | 3 ++ .../bladerf2input/bladerf2inputsettings.h | 1 + 11 files changed, 97 insertions(+), 20 deletions(-) diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h index 43474d7fa..f4664e6fb 100644 --- a/devices/bladerf2/devicebladerf2shared.h +++ b/devices/bladerf2/devicebladerf2shared.h @@ -36,18 +36,21 @@ public: public: uint64_t getCenterFrequency() const { return m_centerFrequency; } + int getLOppmTenths() const { return m_LOppmTenths; } int getFcPos() const { return m_fcPos; } int getDevSampleRate() const { return m_devSampleRate; } bool getRxElseTx() const { return m_rxElseTx; } static MsgReportBuddyChange* create( uint64_t centerFrequency, + int LOppmTenths, int fcPos, int devSampleRate, bool rxElseTx) { return new MsgReportBuddyChange( centerFrequency, + LOppmTenths, fcPos, devSampleRate, rxElseTx); @@ -55,17 +58,20 @@ public: private: uint64_t m_centerFrequency; //!< Center frequency + int m_LOppmTenths; //!< LO soft correction in tenths of ppm int m_fcPos; //!< Center frequency position int m_devSampleRate; //!< device/host sample rate bool m_rxElseTx; //!< tells which side initiated the message MsgReportBuddyChange( uint64_t centerFrequency, + int LOppmTenths, int fcPos, int devSampleRate, bool rxElseTx) : Message(), m_centerFrequency(centerFrequency), + m_LOppmTenths(LOppmTenths), m_fcPos(fcPos), m_devSampleRate(devSampleRate), m_rxElseTx(rxElseTx) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 87a3f2331..a074f870a 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -803,6 +803,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( settings.m_centerFrequency, 0, + 0, settings.m_devSampleRate, false); (*itSource)->getSampleSourceInputMessageQueue()->push(report); @@ -820,6 +821,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( settings.m_centerFrequency, 0, + 0, settings.m_devSampleRate, false); (*itSink)->getSampleSinkInputMessageQueue()->push(report); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp index 446930cb5..82eea6160 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp @@ -67,8 +67,6 @@ BladeRF2OutputGui::BladeRF2OutputGui(DeviceUISet *deviceUISet, QWidget* parent) ui->gain->setPageStep(1); ui->gain->setSingleStep(1); - ui->label_decim->setText(QString::fromUtf8("I\u2191")); - connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(500); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui index 7facfdc9a..e091753f9 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui @@ -319,7 +319,7 @@ - I + Int diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 6060311b1..44287e514 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -538,6 +538,25 @@ void BladeRF2Input::setCenterFrequency(qint64 centerFrequency) } } +bool BladeRF2Input::setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz) +{ + qint64 df = ((qint64)freq_hz * m_settings.m_LOppmTenths) / 10000000LL; + freq_hz += df; + + int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(requestedChannel), freq_hz); + + if (status < 0) { + qWarning("BladeRF2Input::setDeviceCenterFrequency: bladerf_set_frequency(%lld) failed: %s", + freq_hz, bladerf_strerror(status)); + return false; + } + else + { + qDebug("BladeRF2Input::setDeviceCenterFrequency: bladerf_set_frequency(%lld)", freq_hz); + return true; + } +} + void BladeRF2Input::getFrequencyRange(uint64_t& min, uint64_t& max, int& step) { if (m_deviceShared.m_dev) { @@ -595,9 +614,10 @@ bool BladeRF2Input::handleMessage(const Message& message) { int requestedChannel = m_deviceAPI->getItemIndex(); - if (report.getRxElseTx()) // Rx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth + if (report.getRxElseTx()) // Rx buddy change: check for: frequency, LO correction, gain mode and value, bias tee, sample rate, bandwidth { settings.m_devSampleRate = report.getDevSampleRate(); + settings.m_LOppmTenths = report.getLOppmTenths(); settings.m_centerFrequency = report.getCenterFrequency(); settings.m_fcPos = (BladeRF2InputSettings::fcPos_t) report.getFcPos(); @@ -785,6 +805,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo } if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_LOppmTenths != settings.m_LOppmTenths) || (m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_log2Decim != settings.m_log2Decim) || force) @@ -801,16 +822,8 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if (dev != 0) { - int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(requestedChannel), deviceCenterFrequency); - - if (status < 0) { - qWarning("BladeRF2Input::applySettings: bladerf_set_frequency(%lld) failed: %s", - settings.m_centerFrequency, bladerf_strerror(status)); - } - else + if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency)) { - qDebug("BladeRF2Input::applySettings: bladerf_set_frequency(%lld)", settings.m_centerFrequency); - if (getMessageQueueToGUI()) { int min, max, step; @@ -882,6 +895,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo { DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( settings.m_centerFrequency, + settings.m_LOppmTenths, (int) settings.m_fcPos, settings.m_devSampleRate, true); @@ -899,6 +913,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo { DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( settings.m_centerFrequency, + settings.m_LOppmTenths, (int) settings.m_fcPos, settings.m_devSampleRate, true); @@ -910,6 +925,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo qDebug() << "BladeRF2Input::applySettings: " << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths << " m_bandwidth: " << m_settings.m_bandwidth << " m_log2Decim: " << m_settings.m_log2Decim << " m_fcPos: " << m_settings.m_fcPos diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h index af61d5776..9c5c0f81b 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.h +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -29,6 +29,7 @@ class DeviceSourceAPI; class BladeRF2InputThread; class FileRecord; struct bladerf_gain_modes; +struct bladerf; class BladeRF2Input : public DeviceSampleSource { @@ -191,6 +192,7 @@ private: BladeRF2InputThread *findThread(); void moveThreadToBuddy(); bool applySettings(const BladeRF2InputSettings& settings, bool force = false); + bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp index 34f44d6a7..3ee2280c7 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -229,6 +229,8 @@ void BladeRF2InputGui::displaySettings() blockApplySettings(true); ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); ui->sampleRate->setValue(m_settings.m_devSampleRate); ui->bandwidth->setValue(m_settings.m_bandwidth / 1000); @@ -262,6 +264,13 @@ void BladeRF2InputGui::on_centerFrequency_changed(quint64 value) sendSettings(); } +void BladeRF2InputGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + void BladeRF2InputGui::on_sampleRate_changed(quint64 value) { m_settings.m_devSampleRate = value; diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.h b/plugins/samplesource/bladerf2input/bladerf2inputgui.h index f52c82022..6ec59f776 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.h @@ -74,6 +74,7 @@ private: private slots: void handleInputMessages(); void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); void on_sampleRate_changed(quint64 value); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui index 68aad81a2..c3240635e 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui @@ -7,7 +7,7 @@ 0 0 350 - 200 + 220
    @@ -19,7 +19,7 @@ 350 - 200 + 220 @@ -174,11 +174,50 @@
    - - - Qt::Horizontal - - + + + + + LO ppm + + + + + + + Local Oscillator ppm correction + + + -20 + + + 20 + + + 1 + + + Qt::Horizontal + + + + + + + + 26 + 0 + + + + -0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + diff --git a/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp b/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp index d0bb3e807..07e604ddc 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp @@ -26,6 +26,7 @@ BladeRF2InputSettings::BladeRF2InputSettings() void BladeRF2InputSettings::resetToDefaults() { m_centerFrequency = 435000*1000; + m_LOppmTenths = 0; m_devSampleRate = 3072000; m_bandwidth = 1500000; m_gainMode = 0; @@ -51,6 +52,7 @@ QByteArray BladeRF2InputSettings::serialize() const s.writeS32(7, (int) m_fcPos); s.writeBool(8, m_dcBlock); s.writeBool(9, m_iqCorrection); + s.writeS32(10, m_LOppmTenths); return s.final(); } @@ -79,6 +81,7 @@ bool BladeRF2InputSettings::deserialize(const QByteArray& data) m_fcPos = (fcPos_t) intval; d.readBool(8, &m_dcBlock); d.readBool(9, &m_iqCorrection); + d.readS32(10, &m_LOppmTenths); return true; } diff --git a/plugins/samplesource/bladerf2input/bladerf2inputsettings.h b/plugins/samplesource/bladerf2input/bladerf2inputsettings.h index b44f63bd3..3cd44fb62 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputsettings.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputsettings.h @@ -29,6 +29,7 @@ struct BladeRF2InputSettings { } fcPos_t; quint64 m_centerFrequency; + qint32 m_LOppmTenths; qint32 m_devSampleRate; qint32 m_bandwidth; int m_gainMode; From e754dee62534f6497ca6c0076ef5f881ba0c1dfe Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 30 Sep 2018 10:59:52 +0200 Subject: [PATCH 821/956] BladeRF2 output: implemented LO soft correction --- .../bladerf2output/bladerf2output.cpp | 40 +++++++++----- .../bladerf2output/bladerf2output.h | 1 + .../bladerf2output/bladerf2outputgui.cpp | 9 ++++ .../bladerf2output/bladerf2outputgui.h | 1 + .../bladerf2output/bladerf2outputgui.ui | 53 ++++++++++++++++--- .../bladerf2output/bladerf2outputsettings.cpp | 3 ++ .../bladerf2output/bladerf2outputsettings.h | 1 + 7 files changed, 88 insertions(+), 20 deletions(-) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index a074f870a..6c6903185 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -502,6 +502,25 @@ void BladeRF2Output::setCenterFrequency(qint64 centerFrequency) } } +bool BladeRF2Output::setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz) +{ + qint64 df = ((qint64)freq_hz * m_settings.m_LOppmTenths) / 10000000LL; + freq_hz += df; + + int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(requestedChannel), freq_hz); + + if (status < 0) { + qWarning("BladeRF2Output::setDeviceCenterFrequency: bladerf_set_frequency(%lld) failed: %s", + freq_hz, bladerf_strerror(status)); + return false; + } + else + { + qDebug("BladeRF2Output::setDeviceCenterFrequency: bladerf_set_frequency(%lld)", freq_hz); + return true; + } +} + void BladeRF2Output::getFrequencyRange(uint64_t& min, uint64_t& max, int& step) { if (m_deviceShared.m_dev) { @@ -572,6 +591,7 @@ bool BladeRF2Output::handleMessage(const Message& message) else // Tx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth { settings.m_devSampleRate = report.getDevSampleRate(); + settings.m_LOppmTenths = report.getLOppmTenths(); settings.m_centerFrequency = report.getCenterFrequency(); status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_uint); @@ -733,6 +753,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool } if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_LOppmTenths != settings.m_LOppmTenths) || (m_settings.m_devSampleRate != settings.m_devSampleRate) || force) { forwardChangeOwnDSP = true; @@ -740,16 +761,8 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool if (dev != 0) { - int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(requestedChannel), settings.m_centerFrequency); - - if (status < 0) { - qWarning("BladeRF2Output::applySettings: bladerf_set_frequency(%lld) failed: %s", - settings.m_centerFrequency, bladerf_strerror(status)); - } - else + if (setDeviceCenterFrequency(dev, requestedChannel, settings.m_centerFrequency)) { - qDebug("BladeRF2Output::applySettings: bladerf_set_frequency(%lld)", settings.m_centerFrequency); - if (getMessageQueueToGUI()) { int min, max, step; @@ -802,8 +815,8 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool { DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( settings.m_centerFrequency, - 0, - 0, + settings.m_LOppmTenths, + 2, settings.m_devSampleRate, false); (*itSource)->getSampleSourceInputMessageQueue()->push(report); @@ -820,8 +833,8 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool { DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( settings.m_centerFrequency, - 0, - 0, + settings.m_LOppmTenths, + 2, settings.m_devSampleRate, false); (*itSink)->getSampleSinkInputMessageQueue()->push(report); @@ -832,6 +845,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool qDebug() << "BladeRF2Output::applySettings: " << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths << " m_bandwidth: " << m_settings.m_bandwidth << " m_log2Interp: " << m_settings.m_log2Interp << " m_devSampleRate: " << m_settings.m_devSampleRate diff --git a/plugins/samplesink/bladerf2output/bladerf2output.h b/plugins/samplesink/bladerf2output/bladerf2output.h index 3aa11401b..5968e6733 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.h +++ b/plugins/samplesink/bladerf2output/bladerf2output.h @@ -152,6 +152,7 @@ private: BladeRF2OutputThread *findThread(); void moveThreadToBuddy(); bool applySettings(const BladeRF2OutputSettings& settings, bool force); + bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2OutputSettings& settings); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp index 82eea6160..a8e47c13f 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp @@ -217,6 +217,8 @@ void BladeRF2OutputGui::displaySettings() blockApplySettings(true); ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); ui->sampleRate->setValue(m_settings.m_devSampleRate); ui->bandwidth->setValue(m_settings.m_bandwidth / 1000); @@ -240,6 +242,13 @@ void BladeRF2OutputGui::on_centerFrequency_changed(quint64 value) sendSettings(); } +void BladeRF2OutputGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + void BladeRF2OutputGui::on_sampleRate_changed(quint64 value) { m_settings.m_devSampleRate = value; diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.h b/plugins/samplesink/bladerf2output/bladerf2outputgui.h index c93c2f9fe..7027c1bc3 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.h @@ -74,6 +74,7 @@ private: private slots: void handleInputMessages(); void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); void on_biasTee_toggled(bool checked); void on_sampleRate_changed(quint64 value); void on_bandwidth_changed(quint64 value); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui index e091753f9..2e754dead 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui @@ -158,6 +158,52 @@ + + + + + + LO ppm + + + + + + + Local Oscillator ppm correction + + + -20 + + + 20 + + + 1 + + + Qt::Horizontal + + + + + + + + 26 + 0 + + + + -0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + @@ -240,13 +286,6 @@ - - - - Qt::Horizontal - - - diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp index b7f808c01..f78fa0375 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp @@ -28,6 +28,7 @@ BladeRF2OutputSettings::BladeRF2OutputSettings() void BladeRF2OutputSettings::resetToDefaults() { m_centerFrequency = 435000*1000; + m_LOppmTenths = 0; m_devSampleRate = 3072000; m_bandwidth = 1500000; m_globalGain = -3; @@ -41,6 +42,7 @@ QByteArray BladeRF2OutputSettings::serialize() const s.writeS32(1, m_devSampleRate); s.writeS32(2, m_bandwidth); + s.writeS32(3, m_LOppmTenths); s.writeS32(4, m_globalGain); s.writeBool(5, m_biasTee); s.writeU32(6, m_log2Interp); @@ -62,6 +64,7 @@ bool BladeRF2OutputSettings::deserialize(const QByteArray& data) { d.readS32(1, &m_devSampleRate); d.readS32(2, &m_bandwidth); + d.readS32(3, &m_LOppmTenths); d.readS32(4, &m_globalGain); d.readBool(5, &m_biasTee); d.readU32(6, &m_log2Interp); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h index 333aa750d..92f7111ba 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h @@ -22,6 +22,7 @@ struct BladeRF2OutputSettings { quint64 m_centerFrequency; + int m_LOppmTenths; qint32 m_devSampleRate; qint32 m_bandwidth; int m_globalGain; From 98a87bb86079a3afc18154c04f4d63a7fd8b34cb Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 30 Sep 2018 11:10:07 +0200 Subject: [PATCH 822/956] BladeRF2: REST API: implemented LO soft correction setting --- .../bladerf2output/bladerf2output.cpp | 4 ++ .../bladerf2input/bladerf2input.cpp | 4 ++ sdrbase/resources/webapi/doc/html2/index.html | 11 +++-- .../webapi/doc/swagger/include/BladeRF2.yaml | 6 ++- .../api/swagger/include/BladeRF2.yaml | 4 ++ swagger/sdrangel/code/html2/index.html | 11 +++-- .../qt5/client/SWGBladeRF2InputSettings.cpp | 21 ++++++++++ .../qt5/client/SWGBladeRF2InputSettings.h | 6 +++ .../qt5/client/SWGBladeRF2OutputSettings.cpp | 40 +++++++++---------- .../qt5/client/SWGBladeRF2OutputSettings.h | 12 +++--- 10 files changed, 83 insertions(+), 36 deletions(-) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 6c6903185..38cbe76cb 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -876,6 +876,9 @@ int BladeRF2Output::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("centerFrequency")) { settings.m_centerFrequency = response.getBladeRf2OutputSettings()->getCenterFrequency(); } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getBladeRf2OutputSettings()->getLOppmTenths(); + } if (deviceSettingsKeys.contains("devSampleRate")) { settings.m_devSampleRate = response.getBladeRf2OutputSettings()->getDevSampleRate(); } @@ -916,6 +919,7 @@ int BladeRF2Output::webapiReportGet(SWGSDRangel::SWGDeviceReport& response, QStr void BladeRF2Output::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2OutputSettings& settings) { response.getBladeRf2OutputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf2OutputSettings()->setLOppmTenths(settings.m_LOppmTenths); response.getBladeRf2OutputSettings()->setDevSampleRate(settings.m_devSampleRate); response.getBladeRf2OutputSettings()->setBandwidth(settings.m_bandwidth); response.getBladeRf2OutputSettings()->setLog2Interp(settings.m_log2Interp); diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 44287e514..2ce1fbf41 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -960,6 +960,9 @@ int BladeRF2Input::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("centerFrequency")) { settings.m_centerFrequency = response.getBladeRf2InputSettings()->getCenterFrequency(); } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getBladeRf2InputSettings()->getLOppmTenths(); + } if (deviceSettingsKeys.contains("devSampleRate")) { settings.m_devSampleRate = response.getBladeRf2InputSettings()->getDevSampleRate(); } @@ -1015,6 +1018,7 @@ int BladeRF2Input::webapiReportGet(SWGSDRangel::SWGDeviceReport& response, QStri void BladeRF2Input::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings) { response.getBladeRf2InputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf2InputSettings()->setLOppmTenths(settings.m_LOppmTenths); response.getBladeRf2InputSettings()->setDevSampleRate(settings.m_devSampleRate); response.getBladeRf2InputSettings()->setBandwidth(settings.m_bandwidth); response.getBladeRf2InputSettings()->setLog2Decim(settings.m_log2Decim); diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 162f29506..f355d8eab 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1331,6 +1331,9 @@ margin-bottom: 20px; "type" : "integer", "format" : "int64" }, + "LOppmTenths" : { + "type" : "integer" + }, "devSampleRate" : { "type" : "integer" }, @@ -1387,15 +1390,15 @@ margin-bottom: 20px; "type" : "integer", "format" : "int64" }, + "LOppmTenths" : { + "type" : "integer" + }, "devSampleRate" : { "type" : "integer" }, "bandwidth" : { "type" : "integer" }, - "gainMode" : { - "type" : "integer" - }, "globalGain" : { "type" : "integer" }, @@ -23225,7 +23228,7 @@ except ApiException as e:
    - Generated 2018-09-29T10:10:59.386+02:00 + Generated 2018-09-30T11:01:56.666+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml index 5a6e73ed3..dd873fc17 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml @@ -4,6 +4,8 @@ BladeRF2InputSettings: centerFrequency: type: integer format: int64 + LOppmTenths: + type: integer devSampleRate: type: integer bandwidth: @@ -47,12 +49,12 @@ BladeRF2OutputSettings: centerFrequency: type: integer format: int64 + LOppmTenths: + type: integer devSampleRate: type: integer bandwidth: type: integer - gainMode: - type: integer globalGain: type: integer biasTee: diff --git a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml index d8816fa23..9fb90fded 100644 --- a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml +++ b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml @@ -4,6 +4,8 @@ BladeRF2InputSettings: centerFrequency: type: integer format: int64 + LOppmTenths: + type: integer devSampleRate: type: integer bandwidth: @@ -47,6 +49,8 @@ BladeRF2OutputSettings: centerFrequency: type: integer format: int64 + LOppmTenths: + type: integer devSampleRate: type: integer bandwidth: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 162f29506..f355d8eab 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1331,6 +1331,9 @@ margin-bottom: 20px; "type" : "integer", "format" : "int64" }, + "LOppmTenths" : { + "type" : "integer" + }, "devSampleRate" : { "type" : "integer" }, @@ -1387,15 +1390,15 @@ margin-bottom: 20px; "type" : "integer", "format" : "int64" }, + "LOppmTenths" : { + "type" : "integer" + }, "devSampleRate" : { "type" : "integer" }, "bandwidth" : { "type" : "integer" }, - "gainMode" : { - "type" : "integer" - }, "globalGain" : { "type" : "integer" }, @@ -23225,7 +23228,7 @@ except ApiException as e:
    - Generated 2018-09-29T10:10:59.386+02:00 + Generated 2018-09-30T11:01:56.666+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp index 60e5492a4..a8c80301a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp @@ -30,6 +30,8 @@ SWGBladeRF2InputSettings::SWGBladeRF2InputSettings(QString* json) { SWGBladeRF2InputSettings::SWGBladeRF2InputSettings() { center_frequency = 0L; m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; dev_sample_rate = 0; m_dev_sample_rate_isSet = false; bandwidth = 0; @@ -60,6 +62,8 @@ void SWGBladeRF2InputSettings::init() { center_frequency = 0L; m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; dev_sample_rate = 0; m_dev_sample_rate_isSet = false; bandwidth = 0; @@ -94,6 +98,7 @@ SWGBladeRF2InputSettings::cleanup() { + if(file_record_name != nullptr) { delete file_record_name; } @@ -112,6 +117,8 @@ void SWGBladeRF2InputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); + ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); @@ -151,6 +158,9 @@ SWGBladeRF2InputSettings::asJsonObject() { if(m_center_frequency_isSet){ obj->insert("centerFrequency", QJsonValue(center_frequency)); } + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); + } if(m_dev_sample_rate_isSet){ obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); } @@ -195,6 +205,16 @@ SWGBladeRF2InputSettings::setCenterFrequency(qint64 center_frequency) { this->m_center_frequency_isSet = true; } +qint32 +SWGBladeRF2InputSettings::getLOppmTenths() { + return l_oppm_tenths; +} +void +SWGBladeRF2InputSettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; +} + qint32 SWGBladeRF2InputSettings::getDevSampleRate() { return dev_sample_rate; @@ -301,6 +321,7 @@ SWGBladeRF2InputSettings::isSet(){ bool isObjectUpdated = false; do{ if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} if(m_bandwidth_isSet){ isObjectUpdated = true; break;} if(m_gain_mode_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h index 1a24a82f8..fdc92abe2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h @@ -45,6 +45,9 @@ public: qint64 getCenterFrequency(); void setCenterFrequency(qint64 center_frequency); + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); + qint32 getDevSampleRate(); void setDevSampleRate(qint32 dev_sample_rate); @@ -82,6 +85,9 @@ private: qint64 center_frequency; bool m_center_frequency_isSet; + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; + qint32 dev_sample_rate; bool m_dev_sample_rate_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp index f28ca0d5c..df7a85fd4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp @@ -30,12 +30,12 @@ SWGBladeRF2OutputSettings::SWGBladeRF2OutputSettings(QString* json) { SWGBladeRF2OutputSettings::SWGBladeRF2OutputSettings() { center_frequency = 0L; m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; dev_sample_rate = 0; m_dev_sample_rate_isSet = false; bandwidth = 0; m_bandwidth_isSet = false; - gain_mode = 0; - m_gain_mode_isSet = false; global_gain = 0; m_global_gain_isSet = false; bias_tee = 0; @@ -52,12 +52,12 @@ void SWGBladeRF2OutputSettings::init() { center_frequency = 0L; m_center_frequency_isSet = false; + l_oppm_tenths = 0; + m_l_oppm_tenths_isSet = false; dev_sample_rate = 0; m_dev_sample_rate_isSet = false; bandwidth = 0; m_bandwidth_isSet = false; - gain_mode = 0; - m_gain_mode_isSet = false; global_gain = 0; m_global_gain_isSet = false; bias_tee = 0; @@ -90,12 +90,12 @@ void SWGBladeRF2OutputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + ::SWGSDRangel::setValue(&l_oppm_tenths, pJson["LOppmTenths"], "qint32", ""); + ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); - ::SWGSDRangel::setValue(&gain_mode, pJson["gainMode"], "qint32", ""); - ::SWGSDRangel::setValue(&global_gain, pJson["globalGain"], "qint32", ""); ::SWGSDRangel::setValue(&bias_tee, pJson["biasTee"], "qint32", ""); @@ -121,15 +121,15 @@ SWGBladeRF2OutputSettings::asJsonObject() { if(m_center_frequency_isSet){ obj->insert("centerFrequency", QJsonValue(center_frequency)); } + if(m_l_oppm_tenths_isSet){ + obj->insert("LOppmTenths", QJsonValue(l_oppm_tenths)); + } if(m_dev_sample_rate_isSet){ obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); } if(m_bandwidth_isSet){ obj->insert("bandwidth", QJsonValue(bandwidth)); } - if(m_gain_mode_isSet){ - obj->insert("gainMode", QJsonValue(gain_mode)); - } if(m_global_gain_isSet){ obj->insert("globalGain", QJsonValue(global_gain)); } @@ -153,6 +153,16 @@ SWGBladeRF2OutputSettings::setCenterFrequency(qint64 center_frequency) { this->m_center_frequency_isSet = true; } +qint32 +SWGBladeRF2OutputSettings::getLOppmTenths() { + return l_oppm_tenths; +} +void +SWGBladeRF2OutputSettings::setLOppmTenths(qint32 l_oppm_tenths) { + this->l_oppm_tenths = l_oppm_tenths; + this->m_l_oppm_tenths_isSet = true; +} + qint32 SWGBladeRF2OutputSettings::getDevSampleRate() { return dev_sample_rate; @@ -173,16 +183,6 @@ SWGBladeRF2OutputSettings::setBandwidth(qint32 bandwidth) { this->m_bandwidth_isSet = true; } -qint32 -SWGBladeRF2OutputSettings::getGainMode() { - return gain_mode; -} -void -SWGBladeRF2OutputSettings::setGainMode(qint32 gain_mode) { - this->gain_mode = gain_mode; - this->m_gain_mode_isSet = true; -} - qint32 SWGBladeRF2OutputSettings::getGlobalGain() { return global_gain; @@ -219,9 +219,9 @@ SWGBladeRF2OutputSettings::isSet(){ bool isObjectUpdated = false; do{ if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_l_oppm_tenths_isSet){ isObjectUpdated = true; break;} if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} if(m_bandwidth_isSet){ isObjectUpdated = true; break;} - if(m_gain_mode_isSet){ isObjectUpdated = true; break;} if(m_global_gain_isSet){ isObjectUpdated = true; break;} if(m_bias_tee_isSet){ isObjectUpdated = true; break;} if(m_log2_interp_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h index 89daff30f..af1a497c3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h @@ -44,15 +44,15 @@ public: qint64 getCenterFrequency(); void setCenterFrequency(qint64 center_frequency); + qint32 getLOppmTenths(); + void setLOppmTenths(qint32 l_oppm_tenths); + qint32 getDevSampleRate(); void setDevSampleRate(qint32 dev_sample_rate); qint32 getBandwidth(); void setBandwidth(qint32 bandwidth); - qint32 getGainMode(); - void setGainMode(qint32 gain_mode); - qint32 getGlobalGain(); void setGlobalGain(qint32 global_gain); @@ -69,15 +69,15 @@ private: qint64 center_frequency; bool m_center_frequency_isSet; + qint32 l_oppm_tenths; + bool m_l_oppm_tenths_isSet; + qint32 dev_sample_rate; bool m_dev_sample_rate_isSet; qint32 bandwidth; bool m_bandwidth_isSet; - qint32 gain_mode; - bool m_gain_mode_isSet; - qint32 global_gain; bool m_global_gain_isSet; From d770da9590d1743448edc3660ef2ed999392d879 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 30 Sep 2018 23:42:06 +0200 Subject: [PATCH 823/956] Updated general hardware requirements --- Readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 4a08d8ce5..9f571ac15 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,9 @@

    Hardware requirements

    -SDRangel is a near real time application that is demanding on CPU power and clock speeds for low latency. Recent (2015 or later) Core i7 class CPU is recommended preferably with 4 HT CPU cores (8 logical CPUs or more) with nominal clock over 2 GHz and at least 8 GB RAM. Modern Intel processors will include a GPU suitable for proper OpenGL support. On the other hand SDRangel is not as demanding as recent computer games for graphics and CPU integrated graphics are perfectly fine. USB-3 ports are also preferable for high speed, low latency USB communication. +SDRangel is a near real time application that is demanding on CPU power and clock speeds for low latency. Recent (2015 or later) Core i7 class CPU is recommended preferably with 4 HT CPU cores (8 logical CPUs or more) with nominal clock over 2 GHz and at least 8 GB RAM. Modern Intel processors will include a GPU suitable for proper OpenGL support. On the other hand SDRangel is not as demanding as recent computer games for graphics and CPU integrated graphics are perfectly fine. USB-3 ports are also preferable for high speed, low latency USB communication. It may run on beefy SBCs and was tested successfully on a Udoo Ultra. + +The server variant does not need a graphical display and therefore OpenGL support. It can run on smaller devices check the Wiki for hardware requirements and a list of successful cases.

    Source code

    From 2323d21a430508f45edaac1bca800d6cd8fda14f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 30 Sep 2018 23:42:52 +0200 Subject: [PATCH 824/956] BladeRF2: fixed MO mode by multiplying sample rate by the number of Tx channels --- .../bladerf2output/bladerf2output.cpp | 50 +++++++++++++++---- .../bladerf2output/bladerf2output.h | 1 + .../bladerf2output/bladerf2outputthread.cpp | 2 +- .../bladerf2input/bladerf2input.cpp | 29 +++++++++-- 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 38cbe76cb..9e6926bfc 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -316,6 +316,8 @@ bool BladeRF2Output::start() bladeRF2OutputThread->setFifo(requestedChannel, &m_sampleSourceFifo); bladeRF2OutputThread->setLog2Interpolation(requestedChannel, m_settings.m_log2Interp); + applySettings(m_settings, true); // re-apply forcibly to set sample rate with the new number of channels + if (needsStart) { qDebug("BladeRF2Output::start: enabling channel(s) and (re)sart buddy thread"); @@ -332,8 +334,6 @@ bool BladeRF2Output::start() bladeRF2OutputThread->startWork(); } - applySettings(m_settings, true); - qDebug("BladeRF2Output::start: started"); m_running = true; @@ -429,6 +429,13 @@ void BladeRF2Output::stop() ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); } + for (int i = 0; i < nbOriginalChannels-1; i++) // close all inactive channels + { + if (fifos[i] == 0) { + m_deviceShared.m_dev->closeTx(i); + } + } + m_deviceShared.m_dev->closeTx(requestedChannel); // close the last channel if (stillActiveFIFO) { @@ -441,6 +448,8 @@ void BladeRF2Output::stop() bladeRF2OutputThread->setFifo(requestedChannel, 0); // remove FIFO } + applySettings(m_settings, true); // re-apply forcibly to set sample rate with the new number of channels + m_running = false; } @@ -577,16 +586,19 @@ bool BladeRF2Output::handleMessage(const Message& message) if (dev) // The BladeRF device must have been open to do so { int requestedChannel = m_deviceAPI->getItemIndex(); + int nbChannels = getNbChannels(); if (report.getRxElseTx()) // Rx buddy change: check for sample rate change only { - status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_uint); - - if (status < 0) { - qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); - } else { - settings.m_devSampleRate = tmp_uint+1; - } + tmp_uint = report.getDevSampleRate(); + settings.m_devSampleRate = tmp_uint / (nbChannels == 0 ? 1 : nbChannels); +// status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_uint); +// +// if (status < 0) { +// qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); +// } else { +// settings.m_devSampleRate = tmp_uint / (nbChannels == 0 ? 1 : nbChannels); +// } } else // Tx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth { @@ -664,6 +676,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool struct bladerf *dev = m_deviceShared.m_dev->getDev(); int requestedChannel = m_deviceAPI->getItemIndex(); + int nbChannels = getNbChannels(); if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_log2Interp != settings.m_log2Interp) || force) { @@ -705,7 +718,10 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool if (dev != 0) { unsigned int actualSamplerate; - int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), settings.m_devSampleRate, &actualSamplerate); + + int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), + settings.m_devSampleRate * (nbChannels == 0 ? 1 : nbChannels), + &actualSamplerate); if (status < 0) { @@ -817,7 +833,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool settings.m_centerFrequency, settings.m_LOppmTenths, 2, - settings.m_devSampleRate, + settings.m_devSampleRate * (nbChannels == 0 ? 1 : nbChannels), // need to forward actual rate to the Rx side false); (*itSource)->getSampleSourceInputMessageQueue()->push(report); } @@ -849,12 +865,24 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool << " m_bandwidth: " << m_settings.m_bandwidth << " m_log2Interp: " << m_settings.m_log2Interp << " m_devSampleRate: " << m_settings.m_devSampleRate + << " nbChannels: " << nbChannels << " m_globalGain: " << m_settings.m_globalGain << " m_biasTee: " << m_settings.m_biasTee; return true; } +int BladeRF2Output::getNbChannels() +{ + BladeRF2OutputThread *bladeRF2OutputThread = findThread(); + + if (bladeRF2OutputThread) { + return bladeRF2OutputThread->getNbChannels(); + } else { + return 0; + } +} + int BladeRF2Output::webapiSettingsGet( SWGSDRangel::SWGDeviceSettings& response, QString& errorMessage __attribute__((unused))) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.h b/plugins/samplesink/bladerf2output/bladerf2output.h index 5968e6733..9bc83a61a 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.h +++ b/plugins/samplesink/bladerf2output/bladerf2output.h @@ -152,6 +152,7 @@ private: BladeRF2OutputThread *findThread(); void moveThreadToBuddy(); bool applySettings(const BladeRF2OutputSettings& settings, bool force); + int getNbChannels(); bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2OutputSettings& settings); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp index 106d1ef91..39df9332e 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp @@ -75,7 +75,7 @@ void BladeRF2OutputThread::run() int status; if (m_nbChannels > 1) { - status = bladerf_sync_config(m_dev, BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11, 64, 16384, 32, 1500); + status = bladerf_sync_config(m_dev, BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11, 128, 16384, 32, 1500); } else { status = bladerf_sync_config(m_dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 1500); } diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 2ce1fbf41..53a25f04c 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -645,12 +645,31 @@ bool BladeRF2Input::handleMessage(const Message& message) } else // Tx buddy change: check for sample rate change only { - status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); + settings.m_devSampleRate = report.getDevSampleRate(); +// status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); +// +// if (status < 0) { +// qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); +// } else { +// settings.m_devSampleRate = tmp_uint; +// } - if (status < 0) { - qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); - } else { - settings.m_devSampleRate = tmp_uint+1; + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_devSampleRate); + + if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency)) + { + if (getMessageQueueToGUI()) + { + int min, max, step; + getGlobalGainRange(min, max, step); + MsgReportGainRange *msg = MsgReportGainRange::create(min, max, step); + getMessageQueueToGUI()->push(msg); + } } } From 593209185e299e3a9551a55d8024b2220e3ea9f3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 1 Oct 2018 10:30:34 +0200 Subject: [PATCH 825/956] BladeRF2 output: Windows build --- .../bladerf2output/bladerf2output.pro | 53 +++++++++++++++++++ sdrangel.windows.pro | 1 + 2 files changed, 54 insertions(+) create mode 100644 plugins/samplesink/bladerf2output/bladerf2output.pro diff --git a/plugins/samplesink/bladerf2output/bladerf2output.pro b/plugins/samplesink/bladerf2output/bladerf2output.pro new file mode 100644 index 000000000..8844d2926 --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2output.pro @@ -0,0 +1,53 @@ +#-------------------------------------------- +# +# Pro file for Windows builds with Qt Creator +# +#-------------------------------------------- + +TEMPLATE = lib +CONFIG += plugin + +QT += core gui widgets multimedia opengl + +TARGET = outputbladerf2 + +DEFINES += USE_SSE2=1 +QMAKE_CXXFLAGS += -msse2 +DEFINES += USE_SSE4_1=1 +QMAKE_CXXFLAGS += -msse4.1 +QMAKE_CXXFLAGS += -std=c++11 + +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" +INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports +INCLUDEPATH += ../../../sdrbase +INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client +INCLUDEPATH += ../../../devices +INCLUDEPATH += $$LIBBLADERF/include + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +SOURCES += bladerf2outputgui.cpp\ + bladerf2output.cpp\ + bladerf2outputplugin.cpp\ + bladerf2outputsettings.cpp\ + bladerf2outputthread.cpp + +HEADERS += bladerf2outputgui.h\ + bladerf2output.h\ + bladerf2outputplugin.h\ + bladerf2outputsettings.h\ + bladerf2outputthread.h + +FORMS += bladerf2outputgui.ui + +LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase +LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger +LIBS += -L$$LIBBLADERF/lib -lbladeRF +LIBS += -L../../../devices/$${build_subdir} -ldevices + +RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index a322cbc3c..ff631ac39 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -42,6 +42,7 @@ SUBDIRS += plugins/samplesource/rtlsdr SUBDIRS += plugins/samplesource/testsource SUBDIRS += plugins/samplesink/filesink SUBDIRS += plugins/samplesink/bladerf1output +SUBDIRS += plugins/samplesink/bladerf2output SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput From 49a0dbac404dd0a0d37b29ee2a596caeaec75696 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 2 Oct 2018 05:18:34 +0200 Subject: [PATCH 826/956] ScopeVis: corrected memory index when in trace memory mode --- sdrgui/dsp/scopevis.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 3e6883bdf..2c2967281 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -232,7 +232,13 @@ void ScopeVis::processMemoryTrace() { if ((m_currentTraceMemoryIndex > 0) && (m_currentTraceMemoryIndex < m_nbTraceMemories)) { - SampleVector::const_iterator mend = m_traceDiscreteMemory.at(m_currentTraceMemoryIndex).m_endPoint; + int traceMemoryIndex = m_traceDiscreteMemory.currentIndex() - m_currentTraceMemoryIndex; // actual index in memory bank + + if (traceMemoryIndex < 0) { + traceMemoryIndex += m_nbTraceMemories; + } + + SampleVector::const_iterator mend = m_traceDiscreteMemory.at(traceMemoryIndex).m_endPoint; SampleVector::const_iterator mbegin = mend - m_traceSize; SampleVector::const_iterator mbegin_tb = mbegin - m_maxTraceDelay; m_nbSamples = m_traceSize + m_maxTraceDelay; From f4976485fc7ae3ed9be699cece653998ea8c9599 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 2 Oct 2018 06:11:34 +0200 Subject: [PATCH 827/956] BladeRF2 output: tried to fix SO->MO and MO->SO cycle --- debian/changelog | 3 +- devices/bladerf2/devicebladerf2.cpp | 38 ++++++------------- .../bladerf2output/bladerf2output.cpp | 31 +++++++++------ 3 files changed, 33 insertions(+), 39 deletions(-) diff --git a/debian/changelog b/debian/changelog index 66bc4c1fa..c792b80cf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ sdrangel (4.2.0-1) unstable; urgency=medium * LibbladeRF 2.0 support with BladeRF Micro + * Scope: corrected trace memory index position - -- Edouard Griffiths, F4EXB Sun, 14 Oct 2018 21:14:18 +0200 + -- Edouard Griffiths, F4EXB Sun, 7 Oct 2018 21:14:18 +0200 sdrangel (4.1.0-1) unstable; urgency=medium diff --git a/devices/bladerf2/devicebladerf2.cpp b/devices/bladerf2/devicebladerf2.cpp index fc93bd5b5..187bcb2d5 100644 --- a/devices/bladerf2/devicebladerf2.cpp +++ b/devices/bladerf2/devicebladerf2.cpp @@ -150,7 +150,7 @@ bool DeviceBladeRF2::openRx(int channel) if (status < 0) { - qCritical("DeviceBladeRF2::openRx: Failed to enable Rx channel %d: %s", + qCritical("DeviceBladeRF2::openRx: failed to enable Rx channel %d: %s", channel, bladerf_strerror(status)); return false; } @@ -163,8 +163,8 @@ bool DeviceBladeRF2::openRx(int channel) } else { - qCritical("DeviceBladeRF2::openRx: Rx channel %d already opened", channel); - return false; + qDebug("DeviceBladeRF2::openRx: Rx channel %d already opened", channel); + return true; } } @@ -201,8 +201,8 @@ bool DeviceBladeRF2::openTx(int channel) } else { - qCritical("DeviceBladeRF2::openTx: Tx channel %d already opened", channel); - return false; + qDebug("DeviceBladeRF2::openTx: Tx channel %d already opened", channel); + return true; } } @@ -220,26 +220,18 @@ void DeviceBladeRF2::closeRx(int channel) if (m_rxOpen[channel]) { - for (int i = 0; i < m_nbRxChannels; i++) - { - if ((i != channel) && (m_rxOpen[i])) - { - qDebug("DeviceBladeRF2::closeRx: not closing channel %d as %d is still open", channel, i); - } - } - int status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_RX(channel), false); m_rxOpen[channel] = false; if (status < 0) { - qCritical("DeviceBladeRF2::closeRx: cannot close channel %d: %s", channel, bladerf_strerror(status)); + qCritical("DeviceBladeRF2::closeRx: failed to disable Rx channel %d: %s", channel, bladerf_strerror(status)); } else { - qDebug("DeviceBladeRF2::closeRx: channel %d closed", channel); + qDebug("DeviceBladeRF2::closeRx: Rx channel %d disabled", channel); } } else { - qCritical("DeviceBladeRF2::closeRx: Rx channel %d already closed", channel); + qDebug("DeviceBladeRF2::closeRx: Rx channel %d already closed", channel); } } @@ -257,26 +249,18 @@ void DeviceBladeRF2::closeTx(int channel) if (m_txOpen[channel]) { - for (int i = 0; i < m_nbTxChannels; i++) - { - if ((i != channel) && (m_txOpen[i])) - { - qDebug("DeviceBladeRF2::closeTx: not closing channel %d as %d is still open", channel, i); - } - } - int status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(channel), false); m_txOpen[channel] = false; if (status < 0) { - qCritical("DeviceBladeRF2::closeTx: cannot close channel %d: %s", channel, bladerf_strerror(status)); + qCritical("DeviceBladeRF2::closeTx: failed to disable Tx channel %d: %s", channel, bladerf_strerror(status)); } else { - qDebug("DeviceBladeRF2::closeTx: channel %d closed", channel); + qDebug("DeviceBladeRF2::closeTx: Tx channel %d disabled", channel); } } else { - qCritical("DeviceBladeRF2::closeTx: Rx channel %d already closed", channel); + qDebug("DeviceBladeRF2::closeTx: Rx channel %d already closed", channel); } } diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 9e6926bfc..39aba7956 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -298,6 +298,11 @@ bool BladeRF2Output::start() ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); } + // close all channels + for (int i = bladeRF2OutputThread->getNbChannels()-1; i >= 0; i--) { + m_deviceShared.m_dev->closeTx(i); + } + needsStart = true; } else @@ -320,11 +325,9 @@ bool BladeRF2Output::start() if (needsStart) { - qDebug("BladeRF2Output::start: enabling channel(s) and (re)sart buddy thread"); + qDebug("BladeRF2Output::start: enabling channel(s) and (re)starting the thread"); - int nbChannels = bladeRF2OutputThread->getNbChannels(); - - for (int i = 0; i < nbChannels; i++) + for (unsigned int i = 0; i < bladeRF2OutputThread->getNbChannels(); i++) // open all channels { if (!m_deviceShared.m_dev->openTx(i)) { qCritical("BladeRF2Output::start: channel %u cannot be enabled", i); @@ -429,16 +432,22 @@ void BladeRF2Output::stop() ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); } - for (int i = 0; i < nbOriginalChannels-1; i++) // close all inactive channels - { - if (fifos[i] == 0) { - m_deviceShared.m_dev->closeTx(i); - } + // close all channels + for (int i = nbOriginalChannels-1; i >= 0; i--) { + m_deviceShared.m_dev->closeTx(i); } - m_deviceShared.m_dev->closeTx(requestedChannel); // close the last channel + if (stillActiveFIFO) + { + qDebug("BladeRF2Output::stop: enabling channel(s) and restarting the thread"); + + for (unsigned int i = 0; i < bladeRF2OutputThread->getNbChannels(); i++) // open all channels + { + if (!m_deviceShared.m_dev->openTx(i)) { + qCritical("BladeRF2Output::start: channel %u cannot be enabled", i); + } + } - if (stillActiveFIFO) { bladeRF2OutputThread->startWork(); } } From 4a1376e47476f9468ec9f5b7b1e1d26ad724f566 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 2 Oct 2018 08:45:14 +0200 Subject: [PATCH 828/956] FileSource: process record header if file is long enough. Else close file so that start is aborted with error --- .../filesource/filesourceinput.cpp | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 3736dcc0a..032d39f85 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -81,18 +81,23 @@ void FileSourceInput::openFileStream() m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); quint64 fileSize = m_ifstream.tellg(); - m_ifstream.seekg(0,std::ios_base::beg); - FileRecord::Header header; - FileRecord::readHeader(m_ifstream, header); - m_sampleRate = header.sampleRate; - m_centerFrequency = header.centerFrequency; - m_startingTimeStamp = header.startTimeStamp; - m_sampleSize = header.sampleSize; + if (fileSize > sizeof(FileRecord::Header)) + { + // TODO: add CRC + m_ifstream.seekg(0,std::ios_base::beg); + FileRecord::Header header; + FileRecord::readHeader(m_ifstream, header); - if (fileSize > sizeof(FileRecord::Header)) { - m_recordLength = (fileSize - sizeof(FileRecord::Header)) / (4 * m_sampleRate); - } else { + m_sampleRate = header.sampleRate; + m_centerFrequency = header.centerFrequency; + m_startingTimeStamp = header.startTimeStamp; + m_sampleSize = header.sampleSize; + + m_recordLength = (fileSize - sizeof(FileRecord::Header)) / (4 * m_sampleRate); + } + else + { m_recordLength = 0; } @@ -108,6 +113,10 @@ void FileSourceInput::openFileStream() m_recordLength); // file stream data getMessageQueueToGUI()->push(report); } + + if (m_recordLength == 0) { + m_ifstream.close(); + } } void FileSourceInput::seekFileStream(int seekPercentage) @@ -132,6 +141,12 @@ void FileSourceInput::init() bool FileSourceInput::start() { + if (!m_ifstream.is_open()) + { + qWarning("FileSourceInput::start: file not open. not starting"); + return false; + } + QMutexLocker mutexLocker(&m_mutex); qDebug() << "FileSourceInput::start"; From 3f7cfb4dd81b52c06f0c0f2773d66243fd6da5c5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 3 Oct 2018 04:19:05 +0200 Subject: [PATCH 829/956] WFM demod: fixed a missing mutex when re-configuring interpolator --- plugins/channelrx/demodwfm/wfmdemod.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index 741a16706..3b1d49619 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -289,9 +289,11 @@ void WFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse if ((inputSampleRate != m_inputSampleRate) || force) { qDebug() << "WFMDemod::applyChannelSettings: m_interpolator.create"; + m_settingsMutex.lock(); m_interpolator.create(16, inputSampleRate, m_settings.m_afBandwidth); m_interpolatorDistanceRemain = (Real) inputSampleRate / (Real) m_audioSampleRate; m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; + m_settingsMutex.unlock(); qDebug() << "WFMDemod::applySettings: m_rfFilter->create_filter"; Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / inputSampleRate; Real hiCut = (m_settings.m_rfBandwidth / 2.0) / inputSampleRate; From c579d66b5902e3000d30e253b9db4fb6f82ef1b1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 3 Oct 2018 06:14:26 +0200 Subject: [PATCH 830/956] BladeRF2: implemented transverter mode --- .../bladerf2output/bladerf2output.cpp | 18 +++++++- .../bladerf2output/bladerf2outputgui.cpp | 36 ++++++++++++++++ .../bladerf2output/bladerf2outputgui.h | 3 ++ .../bladerf2output/bladerf2outputgui.ui | 43 ++++++++++++++----- .../bladerf2output/bladerf2outputsettings.cpp | 6 +++ .../bladerf2output/bladerf2outputsettings.h | 2 + .../limesdroutput/limesdroutput.cpp | 9 ++-- .../bladerf2input/bladerf2input.cpp | 17 +++++++- .../bladerf2input/bladerf2inputgui.cpp | 36 ++++++++++++++++ .../bladerf2input/bladerf2inputgui.h | 3 ++ .../bladerf2input/bladerf2inputgui.ui | 29 +++++++++++-- .../bladerf2input/bladerf2inputsettings.cpp | 6 +++ .../bladerf2input/bladerf2inputsettings.h | 2 + sdrbase/resources/webapi/doc/html2/index.html | 16 ++++++- .../webapi/doc/swagger/include/BladeRF2.yaml | 10 +++++ .../api/swagger/include/BladeRF2.yaml | 10 +++++ swagger/sdrangel/code/html2/index.html | 16 ++++++- .../qt5/client/SWGBladeRF2InputSettings.cpp | 42 ++++++++++++++++++ .../qt5/client/SWGBladeRF2InputSettings.h | 12 ++++++ .../qt5/client/SWGBladeRF2OutputSettings.cpp | 42 ++++++++++++++++++ .../qt5/client/SWGBladeRF2OutputSettings.h | 12 ++++++ 21 files changed, 348 insertions(+), 22 deletions(-) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 39aba7956..6b00b07b3 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -686,6 +686,9 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool struct bladerf *dev = m_deviceShared.m_dev->getDev(); int requestedChannel = m_deviceAPI->getItemIndex(); int nbChannels = getNbChannels(); + qint64 deviceCenterFrequency = settings.m_centerFrequency; + deviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_log2Interp != settings.m_log2Interp) || force) { @@ -778,6 +781,8 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool } if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_transverterMode != settings.m_transverterMode) + || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) || (m_settings.m_LOppmTenths != settings.m_LOppmTenths) || (m_settings.m_devSampleRate != settings.m_devSampleRate) || force) { @@ -786,7 +791,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool if (dev != 0) { - if (setDeviceCenterFrequency(dev, requestedChannel, settings.m_centerFrequency)) + if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency)) { if (getMessageQueueToGUI()) { @@ -869,6 +874,9 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool m_settings = settings; qDebug() << "BladeRF2Output::applySettings: " + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " deviceCenterFrequency: " << deviceCenterFrequency << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" << " m_LOppmTenths: " << m_settings.m_LOppmTenths << " m_bandwidth: " << m_settings.m_bandwidth @@ -931,6 +939,12 @@ int BladeRF2Output::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("globalGain")) { settings.m_globalGain = response.getBladeRf2OutputSettings()->getGlobalGain(); } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getBladeRf2OutputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getBladeRf2OutputSettings()->getTransverterMode() != 0; + } MsgConfigureBladeRF2 *msg = MsgConfigureBladeRF2::create(settings, force); m_inputMessageQueue.push(msg); @@ -962,6 +976,8 @@ void BladeRF2Output::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response.getBladeRf2OutputSettings()->setLog2Interp(settings.m_log2Interp); response.getBladeRf2OutputSettings()->setBiasTee(settings.m_biasTee ? 1 : 0); response.getBladeRf2OutputSettings()->setGlobalGain(settings.m_globalGain); + response.getBladeRf2OutputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getBladeRf2OutputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); } void BladeRF2Output::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp index a8e47c13f..283a68cb0 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp @@ -134,6 +134,32 @@ bool BladeRF2OutputGui::deserialize(const QByteArray& data) } } +void BladeRF2OutputGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + int step; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_sampleSink->getFrequencyRange(f_min, f_max, step); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("BladeRF2OutputGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + +void BladeRF2OutputGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + bool BladeRF2OutputGui::handleMessage(const Message& message) { if (BladeRF2Output::MsgConfigureBladeRF2::match(message)) @@ -282,6 +308,16 @@ void BladeRF2OutputGui::on_gain_valueChanged(int value) sendSettings(); } +void BladeRF2OutputGui::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("LimeSDRInputGUI::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + void BladeRF2OutputGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.h b/plugins/samplesink/bladerf2output/bladerf2outputgui.h index 7027c1bc3..edbc35496 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.h @@ -70,6 +70,8 @@ private: void displaySettings(); void sendSettings(); void updateSampleRateAndFrequency(); + void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); private slots: void handleInputMessages(); @@ -81,6 +83,7 @@ private slots: void on_interp_currentIndexChanged(int index); void on_gain_valueChanged(int value); void on_startStop_toggled(bool checked); + void on_transverter_clicked(); void updateHardware(); void updateStatus(); }; diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui index 2e754dead..697d5e909 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui @@ -261,16 +261,6 @@
    - - - - Bias Tee - - - BT - - - @@ -284,6 +274,22 @@ + + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + @@ -411,7 +417,7 @@ - + 3 @@ -448,6 +454,16 @@
    + + + + Bias Tee + + + BT + + + @@ -488,6 +504,11 @@ QToolButton
    gui/buttonswitch.h
    + + TransverterButton + QPushButton +
    gui/transverterbutton.h
    +
    diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp index f78fa0375..2ea428ef5 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp @@ -34,6 +34,8 @@ void BladeRF2OutputSettings::resetToDefaults() m_globalGain = -3; m_biasTee = false; m_log2Interp = 0; + m_transverterMode = false; + m_transverterDeltaFrequency = 0; } QByteArray BladeRF2OutputSettings::serialize() const @@ -46,6 +48,8 @@ QByteArray BladeRF2OutputSettings::serialize() const s.writeS32(4, m_globalGain); s.writeBool(5, m_biasTee); s.writeU32(6, m_log2Interp); + s.writeBool(7, m_transverterMode); + s.writeS64(8, m_transverterDeltaFrequency); return s.final(); } @@ -68,6 +72,8 @@ bool BladeRF2OutputSettings::deserialize(const QByteArray& data) d.readS32(4, &m_globalGain); d.readBool(5, &m_biasTee); d.readU32(6, &m_log2Interp); + d.readBool(7, &m_transverterMode, false); + d.readS64(8, &m_transverterDeltaFrequency, 0); return true; } diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h index 92f7111ba..b1385be41 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h @@ -28,6 +28,8 @@ struct BladeRF2OutputSettings { int m_globalGain; bool m_biasTee; quint32 m_log2Interp; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; BladeRF2OutputSettings(); void resetToDefaults(); diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp index 657864a44..a166d488c 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp @@ -590,9 +590,12 @@ bool LimeSDROutput::handleMessage(const Message& message) m_settings.m_extClock = report.getExtClock(); m_settings.m_extClockFreq = report.getExtClockFeq(); - DeviceLimeSDRShared::MsgReportClockSourceChange *reportToGUI = DeviceLimeSDRShared::MsgReportClockSourceChange::create( - m_settings.m_extClock, m_settings.m_extClockFreq); - getMessageQueueToGUI()->push(reportToGUI); + if (getMessageQueueToGUI()) + { + DeviceLimeSDRShared::MsgReportClockSourceChange *reportToGUI = DeviceLimeSDRShared::MsgReportClockSourceChange::create( + m_settings.m_extClock, m_settings.m_extClockFreq); + getMessageQueueToGUI()->push(reportToGUI); + } return true; } diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 53a25f04c..60387f70d 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -749,6 +749,9 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo struct bladerf *dev = m_deviceShared.m_dev->getDev(); int requestedChannel = m_deviceAPI->getItemIndex(); + qint64 xlatedDeviceCenterFrequency = settings.m_centerFrequency; + xlatedDeviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + xlatedDeviceCenterFrequency = xlatedDeviceCenterFrequency < 0 ? 0 : xlatedDeviceCenterFrequency; if ((m_settings.m_dcBlock != settings.m_dcBlock) || (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) @@ -824,13 +827,15 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo } if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_transverterMode != settings.m_transverterMode) + || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) || (m_settings.m_LOppmTenths != settings.m_LOppmTenths) || (m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_log2Decim != settings.m_log2Decim) || force) { qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( - settings.m_centerFrequency, + xlatedDeviceCenterFrequency, 0, settings.m_log2Decim, (DeviceSampleSource::fcPos_t) settings.m_fcPos, @@ -943,6 +948,8 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo m_settings = settings; qDebug() << "BladeRF2Input::applySettings: " + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" << " m_LOppmTenths: " << m_settings.m_LOppmTenths << " m_bandwidth: " << m_settings.m_bandwidth @@ -1009,6 +1016,12 @@ int BladeRF2Input::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("globalGain")) { settings.m_globalGain = response.getBladeRf2InputSettings()->getGlobalGain(); } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getBladeRf2InputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getBladeRf2InputSettings()->getTransverterMode() != 0; + } if (deviceSettingsKeys.contains("fileRecordName")) { settings.m_fileRecordName = *response.getBladeRf1InputSettings()->getFileRecordName(); } @@ -1047,6 +1060,8 @@ void BladeRF2Input::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& r response.getBladeRf2InputSettings()->setBiasTee(settings.m_biasTee ? 1 : 0); response.getBladeRf2InputSettings()->setGainMode(settings.m_gainMode); response.getBladeRf2InputSettings()->setGlobalGain(settings.m_globalGain); + response.getBladeRf2InputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getBladeRf2InputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); if (response.getBladeRf2InputSettings()->getFileRecordName()) { *response.getBladeRf2InputSettings()->getFileRecordName() = settings.m_fileRecordName; diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp index 3ee2280c7..14c126aa7 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -146,6 +146,32 @@ bool BladeRF2InputGui::deserialize(const QByteArray& data) } } +void BladeRF2InputGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + int step; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_sampleSource->getFrequencyRange(f_min, f_max, step); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("BladeRF2OutputGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + +void BladeRF2InputGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + bool BladeRF2InputGui::handleMessage(const Message& message) { if (BladeRF2Input::MsgConfigureBladeRF2::match(message)) @@ -355,6 +381,16 @@ void BladeRF2InputGui::on_gain_valueChanged(int value) sendSettings(); } +void BladeRF2InputGui::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("LimeSDRInputGUI::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + void BladeRF2InputGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.h b/plugins/samplesource/bladerf2input/bladerf2inputgui.h index 6ec59f776..33ff82bd7 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.h @@ -69,6 +69,8 @@ private: void displaySettings(); void sendSettings(); void updateSampleRateAndFrequency(); + void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); void blockApplySettings(bool block); private slots: @@ -84,6 +86,7 @@ private slots: void on_fcPos_currentIndexChanged(int index); void on_gainMode_currentIndexChanged(int index); void on_gain_valueChanged(int value); + void on_transverter_clicked(); void on_startStop_toggled(bool checked); void on_record_toggled(bool checked); void updateHardware(); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui index c3240635e..aa9c8cbfa 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui @@ -310,12 +310,18 @@ - + + + + 24 + 24 + + - Bias Tee + Transverter frequency translation dialog - BT + X @@ -487,7 +493,7 @@ - + 3 @@ -531,6 +537,16 @@ + + + + Bias Tee + + + BT + + + @@ -571,6 +587,11 @@ QToolButton
    gui/buttonswitch.h
    + + TransverterButton + QPushButton +
    gui/transverterbutton.h
    +
    diff --git a/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp b/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp index 07e604ddc..21e939ff2 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp @@ -36,6 +36,8 @@ void BladeRF2InputSettings::resetToDefaults() m_fcPos = FC_POS_INFRA; m_dcBlock = false; m_iqCorrection = false; + m_transverterMode = false; + m_transverterDeltaFrequency = 0; m_fileRecordName = ""; } @@ -53,6 +55,8 @@ QByteArray BladeRF2InputSettings::serialize() const s.writeBool(8, m_dcBlock); s.writeBool(9, m_iqCorrection); s.writeS32(10, m_LOppmTenths); + s.writeBool(11, m_transverterMode); + s.writeS64(12, m_transverterDeltaFrequency); return s.final(); } @@ -82,6 +86,8 @@ bool BladeRF2InputSettings::deserialize(const QByteArray& data) d.readBool(8, &m_dcBlock); d.readBool(9, &m_iqCorrection); d.readS32(10, &m_LOppmTenths); + d.readBool(11, &m_transverterMode, false); + d.readS64(12, &m_transverterDeltaFrequency, 0); return true; } diff --git a/plugins/samplesource/bladerf2input/bladerf2inputsettings.h b/plugins/samplesource/bladerf2input/bladerf2inputsettings.h index 3cd44fb62..32af13ab4 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputsettings.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputsettings.h @@ -39,6 +39,8 @@ struct BladeRF2InputSettings { fcPos_t m_fcPos; bool m_dcBlock; bool m_iqCorrection; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; QString m_fileRecordName; BladeRF2InputSettings(); diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index f355d8eab..fe31a31c5 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1361,6 +1361,13 @@ margin-bottom: 20px; "iqCorrection" : { "type" : "integer" }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, "fileRecordName" : { "type" : "string" } @@ -1407,6 +1414,13 @@ margin-bottom: 20px; }, "log2Interp" : { "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" } }, "description" : "BladeRF2" @@ -23228,7 +23242,7 @@ except ApiException as e:
    - Generated 2018-09-30T11:01:56.666+02:00 + Generated 2018-10-03T06:02:12.961+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml index dd873fc17..36e0f7c32 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF2.yaml @@ -24,6 +24,11 @@ BladeRF2InputSettings: type: integer iqCorrection: type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 fileRecordName: type: string @@ -61,6 +66,11 @@ BladeRF2OutputSettings: type: integer log2Interp: type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 BladeRF2OutputReport: description: BladeRF2 diff --git a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml index 9fb90fded..482d928fc 100644 --- a/swagger/sdrangel/api/swagger/include/BladeRF2.yaml +++ b/swagger/sdrangel/api/swagger/include/BladeRF2.yaml @@ -24,6 +24,11 @@ BladeRF2InputSettings: type: integer iqCorrection: type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 fileRecordName: type: string @@ -61,6 +66,11 @@ BladeRF2OutputSettings: type: integer log2Interp: type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 BladeRF2OutputReport: description: BladeRF2 diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index f355d8eab..fe31a31c5 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1361,6 +1361,13 @@ margin-bottom: 20px; "iqCorrection" : { "type" : "integer" }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" + }, "fileRecordName" : { "type" : "string" } @@ -1407,6 +1414,13 @@ margin-bottom: 20px; }, "log2Interp" : { "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" } }, "description" : "BladeRF2" @@ -23228,7 +23242,7 @@ except ApiException as e:
    - Generated 2018-09-30T11:01:56.666+02:00 + Generated 2018-10-03T06:02:12.961+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp index a8c80301a..2fefeb421 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.cpp @@ -50,6 +50,10 @@ SWGBladeRF2InputSettings::SWGBladeRF2InputSettings() { m_dc_block_isSet = false; iq_correction = 0; m_iq_correction_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; file_record_name = nullptr; m_file_record_name_isSet = false; } @@ -82,6 +86,10 @@ SWGBladeRF2InputSettings::init() { m_dc_block_isSet = false; iq_correction = 0; m_iq_correction_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; file_record_name = new QString(""); m_file_record_name_isSet = false; } @@ -99,6 +107,8 @@ SWGBladeRF2InputSettings::cleanup() { + + if(file_record_name != nullptr) { delete file_record_name; } @@ -137,6 +147,10 @@ SWGBladeRF2InputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); } @@ -188,6 +202,12 @@ SWGBladeRF2InputSettings::asJsonObject() { if(m_iq_correction_isSet){ obj->insert("iqCorrection", QJsonValue(iq_correction)); } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } if(file_record_name != nullptr && *file_record_name != QString("")){ toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); } @@ -305,6 +325,26 @@ SWGBladeRF2InputSettings::setIqCorrection(qint32 iq_correction) { this->m_iq_correction_isSet = true; } +qint32 +SWGBladeRF2InputSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGBladeRF2InputSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGBladeRF2InputSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGBladeRF2InputSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + QString* SWGBladeRF2InputSettings::getFileRecordName() { return file_record_name; @@ -331,6 +371,8 @@ SWGBladeRF2InputSettings::isSet(){ if(m_fc_pos_isSet){ isObjectUpdated = true; break;} if(m_dc_block_isSet){ isObjectUpdated = true; break;} if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h index fdc92abe2..10ee26670 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2InputSettings.h @@ -75,6 +75,12 @@ public: qint32 getIqCorrection(); void setIqCorrection(qint32 iq_correction); + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + QString* getFileRecordName(); void setFileRecordName(QString* file_record_name); @@ -115,6 +121,12 @@ private: qint32 iq_correction; bool m_iq_correction_isSet; + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + QString* file_record_name; bool m_file_record_name_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp index df7a85fd4..39d6a4a49 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.cpp @@ -42,6 +42,10 @@ SWGBladeRF2OutputSettings::SWGBladeRF2OutputSettings() { m_bias_tee_isSet = false; log2_interp = 0; m_log2_interp_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; } SWGBladeRF2OutputSettings::~SWGBladeRF2OutputSettings() { @@ -64,6 +68,10 @@ SWGBladeRF2OutputSettings::init() { m_bias_tee_isSet = false; log2_interp = 0; m_log2_interp_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; } void @@ -75,6 +83,8 @@ SWGBladeRF2OutputSettings::cleanup() { + + } SWGBladeRF2OutputSettings* @@ -102,6 +112,10 @@ SWGBladeRF2OutputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&log2_interp, pJson["log2Interp"], "qint32", ""); + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + } QString @@ -139,6 +153,12 @@ SWGBladeRF2OutputSettings::asJsonObject() { if(m_log2_interp_isSet){ obj->insert("log2Interp", QJsonValue(log2_interp)); } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } return obj; } @@ -213,6 +233,26 @@ SWGBladeRF2OutputSettings::setLog2Interp(qint32 log2_interp) { this->m_log2_interp_isSet = true; } +qint32 +SWGBladeRF2OutputSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGBladeRF2OutputSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGBladeRF2OutputSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGBladeRF2OutputSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + bool SWGBladeRF2OutputSettings::isSet(){ @@ -225,6 +265,8 @@ SWGBladeRF2OutputSettings::isSet(){ if(m_global_gain_isSet){ isObjectUpdated = true; break;} if(m_bias_tee_isSet){ isObjectUpdated = true; break;} if(m_log2_interp_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h index af1a497c3..b4fcfd7f2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRF2OutputSettings.h @@ -62,6 +62,12 @@ public: qint32 getLog2Interp(); void setLog2Interp(qint32 log2_interp); + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + virtual bool isSet() override; @@ -87,6 +93,12 @@ private: qint32 log2_interp; bool m_log2_interp_isSet; + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + }; } From d79e8a44957ee1bfa2e0a5bc94d13cfc31c7fc3f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 6 Oct 2018 04:37:43 +0200 Subject: [PATCH 831/956] Removed explicit SSE2 code in GLSpectrum. Fixes issue #192 --- sdrgui/gui/glspectrum.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 1a9356469..69ad6d5ab 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -15,7 +15,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifdef USE_SSE2 +#if 0 //def USE_SSE2 #include #endif @@ -416,7 +416,7 @@ void GLSpectrum::updateHistogram(const std::vector& spectrum) m_currentSpectrum = &spectrum; // Store spectrum for current spectrum line display -#ifdef USE_SSE2 +#if 0 //def USE_SSE2 if(m_decay >= 0) { // normal const __m128 refl = {m_referenceLevel, m_referenceLevel, m_referenceLevel, m_referenceLevel}; const __m128 power = {m_powerRange, m_powerRange, m_powerRange, m_powerRange}; From 15bc1e214978bd49d01947c74f3d5e7d8f8dab54 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 6 Oct 2018 21:25:27 +0200 Subject: [PATCH 832/956] AudioDeviceManager: fix device infos cleanup segfault due to iterator processing --- sdrbase/audio/audiodevicemanager.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/sdrbase/audio/audiodevicemanager.cpp b/sdrbase/audio/audiodevicemanager.cpp index 439bbfbd0..b6f0d0418 100644 --- a/sdrbase/audio/audiodevicemanager.cpp +++ b/sdrbase/audio/audiodevicemanager.cpp @@ -669,18 +669,24 @@ void AudioDeviceManager::inputInfosCleanup() deviceNames.insert(m_defaultDeviceName); QList::const_iterator itd = m_inputDevicesInfo.begin(); - for (; itd != m_inputDevicesInfo.end(); ++itd) { + for (; itd != m_inputDevicesInfo.end(); ++itd) + { + qDebug("AudioDeviceManager::inputInfosCleanup: device: %s", qPrintable(itd->deviceName())); deviceNames.insert(itd->deviceName()); } QMap::iterator itm = m_audioInputInfos.begin(); - for (; itm != m_audioInputInfos.end(); ++itm) + for (; itm != m_audioInputInfos.end();) { if (!deviceNames.contains(itm.key())) { qDebug("AudioDeviceManager::inputInfosCleanup: removing key: %s", qPrintable(itm.key())); - m_audioInputInfos.remove(itm.key()); + m_audioInputInfos.erase(itm++); + } + else + { + ++itm; } } } @@ -691,18 +697,24 @@ void AudioDeviceManager::outputInfosCleanup() deviceNames.insert(m_defaultDeviceName); QList::const_iterator itd = m_outputDevicesInfo.begin(); - for (; itd != m_outputDevicesInfo.end(); ++itd) { + for (; itd != m_outputDevicesInfo.end(); ++itd) + { + qDebug("AudioDeviceManager::outputInfosCleanup: device: %s", qPrintable(itd->deviceName())); deviceNames.insert(itd->deviceName()); } QMap::iterator itm = m_audioOutputInfos.begin(); - for (; itm != m_audioOutputInfos.end(); ++itm) + for (; itm != m_audioOutputInfos.end();) { if (!deviceNames.contains(itm.key())) { qDebug("AudioDeviceManager::outputInfosCleanup: removing key: %s", qPrintable(itm.key())); - m_audioOutputInfos.remove(itm.key()); + m_audioOutputInfos.erase(itm++); + } + else + { + ++itm; } } } From 863c7e15eff141659494aca9d8e9bc3f88244d86 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 6 Oct 2018 22:34:48 +0200 Subject: [PATCH 833/956] Audio: list available devices to console in debug mode --- sdrbase/audio/audiodevicemanager.cpp | 8 ++++++++ sdrbase/audio/audioinput.cpp | 2 +- sdrbase/audio/audiooutput.cpp | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sdrbase/audio/audiodevicemanager.cpp b/sdrbase/audio/audiodevicemanager.cpp index b6f0d0418..b5043a6e9 100644 --- a/sdrbase/audio/audiodevicemanager.cpp +++ b/sdrbase/audio/audiodevicemanager.cpp @@ -58,6 +58,14 @@ AudioDeviceManager::AudioDeviceManager() { m_inputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); m_outputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + + for (int i = 0; i < m_inputDevicesInfo.size(); i++) { + qDebug("AudioDeviceManager::AudioDeviceManager: input device #%d: %s", i, qPrintable(m_inputDevicesInfo[i].deviceName())); + } + + for (int i = 0; i < m_outputDevicesInfo.size(); i++) { + qDebug("AudioDeviceManager::AudioDeviceManager: output device #%d: %s", i, qPrintable(m_outputDevicesInfo[i].deviceName())); + } } AudioDeviceManager::~AudioDeviceManager() diff --git a/sdrbase/audio/audioinput.cpp b/sdrbase/audio/audioinput.cpp index 17362b640..316100dbb 100644 --- a/sdrbase/audio/audioinput.cpp +++ b/sdrbase/audio/audioinput.cpp @@ -94,7 +94,7 @@ bool AudioInput::start(int device, int rate) if (m_audioFormat.sampleSize() != 16) { - qWarning("AudioInput::start: Audio device ( %s ) failed", qPrintable(devInfo.defaultInputDevice().deviceName())); + qWarning("AudioInput::start: Audio device '%s' failed", qPrintable(devInfo.defaultInputDevice().deviceName())); return false; } diff --git a/sdrbase/audio/audiooutput.cpp b/sdrbase/audio/audiooutput.cpp index d6d2d107d..bd1d7def9 100644 --- a/sdrbase/audio/audiooutput.cpp +++ b/sdrbase/audio/audiooutput.cpp @@ -106,7 +106,7 @@ bool AudioOutput::start(int device, int rate) if (m_audioFormat.sampleSize() != 16) { - qWarning("AudioOutput::start: Audio device ( %s ) failed", qPrintable(devInfo.defaultOutputDevice().deviceName())); + qWarning("AudioOutput::start: Audio device '%s' failed", qPrintable(devInfo.defaultOutputDevice().deviceName())); return false; } From 8d99e5c980ca3702af8b3fda53d36ef5b8769aeb Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 6 Oct 2018 23:01:07 +0200 Subject: [PATCH 834/956] AudioDeviceManager: more debug messages --- sdrbase/audio/audiodevicemanager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sdrbase/audio/audiodevicemanager.cpp b/sdrbase/audio/audiodevicemanager.cpp index b5043a6e9..1b6d90c8d 100644 --- a/sdrbase/audio/audiodevicemanager.cpp +++ b/sdrbase/audio/audiodevicemanager.cpp @@ -56,13 +56,16 @@ QDataStream& operator>>(QDataStream& ds, AudioDeviceManager::OutputDeviceInfo& i AudioDeviceManager::AudioDeviceManager() { + qDebug("AudioDeviceManager::AudioDeviceManager: scan input devices"); m_inputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); - m_outputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); for (int i = 0; i < m_inputDevicesInfo.size(); i++) { qDebug("AudioDeviceManager::AudioDeviceManager: input device #%d: %s", i, qPrintable(m_inputDevicesInfo[i].deviceName())); } + qDebug("AudioDeviceManager::AudioDeviceManager: scan output devices"); + m_outputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + for (int i = 0; i < m_outputDevicesInfo.size(); i++) { qDebug("AudioDeviceManager::AudioDeviceManager: output device #%d: %s", i, qPrintable(m_outputDevicesInfo[i].deviceName())); } From 89d5155694ea21b84810571dab28ea4bdb736c49 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 7 Oct 2018 19:48:45 +0200 Subject: [PATCH 835/956] BladeRF2: updated documentation --- Readme.md | 4 +- doc/img/BladeRF2Input_plugin.png | Bin 21758 -> 24861 bytes doc/img/BladeRF2Input_plugin.xcf | Bin 94314 -> 106090 bytes doc/img/BladeRF2Output_plugin.png | Bin 0 -> 20634 bytes doc/img/BladeRF2Output_plugin.xcf | Bin 0 -> 91786 bytes plugins/samplesink/bladerf2output/readme.md | 116 +++++++++++++++++++ plugins/samplesource/bladerf2input/readme.md | 55 +++++++-- 7 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 doc/img/BladeRF2Output_plugin.png create mode 100644 doc/img/BladeRF2Output_plugin.xcf create mode 100644 plugins/samplesink/bladerf2output/readme.md diff --git a/Readme.md b/Readme.md index 9f571ac15..49919f2c7 100644 --- a/Readme.md +++ b/Readme.md @@ -100,8 +100,6 @@ It is recommended to add `-DRX_SAMPLE_24BIT=ON` on the cmake command line to act

    BladeRF classic (v.1)

    -Linux only. - [BladeRF1](https://www.nuand.com/bladerf-1) is supported through the libbladeRF library that should be installed in your system for proper build of the software and operation support. Add `libbladerf-dev` to the list of dependencies to install. Note that libbladeRF v2 is used since version 4.2.0 (git tag 2018.08). If you compile and use your own location for libbladeRF install directory you need to specify library and include locations. Example with `/opt/install/libbladerf` with the following defines on `cmake` command line: @@ -116,7 +114,7 @@ The plugins used to support BladeRF classic are specific to this version of the

    BladeRF micro (v.2)

    -Linux only. From version 4.2.0 +From version 4.2.0. Output (Tx) for Linux only. [BladeRF 2 micro](https://www.nuand.com/bladerf-2-0-micro/) is also supported using libbladeRF library that should be installed and configured in the same way as for BladeRF1. diff --git a/doc/img/BladeRF2Input_plugin.png b/doc/img/BladeRF2Input_plugin.png index 630de617feef96dbf8b9b85dd6363dc42fdb0961..bfb2eb03d88e3d1976a4110e6e9990407dd985d6 100644 GIT binary patch literal 24861 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 literal 21758 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 diff --git a/doc/img/BladeRF2Input_plugin.xcf b/doc/img/BladeRF2Input_plugin.xcf index 0c27a72b1b5df411346e01a8a980ce90895116d2..55872d3eba841e49c315912ff46650b050ad382a 100644 GIT binary patch literal 106090 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

    Introduction

    + +This output sample sink plugin sends its samples to a [BladeRF2 device](https://www.nuand.com/bladerf-2). This is available since v4.2.0 in Linux distributions only. + +

    Build

    + +The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. + +Note that the libbladeRF v2 (specifically the git tag 2018.08) is used. + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases. + +

    Multiple Output (MO) mode

    + +If two device sets one for each Tx channel are opened then the SO/MO mode switch is governed by which channels are effectively streaming. As soon as the Tx2 (channel 1) is running then the MO mode is engaged. + +The MO mode triggers the doubling of the sample rate and since the sample rate is common to the DAC and ADC it is also doubled for Rx channels. If some device sets for Rx channels are present then the sample rate in these device sets is automatically adjusted. + +For example the channel 0 is started alone at a sample rate of 3 MS/s and a sibling Rx channel is also handled in another device set. Then the sample rate of this Rx channel is also 3 MS/s. If the second Tx channel is started then the sample rate is doubled to 6 MS/s for the Tx channels and also for the Rx channel. If the second Tx channel is stopped then a transistion from MO to SO (Single Output) occurs and the sample rate is returned to 3 MS/s for all Tx and Rx channels present. + +

    Interface

    + +![BladeRF2 output plugin GUI](../../../doc/img/BladeRF2Output_plugin.png) + +

    1: Start/Stop

    + +Device start / stop button. + + - Blue triangle icon: device is ready and can be started + - Red square icon: device is running and can be stopped + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + +

    2: Baseband sample rate

    + +This is the baseband sample rate in kS/s before interpolation (4) to produce the final stream that is sent to the BladeRF device. Thus this is the device sample rate (6) divided by the interpolation factor (4). + +Transmission latency depends essentially in the delay in the sample FIFO. The FIFO size is calculated as follows: + +For interpolation by 32 the size is fixed at 150000 samples, Delay is 150000 / B where B is the baseband sample rate. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 500 kS/s: + +![BladeRF1 output plugin FIFO delay 32](../../../doc/img/BladeRF1Output_plugin_fifodly_32.png) + +For lower interpolation rates the size is calculated to give a fixed delay of 250 ms or 75000 samples whichever is bigger. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 400 kS/s. The 250 ms delay is reached at 300 kS/s: + +![BladeRF1 output plugin FIFO delay other](../../../doc/img/BladeRF1Output_plugin_fifodly_other.png) + +

    3: Frequency

    + +This is the center frequency of transmission in kHz. The center frequency is the same for all Tx channels. The GUI of the sibling channel if present is adjusted automatically. + +

    4: LO ppm correction

    + +Use this slider to adjust LO correction in ppm. It can be varied from -20.0 to 20.0 in 0.1 steps and is applied in software. This applies to the oscillator that controls both the Rx and Tx frequency therefore it is also changed on the related Rx and Tx plugin(s) if they are active. + +

    5: Tx filter bandwidth

    + +This is the Tx filter bandwidth in kHz. Minimum and maximum values are adjusted automatically. Normal range is from 200 kHz to 56 MHz. The Tx filter bandwidth is the same for all Tx channels. The GUI of the sibling channel if present is adjusted automatically. + +

    6: Transverter mode open dialog

    + +This button opens a dialog to set the transverter mode frequency translation options: + +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) + +Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. + +

    6.1: Translating frequency

    + +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for up converters and negative for down converters. + +For example with a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set to 7,130 kHz the PlutoSDR will be set to 127.130 MHz. + +If you use an up converter to transmit at the 6 cm band narrowband center frequency of 5670 MHz with the PlutoSDR set at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. + +For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to transmit at the 10368 MHz frequency with 432 MHz for the PlutoSDR you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. + +The Hz precision allows a fine tuning of the transverter LO offset + +

    6.2: Translating frequency enable/disable

    + +Use this toggle button to activate or deactivate the frequency translation + +

    6.3: Confirmation buttons

    + +Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. + +

    7: Device sample rate

    + +This is the BladeRF device DAC sample rate in S/s. + +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +

    8: Interpolation factor

    + +The baseband stream is interpolated by this value before being sent to the BladeRF device. Possible values are: + + - **1**: no interpolation + - **2**: multiply baseband stream sample rate by 2 + - **4**: multiply baseband stream sample rate by 4 + - **8**: multiply baseband stream sample rate by 8 + - **16**: multiply baseband stream sample rate by 16 + - **32**: multiply baseband stream sample rate by 32 + +The main samples buffer is based on the baseband sample rate and will introduce ~500ms delay for interpolation by 16 or lower and ~1s for interpolation by 32. + +

    9: Gain control

    + +Use this slider to adjust gain in manual mode. The gain varies from -89 to 0 dB in 1 dB steps. Thus this is in fact an attenuator + +

    10: Bias tee control

    + +Use this toggle button to activate or de-activate the bias tee. Note that according to BladeRF v2 specs the bias tee is simultanously present on all Tx RF ports. The GUI of the sibling channel if present is adjusted automatically. diff --git a/plugins/samplesource/bladerf2input/readme.md b/plugins/samplesource/bladerf2input/readme.md index 721a4c8fa..be9e76c2f 100644 --- a/plugins/samplesource/bladerf2input/readme.md +++ b/plugins/samplesource/bladerf2input/readme.md @@ -2,7 +2,7 @@

    Introduction

    -This input sample source plugin gets its samples from a [BladeRF 2.0 micro device](https://www.nuand.com/bladerf-2) using LibbladeRF v.2. This is available since v4.2.0 in Linux distributions only. +This input sample source plugin gets its samples from a [BladeRF 2.0 micro device](https://www.nuand.com/bladerf-2) using LibbladeRF v.2.

    Build

    @@ -40,22 +40,52 @@ Record baseband I/Q stream toggle button Baseband I/Q sample rate in kS/s. This is the device sample rate (4) divided by the decimation factor (6). -

    2: Auto correction options

    +

    2: LO ppm correction

    + +Use this slider to adjust LO correction in ppm. It can be varied from -20.0 to 20.0 in 0.1 steps and is applied in software. This applies to the oscillator that controls both the Rx and Tx frequency therefore it is also changed on the related Rx and Tx plugin(s) if they are active. + +

    3: Auto correction options

    These buttons control the local DSP auto correction options: - **DC**: auto remove DC component - **IQ**: auto make I/Q balance. The DC correction must be enabled for this to be effective. -

    3: Rx filter bandwidth

    +

    4: Rx filter bandwidth

    This is the Rx filter bandwidth in kHz. Minimum and maximum values are adjusted automatically. Normal range is from 200 kHz to 56 MHz. The Rx filter bandwidth is the same for all Rx channels. The GUI of the sibling channel if present is adjusted automatically. -

    4: Bias tee control

    +

    5: Transverter mode open dialog

    -Use this toggle button to activate or de-activate the bias tee. Note that according to BladeRF v2 specs the bias tee is simultanously present on all Rx RF ports. The GUI of the sibling channel if present is adjusted automatically. +This button opens a dialog to set the transverter mode frequency translation options: -

    5: Device sample rate

    +![Input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) + +Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. + +

    5.1: Translating frequency

    + +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. + +For example a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set at 7,130 kHz the PlutoSDR will be set to 127.130 MHz. + +If you use a down converter to receive the 6 cm band narrowband center frequency of 5670 MHz at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. + +For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to receive the 10368 MHz frequency at 432 MHz you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. + +The Hz precision allows a fine tuning of the transverter LO offset + +

    5.2: Translating frequency enable/disable

    + +Use this toggle button to activate or deactivate the frequency translation + +

    5.3: Confirmation buttons

    + +Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. + +

    6: Device sample rate

    This is the BladeRF device ADC sample rate in S/s. @@ -63,7 +93,7 @@ Use the wheels to adjust the sample rate. Left click on a digit sets the cursor The ADC sample rate is the same for all Rx channels. The GUI of the sibling channel if present is adjusted automatically. -

    6: Baseband center frequency position relative the the BladeRF Rx center frequency

    +

    7: Baseband center frequency position relative the the BladeRF Rx center frequency

    Possible values are: @@ -76,11 +106,11 @@ With SR as the sample rate before decimation Fc is calculated as: - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband. -

    7: Decimation factor

    +

    8: Decimation factor

    The I/Q stream from the BladeRF ADC is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. -

    8: Gain mode

    +

    9: Gain mode

    This selects the gain processing in use. Values are fetched automatically from the device. Normal values are @@ -90,6 +120,11 @@ This selects the gain processing in use. Values are fetched automatically from t - **slow**: slow AGC - **hybrid**: hybrid AGC -

    9: Manual gain control

    +

    10: Manual gain control

    Use this slider to adjust gain in manual mode. This control is disabled in non manual modes (all modes but manual). The minumum, maximum and step values are fetched automatically from the device and may vary depending on the center frequency. For frequencies around 400 MHz the gain varies from -16 to 60 dB in 1 dB steps. + +

    11: Bias tee control

    + +Use this toggle button to activate or de-activate the bias tee. Note that according to BladeRF v2 specs the bias tee is simultanously present on all Rx RF ports. The GUI of the sibling channel if present is adjusted automatically. + From 39c001f95e3a09656c7f86cb147f88e663202d22 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 7 Oct 2018 19:50:23 +0200 Subject: [PATCH 836/956] BladeRF: updated Windows install script --- windows.install.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/windows.install.bat b/windows.install.bat index 589ee9a50..9e58f2b0f 100644 --- a/windows.install.bat +++ b/windows.install.bat @@ -67,6 +67,7 @@ copy plugins\samplesource\plutosdrinput\%1\inputplutosdr.dll %2\plugins\sampleso REM copy plugins\samplesource\sdrdaemonsource\%1\inputsdrdaemonsource.dll %2\plugins\samplesource copy plugins\samplesink\filesink\%1\outputfilesink.dll %2\plugins\samplesink copy plugins\samplesink\bladerf1output\%1\outputbladerf1.dll %2\plugins\samplesink +copy plugins\samplesink\bladerf2output\%1\outputbladerf2.dll %2\plugins\samplesink copy plugins\samplesink\hackrfoutput\%1\outputhackrf.dll %2\plugins\samplesink copy plugins\samplesink\limesdroutput\%1\outputlimesdr.dll %2\plugins\samplesink copy plugins\samplesink\plutosdroutput\%1\outputplutosdr.dll %2\plugins\samplesink From f88dcd01e61feaf1385a1f47411714304732fd7e Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 8 Oct 2018 19:01:01 +0200 Subject: [PATCH 837/956] FileRecord improvement: create a command to rescue corrupted .sdriq files --- rescuesdriq/rescuesdriq.go | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 rescuesdriq/rescuesdriq.go diff --git a/rescuesdriq/rescuesdriq.go b/rescuesdriq/rescuesdriq.go new file mode 100644 index 000000000..614fa4216 --- /dev/null +++ b/rescuesdriq/rescuesdriq.go @@ -0,0 +1,77 @@ +package main + +import ( + "flag" + "fmt" + "bufio" + "io" + "os" + "bytes" + "encoding/binary" + "time" +) + + +type HeaderStd struct { + SampleRate uint32 + CenterFrequency uint64 + StartTimestamp int64 + SampleSize uint32 + _ uint32 + CRC32 uint32 +} + +func analyze(fileName string) HeaderStd { + fmt.Println("input file:", fileName) + + // open input file + fi, err := os.Open(fileName) + if err != nil { + panic(err) + } + // close fi on exit and check for its returned error + defer func() { + if err := fi.Close(); err != nil { + panic(err) + } + }() + // make a read buffer + r := bufio.NewReader(fi) + + headerbuf := make([]byte, 32) // This is a full header with CRC + n, err := r.Read(headerbuf) + if err != nil && err != io.EOF { + panic(err) + } + if (n != 32) { + panic("Header too small") + } + + var header HeaderStd + headerr := bytes.NewReader(headerbuf) + err = binary.Read(headerr, binary.LittleEndian, &header) + if err != nil { + panic(err) + } + + fmt.Println("Sample rate:", header.SampleRate) + fmt.Println("Frequency :", header.CenterFrequency) + fmt.Println("Sample Size:", header.SampleSize) + tm := time.Unix(header.StartTimestamp, 0) + fmt.Println("Start :", tm) + + return header +} + +func main() { + wordPtr := flag.String("in", "foo", "input file") + flag.Parse() + flagSeen := make(map[string]bool) + flag.Visit(func(f *flag.Flag) { flagSeen[f.Name] = true }) + + if flagSeen["in"] { + analyze(*wordPtr) + } else { + fmt.Println("No input file given") + } +} From f6b1fd252e3b1b16e0e16d1f30c02dd6e19436a4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 9 Oct 2018 01:06:39 +0200 Subject: [PATCH 838/956] FileRecord improvement: finalized the command to rescue corrupted .sdriq files --- .gitignore | 1 + rescuesdriq/readme.md | 59 ++++++++++++++ rescuesdriq/rescuesdriq.go | 157 ++++++++++++++++++++++++++++++------- 3 files changed, 190 insertions(+), 27 deletions(-) create mode 100644 rescuesdriq/readme.md diff --git a/.gitignore b/.gitignore index 6aa50abf1..e8a7ef5ed 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ sdrangelove.supp *.cs *.pro.user .idea/* +rescuesdriq/rescuesdriq debian/sdrangel/* debian/sdrangel.substvars debian/files diff --git a/rescuesdriq/readme.md b/rescuesdriq/readme.md new file mode 100644 index 000000000..83a33ed93 --- /dev/null +++ b/rescuesdriq/readme.md @@ -0,0 +1,59 @@ +

    Repair or reformat record (.sdriq) files

    + +

    Usage

    + +This utility attempts to repair .sdriq files that have their header corrupted or with a pre version 4.2.1 header. Since version 4.2.1 a CRC32 checksum is present and the file will not be played if the check of the header content against the CRC32 fails. + +The header is composed as follows: + + - Sample rate in S/s (4 bytes, 32 bits) + - Center frequency in Hz (8 bytes, 64 bits) + - Start time Unix timestamp epoch in seconds (8 bytes, 64 bits) + - Sample size as 16 or 24 bits (4 bytes, 32 bits) + - filler with all zeroes (4 bytes, 32 bits) + - CRC32 (IEEE) of the 28 bytes above (4 bytes, 32 bits) + +The header size is 32 bytes in total which is a multiple of 8 bytes thus occupies an integer number of samples whether in 16 or 24 bits mode. When migrating from a pre version 4.2.1 header you may crunch a very small amount of samples. + +You can replace values in the header with the following options: + + - -sr uint + Sample rate (S/s) + - -cf uint + Center frequency (Hz) + - -ts string + start time RFC3339 (ex: 2006-01-02T15:04:05Z) + - -now + use now for start time + - -sz uint + Sample size (16 or 24) (default 16) + +You need to specify an input file. If no output file is specified the current header values are printed to the console and the program exits: + + - -in string + input file (default "foo") + +To convert to a new file you need to specify the output file: + + - -out string + output file (default "foo") + +You can specify a block size in multiples of 4k for the copy. Large blocks will yield a faster copy but a larger output file. With the default of 1 (4k) the copy does not take much time anyway: + + - -bz uint + Copy block size in multiple of 4k (default 1) + +

    Build

    + +The program is written in go and is provided only in source code form. Compiling it is very easy: + +

    Install go

    + +You will usually find a `golang` package in your distribution. For example in Ubuntu or Debian you can install it with `sudo apt-get install golang`. You can find binary distributions for many systems at the [Go download site](https://golang.org/dl/) + +

    Build the program

    + +In this directory just do `go build` + + + \ No newline at end of file diff --git a/rescuesdriq/rescuesdriq.go b/rescuesdriq/rescuesdriq.go index 614fa4216..fdf4108ab 100644 --- a/rescuesdriq/rescuesdriq.go +++ b/rescuesdriq/rescuesdriq.go @@ -9,6 +9,7 @@ import ( "bytes" "encoding/binary" "time" + "hash/crc32" ) @@ -17,27 +18,18 @@ type HeaderStd struct { CenterFrequency uint64 StartTimestamp int64 SampleSize uint32 - _ uint32 + Filler uint32 CRC32 uint32 } -func analyze(fileName string) HeaderStd { - fmt.Println("input file:", fileName) - // open input file - fi, err := os.Open(fileName) - if err != nil { - panic(err) +func check(e error) { + if e != nil { + panic(e) } - // close fi on exit and check for its returned error - defer func() { - if err := fi.Close(); err != nil { - panic(err) - } - }() - // make a read buffer - r := bufio.NewReader(fi) - +} + +func analyze(r *bufio.Reader) HeaderStd { headerbuf := make([]byte, 32) // This is a full header with CRC n, err := r.Read(headerbuf) if err != nil && err != io.EOF { @@ -50,27 +42,138 @@ func analyze(fileName string) HeaderStd { var header HeaderStd headerr := bytes.NewReader(headerbuf) err = binary.Read(headerr, binary.LittleEndian, &header) - if err != nil { - panic(err) - } - - fmt.Println("Sample rate:", header.SampleRate) - fmt.Println("Frequency :", header.CenterFrequency) - fmt.Println("Sample Size:", header.SampleSize) - tm := time.Unix(header.StartTimestamp, 0) - fmt.Println("Start :", tm) + check(err) return header } +func writeHeader(writer *bufio.Writer, header *HeaderStd) { + var bin_buf bytes.Buffer + binary.Write(&bin_buf, binary.LittleEndian, header) + noh, err := writer.Write(bin_buf.Bytes()) + check(err) + fmt.Printf("Wrote %d bytes header\n", noh) +} + +func printHeader(header *HeaderStd) { + fmt.Println("Sample rate:", header.SampleRate) + fmt.Println("Frequency :", header.CenterFrequency) + fmt.Println("Sample Size:", header.SampleSize) + tm := time.Unix(header.StartTimestamp, 0) + fmt.Println("Start :", tm) +} + +func setCRC(header *HeaderStd) { + var bin_buf bytes.Buffer + header.Filler = 0 + binary.Write(&bin_buf, binary.LittleEndian, header) + header.CRC32 = crc32.ChecksumIEEE(bin_buf.Bytes()[0:28]) +} + +func copyContent(reader *bufio.Reader, writer *bufio.Writer, blockSize uint) { + p := make([]byte, blockSize*4096) // buffer in 4k multiples + var sz int64 = 0 + + for { + n, err := reader.Read(p) + + if err != nil { + if err == io.EOF { + writer.Write(p[0:n]) + sz += int64(n) + break + } else { + fmt.Println("An error occured during content copy. Aborting") + break + } + } else { + writer.Write(p) + sz += int64(blockSize)*4096 + } + + fmt.Printf("Wrote %d bytes\r", sz) + } + + fmt.Printf("Wrote %d bytes\r", sz) +} + func main() { - wordPtr := flag.String("in", "foo", "input file") + inFileStr := flag.String("in", "foo", "input file") + outFileStr := flag.String("out", "foo", "output file") + sampleRate := flag.Uint("sr", 0, "Sample rate (S/s)") + centerFreq := flag.Uint64("cf", 0, "Center frequency (Hz)") + sampleSize := flag.Uint("sz", 16, "Sample size (16 or 24)") + timeStr := flag.String("ts", "", "start time RFC3339 (ex: 2006-01-02T15:04:05Z)") + timeNow := flag.Bool("now", false , "use now for start time") + blockSize := flag.Uint("bz", 1, "Copy block size in multiple of 4k") + flag.Parse() flagSeen := make(map[string]bool) flag.Visit(func(f *flag.Flag) { flagSeen[f.Name] = true }) if flagSeen["in"] { - analyze(*wordPtr) + fmt.Println("input file :", *inFileStr) + + // open input file + fi, err := os.Open(*inFileStr) + check(err) + // close fi on exit and check for its returned error + defer func() { + err := fi.Close(); + check(err) + }() + // make a read buffer + reader := bufio.NewReader(fi) + var headerOrigin HeaderStd = analyze(reader) + printHeader(&headerOrigin) + + if flagSeen["out"] { + if flagSeen["sr"] { + headerOrigin.SampleRate = uint32(*sampleRate) + } else if flagSeen["cf"] { + headerOrigin.CenterFrequency = *centerFreq + } else if flagSeen["sz"] { + if (*sampleSize == 16) || (*sampleSize == 24) { + headerOrigin.SampleSize = uint32(*sampleSize) + } else { + fmt.Println("Incorrect sample size specified. Defaulting to 16") + headerOrigin.SampleSize = 16 + } + } else if flagSeen["ts"] { + t, err := time.Parse(time.RFC3339, *timeStr) + if (err == nil) { + headerOrigin.StartTimestamp = t.Unix() + } else { + fmt.Println("Incorrect time specified. Defaulting to now") + headerOrigin.StartTimestamp = int64(time.Now().Unix()) + } + } else if *timeNow { + headerOrigin.StartTimestamp = int64(time.Now().Unix()) + } + + fmt.Println("\nHeader is now") + printHeader(&headerOrigin) + setCRC(&headerOrigin) + fmt.Println("CRC32 :", headerOrigin.CRC32) + fmt.Println("Output file:", *outFileStr) + + fo, err := os.Create(*outFileStr) + check(err) + + defer func() { + err := fo.Close(); + check(err) + }() + + writer := bufio.NewWriter(fo) + + writeHeader(writer, &headerOrigin) + copyContent(reader, writer, *blockSize) + + fmt.Println("\nCopy done") + writer.Flush() + } + } else { fmt.Println("No input file given") } From 6269125d2cead240434471470574f86167be32e9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 9 Oct 2018 02:08:06 +0200 Subject: [PATCH 839/956] BladeRF2: fixed Tx channel enable/disable wrapping routine thus fixing issue #225 --- debian/changelog | 7 +++++++ devices/bladerf2/devicebladerf2.cpp | 2 +- plugins/samplesink/bladerf2output/bladerf2output.cpp | 10 ++++------ plugins/samplesink/bladerf2output/readme.md | 12 ++---------- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/debian/changelog b/debian/changelog index c792b80cf..4d864899b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +sdrangel (4.2.1-1) unstable; urgency=medium + + * FileRecord improvement with robust header and some fixes. Fixes issue #206 + * BladeRF2 MO Tx fix so that the two channels are used effectively + + -- Edouard Griffiths, F4EXB Sun, 14 Oct 2018 21:14:18 +0200 + sdrangel (4.2.0-1) unstable; urgency=medium * LibbladeRF 2.0 support with BladeRF Micro diff --git a/devices/bladerf2/devicebladerf2.cpp b/devices/bladerf2/devicebladerf2.cpp index 187bcb2d5..e0db04adf 100644 --- a/devices/bladerf2/devicebladerf2.cpp +++ b/devices/bladerf2/devicebladerf2.cpp @@ -184,7 +184,7 @@ bool DeviceBladeRF2::openTx(int channel) if (!m_txOpen[channel]) { - status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(0), true); + status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(channel), true); if (status < 0) { diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 6b00b07b3..0da56e24f 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -595,18 +595,16 @@ bool BladeRF2Output::handleMessage(const Message& message) if (dev) // The BladeRF device must have been open to do so { int requestedChannel = m_deviceAPI->getItemIndex(); - int nbChannels = getNbChannels(); if (report.getRxElseTx()) // Rx buddy change: check for sample rate change only { - tmp_uint = report.getDevSampleRate(); - settings.m_devSampleRate = tmp_uint / (nbChannels == 0 ? 1 : nbChannels); + settings.m_devSampleRate = report.getDevSampleRate(); // status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_uint); // // if (status < 0) { // qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); // } else { -// settings.m_devSampleRate = tmp_uint / (nbChannels == 0 ? 1 : nbChannels); +// settings.m_devSampleRate = tmp_uint; // } } else // Tx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth @@ -732,7 +730,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool unsigned int actualSamplerate; int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), - settings.m_devSampleRate * (nbChannels == 0 ? 1 : nbChannels), + settings.m_devSampleRate, &actualSamplerate); if (status < 0) @@ -847,7 +845,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool settings.m_centerFrequency, settings.m_LOppmTenths, 2, - settings.m_devSampleRate * (nbChannels == 0 ? 1 : nbChannels), // need to forward actual rate to the Rx side + settings.m_devSampleRate, // need to forward actual rate to the Rx side false); (*itSource)->getSampleSourceInputMessageQueue()->push(report); } diff --git a/plugins/samplesink/bladerf2output/readme.md b/plugins/samplesink/bladerf2output/readme.md index dcf7f5120..070ffc554 100644 --- a/plugins/samplesink/bladerf2output/readme.md +++ b/plugins/samplesink/bladerf2output/readme.md @@ -8,18 +8,10 @@ This output sample sink plugin sends its samples to a [BladeRF2 device](https:// The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. -Note that the libbladeRF v2 (specifically the git tag 2018.08) is used. +Note that the libbladeRF v2 (specifically the git tag 2018.08) is used. The FPGA image v0.7.3 should be used accordingly. The FPGA .rbf file should be copied to the folder where the `sdrangel` binary resides. You can download FPGA images from [here](https://www.nuand.com/fpga_images/) The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases. - -

    Multiple Output (MO) mode

    - -If two device sets one for each Tx channel are opened then the SO/MO mode switch is governed by which channels are effectively streaming. As soon as the Tx2 (channel 1) is running then the MO mode is engaged. - -The MO mode triggers the doubling of the sample rate and since the sample rate is common to the DAC and ADC it is also doubled for Rx channels. If some device sets for Rx channels are present then the sample rate in these device sets is automatically adjusted. - -For example the channel 0 is started alone at a sample rate of 3 MS/s and a sibling Rx channel is also handled in another device set. Then the sample rate of this Rx channel is also 3 MS/s. If the second Tx channel is started then the sample rate is doubled to 6 MS/s for the Tx channels and also for the Rx channel. If the second Tx channel is stopped then a transistion from MO to SO (Single Output) occurs and the sample rate is returned to 3 MS/s for all Tx and Rx channels present. - +

    Interface

    ![BladeRF2 output plugin GUI](../../../doc/img/BladeRF2Output_plugin.png) From cc49d5c2662afdd3db1758d216ad0de645b2bc2c Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 9 Oct 2018 09:26:28 +0200 Subject: [PATCH 840/956] FileRecord improvement: CRC check and sample size fix --- .../filesource/filesourceinput.cpp | 21 +++++-- sdrbase/dsp/filerecord.cpp | 55 +++++++++++++------ sdrbase/dsp/filerecord.h | 36 +++++++++--- 3 files changed, 81 insertions(+), 31 deletions(-) diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 032d39f85..24f7c2097 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -16,6 +16,7 @@ #include #include + #include #include "SWGDeviceSettings.h" @@ -87,14 +88,22 @@ void FileSourceInput::openFileStream() // TODO: add CRC m_ifstream.seekg(0,std::ios_base::beg); FileRecord::Header header; - FileRecord::readHeader(m_ifstream, header); - m_sampleRate = header.sampleRate; - m_centerFrequency = header.centerFrequency; - m_startingTimeStamp = header.startTimeStamp; - m_sampleSize = header.sampleSize; + if (FileRecord::readHeader(m_ifstream, header)) // CRC OK + { + m_sampleRate = header.sampleRate; + m_centerFrequency = header.centerFrequency; + m_startingTimeStamp = header.startTimeStamp; + m_sampleSize = header.sampleSize; + + m_recordLength = (fileSize - sizeof(FileRecord::Header)) / ((m_sampleSize == 24 ? 8 : 4) * m_sampleRate); + } + else + { + qCritical("FileSourceInput::openFileStream: bad CRC header"); + m_recordLength = 0; + } - m_recordLength = (fileSize - sizeof(FileRecord::Header)) / (4 * m_sampleRate); } else { diff --git a/sdrbase/dsp/filerecord.cpp b/sdrbase/dsp/filerecord.cpp index 56f1e07cc..55ae8cfc0 100644 --- a/sdrbase/dsp/filerecord.cpp +++ b/sdrbase/dsp/filerecord.cpp @@ -1,10 +1,30 @@ -#include +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 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 // +// // +// 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 "dsp/dspcommands.h" #include "util/simpleserializer.h" #include "util/message.h" -#include -#include +#include "filerecord.h" FileRecord::FileRecord() : BasebandSampleSink(), @@ -128,21 +148,24 @@ void FileRecord::handleConfigure(const QString& fileName) void FileRecord::writeHeader() { - m_sampleFile.write((const char *) &m_sampleRate, sizeof(qint32)); // 4 bytes - m_sampleFile.write((const char *) &m_centerFrequency, sizeof(quint64)); // 8 bytes + Header header; + header.sampleRate = m_sampleRate; + header.centerFrequency = m_centerFrequency; std::time_t ts = time(0); - m_sampleFile.write((const char *) &ts, sizeof(std::time_t)); // 8 bytes - quint32 sampleSize = SDR_RX_SAMP_SZ; - m_sampleFile.write((const char *) &sampleSize, sizeof(int)); // 4 bytes + header.startTimeStamp = ts; + header.sampleSize = SDR_RX_SAMP_SZ; + header.filler = 0; + boost::crc_32_type crc32; + crc32.process_bytes(&header, 28); + header.crc32 = crc32.checksum(); + + m_sampleFile.write((const char *) &header, sizeof(Header)); } -void FileRecord::readHeader(std::ifstream& sampleFile, Header& header) +bool FileRecord::readHeader(std::ifstream& sampleFile, Header& header) { - sampleFile.read((char *) &(header.sampleRate), sizeof(qint32)); - sampleFile.read((char *) &(header.centerFrequency), sizeof(quint64)); - sampleFile.read((char *) &(header.startTimeStamp), sizeof(std::time_t)); - sampleFile.read((char *) &(header.sampleSize), sizeof(quint32)); - if ((header.sampleSize != 16) && (header.sampleSize != 24)) { // assume 16 bits if garbage (old I/Q file) - header.sampleSize = 16; - } + sampleFile.read((char *) &header, sizeof(Header)); + boost::crc_32_type crc32; + crc32.process_bytes(&header, 28); + return header.crc32 == crc32.checksum(); } diff --git a/sdrbase/dsp/filerecord.h b/sdrbase/dsp/filerecord.h index 08970e004..51edbca49 100644 --- a/sdrbase/dsp/filerecord.h +++ b/sdrbase/dsp/filerecord.h @@ -1,5 +1,21 @@ -#ifndef INCLUDE_FILESINK_H -#define INCLUDE_FILESINK_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 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 // +// // +// 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_FILERECORD_H +#define INCLUDE_FILERECORD_H #include #include @@ -16,10 +32,12 @@ public: struct Header { - qint32 sampleRate; - quint64 centerFrequency; - std::time_t startTimeStamp; - quint32 sampleSize; + quint32 sampleRate; + quint64 centerFrequency; + quint64 startTimeStamp; + quint32 sampleSize; + quint32 filler; + quint32 crc32; }; FileRecord(); @@ -37,11 +55,11 @@ public: virtual bool handleMessage(const Message& message); void startRecording(); void stopRecording(); - static void readHeader(std::ifstream& samplefile, Header& header); + static bool readHeader(std::ifstream& samplefile, Header& header); //!< returns true if CRC checksum is correct else false private: QString m_fileName; - qint32 m_sampleRate; + quint32 m_sampleRate; quint64 m_centerFrequency; bool m_recordOn; bool m_recordStart; @@ -52,4 +70,4 @@ private: void writeHeader(); }; -#endif // INCLUDE_FILESINK_H +#endif // INCLUDE_FILERECORD_H From 38aa1a8e77163ed17c5fbafb84a01f4208771d5a Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 9 Oct 2018 16:40:57 +0200 Subject: [PATCH 841/956] FileRecord improvement: fixed header packing and CRC32 computation --- .../filesource/filesourceinput.cpp | 26 ++++--- rescuesdriq/rescuesdriq.go | 69 +++++++++++-------- sdrbase/dsp/filerecord.h | 4 +- 3 files changed, 57 insertions(+), 42 deletions(-) diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 24f7c2097..636a9577b 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -86,24 +86,25 @@ void FileSourceInput::openFileStream() if (fileSize > sizeof(FileRecord::Header)) { // TODO: add CRC - m_ifstream.seekg(0,std::ios_base::beg); FileRecord::Header header; + m_ifstream.seekg(0,std::ios_base::beg); + bool crcOK = FileRecord::readHeader(m_ifstream, header); + m_sampleRate = header.sampleRate; + m_centerFrequency = header.centerFrequency; + m_startingTimeStamp = header.startTimeStamp; + m_sampleSize = header.sampleSize; + QString crcHex = QString("%1").arg(header.crc32 , 0, 16); - if (FileRecord::readHeader(m_ifstream, header)) // CRC OK + if (crcOK) { - m_sampleRate = header.sampleRate; - m_centerFrequency = header.centerFrequency; - m_startingTimeStamp = header.startTimeStamp; - m_sampleSize = header.sampleSize; - + qDebug("FileSourceInput::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex)); m_recordLength = (fileSize - sizeof(FileRecord::Header)) / ((m_sampleSize == 24 ? 8 : 4) * m_sampleRate); } else { - qCritical("FileSourceInput::openFileStream: bad CRC header"); + qCritical("FileSourceInput::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex)); m_recordLength = 0; } - } else { @@ -111,8 +112,11 @@ void FileSourceInput::openFileStream() } qDebug() << "FileSourceInput::openFileStream: " << m_fileName.toStdString().c_str() - << " fileSize: " << fileSize << "bytes" - << " length: " << m_recordLength << " seconds"; + << " fileSize: " << fileSize << " bytes" + << " length: " << m_recordLength << " seconds" + << " sample rate: " << m_sampleRate << " S/s" + << " center frequency: " << m_centerFrequency << " Hz" + << " sample size: " << m_sampleSize << " bits"; if (getMessageQueueToGUI()) { MsgReportFileSourceStreamData *report = MsgReportFileSourceStreamData::create(m_sampleRate, diff --git a/rescuesdriq/rescuesdriq.go b/rescuesdriq/rescuesdriq.go index fdf4108ab..6149140b1 100644 --- a/rescuesdriq/rescuesdriq.go +++ b/rescuesdriq/rescuesdriq.go @@ -12,7 +12,7 @@ import ( "hash/crc32" ) - + type HeaderStd struct { SampleRate uint32 CenterFrequency uint64 @@ -22,7 +22,7 @@ type HeaderStd struct { CRC32 uint32 } - + func check(e error) { if e != nil { panic(e) @@ -38,12 +38,12 @@ func analyze(r *bufio.Reader) HeaderStd { if (n != 32) { panic("Header too small") } - + var header HeaderStd headerr := bytes.NewReader(headerbuf) err = binary.Read(headerr, binary.LittleEndian, &header) check(err) - + return header } @@ -55,14 +55,6 @@ func writeHeader(writer *bufio.Writer, header *HeaderStd) { fmt.Printf("Wrote %d bytes header\n", noh) } -func printHeader(header *HeaderStd) { - fmt.Println("Sample rate:", header.SampleRate) - fmt.Println("Frequency :", header.CenterFrequency) - fmt.Println("Sample Size:", header.SampleSize) - tm := time.Unix(header.StartTimestamp, 0) - fmt.Println("Start :", tm) -} - func setCRC(header *HeaderStd) { var bin_buf bytes.Buffer header.Filler = 0 @@ -70,13 +62,30 @@ func setCRC(header *HeaderStd) { header.CRC32 = crc32.ChecksumIEEE(bin_buf.Bytes()[0:28]) } +func getCRC(header *HeaderStd) uint32 { + var bin_buf bytes.Buffer + header.Filler = 0 + binary.Write(&bin_buf, binary.LittleEndian, header) + return crc32.ChecksumIEEE(bin_buf.Bytes()[0:28]) +} + +func printHeader(header *HeaderStd) { + fmt.Println("Sample rate:", header.SampleRate) + fmt.Println("Frequency :", header.CenterFrequency) + fmt.Println("Sample Size:", header.SampleSize) + tm := time.Unix(header.StartTimestamp, 0) + fmt.Println("Start :", tm) + fmt.Println("CRC32 :", header.CRC32) + fmt.Println("CRC32 OK :", getCRC(header)) +} + func copyContent(reader *bufio.Reader, writer *bufio.Writer, blockSize uint) { p := make([]byte, blockSize*4096) // buffer in 4k multiples var sz int64 = 0 - + for { n, err := reader.Read(p) - + if err != nil { if err == io.EOF { writer.Write(p[0:n]) @@ -90,10 +99,10 @@ func copyContent(reader *bufio.Reader, writer *bufio.Writer, blockSize uint) { writer.Write(p) sz += int64(blockSize)*4096 } - + fmt.Printf("Wrote %d bytes\r", sz) } - + fmt.Printf("Wrote %d bytes\r", sz) } @@ -106,27 +115,27 @@ func main() { timeStr := flag.String("ts", "", "start time RFC3339 (ex: 2006-01-02T15:04:05Z)") timeNow := flag.Bool("now", false , "use now for start time") blockSize := flag.Uint("bz", 1, "Copy block size in multiple of 4k") - + flag.Parse() flagSeen := make(map[string]bool) flag.Visit(func(f *flag.Flag) { flagSeen[f.Name] = true }) - + if flagSeen["in"] { fmt.Println("input file :", *inFileStr) - + // open input file fi, err := os.Open(*inFileStr) check(err) // close fi on exit and check for its returned error defer func() { - err := fi.Close(); + err := fi.Close(); check(err) }() // make a read buffer - reader := bufio.NewReader(fi) + reader := bufio.NewReader(fi) var headerOrigin HeaderStd = analyze(reader) printHeader(&headerOrigin) - + if flagSeen["out"] { if flagSeen["sr"] { headerOrigin.SampleRate = uint32(*sampleRate) @@ -134,7 +143,7 @@ func main() { headerOrigin.CenterFrequency = *centerFreq } else if flagSeen["sz"] { if (*sampleSize == 16) || (*sampleSize == 24) { - headerOrigin.SampleSize = uint32(*sampleSize) + headerOrigin.SampleSize = uint32(*sampleSize) } else { fmt.Println("Incorrect sample size specified. Defaulting to 16") headerOrigin.SampleSize = 16 @@ -150,30 +159,30 @@ func main() { } else if *timeNow { headerOrigin.StartTimestamp = int64(time.Now().Unix()) } - + fmt.Println("\nHeader is now") printHeader(&headerOrigin) setCRC(&headerOrigin) fmt.Println("CRC32 :", headerOrigin.CRC32) fmt.Println("Output file:", *outFileStr) - + fo, err := os.Create(*outFileStr) check(err) - + defer func() { - err := fo.Close(); + err := fo.Close(); check(err) }() writer := bufio.NewWriter(fo) - + writeHeader(writer, &headerOrigin) copyContent(reader, writer, *blockSize) - + fmt.Println("\nCopy done") writer.Flush() } - + } else { fmt.Println("No input file given") } diff --git a/sdrbase/dsp/filerecord.h b/sdrbase/dsp/filerecord.h index 51edbca49..07248cea6 100644 --- a/sdrbase/dsp/filerecord.h +++ b/sdrbase/dsp/filerecord.h @@ -30,15 +30,17 @@ class Message; class SDRBASE_API FileRecord : public BasebandSampleSink { public: +#pragma pack(push, 1) struct Header { - quint32 sampleRate; + quint32 sampleRate; quint64 centerFrequency; quint64 startTimeStamp; quint32 sampleSize; quint32 filler; quint32 crc32; }; +#pragma pack(pop) FileRecord(); FileRecord(const QString& filename); From bfb7583544a26e6a10e1cbb548f77bf14da1c30e Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 9 Oct 2018 18:28:59 +0200 Subject: [PATCH 842/956] FileRecord improvement: unit test for the rescue program --- rescuesdriq/readme.md | 30 ++++++----- rescuesdriq/rescuesdriq.go | 94 ++++++++++++++++----------------- rescuesdriq/rescuesdriq_test.go | 26 +++++++++ 3 files changed, 90 insertions(+), 60 deletions(-) create mode 100644 rescuesdriq/rescuesdriq_test.go diff --git a/rescuesdriq/readme.md b/rescuesdriq/readme.md index 83a33ed93..6d2092bc0 100644 --- a/rescuesdriq/readme.md +++ b/rescuesdriq/readme.md @@ -12,9 +12,9 @@ The header is composed as follows: - Sample size as 16 or 24 bits (4 bytes, 32 bits) - filler with all zeroes (4 bytes, 32 bits) - CRC32 (IEEE) of the 28 bytes above (4 bytes, 32 bits) - -The header size is 32 bytes in total which is a multiple of 8 bytes thus occupies an integer number of samples whether in 16 or 24 bits mode. When migrating from a pre version 4.2.1 header you may crunch a very small amount of samples. - + +The header size is 32 bytes in total which is a multiple of 8 bytes thus occupies an integer number of samples whether in 16 or 24 bits mode. When migrating from a pre version 4.2.1 header you may crunch a very small amount of samples. + You can replace values in the header with the following options: - -sr uint @@ -28,21 +28,21 @@ You can replace values in the header with the following options: - -sz uint Sample size (16 or 24) (default 16) -You need to specify an input file. If no output file is specified the current header values are printed to the console and the program exits: - +You need to specify an input file. If no output file is specified the current header values are printed to the console and the program exits: + - -in string input file (default "foo") - + To convert to a new file you need to specify the output file: - -out string output file (default "foo") - -You can specify a block size in multiples of 4k for the copy. Large blocks will yield a faster copy but a larger output file. With the default of 1 (4k) the copy does not take much time anyway: - + +You can specify a block size in multiples of 4k for the copy. Large blocks will yield a faster copy but a larger output file. With the default of 1 (4k) the copy does not take much time anyway: + - -bz uint Copy block size in multiple of 4k (default 1) - +

    Build

    The program is written in go and is provided only in source code form. Compiling it is very easy: @@ -55,5 +55,11 @@ You will usually find a `golang` package in your distribution. For example in Ub In this directory just do `go build` - - \ No newline at end of file +

    Unit testing

    + +Unit test (very simple) is located in `rescuesdriq_test.go`. It uses the [Go Convey](https://github.com/smartystreets/goconvey) framework. You should first install it with: +`go get github.com/smartystreets/goconvey` + +You can run unit test from command line with: `go test` + +Or with the Go Convey server that you start from this directory with: `$GOPATH/bin/goconvey` where `$GOPATH` is the path to your go installation. diff --git a/rescuesdriq/rescuesdriq.go b/rescuesdriq/rescuesdriq.go index 6149140b1..adbfdf619 100644 --- a/rescuesdriq/rescuesdriq.go +++ b/rescuesdriq/rescuesdriq.go @@ -1,41 +1,39 @@ package main import ( + "bufio" + "bytes" + "encoding/binary" "flag" "fmt" - "bufio" - "io" - "os" - "bytes" - "encoding/binary" - "time" - "hash/crc32" + "hash/crc32" + "io" + "os" + "time" ) - type HeaderStd struct { - SampleRate uint32 - CenterFrequency uint64 - StartTimestamp int64 - SampleSize uint32 - Filler uint32 - CRC32 uint32 + SampleRate uint32 + CenterFrequency uint64 + StartTimestamp int64 + SampleSize uint32 + Filler uint32 + CRC32 uint32 } - func check(e error) { - if e != nil { - panic(e) - } + if e != nil { + panic(e) + } } func analyze(r *bufio.Reader) HeaderStd { - headerbuf := make([]byte, 32) // This is a full header with CRC + headerbuf := make([]byte, 32) // This is a full header with CRC n, err := r.Read(headerbuf) if err != nil && err != io.EOF { panic(err) } - if (n != 32) { + if n != 32 { panic("Header too small") } @@ -44,7 +42,7 @@ func analyze(r *bufio.Reader) HeaderStd { err = binary.Read(headerr, binary.LittleEndian, &header) check(err) - return header + return header } func writeHeader(writer *bufio.Writer, header *HeaderStd) { @@ -62,7 +60,7 @@ func setCRC(header *HeaderStd) { header.CRC32 = crc32.ChecksumIEEE(bin_buf.Bytes()[0:28]) } -func getCRC(header *HeaderStd) uint32 { +func GetCRC(header *HeaderStd) uint32 { var bin_buf bytes.Buffer header.Filler = 0 binary.Write(&bin_buf, binary.LittleEndian, header) @@ -70,13 +68,13 @@ func getCRC(header *HeaderStd) uint32 { } func printHeader(header *HeaderStd) { - fmt.Println("Sample rate:", header.SampleRate) - fmt.Println("Frequency :", header.CenterFrequency) - fmt.Println("Sample Size:", header.SampleSize) - tm := time.Unix(header.StartTimestamp, 0) - fmt.Println("Start :", tm) + fmt.Println("Sample rate:", header.SampleRate) + fmt.Println("Frequency :", header.CenterFrequency) + fmt.Println("Sample Size:", header.SampleSize) + tm := time.Unix(header.StartTimestamp, 0) + fmt.Println("Start :", tm) fmt.Println("CRC32 :", header.CRC32) - fmt.Println("CRC32 OK :", getCRC(header)) + fmt.Println("CRC32 OK :", GetCRC(header)) } func copyContent(reader *bufio.Reader, writer *bufio.Writer, blockSize uint) { @@ -97,7 +95,7 @@ func copyContent(reader *bufio.Reader, writer *bufio.Writer, blockSize uint) { } } else { writer.Write(p) - sz += int64(blockSize)*4096 + sz += int64(blockSize) * 4096 } fmt.Printf("Wrote %d bytes\r", sz) @@ -107,14 +105,14 @@ func copyContent(reader *bufio.Reader, writer *bufio.Writer, blockSize uint) { } func main() { - inFileStr := flag.String("in", "foo", "input file") + inFileStr := flag.String("in", "foo", "input file") outFileStr := flag.String("out", "foo", "output file") sampleRate := flag.Uint("sr", 0, "Sample rate (S/s)") centerFreq := flag.Uint64("cf", 0, "Center frequency (Hz)") sampleSize := flag.Uint("sz", 16, "Sample size (16 or 24)") - timeStr := flag.String("ts", "", "start time RFC3339 (ex: 2006-01-02T15:04:05Z)") - timeNow := flag.Bool("now", false , "use now for start time") - blockSize := flag.Uint("bz", 1, "Copy block size in multiple of 4k") + timeStr := flag.String("ts", "", "start time RFC3339 (ex: 2006-01-02T15:04:05Z)") + timeNow := flag.Bool("now", false, "use now for start time") + blockSize := flag.Uint("bz", 1, "Copy block size in multiple of 4k") flag.Parse() flagSeen := make(map[string]bool) @@ -123,16 +121,16 @@ func main() { if flagSeen["in"] { fmt.Println("input file :", *inFileStr) - // open input file - fi, err := os.Open(*inFileStr) - check(err) - // close fi on exit and check for its returned error - defer func() { - err := fi.Close(); - check(err) - }() - // make a read buffer - reader := bufio.NewReader(fi) + // open input file + fi, err := os.Open(*inFileStr) + check(err) + // close fi on exit and check for its returned error + defer func() { + err := fi.Close() + check(err) + }() + // make a read buffer + reader := bufio.NewReader(fi) var headerOrigin HeaderStd = analyze(reader) printHeader(&headerOrigin) @@ -150,7 +148,7 @@ func main() { } } else if flagSeen["ts"] { t, err := time.Parse(time.RFC3339, *timeStr) - if (err == nil) { + if err == nil { headerOrigin.StartTimestamp = t.Unix() } else { fmt.Println("Incorrect time specified. Defaulting to now") @@ -169,10 +167,10 @@ func main() { fo, err := os.Create(*outFileStr) check(err) - defer func() { - err := fo.Close(); - check(err) - }() + defer func() { + err := fo.Close() + check(err) + }() writer := bufio.NewWriter(fo) diff --git a/rescuesdriq/rescuesdriq_test.go b/rescuesdriq/rescuesdriq_test.go new file mode 100644 index 000000000..3046a2c1b --- /dev/null +++ b/rescuesdriq/rescuesdriq_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestSpec(t *testing.T) { + + // Only pass t into top-level Convey calls + Convey("Given a header structure", t, func() { + var header HeaderStd + header.SampleRate = 75000 + header.CenterFrequency = 435000000 + header.StartTimestamp = 1539083921 + header.SampleSize = 16 + header.Filler = 0 + + crc32 := GetCRC(&header) + + Convey("The CRC32 value should be 2294957931", func() { + So(crc32, ShouldEqual, 2294957931) + }) + }) +} From 4032d62b3df74092e9d9dac97a0d916103c1ce73 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 9 Oct 2018 18:52:39 +0200 Subject: [PATCH 843/956] FileRecord improvement: added visual indicator for CRC check in the GUI --- .../samplesource/filesource/filesourcegui.cpp | 13 +++++++++++ .../samplesource/filesource/filesourcegui.ui | 22 +++++++++++++++++++ .../filesource/filesourceinput.cpp | 6 +++++ .../samplesource/filesource/filesourceinput.h | 19 ++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index 8bcedc3af..de56a6d5f 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -57,6 +57,7 @@ FileSourceGui::FileSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, 0, pow(10,7)); ui->fileNameText->setText(m_fileName); + ui->crcLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); @@ -198,6 +199,17 @@ bool FileSourceGui::handleMessage(const Message& message) return true; } + else if (FileSourceInput::MsgReportHeaderCRC::match(message)) + { + FileSourceInput::MsgReportHeaderCRC& notif = (FileSourceInput::MsgReportHeaderCRC&) message; + if (notif.isOK()) { + ui->crcLabel->setStyleSheet("QLabel { background-color : green; }"); + } else { + ui->crcLabel->setStyleSheet("QLabel { background-color : red; }"); + } + + return true; + } else { return false; @@ -292,6 +304,7 @@ void FileSourceGui::on_showFileDialog_clicked(bool checked __attribute__((unused { m_fileName = fileName; ui->fileNameText->setText(m_fileName); + ui->crcLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); configureFileName(); } } diff --git a/plugins/samplesource/filesource/filesourcegui.ui b/plugins/samplesource/filesource/filesourcegui.ui index f6a01558a..a634995e8 100644 --- a/plugins/samplesource/filesource/filesourcegui.ui +++ b/plugins/samplesource/filesource/filesourcegui.ui @@ -287,6 +287,28 @@ + + + + Qt::Vertical + + + + + + + + 8 + + + + CRC status: Green: OK Red: KO Grey: undefined + + + CRC + + + diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 636a9577b..053eadac8 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -43,6 +43,7 @@ MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgStartStop, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceAcquisition, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportHeaderCRC, Message) FileSourceInput::FileSourceInput(DeviceSourceAPI *deviceAPI) : m_deviceAPI(deviceAPI), @@ -105,6 +106,11 @@ void FileSourceInput::openFileStream() qCritical("FileSourceInput::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex)); m_recordLength = 0; } + + if (getMessageQueueToGUI()) { + MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(crcOK); + getMessageQueueToGUI()->push(report); + } } else { diff --git a/plugins/samplesource/filesource/filesourceinput.h b/plugins/samplesource/filesource/filesourceinput.h index 38c160261..4f149da7e 100644 --- a/plugins/samplesource/filesource/filesourceinput.h +++ b/plugins/samplesource/filesource/filesourceinput.h @@ -228,6 +228,25 @@ public: { } }; + class MsgReportHeaderCRC : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isOK() const { return m_ok; } + + static MsgReportHeaderCRC* create(bool ok) { + return new MsgReportHeaderCRC(ok); + } + + protected: + bool m_ok; + + MsgReportHeaderCRC(bool ok) : + Message(), + m_ok(ok) + { } + }; + FileSourceInput(DeviceSourceAPI *deviceAPI); virtual ~FileSourceInput(); virtual void destroy(); From ef1e9c2b257a9ed13ea6f9158a75c838d01dd980 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 9 Oct 2018 23:15:59 +0200 Subject: [PATCH 844/956] File source: added documentation. Bumped version --- Readme.md | 2 +- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 4 +- doc/img/FileSource_plugin.png | Bin 0 -> 22810 bytes doc/img/FileSource_plugin.xcf | Bin 0 -> 98617 bytes .../bladerf2output/bladerf2outputplugin.cpp | 2 +- .../bladerf2input/bladerf2inputplugin.cpp | 2 +- .../filesource/filesourceplugin.cpp | 2 +- plugins/samplesource/filesource/readme.md | 106 ++++++++++++++++++ 11 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 doc/img/FileSource_plugin.png create mode 100644 doc/img/FileSource_plugin.xcf create mode 100644 plugins/samplesource/filesource/readme.md diff --git a/Readme.md b/Readme.md index 49919f2c7..90d3fa11c 100644 --- a/Readme.md +++ b/Readme.md @@ -263,7 +263,7 @@ This is the `demoddsd` plugin. At present it can be used to decode the following It is based on the [DSDcc](https://github.com/f4exb/dsdcc) C++ library which is a rewrite of the original [DSD](https://github.com/szechyjs/dsd) program. So you will need to have DSDcc installed in your system. Please follow instructions in [DSDcc readme](https://github.com/f4exb/dsdcc/blob/master/Readme.md) to build and install DSDcc. If you install it in a custom location say `/opt/install/dsdcc` you will need to add these defines to the cmake command: `-DLIBDSDCC_INCLUDE_DIR=/opt/install/dsdcc/include/dsdcc -DLIBDSDCC_LIBRARIES=/opt/install/dsdcc/lib/libdsdcc.so` -If you have one or more serial devices interfacing the AMBE3000 chip in packet mode you can use them to decode AMBE voice frames. For that purpose you will need to compile with [SerialDV](https://github.com/f4exb/serialDV) support. Please refer to this project Readme.md to compile and install SerialDV. If you install it in a custom location say `/opt/install/serialdv` you will need to add these defines to the cmake command: `-DLIBSERIALDV_INCLUDE_DIR=/opt/install/serialdv/include/serialdv -DLIBSERIALDV_LIBRARY=/opt/install/serialdv/lib/libserialdv.so` Also your user must be a member of group `dialout` to be able to use the dongle. +If you have one or more serial devices interfacing the AMBE3000 chip in packet mode you can use them to decode AMBE voice frames. For that purpose you will need to compile with [SerialDV](https://github.com/f4exb/serialDV) support. Please refer to this project Readme.md to compile and install SerialDV. If you install it in a custom location say `/opt/install/serialdv` you will need to add these defines to the cmake command: `-DLIBSERIALDV_INCLUDE_DIR=/opt/install/serialdv/include/serialdv -DLIBSERIALDV_LIBRARY=/opt/install/serialdv/lib/libserialdv.so` Also your user must be a member of group `dialout` (Ubuntu/Debian) or `uucp` (Arch) to be able to use the dongle. Although such serial devices work with a serial interface at 400 kb in practice maybe for other reasons they are capable of handling only one conversation at a time. The software will allocate the device dynamically to a conversation with an inactivity timeout of 1 second so that conversations do not get interrupted constantly making the audio output too choppy. In practice you will have to have as many devices connected to your system as the number of conversations you would like to be handled in parallel. diff --git a/app/main.cpp b/app/main.cpp index 464499d50..a0fddcdf5 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.2.0"); + QCoreApplication::setApplicationVersion("4.2.1"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 61eca0e2b..0ec5d274a 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.2.0"); + QCoreApplication::setApplicationVersion("4.2.1"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 6230ef960..8141b79ea 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.2.0"); + QCoreApplication::setApplicationVersion("4.2.1"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 4d864899b..a977768c6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,9 @@ sdrangel (4.2.1-1) unstable; urgency=medium * FileRecord improvement with robust header and some fixes. Fixes issue #206 - * BladeRF2 MO Tx fix so that the two channels are used effectively + * BladeRF2 MO Tx fix so that the two channels are used effectively. Fixes issue #225 - -- Edouard Griffiths, F4EXB Sun, 14 Oct 2018 21:14:18 +0200 + -- Edouard Griffiths, F4EXB Wed, 10 Oct 2018 21:14:18 +0200 sdrangel (4.2.0-1) unstable; urgency=medium diff --git a/doc/img/FileSource_plugin.png b/doc/img/FileSource_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..3f45840f9eed598069c9b78c647541e15a5d2512 GIT binary patch literal 22810 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 -When phosphor display is engaged (4.C) and stroke decay is 1 (4.7) this divides the unit decay by this value by diminishing histogram pixel value by one each time a decay divisor of FFTs have been produced. So actual decay rate is 1 over this value. +When phosphor display is engaged (4.C) and stroke decay is 1 (4.7) this divides the unit decay by this value by diminishing histogram pixel value by one each time a number of FFTs equal to this number have been produced. Thus the actual decay rate is 1 over this value. This allow setting a slower decay rate than one unit for each new FFT.

    4.9. Phosphor display stroke strength

    From 9008f26fc3430e74f9747b0a7a8bb79270f7e7d0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 20 Oct 2018 19:20:11 +0200 Subject: [PATCH 881/956] Spectrum: reworked histogram palette --- sdrgui/gui/glspectrum.cpp | 44 ++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 1b7ee8940..a0e5fafe6 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -86,23 +86,37 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_waterfallPalette[239] = 0xffffffff; m_histogramPalette[0] = 0; - for(int i = 16; i < 240; i++) { - QColor c; - c.setHsv(239 - i, 255 - ((i < 200) ? 0 : (i - 200) * 3), 150 + ((i < 100) ? i : 100)); - ((quint8*)&m_histogramPalette[i])[0] = c.red(); - ((quint8*)&m_histogramPalette[i])[1] = c.green(); - ((quint8*)&m_histogramPalette[i])[2] = c.blue(); - ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); - } - for(int i = 1; i < 16; i++) { - QColor c; - c.setHsv(255, 128, 48 + i * 4); - ((quint8*)&m_histogramPalette[i])[0] = c.red(); - ((quint8*)&m_histogramPalette[i])[1] = c.green(); - ((quint8*)&m_histogramPalette[i])[2] = c.blue(); - ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); + + for (int i = 1; i < 240; i++) + { + QColor c; + int val = i < 60 ? 255 : 200; + int sat = i < 60 ? 128 : i < 180 ? 255 : 180; + c.setHsv(239 - i, sat, val); + ((quint8*)&m_histogramPalette[i])[0] = c.red(); + ((quint8*)&m_histogramPalette[i])[1] = c.green(); + ((quint8*)&m_histogramPalette[i])[2] = c.blue(); + ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); } + // Original palette: +// for(int i = 16; i < 240; i++) { +// QColor c; +// c.setHsv(239 - i, 255 - ((i < 200) ? 0 : (i - 200) * 3), 150 + ((i < 100) ? i : 100)); +// ((quint8*)&m_histogramPalette[i])[0] = c.red(); +// ((quint8*)&m_histogramPalette[i])[1] = c.green(); +// ((quint8*)&m_histogramPalette[i])[2] = c.blue(); +// ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); +// } +// for(int i = 1; i < 16; i++) { +// QColor c; +// c.setHsv(255, 128, 48 + i * 4); +// ((quint8*)&m_histogramPalette[i])[0] = c.red(); +// ((quint8*)&m_histogramPalette[i])[1] = c.green(); +// ((quint8*)&m_histogramPalette[i])[2] = c.blue(); +// ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); +// } + m_decayDivisor = 1; m_decayDivisorCount = m_decayDivisor; m_histogramStroke = 30; From 67de0d9c5752855075f4ede0659216c23fca3d20 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 20 Oct 2018 19:28:27 +0200 Subject: [PATCH 882/956] Spectrum: updated documentation --- sdrgui/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 5a56fa4e3..10f0cc163 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -288,7 +288,7 @@ When phosphor display is engaged (4.C) and stroke decay is 1 (4.7) this divides

    4.9. Phosphor display stroke strength

    -This controls the stroke strength when phosphor display is engaged (4.C). +This controls the stroke strength when phosphor display is engaged (4.C). The histogram value is incremented by this value at each new FFT until the maximum (red) is reached.

    4.A. Trace intensity

    From eed10172f42cac0c311ceac67193ce4500f50f7d Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 22 Oct 2018 14:51:47 +0200 Subject: [PATCH 883/956] DSD demod: changed squelch 0.1 dB steps to 1 dB steps --- plugins/channelrx/demoddsd/dsddemodgui.cpp | 8 ++++---- plugins/channelrx/demoddsd/dsddemodgui.ui | 14 ++++---------- plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- plugins/channelrx/demoddsd/dsddemodsettings.cpp | 6 +++--- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index e299c5b5b..514a3e333 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -236,8 +236,8 @@ void DSDDemodGUI::on_squelchGate_valueChanged(int value) void DSDDemodGUI::on_squelch_valueChanged(int value) { - ui->squelchText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); - m_settings.m_squelch = value / 10.0; + ui->squelchText->setText(QString("%1").arg(value / 1.0, 0, 'f', 0)); + m_settings.m_squelch = value; applySettings(); } @@ -413,8 +413,8 @@ void DSDDemodGUI::displaySettings() ui->fmDeviation->setValue(m_settings.m_fmDeviation / 100.0); ui->fmDeviationText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(ui->fmDeviation->value() / 10.0, 0, 'f', 1)); - ui->squelch->setValue(m_settings.m_squelch * 10.0); - ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 10.0, 0, 'f', 1)); + ui->squelch->setValue(m_settings.m_squelch); + ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 1.0, 0, 'f', 0)); ui->squelchGate->setValue(m_settings.m_squelchGate); ui->squelchGateText->setText(QString("%1").arg(ui->squelchGate->value() * 10.0, 0, 'f', 0)); diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index f3a2fb54f..03548e69c 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -399,7 +399,7 @@ Squelch threshold (dB) - -1000 + -100 0 @@ -411,7 +411,7 @@ 1 - -150 + -40 @@ -419,21 +419,15 @@ - 0 + 30 0 - - - 40 - 16777215 - - Squelch threshold (dB) - -15.0 + -100 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index d10957be0..0d14131da 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("4.1.0"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index ee2734137..d73e029dd 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -61,7 +61,7 @@ QByteArray DSDDemodSettings::serialize() const s.writeS32(2, m_rfBandwidth/100.0); s.writeS32(3, m_demodGain*100.0); s.writeS32(4, m_fmDeviation/100.0); - s.writeS32(5, m_squelch*10.0); + s.writeS32(5, m_squelch); s.writeU32(7, m_rgbColor); s.writeS32(8, m_squelchGate); s.writeS32(9, m_volume*10.0); @@ -120,8 +120,8 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) m_demodGain = tmp / 100.0; d.readS32(4, &tmp, 50); m_fmDeviation = tmp * 100.0; - d.readS32(5, &tmp, -400); - m_squelch = tmp / 10.0; + d.readS32(5, &tmp, -40); + m_squelch = tmp < -100 ? tmp / 10.0 : tmp; d.readU32(7, &m_rgbColor); d.readS32(8, &m_squelchGate, 5); d.readS32(9, &tmp, 20); From 4b8461981e04f0343f1bcbbea9a8dbad15e7dd8a Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 22 Oct 2018 14:52:01 +0200 Subject: [PATCH 884/956] Bumped version to 4.2.4 --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- plugins/samplesink/limesdroutput/limesdroutputplugin.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinputplugin.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 56057b262..aeca0f296 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.2.3"); + QCoreApplication::setApplicationVersion("4.2.4"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index d6c45498f..055fdb98c 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.2.3"); + QCoreApplication::setApplicationVersion("4.2.4"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 79591e924..0c7f89079 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.2.3"); + QCoreApplication::setApplicationVersion("4.2.4"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp index 2783fe9cb..1c4d765e9 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp @@ -34,7 +34,7 @@ const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = { QString("LimeSDR Output"), - QString("4.0.7"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 713bb048a..a61ed225c 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("4.0.7"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From c00f658286720b2a278e9d65c07925505d6d24ba Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 22 Oct 2018 14:56:09 +0200 Subject: [PATCH 885/956] LimeSuite 18.10.0: Windows build fixes --- liblimesuite/liblimesuite.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/liblimesuite/liblimesuite.pro b/liblimesuite/liblimesuite.pro index 133c0b908..da6c4e0d8 100644 --- a/liblimesuite/liblimesuite.pro +++ b/liblimesuite/liblimesuite.pro @@ -25,6 +25,7 @@ CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" #CONFIG(MINGW64):INCLUDEPATH += "..\libsqlite3\src" INCLUDEPATH += $$LIBLIMESUITESRC/src +INCLUDEPATH += $$LIBLIMESUITESRC/src/lime INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 INCLUDEPATH += $$LIBLIMESUITESRC/src/ConnectionRegistry INCLUDEPATH += $$LIBLIMESUITESRC/src/FPGA_common From f813c0134e9a66018acf66989fb86347a2bfb736 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 22 Oct 2018 18:21:09 +0200 Subject: [PATCH 886/956] LimeSuite 18.10.0: adapted Debian build --- debian/changelog | 7 +++++++ liblimesuite/CMakeLists.txt | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index e286444e9..f88a1fb45 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +sdrangel (4.2.4-1) unstable; urgency=medium + + * LimeSDR: use LimeSuite 18.10.0 for builds + * DSD demod: use 1 dB steps for squelch + + -- Edouard Griffiths, F4EXB Wed, 24 Oct 2018 21:14:18 +0200 + sdrangel (4.2.3-1) unstable; urgency=medium * Scope: fixed channel rate affecting scope in memory mode. Issue #227 diff --git a/liblimesuite/CMakeLists.txt b/liblimesuite/CMakeLists.txt index 70589b041..36f6662f9 100644 --- a/liblimesuite/CMakeLists.txt +++ b/liblimesuite/CMakeLists.txt @@ -47,7 +47,8 @@ set(limesuite_SOURCES ) set(limesuite_HEADERS - ${LIBLIMESUITESRC}/src/API/*.h + ${LIBLIMESUITESRC}/src/lime/*.h + ${LIBLIMESUITESRC}/src/API/*.h ${LIBLIMESUITESRC}/src/GFIR/*.h ${LIBLIMESUITESRC}/src/protocols/*.h ${LIBLIMESUITESRC}/src/ConnectionRegistry/*.h @@ -64,6 +65,7 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${LIBUSB_INCLUDE_DIR} ${LIBLIMESUITESRC}/src + ${LIBLIMESUITESRC}/src/lime ${LIBLIMESUITESRC}/src/API ${LIBLIMESUITESRC}/src/GFIR ${LIBLIMESUITESRC}/src/protocols From 30e36157a8d8eb179b625fa20f2973199952f041 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 23 Oct 2018 14:34:56 +0200 Subject: [PATCH 887/956] Test source: basic pulse test pattern --- .../samplesource/testsource/testsourcegui.ui | 5 ++ .../testsource/testsourceplugin.cpp | 2 +- .../testsource/testsourcesettings.h | 1 + .../testsource/testsourcethread.cpp | 47 +++++++++++++++++++ .../testsource/testsourcethread.h | 5 ++ 5 files changed, 59 insertions(+), 1 deletion(-) diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui index 31f2bbd58..90fb1f795 100644 --- a/plugins/samplesource/testsource/testsourcegui.ui +++ b/plugins/samplesource/testsource/testsourcegui.ui @@ -480,6 +480,11 @@ FM + + + Pul + + diff --git a/plugins/samplesource/testsource/testsourceplugin.cpp b/plugins/samplesource/testsource/testsourceplugin.cpp index ac8dc459d..ad19169ad 100644 --- a/plugins/samplesource/testsource/testsourceplugin.cpp +++ b/plugins/samplesource/testsource/testsourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor TestSourcePlugin::m_pluginDescriptor = { QString("Test Source input"), - QString("4.1.0"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/testsource/testsourcesettings.h b/plugins/samplesource/testsource/testsourcesettings.h index 6d39c22cf..37dd52797 100644 --- a/plugins/samplesource/testsource/testsourcesettings.h +++ b/plugins/samplesource/testsource/testsourcesettings.h @@ -37,6 +37,7 @@ struct TestSourceSettings { ModulationNone, ModulationAM, ModulationFM, + ModulationPulse, ModulationLast } Modulation; diff --git a/plugins/samplesource/testsource/testsourcethread.cpp b/plugins/samplesource/testsource/testsourcethread.cpp index 361a9a04c..432a5aa12 100644 --- a/plugins/samplesource/testsource/testsourcethread.cpp +++ b/plugins/samplesource/testsource/testsourcethread.cpp @@ -38,6 +38,11 @@ TestSourceThread::TestSourceThread(SampleSinkFifo* sampleFifo, QObject* parent) m_amModulation(0.5f), m_fmDeviationUnit(0.0f), m_fmPhasor(0.0f), + m_pulseWidth(150), + m_pulseSampleCount(0), + m_pulsePatternCount(0), + m_pulsePatternCycle(8), + m_pulsePatternPlaces(3), m_samplerate(48000), m_log2Decim(4), m_fcPos(0), @@ -262,6 +267,48 @@ void TestSourceThread::generate(quint32 chunksize) m_buf[i++] = (int16_t) (im * (float) m_amplitudeBitsQ); } break; + case TestSourceSettings::ModulationPulse: + { + if (m_pulseSampleCount < m_pulseWidth) // sync pattern: 0 + { + m_buf[i++] = m_amplitudeBitsDC; + m_buf[i++] = 0; + } + else if (m_pulseSampleCount < 2*m_pulseWidth) // sync pattern: 1 + { + m_buf[i++] = (int16_t) (m_amplitudeBitsI + m_amplitudeBitsDC); + m_buf[i++] = (int16_t) (m_phaseImbalance * (float) m_amplitudeBitsQ); + } + else if (m_pulseSampleCount < 3*m_pulseWidth) // sync pattern: 0 + { + m_buf[i++] = m_amplitudeBitsDC; + m_buf[i++] = 0; + } + else if (m_pulseSampleCount < (3+m_pulsePatternPlaces)*m_pulseWidth) // binary pattern + { + uint32_t patPulseSampleCount = m_pulseSampleCount - 3*m_pulseWidth; + uint32_t patPulseIndex = patPulseSampleCount / m_pulseWidth; + float patFigure = (m_pulsePatternCount & (1< Date: Tue, 23 Oct 2018 22:38:16 +0200 Subject: [PATCH 888/956] Spectrum: reworked max hold --- sdrgui/gui/glspectrum.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index a0e5fafe6..c7945455d 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -798,36 +798,37 @@ void GLSpectrum::paintGL() // paint max hold lines on top of histogram if (m_displayMaxHold) { - if (m_maxHold.size() < (uint)m_fftSize) - m_maxHold.resize(m_fftSize); + if (m_maxHold.size() < (uint) m_fftSize) { + m_maxHold.resize(m_fftSize); + } - for(int i = 0; i < m_fftSize; i++) + for (int i = 0; i < m_fftSize; i++) { int j; quint8* bs = m_histogram + i * 100; - for(j = 99; j > 1; j--) + for (j = 99; j >= 0; j--) { - if(bs[j] > 0) { + if (bs[j] > 0) { break; } } - j = j - 99; - m_maxHold[i] = (j * m_powerRange) / 99.0 + m_referenceLevel; + // m_referenceLevel : top + // m_referenceLevel - m_powerRange : bottom + m_maxHold[i] = ((j - 99) * m_powerRange) / 99.0 + m_referenceLevel; } { GLfloat *q3 = m_q3FFT.m_array; - Real bottom = -m_powerRange; - for(int i = 0; i < m_fftSize; i++) + for (int i = 0; i < m_fftSize; i++) { Real v = m_maxHold[i] - m_referenceLevel; - if(v > 0) { + if (v >= 0) { v = 0; - } else if(v < bottom) { - v = bottom; + } else if (v < -m_powerRange) { + v = -m_powerRange; } q3[2*i] = (Real) i; From faf428ed3d628c35598b6e5d581b4da7fc8b4cae Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 24 Oct 2018 00:10:12 +0200 Subject: [PATCH 889/956] Test source: added sawtooth pattern --- .../samplesource/testsource/testsourcegui.ui | 7 +++- .../testsource/testsourceinput.cpp | 9 ++++- .../testsource/testsourcesettings.h | 3 +- .../testsource/testsourcethread.cpp | 34 +++++++++++++++++-- .../testsource/testsourcethread.h | 2 ++ 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui index 90fb1f795..2abaa5717 100644 --- a/plugins/samplesource/testsource/testsourcegui.ui +++ b/plugins/samplesource/testsource/testsourcegui.ui @@ -482,7 +482,12 @@ - Pul + P0 + + + + + P1 diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 94efed087..6a5509481 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -354,8 +354,15 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, bool for if ((m_settings.m_modulation != settings.m_modulation) || force) { - if (m_testSourceThread != 0) { + if (m_testSourceThread != 0) + { m_testSourceThread->setModulation(settings.m_modulation); + + if (settings.m_modulation == TestSourceSettings::ModulationPattern0) { + m_testSourceThread->setPattern0(); + } else if (settings.m_modulation == TestSourceSettings::ModulationPattern1) { + m_testSourceThread->setPattern1(); + } } } diff --git a/plugins/samplesource/testsource/testsourcesettings.h b/plugins/samplesource/testsource/testsourcesettings.h index 37dd52797..9c1c8f3fe 100644 --- a/plugins/samplesource/testsource/testsourcesettings.h +++ b/plugins/samplesource/testsource/testsourcesettings.h @@ -37,7 +37,8 @@ struct TestSourceSettings { ModulationNone, ModulationAM, ModulationFM, - ModulationPulse, + ModulationPattern0, + ModulationPattern1, ModulationLast } Modulation; diff --git a/plugins/samplesource/testsource/testsourcethread.cpp b/plugins/samplesource/testsource/testsourcethread.cpp index 432a5aa12..5f37d726f 100644 --- a/plugins/samplesource/testsource/testsourcethread.cpp +++ b/plugins/samplesource/testsource/testsourcethread.cpp @@ -267,7 +267,7 @@ void TestSourceThread::generate(quint32 chunksize) m_buf[i++] = (int16_t) (im * (float) m_amplitudeBitsQ); } break; - case TestSourceSettings::ModulationPulse: + case TestSourceSettings::ModulationPattern0: // binary pattern { if (m_pulseSampleCount < m_pulseWidth) // sync pattern: 0 { @@ -288,7 +288,7 @@ void TestSourceThread::generate(quint32 chunksize) { uint32_t patPulseSampleCount = m_pulseSampleCount - 3*m_pulseWidth; uint32_t patPulseIndex = patPulseSampleCount / m_pulseWidth; - float patFigure = (m_pulsePatternCount & (1< Date: Wed, 24 Oct 2018 01:05:49 +0200 Subject: [PATCH 890/956] Spectrum: enhanced (again) the histogram (phosphor) palette --- sdrgui/gui/glspectrum.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index c7945455d..eeeff9fb2 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -90,16 +90,29 @@ GLSpectrum::GLSpectrum(QWidget* parent) : for (int i = 1; i < 240; i++) { QColor c; - int val = i < 60 ? 255 : 200; - int sat = i < 60 ? 128 : i < 180 ? 255 : 180; - c.setHsv(239 - i, sat, val); + int val = i < 60 ? 128 + (60-i) : 128; + int sat = i < 60 ? 140 + i : i < 180 ? 200 : 200 - (i-180); + c.setHsl(239 - i, sat, val); ((quint8*)&m_histogramPalette[i])[0] = c.red(); ((quint8*)&m_histogramPalette[i])[1] = c.green(); ((quint8*)&m_histogramPalette[i])[2] = c.blue(); ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); } - // Original palette: + // 4.2.3 palette +// for (int i = 1; i < 240; i++) +// { +// QColor c; +// int val = i < 60 ? 255 : 200; +// int sat = i < 60 ? 128 : i < 180 ? 255 : 180; +// c.setHsv(239 - i, sat, val); +// ((quint8*)&m_histogramPalette[i])[0] = c.red(); +// ((quint8*)&m_histogramPalette[i])[1] = c.green(); +// ((quint8*)&m_histogramPalette[i])[2] = c.blue(); +// ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); +// } + + // Original palette: // for(int i = 16; i < 240; i++) { // QColor c; // c.setHsv(239 - i, 255 - ((i < 200) ? 0 : (i - 200) * 3), 150 + ((i < 100) ? i : 100)); From 309693469a45094884581477cb1e5334ae80bf32 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 24 Oct 2018 08:29:49 +0200 Subject: [PATCH 891/956] Test source: updated documentation --- plugins/samplesource/testsource/readme.md | 7 +++++++ sdrgui/gui/glspectrum.cpp | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/samplesource/testsource/readme.md b/plugins/samplesource/testsource/readme.md index 8f64bffbd..8ddcb39df 100644 --- a/plugins/samplesource/testsource/readme.md +++ b/plugins/samplesource/testsource/readme.md @@ -82,6 +82,13 @@ This controls the generator sample rate in samples per second. - **No**: No modulation - **AM**: Amplitude modulation (AM) - **FM**: Frequency modulation (FM) + - **P0**: Pattern 0 is a binary pattern + - Pulse width: 150 samples + - Sync pattern: 010 at full amplitude + - Binary pattern LSB first on 3 bits from 0 to 7 at 0.3 amplitude + - **P1**: Pattern 1 is a sawtooth pattern + - Pulse width: 1000 samples + - Starts at full amplitude then amplitude decreases linearly down to zero

    5: Modulating tone frequency

    diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index eeeff9fb2..f7d9767d4 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -90,9 +90,9 @@ GLSpectrum::GLSpectrum(QWidget* parent) : for (int i = 1; i < 240; i++) { QColor c; - int val = i < 60 ? 128 + (60-i) : 128; - int sat = i < 60 ? 140 + i : i < 180 ? 200 : 200 - (i-180); - c.setHsl(239 - i, sat, val); + int light = i < 60 ? 128 + (60-i) : 128; + int sat = i < 60 ? 140 + i : i < 180 ? 200 : 200 - (i-180); + c.setHsl(239 - i, sat, light); ((quint8*)&m_histogramPalette[i])[0] = c.red(); ((quint8*)&m_histogramPalette[i])[1] = c.green(); ((quint8*)&m_histogramPalette[i])[2] = c.blue(); From ea328ca85e96beabcc75df8d49ea25b9eddf244b Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 24 Oct 2018 13:21:03 +0200 Subject: [PATCH 892/956] Scope: trigger delay optimization --- sdrgui/dsp/scopevis.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 5ac6a2e10..6761126ce 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -325,8 +325,8 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa { if (triggerCondition->m_triggerDelayCount > 0) // skip samples during delay period { - triggerCondition->m_triggerDelayCount--; - ++begin; + begin += triggerCondition->m_triggerDelayCount; + triggerCondition->m_triggerDelayCount = 0; continue; } else // process trigger From 02d0a46f3118a6afa70a590d386dccac4b1f5714 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 24 Oct 2018 13:54:33 +0200 Subject: [PATCH 893/956] Scope: removed breaking optimization thus fixing trigger. Should fix issue #233 --- plugins/channelrx/chanalyzer/chanalyzerplugin.cpp | 2 +- plugins/channelrx/demodatv/atvdemodplugin.cpp | 2 +- sdrgui/dsp/scopevis.cpp | 14 +++++--------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index f25605b6d..8dcb43823 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("4.2.3"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodatv/atvdemodplugin.cpp b/plugins/channelrx/demodatv/atvdemodplugin.cpp index 66b78641a..f728462f5 100644 --- a/plugins/channelrx/demodatv/atvdemodplugin.cpp +++ b/plugins/channelrx/demodatv/atvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor ATVDemodPlugin::m_ptrPluginDescriptor = { QString("ATV Demodulator"), - QString("4.2.3"), + QString("4.2.4"), QString("(c) F4HKW for F4EXB / SDRAngel"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 6761126ce..3cbb97052 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -368,7 +368,7 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa } else // this was the last trigger then start trace { - m_traceStart = true; // start trace processing + m_traceStart = true; // start of trace processing m_nbSamples = m_traceSize + m_maxTraceDelay; m_triggerComparator.reset(); m_triggerState = TriggerTriggered; @@ -378,15 +378,11 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa } ++begin; - } - } - } + } // look for trigger + } // untriggered or delayed + } // triggering active // trace process - if (m_glScope->getDataChanged()) // optimization: process trace only if required by glScope - { - m_triggerState = TriggerUntriggered; - } if (m_triggerState == TriggerTriggered) { @@ -395,7 +391,7 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa SampleVector::iterator mend = m_traceDiscreteMemory.current().current(); SampleVector::iterator mbegin = mend - count; - if (m_traceStart) + if (m_traceStart) // start of trace processing { // trace back if (m_maxTraceDelay > 0) From acbaa14dbdce7729a243ef93406dfe215f874100 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 25 Oct 2018 13:12:49 +0200 Subject: [PATCH 894/956] Scope: some code refactoring to make it more elegant. Updated Debian changelog --- debian/changelog | 1 + sdrgui/dsp/scopevis.cpp | 90 ++++++++++++++++++++--------------------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/debian/changelog b/debian/changelog index f88a1fb45..21a540f07 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,6 +2,7 @@ sdrangel (4.2.4-1) unstable; urgency=medium * LimeSDR: use LimeSuite 18.10.0 for builds * DSD demod: use 1 dB steps for squelch + * Scope: fixed some trigger issues. Fixes issue #233 -- Edouard Griffiths, F4EXB Wed, 24 Oct 2018 21:14:18 +0200 diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 3cbb97052..203cf3fe8 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -313,62 +313,32 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa m_triggerState = TriggerTriggered; } } - else + else if ((m_triggerState == TriggerUntriggered) || (m_triggerState == TriggerDelay)) // look for trigger or past trigger in delay mode { - if ((m_triggerState == TriggerUntriggered) || (m_triggerState == TriggerDelay)) + TriggerCondition* triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition + + while (begin < end) { - TriggerCondition* triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition - - while (begin < end) + if (m_triggerState == TriggerDelay) // delayed trigger { - if (m_triggerState == TriggerDelay) + if (triggerCondition->m_triggerDelayCount > 0) // skip samples during delay period { - if (triggerCondition->m_triggerDelayCount > 0) // skip samples during delay period - { - begin += triggerCondition->m_triggerDelayCount; - triggerCondition->m_triggerDelayCount = 0; - continue; - } - else // process trigger - { - if (nextTrigger()) // move to next trigger and keep going - { - m_triggerComparator.reset(); - m_triggerState = TriggerUntriggered; - ++begin; - continue; - } - else // this was the last trigger then start trace - { - m_traceStart = true; // start trace processing - m_nbSamples = m_traceSize + m_maxTraceDelay; - m_triggerComparator.reset(); - m_triggerState = TriggerTriggered; - triggerPointToEnd = end - begin; - break; - } - } + begin += triggerCondition->m_triggerDelayCount; + triggerCondition->m_triggerDelayCount = 0; + continue; } - - // look for trigger - if (m_triggerComparator.triggered(*begin, *triggerCondition)) + else // process trigger { - if (triggerCondition->m_triggerData.m_triggerDelay > 0) - { - triggerCondition->m_triggerDelayCount = triggerCondition->m_triggerData.m_triggerDelay; // initialize delayed samples counter - m_triggerState = TriggerDelay; - ++begin; - continue; - } - if (nextTrigger()) // move to next trigger and keep going { m_triggerComparator.reset(); m_triggerState = TriggerUntriggered; + ++begin; + continue; } else // this was the last trigger then start trace { - m_traceStart = true; // start of trace processing + m_traceStart = true; // start trace processing m_nbSamples = m_traceSize + m_maxTraceDelay; m_triggerComparator.reset(); m_triggerState = TriggerTriggered; @@ -376,11 +346,37 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa break; } } + } - ++begin; - } // look for trigger - } // untriggered or delayed - } // triggering active + if (m_triggerComparator.triggered(*begin, *triggerCondition)) // matched the current trigger + { + if (triggerCondition->m_triggerData.m_triggerDelay > 0) + { + triggerCondition->m_triggerDelayCount = triggerCondition->m_triggerData.m_triggerDelay; // initialize delayed samples counter + m_triggerState = TriggerDelay; + ++begin; + continue; + } + + if (nextTrigger()) // move to next trigger and keep going + { + m_triggerComparator.reset(); + m_triggerState = TriggerUntriggered; + } + else // this was the last trigger then start trace + { + m_traceStart = true; // start of trace processing + m_nbSamples = m_traceSize + m_maxTraceDelay; + m_triggerComparator.reset(); + m_triggerState = TriggerTriggered; + triggerPointToEnd = end - begin; + break; + } + } + + ++begin; + } // look for trigger + } // untriggered or delayed // trace process From f43c07b9e5cdcfc24b01b1fff342da9355c6efa3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 25 Oct 2018 13:53:58 +0200 Subject: [PATCH 895/956] Test source: added a square pattern --- plugins/samplesource/testsource/readme.md | 29 ++++++++++--------- .../samplesource/testsource/testsourcegui.ui | 5 ++++ .../testsource/testsourceinput.cpp | 2 ++ .../testsource/testsourcesettings.h | 1 + .../testsource/testsourcethread.cpp | 24 +++++++++++++++ .../testsource/testsourcethread.h | 1 + 6 files changed, 49 insertions(+), 13 deletions(-) diff --git a/plugins/samplesource/testsource/readme.md b/plugins/samplesource/testsource/readme.md index 8ddcb39df..6b198eaa1 100644 --- a/plugins/samplesource/testsource/readme.md +++ b/plugins/samplesource/testsource/readme.md @@ -2,7 +2,7 @@

    Introduction

    -This input sample source plugin is an internal continuous wave generator that can be used to carry out test of software internals. +This input sample source plugin is an internal continuous wave generator that can be used to carry out test of software internals.

    Build

    @@ -22,19 +22,19 @@ This is the center frequency of reception in kHz.

    1.2: Start/Stop

    -Device start / stop button. +Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. - +

    1.3: Record

    Record baseband I/Q stream toggle button

    1.4: Stream sample rate

    -Baseband I/Q sample rate in kS/s. This is the device to host sample rate (3) divided by the decimation factor (4). +Baseband I/Q sample rate in kS/s. This is the device to host sample rate (3) divided by the decimation factor (4).

    2: Various options

    @@ -57,10 +57,10 @@ This exercises the decimation chain.

    2.3: Baseband center frequency position relative the center frequency

    - **Cen**: the decimation operation takes place around the center frequency Fs - - **Inf**: the decimation operation takes place around Fs - Fc. + - **Inf**: the decimation operation takes place around Fs - Fc. - **Sup**: the decimation operation takes place around Fs + Fc. - -With SR as the sample rate before decimation Fc is calculated as: + +With SR as the sample rate before decimation Fc is calculated as: - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband. @@ -89,7 +89,10 @@ This controls the generator sample rate in samples per second. - **P1**: Pattern 1 is a sawtooth pattern - Pulse width: 1000 samples - Starts at full amplitude then amplitude decreases linearly down to zero - + - **P2**: Pattern 2 is a 50% duty cycle square pattern + - Pulse width: 1000 samples + - Starts with a full amplitude pulse then down to zero for the duration of one pulse +

    5: Modulating tone frequency

    This controls the modulating tone frequency in kHz in 10 Hz steps. @@ -97,23 +100,23 @@ This controls the modulating tone frequency in kHz in 10 Hz steps.

    6: Carrier shift from center frequency

    Use this control to set the offset of the carrier from the center frequency of reception. - +

    7: AM modulation factor

    This controls the AM modulation factor from 0 to 99%

    8: FM deviation

    -This controls the frequency modulation deviation in kHz in 100 Hz steps. It cannot exceed the sample rate. - +This controls the frequency modulation deviation in kHz in 100 Hz steps. It cannot exceed the sample rate. +

    9: Amplitude coarse control

    This slider controls the number of amplitude bits by steps of 100 bits. The total number of amplitude bits appear on the right. - +

    10: Amplitude fine control

    This slider controls the number of amplitude bits by steps of 1 bit. The signal power in dB relative to the maximum power (full bit range) appear on the right. - +

    11: DC bias

    Use this slider to give a DC component in percentage of maximum amplitude. diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui index 2abaa5717..0706fc67e 100644 --- a/plugins/samplesource/testsource/testsourcegui.ui +++ b/plugins/samplesource/testsource/testsourcegui.ui @@ -490,6 +490,11 @@ P1 + + + P2 + + diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index 6a5509481..3d230b2d6 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -362,6 +362,8 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, bool for m_testSourceThread->setPattern0(); } else if (settings.m_modulation == TestSourceSettings::ModulationPattern1) { m_testSourceThread->setPattern1(); + } else if (settings.m_modulation == TestSourceSettings::ModulationPattern2) { + m_testSourceThread->setPattern2(); } } } diff --git a/plugins/samplesource/testsource/testsourcesettings.h b/plugins/samplesource/testsource/testsourcesettings.h index 9c1c8f3fe..60c7d055f 100644 --- a/plugins/samplesource/testsource/testsourcesettings.h +++ b/plugins/samplesource/testsource/testsourcesettings.h @@ -39,6 +39,7 @@ struct TestSourceSettings { ModulationFM, ModulationPattern0, ModulationPattern1, + ModulationPattern2, ModulationLast } Modulation; diff --git a/plugins/samplesource/testsource/testsourcethread.cpp b/plugins/samplesource/testsource/testsourcethread.cpp index 5f37d726f..0e972b776 100644 --- a/plugins/samplesource/testsource/testsourcethread.cpp +++ b/plugins/samplesource/testsource/testsourcethread.cpp @@ -324,6 +324,24 @@ void TestSourceThread::generate(quint32 chunksize) } } break; + case TestSourceSettings::ModulationPattern2: // 50% duty cycle square pattern + { + if (m_pulseSampleCount < m_pulseWidth) // 1 + { + m_buf[i++] = (int16_t) (m_amplitudeBitsI + m_amplitudeBitsDC); + m_buf[i++] = (int16_t) (m_phaseImbalance * (float) m_amplitudeBitsQ); + } else { // 0 + m_buf[i++] = m_amplitudeBitsDC; + m_buf[i++] = 0; + } + + if (m_pulseSampleCount < 2*m_pulseWidth - 1) { + m_pulseSampleCount++; + } else { + m_pulseSampleCount = 0; + } + } + break; case TestSourceSettings::ModulationNone: default: { @@ -419,3 +437,9 @@ void TestSourceThread::setPattern1() m_pulseWidth = 1000; m_pulseSampleCount = 0; } + +void TestSourceThread::setPattern2() +{ + m_pulseWidth = 1000; + m_pulseSampleCount = 0; +} diff --git a/plugins/samplesource/testsource/testsourcethread.h b/plugins/samplesource/testsource/testsourcethread.h index 7667eddf8..0944c603e 100644 --- a/plugins/samplesource/testsource/testsourcethread.h +++ b/plugins/samplesource/testsource/testsourcethread.h @@ -77,6 +77,7 @@ public: void setFMDeviation(float deviation); void setPattern0(); void setPattern1(); + void setPattern2(); private: QMutex m_startWaitMutex; From 31398954ef2fe1306e733bee532702a58e307313 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 25 Oct 2018 14:50:29 +0200 Subject: [PATCH 896/956] Scope: make the code more straightforward --- sdrgui/dsp/scopevis.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 203cf3fe8..1f9450502 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -382,32 +382,30 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa if (m_triggerState == TriggerTriggered) { - int remainder = -1; + int remainder; int count = end - begin; // number of samples in traceback buffer past the current point SampleVector::iterator mend = m_traceDiscreteMemory.current().current(); SampleVector::iterator mbegin = mend - count; if (m_traceStart) // start of trace processing { - // trace back - if (m_maxTraceDelay > 0) - { + // process until begin point + + if (m_maxTraceDelay > 0) { // trace back processTraces(mbegin - m_preTriggerDelay - m_maxTraceDelay, mbegin - m_preTriggerDelay, true); } - // pre-trigger - if (m_preTriggerDelay > 0) - { - remainder = processTraces(mbegin - m_preTriggerDelay, mbegin); + if (m_preTriggerDelay > 0) { // pre-trigger + processTraces(mbegin - m_preTriggerDelay, mbegin); } + // process the rest of the trace + + remainder = processTraces(mbegin, mend); m_traceStart = false; } - - - if (remainder < 0) + else // process the current trace { - // live trace remainder = processTraces(mbegin, mend); } From 5889567267bb1a7f0b3f69d09e858447557e2c3e Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 25 Oct 2018 18:48:02 +0200 Subject: [PATCH 897/956] Experimental HDPI support --- app/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/main.cpp b/app/main.cpp index aeca0f296..64485de3b 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -36,6 +36,8 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); QCoreApplication::setApplicationVersion("4.2.4"); + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // DPI support + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); //HiDPI pixmaps #if 1 qApp->setStyle(QStyleFactory::create("fusion")); From e923ac571b9b8016d5ea9d6de503bb8587b7c140 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 26 Oct 2018 18:35:18 +0200 Subject: [PATCH 898/956] Scope: removed dubious code that could cause trigger bugs --- sdrgui/dsp/scopevis.cpp | 9 +++++---- sdrgui/dsp/scopevis.h | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 1f9450502..115b0f03a 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -297,10 +297,11 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa m_traceDiscreteMemory.current().write(cbegin, end); - if (m_traceDiscreteMemory.current().absoluteFill() < m_traceSize) - { - return; // not enough samples in memory - } + // Removed in 4.2.4 may cause trigger bug + // if (m_traceDiscreteMemory.current().absoluteFill() < m_traceSize) + // { + // return; // not enough samples in memory + // } // trigger process diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index b76f8fb62..855aff7ca 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -727,8 +727,9 @@ private: { uint32_t nextMemIndex = m_currentMemIndex < (m_memSize-1) ? m_currentMemIndex+1 : 0; m_traceBackBuffers[nextMemIndex].reset(); - m_traceBackBuffers[nextMemIndex].write(m_traceBackBuffers[m_currentMemIndex].m_endPoint - m_traceSize, - m_traceBackBuffers[m_currentMemIndex].m_endPoint); + // Removed in 4.2.4 may cause trigger bug + // m_traceBackBuffers[nextMemIndex].write(m_traceBackBuffers[m_currentMemIndex].m_endPoint - m_traceSize, + // m_traceBackBuffers[m_currentMemIndex].m_endPoint); m_currentMemIndex = nextMemIndex; return m_traceBackBuffers[m_currentMemIndex]; // new trace } From ba64e1cd52d0206fe3ce857b253f15c759abc9f8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 27 Oct 2018 06:13:42 +0200 Subject: [PATCH 899/956] Scope: restored some of the commented out code --- sdrgui/dsp/scopevis.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index 855aff7ca..b76f8fb62 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -727,9 +727,8 @@ private: { uint32_t nextMemIndex = m_currentMemIndex < (m_memSize-1) ? m_currentMemIndex+1 : 0; m_traceBackBuffers[nextMemIndex].reset(); - // Removed in 4.2.4 may cause trigger bug - // m_traceBackBuffers[nextMemIndex].write(m_traceBackBuffers[m_currentMemIndex].m_endPoint - m_traceSize, - // m_traceBackBuffers[m_currentMemIndex].m_endPoint); + m_traceBackBuffers[nextMemIndex].write(m_traceBackBuffers[m_currentMemIndex].m_endPoint - m_traceSize, + m_traceBackBuffers[m_currentMemIndex].m_endPoint); m_currentMemIndex = nextMemIndex; return m_traceBackBuffers[m_currentMemIndex]; // new trace } From 7ab87d594a029f5c41c273e13a0b93ce31780bda Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 27 Oct 2018 07:32:14 +0200 Subject: [PATCH 900/956] Scope: update trace continuously for sweep times of one second or more --- plugins/channelrx/chanalyzer/readme.md | 4 +++- sdrgui/dsp/scopevis.cpp | 11 +++++++++-- sdrgui/dsp/scopevis.h | 1 - 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/chanalyzer/readme.md b/plugins/channelrx/chanalyzer/readme.md index 7eaa726de..8fd9755a0 100644 --- a/plugins/channelrx/chanalyzer/readme.md +++ b/plugins/channelrx/chanalyzer/readme.md @@ -32,7 +32,9 @@ The interface is essentially divided in the following sections 4. Scope trace control 5. Scope trigger control -Note: the spectrum view (Channel spectrum) is not presented here. +Note 1: the scope trace is updated continuously for sweep times of 1 second or more else the display is refreshed only when the trace finishes. + +Note 2: the spectrum view (Channel spectrum) is not presented here.

    C. Channel controls

    diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 115b0f03a..44a9a3834 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -595,10 +595,17 @@ int ScopeVis::processTraces(const SampleVector::const_iterator& cbegin, const Sa m_nbSamples--; } + float traceTime = ((float) m_traceSize) / m_sampleRate; + + if (traceTime >= 1.0f) { // display continuously if trace time is 1 second or more + m_glScope->newTraces(&m_traces.m_traces[m_traces.currentBufferIndex()]); + } + if (m_nbSamples == 0) // finished { - //sqDebug("ScopeVis::processTraces: m_traceCount: %d", m_traces.m_tracesControl.begin()->m_traceCount[m_traces.currentBufferIndex()]); - m_glScope->newTraces(&m_traces.m_traces[m_traces.currentBufferIndex()]); + if (traceTime < 1.0f) { // display only at trace end if trace time is less than 1 second + m_glScope->newTraces(&m_traces.m_traces[m_traces.currentBufferIndex()]); + } m_traces.switchBuffer(); return end - begin; // return remainder count } diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index b76f8fb62..9eb5dd963 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -562,7 +562,6 @@ private: TriggerUntriggered, //!< Trigger is not kicked off yet (or trigger list is empty) TriggerTriggered, //!< Trigger has been kicked off TriggerDelay, //!< Trigger conditions have been kicked off but it is waiting for delay before final kick off - TriggerNewConfig, //!< Special condition when a new configuration has been received }; struct TriggerCondition From a2d837226203c02cbc47f7143f4937fc899e4f74 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 27 Oct 2018 18:19:08 +0200 Subject: [PATCH 901/956] Scope: corrected report of samples from one trace to the next --- sdrgui/dsp/scopevis.cpp | 2 +- sdrgui/dsp/scopevis.h | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 44a9a3834..cb5b63f3c 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -414,7 +414,7 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa { mbegin = mend - remainder; m_traceDiscreteMemory.current().m_endPoint = mbegin; - m_traceDiscreteMemory.store(); // next memory trace + m_traceDiscreteMemory.store(m_preTriggerDelay+remainder); // next memory trace. m_triggerState = TriggerUntriggered; m_triggerWaitForReset = m_triggerOneShot; diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index 9eb5dd963..d32ca47a0 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -721,12 +721,13 @@ private: /** * Move index forward by one position and return reference to the trace at this position * Copy a trace length of samples into the new memory slot + * samplesToReport are the number of samples to report on the next trace */ - TraceBackBuffer &store() + TraceBackBuffer &store(int samplesToReport) { uint32_t nextMemIndex = m_currentMemIndex < (m_memSize-1) ? m_currentMemIndex+1 : 0; m_traceBackBuffers[nextMemIndex].reset(); - m_traceBackBuffers[nextMemIndex].write(m_traceBackBuffers[m_currentMemIndex].m_endPoint - m_traceSize, + m_traceBackBuffers[nextMemIndex].write(m_traceBackBuffers[m_currentMemIndex].m_endPoint - samplesToReport, m_traceBackBuffers[m_currentMemIndex].m_endPoint); m_currentMemIndex = nextMemIndex; return m_traceBackBuffers[m_currentMemIndex]; // new trace From f142c98e82b97a6e3d341793178b8e8057cf6205 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 27 Oct 2018 19:45:42 +0200 Subject: [PATCH 902/956] Scope: clear display trace before update if trace time is 1s or higher (progressive display) --- sdrgui/dsp/scopevis.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index cb5b63f3c..170214162 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -390,6 +390,14 @@ void ScopeVis::processTrace(const SampleVector::const_iterator& cbegin, const Sa if (m_traceStart) // start of trace processing { + // if trace time is 1s or more the display is progressive so we have to clear it first + + float traceTime = ((float) m_traceSize) / m_sampleRate; + + if (traceTime >= 1.0f) { + initTraceBuffers(); + } + // process until begin point if (m_maxTraceDelay > 0) { // trace back From d17df4690a6f6cdbf44395a178b9c49744838c94 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 27 Oct 2018 23:16:56 +0200 Subject: [PATCH 903/956] Scope: implemented a fixed trigger holdoff of 2 samples --- sdrgui/dsp/scopevis.h | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index d32ca47a0..2d6084e79 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -1021,7 +1021,7 @@ private: class TriggerComparator { public: - TriggerComparator() : m_level(0), m_reset(true) + TriggerComparator() : m_level(0), m_reset(true), m_holdoff(2), m_trues(0), m_falses(0) { computeLevels(); } @@ -1044,6 +1044,25 @@ private: condition = triggerCondition.m_projector.run(s) > m_level; } + if (condition) + { + if (m_trues < m_holdoff) { + condition = false; + m_trues++; + } else { + m_falses = 0; + } + } + else + { + if (m_falses < m_holdoff) { + condition = true; + m_falses++; + } else { + m_trues = 0; + } + } + if (m_reset) { triggerCondition.m_prevCondition = condition; @@ -1087,6 +1106,9 @@ private: Real m_levelPowerDB; Real m_levelPowerLin; bool m_reset; + uint32_t m_holdoff; + uint32_t m_trues; + uint32_t m_falses; }; GLScope* m_glScope; From 489a55a886344f6c607f10a7a2806e6da86e1768 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 28 Oct 2018 01:38:58 +0200 Subject: [PATCH 904/956] Scope: implemented trigger holdoff --- debian/changelog | 3 +- doc/img/ChAnalyzerNG_plugin_scope3.png | Bin 15070 -> 17076 bytes doc/img/ChAnalyzerNG_plugin_scope3.xcf | Bin 84899 -> 103271 bytes plugins/channelrx/chanalyzer/readme.md | 22 ++++++++---- sdrgui/dsp/scopevis.h | 28 +++++++++------ sdrgui/gui/glscopegui.cpp | 13 +++++++ sdrgui/gui/glscopegui.h | 1 + sdrgui/gui/glscopegui.ui | 48 +++++++++++++++++++++++++ 8 files changed, 97 insertions(+), 18 deletions(-) diff --git a/debian/changelog b/debian/changelog index 21a540f07..b84f7bfb6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,8 +3,9 @@ sdrangel (4.2.4-1) unstable; urgency=medium * LimeSDR: use LimeSuite 18.10.0 for builds * DSD demod: use 1 dB steps for squelch * Scope: fixed some trigger issues. Fixes issue #233 + * Scope: implemented trigger holdoff. May fix more trigger issues. - -- Edouard Griffiths, F4EXB Wed, 24 Oct 2018 21:14:18 +0200 + -- Edouard Griffiths, F4EXB Sat, 27 Oct 2018 21:14:18 +0200 sdrangel (4.2.3-1) unstable; urgency=medium diff --git a/doc/img/ChAnalyzerNG_plugin_scope3.png b/doc/img/ChAnalyzerNG_plugin_scope3.png index 87781e7d6c919a6f6fb7f35783beb489e932406a..09b8e1af24fcecea97b8c6b7c82562587023767f 100644 GIT binary patch literal 17076 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 HG6?)%N*k*I literal 15070 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 C3a1PJ delta 10614 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 Pp2B2I*%QCo(%9. Trigger level adjustment +

    9. Trigger holdoff

    + +Use this button to control the trigger holdoff in a number of samples from 1 to 12. + +The above level condition is counted for a number of samples and if it is has been true for the holdoff number of samples the condition is declared true. A similar count is used for the false condition (below level). + +When the condition is declared true the counter of false conditions is reset and accordingly when the condition is declared false the counter of true conditions is reset. + +A value above 1 helps eliminating false triggers when small spikes appear on the leading or falling edge of a larger pulse. A value of 1 (minimum) means that the holdoff is not active. + +

    10. Trigger level adjustment

    This pair of sliders let you adjust the trigger level, The level appears on the left of the sliders. @@ -406,26 +416,26 @@ The bottom slider is a fine adjustment. Each step moves the trigger level by an - MagDB: 0.01 dB - Phi, dPhi: 20.0E-6 -

    10: Trigger delay

    +

    11: Trigger delay

    The actual trigger top can be moved forward by a number of samples. This pair of slider lets you adjust this delay. The delay in time units appears at the left of the sliders and the amount of samples as a tooltip The top slider is a coarse adjustment. Each step moves the delay by a trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples -

    11. Pre-trigger delay

    +

    12. Pre-trigger delay

    The trace can start an amount of time before the trigger top. This pair of sliders let you adjust this amount of time which is displayed at the left of the sliders. The corresponding number of samples appear as a tooltip. The top slider is a coarse adjustment. Each step moves the delay by a hundreth of the trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples. -

    12. Trigger line color

    +

    13. Trigger line color

    This area shows the current trigger line color. When clicking on it a color chooser dialog appears that lets you change the color of the current trigger line color. This line appears when the selected trace projection matches the trigger projection. -

    13. One-shot trigger

    +

    14. One-shot trigger

    This button toggles a one shot trigger. When the (final) trigger is raised only one trace is processed until the button is released. -

    14. Freerun

    +

    15. Freerun

    When active the triggers are disabled and traces are processed continuously. This is the default at plugin start time. diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index 2d6084e79..45742b5cd 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -105,6 +105,7 @@ public: int m_triggerLevelFine; bool m_triggerPositiveEdge; //!< Trigger on the positive edge (else negative) bool m_triggerBothEdges; //!< Trigger on both edges (else only one) + uint32_t m_triggerHoldoff; //!< Trigger holdoff in number of samples uint32_t m_triggerDelay; //!< Delay before the trigger is kicked off in number of samples (trigger delay) double m_triggerDelayMult; //!< Trigger delay as a multiplier of trace length int m_triggerDelayCoarse; @@ -123,6 +124,7 @@ public: m_triggerLevelFine(0), m_triggerPositiveEdge(true), m_triggerBothEdges(false), + m_triggerHoldoff(1), m_triggerDelay(0), m_triggerDelayMult(0.0), m_triggerDelayCoarse(0), @@ -572,13 +574,18 @@ private: bool m_prevCondition; //!< Condition (above threshold) at previous sample uint32_t m_triggerDelayCount; //!< Counter of samples for delay uint32_t m_triggerCounter; //!< Counter of trigger occurences + uint32_t m_trues; //!< Count of true conditions for holdoff processing + uint32_t m_falses; //!< Count of false conditions for holdoff processing + TriggerCondition(const TriggerData& triggerData) : m_projector(Projector::ProjectionReal), m_triggerData(triggerData), m_prevCondition(false), m_triggerDelayCount(0), - m_triggerCounter(0) + m_triggerCounter(0), + m_trues(0), + m_falses(0) { } @@ -607,6 +614,8 @@ private: m_prevCondition = false; m_triggerDelayCount = 0; m_triggerCounter = 0; + m_trues = 0; + m_falses = 0; } void operator=(const TriggerCondition& other) @@ -1021,7 +1030,7 @@ private: class TriggerComparator { public: - TriggerComparator() : m_level(0), m_reset(true), m_holdoff(2), m_trues(0), m_falses(0) + TriggerComparator() : m_level(0), m_reset(true) { computeLevels(); } @@ -1046,20 +1055,20 @@ private: if (condition) { - if (m_trues < m_holdoff) { + if (triggerCondition.m_trues < triggerCondition.m_triggerData.m_triggerHoldoff) { condition = false; - m_trues++; + triggerCondition.m_trues++; } else { - m_falses = 0; + triggerCondition.m_falses = 0; } } else { - if (m_falses < m_holdoff) { + if (triggerCondition.m_falses < triggerCondition.m_triggerData.m_triggerHoldoff) { condition = true; - m_falses++; + triggerCondition.m_falses++; } else { - m_trues = 0; + triggerCondition.m_trues = 0; } } @@ -1106,9 +1115,6 @@ private: Real m_levelPowerDB; Real m_levelPowerLin; bool m_reset; - uint32_t m_holdoff; - uint32_t m_trues; - uint32_t m_falses; }; GLScope* m_glScope; diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp index b4fe97e64..2ffca3cdb 100644 --- a/sdrgui/gui/glscopegui.cpp +++ b/sdrgui/gui/glscopegui.cpp @@ -220,6 +220,7 @@ QByteArray GLScopeGUI::serialize() const s.writeFloat(218 + 16*i, triggerData.m_triggerColorR); s.writeFloat(219 + 16*i, triggerData.m_triggerColorG); s.writeFloat(220 + 16*i, triggerData.m_triggerColorB); + s.writeU32(221 + 16*i, triggerData.m_triggerHoldoff); } return s.final(); @@ -402,6 +403,9 @@ bool GLScopeGUI::deserialize(const QByteArray& data) d.readFloat(219 + 16*iTrigger, &g, 1.0f); d.readFloat(220 + 16*iTrigger, &b, 1.0f); m_focusedTriggerColor.setRgbF(r, g, b); + d.readU32(221 + 16*iTrigger, &uintValue, 1); + ui->trigHoldoff->setValue(uintValue); + ui->trigHoldoffText->setText(tr("%1").arg(uintValue)); fillTriggerData(triggerData); @@ -895,6 +899,12 @@ void GLScopeGUI::on_trigBoth_toggled(bool checked) changeCurrentTrigger(); } +void GLScopeGUI::on_trigHoldoff_valueChanged(int value) +{ + ui->trigHoldoffText->setText(tr("%1").arg(value)); + changeCurrentTrigger(); +} + void GLScopeGUI::on_trigLevelCoarse_valueChanged(int value __attribute__((unused))) { setTrigLevelDisplay(); @@ -1338,6 +1348,7 @@ void GLScopeGUI::fillTriggerData(ScopeVis::TriggerData& triggerData) triggerData.m_triggerLevelFine = ui->trigLevelFine->value(); triggerData.m_triggerPositiveEdge = ui->trigPos->isChecked(); triggerData.m_triggerBothEdges = ui->trigBoth->isChecked(); + triggerData.m_triggerHoldoff = ui->trigHoldoff->value(); triggerData.m_triggerRepeat = ui->trigCount->value(); triggerData.m_triggerDelayMult = ui->trigDelayCoarse->value() + ui->trigDelayFine->value() / (ScopeVis::m_traceChunkSize / 10.0); triggerData.m_triggerDelay = (int) (m_traceLenMult * ScopeVis::m_traceChunkSize * triggerData.m_triggerDelayMult); @@ -1405,6 +1416,8 @@ void GLScopeGUI::setTriggerUI(const ScopeVis::TriggerData& triggerData) } } + ui->trigHoldoffText->setText(tr("%1").arg(triggerData.m_triggerHoldoff)); + ui->trigLevelCoarse->setValue(triggerData.m_triggerLevelCoarse); ui->trigLevelFine->setValue(triggerData.m_triggerLevelFine); setTrigLevelDisplay(); diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h index c5d181f35..de6687807 100644 --- a/sdrgui/gui/glscopegui.h +++ b/sdrgui/gui/glscopegui.h @@ -227,6 +227,7 @@ private slots: void on_trigPos_toggled(bool checked); void on_trigNeg_toggled(bool checked); void on_trigBoth_toggled(bool checked); + void on_trigHoldoff_valueChanged(int value); void on_trigLevelCoarse_valueChanged(int value); void on_trigLevelFine_valueChanged(int value); void on_trigDelayCoarse_valueChanged(int value); diff --git a/sdrgui/gui/glscopegui.ui b/sdrgui/gui/glscopegui.ui index 04a336824..bcc06e97e 100644 --- a/sdrgui/gui/glscopegui.ui +++ b/sdrgui/gui/glscopegui.ui @@ -1558,6 +1558,54 @@ kS/s
    + + + + H: + + + + + + + + 24 + 24 + + + + Trigger holdoff in number of samples + + + 1 + + + 12 + + + 1 + + + + + + + + 15 + 0 + + + + Number of samples for holdoff + + + 10 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + From 7faee5f2129a6d48a2d4197b7f1a125e850137a8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 28 Oct 2018 02:03:03 +0200 Subject: [PATCH 905/956] Scope: removed useless resizing of complex trace by 4 times the amount actually needed --- sdrgui/dsp/scopevis.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index 45742b5cd..b00f0dcc3 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -723,7 +723,7 @@ private: for (std::vector::iterator it = m_traceBackBuffers.begin(); it != m_traceBackBuffers.end(); ++it) { - it->resize(4*m_traceSize); // TODO: why 4? + it->resize(m_traceSize); // was multiplied by 4 } } From 675ea97b87a02111ea2e080992b844cd9e873c36 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 28 Oct 2018 09:17:20 +0100 Subject: [PATCH 906/956] Qt's High DPI instructions are available only since 5.6 --- app/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/main.cpp b/app/main.cpp index 64485de3b..8470d5f6e 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -36,8 +36,10 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); QCoreApplication::setApplicationVersion("4.2.4"); +#if QT_VERSION >= 0x050600 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // DPI support QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); //HiDPI pixmaps +#endif #if 1 qApp->setStyle(QStyleFactory::create("fusion")); From c82d8387082141d5fea5bba16288f54abcb4e729 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 29 Oct 2018 16:39:25 +0100 Subject: [PATCH 907/956] SoapySDR support: build infrastructure and input plugin enumeration --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- cmake/Modules/FindSoapySDR.cmake | 34 +++++ debian/changelog | 6 + devices/CMakeLists.txt | 6 + devices/readme.md | 4 + devices/soapysdr/CMakeLists.txt | 46 ++++++ devices/soapysdr/devicesoapysdrscan.cpp | 128 +++++++++++++++++ devices/soapysdr/devicesoapysdrscan.h | 51 +++++++ plugins/samplesource/CMakeLists.txt | 8 ++ .../bladerf2input/bladerf2inputgui.h | 6 +- .../samplesource/soapysdrinput/CMakeLists.txt | 78 +++++++++++ .../soapysdrinput/soapysdrinput.cpp | 80 +++++++++++ .../soapysdrinput/soapysdrinput.h | 56 ++++++++ .../soapysdrinput/soapysdrinputgui.cpp | 86 ++++++++++++ .../soapysdrinput/soapysdrinputgui.h | 70 ++++++++++ .../soapysdrinput/soapysdrinputplugin.cpp | 132 ++++++++++++++++++ .../soapysdrinput/soapysdrinputplugin.h | 56 ++++++++ 19 files changed, 847 insertions(+), 6 deletions(-) create mode 100644 cmake/Modules/FindSoapySDR.cmake create mode 100644 devices/soapysdr/CMakeLists.txt create mode 100644 devices/soapysdr/devicesoapysdrscan.cpp create mode 100644 devices/soapysdr/devicesoapysdrscan.h create mode 100644 plugins/samplesource/soapysdrinput/CMakeLists.txt create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinput.cpp create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinput.h create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputgui.h create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputplugin.h diff --git a/app/main.cpp b/app/main.cpp index 8470d5f6e..8d1f8de47 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.2.4"); + QCoreApplication::setApplicationVersion("4.3.0"); #if QT_VERSION >= 0x050600 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // DPI support QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); //HiDPI pixmaps diff --git a/appbench/main.cpp b/appbench/main.cpp index 055fdb98c..1bb7f1f5f 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.2.4"); + QCoreApplication::setApplicationVersion("4.3.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 0c7f89079..e11724111 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.2.4"); + QCoreApplication::setApplicationVersion("4.3.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/cmake/Modules/FindSoapySDR.cmake b/cmake/Modules/FindSoapySDR.cmake new file mode 100644 index 000000000..cf8e274c9 --- /dev/null +++ b/cmake/Modules/FindSoapySDR.cmake @@ -0,0 +1,34 @@ +# Find Soapy SDR + +if (NOT SOAPYSDR_INCLUDE_DIR) + FIND_PATH (SOAPYSDR_INCLUDE_DIR + NAMES SoapySDR/Version.h + PATHS + /usr/include + /usr/local/include + ) +endif() + +if (NOT SOAPYSDR_LIBRARY) + FIND_LIBRARY (SOAPYSDR_LIBRARY + NAMES SoapySDR + HINTS ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib64 + PATHS /usr/local/lib + /usr/local/lib64 + /usr/lib + /usr/lib64 + ) +endif() + +if (SOAPYSDR_INCLUDE_DIR AND SOAPYSDR_LIBRARY) + SET(SOAPYSDR_FOUND TRUE) +endif() + +if (SOAPYSDR_FOUND) + MESSAGE (STATUS "Found SoapySDR: ${SOAPYSDR_INCLUDE_DIR}, ${SOAPYSDR_LIBRARY}") +else() + MESSAGE (STATUS "Could not find SoapySDR") +endif() + +mark_as_advanced(SOAPYSDR_INCLUDE_DIR SOAPYSDR_LIBRARY) diff --git a/debian/changelog b/debian/changelog index b84f7bfb6..b3dec420b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (4.3.0-1) unstable; urgency=medium + + * SoapySDR support + + -- Edouard Griffiths, F4EXB Sun, 04 Nov 2018 21:14:18 +0200 + sdrangel (4.2.4-1) unstable; urgency=medium * LimeSDR: use LimeSuite 18.10.0 for builds diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt index 5437b4595..9626e5db9 100644 --- a/devices/CMakeLists.txt +++ b/devices/CMakeLists.txt @@ -9,6 +9,7 @@ if (BUILD_DEBIAN) add_subdirectory(limesdr) add_subdirectory(perseus) add_subdirectory(plutosdr) + add_subdirectory(soapysdr) else(BUILD_DEBIAN) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) @@ -35,4 +36,9 @@ else(BUILD_DEBIAN) if(LIBUSB_FOUND AND LIBPERSEUS_FOUND) add_subdirectory(perseus) endif() + + find_package(SoapySDR) + if(LIBUSB_FOUND AND SOAPYSDR_FOUND) + add_subdirectory(soapysdr) + endif() endif (BUILD_DEBIAN) diff --git a/devices/readme.md b/devices/readme.md index f6b28ca62..21e334eff 100644 --- a/devices/readme.md +++ b/devices/readme.md @@ -21,3 +21,7 @@ This folder contains classes and methods that can be used by different plugins t - PlutoSDR: one Rx and one Tx full duplex. Plugins are - plutosdrinput - plutosdroutput + + - SoapySDR: Soapy SDR virtual device + - soapysdrinput + - soapysdroutput \ No newline at end of file diff --git a/devices/soapysdr/CMakeLists.txt b/devices/soapysdr/CMakeLists.txt new file mode 100644 index 000000000..57f627bd9 --- /dev/null +++ b/devices/soapysdr/CMakeLists.txt @@ -0,0 +1,46 @@ +project(soapysdrdevice) + +set (CMAKE_CXX_STANDARD 11) + +set(soapysdrdevice_SOURCES + devicesoapysdrscan.cpp +) + +set(soapysdrdevice_HEADERS + devicesoapysdrscan.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${SOAPYSDRSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${SOAPYSDR_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#add_definitions(${QT_DEFINITIONS}) +#add_definitions(-DQT_SHARED) + +add_library(soapysdrdevice SHARED + ${soapysdrdevice_SOURCES} +) + +if (BUILD_DEBIAN) +target_link_libraries(soapysdrdevice + soapysdr + sdrbase +) +else (BUILD_DEBIAN) +target_link_libraries(soapysdrdevice + ${SOAPYSDR_LIBRARY} + sdrbase +) +endif (BUILD_DEBIAN) + +install(TARGETS soapysdrdevice DESTINATION lib) diff --git a/devices/soapysdr/devicesoapysdrscan.cpp b/devices/soapysdr/devicesoapysdrscan.cpp new file mode 100644 index 000000000..2be76ab72 --- /dev/null +++ b/devices/soapysdr/devicesoapysdrscan.cpp @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 +#include + +#include "devicesoapysdrscan.h" + +void DeviceSoapySDRScan::scan() +{ + qDebug("DeviceSoapySDRScan::scan: Lib Version: v%s", SoapySDR::getLibVersion().c_str()); + qDebug("DeviceSoapySDRScan::scan: API Version: v%s", SoapySDR::getAPIVersion().c_str()); + qDebug("DeviceSoapySDRScan::scan: ABI Version: v%s", SoapySDR::getABIVersion().c_str()); + qDebug("DeviceSoapySDRScan::scan: Install root: %s", SoapySDR::getRootPath().c_str()); + + const std::vector& modules = SoapySDR::listModules(); + + for (const auto &it : modules) + { + const auto &errMsg = SoapySDR::loadModule(it); + + if (not errMsg.empty()) { + qWarning("DeviceSoapySDRScan::scan: cannot load module %s: %s", it.c_str(), errMsg.c_str()); + } else { + qDebug("DeviceSoapySDRScan::scan: loaded module: %s", it.c_str()); + } + } + + SoapySDR::FindFunctions findFunctions = SoapySDR::Registry::listFindFunctions(); + SoapySDR::Kwargs kwargs; + m_deviceEnums.clear(); + + for (const auto &it : findFunctions) // for each driver + { + qDebug("DeviceSoapySDRScan::scan: driver: %s", it.first.c_str()); + kwargs["driver"] = it.first; + + SoapySDR::KwargsList kwargsList = SoapySDR::Device::enumerate(kwargs); + SoapySDR::KwargsList::const_iterator kit = kwargsList.begin(); + + for (int deviceSeq = 0; kit != kwargsList.end(); ++kit, deviceSeq++) // for each device + { + m_deviceEnums.push_back(SoapySDRDeviceEnum()); + m_deviceEnums.back().m_driverName = QString(it.first.c_str()); + + // collect identification information + for (SoapySDR::Kwargs::const_iterator kargIt = kit->begin(); kargIt != kit->end(); ++kargIt) + { + if (kargIt->first == "label") + { + m_deviceEnums.back().m_label = QString(kargIt->second.c_str()); + qDebug("DeviceSoapySDRScan::scan: %s #%u %s", + m_deviceEnums.back().m_driverName.toStdString().c_str(), + deviceSeq, + kargIt->second.c_str()); + } + else if ((m_deviceEnums.back().m_idKey.size() == 0) && (kargIt->first == "serial")) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); + } + else if ((m_deviceEnums.back().m_idKey.size() == 0) && (kargIt->first == "device_id")) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); + } + else if ((m_deviceEnums.back().m_idKey.size() == 0) && (kargIt->first == "addr")) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); + } + } + + // access the device to get the number of Rx and Tx channels and at the same time probe + // whether it is available for Soapy + + try + { + SoapySDR::Device *device; + SoapySDR::Kwargs kwargs; + kwargs["driver"] = m_deviceEnums.back().m_driverName.toStdString(); + + if (m_deviceEnums.back().m_idKey.size() > 0) { + kwargs[m_deviceEnums.back().m_idKey.toStdString()] = m_deviceEnums.back().m_idValue.toStdString(); + } + + device = SoapySDR::Device::make(kwargs); + m_deviceEnums.back().m_nbRx = device->getNumChannels(SOAPY_SDR_RX); + m_deviceEnums.back().m_nbTx = device->getNumChannels(SOAPY_SDR_TX); + qDebug("DeviceSoapySDRScan::scan: %s #%u driver=%s hardware=%s #Rx=%u #Tx=%u", + m_deviceEnums.back().m_driverName.toStdString().c_str(), + deviceSeq, + device->getDriverKey().c_str(), + device->getHardwareKey().c_str(), + m_deviceEnums.back().m_nbRx, + m_deviceEnums.back().m_nbTx); + + SoapySDR::Device::unmake(device); + } + catch (const std::exception &ex) + { + qWarning("DeviceSoapySDRScan::scan: %s #%u cannot be opened: %s", + m_deviceEnums.back().m_driverName.toStdString().c_str(), + deviceSeq, + ex.what()); + m_deviceEnums.pop_back(); + } + } // for each device + } +} diff --git a/devices/soapysdr/devicesoapysdrscan.h b/devices/soapysdr/devicesoapysdrscan.h new file mode 100644 index 000000000..bfc7f80dc --- /dev/null +++ b/devices/soapysdr/devicesoapysdrscan.h @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 DEVICES_SOAPYSDR_DEVICESOAPYSDRSCAN_H_ +#define DEVICES_SOAPYSDR_DEVICESOAPYSDRSCAN_H_ + +#include + +#include + +#include "export.h" + +class DEVICES_API DeviceSoapySDRScan +{ +public: + struct SoapySDRDeviceEnum + { + QString m_driverName; + QString m_label; //!< the label key for display should always be present + QString m_idKey; //!< key to uniquely identify device + QString m_idValue; //!< value for the above key + uint32_t m_nbRx; + uint32_t m_nbTx; + + SoapySDRDeviceEnum() : m_nbRx(0), m_nbTx(0) + {} + }; + + void scan(); + uint32_t getNbDevices() const { return m_deviceEnums.size(); } + const std::vector& getDevicesEnumeration() const { return m_deviceEnums; } + +private: + std::vector m_deviceEnums; +}; + + +#endif /* DEVICES_SOAPYSDR_DEVICESOAPYSDRSCAN_H_ */ diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index c3c54d52a..93fc805c7 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -77,6 +77,14 @@ else(LIBUSB_FOUND AND LIBMIRISDR_FOUND) message(STATUS "LibMiriSDR NOT found") endif(LIBUSB_FOUND AND LIBMIRISDR_FOUND) +find_package(SoapySDR) +if(LIBUSB_FOUND AND SOAPYSDR_FOUND) + add_subdirectory(soapysdrinput) + message(STATUS "SoapySDR found") +else() + message(STATUS "SoapySDR not found") +endif() + if (BUILD_DEBIAN) add_subdirectory(airspy) add_subdirectory(airspyhf) diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.h b/plugins/samplesource/bladerf2input/bladerf2inputgui.h index 33ff82bd7..665670f3d 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.h @@ -42,11 +42,11 @@ public: void setName(const QString& name); QString getName() const; - void resetToDefaults(); + virtual void resetToDefaults(); virtual qint64 getCenterFrequency() const; virtual void setCenterFrequency(qint64 centerFrequency); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual bool handleMessage(const Message& message); diff --git a/plugins/samplesource/soapysdrinput/CMakeLists.txt b/plugins/samplesource/soapysdrinput/CMakeLists.txt new file mode 100644 index 000000000..9e461fb3c --- /dev/null +++ b/plugins/samplesource/soapysdrinput/CMakeLists.txt @@ -0,0 +1,78 @@ +project(soapysdrinput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(soapysdrinput_SOURCES + soapysdrinputgui.cpp + soapysdrinput.cpp + soapysdrinputplugin.cpp +# soapysdrinputsettings.cpp +# soapysdrinputthread.cpp +) + +set(soapysdrinput_HEADERS + soapysdrinputgui.h + soapysdrinput.h + soapysdrinputplugin.h +# soapysdrinputsettings.h +# soapysdrinputthread.h +) + +set(soapysdrinput_FORMS +# soapysdrinputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${SOAPYSDRSRC}/include + ${SOAPYSDRSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${SOAPYSDR_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(soapysdrinput_FORMS_HEADERS ${soapysdrinput_FORMS}) + +add_library(inputsoapysdr SHARED + ${soapysdrinput_SOURCES} + ${soapysdrinput_HEADERS_MOC} + ${soapysdrinput_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputsoapysdr + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + soapysdrdevice +) +else (BUILD_DEBIAN) +target_link_libraries(inputsoapysdr + ${QT_LIBRARIES} + ${SOAPYSDR_LIBRARY} + sdrbase + sdrgui + swagger + soapysdrdevice +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputsoapysdr Qt5::Core Qt5::Widgets) + +install(TARGETS inputsoapysdr DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp new file mode 100644 index 000000000..70597a8bb --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "soapysdrinput.h" + +SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) +{ +} + +SoapySDRInput::~SoapySDRInput() +{ +} + +void SoapySDRInput::destroy() +{ + delete this; +} + +void SoapySDRInput::init() +{ +} + +bool SoapySDRInput::start() +{ + return false; +} + +void SoapySDRInput::stop() +{ +} + +QByteArray SoapySDRInput::serialize() const +{ + SimpleSerializer s(1); + return s.final(); +} + +bool SoapySDRInput::deserialize(const QByteArray& data) +{ + return false; +} + +const QString& SoapySDRInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int SoapySDRInput::getSampleRate() const +{ + return 0; +} + +quint64 SoapySDRInput::getCenterFrequency() const +{ + return 0; +} + +void SoapySDRInput::setCenterFrequency(qint64 centerFrequency) +{ +} + +bool SoapySDRInput::handleMessage(const Message& message) +{ + return false; +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h new file mode 100644 index 000000000..0beeb42d3 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUT_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUT_H_ + +#include +#include +#include + +#include "dsp/devicesamplesource.h" + +class DeviceSourceAPI; + +class SoapySDRInput : public DeviceSampleSource +{ +public: + SoapySDRInput(DeviceSourceAPI *deviceAPI); + virtual ~SoapySDRInput(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + virtual bool handleMessage(const Message& message); + +private: + QString m_deviceDescription; +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUT_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp new file mode 100644 index 000000000..fe29fb4d0 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/devicesourceapi.h" +#include "device/deviceuiset.h" +#include "util/simpleserializer.h" + +#include "soapysdrinputgui.h" + +SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(0), + m_deviceUISet(deviceUISet), + m_forceSettings(true), + m_doApplySettings(true), + m_sampleSource(0), + m_sampleRate(0), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) +{ +} + +SoapySDRInputGui::~SoapySDRInputGui() +{ +} + +void SoapySDRInputGui::destroy() +{ + delete this; +} + +void SoapySDRInputGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString SoapySDRInputGui::getName() const +{ + return objectName(); +} + +void SoapySDRInputGui::resetToDefaults() +{ +} + +qint64 SoapySDRInputGui::getCenterFrequency() const +{ + return 0; +} + +void SoapySDRInputGui::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ +} + +QByteArray SoapySDRInputGui::serialize() const +{ + SimpleSerializer s(1); + return s.final(); +} + +bool SoapySDRInputGui::deserialize(const QByteArray& data __attribute__((unused))) +{ + return false; +} + + +bool SoapySDRInputGui::handleMessage(const Message& message __attribute__((unused))) +{ + return false; +} + + diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h new file mode 100644 index 000000000..970b72d7a --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ + +#include +#include + +#include "plugin/plugininstancegui.h" +#include "util/messagequeue.h" + +#include "soapysdrinput.h" + +class DeviceUISet; + +namespace Ui { + class SoapySDRInputGui; +} + +class SoapySDRInputGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~SoapySDRInputGui(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + virtual void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::SoapySDRInputGui* ui; + + DeviceUISet* m_deviceUISet; + bool m_forceSettings; + bool m_doApplySettings; + QTimer m_updateTimer; + QTimer m_statusTimer; + SoapySDRInput* m_sampleSource; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp new file mode 100644 index 000000000..690457e02 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp @@ -0,0 +1,132 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "device/devicesourceapi.h" +#include "soapysdr/devicesoapysdrscan.h" + +#include "soapysdrinputplugin.h" + +#ifdef SERVER_MODE +#include "soapysdrinput.h" +#else +#include "soapysdrinputgui.h" +#endif + +const PluginDescriptor SoapySDRInputPlugin::m_pluginDescriptor = { + QString("SoapySDR Input"), + QString("4.3.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString SoapySDRInputPlugin::m_hardwareID = "SoapySDR"; +const QString SoapySDRInputPlugin::m_deviceTypeID = SOAPYSDRINPUT_DEVICE_TYPE_ID; + +SoapySDRInputPlugin::SoapySDRInputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& SoapySDRInputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void SoapySDRInputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSource(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices SoapySDRInputPlugin::enumSampleSources() +{ + SamplingDevices result; + DeviceSoapySDRScan scanner; + scanner.scan(); + const std::vector& devicesEnumeration = scanner.getDevicesEnumeration(); + qDebug("SoapySDRInputPlugin::enumSampleSources: found %lu devices", devicesEnumeration.size()); + std::vector::const_iterator it = devicesEnumeration.begin(); + + for (int idev = 0; it != devicesEnumeration.end(); ++it, idev++) + { + unsigned int nbRxChannels = it->m_nbRx; + + for (unsigned int ichan = 0; ichan < nbRxChannels; ichan++) + { + QString displayedName(QString("SoapySDR[%1:%2] %3").arg(idev).arg(ichan).arg(it->m_label)); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + it->m_idValue, + idev, + PluginInterface::SamplingDevice::PhysicalDevice, + true, + nbRxChannels, + ichan)); + } + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* SoapySDRInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* SoapySDRInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sourceId == m_deviceTypeID) + { + SoapySDRInputGui* gui = new SoapySDRInputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSource *SoapySDRInputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) +{ + if (sourceId == m_deviceTypeID) + { + SoapySDRInput *input = new SoapySDRInput(deviceAPI); + return input; + } + else + { + return 0; + } +} + + + + + diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.h b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.h new file mode 100644 index 000000000..c53acf12a --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.h @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTPLUGIN_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTPLUGIN_H_ + +#include +#include "plugin/plugininterface.h" + +class PluginAPI; +class DeviceSourceAPI; +class DeviceUISet; + +#define SOAPYSDRINPUT_DEVICE_TYPE_ID "sdrangel.samplesource.soapysdrinput" + +class SoapySDRInputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID SOAPYSDRINPUT_DEVICE_TYPE_ID) + +public: + explicit SoapySDRInputPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual SamplingDevices enumSampleSources(); + virtual PluginInstanceGUI* createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleSource* createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI); + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTPLUGIN_H_ */ From 0e9a0f4f6d9c0f683cce8918e551c63ac70f1644 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 29 Oct 2018 17:20:04 +0100 Subject: [PATCH 908/956] SoapySDR support: use singleton for enumeration and device management --- devices/soapysdr/CMakeLists.txt | 2 + devices/soapysdr/devicesoapysdr.cpp | 68 +++++++++++++++++++ devices/soapysdr/devicesoapysdr.h | 46 +++++++++++++ .../soapysdrinput/soapysdrinputplugin.cpp | 7 +- 4 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 devices/soapysdr/devicesoapysdr.cpp create mode 100644 devices/soapysdr/devicesoapysdr.h diff --git a/devices/soapysdr/CMakeLists.txt b/devices/soapysdr/CMakeLists.txt index 57f627bd9..5ee756732 100644 --- a/devices/soapysdr/CMakeLists.txt +++ b/devices/soapysdr/CMakeLists.txt @@ -3,10 +3,12 @@ project(soapysdrdevice) set (CMAKE_CXX_STANDARD 11) set(soapysdrdevice_SOURCES + devicesoapysdr.cpp devicesoapysdrscan.cpp ) set(soapysdrdevice_HEADERS + devicesoapysdr.h devicesoapysdrscan.h ) diff --git a/devices/soapysdr/devicesoapysdr.cpp b/devices/soapysdr/devicesoapysdr.cpp new file mode 100644 index 000000000..6cb06b258 --- /dev/null +++ b/devices/soapysdr/devicesoapysdr.cpp @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "devicesoapysdr.h" + +DeviceSoapySDR::DeviceSoapySDR() +{ + m_scanner.scan(); +} + +DeviceSoapySDR::~DeviceSoapySDR() +{} + +DeviceSoapySDR& DeviceSoapySDR::instance() +{ + static DeviceSoapySDR inst; + return inst; +} + +SoapySDR::Device *DeviceSoapySDR::openSoapySDR(uint32_t sequence) +{ + instance(); + return openopenSoapySDRFromSequence(sequence); +} + +SoapySDR::Device *DeviceSoapySDR::openopenSoapySDRFromSequence(uint32_t sequence) +{ + if (sequence > m_scanner.getNbDevices()) + { + return 0; + } + else + { + const DeviceSoapySDRScan::SoapySDRDeviceEnum& deviceEnum = m_scanner.getDevicesEnumeration()[sequence]; + + try + { + SoapySDR::Kwargs kwargs; + kwargs["driver"] = deviceEnum.m_driverName.toStdString(); + + if (deviceEnum.m_idKey.size() > 0) { + kwargs[deviceEnum.m_idKey.toStdString()] = deviceEnum.m_idValue.toStdString(); + } + + SoapySDR::Device *device = SoapySDR::Device::make(kwargs); + return device; + } + catch (const std::exception &ex) + { + qWarning("DeviceSoapySDR::openopenSoapySDRFromSequence: %s cannot be opened: %s", + deviceEnum.m_label.toStdString().c_str(), ex.what()); + return 0; + } + } +} diff --git a/devices/soapysdr/devicesoapysdr.h b/devices/soapysdr/devicesoapysdr.h new file mode 100644 index 000000000..95e0b0d64 --- /dev/null +++ b/devices/soapysdr/devicesoapysdr.h @@ -0,0 +1,46 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 DEVICES_SOAPYSDR_DEVICESOAPYSDR_H_ +#define DEVICES_SOAPYSDR_DEVICESOAPYSDR_H_ + +#include +#include + +#include "export.h" +#include "devicesoapysdrscan.h" + +class DEVICES_API DeviceSoapySDR +{ +public: + static DeviceSoapySDR& instance(); + SoapySDR::Device *openSoapySDR(uint32_t sequence); + + uint32_t getNbDevices() const { return m_scanner.getNbDevices(); } + const std::vector& getDevicesEnumeration() const { return m_scanner.getDevicesEnumeration(); } + +protected: + DeviceSoapySDR(); + DeviceSoapySDR(const DeviceSoapySDR&) {} + DeviceSoapySDR& operator=(const DeviceSoapySDR& other __attribute__((unused))) { return *this; } + ~DeviceSoapySDR(); + +private: + SoapySDR::Device *openopenSoapySDRFromSequence(uint32_t sequence); + DeviceSoapySDRScan m_scanner; +}; + +#endif /* DEVICES_SOAPYSDR_DEVICESOAPYSDR_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp index 690457e02..0c1afccf4 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp @@ -18,7 +18,7 @@ #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "device/devicesourceapi.h" -#include "soapysdr/devicesoapysdrscan.h" +#include "soapysdr/devicesoapysdr.h" #include "soapysdrinputplugin.h" @@ -58,9 +58,8 @@ void SoapySDRInputPlugin::initPlugin(PluginAPI* pluginAPI) PluginInterface::SamplingDevices SoapySDRInputPlugin::enumSampleSources() { SamplingDevices result; - DeviceSoapySDRScan scanner; - scanner.scan(); - const std::vector& devicesEnumeration = scanner.getDevicesEnumeration(); + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + const std::vector& devicesEnumeration = deviceSoapySDR.getDevicesEnumeration(); qDebug("SoapySDRInputPlugin::enumSampleSources: found %lu devices", devicesEnumeration.size()); std::vector::const_iterator it = devicesEnumeration.begin(); From 8f2ec099f354b4b3ecc64b4572b850b4a0449a9b Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 29 Oct 2018 18:27:58 +0100 Subject: [PATCH 909/956] SoapySDR support: create output plugin with enumeration --- plugins/samplesink/CMakeLists.txt | 6 + .../samplesink/bladerf2output/CMakeLists.txt | 2 +- .../bladerf2output/bladerf2outputgui.h | 4 +- .../bladerf2output/bladerf2outputplugin.h | 2 +- .../samplesink/soapysdroutput/CMakeLists.txt | 78 ++++++++++ .../soapysdroutput/soapysdroutput.cpp | 84 +++++++++++ .../soapysdroutput/soapysdroutput.h | 52 +++++++ .../soapysdroutput/soapysdroutputgui.cpp | 89 ++++++++++++ .../soapysdroutput/soapysdroutputgui.h | 69 +++++++++ .../soapysdroutput/soapysdroutputplugin.cpp | 133 ++++++++++++++++++ .../soapysdroutput/soapysdroutputplugin.h | 54 +++++++ plugins/samplesource/CMakeLists.txt | 1 + .../soapysdrinput/soapysdrinput.cpp | 9 +- .../soapysdrinput/soapysdrinputplugin.cpp | 1 + 14 files changed, 576 insertions(+), 8 deletions(-) create mode 100644 plugins/samplesink/soapysdroutput/CMakeLists.txt create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutput.cpp create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutput.h create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputgui.h create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputplugin.h diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index 2ea30aa73..a57d121a4 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -28,6 +28,11 @@ if(CM256CC_FOUND) add_subdirectory(sdrdaemonsink) endif(CM256CC_FOUND) +find_package(SoapySDR) +if(LIBUSB_FOUND AND SOAPYSDR_FOUND) + add_subdirectory(soapysdroutput) +endif() + if (BUILD_DEBIAN) add_subdirectory(bladerf1output) add_subdirectory(bladerf2output) @@ -35,6 +40,7 @@ if (BUILD_DEBIAN) add_subdirectory(limesdroutput) add_subdirectory(plutosdroutput) add_subdirectory(sdrdaemonsink) + add_subdirectory(soapysdroutput) endif (BUILD_DEBIAN) add_subdirectory(filesink) diff --git a/plugins/samplesink/bladerf2output/CMakeLists.txt b/plugins/samplesink/bladerf2output/CMakeLists.txt index 620cb2965..0572c08fa 100644 --- a/plugins/samplesink/bladerf2output/CMakeLists.txt +++ b/plugins/samplesink/bladerf2output/CMakeLists.txt @@ -13,7 +13,7 @@ set(bladerf2output_SOURCES set(bladerf2output_HEADERS bladerf2outputgui.h bladerf2output.h - bladerf1soutputplugin.h + bladerf2outputplugin.h bladerf2outputsettings.h bladerf2outputthread.h ) diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.h b/plugins/samplesink/bladerf2output/bladerf2outputgui.h index edbc35496..7c3b9be78 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputgui.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.h @@ -46,8 +46,8 @@ public: void resetToDefaults(); virtual qint64 getCenterFrequency() const; virtual void setCenterFrequency(qint64 centerFrequency); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual bool handleMessage(const Message& message); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputplugin.h b/plugins/samplesink/bladerf2output/bladerf2outputplugin.h index ef10913de..c4f943e57 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputplugin.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.h @@ -22,7 +22,7 @@ class PluginAPI; -#define BLADERF2OUTPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf2output" +#define BLADERF2OUTPUT_DEVICE_TYPE_ID "sdrangel.samplesink.bladerf2output" class BladeRF2OutputPlugin : public QObject, public PluginInterface { Q_OBJECT diff --git a/plugins/samplesink/soapysdroutput/CMakeLists.txt b/plugins/samplesink/soapysdroutput/CMakeLists.txt new file mode 100644 index 000000000..e52e60f49 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/CMakeLists.txt @@ -0,0 +1,78 @@ +project(soapysdroutput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(soapysdroutput_SOURCES + soapysdroutputgui.cpp + soapysdroutput.cpp + soapysdroutputplugin.cpp +# soapysdroutputsettings.cpp +# soapysdroutputthread.cpp +) + +set(soapysdroutput_HEADERS + soapysdroutputgui.h + soapysdroutput.h + soapysdroutputplugin.h +# soapysdroutputsettings.h +# soapysdroutputthread.h +) + +set(soapysdroutput_FORMS +# soapysdroutputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${SOAPYSDRSRC}/include + ${SOAPYSDRSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${SOAPYSDR_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(soapysdroutput_FORMS_HEADERS ${soapysdroutput_FORMS}) + +add_library(outputsoapysdr SHARED + ${soapysdroutput_SOURCES} + ${soapysdroutput_HEADERS_MOC} + ${soapysdroutput_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputsoapysdr + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + soapysdrdevice +) +else (BUILD_DEBIAN) +target_link_libraries(outputsoapysdr + ${QT_LIBRARIES} + ${SOAPYSDR_LIBRARY} + sdrbase + sdrgui + swagger + soapysdrdevice +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputsoapysdr Qt5::Core Qt5::Widgets) + +install(TARGETS outputsoapysdr DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp new file mode 100644 index 000000000..634bf396a --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "soapysdroutput.h" + +SoapySDROutput::SoapySDROutput(DeviceSinkAPI *deviceAPI __attribute__((unused))) : + m_deviceDescription("SoapySDROutput") +{ +} + +SoapySDROutput::~SoapySDROutput() +{ +} + +void SoapySDROutput::destroy() +{ + delete this; +} + +void SoapySDROutput::init() +{ +} + +bool SoapySDROutput::start() +{ + return false; +} + +void SoapySDROutput::stop() +{ +} + +QByteArray SoapySDROutput::serialize() const +{ + SimpleSerializer s(1); + return s.final(); +} + +bool SoapySDROutput::deserialize(const QByteArray& data __attribute__((unused))) +{ + return false; +} + +const QString& SoapySDROutput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int SoapySDROutput::getSampleRate() const +{ + return 0; +} + +quint64 SoapySDROutput::getCenterFrequency() const +{ + return 0; +} + +void SoapySDROutput::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ +} + +bool SoapySDROutput::handleMessage(const Message& message __attribute__((unused))) +{ + return false; +} + + + diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.h b/plugins/samplesink/soapysdroutput/soapysdroutput.h new file mode 100644 index 000000000..34355171e --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUT_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUT_H_ + +#include + +#include "dsp/devicesamplesink.h" + +class DeviceSinkAPI; + +class SoapySDROutput : public DeviceSampleSink { +public: + SoapySDROutput(DeviceSinkAPI *deviceAPI); + virtual ~SoapySDROutput(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + virtual bool handleMessage(const Message& message); + +private: + QString m_deviceDescription; +}; + + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUT_H_ */ diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp new file mode 100644 index 000000000..27a77ad9f --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/devicesinkapi.h" +#include "device/deviceuiset.h" +#include "util/simpleserializer.h" + +#include "soapysdroutputgui.h" + +SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(0), + m_deviceUISet(deviceUISet), + m_forceSettings(true), + m_doApplySettings(true), + m_sampleSink(0), + m_sampleRate(0), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) +{ +} + +SoapySDROutputGui::~SoapySDROutputGui() +{ +} + +void SoapySDROutputGui::destroy() +{ + delete this; +} + +void SoapySDROutputGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString SoapySDROutputGui::getName() const +{ + return objectName(); +} + +void SoapySDROutputGui::resetToDefaults() +{ +} + +qint64 SoapySDROutputGui::getCenterFrequency() const +{ + return 0; +} + +void SoapySDROutputGui::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ +} + +QByteArray SoapySDROutputGui::serialize() const +{ + SimpleSerializer s(1); + return s.final(); +} + +bool SoapySDROutputGui::deserialize(const QByteArray& data __attribute__((unused))) +{ + return false; +} + + +bool SoapySDROutputGui::handleMessage(const Message& message __attribute__((unused))) +{ + return false; +} + + + + + diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h new file mode 100644 index 000000000..8c7e07c69 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTGUI_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTGUI_H_ + +#include +#include + +#include "plugin/plugininstancegui.h" +#include "util/messagequeue.h" + +#include "soapysdroutput.h" + +class DeviceSampleSink; +class DeviceUISet; + +namespace Ui { + class SoapySDROutputGui; +} + +class SoapySDROutputGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~SoapySDROutputGui(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::SoapySDROutputGui* ui; + + DeviceUISet* m_deviceUISet; + bool m_forceSettings; + bool m_doApplySettings; + QTimer m_updateTimer; + QTimer m_statusTimer; + SoapySDROutput* m_sampleSink; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; +}; + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTGUI_H_ */ diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp new file mode 100644 index 000000000..f8691ed50 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp @@ -0,0 +1,133 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "device/devicesourceapi.h" +#include "soapysdr/devicesoapysdr.h" + +#include "soapysdroutputplugin.h" + +#ifdef SERVER_MODE +#include "soapysdroutput.h" +#else +#include "soapysdroutputgui.h" +#endif + +const PluginDescriptor SoapySDROutputPlugin::m_pluginDescriptor = { + QString("SoapySDR Output"), + QString("4.3.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString SoapySDROutputPlugin::m_hardwareID = "SoapySDR"; +const QString SoapySDROutputPlugin::m_deviceTypeID = SOAPYSDROUTPUT_DEVICE_TYPE_ID; + +SoapySDROutputPlugin::SoapySDROutputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& SoapySDROutputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void SoapySDROutputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSink(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices SoapySDROutputPlugin::enumSampleSinks() +{ + SamplingDevices result; + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + const std::vector& devicesEnumeration = deviceSoapySDR.getDevicesEnumeration(); + qDebug("SoapySDRInputPlugin::enumSampleSources: found %lu devices", devicesEnumeration.size()); + std::vector::const_iterator it = devicesEnumeration.begin(); + + for (int idev = 0; it != devicesEnumeration.end(); ++it, idev++) + { + unsigned int nbTxChannels = it->m_nbTx; + + for (unsigned int ichan = 0; ichan < nbTxChannels; ichan++) + { + qDebug("SoapySDROutputPlugin::enumSampleSinks: device #%d (%s) channel %u", idev, it->m_label, ichan); + QString displayedName(QString("SoapySDR[%1:%2] %3").arg(idev).arg(ichan).arg(it->m_label)); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + it->m_idValue, + idev, + PluginInterface::SamplingDevice::PhysicalDevice, + false, + nbTxChannels, + ichan)); + } + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* SoapySDROutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* SoapySDROutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sinkId == m_deviceTypeID) + { + SoapySDROutputGui* gui = new SoapySDROutputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSink* SoapySDROutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) +{ + if(sinkId == m_deviceTypeID) + { + SoapySDROutput* output = new SoapySDROutput(deviceAPI); + return output; + } + else + { + return 0; + } +} + + + + + diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputplugin.h b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.h new file mode 100644 index 000000000..5bf655f83 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.h @@ -0,0 +1,54 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTPLUGIN_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTPLUGIN_H_ + +#include +#include "plugin/plugininterface.h" + +class PluginAPI; + +#define SOAPYSDROUTPUT_DEVICE_TYPE_ID "sdrangel.samplesink.soapysdroutput" + +class SoapySDROutputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID SOAPYSDROUTPUT_DEVICE_TYPE_ID) + +public: + explicit SoapySDROutputPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual SamplingDevices enumSampleSinks(); + + virtual PluginInstanceGUI* createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet); + + virtual DeviceSampleSink* createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI); + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTPLUGIN_H_ */ diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index 93fc805c7..9e442fa11 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -97,6 +97,7 @@ if (BUILD_DEBIAN) add_subdirectory(rtlsdr) add_subdirectory(sdrdaemonsource) add_subdirectory(sdrplay) + add_subdirectory(soapysdrinput) endif (BUILD_DEBIAN) add_subdirectory(filesource) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 70597a8bb..c62ab43b2 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -18,7 +18,8 @@ #include "soapysdrinput.h" -SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) +SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI __attribute__((unused))) : + m_deviceDescription("SoapySDRInput") { } @@ -50,7 +51,7 @@ QByteArray SoapySDRInput::serialize() const return s.final(); } -bool SoapySDRInput::deserialize(const QByteArray& data) +bool SoapySDRInput::deserialize(const QByteArray& data __attribute__((unused))) { return false; } @@ -70,11 +71,11 @@ quint64 SoapySDRInput::getCenterFrequency() const return 0; } -void SoapySDRInput::setCenterFrequency(qint64 centerFrequency) +void SoapySDRInput::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) { } -bool SoapySDRInput::handleMessage(const Message& message) +bool SoapySDRInput::handleMessage(const Message& message __attribute__((unused))) { return false; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp index 0c1afccf4..495fa7bd4 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp @@ -69,6 +69,7 @@ PluginInterface::SamplingDevices SoapySDRInputPlugin::enumSampleSources() for (unsigned int ichan = 0; ichan < nbRxChannels; ichan++) { + qDebug("SoapySDRInputPlugin::enumSampleSources: device #%d (%s) channel %u", idev, it->m_label, ichan); QString displayedName(QString("SoapySDR[%1:%2] %3").arg(idev).arg(ichan).arg(it->m_label)); result.append(SamplingDevice(displayedName, m_hardwareID, From d8b82ddecd5dce6d664d0b2f98af2bb26383e0da Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 30 Oct 2018 00:19:51 +0100 Subject: [PATCH 910/956] SoapySDR support: set enumeration serial to driver and sequence so buddies are paired appropriately --- devices/soapysdr/devicesoapysdrscan.cpp | 50 ++++++++++--------- devices/soapysdr/devicesoapysdrscan.h | 9 ++-- .../soapysdroutput/soapysdroutputplugin.cpp | 8 +-- .../soapysdrinput/soapysdrinputplugin.cpp | 8 +-- 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/devices/soapysdr/devicesoapysdrscan.cpp b/devices/soapysdr/devicesoapysdrscan.cpp index 2be76ab72..61b44d0c2 100644 --- a/devices/soapysdr/devicesoapysdrscan.cpp +++ b/devices/soapysdr/devicesoapysdrscan.cpp @@ -60,33 +60,35 @@ void DeviceSoapySDRScan::scan() { m_deviceEnums.push_back(SoapySDRDeviceEnum()); m_deviceEnums.back().m_driverName = QString(it.first.c_str()); + m_deviceEnums.back().m_sequence = deviceSeq; // collect identification information - for (SoapySDR::Kwargs::const_iterator kargIt = kit->begin(); kargIt != kit->end(); ++kargIt) + + SoapySDR::Kwargs::const_iterator kargIt; + + if ((kargIt = kit->find("label")) != kit->end()) { - if (kargIt->first == "label") - { - m_deviceEnums.back().m_label = QString(kargIt->second.c_str()); - qDebug("DeviceSoapySDRScan::scan: %s #%u %s", - m_deviceEnums.back().m_driverName.toStdString().c_str(), - deviceSeq, - kargIt->second.c_str()); - } - else if ((m_deviceEnums.back().m_idKey.size() == 0) && (kargIt->first == "serial")) - { - m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); - m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); - } - else if ((m_deviceEnums.back().m_idKey.size() == 0) && (kargIt->first == "device_id")) - { - m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); - m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); - } - else if ((m_deviceEnums.back().m_idKey.size() == 0) && (kargIt->first == "addr")) - { - m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); - m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); - } + m_deviceEnums.back().m_label = QString(kargIt->second.c_str()); + qDebug("DeviceSoapySDRScan::scan: %s #%u %s", + m_deviceEnums.back().m_driverName.toStdString().c_str(), + deviceSeq, + kargIt->second.c_str()); + } + + if ((kargIt = kit->find("serial")) != kit->end()) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); + } + else if ((kargIt = kit->find("device_id")) != kit->end()) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); + } + else if ((kargIt = kit->find("addr")) != kit->end()) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); } // access the device to get the number of Rx and Tx channels and at the same time probe diff --git a/devices/soapysdr/devicesoapysdrscan.h b/devices/soapysdr/devicesoapysdrscan.h index bfc7f80dc..b4cc59a0d 100644 --- a/devices/soapysdr/devicesoapysdrscan.h +++ b/devices/soapysdr/devicesoapysdrscan.h @@ -29,13 +29,14 @@ public: struct SoapySDRDeviceEnum { QString m_driverName; - QString m_label; //!< the label key for display should always be present - QString m_idKey; //!< key to uniquely identify device - QString m_idValue; //!< value for the above key + uint32_t m_sequence; //!< device sequence for this driver + QString m_label; //!< the label key for display should always be present + QString m_idKey; //!< key to uniquely identify device + QString m_idValue; //!< value for the above key uint32_t m_nbRx; uint32_t m_nbTx; - SoapySDRDeviceEnum() : m_nbRx(0), m_nbTx(0) + SoapySDRDeviceEnum() : m_sequence(0), m_nbRx(0), m_nbTx(0) {} }; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp index f8691ed50..cdb6def6d 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp @@ -61,7 +61,7 @@ PluginInterface::SamplingDevices SoapySDROutputPlugin::enumSampleSinks() SamplingDevices result; DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); const std::vector& devicesEnumeration = deviceSoapySDR.getDevicesEnumeration(); - qDebug("SoapySDRInputPlugin::enumSampleSources: found %lu devices", devicesEnumeration.size()); + qDebug("SoapySDROutputPlugin::enumSampleSinks: %lu SoapySDR devices. Enumerate these with Tx channel(s):", devicesEnumeration.size()); std::vector::const_iterator it = devicesEnumeration.begin(); for (int idev = 0; it != devicesEnumeration.end(); ++it, idev++) @@ -70,12 +70,14 @@ PluginInterface::SamplingDevices SoapySDROutputPlugin::enumSampleSinks() for (unsigned int ichan = 0; ichan < nbTxChannels; ichan++) { - qDebug("SoapySDROutputPlugin::enumSampleSinks: device #%d (%s) channel %u", idev, it->m_label, ichan); QString displayedName(QString("SoapySDR[%1:%2] %3").arg(idev).arg(ichan).arg(it->m_label)); + QString serial(QString("%1-%2").arg(it->m_driverName).arg(it->m_sequence)); + qDebug("SoapySDROutputPlugin::enumSampleSinks: device #%d (%s) serial %s channel %u", + idev, it->m_label.toStdString().c_str(), serial.toStdString().c_str(), ichan); result.append(SamplingDevice(displayedName, m_hardwareID, m_deviceTypeID, - it->m_idValue, + serial, idev, PluginInterface::SamplingDevice::PhysicalDevice, false, diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp index 495fa7bd4..f0e6ed867 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp @@ -60,7 +60,7 @@ PluginInterface::SamplingDevices SoapySDRInputPlugin::enumSampleSources() SamplingDevices result; DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); const std::vector& devicesEnumeration = deviceSoapySDR.getDevicesEnumeration(); - qDebug("SoapySDRInputPlugin::enumSampleSources: found %lu devices", devicesEnumeration.size()); + qDebug("SoapySDRInputPlugin::enumSampleSources: %lu SoapySDR devices. Enumerate these with Rx channel(s):", devicesEnumeration.size()); std::vector::const_iterator it = devicesEnumeration.begin(); for (int idev = 0; it != devicesEnumeration.end(); ++it, idev++) @@ -69,12 +69,14 @@ PluginInterface::SamplingDevices SoapySDRInputPlugin::enumSampleSources() for (unsigned int ichan = 0; ichan < nbRxChannels; ichan++) { - qDebug("SoapySDRInputPlugin::enumSampleSources: device #%d (%s) channel %u", idev, it->m_label, ichan); QString displayedName(QString("SoapySDR[%1:%2] %3").arg(idev).arg(ichan).arg(it->m_label)); + QString serial(QString("%1-%2").arg(it->m_driverName).arg(it->m_sequence)); + qDebug("SoapySDRInputPlugin::enumSampleSources: device #%d (%s) serial %s channel %u", + idev, it->m_label.toStdString().c_str(), serial.toStdString().c_str(), ichan); result.append(SamplingDevice(displayedName, m_hardwareID, m_deviceTypeID, - it->m_idValue, + serial, idev, PluginInterface::SamplingDevice::PhysicalDevice, true, From 124af5a7b4b031ebb9ef8445edcf28badf84f553 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 30 Oct 2018 01:58:39 +0100 Subject: [PATCH 911/956] SoapySDR support: input: open and close --- devices/bladerf2/devicebladerf2shared.cpp | 3 +- devices/soapysdr/CMakeLists.txt | 2 + devices/soapysdr/devicesoapysdr.cpp | 5 + devices/soapysdr/devicesoapysdr.h | 1 + .../soapysdrinput/soapysdrinput.cpp | 120 +++++++++++++++++- .../soapysdrinput/soapysdrinput.h | 7 + 6 files changed, 135 insertions(+), 3 deletions(-) diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp index 7339a7021..95a305f70 100644 --- a/devices/bladerf2/devicebladerf2shared.cpp +++ b/devices/bladerf2/devicebladerf2shared.cpp @@ -25,7 +25,8 @@ const int DeviceBladeRF2Shared::m_sampleFifoMinSize32 = 150000; // Fixed for DeviceBladeRF2Shared::DeviceBladeRF2Shared() : m_dev(0), m_channel(-1), - m_source(0) + m_source(0), + m_sink(0) {} DeviceBladeRF2Shared::~DeviceBladeRF2Shared() diff --git a/devices/soapysdr/CMakeLists.txt b/devices/soapysdr/CMakeLists.txt index 5ee756732..c410e6ee2 100644 --- a/devices/soapysdr/CMakeLists.txt +++ b/devices/soapysdr/CMakeLists.txt @@ -5,11 +5,13 @@ set (CMAKE_CXX_STANDARD 11) set(soapysdrdevice_SOURCES devicesoapysdr.cpp devicesoapysdrscan.cpp + devicesoapysdrshared.cpp ) set(soapysdrdevice_HEADERS devicesoapysdr.h devicesoapysdrscan.h + devicesoapysdrshared.h ) if (BUILD_DEBIAN) diff --git a/devices/soapysdr/devicesoapysdr.cpp b/devices/soapysdr/devicesoapysdr.cpp index 6cb06b258..2f5e18a1b 100644 --- a/devices/soapysdr/devicesoapysdr.cpp +++ b/devices/soapysdr/devicesoapysdr.cpp @@ -36,6 +36,11 @@ SoapySDR::Device *DeviceSoapySDR::openSoapySDR(uint32_t sequence) return openopenSoapySDRFromSequence(sequence); } +void DeviceSoapySDR::closeSoapySdr(SoapySDR::Device *device) +{ + SoapySDR::Device::unmake(device); +} + SoapySDR::Device *DeviceSoapySDR::openopenSoapySDRFromSequence(uint32_t sequence) { if (sequence > m_scanner.getNbDevices()) diff --git a/devices/soapysdr/devicesoapysdr.h b/devices/soapysdr/devicesoapysdr.h index 95e0b0d64..be697024f 100644 --- a/devices/soapysdr/devicesoapysdr.h +++ b/devices/soapysdr/devicesoapysdr.h @@ -28,6 +28,7 @@ class DEVICES_API DeviceSoapySDR public: static DeviceSoapySDR& instance(); SoapySDR::Device *openSoapySDR(uint32_t sequence); + void closeSoapySdr(SoapySDR::Device *device); uint32_t getNbDevices() const { return m_scanner.getNbDevices(); } const std::vector& getDevicesEnumeration() const { return m_scanner.getDevicesEnumeration(); } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index c62ab43b2..9bea83d1a 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -16,10 +16,19 @@ #include "util/simpleserializer.h" +#include "device/devicesourceapi.h" +#include "device/devicesinkapi.h" +#include "dsp/dspcommands.h" +#include "dsp/filerecord.h" +#include "dsp/dspengine.h" +#include "soapysdr/devicesoapysdr.h" + #include "soapysdrinput.h" -SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI __attribute__((unused))) : - m_deviceDescription("SoapySDRInput") +SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_deviceDescription("SoapySDRInput"), + m_running(false) { } @@ -32,6 +41,113 @@ void SoapySDRInput::destroy() delete this; } +bool SoapySDRInput::openDevice() +{ + if (!m_sampleFifo.setSize(96000 * 4)) + { + qCritical("SoapySDRInput::openDevice: could not allocate SampleFifo"); + return false; + } + else + { + qDebug("SoapySDRInput::openDevice: allocated SampleFifo"); + } + + // look for Rx buddies and get reference to the device object + if (m_deviceAPI->getSourceBuddies().size() > 0) // look source sibling first + { + qDebug("SoapySDRInput::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceSoapySDRShared *deviceSoapySDRShared = (DeviceSoapySDRShared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceSoapySDRShared == 0) + { + qCritical("SoapySDRInput::openDevice: the source buddy shared pointer is null"); + return false; + } + + SoapySDR::Device *device = deviceSoapySDRShared->m_device; + + if (device == 0) + { + qCritical("SoapySDRInput::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_device = device; + } + // look for Tx buddies and get reference to the device object + else if (m_deviceAPI->getSinkBuddies().size() > 0) // then sink + { + qDebug("SoapySDRInput::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceSoapySDRShared *deviceSoapySDRShared = (DeviceSoapySDRShared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceSoapySDRShared == 0) + { + qCritical("SoapySDRInput::openDevice: the sink buddy shared pointer is null"); + return false; + } + + SoapySDR::Device *device = deviceSoapySDRShared->m_device; + + if (device == 0) + { + qCritical("SoapySDRInput::openDevice: cannot get device pointer from Tx buddy"); + return false; + } + + m_deviceShared.m_device = device; + } + // There are no buddies then create the first SoapySDR device + else + { + qDebug("SoapySDRInput::openDevice: open device here"); + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + m_deviceShared.m_device = deviceSoapySDR.openSoapySDR(m_deviceAPI->getSampleSourceSequence()); + + if (!m_deviceShared.m_device) + { + qCritical("BladeRF2Input::openDevice: cannot open BladeRF2 device"); + return false; + } + } + + m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel + m_deviceShared.m_source = this; + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void SoapySDRInput::closeDevice() +{ + if (m_deviceShared.m_device == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + +// if (m_thread) { // stills own the thread => transfer to a buddy +// moveThreadToBuddy(); +// } + + m_deviceShared.m_channel = -1; // publicly release channel + m_deviceShared.m_source = 0; + + // No buddies so effectively close the device + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + deviceSoapySDR.closeSoapySdr(m_deviceShared.m_device); + m_deviceShared.m_device = 0; + } +} + void SoapySDRInput::init() { } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h index 0beeb42d3..2c03ea257 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -21,6 +21,7 @@ #include #include +#include "soapysdr/devicesoapysdrshared.h" #include "dsp/devicesamplesource.h" class DeviceSourceAPI; @@ -48,7 +49,13 @@ public: virtual bool handleMessage(const Message& message); private: + DeviceSourceAPI *m_deviceAPI; + DeviceSoapySDRShared m_deviceShared; QString m_deviceDescription; + bool m_running; + + bool openDevice(); + void closeDevice(); }; From 6cede7a667fa0891194efb44a0494992f2cc24d0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 30 Oct 2018 10:02:32 +0100 Subject: [PATCH 912/956] SoapySDR support: output: open and close --- .../soapysdroutput/soapysdroutput.cpp | 110 +++++++++++++++++- .../soapysdroutput/soapysdroutput.h | 7 ++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 634bf396a..2cbcc484a 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -15,11 +15,18 @@ /////////////////////////////////////////////////////////////////////////////////// #include "util/simpleserializer.h" +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "device/devicesinkapi.h" +#include "device/devicesourceapi.h" +#include "soapysdr/devicesoapysdr.h" #include "soapysdroutput.h" -SoapySDROutput::SoapySDROutput(DeviceSinkAPI *deviceAPI __attribute__((unused))) : - m_deviceDescription("SoapySDROutput") +SoapySDROutput::SoapySDROutput(DeviceSinkAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_deviceDescription("SoapySDROutput"), + m_running(false) { } @@ -32,6 +39,105 @@ void SoapySDROutput::destroy() delete this; } +bool SoapySDROutput::openDevice() +{ + m_sampleSourceFifo.resize(96000 * 4); + + // look for Tx buddies and get reference to the device object + if (m_deviceAPI->getSinkBuddies().size() > 0) // look sink sibling first + { + qDebug("SoapySDROutput::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceSoapySDRShared *deviceSoapySDRShared = (DeviceSoapySDRShared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceSoapySDRShared == 0) + { + qCritical("SoapySDROutput::openDevice: the sink buddy shared pointer is null"); + return false; + } + + SoapySDR::Device *device = deviceSoapySDRShared->m_device; + + if (device == 0) + { + qCritical("SoapySDROutput::openDevice: cannot get device pointer from Tx buddy"); + return false; + } + + m_deviceShared.m_device = device; + } + // look for Rx buddies and get reference to the device object + else if (m_deviceAPI->getSourceBuddies().size() > 0) // then source + { + qDebug("SoapySDROutput::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceSoapySDRShared *deviceSoapySDRShared = (DeviceSoapySDRShared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceSoapySDRShared == 0) + { + qCritical("SoapySDROutput::openDevice: the source buddy shared pointer is null"); + return false; + } + + SoapySDR::Device *device = deviceSoapySDRShared->m_device; + + if (device == 0) + { + qCritical("SoapySDROutput::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_device = device; + } + // There are no buddies then create the first BladeRF2 device + else + { + qDebug("SoapySDROutput::openDevice: open device here"); + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + m_deviceShared.m_device = deviceSoapySDR.openSoapySDR(m_deviceAPI->getSampleSinkSequence()); + + if (!m_deviceShared.m_device) + { + qCritical("SoapySDROutput::openDevice: cannot open SoapySDR device"); + return false; + } + } + + m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel + m_deviceShared.m_sink = this; + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void SoapySDROutput::closeDevice() +{ + if (m_deviceShared.m_device == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + +// if (m_thread) { // stills own the thread => transfer to a buddy +// moveThreadToBuddy(); +// } + + m_deviceShared.m_channel = -1; // publicly release channel + m_deviceShared.m_sink = 0; + + // No buddies so effectively close the device + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + deviceSoapySDR.closeSoapySdr(m_deviceShared.m_device); + m_deviceShared.m_device = 0; + } +} + void SoapySDROutput::init() { } diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.h b/plugins/samplesink/soapysdroutput/soapysdroutput.h index 34355171e..f69336414 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.h @@ -20,6 +20,7 @@ #include #include "dsp/devicesamplesink.h" +#include "soapysdr/devicesoapysdrshared.h" class DeviceSinkAPI; @@ -45,7 +46,13 @@ public: virtual bool handleMessage(const Message& message); private: + DeviceSinkAPI *m_deviceAPI; QString m_deviceDescription; + DeviceSoapySDRShared m_deviceShared; + bool m_running; + + bool openDevice(); + void closeDevice(); }; From 0d0995848369b528f0f87932f7af6deedc2a9c0e Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 30 Oct 2018 20:31:16 +0100 Subject: [PATCH 913/956] SoapySDR support: input: settings and thread output: settings --- devices/soapysdr/devicesoapysdr.h | 2 + devices/soapysdr/devicesoapysdrshared.cpp | 27 + devices/soapysdr/devicesoapysdrshared.h | 43 ++ .../bladerf2output/bladerf2outputsettings.h | 1 - .../samplesink/soapysdroutput/CMakeLists.txt | 4 +- .../soapysdroutput/soapysdroutput.cpp | 2 +- .../soapysdroutput/soapysdroutput.h | 3 + .../soapysdroutput/soapysdroutputsettings.cpp | 75 +++ .../soapysdroutput/soapysdroutputsettings.h | 36 ++ .../bladerf2input/bladerf2inputsettings.h | 1 - .../samplesource/soapysdrinput/CMakeLists.txt | 8 +- .../soapysdrinput/soapysdrinput.cpp | 25 +- .../soapysdrinput/soapysdrinput.h | 5 + .../soapysdrinput/soapysdrinputsettings.cpp | 87 +++ .../soapysdrinput/soapysdrinputsettings.h | 47 ++ .../soapysdrinput/soapysdrinputthread.cpp | 532 ++++++++++++++++++ .../soapysdrinput/soapysdrinputthread.h | 108 ++++ 17 files changed, 994 insertions(+), 12 deletions(-) create mode 100644 devices/soapysdr/devicesoapysdrshared.cpp create mode 100644 devices/soapysdr/devicesoapysdrshared.h create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputsettings.h create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputsettings.h create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputthread.h diff --git a/devices/soapysdr/devicesoapysdr.h b/devices/soapysdr/devicesoapysdr.h index be697024f..c8be84557 100644 --- a/devices/soapysdr/devicesoapysdr.h +++ b/devices/soapysdr/devicesoapysdr.h @@ -33,6 +33,8 @@ public: uint32_t getNbDevices() const { return m_scanner.getNbDevices(); } const std::vector& getDevicesEnumeration() const { return m_scanner.getDevicesEnumeration(); } + static const unsigned int blockSize = (1<<14); + protected: DeviceSoapySDR(); DeviceSoapySDR(const DeviceSoapySDR&) {} diff --git a/devices/soapysdr/devicesoapysdrshared.cpp b/devices/soapysdr/devicesoapysdrshared.cpp new file mode 100644 index 000000000..16a2ef64e --- /dev/null +++ b/devices/soapysdr/devicesoapysdrshared.cpp @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "devicesoapysdrshared.h" + +DeviceSoapySDRShared::DeviceSoapySDRShared() : + m_device(0), + m_channel(-1), + m_source(0), + m_sink(0) +{} + +DeviceSoapySDRShared::~DeviceSoapySDRShared() +{} diff --git a/devices/soapysdr/devicesoapysdrshared.h b/devices/soapysdr/devicesoapysdrshared.h new file mode 100644 index 000000000..4fce21a2b --- /dev/null +++ b/devices/soapysdr/devicesoapysdrshared.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 DEVICES_SOAPYSDR_DEVICESOAPYSDRSHARED_H_ +#define DEVICES_SOAPYSDR_DEVICESOAPYSDRSHARED_H_ + +#include + +#include "export.h" + +class SoapySDRInput; +class SoapySDROutput; + +/** + * Structure shared by a buddy with other buddies + */ +class DEVICES_API DeviceSoapySDRShared +{ +public: + DeviceSoapySDRShared(); + ~DeviceSoapySDRShared(); + + SoapySDR::Device *m_device; + int m_channel; //!< allocated channel (-1 if none) + SoapySDRInput *m_source; + SoapySDROutput *m_sink; +}; + + +#endif /* DEVICES_SOAPYSDR_DEVICESOAPYSDRSHARED_H_ */ diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h index b1385be41..b83cc380e 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h @@ -18,7 +18,6 @@ #define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTSETTINGS_H_ #include -#include struct BladeRF2OutputSettings { quint64 m_centerFrequency; diff --git a/plugins/samplesink/soapysdroutput/CMakeLists.txt b/plugins/samplesink/soapysdroutput/CMakeLists.txt index e52e60f49..0015a6feb 100644 --- a/plugins/samplesink/soapysdroutput/CMakeLists.txt +++ b/plugins/samplesink/soapysdroutput/CMakeLists.txt @@ -6,7 +6,7 @@ set(soapysdroutput_SOURCES soapysdroutputgui.cpp soapysdroutput.cpp soapysdroutputplugin.cpp -# soapysdroutputsettings.cpp + soapysdroutputsettings.cpp # soapysdroutputthread.cpp ) @@ -14,7 +14,7 @@ set(soapysdroutput_HEADERS soapysdroutputgui.h soapysdroutput.h soapysdroutputplugin.h -# soapysdroutputsettings.h + soapysdroutputsettings.h # soapysdroutputthread.h ) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 2cbcc484a..d1bd40485 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -41,7 +41,7 @@ void SoapySDROutput::destroy() bool SoapySDROutput::openDevice() { - m_sampleSourceFifo.resize(96000 * 4); + m_sampleSourceFifo.resize(m_settings.m_devSampleRate/(1<<(m_settings.m_log2Interp <= 4 ? m_settings.m_log2Interp : 4))); // look for Tx buddies and get reference to the device object if (m_deviceAPI->getSinkBuddies().size() > 0) // look sink sibling first diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.h b/plugins/samplesink/soapysdroutput/soapysdroutput.h index f69336414..d78265eae 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.h @@ -22,6 +22,8 @@ #include "dsp/devicesamplesink.h" #include "soapysdr/devicesoapysdrshared.h" +#include "soapysdroutputsettings.h" + class DeviceSinkAPI; class SoapySDROutput : public DeviceSampleSink { @@ -47,6 +49,7 @@ public: private: DeviceSinkAPI *m_deviceAPI; + SoapySDROutputSettings m_settings; QString m_deviceDescription; DeviceSoapySDRShared m_deviceShared; bool m_running; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp new file mode 100644 index 000000000..140212219 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -0,0 +1,75 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "util/simpleserializer.h" + +#include "soapysdroutputsettings.h" + +SoapySDROutputSettings::SoapySDROutputSettings() +{ + resetToDefaults(); +} + +void SoapySDROutputSettings::resetToDefaults() +{ + m_centerFrequency = 435000*1000; + m_LOppmTenths = 0; + m_devSampleRate = 1024000; + m_log2Interp = 0; + m_transverterMode = false; + m_transverterDeltaFrequency = 0; +} + +QByteArray SoapySDROutputSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_devSampleRate); + s.writeS32(2, m_LOppmTenths); + s.writeU32(3, m_log2Interp); + s.writeBool(4, m_transverterMode); + s.writeS64(5, m_transverterDeltaFrequency); + + return s.final(); +} + +bool SoapySDROutputSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + d.readS32(1, &m_devSampleRate); + d.readS32(2, &m_LOppmTenths); + d.readU32(3, &m_log2Interp); + d.readBool(4, &m_transverterMode, false); + d.readS64(5, &m_transverterDeltaFrequency, 0); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h new file mode 100644 index 000000000..1e6f190e2 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -0,0 +1,36 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTSETTINGS_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTSETTINGS_H_ + +#include + +struct SoapySDROutputSettings { + quint64 m_centerFrequency; + int m_LOppmTenths; + qint32 m_devSampleRate; + quint32 m_log2Interp; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + + SoapySDROutputSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTSETTINGS_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2inputsettings.h b/plugins/samplesource/bladerf2input/bladerf2inputsettings.h index 32af13ab4..722be9e6e 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputsettings.h +++ b/plugins/samplesource/bladerf2input/bladerf2inputsettings.h @@ -19,7 +19,6 @@ #include #include -#include struct BladeRF2InputSettings { typedef enum { diff --git a/plugins/samplesource/soapysdrinput/CMakeLists.txt b/plugins/samplesource/soapysdrinput/CMakeLists.txt index 9e461fb3c..a6606a303 100644 --- a/plugins/samplesource/soapysdrinput/CMakeLists.txt +++ b/plugins/samplesource/soapysdrinput/CMakeLists.txt @@ -6,16 +6,16 @@ set(soapysdrinput_SOURCES soapysdrinputgui.cpp soapysdrinput.cpp soapysdrinputplugin.cpp -# soapysdrinputsettings.cpp -# soapysdrinputthread.cpp + soapysdrinputsettings.cpp + soapysdrinputthread.cpp ) set(soapysdrinput_HEADERS soapysdrinputgui.h soapysdrinput.h soapysdrinputplugin.h -# soapysdrinputsettings.h -# soapysdrinputthread.h + soapysdrinputsettings.h + soapysdrinputthread.h ) set(soapysdrinput_FORMS diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 9bea83d1a..6c0575e0a 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -23,10 +23,12 @@ #include "dsp/dspengine.h" #include "soapysdr/devicesoapysdr.h" +#include "soapysdrinputthread.h" #include "soapysdrinput.h" SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) : m_deviceAPI(deviceAPI), + m_thread(0), m_deviceDescription("SoapySDRInput"), m_running(false) { @@ -131,9 +133,9 @@ void SoapySDRInput::closeDevice() stop(); } -// if (m_thread) { // stills own the thread => transfer to a buddy -// moveThreadToBuddy(); -// } + if (m_thread) { // stills own the thread => transfer to a buddy + moveThreadToBuddy(); + } m_deviceShared.m_channel = -1; // publicly release channel m_deviceShared.m_source = 0; @@ -152,6 +154,23 @@ void SoapySDRInput::init() { } +void SoapySDRInput::moveThreadToBuddy() +{ + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + SoapySDRInput *buddySource = ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source; + + if (buddySource) + { + buddySource->setThread(m_thread); + m_thread = 0; // zero for others + } + } +} + bool SoapySDRInput::start() { return false; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h index 2c03ea257..830aa0ebd 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -25,6 +25,7 @@ #include "dsp/devicesamplesource.h" class DeviceSourceAPI; +class SoapySDRInputThread; class SoapySDRInput : public DeviceSampleSource { @@ -36,6 +37,8 @@ public: virtual void init(); virtual bool start(); virtual void stop(); + SoapySDRInputThread *getThread() { return m_thread; } + void setThread(SoapySDRInputThread *thread) { m_thread = thread; } virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); @@ -51,11 +54,13 @@ public: private: DeviceSourceAPI *m_deviceAPI; DeviceSoapySDRShared m_deviceShared; + SoapySDRInputThread *m_thread; QString m_deviceDescription; bool m_running; bool openDevice(); void closeDevice(); + void moveThreadToBuddy(); }; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp new file mode 100644 index 000000000..ba8e75e2a --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "soapysdrinputsettings.h" + +SoapySDRInputSettings::SoapySDRInputSettings() +{ + resetToDefaults(); +} + +void SoapySDRInputSettings::resetToDefaults() +{ + m_centerFrequency = 435000*1000; + m_LOppmTenths = 0; + m_devSampleRate = 1024000; + m_log2Decim = 0; + m_fcPos = FC_POS_CENTER; + m_dcBlock = false; + m_iqCorrection = false; + m_transverterMode = false; + m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; +} + +QByteArray SoapySDRInputSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_devSampleRate); + s.writeU32(2, m_log2Decim); + s.writeS32(3, (int) m_fcPos); + s.writeBool(4, m_dcBlock); + s.writeBool(5, m_iqCorrection); + s.writeS32(6, m_LOppmTenths); + s.writeBool(7, m_transverterMode); + s.writeS64(8, m_transverterDeltaFrequency); + + return s.final(); +} + +bool SoapySDRInputSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + int intval; + + d.readS32(1, &m_devSampleRate, 1024000); + d.readU32(2, &m_log2Decim); + d.readS32(3, &intval, (int) FC_POS_CENTER); + m_fcPos = (fcPos_t) intval; + d.readBool(4, &m_dcBlock); + d.readBool(5, &m_iqCorrection); + d.readS32(6, &m_LOppmTenths); + d.readBool(7, &m_transverterMode, false); + d.readS64(8, &m_transverterDeltaFrequency, 0); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h new file mode 100644 index 000000000..53c967b03 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTSETTINGS_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTSETTINGS_H_ + +#include +#include + +struct SoapySDRInputSettings { + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + quint64 m_centerFrequency; + qint32 m_LOppmTenths; + qint32 m_devSampleRate; + quint32 m_log2Decim; + fcPos_t m_fcPos; + bool m_dcBlock; + bool m_iqCorrection; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; + + SoapySDRInputSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTSETTINGS_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp new file mode 100644 index 000000000..96748ec87 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp @@ -0,0 +1,532 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "dsp/samplesinkfifo.h" +#include "soapysdr/devicesoapysdr.h" + +#include "soapysdrinputthread.h" + +SoapySDRInputThread::SoapySDRInputThread(SoapySDR::Device* dev, unsigned int nbRxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_sampleRate(0), + m_nbChannels(nbRxChannels), + m_decimatorType(DecimatorFloat) +{ + qDebug("SoapySDRInputThread::SoapySDRInputThread"); + m_channels = new Channel[nbRxChannels]; + + for (unsigned int i = 0; i < nbRxChannels; i++) { + m_channels[i].m_convertBuffer.resize(DeviceSoapySDR::blockSize, Sample{0,0}); + } + + m_buf = new qint16[2*DeviceSoapySDR::blockSize*nbRxChannels]; +} + +SoapySDRInputThread::~SoapySDRInputThread() +{ + qDebug("SoapySDRInputThread::~SoapySDRInputThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_buf; + delete[] m_channels; +} + +void SoapySDRInputThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void SoapySDRInputThread::stopWork() +{ + m_running = false; + wait(); +} + +void SoapySDRInputThread::run() +{ + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if ((m_nbChannels > 0) && (nbFifos > 0)) + { + // build channels list + std::vector channels(m_nbChannels); + std::iota(channels.begin(), channels.end(), 0); // Fill with 0, 1, ..., m_nbChannels-1. + + //initialize the sample rate for all channels + for (const auto &it : channels) { + m_dev->setSampleRate(SOAPY_SDR_RX, it, m_sampleRate); + } + + // Determine sample format to be used + double fullScale(0.0); + std::string format = m_dev->getNativeStreamFormat(SOAPY_SDR_RX, channels.front(), fullScale); + + if ((format == "CF8") && (fullScale == 128.0)) { // 8 bit signed - native + m_decimatorType = Decimator8; + } else if ((format == "CF16") && (fullScale == 2048.0)) { // 12 bit signed - native + m_decimatorType = Decimator12; + } else if ((format == "CF16") && (fullScale == 32768.0)) { // 16 bit signed - native + m_decimatorType = Decimator16; + } else { // for other types make a conversion to float + m_decimatorType = DecimatorFloat; + format = "CF32"; + } + + unsigned int elemSize = SoapySDR::formatToSize(format); // sample (I+Q) size in bytes + SoapySDR::Stream *stream = m_dev->setupStream(SOAPY_SDR_RX, format, channels); + + //allocate buffers for the stream read/write + const unsigned int numElems = m_dev->getStreamMTU(stream); // number of samples (I+Q) + std::vector> buffMem(m_nbChannels, std::vector(elemSize*numElems)); + std::vector buffs(m_nbChannels); + + for (std::size_t i = 0; i < m_nbChannels; i++) { + buffs[i] = buffMem[i].data(); + } + + qDebug("SoapySDRInputThread::run: start running loop"); + m_dev->activateStream(stream); + int flags(0); + long long timeNs(0); + + while (m_running) + { + int ret = m_dev->readStream(stream, buffs.data(), numElems, flags, timeNs); + + if (ret < 0) + { + qCritical("SoapySDRInputThread::run: Unexpected read stream error: %s", SoapySDR::errToStr(ret)); + break; + } + + if (m_nbChannels > 1) + { + callbackMI(buffs, (elemSize/2)*numElems); + } + else + { + switch (m_decimatorType) + { + case Decimator8: + callbackSI8((const qint8*) buffs[0], (elemSize/2)*numElems); + break; + case Decimator12: + callbackSI12((const qint16*) buffs[0], (elemSize/2)*numElems); + break; + case Decimator16: + callbackSI16((const qint16*) buffs[0], (elemSize/2)*numElems); + break; + case DecimatorFloat: + default: + callbackSIF((const float*) buffs[0], (elemSize/2)*numElems); + } + } + } + + qDebug("SoapySDRInputThread::run: stop running loop"); + m_dev->deactivateStream(stream); + } + else + { + qWarning("SoapySDRInputThread::run: no channels or FIFO allocated. Aborting"); + } + + m_running = false; +} + +unsigned int SoapySDRInputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (unsigned int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void SoapySDRInputThread::setLog2Decimation(unsigned int channel, unsigned int log2_decim) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_log2Decim = log2_decim; + } +} + +unsigned int SoapySDRInputThread::getLog2Decimation(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_log2Decim; + } else { + return 0; + } +} + +void SoapySDRInputThread::setFcPos(unsigned int channel, int fcPos) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_fcPos = fcPos; + } +} + +int SoapySDRInputThread::getFcPos(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_fcPos; + } else { + return 0; + } +} + +void SoapySDRInputThread::setFifo(unsigned int channel, SampleSinkFifo *sampleFifo) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSinkFifo *SoapySDRInputThread::getFifo(unsigned int channel) +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void SoapySDRInputThread::callbackMI(std::vector& buffs, qint32 samplesPerChannel) +{ + for(unsigned int ichan = 0; ichan < m_nbChannels; ichan++) + { + switch (m_decimatorType) + { + case Decimator8: + callbackSI8((const qint8*) buffs[ichan], samplesPerChannel, ichan); + break; + case Decimator12: + callbackSI12((const qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case Decimator16: + callbackSI16((const qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case DecimatorFloat: + default: + callbackSIF((const float*) buffs[ichan], samplesPerChannel, ichan); + } + } +} + +void SoapySDRInputThread::callbackSI8(const qint8* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators8.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators8.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators8.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators8.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators8.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators8.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators8.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators8.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators8.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators8.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators8.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators8.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators8.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators8.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators8.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators8.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators8.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators8.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators8.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} + +void SoapySDRInputThread::callbackSI12(const qint16* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators12.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators12.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators12.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators12.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators12.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators12.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators12.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators12.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators12.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators12.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators12.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators12.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators12.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators12.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators12.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators12.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators12.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators12.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators12.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} + +void SoapySDRInputThread::callbackSI16(const qint16* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators16.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators16.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators16.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators16.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators16.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators16.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators16.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators16.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators16.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators16.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators16.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators16.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators16.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators16.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators16.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators16.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators16.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators16.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators16.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.h b/plugins/samplesource/soapysdrinput/soapysdrinputthread.h new file mode 100644 index 000000000..6118926f7 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.h @@ -0,0 +1,108 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTTHREAD_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTTHREAD_H_ + +// SoapySDR is a device wrapper with a single stream supporting one or many Rx +// Therefore only one thread can be allocated for the Rx side +// All FIFOs must be registered before calling startWork() + +#include +#include +#include + +#include + +#include "soapysdr/devicesoapysdrshared.h" +#include "dsp/decimators.h" +#include "dsp/decimatorsfi.h" + +class SampleSinkFifo; + +class SoapySDRInputThread : public QThread { + Q_OBJECT + +public: + SoapySDRInputThread(SoapySDR::Device* dev, unsigned int nbRxChannels, QObject* parent = NULL); + ~SoapySDRInputThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + unsigned int getNbChannels() const { return m_nbChannels; } + void setLog2Decimation(unsigned int channel, unsigned int log2_decim); + unsigned int getLog2Decimation(unsigned int channel) const; + void setSampleRate(unsigned int sampleRate) { m_sampleRate = sampleRate; } + unsigned int getSampleRate() const { return m_sampleRate; } + void setFcPos(unsigned int channel, int fcPos); + int getFcPos(unsigned int channel) const; + void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); + SampleSinkFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleVector m_convertBuffer; + SampleSinkFifo* m_sampleFifo; + unsigned int m_log2Decim; + int m_fcPos; + Decimators m_decimators8; + Decimators m_decimators12; + Decimators m_decimators16; + DecimatorsFI m_decimatorsFloat; + + Channel() : + m_sampleFifo(0), + m_log2Decim(0), + m_fcPos(0) + {} + + ~Channel() + {} + }; + + enum DecimatorType + { + Decimator8, + Decimator12, + Decimator16, + DecimatorFloat + }; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + SoapySDR::Device* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Rx channels + unsigned int m_sampleRate; + qint16 *m_buf; //!< Full buffer for SISO or MIMO operation + unsigned int m_nbChannels; + DecimatorType m_decimatorType; + + void run(); + unsigned int getNbFifos(); + void callbackSI8(const qint8* buf, qint32 len, unsigned int channel = 0); + void callbackSI12(const qint16* buf, qint32 len, unsigned int channel = 0); + void callbackSI16(const qint16* buf, qint32 len, unsigned int channel = 0); + void callbackSIF(const float* buf, qint32 len, unsigned int channel = 0); + void callbackMI(std::vector& buffs, qint32 samplesPerChannel); +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTTHREAD_H_ */ From ee6a7e465362ea67a82de8abb4b3949a1927bf33 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 31 Oct 2018 01:26:21 +0100 Subject: [PATCH 914/956] SoapySDR support: device parameters --- devices/soapysdr/CMakeLists.txt | 2 + devices/soapysdr/devicesoapysdrparams.cpp | 87 +++++++++++++++++++++++ devices/soapysdr/devicesoapysdrparams.h | 80 +++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 devices/soapysdr/devicesoapysdrparams.cpp create mode 100644 devices/soapysdr/devicesoapysdrparams.h diff --git a/devices/soapysdr/CMakeLists.txt b/devices/soapysdr/CMakeLists.txt index c410e6ee2..55ef5369b 100644 --- a/devices/soapysdr/CMakeLists.txt +++ b/devices/soapysdr/CMakeLists.txt @@ -6,12 +6,14 @@ set(soapysdrdevice_SOURCES devicesoapysdr.cpp devicesoapysdrscan.cpp devicesoapysdrshared.cpp + devicesoapysdrparams.cpp ) set(soapysdrdevice_HEADERS devicesoapysdr.h devicesoapysdrscan.h devicesoapysdrshared.h + devicesoapysdrparams.h ) if (BUILD_DEBIAN) diff --git a/devices/soapysdr/devicesoapysdrparams.cpp b/devices/soapysdr/devicesoapysdrparams.cpp new file mode 100644 index 000000000..482567b30 --- /dev/null +++ b/devices/soapysdr/devicesoapysdrparams.cpp @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "devicesoapysdrparams.h" + +DeviceSoapySDRParams::DeviceSoapySDRParams(SoapySDR::Device *device) : + m_device(device) +{ + fillParams(); +} + +DeviceSoapySDRParams::~DeviceSoapySDRParams() +{} + +void DeviceSoapySDRParams::fillParams() +{ + m_deviceSettingsArgs = m_device->getSettingInfo(); + m_nbRx = m_device->getNumChannels(SOAPY_SDR_RX); + m_nbTx = m_device->getNumChannels(SOAPY_SDR_TX); + + for (unsigned int ichan = 0; ichan < m_nbRx; ichan++) { + fillChannelParams(m_RxChannelsSettings, SOAPY_SDR_RX, ichan); + } + + for (unsigned int ichan = 0; ichan < m_nbTx; ichan++) { + fillChannelParams(m_TxChannelsSettings, SOAPY_SDR_TX, ichan); + } +} + +void DeviceSoapySDRParams::fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan) +{ + channelSettings.push_back(ChannelSetting()); + + channelSettings.back().m_streamSettingsArgs = m_device->getStreamArgsInfo(direction, ichan); + channelSettings.back().m_antennas = m_device->listAntennas(direction, ichan); + channelSettings.back().m_hasDCAutoCorrection = m_device->hasDCOffsetMode(direction, ichan); + channelSettings.back().m_hasDCOffsetValue = m_device->hasDCOffset(direction, ichan); + channelSettings.back().m_hasIQBalanceValue = m_device->hasIQBalance(direction, ichan); + channelSettings.back().m_hasFrequencyCorrectionValue = m_device->hasFrequencyCorrection(direction, ichan); + + // gains + + channelSettings.back().m_hasAGC = m_device->hasGainMode(direction, ichan); + channelSettings.back().m_gainRange = m_device->getGainRange(direction, ichan); + std::vector gainsList = m_device->listGains(direction, ichan); + + for (const auto &it : gainsList) + { + channelSettings.back().m_gainSettings.push_back(GainSetting()); + channelSettings.back().m_gainSettings.back().m_name = it; + channelSettings.back().m_gainSettings.back().m_range = m_device->getGainRange(direction, ichan, it); + } + + // frequencies + + std::vector freqsList = m_device->listFrequencies(direction, ichan); + + for (const auto &it : freqsList) + { + channelSettings.back().m_frequencySettings.push_back(FrequencySetting()); + channelSettings.back().m_frequencySettings.back().m_name = it; + channelSettings.back().m_frequencySettings.back().m_ranges = m_device->getFrequencyRange(direction, ichan, it); + } + + channelSettings.back().m_frequencySettingsArgs = m_device->getFrequencyArgsInfo(direction, ichan); + + // sample rates + + channelSettings.back().m_ratesRanges = m_device->getSampleRateRange(direction, ichan); + + // bandwidths + + channelSettings.back().m_bandwidthsRanges = m_device->getBandwidthRange(direction, ichan); +} diff --git a/devices/soapysdr/devicesoapysdrparams.h b/devices/soapysdr/devicesoapysdrparams.h new file mode 100644 index 000000000..447736ac8 --- /dev/null +++ b/devices/soapysdr/devicesoapysdrparams.h @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 DEVICES_SOAPYSDR_DEVICESOAPYSDRPARAMS_H_ +#define DEVICES_SOAPYSDR_DEVICESOAPYSDRPARAMS_H_ + +#include +#include + +#include "export.h" + +/** + * This structure refers to one physical device shared among parties (logical devices represented by + * the DeviceSinkAPI or DeviceSourceAPI). + * It allows storing information on the common resources in one place and is shared among participants. + * There is only one copy that is constructed by the first participant and destroyed by the last. + * A participant knows it is the first or last by checking the lists of buddies (Rx + Tx). + */ + +class DEVICES_API DeviceSoapySDRParams +{ +public: + struct GainSetting + { + std::string m_name; //!< Name of the gain element + SoapySDR::Range m_range; //!< Gain range + }; + + struct FrequencySetting + { + std::string m_name; //!< Name of the tunable element + SoapySDR::RangeList m_ranges; //!< List of ranges of the tunable element + }; + + struct ChannelSetting + { + SoapySDR::ArgInfoList m_streamSettingsArgs; //!< common stream parameters + bool m_hasDCAutoCorrection; //!< DC offset auto correction flag + bool m_hasDCOffsetValue; //!< DC offset value flag + bool m_hasIQBalanceValue; //!< IQ correction value flag + bool m_hasFrequencyCorrectionValue; //!< Frequency correction value flag + std::vector m_antennas; //!< Antenna ports names + bool m_hasAGC; //!< AGC flag + SoapySDR::Range m_gainRange; //!< Global gain range + std::vector m_gainSettings; //!< gain elements settings + std::vector m_frequencySettings; //!< tunable elements settings + SoapySDR::ArgInfoList m_frequencySettingsArgs; //!< common tuning parameters + SoapySDR::RangeList m_ratesRanges; //!< list of ranges of sample rates + SoapySDR::RangeList m_bandwidthsRanges; //!< list of ranges of bandwidths + }; + + DeviceSoapySDRParams(SoapySDR::Device *device); + ~DeviceSoapySDRParams(); + +private: + void fillParams(); + void fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan); + SoapySDR::Device *m_device; + SoapySDR::ArgInfoList m_deviceSettingsArgs; //!< list (vector) of device settings arguments + uint32_t m_nbRx; //!< number of Rx channels + uint32_t m_nbTx; //!< number of Tx channels + std::vector m_RxChannelsSettings; + std::vector m_TxChannelsSettings; +}; + + +#endif /* DEVICES_SOAPYSDR_DEVICESOAPYSDRPARAMS_H_ */ From 9c459ca33679442739692f5dc437f6a7001c14f6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 31 Oct 2018 12:22:46 +0100 Subject: [PATCH 915/956] SoapySDR support: get and print device parameters applied to input --- devices/soapysdr/devicesoapysdrparams.cpp | 175 ++++++++ devices/soapysdr/devicesoapysdrparams.h | 33 ++ devices/soapysdr/devicesoapysdrshared.h | 2 + .../samplesource/soapysdrinput/CMakeLists.txt | 2 +- .../soapysdrinput/soapysdrinput.cpp | 9 +- .../soapysdrinput/soapysdrinputgui.cpp | 8 +- .../soapysdrinput/soapysdrinputgui.ui | 411 ++++++++++++++++++ 7 files changed, 637 insertions(+), 3 deletions(-) create mode 100644 plugins/samplesource/soapysdrinput/soapysdrinputgui.ui diff --git a/devices/soapysdr/devicesoapysdrparams.cpp b/devices/soapysdr/devicesoapysdrparams.cpp index 482567b30..4baf11e3a 100644 --- a/devices/soapysdr/devicesoapysdrparams.cpp +++ b/devices/soapysdr/devicesoapysdrparams.cpp @@ -14,12 +14,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include +#include + +#include + #include "devicesoapysdrparams.h" DeviceSoapySDRParams::DeviceSoapySDRParams(SoapySDR::Device *device) : m_device(device) { fillParams(); + printParams(); } DeviceSoapySDRParams::~DeviceSoapySDRParams() @@ -85,3 +91,172 @@ void DeviceSoapySDRParams::fillChannelParams(std::vector& channe channelSettings.back().m_bandwidthsRanges = m_device->getBandwidthRange(direction, ichan); } + +void DeviceSoapySDRParams::printParams() +{ + qDebug() << "DeviceSoapySDRParams::printParams: m_deviceSettingsArgs:\n" << argInfoListToString(m_deviceSettingsArgs).c_str(); + int ichan = 0; + + for (const auto &it : m_RxChannelsSettings) + { + qDebug() << "DeviceSoapySDRParams::printParams: Rx channel " << ichan; + printChannelParams(it); + ichan++; + } + + ichan = 0; + + for (const auto &it : m_TxChannelsSettings) + { + qDebug() << "DeviceSoapySDRParams::printParams: Tx channel " << ichan; + printChannelParams(it); + ichan++; + } +} + +void DeviceSoapySDRParams::printChannelParams(const ChannelSetting& channelSetting) +{ + qDebug() << "DeviceSoapySDRParams::printParams: m_streamSettingsArgs:\n" << argInfoListToString(channelSetting.m_streamSettingsArgs).c_str(); + qDebug() << "DeviceSoapySDRParams::printParams:" + << " m_hasDCAutoCorrection: " << channelSetting.m_hasDCAutoCorrection + << " m_hasDCOffsetValue: " << channelSetting.m_hasDCOffsetValue + << " m_hasIQBalanceValue: " << channelSetting.m_hasIQBalanceValue + << " m_hasFrequencyCorrectionValue: " << channelSetting.m_hasFrequencyCorrectionValue + << " m_hasAGC: " << channelSetting.m_hasAGC; + qDebug() << "DeviceSoapySDRParams::printParams: m_antennas: " << vectorToString(channelSetting.m_antennas).c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_gainRange: " << rangeToString(channelSetting.m_gainRange).c_str(); + + qDebug() << "DeviceSoapySDRParams::printParams: individual gains..."; + + for (const auto &gainIt : channelSetting.m_gainSettings) + { + qDebug() << "DeviceSoapySDRParams::printParams: m_name: " << gainIt.m_name.c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_range: " << rangeToString(gainIt.m_range).c_str(); + } + + qDebug() << "DeviceSoapySDRParams::printParams: tunable elements..."; + + for (const auto &freqIt : channelSetting.m_frequencySettings) + { + qDebug() << "DeviceSoapySDRParams::printParams: m_name: " << freqIt.m_name.c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_range (kHz): " << rangeListToString(freqIt.m_ranges, 1e3).c_str(); + } + + qDebug() << "DeviceSoapySDRParams::printParams: m_frequencySettingsArgs:\n" << argInfoListToString(channelSetting.m_frequencySettingsArgs).c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_ratesRanges (kHz): " << rangeListToString(channelSetting.m_ratesRanges, 1e3).c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_bandwidthsRanges (kHz): " << rangeListToString(channelSetting.m_bandwidthsRanges, 1e3).c_str(); +} + +std::string DeviceSoapySDRParams::argInfoToString(const SoapySDR::ArgInfo &argInfo, const std::string indent) +{ + std::stringstream ss; + + //name, or use key if missing + std::string name = argInfo.name; + if (argInfo.name.empty()) name = argInfo.key; + ss << indent << " * " << name; + + //optional description + std::string desc = argInfo.description; + const std::string replace("\n"+indent+" "); + + for (std::size_t pos = 0; (pos=desc.find("\n", pos)) != std::string::npos; pos+=replace.size()) { + desc.replace(pos, 1, replace); + } + + if (not desc.empty()) { + ss << " - " << desc << std::endl << indent << " "; + } + + //other fields + ss << " [key=" << argInfo.key; + + if (not argInfo.units.empty()) { + ss << ", units=" << argInfo.units; + } + + if (not argInfo.value.empty()) { + ss << ", default=" << argInfo.value; + } + + //type + switch (argInfo.type) + { + case SoapySDR::ArgInfo::BOOL: + ss << ", type=bool"; + break; + case SoapySDR::ArgInfo::INT: + ss << ", type=int"; + break; + case SoapySDR::ArgInfo::FLOAT: + ss << ", type=float"; + break; + case SoapySDR::ArgInfo::STRING: + ss << ", type=string"; + break; + } + + //optional range/enumeration + if (argInfo.range.minimum() < argInfo.range.maximum()) { + ss << ", range=" << rangeToString(argInfo.range); + } + + if (not argInfo.options.empty()) { + ss << ", options=(" << vectorToString(argInfo.options) << ")"; + } + + ss << "]"; + + return ss.str(); +} + +std::string DeviceSoapySDRParams::argInfoListToString(const SoapySDR::ArgInfoList &argInfos) +{ + std::stringstream ss; + + for (std::size_t i = 0; i < argInfos.size(); i++) { + ss << argInfoToString(argInfos[i]) << std::endl; + } + + return ss.str(); +} + +std::string DeviceSoapySDRParams::rangeToString(const SoapySDR::Range &range) +{ + std::stringstream ss; + ss << "[" << range.minimum() << ", " << range.maximum(); + + if (range.step() != 0.0) { + ss << ", " << range.step(); + } + + ss << "]"; + return ss.str(); +} + +std::string DeviceSoapySDRParams::rangeListToString(const SoapySDR::RangeList &range, const double scale) +{ + const std::size_t MAXRLEN = 10; //for abbreviating long lists + std::stringstream ss; + + for (std::size_t i = 0; i < range.size(); i++) + { + if (range.size() >= MAXRLEN and i >= MAXRLEN/2 and i < (range.size()-MAXRLEN/2)) + { + if (i == MAXRLEN) ss << ", ..."; + continue; + } + + if (not ss.str().empty()) { + ss << ", "; + } + + if (range[i].minimum() == range[i].maximum()) { + ss << (range[i].minimum()/scale); + } else { + ss << "[" << (range[i].minimum()/scale) << ", " << (range[i].maximum()/scale) << "]"; + } + } + + return ss.str(); +} diff --git a/devices/soapysdr/devicesoapysdrparams.h b/devices/soapysdr/devicesoapysdrparams.h index 447736ac8..09d5c4846 100644 --- a/devices/soapysdr/devicesoapysdrparams.h +++ b/devices/soapysdr/devicesoapysdrparams.h @@ -18,6 +18,9 @@ #define DEVICES_SOAPYSDR_DEVICESOAPYSDRPARAMS_H_ #include +#include +#include + #include #include "export.h" @@ -68,6 +71,36 @@ public: private: void fillParams(); void fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan); + void printParams(); + void printChannelParams(const ChannelSetting& channelSetting); + + // Printing functions copied from SoapySDR's SoapySDRProbe.cpp + std::string argInfoToString(const SoapySDR::ArgInfo &argInfo, const std::string indent = " "); + std::string argInfoListToString(const SoapySDR::ArgInfoList &argInfos); + std::string rangeToString(const SoapySDR::Range &range); + std::string rangeListToString(const SoapySDR::RangeList &range, const double scale); + + template + std::string vectorToString(const std::vector &options) + { + std::stringstream ss; + + if (options.empty()) { + return ""; + } + + for (std::size_t i = 0; i < options.size(); i++) + { + if (not ss.str().empty()) { + ss << ", "; + } + + ss << options[i]; + } + + return ss.str(); + } + SoapySDR::Device *m_device; SoapySDR::ArgInfoList m_deviceSettingsArgs; //!< list (vector) of device settings arguments uint32_t m_nbRx; //!< number of Rx channels diff --git a/devices/soapysdr/devicesoapysdrshared.h b/devices/soapysdr/devicesoapysdrshared.h index 4fce21a2b..8ab019ba1 100644 --- a/devices/soapysdr/devicesoapysdrshared.h +++ b/devices/soapysdr/devicesoapysdrshared.h @@ -20,6 +20,7 @@ #include #include "export.h" +#include "devicesoapysdrparams.h" class SoapySDRInput; class SoapySDROutput; @@ -34,6 +35,7 @@ public: ~DeviceSoapySDRShared(); SoapySDR::Device *m_device; + DeviceSoapySDRParams *m_deviceParams; int m_channel; //!< allocated channel (-1 if none) SoapySDRInput *m_source; SoapySDROutput *m_sink; diff --git a/plugins/samplesource/soapysdrinput/CMakeLists.txt b/plugins/samplesource/soapysdrinput/CMakeLists.txt index a6606a303..b87a6567f 100644 --- a/plugins/samplesource/soapysdrinput/CMakeLists.txt +++ b/plugins/samplesource/soapysdrinput/CMakeLists.txt @@ -19,7 +19,7 @@ set(soapysdrinput_HEADERS ) set(soapysdrinput_FORMS -# soapysdrinputgui.ui + soapysdrinputgui.ui ) if (BUILD_DEBIAN) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 6c0575e0a..8622b31c9 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -32,6 +32,7 @@ SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) : m_deviceDescription("SoapySDRInput"), m_running(false) { + openDevice(); } SoapySDRInput::~SoapySDRInput() @@ -78,6 +79,7 @@ bool SoapySDRInput::openDevice() } m_deviceShared.m_device = device; + m_deviceShared.m_deviceParams = deviceSoapySDRShared->m_deviceParams; } // look for Tx buddies and get reference to the device object else if (m_deviceAPI->getSinkBuddies().size() > 0) // then sink @@ -102,6 +104,7 @@ bool SoapySDRInput::openDevice() } m_deviceShared.m_device = device; + m_deviceShared.m_deviceParams = deviceSoapySDRShared->m_deviceParams; } // There are no buddies then create the first SoapySDR device else @@ -115,6 +118,8 @@ bool SoapySDRInput::openDevice() qCritical("BladeRF2Input::openDevice: cannot open BladeRF2 device"); return false; } + + m_deviceShared.m_deviceParams = new DeviceSoapySDRParams(m_deviceShared.m_device); } m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel @@ -140,10 +145,12 @@ void SoapySDRInput::closeDevice() m_deviceShared.m_channel = -1; // publicly release channel m_deviceShared.m_source = 0; - // No buddies so effectively close the device + // No buddies so effectively close the device and delete parameters if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) { + delete m_deviceShared.m_deviceParams; + m_deviceShared.m_deviceParams = 0; DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); deviceSoapySDR.closeSoapySdr(m_deviceShared.m_device); m_deviceShared.m_device = 0; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index fe29fb4d0..ec07b97f3 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -20,22 +20,28 @@ #include "device/deviceuiset.h" #include "util/simpleserializer.h" +#include "ui_soapysdrinputgui.h" #include "soapysdrinputgui.h" SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : QWidget(parent), - ui(0), + ui(new Ui::SoapySDRInputGui), m_deviceUISet(deviceUISet), m_forceSettings(true), m_doApplySettings(true), m_sampleSource(0), m_sampleRate(0), + m_deviceCenterFrequency(0), m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { + m_sampleSource = (SoapySDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); + ui->setupUi(this); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); } SoapySDRInputGui::~SoapySDRInputGui() { + delete ui; } void SoapySDRInputGui::destroy() diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui new file mode 100644 index 000000000..30049db37 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -0,0 +1,411 @@ + + + SoapySDRInputGui + + + + 0 + 0 + 324 + 276 + + + + + 0 + 0 + + + + + 320 + 132 + + + + + Liberation Sans + 9 + + + + SoapySDR + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + Toggle record I/Q samples from device + + + + + + + :/record_off.png:/record_off.png + + + + + + + + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + true + + + + + 0 + 0 + 304 + 128 + + + + + 0 + 0 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Data1 + + + + + + + + 0 + + + + + 1 + + + + + 2 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Kiki + + + + + + + Qt::Horizontal + + + + + + + 0.0 + + + + + + + + + + + TextLabel + + + + + + + ... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Zozo + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Didi + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + ValueDial + QWidget +
    gui/valuedial.h
    + 1 +
    + + ButtonSwitch + QToolButton +
    gui/buttonswitch.h
    +
    +
    + + + + +
    From cc08f42ea620f268fe2b6fb51d434eb762c8ec0c Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Nov 2018 02:32:26 +0100 Subject: [PATCH 916/956] SoapySDR support: GUI component to deal with ranges with discrete values --- devices/soapysdr/devicesoapysdrparams.cpp | 28 ++++---- devices/soapysdr/devicesoapysdrparams.h | 28 ++++++-- .../samplesource/soapysdrinput/CMakeLists.txt | 3 + .../soapysdrinput/discreterangegui.cpp | 59 +++++++++++++++ .../soapysdrinput/discreterangegui.h | 52 ++++++++++++++ .../soapysdrinput/discreterangegui.ui | 48 +++++++++++++ .../soapysdrinput/soapysdrinput.cpp | 34 +++++++++ .../soapysdrinput/soapysdrinput.h | 28 ++++++++ .../soapysdrinput/soapysdrinputgui.cpp | 62 ++++++++++++++++ .../soapysdrinput/soapysdrinputgui.h | 3 + .../soapysdrinput/soapysdrinputgui.ui | 71 ++++--------------- 11 files changed, 340 insertions(+), 76 deletions(-) create mode 100644 plugins/samplesource/soapysdrinput/discreterangegui.cpp create mode 100644 plugins/samplesource/soapysdrinput/discreterangegui.h create mode 100644 plugins/samplesource/soapysdrinput/discreterangegui.ui diff --git a/devices/soapysdr/devicesoapysdrparams.cpp b/devices/soapysdr/devicesoapysdrparams.cpp index 4baf11e3a..133f7efee 100644 --- a/devices/soapysdr/devicesoapysdrparams.cpp +++ b/devices/soapysdr/devicesoapysdrparams.cpp @@ -46,9 +46,9 @@ void DeviceSoapySDRParams::fillParams() } } -void DeviceSoapySDRParams::fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan) +void DeviceSoapySDRParams::fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan) { - channelSettings.push_back(ChannelSetting()); + channelSettings.push_back(ChannelSettings()); channelSettings.back().m_streamSettingsArgs = m_device->getStreamArgsInfo(direction, ichan); channelSettings.back().m_antennas = m_device->listAntennas(direction, ichan); @@ -114,7 +114,7 @@ void DeviceSoapySDRParams::printParams() } } -void DeviceSoapySDRParams::printChannelParams(const ChannelSetting& channelSetting) +void DeviceSoapySDRParams::printChannelParams(const ChannelSettings& channelSetting) { qDebug() << "DeviceSoapySDRParams::printParams: m_streamSettingsArgs:\n" << argInfoListToString(channelSetting.m_streamSettingsArgs).c_str(); qDebug() << "DeviceSoapySDRParams::printParams:" @@ -236,25 +236,27 @@ std::string DeviceSoapySDRParams::rangeToString(const SoapySDR::Range &range) std::string DeviceSoapySDRParams::rangeListToString(const SoapySDR::RangeList &range, const double scale) { - const std::size_t MAXRLEN = 10; //for abbreviating long lists std::stringstream ss; for (std::size_t i = 0; i < range.size(); i++) { - if (range.size() >= MAXRLEN and i >= MAXRLEN/2 and i < (range.size()-MAXRLEN/2)) - { - if (i == MAXRLEN) ss << ", ..."; - continue; - } - if (not ss.str().empty()) { ss << ", "; } - if (range[i].minimum() == range[i].maximum()) { + if (range[i].minimum() == range[i].maximum()) + { ss << (range[i].minimum()/scale); - } else { - ss << "[" << (range[i].minimum()/scale) << ", " << (range[i].maximum()/scale) << "]"; + } + else + { + ss << "[" << (range[i].minimum()/scale) << ", " << (range[i].maximum()/scale); + + if (range[i].step() != 0.0) { + ss << ", " << (range[i].step()/scale); + } + + ss << "]"; } } diff --git a/devices/soapysdr/devicesoapysdrparams.h b/devices/soapysdr/devicesoapysdrparams.h index 09d5c4846..67baf5cac 100644 --- a/devices/soapysdr/devicesoapysdrparams.h +++ b/devices/soapysdr/devicesoapysdrparams.h @@ -48,7 +48,7 @@ public: SoapySDR::RangeList m_ranges; //!< List of ranges of the tunable element }; - struct ChannelSetting + struct ChannelSettings { SoapySDR::ArgInfoList m_streamSettingsArgs; //!< common stream parameters bool m_hasDCAutoCorrection; //!< DC offset auto correction flag @@ -68,11 +68,29 @@ public: DeviceSoapySDRParams(SoapySDR::Device *device); ~DeviceSoapySDRParams(); + const ChannelSettings* getRxChannelSettings(uint32_t index) + { + if (index < m_nbRx) { + return &m_RxChannelsSettings[index]; + } else { + return 0; + } + } + + const ChannelSettings* getTxChannelSettings(uint32_t index) + { + if (index < m_nbTx) { + return &m_TxChannelsSettings[index]; + } else { + return 0; + } + } + private: void fillParams(); - void fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan); + void fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan); void printParams(); - void printChannelParams(const ChannelSetting& channelSetting); + void printChannelParams(const ChannelSettings& channelSetting); // Printing functions copied from SoapySDR's SoapySDRProbe.cpp std::string argInfoToString(const SoapySDR::ArgInfo &argInfo, const std::string indent = " "); @@ -105,8 +123,8 @@ private: SoapySDR::ArgInfoList m_deviceSettingsArgs; //!< list (vector) of device settings arguments uint32_t m_nbRx; //!< number of Rx channels uint32_t m_nbTx; //!< number of Tx channels - std::vector m_RxChannelsSettings; - std::vector m_TxChannelsSettings; + std::vector m_RxChannelsSettings; + std::vector m_TxChannelsSettings; }; diff --git a/plugins/samplesource/soapysdrinput/CMakeLists.txt b/plugins/samplesource/soapysdrinput/CMakeLists.txt index b87a6567f..ec5f14670 100644 --- a/plugins/samplesource/soapysdrinput/CMakeLists.txt +++ b/plugins/samplesource/soapysdrinput/CMakeLists.txt @@ -8,6 +8,7 @@ set(soapysdrinput_SOURCES soapysdrinputplugin.cpp soapysdrinputsettings.cpp soapysdrinputthread.cpp + discreterangegui.cpp ) set(soapysdrinput_HEADERS @@ -16,10 +17,12 @@ set(soapysdrinput_HEADERS soapysdrinputplugin.h soapysdrinputsettings.h soapysdrinputthread.h + discreterangegui.h ) set(soapysdrinput_FORMS soapysdrinputgui.ui + discreterangegui.ui ) if (BUILD_DEBIAN) diff --git a/plugins/samplesource/soapysdrinput/discreterangegui.cpp b/plugins/samplesource/soapysdrinput/discreterangegui.cpp new file mode 100644 index 000000000..654b3aea0 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/discreterangegui.cpp @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "ui_discreterangegui.h" +#include "discreterangegui.h" + +DiscreteRangeGUI::DiscreteRangeGUI(QWidget* parent) : + QWidget(parent), + ui(new Ui::DiscreteRangeGUI) +{ + ui->setupUi(this); +} + +DiscreteRangeGUI::~DiscreteRangeGUI() +{ + delete ui; +} + +void DiscreteRangeGUI::setLabel(const QString& text) +{ + ui->rangeLabel->setText(text); +} + +void DiscreteRangeGUI::setUnits(const QString& units) +{ + ui->rangeUnits->setText(units); +} + +void DiscreteRangeGUI::addItem(const QString& itemStr, double itemValue) +{ + ui->rangeCombo->blockSignals(true); + ui->rangeCombo->addItem(itemStr); + itemValues.push_back(itemValue); + ui->rangeCombo->blockSignals(false); +} + +double DiscreteRangeGUI::getCurrentValue() +{ + return itemValues[ui->rangeCombo->currentIndex()]; +} + +void DiscreteRangeGUI::on_rangeCombo_currentIndexChanged(int index) +{ + double newRange = itemValues[index]; + emit rangeChanged(newRange); +} diff --git a/plugins/samplesource/soapysdrinput/discreterangegui.h b/plugins/samplesource/soapysdrinput/discreterangegui.h new file mode 100644 index 000000000..291d195d4 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/discreterangegui.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_DISCRETERANGEGUI_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_DISCRETERANGEGUI_H_ + +#include +#include + +namespace Ui { + class DiscreteRangeGUI; +} + +class DiscreteRangeGUI : public QWidget +{ + Q_OBJECT +public: + explicit DiscreteRangeGUI(QWidget* parent = 0); + ~DiscreteRangeGUI(); + + void setLabel(const QString& text); + void setUnits(const QString& units); + void addItem(const QString& itemStr, double itemValue); + double getCurrentValue(); + +signals: + void rangeChanged(double value); + +private slots: + void on_rangeCombo_currentIndexChanged(int index); + +private: + Ui::DiscreteRangeGUI* ui; + std::vector itemValues; +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_DISCRETERANGEGUI_H_ */ diff --git a/plugins/samplesource/soapysdrinput/discreterangegui.ui b/plugins/samplesource/soapysdrinput/discreterangegui.ui new file mode 100644 index 000000000..b8f36aff0 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/discreterangegui.ui @@ -0,0 +1,48 @@ + + + DiscreteRangeGUI + + + + 0 + 0 + 203 + 30 + + + + Form + + + + + 0 + 0 + 172 + 29 + + + + + + + Label + + + + + + + + + + Unit + + + + + + + + + diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 8622b31c9..a765e340a 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -157,6 +157,40 @@ void SoapySDRInput::closeDevice() } } +void SoapySDRInput::getFrequencyRange(uint64_t& min, uint64_t& max) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + + if (channelSettings && (channelSettings->m_frequencySettings.size() > 0)) + { + DeviceSoapySDRParams::FrequencySetting freqSettings = channelSettings->m_frequencySettings[0]; + SoapySDR::RangeList rangeList = freqSettings.m_ranges; + + if (rangeList.size() > 0) // TODO: handle multiple ranges + { + SoapySDR::Range range = rangeList[0]; + min = range.minimum(); + max = range.maximum(); + } + else + { + min = 0; + max = 0; + } + } + else + { + min = 0; + max = 0; + } +} + +const SoapySDR::RangeList& SoapySDRInput::getRateRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_ratesRanges; +} + void SoapySDRInput::init() { } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h index 830aa0ebd..7bde0a771 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -24,12 +24,37 @@ #include "soapysdr/devicesoapysdrshared.h" #include "dsp/devicesamplesource.h" +#include "soapysdrinputsettings.h" + class DeviceSourceAPI; class SoapySDRInputThread; class SoapySDRInput : public DeviceSampleSource { public: + class MsgConfigureSoapySDR : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SoapySDRInputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSoapySDR* create(const SoapySDRInputSettings& settings, bool force) + { + return new MsgConfigureSoapySDR(settings, force); + } + + private: + SoapySDRInputSettings m_settings; + bool m_force; + + MsgConfigureSoapySDR(const SoapySDRInputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + SoapySDRInput(DeviceSourceAPI *deviceAPI); virtual ~SoapySDRInput(); virtual void destroy(); @@ -51,6 +76,9 @@ public: virtual bool handleMessage(const Message& message); + void getFrequencyRange(uint64_t& min, uint64_t& max); + const SoapySDR::RangeList& getRateRanges(); + private: DeviceSourceAPI *m_deviceAPI; DeviceSoapySDRShared m_deviceShared; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index ec07b97f3..7049039d5 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -21,6 +21,7 @@ #include "util/simpleserializer.h" #include "ui_soapysdrinputgui.h" +#include "discreterangegui.h" #include "soapysdrinputgui.h" SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : @@ -36,7 +37,13 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : { m_sampleSource = (SoapySDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); ui->setupUi(this); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + uint64_t f_min, f_max; + m_sampleSource->getFrequencyRange(f_min, f_max); + ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + + createRangesControl(m_sampleSource->getRateRanges(), "Sample Rate", "kS/s"); } SoapySDRInputGui::~SoapySDRInputGui() @@ -49,6 +56,61 @@ void SoapySDRInputGui::destroy() delete this; } +void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit) +{ + if (rangeList.size() == 0) { // return early if the range list is empty + return; + } + + bool rangeDiscrete = true; // discretes values + bool rangeInterval = true; // intervals + + for (const auto &it : rangeList) + { + if (it.minimum() != it.maximum()) { + rangeDiscrete = false; + } else { + rangeInterval = false; + } + } + + if (rangeDiscrete) + { + DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(ui->scrollAreaWidgetContents); + rangeGUI->setLabel(text); + rangeGUI->setUnits(unit); + + for (const auto &it : rangeList) { + rangeGUI->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0)), it.minimum()); + } + +// QHBoxLayout *layout = new QHBoxLayout(); +// QLabel *rangeLabel = new QLabel(); +// rangeLabel->setText(text); +// QLabel *rangeUnit = new QLabel(); +// rangeUnit->setText(unit); +// QComboBox *rangeCombo = new QComboBox(); +// +// for (const auto &it : rangeList) { +// rangeCombo->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0))); +// } +// +// layout->addWidget(rangeLabel); +// layout->addWidget(rangeCombo); +// layout->addWidget(rangeUnit); +// layout->setMargin(0); +// layout->setSpacing(6); +// rangeLabel->show(); +// rangeCombo->show(); +// QWidget *window = new QWidget(ui->scrollAreaWidgetContents); +// window->setFixedWidth(300); +// window->setFixedHeight(30); +// window->setContentsMargins(0,0,0,0); +// //window->setStyleSheet("background-color:black;"); +// window->setLayout(layout); + } +} + void SoapySDRInputGui::setName(const QString& name) { setObjectName(name); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 970b72d7a..0210c5c49 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -20,6 +20,8 @@ #include #include +#include + #include "plugin/plugininstancegui.h" #include "util/messagequeue.h" @@ -51,6 +53,7 @@ public: virtual bool handleMessage(const Message& message); private: + void createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit); Ui::SoapySDRInputGui* ui; DeviceUISet* m_deviceUISet; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index 30049db37..0da6bc007 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -7,15 +7,9 @@ 0 0 324 - 276 + 176
    - - - 0 - 0 - - 320 @@ -190,15 +184,9 @@ 0 0 304 - 128 + 120 - - - 0 - 0 - - 3 @@ -314,6 +302,17 @@
    + + + + + + Tata + + + + + @@ -341,54 +340,10 @@ - - - - - - Didi - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - From d5ce8336686a895f1f07306bdfa71f9c9612e63d Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Nov 2018 03:45:21 +0100 Subject: [PATCH 917/956] SoapySDR support: GUI component to deal with interval ranges --- .../samplesource/soapysdrinput/CMakeLists.txt | 3 + .../soapysdrinput/intervalrangegui.cpp | 95 +++++++++++++++++++ .../soapysdrinput/intervalrangegui.h | 55 +++++++++++ .../soapysdrinput/intervalrangegui.ui | 91 ++++++++++++++++++ .../soapysdrinput/soapysdrinputgui.cpp | 15 ++- 5 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 plugins/samplesource/soapysdrinput/intervalrangegui.cpp create mode 100644 plugins/samplesource/soapysdrinput/intervalrangegui.h create mode 100644 plugins/samplesource/soapysdrinput/intervalrangegui.ui diff --git a/plugins/samplesource/soapysdrinput/CMakeLists.txt b/plugins/samplesource/soapysdrinput/CMakeLists.txt index ec5f14670..70e4553c9 100644 --- a/plugins/samplesource/soapysdrinput/CMakeLists.txt +++ b/plugins/samplesource/soapysdrinput/CMakeLists.txt @@ -9,6 +9,7 @@ set(soapysdrinput_SOURCES soapysdrinputsettings.cpp soapysdrinputthread.cpp discreterangegui.cpp + intervalrangegui.cpp ) set(soapysdrinput_HEADERS @@ -18,11 +19,13 @@ set(soapysdrinput_HEADERS soapysdrinputsettings.h soapysdrinputthread.h discreterangegui.h + intervalrangegui.h ) set(soapysdrinput_FORMS soapysdrinputgui.ui discreterangegui.ui + intervalrangegui.ui ) if (BUILD_DEBIAN) diff --git a/plugins/samplesource/soapysdrinput/intervalrangegui.cpp b/plugins/samplesource/soapysdrinput/intervalrangegui.cpp new file mode 100644 index 000000000..9e31b85ea --- /dev/null +++ b/plugins/samplesource/soapysdrinput/intervalrangegui.cpp @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "ui_intervalrangegui.h" +#include "intervalrangegui.h" + +IntervalRangeGUI::IntervalRangeGUI(QWidget* parent) : + QWidget(parent), + ui(new Ui::IntervalRangeGUI), + m_nbDigits(7) +{ + ui->setupUi(this); + ui->value->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); +} + +IntervalRangeGUI::~IntervalRangeGUI() +{ + delete ui; +} + +void IntervalRangeGUI::setLabel(const QString& text) +{ + ui->rangeLabel->setText(text); +} + +void IntervalRangeGUI::setUnits(const QString& units) +{ + ui->rangeUnits->setText(units); +} + +void IntervalRangeGUI::addInterval(double minimum, double maximum) +{ + ui->rangeInterval->blockSignals(true); + ui->rangeInterval->addItem(QString("%1").arg(m_minima.size())); + ui->rangeInterval->blockSignals(false); + m_minima.push_back(minimum); + m_maxima.push_back(maximum); +} + +void IntervalRangeGUI::reset() +{ + if (m_minima.size() > 0) + { + double maxLog = 0.0; + + for (const auto &it : m_maxima) + { + if (log10(it) > maxLog) { + maxLog = log10(it); + } + } + + m_nbDigits = maxLog; + m_nbDigits++; + ui->rangeInterval->blockSignals(true); + ui->rangeInterval->setCurrentIndex(0); + ui->rangeInterval->blockSignals(false); + ui->value->setValueRange(m_nbDigits, m_minima[0], m_maxima[0]); + } + + if (m_minima.size() == 1) { + ui->rangeInterval->setDisabled(true); + } +} + +double IntervalRangeGUI::getCurrentValue() +{ + return ui->value->getValue(); +} + +void IntervalRangeGUI::on_value_changed(quint64 value) +{ + emit valueChanged(value); +} + +void IntervalRangeGUI::on_rangeInterval_currentIndexChanged(int index) +{ + ui->value->setValueRange(m_nbDigits, m_minima[index], m_maxima[index]); +} + diff --git a/plugins/samplesource/soapysdrinput/intervalrangegui.h b/plugins/samplesource/soapysdrinput/intervalrangegui.h new file mode 100644 index 000000000..f858b7b1a --- /dev/null +++ b/plugins/samplesource/soapysdrinput/intervalrangegui.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_INTERVALRANGEGUI_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_INTERVALRANGEGUI_H_ + +#include +#include + +namespace Ui { + class IntervalRangeGUI; +} + +class IntervalRangeGUI : public QWidget +{ + Q_OBJECT +public: + explicit IntervalRangeGUI(QWidget* parent = 0); + ~IntervalRangeGUI(); + + void setLabel(const QString& text); + void setUnits(const QString& units); + void addInterval(double minimum, double maximum); + void reset(); + double getCurrentValue(); + +signals: + void valueChanged(double value); + +private slots: + void on_value_changed(quint64 value); + void on_rangeInterval_currentIndexChanged(int index); + +private: + Ui::IntervalRangeGUI* ui; + std::vector m_minima; + std::vector m_maxima; + int m_nbDigits; +}; + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_INTERVALRANGEGUI_H_ */ diff --git a/plugins/samplesource/soapysdrinput/intervalrangegui.ui b/plugins/samplesource/soapysdrinput/intervalrangegui.ui new file mode 100644 index 000000000..f7dc19a5e --- /dev/null +++ b/plugins/samplesource/soapysdrinput/intervalrangegui.ui @@ -0,0 +1,91 @@ + + + IntervalRangeGUI + + + + 0 + 0 + 263 + 30 + + + + Form + + + + + 0 + 0 + 262 + 29 + + + + + + + Label + + + + + + + + 0 + 0 + + + + + 120 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + + + + + Unit + + + + + + + + 50 + 16777215 + + + + Range select + + + + + + + + + ValueDial + QWidget +
    gui/valuedial.h
    + 1 +
    +
    + + +
    diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 7049039d5..ae218754e 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -22,6 +22,7 @@ #include "ui_soapysdrinputgui.h" #include "discreterangegui.h" +#include "intervalrangegui.h" #include "soapysdrinputgui.h" SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : @@ -43,7 +44,7 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleSource->getFrequencyRange(f_min, f_max); ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); - createRangesControl(m_sampleSource->getRateRanges(), "Sample Rate", "kS/s"); + createRangesControl(m_sampleSource->getRateRanges(), "SR", "kS/s"); } SoapySDRInputGui::~SoapySDRInputGui() @@ -109,6 +110,18 @@ void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, // //window->setStyleSheet("background-color:black;"); // window->setLayout(layout); } + else if (rangeInterval) + { + IntervalRangeGUI *rangeGUI = new IntervalRangeGUI(ui->scrollAreaWidgetContents); + rangeGUI->setLabel(text); + rangeGUI->setUnits(unit); + + for (const auto &it : rangeList) { + rangeGUI->addInterval(it.minimum(), it.maximum()); + } + + rangeGUI->reset(); + } } void SoapySDRInputGui::setName(const QString& name) From f79e6bc3ab21067c7b8a1dbd1988886ca94f90cf Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Nov 2018 10:06:27 +0100 Subject: [PATCH 918/956] SoapySDR support: interface for all generic GUI elements --- .../samplesource/soapysdrinput/CMakeLists.txt | 2 + .../soapysdrinput/discreterangegui.cpp | 22 ++++++++++- .../soapysdrinput/discreterangegui.h | 12 +++--- .../soapysdrinput/intervalrangegui.cpp | 9 ++++- .../soapysdrinput/intervalrangegui.h | 12 +++--- .../soapysdrinput/itemsettinggui.cpp | 11 ++++++ .../soapysdrinput/itemsettinggui.h | 39 +++++++++++++++++++ .../soapysdrinput/soapysdrinputgui.cpp | 13 ++++++- .../soapysdrinput/soapysdrinputgui.h | 6 +++ 9 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 plugins/samplesource/soapysdrinput/itemsettinggui.cpp create mode 100644 plugins/samplesource/soapysdrinput/itemsettinggui.h diff --git a/plugins/samplesource/soapysdrinput/CMakeLists.txt b/plugins/samplesource/soapysdrinput/CMakeLists.txt index 70e4553c9..e63f1525b 100644 --- a/plugins/samplesource/soapysdrinput/CMakeLists.txt +++ b/plugins/samplesource/soapysdrinput/CMakeLists.txt @@ -8,6 +8,7 @@ set(soapysdrinput_SOURCES soapysdrinputplugin.cpp soapysdrinputsettings.cpp soapysdrinputthread.cpp + itemsettinggui.cpp discreterangegui.cpp intervalrangegui.cpp ) @@ -18,6 +19,7 @@ set(soapysdrinput_HEADERS soapysdrinputplugin.h soapysdrinputsettings.h soapysdrinputthread.h + itemsettinggui.h discreterangegui.h intervalrangegui.h ) diff --git a/plugins/samplesource/soapysdrinput/discreterangegui.cpp b/plugins/samplesource/soapysdrinput/discreterangegui.cpp index 654b3aea0..8a74bdd0a 100644 --- a/plugins/samplesource/soapysdrinput/discreterangegui.cpp +++ b/plugins/samplesource/soapysdrinput/discreterangegui.cpp @@ -18,7 +18,7 @@ #include "discreterangegui.h" DiscreteRangeGUI::DiscreteRangeGUI(QWidget* parent) : - QWidget(parent), + ItemSettingGUI(parent), ui(new Ui::DiscreteRangeGUI) { ui->setupUi(this); @@ -52,8 +52,26 @@ double DiscreteRangeGUI::getCurrentValue() return itemValues[ui->rangeCombo->currentIndex()]; } +void DiscreteRangeGUI::setValue(double value) +{ + int index = 0; + + for (const auto &it : itemValues) + { + if (it >= value) + { + ui->rangeCombo->blockSignals(true); + ui->rangeCombo->setCurrentIndex(index); + ui->rangeCombo->blockSignals(false); + break; + } + + index++; + } +} + void DiscreteRangeGUI::on_rangeCombo_currentIndexChanged(int index) { double newRange = itemValues[index]; - emit rangeChanged(newRange); + emit ItemSettingGUI::valueChanged(newRange); } diff --git a/plugins/samplesource/soapysdrinput/discreterangegui.h b/plugins/samplesource/soapysdrinput/discreterangegui.h index 291d195d4..76d5e42a1 100644 --- a/plugins/samplesource/soapysdrinput/discreterangegui.h +++ b/plugins/samplesource/soapysdrinput/discreterangegui.h @@ -20,24 +20,24 @@ #include #include +#include "itemsettinggui.h" + namespace Ui { class DiscreteRangeGUI; } -class DiscreteRangeGUI : public QWidget +class DiscreteRangeGUI : public ItemSettingGUI { Q_OBJECT public: explicit DiscreteRangeGUI(QWidget* parent = 0); - ~DiscreteRangeGUI(); + virtual ~DiscreteRangeGUI(); void setLabel(const QString& text); void setUnits(const QString& units); void addItem(const QString& itemStr, double itemValue); - double getCurrentValue(); - -signals: - void rangeChanged(double value); + virtual double getCurrentValue(); + virtual void setValue(double value); private slots: void on_rangeCombo_currentIndexChanged(int index); diff --git a/plugins/samplesource/soapysdrinput/intervalrangegui.cpp b/plugins/samplesource/soapysdrinput/intervalrangegui.cpp index 9e31b85ea..10a3df81e 100644 --- a/plugins/samplesource/soapysdrinput/intervalrangegui.cpp +++ b/plugins/samplesource/soapysdrinput/intervalrangegui.cpp @@ -20,7 +20,7 @@ #include "intervalrangegui.h" IntervalRangeGUI::IntervalRangeGUI(QWidget* parent) : - QWidget(parent), + ItemSettingGUI(parent), ui(new Ui::IntervalRangeGUI), m_nbDigits(7) { @@ -83,9 +83,14 @@ double IntervalRangeGUI::getCurrentValue() return ui->value->getValue(); } +void IntervalRangeGUI::setValue(double value) +{ + ui->value->setValue(value); +} + void IntervalRangeGUI::on_value_changed(quint64 value) { - emit valueChanged(value); + emit ItemSettingGUI::valueChanged(value); } void IntervalRangeGUI::on_rangeInterval_currentIndexChanged(int index) diff --git a/plugins/samplesource/soapysdrinput/intervalrangegui.h b/plugins/samplesource/soapysdrinput/intervalrangegui.h index f858b7b1a..999452124 100644 --- a/plugins/samplesource/soapysdrinput/intervalrangegui.h +++ b/plugins/samplesource/soapysdrinput/intervalrangegui.h @@ -21,25 +21,25 @@ #include #include +#include "itemsettinggui.h" + namespace Ui { class IntervalRangeGUI; } -class IntervalRangeGUI : public QWidget +class IntervalRangeGUI : public ItemSettingGUI { Q_OBJECT public: explicit IntervalRangeGUI(QWidget* parent = 0); - ~IntervalRangeGUI(); + virtual ~IntervalRangeGUI(); void setLabel(const QString& text); void setUnits(const QString& units); void addInterval(double minimum, double maximum); void reset(); - double getCurrentValue(); - -signals: - void valueChanged(double value); + virtual double getCurrentValue(); + virtual void setValue(double value); private slots: void on_value_changed(quint64 value); diff --git a/plugins/samplesource/soapysdrinput/itemsettinggui.cpp b/plugins/samplesource/soapysdrinput/itemsettinggui.cpp new file mode 100644 index 000000000..a85dedc37 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/itemsettinggui.cpp @@ -0,0 +1,11 @@ +/* + * itemsettinggui.cpp + * + * Created on: Nov 1, 2018 + * Author: f4exb + */ + +#include "itemsettinggui.h" + +ItemSettingGUI::ItemSettingGUI(QWidget *parent) : QWidget(parent) {} + diff --git a/plugins/samplesource/soapysdrinput/itemsettinggui.h b/plugins/samplesource/soapysdrinput/itemsettinggui.h new file mode 100644 index 000000000..e84621cd3 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/itemsettinggui.h @@ -0,0 +1,39 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +// This is an interface to an elementary GUI item used to get/set setting from the GUI + +#ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_ITEMSETTINGGUI_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_ITEMSETTINGGUI_H_ + +#include + +class ItemSettingGUI : public QWidget +{ + Q_OBJECT +public: + explicit ItemSettingGUI(QWidget *parent = 0); + virtual ~ItemSettingGUI() {} + virtual double getCurrentValue() = 0; + virtual void setValue(double value) = 0; + +signals: + void valueChanged(double value); +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_ITEMSETTINGGUI_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index ae218754e..1cc18b2b7 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -34,7 +34,8 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleSource(0), m_sampleRate(0), m_deviceCenterFrequency(0), - m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), + m_sampleRateGUI(0) { m_sampleSource = (SoapySDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); ui->setupUi(this); @@ -85,6 +86,8 @@ void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, rangeGUI->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0)), it.minimum()); } + m_sampleRateGUI = rangeGUI; + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); // QHBoxLayout *layout = new QHBoxLayout(); // QLabel *rangeLabel = new QLabel(); // rangeLabel->setText(text); @@ -121,6 +124,9 @@ void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, } rangeGUI->reset(); + + m_sampleRateGUI = rangeGUI; + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); } } @@ -164,4 +170,7 @@ bool SoapySDRInputGui::handleMessage(const Message& message __attribute__((unuse return false; } - +void SoapySDRInputGui::sampleRateChanged(double sampleRate) +{ + qDebug("SoapySDRInputGui::sampleRateChanged: %lf", sampleRate); +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 0210c5c49..7517e610d 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -28,6 +28,7 @@ #include "soapysdrinput.h" class DeviceUISet; +class ItemSettingGUI; namespace Ui { class SoapySDRInputGui; @@ -66,6 +67,11 @@ private: quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_lastEngineState; MessageQueue m_inputMessageQueue; + + ItemSettingGUI *m_sampleRateGUI; + +private slots: + void sampleRateChanged(double sampleRate); }; From 5acac7b9fae9465562f92ea70b716cfe6105f5b0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 1 Nov 2018 11:43:42 +0100 Subject: [PATCH 919/956] SoapySDR support: input GUI: implement fixed elements support --- .../bladerf2input/bladerf2inputgui.cpp | 2 +- .../soapysdrinput/soapysdrinput.h | 46 ++++- .../soapysdrinput/soapysdrinputgui.cpp | 175 ++++++++++++++++++ .../soapysdrinput/soapysdrinputgui.h | 18 ++ .../soapysdrinput/soapysdrinputgui.ui | 158 ++++++++++++++++ 5 files changed, 394 insertions(+), 5 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp index 14c126aa7..02e78775a 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -385,7 +385,7 @@ void BladeRF2InputGui::on_transverter_clicked() { m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); - qDebug("LimeSDRInputGUI::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + qDebug("BladeRF2InputGui::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); updateFrequencyLimits(); setCenterFrequencySetting(ui->centerFrequency->getValueNew()); sendSettings(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h index 7bde0a771..e7c184f62 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -32,29 +32,67 @@ class SoapySDRInputThread; class SoapySDRInput : public DeviceSampleSource { public: - class MsgConfigureSoapySDR : public Message { + class MsgConfigureSoapySDRInput : public Message { MESSAGE_CLASS_DECLARATION public: const SoapySDRInputSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureSoapySDR* create(const SoapySDRInputSettings& settings, bool force) + static MsgConfigureSoapySDRInput* create(const SoapySDRInputSettings& settings, bool force) { - return new MsgConfigureSoapySDR(settings, force); + return new MsgConfigureSoapySDRInput(settings, force); } private: SoapySDRInputSettings m_settings; bool m_force; - MsgConfigureSoapySDR(const SoapySDRInputSettings& settings, bool force) : + MsgConfigureSoapySDRInput(const SoapySDRInputSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) { } }; + class MsgFileRecord : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgFileRecord* create(bool startStop) { + return new MsgFileRecord(startStop); + } + + protected: + bool m_startStop; + + MsgFileRecord(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + 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) + { } + }; + SoapySDRInput(DeviceSourceAPI *deviceAPI); virtual ~SoapySDRInput(); virtual void destroy(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 1cc18b2b7..6c0ea16de 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -14,11 +14,14 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "dsp/dspengine.h" #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" #include "device/deviceuiset.h" #include "util/simpleserializer.h" +#include "gui/glspectrum.h" #include "ui_soapysdrinputgui.h" #include "discreterangegui.h" @@ -174,3 +177,175 @@ void SoapySDRInputGui::sampleRateChanged(double sampleRate) { qDebug("SoapySDRInputGui::sampleRateChanged: %lf", sampleRate); } + +void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) +{ + qDebug("SoapySDRInputGui::on_centerFrequency_changed: %llu", value); +} + +void SoapySDRInputGui::on_dcOffset_toggled(bool checked) +{ + m_settings.m_dcBlock = checked; + sendSettings(); +} + +void SoapySDRInputGui::on_iqImbalance_toggled(bool checked) +{ + m_settings.m_iqCorrection = checked; + sendSettings(); +} + +void SoapySDRInputGui::on_decim_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) + return; + m_settings.m_log2Decim = index; + sendSettings(); +} + +void SoapySDRInputGui::on_fcPos_currentIndexChanged(int index) +{ + if (index == 0) { + m_settings.m_fcPos = SoapySDRInputSettings::FC_POS_INFRA; + sendSettings(); + } else if (index == 1) { + m_settings.m_fcPos = SoapySDRInputSettings::FC_POS_SUPRA; + sendSettings(); + } else if (index == 2) { + m_settings.m_fcPos = SoapySDRInputSettings::FC_POS_CENTER; + sendSettings(); + } +} + +void SoapySDRInputGui::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("SoapySDRInputGui::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + +void SoapySDRInputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + SoapySDRInput::MsgStartStop *message = SoapySDRInput::MsgStartStop::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void SoapySDRInputGui::on_record_toggled(bool checked) +{ + if (checked) { + ui->record->setStyleSheet("QToolButton { background-color : red; }"); + } else { + ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + + SoapySDRInput::MsgFileRecord* message = SoapySDRInput::MsgFileRecord::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); +} + +void SoapySDRInputGui::displaySettings() +{ + blockApplySettings(true); + + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + m_sampleRateGUI->setValue(m_settings.m_devSampleRate); + + ui->dcOffset->setChecked(m_settings.m_dcBlock); + ui->iqImbalance->setChecked(m_settings.m_iqCorrection); + + ui->decim->setCurrentIndex(m_settings.m_log2Decim); + ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); + + blockApplySettings(false); +} + +void SoapySDRInputGui::sendSettings() +{ + if (!m_updateTimer.isActive()) { + m_updateTimer.start(100); + } +} + +void SoapySDRInputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateText->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); +} + +void SoapySDRInputGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_sampleSource->getFrequencyRange(f_min, f_max); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("SoapySDRInputGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + +void SoapySDRInputGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + +void SoapySDRInputGui::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void SoapySDRInputGui::updateHardware() +{ + if (m_doApplySettings) + { + qDebug() << "SoapySDRInputGui::updateHardware"; + SoapySDRInput::MsgConfigureSoapySDRInput* message = SoapySDRInput::MsgConfigureSoapySDRInput::create(m_settings, m_forceSettings); + m_sampleSource->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void SoapySDRInputGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceSourceAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DSPDeviceSourceEngine::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DSPDeviceSourceEngine::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DSPDeviceSourceEngine::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DSPDeviceSourceEngine::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSourceAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 7517e610d..1c86d5fbb 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -60,6 +60,7 @@ private: DeviceUISet* m_deviceUISet; bool m_forceSettings; bool m_doApplySettings; + SoapySDRInputSettings m_settings; QTimer m_updateTimer; QTimer m_statusTimer; SoapySDRInput* m_sampleSource; @@ -70,8 +71,25 @@ private: ItemSettingGUI *m_sampleRateGUI; + void displaySettings(); + void sendSettings(); + void updateSampleRateAndFrequency(); + void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); + void blockApplySettings(bool block); + private slots: + void on_centerFrequency_changed(quint64 value); void sampleRateChanged(double sampleRate); + void on_dcOffset_toggled(bool checked); + void on_iqImbalance_toggled(bool checked); + void on_decim_currentIndexChanged(int index); + void on_fcPos_currentIndexChanged(int index); + void on_transverter_clicked(); + void on_startStop_toggled(bool checked); + void on_record_toggled(bool checked); + void updateHardware(); + void updateStatus(); }; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index 0da6bc007..2bb580f7a 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -166,6 +166,159 @@ + + + + 6 + + + 6 + + + + + Auto + + + + + + + Software DC block + + + DC + + + + + + + Software IQ correction + + + IQ + + + + + + + Fp + + + + + + + + 50 + 0 + + + + Relative position of device center frequency + + + + Inf + + + + + Sup + + + + + Cen + + + + + + + + Dec + + + + + + + + 30 + 0 + + + + Software decimation factor + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + X + + + + + @@ -358,6 +511,11 @@ QToolButton
    gui/buttonswitch.h
    + + TransverterButton + QPushButton +
    gui/transverterbutton.h
    +
    From ea98f2e1c9ea5afac6f71a8157bda111ef2f213f Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 2 Nov 2018 02:33:04 +0100 Subject: [PATCH 920/956] SoapySDR support: input: center frequency and sample rate handling --- devices/soapysdr/devicesoapysdrparams.cpp | 36 +++ devices/soapysdr/devicesoapysdrparams.h | 3 + devices/soapysdr/devicesoapysdrshared.cpp | 2 + devices/soapysdr/devicesoapysdrshared.h | 48 +++ .../bladerf2input/bladerf2input.cpp | 8 +- .../bladerf2input/bladerf2input.h | 2 +- .../soapysdrinput/soapysdrinput.cpp | 299 +++++++++++++++++- .../soapysdrinput/soapysdrinput.h | 16 +- .../soapysdrinput/soapysdrinputgui.cpp | 69 +++- .../soapysdrinput/soapysdrinputgui.h | 2 + .../soapysdrinput/soapysdrinputgui.ui | 48 ++- .../soapysdrinput/soapysdrinputthread.cpp | 8 + 12 files changed, 527 insertions(+), 14 deletions(-) diff --git a/devices/soapysdr/devicesoapysdrparams.cpp b/devices/soapysdr/devicesoapysdrparams.cpp index 133f7efee..2caa0ba9e 100644 --- a/devices/soapysdr/devicesoapysdrparams.cpp +++ b/devices/soapysdr/devicesoapysdrparams.cpp @@ -31,6 +31,42 @@ DeviceSoapySDRParams::DeviceSoapySDRParams(SoapySDR::Device *device) : DeviceSoapySDRParams::~DeviceSoapySDRParams() {} +std::string DeviceSoapySDRParams::getRxChannelMainTunableElementName(uint32_t index) +{ + if (index < m_nbRx) + { + return std::string("RF"); + } + else + { + const ChannelSettings& channelSettings = m_RxChannelsSettings[index]; + + if (channelSettings.m_frequencySettings.size() > 0) { + return channelSettings.m_frequencySettings.front().m_name; + } else { + return std::string("RF"); + } + } +} + +std::string DeviceSoapySDRParams::getTxChannelMainTunableElementName(uint32_t index) +{ + if (index < m_nbRx) + { + return std::string("RF"); + } + else + { + const ChannelSettings& channelSettings = m_RxChannelsSettings[index]; + + if (channelSettings.m_frequencySettings.size() > 0) { + return channelSettings.m_frequencySettings.front().m_name; + } else { + return std::string("RF"); + } + } +} + void DeviceSoapySDRParams::fillParams() { m_deviceSettingsArgs = m_device->getSettingInfo(); diff --git a/devices/soapysdr/devicesoapysdrparams.h b/devices/soapysdr/devicesoapysdrparams.h index 67baf5cac..2489b8f85 100644 --- a/devices/soapysdr/devicesoapysdrparams.h +++ b/devices/soapysdr/devicesoapysdrparams.h @@ -86,6 +86,9 @@ public: } } + std::string getRxChannelMainTunableElementName(uint32_t index); + std::string getTxChannelMainTunableElementName(uint32_t index); + private: void fillParams(); void fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan); diff --git a/devices/soapysdr/devicesoapysdrshared.cpp b/devices/soapysdr/devicesoapysdrshared.cpp index 16a2ef64e..bbfabd460 100644 --- a/devices/soapysdr/devicesoapysdrshared.cpp +++ b/devices/soapysdr/devicesoapysdrshared.cpp @@ -16,6 +16,8 @@ #include "devicesoapysdrshared.h" +MESSAGE_CLASS_DEFINITION(DeviceSoapySDRShared::MsgReportBuddyChange, Message) + DeviceSoapySDRShared::DeviceSoapySDRShared() : m_device(0), m_channel(-1), diff --git a/devices/soapysdr/devicesoapysdrshared.h b/devices/soapysdr/devicesoapysdrshared.h index 8ab019ba1..36d54c2bc 100644 --- a/devices/soapysdr/devicesoapysdrshared.h +++ b/devices/soapysdr/devicesoapysdrshared.h @@ -19,6 +19,7 @@ #include +#include "util/message.h" #include "export.h" #include "devicesoapysdrparams.h" @@ -31,6 +32,53 @@ class SoapySDROutput; class DEVICES_API DeviceSoapySDRShared { public: + class MsgReportBuddyChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + uint64_t getCenterFrequency() const { return m_centerFrequency; } + int getLOppmTenths() const { return m_LOppmTenths; } + int getFcPos() const { return m_fcPos; } + int getDevSampleRate() const { return m_devSampleRate; } + bool getRxElseTx() const { return m_rxElseTx; } + + static MsgReportBuddyChange* create( + uint64_t centerFrequency, + int LOppmTenths, + int fcPos, + int devSampleRate, + bool rxElseTx) + { + return new MsgReportBuddyChange( + centerFrequency, + LOppmTenths, + fcPos, + devSampleRate, + rxElseTx); + } + + private: + uint64_t m_centerFrequency; //!< Center frequency + int m_LOppmTenths; //!< LO soft correction in tenths of ppm + int m_fcPos; //!< Center frequency position + int m_devSampleRate; //!< device/host sample rate + bool m_rxElseTx; //!< tells which side initiated the message + + MsgReportBuddyChange( + uint64_t centerFrequency, + int LOppmTenths, + int fcPos, + int devSampleRate, + bool rxElseTx) : + Message(), + m_centerFrequency(centerFrequency), + m_LOppmTenths(LOppmTenths), + m_fcPos(fcPos), + m_devSampleRate(devSampleRate), + m_rxElseTx(rxElseTx) + { } + }; + DeviceSoapySDRShared(); ~DeviceSoapySDRShared(); diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 60387f70d..5142484e4 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -538,9 +538,9 @@ void BladeRF2Input::setCenterFrequency(qint64 centerFrequency) } } -bool BladeRF2Input::setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz) +bool BladeRF2Input::setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) { - qint64 df = ((qint64)freq_hz * m_settings.m_LOppmTenths) / 10000000LL; + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; freq_hz += df; int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(requestedChannel), freq_hz); @@ -661,7 +661,7 @@ bool BladeRF2Input::handleMessage(const Message& message) (DeviceSampleSource::fcPos_t) settings.m_fcPos, settings.m_devSampleRate); - if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency)) + if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency, settings.m_LOppmTenths)) { if (getMessageQueueToGUI()) { @@ -846,7 +846,7 @@ bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool fo if (dev != 0) { - if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency)) + if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency, settings.m_LOppmTenths)) { if (getMessageQueueToGUI()) { diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h index 9c5c0f81b..4b9a79561 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.h +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -192,7 +192,7 @@ private: BladeRF2InputThread *findThread(); void moveThreadToBuddy(); bool applySettings(const BladeRF2InputSettings& settings, bool force = false); - bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz); + bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index a765e340a..c5a1ad31a 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "util/simpleserializer.h" #include "device/devicesourceapi.h" @@ -26,17 +28,27 @@ #include "soapysdrinputthread.h" #include "soapysdrinput.h" +MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgConfigureSoapySDRInput, Message) +MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgFileRecord, Message) +MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgStartStop, Message) + SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) : m_deviceAPI(deviceAPI), - m_thread(0), + m_settings(), m_deviceDescription("SoapySDRInput"), - m_running(false) + m_running(false), + m_thread(0) { openDevice(); + + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); + m_deviceAPI->addSink(m_fileSink); } SoapySDRInput::~SoapySDRInput() { + m_deviceAPI->removeSink(m_fileSink); + delete m_fileSink; } void SoapySDRInput::destroy() @@ -195,6 +207,38 @@ void SoapySDRInput::init() { } +SoapySDRInputThread *SoapySDRInput::findThread() +{ + if (m_thread == 0) // this does not own the thread + { + SoapySDRInputThread *soapySDRInputThread = 0; + + // find a buddy that has allocated the thread + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + SoapySDRInput *buddySource = ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source; + + if (buddySource) + { + soapySDRInputThread = buddySource->getThread(); + + if (soapySDRInputThread) { + break; + } + } + } + + return soapySDRInputThread; + } + else + { + return m_thread; // own thread + } +} + void SoapySDRInput::moveThreadToBuddy() { const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); @@ -251,7 +295,256 @@ void SoapySDRInput::setCenterFrequency(qint64 centerFrequency __attribute__((unu { } +bool SoapySDRInput::setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) +{ + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; + freq_hz += df; + + try + { + dev->setFrequency(SOAPY_SDR_RX, + requestedChannel, + m_deviceShared.m_deviceParams->getRxChannelMainTunableElementName(requestedChannel), + freq_hz); + qDebug("SoapySDRInput::setDeviceCenterFrequency: setFrequency(%llu)", freq_hz); + return true; + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: could not set frequency: %llu: %s", freq_hz, ex.what()); + return false; + } +} + bool SoapySDRInput::handleMessage(const Message& message __attribute__((unused))) { - return false; + if (MsgConfigureSoapySDRInput::match(message)) + { + MsgConfigureSoapySDRInput& conf = (MsgConfigureSoapySDRInput&) message; + qDebug() << "SoapySDRInput::handleMessage: MsgConfigureSoapySDRInput"; + + if (!applySettings(conf.getSettings(), conf.getForce())) { + qDebug("SoapySDRInput::handleMessage: MsgConfigureSoapySDRInput config error"); + } + + return true; + } + else if (MsgFileRecord::match(message)) + { + MsgFileRecord& conf = (MsgFileRecord&) message; + qDebug() << "SoapySDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); + + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + + m_fileSink->startRecording(); + } + else + { + m_fileSink->stopRecording(); + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "SoapySDRInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initAcquisition()) + { + m_deviceAPI->startAcquisition(); + } + } + else + { + m_deviceAPI->stopAcquisition(); + } + + return true; + } + else if (DeviceSoapySDRShared::MsgReportBuddyChange::match(message)) + { + int requestedChannel = m_deviceAPI->getItemIndex(); + DeviceSoapySDRShared::MsgReportBuddyChange& report = (DeviceSoapySDRShared::MsgReportBuddyChange&) message; + SoapySDRInputSettings settings = m_settings; + settings.m_fcPos = (SoapySDRInputSettings::fcPos_t) report.getFcPos(); + + settings.m_centerFrequency = m_deviceShared.m_device->getFrequency( + SOAPY_SDR_RX, + requestedChannel, + m_deviceShared.m_deviceParams->getRxChannelMainTunableElementName(requestedChannel)); + + settings.m_devSampleRate = m_deviceShared.m_device->getSampleRate(SOAPY_SDR_RX, requestedChannel); + + SoapySDRInputThread *inputThread = findThread(); + + if (inputThread) + { + inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); + } + + m_settings = settings; + + return true; + } + else + { + return false; + } +} + +bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool force) +{ + bool forwardChangeOwnDSP = false; + bool forwardChangeToBuddies = false; + + SoapySDR::Device *dev = m_deviceShared.m_device; + SoapySDRInputThread *inputThread = findThread(); + int requestedChannel = m_deviceAPI->getItemIndex(); + qint64 xlatedDeviceCenterFrequency = settings.m_centerFrequency; + xlatedDeviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + xlatedDeviceCenterFrequency = xlatedDeviceCenterFrequency < 0 ? 0 : xlatedDeviceCenterFrequency; + + if ((m_settings.m_dcBlock != settings.m_dcBlock) || + (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); + } + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + forwardChangeOwnDSP = true; + forwardChangeToBuddies = true; + + if (dev != 0) + { + try + { + dev->setSampleRate(SOAPY_SDR_RX, requestedChannel, settings.m_devSampleRate); + qDebug() << "SoapySDRInput::applySettings: setSampleRate OK: " << settings.m_devSampleRate; + + if (inputThread) + { + bool wasRunning = inputThread->isRunning(); + inputThread->stopWork(); + inputThread->setSampleRate(settings.m_devSampleRate); + + if (wasRunning) { + inputThread->startWork(); + } + } + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, ex.what()); + } + } + } + + if ((m_settings.m_fcPos != settings.m_fcPos) || force) + { + SoapySDRInputThread *inputThread = findThread(); + + if (inputThread != 0) + { + inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); + qDebug() << "SoapySDRInput::applySettings: set fc pos (enum) to " << (int) settings.m_fcPos; + } + } + + if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + forwardChangeOwnDSP = true; + SoapySDRInputThread *inputThread = findThread(); + + if (inputThread != 0) + { + inputThread->setLog2Decimation(requestedChannel, settings.m_log2Decim); + qDebug() << "SoapySDRInput::applySettings: set decimation to " << (1<handleMessage(*notif); // forward to file sink + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } + + if (forwardChangeToBuddies) + { + // send to source buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + + for (const auto &itSource : sourceBuddies) + { + DeviceSoapySDRShared::MsgReportBuddyChange *report = DeviceSoapySDRShared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + (int) settings.m_fcPos, + settings.m_devSampleRate, + true); + itSource->getSampleSourceInputMessageQueue()->push(report); + } + + for (const auto &itSink : sinkBuddies) + { + DeviceSoapySDRShared::MsgReportBuddyChange *report = DeviceSoapySDRShared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + (int) settings.m_fcPos, + settings.m_devSampleRate, + true); + itSink->getSampleSinkInputMessageQueue()->push(report); + } + } + + m_settings = settings; + + qDebug() << "SoapySDRInput::applySettings: " + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_log2Decim: " << m_settings.m_log2Decim + << " m_fcPos: " << m_settings.m_fcPos + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_dcBlock: " << m_settings.m_dcBlock + << " m_iqCorrection: " << m_settings.m_iqCorrection; + + return true; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h index e7c184f62..3d5dffc1b 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -28,6 +28,12 @@ class DeviceSourceAPI; class SoapySDRInputThread; +class FileRecord; + +namespace SoapySDR +{ + class Device; +} class SoapySDRInput : public DeviceSampleSource { @@ -119,14 +125,20 @@ public: private: DeviceSourceAPI *m_deviceAPI; - DeviceSoapySDRShared m_deviceShared; - SoapySDRInputThread *m_thread; + QMutex m_mutex; + SoapySDRInputSettings m_settings; QString m_deviceDescription; bool m_running; + SoapySDRInputThread *m_thread; + DeviceSoapySDRShared m_deviceShared; + FileRecord *m_fileSink; //!< File sink to record device I/Q output bool openDevice(); void closeDevice(); + SoapySDRInputThread *findThread(); void moveThreadToBuddy(); + bool applySettings(const SoapySDRInputSettings& settings, bool force = false); + bool setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); }; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 6c0ea16de..9e7c2e0f7 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -49,6 +49,17 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); createRangesControl(m_sampleSource->getRateRanges(), "SR", "kS/s"); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); + + sendSettings(); } SoapySDRInputGui::~SoapySDRInputGui() @@ -170,17 +181,59 @@ bool SoapySDRInputGui::deserialize(const QByteArray& data __attribute__((unused) bool SoapySDRInputGui::handleMessage(const Message& message __attribute__((unused))) { - return false; + if (SoapySDRInput::MsgStartStop::match(message)) + { + SoapySDRInput::MsgStartStop& notif = (SoapySDRInput::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void SoapySDRInputGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("SoapySDRInputGui::handleInputMessages: message: %s", message->getIdentifier()); + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("SoapySDRInputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } } void SoapySDRInputGui::sampleRateChanged(double sampleRate) { - qDebug("SoapySDRInputGui::sampleRateChanged: %lf", sampleRate); + m_settings.m_devSampleRate = sampleRate; + sendSettings(); } void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) { - qDebug("SoapySDRInputGui::on_centerFrequency_changed: %llu", value); + m_settings.m_centerFrequency = value * 1000; + sendSettings(); } void SoapySDRInputGui::on_dcOffset_toggled(bool checked) @@ -227,6 +280,13 @@ void SoapySDRInputGui::on_transverter_clicked() sendSettings(); } +void SoapySDRInputGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + void SoapySDRInputGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) @@ -261,6 +321,9 @@ void SoapySDRInputGui::displaySettings() ui->decim->setCurrentIndex(m_settings.m_log2Decim); ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + blockApplySettings(false); } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 1c86d5fbb..864546904 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -79,7 +79,9 @@ private: void blockApplySettings(bool block); private slots: + void handleInputMessages(); void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); void sampleRateChanged(double sampleRate); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index 2bb580f7a..b5e295eef 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -167,7 +167,7 @@ - + 6 @@ -319,6 +319,52 @@ + + + + + + LO ppm + + + + + + + Local Oscillator software ppm correction + + + -1000 + + + 1000 + + + 1 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + -100.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp index 96748ec87..f28cc0ea9 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp @@ -57,6 +57,10 @@ SoapySDRInputThread::~SoapySDRInputThread() void SoapySDRInputThread::startWork() { + if (m_running) { + return; + } + m_startWaitMutex.lock(); start(); @@ -69,6 +73,10 @@ void SoapySDRInputThread::startWork() void SoapySDRInputThread::stopWork() { + if (!m_running) { + return; + } + m_running = false; wait(); } From f5e9b44bf6e0070506c802585b7c4048e5044b69 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 2 Nov 2018 10:16:14 +0100 Subject: [PATCH 921/956] SoapySDR support: input: start/stop handling --- .../bladerf2input/bladerf2input.cpp | 8 +- .../soapysdrinput/soapysdrinput.cpp | 224 +++++++++++++++++- 2 files changed, 227 insertions(+), 5 deletions(-) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 5142484e4..577c4a323 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -269,8 +269,8 @@ bool BladeRF2Input::start() // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. // It marks the thread as needing start. // - // If the requested channel is within the thread channel range (this thread being already allocated) it simply removes its FIFO reference - // so that the samples are not fed to the FIFO anymore and leaves the thread unchanged (no stop, no delete/new) + // If the requested channel is within the thread channel range (this thread being already allocated) it simply adds its FIFO reference + // so that the samples are fed to the FIFO and leaves the thread unchanged (no stop, no delete/new) // // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is // 1 if channel 0 is requested (SI mode) and 2 if channel 1 is requested (MI mode). It marks the thread as needing start. @@ -321,7 +321,7 @@ bool BladeRF2Input::start() bladerf2InputThread->setFcPos(i, fcPoss[i]); } - // remove old thread address from buddies (reset in all buddies) + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); std::vector::const_iterator it = sourceBuddies.begin(); @@ -457,7 +457,7 @@ void BladeRF2Input::stop() qDebug("BladeRF2Input::stop: do not re-create thread as there are no more FIFOs active"); } - // remove old thread address from buddies (reset in all buddies) + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); std::vector::const_iterator it = sourceBuddies.begin(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index c5a1ad31a..059bf53a9 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -258,11 +258,233 @@ void SoapySDRInput::moveThreadToBuddy() bool SoapySDRInput::start() { - return false; + // There is a single thread per physical device (Rx side). This thread is unique and referenced by a unique + // buddy in the group of source buddies associated with this physical device. + // + // This start method is responsible for managing the thread and number of channels when the streaming of a Rx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following + // + // There are two possible working modes: + // - Single Input (SI) with only one channel streaming. This HAS to be channel 0. + // - Multiple Input (MI) with two or more channels. It MUST be in this configuration if any channel other than 0 + // is used irrespective of what you actually do with samples coming from ignored channels. + // For example When we will run with only channel 2 streaming from the client perspective the channels 0 amnd 1 will actually + // be enabled and streaming but its samples will just be disregarded. + // This means that all channels up to the highest in index being used are activated. + // + // It manages the transition form SI where only one channel (the first or channel 0) should be running to the + // Multiple Input (MI) if the requested channel is 1 or more. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply adds its FIFO reference + // so that the samples are fed to the FIFO and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SI mode) and 3 if channel 2 is requested (MI mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + // + // Note: this is quite similar to the BladeRF2 start handling. The main difference is that the channel allocation (enabling) process is + // done in the thread object. + + if (!m_deviceShared.m_device) + { + qDebug("SoapySDRInput::start: no device object"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDRInputThread *soapySDRInputThread = findThread(); + bool needsStart = false; + + if (soapySDRInputThread) // if thread is already allocated + { + qDebug("SoapySDRInput::start: thread is already allocated"); + + int nbOriginalChannels = soapySDRInputThread->getNbChannels(); + + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + { + qDebug("SoapySDRInput::start: expand channels. Re-allocate thread and take ownership"); + + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels]; + int *fcPoss = new int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { + fifos[i] = soapySDRInputThread->getFifo(i); + log2Decims[i] = soapySDRInputThread->getLog2Decimation(i); + fcPoss[i] = soapySDRInputThread->getFcPos(i); + } + + soapySDRInputThread->stopWork(); + delete soapySDRInputThread; + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDRInputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { + soapySDRInputThread->setFifo(i, fifos[i]); + soapySDRInputThread->setLog2Decimation(i, log2Decims[i]); + soapySDRInputThread->setFcPos(i, fcPoss[i]); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + needsStart = true; + } + else + { + qDebug("SoapySDRInput::start: keep buddy thread"); + } + } + else // first allocation + { + qDebug("SoapySDRInput::start: allocate thread and take ownership"); + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDRInputThread; // take ownership + needsStart = true; + } + + soapySDRInputThread->setFifo(requestedChannel, &m_sampleFifo); + soapySDRInputThread->setLog2Decimation(requestedChannel, m_settings.m_log2Decim); + soapySDRInputThread->setFcPos(requestedChannel, (int) m_settings.m_fcPos); + + if (needsStart) + { + qDebug("SoapySDRInput::start: (re)sart buddy thread"); + soapySDRInputThread->startWork(); + } + + applySettings(m_settings, true); + + qDebug("SoapySDRInput::start: started"); + m_running = true; + + return true; } void SoapySDRInput::stop() { + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Rx channel is stopped + // + // If the thread is currently managing only one channel (SI mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MI mode) and we are removing the last channel. The transition + // or reduction of MI size is handled by stopping the thread, deleting it and creating a new one + // with one channel less if (and only if) there is still a channel active. + // + // If the thread is currently managing many channels (MI mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that it will not stream into this FIFO + // anymore. In this case the channel is not closed (this is managed in the thread object) so that other channels + // can continue with the same configuration. The device continues streaming on this channel but the samples are simply + // dropped (by removing FIFO reference). + // + // Note: this is quite similar to the BladeRF2 stop handling. The main difference is that the channel allocation (enabling) process is + // done in the thread object. + + if (!m_running) { + return; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDRInputThread *soapySDRInputThread = findThread(); + + if (soapySDRInputThread == 0) { // no thread allocated + return; + } + + int nbOriginalChannels = soapySDRInputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SI mode => just stop and delete the thread + { + qDebug("SoapySDRInput::stop: SI mode. Just stop and delete the thread"); + soapySDRInputThread->stopWork(); + delete soapySDRInputThread; + m_thread = 0; + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + } + else if (requestedChannel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread + { + qDebug("SoapySDRInput::stop: MI mode. Reduce by deleting and re-creating the thread"); + soapySDRInputThread->stopWork(); + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; + int *fcPoss = new int[nbOriginalChannels-1]; + int highestActiveChannelIndex = -1; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references and get the channel with highest index + { + fifos[i] = soapySDRInputThread->getFifo(i); + + if ((soapySDRInputThread->getFifo(i) != 0) && (i > highestActiveChannelIndex)) { + highestActiveChannelIndex = i; + } + + log2Decims[i] = soapySDRInputThread->getLog2Decimation(i); + fcPoss[i] = soapySDRInputThread->getFcPos(i); + } + + delete soapySDRInputThread; + m_thread = 0; + + if (highestActiveChannelIndex >= 0) // there is at least one channel still active + { + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, highestActiveChannelIndex+1); + m_thread = soapySDRInputThread; // take ownership + + for (int i = 0; i < highestActiveChannelIndex; i++) // restore original FIFO references + { + soapySDRInputThread->setFifo(i, fifos[i]); + soapySDRInputThread->setLog2Decimation(i, log2Decims[i]); + soapySDRInputThread->setFcPos(i, fcPoss[i]); + } + } + else + { + qDebug("SoapySDRInput::stop: do not re-create thread as there are no more FIFOs active"); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + if (highestActiveChannelIndex >= 0) { + soapySDRInputThread->startWork(); + } + } + else // remove channel from existing thread + { + qDebug("SoapySDRInput::stop: MI mode. Not changing MI configuration. Just remove FIFO reference"); + soapySDRInputThread->setFifo(requestedChannel, 0); // remove FIFO + } + + m_running = false; } QByteArray SoapySDRInput::serialize() const From 0ff0a4ff06d377b98e21106b0e1f332bd4849bdd Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 2 Nov 2018 13:07:30 +0100 Subject: [PATCH 922/956] SoapySDR support: input: streaming basics --- .../soapysdrinput/soapysdrinputthread.cpp | 127 ++++++++++++++++-- .../soapysdrinput/soapysdrinputthread.h | 1 - 2 files changed, 114 insertions(+), 14 deletions(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp index f28cc0ea9..6c24bbce4 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp @@ -35,12 +35,6 @@ SoapySDRInputThread::SoapySDRInputThread(SoapySDR::Device* dev, unsigned int nbR { qDebug("SoapySDRInputThread::SoapySDRInputThread"); m_channels = new Channel[nbRxChannels]; - - for (unsigned int i = 0; i < nbRxChannels; i++) { - m_channels[i].m_convertBuffer.resize(DeviceSoapySDR::blockSize, Sample{0,0}); - } - - m_buf = new qint16[2*DeviceSoapySDR::blockSize*nbRxChannels]; } SoapySDRInputThread::~SoapySDRInputThread() @@ -51,7 +45,6 @@ SoapySDRInputThread::~SoapySDRInputThread() stopWork(); } - delete[] m_buf; delete[] m_channels; } @@ -103,11 +96,13 @@ void SoapySDRInputThread::run() double fullScale(0.0); std::string format = m_dev->getNativeStreamFormat(SOAPY_SDR_RX, channels.front(), fullScale); - if ((format == "CF8") && (fullScale == 128.0)) { // 8 bit signed - native + qDebug("SoapySDRInputThread::run: format: %s fullScale: %f", format.c_str(), fullScale); + + if ((format == "CS8") && (fullScale == 128.0)) { // 8 bit signed - native m_decimatorType = Decimator8; - } else if ((format == "CF16") && (fullScale == 2048.0)) { // 12 bit signed - native + } else if ((format == "CS16") && (fullScale == 2048.0)) { // 12 bit signed - native m_decimatorType = Decimator12; - } else if ((format == "CF16") && (fullScale == 32768.0)) { // 16 bit signed - native + } else if ((format == "CS16") && (fullScale == 32768.0)) { // 16 bit signed - native m_decimatorType = Decimator16; } else { // for other types make a conversion to float m_decimatorType = DecimatorFloat; @@ -126,16 +121,28 @@ void SoapySDRInputThread::run() buffs[i] = buffMem[i].data(); } - qDebug("SoapySDRInputThread::run: start running loop"); + for (unsigned int i = 0; i < m_nbChannels; i++) { + m_channels[i].m_convertBuffer.resize(numElems, Sample{0,0}); + } + m_dev->activateStream(stream); int flags(0); long long timeNs(0); + float blockTime = ((float) numElems) / (m_sampleRate <= 0 ? 1024000 : m_sampleRate); + long timeoutUs = 2000000 * blockTime; // 10 times the block time + + qDebug("SoapySDRInputThread::run: numElems: %u elemSize: %u timeoutUs: %ld", numElems, elemSize, timeoutUs); + qDebug("SoapySDRInputThread::run: start running loop"); while (m_running) { - int ret = m_dev->readStream(stream, buffs.data(), numElems, flags, timeNs); + int ret = m_dev->readStream(stream, buffs.data(), numElems, flags, timeNs, timeoutUs); - if (ret < 0) + if (ret == SOAPY_SDR_TIMEOUT) + { + qWarning("SoapySDRInputThread::run: timeout: flags: %d timeNs: %lld timeoutUs: %ld", flags, timeNs, timeoutUs); + } + else if (ret < 0) { qCritical("SoapySDRInputThread::run: Unexpected read stream error: %s", SoapySDR::errToStr(ret)); break; @@ -167,6 +174,7 @@ void SoapySDRInputThread::run() qDebug("SoapySDRInputThread::run: stop running loop"); m_dev->deactivateStream(stream); + m_dev->closeStream(stream); } else { @@ -538,3 +546,96 @@ void SoapySDRInputThread::callbackSI16(const qint16* buf, qint32 len, unsigned i m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); } + +void SoapySDRInputThread::callbackSIF(const float* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimatorsFloat.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimatorsFloat.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimatorsFloat.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimatorsFloat.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimatorsFloat.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimatorsFloat.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimatorsFloat.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimatorsFloat.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimatorsFloat.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimatorsFloat.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimatorsFloat.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimatorsFloat.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimatorsFloat.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimatorsFloat.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimatorsFloat.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimatorsFloat.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimatorsFloat.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimatorsFloat.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimatorsFloat.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.h b/plugins/samplesource/soapysdrinput/soapysdrinputthread.h index 6118926f7..d0c91c447 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputthread.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.h @@ -90,7 +90,6 @@ private: Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Rx channels unsigned int m_sampleRate; - qint16 *m_buf; //!< Full buffer for SISO or MIMO operation unsigned int m_nbChannels; DecimatorType m_decimatorType; From 2cab4ff7bd794d3497ef8431abcc7002917bb486 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 2 Nov 2018 16:19:45 +0100 Subject: [PATCH 923/956] SoapySDR support: input: corrected number of samples in handler --- .../samplesource/soapysdrinput/soapysdrinputthread.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp index 6c24bbce4..dd969a8f6 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp @@ -150,24 +150,24 @@ void SoapySDRInputThread::run() if (m_nbChannels > 1) { - callbackMI(buffs, (elemSize/2)*numElems); + callbackMI(buffs, numElems*2); // size given in number of I or Q samples (2 items per sample) } else { switch (m_decimatorType) { case Decimator8: - callbackSI8((const qint8*) buffs[0], (elemSize/2)*numElems); + callbackSI8((const qint8*) buffs[0], numElems*2); break; case Decimator12: - callbackSI12((const qint16*) buffs[0], (elemSize/2)*numElems); + callbackSI12((const qint16*) buffs[0], numElems*2); break; case Decimator16: - callbackSI16((const qint16*) buffs[0], (elemSize/2)*numElems); + callbackSI16((const qint16*) buffs[0], numElems*2); break; case DecimatorFloat: default: - callbackSIF((const float*) buffs[0], (elemSize/2)*numElems); + callbackSIF((const float*) buffs[0], numElems*2); } } } From 579c7d31f10e9d360ea2cf28e93fa49e5ae57221 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 3 Nov 2018 00:07:43 +0100 Subject: [PATCH 924/956] SoapySDR support: moved common gui elements to sdrgui --- plugins/samplesource/soapysdrinput/CMakeLists.txt | 8 -------- .../samplesource/soapysdrinput/soapysdrinputgui.cpp | 4 ++-- sdrgui/CMakeLists.txt | 10 ++++++++++ .../soapygui}/discreterangegui.cpp | 0 .../soapygui}/discreterangegui.h | 0 .../soapygui}/discreterangegui.ui | 0 .../soapygui}/intervalrangegui.cpp | 0 .../soapygui}/intervalrangegui.h | 0 .../soapygui}/intervalrangegui.ui | 0 .../soapygui}/itemsettinggui.cpp | 0 .../soapysdrinput => sdrgui/soapygui}/itemsettinggui.h | 0 11 files changed, 12 insertions(+), 10 deletions(-) rename {plugins/samplesource/soapysdrinput => sdrgui/soapygui}/discreterangegui.cpp (100%) rename {plugins/samplesource/soapysdrinput => sdrgui/soapygui}/discreterangegui.h (100%) rename {plugins/samplesource/soapysdrinput => sdrgui/soapygui}/discreterangegui.ui (100%) rename {plugins/samplesource/soapysdrinput => sdrgui/soapygui}/intervalrangegui.cpp (100%) rename {plugins/samplesource/soapysdrinput => sdrgui/soapygui}/intervalrangegui.h (100%) rename {plugins/samplesource/soapysdrinput => sdrgui/soapygui}/intervalrangegui.ui (100%) rename {plugins/samplesource/soapysdrinput => sdrgui/soapygui}/itemsettinggui.cpp (100%) rename {plugins/samplesource/soapysdrinput => sdrgui/soapygui}/itemsettinggui.h (100%) diff --git a/plugins/samplesource/soapysdrinput/CMakeLists.txt b/plugins/samplesource/soapysdrinput/CMakeLists.txt index e63f1525b..b87a6567f 100644 --- a/plugins/samplesource/soapysdrinput/CMakeLists.txt +++ b/plugins/samplesource/soapysdrinput/CMakeLists.txt @@ -8,9 +8,6 @@ set(soapysdrinput_SOURCES soapysdrinputplugin.cpp soapysdrinputsettings.cpp soapysdrinputthread.cpp - itemsettinggui.cpp - discreterangegui.cpp - intervalrangegui.cpp ) set(soapysdrinput_HEADERS @@ -19,15 +16,10 @@ set(soapysdrinput_HEADERS soapysdrinputplugin.h soapysdrinputsettings.h soapysdrinputthread.h - itemsettinggui.h - discreterangegui.h - intervalrangegui.h ) set(soapysdrinput_FORMS soapysdrinputgui.ui - discreterangegui.ui - intervalrangegui.ui ) if (BUILD_DEBIAN) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 9e7c2e0f7..1f2196dd6 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -22,10 +22,10 @@ #include "device/deviceuiset.h" #include "util/simpleserializer.h" #include "gui/glspectrum.h" +#include "soapygui/discreterangegui.h" +#include "soapygui/intervalrangegui.h" #include "ui_soapysdrinputgui.h" -#include "discreterangegui.h" -#include "intervalrangegui.h" #include "soapysdrinputgui.h" SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 95e655c92..1130a0a47 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -54,6 +54,10 @@ set(sdrgui_SOURCES device/deviceuiset.cpp + soapygui/discreterangegui.cpp + soapygui/intervalrangegui.cpp + soapygui/itemsettinggui.cpp + webapi/webapiadaptergui.cpp ) @@ -111,6 +115,10 @@ set(sdrgui_HEADERS device/deviceuiset.h + soapygui/discreterangegui.h + soapygui/intervalrangegui.h + soapygui/itemsettinggui.h + webapi/webapiadaptergui.h ) @@ -139,6 +147,8 @@ set(sdrgui_FORMS gui/myposdialog.ui gui/transverterdialog.ui gui/loggingdialog.ui + soapygui/discreterangegui.ui + soapygui/intervalrangegui.ui ) set(sdrgui_RESOURCES diff --git a/plugins/samplesource/soapysdrinput/discreterangegui.cpp b/sdrgui/soapygui/discreterangegui.cpp similarity index 100% rename from plugins/samplesource/soapysdrinput/discreterangegui.cpp rename to sdrgui/soapygui/discreterangegui.cpp diff --git a/plugins/samplesource/soapysdrinput/discreterangegui.h b/sdrgui/soapygui/discreterangegui.h similarity index 100% rename from plugins/samplesource/soapysdrinput/discreterangegui.h rename to sdrgui/soapygui/discreterangegui.h diff --git a/plugins/samplesource/soapysdrinput/discreterangegui.ui b/sdrgui/soapygui/discreterangegui.ui similarity index 100% rename from plugins/samplesource/soapysdrinput/discreterangegui.ui rename to sdrgui/soapygui/discreterangegui.ui diff --git a/plugins/samplesource/soapysdrinput/intervalrangegui.cpp b/sdrgui/soapygui/intervalrangegui.cpp similarity index 100% rename from plugins/samplesource/soapysdrinput/intervalrangegui.cpp rename to sdrgui/soapygui/intervalrangegui.cpp diff --git a/plugins/samplesource/soapysdrinput/intervalrangegui.h b/sdrgui/soapygui/intervalrangegui.h similarity index 100% rename from plugins/samplesource/soapysdrinput/intervalrangegui.h rename to sdrgui/soapygui/intervalrangegui.h diff --git a/plugins/samplesource/soapysdrinput/intervalrangegui.ui b/sdrgui/soapygui/intervalrangegui.ui similarity index 100% rename from plugins/samplesource/soapysdrinput/intervalrangegui.ui rename to sdrgui/soapygui/intervalrangegui.ui diff --git a/plugins/samplesource/soapysdrinput/itemsettinggui.cpp b/sdrgui/soapygui/itemsettinggui.cpp similarity index 100% rename from plugins/samplesource/soapysdrinput/itemsettinggui.cpp rename to sdrgui/soapygui/itemsettinggui.cpp diff --git a/plugins/samplesource/soapysdrinput/itemsettinggui.h b/sdrgui/soapygui/itemsettinggui.h similarity index 100% rename from plugins/samplesource/soapysdrinput/itemsettinggui.h rename to sdrgui/soapygui/itemsettinggui.h From 6a9607c8fc1519801f914739fb5c2e2bd2b57805 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Nov 2018 11:45:59 +0100 Subject: [PATCH 925/956] SoapySDR support: output: implemented thread and related methods --- .../bladerf2output/bladerf2output.cpp | 8 +- .../bladerf2output/bladerf2outputthread.h | 4 +- .../samplesink/soapysdroutput/CMakeLists.txt | 6 +- .../soapysdroutput/soapysdroutput.cpp | 359 ++++++++++++++- .../soapysdroutput/soapysdroutput.h | 63 ++- .../soapysdroutput/soapysdroutputgui.cpp | 283 +++++++++++- .../soapysdroutput/soapysdroutputgui.h | 24 + .../soapysdroutput/soapysdroutputgui.ui | 357 +++++++++++++++ .../soapysdroutput/soapysdroutputthread.cpp | 411 ++++++++++++++++++ .../soapysdroutput/soapysdroutputthread.h | 94 ++++ .../soapysdrinput/soapysdrinput.cpp | 15 +- .../soapysdrinput/soapysdrinputgui.cpp | 27 +- .../soapysdrinput/soapysdrinputgui.h | 2 - .../soapysdrinput/soapysdrinputthread.h | 2 +- 14 files changed, 1611 insertions(+), 44 deletions(-) create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputgui.ui create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp create mode 100644 plugins/samplesink/soapysdroutput/soapysdroutputthread.h diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 0da56e24f..9b9085357 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -174,7 +174,7 @@ BladeRF2OutputThread *BladeRF2Output::findThread() { if (m_thread == 0) // this does not own the thread { - BladeRF2OutputThread *BladeRF2OutputThread = 0; + BladeRF2OutputThread *bladeRF2OutputThread = 0; // find a buddy that has allocated the thread const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); @@ -186,15 +186,15 @@ BladeRF2OutputThread *BladeRF2Output::findThread() if (buddySink) { - BladeRF2OutputThread = buddySink->getThread(); + bladeRF2OutputThread = buddySink->getThread(); - if (BladeRF2OutputThread) { + if (bladeRF2OutputThread) { break; } } } - return BladeRF2OutputThread; + return bladeRF2OutputThread; } else { diff --git a/plugins/samplesink/bladerf2output/bladerf2outputthread.h b/plugins/samplesink/bladerf2output/bladerf2outputthread.h index 012b1ad36..5b93d84c5 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputthread.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputthread.h @@ -40,8 +40,6 @@ public: unsigned int getNbChannels() const { return m_nbChannels; } void setLog2Interpolation(unsigned int channel, unsigned int log2_interp); unsigned int getLog2Interpolation(unsigned int channel) const; - void setFcPos(unsigned int channel, int fcPos); - int getFcPos(unsigned int channel) const; void setFifo(unsigned int channel, SampleSourceFifo *sampleFifo); SampleSourceFifo *getFifo(unsigned int channel); @@ -66,7 +64,7 @@ private: bool m_running; struct bladerf* m_dev; - Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Rx channels + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Tx channels qint16 *m_buf; //!< Full buffer for SISO or MIMO operation unsigned int m_nbChannels; diff --git a/plugins/samplesink/soapysdroutput/CMakeLists.txt b/plugins/samplesink/soapysdroutput/CMakeLists.txt index 0015a6feb..95011bb52 100644 --- a/plugins/samplesink/soapysdroutput/CMakeLists.txt +++ b/plugins/samplesink/soapysdroutput/CMakeLists.txt @@ -7,7 +7,7 @@ set(soapysdroutput_SOURCES soapysdroutput.cpp soapysdroutputplugin.cpp soapysdroutputsettings.cpp -# soapysdroutputthread.cpp + soapysdroutputthread.cpp ) set(soapysdroutput_HEADERS @@ -15,11 +15,11 @@ set(soapysdroutput_HEADERS soapysdroutput.h soapysdroutputplugin.h soapysdroutputsettings.h -# soapysdroutputthread.h + soapysdroutputthread.h ) set(soapysdroutput_FORMS -# soapysdroutputgui.ui + soapysdroutputgui.ui ) if (BUILD_DEBIAN) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index d1bd40485..c0e60b784 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -21,17 +21,28 @@ #include "device/devicesourceapi.h" #include "soapysdr/devicesoapysdr.h" +#include "soapysdroutputthread.h" #include "soapysdroutput.h" +MESSAGE_CLASS_DEFINITION(SoapySDROutput::MsgConfigureSoapySDROutput, Message) +MESSAGE_CLASS_DEFINITION(SoapySDROutput::MsgStartStop, Message) + SoapySDROutput::SoapySDROutput(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_deviceDescription("SoapySDROutput"), - m_running(false) + m_running(false), + m_thread(0) { + openDevice(); } SoapySDROutput::~SoapySDROutput() { + if (m_running) { + stop(); + } + + closeDevice(); } void SoapySDROutput::destroy() @@ -121,9 +132,9 @@ void SoapySDROutput::closeDevice() stop(); } -// if (m_thread) { // stills own the thread => transfer to a buddy -// moveThreadToBuddy(); -// } + if (m_thread) { // stills own the thread => transfer to a buddy + moveThreadToBuddy(); + } m_deviceShared.m_channel = -1; // publicly release channel m_deviceShared.m_sink = 0; @@ -138,28 +149,342 @@ void SoapySDROutput::closeDevice() } } +void SoapySDROutput::getFrequencyRange(uint64_t& min, uint64_t& max) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + + if (channelSettings && (channelSettings->m_frequencySettings.size() > 0)) + { + DeviceSoapySDRParams::FrequencySetting freqSettings = channelSettings->m_frequencySettings[0]; + SoapySDR::RangeList rangeList = freqSettings.m_ranges; + + if (rangeList.size() > 0) // TODO: handle multiple ranges + { + SoapySDR::Range range = rangeList[0]; + min = range.minimum(); + max = range.maximum(); + } + else + { + min = 0; + max = 0; + } + } + else + { + min = 0; + max = 0; + } +} + +const SoapySDR::RangeList& SoapySDROutput::getRateRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_ratesRanges; +} + void SoapySDROutput::init() { + applySettings(m_settings, true); +} + +SoapySDROutputThread *SoapySDROutput::findThread() +{ + if (m_thread == 0) // this does not own the thread + { + SoapySDROutputThread *soapySDROutputThread = 0; + + // find a buddy that has allocated the thread + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) + { + SoapySDROutput *buddySink = ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink; + + if (buddySink) + { + soapySDROutputThread = buddySink->getThread(); + + if (soapySDROutputThread) { + break; + } + } + } + + return soapySDROutputThread; + } + else + { + return m_thread; // own thread + } +} + +void SoapySDROutput::moveThreadToBuddy() +{ + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) + { + SoapySDROutput *buddySink = ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink; + + if (buddySink) + { + buddySink->setThread(m_thread); + m_thread = 0; // zero for others + } + } } bool SoapySDROutput::start() { - return false; + // There is a single thread per physical device (Tx side). This thread is unique and referenced by a unique + // buddy in the group of sink buddies associated with this physical device. + // + // This start method is responsible for managing the thread and channel enabling when the streaming of a Tx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following + // + // There are two possible working modes: + // - Single Output (SO) with only one channel streaming. This HAS to be channel 0. + // - Multiple Output (MO) with two or more channels. It MUST be in this configuration if any channel other than 0 + // is used. For example when we will run with only channel 2 streaming from the client perspective the channels 0 and 1 + // will actually be enabled and streaming but zero samples will be sent to it. + // + // It manages the transition form SO where only one channel (the first or channel 0) should be running to the + // Multiple Output (MO) if the requested channel is 1 or more. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply removes its FIFO reference + // so that the samples are not taken from the FIFO anymore and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SO mode) and 3 if channel 2 is requested (MO mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + // + // Note: this is quite similar to the BladeRF2 start handling. The main difference is that the channel allocation (enabling) process is + // done in the thread object. + + + if (!m_deviceShared.m_device) + { + qDebug("SoapySDROutput::start: no device object"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDROutputThread *soapySDROutputThread = findThread(); + bool needsStart = false; + + if (soapySDROutputThread) // if thread is already allocated + { + qDebug("SoapySDROutput::start: thread is already allocated"); + + int nbOriginalChannels = soapySDROutputThread->getNbChannels(); + + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + { + qDebug("SoapySDROutput::start: expand channels. Re-allocate thread and take ownership"); + + SampleSourceFifo **fifos = new SampleSourceFifo*[nbOriginalChannels]; + unsigned int *log2Interps = new unsigned int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { + fifos[i] = soapySDROutputThread->getFifo(i); + log2Interps[i] = soapySDROutputThread->getLog2Interpolation(i); + } + + soapySDROutputThread->stopWork(); + delete soapySDROutputThread; + soapySDROutputThread = new SoapySDROutputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDROutputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { + soapySDROutputThread->setFifo(i, fifos[i]); + soapySDROutputThread->setLog2Interpolation(i, log2Interps[i]); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning sink. + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + needsStart = true; + } + else + { + qDebug("SoapySDROutput::start: keep buddy thread"); + } + } + else // first allocation + { + qDebug("SoapySDROutput::start: allocate thread and take ownership"); + soapySDROutputThread = new SoapySDROutputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDROutputThread; // take ownership + needsStart = true; + } + + soapySDROutputThread->setFifo(requestedChannel, &m_sampleSourceFifo); + soapySDROutputThread->setLog2Interpolation(requestedChannel, m_settings.m_log2Interp); + + if (needsStart) + { + qDebug("SoapySDROutput::start: (re)sart buddy thread"); + soapySDROutputThread->startWork(); + } + + applySettings(m_settings, true); // re-apply forcibly to set sample rate with the new number of channels + + qDebug("SoapySDROutput::start: started"); + m_running = true; + + return true; } void SoapySDROutput::stop() { + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Tx channel is stopped + // + // If the thread is currently managing only one channel (SO mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MO mode) and we are removing the last channel. The transition + // from MO to SO or reduction of MO size is handled by stopping the thread, deleting it and creating a new one + // with the maximum number of channels needed if (and only if) there is still a channel active. + // + // If the thread is currently managing many channels (MO mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that this FIFO will not be used anymore. + // In this case the channel is not closed (this is managed in the thread object) so that other channels can continue with the + // same configuration. The device continues streaming on this channel but the samples are set to all zeros. + + if (!m_running) { + return; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDROutputThread *soapySDROutputThread = findThread(); + + if (soapySDROutputThread == 0) { // no thread allocated + return; + } + + int nbOriginalChannels = soapySDROutputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SO mode => just stop and delete the thread + { + qDebug("SoapySDROutput::stop: SO mode. Just stop and delete the thread"); + soapySDROutputThread->stopWork(); + delete soapySDROutputThread; + m_thread = 0; + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + } + else if (requestedChannel == nbOriginalChannels - 1) // remove last MO channel => reduce by deleting and re-creating the thread + { + qDebug("SoapySDROutput::stop: MO mode. Reduce by deleting and re-creating the thread"); + soapySDROutputThread->stopWork(); + SampleSourceFifo **fifos = new SampleSourceFifo*[nbOriginalChannels-1]; + unsigned int *log2Interps = new unsigned int[nbOriginalChannels-1]; + int highestActiveChannelIndex = -1; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references + { + fifos[i] = soapySDROutputThread->getFifo(i); + + if ((soapySDROutputThread->getFifo(i) != 0) && (i > highestActiveChannelIndex)) { + highestActiveChannelIndex = i; + } + + log2Interps[i] = soapySDROutputThread->getLog2Interpolation(i); + } + + delete soapySDROutputThread; + m_thread = 0; + + if (highestActiveChannelIndex >= 0) + { + soapySDROutputThread = new SoapySDROutputThread(m_deviceShared.m_device, highestActiveChannelIndex+1); + m_thread = soapySDROutputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels-1; i++) // restore original FIFO references + { + soapySDROutputThread->setFifo(i, fifos[i]); + soapySDROutputThread->setLog2Interpolation(i, log2Interps[i]); + } + } + else + { + qDebug("SoapySDROutput::stop: do not re-create thread as there are no more FIFOs active"); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning sink. + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + if (highestActiveChannelIndex >= 0) + { + qDebug("SoapySDROutput::stop: restarting the thread"); + soapySDROutputThread->startWork(); + } + } + else // remove channel from existing thread + { + qDebug("SoapySDROutput::stop: MO mode. Not changing MO configuration. Just remove FIFO reference"); + soapySDROutputThread->setFifo(requestedChannel, 0); // remove FIFO + } + + applySettings(m_settings, true); // re-apply forcibly to set sample rate with the new number of channels + + m_running = false; } QByteArray SoapySDROutput::serialize() const { - SimpleSerializer s(1); - return s.final(); + return m_settings.serialize(); } bool SoapySDROutput::deserialize(const QByteArray& data __attribute__((unused))) { - return false; + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureSoapySDROutput* message = MsgConfigureSoapySDROutput::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureSoapySDROutput* messageToGUI = MsgConfigureSoapySDROutput::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; } const QString& SoapySDROutput::getDeviceDescription() const @@ -169,16 +494,28 @@ const QString& SoapySDROutput::getDeviceDescription() const int SoapySDROutput::getSampleRate() const { - return 0; + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } } bool SoapySDROutput::handleMessage(const Message& message __attribute__((unused))) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.h b/plugins/samplesink/soapysdroutput/soapysdroutput.h index d78265eae..b9ec34c63 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.h @@ -18,6 +18,8 @@ #define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUT_H_ #include +#include +#include #include "dsp/devicesamplesink.h" #include "soapysdr/devicesoapysdrshared.h" @@ -25,9 +27,57 @@ #include "soapysdroutputsettings.h" class DeviceSinkAPI; +class SoapySDROutputThread; + +namespace SoapySDR +{ + class Device; +} class SoapySDROutput : public DeviceSampleSink { public: + class MsgConfigureSoapySDROutput : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SoapySDROutputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSoapySDROutput* create(const SoapySDROutputSettings& settings, bool force) + { + return new MsgConfigureSoapySDROutput(settings, force); + } + + private: + SoapySDROutputSettings m_settings; + bool m_force; + + MsgConfigureSoapySDROutput(const SoapySDROutputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + 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) + { } + }; + SoapySDROutput(DeviceSinkAPI *deviceAPI); virtual ~SoapySDROutput(); virtual void destroy(); @@ -35,6 +85,8 @@ public: virtual void init(); virtual bool start(); virtual void stop(); + SoapySDROutputThread *getThread() { return m_thread; } + void setThread(SoapySDROutputThread *thread) { m_thread = thread; } virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); @@ -47,15 +99,24 @@ public: virtual bool handleMessage(const Message& message); + void getFrequencyRange(uint64_t& min, uint64_t& max); + const SoapySDR::RangeList& getRateRanges(); + private: DeviceSinkAPI *m_deviceAPI; + QMutex m_mutex; SoapySDROutputSettings m_settings; QString m_deviceDescription; - DeviceSoapySDRShared m_deviceShared; bool m_running; + SoapySDROutputThread *m_thread; + DeviceSoapySDRShared m_deviceShared; bool openDevice(); void closeDevice(); + SoapySDROutputThread *findThread(); + void moveThreadToBuddy(); + bool applySettings(const SoapySDROutputSettings& settings, bool force = false); + bool setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); }; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index 27a77ad9f..c1b9f09c6 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -14,17 +14,23 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "dsp/dspengine.h" #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" #include "device/deviceuiset.h" #include "util/simpleserializer.h" +#include "ui_soapysdroutputgui.h" +#include "gui/glspectrum.h" +#include "soapygui/discreterangegui.h" +#include "soapygui/intervalrangegui.h" #include "soapysdroutputgui.h" SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) : QWidget(parent), - ui(0), + ui(new Ui::SoapySDROutputGui), m_deviceUISet(deviceUISet), m_forceSettings(true), m_doApplySettings(true), @@ -32,10 +38,31 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) m_sampleRate(0), m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) { + m_sampleSink = (SoapySDROutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); + ui->setupUi(this); + + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + uint64_t f_min, f_max; + m_sampleSink->getFrequencyRange(f_min, f_max); + ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + + createRangesControl(m_sampleSink->getRateRanges(), "SR", "kS/s"); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSink->setMessageQueueToGUI(&m_inputMessageQueue); + + sendSettings(); } SoapySDROutputGui::~SoapySDROutputGui() { + delete ui; } void SoapySDROutputGui::destroy() @@ -43,6 +70,54 @@ void SoapySDROutputGui::destroy() delete this; } +void SoapySDROutputGui::createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit) +{ + if (rangeList.size() == 0) { // return early if the range list is empty + return; + } + + bool rangeDiscrete = true; // discretes values + bool rangeInterval = true; // intervals + + for (const auto &it : rangeList) + { + if (it.minimum() != it.maximum()) { + rangeDiscrete = false; + } else { + rangeInterval = false; + } + } + + if (rangeDiscrete) + { + DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(ui->scrollAreaWidgetContents); + rangeGUI->setLabel(text); + rangeGUI->setUnits(unit); + + for (const auto &it : rangeList) { + rangeGUI->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0)), it.minimum()); + } + + m_sampleRateGUI = rangeGUI; + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); + } + else if (rangeInterval) + { + IntervalRangeGUI *rangeGUI = new IntervalRangeGUI(ui->scrollAreaWidgetContents); + rangeGUI->setLabel(text); + rangeGUI->setUnits(unit); + + for (const auto &it : rangeList) { + rangeGUI->addInterval(it.minimum(), it.maximum()); + } + + rangeGUI->reset(); + + m_sampleRateGUI = rangeGUI; + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); + } +} + void SoapySDROutputGui::setName(const QString& name) { setObjectName(name); @@ -55,35 +130,225 @@ QString SoapySDROutputGui::getName() const void SoapySDROutputGui::resetToDefaults() { + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); } qint64 SoapySDROutputGui::getCenterFrequency() const { - return 0; + return m_settings.m_centerFrequency; } -void SoapySDROutputGui::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +void SoapySDROutputGui::setCenterFrequency(qint64 centerFrequency) { + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); } QByteArray SoapySDROutputGui::serialize() const { - SimpleSerializer s(1); - return s.final(); + return m_settings.serialize(); } -bool SoapySDROutputGui::deserialize(const QByteArray& data __attribute__((unused))) +bool SoapySDROutputGui::deserialize(const QByteArray& data) { - return false; + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } } -bool SoapySDROutputGui::handleMessage(const Message& message __attribute__((unused))) +bool SoapySDROutputGui::handleMessage(const Message& message) { - return false; + if (SoapySDROutput::MsgStartStop::match(message)) + { + SoapySDROutput::MsgStartStop& notif = (SoapySDROutput::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } } +void SoapySDROutputGui::handleInputMessages() +{ + Message* message; + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("SoapySDROutputGui::handleInputMessages: message: %s", message->getIdentifier()); + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("SoapySDROutputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void SoapySDROutputGui::sampleRateChanged(double sampleRate) +{ + m_settings.m_devSampleRate = sampleRate; + sendSettings(); +} + +void SoapySDROutputGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void SoapySDROutputGui::on_interp_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) + return; + m_settings.m_log2Interp = index; + sendSettings(); +} + +void SoapySDROutputGui::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("SoapySDROutputGui::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + +void SoapySDROutputGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + +void SoapySDROutputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + SoapySDROutput::MsgStartStop *message = SoapySDROutput::MsgStartStop::create(checked); + m_sampleSink->getInputMessageQueue()->push(message); + } +} + +void SoapySDROutputGui::displaySettings() +{ + blockApplySettings(true); + + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + m_sampleRateGUI->setValue(m_settings.m_devSampleRate); + + ui->interp->setCurrentIndex(m_settings.m_log2Interp); + + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + + blockApplySettings(false); +} + +void SoapySDROutputGui::sendSettings() +{ + if (!m_updateTimer.isActive()) { + m_updateTimer.start(100); + } +} + +void SoapySDROutputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateText->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); +} + +void SoapySDROutputGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_sampleSink->getFrequencyRange(f_min, f_max); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("SoapySDRInputGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + +void SoapySDROutputGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + +void SoapySDROutputGui::updateHardware() +{ + if (m_doApplySettings) + { + qDebug() << "SoapySDROutputGui::updateHardware"; + SoapySDROutput::MsgConfigureSoapySDROutput* message = SoapySDROutput::MsgConfigureSoapySDROutput::create(m_settings, m_forceSettings); + m_sampleSink->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void SoapySDROutputGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceSinkAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DSPDeviceSinkEngine::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DSPDeviceSinkEngine::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DSPDeviceSinkEngine::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DSPDeviceSinkEngine::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSinkAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index 8c7e07c69..3d130f390 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -24,9 +24,11 @@ #include "util/messagequeue.h" #include "soapysdroutput.h" +#include "soapysdroutputsettings.h" class DeviceSampleSink; class DeviceUISet; +class ItemSettingGUI; namespace Ui { class SoapySDROutputGui; @@ -52,11 +54,13 @@ public: virtual bool handleMessage(const Message& message); private: + void createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit); Ui::SoapySDROutputGui* ui; DeviceUISet* m_deviceUISet; bool m_forceSettings; bool m_doApplySettings; + SoapySDROutputSettings m_settings; QTimer m_updateTimer; QTimer m_statusTimer; SoapySDROutput* m_sampleSink; @@ -64,6 +68,26 @@ private: quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_lastEngineState; MessageQueue m_inputMessageQueue; + + ItemSettingGUI *m_sampleRateGUI; + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void sendSettings(); + void updateSampleRateAndFrequency(); + void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); + +private slots: + void handleInputMessages(); + void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); + void sampleRateChanged(double sampleRate); + void on_interp_currentIndexChanged(int index); + void on_transverter_clicked(); + void on_startStop_toggled(bool checked); + void updateHardware(); + void updateStatus(); }; #endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTGUI_H_ */ diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui b/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui new file mode 100644 index 000000000..839ce9fc7 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui @@ -0,0 +1,357 @@ + + + SoapySDROutputGui + + + + 0 + 0 + 324 + 176 + + + + + 320 + 132 + + + + + Liberation Sans + 9 + + + + SoapySDR + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + 6 + + + 6 + + + + + Interp + + + + + + + + 30 + 0 + + + + Software decimation factor + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + X + + + + + + + + + + + LO ppm + + + + + + + Local Oscillator software ppm correction + + + -1000 + + + 1000 + + + 1 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + -100.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + true + + + + + 0 + 0 + 318 + 51 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + ValueDial + QWidget +
    gui/valuedial.h
    + 1 +
    + + ButtonSwitch + QToolButton +
    gui/buttonswitch.h
    +
    + + TransverterButton + QPushButton +
    gui/transverterbutton.h
    +
    +
    + + + + +
    diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp new file mode 100644 index 000000000..f90ea6f56 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp @@ -0,0 +1,411 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 // +// // +// 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/samplesourcefifo.h" + +#include "soapysdroutputthread.h" + +SoapySDROutputThread::SoapySDROutputThread(SoapySDR::Device* dev, unsigned int nbTxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_sampleRate(0), + m_nbChannels(nbTxChannels), + m_interpolatorType(InterpolatorFloat) +{ + qDebug("SoapySDROutputThread::SoapySDROutputThread"); + m_channels = new Channel[nbTxChannels]; +} + +SoapySDROutputThread::~SoapySDROutputThread() +{ + qDebug("SoapySDROutputThread::~SoapySDROutputThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_channels; +} + +void SoapySDROutputThread::startWork() +{ + if (m_running) { + return; + } + + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void SoapySDROutputThread::stopWork() +{ + if (!m_running) { + return; + } + + m_running = false; + wait(); +} + +void SoapySDROutputThread::run() +{ + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if ((m_nbChannels > 0) && (nbFifos > 0)) + { + // build channels list + std::vector channels(m_nbChannels); + std::iota(channels.begin(), channels.end(), 0); // Fill with 0, 1, ..., m_nbChannels-1. + + //initialize the sample rate for all channels + for (const auto &it : channels) { + m_dev->setSampleRate(SOAPY_SDR_TX, it, m_sampleRate); + } + + // Determine sample format to be used + double fullScale(0.0); + std::string format = m_dev->getNativeStreamFormat(SOAPY_SDR_TX, channels.front(), fullScale); + + qDebug("SoapySDROutputThread::run: format: %s fullScale: %f", format.c_str(), fullScale); + + if ((format == "CS8") && (fullScale == 128.0)) { // 8 bit signed - native + m_interpolatorType = Interpolator8; + } else if ((format == "CS16") && (fullScale == 2048.0)) { // 12 bit signed - native + m_interpolatorType = Interpolator12; + } else if ((format == "CS16") && (fullScale == 32768.0)) { // 16 bit signed - native + m_interpolatorType = Interpolator16; + } else { // for other types make a conversion to float + m_interpolatorType = InterpolatorFloat; + format = "CF32"; + } + + unsigned int elemSize = SoapySDR::formatToSize(format); // sample (I+Q) size in bytes + SoapySDR::Stream *stream = m_dev->setupStream(SOAPY_SDR_TX, format, channels); + + //allocate buffers for the stream read/write + const unsigned int numElems = m_dev->getStreamMTU(stream); // number of samples (I+Q) + std::vector> buffMem(m_nbChannels, std::vector(elemSize*numElems)); + std::vector buffs(m_nbChannels); + + for (std::size_t i = 0; i < m_nbChannels; i++) { + buffs[i] = buffMem[i].data(); + } + + m_dev->activateStream(stream); + int flags(0); + long long timeNs(0); + float blockTime = ((float) numElems) / (m_sampleRate <= 0 ? 1024000 : m_sampleRate); + long timeoutUs = 2000000 * blockTime; // 10 times the block time + + qDebug("SoapySDROutputThread::run: numElems: %u elemSize: %u timeoutUs: %ld", numElems, elemSize, timeoutUs); + qDebug("SoapySDROutputThread::run: start running loop"); + + while (m_running) + { + int ret = m_dev->writeStream(stream, buffs.data(), numElems, flags, timeNs, timeoutUs); + + if (ret == SOAPY_SDR_TIMEOUT) + { + qWarning("SoapySDROutputThread::run: timeout: flags: %d timeNs: %lld timeoutUs: %ld", flags, timeNs, timeoutUs); + } + else if (ret < 0) + { + qCritical("SoapySDROutputThread::run: Unexpected write stream error: %s", SoapySDR::errToStr(ret)); + break; + } + + if (m_nbChannels > 1) + { + callbackMO(buffs, numElems*2); // size given in number of I or Q samples (2 items per sample) + } + else + { + switch (m_interpolatorType) + { + case Interpolator8: + callbackSO8((qint8*) buffs[0], numElems*2); + break; + case Interpolator12: + callbackSO12((qint16*) buffs[0], numElems*2); + break; + case Interpolator16: + callbackSO16((qint16*) buffs[0], numElems*2); + break; + case InterpolatorFloat: + default: + // TODO + break; + } + } + } + + qDebug("SoapySDROutputThread::run: stop running loop"); + m_dev->deactivateStream(stream); + m_dev->closeStream(stream); + + } + else + { + qWarning("SoapySDROutputThread::run: no channels or FIFO allocated. Aborting"); + } + + m_running = false; +} + +unsigned int SoapySDROutputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (unsigned int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void SoapySDROutputThread::setLog2Interpolation(unsigned int channel, unsigned int log2_interp) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_log2Interp = log2_interp; + } +} + +unsigned int SoapySDROutputThread::getLog2Interpolation(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_log2Interp; + } else { + return 0; + } +} + +void SoapySDROutputThread::setFifo(unsigned int channel, SampleSourceFifo *sampleFifo) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSourceFifo *SoapySDROutputThread::getFifo(unsigned int channel) +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void SoapySDROutputThread::callbackMO(std::vector& buffs, qint32 samplesPerChannel) +{ + for(unsigned int ichan = 0; ichan < m_nbChannels; ichan++) + { + switch (m_interpolatorType) + { + case Interpolator8: + callbackSO8((qint8*) buffs[ichan], samplesPerChannel, ichan); + break; + case Interpolator12: + callbackSO12((qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case Interpolator16: + callbackSO16((qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case InterpolatorFloat: + default: + // TODO + break; + } + } +} + +// Interpolate according to specified log2 (ex: log2=4 => decim=16). len is a number of samples (not a number of I or Q) + +void SoapySDROutputThread::callbackSO8(qint8* buf, qint32 len, unsigned int channel) +{ + if (m_channels[channel].m_sampleFifo) + { + float bal = m_channels[channel].m_sampleFifo->getRWBalance(); + + if (bal < -0.25) { + qDebug("SoapySDROutputThread::callbackSO8: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("SoapySDROutputThread::callbackSO8: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<getRWBalance(); + + if (bal < -0.25) { + qDebug("SoapySDROutputThread::callbackSO12: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("SoapySDROutputThread::callbackSO12: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<getRWBalance(); + + if (bal < -0.25) { + qDebug("SoapySDROutputThread::callbackSO16: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("SoapySDROutputThread::callbackSO16: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTTHREAD_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTTHREAD_H_ + + +#include +#include +#include + +#include + +#include "soapysdr/devicesoapysdrshared.h" +#include "dsp/interpolators.h" + +class SampleSourceFifo; + +class SoapySDROutputThread : public QThread { + Q_OBJECT + +public: + SoapySDROutputThread(SoapySDR::Device* dev, unsigned int nbTxChannels, QObject* parent = 0); + ~SoapySDROutputThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + unsigned int getNbChannels() const { return m_nbChannels; } + void setLog2Interpolation(unsigned int channel, unsigned int log2_interp); + unsigned int getLog2Interpolation(unsigned int channel) const; + void setFifo(unsigned int channel, SampleSourceFifo *sampleFifo); + SampleSourceFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleSourceFifo* m_sampleFifo; + unsigned int m_log2Interp; + Interpolators m_interpolators8; + Interpolators m_interpolators12; + Interpolators m_interpolators16; + + Channel() : + m_sampleFifo(0), + m_log2Interp(0) + {} + + ~Channel() + {} + }; + + enum InterpolatorType + { + Interpolator8, + Interpolator12, + Interpolator16, + InterpolatorFloat + }; + + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + SoapySDR::Device* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Tx channels + unsigned int m_sampleRate; + unsigned int m_nbChannels; + InterpolatorType m_interpolatorType; + + void run(); + unsigned int getNbFifos(); + void callbackSO8(qint8* buf, qint32 len, unsigned int channel = 0); + void callbackSO12(qint16* buf, qint32 len, unsigned int channel = 0); + void callbackSO16(qint16* buf, qint32 len, unsigned int channel = 0); + void callbackMO(std::vector& buffs, qint32 samplesPerChannel); +}; + + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTTHREAD_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 059bf53a9..173791905 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -47,8 +47,14 @@ SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) : SoapySDRInput::~SoapySDRInput() { + if (m_running) { + stop(); + } + m_deviceAPI->removeSink(m_fileSink); delete m_fileSink; + + closeDevice(); } void SoapySDRInput::destroy() @@ -205,6 +211,7 @@ const SoapySDR::RangeList& SoapySDRInput::getRateRanges() void SoapySDRInput::init() { + applySettings(m_settings, true); } SoapySDRInputThread *SoapySDRInput::findThread() @@ -271,7 +278,7 @@ bool SoapySDRInput::start() // - Single Input (SI) with only one channel streaming. This HAS to be channel 0. // - Multiple Input (MI) with two or more channels. It MUST be in this configuration if any channel other than 0 // is used irrespective of what you actually do with samples coming from ignored channels. - // For example When we will run with only channel 2 streaming from the client perspective the channels 0 amnd 1 will actually + // For example When we will run with only channel 2 streaming from the client perspective the channels 0 and 1 will actually // be enabled and streaming but its samples will just be disregarded. // This means that all channels up to the highest in index being used are activated. // @@ -386,7 +393,7 @@ void SoapySDRInput::stop() // // If the thread is currently managing many channels (MI mode) and we are removing the last channel. The transition // or reduction of MI size is handled by stopping the thread, deleting it and creating a new one - // with one channel less if (and only if) there is still a channel active. + // with the maximum number of channels needed if (and only if) there is still a channel active. // // If the thread is currently managing many channels (MI mode) but the channel being stopped is not the last // channel then the FIFO reference is simply removed from the thread so that it will not stream into this FIFO @@ -474,7 +481,9 @@ void SoapySDRInput::stop() ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); } - if (highestActiveChannelIndex >= 0) { + if (highestActiveChannelIndex >= 0) + { + qDebug("SoapySDRInput::stop: restarting the thread"); soapySDRInputThread->startWork(); } } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 1f2196dd6..a2bd94640 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -156,30 +156,43 @@ QString SoapySDRInputGui::getName() const void SoapySDRInputGui::resetToDefaults() { + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); } qint64 SoapySDRInputGui::getCenterFrequency() const { - return 0; + return m_settings.m_centerFrequency; } -void SoapySDRInputGui::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +void SoapySDRInputGui::setCenterFrequency(qint64 centerFrequency) { + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); } QByteArray SoapySDRInputGui::serialize() const { - SimpleSerializer s(1); - return s.final(); + return m_settings.serialize(); } -bool SoapySDRInputGui::deserialize(const QByteArray& data __attribute__((unused))) +bool SoapySDRInputGui::deserialize(const QByteArray& data) { - return false; + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } } -bool SoapySDRInputGui::handleMessage(const Message& message __attribute__((unused))) +bool SoapySDRInputGui::handleMessage(const Message& message) { if (SoapySDRInput::MsgStartStop::match(message)) { diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 864546904..bb2b06511 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -20,8 +20,6 @@ #include #include -#include - #include "plugin/plugininstancegui.h" #include "util/messagequeue.h" diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.h b/plugins/samplesource/soapysdrinput/soapysdrinputthread.h index d0c91c447..96aeca95b 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputthread.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.h @@ -37,7 +37,7 @@ class SoapySDRInputThread : public QThread { Q_OBJECT public: - SoapySDRInputThread(SoapySDR::Device* dev, unsigned int nbRxChannels, QObject* parent = NULL); + SoapySDRInputThread(SoapySDR::Device* dev, unsigned int nbRxChannels, QObject* parent = 0); ~SoapySDRInputThread(); void startWork(); From 564a99d14e10817782e9c1f2d3cb82246af84589 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Nov 2018 18:42:51 +0100 Subject: [PATCH 926/956] SoapySDR support: fixes --- debian/changelog | 3 +- .../bladerf2output/bladerf2output.cpp | 6 +- .../bladerf2output/bladerf2output.h | 2 +- .../bladerf2output/bladerf2outputplugin.cpp | 2 +- .../soapysdroutput/soapysdroutput.cpp | 209 +++++++++++++++++- .../soapysdroutput/soapysdroutputthread.cpp | 57 +++-- .../soapysdroutput/soapysdroutputthread.h | 2 + .../bladerf2input/bladerf2inputplugin.cpp | 2 +- .../soapysdrinput/soapysdrinput.cpp | 29 ++- 9 files changed, 277 insertions(+), 35 deletions(-) diff --git a/debian/changelog b/debian/changelog index b3dec420b..91461fb1d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ sdrangel (4.3.0-1) unstable; urgency=medium * SoapySDR support + * BladeRF2 corrections - -- Edouard Griffiths, F4EXB Sun, 04 Nov 2018 21:14:18 +0200 + -- Edouard Griffiths, F4EXB Sun, 18 Nov 2018 21:14:18 +0100 sdrangel (4.2.4-1) unstable; urgency=medium diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp index 9b9085357..8ebec2a37 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -520,9 +520,9 @@ void BladeRF2Output::setCenterFrequency(qint64 centerFrequency) } } -bool BladeRF2Output::setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz) +bool BladeRF2Output::setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) { - qint64 df = ((qint64)freq_hz * m_settings.m_LOppmTenths) / 10000000LL; + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; freq_hz += df; int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(requestedChannel), freq_hz); @@ -789,7 +789,7 @@ bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool if (dev != 0) { - if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency)) + if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency, settings.m_LOppmTenths)) { if (getMessageQueueToGUI()) { diff --git a/plugins/samplesink/bladerf2output/bladerf2output.h b/plugins/samplesink/bladerf2output/bladerf2output.h index 9bc83a61a..9b501aeaa 100644 --- a/plugins/samplesink/bladerf2output/bladerf2output.h +++ b/plugins/samplesink/bladerf2output/bladerf2output.h @@ -153,7 +153,7 @@ private: void moveThreadToBuddy(); bool applySettings(const BladeRF2OutputSettings& settings, bool force); int getNbChannels(); - bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz); + bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2OutputSettings& settings); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); diff --git a/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp index c65b66fa5..32c222514 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor BladeRF2OutputPlugin::m_pluginDescriptor = { QString("BladeRF2 Output"), - QString("4.2.1"), + QString("4.3.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index c0e60b784..325cd09a3 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "util/simpleserializer.h" #include "dsp/dspcommands.h" #include "dsp/dspengine.h" @@ -77,6 +79,7 @@ bool SoapySDROutput::openDevice() } m_deviceShared.m_device = device; + m_deviceShared.m_deviceParams = deviceSoapySDRShared->m_deviceParams; } // look for Rx buddies and get reference to the device object else if (m_deviceAPI->getSourceBuddies().size() > 0) // then source @@ -101,6 +104,7 @@ bool SoapySDROutput::openDevice() } m_deviceShared.m_device = device; + m_deviceShared.m_deviceParams = deviceSoapySDRShared->m_deviceParams; } // There are no buddies then create the first BladeRF2 device else @@ -114,6 +118,8 @@ bool SoapySDROutput::openDevice() qCritical("SoapySDROutput::openDevice: cannot open SoapySDR device"); return false; } + + m_deviceShared.m_deviceParams = new DeviceSoapySDRParams(m_deviceShared.m_device); } m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel @@ -341,11 +347,10 @@ bool SoapySDROutput::start() if (needsStart) { qDebug("SoapySDROutput::start: (re)sart buddy thread"); + soapySDROutputThread->setSampleRate(m_settings.m_devSampleRate); soapySDROutputThread->startWork(); } - applySettings(m_settings, true); // re-apply forcibly to set sample rate with the new number of channels - qDebug("SoapySDROutput::start: started"); m_running = true; @@ -518,10 +523,206 @@ void SoapySDROutput::setCenterFrequency(qint64 centerFrequency) } } -bool SoapySDROutput::handleMessage(const Message& message __attribute__((unused))) +bool SoapySDROutput::setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) { - return false; + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; + freq_hz += df; + + try + { + dev->setFrequency(SOAPY_SDR_TX, + requestedChannel, + m_deviceShared.m_deviceParams->getTxChannelMainTunableElementName(requestedChannel), + freq_hz); + qDebug("SoapySDROutput::setDeviceCenterFrequency: setFrequency(%llu)", freq_hz); + return true; + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: could not set frequency: %llu: %s", freq_hz, ex.what()); + return false; + } } +bool SoapySDROutput::handleMessage(const Message& message) +{ + if (MsgConfigureSoapySDROutput::match(message)) + { + MsgConfigureSoapySDROutput& conf = (MsgConfigureSoapySDROutput&) message; + qDebug() << "SoapySDROutput::handleMessage: MsgConfigureSoapySDROutput"; + if (!applySettings(conf.getSettings(), conf.getForce())) { + qDebug("SoapySDROutput::handleMessage: MsgConfigureSoapySDROutput config error"); + } + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "SoapySDROutput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initGeneration()) + { + m_deviceAPI->startGeneration(); + } + } + else + { + m_deviceAPI->stopGeneration(); + } + + return true; + } + else if (DeviceSoapySDRShared::MsgReportBuddyChange::match(message)) + { + int requestedChannel = m_deviceAPI->getItemIndex(); + //DeviceSoapySDRShared::MsgReportBuddyChange& report = (DeviceSoapySDRShared::MsgReportBuddyChange&) message; + SoapySDROutputSettings settings = m_settings; + //bool fromRxBuddy = report.getRxElseTx(); + + settings.m_centerFrequency = m_deviceShared.m_device->getFrequency( + SOAPY_SDR_TX, + requestedChannel, + m_deviceShared.m_deviceParams->getTxChannelMainTunableElementName(requestedChannel)); + + settings.m_devSampleRate = m_deviceShared.m_device->getSampleRate(SOAPY_SDR_TX, requestedChannel); + + //SoapySDROutputThread *outputThread = findThread(); + + m_settings = settings; + + // propagate settings to GUI if any + if (getMessageQueueToGUI()) + { + MsgConfigureSoapySDROutput *reportToGUI = MsgConfigureSoapySDROutput::create(m_settings, false); + getMessageQueueToGUI()->push(reportToGUI); + } + + return true; + } + else + { + return false; + } +} + +bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool force) +{ + bool forwardChangeOwnDSP = false; + bool forwardChangeToBuddies = false; + + SoapySDR::Device *dev = m_deviceShared.m_device; + SoapySDROutputThread *outputThread = findThread(); + int requestedChannel = m_deviceAPI->getItemIndex(); + qint64 xlatedDeviceCenterFrequency = settings.m_centerFrequency; + xlatedDeviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + xlatedDeviceCenterFrequency = xlatedDeviceCenterFrequency < 0 ? 0 : xlatedDeviceCenterFrequency; + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + forwardChangeOwnDSP = true; + forwardChangeToBuddies = true; + + if (dev != 0) + { + try + { + dev->setSampleRate(SOAPY_SDR_TX, requestedChannel, settings.m_devSampleRate); + qDebug() << "SoapySDROutput::applySettings: setSampleRate OK: " << settings.m_devSampleRate; + + if (outputThread) + { + bool wasRunning = outputThread->isRunning(); + outputThread->stopWork(); + outputThread->setSampleRate(settings.m_devSampleRate); + + if (wasRunning) { + outputThread->startWork(); + } + } + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, ex.what()); + } + } + } + + if ((m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + forwardChangeOwnDSP = true; + + if (outputThread != 0) + { + outputThread->setLog2Interpolation(requestedChannel, settings.m_log2Interp); + qDebug() << "SoapySDROutput::applySettings: set decimation to " << (1<getDeviceEngineInputMessageQueue()->push(notif); + } + + if (forwardChangeToBuddies) + { + // send to source buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + + for (const auto &itSource : sourceBuddies) + { + DeviceSoapySDRShared::MsgReportBuddyChange *report = DeviceSoapySDRShared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + 2, + settings.m_devSampleRate, + false); + itSource->getSampleSourceInputMessageQueue()->push(report); + } + + for (const auto &itSink : sinkBuddies) + { + DeviceSoapySDRShared::MsgReportBuddyChange *report = DeviceSoapySDRShared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + 2, + settings.m_devSampleRate, + false); + itSink->getSampleSinkInputMessageQueue()->push(report); + } + } + + m_settings = settings; + + qDebug() << "SoapySDROutput::applySettings: " + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_log2Interp: " << m_settings.m_log2Interp + << " m_devSampleRate: " << m_settings.m_devSampleRate; + + return true; +} diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp index f90ea6f56..b7303d401 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp @@ -86,6 +86,7 @@ void SoapySDROutputThread::run() std::iota(channels.begin(), channels.end(), 0); // Fill with 0, 1, ..., m_nbChannels-1. //initialize the sample rate for all channels + qDebug("SoapySDROutputThread::run: m_sampleRate: %u", m_sampleRate); for (const auto &it : channels) { m_dev->setSampleRate(SOAPY_SDR_TX, it, m_sampleRate); } @@ -144,20 +145,20 @@ void SoapySDROutputThread::run() if (m_nbChannels > 1) { - callbackMO(buffs, numElems*2); // size given in number of I or Q samples (2 items per sample) + callbackMO(buffs, numElems); // size given in number of samples (1 item per sample) } else { switch (m_interpolatorType) { case Interpolator8: - callbackSO8((qint8*) buffs[0], numElems*2); + callbackSO8((qint8*) buffs[0], numElems); break; case Interpolator12: - callbackSO12((qint16*) buffs[0], numElems*2); + callbackSO12((qint16*) buffs[0], numElems); break; case Interpolator16: - callbackSO16((qint16*) buffs[0], numElems*2); + callbackSO16((qint16*) buffs[0], numElems); break; case InterpolatorFloat: default: @@ -230,21 +231,41 @@ void SoapySDROutputThread::callbackMO(std::vector& buffs, qint32 samples { for(unsigned int ichan = 0; ichan < m_nbChannels; ichan++) { - switch (m_interpolatorType) + if (m_channels[ichan].m_sampleFifo) { - case Interpolator8: - callbackSO8((qint8*) buffs[ichan], samplesPerChannel, ichan); - break; - case Interpolator12: - callbackSO12((qint16*) buffs[ichan], samplesPerChannel, ichan); - break; - case Interpolator16: - callbackSO16((qint16*) buffs[ichan], samplesPerChannel, ichan); - break; - case InterpolatorFloat: - default: - // TODO - break; + switch (m_interpolatorType) + { + case Interpolator8: + callbackSO8((qint8*) buffs[ichan], samplesPerChannel, ichan); + break; + case Interpolator12: + callbackSO12((qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case Interpolator16: + callbackSO16((qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case InterpolatorFloat: + default: + // TODO + break; + } + } + else // no FIFO for this channel means channel is unused: fill with zeros + { + switch (m_interpolatorType) + { + case Interpolator8: + std::fill((qint8*) buffs[ichan], (qint8*) buffs[ichan] + 2*samplesPerChannel, 0); + break; + case Interpolator12: + case Interpolator16: + std::fill((qint16*) buffs[ichan], (qint16*) buffs[ichan] + 2*samplesPerChannel, 0); + break; + case InterpolatorFloat: + default: + // TODO + break; + } } } } diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputthread.h b/plugins/samplesink/soapysdroutput/soapysdroutputthread.h index 76cdde042..559d98817 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputthread.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputthread.h @@ -42,6 +42,8 @@ public: unsigned int getNbChannels() const { return m_nbChannels; } void setLog2Interpolation(unsigned int channel, unsigned int log2_interp); unsigned int getLog2Interpolation(unsigned int channel) const; + void setSampleRate(unsigned int sampleRate) { m_sampleRate = sampleRate; } + unsigned int getSampleRate() const { return m_sampleRate; } void setFifo(unsigned int channel, SampleSourceFifo *sampleFifo); SampleSourceFifo *getFifo(unsigned int channel); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp index 59e13c0dd..2008e9bd7 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor Blderf2InputPlugin::m_pluginDescriptor = { QString("BladeRF2 Input"), - QString("4.2.1"), + QString("4.3.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 173791905..5f57035f5 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -372,11 +372,10 @@ bool SoapySDRInput::start() if (needsStart) { qDebug("SoapySDRInput::start: (re)sart buddy thread"); + soapySDRInputThread->setSampleRate(m_settings.m_devSampleRate); soapySDRInputThread->startWork(); } - applySettings(m_settings, true); - qDebug("SoapySDRInput::start: started"); m_running = true; @@ -514,16 +513,28 @@ const QString& SoapySDRInput::getDeviceDescription() const int SoapySDRInput::getSampleRate() const { - return 0; + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } } bool SoapySDRInput::setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) @@ -607,6 +618,7 @@ bool SoapySDRInput::handleMessage(const Message& message __attribute__((unused)) DeviceSoapySDRShared::MsgReportBuddyChange& report = (DeviceSoapySDRShared::MsgReportBuddyChange&) message; SoapySDRInputSettings settings = m_settings; settings.m_fcPos = (SoapySDRInputSettings::fcPos_t) report.getFcPos(); + //bool fromRxBuddy = report.getRxElseTx(); settings.m_centerFrequency = m_deviceShared.m_device->getFrequency( SOAPY_SDR_RX, @@ -624,6 +636,13 @@ bool SoapySDRInput::handleMessage(const Message& message __attribute__((unused)) m_settings = settings; + // propagate settings to GUI if any + if (getMessageQueueToGUI()) + { + MsgConfigureSoapySDRInput *reportToGUI = MsgConfigureSoapySDRInput::create(m_settings, false); + getMessageQueueToGUI()->push(reportToGUI); + } + return true; } else @@ -683,8 +702,6 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo if ((m_settings.m_fcPos != settings.m_fcPos) || force) { - SoapySDRInputThread *inputThread = findThread(); - if (inputThread != 0) { inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); From 2bc59154bf1b2d47501a43cd36211c2509db2fe8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Nov 2018 18:56:57 +0100 Subject: [PATCH 927/956] SoapySDR support: output: fixed sample FIFO resizing --- devices/soapysdr/devicesoapysdrshared.cpp | 4 +++ devices/soapysdr/devicesoapysdrshared.h | 4 +++ .../soapysdroutput/soapysdroutput.cpp | 32 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/devices/soapysdr/devicesoapysdrshared.cpp b/devices/soapysdr/devicesoapysdrshared.cpp index bbfabd460..e7ef10f70 100644 --- a/devices/soapysdr/devicesoapysdrshared.cpp +++ b/devices/soapysdr/devicesoapysdrshared.cpp @@ -18,6 +18,10 @@ MESSAGE_CLASS_DEFINITION(DeviceSoapySDRShared::MsgReportBuddyChange, Message) +const float DeviceSoapySDRShared::m_sampleFifoLengthInSeconds = 0.25; +const int DeviceSoapySDRShared::m_sampleFifoMinSize = 75000; // 300 kS/s knee +const int DeviceSoapySDRShared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32 + DeviceSoapySDRShared::DeviceSoapySDRShared() : m_device(0), m_channel(-1), diff --git a/devices/soapysdr/devicesoapysdrshared.h b/devices/soapysdr/devicesoapysdrshared.h index 36d54c2bc..85adc878a 100644 --- a/devices/soapysdr/devicesoapysdrshared.h +++ b/devices/soapysdr/devicesoapysdrshared.h @@ -87,6 +87,10 @@ public: int m_channel; //!< allocated channel (-1 if none) SoapySDRInput *m_source; SoapySDROutput *m_sink; + + static const float m_sampleFifoLengthInSeconds; + static const int m_sampleFifoMinSize; + static const int m_sampleFifoMinSize32; }; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 325cd09a3..447bb3c58 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -621,6 +621,38 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool xlatedDeviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; xlatedDeviceCenterFrequency = xlatedDeviceCenterFrequency < 0 ? 0 : xlatedDeviceCenterFrequency; + // resize FIFO + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + SoapySDROutputThread *soapySDROutputThread = findThread(); + SampleSourceFifo *fifo = 0; + + if (soapySDROutputThread) + { + fifo = soapySDROutputThread->getFifo(requestedChannel); + soapySDROutputThread->setFifo(requestedChannel, 0); + } + + int fifoSize; + + if (settings.m_log2Interp >= 5) + { + fifoSize = DeviceSoapySDRShared::m_sampleFifoMinSize32; + } + else + { + fifoSize = std::max( + (int) ((settings.m_devSampleRate/(1<setFifo(requestedChannel, &m_sampleSourceFifo); + } + } + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) { forwardChangeOwnDSP = true; From 45a569655aab13b77e6b39afd733b3b8ffe186e5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Nov 2018 19:17:47 +0100 Subject: [PATCH 928/956] SoapySDR support: fixed update settings processing in GUIs --- .../samplesink/soapysdroutput/soapysdroutput.cpp | 5 +++-- .../samplesink/soapysdroutput/soapysdroutputgui.cpp | 12 +++++++++++- .../samplesource/soapysdrinput/soapysdrinput.cpp | 5 +++-- .../samplesource/soapysdrinput/soapysdrinputgui.cpp | 13 +++++++++++-- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 447bb3c58..b861c2348 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -583,12 +583,13 @@ bool SoapySDROutput::handleMessage(const Message& message) SoapySDROutputSettings settings = m_settings; //bool fromRxBuddy = report.getRxElseTx(); - settings.m_centerFrequency = m_deviceShared.m_device->getFrequency( + double centerFrequency = m_deviceShared.m_device->getFrequency( SOAPY_SDR_TX, requestedChannel, m_deviceShared.m_deviceParams->getTxChannelMainTunableElementName(requestedChannel)); - settings.m_devSampleRate = m_deviceShared.m_device->getSampleRate(SOAPY_SDR_TX, requestedChannel); + settings.m_centerFrequency = round(centerFrequency/1000.0) * 1000; + settings.m_devSampleRate = round(m_deviceShared.m_device->getSampleRate(SOAPY_SDR_TX, requestedChannel)); //SoapySDROutputThread *outputThread = findThread(); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index c1b9f09c6..8d8ad295c 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -168,7 +168,17 @@ bool SoapySDROutputGui::deserialize(const QByteArray& data) bool SoapySDROutputGui::handleMessage(const Message& message) { - if (SoapySDROutput::MsgStartStop::match(message)) + if (SoapySDROutput::MsgConfigureSoapySDROutput::match(message)) + { + const SoapySDROutput::MsgConfigureSoapySDROutput& cfg = (SoapySDROutput::MsgConfigureSoapySDROutput&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (SoapySDROutput::MsgStartStop::match(message)) { SoapySDROutput::MsgStartStop& notif = (SoapySDROutput::MsgStartStop&) message; blockApplySettings(true); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 5f57035f5..38adbe497 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -620,12 +620,13 @@ bool SoapySDRInput::handleMessage(const Message& message __attribute__((unused)) settings.m_fcPos = (SoapySDRInputSettings::fcPos_t) report.getFcPos(); //bool fromRxBuddy = report.getRxElseTx(); - settings.m_centerFrequency = m_deviceShared.m_device->getFrequency( + double centerFrequency = m_deviceShared.m_device->getFrequency( SOAPY_SDR_RX, requestedChannel, m_deviceShared.m_deviceParams->getRxChannelMainTunableElementName(requestedChannel)); - settings.m_devSampleRate = m_deviceShared.m_device->getSampleRate(SOAPY_SDR_RX, requestedChannel); + settings.m_centerFrequency = round(centerFrequency/1000.0) * 1000; + settings.m_devSampleRate = round(m_deviceShared.m_device->getSampleRate(SOAPY_SDR_RX, requestedChannel)); SoapySDRInputThread *inputThread = findThread(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index a2bd94640..e26547931 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -191,10 +191,19 @@ bool SoapySDRInputGui::deserialize(const QByteArray& data) } } - bool SoapySDRInputGui::handleMessage(const Message& message) { - if (SoapySDRInput::MsgStartStop::match(message)) + if (SoapySDRInput::MsgConfigureSoapySDRInput::match(message)) + { + const SoapySDRInput::MsgConfigureSoapySDRInput& cfg = (SoapySDRInput::MsgConfigureSoapySDRInput&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (SoapySDRInput::MsgStartStop::match(message)) { SoapySDRInput::MsgStartStop& notif = (SoapySDRInput::MsgStartStop&) message; blockApplySettings(true); From bf3fdcbfc2f2ec6227133c58858f1da8e0c4cdc0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 4 Nov 2018 23:54:16 +0100 Subject: [PATCH 929/956] SoapySDR support: input: antenna selection GUI --- devices/soapysdr/devicesoapysdrparams.cpp | 2 - .../soapysdroutput/soapysdroutput.cpp | 6 + .../soapysdroutput/soapysdroutput.h | 1 + .../soapysdrinput/soapysdrinput.cpp | 38 ++++- .../soapysdrinput/soapysdrinput.h | 2 + .../soapysdrinput/soapysdrinputgui.cpp | 35 ++++- .../soapysdrinput/soapysdrinputgui.h | 7 +- .../soapysdrinput/soapysdrinputgui.ui | 141 +----------------- .../soapysdrinput/soapysdrinputsettings.cpp | 3 + .../soapysdrinput/soapysdrinputsettings.h | 1 + sdrgui/CMakeLists.txt | 2 + sdrgui/soapygui/stringrangegui.cpp | 78 ++++++++++ sdrgui/soapygui/stringrangegui.h | 52 +++++++ 13 files changed, 224 insertions(+), 144 deletions(-) create mode 100644 sdrgui/soapygui/stringrangegui.cpp create mode 100644 sdrgui/soapygui/stringrangegui.h diff --git a/devices/soapysdr/devicesoapysdrparams.cpp b/devices/soapysdr/devicesoapysdrparams.cpp index 2caa0ba9e..0e5d25c03 100644 --- a/devices/soapysdr/devicesoapysdrparams.cpp +++ b/devices/soapysdr/devicesoapysdrparams.cpp @@ -120,11 +120,9 @@ void DeviceSoapySDRParams::fillChannelParams(std::vector& chann channelSettings.back().m_frequencySettingsArgs = m_device->getFrequencyArgsInfo(direction, ichan); // sample rates - channelSettings.back().m_ratesRanges = m_device->getSampleRateRange(direction, ichan); // bandwidths - channelSettings.back().m_bandwidthsRanges = m_device->getBandwidthRange(direction, ichan); } diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index b861c2348..78d97fc1c 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -189,6 +189,12 @@ const SoapySDR::RangeList& SoapySDROutput::getRateRanges() return channelSettings->m_ratesRanges; } +const std::vector& SoapySDROutput::getAntennas() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_antennas; +} + void SoapySDROutput::init() { applySettings(m_settings, true); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.h b/plugins/samplesink/soapysdroutput/soapysdroutput.h index b9ec34c63..d954e1e35 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.h @@ -101,6 +101,7 @@ public: void getFrequencyRange(uint64_t& min, uint64_t& max); const SoapySDR::RangeList& getRateRanges(); + const std::vector& getAntennas(); private: DeviceSinkAPI *m_deviceAPI; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 38adbe497..30b5b7d23 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -209,6 +209,24 @@ const SoapySDR::RangeList& SoapySDRInput::getRateRanges() return channelSettings->m_ratesRanges; } +const std::vector& SoapySDRInput::getAntennas() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_antennas; +} + +int SoapySDRInput::getAntennaIndex(const std::string& antenna) +{ + const std::vector& antennaList = getAntennas(); + std::vector::const_iterator it = std::find(antennaList.begin(), antennaList.end(), antenna); + + if (it == antennaList.end()) { + return -1; + } else { + return it - antennaList.begin(); + } +} + void SoapySDRInput::init() { applySettings(m_settings, true); @@ -745,6 +763,23 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo } } + if ((m_settings.m_antenna != settings.m_antenna) || force) + { + if (dev != 0) + { + try + { + dev->setAntenna(SOAPY_SDR_RX, requestedChannel, settings.m_antenna.toStdString()); + qDebug("SoapySDRInput::applySettings: set antenna to %s", settings.m_antenna.toStdString().c_str()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set antenna to %s: %s", + settings.m_antenna.toStdString().c_str(), ex.what()); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& getAntennas(); + int getAntennaIndex(const std::string& antenna); private: DeviceSourceAPI *m_deviceAPI; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index e26547931..7f749a4e9 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -49,6 +49,7 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); createRangesControl(m_sampleSource->getRateRanges(), "SR", "kS/s"); + createAntennasControl(m_sampleSource->getAntennas()); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); @@ -92,7 +93,7 @@ void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, if (rangeDiscrete) { - DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(ui->scrollAreaWidgetContents); + DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(this); rangeGUI->setLabel(text); rangeGUI->setUnits(unit); @@ -101,6 +102,9 @@ void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, } m_sampleRateGUI = rangeGUI; + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(rangeGUI); + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); // QHBoxLayout *layout = new QHBoxLayout(); // QLabel *rangeLabel = new QLabel(); @@ -140,10 +144,29 @@ void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, rangeGUI->reset(); m_sampleRateGUI = rangeGUI; + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(rangeGUI); + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); } } +void SoapySDRInputGui::createAntennasControl(const std::vector& antennaList) +{ + m_antennas = new StringRangeGUI(this); + m_antennas->setLabel(QString("Antenna")); + m_antennas->setUnits(QString("Port")); + + for (const auto &it : antennaList) { + m_antennas->addItem(QString(it.c_str()), it); + } + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(m_antennas); + + connect(m_antennas, SIGNAL(valueChanged()), this, SLOT(antennasChanged())); +} + void SoapySDRInputGui::setName(const QString& name) { setObjectName(name); @@ -252,6 +275,14 @@ void SoapySDRInputGui::sampleRateChanged(double sampleRate) sendSettings(); } +void SoapySDRInputGui::antennasChanged() +{ + const std::string& antennaStr = m_antennas->getCurrentValue(); + m_settings.m_antenna = QString(antennaStr.c_str()); + + sendSettings(); +} + void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; @@ -346,6 +377,8 @@ void SoapySDRInputGui::displaySettings() ui->LOppm->setValue(m_settings.m_LOppmTenths); ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + m_antennas->setValue(m_settings.m_antenna.toStdString()); + blockApplySettings(false); } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index bb2b06511..8fbe09013 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -17,12 +17,13 @@ #ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ #define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ +#include #include #include +#include #include "plugin/plugininstancegui.h" #include "util/messagequeue.h" - #include "soapysdrinput.h" class DeviceUISet; @@ -53,6 +54,8 @@ public: private: void createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit); + void createAntennasControl(const std::vector& antennaList); + Ui::SoapySDRInputGui* ui; DeviceUISet* m_deviceUISet; @@ -68,6 +71,7 @@ private: MessageQueue m_inputMessageQueue; ItemSettingGUI *m_sampleRateGUI; + StringRangeGUI *m_antennas; void displaySettings(); void sendSettings(); @@ -81,6 +85,7 @@ private slots: void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void sampleRateChanged(double sampleRate); + void antennasChanged(); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); void on_decim_currentIndexChanged(int index); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index b5e295eef..c6d29c657 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -382,8 +382,8 @@ 0 0 - 304 - 120 + 318 + 51
    @@ -402,143 +402,6 @@ 0 - - - - - - Data1 - - - - - - - - 0 - - - - - 1 - - - - - 2 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Kiki - - - - - - - Qt::Horizontal - - - - - - - 0.0 - - - - - - - - - - - TextLabel - - - - - - - ... - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Tata - - - - - - - - - - - Zozo - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - -
    diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp index ba8e75e2a..901c7f9dc 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -35,6 +35,7 @@ void SoapySDRInputSettings::resetToDefaults() m_transverterMode = false; m_transverterDeltaFrequency = 0; m_fileRecordName = ""; + m_antenna = "NONE"; } QByteArray SoapySDRInputSettings::serialize() const @@ -49,6 +50,7 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeS32(6, m_LOppmTenths); s.writeBool(7, m_transverterMode); s.writeS64(8, m_transverterDeltaFrequency); + s.writeString(9, m_antenna); return s.final(); } @@ -76,6 +78,7 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) d.readS32(6, &m_LOppmTenths); d.readBool(7, &m_transverterMode, false); d.readS64(8, &m_transverterDeltaFrequency, 0); + d.readString(9, &m_antenna, "NONE"); return true; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h index 53c967b03..8991c8ec1 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -37,6 +37,7 @@ struct SoapySDRInputSettings { bool m_transverterMode; qint64 m_transverterDeltaFrequency; QString m_fileRecordName; + QString m_antenna; SoapySDRInputSettings(); void resetToDefaults(); diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 1130a0a47..709164adf 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -57,6 +57,7 @@ set(sdrgui_SOURCES soapygui/discreterangegui.cpp soapygui/intervalrangegui.cpp soapygui/itemsettinggui.cpp + soapygui/stringrangegui.cpp webapi/webapiadaptergui.cpp ) @@ -118,6 +119,7 @@ set(sdrgui_HEADERS soapygui/discreterangegui.h soapygui/intervalrangegui.h soapygui/itemsettinggui.h + soapygui/stringrangegui.h webapi/webapiadaptergui.h ) diff --git a/sdrgui/soapygui/stringrangegui.cpp b/sdrgui/soapygui/stringrangegui.cpp new file mode 100644 index 000000000..2cf3cb011 --- /dev/null +++ b/sdrgui/soapygui/stringrangegui.cpp @@ -0,0 +1,78 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "ui_discreterangegui.h" + +#include "stringrangegui.h" + +StringRangeGUI::StringRangeGUI(QWidget* parent) : + QWidget(parent), + ui(new Ui::DiscreteRangeGUI) +{ + ui->setupUi(this); +} + +StringRangeGUI::~StringRangeGUI() +{ + delete ui; +} + +void StringRangeGUI::setLabel(const QString& text) +{ + ui->rangeLabel->setText(text); +} + +void StringRangeGUI::setUnits(const QString& units) +{ + ui->rangeUnits->setText(units); +} + +void StringRangeGUI::addItem(const QString& itemStr, const std::string& itemValue) +{ + ui->rangeCombo->blockSignals(true); + ui->rangeCombo->addItem(itemStr); + itemValues.push_back(itemValue); + ui->rangeCombo->blockSignals(false); +} + +const std::string& StringRangeGUI::getCurrentValue() +{ + return itemValues[ui->rangeCombo->currentIndex()]; +} + +void StringRangeGUI::setValue(const std::string& value) +{ + int index = 0; + + for (const auto &it : itemValues) + { + if (it >= value) + { + ui->rangeCombo->blockSignals(true); + ui->rangeCombo->setCurrentIndex(index); + ui->rangeCombo->blockSignals(false); + break; + } + + index++; + } +} + +void StringRangeGUI::on_rangeCombo_currentIndexChanged(int index __attribute__((unused))) +{ + emit valueChanged(); +} + diff --git a/sdrgui/soapygui/stringrangegui.h b/sdrgui/soapygui/stringrangegui.h new file mode 100644 index 000000000..994fcf378 --- /dev/null +++ b/sdrgui/soapygui/stringrangegui.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 SDRGUI_SOAPYGUI_STRINGRANGEGUI_H_ +#define SDRGUI_SOAPYGUI_STRINGRANGEGUI_H_ + +#include + +namespace Ui { + class DiscreteRangeGUI; +} + +class StringRangeGUI : public QWidget +{ + Q_OBJECT +public: + explicit StringRangeGUI(QWidget* parent = 0); + virtual ~StringRangeGUI(); + + void setLabel(const QString& text); + void setUnits(const QString& units); + void addItem(const QString& itemStr, const std::string& itemValue); + const std::string& getCurrentValue(); + void setValue(const std::string& value); + +signals: + void valueChanged(); + +private slots: + void on_rangeCombo_currentIndexChanged(int index); + +private: + Ui::DiscreteRangeGUI* ui; + std::vector itemValues; +}; + + + +#endif /* SDRGUI_SOAPYGUI_STRINGRANGEGUI_H_ */ From 0d0b8c9618b0097c6738fc9d5aced741588fa2e2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Nov 2018 02:19:40 +0100 Subject: [PATCH 930/956] SoapySDR support: input: bandwidth selection GUI --- .../soapysdroutput/soapysdroutput.cpp | 17 +++++ .../soapysdroutput/soapysdroutputgui.cpp | 40 +++++++++- .../soapysdroutput/soapysdroutputgui.h | 5 ++ .../soapysdroutput/soapysdroutputsettings.cpp | 3 + .../soapysdroutput/soapysdroutputsettings.h | 5 +- .../soapysdrinput/soapysdrinput.cpp | 33 +++++++- .../soapysdrinput/soapysdrinput.h | 3 +- .../soapysdrinput/soapysdrinputgui.cpp | 75 +++++++++---------- .../soapysdrinput/soapysdrinputgui.h | 15 +++- .../soapysdrinput/soapysdrinputgui.ui | 5 +- .../soapysdrinput/soapysdrinputsettings.cpp | 3 + .../soapysdrinput/soapysdrinputsettings.h | 1 + sdrgui/soapygui/discreterangegui.ui | 12 +++ sdrgui/soapygui/intervalrangegui.ui | 12 +++ 14 files changed, 176 insertions(+), 53 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 78d97fc1c..8272d92e9 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -717,6 +717,23 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool } } + if ((m_settings.m_antenna != settings.m_antenna) || force) + { + if (dev != 0) + { + try + { + dev->setAntenna(SOAPY_SDR_TX, requestedChannel, settings.m_antenna.toStdString()); + qDebug("SoapySDROutput::applySettings: set antenna to %s", settings.m_antenna.toStdString().c_str()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set antenna to %s: %s", + settings.m_antenna.toStdString().c_str(), ex.what()); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<getFrequencyRange(f_min, f_max); ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + createAntennasControl(m_sampleSink->getAntennas()); createRangesControl(m_sampleSink->getRateRanges(), "SR", "kS/s"); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); @@ -90,7 +92,7 @@ void SoapySDROutputGui::createRangesControl(const SoapySDR::RangeList& rangeList if (rangeDiscrete) { - DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(ui->scrollAreaWidgetContents); + DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(this); rangeGUI->setLabel(text); rangeGUI->setUnits(unit); @@ -99,11 +101,14 @@ void SoapySDROutputGui::createRangesControl(const SoapySDR::RangeList& rangeList } m_sampleRateGUI = rangeGUI; + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(rangeGUI); + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); } else if (rangeInterval) { - IntervalRangeGUI *rangeGUI = new IntervalRangeGUI(ui->scrollAreaWidgetContents); + IntervalRangeGUI *rangeGUI = new IntervalRangeGUI(this); rangeGUI->setLabel(text); rangeGUI->setUnits(unit); @@ -114,10 +119,33 @@ void SoapySDROutputGui::createRangesControl(const SoapySDR::RangeList& rangeList rangeGUI->reset(); m_sampleRateGUI = rangeGUI; + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(rangeGUI); + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); } } +void SoapySDROutputGui::createAntennasControl(const std::vector& antennaList) +{ + if (antennaList.size() == 0) { // return early if the antenna list is empty + return; + } + + m_antennas = new StringRangeGUI(this); + m_antennas->setLabel(QString("Antenna")); + m_antennas->setUnits(QString("Port")); + + for (const auto &it : antennaList) { + m_antennas->addItem(QString(it.c_str()), it); + } + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(m_antennas); + + connect(m_antennas, SIGNAL(valueChanged()), this, SLOT(antennasChanged())); +} + void SoapySDROutputGui::setName(const QString& name) { setObjectName(name); @@ -227,6 +255,14 @@ void SoapySDROutputGui::sampleRateChanged(double sampleRate) sendSettings(); } +void SoapySDROutputGui::antennasChanged() +{ + const std::string& antennaStr = m_antennas->getCurrentValue(); + m_settings.m_antenna = QString(antennaStr.c_str()); + + sendSettings(); +} + void SoapySDROutputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index 3d130f390..84b9250eb 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -29,6 +29,7 @@ class DeviceSampleSink; class DeviceUISet; class ItemSettingGUI; +class StringRangeGUI; namespace Ui { class SoapySDROutputGui; @@ -55,6 +56,8 @@ public: private: void createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit); + void createAntennasControl(const std::vector& antennaList); + Ui::SoapySDROutputGui* ui; DeviceUISet* m_deviceUISet; @@ -70,6 +73,7 @@ private: MessageQueue m_inputMessageQueue; ItemSettingGUI *m_sampleRateGUI; + StringRangeGUI *m_antennas; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); @@ -83,6 +87,7 @@ private slots: void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void sampleRateChanged(double sampleRate); + void antennasChanged(); void on_interp_currentIndexChanged(int index); void on_transverter_clicked(); void on_startStop_toggled(bool checked); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp index 140212219..ec2e6c869 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -32,6 +32,7 @@ void SoapySDROutputSettings::resetToDefaults() m_log2Interp = 0; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_antenna = "NONE"; } QByteArray SoapySDROutputSettings::serialize() const @@ -43,6 +44,7 @@ QByteArray SoapySDROutputSettings::serialize() const s.writeU32(3, m_log2Interp); s.writeBool(4, m_transverterMode); s.writeS64(5, m_transverterDeltaFrequency); + s.writeString(6, m_antenna); return s.final(); } @@ -64,6 +66,7 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) d.readU32(3, &m_log2Interp); d.readBool(4, &m_transverterMode, false); d.readS64(5, &m_transverterDeltaFrequency, 0); + d.readString(6, &m_antenna, "NONE"); return true; } diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h index 1e6f190e2..a5a4b0181 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -24,8 +24,9 @@ struct SoapySDROutputSettings { int m_LOppmTenths; qint32 m_devSampleRate; quint32 m_log2Interp; - bool m_transverterMode; - qint64 m_transverterDeltaFrequency; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + QString m_antenna; SoapySDROutputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 30b5b7d23..97b6e2de2 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -203,16 +203,22 @@ void SoapySDRInput::getFrequencyRange(uint64_t& min, uint64_t& max) } } +const std::vector& SoapySDRInput::getAntennas() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_antennas; +} + const SoapySDR::RangeList& SoapySDRInput::getRateRanges() { const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); return channelSettings->m_ratesRanges; } -const std::vector& SoapySDRInput::getAntennas() +const SoapySDR::RangeList& SoapySDRInput::getBandwidthRanges() { const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); - return channelSettings->m_antennas; + return channelSettings->m_bandwidthsRanges; } int SoapySDRInput::getAntennaIndex(const std::string& antenna) @@ -645,6 +651,7 @@ bool SoapySDRInput::handleMessage(const Message& message __attribute__((unused)) settings.m_centerFrequency = round(centerFrequency/1000.0) * 1000; settings.m_devSampleRate = round(m_deviceShared.m_device->getSampleRate(SOAPY_SDR_RX, requestedChannel)); + settings.m_bandwidth = round(m_deviceShared.m_device->getBandwidth(SOAPY_SDR_RX, requestedChannel)); SoapySDRInputThread *inputThread = findThread(); @@ -780,6 +787,25 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo } } + if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) + { + forwardChangeToBuddies = true; + + if (dev != 0) + { + try + { + dev->setBandwidth(SOAPY_SDR_RX, requestedChannel, settings.m_bandwidth); + qDebug("SoapySDRInput::applySettings: bandwidth set to %u", settings.m_bandwidth); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set bandwidth to %u: %s", + settings.m_bandwidth, ex.what()); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& getAntennas(); + const SoapySDR::RangeList& getRateRanges(); + const SoapySDR::RangeList& getBandwidthRanges(); int getAntennaIndex(const std::string& antenna); private: diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 7f749a4e9..8dea5e21b 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -38,7 +38,8 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleRate(0), m_deviceCenterFrequency(0), m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), - m_sampleRateGUI(0) + m_sampleRateGUI(0), + m_bandwidthGUI(0) { m_sampleSource = (SoapySDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); ui->setupUi(this); @@ -48,8 +49,16 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleSource->getFrequencyRange(f_min, f_max); ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); - createRangesControl(m_sampleSource->getRateRanges(), "SR", "kS/s"); createAntennasControl(m_sampleSource->getAntennas()); + createRangesControl(&m_sampleRateGUI, m_sampleSource->getRateRanges(), "SR", "S/s"); + createRangesControl(&m_bandwidthGUI, m_sampleSource->getBandwidthRanges(), "BW", "Hz"); + + if (m_sampleRateGUI) { + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); + } + if (m_bandwidthGUI) { + connect(m_bandwidthGUI, SIGNAL(valueChanged(double)), this, SLOT(bandwidthChanged(double))); + } connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); @@ -73,7 +82,11 @@ void SoapySDRInputGui::destroy() delete this; } -void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit) +void SoapySDRInputGui::createRangesControl( + ItemSettingGUI **settingGUI, + const SoapySDR::RangeList& rangeList, + const QString& text, + const QString& unit) { if (rangeList.size() == 0) { // return early if the range list is empty return; @@ -95,41 +108,15 @@ void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, { DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(this); rangeGUI->setLabel(text); - rangeGUI->setUnits(unit); + rangeGUI->setUnits(QString("k%1").arg(unit)); for (const auto &it : rangeList) { rangeGUI->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0)), it.minimum()); } - m_sampleRateGUI = rangeGUI; + *settingGUI = rangeGUI; QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); layout->addWidget(rangeGUI); - - connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); -// QHBoxLayout *layout = new QHBoxLayout(); -// QLabel *rangeLabel = new QLabel(); -// rangeLabel->setText(text); -// QLabel *rangeUnit = new QLabel(); -// rangeUnit->setText(unit); -// QComboBox *rangeCombo = new QComboBox(); -// -// for (const auto &it : rangeList) { -// rangeCombo->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0))); -// } -// -// layout->addWidget(rangeLabel); -// layout->addWidget(rangeCombo); -// layout->addWidget(rangeUnit); -// layout->setMargin(0); -// layout->setSpacing(6); -// rangeLabel->show(); -// rangeCombo->show(); -// QWidget *window = new QWidget(ui->scrollAreaWidgetContents); -// window->setFixedWidth(300); -// window->setFixedHeight(30); -// window->setContentsMargins(0,0,0,0); -// //window->setStyleSheet("background-color:black;"); -// window->setLayout(layout); } else if (rangeInterval) { @@ -143,16 +130,18 @@ void SoapySDRInputGui::createRangesControl(const SoapySDR::RangeList& rangeList, rangeGUI->reset(); - m_sampleRateGUI = rangeGUI; + *settingGUI = rangeGUI; QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); layout->addWidget(rangeGUI); - - connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); } } void SoapySDRInputGui::createAntennasControl(const std::vector& antennaList) { + if (antennaList.size() == 0) { // return early if the antenna list is empty + return; + } + m_antennas = new StringRangeGUI(this); m_antennas->setLabel(QString("Antenna")); m_antennas->setUnits(QString("Port")); @@ -269,17 +258,23 @@ void SoapySDRInputGui::handleInputMessages() } } +void SoapySDRInputGui::antennasChanged() +{ + const std::string& antennaStr = m_antennas->getCurrentValue(); + m_settings.m_antenna = QString(antennaStr.c_str()); + + sendSettings(); +} + void SoapySDRInputGui::sampleRateChanged(double sampleRate) { m_settings.m_devSampleRate = sampleRate; sendSettings(); } -void SoapySDRInputGui::antennasChanged() +void SoapySDRInputGui::bandwidthChanged(double bandwidth) { - const std::string& antennaStr = m_antennas->getCurrentValue(); - m_settings.m_antenna = QString(antennaStr.c_str()); - + m_settings.m_bandwidth = bandwidth; sendSettings(); } @@ -366,7 +361,9 @@ void SoapySDRInputGui::displaySettings() blockApplySettings(true); ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + m_antennas->setValue(m_settings.m_antenna.toStdString()); m_sampleRateGUI->setValue(m_settings.m_devSampleRate); + m_bandwidthGUI->setValue(m_settings.m_bandwidth); ui->dcOffset->setChecked(m_settings.m_dcBlock); ui->iqImbalance->setChecked(m_settings.m_iqCorrection); @@ -377,8 +374,6 @@ void SoapySDRInputGui::displaySettings() ui->LOppm->setValue(m_settings.m_LOppmTenths); ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); - m_antennas->setValue(m_settings.m_antenna.toStdString()); - blockApplySettings(false); } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 8fbe09013..7a1ab29d4 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -28,6 +28,7 @@ class DeviceUISet; class ItemSettingGUI; +class StringRangeGUI; namespace Ui { class SoapySDRInputGui; @@ -53,7 +54,11 @@ public: virtual bool handleMessage(const Message& message); private: - void createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit); + void createRangesControl( + ItemSettingGUI **settingGUI, + const SoapySDR::RangeList& rangeList, + const QString& text, + const QString& unit); void createAntennasControl(const std::vector& antennaList); Ui::SoapySDRInputGui* ui; @@ -70,8 +75,9 @@ private: int m_lastEngineState; MessageQueue m_inputMessageQueue; - ItemSettingGUI *m_sampleRateGUI; StringRangeGUI *m_antennas; + ItemSettingGUI *m_sampleRateGUI; + ItemSettingGUI *m_bandwidthGUI; void displaySettings(); void sendSettings(); @@ -82,10 +88,11 @@ private: private slots: void handleInputMessages(); - void on_centerFrequency_changed(quint64 value); - void on_LOppm_valueChanged(int value); void sampleRateChanged(double sampleRate); void antennasChanged(); + void bandwidthChanged(double bandwidth); + void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); void on_decim_currentIndexChanged(int index); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index c6d29c657..89d9fa789 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -374,6 +374,9 @@
    + + Qt::ScrollBarAlwaysOn + true @@ -382,7 +385,7 @@ 0 0 - 318 + 304 51 diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp index 901c7f9dc..9489947b9 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -36,6 +36,7 @@ void SoapySDRInputSettings::resetToDefaults() m_transverterDeltaFrequency = 0; m_fileRecordName = ""; m_antenna = "NONE"; + m_bandwidth = 1000000; } QByteArray SoapySDRInputSettings::serialize() const @@ -51,6 +52,7 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeBool(7, m_transverterMode); s.writeS64(8, m_transverterDeltaFrequency); s.writeString(9, m_antenna); + s.writeU32(10, m_bandwidth); return s.final(); } @@ -79,6 +81,7 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) d.readBool(7, &m_transverterMode, false); d.readS64(8, &m_transverterDeltaFrequency, 0); d.readString(9, &m_antenna, "NONE"); + d.readU32(10, &m_bandwidth, 1000000); return true; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h index 8991c8ec1..62a942814 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -38,6 +38,7 @@ struct SoapySDRInputSettings { qint64 m_transverterDeltaFrequency; QString m_fileRecordName; QString m_antenna; + quint32 m_bandwidth; SoapySDRInputSettings(); void resetToDefaults(); diff --git a/sdrgui/soapygui/discreterangegui.ui b/sdrgui/soapygui/discreterangegui.ui index b8f36aff0..97f17c230 100644 --- a/sdrgui/soapygui/discreterangegui.ui +++ b/sdrgui/soapygui/discreterangegui.ui @@ -10,6 +10,18 @@ 30 + + + 0 + 0 + + + + + 0 + 30 + + Form diff --git a/sdrgui/soapygui/intervalrangegui.ui b/sdrgui/soapygui/intervalrangegui.ui index f7dc19a5e..7bf944db1 100644 --- a/sdrgui/soapygui/intervalrangegui.ui +++ b/sdrgui/soapygui/intervalrangegui.ui @@ -10,6 +10,18 @@ 30 + + + 0 + 0 + + + + + 0 + 30 + + Form From 52e9a720989426b82322f239fcdd03d307f81626 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Nov 2018 02:40:02 +0100 Subject: [PATCH 931/956] SoapySDR support: output: bandwidth selection GUI --- .../soapysdroutput/soapysdroutput.cpp | 29 +++++++++++++++- .../soapysdroutput/soapysdroutput.h | 1 + .../soapysdroutput/soapysdroutputgui.cpp | 34 +++++++++++++------ .../soapysdroutput/soapysdroutputgui.h | 12 +++++-- .../soapysdroutput/soapysdroutputsettings.cpp | 3 ++ .../soapysdroutput/soapysdroutputsettings.h | 1 + .../soapysdrinput/soapysdrinputgui.ui | 4 +-- sdrgui/soapygui/intervalrangegui.cpp | 1 + 8 files changed, 69 insertions(+), 16 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 8272d92e9..7e4d4992f 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -195,6 +195,12 @@ const std::vector& SoapySDROutput::getAntennas() return channelSettings->m_antennas; } +const SoapySDR::RangeList& SoapySDROutput::getBandwidthRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_bandwidthsRanges; +} + void SoapySDROutput::init() { applySettings(m_settings, true); @@ -596,6 +602,7 @@ bool SoapySDROutput::handleMessage(const Message& message) settings.m_centerFrequency = round(centerFrequency/1000.0) * 1000; settings.m_devSampleRate = round(m_deviceShared.m_device->getSampleRate(SOAPY_SDR_TX, requestedChannel)); + settings.m_bandwidth = round(m_deviceShared.m_device->getBandwidth(SOAPY_SDR_TX, requestedChannel)); //SoapySDROutputThread *outputThread = findThread(); @@ -734,6 +741,25 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool } } + if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) + { + forwardChangeToBuddies = true; + + if (dev != 0) + { + try + { + dev->setBandwidth(SOAPY_SDR_TX, requestedChannel, settings.m_bandwidth); + qDebug("SoapySDROutput::applySettings: bandwidth set to %u", settings.m_bandwidth); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set bandwidth to %u: %s", + settings.m_bandwidth, ex.what()); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& getAntennas(); + const SoapySDR::RangeList& getBandwidthRanges(); private: DeviceSinkAPI *m_deviceAPI; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index 7ec22a0e4..42bf83b1f 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -48,7 +48,15 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); createAntennasControl(m_sampleSink->getAntennas()); - createRangesControl(m_sampleSink->getRateRanges(), "SR", "kS/s"); + createRangesControl(&m_sampleRateGUI, m_sampleSink->getRateRanges(), "SR", "S/s"); + createRangesControl(&m_bandwidthGUI, m_sampleSink->getBandwidthRanges(), "BW", "Hz"); + + if (m_sampleRateGUI) { + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); + } + if (m_bandwidthGUI) { + connect(m_bandwidthGUI, SIGNAL(valueChanged(double)), this, SLOT(bandwidthChanged(double))); + } connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); @@ -72,7 +80,11 @@ void SoapySDROutputGui::destroy() delete this; } -void SoapySDROutputGui::createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit) +void SoapySDROutputGui::createRangesControl( + ItemSettingGUI **settingGUI, + const SoapySDR::RangeList& rangeList, + const QString& text, + const QString& unit) { if (rangeList.size() == 0) { // return early if the range list is empty return; @@ -94,17 +106,15 @@ void SoapySDROutputGui::createRangesControl(const SoapySDR::RangeList& rangeList { DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(this); rangeGUI->setLabel(text); - rangeGUI->setUnits(unit); + rangeGUI->setUnits(QString("k%1").arg(unit)); for (const auto &it : rangeList) { rangeGUI->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0)), it.minimum()); } - m_sampleRateGUI = rangeGUI; + *settingGUI = rangeGUI; QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); layout->addWidget(rangeGUI); - - connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); } else if (rangeInterval) { @@ -118,11 +128,9 @@ void SoapySDROutputGui::createRangesControl(const SoapySDR::RangeList& rangeList rangeGUI->reset(); - m_sampleRateGUI = rangeGUI; + *settingGUI = rangeGUI; QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); layout->addWidget(rangeGUI); - - connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); } } @@ -259,10 +267,16 @@ void SoapySDROutputGui::antennasChanged() { const std::string& antennaStr = m_antennas->getCurrentValue(); m_settings.m_antenna = QString(antennaStr.c_str()); - sendSettings(); } +void SoapySDROutputGui::bandwidthChanged(double bandwidth) +{ + m_settings.m_bandwidth = bandwidth; + sendSettings(); +} + + void SoapySDROutputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index 84b9250eb..f75108544 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -55,7 +55,11 @@ public: virtual bool handleMessage(const Message& message); private: - void createRangesControl(const SoapySDR::RangeList& rangeList, const QString& text, const QString& unit); + void createRangesControl( + ItemSettingGUI **settingGUI, + const SoapySDR::RangeList& rangeList, + const QString& text, + const QString& unit); void createAntennasControl(const std::vector& antennaList); Ui::SoapySDROutputGui* ui; @@ -74,6 +78,7 @@ private: ItemSettingGUI *m_sampleRateGUI; StringRangeGUI *m_antennas; + ItemSettingGUI *m_bandwidthGUI; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); @@ -84,10 +89,11 @@ private: private slots: void handleInputMessages(); - void on_centerFrequency_changed(quint64 value); - void on_LOppm_valueChanged(int value); void sampleRateChanged(double sampleRate); void antennasChanged(); + void bandwidthChanged(double bandwidth); + void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); void on_interp_currentIndexChanged(int index); void on_transverter_clicked(); void on_startStop_toggled(bool checked); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp index ec2e6c869..2efd42e74 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -33,6 +33,7 @@ void SoapySDROutputSettings::resetToDefaults() m_transverterMode = false; m_transverterDeltaFrequency = 0; m_antenna = "NONE"; + m_bandwidth = 1000000; } QByteArray SoapySDROutputSettings::serialize() const @@ -45,6 +46,7 @@ QByteArray SoapySDROutputSettings::serialize() const s.writeBool(4, m_transverterMode); s.writeS64(5, m_transverterDeltaFrequency); s.writeString(6, m_antenna); + s.writeU32(7, m_bandwidth); return s.final(); } @@ -67,6 +69,7 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) d.readBool(4, &m_transverterMode, false); d.readS64(5, &m_transverterDeltaFrequency, 0); d.readString(6, &m_antenna, "NONE"); + d.readU32(7, &m_bandwidth, 1000000); return true; } diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h index a5a4b0181..9e368bc45 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -27,6 +27,7 @@ struct SoapySDROutputSettings { bool m_transverterMode; qint64 m_transverterDeltaFrequency; QString m_antenna; + quint32 m_bandwidth; SoapySDROutputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index 89d9fa789..be2341a59 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -375,7 +375,7 @@ - Qt::ScrollBarAlwaysOn + Qt::ScrollBarAsNeeded true @@ -385,7 +385,7 @@ 0 0 - 304 + 318 51 diff --git a/sdrgui/soapygui/intervalrangegui.cpp b/sdrgui/soapygui/intervalrangegui.cpp index 10a3df81e..346227e9b 100644 --- a/sdrgui/soapygui/intervalrangegui.cpp +++ b/sdrgui/soapygui/intervalrangegui.cpp @@ -96,5 +96,6 @@ void IntervalRangeGUI::on_value_changed(quint64 value) void IntervalRangeGUI::on_rangeInterval_currentIndexChanged(int index) { ui->value->setValueRange(m_nbDigits, m_minima[index], m_maxima[index]); + emit ItemSettingGUI::valueChanged(ui->value->getValueNew()); } From 0f1452703934048779e69635ba8b8f721b5af31f Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 5 Nov 2018 17:27:32 +0100 Subject: [PATCH 932/956] SoapySDR support: input: tunable elements GUIs --- .../soapysdroutput/soapysdroutput.cpp | 6 +++ .../soapysdroutput/soapysdroutput.h | 1 + .../soapysdrinput/soapysdrinput.cpp | 6 +++ .../soapysdrinput/soapysdrinput.h | 1 + .../soapysdrinput/soapysdrinputgui.cpp | 26 ++++++++++++ .../soapysdrinput/soapysdrinputgui.h | 6 ++- .../soapysdrinput/soapysdrinputsettings.cpp | 23 +++++++++++ .../soapysdrinput/soapysdrinputsettings.h | 6 +++ sdrgui/CMakeLists.txt | 26 ++++++------ sdrgui/soapygui/dynamicitemsettinggui.cpp | 34 +++++++++++++++ sdrgui/soapygui/dynamicitemsettinggui.h | 41 +++++++++++++++++++ 11 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 sdrgui/soapygui/dynamicitemsettinggui.cpp create mode 100644 sdrgui/soapygui/dynamicitemsettinggui.h diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 7e4d4992f..997520360 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -201,6 +201,12 @@ const SoapySDR::RangeList& SoapySDROutput::getBandwidthRanges() return channelSettings->m_bandwidthsRanges; } +const std::vector& SoapySDROutput::getTunableElements() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_frequencySettings; +} + void SoapySDROutput::init() { applySettings(m_settings, true); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.h b/plugins/samplesink/soapysdroutput/soapysdroutput.h index 56b21850f..ef848e94a 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.h @@ -103,6 +103,7 @@ public: const SoapySDR::RangeList& getRateRanges(); const std::vector& getAntennas(); const SoapySDR::RangeList& getBandwidthRanges(); + const std::vector& getTunableElements(); private: DeviceSinkAPI *m_deviceAPI; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 97b6e2de2..5d505c8b8 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -233,6 +233,12 @@ int SoapySDRInput::getAntennaIndex(const std::string& antenna) } } +const std::vector& SoapySDRInput::getTunableElements() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_frequencySettings; +} + void SoapySDRInput::init() { applySettings(m_settings, true); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h index 5001b1417..212f9f8b7 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -125,6 +125,7 @@ public: const SoapySDR::RangeList& getRateRanges(); const SoapySDR::RangeList& getBandwidthRanges(); int getAntennaIndex(const std::string& antenna); + const std::vector& getTunableElements(); private: DeviceSourceAPI *m_deviceAPI; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 8dea5e21b..4470a31a3 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -24,6 +24,8 @@ #include "gui/glspectrum.h" #include "soapygui/discreterangegui.h" #include "soapygui/intervalrangegui.h" +#include "soapygui/stringrangegui.h" +#include "soapygui/dynamicitemsettinggui.h" #include "ui_soapysdrinputgui.h" #include "soapysdrinputgui.h" @@ -52,6 +54,7 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : createAntennasControl(m_sampleSource->getAntennas()); createRangesControl(&m_sampleRateGUI, m_sampleSource->getRateRanges(), "SR", "S/s"); createRangesControl(&m_bandwidthGUI, m_sampleSource->getBandwidthRanges(), "BW", "Hz"); + createTunableElementsControl(m_sampleSource->getTunableElements()); if (m_sampleRateGUI) { connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); @@ -156,6 +159,24 @@ void SoapySDRInputGui::createAntennasControl(const std::vector& ant connect(m_antennas, SIGNAL(valueChanged()), this, SLOT(antennasChanged())); } +void SoapySDRInputGui::createTunableElementsControl(const std::vector& tunableElementsList) +{ + if (tunableElementsList.size() <= 1) { // This list is created for other elements than the main one (RF) which is always at index 0 + return; + } + + std::vector::const_iterator it = tunableElementsList.begin() + 1; + + for (int i = 0; it != tunableElementsList.end(); ++it, i++) + { + ItemSettingGUI *rangeGUI; + createRangesControl(&rangeGUI, it->m_ranges, QString("%1 freq").arg(it->m_name.c_str()), QString("Hz")); + DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(rangeGUI, QString(it->m_name.c_str())); + m_tunableElementsGUIs.push_back(gui); + connect(m_tunableElementsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(tunableElementChanged(QString, double))); + } +} + void SoapySDRInputGui::setName(const QString& name) { setObjectName(name); @@ -278,6 +299,11 @@ void SoapySDRInputGui::bandwidthChanged(double bandwidth) sendSettings(); } +void SoapySDRInputGui::tunableElementChanged(QString name, double value) +{ + qDebug("SoapySDRInputGui::tunableElementChanged: name: %s value: %lf", name.toStdString().c_str(), value); +} + void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 7a1ab29d4..deaee34a5 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -17,18 +17,19 @@ #ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ #define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ -#include #include #include #include #include "plugin/plugininstancegui.h" #include "util/messagequeue.h" + #include "soapysdrinput.h" class DeviceUISet; class ItemSettingGUI; class StringRangeGUI; +class DynamicItemSettingGUI; namespace Ui { class SoapySDRInputGui; @@ -60,6 +61,7 @@ private: const QString& text, const QString& unit); void createAntennasControl(const std::vector& antennaList); + void createTunableElementsControl(const std::vector& tunableElementsList); Ui::SoapySDRInputGui* ui; @@ -78,6 +80,7 @@ private: StringRangeGUI *m_antennas; ItemSettingGUI *m_sampleRateGUI; ItemSettingGUI *m_bandwidthGUI; + std::vector m_tunableElementsGUIs; void displaySettings(); void sendSettings(); @@ -90,6 +93,7 @@ private slots: void handleInputMessages(); void sampleRateChanged(double sampleRate); void antennasChanged(); + void tunableElementChanged(QString name, double value); void bandwidthChanged(double bandwidth); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp index 9489947b9..46f999cc4 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "util/simpleserializer.h" #include "soapysdrinputsettings.h" @@ -53,6 +55,7 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeS64(8, m_transverterDeltaFrequency); s.writeString(9, m_antenna); s.writeU32(10, m_bandwidth); + s.writeBlob(11, serializeNamedElementMap(m_tunableElements)); return s.final(); } @@ -70,6 +73,7 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { int intval; + QByteArray blob; d.readS32(1, &m_devSampleRate, 1024000); d.readU32(2, &m_log2Decim); @@ -82,6 +86,8 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) d.readS64(8, &m_transverterDeltaFrequency, 0); d.readString(9, &m_antenna, "NONE"); d.readU32(10, &m_bandwidth, 1000000); + d.readBlob(11, &blob); + deserializeNamedElementMap(blob, m_tunableElements); return true; } @@ -91,3 +97,20 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) return false; } } + +QByteArray SoapySDRInputSettings::serializeNamedElementMap(const QMap& map) const +{ + QByteArray data; + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + (*stream) << map; + delete stream; + + return data; +} + +void SoapySDRInputSettings::deserializeNamedElementMap(const QByteArray& data, QMap& map) +{ + QDataStream *stream = new QDataStream(data); + (*stream) >> map; + delete stream; +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h index 62a942814..a74f0bc92 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -19,6 +19,7 @@ #include #include +#include struct SoapySDRInputSettings { typedef enum { @@ -39,11 +40,16 @@ struct SoapySDRInputSettings { QString m_fileRecordName; QString m_antenna; quint32 m_bandwidth; + QMap m_tunableElements; SoapySDRInputSettings(); void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); + +private: + QByteArray serializeNamedElementMap(const QMap& map) const; + void deserializeNamedElementMap(const QByteArray& data, QMap& map); }; #endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTSETTINGS_H_ */ diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 709164adf..cede779da 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -45,20 +45,21 @@ set(sdrgui_SOURCES gui/tvscreen.cpp gui/valuedial.cpp gui/valuedialz.cpp - + dsp/scopevis.cpp dsp/scopevismulti.cpp dsp/scopevisxy.cpp dsp/spectrumvis.cpp dsp/spectrumscopecombovis.cpp - + device/deviceuiset.cpp - + soapygui/discreterangegui.cpp soapygui/intervalrangegui.cpp soapygui/itemsettinggui.cpp soapygui/stringrangegui.cpp - + soapygui/dynamicitemsettinggui.cpp + webapi/webapiadaptergui.cpp ) @@ -92,7 +93,7 @@ set(sdrgui_HEADERS gui/glspectrumgui.h gui/indicator.h gui/levelmeter.h - gui/loggingdialog.h + gui/loggingdialog.h gui/mypositiondialog.h gui/physicalunit.h gui/pluginsdialog.h @@ -104,23 +105,24 @@ set(sdrgui_HEADERS gui/tickedslider.h gui/transverterbutton.h gui/transverterdialog.h - gui/tvscreen.h + gui/tvscreen.h gui/valuedial.h gui/valuedialz.h - + dsp/scopevis.h dsp/scopevismulti.h - dsp/scopevisxy.h + dsp/scopevisxy.h dsp/spectrumvis.h dsp/spectrumscopecombovis.h - + device/deviceuiset.h - + soapygui/discreterangegui.h soapygui/intervalrangegui.h soapygui/itemsettinggui.h soapygui/stringrangegui.h - + soapygui/dynamicitemsettinggui.h + webapi/webapiadaptergui.h ) @@ -180,7 +182,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_SOURCE_DIR}/logging ${CMAKE_SOURCE_DIR}/httpserver - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_CURRENT_BINARY_DIR} ${OPENGL_INCLUDE_DIR} ) diff --git a/sdrgui/soapygui/dynamicitemsettinggui.cpp b/sdrgui/soapygui/dynamicitemsettinggui.cpp new file mode 100644 index 000000000..d457c701c --- /dev/null +++ b/sdrgui/soapygui/dynamicitemsettinggui.cpp @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "dynamicitemsettinggui.h" + +DynamicItemSettingGUI::DynamicItemSettingGUI(ItemSettingGUI *itemSettingGUI, const QString& name, QObject *parent) : + QObject(parent), + m_itemSettingGUI(itemSettingGUI), + m_name(name) +{ + connect(m_itemSettingGUI, SIGNAL(valueChanged(double)), this, SLOT(processValueChanged(double))); +} + +DynamicItemSettingGUI::~DynamicItemSettingGUI() +{ + disconnect(m_itemSettingGUI, SIGNAL(valueChanged(double)), this, SLOT(processValueChanged(double))); +} + +void DynamicItemSettingGUI::processValueChanged(double value) { + emit valueChanged(m_name, value); +} diff --git a/sdrgui/soapygui/dynamicitemsettinggui.h b/sdrgui/soapygui/dynamicitemsettinggui.h new file mode 100644 index 000000000..2a197ba44 --- /dev/null +++ b/sdrgui/soapygui/dynamicitemsettinggui.h @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "itemsettinggui.h" + +class DynamicItemSettingGUI : public QObject +{ + Q_OBJECT +public: + DynamicItemSettingGUI(ItemSettingGUI *itemSettingGUI, const QString& name, QObject *parent = 0); + ~DynamicItemSettingGUI(); + + const QString& getName() const { return m_name; } + double getValue() const { return m_itemSettingGUI->getCurrentValue(); } + +signals: + void valueChanged(QString itemName, double value); + +private slots: + void processValueChanged(double value); + +private: + ItemSettingGUI *m_itemSettingGUI; + QString m_name; +}; \ No newline at end of file From 6ea676d5c4fa3c7a7c13ddbaa119b9cdf77354f4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 6 Nov 2018 08:32:47 +0100 Subject: [PATCH 933/956] SoapySDR support: tunable elements GUIs fixes --- .../soapysdroutput/soapysdroutputgui.cpp | 16 +++++++-- .../soapysdroutput/soapysdroutputgui.h | 2 +- .../soapysdrinput/soapysdrinput.cpp | 25 +++++++++++++ .../soapysdrinput/soapysdrinputgui.cpp | 36 ++++++++++++++++--- .../soapysdrinput/soapysdrinputgui.h | 1 + sdrgui/soapygui/dynamicitemsettinggui.h | 3 +- sdrgui/soapygui/intervalrangegui.cpp | 6 ++-- sdrgui/soapygui/intervalrangegui.h | 2 +- sdrgui/soapygui/intervalrangegui.ui | 14 ++++---- 9 files changed, 86 insertions(+), 19 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index 42bf83b1f..f57816906 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -37,7 +37,10 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) m_doApplySettings(true), m_sampleSink(0), m_sampleRate(0), - m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), + m_antennas(0), + m_sampleRateGUI(0), + m_bandwidthGUI(0) { m_sampleSink = (SoapySDROutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); ui->setupUi(this); @@ -322,7 +325,16 @@ void SoapySDROutputGui::displaySettings() blockApplySettings(true); ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); - m_sampleRateGUI->setValue(m_settings.m_devSampleRate); + + if (m_antennas) { + m_antennas->setValue(m_settings.m_antenna.toStdString()); + } + if (m_sampleRateGUI) { + m_sampleRateGUI->setValue(m_settings.m_devSampleRate); + } + if (m_bandwidthGUI) { + m_bandwidthGUI->setValue(m_settings.m_bandwidth); + } ui->interp->setCurrentIndex(m_settings.m_log2Interp); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index f75108544..fe460e29e 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -76,8 +76,8 @@ private: int m_lastEngineState; MessageQueue m_inputMessageQueue; - ItemSettingGUI *m_sampleRateGUI; StringRangeGUI *m_antennas; + ItemSettingGUI *m_sampleRateGUI; ItemSettingGUI *m_bandwidthGUI; void blockApplySettings(bool block) { m_doApplySettings = !block; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 5d505c8b8..f03411a76 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -812,6 +812,31 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo } } + for (const auto &oname : m_settings.m_tunableElements.keys()) + { + auto nvalue = settings.m_tunableElements.find(oname); + + if (nvalue != settings.m_tunableElements.end() && (m_settings.m_tunableElements[oname] != *nvalue)) + { + if (dev != 0) + { + try + { + dev->setFrequency(SOAPY_SDR_RX, requestedChannel, oname.toStdString(), *nvalue); + qDebug("SoapySDRInput::applySettings: tunable element %s frequency set to %lf", + oname.toStdString().c_str(), *nvalue); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set tunable element %s to %lf: %s", + oname.toStdString().c_str(), *nvalue, ex.what()); + } + } + + m_settings.m_tunableElements[oname] = *nvalue; + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<m_ranges, QString("%1 freq").arg(it->m_name.c_str()), QString("Hz")); + createRangesControl( + &rangeGUI, + it->m_ranges, + QString("%1 freq").arg(it->m_name.c_str()), + QString((it->m_name == "CORR") ? "ppm" : "Hz")); DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(rangeGUI, QString(it->m_name.c_str())); m_tunableElementsGUIs.push_back(gui); connect(m_tunableElementsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(tunableElementChanged(QString, double))); @@ -302,6 +307,8 @@ void SoapySDRInputGui::bandwidthChanged(double bandwidth) void SoapySDRInputGui::tunableElementChanged(QString name, double value) { qDebug("SoapySDRInputGui::tunableElementChanged: name: %s value: %lf", name.toStdString().c_str(), value); + m_settings.m_tunableElements[name] = value; + sendSettings(); } void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) @@ -387,9 +394,16 @@ void SoapySDRInputGui::displaySettings() blockApplySettings(true); ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); - m_antennas->setValue(m_settings.m_antenna.toStdString()); - m_sampleRateGUI->setValue(m_settings.m_devSampleRate); - m_bandwidthGUI->setValue(m_settings.m_bandwidth); + + if (m_antennas) { + m_antennas->setValue(m_settings.m_antenna.toStdString()); + } + if (m_sampleRateGUI) { + m_sampleRateGUI->setValue(m_settings.m_devSampleRate); + } + if (m_bandwidthGUI) { + m_bandwidthGUI->setValue(m_settings.m_bandwidth); + } ui->dcOffset->setChecked(m_settings.m_dcBlock); ui->iqImbalance->setChecked(m_settings.m_iqCorrection); @@ -400,9 +414,23 @@ void SoapySDRInputGui::displaySettings() ui->LOppm->setValue(m_settings.m_LOppmTenths); ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + displayTunableElementsControlSettings(); + blockApplySettings(false); } +void SoapySDRInputGui::displayTunableElementsControlSettings() +{ + for (const auto &it : m_tunableElementsGUIs) + { + QMap::const_iterator elIt = m_settings.m_tunableElements.find(it->getName()); + + if (elIt != m_settings.m_tunableElements.end()) { + it->setValue(*elIt); + } + } +} + void SoapySDRInputGui::sendSettings() { if (!m_updateTimer.isActive()) { diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index deaee34a5..b965b7517 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -83,6 +83,7 @@ private: std::vector m_tunableElementsGUIs; void displaySettings(); + void displayTunableElementsControlSettings(); void sendSettings(); void updateSampleRateAndFrequency(); void updateFrequencyLimits(); diff --git a/sdrgui/soapygui/dynamicitemsettinggui.h b/sdrgui/soapygui/dynamicitemsettinggui.h index 2a197ba44..f90a97cac 100644 --- a/sdrgui/soapygui/dynamicitemsettinggui.h +++ b/sdrgui/soapygui/dynamicitemsettinggui.h @@ -28,6 +28,7 @@ public: const QString& getName() const { return m_name; } double getValue() const { return m_itemSettingGUI->getCurrentValue(); } + void setValue(double value) { m_itemSettingGUI->setValue(value); } signals: void valueChanged(QString itemName, double value); @@ -38,4 +39,4 @@ private slots: private: ItemSettingGUI *m_itemSettingGUI; QString m_name; -}; \ No newline at end of file +}; diff --git a/sdrgui/soapygui/intervalrangegui.cpp b/sdrgui/soapygui/intervalrangegui.cpp index 346227e9b..ca1db22f2 100644 --- a/sdrgui/soapygui/intervalrangegui.cpp +++ b/sdrgui/soapygui/intervalrangegui.cpp @@ -70,7 +70,7 @@ void IntervalRangeGUI::reset() ui->rangeInterval->blockSignals(true); ui->rangeInterval->setCurrentIndex(0); ui->rangeInterval->blockSignals(false); - ui->value->setValueRange(m_nbDigits, m_minima[0], m_maxima[0]); + ui->value->setValueRange(m_minima[0] >= 0, m_nbDigits, m_minima[0], m_maxima[0]); } if (m_minima.size() == 1) { @@ -88,14 +88,14 @@ void IntervalRangeGUI::setValue(double value) ui->value->setValue(value); } -void IntervalRangeGUI::on_value_changed(quint64 value) +void IntervalRangeGUI::on_value_changed(qint64 value) { emit ItemSettingGUI::valueChanged(value); } void IntervalRangeGUI::on_rangeInterval_currentIndexChanged(int index) { - ui->value->setValueRange(m_nbDigits, m_minima[index], m_maxima[index]); + ui->value->setValueRange(m_minima[index] >= 0, m_nbDigits, m_minima[index], m_maxima[index]); emit ItemSettingGUI::valueChanged(ui->value->getValueNew()); } diff --git a/sdrgui/soapygui/intervalrangegui.h b/sdrgui/soapygui/intervalrangegui.h index 999452124..1e3b00986 100644 --- a/sdrgui/soapygui/intervalrangegui.h +++ b/sdrgui/soapygui/intervalrangegui.h @@ -42,7 +42,7 @@ public: virtual void setValue(double value); private slots: - void on_value_changed(quint64 value); + void on_value_changed(qint64 value); void on_rangeInterval_currentIndexChanged(int index); private: diff --git a/sdrgui/soapygui/intervalrangegui.ui b/sdrgui/soapygui/intervalrangegui.ui index 7bf944db1..290dd577a 100644 --- a/sdrgui/soapygui/intervalrangegui.ui +++ b/sdrgui/soapygui/intervalrangegui.ui @@ -6,8 +6,8 @@ 0 0 - 263 - 30 + 295 + 33 @@ -30,7 +30,7 @@ 0 0 - 262 + 292 29 @@ -43,7 +43,7 @@ - + 0 @@ -52,7 +52,7 @@ - 120 + 150 16 @@ -92,9 +92,9 @@ - ValueDial + ValueDialZ QWidget -
    gui/valuedial.h
    +
    gui/valuedialz.h
    1
    From e17828c7ee4cac8017c33e186e5e13221e3e8daf Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 6 Nov 2018 20:19:20 +0100 Subject: [PATCH 934/956] SoapySDR support: output: tunable elements GUIs --- .../soapysdroutput/soapysdroutput.cpp | 25 +++++++++++ .../soapysdroutput/soapysdroutputgui.cpp | 42 +++++++++++++++++++ .../soapysdroutput/soapysdroutputgui.h | 7 +++- .../soapysdroutput/soapysdroutputsettings.cpp | 24 +++++++++++ .../soapysdroutput/soapysdroutputsettings.h | 6 +++ .../soapysdrinput/soapysdrinputgui.cpp | 1 - .../soapysdrinput/soapysdrinputgui.h | 4 +- 7 files changed, 105 insertions(+), 4 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 997520360..45496b3a2 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -766,6 +766,31 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool } } + for (const auto &oname : m_settings.m_tunableElements.keys()) + { + auto nvalue = settings.m_tunableElements.find(oname); + + if (nvalue != settings.m_tunableElements.end() && (m_settings.m_tunableElements[oname] != *nvalue)) + { + if (dev != 0) + { + try + { + dev->setFrequency(SOAPY_SDR_TX, requestedChannel, oname.toStdString(), *nvalue); + qDebug("SoapySDROutput::applySettings: tunable element %s frequency set to %lf", + oname.toStdString().c_str(), *nvalue); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set tunable element %s to %lf: %s", + oname.toStdString().c_str(), *nvalue, ex.what()); + } + } + + m_settings.m_tunableElements[oname] = *nvalue; + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& an connect(m_antennas, SIGNAL(valueChanged()), this, SLOT(antennasChanged())); } +void SoapySDROutputGui::createTunableElementsControl(const std::vector& tunableElementsList) +{ + if (tunableElementsList.size() <= 1) { // This list is created for other elements than the main one (RF) which is always at index 0 + return; + } + + std::vector::const_iterator it = tunableElementsList.begin() + 1; + + for (int i = 0; it != tunableElementsList.end(); ++it, i++) + { + ItemSettingGUI *rangeGUI; + createRangesControl( + &rangeGUI, + it->m_ranges, + QString("%1 freq").arg(it->m_name.c_str()), + QString((it->m_name == "CORR") ? "ppm" : "Hz")); + DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(rangeGUI, QString(it->m_name.c_str())); + m_tunableElementsGUIs.push_back(gui); + connect(m_tunableElementsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(tunableElementChanged(QString, double))); + } +} + void SoapySDROutputGui::setName(const QString& name) { setObjectName(name); @@ -279,6 +302,11 @@ void SoapySDROutputGui::bandwidthChanged(double bandwidth) sendSettings(); } +void SoapySDROutputGui::tunableElementChanged(QString name, double value) +{ + m_settings.m_tunableElements[name] = value; + sendSettings(); +} void SoapySDROutputGui::on_centerFrequency_changed(quint64 value) { @@ -341,9 +369,23 @@ void SoapySDROutputGui::displaySettings() ui->LOppm->setValue(m_settings.m_LOppmTenths); ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + displayTunableElementsControlSettings(); + blockApplySettings(false); } +void SoapySDROutputGui::displayTunableElementsControlSettings() +{ + for (const auto &it : m_tunableElementsGUIs) + { + QMap::const_iterator elIt = m_settings.m_tunableElements.find(it->getName()); + + if (elIt != m_settings.m_tunableElements.end()) { + it->setValue(*elIt); + } + } +} + void SoapySDROutputGui::sendSettings() { if (!m_updateTimer.isActive()) { diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index fe460e29e..4123231a5 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -30,6 +30,7 @@ class DeviceSampleSink; class DeviceUISet; class ItemSettingGUI; class StringRangeGUI; +class DynamicItemSettingGUI; namespace Ui { class SoapySDROutputGui; @@ -61,6 +62,7 @@ private: const QString& text, const QString& unit); void createAntennasControl(const std::vector& antennaList); + void createTunableElementsControl(const std::vector& tunableElementsList); Ui::SoapySDROutputGui* ui; @@ -79,9 +81,11 @@ private: StringRangeGUI *m_antennas; ItemSettingGUI *m_sampleRateGUI; ItemSettingGUI *m_bandwidthGUI; + std::vector m_tunableElementsGUIs; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); + void displayTunableElementsControlSettings(); void sendSettings(); void updateSampleRateAndFrequency(); void updateFrequencyLimits(); @@ -89,9 +93,10 @@ private: private slots: void handleInputMessages(); - void sampleRateChanged(double sampleRate); void antennasChanged(); + void sampleRateChanged(double sampleRate); void bandwidthChanged(double bandwidth); + void tunableElementChanged(QString name, double value); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void on_interp_currentIndexChanged(int index); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp index 2efd42e74..7e5dd37f7 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -15,6 +15,8 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include + #include "util/simpleserializer.h" #include "soapysdroutputsettings.h" @@ -47,6 +49,7 @@ QByteArray SoapySDROutputSettings::serialize() const s.writeS64(5, m_transverterDeltaFrequency); s.writeString(6, m_antenna); s.writeU32(7, m_bandwidth); + s.writeBlob(8, serializeNamedElementMap(m_tunableElements)); return s.final(); } @@ -63,6 +66,8 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { + QByteArray blob; + d.readS32(1, &m_devSampleRate); d.readS32(2, &m_LOppmTenths); d.readU32(3, &m_log2Interp); @@ -70,6 +75,8 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) d.readS64(5, &m_transverterDeltaFrequency, 0); d.readString(6, &m_antenna, "NONE"); d.readU32(7, &m_bandwidth, 1000000); + d.readBlob(8, &blob); + deserializeNamedElementMap(blob, m_tunableElements); return true; } @@ -79,3 +86,20 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) return false; } } + +QByteArray SoapySDROutputSettings::serializeNamedElementMap(const QMap& map) const +{ + QByteArray data; + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + (*stream) << map; + delete stream; + + return data; +} + +void SoapySDROutputSettings::deserializeNamedElementMap(const QByteArray& data, QMap& map) +{ + QDataStream *stream = new QDataStream(data); + (*stream) >> map; + delete stream; +} \ No newline at end of file diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h index 9e368bc45..21b9ade24 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -18,6 +18,7 @@ #define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTSETTINGS_H_ #include +#include struct SoapySDROutputSettings { quint64 m_centerFrequency; @@ -28,11 +29,16 @@ struct SoapySDROutputSettings { qint64 m_transverterDeltaFrequency; QString m_antenna; quint32 m_bandwidth; + QMap m_tunableElements; SoapySDROutputSettings(); void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); + +private: + QByteArray serializeNamedElementMap(const QMap& map) const; + void deserializeNamedElementMap(const QByteArray& data, QMap& map); }; #endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTSETTINGS_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 7411a78c5..a3649468f 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -306,7 +306,6 @@ void SoapySDRInputGui::bandwidthChanged(double bandwidth) void SoapySDRInputGui::tunableElementChanged(QString name, double value) { - qDebug("SoapySDRInputGui::tunableElementChanged: name: %s value: %lf", name.toStdString().c_str(), value); m_settings.m_tunableElements[name] = value; sendSettings(); } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index b965b7517..0a0233dc8 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -92,10 +92,10 @@ private: private slots: void handleInputMessages(); - void sampleRateChanged(double sampleRate); void antennasChanged(); - void tunableElementChanged(QString name, double value); + void sampleRateChanged(double sampleRate); void bandwidthChanged(double bandwidth); + void tunableElementChanged(QString name, double value); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void on_dcOffset_toggled(bool checked); From 5c8073bade6720de124c3d70220ab6408a31bf29 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 6 Nov 2018 22:41:10 +0100 Subject: [PATCH 935/956] SoapySDR support: output: tunable elements GUIs fixes --- plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index cec15e0a6..5ada82fc1 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -54,6 +54,7 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) createAntennasControl(m_sampleSink->getAntennas()); createRangesControl(&m_sampleRateGUI, m_sampleSink->getRateRanges(), "SR", "S/s"); createRangesControl(&m_bandwidthGUI, m_sampleSink->getBandwidthRanges(), "BW", "Hz"); + createTunableElementsControl(m_sampleSink->getTunableElements()); if (m_sampleRateGUI) { connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); From d7be0927b1002bdec8ce44784de5b226e7429339 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 01:33:17 +0100 Subject: [PATCH 936/956] SoapySDR support: input: global gain GUI --- .../soapysdrinput/soapysdrinput.cpp | 44 +++++++++- .../soapysdrinput/soapysdrinput.h | 2 + .../soapysdrinput/soapysdrinputgui.cpp | 29 ++++++- .../soapysdrinput/soapysdrinputgui.h | 4 + .../soapysdrinput/soapysdrinputsettings.cpp | 3 + .../soapysdrinput/soapysdrinputsettings.h | 1 + sdrgui/CMakeLists.txt | 3 + sdrgui/soapygui/intervalslidergui.cpp | 71 ++++++++++++++++ sdrgui/soapygui/intervalslidergui.h | 52 ++++++++++++ sdrgui/soapygui/intervalslidergui.ui | 83 +++++++++++++++++++ 10 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 sdrgui/soapygui/intervalslidergui.cpp create mode 100644 sdrgui/soapygui/intervalslidergui.h create mode 100644 sdrgui/soapygui/intervalslidergui.ui diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index f03411a76..e29b0582b 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -184,7 +184,7 @@ void SoapySDRInput::getFrequencyRange(uint64_t& min, uint64_t& max) DeviceSoapySDRParams::FrequencySetting freqSettings = channelSettings->m_frequencySettings[0]; SoapySDR::RangeList rangeList = freqSettings.m_ranges; - if (rangeList.size() > 0) // TODO: handle multiple ranges + if (rangeList.size() > 0) { SoapySDR::Range range = rangeList[0]; min = range.minimum(); @@ -203,6 +203,22 @@ void SoapySDRInput::getFrequencyRange(uint64_t& min, uint64_t& max) } } +void SoapySDRInput::getGlobalGainRange(int& min, int& max) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + + if (channelSettings) + { + min = channelSettings->m_gainRange.minimum(); + max = channelSettings->m_gainRange.maximum(); + } + else + { + min = 0; + max = 0; + } +} + const std::vector& SoapySDRInput::getAntennas() { const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); @@ -239,6 +255,12 @@ const std::vector& SoapySDRInput::getTun return channelSettings->m_frequencySettings; } +const std::vector& SoapySDRInput::getIndividualGainsRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_gainSettings; +} + void SoapySDRInput::init() { applySettings(m_settings, true); @@ -837,6 +859,23 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo } } + if ((m_settings.m_globalGain != settings.m_globalGain) || force) + { + if (dev != 0) + { + try + { + dev->setGain(SOAPY_SDR_RX, requestedChannel, settings.m_globalGain); + qDebug("SoapySDRInput::applySettings: set gain to %d", settings.m_globalGain); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set gain to %d: %s", + settings.m_globalGain, ex.what()); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& getAntennas(); const SoapySDR::RangeList& getRateRanges(); const SoapySDR::RangeList& getBandwidthRanges(); int getAntennaIndex(const std::string& antenna); const std::vector& getTunableElements(); + const std::vector& getIndividualGainsRanges(); private: DeviceSourceAPI *m_deviceAPI; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index a3649468f..ba05dcadf 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -26,6 +26,7 @@ #include "soapygui/intervalrangegui.h" #include "soapygui/stringrangegui.h" #include "soapygui/dynamicitemsettinggui.h" +#include "soapygui/intervalslidergui.h" #include "ui_soapysdrinputgui.h" #include "soapysdrinputgui.h" @@ -42,7 +43,8 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_antennas(0), m_sampleRateGUI(0), - m_bandwidthGUI(0) + m_bandwidthGUI(0), + m_gainSliderGUI(0) { m_sampleSource = (SoapySDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); ui->setupUi(this); @@ -56,6 +58,7 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : createRangesControl(&m_sampleRateGUI, m_sampleSource->getRateRanges(), "SR", "S/s"); createRangesControl(&m_bandwidthGUI, m_sampleSource->getBandwidthRanges(), "BW", "Hz"); createTunableElementsControl(m_sampleSource->getTunableElements()); + createGlobalGainControl(); if (m_sampleRateGUI) { connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); @@ -182,6 +185,21 @@ void SoapySDRInputGui::createTunableElementsControl(const std::vectorgetGlobalGainRange(min, max); + m_gainSliderGUI->setInterval(min, max); + m_gainSliderGUI->setLabel(QString("Global gain")); + m_gainSliderGUI->setUnits(QString("")); + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(m_gainSliderGUI); + + connect(m_gainSliderGUI, SIGNAL(valueChanged(double)), this, SLOT(globalGainChanged(double))); +} + void SoapySDRInputGui::setName(const QString& name) { setObjectName(name); @@ -310,6 +328,12 @@ void SoapySDRInputGui::tunableElementChanged(QString name, double value) sendSettings(); } +void SoapySDRInputGui::globalGainChanged(double gain) +{ + m_settings.m_globalGain = round(gain); + sendSettings(); +} + void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; @@ -403,6 +427,9 @@ void SoapySDRInputGui::displaySettings() if (m_bandwidthGUI) { m_bandwidthGUI->setValue(m_settings.m_bandwidth); } + if (m_gainSliderGUI) { + m_gainSliderGUI->setValue(m_settings.m_globalGain); + } ui->dcOffset->setChecked(m_settings.m_dcBlock); ui->iqImbalance->setChecked(m_settings.m_iqCorrection); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 0a0233dc8..8678ef617 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -30,6 +30,7 @@ class DeviceUISet; class ItemSettingGUI; class StringRangeGUI; class DynamicItemSettingGUI; +class IntervalSliderGUI; namespace Ui { class SoapySDRInputGui; @@ -62,6 +63,7 @@ private: const QString& unit); void createAntennasControl(const std::vector& antennaList); void createTunableElementsControl(const std::vector& tunableElementsList); + void createGlobalGainControl(); Ui::SoapySDRInputGui* ui; @@ -81,6 +83,7 @@ private: ItemSettingGUI *m_sampleRateGUI; ItemSettingGUI *m_bandwidthGUI; std::vector m_tunableElementsGUIs; + IntervalSliderGUI *m_gainSliderGUI; void displaySettings(); void displayTunableElementsControlSettings(); @@ -96,6 +99,7 @@ private slots: void sampleRateChanged(double sampleRate); void bandwidthChanged(double bandwidth); void tunableElementChanged(QString name, double value); + void globalGainChanged(double gain); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void on_dcOffset_toggled(bool checked); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp index 46f999cc4..2fc6d4232 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -39,6 +39,7 @@ void SoapySDRInputSettings::resetToDefaults() m_fileRecordName = ""; m_antenna = "NONE"; m_bandwidth = 1000000; + m_globalGain = 0; } QByteArray SoapySDRInputSettings::serialize() const @@ -56,6 +57,7 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeString(9, m_antenna); s.writeU32(10, m_bandwidth); s.writeBlob(11, serializeNamedElementMap(m_tunableElements)); + s.writeS32(12, m_globalGain); return s.final(); } @@ -88,6 +90,7 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) d.readU32(10, &m_bandwidth, 1000000); d.readBlob(11, &blob); deserializeNamedElementMap(blob, m_tunableElements); + d.readS32(12, &m_globalGain, 0); return true; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h index a74f0bc92..a01242f0c 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -41,6 +41,7 @@ struct SoapySDRInputSettings { QString m_antenna; quint32 m_bandwidth; QMap m_tunableElements; + qint32 m_globalGain; SoapySDRInputSettings(); void resetToDefaults(); diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index cede779da..2914b29b5 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -59,6 +59,7 @@ set(sdrgui_SOURCES soapygui/itemsettinggui.cpp soapygui/stringrangegui.cpp soapygui/dynamicitemsettinggui.cpp + soapygui/intervalslidergui.cpp webapi/webapiadaptergui.cpp ) @@ -122,6 +123,7 @@ set(sdrgui_HEADERS soapygui/itemsettinggui.h soapygui/stringrangegui.h soapygui/dynamicitemsettinggui.h + soapygui/intervalslidergui.h webapi/webapiadaptergui.h ) @@ -153,6 +155,7 @@ set(sdrgui_FORMS gui/loggingdialog.ui soapygui/discreterangegui.ui soapygui/intervalrangegui.ui + soapygui/intervalslidergui.ui ) set(sdrgui_RESOURCES diff --git a/sdrgui/soapygui/intervalslidergui.cpp b/sdrgui/soapygui/intervalslidergui.cpp new file mode 100644 index 000000000..12e71f476 --- /dev/null +++ b/sdrgui/soapygui/intervalslidergui.cpp @@ -0,0 +1,71 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "ui_intervalslidergui.h" +#include "intervalslidergui.h" + +IntervalSliderGUI::IntervalSliderGUI(QWidget* parent) : + QWidget(parent), + ui(new Ui::IntervalSliderGUI), + m_minimum(0), + m_maximum(0) +{ + ui->setupUi(this); +} + +IntervalSliderGUI::~IntervalSliderGUI() +{ + delete ui; +} + +void IntervalSliderGUI::setLabel(const QString& text) +{ + ui->intervalLabel->setText(text); +} + +void IntervalSliderGUI::setUnits(const QString& units) +{ + ui->intervalUnits->setText(units); +} + +void IntervalSliderGUI::setInterval(double minimum, double maximum) +{ + ui->intervalSlider->blockSignals(true); + ui->intervalSlider->setMinimum(minimum); + ui->intervalSlider->setMaximum(maximum); + ui->intervalSlider->blockSignals(false); + m_minimum = minimum; + m_maximum = maximum; +} + +double IntervalSliderGUI::getCurrentValue() +{ + return ui->intervalSlider->value(); +} + +void IntervalSliderGUI::setValue(double value) +{ + ui->intervalSlider->setValue(value); + ui->valueText->setText(QString("%1").arg(ui->intervalSlider->value())); +} + +void IntervalSliderGUI::on_intervalSlider_valueChanged(int value) +{ + ui->valueText->setText(QString("%1").arg(value)); + emit valueChanged(value); +} diff --git a/sdrgui/soapygui/intervalslidergui.h b/sdrgui/soapygui/intervalslidergui.h new file mode 100644 index 000000000..aff65f0c9 --- /dev/null +++ b/sdrgui/soapygui/intervalslidergui.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 SDRGUI_SOAPYGUI_INTERVALSLIDERGUI_H_ +#define SDRGUI_SOAPYGUI_INTERVALSLIDERGUI_H_ + +#include +#include + +namespace Ui { + class IntervalSliderGUI; +} + +class IntervalSliderGUI : public QWidget +{ + Q_OBJECT +public: + explicit IntervalSliderGUI(QWidget* parent = 0); + virtual ~IntervalSliderGUI(); + + void setLabel(const QString& text); + void setUnits(const QString& units); + void setInterval(double minimum, double maximum); + virtual double getCurrentValue(); + virtual void setValue(double value); + +signals: + void valueChanged(double value); + +private slots: + void on_intervalSlider_valueChanged(int value); + +private: + Ui::IntervalSliderGUI* ui; + double m_minimum; + double m_maximum; +}; + +#endif /* SDRGUI_SOAPYGUI_INTERVALSLIDERGUI_H_ */ diff --git a/sdrgui/soapygui/intervalslidergui.ui b/sdrgui/soapygui/intervalslidergui.ui new file mode 100644 index 000000000..ed34216e1 --- /dev/null +++ b/sdrgui/soapygui/intervalslidergui.ui @@ -0,0 +1,83 @@ + + + IntervalSliderGUI + + + + 0 + 0 + 203 + 30 + + + + + 0 + 0 + + + + + 0 + 30 + + + + Form + + + + + 0 + 0 + 179 + 29 + + + + + + + Label + + + + + + + 1 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Unit + + + + + + + + + From d4e73086d0f27e23b742c16937bd0d0bebb956d1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 13:08:35 +0100 Subject: [PATCH 937/956] SoapySDR support: output: global gain GUI --- .../soapysdroutput/soapysdroutput.cpp | 45 ++++++++++++++++++- .../soapysdroutput/soapysdroutput.h | 2 + .../soapysdroutput/soapysdroutputgui.cpp | 29 +++++++++++- .../soapysdroutput/soapysdroutputgui.h | 4 ++ .../soapysdroutput/soapysdroutputsettings.cpp | 3 ++ .../soapysdroutput/soapysdroutputsettings.h | 1 + .../soapysdrinput/soapysdrinput.cpp | 4 +- 7 files changed, 83 insertions(+), 5 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 45496b3a2..894c7795f 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -164,7 +164,7 @@ void SoapySDROutput::getFrequencyRange(uint64_t& min, uint64_t& max) DeviceSoapySDRParams::FrequencySetting freqSettings = channelSettings->m_frequencySettings[0]; SoapySDR::RangeList rangeList = freqSettings.m_ranges; - if (rangeList.size() > 0) // TODO: handle multiple ranges + if (rangeList.size() > 0) { SoapySDR::Range range = rangeList[0]; min = range.minimum(); @@ -183,6 +183,23 @@ void SoapySDROutput::getFrequencyRange(uint64_t& min, uint64_t& max) } } +void SoapySDROutput::getGlobalGainRange(int& min, int& max) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + + if (channelSettings) + { + min = channelSettings->m_gainRange.minimum(); + max = channelSettings->m_gainRange.maximum(); + } + else + { + min = 0; + max = 0; + } +} + + const SoapySDR::RangeList& SoapySDROutput::getRateRanges() { const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); @@ -207,6 +224,12 @@ const std::vector& SoapySDROutput::getTu return channelSettings->m_frequencySettings; } +const std::vector& SoapySDROutput::getIndividualGainsRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_gainSettings; +} + void SoapySDROutput::init() { applySettings(m_settings, true); @@ -791,6 +814,23 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool } } + if ((m_settings.m_globalGain != settings.m_globalGain) || force) + { + if (dev != 0) + { + try + { + dev->setGain(SOAPY_SDR_TX, requestedChannel, settings.m_globalGain); + qDebug("SoapySDROutput::applySettings: set global gain to %d", settings.m_globalGain); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set global gain to %d: %s", + settings.m_globalGain, ex.what()); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& getAntennas(); const SoapySDR::RangeList& getBandwidthRanges(); const std::vector& getTunableElements(); + const std::vector& getIndividualGainsRanges(); private: DeviceSinkAPI *m_deviceAPI; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index 5ada82fc1..5afe5d3c9 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -27,6 +27,7 @@ #include "soapygui/intervalrangegui.h" #include "soapygui/stringrangegui.h" #include "soapygui/dynamicitemsettinggui.h" +#include "soapygui/intervalslidergui.h" #include "soapysdroutputgui.h" @@ -41,7 +42,8 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_antennas(0), m_sampleRateGUI(0), - m_bandwidthGUI(0) + m_bandwidthGUI(0), + m_gainSliderGUI(0) { m_sampleSink = (SoapySDROutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); ui->setupUi(this); @@ -55,6 +57,7 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) createRangesControl(&m_sampleRateGUI, m_sampleSink->getRateRanges(), "SR", "S/s"); createRangesControl(&m_bandwidthGUI, m_sampleSink->getBandwidthRanges(), "BW", "Hz"); createTunableElementsControl(m_sampleSink->getTunableElements()); + createGlobalGainControl(); if (m_sampleRateGUI) { connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); @@ -181,6 +184,21 @@ void SoapySDROutputGui::createTunableElementsControl(const std::vectorgetGlobalGainRange(min, max); + m_gainSliderGUI->setInterval(min, max); + m_gainSliderGUI->setLabel(QString("Global gain")); + m_gainSliderGUI->setUnits(QString("")); + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(m_gainSliderGUI); + + connect(m_gainSliderGUI, SIGNAL(valueChanged(double)), this, SLOT(globalGainChanged(double))); +} + void SoapySDROutputGui::setName(const QString& name) { setObjectName(name); @@ -309,6 +327,12 @@ void SoapySDROutputGui::tunableElementChanged(QString name, double value) sendSettings(); } +void SoapySDROutputGui::globalGainChanged(double gain) +{ + m_settings.m_globalGain = round(gain); + sendSettings(); +} + void SoapySDROutputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; @@ -364,6 +388,9 @@ void SoapySDROutputGui::displaySettings() if (m_bandwidthGUI) { m_bandwidthGUI->setValue(m_settings.m_bandwidth); } + if (m_gainSliderGUI) { + m_gainSliderGUI->setValue(m_settings.m_globalGain); + } ui->interp->setCurrentIndex(m_settings.m_log2Interp); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index 4123231a5..e6174d3f8 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -31,6 +31,7 @@ class DeviceUISet; class ItemSettingGUI; class StringRangeGUI; class DynamicItemSettingGUI; +class IntervalSliderGUI; namespace Ui { class SoapySDROutputGui; @@ -63,6 +64,7 @@ private: const QString& unit); void createAntennasControl(const std::vector& antennaList); void createTunableElementsControl(const std::vector& tunableElementsList); + void createGlobalGainControl(); Ui::SoapySDROutputGui* ui; @@ -82,6 +84,7 @@ private: ItemSettingGUI *m_sampleRateGUI; ItemSettingGUI *m_bandwidthGUI; std::vector m_tunableElementsGUIs; + IntervalSliderGUI *m_gainSliderGUI; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); @@ -97,6 +100,7 @@ private slots: void sampleRateChanged(double sampleRate); void bandwidthChanged(double bandwidth); void tunableElementChanged(QString name, double value); + void globalGainChanged(double gain); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void on_interp_currentIndexChanged(int index); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp index 7e5dd37f7..771513ef5 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -36,6 +36,7 @@ void SoapySDROutputSettings::resetToDefaults() m_transverterDeltaFrequency = 0; m_antenna = "NONE"; m_bandwidth = 1000000; + m_globalGain = 0; } QByteArray SoapySDROutputSettings::serialize() const @@ -50,6 +51,7 @@ QByteArray SoapySDROutputSettings::serialize() const s.writeString(6, m_antenna); s.writeU32(7, m_bandwidth); s.writeBlob(8, serializeNamedElementMap(m_tunableElements)); + s.writeS32(12, m_globalGain); return s.final(); } @@ -77,6 +79,7 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) d.readU32(7, &m_bandwidth, 1000000); d.readBlob(8, &blob); deserializeNamedElementMap(blob, m_tunableElements); + d.readS32(12, &m_globalGain, 0); return true; } diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h index 21b9ade24..9a565e8f4 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -30,6 +30,7 @@ struct SoapySDROutputSettings { QString m_antenna; quint32 m_bandwidth; QMap m_tunableElements; + qint32 m_globalGain; SoapySDROutputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index e29b0582b..c05451dc8 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -866,11 +866,11 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo try { dev->setGain(SOAPY_SDR_RX, requestedChannel, settings.m_globalGain); - qDebug("SoapySDRInput::applySettings: set gain to %d", settings.m_globalGain); + qDebug("SoapySDRInput::applySettings: set global gain to %d", settings.m_globalGain); } catch (const std::exception &ex) { - qCritical("SoapySDRInput::applySettings: cannot set gain to %d: %s", + qCritical("SoapySDRInput::applySettings: cannot set global gain to %d: %s", settings.m_globalGain, ex.what()); } } From cfeaca424e87160d1eebd9674b63ba279bcaca2d Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 13:38:42 +0100 Subject: [PATCH 938/956] SoapySDR support: input: individual gains GUIs --- .../soapysdrinput/soapysdrinput.cpp | 25 ++++++++++++ .../soapysdrinput/soapysdrinputgui.cpp | 39 +++++++++++++++++++ .../soapysdrinput/soapysdrinputgui.h | 4 ++ .../soapysdrinput/soapysdrinputsettings.cpp | 3 ++ .../soapysdrinput/soapysdrinputsettings.h | 1 + sdrgui/soapygui/intervalslidergui.cpp | 4 +- sdrgui/soapygui/intervalslidergui.h | 7 ++-- 7 files changed, 77 insertions(+), 6 deletions(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index c05451dc8..e78bf5fe7 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -876,6 +876,31 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo } } + for (const auto &oname : m_settings.m_individualGains.keys()) + { + auto nvalue = settings.m_individualGains.find(oname); + + if (nvalue != settings.m_individualGains.end() && (m_settings.m_individualGains[oname] != *nvalue)) + { + if (dev != 0) + { + try + { + dev->setGain(SOAPY_SDR_RX, requestedChannel, oname.toStdString(), *nvalue); + qDebug("SoapySDRInput::applySettings: individual gain %s set to %lf", + oname.toStdString().c_str(), *nvalue); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set individual gain %s to %lf: %s", + oname.toStdString().c_str(), *nvalue, ex.what()); + } + } + + m_settings.m_individualGains[oname] = *nvalue; + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& individualGainsList) +{ + if (individualGainsList.size() == 0) { // Leave early if list of individual gains is empty + return; + } + + std::vector::const_iterator it = individualGainsList.begin(); + + for (int i = 0; it != individualGainsList.end(); ++it, i++) + { + IntervalSliderGUI *gainGUI = new IntervalSliderGUI(this); + gainGUI->setInterval(it->m_range.minimum(), it->m_range.maximum()); + gainGUI->setLabel(QString("%1 gain").arg(it->m_name.c_str())); + gainGUI->setUnits(QString("")); + DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(gainGUI, QString(it->m_name.c_str())); + m_individualGainsGUIs.push_back(gui); + connect(m_individualGainsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(individualGainChanged(QString, double))); + } +} + void SoapySDRInputGui::setName(const QString& name) { setObjectName(name); @@ -334,6 +354,12 @@ void SoapySDRInputGui::globalGainChanged(double gain) sendSettings(); } +void SoapySDRInputGui::individualGainChanged(QString name, double value) +{ + m_settings.m_individualGains[name] = value; + sendSettings(); +} + void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; @@ -441,6 +467,7 @@ void SoapySDRInputGui::displaySettings() ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); displayTunableElementsControlSettings(); + displayIndividualGainsControlSettings(); blockApplySettings(false); } @@ -457,6 +484,18 @@ void SoapySDRInputGui::displayTunableElementsControlSettings() } } +void SoapySDRInputGui::displayIndividualGainsControlSettings() +{ + for (const auto &it : m_individualGainsGUIs) + { + QMap::const_iterator elIt = m_settings.m_individualGains.find(it->getName()); + + if (elIt != m_settings.m_individualGains.end()) { + it->setValue(*elIt); + } + } +} + void SoapySDRInputGui::sendSettings() { if (!m_updateTimer.isActive()) { diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 8678ef617..ec1903cb5 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -64,6 +64,7 @@ private: void createAntennasControl(const std::vector& antennaList); void createTunableElementsControl(const std::vector& tunableElementsList); void createGlobalGainControl(); + void createIndividualGainsControl(const std::vector& individualGainsList); Ui::SoapySDRInputGui* ui; @@ -84,9 +85,11 @@ private: ItemSettingGUI *m_bandwidthGUI; std::vector m_tunableElementsGUIs; IntervalSliderGUI *m_gainSliderGUI; + std::vector m_individualGainsGUIs; void displaySettings(); void displayTunableElementsControlSettings(); + void displayIndividualGainsControlSettings(); void sendSettings(); void updateSampleRateAndFrequency(); void updateFrequencyLimits(); @@ -100,6 +103,7 @@ private slots: void bandwidthChanged(double bandwidth); void tunableElementChanged(QString name, double value); void globalGainChanged(double gain); + void individualGainChanged(QString name, double value); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void on_dcOffset_toggled(bool checked); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp index 2fc6d4232..e8252b2b9 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -58,6 +58,7 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeU32(10, m_bandwidth); s.writeBlob(11, serializeNamedElementMap(m_tunableElements)); s.writeS32(12, m_globalGain); + s.writeBlob(13, serializeNamedElementMap(m_individualGains)); return s.final(); } @@ -91,6 +92,8 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) d.readBlob(11, &blob); deserializeNamedElementMap(blob, m_tunableElements); d.readS32(12, &m_globalGain, 0); + d.readBlob(13, &blob); + deserializeNamedElementMap(blob, m_individualGains); return true; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h index a01242f0c..7acdf6343 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -42,6 +42,7 @@ struct SoapySDRInputSettings { quint32 m_bandwidth; QMap m_tunableElements; qint32 m_globalGain; + QMap m_individualGains; SoapySDRInputSettings(); void resetToDefaults(); diff --git a/sdrgui/soapygui/intervalslidergui.cpp b/sdrgui/soapygui/intervalslidergui.cpp index 12e71f476..f3620004f 100644 --- a/sdrgui/soapygui/intervalslidergui.cpp +++ b/sdrgui/soapygui/intervalslidergui.cpp @@ -20,7 +20,7 @@ #include "intervalslidergui.h" IntervalSliderGUI::IntervalSliderGUI(QWidget* parent) : - QWidget(parent), + ItemSettingGUI(parent), ui(new Ui::IntervalSliderGUI), m_minimum(0), m_maximum(0) @@ -67,5 +67,5 @@ void IntervalSliderGUI::setValue(double value) void IntervalSliderGUI::on_intervalSlider_valueChanged(int value) { ui->valueText->setText(QString("%1").arg(value)); - emit valueChanged(value); + emit ItemSettingGUI::valueChanged(value); } diff --git a/sdrgui/soapygui/intervalslidergui.h b/sdrgui/soapygui/intervalslidergui.h index aff65f0c9..fa89a87aa 100644 --- a/sdrgui/soapygui/intervalslidergui.h +++ b/sdrgui/soapygui/intervalslidergui.h @@ -20,11 +20,13 @@ #include #include +#include "itemsettinggui.h" + namespace Ui { class IntervalSliderGUI; } -class IntervalSliderGUI : public QWidget +class IntervalSliderGUI : public ItemSettingGUI { Q_OBJECT public: @@ -37,9 +39,6 @@ public: virtual double getCurrentValue(); virtual void setValue(double value); -signals: - void valueChanged(double value); - private slots: void on_intervalSlider_valueChanged(int value); From d2eb9130b01c1d26d8e5e1cc8aeaed14e4814cec Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 13:47:47 +0100 Subject: [PATCH 939/956] SoapySDR support: output: individual gains GUIs --- .../soapysdroutput/soapysdroutput.cpp | 25 ++++++++++++ .../soapysdroutput/soapysdroutputgui.cpp | 39 +++++++++++++++++++ .../soapysdroutput/soapysdroutputgui.h | 4 ++ .../soapysdroutput/soapysdroutputsettings.cpp | 3 ++ .../soapysdroutput/soapysdroutputsettings.h | 1 + 5 files changed, 72 insertions(+) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 894c7795f..9050ef98b 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -831,6 +831,31 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool } } + for (const auto &oname : m_settings.m_individualGains.keys()) + { + auto nvalue = settings.m_individualGains.find(oname); + + if (nvalue != settings.m_individualGains.end() && (m_settings.m_individualGains[oname] != *nvalue)) + { + if (dev != 0) + { + try + { + dev->setGain(SOAPY_SDR_TX, requestedChannel, oname.toStdString(), *nvalue); + qDebug("SoapySDROutput::applySettings: individual gain %s set to %lf", + oname.toStdString().c_str(), *nvalue); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set individual gain %s to %lf: %s", + oname.toStdString().c_str(), *nvalue, ex.what()); + } + } + + m_settings.m_individualGains[oname] = *nvalue; + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& individualGainsList) +{ + if (individualGainsList.size() == 0) { // Leave early if list of individual gains is empty + return; + } + + std::vector::const_iterator it = individualGainsList.begin(); + + for (int i = 0; it != individualGainsList.end(); ++it, i++) + { + IntervalSliderGUI *gainGUI = new IntervalSliderGUI(this); + gainGUI->setInterval(it->m_range.minimum(), it->m_range.maximum()); + gainGUI->setLabel(QString("%1 gain").arg(it->m_name.c_str())); + gainGUI->setUnits(QString("")); + DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(gainGUI, QString(it->m_name.c_str())); + m_individualGainsGUIs.push_back(gui); + connect(m_individualGainsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(individualGainChanged(QString, double))); + } +} + void SoapySDROutputGui::setName(const QString& name) { setObjectName(name); @@ -333,6 +353,12 @@ void SoapySDROutputGui::globalGainChanged(double gain) sendSettings(); } +void SoapySDROutputGui::individualGainChanged(QString name, double value) +{ + m_settings.m_individualGains[name] = value; + sendSettings(); +} + void SoapySDROutputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; @@ -398,6 +424,7 @@ void SoapySDROutputGui::displaySettings() ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); displayTunableElementsControlSettings(); + displayIndividualGainsControlSettings(); blockApplySettings(false); } @@ -414,6 +441,18 @@ void SoapySDROutputGui::displayTunableElementsControlSettings() } } +void SoapySDROutputGui::displayIndividualGainsControlSettings() +{ + for (const auto &it : m_individualGainsGUIs) + { + QMap::const_iterator elIt = m_settings.m_individualGains.find(it->getName()); + + if (elIt != m_settings.m_individualGains.end()) { + it->setValue(*elIt); + } + } +} + void SoapySDROutputGui::sendSettings() { if (!m_updateTimer.isActive()) { diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index e6174d3f8..9c9904ba4 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -65,6 +65,7 @@ private: void createAntennasControl(const std::vector& antennaList); void createTunableElementsControl(const std::vector& tunableElementsList); void createGlobalGainControl(); + void createIndividualGainsControl(const std::vector& individualGainsList); Ui::SoapySDROutputGui* ui; @@ -85,10 +86,12 @@ private: ItemSettingGUI *m_bandwidthGUI; std::vector m_tunableElementsGUIs; IntervalSliderGUI *m_gainSliderGUI; + std::vector m_individualGainsGUIs; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); void displayTunableElementsControlSettings(); + void displayIndividualGainsControlSettings(); void sendSettings(); void updateSampleRateAndFrequency(); void updateFrequencyLimits(); @@ -101,6 +104,7 @@ private slots: void bandwidthChanged(double bandwidth); void tunableElementChanged(QString name, double value); void globalGainChanged(double gain); + void individualGainChanged(QString name, double value); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void on_interp_currentIndexChanged(int index); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp index 771513ef5..78f73be43 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -52,6 +52,7 @@ QByteArray SoapySDROutputSettings::serialize() const s.writeU32(7, m_bandwidth); s.writeBlob(8, serializeNamedElementMap(m_tunableElements)); s.writeS32(12, m_globalGain); + s.writeBlob(13, serializeNamedElementMap(m_individualGains)); return s.final(); } @@ -80,6 +81,8 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) d.readBlob(8, &blob); deserializeNamedElementMap(blob, m_tunableElements); d.readS32(12, &m_globalGain, 0); + d.readBlob(13, &blob); + deserializeNamedElementMap(blob, m_individualGains); return true; } diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h index 9a565e8f4..9ac49a6b6 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -31,6 +31,7 @@ struct SoapySDROutputSettings { quint32 m_bandwidth; QMap m_tunableElements; qint32 m_globalGain; + QMap m_individualGains; SoapySDROutputSettings(); void resetToDefaults(); From f13a7e3ed892eac75e748ae7f8e92b1bc647c193 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 14:00:19 +0100 Subject: [PATCH 940/956] SoapySDR support: added missing export for clang-cl in new soapygui GUI objects --- sdrgui/soapygui/discreterangegui.h | 3 ++- sdrgui/soapygui/dynamicitemsettinggui.h | 3 ++- sdrgui/soapygui/intervalrangegui.h | 3 ++- sdrgui/soapygui/intervalslidergui.h | 3 ++- sdrgui/soapygui/itemsettinggui.h | 4 +++- sdrgui/soapygui/stringrangegui.h | 4 +++- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/sdrgui/soapygui/discreterangegui.h b/sdrgui/soapygui/discreterangegui.h index 76d5e42a1..86b495a47 100644 --- a/sdrgui/soapygui/discreterangegui.h +++ b/sdrgui/soapygui/discreterangegui.h @@ -20,13 +20,14 @@ #include #include +#include "export.h" #include "itemsettinggui.h" namespace Ui { class DiscreteRangeGUI; } -class DiscreteRangeGUI : public ItemSettingGUI +class SDRGUI_API DiscreteRangeGUI : public ItemSettingGUI { Q_OBJECT public: diff --git a/sdrgui/soapygui/dynamicitemsettinggui.h b/sdrgui/soapygui/dynamicitemsettinggui.h index f90a97cac..ed64efaae 100644 --- a/sdrgui/soapygui/dynamicitemsettinggui.h +++ b/sdrgui/soapygui/dynamicitemsettinggui.h @@ -17,9 +17,10 @@ #include #include +#include "export.h" #include "itemsettinggui.h" -class DynamicItemSettingGUI : public QObject +class SDRGUI_API DynamicItemSettingGUI : public QObject { Q_OBJECT public: diff --git a/sdrgui/soapygui/intervalrangegui.h b/sdrgui/soapygui/intervalrangegui.h index 1e3b00986..a5df1235e 100644 --- a/sdrgui/soapygui/intervalrangegui.h +++ b/sdrgui/soapygui/intervalrangegui.h @@ -21,13 +21,14 @@ #include #include +#include "export.h" #include "itemsettinggui.h" namespace Ui { class IntervalRangeGUI; } -class IntervalRangeGUI : public ItemSettingGUI +class SDRGUI_API IntervalRangeGUI : public ItemSettingGUI { Q_OBJECT public: diff --git a/sdrgui/soapygui/intervalslidergui.h b/sdrgui/soapygui/intervalslidergui.h index fa89a87aa..1ffc1aad7 100644 --- a/sdrgui/soapygui/intervalslidergui.h +++ b/sdrgui/soapygui/intervalslidergui.h @@ -20,13 +20,14 @@ #include #include +#include "export.h" #include "itemsettinggui.h" namespace Ui { class IntervalSliderGUI; } -class IntervalSliderGUI : public ItemSettingGUI +class SDRGUI_API IntervalSliderGUI : public ItemSettingGUI { Q_OBJECT public: diff --git a/sdrgui/soapygui/itemsettinggui.h b/sdrgui/soapygui/itemsettinggui.h index e84621cd3..7c8757f6f 100644 --- a/sdrgui/soapygui/itemsettinggui.h +++ b/sdrgui/soapygui/itemsettinggui.h @@ -21,7 +21,9 @@ #include -class ItemSettingGUI : public QWidget +#include "export.h" + +class SDRGUI_API ItemSettingGUI : public QWidget { Q_OBJECT public: diff --git a/sdrgui/soapygui/stringrangegui.h b/sdrgui/soapygui/stringrangegui.h index 994fcf378..5a4ec9266 100644 --- a/sdrgui/soapygui/stringrangegui.h +++ b/sdrgui/soapygui/stringrangegui.h @@ -19,11 +19,13 @@ #include +#include "export.h" + namespace Ui { class DiscreteRangeGUI; } -class StringRangeGUI : public QWidget +class SDRGUI_API StringRangeGUI : public QWidget { Q_OBJECT public: From e636d448a7c6570317df1eebadb08cb490bcefb0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 16:37:58 +0100 Subject: [PATCH 941/956] Updated general Readme with most recent changes --- Readme.md | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/Readme.md b/Readme.md index f7600dfa6..39c9c1931 100644 --- a/Readme.md +++ b/Readme.md @@ -52,7 +52,7 @@ Since version 4 a REST API is available to interact with the SDRangel applicatio

    Server instance

    -Since version 4 the `sdrangelsrv` binary launches a server mode SDRangel instance that runs wihout the GUI. More information is provided in the Readme file of the `sdrsrv` folder. +Since version 4 the `sdrangelsrv` binary launches a server mode SDRangel instance that runs wihout the GUI. More information is provided in the Readme file of the `sdrsrv` folder.

    Detached RF head server (SDRdaemon)

    @@ -70,7 +70,7 @@ The audio devices with Qt are supported through pulseaudio and unless you are us - Select HDMI from the profiles list in the 'Configuration' tab - Then in the 'Output devices' tab the HDMI / display port is selected as it is normally the only one. Just double check this - In SDRangel's Preferences/Audio menu make sure something like `alsa_output.pci-0000_00_1b.0.hdmi-stereo` is selected. The default might also work after the pulseaudio configuration you just made. - + In case you cannot see anything related to HDMI or your desired audio device in pavucontrol just restart pulseaudio with `pulseaudio -k` (`-k` kills the previous instance before restarting) and do the above steps again.

    Supported hardware

    @@ -147,7 +147,7 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3. [LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next). -

    ⚠ Version 18.04.1 of LimeSuite is used in the builds and corresponding gateware loaded in the LimeSDR should be is used (2.16 for LimeSDR-USB and 1.24 for LimeSDR-Mini). If you compile from source version 18.04.1 of LimeSuite must be used.

    +

    ⚠ Version 18.10.0 of LimeSuite is used in the builds and corresponding gateware loaded in the LimeSDR should be is used. If you compile from source version 18.10.0 of LimeSuite must be used.

    ⚠ LimeSDR-Mini seems to have problems with Zadig driver therefore it is supported in Linux only.

    @@ -212,7 +212,7 @@ The [File source plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samp Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `FileSource[0]` even if no physical device is connected. -The `.sdriq` format produced are the 2x2 bytes I/Q samples with a header containing the center frequency of the baseband, the sample rate and the timestamp of the recording start. Note that this header length is a multiple of the sample size so the file can be read with a simple 2x2 bytes I/Q reader such as a GNU Radio file source block. It will just produce a short glitch at the beginning corresponding to the header data. +The `.sdriq` format produced are the 2x2 bytes I/Q samples with a header containing the center frequency of the baseband, the sample rate and the timestamp of the recording start. Note that this header length is a multiple of the sample size so the file can be read with a simple 2x2 bytes I/Q reader such as a GNU Radio file source block. It will just produce a short glitch at the beginning corresponding to the header data.

    File output

    @@ -222,13 +222,13 @@ Note that this plugin does not require any of the hardware support libraries nor

    Test source

    -The [Test source plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/samplesource/testsource) is an internal continuous wave generator that can be used to carry out test of software internals. +The [Test source plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/samplesource/testsource) is an internal continuous wave generator that can be used to carry out test of software internals.

    SDRdaemon receiver input

    Linux only. -The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of an instance of SDRangel running the Daemon Sink channel plugin. On the "Data" line you must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiving center frequency. The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. However it is used just to display basic information about the remote. +The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of an instance of SDRangel running the Daemon Sink channel plugin. On the "Data" line you must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiving center frequency. The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. However it is used just to display basic information about the remote. The data blocks transmitted via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. @@ -236,7 +236,7 @@ There is an automated skew rate compensation in place. During rate readjustment This plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. -Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `SDRdaemonSource[0]` even if no physical device is connected. +Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `SDRdaemonSource[0]` even if no physical device is connected.

    SDRdaemon transmitter output

    @@ -266,7 +266,7 @@ It is based on the [DSDcc](https://github.com/f4exb/dsdcc) C++ library which is If you have one or more serial devices interfacing the AMBE3000 chip in packet mode you can use them to decode AMBE voice frames. For that purpose you will need to compile with [SerialDV](https://github.com/f4exb/serialDV) support. Please refer to this project Readme.md to compile and install SerialDV. If you install it in a custom location say `/opt/install/serialdv` you will need to add these defines to the cmake command: `-DLIBSERIALDV_INCLUDE_DIR=/opt/install/serialdv/include/serialdv -DLIBSERIALDV_LIBRARY=/opt/install/serialdv/lib/libserialdv.so` Also your user must be a member of group `dialout` (Ubuntu/Debian) or `uucp` (Arch) to be able to use the dongle. -Although such serial devices work with a serial interface at 400 kb in practice maybe for other reasons they are capable of handling only one conversation at a time. The software will allocate the device dynamically to a conversation with an inactivity timeout of 1 second so that conversations do not get interrupted constantly making the audio output too choppy. In practice you will have to have as many devices connected to your system as the number of conversations you would like to be handled in parallel. +Although such serial devices work with a serial interface at 400 kb in practice maybe for other reasons they are capable of handling only one conversation at a time. The software will allocate the device dynamically to a conversation with an inactivity timeout of 1 second so that conversations do not get interrupted constantly making the audio output too choppy. In practice you will have to have as many devices connected to your system as the number of conversations you would like to be handled in parallel. Alternatively you can use [mbelib](https://github.com/szechyjs/mbelib) but mbelib comes with some copyright issues (see next). If you have mbelib installed in a custom location, say `/opt/install/mbelib` you will need to add these defines to the cmake command: `-DLIBMBE_INCLUDE_DIR=/opt/install/mbelib/include -DLIBMBE_LIBRARY=/opt/install/mbelib/lib/libmbe.so` @@ -291,7 +291,7 @@ In the [releases](https://github.com/f4exb/sdrangel/releases) section one can fi

    Debian distributions

    -It is provided in the form of .deb packages for x86_64 architectures with SSE 4.1 support. +It is provided in the form of .deb packages for x86_64 architectures with SSE 4.1 support. Install it as usual for .deb packages: @@ -303,29 +303,27 @@ Prior to apt-get v 1.1 (before Ubuntu 16.04) in a terminal do: - `sudo apt-get upgrade` - `sudo dpkg -i sdrangel_vx.y.z-1_amd64.deb` where x.y.z is the version number - `sudo apt-get -f install` this will install missing dependencies - + Since apt-get v 1.1 installation is possible from a local file: - cd to where the archive has been downloaded - - `sudo apt-get install ./sdrangel_vx.y.z-1_amd64.deb` where x.y.z is the version number + - `sudo apt-get install ./sdrangel_vx.y.z-1_amd64.deb` where x.y.z is the version number - `sudo apt-get -f install` this will install missing dependencies - + The software is installed in `/opt/sdrangel` you can start it from the command line with: - `/opt/sdrangel/bin/sdrangel` **⚠** The udev rules are not set by the package installation so you will have to set it manually in order to be able to access the various SDR hardware. The `udev-rules` folder contains the rules file and the `install.sh` script that you can run as sudo to install all rules files. You may also adapt the script to copy only the required files. - +

    Ubuntu 18.04

    The default CPU governor is now `powersave` which exhibits excessive CPU usage when running SDRangel. In the case of benchmarking and maybe high throughput usage it is recommended to switch to `performance` before running SDRangel by running the command: `sudo cpupower frequency-set --governor performance`. You can turn it back to `powersave` any time by running: `sudo cpupower frequency-set --governor powersave`. It is normal that with a lower CPU frequency the relative CPU usage rises for the same actual load. If not impairing operation this is normal and overall beneficial for heat and power consumption. - -

    Windows distribution

    -The last Windows distribution is for 4.0.0. +

    Windows distribution

    This is the archive of the complete binary distribution that expands to the `sdrangel` directory. You can install it anywhere you like and click on `sdrangel.exe` to start. -⚠ Windows distribution was provided as a by product thanks to the Qt toolchain. The platform of choice to run SDRangel is definitely Linux and very little support can be given for this Windows distribution. +⚠ Windows distribution is provided as a by product thanks to the Qt toolchain. The platform of choice to run SDRangel is definitely Linux and very little support can be given for this Windows distribution.

    Software build

    @@ -333,8 +331,8 @@ This is the archive of the complete binary distribution that expands to the `sdr To be sure you will need at least Qt version 5.5. It definitely does not work with versions earlier than 5.3 but neither 5.3 nor 5.4 were tested. - - Linux builds are made with 5.5.1 (Xenial) and 5.9 (Artful, Stretch) - - Windows build was made with 5.10.1 in 32 bit mode and has Qt ANGLE support (OpenGL emulation with DirectX) + - Linux builds are made with 5.5.1 (Xenial), 5.9 (Artful, Stretch, Bionic) and 5.11 (Cosmic) + - Windows build is made with 5.10.1 in 32 bit mode and has Qt ANGLE support (OpenGL emulation with DirectX)

    24 bit DSP

    @@ -396,7 +394,7 @@ For Debian Jessie or Stretch: This has been tested with the Leap 42.3 distribution: -`sudo zypper install Mesa-libGL1 Mesa-libEGL-devel Mesa-libGL-devel Mesa-libGLESv1_CM-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel Mesa-libglapi-devel libOSMesa-devel` +`sudo zypper install Mesa-libGL1 Mesa-libEGL-devel Mesa-libGL-devel Mesa-libGLESv1_CM-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel Mesa-libglapi-devel libOSMesa-devel` `sudo zypper install cmake fftw3-devel gcc-c++ libusb-1_0-devel libqt5-qtbase-devel libQt5OpenGL-devel libqt5-qtmultimedia-devel libqt5-qttools-devel libQt5Network-devel libQt5Widgets-devel boost-devel alsa-devel pulseaudio opencv-devel` From b316af2d1db750544ab10be2f63c2bda93855501 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 20:06:07 +0100 Subject: [PATCH 942/956] SoapySDR support: individual gains GUIs fixes --- .../samplesink/soapysdroutput/soapysdroutputgui.cpp | 3 +++ .../samplesource/soapysdrinput/soapysdrinputgui.cpp | 3 +++ sdrgui/soapygui/intervalslidergui.ui | 12 +++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index bfc1790c2..75f9ae9fd 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -58,6 +58,7 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) createRangesControl(&m_bandwidthGUI, m_sampleSink->getBandwidthRanges(), "BW", "Hz"); createTunableElementsControl(m_sampleSink->getTunableElements()); createGlobalGainControl(); + createIndividualGainsControl(m_sampleSink->getIndividualGainsRanges()); if (m_sampleRateGUI) { connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); @@ -205,6 +206,7 @@ void SoapySDROutputGui::createIndividualGainsControl(const std::vectorscrollAreaWidgetContents->layout(); std::vector::const_iterator it = individualGainsList.begin(); for (int i = 0; it != individualGainsList.end(); ++it, i++) @@ -214,6 +216,7 @@ void SoapySDROutputGui::createIndividualGainsControl(const std::vectorsetLabel(QString("%1 gain").arg(it->m_name.c_str())); gainGUI->setUnits(QString("")); DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(gainGUI, QString(it->m_name.c_str())); + layout->addWidget(gainGUI); m_individualGainsGUIs.push_back(gui); connect(m_individualGainsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(individualGainChanged(QString, double))); } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 01b37ea9b..31ee8d95a 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -59,6 +59,7 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : createRangesControl(&m_bandwidthGUI, m_sampleSource->getBandwidthRanges(), "BW", "Hz"); createTunableElementsControl(m_sampleSource->getTunableElements()); createGlobalGainControl(); + createIndividualGainsControl(m_sampleSource->getIndividualGainsRanges()); if (m_sampleRateGUI) { connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); @@ -206,6 +207,7 @@ void SoapySDRInputGui::createIndividualGainsControl(const std::vectorscrollAreaWidgetContents->layout(); std::vector::const_iterator it = individualGainsList.begin(); for (int i = 0; it != individualGainsList.end(); ++it, i++) @@ -215,6 +217,7 @@ void SoapySDRInputGui::createIndividualGainsControl(const std::vectorsetLabel(QString("%1 gain").arg(it->m_name.c_str())); gainGUI->setUnits(QString("")); DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(gainGUI, QString(it->m_name.c_str())); + layout->addWidget(gainGUI); m_individualGainsGUIs.push_back(gui); connect(m_individualGainsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(individualGainChanged(QString, double))); } diff --git a/sdrgui/soapygui/intervalslidergui.ui b/sdrgui/soapygui/intervalslidergui.ui index ed34216e1..282be3207 100644 --- a/sdrgui/soapygui/intervalslidergui.ui +++ b/sdrgui/soapygui/intervalslidergui.ui @@ -6,7 +6,7 @@ 0 0 - 203 + 266 30 @@ -30,13 +30,19 @@ 0 0 - 179 - 29 + 256 + 21 + + + 80 + 0 + + Label From 9d014d841b29c485714c2bb37c30a6880e284865 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 23:21:37 +0100 Subject: [PATCH 943/956] SoapySDR support: input: manage global and individual gains coupling --- .../soapysdrinput/soapysdrinput.cpp | 47 ++++++++++++++++++- .../soapysdrinput/soapysdrinput.h | 28 +++++++++++ .../soapysdrinput/soapysdrinputgui.cpp | 19 ++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index e78bf5fe7..b30553e14 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -31,6 +31,7 @@ MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgConfigureSoapySDRInput, Message) MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgFileRecord, Message) MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgReportGainChange, Message) SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) : m_deviceAPI(deviceAPI), @@ -40,6 +41,7 @@ SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) : m_thread(0) { openDevice(); + initGainSettings(m_settings); m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); @@ -261,6 +263,19 @@ const std::vector& SoapySDRInput::getIndividu return channelSettings->m_gainSettings; } +void SoapySDRInput::initGainSettings(SoapySDRInputSettings& settings) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + settings.m_individualGains.clear(); + settings.m_globalGain = 0; + + for (const auto &it : channelSettings->m_gainSettings) { + settings.m_individualGains[QString(it.m_name.c_str())] = 0.0; + } + + updateGains(m_deviceShared.m_device, m_deviceShared.m_channel, settings); +} + void SoapySDRInput::init() { applySettings(m_settings, true); @@ -610,6 +625,19 @@ bool SoapySDRInput::setDeviceCenterFrequency(SoapySDR::Device *dev, int requeste } } +void SoapySDRInput::updateGains(SoapySDR::Device *dev, int requestedChannel, SoapySDRInputSettings& settings) +{ + if (dev == 0) { + return; + } + + settings.m_globalGain = round(dev->getGain(SOAPY_SDR_RX, requestedChannel)); + + for (const auto &name : settings.m_individualGains.keys()) { + settings.m_individualGains[name] = dev->getGain(SOAPY_SDR_RX, requestedChannel, name.toStdString()); + } +} + bool SoapySDRInput::handleMessage(const Message& message __attribute__((unused))) { if (MsgConfigureSoapySDRInput::match(message)) @@ -709,6 +737,8 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo { bool forwardChangeOwnDSP = false; bool forwardChangeToBuddies = false; + bool globalGainChanged = false; + bool individualGainsChanged = false; SoapySDR::Device *dev = m_deviceShared.m_device; SoapySDRInputThread *inputThread = findThread(); @@ -867,6 +897,7 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo { dev->setGain(SOAPY_SDR_RX, requestedChannel, settings.m_globalGain); qDebug("SoapySDRInput::applySettings: set global gain to %d", settings.m_globalGain); + globalGainChanged = true; } catch (const std::exception &ex) { @@ -880,7 +911,7 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo { auto nvalue = settings.m_individualGains.find(oname); - if (nvalue != settings.m_individualGains.end() && (m_settings.m_individualGains[oname] != *nvalue)) + if (nvalue != settings.m_individualGains.end() && ((m_settings.m_individualGains[oname] != *nvalue) || force)) { if (dev != 0) { @@ -889,6 +920,7 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo dev->setGain(SOAPY_SDR_RX, requestedChannel, oname.toStdString(), *nvalue); qDebug("SoapySDRInput::applySettings: individual gain %s set to %lf", oname.toStdString().c_str(), *nvalue); + individualGainsChanged = true; } catch (const std::exception &ex) { @@ -940,6 +972,19 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo m_settings = settings; + if (globalGainChanged || individualGainsChanged) + { + if (dev) { + updateGains(dev, requestedChannel, m_settings); + } + + if (getMessageQueueToGUI()) + { + MsgReportGainChange *report = MsgReportGainChange::create(m_settings, individualGainsChanged, globalGainChanged); + getMessageQueueToGUI()->push(report); + } + } + qDebug() << "SoapySDRInput::applySettings: " << " m_transverterMode: " << m_settings.m_transverterMode << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h index 94cb40e1b..a44e52831 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -99,6 +99,32 @@ public: { } }; + class MsgReportGainChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SoapySDRInputSettings& getSettings() const { return m_settings; } + bool getGlobalGain() const { return m_globalGain; } + bool getIndividualGains() const { return m_individualGains; } + + static MsgReportGainChange* create(const SoapySDRInputSettings& settings, bool globalGain, bool individualGains) + { + return new MsgReportGainChange(settings, globalGain, individualGains); + } + + private: + SoapySDRInputSettings m_settings; + bool m_globalGain; + bool m_individualGains; + + MsgReportGainChange(const SoapySDRInputSettings& settings, bool globalGain, bool individualGains) : + Message(), + m_settings(settings), + m_globalGain(globalGain), + m_individualGains(individualGains) + { } + }; + SoapySDRInput(DeviceSourceAPI *deviceAPI); virtual ~SoapySDRInput(); virtual void destroy(); @@ -128,6 +154,7 @@ public: int getAntennaIndex(const std::string& antenna); const std::vector& getTunableElements(); const std::vector& getIndividualGainsRanges(); + void initGainSettings(SoapySDRInputSettings& settings); private: DeviceSourceAPI *m_deviceAPI; @@ -145,6 +172,7 @@ private: void moveThreadToBuddy(); bool applySettings(const SoapySDRInputSettings& settings, bool force = false); bool setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); + void updateGains(SoapySDR::Device *dev, int requestedChannel, SoapySDRInputSettings& settings); }; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 31ee8d95a..5fd8ccb24 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -60,6 +60,7 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : createTunableElementsControl(m_sampleSource->getTunableElements()); createGlobalGainControl(); createIndividualGainsControl(m_sampleSource->getIndividualGainsRanges()); + m_sampleSource->initGainSettings(m_settings); if (m_sampleRateGUI) { connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); @@ -282,6 +283,24 @@ bool SoapySDRInputGui::handleMessage(const Message& message) return true; } + else if (SoapySDRInput::MsgReportGainChange::match(message)) + { + const SoapySDRInput::MsgReportGainChange& report = (SoapySDRInput::MsgReportGainChange&) message; + const SoapySDRInputSettings& gainSettings = report.getSettings(); + + if (report.getGlobalGain()) { + m_settings.m_globalGain = gainSettings.m_globalGain; + } + if (report.getIndividualGains()) { + m_settings.m_individualGains = gainSettings.m_individualGains; + } + + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } else if (SoapySDRInput::MsgStartStop::match(message)) { SoapySDRInput::MsgStartStop& notif = (SoapySDRInput::MsgStartStop&) message; From 59c8ecd2d0d1cfc104612eacc478f22d51f99926 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 7 Nov 2018 23:54:32 +0100 Subject: [PATCH 944/956] SoapySDR support: output: manage global and individual gains coupling --- .../soapysdroutput/soapysdroutput.cpp | 47 ++++++++++++++++++- .../soapysdroutput/soapysdroutput.h | 28 +++++++++++ .../soapysdroutput/soapysdroutputgui.cpp | 19 ++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index 9050ef98b..a54e3dc9a 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -28,6 +28,7 @@ MESSAGE_CLASS_DEFINITION(SoapySDROutput::MsgConfigureSoapySDROutput, Message) MESSAGE_CLASS_DEFINITION(SoapySDROutput::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SoapySDROutput::MsgReportGainChange, Message) SoapySDROutput::SoapySDROutput(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), @@ -36,6 +37,7 @@ SoapySDROutput::SoapySDROutput(DeviceSinkAPI *deviceAPI) : m_thread(0) { openDevice(); + initGainSettings(m_settings); } SoapySDROutput::~SoapySDROutput() @@ -230,6 +232,19 @@ const std::vector& SoapySDROutput::getIndivid return channelSettings->m_gainSettings; } +void SoapySDROutput::initGainSettings(SoapySDROutputSettings& settings) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + settings.m_individualGains.clear(); + settings.m_globalGain = 0; + + for (const auto &it : channelSettings->m_gainSettings) { + settings.m_individualGains[QString(it.m_name.c_str())] = 0.0; + } + + updateGains(m_deviceShared.m_device, m_deviceShared.m_channel, settings); +} + void SoapySDROutput::init() { applySettings(m_settings, true); @@ -585,6 +600,19 @@ bool SoapySDROutput::setDeviceCenterFrequency(SoapySDR::Device *dev, int request } } +void SoapySDROutput::updateGains(SoapySDR::Device *dev, int requestedChannel, SoapySDROutputSettings& settings) +{ + if (dev == 0) { + return; + } + + settings.m_globalGain = round(dev->getGain(SOAPY_SDR_TX, requestedChannel)); + + for (const auto &name : settings.m_individualGains.keys()) { + settings.m_individualGains[name] = dev->getGain(SOAPY_SDR_TX, requestedChannel, name.toStdString()); + } +} + bool SoapySDROutput::handleMessage(const Message& message) { if (MsgConfigureSoapySDROutput::match(message)) @@ -656,6 +684,8 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool { bool forwardChangeOwnDSP = false; bool forwardChangeToBuddies = false; + bool globalGainChanged = false; + bool individualGainsChanged = false; SoapySDR::Device *dev = m_deviceShared.m_device; SoapySDROutputThread *outputThread = findThread(); @@ -822,6 +852,7 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool { dev->setGain(SOAPY_SDR_TX, requestedChannel, settings.m_globalGain); qDebug("SoapySDROutput::applySettings: set global gain to %d", settings.m_globalGain); + globalGainChanged = true; } catch (const std::exception &ex) { @@ -835,7 +866,7 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool { auto nvalue = settings.m_individualGains.find(oname); - if (nvalue != settings.m_individualGains.end() && (m_settings.m_individualGains[oname] != *nvalue)) + if (nvalue != settings.m_individualGains.end() && ((m_settings.m_individualGains[oname] != *nvalue) || force)) { if (dev != 0) { @@ -844,6 +875,7 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool dev->setGain(SOAPY_SDR_TX, requestedChannel, oname.toStdString(), *nvalue); qDebug("SoapySDROutput::applySettings: individual gain %s set to %lf", oname.toStdString().c_str(), *nvalue); + individualGainsChanged = true; } catch (const std::exception &ex) { @@ -894,6 +926,19 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool m_settings = settings; + if (globalGainChanged || individualGainsChanged) + { + if (dev) { + updateGains(dev, requestedChannel, m_settings); + } + + if (getMessageQueueToGUI()) + { + MsgReportGainChange *report = MsgReportGainChange::create(m_settings, individualGainsChanged, globalGainChanged); + getMessageQueueToGUI()->push(report); + } + } + qDebug() << "SoapySDROutput::applySettings: " << " m_transverterMode: " << m_settings.m_transverterMode << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.h b/plugins/samplesink/soapysdroutput/soapysdroutput.h index 4089fcd9c..12ee21b57 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.h @@ -59,6 +59,32 @@ public: { } }; + class MsgReportGainChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SoapySDROutputSettings& getSettings() const { return m_settings; } + bool getGlobalGain() const { return m_globalGain; } + bool getIndividualGains() const { return m_individualGains; } + + static MsgReportGainChange* create(const SoapySDROutputSettings& settings, bool globalGain, bool individualGains) + { + return new MsgReportGainChange(settings, globalGain, individualGains); + } + + private: + SoapySDROutputSettings m_settings; + bool m_globalGain; + bool m_individualGains; + + MsgReportGainChange(const SoapySDROutputSettings& settings, bool globalGain, bool individualGains) : + Message(), + m_settings(settings), + m_globalGain(globalGain), + m_individualGains(individualGains) + { } + }; + class MsgStartStop : public Message { MESSAGE_CLASS_DECLARATION @@ -106,6 +132,7 @@ public: const SoapySDR::RangeList& getBandwidthRanges(); const std::vector& getTunableElements(); const std::vector& getIndividualGainsRanges(); + void initGainSettings(SoapySDROutputSettings& settings); private: DeviceSinkAPI *m_deviceAPI; @@ -122,6 +149,7 @@ private: void moveThreadToBuddy(); bool applySettings(const SoapySDROutputSettings& settings, bool force = false); bool setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); + void updateGains(SoapySDR::Device *dev, int requestedChannel, SoapySDROutputSettings& settings); }; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index 75f9ae9fd..1b9fc81d6 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -59,6 +59,7 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) createTunableElementsControl(m_sampleSink->getTunableElements()); createGlobalGainControl(); createIndividualGainsControl(m_sampleSink->getIndividualGainsRanges()); + m_sampleSink->initGainSettings(m_settings); if (m_sampleRateGUI) { connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); @@ -282,6 +283,24 @@ bool SoapySDROutputGui::handleMessage(const Message& message) return true; } + else if (SoapySDROutput::MsgReportGainChange::match(message)) + { + const SoapySDROutput::MsgReportGainChange& report = (SoapySDROutput::MsgReportGainChange&) message; + const SoapySDROutputSettings& gainSettings = report.getSettings(); + + if (report.getGlobalGain()) { + m_settings.m_globalGain = gainSettings.m_globalGain; + } + if (report.getIndividualGains()) { + m_settings.m_individualGains = gainSettings.m_individualGains; + } + + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } else if (SoapySDROutput::MsgStartStop::match(message)) { SoapySDROutput::MsgStartStop& notif = (SoapySDROutput::MsgStartStop&) message; From f97091e0f3523d79c9609adcd94aa30a36ce1727 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 01:31:39 +0100 Subject: [PATCH 945/956] SoapySDR support: input: AGC GUI --- .../soapysdrinput/soapysdrinput.cpp | 22 +++++++++++++++ .../soapysdrinput/soapysdrinput.h | 1 + .../soapysdrinput/soapysdrinputgui.cpp | 28 ++++++++++++++++++- .../soapysdrinput/soapysdrinputgui.h | 4 ++- .../soapysdrinput/soapysdrinputgui.ui | 9 +----- .../soapysdrinput/soapysdrinputsettings.cpp | 3 ++ .../soapysdrinput/soapysdrinputsettings.h | 1 + 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index b30553e14..556d72687 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -221,6 +221,12 @@ void SoapySDRInput::getGlobalGainRange(int& min, int& max) } } +bool SoapySDRInput::isAGCSupported() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasAGC; +} + const std::vector& SoapySDRInput::getAntennas() { const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); @@ -933,6 +939,22 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo } } + if ((m_settings.m_autoGain != settings.m_autoGain) || force) + { + if (dev != 0) + { + try + { + dev->setGainMode(SOAPY_SDR_RX, requestedChannel, settings.m_autoGain); + qDebug("SoapySDRInput::applySettings: %s AGC", settings.m_autoGain ? "set" : "unset"); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot %s AGC", settings.m_autoGain ? "set" : "unset"); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& getAntennas(); const SoapySDR::RangeList& getRateRanges(); const SoapySDR::RangeList& getBandwidthRanges(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 5fd8ccb24..62ebd890f 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -15,6 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "dsp/dspengine.h" #include "dsp/dspcommands.h" @@ -44,7 +45,8 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_antennas(0), m_sampleRateGUI(0), m_bandwidthGUI(0), - m_gainSliderGUI(0) + m_gainSliderGUI(0), + m_autoGain(0) { m_sampleSource = (SoapySDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); ui->setupUi(this); @@ -197,6 +199,21 @@ void SoapySDRInputGui::createGlobalGainControl() m_gainSliderGUI->setUnits(QString("")); QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + + QFrame *line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + layout->addWidget(line); + + if (m_sampleSource->isAGCSupported()) + { + m_autoGain = new QCheckBox(this); + m_autoGain->setText(QString("AGC")); + layout->addWidget(m_autoGain); + + connect(m_autoGain, SIGNAL(toggled(bool)), this, SLOT(autoGainChanged(bool))); + } + layout->addWidget(m_gainSliderGUI); connect(m_gainSliderGUI, SIGNAL(valueChanged(double)), this, SLOT(globalGainChanged(double))); @@ -376,6 +393,12 @@ void SoapySDRInputGui::globalGainChanged(double gain) sendSettings(); } +void SoapySDRInputGui::autoGainChanged(bool set) +{ + m_settings.m_autoGain = set; + sendSettings(); +} + void SoapySDRInputGui::individualGainChanged(QString name, double value) { m_settings.m_individualGains[name] = value; @@ -478,6 +501,9 @@ void SoapySDRInputGui::displaySettings() if (m_gainSliderGUI) { m_gainSliderGUI->setValue(m_settings.m_globalGain); } + if (m_autoGain) { + m_autoGain->setChecked(m_settings.m_autoGain); + } ui->dcOffset->setChecked(m_settings.m_dcBlock); ui->iqImbalance->setChecked(m_settings.m_iqCorrection); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index ec1903cb5..65fd3a916 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -19,7 +19,6 @@ #include #include -#include #include "plugin/plugininstancegui.h" #include "util/messagequeue.h" @@ -31,6 +30,7 @@ class ItemSettingGUI; class StringRangeGUI; class DynamicItemSettingGUI; class IntervalSliderGUI; +class QCheckBox; namespace Ui { class SoapySDRInputGui; @@ -86,6 +86,7 @@ private: std::vector m_tunableElementsGUIs; IntervalSliderGUI *m_gainSliderGUI; std::vector m_individualGainsGUIs; + QCheckBox *m_autoGain; void displaySettings(); void displayTunableElementsControlSettings(); @@ -103,6 +104,7 @@ private slots: void bandwidthChanged(double bandwidth); void tunableElementChanged(QString name, double value); void globalGainChanged(double gain); + void autoGainChanged(bool set); void individualGainChanged(QString name, double value); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index be2341a59..0bb9c3aec 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -365,13 +365,6 @@
    - - - - Qt::Horizontal - - - @@ -386,7 +379,7 @@ 0 0 318 - 51 + 53 diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp index e8252b2b9..6614e596c 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -40,6 +40,7 @@ void SoapySDRInputSettings::resetToDefaults() m_antenna = "NONE"; m_bandwidth = 1000000; m_globalGain = 0; + m_autoGain = false; } QByteArray SoapySDRInputSettings::serialize() const @@ -59,6 +60,7 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeBlob(11, serializeNamedElementMap(m_tunableElements)); s.writeS32(12, m_globalGain); s.writeBlob(13, serializeNamedElementMap(m_individualGains)); + s.writeBool(14, m_autoGain); return s.final(); } @@ -94,6 +96,7 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) d.readS32(12, &m_globalGain, 0); d.readBlob(13, &blob); deserializeNamedElementMap(blob, m_individualGains); + d.readBool(14, &m_autoGain, false); return true; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h index 7acdf6343..67623834c 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -43,6 +43,7 @@ struct SoapySDRInputSettings { QMap m_tunableElements; qint32 m_globalGain; QMap m_individualGains; + bool m_autoGain; SoapySDRInputSettings(); void resetToDefaults(); From ef672300d2d1852b8731858ef6e3c9aee41e71c6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 01:52:48 +0100 Subject: [PATCH 946/956] SoapySDR support: output: AGC GUI --- .../soapysdroutput/soapysdroutput.cpp | 21 ++++++++++++++ .../soapysdroutput/soapysdroutput.h | 1 + .../soapysdroutput/soapysdroutputgui.cpp | 28 ++++++++++++++++++- .../soapysdroutput/soapysdroutputgui.h | 3 ++ .../soapysdroutput/soapysdroutputgui.ui | 9 +----- .../soapysdroutput/soapysdroutputsettings.cpp | 5 +++- .../soapysdroutput/soapysdroutputsettings.h | 1 + 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index a54e3dc9a..e8eb759c8 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -201,6 +201,11 @@ void SoapySDROutput::getGlobalGainRange(int& min, int& max) } } +bool SoapySDROutput::isAGCSupported() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasAGC; +} const SoapySDR::RangeList& SoapySDROutput::getRateRanges() { @@ -888,6 +893,22 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool } } + if ((m_settings.m_autoGain != settings.m_autoGain) || force) + { + if (dev != 0) + { + try + { + dev->setGainMode(SOAPY_SDR_TX, requestedChannel, settings.m_autoGain); + qDebug("SoapySDROutput::applySettings: %s AGC", settings.m_autoGain ? "set" : "unset"); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot %s AGC", settings.m_autoGain ? "set" : "unset"); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& getAntennas(); const SoapySDR::RangeList& getBandwidthRanges(); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index 1b9fc81d6..38b745e1d 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -15,6 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "dsp/dspengine.h" #include "dsp/dspcommands.h" @@ -43,7 +44,8 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) m_antennas(0), m_sampleRateGUI(0), m_bandwidthGUI(0), - m_gainSliderGUI(0) + m_gainSliderGUI(0), + m_autoGain(0) { m_sampleSink = (SoapySDROutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); ui->setupUi(this); @@ -196,6 +198,21 @@ void SoapySDROutputGui::createGlobalGainControl() m_gainSliderGUI->setUnits(QString("")); QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + + QFrame *line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + layout->addWidget(line); + + if (m_sampleSink->isAGCSupported()) + { + m_autoGain = new QCheckBox(this); + m_autoGain->setText(QString("AGC")); + layout->addWidget(m_autoGain); + + connect(m_autoGain, SIGNAL(toggled(bool)), this, SLOT(autoGainChanged(bool))); + } + layout->addWidget(m_gainSliderGUI); connect(m_gainSliderGUI, SIGNAL(valueChanged(double)), this, SLOT(globalGainChanged(double))); @@ -375,6 +392,12 @@ void SoapySDROutputGui::globalGainChanged(double gain) sendSettings(); } +void SoapySDROutputGui::autoGainChanged(bool set) +{ + m_settings.m_autoGain = set; + sendSettings(); +} + void SoapySDROutputGui::individualGainChanged(QString name, double value) { m_settings.m_individualGains[name] = value; @@ -439,6 +462,9 @@ void SoapySDROutputGui::displaySettings() if (m_gainSliderGUI) { m_gainSliderGUI->setValue(m_settings.m_globalGain); } + if (m_autoGain) { + m_autoGain->setChecked(m_settings.m_autoGain); + } ui->interp->setCurrentIndex(m_settings.m_log2Interp); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index 9c9904ba4..c108fa837 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -32,6 +32,7 @@ class ItemSettingGUI; class StringRangeGUI; class DynamicItemSettingGUI; class IntervalSliderGUI; +class QCheckBox; namespace Ui { class SoapySDROutputGui; @@ -87,6 +88,7 @@ private: std::vector m_tunableElementsGUIs; IntervalSliderGUI *m_gainSliderGUI; std::vector m_individualGainsGUIs; + QCheckBox *m_autoGain; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); @@ -104,6 +106,7 @@ private slots: void bandwidthChanged(double bandwidth); void tunableElementChanged(QString name, double value); void globalGainChanged(double gain); + void autoGainChanged(bool set); void individualGainChanged(QString name, double value); void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui b/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui index 839ce9fc7..f173efefd 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui @@ -289,13 +289,6 @@
    - - - - Qt::Horizontal - - - @@ -307,7 +300,7 @@ 0 0 318 - 51 + 53 diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp index 78f73be43..2be673c1e 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -37,6 +37,7 @@ void SoapySDROutputSettings::resetToDefaults() m_antenna = "NONE"; m_bandwidth = 1000000; m_globalGain = 0; + m_autoGain = false; } QByteArray SoapySDROutputSettings::serialize() const @@ -53,6 +54,7 @@ QByteArray SoapySDROutputSettings::serialize() const s.writeBlob(8, serializeNamedElementMap(m_tunableElements)); s.writeS32(12, m_globalGain); s.writeBlob(13, serializeNamedElementMap(m_individualGains)); + s.writeBool(14, m_autoGain); return s.final(); } @@ -83,6 +85,7 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) d.readS32(12, &m_globalGain, 0); d.readBlob(13, &blob); deserializeNamedElementMap(blob, m_individualGains); + d.readBool(14, &m_autoGain, false); return true; } @@ -108,4 +111,4 @@ void SoapySDROutputSettings::deserializeNamedElementMap(const QByteArray& data, QDataStream *stream = new QDataStream(data); (*stream) >> map; delete stream; -} \ No newline at end of file +} diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h index 9ac49a6b6..e5ee04b83 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -32,6 +32,7 @@ struct SoapySDROutputSettings { QMap m_tunableElements; qint32 m_globalGain; QMap m_individualGains; + bool m_autoGain; SoapySDROutputSettings(); void resetToDefaults(); From 48340f253a88e54da3c2bdeac3ba1a958fcf30dc Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 02:28:01 +0100 Subject: [PATCH 947/956] SoapySDR support: fixed StringRangeGUI::setValue --- plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp | 1 + sdrgui/soapygui/stringrangegui.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 62ebd890f..49caee713 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -490,6 +490,7 @@ void SoapySDRInputGui::displaySettings() ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); if (m_antennas) { + qDebug("SoapySDRInputGui::displaySettings: m_antenna: %s", m_settings.m_antenna.toStdString().c_str()); m_antennas->setValue(m_settings.m_antenna.toStdString()); } if (m_sampleRateGUI) { diff --git a/sdrgui/soapygui/stringrangegui.cpp b/sdrgui/soapygui/stringrangegui.cpp index 2cf3cb011..677bcd5db 100644 --- a/sdrgui/soapygui/stringrangegui.cpp +++ b/sdrgui/soapygui/stringrangegui.cpp @@ -59,7 +59,7 @@ void StringRangeGUI::setValue(const std::string& value) for (const auto &it : itemValues) { - if (it >= value) + if (it == value) { ui->rangeCombo->blockSignals(true); ui->rangeCombo->setCurrentIndex(index); From 98b79de593f8ebcc27a319a4a2ba0e84be5ca7f3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 14:35:02 +0100 Subject: [PATCH 948/956] SoapySDR support: added GUI for complex factors (manual DC offset and IQ imbalance settings) --- sdrgui/CMakeLists.txt | 3 + sdrgui/soapygui/complexfactorgui.cpp | 88 +++++++++++++ sdrgui/soapygui/complexfactorgui.h | 65 ++++++++++ sdrgui/soapygui/complexfactorgui.ui | 183 +++++++++++++++++++++++++++ 4 files changed, 339 insertions(+) create mode 100644 sdrgui/soapygui/complexfactorgui.cpp create mode 100644 sdrgui/soapygui/complexfactorgui.h create mode 100644 sdrgui/soapygui/complexfactorgui.ui diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 2914b29b5..f401299f2 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -60,6 +60,7 @@ set(sdrgui_SOURCES soapygui/stringrangegui.cpp soapygui/dynamicitemsettinggui.cpp soapygui/intervalslidergui.cpp + soapygui/complexfactorgui.cpp webapi/webapiadaptergui.cpp ) @@ -124,6 +125,7 @@ set(sdrgui_HEADERS soapygui/stringrangegui.h soapygui/dynamicitemsettinggui.h soapygui/intervalslidergui.h + soapygui/complexfactorgui.h webapi/webapiadaptergui.h ) @@ -156,6 +158,7 @@ set(sdrgui_FORMS soapygui/discreterangegui.ui soapygui/intervalrangegui.ui soapygui/intervalslidergui.ui + soapygui/complexfactorgui.ui ) set(sdrgui_RESOURCES diff --git a/sdrgui/soapygui/complexfactorgui.cpp b/sdrgui/soapygui/complexfactorgui.cpp new file mode 100644 index 000000000..c83920301 --- /dev/null +++ b/sdrgui/soapygui/complexfactorgui.cpp @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "ui_complexfactorgui.h" +#include "complexfactorgui.h" + +ComplexFactorGUI::ComplexFactorGUI(QWidget *parent) : + QWidget(parent), + ui(new Ui::ComplexFactorGUI) +{ + ui->setupUi(this); + ui->automatic->setChecked(false); +} + +ComplexFactorGUI::~ComplexFactorGUI() +{ + delete ui; +} + +double ComplexFactorGUI::getModule() const +{ + return ui->module->value() / 100.0; +} + +double ComplexFactorGUI::getArgument() const +{ + return ui->arg->value() * 1.0; +} + +bool ComplexFactorGUI::getAutomatic() const +{ + return ui->automatic->isChecked(); +} + +void ComplexFactorGUI::setModule(double value) +{ + ui->module->setValue((int) (value < -1.0 ? -1.0 : value > 1.0 ? 1.0 : value)*100.0f); +} + +void ComplexFactorGUI::setArgument(double value) +{ + ui->module->setValue((int) (value < -180.0 ? -180.0 : value > 180.0 ? 180.0 : value)); +} + +void ComplexFactorGUI::setAutomatic(bool automatic) +{ + ui->automatic->setChecked(automatic); +} + +void ComplexFactorGUI::setAutomaticEnable(bool enable) +{ + ui->automatic->setEnabled(enable); +} + +void ComplexFactorGUI::setLabel(const QString& text) +{ + ui->label->setText(text); +} + +void ComplexFactorGUI::on_automatic_toggled(bool set) +{ + ui->module->setEnabled(!set); + ui->arg->setEnabled(!set); + emit automaticChanged(set); +} + +void ComplexFactorGUI::on_module_valueChanged(int value) +{ + emit moduleChanged(value / 100.0f); +} + +void ComplexFactorGUI::on_arg_valueChanged(int value) +{ + emit argumentChanged(value); +} \ No newline at end of file diff --git a/sdrgui/soapygui/complexfactorgui.h b/sdrgui/soapygui/complexfactorgui.h new file mode 100644 index 000000000..d5eb7dfdc --- /dev/null +++ b/sdrgui/soapygui/complexfactorgui.h @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +// This is an interface to an elementary GUI item used to get/set a normalized complex value from the GUI +// There is an automatic check to activate/deactivate possible automatic setting +// It is intended to be used primarily for DC offset and IQ imbalance corrections + +#ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_COMPLEXFACTORGUI_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_COMPLEXFACTORGUI_H_ + +#include +#include + +#include "export.h" + +namespace Ui { + class ComplexFactorGUI; +} + +class SDRGUI_API ComplexFactorGUI : public QWidget +{ + Q_OBJECT +public: + explicit ComplexFactorGUI(QWidget *parent = 0); + ~ComplexFactorGUI(); + + double getModule() const; + double getArgument() const; + bool getAutomatic() const; + + void setModule(double value); + void setArgument(double value); + void setAutomatic(bool automatic); + void setAutomaticEnable(bool enable); + + void setLabel(const QString& text); + +signals: + void moduleChanged(double value); + void argumentChanged(double value); + void automaticChanged(bool value); + +private slots: + void on_automatic_toggled(bool set); + void on_module_valueChanged(int value); + void on_arg_valueChanged(int value); + +private: + Ui::ComplexFactorGUI* ui; +}; + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_COMPLEXFACTORGUI_H_ */ diff --git a/sdrgui/soapygui/complexfactorgui.ui b/sdrgui/soapygui/complexfactorgui.ui new file mode 100644 index 000000000..01d5b1b3a --- /dev/null +++ b/sdrgui/soapygui/complexfactorgui.ui @@ -0,0 +1,183 @@ + + + ComplexFactorGUI + + + + 0 + 0 + 307 + 51 + + + + + 0 + 0 + + + + + 0 + 30 + + + + Form + + + + + 0 + 0 + 301 + 48 + + + + + 8 + + + + + + 40 + 0 + + + + Label + + + + + + + Qt::RightToLeft + + + Auto + + + + + + + 6 + + + 6 + + + + + + + + 32 + 0 + + + + Mod + + + + + + + Normalized module + + + -100 + + + 100 + + + 1 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + -1.00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 32 + 0 + + + + Arg + + + + + + + Argument (angle) in degrees + + + -180 + + + 180 + + + 1 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + -180 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + From 66f95bc0a66cf290fd69a28a0747be8c7d458442 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 14:35:26 +0100 Subject: [PATCH 949/956] SoapySDR support: input: auto correction GUIs (1) --- .../soapysdrinput/soapysdrinput.cpp | 28 ++++++-- .../soapysdrinput/soapysdrinput.h | 4 ++ .../soapysdrinput/soapysdrinputgui.cpp | 66 +++++++++++++++++-- .../soapysdrinput/soapysdrinputgui.h | 13 ++++ .../soapysdrinput/soapysdrinputsettings.cpp | 22 ++++--- .../soapysdrinput/soapysdrinputsettings.h | 8 ++- 6 files changed, 121 insertions(+), 20 deletions(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index 556d72687..a74f111f2 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -282,6 +282,24 @@ void SoapySDRInput::initGainSettings(SoapySDRInputSettings& settings) updateGains(m_deviceShared.m_device, m_deviceShared.m_channel, settings); } +bool SoapySDRInput::hasDCAutoCorrection() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasDCAutoCorrection; +} + +bool SoapySDRInput::hasDCCorrectionValue() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasDCOffsetValue; +} + +bool SoapySDRInput::hasIQCorrectionValue() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasIQBalanceValue; +} + void SoapySDRInput::init() { applySettings(m_settings, true); @@ -753,10 +771,10 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo xlatedDeviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; xlatedDeviceCenterFrequency = xlatedDeviceCenterFrequency < 0 ? 0 : xlatedDeviceCenterFrequency; - if ((m_settings.m_dcBlock != settings.m_dcBlock) || - (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + if ((m_settings.m_softDCCorrection != settings.m_softDCCorrection) || + (m_settings.m_softIQCorrection != settings.m_softIQCorrection) || force) { - m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); + m_deviceAPI->configureCorrections(settings.m_softDCCorrection, settings.m_softIQCorrection); } if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) @@ -1015,8 +1033,8 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo << " m_log2Decim: " << m_settings.m_log2Decim << " m_fcPos: " << m_settings.m_fcPos << " m_devSampleRate: " << m_settings.m_devSampleRate - << " m_dcBlock: " << m_settings.m_dcBlock - << " m_iqCorrection: " << m_settings.m_iqCorrection + << " m_softDCCorrection: " << m_settings.m_softDCCorrection + << " m_softIQCorrection: " << m_settings.m_softIQCorrection << " m_antenna: " << m_settings.m_antenna << " m_bandwidth: " << m_settings.m_bandwidth << " m_globalGain: " << m_settings.m_globalGain; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h index 849e5783d..d18c5194e 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -156,6 +156,10 @@ public: const std::vector& getTunableElements(); const std::vector& getIndividualGainsRanges(); void initGainSettings(SoapySDRInputSettings& settings); + bool hasDCAutoCorrection(); + bool hasDCCorrectionValue(); + bool hasIQAutoCorrection() { return false; } // not in SoapySDR interface + bool hasIQCorrectionValue(); private: DeviceSourceAPI *m_deviceAPI; diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 49caee713..b29b57fda 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -28,6 +28,7 @@ #include "soapygui/stringrangegui.h" #include "soapygui/dynamicitemsettinggui.h" #include "soapygui/intervalslidergui.h" +#include "soapygui/complexfactorgui.h" #include "ui_soapysdrinputgui.h" #include "soapysdrinputgui.h" @@ -46,7 +47,11 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleRateGUI(0), m_bandwidthGUI(0), m_gainSliderGUI(0), - m_autoGain(0) + m_autoGain(0), + m_dcCorrectionGUI(0), + m_iqCorrectionGUI(0), + m_autoDCCorrection(0), + m_autoIQCorrection(0) { m_sampleSource = (SoapySDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); ui->setupUi(this); @@ -241,6 +246,57 @@ void SoapySDRInputGui::createIndividualGainsControl(const std::vectorscrollAreaWidgetContents->layout(); + + if (m_sampleSource->hasDCCorrectionValue()) // complex GUI + { + m_dcCorrectionGUI = new ComplexFactorGUI(this); + m_dcCorrectionGUI->setLabel(QString("DC")); + m_dcCorrectionGUI->setAutomaticEnable(m_sampleSource->hasDCAutoCorrection()); + layout->addWidget(m_dcCorrectionGUI); + + connect(m_dcCorrectionGUI, SIGNAL(moduleChanged(double)), this, SLOT(dcCorrectionModuleChanged(double))); + connect(m_dcCorrectionGUI, SIGNAL(argumentChanged(double)), this, SLOT(dcCorrectionArgumentChanged(double))); + + if (m_sampleSource->hasDCAutoCorrection()) { + connect(m_dcCorrectionGUI, SIGNAL(automaticChanged(bool)), this, SLOT(autoDCCorrectionChanged(bool))); + } + } + else if (m_sampleSource->hasDCAutoCorrection()) // simple checkbox + { + m_autoDCCorrection = new QCheckBox(this); + m_autoDCCorrection->setText(QString("DC corr")); + layout->addWidget(m_autoDCCorrection); + + connect(m_autoDCCorrection, SIGNAL(toggled(bool)), this, SLOT(autoDCCorrectionChanged(bool))); + } + + if (m_sampleSource->hasIQCorrectionValue()) // complex GUI + { + m_iqCorrectionGUI = new ComplexFactorGUI(this); + m_iqCorrectionGUI->setLabel(QString("IQ")); + m_iqCorrectionGUI->setAutomaticEnable(m_sampleSource->hasIQAutoCorrection()); + layout->addWidget(m_iqCorrectionGUI); + + connect(m_iqCorrectionGUI, SIGNAL(moduleChanged(double)), this, SLOT(iqCorrectionModuleChanged(double))); + connect(m_iqCorrectionGUI, SIGNAL(argumentChanged(double)), this, SLOT(iqCorrectionArgumentChanged(double))); + + if (m_sampleSource->hasIQAutoCorrection()) { + connect(m_iqCorrectionGUI, SIGNAL(automaticChanged(bool)), this, SLOT(autoIQCorrectionChanged(bool))); + } + } + else if (m_sampleSource->hasIQAutoCorrection()) // simple checkbox + { + m_autoIQCorrection = new QCheckBox(this); + m_autoIQCorrection->setText(QString("IQ corr")); + layout->addWidget(m_autoIQCorrection); + + connect(m_autoIQCorrection, SIGNAL(toggled(bool)), this, SLOT(autoIQCorrectionChanged(bool))); + } +} + void SoapySDRInputGui::setName(const QString& name) { setObjectName(name); @@ -413,13 +469,13 @@ void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) void SoapySDRInputGui::on_dcOffset_toggled(bool checked) { - m_settings.m_dcBlock = checked; + m_settings.m_softDCCorrection = checked; sendSettings(); } void SoapySDRInputGui::on_iqImbalance_toggled(bool checked) { - m_settings.m_iqCorrection = checked; + m_settings.m_softIQCorrection = checked; sendSettings(); } @@ -506,8 +562,8 @@ void SoapySDRInputGui::displaySettings() m_autoGain->setChecked(m_settings.m_autoGain); } - ui->dcOffset->setChecked(m_settings.m_dcBlock); - ui->iqImbalance->setChecked(m_settings.m_iqCorrection); + ui->dcOffset->setChecked(m_settings.m_softDCCorrection); + ui->iqImbalance->setChecked(m_settings.m_softIQCorrection); ui->decim->setCurrentIndex(m_settings.m_log2Decim); ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 65fd3a916..9e0e6bc5e 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -31,6 +31,7 @@ class StringRangeGUI; class DynamicItemSettingGUI; class IntervalSliderGUI; class QCheckBox; +class ComplexFactorGUI; namespace Ui { class SoapySDRInputGui; @@ -65,6 +66,7 @@ private: void createTunableElementsControl(const std::vector& tunableElementsList); void createGlobalGainControl(); void createIndividualGainsControl(const std::vector& individualGainsList); + void createCorrectionsControl(); Ui::SoapySDRInputGui* ui; @@ -87,6 +89,10 @@ private: IntervalSliderGUI *m_gainSliderGUI; std::vector m_individualGainsGUIs; QCheckBox *m_autoGain; + ComplexFactorGUI *m_dcCorrectionGUI; + ComplexFactorGUI *m_iqCorrectionGUI; + QCheckBox *m_autoDCCorrection; + QCheckBox *m_autoIQCorrection; void displaySettings(); void displayTunableElementsControlSettings(); @@ -106,6 +112,13 @@ private slots: void globalGainChanged(double gain); void autoGainChanged(bool set); void individualGainChanged(QString name, double value); + void autoDCCorrectionChanged(bool set); + void autoIQCorrectionChanged(bool set); + void dcCorrectionModuleChanged(double value); + void dcCorrectionArgumentChanged(double value); + void iqCorrectionModuleChanged(double value); + void iqCorrectionArgumentChanged(double value); + void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void on_dcOffset_toggled(bool checked); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp index 6614e596c..7ae9780bf 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -32,8 +32,8 @@ void SoapySDRInputSettings::resetToDefaults() m_devSampleRate = 1024000; m_log2Decim = 0; m_fcPos = FC_POS_CENTER; - m_dcBlock = false; - m_iqCorrection = false; + m_softDCCorrection = false; + m_softIQCorrection = false; m_transverterMode = false; m_transverterDeltaFrequency = 0; m_fileRecordName = ""; @@ -41,6 +41,8 @@ void SoapySDRInputSettings::resetToDefaults() m_bandwidth = 1000000; m_globalGain = 0; m_autoGain = false; + m_autoDCCorrection = false; + m_autoIQCorrection = false; } QByteArray SoapySDRInputSettings::serialize() const @@ -50,8 +52,8 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeS32(1, m_devSampleRate); s.writeU32(2, m_log2Decim); s.writeS32(3, (int) m_fcPos); - s.writeBool(4, m_dcBlock); - s.writeBool(5, m_iqCorrection); + s.writeBool(4, m_softDCCorrection); + s.writeBool(5, m_softIQCorrection); s.writeS32(6, m_LOppmTenths); s.writeBool(7, m_transverterMode); s.writeS64(8, m_transverterDeltaFrequency); @@ -61,6 +63,8 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeS32(12, m_globalGain); s.writeBlob(13, serializeNamedElementMap(m_individualGains)); s.writeBool(14, m_autoGain); + s.writeBool(15, m_autoDCCorrection); + s.writeBool(16, m_autoIQCorrection); return s.final(); } @@ -81,12 +85,12 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) QByteArray blob; d.readS32(1, &m_devSampleRate, 1024000); - d.readU32(2, &m_log2Decim); + d.readU32(2, &m_log2Decim, 0); d.readS32(3, &intval, (int) FC_POS_CENTER); m_fcPos = (fcPos_t) intval; - d.readBool(4, &m_dcBlock); - d.readBool(5, &m_iqCorrection); - d.readS32(6, &m_LOppmTenths); + d.readBool(4, &m_softDCCorrection, false); + d.readBool(5, &m_softIQCorrection, false); + d.readS32(6, &m_LOppmTenths, 0); d.readBool(7, &m_transverterMode, false); d.readS64(8, &m_transverterDeltaFrequency, 0); d.readString(9, &m_antenna, "NONE"); @@ -97,6 +101,8 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) d.readBlob(13, &blob); deserializeNamedElementMap(blob, m_individualGains); d.readBool(14, &m_autoGain, false); + d.readBool(15, &m_autoDCCorrection, false); + d.readBool(16, &m_autoIQCorrection, false); return true; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h index 67623834c..f2df1821b 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -33,8 +33,8 @@ struct SoapySDRInputSettings { qint32 m_devSampleRate; quint32 m_log2Decim; fcPos_t m_fcPos; - bool m_dcBlock; - bool m_iqCorrection; + bool m_softDCCorrection; + bool m_softIQCorrection; bool m_transverterMode; qint64 m_transverterDeltaFrequency; QString m_fileRecordName; @@ -44,6 +44,10 @@ struct SoapySDRInputSettings { qint32 m_globalGain; QMap m_individualGains; bool m_autoGain; + bool m_autoDCCorrection; + bool m_autoIQCorrection; + std::complex m_dcCorrection; + std::complex m_iqCorrection; SoapySDRInputSettings(); void resetToDefaults(); From e5748444c54a7b4874cb30a2ce6fd87c38f16444 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 16:27:33 +0100 Subject: [PATCH 950/956] SoapySDR support: input: auto correction GUIs (2) --- .../soapysdrinput/soapysdrinputgui.cpp | 68 +++++++++++++++++++ .../soapysdrinput/soapysdrinputgui.h | 1 + sdrgui/soapygui/complexfactorgui.ui | 4 +- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index b29b57fda..2b5bf4364 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -461,6 +461,48 @@ void SoapySDRInputGui::individualGainChanged(QString name, double value) sendSettings(); } +void SoapySDRInputGui::autoDCCorrectionChanged(bool set) +{ + m_settings.m_autoDCCorrection = set; + sendSettings(); +} + +void SoapySDRInputGui::autoIQCorrectionChanged(bool set) +{ + m_settings.m_autoIQCorrection = set; + sendSettings(); +} + +void SoapySDRInputGui::dcCorrectionModuleChanged(double value) +{ + std::complex dcCorrection = std::polar(value, arg(m_settings.m_dcCorrection)); + m_settings.m_dcCorrection = dcCorrection; + sendSettings(); +} + +void SoapySDRInputGui::dcCorrectionArgumentChanged(double value) +{ + double angleInRadians = (value / 180.0) * M_PI; + std::complex dcCorrection = std::polar(abs(m_settings.m_dcCorrection), angleInRadians); + m_settings.m_dcCorrection = dcCorrection; + sendSettings(); +} + +void SoapySDRInputGui::iqCorrectionModuleChanged(double value) +{ + std::complex iqCorrection = std::polar(value, arg(m_settings.m_iqCorrection)); + m_settings.m_iqCorrection = iqCorrection; + sendSettings(); +} + +void SoapySDRInputGui::iqCorrectionArgumentChanged(double value) +{ + double angleInRadians = (value / 180.0) * M_PI; + std::complex iqCorrection = std::polar(abs(m_settings.m_iqCorrection), angleInRadians); + m_settings.m_iqCorrection = iqCorrection; + sendSettings(); +} + void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; @@ -573,6 +615,7 @@ void SoapySDRInputGui::displaySettings() displayTunableElementsControlSettings(); displayIndividualGainsControlSettings(); + displayCorrectionsSettings(); blockApplySettings(false); } @@ -601,6 +644,31 @@ void SoapySDRInputGui::displayIndividualGainsControlSettings() } } +void SoapySDRInputGui::displayCorrectionsSettings() +{ + if (m_dcCorrectionGUI) + { + m_dcCorrectionGUI->setAutomatic(m_settings.m_autoDCCorrection); + m_dcCorrectionGUI->setModule(abs(m_settings.m_dcCorrection)); + m_dcCorrectionGUI->setArgument(arg(m_settings.m_dcCorrection)*(180.0/M_PI)); + } + + if (m_iqCorrectionGUI) + { + m_iqCorrectionGUI->setAutomatic(m_settings.m_autoIQCorrection); + m_iqCorrectionGUI->setModule(abs(m_settings.m_iqCorrection)); + m_iqCorrectionGUI->setArgument(arg(m_settings.m_iqCorrection)*(180.0/M_PI)); + } + + if (m_autoDCCorrection) { + m_autoDCCorrection->setChecked(m_settings.m_autoDCCorrection); + } + + if (m_autoIQCorrection) { + m_autoIQCorrection->setChecked(m_settings.m_autoIQCorrection); + } +} + void SoapySDRInputGui::sendSettings() { if (!m_updateTimer.isActive()) { diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h index 9e0e6bc5e..7f65de91a 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -97,6 +97,7 @@ private: void displaySettings(); void displayTunableElementsControlSettings(); void displayIndividualGainsControlSettings(); + void displayCorrectionsSettings(); void sendSettings(); void updateSampleRateAndFrequency(); void updateFrequencyLimits(); diff --git a/sdrgui/soapygui/complexfactorgui.ui b/sdrgui/soapygui/complexfactorgui.ui index 01d5b1b3a..37d3d9175 100644 --- a/sdrgui/soapygui/complexfactorgui.ui +++ b/sdrgui/soapygui/complexfactorgui.ui @@ -90,7 +90,7 @@ Normalized module - -100 + 0 100 @@ -112,7 +112,7 @@ - -1.00 + 1.00 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter From 90de728990021af969f2999647ece9ae29d8be19 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 16:53:15 +0100 Subject: [PATCH 951/956] SoapySDR support: input: auto correction GUIs (3) --- .../soapysdrinput/soapysdrinput.cpp | 48 +++++++++++++++++++ .../soapysdrinput/soapysdrinputsettings.cpp | 13 +++++ .../soapysdrinput/soapysdrinputsettings.h | 4 +- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index a74f111f2..fa591af87 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -973,6 +973,54 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo } } + if ((m_settings.m_autoDCCorrection != settings.m_autoDCCorrection) || force) + { + if ((dev != 0) && hasDCAutoCorrection()) + { + try + { + dev->setDCOffsetMode(SOAPY_SDR_RX, requestedChannel, settings.m_autoDCCorrection); + qDebug("SoapySDRInput::applySettings: %s DC auto correction", settings.m_autoGain ? "set" : "unset"); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot %s DC auto correction", settings.m_autoGain ? "set" : "unset"); + } + } + } + + if ((m_settings.m_dcCorrection != settings.m_dcCorrection) || force) + { + if ((dev != 0) && hasDCCorrectionValue()) + { + try + { + dev->setDCOffset(SOAPY_SDR_RX, requestedChannel, settings.m_dcCorrection); + qDebug("SoapySDRInput::applySettings: DC offset correction set to (%lf, %lf)", settings.m_dcCorrection.real(), settings.m_dcCorrection.imag()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set DC offset correction to (%lf, %lf)", settings.m_dcCorrection.real(), settings.m_dcCorrection.imag()); + } + } + } + + if ((m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + if ((dev != 0) && hasIQCorrectionValue()) + { + try + { + dev->setIQBalance(SOAPY_SDR_RX, requestedChannel, settings.m_iqCorrection); + qDebug("SoapySDRInput::applySettings: IQ balance correction set to (%lf, %lf)", settings.m_iqCorrection.real(), settings.m_iqCorrection.imag()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set IQ balance correction to (%lf, %lf)", settings.m_iqCorrection.real(), settings.m_iqCorrection.imag()); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<{0,0}; + m_iqCorrection = std::complex{0,0}; } QByteArray SoapySDRInputSettings::serialize() const @@ -65,6 +67,10 @@ QByteArray SoapySDRInputSettings::serialize() const s.writeBool(14, m_autoGain); s.writeBool(15, m_autoDCCorrection); s.writeBool(16, m_autoIQCorrection); + s.writeDouble(17, m_dcCorrection.real()); + s.writeDouble(18, m_dcCorrection.imag()); + s.writeDouble(19, m_iqCorrection.real()); + s.writeDouble(20, m_iqCorrection.imag()); return s.final(); } @@ -83,6 +89,7 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) { int intval; QByteArray blob; + double realval, imagval; d.readS32(1, &m_devSampleRate, 1024000); d.readU32(2, &m_log2Decim, 0); @@ -103,6 +110,12 @@ bool SoapySDRInputSettings::deserialize(const QByteArray& data) d.readBool(14, &m_autoGain, false); d.readBool(15, &m_autoDCCorrection, false); d.readBool(16, &m_autoIQCorrection, false); + d.readDouble(17, &realval, 0); + d.readDouble(18, &imagval, 0); + m_dcCorrection = std::complex{realval, imagval}; + d.readDouble(19, &realval, 0); + d.readDouble(20, &imagval, 0); + m_iqCorrection = std::complex{realval, imagval}; return true; } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h index f2df1821b..4bbb2db47 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -46,8 +46,8 @@ struct SoapySDRInputSettings { bool m_autoGain; bool m_autoDCCorrection; bool m_autoIQCorrection; - std::complex m_dcCorrection; - std::complex m_iqCorrection; + std::complex m_dcCorrection; + std::complex m_iqCorrection; SoapySDRInputSettings(); void resetToDefaults(); From fd3ea0711dbcb58068c3615c4242d6bff43e3695 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 19:01:13 +0100 Subject: [PATCH 952/956] Windows: experimental MSVC2017 toolchain (1) --- fcdlib/fcdlib.pro | 2 ++ mbelib/mbelib.pro | 1 + 2 files changed, 3 insertions(+) diff --git a/fcdlib/fcdlib.pro b/fcdlib/fcdlib.pro index e75af59bf..e69ae264a 100644 --- a/fcdlib/fcdlib.pro +++ b/fcdlib/fcdlib.pro @@ -11,6 +11,7 @@ TARGET = fcdlib CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" SOURCES = $$PWD/fcdtraits.cpp\ $$PWD/fcdproplusconst.cpp\ @@ -22,3 +23,4 @@ HEADERS = $$PWD/fcdtraits.h\ CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 diff --git a/mbelib/mbelib.pro b/mbelib/mbelib.pro index 1714145f5..9fa45a02b 100644 --- a/mbelib/mbelib.pro +++ b/mbelib/mbelib.pro @@ -11,6 +11,7 @@ TARGET = mbelib CONFIG(MINGW32):LIBMBELIBSRC = "C:\softs\mbelib" CONFIG(MINGW64):LIBMBELIBSRC = "C:\softs\mbelib" +CONFIG(MSVC):LIBMBELIBSRC = "C:\softs\mbelib" CONFIG(macx):LIBMBELIBSRC = "../../deps/mbelib" INCLUDEPATH += $$LIBMBELIBSRC From 1168c18e3afbeadc3acd91d8fe88b3954294503b Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 8 Nov 2018 21:42:49 +0100 Subject: [PATCH 953/956] SoapySDR support: output: auto correction GUIs --- .../soapysdroutput/soapysdroutput.cpp | 66 +++++++++ .../soapysdroutput/soapysdroutput.h | 4 + .../soapysdroutput/soapysdroutputgui.cpp | 127 +++++++++++++++++- .../soapysdroutput/soapysdroutputgui.h | 15 +++ .../soapysdroutput/soapysdroutputsettings.cpp | 25 +++- .../soapysdroutput/soapysdroutputsettings.h | 4 + .../soapysdrinput/soapysdrinput.cpp | 4 +- .../soapysdrinput/soapysdrinputgui.cpp | 1 + 8 files changed, 240 insertions(+), 6 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp index e8eb759c8..d3642adc4 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -250,6 +250,24 @@ void SoapySDROutput::initGainSettings(SoapySDROutputSettings& settings) updateGains(m_deviceShared.m_device, m_deviceShared.m_channel, settings); } +bool SoapySDROutput::hasDCAutoCorrection() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasDCAutoCorrection; +} + +bool SoapySDROutput::hasDCCorrectionValue() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasDCOffsetValue; +} + +bool SoapySDROutput::hasIQCorrectionValue() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasIQBalanceValue; +} + void SoapySDROutput::init() { applySettings(m_settings, true); @@ -909,6 +927,54 @@ bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool } } + if ((m_settings.m_autoDCCorrection != settings.m_autoDCCorrection) || force) + { + if ((dev != 0) && hasDCAutoCorrection()) + { + try + { + dev->setDCOffsetMode(SOAPY_SDR_TX, requestedChannel, settings.m_autoDCCorrection); + qDebug("SoapySDROutput::applySettings: %s DC auto correction", settings.m_autoDCCorrection ? "set" : "unset"); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot %s DC auto correction", settings.m_autoDCCorrection ? "set" : "unset"); + } + } + } + + if ((m_settings.m_dcCorrection != settings.m_dcCorrection) || force) + { + if ((dev != 0) && hasDCCorrectionValue()) + { + try + { + dev->setDCOffset(SOAPY_SDR_TX, requestedChannel, settings.m_dcCorrection); + qDebug("SoapySDROutput::applySettings: DC offset correction set to (%lf, %lf)", settings.m_dcCorrection.real(), settings.m_dcCorrection.imag()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set DC offset correction to (%lf, %lf)", settings.m_dcCorrection.real(), settings.m_dcCorrection.imag()); + } + } + } + + if ((m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + if ((dev != 0) && hasIQCorrectionValue()) + { + try + { + dev->setIQBalance(SOAPY_SDR_TX, requestedChannel, settings.m_iqCorrection); + qDebug("SoapySDROutput::applySettings: IQ balance correction set to (%lf, %lf)", settings.m_iqCorrection.real(), settings.m_iqCorrection.imag()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set IQ balance correction to (%lf, %lf)", settings.m_iqCorrection.real(), settings.m_iqCorrection.imag()); + } + } + } + if (forwardChangeOwnDSP) { int sampleRate = settings.m_devSampleRate/(1<& getTunableElements(); const std::vector& getIndividualGainsRanges(); void initGainSettings(SoapySDROutputSettings& settings); + bool hasDCAutoCorrection(); + bool hasDCCorrectionValue(); + bool hasIQAutoCorrection() { return false; } // not in SoapySDR interface + bool hasIQCorrectionValue(); private: DeviceSinkAPI *m_deviceAPI; diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index 38b745e1d..b57f4b56b 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -29,6 +29,7 @@ #include "soapygui/stringrangegui.h" #include "soapygui/dynamicitemsettinggui.h" #include "soapygui/intervalslidergui.h" +#include "soapygui/complexfactorgui.h" #include "soapysdroutputgui.h" @@ -45,7 +46,11 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) m_sampleRateGUI(0), m_bandwidthGUI(0), m_gainSliderGUI(0), - m_autoGain(0) + m_autoGain(0), + m_dcCorrectionGUI(0), + m_iqCorrectionGUI(0), + m_autoDCCorrection(0), + m_autoIQCorrection(0) { m_sampleSink = (SoapySDROutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); ui->setupUi(this); @@ -55,6 +60,7 @@ SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) m_sampleSink->getFrequencyRange(f_min, f_max); ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + createCorrectionsControl(); createAntennasControl(m_sampleSink->getAntennas()); createRangesControl(&m_sampleRateGUI, m_sampleSink->getRateRanges(), "SR", "S/s"); createRangesControl(&m_bandwidthGUI, m_sampleSink->getBandwidthRanges(), "BW", "Hz"); @@ -240,6 +246,57 @@ void SoapySDROutputGui::createIndividualGainsControl(const std::vectorscrollAreaWidgetContents->layout(); + + if (m_sampleSink->hasDCCorrectionValue()) // complex GUI + { + m_dcCorrectionGUI = new ComplexFactorGUI(this); + m_dcCorrectionGUI->setLabel(QString("DC")); + m_dcCorrectionGUI->setAutomaticEnable(m_sampleSink->hasDCAutoCorrection()); + layout->addWidget(m_dcCorrectionGUI); + + connect(m_dcCorrectionGUI, SIGNAL(moduleChanged(double)), this, SLOT(dcCorrectionModuleChanged(double))); + connect(m_dcCorrectionGUI, SIGNAL(argumentChanged(double)), this, SLOT(dcCorrectionArgumentChanged(double))); + + if (m_sampleSink->hasDCAutoCorrection()) { + connect(m_dcCorrectionGUI, SIGNAL(automaticChanged(bool)), this, SLOT(autoDCCorrectionChanged(bool))); + } + } + else if (m_sampleSink->hasDCAutoCorrection()) // simple checkbox + { + m_autoDCCorrection = new QCheckBox(this); + m_autoDCCorrection->setText(QString("DC corr")); + layout->addWidget(m_autoDCCorrection); + + connect(m_autoDCCorrection, SIGNAL(toggled(bool)), this, SLOT(autoDCCorrectionChanged(bool))); + } + + if (m_sampleSink->hasIQCorrectionValue()) // complex GUI + { + m_iqCorrectionGUI = new ComplexFactorGUI(this); + m_iqCorrectionGUI->setLabel(QString("IQ")); + m_iqCorrectionGUI->setAutomaticEnable(m_sampleSink->hasIQAutoCorrection()); + layout->addWidget(m_iqCorrectionGUI); + + connect(m_iqCorrectionGUI, SIGNAL(moduleChanged(double)), this, SLOT(iqCorrectionModuleChanged(double))); + connect(m_iqCorrectionGUI, SIGNAL(argumentChanged(double)), this, SLOT(iqCorrectionArgumentChanged(double))); + + if (m_sampleSink->hasIQAutoCorrection()) { + connect(m_iqCorrectionGUI, SIGNAL(automaticChanged(bool)), this, SLOT(autoIQCorrectionChanged(bool))); + } + } + else if (m_sampleSink->hasIQAutoCorrection()) // simple checkbox + { + m_autoIQCorrection = new QCheckBox(this); + m_autoIQCorrection->setText(QString("IQ corr")); + layout->addWidget(m_autoIQCorrection); + + connect(m_autoIQCorrection, SIGNAL(toggled(bool)), this, SLOT(autoIQCorrectionChanged(bool))); + } +} + void SoapySDROutputGui::setName(const QString& name) { setObjectName(name); @@ -404,6 +461,48 @@ void SoapySDROutputGui::individualGainChanged(QString name, double value) sendSettings(); } +void SoapySDROutputGui::autoDCCorrectionChanged(bool set) +{ + m_settings.m_autoDCCorrection = set; + sendSettings(); +} + +void SoapySDROutputGui::autoIQCorrectionChanged(bool set) +{ + m_settings.m_autoIQCorrection = set; + sendSettings(); +} + +void SoapySDROutputGui::dcCorrectionModuleChanged(double value) +{ + std::complex dcCorrection = std::polar(value, arg(m_settings.m_dcCorrection)); + m_settings.m_dcCorrection = dcCorrection; + sendSettings(); +} + +void SoapySDROutputGui::dcCorrectionArgumentChanged(double value) +{ + double angleInRadians = (value / 180.0) * M_PI; + std::complex dcCorrection = std::polar(abs(m_settings.m_dcCorrection), angleInRadians); + m_settings.m_dcCorrection = dcCorrection; + sendSettings(); +} + +void SoapySDROutputGui::iqCorrectionModuleChanged(double value) +{ + std::complex iqCorrection = std::polar(value, arg(m_settings.m_iqCorrection)); + m_settings.m_iqCorrection = iqCorrection; + sendSettings(); +} + +void SoapySDROutputGui::iqCorrectionArgumentChanged(double value) +{ + double angleInRadians = (value / 180.0) * M_PI; + std::complex iqCorrection = std::polar(abs(m_settings.m_iqCorrection), angleInRadians); + m_settings.m_iqCorrection = iqCorrection; + sendSettings(); +} + void SoapySDROutputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; @@ -473,6 +572,7 @@ void SoapySDROutputGui::displaySettings() displayTunableElementsControlSettings(); displayIndividualGainsControlSettings(); + displayCorrectionsSettings(); blockApplySettings(false); } @@ -501,6 +601,31 @@ void SoapySDROutputGui::displayIndividualGainsControlSettings() } } +void SoapySDROutputGui::displayCorrectionsSettings() +{ + if (m_dcCorrectionGUI) + { + m_dcCorrectionGUI->setAutomatic(m_settings.m_autoDCCorrection); + m_dcCorrectionGUI->setModule(abs(m_settings.m_dcCorrection)); + m_dcCorrectionGUI->setArgument(arg(m_settings.m_dcCorrection)*(180.0/M_PI)); + } + + if (m_iqCorrectionGUI) + { + m_iqCorrectionGUI->setAutomatic(m_settings.m_autoIQCorrection); + m_iqCorrectionGUI->setModule(abs(m_settings.m_iqCorrection)); + m_iqCorrectionGUI->setArgument(arg(m_settings.m_iqCorrection)*(180.0/M_PI)); + } + + if (m_autoDCCorrection) { + m_autoDCCorrection->setChecked(m_settings.m_autoDCCorrection); + } + + if (m_autoIQCorrection) { + m_autoIQCorrection->setChecked(m_settings.m_autoIQCorrection); + } +} + void SoapySDROutputGui::sendSettings() { if (!m_updateTimer.isActive()) { diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h index c108fa837..1ac196323 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -33,6 +33,7 @@ class StringRangeGUI; class DynamicItemSettingGUI; class IntervalSliderGUI; class QCheckBox; +class ComplexFactorGUI; namespace Ui { class SoapySDROutputGui; @@ -67,6 +68,7 @@ private: void createTunableElementsControl(const std::vector& tunableElementsList); void createGlobalGainControl(); void createIndividualGainsControl(const std::vector& individualGainsList); + void createCorrectionsControl(); Ui::SoapySDROutputGui* ui; @@ -89,11 +91,16 @@ private: IntervalSliderGUI *m_gainSliderGUI; std::vector m_individualGainsGUIs; QCheckBox *m_autoGain; + ComplexFactorGUI *m_dcCorrectionGUI; + ComplexFactorGUI *m_iqCorrectionGUI; + QCheckBox *m_autoDCCorrection; + QCheckBox *m_autoIQCorrection; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); void displayTunableElementsControlSettings(); void displayIndividualGainsControlSettings(); + void displayCorrectionsSettings(); void sendSettings(); void updateSampleRateAndFrequency(); void updateFrequencyLimits(); @@ -101,6 +108,7 @@ private: private slots: void handleInputMessages(); + void antennasChanged(); void sampleRateChanged(double sampleRate); void bandwidthChanged(double bandwidth); @@ -108,6 +116,13 @@ private slots: void globalGainChanged(double gain); void autoGainChanged(bool set); void individualGainChanged(QString name, double value); + void autoDCCorrectionChanged(bool set); + void autoIQCorrectionChanged(bool set); + void dcCorrectionModuleChanged(double value); + void dcCorrectionArgumentChanged(double value); + void iqCorrectionModuleChanged(double value); + void iqCorrectionArgumentChanged(double value); + void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); void on_interp_currentIndexChanged(int index); diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp index 2be673c1e..9abc87b96 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -38,6 +38,10 @@ void SoapySDROutputSettings::resetToDefaults() m_bandwidth = 1000000; m_globalGain = 0; m_autoGain = false; + m_autoDCCorrection = false; + m_autoIQCorrection = false; + m_dcCorrection = std::complex{0,0}; + m_iqCorrection = std::complex{0,0}; } QByteArray SoapySDROutputSettings::serialize() const @@ -55,6 +59,12 @@ QByteArray SoapySDROutputSettings::serialize() const s.writeS32(12, m_globalGain); s.writeBlob(13, serializeNamedElementMap(m_individualGains)); s.writeBool(14, m_autoGain); + s.writeBool(15, m_autoDCCorrection); + s.writeBool(16, m_autoIQCorrection); + s.writeDouble(17, m_dcCorrection.real()); + s.writeDouble(18, m_dcCorrection.imag()); + s.writeDouble(19, m_iqCorrection.real()); + s.writeDouble(20, m_iqCorrection.imag()); return s.final(); } @@ -72,10 +82,11 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { QByteArray blob; + double realval, imagval; - d.readS32(1, &m_devSampleRate); - d.readS32(2, &m_LOppmTenths); - d.readU32(3, &m_log2Interp); + d.readS32(1, &m_devSampleRate, 1024000); + d.readS32(2, &m_LOppmTenths, 0); + d.readU32(3, &m_log2Interp, 0); d.readBool(4, &m_transverterMode, false); d.readS64(5, &m_transverterDeltaFrequency, 0); d.readString(6, &m_antenna, "NONE"); @@ -86,6 +97,14 @@ bool SoapySDROutputSettings::deserialize(const QByteArray& data) d.readBlob(13, &blob); deserializeNamedElementMap(blob, m_individualGains); d.readBool(14, &m_autoGain, false); + d.readBool(15, &m_autoDCCorrection, false); + d.readBool(16, &m_autoIQCorrection, false); + d.readDouble(17, &realval, 0); + d.readDouble(18, &imagval, 0); + m_dcCorrection = std::complex{realval, imagval}; + d.readDouble(19, &realval, 0); + d.readDouble(20, &imagval, 0); + m_iqCorrection = std::complex{realval, imagval}; return true; } diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h index e5ee04b83..d0cf8f448 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -33,6 +33,10 @@ struct SoapySDROutputSettings { qint32 m_globalGain; QMap m_individualGains; bool m_autoGain; + bool m_autoDCCorrection; + bool m_autoIQCorrection; + std::complex m_dcCorrection; + std::complex m_iqCorrection; SoapySDROutputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index fa591af87..198c9d822 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -980,11 +980,11 @@ bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool fo try { dev->setDCOffsetMode(SOAPY_SDR_RX, requestedChannel, settings.m_autoDCCorrection); - qDebug("SoapySDRInput::applySettings: %s DC auto correction", settings.m_autoGain ? "set" : "unset"); + qDebug("SoapySDRInput::applySettings: %s DC auto correction", settings.m_autoDCCorrection ? "set" : "unset"); } catch (const std::exception &ex) { - qCritical("SoapySDRInput::applySettings: cannot %s DC auto correction", settings.m_autoGain ? "set" : "unset"); + qCritical("SoapySDRInput::applySettings: cannot %s DC auto correction", settings.m_autoDCCorrection ? "set" : "unset"); } } } diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 2b5bf4364..6218f6b54 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -61,6 +61,7 @@ SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleSource->getFrequencyRange(f_min, f_max); ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + createCorrectionsControl(); createAntennasControl(m_sampleSource->getAntennas()); createRangesControl(&m_sampleRateGUI, m_sampleSource->getRateRanges(), "SR", "S/s"); createRangesControl(&m_bandwidthGUI, m_sampleSource->getBandwidthRanges(), "BW", "Hz"); From e1ac6c2caab81ad98bb4bba34e9adb57748807a0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 9 Nov 2018 00:31:20 +0100 Subject: [PATCH 954/956] SoapySDR support: fixed createTunableElementsControl --- plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp | 6 +++++- plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index b57f4b56b..c37dfacf8 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -159,7 +159,7 @@ void SoapySDROutputGui::createAntennasControl(const std::vector& an } m_antennas = new StringRangeGUI(this); - m_antennas->setLabel(QString("Antenna")); + m_antennas->setLabel(QString("RF out")); m_antennas->setUnits(QString("Port")); for (const auto &it : antennaList) { @@ -182,6 +182,10 @@ void SoapySDROutputGui::createTunableElementsControl(const std::vectorm_ranges.size() == 0) { // skip empty ranges lists + continue; + } + ItemSettingGUI *rangeGUI; createRangesControl( &rangeGUI, diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 6218f6b54..6aea2e265 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -160,7 +160,7 @@ void SoapySDRInputGui::createAntennasControl(const std::vector& ant } m_antennas = new StringRangeGUI(this); - m_antennas->setLabel(QString("Antenna")); + m_antennas->setLabel(QString("RF in")); m_antennas->setUnits(QString("Port")); for (const auto &it : antennaList) { @@ -183,6 +183,10 @@ void SoapySDRInputGui::createTunableElementsControl(const std::vectorm_ranges.size() == 0) { // skip empty ranges lists + continue; + } + ItemSettingGUI *rangeGUI; createRangesControl( &rangeGUI, From 4d3113eabc7f63b42dc8390ca83430488c475fb1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 9 Nov 2018 00:48:42 +0100 Subject: [PATCH 955/956] SoapySDR support: cosmetic changes --- .../samplesink/soapysdroutput/soapysdroutputgui.cpp | 6 ++++-- .../samplesource/soapysdrinput/soapysdrinputgui.cpp | 12 ++++++++---- sdrgui/soapygui/complexfactorgui.cpp | 7 ++++++- sdrgui/soapygui/complexfactorgui.h | 1 + 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp index c37dfacf8..dbee2b4d4 100644 --- a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -271,7 +271,8 @@ void SoapySDROutputGui::createCorrectionsControl() else if (m_sampleSink->hasDCAutoCorrection()) // simple checkbox { m_autoDCCorrection = new QCheckBox(this); - m_autoDCCorrection->setText(QString("DC corr")); + m_autoDCCorrection->setText(QString("Auto DC corr")); + m_autoDCCorrection->setToolTip(QString("Automatic hardware DC offset correction")); layout->addWidget(m_autoDCCorrection); connect(m_autoDCCorrection, SIGNAL(toggled(bool)), this, SLOT(autoDCCorrectionChanged(bool))); @@ -294,7 +295,8 @@ void SoapySDROutputGui::createCorrectionsControl() else if (m_sampleSink->hasIQAutoCorrection()) // simple checkbox { m_autoIQCorrection = new QCheckBox(this); - m_autoIQCorrection->setText(QString("IQ corr")); + m_autoIQCorrection->setText(QString("Auto IQ corr")); + m_autoIQCorrection->setToolTip(QString("Automatic hardware IQ imbalance correction")); layout->addWidget(m_autoIQCorrection); connect(m_autoIQCorrection, SIGNAL(toggled(bool)), this, SLOT(autoIQCorrectionChanged(bool))); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp index 6aea2e265..b8c387d76 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -258,7 +258,8 @@ void SoapySDRInputGui::createCorrectionsControl() if (m_sampleSource->hasDCCorrectionValue()) // complex GUI { m_dcCorrectionGUI = new ComplexFactorGUI(this); - m_dcCorrectionGUI->setLabel(QString("DC")); + m_dcCorrectionGUI->setLabel(QString("DC corr")); + m_dcCorrectionGUI->setToolTip(QString("Hardware DC offset correction")); m_dcCorrectionGUI->setAutomaticEnable(m_sampleSource->hasDCAutoCorrection()); layout->addWidget(m_dcCorrectionGUI); @@ -272,7 +273,8 @@ void SoapySDRInputGui::createCorrectionsControl() else if (m_sampleSource->hasDCAutoCorrection()) // simple checkbox { m_autoDCCorrection = new QCheckBox(this); - m_autoDCCorrection->setText(QString("DC corr")); + m_autoDCCorrection->setText(QString("Auto DC corr")); + m_autoDCCorrection->setToolTip(QString("Automatic hardware DC offset correction")); layout->addWidget(m_autoDCCorrection); connect(m_autoDCCorrection, SIGNAL(toggled(bool)), this, SLOT(autoDCCorrectionChanged(bool))); @@ -281,7 +283,8 @@ void SoapySDRInputGui::createCorrectionsControl() if (m_sampleSource->hasIQCorrectionValue()) // complex GUI { m_iqCorrectionGUI = new ComplexFactorGUI(this); - m_iqCorrectionGUI->setLabel(QString("IQ")); + m_iqCorrectionGUI->setLabel(QString("IQ corr")); + m_iqCorrectionGUI->setToolTip(QString("Hardware IQ imbalance correction")); m_iqCorrectionGUI->setAutomaticEnable(m_sampleSource->hasIQAutoCorrection()); layout->addWidget(m_iqCorrectionGUI); @@ -295,7 +298,8 @@ void SoapySDRInputGui::createCorrectionsControl() else if (m_sampleSource->hasIQAutoCorrection()) // simple checkbox { m_autoIQCorrection = new QCheckBox(this); - m_autoIQCorrection->setText(QString("IQ corr")); + m_autoIQCorrection->setText(QString("Auto IQ corr")); + m_autoIQCorrection->setToolTip(QString("Automatic hardware IQ imbalance correction")); layout->addWidget(m_autoIQCorrection); connect(m_autoIQCorrection, SIGNAL(toggled(bool)), this, SLOT(autoIQCorrectionChanged(bool))); diff --git a/sdrgui/soapygui/complexfactorgui.cpp b/sdrgui/soapygui/complexfactorgui.cpp index c83920301..57edb4e83 100644 --- a/sdrgui/soapygui/complexfactorgui.cpp +++ b/sdrgui/soapygui/complexfactorgui.cpp @@ -70,6 +70,11 @@ void ComplexFactorGUI::setLabel(const QString& text) ui->label->setText(text); } +void ComplexFactorGUI::setToolTip(const QString& text) +{ + ui->label->setToolTip(text); +} + void ComplexFactorGUI::on_automatic_toggled(bool set) { ui->module->setEnabled(!set); @@ -85,4 +90,4 @@ void ComplexFactorGUI::on_module_valueChanged(int value) void ComplexFactorGUI::on_arg_valueChanged(int value) { emit argumentChanged(value); -} \ No newline at end of file +} diff --git a/sdrgui/soapygui/complexfactorgui.h b/sdrgui/soapygui/complexfactorgui.h index d5eb7dfdc..894479d24 100644 --- a/sdrgui/soapygui/complexfactorgui.h +++ b/sdrgui/soapygui/complexfactorgui.h @@ -47,6 +47,7 @@ public: void setAutomaticEnable(bool enable); void setLabel(const QString& text); + void setToolTip(const QString& text); signals: void moduleChanged(double value); From 97ee2cd5beee0f97608c932c83e359643e872713 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 9 Nov 2018 19:06:23 +0100 Subject: [PATCH 956/956] Windows: experimental MSVC2017 toolchain (2) --- devices/devices.pro | 160 +++++++++++++++++-------- devices/hackrf/devicehackrf.h | 4 + libairspy/libairspy.pro | 8 ++ libairspyhf/libairspyhf.pro | 6 + libhackrf/libhackrf.pro | 6 + librtlsdr/librtlsdr.pro | 4 + logging/logging.pro | 2 + plugins/samplesource/rtlsdr/rtlsdr.pro | 3 + qrtplib/qrtplib.pro | 2 + sdrangel.windows.pro | 10 +- sdrbase/channel/channelsinkapi.h | 19 +++ sdrbase/sdrbase.pro | 2 +- swagger/swagger.pro | 2 + 13 files changed, 172 insertions(+), 56 deletions(-) diff --git a/devices/devices.pro b/devices/devices.pro index 2d5037f8d..0199ff713 100644 --- a/devices/devices.pro +++ b/devices/devices.pro @@ -18,15 +18,22 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 macx:QMAKE_LFLAGS += -F/Library/Frameworks +CONFIG(MSVC):DEFINES += devices_EXPORTS + CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" + CONFIG(macx):LIBHACKRFSRC = "/opt/local/include" CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MSVC):LIBHACKRFSRC = "C:\softs\hackrf\host" + CONFIG(macx):LIBLIMESUITESRC = "../../../LimeSuite-17.12.0" CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" + CONFIG(MINGW32):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" + CONFIG(macx):LIBIIOSRC = "../../../libiio" CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" @@ -34,82 +41,135 @@ CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" INCLUDEPATH += $$PWD INCLUDEPATH += ../exports INCLUDEPATH += ../sdrbase -INCLUDEPATH += $$LIBBLADERF/include -INCLUDEPATH += $$LIBHACKRFSRC INCLUDEPATH += "C:\softs\boost_1_66_0" INCLUDEPATH += "C:\softs\libusb-1.0.22\include" -INCLUDEPATH += ../liblimesuite/srcmw -INCLUDEPATH += $$LIBLIMESUITESRC/src -INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 -INCLUDEPATH += $$LIBLIMESUITESRC/src/ConnectionRegistry -INCLUDEPATH += $$LIBLIMESUITESRC/src/FPGA_common -INCLUDEPATH += $$LIBLIMESUITESRC/src/GFIR -INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m -INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m_mcu -INCLUDEPATH += $$LIBLIMESUITESRC/src/Si5351C -INCLUDEPATH += $$LIBLIMESUITESRC/src/protocols -INCLUDEPATH += $$LIBLIMESUITESRC/external/cpp-feather-ini-parser +INCLUDEPATH += $$LIBBLADERF/include +INCLUDEPATH += $$LIBHACKRFSRC + +MINGW32 || MINGW64 || macx { + INCLUDEPATH += ../liblimesuite/srcmw + INCLUDEPATH += $$LIBLIMESUITESRC/src + INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 + INCLUDEPATH += $$LIBLIMESUITESRC/src/ConnectionRegistry + INCLUDEPATH += $$LIBLIMESUITESRC/src/FPGA_common + INCLUDEPATH += $$LIBLIMESUITESRC/src/GFIR + INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m + INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m_mcu + INCLUDEPATH += $$LIBLIMESUITESRC/src/Si5351C + INCLUDEPATH += $$LIBLIMESUITESRC/src/protocols + INCLUDEPATH += $$LIBLIMESUITESRC/external/cpp-feather-ini-parser +} + +MSVC { + INCLUDEPATH += "C:\softs\PothosSDR\include" +} + INCLUDEPATH += $$LIBPERSEUSSRC !macx:INCLUDEPATH += $$LIBIIOSRC CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -!macx:SOURCES += bladerf1/devicebladerf1.cpp\ - bladerf1/devicebladerf1values.cpp\ - bladerf1/devicebladerf1shared.cpp +MINGW32 || MINGW64 { + SOURCES += bladerf1/devicebladerf1.cpp\ + bladerf1/devicebladerf1values.cpp\ + bladerf1/devicebladerf1shared.cpp -!macx:SOURCES += bladerf2/devicebladerf2.cpp\ - bladerf2/devicebladerf2shared.cpp + SOURCES += bladerf2/devicebladerf2.cpp\ + bladerf2/devicebladerf2shared.cpp -SOURCES += hackrf/devicehackrf.cpp\ - hackrf/devicehackrfvalues.cpp\ - hackrf/devicehackrfshared.cpp + SOURCES += hackrf/devicehackrf.cpp\ + hackrf/devicehackrfvalues.cpp\ + hackrf/devicehackrfshared.cpp -SOURCES += limesdr/devicelimesdr.cpp\ - limesdr/devicelimesdrparam.cpp\ - limesdr/devicelimesdrshared.cpp + SOURCES += limesdr/devicelimesdr.cpp\ + limesdr/devicelimesdrparam.cpp\ + limesdr/devicelimesdrshared.cpp -!macx:SOURCES += plutosdr/deviceplutosdr.cpp\ - plutosdr/deviceplutosdrbox.cpp\ - plutosdr/deviceplutosdrparams.cpp\ - plutosdr/deviceplutosdrscan.cpp\ - plutosdr/deviceplutosdrshared.cpp + SOURCES += plutosdr/deviceplutosdr.cpp\ + plutosdr/deviceplutosdrbox.cpp\ + plutosdr/deviceplutosdrparams.cpp\ + plutosdr/deviceplutosdrscan.cpp\ + plutosdr/deviceplutosdrshared.cpp -!macx:HEADERS += bladerf2/devicebladerf2.h\ - bladerf2/devicebladerf2shared.h + HEADERS += bladerf2/devicebladerf2.h\ + bladerf2/devicebladerf2shared.h -!macx:HEADERS += bladerf1/devicebladerf1.h\ - bladerf1/devicebladerf1param.h\ - bladerf1/devicebladerf1values.h\ - bladerf1/devicebladerf1shared.h + HEADERS += bladerf1/devicebladerf1.h\ + bladerf1/devicebladerf1param.h\ + bladerf1/devicebladerf1values.h\ + bladerf1/devicebladerf1shared.h -HEADERS += hackrf/devicehackrf.h\ - hackrf/devicehackrfparam.h\ - hackrf/devicehackrfvalues.h\ - hackrf/devicehackrfshared.h + HEADERS += hackrf/devicehackrf.h\ + hackrf/devicehackrfparam.h\ + hackrf/devicehackrfvalues.h\ + hackrf/devicehackrfshared.h -HEADERS += limesdr/devicelimesdr.h\ - limesdr/devicelimesdrparam.h\ - limesdr/devicelimesdrshared.h + HEADERS += limesdr/devicelimesdr.h\ + limesdr/devicelimesdrparam.h\ + limesdr/devicelimesdrshared.h -HEADERS += plutosdr/deviceplutosdr.h\ - plutosdr/deviceplutosdrbox.h\ - plutosdr/deviceplutosdrparams.h\ - plutosdr/deviceplutosdrscan.h\ - plutosdr/deviceplutosdrshared.h + HEADERS += plutosdr/deviceplutosdr.h\ + plutosdr/deviceplutosdrbox.h\ + plutosdr/deviceplutosdrparams.h\ + plutosdr/deviceplutosdrscan.h\ + plutosdr/deviceplutosdrshared.h +} + +macx { + SOURCES += hackrf/devicehackrf.cpp\ + hackrf/devicehackrfvalues.cpp\ + hackrf/devicehackrfshared.cpp + + SOURCES += limesdr/devicelimesdr.cpp\ + limesdr/devicelimesdrparam.cpp\ + limesdr/devicelimesdrshared.cpp + + HEADERS += hackrf/devicehackrf.h\ + hackrf/devicehackrfparam.h\ + hackrf/devicehackrfvalues.h\ + hackrf/devicehackrfshared.h + + HEADERS += limesdr/devicelimesdr.h\ + limesdr/devicelimesdrparam.h\ + limesdr/devicelimesdrshared.h +} + +MSVC { + SOURCES += hackrf/devicehackrf.cpp\ + hackrf/devicehackrfvalues.cpp\ + hackrf/devicehackrfshared.cpp + + SOURCES += limesdr/devicelimesdr.cpp\ + limesdr/devicelimesdrparam.cpp\ + limesdr/devicelimesdrshared.cpp + + HEADERS += hackrf/devicehackrf.h\ + hackrf/devicehackrfparam.h\ + hackrf/devicehackrfvalues.h\ + hackrf/devicehackrfshared.h + + HEADERS += limesdr/devicelimesdr.h\ + limesdr/devicelimesdrparam.h\ + limesdr/devicelimesdrshared.h +} LIBS += -L../sdrbase/$${build_subdir} -lsdrbase -!macx { + +MINGW32 || MINGW64 { LIBS += -L$$LIBBLADERF/lib -lbladeRF LIBS += -L../libhackrf/$${build_subdir} -llibhackrf LIBS += -L../liblimesuite/$${build_subdir} -lliblimesuite LIBS += -L../libiio/$${build_subdir} -llibiio } + macx { - LIBS -= -L../libbladerf/$${build_subdir} -llibbladerf - LIBS -= -L../libhackrf/$${build_subdir} -llibhackrf LIBS += -L/opt/local/lib -lhackrf LIBS += -L/usr/local/lib -lLimeSuite LIBS += -framework iio } + +MSVC { + LIBS += -L../libhackrf/$${build_subdir} -llibhackrf + LIBS += -LC:\softs\PothosSDR\lib -lLimeSuite +} diff --git a/devices/hackrf/devicehackrf.h b/devices/hackrf/devicehackrf.h index 4cbb39d92..291e617b6 100644 --- a/devices/hackrf/devicehackrf.h +++ b/devices/hackrf/devicehackrf.h @@ -30,7 +30,11 @@ public: protected: DeviceHackRF(); DeviceHackRF(const DeviceHackRF&) {} +#ifdef _MSC_VER + DeviceHackRF& operator=(const DeviceHackRF& other) { return *this; } +#else DeviceHackRF& operator=(const DeviceHackRF& other __attribute__((unused))) { return *this; } +#endif ~DeviceHackRF(); private: static hackrf_device *open_hackrf_from_sequence(int sequence); diff --git a/libairspy/libairspy.pro b/libairspy/libairspy.pro index 4b95674d9..fbefedc88 100644 --- a/libairspy/libairspy.pro +++ b/libairspy/libairspy.pro @@ -11,10 +11,16 @@ TARGET = libairspy CONFIG(MINGW32):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" CONFIG(MINGW64):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" +CONFIG(MSVC):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" + INCLUDEPATH += $$LIBAIRSPYSRC/src CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\pthreads-w32\include" + +CONFIG(MSVC):DEFINES += _TIMESPEC_DEFINED SOURCES = $$LIBAIRSPYSRC/src/airspy.c\ $$LIBAIRSPYSRC/src/iqconverter_float.c\ @@ -28,6 +34,8 @@ HEADERS = $$LIBAIRSPYSRC/src/airspy.h\ CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\pthreads-w32\lib\x64 -lpthreadVC2 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libairspyhf/libairspyhf.pro b/libairspyhf/libairspyhf.pro index e11678f0e..1d4bf12d7 100644 --- a/libairspyhf/libairspyhf.pro +++ b/libairspyhf/libairspyhf.pro @@ -11,10 +11,14 @@ TARGET = libairspyhf CONFIG(MINGW32):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" CONFIG(MINGW64):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" +CONFIG(MSVC):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" + INCLUDEPATH += $$LIBAIRSPYHFSRC/src CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\pthreads-w32\include" SOURCES = $$LIBAIRSPYHFSRC/src/airspyhf.c\ $$LIBAIRSPYHFSRC/src/iqbalancer.c @@ -25,6 +29,8 @@ HEADERS = $$LIBAIRSPYHFSRC/src/airspyhf.h\ CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\pthreads-w32\lib\x64 -lpthreadVC2 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libhackrf/libhackrf.pro b/libhackrf/libhackrf.pro index d47e6ca02..b270160de 100644 --- a/libhackrf/libhackrf.pro +++ b/libhackrf/libhackrf.pro @@ -11,10 +11,14 @@ TARGET = libhackrf CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" +CONFIG(MSVC):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" + INCLUDEPATH += $$LIBHACKRFSRC/src CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\pthreads-w32\include" SOURCES = $$LIBHACKRFSRC/src/hackrf.c @@ -22,6 +26,8 @@ HEADERS = $$LIBHACKRFSRC/src/hackrf.h CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\pthreads-w32\lib\x64 -lpthreadVC2 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/librtlsdr/librtlsdr.pro b/librtlsdr/librtlsdr.pro index b6e66249a..3af498a62 100644 --- a/librtlsdr/librtlsdr.pro +++ b/librtlsdr/librtlsdr.pro @@ -11,10 +11,13 @@ TARGET = librtlsdr CONFIG(MINGW32):LIBRTLSDRSRC = "C:\softs\librtlsdr" CONFIG(MINGW64):LIBRTLSDRSRC = "C:\softs\librtlsdr" +CONFIG(MSVC):LIBRTLSDRSRC = "C:\softs\librtlsdr" + INCLUDEPATH += $$LIBRTLSDRSRC/include CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" SOURCES = $$LIBRTLSDRSRC/src/librtlsdr.c\ $$LIBRTLSDRSRC/src/tuner_e4k.c\ @@ -39,6 +42,7 @@ HEADERS = $$LIBRTLSDRSRC/include/reg_field.h\ CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/logging/logging.pro b/logging/logging.pro index 670800044..f3016a033 100644 --- a/logging/logging.pro +++ b/logging/logging.pro @@ -14,6 +14,8 @@ INCLUDEPATH += ../exports QMAKE_CXXFLAGS += -std=c++11 +CONFIG(MSVC):DEFINES += logging_EXPORTS + CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug diff --git a/plugins/samplesource/rtlsdr/rtlsdr.pro b/plugins/samplesource/rtlsdr/rtlsdr.pro index 3e91ff20b..f88833f4a 100644 --- a/plugins/samplesource/rtlsdr/rtlsdr.pro +++ b/plugins/samplesource/rtlsdr/rtlsdr.pro @@ -19,11 +19,14 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBRTLSDRSRC = "C:\softs\librtlsdr" CONFIG(MINGW64):LIBRTLSDRSRC = "C:\softs\librtlsdr" +CONFIG(MSVC):LIBRTLSDRSRC = "C:\softs\librtlsdr" + INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client + !macx:INCLUDEPATH += $$LIBRTLSDRSRC/include macx:INCLUDEPATH += /opt/local/include diff --git a/qrtplib/qrtplib.pro b/qrtplib/qrtplib.pro index bfe30b4a9..adfffceca 100644 --- a/qrtplib/qrtplib.pro +++ b/qrtplib/qrtplib.pro @@ -14,6 +14,8 @@ INCLUDEPATH += ../exports QMAKE_CXXFLAGS += -std=c++11 +CONFIG(MSVC):DEFINES += qrtplib_EXPORTS + CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index ff631ac39..a27c13f78 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -21,9 +21,9 @@ SUBDIRS += libairspy SUBDIRS += libairspyhf #SUBDIRS += libbladerf SUBDIRS += libhackrf -SUBDIRS += libiio -SUBDIRS += liblimesuite -SUBDIRS += libperseus +CONFIG(!MSVC):SUBDIRS += libiio +CONFIG(!MSVC):SUBDIRS += liblimesuite +CONFIG(!MSVC):SUBDIRS += libperseus SUBDIRS += librtlsdr SUBDIRS += devices SUBDIRS += mbelib @@ -36,7 +36,7 @@ SUBDIRS += plugins/samplesource/bladerf2input SUBDIRS += plugins/samplesource/filesource SUBDIRS += plugins/samplesource/hackrfinput SUBDIRS += plugins/samplesource/limesdrinput -SUBDIRS += plugins/samplesource/plutosdrinput +CONFIG(!MSVC):SUBDIRS += plugins/samplesource/plutosdrinput #SUBDIRS += plugins/samplesource/sdrdaemonsource SUBDIRS += plugins/samplesource/rtlsdr SUBDIRS += plugins/samplesource/testsource @@ -45,7 +45,7 @@ SUBDIRS += plugins/samplesink/bladerf1output SUBDIRS += plugins/samplesink/bladerf2output SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput -SUBDIRS += plugins/samplesink/plutosdroutput +CONFIG(!MSVC):SUBDIRS += plugins/samplesink/plutosdroutput #SUBDIRS += plugins/samplesink/sdrdaemonsink SUBDIRS += plugins/channelrx/chanalyzer SUBDIRS += plugins/channelrx/demodam diff --git a/sdrbase/channel/channelsinkapi.h b/sdrbase/channel/channelsinkapi.h index ee0494c35..ecfa13bf3 100644 --- a/sdrbase/channel/channelsinkapi.h +++ b/sdrbase/channel/channelsinkapi.h @@ -46,6 +46,24 @@ public: virtual QByteArray serialize() const = 0; virtual bool deserialize(const QByteArray& data) = 0; +#ifdef _MSC_VER + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } +#else virtual int webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response __attribute__((unused)), QString& errorMessage) @@ -62,6 +80,7 @@ public: SWGSDRangel::SWGChannelReport& response __attribute__((unused)), QString& errorMessage) { errorMessage = "Not implemented"; return 501; } +#endif int getIndexInDeviceSet() const { return m_indexInDeviceSet; } void setIndexInDeviceSet(int indexInDeviceSet) { m_indexInDeviceSet = indexInDeviceSet; } diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index f714c64f6..37b0f4e91 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -40,7 +40,7 @@ CONFIG(MINGW64):INCLUDEPATH += "C:\softs\serialDV" CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" -win32 { +MINGW32 || MINGW64 { HEADERS += \ dsp/dvserialengine.h \ dsp/dvserialworker.h diff --git a/swagger/swagger.pro b/swagger/swagger.pro index 1b0cd9ad9..364940d69 100644 --- a/swagger/swagger.pro +++ b/swagger/swagger.pro @@ -23,6 +23,8 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 +CONFIG(MSVC):DEFINES += swagger_EXPORTS + CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug

    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 literal 0 HcmV?d00001 diff --git a/doc/img/FileSource_plugin.xcf b/doc/img/FileSource_plugin.xcf new file mode 100644 index 0000000000000000000000000000000000000000..6e22a5a396ffc57bee07b0d4c37e17e21181a668 GIT binary patch literal 98617 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 Yfsj^JU_O<-Qr9HUBJa{6CMXa24~C;gkN^Mx literal 0 HcmV?d00001 diff --git a/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp index 09fc023a1..c65b66fa5 100644 --- a/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor BladeRF2OutputPlugin::m_pluginDescriptor = { QString("BladeRF2 Output"), - QString("4.2.0"), + QString("4.2.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp index a3536a860..59e13c0dd 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor Blderf2InputPlugin::m_pluginDescriptor = { QString("BladeRF2 Input"), - QString("4.2.0"), + QString("4.2.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/filesource/filesourceplugin.cpp b/plugins/samplesource/filesource/filesourceplugin.cpp index 10057f205..71a128bc2 100644 --- a/plugins/samplesource/filesource/filesourceplugin.cpp +++ b/plugins/samplesource/filesource/filesourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor FileSourcePlugin::m_pluginDescriptor = { QString("File source input"), - QString("3.14.5"), + QString("4.2.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/filesource/readme.md b/plugins/samplesource/filesource/readme.md new file mode 100644 index 000000000..aebd4ee64 --- /dev/null +++ b/plugins/samplesource/filesource/readme.md @@ -0,0 +1,106 @@ +

    File source input plugin

    + +

    Introduction

    + +This plugin reads a file of I/Q samples that have been previously saved with the file record button of other sampling source devices. The file starts with a 32 byte header of all unsigned integer of various sizes containing meta data: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Displ.BytesDescription
    04Sample rate in S/s
    48Center frequency in Hz
    128Unix epoch (timestamp) of start
    204Sample size (16 or 24 bits)
    244Filler with zeroes
    284CRC32 of the previous 28 bytes
    + +The header takes an integer number of 16 (4 bytes) or 24 (8 bytes) bits samples. To calculate CRC it is assumed that bytes are in little endian order. + +

    Interface

    + +![FileSource input plugin GUI](../../../doc/img/FileSource_plugin.png) + +

    1: Start/Stop

    + +Device start / stop button. + + - Blue triangle icon: ready to be started + - Green square icon: currently running and can be stopped + - Magenta (or pink) square icon: an error occurred. The file may not be found or this can be a header CRC error or the file is too small (less than the header length). You may stop and choose another file. + +

    2: Stream sample rate

    + +Baseband I/Q sample rate in kS/s. This is the sample rate present in the header. + +

    3: Frequency

    + +This is the center frequency of reception in kHz when the record was taken and written in the header. + +

    4: Open file

    + +Opens a file dialog to select the input file. It expects a default extension of `.sdriq`. This button is disabled when the stream is running. You need to pause (button 11) to make it active and thus be able to select another file. + +

    5: File path

    + +Absolute path of the file being read + +

    6: File recorded sample rate

    + +Sample rate of the record in kS/s as written in the header. The reading process is based on this sample rate. + +

    7: Sample size

    + +This is the sample size in bits as written in the header. The reading process is based on this sample size. + +

    8: CRC indicator

    + +Indicates if the header block CRC check has succeeded (green) or failed (red) or undetermined yet (grey). If the header is corrupted you may try to reconstruct a valid header using the `rescuesdriq` utility in the folder with the same name. See the [readme](../../../rescuesdriq/readme.md) for details. + +

    9: Current timestamp

    + +This is the timestamp of the current pointer in the file based on the start time, number of samples read and sample rate. + +

    10: Loop

    + +Use this button to read in a loop or read only once + +

    11: Play/pause

    + +This is the play/pause button + +

    12: Relative timestamp and record length

    + +Left is the relative timestamp of the current pointer from the start of the record. Right is the total record time. + +

    13: Current pointer gauge

    + +This represents the position of the current pointer position in the complete recording. It can be used it paused mode to position the current pointer by moving the slider. + \ No newline at end of file From 993ab36a2967dab83f4902672b19eb54cb600f19 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 10 Oct 2018 14:05:21 +0200 Subject: [PATCH 845/956] FileRecord improvement: update FileSink plugin and writeHeader private method in FileRecord accordingly --- plugins/samplesink/filesink/filesinkoutput.cpp | 11 +++++++---- plugins/samplesink/filesink/filesinkplugin.cpp | 2 +- sdrbase/dsp/filerecord.cpp | 13 +++++++++---- sdrbase/dsp/filerecord.h | 1 + 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/plugins/samplesink/filesink/filesinkoutput.cpp b/plugins/samplesink/filesink/filesinkoutput.cpp index b9ea22fb9..d73747c1a 100644 --- a/plugins/samplesink/filesink/filesinkoutput.cpp +++ b/plugins/samplesink/filesink/filesinkoutput.cpp @@ -68,12 +68,15 @@ void FileSinkOutput::openFileStream() m_ofstream.open(m_fileName.toStdString().c_str(), std::ios::binary); + FileRecord::Header header; int actualSampleRate = m_settings.m_sampleRate * (1< Date: Thu, 11 Oct 2018 08:52:50 +0200 Subject: [PATCH 846/956] NFM demod: changed squelch 0.1 dB steps to 1 dB steps --- plugins/channelrx/demodnfm/nfmdemod.cpp | 8 ++++---- plugins/channelrx/demodnfm/nfmdemodgui.cpp | 8 ++++---- plugins/channelrx/demodnfm/nfmdemodgui.ui | 14 ++++---------- plugins/channelrx/demodnfm/nfmdemodsettings.cpp | 6 +++--- plugins/channelrx/demodnfm/nfmdemodsettings.h | 2 +- plugins/channelrx/demodnfm/nfmplugin.cpp | 2 +- plugins/channelrx/demodnfm/readme.md | 2 +- plugins/samplesource/perseus/perseusplugin.cpp | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 4 ++-- .../webapi/doc/swagger/include/NFMDemod.yaml | 2 +- swagger/sdrangel/api/swagger/include/NFMDemod.yaml | 2 +- swagger/sdrangel/code/html2/index.html | 4 ++-- swagger/sdrangel/examples/rx_test.py | 2 +- swagger/sdrangel/examples/rx_tx_test.py | 2 +- swagger/sdrangel/examples/scanner.py | 2 +- 15 files changed, 28 insertions(+), 34 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 85a6e9263..2002d2463 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -523,14 +523,14 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) (settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) { if (settings.m_deltaSquelch) - { // input is a value in negative millis - m_squelchLevel = (- settings.m_squelch) / 1000.0; + { // input is a value in negative centis + m_squelchLevel = (- settings.m_squelch) / 100.0; m_afSquelch.setThreshold(m_squelchLevel); m_afSquelch.reset(); } else - { // input is a value in centi-Bels - m_squelchLevel = std::pow(10.0, settings.m_squelch / 100.0); + { // input is a value in deci-Bels + m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); m_movingAverage.reset(); } diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.cpp b/plugins/channelrx/demodnfm/nfmdemodgui.cpp index 3f4ba6f45..57e062311 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodgui.cpp @@ -182,12 +182,12 @@ void NFMDemodGUI::on_squelch_valueChanged(int value) { if (ui->deltaSquelch->isChecked()) { - ui->squelchText->setText(QString("%1").arg(-value / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg(-value / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch deviation threshold (%)")); } else { - ui->squelchText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg(value / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch power threshold (dB)")); } m_settings.m_squelch = value * 1.0; @@ -368,13 +368,13 @@ void NFMDemodGUI::displaySettings() if (m_settings.m_deltaSquelch) { - ui->squelchText->setText(QString("%1").arg((-m_settings.m_squelch) / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg((-m_settings.m_squelch) / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch AF balance threshold (%)")); ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); } else { - ui->squelchText->setText(QString("%1").arg(m_settings.m_squelch / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg(m_settings.m_squelch / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch power threshold (dB)")); ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); } diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.ui b/plugins/channelrx/demodnfm/nfmdemodgui.ui index 070458b1c..fc8fe0075 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.ui +++ b/plugins/channelrx/demodnfm/nfmdemodgui.ui @@ -428,7 +428,7 @@ Squelch threshold (dB) - -1000 + -100 0 @@ -440,7 +440,7 @@ 1 - -150 + -100 @@ -448,21 +448,15 @@ - 40 + 28 0 - - - 16777215 - 16777215 - - Squelch threshold (dB) - -15.0 + -100 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter diff --git a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp index 718404ecd..49740e21a 100644 --- a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp @@ -44,7 +44,7 @@ void NFMDemodSettings::resetToDefaults() m_fmDeviation = 2000; m_squelchGate = 5; // 10s of ms at 48000 Hz sample rate. Corresponds to 2400 for AGC attack m_deltaSquelch = false; - m_squelch = -300.0; + m_squelch = -30.0; m_volume = 1.0; m_ctcssOn = false; m_audioMute = false; @@ -109,8 +109,8 @@ bool NFMDemodSettings::deserialize(const QByteArray& data) m_afBandwidth = tmp * 1000.0; d.readS32(4, &tmp, 20); m_volume = tmp / 10.0; - d.readS32(5, &tmp, -300); - m_squelch = tmp * 1.0; + d.readS32(5, &tmp, -30); + m_squelch = (tmp < -100 ? -100 : tmp) * 1.0; d.readU32(7, &m_rgbColor, QColor(255, 0, 0).rgb()); d.readS32(8, &m_ctcssIndex, 0); d.readBool(9, &m_ctcssOn, false); diff --git a/plugins/channelrx/demodnfm/nfmdemodsettings.h b/plugins/channelrx/demodnfm/nfmdemodsettings.h index 6fef3d3ee..285735674 100644 --- a/plugins/channelrx/demodnfm/nfmdemodsettings.h +++ b/plugins/channelrx/demodnfm/nfmdemodsettings.h @@ -33,7 +33,7 @@ struct NFMDemodSettings int m_fmDeviation; int m_squelchGate; bool m_deltaSquelch; - Real m_squelch; //!< centi-Bels + Real m_squelch; //!< deci-Bels Real m_volume; bool m_ctcssOn; bool m_audioMute; diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index 97ef60aae..53b5f8357 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("4.1.0"), + QString("4.2.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodnfm/readme.md b/plugins/channelrx/demodnfm/readme.md index 9f1bdfb14..aa3fff81b 100644 --- a/plugins/channelrx/demodnfm/readme.md +++ b/plugins/channelrx/demodnfm/readme.md @@ -46,7 +46,7 @@ Use this button to toggle between AF (on) and RF power (off) based squelch.

    Power threshold mode

    -Case when the delta/Level squelch control (7) is off (power). This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button. +Case when the delta/Level squelch control (7) is off (power). This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 1 dB steps from 0 to -100 dB using the dial button.

    Audio frequency delta mode

    diff --git a/plugins/samplesource/perseus/perseusplugin.cpp b/plugins/samplesource/perseus/perseusplugin.cpp index f51ee3005..f4e5e6276 100644 --- a/plugins/samplesource/perseus/perseusplugin.cpp +++ b/plugins/samplesource/perseus/perseusplugin.cpp @@ -81,7 +81,7 @@ PluginInterface::SamplingDevices PerseusPlugin::enumSampleSources() 1, 0)); - qDebug("PerseusPlugin::enumSampleSources: enumerated PlutoSDR device #%d", i); + qDebug("PerseusPlugin::enumSampleSources: enumerated Perseus device #%d", i); } return result; diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index fe31a31c5..9f2c3c342 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -2802,7 +2802,7 @@ margin-bottom: 20px; "squelch" : { "type" : "number", "format" : "float", - "description" : "power squelch threshold in centi-bels" + "description" : "power squelch threshold in deci-bels" }, "volume" : { "type" : "number", @@ -23242,7 +23242,7 @@ except ApiException as e:
    - Generated 2018-10-03T06:02:12.961+02:00 + Generated 2018-10-11T08:49:05.249+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml index bdbdde412..5dc7129c3 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml @@ -21,7 +21,7 @@ NFMDemodSettings: deltaSquelch: type: integer squelch: - description: power squelch threshold in centi-bels + description: power squelch threshold in deci-bels type: number format: float volume: diff --git a/swagger/sdrangel/api/swagger/include/NFMDemod.yaml b/swagger/sdrangel/api/swagger/include/NFMDemod.yaml index bdbdde412..5dc7129c3 100644 --- a/swagger/sdrangel/api/swagger/include/NFMDemod.yaml +++ b/swagger/sdrangel/api/swagger/include/NFMDemod.yaml @@ -21,7 +21,7 @@ NFMDemodSettings: deltaSquelch: type: integer squelch: - description: power squelch threshold in centi-bels + description: power squelch threshold in deci-bels type: number format: float volume: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index fe31a31c5..9f2c3c342 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -2802,7 +2802,7 @@ margin-bottom: 20px; "squelch" : { "type" : "number", "format" : "float", - "description" : "power squelch threshold in centi-bels" + "description" : "power squelch threshold in deci-bels" }, "volume" : { "type" : "number", @@ -23242,7 +23242,7 @@ except ApiException as e:
    - Generated 2018-10-03T06:02:12.961+02:00 + Generated 2018-10-11T08:49:05.249+02:00
    diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 10b149872..dbf69fd5e 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -252,7 +252,7 @@ def setupChannel(deviceset_url, options): settings["NFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 settings["NFMDemodSettings"]["rfBandwidth"] = options.rf_bw settings["NFMDemodSettings"]["volume"] = options.volume - settings["NFMDemodSettings"]["squelch"] = options.squelch_db * 10 # centi-Bels + settings["NFMDemodSettings"]["squelch"] = options.squelch_db # deci-Bels settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate // 10 # 10's of ms settings["NFMDemodSettings"]["title"] = "Channel %d" % i elif options.channel_id == "BFMDemod": diff --git a/swagger/sdrangel/examples/rx_tx_test.py b/swagger/sdrangel/examples/rx_tx_test.py index 621a2eb0e..bb6516450 100644 --- a/swagger/sdrangel/examples/rx_tx_test.py +++ b/swagger/sdrangel/examples/rx_tx_test.py @@ -164,7 +164,7 @@ def main(): settings["NFMDemodSettings"]["rfBandwidth"] = 12500 settings["NFMDemodSettings"]["fmDeviation"] = 3000 settings["NFMDemodSettings"]["afBandwidth"] = 4000 - settings["NFMDemodSettings"]["squelch"] = -700 + settings["NFMDemodSettings"]["squelch"] = -70 settings["NFMDemodSettings"]["volume"] = 2.0 if options.udp_copy is not None: diff --git a/swagger/sdrangel/examples/scanner.py b/swagger/sdrangel/examples/scanner.py index 597332103..cd6e16634 100755 --- a/swagger/sdrangel/examples/scanner.py +++ b/swagger/sdrangel/examples/scanner.py @@ -191,7 +191,7 @@ def setupChannels(scan_control, options): settings["NFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 settings["NFMDemodSettings"]["rfBandwidth"] = options.rf_bw settings["NFMDemodSettings"]["volume"] = options.volume - settings["NFMDemodSettings"]["squelch"] = options.squelch_db * 10 # centi-Bels + settings["NFMDemodSettings"]["squelch"] = options.squelch_db # deci-Bels settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate / 10 # 10's of ms settings["NFMDemodSettings"]["title"] = "Channel %d" % i elif options.channel_id == "AMDemod": From ed1b706661db3d3fb2f69bc48609bf9be8091e57 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 11 Oct 2018 15:14:58 +0200 Subject: [PATCH 847/956] NFM demod: if squelch < -100 assume it is old format and divide by 10 --- plugins/channelrx/demodnfm/nfmdemodsettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp index 49740e21a..b23684402 100644 --- a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp @@ -110,7 +110,7 @@ bool NFMDemodSettings::deserialize(const QByteArray& data) d.readS32(4, &tmp, 20); m_volume = tmp / 10.0; d.readS32(5, &tmp, -30); - m_squelch = (tmp < -100 ? -100 : tmp) * 1.0; + m_squelch = (tmp < -100 ? tmp/10 : tmp) * 1.0; d.readU32(7, &m_rgbColor, QColor(255, 0, 0).rgb()); d.readS32(8, &m_ctcssIndex, 0); d.readBool(9, &m_ctcssOn, false); From a7b954dbf8c8bc3a4527441d137c3935ce859c9d Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 11 Oct 2018 16:09:40 +0200 Subject: [PATCH 848/956] NFM demod: fixed squelch display --- plugins/channelrx/demodnfm/nfmdemodgui.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.cpp b/plugins/channelrx/demodnfm/nfmdemodgui.cpp index 57e062311..98c2361d4 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodgui.cpp @@ -164,15 +164,15 @@ void NFMDemodGUI::on_deltaSquelch_toggled(bool checked) { if (checked) { - ui->squelchText->setText(QString("%1").arg((-ui->squelch->value()) / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg((-ui->squelch->value()) / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch AF balance threshold (%)")); ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); } else { - ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch power threshold (dB)")); - ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); + ui->squelch->setToolTip(tr("Squelch power threshold (dB)")); } m_settings.m_deltaSquelch = checked; applySettings(); @@ -183,12 +183,14 @@ void NFMDemodGUI::on_squelch_valueChanged(int value) if (ui->deltaSquelch->isChecked()) { ui->squelchText->setText(QString("%1").arg(-value / 1.0, 0, 'f', 0)); - ui->squelchText->setToolTip(tr("Squelch deviation threshold (%)")); + ui->squelchText->setToolTip(tr("Squelch AF balance threshold (%)")); + ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); } else { ui->squelchText->setText(QString("%1").arg(value / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch power threshold (dB)")); + ui->squelch->setToolTip(tr("Squelch power threshold (dB)")); } m_settings.m_squelch = value * 1.0; applySettings(); @@ -376,7 +378,7 @@ void NFMDemodGUI::displaySettings() { ui->squelchText->setText(QString("%1").arg(m_settings.m_squelch / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch power threshold (dB)")); - ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); + ui->squelch->setToolTip(tr("Squelch power threshold (dB)")); } ui->ctcssOn->setChecked(m_settings.m_ctcssOn); From 0e074cda36842623cd2cba52c77b3feb0a9ceb33 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 11 Oct 2018 21:14:30 +0200 Subject: [PATCH 849/956] BladeRF: updated documentation --- Readme.md | 3 ++- debian/changelog | 1 + plugins/samplesink/bladerf2output/readme.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 90d3fa11c..f7600dfa6 100644 --- a/Readme.md +++ b/Readme.md @@ -39,6 +39,7 @@ Since version 2 SDRangel can integrate more than one hardware device running con Since version 3 transmission or signal generation is supported for BladeRF, HackRF (since version 3.1), LimeSDR (since version 3.4) and PlutoSDR (since version 3.7.8) using a sample sink plugin. These plugins are: - [BladeRF1 output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) + - [BladeRF2 output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf2output) - [HackRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/hackrfoutput) - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) - [PlutoSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/plutosdroutput) @@ -114,7 +115,7 @@ The plugins used to support BladeRF classic are specific to this version of the

    BladeRF micro (v.2)

    -From version 4.2.0. Output (Tx) for Linux only. +From version 4.2.0. [BladeRF 2 micro](https://www.nuand.com/bladerf-2-0-micro/) is also supported using libbladeRF library that should be installed and configured in the same way as for BladeRF1. diff --git a/debian/changelog b/debian/changelog index a977768c6..bf3560720 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,6 +2,7 @@ sdrangel (4.2.1-1) unstable; urgency=medium * FileRecord improvement with robust header and some fixes. Fixes issue #206 * BladeRF2 MO Tx fix so that the two channels are used effectively. Fixes issue #225 + * NFM demod: set squelch step to 1 dB -- Edouard Griffiths, F4EXB Wed, 10 Oct 2018 21:14:18 +0200 diff --git a/plugins/samplesink/bladerf2output/readme.md b/plugins/samplesink/bladerf2output/readme.md index 070ffc554..8719e7163 100644 --- a/plugins/samplesink/bladerf2output/readme.md +++ b/plugins/samplesink/bladerf2output/readme.md @@ -2,7 +2,7 @@

    Introduction

    -This output sample sink plugin sends its samples to a [BladeRF2 device](https://www.nuand.com/bladerf-2). This is available since v4.2.0 in Linux distributions only. +This output sample sink plugin sends its samples to a [BladeRF2 device](https://www.nuand.com/bladerf-2). This is available since v4.2.0.

    Build

    From 22693ac613cf6433183f66dd93806df0c7e03f8a Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 12 Oct 2018 08:47:14 +0200 Subject: [PATCH 850/956] Spectrum: added max function in the 'averaging' modes --- debian/changelog | 6 ++ sdrbase/util/fixedaverage2d.h | 24 ++++---- sdrbase/util/max2d.h | 107 ++++++++++++++++++++++++++++++++++ sdrgui/dsp/spectrumvis.cpp | 76 +++++++++++++++++++----- sdrgui/dsp/spectrumvis.h | 21 ++++--- sdrgui/gui/glspectrumgui.cpp | 10 ++-- sdrgui/gui/glspectrumgui.h | 3 +- sdrgui/gui/glspectrumgui.ui | 5 ++ 8 files changed, 212 insertions(+), 40 deletions(-) create mode 100644 sdrbase/util/max2d.h diff --git a/debian/changelog b/debian/changelog index bf3560720..10534c313 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (4.2.2-1) unstable; urgency=medium + + * Spectrum: option to get max over a number of FFTs. Implements issue #207 + + -- Edouard Griffiths, F4EXB Sun, 14 Oct 2018 21:14:18 +0200 + sdrangel (4.2.1-1) unstable; urgency=medium * FileRecord improvement with robust header and some fixes. Fixes issue #206 diff --git a/sdrbase/util/fixedaverage2d.h b/sdrbase/util/fixedaverage2d.h index 192dcfe6a..c9f7b0ffa 100644 --- a/sdrbase/util/fixedaverage2d.h +++ b/sdrbase/util/fixedaverage2d.h @@ -24,7 +24,7 @@ template class FixedAverage2D { public: - FixedAverage2D() : m_sum(0), m_sumSize(0), m_width(0), m_size(0), m_avgIndex(0) {} + FixedAverage2D() : m_sum(0), m_maxSize(0), m_width(0), m_size(0), m_maxIndex(0) {} ~FixedAverage2D() { @@ -35,20 +35,20 @@ public: void resize(unsigned int width, unsigned int size) { - if (width > m_sumSize) + if (width > m_maxSize) { - m_sumSize = width; + m_maxSize = width; if (m_sum) { delete[] m_sum; } - m_sum = new T[m_sumSize]; + m_sum = new T[m_maxSize]; } m_width = width; m_size = size; std::fill(m_sum, m_sum+m_width, 0); - m_avgIndex = 0; + m_maxIndex = 0; } bool storeAndGetAvg(T& avg, T v, unsigned int index) @@ -61,7 +61,7 @@ public: m_sum[index] += v; - if (m_avgIndex == m_size - 1) + if (m_maxIndex == m_size - 1) { avg = m_sum[index]/m_size; return true; @@ -82,7 +82,7 @@ public: m_sum[index] += v; - if (m_avgIndex < m_size - 1) + if (m_maxIndex < m_size - 1) { sum = m_sum[index]; return true; @@ -99,25 +99,25 @@ public: return true; } - if (m_avgIndex == m_size - 1) + if (m_maxIndex == m_size - 1) { - m_avgIndex = 0; + m_maxIndex = 0; std::fill(m_sum, m_sum+m_width, 0); return true; } else { - m_avgIndex++; + m_maxIndex++; return false; } } private: T *m_sum; - unsigned int m_sumSize; + unsigned int m_maxSize; unsigned int m_width; unsigned int m_size; - unsigned int m_avgIndex; + unsigned int m_maxIndex; }; diff --git a/sdrbase/util/max2d.h b/sdrbase/util/max2d.h new file mode 100644 index 000000000..58be966c9 --- /dev/null +++ b/sdrbase/util/max2d.h @@ -0,0 +1,107 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 SDRBASE_UTIL_MAX2D_H_ +#define SDRBASE_UTIL_MAX2D_H_ + +#include + +template +class Max2D +{ +public: + Max2D() : m_max(0), m_maxSize(0), m_width(0), m_size(0), m_maxIndex(0) {} + + ~Max2D() + { + if (m_max) { + delete[] m_max; + } + } + + void resize(unsigned int width, unsigned int size) + { + if (width > m_maxSize) + { + m_maxSize = width; + if (m_max) { + delete[] m_max; + } + m_max = new T[m_maxSize]; + } + + m_width = width; + m_size = size; + + std::fill(m_max, m_max+m_width, 0); + m_maxIndex = 0; + } + + bool storeAndGetMax(T& max, T v, unsigned int index) + { + if (m_size <= 1) + { + max = v; + return true; + } + + if (m_maxIndex == 0) + { + m_max[index] = v; + return false; + } + else if (m_maxIndex == m_size - 1) + { + m_max[index] = std::max(m_max[index], v); + max = m_max[index]; + return true; + } + else + { + m_max[index] = std::max(m_max[index], v); + return false; + } + } + + bool nextMax() + { + if (m_size <= 1) { + return true; + } + + if (m_maxIndex == m_size - 1) + { + m_maxIndex = 0; + std::fill(m_max, m_max+m_width, 0); + return true; + } + else + { + m_maxIndex++; + return false; + } + } + +private: + T *m_max; + unsigned int m_maxSize; + unsigned int m_width; + unsigned int m_size; + unsigned int m_maxIndex; +}; + +#endif /* SDRBASE_UTIL_MAX2D_H_ */ diff --git a/sdrgui/dsp/spectrumvis.cpp b/sdrgui/dsp/spectrumvis.cpp index 7bc7f5426..0389cdc91 100644 --- a/sdrgui/dsp/spectrumvis.cpp +++ b/sdrgui/dsp/spectrumvis.cpp @@ -26,7 +26,7 @@ SpectrumVis::SpectrumVis(Real scalef, GLSpectrum* glSpectrum) : m_scalef(scalef), m_glSpectrum(glSpectrum), m_averageNb(0), - m_averagingMode(AvgModeNone), + m_avgMode(AvgModeNone), m_linear(false), m_ofs(0), m_powFFTDiv(1.0), @@ -114,7 +114,7 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV Real v; std::size_t halfSize = m_fftSize / 2; - if (m_averagingMode == AvgModeNone) + if (m_avgMode == AvgModeNone) { if ( positiveOnly ) { @@ -146,7 +146,7 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV // send new data to visualisation m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); } - else if (m_averagingMode == AvgModeMoving) + else if (m_avgMode == AvgModeMovingAvg) { if ( positiveOnly ) { @@ -182,7 +182,7 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); m_movingAverage.nextAverage(); } - else if (m_averagingMode == AvgModeFixed) + else if (m_avgMode == AvgModeFixedAvg) { double avg; @@ -195,7 +195,7 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV if (m_fixedAverage.storeAndGetAvg(avg, v, i)) { - avg = m_linear ? v/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; + avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; m_powerSpectrum[i * 2] = avg; m_powerSpectrum[i * 2 + 1] = avg; } @@ -210,7 +210,7 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV if (m_fixedAverage.storeAndGetAvg(avg, v, i+halfSize)) { // result available - avg = m_linear ? v/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; + avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; m_powerSpectrum[i] = avg; } @@ -219,16 +219,61 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV if (m_fixedAverage.storeAndGetAvg(avg, v, i)) { // result available - avg = m_linear ? v/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; + avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; m_powerSpectrum[i + halfSize] = avg; } } } - if (m_fixedAverage.nextAverage()) - { // result available - // send new data to visualisation - m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + if (m_fixedAverage.nextAverage()) { // result available + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); // send new data to visualisation + } + } + else if (m_avgMode == AvgModeMax) + { + double max; + + if ( positiveOnly ) + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + + if (m_max.storeAndGetMax(max, v, i)) + { + max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; + m_powerSpectrum[i * 2] = max; + m_powerSpectrum[i * 2 + 1] = max; + } + } + } + else + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i + halfSize]; + v = c.real() * c.real() + c.imag() * c.imag(); + + if (m_max.storeAndGetMax(max, v, i+halfSize)) + { // result available + max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; + m_powerSpectrum[i] = max; + } + + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + + if (m_max.storeAndGetMax(max, v, i)) + { // result available + max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; + m_powerSpectrum[i + halfSize] = max; + } + } + } + + if (m_max.nextMax()) { // result available + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); // send new data to visualisation } } @@ -269,7 +314,7 @@ bool SpectrumVis::handleMessage(const Message& message) handleConfigure(conf.getFFTSize(), conf.getOverlapPercent(), conf.getAverageNb(), - conf.getAveragingMode(), + conf.getAvgMode(), conf.getWindow(), conf.getLinear()); return true; @@ -283,10 +328,12 @@ bool SpectrumVis::handleMessage(const Message& message) void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, unsigned int averageNb, - AveragingMode averagingMode, + AvgMode averagingMode, FFTWindow::Function window, bool linear) { +// qDebug("SpectrumVis::handleConfigure, fftSize: %d overlapPercent: %d averageNb: %u averagingMode: %d window: %d linear: %s", +// fftSize, overlapPercent, averageNb, (int) averagingMode, (int) window, linear ? "true" : "false"); QMutexLocker mutexLocker(&m_mutex); if (fftSize > MAX_FFT_SIZE) @@ -319,8 +366,9 @@ void SpectrumVis::handleConfigure(int fftSize, m_fftBufferFill = m_overlapSize; m_movingAverage.resize(fftSize, averageNb); m_fixedAverage.resize(fftSize, averageNb); + m_max.resize(fftSize, averageNb); m_averageNb = averageNb; - m_averagingMode = averagingMode; + m_avgMode = averagingMode; m_linear = linear; m_ofs = 20.0f * log10f(1.0f / m_fftSize); m_powFFTDiv = m_fftSize*m_fftSize; diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index a86a5c0fd..b01c6459d 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -9,6 +9,7 @@ #include "util/message.h" #include "util/movingaverage2d.h" #include "util/fixedaverage2d.h" +#include "util/max2d.h" class GLSpectrum; class MessageQueue; @@ -16,11 +17,12 @@ class MessageQueue; class SDRGUI_API SpectrumVis : public BasebandSampleSink { public: - enum AveragingMode + enum AvgMode { AvgModeNone, - AvgModeMoving, - AvgModeFixed + AvgModeMovingAvg, + AvgModeFixedAvg, + AvgModeMax }; class MsgConfigureSpectrumVis : public Message { @@ -31,7 +33,7 @@ public: int fftSize, int overlapPercent, unsigned int averageNb, - int averagingMode, + int preProcessMode, FFTWindow::Function window, bool linear) : Message(), @@ -41,13 +43,13 @@ public: m_window(window), m_linear(linear) { - m_averagingMode = averagingMode < 0 ? AvgModeNone : averagingMode > 2 ? AvgModeFixed : (SpectrumVis::AveragingMode) averagingMode; + m_avgMode = preProcessMode < 0 ? AvgModeNone : preProcessMode > 3 ? AvgModeMax : (SpectrumVis::AvgMode) preProcessMode; } int getFFTSize() const { return m_fftSize; } int getOverlapPercent() const { return m_overlapPercent; } unsigned int getAverageNb() const { return m_averageNb; } - SpectrumVis::AveragingMode getAveragingMode() const { return m_averagingMode; } + SpectrumVis::AvgMode getAvgMode() const { return m_avgMode; } FFTWindow::Function getWindow() const { return m_window; } bool getLinear() const { return m_linear; } @@ -55,7 +57,7 @@ public: int m_fftSize; int m_overlapPercent; unsigned int m_averageNb; - SpectrumVis::AveragingMode m_averagingMode; + SpectrumVis::AvgMode m_avgMode; FFTWindow::Function m_window; bool m_linear; }; @@ -95,8 +97,9 @@ private: GLSpectrum* m_glSpectrum; MovingAverage2D m_movingAverage; FixedAverage2D m_fixedAverage; + Max2D m_max; unsigned int m_averageNb; - AveragingMode m_averagingMode; + AvgMode m_avgMode; bool m_linear; Real m_ofs; @@ -108,7 +111,7 @@ private: void handleConfigure(int fftSize, int overlapPercent, unsigned int averageNb, - AveragingMode averagingMode, + AvgMode averagingMode, FFTWindow::Function window, bool linear); }; diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 62a200437..d0ba90b1d 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -141,7 +141,7 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) Real waterfallShare; d.readReal(18, &waterfallShare, 0.66); d.readS32(19, &tmp, 0); - m_averagingMode = tmp < 0 ? AvgModeNone : tmp > 2 ? AvgModeFixed : (AveragingMode) tmp; + m_averagingMode = tmp < 0 ? AvgModeNone : tmp > 3 ? AvgModeMax : (AveragingMode) tmp; d.readS32(20, &tmp, 0); m_averagingIndex = getAveragingIndex(tmp); m_averagingNb = getAveragingValue(m_averagingIndex); @@ -244,7 +244,7 @@ void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index) void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) { - m_averagingMode = index < 0 ? AvgModeNone : index > 2 ? AvgModeFixed : (AveragingMode) index; + m_averagingMode = index < 0 ? AvgModeNone : index > 3 ? AvgModeMax : (AveragingMode) index; if(m_spectrumVis != 0) { m_spectrumVis->configure(m_messageQueueToVis, @@ -258,7 +258,7 @@ void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) if (m_glSpectrum != 0) { - if (m_averagingMode == AvgModeFixed) { + if ((m_averagingMode == AvgModeFixed) || (m_averagingMode == AvgModeMax)) { m_glSpectrum->setTimingRate(m_averagingNb == 0 ? 1 : m_averagingNb); } else { m_glSpectrum->setTimingRate(1); @@ -283,8 +283,10 @@ void GLSpectrumGUI::on_averaging_currentIndexChanged(int index) if (m_glSpectrum != 0) { - if (m_averagingMode == AvgModeFixed) { + if ((m_averagingMode == AvgModeFixed) || (m_averagingMode == AvgModeMax)) { m_glSpectrum->setTimingRate(m_averagingNb == 0 ? 1 : m_averagingNb); + } else { + m_glSpectrum->setTimingRate(1); } } diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 13d305231..9cf02a20d 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -22,7 +22,8 @@ public: { AvgModeNone, AvgModeMoving, - AvgModeFixed + AvgModeFixed, + AvgModeMax }; explicit GLSpectrumGUI(QWidget* parent = NULL); diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index 277c560ed..a874c7068 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -565,6 +565,11 @@ Fix
    + + + Max + +
    From 678af4e18637b585e5081dbdffb443ca868ea140 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 12 Oct 2018 17:05:03 +0200 Subject: [PATCH 851/956] File Input: fixed wrong times display on file seek when sample count exceeds int limit (use of int64 and uint64) --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- .../samplesource/filesource/filesourcegui.cpp | 16 ++++------ .../samplesource/filesource/filesourcegui.h | 6 ++-- .../filesource/filesourceinput.cpp | 6 ++-- .../samplesource/filesource/filesourceinput.h | 30 +++++++++---------- .../filesource/filesourceplugin.cpp | 2 +- .../filesource/filesourcethread.h | 17 ++++++----- 9 files changed, 41 insertions(+), 42 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index a0fddcdf5..c9938efd6 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.2.1"); + QCoreApplication::setApplicationVersion("4.2.2"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 0ec5d274a..6dba5b70d 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.2.1"); + QCoreApplication::setApplicationVersion("4.2.2"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 8141b79ea..89643ee0d 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.2.1"); + QCoreApplication::setApplicationVersion("4.2.2"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index de56a6d5f..86c2a0ac2 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -286,10 +286,6 @@ void FileSourceGui::on_navTimeSlider_valueChanged(int value) { if (m_enableNavTime && ((value >= 0) && (value <= 100))) { - int t_sec = (m_recordLength * value) / 100; - QTime t(0, 0, 0, 0); - t = t.addSecs(t_sec); - FileSourceInput::MsgConfigureFileSourceSeek* message = FileSourceInput::MsgConfigureFileSourceSeek::create(value); m_sampleSource->getInputMessageQueue()->push(message); } @@ -338,12 +334,12 @@ void FileSourceGui::updateWithStreamData() void FileSourceGui::updateWithStreamTime() { - int t_sec = 0; - int t_msec = 0; + qint64 t_sec = 0; + qint64 t_msec = 0; if (m_sampleRate > 0){ t_sec = m_samplesCount / m_sampleRate; - t_msec = (m_samplesCount - (t_sec * m_sampleRate)) * 1000 / m_sampleRate; + t_msec = (m_samplesCount - (t_sec * m_sampleRate)) * 1000LL / m_sampleRate; } QTime t(0, 0, 0, 0); @@ -352,10 +348,10 @@ void FileSourceGui::updateWithStreamTime() QString s_timems = t.toString("HH:mm:ss.zzz"); ui->relTimeText->setText(s_timems); - quint64 startingTimeStampMsec = (quint64) m_startingTimeStamp * 1000LL; + qint64 startingTimeStampMsec = m_startingTimeStamp * 1000LL; QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); - dt = dt.addSecs((quint64) t_sec); - dt = dt.addMSecs((quint64) t_msec); + dt = dt.addSecs(t_sec); + dt = dt.addMSecs(t_msec); QString s_date = dt.toString("yyyy-MM-dd HH:mm:ss.zzz"); ui->absTimeText->setText(s_date); diff --git a/plugins/samplesource/filesource/filesourcegui.h b/plugins/samplesource/filesource/filesourcegui.h index a580d528f..9ed216613 100644 --- a/plugins/samplesource/filesource/filesourcegui.h +++ b/plugins/samplesource/filesource/filesourcegui.h @@ -65,9 +65,9 @@ private: int m_sampleRate; quint32 m_sampleSize; quint64 m_centerFrequency; - quint32 m_recordLength; - std::time_t m_startingTimeStamp; - int m_samplesCount; + quint64 m_recordLength; + quint64 m_startingTimeStamp; + quint64 m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; int m_deviceSampleRate; diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 053eadac8..67929a00e 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -144,9 +144,9 @@ void FileSourceInput::seekFileStream(int seekPercentage) if ((m_ifstream.is_open()) && m_fileSourceThread && !m_fileSourceThread->isRunning()) { - int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; + quint64 seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; m_fileSourceThread->setSamplesCount(seekPoint); - seekPoint *= 4; // + sizeof(FileSink::Header) + seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileSink::Header) m_ifstream.clear(); m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg); } @@ -276,7 +276,7 @@ void FileSourceInput::setCenterFrequency(qint64 centerFrequency) } } -std::time_t FileSourceInput::getStartingTimeStamp() const +quint64 FileSourceInput::getStartingTimeStamp() const { return m_startingTimeStamp; } diff --git a/plugins/samplesource/filesource/filesourceinput.h b/plugins/samplesource/filesource/filesourceinput.h index 4f149da7e..cc731bf13 100644 --- a/plugins/samplesource/filesource/filesourceinput.h +++ b/plugins/samplesource/filesource/filesourceinput.h @@ -175,14 +175,14 @@ public: int getSampleRate() const { return m_sampleRate; } quint32 getSampleSize() const { return m_sampleSize; } quint64 getCenterFrequency() const { return m_centerFrequency; } - std::time_t getStartingTimeStamp() const { return m_startingTimeStamp; } - quint32 getRecordLength() const { return m_recordLength; } + quint64 getStartingTimeStamp() const { return m_startingTimeStamp; } + quint64 getRecordLength() const { return m_recordLength; } static MsgReportFileSourceStreamData* create(int sampleRate, quint32 sampleSize, quint64 centerFrequency, - std::time_t startingTimeStamp, - quint32 recordLength) + quint64 startingTimeStamp, + quint64 recordLength) { return new MsgReportFileSourceStreamData(sampleRate, sampleSize, centerFrequency, startingTimeStamp, recordLength); } @@ -191,14 +191,14 @@ public: int m_sampleRate; quint32 m_sampleSize; quint64 m_centerFrequency; - std::time_t m_startingTimeStamp; - quint32 m_recordLength; + quint64 m_startingTimeStamp; + quint64 m_recordLength; MsgReportFileSourceStreamData(int sampleRate, quint32 sampleSize, quint64 centerFrequency, - std::time_t startingTimeStamp, - quint32 recordLength) : + quint64 startingTimeStamp, + quint64 recordLength) : Message(), m_sampleRate(sampleRate), m_sampleSize(sampleSize), @@ -212,17 +212,17 @@ public: MESSAGE_CLASS_DECLARATION public: - std::size_t getSamplesCount() const { return m_samplesCount; } + quint64 getSamplesCount() const { return m_samplesCount; } - static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount) + static MsgReportFileSourceStreamTiming* create(quint64 samplesCount) { return new MsgReportFileSourceStreamTiming(samplesCount); } protected: - std::size_t m_samplesCount; + quint64 m_samplesCount; - MsgReportFileSourceStreamTiming(std::size_t samplesCount) : + MsgReportFileSourceStreamTiming(quint64 samplesCount) : Message(), m_samplesCount(samplesCount) { } @@ -263,7 +263,7 @@ public: virtual int getSampleRate() const; virtual quint64 getCenterFrequency() const; virtual void setCenterFrequency(qint64 centerFrequency); - std::time_t getStartingTimeStamp() const; + quint64 getStartingTimeStamp() const; virtual bool handleMessage(const Message& message); @@ -295,8 +295,8 @@ public: int m_sampleRate; quint32 m_sampleSize; quint64 m_centerFrequency; - quint32 m_recordLength; //!< record length in seconds computed from file size - std::time_t m_startingTimeStamp; + quint64 m_recordLength; //!< record length in seconds computed from file size + quint64 m_startingTimeStamp; const QTimer& m_masterTimer; void openFileStream(); diff --git a/plugins/samplesource/filesource/filesourceplugin.cpp b/plugins/samplesource/filesource/filesourceplugin.cpp index 71a128bc2..2f4de4d84 100644 --- a/plugins/samplesource/filesource/filesourceplugin.cpp +++ b/plugins/samplesource/filesource/filesourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor FileSourcePlugin::m_pluginDescriptor = { QString("File source input"), - QString("4.2.1"), + QString("4.2.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/filesource/filesourcethread.h b/plugins/samplesource/filesource/filesourcethread.h index ef02de9a1..6e26589d7 100644 --- a/plugins/samplesource/filesource/filesourcethread.h +++ b/plugins/samplesource/filesource/filesourcethread.h @@ -44,8 +44,11 @@ public: void setSampleRateAndSize(int samplerate, quint32 samplesize); void setBuffers(std::size_t chunksize); bool isRunning() const { return m_running; } - std::size_t getSamplesCount() const { return m_samplesCount; } - void setSamplesCount(int samplesCount) { m_samplesCount = samplesCount; } + quint64 getSamplesCount() const { + qDebug("FileSourceThread::getSamplesCount: m_samplesCount: %llu", m_samplesCount); + return m_samplesCount; + } + void setSamplesCount(quint64 samplesCount) { m_samplesCount = samplesCount; } void connectTimer(const QTimer& timer); @@ -58,14 +61,14 @@ private: quint8 *m_fileBuf; quint8 *m_convertBuf; std::size_t m_bufsize; - std::size_t m_chunksize; + qint64 m_chunksize; SampleSinkFifo* m_sampleFifo; - std::size_t m_samplesCount; + quint64 m_samplesCount; int m_samplerate; //!< File I/Q stream original sample rate - quint32 m_samplesize; //!< File effective sample size in bits (I or Q). Ex: 16, 24. - quint32 m_samplebytes; //!< Number of bytes used to store a I or Q sample. Ex: 2. 4. - int m_throttlems; + quint64 m_samplesize; //!< File effective sample size in bits (I or Q). Ex: 16, 24. + quint64 m_samplebytes; //!< Number of bytes used to store a I or Q sample. Ex: 2. 4. + qint64 m_throttlems; QElapsedTimer m_elapsedTimer; bool m_throttleToggle; From 707e314cba14831bdbaadc21db0ddfa8c55e0338 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 12 Oct 2018 17:14:06 +0200 Subject: [PATCH 852/956] File Input: REST API: fixed wrong times on report when sample count exceeds int limit (use of int64 and uint64) --- plugins/samplesource/filesource/filesourceinput.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 67929a00e..5f5f51896 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -424,9 +424,9 @@ int FileSourceInput::webapiReportGet( void FileSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { - int t_sec = 0; - int t_msec = 0; - std::size_t samplesCount = 0; + qint64 t_sec = 0; + qint64 t_msec = 0; + quint64 samplesCount = 0; if (m_fileSourceThread) { samplesCount = m_fileSourceThread->getSamplesCount(); @@ -443,10 +443,10 @@ void FileSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& res t = t.addMSecs(t_msec); response.getFileSourceReport()->setElapsedTime(new QString(t.toString("HH:mm:ss.zzz"))); - quint64 startingTimeStampMsec = (quint64) m_startingTimeStamp * 1000LL; + qint64 startingTimeStampMsec = m_startingTimeStamp * 1000LL; QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); - dt = dt.addSecs((quint64) t_sec); - dt = dt.addMSecs((quint64) t_msec); + dt = dt.addSecs(t_sec); + dt = dt.addMSecs(t_msec); response.getFileSourceReport()->setAbsoluteTime(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz"))); QTime recordLength(0, 0, 0, 0); From 1674ab0e29ded87ab9c2e589572fc5fdeda212ec Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 13 Oct 2018 06:29:43 +0200 Subject: [PATCH 853/956] Updated documentation. Extended spectrum number of averaging samples --- debian/changelog | 1 + sdrgui/gui/glspectrumgui.cpp | 2 +- sdrgui/gui/glspectrumgui.ui | 47 +++++++++++++++++++++++++++++++++++- sdrgui/readme.md | 7 ++++-- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/debian/changelog b/debian/changelog index 10534c313..b35bd92f7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,7 @@ sdrangel (4.2.2-1) unstable; urgency=medium * Spectrum: option to get max over a number of FFTs. Implements issue #207 + * File Input: fixed wrong times displays due to 32 bit integer ovevlow. Issue #206 -- Edouard Griffiths, F4EXB Sun, 14 Oct 2018 21:14:18 +0200 diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index d0ba90b1d..cd3d73d1e 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -30,7 +30,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_invert(true), m_averagingMode(AvgModeNone), m_averagingIndex(0), - m_averagingMaxScale(2), + m_averagingMaxScale(5), m_averagingNb(0) { ui->setupUi(this); diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index a874c7068..6c81795fb 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -591,7 +591,7 @@ - 0 + 1 @@ -639,6 +639,51 @@ 1k + + + 2k + + + + + 5k + + + + + 10k + + + + + 20k + + + + + 50k + + + + + 1e5 + + + + + 2e5 + + + + + 5e5 + + + + + 1M + + diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 6ef34bfe7..46554c45f 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -269,11 +269,14 @@ Use this combo to select which averaging mode is applied: - **No**: no averaging. Disables averaging regardless of the number of averaged samples (4.6). This is the default option - **Mov**: moving average. This is a sliding average over the amount of samples specified next (4.6). There is one complete FFT line produced at every FFT sampling period - **Fix**: fixed average. Average is done over the amount of samples specified next (4.6) and a result is produced at the end of the corresponding period then the next block of averaged samples is processed. There is one complete FFT line produced every FFT sampling period multiplied by the number of averaged samples (4.6). The time scale on the waterfall display is updated accordingly. + - **Max**: this is not an averaging but a max hold. It will retain the maximum value over the amount of samples specified next (4.6). Similarly to the fixed average a result is produced at the end of the corresponding period which results in slowing down the waterfall display. The point of this mode is to make outlying short bursts within the "averaging" period stand out. With averaging they would only cause a modest increase and could be missed out.

    4.6. Number of averaged samples

    -Each FFT bin (squared magnitude) is averaged over a number of samples. This combo allows selecting the number of samples between these values: 0 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000). The tooltip mentions the resulting averaging period considering the baseband sample rate and FFT size. -Averaging reduces the noise variance and can be used to better detect weak continuous signals. The fixed averaging mode allows long time monitoring on the waterfall. +Each FFT bin (squared magnitude) is averaged or max'ed over a number of samples. This combo allows selecting the number of samples between these values: 0 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000), 2k, 5k, 10k, 20k, 50k, 1e5 (100000), 2e5, 5e5, 1M (1000000). The tooltip mentions the resulting averaging period considering the baseband sample rate and FFT size. +Averaging reduces the noise variance and can be used to better detect weak continuous signals. The fixed averaging mode allows long time monitoring on the waterfall. The max mode helps showing short bursts that may appear during the "averaging" period. + +☞ Note: The spectrum display is refreshed every 50ms (20 FPS). Setting an averaging time above this value will make sure that a short burst is not missed particularly when using the max mode.

    4.7. Phosphor display stroke decay

    From bb1e3f3933446d9ce5382aff8b0f5c320dbc07b7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 01:16:39 +0200 Subject: [PATCH 854/956] File Input: acceleration and loop handling phase 1 --- .../samplesource/filesource/filesourcegui.cpp | 52 ++++++++++++- .../samplesource/filesource/filesourcegui.h | 4 + .../samplesource/filesource/filesourcegui.ui | 73 +++++++++++++++---- .../filesource/filesourceinput.cpp | 25 ++++++- .../filesource/filesourcesettings.cpp | 62 ++++++++++++++++ .../filesource/filesourcesettings.h | 5 ++ .../filesource/filesourcethread.cpp | 32 +++++--- .../filesource/filesourcethread.h | 34 +++++++-- sdrgui/gui/glspectrumgui.cpp | 10 ++- 9 files changed, 255 insertions(+), 42 deletions(-) diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index 86c2a0ac2..e03ad4e20 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -51,7 +51,8 @@ FileSourceGui::FileSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_samplesCount(0), m_tickCount(0), m_enableNavTime(false), - m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), + m_accelerationMaxScale(1) { ui->setupUi(this); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); @@ -64,8 +65,10 @@ FileSourceGui::FileSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_statusTimer.start(500); displaySettings(); + setAccelerationCombo(); ui->navTimeSlider->setEnabled(false); + ui->acceleration->setEnabled(false); ui->playLoop->setChecked(true); // FIXME: always play in a loop ui->playLoop->setEnabled(false); @@ -279,6 +282,7 @@ void FileSourceGui::on_play_toggled(bool checked) FileSourceInput::MsgConfigureFileSourceWork* message = FileSourceInput::MsgConfigureFileSourceWork::create(checked); m_sampleSource->getInputMessageQueue()->push(message); ui->navTimeSlider->setEnabled(!checked); + ui->acceleration->setEnabled(!checked); m_enableNavTime = !checked; } @@ -305,6 +309,13 @@ void FileSourceGui::on_showFileDialog_clicked(bool checked __attribute__((unused } } +void FileSourceGui::on_acceleration_currentIndexChanged(int index) +{ + m_settings.m_accelerationFactor = FileSourceSettings::getAccelerationValue(index); + FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings); + m_sampleSource->getInputMessageQueue()->push(message); +} + void FileSourceGui::configureFileName() { qDebug() << "FileSourceGui::configureFileName: " << m_fileName.toStdString().c_str(); @@ -369,3 +380,42 @@ void FileSourceGui::tick() m_sampleSource->getInputMessageQueue()->push(message); } } + +void FileSourceGui::setAccelerationCombo() +{ + ui->acceleration->blockSignals(true); + ui->acceleration->clear(); + ui->acceleration->addItem(QString("1")); + + for (unsigned int i = 0; i <= m_accelerationMaxScale; i++) + { + QString s; + int m = pow(10.0, i); + int x = 2*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + x = 5*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + x = 10*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + } + + ui->acceleration->blockSignals(false); +} + +void FileSourceGui::setNumberStr(int n, QString& s) +{ + if (n < 1000) { + s = tr("%1").arg(n); + } else if (n < 100000) { + s = tr("%1k").arg(n/1000); + } else if (n < 1000000) { + s = tr("%1e5").arg(n/100000); + } else if (n < 1000000000) { + s = tr("%1M").arg(n/1000000); + } else { + s = tr("%1G").arg(n/1000000000); + } +} diff --git a/plugins/samplesource/filesource/filesourcegui.h b/plugins/samplesource/filesource/filesourcegui.h index 9ed216613..40ef0cf57 100644 --- a/plugins/samplesource/filesource/filesourcegui.h +++ b/plugins/samplesource/filesource/filesourcegui.h @@ -74,6 +74,7 @@ private: quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_lastEngineState; MessageQueue m_inputMessageQueue; + unsigned int m_accelerationMaxScale; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); @@ -84,6 +85,8 @@ private: void updateWithAcquisition(); void updateWithStreamData(); void updateWithStreamTime(); + void setAccelerationCombo(); + void setNumberStr(int n, QString& s); private slots: void handleInputMessages(); @@ -92,6 +95,7 @@ private slots: void on_play_toggled(bool checked); void on_navTimeSlider_valueChanged(int value); void on_showFileDialog_clicked(bool checked); + void on_acceleration_currentIndexChanged(int index); void updateStatus(); void tick(); }; diff --git a/plugins/samplesource/filesource/filesourcegui.ui b/plugins/samplesource/filesource/filesourcegui.ui index a634995e8..221fb8771 100644 --- a/plugins/samplesource/filesource/filesourcegui.ui +++ b/plugins/samplesource/filesource/filesourcegui.ui @@ -418,6 +418,65 @@
    + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + Acceleration factor + + + + 1 + + + + + 2 + + + + + 5 + + + + + 10 + + + + + 20 + + + + + 50 + + + + + 100 + + + + @@ -528,20 +587,6 @@ - - - - Qt::Horizontal - - - - - - - Qt::Horizontal - - - diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 5f5f51896..e39023412 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -86,7 +86,6 @@ void FileSourceInput::openFileStream() if (fileSize > sizeof(FileRecord::Header)) { - // TODO: add CRC FileRecord::Header header; m_ifstream.seekg(0,std::ios_base::beg); bool crcOK = FileRecord::readHeader(m_ifstream, header); @@ -181,9 +180,8 @@ bool FileSourceInput::start() //openFileStream(); - m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo); - m_fileSourceThread->setSampleRateAndSize(m_sampleRate, m_sampleSize); - m_fileSourceThread->connectTimer(m_masterTimer); + m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue); + m_fileSourceThread->setSampleRateAndSize(m_settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed m_fileSourceThread->startWork(); m_deviceDescription = "FileSource"; @@ -361,6 +359,19 @@ bool FileSourceInput::handleMessage(const Message& message) return true; } + else if (FileSourceThread::MsgReportEOF::match(message)) + { + qDebug() << "FileSourceInput::handleMessage: MsgReportEOF"; + m_fileSourceThread->stopWork(); + + if (m_settings.m_loop) + { + seekFileStream(0); + m_fileSourceThread->startWork(); + } + + return true; + } else { return false; @@ -373,6 +384,12 @@ bool FileSourceInput::applySettings(const FileSourceSettings& settings, bool for m_centerFrequency = settings.m_centerFrequency; } + if ((m_settings.m_accelerationFactor != settings.m_accelerationFactor) || force) + { + QMutexLocker mutexLocker(&m_mutex); + m_fileSourceThread->setSampleRateAndSize(settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed + } + m_settings = settings; return true; } diff --git a/plugins/samplesource/filesource/filesourcesettings.cpp b/plugins/samplesource/filesource/filesourcesettings.cpp index f08701703..c5c2f0442 100644 --- a/plugins/samplesource/filesource/filesourcesettings.cpp +++ b/plugins/samplesource/filesource/filesourcesettings.cpp @@ -18,6 +18,8 @@ #include "filesourcesettings.h" +const unsigned int FileSourceSettings::m_accelerationMaxScale = 1; + FileSourceSettings::FileSourceSettings() { resetToDefaults(); @@ -28,12 +30,16 @@ void FileSourceSettings::resetToDefaults() m_centerFrequency = 435000000; m_sampleRate = 48000; m_fileName = "./test.sdriq"; + m_accelerationFactor = 1; + m_loop = true; } QByteArray FileSourceSettings::serialize() const { SimpleSerializer s(1); s.writeString(1, m_fileName); + s.writeU32(2, m_accelerationFactor); + s.writeBool(3, m_loop); return s.final(); } @@ -48,6 +54,8 @@ bool FileSourceSettings::deserialize(const QByteArray& data) if(d.getVersion() == 1) { d.readString(1, &m_fileName, "./test.sdriq"); + d.readU32(2, &m_accelerationFactor, 1); + d.readBool(3, &m_loop, true); return true; } else { resetToDefaults(); @@ -55,6 +63,60 @@ bool FileSourceSettings::deserialize(const QByteArray& data) } } +int FileSourceSettings::getAccelerationIndex(int accelerationValue) +{ + if (accelerationValue <= 1) { + return 0; + } + + int v = accelerationValue; + int j = 0; + + for (int i = 0; i <= accelerationValue; i++) + { + if (v < 20) + { + if (v < 2) { + j = 0; + } else if (v < 5) { + j = 1; + } else if (v < 10) { + j = 2; + } else { + j = 3; + } + + return 3*i + j; + } + + v /= 10; + } + + return 3*m_accelerationMaxScale + 3; +} + +int FileSourceSettings::getAccelerationValue(int accelerationIndex) +{ + if (accelerationIndex <= 0) { + return 1; + } + + unsigned int v = accelerationIndex - 1; + int m = pow(10.0, v/3 > m_accelerationMaxScale ? m_accelerationMaxScale : v/3); + int x; + + if (v % 3 == 0) { + x = 2; + } else if (v % 3 == 1) { + x = 5; + } else if (v % 3 == 2) { + x = 10; + } + + return x * m; +} + + diff --git a/plugins/samplesource/filesource/filesourcesettings.h b/plugins/samplesource/filesource/filesourcesettings.h index 8bcc065f5..d355e842c 100644 --- a/plugins/samplesource/filesource/filesourcesettings.h +++ b/plugins/samplesource/filesource/filesourcesettings.h @@ -24,6 +24,9 @@ struct FileSourceSettings { quint64 m_centerFrequency; qint32 m_sampleRate; QString m_fileName; + quint32 m_accelerationFactor; + bool m_loop; + static const unsigned int m_accelerationMaxScale; //!< Max power of 10 multiplier to 2,5,10 base ex: 2 -> 2,5,10,20,50,100,200,500,1000 FileSourceSettings(); ~FileSourceSettings() {} @@ -31,6 +34,8 @@ struct FileSourceSettings { void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); + static int getAccelerationIndex(int averaging); + static int getAccelerationValue(int averagingIndex); }; #endif /* PLUGINS_SAMPLESOURCE_FILESOURCE_FILESOURCESETTINGS_H_ */ diff --git a/plugins/samplesource/filesource/filesourcethread.cpp b/plugins/samplesource/filesource/filesourcethread.cpp index d69099bbf..1afa43e30 100644 --- a/plugins/samplesource/filesource/filesourcethread.cpp +++ b/plugins/samplesource/filesource/filesourcethread.cpp @@ -22,8 +22,15 @@ #include "dsp/filerecord.h" #include "filesourcethread.h" #include "dsp/samplesinkfifo.h" +#include "util/messagequeue.h" -FileSourceThread::FileSourceThread(std::ifstream *samplesStream, SampleSinkFifo* sampleFifo, QObject* parent) : +MESSAGE_CLASS_DEFINITION(FileSourceThread::MsgReportEOF, Message) + +FileSourceThread::FileSourceThread(std::ifstream *samplesStream, + SampleSinkFifo* sampleFifo, + const QTimer& timer, + MessageQueue *fileInputMessageQueue, + QObject* parent) : QThread(parent), m_running(false), m_ifstream(samplesStream), @@ -33,6 +40,8 @@ FileSourceThread::FileSourceThread(std::ifstream *samplesStream, SampleSinkFifo* m_chunksize(0), m_sampleFifo(sampleFifo), m_samplesCount(0), + m_timer(timer), + m_fileInputMessageQueue(fileInputMessageQueue), m_samplerate(0), m_samplesize(0), m_samplebytes(0), @@ -70,6 +79,7 @@ void FileSourceThread::startWork() while(!m_running) m_startWaiter.wait(&m_startWaitMutex, 100); m_startWaitMutex.unlock(); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); } else { @@ -80,6 +90,7 @@ void FileSourceThread::startWork() void FileSourceThread::stopWork() { qDebug() << "FileSourceThread::stopWork"; + disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); m_running = false; wait(); } @@ -101,7 +112,6 @@ void FileSourceThread::setSampleRateAndSize(int samplerate, quint32 samplesize) m_samplerate = samplerate; m_samplesize = samplesize; m_samplebytes = m_samplesize > 16 ? sizeof(int32_t) : sizeof(int16_t); - // TODO: implement FF and slow motion here. 2 corresponds to live. 1 is half speed, 4 is double speed m_chunksize = (m_samplerate * 2 * m_samplebytes * m_throttlems) / 1000; setBuffers(m_chunksize); @@ -161,12 +171,6 @@ void FileSourceThread::run() m_running = false; } -void FileSourceThread::connectTimer(const QTimer& timer) -{ - qDebug() << "FileSourceThread::connectTimer"; - connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); -} - void FileSourceThread::tick() { if (m_running) @@ -187,12 +191,16 @@ void FileSourceThread::tick() if (m_ifstream->eof()) { writeToSampleFifo(m_fileBuf, (qint32) m_ifstream->gcount()); + MsgReportEOF *message = MsgReportEOF::create(); + m_fileInputMessageQueue->push(message); //m_sampleFifo->write(m_buf, m_ifstream->gcount()); // TODO: handle loop playback situation - m_ifstream->clear(); - m_ifstream->seekg(sizeof(FileRecord::Header), std::ios::beg); - m_samplesCount = 0; - //stopWork(); + +// m_ifstream->clear(); +// m_ifstream->seekg(sizeof(FileRecord::Header), std::ios::beg); +// m_samplesCount = 0; + + //stopWork(); //m_ifstream->close(); } else diff --git a/plugins/samplesource/filesource/filesourcethread.h b/plugins/samplesource/filesource/filesourcethread.h index 6e26589d7..152fbd00e 100644 --- a/plugins/samplesource/filesource/filesourcethread.h +++ b/plugins/samplesource/filesource/filesourcethread.h @@ -27,16 +27,39 @@ #include #include "dsp/inthalfbandfilter.h" +#include "util/message.h" #define FILESOURCE_THROTTLE_MS 50 class SampleSinkFifo; +class MessageQueue; class FileSourceThread : public QThread { Q_OBJECT public: - FileSourceThread(std::ifstream *samplesStream, SampleSinkFifo* sampleFifo, QObject* parent = NULL); + class MsgReportEOF : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgReportEOF* create() + { + return new MsgReportEOF(); + } + + private: + + MsgReportEOF() : + Message() + { } + }; + + FileSourceThread(std::ifstream *samplesStream, + SampleSinkFifo* sampleFifo, + const QTimer& timer, + MessageQueue *fileInputMessageQueue, + QObject* parent = NULL); ~FileSourceThread(); void startWork(); @@ -44,14 +67,9 @@ public: void setSampleRateAndSize(int samplerate, quint32 samplesize); void setBuffers(std::size_t chunksize); bool isRunning() const { return m_running; } - quint64 getSamplesCount() const { - qDebug("FileSourceThread::getSamplesCount: m_samplesCount: %llu", m_samplesCount); - return m_samplesCount; - } + quint64 getSamplesCount() const { return m_samplesCount; } void setSamplesCount(quint64 samplesCount) { m_samplesCount = samplesCount; } - void connectTimer(const QTimer& timer); - private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; @@ -64,6 +82,8 @@ private: qint64 m_chunksize; SampleSinkFifo* m_sampleFifo; quint64 m_samplesCount; + const QTimer& m_timer; + MessageQueue *m_fileInputMessageQueue; int m_samplerate; //!< File I/Q stream original sample rate quint64 m_samplesize; //!< File effective sample size in bits (I or Q). Ex: 16, 24. diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index cd3d73d1e..25eefc448 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -454,7 +454,7 @@ void GLSpectrumGUI::on_clearSpectrum_clicked(bool checked __attribute__((unused) int GLSpectrumGUI::getAveragingIndex(int averagingValue) const { - if (averagingValue <= 0) { + if (averagingValue <= 1) { return 0; } @@ -487,7 +487,7 @@ int GLSpectrumGUI::getAveragingIndex(int averagingValue) const int GLSpectrumGUI::getAveragingValue(int averagingIndex) const { if (averagingIndex <= 0) { - return 0; + return 1; } int v = averagingIndex - 1; @@ -508,7 +508,7 @@ int GLSpectrumGUI::getAveragingValue(int averagingIndex) const void GLSpectrumGUI::setAveragingCombo() { ui->averaging->clear(); - ui->averaging->addItem(QString("0")); + ui->averaging->addItem(QString("1")); for (int i = 0; i <= m_averagingMaxScale; i++) { @@ -530,8 +530,10 @@ void GLSpectrumGUI::setNumberStr(int n, QString& s) { if (n < 1000) { s = tr("%1").arg(n); - } else if (n < 1000000) { + } else if (n < 100000) { s = tr("%1k").arg(n/1000); + } else if (n < 1000000) { + s = tr("%1e5").arg(n/100000); } else if (n < 1000000000) { s = tr("%1M").arg(n/1000000); } else { From 2c87e670576b2b9b6fceb3dcda2419344e2fa033 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 01:52:40 +0200 Subject: [PATCH 855/956] File Input: loop handling phase 2 --- .../samplesource/filesource/filesourcegui.cpp | 27 +++++++++++++++---- .../filesource/filesourceinput.cpp | 18 ++++++++++++- .../samplesource/filesource/filesourceinput.h | 19 +++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index e03ad4e20..eb6509f58 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -64,13 +64,11 @@ FileSourceGui::FileSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(500); - displaySettings(); setAccelerationCombo(); + displaySettings(); ui->navTimeSlider->setEnabled(false); ui->acceleration->setEnabled(false); - ui->playLoop->setChecked(true); // FIXME: always play in a loop - ui->playLoop->setEnabled(false); m_sampleSource = m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -202,6 +200,17 @@ bool FileSourceGui::handleMessage(const Message& message) return true; } + else if (FileSourceInput::MsgPlayPause::match(message)) + { + FileSourceInput::MsgPlayPause& notif = (FileSourceInput::MsgPlayPause&) message; + bool checked = notif.getPlayPause(); + ui->play->setChecked(checked); + ui->navTimeSlider->setEnabled(!checked); + ui->acceleration->setEnabled(!checked); + m_enableNavTime = !checked; + + return true; + } else if (FileSourceInput::MsgReportHeaderCRC::match(message)) { FileSourceInput::MsgReportHeaderCRC& notif = (FileSourceInput::MsgReportHeaderCRC&) message; @@ -228,15 +237,23 @@ void FileSourceGui::updateSampleRateAndFrequency() void FileSourceGui::displaySettings() { + ui->playLoop->blockSignals(true); + ui->acceleration->blockSignals(true); + ui->playLoop->setChecked(m_settings.m_loop); + ui->acceleration->setCurrentIndex(FileSourceSettings::getAccelerationIndex(m_settings.m_accelerationFactor)); + ui->acceleration->blockSignals(false); + ui->playLoop->blockSignals(false); } void FileSourceGui::sendSettings() { } -void FileSourceGui::on_playLoop_toggled(bool checked __attribute__((unused))) +void FileSourceGui::on_playLoop_toggled(bool checked) { - // TODO: do something about it! + m_settings.m_loop = checked; + FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings); + m_sampleSource->getInputMessageQueue()->push(message); } void FileSourceGui::on_startStop_toggled(bool checked) diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index e39023412..c2fd15b29 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -40,6 +40,7 @@ MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceWork, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceSeek, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceStreamTiming, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgPlayPause, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceAcquisition, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamTiming, Message) @@ -332,7 +333,8 @@ bool FileSourceInput::handleMessage(const Message& message) if (m_fileSourceThread != 0) { - if (getMessageQueueToGUI()) { + if (getMessageQueueToGUI()) + { report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); getMessageQueueToGUI()->push(report); } @@ -364,11 +366,25 @@ bool FileSourceInput::handleMessage(const Message& message) qDebug() << "FileSourceInput::handleMessage: MsgReportEOF"; m_fileSourceThread->stopWork(); + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamTiming *report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); + getMessageQueueToGUI()->push(report); + } + if (m_settings.m_loop) { seekFileStream(0); m_fileSourceThread->startWork(); } + else + { + if (getMessageQueueToGUI()) + { + MsgPlayPause *report = MsgPlayPause::create(false); + getMessageQueueToGUI()->push(report); + } + } return true; } diff --git a/plugins/samplesource/filesource/filesourceinput.h b/plugins/samplesource/filesource/filesourceinput.h index cc731bf13..66c1c501c 100644 --- a/plugins/samplesource/filesource/filesourceinput.h +++ b/plugins/samplesource/filesource/filesourceinput.h @@ -168,6 +168,25 @@ public: { } }; + class MsgPlayPause : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getPlayPause() const { return m_playPause; } + + static MsgPlayPause* create(bool playPause) { + return new MsgPlayPause(playPause); + } + + protected: + bool m_playPause; + + MsgPlayPause(bool playPause) : + Message(), + m_playPause(playPause) + { } + }; + class MsgReportFileSourceStreamData : public Message { MESSAGE_CLASS_DECLARATION From 307ee9ce9eee414b385cd8b7a228413cfc8bcaaa Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 02:25:10 +0200 Subject: [PATCH 856/956] File Input: fixed segfault when thread is not (yet) allocated --- .../samplesource/filesource/filesourcegui.cpp | 31 ++++++++++--------- .../samplesource/filesource/filesourcegui.h | 1 - .../filesource/filesourceinput.cpp | 7 +++-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index eb6509f58..45e7b8743 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -51,8 +51,7 @@ FileSourceGui::FileSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_samplesCount(0), m_tickCount(0), m_enableNavTime(false), - m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), - m_accelerationMaxScale(1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { ui->setupUi(this); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); @@ -164,9 +163,7 @@ bool FileSourceGui::handleMessage(const Message& message) { const FileSourceInput::MsgConfigureFileSource& cfg = (FileSourceInput::MsgConfigureFileSource&) message; m_settings = cfg.getSettings(); - blockApplySettings(true); displaySettings(); - blockApplySettings(false); return true; } else if (FileSourceInput::MsgReportFileSourceAcquisition::match(message)) @@ -237,12 +234,10 @@ void FileSourceGui::updateSampleRateAndFrequency() void FileSourceGui::displaySettings() { - ui->playLoop->blockSignals(true); - ui->acceleration->blockSignals(true); + blockApplySettings(true); ui->playLoop->setChecked(m_settings.m_loop); ui->acceleration->setCurrentIndex(FileSourceSettings::getAccelerationIndex(m_settings.m_accelerationFactor)); - ui->acceleration->blockSignals(false); - ui->playLoop->blockSignals(false); + blockApplySettings(false); } void FileSourceGui::sendSettings() @@ -251,9 +246,12 @@ void FileSourceGui::sendSettings() void FileSourceGui::on_playLoop_toggled(bool checked) { - m_settings.m_loop = checked; - FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings); - m_sampleSource->getInputMessageQueue()->push(message); + if (m_doApplySettings) + { + m_settings.m_loop = checked; + FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings); + m_sampleSource->getInputMessageQueue()->push(message); + } } void FileSourceGui::on_startStop_toggled(bool checked) @@ -328,9 +326,12 @@ void FileSourceGui::on_showFileDialog_clicked(bool checked __attribute__((unused void FileSourceGui::on_acceleration_currentIndexChanged(int index) { - m_settings.m_accelerationFactor = FileSourceSettings::getAccelerationValue(index); - FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings); - m_sampleSource->getInputMessageQueue()->push(message); + if (m_doApplySettings) + { + m_settings.m_accelerationFactor = FileSourceSettings::getAccelerationValue(index); + FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings); + m_sampleSource->getInputMessageQueue()->push(message); + } } void FileSourceGui::configureFileName() @@ -404,7 +405,7 @@ void FileSourceGui::setAccelerationCombo() ui->acceleration->clear(); ui->acceleration->addItem(QString("1")); - for (unsigned int i = 0; i <= m_accelerationMaxScale; i++) + for (unsigned int i = 0; i <= FileSourceSettings::m_accelerationMaxScale; i++) { QString s; int m = pow(10.0, i); diff --git a/plugins/samplesource/filesource/filesourcegui.h b/plugins/samplesource/filesource/filesourcegui.h index 40ef0cf57..2e43c5b42 100644 --- a/plugins/samplesource/filesource/filesourcegui.h +++ b/plugins/samplesource/filesource/filesourcegui.h @@ -74,7 +74,6 @@ private: quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_lastEngineState; MessageQueue m_inputMessageQueue; - unsigned int m_accelerationMaxScale; void blockApplySettings(bool block) { m_doApplySettings = !block; } void displaySettings(); diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index c2fd15b29..43a4c803c 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -402,8 +402,11 @@ bool FileSourceInput::applySettings(const FileSourceSettings& settings, bool for if ((m_settings.m_accelerationFactor != settings.m_accelerationFactor) || force) { - QMutexLocker mutexLocker(&m_mutex); - m_fileSourceThread->setSampleRateAndSize(settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed + if (m_fileSourceThread) + { + QMutexLocker mutexLocker(&m_mutex); + m_fileSourceThread->setSampleRateAndSize(settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed + } } m_settings = settings; From b0b2af252cab32e4d22acd44b0e745b3fee0c1f9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 02:25:51 +0200 Subject: [PATCH 857/956] File Input: dos2unix conversion --- .../filesource/filesourceinput.cpp | 994 +++++++++--------- 1 file changed, 497 insertions(+), 497 deletions(-) diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 43a4c803c..af33296f3 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -1,497 +1,497 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 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 // -// // -// 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 "SWGDeviceSettings.h" -#include "SWGFileSourceSettings.h" -#include "SWGDeviceState.h" -#include "SWGDeviceReport.h" -#include "SWGFileSourceSettings.h" - -#include "util/simpleserializer.h" -#include "dsp/dspcommands.h" -#include "dsp/dspengine.h" -#include "dsp/filerecord.h" -#include "device/devicesourceapi.h" - -#include "filesourceinput.h" -#include "filesourcethread.h" - -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSource, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceName, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceWork, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceStreamTiming, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgPlayPause, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceAcquisition, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamData, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamTiming, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportHeaderCRC, Message) - -FileSourceInput::FileSourceInput(DeviceSourceAPI *deviceAPI) : - m_deviceAPI(deviceAPI), - m_settings(), - m_fileSourceThread(NULL), - m_deviceDescription(), - m_fileName("..."), - m_sampleRate(0), - m_sampleSize(0), - m_centerFrequency(0), - m_recordLength(0), - m_startingTimeStamp(0), - m_masterTimer(deviceAPI->getMasterTimer()) -{ - qDebug("FileSourceInput::FileSourceInput: device source engine: %p", m_deviceAPI->getDeviceSourceEngine()); - qDebug("FileSourceInput::FileSourceInput: device source engine message queue: %p", m_deviceAPI->getDeviceEngineInputMessageQueue()); - qDebug("FileSourceInput::FileSourceInput: device source: %p", m_deviceAPI->getDeviceSourceEngine()->getSource()); -} - -FileSourceInput::~FileSourceInput() -{ - stop(); -} - -void FileSourceInput::destroy() -{ - delete this; -} - -void FileSourceInput::openFileStream() -{ - //stopInput(); - - if (m_ifstream.is_open()) { - m_ifstream.close(); - } - - m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); - quint64 fileSize = m_ifstream.tellg(); - - if (fileSize > sizeof(FileRecord::Header)) - { - FileRecord::Header header; - m_ifstream.seekg(0,std::ios_base::beg); - bool crcOK = FileRecord::readHeader(m_ifstream, header); - m_sampleRate = header.sampleRate; - m_centerFrequency = header.centerFrequency; - m_startingTimeStamp = header.startTimeStamp; - m_sampleSize = header.sampleSize; - QString crcHex = QString("%1").arg(header.crc32 , 0, 16); - - if (crcOK) - { - qDebug("FileSourceInput::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex)); - m_recordLength = (fileSize - sizeof(FileRecord::Header)) / ((m_sampleSize == 24 ? 8 : 4) * m_sampleRate); - } - else - { - qCritical("FileSourceInput::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex)); - m_recordLength = 0; - } - - if (getMessageQueueToGUI()) { - MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(crcOK); - getMessageQueueToGUI()->push(report); - } - } - else - { - m_recordLength = 0; - } - - qDebug() << "FileSourceInput::openFileStream: " << m_fileName.toStdString().c_str() - << " fileSize: " << fileSize << " bytes" - << " length: " << m_recordLength << " seconds" - << " sample rate: " << m_sampleRate << " S/s" - << " center frequency: " << m_centerFrequency << " Hz" - << " sample size: " << m_sampleSize << " bits"; - - if (getMessageQueueToGUI()) { - MsgReportFileSourceStreamData *report = MsgReportFileSourceStreamData::create(m_sampleRate, - m_sampleSize, - m_centerFrequency, - m_startingTimeStamp, - m_recordLength); // file stream data - getMessageQueueToGUI()->push(report); - } - - if (m_recordLength == 0) { - m_ifstream.close(); - } -} - -void FileSourceInput::seekFileStream(int seekPercentage) -{ - QMutexLocker mutexLocker(&m_mutex); - - if ((m_ifstream.is_open()) && m_fileSourceThread && !m_fileSourceThread->isRunning()) - { - quint64 seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; - m_fileSourceThread->setSamplesCount(seekPoint); - seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileSink::Header) - m_ifstream.clear(); - m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg); - } -} - -void FileSourceInput::init() -{ - DSPSignalNotification *notif = new DSPSignalNotification(m_settings.m_sampleRate, m_settings.m_centerFrequency); - m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); -} - -bool FileSourceInput::start() -{ - if (!m_ifstream.is_open()) - { - qWarning("FileSourceInput::start: file not open. not starting"); - return false; - } - - QMutexLocker mutexLocker(&m_mutex); - qDebug() << "FileSourceInput::start"; - - if (m_ifstream.tellg() != 0) { - m_ifstream.clear(); - m_ifstream.seekg(sizeof(FileRecord::Header), std::ios::beg); - } - - if(!m_sampleFifo.setSize(m_sampleRate * sizeof(Sample))) { - qCritical("Could not allocate SampleFifo"); - return false; - } - - //openFileStream(); - - m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue); - m_fileSourceThread->setSampleRateAndSize(m_settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed - m_fileSourceThread->startWork(); - m_deviceDescription = "FileSource"; - - mutexLocker.unlock(); - //applySettings(m_generalSettings, m_settings, true); - qDebug("FileSourceInput::startInput: started"); - - if (getMessageQueueToGUI()) { - MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(true); // acquisition on - getMessageQueueToGUI()->push(report); - } - - return true; -} - -void FileSourceInput::stop() -{ - qDebug() << "FileSourceInput::stop"; - QMutexLocker mutexLocker(&m_mutex); - - if(m_fileSourceThread != 0) - { - m_fileSourceThread->stopWork(); - delete m_fileSourceThread; - m_fileSourceThread = 0; - } - - m_deviceDescription.clear(); - - if (getMessageQueueToGUI()) { - MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(false); // acquisition off - getMessageQueueToGUI()->push(report); - } -} - -QByteArray FileSourceInput::serialize() const -{ - return m_settings.serialize(); -} - -bool FileSourceInput::deserialize(const QByteArray& data) -{ - bool success = true; - - if (!m_settings.deserialize(data)) - { - m_settings.resetToDefaults(); - success = false; - } - - MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings); - m_inputMessageQueue.push(message); - - if (getMessageQueueToGUI()) - { - MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings); - getMessageQueueToGUI()->push(messageToGUI); - } - - return success; -} - -const QString& FileSourceInput::getDeviceDescription() const -{ - return m_deviceDescription; -} - -int FileSourceInput::getSampleRate() const -{ - return m_sampleRate; -} - -quint64 FileSourceInput::getCenterFrequency() const -{ - return m_centerFrequency; -} - -void FileSourceInput::setCenterFrequency(qint64 centerFrequency) -{ - FileSourceSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; - - MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings); - m_inputMessageQueue.push(message); - - if (getMessageQueueToGUI()) - { - MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings); - getMessageQueueToGUI()->push(messageToGUI); - } -} - -quint64 FileSourceInput::getStartingTimeStamp() const -{ - return m_startingTimeStamp; -} - -bool FileSourceInput::handleMessage(const Message& message) -{ - if (MsgConfigureFileSource::match(message)) - { - MsgConfigureFileSource& conf = (MsgConfigureFileSource&) message; - FileSourceSettings settings = conf.getSettings(); - applySettings(settings); - return true; - } - else if (MsgConfigureFileSourceName::match(message)) - { - MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) message; - m_fileName = conf.getFileName(); - openFileStream(); - return true; - } - else if (MsgConfigureFileSourceWork::match(message)) - { - MsgConfigureFileSourceWork& conf = (MsgConfigureFileSourceWork&) message; - bool working = conf.isWorking(); - - if (m_fileSourceThread != 0) - { - if (working) - { - m_fileSourceThread->startWork(); - /* - MsgReportFileSourceStreamTiming *report = - MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); - getOutputMessageQueueToGUI()->push(report);*/ - } - else - { - m_fileSourceThread->stopWork(); - } - } - - return true; - } - else if (MsgConfigureFileSourceSeek::match(message)) - { - MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) message; - int seekPercentage = conf.getPercentage(); - seekFileStream(seekPercentage); - - return true; - } - else if (MsgConfigureFileSourceStreamTiming::match(message)) - { - MsgReportFileSourceStreamTiming *report; - - if (m_fileSourceThread != 0) - { - if (getMessageQueueToGUI()) - { - report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); - getMessageQueueToGUI()->push(report); - } - } - - return true; - } - else if (MsgStartStop::match(message)) - { - MsgStartStop& cmd = (MsgStartStop&) message; - qDebug() << "FileSourceInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); - - if (cmd.getStartStop()) - { - if (m_deviceAPI->initAcquisition()) - { - m_deviceAPI->startAcquisition(); - } - } - else - { - m_deviceAPI->stopAcquisition(); - } - - return true; - } - else if (FileSourceThread::MsgReportEOF::match(message)) - { - qDebug() << "FileSourceInput::handleMessage: MsgReportEOF"; - m_fileSourceThread->stopWork(); - - if (getMessageQueueToGUI()) - { - MsgReportFileSourceStreamTiming *report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); - getMessageQueueToGUI()->push(report); - } - - if (m_settings.m_loop) - { - seekFileStream(0); - m_fileSourceThread->startWork(); - } - else - { - if (getMessageQueueToGUI()) - { - MsgPlayPause *report = MsgPlayPause::create(false); - getMessageQueueToGUI()->push(report); - } - } - - return true; - } - else - { - return false; - } -} - -bool FileSourceInput::applySettings(const FileSourceSettings& settings, bool force) -{ - if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || force) { - m_centerFrequency = settings.m_centerFrequency; - } - - if ((m_settings.m_accelerationFactor != settings.m_accelerationFactor) || force) - { - if (m_fileSourceThread) - { - QMutexLocker mutexLocker(&m_mutex); - m_fileSourceThread->setSampleRateAndSize(settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed - } - } - - m_settings = settings; - return true; -} - -int FileSourceInput::webapiSettingsGet( - SWGSDRangel::SWGDeviceSettings& response, - QString& errorMessage __attribute__((unused))) -{ - response.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings()); - response.getFileSourceSettings()->setFileName(new QString(m_settings.m_fileName)); - return 200; -} - -int FileSourceInput::webapiRunGet( - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage __attribute__((unused))) -{ - m_deviceAPI->getDeviceEngineStateStr(*response.getState()); - return 200; -} - -int FileSourceInput::webapiRun( - bool run, - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage __attribute__((unused))) -{ - m_deviceAPI->getDeviceEngineStateStr(*response.getState()); - MsgStartStop *message = MsgStartStop::create(run); - m_inputMessageQueue.push(message); - - if (getMessageQueueToGUI()) // forward to GUI if any - { - MsgStartStop *msgToGUI = MsgStartStop::create(run); - getMessageQueueToGUI()->push(msgToGUI); - } - - return 200; -} - -int FileSourceInput::webapiReportGet( - SWGSDRangel::SWGDeviceReport& response, - QString& errorMessage __attribute__((unused))) -{ - response.setFileSourceReport(new SWGSDRangel::SWGFileSourceReport()); - response.getFileSourceReport()->init(); - webapiFormatDeviceReport(response); - return 200; -} - -void FileSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) -{ - qint64 t_sec = 0; - qint64 t_msec = 0; - quint64 samplesCount = 0; - - if (m_fileSourceThread) { - samplesCount = m_fileSourceThread->getSamplesCount(); - } - - if (m_sampleRate > 0) - { - t_sec = samplesCount / m_sampleRate; - t_msec = (samplesCount - (t_sec * m_sampleRate)) * 1000 / m_sampleRate; - } - - QTime t(0, 0, 0, 0); - t = t.addSecs(t_sec); - t = t.addMSecs(t_msec); - response.getFileSourceReport()->setElapsedTime(new QString(t.toString("HH:mm:ss.zzz"))); - - qint64 startingTimeStampMsec = m_startingTimeStamp * 1000LL; - QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); - dt = dt.addSecs(t_sec); - dt = dt.addMSecs(t_msec); - response.getFileSourceReport()->setAbsoluteTime(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz"))); - - QTime recordLength(0, 0, 0, 0); - recordLength = recordLength.addSecs(m_recordLength); - response.getFileSourceReport()->setDurationTime(new QString(recordLength.toString("HH:mm:ss"))); - - response.getFileSourceReport()->setFileName(new QString(m_fileName)); - response.getFileSourceReport()->setSampleRate(m_sampleRate); - response.getFileSourceReport()->setSampleSize(m_sampleSize); -} - - +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 // +// // +// 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 "SWGDeviceSettings.h" +#include "SWGFileSourceSettings.h" +#include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGFileSourceSettings.h" + +#include "util/simpleserializer.h" +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "dsp/filerecord.h" +#include "device/devicesourceapi.h" + +#include "filesourceinput.h" +#include "filesourcethread.h" + +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSource, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceName, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceWork, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceSeek, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgPlayPause, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceAcquisition, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamData, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportHeaderCRC, Message) + +FileSourceInput::FileSourceInput(DeviceSourceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_fileSourceThread(NULL), + m_deviceDescription(), + m_fileName("..."), + m_sampleRate(0), + m_sampleSize(0), + m_centerFrequency(0), + m_recordLength(0), + m_startingTimeStamp(0), + m_masterTimer(deviceAPI->getMasterTimer()) +{ + qDebug("FileSourceInput::FileSourceInput: device source engine: %p", m_deviceAPI->getDeviceSourceEngine()); + qDebug("FileSourceInput::FileSourceInput: device source engine message queue: %p", m_deviceAPI->getDeviceEngineInputMessageQueue()); + qDebug("FileSourceInput::FileSourceInput: device source: %p", m_deviceAPI->getDeviceSourceEngine()->getSource()); +} + +FileSourceInput::~FileSourceInput() +{ + stop(); +} + +void FileSourceInput::destroy() +{ + delete this; +} + +void FileSourceInput::openFileStream() +{ + //stopInput(); + + if (m_ifstream.is_open()) { + m_ifstream.close(); + } + + m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); + quint64 fileSize = m_ifstream.tellg(); + + if (fileSize > sizeof(FileRecord::Header)) + { + FileRecord::Header header; + m_ifstream.seekg(0,std::ios_base::beg); + bool crcOK = FileRecord::readHeader(m_ifstream, header); + m_sampleRate = header.sampleRate; + m_centerFrequency = header.centerFrequency; + m_startingTimeStamp = header.startTimeStamp; + m_sampleSize = header.sampleSize; + QString crcHex = QString("%1").arg(header.crc32 , 0, 16); + + if (crcOK) + { + qDebug("FileSourceInput::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex)); + m_recordLength = (fileSize - sizeof(FileRecord::Header)) / ((m_sampleSize == 24 ? 8 : 4) * m_sampleRate); + } + else + { + qCritical("FileSourceInput::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex)); + m_recordLength = 0; + } + + if (getMessageQueueToGUI()) { + MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(crcOK); + getMessageQueueToGUI()->push(report); + } + } + else + { + m_recordLength = 0; + } + + qDebug() << "FileSourceInput::openFileStream: " << m_fileName.toStdString().c_str() + << " fileSize: " << fileSize << " bytes" + << " length: " << m_recordLength << " seconds" + << " sample rate: " << m_sampleRate << " S/s" + << " center frequency: " << m_centerFrequency << " Hz" + << " sample size: " << m_sampleSize << " bits"; + + if (getMessageQueueToGUI()) { + MsgReportFileSourceStreamData *report = MsgReportFileSourceStreamData::create(m_sampleRate, + m_sampleSize, + m_centerFrequency, + m_startingTimeStamp, + m_recordLength); // file stream data + getMessageQueueToGUI()->push(report); + } + + if (m_recordLength == 0) { + m_ifstream.close(); + } +} + +void FileSourceInput::seekFileStream(int seekPercentage) +{ + QMutexLocker mutexLocker(&m_mutex); + + if ((m_ifstream.is_open()) && m_fileSourceThread && !m_fileSourceThread->isRunning()) + { + quint64 seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; + m_fileSourceThread->setSamplesCount(seekPoint); + seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileSink::Header) + m_ifstream.clear(); + m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg); + } +} + +void FileSourceInput::init() +{ + DSPSignalNotification *notif = new DSPSignalNotification(m_settings.m_sampleRate, m_settings.m_centerFrequency); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); +} + +bool FileSourceInput::start() +{ + if (!m_ifstream.is_open()) + { + qWarning("FileSourceInput::start: file not open. not starting"); + return false; + } + + QMutexLocker mutexLocker(&m_mutex); + qDebug() << "FileSourceInput::start"; + + if (m_ifstream.tellg() != 0) { + m_ifstream.clear(); + m_ifstream.seekg(sizeof(FileRecord::Header), std::ios::beg); + } + + if(!m_sampleFifo.setSize(m_sampleRate * sizeof(Sample))) { + qCritical("Could not allocate SampleFifo"); + return false; + } + + //openFileStream(); + + m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue); + m_fileSourceThread->setSampleRateAndSize(m_settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed + m_fileSourceThread->startWork(); + m_deviceDescription = "FileSource"; + + mutexLocker.unlock(); + //applySettings(m_generalSettings, m_settings, true); + qDebug("FileSourceInput::startInput: started"); + + if (getMessageQueueToGUI()) { + MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(true); // acquisition on + getMessageQueueToGUI()->push(report); + } + + return true; +} + +void FileSourceInput::stop() +{ + qDebug() << "FileSourceInput::stop"; + QMutexLocker mutexLocker(&m_mutex); + + if(m_fileSourceThread != 0) + { + m_fileSourceThread->stopWork(); + delete m_fileSourceThread; + m_fileSourceThread = 0; + } + + m_deviceDescription.clear(); + + if (getMessageQueueToGUI()) { + MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(false); // acquisition off + getMessageQueueToGUI()->push(report); + } +} + +QByteArray FileSourceInput::serialize() const +{ + return m_settings.serialize(); +} + +bool FileSourceInput::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) + { + MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings); + getMessageQueueToGUI()->push(messageToGUI); + } + + return success; +} + +const QString& FileSourceInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int FileSourceInput::getSampleRate() const +{ + return m_sampleRate; +} + +quint64 FileSourceInput::getCenterFrequency() const +{ + return m_centerFrequency; +} + +void FileSourceInput::setCenterFrequency(qint64 centerFrequency) +{ + FileSourceSettings settings = m_settings; + settings.m_centerFrequency = centerFrequency; + + MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) + { + MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings); + getMessageQueueToGUI()->push(messageToGUI); + } +} + +quint64 FileSourceInput::getStartingTimeStamp() const +{ + return m_startingTimeStamp; +} + +bool FileSourceInput::handleMessage(const Message& message) +{ + if (MsgConfigureFileSource::match(message)) + { + MsgConfigureFileSource& conf = (MsgConfigureFileSource&) message; + FileSourceSettings settings = conf.getSettings(); + applySettings(settings); + return true; + } + else if (MsgConfigureFileSourceName::match(message)) + { + MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) message; + m_fileName = conf.getFileName(); + openFileStream(); + return true; + } + else if (MsgConfigureFileSourceWork::match(message)) + { + MsgConfigureFileSourceWork& conf = (MsgConfigureFileSourceWork&) message; + bool working = conf.isWorking(); + + if (m_fileSourceThread != 0) + { + if (working) + { + m_fileSourceThread->startWork(); + /* + MsgReportFileSourceStreamTiming *report = + MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); + getOutputMessageQueueToGUI()->push(report);*/ + } + else + { + m_fileSourceThread->stopWork(); + } + } + + return true; + } + else if (MsgConfigureFileSourceSeek::match(message)) + { + MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) message; + int seekPercentage = conf.getPercentage(); + seekFileStream(seekPercentage); + + return true; + } + else if (MsgConfigureFileSourceStreamTiming::match(message)) + { + MsgReportFileSourceStreamTiming *report; + + if (m_fileSourceThread != 0) + { + if (getMessageQueueToGUI()) + { + report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); + getMessageQueueToGUI()->push(report); + } + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "FileSourceInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initAcquisition()) + { + m_deviceAPI->startAcquisition(); + } + } + else + { + m_deviceAPI->stopAcquisition(); + } + + return true; + } + else if (FileSourceThread::MsgReportEOF::match(message)) + { + qDebug() << "FileSourceInput::handleMessage: MsgReportEOF"; + m_fileSourceThread->stopWork(); + + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamTiming *report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); + getMessageQueueToGUI()->push(report); + } + + if (m_settings.m_loop) + { + seekFileStream(0); + m_fileSourceThread->startWork(); + } + else + { + if (getMessageQueueToGUI()) + { + MsgPlayPause *report = MsgPlayPause::create(false); + getMessageQueueToGUI()->push(report); + } + } + + return true; + } + else + { + return false; + } +} + +bool FileSourceInput::applySettings(const FileSourceSettings& settings, bool force) +{ + if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || force) { + m_centerFrequency = settings.m_centerFrequency; + } + + if ((m_settings.m_accelerationFactor != settings.m_accelerationFactor) || force) + { + if (m_fileSourceThread) + { + QMutexLocker mutexLocker(&m_mutex); + m_fileSourceThread->setSampleRateAndSize(settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed + } + } + + m_settings = settings; + return true; +} + +int FileSourceInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings()); + response.getFileSourceSettings()->setFileName(new QString(m_settings.m_fileName)); + return 200; +} + +int FileSourceInput::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int FileSourceInput::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + getMessageQueueToGUI()->push(msgToGUI); + } + + return 200; +} + +int FileSourceInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFileSourceReport(new SWGSDRangel::SWGFileSourceReport()); + response.getFileSourceReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void FileSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + qint64 t_sec = 0; + qint64 t_msec = 0; + quint64 samplesCount = 0; + + if (m_fileSourceThread) { + samplesCount = m_fileSourceThread->getSamplesCount(); + } + + if (m_sampleRate > 0) + { + t_sec = samplesCount / m_sampleRate; + t_msec = (samplesCount - (t_sec * m_sampleRate)) * 1000 / m_sampleRate; + } + + QTime t(0, 0, 0, 0); + t = t.addSecs(t_sec); + t = t.addMSecs(t_msec); + response.getFileSourceReport()->setElapsedTime(new QString(t.toString("HH:mm:ss.zzz"))); + + qint64 startingTimeStampMsec = m_startingTimeStamp * 1000LL; + QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); + dt = dt.addSecs(t_sec); + dt = dt.addMSecs(t_msec); + response.getFileSourceReport()->setAbsoluteTime(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz"))); + + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addSecs(m_recordLength); + response.getFileSourceReport()->setDurationTime(new QString(recordLength.toString("HH:mm:ss"))); + + response.getFileSourceReport()->setFileName(new QString(m_fileName)); + response.getFileSourceReport()->setSampleRate(m_sampleRate); + response.getFileSourceReport()->setSampleSize(m_sampleSize); +} + + From a6557cd4f9b1e00cfd27a433040f6ad496aa0a7e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 02:38:24 +0200 Subject: [PATCH 858/956] File Input: use millis instead of percent for navigation slider --- plugins/samplesource/filesource/filesourcegui.cpp | 4 ++-- plugins/samplesource/filesource/filesourcegui.ui | 2 +- .../samplesource/filesource/filesourceinput.cpp | 8 ++++---- plugins/samplesource/filesource/filesourceinput.h | 14 +++++++------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index 45e7b8743..5d7524b2e 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -303,7 +303,7 @@ void FileSourceGui::on_play_toggled(bool checked) void FileSourceGui::on_navTimeSlider_valueChanged(int value) { - if (m_enableNavTime && ((value >= 0) && (value <= 100))) + if (m_enableNavTime && ((value >= 0) && (value <= 1000))) { FileSourceInput::MsgConfigureFileSourceSeek* message = FileSourceInput::MsgConfigureFileSourceSeek::create(value); m_sampleSource->getInputMessageQueue()->push(message); @@ -387,7 +387,7 @@ void FileSourceGui::updateWithStreamTime() if (!m_enableNavTime) { float posRatio = (float) t_sec / (float) m_recordLength; - ui->navTimeSlider->setValue((int) (posRatio * 100.0)); + ui->navTimeSlider->setValue((int) (posRatio * 1000.0)); } } diff --git a/plugins/samplesource/filesource/filesourcegui.ui b/plugins/samplesource/filesource/filesourcegui.ui index 221fb8771..f40501a14 100644 --- a/plugins/samplesource/filesource/filesourcegui.ui +++ b/plugins/samplesource/filesource/filesourcegui.ui @@ -558,7 +558,7 @@ Time navigator - 100 + 1000 1 diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index af33296f3..4f8ed2175 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -138,13 +138,13 @@ void FileSourceInput::openFileStream() } } -void FileSourceInput::seekFileStream(int seekPercentage) +void FileSourceInput::seekFileStream(int seekMillis) { QMutexLocker mutexLocker(&m_mutex); if ((m_ifstream.is_open()) && m_fileSourceThread && !m_fileSourceThread->isRunning()) { - quint64 seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; + quint64 seekPoint = ((m_recordLength * seekMillis) / 1000) * m_sampleRate; m_fileSourceThread->setSamplesCount(seekPoint); seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileSink::Header) m_ifstream.clear(); @@ -322,8 +322,8 @@ bool FileSourceInput::handleMessage(const Message& message) else if (MsgConfigureFileSourceSeek::match(message)) { MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) message; - int seekPercentage = conf.getPercentage(); - seekFileStream(seekPercentage); + int seekMillis = conf.getMillis(); + seekFileStream(seekMillis); return true; } diff --git a/plugins/samplesource/filesource/filesourceinput.h b/plugins/samplesource/filesource/filesourceinput.h index 66c1c501c..b5683d65a 100644 --- a/plugins/samplesource/filesource/filesourceinput.h +++ b/plugins/samplesource/filesource/filesourceinput.h @@ -113,19 +113,19 @@ public: MESSAGE_CLASS_DECLARATION public: - int getPercentage() const { return m_seekPercentage; } + int getMillis() const { return m_seekMillis; } - static MsgConfigureFileSourceSeek* create(int seekPercentage) + static MsgConfigureFileSourceSeek* create(int seekMillis) { - return new MsgConfigureFileSourceSeek(seekPercentage); + return new MsgConfigureFileSourceSeek(seekMillis); } protected: - int m_seekPercentage; //!< percentage of seek position from the beginning 0..100 + int m_seekMillis; //!< millis of seek position from the beginning 0..1000 - MsgConfigureFileSourceSeek(int seekPercentage) : + MsgConfigureFileSourceSeek(int seekMillis) : Message(), - m_seekPercentage(seekPercentage) + m_seekMillis(seekMillis) { } }; @@ -319,7 +319,7 @@ public: const QTimer& m_masterTimer; void openFileStream(); - void seekFileStream(int seekPercentage); + void seekFileStream(int seekMillis); bool applySettings(const FileSourceSettings& settings, bool force = false); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; From db4ba51360a6de8cb0551af676b258cbe5c74190 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 02:38:53 +0200 Subject: [PATCH 859/956] File Input: dos2unix conversion --- .../samplesource/filesource/filesourceinput.h | 654 +++++++++--------- 1 file changed, 327 insertions(+), 327 deletions(-) diff --git a/plugins/samplesource/filesource/filesourceinput.h b/plugins/samplesource/filesource/filesourceinput.h index b5683d65a..42e074672 100644 --- a/plugins/samplesource/filesource/filesourceinput.h +++ b/plugins/samplesource/filesource/filesourceinput.h @@ -1,327 +1,327 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 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 // -// // -// 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_FILESOURCEINPUT_H -#define INCLUDE_FILESOURCEINPUT_H - -#include -#include -#include -#include -#include -#include - -#include -#include "filesourcesettings.h" - -class FileSourceThread; -class DeviceSourceAPI; - -class FileSourceInput : public DeviceSampleSource { -public: - class MsgConfigureFileSource : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const FileSourceSettings& getSettings() const { return m_settings; } - - static MsgConfigureFileSource* create(const FileSourceSettings& settings) - { - return new MsgConfigureFileSource(settings); - } - - private: - FileSourceSettings m_settings; - - MsgConfigureFileSource(const FileSourceSettings& settings) : - Message(), - m_settings(settings) - { } - }; - - class MsgConfigureFileSourceName : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const QString& getFileName() const { return m_fileName; } - - static MsgConfigureFileSourceName* create(const QString& fileName) - { - return new MsgConfigureFileSourceName(fileName); - } - - private: - QString m_fileName; - - MsgConfigureFileSourceName(const QString& fileName) : - Message(), - m_fileName(fileName) - { } - }; - - class MsgConfigureFileSourceWork : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool isWorking() const { return m_working; } - - static MsgConfigureFileSourceWork* create(bool working) - { - return new MsgConfigureFileSourceWork(working); - } - - private: - bool m_working; - - MsgConfigureFileSourceWork(bool working) : - Message(), - m_working(working) - { } - }; - - class MsgConfigureFileSourceStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgConfigureFileSourceStreamTiming* create() - { - return new MsgConfigureFileSourceStreamTiming(); - } - - private: - - MsgConfigureFileSourceStreamTiming() : - Message() - { } - }; - - class MsgConfigureFileSourceSeek : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getMillis() const { return m_seekMillis; } - - static MsgConfigureFileSourceSeek* create(int seekMillis) - { - return new MsgConfigureFileSourceSeek(seekMillis); - } - - protected: - int m_seekMillis; //!< millis of seek position from the beginning 0..1000 - - MsgConfigureFileSourceSeek(int seekMillis) : - Message(), - m_seekMillis(seekMillis) - { } - }; - - class MsgReportFileSourceAcquisition : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getAcquisition() const { return m_acquisition; } - - static MsgReportFileSourceAcquisition* create(bool acquisition) - { - return new MsgReportFileSourceAcquisition(acquisition); - } - - protected: - bool m_acquisition; - - MsgReportFileSourceAcquisition(bool acquisition) : - Message(), - m_acquisition(acquisition) - { } - }; - - 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) - { } - }; - - class MsgPlayPause : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getPlayPause() const { return m_playPause; } - - static MsgPlayPause* create(bool playPause) { - return new MsgPlayPause(playPause); - } - - protected: - bool m_playPause; - - MsgPlayPause(bool playPause) : - Message(), - m_playPause(playPause) - { } - }; - - class MsgReportFileSourceStreamData : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - quint32 getSampleSize() const { return m_sampleSize; } - quint64 getCenterFrequency() const { return m_centerFrequency; } - quint64 getStartingTimeStamp() const { return m_startingTimeStamp; } - quint64 getRecordLength() const { return m_recordLength; } - - static MsgReportFileSourceStreamData* create(int sampleRate, - quint32 sampleSize, - quint64 centerFrequency, - quint64 startingTimeStamp, - quint64 recordLength) - { - return new MsgReportFileSourceStreamData(sampleRate, sampleSize, centerFrequency, startingTimeStamp, recordLength); - } - - protected: - int m_sampleRate; - quint32 m_sampleSize; - quint64 m_centerFrequency; - quint64 m_startingTimeStamp; - quint64 m_recordLength; - - MsgReportFileSourceStreamData(int sampleRate, - quint32 sampleSize, - quint64 centerFrequency, - quint64 startingTimeStamp, - quint64 recordLength) : - Message(), - m_sampleRate(sampleRate), - m_sampleSize(sampleSize), - m_centerFrequency(centerFrequency), - m_startingTimeStamp(startingTimeStamp), - m_recordLength(recordLength) - { } - }; - - class MsgReportFileSourceStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - quint64 getSamplesCount() const { return m_samplesCount; } - - static MsgReportFileSourceStreamTiming* create(quint64 samplesCount) - { - return new MsgReportFileSourceStreamTiming(samplesCount); - } - - protected: - quint64 m_samplesCount; - - MsgReportFileSourceStreamTiming(quint64 samplesCount) : - Message(), - m_samplesCount(samplesCount) - { } - }; - - class MsgReportHeaderCRC : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool isOK() const { return m_ok; } - - static MsgReportHeaderCRC* create(bool ok) { - return new MsgReportHeaderCRC(ok); - } - - protected: - bool m_ok; - - MsgReportHeaderCRC(bool ok) : - Message(), - m_ok(ok) - { } - }; - - FileSourceInput(DeviceSourceAPI *deviceAPI); - virtual ~FileSourceInput(); - virtual void destroy(); - - virtual void init(); - virtual bool start(); - virtual void stop(); - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } - virtual const QString& getDeviceDescription() const; - virtual int getSampleRate() const; - virtual quint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - quint64 getStartingTimeStamp() const; - - virtual bool handleMessage(const Message& message); - - virtual int webapiSettingsGet( - SWGSDRangel::SWGDeviceSettings& response, - QString& errorMessage); - - virtual int webapiRunGet( - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage); - - virtual int webapiRun( - bool run, - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage); - - virtual int webapiReportGet( - SWGSDRangel::SWGDeviceReport& response, - QString& errorMessage); - - private: - DeviceSourceAPI *m_deviceAPI; - QMutex m_mutex; - FileSourceSettings m_settings; - std::ifstream m_ifstream; - FileSourceThread* m_fileSourceThread; - QString m_deviceDescription; - QString m_fileName; - int m_sampleRate; - quint32 m_sampleSize; - quint64 m_centerFrequency; - quint64 m_recordLength; //!< record length in seconds computed from file size - quint64 m_startingTimeStamp; - const QTimer& m_masterTimer; - - void openFileStream(); - void seekFileStream(int seekMillis); - bool applySettings(const FileSourceSettings& settings, bool force = false); - void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); -}; - -#endif // INCLUDE_FILESOURCEINPUT_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 // +// // +// 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_FILESOURCEINPUT_H +#define INCLUDE_FILESOURCEINPUT_H + +#include +#include +#include +#include +#include +#include + +#include +#include "filesourcesettings.h" + +class FileSourceThread; +class DeviceSourceAPI; + +class FileSourceInput : public DeviceSampleSource { +public: + class MsgConfigureFileSource : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const FileSourceSettings& getSettings() const { return m_settings; } + + static MsgConfigureFileSource* create(const FileSourceSettings& settings) + { + return new MsgConfigureFileSource(settings); + } + + private: + FileSourceSettings m_settings; + + MsgConfigureFileSource(const FileSourceSettings& settings) : + Message(), + m_settings(settings) + { } + }; + + class MsgConfigureFileSourceName : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QString& getFileName() const { return m_fileName; } + + static MsgConfigureFileSourceName* create(const QString& fileName) + { + return new MsgConfigureFileSourceName(fileName); + } + + private: + QString m_fileName; + + MsgConfigureFileSourceName(const QString& fileName) : + Message(), + m_fileName(fileName) + { } + }; + + class MsgConfigureFileSourceWork : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isWorking() const { return m_working; } + + static MsgConfigureFileSourceWork* create(bool working) + { + return new MsgConfigureFileSourceWork(working); + } + + private: + bool m_working; + + MsgConfigureFileSourceWork(bool working) : + Message(), + m_working(working) + { } + }; + + class MsgConfigureFileSourceStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgConfigureFileSourceStreamTiming* create() + { + return new MsgConfigureFileSourceStreamTiming(); + } + + private: + + MsgConfigureFileSourceStreamTiming() : + Message() + { } + }; + + class MsgConfigureFileSourceSeek : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMillis() const { return m_seekMillis; } + + static MsgConfigureFileSourceSeek* create(int seekMillis) + { + return new MsgConfigureFileSourceSeek(seekMillis); + } + + protected: + int m_seekMillis; //!< millis of seek position from the beginning 0..1000 + + MsgConfigureFileSourceSeek(int seekMillis) : + Message(), + m_seekMillis(seekMillis) + { } + }; + + class MsgReportFileSourceAcquisition : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getAcquisition() const { return m_acquisition; } + + static MsgReportFileSourceAcquisition* create(bool acquisition) + { + return new MsgReportFileSourceAcquisition(acquisition); + } + + protected: + bool m_acquisition; + + MsgReportFileSourceAcquisition(bool acquisition) : + Message(), + m_acquisition(acquisition) + { } + }; + + 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) + { } + }; + + class MsgPlayPause : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getPlayPause() const { return m_playPause; } + + static MsgPlayPause* create(bool playPause) { + return new MsgPlayPause(playPause); + } + + protected: + bool m_playPause; + + MsgPlayPause(bool playPause) : + Message(), + m_playPause(playPause) + { } + }; + + class MsgReportFileSourceStreamData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + quint32 getSampleSize() const { return m_sampleSize; } + quint64 getCenterFrequency() const { return m_centerFrequency; } + quint64 getStartingTimeStamp() const { return m_startingTimeStamp; } + quint64 getRecordLength() const { return m_recordLength; } + + static MsgReportFileSourceStreamData* create(int sampleRate, + quint32 sampleSize, + quint64 centerFrequency, + quint64 startingTimeStamp, + quint64 recordLength) + { + return new MsgReportFileSourceStreamData(sampleRate, sampleSize, centerFrequency, startingTimeStamp, recordLength); + } + + protected: + int m_sampleRate; + quint32 m_sampleSize; + quint64 m_centerFrequency; + quint64 m_startingTimeStamp; + quint64 m_recordLength; + + MsgReportFileSourceStreamData(int sampleRate, + quint32 sampleSize, + quint64 centerFrequency, + quint64 startingTimeStamp, + quint64 recordLength) : + Message(), + m_sampleRate(sampleRate), + m_sampleSize(sampleSize), + m_centerFrequency(centerFrequency), + m_startingTimeStamp(startingTimeStamp), + m_recordLength(recordLength) + { } + }; + + class MsgReportFileSourceStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + quint64 getSamplesCount() const { return m_samplesCount; } + + static MsgReportFileSourceStreamTiming* create(quint64 samplesCount) + { + return new MsgReportFileSourceStreamTiming(samplesCount); + } + + protected: + quint64 m_samplesCount; + + MsgReportFileSourceStreamTiming(quint64 samplesCount) : + Message(), + m_samplesCount(samplesCount) + { } + }; + + class MsgReportHeaderCRC : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isOK() const { return m_ok; } + + static MsgReportHeaderCRC* create(bool ok) { + return new MsgReportHeaderCRC(ok); + } + + protected: + bool m_ok; + + MsgReportHeaderCRC(bool ok) : + Message(), + m_ok(ok) + { } + }; + + FileSourceInput(DeviceSourceAPI *deviceAPI); + virtual ~FileSourceInput(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + quint64 getStartingTimeStamp() const; + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + + private: + DeviceSourceAPI *m_deviceAPI; + QMutex m_mutex; + FileSourceSettings m_settings; + std::ifstream m_ifstream; + FileSourceThread* m_fileSourceThread; + QString m_deviceDescription; + QString m_fileName; + int m_sampleRate; + quint32 m_sampleSize; + quint64 m_centerFrequency; + quint64 m_recordLength; //!< record length in seconds computed from file size + quint64 m_startingTimeStamp; + const QTimer& m_masterTimer; + + void openFileStream(); + void seekFileStream(int seekMillis); + bool applySettings(const FileSourceSettings& settings, bool force = false); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); +}; + +#endif // INCLUDE_FILESOURCEINPUT_H From 85f7e69da5b3b36cc8a0f0a1dda8ce23ba4592b9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 02:58:14 +0200 Subject: [PATCH 860/956] File Input: extended acceleration up to x1000 --- plugins/samplesource/filesource/filesourcegui.ui | 15 +++++++++++++++ .../samplesource/filesource/filesourceinput.cpp | 6 +++++- .../filesource/filesourcesettings.cpp | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/samplesource/filesource/filesourcegui.ui b/plugins/samplesource/filesource/filesourcegui.ui index f40501a14..458043fa2 100644 --- a/plugins/samplesource/filesource/filesourcegui.ui +++ b/plugins/samplesource/filesource/filesourcegui.ui @@ -475,6 +475,21 @@ 100 + + + 200 + + + + + 500 + + + + + 1k + + diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 4f8ed2175..4f0241a55 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -174,7 +174,7 @@ bool FileSourceInput::start() m_ifstream.seekg(sizeof(FileRecord::Header), std::ios::beg); } - if(!m_sampleFifo.setSize(m_sampleRate * sizeof(Sample))) { + if(!m_sampleFifo.setSize(m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample))) { qCritical("Could not allocate SampleFifo"); return false; } @@ -405,6 +405,10 @@ bool FileSourceInput::applySettings(const FileSourceSettings& settings, bool for if (m_fileSourceThread) { QMutexLocker mutexLocker(&m_mutex); + if (!m_sampleFifo.setSize(m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample))) { + qCritical("FileSourceInput::applySettings: could not reallocate sample FIFO size to %lu", + m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample)); + } m_fileSourceThread->setSampleRateAndSize(settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed } } diff --git a/plugins/samplesource/filesource/filesourcesettings.cpp b/plugins/samplesource/filesource/filesourcesettings.cpp index c5c2f0442..950f1c1af 100644 --- a/plugins/samplesource/filesource/filesourcesettings.cpp +++ b/plugins/samplesource/filesource/filesourcesettings.cpp @@ -18,7 +18,7 @@ #include "filesourcesettings.h" -const unsigned int FileSourceSettings::m_accelerationMaxScale = 1; +const unsigned int FileSourceSettings::m_accelerationMaxScale = 2; FileSourceSettings::FileSourceSettings() { From 6852b70597ffe994b5cbe7eab7bf56206844b3eb Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 03:23:07 +0200 Subject: [PATCH 861/956] File Input: Updated documentation --- debian/changelog | 1 + doc/img/FileSource_plugin.png | Bin 22810 -> 23426 bytes doc/img/FileSource_plugin.xcf | Bin 98617 -> 102763 bytes plugins/samplesource/filesource/readme.md | 12 ++++++++++-- sdrgui/readme.md | 2 +- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index b35bd92f7..69b1522f2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,6 +2,7 @@ sdrangel (4.2.2-1) unstable; urgency=medium * Spectrum: option to get max over a number of FFTs. Implements issue #207 * File Input: fixed wrong times displays due to 32 bit integer ovevlow. Issue #206 + * File Input: implemented play loop and playback acceleration -- Edouard Griffiths, F4EXB Sun, 14 Oct 2018 21:14:18 +0200 diff --git a/doc/img/FileSource_plugin.png b/doc/img/FileSource_plugin.png index 3f45840f9eed598069c9b78c647541e15a5d2512..a01a2244de87a3d2327e70c1d216671f986deb30 100644 GIT binary patch literal 23426 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 literal 22810 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 diff --git a/doc/img/FileSource_plugin.xcf b/doc/img/FileSource_plugin.xcf index 6e22a5a396ffc57bee07b0d4c37e17e21181a668..acf561e39faff3c4a884c4970ffb6dac70590116 100644 GIT binary patch delta 7207 zcmb{13s}@;zQFO{e})0^f|>$(BSe8vxrmyXiJ+ouDk?|BOQDwJHPTizyYL@-B=3m* zBeN1vmr7evcOq@wSZbqGCRRJg?Xr7V_Y~`+m1|pw2>dzUmzmL-Q_nfi|DFuwbo|2O;n3zM{&$HtD<^fr-z*G0Pa66yJSk-mu{;SQ0QbdiKnBKOP^ zxqpSogcn2}$P!7f5y`9*S+Gsyk>MiCIsWn9B5Pwsp87=OxerA)JtVSovB-;OL=Nl` zc{4`j$fqI>Q{+9a`~D5lw$i6*mYtK0e`p~chli_QEii_5UEwcX=+zo_OQ<%}INW!X zHr&`Zc&kzRi{(+)&()E%2!y~-#QLoY(A+U=i}9_sj~*b+yGu%oIl@}%C+40~OQ~5R zL`zCtnqDg{_MGjGl$MBh#3t8EN_YE&{G@SDNr|+_ zbah{Krk}KOrY0@kCtJnbTN+Sm7Bg$tVMJc_jRf90#U^G+sh`T<_J0KXak+Lzpe;nz z;77B~R5fU$2F^B1n`u%5&1}6HZK^WOW}^Obqvd9p#7&Cuz1iCAKF}4>%x3Y9P=prq znoo!#Trpck#q{wQL=tbfWM{+F*+EoS_X;WZQ>t%PmT z)cs=i@SltCe@A{A_mk_B_3wKB%!pMQgT9(8KReR;%3Er={Pc1bF?*@{tfFGBxwz6I z9{zLj4(cK!Zq1w(@S!T9QGFDAHhKY1| z`Z}x^32I;u(V!k&nrA$aeIalRrh$DS@G(4#9oUP*cn_c8Kk*$}MLKptKg41Trr{xx z*&QFF^Q=fG66n+<(m4fNaE>Hw2u37EAQc(NMlSMEgfdj18s|~(2oDL?jH^p0IG*o# z&`%p?^nZD!@mkk@+H|%180Re8jD;DUj4mq|M0LCDnxSW}Q5ERs+Rbk6Kiz|jpzggL z5jKlv+;vBYe(1Eb##vKStsVxa+KHSNr&*)c;c2JMX;#xy@g{rCnKLyie(g-nnbU{P zoN+qahj=temi(;}FBQ0{&vwsLP&izll1eJ)${VYIJ9n*LkguQ%;S$ zxvtYmZuPmvA+Ia)#MU&QsygX8+$R3cX7>LYmGj5#S5%=N302pSinGRbZ=O7-OR7`1 zd@B*gz44Kb2mCD-V{C9|{Rvn7JS#-iFZc!Cd4j+@N#7{n@eo#7$ZR}{wRj#c;dQ)& zkMJk_9oIxc{SksF+=GdjEplfDHn4(vJt)#^C7!`{ye86nJ~oN;2}2x`kcKSeAP)s7 zMmZ``i#l9^$qKUg8}VHqvq-VAt8X7|TKmfRq3cV=ii`zOVb-6nny^scSXi_psC#$K zPu0vA7C*=_wa0SJm=N)z-v3eGny8xff1Y>gINS&3sG6sy5yT=Mz&tF+Q`m}Ea0tJ} z8GMP$xFIs2BYIf3P#C<{2OK z1>={%rZ6}Vy}&1r!J{x4bFmETu?2f@5bxr&$dH#s9*$ii61xtph1lJA1II<;SU_@mjQ9$nC*o9LZcer*G8F%}JrHSDYX1`hDL7|Yvk zCbMVtZZqw-euNs+^-SaHqusO!iBYfFiS`4}L@8g@F-o>32*{K_+hP9{Q$;p$U z%2yp4$|dgV=ao}?etMUIH!}+5B4Z;WB-GwqrQVYNXJYNy>Rkq?tQ>{QLNp`1YgZ$p zZ;ovmulZlqCqu`t`^IZgi7eap=JjzfG0ZoX80XN!hWI(0gHCHv_eS~-s&#emDBsw< zli9yR9iI;Qou59&eKF5EQ^$`<;uCQWo&wLIYM${i{}f5G;0};fQXIYPoN9s-b5(%mj!n#WG~Tz>PoL7=74}S49Xn+x ze-`kZr1NJ(I)6f_<{6*Dx}U`0~Gc&OS>#zyCK>{<6<70e=i)bXF0E8kMBQXh?Sb}xfgxzf3!J1=bN^wBctXEt! z@XTDza8|i*Y*wWsVe&bvK4+IJ?wPGRcFr;WIO5_9ehJ-od~UYL++5_N2xX{1HO`|R z4I=Yw2u37EAQc(N7J2A1Ttp+)$O;h23Pm(VViGd31naN~yYU8&<70e=i)bXl0E8kM zBQXh?R3lJxTu8g@AGK(n@66Q<7v=cI7UemvW-PJjk8n}dysNR88!Qh(Z>Tp9ukpnv zMHchfVey~A>$|vFB)b#(U@%5w3g%-4p2jx3inm0TJSMU<4+SWu8cTVZmR6z`b+`gk zWLXfx5QikBAqzRkLjj6Wj!M*`4p(5R4ciehZ@PcfO3gI^@1?Plm#Mu`MfLEkjCAZ? z__|ep{1aE$yBLpu$KT0;;GDI*gQ?~jU$swU6-#H;`#6Wc;d_zQ8oHxDh9U(su?VZN zLF9=Bku}S}JKLHq*h4Ya9Hhfr+nUq(0+-Mvk{gI#h`}gK##}7JdThZS9K^dgjW2Ks zO%&sjqa}Nlf7H4b*95$a@g!SUdvh1#$#BODE3>Tn({@+bGg-BYo-XCf0_E2@;XD2e zUphRq7;CT*FXC5t8z167@U_UZ2k=KUi{$+lXYeI1^KM1Hb_#7zp%@#IMK-XFZdiy_ zu%i&AIEpHq#RXg?Awf5UBLT^nfrVHFI|?aAkz@YqHv^(J8scj1iK_{2e8V@k@wg*q zeWq34%ym`sF2-i=u`?M{F{gd_=J;kNy7@Z+>Q%$u8OegrGR7#!a)KB$(VtKSOq%@QHrCe!dYCvRT33+LpTyB zMy{jg+0O%_b`4`dHLqgqn&b+3V!JXOr}GE;=|%h{r_JXtL7Q6$QOC1 zx5)AS7>X3k1dHYPYLOG%=OnqF` zLmZNjhAiYD4+SVjIVw?$I$VKC!a)dADeGM-jjL}(J0cI21%yaEkvr8y5IAGenn#u{ zNEqt+4s->f->6Tz`UCXbtiq$9zDKk zt_sc_ZyDb*%Xw~mxH~XsoMl`~ZPD3r;p3X-s3S5{EGaEhR-8@gkzy8{@H=5{PqC&n zWx9r}DXon3OfmPJu$(aWrTDo=W+z*dTbG|r4o+@9eB#96WLt96Y;}ewu7@ORZwGjS zW36Lbmz^COKGtOd1+Tv0CGXsKO12ON>vXjQYQ*j<;Jz{a;ka+byH+ zUA_ND9dEacIxnC1dc55_-fkIn&(d?R)7!1%?Uu2d_j=U*mbMwEj(wzm;CVlo#>4(! zU}yN?@BG)wmry_dsdfQ&7Weoc_;7G`C5N&dHU^5;;a8&FIZ?E}RiX{PN3_Iz(MEqS z+T?!|ZT4W%9?TGJ#UDk>i4|@2Fwve&5=~DRZQ~`;HqGYS-c7}HxY6b~tabEtJakIm zcFcWKe!9OO*0!A!ZD%BgVvNtYww;@9FG3kAP>u7bM}ufPYzRgqMj#a#$VM*mQG_y7 zNT#-s@CQ1LXhs`1wITqW&=oxpiaxjt0}zeDh{rID#Au8KA0GJx^}byjwY$!)^}YGe G{Qm~ml3{%S delta 4880 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: Relative timestamp and record length

    +

    12: Playback acceleration

    + +Use this combo to select play back acceleration to values of 1 (no acceleration), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000) times. This is useful on long recordings used in conjunction with the spectrum "Max" averaging mode in order to see the waterfall over a long period. Thus the waterfall will be filled much faster. + +☞ Note that this control is enabled only in paused mode. + +⚠ The result when using channel plugins with acceleration is unpredictable. Use this tool to locate your signal of interest then play at normal speed to get proper demodulation or decoding. + +

    13: Relative timestamp and record length

    Left is the relative timestamp of the current pointer from the start of the record. Right is the total record time. -

    13: Current pointer gauge

    +

    14: Current pointer gauge

    This represents the position of the current pointer position in the complete recording. It can be used it paused mode to position the current pointer by moving the slider. \ No newline at end of file diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 46554c45f..1f6ee8f87 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -273,7 +273,7 @@ Use this combo to select which averaging mode is applied:

    4.6. Number of averaged samples

    -Each FFT bin (squared magnitude) is averaged or max'ed over a number of samples. This combo allows selecting the number of samples between these values: 0 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000), 2k, 5k, 10k, 20k, 50k, 1e5 (100000), 2e5, 5e5, 1M (1000000). The tooltip mentions the resulting averaging period considering the baseband sample rate and FFT size. +Each FFT bin (squared magnitude) is averaged or max'ed over a number of samples. This combo allows selecting the number of samples between these values: 1 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000), 2k, 5k, 10k, 20k, 50k, 1e5 (100000), 2e5, 5e5, 1M (1000000). The tooltip mentions the resulting averaging period considering the baseband sample rate and FFT size. Averaging reduces the noise variance and can be used to better detect weak continuous signals. The fixed averaging mode allows long time monitoring on the waterfall. The max mode helps showing short bursts that may appear during the "averaging" period. ☞ Note: The spectrum display is refreshed every 50ms (20 FPS). Setting an averaging time above this value will make sure that a short burst is not missed particularly when using the max mode. From b397cd3a4bb45497b27dee194b62b3cc21de989f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 14 Oct 2018 10:38:31 +0200 Subject: [PATCH 862/956] File Input: REST API: updated with new settings elements --- .../samplesource/filesource/filesourcegui.cpp | 6 +- .../filesource/filesourceinput.cpp | 64 ++++++++++++++----- .../samplesource/filesource/filesourceinput.h | 18 ++++-- .../filesource/filesourcethread.cpp | 10 --- sdrbase/resources/webapi/doc/html2/index.html | 13 +++- .../doc/swagger/include/FileSource.yaml | 7 ++ .../api/swagger/include/FileSource.yaml | 7 ++ swagger/sdrangel/code/html2/index.html | 13 +++- .../code/qt5/client/SWGFileSourceSettings.cpp | 42 ++++++++++++ .../code/qt5/client/SWGFileSourceSettings.h | 12 ++++ 10 files changed, 154 insertions(+), 38 deletions(-) diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index 5d7524b2e..f19e500dd 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -249,7 +249,7 @@ void FileSourceGui::on_playLoop_toggled(bool checked) if (m_doApplySettings) { m_settings.m_loop = checked; - FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings); + FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings, false); m_sampleSource->getInputMessageQueue()->push(message); } } @@ -329,7 +329,7 @@ void FileSourceGui::on_acceleration_currentIndexChanged(int index) if (m_doApplySettings) { m_settings.m_accelerationFactor = FileSourceSettings::getAccelerationValue(index); - FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings); + FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings, false); m_sampleSource->getInputMessageQueue()->push(message); } } @@ -358,7 +358,7 @@ void FileSourceGui::updateWithStreamData() recordLength = recordLength.addSecs(m_recordLength); QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); - updateWithStreamTime(); // TODO: remove when time data is implemented + updateWithStreamTime(); } void FileSourceGui::updateWithStreamTime() diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 4f0241a55..74f0b4c26 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -179,15 +179,12 @@ bool FileSourceInput::start() return false; } - //openFileStream(); - m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue); m_fileSourceThread->setSampleRateAndSize(m_settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed m_fileSourceThread->startWork(); m_deviceDescription = "FileSource"; mutexLocker.unlock(); - //applySettings(m_generalSettings, m_settings, true); qDebug("FileSourceInput::startInput: started"); if (getMessageQueueToGUI()) { @@ -233,12 +230,12 @@ bool FileSourceInput::deserialize(const QByteArray& data) success = false; } - MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings); + MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings, true); m_inputMessageQueue.push(message); if (getMessageQueueToGUI()) { - MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings); + MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings, true); getMessageQueueToGUI()->push(messageToGUI); } @@ -265,12 +262,12 @@ void FileSourceInput::setCenterFrequency(qint64 centerFrequency) FileSourceSettings settings = m_settings; settings.m_centerFrequency = centerFrequency; - MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings); + MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings, false); m_inputMessageQueue.push(message); if (getMessageQueueToGUI()) { - MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings); + MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings, false); getMessageQueueToGUI()->push(messageToGUI); } } @@ -303,16 +300,9 @@ bool FileSourceInput::handleMessage(const Message& message) if (m_fileSourceThread != 0) { - if (working) - { + if (working) { m_fileSourceThread->startWork(); - /* - MsgReportFileSourceStreamTiming *report = - MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); - getOutputMessageQueueToGUI()->push(report);*/ - } - else - { + } else { m_fileSourceThread->stopWork(); } } @@ -422,7 +412,39 @@ int FileSourceInput::webapiSettingsGet( QString& errorMessage __attribute__((unused))) { response.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings()); - response.getFileSourceSettings()->setFileName(new QString(m_settings.m_fileName)); + response.getFileSourceSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int FileSourceInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + FileSourceSettings settings = m_settings; + + if (deviceSettingsKeys.contains("fileName")) { + settings.m_fileName = *response.getFileSourceSettings()->getFileName(); + } + if (deviceSettingsKeys.contains("accelerationFactor")) { + settings.m_accelerationFactor = response.getFileSourceSettings()->getAccelerationFactor(); + } + if (deviceSettingsKeys.contains("loop")) { + settings.m_loop = response.getFileSourceSettings()->getLoop() != 0; + } + + MsgConfigureFileSource *msg = MsgConfigureFileSource::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureFileSource *msgToGUI = MsgConfigureFileSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); return 200; } @@ -462,6 +484,14 @@ int FileSourceInput::webapiReportGet( return 200; } +void FileSourceInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FileSourceSettings& settings) +{ + response.getFileSourceSettings()->setFileName(new QString(settings.m_fileName)); + response.getFileSourceSettings()->setAccelerationFactor(settings.m_accelerationFactor); + response.getFileSourceSettings()->setLoop(settings.m_loop ? 1 : 0); + +} + void FileSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { qint64 t_sec = 0; diff --git a/plugins/samplesource/filesource/filesourceinput.h b/plugins/samplesource/filesource/filesourceinput.h index 42e074672..adef3b065 100644 --- a/plugins/samplesource/filesource/filesourceinput.h +++ b/plugins/samplesource/filesource/filesourceinput.h @@ -37,18 +37,21 @@ public: public: const FileSourceSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } - static MsgConfigureFileSource* create(const FileSourceSettings& settings) + static MsgConfigureFileSource* create(const FileSourceSettings& settings, bool force) { - return new MsgConfigureFileSource(settings); + return new MsgConfigureFileSource(settings, force); } private: FileSourceSettings m_settings; + bool m_force; - MsgConfigureFileSource(const FileSourceSettings& settings) : + MsgConfigureFileSource(const FileSourceSettings& settings, bool force) : Message(), - m_settings(settings) + m_settings(settings), + m_force(force) { } }; @@ -290,6 +293,12 @@ public: SWGSDRangel::SWGDeviceSettings& response, QString& errorMessage); + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -321,6 +330,7 @@ public: void openFileStream(); void seekFileStream(int seekMillis); bool applySettings(const FileSourceSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FileSourceSettings& settings); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; diff --git a/plugins/samplesource/filesource/filesourcethread.cpp b/plugins/samplesource/filesource/filesourcethread.cpp index 1afa43e30..d9014f157 100644 --- a/plugins/samplesource/filesource/filesourcethread.cpp +++ b/plugins/samplesource/filesource/filesourcethread.cpp @@ -193,20 +193,10 @@ void FileSourceThread::tick() writeToSampleFifo(m_fileBuf, (qint32) m_ifstream->gcount()); MsgReportEOF *message = MsgReportEOF::create(); m_fileInputMessageQueue->push(message); - //m_sampleFifo->write(m_buf, m_ifstream->gcount()); - // TODO: handle loop playback situation - -// m_ifstream->clear(); -// m_ifstream->seekg(sizeof(FileRecord::Header), std::ios::beg); -// m_samplesCount = 0; - - //stopWork(); - //m_ifstream->close(); } else { writeToSampleFifo(m_fileBuf, (qint32) m_chunksize); - //m_sampleFifo->write(m_buf, m_chunksize); m_samplesCount += m_chunksize / (2 * m_samplebytes); } } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 9f2c3c342..61c526a87 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -2297,7 +2297,16 @@ margin-bottom: 20px; defs.FileSourceSettings = { "properties" : { "fileName" : { - "type" : "string" + "type" : "string", + "description" : "The name (path) of the file being read" + }, + "accelerationFactor" : { + "type" : "integer", + "description" : "Playback acceleration (1 if normal speed)" + }, + "loop" : { + "type" : "integer", + "description" : "1 if playing in a loop else 0" } }, "description" : "FileSource" @@ -23242,7 +23251,7 @@ except ApiException as e:
    - Generated 2018-10-11T08:49:05.249+02:00 + Generated 2018-10-14T10:07:16.959+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/FileSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/FileSource.yaml index 2a3c847f9..f608a64da 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/FileSource.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/FileSource.yaml @@ -2,7 +2,14 @@ FileSourceSettings: description: FileSource properties: fileName: + description: The name (path) of the file being read type: string + accelerationFactor: + description: Playback acceleration (1 if normal speed) + type: integer + loop: + description: 1 if playing in a loop else 0 + type: integer FileSourceReport: description: FileSource diff --git a/swagger/sdrangel/api/swagger/include/FileSource.yaml b/swagger/sdrangel/api/swagger/include/FileSource.yaml index 2a3c847f9..f608a64da 100644 --- a/swagger/sdrangel/api/swagger/include/FileSource.yaml +++ b/swagger/sdrangel/api/swagger/include/FileSource.yaml @@ -2,7 +2,14 @@ FileSourceSettings: description: FileSource properties: fileName: + description: The name (path) of the file being read type: string + accelerationFactor: + description: Playback acceleration (1 if normal speed) + type: integer + loop: + description: 1 if playing in a loop else 0 + type: integer FileSourceReport: description: FileSource diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 9f2c3c342..61c526a87 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -2297,7 +2297,16 @@ margin-bottom: 20px; defs.FileSourceSettings = { "properties" : { "fileName" : { - "type" : "string" + "type" : "string", + "description" : "The name (path) of the file being read" + }, + "accelerationFactor" : { + "type" : "integer", + "description" : "Playback acceleration (1 if normal speed)" + }, + "loop" : { + "type" : "integer", + "description" : "1 if playing in a loop else 0" } }, "description" : "FileSource" @@ -23242,7 +23251,7 @@ except ApiException as e:
    - Generated 2018-10-11T08:49:05.249+02:00 + Generated 2018-10-14T10:07:16.959+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp index ff7fe5645..3523740a5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.cpp @@ -30,6 +30,10 @@ SWGFileSourceSettings::SWGFileSourceSettings(QString* json) { SWGFileSourceSettings::SWGFileSourceSettings() { file_name = nullptr; m_file_name_isSet = false; + acceleration_factor = 0; + m_acceleration_factor_isSet = false; + loop = 0; + m_loop_isSet = false; } SWGFileSourceSettings::~SWGFileSourceSettings() { @@ -40,6 +44,10 @@ void SWGFileSourceSettings::init() { file_name = new QString(""); m_file_name_isSet = false; + acceleration_factor = 0; + m_acceleration_factor_isSet = false; + loop = 0; + m_loop_isSet = false; } void @@ -47,6 +55,8 @@ SWGFileSourceSettings::cleanup() { if(file_name != nullptr) { delete file_name; } + + } SWGFileSourceSettings* @@ -62,6 +72,10 @@ void SWGFileSourceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&file_name, pJson["fileName"], "QString", "QString"); + ::SWGSDRangel::setValue(&acceleration_factor, pJson["accelerationFactor"], "qint32", ""); + + ::SWGSDRangel::setValue(&loop, pJson["loop"], "qint32", ""); + } QString @@ -81,6 +95,12 @@ SWGFileSourceSettings::asJsonObject() { if(file_name != nullptr && *file_name != QString("")){ toJsonValue(QString("fileName"), file_name, obj, QString("QString")); } + if(m_acceleration_factor_isSet){ + obj->insert("accelerationFactor", QJsonValue(acceleration_factor)); + } + if(m_loop_isSet){ + obj->insert("loop", QJsonValue(loop)); + } return obj; } @@ -95,12 +115,34 @@ SWGFileSourceSettings::setFileName(QString* file_name) { this->m_file_name_isSet = true; } +qint32 +SWGFileSourceSettings::getAccelerationFactor() { + return acceleration_factor; +} +void +SWGFileSourceSettings::setAccelerationFactor(qint32 acceleration_factor) { + this->acceleration_factor = acceleration_factor; + this->m_acceleration_factor_isSet = true; +} + +qint32 +SWGFileSourceSettings::getLoop() { + return loop; +} +void +SWGFileSourceSettings::setLoop(qint32 loop) { + this->loop = loop; + this->m_loop_isSet = true; +} + bool SWGFileSourceSettings::isSet(){ bool isObjectUpdated = false; do{ if(file_name != nullptr && *file_name != QString("")){ isObjectUpdated = true; break;} + if(m_acceleration_factor_isSet){ isObjectUpdated = true; break;} + if(m_loop_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h index 8f795c2bb..754fef77c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h @@ -45,6 +45,12 @@ public: QString* getFileName(); void setFileName(QString* file_name); + qint32 getAccelerationFactor(); + void setAccelerationFactor(qint32 acceleration_factor); + + qint32 getLoop(); + void setLoop(qint32 loop); + virtual bool isSet() override; @@ -52,6 +58,12 @@ private: QString* file_name; bool m_file_name_isSet; + qint32 acceleration_factor; + bool m_acceleration_factor_isSet; + + qint32 loop; + bool m_loop_isSet; + }; } From 5d57f40e83eb250963b4abf23ea42642d0b061ed Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 15 Oct 2018 02:47:26 +0200 Subject: [PATCH 863/956] Scope: save/load traces memory (1) --- sdrbase/util/doublebuffer.h | 46 ++++++++++++++++++++++++++ sdrgui/gui/glscopegui.cpp | 36 +++++++++++++++++++- sdrgui/gui/glscopegui.h | 2 ++ sdrgui/gui/glscopegui.ui | 65 +++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 1 deletion(-) diff --git a/sdrbase/util/doublebuffer.h b/sdrbase/util/doublebuffer.h index b76db989a..f7bfb22ba 100644 --- a/sdrbase/util/doublebuffer.h +++ b/sdrbase/util/doublebuffer.h @@ -20,6 +20,10 @@ #include #include +#include + +#include "simpleserializer.h" + template class DoubleBufferSimple { @@ -89,6 +93,48 @@ public: unsigned int absoluteFill() const { return m_current - m_data.begin(); } void reset() { m_current = m_data.begin(); } + QByteArray serialize() const + { + SimpleSerializer s(1); + + QByteArray buf(reinterpret_cast(m_data.data()), m_data.size()*sizeof(T)); + s.writeS32(1, m_size); + s.writeU32(2, m_current - m_data.begin()); + s.writeBlob(3, buf); + + return s.final(); + } + + bool deserialize(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + unsigned int tmpUInt; + QByteArray buf; + + d.readS32(1, &m_size, m_data.size()/2); + m_data.resize(2*m_size); + d.readU32(2, &tmpUInt, 0); + m_current = m_data.begin() + tmpUInt; + d.readBlob(3, &buf); + const T* begin = reinterpret_cast(buf.data()); + const T* end = begin + (buf.length()/sizeof(T)); + std::copy(m_data.begin(), begin, end); + + return true; + } + else + { + return false; + } + } + private: int m_size; std::vector m_data; diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp index 17b08b15b..82ce036b7 100644 --- a/sdrgui/gui/glscopegui.cpp +++ b/sdrgui/gui/glscopegui.cpp @@ -16,6 +16,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "glscopegui.h" #include "glscope.h" @@ -768,12 +769,43 @@ void GLScopeGUI::on_traceColor_clicked() } } +void GLScopeGUI::on_memorySave_clicked(bool checked __attribute__((unused))) +{ + qDebug("GLScopeGUI::on_memorySave_clicked"); + QString fileName = QFileDialog::getSaveFileName(this, + tr("Open trace memory file"), ".", tr("Trace memory files (*.trcm)"), 0, QFileDialog::DontUseNativeDialog); + + if (fileName != "") + { + QFileInfo fileInfo(fileName); + + if (fileInfo.suffix() != "trcm") { + fileName += ".trcm"; + } + + qDebug("GLScopeGUI::on_memorySave_clicked: %s", qPrintable(fileName)); + } +} + +void GLScopeGUI::on_memoryLoad_clicked(bool checked __attribute__((unused))) +{ + qDebug("GLScopeGUI::on_memoryLoad_clicked"); + + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open trace memory file"), ".", tr("Trace memory files (*.trcm)"), 0, QFileDialog::DontUseNativeDialog); + + if (fileName != "") + { + qDebug("GLScopeGUI::on_memoryLoad_clicked: %s", qPrintable(fileName)); + } +} + void GLScopeGUI::on_mem_valueChanged(int value) { QString text; text.sprintf("%02d", value); ui->memText->setText(text); - disableLiveMode(value > 0); // block trigger UI line if memory is active + disableLiveMode(value > 0); // live / memory mode toggle m_scopeVis->setMemoryIndex(value); } @@ -1224,6 +1256,8 @@ void GLScopeGUI::disableLiveMode(bool disable) ui->trigPre->setEnabled(!disable); ui->trigOneShot->setEnabled(!disable); ui->freerun->setEnabled(!disable); + ui->memorySave->setEnabled(disable); + ui->memoryLoad->setEnabled(disable); } void GLScopeGUI::fillTraceData(ScopeVis::TraceData& traceData) diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h index 77584c471..be3ccdd98 100644 --- a/sdrgui/gui/glscopegui.h +++ b/sdrgui/gui/glscopegui.h @@ -211,6 +211,8 @@ private slots: void on_traceDelayFine_valueChanged(int value); void on_traceView_toggled(bool checked); void on_traceColor_clicked(); + void on_memorySave_clicked(bool checked); + void on_memoryLoad_clicked(bool checked); void on_mem_valueChanged(int value); // Third row void on_trig_valueChanged(int value); diff --git a/sdrgui/gui/glscopegui.ui b/sdrgui/gui/glscopegui.ui index e4da3f321..04a336824 100644 --- a/sdrgui/gui/glscopegui.ui +++ b/sdrgui/gui/glscopegui.ui @@ -1102,6 +1102,71 @@ kS/s
    + + + + 0 + + + + + false + + + + 18 + 18 + + + + Save traces in memory + + + + + + + :/save.png:/save.png + + + + 16 + 16 + + + + + + + + false + + + + 18 + 18 + + + + Load traces into memory + + + + + + + :/load.png:/load.png + + + + 16 + 16 + + + + + + From 64cf05f1c6bec9cdea7a33248f9b4c817ff10cfb Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 15 Oct 2018 08:45:44 +0200 Subject: [PATCH 864/956] Scope: save/load traces memory (2) --- sdrbase/util/doublebuffer.h | 1 + sdrgui/dsp/scopevis.h | 94 +++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/sdrbase/util/doublebuffer.h b/sdrbase/util/doublebuffer.h index f7bfb22ba..169e3adf5 100644 --- a/sdrbase/util/doublebuffer.h +++ b/sdrbase/util/doublebuffer.h @@ -90,6 +90,7 @@ public: } typename std::vector::iterator getCurrent() const { return m_current + m_size; } + typename std::vector::iterator begin() const { return m_data.begin(); } unsigned int absoluteFill() const { return m_current - m_data.begin(); } void reset() { m_current = m_data.begin(); } diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index 0bbe91797..222c2c6f2 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -20,6 +20,8 @@ #include #include +#include + #include #include #include @@ -599,6 +601,44 @@ private: } SampleVector::iterator current() { return m_traceBuffer.getCurrent(); } + + QByteArray serialize() const + { + SimpleSerializer s(1); + + QByteArray buffer = m_traceBuffer.serialize(); + unsigned int endDelta = m_endPoint - m_traceBuffer.begin(); + s.writeU32(1, endDelta); + s.writeBlob(2, buffer); + + return s.final(); + } + + bool deserialize(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + unsigned int tmpUInt; + QByteArray buf; + + d.readU32(1, &tmpUInt, 0); + d.readBlob(2, &buf); + m_traceBuffer.deserialize(buf); + m_endPoint = m_traceBuffer.begin() + tmpUInt; + + return true; + } + else + { + return false; + } + } }; struct TraceBackDiscreteMemory @@ -672,6 +712,60 @@ private: * Return current memory index */ uint32_t currentIndex() const { return m_currentMemIndex; } + + /** + * Serializer + */ + QByteArray serialize() const + { + SimpleSerializer s(1); + + s.writeU32(1, m_memSize); + s.writeU32(2, m_currentMemIndex); + s.writeU32(3, m_traceSize); + + for (unsigned int i = 0; i < m_memSize; i++) + { + QByteArray buffer = m_traceBackBuffers[i].serialize(); + s.writeBlob(100+i, buffer); + } + + return s.final(); + } + + /** + * Deserializer + */ + bool deserialize(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + d.readU32(1, &m_memSize, 0); + d.readU32(2, &m_currentMemIndex, 0); + d.readU32(3, &m_traceSize, 0); + m_traceBackBuffers.resize(m_memSize); + resize(m_traceSize); + + for (unsigned int i = 0; i < m_memSize; i++) + { + QByteArray buffer; + d.readBlob(100+i, &buffer); + m_traceBackBuffers[i].deserialize(buffer); + } + + return true; + } + else + { + return false; + } + } }; /** From 1caf68eb9b1408a82e10d97fb68e6b056d939765 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 15 Oct 2018 10:38:07 +0200 Subject: [PATCH 865/956] Scope: save/load traces memory (3): compile fix --- sdrbase/util/doublebuffer.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sdrbase/util/doublebuffer.h b/sdrbase/util/doublebuffer.h index 169e3adf5..1dafc3334 100644 --- a/sdrbase/util/doublebuffer.h +++ b/sdrbase/util/doublebuffer.h @@ -90,7 +90,8 @@ public: } typename std::vector::iterator getCurrent() const { return m_current + m_size; } - typename std::vector::iterator begin() const { return m_data.begin(); } + typename std::vector::const_iterator begin() const { return m_data.begin(); } + typename std::vector::iterator begin() { return m_data.begin(); } unsigned int absoluteFill() const { return m_current - m_data.begin(); } void reset() { m_current = m_data.begin(); } @@ -124,9 +125,7 @@ public: d.readU32(2, &tmpUInt, 0); m_current = m_data.begin() + tmpUInt; d.readBlob(3, &buf); - const T* begin = reinterpret_cast(buf.data()); - const T* end = begin + (buf.length()/sizeof(T)); - std::copy(m_data.begin(), begin, end); + std::copy(reinterpret_cast(m_data.data()), buf.data(), buf.data() + buf.size()); return true; } From be3643005779337a3680aa8e645a7b3ad3a46c68 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 16 Oct 2018 00:08:33 +0200 Subject: [PATCH 866/956] Channel Analyzer GUI: set sample rate via ScopeVis only --- plugins/channelrx/chanalyzer/chanalyzergui.cpp | 1 - plugins/channelrx/chanalyzer/chanalyzerplugin.cpp | 2 +- sdrgui/dsp/scopevis.cpp | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index d9ae040f9..428b171cc 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -462,7 +462,6 @@ void ChannelAnalyzerGUI::setNewFinalRate() QString s = QString::number(m_rate/1000.0, 'f', 1); ui->spanText->setText(tr("%1 kS/s").arg(s)); - ui->glScope->setSampleRate(m_rate); m_scopeVis->setSampleRate(m_rate); } diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index 4d9bff02b..f25605b6d 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("4.0.7"), + QString("4.2.3"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 2c2967281..31cb4ba65 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -76,10 +76,10 @@ ScopeVis::~ScopeVis() void ScopeVis::setSampleRate(int sampleRate) { - if (sampleRate != m_sampleRate) - { - m_sampleRate = sampleRate; - if (m_glScope) m_glScope->setSampleRate(m_sampleRate); + m_sampleRate = sampleRate; + + if (m_glScope) { + m_glScope->setSampleRate(m_sampleRate); } } From 966d957f895d428ab57ffbb3ce2e719ff9a68ede Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 16 Oct 2018 00:31:45 +0200 Subject: [PATCH 867/956] Scope: fixed channel rate affecting scope rate in memory mode --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 +++++ .../channelrx/chanalyzer/chanalyzergui.cpp | 2 +- plugins/channelrx/demodatv/atvdemodgui.cpp | 2 +- plugins/channelrx/demodatv/atvdemodplugin.cpp | 2 +- sdrgui/dsp/scopevis.cpp | 22 +++++++++++++++++-- sdrgui/dsp/scopevis.h | 11 ++++++++-- 9 files changed, 41 insertions(+), 10 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index c9938efd6..56057b262 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.2.2"); + QCoreApplication::setApplicationVersion("4.2.3"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp index 6dba5b70d..d6c45498f 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.2.2"); + QCoreApplication::setApplicationVersion("4.2.3"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 89643ee0d..79591e924 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.2.2"); + QCoreApplication::setApplicationVersion("4.2.3"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 69b1522f2..899edc393 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (4.2.3-1) unstable; urgency=medium + + * Scope: fixed channel rate affecting scope in memory mode. Issue #227 + + -- Edouard Griffiths, F4EXB Sun, 21 Oct 2018 21:14:18 +0200 + sdrangel (4.2.2-1) unstable; urgency=medium * Spectrum: option to get max over a number of FFTs. Implements issue #207 diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 428b171cc..18e1c9b25 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -462,7 +462,7 @@ void ChannelAnalyzerGUI::setNewFinalRate() QString s = QString::number(m_rate/1000.0, 'f', 1); ui->spanText->setText(tr("%1 kS/s").arg(s)); - m_scopeVis->setSampleRate(m_rate); + m_scopeVis->setLiveRate(m_rate); } void ChannelAnalyzerGUI::setFiltersUIBoundaries() diff --git a/plugins/channelrx/demodatv/atvdemodgui.cpp b/plugins/channelrx/demodatv/atvdemodgui.cpp index 09ed50f86..4fadfd9b6 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.cpp +++ b/plugins/channelrx/demodatv/atvdemodgui.cpp @@ -211,7 +211,7 @@ bool ATVDemodGUI::handleMessage(const Message& objMessage) int nbPointsPerLine = ((ATVDemod::MsgReportEffectiveSampleRate&)objMessage).getNbPointsPerLine(); ui->channelSampleRateText->setText(tr("%1k").arg(sampleRate/1000.0f, 0, 'f', 2)); ui->nbPointsPerLineText->setText(tr("%1p").arg(nbPointsPerLine)); - m_scopeVis->setSampleRate(sampleRate); + m_scopeVis->setLiveRate(sampleRate); setRFFiltersSlidersRange(sampleRate); lineTimeUpdate(); topTimeUpdate(); diff --git a/plugins/channelrx/demodatv/atvdemodplugin.cpp b/plugins/channelrx/demodatv/atvdemodplugin.cpp index 2a3852120..66b78641a 100644 --- a/plugins/channelrx/demodatv/atvdemodplugin.cpp +++ b/plugins/channelrx/demodatv/atvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor ATVDemodPlugin::m_ptrPluginDescriptor = { QString("ATV Demodulator"), - QString("3.14.5"), + QString("4.2.3"), QString("(c) F4HKW for F4EXB / SDRAngel"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 31cb4ba65..3751edf4d 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -52,6 +52,8 @@ ScopeVis::ScopeVis(GLScope* glScope) : m_timeOfsProMill(0), m_traceStart(true), m_sampleRate(0), + m_liveRate(0), + m_memoryRate(0), m_traceDiscreteMemory(m_nbTraceMemories), m_freeRun(true), m_maxTraceDelay(0), @@ -74,6 +76,15 @@ ScopeVis::~ScopeVis() } } +void ScopeVis::setLiveRate(int sampleRate) +{ + m_liveRate = sampleRate; + + if (m_currentTraceMemoryIndex == 0) { // update only in live mode + setSampleRate(m_liveRate); + } +} + void ScopeVis::setSampleRate(int sampleRate) { m_sampleRate = sampleRate; @@ -589,7 +600,7 @@ bool ScopeVis::handleMessage(const Message& message) if (DSPSignalNotification::match(message)) { DSPSignalNotification& notif = (DSPSignalNotification&) message; - setSampleRate(notif.getSampleRate()); + setLiveRate(notif.getSampleRate()); qDebug() << "ScopeVis::handleMessage: DSPSignalNotification: m_sampleRate: " << m_sampleRate; return true; } @@ -827,9 +838,16 @@ bool ScopeVis::handleMessage(const Message& message) if (memoryIndex != m_currentTraceMemoryIndex) { + // on transition from live rate initialize memory rate to live rate + if (memoryIndex == 0) { + m_memoryRate = m_liveRate; + } + m_currentTraceMemoryIndex = memoryIndex; - if (m_currentTraceMemoryIndex > 0) { + if (m_currentTraceMemoryIndex == 0) { // transition to live mode + setSampleRate(m_liveRate); // reset to live rate + } else { processMemoryTrace(); } } diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index 222c2c6f2..a7a90aabf 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -152,7 +152,7 @@ public: ScopeVis(GLScope* glScope = 0); virtual ~ScopeVis(); - void setSampleRate(int sampleRate); + void setLiveRate(int sampleRate); void configure(uint32_t traceSize, uint32_t timeBase, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun); void addTrace(const TraceData& traceData); void changeTrace(const TraceData& traceData, uint32_t traceIndex); @@ -1049,7 +1049,9 @@ private: uint32_t m_timeOfsProMill; //!< Start trace shift in 1/1000 trace size bool m_traceStart; //!< Trace is at start point SampleVector::const_iterator m_triggerPoint; //!< Trigger start location in the samples vector - int m_sampleRate; + int m_sampleRate; //!< Actual sample rate being used + int m_liveRate; //!< Sample rate in live mode + int m_memoryRate; //!< Sample rate in memory mode TraceBackDiscreteMemory m_traceDiscreteMemory; //!< Complex trace memory for triggered states TODO: vectorize when more than on input is allowed bool m_freeRun; //!< True if free running (trigger globally disabled) int m_maxTraceDelay; //!< Maximum trace delay @@ -1109,6 +1111,11 @@ private: * - Trace in memory: call process memory trace */ void updateGLScopeDisplay(); + + /** + * Set the actual sample rate + */ + void setSampleRate(int sampleRate); }; From 1838253e923a685871eb4965b5eb6a10d8470235 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 16 Oct 2018 05:10:35 +0200 Subject: [PATCH 868/956] Scope: save/restore live trace size when transitioning from/to memory mode --- sdrgui/dsp/scopevis.cpp | 43 +++++++++++++++++++++++++---------------- sdrgui/dsp/scopevis.h | 17 ++++++++++++---- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 3751edf4d..a0d5c7a40 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -47,13 +47,13 @@ ScopeVis::ScopeVis(GLScope* glScope) : m_triggerState(TriggerUntriggered), m_focusedTraceIndex(0), m_traceSize(m_traceChunkSize), + m_liveTraceSize(m_traceChunkSize), m_nbSamples(0), m_timeBase(1), m_timeOfsProMill(0), m_traceStart(true), m_sampleRate(0), - m_liveRate(0), - m_memoryRate(0), + m_liveSampleRate(0), m_traceDiscreteMemory(m_nbTraceMemories), m_freeRun(true), m_maxTraceDelay(0), @@ -78,10 +78,10 @@ ScopeVis::~ScopeVis() void ScopeVis::setLiveRate(int sampleRate) { - m_liveRate = sampleRate; + m_liveSampleRate = sampleRate; if (m_currentTraceMemoryIndex == 0) { // update only in live mode - setSampleRate(m_liveRate); + setSampleRate(m_liveSampleRate); } } @@ -94,6 +94,18 @@ void ScopeVis::setSampleRate(int sampleRate) } } +void ScopeVis::setTraceSize(uint32_t traceSize) +{ + m_traceSize = traceSize; + m_traces.resize(m_traceSize); + m_traceDiscreteMemory.resize(m_traceSize); + initTraceBuffers(); + + if (m_glScope) { + m_glScope->setTraceSize(m_traceSize); + } +} + void ScopeVis::configure(uint32_t traceSize, uint32_t timeBase, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun) { Message* cmd = MsgConfigureScopeVisNG::create(traceSize, timeBase, timeOfsProMill, triggerPre, freeRun); @@ -617,14 +629,7 @@ bool ScopeVis::handleMessage(const Message& message) if (m_traceSize != traceSize) { - m_traceSize = traceSize; - m_traces.resize(m_traceSize); - m_traceDiscreteMemory.resize(m_traceSize); - initTraceBuffers(); - - if (m_glScope) { - m_glScope->setTraceSize(m_traceSize); - } + setTraceSize(traceSize); } if (m_timeBase != timeBase) @@ -838,16 +843,20 @@ bool ScopeVis::handleMessage(const Message& message) if (memoryIndex != m_currentTraceMemoryIndex) { - // on transition from live rate initialize memory rate to live rate + // transition from live mode if (memoryIndex == 0) { - m_memoryRate = m_liveRate; + m_liveTraceSize = m_traceSize; } m_currentTraceMemoryIndex = memoryIndex; - if (m_currentTraceMemoryIndex == 0) { // transition to live mode - setSampleRate(m_liveRate); // reset to live rate - } else { + if (m_currentTraceMemoryIndex == 0) // transition to live mode + { + setSampleRate(m_liveSampleRate); // reset to live rate + setTraceSize(m_liveTraceSize); // reset to live trace size + } + else + { processMemoryTrace(); } } diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index a7a90aabf..a45c95d20 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -748,9 +748,13 @@ private: { d.readU32(1, &m_memSize, 0); d.readU32(2, &m_currentMemIndex, 0); - d.readU32(3, &m_traceSize, 0); + uint32_t traceSize; + d.readU32(3, &traceSize, 0); m_traceBackBuffers.resize(m_memSize); - resize(m_traceSize); + + if (traceSize != m_traceSize) { + resize(traceSize); + } for (unsigned int i = 0; i < m_memSize; i++) { @@ -1044,14 +1048,14 @@ private: Traces m_traces; //!< Displayable traces int m_focusedTraceIndex; //!< Index of the trace that has focus uint32_t m_traceSize; //!< Size of traces in number of samples + uint32_t m_liveTraceSize; //!< Size of traces in number of samples in live mode int m_nbSamples; //!< Number of samples yet to process in one complex trace uint32_t m_timeBase; //!< Trace display time divisor uint32_t m_timeOfsProMill; //!< Start trace shift in 1/1000 trace size bool m_traceStart; //!< Trace is at start point SampleVector::const_iterator m_triggerPoint; //!< Trigger start location in the samples vector int m_sampleRate; //!< Actual sample rate being used - int m_liveRate; //!< Sample rate in live mode - int m_memoryRate; //!< Sample rate in memory mode + int m_liveSampleRate; //!< Sample rate in live mode TraceBackDiscreteMemory m_traceDiscreteMemory; //!< Complex trace memory for triggered states TODO: vectorize when more than on input is allowed bool m_freeRun; //!< True if free running (trigger globally disabled) int m_maxTraceDelay; //!< Maximum trace delay @@ -1116,6 +1120,11 @@ private: * Set the actual sample rate */ void setSampleRate(int sampleRate); + + /** + * Set the traces size + */ + void setTraceSize(uint32_t traceSize); }; From a03165c688489d9e34982ab105554e0624b25f98 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 16 Oct 2018 06:24:55 +0200 Subject: [PATCH 869/956] Scope: save/load traces memory (4): first working --- sdrbase/util/doublebuffer.h | 4 +++- sdrgui/dsp/scopevis.h | 48 ++++++++++++++++++++++++++++++++++++- sdrgui/gui/glscopegui.cpp | 33 ++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/sdrbase/util/doublebuffer.h b/sdrbase/util/doublebuffer.h index 1dafc3334..cb29255cd 100644 --- a/sdrbase/util/doublebuffer.h +++ b/sdrbase/util/doublebuffer.h @@ -125,7 +125,9 @@ public: d.readU32(2, &tmpUInt, 0); m_current = m_data.begin() + tmpUInt; d.readBlob(3, &buf); - std::copy(reinterpret_cast(m_data.data()), buf.data(), buf.data() + buf.size()); + //qDebug("DoubleBufferSimple::deserialize: m_data.size(): %u buf.size(): %d", m_data.size(), buf.size()); + //std::copy(reinterpret_cast(m_data.data()), buf.data(), buf.data() + buf.size()); // bug + memcpy(reinterpret_cast(m_data.data()), buf.data(), buf.size()); return true; } diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index a45c95d20..174b0ae6a 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -167,6 +167,52 @@ public: void setOneShot(bool oneShot); void setMemoryIndex(uint32_t memoryIndex); + QByteArray serializeMemory() const + { + SimpleSerializer s(1); + + s.writeU32(1, m_traceSize); + s.writeS32(2, m_sampleRate); + QByteArray buffer = m_traceDiscreteMemory.serialize(); + s.writeBlob(3, buffer); + + return s.final(); + } + + bool deserializeMemory(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + uint32_t traceSize; + int sampleRate; + QByteArray buf; + bool traceDiscreteMemorySuccess; + + d.readU32(1, &traceSize, m_traceChunkSize); + d.readS32(2, &sampleRate, 0); + setSampleRate(sampleRate); + setTraceSize(traceSize); + d.readBlob(3, &buf); + traceDiscreteMemorySuccess = m_traceDiscreteMemory.deserialize(buf); + + if (traceDiscreteMemorySuccess && (m_glScope) && (m_currentTraceMemoryIndex > 0)) { + processMemoryTrace(); + } + + return traceDiscreteMemorySuccess; + } + else + { + return false; + } + } + void getTriggerData(TriggerData& triggerData, uint32_t triggerIndex) { if (triggerIndex < m_triggerConditions.size()) @@ -665,7 +711,7 @@ private: for (std::vector::iterator it = m_traceBackBuffers.begin(); it != m_traceBackBuffers.end(); ++it) { - it->resize(4*m_traceSize); + it->resize(4*m_traceSize); // TODO: why 4? } } diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp index 82ce036b7..e218c9d48 100644 --- a/sdrgui/gui/glscopegui.cpp +++ b/sdrgui/gui/glscopegui.cpp @@ -17,6 +17,7 @@ #include #include +#include #include "glscopegui.h" #include "glscope.h" @@ -771,7 +772,6 @@ void GLScopeGUI::on_traceColor_clicked() void GLScopeGUI::on_memorySave_clicked(bool checked __attribute__((unused))) { - qDebug("GLScopeGUI::on_memorySave_clicked"); QString fileName = QFileDialog::getSaveFileName(this, tr("Open trace memory file"), ".", tr("Trace memory files (*.trcm)"), 0, QFileDialog::DontUseNativeDialog); @@ -783,7 +783,20 @@ void GLScopeGUI::on_memorySave_clicked(bool checked __attribute__((unused))) fileName += ".trcm"; } - qDebug("GLScopeGUI::on_memorySave_clicked: %s", qPrintable(fileName)); + QFile exportFile(fileName); + + if (exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QString base64Str = m_scopeVis->serializeMemory().toBase64(); + QTextStream outstream(&exportFile); + outstream << base64Str; + exportFile.close(); + qDebug("GLScopeGUI::on_memorySave_clicked: saved to %s", qPrintable(fileName)); + } + else + { + QMessageBox::information(this, tr("Message"), tr("Cannot open %1 file for writing").arg(fileName)); + } } } @@ -796,7 +809,21 @@ void GLScopeGUI::on_memoryLoad_clicked(bool checked __attribute__((unused))) if (fileName != "") { - qDebug("GLScopeGUI::on_memoryLoad_clicked: %s", qPrintable(fileName)); + QFile exportFile(fileName); + + if (exportFile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QByteArray base64Str; + QTextStream instream(&exportFile); + instream >> base64Str; + exportFile.close(); + m_scopeVis->deserializeMemory(QByteArray::fromBase64(base64Str)); + qDebug("GLScopeGUI::on_memoryLoad_clicked: loaded from %s", qPrintable(fileName)); + } + else + { + QMessageBox::information(this, tr("Message"), tr("Cannot open file %1 for reading").arg(fileName)); + } } } From d510e344a55fb0ee13c9b14f7c55930c34e7eac3 Mon Sep 17 00:00:00 2001 From: beta-tester Date: Tue, 16 Oct 2018 13:16:23 +0200 Subject: [PATCH 870/956] added option for test Hold --- sdrgui/gui/glspectrum.cpp | 27 ++++++++++++++++++++------- sdrgui/gui/glspectrum.h | 2 ++ sdrgui/gui/glspectrumgui.cpp | 15 +++++++++++++++ sdrgui/gui/glspectrumgui.h | 2 ++ sdrgui/gui/glspectrumgui.ui | 25 +++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 69ad6d5ab..1e35e7968 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -49,6 +49,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_displayTraceIntensity(50), m_invertedWaterfall(false), m_displayMaxHold(false), + m_displayTestHold(false), // test m_currentSpectrum(0), m_displayCurrent(false), m_waterfallBuffer(NULL), @@ -248,6 +249,15 @@ void GLSpectrum::setDisplayMaxHold(bool display) update(); } +// test +void GLSpectrum::setDisplayTestHold(bool value) +{ + m_displayTestHold = value; + m_changesPending = true; + stopDrag(); + update(); +} + void GLSpectrum::setDisplayCurrent(bool display) { m_displayCurrent = display; @@ -395,14 +405,17 @@ void GLSpectrum::updateHistogram(const std::vector& spectrum) { *h = *h - sub; } - else if(*h > 0) + else if(!m_displayTestHold) { - *h = *h - 1; - } - else - { - *b = *b - 1; - *h = m_histogramLateHoldoff; + if(*h > 0) + { + *h = *h - 1; + } + else + { + *b = *b - 1; + *h = m_histogramLateHoldoff; + } } } diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index c1be4bdf9..637f4d26d 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -76,6 +76,7 @@ public: void setLsbDisplay(bool lsbDisplay); void setInvertedWaterfall(bool inv); void setDisplayMaxHold(bool display); + void setDisplayTestHold(bool value); // test void setDisplayCurrent(bool display); void setDisplayHistogram(bool display); void setDisplayGrid(bool display); @@ -145,6 +146,7 @@ private: std::vector m_maxHold; bool m_displayMaxHold; + bool m_displayTestHold; // test const std::vector *m_currentSpectrum; bool m_displayCurrent; diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 25eefc448..27c2bb2c8 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -24,6 +24,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_displayWaterfall(true), m_invertedWaterfall(false), m_displayMaxHold(false), + m_displayTestHold(false), // test m_displayCurrent(false), m_displayHistogram(false), m_displayGrid(false), @@ -73,6 +74,7 @@ void GLSpectrumGUI::resetToDefaults() m_displayWaterfall = true; m_invertedWaterfall = false; m_displayMaxHold = false; + m_displayTestHold = false; // test m_displayHistogram = false; m_displayGrid = false; m_invert = true; @@ -93,6 +95,7 @@ QByteArray GLSpectrumGUI::serialize() const s.writeBool(6, m_displayWaterfall); s.writeBool(7, m_invertedWaterfall); s.writeBool(8, m_displayMaxHold); + //s.writeBool(?, m_displayTestHold); // test s.writeBool(9, m_displayHistogram); s.writeS32(10, m_decay); s.writeBool(11, m_displayGrid); @@ -129,6 +132,7 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) d.readBool(6, &m_displayWaterfall, true); d.readBool(7, &m_invertedWaterfall, false); d.readBool(8, &m_displayMaxHold, false); + m_displayTestHold = false; // test d.readBool(9, &m_displayHistogram, false); d.readS32(10, &m_decay, 0); d.readBool(11, &m_displayGrid, false); @@ -175,6 +179,7 @@ void GLSpectrumGUI::applySettings() ui->stroke->setSliderPosition(m_histogramStroke); ui->waterfall->setChecked(m_displayWaterfall); ui->maxHold->setChecked(m_displayMaxHold); + ui->testHold->setChecked(m_displayTestHold); // test ui->current->setChecked(m_displayCurrent); ui->histogram->setChecked(m_displayHistogram); ui->invert->setChecked(m_invert); @@ -190,6 +195,7 @@ void GLSpectrumGUI::applySettings() m_glSpectrum->setDisplayWaterfall(m_displayWaterfall); m_glSpectrum->setInvertedWaterfall(m_invertedWaterfall); m_glSpectrum->setDisplayMaxHold(m_displayMaxHold); + m_glSpectrum->setDisplayTestHold(m_displayTestHold); // test m_glSpectrum->setDisplayCurrent(m_displayCurrent); m_glSpectrum->setDisplayHistogram(m_displayHistogram); m_glSpectrum->setDecay(m_decay); @@ -403,6 +409,15 @@ void GLSpectrumGUI::on_maxHold_toggled(bool checked) } } +// test +void GLSpectrumGUI::on_testHold_toggled(bool checked) +{ + m_displayTestHold = checked; + if(m_glSpectrum != 0) { + m_glSpectrum->setDisplayTestHold(m_displayTestHold); + } +} + void GLSpectrumGUI::on_current_toggled(bool checked) { m_displayCurrent = checked; diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 9cf02a20d..be05916de 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -56,6 +56,7 @@ private: bool m_displayWaterfall; bool m_invertedWaterfall; bool m_displayMaxHold; + bool m_displayTestHold; // test bool m_displayCurrent; bool m_displayHistogram; bool m_displayGrid; @@ -92,6 +93,7 @@ private slots: void on_waterfall_toggled(bool checked); void on_histogram_toggled(bool checked); void on_maxHold_toggled(bool checked); + void on_testHold_toggled(bool checked); // test void on_current_toggled(bool checked); void on_invert_toggled(bool checked); void on_grid_toggled(bool checked); diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index 6c81795fb..b405515eb 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -704,6 +704,31 @@ + + + + + Toggle test Hold + + + Test Hold + + + + :/testHold.png:/testHold.png + + + + 16 + 16 + + + + true + + + + From efb48ce1cccf7c62c398ccc96ea899a513d41c97 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 16 Oct 2018 18:43:46 +0200 Subject: [PATCH 871/956] Scope: save/load traces memory: fixed trace length and pre trigger info propagation to GUI --- sdrgui/dsp/scopevis.cpp | 30 ++++++++++++++++++++---------- sdrgui/dsp/scopevis.h | 23 ++++++++++++++++------- sdrgui/gui/glscope.cpp | 12 ++++++++++-- sdrgui/gui/glscope.h | 6 ++++-- sdrgui/gui/glscopegui.cpp | 17 +++++++++++++++++ sdrgui/gui/glscopegui.h | 2 ++ 6 files changed, 69 insertions(+), 21 deletions(-) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index a0d5c7a40..9d65c6456 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -42,6 +42,7 @@ const uint ScopeVis::m_traceChunkSize = 4800; ScopeVis::ScopeVis(GLScope* glScope) : m_glScope(glScope), m_preTriggerDelay(0), + m_livePreTriggerDelay(0), m_currentTriggerIndex(0), m_focusedTriggerIndex(0), m_triggerState(TriggerUntriggered), @@ -94,7 +95,7 @@ void ScopeVis::setSampleRate(int sampleRate) } } -void ScopeVis::setTraceSize(uint32_t traceSize) +void ScopeVis::setTraceSize(uint32_t traceSize, bool emitSignal) { m_traceSize = traceSize; m_traces.resize(m_traceSize); @@ -102,7 +103,16 @@ void ScopeVis::setTraceSize(uint32_t traceSize) initTraceBuffers(); if (m_glScope) { - m_glScope->setTraceSize(m_traceSize); + m_glScope->setTraceSize(m_traceSize, emitSignal); + } +} + +void ScopeVis::setPreTriggerDelay(uint32_t preTriggerDelay, bool emitSignal) +{ + m_preTriggerDelay = preTriggerDelay; + + if (m_glScope) { + m_glScope->setTriggerPre(m_preTriggerDelay, emitSignal); } } @@ -652,11 +662,7 @@ bool ScopeVis::handleMessage(const Message& message) if (m_preTriggerDelay != triggerPre) { - m_preTriggerDelay = triggerPre; - - if (m_glScope) { - m_glScope->setTriggerPre(m_preTriggerDelay); - } + setPreTriggerDelay(triggerPre); } if (freeRun != m_freeRun) @@ -844,16 +850,20 @@ bool ScopeVis::handleMessage(const Message& message) if (memoryIndex != m_currentTraceMemoryIndex) { // transition from live mode - if (memoryIndex == 0) { + if (m_currentTraceMemoryIndex == 0) + { m_liveTraceSize = m_traceSize; + m_livePreTriggerDelay = m_preTriggerDelay; } m_currentTraceMemoryIndex = memoryIndex; - if (m_currentTraceMemoryIndex == 0) // transition to live mode + // transition to live mode + if (m_currentTraceMemoryIndex == 0) { setSampleRate(m_liveSampleRate); // reset to live rate - setTraceSize(m_liveTraceSize); // reset to live trace size + setTraceSize(m_liveTraceSize, true); // reset to live trace size + setPreTriggerDelay(m_livePreTriggerDelay, true); // reset to live pre-trigger delay } else { diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index 174b0ae6a..97b2cda46 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -172,9 +172,10 @@ public: SimpleSerializer s(1); s.writeU32(1, m_traceSize); - s.writeS32(2, m_sampleRate); + s.writeU32(2, m_preTriggerDelay); + s.writeS32(3, m_sampleRate); QByteArray buffer = m_traceDiscreteMemory.serialize(); - s.writeBlob(3, buffer); + s.writeBlob(4, buffer); return s.final(); } @@ -189,16 +190,18 @@ public: if (d.getVersion() == 1) { - uint32_t traceSize; + uint32_t traceSize, preTriggerDelay; int sampleRate; QByteArray buf; bool traceDiscreteMemorySuccess; d.readU32(1, &traceSize, m_traceChunkSize); - d.readS32(2, &sampleRate, 0); + d.readU32(2, &preTriggerDelay, 0); + d.readS32(3, &sampleRate, 0); setSampleRate(sampleRate); - setTraceSize(traceSize); - d.readBlob(3, &buf); + setTraceSize(traceSize, true); + setPreTriggerDelay(preTriggerDelay, true); + d.readBlob(4, &buf); traceDiscreteMemorySuccess = m_traceDiscreteMemory.deserialize(buf); if (traceDiscreteMemorySuccess && (m_glScope) && (m_currentTraceMemoryIndex > 0)) { @@ -1087,6 +1090,7 @@ private: GLScope* m_glScope; uint32_t m_preTriggerDelay; //!< Pre-trigger delay in number of samples + uint32_t m_livePreTriggerDelay; //!< Pre-trigger delay in number of samples in live mode std::vector m_triggerConditions; //!< Chain of triggers uint32_t m_currentTriggerIndex; //!< Index of current index in the chain uint32_t m_focusedTriggerIndex; //!< Index of the trigger that has focus @@ -1170,7 +1174,12 @@ private: /** * Set the traces size */ - void setTraceSize(uint32_t traceSize); + void setTraceSize(uint32_t traceSize, bool emitSignal = false); + + /** + * Set the pre trigger delay + */ + void setPreTriggerDelay(uint32_t preTriggerDelay, bool emitSignal = false); }; diff --git a/sdrgui/gui/glscope.cpp b/sdrgui/gui/glscope.cpp index 6c710411b..76418b698 100644 --- a/sdrgui/gui/glscope.cpp +++ b/sdrgui/gui/glscope.cpp @@ -938,11 +938,15 @@ void GLScope::setTimeBase(int timeBase) update(); } -void GLScope::setTriggerPre(uint32_t triggerPre) +void GLScope::setTriggerPre(uint32_t triggerPre, bool emitSignal) { m_triggerPre = triggerPre; m_configChanged = true; update(); + + if (emitSignal) { + emit preTriggerChanged(m_triggerPre); + } } void GLScope::setTimeOfsProMill(int timeOfsProMill) @@ -966,11 +970,15 @@ void GLScope::setDisplayMode(DisplayMode displayMode) update(); } -void GLScope::setTraceSize(int traceSize) +void GLScope::setTraceSize(int traceSize, bool emitSignal) { m_traceSize = traceSize; m_configChanged = true; update(); + + if (emitSignal) { + emit traceSizeChanged(m_traceSize); + } } void GLScope::updateDisplay() diff --git a/sdrgui/gui/glscope.h b/sdrgui/gui/glscope.h index 9d7acf152..c33e91cc0 100644 --- a/sdrgui/gui/glscope.h +++ b/sdrgui/gui/glscope.h @@ -58,13 +58,13 @@ public: int getSampleRate() const { return m_sampleRate; } int getTraceSize() const { return m_traceSize; } - void setTriggerPre(uint32_t triggerPre); //!< number of samples + void setTriggerPre(uint32_t triggerPre, bool emitSignal = false); //!< number of samples void setTimeOfsProMill(int timeOfsProMill); void setSampleRate(int sampleRate); void setTimeBase(int timeBase); void setFocusedTraceIndex(uint32_t traceIndex); void setDisplayMode(DisplayMode displayMode); - void setTraceSize(int trceSize); + void setTraceSize(int trceSize, bool emitSignal = false); void updateDisplay(); void setDisplayGridIntensity(int intensity); void setDisplayTraceIntensity(int intensity); @@ -78,6 +78,8 @@ public: signals: void sampleRateChanged(int); + void traceSizeChanged(uint32_t); + void preTriggerChanged(uint32_t); //!< number of samples private: std::vector *m_tracesData; diff --git a/sdrgui/gui/glscopegui.cpp b/sdrgui/gui/glscopegui.cpp index e218c9d48..b4fe97e64 100644 --- a/sdrgui/gui/glscopegui.cpp +++ b/sdrgui/gui/glscopegui.cpp @@ -104,6 +104,8 @@ void GLScopeGUI::setBuddies(MessageQueue* messageQueue, ScopeVis* scopeVis, GLSc setEnabled(true); connect(m_glScope, SIGNAL(sampleRateChanged(int)), this, SLOT(on_scope_sampleRateChanged(int))); + connect(m_glScope, SIGNAL(traceSizeChanged(uint32_t)), this, SLOT(on_scope_traceSizeChanged(uint32_t))); + connect(m_glScope, SIGNAL(preTriggerChanged(uint32_t)), this, SLOT(on_scope_preTriggerChanged(uint32_t))); ui->traceMode->clear(); fillProjectionCombo(ui->traceMode); @@ -149,6 +151,21 @@ void GLScopeGUI::on_scope_sampleRateChanged(int sampleRate) setTrigDelayDisplay(); } +void GLScopeGUI::on_scope_traceSizeChanged(uint32_t traceNbSamples) +{ + qDebug("GLScopeGUI::on_scope_traceSizeChanged: %u", traceNbSamples); + m_traceLenMult = traceNbSamples / ScopeVis::m_traceChunkSize; + ui->traceLen->setValue(m_traceLenMult); + setTraceLenDisplay(); +} + +void GLScopeGUI::on_scope_preTriggerChanged(uint32_t preTriggerNbSamples) +{ + qDebug("GLScopeGUI::on_scope_preTriggerChanged: %u", preTriggerNbSamples); + ui->trigPre->setValue(preTriggerNbSamples*100 / m_glScope->getTraceSize()); // slider position is a percentage value of the trace size + setTrigPreDisplay(); +} + void GLScopeGUI::resetToDefaults() { } diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h index be3ccdd98..c5d181f35 100644 --- a/sdrgui/gui/glscopegui.h +++ b/sdrgui/gui/glscopegui.h @@ -185,6 +185,8 @@ private: private slots: void on_scope_sampleRateChanged(int value); + void on_scope_traceSizeChanged(uint32_t value); + void on_scope_preTriggerChanged(uint32_t value); // First row void on_onlyX_toggled(bool checked); void on_onlyY_toggled(bool checked); From 673a145490afc386c7ad848cac4b53b7cdaed8e2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 16 Oct 2018 23:56:09 +0200 Subject: [PATCH 872/956] Spectrum: limit averaging depth to 1000 when in moving average mode to avoid memory exhaustion --- debian/changelog | 1 + sdrgui/dsp/spectrumvis.cpp | 2 +- sdrgui/gui/glspectrumgui.cpp | 15 +++++++++++++++ sdrgui/readme.md | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 899edc393..35ee4c187 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,7 @@ sdrangel (4.2.3-1) unstable; urgency=medium * Scope: fixed channel rate affecting scope in memory mode. Issue #227 + * Spectrum: limit depth to 1000 when in moving average mode to avoid RAM exhaustion -- Edouard Griffiths, F4EXB Sun, 21 Oct 2018 21:14:18 +0200 diff --git a/sdrgui/dsp/spectrumvis.cpp b/sdrgui/dsp/spectrumvis.cpp index 0389cdc91..0fbeb0384 100644 --- a/sdrgui/dsp/spectrumvis.cpp +++ b/sdrgui/dsp/spectrumvis.cpp @@ -364,7 +364,7 @@ void SpectrumVis::handleConfigure(int fftSize, m_overlapSize = (m_fftSize * m_overlapPercent) / 100; m_refillSize = m_fftSize - m_overlapSize; m_fftBufferFill = m_overlapSize; - m_movingAverage.resize(fftSize, averageNb); + m_movingAverage.resize(fftSize, averageNb > 1000 ? 1000 : averageNb); // Capping to avoid out of memory condition m_fixedAverage.resize(fftSize, averageNb); m_max.resize(fftSize, averageNb); m_averageNb = averageNb; diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 25eefc448..0d2dc6814 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -246,6 +246,18 @@ void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index) { m_averagingMode = index < 0 ? AvgModeNone : index > 3 ? AvgModeMax : (AveragingMode) index; + if (m_averagingMode == AvgModeMoving) + { + m_averagingMaxScale = 2; + setAveragingCombo(); + m_averagingNb = m_averagingNb > 1000 ? 1000 : m_averagingNb; + } + else + { + m_averagingMaxScale = 5; + setAveragingCombo(); + } + if(m_spectrumVis != 0) { m_spectrumVis->configure(m_messageQueueToVis, m_fftSize, @@ -507,6 +519,7 @@ int GLSpectrumGUI::getAveragingValue(int averagingIndex) const void GLSpectrumGUI::setAveragingCombo() { + int index = ui->averaging->currentIndex(); ui->averaging->clear(); ui->averaging->addItem(QString("1")); @@ -524,6 +537,8 @@ void GLSpectrumGUI::setAveragingCombo() setNumberStr(x, s); ui->averaging->addItem(s); } + + ui->averaging->setCurrentIndex(index >= ui->averaging->count() ? ui->averaging->count() - 1 : index); } void GLSpectrumGUI::setNumberStr(int n, QString& s) diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 1f6ee8f87..8130633f3 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -273,7 +273,7 @@ Use this combo to select which averaging mode is applied:

    4.6. Number of averaged samples

    -Each FFT bin (squared magnitude) is averaged or max'ed over a number of samples. This combo allows selecting the number of samples between these values: 1 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000), 2k, 5k, 10k, 20k, 50k, 1e5 (100000), 2e5, 5e5, 1M (1000000). The tooltip mentions the resulting averaging period considering the baseband sample rate and FFT size. +Each FFT bin (squared magnitude) is averaged or max'ed over a number of samples. This combo allows selecting the number of samples between these values: 1 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000) for all modes and in addition 2k, 5k, 10k, 20k, 50k, 1e5 (100000), 2e5, 5e5, 1M (1000000) for "fixed" and "max" modes. The tooltip mentions the resulting averaging period considering the baseband sample rate and FFT size. Averaging reduces the noise variance and can be used to better detect weak continuous signals. The fixed averaging mode allows long time monitoring on the waterfall. The max mode helps showing short bursts that may appear during the "averaging" period. ☞ Note: The spectrum display is refreshed every 50ms (20 FPS). Setting an averaging time above this value will make sure that a short burst is not missed particularly when using the max mode. From 2f955ba979734f941079a881da70d5fc0d94142a Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 17 Oct 2018 13:22:09 +0200 Subject: [PATCH 873/956] Revert "added option for test Hold". Doing something else for the same result. This reverts commit d510e344a55fb0ee13c9b14f7c55930c34e7eac3. --- sdrgui/gui/glspectrum.cpp | 27 +++++++-------------------- sdrgui/gui/glspectrum.h | 2 -- sdrgui/gui/glspectrumgui.cpp | 15 --------------- sdrgui/gui/glspectrumgui.h | 2 -- sdrgui/gui/glspectrumgui.ui | 25 ------------------------- 5 files changed, 7 insertions(+), 64 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 1e35e7968..69ad6d5ab 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -49,7 +49,6 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_displayTraceIntensity(50), m_invertedWaterfall(false), m_displayMaxHold(false), - m_displayTestHold(false), // test m_currentSpectrum(0), m_displayCurrent(false), m_waterfallBuffer(NULL), @@ -249,15 +248,6 @@ void GLSpectrum::setDisplayMaxHold(bool display) update(); } -// test -void GLSpectrum::setDisplayTestHold(bool value) -{ - m_displayTestHold = value; - m_changesPending = true; - stopDrag(); - update(); -} - void GLSpectrum::setDisplayCurrent(bool display) { m_displayCurrent = display; @@ -405,17 +395,14 @@ void GLSpectrum::updateHistogram(const std::vector& spectrum) { *h = *h - sub; } - else if(!m_displayTestHold) + else if(*h > 0) { - if(*h > 0) - { - *h = *h - 1; - } - else - { - *b = *b - 1; - *h = m_histogramLateHoldoff; - } + *h = *h - 1; + } + else + { + *b = *b - 1; + *h = m_histogramLateHoldoff; } } diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index 637f4d26d..c1be4bdf9 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -76,7 +76,6 @@ public: void setLsbDisplay(bool lsbDisplay); void setInvertedWaterfall(bool inv); void setDisplayMaxHold(bool display); - void setDisplayTestHold(bool value); // test void setDisplayCurrent(bool display); void setDisplayHistogram(bool display); void setDisplayGrid(bool display); @@ -146,7 +145,6 @@ private: std::vector m_maxHold; bool m_displayMaxHold; - bool m_displayTestHold; // test const std::vector *m_currentSpectrum; bool m_displayCurrent; diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 029387dad..0d2dc6814 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -24,7 +24,6 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_displayWaterfall(true), m_invertedWaterfall(false), m_displayMaxHold(false), - m_displayTestHold(false), // test m_displayCurrent(false), m_displayHistogram(false), m_displayGrid(false), @@ -74,7 +73,6 @@ void GLSpectrumGUI::resetToDefaults() m_displayWaterfall = true; m_invertedWaterfall = false; m_displayMaxHold = false; - m_displayTestHold = false; // test m_displayHistogram = false; m_displayGrid = false; m_invert = true; @@ -95,7 +93,6 @@ QByteArray GLSpectrumGUI::serialize() const s.writeBool(6, m_displayWaterfall); s.writeBool(7, m_invertedWaterfall); s.writeBool(8, m_displayMaxHold); - //s.writeBool(?, m_displayTestHold); // test s.writeBool(9, m_displayHistogram); s.writeS32(10, m_decay); s.writeBool(11, m_displayGrid); @@ -132,7 +129,6 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) d.readBool(6, &m_displayWaterfall, true); d.readBool(7, &m_invertedWaterfall, false); d.readBool(8, &m_displayMaxHold, false); - m_displayTestHold = false; // test d.readBool(9, &m_displayHistogram, false); d.readS32(10, &m_decay, 0); d.readBool(11, &m_displayGrid, false); @@ -179,7 +175,6 @@ void GLSpectrumGUI::applySettings() ui->stroke->setSliderPosition(m_histogramStroke); ui->waterfall->setChecked(m_displayWaterfall); ui->maxHold->setChecked(m_displayMaxHold); - ui->testHold->setChecked(m_displayTestHold); // test ui->current->setChecked(m_displayCurrent); ui->histogram->setChecked(m_displayHistogram); ui->invert->setChecked(m_invert); @@ -195,7 +190,6 @@ void GLSpectrumGUI::applySettings() m_glSpectrum->setDisplayWaterfall(m_displayWaterfall); m_glSpectrum->setInvertedWaterfall(m_invertedWaterfall); m_glSpectrum->setDisplayMaxHold(m_displayMaxHold); - m_glSpectrum->setDisplayTestHold(m_displayTestHold); // test m_glSpectrum->setDisplayCurrent(m_displayCurrent); m_glSpectrum->setDisplayHistogram(m_displayHistogram); m_glSpectrum->setDecay(m_decay); @@ -421,15 +415,6 @@ void GLSpectrumGUI::on_maxHold_toggled(bool checked) } } -// test -void GLSpectrumGUI::on_testHold_toggled(bool checked) -{ - m_displayTestHold = checked; - if(m_glSpectrum != 0) { - m_glSpectrum->setDisplayTestHold(m_displayTestHold); - } -} - void GLSpectrumGUI::on_current_toggled(bool checked) { m_displayCurrent = checked; diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index be05916de..9cf02a20d 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -56,7 +56,6 @@ private: bool m_displayWaterfall; bool m_invertedWaterfall; bool m_displayMaxHold; - bool m_displayTestHold; // test bool m_displayCurrent; bool m_displayHistogram; bool m_displayGrid; @@ -93,7 +92,6 @@ private slots: void on_waterfall_toggled(bool checked); void on_histogram_toggled(bool checked); void on_maxHold_toggled(bool checked); - void on_testHold_toggled(bool checked); // test void on_current_toggled(bool checked); void on_invert_toggled(bool checked); void on_grid_toggled(bool checked); diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index b405515eb..6c81795fb 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -704,31 +704,6 @@
    - - - - - Toggle test Hold - - - Test Hold - - - - :/testHold.png:/testHold.png - - - - 16 - 16 - - - - true - - - - From 9eb08541ee1683654b3e571c26a7eccab46e2499 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 17 Oct 2018 13:35:04 +0200 Subject: [PATCH 874/956] Spectrum: allow zero decay so history (including max hold) is kept forever --- sdrgui/gui/glspectrum.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 69ad6d5ab..f96a8f624 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -371,31 +371,31 @@ void GLSpectrum::updateHistogram(const std::vector& spectrum) { quint8* b = m_histogram; quint8* h = m_histogramHoldoff; - int sub = 1; + int sub = 0; // was 1; int fftMulSize = 100 * m_fftSize; - if(m_decay > 0) - sub += m_decay; + //if(m_decay > 0) + sub += m_decay; // allow zero decay so history (including max hold) is kept forever if (m_displayHistogram || m_displayMaxHold) { m_histogramHoldoffCount--; - if(m_histogramHoldoffCount <= 0) + if (m_histogramHoldoffCount <= 0) { - for(int i = 0; i < fftMulSize; i++) + for (int i = 0; i < fftMulSize; i++) { - if((*b>>4) > 0) // *b > 16 + if (*b > 16) // was 20 { *b = *b - sub; } - else if(*b > 0) + else if (*b > 0) { - if(*h >= sub) + if (*h >= sub) { *h = *h - sub; } - else if(*h > 0) + else if (*h > 0) { *h = *h - 1; } From 71e9556b6fbd2617330028260533f6c56a71fd97 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 17 Oct 2018 14:21:25 +0200 Subject: [PATCH 875/956] Spectrum: restore holdoff to what it should actually do (trim transient contribution). Reviewed decay, holdoff and stoke limits --- sdrgui/gui/glspectrum.cpp | 24 ++++++------------------ sdrgui/gui/glspectrum.h | 2 +- sdrgui/gui/glspectrumgui.cpp | 18 +++++++++--------- sdrgui/gui/glspectrumgui.h | 2 +- sdrgui/gui/glspectrumgui.ui | 4 ++-- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index f96a8f624..eaf6e8b62 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -104,9 +104,9 @@ GLSpectrum::GLSpectrum(QWidget* parent) : ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); } - m_histogramHoldoffBase = 2; // was 4 + m_histogramHoldoffBase = 4; // was 2 m_histogramHoldoffCount = m_histogramHoldoffBase; - m_histogramLateHoldoff = 1; // was 20 + m_histogramLateHoldoff = 20; // was 1 m_histogramStroke = 40; // was 4 m_timeScale.setFont(font()); @@ -170,29 +170,17 @@ void GLSpectrum::setPowerRange(Real powerRange) void GLSpectrum::setDecay(int decay) { - m_decay = decay; - if(m_decay < 0) - m_decay = 0; - else if(m_decay > 10) - m_decay = 10; + m_decay = decay < 0 ? 0 : decay > 20 ? 20 : decay; } -void GLSpectrum::setHistoLateHoldoff(int lateHoldoff) +void GLSpectrum::setHistoHoldoffBase(int holdoffBase) { - m_histogramLateHoldoff = lateHoldoff; - if(m_histogramLateHoldoff < 0) - m_histogramLateHoldoff = 0; - else if(m_histogramLateHoldoff > 20) - m_histogramLateHoldoff = 20; + m_histogramHoldoffBase = holdoffBase < 0 ? 0 : holdoffBase > 20 ? 20 : holdoffBase; } void GLSpectrum::setHistoStroke(int stroke) { - m_histogramStroke = stroke; - if(m_histogramStroke < 4) - m_histogramStroke = 4; - else if(m_histogramStroke > 240) - m_histogramStroke = 240; + m_histogramStroke = stroke < 4 ? 4 : stroke > 240 ? 240 : stroke; } void GLSpectrum::setSampleRate(qint32 sampleRate) diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index c1be4bdf9..0ecbfbb85 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -69,7 +69,7 @@ public: void setReferenceLevel(Real referenceLevel); void setPowerRange(Real powerRange); void setDecay(int decay); - void setHistoLateHoldoff(int lateHoldoff); + void setHistoHoldoffBase(int holdoffBase); void setHistoStroke(int stroke); void setDisplayWaterfall(bool display); void setSsbSpectrum(bool ssbSpectrum); diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 0d2dc6814..b131664f7 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -17,7 +17,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_refLevel(0), m_powerRange(100), m_decay(0), - m_histogramLateHoldoff(1), + m_histogramHoldoffBase(0), m_histogramStroke(40), m_displayGridIntensity(5), m_displayTraceIntensity(50), @@ -67,7 +67,7 @@ void GLSpectrumGUI::resetToDefaults() m_refLevel = 0; m_powerRange = 100; m_decay = 0; - m_histogramLateHoldoff = 1; + m_histogramHoldoffBase = 0; m_histogramStroke = 40; m_displayGridIntensity = 5, m_displayWaterfall = true; @@ -98,7 +98,7 @@ QByteArray GLSpectrumGUI::serialize() const s.writeBool(11, m_displayGrid); s.writeBool(12, m_invert); s.writeS32(13, m_displayGridIntensity); - s.writeS32(14, m_histogramLateHoldoff); + s.writeS32(14, m_histogramHoldoffBase); s.writeS32(15, m_histogramStroke); s.writeBool(16, m_displayCurrent); s.writeS32(17, m_displayTraceIntensity); @@ -134,7 +134,7 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) d.readBool(11, &m_displayGrid, false); d.readBool(12, &m_invert, true); d.readS32(13, &m_displayGridIntensity, 5); - d.readS32(14, &m_histogramLateHoldoff, 1); + d.readS32(14, &m_histogramHoldoffBase, 0); d.readS32(15, &m_histogramStroke, 40); d.readBool(16, &m_displayCurrent, false); d.readS32(17, &m_displayTraceIntensity, 50); @@ -171,7 +171,7 @@ void GLSpectrumGUI::applySettings() ui->averagingMode->setCurrentIndex((int) m_averagingMode); ui->linscale->setChecked(m_linear); ui->decay->setSliderPosition(m_decay); - ui->holdoff->setSliderPosition(m_histogramLateHoldoff); + ui->holdoff->setSliderPosition(m_histogramHoldoffBase); ui->stroke->setSliderPosition(m_histogramStroke); ui->waterfall->setChecked(m_displayWaterfall); ui->maxHold->setChecked(m_displayMaxHold); @@ -182,7 +182,7 @@ void GLSpectrumGUI::applySettings() ui->gridIntensity->setSliderPosition(m_displayGridIntensity); ui->decay->setToolTip(QString("Decay: %1").arg(m_decay)); - ui->holdoff->setToolTip(QString("Holdoff: %1").arg(m_histogramLateHoldoff)); + ui->holdoff->setToolTip(QString("Holdoff: %1").arg(m_histogramHoldoffBase)); ui->stroke->setToolTip(QString("Stroke: %1").arg(m_histogramStroke)); ui->gridIntensity->setToolTip(QString("Grid intensity: %1").arg(m_displayGridIntensity)); ui->traceIntensity->setToolTip(QString("Trace intensity: %1").arg(m_displayTraceIntensity)); @@ -193,7 +193,7 @@ void GLSpectrumGUI::applySettings() m_glSpectrum->setDisplayCurrent(m_displayCurrent); m_glSpectrum->setDisplayHistogram(m_displayHistogram); m_glSpectrum->setDecay(m_decay); - m_glSpectrum->setHistoLateHoldoff(m_histogramLateHoldoff); + m_glSpectrum->setHistoHoldoffBase(m_histogramHoldoffBase); m_glSpectrum->setHistoStroke(m_histogramStroke); m_glSpectrum->setInvertedWaterfall(m_invert); m_glSpectrum->setDisplayGrid(m_displayGrid); @@ -372,8 +372,8 @@ void GLSpectrumGUI::on_holdoff_valueChanged(int index) if (index < 0) { return; } - m_histogramLateHoldoff = index; - //ui->holdoff->setToolTip(QString("Holdoff: %1").arg(m_histogramLateHoldoff)); + m_histogramHoldoffBase = index; + //ui->holdoff->setToolTip(QString("Holdoff: %1").arg(m_histogramHoldoffBase)); if(m_glSpectrum != 0) { applySettings(); } diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 9cf02a20d..d3f4537d4 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -49,7 +49,7 @@ private: Real m_refLevel; Real m_powerRange; int m_decay; - int m_histogramLateHoldoff; + int m_histogramHoldoffBase; int m_histogramStroke; int m_displayGridIntensity; int m_displayTraceIntensity; diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index 6c81795fb..bf898392f 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -17,7 +17,7 @@ - Oscilloscope + Spectrum @@ -52,7 +52,7 @@ Decay: - 240 + 20 1 From 635bea2059ca4dda22bd16e2030dc6e6b0ccaa8a Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 17 Oct 2018 14:26:31 +0200 Subject: [PATCH 876/956] Spectrum: updated documentation --- sdrgui/readme.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 8130633f3..34b5cfbf4 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -2,7 +2,7 @@

    Multi device support

    -Starting with version 2 SDRangel supports running several sampling devices simultaneously. Each concurrent device is associated to a slot with a set of tabbed windows in the UI. These tabs are marked R0, R1, R2... +Starting with version 2 SDRangel supports running several sampling devices simultaneously. Each concurrent device is associated to a slot with a set of tabbed windows in the UI. These tabs are marked R0, R1, R2... The slots are arranged in a stacked fashion so that when a new device is added with the Acquisition -> Add device set menu a new slot is allocated in the last position and when a device is removed with the Acquisition -> Remove last device set menu the slot in the last position is deleted. Slot 0 (R0) receiver slot is created at initialization and cannot be deleted with the menu. The letter "R" in the tab names indicates that the slot is for a receiver (source) device while "T" designates a transmitter (sink) device. @@ -52,7 +52,7 @@ The following items are presented hierarchically from left to right: - _My Position_: opens a dialog to enter your station ("My Position") coordinates in decimal degrees with north latitudes positive and east longitudes positive. This is used whenever positional data is to be displayed (APRS, DPRS, ...). For it now only works with D-Star $$CRC frames. See [DSD demod plugin](../plugins/channelrx/demoddsd/readme.md) for details on how to decode Digital Voice modes. - Help: - _Loaded Plugins_: shows details about the loaded plugins (see 1.3 below for details) - - _About_: current version and blah blah. + - _About_: current version and blah blah.

    1.1. Preferences - Audio

    @@ -151,12 +151,12 @@ This is where the plugin GUI specific to the device is displayed. Control of one - When a play icon (▶) is displayed with a blue background the device is ready to start - When a stop icon (■) is displayed with a green background the device is currently running - When a play icon (▶) is displayed with a red background there is an error and a popup displays the error message. An Error typically occurs when you try to start the same device in more than one tab. - +

    2.2. Record I/Q

    This is the I/Q from device record toggle. When a red background is displayed the recording is currently active. The name of the file created is `test_n.sdriq` where `n` is the slot number. -The format is S16LE I/Q samples. Thus there are 4 bytes per sample. I and Q values are 16 bit signed integers. The file starts with a context header containing information about center frequency, sample rate and timestamp of the start of the recording. This header has a length which is a multiple of a sample size (normally 24 bytes thus 6 samples). Thus this file can be used as a raw I/Q file with S16LE samples tolerating a glitch at the start corresponding to the 6 "random" samples. +The format is S16LE I/Q samples. Thus there are 4 bytes per sample. I and Q values are 16 bit signed integers. The file starts with a context header containing information about center frequency, sample rate and timestamp of the start of the recording. This header has a length which is a multiple of a sample size (normally 24 bytes thus 6 samples). Thus this file can be used as a raw I/Q file with S16LE samples tolerating a glitch at the start corresponding to the 6 "random" samples. You can also zap the 24 bytes header with this Linux command: `tail -c +25 myfile.sdriq > myfile.raw` @@ -171,7 +171,7 @@ This is the sampling rate in kS/s of the I/Q stream extracted from the device af

    2.4. Center frequency

    -This is the current center frequency in kHz with dot separated thousands (MHz, GHz). On devices for which frequency can be directly controlled (i.e. all except File Source and SDRdaemon) you can use the thumbwheels to set the frequency. Thumbwheels move with the mouse wheel when hovering over a digit. +This is the current center frequency in kHz with dot separated thousands (MHz, GHz). On devices for which frequency can be directly controlled (i.e. all except File Source and SDRdaemon) you can use the thumbwheels to set the frequency. Thumbwheels move with the mouse wheel when hovering over a digit. When left clicking on a digit a cursor is set on it and you can also use the arrows to move the corresponding thumbwheel. @@ -244,7 +244,7 @@ Use this combo box to select which window is applied to the FFT: - **Ham**: Hamming (default) - **Han**: Hanning - **Rec**: Rectangular (no window) - +

    4.2. FFT size

    Select the size of the FFT window among these values: @@ -269,10 +269,10 @@ Use this combo to select which averaging mode is applied: - **No**: no averaging. Disables averaging regardless of the number of averaged samples (4.6). This is the default option - **Mov**: moving average. This is a sliding average over the amount of samples specified next (4.6). There is one complete FFT line produced at every FFT sampling period - **Fix**: fixed average. Average is done over the amount of samples specified next (4.6) and a result is produced at the end of the corresponding period then the next block of averaged samples is processed. There is one complete FFT line produced every FFT sampling period multiplied by the number of averaged samples (4.6). The time scale on the waterfall display is updated accordingly. - - **Max**: this is not an averaging but a max hold. It will retain the maximum value over the amount of samples specified next (4.6). Similarly to the fixed average a result is produced at the end of the corresponding period which results in slowing down the waterfall display. The point of this mode is to make outlying short bursts within the "averaging" period stand out. With averaging they would only cause a modest increase and could be missed out. + - **Max**: this is not an averaging but a max hold. It will retain the maximum value over the amount of samples specified next (4.6). Similarly to the fixed average a result is produced at the end of the corresponding period which results in slowing down the waterfall display. The point of this mode is to make outlying short bursts within the "averaging" period stand out. With averaging they would only cause a modest increase and could be missed out.

    4.6. Number of averaged samples

    - + Each FFT bin (squared magnitude) is averaged or max'ed over a number of samples. This combo allows selecting the number of samples between these values: 1 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000) for all modes and in addition 2k, 5k, 10k, 20k, 50k, 1e5 (100000), 2e5, 5e5, 1M (1000000) for "fixed" and "max" modes. The tooltip mentions the resulting averaging period considering the baseband sample rate and FFT size. Averaging reduces the noise variance and can be used to better detect weak continuous signals. The fixed averaging mode allows long time monitoring on the waterfall. The max mode helps showing short bursts that may appear during the "averaging" period. @@ -280,15 +280,15 @@ Averaging reduces the noise variance and can be used to better detect weak conti

    4.7. Phosphor display stroke decay

    -This controls the decay rate of the stroke when phosphor display is engaged (4.C) +This controls the decay rate of the stroke when phosphor display is engaged (4.C). A value of zero means no decay and thus phosphor history and max hold (red line) will be kept until the clear button (4.B) is pressed.

    4.8. Phosphor display holdoff

    -This controls the holdoff when phosphor display is engaged (4.C) +This controls the holdoff value when phosphor display is engaged (4.C). The holdoff value will drive how much hits are needed before an even appears in the history. Practically you increase this value to trim larger transient signals. A value of 0 means no holdoff and all signals are contributing.

    4.9. Phosphor display stroke strength

    -This controls the stroke strength when phosphor display is engaged (4.C) +This controls the stroke strength when phosphor display is engaged (4.C).

    4.A. Trace intensity

    @@ -334,7 +334,7 @@ When in linear mode the range control (4.4) has no effect because the actual ran

    5. Presets and commands

    -The presets and commands tree view are by default stacked in tabs. The following sections describe the presets section 5A) and commands (section 5B) views successively +The presets and commands tree view are by default stacked in tabs. The following sections describe the presets section 5A) and commands (section 5B) views successively

    5A. Presets

    @@ -364,7 +364,7 @@ You can give a name to your preset. Names need not to be unique.

    5A.6. Preset control or actions

    -The controls are located as icons at the bottom of the window: +The controls are located as icons at the bottom of the window: ![Main Window presets](../doc/img/MainWindow_presets.png) @@ -378,7 +378,7 @@ Click on this icon to create a update the selected preset with the current value
    5A.6.3. Edit preset
    -Opens a new window where you can change the group name and description. +Opens a new window where you can change the group name and description. - for group items you can rename the group or merge all group presets into an existing group by selecting this existing group - for preset items you can: @@ -400,12 +400,12 @@ This is the opposite of the previous operation. This will create a new preset in
    5A.6.7. Delete preset
    - - on a preset item: deletes the selected preset. + - on a preset item: deletes the selected preset. - on a preset group: deletes the group and all its presets.
    5A.6.8. Load preset
    -Applies the selected preset to the current device set (source and channel plugins). +Applies the selected preset to the current device set (source and channel plugins).

    5B. Commands

    @@ -441,7 +441,7 @@ This is a descriptive text of the key sequence that is used for the key binding.

    5B.6. Command control or actions

    -The controls are located as icons at the bottom of the window: +The controls are located as icons at the bottom of the window: ![Main Window commands](../doc/img/MainWindow_commands.png) @@ -497,7 +497,7 @@ You can use special codes to insert information specific to the application cont - `%1`: the address of the web REST API - `%2`: the port of the web REST API - `%3`: the currently selected device set index - +
    5B.6.3.6. Key binding
    Use this checkbox to enable or disable the command execution binding to a key or combination of keys press or release event @@ -539,7 +539,7 @@ When the process is not running the stop icon (■) is displayed. The backgr - no color (same as background): the process has never run during this session - red: the process ended with error - green: the process ended successfully. This does not mean that there was no programmatic error. - + When the process is running the play icon (▶) is displayed with an orange background.
    5B.6.5.2. Refresh data
    @@ -577,7 +577,7 @@ This is the translation of `QProcess::ProcessError`. Possible values are: - `Write error`: an error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - `Read error`: an error occurred when attempting to read from the process. For example, the process may not be running. - `Unknown error`: an unknown error occurred. - +
    5B.6.5.9. Exit code
    This is the program exit code. When the process crashes this is the signal by which the process end was caused. For example if you kill the process with button (6) it sends the process a SIGKILL (code 9) and therefore the value is 9. @@ -632,7 +632,7 @@ When the mouse is over the channel window or over the central line in the spectr - Title: channel window title - AdSnd: UDP address and send port - AdRcv: UDP address and receive port - +

    6.4: Validate and exit dialog

    Validates the data (saves it in the channel marker object) and exits the dialog @@ -646,7 +646,7 @@ Do not make any changes and exit dialog This shows the spectrum in the passband returned from the sampling device possibly after decimation. The actual sample rate is shown in the device control at the left of the frequency display (2.3) The spectrum display is controlled by the display control (4). - +

    8. Status

    ![Main Window status](../doc/img/MainWindow_status.png) From 9158da20e0b51ed690b5e9825ea8ddd9fe8bd036 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 17 Oct 2018 15:28:53 +0200 Subject: [PATCH 877/956] Spectrum: reviewed histogram capping to phosphor palette and stroke value limits --- sdrgui/gui/glspectrum.cpp | 23 ++++++++++++++--------- sdrgui/gui/glspectrumgui.cpp | 10 ++-------- sdrgui/gui/glspectrumgui.ui | 4 ++-- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index eaf6e8b62..8c8bedc8f 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -107,7 +107,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_histogramHoldoffBase = 4; // was 2 m_histogramHoldoffCount = m_histogramHoldoffBase; m_histogramLateHoldoff = 20; // was 1 - m_histogramStroke = 40; // was 4 + m_histogramStroke = 4; m_timeScale.setFont(font()); m_timeScale.setOrientation(Qt::Vertical); @@ -175,12 +175,12 @@ void GLSpectrum::setDecay(int decay) void GLSpectrum::setHistoHoldoffBase(int holdoffBase) { - m_histogramHoldoffBase = holdoffBase < 0 ? 0 : holdoffBase > 20 ? 20 : holdoffBase; + m_histogramHoldoffBase = holdoffBase < 0 ? 0 : holdoffBase > 240 ? 240 : holdoffBase; } void GLSpectrum::setHistoStroke(int stroke) { - m_histogramStroke = stroke < 4 ? 4 : stroke > 240 ? 240 : stroke; + m_histogramStroke = stroke < 1 ? 1 : stroke > 40 ? 40 : stroke; } void GLSpectrum::setSampleRate(qint32 sampleRate) @@ -373,7 +373,7 @@ void GLSpectrum::updateHistogram(const std::vector& spectrum) { for (int i = 0; i < fftMulSize; i++) { - if (*b > 16) // was 20 + if (*b > 20) // must be more than max value of m_decay { *b = *b - sub; } @@ -470,15 +470,20 @@ void GLSpectrum::updateHistogram(const std::vector& spectrum) } } #else - for(int i = 0; i < m_fftSize; i++) { + for (int i = 0; i < m_fftSize; i++) + { int v = (int)((spectrum[i] - m_referenceLevel) * 100.0 / m_powerRange + 100.0); - if ((v >= 0) && (v <= 99)) { + if ((v >= 0) && (v <= 99)) + { b = m_histogram + i * 100 + v; - if(*b < 220) + + // capping to 239 as palette values are [0..239] + if (*b + m_histogramStroke <= 239) { *b += m_histogramStroke; // was 4 - else if(*b < 239) - *b += 1; + } else { + *b = 239; + } } } #endif diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index b131664f7..1c65233d6 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -68,7 +68,7 @@ void GLSpectrumGUI::resetToDefaults() m_powerRange = 100; m_decay = 0; m_histogramHoldoffBase = 0; - m_histogramStroke = 40; + m_histogramStroke = 4; m_displayGridIntensity = 5, m_displayWaterfall = true; m_invertedWaterfall = false; @@ -135,7 +135,7 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) d.readBool(12, &m_invert, true); d.readS32(13, &m_displayGridIntensity, 5); d.readS32(14, &m_histogramHoldoffBase, 0); - d.readS32(15, &m_histogramStroke, 40); + d.readS32(15, &m_histogramStroke, 4); d.readBool(16, &m_displayCurrent, false); d.readS32(17, &m_displayTraceIntensity, 50); Real waterfallShare; @@ -369,9 +369,6 @@ void GLSpectrumGUI::on_decay_valueChanged(int index) void GLSpectrumGUI::on_holdoff_valueChanged(int index) { - if (index < 0) { - return; - } m_histogramHoldoffBase = index; //ui->holdoff->setToolTip(QString("Holdoff: %1").arg(m_histogramHoldoffBase)); if(m_glSpectrum != 0) { @@ -381,9 +378,6 @@ void GLSpectrumGUI::on_holdoff_valueChanged(int index) void GLSpectrumGUI::on_stroke_valueChanged(int index) { - if (index < 4) { - return; - } m_histogramStroke = index; //ui->stroke->setToolTip(QString("Stroke: %1").arg(m_histogramStroke)); if(m_glSpectrum != 0) { diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index bf898392f..7dbcb01aa 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -93,10 +93,10 @@ Stroke:
    - 4 + 1 - 240 + 40 1 From e41df6f2780834b9fd60980b882d054277e93651 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 19 Oct 2018 00:40:14 +0200 Subject: [PATCH 878/956] Spectrum: reworked phosphor display controls --- debian/changelog | 3 +- plugins/channeltx/modnfm/readme.md | 2 +- sdrgui/gui/glspectrum.cpp | 70 +++++++++--------------------- sdrgui/gui/glspectrum.h | 10 ++--- sdrgui/gui/glspectrumgui.cpp | 32 +++++++------- sdrgui/gui/glspectrumgui.h | 4 +- sdrgui/gui/glspectrumgui.ui | 8 ++-- sdrgui/readme.md | 6 +-- 8 files changed, 52 insertions(+), 83 deletions(-) diff --git a/debian/changelog b/debian/changelog index 35ee4c187..e286444e9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,8 +2,9 @@ sdrangel (4.2.3-1) unstable; urgency=medium * Scope: fixed channel rate affecting scope in memory mode. Issue #227 * Spectrum: limit depth to 1000 when in moving average mode to avoid RAM exhaustion + * Spectrum: reworked phosphor display controls. Re-implements issue #207 - -- Edouard Griffiths, F4EXB Sun, 21 Oct 2018 21:14:18 +0200 + -- Edouard Griffiths, F4EXB Fri, 19 Oct 2018 21:14:18 +0200 sdrangel (4.2.2-1) unstable; urgency=medium diff --git a/plugins/channeltx/modnfm/readme.md b/plugins/channeltx/modnfm/readme.md index 1ec04715a..8e6395abb 100644 --- a/plugins/channeltx/modnfm/readme.md +++ b/plugins/channeltx/modnfm/readme.md @@ -8,7 +8,7 @@ This plugin can be used to generate a narrowband frequency modulated signal. "Na ![NFM Modulator plugin GUI](../../../doc/img/NFMMod_plugin.png) -

    1: Frequency shift from center frequency of transmission/h3> +

    1: Frequency shift from center frequency of transmission

    Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 8c8bedc8f..1d737b6a2 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -40,7 +40,7 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_referenceLevel(0), m_powerRange(100), m_linear(false), - m_decay(0), + m_decay(1), m_sampleRate(500000), m_timingRate(1), m_fftSize(512), @@ -51,16 +51,15 @@ GLSpectrum::GLSpectrum(QWidget* parent) : m_displayMaxHold(false), m_currentSpectrum(0), m_displayCurrent(false), - m_waterfallBuffer(NULL), + m_waterfallBuffer(0), m_waterfallBufferPos(0), m_waterfallTextureHeight(-1), m_waterfallTexturePos(0), m_displayWaterfall(true), m_ssbSpectrum(false), m_lsbDisplay(false), - m_histogramBuffer(NULL), - m_histogram(NULL), - m_histogramHoldoff(NULL), + m_histogramBuffer(0), + m_histogram(0), m_displayHistogram(true), m_displayChanged(false), m_matrixLoc(0), @@ -86,8 +85,8 @@ GLSpectrum::GLSpectrum(QWidget* parent) : } m_waterfallPalette[239] = 0xffffffff; - m_histogramPalette[0] = m_waterfallPalette[0]; - for(int i = 1; i < 240; i++) { + m_histogramPalette[0] = 0; + for(int i = 16; i < 240; i++) { QColor c; c.setHsv(239 - i, 255 - ((i < 200) ? 0 : (i - 200) * 3), 150 + ((i < 100) ? i : 100)); ((quint8*)&m_histogramPalette[i])[0] = c.red(); @@ -97,17 +96,16 @@ GLSpectrum::GLSpectrum(QWidget* parent) : } for(int i = 1; i < 16; i++) { QColor c; - c.setHsv(270, 128, 48 + i * 4); + c.setHsv(255, 128, 48 + i * 4); ((quint8*)&m_histogramPalette[i])[0] = c.red(); ((quint8*)&m_histogramPalette[i])[1] = c.green(); ((quint8*)&m_histogramPalette[i])[2] = c.blue(); ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); } - m_histogramHoldoffBase = 4; // was 2 - m_histogramHoldoffCount = m_histogramHoldoffBase; - m_histogramLateHoldoff = 20; // was 1 - m_histogramStroke = 4; + m_decayDivisor = 1; + m_decayDivisorCount = m_decayDivisor; + m_histogramStroke = 30; m_timeScale.setFont(font()); m_timeScale.setOrientation(Qt::Vertical); @@ -141,10 +139,6 @@ GLSpectrum::~GLSpectrum() delete[] m_histogram; m_histogram = NULL; } - if(m_histogramHoldoff != NULL) { - delete[] m_histogramHoldoff; - m_histogramHoldoff = NULL; - } } void GLSpectrum::setCenterFrequency(qint64 frequency) @@ -173,14 +167,14 @@ void GLSpectrum::setDecay(int decay) m_decay = decay < 0 ? 0 : decay > 20 ? 20 : decay; } -void GLSpectrum::setHistoHoldoffBase(int holdoffBase) +void GLSpectrum::setDecayDivisor(int decayDivisor) { - m_histogramHoldoffBase = holdoffBase < 0 ? 0 : holdoffBase > 240 ? 240 : holdoffBase; + m_decayDivisor = decayDivisor < 1 ? 1 : decayDivisor > 20 ? 20 : decayDivisor; } void GLSpectrum::setHistoStroke(int stroke) { - m_histogramStroke = stroke < 1 ? 1 : stroke > 40 ? 40 : stroke; + m_histogramStroke = stroke < 1 ? 1 : stroke > 60 ? 60 : stroke; } void GLSpectrum::setSampleRate(qint32 sampleRate) @@ -358,7 +352,6 @@ void GLSpectrum::updateWaterfall(const std::vector& spectrum) void GLSpectrum::updateHistogram(const std::vector& spectrum) { quint8* b = m_histogram; - quint8* h = m_histogramHoldoff; int sub = 0; // was 1; int fftMulSize = 100 * m_fftSize; @@ -367,38 +360,22 @@ void GLSpectrum::updateHistogram(const std::vector& spectrum) if (m_displayHistogram || m_displayMaxHold) { - m_histogramHoldoffCount--; + m_decayDivisorCount--; - if (m_histogramHoldoffCount <= 0) + if ((m_decay > 1) || (m_decayDivisorCount <= 0)) { for (int i = 0; i < fftMulSize; i++) { - if (*b > 20) // must be more than max value of m_decay - { - *b = *b - sub; - } - else if (*b > 0) - { - if (*h >= sub) - { - *h = *h - sub; - } - else if (*h > 0) - { - *h = *h - 1; - } - else - { - *b = *b - 1; - *h = m_histogramLateHoldoff; - } + if (*b > m_decay) { + *b = *b - m_decay; + } else { + *b = 0; } b++; - h++; } - m_histogramHoldoffCount = m_histogramHoldoffBase; + m_decayDivisorCount = m_decayDivisor; } } @@ -534,7 +511,6 @@ void GLSpectrum::clearSpectrumHistogram() return; memset(m_histogram, 0x00, 100 * m_fftSize); - memset(m_histogramHoldoff, 0x07, 100 * m_fftSize); m_mutex.unlock(); update(); @@ -1577,10 +1553,6 @@ void GLSpectrum::applyChanges() delete[] m_histogram; m_histogram = NULL; } - if(m_histogramHoldoff != NULL) { - delete[] m_histogramHoldoff; - m_histogramHoldoff = NULL; - } m_histogramBuffer = new QImage(m_fftSize, 100, QImage::Format_RGB32); @@ -1598,8 +1570,6 @@ void GLSpectrum::applyChanges() m_histogram = new quint8[100 * m_fftSize]; memset(m_histogram, 0x00, 100 * m_fftSize); - m_histogramHoldoff = new quint8[100 * m_fftSize]; - memset(m_histogramHoldoff, 0x07, 100 * m_fftSize); m_q3FFT.allocate(2*m_fftSize); } diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index 0ecbfbb85..46155848b 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -69,7 +69,7 @@ public: void setReferenceLevel(Real referenceLevel); void setPowerRange(Real powerRange); void setDecay(int decay); - void setHistoHoldoffBase(int holdoffBase); + void setDecayDivisor(int decayDivisor); void setHistoStroke(int stroke); void setDisplayWaterfall(bool display); void setSsbSpectrum(bool ssbSpectrum); @@ -171,11 +171,9 @@ private: QRgb m_histogramPalette[240]; QImage* m_histogramBuffer; - quint8* m_histogram; - quint8* m_histogramHoldoff; - int m_histogramHoldoffBase; - int m_histogramHoldoffCount; - int m_histogramLateHoldoff; + quint8* m_histogram; //!< Spectrum phosphor matrix of FFT width and PSD height scaled to 100. values [0..239] + int m_decayDivisor; + int m_decayDivisorCount; int m_histogramStroke; QMatrix4x4 m_glHistogramSpectrumMatrix; QMatrix4x4 m_glHistogramBoxMatrix; diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 1c65233d6..8e266039d 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -16,9 +16,9 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) : m_fftWindow(FFTWindow::Hamming), m_refLevel(0), m_powerRange(100), - m_decay(0), - m_histogramHoldoffBase(0), - m_histogramStroke(40), + m_decay(1), + m_decayDivisor(1), + m_histogramStroke(30), m_displayGridIntensity(5), m_displayTraceIntensity(50), m_displayWaterfall(true), @@ -66,9 +66,9 @@ void GLSpectrumGUI::resetToDefaults() m_fftWindow = FFTWindow::Hamming; m_refLevel = 0; m_powerRange = 100; - m_decay = 0; - m_histogramHoldoffBase = 0; - m_histogramStroke = 4; + m_decay = 1; + m_decayDivisor = 1; + m_histogramStroke = 30; m_displayGridIntensity = 5, m_displayWaterfall = true; m_invertedWaterfall = false; @@ -98,7 +98,7 @@ QByteArray GLSpectrumGUI::serialize() const s.writeBool(11, m_displayGrid); s.writeBool(12, m_invert); s.writeS32(13, m_displayGridIntensity); - s.writeS32(14, m_histogramHoldoffBase); + s.writeS32(14, m_decayDivisor); s.writeS32(15, m_histogramStroke); s.writeBool(16, m_displayCurrent); s.writeS32(17, m_displayTraceIntensity); @@ -130,12 +130,12 @@ bool GLSpectrumGUI::deserialize(const QByteArray& data) d.readBool(7, &m_invertedWaterfall, false); d.readBool(8, &m_displayMaxHold, false); d.readBool(9, &m_displayHistogram, false); - d.readS32(10, &m_decay, 0); + d.readS32(10, &m_decay, 1); d.readBool(11, &m_displayGrid, false); d.readBool(12, &m_invert, true); d.readS32(13, &m_displayGridIntensity, 5); - d.readS32(14, &m_histogramHoldoffBase, 0); - d.readS32(15, &m_histogramStroke, 4); + d.readS32(14, &m_decayDivisor, 1); + d.readS32(15, &m_histogramStroke, 30); d.readBool(16, &m_displayCurrent, false); d.readS32(17, &m_displayTraceIntensity, 50); Real waterfallShare; @@ -171,7 +171,7 @@ void GLSpectrumGUI::applySettings() ui->averagingMode->setCurrentIndex((int) m_averagingMode); ui->linscale->setChecked(m_linear); ui->decay->setSliderPosition(m_decay); - ui->holdoff->setSliderPosition(m_histogramHoldoffBase); + ui->decayDivisor->setSliderPosition(m_decayDivisor); ui->stroke->setSliderPosition(m_histogramStroke); ui->waterfall->setChecked(m_displayWaterfall); ui->maxHold->setChecked(m_displayMaxHold); @@ -182,7 +182,7 @@ void GLSpectrumGUI::applySettings() ui->gridIntensity->setSliderPosition(m_displayGridIntensity); ui->decay->setToolTip(QString("Decay: %1").arg(m_decay)); - ui->holdoff->setToolTip(QString("Holdoff: %1").arg(m_histogramHoldoffBase)); + ui->decayDivisor->setToolTip(QString("Decay divisor: %1").arg(m_decayDivisor)); ui->stroke->setToolTip(QString("Stroke: %1").arg(m_histogramStroke)); ui->gridIntensity->setToolTip(QString("Grid intensity: %1").arg(m_displayGridIntensity)); ui->traceIntensity->setToolTip(QString("Trace intensity: %1").arg(m_displayTraceIntensity)); @@ -193,7 +193,7 @@ void GLSpectrumGUI::applySettings() m_glSpectrum->setDisplayCurrent(m_displayCurrent); m_glSpectrum->setDisplayHistogram(m_displayHistogram); m_glSpectrum->setDecay(m_decay); - m_glSpectrum->setHistoHoldoffBase(m_histogramHoldoffBase); + m_glSpectrum->setDecayDivisor(m_decayDivisor); m_glSpectrum->setHistoStroke(m_histogramStroke); m_glSpectrum->setInvertedWaterfall(m_invert); m_glSpectrum->setDisplayGrid(m_displayGrid); @@ -367,10 +367,10 @@ void GLSpectrumGUI::on_decay_valueChanged(int index) } } -void GLSpectrumGUI::on_holdoff_valueChanged(int index) +void GLSpectrumGUI::on_decayDivisor_valueChanged(int index) { - m_histogramHoldoffBase = index; - //ui->holdoff->setToolTip(QString("Holdoff: %1").arg(m_histogramHoldoffBase)); + m_decayDivisor = index; + //ui->decayDivisor->setToolTip(QString("Decay divisor: %1").arg(m_decayDivisor)); if(m_glSpectrum != 0) { applySettings(); } diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index d3f4537d4..8d1840004 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -49,7 +49,7 @@ private: Real m_refLevel; Real m_powerRange; int m_decay; - int m_histogramHoldoffBase; + int m_decayDivisor; int m_histogramStroke; int m_displayGridIntensity; int m_displayTraceIntensity; @@ -81,7 +81,7 @@ private slots: void on_refLevel_currentIndexChanged(int index); void on_levelRange_currentIndexChanged(int index); void on_decay_valueChanged(int index); - void on_holdoff_valueChanged(int index); + void on_decayDivisor_valueChanged(int index); void on_stroke_valueChanged(int index); void on_gridIntensity_valueChanged(int index); void on_traceIntensity_valueChanged(int index); diff --git a/sdrgui/gui/glspectrumgui.ui b/sdrgui/gui/glspectrumgui.ui index 7dbcb01aa..6af63db77 100644 --- a/sdrgui/gui/glspectrumgui.ui +++ b/sdrgui/gui/glspectrumgui.ui @@ -60,7 +60,7 @@
    - + 24 @@ -68,10 +68,10 @@ - Holdoff: + Decay divisor: - 0 + 1 20 @@ -96,7 +96,7 @@ 1 - 40 + 60 1 diff --git a/sdrgui/readme.md b/sdrgui/readme.md index 34b5cfbf4..ba4e719a4 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -280,11 +280,11 @@ Averaging reduces the noise variance and can be used to better detect weak conti

    4.7. Phosphor display stroke decay

    -This controls the decay rate of the stroke when phosphor display is engaged (4.C). A value of zero means no decay and thus phosphor history and max hold (red line) will be kept until the clear button (4.B) is pressed. +This controls the decay rate of the stroke when phosphor display is engaged (4.C). The histogram pixel value is diminished by this value each time a new FFT is produced. A value of zero means no decay and thus phosphor history and max hold (red line) will be kept until the clear button (4.B) is pressed. -

    4.8. Phosphor display holdoff

    +

    4.8. Phosphor display stroke decay divisor

    -This controls the holdoff value when phosphor display is engaged (4.C). The holdoff value will drive how much hits are needed before an even appears in the history. Practically you increase this value to trim larger transient signals. A value of 0 means no holdoff and all signals are contributing. +When phosphor display is engaged (4.C) and stroke decay is 1 (4.7) this divides the unit decay by this value by diminishing histogram pixel value by one each time a decay divisor of FFTs have been produced. So actual decay rate is 1 over this value.

    4.9. Phosphor display stroke strength

    From 8836b98542a75a12d47a49910751b86789ff99b6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 19 Oct 2018 08:19:12 +0200 Subject: [PATCH 879/956] Spectrum: do not process decay at all if decay = 0. Some code cleanup --- sdrgui/gui/glspectrum.cpp | 54 +++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/sdrgui/gui/glspectrum.cpp b/sdrgui/gui/glspectrum.cpp index 1d737b6a2..1b7ee8940 100644 --- a/sdrgui/gui/glspectrum.cpp +++ b/sdrgui/gui/glspectrum.cpp @@ -352,13 +352,9 @@ void GLSpectrum::updateWaterfall(const std::vector& spectrum) void GLSpectrum::updateHistogram(const std::vector& spectrum) { quint8* b = m_histogram; - int sub = 0; // was 1; int fftMulSize = 100 * m_fftSize; - //if(m_decay > 0) - sub += m_decay; // allow zero decay so history (including max hold) is kept forever - - if (m_displayHistogram || m_displayMaxHold) + if ((m_displayHistogram || m_displayMaxHold) && (m_decay != 0)) { m_decayDivisorCount--; @@ -795,25 +791,31 @@ void GLSpectrum::paintGL() { int j; quint8* bs = m_histogram + i * 100; - for(j = 99; j > 1; j--) { - if(bs[j] > 0) - break; + + for(j = 99; j > 1; j--) + { + if(bs[j] > 0) { + break; + } } - // TODO: ((bs[j] * (float)j) + (bs[j + 1] * (float)(j + 1))) / (bs[j] + bs[j + 1]) + j = j - 99; m_maxHold[i] = (j * m_powerRange) / 99.0 + m_referenceLevel; } { - //GLfloat q3[2*m_fftSize]; GLfloat *q3 = m_q3FFT.m_array; Real bottom = -m_powerRange; - for(int i = 0; i < m_fftSize; i++) { + for(int i = 0; i < m_fftSize; i++) + { Real v = m_maxHold[i] - m_referenceLevel; - if(v > 0) - v = 0; - else if(v < bottom) - v = bottom; + + if(v > 0) { + v = 0; + } else if(v < bottom) { + v = bottom; + } + q3[2*i] = (Real) i; q3[2*i+1] = v; } @@ -828,15 +830,18 @@ void GLSpectrum::paintGL() { { Real bottom = -m_powerRange; - //GLfloat q3[2*m_fftSize]; GLfloat *q3 = m_q3FFT.m_array; - for(int i = 0; i < m_fftSize; i++) { + for(int i = 0; i < m_fftSize; i++) + { Real v = (*m_currentSpectrum)[i] - m_referenceLevel; - if(v > 0) - v = 0; - else if(v < bottom) - v = bottom; + + if(v > 0) { + v = 0; + } else if(v < bottom) { + v = bottom; + } + q3[2*i] = (Real) i; q3[2*i+1] = v; } @@ -854,13 +859,13 @@ void GLSpectrum::paintGL() tickList = &m_timeScale.getTickList(); { - //GLfloat q3[4*tickList->count()]; GLfloat *q3 = m_q3TickTime.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; + if (tick->major) { if(tick->textSize > 0) @@ -882,13 +887,13 @@ void GLSpectrum::paintGL() tickList = &m_frequencyScale.getTickList(); { - //GLfloat q3[4*tickList->count()]; GLfloat *q3 = m_q3TickFrequency.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; + if (tick->major) { if (tick->textSize > 0) @@ -916,13 +921,13 @@ void GLSpectrum::paintGL() tickList = &m_powerScale.getTickList(); { - //GLfloat q3[4*tickList->count()]; GLfloat *q3 = m_q3TickPower.m_array; int effectiveTicks = 0; for(int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; + if(tick->major) { if(tick->textSize > 0) @@ -944,7 +949,6 @@ void GLSpectrum::paintGL() tickList = &m_frequencyScale.getTickList(); { - //GLfloat q3[4*tickList->count()]; GLfloat *q3 = m_q3TickFrequency.m_array; int effectiveTicks = 0; From fac0e652c49aa094adaa4d50657dc822ffe69c1d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 20 Oct 2018 10:41:49 +0200 Subject: [PATCH 880/956] Scope: updated documentation and fixed live rate setting when a decimation takes place --- doc/img/ChAnalyzerNG_plugin_scope2.png | Bin 13888 -> 16191 bytes doc/img/ChAnalyzerNG_plugin_scope2.xcf | Bin 71337 -> 82591 bytes .../channelrx/chanalyzer/chanalyzergui.cpp | 4 +++- plugins/channelrx/chanalyzer/readme.md | 14 ++++++++++++-- sdrgui/dsp/scopevis.cpp | 12 ++++++++++-- sdrgui/dsp/scopevis.h | 2 ++ sdrgui/readme.md | 2 +- 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/doc/img/ChAnalyzerNG_plugin_scope2.png b/doc/img/ChAnalyzerNG_plugin_scope2.png index dcf1d28262e54b503e9bcb926f71b03c061ad90c..cefd35fe0aaedcd59c59bc858f3169a5a0e32471 100644 GIT binary patch literal 16191 zcmd6OgHEE$6ed=zURB& zUvTg9I~>5xe)f9inlZ+hbA`TBkVHozLV<&WLzk8kSAv5>LIXcXJ$VfN@1!N+0=_*q zl9dz(-@$by*6a(uJ++h4bcBOL#fE*s!+l970RM>WBrPw2yo!R2OU+*`Uj%z6iG_-W zlbEfwwW*C0_!SOL%)!*q$<&10)xzlmxumrGI}=t@WH>l-IBD@WDsHp;3+`Sxs%;Ms z>aGP7n*9h*Ww8w5Vp+sYrd8f)>b!gAYAP=I92eR7bd!T|hde5|=SUA3iVM**2-v0P z=cfv5^b5<*Ay4yTrl=O{jC&-SG&(*9jm{4baEenHpW%mYuKkQwP*=~|Nf$0C9xYJF zFh&pz`S*e|Gxh)XmrHsBddmM^kCD(M#Q$@3XZwBq_pbpZVE8YbnYrP=9k>oPUSWD%rmG{F+6adh6du zD=IrxJ^XnJUYt^#f|66Bi~fCtShMb$uPfu+=t}otDj#DDR>m_wfFDDo!oT_%GVvCD zC=Kd6WAfmhb`k#0A!aHsF0K_hNo+XTyPU^kM2<#Q=!N@K>Uw8+(QMZ9(J5)U7w+>| zJPG(NH3y}i8tBpKWiAf&ILOulp_`I%DR_AtYLYv7l$7HfWwTEvHj!SoZpY)Y;XJ*a z_ffU8ebwso9U-VpwLp(;*#nw$^NcO684498|J^Am?34`B%kRo~UD;`)EeAhHY*H{b zZ;Adaerv&reOuu7RVqaJC0Or|_qt>D$*7&I??^*A3)_-AaHC%*pHS2zu9F3eX&d&u zm_DjImSh~9$JiD2J0oi!-dC--*!{U^@bwO-#i!%~qkge##s9+aa3sb9$?8Grih(jx z2EHpuEzM?2-av{`?mHeU1a5R2u1}%8H-qg&e!B9N+gzvsPx@$b-*}rZ&QFFFw|UYq z$Mp1Rwc*U@6Z&cO{A%EZN|+*6s-z*Dg+rtPORZ}5nxgp7oCmJMi{Rzmra?NswHbZn z`bCKb&#dH;Pu5H=zP8#c-rl=b1RlY_i^sv{TYae84qANVa|;QpXm&alE^o!LaVJXh z!cO0hxb@X#VySVddN$YdHu2rv0~qv ztKo504)0f*@C(MzGD8+Gv<+6Bwgewr;lFv9YZ+q3OF4RdSno*Ln!@tn81WLi*w?3S zemGDEIUMzO-AOH<)DS7E_wDZ(-ET3VC!Rx}v(7<~ zs=4W=sP@)h&FtMzNt=3M&)y3=$@*uFuM|D_``W5y)=F)cPh}ntJ~#id`!U8%6TGRUy$Aa2=_DbJ|U+A~3llDfe z%DQdD_VJL{n)t&ll9dhuqOG4ud12#y5T@ar!$w{RC;u564rvqmACXO=snlDt%G0^G z%zkRnH{(Qk-=<$!E$n;S;1{$!;k)bWs3kM}A^&WSwDkFp6C<}%Y}m(g7GgSWKk8F# zztD#H!Rqq%pvzcV%rE3ltHn)EJT))XBwrAxW8pkac6qMs=^aj&E5cY!>#mxk_yt&g17i$(J-9wY# z928yK7OZ7|;Kv9%ztP)fpc8qptsj)3(!LIJHA-`yqZmf$LT-N$-1dfslO>2ynEt{E z2@9rbUY^`-rOSmw=I+q%`@10h_IGReVVvKrnGCMUp@*cp$f9#N^rOKG9}Q-hr)%E2 zQ&Un>h{20l;FOT@NlmfaQ)iM%n-~$;1P6bk!ici1thv#rV-VU_qUEZ#Pxc@)9~um@ zu%-7Em4TGBjOmXFVKd{z;*~*J1`%Uv?LVLnt0LimvQ*$_DE4ucG=Pg8-7UA9{$D8+gD%nlog>T2V881QLagnjs$ymKRxG$ zY@8Wz0X`_ITcldP-j;tL5f>-&1LdLY7Y}g@rUeqEQOlUR2t0m-NR8l`!C>*hn%7%5+cdy**W*^8SY}Jq8?G67na4_Ak0%?@mz+{{a{ZrP~XZ@ zK)GMf{Z>Up8fx*LI$xqlj*|-DgLFZ(SZ{-lg_z_G-K081Jh=eA)F0j<$5Oe37Nskq z;@#0^!s#&~0dT(9IhMY3BQfAM*>PNIU28c^uT)6CP@E|}KQI5hE^NU6(Ragh{|F=w z*hM6JZ7HQc$vJl*B-&a?LkpC%oBy;iaB5h1X#%NRWT8zeut6@d-3Q zOnW&_I%XPI2Gwwrr*hpL>h=$~96yco=xQf6Tt@$4{VbgqE!w&K;X7qrE`VHIMh1tp z6&iKV>-i|91x4KAGuExD;%n+OL=vPb8+8w7^jk3h!aWP0VZ4;OSC_kCzx=`TC%iOh zv^2h(faleEA+dTDyKYtF8}st$Rtj(d32`C=$V*Cj^U1dws+^qy%Pk?CWZNa=-m34S z5=*#$Ln3L)Qu+{61q{}(rfVPNw;=xxxvtR8SbQVQno+JeejF&*&c?tHTG|)Qwodl> ztmH8{A76X~)#0`x!K=YLT9|Jl!A@y@bba+S8;?|(hx$ysFd~f z>^#jFju-@kLxX*Fv5tVM)OlC=Bo5JBhA~JIO}xDqf~MGkvRMdc!S^QC)PxjD+K3So zgE0hdv+iFE8U8}3QVLOD`_sS4C0dTn#mC1>7wx>b$q8NDJU6SKwQ6KhyGAx%nBa(y zA7FHi%HnfFKw>})9Vqq9Z6-G*XCGL+{xw@l49Q%7IPMjTdclW<^$-tBT^PZL(S2g} z_WK?u5^vucaio6A%$&63O4?Xohl@!`DI_aK8i!6@q9{Fn7sK(Ikiuv8TL`C6^Fq{u zjr!XV&aUs<0(v&XA%^z_&ghq>)l93D9LUi&l~AJirr7CH`Rkw5TN{j~cefr5R~(6n z6fj&oyy5bkkWe~H*t-}F?s^UN$-#URcJf?cj}?6TK6q&+UvE2uu=K~3srAnZ&sqTJQBmIZgx?6Aa*C1!5IKFL23kZ0` z+H%;bRkwL^eB3`eN=rqBh)F7!l8{Ov3f~2{L5Ioa@`K{&A(y%5OEPTnl^V=-2mV;Z z#IoR*_L?b;KS%GNr`$HBZU#bFQ{lrM_=RxS_L|dlI^*T;+#FyZD=GVKZcOuZOUPt% zpZ3h{w~r<6d|2>Qlqz)O&uCmYu!Q1;L_~C3abx4mf*rg^fKhGGaPYZJO@7=)QOEw;K3W zOmY$*5+ge`KWWY*R6VUqVzA0iyshyvHd0(Qh6h=+oR0jdDNb z$x&D4jkYDuiKG4`ni``>d6BpfDbzJ5{?M?nZ~71)5aE~my30py^b8Cun_0fSHVsUb zRA7;m6xp@43Ekb|tE;PaO**A|tzX83Z%RA)V`F1acc-FTS_IGcX1XNsb-SmljGI=1 zh&7rVXhw4sScS}A=JW;mxOp1CMN9JuMc;mSFKO7ZcEq`RO3k@d&lKD1cy6sm>2KG( z(5S0U9t(hfx)M;eX8Hl{T+Gx47>@jPE}PvE@#ynsEx&EPUTyiJ|4Nyten& z#2LPe*tRA1+c(Ojm->2Zqq+H~*JrzZ&QrR<116MobirI7e?LmlD_r*!Lx%s-=Vp>R z`52bew0LO(n!@>%vf;#Zfn}KSP=QZ0%#pmy>0CRC$;z@tQ=~N4C{qKaCgHhxB4HxS z`vn|}dpTO{_wU~)vd;5nDEM4$;8)kw9JvW{DMVpeV((A>lBWGdZ%~Ncz=-Hj{-JEs9ic7O*qa8&WkF6PaIq|pmpH%b9 z^>{^%YLlnNy%8%j=2@o4>jQ&2v09A}_cshIET*@YN5I7n8z6U)Sw7dHz>Gw@g7=qe zrP>Wq2?@oD^oT{#k|=V|rbtjQ7}j<>R}h%tG1XVK`;4jz@in+MSw=J~qeA^jU#ixN zJ1gg!^4hOHLdGO5J@!r>nqysP_2d~C7;xH~exIs#&s+c1-3xOf``TL%z6)>AsOlLZ zVd$hKh%^um^z`&>j5gV8tqCIbALr^>9RCcnFNTMNw67i6olU9}4;{2#=&ivKOJR1w zJi`0tJT@y!aV%2^p4aV=JT)~{2e{pE8c)UefT8?SgJPC&Kw#jc1^1sMMsDsTVejk8 zsR_;v>I5D4jYO~W8T09V7dYXYy;pCeNp*|t6H-#7K-^=B+_J$9CBNJZrOV6~3&waM z;-e!#5et_A(x2C12Xb#uw0s)3{iLcoA$-?LS1{sX7p92a5KF_QJk^iu6ddy)4uS5h zf`|%KM?$;o->vO(wDG;EC8CZ{68s3`Z*%(?mu^MXG{tF@T)Q)y##x*H(kZTYA{W(zYbI(Rc!eVHkc};@!$ls2gA_U8WmC{6M$sl6N2E{;Y!k?xClr2Od3z#L^4VPe6u5UQI#k3rKe- zM}1_<@7}fFKq0mXYq`19^)?HZrOAnjJuwtwjNWa^1q$nb{t&@C{4F2c8T`&l?eB8& z#wRA4_Ns?8T0OX|XKPnJp|Y}hB=`08IWBo$u$vCzK+Y=b&wg5j8C<3-zYt0+iKswa zA_%HO-+3YE_O7-4_6a7Y?KxsQ@>hY*Ee!TL9gz4{@BMa502L+NNCy_jg7psbszf> zF$G_1q+Ar7oNdV}DA4lpr5bhx9j!%+R2%i6fMgGf@uc1xrX{K$8xFDY@y0FcN=ly= zY$_`E_Quqb^{$*p^%{!D9v7Ft5@_gkFyc;oiiz2`cJvGvSGB-leB5f`d*W_+bJXpv z*Qn5ij-L0KY7~h;Z?wq$*2Qb}G(_`Cz6^zV8`nBxhqnMFai>Lkyc~}M{u>=k=9AK} z=n)QTo6q$&osm(?XMK^gU&^r8K7E>vMp~;7PI52xOKjxb@YKDKgc$>4r@Ob;@&QK_ zz1yHvd(x9pdpyofkW?X^_x%ti!xFXAAf#`t!?I3|n2j@4PPP6@TU4;;q7?@Eq@~$s z2lRceXg~;2OevJJkoaqd02_^uk2hZZNwepD!J9FPLTB8D&DJnC0ycYVzUfss9?R+v z3Mj9FD(>;?-nb%YE}eM=aX-KRUW?Y6LUaAbqZa>ViEuo4HlJIt4$Qo~ygEiY7I`0^ zwxf$oAXgityfnk$maYuK;U)CP50;FC3&@n#!r$A0EW zexBux{vWJhZQw3QDkCFvd9|5UHMeicD_zszc4P!f5~z}fz~4b|n~h~!w}@EJa-JV7 zpuX19A_5s>12{1qCFLhj#TUI!bq$z5^68|&wZCE4H4D)TpB2T3D)!HZ!|S$G~d`o^04|dND)ezVcQ7Aa2NEg6&-649h3P+d#}rtU>)a4)xE`5fe#H-%?maxmXVbipMXPmU&TN>?jxsQ^CjHmjH#ZR z%l!|HA1L>xr~KTfv>4Y5vV3;#?3wXa*VZE059@80M&lKQ32XOMd7f7qbRguA zx1G;^EY)ekYF-#6ah-nywuu1%*8PU1KcEbhvX6kUA$41Z2cB8n+SA~rGH=r`kt2!u z-DO|K#f2LqIOjKf7tf+wPH%>k^*E|Y0nV0(c||k0H87-dz8rh`aU=ozCuB0O7!W&z z5MQ?2HL?c-P@;QXyw&Le6a$}Vyx8K7&T*n8f92@H2fXwJzf;Kkybb`!Y^ieV>+3Oz zi4samSgNaMFqT@<+L@!13|%j<*pGHg*>P*rVflvR6a;_gJSb-$8nY1v)&m z>ulX$x%8hT`I|xU#7~bar($0ie+YyoxD{fyf30J+g1@y{&6eU58^D^fF;%E*;$P_IT(e zSG`3stnwH}+YSjpTa1}^mmnf1e@50~B}olOzHq7fod4Fg-MHSZKwgHWrb%`EG8cFG z{q^y2C9s#znNO$S0WM!Vazn78n_`JJ?9A)}lI` zWZ&?y?d#dLA7VlR8ES~i1#7?feXYRnYTC>k0?7RRS9LLWvl_naD%IlSaoLwgASIpv zSBi^`?En}zq;G8nIMw>MaMslUx-9Bfui_m?`GSF8fFQ*HWwk_Z%&6)|g}%tJ*(;D< z-#&WS3u%PTsJDB+>0+vA8E+iib>e5bs{kRywMe9-n#aV*NI8t9w$l8Yr}gz?@^gH) zQ#V0?m1C2VdKE?P9<}$!Qbo=!e)6u%@yZ+nJ9Bq)0bp+`yYXXygiI%kRnfnn2&WBL z6{{$Og@;Sa$b5q#BmtLUg0!!o{Nv!`t7|KaE)oi#48QU^9-w2^tPTLc^l-0cjKkew z!F-)a$b%Dj@EX_&bAWK+<@1$^QSn(#RyqS!+eCctz5a}4>04awOqOW2dej|VxBDZ` zdtWR}@{}`B4C@ZZewsx(@`^{j^a*}FYQukU(i?>}|n7ZzEVi9am|dz~H;fd(u& zrQ?Y1xH-CUe*-n2D%VB-z5oOJWm$~cSd8V3t0>>TeRF%byC^~T3$LwRgH#~v85vO` zK1M6Fx6+R8Gaio70asCm5Q4 zIG0uG50-)UT)zcMd0v{92U79mmJuf(_r&38m22>tSTZ9Vc;gJ+Z>m4>^-bN5~ z5ZO;$M+>t;mJHgJiI>*>5U6E zuxG!&8uJBeQ{#4i8a)e3Syg%hCIKxiZQPeHgCLO5+K)Ge+*d>CwtjpS%dkam0jWGy zz?HRO$wvU}4=mgH@r01z;z}4A8ivpK1~kcPaP=U??zdxK_5`IP&~@EgR-C~d0yb0T zv8-E^3OVW<#UmjZf$0th-*+RPO1kXN_B`A}9~{PnE)GHJsO!I`$3mI6;gL;d&9hx< zgSjTiy_tuHcFcIPb=W4MQe7Z{!+6WyLUS@;uJ(6lQ^d}y)Hpb~Xx-i&CV%##&9}ph z1187beSm`_O@_?^XwHgV5F=ji{kF0z=k4{sTfxakLr5Zo0;Z^doB0iV4xkU8!Dl?i zc+p>PJs08pyIgk&bkW`$Vzr>MHu~8@?(muyFx-NFz+eF2YNyRD#eRn3;*Y?NmOm3a zoPsZ5JxZXp@YKrw$Z@-^2i=WU=Q%uA;cEHrwA~4)kcAF~2cTqp?#I+{kedZ4ugJa< zLMxZo+0JAH5m#6sDn5t#=nKfguZi*TtzSjhY24Pu+$<%NGN3MD1b1x-@4mD%snW3H zeerhXe5A#)P7Ca@kBB)(njL>eZb8A+^IoZq%r0#ws-)JqI3RNS*C5QURMUM_(1qIdzBgtZ;((E7H`nHewKgFxm?g2|8K z`E32ip;#(O#s#7l=Uuf-&jUW7A1!=${%C*po;ty5scnJRXUSC$X=VPR)y@8##2w?hO41b19kN{U=SV9qTgKE4YC5CbFQ zadXReHyJP~!q_e2WvvG!zuHLeeU_!Jsq*sjjQJEB2+^X?(eSQNu;DoBVTU?;vn~>v zqIQj$d?5K0_>89Oof3cv#3dxEXsA#e5EY>?9JLz$^4xQ>2%x`~%hhlgcooH2Ad4SR z`AVKeg3gmF2aq}n3Q9<1WL{}$1Q55@xFv(T-UMTi^bZam=ld5ZT{{zEj#zQu>}S5% zJ1s^@zbq*Q$d=6K@~@BtI37C?oIswmDb0$D>jarW&cuY8H91`WZbuzZ6%XJ>FCn)^ zk05RL*B^7#x7;@~`hY4G$vOceNnk701NR}PU(Qn-g>Sj473=chiDi) z_dI9{E~K{=F#<5L8QP|8+}`i|=IfxtszX!%*jPm^)#*OxT~a)pS$A=X{aWwQc0txE z5N-fPRunqJ0nB+7aG)2$UMYQ~o~07|0&%Ou@63WPPb@XJhWrrg4h%J>_-V@|$%Wp@ z|I!K8HuA=KO0yANr|)}L$6lWu!r2x{%;Ru*Xb8k-(NVt57jQ|;n%^Q~V`4sNOUevm z;?AbCs_)xC>e5MzhfO%1$7>wfBquVdAKxrJWWqFn2#~CiQ1J-xyZwl470>f_D};2E z$;a>&kLsF$%%@h8SXs#i99=?AE`q(@@b$jQm%eV+46J3I)|HHx*~35W=H9o1&P_)r zV0sRl8Lk}mtK!EeCxqNq1W8OEdDvxC-}sg5Hp7yH*>K8xW2h)x!GyGp4J(O&OD?#S z0zXXTqeELxFm`{JQ86-xNs_t;0Bj9tS>&Mo)(;AZ`V&AZVH)uEL@`en7xlu`{UB_^{OGol#yG6c`B8hgO{U3168-R9OMyG@K-IxHWp)hdRZ%c8F+VU*W78?dBqWH7ii!s61zA{FfZ~83+~wc3^5Jjn z=jP{En~#ygMUjb=Yj#L{iJ9B~1EPNQYcT0IP-$R($)a6PZ%j=+{hhDM*;T2!@6)8^ z*<#Q~bkr?`SANKt3c)-!V0}+MYFAk+17-#m1}kAw($aLG$-Hr*`#w)v=4EGR@631!{wPo+1B{4|kFRbt2grMV z=FrsjPcfOc5$%?)QmUsgbPEfjo^bpckWNr^!$!LX9?^&UyGZLtmY}!fzaLT}T>}zO zDdV8@l|cfeiqMjg4lHGFz9}9S(ksDa4lt1r5%T*tO|nksSFzy6i$%{!Qy@Z^{w~#I z)~NIYROK-mK|25-bJWhh#j zphRJ9?{xxY-tL@B%%SI01gNY4@F9UJ<*PF50zg1t6iLYsTCEy0bl?Go_qSI72RZ;m zaRQi^4&XSHNFpguo9{Iq7 zG$;eJAyTcq@+WxGP@s;$)HV?4trz`tzeE;*WSIqeaR8EjhI*WX>iHepwVn%hm@6sIUXLL1}Iiycx*Q7gbRWD<#fwcOoNEzr-%Xa(` ziwbQa-rnOUnXhMe636ttjslmke*u9w7+=rLAGlW(sgUJf4B*=-Jhr4`A`gNX!Jp4| zr-+T|*Ecs`0h*KPeXa@qXz*yQj|s^Ywys+YLPA1ZGe1ja(uM+1DVUhrHfzXyh6XfS z#Q;?r0C}$I=AfAmi0j~nUKwqhpkJp^Wh4=3D&Vr$zGBDr;RiWL$vxl?(?(J(0ITA( z!S)4k7v^?XCs_|K5^90}bElyh^C;lUP2%4 zZF8b%>F64O)BNui-S)x%YSGbCjP$1SIRZ!oJQXIH0CfOlW4L8d#chs^35tDWWYtwv zy0H}n-vF8fh<65uxTu1K1p{cd1GN}X2{8~>z$J6XbLl{xUj+cv5NIHNu9+dgs)#C0fT?zPA#_R9;xQH^Q@2dN{l9qK*<`g25U@aWVN(006kM z30GmCTizw#2O)eNJ9r>!uzeB%i49g?;1M5Jx}TWAqzoYF(zCMCQc@x$5A^^EB@#hT zHJf5uy^rYHG7Ul+1kge9lC!svDO?h(u9l(Pz_B_Hx7BnzdTb1+sTn1gB@=dnEfVG;y zsJ$_aCqZtHQTJ!-a)7a{W~zG{l~+OkZMC?hyb}~Zj#RhPpOTRth1U#UMa7(@*m8$? zCz;zM|J2y4)(gnfVqaZd0m$0g>G|{L&j?bXFF+LsSy0)S_$=$;mM-QKh{e;bpS{P& z8HF&^0?E1q{PYTZA?lGz!IZp!P;5FFUVy;}&JWK3s2~OD))^#$T20K3B2kTVlD&~f z;e;NpfppMMqa-V7=;+Pfy7eFb08;Pn?hZo$2?;oH@$n#r0*-uu9M`bmLJZ894@3+Q zmuoJ~p`oEGiRyYqcA|fvjMmw&YSrqfo^rOLzHEsBNDK{hNkHH5F6sz>>!NVXwR{kr zXTBX&ZkVuzhsWW1FmL9X=HIvWwHTF%Hoj=qVb#{F7leb~kaXd95QPl%^e`z8q{rh6 z$V02BpV4Y}=)BunWUcjF5)k`=wcE07L<9xX!EOQKrnW!43gR`q$H4>7{`sV&?-@{{ zK}*<3hp-|F%IGF79g1*7S!XR65XcT>Zt$FBUWf1i0nmWvuE0U#XTW=w?RYC^tZ)zd z-+_GI55OK7nf?%_DbpG?dEO0k!h!V{m*lc$Y;7L(I%{kFW_B8yz(xn-ml?prlJ$L3 z-{-u&BDpW7=~gx}GD;EhNwdy8+9FldF9f$BLQ;YEL`fqe-sfB@oQ8;tIl9ja*2h; zpHvn&6IW~u=k%2}Xo2?M>4R(o-}0s%=w3i-+q44^8iQ1@AJk(FP^lj7FCeXA*M&YZ zsO4$Ejq5}zYBH4`%f6Pm2zp;xT@>}4M)ffo$gv=S`2Qpv0HU{=npq39*?Nj+ARp2^ zt0f~y!_PmvWip{NH{k1qJATm>nUg(L(QX!qD7jKXwz2`~@}~k`3z}4LH8T;QuLo#S z3y@Epw#Ng3m=$-vKZi|93d^4W$AA!mWfP{>4P?r)Vsj;9B($NIC;KJ{(u!Xs`JS3? zJUkSeL5|%7y+C1=%M_e}qWz*A_u4VGro)m^Q0TR+V?4=w1LQmA+})zN{a&LgFn`eq zZM%_@l{H>&_h;5=i~%Gv4q);^pD@trMm4I^;Jui4)U3B61gbwU85oxM*aCIpj|?pW zO=_kH=#w%{5>GL00Cdl!qII}9I;~>Uxbm3a^+3+Z=v$Q)5NwWv>1;~5}d-xat0IdQ~<6)2mBE;^6*fmuv z_Mx9N2qwQ@93wi#SU0udoG@dHOT;T*37Pi)V5!1 z%=vY9d`6!9EKMZt=H}LaCgTPg`AjYMptteiRtOA|Oe%s2mLj@%{ytCVP!>V?$m6?m??Gkbt` zi-a~NOi^khsif=yYqR|sP8B?jakb34I@whJE|FJ!TFl>%NWmB5=Ewp%mn;(2h*&^U zDzkKPdp@c7JoeA9__=T2qgu0$|2U@nRUYKHlcY>bn2OvHt z?gAq@I67)JIpi&!Jw9iK<4ALk@}!?q$iH@}zIb>#eCWR@86HJfC>qqxnn&7^)%)cX z))zAZ^It&RuL1fdzr2T13`b=u)RIW=&0sq%16b2Y#*9_3S&d=C+{YK2+#n`9J4JIY z>O(-*2nj$@Ol?@$>@WAmwAN2+sjQ3vZVASa)c3CeD7J08dR#EE-5?~AZ4QrU`P>8* zH~-DoBVRCkJnKpuiNnUs!mD+|yhHY`O>@$H&{5=(}~YgR|=6V;U5QPOPmP3N{XS;-72IT~nGCY=B4to3;V!WSRXPIx%-laap!9mnX}%IX5rZScl9A-;dSs#OmyGQJO=;&TgRF zY=I^(07P}yrT^rZh&|^&#esjN{)WtS!gQ|fU#!*I?N+k#hT3`*C#Z308?GmM)(jKT z)Z=n>0q}(qq9tviAt9V-j=5<80{bazy6emzKA*TPn1h2DhvaZHG&JyKKr->n5rW>7 z24Dc&v&YW_Ol(sN3AN5@i`^cfi|hKxfmuQ&=xK|3=Gb0(J)?$3o;;+xdOLaA>=xaz z()K!H-MH`6IFys(;=ay`Wwr|`V~qJ@8RENePGmqbKkK_c{I(;Zl;7N25dZ@#S}Ph1 z<5(KA-=$jSFz_(>PK@m`3U8(>@WPP*pl|cOL9jjASXbF%UXR4&J?#p3f`o>M1IA9* zoxh$u)LxCewYrjw&=vy|1QWWvDch6v$=^6%0xQMTg@Vb{K{P;X$QcJ~K-n1+Z}z(Y!;(GSJNDY5Q$B51x;#%@ zOBAHCKkCB+14ZTtOqJHU&-q2X(&Et+SUR|>ef(Q%ZD>jtNc~%RkDY|^f8R7~?)}Nm zqoJhKW14ENnp;bJPAUIk--YM=+{H9N>XmWB9B5eZd6(A0IhPbHS=U#Nt*wX$bM6&2 zAm4c=IZ06&J6;L>kS1SQ{I2c`o#pen#i$;mgq+n8+SzIOM<1maz+sYM`aF8^xs$RYW95>k1W`m> zGM@PkgI1EfW+I{uHfOS%DI|kd(oVZ7K{;Iv-<`utE?ga*cmvPeUKHi{T$1tAa?=LA&uI~wIGMBU>j-6Rn5Z(A^oZC|wrFadgAe1=V1J9X_fc~* z-6tUBH@ArvSg9p*OU(&Yiij`w%0l||_sKHinyfW|k<%0Q1z^h(7Z=@UmNP~O<$N_$ zK*sVl3i+a0Mp=pwN-|b&@DX=^q0B6MCyEz&W{1ggvV4<7w$kARHEmVEv_8{YO0J2k zutyRzZES<*MZ6#;{$90C6xb_#01oBT!TsJ41YMQ_;lf`GpU zmIg$xD&Byj44D^8=^_4mz<=++8P|P6wJk3iZ>>%J42<0~A9L!=V#^Qe|8pyLF#8`1 zjt54>n8|}Q=)+h?Tkt-(zC|D4+FKKz+TIQYooIXKb?Z(Dhov)v&Jz25%! zRHjsT?tjm8*2-j}qbJu}#2l-OYq5g)cgtOHJg=W+5yJKMpOOCXdJn47S%Q|+w`c(? zh=~=HhMQK-FH9CspZHX*(4u}fejOcJf`^kS=*>iJVcAh#;T zpD(O5JUk_PAmnIZ5P`izT+4sP_wX?)L=PF#hD*tMEL@?38DIWc);9+nz54-MQ=Ob0 zl#(|NBdaxc@9$zQXo51J-fg??({1=NB#WB@1sQrik>J>ZXtIqoUeTy(wAcj&n|HR2 z1RiW(EF^{tny1`GwGKSJJ1P<-DX}_lUYq($aoKo0FXc|DLdSC-74#bQVfp*S6oy?< z69u;ut;YDXrHh+^W1h*b2f6=Ui*xDoK}6V1W7R%f3)+2O4nu9Jp-L`263${gZ6nLe z{}d9ywh+gxlo3k%Fy!5eQsSZi&tulwa<`j8f;Z{9VpQySSIQJuGR9VthQ4M?fnzsc zW&b^hE7ErNlr>Gseuk{qGs~6a-F=AE);P<+-?dRv!dC`$AU3!?+^-^U1cwB`oMOl5 zn{)#e7~2d9320QjN4fL#Kn!ec?u>gl72d>cCw@@+-?#A5aSXk9ua}B=kp8$&f!#hgP*aFbj9imT%onx1lP!KN{ IHTd*@0PsYmWB>pF literal 13888 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 diff --git a/doc/img/ChAnalyzerNG_plugin_scope2.xcf b/doc/img/ChAnalyzerNG_plugin_scope2.xcf index 8b46b11406144c37cb919f2434973d9453aab707..f3354046d7a22b389777e475028d532539c9a197 100644 GIT binary patch delta 14031 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 delta 5885 zcmb{0d03Ry9>DS63xj|ls|bn;Ziu)bS4~M}Nl{6Gh}=>@QqxjEQ*q}-xmqfM`etry zMy9yiT%WXDF!Q;ZrkR;mXqo!B91;JzBEefkTFns&?;+B;uSiTIk=`jHNsmQdIU$n1 zQDo$Gkt{!vw^BuBjTM=*U8I1=mktzJGfJear$}Y2DD@bpDZcy7w<68C<$0pODL5h` znD-WO;GX6^aalUt?6WKt*-D!l>S?&o+oS@^0oq}t zhyCRXu?7=sFtG-vP}|pDZrN51op#tVUrVQsJkB$j@|d1=&er>nPUp2X&%3r`jdXrC zcT42oyFV;X{@xS*9B;6$=h@V)>wD_oRjY2bKy7I$KK-wAnx*gL&TS#6PUrdTr{~cJ zFM}BGH|^tGh;=E&R_w!ZoX0KHh`4&7DLNnuiAX~hb?%x=rx2ysDpGf`h}(F~LIH}g z8GCRH)wqEtBJS>JjCSaO0T_<)n1uoqi+FSw@%$cacF#v5UUkt3VG=D~tj_C2yb4z4 zH3JK=8XNHmzQFgmjz?smE*c?B^X~Fct8(q=<9Wb>$SqSgB@J2 z8kKfq9YcIMm(9)QMD!hIKju5mSQP$}LG|Bj8Mm!Z|I3{76EPbLupDdc;{tAq1UwaK zSPxCm9zBtORAgc{7NZ1PuoqwAoJe3G+KMWH)KOr6Fu}lan2AMLgH8ApNAUxG!DEre zZU{tMbVq*-6KQ%97jQ=;h!2qpIYqf=1VL?QajeX-1vFaGDkIp_tBeemP6Q7;f#g5BLJKl z;T_Fk&*EB-5@|gd|HQla0RO`0_y(8oyGW=3_0bZY(HpObwD}BQljSx))A>y#%mF^2 zG{XLY7r+q|_8Q*8Tr9);_z0ijYy6DgMA|xtv>$;9$O8**Uykjl!dLhSzlwAacp;b> zb?Ar~yo3>$fIKWgIkuw;U*RYG$|-QqIJ@_DmyifIu{4`ZM+DiAMT8qu5{@{kojFPL zW}E3Q%|)Wm<2wE$62l^5JnZA13ln+ndAx{M@g`x!*oWgdk6WlA)6pJiiVlcEBGQnBToj@dTd@zv$@FfcbBe(&B=(vmE7tneYdZF+ z{n!ijj1g(~9n?6!n(56p({Ua6CdjwBq@y^4i@0qc*XKErK78`^8HCrtH_JZr@g6=D z>06A$_+6x5ut+>tI-ZRZAB`lWBb!Xet8~axd>OW3KThCB+=fZyMNb5Y^xuQ4A_M9p zMkIll1Y#11NhBtbn8a{IBMIrqhKeGTVH@`21b!sbiML^5g`NmPIHHk+bYw$C5z4TQ zOiwYMc&&+BNHTR~ZZG^T+6s^rUz1w=~L-RsyA!g zAU5!z&%g#A^fTDNgAd@Q$dHC0AwwRB40Qn~(9lqHLq80`SWF|+L*JpZ5*x4!hjA8H z@jztQR8-)ENNOU|z~oYMQ3xiMx)u9y9OrQhH6m#qXo?PqLL$9kU8#XcOz zdE7z`tM))sbU+jmkw&KDjX{%U)eXs*Zpn&mO=m2&AIm5)9++L@ppGiBj9W9P*Z-(P zBAM*=Ogcjoxj4avS_pHV$?*_4L4)zLXEo z;V{nPDjtvp7X*-LKjXx0ms~?iI$5$}Thk@h|6*!0T{74>bN31dwd_~>4C#!4ta2l6!k4!f-rtbA{(IQW8Q7ivr$&7)kBVize8=Hz!Z!+>w zfR*^bK5k=<$Va&%hx&;e%@Fy9EBOPPy1E6J!=*DKS2(?{6L*^{dY>fSzbo>9J!Wc; zftW4MilIb*VQtB`jkWS`-F3$DZ;c~Wk3Sz*ipL^;Ak0S*)?ht2mh@J8tSO$FD4rEC z;vi1pBJPUfrA67U+atNWn-spanText->setText(tr("%1 kS/s").arg(s)); - m_scopeVis->setLiveRate(m_rate); + m_scopeVis->setLiveRate(getRequestedChannelSampleRate()); } void ChannelAnalyzerGUI::setFiltersUIBoundaries() @@ -547,6 +547,8 @@ void ChannelAnalyzerGUI::applySettings(bool force) ChannelAnalyzer::MsgConfigureChannelAnalyzer* message = ChannelAnalyzer::MsgConfigureChannelAnalyzer::create( m_settings, force); m_channelAnalyzer->getInputMessageQueue()->push(message); + + m_scopeVis->setLiveRateLog2Decim(m_settings.m_spanLog2); } } diff --git a/plugins/channelrx/chanalyzer/readme.md b/plugins/channelrx/chanalyzer/readme.md index 17b9f20cb..7eaa726de 100644 --- a/plugins/channelrx/chanalyzer/readme.md +++ b/plugins/channelrx/chanalyzer/readme.md @@ -321,12 +321,22 @@ By default the trace display is enabled and this checkbox is checked. You can op This area shows the current trace color. When clicking on it a color chooser dialog appears that lets you change the color of the current trace -

    11. Memory select

    +

    11. Save traces in memory

    -The last 15 traces are stored in memory and this button lets you browse through traces in memory. The memory index appears on the left of the button. Traces in memory are sorted from latest (1) to oldest (15). The memory index 0 is the current live trace. When indexes > 0 are selected the live trace is suspended. +While in memory mode (see E.13 next) use this button to save the bank of traces in memory (50 last traces) to file. A file dialog will open to let you choose the file name and locaion. By default the file extension is `.trcm`. + +

    12. Load traces into memory

    + +While in memory mode (see E.13 next) use this button to load traces previously saved to file using the (E.11) button into the traces memory bank (50 traces). A file dialog will open to let you select the file. It will look for files with `.trcm` extension by default. + +

    13. Memory select

    + +The last 50 traces are stored in memory and this button lets you browse through traces in memory. The memory index appears on the left of the button. Traces in memory are sorted from latest (1) to oldest (50). The memory index 0 is the current live trace. When indexes > 0 are selected the live trace is suspended. It is the complex signal that is memorized actually so when a trace in memory is selected you can still use the global and trace controls to change the display. In particular the projection mode and the number of traces can be changed. Only the full trace length cannot be modified. When in memory mode the triggers are disabled since they only apply to a live trace. +While in memory trace the save (E.11) and load (E.12) traces to file buttons can be used. +

    F. Trigger control line

    ![Channel Analyzer NG plugin scope1 controls](../../../doc/img/ChAnalyzerNG_plugin_scope3.png) diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 9d65c6456..5ac6a2e10 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -55,6 +55,7 @@ ScopeVis::ScopeVis(GLScope* glScope) : m_traceStart(true), m_sampleRate(0), m_liveSampleRate(0), + m_liveLog2Decim(0), m_traceDiscreteMemory(m_nbTraceMemories), m_freeRun(true), m_maxTraceDelay(0), @@ -82,12 +83,19 @@ void ScopeVis::setLiveRate(int sampleRate) m_liveSampleRate = sampleRate; if (m_currentTraceMemoryIndex == 0) { // update only in live mode - setSampleRate(m_liveSampleRate); + setSampleRate(m_liveSampleRate/(1<4.8. Phosphor display stroke decay divisor

    h<<^mII%$1o9o9_ZNeLhqZ%4nMCBQ=`mTW#j4 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 diff --git a/doc/img/BladeRF2Output_plugin.png b/doc/img/BladeRF2Output_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..ed9813b139b52d8c597dc94f99d00e7ff3c28a04 GIT binary patch literal 20634 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 G68$fe3KSOr literal 0 HcmV?d00001 diff --git a/doc/img/BladeRF2Output_plugin.xcf b/doc/img/BladeRF2Output_plugin.xcf new file mode 100644 index 0000000000000000000000000000000000000000..890f4087aa37d34428e6973f57a903f7d9d5d227 GIT binary patch literal 91786 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�QZQ2NaPKr+_Q*3R2<~$rYr;Dd0-Hf|NK| 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�*^^Wr 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 2.0 micro (v2) output plugin + +

  • daemonDeviceReportGet @@ -4230,41 +4233,41 @@ margin-bottom: 20px;

    Daemon

    -
    -
    +
    +
    -

    daemonDataSettingsGet

    +

    daemonChannelSettingsGet

    -

    Get data handling details

    +

    Get channel handling details


    -
    /sdrdaemon/data/settings
    +
    /sdrdaemon/channel/settings

    Usage and SDK Samples

    -
    -
    curl -X GET "http://localhost/sdrdaemon/data/settings"
    +
    +
    curl -X GET "http://localhost/sdrdaemon/channel/settings"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -4279,17 +4282,17 @@ public class DaemonApiExample {
             
             DaemonApi apiInstance = new DaemonApi();
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +            ChannelSettings result = apiInstance.daemonChannelSettingsGet();
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsGet");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsGet");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.DaemonApi;
     
     public class DaemonApiExample {
    @@ -4297,25 +4300,25 @@ public class DaemonApiExample {
         public static void main(String[] args) {
             DaemonApi apiInstance = new DaemonApi();
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +            ChannelSettings result = apiInstance.daemonChannelSettingsGet();
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsGet");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsGet");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    
     DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -[apiInstance daemonDataSettingsGetWithCompletionHandler: 
    -              ^(SDRDaemonDataSettings output, NSError* error) {
    +[apiInstance daemonChannelSettingsGetWithCompletionHandler: 
    +              ^(ChannelSettings output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
                                 }
    @@ -4326,7 +4329,7 @@ DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.DaemonApi()
    @@ -4338,14 +4341,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.daemonDataSettingsGet(callback);
    +api.daemonChannelSettingsGet(callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -4354,7 +4357,7 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class daemonDataSettingsGetExample
    +    public class daemonChannelSettingsGetExample
         {
             public void main()
             {
    @@ -4363,12 +4366,12 @@ namespace Example
     
                 try
                 {
    -                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +                ChannelSettings result = apiInstance.daemonChannelSettingsGet();
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsGet: " + e.Message );
    +                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsGet: " + e.Message );
                 }
             }
         }
    @@ -4376,22 +4379,22 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
     $api_instance = new Swagger\Client\Api\DaemonApi();
     
     try {
    -    $result = $api_instance->daemonDataSettingsGet();
    +    $result = $api_instance->daemonChannelSettingsGet();
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDataSettingsGet: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling DaemonApi->daemonChannelSettingsGet: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::DaemonApi;
    @@ -4399,15 +4402,15 @@ use SWGSDRangel::DaemonApi;
     my $api_instance = SWGSDRangel::DaemonApi->new();
     
     eval { 
    -    my $result = $api_instance->daemonDataSettingsGet();
    +    my $result = $api_instance->daemonChannelSettingsGet();
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDataSettingsGet: $@\n";
    +    warn "Exception when calling DaemonApi->daemonChannelSettingsGet: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -4418,10 +4421,10 @@ from pprint import pprint
     api_instance = swagger_sdrangel.DaemonApi()
     
     try: 
    -    api_response = api_instance.daemon_data_settings_get()
    +    api_response = api_instance.daemon_channel_settings_get()
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDataSettingsGet: %s\n" % e)
    + print("Exception when calling DaemonApi->daemonChannelSettingsGet: %s\n" % e)
    @@ -4433,24 +4436,24 @@ except ApiException as e:

    Responses

    -

    Status: 200 - On success returns current data handling details

    +

    Status: 200 - On success return channel settings

    -
    -
    +
    +
    - +
    @@ -4480,14 +4483,14 @@ except ApiException as e:
    -
    -
    +
    +
    - +
    @@ -4523,14 +4526,14 @@ except ApiException as e:
    -
    -
    +
    +
    - +

    -
    -
    +
    +
    -

    daemonDataSettingsPatch

    +

    daemonChannelSettingsPatch

    -

    Apply data handling details differentially (no force)

    +

    Apply channel handling details differentially (no force)


    -
    /sdrdaemon/data/settings
    +
    /sdrdaemon/channel/settings

    Usage and SDK Samples

    -
    -
    curl -X PATCH "http://localhost/sdrdaemon/data/settings"
    +
    +
    curl -X PATCH "http://localhost/sdrdaemon/channel/settings"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -4613,47 +4616,47 @@ public class DaemonApiExample {
         public static void main(String[] args) {
             
             DaemonApi apiInstance = new DaemonApi();
    -        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +        ChannelSettings body = ; // ChannelSettings | Data handling detail to apply
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +            ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPatch");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.DaemonApi;
     
     public class DaemonApiExample {
     
         public static void main(String[] args) {
             DaemonApi apiInstance = new DaemonApi();
    -        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +        ChannelSettings body = ; // ChannelSettings | Data handling detail to apply
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +            ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPatch");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    -
    SDRDaemonDataSettings *body = ; // Data handling detail to apply
    +                            
    +
    ChannelSettings *body = ; // Data handling detail to apply
     
     DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -[apiInstance daemonDataSettingsPatchWith:body
    -              completionHandler: ^(SDRDaemonDataSettings output, NSError* error) {
    +[apiInstance daemonChannelSettingsPatchWith:body
    +              completionHandler: ^(ChannelSettings output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
                                 }
    @@ -4664,12 +4667,12 @@ DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.DaemonApi()
     
    -var body = ; // {SDRDaemonDataSettings} Data handling detail to apply
    +var body = ; // {ChannelSettings} Data handling detail to apply
     
     
     var callback = function(error, data, response) {
    @@ -4679,14 +4682,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.daemonDataSettingsPatch(body, callback);
    +api.daemonChannelSettingsPatch(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -4695,22 +4698,22 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class daemonDataSettingsPatchExample
    +    public class daemonChannelSettingsPatchExample
         {
             public void main()
             {
                 
                 var apiInstance = new DaemonApi();
    -            var body = new SDRDaemonDataSettings(); // SDRDaemonDataSettings | Data handling detail to apply
    +            var body = new ChannelSettings(); // ChannelSettings | Data handling detail to apply
     
                 try
                 {
    -                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +                ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsPatch: " + e.Message );
    +                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsPatch: " + e.Message );
                 }
             }
         }
    @@ -4718,40 +4721,40 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
     $api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +$body = ; // ChannelSettings | Data handling detail to apply
     
     try {
    -    $result = $api_instance->daemonDataSettingsPatch($body);
    +    $result = $api_instance->daemonChannelSettingsPatch($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDataSettingsPatch: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling DaemonApi->daemonChannelSettingsPatch: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::DaemonApi;
     
     my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::SDRDaemonDataSettings->new(); # SDRDaemonDataSettings | Data handling detail to apply
    +my $body = SWGSDRangel::Object::ChannelSettings->new(); # ChannelSettings | Data handling detail to apply
     
     eval { 
    -    my $result = $api_instance->daemonDataSettingsPatch(body => $body);
    +    my $result = $api_instance->daemonChannelSettingsPatch(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDataSettingsPatch: $@\n";
    +    warn "Exception when calling DaemonApi->daemonChannelSettingsPatch: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -4760,13 +4763,13 @@ from pprint import pprint
     
     # create an instance of the API class
     api_instance = swagger_sdrangel.DaemonApi()
    -body =  # SDRDaemonDataSettings | Data handling detail to apply
    +body =  # ChannelSettings | Data handling detail to apply
     
     try: 
    -    api_response = api_instance.daemon_data_settings_patch(body)
    +    api_response = api_instance.daemon_channel_settings_patch(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDataSettingsPatch: %s\n" % e)
    + print("Exception when calling DaemonApi->daemonChannelSettingsPatch: %s\n" % e)
    @@ -4792,7 +4795,7 @@ $(document).ready(function() { "description" : "Data handling detail to apply", "required" : true, "schema" : { - "$ref" : "#/definitions/SDRDaemonDataSettings" + "$ref" : "#/definitions/ChannelSettings" } }; var schema = schemaWrapper.schema; @@ -4806,12 +4809,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_daemonDataSettingsPatch_body'); + var result = $('#d2e199_daemonChannelSettingsPatch_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -4824,20 +4827,20 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -4867,14 +4870,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -4910,14 +4913,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +

    -
    -
    +
    +
    -

    daemonDataSettingsPut

    +

    daemonChannelSettingsPut

    -

    Apply data handling details unconditionally (force)

    +

    Apply channel handling details unconditionally (force)


    -
    /sdrdaemon/data/settings
    +
    /sdrdaemon/channel/settings

    Usage and SDK Samples

    -
    -
    curl -X PUT "http://localhost/sdrdaemon/data/settings"
    +
    +
    curl -X PUT "http://localhost/sdrdaemon/channel/settings"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -5000,47 +5003,47 @@ public class DaemonApiExample {
         public static void main(String[] args) {
             
             DaemonApi apiInstance = new DaemonApi();
    -        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling details to apply
    +        ChannelSettings body = ; // ChannelSettings | Channel handling details to apply
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +            ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPut");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPut");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.DaemonApi;
     
     public class DaemonApiExample {
     
         public static void main(String[] args) {
             DaemonApi apiInstance = new DaemonApi();
    -        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling details to apply
    +        ChannelSettings body = ; // ChannelSettings | Channel handling details to apply
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +            ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPut");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPut");
                 e.printStackTrace();
             }
         }
     }
    -
    -
    SDRDaemonDataSettings *body = ; // Data handling details to apply
    +                            
    +
    ChannelSettings *body = ; // Channel handling details to apply
     
     DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -[apiInstance daemonDataSettingsPutWith:body
    -              completionHandler: ^(SDRDaemonDataSettings output, NSError* error) {
    +[apiInstance daemonChannelSettingsPutWith:body
    +              completionHandler: ^(ChannelSettings output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
                                 }
    @@ -5051,12 +5054,12 @@ DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.DaemonApi()
     
    -var body = ; // {SDRDaemonDataSettings} Data handling details to apply
    +var body = ; // {ChannelSettings} Channel handling details to apply
     
     
     var callback = function(error, data, response) {
    @@ -5066,14 +5069,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.daemonDataSettingsPut(body, callback);
    +api.daemonChannelSettingsPut(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -5082,22 +5085,22 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class daemonDataSettingsPutExample
    +    public class daemonChannelSettingsPutExample
         {
             public void main()
             {
                 
                 var apiInstance = new DaemonApi();
    -            var body = new SDRDaemonDataSettings(); // SDRDaemonDataSettings | Data handling details to apply
    +            var body = new ChannelSettings(); // ChannelSettings | Channel handling details to apply
     
                 try
                 {
    -                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +                ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsPut: " + e.Message );
    +                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsPut: " + e.Message );
                 }
             }
         }
    @@ -5105,40 +5108,40 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
     $api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // SDRDaemonDataSettings | Data handling details to apply
    +$body = ; // ChannelSettings | Channel handling details to apply
     
     try {
    -    $result = $api_instance->daemonDataSettingsPut($body);
    +    $result = $api_instance->daemonChannelSettingsPut($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDataSettingsPut: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling DaemonApi->daemonChannelSettingsPut: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::DaemonApi;
     
     my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::SDRDaemonDataSettings->new(); # SDRDaemonDataSettings | Data handling details to apply
    +my $body = SWGSDRangel::Object::ChannelSettings->new(); # ChannelSettings | Channel handling details to apply
     
     eval { 
    -    my $result = $api_instance->daemonDataSettingsPut(body => $body);
    +    my $result = $api_instance->daemonChannelSettingsPut(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDataSettingsPut: $@\n";
    +    warn "Exception when calling DaemonApi->daemonChannelSettingsPut: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -5147,13 +5150,13 @@ from pprint import pprint
     
     # create an instance of the API class
     api_instance = swagger_sdrangel.DaemonApi()
    -body =  # SDRDaemonDataSettings | Data handling details to apply
    +body =  # ChannelSettings | Channel handling details to apply
     
     try: 
    -    api_response = api_instance.daemon_data_settings_put(body)
    +    api_response = api_instance.daemon_channel_settings_put(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDataSettingsPut: %s\n" % e)
    + print("Exception when calling DaemonApi->daemonChannelSettingsPut: %s\n" % e)
    @@ -5176,10 +5179,10 @@ $(document).ready(function() { var schemaWrapper = { "in" : "body", "name" : "body", - "description" : "Data handling details to apply", + "description" : "Channel handling details to apply", "required" : true, "schema" : { - "$ref" : "#/definitions/SDRDaemonDataSettings" + "$ref" : "#/definitions/ChannelSettings" } }; var schema = schemaWrapper.schema; @@ -5193,12 +5196,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_daemonDataSettingsPut_body'); + var result = $('#d2e199_daemonChannelSettingsPut_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -5211,20 +5214,20 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -5254,14 +5257,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -5297,14 +5300,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -28212,7 +28215,7 @@ except ApiException as e:
    - Generated 2018-08-23T00:21:49.115+02:00 + Generated 2018-08-23T14:53:21.934+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannel.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannel.yaml new file mode 100644 index 000000000..332732524 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannel.yaml @@ -0,0 +1,15 @@ +SDRDaemonChannelSettings: + description: "Data handling details for SDRDaemon" + properties: + nbFECBlocks: + description: "Number of FEC blocks per frame" + type: integer + dataAddress: + description: "Receiving USB data address" + type: string + dataPort: + description: "Receiving USB data port" + type: integer + txDelay: + description: "Minimum delay in ms between consecutive USB blocks transmissions" + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 6fce1d3ae..10ecf2699 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1298,46 +1298,46 @@ paths: "501": $ref: "#/responses/Response_501" - /sdrdaemon/data/settings: + /sdrdaemon/channel/settings: x-swagger-router-controller: deviceset get: - description: Get data handling details - operationId: daemonDataSettingsGet + description: Get channel handling details + operationId: daemonChannelSettingsGet tags: - Daemon responses: "200": - description: On success returns current data handling details + description: On success return channel settings schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" "500": $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" put: - description: Apply data handling details unconditionally (force) - operationId: daemonDataSettingsPut + description: Apply channel handling details unconditionally (force) + operationId: daemonChannelSettingsPut tags: - Daemon parameters: - name: body in: body - description: Data handling details to apply + description: Channel handling details to apply required: true schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" responses: "200": description: On success returns new settings values schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" "500": $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" patch: - description: Apply data handling details differentially (no force) - operationId: daemonDataSettingsPatch + description: Apply channel handling details differentially (no force) + operationId: daemonChannelSettingsPatch tags: - Daemon parameters: @@ -1346,12 +1346,12 @@ paths: description: Data handling detail to apply required: true schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" responses: "200": description: On success returns new settings values schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" "500": $ref: "#/responses/Response_500" "501": @@ -2194,6 +2194,8 @@ definitions: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" + SDRDaemonChannelSettings: + $ref: "/doc/swagger/include/SDRDaemonChannel.yaml#/SDRDaemonChannelSettings" SSBModSettings: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: @@ -2244,22 +2246,6 @@ definitions: WFMModReport: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModReport" - SDRDaemonDataSettings: - description: "Data handling details for SDRDaemon" - properties: - nbFECBlocks: - description: "Number of FEC blocks per frame" - type: integer - dataAddress: - description: "Receiving USB data address" - type: string - dataPort: - description: "Receiving USB data port" - type: integer - txDelay: - description: "Minimum delay in ms between consecutive USB blocks transmissions" - type: integer - responses: Response_500: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 3b0d1e8a4..96a2a31a5 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2146,6 +2146,14 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "SDRDaemonChannel") + { + QJsonObject sdrDaemonChannelSettingsJsonObject = jsonObject["SDRDaemonChannelSettings"].toObject(); + channelSettingsKeys = sdrDaemonChannelSettingsJsonObject.keys(); + channelSettings.setSdrDaemonChannelSettings(new SWGSDRangel::SWGSDRDaemonChannelSettings()); + channelSettings.getSdrDaemonChannelSettings()->fromJsonObject(sdrDaemonChannelSettingsJsonObject); + return true; + } else if (*channelType == "SSBDemod") { if (channelSettings.getTx() == 0) @@ -2384,6 +2392,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setDsdDemodSettings(0); channelSettings.setNfmDemodSettings(0); channelSettings.setNfmModSettings(0); + channelSettings.setSdrDaemonChannelSettings(0); channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); channelSettings.setUdpSinkSettings(0); diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index 7d084edbd..2eae164f2 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -23,7 +23,7 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGDeviceReport.h" -#include "SWGSDRDaemonDataSettings.h" +#include "SWGChannelSettings.h" #include "SWGErrorResponse.h" #include "dsp/dsptypes.h" @@ -39,7 +39,7 @@ QString WebAPIAdapterDaemon::daemonInstanceSummaryURL = "/sdrdaemon"; QString WebAPIAdapterDaemon::daemonInstanceLoggingURL = "/sdrdaemon/logging"; -QString WebAPIAdapterDaemon::daemonDataSettingsURL = "/sdrdaemon/data/settings"; +QString WebAPIAdapterDaemon::daemonChannelSettingsURL = "/sdrdaemon/channel/settings"; QString WebAPIAdapterDaemon::daemonDeviceSettingsURL = "/sdrdaemon/device/settings"; QString WebAPIAdapterDaemon::daemonDeviceReportURL = "/sdrdaemon/device/report"; QString WebAPIAdapterDaemon::daemonRunURL = "/sdrdaemon/run"; @@ -176,8 +176,8 @@ int WebAPIAdapterDaemon::daemonInstanceLoggingPut( return 200; } -int WebAPIAdapterDaemon::daemonDataSettingsGet( - SWGSDRangel::SWGSDRDaemonDataSettings& response __attribute__((unused)), +int WebAPIAdapterDaemon::daemonChannelSettingsGet( + SWGSDRangel::SWGChannelSettings& response __attribute__((unused)), SWGSDRangel::SWGErrorResponse& error) { error.init(); @@ -185,10 +185,10 @@ int WebAPIAdapterDaemon::daemonDataSettingsGet( return 501; } -int WebAPIAdapterDaemon::daemonDataSettingsPutPatch( +int WebAPIAdapterDaemon::daemonChannelSettingsPutPatch( bool force __attribute__((unused)), - const QStringList& dataSettingsKeys __attribute__((unused)), - SWGSDRangel::SWGSDRDaemonDataSettings& response __attribute__((unused)), + const QStringList& channelSettingsKeys __attribute__((unused)), + SWGSDRangel::SWGChannelSettings& response __attribute__((unused)), SWGSDRangel::SWGErrorResponse& error) { error.init(); diff --git a/sdrdaemon/webapi/webapiadapterdaemon.h b/sdrdaemon/webapi/webapiadapterdaemon.h index 6403900bf..df4c7c592 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.h +++ b/sdrdaemon/webapi/webapiadapterdaemon.h @@ -33,7 +33,7 @@ namespace SWGSDRangel class SWGSuccessResponse; class SWGErrorResponse; class SWGLoggingInfo; - class SWGSDRDaemonDataSettings; + class SWGChannelSettings; } class SDRDaemonMain; @@ -57,14 +57,14 @@ public: SWGSDRangel::SWGLoggingInfo& response, SWGSDRangel::SWGErrorResponse& error); - int daemonDataSettingsGet( - SWGSDRangel::SWGSDRDaemonDataSettings& response, + int daemonChannelSettingsGet( + SWGSDRangel::SWGChannelSettings& response, SWGSDRangel::SWGErrorResponse& error); - int daemonDataSettingsPutPatch( + int daemonChannelSettingsPutPatch( bool force, - const QStringList& dataSettingsKeys, - SWGSDRangel::SWGSDRDaemonDataSettings& response, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, SWGSDRangel::SWGErrorResponse& error); int daemonDeviceSettingsGet( @@ -95,7 +95,7 @@ public: static QString daemonInstanceSummaryURL; static QString daemonInstanceLoggingURL; - static QString daemonDataSettingsURL; + static QString daemonChannelSettingsURL; static QString daemonDeviceSettingsURL; static QString daemonDeviceReportURL; static QString daemonRunURL; diff --git a/sdrdaemon/webapi/webapirequestmapper.cpp b/sdrdaemon/webapi/webapirequestmapper.cpp index a5a162208..ad4c4df62 100644 --- a/sdrdaemon/webapi/webapirequestmapper.cpp +++ b/sdrdaemon/webapi/webapirequestmapper.cpp @@ -26,7 +26,7 @@ #include "webapirequestmapper.h" #include "SWGDaemonSummaryResponse.h" #include "SWGInstanceDevicesResponse.h" -#include "SWGSDRDaemonDataSettings.h" +#include "SWGChannelSettings.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGDeviceReport.h" @@ -96,8 +96,8 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http daemonInstanceSummaryService(request, response); } else if (path == WebAPIAdapterDaemon::daemonInstanceLoggingURL) { daemonInstanceLoggingService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonDataSettingsURL) { - daemonDataSettingsService(request, response); + } else if (path == WebAPIAdapterDaemon::daemonChannelSettingsURL) { + daemonChannelSettingsService(request, response); } else if (path == WebAPIAdapterDaemon::daemonDeviceSettingsURL) { daemonDeviceSettingsService(request, response); } else if (path == WebAPIAdapterDaemon::daemonDeviceReportURL) { @@ -191,27 +191,41 @@ void WebAPIRequestMapper::daemonInstanceLoggingService(qtwebapp::HttpRequest& re } } -void WebAPIRequestMapper::daemonDataSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +void WebAPIRequestMapper::daemonChannelSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); response.setHeader("Access-Control-Allow-Origin", "*"); - if ((request.getMethod() == "PUT") || (request.getMethod() == "PATCH")) + if (request.getMethod() == "GET") + { + SWGSDRangel::SWGChannelSettings normalResponse; + resetChannelSettings(normalResponse); + int status = m_adapter->daemonChannelSettingsGet(normalResponse, errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + else if ((request.getMethod() == "PUT") || (request.getMethod() == "PATCH")) { QString jsonStr = request.getBody(); QJsonObject jsonObject; if (parseJsonBody(jsonStr, jsonObject, response)) { - SWGSDRangel::SWGSDRDaemonDataSettings normalResponse; - QStringList dataSettingsKeys; + SWGSDRangel::SWGChannelSettings normalResponse; + resetChannelSettings(normalResponse); + QStringList channelSettingsKeys; - if (validateDataSettings(normalResponse, jsonObject, dataSettingsKeys)) + if (validateChannelSettings(normalResponse, jsonObject, channelSettingsKeys)) { - int status = m_adapter->daemonDataSettingsPutPatch( + int status = m_adapter->daemonChannelSettingsPutPatch( (request.getMethod() == "PUT"), // force settings on PUT - dataSettingsKeys, + channelSettingsKeys, normalResponse, errorResponse); response.setStatus(status); @@ -238,18 +252,6 @@ void WebAPIRequestMapper::daemonDataSettingsService(qtwebapp::HttpRequest& reque response.write(errorResponse.asJson().toUtf8()); } } - else if (request.getMethod() == "GET") - { - SWGSDRangel::SWGSDRDaemonDataSettings normalResponse; - int status = m_adapter->daemonDataSettingsGet(normalResponse, errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } else { response.setStatus(405,"Invalid HTTP method"); @@ -411,51 +413,248 @@ void WebAPIRequestMapper::daemonRunService(qtwebapp::HttpRequest& request, qtweb } } - -bool WebAPIRequestMapper::validateDataSettings(SWGSDRangel::SWGSDRDaemonDataSettings& dataSettings, QJsonObject& jsonObject, QStringList& dataSettingsKeys) +// TODO: put in library in common with SDRangel. Can be static. +bool WebAPIRequestMapper::validateChannelSettings( + SWGSDRangel::SWGChannelSettings& channelSettings, + QJsonObject& jsonObject, + QStringList& channelSettingsKeys) { - if (jsonObject.contains("nbFECBlocks")) - { - int nbFECBlocks = jsonObject["nbFECBlocks"].toInt(); - - if (nbFECBlocks >=0 && nbFECBlocks < 127) { - dataSettings.setNbFecBlocks(nbFECBlocks); - } else { - dataSettings.setNbFecBlocks(0); - } + if (jsonObject.contains("tx")) { + channelSettings.setTx(jsonObject["tx"].toInt()); + } else { + channelSettings.setTx(0); // assume Rx } - if (jsonObject.contains("dataPort")) - { - int dataPort = jsonObject["dataPort"].toInt(); - - if (dataPort > 1023 && dataPort < 65536) { - dataSettings.setDataPort(dataPort); - } else { - dataSettings.setDataPort(9090); - } - } - - if (jsonObject.contains("txDelay")) - { - int txDelay = jsonObject["txDelay"].toInt(); - - if (txDelay > 100) { - dataSettings.setTxDelay(txDelay); - } else { - dataSettings.setTxDelay(100); - } - } - - if (jsonObject.contains("dataAddress") && jsonObject["dataAddress"].isString()) { - dataSettings.setDataAddress(new QString(jsonObject["dataAddress"].toString())); + if (jsonObject.contains("channelType") && jsonObject["channelType"].isString()) { + channelSettings.setChannelType(new QString(jsonObject["channelType"].toString())); } else { return false; } - dataSettingsKeys = jsonObject.keys(); + QString *channelType = channelSettings.getChannelType(); - return true; + if (*channelType == "AMDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject amDemodSettingsJsonObject = jsonObject["AMDemodSettings"].toObject(); + channelSettingsKeys = amDemodSettingsJsonObject.keys(); + channelSettings.setAmDemodSettings(new SWGSDRangel::SWGAMDemodSettings()); + channelSettings.getAmDemodSettings()->fromJsonObject(amDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "AMMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject amModSettingsJsonObject = jsonObject["AMModSettings"].toObject(); + channelSettingsKeys = amModSettingsJsonObject.keys(); + + if (channelSettingsKeys.contains("cwKeyer")) + { + QJsonObject cwKeyerSettingsJsonObject; + appendSettingsSubKeys(amModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); + } + + channelSettings.setAmModSettings(new SWGSDRangel::SWGAMModSettings()); + channelSettings.getAmModSettings()->fromJsonObject(amModSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "ATVMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject atvModSettingsJsonObject = jsonObject["ATVModSettings"].toObject(); + channelSettingsKeys = atvModSettingsJsonObject.keys(); + channelSettings.setAtvModSettings(new SWGSDRangel::SWGATVModSettings()); + channelSettings.getAtvModSettings()->fromJsonObject(atvModSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "BFMDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject bfmDemodSettingsJsonObject = jsonObject["BFMDemodSettings"].toObject(); + channelSettingsKeys = bfmDemodSettingsJsonObject.keys(); + channelSettings.setBfmDemodSettings(new SWGSDRangel::SWGBFMDemodSettings()); + channelSettings.getBfmDemodSettings()->fromJsonObject(bfmDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "DSDDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject dsdDemodSettingsJsonObject = jsonObject["DSDDemodSettings"].toObject(); + channelSettingsKeys = dsdDemodSettingsJsonObject.keys(); + channelSettings.setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings()); + channelSettings.getDsdDemodSettings()->fromJsonObject(dsdDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "NFMDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject nfmDemodSettingsJsonObject = jsonObject["NFMDemodSettings"].toObject(); + channelSettingsKeys = nfmDemodSettingsJsonObject.keys(); + channelSettings.setNfmDemodSettings(new SWGSDRangel::SWGNFMDemodSettings()); + channelSettings.getNfmDemodSettings()->fromJsonObject(nfmDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "NFMMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject nfmModSettingsJsonObject = jsonObject["NFMModSettings"].toObject(); + channelSettingsKeys = nfmModSettingsJsonObject.keys(); + + if (channelSettingsKeys.contains("cwKeyer")) + { + QJsonObject cwKeyerSettingsJsonObject; + appendSettingsSubKeys(nfmModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); + } + + channelSettings.setNfmModSettings(new SWGSDRangel::SWGNFMModSettings()); + channelSettings.getNfmModSettings()->fromJsonObject(nfmModSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "SDRDaemonChannel") + { + QJsonObject sdrDaemonChannelSettingsJsonObject = jsonObject["SDRDaemonChannelSettings"].toObject(); + channelSettingsKeys = sdrDaemonChannelSettingsJsonObject.keys(); + channelSettings.setSdrDaemonChannelSettings(new SWGSDRangel::SWGSDRDaemonChannelSettings()); + channelSettings.getSdrDaemonChannelSettings()->fromJsonObject(sdrDaemonChannelSettingsJsonObject); + return true; + } + else if (*channelType == "SSBDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject ssbDemodSettingsJsonObject = jsonObject["SSBDemodSettings"].toObject(); + channelSettingsKeys = ssbDemodSettingsJsonObject.keys(); + channelSettings.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings()); + channelSettings.getSsbDemodSettings()->fromJsonObject(ssbDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "SSBMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject ssbModSettingsJsonObject = jsonObject["SSBModSettings"].toObject(); + channelSettingsKeys = ssbModSettingsJsonObject.keys(); + + if (channelSettingsKeys.contains("cwKeyer")) + { + QJsonObject cwKeyerSettingsJsonObject; + appendSettingsSubKeys(ssbModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); + } + + channelSettings.setSsbModSettings(new SWGSDRangel::SWGSSBModSettings()); + channelSettings.getSsbModSettings()->fromJsonObject(ssbModSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "UDPSink") + { + if (channelSettings.getTx() != 0) + { + QJsonObject udpSinkSettingsJsonObject = jsonObject["UDPSinkSettings"].toObject(); + channelSettingsKeys = udpSinkSettingsJsonObject.keys(); + channelSettings.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); + channelSettings.getUdpSinkSettings()->fromJsonObject(udpSinkSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "UDPSrc") + { + if (channelSettings.getTx() == 0) + { + QJsonObject udpSrcSettingsJsonObject = jsonObject["UDPSrcSettings"].toObject(); + channelSettingsKeys = udpSrcSettingsJsonObject.keys(); + channelSettings.setUdpSrcSettings(new SWGSDRangel::SWGUDPSrcSettings()); + channelSettings.getUdpSrcSettings()->fromJsonObject(udpSrcSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "WFMDemod") + { + if (channelSettings.getTx() == 0) + { + QJsonObject wfmDemodSettingsJsonObject = jsonObject["WFMDemodSettings"].toObject(); + channelSettingsKeys = wfmDemodSettingsJsonObject.keys(); + channelSettings.setWfmDemodSettings(new SWGSDRangel::SWGWFMDemodSettings()); + channelSettings.getWfmDemodSettings()->fromJsonObject(wfmDemodSettingsJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "WFMMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject wfmModSettingsJsonObject = jsonObject["WFMModSettings"].toObject(); + channelSettingsKeys = wfmModSettingsJsonObject.keys(); + + if (channelSettingsKeys.contains("cwKeyer")) + { + QJsonObject cwKeyerSettingsJsonObject; + appendSettingsSubKeys(wfmModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); + } + + channelSettings.setWfmModSettings(new SWGSDRangel::SWGWFMModSettings()); + channelSettings.getWfmModSettings()->fromJsonObject(wfmModSettingsJsonObject); + return true; + } + else { + return false; + } + } + else + { + return false; + } } // TODO: put in library in common with SDRangel. Can be static. @@ -774,6 +973,27 @@ bool WebAPIRequestMapper::parseJsonBody(QString& jsonStr, QJsonObject& jsonObjec } } +// TODO: put in library in common with SDRangel. Can be static. +void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings) +{ + channelSettings.cleanup(); + channelSettings.setChannelType(0); + channelSettings.setAmDemodSettings(0); + channelSettings.setAmModSettings(0); + channelSettings.setAtvModSettings(0); + channelSettings.setBfmDemodSettings(0); + channelSettings.setDsdDemodSettings(0); + channelSettings.setNfmDemodSettings(0); + channelSettings.setNfmModSettings(0); + channelSettings.setSdrDaemonChannelSettings(0); + channelSettings.setSsbDemodSettings(0); + channelSettings.setSsbModSettings(0); + channelSettings.setUdpSinkSettings(0); + channelSettings.setUdpSrcSettings(0); + channelSettings.setWfmDemodSettings(0); + channelSettings.setWfmModSettings(0); +} + // TODO: put in library in common with SDRangel. Can be static. void WebAPIRequestMapper::resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings) { diff --git a/sdrdaemon/webapi/webapirequestmapper.h b/sdrdaemon/webapi/webapirequestmapper.h index 927fa8fc9..49363493a 100644 --- a/sdrdaemon/webapi/webapirequestmapper.h +++ b/sdrdaemon/webapi/webapirequestmapper.h @@ -30,7 +30,7 @@ namespace SWGSDRangel { - class SWGSDRDaemonDataSettings; + class SWGChannelSettings; class SWGDeviceSettings; class SWGDeviceReport; } @@ -54,12 +54,12 @@ private: void daemonInstanceSummaryService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void daemonInstanceLoggingService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonDataSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void daemonChannelSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void daemonDeviceSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void daemonRunService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void daemonDeviceReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - bool validateDataSettings(SWGSDRangel::SWGSDRDaemonDataSettings& dataSettings, QJsonObject& jsonObject, QStringList& dataSettingsKeys); + bool validateChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings, QJsonObject& jsonObject, QStringList& channelSettingsKeys); bool validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys); void appendSettingsSubKeys( @@ -70,6 +70,7 @@ private: bool parseJsonBody(QString& jsonStr, QJsonObject& jsonObject, qtwebapp::HttpResponse& response); + void resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings); void resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings); void resetDeviceReport(SWGSDRangel::SWGDeviceReport& deviceReport); }; diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonChannel.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonChannel.yaml new file mode 100644 index 000000000..332732524 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonChannel.yaml @@ -0,0 +1,15 @@ +SDRDaemonChannelSettings: + description: "Data handling details for SDRDaemon" + properties: + nbFECBlocks: + description: "Number of FEC blocks per frame" + type: integer + dataAddress: + description: "Receiving USB data address" + type: string + dataPort: + description: "Receiving USB data port" + type: integer + txDelay: + description: "Minimum delay in ms between consecutive USB blocks transmissions" + type: integer diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index c6b1d8920..5f5147cf8 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1298,46 +1298,46 @@ paths: "501": $ref: "#/responses/Response_501" - /sdrdaemon/data/settings: + /sdrdaemon/channel/settings: x-swagger-router-controller: deviceset get: - description: Get data handling details - operationId: daemonDataSettingsGet + description: Get channel handling details + operationId: daemonChannelSettingsGet tags: - Daemon responses: "200": - description: On success returns current data handling details + description: On success return channel settings schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" "500": $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" put: - description: Apply data handling details unconditionally (force) - operationId: daemonDataSettingsPut + description: Apply channel handling details unconditionally (force) + operationId: daemonChannelSettingsPut tags: - Daemon parameters: - name: body in: body - description: Data handling details to apply + description: Channel handling details to apply required: true schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" responses: "200": description: On success returns new settings values schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" "500": $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" patch: - description: Apply data handling details differentially (no force) - operationId: daemonDataSettingsPatch + description: Apply channel handling details differentially (no force) + operationId: daemonChannelSettingsPatch tags: - Daemon parameters: @@ -1346,12 +1346,12 @@ paths: description: Data handling detail to apply required: true schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" responses: "200": description: On success returns new settings values schema: - $ref: "#/definitions/SDRDaemonDataSettings" + $ref: "#/definitions/ChannelSettings" "500": $ref: "#/responses/Response_500" "501": @@ -2194,6 +2194,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" + SDRDaemonChannelSettings: + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannel.yaml#/SDRDaemonChannelSettings" SSBModSettings: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: @@ -2244,22 +2246,6 @@ definitions: WFMModReport: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModReport" - SDRDaemonDataSettings: - description: "Data handling details for SDRDaemon" - properties: - nbFECBlocks: - description: "Number of FEC blocks per frame" - type: integer - dataAddress: - description: "Receiving USB data address" - type: string - dataPort: - description: "Receiving USB data port" - type: integer - txDelay: - description: "Minimum delay in ms between consecutive USB blocks transmissions" - type: integer - responses: Response_500: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index d1c41a364..20c65c84f 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1467,6 +1467,9 @@ margin-bottom: 20px; "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" }, + "SDRDaemonChannelSettings" : { + "$ref" : "#/definitions/SDRDaemonChannelSettings" + }, "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" }, @@ -3143,7 +3146,7 @@ margin-bottom: 20px; }, "description" : "RTLSDR" }; - defs.SDRDaemonDataSettings = { + defs.SDRDaemonChannelSettings = { "properties" : { "nbFECBlocks" : { "type" : "integer", @@ -4024,14 +4027,14 @@ margin-bottom: 20px; -
  • - daemonDataSettingsGet +
  • + daemonChannelSettingsGet
  • -
  • - daemonDataSettingsPatch +
  • + daemonChannelSettingsPatch
  • -
  • - daemonDataSettingsPut +
  • + daemonChannelSettingsPut
  • daemonDeviceReportGet @@ -4230,41 +4233,41 @@ margin-bottom: 20px;

    Daemon

    -
    -
    +
    +
    -

    daemonDataSettingsGet

    +

    daemonChannelSettingsGet

    -

    Get data handling details

    +

    Get channel handling details


    -
    /sdrdaemon/data/settings
    +
    /sdrdaemon/channel/settings

    Usage and SDK Samples

    -
    -
    curl -X GET "http://localhost/sdrdaemon/data/settings"
    +
    +
    curl -X GET "http://localhost/sdrdaemon/channel/settings"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -4279,17 +4282,17 @@ public class DaemonApiExample {
             
             DaemonApi apiInstance = new DaemonApi();
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +            ChannelSettings result = apiInstance.daemonChannelSettingsGet();
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsGet");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsGet");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.DaemonApi;
     
     public class DaemonApiExample {
    @@ -4297,25 +4300,25 @@ public class DaemonApiExample {
         public static void main(String[] args) {
             DaemonApi apiInstance = new DaemonApi();
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +            ChannelSettings result = apiInstance.daemonChannelSettingsGet();
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsGet");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsGet");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    
     DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -[apiInstance daemonDataSettingsGetWithCompletionHandler: 
    -              ^(SDRDaemonDataSettings output, NSError* error) {
    +[apiInstance daemonChannelSettingsGetWithCompletionHandler: 
    +              ^(ChannelSettings output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
                                 }
    @@ -4326,7 +4329,7 @@ DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.DaemonApi()
    @@ -4338,14 +4341,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.daemonDataSettingsGet(callback);
    +api.daemonChannelSettingsGet(callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -4354,7 +4357,7 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class daemonDataSettingsGetExample
    +    public class daemonChannelSettingsGetExample
         {
             public void main()
             {
    @@ -4363,12 +4366,12 @@ namespace Example
     
                 try
                 {
    -                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsGet();
    +                ChannelSettings result = apiInstance.daemonChannelSettingsGet();
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsGet: " + e.Message );
    +                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsGet: " + e.Message );
                 }
             }
         }
    @@ -4376,22 +4379,22 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
     $api_instance = new Swagger\Client\Api\DaemonApi();
     
     try {
    -    $result = $api_instance->daemonDataSettingsGet();
    +    $result = $api_instance->daemonChannelSettingsGet();
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDataSettingsGet: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling DaemonApi->daemonChannelSettingsGet: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::DaemonApi;
    @@ -4399,15 +4402,15 @@ use SWGSDRangel::DaemonApi;
     my $api_instance = SWGSDRangel::DaemonApi->new();
     
     eval { 
    -    my $result = $api_instance->daemonDataSettingsGet();
    +    my $result = $api_instance->daemonChannelSettingsGet();
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDataSettingsGet: $@\n";
    +    warn "Exception when calling DaemonApi->daemonChannelSettingsGet: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -4418,10 +4421,10 @@ from pprint import pprint
     api_instance = swagger_sdrangel.DaemonApi()
     
     try: 
    -    api_response = api_instance.daemon_data_settings_get()
    +    api_response = api_instance.daemon_channel_settings_get()
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDataSettingsGet: %s\n" % e)
    + print("Exception when calling DaemonApi->daemonChannelSettingsGet: %s\n" % e)
    @@ -4433,24 +4436,24 @@ except ApiException as e:

    Responses

    -

    Status: 200 - On success returns current data handling details

    +

    Status: 200 - On success return channel settings

    -
    -
    +
    +
    - +
    @@ -4480,14 +4483,14 @@ except ApiException as e:
    -
    -
    +
    +
    - +
    @@ -4523,14 +4526,14 @@ except ApiException as e:
    -
    -
    +
    +
    - +

    -
    -
    +
    +
    -

    daemonDataSettingsPatch

    +

    daemonChannelSettingsPatch

    -

    Apply data handling details differentially (no force)

    +

    Apply channel handling details differentially (no force)


    -
    /sdrdaemon/data/settings
    +
    /sdrdaemon/channel/settings

    Usage and SDK Samples

    -
    -
    curl -X PATCH "http://localhost/sdrdaemon/data/settings"
    +
    +
    curl -X PATCH "http://localhost/sdrdaemon/channel/settings"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -4613,47 +4616,47 @@ public class DaemonApiExample {
         public static void main(String[] args) {
             
             DaemonApi apiInstance = new DaemonApi();
    -        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +        ChannelSettings body = ; // ChannelSettings | Data handling detail to apply
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +            ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPatch");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.DaemonApi;
     
     public class DaemonApiExample {
     
         public static void main(String[] args) {
             DaemonApi apiInstance = new DaemonApi();
    -        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +        ChannelSettings body = ; // ChannelSettings | Data handling detail to apply
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +            ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPatch");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    -
    SDRDaemonDataSettings *body = ; // Data handling detail to apply
    +                            
    +
    ChannelSettings *body = ; // Data handling detail to apply
     
     DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -[apiInstance daemonDataSettingsPatchWith:body
    -              completionHandler: ^(SDRDaemonDataSettings output, NSError* error) {
    +[apiInstance daemonChannelSettingsPatchWith:body
    +              completionHandler: ^(ChannelSettings output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
                                 }
    @@ -4664,12 +4667,12 @@ DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.DaemonApi()
     
    -var body = ; // {SDRDaemonDataSettings} Data handling detail to apply
    +var body = ; // {ChannelSettings} Data handling detail to apply
     
     
     var callback = function(error, data, response) {
    @@ -4679,14 +4682,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.daemonDataSettingsPatch(body, callback);
    +api.daemonChannelSettingsPatch(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -4695,22 +4698,22 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class daemonDataSettingsPatchExample
    +    public class daemonChannelSettingsPatchExample
         {
             public void main()
             {
                 
                 var apiInstance = new DaemonApi();
    -            var body = new SDRDaemonDataSettings(); // SDRDaemonDataSettings | Data handling detail to apply
    +            var body = new ChannelSettings(); // ChannelSettings | Data handling detail to apply
     
                 try
                 {
    -                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPatch(body);
    +                ChannelSettings result = apiInstance.daemonChannelSettingsPatch(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsPatch: " + e.Message );
    +                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsPatch: " + e.Message );
                 }
             }
         }
    @@ -4718,40 +4721,40 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
     $api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // SDRDaemonDataSettings | Data handling detail to apply
    +$body = ; // ChannelSettings | Data handling detail to apply
     
     try {
    -    $result = $api_instance->daemonDataSettingsPatch($body);
    +    $result = $api_instance->daemonChannelSettingsPatch($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDataSettingsPatch: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling DaemonApi->daemonChannelSettingsPatch: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::DaemonApi;
     
     my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::SDRDaemonDataSettings->new(); # SDRDaemonDataSettings | Data handling detail to apply
    +my $body = SWGSDRangel::Object::ChannelSettings->new(); # ChannelSettings | Data handling detail to apply
     
     eval { 
    -    my $result = $api_instance->daemonDataSettingsPatch(body => $body);
    +    my $result = $api_instance->daemonChannelSettingsPatch(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDataSettingsPatch: $@\n";
    +    warn "Exception when calling DaemonApi->daemonChannelSettingsPatch: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -4760,13 +4763,13 @@ from pprint import pprint
     
     # create an instance of the API class
     api_instance = swagger_sdrangel.DaemonApi()
    -body =  # SDRDaemonDataSettings | Data handling detail to apply
    +body =  # ChannelSettings | Data handling detail to apply
     
     try: 
    -    api_response = api_instance.daemon_data_settings_patch(body)
    +    api_response = api_instance.daemon_channel_settings_patch(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDataSettingsPatch: %s\n" % e)
    + print("Exception when calling DaemonApi->daemonChannelSettingsPatch: %s\n" % e)
    @@ -4792,7 +4795,7 @@ $(document).ready(function() { "description" : "Data handling detail to apply", "required" : true, "schema" : { - "$ref" : "#/definitions/SDRDaemonDataSettings" + "$ref" : "#/definitions/ChannelSettings" } }; var schema = schemaWrapper.schema; @@ -4806,12 +4809,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_daemonDataSettingsPatch_body'); + var result = $('#d2e199_daemonChannelSettingsPatch_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -4824,20 +4827,20 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -4867,14 +4870,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -4910,14 +4913,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +

    -
    -
    +
    +
    -

    daemonDataSettingsPut

    +

    daemonChannelSettingsPut

    -

    Apply data handling details unconditionally (force)

    +

    Apply channel handling details unconditionally (force)


    -
    /sdrdaemon/data/settings
    +
    /sdrdaemon/channel/settings

    Usage and SDK Samples

    -
    -
    curl -X PUT "http://localhost/sdrdaemon/data/settings"
    +
    +
    curl -X PUT "http://localhost/sdrdaemon/channel/settings"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -5000,47 +5003,47 @@ public class DaemonApiExample {
         public static void main(String[] args) {
             
             DaemonApi apiInstance = new DaemonApi();
    -        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling details to apply
    +        ChannelSettings body = ; // ChannelSettings | Channel handling details to apply
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +            ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPut");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPut");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.DaemonApi;
     
     public class DaemonApiExample {
     
         public static void main(String[] args) {
             DaemonApi apiInstance = new DaemonApi();
    -        SDRDaemonDataSettings body = ; // SDRDaemonDataSettings | Data handling details to apply
    +        ChannelSettings body = ; // ChannelSettings | Channel handling details to apply
             try {
    -            SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +            ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling DaemonApi#daemonDataSettingsPut");
    +            System.err.println("Exception when calling DaemonApi#daemonChannelSettingsPut");
                 e.printStackTrace();
             }
         }
     }
    -
    -
    SDRDaemonDataSettings *body = ; // Data handling details to apply
    +                            
    +
    ChannelSettings *body = ; // Channel handling details to apply
     
     DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -[apiInstance daemonDataSettingsPutWith:body
    -              completionHandler: ^(SDRDaemonDataSettings output, NSError* error) {
    +[apiInstance daemonChannelSettingsPutWith:body
    +              completionHandler: ^(ChannelSettings output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
                                 }
    @@ -5051,12 +5054,12 @@ DaemonApi *apiInstance = [[DaemonApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.DaemonApi()
     
    -var body = ; // {SDRDaemonDataSettings} Data handling details to apply
    +var body = ; // {ChannelSettings} Channel handling details to apply
     
     
     var callback = function(error, data, response) {
    @@ -5066,14 +5069,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.daemonDataSettingsPut(body, callback);
    +api.daemonChannelSettingsPut(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -5082,22 +5085,22 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class daemonDataSettingsPutExample
    +    public class daemonChannelSettingsPutExample
         {
             public void main()
             {
                 
                 var apiInstance = new DaemonApi();
    -            var body = new SDRDaemonDataSettings(); // SDRDaemonDataSettings | Data handling details to apply
    +            var body = new ChannelSettings(); // ChannelSettings | Channel handling details to apply
     
                 try
                 {
    -                SDRDaemonDataSettings result = apiInstance.daemonDataSettingsPut(body);
    +                ChannelSettings result = apiInstance.daemonChannelSettingsPut(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling DaemonApi.daemonDataSettingsPut: " + e.Message );
    +                Debug.Print("Exception when calling DaemonApi.daemonChannelSettingsPut: " + e.Message );
                 }
             }
         }
    @@ -5105,40 +5108,40 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
     $api_instance = new Swagger\Client\Api\DaemonApi();
    -$body = ; // SDRDaemonDataSettings | Data handling details to apply
    +$body = ; // ChannelSettings | Channel handling details to apply
     
     try {
    -    $result = $api_instance->daemonDataSettingsPut($body);
    +    $result = $api_instance->daemonChannelSettingsPut($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling DaemonApi->daemonDataSettingsPut: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling DaemonApi->daemonChannelSettingsPut: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::DaemonApi;
     
     my $api_instance = SWGSDRangel::DaemonApi->new();
    -my $body = SWGSDRangel::Object::SDRDaemonDataSettings->new(); # SDRDaemonDataSettings | Data handling details to apply
    +my $body = SWGSDRangel::Object::ChannelSettings->new(); # ChannelSettings | Channel handling details to apply
     
     eval { 
    -    my $result = $api_instance->daemonDataSettingsPut(body => $body);
    +    my $result = $api_instance->daemonChannelSettingsPut(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling DaemonApi->daemonDataSettingsPut: $@\n";
    +    warn "Exception when calling DaemonApi->daemonChannelSettingsPut: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -5147,13 +5150,13 @@ from pprint import pprint
     
     # create an instance of the API class
     api_instance = swagger_sdrangel.DaemonApi()
    -body =  # SDRDaemonDataSettings | Data handling details to apply
    +body =  # ChannelSettings | Channel handling details to apply
     
     try: 
    -    api_response = api_instance.daemon_data_settings_put(body)
    +    api_response = api_instance.daemon_channel_settings_put(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling DaemonApi->daemonDataSettingsPut: %s\n" % e)
    + print("Exception when calling DaemonApi->daemonChannelSettingsPut: %s\n" % e)
    @@ -5176,10 +5179,10 @@ $(document).ready(function() { var schemaWrapper = { "in" : "body", "name" : "body", - "description" : "Data handling details to apply", + "description" : "Channel handling details to apply", "required" : true, "schema" : { - "$ref" : "#/definitions/SDRDaemonDataSettings" + "$ref" : "#/definitions/ChannelSettings" } }; var schema = schemaWrapper.schema; @@ -5193,12 +5196,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_daemonDataSettingsPut_body'); + var result = $('#d2e199_daemonChannelSettingsPut_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -5211,20 +5214,20 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -5254,14 +5257,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -5297,14 +5300,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -28212,7 +28215,7 @@ except ApiException as e:
    - Generated 2018-08-23T00:21:49.115+02:00 + Generated 2018-08-23T14:53:21.934+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 4efc140c8..1b3689f77 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -46,6 +46,8 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; m_nfm_mod_settings_isSet = false; + sdr_daemon_channel_settings = nullptr; + m_sdr_daemon_channel_settings_isSet = false; ssb_mod_settings = nullptr; m_ssb_mod_settings_isSet = false; ssb_demod_settings = nullptr; @@ -84,6 +86,8 @@ SWGChannelSettings::init() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); m_nfm_mod_settings_isSet = false; + sdr_daemon_channel_settings = new SWGSDRDaemonChannelSettings(); + m_sdr_daemon_channel_settings_isSet = false; ssb_mod_settings = new SWGSSBModSettings(); m_ssb_mod_settings_isSet = false; ssb_demod_settings = new SWGSSBDemodSettings(); @@ -125,6 +129,9 @@ SWGChannelSettings::cleanup() { if(nfm_mod_settings != nullptr) { delete nfm_mod_settings; } + if(sdr_daemon_channel_settings != nullptr) { + delete sdr_daemon_channel_settings; + } if(ssb_mod_settings != nullptr) { delete ssb_mod_settings; } @@ -174,6 +181,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); + ::SWGSDRangel::setValue(&sdr_daemon_channel_settings, pJson["SDRDaemonChannelSettings"], "SWGSDRDaemonChannelSettings", "SWGSDRDaemonChannelSettings"); + ::SWGSDRangel::setValue(&ssb_mod_settings, pJson["SSBModSettings"], "SWGSSBModSettings", "SWGSSBModSettings"); ::SWGSDRangel::setValue(&ssb_demod_settings, pJson["SSBDemodSettings"], "SWGSSBDemodSettings", "SWGSSBDemodSettings"); @@ -229,6 +238,9 @@ SWGChannelSettings::asJsonObject() { if((nfm_mod_settings != nullptr) && (nfm_mod_settings->isSet())){ toJsonValue(QString("NFMModSettings"), nfm_mod_settings, obj, QString("SWGNFMModSettings")); } + if((sdr_daemon_channel_settings != nullptr) && (sdr_daemon_channel_settings->isSet())){ + toJsonValue(QString("SDRDaemonChannelSettings"), sdr_daemon_channel_settings, obj, QString("SWGSDRDaemonChannelSettings")); + } if((ssb_mod_settings != nullptr) && (ssb_mod_settings->isSet())){ toJsonValue(QString("SSBModSettings"), ssb_mod_settings, obj, QString("SWGSSBModSettings")); } @@ -341,6 +353,16 @@ SWGChannelSettings::setNfmModSettings(SWGNFMModSettings* nfm_mod_settings) { this->m_nfm_mod_settings_isSet = true; } +SWGSDRDaemonChannelSettings* +SWGChannelSettings::getSdrDaemonChannelSettings() { + return sdr_daemon_channel_settings; +} +void +SWGChannelSettings::setSdrDaemonChannelSettings(SWGSDRDaemonChannelSettings* sdr_daemon_channel_settings) { + this->sdr_daemon_channel_settings = sdr_daemon_channel_settings; + this->m_sdr_daemon_channel_settings_isSet = true; +} + SWGSSBModSettings* SWGChannelSettings::getSsbModSettings() { return ssb_mod_settings; @@ -415,6 +437,7 @@ SWGChannelSettings::isSet(){ if(dsd_demod_settings != nullptr && dsd_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} + if(sdr_daemon_channel_settings != nullptr && sdr_daemon_channel_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_demod_settings != nullptr && ssb_demod_settings->isSet()){ isObjectUpdated = true; break;} if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 600a707a7..9973ab3af 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -29,6 +29,7 @@ #include "SWGDSDDemodSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" +#include "SWGSDRDaemonChannelSettings.h" #include "SWGSSBDemodSettings.h" #include "SWGSSBModSettings.h" #include "SWGUDPSinkSettings.h" @@ -82,6 +83,9 @@ public: SWGNFMModSettings* getNfmModSettings(); void setNfmModSettings(SWGNFMModSettings* nfm_mod_settings); + SWGSDRDaemonChannelSettings* getSdrDaemonChannelSettings(); + void setSdrDaemonChannelSettings(SWGSDRDaemonChannelSettings* sdr_daemon_channel_settings); + SWGSSBModSettings* getSsbModSettings(); void setSsbModSettings(SWGSSBModSettings* ssb_mod_settings); @@ -131,6 +135,9 @@ private: SWGNFMModSettings* nfm_mod_settings; bool m_nfm_mod_settings_isSet; + SWGSDRDaemonChannelSettings* sdr_daemon_channel_settings; + bool m_sdr_daemon_channel_settings_isSet; + SWGSSBModSettings* ssb_mod_settings; bool m_ssb_mod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp index 1a8a6f613..23886faef 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp @@ -29,9 +29,9 @@ SWGDaemonApi::SWGDaemonApi(QString host, QString basePath) { } void -SWGDaemonApi::daemonDataSettingsGet() { +SWGDaemonApi::daemonChannelSettingsGet() { QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/data/settings"); + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/channel/settings"); @@ -49,13 +49,13 @@ SWGDaemonApi::daemonDataSettingsGet() { connect(worker, &SWGHttpRequestWorker::on_execution_finished, this, - &SWGDaemonApi::daemonDataSettingsGetCallback); + &SWGDaemonApi::daemonChannelSettingsGetCallback); worker->execute(&input); } void -SWGDaemonApi::daemonDataSettingsGetCallback(SWGHttpRequestWorker * worker) { +SWGDaemonApi::daemonChannelSettingsGetCallback(SWGHttpRequestWorker * worker) { QString msg; QString error_str = worker->error_str; QNetworkReply::NetworkError error_type = worker->error_type; @@ -69,21 +69,21 @@ SWGDaemonApi::daemonDataSettingsGetCallback(SWGHttpRequestWorker * worker) { QString json(worker->response); - SWGSDRDaemonDataSettings* output = static_cast(create(json, QString("SWGSDRDaemonDataSettings"))); + SWGChannelSettings* output = static_cast(create(json, QString("SWGChannelSettings"))); worker->deleteLater(); if (worker->error_type == QNetworkReply::NoError) { - emit daemonDataSettingsGetSignal(output); + emit daemonChannelSettingsGetSignal(output); } else { - emit daemonDataSettingsGetSignalE(output, error_type, error_str); - emit daemonDataSettingsGetSignalEFull(worker, error_type, error_str); + emit daemonChannelSettingsGetSignalE(output, error_type, error_str); + emit daemonChannelSettingsGetSignalEFull(worker, error_type, error_str); } } void -SWGDaemonApi::daemonDataSettingsPatch(SWGSDRDaemonDataSettings& body) { +SWGDaemonApi::daemonChannelSettingsPatch(SWGChannelSettings& body) { QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/data/settings"); + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/channel/settings"); @@ -104,13 +104,13 @@ SWGDaemonApi::daemonDataSettingsPatch(SWGSDRDaemonDataSettings& body) { connect(worker, &SWGHttpRequestWorker::on_execution_finished, this, - &SWGDaemonApi::daemonDataSettingsPatchCallback); + &SWGDaemonApi::daemonChannelSettingsPatchCallback); worker->execute(&input); } void -SWGDaemonApi::daemonDataSettingsPatchCallback(SWGHttpRequestWorker * worker) { +SWGDaemonApi::daemonChannelSettingsPatchCallback(SWGHttpRequestWorker * worker) { QString msg; QString error_str = worker->error_str; QNetworkReply::NetworkError error_type = worker->error_type; @@ -124,21 +124,21 @@ SWGDaemonApi::daemonDataSettingsPatchCallback(SWGHttpRequestWorker * worker) { QString json(worker->response); - SWGSDRDaemonDataSettings* output = static_cast(create(json, QString("SWGSDRDaemonDataSettings"))); + SWGChannelSettings* output = static_cast(create(json, QString("SWGChannelSettings"))); worker->deleteLater(); if (worker->error_type == QNetworkReply::NoError) { - emit daemonDataSettingsPatchSignal(output); + emit daemonChannelSettingsPatchSignal(output); } else { - emit daemonDataSettingsPatchSignalE(output, error_type, error_str); - emit daemonDataSettingsPatchSignalEFull(worker, error_type, error_str); + emit daemonChannelSettingsPatchSignalE(output, error_type, error_str); + emit daemonChannelSettingsPatchSignalEFull(worker, error_type, error_str); } } void -SWGDaemonApi::daemonDataSettingsPut(SWGSDRDaemonDataSettings& body) { +SWGDaemonApi::daemonChannelSettingsPut(SWGChannelSettings& body) { QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/data/settings"); + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/channel/settings"); @@ -159,13 +159,13 @@ SWGDaemonApi::daemonDataSettingsPut(SWGSDRDaemonDataSettings& body) { connect(worker, &SWGHttpRequestWorker::on_execution_finished, this, - &SWGDaemonApi::daemonDataSettingsPutCallback); + &SWGDaemonApi::daemonChannelSettingsPutCallback); worker->execute(&input); } void -SWGDaemonApi::daemonDataSettingsPutCallback(SWGHttpRequestWorker * worker) { +SWGDaemonApi::daemonChannelSettingsPutCallback(SWGHttpRequestWorker * worker) { QString msg; QString error_str = worker->error_str; QNetworkReply::NetworkError error_type = worker->error_type; @@ -179,14 +179,14 @@ SWGDaemonApi::daemonDataSettingsPutCallback(SWGHttpRequestWorker * worker) { QString json(worker->response); - SWGSDRDaemonDataSettings* output = static_cast(create(json, QString("SWGSDRDaemonDataSettings"))); + SWGChannelSettings* output = static_cast(create(json, QString("SWGChannelSettings"))); worker->deleteLater(); if (worker->error_type == QNetworkReply::NoError) { - emit daemonDataSettingsPutSignal(output); + emit daemonChannelSettingsPutSignal(output); } else { - emit daemonDataSettingsPutSignalE(output, error_type, error_str); - emit daemonDataSettingsPutSignalEFull(worker, error_type, error_str); + emit daemonChannelSettingsPutSignalE(output, error_type, error_str); + emit daemonChannelSettingsPutSignalEFull(worker, error_type, error_str); } } diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h index 19cbba2e6..74b7083f6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h @@ -15,13 +15,13 @@ #include "SWGHttpRequest.h" +#include "SWGChannelSettings.h" #include "SWGDaemonSummaryResponse.h" #include "SWGDeviceReport.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGErrorResponse.h" #include "SWGLoggingInfo.h" -#include "SWGSDRDaemonDataSettings.h" #include @@ -39,9 +39,9 @@ public: QString basePath; QMap defaultHeaders; - void daemonDataSettingsGet(); - void daemonDataSettingsPatch(SWGSDRDaemonDataSettings& body); - void daemonDataSettingsPut(SWGSDRDaemonDataSettings& body); + void daemonChannelSettingsGet(); + void daemonChannelSettingsPatch(SWGChannelSettings& body); + void daemonChannelSettingsPut(SWGChannelSettings& body); void daemonDeviceReportGet(); void daemonDeviceSettingsGet(); void daemonDeviceSettingsPatch(SWGDeviceSettings& body); @@ -54,9 +54,9 @@ public: void daemonRunPost(); private: - void daemonDataSettingsGetCallback (SWGHttpRequestWorker * worker); - void daemonDataSettingsPatchCallback (SWGHttpRequestWorker * worker); - void daemonDataSettingsPutCallback (SWGHttpRequestWorker * worker); + void daemonChannelSettingsGetCallback (SWGHttpRequestWorker * worker); + void daemonChannelSettingsPatchCallback (SWGHttpRequestWorker * worker); + void daemonChannelSettingsPutCallback (SWGHttpRequestWorker * worker); void daemonDeviceReportGetCallback (SWGHttpRequestWorker * worker); void daemonDeviceSettingsGetCallback (SWGHttpRequestWorker * worker); void daemonDeviceSettingsPatchCallback (SWGHttpRequestWorker * worker); @@ -69,9 +69,9 @@ private: void daemonRunPostCallback (SWGHttpRequestWorker * worker); signals: - void daemonDataSettingsGetSignal(SWGSDRDaemonDataSettings* summary); - void daemonDataSettingsPatchSignal(SWGSDRDaemonDataSettings* summary); - void daemonDataSettingsPutSignal(SWGSDRDaemonDataSettings* summary); + void daemonChannelSettingsGetSignal(SWGChannelSettings* summary); + void daemonChannelSettingsPatchSignal(SWGChannelSettings* summary); + void daemonChannelSettingsPutSignal(SWGChannelSettings* summary); void daemonDeviceReportGetSignal(SWGDeviceReport* summary); void daemonDeviceSettingsGetSignal(SWGDeviceSettings* summary); void daemonDeviceSettingsPatchSignal(SWGDeviceSettings* summary); @@ -83,9 +83,9 @@ signals: void daemonRunGetSignal(SWGDeviceState* summary); void daemonRunPostSignal(SWGDeviceState* summary); - void daemonDataSettingsGetSignalE(SWGSDRDaemonDataSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDataSettingsPatchSignalE(SWGSDRDaemonDataSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDataSettingsPutSignalE(SWGSDRDaemonDataSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonChannelSettingsGetSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonChannelSettingsPatchSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonChannelSettingsPutSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonDeviceReportGetSignalE(SWGDeviceReport* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonDeviceSettingsGetSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonDeviceSettingsPatchSignalE(SWGDeviceSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); @@ -97,9 +97,9 @@ signals: void daemonRunGetSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonRunPostSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDataSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDataSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void daemonDataSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonChannelSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonChannelSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonChannelSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonDeviceReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonDeviceSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonDeviceSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 915d4b0c9..30a8a0985 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -89,7 +89,7 @@ #include "SWGRDSReport_altFrequencies.h" #include "SWGRtlSdrReport.h" #include "SWGRtlSdrSettings.h" -#include "SWGSDRDaemonDataSettings.h" +#include "SWGSDRDaemonChannelSettings.h" #include "SWGSDRPlayReport.h" #include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSinkReport.h" @@ -341,8 +341,8 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } - if(QString("SWGSDRDaemonDataSettings").compare(type) == 0) { - return new SWGSDRDaemonDataSettings(); + if(QString("SWGSDRDaemonChannelSettings").compare(type) == 0) { + return new SWGSDRDaemonChannelSettings(); } if(QString("SWGSDRPlayReport").compare(type) == 0) { return new SWGSDRPlayReport(); diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.cpp similarity index 81% rename from swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.cpp rename to swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.cpp index 1a8925524..1d84efbac 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.cpp @@ -11,7 +11,7 @@ */ -#include "SWGSDRDaemonDataSettings.h" +#include "SWGSDRDaemonChannelSettings.h" #include "SWGHelpers.h" @@ -22,12 +22,12 @@ namespace SWGSDRangel { -SWGSDRDaemonDataSettings::SWGSDRDaemonDataSettings(QString* json) { +SWGSDRDaemonChannelSettings::SWGSDRDaemonChannelSettings(QString* json) { init(); this->fromJson(*json); } -SWGSDRDaemonDataSettings::SWGSDRDaemonDataSettings() { +SWGSDRDaemonChannelSettings::SWGSDRDaemonChannelSettings() { nb_fec_blocks = 0; m_nb_fec_blocks_isSet = false; data_address = nullptr; @@ -38,12 +38,12 @@ SWGSDRDaemonDataSettings::SWGSDRDaemonDataSettings() { m_tx_delay_isSet = false; } -SWGSDRDaemonDataSettings::~SWGSDRDaemonDataSettings() { +SWGSDRDaemonChannelSettings::~SWGSDRDaemonChannelSettings() { this->cleanup(); } void -SWGSDRDaemonDataSettings::init() { +SWGSDRDaemonChannelSettings::init() { nb_fec_blocks = 0; m_nb_fec_blocks_isSet = false; data_address = new QString(""); @@ -55,7 +55,7 @@ SWGSDRDaemonDataSettings::init() { } void -SWGSDRDaemonDataSettings::cleanup() { +SWGSDRDaemonChannelSettings::cleanup() { if(data_address != nullptr) { delete data_address; @@ -64,8 +64,8 @@ SWGSDRDaemonDataSettings::cleanup() { } -SWGSDRDaemonDataSettings* -SWGSDRDaemonDataSettings::fromJson(QString &json) { +SWGSDRDaemonChannelSettings* +SWGSDRDaemonChannelSettings::fromJson(QString &json) { QByteArray array (json.toStdString().c_str()); QJsonDocument doc = QJsonDocument::fromJson(array); QJsonObject jsonObject = doc.object(); @@ -74,7 +74,7 @@ SWGSDRDaemonDataSettings::fromJson(QString &json) { } void -SWGSDRDaemonDataSettings::fromJsonObject(QJsonObject &pJson) { +SWGSDRDaemonChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); @@ -86,7 +86,7 @@ SWGSDRDaemonDataSettings::fromJsonObject(QJsonObject &pJson) { } QString -SWGSDRDaemonDataSettings::asJson () +SWGSDRDaemonChannelSettings::asJson () { QJsonObject* obj = this->asJsonObject(); @@ -97,7 +97,7 @@ SWGSDRDaemonDataSettings::asJson () } QJsonObject* -SWGSDRDaemonDataSettings::asJsonObject() { +SWGSDRDaemonChannelSettings::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(m_nb_fec_blocks_isSet){ obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); @@ -116,48 +116,48 @@ SWGSDRDaemonDataSettings::asJsonObject() { } qint32 -SWGSDRDaemonDataSettings::getNbFecBlocks() { +SWGSDRDaemonChannelSettings::getNbFecBlocks() { return nb_fec_blocks; } void -SWGSDRDaemonDataSettings::setNbFecBlocks(qint32 nb_fec_blocks) { +SWGSDRDaemonChannelSettings::setNbFecBlocks(qint32 nb_fec_blocks) { this->nb_fec_blocks = nb_fec_blocks; this->m_nb_fec_blocks_isSet = true; } QString* -SWGSDRDaemonDataSettings::getDataAddress() { +SWGSDRDaemonChannelSettings::getDataAddress() { return data_address; } void -SWGSDRDaemonDataSettings::setDataAddress(QString* data_address) { +SWGSDRDaemonChannelSettings::setDataAddress(QString* data_address) { this->data_address = data_address; this->m_data_address_isSet = true; } qint32 -SWGSDRDaemonDataSettings::getDataPort() { +SWGSDRDaemonChannelSettings::getDataPort() { return data_port; } void -SWGSDRDaemonDataSettings::setDataPort(qint32 data_port) { +SWGSDRDaemonChannelSettings::setDataPort(qint32 data_port) { this->data_port = data_port; this->m_data_port_isSet = true; } qint32 -SWGSDRDaemonDataSettings::getTxDelay() { +SWGSDRDaemonChannelSettings::getTxDelay() { return tx_delay; } void -SWGSDRDaemonDataSettings::setTxDelay(qint32 tx_delay) { +SWGSDRDaemonChannelSettings::setTxDelay(qint32 tx_delay) { this->tx_delay = tx_delay; this->m_tx_delay_isSet = true; } bool -SWGSDRDaemonDataSettings::isSet(){ +SWGSDRDaemonChannelSettings::isSet(){ bool isObjectUpdated = false; do{ if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.h similarity index 85% rename from swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.h rename to swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.h index a0edbaf28..62106af4f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonDataSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.h @@ -11,13 +11,13 @@ */ /* - * SWGSDRDaemonDataSettings.h + * SWGSDRDaemonChannelSettings.h * * Data handling details for SDRDaemon */ -#ifndef SWGSDRDaemonDataSettings_H_ -#define SWGSDRDaemonDataSettings_H_ +#ifndef SWGSDRDaemonChannelSettings_H_ +#define SWGSDRDaemonChannelSettings_H_ #include @@ -29,18 +29,18 @@ namespace SWGSDRangel { -class SWG_API SWGSDRDaemonDataSettings: public SWGObject { +class SWG_API SWGSDRDaemonChannelSettings: public SWGObject { public: - SWGSDRDaemonDataSettings(); - SWGSDRDaemonDataSettings(QString* json); - virtual ~SWGSDRDaemonDataSettings(); + SWGSDRDaemonChannelSettings(); + SWGSDRDaemonChannelSettings(QString* json); + virtual ~SWGSDRDaemonChannelSettings(); void init(); void cleanup(); virtual QString asJson () override; virtual QJsonObject* asJsonObject() override; virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGSDRDaemonDataSettings* fromJson(QString &jsonString) override; + virtual SWGSDRDaemonChannelSettings* fromJson(QString &jsonString) override; qint32 getNbFecBlocks(); void setNbFecBlocks(qint32 nb_fec_blocks); @@ -74,4 +74,4 @@ private: } -#endif /* SWGSDRDaemonDataSettings_H_ */ +#endif /* SWGSDRDaemonChannelSettings_H_ */ From 5bf657cd9a8c4de519a0fe115e0b23fd0b81bcb8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 23 Aug 2018 17:38:25 +0200 Subject: [PATCH 652/956] SDRdaemon: changed generic channel settings to sink channel settings --- sdrbase/resources/res.qrc | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 8 ++-- ...Channel.yaml => SDRDaemonChannelSink.yaml} | 2 +- .../resources/webapi/doc/swagger/swagger.yaml | 4 +- sdrbase/webapi/webapirequestmapper.cpp | 20 ++++++---- sdrdaemon/webapi/webapirequestmapper.cpp | 20 ++++++---- ...Channel.yaml => SDRDaemonChannelSink.yaml} | 2 +- swagger/sdrangel/api/swagger/swagger.yaml | 4 +- swagger/sdrangel/code/html2/index.html | 8 ++-- .../code/qt5/client/SWGChannelSettings.cpp | 32 +++++++-------- .../code/qt5/client/SWGChannelSettings.h | 10 ++--- .../code/qt5/client/SWGModelFactory.h | 6 +-- ...pp => SWGSDRDaemonChannelSinkSettings.cpp} | 40 +++++++++---------- ...gs.h => SWGSDRDaemonChannelSinkSettings.h} | 18 ++++----- 14 files changed, 94 insertions(+), 82 deletions(-) rename sdrbase/resources/webapi/doc/swagger/include/{SDRDaemonChannel.yaml => SDRDaemonChannelSink.yaml} (93%) rename swagger/sdrangel/api/swagger/include/{SDRDaemonChannel.yaml => SDRDaemonChannelSink.yaml} (93%) rename swagger/sdrangel/code/qt5/client/{SWGSDRDaemonChannelSettings.cpp => SWGSDRDaemonChannelSinkSettings.cpp} (79%) rename swagger/sdrangel/code/qt5/client/{SWGSDRDaemonChannelSettings.h => SWGSDRDaemonChannelSinkSettings.h} (84%) diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index f5f346932..52e87e0dc 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -21,7 +21,7 @@ webapi/doc/swagger/include/Perseus.yaml webapi/doc/swagger/include/PlutoSdr.yaml webapi/doc/swagger/include/RtlSdr.yaml - webapi/doc/swagger/include/SDRDaemonChannel.yaml + webapi/doc/swagger/include/SDRDaemonChannelSink.yaml webapi/doc/swagger/include/SDRDaemonSource.yaml webapi/doc/swagger/include/SDRDaemonSink.yaml webapi/doc/swagger/include/SDRPlay.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 20c65c84f..7d76d093b 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1467,8 +1467,8 @@ margin-bottom: 20px; "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" }, - "SDRDaemonChannelSettings" : { - "$ref" : "#/definitions/SDRDaemonChannelSettings" + "SDRDaemonChannelSinkSettings" : { + "$ref" : "#/definitions/SDRDaemonChannelSinkSettings" }, "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" @@ -3146,7 +3146,7 @@ margin-bottom: 20px; }, "description" : "RTLSDR" }; - defs.SDRDaemonChannelSettings = { + defs.SDRDaemonChannelSinkSettings = { "properties" : { "nbFECBlocks" : { "type" : "integer", @@ -28215,7 +28215,7 @@ except ApiException as e:
    - Generated 2018-08-23T14:53:21.934+02:00 + Generated 2018-08-23T17:21:24.907+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannel.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSink.yaml similarity index 93% rename from sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannel.yaml rename to sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSink.yaml index 332732524..63c34423a 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannel.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSink.yaml @@ -1,4 +1,4 @@ -SDRDaemonChannelSettings: +SDRDaemonChannelSinkSettings: description: "Data handling details for SDRDaemon" properties: nbFECBlocks: diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 10ecf2699..4ef531051 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -2194,8 +2194,8 @@ definitions: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" - SDRDaemonChannelSettings: - $ref: "/doc/swagger/include/SDRDaemonChannel.yaml#/SDRDaemonChannelSettings" + SDRDaemonChannelSinkSettings: + $ref: "/doc/swagger/include/SDRDaemonChannelSink.yaml#/SDRDaemonChannelSinkSettings" SSBModSettings: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 96a2a31a5..5f08de819 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2146,13 +2146,19 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } - else if (*channelType == "SDRDaemonChannel") + else if (*channelType == "SDRDaemonChannelSink") { - QJsonObject sdrDaemonChannelSettingsJsonObject = jsonObject["SDRDaemonChannelSettings"].toObject(); - channelSettingsKeys = sdrDaemonChannelSettingsJsonObject.keys(); - channelSettings.setSdrDaemonChannelSettings(new SWGSDRangel::SWGSDRDaemonChannelSettings()); - channelSettings.getSdrDaemonChannelSettings()->fromJsonObject(sdrDaemonChannelSettingsJsonObject); - return true; + if (channelSettings.getTx() == 0) + { + QJsonObject sdrDaemonChannelSinkSettingsJsonObject = jsonObject["SDRDaemonChannelSinkSettings"].toObject(); + channelSettingsKeys = sdrDaemonChannelSinkSettingsJsonObject.keys(); + channelSettings.setSdrDaemonChannelSinkSettings(new SWGSDRangel::SWGSDRDaemonChannelSinkSettings()); + channelSettings.getSdrDaemonChannelSinkSettings()->fromJsonObject(sdrDaemonChannelSinkSettingsJsonObject); + return true; + } + else { + return false; + } } else if (*channelType == "SSBDemod") { @@ -2392,7 +2398,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setDsdDemodSettings(0); channelSettings.setNfmDemodSettings(0); channelSettings.setNfmModSettings(0); - channelSettings.setSdrDaemonChannelSettings(0); + channelSettings.setSdrDaemonChannelSinkSettings(0); channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); channelSettings.setUdpSinkSettings(0); diff --git a/sdrdaemon/webapi/webapirequestmapper.cpp b/sdrdaemon/webapi/webapirequestmapper.cpp index ad4c4df62..4d3def6ae 100644 --- a/sdrdaemon/webapi/webapirequestmapper.cpp +++ b/sdrdaemon/webapi/webapirequestmapper.cpp @@ -545,13 +545,19 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } - else if (*channelType == "SDRDaemonChannel") + else if (*channelType == "SDRDaemonChannelSink") { - QJsonObject sdrDaemonChannelSettingsJsonObject = jsonObject["SDRDaemonChannelSettings"].toObject(); - channelSettingsKeys = sdrDaemonChannelSettingsJsonObject.keys(); - channelSettings.setSdrDaemonChannelSettings(new SWGSDRangel::SWGSDRDaemonChannelSettings()); - channelSettings.getSdrDaemonChannelSettings()->fromJsonObject(sdrDaemonChannelSettingsJsonObject); - return true; + if (channelSettings.getTx() == 0) + { + QJsonObject sdrDaemonChannelSinkSettingsJsonObject = jsonObject["SDRDaemonChannelSinkSettings"].toObject(); + channelSettingsKeys = sdrDaemonChannelSinkSettingsJsonObject.keys(); + channelSettings.setSdrDaemonChannelSinkSettings(new SWGSDRangel::SWGSDRDaemonChannelSinkSettings()); + channelSettings.getSdrDaemonChannelSinkSettings()->fromJsonObject(sdrDaemonChannelSinkSettingsJsonObject); + return true; + } + else { + return false; + } } else if (*channelType == "SSBDemod") { @@ -985,7 +991,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setDsdDemodSettings(0); channelSettings.setNfmDemodSettings(0); channelSettings.setNfmModSettings(0); - channelSettings.setSdrDaemonChannelSettings(0); + channelSettings.setSdrDaemonChannelSinkSettings(0); channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); channelSettings.setUdpSinkSettings(0); diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonChannel.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSink.yaml similarity index 93% rename from swagger/sdrangel/api/swagger/include/SDRDaemonChannel.yaml rename to swagger/sdrangel/api/swagger/include/SDRDaemonChannelSink.yaml index 332732524..63c34423a 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonChannel.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSink.yaml @@ -1,4 +1,4 @@ -SDRDaemonChannelSettings: +SDRDaemonChannelSinkSettings: description: "Data handling details for SDRDaemon" properties: nbFECBlocks: diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 5f5147cf8..76bd46b24 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -2194,8 +2194,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" - SDRDaemonChannelSettings: - $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannel.yaml#/SDRDaemonChannelSettings" + SDRDaemonChannelSinkSettings: + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannelSink.yaml#/SDRDaemonChannelSinkSettings" SSBModSettings: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 20c65c84f..7d76d093b 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1467,8 +1467,8 @@ margin-bottom: 20px; "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" }, - "SDRDaemonChannelSettings" : { - "$ref" : "#/definitions/SDRDaemonChannelSettings" + "SDRDaemonChannelSinkSettings" : { + "$ref" : "#/definitions/SDRDaemonChannelSinkSettings" }, "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" @@ -3146,7 +3146,7 @@ margin-bottom: 20px; }, "description" : "RTLSDR" }; - defs.SDRDaemonChannelSettings = { + defs.SDRDaemonChannelSinkSettings = { "properties" : { "nbFECBlocks" : { "type" : "integer", @@ -28215,7 +28215,7 @@ except ApiException as e:
    - Generated 2018-08-23T14:53:21.934+02:00 + Generated 2018-08-23T17:21:24.907+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 1b3689f77..751c40280 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -46,8 +46,8 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; m_nfm_mod_settings_isSet = false; - sdr_daemon_channel_settings = nullptr; - m_sdr_daemon_channel_settings_isSet = false; + sdr_daemon_channel_sink_settings = nullptr; + m_sdr_daemon_channel_sink_settings_isSet = false; ssb_mod_settings = nullptr; m_ssb_mod_settings_isSet = false; ssb_demod_settings = nullptr; @@ -86,8 +86,8 @@ SWGChannelSettings::init() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); m_nfm_mod_settings_isSet = false; - sdr_daemon_channel_settings = new SWGSDRDaemonChannelSettings(); - m_sdr_daemon_channel_settings_isSet = false; + sdr_daemon_channel_sink_settings = new SWGSDRDaemonChannelSinkSettings(); + m_sdr_daemon_channel_sink_settings_isSet = false; ssb_mod_settings = new SWGSSBModSettings(); m_ssb_mod_settings_isSet = false; ssb_demod_settings = new SWGSSBDemodSettings(); @@ -129,8 +129,8 @@ SWGChannelSettings::cleanup() { if(nfm_mod_settings != nullptr) { delete nfm_mod_settings; } - if(sdr_daemon_channel_settings != nullptr) { - delete sdr_daemon_channel_settings; + if(sdr_daemon_channel_sink_settings != nullptr) { + delete sdr_daemon_channel_sink_settings; } if(ssb_mod_settings != nullptr) { delete ssb_mod_settings; @@ -181,7 +181,7 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); - ::SWGSDRangel::setValue(&sdr_daemon_channel_settings, pJson["SDRDaemonChannelSettings"], "SWGSDRDaemonChannelSettings", "SWGSDRDaemonChannelSettings"); + ::SWGSDRangel::setValue(&sdr_daemon_channel_sink_settings, pJson["SDRDaemonChannelSinkSettings"], "SWGSDRDaemonChannelSinkSettings", "SWGSDRDaemonChannelSinkSettings"); ::SWGSDRangel::setValue(&ssb_mod_settings, pJson["SSBModSettings"], "SWGSSBModSettings", "SWGSSBModSettings"); @@ -238,8 +238,8 @@ SWGChannelSettings::asJsonObject() { if((nfm_mod_settings != nullptr) && (nfm_mod_settings->isSet())){ toJsonValue(QString("NFMModSettings"), nfm_mod_settings, obj, QString("SWGNFMModSettings")); } - if((sdr_daemon_channel_settings != nullptr) && (sdr_daemon_channel_settings->isSet())){ - toJsonValue(QString("SDRDaemonChannelSettings"), sdr_daemon_channel_settings, obj, QString("SWGSDRDaemonChannelSettings")); + if((sdr_daemon_channel_sink_settings != nullptr) && (sdr_daemon_channel_sink_settings->isSet())){ + toJsonValue(QString("SDRDaemonChannelSinkSettings"), sdr_daemon_channel_sink_settings, obj, QString("SWGSDRDaemonChannelSinkSettings")); } if((ssb_mod_settings != nullptr) && (ssb_mod_settings->isSet())){ toJsonValue(QString("SSBModSettings"), ssb_mod_settings, obj, QString("SWGSSBModSettings")); @@ -353,14 +353,14 @@ SWGChannelSettings::setNfmModSettings(SWGNFMModSettings* nfm_mod_settings) { this->m_nfm_mod_settings_isSet = true; } -SWGSDRDaemonChannelSettings* -SWGChannelSettings::getSdrDaemonChannelSettings() { - return sdr_daemon_channel_settings; +SWGSDRDaemonChannelSinkSettings* +SWGChannelSettings::getSdrDaemonChannelSinkSettings() { + return sdr_daemon_channel_sink_settings; } void -SWGChannelSettings::setSdrDaemonChannelSettings(SWGSDRDaemonChannelSettings* sdr_daemon_channel_settings) { - this->sdr_daemon_channel_settings = sdr_daemon_channel_settings; - this->m_sdr_daemon_channel_settings_isSet = true; +SWGChannelSettings::setSdrDaemonChannelSinkSettings(SWGSDRDaemonChannelSinkSettings* sdr_daemon_channel_sink_settings) { + this->sdr_daemon_channel_sink_settings = sdr_daemon_channel_sink_settings; + this->m_sdr_daemon_channel_sink_settings_isSet = true; } SWGSSBModSettings* @@ -437,7 +437,7 @@ SWGChannelSettings::isSet(){ if(dsd_demod_settings != nullptr && dsd_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} - if(sdr_daemon_channel_settings != nullptr && sdr_daemon_channel_settings->isSet()){ isObjectUpdated = true; break;} + if(sdr_daemon_channel_sink_settings != nullptr && sdr_daemon_channel_sink_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_demod_settings != nullptr && ssb_demod_settings->isSet()){ isObjectUpdated = true; break;} if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 9973ab3af..9742e650f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -29,7 +29,7 @@ #include "SWGDSDDemodSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" -#include "SWGSDRDaemonChannelSettings.h" +#include "SWGSDRDaemonChannelSinkSettings.h" #include "SWGSSBDemodSettings.h" #include "SWGSSBModSettings.h" #include "SWGUDPSinkSettings.h" @@ -83,8 +83,8 @@ public: SWGNFMModSettings* getNfmModSettings(); void setNfmModSettings(SWGNFMModSettings* nfm_mod_settings); - SWGSDRDaemonChannelSettings* getSdrDaemonChannelSettings(); - void setSdrDaemonChannelSettings(SWGSDRDaemonChannelSettings* sdr_daemon_channel_settings); + SWGSDRDaemonChannelSinkSettings* getSdrDaemonChannelSinkSettings(); + void setSdrDaemonChannelSinkSettings(SWGSDRDaemonChannelSinkSettings* sdr_daemon_channel_sink_settings); SWGSSBModSettings* getSsbModSettings(); void setSsbModSettings(SWGSSBModSettings* ssb_mod_settings); @@ -135,8 +135,8 @@ private: SWGNFMModSettings* nfm_mod_settings; bool m_nfm_mod_settings_isSet; - SWGSDRDaemonChannelSettings* sdr_daemon_channel_settings; - bool m_sdr_daemon_channel_settings_isSet; + SWGSDRDaemonChannelSinkSettings* sdr_daemon_channel_sink_settings; + bool m_sdr_daemon_channel_sink_settings_isSet; SWGSSBModSettings* ssb_mod_settings; bool m_ssb_mod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 30a8a0985..05f3854fb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -89,7 +89,7 @@ #include "SWGRDSReport_altFrequencies.h" #include "SWGRtlSdrReport.h" #include "SWGRtlSdrSettings.h" -#include "SWGSDRDaemonChannelSettings.h" +#include "SWGSDRDaemonChannelSinkSettings.h" #include "SWGSDRPlayReport.h" #include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSinkReport.h" @@ -341,8 +341,8 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } - if(QString("SWGSDRDaemonChannelSettings").compare(type) == 0) { - return new SWGSDRDaemonChannelSettings(); + if(QString("SWGSDRDaemonChannelSinkSettings").compare(type) == 0) { + return new SWGSDRDaemonChannelSinkSettings(); } if(QString("SWGSDRPlayReport").compare(type) == 0) { return new SWGSDRPlayReport(); diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.cpp similarity index 79% rename from swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.cpp rename to swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.cpp index 1d84efbac..194d6a747 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.cpp @@ -11,7 +11,7 @@ */ -#include "SWGSDRDaemonChannelSettings.h" +#include "SWGSDRDaemonChannelSinkSettings.h" #include "SWGHelpers.h" @@ -22,12 +22,12 @@ namespace SWGSDRangel { -SWGSDRDaemonChannelSettings::SWGSDRDaemonChannelSettings(QString* json) { +SWGSDRDaemonChannelSinkSettings::SWGSDRDaemonChannelSinkSettings(QString* json) { init(); this->fromJson(*json); } -SWGSDRDaemonChannelSettings::SWGSDRDaemonChannelSettings() { +SWGSDRDaemonChannelSinkSettings::SWGSDRDaemonChannelSinkSettings() { nb_fec_blocks = 0; m_nb_fec_blocks_isSet = false; data_address = nullptr; @@ -38,12 +38,12 @@ SWGSDRDaemonChannelSettings::SWGSDRDaemonChannelSettings() { m_tx_delay_isSet = false; } -SWGSDRDaemonChannelSettings::~SWGSDRDaemonChannelSettings() { +SWGSDRDaemonChannelSinkSettings::~SWGSDRDaemonChannelSinkSettings() { this->cleanup(); } void -SWGSDRDaemonChannelSettings::init() { +SWGSDRDaemonChannelSinkSettings::init() { nb_fec_blocks = 0; m_nb_fec_blocks_isSet = false; data_address = new QString(""); @@ -55,7 +55,7 @@ SWGSDRDaemonChannelSettings::init() { } void -SWGSDRDaemonChannelSettings::cleanup() { +SWGSDRDaemonChannelSinkSettings::cleanup() { if(data_address != nullptr) { delete data_address; @@ -64,8 +64,8 @@ SWGSDRDaemonChannelSettings::cleanup() { } -SWGSDRDaemonChannelSettings* -SWGSDRDaemonChannelSettings::fromJson(QString &json) { +SWGSDRDaemonChannelSinkSettings* +SWGSDRDaemonChannelSinkSettings::fromJson(QString &json) { QByteArray array (json.toStdString().c_str()); QJsonDocument doc = QJsonDocument::fromJson(array); QJsonObject jsonObject = doc.object(); @@ -74,7 +74,7 @@ SWGSDRDaemonChannelSettings::fromJson(QString &json) { } void -SWGSDRDaemonChannelSettings::fromJsonObject(QJsonObject &pJson) { +SWGSDRDaemonChannelSinkSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); @@ -86,7 +86,7 @@ SWGSDRDaemonChannelSettings::fromJsonObject(QJsonObject &pJson) { } QString -SWGSDRDaemonChannelSettings::asJson () +SWGSDRDaemonChannelSinkSettings::asJson () { QJsonObject* obj = this->asJsonObject(); @@ -97,7 +97,7 @@ SWGSDRDaemonChannelSettings::asJson () } QJsonObject* -SWGSDRDaemonChannelSettings::asJsonObject() { +SWGSDRDaemonChannelSinkSettings::asJsonObject() { QJsonObject* obj = new QJsonObject(); if(m_nb_fec_blocks_isSet){ obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); @@ -116,48 +116,48 @@ SWGSDRDaemonChannelSettings::asJsonObject() { } qint32 -SWGSDRDaemonChannelSettings::getNbFecBlocks() { +SWGSDRDaemonChannelSinkSettings::getNbFecBlocks() { return nb_fec_blocks; } void -SWGSDRDaemonChannelSettings::setNbFecBlocks(qint32 nb_fec_blocks) { +SWGSDRDaemonChannelSinkSettings::setNbFecBlocks(qint32 nb_fec_blocks) { this->nb_fec_blocks = nb_fec_blocks; this->m_nb_fec_blocks_isSet = true; } QString* -SWGSDRDaemonChannelSettings::getDataAddress() { +SWGSDRDaemonChannelSinkSettings::getDataAddress() { return data_address; } void -SWGSDRDaemonChannelSettings::setDataAddress(QString* data_address) { +SWGSDRDaemonChannelSinkSettings::setDataAddress(QString* data_address) { this->data_address = data_address; this->m_data_address_isSet = true; } qint32 -SWGSDRDaemonChannelSettings::getDataPort() { +SWGSDRDaemonChannelSinkSettings::getDataPort() { return data_port; } void -SWGSDRDaemonChannelSettings::setDataPort(qint32 data_port) { +SWGSDRDaemonChannelSinkSettings::setDataPort(qint32 data_port) { this->data_port = data_port; this->m_data_port_isSet = true; } qint32 -SWGSDRDaemonChannelSettings::getTxDelay() { +SWGSDRDaemonChannelSinkSettings::getTxDelay() { return tx_delay; } void -SWGSDRDaemonChannelSettings::setTxDelay(qint32 tx_delay) { +SWGSDRDaemonChannelSinkSettings::setTxDelay(qint32 tx_delay) { this->tx_delay = tx_delay; this->m_tx_delay_isSet = true; } bool -SWGSDRDaemonChannelSettings::isSet(){ +SWGSDRDaemonChannelSinkSettings::isSet(){ bool isObjectUpdated = false; do{ if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.h similarity index 84% rename from swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.h rename to swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.h index 62106af4f..5b7d56fb2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.h @@ -11,13 +11,13 @@ */ /* - * SWGSDRDaemonChannelSettings.h + * SWGSDRDaemonChannelSinkSettings.h * * Data handling details for SDRDaemon */ -#ifndef SWGSDRDaemonChannelSettings_H_ -#define SWGSDRDaemonChannelSettings_H_ +#ifndef SWGSDRDaemonChannelSinkSettings_H_ +#define SWGSDRDaemonChannelSinkSettings_H_ #include @@ -29,18 +29,18 @@ namespace SWGSDRangel { -class SWG_API SWGSDRDaemonChannelSettings: public SWGObject { +class SWG_API SWGSDRDaemonChannelSinkSettings: public SWGObject { public: - SWGSDRDaemonChannelSettings(); - SWGSDRDaemonChannelSettings(QString* json); - virtual ~SWGSDRDaemonChannelSettings(); + SWGSDRDaemonChannelSinkSettings(); + SWGSDRDaemonChannelSinkSettings(QString* json); + virtual ~SWGSDRDaemonChannelSinkSettings(); void init(); void cleanup(); virtual QString asJson () override; virtual QJsonObject* asJsonObject() override; virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGSDRDaemonChannelSettings* fromJson(QString &jsonString) override; + virtual SWGSDRDaemonChannelSinkSettings* fromJson(QString &jsonString) override; qint32 getNbFecBlocks(); void setNbFecBlocks(qint32 nb_fec_blocks); @@ -74,4 +74,4 @@ private: } -#endif /* SWGSDRDaemonChannelSettings_H_ */ +#endif /* SWGSDRDaemonChannelSinkSettings_H_ */ From 4f43e511783c59e689117896c13edb2366556b14 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 23 Aug 2018 18:17:15 +0200 Subject: [PATCH 653/956] SDRDaemon: Web API: implemented channel sink settings getter and setter --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 62 +++++++++++++ sdrdaemon/channel/sdrdaemonchannelsink.h | 11 +++ sdrdaemon/webapi/webapiadapterdaemon.cpp | 102 ++++++++++++++++++++- 3 files changed, 171 insertions(+), 4 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 62d6c5d8d..730572525 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -25,6 +25,8 @@ #include #include +#include "SWGChannelSettings.h" + #include "util/simpleserializer.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" @@ -312,3 +314,63 @@ void SDRDaemonChannelSink::applySettings(const SDRDaemonChannelSinkSettings& set m_settings = settings; } + +int SDRDaemonChannelSink::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSinkSettings(new SWGSDRangel::SWGSDRDaemonChannelSinkSettings()); + response.getSdrDaemonChannelSinkSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int SDRDaemonChannelSink::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + SDRDaemonChannelSinkSettings settings = m_settings; + + if (channelSettingsKeys.contains("nbFECBlocks")) { + settings.m_nbFECBlocks = response.getSdrDaemonChannelSinkSettings()->getNbFecBlocks(); + } + if (channelSettingsKeys.contains("txDelay")) { + settings.m_txDelay = response.getSdrDaemonChannelSinkSettings()->getTxDelay(); + } + if (channelSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonChannelSinkSettings()->getDataAddress(); + } + if (channelSettingsKeys.contains("dataPort")) { + settings.m_dataPort = response.getSdrDaemonChannelSinkSettings()->getDataPort(); + } + + MsgConfigureSDRDaemonChannelSink *msg = MsgConfigureSDRDaemonChannelSink::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("SDRDaemonChannelSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRDaemonChannelSink *msgToGUI = MsgConfigureSDRDaemonChannelSink::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +void SDRDaemonChannelSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSinkSettings& settings) +{ + response.getSdrDaemonChannelSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); + response.getSdrDaemonChannelSinkSettings()->setTxDelay(settings.m_txDelay); + + if (response.getSdrDaemonChannelSinkSettings()->getDataAddress()) { + *response.getSdrDaemonChannelSinkSettings()->getDataAddress() = settings.m_dataAddress; + } else { + response.getSdrDaemonChannelSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); + } + + response.getSdrDaemonChannelSinkSettings()->setDataPort(settings.m_dataPort); +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h index 06b8ebbe4..2bf2cd754 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ b/sdrdaemon/channel/sdrdaemonchannelsink.h @@ -80,6 +80,16 @@ public: 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; } @@ -123,6 +133,7 @@ private: uint16_t m_dataPort; void applySettings(const SDRDaemonChannelSinkSettings& settings, bool force = false); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSinkSettings& settings); }; #endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index 2eae164f2..c89704648 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -31,6 +31,8 @@ #include "dsp/dspdevicesinkengine.h" #include "device/devicesourceapi.h" #include "device/devicesinkapi.h" +#include "channel/channelsourceapi.h" +#include "channel/channelsinkapi.h" #include "dsp/devicesamplesink.h" #include "dsp/devicesamplesource.h" #include "webapiadapterdaemon.h" @@ -181,8 +183,46 @@ int WebAPIAdapterDaemon::daemonChannelSettingsGet( SWGSDRangel::SWGErrorResponse& error) { error.init(); - *error.getMessage() = "Not implemented"; - return 501; + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + ChannelSinkAPI *channelAPI = m_sdrDaemonMain.m_deviceSourceAPI->getChanelAPIAt(0); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel"); + return 500; // a SDRDaemon sink channel should have been created so this is a server error + } + else + { + response.setChannelType(new QString()); + channelAPI->getIdentifier(*response.getChannelType()); + response.setTx(0); + return channelAPI->webapiSettingsGet(response, *error.getMessage()); + } + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + ChannelSourceAPI *channelAPI = m_sdrDaemonMain.m_deviceSinkAPI->getChanelAPIAt(0); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel"); + return 500; // a SDRDaemon source channel should have been created so this is a server error + } + else + { + response.setChannelType(new QString()); + channelAPI->getIdentifier(*response.getChannelType()); + response.setTx(1); + return channelAPI->webapiSettingsGet(response, *error.getMessage()); + } + } + else + { + *error.getMessage() = QString("Device not created error"); + return 500; + } } int WebAPIAdapterDaemon::daemonChannelSettingsPutPatch( @@ -192,8 +232,62 @@ int WebAPIAdapterDaemon::daemonChannelSettingsPutPatch( SWGSDRangel::SWGErrorResponse& error) { error.init(); - *error.getMessage() = "Not implemented"; - return 501; + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + ChannelSinkAPI *channelAPI = m_sdrDaemonMain.m_deviceSourceAPI->getChanelAPIAt(0); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel"); + return 500; + } + else + { + QString channelType; + channelAPI->getIdentifier(channelType); + + if (channelType == *response.getChannelType()) + { + return channelAPI->webapiSettingsPutPatch(force, channelSettingsKeys, response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("Channel has wrong type. Found %1.").arg(channelType); + return 500; + } + } + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + ChannelSourceAPI *channelAPI = m_sdrDaemonMain.m_deviceSinkAPI->getChanelAPIAt(0); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel"); + return 500; + } + else + { + QString channelType; + channelAPI->getIdentifier(channelType); + + if (channelType == *response.getChannelType()) + { + return channelAPI->webapiSettingsPutPatch(force, channelSettingsKeys, response, *error.getMessage()); + } + else + { + *error.getMessage() = QString("Channel has wrong type. Found %3.").arg(channelType); + return 500; + } + } + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } } int WebAPIAdapterDaemon::daemonDeviceSettingsGet( From 0b195947d84f962ff69ebdd122ecc7d3e8ce790f Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 23 Aug 2018 22:17:26 +0200 Subject: [PATCH 654/956] SDRDaemon: Web API: check channel settings --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 36 ++++++++++++++++++---- sdrdaemon/sdrdaemonparser.cpp | 2 +- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 730572525..7a551fa0d 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -333,17 +333,41 @@ int SDRDaemonChannelSink::webapiSettingsPutPatch( { SDRDaemonChannelSinkSettings settings = m_settings; - if (channelSettingsKeys.contains("nbFECBlocks")) { - settings.m_nbFECBlocks = response.getSdrDaemonChannelSinkSettings()->getNbFecBlocks(); + if (channelSettingsKeys.contains("nbFECBlocks")) + { + int nbFECBlocks = response.getSdrDaemonChannelSinkSettings()->getNbFecBlocks(); + + if ((nbFECBlocks < 0) || (nbFECBlocks > 127)) { + settings.m_nbFECBlocks = 8; + } else { + settings.m_nbFECBlocks = response.getSdrDaemonChannelSinkSettings()->getNbFecBlocks(); + } } - if (channelSettingsKeys.contains("txDelay")) { - settings.m_txDelay = response.getSdrDaemonChannelSinkSettings()->getTxDelay(); + + if (channelSettingsKeys.contains("txDelay")) + { + int txDelay = response.getSdrDaemonChannelSinkSettings()->getTxDelay(); + + if (txDelay < 0) { + settings.m_txDelay = 100; + } else { + settings.m_txDelay = txDelay; + } } + if (channelSettingsKeys.contains("dataAddress")) { settings.m_dataAddress = *response.getSdrDaemonChannelSinkSettings()->getDataAddress(); } - if (channelSettingsKeys.contains("dataPort")) { - settings.m_dataPort = response.getSdrDaemonChannelSinkSettings()->getDataPort(); + + if (channelSettingsKeys.contains("dataPort")) + { + int dataPort = response.getSdrDaemonChannelSinkSettings()->getDataPort(); + + if ((dataPort < 1024) || (dataPort > 65535)) { + settings.m_dataPort = 9090; + } else { + settings.m_dataPort = dataPort; + } } MsgConfigureSDRDaemonChannelSink *msg = MsgConfigureSDRDaemonChannelSink::create(settings, force); diff --git a/sdrdaemon/sdrdaemonparser.cpp b/sdrdaemon/sdrdaemonparser.cpp index 398ab9d48..a3fd6e222 100644 --- a/sdrdaemon/sdrdaemonparser.cpp +++ b/sdrdaemon/sdrdaemonparser.cpp @@ -229,7 +229,7 @@ void SDRDaemonParser::parse(const QCoreApplication& app) QString txDelayStr = m_parser.value(m_txDelayOption); int txDelay = txDelayStr.toInt(&ok); - if (ok && (txDelay > 0)) + if (ok && (txDelay >= 0)) { m_txDelay = txDelay; qDebug() << "SDRDaemonParser::parse: Tx delay: " << m_txDelay; From edffed4ff06114d9d402d0cf024705fd9b562a4d Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 23 Aug 2018 22:45:43 +0200 Subject: [PATCH 655/956] SDRDaemon: create and use channel source settings --- sdrdaemon/CMakeLists.txt | 2 + .../channel/sdrdaemonchannelsinksettings.cpp | 3 - sdrdaemon/channel/sdrdaemonchannelsource.cpp | 57 +++++++++++-- sdrdaemon/channel/sdrdaemonchannelsource.h | 30 +++++++ .../sdrdaemonchannelsourcesettings.cpp | 85 +++++++++++++++++++ .../channel/sdrdaemonchannelsourcesettings.h | 43 ++++++++++ 6 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcesettings.cpp create mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcesettings.h diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index c7f9e82c7..36f116f7a 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -10,6 +10,7 @@ set(sdrdaemon_SOURCES channel/sdrdaemondataqueue.cpp channel/sdrdaemonchannelsinkthread.cpp channel/sdrdaemonchannelsinksettings.cpp + channel/sdrdaemonchannelsourcesettings.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -26,6 +27,7 @@ set(sdrdaemon_HEADERS channel/sdrdaemondatablock.h channel/sdrdaemonchannelsinkthread.h channel/sdrdaemonchannelsinksettings.h + channel/sdrdaemonchannelsourcesettings.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp b/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp index 7a5a2e57c..f05b3a103 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp @@ -20,9 +20,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include - -#include "dsp/dspengine.h" #include "util/simpleserializer.h" #include "settings/serializable.h" #include "channel/sdrdaemonchannelsinksettings.h" diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index f22886299..a21846337 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -20,12 +20,16 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "util/simpleserializer.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/upchannelizer.h" #include "device/devicesinkapi.h" #include "sdrdaemonchannelsource.h" +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource, Message) + const QString SDRDaemonChannelSource::m_channelIdURI = "sdrangel.channel.sdrdaemonsource"; const QString SDRDaemonChannelSource::m_channelId = "SDRDaemonChannelSource"; @@ -33,7 +37,9 @@ SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : ChannelSourceAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_running(false), - m_samplesCount(0) + m_samplesCount(0), + m_dataAddress("127.0.0.1"), + m_dataPort(9090) { setObjectName(m_channelId); @@ -78,17 +84,58 @@ void SDRDaemonChannelSource::stop() bool SDRDaemonChannelSource::handleMessage(const Message& cmd __attribute__((unused))) { - return false; + if (MsgConfigureSDRDaemonChannelSource::match(cmd)) + { + MsgConfigureSDRDaemonChannelSource& cfg = (MsgConfigureSDRDaemonChannelSource&) cmd; + qDebug() << "SDRDaemonChannelSource::handleMessage: MsgConfigureSDRDaemonChannelSource"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else + { + return false; + } } QByteArray SDRDaemonChannelSource::serialize() const { - SimpleSerializer s(1); - return s.final(); + return m_settings.serialize(); } bool SDRDaemonChannelSource::deserialize(const QByteArray& data __attribute__((unused))) { - return false; + if (m_settings.deserialize(data)) + { + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } } +void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& settings, bool force) +{ + qDebug() << "SDRDaemonChannelSource::applySettings:" + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort + << " force: " << force; + + 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; +} + + diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h index c5b543199..7259b2827 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -25,6 +25,7 @@ #include "dsp/basebandsamplesource.h" #include "channel/channelsourceapi.h" +#include "channel/sdrdaemonchannelsourcesettings.h" class ThreadedBasebandSampleSource; class UpChannelizer; @@ -33,6 +34,29 @@ class DeviceSinkAPI; class SDRDaemonChannelSource : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: + class MsgConfigureSDRDaemonChannelSource : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SDRDaemonChannelSourceSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSDRDaemonChannelSource* create(const SDRDaemonChannelSourceSettings& settings, bool force) + { + return new MsgConfigureSDRDaemonChannelSource(settings, force); + } + + private: + SDRDaemonChannelSourceSettings m_settings; + bool m_force; + + MsgConfigureSDRDaemonChannelSource(const SDRDaemonChannelSourceSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI); ~SDRDaemonChannelSource(); virtual void destroy() { delete this; } @@ -58,7 +82,13 @@ private: UpChannelizer* m_channelizer; bool m_running; + SDRDaemonChannelSourceSettings m_settings; uint32_t m_samplesCount; + + QString m_dataAddress; + uint16_t m_dataPort; + + void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); }; diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcesettings.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcesettings.cpp new file mode 100644 index 000000000..2b14fe23b --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsourcesettings.cpp @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "channel/sdrdaemonchannelsourcesettings.h" + +SDRDaemonChannelSourceSettings::SDRDaemonChannelSourceSettings() +{ + resetToDefaults(); +} + +void SDRDaemonChannelSourceSettings::resetToDefaults() +{ + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; +} + +QByteArray SDRDaemonChannelSourceSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeString(1, m_dataAddress); + s.writeU32(2, m_dataPort); + + return s.final(); +} + +bool SDRDaemonChannelSourceSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + uint32_t tmp; + QString strtmp; + + d.readString(1, &m_dataAddress, "127.0.0.1"); + d.readU32(2, &tmp, 0); + + if ((tmp > 1023) && (tmp < 65535)) { + m_dataPort = tmp; + } else { + m_dataPort = 9090; + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + + + + + + diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h b/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h new file mode 100644 index 000000000..ebd26735f --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) 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 SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCESETTINGS_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCESETTINGS_H_ + + +#include + +class Serializable; + +struct SDRDaemonChannelSourceSettings +{ + QString m_dataAddress; + uint16_t m_dataPort; + + SDRDaemonChannelSourceSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCESETTINGS_H_ */ From 804c19904ba38a739f2274c78a96c1e2348d1267 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 24 Aug 2018 08:43:15 +0200 Subject: [PATCH 656/956] SDRDaemon: added channel source thread canvas --- sdrdaemon/CMakeLists.txt | 2 + .../channel/sdrdaemonchannelsourcethread.cpp | 112 ++++++++++++++++++ .../channel/sdrdaemonchannelsourcethread.h | 90 ++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp create mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcethread.h diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index 36f116f7a..aa834977d 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -11,6 +11,7 @@ set(sdrdaemon_SOURCES channel/sdrdaemonchannelsinkthread.cpp channel/sdrdaemonchannelsinksettings.cpp channel/sdrdaemonchannelsourcesettings.cpp + channel/sdrdaemonchannelsourcethread.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -28,6 +29,7 @@ set(sdrdaemon_HEADERS channel/sdrdaemonchannelsinkthread.h channel/sdrdaemonchannelsinksettings.h channel/sdrdaemonchannelsourcesettings.h + channel/sdrdaemonchannelsourcethread.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp new file mode 100644 index 000000000..5498b9a84 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp @@ -0,0 +1,112 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) UDP receiver 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 "channel/sdrdaemonchannelsourcethread.h" + +#include "cm256.h" + +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgStartStop, Message) + +SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(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); +} + +SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread() +{ + qDebug("SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread"); +} + +void SDRDaemonChannelSourceThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void SDRDaemonChannelSourceThread::startWork() +{ + qDebug("SDRDaemonChannelSourceThread::startWork"); + m_startWaitMutex.lock(); + m_socket = new QUdpSocket(this); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void SDRDaemonChannelSourceThread::stopWork() +{ + qDebug("SDRDaemonChannelSourceThread::stopWork"); + delete m_socket; + m_socket = 0; + m_running = false; + wait(); +} + +void SDRDaemonChannelSourceThread::run() +{ + qDebug("SDRDaemonChannelSourceThread::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("SDRDaemonChannelSourceThread::run: end"); +} + + +void SDRDaemonChannelSourceThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + } +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.h b/sdrdaemon/channel/sdrdaemonchannelsourcethread.h new file mode 100644 index 000000000..e7f89e8ce --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsourcethread.h @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) UDP receiver 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCETHREAD_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCETHREAD_H_ + +#include +#include +#include +#include + +#include "util/message.h" +#include "util/messagequeue.h" + +class SDRDaemonDataQueue; +class SDRDaemonDataBlock; +class CM256; +class QUdpSocket; + +class SDRDaemonChannelSourceThread : 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) + { } + }; + + SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent = 0); + ~SDRDaemonChannelSourceThread(); + + 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(); + +private slots: + void handleInputMessages(); +}; + + + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCETHREAD_H_ */ From f30dcf7753a8e7c5bdfec476ec9728151639750f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 26 Aug 2018 17:22:22 +0200 Subject: [PATCH 657/956] SDRdaemon: channel source receiving data --- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 60 ++++++++++++++++--- sdrdaemon/channel/sdrdaemonchannelsource.h | 17 +++++- .../channel/sdrdaemonchannelsourcesettings.h | 4 +- .../channel/sdrdaemonchannelsourcethread.cpp | 37 +++++++++++- .../channel/sdrdaemonchannelsourcethread.h | 28 ++++++++- 5 files changed, 131 insertions(+), 15 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index a21846337..e16e6baf9 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -27,6 +27,8 @@ #include "dsp/upchannelizer.h" #include "device/devicesinkapi.h" #include "sdrdaemonchannelsource.h" +#include "channel/sdrdaemonchannelsourcethread.h" +#include "channel/sdrdaemondatablock.h" MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource, Message) @@ -36,6 +38,7 @@ const QString SDRDaemonChannelSource::m_channelId = "SDRDaemonChannelSource"; SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : ChannelSourceAPI(m_channelIdURI), m_deviceAPI(deviceAPI), + m_sourceThread(0), m_running(false), m_samplesCount(0), m_dataAddress("127.0.0.1"), @@ -47,6 +50,9 @@ SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); + + connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; } SDRDaemonChannelSource::~SDRDaemonChannelSource() @@ -62,23 +68,34 @@ void SDRDaemonChannelSource::pull(Sample& sample) sample.m_real = 0.0f; sample.m_imag = 0.0f; - if (m_samplesCount < 1023) { - m_samplesCount++; - } else { - qDebug("SDRDaemonChannelSource::pull: 1024 samples pulled"); - m_samplesCount = 0; - } + m_samplesCount++; } void SDRDaemonChannelSource::start() { qDebug("SDRDaemonChannelSink::start"); + + if (m_running) { + stop(); + } + + m_sourceThread = new SDRDaemonChannelSourceThread(&m_dataQueue, m_cm256p); + m_sourceThread->startStop(true); + m_sourceThread->dataBind(m_dataAddress, m_dataPort); m_running = true; } void SDRDaemonChannelSource::stop() { qDebug("SDRDaemonChannelSink::stop"); + + if (m_sourceThread != 0) + { + m_sourceThread->startStop(false); + m_sourceThread->deleteLater(); + m_sourceThread = 0; + } + m_running = false; } @@ -127,15 +144,42 @@ void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& << " m_dataPort: " << settings.m_dataPort << " force: " << force; - if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { + bool change = false; + + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) + { m_dataAddress = settings.m_dataAddress; + change = true; } - if ((m_settings.m_dataPort != settings.m_dataPort) || force) { + if ((m_settings.m_dataPort != settings.m_dataPort) || force) + { m_dataPort = settings.m_dataPort; + change = true; + } + + if (change && m_sourceThread) { + m_sourceThread->dataBind(m_dataAddress, m_dataPort); } m_settings = settings; } +bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock __attribute__((unused))) +{ + //TODO: Push into R/W buffer + return true; +} +void SDRDaemonChannelSource::handleData() +{ + SDRDaemonDataBlock* dataBlock; + + while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) + { + if (handleDataBlock(*dataBlock)) + { + delete dataBlock; + } + } +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h index 7259b2827..be1e73e71 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -23,13 +23,18 @@ #ifndef SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCE_H_ #define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCE_H_ +#include "cm256.h" + #include "dsp/basebandsamplesource.h" #include "channel/channelsourceapi.h" #include "channel/sdrdaemonchannelsourcesettings.h" +#include "channel/sdrdaemondataqueue.h" class ThreadedBasebandSampleSource; class UpChannelizer; class DeviceSinkAPI; +class SDRDaemonChannelSourceThread; +class SDRDaemonDataBlock; class SDRDaemonChannelSource : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT @@ -80,15 +85,23 @@ private: DeviceSinkAPI *m_deviceAPI; ThreadedBasebandSampleSource* m_threadedChannelizer; UpChannelizer* m_channelizer; - + SDRDaemonDataQueue m_dataQueue; + SDRDaemonChannelSourceThread *m_sourceThread; + CM256 m_cm256; + CM256 *m_cm256p; bool m_running; + SDRDaemonChannelSourceSettings m_settings; - uint32_t m_samplesCount; + uint64_t m_samplesCount; QString m_dataAddress; uint16_t m_dataPort; void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); + bool handleDataBlock(SDRDaemonDataBlock& dataBlock); + +private slots: + void handleData(); }; diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h b/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h index ebd26735f..7cd4268b6 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h +++ b/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h @@ -30,8 +30,8 @@ class Serializable; struct SDRDaemonChannelSourceSettings { - QString m_dataAddress; - uint16_t m_dataPort; + QString m_dataAddress; //!< Listening (local) data address + uint16_t m_dataPort; //!< Listening data port SDRDaemonChannelSourceSettings(); void resetToDefaults(); diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp index 5498b9a84..f644a9523 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp @@ -29,6 +29,7 @@ #include "cm256.h" MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgDataBind, Message) SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent) : QThread(parent), @@ -39,7 +40,6 @@ SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(SDRDaemonDataQueue *d m_socket(0) { connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); - connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); } SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread() @@ -53,6 +53,12 @@ void SDRDaemonChannelSourceThread::startStop(bool start) m_inputMessageQueue.push(msg); } +void SDRDaemonChannelSourceThread::dataBind(const QString& address, uint16_t port) +{ + MsgDataBind *msg = MsgDataBind::create(address, port); + m_inputMessageQueue.push(msg); +} + void SDRDaemonChannelSourceThread::startWork() { qDebug("SDRDaemonChannelSourceThread::startWork"); @@ -108,5 +114,34 @@ void SDRDaemonChannelSourceThread::handleInputMessages() delete message; } + else if (MsgDataBind::match(*message)) + { + MsgDataBind* notif = (MsgDataBind*) message; + qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); + + if (m_socket) + { + disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + m_socket->bind(notif->getAddress(), notif->getPort()); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + } + } + } +} + +void SDRDaemonChannelSourceThread::readPendingDatagrams() +{ + char data[1024]; + while (m_socket->hasPendingDatagrams()) + { + QHostAddress sender; + quint16 senderPort = 0; + qint64 pendingDataSize = m_socket->pendingDatagramSize(); + m_socket->readDatagram(data, pendingDataSize, &sender, &senderPort); + qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: %lld bytes received from %s:%d", + pendingDataSize, + qPrintable(sender.toString()), + senderPort); + } } diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.h b/sdrdaemon/channel/sdrdaemonchannelsourcethread.h index e7f89e8ce..de4767578 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsourcethread.h +++ b/sdrdaemon/channel/sdrdaemonchannelsourcethread.h @@ -58,24 +58,47 @@ public: { } }; + class MsgDataBind : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QHostAddress getAddress() const { return m_address; } + uint16_t getPort() const { return m_port; } + + static MsgDataBind* create(const QString& address, uint16_t port) { + return new MsgDataBind(address, port); + } + + protected: + QHostAddress m_address; + uint16_t m_port; + + MsgDataBind(const QString& address, uint16_t port) : + Message(), + m_port(port) + { + m_address.setAddress(address); + } + }; + SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent = 0); ~SDRDaemonChannelSourceThread(); void startStop(bool start); + void dataBind(const QString& address, uint16_t port); private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; bool m_running; + MessageQueue m_inputMessageQueue; SDRDaemonDataQueue *m_dataQueue; CM256 *m_cm256; //!< CM256 library object QHostAddress m_address; QUdpSocket *m_socket; - MessageQueue m_inputMessageQueue; - void startWork(); void stopWork(); @@ -83,6 +106,7 @@ private: private slots: void handleInputMessages(); + void readPendingDatagrams(); }; From 77ed5480341050c193b13cb9f1be7823e4d2abe1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 27 Aug 2018 01:09:12 +0200 Subject: [PATCH 658/956] SDRdaemon: channel source mechanism with FEC recovery --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 16 +-- .../channel/sdrdaemonchannelsinkthread.cpp | 12 +- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 105 +++++++++++++++++- sdrdaemon/channel/sdrdaemonchannelsource.h | 5 + .../channel/sdrdaemonchannelsourcethread.cpp | 72 ++++++++++-- .../channel/sdrdaemonchannelsourcethread.h | 7 +- sdrdaemon/channel/sdrdaemondatablock.h | 22 +++- 7 files changed, 209 insertions(+), 30 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 7a551fa0d..a10d6a771 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -70,7 +70,7 @@ SDRDaemonChannelSink::SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI) : SDRDaemonChannelSink::~SDRDaemonChannelSink() { m_dataBlockMutex.lock(); - if (m_dataBlock && !m_dataBlock->m_controlBlock.m_complete) { + if (m_dataBlock && !m_dataBlock->m_txControlBlock.m_complete) { delete m_dataBlock; } m_dataBlockMutex.unlock(); @@ -171,13 +171,13 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const if (m_txBlockIndex == SDRDaemonNbOrginalBlocks - 1) // frame complete { m_dataBlockMutex.lock(); - m_dataBlock->m_controlBlock.m_frameIndex = m_frameCount; - m_dataBlock->m_controlBlock.m_processed = false; - m_dataBlock->m_controlBlock.m_complete = true; - m_dataBlock->m_controlBlock.m_nbBlocksFEC = m_nbBlocksFEC; - m_dataBlock->m_controlBlock.m_txDelay = m_txDelay; - m_dataBlock->m_controlBlock.m_dataAddress = m_dataAddress; - m_dataBlock->m_controlBlock.m_dataPort = m_dataPort; + 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 diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp index e975dddcb..368a37e3c 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp @@ -94,11 +94,11 @@ bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) CM256::cm256_block descriptorBlocks[256]; //!< Pointers to data for CM256 encoder SDRDaemonProtectedBlock fecBlocks[256]; //!< FEC data - uint16_t frameIndex = dataBlock.m_controlBlock.m_frameIndex; - int nbBlocksFEC = dataBlock.m_controlBlock.m_nbBlocksFEC; - int txDelay = dataBlock.m_controlBlock.m_txDelay; - m_address.setAddress(dataBlock.m_controlBlock.m_dataAddress); - uint16_t dataPort = dataBlock.m_controlBlock.m_dataPort; + 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 @@ -158,7 +158,7 @@ bool SDRDaemonChannelSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) } } - dataBlock.m_controlBlock.m_processed = true; + dataBlock.m_txControlBlock.m_processed = true; return true; } diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index e16e6baf9..cc1a5af32 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -20,6 +20,9 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include +#include + #include #include "util/simpleserializer.h" @@ -53,6 +56,7 @@ SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; + m_currentMeta.init(); } SDRDaemonChannelSource::~SDRDaemonChannelSource() @@ -79,7 +83,7 @@ void SDRDaemonChannelSource::start() stop(); } - m_sourceThread = new SDRDaemonChannelSourceThread(&m_dataQueue, m_cm256p); + m_sourceThread = new SDRDaemonChannelSourceThread(&m_dataQueue); m_sourceThread->startStop(true); m_sourceThread->dataBind(m_dataAddress, m_dataPort); m_running = true; @@ -165,8 +169,91 @@ void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& m_settings = settings; } -bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock __attribute__((unused))) +bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock) { + if (dataBlock.m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) + { + qWarning("SDRDaemonChannelSource::handleDataBlock: incomplete data block: not processing"); + } + else + { + int blockCount = 0; + + for (int blockIndex = 0; blockIndex < 256; blockIndex++) + { + if ((blockIndex == 0) && (dataBlock.m_rxControlBlock.m_metaRetrieved)) + { + m_cm256DescriptorBlocks[blockCount].Index = 0; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock.m_superBlocks[0].m_protectedBlock); + blockCount++; + } + else if (dataBlock.m_superBlocks[blockIndex].m_header.m_blockIndex != 0) + { + m_cm256DescriptorBlocks[blockCount].Index = dataBlock.m_superBlocks[blockIndex].m_header.m_blockIndex; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock.m_superBlocks[blockIndex].m_protectedBlock); + blockCount++; + } + } + + //qDebug("SDRDaemonChannelSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); + + // Need to use the CM256 recovery + if (m_cm256p &&(dataBlock.m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) + { + qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock.m_rxControlBlock.m_recoveryCount); + CM256::cm256_encoder_params paramsCM256; + paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes + + if (m_currentMeta.m_tv_sec == 0) { + paramsCM256.RecoveryCount = dataBlock.m_rxControlBlock.m_recoveryCount; + } else { + paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; + } + + if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode + { + qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" + << " m_originalCount: " << dataBlock.m_rxControlBlock.m_originalCount + << " m_recoveryCount: " << dataBlock.m_rxControlBlock.m_recoveryCount; + } + else + { + for (int ir = 0; ir < dataBlock.m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks + { + int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock.m_rxControlBlock.m_recoveryCount + ir; + int blockIndex = m_cm256DescriptorBlocks[recoveryIndex].Index; + SDRDaemonProtectedBlock *recoveredBlock = + (SDRDaemonProtectedBlock *) m_cm256DescriptorBlocks[recoveryIndex].Block; + memcpy((void *) &(dataBlock.m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); + if ((blockIndex == 0) && !dataBlock.m_rxControlBlock.m_metaRetrieved) { + dataBlock.m_rxControlBlock.m_metaRetrieved = true; + } + } + } + } + + // Validate block zero and retrieve its data + if (dataBlock.m_rxControlBlock.m_metaRetrieved) + { + SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock.m_superBlocks[0].m_protectedBlock); + boost::crc_32_type crc32; + crc32.process_bytes(metaData, 20); + + if (crc32.checksum() == metaData->m_crc32) + { + if (!(m_currentMeta == *metaData)) { + printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); + } + + m_currentMeta = *metaData; + } + else + { + qWarning() << "SDRDaemonChannelSource::handleDataBlock: recovered meta: invalid CRC32"; + } + } + } //TODO: Push into R/W buffer return true; } @@ -183,3 +270,17 @@ void SDRDaemonChannelSource::handleData() } } } + +void SDRDaemonChannelSource::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) +{ + qDebug() << header << ": " + << "|" << 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 + << "|"; +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h index be1e73e71..433bf8250 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -29,6 +29,7 @@ #include "channel/channelsourceapi.h" #include "channel/sdrdaemonchannelsourcesettings.h" #include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" class ThreadedBasebandSampleSource; class UpChannelizer; @@ -97,8 +98,12 @@ private: QString m_dataAddress; uint16_t m_dataPort; + CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) + SDRDaemonMetaDataFEC m_currentMeta; + void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); bool handleDataBlock(SDRDaemonDataBlock& dataBlock); + void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); private slots: void handleData(); diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp index f644a9523..533ace7d2 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp @@ -20,6 +20,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include #include "channel/sdrdaemondataqueue.h" @@ -31,14 +33,14 @@ MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgStartStop, Message) MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgDataBind, Message) -SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent) : +SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : QThread(parent), m_running(false), m_dataQueue(dataQueue), - m_cm256(cm256), m_address(QHostAddress::LocalHost), m_socket(0) { + std::fill(m_dataBlocks, m_dataBlocks+4, (SDRDaemonDataBlock *) 0); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); } @@ -131,17 +133,69 @@ void SDRDaemonChannelSourceThread::handleInputMessages() void SDRDaemonChannelSourceThread::readPendingDatagrams() { - char data[1024]; + SDRDaemonSuperBlock superBlock; + qint64 size; + while (m_socket->hasPendingDatagrams()) { QHostAddress sender; quint16 senderPort = 0; - qint64 pendingDataSize = m_socket->pendingDatagramSize(); - m_socket->readDatagram(data, pendingDataSize, &sender, &senderPort); - qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: %lld bytes received from %s:%d", - pendingDataSize, - qPrintable(sender.toString()), - senderPort); + //qint64 pendingDataSize = m_socket->pendingDatagramSize(); + size = m_socket->readDatagram((char *) &superBlock, (long long int) sizeof(SDRDaemonSuperBlock), &sender, &senderPort); + if (size == sizeof(SDRDaemonSuperBlock)) + { + unsigned int dataBlockIndex = superBlock.m_header.m_frameIndex % m_nbDataBlocks; + + // create the first block for this index + if (m_dataBlocks[dataBlockIndex] == 0) { + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + } + + if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex < 0) + { + // initialize virgin block with the frame index + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + else + { + // if the frame index is not the same for the same slot it means we are starting a new frame + uint32_t frameIndex = m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex; + + if (superBlock.m_header.m_frameIndex != frameIndex) + { + //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", frameIndex); + m_dataQueue->push(m_dataBlocks[dataBlockIndex]); + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + } + + m_dataBlocks[dataBlockIndex]->m_superBlocks[superBlock.m_header.m_blockIndex] = superBlock; + + if (superBlock.m_header.m_blockIndex == 0) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_metaRetrieved = true; + } + + if (superBlock.m_header.m_blockIndex < SDRDaemonNbOrginalBlocks) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_originalCount++; + } else { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_recoveryCount++; + } + + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount++; + +// // if enough data blocks to decode push into data queue +// if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount == SDRDaemonNbOrginalBlocks) +// { +// //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", superBlock.m_header.m_frameIndex); +// m_dataQueue->push(m_dataBlocks[dataBlockIndex]); +// m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); +// } + } + else + { + qWarning("SDRDaemonChannelSourceThread::readPendingDatagrams: wrong super block size not processing"); + } } } diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.h b/sdrdaemon/channel/sdrdaemonchannelsourcethread.h index de4767578..37892d69b 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsourcethread.h +++ b/sdrdaemon/channel/sdrdaemonchannelsourcethread.h @@ -33,7 +33,6 @@ class SDRDaemonDataQueue; class SDRDaemonDataBlock; -class CM256; class QUdpSocket; class SDRDaemonChannelSourceThread : public QThread { @@ -81,7 +80,7 @@ public: } }; - SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent = 0); + SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); ~SDRDaemonChannelSourceThread(); void startStop(bool start); @@ -94,11 +93,13 @@ private: MessageQueue m_inputMessageQueue; SDRDaemonDataQueue *m_dataQueue; - CM256 *m_cm256; //!< CM256 library object QHostAddress m_address; QUdpSocket *m_socket; + static const uint32_t m_nbDataBlocks = 4; //!< number of data blocks in the ring buffer + SDRDaemonDataBlock *m_dataBlocks[m_nbDataBlocks]; //!< ring buffer of data blocks indexed by frame affinity + void startWork(); void stopWork(); diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index b8aec1a71..af7a635b0 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -57,7 +57,7 @@ struct SDRDaemonMetaDataFEC m_sampleBytes = 0; m_sampleBits = 0; m_nbOriginalBlocks = 0; - m_nbFECBlocks = -1; + m_nbFECBlocks = 0; m_tv_sec = 0; m_tv_usec = 0; m_crc32 = 0; @@ -125,6 +125,23 @@ struct SDRDaemonTxControlBlock } }; +struct SDRDaemonRxControlBlock +{ + int m_blockCount; //!< number of blocks received for this frame + int m_originalCount; //!< number of original blocks received + int m_recoveryCount; //!< number of recovery blocks received + bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved + int m_frameIndex; //!< this frame index or -1 if unset + + SDRDaemonRxControlBlock() { + m_blockCount = 0; + m_originalCount = 0; + m_recoveryCount = 0; + m_metaRetrieved = false; + m_frameIndex = -1; + } +}; + class SDRDaemonDataBlock { public: @@ -134,7 +151,8 @@ public: ~SDRDaemonDataBlock() { delete[] m_superBlocks; } - SDRDaemonTxControlBlock m_controlBlock; + SDRDaemonTxControlBlock m_txControlBlock; + SDRDaemonRxControlBlock m_rxControlBlock; SDRDaemonSuperBlock *m_superBlocks; }; From 790a62cba3b0915bf485ded97482c12f412d05d8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 27 Aug 2018 02:41:06 +0200 Subject: [PATCH 659/956] SDRdaemon: channel source make updates from meta data --- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 2 +- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 28 +++++++++++++++++-- .../channel/sdrdaemonchannelsourcethread.cpp | 8 ------ sdrdaemon/channel/sdrdaemondatablock.h | 2 +- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index a10d6a771..23a7b5e03 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -134,7 +134,7 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const qDebug() << "SDRDaemonChannelSink::feed: meta: " << "|" << metaData.m_centerFrequency << ":" << metaData.m_sampleRate - << ":" << (int) metaData.m_sampleBytes + << ":" << (int) (metaData.m_sampleBytes & 0xF) << ":" << (int) metaData.m_sampleBits << "|" << (int) metaData.m_nbOriginalBlocks << ":" << (int) metaData.m_nbFECBlocks diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index cc1a5af32..511b6f49d 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -28,6 +28,7 @@ #include "util/simpleserializer.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/upchannelizer.h" +#include "dsp/devicesamplesink.h" #include "device/devicesinkapi.h" #include "sdrdaemonchannelsource.h" #include "channel/sdrdaemonchannelsourcethread.h" @@ -105,7 +106,19 @@ void SDRDaemonChannelSource::stop() bool SDRDaemonChannelSource::handleMessage(const Message& cmd __attribute__((unused))) { - if (MsgConfigureSDRDaemonChannelSource::match(cmd)) + if (UpChannelizer::MsgChannelizerNotification::match(cmd)) + { + UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "SDRDaemonChannelSource::handleMessage: UpChannelizer::MsgChannelizerNotification:" + << " basebandSampleRate: " << notif.getBasebandSampleRate() + << " outputSampleRate: " << notif.getSampleRate() + << " inputFrequencyOffset: " << notif.getFrequencyOffset(); + + //applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset()); + + return true; + } + else if (MsgConfigureSDRDaemonChannelSource::match(cmd)) { MsgConfigureSDRDaemonChannelSource& cfg = (MsgConfigureSDRDaemonChannelSource&) cmd; qDebug() << "SDRDaemonChannelSource::handleMessage: MsgConfigureSDRDaemonChannelSource"; @@ -242,8 +255,17 @@ bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock) if (crc32.checksum() == metaData->m_crc32) { - if (!(m_currentMeta == *metaData)) { + if (!(m_currentMeta == *metaData)) + { printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); + + if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { + m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency); + } + + if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) { + m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); + } } m_currentMeta = *metaData; @@ -273,7 +295,7 @@ void SDRDaemonChannelSource::handleData() void SDRDaemonChannelSource::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) { - qDebug() << header << ": " + qDebug().noquote() << header << ": " << "|" << metaData->m_centerFrequency << ":" << metaData->m_sampleRate << ":" << (int) (metaData->m_sampleBytes & 0xF) diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp index 533ace7d2..3b2d55998 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp @@ -184,14 +184,6 @@ void SDRDaemonChannelSourceThread::readPendingDatagrams() } m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount++; - -// // if enough data blocks to decode push into data queue -// if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount == SDRDaemonNbOrginalBlocks) -// { -// //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", superBlock.m_header.m_frameIndex); -// m_dataQueue->push(m_dataBlocks[dataBlockIndex]); -// m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); -// } } else { diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index af7a635b0..31dfe66ae 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -37,7 +37,7 @@ struct SDRDaemonMetaDataFEC { uint32_t m_centerFrequency; //!< 4 center frequency in kHz uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_sampleBytes; //!< 9 number of bytes per sample (2 or 3) + uint8_t m_sampleBytes; //!< 9 4 LSB: number of bytes per sample (2 or 3) uint8_t m_sampleBits; //!< 10 number of effective bits per sample (deprecated) uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC From c1bd4f6b44b929ca50e244ca0286915ee2afa148 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 27 Aug 2018 17:45:27 +0200 Subject: [PATCH 660/956] Up channelizer: fixed copy+paste bug --- sdrbase/dsp/dspdevicesinkengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrbase/dsp/dspdevicesinkengine.cpp b/sdrbase/dsp/dspdevicesinkengine.cpp index 1098770c5..a890f1875 100644 --- a/sdrbase/dsp/dspdevicesinkengine.cpp +++ b/sdrbase/dsp/dspdevicesinkengine.cpp @@ -592,7 +592,7 @@ void DSPDeviceSinkEngine::checkNumberOfBasebandSources() if (m_threadedBasebandSampleSources.size() == 1) { m_threadedBasebandSampleSources.back()->setDeviceSampleSourceFifo(sampleFifo); } else if (m_basebandSampleSources.size() == 1) { - m_threadedBasebandSampleSources.back()->setDeviceSampleSourceFifo(sampleFifo); + m_basebandSampleSources.back()->setDeviceSampleSourceFifo(sampleFifo); } m_multipleSourcesDivisionFactor = 1; // for consistency but it is not used in this case From 083f31aed4ad728fabb4afb809272498fd8cc51b Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 27 Aug 2018 18:27:09 +0200 Subject: [PATCH 661/956] SDRdaemon: sample buffer dedicated to channel source --- sdrdaemon/CMakeLists.txt | 2 + .../channel/sdrdaemonchannelsourcebuffer.cpp | 85 +++++++++++++++++++ .../channel/sdrdaemonchannelsourcebuffer.h | 42 +++++++++ 3 files changed, 129 insertions(+) create mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcebuffer.cpp create mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcebuffer.h diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index aa834977d..fa97be443 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -12,6 +12,7 @@ set(sdrdaemon_SOURCES channel/sdrdaemonchannelsinksettings.cpp channel/sdrdaemonchannelsourcesettings.cpp channel/sdrdaemonchannelsourcethread.cpp + channel/sdrdaemonchannelsourcebuffer.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -30,6 +31,7 @@ set(sdrdaemon_HEADERS channel/sdrdaemonchannelsinksettings.h channel/sdrdaemonchannelsourcesettings.h channel/sdrdaemonchannelsourcethread.h + channel/sdrdaemonchannelsourcebuffer.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.cpp new file mode 100644 index 000000000..8cc2112e9 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.cpp @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx). Samples buffer // +// // +// 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 "channel/sdrdaemonchannelsourcebuffer.h" + +SDRDaemonChannelSourceBuffer::SDRDaemonChannelSourceBuffer(uint32_t nbSamples) +{ + m_buffer = new Sample[nbSamples]; + m_wp = nbSamples/2; + m_rp = 0; + m_size = nbSamples; +} + +SDRDaemonChannelSourceBuffer::~SDRDaemonChannelSourceBuffer() +{ + delete[] m_buffer; +} + +void SDRDaemonChannelSourceBuffer::resize(uint32_t nbSamples) +{ + if (nbSamples > m_size) + { + delete[] m_buffer; + m_buffer = new Sample[nbSamples]; + } + + m_wp = nbSamples/2; + m_rp = 0; + m_size = nbSamples; +} + +void SDRDaemonChannelSourceBuffer::write(Sample *begin, uint32_t nbSamples) +{ + if (m_wp + nbSamples < m_size) + { + std::copy(begin, begin+nbSamples, &m_buffer[m_wp]); + m_wp += nbSamples; + } + else // wrap + { + int first = m_size - m_wp; + std::copy(begin, begin+first, &m_buffer[m_wp]); + int second = nbSamples - first; + std::copy(begin+first, begin+nbSamples, m_buffer); + m_wp = second; + } +} + +void SDRDaemonChannelSourceBuffer::readOne(Sample& sample) +{ + sample = m_buffer[m_rp]; + m_rp++; + + if (m_rp == m_size) { // wrap + m_rp = 0; + } +} + +int SDRDaemonChannelSourceBuffer::getRWBalancePercent() +{ + if (m_wp > m_rp) { + return (((m_wp - m_rp) - (m_size/2))*100)/m_size; + } else { + return (((m_size/2) - (m_rp - m_wp))*100)/m_size; + } +} \ No newline at end of file diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.h b/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.h new file mode 100644 index 000000000..7ec10bde7 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.h @@ -0,0 +1,42 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx). Samples buffer // +// // +// 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 "dsp/dsptypes.h" + +class SDRDaemonChannelSourceBuffer +{ +public: + SDRDaemonChannelSourceBuffer(uint32_t nbSamples); + ~SDRDaemonChannelSourceBuffer(); + void resize(uint32_t nbSamples); + void write(Sample *begin, uint32_t nbSamples); + void readOne(Sample& sample); + int getRWBalancePercent(); //!< positive write leads, negative read leads, balance = 0, percentage of buffer size + +private: + Sample *m_buffer; + uint32_t m_size; + uint32_t m_rp; + uint32_t m_wp; +}; From 82cba84a50a9c8a3a18a2abc7dcee098fd0c9b4c Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 28 Aug 2018 06:29:59 +0200 Subject: [PATCH 662/956] SDRDaemonSink: fixes --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 5 +- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 52 +++++++------------ .../sdrdaemonsink/sdrdaemonsinksettings.cpp | 6 +-- 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 55e65d616..d3c4f4ef6 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -205,8 +205,6 @@ void SDRdaemonSinkGui::handleInputMessages() while ((message = m_inputMessageQueue.pop()) != 0) { - qDebug("SDRdaemonSinkGui::handleInputMessages: message: %s", message->getIdentifier()); - if (DSPSignalNotification::match(*message)) { DSPSignalNotification* notif = (DSPSignalNotification*) message; @@ -418,6 +416,7 @@ void SDRdaemonSinkGui::on_interp_currentIndexChanged(int index) m_settings.m_log2Interp = index; updateSampleRateAndFrequency(); sendControl(); + sendSettings(); } void SDRdaemonSinkGui::on_txDelay_valueChanged(int value) @@ -506,6 +505,8 @@ void SDRdaemonSinkGui::on_applyButton_clicked(bool checked __attribute__((unused { m_settings.m_dataPort = udpDataPort; } + + sendSettings(); } void SDRdaemonSinkGui::on_sendButton_clicked(bool checked __attribute__((unused))) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index a90b8a301..07655d6a5 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -248,22 +248,15 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b if (force || (m_settings.m_address != settings.m_address) || (m_settings.m_dataPort != settings.m_dataPort)) { - m_settings.m_address = settings.m_address; - m_settings.m_dataPort = settings.m_dataPort; - - if (m_sdrDaemonSinkThread != 0) - { - m_sdrDaemonSinkThread->setRemoteAddress(m_settings.m_address, m_settings.m_dataPort); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setRemoteAddress(settings.m_address, settings.m_dataPort); } } if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency)) { - m_settings.m_centerFrequency = settings.m_centerFrequency; - - if (m_sdrDaemonSinkThread != 0) - { - m_sdrDaemonSinkThread->setCenterFrequency(m_settings.m_centerFrequency); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setCenterFrequency(settings.m_centerFrequency); } forwardChange = true; @@ -271,11 +264,8 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b if (force || (m_settings.m_sampleRate != settings.m_sampleRate)) { - m_settings.m_sampleRate = settings.m_sampleRate; - - if (m_sdrDaemonSinkThread != 0) - { - m_sdrDaemonSinkThread->setSamplerate(m_settings.m_sampleRate); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setSamplerate(settings.m_sampleRate); } forwardChange = true; @@ -284,17 +274,13 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b if (force || (m_settings.m_log2Interp != settings.m_log2Interp)) { - m_settings.m_log2Interp = settings.m_log2Interp; forwardChange = true; } if (force || (m_settings.m_nbFECBlocks != settings.m_nbFECBlocks)) { - m_settings.m_nbFECBlocks = settings.m_nbFECBlocks; - - if (m_sdrDaemonSinkThread != 0) - { - m_sdrDaemonSinkThread->setNbBlocksFEC(m_settings.m_nbFECBlocks); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setNbBlocksFEC(settings.m_nbFECBlocks); } changeTxDelay = true; @@ -302,13 +288,12 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b if (force || (m_settings.m_txDelay != settings.m_txDelay)) { - m_settings.m_txDelay = settings.m_txDelay; changeTxDelay = true; } if (changeTxDelay) { - double delay = ((127*127*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); + double delay = ((127*127*settings.m_txDelay) / settings.m_sampleRate)/(128 + settings.m_nbFECBlocks); qDebug("SDRdaemonSinkOutput::applySettings: Tx delay: %f us", delay*1e6); if (m_sdrDaemonSinkThread != 0) @@ -323,19 +308,22 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b mutexLocker.unlock(); - qDebug("SDRdaemonSinkOutput::applySettings: %s m_centerFrequency: %llu m_sampleRate: %llu m_log2Interp: %d m_txDelay: %f m_nbFECBlocks: %d", - forwardChange ? "forward change" : "", - m_settings.m_centerFrequency, - m_settings.m_sampleRate, - m_settings.m_log2Interp, - m_settings.m_txDelay, - m_settings.m_nbFECBlocks); + qDebug() << "SDRdaemonSinkOutput::applySettings:" + << " m_centerFrequency: " << settings.m_centerFrequency + << " m_sampleRate: " << settings.m_sampleRate + << " m_log2Interp: " << settings.m_log2Interp + << " m_txDelay: " << settings.m_txDelay + << " m_nbFECBlocks: " << settings.m_nbFECBlocks + << " m_address: " << settings.m_address + << " m_dataPort: " << settings.m_dataPort; if (forwardChange) { - DSPSignalNotification *notif = new DSPSignalNotification(m_settings.m_sampleRate, m_settings.m_centerFrequency); + DSPSignalNotification *notif = new DSPSignalNotification(settings.m_sampleRate, settings.m_centerFrequency); m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); } + + m_settings = settings; } int SDRdaemonSinkOutput::webapiRunGet( diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp index 415f47e2e..870a13efe 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp @@ -25,12 +25,12 @@ SDRdaemonSinkSettings::SDRdaemonSinkSettings() void SDRdaemonSinkSettings::resetToDefaults() { m_centerFrequency = 435000*1000; - m_sampleRate = 192000; - m_log2Interp = 4; + m_sampleRate = 48000; + m_log2Interp = 0; m_txDelay = 0.5; m_nbFECBlocks = 0; m_address = "127.0.0.1"; - m_dataPort = 9092; + m_dataPort = 9090; m_controlPort = 9093; m_specificParameters = ""; } From 8196a70753b00ea290f58893e3069080642f3fc9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 28 Aug 2018 06:33:15 +0200 Subject: [PATCH 663/956] SDRdaemon: data read queue --- sdrdaemon/CMakeLists.txt | 4 +- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 54 +++---- sdrdaemon/channel/sdrdaemonchannelsource.h | 5 +- .../channel/sdrdaemonchannelsourcebuffer.cpp | 85 ----------- sdrdaemon/channel/sdrdaemondatablock.h | 1 + sdrdaemon/channel/sdrdaemondatareadqueue.cpp | 142 ++++++++++++++++++ ...ourcebuffer.h => sdrdaemondatareadqueue.h} | 39 +++-- 7 files changed, 198 insertions(+), 132 deletions(-) delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcebuffer.cpp create mode 100644 sdrdaemon/channel/sdrdaemondatareadqueue.cpp rename sdrdaemon/channel/{sdrdaemonchannelsourcebuffer.h => sdrdaemondatareadqueue.h} (67%) diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt index fa97be443..edbd3aa9a 100644 --- a/sdrdaemon/CMakeLists.txt +++ b/sdrdaemon/CMakeLists.txt @@ -8,11 +8,11 @@ set(sdrdaemon_SOURCES channel/sdrdaemonchannelsink.cpp channel/sdrdaemonchannelsource.cpp channel/sdrdaemondataqueue.cpp + channel/sdrdaemondatareadqueue.cpp channel/sdrdaemonchannelsinkthread.cpp channel/sdrdaemonchannelsinksettings.cpp channel/sdrdaemonchannelsourcesettings.cpp channel/sdrdaemonchannelsourcethread.cpp - channel/sdrdaemonchannelsourcebuffer.cpp webapi/webapiadapterdaemon.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp @@ -26,12 +26,12 @@ set(sdrdaemon_HEADERS channel/sdrdaemonchannelsink.h channel/sdrdaemonchannelsource.h channel/sdrdaemondataqueue.h + channel/sdrdaemondatareadqueue.h channel/sdrdaemondatablock.h channel/sdrdaemonchannelsinkthread.h channel/sdrdaemonchannelsinksettings.h channel/sdrdaemonchannelsourcesettings.h channel/sdrdaemonchannelsourcethread.h - channel/sdrdaemonchannelsourcebuffer.h webapi/webapiadapterdaemon.h webapi/webapirequestmapper.h webapi/webapiserver.h diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index 511b6f49d..f8e28081f 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -70,9 +70,7 @@ SDRDaemonChannelSource::~SDRDaemonChannelSource() void SDRDaemonChannelSource::pull(Sample& sample) { - sample.m_real = 0.0f; - sample.m_imag = 0.0f; - + m_dataReadQueue.readSample(sample); m_samplesCount++; } @@ -182,9 +180,9 @@ void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& m_settings = settings; } -bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock) +void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) { - if (dataBlock.m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) + if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) { qWarning("SDRDaemonChannelSource::handleDataBlock: incomplete data block: not processing"); } @@ -194,16 +192,16 @@ bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock) for (int blockIndex = 0; blockIndex < 256; blockIndex++) { - if ((blockIndex == 0) && (dataBlock.m_rxControlBlock.m_metaRetrieved)) + if ((blockIndex == 0) && (dataBlock->m_rxControlBlock.m_metaRetrieved)) { m_cm256DescriptorBlocks[blockCount].Index = 0; - m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock.m_superBlocks[0].m_protectedBlock); + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[0].m_protectedBlock); blockCount++; } - else if (dataBlock.m_superBlocks[blockIndex].m_header.m_blockIndex != 0) + else if (dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex != 0) { - m_cm256DescriptorBlocks[blockCount].Index = dataBlock.m_superBlocks[blockIndex].m_header.m_blockIndex; - m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock.m_superBlocks[blockIndex].m_protectedBlock); + m_cm256DescriptorBlocks[blockCount].Index = dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock); blockCount++; } } @@ -211,15 +209,15 @@ bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock) //qDebug("SDRDaemonChannelSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); // Need to use the CM256 recovery - if (m_cm256p &&(dataBlock.m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) + if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) { - qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock.m_rxControlBlock.m_recoveryCount); + qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); CM256::cm256_encoder_params paramsCM256; paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes if (m_currentMeta.m_tv_sec == 0) { - paramsCM256.RecoveryCount = dataBlock.m_rxControlBlock.m_recoveryCount; + paramsCM256.RecoveryCount = dataBlock->m_rxControlBlock.m_recoveryCount; } else { paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; } @@ -227,29 +225,29 @@ bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock) if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode { qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" - << " m_originalCount: " << dataBlock.m_rxControlBlock.m_originalCount - << " m_recoveryCount: " << dataBlock.m_rxControlBlock.m_recoveryCount; + << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount + << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; } else { - for (int ir = 0; ir < dataBlock.m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks + for (int ir = 0; ir < dataBlock->m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks { - int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock.m_rxControlBlock.m_recoveryCount + ir; + int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_recoveryCount + ir; int blockIndex = m_cm256DescriptorBlocks[recoveryIndex].Index; SDRDaemonProtectedBlock *recoveredBlock = (SDRDaemonProtectedBlock *) m_cm256DescriptorBlocks[recoveryIndex].Block; - memcpy((void *) &(dataBlock.m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); - if ((blockIndex == 0) && !dataBlock.m_rxControlBlock.m_metaRetrieved) { - dataBlock.m_rxControlBlock.m_metaRetrieved = true; + memcpy((void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); + if ((blockIndex == 0) && !dataBlock->m_rxControlBlock.m_metaRetrieved) { + dataBlock->m_rxControlBlock.m_metaRetrieved = true; } } } } // Validate block zero and retrieve its data - if (dataBlock.m_rxControlBlock.m_metaRetrieved) + if (dataBlock->m_rxControlBlock.m_metaRetrieved) { - SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock.m_superBlocks[0].m_protectedBlock); + SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock->m_superBlocks[0].m_protectedBlock); boost::crc_32_type crc32; crc32.process_bytes(metaData, 20); @@ -275,21 +273,17 @@ bool SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock& dataBlock) qWarning() << "SDRDaemonChannelSource::handleDataBlock: recovered meta: invalid CRC32"; } } + + m_dataReadQueue.push(dataBlock); // Push into R/W buffer } - //TODO: Push into R/W buffer - return true; } void SDRDaemonChannelSource::handleData() { SDRDaemonDataBlock* dataBlock; - while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) - { - if (handleDataBlock(*dataBlock)) - { - delete dataBlock; - } + while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) { + handleDataBlock(dataBlock); } } diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h index 433bf8250..7b2438014 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -30,6 +30,7 @@ #include "channel/sdrdaemonchannelsourcesettings.h" #include "channel/sdrdaemondataqueue.h" #include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemondatareadqueue.h" class ThreadedBasebandSampleSource; class UpChannelizer; @@ -101,8 +102,10 @@ private: CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) SDRDaemonMetaDataFEC m_currentMeta; + SDRDaemonDataReadQueue m_dataReadQueue; + void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); - bool handleDataBlock(SDRDaemonDataBlock& dataBlock); + void handleDataBlock(SDRDaemonDataBlock *dataBlock); void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); private slots: diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.cpp deleted file mode 100644 index 8cc2112e9..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx). Samples buffer // -// // -// 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 "channel/sdrdaemonchannelsourcebuffer.h" - -SDRDaemonChannelSourceBuffer::SDRDaemonChannelSourceBuffer(uint32_t nbSamples) -{ - m_buffer = new Sample[nbSamples]; - m_wp = nbSamples/2; - m_rp = 0; - m_size = nbSamples; -} - -SDRDaemonChannelSourceBuffer::~SDRDaemonChannelSourceBuffer() -{ - delete[] m_buffer; -} - -void SDRDaemonChannelSourceBuffer::resize(uint32_t nbSamples) -{ - if (nbSamples > m_size) - { - delete[] m_buffer; - m_buffer = new Sample[nbSamples]; - } - - m_wp = nbSamples/2; - m_rp = 0; - m_size = nbSamples; -} - -void SDRDaemonChannelSourceBuffer::write(Sample *begin, uint32_t nbSamples) -{ - if (m_wp + nbSamples < m_size) - { - std::copy(begin, begin+nbSamples, &m_buffer[m_wp]); - m_wp += nbSamples; - } - else // wrap - { - int first = m_size - m_wp; - std::copy(begin, begin+first, &m_buffer[m_wp]); - int second = nbSamples - first; - std::copy(begin+first, begin+nbSamples, m_buffer); - m_wp = second; - } -} - -void SDRDaemonChannelSourceBuffer::readOne(Sample& sample) -{ - sample = m_buffer[m_rp]; - m_rp++; - - if (m_rp == m_size) { // wrap - m_rp = 0; - } -} - -int SDRDaemonChannelSourceBuffer::getRWBalancePercent() -{ - if (m_wp > m_rp) { - return (((m_wp - m_rp) - (m_size/2))*100)/m_size; - } else { - return (((m_size/2) - (m_rp - m_wp))*100)/m_size; - } -} \ No newline at end of file diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 31dfe66ae..bf1ab552f 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "dsp/dsptypes.h" #define UDPSINKFEC_UDPSIZE 512 diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp new file mode 100644 index 000000000..7cc76b227 --- /dev/null +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp @@ -0,0 +1,142 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) data blocks to read queue // +// // +// 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 "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemondatareadqueue.h" + +const uint32_t SDRDaemonDataReadQueue::MaxSize = 20; + +SDRDaemonDataReadQueue::SDRDaemonDataReadQueue() : + m_dataBlock(0), + m_blockIndex(1), + m_sampleIndex(0) +{} + +SDRDaemonDataReadQueue::~SDRDaemonDataReadQueue() +{ + SDRDaemonDataBlock* data; + + while ((data = pop()) != 0) + { + qDebug("SDRDaemonDataReadQueue::~SDRDaemonDataReadQueue: data block was still in queue"); + delete data; + } +} + +void SDRDaemonDataReadQueue::push(SDRDaemonDataBlock* dataBlock) +{ + if (size() > MaxSize) + { + qWarning("SDRDaemonDataReadQueue::push: queue is full"); + SDRDaemonDataBlock *data = m_dataReadQueue.takeLast(); + delete data; + } + + m_dataReadQueue.append(dataBlock); +} + +SDRDaemonDataBlock* SDRDaemonDataReadQueue::pop() +{ + if (m_dataReadQueue.isEmpty()) + { + return 0; + } + else + { + m_blockIndex = 1; + m_sampleIndex = 0; + + return m_dataReadQueue.takeFirst(); + } +} + +uint32_t SDRDaemonDataReadQueue::size() +{ + return m_dataReadQueue.size(); +} + +void SDRDaemonDataReadQueue::readSample(Sample& s) +{ + // initial state + if (m_dataBlock == 0) + { + if (size() >= MaxSize/2) + { + qDebug("SDRDaemonDataReadQueue::readSample: initial pop new block: queue size: %u", size()); + m_blockIndex = 1; + m_dataBlock = m_dataReadQueue.takeFirst(); + s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; + m_sampleIndex++; + } + else + { + s = Sample{0, 0}; + } + + return; + } + + if (m_sampleIndex < SDRDaemonSamplesPerBlock) + { + s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; + m_sampleIndex++; + } + else + { + m_sampleIndex = 0; + m_blockIndex++; + + if (m_blockIndex < SDRDaemonNbOrginalBlocks) + { + s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; + m_sampleIndex++; + } + else + { + if (m_dataBlock) + { + delete m_dataBlock; + m_dataBlock = 0; + + if (size() == 0) { + qWarning("SDRDaemonDataReadQueue::readSample: try to pop new block but queue is empty"); + } + } + + if (size() > 0) + { + qDebug("SDRDaemonDataReadQueue::readSample: pop new block: queue size: %u", size()); + m_blockIndex = 1; + m_dataBlock = m_dataReadQueue.takeFirst(); + s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; + m_sampleIndex++; + } + else + { + s = Sample{0, 0}; + } + } + } +} + + + diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.h b/sdrdaemon/channel/sdrdaemondatareadqueue.h similarity index 67% rename from sdrdaemon/channel/sdrdaemonchannelsourcebuffer.h rename to sdrdaemon/channel/sdrdaemondatareadqueue.h index 7ec10bde7..19c0687b1 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsourcebuffer.h +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.h @@ -1,7 +1,7 @@ /////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2018 Edouard Griffiths, F4EXB. // // // -// SDRdaemon source channel (Tx). Samples buffer // +// SDRdaemon sink channel (Rx) data blocks to read queue // // // // 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 // @@ -20,23 +20,34 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include +#ifndef SDRDAEMON_CHANNEL_SDRDAEMONDATAREADQUEUE_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONDATAREADQUEUE_H_ -#include "dsp/dsptypes.h" +#include -class SDRDaemonChannelSourceBuffer +class SDRDaemonDataBlock; +class Sample; + +class SDRDaemonDataReadQueue { public: - SDRDaemonChannelSourceBuffer(uint32_t nbSamples); - ~SDRDaemonChannelSourceBuffer(); - void resize(uint32_t nbSamples); - void write(Sample *begin, uint32_t nbSamples); - void readOne(Sample& sample); - int getRWBalancePercent(); //!< positive write leads, negative read leads, balance = 0, percentage of buffer size + SDRDaemonDataReadQueue(); + ~SDRDaemonDataReadQueue(); + + void push(SDRDaemonDataBlock* dataBlock); //!< push block on the queue + SDRDaemonDataBlock* pop(); //!< Pop block from the queue + void readSample(Sample& s); //!< Read sample from queue + uint32_t size(); //!< Returns queue size + + static const uint32_t MaxSize; private: - Sample *m_buffer; - uint32_t m_size; - uint32_t m_rp; - uint32_t m_wp; + QQueue m_dataReadQueue; + SDRDaemonDataBlock *m_dataBlock; + uint32_t m_blockIndex; + uint32_t m_sampleIndex; }; + + + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATAREADQUEUE_H_ */ From b07277a5e2b27f2a2ff8ee650f16d8fffac41fb8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 28 Aug 2018 14:04:30 +0200 Subject: [PATCH 664/956] SDRdaemon: manage data read queue full --- sdrdaemon/channel/sdrdaemondatareadqueue.cpp | 16 ++++++++++++---- sdrdaemon/channel/sdrdaemondatareadqueue.h | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp index 7cc76b227..7081ee569 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp @@ -28,7 +28,8 @@ const uint32_t SDRDaemonDataReadQueue::MaxSize = 20; SDRDaemonDataReadQueue::SDRDaemonDataReadQueue() : m_dataBlock(0), m_blockIndex(1), - m_sampleIndex(0) + m_sampleIndex(0), + m_full(false) {} SDRDaemonDataReadQueue::~SDRDaemonDataReadQueue() @@ -44,14 +45,21 @@ SDRDaemonDataReadQueue::~SDRDaemonDataReadQueue() void SDRDaemonDataReadQueue::push(SDRDaemonDataBlock* dataBlock) { - if (size() > MaxSize) + if (size() >= MaxSize) { qWarning("SDRDaemonDataReadQueue::push: queue is full"); + m_full = true; // stop filling the queue SDRDaemonDataBlock *data = m_dataReadQueue.takeLast(); delete data; } - m_dataReadQueue.append(dataBlock); + if (m_full) { + m_full = (size() > MaxSize/2); // do not fill queue again before queue is half size + } + + if (!m_full) { + m_dataReadQueue.append(dataBlock); + } } SDRDaemonDataBlock* SDRDaemonDataReadQueue::pop() @@ -76,7 +84,7 @@ uint32_t SDRDaemonDataReadQueue::size() void SDRDaemonDataReadQueue::readSample(Sample& s) { - // initial state + // depletion/repletion state if (m_dataBlock == 0) { if (size() >= MaxSize/2) diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.h b/sdrdaemon/channel/sdrdaemondatareadqueue.h index 19c0687b1..7caa3e5e6 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.h +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.h @@ -46,6 +46,7 @@ private: SDRDaemonDataBlock *m_dataBlock; uint32_t m_blockIndex; uint32_t m_sampleIndex; + bool m_full; //!< full condition was hit }; From 5013e77f24b94254d4ce37a7ac13d1d4f034e509 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 28 Aug 2018 23:31:07 +0200 Subject: [PATCH 665/956] SDRdaemmon: channel source: allow passing of data address and port at startup --- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 26 +++++++++++--------- sdrdaemon/channel/sdrdaemonchannelsource.h | 5 ++-- sdrdaemon/sdrdaemonmain.cpp | 1 + 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index f8e28081f..8d6131eae 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -44,9 +44,7 @@ SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_sourceThread(0), m_running(false), - m_samplesCount(0), - m_dataAddress("127.0.0.1"), - m_dataPort(9090) + m_samplesCount(0) { setObjectName(m_channelId); @@ -84,7 +82,7 @@ void SDRDaemonChannelSource::start() m_sourceThread = new SDRDaemonChannelSourceThread(&m_dataQueue); m_sourceThread->startStop(true); - m_sourceThread->dataBind(m_dataAddress, m_dataPort); + m_sourceThread->dataBind(m_settings.m_dataAddress, m_settings.m_dataPort); m_running = true; } @@ -102,6 +100,16 @@ void SDRDaemonChannelSource::stop() m_running = false; } +void SDRDaemonChannelSource::setDataLink(const QString& dataAddress, uint16_t dataPort) +{ + SDRDaemonChannelSourceSettings settings = m_settings; + settings.m_dataAddress = dataAddress; + settings.m_dataPort = dataPort; + + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, false); + m_inputMessageQueue.push(msg); +} + bool SDRDaemonChannelSource::handleMessage(const Message& cmd __attribute__((unused))) { if (UpChannelizer::MsgChannelizerNotification::match(cmd)) @@ -161,20 +169,16 @@ void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& bool change = false; - if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) - { - m_dataAddress = settings.m_dataAddress; + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { change = true; } - if ((m_settings.m_dataPort != settings.m_dataPort) || force) - { - m_dataPort = settings.m_dataPort; + if ((m_settings.m_dataPort != settings.m_dataPort) || force) { change = true; } if (change && m_sourceThread) { - m_sourceThread->dataBind(m_dataAddress, m_dataPort); + m_sourceThread->dataBind(settings.m_dataAddress, settings.m_dataPort); } m_settings = settings; diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h index 7b2438014..f61c5cca1 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -80,6 +80,8 @@ public: virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + void setDataLink(const QString& dataAddress, uint16_t dataPort); + static const QString m_channelIdURI; static const QString m_channelId; @@ -96,9 +98,6 @@ private: SDRDaemonChannelSourceSettings m_settings; uint64_t m_samplesCount; - QString m_dataAddress; - uint16_t m_dataPort; - CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) SDRDaemonMetaDataFEC m_currentMeta; diff --git a/sdrdaemon/sdrdaemonmain.cpp b/sdrdaemon/sdrdaemonmain.cpp index 963388adf..17492a076 100644 --- a/sdrdaemon/sdrdaemonmain.cpp +++ b/sdrdaemon/sdrdaemonmain.cpp @@ -107,6 +107,7 @@ SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonPa info.noquote(); info << msg; m_channelSource = new SDRDaemonChannelSource(m_deviceSinkAPI); + m_channelSource->setDataLink(parser.getDataAddress(), parser.getDataPort()); } else { From c7bcfaead179bfa9470ed0eff3673a15ac3ba12c Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 29 Aug 2018 08:48:57 +0200 Subject: [PATCH 666/956] SDRdaemon: channel source data read queue: scale max length to sample rate --- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 13 ++++++++++++- sdrdaemon/channel/sdrdaemonchannelsource.h | 1 + sdrdaemon/channel/sdrdaemondatareadqueue.cpp | 18 +++++++++++++----- sdrdaemon/channel/sdrdaemondatareadqueue.h | 6 ++++-- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index 8d6131eae..d51ca6680 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -265,8 +265,10 @@ void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency); } - if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) { + if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) + { m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); + m_dataReadQueue.setSize(calculateDataReadQueueSize(metaData->m_sampleRate)); } } @@ -304,3 +306,12 @@ void SDRDaemonChannelSource::printMeta(const QString& header, SDRDaemonMetaDataF << ":" << metaData->m_tv_usec << "|"; } + +uint32_t SDRDaemonChannelSource::calculateDataReadQueueSize(int sampleRate) +{ + // scale for 20 blocks at 48 kS/s. Take next even number. + uint32_t maxSize = sampleRate / 2400; + maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; + qDebug("SDRDaemonChannelSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); + return maxSize; +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h index f61c5cca1..e743b3418 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -106,6 +106,7 @@ private: void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); void handleDataBlock(SDRDaemonDataBlock *dataBlock); void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); + uint32_t calculateDataReadQueueSize(int sampleRate); private slots: void handleData(); diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp index 7081ee569..7dc92be51 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp @@ -23,10 +23,11 @@ #include "channel/sdrdaemondatablock.h" #include "channel/sdrdaemondatareadqueue.h" -const uint32_t SDRDaemonDataReadQueue::MaxSize = 20; +const uint32_t SDRDaemonDataReadQueue::MinimumMaxSize = 10; SDRDaemonDataReadQueue::SDRDaemonDataReadQueue() : m_dataBlock(0), + m_maxSize(MinimumMaxSize), m_blockIndex(1), m_sampleIndex(0), m_full(false) @@ -45,7 +46,7 @@ SDRDaemonDataReadQueue::~SDRDaemonDataReadQueue() void SDRDaemonDataReadQueue::push(SDRDaemonDataBlock* dataBlock) { - if (size() >= MaxSize) + if (size() >= m_maxSize) { qWarning("SDRDaemonDataReadQueue::push: queue is full"); m_full = true; // stop filling the queue @@ -54,7 +55,7 @@ void SDRDaemonDataReadQueue::push(SDRDaemonDataBlock* dataBlock) } if (m_full) { - m_full = (size() > MaxSize/2); // do not fill queue again before queue is half size + m_full = (size() > m_maxSize/2); // do not fill queue again before queue is half size } if (!m_full) { @@ -77,17 +78,24 @@ SDRDaemonDataBlock* SDRDaemonDataReadQueue::pop() } } -uint32_t SDRDaemonDataReadQueue::size() +uint32_t SDRDaemonDataReadQueue::size() const { return m_dataReadQueue.size(); } +void SDRDaemonDataReadQueue::setSize(uint32_t size) +{ + if (size != m_maxSize) { + m_maxSize = size < MinimumMaxSize ? MinimumMaxSize : size; + } +} + void SDRDaemonDataReadQueue::readSample(Sample& s) { // depletion/repletion state if (m_dataBlock == 0) { - if (size() >= MaxSize/2) + if (size() >= m_maxSize/2) { qDebug("SDRDaemonDataReadQueue::readSample: initial pop new block: queue size: %u", size()); m_blockIndex = 1; diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.h b/sdrdaemon/channel/sdrdaemondatareadqueue.h index 7caa3e5e6..fe9a88edc 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.h +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.h @@ -37,13 +37,15 @@ public: void push(SDRDaemonDataBlock* dataBlock); //!< push block on the queue SDRDaemonDataBlock* pop(); //!< Pop block from the queue void readSample(Sample& s); //!< Read sample from queue - uint32_t size(); //!< Returns queue size + uint32_t size() const; //!< Returns queue size + void setSize(uint32_t size); //!< Sets the queue size - static const uint32_t MaxSize; + static const uint32_t MinimumMaxSize; private: QQueue m_dataReadQueue; SDRDaemonDataBlock *m_dataBlock; + uint32_t m_maxSize; uint32_t m_blockIndex; uint32_t m_sampleIndex; bool m_full; //!< full condition was hit From 133f9133bd91d84da4834629272a72010c60ece7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 29 Aug 2018 18:39:40 +0200 Subject: [PATCH 667/956] SDRDaemonSink: refactoring (1) --- plugins/samplesink/CMakeLists.txt | 9 +- .../samplesink/sdrdaemonsink/CMakeLists.txt | 4 - .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 297 +++--------------- .../sdrdaemonsink/sdrdaemonsinkgui.h | 8 +- .../sdrdaemonsink/sdrdaemonsinkgui.ui | 126 ++------ .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 42 +-- .../sdrdaemonsink/sdrdaemonsinkplugin.cpp | 2 +- .../sdrdaemonsink/sdrdaemonsinksettings.cpp | 31 +- .../sdrdaemonsink/sdrdaemonsinksettings.h | 9 +- .../sdrdaemonsink/sdrdaemonsinkthread.h | 2 +- sdrbase/resources/webapi/doc/html2/index.html | 19 +- .../doc/swagger/include/SDRDaemonSink.yaml | 12 +- .../api/swagger/include/SDRDaemonSink.yaml | 12 +- swagger/sdrangel/code/html2/index.html | 19 +- .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 137 ++++---- .../qt5/client/SWGSDRdaemonSinkSettings.h | 38 +-- 16 files changed, 209 insertions(+), 558 deletions(-) diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index 3d32cd02e..277dfefed 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -23,19 +23,16 @@ if(LIBUSB_FOUND AND LIBIIO_FOUND) endif(LIBUSB_FOUND AND LIBIIO_FOUND) find_package(CM256cc) -find_package(LibNANOMSG) -if(CM256CC_FOUND AND LIBNANOMSG_FOUND) +if(CM256CC_FOUND) add_subdirectory(sdrdaemonsink) -endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) +endif(CM256CC_FOUND) if (BUILD_DEBIAN) add_subdirectory(bladerfoutput) add_subdirectory(hackrfoutput) add_subdirectory(limesdroutput) add_subdirectory(plutosdroutput) - if (LIBNANOMSG_FOUND) - add_subdirectory(sdrdaemonsink) - endif (LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsink) endif (BUILD_DEBIAN) add_subdirectory(filesink) diff --git a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt index 0d07ae186..a2e7e9e29 100644 --- a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt +++ b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt @@ -41,7 +41,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBCM256CCSRC} - ${LIBNANOMSG_INCLUDE_DIR} ) else (BUILD_DEBIAN) include_directories( @@ -50,7 +49,6 @@ include_directories( ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${CM256CC_INCLUDE_DIR} - ${LIBNANOMSG_INCLUDE_DIR} ) endif (BUILD_DEBIAN) @@ -73,7 +71,6 @@ target_link_libraries(outputsdrdaemonsink sdrgui swagger cm256cc - ${LIBNANOMSG_LIBRARIES} ) else (BUILD_DEBIAN) target_link_libraries(outputsdrdaemonsink @@ -82,7 +79,6 @@ target_link_libraries(outputsdrdaemonsink sdrgui swagger ${CM256CC_LIBRARIES} - ${LIBNANOMSG_LIBRARIES} ) endif (BUILD_DEBIAN) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index d3c4f4ef6..f3e48d9b5 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -24,9 +24,6 @@ #include #include -#include -#include - #include "ui_sdrdaemonsinkgui.h" #include "plugin/pluginapi.h" #include "gui/colormapper.h" @@ -54,15 +51,6 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_doApplySettings(true), m_forceSettings(true) { - m_nnSender = nn_socket(AF_SP, NN_PAIR); - assert(m_nnSender != -1); - int millis = 500; - int rc = nn_setsockopt(m_nnSender, NN_SOL_SOCKET, NN_SNDTIMEO, &millis, sizeof (millis)); - - if (rc != 0) { - qCritical("SDRdaemonSinkGui::SDRdaemonSinkGui: nn_setsockopt failed with rc %d", rc); - } - m_countUnrecoverable = 0; m_countRecovered = 0; @@ -92,7 +80,6 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : displayEventTimer(); displaySettings(); - sendControl(true); sendSettings(); } @@ -139,7 +126,6 @@ void SDRdaemonSinkGui::setCenterFrequency(qint64 centerFrequency) { m_settings.m_centerFrequency = centerFrequency; displaySettings(); - sendControl(); sendSettings(); } @@ -156,7 +142,6 @@ bool SDRdaemonSinkGui::deserialize(const QByteArray& data) { displaySettings(); blockApplySettings(false); - sendControl(true); m_forceSettings = true; sendSettings(); return true; @@ -228,7 +213,7 @@ void SDRdaemonSinkGui::updateSampleRateAndFrequency() { m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); - ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate*(1<deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate) / 1000)); } void SDRdaemonSinkGui::updateTxDelayTooltip() @@ -241,8 +226,7 @@ void SDRdaemonSinkGui::displaySettings() { ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); ui->sampleRate->setValue(m_settings.m_sampleRate); - ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate*(1<interp->setCurrentIndex(m_settings.m_log2Interp); + ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate) / 1000)); ui->txDelay->setValue(m_settings.m_txDelay*100); ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay*100)); ui->nbFECBlocks->setValue(m_settings.m_nbFECBlocks); @@ -251,100 +235,10 @@ void SDRdaemonSinkGui::displaySettings() QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s0).arg(s1)); - ui->address->setText(m_settings.m_address); + ui->apiAddress->setText(m_settings.m_apiAddress); + ui->apiPort->setText(tr("%1").arg(m_settings.m_apiPort)); + ui->dataAddress->setText(m_settings.m_dataAddress); ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); - ui->controlPort->setText(tr("%1").arg(m_settings.m_controlPort)); - ui->specificParms->setText(m_settings.m_specificParameters); -} - -void SDRdaemonSinkGui::sendControl(bool force) -{ - if ((m_settings.m_address != m_controlSettings.m_address) || - (m_settings.m_controlPort != m_controlSettings.m_controlPort) || force) - { - int rc = nn_shutdown(m_nnSender, 0); - - if (rc < 0) { - qDebug() << "SDRdaemonSinkGui::sendControl: disconnection failed"; - } else { - qDebug() << "SDRdaemonSinkGui::sendControl: disconnection successful"; - } - - std::ostringstream os; - os << "tcp://" << m_settings.m_address.toStdString() << ":" << m_settings.m_controlPort; - std::string addrstrng = os.str(); - rc = nn_connect(m_nnSender, addrstrng.c_str()); - - if (rc < 0) - { - qDebug() << "SDRdaemonSinkGui::sendControl: connection to " << addrstrng.c_str() << " failed"; - QMessageBox::information(this, tr("Message"), tr("Cannot connect to remote control port")); - return; - } - else - { - qDebug() << "SDRdaemonSinkGui::sendControl: connection to " << addrstrng.c_str() << " successful"; - force = true; - } - } - - std::ostringstream os; - int nbArgs = 0; - - if ((m_settings.m_centerFrequency != m_controlSettings.m_centerFrequency) || force) - { - os << "freq=" << m_settings.m_centerFrequency; - nbArgs++; - } - - if ((m_settings.m_sampleRate != m_controlSettings.m_sampleRate) || (m_settings.m_log2Interp != m_controlSettings.m_log2Interp) || force) - { - if (nbArgs > 0) os << ","; - os << "srate=" << m_settings.m_sampleRate * (1< 0) - { - int config_size = os.str().size(); - int rc = nn_send(m_nnSender, (void *) os.str().c_str(), config_size, 0); - - if (rc != config_size) - { - //QMessageBox::information(this, tr("Message"), tr("Cannot send message to remote control port")); - qDebug() << "SDRdaemonSinkGui::sendControl: Cannot send message to remote control port." - << " remoteAddress: " << m_settings.m_address - << " remotePort: " << m_settings.m_controlPort - << " message: " << os.str().c_str(); - } - else - { - qDebug() << "SDRdaemonSinkGui::sendControl:" - << "remoteAddress:" << m_settings.m_address - << "remotePort:" << m_settings.m_controlPort - << "message:" << os.str().c_str(); - } - } - - m_controlSettings.m_address = m_settings.m_address; - m_controlSettings.m_controlPort = m_settings.m_controlPort; - m_controlSettings.m_centerFrequency = m_settings.m_centerFrequency; - m_controlSettings.m_sampleRate = m_settings.m_sampleRate; - m_controlSettings.m_log2Interp = m_settings.m_log2Interp; - m_controlSettings.m_specificParameters = m_settings.m_specificParameters; } void SDRdaemonSinkGui::sendSettings() @@ -395,7 +289,6 @@ void SDRdaemonSinkGui::updateStatus() void SDRdaemonSinkGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; - sendControl(); sendSettings(); } @@ -403,19 +296,6 @@ void SDRdaemonSinkGui::on_sampleRate_changed(quint64 value) { m_settings.m_sampleRate = value; updateTxDelayTooltip(); - sendControl(); - sendSettings(); -} - -void SDRdaemonSinkGui::on_interp_currentIndexChanged(int index) -{ - if (index < 0) { - return; - } - - m_settings.m_log2Interp = index; - updateSampleRateAndFrequency(); - sendControl(); sendSettings(); } @@ -439,63 +319,63 @@ void SDRdaemonSinkGui::on_nbFECBlocks_valueChanged(int value) sendSettings(); } -void SDRdaemonSinkGui::on_address_returnPressed() +void SDRdaemonSinkGui::on_apiAddress_returnPressed() { - m_settings.m_address = ui->address->text(); - sendControl(); + m_settings.m_apiAddress = ui->apiAddress->text(); + sendSettings(); +} + +void SDRdaemonSinkGui::on_apiPort_returnPressed() +{ + bool dataOk; + int apiPort = ui->apiPort->text().toInt(&dataOk); + + if((!dataOk) || (apiPort < 1024) || (apiPort > 65535)) + { + return; + } + else + { + m_settings.m_apiPort = apiPort; + } + + sendSettings(); +} + +void SDRdaemonSinkGui::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); sendSettings(); } void SDRdaemonSinkGui::on_dataPort_returnPressed() { bool dataOk; - int udpDataPort = ui->dataPort->text().toInt(&dataOk); + int dataPort = ui->dataPort->text().toInt(&dataOk); - if((!dataOk) || (udpDataPort < 1024) || (udpDataPort > 65535)) + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) { return; } else { - m_settings.m_dataPort = udpDataPort; + m_settings.m_dataPort = dataPort; } sendSettings(); } -void SDRdaemonSinkGui::on_controlPort_returnPressed() -{ - bool ctlOk; - int udpCtlPort = ui->controlPort->text().toInt(&ctlOk); - - if((!ctlOk) || (udpCtlPort < 1024) || (udpCtlPort > 65535)) - { - return; - } - else - { - m_settings.m_controlPort = udpCtlPort; - } - - sendControl(); -} - -void SDRdaemonSinkGui::on_specificParms_returnPressed() -{ - m_settings.m_specificParameters = ui->specificParms->text(); - sendControl(); -} - void SDRdaemonSinkGui::on_applyButton_clicked(bool checked __attribute__((unused))) { - m_settings.m_address = ui->address->text(); + m_settings.m_apiAddress = ui->apiAddress->text(); + m_settings.m_dataAddress = ui->dataAddress->text(); - bool ctlOk; - int udpCtlPort = ui->controlPort->text().toInt(&ctlOk); + bool apiOk; + int apiPort = ui->apiPort->text().toInt(&apiOk); - if((ctlOk) && (udpCtlPort >= 1024) && (udpCtlPort < 65535)) + if((apiOk) && (apiPort >= 1024) && (apiPort < 65535)) { - m_settings.m_controlPort = udpCtlPort; + m_settings.m_apiPort = apiPort; } bool dataOk; @@ -509,11 +389,6 @@ void SDRdaemonSinkGui::on_applyButton_clicked(bool checked __attribute__((unused sendSettings(); } -void SDRdaemonSinkGui::on_sendButton_clicked(bool checked __attribute__((unused))) -{ - sendControl(true); -} - void SDRdaemonSinkGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) @@ -570,101 +445,9 @@ void SDRdaemonSinkGui::tick() { if ((++m_tickCount & 0xf) == 0) // 16*50ms ~800ms { - void *msgBuf = 0; - SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming::create(); m_deviceSampleSink->getInputMessageQueue()->push(message); - - int len = nn_recv(m_nnSender, &msgBuf, NN_MSG, NN_DONTWAIT); - - if ((len > 0) && msgBuf) - { - std::string msg((char *) msgBuf, len); - std::vector strs; - boost::split(strs, msg, boost::is_any_of(":")); - unsigned int nbTokens = strs.size(); - unsigned int status = 0; - bool updateEventCounts = false; - - if (nbTokens > 0) // at least the queue length is given - { - try - { - int queueLength = boost::lexical_cast(strs[0]); - ui->queueLengthText->setText(QString::fromStdString(strs[0])); - m_nbSinceLastFlowCheck++; - int samplesCorr = 0; - bool quickStart = false; - - if (queueLength < 2) - { - samplesCorr = 127*8; - quickStart = true; - } - else if (queueLength < 16) - { - samplesCorr = ((8 - queueLength)*16)/m_nbSinceLastFlowCheck; - } - else - { - samplesCorr = -127*16; - quickStart = true; - } - - if (samplesCorr != 0) - { - samplesCorr = quickStart ? samplesCorr : samplesCorr <= -50 ? -50 : samplesCorr >= 50 ? 50 : samplesCorr; - SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection::create(samplesCorr); - m_deviceSampleSink->getInputMessageQueue()->push(message); - m_nbSinceLastFlowCheck = 0; - } - } - catch(const boost::bad_lexical_cast &) - { - qDebug("SDRdaemonSinkGui::tick: queue length invalid: %s", strs[0].c_str()); - } - } - - if (nbTokens > 1) // the quality status is given also - { - if (strs[1] == "2") - { - status = 2; - } - else if (strs[1] == "1") - { - status = 1; - if (m_countUnrecoverable < 999) m_countUnrecoverable++; - updateEventCounts = true; - qDebug("SDRdaemonSinkGui::tick: %s", msg.c_str()); - } - else - { - if (m_countRecovered < 999) m_countRecovered++; - updateEventCounts = true; - qDebug("SDRdaemonSinkGui::tick: %s", msg.c_str()); - } - } - - if (nbTokens > 2) // the quality indicator message is given also - { - ui->qualityStatusText->setText(QString::fromStdString(strs[2])); - } - - if (status == 2) { // all OK - ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }"); - } else if (status == 1) { // unrecoverable errors - ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }"); - } else { // recoverable errors or unknown status - ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(56,56,56); }"); - } - - if (updateEventCounts) - { - displayEventCounts(); - } - } - + displayEventTimer(); } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index e562d3675..6e7e02395 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -99,15 +99,13 @@ private slots: void handleInputMessages(); void on_centerFrequency_changed(quint64 value); void on_sampleRate_changed(quint64 value); - void on_interp_currentIndexChanged(int index); void on_txDelay_valueChanged(int value); void on_nbFECBlocks_valueChanged(int value); - void on_address_returnPressed(); + void on_apiAddress_returnPressed(); + void on_apiPort_returnPressed(); + void on_dataAddress_returnPressed(); void on_dataPort_returnPressed(); - void on_controlPort_returnPressed(); - void on_specificParms_returnPressed(); void on_applyButton_clicked(bool checked); - void on_sendButton_clicked(bool checked); void on_startStop_toggled(bool checked); void on_eventCountsReset_clicked(bool checked); void updateHardware(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index fc4484eb5..27175b4cb 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -6,7 +6,7 @@ 0 0 - 411 + 400 217 @@ -18,7 +18,7 @@ - 380 + 400 190 @@ -221,55 +221,6 @@ - - - - Int - - - - - - - Interpolation - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - 32 - - - - - 64 - - - - @@ -536,17 +487,17 @@ - + - Addr: + API - + - 120 + 100 0 @@ -562,14 +513,7 @@ - - - D: - - - - - + Remote data connection port @@ -579,32 +523,39 @@ 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + - C: + Data - - - Remote control port + + + + 100 + 0 + + + 000.000.000.000 + + + 0... + + + + + 00000 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - @@ -622,33 +573,6 @@ - - - - - - Sp: - - - - - - - - - - - 50 - 16777215 - - - - Send - - - - - diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 07655d6a5..c0803769e 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -66,7 +66,7 @@ bool SDRdaemonSinkOutput::start() qDebug() << "SDRdaemonSinkOutput::start"; m_sdrDaemonSinkThread = new SDRdaemonSinkThread(&m_sampleSourceFifo); - m_sdrDaemonSinkThread->setRemoteAddress(m_settings.m_address, m_settings.m_dataPort); + m_sdrDaemonSinkThread->setDataAddress(m_settings.m_dataAddress, m_settings.m_dataPort); m_sdrDaemonSinkThread->setCenterFrequency(m_settings.m_centerFrequency); m_sdrDaemonSinkThread->setSamplerate(m_settings.m_sampleRate); m_sdrDaemonSinkThread->setNbBlocksFEC(m_settings.m_nbFECBlocks); @@ -246,10 +246,10 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b bool forwardChange = false; bool changeTxDelay = false; - if (force || (m_settings.m_address != settings.m_address) || (m_settings.m_dataPort != settings.m_dataPort)) + if (force || (m_settings.m_dataAddress != settings.m_dataAddress) || (m_settings.m_dataPort != settings.m_dataPort)) { if (m_sdrDaemonSinkThread != 0) { - m_sdrDaemonSinkThread->setRemoteAddress(settings.m_address, settings.m_dataPort); + m_sdrDaemonSinkThread->setDataAddress(settings.m_dataAddress, settings.m_dataPort); } } @@ -272,11 +272,6 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b changeTxDelay = true; } - if (force || (m_settings.m_log2Interp != settings.m_log2Interp)) - { - forwardChange = true; - } - if (force || (m_settings.m_nbFECBlocks != settings.m_nbFECBlocks)) { if (m_sdrDaemonSinkThread != 0) { @@ -311,10 +306,11 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b qDebug() << "SDRdaemonSinkOutput::applySettings:" << " m_centerFrequency: " << settings.m_centerFrequency << " m_sampleRate: " << settings.m_sampleRate - << " m_log2Interp: " << settings.m_log2Interp << " m_txDelay: " << settings.m_txDelay << " m_nbFECBlocks: " << settings.m_nbFECBlocks - << " m_address: " << settings.m_address + << " m_apiAddress: " << settings.m_apiAddress + << " m_apiPort: " << settings.m_apiPort + << " m_dataAddress: " << settings.m_dataAddress << " m_dataPort: " << settings.m_dataPort; if (forwardChange) @@ -376,27 +372,24 @@ int SDRdaemonSinkOutput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("sampleRate")) { settings.m_sampleRate = response.getSdrDaemonSinkSettings()->getSampleRate(); } - if (deviceSettingsKeys.contains("log2Interp")) { - settings.m_log2Interp = response.getSdrDaemonSinkSettings()->getLog2Interp(); - } if (deviceSettingsKeys.contains("txDelay")) { settings.m_txDelay = response.getSdrDaemonSinkSettings()->getTxDelay(); } if (deviceSettingsKeys.contains("nbFECBlocks")) { settings.m_nbFECBlocks = response.getSdrDaemonSinkSettings()->getNbFecBlocks(); } - if (deviceSettingsKeys.contains("address")) { - settings.m_address = *response.getSdrDaemonSinkSettings()->getAddress(); + if (deviceSettingsKeys.contains("apiAddress")) { + settings.m_apiAddress = *response.getSdrDaemonSinkSettings()->getApiAddress(); + } + if (deviceSettingsKeys.contains("apiPort")) { + settings.m_apiPort = response.getSdrDaemonSinkSettings()->getApiPort(); + } + if (deviceSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonSinkSettings()->getDataAddress(); } if (deviceSettingsKeys.contains("dataPort")) { settings.m_dataPort = response.getSdrDaemonSinkSettings()->getDataPort(); } - if (deviceSettingsKeys.contains("controlPort")) { - settings.m_controlPort = response.getSdrDaemonSinkSettings()->getControlPort(); - } - if (deviceSettingsKeys.contains("specificParameters")) { - settings.m_specificParameters = *response.getSdrDaemonSinkSettings()->getSpecificParameters(); - } MsgConfigureSDRdaemonSink *msg = MsgConfigureSDRdaemonSink::create(settings, force); m_inputMessageQueue.push(msg); @@ -425,13 +418,12 @@ void SDRdaemonSinkOutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSetti { response.getSdrDaemonSinkSettings()->setCenterFrequency(settings.m_centerFrequency); response.getSdrDaemonSinkSettings()->setSampleRate(settings.m_sampleRate); - response.getSdrDaemonSinkSettings()->setLog2Interp(settings.m_log2Interp); response.getSdrDaemonSinkSettings()->setTxDelay(settings.m_txDelay); response.getSdrDaemonSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); - response.getSdrDaemonSinkSettings()->setAddress(new QString(settings.m_address)); + response.getSdrDaemonSinkSettings()->setApiAddress(new QString(settings.m_apiAddress)); + response.getSdrDaemonSinkSettings()->setApiPort(settings.m_apiPort); + response.getSdrDaemonSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); response.getSdrDaemonSinkSettings()->setDataPort(settings.m_dataPort); - response.getSdrDaemonSinkSettings()->setControlPort(settings.m_controlPort); - response.getSdrDaemonSinkSettings()->setSpecificParameters(new QString(settings.m_specificParameters)); } void SDRdaemonSinkOutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp index 76a3c0bb0..33c7f05c8 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor SDRdaemonSinkPlugin::m_pluginDescriptor = { QString("SDRdaemon sink output"), - QString("4.0.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp index 870a13efe..af41069cf 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp @@ -26,27 +26,26 @@ void SDRdaemonSinkSettings::resetToDefaults() { m_centerFrequency = 435000*1000; m_sampleRate = 48000; - m_log2Interp = 0; m_txDelay = 0.5; m_nbFECBlocks = 0; - m_address = "127.0.0.1"; + m_apiAddress = "127.0.0.1"; + m_apiPort = 9091; + m_dataAddress = "127.0.0.1"; m_dataPort = 9090; - m_controlPort = 9093; - m_specificParameters = ""; } QByteArray SDRdaemonSinkSettings::serialize() const { SimpleSerializer s(1); - s.writeU64(1, m_sampleRate); - s.writeU32(2, m_log2Interp); + s.writeU64(1, m_centerFrequency); + s.writeU32(2, m_sampleRate); s.writeFloat(3, m_txDelay); s.writeU32(4, m_nbFECBlocks); - s.writeString(5, m_address); - s.writeU32(6, m_dataPort); - s.writeU32(7, m_controlPort); - s.writeString(8, m_specificParameters); + s.writeString(5, m_apiAddress); + s.writeU32(6, m_apiPort); + s.writeString(7, m_dataAddress); + s.writeU32(8, m_dataPort); return s.final(); } @@ -64,16 +63,16 @@ bool SDRdaemonSinkSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { quint32 uintval; - d.readU64(1, &m_sampleRate, 48000); - d.readU32(2, &m_log2Interp, 0); + d.readU64(1, &m_centerFrequency, 435000*1000); + d.readU32(2, &m_sampleRate, 48000); d.readFloat(3, &m_txDelay, 0.5); d.readU32(4, &m_nbFECBlocks, 0); - d.readString(5, &m_address, "127.0.0.1"); + d.readString(5, &m_apiAddress, "127.0.0.1"); d.readU32(6, &uintval, 9090); + m_apiPort = uintval % (1<<16); + d.readString(7, &m_dataAddress, "127.0.0.1"); + d.readU32(8, &uintval, 9090); m_dataPort = uintval % (1<<16); - d.readU32(7, &uintval, 9090); - m_controlPort = uintval % (1<<16); - d.readString(8, &m_specificParameters, ""); return true; } else diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h index 5f20970f2..9865bfff3 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h @@ -21,14 +21,13 @@ struct SDRdaemonSinkSettings { quint64 m_centerFrequency; - quint64 m_sampleRate; - quint32 m_log2Interp; + quint32 m_sampleRate; float m_txDelay; quint32 m_nbFECBlocks; - QString m_address; + QString m_apiAddress; + quint16 m_apiPort; + QString m_dataAddress; quint16 m_dataPort; - quint16 m_controlPort; - QString m_specificParameters; SDRdaemonSinkSettings(); void resetToDefaults(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h index 581a6cb88..d2566abc9 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h @@ -50,7 +50,7 @@ public: void setSamplerate(int samplerate); void setNbBlocksFEC(uint32_t nbBlocksFEC) { m_udpSinkFEC.setNbBlocksFEC(nbBlocksFEC); }; void setTxDelay(uint32_t txDelay) { m_udpSinkFEC.setTxDelay(txDelay); }; - void setRemoteAddress(const QString& address, uint16_t port) { m_udpSinkFEC.setRemoteAddress(address, port); } + void setDataAddress(const QString& address, uint16_t port) { m_udpSinkFEC.setRemoteAddress(address, port); } bool isRunning() const { return m_running; } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 7d76d093b..95c5bcb5f 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3274,9 +3274,6 @@ margin-bottom: 20px; "sampleRate" : { "type" : "integer" }, - "log2Interp" : { - "type" : "integer" - }, "txDelay" : { "type" : "number", "format" : "float", @@ -3285,17 +3282,17 @@ margin-bottom: 20px; "nbFECBlocks" : { "type" : "integer" }, - "address" : { + "apiAddress" : { + "type" : "string" + }, + "apiPort" : { + "type" : "integer" + }, + "dataAddress" : { "type" : "string" }, "dataPort" : { "type" : "integer" - }, - "controlPort" : { - "type" : "integer" - }, - "specificParameters" : { - "type" : "string" } }, "description" : "SDRdaemonSink" @@ -28215,7 +28212,7 @@ except ApiException as e:
    - Generated 2018-08-23T17:21:24.907+02:00 + Generated 2018-08-29T15:59:39.880+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml index 40bac741e..17a22003f 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml @@ -6,22 +6,20 @@ SDRdaemonSinkSettings: format: uint64 sampleRate: type: integer - log2Interp: - type: integer txDelay: description: minimum delay in ms between two consecutive packets sending type: number format: float nbFECBlocks: type: integer - address: + apiAddress: + type: string + apiPort: + type: integer + dataAddress: type: string dataPort: type: integer - controlPort: - type: integer - specificParameters: - type: string SDRdaemonSinkReport: description: SDRdaemonSource diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml index 40bac741e..17a22003f 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml @@ -6,22 +6,20 @@ SDRdaemonSinkSettings: format: uint64 sampleRate: type: integer - log2Interp: - type: integer txDelay: description: minimum delay in ms between two consecutive packets sending type: number format: float nbFECBlocks: type: integer - address: + apiAddress: + type: string + apiPort: + type: integer + dataAddress: type: string dataPort: type: integer - controlPort: - type: integer - specificParameters: - type: string SDRdaemonSinkReport: description: SDRdaemonSource diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 7d76d093b..95c5bcb5f 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -3274,9 +3274,6 @@ margin-bottom: 20px; "sampleRate" : { "type" : "integer" }, - "log2Interp" : { - "type" : "integer" - }, "txDelay" : { "type" : "number", "format" : "float", @@ -3285,17 +3282,17 @@ margin-bottom: 20px; "nbFECBlocks" : { "type" : "integer" }, - "address" : { + "apiAddress" : { + "type" : "string" + }, + "apiPort" : { + "type" : "integer" + }, + "dataAddress" : { "type" : "string" }, "dataPort" : { "type" : "integer" - }, - "controlPort" : { - "type" : "integer" - }, - "specificParameters" : { - "type" : "string" } }, "description" : "SDRdaemonSink" @@ -28215,7 +28212,7 @@ except ApiException as e:
    - Generated 2018-08-23T17:21:24.907+02:00 + Generated 2018-08-29T15:59:39.880+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp index 1e02e5784..1c9997f76 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp @@ -32,20 +32,18 @@ SWGSDRdaemonSinkSettings::SWGSDRdaemonSinkSettings() { m_center_frequency_isSet = false; sample_rate = 0; m_sample_rate_isSet = false; - log2_interp = 0; - m_log2_interp_isSet = false; tx_delay = 0.0f; m_tx_delay_isSet = false; nb_fec_blocks = 0; m_nb_fec_blocks_isSet = false; - address = nullptr; - m_address_isSet = false; + api_address = nullptr; + m_api_address_isSet = false; + api_port = 0; + m_api_port_isSet = false; + data_address = nullptr; + m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; - control_port = 0; - m_control_port_isSet = false; - specific_parameters = nullptr; - m_specific_parameters_isSet = false; } SWGSDRdaemonSinkSettings::~SWGSDRdaemonSinkSettings() { @@ -58,20 +56,18 @@ SWGSDRdaemonSinkSettings::init() { m_center_frequency_isSet = false; sample_rate = 0; m_sample_rate_isSet = false; - log2_interp = 0; - m_log2_interp_isSet = false; tx_delay = 0.0f; m_tx_delay_isSet = false; nb_fec_blocks = 0; m_nb_fec_blocks_isSet = false; - address = new QString(""); - m_address_isSet = false; + api_address = new QString(""); + m_api_address_isSet = false; + api_port = 0; + m_api_port_isSet = false; + data_address = new QString(""); + m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; - control_port = 0; - m_control_port_isSet = false; - specific_parameters = new QString(""); - m_specific_parameters_isSet = false; } void @@ -80,15 +76,14 @@ SWGSDRdaemonSinkSettings::cleanup() { - - if(address != nullptr) { - delete address; + if(api_address != nullptr) { + delete api_address; } - - if(specific_parameters != nullptr) { - delete specific_parameters; + if(data_address != nullptr) { + delete data_address; } + } SWGSDRdaemonSinkSettings* @@ -106,20 +101,18 @@ SWGSDRdaemonSinkSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); - ::SWGSDRangel::setValue(&log2_interp, pJson["log2Interp"], "qint32", ""); - ::SWGSDRangel::setValue(&tx_delay, pJson["txDelay"], "float", ""); ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); - ::SWGSDRangel::setValue(&address, pJson["address"], "QString", "QString"); + ::SWGSDRangel::setValue(&api_address, pJson["apiAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&api_port, pJson["apiPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); - ::SWGSDRangel::setValue(&control_port, pJson["controlPort"], "qint32", ""); - - ::SWGSDRangel::setValue(&specific_parameters, pJson["specificParameters"], "QString", "QString"); - } QString @@ -142,27 +135,24 @@ SWGSDRdaemonSinkSettings::asJsonObject() { if(m_sample_rate_isSet){ obj->insert("sampleRate", QJsonValue(sample_rate)); } - if(m_log2_interp_isSet){ - obj->insert("log2Interp", QJsonValue(log2_interp)); - } if(m_tx_delay_isSet){ obj->insert("txDelay", QJsonValue(tx_delay)); } if(m_nb_fec_blocks_isSet){ obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); } - if(address != nullptr && *address != QString("")){ - toJsonValue(QString("address"), address, obj, QString("QString")); + if(api_address != nullptr && *api_address != QString("")){ + toJsonValue(QString("apiAddress"), api_address, obj, QString("QString")); + } + if(m_api_port_isSet){ + obj->insert("apiPort", QJsonValue(api_port)); + } + if(data_address != nullptr && *data_address != QString("")){ + toJsonValue(QString("dataAddress"), data_address, obj, QString("QString")); } if(m_data_port_isSet){ obj->insert("dataPort", QJsonValue(data_port)); } - if(m_control_port_isSet){ - obj->insert("controlPort", QJsonValue(control_port)); - } - if(specific_parameters != nullptr && *specific_parameters != QString("")){ - toJsonValue(QString("specificParameters"), specific_parameters, obj, QString("QString")); - } return obj; } @@ -187,16 +177,6 @@ SWGSDRdaemonSinkSettings::setSampleRate(qint32 sample_rate) { this->m_sample_rate_isSet = true; } -qint32 -SWGSDRdaemonSinkSettings::getLog2Interp() { - return log2_interp; -} -void -SWGSDRdaemonSinkSettings::setLog2Interp(qint32 log2_interp) { - this->log2_interp = log2_interp; - this->m_log2_interp_isSet = true; -} - float SWGSDRdaemonSinkSettings::getTxDelay() { return tx_delay; @@ -218,13 +198,33 @@ SWGSDRdaemonSinkSettings::setNbFecBlocks(qint32 nb_fec_blocks) { } QString* -SWGSDRdaemonSinkSettings::getAddress() { - return address; +SWGSDRdaemonSinkSettings::getApiAddress() { + return api_address; } void -SWGSDRdaemonSinkSettings::setAddress(QString* address) { - this->address = address; - this->m_address_isSet = true; +SWGSDRdaemonSinkSettings::setApiAddress(QString* api_address) { + this->api_address = api_address; + this->m_api_address_isSet = true; +} + +qint32 +SWGSDRdaemonSinkSettings::getApiPort() { + return api_port; +} +void +SWGSDRdaemonSinkSettings::setApiPort(qint32 api_port) { + this->api_port = api_port; + this->m_api_port_isSet = true; +} + +QString* +SWGSDRdaemonSinkSettings::getDataAddress() { + return data_address; +} +void +SWGSDRdaemonSinkSettings::setDataAddress(QString* data_address) { + this->data_address = data_address; + this->m_data_address_isSet = true; } qint32 @@ -237,26 +237,6 @@ SWGSDRdaemonSinkSettings::setDataPort(qint32 data_port) { this->m_data_port_isSet = true; } -qint32 -SWGSDRdaemonSinkSettings::getControlPort() { - return control_port; -} -void -SWGSDRdaemonSinkSettings::setControlPort(qint32 control_port) { - this->control_port = control_port; - this->m_control_port_isSet = true; -} - -QString* -SWGSDRdaemonSinkSettings::getSpecificParameters() { - return specific_parameters; -} -void -SWGSDRdaemonSinkSettings::setSpecificParameters(QString* specific_parameters) { - this->specific_parameters = specific_parameters; - this->m_specific_parameters_isSet = true; -} - bool SWGSDRdaemonSinkSettings::isSet(){ @@ -264,13 +244,12 @@ SWGSDRdaemonSinkSettings::isSet(){ do{ if(m_center_frequency_isSet){ isObjectUpdated = true; break;} if(m_sample_rate_isSet){ isObjectUpdated = true; break;} - if(m_log2_interp_isSet){ isObjectUpdated = true; break;} if(m_tx_delay_isSet){ isObjectUpdated = true; break;} if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} - if(address != nullptr && *address != QString("")){ isObjectUpdated = true; break;} + if(api_address != nullptr && *api_address != QString("")){ isObjectUpdated = true; break;} + if(m_api_port_isSet){ isObjectUpdated = true; break;} + if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} if(m_data_port_isSet){ isObjectUpdated = true; break;} - if(m_control_port_isSet){ isObjectUpdated = true; break;} - if(specific_parameters != nullptr && *specific_parameters != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h index f6268a8da..d87d3763b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h @@ -48,27 +48,24 @@ public: qint32 getSampleRate(); void setSampleRate(qint32 sample_rate); - qint32 getLog2Interp(); - void setLog2Interp(qint32 log2_interp); - float getTxDelay(); void setTxDelay(float tx_delay); qint32 getNbFecBlocks(); void setNbFecBlocks(qint32 nb_fec_blocks); - QString* getAddress(); - void setAddress(QString* address); + QString* getApiAddress(); + void setApiAddress(QString* api_address); + + qint32 getApiPort(); + void setApiPort(qint32 api_port); + + QString* getDataAddress(); + void setDataAddress(QString* data_address); qint32 getDataPort(); void setDataPort(qint32 data_port); - qint32 getControlPort(); - void setControlPort(qint32 control_port); - - QString* getSpecificParameters(); - void setSpecificParameters(QString* specific_parameters); - virtual bool isSet() override; @@ -79,27 +76,24 @@ private: qint32 sample_rate; bool m_sample_rate_isSet; - qint32 log2_interp; - bool m_log2_interp_isSet; - float tx_delay; bool m_tx_delay_isSet; qint32 nb_fec_blocks; bool m_nb_fec_blocks_isSet; - QString* address; - bool m_address_isSet; + QString* api_address; + bool m_api_address_isSet; + + qint32 api_port; + bool m_api_port_isSet; + + QString* data_address; + bool m_data_address_isSet; qint32 data_port; bool m_data_port_isSet; - qint32 control_port; - bool m_control_port_isSet; - - QString* specific_parameters; - bool m_specific_parameters_isSet; - }; } From af5e61ca483c935f9adcdc176fe6874e3f8e0610 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 29 Aug 2018 22:08:28 +0200 Subject: [PATCH 668/956] SDRdaemon: channel source: meta data frequency is in kHz --- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index d51ca6680..08538d0d9 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -262,7 +262,7 @@ void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { - m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency); + m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency*1000); // frequency is in kHz } if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) From a8d45fd25373694f165f03586facd9271f21bc57 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 29 Aug 2018 22:08:58 +0200 Subject: [PATCH 669/956] SDRDaemonSink: refactoring (2) --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 8 ++-- .../sdrdaemonsink/sdrdaemonsinkthread.cpp | 45 +------------------ 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index f3e48d9b5..60c9170b8 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -43,7 +43,6 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_deviceUISet(deviceUISet), m_settings(), m_deviceSampleSink(0), - m_sampleRate(0), m_samplesCount(0), m_tickCount(0), m_nbSinceLastFlowCheck(0), @@ -193,9 +192,9 @@ void SDRdaemonSinkGui::handleInputMessages() if (DSPSignalNotification::match(*message)) { DSPSignalNotification* notif = (DSPSignalNotification*) message; - qDebug("SDRdaemonSinkGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); m_sampleRate = notif->getSampleRate(); m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("SDRdaemonSinkGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); updateSampleRateAndFrequency(); delete message; @@ -224,9 +223,9 @@ void SDRdaemonSinkGui::updateTxDelayTooltip() void SDRdaemonSinkGui::displaySettings() { + blockApplySettings(true); ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); ui->sampleRate->setValue(m_settings.m_sampleRate); - ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate) / 1000)); ui->txDelay->setValue(m_settings.m_txDelay*100); ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay*100)); ui->nbFECBlocks->setValue(m_settings.m_nbFECBlocks); @@ -239,6 +238,7 @@ void SDRdaemonSinkGui::displaySettings() ui->apiPort->setText(tr("%1").arg(m_settings.m_apiPort)); ui->dataAddress->setText(m_settings.m_dataAddress); ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); + blockApplySettings(false); } void SDRdaemonSinkGui::sendSettings() @@ -447,7 +447,7 @@ void SDRdaemonSinkGui::tick() { SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming::create(); m_deviceSampleSink->getInputMessageQueue()->push(message); - + displayEventTimer(); } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp index 76b96d566..be7bda63d 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp @@ -86,6 +86,7 @@ void SDRdaemonSinkThread::setSamplerate(int samplerate) m_samplerate = samplerate; m_samplesChunkSize = (m_samplerate * m_throttlems) / 1000; + m_udpSinkFEC.setSampleRate(m_samplerate); if (wasRunning) { startWork(); @@ -126,12 +127,6 @@ void SDRdaemonSinkThread::tick() m_throttleToggle = !m_throttleToggle; } -// if (m_throttlems > m_maxThrottlems) -// { -// qDebug("FileSinkThread::tick: m_maxThrottlems: %d", m_maxThrottlems); -// m_maxThrottlems = m_throttlems; -// } - SampleVector::iterator readUntil; m_sampleFifo->readAdvance(readUntil, m_samplesChunkSize); // pull samples @@ -139,43 +134,5 @@ void SDRdaemonSinkThread::tick() m_samplesCount += m_samplesChunkSize; m_udpSinkFEC.write(beginRead, m_samplesChunkSize); -// m_ofstream->write(reinterpret_cast(&(*beginRead)), m_samplesChunkSize*sizeof(Sample)); // send samples - - // interpolation is done on the far side -// if (m_log2Interpolation == 0) -// { -// m_ofstream->write(reinterpret_cast(&(*beginRead)), m_samplesChunkSize*sizeof(Sample)); // send samples -// } -// else -// { -// int chunkSize = std::min((int) m_samplesChunkSize, m_samplerate); -// -// switch (m_log2Interpolation) -// { -// case 1: -// m_interpolators.interpolate2_cen(&beginRead, m_buf, chunkSize*(1<write(reinterpret_cast(m_buf), m_samplesChunkSize*(1< Date: Thu, 30 Aug 2018 01:56:53 +0200 Subject: [PATCH 670/956] SDRdaemon: channel source report --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 10 +- .../sdrdaemonsink/sdrdaemonsinkgui.h | 3 +- .../sdrdaemonsink/sdrdaemonsinkgui.ui | 199 ++++++-- sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 478 +++++++++++++++++- .../include/SDRDaemonChannelSource.yaml | 34 ++ .../resources/webapi/doc/swagger/swagger.yaml | 36 +- sdrbase/webapi/webapirequestmapper.cpp | 2 + sdrdaemon/channel/sdrdaemonchannelsource.cpp | 100 +++- sdrdaemon/channel/sdrdaemonchannelsource.h | 19 + sdrdaemon/channel/sdrdaemondatareadqueue.cpp | 24 +- sdrdaemon/channel/sdrdaemondatareadqueue.h | 7 +- sdrdaemon/webapi/webapiadapterdaemon.cpp | 60 ++- sdrdaemon/webapi/webapiadapterdaemon.h | 8 +- sdrdaemon/webapi/webapirequestmapper.cpp | 49 +- sdrdaemon/webapi/webapirequestmapper.h | 3 + .../include/SDRDaemonChannelSource.yaml | 34 ++ swagger/sdrangel/api/swagger/swagger.yaml | 36 +- swagger/sdrangel/code/html2/index.html | 478 +++++++++++++++++- .../code/qt5/client/SWGChannelReport.cpp | 23 + .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 + .../code/qt5/client/SWGChannelSettings.h | 7 + .../sdrangel/code/qt5/client/SWGDaemonApi.cpp | 52 ++ .../sdrangel/code/qt5/client/SWGDaemonApi.h | 6 + .../code/qt5/client/SWGModelFactory.h | 8 + .../SWGSDRDaemonChannelSourceReport.cpp | 232 +++++++++ .../client/SWGSDRDaemonChannelSourceReport.h | 94 ++++ .../SWGSDRDaemonChannelSourceSettings.cpp | 129 +++++ .../SWGSDRDaemonChannelSourceSettings.h | 65 +++ 30 files changed, 2141 insertions(+), 86 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml create mode 100644 swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 60c9170b8..687045947 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -365,10 +365,9 @@ void SDRdaemonSinkGui::on_dataPort_returnPressed() sendSettings(); } -void SDRdaemonSinkGui::on_applyButton_clicked(bool checked __attribute__((unused))) +void SDRdaemonSinkGui::on_apiApplyButton_clicked(bool checked __attribute__((unused))) { m_settings.m_apiAddress = ui->apiAddress->text(); - m_settings.m_dataAddress = ui->dataAddress->text(); bool apiOk; int apiPort = ui->apiPort->text().toInt(&apiOk); @@ -378,6 +377,13 @@ void SDRdaemonSinkGui::on_applyButton_clicked(bool checked __attribute__((unused m_settings.m_apiPort = apiPort; } + sendSettings(); +} + +void SDRdaemonSinkGui::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + bool dataOk; int udpDataPort = ui->dataPort->text().toInt(&dataOk); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 6e7e02395..db9b8d03e 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -105,7 +105,8 @@ private slots: void on_apiPort_returnPressed(); void on_dataAddress_returnPressed(); void on_dataPort_returnPressed(); - void on_applyButton_clicked(bool checked); + void on_apiApplyButton_clicked(bool checked); + void on_dataApplyButton_clicked(bool checked); void on_startStop_toggled(bool checked); void on_eventCountsReset_clicked(bool checked); void updateHardware(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 27175b4cb..23b83ab7f 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 217 + 360 + 237 @@ -18,7 +18,7 @@ - 400 + 360 190 @@ -345,32 +345,6 @@ - - - - QL: - - - - - - - - 18 - 0 - - - - Current transmitter queue length in number of vectors - - - 00 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -395,22 +369,6 @@ - - - - - 52 - 0 - - - - Tx status since last poll: minimum of blocks received / maximum number of blocks used for recovery - - - 100/100 - - - @@ -485,9 +443,61 @@ - + + + + + QL + + + + + + + + 16777215 + 14 + + + + Queue length gauge + + + 50 + + + + + + + + 50 + 0 + + + + Queue length / Queue size + + + 000/000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + 30 + 0 + + API @@ -497,12 +507,12 @@ - 100 + 120 0 - Remote data connection IP address + API IP address 000.000.000.000 @@ -512,10 +522,23 @@ + + + + : + + + + + + 50 + 16777215 + + - Remote data connection port + API IP port 00000 @@ -525,8 +548,47 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 30 + 16777215 + + + + Set API address and port + + + Set + + + + + + + + + + 30 + 0 + + Data @@ -536,10 +598,13 @@ - 100 + 120 0 + + Remote data address + 000.000.000.000 @@ -548,8 +613,24 @@ + + + + : + + + + + + 50 + 16777215 + + + + Remote data port + 00000 @@ -559,13 +640,29 @@ - + + + Qt::Horizontal + + + + 40 + 20 + + + + + + 30 16777215 + + Set data address and port + Set diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 52e87e0dc..179cd9eaa 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -22,6 +22,7 @@ webapi/doc/swagger/include/PlutoSdr.yaml webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger/include/SDRDaemonChannelSink.yaml + webapi/doc/swagger/include/SDRDaemonChannelSource.yaml webapi/doc/swagger/include/SDRDaemonSource.yaml webapi/doc/swagger/include/SDRDaemonSink.yaml webapi/doc/swagger/include/SDRPlay.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 95c5bcb5f..62a05233f 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1416,6 +1416,9 @@ margin-bottom: 20px; "SSBDemodReport" : { "$ref" : "#/definitions/SSBDemodReport" }, + "SDRDaemonChannelSourceReport" : { + "$ref" : "#/definitions/SDRDaemonChannelSourceReport" + }, "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" }, @@ -1470,6 +1473,9 @@ margin-bottom: 20px; "SDRDaemonChannelSinkSettings" : { "$ref" : "#/definitions/SDRDaemonChannelSinkSettings" }, + "SDRDaemonChannelSourceSettings" : { + "$ref" : "#/definitions/SDRDaemonChannelSourceSettings" + }, "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" }, @@ -3166,6 +3172,52 @@ margin-bottom: 20px; } }, "description" : "Data handling details for SDRDaemon" +}; + defs.SDRDaemonChannelSourceReport = { + "properties" : { + "queueLength" : { + "type" : "integer", + "description" : "Data read/write queue length in number of data frames" + }, + "queueSize" : { + "type" : "integer", + "description" : "Data read/write queue size in number of data frames" + }, + "samplesCount" : { + "type" : "integer", + "description" : "Absolute consumed samples count" + }, + "correctableErrorsCount" : { + "type" : "integer", + "description" : "Absolute number of correctable errors" + }, + "uncorrectableErrorsCount" : { + "type" : "integer", + "description" : "Absolute number of uncorrectable errors" + }, + "tvSec" : { + "type" : "integer", + "description" : "Counts timestamp seconds" + }, + "tvUSec" : { + "type" : "integer", + "description" : "Counts timestamp microseconds" + } + }, + "description" : "SDRDaemon channel source report" +}; + defs.SDRDaemonChannelSourceSettings = { + "properties" : { + "dataAddress" : { + "type" : "string", + "description" : "Remote USB data address" + }, + "dataPort" : { + "type" : "integer", + "description" : "Remote USB data port" + } + }, + "description" : "Data handling details for SDRDaemon" }; defs.SDRPlayReport = { "properties" : { @@ -4024,6 +4076,9 @@ margin-bottom: 20px; +
  • + daemonChannelReportGet +
  • daemonChannelSettingsGet
  • @@ -4230,6 +4285,427 @@ margin-bottom: 20px;

    Daemon

    +
    +
    +
    +

    daemonChannelReportGet

    +

    +
    +
    +
    +

    +

    get the channel report

    +

    +
    +
    /sdrdaemon/channel/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrdaemon/channel/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            ChannelReport result = apiInstance.daemonChannelReportGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonChannelReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            ChannelReport result = apiInstance.daemonChannelReportGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonChannelReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonChannelReportGetWithCompletionHandler: 
    +              ^(ChannelReport output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonChannelReportGet(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonChannelReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +
    +            try
    +            {
    +                ChannelReport result = apiInstance.daemonChannelReportGet();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonChannelReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +
    +try {
    +    $result = $api_instance->daemonChannelReportGet();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonChannelReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +
    +eval { 
    +    my $result = $api_instance->daemonChannelReportGet();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonChannelReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +
    +try: 
    +    api_response = api_instance.daemon_channel_report_get()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonChannelReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - On success return channel report

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set or channel index

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device or channel not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -28212,7 +28688,7 @@ except ApiException as e:
    - Generated 2018-08-29T15:59:39.880+02:00 + Generated 2018-08-30T01:40:47.594+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml new file mode 100644 index 000000000..bed33bf1a --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml @@ -0,0 +1,34 @@ +SDRDaemonChannelSourceSettings: + description: "Data handling details for SDRDaemon" + properties: + dataAddress: + description: "Remote USB data address" + type: string + dataPort: + description: "Remote USB data port" + type: integer + +SDRDaemonChannelSourceReport: + description: "SDRDaemon channel source report" + properties: + queueLength: + description: "Data read/write queue length in number of data frames" + type: integer + queueSize: + description: "Data read/write queue size in number of data frames" + type: integer + samplesCount: + description: "Absolute consumed samples count" + type: integer + correctableErrorsCount: + description: "Absolute number of correctable errors" + type: integer + uncorrectableErrorsCount: + description: "Absolute number of uncorrectable errors" + type: integer + tvSec: + description: "Counts timestamp seconds" + type: integer + tvUSec: + description: "Counts timestamp microseconds" + type: integer \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 4ef531051..bbf8870b9 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1299,7 +1299,7 @@ paths: $ref: "#/responses/Response_501" /sdrdaemon/channel/settings: - x-swagger-router-controller: deviceset + x-swagger-router-controller: daemon get: description: Get channel handling details operationId: daemonChannelSettingsGet @@ -1360,7 +1360,7 @@ paths: /sdrdaemon/device/settings: - x-swagger-router-controller: deviceset + x-swagger-router-controller: daemon get: description: Get device settings operationId: daemonDeviceSettingsGet @@ -1431,7 +1431,7 @@ paths: $ref: "#/responses/Response_501" /sdrdaemon/run: - x-swagger-router-controller: deviceset + x-swagger-router-controller: daemon get: description: get device run status operationId: daemonRunGet @@ -1500,7 +1500,7 @@ paths: $ref: "#/responses/Response_501" /sdrdaemon/device/report: - x-swagger-router-controller: deviceset + x-swagger-router-controller: daemon get: description: get the device report operationId: daemonDeviceReportGet @@ -1524,6 +1524,30 @@ paths: "501": $ref: "#/responses/Response_501" + /sdrdaemon/channel/report: + x-swagger-router-controller: daemon + get: + description: get the channel report + operationId: daemonChannelReportGet + tags: + - Daemon + responses: + "200": + description: On success return channel report + schema: + $ref: "#/definitions/ChannelReport" + "400": + description: Invalid device set or channel index + schema: + $ref: "#/definitions/ErrorResponse" + "404": + description: Device or channel not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" /swagger: x-swagger-pipe: swagger_raw @@ -2196,6 +2220,8 @@ definitions: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" SDRDaemonChannelSinkSettings: $ref: "/doc/swagger/include/SDRDaemonChannelSink.yaml#/SDRDaemonChannelSinkSettings" + SDRDaemonChannelSourceSettings: + $ref: "/doc/swagger/include/SDRDaemonChannelSource.yaml#/SDRDaemonChannelSourceSettings" SSBModSettings: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: @@ -2235,6 +2261,8 @@ definitions: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModReport" SSBDemodReport: $ref: "/doc/swagger/include/SSBDemod.yaml#/SSBDemodReport" + SDRDaemonChannelSourceReport: + $ref: "/doc/swagger/include/SDRDaemonChannelSource.yaml#/SDRDaemonChannelSourceReport" SSBModReport: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 5f08de819..d58fcd22d 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2399,6 +2399,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setNfmDemodSettings(0); channelSettings.setNfmModSettings(0); channelSettings.setSdrDaemonChannelSinkSettings(0); + channelSettings.setSdrDaemonChannelSourceSettings(0); channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); channelSettings.setUdpSinkSettings(0); @@ -2418,6 +2419,7 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan channelReport.setDsdDemodReport(0); channelReport.setNfmDemodReport(0); channelReport.setNfmModReport(0); + channelReport.setSdrDaemonChannelSourceReport(0); channelReport.setSsbDemodReport(0); channelReport.setSsbModReport(0); channelReport.setUdpSinkReport(0); diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index 08538d0d9..f89e54738 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -20,11 +20,17 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include +#include #include #include #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGSDRDaemonChannelSourceReport.h" + #include "util/simpleserializer.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/upchannelizer.h" @@ -44,7 +50,9 @@ SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_sourceThread(0), m_running(false), - m_samplesCount(0) + m_samplesCount(0), + m_nbCorrectableErrors(0), + m_nbUncorrectableErrors(0) { setObjectName(m_channelId); @@ -226,6 +234,13 @@ void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; } + // update counters + if (dataBlock->m_rxControlBlock.m_recoveryCount > paramsCM256.RecoveryCount) { + m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_originalCount; + } else { + m_nbCorrectableErrors += dataBlock->m_rxControlBlock.m_recoveryCount; + } + if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode { qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" @@ -315,3 +330,86 @@ uint32_t SDRDaemonChannelSource::calculateDataReadQueueSize(int sampleRate) qDebug("SDRDaemonChannelSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); return maxSize; } + +int SDRDaemonChannelSource::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); + response.getSdrDaemonChannelSourceSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int SDRDaemonChannelSource::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + SDRDaemonChannelSourceSettings settings = m_settings; + + if (channelSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonChannelSourceSettings()->getDataAddress(); + } + + if (channelSettingsKeys.contains("dataPort")) + { + int dataPort = response.getSdrDaemonChannelSourceSettings()->getDataPort(); + + if ((dataPort < 1024) || (dataPort > 65535)) { + settings.m_dataPort = 9090; + } else { + settings.m_dataPort = dataPort; + } + } + + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("SDRDaemonChannelSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRDaemonChannelSource *msgToGUI = MsgConfigureSDRDaemonChannelSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int SDRDaemonChannelSource::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSourceReport(new SWGSDRangel::SWGSDRDaemonChannelSourceReport()); + response.getSdrDaemonChannelSourceReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void SDRDaemonChannelSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings) +{ + if (response.getSdrDaemonChannelSourceSettings()->getDataAddress()) { + *response.getSdrDaemonChannelSourceSettings()->getDataAddress() = settings.m_dataAddress; + } else { + response.getSdrDaemonChannelSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); + } + + response.getSdrDaemonChannelSourceSettings()->setDataPort(settings.m_dataPort); +} + +void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + struct timeval tv; + gettimeofday(&tv, 0); + + response.getSdrDaemonChannelSourceReport()->setTvSec(tv.tv_sec); + response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); + response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); + response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); + response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); + response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); + response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); +} diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h index e743b3418..79621da5c 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -80,6 +80,20 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + void setDataLink(const QString& dataAddress, uint16_t dataPort); static const QString m_channelIdURI; @@ -103,10 +117,15 @@ private: SDRDaemonDataReadQueue m_dataReadQueue; + uint32_t m_nbCorrectableErrors; //!< count of correctable errors in number of blocks + uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks + void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); void handleDataBlock(SDRDaemonDataBlock *dataBlock); void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); uint32_t calculateDataReadQueueSize(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); private slots: void handleData(); diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp index 7dc92be51..3c947c725 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp @@ -30,6 +30,7 @@ SDRDaemonDataReadQueue::SDRDaemonDataReadQueue() : m_maxSize(MinimumMaxSize), m_blockIndex(1), m_sampleIndex(0), + m_sampleCount(0), m_full(false) {} @@ -46,7 +47,7 @@ SDRDaemonDataReadQueue::~SDRDaemonDataReadQueue() void SDRDaemonDataReadQueue::push(SDRDaemonDataBlock* dataBlock) { - if (size() >= m_maxSize) + if (length() >= m_maxSize) { qWarning("SDRDaemonDataReadQueue::push: queue is full"); m_full = true; // stop filling the queue @@ -55,7 +56,7 @@ void SDRDaemonDataReadQueue::push(SDRDaemonDataBlock* dataBlock) } if (m_full) { - m_full = (size() > m_maxSize/2); // do not fill queue again before queue is half size + m_full = (length() > m_maxSize/2); // do not fill queue again before queue is half size } if (!m_full) { @@ -78,11 +79,6 @@ SDRDaemonDataBlock* SDRDaemonDataReadQueue::pop() } } -uint32_t SDRDaemonDataReadQueue::size() const -{ - return m_dataReadQueue.size(); -} - void SDRDaemonDataReadQueue::setSize(uint32_t size) { if (size != m_maxSize) { @@ -95,13 +91,14 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) // depletion/repletion state if (m_dataBlock == 0) { - if (size() >= m_maxSize/2) + if (length() >= m_maxSize/2) { - qDebug("SDRDaemonDataReadQueue::readSample: initial pop new block: queue size: %u", size()); + qDebug("SDRDaemonDataReadQueue::readSample: initial pop new block: queue size: %u", length()); m_blockIndex = 1; m_dataBlock = m_dataReadQueue.takeFirst(); s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; m_sampleIndex++; + m_sampleCount++; } else { @@ -115,6 +112,7 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) { s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; m_sampleIndex++; + m_sampleCount++; } else { @@ -125,6 +123,7 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) { s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; m_sampleIndex++; + m_sampleCount++; } else { @@ -133,18 +132,19 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) delete m_dataBlock; m_dataBlock = 0; - if (size() == 0) { + if (length() == 0) { qWarning("SDRDaemonDataReadQueue::readSample: try to pop new block but queue is empty"); } } - if (size() > 0) + if (length() > 0) { - qDebug("SDRDaemonDataReadQueue::readSample: pop new block: queue size: %u", size()); + //qDebug("SDRDaemonDataReadQueue::readSample: pop new block: queue size: %u", length()); m_blockIndex = 1; m_dataBlock = m_dataReadQueue.takeFirst(); s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; m_sampleIndex++; + m_sampleCount++; } else { diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.h b/sdrdaemon/channel/sdrdaemondatareadqueue.h index fe9a88edc..d96f98366 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.h +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.h @@ -37,8 +37,10 @@ public: void push(SDRDaemonDataBlock* dataBlock); //!< push block on the queue SDRDaemonDataBlock* pop(); //!< Pop block from the queue void readSample(Sample& s); //!< Read sample from queue - uint32_t size() const; //!< Returns queue size - void setSize(uint32_t size); //!< Sets the queue size + uint32_t length() const { return m_dataReadQueue.size(); } //!< Returns queue length + uint32_t size() const { return m_maxSize; } //!< Returns queue size (max length) + void setSize(uint32_t size); //!< Sets the queue size (max length) + uint32_t readSampleCount() const { return m_sampleCount; } //!< Returns the absolute number of samples read static const uint32_t MinimumMaxSize; @@ -48,6 +50,7 @@ private: uint32_t m_maxSize; uint32_t m_blockIndex; uint32_t m_sampleIndex; + uint32_t m_sampleCount; bool m_full; //!< full condition was hit }; diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp index c89704648..dbcddb71b 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ b/sdrdaemon/webapi/webapiadapterdaemon.cpp @@ -23,6 +23,7 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGDeviceReport.h" +#include "SWGChannelReport.h" #include "SWGChannelSettings.h" #include "SWGErrorResponse.h" @@ -44,6 +45,7 @@ QString WebAPIAdapterDaemon::daemonInstanceLoggingURL = "/sdrdaemon/logging"; QString WebAPIAdapterDaemon::daemonChannelSettingsURL = "/sdrdaemon/channel/settings"; QString WebAPIAdapterDaemon::daemonDeviceSettingsURL = "/sdrdaemon/device/settings"; QString WebAPIAdapterDaemon::daemonDeviceReportURL = "/sdrdaemon/device/report"; +QString WebAPIAdapterDaemon::daemonChannelReportURL = "/sdrdaemon/channel/report"; QString WebAPIAdapterDaemon::daemonRunURL = "/sdrdaemon/run"; WebAPIAdapterDaemon::WebAPIAdapterDaemon(SDRDaemonMain& sdrDaemonMain) : @@ -179,7 +181,7 @@ int WebAPIAdapterDaemon::daemonInstanceLoggingPut( } int WebAPIAdapterDaemon::daemonChannelSettingsGet( - SWGSDRangel::SWGChannelSettings& response __attribute__((unused)), + SWGSDRangel::SWGChannelSettings& response, SWGSDRangel::SWGErrorResponse& error) { error.init(); @@ -226,9 +228,9 @@ int WebAPIAdapterDaemon::daemonChannelSettingsGet( } int WebAPIAdapterDaemon::daemonChannelSettingsPutPatch( - bool force __attribute__((unused)), - const QStringList& channelSettingsKeys __attribute__((unused)), - SWGSDRangel::SWGChannelSettings& response __attribute__((unused)), + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, SWGSDRangel::SWGErrorResponse& error) { error.init(); @@ -443,7 +445,7 @@ int WebAPIAdapterDaemon::daemonRunDelete( } } -int WebAPIAdapterDaemon::daemonReportGet( +int WebAPIAdapterDaemon::daemonDeviceReportGet( SWGSDRangel::SWGDeviceReport& response, SWGSDRangel::SWGErrorResponse& error) { @@ -470,6 +472,54 @@ int WebAPIAdapterDaemon::daemonReportGet( } } +int WebAPIAdapterDaemon::daemonChannelReportGet( + SWGSDRangel::SWGChannelReport& response, + SWGSDRangel::SWGErrorResponse& error) +{ + error.init(); + + if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx + { + ChannelSinkAPI *channelAPI = m_sdrDaemonMain.m_deviceSourceAPI->getChanelAPIAt(0); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel"); + return 500; // a SDRDaemon sink channel should have been created so this is a server error + } + else + { + response.setChannelType(new QString()); + channelAPI->getIdentifier(*response.getChannelType()); + response.setTx(0); + return channelAPI->webapiReportGet(response, *error.getMessage()); + } + } + else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx + { + ChannelSourceAPI *channelAPI = m_sdrDaemonMain.m_deviceSinkAPI->getChanelAPIAt(0); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel"); + return 500; // a SDRDaemon source channel should have been created so this is a server error + } + else + { + response.setChannelType(new QString()); + channelAPI->getIdentifier(*response.getChannelType()); + response.setTx(1); + return channelAPI->webapiReportGet(response, *error.getMessage()); + } + } + else + { + *error.getMessage() = QString("Device not created error"); + return 500; + } +} + + // TODO: put in library in common with SDRangel. Can be static. QtMsgType WebAPIAdapterDaemon::getMsgTypeFromString(const QString& msgTypeString) { diff --git a/sdrdaemon/webapi/webapiadapterdaemon.h b/sdrdaemon/webapi/webapiadapterdaemon.h index df4c7c592..cbc3e6c51 100644 --- a/sdrdaemon/webapi/webapiadapterdaemon.h +++ b/sdrdaemon/webapi/webapiadapterdaemon.h @@ -30,6 +30,7 @@ namespace SWGSDRangel class SWGDeviceSettings; class SWGDeviceState; class SWGDeviceReport; + class SWGChannelReport; class SWGSuccessResponse; class SWGErrorResponse; class SWGLoggingInfo; @@ -89,15 +90,20 @@ public: SWGSDRangel::SWGDeviceState& response, SWGSDRangel::SWGErrorResponse& error); - int daemonReportGet( + int daemonDeviceReportGet( SWGSDRangel::SWGDeviceReport& response, SWGSDRangel::SWGErrorResponse& error); + int daemonChannelReportGet( + SWGSDRangel::SWGChannelReport& response, + SWGSDRangel::SWGErrorResponse& error); + static QString daemonInstanceSummaryURL; static QString daemonInstanceLoggingURL; static QString daemonChannelSettingsURL; static QString daemonDeviceSettingsURL; static QString daemonDeviceReportURL; + static QString daemonChannelReportURL; static QString daemonRunURL; private: diff --git a/sdrdaemon/webapi/webapirequestmapper.cpp b/sdrdaemon/webapi/webapirequestmapper.cpp index 4d3def6ae..8b72965d1 100644 --- a/sdrdaemon/webapi/webapirequestmapper.cpp +++ b/sdrdaemon/webapi/webapirequestmapper.cpp @@ -27,6 +27,7 @@ #include "SWGDaemonSummaryResponse.h" #include "SWGInstanceDevicesResponse.h" #include "SWGChannelSettings.h" +#include "SWGChannelReport.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGDeviceReport.h" @@ -98,6 +99,8 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http daemonInstanceLoggingService(request, response); } else if (path == WebAPIAdapterDaemon::daemonChannelSettingsURL) { daemonChannelSettingsService(request, response); + } else if (path == WebAPIAdapterDaemon::daemonChannelReportURL) { + daemonChannelReportService(request, response); } else if (path == WebAPIAdapterDaemon::daemonDeviceSettingsURL) { daemonDeviceSettingsService(request, response); } else if (path == WebAPIAdapterDaemon::daemonDeviceReportURL) { @@ -261,6 +264,29 @@ void WebAPIRequestMapper::daemonChannelSettingsService(qtwebapp::HttpRequest& re } } +void WebAPIRequestMapper::daemonChannelReportService( + qtwebapp::HttpRequest& request, + qtwebapp::HttpResponse& response) +{ + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + response.setHeader("Access-Control-Allow-Origin", "*"); + + if (request.getMethod() == "GET") + { + SWGSDRangel::SWGChannelReport normalResponse; + resetChannelReport(normalResponse); + int status = m_adapter->daemonChannelReportGet(normalResponse, errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } +} + void WebAPIRequestMapper::daemonDeviceSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) { SWGSDRangel::SWGErrorResponse errorResponse; @@ -341,7 +367,7 @@ void WebAPIRequestMapper::daemonDeviceReportService(qtwebapp::HttpRequest& reque { SWGSDRangel::SWGDeviceReport normalResponse; resetDeviceReport(normalResponse); - int status = m_adapter->daemonReportGet(normalResponse, errorResponse); + int status = m_adapter->daemonDeviceReportGet(normalResponse, errorResponse); response.setStatus(status); if (status/100 == 2) { @@ -992,6 +1018,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setNfmDemodSettings(0); channelSettings.setNfmModSettings(0); channelSettings.setSdrDaemonChannelSinkSettings(0); + channelSettings.setSdrDaemonChannelSourceSettings(0); channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); channelSettings.setUdpSinkSettings(0); @@ -1000,6 +1027,26 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setWfmModSettings(0); } +void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& channelReport) +{ + channelReport.cleanup(); + channelReport.setChannelType(0); + channelReport.setAmDemodReport(0); + channelReport.setAmModReport(0); + channelReport.setAtvModReport(0); + channelReport.setBfmDemodReport(0); + channelReport.setDsdDemodReport(0); + channelReport.setNfmDemodReport(0); + channelReport.setNfmModReport(0); + channelReport.setSdrDaemonChannelSourceReport(0); + channelReport.setSsbDemodReport(0); + channelReport.setSsbModReport(0); + channelReport.setUdpSinkReport(0); + channelReport.setUdpSrcReport(0); + channelReport.setWfmDemodReport(0); + channelReport.setWfmModReport(0); +} + // TODO: put in library in common with SDRangel. Can be static. void WebAPIRequestMapper::resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings) { diff --git a/sdrdaemon/webapi/webapirequestmapper.h b/sdrdaemon/webapi/webapirequestmapper.h index 49363493a..67030891b 100644 --- a/sdrdaemon/webapi/webapirequestmapper.h +++ b/sdrdaemon/webapi/webapirequestmapper.h @@ -31,6 +31,7 @@ namespace SWGSDRangel { class SWGChannelSettings; + class SWGChannelReport; class SWGDeviceSettings; class SWGDeviceReport; } @@ -58,6 +59,7 @@ private: void daemonDeviceSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void daemonRunService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void daemonDeviceReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void daemonChannelReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); bool validateChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings, QJsonObject& jsonObject, QStringList& channelSettingsKeys); bool validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys); @@ -71,6 +73,7 @@ private: bool parseJsonBody(QString& jsonStr, QJsonObject& jsonObject, qtwebapp::HttpResponse& response); void resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings); + void resetChannelReport(SWGSDRangel::SWGChannelReport& deviceSettings); void resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings); void resetDeviceReport(SWGSDRangel::SWGDeviceReport& deviceReport); }; diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml new file mode 100644 index 000000000..bed33bf1a --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml @@ -0,0 +1,34 @@ +SDRDaemonChannelSourceSettings: + description: "Data handling details for SDRDaemon" + properties: + dataAddress: + description: "Remote USB data address" + type: string + dataPort: + description: "Remote USB data port" + type: integer + +SDRDaemonChannelSourceReport: + description: "SDRDaemon channel source report" + properties: + queueLength: + description: "Data read/write queue length in number of data frames" + type: integer + queueSize: + description: "Data read/write queue size in number of data frames" + type: integer + samplesCount: + description: "Absolute consumed samples count" + type: integer + correctableErrorsCount: + description: "Absolute number of correctable errors" + type: integer + uncorrectableErrorsCount: + description: "Absolute number of uncorrectable errors" + type: integer + tvSec: + description: "Counts timestamp seconds" + type: integer + tvUSec: + description: "Counts timestamp microseconds" + type: integer \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 76bd46b24..fa7612f74 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1299,7 +1299,7 @@ paths: $ref: "#/responses/Response_501" /sdrdaemon/channel/settings: - x-swagger-router-controller: deviceset + x-swagger-router-controller: daemon get: description: Get channel handling details operationId: daemonChannelSettingsGet @@ -1360,7 +1360,7 @@ paths: /sdrdaemon/device/settings: - x-swagger-router-controller: deviceset + x-swagger-router-controller: daemon get: description: Get device settings operationId: daemonDeviceSettingsGet @@ -1431,7 +1431,7 @@ paths: $ref: "#/responses/Response_501" /sdrdaemon/run: - x-swagger-router-controller: deviceset + x-swagger-router-controller: daemon get: description: get device run status operationId: daemonRunGet @@ -1500,7 +1500,7 @@ paths: $ref: "#/responses/Response_501" /sdrdaemon/device/report: - x-swagger-router-controller: deviceset + x-swagger-router-controller: daemon get: description: get the device report operationId: daemonDeviceReportGet @@ -1524,6 +1524,30 @@ paths: "501": $ref: "#/responses/Response_501" + /sdrdaemon/channel/report: + x-swagger-router-controller: daemon + get: + description: get the channel report + operationId: daemonChannelReportGet + tags: + - Daemon + responses: + "200": + description: On success return channel report + schema: + $ref: "#/definitions/ChannelReport" + "400": + description: Invalid device set or channel index + schema: + $ref: "#/definitions/ErrorResponse" + "404": + description: Device or channel not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" /swagger: x-swagger-pipe: swagger_raw @@ -2196,6 +2220,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" SDRDaemonChannelSinkSettings: $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannelSink.yaml#/SDRDaemonChannelSinkSettings" + SDRDaemonChannelSourceSettings: + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannelSource.yaml#/SDRDaemonChannelSourceSettings" SSBModSettings: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: @@ -2235,6 +2261,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModReport" SSBDemodReport: $ref: "http://localhost:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodReport" + SDRDaemonChannelSourceReport: + $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannelSource.yaml#/SDRDaemonChannelSourceReport" SSBModReport: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 95c5bcb5f..62a05233f 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1416,6 +1416,9 @@ margin-bottom: 20px; "SSBDemodReport" : { "$ref" : "#/definitions/SSBDemodReport" }, + "SDRDaemonChannelSourceReport" : { + "$ref" : "#/definitions/SDRDaemonChannelSourceReport" + }, "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" }, @@ -1470,6 +1473,9 @@ margin-bottom: 20px; "SDRDaemonChannelSinkSettings" : { "$ref" : "#/definitions/SDRDaemonChannelSinkSettings" }, + "SDRDaemonChannelSourceSettings" : { + "$ref" : "#/definitions/SDRDaemonChannelSourceSettings" + }, "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" }, @@ -3166,6 +3172,52 @@ margin-bottom: 20px; } }, "description" : "Data handling details for SDRDaemon" +}; + defs.SDRDaemonChannelSourceReport = { + "properties" : { + "queueLength" : { + "type" : "integer", + "description" : "Data read/write queue length in number of data frames" + }, + "queueSize" : { + "type" : "integer", + "description" : "Data read/write queue size in number of data frames" + }, + "samplesCount" : { + "type" : "integer", + "description" : "Absolute consumed samples count" + }, + "correctableErrorsCount" : { + "type" : "integer", + "description" : "Absolute number of correctable errors" + }, + "uncorrectableErrorsCount" : { + "type" : "integer", + "description" : "Absolute number of uncorrectable errors" + }, + "tvSec" : { + "type" : "integer", + "description" : "Counts timestamp seconds" + }, + "tvUSec" : { + "type" : "integer", + "description" : "Counts timestamp microseconds" + } + }, + "description" : "SDRDaemon channel source report" +}; + defs.SDRDaemonChannelSourceSettings = { + "properties" : { + "dataAddress" : { + "type" : "string", + "description" : "Remote USB data address" + }, + "dataPort" : { + "type" : "integer", + "description" : "Remote USB data port" + } + }, + "description" : "Data handling details for SDRDaemon" }; defs.SDRPlayReport = { "properties" : { @@ -4024,6 +4076,9 @@ margin-bottom: 20px; +
  • + daemonChannelReportGet +
  • daemonChannelSettingsGet
  • @@ -4230,6 +4285,427 @@ margin-bottom: 20px;

    Daemon

    +
    +
    +
    +

    daemonChannelReportGet

    +

    +
    +
    +
    +

    +

    get the channel report

    +

    +
    +
    /sdrdaemon/channel/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrdaemon/channel/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DaemonApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            ChannelReport result = apiInstance.daemonChannelReportGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonChannelReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DaemonApi;
    +
    +public class DaemonApiExample {
    +
    +    public static void main(String[] args) {
    +        DaemonApi apiInstance = new DaemonApi();
    +        try {
    +            ChannelReport result = apiInstance.daemonChannelReportGet();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DaemonApi#daemonChannelReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +DaemonApi *apiInstance = [[DaemonApi alloc] init];
    +
    +[apiInstance daemonChannelReportGetWithCompletionHandler: 
    +              ^(ChannelReport output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DaemonApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.daemonChannelReportGet(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class daemonChannelReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DaemonApi();
    +
    +            try
    +            {
    +                ChannelReport result = apiInstance.daemonChannelReportGet();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DaemonApi.daemonChannelReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DaemonApi();
    +
    +try {
    +    $result = $api_instance->daemonChannelReportGet();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DaemonApi->daemonChannelReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DaemonApi;
    +
    +my $api_instance = SWGSDRangel::DaemonApi->new();
    +
    +eval { 
    +    my $result = $api_instance->daemonChannelReportGet();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DaemonApi->daemonChannelReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DaemonApi()
    +
    +try: 
    +    api_response = api_instance.daemon_channel_report_get()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DaemonApi->daemonChannelReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - On success return channel report

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set or channel index

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device or channel not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -28212,7 +28688,7 @@ except ApiException as e:
    - Generated 2018-08-29T15:59:39.880+02:00 + Generated 2018-08-30T01:40:47.594+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index d90cda597..520dc6d29 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -48,6 +48,8 @@ SWGChannelReport::SWGChannelReport() { m_nfm_mod_report_isSet = false; ssb_demod_report = nullptr; m_ssb_demod_report_isSet = false; + sdr_daemon_channel_source_report = nullptr; + m_sdr_daemon_channel_source_report_isSet = false; ssb_mod_report = nullptr; m_ssb_mod_report_isSet = false; udp_sink_report = nullptr; @@ -86,6 +88,8 @@ SWGChannelReport::init() { m_nfm_mod_report_isSet = false; ssb_demod_report = new SWGSSBDemodReport(); m_ssb_demod_report_isSet = false; + sdr_daemon_channel_source_report = new SWGSDRDaemonChannelSourceReport(); + m_sdr_daemon_channel_source_report_isSet = false; ssb_mod_report = new SWGSSBModReport(); m_ssb_mod_report_isSet = false; udp_sink_report = new SWGUDPSinkReport(); @@ -128,6 +132,9 @@ SWGChannelReport::cleanup() { if(ssb_demod_report != nullptr) { delete ssb_demod_report; } + if(sdr_daemon_channel_source_report != nullptr) { + delete sdr_daemon_channel_source_report; + } if(ssb_mod_report != nullptr) { delete ssb_mod_report; } @@ -176,6 +183,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ssb_demod_report, pJson["SSBDemodReport"], "SWGSSBDemodReport", "SWGSSBDemodReport"); + ::SWGSDRangel::setValue(&sdr_daemon_channel_source_report, pJson["SDRDaemonChannelSourceReport"], "SWGSDRDaemonChannelSourceReport", "SWGSDRDaemonChannelSourceReport"); + ::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport"); ::SWGSDRangel::setValue(&udp_sink_report, pJson["UDPSinkReport"], "SWGUDPSinkReport", "SWGUDPSinkReport"); @@ -232,6 +241,9 @@ SWGChannelReport::asJsonObject() { if((ssb_demod_report != nullptr) && (ssb_demod_report->isSet())){ toJsonValue(QString("SSBDemodReport"), ssb_demod_report, obj, QString("SWGSSBDemodReport")); } + if((sdr_daemon_channel_source_report != nullptr) && (sdr_daemon_channel_source_report->isSet())){ + toJsonValue(QString("SDRDaemonChannelSourceReport"), sdr_daemon_channel_source_report, obj, QString("SWGSDRDaemonChannelSourceReport")); + } if((ssb_mod_report != nullptr) && (ssb_mod_report->isSet())){ toJsonValue(QString("SSBModReport"), ssb_mod_report, obj, QString("SWGSSBModReport")); } @@ -351,6 +363,16 @@ SWGChannelReport::setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report) { this->m_ssb_demod_report_isSet = true; } +SWGSDRDaemonChannelSourceReport* +SWGChannelReport::getSdrDaemonChannelSourceReport() { + return sdr_daemon_channel_source_report; +} +void +SWGChannelReport::setSdrDaemonChannelSourceReport(SWGSDRDaemonChannelSourceReport* sdr_daemon_channel_source_report) { + this->sdr_daemon_channel_source_report = sdr_daemon_channel_source_report; + this->m_sdr_daemon_channel_source_report_isSet = true; +} + SWGSSBModReport* SWGChannelReport::getSsbModReport() { return ssb_mod_report; @@ -416,6 +438,7 @@ SWGChannelReport::isSet(){ if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_demod_report != nullptr && ssb_demod_report->isSet()){ isObjectUpdated = true; break;} + if(sdr_daemon_channel_source_report != nullptr && sdr_daemon_channel_source_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} if(udp_sink_report != nullptr && udp_sink_report->isSet()){ isObjectUpdated = true; break;} if(udp_src_report != nullptr && udp_src_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 0f7ad8b02..f6cd8f662 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -29,6 +29,7 @@ #include "SWGDSDDemodReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" +#include "SWGSDRDaemonChannelSourceReport.h" #include "SWGSSBDemodReport.h" #include "SWGSSBModReport.h" #include "SWGUDPSinkReport.h" @@ -85,6 +86,9 @@ public: SWGSSBDemodReport* getSsbDemodReport(); void setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report); + SWGSDRDaemonChannelSourceReport* getSdrDaemonChannelSourceReport(); + void setSdrDaemonChannelSourceReport(SWGSDRDaemonChannelSourceReport* sdr_daemon_channel_source_report); + SWGSSBModReport* getSsbModReport(); void setSsbModReport(SWGSSBModReport* ssb_mod_report); @@ -134,6 +138,9 @@ private: SWGSSBDemodReport* ssb_demod_report; bool m_ssb_demod_report_isSet; + SWGSDRDaemonChannelSourceReport* sdr_daemon_channel_source_report; + bool m_sdr_daemon_channel_source_report_isSet; + SWGSSBModReport* ssb_mod_report; bool m_ssb_mod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 751c40280..37ef26b00 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -48,6 +48,8 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_mod_settings_isSet = false; sdr_daemon_channel_sink_settings = nullptr; m_sdr_daemon_channel_sink_settings_isSet = false; + sdr_daemon_channel_source_settings = nullptr; + m_sdr_daemon_channel_source_settings_isSet = false; ssb_mod_settings = nullptr; m_ssb_mod_settings_isSet = false; ssb_demod_settings = nullptr; @@ -88,6 +90,8 @@ SWGChannelSettings::init() { m_nfm_mod_settings_isSet = false; sdr_daemon_channel_sink_settings = new SWGSDRDaemonChannelSinkSettings(); m_sdr_daemon_channel_sink_settings_isSet = false; + sdr_daemon_channel_source_settings = new SWGSDRDaemonChannelSourceSettings(); + m_sdr_daemon_channel_source_settings_isSet = false; ssb_mod_settings = new SWGSSBModSettings(); m_ssb_mod_settings_isSet = false; ssb_demod_settings = new SWGSSBDemodSettings(); @@ -132,6 +136,9 @@ SWGChannelSettings::cleanup() { if(sdr_daemon_channel_sink_settings != nullptr) { delete sdr_daemon_channel_sink_settings; } + if(sdr_daemon_channel_source_settings != nullptr) { + delete sdr_daemon_channel_source_settings; + } if(ssb_mod_settings != nullptr) { delete ssb_mod_settings; } @@ -183,6 +190,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&sdr_daemon_channel_sink_settings, pJson["SDRDaemonChannelSinkSettings"], "SWGSDRDaemonChannelSinkSettings", "SWGSDRDaemonChannelSinkSettings"); + ::SWGSDRangel::setValue(&sdr_daemon_channel_source_settings, pJson["SDRDaemonChannelSourceSettings"], "SWGSDRDaemonChannelSourceSettings", "SWGSDRDaemonChannelSourceSettings"); + ::SWGSDRangel::setValue(&ssb_mod_settings, pJson["SSBModSettings"], "SWGSSBModSettings", "SWGSSBModSettings"); ::SWGSDRangel::setValue(&ssb_demod_settings, pJson["SSBDemodSettings"], "SWGSSBDemodSettings", "SWGSSBDemodSettings"); @@ -241,6 +250,9 @@ SWGChannelSettings::asJsonObject() { if((sdr_daemon_channel_sink_settings != nullptr) && (sdr_daemon_channel_sink_settings->isSet())){ toJsonValue(QString("SDRDaemonChannelSinkSettings"), sdr_daemon_channel_sink_settings, obj, QString("SWGSDRDaemonChannelSinkSettings")); } + if((sdr_daemon_channel_source_settings != nullptr) && (sdr_daemon_channel_source_settings->isSet())){ + toJsonValue(QString("SDRDaemonChannelSourceSettings"), sdr_daemon_channel_source_settings, obj, QString("SWGSDRDaemonChannelSourceSettings")); + } if((ssb_mod_settings != nullptr) && (ssb_mod_settings->isSet())){ toJsonValue(QString("SSBModSettings"), ssb_mod_settings, obj, QString("SWGSSBModSettings")); } @@ -363,6 +375,16 @@ SWGChannelSettings::setSdrDaemonChannelSinkSettings(SWGSDRDaemonChannelSinkSetti this->m_sdr_daemon_channel_sink_settings_isSet = true; } +SWGSDRDaemonChannelSourceSettings* +SWGChannelSettings::getSdrDaemonChannelSourceSettings() { + return sdr_daemon_channel_source_settings; +} +void +SWGChannelSettings::setSdrDaemonChannelSourceSettings(SWGSDRDaemonChannelSourceSettings* sdr_daemon_channel_source_settings) { + this->sdr_daemon_channel_source_settings = sdr_daemon_channel_source_settings; + this->m_sdr_daemon_channel_source_settings_isSet = true; +} + SWGSSBModSettings* SWGChannelSettings::getSsbModSettings() { return ssb_mod_settings; @@ -438,6 +460,7 @@ SWGChannelSettings::isSet(){ if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} if(sdr_daemon_channel_sink_settings != nullptr && sdr_daemon_channel_sink_settings->isSet()){ isObjectUpdated = true; break;} + if(sdr_daemon_channel_source_settings != nullptr && sdr_daemon_channel_source_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_demod_settings != nullptr && ssb_demod_settings->isSet()){ isObjectUpdated = true; break;} if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 9742e650f..90bdf4c0e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -30,6 +30,7 @@ #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" #include "SWGSDRDaemonChannelSinkSettings.h" +#include "SWGSDRDaemonChannelSourceSettings.h" #include "SWGSSBDemodSettings.h" #include "SWGSSBModSettings.h" #include "SWGUDPSinkSettings.h" @@ -86,6 +87,9 @@ public: SWGSDRDaemonChannelSinkSettings* getSdrDaemonChannelSinkSettings(); void setSdrDaemonChannelSinkSettings(SWGSDRDaemonChannelSinkSettings* sdr_daemon_channel_sink_settings); + SWGSDRDaemonChannelSourceSettings* getSdrDaemonChannelSourceSettings(); + void setSdrDaemonChannelSourceSettings(SWGSDRDaemonChannelSourceSettings* sdr_daemon_channel_source_settings); + SWGSSBModSettings* getSsbModSettings(); void setSsbModSettings(SWGSSBModSettings* ssb_mod_settings); @@ -138,6 +142,9 @@ private: SWGSDRDaemonChannelSinkSettings* sdr_daemon_channel_sink_settings; bool m_sdr_daemon_channel_sink_settings_isSet; + SWGSDRDaemonChannelSourceSettings* sdr_daemon_channel_source_settings; + bool m_sdr_daemon_channel_source_settings_isSet; + SWGSSBModSettings* ssb_mod_settings; bool m_ssb_mod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp index 23886faef..f1498b845 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp @@ -28,6 +28,58 @@ SWGDaemonApi::SWGDaemonApi(QString host, QString basePath) { this->basePath = basePath; } +void +SWGDaemonApi::daemonChannelReportGet() { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrdaemon/channel/report"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "GET"); + + + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDaemonApi::daemonChannelReportGetCallback); + + worker->execute(&input); +} + +void +SWGDaemonApi::daemonChannelReportGetCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGChannelReport* output = static_cast(create(json, QString("SWGChannelReport"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit daemonChannelReportGetSignal(output); + } else { + emit daemonChannelReportGetSignalE(output, error_type, error_str); + emit daemonChannelReportGetSignalEFull(worker, error_type, error_str); + } +} + void SWGDaemonApi::daemonChannelSettingsGet() { QString fullPath; diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h index 74b7083f6..10f9ccad2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonApi.h @@ -15,6 +15,7 @@ #include "SWGHttpRequest.h" +#include "SWGChannelReport.h" #include "SWGChannelSettings.h" #include "SWGDaemonSummaryResponse.h" #include "SWGDeviceReport.h" @@ -39,6 +40,7 @@ public: QString basePath; QMap defaultHeaders; + void daemonChannelReportGet(); void daemonChannelSettingsGet(); void daemonChannelSettingsPatch(SWGChannelSettings& body); void daemonChannelSettingsPut(SWGChannelSettings& body); @@ -54,6 +56,7 @@ public: void daemonRunPost(); private: + void daemonChannelReportGetCallback (SWGHttpRequestWorker * worker); void daemonChannelSettingsGetCallback (SWGHttpRequestWorker * worker); void daemonChannelSettingsPatchCallback (SWGHttpRequestWorker * worker); void daemonChannelSettingsPutCallback (SWGHttpRequestWorker * worker); @@ -69,6 +72,7 @@ private: void daemonRunPostCallback (SWGHttpRequestWorker * worker); signals: + void daemonChannelReportGetSignal(SWGChannelReport* summary); void daemonChannelSettingsGetSignal(SWGChannelSettings* summary); void daemonChannelSettingsPatchSignal(SWGChannelSettings* summary); void daemonChannelSettingsPutSignal(SWGChannelSettings* summary); @@ -83,6 +87,7 @@ signals: void daemonRunGetSignal(SWGDeviceState* summary); void daemonRunPostSignal(SWGDeviceState* summary); + void daemonChannelReportGetSignalE(SWGChannelReport* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonChannelSettingsGetSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonChannelSettingsPatchSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonChannelSettingsPutSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); @@ -97,6 +102,7 @@ signals: void daemonRunGetSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); void daemonRunPostSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void daemonChannelReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonChannelSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonChannelSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void daemonChannelSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 05f3854fb..48555ccd4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -90,6 +90,8 @@ #include "SWGRtlSdrReport.h" #include "SWGRtlSdrSettings.h" #include "SWGSDRDaemonChannelSinkSettings.h" +#include "SWGSDRDaemonChannelSourceReport.h" +#include "SWGSDRDaemonChannelSourceSettings.h" #include "SWGSDRPlayReport.h" #include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSinkReport.h" @@ -344,6 +346,12 @@ namespace SWGSDRangel { if(QString("SWGSDRDaemonChannelSinkSettings").compare(type) == 0) { return new SWGSDRDaemonChannelSinkSettings(); } + if(QString("SWGSDRDaemonChannelSourceReport").compare(type) == 0) { + return new SWGSDRDaemonChannelSourceReport(); + } + if(QString("SWGSDRDaemonChannelSourceSettings").compare(type) == 0) { + return new SWGSDRDaemonChannelSourceSettings(); + } if(QString("SWGSDRPlayReport").compare(type) == 0) { return new SWGSDRPlayReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp new file mode 100644 index 000000000..1d0659eba --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp @@ -0,0 +1,232 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRDaemonChannelSourceReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRDaemonChannelSourceReport::SWGSDRDaemonChannelSourceReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRDaemonChannelSourceReport::SWGSDRDaemonChannelSourceReport() { + queue_length = 0; + m_queue_length_isSet = false; + queue_size = 0; + m_queue_size_isSet = false; + samples_count = 0; + m_samples_count_isSet = false; + correctable_errors_count = 0; + m_correctable_errors_count_isSet = false; + uncorrectable_errors_count = 0; + m_uncorrectable_errors_count_isSet = false; + tv_sec = 0; + m_tv_sec_isSet = false; + tv_u_sec = 0; + m_tv_u_sec_isSet = false; +} + +SWGSDRDaemonChannelSourceReport::~SWGSDRDaemonChannelSourceReport() { + this->cleanup(); +} + +void +SWGSDRDaemonChannelSourceReport::init() { + queue_length = 0; + m_queue_length_isSet = false; + queue_size = 0; + m_queue_size_isSet = false; + samples_count = 0; + m_samples_count_isSet = false; + correctable_errors_count = 0; + m_correctable_errors_count_isSet = false; + uncorrectable_errors_count = 0; + m_uncorrectable_errors_count_isSet = false; + tv_sec = 0; + m_tv_sec_isSet = false; + tv_u_sec = 0; + m_tv_u_sec_isSet = false; +} + +void +SWGSDRDaemonChannelSourceReport::cleanup() { + + + + + + + +} + +SWGSDRDaemonChannelSourceReport* +SWGSDRDaemonChannelSourceReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRDaemonChannelSourceReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&queue_length, pJson["queueLength"], "qint32", ""); + + ::SWGSDRangel::setValue(&queue_size, pJson["queueSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&samples_count, pJson["samplesCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&correctable_errors_count, pJson["correctableErrorsCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&uncorrectable_errors_count, pJson["uncorrectableErrorsCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&tv_sec, pJson["tvSec"], "qint32", ""); + + ::SWGSDRangel::setValue(&tv_u_sec, pJson["tvUSec"], "qint32", ""); + +} + +QString +SWGSDRDaemonChannelSourceReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRDaemonChannelSourceReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_queue_length_isSet){ + obj->insert("queueLength", QJsonValue(queue_length)); + } + if(m_queue_size_isSet){ + obj->insert("queueSize", QJsonValue(queue_size)); + } + if(m_samples_count_isSet){ + obj->insert("samplesCount", QJsonValue(samples_count)); + } + if(m_correctable_errors_count_isSet){ + obj->insert("correctableErrorsCount", QJsonValue(correctable_errors_count)); + } + if(m_uncorrectable_errors_count_isSet){ + obj->insert("uncorrectableErrorsCount", QJsonValue(uncorrectable_errors_count)); + } + if(m_tv_sec_isSet){ + obj->insert("tvSec", QJsonValue(tv_sec)); + } + if(m_tv_u_sec_isSet){ + obj->insert("tvUSec", QJsonValue(tv_u_sec)); + } + + return obj; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getQueueLength() { + return queue_length; +} +void +SWGSDRDaemonChannelSourceReport::setQueueLength(qint32 queue_length) { + this->queue_length = queue_length; + this->m_queue_length_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getQueueSize() { + return queue_size; +} +void +SWGSDRDaemonChannelSourceReport::setQueueSize(qint32 queue_size) { + this->queue_size = queue_size; + this->m_queue_size_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getSamplesCount() { + return samples_count; +} +void +SWGSDRDaemonChannelSourceReport::setSamplesCount(qint32 samples_count) { + this->samples_count = samples_count; + this->m_samples_count_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getCorrectableErrorsCount() { + return correctable_errors_count; +} +void +SWGSDRDaemonChannelSourceReport::setCorrectableErrorsCount(qint32 correctable_errors_count) { + this->correctable_errors_count = correctable_errors_count; + this->m_correctable_errors_count_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getUncorrectableErrorsCount() { + return uncorrectable_errors_count; +} +void +SWGSDRDaemonChannelSourceReport::setUncorrectableErrorsCount(qint32 uncorrectable_errors_count) { + this->uncorrectable_errors_count = uncorrectable_errors_count; + this->m_uncorrectable_errors_count_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getTvSec() { + return tv_sec; +} +void +SWGSDRDaemonChannelSourceReport::setTvSec(qint32 tv_sec) { + this->tv_sec = tv_sec; + this->m_tv_sec_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getTvUSec() { + return tv_u_sec; +} +void +SWGSDRDaemonChannelSourceReport::setTvUSec(qint32 tv_u_sec) { + this->tv_u_sec = tv_u_sec; + this->m_tv_u_sec_isSet = true; +} + + +bool +SWGSDRDaemonChannelSourceReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_queue_length_isSet){ isObjectUpdated = true; break;} + if(m_queue_size_isSet){ isObjectUpdated = true; break;} + if(m_samples_count_isSet){ isObjectUpdated = true; break;} + if(m_correctable_errors_count_isSet){ isObjectUpdated = true; break;} + if(m_uncorrectable_errors_count_isSet){ isObjectUpdated = true; break;} + if(m_tv_sec_isSet){ isObjectUpdated = true; break;} + if(m_tv_u_sec_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h new file mode 100644 index 000000000..737a3e948 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h @@ -0,0 +1,94 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRDaemonChannelSourceReport.h + * + * SDRDaemon channel source report + */ + +#ifndef SWGSDRDaemonChannelSourceReport_H_ +#define SWGSDRDaemonChannelSourceReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRDaemonChannelSourceReport: public SWGObject { +public: + SWGSDRDaemonChannelSourceReport(); + SWGSDRDaemonChannelSourceReport(QString* json); + virtual ~SWGSDRDaemonChannelSourceReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRDaemonChannelSourceReport* fromJson(QString &jsonString) override; + + qint32 getQueueLength(); + void setQueueLength(qint32 queue_length); + + qint32 getQueueSize(); + void setQueueSize(qint32 queue_size); + + qint32 getSamplesCount(); + void setSamplesCount(qint32 samples_count); + + qint32 getCorrectableErrorsCount(); + void setCorrectableErrorsCount(qint32 correctable_errors_count); + + qint32 getUncorrectableErrorsCount(); + void setUncorrectableErrorsCount(qint32 uncorrectable_errors_count); + + qint32 getTvSec(); + void setTvSec(qint32 tv_sec); + + qint32 getTvUSec(); + void setTvUSec(qint32 tv_u_sec); + + + virtual bool isSet() override; + +private: + qint32 queue_length; + bool m_queue_length_isSet; + + qint32 queue_size; + bool m_queue_size_isSet; + + qint32 samples_count; + bool m_samples_count_isSet; + + qint32 correctable_errors_count; + bool m_correctable_errors_count_isSet; + + qint32 uncorrectable_errors_count; + bool m_uncorrectable_errors_count_isSet; + + qint32 tv_sec; + bool m_tv_sec_isSet; + + qint32 tv_u_sec; + bool m_tv_u_sec_isSet; + +}; + +} + +#endif /* SWGSDRDaemonChannelSourceReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp new file mode 100644 index 000000000..5a39bd498 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp @@ -0,0 +1,129 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSDRDaemonChannelSourceSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSDRDaemonChannelSourceSettings::SWGSDRDaemonChannelSourceSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSDRDaemonChannelSourceSettings::SWGSDRDaemonChannelSourceSettings() { + data_address = nullptr; + m_data_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; +} + +SWGSDRDaemonChannelSourceSettings::~SWGSDRDaemonChannelSourceSettings() { + this->cleanup(); +} + +void +SWGSDRDaemonChannelSourceSettings::init() { + data_address = new QString(""); + m_data_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; +} + +void +SWGSDRDaemonChannelSourceSettings::cleanup() { + if(data_address != nullptr) { + delete data_address; + } + +} + +SWGSDRDaemonChannelSourceSettings* +SWGSDRDaemonChannelSourceSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSDRDaemonChannelSourceSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); + +} + +QString +SWGSDRDaemonChannelSourceSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSDRDaemonChannelSourceSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(data_address != nullptr && *data_address != QString("")){ + toJsonValue(QString("dataAddress"), data_address, obj, QString("QString")); + } + if(m_data_port_isSet){ + obj->insert("dataPort", QJsonValue(data_port)); + } + + return obj; +} + +QString* +SWGSDRDaemonChannelSourceSettings::getDataAddress() { + return data_address; +} +void +SWGSDRDaemonChannelSourceSettings::setDataAddress(QString* data_address) { + this->data_address = data_address; + this->m_data_address_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceSettings::getDataPort() { + return data_port; +} +void +SWGSDRDaemonChannelSourceSettings::setDataPort(qint32 data_port) { + this->data_port = data_port; + this->m_data_port_isSet = true; +} + + +bool +SWGSDRDaemonChannelSourceSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} + if(m_data_port_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h new file mode 100644 index 000000000..123e1956e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h @@ -0,0 +1,65 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSDRDaemonChannelSourceSettings.h + * + * Data handling details for SDRDaemon + */ + +#ifndef SWGSDRDaemonChannelSourceSettings_H_ +#define SWGSDRDaemonChannelSourceSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSDRDaemonChannelSourceSettings: public SWGObject { +public: + SWGSDRDaemonChannelSourceSettings(); + SWGSDRDaemonChannelSourceSettings(QString* json); + virtual ~SWGSDRDaemonChannelSourceSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSDRDaemonChannelSourceSettings* fromJson(QString &jsonString) override; + + QString* getDataAddress(); + void setDataAddress(QString* data_address); + + qint32 getDataPort(); + void setDataPort(qint32 data_port); + + + virtual bool isSet() override; + +private: + QString* data_address; + bool m_data_address_isSet; + + qint32 data_port; + bool m_data_port_isSet; + +}; + +} + +#endif /* SWGSDRDaemonChannelSourceSettings_H_ */ From e20e14ac75b9db8736c17a1a147d20ee04658ee5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 30 Aug 2018 02:58:10 +0200 Subject: [PATCH 671/956] SDRDaemonSink: use SDRdaemon REST API to get channel source information --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 62 ++++++++++++++++++- .../sdrdaemonsink/sdrdaemonsinkgui.h | 10 ++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 687045947..21dca7f85 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include #include #include @@ -74,6 +77,9 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + m_time.start(); displayEventCounts(); displayEventTimer(); @@ -84,6 +90,7 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : SDRdaemonSinkGui::~SDRdaemonSinkGui() { + delete m_networkManager; delete ui; } @@ -451,9 +458,60 @@ void SDRdaemonSinkGui::tick() { if ((++m_tickCount & 0xf) == 0) // 16*50ms ~800ms { - SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming::create(); - m_deviceSampleSink->getInputMessageQueue()->push(message); + QString reportURL = QString("http://%1:%2/sdrdaemon/channel/report").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(reportURL)); + m_networkManager->get(m_networkRequest); + +// SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming::create(); +// m_deviceSampleSink->getInputMessageQueue()->push(message); displayEventTimer(); } } + +void SDRdaemonSinkGui::networkManagerFinished(QNetworkReply *reply) +{ + if (reply->error()) + { + qDebug() << "SDRdaemonSinkGui::networkManagerFinished" << reply->errorString(); + return; + } + + QString answer = reply->readAll(); + + try + { + QByteArray jsonBytes(answer.toStdString().c_str()); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error); + + if (error.error == QJsonParseError::NoError) + { + analyzeChannelReport(doc.object()); + } + else + { + QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); + qDebug().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + } + } + catch (const std::exception& ex) + { + QString errorMsg = QString("Error parsing request: ") + ex.what(); + qDebug().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + } +} + +void SDRdaemonSinkGui::analyzeChannelReport(const QJsonObject& jsonObject) +{ + if (jsonObject.contains("SDRDaemonChannelSourceReport")) + { + QJsonObject report = jsonObject["SDRDaemonChannelSourceReport"].toObject(); + int queueSize = report["queueSize"].toInt(); + queueSize = queueSize == 0 ? 10 : queueSize; + int queueLength = report["queueLength"].toInt(); + QString queueLengthText = QString("%1/%2").arg(queueLength).arg(queueSize); + ui->queueLengthText->setText(queueLengthText); + ui->queueLengthGauge->setValue((queueLength*100)/queueSize); + } +} diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index db9b8d03e..0e5b7edcf 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -21,13 +21,16 @@ #include #include #include +#include #include "util/messagequeue.h" #include "sdrdaemonsinksettings.h" #include "sdrdaemonsinkoutput.h" - +class QNetworkAccessManager; +class QNetworkReply; +class QJsonObject; class DeviceSampleSink; class DeviceUISet; @@ -84,6 +87,9 @@ private: MessageQueue m_inputMessageQueue; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + void blockApplySettings(bool block); void displaySettings(); void displayTime(); @@ -94,6 +100,7 @@ private: void updateTxDelayTooltip(); void displayEventCounts(); void displayEventTimer(); + void analyzeChannelReport(const QJsonObject& jsonObject); private slots: void handleInputMessages(); @@ -112,6 +119,7 @@ private slots: void updateHardware(); void updateStatus(); void tick(); + void networkManagerFinished(QNetworkReply *reply); }; #endif // INCLUDE_FILESINKGUI_H From 829299cb749c16972ede03f331619bb0bf549d51 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 30 Aug 2018 08:45:57 +0200 Subject: [PATCH 672/956] SDRDaemonSink: GUI: completed status display --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 42 +++++++++++++++++++ .../sdrdaemonsink/sdrdaemonsinkgui.h | 4 ++ .../sdrdaemonsink/sdrdaemonsinkgui.ui | 3 ++ 3 files changed, 49 insertions(+) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 21dca7f85..ca16a7939 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -55,6 +55,9 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : { m_countUnrecoverable = 0; m_countRecovered = 0; + m_lastCountUnrecoverable = 0; + m_lastCountRecovered = 0; + m_resetCounts = true; m_paletteGreenText.setColor(QPalette::WindowText, Qt::green); m_paletteRedText.setColor(QPalette::WindowText, Qt::red); @@ -68,6 +71,8 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); ui->sampleRate->setValueRange(7, 32000U, 9000000U); + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); @@ -428,6 +433,23 @@ void SDRdaemonSinkGui::displayEventCounts() ui->eventRecText->setText(nstr); } +void SDRdaemonSinkGui::displayEventStatus(int recoverableCount, int unrecoverableCount) +{ + + if (unrecoverableCount == 0) + { + if (recoverableCount == 0) { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + } + else + { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }"); + } +} + void SDRdaemonSinkGui::displayEventTimer() { int elapsedTimeMillis = m_time.elapsed(); @@ -473,6 +495,7 @@ void SDRdaemonSinkGui::networkManagerFinished(QNetworkReply *reply) { if (reply->error()) { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); qDebug() << "SDRdaemonSinkGui::networkManagerFinished" << reply->errorString(); return; } @@ -487,16 +510,19 @@ void SDRdaemonSinkGui::networkManagerFinished(QNetworkReply *reply) if (error.error == QJsonParseError::NoError) { + ui->apiAddressLabel->setStyleSheet("QLabel { background-color : green; }"); analyzeChannelReport(doc.object()); } else { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); qDebug().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; } } catch (const std::exception& ex) { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); QString errorMsg = QString("Error parsing request: ") + ex.what(); qDebug().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; } @@ -513,5 +539,21 @@ void SDRdaemonSinkGui::analyzeChannelReport(const QJsonObject& jsonObject) QString queueLengthText = QString("%1/%2").arg(queueLength).arg(queueSize); ui->queueLengthText->setText(queueLengthText); ui->queueLengthGauge->setValue((queueLength*100)/queueSize); + int unrecoverableCount = report["uncorrectableErrorsCount"].toInt(); + int recoverableCount = report["correctableErrorsCount"].toInt(); + + if (!m_resetCounts) + { + int recoverableCountDelta = recoverableCount - m_lastCountRecovered; + int unrecoverableCountDelta = unrecoverableCount - m_lastCountUnrecoverable; + displayEventStatus(recoverableCountDelta, unrecoverableCountDelta); + m_countRecovered += recoverableCountDelta; + m_countUnrecoverable += unrecoverableCountDelta; + displayEventCounts(); + } + + m_resetCounts = false; + m_lastCountRecovered = recoverableCount; + m_lastCountUnrecoverable = unrecoverableCount; } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 0e5b7edcf..80c046c30 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -79,6 +79,9 @@ private: uint32_t m_countUnrecoverable; uint32_t m_countRecovered; + uint32_t m_lastCountUnrecoverable; + uint32_t m_lastCountRecovered; + bool m_resetCounts; QTime m_time; QPalette m_paletteGreenText; @@ -99,6 +102,7 @@ private: void updateSampleRateAndFrequency(); void updateTxDelayTooltip(); void displayEventCounts(); + void displayEventStatus(int recoverableCount, int unrecoverableCount); void displayEventTimer(); void analyzeChannelReport(const QJsonObject& jsonObject); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 23b83ab7f..8f36bd25c 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -498,6 +498,9 @@ 0 + + Green if communication OK else KO + API From 90fb223b0187c8d7d7d710b6fb028ce967d8df82 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 30 Aug 2018 19:14:34 +0200 Subject: [PATCH 673/956] SDRDaemonSink GUI: enhanced status reporting --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 63 +++++++++++++++++-- .../sdrdaemonsink/sdrdaemonsinkgui.h | 6 +- .../sdrdaemonsink/sdrdaemonsinkgui.ui | 60 +++++++++++++++++- 3 files changed, 118 insertions(+), 11 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index ca16a7939..bc78b1704 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -57,6 +57,7 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_countRecovered = 0; m_lastCountUnrecoverable = 0; m_lastCountRecovered = 0; + m_lastSampleCount = 0; m_resetCounts = true; m_paletteGreenText.setColor(QPalette::WindowText, Qt::green); @@ -335,6 +336,10 @@ void SDRdaemonSinkGui::on_apiAddress_returnPressed() { m_settings.m_apiAddress = ui->apiAddress->text(); sendSettings(); + + QString infoURL = QString("http://%1:%2/sdrdaemon").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); } void SDRdaemonSinkGui::on_apiPort_returnPressed() @@ -352,6 +357,10 @@ void SDRdaemonSinkGui::on_apiPort_returnPressed() } sendSettings(); + + QString infoURL = QString("http://%1:%2/sdrdaemon").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); } void SDRdaemonSinkGui::on_dataAddress_returnPressed() @@ -390,6 +399,10 @@ void SDRdaemonSinkGui::on_apiApplyButton_clicked(bool checked __attribute__((unu } sendSettings(); + + QString infoURL = QString("http://%1:%2/sdrdaemon").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); } void SDRdaemonSinkGui::on_dataApplyButton_clicked(bool checked __attribute__((unused))) @@ -496,7 +509,7 @@ void SDRdaemonSinkGui::networkManagerFinished(QNetworkReply *reply) if (reply->error()) { ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); - qDebug() << "SDRdaemonSinkGui::networkManagerFinished" << reply->errorString(); + ui->statusText->setText(reply->errorString()); return; } @@ -511,25 +524,30 @@ void SDRdaemonSinkGui::networkManagerFinished(QNetworkReply *reply) if (error.error == QJsonParseError::NoError) { ui->apiAddressLabel->setStyleSheet("QLabel { background-color : green; }"); - analyzeChannelReport(doc.object()); + ui->statusText->setText(QString("API OK")); + analyzeApiReply(doc.object()); } else { ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); - qDebug().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + ui->statusText->setText(QString("JSON error. See log")); + qInfo().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; } } catch (const std::exception& ex) { ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); QString errorMsg = QString("Error parsing request: ") + ex.what(); - qDebug().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + ui->statusText->setText("Error parsing request. See log for details"); + qInfo().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; } } -void SDRdaemonSinkGui::analyzeChannelReport(const QJsonObject& jsonObject) +void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) { + QString infoLine; + if (jsonObject.contains("SDRDaemonChannelSourceReport")) { QJsonObject report = jsonObject["SDRDaemonChannelSourceReport"].toObject(); @@ -541,6 +559,8 @@ void SDRdaemonSinkGui::analyzeChannelReport(const QJsonObject& jsonObject) ui->queueLengthGauge->setValue((queueLength*100)/queueSize); int unrecoverableCount = report["uncorrectableErrorsCount"].toInt(); int recoverableCount = report["correctableErrorsCount"].toInt(); + int sampleCount = report["samplesCount"].toInt(); + uint64_t timestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); if (!m_resetCounts) { @@ -552,8 +572,41 @@ void SDRdaemonSinkGui::analyzeChannelReport(const QJsonObject& jsonObject) displayEventCounts(); } + if ((sampleCount - m_lastSampleCount) == 0) { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }"); + } + + double remoteStreamRate = (sampleCount - m_lastSampleCount) / (double) (timestampUs - m_lastTimestampUs); + ui->remoteStreamRateText->setText(QString("%1").arg(remoteStreamRate * 1e6, 0, 'f', 0)); + m_resetCounts = false; m_lastCountRecovered = recoverableCount; m_lastCountUnrecoverable = unrecoverableCount; + m_lastSampleCount = sampleCount; + m_lastTimestampUs = timestampUs; + } + + if (jsonObject.contains("version")) { + infoLine = "v" + jsonObject["version"].toString(); + } + + if (jsonObject.contains("qtVersion")) { + infoLine += " Qt" + jsonObject["qtVersion"].toString(); + } + + if (jsonObject.contains("architecture")) { + infoLine += " " + jsonObject["architecture"].toString(); + } + + if (jsonObject.contains("os")) { + infoLine += " " + jsonObject["os"].toString(); + } + + if (jsonObject.contains("dspRxBits") && jsonObject.contains("dspTxBits")) { + infoLine += QString(" %1/%2b").arg(jsonObject["dspRxBits"].toInt()).arg(jsonObject["dspTxBits"].toInt()); + } + + if (infoLine.size() > 0) { + ui->infoText->setText(infoLine); } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 80c046c30..90af1cbc7 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -75,12 +75,12 @@ private: bool m_doApplySettings; bool m_forceSettings; - int m_nnSender; - uint32_t m_countUnrecoverable; uint32_t m_countRecovered; uint32_t m_lastCountUnrecoverable; uint32_t m_lastCountRecovered; + uint32_t m_lastSampleCount; + uint64_t m_lastTimestampUs; bool m_resetCounts; QTime m_time; @@ -104,7 +104,7 @@ private: void displayEventCounts(); void displayEventStatus(int recoverableCount, int unrecoverableCount); void displayEventTimer(); - void analyzeChannelReport(const QJsonObject& jsonObject); + void analyzeApiReply(const QJsonObject& jsonObject); private slots: void handleInputMessages(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 8f36bd25c..9cd957623 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -7,7 +7,7 @@ 0 0 360 - 237 + 270 @@ -19,7 +19,7 @@ 360 - 190 + 270 @@ -358,7 +358,7 @@ false - Frames status: green = all original received, none = some recovered by FEC, red = some lost + Frames status: green = all original received, none = some recovered by FEC, red = some lost, blue = remote not streaming @@ -369,6 +369,38 @@ + + + + + 50 + 0 + + + + Remote stream rate (S/s) + + + 0000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 20 + 0 + + + + S/s + + + @@ -673,6 +705,28 @@ + + + + + + ... + + + + + + + + + + + ... + + + + + From f8383f8cff2a087d40b7bbc47efea0bce900adaa Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 31 Aug 2018 07:38:30 +0200 Subject: [PATCH 674/956] SDRDaemonSink GUI: sample rate active feedback --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 75 +++++++++-------- .../sdrdaemonsink/sdrdaemonsinkgui.h | 12 ++- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 14 ---- .../sdrdaemonsink/sdrdaemonsinkoutput.h | 37 -------- .../sdrdaemonsink/sdrdaemonsinkthread.h | 2 +- sdrbase/util/limitedcounter.h | 84 +++++++++++++++++++ sdrbase/util/movingaverage.h | 1 + sdrdaemon/channel/sdrdaemonchannelsource.cpp | 8 +- sdrdaemon/channel/sdrdaemonchannelsource.h | 1 - sdrdaemon/channel/sdrdaemondatareadqueue.cpp | 1 - sdrdaemon/channel/sdrdaemondatareadqueue.h | 6 +- 11 files changed, 142 insertions(+), 99 deletions(-) create mode 100644 sdrbase/util/limitedcounter.h diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index bc78b1704..98e70281b 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -58,6 +58,7 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_lastCountUnrecoverable = 0; m_lastCountRecovered = 0; m_lastSampleCount = 0; + m_lastTimestampRateCorrection = 0; m_resetCounts = true; m_paletteGreenText.setColor(QPalette::WindowText, Qt::green); @@ -176,12 +177,6 @@ bool SDRdaemonSinkGui::handleMessage(const Message& message) blockApplySettings(false); return true; } - else if (SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming::match(message)) - { - m_samplesCount = ((SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming&)message).getSamplesCount(); - updateWithStreamTime(); - return true; - } else if (SDRdaemonSinkOutput::MsgStartStop::match(message)) { SDRdaemonSinkOutput::MsgStartStop& notif = (SDRdaemonSinkOutput::MsgStartStop&) message; @@ -339,7 +334,7 @@ void SDRdaemonSinkGui::on_apiAddress_returnPressed() QString infoURL = QString("http://%1:%2/sdrdaemon").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); m_networkRequest.setUrl(QUrl(infoURL)); - m_networkManager->get(m_networkRequest); + m_networkManager->get(m_networkRequest); } void SDRdaemonSinkGui::on_apiPort_returnPressed() @@ -360,7 +355,7 @@ void SDRdaemonSinkGui::on_apiPort_returnPressed() QString infoURL = QString("http://%1:%2/sdrdaemon").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); m_networkRequest.setUrl(QUrl(infoURL)); - m_networkManager->get(m_networkRequest); + m_networkManager->get(m_networkRequest); } void SDRdaemonSinkGui::on_dataAddress_returnPressed() @@ -472,35 +467,17 @@ void SDRdaemonSinkGui::displayEventTimer() ui->eventCountsTimeText->setText(s_time); } -void SDRdaemonSinkGui::updateWithStreamTime() -{ - int t_sec = 0; - int t_msec = 0; - - if (m_settings.m_sampleRate > 0){ - t_msec = ((m_samplesCount * 1000) / m_settings.m_sampleRate) % 1000; - t_sec = m_samplesCount / m_settings.m_sampleRate; - } - - QTime t(0, 0, 0, 0); - t = t.addSecs(t_sec); - t = t.addMSecs(t_msec); - QString s_timems = t.toString("HH:mm:ss.zzz"); - //ui->relTimeText->setText(s_timems); TODO with absolute time -} - void SDRdaemonSinkGui::tick() { - if ((++m_tickCount & 0xf) == 0) // 16*50ms ~800ms + if (++m_tickCount == 20) // once per second { QString reportURL = QString("http://%1:%2/sdrdaemon/channel/report").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); m_networkRequest.setUrl(QUrl(reportURL)); m_networkManager->get(m_networkRequest); -// SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming::create(); -// m_deviceSampleSink->getInputMessageQueue()->push(message); - displayEventTimer(); + + m_tickCount = 0; } } @@ -556,12 +533,22 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) int queueLength = report["queueLength"].toInt(); QString queueLengthText = QString("%1/%2").arg(queueLength).arg(queueSize); ui->queueLengthText->setText(queueLengthText); - ui->queueLengthGauge->setValue((queueLength*100)/queueSize); + int queueLengthPercent = (queueLength*100)/queueSize; + ui->queueLengthGauge->setValue(queueLengthPercent); int unrecoverableCount = report["uncorrectableErrorsCount"].toInt(); int recoverableCount = report["correctableErrorsCount"].toInt(); - int sampleCount = report["samplesCount"].toInt(); + LimitedCounter sampleCount(report["samplesCount"].toInt()); uint64_t timestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); + if (m_lastTimestampRateCorrection == 0) { + m_lastTimestampRateCorrection = timestampUs; + } + + if ((timestampUs - m_lastTimestampRateCorrection > 600e6) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) + { + m_lastTimestampRateCorrection = timestampUs; + } + if (!m_resetCounts) { int recoverableCountDelta = recoverableCount - m_lastCountRecovered; @@ -572,12 +559,19 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) displayEventCounts(); } - if ((sampleCount - m_lastSampleCount) == 0) { + LimitedCounter sampleCountDelta = sampleCount - m_lastSampleCount; + + if (sampleCountDelta.value() == 0) { ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }"); } - double remoteStreamRate = (sampleCount - m_lastSampleCount) / (double) (timestampUs - m_lastTimestampUs); - ui->remoteStreamRateText->setText(QString("%1").arg(remoteStreamRate * 1e6, 0, 'f', 0)); + double remoteStreamRate = sampleCountDelta.value()*1e6 / (double) (timestampUs - m_lastTimestampUs); + + if (remoteStreamRate != 0) + { + m_rateMovingAverage(remoteStreamRate); + ui->remoteStreamRateText->setText(QString("%1").arg(m_rateMovingAverage.instantAverage(), 0, 'f', 0)); + } m_resetCounts = false; m_lastCountRecovered = recoverableCount; @@ -610,3 +604,16 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) ui->infoText->setText(infoLine); } } + +void SDRdaemonSinkGui::sampleRateCorrection(int queueLength, int queueSize, int64_t timeDelta) +{ + int nbBlocksDiff = queueLength - (queueSize/2); + int nbSamplesDiff = nbBlocksDiff * 127 * 127; + float sampleCorr = (nbSamplesDiff * 50000.0) / timeDelta; + int chunkCorr = roundf(sampleCorr); + + qDebug("SDRdaemonSinkGui::sampleRateCorrection: %d samples", -chunkCorr); + + SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection::create(-chunkCorr); + m_deviceSampleSink->getInputMessageQueue()->push(message); +} diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 90af1cbc7..c1769c3ce 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -24,6 +24,8 @@ #include #include "util/messagequeue.h" +#include "util/limitedcounter.h" +#include "util/movingaverage.h" #include "sdrdaemonsinksettings.h" #include "sdrdaemonsinkoutput.h" @@ -69,7 +71,8 @@ private: int m_sampleRate; quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_samplesCount; - std::size_t m_tickCount; + MovingAverageUtil m_rateMovingAverage; // ~2mn average + uint32_t m_tickCount; std::size_t m_nbSinceLastFlowCheck; int m_lastEngineState; bool m_doApplySettings; @@ -79,8 +82,9 @@ private: uint32_t m_countRecovered; uint32_t m_lastCountUnrecoverable; uint32_t m_lastCountRecovered; - uint32_t m_lastSampleCount; - uint64_t m_lastTimestampUs; + LimitedCounter m_lastSampleCount; + uint64_t m_lastTimestampUs; + uint64_t m_lastTimestampRateCorrection; bool m_resetCounts; QTime m_time; @@ -98,13 +102,13 @@ private: void displayTime(); void sendControl(bool force = false); void sendSettings(); - void updateWithStreamTime(); void updateSampleRateAndFrequency(); void updateTxDelayTooltip(); void displayEventCounts(); void displayEventStatus(int recoverableCount, int unrecoverableCount); void displayEventTimer(); void analyzeApiReply(const QJsonObject& jsonObject); + void sampleRateCorrection(int queueLength, int queueSize, int64_t timeDelta); private slots: void handleInputMessages(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index c0803769e..01e6e0814 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -36,9 +36,7 @@ MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSink, Message) MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkWork, Message) MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming, Message) MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection, Message) -MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming, Message) SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), @@ -211,18 +209,6 @@ bool SDRdaemonSinkOutput::handleMessage(const Message& message) return true; } - else if (MsgConfigureSDRdaemonSinkStreamTiming::match(message)) - { - MsgReportSDRdaemonSinkStreamTiming *report; - - if (m_sdrDaemonSinkThread != 0 && getMessageQueueToGUI()) - { - report = MsgReportSDRdaemonSinkStreamTiming::create(m_sdrDaemonSinkThread->getSamplesCount()); - getMessageQueueToGUI()->push(report); - } - - return true; - } else if (MsgConfigureSDRdaemonSinkChunkCorrection::match(message)) { MsgConfigureSDRdaemonSinkChunkCorrection& conf = (MsgConfigureSDRdaemonSinkChunkCorrection&) message; diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index 51c7b21c7..a5b4ada47 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -114,43 +114,6 @@ public: { } }; - class MsgConfigureSDRdaemonSinkStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgConfigureSDRdaemonSinkStreamTiming* create() - { - return new MsgConfigureSDRdaemonSinkStreamTiming(); - } - - private: - - MsgConfigureSDRdaemonSinkStreamTiming() : - Message() - { } - }; - - class MsgReportSDRdaemonSinkStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - std::size_t getSamplesCount() const { return m_samplesCount; } - - static MsgReportSDRdaemonSinkStreamTiming* create(std::size_t samplesCount) - { - return new MsgReportSDRdaemonSinkStreamTiming(samplesCount); - } - - protected: - std::size_t m_samplesCount; - - MsgReportSDRdaemonSinkStreamTiming(std::size_t samplesCount) : - Message(), - m_samplesCount(samplesCount) - { } - }; - SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI); virtual ~SDRdaemonSinkOutput(); virtual void destroy(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h index d2566abc9..c4ce84150 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h @@ -67,7 +67,7 @@ private: int m_samplesChunkSize; SampleSourceFifo* m_sampleFifo; - std::size_t m_samplesCount; + uint32_t m_samplesCount; int m_chunkCorrection; int m_samplerate; diff --git a/sdrbase/util/limitedcounter.h b/sdrbase/util/limitedcounter.h new file mode 100644 index 000000000..2ee229705 --- /dev/null +++ b/sdrbase/util/limitedcounter.h @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 SDRBASE_UTIL_LIMITEDCOUNTER_H_ +#define SDRBASE_UTIL_LIMITEDCOUNTER_H_ + +#include + +template +class LimitedCounter +{ +public: + LimitedCounter() : + m_counter(0) + {} + + LimitedCounter(StoreType value) : + m_counter(value < Limit ? value : Limit - 1) + {} + + void operator=(const LimitedCounter& rhs) { + m_counter = rhs.m_counter; + } + + LimitedCounter& operator++() { + m_counter++; + if (m_counter >= Limit) { + m_counter = 0; + } + return *this; + } + + LimitedCounter operator++(int) { + LimitedCounter temp = *this ; + m_counter++; + if (m_counter >= Limit) { + m_counter = 0; + } + return temp; + } + + LimitedCounter& operator+=(const uint32_t rhs) { + m_counter += rhs; + if (m_counter >= Limit) { + m_counter -= Limit; + } + return *this; + } + + StoreType value() const { return m_counter; } + + friend LimitedCounter operator-(const LimitedCounter lhs, const LimitedCounter& rhs) + { + LimitedCounter result; + + if (lhs.m_counter < rhs.m_counter) { + result.m_counter = Limit - lhs.m_counter + rhs.m_counter + 1; + } else { + result.m_counter = lhs.m_counter - rhs.m_counter; + } + + return result; + } + +private: + StoreType m_counter; +}; + + + +#endif /* SDRBASE_UTIL_LIMITEDCOUNTER_H_ */ diff --git a/sdrbase/util/movingaverage.h b/sdrbase/util/movingaverage.h index 74ac1e94e..dfd003d8c 100644 --- a/sdrbase/util/movingaverage.h +++ b/sdrbase/util/movingaverage.h @@ -58,6 +58,7 @@ class MovingAverageUtil double asDouble() const { return ((double)m_total) / N; } float asFloat() const { return ((float)m_total) / N; } operator T() const { return m_total / N; } + T instantAverage() const { return m_total / (m_num_samples == 0 ? 1 : m_num_samples); } private: T m_samples[N]; diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index f89e54738..cd6b14635 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -50,7 +50,6 @@ SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_sourceThread(0), m_running(false), - m_samplesCount(0), m_nbCorrectableErrors(0), m_nbUncorrectableErrors(0) { @@ -77,7 +76,6 @@ SDRDaemonChannelSource::~SDRDaemonChannelSource() void SDRDaemonChannelSource::pull(Sample& sample) { m_dataReadQueue.readSample(sample); - m_samplesCount++; } void SDRDaemonChannelSource::start() @@ -235,8 +233,8 @@ void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) } // update counters - if (dataBlock->m_rxControlBlock.m_recoveryCount > paramsCM256.RecoveryCount) { - m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_originalCount; + if (dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount) { + m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount - dataBlock->m_rxControlBlock.m_originalCount; } else { m_nbCorrectableErrors += dataBlock->m_rxControlBlock.m_recoveryCount; } @@ -409,7 +407,7 @@ void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelRe response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); - response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); + response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount().value()); response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); } diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h index 79621da5c..b9359204b 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ b/sdrdaemon/channel/sdrdaemonchannelsource.h @@ -110,7 +110,6 @@ private: bool m_running; SDRDaemonChannelSourceSettings m_settings; - uint64_t m_samplesCount; CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) SDRDaemonMetaDataFEC m_currentMeta; diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp index 3c947c725..b14921166 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp @@ -30,7 +30,6 @@ SDRDaemonDataReadQueue::SDRDaemonDataReadQueue() : m_maxSize(MinimumMaxSize), m_blockIndex(1), m_sampleIndex(0), - m_sampleCount(0), m_full(false) {} diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.h b/sdrdaemon/channel/sdrdaemondatareadqueue.h index d96f98366..4390a435d 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.h +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.h @@ -25,6 +25,8 @@ #include +#include "util/limitedcounter.h" + class SDRDaemonDataBlock; class Sample; @@ -40,7 +42,7 @@ public: uint32_t length() const { return m_dataReadQueue.size(); } //!< Returns queue length uint32_t size() const { return m_maxSize; } //!< Returns queue size (max length) void setSize(uint32_t size); //!< Sets the queue size (max length) - uint32_t readSampleCount() const { return m_sampleCount; } //!< Returns the absolute number of samples read + LimitedCounter readSampleCount() const { return m_sampleCount; } //!< Returns the absolute number of samples read static const uint32_t MinimumMaxSize; @@ -50,7 +52,7 @@ private: uint32_t m_maxSize; uint32_t m_blockIndex; uint32_t m_sampleIndex; - uint32_t m_sampleCount; + LimitedCounter m_sampleCount; //!< use a counter capped below 2^31 as it is going to be converted to an int in the web interface bool m_full; //!< full condition was hit }; From c25c7fda584a555f8388ea3b1cfd0e3e3f11af1c Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 31 Aug 2018 08:47:18 +0200 Subject: [PATCH 675/956] SDRDaemonSink GUI: sample rate active feedback (2) --- .../samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp | 14 ++++++++++---- .../samplesink/sdrdaemonsink/sdrdaemonsinkgui.h | 4 ++-- sdrbase/resources/webapi/doc/html2/index.html | 2 +- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 2 +- sdrdaemon/channel/sdrdaemondatareadqueue.h | 6 ++---- swagger/sdrangel/code/html2/index.html | 2 +- 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 98e70281b..eada6d7f3 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -537,7 +537,6 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) ui->queueLengthGauge->setValue(queueLengthPercent); int unrecoverableCount = report["uncorrectableErrorsCount"].toInt(); int recoverableCount = report["correctableErrorsCount"].toInt(); - LimitedCounter sampleCount(report["samplesCount"].toInt()); uint64_t timestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); if (m_lastTimestampRateCorrection == 0) { @@ -559,13 +558,20 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) displayEventCounts(); } - LimitedCounter sampleCountDelta = sampleCount - m_lastSampleCount; + uint32_t sampleCountDelta, sampleCount; + sampleCount = report["samplesCount"].toInt(); - if (sampleCountDelta.value() == 0) { + if (sampleCount < m_lastSampleCount) { + sampleCountDelta = (0xFFFFFFFFU - sampleCount) + m_lastSampleCount + 1; + } else { + sampleCountDelta = sampleCount - m_lastSampleCount; + } + + if (sampleCountDelta == 0) { ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }"); } - double remoteStreamRate = sampleCountDelta.value()*1e6 / (double) (timestampUs - m_lastTimestampUs); + double remoteStreamRate = sampleCountDelta*1e6 / (double) (timestampUs - m_lastTimestampUs); if (remoteStreamRate != 0) { diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index c1769c3ce..48dcd901d 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -71,7 +71,7 @@ private: int m_sampleRate; quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_samplesCount; - MovingAverageUtil m_rateMovingAverage; // ~2mn average + MovingAverageUtil m_rateMovingAverage; // ~30s average uint32_t m_tickCount; std::size_t m_nbSinceLastFlowCheck; int m_lastEngineState; @@ -82,7 +82,7 @@ private: uint32_t m_countRecovered; uint32_t m_lastCountUnrecoverable; uint32_t m_lastCountRecovered; - LimitedCounter m_lastSampleCount; + uint32_t m_lastSampleCount; uint64_t m_lastTimestampUs; uint64_t m_lastTimestampRateCorrection; bool m_resetCounts; diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 62a05233f..406dab88e 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -28688,7 +28688,7 @@ except ApiException as e:
    - Generated 2018-08-30T01:40:47.594+02:00 + Generated 2018-08-31T08:45:52.974+02:00
    diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index cd6b14635..580d2ecba 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -407,7 +407,7 @@ void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelRe response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); - response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount().value()); + response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); } diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.h b/sdrdaemon/channel/sdrdaemondatareadqueue.h index 4390a435d..ba43d2c47 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.h +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.h @@ -25,8 +25,6 @@ #include -#include "util/limitedcounter.h" - class SDRDaemonDataBlock; class Sample; @@ -42,7 +40,7 @@ public: uint32_t length() const { return m_dataReadQueue.size(); } //!< Returns queue length uint32_t size() const { return m_maxSize; } //!< Returns queue size (max length) void setSize(uint32_t size); //!< Sets the queue size (max length) - LimitedCounter readSampleCount() const { return m_sampleCount; } //!< Returns the absolute number of samples read + uint32_t readSampleCount() const { return m_sampleCount; } //!< Returns the absolute number of samples read static const uint32_t MinimumMaxSize; @@ -52,7 +50,7 @@ private: uint32_t m_maxSize; uint32_t m_blockIndex; uint32_t m_sampleIndex; - LimitedCounter m_sampleCount; //!< use a counter capped below 2^31 as it is going to be converted to an int in the web interface + uint32_t m_sampleCount; //!< use a counter capped below 2^31 as it is going to be converted to an int in the web interface bool m_full; //!< full condition was hit }; diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 62a05233f..406dab88e 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -28688,7 +28688,7 @@ except ApiException as e:
    - Generated 2018-08-30T01:40:47.594+02:00 + Generated 2018-08-31T08:45:52.974+02:00
    From 26bc4d8f8e175e44c66d1e8fac52ed9474cec856 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 31 Aug 2018 18:30:52 +0200 Subject: [PATCH 676/956] SDRDaemon channel source: added plugin --- plugins/channeltx/CMakeLists.txt | 12 +- plugins/channeltx/modam/ammodgui.cpp | 6 +- .../sdrdaemonchannelsource/CMakeLists.txt | 57 +++ .../sdrdaemonchannelsource.cpp | 413 ++++++++++++++++++ .../sdrdaemonchannelsource.h | 135 ++++++ .../sdrdaemonchannelsourcegui.cpp | 275 ++++++++++++ .../sdrdaemonchannelsourcegui.h | 92 ++++ .../sdrdaemonchannelsourcegui.ui | 179 ++++++++ .../sdrdaemonchannelsourceplugin.cpp | 77 ++++ .../sdrdaemonchannelsourceplugin.h | 47 ++ .../sdrdaemonchannelsourcesettings.cpp | 95 ++++ .../sdrdaemonchannelsourcesettings.h | 48 ++ .../sdrdaemonchannelsourcethread.cpp | 193 ++++++++ .../sdrdaemonchannelsourcethread.h | 115 +++++ 14 files changed, 1738 insertions(+), 6 deletions(-) create mode 100644 plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index 30118e09e..b7186967c 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -1,13 +1,19 @@ project(mod) -find_package(OpenCV) - add_subdirectory(modam) add_subdirectory(modnfm) add_subdirectory(modssb) add_subdirectory(modwfm) add_subdirectory(udpsink) +find_package(CM256cc) + +if(CM256CC_FOUND) + add_subdirectory(sdrdaemonchannelsource) +endif(CM256CC_FOUND) + +find_package(OpenCV) + if (OpenCV_FOUND) -add_subdirectory(modatv) + add_subdirectory(modatv) endif() diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index bf27a61ca..25270da64 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -20,13 +20,10 @@ #include #include -#include "ammodgui.h" - #include "device/devicesinkapi.h" #include "device/deviceuiset.h" #include "dsp/upchannelizer.h" -#include "ui_ammodgui.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" @@ -36,6 +33,9 @@ #include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" +#include "ui_ammodgui.h" +#include "ammodgui.h" + AMModGUI* AMModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) { AMModGUI* gui = new AMModGUI(pluginAPI, deviceUISet, channelTx); diff --git a/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt b/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt new file mode 100644 index 000000000..dc9629b31 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt @@ -0,0 +1,57 @@ +project(sdrdaemonchannelsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(sdrdaemonchannelsource_SOURCES + sdrdaemonchannelsource.cpp + sdrdaemonchannelsourcethread.cpp + sdrdaemonchannelsourcegui.cpp + sdrdaemonchannelsourceplugin.cpp + sdrdaemonchannelsourcesettings.cpp +) + +set(sdrdaemonchannelsource_HEADERS + sdrdaemonchannelsource.h + sdrdaemonchannelsourcethread.h + sdrdaemonchannelsourcegui.h + sdrdaemonchannelsourceplugin.h + sdrdaemonchannelsourcesettings.h +) + +set(sdrdaemonchannelsource_FORMS + sdrdaemonchannelsourcegui.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(sdrdaemonchannelsource_FORMS_HEADERS ${sdrdaemonchannelsource_FORMS}) + +add_library(sdrdaemonchannelsource SHARED + ${sdrdaemonchannelsource_SOURCES} + ${sdrdaemonchannelsource_HEADERS_MOC} + ${sdrdaemonchannelsource_FORMS_HEADERS} +) + +target_link_libraries(sdrdaemonchannelsource + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrdaemon + sdrgui + swagger +) + +target_link_libraries(sdrdaemonchannelsource Qt5::Core Qt5::Widgets Qt5::Network) + +install(TARGETS sdrdaemonchannelsource DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp new file mode 100644 index 000000000..580d2ecba --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp @@ -0,0 +1,413 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) // +// // +// 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 + +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGSDRDaemonChannelSourceReport.h" + +#include "util/simpleserializer.h" +#include "dsp/threadedbasebandsamplesource.h" +#include "dsp/upchannelizer.h" +#include "dsp/devicesamplesink.h" +#include "device/devicesinkapi.h" +#include "sdrdaemonchannelsource.h" +#include "channel/sdrdaemonchannelsourcethread.h" +#include "channel/sdrdaemondatablock.h" + +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource, Message) + +const QString SDRDaemonChannelSource::m_channelIdURI = "sdrangel.channel.sdrdaemonsource"; +const QString SDRDaemonChannelSource::m_channelId = "SDRDaemonChannelSource"; + +SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : + ChannelSourceAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_sourceThread(0), + m_running(false), + m_nbCorrectableErrors(0), + m_nbUncorrectableErrors(0) +{ + setObjectName(m_channelId); + + m_channelizer = new UpChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); + m_deviceAPI->addThreadedSource(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); + + connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; + m_currentMeta.init(); +} + +SDRDaemonChannelSource::~SDRDaemonChannelSource() +{ + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSource(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +void SDRDaemonChannelSource::pull(Sample& sample) +{ + m_dataReadQueue.readSample(sample); +} + +void SDRDaemonChannelSource::start() +{ + qDebug("SDRDaemonChannelSink::start"); + + if (m_running) { + stop(); + } + + m_sourceThread = new SDRDaemonChannelSourceThread(&m_dataQueue); + m_sourceThread->startStop(true); + m_sourceThread->dataBind(m_settings.m_dataAddress, m_settings.m_dataPort); + m_running = true; +} + +void SDRDaemonChannelSource::stop() +{ + qDebug("SDRDaemonChannelSink::stop"); + + if (m_sourceThread != 0) + { + m_sourceThread->startStop(false); + m_sourceThread->deleteLater(); + m_sourceThread = 0; + } + + m_running = false; +} + +void SDRDaemonChannelSource::setDataLink(const QString& dataAddress, uint16_t dataPort) +{ + SDRDaemonChannelSourceSettings settings = m_settings; + settings.m_dataAddress = dataAddress; + settings.m_dataPort = dataPort; + + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, false); + m_inputMessageQueue.push(msg); +} + +bool SDRDaemonChannelSource::handleMessage(const Message& cmd __attribute__((unused))) +{ + if (UpChannelizer::MsgChannelizerNotification::match(cmd)) + { + UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "SDRDaemonChannelSource::handleMessage: UpChannelizer::MsgChannelizerNotification:" + << " basebandSampleRate: " << notif.getBasebandSampleRate() + << " outputSampleRate: " << notif.getSampleRate() + << " inputFrequencyOffset: " << notif.getFrequencyOffset(); + + //applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset()); + + return true; + } + else if (MsgConfigureSDRDaemonChannelSource::match(cmd)) + { + MsgConfigureSDRDaemonChannelSource& cfg = (MsgConfigureSDRDaemonChannelSource&) cmd; + qDebug() << "SDRDaemonChannelSource::handleMessage: MsgConfigureSDRDaemonChannelSource"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else + { + return false; + } +} + +QByteArray SDRDaemonChannelSource::serialize() const +{ + return m_settings.serialize(); +} + +bool SDRDaemonChannelSource::deserialize(const QByteArray& data __attribute__((unused))) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& settings, bool force) +{ + qDebug() << "SDRDaemonChannelSource::applySettings:" + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort + << " force: " << force; + + bool change = false; + + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { + change = true; + } + + if ((m_settings.m_dataPort != settings.m_dataPort) || force) { + change = true; + } + + if (change && m_sourceThread) { + m_sourceThread->dataBind(settings.m_dataAddress, settings.m_dataPort); + } + + m_settings = settings; +} + +void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) +{ + if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) + { + qWarning("SDRDaemonChannelSource::handleDataBlock: incomplete data block: not processing"); + } + else + { + int blockCount = 0; + + for (int blockIndex = 0; blockIndex < 256; blockIndex++) + { + if ((blockIndex == 0) && (dataBlock->m_rxControlBlock.m_metaRetrieved)) + { + m_cm256DescriptorBlocks[blockCount].Index = 0; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[0].m_protectedBlock); + blockCount++; + } + else if (dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex != 0) + { + m_cm256DescriptorBlocks[blockCount].Index = dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock); + blockCount++; + } + } + + //qDebug("SDRDaemonChannelSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); + + // Need to use the CM256 recovery + if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) + { + qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); + CM256::cm256_encoder_params paramsCM256; + paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes + + if (m_currentMeta.m_tv_sec == 0) { + paramsCM256.RecoveryCount = dataBlock->m_rxControlBlock.m_recoveryCount; + } else { + paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; + } + + // update counters + if (dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount) { + m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount - dataBlock->m_rxControlBlock.m_originalCount; + } else { + m_nbCorrectableErrors += dataBlock->m_rxControlBlock.m_recoveryCount; + } + + if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode + { + qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" + << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount + << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; + } + else + { + for (int ir = 0; ir < dataBlock->m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks + { + int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_recoveryCount + ir; + int blockIndex = m_cm256DescriptorBlocks[recoveryIndex].Index; + SDRDaemonProtectedBlock *recoveredBlock = + (SDRDaemonProtectedBlock *) m_cm256DescriptorBlocks[recoveryIndex].Block; + memcpy((void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); + if ((blockIndex == 0) && !dataBlock->m_rxControlBlock.m_metaRetrieved) { + dataBlock->m_rxControlBlock.m_metaRetrieved = true; + } + } + } + } + + // Validate block zero and retrieve its data + if (dataBlock->m_rxControlBlock.m_metaRetrieved) + { + SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock->m_superBlocks[0].m_protectedBlock); + boost::crc_32_type crc32; + crc32.process_bytes(metaData, 20); + + if (crc32.checksum() == metaData->m_crc32) + { + if (!(m_currentMeta == *metaData)) + { + printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); + + if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { + m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency*1000); // frequency is in kHz + } + + if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) + { + m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); + m_dataReadQueue.setSize(calculateDataReadQueueSize(metaData->m_sampleRate)); + } + } + + m_currentMeta = *metaData; + } + else + { + qWarning() << "SDRDaemonChannelSource::handleDataBlock: recovered meta: invalid CRC32"; + } + } + + m_dataReadQueue.push(dataBlock); // Push into R/W buffer + } +} + +void SDRDaemonChannelSource::handleData() +{ + SDRDaemonDataBlock* dataBlock; + + while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) { + handleDataBlock(dataBlock); + } +} + +void SDRDaemonChannelSource::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) +{ + qDebug().noquote() << header << ": " + << "|" << 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 + << "|"; +} + +uint32_t SDRDaemonChannelSource::calculateDataReadQueueSize(int sampleRate) +{ + // scale for 20 blocks at 48 kS/s. Take next even number. + uint32_t maxSize = sampleRate / 2400; + maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; + qDebug("SDRDaemonChannelSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); + return maxSize; +} + +int SDRDaemonChannelSource::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); + response.getSdrDaemonChannelSourceSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int SDRDaemonChannelSource::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + SDRDaemonChannelSourceSettings settings = m_settings; + + if (channelSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonChannelSourceSettings()->getDataAddress(); + } + + if (channelSettingsKeys.contains("dataPort")) + { + int dataPort = response.getSdrDaemonChannelSourceSettings()->getDataPort(); + + if ((dataPort < 1024) || (dataPort > 65535)) { + settings.m_dataPort = 9090; + } else { + settings.m_dataPort = dataPort; + } + } + + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("SDRDaemonChannelSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRDaemonChannelSource *msgToGUI = MsgConfigureSDRDaemonChannelSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int SDRDaemonChannelSource::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSourceReport(new SWGSDRangel::SWGSDRDaemonChannelSourceReport()); + response.getSdrDaemonChannelSourceReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void SDRDaemonChannelSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings) +{ + if (response.getSdrDaemonChannelSourceSettings()->getDataAddress()) { + *response.getSdrDaemonChannelSourceSettings()->getDataAddress() = settings.m_dataAddress; + } else { + response.getSdrDaemonChannelSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); + } + + response.getSdrDaemonChannelSourceSettings()->setDataPort(settings.m_dataPort); +} + +void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + struct timeval tv; + gettimeofday(&tv, 0); + + response.getSdrDaemonChannelSourceReport()->setTvSec(tv.tv_sec); + response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); + response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); + response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); + response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); + response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); + response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); +} diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h new file mode 100644 index 000000000..cf51177c2 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) // +// // +// 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ +#define CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ + +#include "cm256.h" + +#include "dsp/basebandsamplesource.h" +#include "channel/channelsourceapi.h" +#include "util/message.h" +#include "sdrdaemonchannelsourcesettings.h" +#include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemondatareadqueue.h" + +class ThreadedBasebandSampleSource; +class UpChannelizer; +class DeviceSinkAPI; +class SDRDaemonChannelSourceThread; +class SDRDaemonDataBlock; + +class SDRDaemonChannelSource : public BasebandSampleSource, public ChannelSourceAPI { + Q_OBJECT +public: + class MsgConfigureSDRDaemonChannelSource : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SDRDaemonChannelSourceSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSDRDaemonChannelSource* create(const SDRDaemonChannelSourceSettings& settings, bool force) + { + return new MsgConfigureSDRDaemonChannelSource(settings, force); + } + + private: + SDRDaemonChannelSourceSettings m_settings; + bool m_force; + + MsgConfigureSDRDaemonChannelSource(const SDRDaemonChannelSourceSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI); + ~SDRDaemonChannelSource(); + virtual void destroy() { delete this; } + + virtual void pull(Sample& sample); + 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 Source"; } + 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + void setDataLink(const QString& dataAddress, uint16_t dataPort); + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + DeviceSinkAPI *m_deviceAPI; + ThreadedBasebandSampleSource* m_threadedChannelizer; + UpChannelizer* m_channelizer; + SDRDaemonDataQueue m_dataQueue; + SDRDaemonChannelSourceThread *m_sourceThread; + CM256 m_cm256; + CM256 *m_cm256p; + bool m_running; + + SDRDaemonChannelSourceSettings m_settings; + + CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) + SDRDaemonMetaDataFEC m_currentMeta; + + SDRDaemonDataReadQueue m_dataReadQueue; + + uint32_t m_nbCorrectableErrors; //!< count of correctable errors in number of blocks + uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks + + void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); + void handleDataBlock(SDRDaemonDataBlock *dataBlock); + void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); + uint32_t calculateDataReadQueueSize(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + +private slots: + void handleData(); +}; + + +#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ */ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp new file mode 100644 index 000000000..ff46c1914 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp @@ -0,0 +1,275 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) GUI // +// // +// 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 "device/devicesinkapi.h" +#include "device/deviceuiset.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "gui/basicchannelsettingsdialog.h" +#include "mainwindow.h" + +#include "ui_sdrdaemonchannelsourcegui.h" +#include "sdrdaemonchannelsource.h" +#include "sdrdaemonchannelsourcegui.h" + + +SDRDaemonChannelSourceGUI* SDRDaemonChannelSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) +{ + SDRDaemonChannelSourceGUI* gui = new SDRDaemonChannelSourceGUI(pluginAPI, deviceUISet, channelTx); + return gui; +} + +void SDRDaemonChannelSourceGUI::destroy() +{ + delete this; +} + +void SDRDaemonChannelSourceGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString SDRDaemonChannelSourceGUI::getName() const +{ + return objectName(); +} + +qint64 SDRDaemonChannelSourceGUI::getCenterFrequency() const { + return m_channelMarker.getCenterFrequency(); +} + +void SDRDaemonChannelSourceGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ + // you can't do that center frquency is fixed to zero + // m_channelMarker.setCenterFrequency(centerFrequency); + // applySettings(); +} + +void SDRDaemonChannelSourceGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray SDRDaemonChannelSourceGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool SDRDaemonChannelSourceGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool SDRDaemonChannelSourceGUI::handleMessage(const Message& message) +{ + if (SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource::match(message)) + { + const SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource& cfg = (SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void SDRDaemonChannelSourceGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void SDRDaemonChannelSourceGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) +{ +} + +void SDRDaemonChannelSourceGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + +SDRDaemonChannelSourceGUI::SDRDaemonChannelSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : + RollupWidget(parent), + ui(new Ui::SDRDaemonChannelSourceGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_tickCount(0) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_sdrDaemonChannelSource = (SDRDaemonChannelSource*) channelTx; + m_sdrDaemonChannelSource->setMessageQueueToGUI(getInputMessageQueue()); + + connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(Qt::cyan); + m_channelMarker.setBandwidth(5000); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("SDRDaemon sink"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + m_settings.setChannelMarker(&m_channelMarker); + + m_deviceUISet->registerTxChannelInstance(SDRDaemonChannelSource::m_channelIdURI, this); + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + + displaySettings(); + applySettings(true); +} + +SDRDaemonChannelSourceGUI::~SDRDaemonChannelSourceGUI() +{ + m_deviceUISet->removeTxChannelInstance(this); + delete m_sdrDaemonChannelSource; // TODO: check this: when the GUI closes it has to delete the channel + delete ui; +} + +void SDRDaemonChannelSourceGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void SDRDaemonChannelSourceGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + setTitleColor(m_channelMarker.getColor()); + + SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource* message = SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource::create(m_settings, force); + m_sdrDaemonChannelSource->getInputMessageQueue()->push(message); + } +} + +void SDRDaemonChannelSourceGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setBandwidth(5000); // TODO + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + // TODO + + blockApplySettings(false); +} + +void SDRDaemonChannelSourceGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void SDRDaemonChannelSourceGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void SDRDaemonChannelSourceGUI::tick() +{ + // TODO if anything useful +} + +void SDRDaemonChannelSourceGUI::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + applySettings(); +} + +void SDRDaemonChannelSourceGUI::on_dataPort_returnPressed() +{ + bool dataOk; + int dataPort = ui->dataPort->text().toInt(&dataOk); + + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) + { + return; + } + else + { + m_settings.m_dataPort = dataPort; + } + + applySettings(); +} + +void SDRDaemonChannelSourceGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + + bool dataOk; + int udpDataPort = ui->dataPort->text().toInt(&dataOk); + + if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) + { + m_settings.m_dataPort = udpDataPort; + } + + applySettings(); +} \ No newline at end of file diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h new file mode 100644 index 000000000..08ecf3423 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) GUI // +// // +// 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ +#define CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ + +#include "plugin/plugininstancegui.h" +#include "gui/rollupwidget.h" +#include "dsp/channelmarker.h" +#include "util/messagequeue.h" + +#include "sdrdaemonchannelsourcesettings.h" + +class PluginAPI; +class DeviceUISet; +class SDRDaemonChannelSource; +class BasebandSampleSource; + +namespace Ui { + class SDRDaemonChannelSourceGUI; +} + +class SDRDaemonChannelSourceGUI : public RollupWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + static SDRDaemonChannelSourceGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceAPI, BasebandSampleSource *channelTx); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::SDRDaemonChannelSourceGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + SDRDaemonChannelSourceSettings m_settings; + bool m_doApplySettings; + int m_tickCount; + + SDRDaemonChannelSource* m_sdrDaemonChannelSource; + MessageQueue m_inputMessageQueue; + + explicit SDRDaemonChannelSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); + virtual ~SDRDaemonChannelSourceGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void handleSourceMessages(); + void on_dataAddress_returnPressed(); + void on_dataPort_returnPressed(); + void on_dataApplyButton_clicked(bool checked); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void tick(); +}; + +#endif // CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui new file mode 100644 index 000000000..67e75a9ea --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui @@ -0,0 +1,179 @@ + + + SDRDaemonChannelSourceGUI + + + + 0 + 0 + 320 + 90 + + + + + 0 + 0 + + + + + 320 + 90 + + + + + Liberation Sans + 9 + + + + LoRa Demodulator + + + + + 10 + 10 + 301 + 61 + + + + 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 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + RollupWidget + QWidget +
    gui/rollupwidget.h
    + 1 +
    +
    + + +
    diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp new file mode 100644 index 000000000..7415d6215 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "sdrdaemonchannelsourcegui.h" +#endif +#include "sdrdaemonchannelsource.h" +#include "sdrdaemonchannelsourceplugin.h" + +const PluginDescriptor SDRDaemonChannelSourcePlugin::m_pluginDescriptor = { + QString("SDRDaemon source"), + QString("4.1.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +SDRDaemonChannelSourcePlugin::SDRDaemonChannelSourcePlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& SDRDaemonChannelSourcePlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void SDRDaemonChannelSourcePlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register + m_pluginAPI->registerTxChannel(SDRDaemonChannelSource::m_channelIdURI, SDRDaemonChannelSource::m_channelId, this); +} + +#ifdef SERVER_MODE +PluginInstanceGUI* SDRDaemonChannelSourcePlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* SDRDaemonChannelSourcePlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) +{ + return SDRDaemonChannelSourceGUI::create(m_pluginAPI, deviceUISet, txChannel); +} +#endif + +BasebandSampleSource* SDRDaemonChannelSourcePlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) +{ + return new SDRDaemonChannelSource(deviceAPI); +} + +ChannelSourceAPI* SDRDaemonChannelSourcePlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +{ + return new SDRDaemonChannelSource(deviceAPI); +} + diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h new file mode 100644 index 000000000..fb0a07036 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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_SDRDAEMONCHANNELSOURCEPLUGIN_H +#define INCLUDE_SDRDAEMONCHANNELSOURCEPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSource; + +class SDRDaemonChannelSourcePlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channeltx.sdrdaemonsource") + +public: + explicit SDRDaemonChannelSourcePlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual PluginInstanceGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel); + virtual BasebandSampleSource* createTxChannelBS(DeviceSinkAPI *deviceAPI); + virtual ChannelSourceAPI* createTxChannelCS(DeviceSinkAPI *deviceAPI); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_SDRDAEMONCHANNELSOURCEPLUGIN_H diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp new file mode 100644 index 000000000..85892e7fb --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 + +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "sdrdaemonchannelsourcesettings.h" + +SDRDaemonChannelSourceSettings::SDRDaemonChannelSourceSettings() +{ + resetToDefaults(); +} + +void SDRDaemonChannelSourceSettings::resetToDefaults() +{ + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; + m_rgbColor = QColor(0, 255, 255).rgb(); + m_title = "SDRDaemon sink"; + +} + +QByteArray SDRDaemonChannelSourceSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeString(1, m_dataAddress); + s.writeU32(2, m_dataPort); + s.writeU32(3, m_rgbColor); + s.writeString(4, m_title); + + return s.final(); +} + +bool SDRDaemonChannelSourceSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + uint32_t tmp; + QString strtmp; + + d.readString(1, &m_dataAddress, "127.0.0.1"); + d.readU32(2, &tmp, 0); + + if ((tmp > 1023) && (tmp < 65535)) { + m_dataPort = tmp; + } else { + m_dataPort = 9090; + } + + d.readU32(3, &m_rgbColor, QColor(0, 255, 255).rgb()); + d.readString(4, &m_title, "AM Modulator"); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + + + + + + diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h new file mode 100644 index 000000000..ea3f5311c --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ +#define CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ + +#include +#include + +class Serializable; + +struct SDRDaemonChannelSourceSettings +{ + QString m_dataAddress; //!< Listening (local) data address + uint16_t m_dataPort; //!< Listening data port + quint32 m_rgbColor; + QString m_title; + + Serializable *m_channelMarker; + + SDRDaemonChannelSourceSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + +#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ */ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp new file mode 100644 index 000000000..3b2d55998 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp @@ -0,0 +1,193 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) UDP receiver 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 "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemonchannelsourcethread.h" + +#include "cm256.h" + +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgDataBind, Message) + +SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : + QThread(parent), + m_running(false), + m_dataQueue(dataQueue), + m_address(QHostAddress::LocalHost), + m_socket(0) +{ + std::fill(m_dataBlocks, m_dataBlocks+4, (SDRDaemonDataBlock *) 0); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread() +{ + qDebug("SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread"); +} + +void SDRDaemonChannelSourceThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void SDRDaemonChannelSourceThread::dataBind(const QString& address, uint16_t port) +{ + MsgDataBind *msg = MsgDataBind::create(address, port); + m_inputMessageQueue.push(msg); +} + +void SDRDaemonChannelSourceThread::startWork() +{ + qDebug("SDRDaemonChannelSourceThread::startWork"); + m_startWaitMutex.lock(); + m_socket = new QUdpSocket(this); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void SDRDaemonChannelSourceThread::stopWork() +{ + qDebug("SDRDaemonChannelSourceThread::stopWork"); + delete m_socket; + m_socket = 0; + m_running = false; + wait(); +} + +void SDRDaemonChannelSourceThread::run() +{ + qDebug("SDRDaemonChannelSourceThread::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("SDRDaemonChannelSourceThread::run: end"); +} + + +void SDRDaemonChannelSourceThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + else if (MsgDataBind::match(*message)) + { + MsgDataBind* notif = (MsgDataBind*) message; + qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); + + if (m_socket) + { + disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + m_socket->bind(notif->getAddress(), notif->getPort()); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + } + } + } +} + +void SDRDaemonChannelSourceThread::readPendingDatagrams() +{ + SDRDaemonSuperBlock superBlock; + qint64 size; + + while (m_socket->hasPendingDatagrams()) + { + QHostAddress sender; + quint16 senderPort = 0; + //qint64 pendingDataSize = m_socket->pendingDatagramSize(); + size = m_socket->readDatagram((char *) &superBlock, (long long int) sizeof(SDRDaemonSuperBlock), &sender, &senderPort); + + if (size == sizeof(SDRDaemonSuperBlock)) + { + unsigned int dataBlockIndex = superBlock.m_header.m_frameIndex % m_nbDataBlocks; + + // create the first block for this index + if (m_dataBlocks[dataBlockIndex] == 0) { + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + } + + if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex < 0) + { + // initialize virgin block with the frame index + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + else + { + // if the frame index is not the same for the same slot it means we are starting a new frame + uint32_t frameIndex = m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex; + + if (superBlock.m_header.m_frameIndex != frameIndex) + { + //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", frameIndex); + m_dataQueue->push(m_dataBlocks[dataBlockIndex]); + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + } + + m_dataBlocks[dataBlockIndex]->m_superBlocks[superBlock.m_header.m_blockIndex] = superBlock; + + if (superBlock.m_header.m_blockIndex == 0) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_metaRetrieved = true; + } + + if (superBlock.m_header.m_blockIndex < SDRDaemonNbOrginalBlocks) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_originalCount++; + } else { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_recoveryCount++; + } + + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount++; + } + else + { + qWarning("SDRDaemonChannelSourceThread::readPendingDatagrams: wrong super block size not processing"); + } + } +} diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h new file mode 100644 index 000000000..fed573f2e --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h @@ -0,0 +1,115 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) UDP receiver 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ +#define CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ + +#include +#include +#include +#include + +#include "util/message.h" +#include "util/messagequeue.h" + +class SDRDaemonDataQueue; +class SDRDaemonDataBlock; +class QUdpSocket; + +class SDRDaemonChannelSourceThread : 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) + { } + }; + + class MsgDataBind : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QHostAddress getAddress() const { return m_address; } + uint16_t getPort() const { return m_port; } + + static MsgDataBind* create(const QString& address, uint16_t port) { + return new MsgDataBind(address, port); + } + + protected: + QHostAddress m_address; + uint16_t m_port; + + MsgDataBind(const QString& address, uint16_t port) : + Message(), + m_port(port) + { + m_address.setAddress(address); + } + }; + + SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); + ~SDRDaemonChannelSourceThread(); + + void startStop(bool start); + void dataBind(const QString& address, uint16_t port); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + MessageQueue m_inputMessageQueue; + SDRDaemonDataQueue *m_dataQueue; + + QHostAddress m_address; + QUdpSocket *m_socket; + + static const uint32_t m_nbDataBlocks = 4; //!< number of data blocks in the ring buffer + SDRDaemonDataBlock *m_dataBlocks[m_nbDataBlocks]; //!< ring buffer of data blocks indexed by frame affinity + + void startWork(); + void stopWork(); + + void run(); + +private slots: + void handleInputMessages(); + void readPendingDatagrams(); +}; + + + +#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ */ From 5151f389773849ba040abcd8b8deed7b3ad33b1f Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 31 Aug 2018 23:29:53 +0200 Subject: [PATCH 677/956] Daemon channel source new plugin (1) --- plugins/channeltx/CMakeLists.txt | 4 +- plugins/channeltx/daemonsrc/CMakeLists.txt | 57 ++++++ plugins/channeltx/daemonsrc/daemonsrc.cpp | 85 +++++++++ plugins/channeltx/daemonsrc/daemonsrc.h | 57 ++++++ plugins/channeltx/daemonsrc/daemonsrcgui.cpp | 109 +++++++++++ plugins/channeltx/daemonsrc/daemonsrcgui.h | 70 +++++++ plugins/channeltx/daemonsrc/daemonsrcgui.ui | 179 ++++++++++++++++++ .../channeltx/daemonsrc/daemonsrcplugin.cpp | 79 ++++++++ plugins/channeltx/daemonsrc/daemonsrcplugin.h | 47 +++++ .../channeltx/daemonsrc/daemonsrcsettings.cpp | 84 ++++++++ .../channeltx/daemonsrc/daemonsrcsettings.h | 41 ++++ 11 files changed, 809 insertions(+), 3 deletions(-) create mode 100644 plugins/channeltx/daemonsrc/CMakeLists.txt create mode 100644 plugins/channeltx/daemonsrc/daemonsrc.cpp create mode 100644 plugins/channeltx/daemonsrc/daemonsrc.h create mode 100644 plugins/channeltx/daemonsrc/daemonsrcgui.cpp create mode 100644 plugins/channeltx/daemonsrc/daemonsrcgui.h create mode 100644 plugins/channeltx/daemonsrc/daemonsrcgui.ui create mode 100644 plugins/channeltx/daemonsrc/daemonsrcplugin.cpp create mode 100644 plugins/channeltx/daemonsrc/daemonsrcplugin.h create mode 100644 plugins/channeltx/daemonsrc/daemonsrcsettings.cpp create mode 100644 plugins/channeltx/daemonsrc/daemonsrcsettings.h diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index b7186967c..75e3cb345 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -7,13 +7,11 @@ add_subdirectory(modwfm) add_subdirectory(udpsink) find_package(CM256cc) - if(CM256CC_FOUND) - add_subdirectory(sdrdaemonchannelsource) + add_subdirectory(daemonsrc) endif(CM256CC_FOUND) find_package(OpenCV) - if (OpenCV_FOUND) add_subdirectory(modatv) endif() diff --git a/plugins/channeltx/daemonsrc/CMakeLists.txt b/plugins/channeltx/daemonsrc/CMakeLists.txt new file mode 100644 index 000000000..6cb7e1acd --- /dev/null +++ b/plugins/channeltx/daemonsrc/CMakeLists.txt @@ -0,0 +1,57 @@ +project(daemonsrc) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(daemonsrc_SOURCES + daemonsrc.cpp +# daemonsrcthread.cpp + daemonsrcgui.cpp + daemonsrcplugin.cpp + daemonsrcsettings.cpp +) + +set(daemonsrc_HEADERS + daemonsrc.h +# daemonsrcthread.h + daemonsrcgui.h + daemonsrcplugin.h + daemonsrcsettings.h +) + +set(daemonsrc_FORMS + daemonsrcgui.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(daemonsrc_FORMS_HEADERS ${daemonsrc_FORMS}) + +add_library(daemonsrc SHARED + ${daemonsrc_SOURCES} + ${daemonsrc_HEADERS_MOC} + ${daemonsrc_FORMS_HEADERS} +) + +target_link_libraries(daemonsrc + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase +# sdrdaemon + sdrgui + swagger +) + +target_link_libraries(daemonsrc Qt5::Core Qt5::Widgets Qt5::Network) + +install(TARGETS daemonsrc DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp new file mode 100644 index 000000000..8f2f23b26 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "device/devicesinkapi.h" +#include "dsp/upchannelizer.h" +#include "dsp/threadedbasebandsamplesource.h" + +#include "daemonsrc.h" + +const QString DaemonSrc::m_channelIdURI = "sdrangel.channeltx.daemonsrc"; +const QString DaemonSrc::m_channelId ="DaemonSrc"; + +DaemonSrc::DaemonSrc(DeviceSinkAPI *deviceAPI) : + ChannelSourceAPI(m_channelIdURI), + m_deviceAPI(deviceAPI) +{ + setObjectName(m_channelId); + + m_channelizer = new UpChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); + m_deviceAPI->addThreadedSource(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); +} + +DaemonSrc::~DaemonSrc() +{ + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSource(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +void DaemonSrc::pull(Sample& sample) +{ + sample.m_real = 0.0f; + sample.m_imag = 0.0f; +} + +void DaemonSrc::pullAudio(int nbSamples __attribute__((unused))) +{ +} + +void DaemonSrc::start() +{ +} + +void DaemonSrc::stop() +{ +} + +bool DaemonSrc::handleMessage(const Message& cmd __attribute__((unused))) +{ + return false; +} + +QByteArray DaemonSrc::serialize() const +{ + return m_settings.serialize(); +} + +bool DaemonSrc::deserialize(const QByteArray& data __attribute__((unused))) +{ + if (m_settings.deserialize(data)) + { + return true; + } + else + { + m_settings.resetToDefaults(); + return false; + } +} diff --git a/plugins/channeltx/daemonsrc/daemonsrc.h b/plugins/channeltx/daemonsrc/daemonsrc.h new file mode 100644 index 000000000..bf4e24813 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrc.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "dsp/basebandsamplesource.h" +#include "channel/channelsourceapi.h" + +#include "daemonsrcsettings.h" + +class DeviceSinkAPI; +class ThreadedBasebandSampleSource; +class UpChannelizer; + +class DaemonSrc : public BasebandSampleSource, public ChannelSourceAPI { + Q_OBJECT + +public: + DaemonSrc(DeviceSinkAPI *deviceAPI); + ~DaemonSrc(); + + virtual void destroy() { delete this; } + + virtual void pull(Sample& sample); + virtual void pullAudio(int nbSamples); + 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 = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + DeviceSinkAPI* m_deviceAPI; + ThreadedBasebandSampleSource* m_threadedChannelizer; + UpChannelizer* m_channelizer; + + DaemonSrcSettings m_settings; +}; diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp new file mode 100644 index 000000000..d67542d45 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "device/devicesinkapi.h" +#include "device/deviceuiset.h" + +#include "ui_daemonsrcgui.h" +#include "daemonsrcgui.h" + +DaemonSrcGUI* DaemonSrcGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) +{ + DaemonSrcGUI* gui = new DaemonSrcGUI(pluginAPI, deviceUISet, channelTx); + return gui; +} + +void DaemonSrcGUI::destroy() +{ + delete this; +} + +void DaemonSrcGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString DaemonSrcGUI::getName() const +{ + return objectName(); +} + +qint64 DaemonSrcGUI::getCenterFrequency() const { + return 0; +} + +void DaemonSrcGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ +} + +void DaemonSrcGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray DaemonSrcGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool DaemonSrcGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool DaemonSrcGUI::handleMessage(const Message& message __attribute__((unused))) +{ + return false; +} + +DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx __attribute__((unused)), QWidget* parent) : + RollupWidget(parent), + ui(new Ui::DaemonSrcGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); +} + +DaemonSrcGUI::~DaemonSrcGUI() +{ + m_deviceUISet->removeTxChannelInstance(this); + delete ui; +} + +void DaemonSrcGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void DaemonSrcGUI::applySettings(bool force __attribute((unused))) +{ +} + +void DaemonSrcGUI::displaySettings() +{ +} diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.h b/plugins/channeltx/daemonsrc/daemonsrcgui.h new file mode 100644 index 000000000..1f5afe88b --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.h @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCGUI_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCGUI_H_ + +#include "plugin/plugininstancegui.h" +#include "gui/rollupwidget.h" +#include "util/messagequeue.h" + +#include "daemonsrcsettings.h" + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSource; + +namespace Ui { + class DaemonSrcGUI; +} + +class DaemonSrcGUI : public RollupWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + static DaemonSrcGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::DaemonSrcGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + MessageQueue m_inputMessageQueue; + DaemonSrcSettings m_settings; + bool m_doApplySettings; + + explicit DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); + virtual ~DaemonSrcGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + +}; + + +#endif /* PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCGUI_H_ */ diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.ui b/plugins/channeltx/daemonsrc/daemonsrcgui.ui new file mode 100644 index 000000000..822b9bfe3 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.ui @@ -0,0 +1,179 @@ + + + DaemonSrcGUI + + + + 0 + 0 + 320 + 90 + + + + + 0 + 0 + + + + + 320 + 90 + + + + + Liberation Sans + 9 + + + + LoRa Demodulator + + + + + 10 + 10 + 301 + 61 + + + + 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 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + RollupWidget + QWidget +
    gui/rollupwidget.h
    + 1 +
    +
    + + +
    diff --git a/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp b/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp new file mode 100644 index 000000000..83d2f9ab8 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcplugin.cpp @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "daemonsrcgui.h" +#endif +#include "daemonsrc.h" +#include "daemonsrcplugin.h" + +const PluginDescriptor DaemonSrcPlugin::m_pluginDescriptor = { + QString("Daemon channel source"), + QString("4.1.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +DaemonSrcPlugin::DaemonSrcPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& DaemonSrcPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void DaemonSrcPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register source + m_pluginAPI->registerTxChannel(DaemonSrc::m_channelIdURI, DaemonSrc::m_channelId, this); +} + +#ifdef SERVER_MODE +PluginInstanceGUI* DaemonSrcPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* DaemonSrcPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) +{ + return DaemonSrcGUI::create(m_pluginAPI, deviceUISet, txChannel); +} +#endif + +BasebandSampleSource* DaemonSrcPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) +{ + return new DaemonSrc(deviceAPI); +} + +ChannelSourceAPI* DaemonSrcPlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +{ + return new DaemonSrc(deviceAPI); +} + + + diff --git a/plugins/channeltx/daemonsrc/daemonsrcplugin.h b/plugins/channeltx/daemonsrc/daemonsrcplugin.h new file mode 100644 index 000000000..9e353e38e --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcplugin.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCPLUGIN_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCPLUGIN_H_ + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSource; + +class DaemonSrcPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channeltx.daemonsrc") + +public: + explicit DaemonSrcPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual PluginInstanceGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel); + virtual BasebandSampleSource* createTxChannelBS(DeviceSinkAPI *deviceAPI); + virtual ChannelSourceAPI* createTxChannelCS(DeviceSinkAPI *deviceAPI); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif /* PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCPLUGIN_H_ */ diff --git a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp b/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp new file mode 100644 index 000000000..f96414ff9 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "daemonsrcsettings.h" + +#include + +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "daemonsrcsettings.h" + +DaemonSrcSettings::DaemonSrcSettings() +{ + resetToDefaults(); +} + +void DaemonSrcSettings::resetToDefaults() +{ + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; + m_rgbColor = QColor(0, 255, 255).rgb(); + m_title = "SDRDaemon sink"; + +} + +QByteArray DaemonSrcSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeString(1, m_dataAddress); + s.writeU32(2, m_dataPort); + s.writeU32(3, m_rgbColor); + s.writeString(4, m_title); + + return s.final(); +} + +bool DaemonSrcSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + uint32_t tmp; + QString strtmp; + + d.readString(1, &m_dataAddress, "127.0.0.1"); + d.readU32(2, &tmp, 0); + + if ((tmp > 1023) && (tmp < 65535)) { + m_dataPort = tmp; + } else { + m_dataPort = 9090; + } + + d.readU32(3, &m_rgbColor, QColor(0, 255, 255).rgb()); + d.readString(4, &m_title, "AM Modulator"); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/channeltx/daemonsrc/daemonsrcsettings.h b/plugins/channeltx/daemonsrc/daemonsrcsettings.h new file mode 100644 index 000000000..a1fd83594 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcsettings.h @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCSETTINGS_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCSETTINGS_H_ + +#include +#include + +class Serializable; + +struct DaemonSrcSettings +{ + QString m_dataAddress; //!< Listening (local) data address + uint16_t m_dataPort; //!< Listening data port + quint32 m_rgbColor; + QString m_title; + + Serializable *m_channelMarker; + + DaemonSrcSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCSETTINGS_H_ */ From 4f4d417d5ab7d7556d35f59341d0bad62c4bd486 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 1 Sep 2018 00:18:35 +0200 Subject: [PATCH 678/956] Daemon channel source new plugin (2) --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 22 ++++- plugins/channeltx/daemonsrc/daemonsrc.h | 21 +++++ plugins/channeltx/daemonsrc/daemonsrcgui.cpp | 88 ++++++++++++++++++- plugins/channeltx/daemonsrc/daemonsrcgui.h | 14 ++- plugins/channeltx/daemonsrc/daemonsrcgui.ui | 2 +- .../channeltx/daemonsrc/daemonsrcsettings.cpp | 5 +- 6 files changed, 145 insertions(+), 7 deletions(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index 8f2f23b26..694a258dc 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -14,12 +14,16 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "device/devicesinkapi.h" #include "dsp/upchannelizer.h" #include "dsp/threadedbasebandsamplesource.h" #include "daemonsrc.h" +MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgSampleRateNotification, Message) + const QString DaemonSrc::m_channelIdURI = "sdrangel.channeltx.daemonsrc"; const QString DaemonSrc::m_channelId ="DaemonSrc"; @@ -61,8 +65,24 @@ void DaemonSrc::stop() { } -bool DaemonSrc::handleMessage(const Message& cmd __attribute__((unused))) +bool DaemonSrc::handleMessage(const Message& cmd) { + if (UpChannelizer::MsgChannelizerNotification::match(cmd)) + { + UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "DaemonSrc::handleMessage: MsgChannelizerNotification:" + << " basebandSampleRate: " << notif.getBasebandSampleRate() + << " outputSampleRate: " << notif.getSampleRate() + << " inputFrequencyOffset: " << notif.getFrequencyOffset(); + + if (m_guiMessageQueue) + { + MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getBasebandSampleRate()); + m_guiMessageQueue->push(msg); + } + + return true; + } return false; } diff --git a/plugins/channeltx/daemonsrc/daemonsrc.h b/plugins/channeltx/daemonsrc/daemonsrc.h index bf4e24813..743313b92 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.h +++ b/plugins/channeltx/daemonsrc/daemonsrc.h @@ -16,6 +16,7 @@ #include "dsp/basebandsamplesource.h" #include "channel/channelsourceapi.h" +#include "util/message.h" #include "daemonsrcsettings.h" @@ -27,6 +28,26 @@ class DaemonSrc : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: + class MsgSampleRateNotification : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgSampleRateNotification* create(int sampleRate) { + return new MsgSampleRateNotification(sampleRate); + } + + int getSampleRate() const { return m_sampleRate; } + + private: + + MsgSampleRateNotification(int sampleRate) : + Message(), + m_sampleRate(sampleRate) + { } + + int m_sampleRate; + }; + DaemonSrc(DeviceSinkAPI *deviceAPI); ~DaemonSrc(); diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp index d67542d45..7e5074d55 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp @@ -16,7 +16,10 @@ #include "device/devicesinkapi.h" #include "device/deviceuiset.h" +#include "gui/basicchannelsettingsdialog.h" +#include "mainwindow.h" +#include "daemonsrc.h" #include "ui_daemonsrcgui.h" #include "daemonsrcgui.h" @@ -73,8 +76,14 @@ bool DaemonSrcGUI::deserialize(const QByteArray& data) } } -bool DaemonSrcGUI::handleMessage(const Message& message __attribute__((unused))) +bool DaemonSrcGUI::handleMessage(const Message& message) { + if (DaemonSrc::MsgSampleRateNotification::match(message)) + { + DaemonSrc::MsgSampleRateNotification& notif = (DaemonSrc::MsgSampleRateNotification&) message; + m_channelMarker.setBandwidth(notif.getSampleRate()); + } + return false; } @@ -87,6 +96,27 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + + m_daemonSrc = (DaemonSrc*) channelTx; + m_daemonSrc->setMessageQueueToGUI(getInputMessageQueue()); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(m_settings.m_rgbColor); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("Daemon source"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + m_settings.setChannelMarker(&m_channelMarker); + + m_deviceUISet->registerTxChannelInstance(DaemonSrc::m_channelIdURI, this); + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + + displaySettings(); + applySettings(true); } DaemonSrcGUI::~DaemonSrcGUI() @@ -106,4 +136,60 @@ void DaemonSrcGUI::applySettings(bool force __attribute((unused))) void DaemonSrcGUI::displaySettings() { + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setBandwidth(5000); // TODO + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + ui->dataAddress->setText(m_settings.m_dataAddress); + ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); + blockApplySettings(false); +} + +void DaemonSrcGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void DaemonSrcGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void DaemonSrcGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void DaemonSrcGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) +{ +} + +void DaemonSrcGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); } diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.h b/plugins/channeltx/daemonsrc/daemonsrcgui.h index 1f5afe88b..f8d87bcd6 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.h +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.h @@ -20,12 +20,14 @@ #include "plugin/plugininstancegui.h" #include "gui/rollupwidget.h" #include "util/messagequeue.h" +#include "dsp/channelmarker.h" #include "daemonsrcsettings.h" class PluginAPI; class DeviceUISet; class BasebandSampleSource; +class DaemonSrc; namespace Ui { class DaemonSrcGUI; @@ -53,10 +55,13 @@ private: Ui::DaemonSrcGUI* ui; PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; - MessageQueue m_inputMessageQueue; + ChannelMarker m_channelMarker; DaemonSrcSettings m_settings; bool m_doApplySettings; + DaemonSrc* m_daemonSrc; + MessageQueue m_inputMessageQueue; + explicit DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); virtual ~DaemonSrcGUI(); @@ -64,6 +69,13 @@ private: void applySettings(bool force = false); void displaySettings(); + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void handleSourceMessages(); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); }; diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.ui b/plugins/channeltx/daemonsrc/daemonsrcgui.ui index 822b9bfe3..b4c03dffa 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.ui +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.ui @@ -29,7 +29,7 @@ - LoRa Demodulator + Daemon source diff --git a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp b/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp index f96414ff9..30db27dc6 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp @@ -31,9 +31,8 @@ void DaemonSrcSettings::resetToDefaults() { m_dataAddress = "127.0.0.1"; m_dataPort = 9090; - m_rgbColor = QColor(0, 255, 255).rgb(); - m_title = "SDRDaemon sink"; - + m_rgbColor = QColor(140, 4, 4).rgb(); + m_title = "Daemon source"; } QByteArray DaemonSrcSettings::serialize() const From 4345132a9f27aada3b46f5f20c4aef4b17526e9a Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 1 Sep 2018 04:37:23 +0200 Subject: [PATCH 679/956] Daemon channel source new plugin (3) --- plugins/channeltx/daemonsrc/CMakeLists.txt | 8 +- plugins/channeltx/daemonsrc/daemonsrc.cpp | 207 +++++++++++++++++- plugins/channeltx/daemonsrc/daemonsrc.h | 71 +++++- plugins/channeltx/daemonsrc/daemonsrcgui.cpp | 47 +++- plugins/channeltx/daemonsrc/daemonsrcgui.h | 3 + .../channeltx/daemonsrc/daemonsrcthread.cpp | 189 ++++++++++++++++ plugins/channeltx/daemonsrc/daemonsrcthread.h | 109 +++++++++ 7 files changed, 625 insertions(+), 9 deletions(-) create mode 100644 plugins/channeltx/daemonsrc/daemonsrcthread.cpp create mode 100644 plugins/channeltx/daemonsrc/daemonsrcthread.h diff --git a/plugins/channeltx/daemonsrc/CMakeLists.txt b/plugins/channeltx/daemonsrc/CMakeLists.txt index 6cb7e1acd..158eda183 100644 --- a/plugins/channeltx/daemonsrc/CMakeLists.txt +++ b/plugins/channeltx/daemonsrc/CMakeLists.txt @@ -4,7 +4,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(daemonsrc_SOURCES daemonsrc.cpp -# daemonsrcthread.cpp + daemonsrcthread.cpp daemonsrcgui.cpp daemonsrcplugin.cpp daemonsrcsettings.cpp @@ -12,7 +12,7 @@ set(daemonsrc_SOURCES set(daemonsrc_HEADERS daemonsrc.h -# daemonsrcthread.h + daemonsrcthread.h daemonsrcgui.h daemonsrcplugin.h daemonsrcsettings.h @@ -25,7 +25,7 @@ set(daemonsrc_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} -# ${CMAKE_SOURCE_DIR}/sdrdaemon + ${CMAKE_SOURCE_DIR}/sdrdaemon ${CM256CC_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) @@ -47,7 +47,7 @@ target_link_libraries(daemonsrc ${QT_LIBRARIES} ${CM256CC_LIBRARIES} sdrbase -# sdrdaemon + sdrdaemon sdrgui swagger ) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index 694a258dc..798b98018 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -14,22 +14,35 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include +#include + #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGSDRDaemonChannelSourceReport.h" + #include "device/devicesinkapi.h" #include "dsp/upchannelizer.h" #include "dsp/threadedbasebandsamplesource.h" +#include "daemonsrcthread.h" #include "daemonsrc.h" MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgSampleRateNotification, Message) +MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgConfigureDaemonSrc, Message) const QString DaemonSrc::m_channelIdURI = "sdrangel.channeltx.daemonsrc"; const QString DaemonSrc::m_channelId ="DaemonSrc"; DaemonSrc::DaemonSrc(DeviceSinkAPI *deviceAPI) : ChannelSourceAPI(m_channelIdURI), - m_deviceAPI(deviceAPI) + m_deviceAPI(deviceAPI), + m_sourceThread(0), + m_running(false), + m_nbCorrectableErrors(0), + m_nbUncorrectableErrors(0) { setObjectName(m_channelId); @@ -37,6 +50,10 @@ DaemonSrc::DaemonSrc(DeviceSinkAPI *deviceAPI) : m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); + + connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; + m_currentMeta.init(); } DaemonSrc::~DaemonSrc() @@ -49,8 +66,7 @@ DaemonSrc::~DaemonSrc() void DaemonSrc::pull(Sample& sample) { - sample.m_real = 0.0f; - sample.m_imag = 0.0f; + m_dataReadQueue.readSample(sample); } void DaemonSrc::pullAudio(int nbSamples __attribute__((unused))) @@ -59,10 +75,40 @@ void DaemonSrc::pullAudio(int nbSamples __attribute__((unused))) void DaemonSrc::start() { + qDebug("DaemonSrc::start"); + + if (m_running) { + stop(); + } + + m_sourceThread = new DaemonSrcThread(&m_dataQueue); + m_sourceThread->startStop(true); + m_sourceThread->dataBind(m_settings.m_dataAddress, m_settings.m_dataPort); + m_running = true; } void DaemonSrc::stop() { + qDebug("DaemonSrc::stop"); + + if (m_sourceThread != 0) + { + m_sourceThread->startStop(false); + m_sourceThread->deleteLater(); + m_sourceThread = 0; + } + + m_running = false; +} + +void DaemonSrc::setDataLink(const QString& dataAddress, uint16_t dataPort) +{ + DaemonSrcSettings settings = m_settings; + settings.m_dataAddress = dataAddress; + settings.m_dataPort = dataPort; + + MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(settings, false); + m_inputMessageQueue.push(msg); } bool DaemonSrc::handleMessage(const Message& cmd) @@ -83,6 +129,14 @@ bool DaemonSrc::handleMessage(const Message& cmd) return true; } + else if (MsgConfigureDaemonSrc::match(cmd)) + { + MsgConfigureDaemonSrc& cfg = (MsgConfigureDaemonSrc&) cmd; + qDebug() << "MsgConfigureDaemonSrc::handleMessage: MsgConfigureDaemonSrc"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } return false; } @@ -95,11 +149,158 @@ bool DaemonSrc::deserialize(const QByteArray& data __attribute__((unused))) { if (m_settings.deserialize(data)) { + MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(m_settings, true); + m_inputMessageQueue.push(msg); return true; } else { m_settings.resetToDefaults(); + MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(m_settings, true); + m_inputMessageQueue.push(msg); return false; } } + +void DaemonSrc::applySettings(const DaemonSrcSettings& settings, bool force) +{ + qDebug() << "DaemonSrc::applySettings:" + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort + << " force: " << force; + + bool change = false; + + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { + change = true; + } + + if ((m_settings.m_dataPort != settings.m_dataPort) || force) { + change = true; + } + + if (change && m_sourceThread) { + m_sourceThread->dataBind(settings.m_dataAddress, settings.m_dataPort); + } + + m_settings = settings; +} + +void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unused))) +{ +} + +void DaemonSrc::handleData() +{ + SDRDaemonDataBlock* dataBlock; + + while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) { + handleDataBlock(dataBlock); + } +} + +void DaemonSrc::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) +{ + qDebug().noquote() << header << ": " + << "|" << 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 + << "|"; +} + +uint32_t DaemonSrc::calculateDataReadQueueSize(int sampleRate) +{ + // scale for 20 blocks at 48 kS/s. Take next even number. + uint32_t maxSize = sampleRate / 2400; + maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; + qDebug("SDRDaemonChannelSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); + return maxSize; +} + +int DaemonSrc::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); + response.getSdrDaemonChannelSourceSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int DaemonSrc::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + DaemonSrcSettings settings = m_settings; + + if (channelSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonChannelSourceSettings()->getDataAddress(); + } + + if (channelSettingsKeys.contains("dataPort")) + { + int dataPort = response.getSdrDaemonChannelSourceSettings()->getDataPort(); + + if ((dataPort < 1024) || (dataPort > 65535)) { + settings.m_dataPort = 9090; + } else { + settings.m_dataPort = dataPort; + } + } + + MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("DaemonSrc::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureDaemonSrc *msgToGUI = MsgConfigureDaemonSrc::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int DaemonSrc::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSourceReport(new SWGSDRangel::SWGSDRDaemonChannelSourceReport()); + response.getSdrDaemonChannelSourceReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void DaemonSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSrcSettings& settings) +{ + if (response.getSdrDaemonChannelSourceSettings()->getDataAddress()) { + *response.getSdrDaemonChannelSourceSettings()->getDataAddress() = settings.m_dataAddress; + } else { + response.getSdrDaemonChannelSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); + } + + response.getSdrDaemonChannelSourceSettings()->setDataPort(settings.m_dataPort); +} + +void DaemonSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + struct timeval tv; + gettimeofday(&tv, 0); + + response.getSdrDaemonChannelSourceReport()->setTvSec(tv.tv_sec); + response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); + response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); + response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); + response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); + response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); + response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); +} diff --git a/plugins/channeltx/daemonsrc/daemonsrc.h b/plugins/channeltx/daemonsrc/daemonsrc.h index 743313b92..8d0dd9a1e 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.h +++ b/plugins/channeltx/daemonsrc/daemonsrc.h @@ -14,20 +14,50 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "cm256.h" + #include "dsp/basebandsamplesource.h" #include "channel/channelsourceapi.h" #include "util/message.h" #include "daemonsrcsettings.h" +#include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemondatareadqueue.h" -class DeviceSinkAPI; class ThreadedBasebandSampleSource; class UpChannelizer; +class DeviceSinkAPI; +class DaemonSrcThread; +class SDRDaemonDataBlock; class DaemonSrc : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: + class MsgConfigureDaemonSrc : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DaemonSrcSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDaemonSrc* create(const DaemonSrcSettings& settings, bool force) + { + return new MsgConfigureDaemonSrc(settings, force); + } + + private: + DaemonSrcSettings m_settings; + bool m_force; + + MsgConfigureDaemonSrc(const DaemonSrcSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + class MsgSampleRateNotification : public Message { MESSAGE_CLASS_DECLARATION @@ -66,6 +96,22 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + void setDataLink(const QString& dataAddress, uint16_t dataPort); + static const QString m_channelIdURI; static const QString m_channelId; @@ -73,6 +119,29 @@ private: DeviceSinkAPI* m_deviceAPI; ThreadedBasebandSampleSource* m_threadedChannelizer; UpChannelizer* m_channelizer; + SDRDaemonDataQueue m_dataQueue; + DaemonSrcThread *m_sourceThread; + CM256 m_cm256; + CM256 *m_cm256p; + bool m_running; DaemonSrcSettings m_settings; + + CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) + SDRDaemonMetaDataFEC m_currentMeta; + + SDRDaemonDataReadQueue m_dataReadQueue; + + uint32_t m_nbCorrectableErrors; //!< count of correctable errors in number of blocks + uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks + + void applySettings(const DaemonSrcSettings& settings, bool force = false); + void handleDataBlock(SDRDaemonDataBlock *dataBlock); + void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); + uint32_t calculateDataReadQueueSize(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSrcSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + +private slots: + void handleData(); }; diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp index 7e5074d55..9d2212ed9 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp @@ -130,8 +130,15 @@ void DaemonSrcGUI::blockApplySettings(bool block) m_doApplySettings = !block; } -void DaemonSrcGUI::applySettings(bool force __attribute((unused))) +void DaemonSrcGUI::applySettings(bool force) { + if (m_doApplySettings) + { + setTitleColor(m_channelMarker.getColor()); + + DaemonSrc::MsgConfigureDaemonSrc* message = DaemonSrc::MsgConfigureDaemonSrc::create(m_settings, force); + m_daemonSrc->getInputMessageQueue()->push(message); + } } void DaemonSrcGUI::displaySettings() @@ -193,3 +200,41 @@ void DaemonSrcGUI::onMenuDialogCalled(const QPoint &p) applySettings(); } + +void DaemonSrcGUI::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + applySettings(); +} + +void DaemonSrcGUI::on_dataPort_returnPressed() +{ + bool dataOk; + int dataPort = ui->dataPort->text().toInt(&dataOk); + + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) + { + return; + } + else + { + m_settings.m_dataPort = dataPort; + } + + applySettings(); +} + +void DaemonSrcGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + + bool dataOk; + int udpDataPort = ui->dataPort->text().toInt(&dataOk); + + if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) + { + m_settings.m_dataPort = udpDataPort; + } + + applySettings(); +} diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.h b/plugins/channeltx/daemonsrc/daemonsrcgui.h index f8d87bcd6..1c55506bb 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.h +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.h @@ -74,6 +74,9 @@ private: private slots: void handleSourceMessages(); + void on_dataAddress_returnPressed(); + void on_dataPort_returnPressed(); + void on_dataApplyButton_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); }; diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp new file mode 100644 index 000000000..bf2b7c176 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp @@ -0,0 +1,189 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "cm256.h" + +#include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemonchannelsourcethread.h" + +#include "daemonsrcthread.h" + +MESSAGE_CLASS_DEFINITION(DaemonSrcThread::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(DaemonSrcThread::MsgDataBind, Message) + +DaemonSrcThread::DaemonSrcThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : + QThread(parent), + m_running(false), + m_dataQueue(dataQueue), + m_address(QHostAddress::LocalHost), + m_socket(0) +{ + std::fill(m_dataBlocks, m_dataBlocks+4, (SDRDaemonDataBlock *) 0); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +DaemonSrcThread::~DaemonSrcThread() +{ + qDebug("DaemonSrcThread::~SDRDaemonChannelSourceThread"); +} + +void DaemonSrcThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void DaemonSrcThread::dataBind(const QString& address, uint16_t port) +{ + MsgDataBind *msg = MsgDataBind::create(address, port); + m_inputMessageQueue.push(msg); +} + +void SDRDaemonChannelSourceThread::startWork() +{ + qDebug("DaemonSrcThread::startWork"); + m_startWaitMutex.lock(); + m_socket = new QUdpSocket(this); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void DaemonSrcThread::stopWork() +{ + qDebug("DaemonSrcThread::stopWork"); + delete m_socket; + m_socket = 0; + m_running = false; + wait(); +} + +void DaemonSrcThread::run() +{ + qDebug("DaemonSrcThread::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("DaemonSrcThread::run: end"); +} + + +void DaemonSrcThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("DaemonSrcThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + else if (MsgDataBind::match(*message)) + { + MsgDataBind* notif = (MsgDataBind*) message; + qDebug("DaemonSrcThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); + + if (m_socket) + { + disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + m_socket->bind(notif->getAddress(), notif->getPort()); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + } + } + } +} + +void DaemonSrcThread::readPendingDatagrams() +{ + SDRDaemonSuperBlock superBlock; + qint64 size; + + while (m_socket->hasPendingDatagrams()) + { + QHostAddress sender; + quint16 senderPort = 0; + //qint64 pendingDataSize = m_socket->pendingDatagramSize(); + size = m_socket->readDatagram((char *) &superBlock, (long long int) sizeof(SDRDaemonSuperBlock), &sender, &senderPort); + + if (size == sizeof(SDRDaemonSuperBlock)) + { + unsigned int dataBlockIndex = superBlock.m_header.m_frameIndex % m_nbDataBlocks; + + // create the first block for this index + if (m_dataBlocks[dataBlockIndex] == 0) { + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + } + + if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex < 0) + { + // initialize virgin block with the frame index + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + else + { + // if the frame index is not the same for the same slot it means we are starting a new frame + uint32_t frameIndex = m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex; + + if (superBlock.m_header.m_frameIndex != frameIndex) + { + //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", frameIndex); + m_dataQueue->push(m_dataBlocks[dataBlockIndex]); + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + } + + m_dataBlocks[dataBlockIndex]->m_superBlocks[superBlock.m_header.m_blockIndex] = superBlock; + + if (superBlock.m_header.m_blockIndex == 0) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_metaRetrieved = true; + } + + if (superBlock.m_header.m_blockIndex < SDRDaemonNbOrginalBlocks) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_originalCount++; + } else { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_recoveryCount++; + } + + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount++; + } + else + { + qWarning("SDRDaemonChannelSourceThread::readPendingDatagrams: wrong super block size not processing"); + } + } +} + diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.h b/plugins/channeltx/daemonsrc/daemonsrcthread.h new file mode 100644 index 000000000..dc11e1a76 --- /dev/null +++ b/plugins/channeltx/daemonsrc/daemonsrcthread.h @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCTHREAD_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCTHREAD_H_ + +#include +#include +#include +#include + +#include "util/message.h" +#include "util/messagequeue.h" + +class SDRDaemonDataQueue; +class SDRDaemonDataBlock; +class QUdpSocket; + +class DaemonSrcThread : 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) + { } + }; + + class MsgDataBind : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QHostAddress getAddress() const { return m_address; } + uint16_t getPort() const { return m_port; } + + static MsgDataBind* create(const QString& address, uint16_t port) { + return new MsgDataBind(address, port); + } + + protected: + QHostAddress m_address; + uint16_t m_port; + + MsgDataBind(const QString& address, uint16_t port) : + Message(), + m_port(port) + { + m_address.setAddress(address); + } + }; + + DaemonSrcThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); + ~DaemonSrcThread(); + + void startStop(bool start); + void dataBind(const QString& address, uint16_t port); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + MessageQueue m_inputMessageQueue; + SDRDaemonDataQueue *m_dataQueue; + + QHostAddress m_address; + QUdpSocket *m_socket; + + static const uint32_t m_nbDataBlocks = 4; //!< number of data blocks in the ring buffer + SDRDaemonDataBlock *m_dataBlocks[m_nbDataBlocks]; //!< ring buffer of data blocks indexed by frame affinity + + void startWork(); + void stopWork(); + + void run(); + +private slots: + void handleInputMessages(); + void readPendingDatagrams(); +}; + + + +#endif /* PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCTHREAD_H_ */ From 2a6752c4cf9a4c60642b4bffc860ceee2e6925e6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 1 Sep 2018 04:43:15 +0200 Subject: [PATCH 680/956] Daemon channel source new plugin (4) --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 106 ++++++++++++++++++ .../channeltx/daemonsrc/daemonsrcthread.cpp | 2 +- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index 798b98018..c30117492 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include @@ -23,6 +25,7 @@ #include "SWGChannelReport.h" #include "SWGSDRDaemonChannelSourceReport.h" +#include "dsp/devicesamplesink.h" #include "device/devicesinkapi.h" #include "dsp/upchannelizer.h" #include "dsp/threadedbasebandsamplesource.h" @@ -188,6 +191,109 @@ void DaemonSrc::applySettings(const DaemonSrcSettings& settings, bool force) void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unused))) { + if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) + { + qWarning("SDRDaemonChannelSource::handleDataBlock: incomplete data block: not processing"); + } + else + { + int blockCount = 0; + + for (int blockIndex = 0; blockIndex < 256; blockIndex++) + { + if ((blockIndex == 0) && (dataBlock->m_rxControlBlock.m_metaRetrieved)) + { + m_cm256DescriptorBlocks[blockCount].Index = 0; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[0].m_protectedBlock); + blockCount++; + } + else if (dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex != 0) + { + m_cm256DescriptorBlocks[blockCount].Index = dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock); + blockCount++; + } + } + + //qDebug("SDRDaemonChannelSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); + + // Need to use the CM256 recovery + if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) + { + qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); + CM256::cm256_encoder_params paramsCM256; + paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes + + if (m_currentMeta.m_tv_sec == 0) { + paramsCM256.RecoveryCount = dataBlock->m_rxControlBlock.m_recoveryCount; + } else { + paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; + } + + // update counters + if (dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount) { + m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount - dataBlock->m_rxControlBlock.m_originalCount; + } else { + m_nbCorrectableErrors += dataBlock->m_rxControlBlock.m_recoveryCount; + } + + if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode + { + qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" + << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount + << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; + } + else + { + for (int ir = 0; ir < dataBlock->m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks + { + int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_recoveryCount + ir; + int blockIndex = m_cm256DescriptorBlocks[recoveryIndex].Index; + SDRDaemonProtectedBlock *recoveredBlock = + (SDRDaemonProtectedBlock *) m_cm256DescriptorBlocks[recoveryIndex].Block; + memcpy((void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); + if ((blockIndex == 0) && !dataBlock->m_rxControlBlock.m_metaRetrieved) { + dataBlock->m_rxControlBlock.m_metaRetrieved = true; + } + } + } + } + + // Validate block zero and retrieve its data + if (dataBlock->m_rxControlBlock.m_metaRetrieved) + { + SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock->m_superBlocks[0].m_protectedBlock); + boost::crc_32_type crc32; + crc32.process_bytes(metaData, 20); + + if (crc32.checksum() == metaData->m_crc32) + { + if (!(m_currentMeta == *metaData)) + { + printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); + + if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { + m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency*1000); // frequency is in kHz + } + + if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) + { + m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); + m_dataReadQueue.setSize(calculateDataReadQueueSize(metaData->m_sampleRate)); + } + } + + m_currentMeta = *metaData; + } + else + { + qWarning() << "SDRDaemonChannelSource::handleDataBlock: recovered meta: invalid CRC32"; + } + } + + m_dataReadQueue.push(dataBlock); // Push into R/W buffer + } } void DaemonSrc::handleData() diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp index bf2b7c176..e810b944a 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp @@ -56,7 +56,7 @@ void DaemonSrcThread::dataBind(const QString& address, uint16_t port) m_inputMessageQueue.push(msg); } -void SDRDaemonChannelSourceThread::startWork() +void DaemonSrcThread::startWork() { qDebug("DaemonSrcThread::startWork"); m_startWaitMutex.lock(); From 3469b91163d9ebf5f2270d49aa82a7f7f3222de9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 1 Sep 2018 09:53:16 +0200 Subject: [PATCH 681/956] Daemon channel source new plugin (5) --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 14 +++++- plugins/channeltx/daemonsrc/daemonsrcgui.cpp | 16 ++++++- sdrbase/resources/webapi/doc/html2/index.html | 8 +++- .../include/SDRDaemonChannelSource.yaml | 4 ++ sdrbase/webapi/webapirequestmapper.cpp | 12 ++--- .../include/SDRDaemonChannelSource.yaml | 4 ++ swagger/sdrangel/code/html2/index.html | 8 +++- .../SWGSDRDaemonChannelSourceSettings.cpp | 44 +++++++++++++++++++ .../SWGSDRDaemonChannelSourceSettings.h | 12 +++++ 9 files changed, 111 insertions(+), 11 deletions(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index c30117492..e5e949307 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -349,7 +349,6 @@ int DaemonSrc::webapiSettingsPutPatch( if (channelSettingsKeys.contains("dataAddress")) { settings.m_dataAddress = *response.getSdrDaemonChannelSourceSettings()->getDataAddress(); } - if (channelSettingsKeys.contains("dataPort")) { int dataPort = response.getSdrDaemonChannelSourceSettings()->getDataPort(); @@ -360,6 +359,12 @@ int DaemonSrc::webapiSettingsPutPatch( settings.m_dataPort = dataPort; } } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getSdrDaemonChannelSourceSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getSdrDaemonChannelSourceSettings()->getTitle(); + } MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(settings, force); m_inputMessageQueue.push(msg); @@ -395,6 +400,13 @@ void DaemonSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& res } response.getSdrDaemonChannelSourceSettings()->setDataPort(settings.m_dataPort); + response.getSdrDaemonChannelSourceSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getSdrDaemonChannelSourceSettings()->getTitle()) { + *response.getSdrDaemonChannelSourceSettings()->getTitle() = settings.m_title; + } else { + response.getSdrDaemonChannelSourceSettings()->setTitle(new QString(settings.m_title)); + } } void DaemonSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp index 9d2212ed9..a351b763e 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp @@ -82,9 +82,21 @@ bool DaemonSrcGUI::handleMessage(const Message& message) { DaemonSrc::MsgSampleRateNotification& notif = (DaemonSrc::MsgSampleRateNotification&) message; m_channelMarker.setBandwidth(notif.getSampleRate()); + return true; + } + else if (DaemonSrc::MsgConfigureDaemonSrc::match(message)) + { + const DaemonSrc::MsgConfigureDaemonSrc& cfg = (DaemonSrc::MsgConfigureDaemonSrc&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; } - - return false; } DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx __attribute__((unused)), QWidget* parent) : diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 406dab88e..5168c5ff7 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3215,6 +3215,12 @@ margin-bottom: 20px; "dataPort" : { "type" : "integer", "description" : "Remote USB data port" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" } }, "description" : "Data handling details for SDRDaemon" @@ -28688,7 +28694,7 @@ except ApiException as e:
    - Generated 2018-08-31T08:45:52.974+02:00 + Generated 2018-09-01T05:45:40.779+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml index bed33bf1a..686c577c9 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml @@ -7,6 +7,10 @@ SDRDaemonChannelSourceSettings: dataPort: description: "Remote USB data port" type: integer + rgbColor: + type: integer + title: + type: string SDRDaemonChannelSourceReport: description: "SDRDaemon channel source report" diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index d58fcd22d..493472907 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2146,14 +2146,14 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } - else if (*channelType == "SDRDaemonChannelSink") + else if (*channelType == "DaemonSrc") { - if (channelSettings.getTx() == 0) + if (channelSettings.getTx() != 0) { - QJsonObject sdrDaemonChannelSinkSettingsJsonObject = jsonObject["SDRDaemonChannelSinkSettings"].toObject(); - channelSettingsKeys = sdrDaemonChannelSinkSettingsJsonObject.keys(); - channelSettings.setSdrDaemonChannelSinkSettings(new SWGSDRangel::SWGSDRDaemonChannelSinkSettings()); - channelSettings.getSdrDaemonChannelSinkSettings()->fromJsonObject(sdrDaemonChannelSinkSettingsJsonObject); + QJsonObject daemonChannelSourceSettingsJsonObject = jsonObject["SDRDaemonChannelSourceSettings"].toObject(); + channelSettingsKeys = daemonChannelSourceSettingsJsonObject.keys(); + channelSettings.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); + channelSettings.getSdrDaemonChannelSourceSettings()->fromJsonObject(daemonChannelSourceSettingsJsonObject); return true; } else { diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml index bed33bf1a..686c577c9 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml @@ -7,6 +7,10 @@ SDRDaemonChannelSourceSettings: dataPort: description: "Remote USB data port" type: integer + rgbColor: + type: integer + title: + type: string SDRDaemonChannelSourceReport: description: "SDRDaemon channel source report" diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 406dab88e..5168c5ff7 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -3215,6 +3215,12 @@ margin-bottom: 20px; "dataPort" : { "type" : "integer", "description" : "Remote USB data port" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" } }, "description" : "Data handling details for SDRDaemon" @@ -28688,7 +28694,7 @@ except ApiException as e:
  • - Generated 2018-08-31T08:45:52.974+02:00 + Generated 2018-09-01T05:45:40.779+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp index 5a39bd498..a9da57775 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp @@ -32,6 +32,10 @@ SWGSDRDaemonChannelSourceSettings::SWGSDRDaemonChannelSourceSettings() { m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; } SWGSDRDaemonChannelSourceSettings::~SWGSDRDaemonChannelSourceSettings() { @@ -44,6 +48,10 @@ SWGSDRDaemonChannelSourceSettings::init() { m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; } void @@ -52,6 +60,10 @@ SWGSDRDaemonChannelSourceSettings::cleanup() { delete data_address; } + + if(title != nullptr) { + delete title; + } } SWGSDRDaemonChannelSourceSettings* @@ -69,6 +81,10 @@ SWGSDRDaemonChannelSourceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + } QString @@ -91,6 +107,12 @@ SWGSDRDaemonChannelSourceSettings::asJsonObject() { if(m_data_port_isSet){ obj->insert("dataPort", QJsonValue(data_port)); } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } return obj; } @@ -115,6 +137,26 @@ SWGSDRDaemonChannelSourceSettings::setDataPort(qint32 data_port) { this->m_data_port_isSet = true; } +qint32 +SWGSDRDaemonChannelSourceSettings::getRgbColor() { + return rgb_color; +} +void +SWGSDRDaemonChannelSourceSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGSDRDaemonChannelSourceSettings::getTitle() { + return title; +} +void +SWGSDRDaemonChannelSourceSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + bool SWGSDRDaemonChannelSourceSettings::isSet(){ @@ -122,6 +164,8 @@ SWGSDRDaemonChannelSourceSettings::isSet(){ do{ if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} if(m_data_port_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h index 123e1956e..fc881baea 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h @@ -48,6 +48,12 @@ public: qint32 getDataPort(); void setDataPort(qint32 data_port); + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + virtual bool isSet() override; @@ -58,6 +64,12 @@ private: qint32 data_port; bool m_data_port_isSet; + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + }; } From 96dfce0154f5b773cacb8edfb18b63c21bbd535c Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 1 Sep 2018 10:44:12 +0200 Subject: [PATCH 682/956] SDRdaemonSink: added compatibility with SDRangel and SDRdaemon server types --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 79 +++++++++++++--- .../sdrdaemonsink/sdrdaemonsinkgui.h | 3 + .../sdrdaemonsink/sdrdaemonsinkgui.ui | 90 ++++++++++++++++++- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 15 ++++ .../sdrdaemonsink/sdrdaemonsinksettings.cpp | 19 ++++ .../sdrdaemonsink/sdrdaemonsinksettings.h | 9 ++ sdrbase/resources/webapi/doc/html2/index.html | 11 ++- .../doc/swagger/include/SDRDaemonSink.yaml | 6 ++ .../api/swagger/include/SDRDaemonSink.yaml | 6 ++ swagger/sdrangel/code/html2/index.html | 11 ++- .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 63 +++++++++++++ .../qt5/client/SWGSDRdaemonSinkSettings.h | 18 ++++ 12 files changed, 312 insertions(+), 18 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index eada6d7f3..d877941dc 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -327,12 +327,52 @@ void SDRdaemonSinkGui::on_nbFECBlocks_valueChanged(int value) sendSettings(); } +void SDRdaemonSinkGui::on_serverType_currentIndexChanged(int index) +{ + m_settings.m_serverType = (SDRdaemonSinkSettings::ServerType) index; + sendSettings(); + + QString typeCode = m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel ? "sdrangel" : "sdrdaemon"; + QString infoURL = QString("http://%1:%2/%3").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort).arg(typeCode); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); +} + +void SDRdaemonSinkGui::on_deviceIndex_returnPressed() +{ + bool dataOk; + int deviceIndex = ui->deviceIndex->text().toInt(&dataOk); + + if ((!dataOk) || (deviceIndex < 0)) { + return; + } else { + m_settings.m_deviceIndex = deviceIndex; + } + + sendSettings(); +} + +void SDRdaemonSinkGui::on_channelIndex_returnPressed() +{ + bool dataOk; + int channelIndex = ui->channelIndex->text().toInt(&dataOk); + + if ((!dataOk) || (channelIndex < 0)) { + return; + } else { + m_settings.m_channelIndex = channelIndex; + } + + sendSettings(); +} + void SDRdaemonSinkGui::on_apiAddress_returnPressed() { m_settings.m_apiAddress = ui->apiAddress->text(); sendSettings(); - QString infoURL = QString("http://%1:%2/sdrdaemon").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + QString typeCode = m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel ? "sdrangel" : "sdrdaemon"; + QString infoURL = QString("http://%1:%2/%3").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort).arg(typeCode); m_networkRequest.setUrl(QUrl(infoURL)); m_networkManager->get(m_networkRequest); } @@ -342,18 +382,16 @@ void SDRdaemonSinkGui::on_apiPort_returnPressed() bool dataOk; int apiPort = ui->apiPort->text().toInt(&dataOk); - if((!dataOk) || (apiPort < 1024) || (apiPort > 65535)) - { + if((!dataOk) || (apiPort < 1024) || (apiPort > 65535)) { return; - } - else - { + } else { m_settings.m_apiPort = apiPort; } sendSettings(); - QString infoURL = QString("http://%1:%2/sdrdaemon").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + QString typeCode = m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel ? "sdrangel" : "sdrdaemon"; + QString infoURL = QString("http://%1:%2/%3").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort).arg(typeCode); m_networkRequest.setUrl(QUrl(infoURL)); m_networkManager->get(m_networkRequest); } @@ -369,12 +407,9 @@ void SDRdaemonSinkGui::on_dataPort_returnPressed() bool dataOk; int dataPort = ui->dataPort->text().toInt(&dataOk); - if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) - { + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) { return; - } - else - { + } else { m_settings.m_dataPort = dataPort; } @@ -395,7 +430,8 @@ void SDRdaemonSinkGui::on_apiApplyButton_clicked(bool checked __attribute__((unu sendSettings(); - QString infoURL = QString("http://%1:%2/sdrdaemon").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + QString typeCode = m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel ? "sdrangel" : "sdrdaemon"; + QString infoURL = QString("http://%1:%2/%3").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort).arg(typeCode); m_networkRequest.setUrl(QUrl(infoURL)); m_networkManager->get(m_networkRequest); } @@ -471,7 +507,22 @@ void SDRdaemonSinkGui::tick() { if (++m_tickCount == 20) // once per second { - QString reportURL = QString("http://%1:%2/sdrdaemon/channel/report").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + QString reportURL; + + if (m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel) { + reportURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/report") + .arg(m_settings.m_apiAddress) + .arg(m_settings.m_apiPort) + .arg(m_settings.m_deviceIndex) + .arg(m_settings.m_channelIndex); + } + else + { + reportURL = QString("http://%1:%2/sdrdaemon/channel/report") + .arg(m_settings.m_apiAddress) + .arg(m_settings.m_apiPort); + } + m_networkRequest.setUrl(QUrl(reportURL)); m_networkManager->get(m_networkRequest); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 48dcd901d..6c5453b08 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -116,6 +116,9 @@ private slots: void on_sampleRate_changed(quint64 value); void on_txDelay_valueChanged(int value); void on_nbFECBlocks_valueChanged(int value); + void on_serverType_currentIndexChanged(int index); + void on_deviceIndex_returnPressed(); + void on_channelIndex_returnPressed(); void on_apiAddress_returnPressed(); void on_apiPort_returnPressed(); void on_dataAddress_returnPressed(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 9cd957623..5a325b782 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -7,7 +7,7 @@ 0 0 360 - 270 + 300 @@ -19,7 +19,7 @@ 360 - 270 + 300 @@ -520,6 +520,92 @@ + + + + + + Remote server type + + + + Angel + + + + + Daemon + + + + + + + + Device + + + + + + + + 40 + 16777215 + + + + Device index (for SDRangel server) + + + 00 + + + 0 + + + + + + + Channel + + + + + + + + 40 + 16777215 + + + + Channel index (for SDRangel) + + + 00 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 01e6e0814..f83c9b7bd 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -376,6 +376,18 @@ int SDRdaemonSinkOutput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("dataPort")) { settings.m_dataPort = response.getSdrDaemonSinkSettings()->getDataPort(); } + if (deviceSettingsKeys.contains("serverType")) { + int serverType = response.getSdrDaemonSinkSettings()->getServerType(); + settings.m_serverType = serverType < 0 ? SDRdaemonSinkSettings::ServerAngel + : serverType > 1 ? SDRdaemonSinkSettings::ServerAngel + : (SDRdaemonSinkSettings::ServerType) serverType; + } + if (deviceSettingsKeys.contains("deviceIndex")) { + settings.m_deviceIndex = response.getSdrDaemonSinkSettings()->getDeviceIndex(); + } + if (deviceSettingsKeys.contains("channelIndex")) { + settings.m_channelIndex = response.getSdrDaemonSinkSettings()->getChannelIndex(); + } MsgConfigureSDRdaemonSink *msg = MsgConfigureSDRdaemonSink::create(settings, force); m_inputMessageQueue.push(msg); @@ -410,6 +422,9 @@ void SDRdaemonSinkOutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSetti response.getSdrDaemonSinkSettings()->setApiPort(settings.m_apiPort); response.getSdrDaemonSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); response.getSdrDaemonSinkSettings()->setDataPort(settings.m_dataPort); + response.getSdrDaemonSinkSettings()->setServerType((int) settings.m_serverType); + response.getSdrDaemonSinkSettings()->setDeviceIndex(settings.m_deviceIndex); + response.getSdrDaemonSinkSettings()->setChannelIndex(settings.m_channelIndex); } void SDRdaemonSinkOutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp index af41069cf..ad28bdfb0 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp @@ -32,6 +32,9 @@ void SDRdaemonSinkSettings::resetToDefaults() m_apiPort = 9091; m_dataAddress = "127.0.0.1"; m_dataPort = 9090; + m_serverType = ServerAngel; + m_deviceIndex = 0; + m_channelIndex = 0; } QByteArray SDRdaemonSinkSettings::serialize() const @@ -46,6 +49,9 @@ QByteArray SDRdaemonSinkSettings::serialize() const s.writeU32(6, m_apiPort); s.writeString(7, m_dataAddress); s.writeU32(8, m_dataPort); + s.writeS32(9, (int) m_serverType); + s.writeU32(10, m_deviceIndex); + s.writeU32(11, m_channelIndex); return s.final(); } @@ -63,6 +69,8 @@ bool SDRdaemonSinkSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { quint32 uintval; + int intval; + d.readU64(1, &m_centerFrequency, 435000*1000); d.readU32(2, &m_sampleRate, 48000); d.readFloat(3, &m_txDelay, 0.5); @@ -73,6 +81,17 @@ bool SDRdaemonSinkSettings::deserialize(const QByteArray& data) d.readString(7, &m_dataAddress, "127.0.0.1"); d.readU32(8, &uintval, 9090); m_dataPort = uintval % (1<<16); + d.readS32(9, &intval, 0); + + if ((intval < 0) || (intval > 1)) { + m_serverType = ServerAngel; + } else { + m_serverType = (ServerType) intval; + } + + d.readU32(10, &m_deviceIndex, 0); + d.readU32(11, &m_channelIndex, 0); + return true; } else diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h index 9865bfff3..4c9d4d9e7 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h @@ -20,6 +20,12 @@ #include struct SDRdaemonSinkSettings { + + typedef enum { + ServerAngel = 0, + ServerDaemon + } ServerType; + quint64 m_centerFrequency; quint32 m_sampleRate; float m_txDelay; @@ -28,6 +34,9 @@ struct SDRdaemonSinkSettings { quint16 m_apiPort; QString m_dataAddress; quint16 m_dataPort; + ServerType m_serverType; + quint32 m_deviceIndex; + quint32 m_channelIndex; SDRdaemonSinkSettings(); void resetToDefaults(); diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 5168c5ff7..ca83aacde 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3351,6 +3351,15 @@ margin-bottom: 20px; }, "dataPort" : { "type" : "integer" + }, + "serverType" : { + "type" : "integer" + }, + "deviceIndex" : { + "type" : "integer" + }, + "channelIndex" : { + "type" : "integer" } }, "description" : "SDRdaemonSink" @@ -28694,7 +28703,7 @@ except ApiException as e:
    - Generated 2018-09-01T05:45:40.779+02:00 + Generated 2018-09-01T10:19:35.190+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml index 17a22003f..a70b54e3f 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml @@ -20,6 +20,12 @@ SDRdaemonSinkSettings: type: string dataPort: type: integer + serverType: + type: integer + deviceIndex: + type: integer + channelIndex: + type: integer SDRdaemonSinkReport: description: SDRdaemonSource diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml index 17a22003f..a70b54e3f 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml @@ -20,6 +20,12 @@ SDRdaemonSinkSettings: type: string dataPort: type: integer + serverType: + type: integer + deviceIndex: + type: integer + channelIndex: + type: integer SDRdaemonSinkReport: description: SDRdaemonSource diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 5168c5ff7..ca83aacde 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -3351,6 +3351,15 @@ margin-bottom: 20px; }, "dataPort" : { "type" : "integer" + }, + "serverType" : { + "type" : "integer" + }, + "deviceIndex" : { + "type" : "integer" + }, + "channelIndex" : { + "type" : "integer" } }, "description" : "SDRdaemonSink" @@ -28694,7 +28703,7 @@ except ApiException as e:
    - Generated 2018-09-01T05:45:40.779+02:00 + Generated 2018-09-01T10:19:35.190+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp index 1c9997f76..f818aab62 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp @@ -44,6 +44,12 @@ SWGSDRdaemonSinkSettings::SWGSDRdaemonSinkSettings() { m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; + server_type = 0; + m_server_type_isSet = false; + device_index = 0; + m_device_index_isSet = false; + channel_index = 0; + m_channel_index_isSet = false; } SWGSDRdaemonSinkSettings::~SWGSDRdaemonSinkSettings() { @@ -68,6 +74,12 @@ SWGSDRdaemonSinkSettings::init() { m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; + server_type = 0; + m_server_type_isSet = false; + device_index = 0; + m_device_index_isSet = false; + channel_index = 0; + m_channel_index_isSet = false; } void @@ -84,6 +96,9 @@ SWGSDRdaemonSinkSettings::cleanup() { delete data_address; } + + + } SWGSDRdaemonSinkSettings* @@ -113,6 +128,12 @@ SWGSDRdaemonSinkSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); + ::SWGSDRangel::setValue(&server_type, pJson["serverType"], "qint32", ""); + + ::SWGSDRangel::setValue(&device_index, pJson["deviceIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_index, pJson["channelIndex"], "qint32", ""); + } QString @@ -153,6 +174,15 @@ SWGSDRdaemonSinkSettings::asJsonObject() { if(m_data_port_isSet){ obj->insert("dataPort", QJsonValue(data_port)); } + if(m_server_type_isSet){ + obj->insert("serverType", QJsonValue(server_type)); + } + if(m_device_index_isSet){ + obj->insert("deviceIndex", QJsonValue(device_index)); + } + if(m_channel_index_isSet){ + obj->insert("channelIndex", QJsonValue(channel_index)); + } return obj; } @@ -237,6 +267,36 @@ SWGSDRdaemonSinkSettings::setDataPort(qint32 data_port) { this->m_data_port_isSet = true; } +qint32 +SWGSDRdaemonSinkSettings::getServerType() { + return server_type; +} +void +SWGSDRdaemonSinkSettings::setServerType(qint32 server_type) { + this->server_type = server_type; + this->m_server_type_isSet = true; +} + +qint32 +SWGSDRdaemonSinkSettings::getDeviceIndex() { + return device_index; +} +void +SWGSDRdaemonSinkSettings::setDeviceIndex(qint32 device_index) { + this->device_index = device_index; + this->m_device_index_isSet = true; +} + +qint32 +SWGSDRdaemonSinkSettings::getChannelIndex() { + return channel_index; +} +void +SWGSDRdaemonSinkSettings::setChannelIndex(qint32 channel_index) { + this->channel_index = channel_index; + this->m_channel_index_isSet = true; +} + bool SWGSDRdaemonSinkSettings::isSet(){ @@ -250,6 +310,9 @@ SWGSDRdaemonSinkSettings::isSet(){ if(m_api_port_isSet){ isObjectUpdated = true; break;} if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} if(m_data_port_isSet){ isObjectUpdated = true; break;} + if(m_server_type_isSet){ isObjectUpdated = true; break;} + if(m_device_index_isSet){ isObjectUpdated = true; break;} + if(m_channel_index_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h index d87d3763b..20f3f8a88 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h @@ -66,6 +66,15 @@ public: qint32 getDataPort(); void setDataPort(qint32 data_port); + qint32 getServerType(); + void setServerType(qint32 server_type); + + qint32 getDeviceIndex(); + void setDeviceIndex(qint32 device_index); + + qint32 getChannelIndex(); + void setChannelIndex(qint32 channel_index); + virtual bool isSet() override; @@ -94,6 +103,15 @@ private: qint32 data_port; bool m_data_port_isSet; + qint32 server_type; + bool m_server_type_isSet; + + qint32 device_index; + bool m_device_index_isSet; + + qint32 channel_index; + bool m_channel_index_isSet; + }; } From 0709cf02f48dd8e4b4abf7dc1938b370a1aa6bbd Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 1 Sep 2018 22:24:21 +0200 Subject: [PATCH 683/956] DaemonSrc: added header protection --- plugins/channeltx/daemonsrc/daemonsrc.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.h b/plugins/channeltx/daemonsrc/daemonsrc.h index 8d0dd9a1e..7d5bd643f 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.h +++ b/plugins/channeltx/daemonsrc/daemonsrc.h @@ -14,6 +14,9 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#ifndef PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ + #include "cm256.h" #include "dsp/basebandsamplesource.h" @@ -145,3 +148,5 @@ private: private slots: void handleData(); }; + +#endif // PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ From 1075ef6c513b3f82222d3679a62f4131ed57daa5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 1 Sep 2018 22:41:02 +0200 Subject: [PATCH 684/956] DaemonSrc: added server plugin --- pluginssrv/channeltx/CMakeLists.txt | 12 +++-- pluginssrv/channeltx/daemonsrc/CMakeLists.txt | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 pluginssrv/channeltx/daemonsrc/CMakeLists.txt diff --git a/pluginssrv/channeltx/CMakeLists.txt b/pluginssrv/channeltx/CMakeLists.txt index 30118e09e..512390995 100644 --- a/pluginssrv/channeltx/CMakeLists.txt +++ b/pluginssrv/channeltx/CMakeLists.txt @@ -1,13 +1,17 @@ project(mod) -find_package(OpenCV) - add_subdirectory(modam) add_subdirectory(modnfm) add_subdirectory(modssb) add_subdirectory(modwfm) add_subdirectory(udpsink) -if (OpenCV_FOUND) -add_subdirectory(modatv) +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(daemonsrc) +endif() + +find_package(OpenCV) +if (OpenCV_FOUND) + add_subdirectory(modatv) endif() diff --git a/pluginssrv/channeltx/daemonsrc/CMakeLists.txt b/pluginssrv/channeltx/daemonsrc/CMakeLists.txt new file mode 100644 index 000000000..ba4062d3c --- /dev/null +++ b/pluginssrv/channeltx/daemonsrc/CMakeLists.txt @@ -0,0 +1,48 @@ +project(daemonsrc) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/daemonsrc") + +set(daemonsrc_SOURCES + ${PLUGIN_PREFIX}/daemonsrc.cpp + ${PLUGIN_PREFIX}/daemonsrcthread.cpp + ${PLUGIN_PREFIX}/daemonsrcplugin.cpp + ${PLUGIN_PREFIX}/daemonsrcsettings.cpp +) + +set(daemonsrc_HEADERS + ${PLUGIN_PREFIX}/daemonsrc.h + ${PLUGIN_PREFIX}/daemonsrcthread.h + ${PLUGIN_PREFIX}/daemonsrcplugin.h + ${PLUGIN_PREFIX}/daemonsrcsettings.h +) + +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) + +add_library(daemonsrcsrv SHARED + ${daemonsrc_SOURCES} + ${daemonsrc_HEADERS_MOC} +) + +target_link_libraries(daemonsrcsrv + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrdaemon + swagger +) + +target_link_libraries(daemonsrcsrv Qt5::Core Qt5::Network) + +install(TARGETS daemonsrcsrv DESTINATION lib/pluginssrv/channeltx) From 472a9b2532845b73efd1e9e556135d06e4094198 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 2 Sep 2018 02:00:01 +0200 Subject: [PATCH 685/956] DaemonSrc: added missing include fixing mess in MOC generation at least in Qt 5.11.1 --- plugins/channeltx/daemonsrc/daemonsrc.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.h b/plugins/channeltx/daemonsrc/daemonsrc.h index 7d5bd643f..807fe414b 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.h +++ b/plugins/channeltx/daemonsrc/daemonsrc.h @@ -17,6 +17,8 @@ #ifndef PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ #define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ +#include + #include "cm256.h" #include "dsp/basebandsamplesource.h" From b306aa8aa77825f2a2ad1d60a1ea4830f413ed8f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 2 Sep 2018 19:12:03 +0200 Subject: [PATCH 686/956] DaemonSrc: implemented status report to the GUI --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 27 ++ plugins/channeltx/daemonsrc/daemonsrc.h | 93 ++++++ plugins/channeltx/daemonsrc/daemonsrcgui.cpp | 123 +++++++- plugins/channeltx/daemonsrc/daemonsrcgui.h | 20 +- plugins/channeltx/daemonsrc/daemonsrcgui.ui | 282 +++++++++++++++++- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 53 ++-- .../sdrdaemonsink/sdrdaemonsinkgui.h | 37 ++- .../sdrdaemonsink/sdrdaemonsinkthread.h | 2 +- 8 files changed, 607 insertions(+), 30 deletions(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index e5e949307..95eda0614 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -35,6 +35,8 @@ MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgSampleRateNotification, Message) MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgConfigureDaemonSrc, Message) +MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgQueryStreamData, Message) +MESSAGE_CLASS_DEFINITION(DaemonSrc::MsgReportStreamData, Message) const QString DaemonSrc::m_channelIdURI = "sdrangel.channeltx.daemonsrc"; const QString DaemonSrc::m_channelId ="DaemonSrc"; @@ -140,6 +142,30 @@ bool DaemonSrc::handleMessage(const Message& cmd) return true; } + else if (MsgQueryStreamData::match(cmd)) + { + if (m_guiMessageQueue) + { + struct timeval tv; + gettimeofday(&tv, 0); + + MsgReportStreamData *msg = MsgReportStreamData::create( + tv.tv_sec, + tv.tv_usec, + m_dataReadQueue.size(), + m_dataReadQueue.length(), + m_dataReadQueue.readSampleCount(), + m_nbCorrectableErrors, + m_nbUncorrectableErrors, + m_currentMeta.m_nbOriginalBlocks, + m_currentMeta.m_nbFECBlocks, + m_currentMeta.m_centerFrequency, + m_currentMeta.m_sampleRate); + m_guiMessageQueue->push(msg); + } + + return true; + } return false; } @@ -422,3 +448,4 @@ void DaemonSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respons response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); } + diff --git a/plugins/channeltx/daemonsrc/daemonsrc.h b/plugins/channeltx/daemonsrc/daemonsrc.h index 807fe414b..efbd2bbed 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.h +++ b/plugins/channeltx/daemonsrc/daemonsrc.h @@ -83,6 +83,99 @@ public: int m_sampleRate; }; + class MsgQueryStreamData : public Message { + MESSAGE_CLASS_DECLARATION + public: + static MsgQueryStreamData* create() { + return new MsgQueryStreamData(); + } + private: + MsgQueryStreamData() : Message() {} + }; + + class MsgReportStreamData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + uint32_t get_tv_sec() const { return m_tv_sec; } + uint32_t get_tv_usec() const { return m_tv_usec; } + uint32_t get_queueSize() const { return m_queueSize; } + uint32_t get_queueLength() const { return m_queueLength; } + uint32_t get_readSamplesCount() const { return m_readSamplesCount; } + uint32_t get_nbCorrectableErrors() const { return m_nbCorrectableErrors; } + uint32_t get_nbUncorrectableErrors() const { return m_nbUncorrectableErrors; } + uint32_t get_nbOriginalBlocks() const { return m_nbOriginalBlocks; } + uint32_t get_nbFECBlocks() const { return m_nbFECBlocks; } + uint32_t get_centerFreq() const { return m_centerFreq; } + uint32_t get_sampleRate() const { return m_sampleRate; } + + static MsgReportStreamData* create( + uint32_t tv_sec, + uint32_t tv_usec, + uint32_t queueSize, + uint32_t queueLength, + uint32_t readSamplesCount, + uint32_t nbCorrectableErrors, + uint32_t nbUncorrectableErrors, + uint32_t nbOriginalBlocks, + uint32_t nbFECBlocks, + uint32_t centerFreq, + uint32_t sampleRate) + { + return new MsgReportStreamData( + tv_sec, + tv_usec, + queueSize, + queueLength, + readSamplesCount, + nbCorrectableErrors, + nbUncorrectableErrors, + nbOriginalBlocks, + nbFECBlocks, + centerFreq, + sampleRate); + } + + protected: + uint32_t m_tv_sec; + uint32_t m_tv_usec; + uint32_t m_queueSize; + uint32_t m_queueLength; + uint32_t m_readSamplesCount; + uint32_t m_nbCorrectableErrors; + uint32_t m_nbUncorrectableErrors; + uint32_t m_nbOriginalBlocks; + uint32_t m_nbFECBlocks; + uint32_t m_centerFreq; + uint32_t m_sampleRate; + + MsgReportStreamData( + uint32_t tv_sec, + uint32_t tv_usec, + uint32_t queueSize, + uint32_t queueLength, + uint32_t readSamplesCount, + uint32_t nbCorrectableErrors, + uint32_t nbUncorrectableErrors, + uint32_t nbOriginalBlocks, + uint32_t nbFECBlocks, + uint32_t centerFreq, + uint32_t sampleRate) : + Message(), + m_tv_sec(tv_sec), + m_tv_usec(tv_usec), + m_queueSize(queueSize), + m_queueLength(queueLength), + m_readSamplesCount(readSamplesCount), + m_nbCorrectableErrors(nbCorrectableErrors), + m_nbUncorrectableErrors(nbUncorrectableErrors), + m_nbOriginalBlocks(nbOriginalBlocks), + m_nbFECBlocks(nbFECBlocks), + m_centerFreq(centerFreq), + m_sampleRate(sampleRate) + { } + }; + DaemonSrc(DeviceSinkAPI *deviceAPI); ~DaemonSrc(); diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp index a351b763e..2b95ada47 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp @@ -93,6 +93,60 @@ bool DaemonSrcGUI::handleMessage(const Message& message) blockApplySettings(false); return true; } + else if (DaemonSrc::MsgReportStreamData::match(message)) + { + const DaemonSrc::MsgReportStreamData& report = (DaemonSrc::MsgReportStreamData&) message; + ui->centerFrequency->setText(QString("%1").arg(report.get_centerFreq())); + ui->sampleRate->setText(QString("%1").arg(report.get_sampleRate())); + QString nominalNbBlocksText = QString("%1/%2") + .arg(report.get_nbOriginalBlocks() + report.get_nbFECBlocks()) + .arg(report.get_nbFECBlocks()); + ui->nominalNbBlocksText->setText(nominalNbBlocksText); + QString queueLengthText = QString("%1/%2").arg(report.get_queueLength()).arg(report.get_queueSize()); + ui->queueLengthText->setText(queueLengthText); + int queueLengthPercent = (report.get_queueLength()*100)/report.get_queueSize(); + ui->queueLengthGauge->setValue(queueLengthPercent); + int unrecoverableCount = report.get_nbUncorrectableErrors(); + int recoverableCount = report.get_nbCorrectableErrors(); + uint64_t timestampUs = report.get_tv_sec()*1000000ULL + report.get_tv_usec(); + + if (!m_resetCounts) + { + int recoverableCountDelta = recoverableCount - m_lastCountRecovered; + int unrecoverableCountDelta = unrecoverableCount - m_lastCountUnrecoverable; + displayEventStatus(recoverableCountDelta, unrecoverableCountDelta); + m_countRecovered += recoverableCountDelta; + m_countUnrecoverable += unrecoverableCountDelta; + displayEventCounts(); + } + + uint32_t sampleCountDelta, sampleCount; + sampleCount = report.get_readSamplesCount(); + + if (sampleCount < m_lastSampleCount) { + sampleCountDelta = (0xFFFFFFFFU - sampleCount) + m_lastSampleCount + 1; + } else { + sampleCountDelta = sampleCount - m_lastSampleCount; + } + + if (sampleCountDelta == 0) { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }"); + } + + double remoteStreamRate = sampleCountDelta*1e6 / (double) (timestampUs - m_lastTimestampUs); + + if (remoteStreamRate != 0) { + ui->streamRateText->setText(QString("%1").arg(remoteStreamRate, 0, 'f', 0)); + } + + m_resetCounts = false; + m_lastCountRecovered = recoverableCount; + m_lastCountUnrecoverable = unrecoverableCount; + m_lastSampleCount = sampleCount; + m_lastTimestampUs = timestampUs; + + return true; + } else { return false; @@ -103,7 +157,15 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb RollupWidget(parent), ui(new Ui::DaemonSrcGUI), m_pluginAPI(pluginAPI), - m_deviceUISet(deviceUISet) + m_deviceUISet(deviceUISet), + m_countUnrecoverable(0), + m_countRecovered(0), + m_lastCountUnrecoverable(0), + m_lastCountRecovered(0), + m_lastSampleCount(0), + m_lastTimestampUs(0), + m_resetCounts(true), + m_tickCount(0) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); @@ -126,6 +188,9 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb m_deviceUISet->addRollupWidget(this); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + + m_time.start(); displaySettings(); applySettings(true); @@ -250,3 +315,59 @@ void DaemonSrcGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused applySettings(); } + +void DaemonSrcGUI::on_eventCountsReset_clicked(bool checked __attribute__((unused))) +{ + m_countUnrecoverable = 0; + m_countRecovered = 0; + m_time.start(); + displayEventCounts(); + displayEventTimer(); +} + +void DaemonSrcGUI::displayEventCounts() +{ + QString nstr = QString("%1").arg(m_countUnrecoverable, 3, 10, QChar('0')); + ui->eventUnrecText->setText(nstr); + nstr = QString("%1").arg(m_countRecovered, 3, 10, QChar('0')); + ui->eventRecText->setText(nstr); +} + +void DaemonSrcGUI::displayEventStatus(int recoverableCount, int unrecoverableCount) +{ + + if (unrecoverableCount == 0) + { + if (recoverableCount == 0) { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + } + else + { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }"); + } +} + +void DaemonSrcGUI::displayEventTimer() +{ + int elapsedTimeMillis = m_time.elapsed(); + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addSecs(elapsedTimeMillis/1000); + QString s_time = recordLength.toString("HH:mm:ss"); + ui->eventCountsTimeText->setText(s_time); +} + +void DaemonSrcGUI::tick() +{ + if (++m_tickCount == 20) // once per second + { + DaemonSrc::MsgQueryStreamData *msg = DaemonSrc::MsgQueryStreamData::create(); + m_daemonSrc->getInputMessageQueue()->push(msg); + + displayEventTimer(); + + m_tickCount = 0; + } +} diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.h b/plugins/channeltx/daemonsrc/daemonsrcgui.h index 1c55506bb..7b47c9b99 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.h +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.h @@ -17,10 +17,12 @@ #ifndef PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCGUI_H_ #define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCGUI_H_ +#include + #include "plugin/plugininstancegui.h" +#include "dsp/channelmarker.h" #include "gui/rollupwidget.h" #include "util/messagequeue.h" -#include "dsp/channelmarker.h" #include "daemonsrcsettings.h" @@ -62,6 +64,16 @@ private: DaemonSrc* m_daemonSrc; MessageQueue m_inputMessageQueue; + uint32_t m_countUnrecoverable; + uint32_t m_countRecovered; + uint32_t m_lastCountUnrecoverable; + uint32_t m_lastCountRecovered; + uint32_t m_lastSampleCount; + uint64_t m_lastTimestampUs; + bool m_resetCounts; + QTime m_time; + uint32_t m_tickCount; + explicit DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); virtual ~DaemonSrcGUI(); @@ -72,6 +84,10 @@ private: void leaveEvent(QEvent*); void enterEvent(QEvent*); + void displayEventCounts(); + void displayEventStatus(int recoverableCount, int unrecoverableCount); + void displayEventTimer(); + private slots: void handleSourceMessages(); void on_dataAddress_returnPressed(); @@ -79,6 +95,8 @@ private slots: void on_dataApplyButton_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); + void on_eventCountsReset_clicked(bool checked); + void tick(); }; diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.ui b/plugins/channeltx/daemonsrc/daemonsrcgui.ui index b4c03dffa..611064c00 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.ui +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.ui @@ -7,7 +7,7 @@ 0 0 320 - 90 + 140 @@ -19,7 +19,13 @@ 320 - 90 + 140 + + + + + 320 + 16777215 @@ -37,7 +43,7 @@ 10 10 301 - 61 + 121 @@ -150,6 +156,272 @@ + + + + + + Freq + + + + + + + + 60 + 0 + + + + Stream center frequency setting + + + 00000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + SR + + + + + + + + 60 + 0 + + + + Stream nominal sample rate + + + 0000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + S/s + + + + + + + + + + + + 50 + 0 + + + + Nb total blocks / Nb FEC blocks + + + 000/00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Frames status: green = all original received, none = some recovered by FEC, red = some lost, blue = remote not streaming + + + + + + + :/locked.png:/locked.png + + + + + + + + 50 + 0 + + + + Stream actual sample rate + + + 0000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 20 + 0 + + + + S/s + + + + + + + + 22 + 16777215 + + + + 0 + + + + + + + + 25 + 0 + + + + Number of unrecoverable errors since event counts reset + + + 000 + + + + + + + + 25 + 0 + + + + Number of correctable errors since event counts reset + + + 000 + + + + + + + Time since last event counts reset + + + 00:00:00 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + QL + + + + + + + + 16777215 + 14 + + + + Queue length gauge + + + 24 + + + + + + + + 50 + 0 + + + + Queued data blocks / Queue size in data blocks + + + 000/000 + + + + + @@ -174,6 +446,8 @@ 1 - + + + diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index d877941dc..cb54090f7 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -59,6 +59,8 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_lastCountRecovered = 0; m_lastSampleCount = 0; m_lastTimestampRateCorrection = 0; + m_nbSamplesSinceRateCorrection = 0; + m_chunkSizeCorrection = 0; m_resetCounts = true; m_paletteGreenText.setColor(QPalette::WindowText, Qt::green); @@ -242,6 +244,9 @@ void SDRdaemonSinkGui::displaySettings() QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s0).arg(s1)); + ui->serverType->setCurrentIndex((int) m_settings.m_serverType); + ui->deviceIndex->setText(tr("%1").arg(m_settings.m_deviceIndex)); + ui->channelIndex->setText(tr("%1").arg(m_settings.m_channelIndex)); ui->apiAddress->setText(m_settings.m_apiAddress); ui->apiPort->setText(tr("%1").arg(m_settings.m_apiPort)); ui->dataAddress->setText(m_settings.m_dataAddress); @@ -455,6 +460,9 @@ void SDRdaemonSinkGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) { + m_nbSamplesSinceRateCorrection = 0; + m_lastTimestampRateCorrection = 0; + SDRdaemonSinkOutput::MsgStartStop *message = SDRdaemonSinkOutput::MsgStartStop::create(checked); m_deviceSampleSink->getInputMessageQueue()->push(message); } @@ -590,15 +598,6 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) int recoverableCount = report["correctableErrorsCount"].toInt(); uint64_t timestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); - if (m_lastTimestampRateCorrection == 0) { - m_lastTimestampRateCorrection = timestampUs; - } - - if ((timestampUs - m_lastTimestampRateCorrection > 600e6) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) - { - m_lastTimestampRateCorrection = timestampUs; - } - if (!m_resetCounts) { int recoverableCountDelta = recoverableCount - m_lastCountRecovered; @@ -618,16 +617,31 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) sampleCountDelta = sampleCount - m_lastSampleCount; } - if (sampleCountDelta == 0) { + if (sampleCountDelta == 0) + { ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }"); } + else + { + if (m_lastTimestampRateCorrection == 0) { + m_lastTimestampRateCorrection = timestampUs; + } + + //if ((timestampUs - m_lastTimestampRateCorrection > 300e6) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) + if ((m_nbSamplesSinceRateCorrection > 20000000) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) + { + sampleRateCorrection(queueLength, queueSize, timestampUs - m_lastTimestampRateCorrection); + m_lastTimestampRateCorrection = timestampUs; + m_nbSamplesSinceRateCorrection = 0; + } + + m_nbSamplesSinceRateCorrection += sampleCountDelta; + } double remoteStreamRate = sampleCountDelta*1e6 / (double) (timestampUs - m_lastTimestampUs); - if (remoteStreamRate != 0) - { - m_rateMovingAverage(remoteStreamRate); - ui->remoteStreamRateText->setText(QString("%1").arg(m_rateMovingAverage.instantAverage(), 0, 'f', 0)); + if (remoteStreamRate != 0) { + ui->remoteStreamRateText->setText(QString("%1").arg(remoteStreamRate, 0, 'f', 0)); } m_resetCounts = false; @@ -662,15 +676,16 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) } } -void SDRdaemonSinkGui::sampleRateCorrection(int queueLength, int queueSize, int64_t timeDelta) +void SDRdaemonSinkGui::sampleRateCorrection(int queueLength, int queueSize, int64_t timeDeltaUs) { int nbBlocksDiff = queueLength - (queueSize/2); int nbSamplesDiff = nbBlocksDiff * 127 * 127; - float sampleCorr = (nbSamplesDiff * 50000.0) / timeDelta; - int chunkCorr = roundf(sampleCorr); + float sampleCorr = (nbSamplesDiff * 50000.0) / timeDeltaUs; // correction for ~50ms chunks (50000 us) + int chunkCorr = -roundf(sampleCorr); + m_chunkSizeCorrection += chunkCorr; - qDebug("SDRdaemonSinkGui::sampleRateCorrection: %d samples", -chunkCorr); + qDebug("SDRdaemonSinkGui::sampleRateCorrection: %d (%d) samples", m_chunkSizeCorrection, chunkCorr); - SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection::create(-chunkCorr); + SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection::create(m_chunkSizeCorrection); m_deviceSampleSink->getInputMessageQueue()->push(message); } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 6c5453b08..a0d43facc 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -17,15 +17,16 @@ #ifndef INCLUDE_SDRDAEMONSINKGUI_H #define INCLUDE_SDRDAEMONSINKGUI_H -#include +#include + #include #include #include #include +#include "plugin/plugininstancegui.h" #include "util/messagequeue.h" #include "util/limitedcounter.h" -#include "util/movingaverage.h" #include "sdrdaemonsinksettings.h" #include "sdrdaemonsinkoutput.h" @@ -40,6 +41,33 @@ namespace Ui { class SDRdaemonSinkGui; } +class SDRdaemonSinkExpAvg { +public: + SDRdaemonSinkExpAvg(float alpha) : + m_alpha(alpha), + m_start(true), + m_s(0) + {} + int put(int y) + { + if (m_start) { + m_start = false; + m_s = y; + } else { + m_s = m_alpha*y + (1.0-m_alpha)*m_s; + } + return roundf(m_s); + } + void reset() { + m_start = true; + } + +private: + float m_alpha; + bool m_start; + float m_s; +}; + class SDRdaemonSinkGui : public QWidget, public PluginInstanceGUI { Q_OBJECT @@ -71,7 +99,6 @@ private: int m_sampleRate; quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_samplesCount; - MovingAverageUtil m_rateMovingAverage; // ~30s average uint32_t m_tickCount; std::size_t m_nbSinceLastFlowCheck; int m_lastEngineState; @@ -85,6 +112,8 @@ private: uint32_t m_lastSampleCount; uint64_t m_lastTimestampUs; uint64_t m_lastTimestampRateCorrection; + uint32_t m_nbSamplesSinceRateCorrection; + int m_chunkSizeCorrection; bool m_resetCounts; QTime m_time; @@ -108,7 +137,7 @@ private: void displayEventStatus(int recoverableCount, int unrecoverableCount); void displayEventTimer(); void analyzeApiReply(const QJsonObject& jsonObject); - void sampleRateCorrection(int queueLength, int queueSize, int64_t timeDelta); + void sampleRateCorrection(int queueLength, int queueSize, int64_t timeDeltaUs); private slots: void handleInputMessages(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h index c4ce84150..349615fd1 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h @@ -54,7 +54,7 @@ public: bool isRunning() const { return m_running; } - std::size_t getSamplesCount() const { return m_samplesCount; } + uint32_t getSamplesCount() const { return m_samplesCount; } void setSamplesCount(int samplesCount) { m_samplesCount = samplesCount; } void setChunkCorrection(int chunkCorrection) { m_chunkCorrection = chunkCorrection; } From d5f5328ab27c8c83376b8ecb4f30c193d78b34de Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 2 Sep 2018 19:17:14 +0200 Subject: [PATCH 687/956] Removed old SDRDaemonChannelSource --- .../sdrdaemonchannelsource/CMakeLists.txt | 57 --- .../sdrdaemonchannelsource.cpp | 413 ------------------ .../sdrdaemonchannelsource.h | 135 ------ .../sdrdaemonchannelsourcegui.cpp | 275 ------------ .../sdrdaemonchannelsourcegui.h | 92 ---- .../sdrdaemonchannelsourcegui.ui | 179 -------- .../sdrdaemonchannelsourceplugin.cpp | 77 ---- .../sdrdaemonchannelsourceplugin.h | 47 -- .../sdrdaemonchannelsourcesettings.cpp | 95 ---- .../sdrdaemonchannelsourcesettings.h | 48 -- .../sdrdaemonchannelsourcethread.cpp | 193 -------- .../sdrdaemonchannelsourcethread.h | 115 ----- 12 files changed, 1726 deletions(-) delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp delete mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h diff --git a/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt b/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt deleted file mode 100644 index dc9629b31..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt +++ /dev/null @@ -1,57 +0,0 @@ -project(sdrdaemonchannelsource) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - -set(sdrdaemonchannelsource_SOURCES - sdrdaemonchannelsource.cpp - sdrdaemonchannelsourcethread.cpp - sdrdaemonchannelsourcegui.cpp - sdrdaemonchannelsourceplugin.cpp - sdrdaemonchannelsourcesettings.cpp -) - -set(sdrdaemonchannelsource_HEADERS - sdrdaemonchannelsource.h - sdrdaemonchannelsourcethread.h - sdrdaemonchannelsourcegui.h - sdrdaemonchannelsourceplugin.h - sdrdaemonchannelsourcesettings.h -) - -set(sdrdaemonchannelsource_FORMS - sdrdaemonchannelsourcegui.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(sdrdaemonchannelsource_FORMS_HEADERS ${sdrdaemonchannelsource_FORMS}) - -add_library(sdrdaemonchannelsource SHARED - ${sdrdaemonchannelsource_SOURCES} - ${sdrdaemonchannelsource_HEADERS_MOC} - ${sdrdaemonchannelsource_FORMS_HEADERS} -) - -target_link_libraries(sdrdaemonchannelsource - ${QT_LIBRARIES} - ${CM256CC_LIBRARIES} - sdrbase - sdrdaemon - sdrgui - swagger -) - -target_link_libraries(sdrdaemonchannelsource Qt5::Core Qt5::Widgets Qt5::Network) - -install(TARGETS sdrdaemonchannelsource DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp deleted file mode 100644 index 580d2ecba..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp +++ /dev/null @@ -1,413 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) // -// // -// 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 - -#include "SWGChannelSettings.h" -#include "SWGChannelReport.h" -#include "SWGSDRDaemonChannelSourceReport.h" - -#include "util/simpleserializer.h" -#include "dsp/threadedbasebandsamplesource.h" -#include "dsp/upchannelizer.h" -#include "dsp/devicesamplesink.h" -#include "device/devicesinkapi.h" -#include "sdrdaemonchannelsource.h" -#include "channel/sdrdaemonchannelsourcethread.h" -#include "channel/sdrdaemondatablock.h" - -MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource, Message) - -const QString SDRDaemonChannelSource::m_channelIdURI = "sdrangel.channel.sdrdaemonsource"; -const QString SDRDaemonChannelSource::m_channelId = "SDRDaemonChannelSource"; - -SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : - ChannelSourceAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_sourceThread(0), - m_running(false), - m_nbCorrectableErrors(0), - m_nbUncorrectableErrors(0) -{ - setObjectName(m_channelId); - - m_channelizer = new UpChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); - m_deviceAPI->addThreadedSource(m_threadedChannelizer); - m_deviceAPI->addChannelAPI(this); - - connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); - m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; - m_currentMeta.init(); -} - -SDRDaemonChannelSource::~SDRDaemonChannelSource() -{ - m_deviceAPI->removeChannelAPI(this); - m_deviceAPI->removeThreadedSource(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; -} - -void SDRDaemonChannelSource::pull(Sample& sample) -{ - m_dataReadQueue.readSample(sample); -} - -void SDRDaemonChannelSource::start() -{ - qDebug("SDRDaemonChannelSink::start"); - - if (m_running) { - stop(); - } - - m_sourceThread = new SDRDaemonChannelSourceThread(&m_dataQueue); - m_sourceThread->startStop(true); - m_sourceThread->dataBind(m_settings.m_dataAddress, m_settings.m_dataPort); - m_running = true; -} - -void SDRDaemonChannelSource::stop() -{ - qDebug("SDRDaemonChannelSink::stop"); - - if (m_sourceThread != 0) - { - m_sourceThread->startStop(false); - m_sourceThread->deleteLater(); - m_sourceThread = 0; - } - - m_running = false; -} - -void SDRDaemonChannelSource::setDataLink(const QString& dataAddress, uint16_t dataPort) -{ - SDRDaemonChannelSourceSettings settings = m_settings; - settings.m_dataAddress = dataAddress; - settings.m_dataPort = dataPort; - - MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, false); - m_inputMessageQueue.push(msg); -} - -bool SDRDaemonChannelSource::handleMessage(const Message& cmd __attribute__((unused))) -{ - if (UpChannelizer::MsgChannelizerNotification::match(cmd)) - { - UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "SDRDaemonChannelSource::handleMessage: UpChannelizer::MsgChannelizerNotification:" - << " basebandSampleRate: " << notif.getBasebandSampleRate() - << " outputSampleRate: " << notif.getSampleRate() - << " inputFrequencyOffset: " << notif.getFrequencyOffset(); - - //applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset()); - - return true; - } - else if (MsgConfigureSDRDaemonChannelSource::match(cmd)) - { - MsgConfigureSDRDaemonChannelSource& cfg = (MsgConfigureSDRDaemonChannelSource&) cmd; - qDebug() << "SDRDaemonChannelSource::handleMessage: MsgConfigureSDRDaemonChannelSource"; - applySettings(cfg.getSettings(), cfg.getForce()); - - return true; - } - else - { - return false; - } -} - -QByteArray SDRDaemonChannelSource::serialize() const -{ - return m_settings.serialize(); -} - -bool SDRDaemonChannelSource::deserialize(const QByteArray& data __attribute__((unused))) -{ - if (m_settings.deserialize(data)) - { - MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); - m_inputMessageQueue.push(msg); - return true; - } - else - { - m_settings.resetToDefaults(); - MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); - m_inputMessageQueue.push(msg); - return false; - } -} - -void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& settings, bool force) -{ - qDebug() << "SDRDaemonChannelSource::applySettings:" - << " m_dataAddress: " << settings.m_dataAddress - << " m_dataPort: " << settings.m_dataPort - << " force: " << force; - - bool change = false; - - if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { - change = true; - } - - if ((m_settings.m_dataPort != settings.m_dataPort) || force) { - change = true; - } - - if (change && m_sourceThread) { - m_sourceThread->dataBind(settings.m_dataAddress, settings.m_dataPort); - } - - m_settings = settings; -} - -void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) -{ - if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) - { - qWarning("SDRDaemonChannelSource::handleDataBlock: incomplete data block: not processing"); - } - else - { - int blockCount = 0; - - for (int blockIndex = 0; blockIndex < 256; blockIndex++) - { - if ((blockIndex == 0) && (dataBlock->m_rxControlBlock.m_metaRetrieved)) - { - m_cm256DescriptorBlocks[blockCount].Index = 0; - m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[0].m_protectedBlock); - blockCount++; - } - else if (dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex != 0) - { - m_cm256DescriptorBlocks[blockCount].Index = dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex; - m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock); - blockCount++; - } - } - - //qDebug("SDRDaemonChannelSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); - - // Need to use the CM256 recovery - if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) - { - qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); - CM256::cm256_encoder_params paramsCM256; - paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes - paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes - - if (m_currentMeta.m_tv_sec == 0) { - paramsCM256.RecoveryCount = dataBlock->m_rxControlBlock.m_recoveryCount; - } else { - paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; - } - - // update counters - if (dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount) { - m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount - dataBlock->m_rxControlBlock.m_originalCount; - } else { - m_nbCorrectableErrors += dataBlock->m_rxControlBlock.m_recoveryCount; - } - - if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode - { - qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" - << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount - << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; - } - else - { - for (int ir = 0; ir < dataBlock->m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks - { - int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_recoveryCount + ir; - int blockIndex = m_cm256DescriptorBlocks[recoveryIndex].Index; - SDRDaemonProtectedBlock *recoveredBlock = - (SDRDaemonProtectedBlock *) m_cm256DescriptorBlocks[recoveryIndex].Block; - memcpy((void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); - if ((blockIndex == 0) && !dataBlock->m_rxControlBlock.m_metaRetrieved) { - dataBlock->m_rxControlBlock.m_metaRetrieved = true; - } - } - } - } - - // Validate block zero and retrieve its data - if (dataBlock->m_rxControlBlock.m_metaRetrieved) - { - SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock->m_superBlocks[0].m_protectedBlock); - boost::crc_32_type crc32; - crc32.process_bytes(metaData, 20); - - if (crc32.checksum() == metaData->m_crc32) - { - if (!(m_currentMeta == *metaData)) - { - printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); - - if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { - m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency*1000); // frequency is in kHz - } - - if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) - { - m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); - m_dataReadQueue.setSize(calculateDataReadQueueSize(metaData->m_sampleRate)); - } - } - - m_currentMeta = *metaData; - } - else - { - qWarning() << "SDRDaemonChannelSource::handleDataBlock: recovered meta: invalid CRC32"; - } - } - - m_dataReadQueue.push(dataBlock); // Push into R/W buffer - } -} - -void SDRDaemonChannelSource::handleData() -{ - SDRDaemonDataBlock* dataBlock; - - while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) { - handleDataBlock(dataBlock); - } -} - -void SDRDaemonChannelSource::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) -{ - qDebug().noquote() << header << ": " - << "|" << 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 - << "|"; -} - -uint32_t SDRDaemonChannelSource::calculateDataReadQueueSize(int sampleRate) -{ - // scale for 20 blocks at 48 kS/s. Take next even number. - uint32_t maxSize = sampleRate / 2400; - maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; - qDebug("SDRDaemonChannelSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); - return maxSize; -} - -int SDRDaemonChannelSource::webapiSettingsGet( - SWGSDRangel::SWGChannelSettings& response, - QString& errorMessage __attribute__((unused))) -{ - response.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); - response.getSdrDaemonChannelSourceSettings()->init(); - webapiFormatChannelSettings(response, m_settings); - return 200; -} - -int SDRDaemonChannelSource::webapiSettingsPutPatch( - bool force, - const QStringList& channelSettingsKeys, - SWGSDRangel::SWGChannelSettings& response, - QString& errorMessage __attribute__((unused))) -{ - SDRDaemonChannelSourceSettings settings = m_settings; - - if (channelSettingsKeys.contains("dataAddress")) { - settings.m_dataAddress = *response.getSdrDaemonChannelSourceSettings()->getDataAddress(); - } - - if (channelSettingsKeys.contains("dataPort")) - { - int dataPort = response.getSdrDaemonChannelSourceSettings()->getDataPort(); - - if ((dataPort < 1024) || (dataPort > 65535)) { - settings.m_dataPort = 9090; - } else { - settings.m_dataPort = dataPort; - } - } - - MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, force); - m_inputMessageQueue.push(msg); - - qDebug("SDRDaemonChannelSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); - if (m_guiMessageQueue) // forward to GUI if any - { - MsgConfigureSDRDaemonChannelSource *msgToGUI = MsgConfigureSDRDaemonChannelSource::create(settings, force); - m_guiMessageQueue->push(msgToGUI); - } - - webapiFormatChannelSettings(response, settings); - - return 200; -} - -int SDRDaemonChannelSource::webapiReportGet( - SWGSDRangel::SWGChannelReport& response, - QString& errorMessage __attribute__((unused))) -{ - response.setSdrDaemonChannelSourceReport(new SWGSDRangel::SWGSDRDaemonChannelSourceReport()); - response.getSdrDaemonChannelSourceReport()->init(); - webapiFormatChannelReport(response); - return 200; -} - -void SDRDaemonChannelSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings) -{ - if (response.getSdrDaemonChannelSourceSettings()->getDataAddress()) { - *response.getSdrDaemonChannelSourceSettings()->getDataAddress() = settings.m_dataAddress; - } else { - response.getSdrDaemonChannelSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); - } - - response.getSdrDaemonChannelSourceSettings()->setDataPort(settings.m_dataPort); -} - -void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) -{ - struct timeval tv; - gettimeofday(&tv, 0); - - response.getSdrDaemonChannelSourceReport()->setTvSec(tv.tv_sec); - response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); - response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); - response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); - response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); - response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); - response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); -} diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h deleted file mode 100644 index cf51177c2..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h +++ /dev/null @@ -1,135 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) // -// // -// 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ -#define CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ - -#include "cm256.h" - -#include "dsp/basebandsamplesource.h" -#include "channel/channelsourceapi.h" -#include "util/message.h" -#include "sdrdaemonchannelsourcesettings.h" -#include "channel/sdrdaemondataqueue.h" -#include "channel/sdrdaemondatablock.h" -#include "channel/sdrdaemondatareadqueue.h" - -class ThreadedBasebandSampleSource; -class UpChannelizer; -class DeviceSinkAPI; -class SDRDaemonChannelSourceThread; -class SDRDaemonDataBlock; - -class SDRDaemonChannelSource : public BasebandSampleSource, public ChannelSourceAPI { - Q_OBJECT -public: - class MsgConfigureSDRDaemonChannelSource : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const SDRDaemonChannelSourceSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureSDRDaemonChannelSource* create(const SDRDaemonChannelSourceSettings& settings, bool force) - { - return new MsgConfigureSDRDaemonChannelSource(settings, force); - } - - private: - SDRDaemonChannelSourceSettings m_settings; - bool m_force; - - MsgConfigureSDRDaemonChannelSource(const SDRDaemonChannelSourceSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI); - ~SDRDaemonChannelSource(); - virtual void destroy() { delete this; } - - virtual void pull(Sample& sample); - 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 Source"; } - 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); - - virtual int webapiReportGet( - SWGSDRangel::SWGChannelReport& response, - QString& errorMessage); - - void setDataLink(const QString& dataAddress, uint16_t dataPort); - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - DeviceSinkAPI *m_deviceAPI; - ThreadedBasebandSampleSource* m_threadedChannelizer; - UpChannelizer* m_channelizer; - SDRDaemonDataQueue m_dataQueue; - SDRDaemonChannelSourceThread *m_sourceThread; - CM256 m_cm256; - CM256 *m_cm256p; - bool m_running; - - SDRDaemonChannelSourceSettings m_settings; - - CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) - SDRDaemonMetaDataFEC m_currentMeta; - - SDRDaemonDataReadQueue m_dataReadQueue; - - uint32_t m_nbCorrectableErrors; //!< count of correctable errors in number of blocks - uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks - - void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); - void handleDataBlock(SDRDaemonDataBlock *dataBlock); - void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); - uint32_t calculateDataReadQueueSize(int sampleRate); - void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings); - void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); - -private slots: - void handleData(); -}; - - -#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ */ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp deleted file mode 100644 index ff46c1914..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) GUI // -// // -// 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 "device/devicesinkapi.h" -#include "device/deviceuiset.h" -#include "plugin/pluginapi.h" -#include "util/simpleserializer.h" -#include "gui/basicchannelsettingsdialog.h" -#include "mainwindow.h" - -#include "ui_sdrdaemonchannelsourcegui.h" -#include "sdrdaemonchannelsource.h" -#include "sdrdaemonchannelsourcegui.h" - - -SDRDaemonChannelSourceGUI* SDRDaemonChannelSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) -{ - SDRDaemonChannelSourceGUI* gui = new SDRDaemonChannelSourceGUI(pluginAPI, deviceUISet, channelTx); - return gui; -} - -void SDRDaemonChannelSourceGUI::destroy() -{ - delete this; -} - -void SDRDaemonChannelSourceGUI::setName(const QString& name) -{ - setObjectName(name); -} - -QString SDRDaemonChannelSourceGUI::getName() const -{ - return objectName(); -} - -qint64 SDRDaemonChannelSourceGUI::getCenterFrequency() const { - return m_channelMarker.getCenterFrequency(); -} - -void SDRDaemonChannelSourceGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) -{ - // you can't do that center frquency is fixed to zero - // m_channelMarker.setCenterFrequency(centerFrequency); - // applySettings(); -} - -void SDRDaemonChannelSourceGUI::resetToDefaults() -{ - m_settings.resetToDefaults(); - displaySettings(); - applySettings(true); -} - -QByteArray SDRDaemonChannelSourceGUI::serialize() const -{ - return m_settings.serialize(); -} - -bool SDRDaemonChannelSourceGUI::deserialize(const QByteArray& data) -{ - if(m_settings.deserialize(data)) { - displaySettings(); - applySettings(true); - return true; - } else { - resetToDefaults(); - return false; - } -} - -bool SDRDaemonChannelSourceGUI::handleMessage(const Message& message) -{ - if (SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource::match(message)) - { - const SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource& cfg = (SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource&) message; - m_settings = cfg.getSettings(); - blockApplySettings(true); - displaySettings(); - blockApplySettings(false); - return true; - } - else - { - return false; - } -} - -void SDRDaemonChannelSourceGUI::handleSourceMessages() -{ - Message* message; - - while ((message = getInputMessageQueue()->pop()) != 0) - { - if (handleMessage(*message)) - { - delete message; - } - } -} - -void SDRDaemonChannelSourceGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) -{ -} - -void SDRDaemonChannelSourceGUI::onMenuDialogCalled(const QPoint &p) -{ - BasicChannelSettingsDialog dialog(&m_channelMarker, this); - dialog.move(p); - dialog.exec(); - - m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); - m_settings.m_title = m_channelMarker.getTitle(); - - setWindowTitle(m_settings.m_title); - setTitleColor(m_settings.m_rgbColor); - - applySettings(); -} - -SDRDaemonChannelSourceGUI::SDRDaemonChannelSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : - RollupWidget(parent), - ui(new Ui::SDRDaemonChannelSourceGUI), - m_pluginAPI(pluginAPI), - m_deviceUISet(deviceUISet), - m_channelMarker(this), - m_doApplySettings(true), - m_tickCount(0) -{ - ui->setupUi(this); - setAttribute(Qt::WA_DeleteOnClose, true); - connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); - - m_sdrDaemonChannelSource = (SDRDaemonChannelSource*) channelTx; - m_sdrDaemonChannelSource->setMessageQueueToGUI(getInputMessageQueue()); - - connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); - - m_channelMarker.blockSignals(true); - m_channelMarker.setColor(Qt::cyan); - m_channelMarker.setBandwidth(5000); - m_channelMarker.setCenterFrequency(0); - m_channelMarker.setTitle("SDRDaemon sink"); - m_channelMarker.blockSignals(false); - m_channelMarker.setVisible(true); // activate signal on the last setting only - - m_settings.setChannelMarker(&m_channelMarker); - - m_deviceUISet->registerTxChannelInstance(SDRDaemonChannelSource::m_channelIdURI, this); - m_deviceUISet->addChannelMarker(&m_channelMarker); - m_deviceUISet->addRollupWidget(this); - - connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); - - displaySettings(); - applySettings(true); -} - -SDRDaemonChannelSourceGUI::~SDRDaemonChannelSourceGUI() -{ - m_deviceUISet->removeTxChannelInstance(this); - delete m_sdrDaemonChannelSource; // TODO: check this: when the GUI closes it has to delete the channel - delete ui; -} - -void SDRDaemonChannelSourceGUI::blockApplySettings(bool block) -{ - m_doApplySettings = !block; -} - -void SDRDaemonChannelSourceGUI::applySettings(bool force) -{ - if (m_doApplySettings) - { - setTitleColor(m_channelMarker.getColor()); - - SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource* message = SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource::create(m_settings, force); - m_sdrDaemonChannelSource->getInputMessageQueue()->push(message); - } -} - -void SDRDaemonChannelSourceGUI::displaySettings() -{ - m_channelMarker.blockSignals(true); - m_channelMarker.setCenterFrequency(0); - m_channelMarker.setTitle(m_settings.m_title); - m_channelMarker.setBandwidth(5000); // TODO - m_channelMarker.blockSignals(false); - m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only - - setTitleColor(m_settings.m_rgbColor); - setWindowTitle(m_channelMarker.getTitle()); - - blockApplySettings(true); - - // TODO - - blockApplySettings(false); -} - -void SDRDaemonChannelSourceGUI::leaveEvent(QEvent*) -{ - m_channelMarker.setHighlighted(false); -} - -void SDRDaemonChannelSourceGUI::enterEvent(QEvent*) -{ - m_channelMarker.setHighlighted(true); -} - -void SDRDaemonChannelSourceGUI::tick() -{ - // TODO if anything useful -} - -void SDRDaemonChannelSourceGUI::on_dataAddress_returnPressed() -{ - m_settings.m_dataAddress = ui->dataAddress->text(); - applySettings(); -} - -void SDRDaemonChannelSourceGUI::on_dataPort_returnPressed() -{ - bool dataOk; - int dataPort = ui->dataPort->text().toInt(&dataOk); - - if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) - { - return; - } - else - { - m_settings.m_dataPort = dataPort; - } - - applySettings(); -} - -void SDRDaemonChannelSourceGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) -{ - m_settings.m_dataAddress = ui->dataAddress->text(); - - bool dataOk; - int udpDataPort = ui->dataPort->text().toInt(&dataOk); - - if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) - { - m_settings.m_dataPort = udpDataPort; - } - - applySettings(); -} \ No newline at end of file diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h deleted file mode 100644 index 08ecf3423..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h +++ /dev/null @@ -1,92 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) GUI // -// // -// 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ -#define CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ - -#include "plugin/plugininstancegui.h" -#include "gui/rollupwidget.h" -#include "dsp/channelmarker.h" -#include "util/messagequeue.h" - -#include "sdrdaemonchannelsourcesettings.h" - -class PluginAPI; -class DeviceUISet; -class SDRDaemonChannelSource; -class BasebandSampleSource; - -namespace Ui { - class SDRDaemonChannelSourceGUI; -} - -class SDRDaemonChannelSourceGUI : public RollupWidget, public PluginInstanceGUI { - Q_OBJECT - -public: - static SDRDaemonChannelSourceGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceAPI, BasebandSampleSource *channelTx); - virtual void destroy(); - - void setName(const QString& name); - QString getName() const; - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); - virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } - virtual bool handleMessage(const Message& message); - -private: - Ui::SDRDaemonChannelSourceGUI* ui; - PluginAPI* m_pluginAPI; - DeviceUISet* m_deviceUISet; - ChannelMarker m_channelMarker; - SDRDaemonChannelSourceSettings m_settings; - bool m_doApplySettings; - int m_tickCount; - - SDRDaemonChannelSource* m_sdrDaemonChannelSource; - MessageQueue m_inputMessageQueue; - - explicit SDRDaemonChannelSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); - virtual ~SDRDaemonChannelSourceGUI(); - - void blockApplySettings(bool block); - void applySettings(bool force = false); - void displaySettings(); - - void leaveEvent(QEvent*); - void enterEvent(QEvent*); - -private slots: - void handleSourceMessages(); - void on_dataAddress_returnPressed(); - void on_dataPort_returnPressed(); - void on_dataApplyButton_clicked(bool checked); - void onWidgetRolled(QWidget* widget, bool rollDown); - void onMenuDialogCalled(const QPoint& p); - void tick(); -}; - -#endif // CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui deleted file mode 100644 index 67e75a9ea..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui +++ /dev/null @@ -1,179 +0,0 @@ - - - SDRDaemonChannelSourceGUI - - - - 0 - 0 - 320 - 90 - - - - - 0 - 0 - - - - - 320 - 90 - - - - - Liberation Sans - 9 - - - - LoRa Demodulator - - - - - 10 - 10 - 301 - 61 - - - - 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 - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - RollupWidget - QWidget -
    gui/rollupwidget.h
    - 1 -
    -
    - - -
    diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp deleted file mode 100644 index 7415d6215..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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 "plugin/pluginapi.h" - -#ifndef SERVER_MODE -#include "sdrdaemonchannelsourcegui.h" -#endif -#include "sdrdaemonchannelsource.h" -#include "sdrdaemonchannelsourceplugin.h" - -const PluginDescriptor SDRDaemonChannelSourcePlugin::m_pluginDescriptor = { - QString("SDRDaemon source"), - QString("4.1.0"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -SDRDaemonChannelSourcePlugin::SDRDaemonChannelSourcePlugin(QObject* parent) : - QObject(parent), - m_pluginAPI(0) -{ -} - -const PluginDescriptor& SDRDaemonChannelSourcePlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void SDRDaemonChannelSourcePlugin::initPlugin(PluginAPI* pluginAPI) -{ - m_pluginAPI = pluginAPI; - - // register - m_pluginAPI->registerTxChannel(SDRDaemonChannelSource::m_channelIdURI, SDRDaemonChannelSource::m_channelId, this); -} - -#ifdef SERVER_MODE -PluginInstanceGUI* SDRDaemonChannelSourcePlugin::createTxChannelGUI( - DeviceUISet *deviceUISet __attribute__((unused)), - BasebandSampleSource *txChannel __attribute__((unused))) -{ - return 0; -} -#else -PluginInstanceGUI* SDRDaemonChannelSourcePlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) -{ - return SDRDaemonChannelSourceGUI::create(m_pluginAPI, deviceUISet, txChannel); -} -#endif - -BasebandSampleSource* SDRDaemonChannelSourcePlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) -{ - return new SDRDaemonChannelSource(deviceAPI); -} - -ChannelSourceAPI* SDRDaemonChannelSourcePlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) -{ - return new SDRDaemonChannelSource(deviceAPI); -} - diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h deleted file mode 100644 index fb0a07036..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h +++ /dev/null @@ -1,47 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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_SDRDAEMONCHANNELSOURCEPLUGIN_H -#define INCLUDE_SDRDAEMONCHANNELSOURCEPLUGIN_H - -#include -#include "plugin/plugininterface.h" - -class DeviceUISet; -class BasebandSampleSource; - -class SDRDaemonChannelSourcePlugin : public QObject, PluginInterface { - Q_OBJECT - Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "sdrangel.channeltx.sdrdaemonsource") - -public: - explicit SDRDaemonChannelSourcePlugin(QObject* parent = 0); - - const PluginDescriptor& getPluginDescriptor() const; - void initPlugin(PluginAPI* pluginAPI); - - virtual PluginInstanceGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel); - virtual BasebandSampleSource* createTxChannelBS(DeviceSinkAPI *deviceAPI); - virtual ChannelSourceAPI* createTxChannelCS(DeviceSinkAPI *deviceAPI); - -private: - static const PluginDescriptor m_pluginDescriptor; - - PluginAPI* m_pluginAPI; -}; - -#endif // INCLUDE_SDRDAEMONCHANNELSOURCEPLUGIN_H diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp deleted file mode 100644 index 85892e7fb..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// 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 - -#include "util/simpleserializer.h" -#include "settings/serializable.h" -#include "sdrdaemonchannelsourcesettings.h" - -SDRDaemonChannelSourceSettings::SDRDaemonChannelSourceSettings() -{ - resetToDefaults(); -} - -void SDRDaemonChannelSourceSettings::resetToDefaults() -{ - m_dataAddress = "127.0.0.1"; - m_dataPort = 9090; - m_rgbColor = QColor(0, 255, 255).rgb(); - m_title = "SDRDaemon sink"; - -} - -QByteArray SDRDaemonChannelSourceSettings::serialize() const -{ - SimpleSerializer s(1); - s.writeString(1, m_dataAddress); - s.writeU32(2, m_dataPort); - s.writeU32(3, m_rgbColor); - s.writeString(4, m_title); - - return s.final(); -} - -bool SDRDaemonChannelSourceSettings::deserialize(const QByteArray& data) -{ - SimpleDeserializer d(data); - - if(!d.isValid()) - { - resetToDefaults(); - return false; - } - - if(d.getVersion() == 1) - { - uint32_t tmp; - QString strtmp; - - d.readString(1, &m_dataAddress, "127.0.0.1"); - d.readU32(2, &tmp, 0); - - if ((tmp > 1023) && (tmp < 65535)) { - m_dataPort = tmp; - } else { - m_dataPort = 9090; - } - - d.readU32(3, &m_rgbColor, QColor(0, 255, 255).rgb()); - d.readString(4, &m_title, "AM Modulator"); - - return true; - } - else - { - resetToDefaults(); - return false; - } -} - - - - - - - diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h deleted file mode 100644 index ea3f5311c..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h +++ /dev/null @@ -1,48 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ -#define CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ - -#include -#include - -class Serializable; - -struct SDRDaemonChannelSourceSettings -{ - QString m_dataAddress; //!< Listening (local) data address - uint16_t m_dataPort; //!< Listening data port - quint32 m_rgbColor; - QString m_title; - - Serializable *m_channelMarker; - - SDRDaemonChannelSourceSettings(); - void resetToDefaults(); - void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } - QByteArray serialize() const; - bool deserialize(const QByteArray& data); -}; - - -#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ */ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp deleted file mode 100644 index 3b2d55998..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) UDP receiver 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 "channel/sdrdaemondataqueue.h" -#include "channel/sdrdaemondatablock.h" -#include "channel/sdrdaemonchannelsourcethread.h" - -#include "cm256.h" - -MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgDataBind, Message) - -SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : - QThread(parent), - m_running(false), - m_dataQueue(dataQueue), - m_address(QHostAddress::LocalHost), - m_socket(0) -{ - std::fill(m_dataBlocks, m_dataBlocks+4, (SDRDaemonDataBlock *) 0); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); -} - -SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread() -{ - qDebug("SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread"); -} - -void SDRDaemonChannelSourceThread::startStop(bool start) -{ - MsgStartStop *msg = MsgStartStop::create(start); - m_inputMessageQueue.push(msg); -} - -void SDRDaemonChannelSourceThread::dataBind(const QString& address, uint16_t port) -{ - MsgDataBind *msg = MsgDataBind::create(address, port); - m_inputMessageQueue.push(msg); -} - -void SDRDaemonChannelSourceThread::startWork() -{ - qDebug("SDRDaemonChannelSourceThread::startWork"); - m_startWaitMutex.lock(); - m_socket = new QUdpSocket(this); - start(); - while(!m_running) - m_startWaiter.wait(&m_startWaitMutex, 100); - m_startWaitMutex.unlock(); -} - -void SDRDaemonChannelSourceThread::stopWork() -{ - qDebug("SDRDaemonChannelSourceThread::stopWork"); - delete m_socket; - m_socket = 0; - m_running = false; - wait(); -} - -void SDRDaemonChannelSourceThread::run() -{ - qDebug("SDRDaemonChannelSourceThread::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("SDRDaemonChannelSourceThread::run: end"); -} - - -void SDRDaemonChannelSourceThread::handleInputMessages() -{ - Message* message; - - while ((message = m_inputMessageQueue.pop()) != 0) - { - if (MsgStartStop::match(*message)) - { - MsgStartStop* notif = (MsgStartStop*) message; - qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); - - if (notif->getStartStop()) { - startWork(); - } else { - stopWork(); - } - - delete message; - } - else if (MsgDataBind::match(*message)) - { - MsgDataBind* notif = (MsgDataBind*) message; - qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); - - if (m_socket) - { - disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); - m_socket->bind(notif->getAddress(), notif->getPort()); - connect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); - } - } - } -} - -void SDRDaemonChannelSourceThread::readPendingDatagrams() -{ - SDRDaemonSuperBlock superBlock; - qint64 size; - - while (m_socket->hasPendingDatagrams()) - { - QHostAddress sender; - quint16 senderPort = 0; - //qint64 pendingDataSize = m_socket->pendingDatagramSize(); - size = m_socket->readDatagram((char *) &superBlock, (long long int) sizeof(SDRDaemonSuperBlock), &sender, &senderPort); - - if (size == sizeof(SDRDaemonSuperBlock)) - { - unsigned int dataBlockIndex = superBlock.m_header.m_frameIndex % m_nbDataBlocks; - - // create the first block for this index - if (m_dataBlocks[dataBlockIndex] == 0) { - m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); - } - - if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex < 0) - { - // initialize virgin block with the frame index - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; - } - else - { - // if the frame index is not the same for the same slot it means we are starting a new frame - uint32_t frameIndex = m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex; - - if (superBlock.m_header.m_frameIndex != frameIndex) - { - //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", frameIndex); - m_dataQueue->push(m_dataBlocks[dataBlockIndex]); - m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; - } - } - - m_dataBlocks[dataBlockIndex]->m_superBlocks[superBlock.m_header.m_blockIndex] = superBlock; - - if (superBlock.m_header.m_blockIndex == 0) { - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_metaRetrieved = true; - } - - if (superBlock.m_header.m_blockIndex < SDRDaemonNbOrginalBlocks) { - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_originalCount++; - } else { - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_recoveryCount++; - } - - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount++; - } - else - { - qWarning("SDRDaemonChannelSourceThread::readPendingDatagrams: wrong super block size not processing"); - } - } -} diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h deleted file mode 100644 index fed573f2e..000000000 --- a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h +++ /dev/null @@ -1,115 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) UDP receiver 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 . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ -#define CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ - -#include -#include -#include -#include - -#include "util/message.h" -#include "util/messagequeue.h" - -class SDRDaemonDataQueue; -class SDRDaemonDataBlock; -class QUdpSocket; - -class SDRDaemonChannelSourceThread : 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) - { } - }; - - class MsgDataBind : public Message { - MESSAGE_CLASS_DECLARATION - - public: - QHostAddress getAddress() const { return m_address; } - uint16_t getPort() const { return m_port; } - - static MsgDataBind* create(const QString& address, uint16_t port) { - return new MsgDataBind(address, port); - } - - protected: - QHostAddress m_address; - uint16_t m_port; - - MsgDataBind(const QString& address, uint16_t port) : - Message(), - m_port(port) - { - m_address.setAddress(address); - } - }; - - SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); - ~SDRDaemonChannelSourceThread(); - - void startStop(bool start); - void dataBind(const QString& address, uint16_t port); - -private: - QMutex m_startWaitMutex; - QWaitCondition m_startWaiter; - bool m_running; - - MessageQueue m_inputMessageQueue; - SDRDaemonDataQueue *m_dataQueue; - - QHostAddress m_address; - QUdpSocket *m_socket; - - static const uint32_t m_nbDataBlocks = 4; //!< number of data blocks in the ring buffer - SDRDaemonDataBlock *m_dataBlocks[m_nbDataBlocks]; //!< ring buffer of data blocks indexed by frame affinity - - void startWork(); - void stopWork(); - - void run(); - -private slots: - void handleInputMessages(); - void readPendingDatagrams(); -}; - - - -#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ */ From b72347919728319a76d737c72c88aed9adb5fc26 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 2 Sep 2018 19:27:38 +0200 Subject: [PATCH 688/956] DaemonSrc: added more report data to the report API --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 4 + sdrbase/resources/webapi/doc/html2/index.html | 18 +++- .../include/SDRDaemonChannelSource.yaml | 15 +++- .../include/SDRDaemonChannelSource.yaml | 15 +++- swagger/sdrangel/code/html2/index.html | 18 +++- .../SWGSDRDaemonChannelSourceReport.cpp | 84 +++++++++++++++++++ .../client/SWGSDRDaemonChannelSourceReport.h | 24 ++++++ 7 files changed, 174 insertions(+), 4 deletions(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index 95eda0614..c703b7be8 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -447,5 +447,9 @@ void DaemonSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respons response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); + response.getSdrDaemonChannelSourceReport()->setNbOriginalBlocks(m_currentMeta.m_nbOriginalBlocks); + response.getSdrDaemonChannelSourceReport()->setNbFecBlocks(m_currentMeta.m_nbFECBlocks); + response.getSdrDaemonChannelSourceReport()->setCenterFreq(m_currentMeta.m_centerFrequency); + response.getSdrDaemonChannelSourceReport()->setSampleRate(m_currentMeta.m_sampleRate); } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index ca83aacde..5db2c3181 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3202,6 +3202,22 @@ margin-bottom: 20px; "tvUSec" : { "type" : "integer", "description" : "Counts timestamp microseconds" + }, + "nbOriginalBlocks" : { + "type" : "integer", + "description" : "Number of original blocks per frame" + }, + "nbFECBlocks" : { + "type" : "integer", + "description" : "Number of FEC blocks per frame" + }, + "centerFreq" : { + "type" : "integer", + "description" : "Stream center frequency setting in kHz" + }, + "sampleRate" : { + "type" : "integer", + "description" : "Stream nominal sample rate in S/s" } }, "description" : "SDRDaemon channel source report" @@ -28703,7 +28719,7 @@ except ApiException as e:
    - Generated 2018-09-01T10:19:35.190+02:00 + Generated 2018-09-02T19:22:42.257+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml index 686c577c9..b8e41090e 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml @@ -35,4 +35,17 @@ SDRDaemonChannelSourceReport: type: integer tvUSec: description: "Counts timestamp microseconds" - type: integer \ No newline at end of file + type: integer + nbOriginalBlocks: + description: "Number of original blocks per frame" + type: integer + nbFECBlocks: + description: "Number of FEC blocks per frame" + type: integer + centerFreq: + description: "Stream center frequency setting in kHz" + type: integer + sampleRate: + description: "Stream nominal sample rate in S/s" + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml index 686c577c9..b8e41090e 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml @@ -35,4 +35,17 @@ SDRDaemonChannelSourceReport: type: integer tvUSec: description: "Counts timestamp microseconds" - type: integer \ No newline at end of file + type: integer + nbOriginalBlocks: + description: "Number of original blocks per frame" + type: integer + nbFECBlocks: + description: "Number of FEC blocks per frame" + type: integer + centerFreq: + description: "Stream center frequency setting in kHz" + type: integer + sampleRate: + description: "Stream nominal sample rate in S/s" + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index ca83aacde..5db2c3181 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -3202,6 +3202,22 @@ margin-bottom: 20px; "tvUSec" : { "type" : "integer", "description" : "Counts timestamp microseconds" + }, + "nbOriginalBlocks" : { + "type" : "integer", + "description" : "Number of original blocks per frame" + }, + "nbFECBlocks" : { + "type" : "integer", + "description" : "Number of FEC blocks per frame" + }, + "centerFreq" : { + "type" : "integer", + "description" : "Stream center frequency setting in kHz" + }, + "sampleRate" : { + "type" : "integer", + "description" : "Stream nominal sample rate in S/s" } }, "description" : "SDRDaemon channel source report" @@ -28703,7 +28719,7 @@ except ApiException as e:
    - Generated 2018-09-01T10:19:35.190+02:00 + Generated 2018-09-02T19:22:42.257+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp index 1d0659eba..8e4c213ea 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp @@ -42,6 +42,14 @@ SWGSDRDaemonChannelSourceReport::SWGSDRDaemonChannelSourceReport() { m_tv_sec_isSet = false; tv_u_sec = 0; m_tv_u_sec_isSet = false; + nb_original_blocks = 0; + m_nb_original_blocks_isSet = false; + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + center_freq = 0; + m_center_freq_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; } SWGSDRDaemonChannelSourceReport::~SWGSDRDaemonChannelSourceReport() { @@ -64,6 +72,14 @@ SWGSDRDaemonChannelSourceReport::init() { m_tv_sec_isSet = false; tv_u_sec = 0; m_tv_u_sec_isSet = false; + nb_original_blocks = 0; + m_nb_original_blocks_isSet = false; + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + center_freq = 0; + m_center_freq_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; } void @@ -75,6 +91,10 @@ SWGSDRDaemonChannelSourceReport::cleanup() { + + + + } SWGSDRDaemonChannelSourceReport* @@ -102,6 +122,14 @@ SWGSDRDaemonChannelSourceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&tv_u_sec, pJson["tvUSec"], "qint32", ""); + ::SWGSDRangel::setValue(&nb_original_blocks, pJson["nbOriginalBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(¢er_freq, pJson["centerFreq"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + } QString @@ -139,6 +167,18 @@ SWGSDRDaemonChannelSourceReport::asJsonObject() { if(m_tv_u_sec_isSet){ obj->insert("tvUSec", QJsonValue(tv_u_sec)); } + if(m_nb_original_blocks_isSet){ + obj->insert("nbOriginalBlocks", QJsonValue(nb_original_blocks)); + } + if(m_nb_fec_blocks_isSet){ + obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); + } + if(m_center_freq_isSet){ + obj->insert("centerFreq", QJsonValue(center_freq)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } return obj; } @@ -213,6 +253,46 @@ SWGSDRDaemonChannelSourceReport::setTvUSec(qint32 tv_u_sec) { this->m_tv_u_sec_isSet = true; } +qint32 +SWGSDRDaemonChannelSourceReport::getNbOriginalBlocks() { + return nb_original_blocks; +} +void +SWGSDRDaemonChannelSourceReport::setNbOriginalBlocks(qint32 nb_original_blocks) { + this->nb_original_blocks = nb_original_blocks; + this->m_nb_original_blocks_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getNbFecBlocks() { + return nb_fec_blocks; +} +void +SWGSDRDaemonChannelSourceReport::setNbFecBlocks(qint32 nb_fec_blocks) { + this->nb_fec_blocks = nb_fec_blocks; + this->m_nb_fec_blocks_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getCenterFreq() { + return center_freq; +} +void +SWGSDRDaemonChannelSourceReport::setCenterFreq(qint32 center_freq) { + this->center_freq = center_freq; + this->m_center_freq_isSet = true; +} + +qint32 +SWGSDRDaemonChannelSourceReport::getSampleRate() { + return sample_rate; +} +void +SWGSDRDaemonChannelSourceReport::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + bool SWGSDRDaemonChannelSourceReport::isSet(){ @@ -225,6 +305,10 @@ SWGSDRDaemonChannelSourceReport::isSet(){ if(m_uncorrectable_errors_count_isSet){ isObjectUpdated = true; break;} if(m_tv_sec_isSet){ isObjectUpdated = true; break;} if(m_tv_u_sec_isSet){ isObjectUpdated = true; break;} + if(m_nb_original_blocks_isSet){ isObjectUpdated = true; break;} + if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} + if(m_center_freq_isSet){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h index 737a3e948..6438c12ef 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h @@ -62,6 +62,18 @@ public: qint32 getTvUSec(); void setTvUSec(qint32 tv_u_sec); + qint32 getNbOriginalBlocks(); + void setNbOriginalBlocks(qint32 nb_original_blocks); + + qint32 getNbFecBlocks(); + void setNbFecBlocks(qint32 nb_fec_blocks); + + qint32 getCenterFreq(); + void setCenterFreq(qint32 center_freq); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + virtual bool isSet() override; @@ -87,6 +99,18 @@ private: qint32 tv_u_sec; bool m_tv_u_sec_isSet; + qint32 nb_original_blocks; + bool m_nb_original_blocks_isSet; + + qint32 nb_fec_blocks; + bool m_nb_fec_blocks_isSet; + + qint32 center_freq; + bool m_center_freq_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + }; } From 406ca837c3eb34c23cedda9f9c074517a044a3c0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 2 Sep 2018 19:49:53 +0200 Subject: [PATCH 689/956] SSBMod and WFMMod: fixed compiler warnings --- plugins/channeltx/modssb/ssbmod.cpp | 13 +++++++++---- plugins/channeltx/modwfm/wfmmod.cpp | 3 ++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index 501575ac7..17eb07625 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -22,6 +22,7 @@ #include #include +#include #include "SWGChannelSettings.h" #include "SWGChannelReport.h" @@ -80,8 +81,10 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : m_DSBFilter = new fftfilt((2.0f * m_settings.m_bandwidth) / m_audioSampleRate, 2 * m_ssbFftLen); m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size m_DSBFilterBuffer = new Complex[m_ssbFftLen]; - memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); - memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); + std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0}); + std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0}); +// memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); +// memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); m_audioBuffer.resize(1<<14); m_audioBufferFill = 0; @@ -787,12 +790,14 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) { if (settings.m_dsb) { - memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); + std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0}); + //memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); m_DSBFilterBufferIndex = 0; } else { - memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); + std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0}); + //memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); m_SSBFilterBufferIndex = 0; } } diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index 5f7660f5c..d8929546f 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -68,7 +68,8 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : m_rfFilter = new fftfilt(-62500.0 / 384000.0, 62500.0 / 384000.0, m_rfFilterFFTLength); m_rfFilterBuffer = new Complex[m_rfFilterFFTLength]; - memset(m_rfFilterBuffer, 0, sizeof(Complex)*(m_rfFilterFFTLength)); + std::fill(m_rfFilterBuffer, m_rfFilterBuffer+m_rfFilterFFTLength, Complex{0,0}); + //memset(m_rfFilterBuffer, 0, sizeof(Complex)*(m_rfFilterFFTLength)); m_rfFilterBufferIndex = 0; m_audioBuffer.resize(1<<14); From 5d856f8a8084fc00fe3f6332263ec0277ebab694 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 3 Sep 2018 00:15:47 +0200 Subject: [PATCH 690/956] SDRDaemonSink: corrected sample count wrap around calculation --- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index cb54090f7..6ffe18fc5 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -612,7 +612,7 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) sampleCount = report["samplesCount"].toInt(); if (sampleCount < m_lastSampleCount) { - sampleCountDelta = (0xFFFFFFFFU - sampleCount) + m_lastSampleCount + 1; + sampleCountDelta = (0xFFFFFFFFU - m_lastSampleCount) + sampleCount + 1; } else { sampleCountDelta = sampleCount - m_lastSampleCount; } From a0a9dc4dae7397d39768f9ca7f851655ae03fbf4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 3 Sep 2018 09:17:29 +0200 Subject: [PATCH 691/956] SDRDaemonSink: transfer sample rate control from GUI to core (1) --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 9 +- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 129 +++++++++++++++++- .../sdrdaemonsink/sdrdaemonsinkoutput.h | 26 +++- 3 files changed, 157 insertions(+), 7 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 6ffe18fc5..d3dab32f9 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -99,6 +99,7 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : SDRdaemonSinkGui::~SDRdaemonSinkGui() { + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); delete m_networkManager; delete ui; } @@ -684,8 +685,8 @@ void SDRdaemonSinkGui::sampleRateCorrection(int queueLength, int queueSize, int6 int chunkCorr = -roundf(sampleCorr); m_chunkSizeCorrection += chunkCorr; - qDebug("SDRdaemonSinkGui::sampleRateCorrection: %d (%d) samples", m_chunkSizeCorrection, chunkCorr); - - SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection::create(m_chunkSizeCorrection); - m_deviceSampleSink->getInputMessageQueue()->push(message); +// qDebug("SDRdaemonSinkGui::sampleRateCorrection: %d (%d) samples", m_chunkSizeCorrection, chunkCorr); +// +// SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection::create(m_chunkSizeCorrection); +// m_deviceSampleSink->getInputMessageQueue()->push(message); } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index f83c9b7bd..4da9fb24d 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" @@ -44,13 +47,24 @@ SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_sdrDaemonSinkThread(0), m_deviceDescription("SDRdaemonSink"), m_startingTimeStamp(0), - m_masterTimer(deviceAPI->getMasterTimer()) + m_masterTimer(deviceAPI->getMasterTimer()), + m_tickCount(0), + m_lastSampleCount(0), + m_lastTimestampUs(0), + m_lastTimestampRateCorrection(0), + m_nbSamplesSinceRateCorrection(0), + m_chunkSizeCorrection(0) { + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + connect(&m_masterTimer, SIGNAL(timeout()), this, SLOT(tick())); } SDRdaemonSinkOutput::~SDRdaemonSinkOutput() { + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); stop(); + delete m_networkManager; } void SDRdaemonSinkOutput::destroy() @@ -433,4 +447,117 @@ void SDRdaemonSinkOutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response.getSdrDaemonSinkReport()->setSampleCount(m_sdrDaemonSinkThread ? (int) m_sdrDaemonSinkThread->getSamplesCount() : 0); } +void SDRdaemonSinkOutput::tick() +{ + if (++m_tickCount == 20*60) // once per minute + { + QString reportURL; + reportURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/report") + .arg(m_settings.m_apiAddress) + .arg(m_settings.m_apiPort) + .arg(m_settings.m_deviceIndex) + .arg(m_settings.m_channelIndex); + + m_networkRequest.setUrl(QUrl(reportURL)); + m_networkManager->get(m_networkRequest); + + m_tickCount = 0; + } +} + +void SDRdaemonSinkOutput::networkManagerFinished(QNetworkReply *reply) +{ + if (reply->error()) + { + qInfo("SDRdaemonSinkOutput::networkManagerFinished: error: %s", qPrintable(reply->errorString())); + return; + } + + QString answer = reply->readAll(); + + try + { + QByteArray jsonBytes(answer.toStdString().c_str()); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error); + + if (error.error == QJsonParseError::NoError) + { + analyzeApiReply(doc.object()); + } + else + { + QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); + qInfo().noquote() << "SDRdaemonSinkOutput::networkManagerFinished" << errorMsg; + } + } + catch (const std::exception& ex) + { + QString errorMsg = QString("Error parsing request: ") + ex.what(); + qInfo().noquote() << "SDRdaemonSinkOutput::networkManagerFinished" << errorMsg; + } +} + +void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) +{ + QString infoLine; + + if (jsonObject.contains("SDRDaemonChannelSourceReport")) + { + QJsonObject report = jsonObject["SDRDaemonChannelSourceReport"].toObject(); + int queueSize = report["queueSize"].toInt(); + queueSize = queueSize == 0 ? 10 : queueSize; + int queueLength = report["queueLength"].toInt(); + int queueLengthPercent = (queueLength*100)/queueSize; + uint64_t timestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); + + uint32_t sampleCountDelta, sampleCount; + sampleCount = report["samplesCount"].toInt(); + + qDebug("SDRdaemonSinkOutput::analyzeApiReply: sampleCount: %u m_nbSamplesSinceRateCorrection: %u", + sampleCount, + m_nbSamplesSinceRateCorrection); + + if (sampleCount < m_lastSampleCount) { + sampleCountDelta = (0xFFFFFFFFU - m_lastSampleCount) + sampleCount + 1; + } else { + sampleCountDelta = sampleCount - m_lastSampleCount; + } + + if (sampleCountDelta != 0) + { + if (m_lastTimestampRateCorrection == 0) { + m_lastTimestampRateCorrection = timestampUs; + } + else + { + m_nbSamplesSinceRateCorrection += sampleCountDelta; + + if ((m_nbSamplesSinceRateCorrection > 20000000) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) + { + sampleRateCorrection(queueLength, queueSize, timestampUs - m_lastTimestampRateCorrection); + m_lastTimestampRateCorrection = timestampUs; + m_nbSamplesSinceRateCorrection = 0; + } + } + } + + m_lastSampleCount = sampleCount; + m_lastTimestampUs = timestampUs; + } +} + +void SDRdaemonSinkOutput::sampleRateCorrection(int queueLength, int queueSize, int64_t timeDeltaUs) +{ + int nbBlocksDiff = queueLength - (queueSize/2); + int nbSamplesDiff = nbBlocksDiff * 127 * 127; + float sampleCorr = (nbSamplesDiff * 50000.0) / timeDeltaUs; // correction for ~50ms chunks (50000 us) + int chunkCorr = -roundf(sampleCorr); + m_chunkSizeCorrection += chunkCorr; + + qDebug("SDRdaemonSinkOutput::sampleRateCorrection: %d (%d) samples", m_chunkSizeCorrection, chunkCorr); + + MsgConfigureSDRdaemonSinkChunkCorrection* message = MsgConfigureSDRdaemonSinkChunkCorrection::create(m_chunkSizeCorrection); + getInputMessageQueue()->push(message); +} diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index a5b4ada47..598626617 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -17,18 +17,23 @@ #ifndef INCLUDE_SDRDAEMONSINKOUTPUT_H #define INCLUDE_SDRDAEMONSINKOUTPUT_H -#include -#include #include #include #include +#include +#include +#include + #include "dsp/devicesamplesink.h" #include "sdrdaemonsinksettings.h" class SDRdaemonSinkThread; class DeviceSinkAPI; +class QNetworkAccessManager; +class QNetworkReply; +class QJsonObject; class SDRdaemonSinkOutput : public DeviceSampleSink { public: @@ -165,10 +170,27 @@ private: QString m_deviceDescription; std::time_t m_startingTimeStamp; const QTimer& m_masterTimer; + uint32_t m_tickCount; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + uint32_t m_lastSampleCount; + uint64_t m_lastTimestampUs; + uint64_t m_lastTimestampRateCorrection; + uint32_t m_nbSamplesSinceRateCorrection; + int m_chunkSizeCorrection; void applySettings(const SDRdaemonSinkSettings& settings, bool force = false); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSinkSettings& settings); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); + + void analyzeApiReply(const QJsonObject& jsonObject); + void sampleRateCorrection(int queueLength, int queueSize, int64_t timeDeltaUs); + +private slots: + void tick(); + void networkManagerFinished(QNetworkReply *reply); }; #endif // INCLUDE_SDRDAEMONSINKOUTPUT_H From 6f3bb72d52c5a6f7dc0dcda6cda7dab854fd8446 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 3 Sep 2018 18:13:57 +0200 Subject: [PATCH 692/956] SDRDaemonSink: transfer sample rate control from GUI to core (2) --- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 40 ++++++++++--------- .../sdrdaemonsink/sdrdaemonsinkoutput.h | 4 ++ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 4da9fb24d..2b913dc1b 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -41,6 +41,8 @@ MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkWork, Mes MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgStartStop, Message) MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection, Message) +const uint32_t SDRdaemonSinkOutput::NbSamplesForRateCorrection = 5000000; + SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_settings(), @@ -49,6 +51,7 @@ SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_startingTimeStamp(0), m_masterTimer(deviceAPI->getMasterTimer()), m_tickCount(0), + m_tickMultiplier(20), m_lastSampleCount(0), m_lastTimestampUs(0), m_lastTimestampRateCorrection(0), @@ -268,6 +271,9 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b m_sdrDaemonSinkThread->setSamplerate(settings.m_sampleRate); } + m_tickMultiplier = (20*NbSamplesForRateCorrection) / (2*settings.m_sampleRate); // two times per sample filling period + m_tickMultiplier = m_tickMultiplier < 20 ? 20 : m_tickMultiplier; // not below half a second + forwardChange = true; changeTxDelay = true; } @@ -449,7 +455,7 @@ void SDRdaemonSinkOutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& void SDRdaemonSinkOutput::tick() { - if (++m_tickCount == 20*60) // once per minute + if (++m_tickCount == m_tickMultiplier) { QString reportURL; @@ -515,31 +521,29 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) uint32_t sampleCountDelta, sampleCount; sampleCount = report["samplesCount"].toInt(); - qDebug("SDRdaemonSinkOutput::analyzeApiReply: sampleCount: %u m_nbSamplesSinceRateCorrection: %u", - sampleCount, - m_nbSamplesSinceRateCorrection); - if (sampleCount < m_lastSampleCount) { sampleCountDelta = (0xFFFFFFFFU - m_lastSampleCount) + sampleCount + 1; } else { sampleCountDelta = sampleCount - m_lastSampleCount; } - if (sampleCountDelta != 0) + if (m_lastTimestampRateCorrection == 0) { + m_lastTimestampRateCorrection = timestampUs; + } + else { - if (m_lastTimestampRateCorrection == 0) { - m_lastTimestampRateCorrection = timestampUs; - } - else - { - m_nbSamplesSinceRateCorrection += sampleCountDelta; + m_nbSamplesSinceRateCorrection += sampleCountDelta; - if ((m_nbSamplesSinceRateCorrection > 20000000) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) - { - sampleRateCorrection(queueLength, queueSize, timestampUs - m_lastTimestampRateCorrection); - m_lastTimestampRateCorrection = timestampUs; - m_nbSamplesSinceRateCorrection = 0; - } + qDebug("SDRdaemonSinkOutput::analyzeApiReply: queueLengthPercent: %d sampleCount: %u m_nbSamplesSinceRateCorrection: %u", + queueLengthPercent, + sampleCount, + m_nbSamplesSinceRateCorrection); + + if ((m_nbSamplesSinceRateCorrection > NbSamplesForRateCorrection) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) + { + sampleRateCorrection(queueLength, queueSize, timestampUs - m_lastTimestampRateCorrection); + m_lastTimestampRateCorrection = timestampUs; + m_nbSamplesSinceRateCorrection = 0; } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index 598626617..7dec4c616 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -36,6 +37,7 @@ class QNetworkReply; class QJsonObject; class SDRdaemonSinkOutput : public DeviceSampleSink { + Q_OBJECT public: class MsgConfigureSDRdaemonSink : public Message { MESSAGE_CLASS_DECLARATION @@ -171,6 +173,7 @@ private: std::time_t m_startingTimeStamp; const QTimer& m_masterTimer; uint32_t m_tickCount; + uint32_t m_tickMultiplier; QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; @@ -180,6 +183,7 @@ private: uint64_t m_lastTimestampRateCorrection; uint32_t m_nbSamplesSinceRateCorrection; int m_chunkSizeCorrection; + static const uint32_t NbSamplesForRateCorrection; void applySettings(const SDRdaemonSinkSettings& settings, bool force = false); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSinkSettings& settings); From 4a06cc94a0d9c9ad0be84bb492f9520cb87bf472 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 4 Sep 2018 08:43:07 +0200 Subject: [PATCH 693/956] SDRDaemonSink: refactored rate control and removed server type from GUI and REST API --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 40 +---- .../sdrdaemonsink/sdrdaemonsinkgui.h | 1 - .../sdrdaemonsink/sdrdaemonsinkgui.ui | 142 +++++++----------- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 72 +++++---- .../sdrdaemonsink/sdrdaemonsinkoutput.h | 6 +- .../sdrdaemonsink/sdrdaemonsinksettings.cpp | 11 -- .../sdrdaemonsink/sdrdaemonsinksettings.h | 7 - .../sdrdaemonsink/sdrdaemonsinkthread.cpp | 7 + .../sdrdaemonsink/sdrdaemonsinkthread.h | 12 +- sdrbase/resources/webapi/doc/html2/index.html | 5 +- .../doc/swagger/include/SDRDaemonSink.yaml | 4 +- .../api/swagger/include/SDRDaemonSink.yaml | 4 +- swagger/sdrangel/code/html2/index.html | 5 +- .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 21 --- .../qt5/client/SWGSDRdaemonSinkSettings.h | 6 - 15 files changed, 133 insertions(+), 210 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index d3dab32f9..561842d4c 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -245,7 +245,6 @@ void SDRdaemonSinkGui::displaySettings() QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s0).arg(s1)); - ui->serverType->setCurrentIndex((int) m_settings.m_serverType); ui->deviceIndex->setText(tr("%1").arg(m_settings.m_deviceIndex)); ui->channelIndex->setText(tr("%1").arg(m_settings.m_channelIndex)); ui->apiAddress->setText(m_settings.m_apiAddress); @@ -333,17 +332,6 @@ void SDRdaemonSinkGui::on_nbFECBlocks_valueChanged(int value) sendSettings(); } -void SDRdaemonSinkGui::on_serverType_currentIndexChanged(int index) -{ - m_settings.m_serverType = (SDRdaemonSinkSettings::ServerType) index; - sendSettings(); - - QString typeCode = m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel ? "sdrangel" : "sdrdaemon"; - QString infoURL = QString("http://%1:%2/%3").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort).arg(typeCode); - m_networkRequest.setUrl(QUrl(infoURL)); - m_networkManager->get(m_networkRequest); -} - void SDRdaemonSinkGui::on_deviceIndex_returnPressed() { bool dataOk; @@ -377,8 +365,7 @@ void SDRdaemonSinkGui::on_apiAddress_returnPressed() m_settings.m_apiAddress = ui->apiAddress->text(); sendSettings(); - QString typeCode = m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel ? "sdrangel" : "sdrdaemon"; - QString infoURL = QString("http://%1:%2/%3").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort).arg(typeCode); + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); m_networkRequest.setUrl(QUrl(infoURL)); m_networkManager->get(m_networkRequest); } @@ -396,8 +383,7 @@ void SDRdaemonSinkGui::on_apiPort_returnPressed() sendSettings(); - QString typeCode = m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel ? "sdrangel" : "sdrdaemon"; - QString infoURL = QString("http://%1:%2/%3").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort).arg(typeCode); + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); m_networkRequest.setUrl(QUrl(infoURL)); m_networkManager->get(m_networkRequest); } @@ -436,8 +422,7 @@ void SDRdaemonSinkGui::on_apiApplyButton_clicked(bool checked __attribute__((unu sendSettings(); - QString typeCode = m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel ? "sdrangel" : "sdrdaemon"; - QString infoURL = QString("http://%1:%2/%3").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort).arg(typeCode); + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); m_networkRequest.setUrl(QUrl(infoURL)); m_networkManager->get(m_networkRequest); } @@ -518,20 +503,11 @@ void SDRdaemonSinkGui::tick() { QString reportURL; - if (m_settings.m_serverType == SDRdaemonSinkSettings::ServerAngel) { - reportURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/report") - .arg(m_settings.m_apiAddress) - .arg(m_settings.m_apiPort) - .arg(m_settings.m_deviceIndex) - .arg(m_settings.m_channelIndex); - } - else - { - reportURL = QString("http://%1:%2/sdrdaemon/channel/report") - .arg(m_settings.m_apiAddress) - .arg(m_settings.m_apiPort); - } - + reportURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/report") + .arg(m_settings.m_apiAddress) + .arg(m_settings.m_apiPort) + .arg(m_settings.m_deviceIndex) + .arg(m_settings.m_channelIndex); m_networkRequest.setUrl(QUrl(reportURL)); m_networkManager->get(m_networkRequest); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index a0d43facc..2e719c52f 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -145,7 +145,6 @@ private slots: void on_sampleRate_changed(quint64 value); void on_txDelay_valueChanged(int value); void on_nbFECBlocks_valueChanged(int value); - void on_serverType_currentIndexChanged(int index); void on_deviceIndex_returnPressed(); void on_channelIndex_returnPressed(); void on_apiAddress_returnPressed(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 5a325b782..145378676 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -7,7 +7,7 @@ 0 0 360 - 300 + 270 @@ -19,7 +19,7 @@ 360 - 300 + 270 @@ -295,6 +295,58 @@ + + + + Dev + + + + + + + + 40 + 16777215 + + + + Device index (for SDRangel server) + + + 00 + + + 0 + + + + + + + Ch + + + + + + + + 40 + 16777215 + + + + Channel index (for SDRangel) + + + 00 + + + 0 + + + @@ -520,92 +572,6 @@ - - - - - - Remote server type - - - - Angel - - - - - Daemon - - - - - - - - Device - - - - - - - - 40 - 16777215 - - - - Device index (for SDRangel server) - - - 00 - - - 0 - - - - - - - Channel - - - - - - - - 40 - 16777215 - - - - Channel index (for SDRangel) - - - 00 - - - 0 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 2b913dc1b..d30838339 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -14,6 +14,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include #include #include @@ -52,9 +53,13 @@ SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_masterTimer(deviceAPI->getMasterTimer()), m_tickCount(0), m_tickMultiplier(20), + m_lastRemoteSampleCount(0), m_lastSampleCount(0), + m_lastRemoteTimestampUs(0), m_lastTimestampUs(0), + m_lastRemoteTimestampRateCorrection(0), m_lastTimestampRateCorrection(0), + m_nbRemoteSamplesSinceRateCorrection(0), m_nbSamplesSinceRateCorrection(0), m_chunkSizeCorrection(0) { @@ -271,7 +276,7 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b m_sdrDaemonSinkThread->setSamplerate(settings.m_sampleRate); } - m_tickMultiplier = (20*NbSamplesForRateCorrection) / (2*settings.m_sampleRate); // two times per sample filling period + m_tickMultiplier = (21*NbSamplesForRateCorrection) / (2*settings.m_sampleRate); // two times per sample filling period plus small extension m_tickMultiplier = m_tickMultiplier < 20 ? 20 : m_tickMultiplier; // not below half a second forwardChange = true; @@ -396,12 +401,6 @@ int SDRdaemonSinkOutput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("dataPort")) { settings.m_dataPort = response.getSdrDaemonSinkSettings()->getDataPort(); } - if (deviceSettingsKeys.contains("serverType")) { - int serverType = response.getSdrDaemonSinkSettings()->getServerType(); - settings.m_serverType = serverType < 0 ? SDRdaemonSinkSettings::ServerAngel - : serverType > 1 ? SDRdaemonSinkSettings::ServerAngel - : (SDRdaemonSinkSettings::ServerType) serverType; - } if (deviceSettingsKeys.contains("deviceIndex")) { settings.m_deviceIndex = response.getSdrDaemonSinkSettings()->getDeviceIndex(); } @@ -442,15 +441,15 @@ void SDRdaemonSinkOutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSetti response.getSdrDaemonSinkSettings()->setApiPort(settings.m_apiPort); response.getSdrDaemonSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); response.getSdrDaemonSinkSettings()->setDataPort(settings.m_dataPort); - response.getSdrDaemonSinkSettings()->setServerType((int) settings.m_serverType); response.getSdrDaemonSinkSettings()->setDeviceIndex(settings.m_deviceIndex); response.getSdrDaemonSinkSettings()->setChannelIndex(settings.m_channelIndex); } void SDRdaemonSinkOutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) { + struct timeval tv; response.getSdrDaemonSinkReport()->setBufferRwBalance(m_sampleSourceFifo.getRWBalance()); - response.getSdrDaemonSinkReport()->setSampleCount(m_sdrDaemonSinkThread ? (int) m_sdrDaemonSinkThread->getSamplesCount() : 0); + response.getSdrDaemonSinkReport()->setSampleCount(m_sdrDaemonSinkThread ? (int) m_sdrDaemonSinkThread->getSamplesCount(tv) : 0); } void SDRdaemonSinkOutput::tick() @@ -507,7 +506,9 @@ void SDRdaemonSinkOutput::networkManagerFinished(QNetworkReply *reply) void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) { - QString infoLine; + if (!m_sdrDaemonSinkThread) { + return; + } if (jsonObject.contains("SDRDaemonChannelSourceReport")) { @@ -516,10 +517,20 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) queueSize = queueSize == 0 ? 10 : queueSize; int queueLength = report["queueLength"].toInt(); int queueLengthPercent = (queueLength*100)/queueSize; - uint64_t timestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); + uint64_t remoteTimestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); + + uint32_t remoteSampleCountDelta, remoteSampleCount; + remoteSampleCount = report["samplesCount"].toInt(); + + if (remoteSampleCount < m_lastRemoteSampleCount) { + remoteSampleCountDelta = (0xFFFFFFFFU - m_lastRemoteSampleCount) + remoteSampleCount + 1; + } else { + remoteSampleCountDelta = remoteSampleCount - m_lastRemoteSampleCount; + } uint32_t sampleCountDelta, sampleCount; - sampleCount = report["samplesCount"].toInt(); + struct timeval tv; + sampleCount = m_sdrDaemonSinkThread->getSamplesCount(tv); if (sampleCount < m_lastSampleCount) { sampleCountDelta = (0xFFFFFFFFU - m_lastSampleCount) + sampleCount + 1; @@ -527,40 +538,49 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) sampleCountDelta = sampleCount - m_lastSampleCount; } - if (m_lastTimestampRateCorrection == 0) { + uint64_t timestampUs = tv.tv_sec*1000000ULL + tv.tv_usec; + + if (m_lastRemoteTimestampRateCorrection == 0) + { + m_lastRemoteTimestampRateCorrection = remoteTimestampUs; m_lastTimestampRateCorrection = timestampUs; } else { + m_nbRemoteSamplesSinceRateCorrection += remoteSampleCountDelta; m_nbSamplesSinceRateCorrection += sampleCountDelta; - qDebug("SDRdaemonSinkOutput::analyzeApiReply: queueLengthPercent: %d sampleCount: %u m_nbSamplesSinceRateCorrection: %u", + qDebug("SDRdaemonSinkOutput::analyzeApiReply: queueLengthPercent: %d m_nbSamplesSinceRateCorrection: %u", queueLengthPercent, - sampleCount, - m_nbSamplesSinceRateCorrection); + m_nbRemoteSamplesSinceRateCorrection); - if ((m_nbSamplesSinceRateCorrection > NbSamplesForRateCorrection) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) + if (m_nbRemoteSamplesSinceRateCorrection > NbSamplesForRateCorrection) { - sampleRateCorrection(queueLength, queueSize, timestampUs - m_lastTimestampRateCorrection); + sampleRateCorrection(remoteTimestampUs - m_lastRemoteTimestampRateCorrection, + timestampUs - m_lastTimestampRateCorrection, + m_nbRemoteSamplesSinceRateCorrection, + m_nbSamplesSinceRateCorrection); + m_lastRemoteTimestampRateCorrection = remoteTimestampUs; m_lastTimestampRateCorrection = timestampUs; + m_nbRemoteSamplesSinceRateCorrection = 0; m_nbSamplesSinceRateCorrection = 0; } } + m_lastRemoteSampleCount = remoteSampleCount; m_lastSampleCount = sampleCount; - m_lastTimestampUs = timestampUs; + m_lastRemoteTimestampUs = remoteTimestampUs; // TODO: remove + m_lastTimestampUs = timestampUs; // TODO: remove } } -void SDRdaemonSinkOutput::sampleRateCorrection(int queueLength, int queueSize, int64_t timeDeltaUs) +void SDRdaemonSinkOutput::sampleRateCorrection(double remoteTimeDeltaUs, double timeDeltaUs, uint32_t remoteSampleCount, uint32_t sampleCount) { - int nbBlocksDiff = queueLength - (queueSize/2); - int nbSamplesDiff = nbBlocksDiff * 127 * 127; - float sampleCorr = (nbSamplesDiff * 50000.0) / timeDeltaUs; // correction for ~50ms chunks (50000 us) - int chunkCorr = -roundf(sampleCorr); - m_chunkSizeCorrection += chunkCorr; + double deltaSR = (remoteSampleCount/remoteTimeDeltaUs) - (sampleCount/timeDeltaUs); + double chunkCorr = 50000 * deltaSR; // for 50ms chunk intervals (50000us) + m_chunkSizeCorrection += roundf(chunkCorr); - qDebug("SDRdaemonSinkOutput::sampleRateCorrection: %d (%d) samples", m_chunkSizeCorrection, chunkCorr); + qDebug("SDRdaemonSinkOutput::sampleRateCorrection: %d (%f) samples", m_chunkSizeCorrection, chunkCorr); MsgConfigureSDRdaemonSinkChunkCorrection* message = MsgConfigureSDRdaemonSinkChunkCorrection::create(m_chunkSizeCorrection); getInputMessageQueue()->push(message); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index 7dec4c616..6dc29df94 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -178,9 +178,13 @@ private: QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; + uint32_t m_lastRemoteSampleCount; uint32_t m_lastSampleCount; + uint64_t m_lastRemoteTimestampUs; uint64_t m_lastTimestampUs; + uint64_t m_lastRemoteTimestampRateCorrection; uint64_t m_lastTimestampRateCorrection; + uint32_t m_nbRemoteSamplesSinceRateCorrection; uint32_t m_nbSamplesSinceRateCorrection; int m_chunkSizeCorrection; static const uint32_t NbSamplesForRateCorrection; @@ -190,7 +194,7 @@ private: void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); void analyzeApiReply(const QJsonObject& jsonObject); - void sampleRateCorrection(int queueLength, int queueSize, int64_t timeDeltaUs); + void sampleRateCorrection(double remoteTimeDeltaUs, double timeDeltaUs, uint32_t remoteSampleCount, uint32_t sampleCount); private slots: void tick(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp index ad28bdfb0..7cc5d4548 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp @@ -32,7 +32,6 @@ void SDRdaemonSinkSettings::resetToDefaults() m_apiPort = 9091; m_dataAddress = "127.0.0.1"; m_dataPort = 9090; - m_serverType = ServerAngel; m_deviceIndex = 0; m_channelIndex = 0; } @@ -49,7 +48,6 @@ QByteArray SDRdaemonSinkSettings::serialize() const s.writeU32(6, m_apiPort); s.writeString(7, m_dataAddress); s.writeU32(8, m_dataPort); - s.writeS32(9, (int) m_serverType); s.writeU32(10, m_deviceIndex); s.writeU32(11, m_channelIndex); @@ -69,7 +67,6 @@ bool SDRdaemonSinkSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { quint32 uintval; - int intval; d.readU64(1, &m_centerFrequency, 435000*1000); d.readU32(2, &m_sampleRate, 48000); @@ -81,14 +78,6 @@ bool SDRdaemonSinkSettings::deserialize(const QByteArray& data) d.readString(7, &m_dataAddress, "127.0.0.1"); d.readU32(8, &uintval, 9090); m_dataPort = uintval % (1<<16); - d.readS32(9, &intval, 0); - - if ((intval < 0) || (intval > 1)) { - m_serverType = ServerAngel; - } else { - m_serverType = (ServerType) intval; - } - d.readU32(10, &m_deviceIndex, 0); d.readU32(11, &m_channelIndex, 0); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h index 4c9d4d9e7..88176fdec 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h @@ -20,12 +20,6 @@ #include struct SDRdaemonSinkSettings { - - typedef enum { - ServerAngel = 0, - ServerDaemon - } ServerType; - quint64 m_centerFrequency; quint32 m_sampleRate; float m_txDelay; @@ -34,7 +28,6 @@ struct SDRdaemonSinkSettings { quint16 m_apiPort; QString m_dataAddress; quint16 m_dataPort; - ServerType m_serverType; quint32 m_deviceIndex; quint32 m_channelIndex; diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp index be7bda63d..9c9d01816 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp @@ -14,6 +14,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include #include #include @@ -136,3 +137,9 @@ void SDRdaemonSinkThread::tick() m_udpSinkFEC.write(beginRead, m_samplesChunkSize); } } + +uint32_t SDRdaemonSinkThread::getSamplesCount(struct timeval& tv) const +{ + gettimeofday(&tv, 0); + return m_samplesCount; +} diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h index 349615fd1..f84b5f8a8 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h @@ -17,15 +17,16 @@ #ifndef INCLUDE_SDRDAEMONSINKTHREAD_H #define INCLUDE_SDRDAEMONSINKTHREAD_H +#include +#include +#include +#include + #include #include #include #include #include -#include -#include -#include -#include #include "dsp/inthalfbandfilter.h" #include "dsp/interpolators.h" @@ -35,6 +36,7 @@ #define SDRDAEMONSINK_THROTTLE_MS 50 class SampleSourceFifo; +struct timeval; class SDRdaemonSinkThread : public QThread { Q_OBJECT @@ -54,7 +56,7 @@ public: bool isRunning() const { return m_running; } - uint32_t getSamplesCount() const { return m_samplesCount; } + uint32_t getSamplesCount(struct timeval& tv) const; void setSamplesCount(int samplesCount) { m_samplesCount = samplesCount; } void setChunkCorrection(int chunkCorrection) { m_chunkCorrection = chunkCorrection; } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 5db2c3181..621e6e325 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3368,9 +3368,6 @@ margin-bottom: 20px; "dataPort" : { "type" : "integer" }, - "serverType" : { - "type" : "integer" - }, "deviceIndex" : { "type" : "integer" }, @@ -28719,7 +28716,7 @@ except ApiException as e:
    - Generated 2018-09-02T19:22:42.257+02:00 + Generated 2018-09-04T08:40:33.765+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml index a70b54e3f..c9ee31515 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSink.yaml @@ -20,11 +20,11 @@ SDRdaemonSinkSettings: type: string dataPort: type: integer - serverType: - type: integer deviceIndex: + device: remote SDRangel instance deviceset index type: integer channelIndex: + device: remote SDRangel instance channel index type: integer SDRdaemonSinkReport: diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml index a70b54e3f..c9ee31515 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonSink.yaml @@ -20,11 +20,11 @@ SDRdaemonSinkSettings: type: string dataPort: type: integer - serverType: - type: integer deviceIndex: + device: remote SDRangel instance deviceset index type: integer channelIndex: + device: remote SDRangel instance channel index type: integer SDRdaemonSinkReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 5db2c3181..621e6e325 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -3368,9 +3368,6 @@ margin-bottom: 20px; "dataPort" : { "type" : "integer" }, - "serverType" : { - "type" : "integer" - }, "deviceIndex" : { "type" : "integer" }, @@ -28719,7 +28716,7 @@ except ApiException as e:
    - Generated 2018-09-02T19:22:42.257+02:00 + Generated 2018-09-04T08:40:33.765+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp index f818aab62..48b33a891 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.cpp @@ -44,8 +44,6 @@ SWGSDRdaemonSinkSettings::SWGSDRdaemonSinkSettings() { m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; - server_type = 0; - m_server_type_isSet = false; device_index = 0; m_device_index_isSet = false; channel_index = 0; @@ -74,8 +72,6 @@ SWGSDRdaemonSinkSettings::init() { m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; - server_type = 0; - m_server_type_isSet = false; device_index = 0; m_device_index_isSet = false; channel_index = 0; @@ -98,7 +94,6 @@ SWGSDRdaemonSinkSettings::cleanup() { - } SWGSDRdaemonSinkSettings* @@ -128,8 +123,6 @@ SWGSDRdaemonSinkSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); - ::SWGSDRangel::setValue(&server_type, pJson["serverType"], "qint32", ""); - ::SWGSDRangel::setValue(&device_index, pJson["deviceIndex"], "qint32", ""); ::SWGSDRangel::setValue(&channel_index, pJson["channelIndex"], "qint32", ""); @@ -174,9 +167,6 @@ SWGSDRdaemonSinkSettings::asJsonObject() { if(m_data_port_isSet){ obj->insert("dataPort", QJsonValue(data_port)); } - if(m_server_type_isSet){ - obj->insert("serverType", QJsonValue(server_type)); - } if(m_device_index_isSet){ obj->insert("deviceIndex", QJsonValue(device_index)); } @@ -267,16 +257,6 @@ SWGSDRdaemonSinkSettings::setDataPort(qint32 data_port) { this->m_data_port_isSet = true; } -qint32 -SWGSDRdaemonSinkSettings::getServerType() { - return server_type; -} -void -SWGSDRdaemonSinkSettings::setServerType(qint32 server_type) { - this->server_type = server_type; - this->m_server_type_isSet = true; -} - qint32 SWGSDRdaemonSinkSettings::getDeviceIndex() { return device_index; @@ -310,7 +290,6 @@ SWGSDRdaemonSinkSettings::isSet(){ if(m_api_port_isSet){ isObjectUpdated = true; break;} if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} if(m_data_port_isSet){ isObjectUpdated = true; break;} - if(m_server_type_isSet){ isObjectUpdated = true; break;} if(m_device_index_isSet){ isObjectUpdated = true; break;} if(m_channel_index_isSet){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h index 20f3f8a88..14a3198ad 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSinkSettings.h @@ -66,9 +66,6 @@ public: qint32 getDataPort(); void setDataPort(qint32 data_port); - qint32 getServerType(); - void setServerType(qint32 server_type); - qint32 getDeviceIndex(); void setDeviceIndex(qint32 device_index); @@ -103,9 +100,6 @@ private: qint32 data_port; bool m_data_port_isSet; - qint32 server_type; - bool m_server_type_isSet; - qint32 device_index; bool m_device_index_isSet; From 5cf060f4d68239d35edad0817d9b64665d3263ce Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 4 Sep 2018 14:00:42 +0200 Subject: [PATCH 694/956] SDRdaemonSinkOutput: removed unused attributes --- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp | 4 ---- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h | 2 -- 2 files changed, 6 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index d30838339..0a4525a4b 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -55,8 +55,6 @@ SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_tickMultiplier(20), m_lastRemoteSampleCount(0), m_lastSampleCount(0), - m_lastRemoteTimestampUs(0), - m_lastTimestampUs(0), m_lastRemoteTimestampRateCorrection(0), m_lastTimestampRateCorrection(0), m_nbRemoteSamplesSinceRateCorrection(0), @@ -569,8 +567,6 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) m_lastRemoteSampleCount = remoteSampleCount; m_lastSampleCount = sampleCount; - m_lastRemoteTimestampUs = remoteTimestampUs; // TODO: remove - m_lastTimestampUs = timestampUs; // TODO: remove } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index 6dc29df94..30fc4ee2e 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -180,8 +180,6 @@ private: uint32_t m_lastRemoteSampleCount; uint32_t m_lastSampleCount; - uint64_t m_lastRemoteTimestampUs; - uint64_t m_lastTimestampUs; uint64_t m_lastRemoteTimestampRateCorrection; uint64_t m_lastTimestampRateCorrection; uint32_t m_nbRemoteSamplesSinceRateCorrection; From 71462252228ad3ae872a29aa4a6a3b1aa4c679fa Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 4 Sep 2018 15:02:45 +0200 Subject: [PATCH 695/956] SDRdaemonSinkGui: removed rate control code --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 36 ------------------- .../sdrdaemonsink/sdrdaemonsinkgui.h | 4 --- 2 files changed, 40 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 561842d4c..0de35c635 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -58,9 +58,6 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_lastCountUnrecoverable = 0; m_lastCountRecovered = 0; m_lastSampleCount = 0; - m_lastTimestampRateCorrection = 0; - m_nbSamplesSinceRateCorrection = 0; - m_chunkSizeCorrection = 0; m_resetCounts = true; m_paletteGreenText.setColor(QPalette::WindowText, Qt::green); @@ -446,9 +443,6 @@ void SDRdaemonSinkGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) { - m_nbSamplesSinceRateCorrection = 0; - m_lastTimestampRateCorrection = 0; - SDRdaemonSinkOutput::MsgStartStop *message = SDRdaemonSinkOutput::MsgStartStop::create(checked); m_deviceSampleSink->getInputMessageQueue()->push(message); } @@ -598,22 +592,6 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) { ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }"); } - else - { - if (m_lastTimestampRateCorrection == 0) { - m_lastTimestampRateCorrection = timestampUs; - } - - //if ((timestampUs - m_lastTimestampRateCorrection > 300e6) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) - if ((m_nbSamplesSinceRateCorrection > 20000000) && ((queueLengthPercent > 60) || (queueLengthPercent < 40))) - { - sampleRateCorrection(queueLength, queueSize, timestampUs - m_lastTimestampRateCorrection); - m_lastTimestampRateCorrection = timestampUs; - m_nbSamplesSinceRateCorrection = 0; - } - - m_nbSamplesSinceRateCorrection += sampleCountDelta; - } double remoteStreamRate = sampleCountDelta*1e6 / (double) (timestampUs - m_lastTimestampUs); @@ -652,17 +630,3 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) ui->infoText->setText(infoLine); } } - -void SDRdaemonSinkGui::sampleRateCorrection(int queueLength, int queueSize, int64_t timeDeltaUs) -{ - int nbBlocksDiff = queueLength - (queueSize/2); - int nbSamplesDiff = nbBlocksDiff * 127 * 127; - float sampleCorr = (nbSamplesDiff * 50000.0) / timeDeltaUs; // correction for ~50ms chunks (50000 us) - int chunkCorr = -roundf(sampleCorr); - m_chunkSizeCorrection += chunkCorr; - -// qDebug("SDRdaemonSinkGui::sampleRateCorrection: %d (%d) samples", m_chunkSizeCorrection, chunkCorr); -// -// SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection::create(m_chunkSizeCorrection); -// m_deviceSampleSink->getInputMessageQueue()->push(message); -} diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 2e719c52f..683a13774 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -111,9 +111,6 @@ private: uint32_t m_lastCountRecovered; uint32_t m_lastSampleCount; uint64_t m_lastTimestampUs; - uint64_t m_lastTimestampRateCorrection; - uint32_t m_nbSamplesSinceRateCorrection; - int m_chunkSizeCorrection; bool m_resetCounts; QTime m_time; @@ -137,7 +134,6 @@ private: void displayEventStatus(int recoverableCount, int unrecoverableCount); void displayEventTimer(); void analyzeApiReply(const QJsonObject& jsonObject); - void sampleRateCorrection(int queueLength, int queueSize, int64_t timeDeltaUs); private slots: void handleInputMessages(); From b2dc7a6cdc6696be8e96f2dadd5f08db5a27f04d Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 4 Sep 2018 20:35:54 +0200 Subject: [PATCH 696/956] Changed SDRDaemonChannelXxx to DaemonXxx --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 70 ++-- .../channeltx/daemonsrc/daemonsrcthread.cpp | 6 +- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 4 +- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 4 +- sdrbase/resources/res.qrc | 4 +- sdrbase/resources/webapi/doc/html2/index.html | 192 +++++------ ...DaemonChannelSink.yaml => DaemonSink.yaml} | 4 +- ...onChannelSource.yaml => DaemonSource.yaml} | 8 +- .../resources/webapi/doc/swagger/swagger.yaml | 12 +- sdrbase/webapi/webapirequestmapper.cpp | 12 +- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 26 +- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 34 +- sdrdaemon/webapi/webapirequestmapper.cpp | 10 +- ...DaemonChannelSink.yaml => DaemonSink.yaml} | 4 +- ...onChannelSource.yaml => DaemonSource.yaml} | 8 +- swagger/sdrangel/api/swagger/swagger.yaml | 12 +- swagger/sdrangel/code/html2/index.html | 192 +++++------ .../code/qt5/client/SWGChannelReport.cpp | 32 +- .../code/qt5/client/SWGChannelReport.h | 10 +- .../code/qt5/client/SWGChannelSettings.cpp | 64 ++-- .../code/qt5/client/SWGChannelSettings.h | 20 +- .../code/qt5/client/SWGDaemonSinkSettings.cpp | 171 ++++++++++ .../code/qt5/client/SWGDaemonSinkSettings.h | 77 +++++ .../code/qt5/client/SWGDaemonSourceReport.cpp | 316 ++++++++++++++++++ .../code/qt5/client/SWGDaemonSourceReport.h | 118 +++++++ .../qt5/client/SWGDaemonSourceSettings.cpp | 173 ++++++++++ .../code/qt5/client/SWGDaemonSourceSettings.h | 77 +++++ .../code/qt5/client/SWGModelFactory.h | 24 +- 28 files changed, 1308 insertions(+), 376 deletions(-) rename sdrbase/resources/webapi/doc/swagger/include/{SDRDaemonChannelSink.yaml => DaemonSink.yaml} (82%) rename sdrbase/resources/webapi/doc/swagger/include/{SDRDaemonChannelSource.yaml => DaemonSource.yaml} (88%) rename swagger/sdrangel/api/swagger/include/{SDRDaemonChannelSink.yaml => DaemonSink.yaml} (82%) rename swagger/sdrangel/api/swagger/include/{SDRDaemonChannelSource.yaml => DaemonSource.yaml} (88%) create mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.h diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index c703b7be8..7904e3ace 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -23,7 +23,7 @@ #include "SWGChannelSettings.h" #include "SWGChannelReport.h" -#include "SWGSDRDaemonChannelSourceReport.h" +#include "SWGDaemonSourceReport.h" #include "dsp/devicesamplesink.h" #include "device/devicesinkapi.h" @@ -219,7 +219,7 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu { if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) { - qWarning("SDRDaemonChannelSource::handleDataBlock: incomplete data block: not processing"); + qWarning("DaemonSrc::handleDataBlock: incomplete data block: not processing"); } else { @@ -241,12 +241,12 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu } } - //qDebug("SDRDaemonChannelSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); + //qDebug("DaemonSrc::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); // Need to use the CM256 recovery if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) { - qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); + qDebug("DaemonSrc::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); CM256::cm256_encoder_params paramsCM256; paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes @@ -266,7 +266,7 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode { - qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" + qWarning() << "DaemonSrc::handleDataBlock: decode CM256 error:" << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; } @@ -297,7 +297,7 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu { if (!(m_currentMeta == *metaData)) { - printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); + printMeta("DaemonSrc::handleDataBlock", metaData); if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency*1000); // frequency is in kHz @@ -314,7 +314,7 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu } else { - qWarning() << "SDRDaemonChannelSource::handleDataBlock: recovered meta: invalid CRC32"; + qWarning() << "DaemonSrc::handleDataBlock: recovered meta: invalid CRC32"; } } @@ -350,7 +350,7 @@ uint32_t DaemonSrc::calculateDataReadQueueSize(int sampleRate) // scale for 20 blocks at 48 kS/s. Take next even number. uint32_t maxSize = sampleRate / 2400; maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; - qDebug("SDRDaemonChannelSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); + qDebug("DaemonSrc::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); return maxSize; } @@ -358,8 +358,8 @@ int DaemonSrc::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - response.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); - response.getSdrDaemonChannelSourceSettings()->init(); + response.setDaemonSourceSettings(new SWGSDRangel::SWGDaemonSourceSettings()); + response.getDaemonSourceSettings()->init(); webapiFormatChannelSettings(response, m_settings); return 200; } @@ -373,11 +373,11 @@ int DaemonSrc::webapiSettingsPutPatch( DaemonSrcSettings settings = m_settings; if (channelSettingsKeys.contains("dataAddress")) { - settings.m_dataAddress = *response.getSdrDaemonChannelSourceSettings()->getDataAddress(); + settings.m_dataAddress = *response.getDaemonSourceSettings()->getDataAddress(); } if (channelSettingsKeys.contains("dataPort")) { - int dataPort = response.getSdrDaemonChannelSourceSettings()->getDataPort(); + int dataPort = response.getDaemonSourceSettings()->getDataPort(); if ((dataPort < 1024) || (dataPort > 65535)) { settings.m_dataPort = 9090; @@ -386,10 +386,10 @@ int DaemonSrc::webapiSettingsPutPatch( } } if (channelSettingsKeys.contains("rgbColor")) { - settings.m_rgbColor = response.getSdrDaemonChannelSourceSettings()->getRgbColor(); + settings.m_rgbColor = response.getDaemonSourceSettings()->getRgbColor(); } if (channelSettingsKeys.contains("title")) { - settings.m_title = *response.getSdrDaemonChannelSourceSettings()->getTitle(); + settings.m_title = *response.getDaemonSourceSettings()->getTitle(); } MsgConfigureDaemonSrc *msg = MsgConfigureDaemonSrc::create(settings, force); @@ -411,27 +411,27 @@ int DaemonSrc::webapiReportGet( SWGSDRangel::SWGChannelReport& response, QString& errorMessage __attribute__((unused))) { - response.setSdrDaemonChannelSourceReport(new SWGSDRangel::SWGSDRDaemonChannelSourceReport()); - response.getSdrDaemonChannelSourceReport()->init(); + response.setDaemonSourceReport(new SWGSDRangel::SWGDaemonSourceReport()); + response.getDaemonSourceReport()->init(); webapiFormatChannelReport(response); return 200; } void DaemonSrc::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSrcSettings& settings) { - if (response.getSdrDaemonChannelSourceSettings()->getDataAddress()) { - *response.getSdrDaemonChannelSourceSettings()->getDataAddress() = settings.m_dataAddress; + if (response.getDaemonSourceSettings()->getDataAddress()) { + *response.getDaemonSourceSettings()->getDataAddress() = settings.m_dataAddress; } else { - response.getSdrDaemonChannelSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); + response.getDaemonSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); } - response.getSdrDaemonChannelSourceSettings()->setDataPort(settings.m_dataPort); - response.getSdrDaemonChannelSourceSettings()->setRgbColor(settings.m_rgbColor); + response.getDaemonSourceSettings()->setDataPort(settings.m_dataPort); + response.getDaemonSourceSettings()->setRgbColor(settings.m_rgbColor); - if (response.getSdrDaemonChannelSourceSettings()->getTitle()) { - *response.getSdrDaemonChannelSourceSettings()->getTitle() = settings.m_title; + if (response.getDaemonSourceSettings()->getTitle()) { + *response.getDaemonSourceSettings()->getTitle() = settings.m_title; } else { - response.getSdrDaemonChannelSourceSettings()->setTitle(new QString(settings.m_title)); + response.getDaemonSourceSettings()->setTitle(new QString(settings.m_title)); } } @@ -440,16 +440,16 @@ void DaemonSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respons struct timeval tv; gettimeofday(&tv, 0); - response.getSdrDaemonChannelSourceReport()->setTvSec(tv.tv_sec); - response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); - response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); - response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); - response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); - response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); - response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); - response.getSdrDaemonChannelSourceReport()->setNbOriginalBlocks(m_currentMeta.m_nbOriginalBlocks); - response.getSdrDaemonChannelSourceReport()->setNbFecBlocks(m_currentMeta.m_nbFECBlocks); - response.getSdrDaemonChannelSourceReport()->setCenterFreq(m_currentMeta.m_centerFrequency); - response.getSdrDaemonChannelSourceReport()->setSampleRate(m_currentMeta.m_sampleRate); + response.getDaemonSourceReport()->setTvSec(tv.tv_sec); + response.getDaemonSourceReport()->setTvUSec(tv.tv_usec); + response.getDaemonSourceReport()->setQueueSize(m_dataReadQueue.size()); + response.getDaemonSourceReport()->setQueueLength(m_dataReadQueue.length()); + response.getDaemonSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); + response.getDaemonSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); + response.getDaemonSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); + response.getDaemonSourceReport()->setNbOriginalBlocks(m_currentMeta.m_nbOriginalBlocks); + response.getDaemonSourceReport()->setNbFecBlocks(m_currentMeta.m_nbFECBlocks); + response.getDaemonSourceReport()->setCenterFreq(m_currentMeta.m_centerFrequency); + response.getDaemonSourceReport()->setSampleRate(m_currentMeta.m_sampleRate); } diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp index e810b944a..b203eb4f5 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp @@ -41,7 +41,7 @@ DaemonSrcThread::DaemonSrcThread(SDRDaemonDataQueue *dataQueue, QObject* parent) DaemonSrcThread::~DaemonSrcThread() { - qDebug("DaemonSrcThread::~SDRDaemonChannelSourceThread"); + qDebug("DaemonSrcThread::~DaemonSrcThread"); } void DaemonSrcThread::startStop(bool start) @@ -159,7 +159,7 @@ void DaemonSrcThread::readPendingDatagrams() if (superBlock.m_header.m_frameIndex != frameIndex) { - //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", frameIndex); + //qDebug("DaemonSrcThread::readPendingDatagrams: push frame %u", frameIndex); m_dataQueue->push(m_dataBlocks[dataBlockIndex]); m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; @@ -182,7 +182,7 @@ void DaemonSrcThread::readPendingDatagrams() } else { - qWarning("SDRDaemonChannelSourceThread::readPendingDatagrams: wrong super block size not processing"); + qWarning("DaemonSrcThread::readPendingDatagrams: wrong super block size not processing"); } } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 0de35c635..a11fcb007 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -555,9 +555,9 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) { QString infoLine; - if (jsonObject.contains("SDRDaemonChannelSourceReport")) + if (jsonObject.contains("DaemonSourceReport")) { - QJsonObject report = jsonObject["SDRDaemonChannelSourceReport"].toObject(); + QJsonObject report = jsonObject["DaemonSourceReport"].toObject(); int queueSize = report["queueSize"].toInt(); queueSize = queueSize == 0 ? 10 : queueSize; int queueLength = report["queueLength"].toInt(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 0a4525a4b..d6f84c3d5 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -508,9 +508,9 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) return; } - if (jsonObject.contains("SDRDaemonChannelSourceReport")) + if (jsonObject.contains("DaemonSourceReport")) { - QJsonObject report = jsonObject["SDRDaemonChannelSourceReport"].toObject(); + QJsonObject report = jsonObject["DaemonSourceReport"].toObject(); int queueSize = report["queueSize"].toInt(); queueSize = queueSize == 0 ? 10 : queueSize; int queueLength = report["queueLength"].toInt(); diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 179cd9eaa..45afba2ea 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -21,8 +21,8 @@ webapi/doc/swagger/include/Perseus.yaml webapi/doc/swagger/include/PlutoSdr.yaml webapi/doc/swagger/include/RtlSdr.yaml - webapi/doc/swagger/include/SDRDaemonChannelSink.yaml - webapi/doc/swagger/include/SDRDaemonChannelSource.yaml + webapi/doc/swagger/include/DaemonSink.yaml + webapi/doc/swagger/include/DaemonSource.yaml webapi/doc/swagger/include/SDRDaemonSource.yaml webapi/doc/swagger/include/SDRDaemonSink.yaml webapi/doc/swagger/include/SDRPlay.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 621e6e325..daaf4c9f7 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1416,8 +1416,8 @@ margin-bottom: 20px; "SSBDemodReport" : { "$ref" : "#/definitions/SSBDemodReport" }, - "SDRDaemonChannelSourceReport" : { - "$ref" : "#/definitions/SDRDaemonChannelSourceReport" + "DaemonSourceReport" : { + "$ref" : "#/definitions/DaemonSourceReport" }, "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" @@ -1470,11 +1470,11 @@ margin-bottom: 20px; "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" }, - "SDRDaemonChannelSinkSettings" : { - "$ref" : "#/definitions/SDRDaemonChannelSinkSettings" + "DaemonSinkSettings" : { + "$ref" : "#/definitions/DaemonSinkSettings" }, - "SDRDaemonChannelSourceSettings" : { - "$ref" : "#/definitions/SDRDaemonChannelSourceSettings" + "DaemonSourceSettings" : { + "$ref" : "#/definitions/DaemonSourceSettings" }, "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" @@ -1675,6 +1675,95 @@ margin-bottom: 20px; } }, "description" : "DV serial device details" +}; + defs.DaemonSinkSettings = { + "properties" : { + "nbFECBlocks" : { + "type" : "integer", + "description" : "Number of FEC blocks per frame" + }, + "dataAddress" : { + "type" : "string", + "description" : "Receiving USB data address" + }, + "dataPort" : { + "type" : "integer", + "description" : "Receiving USB data port" + }, + "txDelay" : { + "type" : "integer", + "description" : "Minimum delay in ms between consecutive USB blocks transmissions" + } + }, + "description" : "Daemon channel sink settings" +}; + defs.DaemonSourceReport = { + "properties" : { + "queueLength" : { + "type" : "integer", + "description" : "Data read/write queue length in number of data frames" + }, + "queueSize" : { + "type" : "integer", + "description" : "Data read/write queue size in number of data frames" + }, + "samplesCount" : { + "type" : "integer", + "description" : "Absolute consumed samples count" + }, + "correctableErrorsCount" : { + "type" : "integer", + "description" : "Absolute number of correctable errors" + }, + "uncorrectableErrorsCount" : { + "type" : "integer", + "description" : "Absolute number of uncorrectable errors" + }, + "tvSec" : { + "type" : "integer", + "description" : "Counts timestamp seconds" + }, + "tvUSec" : { + "type" : "integer", + "description" : "Counts timestamp microseconds" + }, + "nbOriginalBlocks" : { + "type" : "integer", + "description" : "Number of original blocks per frame" + }, + "nbFECBlocks" : { + "type" : "integer", + "description" : "Number of FEC blocks per frame" + }, + "centerFreq" : { + "type" : "integer", + "description" : "Stream center frequency setting in kHz" + }, + "sampleRate" : { + "type" : "integer", + "description" : "Stream nominal sample rate in S/s" + } + }, + "description" : "Daemon channel source report" +}; + defs.DaemonSourceSettings = { + "properties" : { + "dataAddress" : { + "type" : "string", + "description" : "Remote USB data address" + }, + "dataPort" : { + "type" : "integer", + "description" : "Remote USB data port" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + } + }, + "description" : "Daemon channel source settings" }; defs.DaemonSummaryResponse = { "required" : [ "appname", "architecture", "dspRxBits", "dspTxBits", "os", "pid", "qtVersion", "version" ], @@ -3151,95 +3240,6 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" -}; - defs.SDRDaemonChannelSinkSettings = { - "properties" : { - "nbFECBlocks" : { - "type" : "integer", - "description" : "Number of FEC blocks per frame" - }, - "dataAddress" : { - "type" : "string", - "description" : "Receiving USB data address" - }, - "dataPort" : { - "type" : "integer", - "description" : "Receiving USB data port" - }, - "txDelay" : { - "type" : "integer", - "description" : "Minimum delay in ms between consecutive USB blocks transmissions" - } - }, - "description" : "Data handling details for SDRDaemon" -}; - defs.SDRDaemonChannelSourceReport = { - "properties" : { - "queueLength" : { - "type" : "integer", - "description" : "Data read/write queue length in number of data frames" - }, - "queueSize" : { - "type" : "integer", - "description" : "Data read/write queue size in number of data frames" - }, - "samplesCount" : { - "type" : "integer", - "description" : "Absolute consumed samples count" - }, - "correctableErrorsCount" : { - "type" : "integer", - "description" : "Absolute number of correctable errors" - }, - "uncorrectableErrorsCount" : { - "type" : "integer", - "description" : "Absolute number of uncorrectable errors" - }, - "tvSec" : { - "type" : "integer", - "description" : "Counts timestamp seconds" - }, - "tvUSec" : { - "type" : "integer", - "description" : "Counts timestamp microseconds" - }, - "nbOriginalBlocks" : { - "type" : "integer", - "description" : "Number of original blocks per frame" - }, - "nbFECBlocks" : { - "type" : "integer", - "description" : "Number of FEC blocks per frame" - }, - "centerFreq" : { - "type" : "integer", - "description" : "Stream center frequency setting in kHz" - }, - "sampleRate" : { - "type" : "integer", - "description" : "Stream nominal sample rate in S/s" - } - }, - "description" : "SDRDaemon channel source report" -}; - defs.SDRDaemonChannelSourceSettings = { - "properties" : { - "dataAddress" : { - "type" : "string", - "description" : "Remote USB data address" - }, - "dataPort" : { - "type" : "integer", - "description" : "Remote USB data port" - }, - "rgbColor" : { - "type" : "integer" - }, - "title" : { - "type" : "string" - } - }, - "description" : "Data handling details for SDRDaemon" }; defs.SDRPlayReport = { "properties" : { @@ -28716,7 +28716,7 @@ except ApiException as e:
    - Generated 2018-09-04T08:40:33.765+02:00 + Generated 2018-09-04T20:03:50.896+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSink.yaml b/sdrbase/resources/webapi/doc/swagger/include/DaemonSink.yaml similarity index 82% rename from sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSink.yaml rename to sdrbase/resources/webapi/doc/swagger/include/DaemonSink.yaml index 63c34423a..5e4c881d8 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSink.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/DaemonSink.yaml @@ -1,5 +1,5 @@ -SDRDaemonChannelSinkSettings: - description: "Data handling details for SDRDaemon" +DaemonSinkSettings: + description: "Daemon channel sink settings" properties: nbFECBlocks: description: "Number of FEC blocks per frame" diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/DaemonSource.yaml similarity index 88% rename from sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml rename to sdrbase/resources/webapi/doc/swagger/include/DaemonSource.yaml index b8e41090e..c1d96de6a 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonChannelSource.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/DaemonSource.yaml @@ -1,5 +1,5 @@ -SDRDaemonChannelSourceSettings: - description: "Data handling details for SDRDaemon" +DaemonSourceSettings: + description: "Daemon channel source settings" properties: dataAddress: description: "Remote USB data address" @@ -12,8 +12,8 @@ SDRDaemonChannelSourceSettings: title: type: string -SDRDaemonChannelSourceReport: - description: "SDRDaemon channel source report" +DaemonSourceReport: + description: "Daemon channel source report" properties: queueLength: description: "Data read/write queue length in number of data frames" diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index bbf8870b9..68c4791d3 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -2218,10 +2218,10 @@ definitions: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" - SDRDaemonChannelSinkSettings: - $ref: "/doc/swagger/include/SDRDaemonChannelSink.yaml#/SDRDaemonChannelSinkSettings" - SDRDaemonChannelSourceSettings: - $ref: "/doc/swagger/include/SDRDaemonChannelSource.yaml#/SDRDaemonChannelSourceSettings" + DaemonSinkSettings: + $ref: "/doc/swagger/include/DaemonSink.yaml#/DaemonSinkSettings" + DaemonSourceSettings: + $ref: "/doc/swagger/include/DaemonSource.yaml#/DaemonSourceSettings" SSBModSettings: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: @@ -2261,8 +2261,8 @@ definitions: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModReport" SSBDemodReport: $ref: "/doc/swagger/include/SSBDemod.yaml#/SSBDemodReport" - SDRDaemonChannelSourceReport: - $ref: "/doc/swagger/include/SDRDaemonChannelSource.yaml#/SDRDaemonChannelSourceReport" + DaemonSourceReport: + $ref: "/doc/swagger/include/DaemonSource.yaml#/DaemonSourceReport" SSBModReport: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 493472907..0c3c56fa5 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2150,10 +2150,10 @@ bool WebAPIRequestMapper::validateChannelSettings( { if (channelSettings.getTx() != 0) { - QJsonObject daemonChannelSourceSettingsJsonObject = jsonObject["SDRDaemonChannelSourceSettings"].toObject(); + QJsonObject daemonChannelSourceSettingsJsonObject = jsonObject["DaemonSourceSettings"].toObject(); channelSettingsKeys = daemonChannelSourceSettingsJsonObject.keys(); - channelSettings.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); - channelSettings.getSdrDaemonChannelSourceSettings()->fromJsonObject(daemonChannelSourceSettingsJsonObject); + channelSettings.setDaemonSourceSettings(new SWGSDRangel::SWGDaemonSourceSettings()); + channelSettings.getDaemonSourceSettings()->fromJsonObject(daemonChannelSourceSettingsJsonObject); return true; } else { @@ -2398,8 +2398,8 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setDsdDemodSettings(0); channelSettings.setNfmDemodSettings(0); channelSettings.setNfmModSettings(0); - channelSettings.setSdrDaemonChannelSinkSettings(0); - channelSettings.setSdrDaemonChannelSourceSettings(0); + channelSettings.setDaemonSinkSettings(0); + channelSettings.setDaemonSourceSettings(0); channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); channelSettings.setUdpSinkSettings(0); @@ -2419,7 +2419,7 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan channelReport.setDsdDemodReport(0); channelReport.setNfmDemodReport(0); channelReport.setNfmModReport(0); - channelReport.setSdrDaemonChannelSourceReport(0); + channelReport.setDaemonSourceReport(0); channelReport.setSsbDemodReport(0); channelReport.setSsbModReport(0); channelReport.setUdpSinkReport(0); diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index 23a7b5e03..c21e6e902 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -319,8 +319,8 @@ int SDRDaemonChannelSink::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - response.setSdrDaemonChannelSinkSettings(new SWGSDRangel::SWGSDRDaemonChannelSinkSettings()); - response.getSdrDaemonChannelSinkSettings()->init(); + response.setDaemonSinkSettings(new SWGSDRangel::SWGDaemonSinkSettings()); + response.getDaemonSinkSettings()->init(); webapiFormatChannelSettings(response, m_settings); return 200; } @@ -335,18 +335,18 @@ int SDRDaemonChannelSink::webapiSettingsPutPatch( if (channelSettingsKeys.contains("nbFECBlocks")) { - int nbFECBlocks = response.getSdrDaemonChannelSinkSettings()->getNbFecBlocks(); + int nbFECBlocks = response.getDaemonSinkSettings()->getNbFecBlocks(); if ((nbFECBlocks < 0) || (nbFECBlocks > 127)) { settings.m_nbFECBlocks = 8; } else { - settings.m_nbFECBlocks = response.getSdrDaemonChannelSinkSettings()->getNbFecBlocks(); + settings.m_nbFECBlocks = response.getDaemonSinkSettings()->getNbFecBlocks(); } } if (channelSettingsKeys.contains("txDelay")) { - int txDelay = response.getSdrDaemonChannelSinkSettings()->getTxDelay(); + int txDelay = response.getDaemonSinkSettings()->getTxDelay(); if (txDelay < 0) { settings.m_txDelay = 100; @@ -356,12 +356,12 @@ int SDRDaemonChannelSink::webapiSettingsPutPatch( } if (channelSettingsKeys.contains("dataAddress")) { - settings.m_dataAddress = *response.getSdrDaemonChannelSinkSettings()->getDataAddress(); + settings.m_dataAddress = *response.getDaemonSinkSettings()->getDataAddress(); } if (channelSettingsKeys.contains("dataPort")) { - int dataPort = response.getSdrDaemonChannelSinkSettings()->getDataPort(); + int dataPort = response.getDaemonSinkSettings()->getDataPort(); if ((dataPort < 1024) || (dataPort > 65535)) { settings.m_dataPort = 9090; @@ -387,14 +387,14 @@ int SDRDaemonChannelSink::webapiSettingsPutPatch( void SDRDaemonChannelSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSinkSettings& settings) { - response.getSdrDaemonChannelSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); - response.getSdrDaemonChannelSinkSettings()->setTxDelay(settings.m_txDelay); + response.getDaemonSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); + response.getDaemonSinkSettings()->setTxDelay(settings.m_txDelay); - if (response.getSdrDaemonChannelSinkSettings()->getDataAddress()) { - *response.getSdrDaemonChannelSinkSettings()->getDataAddress() = settings.m_dataAddress; + if (response.getDaemonSinkSettings()->getDataAddress()) { + *response.getDaemonSinkSettings()->getDataAddress() = settings.m_dataAddress; } else { - response.getSdrDaemonChannelSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); + response.getDaemonSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); } - response.getSdrDaemonChannelSinkSettings()->setDataPort(settings.m_dataPort); + response.getDaemonSinkSettings()->setDataPort(settings.m_dataPort); } diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index 580d2ecba..43f879cd5 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -333,8 +333,8 @@ int SDRDaemonChannelSource::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - response.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); - response.getSdrDaemonChannelSourceSettings()->init(); + response.setDaemonSourceSettings(new SWGSDRangel::SWGDaemonSourceSettings()); + response.getDaemonSourceSettings()->init(); webapiFormatChannelSettings(response, m_settings); return 200; } @@ -348,12 +348,12 @@ int SDRDaemonChannelSource::webapiSettingsPutPatch( SDRDaemonChannelSourceSettings settings = m_settings; if (channelSettingsKeys.contains("dataAddress")) { - settings.m_dataAddress = *response.getSdrDaemonChannelSourceSettings()->getDataAddress(); + settings.m_dataAddress = *response.getDaemonSourceSettings()->getDataAddress(); } if (channelSettingsKeys.contains("dataPort")) { - int dataPort = response.getSdrDaemonChannelSourceSettings()->getDataPort(); + int dataPort = response.getDaemonSourceSettings()->getDataPort(); if ((dataPort < 1024) || (dataPort > 65535)) { settings.m_dataPort = 9090; @@ -381,21 +381,21 @@ int SDRDaemonChannelSource::webapiReportGet( SWGSDRangel::SWGChannelReport& response, QString& errorMessage __attribute__((unused))) { - response.setSdrDaemonChannelSourceReport(new SWGSDRangel::SWGSDRDaemonChannelSourceReport()); - response.getSdrDaemonChannelSourceReport()->init(); + response.setDaemonSourceReport(new SWGSDRangel::SWGDaemonSourceReport()); + response.getDaemonSourceReport()->init(); webapiFormatChannelReport(response); return 200; } void SDRDaemonChannelSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings) { - if (response.getSdrDaemonChannelSourceSettings()->getDataAddress()) { - *response.getSdrDaemonChannelSourceSettings()->getDataAddress() = settings.m_dataAddress; + if (response.getDaemonSourceSettings()->getDataAddress()) { + *response.getDaemonSourceSettings()->getDataAddress() = settings.m_dataAddress; } else { - response.getSdrDaemonChannelSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); + response.getDaemonSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); } - response.getSdrDaemonChannelSourceSettings()->setDataPort(settings.m_dataPort); + response.getDaemonSourceSettings()->setDataPort(settings.m_dataPort); } void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) @@ -403,11 +403,11 @@ void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelRe struct timeval tv; gettimeofday(&tv, 0); - response.getSdrDaemonChannelSourceReport()->setTvSec(tv.tv_sec); - response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); - response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); - response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); - response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); - response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); - response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); + response.getDaemonSourceReport()->setTvSec(tv.tv_sec); + response.getDaemonSourceReport()->setTvUSec(tv.tv_usec); + response.getDaemonSourceReport()->setQueueSize(m_dataReadQueue.size()); + response.getDaemonSourceReport()->setQueueLength(m_dataReadQueue.length()); + response.getDaemonSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); + response.getDaemonSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); + response.getDaemonSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); } diff --git a/sdrdaemon/webapi/webapirequestmapper.cpp b/sdrdaemon/webapi/webapirequestmapper.cpp index 8b72965d1..25c582148 100644 --- a/sdrdaemon/webapi/webapirequestmapper.cpp +++ b/sdrdaemon/webapi/webapirequestmapper.cpp @@ -577,8 +577,8 @@ bool WebAPIRequestMapper::validateChannelSettings( { QJsonObject sdrDaemonChannelSinkSettingsJsonObject = jsonObject["SDRDaemonChannelSinkSettings"].toObject(); channelSettingsKeys = sdrDaemonChannelSinkSettingsJsonObject.keys(); - channelSettings.setSdrDaemonChannelSinkSettings(new SWGSDRangel::SWGSDRDaemonChannelSinkSettings()); - channelSettings.getSdrDaemonChannelSinkSettings()->fromJsonObject(sdrDaemonChannelSinkSettingsJsonObject); + channelSettings.setDaemonSinkSettings(new SWGSDRangel::SWGDaemonSinkSettings()); + channelSettings.getDaemonSinkSettings()->fromJsonObject(sdrDaemonChannelSinkSettingsJsonObject); return true; } else { @@ -1017,8 +1017,8 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setDsdDemodSettings(0); channelSettings.setNfmDemodSettings(0); channelSettings.setNfmModSettings(0); - channelSettings.setSdrDaemonChannelSinkSettings(0); - channelSettings.setSdrDaemonChannelSourceSettings(0); + channelSettings.setDaemonSinkSettings(0); + channelSettings.setDaemonSourceSettings(0); channelSettings.setSsbDemodSettings(0); channelSettings.setSsbModSettings(0); channelSettings.setUdpSinkSettings(0); @@ -1038,7 +1038,7 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan channelReport.setDsdDemodReport(0); channelReport.setNfmDemodReport(0); channelReport.setNfmModReport(0); - channelReport.setSdrDaemonChannelSourceReport(0); + channelReport.setDaemonSourceReport(0); channelReport.setSsbDemodReport(0); channelReport.setSsbModReport(0); channelReport.setUdpSinkReport(0); diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSink.yaml b/swagger/sdrangel/api/swagger/include/DaemonSink.yaml similarity index 82% rename from swagger/sdrangel/api/swagger/include/SDRDaemonChannelSink.yaml rename to swagger/sdrangel/api/swagger/include/DaemonSink.yaml index 63c34423a..5e4c881d8 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSink.yaml +++ b/swagger/sdrangel/api/swagger/include/DaemonSink.yaml @@ -1,5 +1,5 @@ -SDRDaemonChannelSinkSettings: - description: "Data handling details for SDRDaemon" +DaemonSinkSettings: + description: "Daemon channel sink settings" properties: nbFECBlocks: description: "Number of FEC blocks per frame" diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml b/swagger/sdrangel/api/swagger/include/DaemonSource.yaml similarity index 88% rename from swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml rename to swagger/sdrangel/api/swagger/include/DaemonSource.yaml index b8e41090e..c1d96de6a 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonChannelSource.yaml +++ b/swagger/sdrangel/api/swagger/include/DaemonSource.yaml @@ -1,5 +1,5 @@ -SDRDaemonChannelSourceSettings: - description: "Data handling details for SDRDaemon" +DaemonSourceSettings: + description: "Daemon channel source settings" properties: dataAddress: description: "Remote USB data address" @@ -12,8 +12,8 @@ SDRDaemonChannelSourceSettings: title: type: string -SDRDaemonChannelSourceReport: - description: "SDRDaemon channel source report" +DaemonSourceReport: + description: "Daemon channel source report" properties: queueLength: description: "Data read/write queue length in number of data frames" diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index fa7612f74..874d677dd 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -2218,10 +2218,10 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" - SDRDaemonChannelSinkSettings: - $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannelSink.yaml#/SDRDaemonChannelSinkSettings" - SDRDaemonChannelSourceSettings: - $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannelSource.yaml#/SDRDaemonChannelSourceSettings" + DaemonSinkSettings: + $ref: "http://localhost:8081/api/swagger/include/DaemonSink.yaml#/DaemonSinkSettings" + DaemonSourceSettings: + $ref: "http://localhost:8081/api/swagger/include/DaemonSource.yaml#/DaemonSourceSettings" SSBModSettings: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" SSBDemodSettings: @@ -2261,8 +2261,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModReport" SSBDemodReport: $ref: "http://localhost:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodReport" - SDRDaemonChannelSourceReport: - $ref: "http://localhost:8081/api/swagger/include/SDRDaemonChannelSource.yaml#/SDRDaemonChannelSourceReport" + DaemonSourceReport: + $ref: "http://localhost:8081/api/swagger/include/DaemonSource.yaml#/DaemonSourceReport" SSBModReport: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" UDPSinkReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 621e6e325..daaf4c9f7 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1416,8 +1416,8 @@ margin-bottom: 20px; "SSBDemodReport" : { "$ref" : "#/definitions/SSBDemodReport" }, - "SDRDaemonChannelSourceReport" : { - "$ref" : "#/definitions/SDRDaemonChannelSourceReport" + "DaemonSourceReport" : { + "$ref" : "#/definitions/DaemonSourceReport" }, "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" @@ -1470,11 +1470,11 @@ margin-bottom: 20px; "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" }, - "SDRDaemonChannelSinkSettings" : { - "$ref" : "#/definitions/SDRDaemonChannelSinkSettings" + "DaemonSinkSettings" : { + "$ref" : "#/definitions/DaemonSinkSettings" }, - "SDRDaemonChannelSourceSettings" : { - "$ref" : "#/definitions/SDRDaemonChannelSourceSettings" + "DaemonSourceSettings" : { + "$ref" : "#/definitions/DaemonSourceSettings" }, "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" @@ -1675,6 +1675,95 @@ margin-bottom: 20px; } }, "description" : "DV serial device details" +}; + defs.DaemonSinkSettings = { + "properties" : { + "nbFECBlocks" : { + "type" : "integer", + "description" : "Number of FEC blocks per frame" + }, + "dataAddress" : { + "type" : "string", + "description" : "Receiving USB data address" + }, + "dataPort" : { + "type" : "integer", + "description" : "Receiving USB data port" + }, + "txDelay" : { + "type" : "integer", + "description" : "Minimum delay in ms between consecutive USB blocks transmissions" + } + }, + "description" : "Daemon channel sink settings" +}; + defs.DaemonSourceReport = { + "properties" : { + "queueLength" : { + "type" : "integer", + "description" : "Data read/write queue length in number of data frames" + }, + "queueSize" : { + "type" : "integer", + "description" : "Data read/write queue size in number of data frames" + }, + "samplesCount" : { + "type" : "integer", + "description" : "Absolute consumed samples count" + }, + "correctableErrorsCount" : { + "type" : "integer", + "description" : "Absolute number of correctable errors" + }, + "uncorrectableErrorsCount" : { + "type" : "integer", + "description" : "Absolute number of uncorrectable errors" + }, + "tvSec" : { + "type" : "integer", + "description" : "Counts timestamp seconds" + }, + "tvUSec" : { + "type" : "integer", + "description" : "Counts timestamp microseconds" + }, + "nbOriginalBlocks" : { + "type" : "integer", + "description" : "Number of original blocks per frame" + }, + "nbFECBlocks" : { + "type" : "integer", + "description" : "Number of FEC blocks per frame" + }, + "centerFreq" : { + "type" : "integer", + "description" : "Stream center frequency setting in kHz" + }, + "sampleRate" : { + "type" : "integer", + "description" : "Stream nominal sample rate in S/s" + } + }, + "description" : "Daemon channel source report" +}; + defs.DaemonSourceSettings = { + "properties" : { + "dataAddress" : { + "type" : "string", + "description" : "Remote USB data address" + }, + "dataPort" : { + "type" : "integer", + "description" : "Remote USB data port" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + } + }, + "description" : "Daemon channel source settings" }; defs.DaemonSummaryResponse = { "required" : [ "appname", "architecture", "dspRxBits", "dspTxBits", "os", "pid", "qtVersion", "version" ], @@ -3151,95 +3240,6 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" -}; - defs.SDRDaemonChannelSinkSettings = { - "properties" : { - "nbFECBlocks" : { - "type" : "integer", - "description" : "Number of FEC blocks per frame" - }, - "dataAddress" : { - "type" : "string", - "description" : "Receiving USB data address" - }, - "dataPort" : { - "type" : "integer", - "description" : "Receiving USB data port" - }, - "txDelay" : { - "type" : "integer", - "description" : "Minimum delay in ms between consecutive USB blocks transmissions" - } - }, - "description" : "Data handling details for SDRDaemon" -}; - defs.SDRDaemonChannelSourceReport = { - "properties" : { - "queueLength" : { - "type" : "integer", - "description" : "Data read/write queue length in number of data frames" - }, - "queueSize" : { - "type" : "integer", - "description" : "Data read/write queue size in number of data frames" - }, - "samplesCount" : { - "type" : "integer", - "description" : "Absolute consumed samples count" - }, - "correctableErrorsCount" : { - "type" : "integer", - "description" : "Absolute number of correctable errors" - }, - "uncorrectableErrorsCount" : { - "type" : "integer", - "description" : "Absolute number of uncorrectable errors" - }, - "tvSec" : { - "type" : "integer", - "description" : "Counts timestamp seconds" - }, - "tvUSec" : { - "type" : "integer", - "description" : "Counts timestamp microseconds" - }, - "nbOriginalBlocks" : { - "type" : "integer", - "description" : "Number of original blocks per frame" - }, - "nbFECBlocks" : { - "type" : "integer", - "description" : "Number of FEC blocks per frame" - }, - "centerFreq" : { - "type" : "integer", - "description" : "Stream center frequency setting in kHz" - }, - "sampleRate" : { - "type" : "integer", - "description" : "Stream nominal sample rate in S/s" - } - }, - "description" : "SDRDaemon channel source report" -}; - defs.SDRDaemonChannelSourceSettings = { - "properties" : { - "dataAddress" : { - "type" : "string", - "description" : "Remote USB data address" - }, - "dataPort" : { - "type" : "integer", - "description" : "Remote USB data port" - }, - "rgbColor" : { - "type" : "integer" - }, - "title" : { - "type" : "string" - } - }, - "description" : "Data handling details for SDRDaemon" }; defs.SDRPlayReport = { "properties" : { @@ -28716,7 +28716,7 @@ except ApiException as e:
    - Generated 2018-09-04T08:40:33.765+02:00 + Generated 2018-09-04T20:03:50.896+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 520dc6d29..f2a5b33ea 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -48,8 +48,8 @@ SWGChannelReport::SWGChannelReport() { m_nfm_mod_report_isSet = false; ssb_demod_report = nullptr; m_ssb_demod_report_isSet = false; - sdr_daemon_channel_source_report = nullptr; - m_sdr_daemon_channel_source_report_isSet = false; + daemon_source_report = nullptr; + m_daemon_source_report_isSet = false; ssb_mod_report = nullptr; m_ssb_mod_report_isSet = false; udp_sink_report = nullptr; @@ -88,8 +88,8 @@ SWGChannelReport::init() { m_nfm_mod_report_isSet = false; ssb_demod_report = new SWGSSBDemodReport(); m_ssb_demod_report_isSet = false; - sdr_daemon_channel_source_report = new SWGSDRDaemonChannelSourceReport(); - m_sdr_daemon_channel_source_report_isSet = false; + daemon_source_report = new SWGDaemonSourceReport(); + m_daemon_source_report_isSet = false; ssb_mod_report = new SWGSSBModReport(); m_ssb_mod_report_isSet = false; udp_sink_report = new SWGUDPSinkReport(); @@ -132,8 +132,8 @@ SWGChannelReport::cleanup() { if(ssb_demod_report != nullptr) { delete ssb_demod_report; } - if(sdr_daemon_channel_source_report != nullptr) { - delete sdr_daemon_channel_source_report; + if(daemon_source_report != nullptr) { + delete daemon_source_report; } if(ssb_mod_report != nullptr) { delete ssb_mod_report; @@ -183,7 +183,7 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ssb_demod_report, pJson["SSBDemodReport"], "SWGSSBDemodReport", "SWGSSBDemodReport"); - ::SWGSDRangel::setValue(&sdr_daemon_channel_source_report, pJson["SDRDaemonChannelSourceReport"], "SWGSDRDaemonChannelSourceReport", "SWGSDRDaemonChannelSourceReport"); + ::SWGSDRangel::setValue(&daemon_source_report, pJson["DaemonSourceReport"], "SWGDaemonSourceReport", "SWGDaemonSourceReport"); ::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport"); @@ -241,8 +241,8 @@ SWGChannelReport::asJsonObject() { if((ssb_demod_report != nullptr) && (ssb_demod_report->isSet())){ toJsonValue(QString("SSBDemodReport"), ssb_demod_report, obj, QString("SWGSSBDemodReport")); } - if((sdr_daemon_channel_source_report != nullptr) && (sdr_daemon_channel_source_report->isSet())){ - toJsonValue(QString("SDRDaemonChannelSourceReport"), sdr_daemon_channel_source_report, obj, QString("SWGSDRDaemonChannelSourceReport")); + if((daemon_source_report != nullptr) && (daemon_source_report->isSet())){ + toJsonValue(QString("DaemonSourceReport"), daemon_source_report, obj, QString("SWGDaemonSourceReport")); } if((ssb_mod_report != nullptr) && (ssb_mod_report->isSet())){ toJsonValue(QString("SSBModReport"), ssb_mod_report, obj, QString("SWGSSBModReport")); @@ -363,14 +363,14 @@ SWGChannelReport::setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report) { this->m_ssb_demod_report_isSet = true; } -SWGSDRDaemonChannelSourceReport* -SWGChannelReport::getSdrDaemonChannelSourceReport() { - return sdr_daemon_channel_source_report; +SWGDaemonSourceReport* +SWGChannelReport::getDaemonSourceReport() { + return daemon_source_report; } void -SWGChannelReport::setSdrDaemonChannelSourceReport(SWGSDRDaemonChannelSourceReport* sdr_daemon_channel_source_report) { - this->sdr_daemon_channel_source_report = sdr_daemon_channel_source_report; - this->m_sdr_daemon_channel_source_report_isSet = true; +SWGChannelReport::setDaemonSourceReport(SWGDaemonSourceReport* daemon_source_report) { + this->daemon_source_report = daemon_source_report; + this->m_daemon_source_report_isSet = true; } SWGSSBModReport* @@ -438,7 +438,7 @@ SWGChannelReport::isSet(){ if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_demod_report != nullptr && ssb_demod_report->isSet()){ isObjectUpdated = true; break;} - if(sdr_daemon_channel_source_report != nullptr && sdr_daemon_channel_source_report->isSet()){ isObjectUpdated = true; break;} + if(daemon_source_report != nullptr && daemon_source_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} if(udp_sink_report != nullptr && udp_sink_report->isSet()){ isObjectUpdated = true; break;} if(udp_src_report != nullptr && udp_src_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index f6cd8f662..c090b4e2f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -27,9 +27,9 @@ #include "SWGATVModReport.h" #include "SWGBFMDemodReport.h" #include "SWGDSDDemodReport.h" +#include "SWGDaemonSourceReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" -#include "SWGSDRDaemonChannelSourceReport.h" #include "SWGSSBDemodReport.h" #include "SWGSSBModReport.h" #include "SWGUDPSinkReport.h" @@ -86,8 +86,8 @@ public: SWGSSBDemodReport* getSsbDemodReport(); void setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report); - SWGSDRDaemonChannelSourceReport* getSdrDaemonChannelSourceReport(); - void setSdrDaemonChannelSourceReport(SWGSDRDaemonChannelSourceReport* sdr_daemon_channel_source_report); + SWGDaemonSourceReport* getDaemonSourceReport(); + void setDaemonSourceReport(SWGDaemonSourceReport* daemon_source_report); SWGSSBModReport* getSsbModReport(); void setSsbModReport(SWGSSBModReport* ssb_mod_report); @@ -138,8 +138,8 @@ private: SWGSSBDemodReport* ssb_demod_report; bool m_ssb_demod_report_isSet; - SWGSDRDaemonChannelSourceReport* sdr_daemon_channel_source_report; - bool m_sdr_daemon_channel_source_report_isSet; + SWGDaemonSourceReport* daemon_source_report; + bool m_daemon_source_report_isSet; SWGSSBModReport* ssb_mod_report; bool m_ssb_mod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 37ef26b00..ab9d493c4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -46,10 +46,10 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; m_nfm_mod_settings_isSet = false; - sdr_daemon_channel_sink_settings = nullptr; - m_sdr_daemon_channel_sink_settings_isSet = false; - sdr_daemon_channel_source_settings = nullptr; - m_sdr_daemon_channel_source_settings_isSet = false; + daemon_sink_settings = nullptr; + m_daemon_sink_settings_isSet = false; + daemon_source_settings = nullptr; + m_daemon_source_settings_isSet = false; ssb_mod_settings = nullptr; m_ssb_mod_settings_isSet = false; ssb_demod_settings = nullptr; @@ -88,10 +88,10 @@ SWGChannelSettings::init() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); m_nfm_mod_settings_isSet = false; - sdr_daemon_channel_sink_settings = new SWGSDRDaemonChannelSinkSettings(); - m_sdr_daemon_channel_sink_settings_isSet = false; - sdr_daemon_channel_source_settings = new SWGSDRDaemonChannelSourceSettings(); - m_sdr_daemon_channel_source_settings_isSet = false; + daemon_sink_settings = new SWGDaemonSinkSettings(); + m_daemon_sink_settings_isSet = false; + daemon_source_settings = new SWGDaemonSourceSettings(); + m_daemon_source_settings_isSet = false; ssb_mod_settings = new SWGSSBModSettings(); m_ssb_mod_settings_isSet = false; ssb_demod_settings = new SWGSSBDemodSettings(); @@ -133,11 +133,11 @@ SWGChannelSettings::cleanup() { if(nfm_mod_settings != nullptr) { delete nfm_mod_settings; } - if(sdr_daemon_channel_sink_settings != nullptr) { - delete sdr_daemon_channel_sink_settings; + if(daemon_sink_settings != nullptr) { + delete daemon_sink_settings; } - if(sdr_daemon_channel_source_settings != nullptr) { - delete sdr_daemon_channel_source_settings; + if(daemon_source_settings != nullptr) { + delete daemon_source_settings; } if(ssb_mod_settings != nullptr) { delete ssb_mod_settings; @@ -188,9 +188,9 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); - ::SWGSDRangel::setValue(&sdr_daemon_channel_sink_settings, pJson["SDRDaemonChannelSinkSettings"], "SWGSDRDaemonChannelSinkSettings", "SWGSDRDaemonChannelSinkSettings"); + ::SWGSDRangel::setValue(&daemon_sink_settings, pJson["DaemonSinkSettings"], "SWGDaemonSinkSettings", "SWGDaemonSinkSettings"); - ::SWGSDRangel::setValue(&sdr_daemon_channel_source_settings, pJson["SDRDaemonChannelSourceSettings"], "SWGSDRDaemonChannelSourceSettings", "SWGSDRDaemonChannelSourceSettings"); + ::SWGSDRangel::setValue(&daemon_source_settings, pJson["DaemonSourceSettings"], "SWGDaemonSourceSettings", "SWGDaemonSourceSettings"); ::SWGSDRangel::setValue(&ssb_mod_settings, pJson["SSBModSettings"], "SWGSSBModSettings", "SWGSSBModSettings"); @@ -247,11 +247,11 @@ SWGChannelSettings::asJsonObject() { if((nfm_mod_settings != nullptr) && (nfm_mod_settings->isSet())){ toJsonValue(QString("NFMModSettings"), nfm_mod_settings, obj, QString("SWGNFMModSettings")); } - if((sdr_daemon_channel_sink_settings != nullptr) && (sdr_daemon_channel_sink_settings->isSet())){ - toJsonValue(QString("SDRDaemonChannelSinkSettings"), sdr_daemon_channel_sink_settings, obj, QString("SWGSDRDaemonChannelSinkSettings")); + if((daemon_sink_settings != nullptr) && (daemon_sink_settings->isSet())){ + toJsonValue(QString("DaemonSinkSettings"), daemon_sink_settings, obj, QString("SWGDaemonSinkSettings")); } - if((sdr_daemon_channel_source_settings != nullptr) && (sdr_daemon_channel_source_settings->isSet())){ - toJsonValue(QString("SDRDaemonChannelSourceSettings"), sdr_daemon_channel_source_settings, obj, QString("SWGSDRDaemonChannelSourceSettings")); + if((daemon_source_settings != nullptr) && (daemon_source_settings->isSet())){ + toJsonValue(QString("DaemonSourceSettings"), daemon_source_settings, obj, QString("SWGDaemonSourceSettings")); } if((ssb_mod_settings != nullptr) && (ssb_mod_settings->isSet())){ toJsonValue(QString("SSBModSettings"), ssb_mod_settings, obj, QString("SWGSSBModSettings")); @@ -365,24 +365,24 @@ SWGChannelSettings::setNfmModSettings(SWGNFMModSettings* nfm_mod_settings) { this->m_nfm_mod_settings_isSet = true; } -SWGSDRDaemonChannelSinkSettings* -SWGChannelSettings::getSdrDaemonChannelSinkSettings() { - return sdr_daemon_channel_sink_settings; +SWGDaemonSinkSettings* +SWGChannelSettings::getDaemonSinkSettings() { + return daemon_sink_settings; } void -SWGChannelSettings::setSdrDaemonChannelSinkSettings(SWGSDRDaemonChannelSinkSettings* sdr_daemon_channel_sink_settings) { - this->sdr_daemon_channel_sink_settings = sdr_daemon_channel_sink_settings; - this->m_sdr_daemon_channel_sink_settings_isSet = true; +SWGChannelSettings::setDaemonSinkSettings(SWGDaemonSinkSettings* daemon_sink_settings) { + this->daemon_sink_settings = daemon_sink_settings; + this->m_daemon_sink_settings_isSet = true; } -SWGSDRDaemonChannelSourceSettings* -SWGChannelSettings::getSdrDaemonChannelSourceSettings() { - return sdr_daemon_channel_source_settings; +SWGDaemonSourceSettings* +SWGChannelSettings::getDaemonSourceSettings() { + return daemon_source_settings; } void -SWGChannelSettings::setSdrDaemonChannelSourceSettings(SWGSDRDaemonChannelSourceSettings* sdr_daemon_channel_source_settings) { - this->sdr_daemon_channel_source_settings = sdr_daemon_channel_source_settings; - this->m_sdr_daemon_channel_source_settings_isSet = true; +SWGChannelSettings::setDaemonSourceSettings(SWGDaemonSourceSettings* daemon_source_settings) { + this->daemon_source_settings = daemon_source_settings; + this->m_daemon_source_settings_isSet = true; } SWGSSBModSettings* @@ -459,8 +459,8 @@ SWGChannelSettings::isSet(){ if(dsd_demod_settings != nullptr && dsd_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} - if(sdr_daemon_channel_sink_settings != nullptr && sdr_daemon_channel_sink_settings->isSet()){ isObjectUpdated = true; break;} - if(sdr_daemon_channel_source_settings != nullptr && sdr_daemon_channel_source_settings->isSet()){ isObjectUpdated = true; break;} + if(daemon_sink_settings != nullptr && daemon_sink_settings->isSet()){ isObjectUpdated = true; break;} + if(daemon_source_settings != nullptr && daemon_source_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_demod_settings != nullptr && ssb_demod_settings->isSet()){ isObjectUpdated = true; break;} if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 90bdf4c0e..c6c536674 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -27,10 +27,10 @@ #include "SWGATVModSettings.h" #include "SWGBFMDemodSettings.h" #include "SWGDSDDemodSettings.h" +#include "SWGDaemonSinkSettings.h" +#include "SWGDaemonSourceSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" -#include "SWGSDRDaemonChannelSinkSettings.h" -#include "SWGSDRDaemonChannelSourceSettings.h" #include "SWGSSBDemodSettings.h" #include "SWGSSBModSettings.h" #include "SWGUDPSinkSettings.h" @@ -84,11 +84,11 @@ public: SWGNFMModSettings* getNfmModSettings(); void setNfmModSettings(SWGNFMModSettings* nfm_mod_settings); - SWGSDRDaemonChannelSinkSettings* getSdrDaemonChannelSinkSettings(); - void setSdrDaemonChannelSinkSettings(SWGSDRDaemonChannelSinkSettings* sdr_daemon_channel_sink_settings); + SWGDaemonSinkSettings* getDaemonSinkSettings(); + void setDaemonSinkSettings(SWGDaemonSinkSettings* daemon_sink_settings); - SWGSDRDaemonChannelSourceSettings* getSdrDaemonChannelSourceSettings(); - void setSdrDaemonChannelSourceSettings(SWGSDRDaemonChannelSourceSettings* sdr_daemon_channel_source_settings); + SWGDaemonSourceSettings* getDaemonSourceSettings(); + void setDaemonSourceSettings(SWGDaemonSourceSettings* daemon_source_settings); SWGSSBModSettings* getSsbModSettings(); void setSsbModSettings(SWGSSBModSettings* ssb_mod_settings); @@ -139,11 +139,11 @@ private: SWGNFMModSettings* nfm_mod_settings; bool m_nfm_mod_settings_isSet; - SWGSDRDaemonChannelSinkSettings* sdr_daemon_channel_sink_settings; - bool m_sdr_daemon_channel_sink_settings_isSet; + SWGDaemonSinkSettings* daemon_sink_settings; + bool m_daemon_sink_settings_isSet; - SWGSDRDaemonChannelSourceSettings* sdr_daemon_channel_source_settings; - bool m_sdr_daemon_channel_source_settings_isSet; + SWGDaemonSourceSettings* daemon_source_settings; + bool m_daemon_source_settings_isSet; SWGSSBModSettings* ssb_mod_settings; bool m_ssb_mod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp new file mode 100644 index 000000000..9f848ff0f --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp @@ -0,0 +1,171 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGDaemonSinkSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDaemonSinkSettings::SWGDaemonSinkSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDaemonSinkSettings::SWGDaemonSinkSettings() { + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + data_address = nullptr; + m_data_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + tx_delay = 0; + m_tx_delay_isSet = false; +} + +SWGDaemonSinkSettings::~SWGDaemonSinkSettings() { + this->cleanup(); +} + +void +SWGDaemonSinkSettings::init() { + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + data_address = new QString(""); + m_data_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + tx_delay = 0; + m_tx_delay_isSet = false; +} + +void +SWGDaemonSinkSettings::cleanup() { + + if(data_address != nullptr) { + delete data_address; + } + + +} + +SWGDaemonSinkSettings* +SWGDaemonSinkSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDaemonSinkSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&tx_delay, pJson["txDelay"], "qint32", ""); + +} + +QString +SWGDaemonSinkSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDaemonSinkSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_nb_fec_blocks_isSet){ + obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); + } + if(data_address != nullptr && *data_address != QString("")){ + toJsonValue(QString("dataAddress"), data_address, obj, QString("QString")); + } + if(m_data_port_isSet){ + obj->insert("dataPort", QJsonValue(data_port)); + } + if(m_tx_delay_isSet){ + obj->insert("txDelay", QJsonValue(tx_delay)); + } + + return obj; +} + +qint32 +SWGDaemonSinkSettings::getNbFecBlocks() { + return nb_fec_blocks; +} +void +SWGDaemonSinkSettings::setNbFecBlocks(qint32 nb_fec_blocks) { + this->nb_fec_blocks = nb_fec_blocks; + this->m_nb_fec_blocks_isSet = true; +} + +QString* +SWGDaemonSinkSettings::getDataAddress() { + return data_address; +} +void +SWGDaemonSinkSettings::setDataAddress(QString* data_address) { + this->data_address = data_address; + this->m_data_address_isSet = true; +} + +qint32 +SWGDaemonSinkSettings::getDataPort() { + return data_port; +} +void +SWGDaemonSinkSettings::setDataPort(qint32 data_port) { + this->data_port = data_port; + this->m_data_port_isSet = true; +} + +qint32 +SWGDaemonSinkSettings::getTxDelay() { + return tx_delay; +} +void +SWGDaemonSinkSettings::setTxDelay(qint32 tx_delay) { + this->tx_delay = tx_delay; + this->m_tx_delay_isSet = true; +} + + +bool +SWGDaemonSinkSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} + if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} + if(m_data_port_isSet){ isObjectUpdated = true; break;} + if(m_tx_delay_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h new file mode 100644 index 000000000..38750cee9 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h @@ -0,0 +1,77 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGDaemonSinkSettings.h + * + * Daemon channel sink settings + */ + +#ifndef SWGDaemonSinkSettings_H_ +#define SWGDaemonSinkSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDaemonSinkSettings: public SWGObject { +public: + SWGDaemonSinkSettings(); + SWGDaemonSinkSettings(QString* json); + virtual ~SWGDaemonSinkSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDaemonSinkSettings* fromJson(QString &jsonString) override; + + qint32 getNbFecBlocks(); + void setNbFecBlocks(qint32 nb_fec_blocks); + + QString* getDataAddress(); + void setDataAddress(QString* data_address); + + qint32 getDataPort(); + void setDataPort(qint32 data_port); + + qint32 getTxDelay(); + void setTxDelay(qint32 tx_delay); + + + virtual bool isSet() override; + +private: + qint32 nb_fec_blocks; + bool m_nb_fec_blocks_isSet; + + QString* data_address; + bool m_data_address_isSet; + + qint32 data_port; + bool m_data_port_isSet; + + qint32 tx_delay; + bool m_tx_delay_isSet; + +}; + +} + +#endif /* SWGDaemonSinkSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp new file mode 100644 index 000000000..b2ba885d2 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp @@ -0,0 +1,316 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGDaemonSourceReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDaemonSourceReport::SWGDaemonSourceReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDaemonSourceReport::SWGDaemonSourceReport() { + queue_length = 0; + m_queue_length_isSet = false; + queue_size = 0; + m_queue_size_isSet = false; + samples_count = 0; + m_samples_count_isSet = false; + correctable_errors_count = 0; + m_correctable_errors_count_isSet = false; + uncorrectable_errors_count = 0; + m_uncorrectable_errors_count_isSet = false; + tv_sec = 0; + m_tv_sec_isSet = false; + tv_u_sec = 0; + m_tv_u_sec_isSet = false; + nb_original_blocks = 0; + m_nb_original_blocks_isSet = false; + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + center_freq = 0; + m_center_freq_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; +} + +SWGDaemonSourceReport::~SWGDaemonSourceReport() { + this->cleanup(); +} + +void +SWGDaemonSourceReport::init() { + queue_length = 0; + m_queue_length_isSet = false; + queue_size = 0; + m_queue_size_isSet = false; + samples_count = 0; + m_samples_count_isSet = false; + correctable_errors_count = 0; + m_correctable_errors_count_isSet = false; + uncorrectable_errors_count = 0; + m_uncorrectable_errors_count_isSet = false; + tv_sec = 0; + m_tv_sec_isSet = false; + tv_u_sec = 0; + m_tv_u_sec_isSet = false; + nb_original_blocks = 0; + m_nb_original_blocks_isSet = false; + nb_fec_blocks = 0; + m_nb_fec_blocks_isSet = false; + center_freq = 0; + m_center_freq_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; +} + +void +SWGDaemonSourceReport::cleanup() { + + + + + + + + + + + +} + +SWGDaemonSourceReport* +SWGDaemonSourceReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDaemonSourceReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&queue_length, pJson["queueLength"], "qint32", ""); + + ::SWGSDRangel::setValue(&queue_size, pJson["queueSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&samples_count, pJson["samplesCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&correctable_errors_count, pJson["correctableErrorsCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&uncorrectable_errors_count, pJson["uncorrectableErrorsCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&tv_sec, pJson["tvSec"], "qint32", ""); + + ::SWGSDRangel::setValue(&tv_u_sec, pJson["tvUSec"], "qint32", ""); + + ::SWGSDRangel::setValue(&nb_original_blocks, pJson["nbOriginalBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); + + ::SWGSDRangel::setValue(¢er_freq, pJson["centerFreq"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + +} + +QString +SWGDaemonSourceReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDaemonSourceReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_queue_length_isSet){ + obj->insert("queueLength", QJsonValue(queue_length)); + } + if(m_queue_size_isSet){ + obj->insert("queueSize", QJsonValue(queue_size)); + } + if(m_samples_count_isSet){ + obj->insert("samplesCount", QJsonValue(samples_count)); + } + if(m_correctable_errors_count_isSet){ + obj->insert("correctableErrorsCount", QJsonValue(correctable_errors_count)); + } + if(m_uncorrectable_errors_count_isSet){ + obj->insert("uncorrectableErrorsCount", QJsonValue(uncorrectable_errors_count)); + } + if(m_tv_sec_isSet){ + obj->insert("tvSec", QJsonValue(tv_sec)); + } + if(m_tv_u_sec_isSet){ + obj->insert("tvUSec", QJsonValue(tv_u_sec)); + } + if(m_nb_original_blocks_isSet){ + obj->insert("nbOriginalBlocks", QJsonValue(nb_original_blocks)); + } + if(m_nb_fec_blocks_isSet){ + obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); + } + if(m_center_freq_isSet){ + obj->insert("centerFreq", QJsonValue(center_freq)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + + return obj; +} + +qint32 +SWGDaemonSourceReport::getQueueLength() { + return queue_length; +} +void +SWGDaemonSourceReport::setQueueLength(qint32 queue_length) { + this->queue_length = queue_length; + this->m_queue_length_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getQueueSize() { + return queue_size; +} +void +SWGDaemonSourceReport::setQueueSize(qint32 queue_size) { + this->queue_size = queue_size; + this->m_queue_size_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getSamplesCount() { + return samples_count; +} +void +SWGDaemonSourceReport::setSamplesCount(qint32 samples_count) { + this->samples_count = samples_count; + this->m_samples_count_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getCorrectableErrorsCount() { + return correctable_errors_count; +} +void +SWGDaemonSourceReport::setCorrectableErrorsCount(qint32 correctable_errors_count) { + this->correctable_errors_count = correctable_errors_count; + this->m_correctable_errors_count_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getUncorrectableErrorsCount() { + return uncorrectable_errors_count; +} +void +SWGDaemonSourceReport::setUncorrectableErrorsCount(qint32 uncorrectable_errors_count) { + this->uncorrectable_errors_count = uncorrectable_errors_count; + this->m_uncorrectable_errors_count_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getTvSec() { + return tv_sec; +} +void +SWGDaemonSourceReport::setTvSec(qint32 tv_sec) { + this->tv_sec = tv_sec; + this->m_tv_sec_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getTvUSec() { + return tv_u_sec; +} +void +SWGDaemonSourceReport::setTvUSec(qint32 tv_u_sec) { + this->tv_u_sec = tv_u_sec; + this->m_tv_u_sec_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getNbOriginalBlocks() { + return nb_original_blocks; +} +void +SWGDaemonSourceReport::setNbOriginalBlocks(qint32 nb_original_blocks) { + this->nb_original_blocks = nb_original_blocks; + this->m_nb_original_blocks_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getNbFecBlocks() { + return nb_fec_blocks; +} +void +SWGDaemonSourceReport::setNbFecBlocks(qint32 nb_fec_blocks) { + this->nb_fec_blocks = nb_fec_blocks; + this->m_nb_fec_blocks_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getCenterFreq() { + return center_freq; +} +void +SWGDaemonSourceReport::setCenterFreq(qint32 center_freq) { + this->center_freq = center_freq; + this->m_center_freq_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getSampleRate() { + return sample_rate; +} +void +SWGDaemonSourceReport::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + + +bool +SWGDaemonSourceReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_queue_length_isSet){ isObjectUpdated = true; break;} + if(m_queue_size_isSet){ isObjectUpdated = true; break;} + if(m_samples_count_isSet){ isObjectUpdated = true; break;} + if(m_correctable_errors_count_isSet){ isObjectUpdated = true; break;} + if(m_uncorrectable_errors_count_isSet){ isObjectUpdated = true; break;} + if(m_tv_sec_isSet){ isObjectUpdated = true; break;} + if(m_tv_u_sec_isSet){ isObjectUpdated = true; break;} + if(m_nb_original_blocks_isSet){ isObjectUpdated = true; break;} + if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} + if(m_center_freq_isSet){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h new file mode 100644 index 000000000..cce8823e6 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h @@ -0,0 +1,118 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGDaemonSourceReport.h + * + * Daemon channel source report + */ + +#ifndef SWGDaemonSourceReport_H_ +#define SWGDaemonSourceReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDaemonSourceReport: public SWGObject { +public: + SWGDaemonSourceReport(); + SWGDaemonSourceReport(QString* json); + virtual ~SWGDaemonSourceReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDaemonSourceReport* fromJson(QString &jsonString) override; + + qint32 getQueueLength(); + void setQueueLength(qint32 queue_length); + + qint32 getQueueSize(); + void setQueueSize(qint32 queue_size); + + qint32 getSamplesCount(); + void setSamplesCount(qint32 samples_count); + + qint32 getCorrectableErrorsCount(); + void setCorrectableErrorsCount(qint32 correctable_errors_count); + + qint32 getUncorrectableErrorsCount(); + void setUncorrectableErrorsCount(qint32 uncorrectable_errors_count); + + qint32 getTvSec(); + void setTvSec(qint32 tv_sec); + + qint32 getTvUSec(); + void setTvUSec(qint32 tv_u_sec); + + qint32 getNbOriginalBlocks(); + void setNbOriginalBlocks(qint32 nb_original_blocks); + + qint32 getNbFecBlocks(); + void setNbFecBlocks(qint32 nb_fec_blocks); + + qint32 getCenterFreq(); + void setCenterFreq(qint32 center_freq); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + + virtual bool isSet() override; + +private: + qint32 queue_length; + bool m_queue_length_isSet; + + qint32 queue_size; + bool m_queue_size_isSet; + + qint32 samples_count; + bool m_samples_count_isSet; + + qint32 correctable_errors_count; + bool m_correctable_errors_count_isSet; + + qint32 uncorrectable_errors_count; + bool m_uncorrectable_errors_count_isSet; + + qint32 tv_sec; + bool m_tv_sec_isSet; + + qint32 tv_u_sec; + bool m_tv_u_sec_isSet; + + qint32 nb_original_blocks; + bool m_nb_original_blocks_isSet; + + qint32 nb_fec_blocks; + bool m_nb_fec_blocks_isSet; + + qint32 center_freq; + bool m_center_freq_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + +}; + +} + +#endif /* SWGDaemonSourceReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.cpp new file mode 100644 index 000000000..006eaf2be --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.cpp @@ -0,0 +1,173 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGDaemonSourceSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDaemonSourceSettings::SWGDaemonSourceSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDaemonSourceSettings::SWGDaemonSourceSettings() { + data_address = nullptr; + m_data_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; +} + +SWGDaemonSourceSettings::~SWGDaemonSourceSettings() { + this->cleanup(); +} + +void +SWGDaemonSourceSettings::init() { + data_address = new QString(""); + m_data_address_isSet = false; + data_port = 0; + m_data_port_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; +} + +void +SWGDaemonSourceSettings::cleanup() { + if(data_address != nullptr) { + delete data_address; + } + + + if(title != nullptr) { + delete title; + } +} + +SWGDaemonSourceSettings* +SWGDaemonSourceSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDaemonSourceSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + +} + +QString +SWGDaemonSourceSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDaemonSourceSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(data_address != nullptr && *data_address != QString("")){ + toJsonValue(QString("dataAddress"), data_address, obj, QString("QString")); + } + if(m_data_port_isSet){ + obj->insert("dataPort", QJsonValue(data_port)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + + return obj; +} + +QString* +SWGDaemonSourceSettings::getDataAddress() { + return data_address; +} +void +SWGDaemonSourceSettings::setDataAddress(QString* data_address) { + this->data_address = data_address; + this->m_data_address_isSet = true; +} + +qint32 +SWGDaemonSourceSettings::getDataPort() { + return data_port; +} +void +SWGDaemonSourceSettings::setDataPort(qint32 data_port) { + this->data_port = data_port; + this->m_data_port_isSet = true; +} + +qint32 +SWGDaemonSourceSettings::getRgbColor() { + return rgb_color; +} +void +SWGDaemonSourceSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGDaemonSourceSettings::getTitle() { + return title; +} +void +SWGDaemonSourceSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + + +bool +SWGDaemonSourceSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} + if(m_data_port_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.h new file mode 100644 index 000000000..a9b0aa714 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceSettings.h @@ -0,0 +1,77 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 4.1.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGDaemonSourceSettings.h + * + * Daemon channel source settings + */ + +#ifndef SWGDaemonSourceSettings_H_ +#define SWGDaemonSourceSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDaemonSourceSettings: public SWGObject { +public: + SWGDaemonSourceSettings(); + SWGDaemonSourceSettings(QString* json); + virtual ~SWGDaemonSourceSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDaemonSourceSettings* fromJson(QString &jsonString) override; + + QString* getDataAddress(); + void setDataAddress(QString* data_address); + + qint32 getDataPort(); + void setDataPort(qint32 data_port); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + + virtual bool isSet() override; + +private: + QString* data_address; + bool m_data_address_isSet; + + qint32 data_port; + bool m_data_port_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + +}; + +} + +#endif /* SWGDaemonSourceSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 48555ccd4..a4e482e76 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -42,6 +42,9 @@ #include "SWGDSDDemodSettings.h" #include "SWGDVSeralDevices.h" #include "SWGDVSerialDevice.h" +#include "SWGDaemonSinkSettings.h" +#include "SWGDaemonSourceReport.h" +#include "SWGDaemonSourceSettings.h" #include "SWGDaemonSummaryResponse.h" #include "SWGDeviceListItem.h" #include "SWGDeviceReport.h" @@ -89,9 +92,6 @@ #include "SWGRDSReport_altFrequencies.h" #include "SWGRtlSdrReport.h" #include "SWGRtlSdrSettings.h" -#include "SWGSDRDaemonChannelSinkSettings.h" -#include "SWGSDRDaemonChannelSourceReport.h" -#include "SWGSDRDaemonChannelSourceSettings.h" #include "SWGSDRPlayReport.h" #include "SWGSDRPlaySettings.h" #include "SWGSDRdaemonSinkReport.h" @@ -202,6 +202,15 @@ namespace SWGSDRangel { if(QString("SWGDVSerialDevice").compare(type) == 0) { return new SWGDVSerialDevice(); } + if(QString("SWGDaemonSinkSettings").compare(type) == 0) { + return new SWGDaemonSinkSettings(); + } + if(QString("SWGDaemonSourceReport").compare(type) == 0) { + return new SWGDaemonSourceReport(); + } + if(QString("SWGDaemonSourceSettings").compare(type) == 0) { + return new SWGDaemonSourceSettings(); + } if(QString("SWGDaemonSummaryResponse").compare(type) == 0) { return new SWGDaemonSummaryResponse(); } @@ -343,15 +352,6 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } - if(QString("SWGSDRDaemonChannelSinkSettings").compare(type) == 0) { - return new SWGSDRDaemonChannelSinkSettings(); - } - if(QString("SWGSDRDaemonChannelSourceReport").compare(type) == 0) { - return new SWGSDRDaemonChannelSourceReport(); - } - if(QString("SWGSDRDaemonChannelSourceSettings").compare(type) == 0) { - return new SWGSDRDaemonChannelSourceSettings(); - } if(QString("SWGSDRPlayReport").compare(type) == 0) { return new SWGSDRPlayReport(); } From dc51f96b3ff16bec5db81ccddd61481e2236b7cb Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 4 Sep 2018 22:28:10 +0200 Subject: [PATCH 697/956] SDRDaemonSinkOutput: set initial conditions of sample rate control at start --- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index d6f84c3d5..026e1c112 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -91,6 +91,9 @@ bool SDRdaemonSinkOutput::start() m_sdrDaemonSinkThread->connectTimer(m_masterTimer); m_sdrDaemonSinkThread->startWork(); + m_lastRemoteTimestampRateCorrection = 0; + m_lastTimestampRateCorrection = 0; + double delay = ((127*127*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); @@ -542,6 +545,8 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) { m_lastRemoteTimestampRateCorrection = remoteTimestampUs; m_lastTimestampRateCorrection = timestampUs; + m_nbRemoteSamplesSinceRateCorrection = 0; + m_nbSamplesSinceRateCorrection = 0; } else { From 96e7d49fbe3ddae200fa5778a69e1c249eac3938 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 5 Sep 2018 01:32:29 +0200 Subject: [PATCH 698/956] DaemonSink (1) --- plugins/channelrx/CMakeLists.txt | 6 + plugins/channelrx/daemonsink/CMakeLists.txt | 57 +++ plugins/channelrx/daemonsink/daemonsink.cpp | 400 ++++++++++++++++++ plugins/channelrx/daemonsink/daemonsink.h | 139 ++++++ plugins/channelrx/daemonsink/daemonsinkgui.ui | 300 +++++++++++++ .../daemonsink/daemonsinksettings.cpp | 96 +++++ .../channelrx/daemonsink/daemonsinksettings.h | 43 ++ .../channelrx/daemonsink/daemonsinkthread.cpp | 198 +++++++++ .../channelrx/daemonsink/daemonsinkthread.h | 86 ++++ .../sdrdaemonsink/sdrdaemonsinkgui.ui | 2 +- 10 files changed, 1326 insertions(+), 1 deletion(-) create mode 100644 plugins/channelrx/daemonsink/CMakeLists.txt create mode 100644 plugins/channelrx/daemonsink/daemonsink.cpp create mode 100644 plugins/channelrx/daemonsink/daemonsink.h create mode 100644 plugins/channelrx/daemonsink/daemonsinkgui.ui create mode 100644 plugins/channelrx/daemonsink/daemonsinksettings.cpp create mode 100644 plugins/channelrx/daemonsink/daemonsinksettings.h create mode 100644 plugins/channelrx/daemonsink/daemonsinkthread.cpp create mode 100644 plugins/channelrx/daemonsink/daemonsinkthread.h 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 From dcd8f94931d5cad6ac09736c603fc3130b8df1c5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 5 Sep 2018 08:44:14 +0200 Subject: [PATCH 699/956] DaemonSink (2) --- plugins/channelrx/daemonsink/CMakeLists.txt | 8 +- plugins/channelrx/daemonsink/daemonsink.cpp | 7 + plugins/channelrx/daemonsink/daemonsink.h | 21 ++ .../channelrx/daemonsink/daemonsinkgui.cpp | 291 ++++++++++++++++++ plugins/channelrx/daemonsink/daemonsinkgui.h | 97 ++++++ plugins/channelrx/daemonsink/daemonsinkgui.ui | 2 +- .../channelrx/daemonsink/daemonsinkplugin.cpp | 81 +++++ .../channelrx/daemonsink/daemonsinkplugin.h | 48 +++ .../daemonsink/daemonsinksettings.cpp | 7 + .../channelrx/daemonsink/daemonsinksettings.h | 5 + .../channeltx/daemonsrc/daemonsrcsettings.cpp | 2 +- 11 files changed, 563 insertions(+), 6 deletions(-) create mode 100644 plugins/channelrx/daemonsink/daemonsinkgui.cpp create mode 100644 plugins/channelrx/daemonsink/daemonsinkgui.h create mode 100644 plugins/channelrx/daemonsink/daemonsinkplugin.cpp create mode 100644 plugins/channelrx/daemonsink/daemonsinkplugin.h diff --git a/plugins/channelrx/daemonsink/CMakeLists.txt b/plugins/channelrx/daemonsink/CMakeLists.txt index 5c52986ce..8a21d9f50 100644 --- a/plugins/channelrx/daemonsink/CMakeLists.txt +++ b/plugins/channelrx/daemonsink/CMakeLists.txt @@ -4,18 +4,18 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(daemonsink_SOURCES daemonsink.cpp -# daemonsinkgui.cpp + daemonsinkgui.cpp daemonsinksettings.cpp daemonsinkthread.cpp -# daemonsinkplugin.cpp + daemonsinkplugin.cpp ) set(daemonsink_HEADERS daemonsink.h -# daemonsinkgui.h + daemonsinkgui.h daemonsinksettings.h daemonsinkthread.h -# daemonsinkplugin.h + daemonsinkplugin.h ) set(daemonsink_FORMS diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index b30e140ae..206860aa6 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -36,6 +36,7 @@ #include "daemonsink.h" MESSAGE_CLASS_DEFINITION(DaemonSink::MsgConfigureDaemonSink, Message) +MESSAGE_CLASS_DEFINITION(DaemonSink::MsgSampleRateNotification, Message) const QString DaemonSink::m_channelIdURI = "sdrangel.channel.daemonsink"; const QString DaemonSink::m_channelId = "DaemonSink"; @@ -237,6 +238,12 @@ bool DaemonSink::handleMessage(const Message& cmd __attribute__((unused))) setSampleRate(notif.getSampleRate()); } + if (m_guiMessageQueue) + { + MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate()); + m_guiMessageQueue->push(msg); + } + return true; } else if (DSPSignalNotification::match(cmd)) diff --git a/plugins/channelrx/daemonsink/daemonsink.h b/plugins/channelrx/daemonsink/daemonsink.h index 247f0e1d9..8b0e38c1a 100644 --- a/plugins/channelrx/daemonsink/daemonsink.h +++ b/plugins/channelrx/daemonsink/daemonsink.h @@ -23,6 +23,7 @@ #ifndef INCLUDE_DAEMONSINK_H_ #define INCLUDE_DAEMONSINK_H_ +#include #include #include "cm256.h" @@ -64,6 +65,26 @@ public: { } }; + class MsgSampleRateNotification : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgSampleRateNotification* create(int sampleRate) { + return new MsgSampleRateNotification(sampleRate); + } + + int getSampleRate() const { return m_sampleRate; } + + private: + + MsgSampleRateNotification(int sampleRate) : + Message(), + m_sampleRate(sampleRate) + { } + + int m_sampleRate; + }; + DaemonSink(DeviceSourceAPI *deviceAPI); virtual ~DaemonSink(); virtual void destroy() { delete this; } diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp new file mode 100644 index 000000000..908d80163 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -0,0 +1,291 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 "device/devicesinkapi.h" +#include "device/deviceuiset.h" +#include "gui/basicchannelsettingsdialog.h" +#include "mainwindow.h" + +#include "daemonsink.h" +#include "ui_daemonsinkgui.h" +#include "daemonsinkgui.h" + +DaemonSinkGUI* DaemonSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelRx) +{ + DaemonSinkGUI* gui = new DaemonSinkGUI(pluginAPI, deviceUISet, channelRx); + return gui; +} + +void DaemonSinkGUI::destroy() +{ + delete this; +} + +void DaemonSinkGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString DaemonSinkGUI::getName() const +{ + return objectName(); +} + +qint64 DaemonSinkGUI::getCenterFrequency() const { + return 0; +} + +void DaemonSinkGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ +} + +void DaemonSinkGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray DaemonSinkGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool DaemonSinkGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool DaemonSinkGUI::handleMessage(const Message& message) +{ + if (DaemonSink::MsgSampleRateNotification::match(message)) + { + DaemonSink::MsgSampleRateNotification& notif = (DaemonSink::MsgSampleRateNotification&) message; + m_channelMarker.setBandwidth(notif.getSampleRate()); + m_sampleRate = notif.getSampleRate(); + return true; + } + else if (DaemonSink::MsgConfigureDaemonSink::match(message)) + { + const DaemonSink::MsgConfigureDaemonSink& cfg = (DaemonSink::MsgConfigureDaemonSink&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +DaemonSinkGUI::DaemonSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelrx, QWidget* parent) : + RollupWidget(parent), + ui(new Ui::DaemonSinkGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_sampleRate(0), + m_tickCount(0) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + + m_daemonSink = (DaemonSink*) channelrx; + m_daemonSink->setMessageQueueToGUI(getInputMessageQueue()); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(m_settings.m_rgbColor); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("Daemon source"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + m_settings.setChannelMarker(&m_channelMarker); + + m_deviceUISet->registerRxChannelInstance(DaemonSink::m_channelIdURI, this); + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + + m_time.start(); + + displaySettings(); + applySettings(true); +} + +DaemonSinkGUI::~DaemonSinkGUI() +{ + m_deviceUISet->removeRxChannelInstance(this); + delete ui; +} + +void DaemonSinkGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void DaemonSinkGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + setTitleColor(m_channelMarker.getColor()); + + DaemonSink::MsgConfigureDaemonSink* message = DaemonSink::MsgConfigureDaemonSink::create(m_settings, force); + m_daemonSink->getInputMessageQueue()->push(message); + } +} + +void DaemonSinkGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setBandwidth(5000); // TODO + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + ui->dataAddress->setText(m_settings.m_dataAddress); + ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); + blockApplySettings(false); +} + +void DaemonSinkGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void DaemonSinkGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void DaemonSinkGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void DaemonSinkGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) +{ +} + +void DaemonSinkGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + +void DaemonSinkGUI::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + applySettings(); +} + +void DaemonSinkGUI::on_dataPort_returnPressed() +{ + bool dataOk; + int dataPort = ui->dataPort->text().toInt(&dataOk); + + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) + { + return; + } + else + { + m_settings.m_dataPort = dataPort; + } + + applySettings(); +} + +void DaemonSinkGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + + bool dataOk; + int udpDataPort = ui->dataPort->text().toInt(&dataOk); + + if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) + { + m_settings.m_dataPort = udpDataPort; + } + + applySettings(); +} + +void DaemonSinkGUI::on_txDelay_valueChanged(int value) +{ + m_settings.m_txDelay = value / 100.0; + ui->txDelayText->setText(tr("%1").arg(value)); + updateTxDelayTooltip(); + applySettings(); +} + +void DaemonSinkGUI::on_nbFECBlocks_valueChanged(int value) +{ + m_settings.m_nbFECBlocks = value; + int nbOriginalBlocks = 128; + int nbFECBlocks = value; + QString s = QString::number(nbOriginalBlocks + nbFECBlocks, 'f', 0); + QString s1 = QString::number(nbFECBlocks, 'f', 0); + ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); + updateTxDelayTooltip(); + applySettings(); +} + +void DaemonSinkGUI::updateTxDelayTooltip() +{ + double delay = m_sampleRate == 0 ? 0.0 : ((127*127*m_settings.m_txDelay) / m_sampleRate)/(128 + m_settings.m_nbFECBlocks); + ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(delay*1e6, 'f', 0))); +} + +void DaemonSinkGUI::tick() +{ + if (++m_tickCount == 20) { // once per second + m_tickCount = 0; + } +} diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.h b/plugins/channelrx/daemonsink/daemonsinkgui.h new file mode 100644 index 000000000..a161b8ebf --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkgui.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKGUI_H_ +#define PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKGUI_H_ + +#include +#include + +#include "plugin/plugininstancegui.h" +#include "dsp/channelmarker.h" +#include "gui/rollupwidget.h" +#include "util/messagequeue.h" + +#include "daemonsinksettings.h" + +class PluginAPI; +class DeviceUISet; +class DaemonSink; +class BasebandSampleSink; + +namespace Ui { + class DaemonSinkGUI; +} + +class DaemonSinkGUI : public RollupWidget, public PluginInstanceGUI { + Q_OBJECT +public: + static DaemonSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::DaemonSinkGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + DaemonSinkSettings m_settings; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + bool m_doApplySettings; + + DaemonSink* m_daemonSink; + MessageQueue m_inputMessageQueue; + + QTime m_time; + uint32_t m_tickCount; + + explicit DaemonSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~DaemonSinkGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void updateTxDelayTooltip(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void handleSourceMessages(); + void on_dataAddress_returnPressed(); + void on_dataPort_returnPressed(); + void on_dataApplyButton_clicked(bool checked); + void on_nbFECBlocks_valueChanged(int value); + void on_txDelay_valueChanged(int value); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void tick(); +}; + + + +#endif /* PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKGUI_H_ */ diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.ui b/plugins/channelrx/daemonsink/daemonsinkgui.ui index 138446c19..8fd0dc41c 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.ui +++ b/plugins/channelrx/daemonsink/daemonsinkgui.ui @@ -139,7 +139,7 @@ - + 30 diff --git a/plugins/channelrx/daemonsink/daemonsinkplugin.cpp b/plugins/channelrx/daemonsink/daemonsinkplugin.cpp new file mode 100644 index 000000000..8de1289c0 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkplugin.cpp @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 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 // +// // +// 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 "daemonsinkplugin.h" + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "daemonsinkgui.h" +#endif +#include "daemonsink.h" + +const PluginDescriptor DaemonSinkPlugin::m_pluginDescriptor = { + QString("Daemon Channel Sink"), + QString("4.1.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +DaemonSinkPlugin::DaemonSinkPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& DaemonSinkPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void DaemonSinkPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register TCP Channel Source + m_pluginAPI->registerRxChannel(DaemonSink::m_channelIdURI, DaemonSink::m_channelId, this); +} + +#ifdef SERVER_MODE +PluginInstanceGUI* DaemonSinkPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* DaemonSinkPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + return DaemonSinkGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif + +BasebandSampleSink* DaemonSinkPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) +{ + return new DaemonSink(deviceAPI); +} + +ChannelSinkAPI* DaemonSinkPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) +{ + return new DaemonSink(deviceAPI); +} + + + + diff --git a/plugins/channelrx/daemonsink/daemonsinkplugin.h b/plugins/channelrx/daemonsink/daemonsinkplugin.h new file mode 100644 index 000000000..16e0792db --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkplugin.h @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 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 // +// // +// 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 PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKPLUGIN_H_ +#define PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKPLUGIN_H_ + + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSink; + +class DaemonSinkPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.demod.daemonsink") + +public: + explicit DaemonSinkPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); + virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif /* PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKPLUGIN_H_ */ diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.cpp b/plugins/channelrx/daemonsink/daemonsinksettings.cpp index 6d2cfac79..d63435d8d 100644 --- a/plugins/channelrx/daemonsink/daemonsinksettings.cpp +++ b/plugins/channelrx/daemonsink/daemonsinksettings.cpp @@ -20,6 +20,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "util/simpleserializer.h" #include "settings/serializable.h" #include "daemonsinksettings.h" @@ -44,6 +46,8 @@ QByteArray DaemonSinkSettings::serialize() const s.writeU32(2, m_txDelay); s.writeString(3, m_dataAddress); s.writeU32(4, m_dataPort); + s.writeU32(5, m_rgbColor); + s.writeString(6, m_title); return s.final(); } @@ -81,6 +85,9 @@ bool DaemonSinkSettings::deserialize(const QByteArray& data) m_dataPort = 9090; } + d.readU32(5, &m_rgbColor, QColor(0, 255, 255).rgb()); + d.readString(6, &m_title, "Daemon sink"); + return true; } else diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.h b/plugins/channelrx/daemonsink/daemonsinksettings.h index e0dd8fc55..49584c57c 100644 --- a/plugins/channelrx/daemonsink/daemonsinksettings.h +++ b/plugins/channelrx/daemonsink/daemonsinksettings.h @@ -33,9 +33,14 @@ struct DaemonSinkSettings uint32_t m_txDelay; QString m_dataAddress; uint16_t m_dataPort; + quint32 m_rgbColor; + QString m_title; + + Serializable *m_channelMarker; DaemonSinkSettings(); void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } QByteArray serialize() const; bool deserialize(const QByteArray& data); }; diff --git a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp b/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp index 30db27dc6..62e8a4dcf 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcsettings.cpp @@ -71,7 +71,7 @@ bool DaemonSrcSettings::deserialize(const QByteArray& data) } d.readU32(3, &m_rgbColor, QColor(0, 255, 255).rgb()); - d.readString(4, &m_title, "AM Modulator"); + d.readString(4, &m_title, "Daemon source"); return true; } From 4819ebf5f656f2a57a670d62a9d74e021a476f88 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 5 Sep 2018 13:28:38 +0200 Subject: [PATCH 700/956] DaemonSink (3) --- plugins/channelrx/daemonsink/daemonsinkgui.cpp | 4 ++-- plugins/channelrx/daemonsink/daemonsinkplugin.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp index 908d80163..31a704d0e 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -14,7 +14,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "device/devicesinkapi.h" +#include "device/devicesourceapi.h" #include "device/deviceuiset.h" #include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" @@ -129,7 +129,7 @@ DaemonSinkGUI::DaemonSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas m_deviceUISet->addRollupWidget(this); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); - connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); m_time.start(); diff --git a/plugins/channelrx/daemonsink/daemonsinkplugin.cpp b/plugins/channelrx/daemonsink/daemonsinkplugin.cpp index 8de1289c0..54d6c3333 100644 --- a/plugins/channelrx/daemonsink/daemonsinkplugin.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkplugin.cpp @@ -48,7 +48,7 @@ void DaemonSinkPlugin::initPlugin(PluginAPI* pluginAPI) { m_pluginAPI = pluginAPI; - // register TCP Channel Source + // register channel Source m_pluginAPI->registerRxChannel(DaemonSink::m_channelIdURI, DaemonSink::m_channelId, this); } From 29583e4d56661a6d9190117706d3150a7a0582e6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 5 Sep 2018 18:25:58 +0200 Subject: [PATCH 701/956] DaemonSink (4) --- plugins/channelrx/daemonsink/daemonsink.cpp | 11 +++++++---- plugins/channelrx/daemonsink/daemonsinkgui.cpp | 13 ++++++++++--- plugins/channelrx/daemonsink/daemonsinksettings.cpp | 2 ++ plugins/channelrx/daemonsink/daemonsinkthread.cpp | 4 +++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 206860aa6..324ecc742 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -83,8 +83,11 @@ DaemonSink::~DaemonSink() void DaemonSink::setTxDelay(int txDelay) { - qDebug() << "DaemonSink::setTxDelay: txDelay: " << txDelay; - m_txDelay = txDelay; + double txDelayRatio = txDelay / 100.0; + double delay = m_sampleRate == 0 ? 1.0 : (127*127*txDelayRatio) / m_sampleRate; + delay /= 128 + m_settings.m_nbFECBlocks; + m_txDelay = roundf(delay*1e6); // microseconds + qDebug() << "DaemonSink::setTxDelay: "<< txDelay << "% m_txDelay: " << m_txDelay << "us"; } void DaemonSink::setNbBlocksFEC(int nbBlocksFEC) @@ -304,11 +307,11 @@ void DaemonSink::applySettings(const DaemonSinkSettings& settings, bool force) << " force: " << force; if ((m_settings.m_nbFECBlocks != settings.m_nbFECBlocks) || force) { - m_nbBlocksFEC = settings.m_nbFECBlocks; + setNbBlocksFEC(settings.m_nbFECBlocks); } if ((m_settings.m_txDelay != settings.m_txDelay) || force) { - m_txDelay = settings.m_txDelay; + setTxDelay(settings.m_txDelay); } if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp index 31a704d0e..78db56c9d 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -129,7 +129,7 @@ DaemonSinkGUI::DaemonSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas m_deviceUISet->addRollupWidget(this); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); - connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + //connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); m_time.start(); @@ -174,6 +174,11 @@ void DaemonSinkGUI::displaySettings() blockApplySettings(true); ui->dataAddress->setText(m_settings.m_dataAddress); ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); + QString s = QString::number(128 + m_settings.m_nbFECBlocks, 'f', 0); + QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0); + ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); + ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay)); + updateTxDelayTooltip(); blockApplySettings(false); } @@ -259,7 +264,7 @@ void DaemonSinkGUI::on_dataApplyButton_clicked(bool checked __attribute__((unuse void DaemonSinkGUI::on_txDelay_valueChanged(int value) { - m_settings.m_txDelay = value / 100.0; + m_settings.m_txDelay = value; // percentage ui->txDelayText->setText(tr("%1").arg(value)); updateTxDelayTooltip(); applySettings(); @@ -279,7 +284,9 @@ void DaemonSinkGUI::on_nbFECBlocks_valueChanged(int value) void DaemonSinkGUI::updateTxDelayTooltip() { - double delay = m_sampleRate == 0 ? 0.0 : ((127*127*m_settings.m_txDelay) / m_sampleRate)/(128 + m_settings.m_nbFECBlocks); + double txDelayRatio = m_settings.m_txDelay / 100.0; + double delay = m_sampleRate == 0 ? 0.0 : (127*127*txDelayRatio) / m_sampleRate; + delay /= 128 + m_settings.m_nbFECBlocks; ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(delay*1e6, 'f', 0))); } diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.cpp b/plugins/channelrx/daemonsink/daemonsinksettings.cpp index d63435d8d..64d672a94 100644 --- a/plugins/channelrx/daemonsink/daemonsinksettings.cpp +++ b/plugins/channelrx/daemonsink/daemonsinksettings.cpp @@ -37,6 +37,8 @@ void DaemonSinkSettings::resetToDefaults() m_txDelay = 100; m_dataAddress = "127.0.0.1"; m_dataPort = 9090; + m_rgbColor = QColor(140, 4, 4).rgb(); + m_title = "Daemon sink"; } QByteArray DaemonSinkSettings::serialize() const diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.cpp b/plugins/channelrx/daemonsink/daemonsinkthread.cpp index 8dfb0f9b3..d194ae32b 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkthread.cpp @@ -97,6 +97,7 @@ bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) uint16_t frameIndex = dataBlock.m_txControlBlock.m_frameIndex; int nbBlocksFEC = dataBlock.m_txControlBlock.m_nbBlocksFEC; int txDelay = dataBlock.m_txControlBlock.m_txDelay; + qDebug("DaemonSinkThread::handleDataBlock: txDelay: %d QS: %d", txDelay, m_dataQueue->size()); m_address.setAddress(dataBlock.m_txControlBlock.m_dataAddress); uint16_t dataPort = dataBlock.m_txControlBlock.m_dataPort; SDRDaemonSuperBlock *txBlockx = dataBlock.m_superBlocks; @@ -109,7 +110,8 @@ bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) { // send block via UDP m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, dataPort); - usleep(txDelay); + //m_socket->SendDataGram((const char*)&txBlockx[i], (int) SDRDaemonUdpSize, m_address.toStdString(), (uint32_t) dataPort); + //usleep(txDelay); } } } From 354409a21a8ccff4dd3def19c7ab9e2b4659810e Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 6 Sep 2018 04:23:27 +0200 Subject: [PATCH 702/956] DaemonSink (5) --- plugins/channelrx/daemonsink/daemonsink.cpp | 19 ++++++++----- plugins/channelrx/daemonsink/daemonsink.h | 2 +- .../channelrx/daemonsink/daemonsinkgui.cpp | 15 ++++++----- plugins/channelrx/daemonsink/daemonsinkgui.h | 2 +- plugins/channelrx/daemonsink/daemonsinkgui.ui | 27 +++++++++++++++++-- .../daemonsink/daemonsinksettings.cpp | 4 +-- .../channelrx/daemonsink/daemonsinkthread.cpp | 3 +-- 7 files changed, 51 insertions(+), 21 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 324ecc742..6f9f18313 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -54,7 +54,7 @@ DaemonSink::DaemonSink(DeviceSourceAPI *deviceAPI) : m_sampleRate(48000), m_sampleBytes(SDR_RX_SAMP_SZ == 24 ? 4 : 2), m_nbBlocksFEC(0), - m_txDelay(100), + m_txDelay(50), m_dataAddress("127.0.0.1"), m_dataPort(9090) { @@ -81,13 +81,16 @@ DaemonSink::~DaemonSink() delete m_channelizer; } -void DaemonSink::setTxDelay(int txDelay) +void DaemonSink::setTxDelay(int txDelay, int nbBlocksFEC) { double txDelayRatio = txDelay / 100.0; double delay = m_sampleRate == 0 ? 1.0 : (127*127*txDelayRatio) / m_sampleRate; - delay /= 128 + m_settings.m_nbFECBlocks; + delay /= 128 + nbBlocksFEC; m_txDelay = roundf(delay*1e6); // microseconds - qDebug() << "DaemonSink::setTxDelay: "<< txDelay << "% m_txDelay: " << m_txDelay << "us"; + qDebug() << "DaemonSink::setTxDelay:" + << " " << txDelay + << "% m_txDelay: " << m_txDelay << "us" + << " m_sampleRate: " << m_sampleRate << "S/s"; } void DaemonSink::setNbBlocksFEC(int nbBlocksFEC) @@ -183,6 +186,7 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec m_dataBlock->m_txControlBlock.m_dataAddress = m_dataAddress; m_dataBlock->m_txControlBlock.m_dataPort = m_dataPort; + qDebug("DaemonSink::feed: m_dataBlock: %p m_dataQueue.sz: %d", m_dataBlock, m_dataQueue.size()); m_dataQueue.push(m_dataBlock); m_dataBlock = new SDRDaemonDataBlock(); // create a new one immediately m_dataBlockMutex.unlock(); @@ -241,6 +245,8 @@ bool DaemonSink::handleMessage(const Message& cmd __attribute__((unused))) setSampleRate(notif.getSampleRate()); } + setTxDelay(m_settings.m_txDelay, m_settings.m_nbFECBlocks); + if (m_guiMessageQueue) { MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate()); @@ -308,10 +314,11 @@ void DaemonSink::applySettings(const DaemonSinkSettings& settings, bool 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); + setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); } if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { @@ -359,7 +366,7 @@ int DaemonSink::webapiSettingsPutPatch( int txDelay = response.getDaemonSinkSettings()->getTxDelay(); if (txDelay < 0) { - settings.m_txDelay = 100; + settings.m_txDelay = 50; } else { settings.m_txDelay = txDelay; } diff --git a/plugins/channelrx/daemonsink/daemonsink.h b/plugins/channelrx/daemonsink/daemonsink.h index 8b0e38c1a..948b16861 100644 --- a/plugins/channelrx/daemonsink/daemonsink.h +++ b/plugins/channelrx/daemonsink/daemonsink.h @@ -118,7 +118,7 @@ public: void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } void setNbBlocksFEC(int nbBlocksFEC); - void setTxDelay(int txDelay); + void setTxDelay(int txDelay, int nbBlocksFEC); void setDataAddress(const QString& address) { m_dataAddress = address; } void setDataPort(uint16_t port) { m_dataPort = port; } diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp index 78db56c9d..a5826665c 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -83,6 +83,7 @@ bool DaemonSinkGUI::handleMessage(const Message& message) DaemonSink::MsgSampleRateNotification& notif = (DaemonSink::MsgSampleRateNotification&) message; m_channelMarker.setBandwidth(notif.getSampleRate()); m_sampleRate = notif.getSampleRate(); + updateTxDelayTime(); return true; } else if (DaemonSink::MsgConfigureDaemonSink::match(message)) @@ -177,8 +178,8 @@ void DaemonSinkGUI::displaySettings() QString s = QString::number(128 + m_settings.m_nbFECBlocks, 'f', 0); QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); - ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay)); - updateTxDelayTooltip(); + ui->txDelayText->setText(tr("%1%").arg(m_settings.m_txDelay)); + updateTxDelayTime(); blockApplySettings(false); } @@ -265,8 +266,8 @@ void DaemonSinkGUI::on_dataApplyButton_clicked(bool checked __attribute__((unuse void DaemonSinkGUI::on_txDelay_valueChanged(int value) { m_settings.m_txDelay = value; // percentage - ui->txDelayText->setText(tr("%1").arg(value)); - updateTxDelayTooltip(); + ui->txDelayText->setText(tr("%1%").arg(value)); + updateTxDelayTime(); applySettings(); } @@ -278,16 +279,16 @@ void DaemonSinkGUI::on_nbFECBlocks_valueChanged(int value) QString s = QString::number(nbOriginalBlocks + nbFECBlocks, 'f', 0); QString s1 = QString::number(nbFECBlocks, 'f', 0); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); - updateTxDelayTooltip(); + updateTxDelayTime(); applySettings(); } -void DaemonSinkGUI::updateTxDelayTooltip() +void DaemonSinkGUI::updateTxDelayTime() { double txDelayRatio = m_settings.m_txDelay / 100.0; double delay = m_sampleRate == 0 ? 0.0 : (127*127*txDelayRatio) / m_sampleRate; delay /= 128 + m_settings.m_nbFECBlocks; - ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(delay*1e6, 'f', 0))); + ui->txDelayTime->setText(tr("%1µs").arg(QString::number(delay*1e6, 'f', 0))); } void DaemonSinkGUI::tick() diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.h b/plugins/channelrx/daemonsink/daemonsinkgui.h index a161b8ebf..139f3281c 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.h +++ b/plugins/channelrx/daemonsink/daemonsinkgui.h @@ -75,7 +75,7 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); - void updateTxDelayTooltip(); + void updateTxDelayTime(); void leaveEvent(QEvent*); void enterEvent(QEvent*); diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.ui b/plugins/channelrx/daemonsink/daemonsinkgui.ui index 8fd0dc41c..a48750f94 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.ui +++ b/plugins/channelrx/daemonsink/daemonsinkgui.ui @@ -158,6 +158,13 @@ + + + + FEC + + + @@ -242,12 +249,28 @@ - 20 + 30 0 - 90 + 50% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 55 + 0 + + + + 10000us Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.cpp b/plugins/channelrx/daemonsink/daemonsinksettings.cpp index 64d672a94..930531123 100644 --- a/plugins/channelrx/daemonsink/daemonsinksettings.cpp +++ b/plugins/channelrx/daemonsink/daemonsinksettings.cpp @@ -34,7 +34,7 @@ DaemonSinkSettings::DaemonSinkSettings() void DaemonSinkSettings::resetToDefaults() { m_nbFECBlocks = 0; - m_txDelay = 100; + m_txDelay = 50; m_dataAddress = "127.0.0.1"; m_dataPort = 9090; m_rgbColor = QColor(140, 4, 4).rgb(); @@ -77,7 +77,7 @@ bool DaemonSinkSettings::deserialize(const QByteArray& data) m_nbFECBlocks = 0; } - d.readU32(2, &m_txDelay, 100); + d.readU32(2, &m_txDelay, 50); d.readString(3, &m_dataAddress, "127.0.0.1"); d.readU32(4, &tmp, 0); diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.cpp b/plugins/channelrx/daemonsink/daemonsinkthread.cpp index d194ae32b..812176088 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkthread.cpp @@ -110,8 +110,7 @@ bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) { // send block via UDP m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, dataPort); - //m_socket->SendDataGram((const char*)&txBlockx[i], (int) SDRDaemonUdpSize, m_address.toStdString(), (uint32_t) dataPort); - //usleep(txDelay); + usleep(txDelay); } } } From 2be1281885cf9730a45fa08ecdaa607cb096d784 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 6 Sep 2018 04:36:56 +0200 Subject: [PATCH 703/956] DaemonSink (6) --- plugins/channelrx/daemonsink/daemonsink.cpp | 8 +++----- plugins/channelrx/daemonsink/daemonsink.h | 4 ---- plugins/channelrx/daemonsink/daemonsinkgui.cpp | 1 + plugins/channelrx/daemonsink/daemonsinkgui.ui | 6 +++--- plugins/channelrx/daemonsink/daemonsinksettings.cpp | 4 ++-- plugins/channelrx/daemonsink/daemonsinkthread.cpp | 9 +++++---- plugins/channelrx/daemonsink/daemonsinkthread.h | 7 +++++-- 7 files changed, 19 insertions(+), 20 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 6f9f18313..56168e657 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -54,7 +54,7 @@ DaemonSink::DaemonSink(DeviceSourceAPI *deviceAPI) : m_sampleRate(48000), m_sampleBytes(SDR_RX_SAMP_SZ == 24 ? 4 : 2), m_nbBlocksFEC(0), - m_txDelay(50), + m_txDelay(35), m_dataAddress("127.0.0.1"), m_dataPort(9090) { @@ -64,8 +64,6 @@ DaemonSink::DaemonSink(DeviceSourceAPI *deviceAPI) : 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() @@ -212,7 +210,7 @@ void DaemonSink::start() stop(); } - m_sinkThread = new DaemonSinkThread(&m_dataQueue, m_cm256p); + m_sinkThread = new DaemonSinkThread(&m_dataQueue); m_sinkThread->startStop(true); m_running = true; } @@ -366,7 +364,7 @@ int DaemonSink::webapiSettingsPutPatch( int txDelay = response.getDaemonSinkSettings()->getTxDelay(); if (txDelay < 0) { - settings.m_txDelay = 50; + settings.m_txDelay = 35; } else { settings.m_txDelay = txDelay; } diff --git a/plugins/channelrx/daemonsink/daemonsink.h b/plugins/channelrx/daemonsink/daemonsink.h index 948b16861..9d0ddb005 100644 --- a/plugins/channelrx/daemonsink/daemonsink.h +++ b/plugins/channelrx/daemonsink/daemonsink.h @@ -26,8 +26,6 @@ #include #include -#include "cm256.h" - #include "dsp/basebandsamplesink.h" #include "channel/channelsinkapi.h" #include "channel/sdrdaemondataqueue.h" @@ -134,8 +132,6 @@ private: 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 diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp index a5826665c..ff886c78c 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -179,6 +179,7 @@ void DaemonSinkGUI::displaySettings() QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); ui->txDelayText->setText(tr("%1%").arg(m_settings.m_txDelay)); + ui->txDelay->setValue(m_settings.m_txDelay); updateTxDelayTime(); blockApplySettings(false); } diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.ui b/plugins/channelrx/daemonsink/daemonsinkgui.ui index a48750f94..3bf070450 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.ui +++ b/plugins/channelrx/daemonsink/daemonsinkgui.ui @@ -232,16 +232,16 @@ Delay between consecutive UDP packets in percentage of nominal UDP packet process time - 10 + 0 - 90 + 70 1 - 50 + 35 diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.cpp b/plugins/channelrx/daemonsink/daemonsinksettings.cpp index 930531123..9b81702f0 100644 --- a/plugins/channelrx/daemonsink/daemonsinksettings.cpp +++ b/plugins/channelrx/daemonsink/daemonsinksettings.cpp @@ -34,7 +34,7 @@ DaemonSinkSettings::DaemonSinkSettings() void DaemonSinkSettings::resetToDefaults() { m_nbFECBlocks = 0; - m_txDelay = 50; + m_txDelay = 35; m_dataAddress = "127.0.0.1"; m_dataPort = 9090; m_rgbColor = QColor(140, 4, 4).rgb(); @@ -77,7 +77,7 @@ bool DaemonSinkSettings::deserialize(const QByteArray& data) m_nbFECBlocks = 0; } - d.readU32(2, &m_txDelay, 50); + d.readU32(2, &m_txDelay, 35); d.readString(3, &m_dataAddress, "127.0.0.1"); d.readU32(4, &tmp, 0); diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.cpp b/plugins/channelrx/daemonsink/daemonsinkthread.cpp index 812176088..319076a08 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkthread.cpp @@ -30,14 +30,15 @@ MESSAGE_CLASS_DEFINITION(DaemonSinkThread::MsgStartStop, Message) -DaemonSinkThread::DaemonSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent) : +DaemonSinkThread::DaemonSinkThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : QThread(parent), m_running(false), m_dataQueue(dataQueue), - m_cm256(cm256), m_address(QHostAddress::LocalHost), m_socket(0) { + + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); } @@ -102,7 +103,7 @@ bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) uint16_t dataPort = dataBlock.m_txControlBlock.m_dataPort; SDRDaemonSuperBlock *txBlockx = dataBlock.m_superBlocks; - if ((nbBlocksFEC == 0) || !m_cm256) // Do not FEC encode + if ((nbBlocksFEC == 0) || !m_cm256p) // Do not FEC encode { if (m_socket) { @@ -134,7 +135,7 @@ bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) } // Encode FEC blocks - if (m_cm256->cm256_encode(cm256Params, descriptorBlocks, fecBlocks)) + if (m_cm256p->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 diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.h b/plugins/channelrx/daemonsink/daemonsinkthread.h index 10db87e9a..451f74720 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.h +++ b/plugins/channelrx/daemonsink/daemonsinkthread.h @@ -25,6 +25,8 @@ #include #include +#include "cm256.h" + #include "util/message.h" #include "util/messagequeue.h" @@ -56,7 +58,7 @@ public: { } }; - DaemonSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent = 0); + DaemonSinkThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); ~DaemonSinkThread(); void startStop(bool start); @@ -67,7 +69,8 @@ private: bool m_running; SDRDaemonDataQueue *m_dataQueue; - CM256 *m_cm256; //!< CM256 library object + CM256 m_cm256; + CM256 *m_cm256p; QHostAddress m_address; QUdpSocket *m_socket; From 54c89f16aaafecc166f3e2802011a1c77df69b5c Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 6 Sep 2018 05:21:43 +0200 Subject: [PATCH 704/956] DaemonSink (7) --- plugins/channelrx/daemonsink/daemonsink.cpp | 10 ++++++++-- plugins/channelrx/daemonsink/daemonsink.h | 3 +++ .../channelrx/daemonsink/daemonsinkthread.cpp | 18 ++++++++++-------- .../channelrx/daemonsink/daemonsinkthread.h | 5 ++++- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 56168e657..959ff950b 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -184,8 +184,9 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec m_dataBlock->m_txControlBlock.m_dataAddress = m_dataAddress; m_dataBlock->m_txControlBlock.m_dataPort = m_dataPort; - qDebug("DaemonSink::feed: m_dataBlock: %p m_dataQueue.sz: %d", m_dataBlock, m_dataQueue.size()); - m_dataQueue.push(m_dataBlock); + //qDebug("DaemonSink::feed: m_dataBlock: %p m_dataQueue.sz: %d", m_dataBlock, m_dataQueue.size()); + emit dataBlockAvailable(m_dataBlock); + //m_dataQueue.push(m_dataBlock); m_dataBlock = new SDRDaemonDataBlock(); // create a new one immediately m_dataBlockMutex.unlock(); @@ -211,6 +212,11 @@ void DaemonSink::start() } m_sinkThread = new DaemonSinkThread(&m_dataQueue); + connect(this, + SIGNAL(dataBlockAvailable(SDRDaemonDataBlock *)), + m_sinkThread, + SLOT(processDataBlock(SDRDaemonDataBlock *)), + Qt::QueuedConnection); m_sinkThread->startStop(true); m_running = true; } diff --git a/plugins/channelrx/daemonsink/daemonsink.h b/plugins/channelrx/daemonsink/daemonsink.h index 9d0ddb005..97b3b1eb8 100644 --- a/plugins/channelrx/daemonsink/daemonsink.h +++ b/plugins/channelrx/daemonsink/daemonsink.h @@ -123,6 +123,9 @@ public: static const QString m_channelIdURI; static const QString m_channelId; +signals: + void dataBlockAvailable(SDRDaemonDataBlock *dataBlock); + private: DeviceSourceAPI *m_deviceAPI; ThreadedBasebandSampleSink* m_threadedChannelizer; diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.cpp b/plugins/channelrx/daemonsink/daemonsinkthread.cpp index 319076a08..fc31692a0 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkthread.cpp @@ -89,7 +89,13 @@ void DaemonSinkThread::run() qDebug("DaemonSinkThread::run: end"); } -bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) +void DaemonSinkThread::processDataBlock(SDRDaemonDataBlock *dataBlock) +{ + handleDataBlock(*dataBlock); + delete dataBlock; +} + +void 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 @@ -98,7 +104,7 @@ bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) uint16_t frameIndex = dataBlock.m_txControlBlock.m_frameIndex; int nbBlocksFEC = dataBlock.m_txControlBlock.m_nbBlocksFEC; int txDelay = dataBlock.m_txControlBlock.m_txDelay; - qDebug("DaemonSinkThread::handleDataBlock: txDelay: %d QS: %d", txDelay, m_dataQueue->size()); + //qDebug("DaemonSinkThread::handleDataBlock: dataBlock: %p QS: %d", &dataBlock, m_dataQueue->size()); m_address.setAddress(dataBlock.m_txControlBlock.m_dataAddress); uint16_t dataPort = dataBlock.m_txControlBlock.m_dataPort; SDRDaemonSuperBlock *txBlockx = dataBlock.m_superBlocks; @@ -139,7 +145,6 @@ bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) { 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 @@ -161,7 +166,6 @@ bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) } dataBlock.m_txControlBlock.m_processed = true; - return true; } void DaemonSinkThread::handleData() @@ -170,10 +174,8 @@ void DaemonSinkThread::handleData() while (m_running && ((dataBlock = m_dataQueue->pop()) != 0)) { - if (handleDataBlock(*dataBlock)) - { - delete dataBlock; - } + handleDataBlock(*dataBlock); + delete dataBlock; } } diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.h b/plugins/channelrx/daemonsink/daemonsinkthread.h index 451f74720..88995c53d 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.h +++ b/plugins/channelrx/daemonsink/daemonsinkthread.h @@ -63,6 +63,9 @@ public: void startStop(bool start); +public slots: + void processDataBlock(SDRDaemonDataBlock *dataBlock); + private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; @@ -81,7 +84,7 @@ private: void stopWork(); void run(); - bool handleDataBlock(SDRDaemonDataBlock& dataBlock); + void handleDataBlock(SDRDaemonDataBlock& dataBlock); private slots: void handleData(); From 3b09e0e5d7f96c2718d7db7756c0c13cfe8e3718 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 6 Sep 2018 05:32:11 +0200 Subject: [PATCH 705/956] DaemonSink: Web API implementation --- plugins/channelrx/daemonsink/daemonsinkgui.cpp | 2 +- sdrbase/webapi/webapirequestmapper.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp index ff886c78c..9819b2efc 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -165,7 +165,7 @@ void DaemonSinkGUI::displaySettings() m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle(m_settings.m_title); - m_channelMarker.setBandwidth(5000); // TODO + m_channelMarker.setBandwidth(m_sampleRate); // TODO m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 0c3c56fa5..d4ed9a3e2 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -2146,6 +2146,20 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "DaemonSink") + { + if (channelSettings.getTx() == 0) + { + QJsonObject daemonChannelSinkSettingsJsonObject = jsonObject["DaemonSinkSettings"].toObject(); + channelSettingsKeys = daemonChannelSinkSettingsJsonObject.keys(); + channelSettings.setDaemonSinkSettings(new SWGSDRangel::SWGDaemonSinkSettings()); + channelSettings.getDaemonSinkSettings()->fromJsonObject(daemonChannelSinkSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "DaemonSrc") { if (channelSettings.getTx() != 0) From b839b5d0c3844ff1f0c0f55a961d6b535814340c Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 6 Sep 2018 05:39:28 +0200 Subject: [PATCH 706/956] DaemonSink: finalization and cleanup --- plugins/channelrx/daemonsink/daemonsink.cpp | 4 +--- plugins/channelrx/daemonsink/daemonsink.h | 2 -- .../channelrx/daemonsink/daemonsinkthread.cpp | 17 +---------------- plugins/channelrx/daemonsink/daemonsinkthread.h | 5 +---- 4 files changed, 3 insertions(+), 25 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 959ff950b..51b70bb71 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -184,9 +184,7 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec m_dataBlock->m_txControlBlock.m_dataAddress = m_dataAddress; m_dataBlock->m_txControlBlock.m_dataPort = m_dataPort; - //qDebug("DaemonSink::feed: m_dataBlock: %p m_dataQueue.sz: %d", m_dataBlock, m_dataQueue.size()); emit dataBlockAvailable(m_dataBlock); - //m_dataQueue.push(m_dataBlock); m_dataBlock = new SDRDaemonDataBlock(); // create a new one immediately m_dataBlockMutex.unlock(); @@ -211,7 +209,7 @@ void DaemonSink::start() stop(); } - m_sinkThread = new DaemonSinkThread(&m_dataQueue); + m_sinkThread = new DaemonSinkThread(); connect(this, SIGNAL(dataBlockAvailable(SDRDaemonDataBlock *)), m_sinkThread, diff --git a/plugins/channelrx/daemonsink/daemonsink.h b/plugins/channelrx/daemonsink/daemonsink.h index 97b3b1eb8..fcadb413a 100644 --- a/plugins/channelrx/daemonsink/daemonsink.h +++ b/plugins/channelrx/daemonsink/daemonsink.h @@ -28,7 +28,6 @@ #include "dsp/basebandsamplesink.h" #include "channel/channelsinkapi.h" -#include "channel/sdrdaemondataqueue.h" #include "channel/sdrdaemondatablock.h" #include "daemonsinksettings.h" @@ -133,7 +132,6 @@ private: bool m_running; DaemonSinkSettings m_settings; - SDRDaemonDataQueue m_dataQueue; DaemonSinkThread *m_sinkThread; int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.cpp b/plugins/channelrx/daemonsink/daemonsinkthread.cpp index fc31692a0..423e17454 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkthread.cpp @@ -22,7 +22,6 @@ #include -#include "channel/sdrdaemondataqueue.h" #include "channel/sdrdaemondatablock.h" #include "daemonsinkthread.h" @@ -30,17 +29,15 @@ MESSAGE_CLASS_DEFINITION(DaemonSinkThread::MsgStartStop, Message) -DaemonSinkThread::DaemonSinkThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : +DaemonSinkThread::DaemonSinkThread(QObject* parent) : QThread(parent), m_running(false), - m_dataQueue(dataQueue), m_address(QHostAddress::LocalHost), m_socket(0) { m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); - connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); } DaemonSinkThread::~DaemonSinkThread() @@ -104,7 +101,6 @@ void DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) uint16_t frameIndex = dataBlock.m_txControlBlock.m_frameIndex; int nbBlocksFEC = dataBlock.m_txControlBlock.m_nbBlocksFEC; int txDelay = dataBlock.m_txControlBlock.m_txDelay; - //qDebug("DaemonSinkThread::handleDataBlock: dataBlock: %p QS: %d", &dataBlock, m_dataQueue->size()); m_address.setAddress(dataBlock.m_txControlBlock.m_dataAddress); uint16_t dataPort = dataBlock.m_txControlBlock.m_dataPort; SDRDaemonSuperBlock *txBlockx = dataBlock.m_superBlocks; @@ -168,17 +164,6 @@ void DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) dataBlock.m_txControlBlock.m_processed = true; } -void DaemonSinkThread::handleData() -{ - SDRDaemonDataBlock* dataBlock; - - while (m_running && ((dataBlock = m_dataQueue->pop()) != 0)) - { - handleDataBlock(*dataBlock); - delete dataBlock; - } -} - void DaemonSinkThread::handleInputMessages() { Message* message; diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.h b/plugins/channelrx/daemonsink/daemonsinkthread.h index 88995c53d..5e468f7a9 100644 --- a/plugins/channelrx/daemonsink/daemonsinkthread.h +++ b/plugins/channelrx/daemonsink/daemonsinkthread.h @@ -30,7 +30,6 @@ #include "util/message.h" #include "util/messagequeue.h" -class SDRDaemonDataQueue; class SDRDaemonDataBlock; class CM256; class QUdpSocket; @@ -58,7 +57,7 @@ public: { } }; - DaemonSinkThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); + DaemonSinkThread(QObject* parent = 0); ~DaemonSinkThread(); void startStop(bool start); @@ -71,7 +70,6 @@ private: QWaitCondition m_startWaiter; bool m_running; - SDRDaemonDataQueue *m_dataQueue; CM256 m_cm256; CM256 *m_cm256p; @@ -87,6 +85,5 @@ private: void handleDataBlock(SDRDaemonDataBlock& dataBlock); private slots: - void handleData(); void handleInputMessages(); }; From 67f523e629681194518c65c33fed047b06e8f095 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 6 Sep 2018 14:29:14 +0200 Subject: [PATCH 707/956] TestSource: make it more robust --- .../testsource/testsourceinput.cpp | 7 ++- .../testsource/testsourceplugin.cpp | 2 +- .../testsource/testsourcethread.cpp | 43 +++++++++++++++---- .../testsource/testsourcethread.h | 32 ++++++++++++-- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index c73a7878d..94efed087 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -74,8 +74,7 @@ bool TestSourceInput::start() m_testSourceThread = new TestSourceThread(&m_sampleFifo); m_testSourceThread->setSamplerate(m_settings.m_sampleRate); - m_testSourceThread->connectTimer(m_masterTimer); - m_testSourceThread->startWork(); + m_testSourceThread->startStop(true); mutexLocker.unlock(); @@ -91,8 +90,8 @@ void TestSourceInput::stop() if (m_testSourceThread != 0) { - m_testSourceThread->stopWork(); - delete m_testSourceThread; + m_testSourceThread->startStop(false); + m_testSourceThread->deleteLater(); m_testSourceThread = 0; } diff --git a/plugins/samplesource/testsource/testsourceplugin.cpp b/plugins/samplesource/testsource/testsourceplugin.cpp index f0994e203..ac8dc459d 100644 --- a/plugins/samplesource/testsource/testsourceplugin.cpp +++ b/plugins/samplesource/testsource/testsourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor TestSourcePlugin::m_pluginDescriptor = { QString("Test Source input"), - QString("4.0.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/testsource/testsourcethread.cpp b/plugins/samplesource/testsource/testsourcethread.cpp index 70fd0d980..361a9a04c 100644 --- a/plugins/samplesource/testsource/testsourcethread.cpp +++ b/plugins/samplesource/testsource/testsourcethread.cpp @@ -22,6 +22,8 @@ #define TESTSOURCE_BLOCKSIZE 16384 +MESSAGE_CLASS_DEFINITION(TestSourceThread::MsgStartStop, Message) + TestSourceThread::TestSourceThread(SampleSinkFifo* sampleFifo, QObject* parent) : QThread(parent), m_running(false), @@ -55,15 +57,17 @@ TestSourceThread::TestSourceThread(SampleSinkFifo* sampleFifo, QObject* parent) m_throttleToggle(false), m_mutex(QMutex::Recursive) { + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); } TestSourceThread::~TestSourceThread() { - stopWork(); } void TestSourceThread::startWork() { + connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); + m_timer.start(50); m_startWaitMutex.lock(); m_elapsedTimer.start(); start(); @@ -76,6 +80,8 @@ void TestSourceThread::stopWork() { m_running = false; wait(); + m_timer.stop(); + disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); } void TestSourceThread::setSamplerate(int samplerate) @@ -177,6 +183,12 @@ void TestSourceThread::setFMDeviation(float deviation) qDebug("TestSourceThread::setFMDeviation: m_fmDeviationUnit: %f", m_fmDeviationUnit); } +void TestSourceThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + void TestSourceThread::run() { m_running = true; @@ -291,19 +303,13 @@ void TestSourceThread::callback(const qint16* buf, qint32 len) m_sampleFifo->write(m_convertBuffer.begin(), it); } -void TestSourceThread::connectTimer(const QTimer& timer) -{ - qDebug() << "TestSourceThread::connectTimer"; - connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); -} - void TestSourceThread::tick() { if (m_running) { qint64 throttlems = m_elapsedTimer.restart(); - if (throttlems != m_throttlems) + if ((throttlems > 45) && (throttlems < 55) && (throttlems != m_throttlems)) { QMutexLocker mutexLocker(&m_mutex); m_throttlems = throttlems; @@ -315,3 +321,24 @@ void TestSourceThread::tick() } } +void TestSourceThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("TestSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + } +} diff --git a/plugins/samplesource/testsource/testsourcethread.h b/plugins/samplesource/testsource/testsourcethread.h index db506d308..2decb7774 100644 --- a/plugins/samplesource/testsource/testsourcethread.h +++ b/plugins/samplesource/testsource/testsourcethread.h @@ -27,6 +27,8 @@ #include "dsp/samplesinkfifo.h" #include "dsp/decimators.h" #include "dsp/ncof.h" +#include "util/message.h" +#include "util/messagequeue.h" #include "testsourcesettings.h" @@ -36,11 +38,29 @@ class TestSourceThread : 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) + { } + }; + TestSourceThread(SampleSinkFifo* sampleFifo, QObject* parent = 0); ~TestSourceThread(); - void startWork(); - void stopWork(); + void startStop(bool start); void setSamplerate(int samplerate); void setLog2Decimation(unsigned int log2_decim); void setFcPos(int fcPos); @@ -56,8 +76,6 @@ public: void setAMModulation(float amModulation); void setFMDeviation(float deviation); - void connectTimer(const QTimer& timer); - private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; @@ -95,14 +113,19 @@ private: int m_fcPosShift; int m_throttlems; + QTimer m_timer; QElapsedTimer m_elapsedTimer; bool m_throttleToggle; QMutex m_mutex; + MessageQueue m_inputMessageQueue; + Decimators m_decimators_8; Decimators m_decimators_12; Decimators m_decimators_16; + void startWork(); + void stopWork(); void run(); void callback(const qint16* buf, qint32 len); void setBuffers(quint32 chunksize); @@ -347,6 +370,7 @@ private: private slots: void tick(); + void handleInputMessages(); }; #endif // _TESTSOURCE_TESTSOURCETHREAD_H_ From b75eb08a9135ce77ee6ea0c3a5e4b2903c63c4d3 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 6 Sep 2018 21:54:09 +0200 Subject: [PATCH 708/956] DaemonSource: pass device center frequency and baseband sample rate in the report --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 2 + sdrbase/resources/webapi/doc/html2/index.html | 10 ++++- .../doc/swagger/include/DaemonSource.yaml | 6 +++ .../api/swagger/include/DaemonSource.yaml | 6 +++ swagger/sdrangel/code/html2/index.html | 10 ++++- .../code/qt5/client/SWGDaemonSourceReport.cpp | 42 +++++++++++++++++++ .../code/qt5/client/SWGDaemonSourceReport.h | 12 ++++++ 7 files changed, 86 insertions(+), 2 deletions(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index 7904e3ace..52fefbf09 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -451,5 +451,7 @@ void DaemonSrc::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respons response.getDaemonSourceReport()->setNbFecBlocks(m_currentMeta.m_nbFECBlocks); response.getDaemonSourceReport()->setCenterFreq(m_currentMeta.m_centerFrequency); response.getDaemonSourceReport()->setSampleRate(m_currentMeta.m_sampleRate); + response.getDaemonSourceReport()->setDeviceCenterFreq(m_deviceAPI->getSampleSink()->getCenterFrequency()/1000); + response.getDaemonSourceReport()->setDeviceSampleRate(m_deviceAPI->getSampleSink()->getSampleRate()); } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index daaf4c9f7..d0824c2b9 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1742,6 +1742,14 @@ margin-bottom: 20px; "sampleRate" : { "type" : "integer", "description" : "Stream nominal sample rate in S/s" + }, + "deviceCenterFreq" : { + "type" : "integer", + "description" : "Device center frequency in kHz" + }, + "deviceSampleRate" : { + "type" : "integer", + "description" : "Device baseband sample rate in S/s" } }, "description" : "Daemon channel source report" @@ -28716,7 +28724,7 @@ except ApiException as e:
    - Generated 2018-09-04T20:03:50.896+02:00 + Generated 2018-09-06T20:10:22.329+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/DaemonSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/DaemonSource.yaml index c1d96de6a..36f13f348 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/DaemonSource.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/DaemonSource.yaml @@ -48,4 +48,10 @@ DaemonSourceReport: sampleRate: description: "Stream nominal sample rate in S/s" type: integer + deviceCenterFreq: + description: "Device center frequency in kHz" + type: integer + deviceSampleRate: + description: "Device baseband sample rate in S/s" + type: integer \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/DaemonSource.yaml b/swagger/sdrangel/api/swagger/include/DaemonSource.yaml index c1d96de6a..36f13f348 100644 --- a/swagger/sdrangel/api/swagger/include/DaemonSource.yaml +++ b/swagger/sdrangel/api/swagger/include/DaemonSource.yaml @@ -48,4 +48,10 @@ DaemonSourceReport: sampleRate: description: "Stream nominal sample rate in S/s" type: integer + deviceCenterFreq: + description: "Device center frequency in kHz" + type: integer + deviceSampleRate: + description: "Device baseband sample rate in S/s" + type: integer \ No newline at end of file diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index daaf4c9f7..d0824c2b9 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1742,6 +1742,14 @@ margin-bottom: 20px; "sampleRate" : { "type" : "integer", "description" : "Stream nominal sample rate in S/s" + }, + "deviceCenterFreq" : { + "type" : "integer", + "description" : "Device center frequency in kHz" + }, + "deviceSampleRate" : { + "type" : "integer", + "description" : "Device baseband sample rate in S/s" } }, "description" : "Daemon channel source report" @@ -28716,7 +28724,7 @@ except ApiException as e:
    - Generated 2018-09-04T20:03:50.896+02:00 + Generated 2018-09-06T20:10:22.329+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp index b2ba885d2..91d991f52 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.cpp @@ -50,6 +50,10 @@ SWGDaemonSourceReport::SWGDaemonSourceReport() { m_center_freq_isSet = false; sample_rate = 0; m_sample_rate_isSet = false; + device_center_freq = 0; + m_device_center_freq_isSet = false; + device_sample_rate = 0; + m_device_sample_rate_isSet = false; } SWGDaemonSourceReport::~SWGDaemonSourceReport() { @@ -80,6 +84,10 @@ SWGDaemonSourceReport::init() { m_center_freq_isSet = false; sample_rate = 0; m_sample_rate_isSet = false; + device_center_freq = 0; + m_device_center_freq_isSet = false; + device_sample_rate = 0; + m_device_sample_rate_isSet = false; } void @@ -95,6 +103,8 @@ SWGDaemonSourceReport::cleanup() { + + } SWGDaemonSourceReport* @@ -130,6 +140,10 @@ SWGDaemonSourceReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + ::SWGSDRangel::setValue(&device_center_freq, pJson["deviceCenterFreq"], "qint32", ""); + + ::SWGSDRangel::setValue(&device_sample_rate, pJson["deviceSampleRate"], "qint32", ""); + } QString @@ -179,6 +193,12 @@ SWGDaemonSourceReport::asJsonObject() { if(m_sample_rate_isSet){ obj->insert("sampleRate", QJsonValue(sample_rate)); } + if(m_device_center_freq_isSet){ + obj->insert("deviceCenterFreq", QJsonValue(device_center_freq)); + } + if(m_device_sample_rate_isSet){ + obj->insert("deviceSampleRate", QJsonValue(device_sample_rate)); + } return obj; } @@ -293,6 +313,26 @@ SWGDaemonSourceReport::setSampleRate(qint32 sample_rate) { this->m_sample_rate_isSet = true; } +qint32 +SWGDaemonSourceReport::getDeviceCenterFreq() { + return device_center_freq; +} +void +SWGDaemonSourceReport::setDeviceCenterFreq(qint32 device_center_freq) { + this->device_center_freq = device_center_freq; + this->m_device_center_freq_isSet = true; +} + +qint32 +SWGDaemonSourceReport::getDeviceSampleRate() { + return device_sample_rate; +} +void +SWGDaemonSourceReport::setDeviceSampleRate(qint32 device_sample_rate) { + this->device_sample_rate = device_sample_rate; + this->m_device_sample_rate_isSet = true; +} + bool SWGDaemonSourceReport::isSet(){ @@ -309,6 +349,8 @@ SWGDaemonSourceReport::isSet(){ if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} if(m_center_freq_isSet){ isObjectUpdated = true; break;} if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_device_center_freq_isSet){ isObjectUpdated = true; break;} + if(m_device_sample_rate_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h index cce8823e6..5d9e63d44 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSourceReport.h @@ -74,6 +74,12 @@ public: qint32 getSampleRate(); void setSampleRate(qint32 sample_rate); + qint32 getDeviceCenterFreq(); + void setDeviceCenterFreq(qint32 device_center_freq); + + qint32 getDeviceSampleRate(); + void setDeviceSampleRate(qint32 device_sample_rate); + virtual bool isSet() override; @@ -111,6 +117,12 @@ private: qint32 sample_rate; bool m_sample_rate_isSet; + qint32 device_center_freq; + bool m_device_center_freq_isSet; + + qint32 device_sample_rate; + bool m_device_sample_rate_isSet; + }; } From 98a1f1952f6dd35501512c7705e734f286984bd2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 7 Sep 2018 00:58:09 +0200 Subject: [PATCH 709/956] SDRDaemonSink and DaemonSource: do not set frequency via SDRDaemonSink --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 4 -- plugins/channeltx/daemonsrc/daemonsrcgui.cpp | 1 - plugins/channeltx/daemonsrc/daemonsrcgui.ui | 59 ++++--------------- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 34 ++++------- .../sdrdaemonsink/sdrdaemonsinkgui.h | 6 +- .../sdrdaemonsink/sdrdaemonsinkgui.ui | 59 +++++++++++++------ .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 37 ++---------- .../sdrdaemonsink/sdrdaemonsinkoutput.h | 3 +- .../sdrdaemonsink/sdrdaemonsinkthread.h | 1 - .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 3 +- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 4 -- 11 files changed, 75 insertions(+), 136 deletions(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index 52fefbf09..e6f711ca1 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -299,10 +299,6 @@ void DaemonSrc::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unu { printMeta("DaemonSrc::handleDataBlock", metaData); - if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { - m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency*1000); // frequency is in kHz - } - if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) { m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp index 2b95ada47..003262f6a 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp @@ -96,7 +96,6 @@ bool DaemonSrcGUI::handleMessage(const Message& message) else if (DaemonSrc::MsgReportStreamData::match(message)) { const DaemonSrc::MsgReportStreamData& report = (DaemonSrc::MsgReportStreamData&) message; - ui->centerFrequency->setText(QString("%1").arg(report.get_centerFreq())); ui->sampleRate->setText(QString("%1").arg(report.get_sampleRate())); QString nominalNbBlocksText = QString("%1/%2") .arg(report.get_nbOriginalBlocks() + report.get_nbFECBlocks()) diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.ui b/plugins/channeltx/daemonsrc/daemonsrcgui.ui index 611064c00..3b4ee8bdd 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.ui +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.ui @@ -158,52 +158,6 @@
    - - - - Freq - - - - - - - - 60 - 0 - - - - Stream center frequency setting - - - 00000000 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - kHz - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -237,6 +191,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index a11fcb007..80cb47f24 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -46,6 +46,7 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_deviceUISet(deviceUISet), m_settings(), m_deviceSampleSink(0), + m_deviceCenterFrequency(0), m_samplesCount(0), m_tickCount(0), m_nbSinceLastFlowCheck(0), @@ -86,6 +87,8 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_networkManager = new QNetworkAccessManager(); connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + m_time.start(); displayEventCounts(); displayEventTimer(); @@ -130,18 +133,6 @@ void SDRdaemonSinkGui::resetToDefaults() sendSettings(); } -qint64 SDRdaemonSinkGui::getCenterFrequency() const -{ - return m_settings.m_centerFrequency; -} - -void SDRdaemonSinkGui::setCenterFrequency(qint64 centerFrequency) -{ - m_settings.m_centerFrequency = centerFrequency; - displaySettings(); - sendSettings(); -} - QByteArray SDRdaemonSinkGui::serialize() const { return m_settings.serialize(); @@ -201,9 +192,8 @@ void SDRdaemonSinkGui::handleInputMessages() { DSPSignalNotification* notif = (DSPSignalNotification*) message; m_sampleRate = notif->getSampleRate(); - m_deviceCenterFrequency = notif->getCenterFrequency(); qDebug("SDRdaemonSinkGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); - updateSampleRateAndFrequency(); + updateSampleRate(); delete message; } @@ -216,10 +206,9 @@ void SDRdaemonSinkGui::handleInputMessages() } } -void SDRdaemonSinkGui::updateSampleRateAndFrequency() +void SDRdaemonSinkGui::updateSampleRate() { m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); - m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate) / 1000)); } @@ -232,7 +221,7 @@ void SDRdaemonSinkGui::updateTxDelayTooltip() void SDRdaemonSinkGui::displaySettings() { blockApplySettings(true); - ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + ui->centerFrequency->setValue(m_deviceCenterFrequency / 1000); ui->sampleRate->setValue(m_settings.m_sampleRate); ui->txDelay->setValue(m_settings.m_txDelay*100); ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay*100)); @@ -296,12 +285,6 @@ void SDRdaemonSinkGui::updateStatus() } } -void SDRdaemonSinkGui::on_centerFrequency_changed(quint64 value) -{ - m_settings.m_centerFrequency = value * 1000; - sendSettings(); -} - void SDRdaemonSinkGui::on_sampleRate_changed(quint64 value) { m_settings.m_sampleRate = value; @@ -558,6 +541,11 @@ void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) if (jsonObject.contains("DaemonSourceReport")) { QJsonObject report = jsonObject["DaemonSourceReport"].toObject(); + m_deviceCenterFrequency = report["deviceCenterFreq"].toInt() * 1000; + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->centerFrequency->setValue(m_deviceCenterFrequency/1000); + int remoteRate = report["deviceSampleRate"].toInt(); + ui->remoteRateText->setText(tr("%1k").arg((float)(remoteRate) / 1000)); int queueSize = report["queueSize"].toInt(); queueSize = queueSize == 0 ? 10 : queueSize; int queueLength = report["queueLength"].toInt(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index 683a13774..186a012f6 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -80,8 +80,8 @@ public: QString getName() const; void resetToDefaults(); - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); + virtual qint64 getCenterFrequency() const { return m_deviceCenterFrequency; } + virtual void setCenterFrequency(qint64 centerFrequency __attribute__((unused))) {} QByteArray serialize() const; bool deserialize(const QByteArray& data); virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } @@ -128,7 +128,7 @@ private: void displayTime(); void sendControl(bool force = false); void sendSettings(); - void updateSampleRateAndFrequency(); + void updateSampleRate(); void updateTxDelayTooltip(); void displayEventCounts(); void displayEventStatus(int recoverableCount, int unrecoverableCount); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 9e8e6680a..760a65de5 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -2,6 +2,9 @@ SDRdaemonSinkGui + + true + 0 @@ -114,7 +117,7 @@ - true + false @@ -146,24 +149,42 @@ - - - kHz - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - + + + + + + + kHz + + + + + + + + + + + + 50 + 0 + + + + Remote baseband sample rate + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 026e1c112..064dfcfe5 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -47,6 +47,7 @@ const uint32_t SDRdaemonSinkOutput::NbSamplesForRateCorrection = 5000000; SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_settings(), + m_centerFrequency(0), m_sdrDaemonSinkThread(0), m_deviceDescription("SDRdaemonSink"), m_startingTimeStamp(0), @@ -85,7 +86,6 @@ bool SDRdaemonSinkOutput::start() m_sdrDaemonSinkThread = new SDRdaemonSinkThread(&m_sampleSourceFifo); m_sdrDaemonSinkThread->setDataAddress(m_settings.m_dataAddress, m_settings.m_dataPort); - m_sdrDaemonSinkThread->setCenterFrequency(m_settings.m_centerFrequency); m_sdrDaemonSinkThread->setSamplerate(m_settings.m_sampleRate); m_sdrDaemonSinkThread->setNbBlocksFEC(m_settings.m_nbFECBlocks); m_sdrDaemonSinkThread->connectTimer(m_masterTimer); @@ -161,22 +161,7 @@ int SDRdaemonSinkOutput::getSampleRate() const quint64 SDRdaemonSinkOutput::getCenterFrequency() const { - return m_settings.m_centerFrequency; -} - -void SDRdaemonSinkOutput::setCenterFrequency(qint64 centerFrequency) -{ - SDRdaemonSinkSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; - - MsgConfigureSDRdaemonSink* message = MsgConfigureSDRdaemonSink::create(settings, false); - m_inputMessageQueue.push(message); - - if (m_guiMessageQueue) - { - MsgConfigureSDRdaemonSink* messageToGUI = MsgConfigureSDRdaemonSink::create(settings, false); - m_guiMessageQueue->push(messageToGUI); - } + return m_centerFrequency; } std::time_t SDRdaemonSinkOutput::getStartingTimeStamp() const @@ -262,15 +247,6 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b } } - if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency)) - { - if (m_sdrDaemonSinkThread != 0) { - m_sdrDaemonSinkThread->setCenterFrequency(settings.m_centerFrequency); - } - - forwardChange = true; - } - if (force || (m_settings.m_sampleRate != settings.m_sampleRate)) { if (m_sdrDaemonSinkThread != 0) { @@ -316,7 +292,6 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b mutexLocker.unlock(); qDebug() << "SDRdaemonSinkOutput::applySettings:" - << " m_centerFrequency: " << settings.m_centerFrequency << " m_sampleRate: " << settings.m_sampleRate << " m_txDelay: " << settings.m_txDelay << " m_nbFECBlocks: " << settings.m_nbFECBlocks @@ -327,7 +302,7 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b if (forwardChange) { - DSPSignalNotification *notif = new DSPSignalNotification(settings.m_sampleRate, settings.m_centerFrequency); + DSPSignalNotification *notif = new DSPSignalNotification(settings.m_sampleRate, m_centerFrequency); m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); } @@ -378,9 +353,6 @@ int SDRdaemonSinkOutput::webapiSettingsPutPatch( { SDRdaemonSinkSettings settings = m_settings; - if (deviceSettingsKeys.contains("centerFrequency")) { - settings.m_centerFrequency = response.getSdrDaemonSinkSettings()->getCenterFrequency(); - } if (deviceSettingsKeys.contains("sampleRate")) { settings.m_sampleRate = response.getSdrDaemonSinkSettings()->getSampleRate(); } @@ -434,7 +406,7 @@ int SDRdaemonSinkOutput::webapiReportGet( void SDRdaemonSinkOutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSinkSettings& settings) { - response.getSdrDaemonSinkSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getSdrDaemonSinkSettings()->setCenterFrequency(m_centerFrequency); response.getSdrDaemonSinkSettings()->setSampleRate(settings.m_sampleRate); response.getSdrDaemonSinkSettings()->setTxDelay(settings.m_txDelay); response.getSdrDaemonSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); @@ -514,6 +486,7 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) if (jsonObject.contains("DaemonSourceReport")) { QJsonObject report = jsonObject["DaemonSourceReport"].toObject(); + m_centerFrequency = report["deviceCenterFreq"].toInt() * 1000; int queueSize = report["queueSize"].toInt(); queueSize = queueSize == 0 ? 10 : queueSize; int queueLength = report["queueLength"].toInt(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index 30fc4ee2e..9e0d9fc66 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -136,7 +136,7 @@ public: virtual const QString& getDeviceDescription() const; virtual int getSampleRate() const; virtual quint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); + virtual void setCenterFrequency(qint64 centerFrequency __attribute__((unused))) {} std::time_t getStartingTimeStamp() const; virtual bool handleMessage(const Message& message); @@ -168,6 +168,7 @@ private: DeviceSinkAPI *m_deviceAPI; QMutex m_mutex; SDRdaemonSinkSettings m_settings; + uint64_t m_centerFrequency; SDRdaemonSinkThread* m_sdrDaemonSinkThread; QString m_deviceDescription; std::time_t m_startingTimeStamp; diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h index f84b5f8a8..39e40bb0c 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h @@ -48,7 +48,6 @@ public: void startWork(); void stopWork(); - void setCenterFrequency(uint64_t centerFrequency) { m_udpSinkFEC.setCenterFrequency(centerFrequency); } void setSamplerate(int samplerate); void setNbBlocksFEC(uint32_t nbBlocksFEC) { m_udpSinkFEC.setNbBlocksFEC(nbBlocksFEC); }; void setTxDelay(uint32_t txDelay) { m_udpSinkFEC.setTxDelay(txDelay); }; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index e1bad17d5..d00213e87 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -28,7 +28,6 @@ MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) UDPSinkFEC::UDPSinkFEC() : - m_centerFrequency(100000), m_sampleRate(48000), m_sampleBytes(1), m_sampleBits(8), @@ -103,7 +102,7 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk gettimeofday(&tv, 0); // create meta data TODO: semaphore - metaData.m_centerFrequency = m_centerFrequency; + metaData.m_centerFrequency = 0; // frequency not set by stream metaData.m_sampleRate = m_sampleRate; metaData.m_sampleBytes = m_sampleBytes; metaData.m_sampleBits = m_sampleBits; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index 0b00d6a52..337a19ea5 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -109,9 +109,6 @@ public: return ret; } - /** 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; } @@ -131,7 +128,6 @@ public: private: std::string m_error; - uint32_t m_centerFrequency; //!< center frequency in kHz uint32_t m_sampleRate; //!< sample rate in Hz uint8_t m_sampleBytes; //!< number of bytes per sample uint8_t m_sampleBits; //!< number of effective bits per sample From 4518984ddba72c6afb213b516e0a7b7a7b39cc44 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 7 Sep 2018 09:22:17 +0200 Subject: [PATCH 710/956] SDRDaemonSink: wait for queue stabilization to start rate control --- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp | 6 +++++- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 064dfcfe5..cf76ab08a 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -58,6 +58,7 @@ SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_lastSampleCount(0), m_lastRemoteTimestampRateCorrection(0), m_lastTimestampRateCorrection(0), + m_lastQueueLength(-2), m_nbRemoteSamplesSinceRateCorrection(0), m_nbSamplesSinceRateCorrection(0), m_chunkSizeCorrection(0) @@ -93,6 +94,7 @@ bool SDRdaemonSinkOutput::start() m_lastRemoteTimestampRateCorrection = 0; m_lastTimestampRateCorrection = 0; + m_lastQueueLength = -2; // set first value out of bounds double delay = ((127*127*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); @@ -514,7 +516,8 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) uint64_t timestampUs = tv.tv_sec*1000000ULL + tv.tv_usec; - if (m_lastRemoteTimestampRateCorrection == 0) + // on initial state wait for queue stabilization + if ((m_lastRemoteTimestampRateCorrection == 0) && (queueLength >= m_lastQueueLength-1) && (queueLength <= m_lastQueueLength+1)) { m_lastRemoteTimestampRateCorrection = remoteTimestampUs; m_lastTimestampRateCorrection = timestampUs; @@ -545,6 +548,7 @@ void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) m_lastRemoteSampleCount = remoteSampleCount; m_lastSampleCount = sampleCount; + m_lastQueueLength = queueLength; } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index 9e0d9fc66..741c6d1bd 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -183,6 +183,7 @@ private: uint32_t m_lastSampleCount; uint64_t m_lastRemoteTimestampRateCorrection; uint64_t m_lastTimestampRateCorrection; + int m_lastQueueLength; uint32_t m_nbRemoteSamplesSinceRateCorrection; uint32_t m_nbSamplesSinceRateCorrection; int m_chunkSizeCorrection; From 6e750b206f8e62483ac23eddf925c6f1918e0ae6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 8 Sep 2018 19:06:48 +0200 Subject: [PATCH 711/956] DaemonSink: added server plugin --- pluginssrv/channelrx/CMakeLists.txt | 5 ++ .../channelrx/daemonsink/CMakeLists.txt | 48 +++++++++++++++++++ sdrbase/resources/webapi/doc/html2/index.html | 8 +++- .../doc/swagger/include/DaemonSink.yaml | 4 ++ .../api/swagger/include/DaemonSink.yaml | 4 ++ swagger/sdrangel/code/html2/index.html | 8 +++- .../code/qt5/client/SWGDaemonSinkSettings.cpp | 44 +++++++++++++++++ .../code/qt5/client/SWGDaemonSinkSettings.h | 12 +++++ 8 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 pluginssrv/channelrx/daemonsink/CMakeLists.txt diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index 8864a4058..f0b816890 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -7,6 +7,11 @@ if((LIBDSDCC_FOUND AND LIBMBE_FOUND) OR BUILD_DEBIAN) add_subdirectory(demoddsd) endif() +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(daemonsink) +endif(CM256CC_FOUND) + add_subdirectory(demodnfm) add_subdirectory(demodssb) add_subdirectory(demodwfm) diff --git a/pluginssrv/channelrx/daemonsink/CMakeLists.txt b/pluginssrv/channelrx/daemonsink/CMakeLists.txt new file mode 100644 index 000000000..07000cde0 --- /dev/null +++ b/pluginssrv/channelrx/daemonsink/CMakeLists.txt @@ -0,0 +1,48 @@ +project(daemonsink) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/daemonsink") + +set(daemonsink_SOURCES + ${PLUGIN_PREFIX}/daemonsink.cpp + ${PLUGIN_PREFIX}/daemonsinksettings.cpp + ${PLUGIN_PREFIX}/daemonsinkthread.cpp + ${PLUGIN_PREFIX}/daemonsinkplugin.cpp +) + +set(daemonsink_HEADERS + ${PLUGIN_PREFIX}/daemonsink.h + ${PLUGIN_PREFIX}/daemonsinksettings.h + ${PLUGIN_PREFIX}/daemonsinkthread.h + ${PLUGIN_PREFIX}/daemonsinkplugin.h +) + +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) + +add_library(daemonsinksrv SHARED + ${daemonsink_SOURCES} + ${daemonsink_HEADERS_MOC} +) + +target_link_libraries(daemonsinksrv + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrdaemon + swagger +) + +target_link_libraries(daemonsinksrv Qt5::Core) + +install(TARGETS daemonsinksrv DESTINATION lib/pluginssrv/channelrx) diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index d0824c2b9..cf816b8ca 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1693,6 +1693,12 @@ margin-bottom: 20px; "txDelay" : { "type" : "integer", "description" : "Minimum delay in ms between consecutive USB blocks transmissions" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" } }, "description" : "Daemon channel sink settings" @@ -28724,7 +28730,7 @@ except ApiException as e:
    - Generated 2018-09-06T20:10:22.329+02:00 + Generated 2018-09-08T18:41:07.952+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/DaemonSink.yaml b/sdrbase/resources/webapi/doc/swagger/include/DaemonSink.yaml index 5e4c881d8..f381ced78 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/DaemonSink.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/DaemonSink.yaml @@ -13,3 +13,7 @@ DaemonSinkSettings: txDelay: description: "Minimum delay in ms between consecutive USB blocks transmissions" type: integer + rgbColor: + type: integer + title: + type: string diff --git a/swagger/sdrangel/api/swagger/include/DaemonSink.yaml b/swagger/sdrangel/api/swagger/include/DaemonSink.yaml index 5e4c881d8..f381ced78 100644 --- a/swagger/sdrangel/api/swagger/include/DaemonSink.yaml +++ b/swagger/sdrangel/api/swagger/include/DaemonSink.yaml @@ -13,3 +13,7 @@ DaemonSinkSettings: txDelay: description: "Minimum delay in ms between consecutive USB blocks transmissions" type: integer + rgbColor: + type: integer + title: + type: string diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index d0824c2b9..cf816b8ca 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1693,6 +1693,12 @@ margin-bottom: 20px; "txDelay" : { "type" : "integer", "description" : "Minimum delay in ms between consecutive USB blocks transmissions" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" } }, "description" : "Daemon channel sink settings" @@ -28724,7 +28730,7 @@ except ApiException as e:
    - Generated 2018-09-06T20:10:22.329+02:00 + Generated 2018-09-08T18:41:07.952+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp index 9f848ff0f..e9f21b8e3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.cpp @@ -36,6 +36,10 @@ SWGDaemonSinkSettings::SWGDaemonSinkSettings() { m_data_port_isSet = false; tx_delay = 0; m_tx_delay_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; } SWGDaemonSinkSettings::~SWGDaemonSinkSettings() { @@ -52,6 +56,10 @@ SWGDaemonSinkSettings::init() { m_data_port_isSet = false; tx_delay = 0; m_tx_delay_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; } void @@ -62,6 +70,10 @@ SWGDaemonSinkSettings::cleanup() { } + + if(title != nullptr) { + delete title; + } } SWGDaemonSinkSettings* @@ -83,6 +95,10 @@ SWGDaemonSinkSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&tx_delay, pJson["txDelay"], "qint32", ""); + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + } QString @@ -111,6 +127,12 @@ SWGDaemonSinkSettings::asJsonObject() { if(m_tx_delay_isSet){ obj->insert("txDelay", QJsonValue(tx_delay)); } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } return obj; } @@ -155,6 +177,26 @@ SWGDaemonSinkSettings::setTxDelay(qint32 tx_delay) { this->m_tx_delay_isSet = true; } +qint32 +SWGDaemonSinkSettings::getRgbColor() { + return rgb_color; +} +void +SWGDaemonSinkSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGDaemonSinkSettings::getTitle() { + return title; +} +void +SWGDaemonSinkSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + bool SWGDaemonSinkSettings::isSet(){ @@ -164,6 +206,8 @@ SWGDaemonSinkSettings::isSet(){ if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} if(m_data_port_isSet){ isObjectUpdated = true; break;} if(m_tx_delay_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h index 38750cee9..9af9d232c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDaemonSinkSettings.h @@ -54,6 +54,12 @@ public: qint32 getTxDelay(); void setTxDelay(qint32 tx_delay); + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + virtual bool isSet() override; @@ -70,6 +76,12 @@ private: qint32 tx_delay; bool m_tx_delay_isSet; + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + }; } From 4c0c51b17db02ea0b142d6961cff1e2a34754722 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 8 Sep 2018 19:24:38 +0200 Subject: [PATCH 712/956] DaemonSink: Web API: implement title and color --- plugins/channelrx/daemonsink/daemonsink.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 51b70bb71..051101adf 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -389,6 +389,14 @@ int DaemonSink::webapiSettingsPutPatch( } } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getDaemonSinkSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getDaemonSinkSettings()->getTitle(); + } + + MsgConfigureDaemonSink *msg = MsgConfigureDaemonSink::create(settings, force); m_inputMessageQueue.push(msg); @@ -416,4 +424,12 @@ void DaemonSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& re } response.getDaemonSinkSettings()->setDataPort(settings.m_dataPort); + response.getDaemonSinkSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getDaemonSinkSettings()->getTitle()) { + *response.getDaemonSinkSettings()->getTitle() = settings.m_title; + } else { + response.getDaemonSinkSettings()->setTitle(new QString(settings.m_title)); + } + } From 95b105f453d2d395fbbec070824afb4ab7621fe6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 8 Sep 2018 23:30:22 +0200 Subject: [PATCH 713/956] SDRDaemonSource: adaptation to remote DaemonSink channel --- .../sdrdaemonsource/sdrdaemonsourcegui.cpp | 251 +++++----- .../sdrdaemonsource/sdrdaemonsourcegui.h | 35 +- .../sdrdaemonsource/sdrdaemonsourcegui.ui | 433 ++++-------------- .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 199 +------- .../sdrdaemonsource/sdrdaemonsourceinput.h | 1 - .../sdrdaemonsourcesettings.cpp | 38 +- .../sdrdaemonsource/sdrdaemonsourcesettings.h | 12 +- sdrbase/resources/webapi/doc/html2/index.html | 32 +- .../doc/swagger/include/SDRDaemonSource.yaml | 24 +- .../api/swagger/include/SDRDaemonSource.yaml | 24 +- swagger/sdrangel/code/html2/index.html | 32 +- .../qt5/client/SWGSDRdaemonSourceSettings.cpp | 214 ++------- .../qt5/client/SWGSDRdaemonSourceSettings.h | 60 +-- 13 files changed, 333 insertions(+), 1022 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 275436901..61933dfac 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -24,14 +24,10 @@ #include #include #include - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#endif +#include +#include +#include +#include #include "ui_sdrdaemonsourcegui.h" #include "gui/colormapper.h" @@ -40,9 +36,9 @@ #include "dsp/dspcommands.h" #include "mainwindow.h" #include "util/simpleserializer.h" - -#include +#include "device/devicesourceapi.h" #include "device/deviceuiset.h" + #include "sdrdaemonsourcegui.h" SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : @@ -80,12 +76,6 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, 0, 9999999U); - ui->freq->setColorMapper(ColorMapper(ColorMapper::GrayGold)); - ui->freq->setValueRange(7, 0, 9999999U); - - ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->sampleRate->setValueRange(7, 32000U, 9999999U); - displaySettings(); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); @@ -97,6 +87,9 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + m_eventsTime.start(); displayEventCounts(); displayEventTimer(); @@ -107,6 +100,8 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent SDRdaemonSourceGui::~SDRdaemonSourceGui() { + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; delete ui; } @@ -149,7 +144,6 @@ bool SDRdaemonSourceGui::deserialize(const QByteArray& data) if (m_settings.deserialize(data)) { - updateTxDelay(); displaySettings(); m_forceSettings = true; sendSettings(); @@ -167,10 +161,8 @@ qint64 SDRdaemonSourceGui::getCenterFrequency() const return m_streamCenterFrequency; } -void SDRdaemonSourceGui::setCenterFrequency(qint64 centerFrequency) +void SDRdaemonSourceGui::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) { - m_settings.m_centerFrequency = centerFrequency; - sendSettings(); } bool SDRdaemonSourceGui::handleMessage(const Message& message) @@ -220,10 +212,8 @@ bool SDRdaemonSourceGui::handleMessage(const Message& message) int nbFECBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getNbFECBlocksPerFrame(); - if (m_nbFECBlocks != nbFECBlocks) - { + if (m_nbFECBlocks != nbFECBlocks) { m_nbFECBlocks = nbFECBlocks; - updateTxDelay(); } updateWithStreamTime(); @@ -256,10 +246,8 @@ void SDRdaemonSourceGui::handleInputMessages() { DSPSignalNotification* notif = (DSPSignalNotification*) message; - if (notif->getSampleRate() != m_streamSampleRate) - { + if (notif->getSampleRate() != m_streamSampleRate) { m_streamSampleRate = notif->getSampleRate(); - updateTxDelay(); } m_streamCenterFrequency = notif->getCenterFrequency(); @@ -289,21 +277,9 @@ void SDRdaemonSourceGui::updateSampleRateAndFrequency() ui->deviceRateText->setText(tr("%1k").arg((float)m_streamSampleRate / 1000)); blockApplySettings(true); ui->centerFrequency->setValue(m_streamCenterFrequency / 1000); - ui->freq->setValue(m_streamCenterFrequency / 1000); blockApplySettings(false); } -void SDRdaemonSourceGui::updateTxDelay() -{ - if (m_streamSampleRate == 0) { - m_txDelay = 0.0; // 0 value will not set the Tx delay - } else { - m_txDelay = ((127*127*m_settings.m_txDelay) / m_streamSampleRate)/(128 + m_nbFECBlocks); - } - - ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(m_txDelay*1e6, 'f', 0))); -} - void SDRdaemonSourceGui::displaySettings() { blockApplySettings(true); @@ -311,24 +287,10 @@ void SDRdaemonSourceGui::displaySettings() ui->centerFrequency->setValue(m_streamCenterFrequency / 1000); ui->deviceRateText->setText(tr("%1k").arg(m_streamSampleRate / 1000.0)); - ui->freq->setValue(m_streamCenterFrequency / 1000); - ui->decim->setCurrentIndex(m_settings.m_log2Decim); - ui->fcPos->setCurrentIndex(m_settings.m_fcPos); - ui->sampleRate->setValue(m_settings.m_sampleRate); - ui->specificParms->setText(m_settings.m_specificParameters); - ui->specificParms->setCursorPosition(0); - ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay*100)); - ui->nbFECBlocks->setValue(m_settings.m_nbFECBlocks); - QString nstr = QString("%1").arg(m_settings.m_nbFECBlocks, 2, 10, QChar('0')); - ui->nbFECBlocksText->setText(nstr); - - QString s0 = QString::number(128 + m_settings.m_nbFECBlocks, 'f', 0); - ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s0).arg(nstr)); - - ui->address->setText(m_settings.m_address); + ui->apiAddress->setText(m_settings.m_apiAddress); + ui->apiPort->setText(tr("%1").arg(m_settings.m_apiPort)); ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); - ui->controlPort->setText(tr("%1").arg(m_settings.m_controlPort)); - ui->specificParms->setText(m_settings.m_specificParameters); + ui->dataAddress->setText(m_settings.m_dataAddress); ui->dcOffset->setChecked(m_settings.m_dcBlock); ui->iqImbalance->setChecked(m_settings.m_iqCorrection); @@ -342,45 +304,48 @@ void SDRdaemonSourceGui::sendSettings() m_updateTimer.start(100); } -void SDRdaemonSourceGui::on_applyButton_clicked(bool checked __attribute__((unused))) +void SDRdaemonSourceGui::on_apiApplyButton_clicked(bool checked __attribute__((unused))) { - m_settings.m_address = ui->address->text(); + m_settings.m_apiAddress = ui->apiAddress->text(); - bool send = false; bool ctlOk; - int udpCtlPort = ui->controlPort->text().toInt(&ctlOk); + int udpApiPort = ui->apiPort->text().toInt(&ctlOk); - if((ctlOk) && (udpCtlPort >= 1024) && (udpCtlPort < 65535)) - { - m_settings.m_controlPort = udpCtlPort; - send = true; + if((ctlOk) && (udpApiPort >= 1024) && (udpApiPort < 65535)) { + m_settings.m_apiPort = udpApiPort; } + sendSettings(); +} + +void SDRdaemonSourceGui::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + bool dataOk; int udpDataPort = ui->dataPort->text().toInt(&dataOk); - if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) - { + if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) { m_settings.m_dataPort = udpDataPort; - send = true; } - if (send) { - sendSettings(); - } -} - -void SDRdaemonSourceGui::on_sendButton_clicked(bool checked __attribute__((unused))) -{ - updateTxDelay(); - m_forceSettings = true; sendSettings(); - ui->specificParms->setCursorPosition(0); } -void SDRdaemonSourceGui::on_address_returnPressed() +void SDRdaemonSourceGui::on_apiAddress_returnPressed() { - m_settings.m_address = ui->address->text(); + m_settings.m_apiAddress = ui->apiAddress->text(); + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); + + sendSettings(); +} + +void SDRdaemonSourceGui::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); sendSettings(); } @@ -400,18 +365,23 @@ void SDRdaemonSourceGui::on_dataPort_returnPressed() } } -void SDRdaemonSourceGui::on_controlPort_returnPressed() +void SDRdaemonSourceGui::on_apiPort_returnPressed() { bool ctlOk; - int udpCtlPort = ui->controlPort->text().toInt(&ctlOk); + int udpApiPort = ui->apiPort->text().toInt(&ctlOk); - if((!ctlOk) || (udpCtlPort < 1024) || (udpCtlPort > 65535)) + if((!ctlOk) || (udpApiPort < 1024) || (udpApiPort > 65535)) { return; } else { - m_settings.m_controlPort = udpCtlPort; + m_settings.m_apiPort = udpApiPort; + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); + sendSettings(); } } @@ -428,54 +398,6 @@ void SDRdaemonSourceGui::on_iqImbalance_toggled(bool checked) sendSettings(); } -void SDRdaemonSourceGui::on_freq_changed(quint64 value) -{ - m_settings.m_centerFrequency = value * 1000; - sendSettings(); -} - -void SDRdaemonSourceGui::on_sampleRate_changed(quint64 value) -{ - m_settings.m_sampleRate = value; - sendSettings(); -} - -void SDRdaemonSourceGui::on_specificParms_returnPressed() -{ - if ((ui->specificParms->text()).size() > 0) { - m_settings.m_specificParameters = ui->specificParms->text(); - sendSettings(); - } -} - -void SDRdaemonSourceGui::on_decim_currentIndexChanged(int index __attribute__((unused))) -{ - m_settings.m_log2Decim = ui->decim->currentIndex(); - sendSettings(); -} - -void SDRdaemonSourceGui::on_fcPos_currentIndexChanged(int index __attribute__((unused))) -{ - m_settings.m_fcPos = ui->fcPos->currentIndex(); - sendSettings(); -} - -void SDRdaemonSourceGui::on_txDelay_valueChanged(int value) -{ - m_settings.m_txDelay = value / 100.0; - ui->txDelayText->setText(tr("%1").arg(value)); - updateTxDelay(); - sendSettings(); -} - -void SDRdaemonSourceGui::on_nbFECBlocks_valueChanged(int value) -{ - m_settings.m_nbFECBlocks = value; - QString nstr = QString("%1").arg(m_settings.m_nbFECBlocks, 2, 10, QChar('0')); - ui->nbFECBlocksText->setText(nstr); - sendSettings(); -} - void SDRdaemonSourceGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) @@ -631,3 +553,72 @@ void SDRdaemonSourceGui::updateStatus() ui->startStop->setEnabled(false); } } + +void SDRdaemonSourceGui::networkManagerFinished(QNetworkReply *reply) +{ + if (reply->error()) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + ui->statusText->setText(reply->errorString()); + return; + } + + QString answer = reply->readAll(); + + try + { + QByteArray jsonBytes(answer.toStdString().c_str()); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error); + + if (error.error == QJsonParseError::NoError) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background-color : green; }"); + ui->statusText->setText(QString("API OK")); + analyzeApiReply(doc.object()); + } + else + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); + ui->statusText->setText(QString("JSON error. See log")); + qInfo().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + } + } + catch (const std::exception& ex) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + QString errorMsg = QString("Error parsing request: ") + ex.what(); + ui->statusText->setText("Error parsing request. See log for details"); + qInfo().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + } +} + +void SDRdaemonSourceGui::analyzeApiReply(const QJsonObject& jsonObject) +{ + QString infoLine; + + if (jsonObject.contains("version")) { + infoLine = "v" + jsonObject["version"].toString(); + } + + if (jsonObject.contains("qtVersion")) { + infoLine += " Qt" + jsonObject["qtVersion"].toString(); + } + + if (jsonObject.contains("architecture")) { + infoLine += " " + jsonObject["architecture"].toString(); + } + + if (jsonObject.contains("os")) { + infoLine += " " + jsonObject["os"].toString(); + } + + if (jsonObject.contains("dspRxBits") && jsonObject.contains("dspTxBits")) { + infoLine += QString(" %1/%2b").arg(jsonObject["dspRxBits"].toInt()).arg(jsonObject["dspTxBits"].toInt()); + } + + if (infoLine.size() > 0) { + ui->infoText->setText(infoLine); + } +} diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h index b85dd08df..b0b5577d5 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h @@ -17,16 +17,21 @@ #ifndef INCLUDE_SDRDAEMONSOURCEGUI_H #define INCLUDE_SDRDAEMONSOURCEGUI_H -#include -#include -#include #include +#include +#include +#include + +#include "plugin/plugininstancegui.h" #include "util/messagequeue.h" #include "sdrdaemonsourceinput.h" class DeviceUISet; +class QNetworkAccessManager; +class QNetworkReply; +class QJsonObject; namespace Ui { class SDRdaemonSourceGui; @@ -98,38 +103,36 @@ private: QPalette m_paletteGreenText; QPalette m_paletteWhiteText; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + void blockApplySettings(bool block); void displaySettings(); void displayTime(); void sendSettings(); void updateWithAcquisition(); void updateWithStreamTime(); - void updateSampleRateAndFrequency(); - void updateTxDelay(); + void updateSampleRateAndFrequency(); void displayEventCounts(); void displayEventTimer(); + void analyzeApiReply(const QJsonObject& jsonObject); private slots: void handleInputMessages(); - void on_applyButton_clicked(bool checked); + void on_apiApplyButton_clicked(bool checked); + void on_dataApplyButton_clicked(bool checked); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); - void on_address_returnPressed(); + void on_apiAddress_returnPressed(); + void on_apiPort_returnPressed(); + void on_dataAddress_returnPressed(); void on_dataPort_returnPressed(); - void on_controlPort_returnPressed(); - void on_sendButton_clicked(bool checked); - void on_freq_changed(quint64 value); - void on_sampleRate_changed(quint64 value); - void on_specificParms_returnPressed(); - void on_decim_currentIndexChanged(int index); - void on_fcPos_currentIndexChanged(int index); void on_startStop_toggled(bool checked); void on_record_toggled(bool checked); void on_eventCountsReset_clicked(bool checked); - void on_txDelay_valueChanged(int value); - void on_nbFECBlocks_valueChanged(int value); void updateHardware(); void updateStatus(); + void networkManagerFinished(QNetworkReply *reply); }; #endif // INCLUDE_SDRDAEMONSOURCEGUI_H diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui index d13313191..65506f809 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui @@ -6,14 +6,14 @@ 0 0 - 372 - 261 + 360 + 270
    - 372 - 261 + 360 + 270 @@ -373,41 +373,6 @@ - - - - - 24 - 24 - - - - Desired number of FEC blocks per frame - - - 64 - - - 1 - - - - - - - - 18 - 0 - - - - Desired number of FEC blocks per frame - - - 00 - - - @@ -577,28 +542,34 @@ - + - + + + + 30 + 0 + + - Addr: + API - + true - 110 + 120 0 - 110 + 120 16777215 @@ -617,52 +588,14 @@ - + - D: + : - - - true - - - - 60 - 0 - - - - - 60 - 16777215 - - - - Local data connection port - - - 00000 - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - C: - - - - - + 60 @@ -703,7 +636,7 @@ - + true @@ -721,54 +654,77 @@ - - - Qt::Horizontal - - - - - + - + + + + 30 + 0 + + - Fc: + Data - - - - 0 - 0 - + + + + 120 + 0 + + + + + 120 + 16777215 + + + + 000.000.000.000 + + + 0.0.0.0 + + + + + + + : + + + + + + + true - 32 - 16 + 60 + 0 - - - Liberation Mono - 12 - false - - - - PointingHandCursor + + + 60 + 16777215 + - Desired device center frequency + Local data connection port + + + 00000 - - - - - kHz + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -786,256 +742,37 @@ - - - Qt::Vertical - - - - - - - UDly - - - - - + - 24 - 24 - - - - Delay between consecutive UDP packets in percentage of nominal UDP packet process time - - - 10 - - - 90 - - - 1 - - - 50 - - - - - - - - 20 - 0 - - - - - 20 + 30 16777215 - 90 + Set - + - + - SR: + ... - - - - - 0 - 0 - - - - - 32 - 16 - - - - - Liberation Mono - 12 - false - - - - PointingHandCursor - - - Desired remote device sample rate - - - - - - - S/s - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Dec: - - - - - - - - 45 - 0 - - - - - 16777215 - 16777215 - - - - Decimation - - - 3 - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - 32 - - - - - 64 - - - - - - - - Fp: - - - - - - - - 50 - 16777215 - - - - Center frequency position (Infradyne, Supradyne, Centered) - - - 2 - - - - Inf - - - - - Sup - - - - - Cen - - - - - + - + - Sp: - - - - - - - Other parameters that are hardware specific - - - - - - - - 50 - 16777215 - - - - Send commands to remote SDRdaemonRx instance - - - Send + ... diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 9c1350a49..59ce673c1 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -57,12 +57,6 @@ SDRdaemonSourceInput::SDRdaemonSourceInput(DeviceSourceAPI *deviceAPI) : m_deviceDescription(), m_startingTimeStamp(0) { - m_sender = nn_socket(AF_SP, NN_PAIR); - assert(m_sender != -1); - int millis = 500; - int rc __attribute__((unused)) = nn_setsockopt (m_sender, NN_SOL_SOCKET, NN_SNDTIMEO, &millis, sizeof (millis)); - assert (rc == 0); - m_sampleFifo.setSize(96000 * 4); m_SDRdaemonUDPHandler = new SDRdaemonSourceUDPHandler(&m_sampleFifo, m_deviceAPI); @@ -141,29 +135,16 @@ const QString& SDRdaemonSourceInput::getDeviceDescription() const int SDRdaemonSourceInput::getSampleRate() const { - if (m_SDRdaemonUDPHandler->getSampleRate()) { - return m_SDRdaemonUDPHandler->getSampleRate(); - } else { - return m_settings.m_sampleRate / (1<getSampleRate(); } quint64 SDRdaemonSourceInput::getCenterFrequency() const { - if (m_SDRdaemonUDPHandler->getCenterFrequency()) { - return m_SDRdaemonUDPHandler->getCenterFrequency(); - } else { - return m_settings.m_centerFrequency; - } + return m_SDRdaemonUDPHandler->getCenterFrequency(); } -void SDRdaemonSourceInput::setCenterFrequency(qint64 centerFrequency) +void SDRdaemonSourceInput::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) { - SDRdaemonSourceSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; - - MsgConfigureSDRdaemonSource* message = MsgConfigureSDRdaemonSource::create(m_settings, false); - m_inputMessageQueue.push(message); } std::time_t SDRdaemonSourceInput::getStartingTimeStamp() const @@ -240,9 +221,7 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) void SDRdaemonSourceInput::applySettings(const SDRdaemonSourceSettings& settings, bool force) { QMutexLocker mutexLocker(&m_mutex); - bool changeTxDelay = false; std::ostringstream os; - int nbArgs = 0; QString remoteAddress; m_SDRdaemonUDPHandler->getRemoteAddress(remoteAddress); @@ -250,139 +229,26 @@ void SDRdaemonSourceInput::applySettings(const SDRdaemonSourceSettings& settings { m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); qDebug("SDRdaemonSourceInput::applySettings: corrections: DC block: %s IQ imbalance: %s", - m_settings.m_dcBlock ? "true" : "false", - m_settings.m_iqCorrection ? "true" : "false"); + settings.m_dcBlock ? "true" : "false", + settings.m_iqCorrection ? "true" : "false"); } - if (force || (m_settings.m_address != settings.m_address) || (m_settings.m_dataPort != settings.m_dataPort)) + if (force || (m_settings.m_dataAddress != settings.m_dataAddress) || (m_settings.m_dataPort != settings.m_dataPort)) { - m_SDRdaemonUDPHandler->configureUDPLink(settings.m_address, settings.m_dataPort); + m_SDRdaemonUDPHandler->configureUDPLink(settings.m_dataAddress, settings.m_dataPort); m_SDRdaemonUDPHandler->getRemoteAddress(remoteAddress); } - if (force || (remoteAddress != m_remoteAddress) || (m_settings.m_controlPort != settings.m_controlPort)) - { - int rc = nn_shutdown(m_sender, 0); - - if (rc < 0) { - qDebug() << "SDRdaemonSourceInput::applySettings: nn disconnection failed"; - } else { - qDebug() << "SDRdaemonSourceInput::applySettings: nn disconnection successful"; - } - - std::ostringstream os; - os << "tcp://" << remoteAddress.toStdString() << ":" << m_settings.m_controlPort; - std::string addrstrng = os.str(); - rc = nn_connect(m_sender, addrstrng.c_str()); - - if (rc < 0) { - qDebug() << "SDRdaemonSourceInput::applySettings: nn connexion to " << addrstrng.c_str() << " failed"; - } else { - qDebug() << "SDRdaemonSourceInput::applySettings: nn connexion to " << addrstrng.c_str() << " successful"; - } - } - - if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency)) - { - os << "freq=" << settings.m_centerFrequency; - nbArgs++; - } - - if (force || (m_settings.m_sampleRate != settings.m_sampleRate) || (m_settings.m_log2Decim != settings.m_log2Decim)) - { - if (nbArgs > 0) os << ","; - os << "srate=" << m_settings.m_sampleRate; - nbArgs++; - changeTxDelay = m_settings.m_sampleRate != settings.m_sampleRate; - } - - if (force || (m_settings.m_log2Decim != settings.m_log2Decim)) - { - if (nbArgs > 0) os << ","; - os << "decim=" << settings.m_log2Decim; - nbArgs++; - } - - if ((m_settings.m_fcPos != settings.m_fcPos) || force) - { - if (nbArgs > 0) os << ","; - os << "fcpos=" << m_settings.m_fcPos; - nbArgs++; - } - - if (force || (m_settings.m_nbFECBlocks != settings.m_nbFECBlocks)) - { - if (nbArgs > 0) os << ","; - os << "fecblk=" << m_settings.m_nbFECBlocks; - nbArgs++; - changeTxDelay = true; - } - - if (force || (m_settings.m_txDelay != settings.m_txDelay)) - { - changeTxDelay = true; - } - - if (changeTxDelay) - { - double delay = ((127*127*settings.m_txDelay) / settings.m_sampleRate)/(128 + settings.m_nbFECBlocks); - qDebug("SDRdaemonSourceInput::applySettings: Tx delay: %f us", delay*1e6); - - if (delay != 0.0) - { - if (nbArgs > 0) os << ","; - os << "txdelay=" << (int) (delay*1e6); - nbArgs++; - } - } - - if ((m_settings.m_specificParameters != settings.m_specificParameters) || force) - { - if (settings.m_specificParameters.size() > 0) - { - if (nbArgs > 0) os << ","; - os << settings.m_specificParameters.toStdString(); - nbArgs++; - } - } - - if (nbArgs > 0) - { - int config_size = os.str().size(); - int rc = nn_send(m_sender, (void *) os.str().c_str(), config_size, 0); - - if (rc != config_size) - { - qDebug() << "SDRdaemonSourceInput::applySettings: Cannot nn send to " - << " remoteAddress: " << remoteAddress - << " remotePort: " << settings.m_controlPort - << " message: " << os.str().c_str(); - } - else - { - qDebug() << "SDRdaemonSourceInput::applySettings: nn send to " - << "remoteAddress:" << remoteAddress - << "remotePort:" << settings.m_controlPort - << "message:" << os.str().c_str(); - } - } - mutexLocker.unlock(); m_settings = settings; m_remoteAddress = remoteAddress; qDebug() << "SDRdaemonSourceInput::applySettings: " - << " m_address: " << m_settings.m_address - << " m_remoteAddress: " << m_remoteAddress + << " m_dataAddress: " << m_settings.m_dataAddress << " m_dataPort: " << m_settings.m_dataPort - << " m_controlPort: " << m_settings.m_controlPort - << " m_centerFrequency: " << m_settings.m_centerFrequency - << " m_sampleRate: " << m_settings.m_sampleRate - << " m_log2Decim: " << m_settings.m_log2Decim - << " m_fcPos: " << m_settings.m_fcPos - << " m_txDelay: " << m_settings.m_txDelay - << " m_nbFECBlocks: " << m_settings.m_nbFECBlocks - << " m_specificParameters: " << m_settings.m_specificParameters; + << " m_apiAddress: " << m_settings.m_apiAddress + << " m_apiPort: " << m_settings.m_apiPort + << " m_remoteAddress: " << m_remoteAddress; } int SDRdaemonSourceInput::webapiRunGet( @@ -429,43 +295,24 @@ int SDRdaemonSourceInput::webapiSettingsPutPatch( { SDRdaemonSourceSettings settings = m_settings; - if (deviceSettingsKeys.contains("centerFrequency")) { - settings.m_centerFrequency = response.getSdrDaemonSourceSettings()->getCenterFrequency(); + if (deviceSettingsKeys.contains("apiAddress")) { + settings.m_apiAddress = *response.getSdrDaemonSourceSettings()->getApiAddress(); } - if (deviceSettingsKeys.contains("sampleRate")) { - settings.m_sampleRate = response.getSdrDaemonSourceSettings()->getSampleRate(); + if (deviceSettingsKeys.contains("apiPort")) { + settings.m_apiPort = response.getSdrDaemonSourceSettings()->getApiPort(); } - if (deviceSettingsKeys.contains("log2Decim")) { - settings.m_log2Decim = response.getSdrDaemonSourceSettings()->getLog2Decim(); - } - if (deviceSettingsKeys.contains("txDelay")) { - settings.m_txDelay = response.getSdrDaemonSourceSettings()->getTxDelay(); - } - if (deviceSettingsKeys.contains("nbFECBlocks")) { - settings.m_txDelay = response.getSdrDaemonSourceSettings()->getNbFecBlocks(); - } - if (deviceSettingsKeys.contains("address")) { - settings.m_address = *response.getSdrDaemonSourceSettings()->getAddress(); + if (deviceSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonSourceSettings()->getDataAddress(); } if (deviceSettingsKeys.contains("dataPort")) { settings.m_dataPort = response.getSdrDaemonSourceSettings()->getDataPort(); } - if (deviceSettingsKeys.contains("controlPort")) { - settings.m_controlPort = response.getSdrDaemonSourceSettings()->getControlPort(); - } - if (deviceSettingsKeys.contains("specificParameters")) { - settings.m_specificParameters = *response.getSdrDaemonSourceSettings()->getSpecificParameters(); - } if (deviceSettingsKeys.contains("dcBlock")) { settings.m_dcBlock = response.getSdrDaemonSourceSettings()->getDcBlock() != 0; } if (deviceSettingsKeys.contains("iqCorrection")) { settings.m_iqCorrection = response.getSdrDaemonSourceSettings()->getIqCorrection() != 0; } - if (deviceSettingsKeys.contains("fcPos")) { - int fcPos = response.getSdrDaemonSourceSettings()->getFcPos(); - settings.m_fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; - } if (deviceSettingsKeys.contains("fileRecordName")) { settings.m_fileRecordName = *response.getSdrDaemonSourceSettings()->getFileRecordName(); } @@ -485,18 +332,12 @@ int SDRdaemonSourceInput::webapiSettingsPutPatch( void SDRdaemonSourceInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSourceSettings& settings) { - response.getSdrDaemonSourceSettings()->setCenterFrequency(settings.m_centerFrequency); - response.getSdrDaemonSourceSettings()->setSampleRate(settings.m_sampleRate); - response.getSdrDaemonSourceSettings()->setLog2Decim(settings.m_log2Decim); - response.getSdrDaemonSourceSettings()->setTxDelay(settings.m_txDelay); - response.getSdrDaemonSourceSettings()->setNbFecBlocks(settings.m_nbFECBlocks); - response.getSdrDaemonSourceSettings()->setAddress(new QString(settings.m_address)); + response.getSdrDaemonSourceSettings()->setApiAddress(new QString(settings.m_apiAddress)); + response.getSdrDaemonSourceSettings()->setApiPort(settings.m_apiPort); + response.getSdrDaemonSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); response.getSdrDaemonSourceSettings()->setDataPort(settings.m_dataPort); - response.getSdrDaemonSourceSettings()->setControlPort(settings.m_controlPort); - response.getSdrDaemonSourceSettings()->setSpecificParameters(new QString(settings.m_specificParameters)); response.getSdrDaemonSourceSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); response.getSdrDaemonSourceSettings()->setIqCorrection(settings.m_iqCorrection); - response.getSdrDaemonSourceSettings()->setFcPos((int) settings.m_fcPos); if (response.getSdrDaemonSourceSettings()->getFileRecordName()) { *response.getSdrDaemonSourceSettings()->getFileRecordName() = settings.m_fileRecordName; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h index 6e035f606..44bc818e6 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h @@ -308,7 +308,6 @@ private: SDRdaemonSourceSettings m_settings; SDRdaemonSourceUDPHandler* m_SDRdaemonUDPHandler; QString m_remoteAddress; - int m_sender; QString m_deviceDescription; std::time_t m_startingTimeStamp; FileRecord *m_fileSink; //!< File sink to record device I/Q output diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp index fccb2981d..76b8a7fae 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp @@ -24,18 +24,12 @@ SDRdaemonSourceSettings::SDRdaemonSourceSettings() void SDRdaemonSourceSettings::resetToDefaults() { - m_centerFrequency = 435000*1000; - m_sampleRate = 256000; - m_log2Decim = 1; - m_txDelay = 0.5; - m_nbFECBlocks = 0; - m_address = "127.0.0.1"; - m_dataPort = 9092; - m_controlPort = 9093; - m_specificParameters = ""; + m_apiAddress = "127.0.0.1"; + m_apiPort = 9091; + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; m_dcBlock = false; m_iqCorrection = false; - m_fcPos = 2; // center m_fileRecordName = ""; } @@ -43,17 +37,12 @@ QByteArray SDRdaemonSourceSettings::serialize() const { SimpleSerializer s(1); - s.writeU64(1, m_sampleRate); - s.writeU32(2, m_log2Decim); - s.writeFloat(3, m_txDelay); - s.writeU32(4, m_nbFECBlocks); - s.writeString(5, m_address); + s.writeString(5, m_apiAddress); s.writeU32(6, m_dataPort); - s.writeU32(7, m_controlPort); - s.writeString(8, m_specificParameters); + s.writeU32(7, m_apiPort); + s.writeString(8, m_dataAddress); s.writeBool(9, m_dcBlock); s.writeBool(10, m_iqCorrection); - s.writeU32(11, m_fcPos); return s.final(); } @@ -71,19 +60,14 @@ bool SDRdaemonSourceSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { quint32 uintval; - d.readU64(1, &m_sampleRate, 48000); - d.readU32(2, &m_log2Decim, 0); - d.readFloat(3, &m_txDelay, 0.5); - d.readU32(4, &m_nbFECBlocks, 0); - d.readString(5, &m_address, "127.0.0.1"); + d.readString(5, &m_apiAddress, "127.0.0.1"); d.readU32(6, &uintval, 9090); m_dataPort = uintval % (1<<16); - d.readU32(7, &uintval, 9090); - m_controlPort = uintval % (1<<16); - d.readString(8, &m_specificParameters, ""); + d.readU32(7, &uintval, 9091); + m_dataPort = uintval % (1<<16); + d.readString(8, &m_dataAddress, "127.0.0.1"); d.readBool(9, &m_dcBlock, false); d.readBool(10, &m_iqCorrection, false); - d.readU32(11, &m_fcPos, 2); return true; } else diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h index d9907a92d..e1b010585 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h @@ -21,18 +21,12 @@ #include struct SDRdaemonSourceSettings { - quint64 m_centerFrequency; - quint64 m_sampleRate; - quint32 m_log2Decim; - float m_txDelay; - quint32 m_nbFECBlocks; - QString m_address; + QString m_apiAddress; + quint16 m_apiPort; + QString m_dataAddress; quint16 m_dataPort; - quint16 m_controlPort; - QString m_specificParameters; bool m_dcBlock; bool m_iqCorrection; - quint32 m_fcPos; QString m_fileRecordName; SDRdaemonSourceSettings(); diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index cf816b8ca..522745737 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3421,46 +3421,24 @@ margin-bottom: 20px; }; defs.SDRdaemonSourceSettings = { "properties" : { - "centerFrequency" : { - "type" : "integer", - "format" : "uint64" + "apiAddress" : { + "type" : "string" }, - "sampleRate" : { + "apiPort" : { "type" : "integer" }, - "log2Decim" : { - "type" : "integer" - }, - "txDelay" : { - "type" : "number", - "format" : "float", - "description" : "minimum delay in ms between two consecutive packets sending" - }, - "nbFECBlocks" : { - "type" : "integer" - }, - "address" : { + "dataAddress" : { "type" : "string" }, "dataPort" : { "type" : "integer" }, - "controlPort" : { - "type" : "integer" - }, - "specificParameters" : { - "type" : "string" - }, "dcBlock" : { "type" : "integer" }, "iqCorrection" : { "type" : "integer" }, - "fcPos" : { - "type" : "integer", - "description" : "0=Infra 1=Supra 2=Center" - }, "fileRecordName" : { "type" : "string" } @@ -28730,7 +28708,7 @@ except ApiException as e:
    - Generated 2018-09-08T18:41:07.952+02:00 + Generated 2018-09-08T22:13:50.971+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSource.yaml b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSource.yaml index 66bf84bd6..31fe6980c 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSource.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/SDRDaemonSource.yaml @@ -1,34 +1,18 @@ SDRdaemonSourceSettings: description: SDRdaemonSource properties: - centerFrequency: + apiAddress: + type: string + apiPort: type: integer - format: uint64 - sampleRate: - type: integer - log2Decim: - type: integer - txDelay: - description: minimum delay in ms between two consecutive packets sending - type: number - format: float - nbFECBlocks: - type: integer - address: + dataAddress: type: string dataPort: type: integer - controlPort: - type: integer - specificParameters: - type: string dcBlock: type: integer iqCorrection: type: integer - fcPos: - description: 0=Infra 1=Supra 2=Center - type: integer fileRecordName: type: string diff --git a/swagger/sdrangel/api/swagger/include/SDRDaemonSource.yaml b/swagger/sdrangel/api/swagger/include/SDRDaemonSource.yaml index 66bf84bd6..31fe6980c 100644 --- a/swagger/sdrangel/api/swagger/include/SDRDaemonSource.yaml +++ b/swagger/sdrangel/api/swagger/include/SDRDaemonSource.yaml @@ -1,34 +1,18 @@ SDRdaemonSourceSettings: description: SDRdaemonSource properties: - centerFrequency: + apiAddress: + type: string + apiPort: type: integer - format: uint64 - sampleRate: - type: integer - log2Decim: - type: integer - txDelay: - description: minimum delay in ms between two consecutive packets sending - type: number - format: float - nbFECBlocks: - type: integer - address: + dataAddress: type: string dataPort: type: integer - controlPort: - type: integer - specificParameters: - type: string dcBlock: type: integer iqCorrection: type: integer - fcPos: - description: 0=Infra 1=Supra 2=Center - type: integer fileRecordName: type: string diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index cf816b8ca..522745737 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -3421,46 +3421,24 @@ margin-bottom: 20px; }; defs.SDRdaemonSourceSettings = { "properties" : { - "centerFrequency" : { - "type" : "integer", - "format" : "uint64" + "apiAddress" : { + "type" : "string" }, - "sampleRate" : { + "apiPort" : { "type" : "integer" }, - "log2Decim" : { - "type" : "integer" - }, - "txDelay" : { - "type" : "number", - "format" : "float", - "description" : "minimum delay in ms between two consecutive packets sending" - }, - "nbFECBlocks" : { - "type" : "integer" - }, - "address" : { + "dataAddress" : { "type" : "string" }, "dataPort" : { "type" : "integer" }, - "controlPort" : { - "type" : "integer" - }, - "specificParameters" : { - "type" : "string" - }, "dcBlock" : { "type" : "integer" }, "iqCorrection" : { "type" : "integer" }, - "fcPos" : { - "type" : "integer", - "description" : "0=Infra 1=Supra 2=Center" - }, "fileRecordName" : { "type" : "string" } @@ -28730,7 +28708,7 @@ except ApiException as e:
    - Generated 2018-09-08T18:41:07.952+02:00 + Generated 2018-09-08T22:13:50.971+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp index a5048d1b6..5f3f904b7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.cpp @@ -28,30 +28,18 @@ SWGSDRdaemonSourceSettings::SWGSDRdaemonSourceSettings(QString* json) { } SWGSDRdaemonSourceSettings::SWGSDRdaemonSourceSettings() { - center_frequency = 0; - m_center_frequency_isSet = false; - sample_rate = 0; - m_sample_rate_isSet = false; - log2_decim = 0; - m_log2_decim_isSet = false; - tx_delay = 0.0f; - m_tx_delay_isSet = false; - nb_fec_blocks = 0; - m_nb_fec_blocks_isSet = false; - address = nullptr; - m_address_isSet = false; + api_address = nullptr; + m_api_address_isSet = false; + api_port = 0; + m_api_port_isSet = false; + data_address = nullptr; + m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; - control_port = 0; - m_control_port_isSet = false; - specific_parameters = nullptr; - m_specific_parameters_isSet = false; dc_block = 0; m_dc_block_isSet = false; iq_correction = 0; m_iq_correction_isSet = false; - fc_pos = 0; - m_fc_pos_isSet = false; file_record_name = nullptr; m_file_record_name_isSet = false; } @@ -62,48 +50,30 @@ SWGSDRdaemonSourceSettings::~SWGSDRdaemonSourceSettings() { void SWGSDRdaemonSourceSettings::init() { - center_frequency = 0; - m_center_frequency_isSet = false; - sample_rate = 0; - m_sample_rate_isSet = false; - log2_decim = 0; - m_log2_decim_isSet = false; - tx_delay = 0.0f; - m_tx_delay_isSet = false; - nb_fec_blocks = 0; - m_nb_fec_blocks_isSet = false; - address = new QString(""); - m_address_isSet = false; + api_address = new QString(""); + m_api_address_isSet = false; + api_port = 0; + m_api_port_isSet = false; + data_address = new QString(""); + m_data_address_isSet = false; data_port = 0; m_data_port_isSet = false; - control_port = 0; - m_control_port_isSet = false; - specific_parameters = new QString(""); - m_specific_parameters_isSet = false; dc_block = 0; m_dc_block_isSet = false; iq_correction = 0; m_iq_correction_isSet = false; - fc_pos = 0; - m_fc_pos_isSet = false; file_record_name = new QString(""); m_file_record_name_isSet = false; } void SWGSDRdaemonSourceSettings::cleanup() { - - - - - - if(address != nullptr) { - delete address; + if(api_address != nullptr) { + delete api_address; } - - if(specific_parameters != nullptr) { - delete specific_parameters; + if(data_address != nullptr) { + delete data_address; } @@ -124,30 +94,18 @@ SWGSDRdaemonSourceSettings::fromJson(QString &json) { void SWGSDRdaemonSourceSettings::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint32", ""); + ::SWGSDRangel::setValue(&api_address, pJson["apiAddress"], "QString", "QString"); - ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + ::SWGSDRangel::setValue(&api_port, pJson["apiPort"], "qint32", ""); - ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); - - ::SWGSDRangel::setValue(&tx_delay, pJson["txDelay"], "float", ""); - - ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); - - ::SWGSDRangel::setValue(&address, pJson["address"], "QString", "QString"); + ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); - ::SWGSDRangel::setValue(&control_port, pJson["controlPort"], "qint32", ""); - - ::SWGSDRangel::setValue(&specific_parameters, pJson["specificParameters"], "QString", "QString"); - ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); - ::SWGSDRangel::setValue(&fc_pos, pJson["fcPos"], "qint32", ""); - ::SWGSDRangel::setValue(&file_record_name, pJson["fileRecordName"], "QString", "QString"); } @@ -166,42 +124,24 @@ SWGSDRdaemonSourceSettings::asJson () QJsonObject* SWGSDRdaemonSourceSettings::asJsonObject() { QJsonObject* obj = new QJsonObject(); - if(m_center_frequency_isSet){ - obj->insert("centerFrequency", QJsonValue(center_frequency)); + if(api_address != nullptr && *api_address != QString("")){ + toJsonValue(QString("apiAddress"), api_address, obj, QString("QString")); } - if(m_sample_rate_isSet){ - obj->insert("sampleRate", QJsonValue(sample_rate)); + if(m_api_port_isSet){ + obj->insert("apiPort", QJsonValue(api_port)); } - if(m_log2_decim_isSet){ - obj->insert("log2Decim", QJsonValue(log2_decim)); - } - if(m_tx_delay_isSet){ - obj->insert("txDelay", QJsonValue(tx_delay)); - } - if(m_nb_fec_blocks_isSet){ - obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); - } - if(address != nullptr && *address != QString("")){ - toJsonValue(QString("address"), address, obj, QString("QString")); + if(data_address != nullptr && *data_address != QString("")){ + toJsonValue(QString("dataAddress"), data_address, obj, QString("QString")); } if(m_data_port_isSet){ obj->insert("dataPort", QJsonValue(data_port)); } - if(m_control_port_isSet){ - obj->insert("controlPort", QJsonValue(control_port)); - } - if(specific_parameters != nullptr && *specific_parameters != QString("")){ - toJsonValue(QString("specificParameters"), specific_parameters, obj, QString("QString")); - } if(m_dc_block_isSet){ obj->insert("dcBlock", QJsonValue(dc_block)); } if(m_iq_correction_isSet){ obj->insert("iqCorrection", QJsonValue(iq_correction)); } - if(m_fc_pos_isSet){ - obj->insert("fcPos", QJsonValue(fc_pos)); - } if(file_record_name != nullptr && *file_record_name != QString("")){ toJsonValue(QString("fileRecordName"), file_record_name, obj, QString("QString")); } @@ -209,64 +149,34 @@ SWGSDRdaemonSourceSettings::asJsonObject() { return obj; } -qint32 -SWGSDRdaemonSourceSettings::getCenterFrequency() { - return center_frequency; +QString* +SWGSDRdaemonSourceSettings::getApiAddress() { + return api_address; } void -SWGSDRdaemonSourceSettings::setCenterFrequency(qint32 center_frequency) { - this->center_frequency = center_frequency; - this->m_center_frequency_isSet = true; +SWGSDRdaemonSourceSettings::setApiAddress(QString* api_address) { + this->api_address = api_address; + this->m_api_address_isSet = true; } qint32 -SWGSDRdaemonSourceSettings::getSampleRate() { - return sample_rate; +SWGSDRdaemonSourceSettings::getApiPort() { + return api_port; } void -SWGSDRdaemonSourceSettings::setSampleRate(qint32 sample_rate) { - this->sample_rate = sample_rate; - this->m_sample_rate_isSet = true; -} - -qint32 -SWGSDRdaemonSourceSettings::getLog2Decim() { - return log2_decim; -} -void -SWGSDRdaemonSourceSettings::setLog2Decim(qint32 log2_decim) { - this->log2_decim = log2_decim; - this->m_log2_decim_isSet = true; -} - -float -SWGSDRdaemonSourceSettings::getTxDelay() { - return tx_delay; -} -void -SWGSDRdaemonSourceSettings::setTxDelay(float tx_delay) { - this->tx_delay = tx_delay; - this->m_tx_delay_isSet = true; -} - -qint32 -SWGSDRdaemonSourceSettings::getNbFecBlocks() { - return nb_fec_blocks; -} -void -SWGSDRdaemonSourceSettings::setNbFecBlocks(qint32 nb_fec_blocks) { - this->nb_fec_blocks = nb_fec_blocks; - this->m_nb_fec_blocks_isSet = true; +SWGSDRdaemonSourceSettings::setApiPort(qint32 api_port) { + this->api_port = api_port; + this->m_api_port_isSet = true; } QString* -SWGSDRdaemonSourceSettings::getAddress() { - return address; +SWGSDRdaemonSourceSettings::getDataAddress() { + return data_address; } void -SWGSDRdaemonSourceSettings::setAddress(QString* address) { - this->address = address; - this->m_address_isSet = true; +SWGSDRdaemonSourceSettings::setDataAddress(QString* data_address) { + this->data_address = data_address; + this->m_data_address_isSet = true; } qint32 @@ -279,26 +189,6 @@ SWGSDRdaemonSourceSettings::setDataPort(qint32 data_port) { this->m_data_port_isSet = true; } -qint32 -SWGSDRdaemonSourceSettings::getControlPort() { - return control_port; -} -void -SWGSDRdaemonSourceSettings::setControlPort(qint32 control_port) { - this->control_port = control_port; - this->m_control_port_isSet = true; -} - -QString* -SWGSDRdaemonSourceSettings::getSpecificParameters() { - return specific_parameters; -} -void -SWGSDRdaemonSourceSettings::setSpecificParameters(QString* specific_parameters) { - this->specific_parameters = specific_parameters; - this->m_specific_parameters_isSet = true; -} - qint32 SWGSDRdaemonSourceSettings::getDcBlock() { return dc_block; @@ -319,16 +209,6 @@ SWGSDRdaemonSourceSettings::setIqCorrection(qint32 iq_correction) { this->m_iq_correction_isSet = true; } -qint32 -SWGSDRdaemonSourceSettings::getFcPos() { - return fc_pos; -} -void -SWGSDRdaemonSourceSettings::setFcPos(qint32 fc_pos) { - this->fc_pos = fc_pos; - this->m_fc_pos_isSet = true; -} - QString* SWGSDRdaemonSourceSettings::getFileRecordName() { return file_record_name; @@ -344,18 +224,12 @@ bool SWGSDRdaemonSourceSettings::isSet(){ bool isObjectUpdated = false; do{ - if(m_center_frequency_isSet){ isObjectUpdated = true; break;} - if(m_sample_rate_isSet){ isObjectUpdated = true; break;} - if(m_log2_decim_isSet){ isObjectUpdated = true; break;} - if(m_tx_delay_isSet){ isObjectUpdated = true; break;} - if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} - if(address != nullptr && *address != QString("")){ isObjectUpdated = true; break;} + if(api_address != nullptr && *api_address != QString("")){ isObjectUpdated = true; break;} + if(m_api_port_isSet){ isObjectUpdated = true; break;} + if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} if(m_data_port_isSet){ isObjectUpdated = true; break;} - if(m_control_port_isSet){ isObjectUpdated = true; break;} - if(specific_parameters != nullptr && *specific_parameters != QString("")){ isObjectUpdated = true; break;} if(m_dc_block_isSet){ isObjectUpdated = true; break;} if(m_iq_correction_isSet){ isObjectUpdated = true; break;} - if(m_fc_pos_isSet){ isObjectUpdated = true; break;} if(file_record_name != nullptr && *file_record_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h index 5e90c8d97..15bc36449 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGSDRdaemonSourceSettings.h @@ -42,42 +42,24 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGSDRdaemonSourceSettings* fromJson(QString &jsonString) override; - qint32 getCenterFrequency(); - void setCenterFrequency(qint32 center_frequency); + QString* getApiAddress(); + void setApiAddress(QString* api_address); - qint32 getSampleRate(); - void setSampleRate(qint32 sample_rate); + qint32 getApiPort(); + void setApiPort(qint32 api_port); - qint32 getLog2Decim(); - void setLog2Decim(qint32 log2_decim); - - float getTxDelay(); - void setTxDelay(float tx_delay); - - qint32 getNbFecBlocks(); - void setNbFecBlocks(qint32 nb_fec_blocks); - - QString* getAddress(); - void setAddress(QString* address); + QString* getDataAddress(); + void setDataAddress(QString* data_address); qint32 getDataPort(); void setDataPort(qint32 data_port); - qint32 getControlPort(); - void setControlPort(qint32 control_port); - - QString* getSpecificParameters(); - void setSpecificParameters(QString* specific_parameters); - qint32 getDcBlock(); void setDcBlock(qint32 dc_block); qint32 getIqCorrection(); void setIqCorrection(qint32 iq_correction); - qint32 getFcPos(); - void setFcPos(qint32 fc_pos); - QString* getFileRecordName(); void setFileRecordName(QString* file_record_name); @@ -85,42 +67,24 @@ public: virtual bool isSet() override; private: - qint32 center_frequency; - bool m_center_frequency_isSet; + QString* api_address; + bool m_api_address_isSet; - qint32 sample_rate; - bool m_sample_rate_isSet; + qint32 api_port; + bool m_api_port_isSet; - qint32 log2_decim; - bool m_log2_decim_isSet; - - float tx_delay; - bool m_tx_delay_isSet; - - qint32 nb_fec_blocks; - bool m_nb_fec_blocks_isSet; - - QString* address; - bool m_address_isSet; + QString* data_address; + bool m_data_address_isSet; qint32 data_port; bool m_data_port_isSet; - qint32 control_port; - bool m_control_port_isSet; - - QString* specific_parameters; - bool m_specific_parameters_isSet; - qint32 dc_block; bool m_dc_block_isSet; qint32 iq_correction; bool m_iq_correction_isSet; - qint32 fc_pos; - bool m_fc_pos_isSet; - QString* file_record_name; bool m_file_record_name_isSet; From 9bd87a0062517042f802d92b0808a0652447c38c Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 9 Sep 2018 09:54:04 +0200 Subject: [PATCH 714/956] REST API: update rx-test script with support for DaemonSink --- swagger/sdrangel/examples/rx_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index ae2fbf696..ccfaad328 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -46,6 +46,10 @@ def getInputOptions(): parser.add_option("--audio-channels", dest="audio_channels", help="Audio: UDP mode (0: L only 1: R only 2: L+R mono 3: LR stereo)", metavar="ENUM_INT", type="int") parser.add_option("--baud-rate", dest="baud_rate", help="DSD: baud rate in Baud", metavar="BAUD", type="int", default=4800) parser.add_option("--fm-dev", dest="fm_deviation", help="DSD: expected FM deviation", metavar="FREQ", type="int", default=5400) + parser.add_option("--dmn-address", dest="daemon_address", help="DaemonSink: destination data address", metavar="IP_ADDRESS", type="string") + parser.add_option("--dmn-port", dest="daemon_port", help="DaemonSink: destination data port", metavar="PORT", type="int") + parser.add_option("--dmn-fec", dest="daemon_fec", help="DaemonSink: number of FEC blocks per frame", metavar="NUMBER", type="int") + parser.add_option("--dmn-tx-delay", dest="daemon_tx_delay", help="DaemonSink: inter block UDP Tx delay percentage", metavar="PERCENT", type="int") (options, args) = parser.parse_args() @@ -299,6 +303,16 @@ def setupChannel(deviceset_url, options): settings["UDPSrcSettings"]["squelchDB"] = options.squelch_db settings["UDPSrcSettings"]["channelMute"] = 0 settings["UDPSrcSettings"]["title"] = "Channel %d" % i + elif options.channel_id == "DaemonSink": + settings["DaemonSinkSettings"]["title"] = "Channel %d" % i + if options.daemon_address: + settings["DaemonSinkSettings"]["dataAddress"] = options.daemon_address + if options.daemon_port: + settings["DaemonSinkSettings"]["dataPort"] = options.daemon_port + if options.daemon_fec: + settings["DaemonSinkSettings"]["nbFECBlocks"] = options.daemon_fec + if options.daemon_tx_delay: + settings["DaemonSinkSettings"]["txDelay"] = options.daemon_tx_delay r = callAPI(deviceset_url + "/channel/%d/settings" % i, "PATCH", None, settings, "Change demod") if r is None: From a56b96a97b4217b5bb4b5ee02a71a3c447ce1c03 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 9 Sep 2018 09:55:21 +0200 Subject: [PATCH 715/956] SDRDaemonSource: updated revision --- plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp index e9fad0b43..2a11bdcb9 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor SDRdaemonSourcePlugin::m_pluginDescriptor = { QString("SDRdaemon source input"), - QString("4.0.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From cfd414073e65d92fb5bd153c30029bb7fee3cbdc Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 9 Sep 2018 10:45:34 +0200 Subject: [PATCH 716/956] SDRDaemon: removed references to nanomsg entirely --- Readme.md | 19 +- ReadmeMacOS.md | 8 - ReadmeWindowsBuild.md | 2 +- apple/deploy.sh | 1 - cmake/Modules/FindLibNANOMSG.cmake | 28 -- debian/control | 2 +- nanomsg/nanomsg.pro | 275 ------------------ plugins/samplesink/sdrdaemonsink/readme.md | 2 +- plugins/samplesource/CMakeLists.txt | 9 +- .../sdrdaemonsource/CMakeLists.txt | 4 - .../samplesource/sdrdaemonsource/readme.md | 2 +- .../sdrdaemonsource/sdrdaemonsource.pro | 6 - .../sdrdaemonsource/sdrdaemonsourceinput.cpp | 9 - pluginssrv/samplesink/CMakeLists.txt | 9 +- .../samplesink/sdrdaemonsink/CMakeLists.txt | 4 - pluginssrv/samplesource/CMakeLists.txt | 9 +- .../sdrdaemonsource/CMakeLists.txt | 4 - sdrangel.windows.pro | 1 - windows64.install.bat | 1 - 19 files changed, 21 insertions(+), 374 deletions(-) delete mode 100644 cmake/Modules/FindLibNANOMSG.cmake delete mode 100644 nanomsg/nanomsg.pro diff --git a/Readme.md b/Readme.md index f555b19dc..54c920605 100644 --- a/Readme.md +++ b/Readme.md @@ -213,27 +213,25 @@ The [Test source plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/s Linux only. -The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of the SDRdaemon receiver server `sdrdaemonrx`. See the [SDRdaemon](https://github.com/f4exb/sdrdaemon) project in this Github repository. You must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiving center frequency. It also opens a TCP link to another port to send service messages such as setting parameters specific to the hardware device connected to the server (default port is `9091`). The `libnanomsg` library is used to support this messaging. +The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of an instance of SDRangel running the Daemon Sink channel plugin. On the "Data" line you must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiving center frequency. The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. However it is used just to display basic information about the remote. The data blocks transmitted via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. There is an automated skew rate compensation in place. During rate readjustment streaming can be suspended or signal glitches can occur for about one second. -This plugin will be built only if the libnanomsg and the [CM256cc library](https://github.com/f4exb/cm256cc) are installed in your system. libnanomsg is available as a dev package in most distributions For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. +This plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. -Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `SDRdaemonSource[0]` even if no physical device is connected. +Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `SDRdaemonSource[0]` even if no physical device is connected.

    SDRdaemon transmitter output

    Linux only. -The [SDRdaemon sink output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) is the client side of the SDRdaemon transmitter server `sdrdaemontx`. See the [SDRdaemon](https://github.com/f4exb/sdrdaemon) project in this Github repository. You must specify the distant address and UDP port to which the plugin connects and samples from the SDRangel application will flow into the transmitter server (default is `127.0.0.1`port `9092`). It also opens a TCP link to another port to exchange service messages such as setting the center frequency or getting status information from the server (default port is `9093`). The `libnanomsg` library is used to support this messaging. +The [SDRdaemon sink output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) is the client side of and instance of SDRangel running the Daemon Source channel plugin. On the "Data" line you must specify the distant address and UDP port to which the plugin connects and samples from the SDRangel application will flow into the transmitter server (default is `127.0.0.1`port `9090`). The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. The API is pinged regularly to retrieve the status of the data blocks queue and allow rate control to stabilize the queue length. Therefore it is important to connect to the API properly (The status line must return "API OK" and the API label should be lit in green). The data blocks sent via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. -There is an automated skew rate compensation in place so that the generator throttling is adjusted to match the actual sample rate of the distant device. This is based on the number of buffer blocks sent back from the distant server using the TCP link. - -This plugin will be built only if the libnanomsg and the [CM256cc library](https://github.com/f4exb/cm256cc) are installed in your system. libnanomsg is available as a dev package in most distributions For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. +This plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) IS installed in your system. For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `SDRdaemonSink[0]` even if no physical device is connected. @@ -358,7 +356,7 @@ Then do `sudo apt-get update` and go to the next step. Alternatively if you have

    With newer versions just do:

    - - `sudo apt-get install cmake g++ pkg-config libfftw3-dev libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev libusb-1.0 librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libnanomsg-dev libopencv-dev libsqlite3-dev libxml2-dev bison flex ffmpeg libavcodec-dev libavformat-dev` + - `sudo apt-get install cmake g++ pkg-config libfftw3-dev libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev libusb-1.0 librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libopencv-dev libsqlite3-dev libxml2-dev bison flex ffmpeg libavcodec-dev libavformat-dev` - `mkdir build && cd build && cmake ../ && make` `librtlsdr-dev` is in the `universe` repo. (utopic 14.10 amd64.) @@ -390,11 +388,10 @@ This has been tested with the Leap 42.3 distribution: Then you should be all set to build the software with `cmake` and `make` as discussed earlier. - Note1: if you are on Leap you will need a more recent g++ compiler so in place of `gcc-c++` use `gcc6-c++` or `gcc7-c++` then add the following in the cmake command: `-DCMAKE_C_COMPILER=/usr/bin/gcc-7 -DCMAKE_CXX_COMPILER=/usr/bin/g++-7` (for gcc 7) and then `-DCMAKE_INSTALL_PREFIX:PATH=...` for the custom install path (not `-DCMAKE_INSTALL_PREFIX=...`) - - Note2: On Leap and aarch64 architectures you will need to build and install `libnanomsg` from [source](https://github.com/nanomsg/nanomsg) then if your installation directory is `/opt/install/nanomsg` you will have to add this to the cmake command line: `-DLIBNANOMSG_INCLUDE_DIR=/opt/install/nanomsg/include -DLIBNANOMSG_LIBRARIES=/opt/install/nanomsg/lib64/libnanomsg.so` - - Note3 for udev rules: installed udev rules for BladeRF and HackRF are targeted at Debian or Ubuntu systems that have a plugdev group for USB hotplug devices. This is not the case in openSUSE. To fix it you can either: + - Note2 for udev rules: installed udev rules for BladeRF and HackRF are targeted at Debian or Ubuntu systems that have a plugdev group for USB hotplug devices. This is not the case in openSUSE. To fix it you can either: - make the udev rules file compatible just remove the `GROUP` parameter on all lines and change `MODE` parameter to `666`. - create a `plugdev` group and add it tou your user group list: `sudo groupadd plugdev` then `sudo usermod -G plugdev -a ` - - Note4: A package has been created in openSUSE thanks to Martin, see: [sdrangel](https://build.opensuse.org/package/show/hardware:sdr/sdrangel). It is based on the latest release on master branch. + - Note3: A package has been created in openSUSE thanks to Martin, see: [sdrangel](https://build.opensuse.org/package/show/hardware:sdr/sdrangel). It is based on the latest release on master branch.

    Fedora

    diff --git a/ReadmeMacOS.md b/ReadmeMacOS.md index bc8523b79..3a916ce6a 100644 --- a/ReadmeMacOS.md +++ b/ReadmeMacOS.md @@ -16,7 +16,6 @@ SDRangel-3.x: + cm256cc + dsdcc + mbelib - + nanomsg + boost_1_64_0/ ### Environment preparation @@ -47,13 +46,6 @@ git clone https://github.com/f4exb/dsdcc.git ``` -##### nanomsg: -``` -git clone https://github.com/nanomsg/nanomsg.git -mkdir build && cd build -cmake -DCMAKE_INSTALL_PREFIX=/opt/local .. -cmake --build . && sudo cmake --build . --target install -``` ## Build Release build configuration with QT Creator diff --git a/ReadmeWindowsBuild.md b/ReadmeWindowsBuild.md index 65f05307b..97c9d92de 100644 --- a/ReadmeWindowsBuild.md +++ b/ReadmeWindowsBuild.md @@ -8,7 +8,7 @@ You should take note that the Windows scheduler is just a piece of crap and not There are no plugins for both flavours of Funcubes since it uses Alsa interface which is Linux exclusively. Changing for the Qt audio portable interface instead could be a solution that will be investigated in the future. -The SDRdaemon plug-in is present only in the 64 bit build version since version 1.1.4. The messaging system based on nanomsg works only in the 64 bit environment. However please be aware that the SDRdaemon plugin is not working well mainly due to the fact that it needs an OS with a decent scheduler and Windows is definitely not this sort of OS (see my previous warning). In fact depending on the case your mileage may vary however the Linux version works always beautifully so you know the options if you really want to use it! +Please be aware that the SDRdaemon plugin is not working well mainly due to the fact that it needs an OS with a decent scheduler and Windows is definitely not this sort of OS (see my previous warning). In fact depending on the case your mileage may vary however the Linux version works always beautifully so you know the options if you really want to use it!

    Build environment

    diff --git a/apple/deploy.sh b/apple/deploy.sh index d08248924..1837832e7 100755 --- a/apple/deploy.sh +++ b/apple/deploy.sh @@ -28,7 +28,6 @@ for f in `find plugins/samplesink/ -name '*.dylib'`; do cp -v $f "${APP_PLUGINS} for f in `find plugins/samplesource/ -name '*.dylib'`; do cp -v $f "${APP_PLUGINS}/samplesource/"; done cd $APP_LIB -cp /opt/local/lib/libnanomsg.5.0.0.dylib . ln -s libdsdcc.dylib libdsdcc.1.dylib ln -s libdevices.dylib libdevices.1.dylib ln -s libsdrbase.dylib libsdrbase.1.dylib diff --git a/cmake/Modules/FindLibNANOMSG.cmake b/cmake/Modules/FindLibNANOMSG.cmake deleted file mode 100644 index 63b8504e3..000000000 --- a/cmake/Modules/FindLibNANOMSG.cmake +++ /dev/null @@ -1,28 +0,0 @@ -if(NOT LIBNANOMSG_FOUND) - - pkg_check_modules (LIBNANOMSG_PKG libnanomsg) - find_path(LIBNANOMSG_INCLUDE_DIR NAMES nanomsg/nn.h - PATHS - ${LIBNANOMSG_PKG_INCLUDE_DIRS} - /usr/include - /usr/local/include - ) - - find_library(LIBNANOMSG_LIBRARIES NAMES nanomsg - PATHS - ${LIBNANOMSG_PKG_LIBRARY_DIRS} - /usr/lib - /usr/local/lib - ) - - if(LIBNANOMSG_INCLUDE_DIR AND LIBNANOMSG_LIBRARIES) - set(LIBNANOMSG_FOUND TRUE CACHE INTERNAL "libnanomsg found") - message(STATUS "Found libnanomsg: ${LIBNANOMSG_INCLUDE_DIR}, ${LIBNANOMSG_LIBRARIES}") - else(LIBNANOMSG_INCLUDE_DIR AND LIBNANOMSG_LIBRARIES) - set(LIBNANOMSG_FOUND FALSE CACHE INTERNAL "libnanomsg found") - message(STATUS "libnanomsg not found.") - endif(LIBNANOMSG_INCLUDE_DIR AND LIBNANOMSG_LIBRARIES) - - mark_as_advanced(LIBNANOMSG_INCLUDE_DIR LIBNANOMSG_LIBRARIES) - -endif(NOT LIBNANOMSG_FOUND) diff --git a/debian/control b/debian/control index 71563bd3a..2da95a2eb 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://github.com/f4exb/sdrangel Package: sdrangel Architecture: any -Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libnanomsg0|libnanomsg4, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, pulseaudio, libxml2, ffmpeg, libavcodec-dev, libavformat-dev, ${shlibs:Depends}, ${misc:Depends} +Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, pulseaudio, libxml2, ffmpeg, libavcodec-dev, libavformat-dev, ${shlibs:Depends}, ${misc:Depends} Description: SDR/Analyzer/Generator front-end for various hardware SDR/Analyzer/Generator front-end for Airspy, BladeRF, HackRF, RTL-SDR, FunCube, LimeSDR, PlutoSDR. Also File source and sink for I/Q samples, network I/Q sources with SDRDaemon. diff --git a/nanomsg/nanomsg.pro b/nanomsg/nanomsg.pro deleted file mode 100644 index efcc924bc..000000000 --- a/nanomsg/nanomsg.pro +++ /dev/null @@ -1,275 +0,0 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -QT += core - -TEMPLATE = lib -TARGET = nanomsg - -CONFIG(MINGW32):LIBNANOMSGSRC = "C:\softs\nanomsg-0.8-beta" -CONFIG(MINGW64):LIBNANOMSGSRC = "C:\softs\nanomsg-0.8-beta" - -CONFIG(MINGW32):DEFINES += NN_HAVE_WINDOWS=1 -CONFIG(MINGW32):DEFINES += _CRT_SECURE_NO_WARNINGS=1 -CONFIG(MINGW32):DEFINES += NN_HAVE_MINGW=1 -CONFIG(MINGW32):DEFINES += NN_HAVE_STDINT=1 -CONFIG(MINGW32):DEFINES += _WIN32_WINNT=0x0600 -CONFIG(MINGW32):DEFINES += NN_EXPORTS=1 - -CONFIG(MINGW64):DEFINES += NN_HAVE_WINDOWS=1 -CONFIG(MINGW64):DEFINES += _CRT_SECURE_NO_WARNINGS=1 -CONFIG(MINGW64):DEFINES += NN_HAVE_MINGW=1 -CONFIG(MINGW64):DEFINES += NN_HAVE_STDINT=1 -CONFIG(MINGW64):DEFINES += _WIN32_WINNT=0x0600 -CONFIG(MINGW64):DEFINES += NN_EXPORTS=1 -CONFIG(MINGW64):DEFINES += _POSIX_C_SOURCE=1 - -INCLUDEPATH += $$LIBNANOMSGSRC/src - -SOURCES = $$LIBNANOMSGSRC/src/core/ep.c\ -$$LIBNANOMSGSRC/src/core/epbase.c\ -$$LIBNANOMSGSRC/src/core/global.c\ -$$LIBNANOMSGSRC/src/core/pipe.c\ -$$LIBNANOMSGSRC/src/core/poll.c\ -$$LIBNANOMSGSRC/src/core/sock.c\ -$$LIBNANOMSGSRC/src/core/sockbase.c\ -$$LIBNANOMSGSRC/src/core/symbol.c\ -$$LIBNANOMSGSRC/src/aio/ctx.c\ -$$LIBNANOMSGSRC/src/aio/fsm.c\ -$$LIBNANOMSGSRC/src/aio/poller.c\ -$$LIBNANOMSGSRC/src/aio/pool.c\ -$$LIBNANOMSGSRC/src/aio/timer.c\ -$$LIBNANOMSGSRC/src/aio/timerset.c\ -$$LIBNANOMSGSRC/src/aio/usock.c\ -$$LIBNANOMSGSRC/src/aio/worker.c\ -$$LIBNANOMSGSRC/src/utils/alloc.c\ -$$LIBNANOMSGSRC/src/utils/atomic.c\ -$$LIBNANOMSGSRC/src/utils/chunk.c\ -$$LIBNANOMSGSRC/src/utils/chunkref.c\ -$$LIBNANOMSGSRC/src/utils/clock.c\ -$$LIBNANOMSGSRC/src/utils/closefd.c\ -$$LIBNANOMSGSRC/src/utils/efd.c\ -$$LIBNANOMSGSRC/src/utils/err.c\ -$$LIBNANOMSGSRC/src/utils/glock.c\ -$$LIBNANOMSGSRC/src/utils/hash.c\ -$$LIBNANOMSGSRC/src/utils/list.c\ -$$LIBNANOMSGSRC/src/utils/msg.c\ -$$LIBNANOMSGSRC/src/utils/mutex.c\ -$$LIBNANOMSGSRC/src/utils/queue.c\ -$$LIBNANOMSGSRC/src/utils/random.c\ -$$LIBNANOMSGSRC/src/utils/sem.c\ -$$LIBNANOMSGSRC/src/utils/sleep.c\ -$$LIBNANOMSGSRC/src/utils/thread.c\ -$$LIBNANOMSGSRC/src/utils/wire.c\ -$$LIBNANOMSGSRC/src/devices/device.c\ -$$LIBNANOMSGSRC/src/devices/tcpmuxd.c\ -$$LIBNANOMSGSRC/src/protocols/utils/dist.c\ -$$LIBNANOMSGSRC/src/protocols/utils/excl.c\ -$$LIBNANOMSGSRC/src/protocols/utils/fq.c\ -$$LIBNANOMSGSRC/src/protocols/utils/lb.c\ -$$LIBNANOMSGSRC/src/protocols/utils/priolist.c\ -$$LIBNANOMSGSRC/src/protocols/bus/bus.c\ -$$LIBNANOMSGSRC/src/protocols/bus/xbus.c\ -$$LIBNANOMSGSRC/src/protocols/pipeline/push.c\ -$$LIBNANOMSGSRC/src/protocols/pipeline/pull.c\ -$$LIBNANOMSGSRC/src/protocols/pipeline/xpull.c\ -$$LIBNANOMSGSRC/src/protocols/pipeline/xpush.c\ -$$LIBNANOMSGSRC/src/protocols/pair/pair.c\ -$$LIBNANOMSGSRC/src/protocols/pair/xpair.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/pub.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/sub.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/trie.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/xpub.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/xsub.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/req.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/rep.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/task.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/xrep.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/xreq.c\ -$$LIBNANOMSGSRC/src/protocols/survey/respondent.c\ -$$LIBNANOMSGSRC/src/protocols/survey/surveyor.c\ -$$LIBNANOMSGSRC/src/protocols/survey/xrespondent.c\ -$$LIBNANOMSGSRC/src/protocols/survey/xsurveyor.c\ -$$LIBNANOMSGSRC/src/transports/utils/backoff.c\ -$$LIBNANOMSGSRC/src/transports/utils/dns.c\ -$$LIBNANOMSGSRC/src/transports/utils/iface.c\ -$$LIBNANOMSGSRC/src/transports/utils/literal.c\ -$$LIBNANOMSGSRC/src/transports/utils/port.c\ -$$LIBNANOMSGSRC/src/transports/utils/streamhdr.c\ -$$LIBNANOMSGSRC/src/transports/utils/base64.c\ -$$LIBNANOMSGSRC/src/transports/inproc/binproc.c\ -$$LIBNANOMSGSRC/src/transports/inproc/cinproc.c\ -$$LIBNANOMSGSRC/src/transports/inproc/inproc.c\ -$$LIBNANOMSGSRC/src/transports/inproc/ins.c\ -$$LIBNANOMSGSRC/src/transports/inproc/msgqueue.c\ -$$LIBNANOMSGSRC/src/transports/inproc/sinproc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/aipc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/bipc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/cipc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/ipc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/sipc.c\ -$$LIBNANOMSGSRC/src/transports/tcp/atcp.c\ -$$LIBNANOMSGSRC/src/transports/tcp/btcp.c\ -$$LIBNANOMSGSRC/src/transports/tcp/ctcp.c\ -$$LIBNANOMSGSRC/src/transports/tcp/stcp.c\ -$$LIBNANOMSGSRC/src/transports/tcp/tcp.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/atcpmux.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/btcpmux.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/ctcpmux.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/stcpmux.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/tcpmux.c\ -$$LIBNANOMSGSRC/src/transports/ws/aws.c\ -$$LIBNANOMSGSRC/src/transports/ws/bws.c\ -$$LIBNANOMSGSRC/src/transports/ws/cws.c\ -$$LIBNANOMSGSRC/src/transports/ws/sws.c\ -$$LIBNANOMSGSRC/src/transports/ws/ws.c\ -$$LIBNANOMSGSRC/src/transports/ws/ws_handshake.c\ -$$LIBNANOMSGSRC/src/transports/ws/sha1.c - -HEADERS = $$LIBNANOMSGSRC/src/nn.h\ -$$LIBNANOMSGSRC/src/inproc.h\ -$$LIBNANOMSGSRC/src/ipc.h\ -$$LIBNANOMSGSRC/src/tcp.h\ -$$LIBNANOMSGSRC/src/ws.h\ -$$LIBNANOMSGSRC/src/pair.h\ -$$LIBNANOMSGSRC/src/pubsub.h\ -$$LIBNANOMSGSRC/src/reqrep.h\ -$$LIBNANOMSGSRC/src/pipeline.h\ -$$LIBNANOMSGSRC/src/survey.h\ -$$LIBNANOMSGSRC/src/bus.h\ -$$LIBNANOMSGSRC/src/core/ep.h\ -$$LIBNANOMSGSRC/src/core/global.h\ -$$LIBNANOMSGSRC/src/core/sock.h\ -$$LIBNANOMSGSRC/src/aio/ctx.h\ -$$LIBNANOMSGSRC/src/aio/fsm.h\ -$$LIBNANOMSGSRC/src/aio/poller.h\ -$$LIBNANOMSGSRC/src/aio/poller_epoll.h\ -$$LIBNANOMSGSRC/src/aio/poller_kqueue.h\ -$$LIBNANOMSGSRC/src/aio/poller_poll.h\ -$$LIBNANOMSGSRC/src/aio/pool.h\ -$$LIBNANOMSGSRC/src/aio/timer.h\ -$$LIBNANOMSGSRC/src/aio/timerset.h\ -$$LIBNANOMSGSRC/src/aio/usock.h\ -$$LIBNANOMSGSRC/src/aio/usock_posix.h\ -$$LIBNANOMSGSRC/src/aio/usock_win.h\ -$$LIBNANOMSGSRC/src/aio/worker.h\ -$$LIBNANOMSGSRC/src/aio/worker_posix.h\ -$$LIBNANOMSGSRC/src/aio/worker_win.h\ -$$LIBNANOMSGSRC/src/utils/alloc.h\ -$$LIBNANOMSGSRC/src/utils/atomic.h\ -$$LIBNANOMSGSRC/src/utils/attr.h\ -$$LIBNANOMSGSRC/src/utils/chunk.h\ -$$LIBNANOMSGSRC/src/utils/chunkref.h\ -$$LIBNANOMSGSRC/src/utils/clock.h\ -$$LIBNANOMSGSRC/src/utils/closefd.h\ -$$LIBNANOMSGSRC/src/utils/cont.h\ -$$LIBNANOMSGSRC/src/utils/efd.h\ -$$LIBNANOMSGSRC/src/utils/efd_eventfd.h\ -$$LIBNANOMSGSRC/src/utils/efd_pipe.h\ -$$LIBNANOMSGSRC/src/utils/efd_socketpair.h\ -$$LIBNANOMSGSRC/src/utils/efd_win.h\ -$$LIBNANOMSGSRC/src/utils/err.h\ -$$LIBNANOMSGSRC/src/utils/fast.h\ -$$LIBNANOMSGSRC/src/utils/fd.h\ -$$LIBNANOMSGSRC/src/utils/glock.h\ -$$LIBNANOMSGSRC/src/utils/hash.h\ -$$LIBNANOMSGSRC/src/utils/int.h\ -$$LIBNANOMSGSRC/src/utils/list.h\ -$$LIBNANOMSGSRC/src/utils/msg.h\ -$$LIBNANOMSGSRC/src/utils/mutex.h\ -$$LIBNANOMSGSRC/src/utils/queue.h\ -$$LIBNANOMSGSRC/src/utils/random.h\ -$$LIBNANOMSGSRC/src/utils/sem.h\ -$$LIBNANOMSGSRC/src/utils/sleep.h\ -$$LIBNANOMSGSRC/src/utils/thread.h\ -$$LIBNANOMSGSRC/src/utils/thread_posix.h\ -$$LIBNANOMSGSRC/src/utils/thread_win.h\ -$$LIBNANOMSGSRC/src/utils/wire.h\ -$$LIBNANOMSGSRC/src/devices/device.h\ -$$LIBNANOMSGSRC/src/protocols/utils/dist.h\ -$$LIBNANOMSGSRC/src/protocols/utils/excl.h\ -$$LIBNANOMSGSRC/src/protocols/utils/fq.h\ -$$LIBNANOMSGSRC/src/protocols/utils/lb.h\ -$$LIBNANOMSGSRC/src/protocols/utils/priolist.h\ -$$LIBNANOMSGSRC/src/protocols/bus/bus.h\ -$$LIBNANOMSGSRC/src/protocols/bus/xbus.h\ -$$LIBNANOMSGSRC/src/protocols/pipeline/push.h\ -$$LIBNANOMSGSRC/src/protocols/pipeline/pull.h\ -$$LIBNANOMSGSRC/src/protocols/pipeline/xpull.h\ -$$LIBNANOMSGSRC/src/protocols/pipeline/xpush.h\ -$$LIBNANOMSGSRC/src/protocols/pair/pair.h\ -$$LIBNANOMSGSRC/src/protocols/pair/xpair.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/pub.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/sub.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/trie.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/xpub.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/xsub.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/req.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/rep.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/task.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/xrep.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/xreq.h\ -$$LIBNANOMSGSRC/src/protocols/survey/respondent.h\ -$$LIBNANOMSGSRC/src/protocols/survey/surveyor.h\ -$$LIBNANOMSGSRC/src/protocols/survey/xrespondent.h\ -$$LIBNANOMSGSRC/src/protocols/survey/xsurveyor.h\ -$$LIBNANOMSGSRC/src/transports/utils/backoff.h\ -$$LIBNANOMSGSRC/src/transports/utils/dns.h\ -$$LIBNANOMSGSRC/src/transports/utils/dns_getaddrinfo.h\ -$$LIBNANOMSGSRC/src/transports/utils/dns_getaddrinfo_a.h\ -$$LIBNANOMSGSRC/src/transports/utils/iface.h\ -$$LIBNANOMSGSRC/src/transports/utils/literal.h\ -$$LIBNANOMSGSRC/src/transports/utils/port.h\ -$$LIBNANOMSGSRC/src/transports/utils/streamhdr.h\ -$$LIBNANOMSGSRC/src/transports/utils/base64.h\ -$$LIBNANOMSGSRC/src/transports/inproc/binproc.h\ -$$LIBNANOMSGSRC/src/transports/inproc/cinproc.h\ -$$LIBNANOMSGSRC/src/transports/inproc/inproc.h\ -$$LIBNANOMSGSRC/src/transports/inproc/ins.h\ -$$LIBNANOMSGSRC/src/transports/inproc/msgqueue.h\ -$$LIBNANOMSGSRC/src/transports/inproc/sinproc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/aipc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/bipc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/cipc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/ipc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/sipc.h\ -$$LIBNANOMSGSRC/src/transports/tcp/atcp.h\ -$$LIBNANOMSGSRC/src/transports/tcp/btcp.h\ -$$LIBNANOMSGSRC/src/transports/tcp/ctcp.h\ -$$LIBNANOMSGSRC/src/transports/tcp/stcp.h\ -$$LIBNANOMSGSRC/src/transports/tcp/tcp.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/atcpmux.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/btcpmux.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/ctcpmux.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/stcpmux.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/tcpmux.h\ -$$LIBNANOMSGSRC/src/transports/ws/aws.h\ -$$LIBNANOMSGSRC/src/transports/ws/bws.h\ -$$LIBNANOMSGSRC/src/transports/ws/cws.h\ -$$LIBNANOMSGSRC/src/transports/ws/sws.h\ -$$LIBNANOMSGSRC/src/transports/ws/ws.h\ -$$LIBNANOMSGSRC/src/transports/ws/ws_handshake.h\ -$$LIBNANOMSGSRC/src/transports/ws/sha1.h\ -$$LIBNANOMSGSRC/src/utils/win.h\ -$$LIBNANOMSGSRC/src/aio/poller_epoll.inc\ -$$LIBNANOMSGSRC/src/aio/poller_kqueue.inc\ -$$LIBNANOMSGSRC/src/aio/poller_poll.inc\ -$$LIBNANOMSGSRC/src/aio/usock_posix.inc\ -$$LIBNANOMSGSRC/src/aio/usock_win.inc\ -$$LIBNANOMSGSRC/src/aio/worker_posix.inc\ -$$LIBNANOMSGSRC/src/aio/worker_win.inc\ -$$LIBNANOMSGSRC/src/utils/efd_eventfd.inc\ -$$LIBNANOMSGSRC/src/utils/efd_pipe.inc\ -$$LIBNANOMSGSRC/src/utils/efd_socketpair.inc\ -$$LIBNANOMSGSRC/src/utils/efd_win.inc\ -$$LIBNANOMSGSRC/src/utils/thread_posix.inc\ -$$LIBNANOMSGSRC/src/utils/thread_win.inc\ -$$LIBNANOMSGSRC/src/transports/utils/dns_getaddrinfo.inc\ -$$LIBNANOMSGSRC/src/transports/utils/dns_getaddrinfo_a.inc - -#CONFIG(MINGW32):LIBS += -lws2_32 -lmswsock -ladvapi32 -CONFIG(MINGW32):LIBS += -lws2_32 -lmswsock -CONFIG(MINGW64):LIBS += -lws2_32 -lmswsock diff --git a/plugins/samplesink/sdrdaemonsink/readme.md b/plugins/samplesink/sdrdaemonsink/readme.md index 3bb218cb5..710958b24 100644 --- a/plugins/samplesink/sdrdaemonsink/readme.md +++ b/plugins/samplesink/sdrdaemonsink/readme.md @@ -8,7 +8,7 @@ Forward Error Correction with a Cauchy MDS block erasure codec is used to preven

    Build

    -The plugin will be built only if `libnanomsg` and the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. `libnanomasg` is present in most distributions and the dev version can be installed using the package manager. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. +The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands.

    Interface

    diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index 3a2419ee9..416713107 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -64,10 +64,9 @@ if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) find_package(CM256cc) -find_package(LibNANOMSG) -if(CM256CC_FOUND AND LIBNANOMSG_FOUND) +if(CM256CC_FOUND) add_subdirectory(sdrdaemonsource) -endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) +endif(CM256CC_FOUND) find_package(LibMiriSDR) if(LIBUSB_FOUND AND LIBMIRISDR_FOUND) @@ -86,9 +85,7 @@ if (BUILD_DEBIAN) add_subdirectory(perseus) add_subdirectory(plutosdrinput) add_subdirectory(rtlsdr) - if (LIBNANOMSG_FOUND) - add_subdirectory(sdrdaemonsource) - endif (LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsource) add_subdirectory(sdrplay) endif (BUILD_DEBIAN) diff --git a/plugins/samplesource/sdrdaemonsource/CMakeLists.txt b/plugins/samplesource/sdrdaemonsource/CMakeLists.txt index e134b5200..387ad4ed9 100644 --- a/plugins/samplesource/sdrdaemonsource/CMakeLists.txt +++ b/plugins/samplesource/sdrdaemonsource/CMakeLists.txt @@ -53,7 +53,6 @@ target_include_directories(inputsdrdaemonsource PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBCM256CCSRC} - ${LIBNANOMSG_INCLUDE_DIR} ) else (BUILD_DEBIAN) target_include_directories(inputsdrdaemonsource PUBLIC @@ -61,7 +60,6 @@ target_include_directories(inputsdrdaemonsource PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CM256CC_INCLUDE_DIR} - ${LIBNANOMSG_INCLUDE_DIR} ) endif (BUILD_DEBIAN) @@ -69,7 +67,6 @@ if (BUILD_DEBIAN) target_link_libraries(inputsdrdaemonsource ${QT_LIBRARIES} cm256cc - ${LIBNANOMSG_LIBRARIES} sdrbase sdrgui swagger @@ -78,7 +75,6 @@ else (BUILD_DEBIAN) target_link_libraries(inputsdrdaemonsource ${QT_LIBRARIES} ${CM256CC_LIBRARIES} - ${LIBNANOMSG_LIBRARIES} sdrbase sdrgui swagger diff --git a/plugins/samplesource/sdrdaemonsource/readme.md b/plugins/samplesource/sdrdaemonsource/readme.md index a69433a8b..b1732a09e 100644 --- a/plugins/samplesource/sdrdaemonsource/readme.md +++ b/plugins/samplesource/sdrdaemonsource/readme.md @@ -10,7 +10,7 @@ Please note that there is no provision for handling out of sync UDP blocks. It i

    Build

    -The plugin will be built only if `libnanomsg` and the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. `libnanomasg` is present in most distributions and the dev version can be installed using the package manager. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. +The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands.

    Interface

    diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro b/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro index 759177a52..b8bffdecc 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro @@ -11,9 +11,6 @@ QT += core gui widgets multimedia network opengl TARGET = inputsdrdaemonsource -CONFIG(MINGW32):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta" -CONFIG(MINGW64):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta" - CONFIG(MINGW32):LIBCM256CCSRC = "D:\softs\cm256cc" CONFIG(MINGW64):LIBCM256CCSRC = "D:\softs\cm256cc" CONFIG(macx):LIBCM256CCSRC = "../../../../deps/cm256cc" @@ -22,7 +19,6 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client -!macx:INCLUDEPATH += $$LIBNANOMSGSRC/src macx:INCLUDEPATH += /opt/local/include INCLUDEPATH += $$LIBCM256CCSRC @@ -60,8 +56,6 @@ FORMS += sdrdaemonsourcegui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui LIBS += -L../../../swagger/$${build_subdir} -lswagger -!macx:LIBS += -L../../../nanomsg/$${build_subdir} -lnanomsg -macx:LIBS += -L/opt/local/lib -lnanomsg LIBS += -L../../../cm256cc/$${build_subdir} -lcm256cc RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 59ce673c1..c4147fbeb 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -19,15 +19,6 @@ #include #include - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#endif - #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGDeviceReport.h" diff --git a/pluginssrv/samplesink/CMakeLists.txt b/pluginssrv/samplesink/CMakeLists.txt index 1e1bd6ba2..71c5022b8 100644 --- a/pluginssrv/samplesink/CMakeLists.txt +++ b/pluginssrv/samplesink/CMakeLists.txt @@ -23,19 +23,16 @@ if(LIBUSB_FOUND AND LIBIIO_FOUND) endif(LIBUSB_FOUND AND LIBIIO_FOUND) find_package(CM256cc) -find_package(LibNANOMSG) -if(CM256CC_FOUND AND LIBNANOMSG_FOUND) +if(CM256CC_FOUND) add_subdirectory(sdrdaemonsink) -endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) +endif(CM256CC_FOUND) if (BUILD_DEBIAN) add_subdirectory(bladerfoutput) add_subdirectory(hackrfoutput) add_subdirectory(limesdroutput) add_subdirectory(plutosdroutput) - if (LIBNANOMSG_FOUND) - add_subdirectory(sdrdaemonsink) - endif (LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsink) endif (BUILD_DEBIAN) add_subdirectory(filesink) diff --git a/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt index ff4115065..d5f794ba3 100644 --- a/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt +++ b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt @@ -36,7 +36,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBCM256CCSRC} - ${LIBNANOMSG_INCLUDE_DIR} ) else (BUILD_DEBIAN) include_directories( @@ -45,7 +44,6 @@ include_directories( ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${CM256CC_INCLUDE_DIR} - ${LIBNANOMSG_INCLUDE_DIR} ) endif (BUILD_DEBIAN) @@ -64,7 +62,6 @@ target_link_libraries(outputsdrdaemonsinksrv sdrbase swagger cm256cc - ${LIBNANOMSG_LIBRARIES} ) else (BUILD_DEBIAN) target_link_libraries(outputsdrdaemonsinksrv @@ -72,7 +69,6 @@ target_link_libraries(outputsdrdaemonsinksrv sdrbase swagger ${CM256CC_LIBRARIES} - ${LIBNANOMSG_LIBRARIES} ) endif (BUILD_DEBIAN) diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 8272a564f..5761f7480 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -64,10 +64,9 @@ if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) find_package(CM256cc) -find_package(LibNANOMSG) -if(CM256CC_FOUND AND LIBNANOMSG_FOUND) +if(CM256CC_FOUND) add_subdirectory(sdrdaemonsource) -endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) +endif(CM256CC_FOUND) find_package(LibMiriSDR) if(LIBUSB_FOUND AND LIBMIRISDR_FOUND) @@ -85,9 +84,7 @@ if (BUILD_DEBIAN) add_subdirectory(perseus) add_subdirectory(plutosdrinput) add_subdirectory(rtlsdr) - if (LIBNANOMSG_FOUND) - add_subdirectory(sdrdaemonsource) - endif (LIBNANOMSG_FOUND) + add_subdirectory(sdrdaemonsource) add_subdirectory(sdrplay) endif (BUILD_DEBIAN) diff --git a/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt b/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt index 2e9b7eb23..98de7ca6d 100644 --- a/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt +++ b/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt @@ -44,7 +44,6 @@ target_include_directories(inputsdrdaemonsourcesrv PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBCM256CCSRC} - ${LIBNANOMSG_INCLUDE_DIR} ) else (BUILD_DEBIAN) target_include_directories(inputsdrdaemonsourcesrv PUBLIC @@ -52,7 +51,6 @@ target_include_directories(inputsdrdaemonsourcesrv PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CM256CC_INCLUDE_DIR} - ${LIBNANOMSG_INCLUDE_DIR} ) endif (BUILD_DEBIAN) @@ -60,7 +58,6 @@ if (BUILD_DEBIAN) target_link_libraries(inputsdrdaemonsourcesrv ${QT_LIBRARIES} cm256cc - ${LIBNANOMSG_LIBRARIES} sdrbase swagger ) @@ -68,7 +65,6 @@ else (BUILD_DEBIAN) target_link_libraries(inputsdrdaemonsourcesrv ${QT_LIBRARIES} ${CM256CC_LIBRARIES} - ${LIBNANOMSG_LIBRARIES} sdrbase swagger ) diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index f0fe75d8e..f73b451ea 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -15,7 +15,6 @@ SUBDIRS += qrtplib SUBDIRS += swagger SUBDIRS += sdrbase SUBDIRS += sdrgui -CONFIG(MINGW64)SUBDIRS += nanomsg SUBDIRS += fcdhid SUBDIRS += fcdlib SUBDIRS += libairspy diff --git a/windows64.install.bat b/windows64.install.bat index ba3e5d0bf..43ad5b7b0 100644 --- a/windows64.install.bat +++ b/windows64.install.bat @@ -30,7 +30,6 @@ copy cm256cc\%1\cm256cc.dll %2 copy mbelib\%1\mbelib.dll %2 copy dsdcc\%1\dsdcc.dll %2 copy serialdv\%1\serialdv.dll %2 -copy nanomsg\%1\nanomsg.dll %2 copy httpserver\%1\httpserver.dll %2 copy logging\%1\logging.dll %2 copy swagger\%1\swagger.dll %2 From 0ae5955b76ebaffc3229eb1674ee6acaa130dc22 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 9 Sep 2018 17:39:36 +0200 Subject: [PATCH 717/956] SDRDaemon: fixed passing number of sample bits and sample bytes --- plugins/channelrx/daemonsink/daemonsink.cpp | 4 ++-- plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp | 6 +++--- .../sdrdaemonsource/sdrdaemonsourcegui.cpp | 4 ++++ .../sdrdaemonsource/sdrdaemonsourcegui.h | 1 + .../sdrdaemonsource/sdrdaemonsourcegui.ui | 10 ++++++++++ .../sdrdaemonsource/sdrdaemonsourceinput.h | 14 ++++++++++---- .../sdrdaemonsource/sdrdaemonsourceudphandler.cpp | 4 +++- 7 files changed, 33 insertions(+), 10 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 051101adf..4ca7ae2e8 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -114,8 +114,8 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec metaData.m_centerFrequency = m_centerFrequency; metaData.m_sampleRate = m_sampleRate; - metaData.m_sampleBytes = m_sampleBytes; - metaData.m_sampleBits = 0; // TODO: deprecated + metaData.m_sampleBytes = m_sampleBytes & 0xF; + metaData.m_sampleBits = SDR_RX_SAMP_SZ; metaData.m_nbOriginalBlocks = SDRDaemonNbOrginalBlocks; metaData.m_nbFECBlocks = m_nbBlocksFEC; metaData.m_tv_sec = tv.tv_sec; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index d00213e87..fdd7bad6a 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -29,8 +29,8 @@ MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) UDPSinkFEC::UDPSinkFEC() : m_sampleRate(48000), - m_sampleBytes(1), - m_sampleBits(8), + m_sampleBytes(SDR_TX_SAMP_SZ == 24 ? 4 : 2), + m_sampleBits(SDR_TX_SAMP_SZ), m_nbSamples(0), m_nbBlocksFEC(0), m_txDelay(0), @@ -104,7 +104,7 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk // create meta data TODO: semaphore metaData.m_centerFrequency = 0; // frequency not set by stream metaData.m_sampleRate = m_sampleRate; - metaData.m_sampleBytes = m_sampleBytes; + metaData.m_sampleBytes = m_sampleBytes & 0xF; metaData.m_sampleBits = m_sampleBits; metaData.m_nbOriginalBlocks = m_nbOriginalBlocks; metaData.m_nbFECBlocks = m_nbBlocksFEC; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 61933dfac..a272dcf3b 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -56,6 +56,7 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent m_bufferGauge(-50), m_nbOriginalBlocks(128), m_nbFECBlocks(0), + m_sampleBits(16), m_samplesCount(0), m_tickCount(0), m_addressEdited(false), @@ -209,6 +210,7 @@ bool SDRdaemonSourceGui::handleMessage(const Message& message) m_avgNbOriginalBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getAvgNbOriginalBlocks(); m_avgNbRecovery = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getAvgNbRecovery(); m_nbOriginalBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getNbOriginalBlocksPerFrame(); + m_sampleBits = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getSampleBits(); int nbFECBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getNbFECBlocksPerFrame(); @@ -493,6 +495,8 @@ void SDRdaemonSourceGui::updateWithStreamTime() QString s1 = QString("%1").arg(m_nbFECBlocks, 2, 10, QChar('0')); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); + ui->sampleBitsText->setText(tr("%1b").arg(m_sampleBits)); + if (updateEventCounts) { displayEventCounts(); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h index b0b5577d5..89cb6dc0c 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h @@ -85,6 +85,7 @@ private: float m_avgNbRecovery; int m_nbOriginalBlocks; int m_nbFECBlocks; + int m_sampleBits; int m_samplesCount; std::size_t m_tickCount; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui index 65506f809..837506fe2 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui @@ -373,6 +373,16 @@
    + + + + Sample size (bits) + + + 16b + + + diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h index 44bc818e6..86dd05c90 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h @@ -140,6 +140,7 @@ public: float getAvgNbRecovery() const { return m_avgNbRecovery; } int getNbOriginalBlocksPerFrame() const { return m_nbOriginalBlocksPerFrame; } int getNbFECBlocksPerFrame() const { return m_nbFECBlocksPerFrame; } + int getSampleBits() const { return m_sampleBits; } static MsgReportSDRdaemonSourceStreamTiming* create(uint32_t tv_sec, uint32_t tv_usec, @@ -154,7 +155,8 @@ public: float avgNbOriginalBlocks, float avgNbRecovery, int nbOriginalBlocksPerFrame, - int nbFECBlocksPerFrame) + int nbFECBlocksPerFrame, + int sampleBits) { return new MsgReportSDRdaemonSourceStreamTiming(tv_sec, tv_usec, @@ -169,7 +171,8 @@ public: avgNbOriginalBlocks, avgNbRecovery, nbOriginalBlocksPerFrame, - nbFECBlocksPerFrame); + nbFECBlocksPerFrame, + sampleBits); } protected: @@ -187,6 +190,7 @@ public: float m_avgNbRecovery; int m_nbOriginalBlocksPerFrame; int m_nbFECBlocksPerFrame; + int m_sampleBits; MsgReportSDRdaemonSourceStreamTiming(uint32_t tv_sec, uint32_t tv_usec, @@ -201,7 +205,8 @@ public: float avgNbOriginalBlocks, float avgNbRecovery, int nbOriginalBlocksPerFrame, - int nbFECBlocksPerFrame) : + int nbFECBlocksPerFrame, + int sampleBits) : Message(), m_tv_sec(tv_sec), m_tv_usec(tv_usec), @@ -216,7 +221,8 @@ public: m_avgNbOriginalBlocks(avgNbOriginalBlocks), m_avgNbRecovery(avgNbRecovery), m_nbOriginalBlocksPerFrame(nbOriginalBlocksPerFrame), - m_nbFECBlocksPerFrame(nbFECBlocksPerFrame) + m_nbFECBlocksPerFrame(nbFECBlocksPerFrame), + m_sampleBits(sampleBits) { } }; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index 5577e55d5..9402b7a79 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -307,6 +307,7 @@ void SDRdaemonSourceUDPHandler::tick() int minNbOriginalBlocks = m_sdrDaemonBuffer.getMinOriginalBlocks(); int nbOriginalBlocks = m_sdrDaemonBuffer.getCurrentMeta().m_nbOriginalBlocks; int nbFECblocks = m_sdrDaemonBuffer.getCurrentMeta().m_nbFECBlocks; + int sampleBits = m_sdrDaemonBuffer.getCurrentMeta().m_sampleBits; //framesDecodingStatus = (minNbOriginalBlocks == nbOriginalBlocks ? 2 : (minNbOriginalBlocks < nbOriginalBlocks - nbFECblocks ? 0 : 1)); if (minNbBlocks < nbOriginalBlocks) { @@ -331,7 +332,8 @@ void SDRdaemonSourceUDPHandler::tick() m_sdrDaemonBuffer.getAvgOriginalBlocks(), m_sdrDaemonBuffer.getAvgNbRecovery(), nbOriginalBlocks, - nbFECblocks); + nbFECblocks, + sampleBits); m_outputMessageQueueToGUI->push(report); } From 7e97f62615be2283033472cc70c0cc832ab075f1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 9 Sep 2018 19:28:44 +0200 Subject: [PATCH 718/956] SDRDaemonSource: sample bit size conversion 16 / 24 bits bidirectional --- .../sdrdaemonsourcesettings.cpp | 6 +- .../sdrdaemonsourceudphandler.cpp | 32 +++- swagger/sdrangel/examples/rx_test.py | 144 ++++++++++-------- 3 files changed, 108 insertions(+), 74 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp index 76b8a7fae..de6bfc2fd 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp @@ -38,8 +38,8 @@ QByteArray SDRdaemonSourceSettings::serialize() const SimpleSerializer s(1); s.writeString(5, m_apiAddress); - s.writeU32(6, m_dataPort); - s.writeU32(7, m_apiPort); + s.writeU32(6, m_apiPort); + s.writeU32(7, m_dataPort); s.writeString(8, m_dataAddress); s.writeBool(9, m_dcBlock); s.writeBool(10, m_iqCorrection); @@ -62,7 +62,7 @@ bool SDRdaemonSourceSettings::deserialize(const QByteArray& data) quint32 uintval; d.readString(5, &m_apiAddress, "127.0.0.1"); d.readU32(6, &uintval, 9090); - m_dataPort = uintval % (1<<16); + m_apiPort = uintval % (1<<16); d.readU32(7, &uintval, 9091); m_dataPort = uintval % (1<<16); d.readString(8, &m_dataAddress, "127.0.0.1"); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index 9402b7a79..d10b399d9 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -264,14 +264,15 @@ void SDRdaemonSourceUDPHandler::tick() } m_readLength = m_readLengthSamples * SDRdaemonSourceBuffer::m_iqSampleSize; + const SDRdaemonSourceBuffer::MetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); - if (SDR_RX_SAMP_SZ == 16) + if (SDR_RX_SAMP_SZ == metaData.m_sampleBits) // same sample size { // read samples directly feeding the SampleFifo (no callback) m_sampleFifo->write(reinterpret_cast(m_sdrDaemonBuffer.readData(m_readLength)), m_readLength); m_samplesCount += m_readLengthSamples; } - else if (SDR_RX_SAMP_SZ == 24) + else if (metaData.m_sampleBits == 16) // 16 -> 24 bits { if (m_readLengthSamples > m_converterBufferNbSamples) { @@ -283,14 +284,37 @@ void SDRdaemonSourceUDPHandler::tick() for (unsigned int is = 0; is < m_readLengthSamples; is++) { - m_converterBuffer[2*is] = ((int16_t*)buf)[2*is]; + m_converterBuffer[2*is] = ((int16_t*)buf)[2*is]; // I m_converterBuffer[2*is]<<=8; - m_converterBuffer[2*is+1] = ((int16_t*)buf)[2*is+1]; + m_converterBuffer[2*is+1] = ((int16_t*)buf)[2*is+1]; // Q m_converterBuffer[2*is+1]<<=8; } m_sampleFifo->write(reinterpret_cast(m_converterBuffer), m_readLengthSamples*sizeof(Sample)); } + else if (metaData.m_sampleBits == 24) // 24 -> 16 bits + { + if (m_readLengthSamples > m_converterBufferNbSamples) + { + if (m_converterBuffer) { delete[] m_converterBuffer; } + m_converterBuffer = new int32_t[m_readLengthSamples]; + } + + uint8_t *buf = m_sdrDaemonBuffer.readData(m_readLength); + + for (unsigned int is = 0; is < m_readLengthSamples; is++) + { + m_converterBuffer[is] = ((int32_t *)buf)[2*is]>>8; // I -> MSB + m_converterBuffer[is] <<=16; + m_converterBuffer[is] += ((int32_t *)buf)[2*is+1]>>8; // Q -> LSB + } + + m_sampleFifo->write(reinterpret_cast(m_converterBuffer), m_readLengthSamples*sizeof(Sample)); + } + else + { + qWarning("SDRdaemonSourceUDPHandler::tick: unexpected sample size in stream: %d bits", (int) metaData.m_sampleBits); + } if (m_tickCount < m_rateDivider) { diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index ccfaad328..5152f9184 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -13,26 +13,28 @@ requests_methods = { "DELETE": requests.delete } + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %%prog [-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") - parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") - parser.add_option("-D", "--device-hwid", dest="device_hwid", help="device hardware id", metavar="HWID", type="string") - parser.add_option("-C", "--channel-id", dest="channel_id", help="channel id", metavar="ID", type="string", default="NFMDemod") - parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") + parser.add_option("-D", "--device-hwid", dest="device_hwid", help="device hardware id", metavar="HWID", type="string") + parser.add_option("-C", "--channel-id", dest="channel_id", help="channel id", metavar="ID", type="string", default="NFMDemod") + parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") parser.add_option("-f", "--channel-freq", dest="channel_freq", help="channel center frequency (Hz)", metavar="FREQ", type="int") - parser.add_option("-U", "--copy-to-udp", dest="udp_copy", help="UDP audio copy to
    [:]", metavar="IP:PORT", type="string") - parser.add_option("-A", "--antenna-path", dest="antenna_path", help="antenna path index", metavar="INDEX", type="int") + parser.add_option("-U", "--copy-to-udp", dest="udp_copy", help="UDP audio copy to
    [:]", metavar="IP:PORT", type="string") + parser.add_option("-A", "--antenna-path", dest="antenna_path", help="antenna path index", metavar="INDEX", type="int") parser.add_option("-s", "--sample-rate", dest="sample_rate", help="device to host sample rate (kS/s)", metavar="RATE", type="int") - parser.add_option("-l", "--log2-decim", dest="log2_decim", help="log2 of the desired software decimation factor", metavar="LOG2", type="int", default=4) - parser.add_option("-b", "--af-bw", dest="af_bw", help="audio babdwidth (kHz)", metavar="FREQUENCY_KHZ", type="int" ,default=3) - parser.add_option("-r", "--rf-bw", dest="rf_bw", help="RF babdwidth (Hz). Sets to nearest available", metavar="FREQUENCY", type="int", default=10000) + parser.add_option("-l", "--log2-decim", dest="log2_decim", help="log2 of the desired software decimation factor", metavar="LOG2", type="int", default=4) + parser.add_option("--device-rfbw", dest="device_rfbw", help="Device RF bandwidth in kHz", metavar="FREQUENCY_KHZ", type="int") + parser.add_option("-b", "--channel-afbw", dest="af_bw", help="Channel audio babdwidth (kHz)", metavar="FREQUENCY_KHZ", type="int" , default=3) + parser.add_option("-r", "--channel-rfbw", dest="rf_bw", help="Channel RF bandwidth (Hz). Sets to nearest available", metavar="FREQUENCY", type="int", default=10000) parser.add_option("--vol", dest="volume", help="audio volume", metavar="VOLUME", type="float", default=1.0) parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="CREATE", action="store_true", default=False) - parser.add_option("--ppm", dest="lo_ppm", help="LO correction in PPM", metavar="PPM", type="float", default=0.0) - parser.add_option("--fc-pos", dest="fc_pos", help="Center frequency position 0:inf 1:sup 2:cen", metavar="ENUM", type="int", default=2) + parser.add_option("--ppm", dest="lo_ppm", help="LO correction in PPM", metavar="PPM", type="float", default=0.0) + parser.add_option("--fc-pos", dest="fc_pos", help="Center frequency position 0:inf 1:sup 2:cen", metavar="ENUM", type="int", default=2) parser.add_option("--sq", dest="squelch_db", help="Squelsch threshold in dB", metavar="DECIBEL", type="float", default=-50.0) parser.add_option("--sq-gate", dest="squelch_gate", help="Squelsch gate in ms", metavar="MILLISECONDS", type="int", default=50) parser.add_option("--stereo", dest="stereo", help="Broadcast FM stereo", metavar="BOOL", action="store_true", default=False) @@ -50,32 +52,33 @@ def getInputOptions(): parser.add_option("--dmn-port", dest="daemon_port", help="DaemonSink: destination data port", metavar="PORT", type="int") parser.add_option("--dmn-fec", dest="daemon_fec", help="DaemonSink: number of FEC blocks per frame", metavar="NUMBER", type="int") parser.add_option("--dmn-tx-delay", dest="daemon_tx_delay", help="DaemonSink: inter block UDP Tx delay percentage", metavar="PERCENT", type="int") - + (options, args) = parser.parse_args() - + if options.address == None: options.address = "127.0.0.1:8091" - + if options.device_index == None: options.device_index = 0 - + if options.device_hwid == None: options.device_hwid = "FileSource" - + if options.device_freq == None: options.device_freq = 435000 - + if options.channel_freq == None: options.channel_freq = 0 - + if options.antenna_path == None: options.antenna_path = 0 - + if options.sample_rate == None: options.sample_rate = 2600 - + return options + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -85,25 +88,27 @@ def printResponse(response): elif "text/plain" in content_type: print(response.text) + # ====================================================================== def callAPI(url, method, params, json, text): request_method = requests_methods.get(method, None) if request_method is not None: - #print(base_url, url, json) - r = request_method(url=base_url+url, params=params, json=json) + # print(base_url, url, json) + r = request_method(url=base_url + url, params=params, json=json) if r.status_code / 100 == 2: print(text + " succeeded") printResponse(r) - return r.json() # all 200 yield application/json response + return r.json() # all 200 yield application/json response else: print(text + " failed") printResponse(r) return None + # ====================================================================== def setup_audio(options): audio_dict = {} - if options.audio_name: # must not be None and reference a valid audio device + if options.audio_name: # must not be None and reference a valid audio device audio_dict["name"] = options.audio_name if options.audio_udp: audio_dict["copyToUDP"] = 0 if options.audio_udp == 0 else 1 @@ -114,39 +119,40 @@ def setup_audio(options): if options.audio_port: audio_dict["udpPort"] = options.audio_port if options.audio_channels: - audio_dict["udpChannelMode"] = 0 if options.audio_channels < 0 else 3 if options.audio_channels > 3 else options.audio_channels - + audio_dict["udpChannelMode"] = 0 if options.audio_channels < 0 else 3 if options.audio_channels > 3 else options.audio_channels + r = callAPI('/audio/output/parameters', "PATCH", None, audio_dict, "setup audio {}".format(options.audio_name)) if r is None: exit(-1) + # ====================================================================== def setupDevice(deviceset_url, options): r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "%s" % options.device_hwid, "tx": 0}, "setup device on Rx device set") if r is None: exit(-1) - + settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") if settings is None: exit(-1) # calculate RF analog and FIR optimal bandpass filters bandwidths - lpFIRBW = options.sample_rate*1000 / (1<> sys.stderr, tb From 5dfc60331c04ff5a287809252a434218ca1fd3c6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 9 Sep 2018 19:52:27 +0200 Subject: [PATCH 719/956] REST API Python examples: source formatting and exception Python3 compatibility --- swagger/sdrangel/examples/add_channel.py | 15 +- .../sdrangel/examples/devicesets_config.py | 2 +- swagger/sdrangel/examples/limesdr_tx.py | 50 ++++--- swagger/sdrangel/examples/nfm_test.py | 36 ++--- swagger/sdrangel/examples/ptt.py | 47 +++--- swagger/sdrangel/examples/rtlsdr_settings.py | 44 +++--- swagger/sdrangel/examples/rx_test.py | 5 +- swagger/sdrangel/examples/rx_tx_test.py | 131 ++++++++--------- swagger/sdrangel/examples/scanner.py | 134 +++++++++-------- swagger/sdrangel/examples/start_stop.py | 34 +++-- swagger/sdrangel/examples/stop_server.py | 18 ++- swagger/sdrangel/examples/tx_test.py | 136 +++++++++--------- 12 files changed, 347 insertions(+), 305 deletions(-) diff --git a/swagger/sdrangel/examples/add_channel.py b/swagger/sdrangel/examples/add_channel.py index 4dee49d14..4de12f9cc 100644 --- a/swagger/sdrangel/examples/add_channel.py +++ b/swagger/sdrangel/examples/add_channel.py @@ -5,22 +5,23 @@ from optparse import OptionParser base_url = "http://127.0.0.1:8091/sdrangel" + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %%prog [-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") - parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") parser.add_option("-c", "--channel-id", dest="channel_id", help="channel ID of channel to add", metavar="ID", type="string") (options, args) = parser.parse_args() - + if options.address is None: options.address = "127.0.0.1:8888" - + if options.device_index is None or options.device_index < 0: options.device_index = 0 - + if options.channel_id is None: print("Please specify channel Id") exit(1) @@ -42,8 +43,8 @@ def main(): else: print("Error adding channel. HTTP: %d" % r.status_code) print json.dumps(r.json(), indent=4, sort_keys=True) - - except Exception, msg: + + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/devicesets_config.py b/swagger/sdrangel/examples/devicesets_config.py index f2bb4965d..6d797de87 100755 --- a/swagger/sdrangel/examples/devicesets_config.py +++ b/swagger/sdrangel/examples/devicesets_config.py @@ -73,7 +73,7 @@ def main(): print("All done!") - except Exception, msg: + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/limesdr_tx.py b/swagger/sdrangel/examples/limesdr_tx.py index 62fd8951b..fa20ab6d1 100644 --- a/swagger/sdrangel/examples/limesdr_tx.py +++ b/swagger/sdrangel/examples/limesdr_tx.py @@ -13,23 +13,25 @@ requests_methods = { "DELETE": requests.delete } + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %%prog [-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") - parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") (options, args) = parser.parse_args() - + if options.address == None: options.address = "127.0.0.1:8091" - + if options.device_index == None: options.device_index = 1 - + return options + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -39,38 +41,40 @@ def printResponse(response): elif "text/plain" in content_type: print(response.text) + # ====================================================================== def callAPI(url, method, params, json, text): request_method = requests_methods.get(method, None) if request_method is not None: - r = request_method(url=base_url+url, params=params, json=json) + r = request_method(url=base_url + url, params=params, json=json) if r.status_code / 100 == 2: print(text + " succeeded") printResponse(r) - return r.json() # all 200 yield application/json response + return r.json() # all 200 yield application/json response else: print(text + " failed") printResponse(r) return None + # ====================================================================== def main(): try: options = getInputOptions() - + global base_url base_url = "http://%s/sdrangel" % options.address - + r = callAPI("/deviceset", "POST", {"tx": 1}, None, "Add Tx device set") if r is None: exit(-1) - + deviceset_url = "/deviceset/%d" % options.device_index - + r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "LimeSDR", "tx": 1}, "setup LimeSDR on Tx device set") if r is None: exit(-1) - + settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get LimeSDR Tx settings") if settings is None: exit(-1) @@ -81,35 +85,35 @@ def main(): settings["limeSdrOutputSettings"]["ncoEnable"] = 1 settings["limeSdrOutputSettings"]["ncoFrequency"] = -500000 settings["limeSdrOutputSettings"]["lpfFIRBW"] = 100000 - settings["limeSdrOutputSettings"]["lpfFIREnable"] = 1 - + settings["limeSdrOutputSettings"]["lpfFIREnable"] = 1 + r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch LimeSDR Tx settings") if r is None: exit(-1) - + r = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": "NFMMod", "tx": 1}, "Create NFM mod") if r is None: exit(-1) - + settings = callAPI(deviceset_url + "/channel/0/settings", "GET", None, None, "Get NFM mod settings") if settings is None: exit(-1) - + settings["NFMModSettings"]["cwKeyer"]["text"] = "VVV DE F4EXB " settings["NFMModSettings"]["cwKeyer"]["loop"] = 1 - settings["NFMModSettings"]["cwKeyer"]["mode"] = 1 # text - settings["NFMModSettings"]["modAFInput"] = 4 # CW text + settings["NFMModSettings"]["cwKeyer"]["mode"] = 1 # text + settings["NFMModSettings"]["modAFInput"] = 4 # CW text settings["NFMModSettings"]["toneFrequency"] = 600 - + r = callAPI(deviceset_url + "/channel/0/settings", "PATCH", None, settings, "Change NFM mod") if r is None: exit(-1) - + r = callAPI(deviceset_url + "/device/run", "POST", None, None, "Start device on deviceset R1") if r is None: exit(-1) - - except Exception, msg: + + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/nfm_test.py b/swagger/sdrangel/examples/nfm_test.py index febbd1a8e..09ea421d2 100644 --- a/swagger/sdrangel/examples/nfm_test.py +++ b/swagger/sdrangel/examples/nfm_test.py @@ -13,19 +13,21 @@ requests_methods = { "DELETE": requests.delete } + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %%prog [-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") (options, args) = parser.parse_args() - + if (options.address == None): options.address = "127.0.0.1:8091" - + return options + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -35,58 +37,60 @@ def printResponse(response): elif "text/plain" in content_type: print(response.text) + # ====================================================================== def callAPI(url, method, params, json, text): request_method = requests_methods.get(method, None) if request_method is not None: - r = request_method(url=base_url+url, params=params, json=json) + r = request_method(url=base_url + url, params=params, json=json) if r.status_code / 100 == 2: print(text + " succeeded") printResponse(r) - return r.json() # all 200 yield application/json response + return r.json() # all 200 yield application/json response else: print(text + " failed") printResponse(r) return None + # ====================================================================== def main(): try: options = getInputOptions() - + global base_url base_url = "http://%s/sdrangel" % options.address - + settings = callAPI("/deviceset/0/channel", "POST", None, {"channelType": "NFMDemod", "tx": 0}, "Create NFM demod") if settings is None: exit(-1) settings["NFMDemodSettings"]["inputFrequencyOffset"] = 12500 settings["NFMDemodSettings"]["afBandwidth"] = 5000 - + r = callAPI("/deviceset/0/channel/0/settings", "PATCH", None, settings, "Change NFM demod") if r is None: exit(-1) - + r = callAPI("/deviceset", "POST", {"tx": 1}, None, "Add Tx device set") if r is None: exit(-1) - + settings = callAPI("/deviceset/1/channel", "POST", None, {"channelType": "NFMMod", "tx": 1}, "Create NFM mod") if settings is None: exit(-1) - + settings["NFMModSettings"]["inputFrequencyOffset"] = 12500 settings["NFMModSettings"]["cwKeyer"]["text"] = "VVV DE F4EXB " settings["NFMModSettings"]["cwKeyer"]["loop"] = 1 - settings["NFMModSettings"]["cwKeyer"]["mode"] = 1 # text - settings["NFMModSettings"]["modAFInput"] = 4 # CW text - + settings["NFMModSettings"]["cwKeyer"]["mode"] = 1 # text + settings["NFMModSettings"]["modAFInput"] = 4 # CW text + r = callAPI("/deviceset/1/channel/0/settings", "PATCH", None, settings, "Change NFM mod") if r is None: exit(-1) - - except Exception, msg: + + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/ptt.py b/swagger/sdrangel/examples/ptt.py index 5c1d245e0..5960f531f 100755 --- a/swagger/sdrangel/examples/ptt.py +++ b/swagger/sdrangel/examples/ptt.py @@ -16,27 +16,29 @@ from optparse import OptionParser base_url = "http://127.0.0.1:8091/sdrangel" + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %prog [-a address:port] [-d index][-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") parser.add_option("-t", "--transmit", dest="transmit", help="transmit", metavar="TRANSMIT", action="store_true", default=False) parser.add_option("-d", "--deviceset-index", dest="deviceset_index", help="index of currently active device set (Rx or Tx)", metavar="INDEX", type="int") (options, args) = parser.parse_args() - + if options.address == None: options.address = "127.0.0.1:8091" - + if options.deviceset_index == None: options.deviceset_index = 0 - + return options + # ====================================================================== def startDevice(deviceIndex): - dev_run_url = base_url+("/deviceset/%d/device/run" % deviceIndex) + dev_run_url = base_url + ("/deviceset/%d/device/run" % deviceIndex) r = requests.get(url=dev_run_url) if r.status_code / 100 == 2: rj = r.json() @@ -55,9 +57,10 @@ def startDevice(deviceIndex): else: print("Error getting device %d running state" % deviceIndex) + # ====================================================================== def stopDevice(deviceIndex): - dev_run_url = base_url+("/deviceset/%d/device/run" % deviceIndex) + dev_run_url = base_url + ("/deviceset/%d/device/run" % deviceIndex) r = requests.get(url=dev_run_url) if r.status_code / 100 == 2: rj = r.json() @@ -75,10 +78,11 @@ def stopDevice(deviceIndex): print("Cannot get device %d running state" % deviceIndex) else: print("Error getting device %d running state" % deviceIndex) - + + # ====================================================================== def setFocus(deviceIndex): - dev_focus_url = base_url+("/deviceset/%d/focus" % deviceIndex) + dev_focus_url = base_url + ("/deviceset/%d/focus" % deviceIndex) r = requests.patch(url=dev_focus_url) if r.status_code / 100 == 2: print("Focus set on device set %d" % deviceIndex) @@ -86,43 +90,44 @@ def setFocus(deviceIndex): print("Set focus on device set is not supported in a server instance") else: print("Error setting focus on device set %d" % deviceIndex) - + + # ====================================================================== def main(): try: options = getInputOptions() global base_url base_url = "http://%s/sdrangel" % options.address - r = requests.get(url=base_url+"/devicesets") + r = requests.get(url=base_url + "/devicesets") if r.status_code / 100 == 2: rj = r.json() deviceSets = rj.get("deviceSets", None) if deviceSets is not None: if len(deviceSets) > 1: if options.transmit: - if deviceSets[options.deviceset_index]["samplingDevice"]["tx"] == 0 and deviceSets[options.deviceset_index+1]["samplingDevice"]["tx"] == 1: + if deviceSets[options.deviceset_index]["samplingDevice"]["tx"] == 0 and deviceSets[options.deviceset_index + 1]["samplingDevice"]["tx"] == 1: stopDevice(options.deviceset_index) time.sleep(1) - startDevice(options.deviceset_index+1) - setFocus(options.deviceset_index+1) + startDevice(options.deviceset_index + 1) + setFocus(options.deviceset_index + 1) else: - print("Incorrect configuration expecting Rx%d and Tx%d" % (options.deviceset_index, options.deviceset_index+1)) + print("Incorrect configuration expecting Rx%d and Tx%d" % (options.deviceset_index, options.deviceset_index + 1)) else: - if deviceSets[options.deviceset_index-1]["samplingDevice"]["tx"] == 0 and deviceSets[options.deviceset_index]["samplingDevice"]["tx"] == 1: + if deviceSets[options.deviceset_index - 1]["samplingDevice"]["tx"] == 0 and deviceSets[options.deviceset_index]["samplingDevice"]["tx"] == 1: stopDevice(options.deviceset_index) time.sleep(1) - startDevice(options.deviceset_index-1) - setFocus(options.deviceset_index-1) + startDevice(options.deviceset_index - 1) + setFocus(options.deviceset_index - 1) else: - print("Incorrect configuration expecting Rx%d and Tx%d" % (options.deviceset_index-1, options.deviceset_index)) + print("Incorrect configuration expecting Rx%d and Tx%d" % (options.deviceset_index - 1, options.deviceset_index)) else: print("Need at least a Rx and a Tx device set") else: - print("Cannot get device sets configuration") + print("Cannot get device sets configuration") else: print("Error getting device sets configuration") - - except Exception, msg: + + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/rtlsdr_settings.py b/swagger/sdrangel/examples/rtlsdr_settings.py index b415f9153..069c8a5a9 100644 --- a/swagger/sdrangel/examples/rtlsdr_settings.py +++ b/swagger/sdrangel/examples/rtlsdr_settings.py @@ -11,29 +11,32 @@ requests_methods = { "DELETE": requests.delete } + def getHwType(): - r = requests.get(url=base_url+"/deviceset/0") - if r.status_code / 100 == 2: + r = requests.get(url=base_url + "/deviceset/0") + if r.status_code / 100 == 2: rj = r.json() devj = rj.get('samplingDevice', None) if devj is not None: - return devj.get('hwType' ,None) + return devj.get('hwType' , None) else: return None else: return None - + + def selectRtlSdr(): - r = requests.put(url=base_url+"/deviceset/0/device", json={"hwType": "RTLSDR"}) - if r.status_code / 100 == 2: + r = requests.put(url=base_url + "/deviceset/0/device", json={"hwType": "RTLSDR"}) + if r.status_code / 100 == 2: print json.dumps(r.json(), indent=4, sort_keys=True) return True else: return False - + + def getRtlSdrSettings(): - r = requests.get(url=base_url+"/deviceset/0/device/settings") - if r.status_code / 100 == 2: + r = requests.get(url=base_url + "/deviceset/0/device/settings") + if r.status_code / 100 == 2: rj = r.json() hwType = rj.get('deviceHwType', None) if hwType is not None and hwType == "RTLSDR": @@ -43,26 +46,28 @@ def getRtlSdrSettings(): return None else: return None - + + def patchRtlSdrSettings(settings): new_settings = {"deviceHwType": "RTLSDR", "tx": 0, "rtlSdrSettings": settings} - r = requests.patch(url=base_url+"/deviceset/0/device/settings", json=new_settings) - if r.status_code / 100 == 2: + r = requests.patch(url=base_url + "/deviceset/0/device/settings", json=new_settings) + if r.status_code / 100 == 2: print json.dumps(r.json(), indent=4, sort_keys=True) else: print "Error HTTP:", r.status_code - + + def deviceRun(run): if run: - r = requests.post(url=base_url+"/deviceset/0/device/run") + r = requests.post(url=base_url + "/deviceset/0/device/run") else: - r = requests.delete(url=base_url+"/deviceset/0/device/run") - if r.status_code / 100 == 2: + r = requests.delete(url=base_url + "/deviceset/0/device/run") + if r.status_code / 100 == 2: print json.dumps(r.json(), indent=4, sort_keys=True) else: print "Error HTTP:", r.status_code - - + + def main(): hwType = getHwType() if hwType is not None: @@ -79,8 +84,7 @@ def main(): settings["gain"] = 445 settings["centerFrequency"] = 433900000 patchRtlSdrSettings(settings) - + if __name__ == "__main__": main() - \ No newline at end of file diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index 5152f9184..ec6957c0f 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -358,10 +358,7 @@ def main(): time.sleep(1) setup_audio(options) -# if options.channel_id == "BFMDemod": -# channelsReport(deviceset_url) - - except Exception, msg: + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/rx_tx_test.py b/swagger/sdrangel/examples/rx_tx_test.py index 832be0a23..621a2eb0e 100644 --- a/swagger/sdrangel/examples/rx_tx_test.py +++ b/swagger/sdrangel/examples/rx_tx_test.py @@ -13,52 +13,54 @@ requests_methods = { "DELETE": requests.delete } + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %%prog [-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") - parser.add_option("-R", "--device-hwid-rx", dest="device_hwid_rx", help="device hardware id for Rx", metavar="HWID", type="string") - parser.add_option("-T", "--device-hwid-tx", dest="device_hwid_tx", help="device hardware id for Tx", metavar="HWID", type="string") - parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-R", "--device-hwid-rx", dest="device_hwid_rx", help="device hardware id for Rx", metavar="HWID", type="string") + parser.add_option("-T", "--device-hwid-tx", dest="device_hwid_tx", help="device hardware id for Tx", metavar="HWID", type="string") + parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") parser.add_option("-f", "--channel-freq", dest="channel_freq", help="channel center frequency (Hz)", metavar="FREQ", type="int") parser.add_option("-U", "--copy-to-udp", dest="udp_copy", help="UDP audio copy to
    [:]", metavar="IP:PORT", type="string") parser.add_option("-s", "--sample-rate-rx", dest="sample_rate_rx", help="device to host (Rx) sample rate (kS/s)", metavar="RATE", type="int") - parser.add_option("-S", "--sample-rate-tx", dest="sample_rate_tx", help="host to device (Tx) sample rate (kS/s)", metavar="RATE", type="int") - parser.add_option("-n", "--antenna-path-rx", dest="antenna_path_rx", help="antenna path index (Rx)", metavar="INDEX", type="int") - parser.add_option("-N", "--antenna-path-tx", dest="antenna_path_tx", help="antenna path index (Tx)", metavar="INDEX", type="int") + parser.add_option("-S", "--sample-rate-tx", dest="sample_rate_tx", help="host to device (Tx) sample rate (kS/s)", metavar="RATE", type="int") + parser.add_option("-n", "--antenna-path-rx", dest="antenna_path_rx", help="antenna path index (Rx)", metavar="INDEX", type="int") + parser.add_option("-N", "--antenna-path-tx", dest="antenna_path_tx", help="antenna path index (Tx)", metavar="INDEX", type="int") (options, args) = parser.parse_args() - + if options.address == None: options.address = "127.0.0.1:8091" - + if options.device_hwid_rx == None: options.device_hwid_rx = "FileSource" - + if options.device_hwid_tx == None: options.device_hwid_tx = "FileSink" - + if options.device_freq == None: options.device_freq = 435000 - + if options.channel_freq == None: options.channel_freq = 0 - + if options.sample_rate_rx == None: options.sample_rate_rx = 2600 - + if options.sample_rate_tx == None: options.sample_rate_tx = 2600 - + if options.antenna_path_rx == None: options.antenna_path_rx = 0 - + if options.antenna_path_tx == None: options.antenna_path_tx = 0 - + return options + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -68,44 +70,46 @@ def printResponse(response): elif "text/plain" in content_type: print(response.text) + # ====================================================================== def callAPI(url, method, params, json, text): request_method = requests_methods.get(method, None) if request_method is not None: - r = request_method(url=base_url+url, params=params, json=json) + r = request_method(url=base_url + url, params=params, json=json) if r.status_code / 100 == 2: print(text + " succeeded") printResponse(r) - return r.json() # all 200 yield application/json response + return r.json() # all 200 yield application/json response else: print(text + " failed") printResponse(r) return None + # ====================================================================== def main(): try: options = getInputOptions() - + global base_url base_url = "http://%s/sdrangel" % options.address - + r = callAPI("/devicesets", "GET", None, None, "Get device set configuration") if r is None: exit(-1) - + nb_devicesets = r['devicesetcount'] - - if nb_devicesets == 0: # server starts without device set so add Rx device set + + if nb_devicesets == 0: # server starts without device set so add Rx device set r1 = callAPI("/deviceset", "POST", {"tx": 0}, None, "Add Rx device set") if r1 is None: exit(-1) - - ### Rx setup - + + # ## Rx setup + deviceset_index_rx = 0 deviceset_url = "/deviceset/%d" % deviceset_index_rx - + r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "%s" % options.device_hwid_rx, "tx": 0}, "setup device on Rx device set") if r is None: exit(-1) @@ -113,13 +117,13 @@ def main(): settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") if settings is None: exit(-1) - + if options.device_hwid_rx == "LimeSDR": settings["limeSdrInputSettings"]["antennaPath"] = options.antenna_path_rx - settings["limeSdrInputSettings"]["devSampleRate"] = options.sample_rate_rx*1000 + settings["limeSdrInputSettings"]["devSampleRate"] = options.sample_rate_rx * 1000 settings["limeSdrInputSettings"]["log2HardDecim"] = 4 settings["limeSdrInputSettings"]["log2SoftDecim"] = 3 - settings["limeSdrInputSettings"]["centerFrequency"] = options.device_freq*1000 + 500000 + settings["limeSdrInputSettings"]["centerFrequency"] = options.device_freq * 1000 + 500000 settings["limeSdrInputSettings"]["ncoEnable"] = 1 settings["limeSdrInputSettings"]["ncoFrequency"] = -500000 settings["limeSdrInputSettings"]["lpfBW"] = 1450000 @@ -127,34 +131,34 @@ def main(): settings["limeSdrInputSettings"]["lpfFIREnable"] = 1 settings['limeSdrInputSettings']['dcBlock'] = 1 elif options.device_hwid_rx == "RTLSDR": - settings['rtlSdrSettings']['devSampleRate'] = options.sample_rate_rx*1000 - settings['rtlSdrSettings']['centerFrequency'] = options.device_freq*1000 + settings['rtlSdrSettings']['devSampleRate'] = options.sample_rate_rx * 1000 + settings['rtlSdrSettings']['centerFrequency'] = options.device_freq * 1000 settings['rtlSdrSettings']['gain'] = 496 settings['rtlSdrSettings']['log2Decim'] = 4 settings['rtlSdrSettings']['dcBlock'] = 1 settings['rtlSdrSettings']['agc'] = 1 elif options.device_hwid_rx == "HackRF": settings['hackRFInputSettings']['LOppmTenths'] = -51 - settings['hackRFInputSettings']['centerFrequency'] = options.device_freq*1000 + settings['hackRFInputSettings']['centerFrequency'] = options.device_freq * 1000 settings['hackRFInputSettings']['dcBlock'] = 1 - settings['hackRFInputSettings']['devSampleRate'] = options.sample_rate_rx*1000 + settings['hackRFInputSettings']['devSampleRate'] = options.sample_rate_rx * 1000 settings['hackRFInputSettings']['lnaExt'] = 1 settings['hackRFInputSettings']['lnaGain'] = 32 settings['hackRFInputSettings']['log2Decim'] = 4 settings['hackRFInputSettings']['vgaGain'] = 24 - + r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") if r is None: exit(-1) - + r = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": "NFMDemod", "tx": 0}, "Create NFM demod") if r is None: exit(-1) - + settings = callAPI(deviceset_url + "/channel/0/settings", "GET", None, None, "Get NFM demod settings") if settings is None: exit(-1) - + settings["NFMDemodSettings"]["title"] = "Test NFM" settings["NFMDemodSettings"]["inputFrequencyOffset"] = options.channel_freq settings["NFMDemodSettings"]["rfBandwidth"] = 12500 @@ -162,7 +166,7 @@ def main(): settings["NFMDemodSettings"]["afBandwidth"] = 4000 settings["NFMDemodSettings"]["squelch"] = -700 settings["NFMDemodSettings"]["volume"] = 2.0 - + if options.udp_copy is not None: address_port = options.udp_copy.split(':') if len(address_port) > 1: @@ -170,37 +174,37 @@ def main(): if len(address_port) > 0: settings["NFMDemodSettings"]["udpAddress"] = address_port[0] settings["NFMDemodSettings"]["copyAudioToUDP"] = 1 - + r = callAPI(deviceset_url + "/channel/0/settings", "PATCH", None, settings, "Change NFM demod") if r is None: exit(-1) - + r = callAPI(deviceset_url + "/device/run", "POST", None, None, "Start running device") if r is None: exit(-1) - - ### Tx setup - + + # ## Tx setup + r = callAPI("/deviceset", "POST", {"tx": 1}, None, "Add Tx device set") if r is None: exit(-1) - + deviceset_url = "/deviceset/%d" % (deviceset_index_rx + 1) - + r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "%s" % options.device_hwid_tx, "tx": 1}, "setup device on Tx device set") if r is None: exit(-1) - + settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") if settings is None: exit(-1) if options.device_hwid_tx == "LimeSDR": settings["limeSdrOutputSettings"]["antennaPath"] = options.antenna_path_tx - settings["limeSdrOutputSettings"]["devSampleRate"] = options.sample_rate_tx*1000 + settings["limeSdrOutputSettings"]["devSampleRate"] = options.sample_rate_tx * 1000 settings["limeSdrOutputSettings"]["log2HardInterp"] = 4 settings["limeSdrOutputSettings"]["log2SoftInterp"] = 4 - settings["limeSdrOutputSettings"]["centerFrequency"] = options.device_freq*1000 + 500000 + settings["limeSdrOutputSettings"]["centerFrequency"] = options.device_freq * 1000 + 500000 settings["limeSdrOutputSettings"]["ncoEnable"] = 1 settings["limeSdrOutputSettings"]["ncoFrequency"] = -500000 settings["limeSdrOutputSettings"]["lpfBW"] = 4050000 @@ -208,44 +212,43 @@ def main(): settings["limeSdrOutputSettings"]["lpfFIREnable"] = 1 elif options.device_hwid_tx == "HackRF": settings['hackRFOutputSettings']['LOppmTenths'] = -51 - settings['hackRFOutputSettings']['centerFrequency'] = options.device_freq*1000 - settings['hackRFOutputSettings']['devSampleRate'] = options.sample_rate_tx*1000 + settings['hackRFOutputSettings']['centerFrequency'] = options.device_freq * 1000 + settings['hackRFOutputSettings']['devSampleRate'] = options.sample_rate_tx * 1000 settings['hackRFOutputSettings']['lnaExt'] = 0 settings['hackRFOutputSettings']['log2Interp'] = 4 settings['hackRFOutputSettings']['vgaGain'] = 24 - + r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") if r is None: exit(-1) - + r = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": "NFMMod", "tx": 1}, "Create NFM mod") if r is None: exit(-1) - + settings = callAPI(deviceset_url + "/channel/0/settings", "GET", None, None, "Get NFM mod settings") if settings is None: exit(-1) - + settings["NFMModSettings"]["title"] = "Test NFM" settings["NFMModSettings"]["inputFrequencyOffset"] = options.channel_freq settings["NFMModSettings"]["cwKeyer"]["text"] = "VVV DE F4EXB " settings["NFMModSettings"]["cwKeyer"]["loop"] = 1 - settings["NFMModSettings"]["cwKeyer"]["mode"] = 1 # text - settings["NFMModSettings"]["modAFInput"] = 4 # CW text + settings["NFMModSettings"]["cwKeyer"]["mode"] = 1 # text + settings["NFMModSettings"]["modAFInput"] = 4 # CW text settings["NFMModSettings"]["toneFrequency"] = 600 - + r = callAPI(deviceset_url + "/channel/0/settings", "PATCH", None, settings, "Change NFM mod") if r is None: exit(-1) - + deviceset_url = "/deviceset/%d" % deviceset_index_rx - + r = callAPI(deviceset_url + "/focus", "PATCH", None, None, "set focus on Rx device set") if r is None: exit(-1) - - - except Exception, msg: + + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/scanner.py b/swagger/sdrangel/examples/scanner.py index 961d8604a..597332103 100755 --- a/swagger/sdrangel/examples/scanner.py +++ b/swagger/sdrangel/examples/scanner.py @@ -28,41 +28,44 @@ requests_methods = { "DELETE": requests.delete } + # ====================================================================== class ScanControl: - def __init__(self, num_channels, channel_step, start_freq, stop_freq, log2_decim): + + def __init__(self, num_channels, channel_step, start_freq, stop_freq, log2_decim): self.channel_shifts = [] if num_channels < 2: self.channel_shifts = [0] limit = 0 else: - limit = ((num_channels-1)*channel_step) / 2 + limit = ((num_channels - 1) * channel_step) / 2 self.channel_shifts = list(np.linspace(-limit, limit, num_channels)) - self.device_start_freq = start_freq + limit + self.device_start_freq = start_freq + limit self.device_stop_freq = stop_freq - limit - self.device_step_freq = 2*limit + channel_step - self.device_sample_rate = (2*limit + channel_step)*(1< 2: options.verbosity = 2 - + return options + # ====================================================================== def setupDevice(scan_control, options): settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") if settings is None: exit(-1) - + if options.device_hwid == "AirspyHF": if scan_control.device_start_freq > 30000000: settings["airspyHFSettings"]["bandIndex"] = 1 @@ -134,7 +138,7 @@ def setupDevice(scan_control, options): settings['rtlSdrSettings']['loPpmCorrection'] = int(options.lo_ppm) settings['rtlSdrSettings']['rfBandwidth'] = scan_control.device_step_freq + 100000 elif options.device_hwid == "HackRF": - settings['hackRFInputSettings']['LOppmTenths'] = int(options.lo_ppm * 10) # in tenths of PPM + settings['hackRFInputSettings']['LOppmTenths'] = int(options.lo_ppm * 10) # in tenths of PPM settings['hackRFInputSettings']['centerFrequency'] = scan_control.device_start_freq settings['hackRFInputSettings']['fcPos'] = options.fc_pos settings['hackRFInputSettings']['dcBlock'] = options.fc_pos == 2 @@ -148,7 +152,8 @@ def setupDevice(scan_control, options): r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") if r is None: exit(-1) - + + # ====================================================================== def changeDeviceFrequency(fc, options): settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") @@ -168,6 +173,7 @@ def changeDeviceFrequency(fc, options): if r is None: exit(-1) + # ====================================================================== def setupChannels(scan_control, options): i = 0 @@ -185,8 +191,8 @@ def setupChannels(scan_control, options): settings["NFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 settings["NFMDemodSettings"]["rfBandwidth"] = options.rf_bw settings["NFMDemodSettings"]["volume"] = options.volume - settings["NFMDemodSettings"]["squelch"] = options.squelch_db * 10 # centi-Bels - settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate / 10 # 10's of ms + settings["NFMDemodSettings"]["squelch"] = options.squelch_db * 10 # centi-Bels + settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate / 10 # 10's of ms settings["NFMDemodSettings"]["title"] = "Channel %d" % i elif options.channel_id == "AMDemod": settings["AMDemodSettings"]["inputFrequencyOffset"] = int(shift) @@ -194,7 +200,7 @@ def setupChannels(scan_control, options): settings["AMDemodSettings"]["volume"] = options.volume settings["AMDemodSettings"]["squelch"] = options.squelch_db settings["AMDemodSettings"]["title"] = "Channel %d" % i - settings["AMDemodSettings"]["bandpassEnable"] = 1 # bandpass filter + settings["AMDemodSettings"]["bandpassEnable"] = 1 # bandpass filter elif options.channel_id == "DSDDemod": settings["DSDDemodSettings"]["inputFrequencyOffset"] = int(shift) settings["DSDDemodSettings"]["rfBandwidth"] = options.rf_bw @@ -205,13 +211,14 @@ def setupChannels(scan_control, options): settings["DSDDemodSettings"]["enableCosineFiltering"] = 1 settings["DSDDemodSettings"]["pllLock"] = 1 settings["DSDDemodSettings"]["title"] = "Channel %d" % i - + r = callAPI(deviceset_url + "/channel/%d/settings" % i, "PATCH", None, settings, "Change demod") if r is None: exit(-1) - - i += 1 - + + i += 1 + + # ====================================================================== def checkScanning(fc, options, display_message): reports = callAPI(deviceset_url + "/channels/report", "GET", None, None, "Get channels report") @@ -222,19 +229,20 @@ def checkScanning(fc, options, display_message): channel = reports["channels"][i] if "report" in channel: if reportKey in channel["report"]: - if options.channel_id == "DSDDemod": # DSD is special because it only stops on voice + if options.channel_id == "DSDDemod": # DSD is special because it only stops on voice stopCondition = channel["report"][reportKey]["slot1On"] == 1 or channel["report"][reportKey]["slot2On"] == 1 else: stopCondition = channel["report"][reportKey]["squelch"] == 1 if stopCondition: - f_channel = channel["deltaFrequency"]+fc - f_frac = round(f_channel/options.excl_tol) + f_channel = channel["deltaFrequency"] + fc + f_frac = round(f_channel / options.excl_tol) if f_frac not in options.excl_flist: - if display_message: # display message only when stopping for the first time - print("%s Stopped at %d Hz" % (datetime.datetime.now().strftime("%H:%M:%S"),f_frac*options.excl_tol)) - return False # stop scanning - return True # continue scanning - + if display_message: # display message only when stopping for the first time + print("%s Stopped at %d Hz" % (datetime.datetime.now().strftime("%H:%M:%S"), f_frac * options.excl_tol)) + return False # stop scanning + return True # continue scanning + + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -244,17 +252,18 @@ def printResponse(response): elif "text/plain" in content_type: print(response.text) + # ====================================================================== def callAPI(url, method, params, json, text): request_method = requests_methods.get(method, None) if request_method is not None: - r = request_method(url=base_url+url, params=params, json=json) + r = request_method(url=base_url + url, params=params, json=json) if r.status_code / 100 == 2: if verbosity >= 1: print(text + " succeeded") if verbosity >= 2: printResponse(r) - return r.json() # all 200 yield application/json response + return r.json() # all 200 yield application/json response else: if verbosity >= 1: print(text + " failed") @@ -262,12 +271,13 @@ def callAPI(url, method, params, json, text): printResponse(r) return None + # ====================================================================== def main(): try: options = getInputOptions() scan_control = ScanControl(options.num_channels, options.freq_step, options.freq_start, options.freq_stop, options.log2_decim) - + # Print calculated scan parameters print("Channel shifts: %s" % scan_control.channel_shifts) @@ -275,43 +285,43 @@ def main(): print("Start: %d" % scan_control.device_start_freq) print("Stop: %d" % scan_control.device_stop_freq) print("Step: %d" % scan_control.device_step_freq) - + if scan_control.device_stop_freq < scan_control.device_start_freq: print("Frequency error") exit(1) - + freqs = [] nb_steps = 0 fc = scan_control.device_start_freq while fc <= scan_control.device_stop_freq: - freqs += [x+fc for x in scan_control.channel_shifts] + freqs += [x + fc for x in scan_control.channel_shifts] fc += scan_control.device_step_freq - nb_steps += 1 + nb_steps += 1 print("Scanned frequencies: %s" % freqs) print("Skipped frequencies: %s" % options.excl_flist) print("In %d steps" % nb_steps) - if options.mock: # Stop there if we are just mocking (no API access) + if options.mock: # Stop there if we are just mocking (no API access) exit(0) - + global base_url base_url = "http://%s/sdrangel" % options.address - - # Set Rx - + + # Set Rx + global deviceset_url deviceset_url = "/deviceset/%d" % options.device_index - - if not options.rerun: # Skip device and channels settings in re-run mode + + if not options.rerun: # Skip device and channels settings in re-run mode if options.create: r = callAPI("/deviceset", "POST", {"tx": 0}, None, "Add Rx device set") if r is None: exit(-1) - + r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": options.device_hwid, "tx": 0}, "setup device on Rx device set") if r is None: exit(-1) - + # Set device and channels setupDevice(scan_control, options) @@ -325,7 +335,7 @@ def main(): fc = scan_control.device_start_freq global verbosity - verbosity = options.verbosity + verbosity = options.verbosity print("Move center to %d Hz" % fc) changeDeviceFrequency(fc, options) @@ -335,7 +345,7 @@ def main(): resume_delay = 0 while True: time.sleep(options.settling_time) - scanning = checkScanning(fc, options, scanning and resume_delay == 0) # shall we move on ? + scanning = checkScanning(fc, options, scanning and resume_delay == 0) # shall we move on ? if scanning: if resume_delay > 0: resume_delay -= 1 @@ -354,14 +364,14 @@ def main(): print("Terminated by user") pass finally: - verbosity = 2 + verbosity = 2 r = callAPI(deviceset_url + "/device/run", "DELETE", None, None, "Stop running device") if r is None: exit(-1) - + except KeyboardInterrupt: pass - except Exception, msg: + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/start_stop.py b/swagger/sdrangel/examples/start_stop.py index 6f266258a..8a09baa0c 100644 --- a/swagger/sdrangel/examples/start_stop.py +++ b/swagger/sdrangel/examples/start_stop.py @@ -5,38 +5,40 @@ from optparse import OptionParser base_url = "http://127.0.0.1:8091/sdrangel" + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %%prog [-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") - parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") parser.add_option("-t", "--stop", dest="stop", help="stop device", metavar="STOP", action="store_true", default=False) parser.add_option("-s", "--start", dest="start", help="start device", metavar="START", action="store_true", default=False) (options, args) = parser.parse_args() - + if (options.address == None): options.address = "127.0.0.1:8888" - + if options.device_index < 0: otions.device_index = 0 - + if options.start and options.stop: print("Cannot start and stop at the same time") exit(1) - + if not options.start and not options.stop: print("Must start or stop") exit(1) return options + # ====================================================================== def startDevice(deviceIndex): - dev_run_url = base_url+("/deviceset/%d/device/run" % deviceIndex) + dev_run_url = base_url + ("/deviceset/%d/device/run" % deviceIndex) r = requests.get(url=dev_run_url) - if r.status_code / 100 == 2: + if r.status_code / 100 == 2: rj = r.json() state = rj.get("state", None) if state is not None: @@ -53,11 +55,12 @@ def startDevice(deviceIndex): else: print("Error getting device %d running state" % deviceIndex) + # ====================================================================== def stopDevice(deviceIndex): - dev_run_url = base_url+("/deviceset/%d/device/run" % deviceIndex) + dev_run_url = base_url + ("/deviceset/%d/device/run" % deviceIndex) r = requests.get(url=dev_run_url) - if r.status_code / 100 == 2: + if r.status_code / 100 == 2: rj = r.json() state = rj.get("state", None) if state is not None: @@ -73,15 +76,16 @@ def stopDevice(deviceIndex): print("Cannot get device %d running state" % deviceIndex) else: print("Error getting device %d running state" % deviceIndex) - + + # ====================================================================== def main(): try: options = getInputOptions() global base_url base_url = "http://%s/sdrangel" % options.address - r = requests.get(url=base_url+"/devicesets") - if r.status_code / 100 == 2: + r = requests.get(url=base_url + "/devicesets") + if r.status_code / 100 == 2: rj = r.json() deviceSets = rj.get("deviceSets", None) if deviceSets is not None: @@ -96,8 +100,8 @@ def main(): print("Cannot get device sets configuration") else: print("Error getting device sets configuration") - - except Exception, msg: + + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/stop_server.py b/swagger/sdrangel/examples/stop_server.py index d7ff6d67a..365e7159c 100644 --- a/swagger/sdrangel/examples/stop_server.py +++ b/swagger/sdrangel/examples/stop_server.py @@ -13,19 +13,21 @@ requests_methods = { "DELETE": requests.delete } + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %%prog [-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") (options, args) = parser.parse_args() - + if (options.address == None): options.address = "127.0.0.1:8091" return options + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -35,25 +37,27 @@ def printResponse(response): elif "text/plain" in content_type: print(response.text) + # ====================================================================== def callAPI(url, method, params, json, text): request_method = requests_methods.get(method, None) if request_method is not None: - r = request_method(url=base_url+url, params=params, json=json) - if r.status_code / 100 == 2: + r = request_method(url=base_url + url, params=params, json=json) + if r.status_code / 100 == 2: print(text + " succeeded") printResponse(r) - return r.json() # all 200 yield application/json response + return r.json() # all 200 yield application/json response else: print(text + " failed") printResponse(r) return None + # ====================================================================== def main(): try: options = getInputOptions() - + global base_url base_url = "http://%s/sdrangel" % options.address @@ -61,7 +65,7 @@ def main(): if settings is None: exit(-1) - except Exception, msg: + except Exception as ex: tb = traceback.format_exc() print >> sys.stderr, tb diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index 06fb64605..37ef3b3b4 100755 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -14,15 +14,16 @@ requests_methods = { "DELETE": requests.delete } + # ====================================================================== def getInputOptions(): parser = OptionParser(usage="usage: %%prog [-t]\n") - parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") - parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") - parser.add_option("-D", "--device-hwid", dest="device_hwid", help="device hardware id", metavar="HWID", type="string") - parser.add_option("-C", "--channel-id", dest="channel_id", help="channel id", metavar="ID", type="string", default="NFMDemod") - parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") + parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") + parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") + parser.add_option("-D", "--device-hwid", dest="device_hwid", help="device hardware id", metavar="HWID", type="string") + parser.add_option("-C", "--channel-id", dest="channel_id", help="channel id", metavar="ID", type="string", default="NFMDemod") + parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") parser.add_option("-f", "--channel-freq", dest="channel_freq", help="channel center frequency (Hz)", metavar="FREQ", type="int") parser.add_option("-s", "--sample-rate", dest="sample_rate", help="host to device sample rate (S/s)", metavar="RATE", type="int") parser.add_option("-l", "--log2-interp", dest="log2_interp", help="log2 of interpolation factor", metavar="RATE", type="int") @@ -34,25 +35,25 @@ def getInputOptions(): parser.add_option("--video", dest="video_file", help="video file for ATV modulator (sends video)", metavar="FILENAME", type="string") (options, args) = parser.parse_args() - + if options.address == None: options.address = "127.0.0.1:8091" - + if options.device_index == None: options.device_index = 1 - + if options.device_hwid == None: options.device_hwid = "FileSource" - + if options.device_freq == None: options.device_freq = 435000 - + if options.channel_freq == None: options.channel_freq = 0 - + if options.sample_rate == None: options.sample_rate = 2600000 - + if options.log2_interp == None: options.log2_interp = 4 @@ -64,6 +65,7 @@ def getInputOptions(): return options + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -73,36 +75,39 @@ def printResponse(response): elif "text/plain" in content_type: print(response.text) + # ====================================================================== def callAPI(url, method, params, json, text): request_method = requests_methods.get(method, None) if request_method is not None: - r = request_method(url=base_url+url, params=params, json=json) + r = request_method(url=base_url + url, params=params, json=json) if r.status_code / 100 == 2: print(text + " succeeded") printResponse(r) - return r.json() # all 200 yield application/json response + return r.json() # all 200 yield application/json response else: print(text + " failed") printResponse(r) return None + # ====================================================================== def setupBladeRFXB200(fc): if fc < 50000: - return 5 # BLADERF_XB200_AUTO_3DB + return 5 # BLADERF_XB200_AUTO_3DB elif fc < 54000: - return 0 # BLADERF_XB200_50M + return 0 # BLADERF_XB200_50M elif fc < 144000: - return 5 # BLADERF_XB200_AUTO_3DB + return 5 # BLADERF_XB200_AUTO_3DB elif fc < 148000: - return 1 # BLADERF_XB200_144M + return 1 # BLADERF_XB200_144M elif fc < 222000: - return 5 # BLADERF_XB200_AUTO_3DB + return 5 # BLADERF_XB200_AUTO_3DB elif fc < 225000: - return 2 # BLADERF_XB200_222M + return 2 # BLADERF_XB200_222M else: - return 5 # BLADERF_XB200_AUTO_3DB + return 5 # BLADERF_XB200_AUTO_3DB + # ====================================================================== def setupDevice(options): @@ -111,17 +116,17 @@ def setupDevice(options): exit(-1) # calculate RF analog and FIR optimal bandpass filters bandwidths - lpFIRBW = options.sample_rate / (1<> sys.stderr, tb From 7158107e58c29e18e18d7e5fa8afd23b3f376603 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 9 Sep 2018 21:26:47 +0200 Subject: [PATCH 720/956] SDRDaemonSink: 16/24 bit support (1): works for 16 bit stream --- .../sdrdaemonsource/sdrdaemonsourcebuffer.cpp | 9 +++------ .../sdrdaemonsource/sdrdaemonsourcebuffer.h | 4 ++-- .../sdrdaemonsource/sdrdaemonsourcegui.cpp | 2 ++ .../sdrdaemonsource/sdrdaemonsourcegui.h | 1 + .../sdrdaemonsource/sdrdaemonsourceinput.h | 14 ++++++++++---- .../sdrdaemonsource/sdrdaemonsourceudphandler.cpp | 6 ++++-- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp index a4e721c96..43ec44c24 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp @@ -25,9 +25,6 @@ -const int SDRdaemonSourceBuffer::m_sampleSize = 2; -const int SDRdaemonSourceBuffer::m_iqSampleSize = 2 * m_sampleSize; - SDRdaemonSourceBuffer::SDRdaemonSourceBuffer() : m_decoderIndexHead(nbDecoderSlots/2), m_frameHead(0), @@ -152,7 +149,7 @@ void SDRdaemonSourceBuffer::rwCorrectionEstimate(int slotIndex) dBytes = (nbDecoderSlots * sizeof(BufferFrame)) - normalizedReadIndex - rwDelta; } - m_balCorrection = (m_balCorrection / 4) + (dBytes / (int) (m_iqSampleSize * m_nbReads)); // correction is in number of samples. Alpha = 0.25 + m_balCorrection = (m_balCorrection / 4) + (dBytes / (int) (m_currentMeta.m_sampleBytes * 2 * m_nbReads)); // correction is in number of samples. Alpha = 0.25 if (m_balCorrection < -m_balCorrLimit) { m_balCorrection = -m_balCorrLimit; @@ -310,9 +307,9 @@ void SDRdaemonSourceBuffer::writeData(char *array) int sampleRate = metaData->m_sampleRate; if (sampleRate > 0) { - m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * m_iqSampleSize); + m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * m_currentMeta.m_sampleBytes * 2); m_balCorrLimit = sampleRate / 1000; // +/- 1 ms correction max per read - m_readNbBytes = (sampleRate * m_iqSampleSize) / 20; + m_readNbBytes = (sampleRate * m_currentMeta.m_sampleBytes * 2) / 20; } printMeta("SDRdaemonSourceBuffer::writeData: new meta", metaData); // print for change other than timestamp diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h index 3cfbd2e76..9b3a671e4 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h @@ -52,6 +52,8 @@ public: void init() { memset((char *) this, 0, sizeof(MetaDataFEC)); + m_sampleBits = 16; // assume 16 bits samples to start with + m_sampleBytes = 2; } }; @@ -158,8 +160,6 @@ public: static const int m_udpPayloadSize = SDRDAEMONSOURCE_UDPSIZE; static const int m_nbOriginalBlocks = SDRDAEMONSOURCE_NBORIGINALBLOCKS; - static const int m_sampleSize; - static const int m_iqSampleSize; private: static const int nbDecoderSlots = SDRDAEMONSOURCE_NBDECODERSLOTS; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index a272dcf3b..802d16368 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -57,6 +57,7 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent m_nbOriginalBlocks(128), m_nbFECBlocks(0), m_sampleBits(16), + m_sampleBytes(2), m_samplesCount(0), m_tickCount(0), m_addressEdited(false), @@ -211,6 +212,7 @@ bool SDRdaemonSourceGui::handleMessage(const Message& message) m_avgNbRecovery = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getAvgNbRecovery(); m_nbOriginalBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getNbOriginalBlocksPerFrame(); m_sampleBits = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getSampleBits(); + m_sampleBytes = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getSampleBytes(); int nbFECBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getNbFECBlocksPerFrame(); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h index 89cb6dc0c..457705f5a 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h @@ -86,6 +86,7 @@ private: int m_nbOriginalBlocks; int m_nbFECBlocks; int m_sampleBits; + int m_sampleBytes; int m_samplesCount; std::size_t m_tickCount; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h index 86dd05c90..077a2b1ac 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h @@ -141,6 +141,7 @@ public: int getNbOriginalBlocksPerFrame() const { return m_nbOriginalBlocksPerFrame; } int getNbFECBlocksPerFrame() const { return m_nbFECBlocksPerFrame; } int getSampleBits() const { return m_sampleBits; } + int getSampleBytes() const { return m_sampleBytes; } static MsgReportSDRdaemonSourceStreamTiming* create(uint32_t tv_sec, uint32_t tv_usec, @@ -156,7 +157,8 @@ public: float avgNbRecovery, int nbOriginalBlocksPerFrame, int nbFECBlocksPerFrame, - int sampleBits) + int sampleBits, + int sampleBytes) { return new MsgReportSDRdaemonSourceStreamTiming(tv_sec, tv_usec, @@ -172,7 +174,8 @@ public: avgNbRecovery, nbOriginalBlocksPerFrame, nbFECBlocksPerFrame, - sampleBits); + sampleBits, + sampleBytes); } protected: @@ -191,6 +194,7 @@ public: int m_nbOriginalBlocksPerFrame; int m_nbFECBlocksPerFrame; int m_sampleBits; + int m_sampleBytes; MsgReportSDRdaemonSourceStreamTiming(uint32_t tv_sec, uint32_t tv_usec, @@ -206,7 +210,8 @@ public: float avgNbRecovery, int nbOriginalBlocksPerFrame, int nbFECBlocksPerFrame, - int sampleBits) : + int sampleBits, + int sampleBytes) : Message(), m_tv_sec(tv_sec), m_tv_usec(tv_usec), @@ -222,7 +227,8 @@ public: m_avgNbRecovery(avgNbRecovery), m_nbOriginalBlocksPerFrame(nbOriginalBlocksPerFrame), m_nbFECBlocksPerFrame(nbFECBlocksPerFrame), - m_sampleBits(sampleBits) + m_sampleBits(sampleBits), + m_sampleBytes(sampleBytes) { } }; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index d10b399d9..c92c7dd82 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -263,8 +263,8 @@ void SDRdaemonSourceUDPHandler::tick() m_readLengthSamples += m_sdrDaemonBuffer.getRWBalanceCorrection(); } - m_readLength = m_readLengthSamples * SDRdaemonSourceBuffer::m_iqSampleSize; const SDRdaemonSourceBuffer::MetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); + m_readLength = m_readLengthSamples * metaData.m_sampleBytes * 2; if (SDR_RX_SAMP_SZ == metaData.m_sampleBits) // same sample size { @@ -332,6 +332,7 @@ void SDRdaemonSourceUDPHandler::tick() int nbOriginalBlocks = m_sdrDaemonBuffer.getCurrentMeta().m_nbOriginalBlocks; int nbFECblocks = m_sdrDaemonBuffer.getCurrentMeta().m_nbFECBlocks; int sampleBits = m_sdrDaemonBuffer.getCurrentMeta().m_sampleBits; + int sampleBytes = m_sdrDaemonBuffer.getCurrentMeta().m_sampleBytes; //framesDecodingStatus = (minNbOriginalBlocks == nbOriginalBlocks ? 2 : (minNbOriginalBlocks < nbOriginalBlocks - nbFECblocks ? 0 : 1)); if (minNbBlocks < nbOriginalBlocks) { @@ -357,7 +358,8 @@ void SDRdaemonSourceUDPHandler::tick() m_sdrDaemonBuffer.getAvgNbRecovery(), nbOriginalBlocks, nbFECblocks, - sampleBits); + sampleBits, + sampleBytes); m_outputMessageQueueToGUI->push(report); } From 22746ff8134ba9f18c6468ca3a065757578a2535 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 9 Sep 2018 22:35:25 +0200 Subject: [PATCH 721/956] SDRDaemonSink: 16/24 bit support (2): works for 16 bit stream --- .../sdrdaemonsource/sdrdaemonsourcebuffer.cpp | 2 +- .../samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp index 43ec44c24..d3bafe007 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp @@ -174,7 +174,7 @@ void SDRdaemonSourceBuffer::checkSlotData(int slotIndex) if (sampleRate > 0) { int64_t ts = m_currentMeta.m_tv_sec * 1000000LL + m_currentMeta.m_tv_usec; - ts -= (rwDelayBytes * 1000000LL) / (sampleRate * sizeof(SDRdaemonSample)); + ts -= (rwDelayBytes * 1000000LL) / (sampleRate * 2 * m_currentMeta.m_sampleBytes); m_tvOut_sec = ts / 1000000LL; m_tvOut_usec = ts - (m_tvOut_sec * 1000000LL); } diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h index 9b3a671e4..20873deb3 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h @@ -57,12 +57,6 @@ public: } }; - struct SDRdaemonSample - { - int16_t i; - int16_t q; - }; - struct Header { uint16_t frameIndex; @@ -70,12 +64,11 @@ public: uint8_t filler; }; - static const int samplesPerBlock = (SDRDAEMONSOURCE_UDPSIZE - sizeof(Header)) / sizeof(SDRdaemonSample); static const int framesSize = SDRDAEMONSOURCE_NBDECODERSLOTS * (SDRDAEMONSOURCE_NBORIGINALBLOCKS - 1) * (SDRDAEMONSOURCE_UDPSIZE - sizeof(Header)); struct ProtectedBlock { - SDRdaemonSample samples[samplesPerBlock]; + uint8_t buf[SDRDAEMONSOURCE_UDPSIZE - sizeof(Header)]; }; struct SuperBlock From 404c73fb802b8ccc565a11c609d0175c3b95de7e Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 10 Sep 2018 02:52:36 +0200 Subject: [PATCH 722/956] SDRDaemon: make Rx side truly 24/16 bit compatible in all configurations --- plugins/channelrx/daemonsink/daemonsink.cpp | 4 ++-- plugins/channelrx/daemonsink/daemonsinkgui.cpp | 2 +- plugins/samplesink/sdrdaemonsink/readme.md | 6 +++--- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp | 2 +- plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp | 6 +++--- plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp | 5 ++--- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 3 ++- plugins/samplesource/sdrdaemonsource/readme.md | 5 +++-- .../samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h | 1 + plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp | 2 +- .../sdrdaemonsource/sdrdaemonsourceudphandler.cpp | 6 +++--- sdrdaemon/channel/sdrdaemondatablock.h | 4 +++- 12 files changed, 25 insertions(+), 21 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index 4ca7ae2e8..a30dacc12 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -52,7 +52,7 @@ DaemonSink::DaemonSink(DeviceSourceAPI *deviceAPI) : m_dataBlock(0), m_centerFrequency(0), m_sampleRate(48000), - m_sampleBytes(SDR_RX_SAMP_SZ == 24 ? 4 : 2), + m_sampleBytes(SDR_RX_SAMP_SZ <= 16 ? 2 : 4), m_nbBlocksFEC(0), m_txDelay(35), m_dataAddress("127.0.0.1"), @@ -82,7 +82,7 @@ DaemonSink::~DaemonSink() void DaemonSink::setTxDelay(int txDelay, int nbBlocksFEC) { double txDelayRatio = txDelay / 100.0; - double delay = m_sampleRate == 0 ? 1.0 : (127*127*txDelayRatio) / m_sampleRate; + double delay = m_sampleRate == 0 ? 1.0 : (127*SDRDaemonSamplesPerBlock*txDelayRatio) / m_sampleRate; delay /= 128 + nbBlocksFEC; m_txDelay = roundf(delay*1e6); // microseconds qDebug() << "DaemonSink::setTxDelay:" diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp index 9819b2efc..1900958b0 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -287,7 +287,7 @@ void DaemonSinkGUI::on_nbFECBlocks_valueChanged(int value) void DaemonSinkGUI::updateTxDelayTime() { double txDelayRatio = m_settings.m_txDelay / 100.0; - double delay = m_sampleRate == 0 ? 0.0 : (127*127*txDelayRatio) / m_sampleRate; + double delay = m_sampleRate == 0 ? 0.0 : (127*SDRDaemonSamplesPerBlock*txDelayRatio) / m_sampleRate; delay /= 128 + m_settings.m_nbFECBlocks; ui->txDelayTime->setText(tr("%1µs").arg(QString::number(delay*1e6, 'f', 0))); } diff --git a/plugins/samplesink/sdrdaemonsink/readme.md b/plugins/samplesink/sdrdaemonsink/readme.md index 710958b24..d04f4cca9 100644 --- a/plugins/samplesink/sdrdaemonsink/readme.md +++ b/plugins/samplesink/sdrdaemonsink/readme.md @@ -50,9 +50,9 @@ The value is a percentage of the nominal time it takes to process a block of sam - Sample rate on the network: _SR_ - Delay percentage: _d_ - Number of FEC blocks: _F_ - - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 4 bytes header (1 sample) thus there are 127 samples remaining effectively. This gives the constant 127*127 = 16219 samples per frame in the formula + - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 8 bytes header (2 samples) thus there are 126 samples remaining effectively. This gives the constant 127*126 = 16002 samples per frame in the formula -Formula: ((127 ✕ 127 ✕ _d_) / _SR_) / (128 + _F_) +Formula: ((127 ✕ 126 ✕ _d_) / _SR_) / (128 + _F_)

    6: Forward Error Correction setting and status

    @@ -64,7 +64,7 @@ This sets the number of FEC blocks per frame. A frame consists of 128 data block

    6.2: Distant transmitter queue length

    -This is the samples queue length reported from the distant transmitter. This is a number of vectors of 127 ✕ 127 ✕ _I_ samples where _I_ is the interpolation factor. This corresponds to a block of 127 ✕ 127 samples sent over the network. This numbers serves to throttle the sample generator so that the queue length is close to 8 vectors. +This is the samples queue length reported from the distant transmitter. This is a number of vectors of 127 ✕ 127 ✕ _I_ samples where _I_ is the interpolation factor. This corresponds to a block of 127 ✕ 126 samples sent over the network. This numbers serves to throttle the sample generator so that the queue length is close to 8 vectors.

    6.3: Stream status

    diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 80cb47f24..08ac4ca32 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -214,7 +214,7 @@ void SDRdaemonSinkGui::updateSampleRate() void SDRdaemonSinkGui::updateTxDelayTooltip() { - double delay = ((127*127*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); + double delay = ((127*126*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(delay*1e6, 'f', 0))); } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index cf76ab08a..bc112b475 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -96,7 +96,7 @@ bool SDRdaemonSinkOutput::start() m_lastTimestampRateCorrection = 0; m_lastQueueLength = -2; // set first value out of bounds - double delay = ((127*127*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); + double delay = ((127*126*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); mutexLocker.unlock(); @@ -278,13 +278,13 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b if (changeTxDelay) { - double delay = ((127*127*settings.m_txDelay) / settings.m_sampleRate)/(128 + settings.m_nbFECBlocks); + double delay = ((127*126*settings.m_txDelay) / settings.m_sampleRate)/(128 + settings.m_nbFECBlocks); qDebug("SDRdaemonSinkOutput::applySettings: Tx delay: %f us", delay*1e6); if (m_sdrDaemonSinkThread != 0) { // delay is calculated as a fraction of the nominal UDP block process time - // frame size: 127 * 127 samples + // frame size: 127 * 126 samples // divided by sample rate gives the frame process time // divided by the number of actual blocks including FEC blocks gives the block (i.e. UDP block) process time m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index fdd7bad6a..9f9c973c6 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -29,7 +29,7 @@ MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) UDPSinkFEC::UDPSinkFEC() : m_sampleRate(48000), - m_sampleBytes(SDR_TX_SAMP_SZ == 24 ? 4 : 2), + m_sampleBytes(SDR_TX_SAMP_SZ <= 16 ? 2 : 4), m_sampleBits(SDR_TX_SAMP_SZ), m_nbSamples(0), m_nbBlocksFEC(0), @@ -101,7 +101,6 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk gettimeofday(&tv, 0); - // create meta data TODO: semaphore metaData.m_centerFrequency = 0; // frequency not set by stream metaData.m_sampleRate = m_sampleRate; metaData.m_sampleBytes = m_sampleBytes & 0xF; @@ -142,6 +141,7 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk m_txBlockIndex = 1; // next Tx block with data } + // TODO: memcpy is valid for 4 bytes samples only (16 bits) else conversion must take place to take only LSB assuming Tx is 16 bit only if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block { memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], @@ -167,7 +167,6 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk int nbBlocksFEC = m_nbBlocksFEC; int txDelay = m_txDelay; - // TODO: send blocks //qDebug("UDPSinkFEC::write: push frame to worker: %u", m_frameCount); m_udpWorker->pushTxFrame(m_txBlocks[m_txBlocksIndex], nbBlocksFEC, txDelay, m_frameCount); //m_txThread = new std::thread(transmitUDP, this, m_txBlocks[m_txBlocksIndex], m_frameCount, nbBlocksFEC, txDelay, m_cm256Valid); diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index 337a19ea5..6fea548e8 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -72,9 +72,10 @@ public: uint16_t frameIndex; uint8_t blockIndex; uint8_t filler; + uint32_t filler2; }; - static const int samplesPerBlock = (m_udpSize - sizeof(Header)) / sizeof(Sample); + static const int samplesPerBlock = (m_udpSize - sizeof(Header)) / (2 * SDR_TX_SAMP_SZ); struct ProtectedBlock { diff --git a/plugins/samplesource/sdrdaemonsource/readme.md b/plugins/samplesource/sdrdaemonsource/readme.md index b1732a09e..91a27b9c7 100644 --- a/plugins/samplesource/sdrdaemonsource/readme.md +++ b/plugins/samplesource/sdrdaemonsource/readme.md @@ -159,9 +159,10 @@ The value is a percentage of the nominal time it takes to process a block of sam - Sample rate on the network: _SR_ - Delay percentage: _d_ - Number of FEC blocks: _F_ - - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 4 bytes header (1 sample) thus there are 127 samples remaining effectively. This gives the constant 127*127 = 16219 samples per frame in the formula + - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 8 bytes header (1 sample for 24 bit and 2 samples for 16 bit) thus there are 126 (16 bit) or 63 (24 bit) samples remaining effectively. -Formula: ((127 ✕ 127 ✕ _d_) / _SR_) / (128 + _F_) +Formula (16 bit): ((127 ✕ 126 ✕ _d_) / _SR_) / (128 + _F_) +Formula (24 bit): ((127 ✕ 63 ✕ _d_) / _SR_) / (128 + _F_) thus half the above

    8: Desired distant device sample rate

    diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h index 20873deb3..5ac67e9fa 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h @@ -62,6 +62,7 @@ public: uint16_t frameIndex; uint8_t blockIndex; uint8_t filler; + uint32_t filler2; }; static const int framesSize = SDRDAEMONSOURCE_NBDECODERSLOTS * (SDRDAEMONSOURCE_NBORIGINALBLOCKS - 1) * (SDRDAEMONSOURCE_UDPSIZE - sizeof(Header)); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 802d16368..008b70319 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -56,7 +56,7 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent m_bufferGauge(-50), m_nbOriginalBlocks(128), m_nbFECBlocks(0), - m_sampleBits(16), + m_sampleBits(16), // assume 16 bits to start with m_sampleBytes(2), m_samplesCount(0), m_tickCount(0), diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index c92c7dd82..1eeebce59 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -264,7 +264,7 @@ void SDRdaemonSourceUDPHandler::tick() } const SDRdaemonSourceBuffer::MetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); - m_readLength = m_readLengthSamples * metaData.m_sampleBytes * 2; + m_readLength = m_readLengthSamples * (metaData.m_sampleBytes & 0xF) * 2; if (SDR_RX_SAMP_SZ == metaData.m_sampleBits) // same sample size { @@ -304,9 +304,9 @@ void SDRdaemonSourceUDPHandler::tick() for (unsigned int is = 0; is < m_readLengthSamples; is++) { - m_converterBuffer[is] = ((int32_t *)buf)[2*is]>>8; // I -> MSB + m_converterBuffer[is] = ((int32_t *)buf)[2*is+1]>>8; // Q -> MSB m_converterBuffer[is] <<=16; - m_converterBuffer[is] += ((int32_t *)buf)[2*is+1]>>8; // Q -> LSB + m_converterBuffer[is] += ((int32_t *)buf)[2*is]>>8; // I -> LSB } m_sampleFifo->write(reinterpret_cast(m_converterBuffer), m_readLengthSamples*sizeof(Sample)); diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index bf1ab552f..6138fa729 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -70,18 +70,20 @@ struct SDRDaemonHeader uint16_t m_frameIndex; uint8_t m_blockIndex; uint8_t m_filler; + uint32_t m_filler2; void init() { m_frameIndex = 0; m_blockIndex = 0; m_filler = 0; + m_filler2 = 0; } }; static const int SDRDaemonUdpSize = UDPSINKFEC_UDPSIZE; static const int SDRDaemonNbOrginalBlocks = UDPSINKFEC_NBORIGINALBLOCKS; -static const int SDRDaemonSamplesPerBlock = (UDPSINKFEC_UDPSIZE - sizeof(SDRDaemonHeader)) / (SDR_RX_SAMP_SZ/4); +static const int SDRDaemonSamplesPerBlock = (UDPSINKFEC_UDPSIZE - sizeof(SDRDaemonHeader)) / sizeof(Sample); struct SDRDaemonProtectedBlock { From 6341bddef3e483672d8f15ea70878e1ea7ca2850 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 10 Sep 2018 08:46:52 +0200 Subject: [PATCH 723/956] SDRDaemonSink: 16/24 bit samples compatibility --- .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index 9f9c973c6..c2a25ac2a 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -141,20 +141,39 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk m_txBlockIndex = 1; // next Tx block with data } - // TODO: memcpy is valid for 4 bytes samples only (16 bits) else conversion must take place to take only LSB assuming Tx is 16 bit only if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block { - memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], - (const char *) &(*it), - inRemainingSamples * sizeof(Sample)); + if (SDR_RX_SAMP_SZ == SDR_TX_SAMP_SZ) // can do direct copy if sizes are equal (to 16 bits) + { + memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], + (const char *) &(*it), + inRemainingSamples * sizeof(Sample)); + } + else // Samples are limited to 16 bits by the modulators + { + for (int is = 0; is < inRemainingSamples; is++) { + m_superBlock.protectedBlock.m_samples[m_sampleIndex+is] = *(it+is); + } + } + m_sampleIndex += inRemainingSamples; it = end; // all input samples are consumed } else // complete super block and initiate the next if not end of frame { - memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], - (const char *) &(*it), - (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); + if (SDR_RX_SAMP_SZ == SDR_TX_SAMP_SZ) // can do direct copy if sizes are equal (to 16 bits) + { + memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], + (const char *) &(*it), + (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); + } + else // Samples are limited to 16 bits by the modulators + { + for (int is = 0; is < samplesPerBlock - m_sampleIndex; is++) { + m_superBlock.protectedBlock.m_samples[m_sampleIndex+is] = *(it+is); + } + } + it += samplesPerBlock - m_sampleIndex; m_sampleIndex = 0; From 1590db3ce2e21cdee3c90bcf0d90c84651425654 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 10 Sep 2018 18:52:40 +0200 Subject: [PATCH 724/956] SDRDaemon: anonymize protected block --- plugins/channelrx/daemonsink/daemonsink.cpp | 16 +++++----- .../channelrx/daemonsink/daemonsinkgui.cpp | 3 +- plugins/channeltx/daemonsrc/daemonsrc.cpp | 1 + sdrdaemon/channel/sdrdaemonchannelsink.cpp | 13 +++++---- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 1 + sdrdaemon/channel/sdrdaemondatablock.h | 6 ++-- sdrdaemon/channel/sdrdaemondatareadqueue.cpp | 15 ++++++---- sdrdaemon/channel/sdrdaemondatareadqueue.h | 29 ++++++++++++++++++- 8 files changed, 60 insertions(+), 24 deletions(-) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp index a30dacc12..2b69618dc 100644 --- a/plugins/channelrx/daemonsink/daemonsink.cpp +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -82,7 +82,8 @@ DaemonSink::~DaemonSink() void DaemonSink::setTxDelay(int txDelay, int nbBlocksFEC) { double txDelayRatio = txDelay / 100.0; - double delay = m_sampleRate == 0 ? 1.0 : (127*SDRDaemonSamplesPerBlock*txDelayRatio) / m_sampleRate; + int samplesPerBlock = SDRDaemonNbBytesPerBlock / sizeof(Sample); + double delay = m_sampleRate == 0 ? 1.0 : (127*samplesPerBlock*txDelayRatio) / m_sampleRate; delay /= 128 + nbBlocksFEC; m_txDelay = roundf(delay*1e6); // microseconds qDebug() << "DaemonSink::setTxDelay:" @@ -152,10 +153,11 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec 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 + // handle different sample sizes... + int samplesPerBlock = SDRDaemonNbBytesPerBlock / sizeof(Sample); + if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block { - memcpy((void *) &m_superBlock.m_protectedBlock.m_samples[m_sampleIndex], + memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], (const void *) &(*(begin+inSamplesIndex)), inRemainingSamples * sizeof(Sample)); m_sampleIndex += inRemainingSamples; @@ -163,10 +165,10 @@ void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVec } else // complete super block and initiate the next if not end of frame { - memcpy((void *) &m_superBlock.m_protectedBlock.m_samples[m_sampleIndex], + memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], (const void *) &(*(begin+inSamplesIndex)), - (SDRDaemonSamplesPerBlock - m_sampleIndex) * sizeof(Sample)); - it += SDRDaemonSamplesPerBlock - m_sampleIndex; + (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); + it += samplesPerBlock - m_sampleIndex; m_sampleIndex = 0; m_superBlock.m_header.m_frameIndex = m_frameCount; diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp index 1900958b0..83338b822 100644 --- a/plugins/channelrx/daemonsink/daemonsinkgui.cpp +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -287,7 +287,8 @@ void DaemonSinkGUI::on_nbFECBlocks_valueChanged(int value) void DaemonSinkGUI::updateTxDelayTime() { double txDelayRatio = m_settings.m_txDelay / 100.0; - double delay = m_sampleRate == 0 ? 0.0 : (127*SDRDaemonSamplesPerBlock*txDelayRatio) / m_sampleRate; + int samplesPerBlock = SDRDaemonNbBytesPerBlock / sizeof(Sample); + double delay = m_sampleRate == 0 ? 0.0 : (127*samplesPerBlock*txDelayRatio) / m_sampleRate; delay /= 128 + m_settings.m_nbFECBlocks; ui->txDelayTime->setText(tr("%1µs").arg(QString::number(delay*1e6, 'f', 0))); } diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index e6f711ca1..d793da79d 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -46,6 +46,7 @@ DaemonSrc::DaemonSrc(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_sourceThread(0), m_running(false), + m_dataReadQueue(SDR_TX_SAMP_SZ <= 16 ? 4 : 8), m_nbCorrectableErrors(0), m_nbUncorrectableErrors(0) { diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp index c21e6e902..2d929450b 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsink.cpp @@ -147,10 +147,11 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const 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 + // handle different sample sizes... + int samplesPerBlock = SDRDaemonNbBytesPerBlock / sizeof(Sample); + if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block { - memcpy((void *) &m_superBlock.m_protectedBlock.m_samples[m_sampleIndex], + memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], (const void *) &(*(begin+inSamplesIndex)), inRemainingSamples * sizeof(Sample)); m_sampleIndex += inRemainingSamples; @@ -158,10 +159,10 @@ void SDRDaemonChannelSink::feed(const SampleVector::const_iterator& begin, const } else // complete super block and initiate the next if not end of frame { - memcpy((void *) &m_superBlock.m_protectedBlock.m_samples[m_sampleIndex], + memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], (const void *) &(*(begin+inSamplesIndex)), - (SDRDaemonSamplesPerBlock - m_sampleIndex) * sizeof(Sample)); - it += SDRDaemonSamplesPerBlock - m_sampleIndex; + (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); + it += samplesPerBlock - m_sampleIndex; m_sampleIndex = 0; m_superBlock.m_header.m_frameIndex = m_frameCount; diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp index 43f879cd5..9d0141272 100644 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ b/sdrdaemon/channel/sdrdaemonchannelsource.cpp @@ -50,6 +50,7 @@ SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_sourceThread(0), m_running(false), + m_dataReadQueue(SDR_TX_SAMP_SZ <= 16 ? 4 : 8), m_nbCorrectableErrors(0), m_nbUncorrectableErrors(0) { diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrdaemon/channel/sdrdaemondatablock.h index 6138fa729..75d2926b6 100644 --- a/sdrdaemon/channel/sdrdaemondatablock.h +++ b/sdrdaemon/channel/sdrdaemondatablock.h @@ -83,14 +83,14 @@ struct SDRDaemonHeader static const int SDRDaemonUdpSize = UDPSINKFEC_UDPSIZE; static const int SDRDaemonNbOrginalBlocks = UDPSINKFEC_NBORIGINALBLOCKS; -static const int SDRDaemonSamplesPerBlock = (UDPSINKFEC_UDPSIZE - sizeof(SDRDaemonHeader)) / sizeof(Sample); +static const int SDRDaemonNbBytesPerBlock = UDPSINKFEC_UDPSIZE - sizeof(SDRDaemonHeader); struct SDRDaemonProtectedBlock { - Sample m_samples[SDRDaemonSamplesPerBlock]; + uint8_t buf[SDRDaemonNbBytesPerBlock]; void init() { - std::fill(m_samples, m_samples+SDRDaemonSamplesPerBlock, Sample{0,0}); + std::fill(buf, buf+SDRDaemonNbBytesPerBlock, 0); } }; diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp index b14921166..c71158771 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.cpp @@ -25,7 +25,8 @@ const uint32_t SDRDaemonDataReadQueue::MinimumMaxSize = 10; -SDRDaemonDataReadQueue::SDRDaemonDataReadQueue() : +SDRDaemonDataReadQueue::SDRDaemonDataReadQueue(uint32_t sampleSize) : + m_sampleSize(sampleSize), m_dataBlock(0), m_maxSize(MinimumMaxSize), m_blockIndex(1), @@ -95,7 +96,7 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) qDebug("SDRDaemonDataReadQueue::readSample: initial pop new block: queue size: %u", length()); m_blockIndex = 1; m_dataBlock = m_dataReadQueue.takeFirst(); - s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; + convertDataToSample(s, m_blockIndex, m_sampleIndex); m_sampleIndex++; m_sampleCount++; } @@ -107,9 +108,11 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) return; } - if (m_sampleIndex < SDRDaemonSamplesPerBlock) + uint32_t samplesPerBlock = SDRDaemonNbBytesPerBlock / m_sampleSize; + + if (m_sampleIndex < samplesPerBlock) { - s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; + convertDataToSample(s, m_blockIndex, m_sampleIndex); m_sampleIndex++; m_sampleCount++; } @@ -120,7 +123,7 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) if (m_blockIndex < SDRDaemonNbOrginalBlocks) { - s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; + convertDataToSample(s, m_blockIndex, m_sampleIndex); m_sampleIndex++; m_sampleCount++; } @@ -141,7 +144,7 @@ void SDRDaemonDataReadQueue::readSample(Sample& s) //qDebug("SDRDaemonDataReadQueue::readSample: pop new block: queue size: %u", length()); m_blockIndex = 1; m_dataBlock = m_dataReadQueue.takeFirst(); - s = m_dataBlock->m_superBlocks[m_blockIndex].m_protectedBlock.m_samples[m_sampleIndex]; + convertDataToSample(s, m_blockIndex, m_sampleIndex); m_sampleIndex++; m_sampleCount++; } diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.h b/sdrdaemon/channel/sdrdaemondatareadqueue.h index ba43d2c47..c20a8f9c6 100644 --- a/sdrdaemon/channel/sdrdaemondatareadqueue.h +++ b/sdrdaemon/channel/sdrdaemondatareadqueue.h @@ -31,7 +31,7 @@ class Sample; class SDRDaemonDataReadQueue { public: - SDRDaemonDataReadQueue(); + SDRDaemonDataReadQueue(uint32_t sampleSize); ~SDRDaemonDataReadQueue(); void push(SDRDaemonDataBlock* dataBlock); //!< push block on the queue @@ -45,6 +45,7 @@ public: static const uint32_t MinimumMaxSize; private: + uint32_t m_sampleSize; QQueue m_dataReadQueue; SDRDaemonDataBlock *m_dataBlock; uint32_t m_maxSize; @@ -52,6 +53,32 @@ private: uint32_t m_sampleIndex; uint32_t m_sampleCount; //!< use a counter capped below 2^31 as it is going to be converted to an int in the web interface bool m_full; //!< full condition was hit + + inline void convertDataToSample(Sample& s, uint32_t blockIndex, uint32_t sampleIndex) + { + if (sizeof(Sample) == m_sampleSize) + { + s = *((Sample*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize])); + } + else if ((sizeof(Sample) == 4) && (m_sampleSize == 8)) + { + int32_t rp = *( (int32_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize]) ); + int32_t ip = *( (int32_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize+4]) ); + s.setReal(rp>>8); + s.setImag(ip>>8); + } + else if ((sizeof(Sample) == 8) && (m_sampleSize == 4)) + { + int32_t rp = *( (int16_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize]) ); + int32_t ip = *( (int16_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*m_sampleSize+2]) ); + s.setReal(rp<<8); + s.setImag(ip<<8); + } + else + { + s = Sample{0, 0}; + } + } }; From d3455d9cdf387ae953344f16b819adb046c952ae Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 00:23:22 +0200 Subject: [PATCH 725/956] Adapted to Python3 --- swagger/sdrangel/examples/rx_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/swagger/sdrangel/examples/rx_test.py b/swagger/sdrangel/examples/rx_test.py index ec6957c0f..5079fd5ec 100755 --- a/swagger/sdrangel/examples/rx_test.py +++ b/swagger/sdrangel/examples/rx_test.py @@ -95,7 +95,7 @@ def callAPI(url, method, params, json, text): if request_method is not None: # print(base_url, url, json) r = request_method(url=base_url + url, params=params, json=json) - if r.status_code / 100 == 2: + if r.status_code // 100 == 2: print(text + " succeeded") printResponse(r) return r.json() # all 200 yield application/json response @@ -137,9 +137,9 @@ def setupDevice(deviceset_url, options): exit(-1) # calculate RF analog and FIR optimal bandpass filters bandwidths - lpFIRBW = options.sample_rate * 1000 / (1 << options.log2_decim) + lpFIRBW = options.sample_rate * 1000 // (1 << options.log2_decim) if options.fc_pos == 2: # center of passband - lpfBW = options.sample_rate * 1000 / (1 << options.log2_decim) + lpfBW = options.sample_rate * 1000 // (1 << options.log2_decim) else: # side of passband if options.log2_decim == 0: lpfBW = options.sample_rate * 1000 @@ -253,7 +253,7 @@ def setupChannel(deviceset_url, options): settings["NFMDemodSettings"]["rfBandwidth"] = options.rf_bw settings["NFMDemodSettings"]["volume"] = options.volume settings["NFMDemodSettings"]["squelch"] = options.squelch_db * 10 # centi-Bels - settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate / 10 # 10's of ms + settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate // 10 # 10's of ms settings["NFMDemodSettings"]["title"] = "Channel %d" % i elif options.channel_id == "BFMDemod": settings["BFMDemodSettings"]["inputFrequencyOffset"] = options.channel_freq From 9bf030c824a8fe5dd6a5e02dd0b54203c455a613 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 00:24:18 +0200 Subject: [PATCH 726/956] SDRDaemonSink: ananonymize protected bloc in UDPSinkFEC also --- .../sdrdaemonsink/sdrdaemonsinkgui.ui | 8 +-- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 2 + .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 50 +++++++++++++++---- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 4 +- .../sdrdaemonsource/sdrdaemonsourcegui.ui | 7 ++- 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 760a65de5..fb2586dfd 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -620,7 +620,7 @@ - API IP address + Remote API IPv4 address 000.000.000.000 @@ -646,7 +646,7 @@ - API IP port + Remote API port 00000 @@ -711,7 +711,7 @@ - Remote data address + Remote data connection IPv4 address 000.000.000.000 @@ -737,7 +737,7 @@ - Remote data port + Remote data connection port 00000 diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index bc112b475..03f4808cb 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -92,9 +92,11 @@ bool SDRdaemonSinkOutput::start() m_sdrDaemonSinkThread->connectTimer(m_masterTimer); m_sdrDaemonSinkThread->startWork(); + // restart auto rate correction m_lastRemoteTimestampRateCorrection = 0; m_lastTimestampRateCorrection = 0; m_lastQueueLength = -2; // set first value out of bounds + m_chunkSizeCorrection = 0; double delay = ((127*126*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index c2a25ac2a..6fa7ee1d2 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -141,18 +141,34 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk m_txBlockIndex = 1; // next Tx block with data } + int samplesPerBlock = bytesPerBlock / (m_sampleBytes*2); + if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block { - if (SDR_RX_SAMP_SZ == SDR_TX_SAMP_SZ) // can do direct copy if sizes are equal (to 16 bits) + if (sizeof(Sample) == m_sampleBytes*2) // can do direct copy if sample sizes are equal { - memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], + memcpy((char *) &m_superBlock.protectedBlock.m_buf[m_sampleIndex*m_sampleBytes*2], (const char *) &(*it), inRemainingSamples * sizeof(Sample)); } - else // Samples are limited to 16 bits by the modulators + else if ((sizeof(Sample) == 8) && (m_sampleBytes == 2)) // modulators produce 16 bit samples { - for (int is = 0; is < inRemainingSamples; is++) { - m_superBlock.protectedBlock.m_samples[m_sampleIndex+is] = *(it+is); + for (int is = 0; is < inRemainingSamples; is++) + { + int16_t *rp = (int16_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2]); + int16_t *ip = (int16_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2+2]); + *rp = (it+is)->m_real & 0xFFFF; + *ip = (it+is)->m_imag & 0xFFFF; + } + } + else if ((sizeof(Sample) == 4) && (m_sampleBytes == 4)) // use 16 bit samples for Tx + { + for (int is = 0; is < inRemainingSamples; is++) + { + int32_t *rp = (int32_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2]); + int32_t *ip = (int32_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2+4]); + *rp = (it+is)->m_real; + *ip = (it+is)->m_imag; } } @@ -161,16 +177,30 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk } else // complete super block and initiate the next if not end of frame { - if (SDR_RX_SAMP_SZ == SDR_TX_SAMP_SZ) // can do direct copy if sizes are equal (to 16 bits) + if (sizeof(Sample) == m_sampleBytes*2) // can do direct copy if sample sizes are equal { - memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], + memcpy((char *) &m_superBlock.protectedBlock.m_buf[m_sampleIndex*m_sampleBytes*2], (const char *) &(*it), (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); } - else // Samples are limited to 16 bits by the modulators + else if ((sizeof(Sample) == 8) && (m_sampleBytes == 2)) // modulators produce 16 bit samples { - for (int is = 0; is < samplesPerBlock - m_sampleIndex; is++) { - m_superBlock.protectedBlock.m_samples[m_sampleIndex+is] = *(it+is); + for (int is = 0; is < samplesPerBlock - m_sampleIndex; is++) + { + int16_t *rp = (int16_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2]); + int16_t *ip = (int16_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2+2]); + *rp = (it+is)->m_real & 0xFFFF; + *ip = (it+is)->m_imag & 0xFFFF; + } + } + else if ((sizeof(Sample) == 4) && (m_sampleBytes == 4)) // use 16 bit samples for Tx + { + for (int is = 0; is < samplesPerBlock - m_sampleIndex; is++) + { + int32_t *rp = (int32_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2]); + int32_t *ip = (int32_t*) &(m_superBlock.protectedBlock.m_buf[(m_sampleIndex+is)*m_sampleBytes*2+4]); + *rp = (it+is)->m_real; + *ip = (it+is)->m_imag; } } diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index 6fea548e8..a1cae77f5 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -75,11 +75,11 @@ public: uint32_t filler2; }; - static const int samplesPerBlock = (m_udpSize - sizeof(Header)) / (2 * SDR_TX_SAMP_SZ); + static const int bytesPerBlock = m_udpSize - sizeof(Header); struct ProtectedBlock { - Sample m_samples[samplesPerBlock]; + uint8_t m_buf[bytesPerBlock]; }; struct SuperBlock diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui index 837506fe2..118f06235 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui @@ -584,7 +584,7 @@ - Local data connection IP address + Remote API IPv4 address 000.000.000.000 @@ -619,7 +619,7 @@ - Remote control port + Remote API port 00000 @@ -692,6 +692,9 @@ 16777215 + + Local data connection IPv4 address + 000.000.000.000 From 6aff1a3db3d66a4083e5ba8f52e6da7cb27107d5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 01:01:43 +0200 Subject: [PATCH 727/956] SDRDaemonSink: calculate Tx delay in UDP sink --- .../sdrdaemonsink/sdrdaemonsinkgui.cpp | 4 +++- .../sdrdaemonsink/sdrdaemonsinkoutput.cpp | 15 +++---------- .../sdrdaemonsink/sdrdaemonsinkthread.h | 2 +- .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 22 ++++++++++++++++--- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 10 ++++----- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 08ac4ca32..d39d3b406 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -38,6 +38,7 @@ #include "device/devicesinkapi.h" #include "device/deviceuiset.h" +#include "udpsinkfec.h" #include "sdrdaemonsinkgui.h" SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : @@ -214,7 +215,8 @@ void SDRdaemonSinkGui::updateSampleRate() void SDRdaemonSinkGui::updateTxDelayTooltip() { - double delay = ((127*126*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); + int samplesPerBlock = UDPSinkFEC::bytesPerBlock / (SDR_TX_SAMP_SZ <= 16 ? 4 : 8); + double delay = ((127*samplesPerBlock*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(delay*1e6, 'f', 0))); } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 03f4808cb..f47b42b48 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -98,8 +98,7 @@ bool SDRdaemonSinkOutput::start() m_lastQueueLength = -2; // set first value out of bounds m_chunkSizeCorrection = 0; - double delay = ((127*126*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); - m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); + m_sdrDaemonSinkThread->setTxDelay(m_settings.m_txDelay); mutexLocker.unlock(); //applySettings(m_generalSettings, m_settings, true); @@ -280,16 +279,8 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b if (changeTxDelay) { - double delay = ((127*126*settings.m_txDelay) / settings.m_sampleRate)/(128 + settings.m_nbFECBlocks); - qDebug("SDRdaemonSinkOutput::applySettings: Tx delay: %f us", delay*1e6); - - if (m_sdrDaemonSinkThread != 0) - { - // delay is calculated as a fraction of the nominal UDP block process time - // frame size: 127 * 126 samples - // divided by sample rate gives the frame process time - // divided by the number of actual blocks including FEC blocks gives the block (i.e. UDP block) process time - m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setTxDelay(settings.m_txDelay); } } diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h index 39e40bb0c..e6a2fd91a 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.h @@ -50,7 +50,7 @@ public: void setSamplerate(int samplerate); void setNbBlocksFEC(uint32_t nbBlocksFEC) { m_udpSinkFEC.setNbBlocksFEC(nbBlocksFEC); }; - void setTxDelay(uint32_t txDelay) { m_udpSinkFEC.setTxDelay(txDelay); }; + void setTxDelay(float txDelay) { m_udpSinkFEC.setTxDelay(txDelay); }; void setDataAddress(const QString& address, uint16_t port) { m_udpSinkFEC.setRemoteAddress(address, port); } bool isRunning() const { return m_running; } diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index 6fa7ee1d2..b8fd17009 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -33,6 +33,7 @@ UDPSinkFEC::UDPSinkFEC() : m_sampleBits(SDR_TX_SAMP_SZ), m_nbSamples(0), m_nbBlocksFEC(0), + m_txDelayRatio(0.0), m_txDelay(0), m_txBlockIndex(0), m_txBlocksIndex(0), @@ -66,16 +67,31 @@ UDPSinkFEC::~UDPSinkFEC() delete m_udpThread; } -void UDPSinkFEC::setTxDelay(uint32_t txDelay) +void UDPSinkFEC::setTxDelay(float txDelayRatio) { - qDebug() << "UDPSinkFEC::setTxDelay: txDelay: " << txDelay; - m_txDelay = txDelay; + // delay is calculated from the fraction of the nominal UDP block process time + // frame size: 127 * (126 or 63 samples depending on I or Q sample bytes of 2 or 4 bytes respectively) + // divided by sample rate gives the frame process time + // divided by the number of actual blocks including FEC blocks gives the block (i.e. UDP block) process time + m_txDelayRatio = txDelayRatio; + int samplesPerBlock = bytesPerBlock / (m_sampleBytes*2); + double delay = ((127*samplesPerBlock*txDelayRatio) / m_sampleRate)/(128 + m_nbBlocksFEC); + m_txDelay = delay * 1e6; + qDebug() << "UDPSinkFEC::setTxDelay: txDelay: " << txDelayRatio << " m_txDelay: " << m_txDelay << " us"; } void UDPSinkFEC::setNbBlocksFEC(uint32_t nbBlocksFEC) { qDebug() << "UDPSinkFEC::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC; m_nbBlocksFEC = nbBlocksFEC; + setTxDelay(m_txDelayRatio); +} + +void UDPSinkFEC::setSampleRate(uint32_t sampleRate) +{ + qDebug() << "UDPSinkFEC::setSampleRate: sampleRate: " << sampleRate; + m_sampleRate = sampleRate; + setTxDelay(m_txDelayRatio); } void UDPSinkFEC::setRemoteAddress(const QString& address, uint16_t port) diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index a1cae77f5..f613fb7ed 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -110,14 +110,11 @@ public: return ret; } - /** Set sample rate given in Hz */ - void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } - - void setSampleBytes(uint8_t sampleBytes) { m_sampleBytes = (sampleBytes & 0x0F) + (m_sampleBytes & 0xF0); } - void setSampleBits(uint8_t sampleBits) { m_sampleBits = sampleBits; } + /** Set sample rate given in S/s */ + void setSampleRate(uint32_t sampleRate); void setNbBlocksFEC(uint32_t nbBlocksFEC); - void setTxDelay(uint32_t txDelay); + void setTxDelay(float txDelayRatio); void setRemoteAddress(const QString& address, uint16_t port); /** Return true if the stream is OK, return false if there is an error. */ @@ -142,6 +139,7 @@ private: MetaDataFEC m_currentMetaFEC; //!< Meta data for current frame uint32_t m_nbBlocksFEC; //!< Variable number of FEC blocks + float m_txDelayRatio; //!< Delay in ratio of nominal frame period uint32_t m_txDelay; //!< Delay in microseconds (usleep) between each sending of an UDP datagram SuperBlock m_txBlocks[4][256]; //!< UDP blocks to send with original data + FEC SuperBlock m_superBlock; //!< current super block being built From 4cd9055fe65c975374f63c8de8629bf8717b9a39 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 08:41:37 +0200 Subject: [PATCH 728/956] DaemonSrc: fixed missing channel deletion on GUI deletion. Added missing signals handling --- plugins/channeltx/daemonsrc/daemonsrc.cpp | 8 ++++---- plugins/channeltx/daemonsrc/daemonsrcgui.cpp | 10 +++++++++- plugins/channeltx/daemonsrc/daemonsrcgui.h | 3 +++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/plugins/channeltx/daemonsrc/daemonsrc.cpp b/plugins/channeltx/daemonsrc/daemonsrc.cpp index d793da79d..318188243 100644 --- a/plugins/channeltx/daemonsrc/daemonsrc.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrc.cpp @@ -52,14 +52,14 @@ DaemonSrc::DaemonSrc(DeviceSinkAPI *deviceAPI) : { setObjectName(m_channelId); + connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; + m_currentMeta.init(); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); - m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; - m_currentMeta.init(); } DaemonSrc::~DaemonSrc() diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp index 003262f6a..3823a9869 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.cpp @@ -169,10 +169,13 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_daemonSrc = (DaemonSrc*) channelTx; m_daemonSrc->setMessageQueueToGUI(getInputMessageQueue()); + connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + m_channelMarker.blockSignals(true); m_channelMarker.setColor(m_settings.m_rgbColor); m_channelMarker.setCenterFrequency(0); @@ -186,8 +189,8 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); - connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); m_time.start(); @@ -198,6 +201,7 @@ DaemonSrcGUI::DaemonSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb DaemonSrcGUI::~DaemonSrcGUI() { m_deviceUISet->removeTxChannelInstance(this); + delete m_daemonSrc; delete ui; } @@ -370,3 +374,7 @@ void DaemonSrcGUI::tick() m_tickCount = 0; } } + +void DaemonSrcGUI::channelMarkerChangedByCursor() +{ +} diff --git a/plugins/channeltx/daemonsrc/daemonsrcgui.h b/plugins/channeltx/daemonsrc/daemonsrcgui.h index 7b47c9b99..4d720e3b4 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcgui.h +++ b/plugins/channeltx/daemonsrc/daemonsrcgui.h @@ -53,6 +53,9 @@ public: virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual bool handleMessage(const Message& message); +public slots: + void channelMarkerChangedByCursor(); + private: Ui::DaemonSrcGUI* ui; PluginAPI* m_pluginAPI; From 6c77f2dfe56e86483451b7e584b3cb386daad35f Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 13:41:09 +0200 Subject: [PATCH 729/956] Complete removal of SDRDaemon as a distinct binary --- CMakeLists.txt | 29 +- appdaemon/main.cpp | 122 -- plugins/channelrx/daemonsink/CMakeLists.txt | 4 +- plugins/channeltx/daemonsrc/CMakeLists.txt | 2 - .../channeltx/daemonsrc/daemonsrcthread.cpp | 1 - .../channelrx/daemonsink/CMakeLists.txt | 4 +- pluginssrv/channeltx/daemonsrc/CMakeLists.txt | 2 - sdrbase/CMakeLists.txt | 23 +- .../channel/sdrdaemondatablock.h | 0 .../channel/sdrdaemondataqueue.cpp | 0 .../channel/sdrdaemondataqueue.h | 0 .../channel/sdrdaemondatareadqueue.cpp | 0 .../channel/sdrdaemondatareadqueue.h | 0 sdrbase/sdrbase.pro | 17 +- sdrdaemon/CMakeLists.txt | 76 -- sdrdaemon/channel/sdrdaemonchannelsink.cpp | 401 ------ sdrdaemon/channel/sdrdaemonchannelsink.h | 139 --- .../channel/sdrdaemonchannelsinksettings.cpp | 96 -- .../channel/sdrdaemonchannelsinksettings.h | 43 - .../channel/sdrdaemonchannelsinkthread.cpp | 198 --- .../channel/sdrdaemonchannelsinkthread.h | 86 -- sdrdaemon/channel/sdrdaemonchannelsource.cpp | 414 ------- sdrdaemon/channel/sdrdaemonchannelsource.h | 134 -- .../sdrdaemonchannelsourcesettings.cpp | 85 -- .../channel/sdrdaemonchannelsourcesettings.h | 43 - .../channel/sdrdaemonchannelsourcethread.cpp | 193 --- .../channel/sdrdaemonchannelsourcethread.h | 115 -- sdrdaemon/sdrdaemonmain.cpp | 387 ------ sdrdaemon/sdrdaemonmain.h | 114 -- sdrdaemon/sdrdaemonparser.cpp | 262 ---- sdrdaemon/sdrdaemonparser.h | 80 -- sdrdaemon/sdrdaemonpreferences.cpp | 95 -- sdrdaemon/sdrdaemonpreferences.h | 53 - sdrdaemon/sdrdaemonsettings.cpp | 51 - sdrdaemon/sdrdaemonsettings.h | 54 - sdrdaemon/webapi/webapiadapterdaemon.cpp | 561 --------- sdrdaemon/webapi/webapiadapterdaemon.h | 116 -- sdrdaemon/webapi/webapirequestmapper.cpp | 1097 ----------------- sdrdaemon/webapi/webapirequestmapper.h | 83 -- sdrdaemon/webapi/webapiserver.cpp | 67 - sdrdaemon/webapi/webapiserver.h | 56 - 41 files changed, 29 insertions(+), 5274 deletions(-) delete mode 100644 appdaemon/main.cpp rename {sdrdaemon => sdrbase}/channel/sdrdaemondatablock.h (100%) rename {sdrdaemon => sdrbase}/channel/sdrdaemondataqueue.cpp (100%) rename {sdrdaemon => sdrbase}/channel/sdrdaemondataqueue.h (100%) rename {sdrdaemon => sdrbase}/channel/sdrdaemondatareadqueue.cpp (100%) rename {sdrdaemon => sdrbase}/channel/sdrdaemondatareadqueue.h (100%) delete mode 100644 sdrdaemon/CMakeLists.txt delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsink.cpp delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsink.h delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsinksettings.h delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsinkthread.h delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsource.cpp delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsource.h delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcesettings.cpp delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcesettings.h delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp delete mode 100644 sdrdaemon/channel/sdrdaemonchannelsourcethread.h delete mode 100644 sdrdaemon/sdrdaemonmain.cpp delete mode 100644 sdrdaemon/sdrdaemonmain.h delete mode 100644 sdrdaemon/sdrdaemonparser.cpp delete mode 100644 sdrdaemon/sdrdaemonparser.h delete mode 100644 sdrdaemon/sdrdaemonpreferences.cpp delete mode 100644 sdrdaemon/sdrdaemonpreferences.h delete mode 100644 sdrdaemon/sdrdaemonsettings.cpp delete mode 100644 sdrdaemon/sdrdaemonsettings.h delete mode 100644 sdrdaemon/webapi/webapiadapterdaemon.cpp delete mode 100644 sdrdaemon/webapi/webapiadapterdaemon.h delete mode 100644 sdrdaemon/webapi/webapirequestmapper.cpp delete mode 100644 sdrdaemon/webapi/webapirequestmapper.h delete mode 100644 sdrdaemon/webapi/webapiserver.cpp delete mode 100644 sdrdaemon/webapi/webapiserver.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed0465ff0..9785a36fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ set(CMAKE_SKIP_BUILD_RPATH FALSE) # when building, don't use the install RPATH already # (but later on when installing) -set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") @@ -222,7 +222,6 @@ endif() add_subdirectory(sdrbase) add_subdirectory(sdrgui) add_subdirectory(sdrsrv) -add_subdirectory(sdrdaemon) add_subdirectory(sdrbench) add_subdirectory(httpserver) add_subdirectory(logging) @@ -327,31 +326,6 @@ target_link_libraries(sdrangelbench target_compile_features(sdrangelbench PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 target_link_libraries(sdrangelbench Qt5::Multimedia) -############################################################################## -# SDRdaemon application - -set(sdrdaemonsrv_SOURCES - appdaemon/main.cpp -) - -add_executable(sdrdaemonsrv - ${sdrdaemonsrv_SOURCES} -) - -target_include_directories(sdrdaemonsrv - PUBLIC ${CMAKE_SOURCE_DIR}/sdrdaemon -) - -target_link_libraries(sdrdaemonsrv - sdrdaemon - sdrbase - logging - ${QT_LIBRARIES} -) - -target_compile_features(sdrdaemonsrv PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 -target_link_libraries(sdrdaemonsrv Qt5::Multimedia) - ############################################################################## if (BUILD_DEBIAN) @@ -385,7 +359,6 @@ endif(LIBUSB_FOUND AND UNIX) install(TARGETS sdrangel DESTINATION bin) install(TARGETS sdrangelsrv DESTINATION bin) install(TARGETS sdrangelbench DESTINATION bin) -install(TARGETS sdrdaemonsrv DESTINATION bin) #install(TARGETS sdrbase DESTINATION lib) #install files and directories diff --git a/appdaemon/main.cpp b/appdaemon/main.cpp deleted file mode 100644 index e9feb9d2b..000000000 --- a/appdaemon/main.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon instance // -// // -// 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 - -#include "dsp/dsptypes.h" -#include "loggerwithfile.h" -#include "sdrdaemonparser.h" -#include "sdrdaemonmain.h" - -void handler(int sig) { - fprintf(stderr, "quit the application by signal(%d).\n", sig); - QCoreApplication::quit(); -} - -void catchUnixSignals(const std::vector& quitSignals) { - sigset_t blocking_mask; - sigemptyset(&blocking_mask); - - for (std::vector::const_iterator it = quitSignals.begin(); it != quitSignals.end(); ++it) { - sigaddset(&blocking_mask, *it); - } - - struct sigaction sa; - sa.sa_handler = handler; - sa.sa_mask = blocking_mask; - sa.sa_flags = 0; - - for (std::vector::const_iterator it = quitSignals.begin(); it != quitSignals.end(); ++it) { - sigaction(*it, &sa, 0); - } -} - -static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *logger) -{ - QCoreApplication a(argc, argv); - - QCoreApplication::setOrganizationName("f4exb"); - QCoreApplication::setApplicationName("SDRdaemonSrv"); - QCoreApplication::setApplicationVersion("4.1.0"); - - int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; - std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); - catchUnixSignals(vsig); - - SDRDaemonParser parser; - parser.parse(a); - -#if QT_VERSION >= 0x050400 - qInfo("%s %s Qt %s %db %s %s DSP Rx:%db Tx:%db PID %lld", - qPrintable(QCoreApplication::applicationName()), - qPrintable(QCoreApplication::applicationVersion()), - qPrintable(QString(QT_VERSION_STR)), - QT_POINTER_SIZE*8, - qPrintable(QSysInfo::currentCpuArchitecture()), - qPrintable(QSysInfo::prettyProductName()), - SDR_RX_SAMP_SZ, - SDR_TX_SAMP_SZ, - QCoreApplication::applicationPid()); -#else - qInfo("%s %s Qt %s %db DSP Rx:%db Tx:%db PID %lld", - qPrintable(QCoreApplication::applicationName()), - qPrintable((QCoreApplication::>applicationVersion()), - qPrintable(QString(QT_VERSION_STR)), - QT_POINTER_SIZE*8, - SDR_RX_SAMP_SZ, - SDR_TX_SAMP_SZ, - QCoreApplication::applicationPid()); -#endif - - SDRDaemonMain m(logger, parser, &a); - - if (m.doAbort()) - { - return -1; - } - else - { - // This will cause the application to exit when SDRdaemon is finished - QObject::connect(&m, SIGNAL(finished()), &a, SLOT(quit())); - - return a.exec(); - } -} - -int main(int argc, char* argv[]) -{ - qtwebapp::LoggerWithFile *logger = new qtwebapp::LoggerWithFile(qApp); - logger->installMsgHandler(); - int res = runQtApplication(argc, argv, logger); - qWarning("SDRdaemon quit."); - return res; -} - - - - - diff --git a/plugins/channelrx/daemonsink/CMakeLists.txt b/plugins/channelrx/daemonsink/CMakeLists.txt index 8a21d9f50..3375cb799 100644 --- a/plugins/channelrx/daemonsink/CMakeLists.txt +++ b/plugins/channelrx/daemonsink/CMakeLists.txt @@ -25,9 +25,8 @@ set(daemonsink_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/sdrdaemon ${CM256CC_INCLUDE_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) @@ -47,7 +46,6 @@ target_link_libraries(daemonsink ${QT_LIBRARIES} ${CM256CC_LIBRARIES} sdrbase - sdrdaemon sdrgui swagger ) diff --git a/plugins/channeltx/daemonsrc/CMakeLists.txt b/plugins/channeltx/daemonsrc/CMakeLists.txt index 158eda183..0353c457f 100644 --- a/plugins/channeltx/daemonsrc/CMakeLists.txt +++ b/plugins/channeltx/daemonsrc/CMakeLists.txt @@ -25,7 +25,6 @@ set(daemonsrc_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/sdrdaemon ${CM256CC_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) @@ -47,7 +46,6 @@ target_link_libraries(daemonsrc ${QT_LIBRARIES} ${CM256CC_LIBRARIES} sdrbase - sdrdaemon sdrgui swagger ) diff --git a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp index b203eb4f5..d73c501cf 100644 --- a/plugins/channeltx/daemonsrc/daemonsrcthread.cpp +++ b/plugins/channeltx/daemonsrc/daemonsrcthread.cpp @@ -21,7 +21,6 @@ #include "channel/sdrdaemondataqueue.h" #include "channel/sdrdaemondatablock.h" -#include "channel/sdrdaemonchannelsourcethread.h" #include "daemonsrcthread.h" diff --git a/pluginssrv/channelrx/daemonsink/CMakeLists.txt b/pluginssrv/channelrx/daemonsink/CMakeLists.txt index 07000cde0..2ff2295da 100644 --- a/pluginssrv/channelrx/daemonsink/CMakeLists.txt +++ b/pluginssrv/channelrx/daemonsink/CMakeLists.txt @@ -20,9 +20,8 @@ set(daemonsink_HEADERS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/sdrdaemon ${CM256CC_INCLUDE_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) @@ -39,7 +38,6 @@ target_link_libraries(daemonsinksrv ${QT_LIBRARIES} ${CM256CC_LIBRARIES} sdrbase - sdrdaemon swagger ) diff --git a/pluginssrv/channeltx/daemonsrc/CMakeLists.txt b/pluginssrv/channeltx/daemonsrc/CMakeLists.txt index ba4062d3c..2121a9fee 100644 --- a/pluginssrv/channeltx/daemonsrc/CMakeLists.txt +++ b/pluginssrv/channeltx/daemonsrc/CMakeLists.txt @@ -20,7 +20,6 @@ set(daemonsrc_HEADERS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/sdrdaemon ${CM256CC_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) @@ -39,7 +38,6 @@ target_link_libraries(daemonsrcsrv ${QT_LIBRARIES} ${CM256CC_LIBRARIES} sdrbase - sdrdaemon swagger ) diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index d29478331..9613b434a 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -9,9 +9,12 @@ set(sdrbase_SOURCES audio/audiooutput.cpp audio/audioinput.cpp audio/audionetsink.cpp - + channel/channelsinkapi.cpp channel/channelsourceapi.cpp + channel/sdrdaemondataqueue.cpp + channel/sdrdaemondatareadqueue.cpp + commands/command.cpp dsp/afsquelch.cpp @@ -22,7 +25,7 @@ set(sdrbase_SOURCES dsp/ctcssdetector.cpp dsp/cwkeyer.cpp dsp/cwkeyersettings.cpp - dsp/decimatorsif.cpp + dsp/decimatorsif.cpp dsp/decimatorsff.cpp dsp/decimatorsfi.cpp dsp/dspcommands.cpp @@ -78,15 +81,15 @@ set(sdrbase_SOURCES util/simpleserializer.cpp #util/spinlock.cpp util/uid.cpp - - plugin/plugininterface.cpp + + plugin/plugininterface.cpp plugin/pluginapi.cpp plugin/pluginmanager.cpp - + webapi/webapiadapterinterface.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp - + mainparser.cpp ) @@ -100,6 +103,10 @@ set(sdrbase_HEADERS channel/channelsinkapi.h channel/channelsourceapi.h + channel/sdrdaemondataqueue.h + channel/sdrdaemondatareadqueue.h + channel/sdrdaemondatablock.h + commands/command.h dsp/afsquelch.h @@ -197,11 +204,11 @@ set(sdrbase_HEADERS util/simpleserializer.h #util/spinlock.h util/uid.h - + webapi/webapiadapterinterface.h webapi/webapirequestmapper.h webapi/webapiserver - + mainparser.h ) diff --git a/sdrdaemon/channel/sdrdaemondatablock.h b/sdrbase/channel/sdrdaemondatablock.h similarity index 100% rename from sdrdaemon/channel/sdrdaemondatablock.h rename to sdrbase/channel/sdrdaemondatablock.h diff --git a/sdrdaemon/channel/sdrdaemondataqueue.cpp b/sdrbase/channel/sdrdaemondataqueue.cpp similarity index 100% rename from sdrdaemon/channel/sdrdaemondataqueue.cpp rename to sdrbase/channel/sdrdaemondataqueue.cpp diff --git a/sdrdaemon/channel/sdrdaemondataqueue.h b/sdrbase/channel/sdrdaemondataqueue.h similarity index 100% rename from sdrdaemon/channel/sdrdaemondataqueue.h rename to sdrbase/channel/sdrdaemondataqueue.h diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.cpp b/sdrbase/channel/sdrdaemondatareadqueue.cpp similarity index 100% rename from sdrdaemon/channel/sdrdaemondatareadqueue.cpp rename to sdrbase/channel/sdrdaemondatareadqueue.cpp diff --git a/sdrdaemon/channel/sdrdaemondatareadqueue.h b/sdrbase/channel/sdrdaemondatareadqueue.h similarity index 100% rename from sdrdaemon/channel/sdrdaemondatareadqueue.h rename to sdrbase/channel/sdrdaemondatareadqueue.h diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index 98412c5ea..f714c64f6 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -57,6 +57,8 @@ SOURCES += audio/audiodevicemanager.cpp\ audio/audionetsink.cpp\ channel/channelsinkapi.cpp\ channel/channelsourceapi.cpp\ + channel/sdrdaemondataqueue.cpp\ + channel/sdrdaemondatareadqueue.cpp\ commands/command.cpp\ device/devicesourceapi.cpp\ device/devicesinkapi.cpp\ @@ -117,7 +119,7 @@ SOURCES += audio/audiodevicemanager.cpp\ util/simpleserializer.cpp\ util/uid.cpp\ plugin/plugininterface.cpp\ - plugin/pluginapi.cpp\ + plugin/pluginapi.cpp\ plugin/pluginmanager.cpp\ webapi/webapiadapterinterface.cpp\ webapi/webapirequestmapper.cpp\ @@ -131,8 +133,11 @@ HEADERS += audio/audiodevicemanager.h\ audio/audioinput.h\ audio/audionetsink.h\ channel/channelsinkapi.h\ - channel/channelsourceapi.h\ - commands/command.h\ + channel/channelsourceapi.h\ + channel/sdrdaemondataqueue.h\ + channel/sdrdaemondatareadqueue.h\ + channel/sdrdaemondatablock.h\ + commands/command.h\ device/devicesourceapi.h\ device/devicesinkapi.h\ device/deviceenumerator.h\ @@ -195,9 +200,9 @@ HEADERS += audio/audiodevicemanager.h\ dsp/devicesamplesource.h\ dsp/devicesamplesink.h\ plugin/plugininstancegui.h\ - plugin/plugininterface.h\ - plugin/pluginapi.h\ - plugin/pluginmanager.h\ + plugin/plugininterface.h\ + plugin/pluginapi.h\ + plugin/pluginmanager.h\ settings/preferences.h\ settings/preset.h\ settings/mainsettings.h\ diff --git a/sdrdaemon/CMakeLists.txt b/sdrdaemon/CMakeLists.txt deleted file mode 100644 index edbd3aa9a..000000000 --- a/sdrdaemon/CMakeLists.txt +++ /dev/null @@ -1,76 +0,0 @@ -project (sdrdaemon) - -set(sdrdaemon_SOURCES - sdrdaemonmain.cpp - sdrdaemonpreferences.cpp - sdrdaemonsettings.cpp - sdrdaemonparser.cpp - channel/sdrdaemonchannelsink.cpp - channel/sdrdaemonchannelsource.cpp - channel/sdrdaemondataqueue.cpp - channel/sdrdaemondatareadqueue.cpp - channel/sdrdaemonchannelsinkthread.cpp - channel/sdrdaemonchannelsinksettings.cpp - channel/sdrdaemonchannelsourcesettings.cpp - channel/sdrdaemonchannelsourcethread.cpp - webapi/webapiadapterdaemon.cpp - webapi/webapirequestmapper.cpp - webapi/webapiserver.cpp -) - -set(sdrdaemon_HEADERS - sdrdaemonmain.h - sdrdaemonpreferences.h - sdrdaemonsettings.h - sdrdaemonparser.h - channel/sdrdaemonchannelsink.h - channel/sdrdaemonchannelsource.h - channel/sdrdaemondataqueue.h - channel/sdrdaemondatareadqueue.h - channel/sdrdaemondatablock.h - channel/sdrdaemonchannelsinkthread.h - channel/sdrdaemonchannelsinksettings.h - channel/sdrdaemonchannelsourcesettings.h - channel/sdrdaemonchannelsourcethread.h - webapi/webapiadapterdaemon.h - webapi/webapirequestmapper.h - webapi/webapiserver.h -) - -set(sdrdaemon_SOURCES - ${sdrdaemon_SOURCES} - ${sdrdaemon_HEADERS} -) - -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_SHARED) - -add_library(sdrdaemon SHARED - ${sdrdaemon_SOURCES} - ${sdrdaemon_HEADERS_MOC} -) - -include_directories( - . - ${CMAKE_SOURCE_DIR}/exports - ${CMAKE_SOURCE_DIR}/sdrbase - ${CMAKE_SOURCE_DIR}/logging - ${CMAKE_SOURCE_DIR}/httpserver - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${CM256CC_INCLUDE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} -) - -target_link_libraries(sdrdaemon - ${QT_LIBRARIES} - ${CM256CC_LIBRARIES} - sdrbase - logging -) - -target_compile_features(sdrdaemon PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 - -target_link_libraries(sdrdaemon Qt5::Core Qt5::Multimedia) - -install(TARGETS sdrdaemon DESTINATION lib) - diff --git a/sdrdaemon/channel/sdrdaemonchannelsink.cpp b/sdrdaemon/channel/sdrdaemonchannelsink.cpp deleted file mode 100644 index 2d929450b..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsink.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// 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 "channel/sdrdaemonchannelsinkthread.h" -#include "sdrdaemonchannelsink.h" - -MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSink::MsgConfigureSDRDaemonChannelSink, Message) - -const QString SDRDaemonChannelSink::m_channelIdURI = "sdrangel.channel.sdrdaemonsink"; -const QString SDRDaemonChannelSink::m_channelId = "SDRDaemonChannelSink"; - -SDRDaemonChannelSink::SDRDaemonChannelSink(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; -} - -SDRDaemonChannelSink::~SDRDaemonChannelSink() -{ - 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 SDRDaemonChannelSink::setTxDelay(int txDelay) -{ - qDebug() << "SDRDaemonChannelSink::setTxDelay: txDelay: " << txDelay; - m_txDelay = txDelay; -} - -void SDRDaemonChannelSink::setNbBlocksFEC(int nbBlocksFEC) -{ - qDebug() << "SDRDaemonChannelSink::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC; - m_nbBlocksFEC = nbBlocksFEC; -} - -void SDRDaemonChannelSink::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 - - // handle different sample sizes... - int samplesPerBlock = SDRDaemonNbBytesPerBlock / sizeof(Sample); - 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_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 SDRDaemonChannelSink::start() -{ - qDebug("SDRDaemonChannelSink::start"); - - memset((void *) &m_currentMetaFEC, 0, sizeof(SDRDaemonMetaDataFEC)); - - if (m_running) { - stop(); - } - - m_sinkThread = new SDRDaemonChannelSinkThread(&m_dataQueue, m_cm256p); - m_sinkThread->startStop(true); - m_running = true; -} - -void SDRDaemonChannelSink::stop() -{ - qDebug("SDRDaemonChannelSink::stop"); - - if (m_sinkThread != 0) - { - m_sinkThread->startStop(false); - m_sinkThread->deleteLater(); - m_sinkThread = 0; - } - - m_running = false; -} - -bool SDRDaemonChannelSink::handleMessage(const Message& cmd __attribute__((unused))) -{ - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - qDebug() << "SDRDaemonChannelSink::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() << "SDRDaemonChannelSink::handleMessage: DSPSignalNotification:" - << " inputSampleRate: " << notif.getSampleRate() - << " centerFrequency: " << notif.getCenterFrequency(); - - setCenterFrequency(notif.getCenterFrequency()); - - return true; - } - else if (MsgConfigureSDRDaemonChannelSink::match(cmd)) - { - MsgConfigureSDRDaemonChannelSink& cfg = (MsgConfigureSDRDaemonChannelSink&) cmd; - qDebug() << "SDRDaemonChannelSink::handleMessage: MsgConfigureSDRDaemonChannelSink"; - applySettings(cfg.getSettings(), cfg.getForce()); - - return true; - } - else - { - return false; - } -} - -QByteArray SDRDaemonChannelSink::serialize() const -{ - return m_settings.serialize(); -} - -bool SDRDaemonChannelSink::deserialize(const QByteArray& data __attribute__((unused))) -{ - if (m_settings.deserialize(data)) - { - MsgConfigureSDRDaemonChannelSink *msg = MsgConfigureSDRDaemonChannelSink::create(m_settings, true); - m_inputMessageQueue.push(msg); - return true; - } - else - { - m_settings.resetToDefaults(); - MsgConfigureSDRDaemonChannelSink *msg = MsgConfigureSDRDaemonChannelSink::create(m_settings, true); - m_inputMessageQueue.push(msg); - return false; - } -} - -void SDRDaemonChannelSink::applySettings(const SDRDaemonChannelSinkSettings& settings, bool force) -{ - qDebug() << "SDRDaemonChannelSink::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 SDRDaemonChannelSink::webapiSettingsGet( - SWGSDRangel::SWGChannelSettings& response, - QString& errorMessage __attribute__((unused))) -{ - response.setDaemonSinkSettings(new SWGSDRangel::SWGDaemonSinkSettings()); - response.getDaemonSinkSettings()->init(); - webapiFormatChannelSettings(response, m_settings); - return 200; -} - -int SDRDaemonChannelSink::webapiSettingsPutPatch( - bool force, - const QStringList& channelSettingsKeys, - SWGSDRangel::SWGChannelSettings& response, - QString& errorMessage __attribute__((unused))) -{ - SDRDaemonChannelSinkSettings 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; - } - } - - MsgConfigureSDRDaemonChannelSink *msg = MsgConfigureSDRDaemonChannelSink::create(settings, force); - m_inputMessageQueue.push(msg); - - qDebug("SDRDaemonChannelSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); - if (m_guiMessageQueue) // forward to GUI if any - { - MsgConfigureSDRDaemonChannelSink *msgToGUI = MsgConfigureSDRDaemonChannelSink::create(settings, force); - m_guiMessageQueue->push(msgToGUI); - } - - webapiFormatChannelSettings(response, settings); - - return 200; -} - -void SDRDaemonChannelSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSinkSettings& 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/sdrdaemon/channel/sdrdaemonchannelsink.h b/sdrdaemon/channel/sdrdaemonchannelsink.h deleted file mode 100644 index 2bf2cd754..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsink.h +++ /dev/null @@ -1,139 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// 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 SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ -#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ - -#include - -#include "cm256.h" - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "channel/sdrdaemondataqueue.h" -#include "channel/sdrdaemondatablock.h" -#include "channel/sdrdaemonchannelsinksettings.h" - -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; -class SDRDaemonChannelSinkThread; - -class SDRDaemonChannelSink : public BasebandSampleSink, public ChannelSinkAPI { - Q_OBJECT -public: - class MsgConfigureSDRDaemonChannelSink : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const SDRDaemonChannelSinkSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureSDRDaemonChannelSink* create(const SDRDaemonChannelSinkSettings& settings, bool force) - { - return new MsgConfigureSDRDaemonChannelSink(settings, force); - } - - private: - SDRDaemonChannelSinkSettings m_settings; - bool m_force; - - MsgConfigureSDRDaemonChannelSink(const SDRDaemonChannelSinkSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - SDRDaemonChannelSink(DeviceSourceAPI *deviceAPI); - virtual ~SDRDaemonChannelSink(); - 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; - - SDRDaemonChannelSinkSettings m_settings; - SDRDaemonDataQueue m_dataQueue; - SDRDaemonChannelSinkThread *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 SDRDaemonChannelSinkSettings& settings, bool force = false); - void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSinkSettings& settings); -}; - -#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINK_H_ */ diff --git a/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp b/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp deleted file mode 100644 index f05b3a103..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsinksettings.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// 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 "channel/sdrdaemonchannelsinksettings.h" - -SDRDaemonChannelSinkSettings::SDRDaemonChannelSinkSettings() -{ - resetToDefaults(); -} - -void SDRDaemonChannelSinkSettings::resetToDefaults() -{ - m_nbFECBlocks = 0; - m_txDelay = 100; - m_dataAddress = "127.0.0.1"; - m_dataPort = 9090; -} - -QByteArray SDRDaemonChannelSinkSettings::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 SDRDaemonChannelSinkSettings::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/sdrdaemon/channel/sdrdaemonchannelsinksettings.h b/sdrdaemon/channel/sdrdaemonchannelsinksettings.h deleted file mode 100644 index ff9be3f25..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsinksettings.h +++ /dev/null @@ -1,43 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// 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 SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINKSETTINGS_H_ -#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINKSETTINGS_H_ - -#include - -class Serializable; - -struct SDRDaemonChannelSinkSettings -{ - uint16_t m_nbFECBlocks; - uint32_t m_txDelay; - QString m_dataAddress; - uint16_t m_dataPort; - - SDRDaemonChannelSinkSettings(); - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); -}; - -#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSINKSETTINGS_H_ */ diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp b/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp deleted file mode 100644 index 368a37e3c..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// 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 "channel/sdrdaemonchannelsinkthread.h" - -#include "cm256.h" - -MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSinkThread::MsgStartStop, Message) - -SDRDaemonChannelSinkThread::SDRDaemonChannelSinkThread(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); -} - -SDRDaemonChannelSinkThread::~SDRDaemonChannelSinkThread() -{ - qDebug("SDRDaemonChannelSinkThread::~SDRDaemonChannelSinkThread"); -} - -void SDRDaemonChannelSinkThread::startStop(bool start) -{ - MsgStartStop *msg = MsgStartStop::create(start); - m_inputMessageQueue.push(msg); -} - -void SDRDaemonChannelSinkThread::startWork() -{ - qDebug("SDRDaemonChannelSinkThread::startWork"); - m_startWaitMutex.lock(); - m_socket = new QUdpSocket(this); - start(); - while(!m_running) - m_startWaiter.wait(&m_startWaitMutex, 100); - m_startWaitMutex.unlock(); -} - -void SDRDaemonChannelSinkThread::stopWork() -{ - qDebug("SDRDaemonChannelSinkThread::stopWork"); - delete m_socket; - m_socket = 0; - m_running = false; - wait(); -} - -void SDRDaemonChannelSinkThread::run() -{ - qDebug("SDRDaemonChannelSinkThread::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("SDRDaemonChannelSinkThread::run: end"); -} - -bool SDRDaemonChannelSinkThread::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 SDRDaemonChannelSinkThread::handleData() -{ - SDRDaemonDataBlock* dataBlock; - - while (m_running && ((dataBlock = m_dataQueue->pop()) != 0)) - { - if (handleDataBlock(*dataBlock)) - { - delete dataBlock; - } - } -} - -void SDRDaemonChannelSinkThread::handleInputMessages() -{ - Message* message; - - while ((message = m_inputMessageQueue.pop()) != 0) - { - if (MsgStartStop::match(*message)) - { - MsgStartStop* notif = (MsgStartStop*) message; - qDebug("SDRDaemonChannelSinkThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); - - if (notif->getStartStop()) { - startWork(); - } else { - stopWork(); - } - - delete message; - } - } -} diff --git a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h b/sdrdaemon/channel/sdrdaemonchannelsinkthread.h deleted file mode 100644 index 1f4e655e5..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsinkthread.h +++ /dev/null @@ -1,86 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// 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 SDRDaemonChannelSinkThread : 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) - { } - }; - - SDRDaemonChannelSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent = 0); - ~SDRDaemonChannelSinkThread(); - - 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/sdrdaemon/channel/sdrdaemonchannelsource.cpp b/sdrdaemon/channel/sdrdaemonchannelsource.cpp deleted file mode 100644 index 9d0141272..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsource.cpp +++ /dev/null @@ -1,414 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) // -// // -// 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 - -#include "SWGChannelSettings.h" -#include "SWGChannelReport.h" -#include "SWGSDRDaemonChannelSourceReport.h" - -#include "util/simpleserializer.h" -#include "dsp/threadedbasebandsamplesource.h" -#include "dsp/upchannelizer.h" -#include "dsp/devicesamplesink.h" -#include "device/devicesinkapi.h" -#include "sdrdaemonchannelsource.h" -#include "channel/sdrdaemonchannelsourcethread.h" -#include "channel/sdrdaemondatablock.h" - -MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource, Message) - -const QString SDRDaemonChannelSource::m_channelIdURI = "sdrangel.channel.sdrdaemonsource"; -const QString SDRDaemonChannelSource::m_channelId = "SDRDaemonChannelSource"; - -SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : - ChannelSourceAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_sourceThread(0), - m_running(false), - m_dataReadQueue(SDR_TX_SAMP_SZ <= 16 ? 4 : 8), - m_nbCorrectableErrors(0), - m_nbUncorrectableErrors(0) -{ - setObjectName(m_channelId); - - m_channelizer = new UpChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); - m_deviceAPI->addThreadedSource(m_threadedChannelizer); - m_deviceAPI->addChannelAPI(this); - - connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); - m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; - m_currentMeta.init(); -} - -SDRDaemonChannelSource::~SDRDaemonChannelSource() -{ - m_deviceAPI->removeChannelAPI(this); - m_deviceAPI->removeThreadedSource(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; -} - -void SDRDaemonChannelSource::pull(Sample& sample) -{ - m_dataReadQueue.readSample(sample); -} - -void SDRDaemonChannelSource::start() -{ - qDebug("SDRDaemonChannelSink::start"); - - if (m_running) { - stop(); - } - - m_sourceThread = new SDRDaemonChannelSourceThread(&m_dataQueue); - m_sourceThread->startStop(true); - m_sourceThread->dataBind(m_settings.m_dataAddress, m_settings.m_dataPort); - m_running = true; -} - -void SDRDaemonChannelSource::stop() -{ - qDebug("SDRDaemonChannelSink::stop"); - - if (m_sourceThread != 0) - { - m_sourceThread->startStop(false); - m_sourceThread->deleteLater(); - m_sourceThread = 0; - } - - m_running = false; -} - -void SDRDaemonChannelSource::setDataLink(const QString& dataAddress, uint16_t dataPort) -{ - SDRDaemonChannelSourceSettings settings = m_settings; - settings.m_dataAddress = dataAddress; - settings.m_dataPort = dataPort; - - MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, false); - m_inputMessageQueue.push(msg); -} - -bool SDRDaemonChannelSource::handleMessage(const Message& cmd __attribute__((unused))) -{ - if (UpChannelizer::MsgChannelizerNotification::match(cmd)) - { - UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "SDRDaemonChannelSource::handleMessage: UpChannelizer::MsgChannelizerNotification:" - << " basebandSampleRate: " << notif.getBasebandSampleRate() - << " outputSampleRate: " << notif.getSampleRate() - << " inputFrequencyOffset: " << notif.getFrequencyOffset(); - - //applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset()); - - return true; - } - else if (MsgConfigureSDRDaemonChannelSource::match(cmd)) - { - MsgConfigureSDRDaemonChannelSource& cfg = (MsgConfigureSDRDaemonChannelSource&) cmd; - qDebug() << "SDRDaemonChannelSource::handleMessage: MsgConfigureSDRDaemonChannelSource"; - applySettings(cfg.getSettings(), cfg.getForce()); - - return true; - } - else - { - return false; - } -} - -QByteArray SDRDaemonChannelSource::serialize() const -{ - return m_settings.serialize(); -} - -bool SDRDaemonChannelSource::deserialize(const QByteArray& data __attribute__((unused))) -{ - if (m_settings.deserialize(data)) - { - MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); - m_inputMessageQueue.push(msg); - return true; - } - else - { - m_settings.resetToDefaults(); - MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); - m_inputMessageQueue.push(msg); - return false; - } -} - -void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& settings, bool force) -{ - qDebug() << "SDRDaemonChannelSource::applySettings:" - << " m_dataAddress: " << settings.m_dataAddress - << " m_dataPort: " << settings.m_dataPort - << " force: " << force; - - bool change = false; - - if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { - change = true; - } - - if ((m_settings.m_dataPort != settings.m_dataPort) || force) { - change = true; - } - - if (change && m_sourceThread) { - m_sourceThread->dataBind(settings.m_dataAddress, settings.m_dataPort); - } - - m_settings = settings; -} - -void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) -{ - if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) - { - qWarning("SDRDaemonChannelSource::handleDataBlock: incomplete data block: not processing"); - } - else - { - int blockCount = 0; - - for (int blockIndex = 0; blockIndex < 256; blockIndex++) - { - if ((blockIndex == 0) && (dataBlock->m_rxControlBlock.m_metaRetrieved)) - { - m_cm256DescriptorBlocks[blockCount].Index = 0; - m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[0].m_protectedBlock); - blockCount++; - } - else if (dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex != 0) - { - m_cm256DescriptorBlocks[blockCount].Index = dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex; - m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock); - blockCount++; - } - } - - //qDebug("SDRDaemonChannelSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); - - // Need to use the CM256 recovery - if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) - { - qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); - CM256::cm256_encoder_params paramsCM256; - paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes - paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes - - if (m_currentMeta.m_tv_sec == 0) { - paramsCM256.RecoveryCount = dataBlock->m_rxControlBlock.m_recoveryCount; - } else { - paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; - } - - // update counters - if (dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount) { - m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount - dataBlock->m_rxControlBlock.m_originalCount; - } else { - m_nbCorrectableErrors += dataBlock->m_rxControlBlock.m_recoveryCount; - } - - if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode - { - qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" - << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount - << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; - } - else - { - for (int ir = 0; ir < dataBlock->m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks - { - int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_recoveryCount + ir; - int blockIndex = m_cm256DescriptorBlocks[recoveryIndex].Index; - SDRDaemonProtectedBlock *recoveredBlock = - (SDRDaemonProtectedBlock *) m_cm256DescriptorBlocks[recoveryIndex].Block; - memcpy((void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); - if ((blockIndex == 0) && !dataBlock->m_rxControlBlock.m_metaRetrieved) { - dataBlock->m_rxControlBlock.m_metaRetrieved = true; - } - } - } - } - - // Validate block zero and retrieve its data - if (dataBlock->m_rxControlBlock.m_metaRetrieved) - { - SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock->m_superBlocks[0].m_protectedBlock); - boost::crc_32_type crc32; - crc32.process_bytes(metaData, 20); - - if (crc32.checksum() == metaData->m_crc32) - { - if (!(m_currentMeta == *metaData)) - { - printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); - - if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { - m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency*1000); // frequency is in kHz - } - - if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) - { - m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); - m_dataReadQueue.setSize(calculateDataReadQueueSize(metaData->m_sampleRate)); - } - } - - m_currentMeta = *metaData; - } - else - { - qWarning() << "SDRDaemonChannelSource::handleDataBlock: recovered meta: invalid CRC32"; - } - } - - m_dataReadQueue.push(dataBlock); // Push into R/W buffer - } -} - -void SDRDaemonChannelSource::handleData() -{ - SDRDaemonDataBlock* dataBlock; - - while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) { - handleDataBlock(dataBlock); - } -} - -void SDRDaemonChannelSource::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) -{ - qDebug().noquote() << header << ": " - << "|" << 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 - << "|"; -} - -uint32_t SDRDaemonChannelSource::calculateDataReadQueueSize(int sampleRate) -{ - // scale for 20 blocks at 48 kS/s. Take next even number. - uint32_t maxSize = sampleRate / 2400; - maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; - qDebug("SDRDaemonChannelSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); - return maxSize; -} - -int SDRDaemonChannelSource::webapiSettingsGet( - SWGSDRangel::SWGChannelSettings& response, - QString& errorMessage __attribute__((unused))) -{ - response.setDaemonSourceSettings(new SWGSDRangel::SWGDaemonSourceSettings()); - response.getDaemonSourceSettings()->init(); - webapiFormatChannelSettings(response, m_settings); - return 200; -} - -int SDRDaemonChannelSource::webapiSettingsPutPatch( - bool force, - const QStringList& channelSettingsKeys, - SWGSDRangel::SWGChannelSettings& response, - QString& errorMessage __attribute__((unused))) -{ - SDRDaemonChannelSourceSettings settings = m_settings; - - if (channelSettingsKeys.contains("dataAddress")) { - settings.m_dataAddress = *response.getDaemonSourceSettings()->getDataAddress(); - } - - if (channelSettingsKeys.contains("dataPort")) - { - int dataPort = response.getDaemonSourceSettings()->getDataPort(); - - if ((dataPort < 1024) || (dataPort > 65535)) { - settings.m_dataPort = 9090; - } else { - settings.m_dataPort = dataPort; - } - } - - MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, force); - m_inputMessageQueue.push(msg); - - qDebug("SDRDaemonChannelSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); - if (m_guiMessageQueue) // forward to GUI if any - { - MsgConfigureSDRDaemonChannelSource *msgToGUI = MsgConfigureSDRDaemonChannelSource::create(settings, force); - m_guiMessageQueue->push(msgToGUI); - } - - webapiFormatChannelSettings(response, settings); - - return 200; -} - -int SDRDaemonChannelSource::webapiReportGet( - SWGSDRangel::SWGChannelReport& response, - QString& errorMessage __attribute__((unused))) -{ - response.setDaemonSourceReport(new SWGSDRangel::SWGDaemonSourceReport()); - response.getDaemonSourceReport()->init(); - webapiFormatChannelReport(response); - return 200; -} - -void SDRDaemonChannelSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings) -{ - if (response.getDaemonSourceSettings()->getDataAddress()) { - *response.getDaemonSourceSettings()->getDataAddress() = settings.m_dataAddress; - } else { - response.getDaemonSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); - } - - response.getDaemonSourceSettings()->setDataPort(settings.m_dataPort); -} - -void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) -{ - struct timeval tv; - gettimeofday(&tv, 0); - - response.getDaemonSourceReport()->setTvSec(tv.tv_sec); - response.getDaemonSourceReport()->setTvUSec(tv.tv_usec); - response.getDaemonSourceReport()->setQueueSize(m_dataReadQueue.size()); - response.getDaemonSourceReport()->setQueueLength(m_dataReadQueue.length()); - response.getDaemonSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); - response.getDaemonSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); - response.getDaemonSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); -} diff --git a/sdrdaemon/channel/sdrdaemonchannelsource.h b/sdrdaemon/channel/sdrdaemonchannelsource.h deleted file mode 100644 index b9359204b..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsource.h +++ /dev/null @@ -1,134 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) // -// // -// 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 SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCE_H_ -#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCE_H_ - -#include "cm256.h" - -#include "dsp/basebandsamplesource.h" -#include "channel/channelsourceapi.h" -#include "channel/sdrdaemonchannelsourcesettings.h" -#include "channel/sdrdaemondataqueue.h" -#include "channel/sdrdaemondatablock.h" -#include "channel/sdrdaemondatareadqueue.h" - -class ThreadedBasebandSampleSource; -class UpChannelizer; -class DeviceSinkAPI; -class SDRDaemonChannelSourceThread; -class SDRDaemonDataBlock; - -class SDRDaemonChannelSource : public BasebandSampleSource, public ChannelSourceAPI { - Q_OBJECT -public: - class MsgConfigureSDRDaemonChannelSource : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const SDRDaemonChannelSourceSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureSDRDaemonChannelSource* create(const SDRDaemonChannelSourceSettings& settings, bool force) - { - return new MsgConfigureSDRDaemonChannelSource(settings, force); - } - - private: - SDRDaemonChannelSourceSettings m_settings; - bool m_force; - - MsgConfigureSDRDaemonChannelSource(const SDRDaemonChannelSourceSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI); - ~SDRDaemonChannelSource(); - virtual void destroy() { delete this; } - - virtual void pull(Sample& sample); - 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 Source"; } - 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); - - virtual int webapiReportGet( - SWGSDRangel::SWGChannelReport& response, - QString& errorMessage); - - void setDataLink(const QString& dataAddress, uint16_t dataPort); - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - DeviceSinkAPI *m_deviceAPI; - ThreadedBasebandSampleSource* m_threadedChannelizer; - UpChannelizer* m_channelizer; - SDRDaemonDataQueue m_dataQueue; - SDRDaemonChannelSourceThread *m_sourceThread; - CM256 m_cm256; - CM256 *m_cm256p; - bool m_running; - - SDRDaemonChannelSourceSettings m_settings; - - CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) - SDRDaemonMetaDataFEC m_currentMeta; - - SDRDaemonDataReadQueue m_dataReadQueue; - - uint32_t m_nbCorrectableErrors; //!< count of correctable errors in number of blocks - uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks - - void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); - void handleDataBlock(SDRDaemonDataBlock *dataBlock); - void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); - uint32_t calculateDataReadQueueSize(int sampleRate); - void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings); - void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); - -private slots: - void handleData(); -}; - - -#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCE_H_ */ diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcesettings.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcesettings.cpp deleted file mode 100644 index 2b14fe23b..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsourcesettings.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// 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 "channel/sdrdaemonchannelsourcesettings.h" - -SDRDaemonChannelSourceSettings::SDRDaemonChannelSourceSettings() -{ - resetToDefaults(); -} - -void SDRDaemonChannelSourceSettings::resetToDefaults() -{ - m_dataAddress = "127.0.0.1"; - m_dataPort = 9090; -} - -QByteArray SDRDaemonChannelSourceSettings::serialize() const -{ - SimpleSerializer s(1); - s.writeString(1, m_dataAddress); - s.writeU32(2, m_dataPort); - - return s.final(); -} - -bool SDRDaemonChannelSourceSettings::deserialize(const QByteArray& data) -{ - SimpleDeserializer d(data); - - if(!d.isValid()) - { - resetToDefaults(); - return false; - } - - if(d.getVersion() == 1) - { - uint32_t tmp; - QString strtmp; - - d.readString(1, &m_dataAddress, "127.0.0.1"); - d.readU32(2, &tmp, 0); - - if ((tmp > 1023) && (tmp < 65535)) { - m_dataPort = tmp; - } else { - m_dataPort = 9090; - } - - return true; - } - else - { - resetToDefaults(); - return false; - } -} - - - - - - - diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h b/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h deleted file mode 100644 index 7cd4268b6..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsourcesettings.h +++ /dev/null @@ -1,43 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) 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 SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCESETTINGS_H_ -#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCESETTINGS_H_ - - -#include - -class Serializable; - -struct SDRDaemonChannelSourceSettings -{ - QString m_dataAddress; //!< Listening (local) data address - uint16_t m_dataPort; //!< Listening data port - - SDRDaemonChannelSourceSettings(); - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); -}; - - -#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCESETTINGS_H_ */ diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp b/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp deleted file mode 100644 index 3b2d55998..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsourcethread.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) UDP receiver 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 "channel/sdrdaemondataqueue.h" -#include "channel/sdrdaemondatablock.h" -#include "channel/sdrdaemonchannelsourcethread.h" - -#include "cm256.h" - -MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgDataBind, Message) - -SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : - QThread(parent), - m_running(false), - m_dataQueue(dataQueue), - m_address(QHostAddress::LocalHost), - m_socket(0) -{ - std::fill(m_dataBlocks, m_dataBlocks+4, (SDRDaemonDataBlock *) 0); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); -} - -SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread() -{ - qDebug("SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread"); -} - -void SDRDaemonChannelSourceThread::startStop(bool start) -{ - MsgStartStop *msg = MsgStartStop::create(start); - m_inputMessageQueue.push(msg); -} - -void SDRDaemonChannelSourceThread::dataBind(const QString& address, uint16_t port) -{ - MsgDataBind *msg = MsgDataBind::create(address, port); - m_inputMessageQueue.push(msg); -} - -void SDRDaemonChannelSourceThread::startWork() -{ - qDebug("SDRDaemonChannelSourceThread::startWork"); - m_startWaitMutex.lock(); - m_socket = new QUdpSocket(this); - start(); - while(!m_running) - m_startWaiter.wait(&m_startWaitMutex, 100); - m_startWaitMutex.unlock(); -} - -void SDRDaemonChannelSourceThread::stopWork() -{ - qDebug("SDRDaemonChannelSourceThread::stopWork"); - delete m_socket; - m_socket = 0; - m_running = false; - wait(); -} - -void SDRDaemonChannelSourceThread::run() -{ - qDebug("SDRDaemonChannelSourceThread::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("SDRDaemonChannelSourceThread::run: end"); -} - - -void SDRDaemonChannelSourceThread::handleInputMessages() -{ - Message* message; - - while ((message = m_inputMessageQueue.pop()) != 0) - { - if (MsgStartStop::match(*message)) - { - MsgStartStop* notif = (MsgStartStop*) message; - qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); - - if (notif->getStartStop()) { - startWork(); - } else { - stopWork(); - } - - delete message; - } - else if (MsgDataBind::match(*message)) - { - MsgDataBind* notif = (MsgDataBind*) message; - qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); - - if (m_socket) - { - disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); - m_socket->bind(notif->getAddress(), notif->getPort()); - connect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); - } - } - } -} - -void SDRDaemonChannelSourceThread::readPendingDatagrams() -{ - SDRDaemonSuperBlock superBlock; - qint64 size; - - while (m_socket->hasPendingDatagrams()) - { - QHostAddress sender; - quint16 senderPort = 0; - //qint64 pendingDataSize = m_socket->pendingDatagramSize(); - size = m_socket->readDatagram((char *) &superBlock, (long long int) sizeof(SDRDaemonSuperBlock), &sender, &senderPort); - - if (size == sizeof(SDRDaemonSuperBlock)) - { - unsigned int dataBlockIndex = superBlock.m_header.m_frameIndex % m_nbDataBlocks; - - // create the first block for this index - if (m_dataBlocks[dataBlockIndex] == 0) { - m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); - } - - if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex < 0) - { - // initialize virgin block with the frame index - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; - } - else - { - // if the frame index is not the same for the same slot it means we are starting a new frame - uint32_t frameIndex = m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex; - - if (superBlock.m_header.m_frameIndex != frameIndex) - { - //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", frameIndex); - m_dataQueue->push(m_dataBlocks[dataBlockIndex]); - m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; - } - } - - m_dataBlocks[dataBlockIndex]->m_superBlocks[superBlock.m_header.m_blockIndex] = superBlock; - - if (superBlock.m_header.m_blockIndex == 0) { - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_metaRetrieved = true; - } - - if (superBlock.m_header.m_blockIndex < SDRDaemonNbOrginalBlocks) { - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_originalCount++; - } else { - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_recoveryCount++; - } - - m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount++; - } - else - { - qWarning("SDRDaemonChannelSourceThread::readPendingDatagrams: wrong super block size not processing"); - } - } -} diff --git a/sdrdaemon/channel/sdrdaemonchannelsourcethread.h b/sdrdaemon/channel/sdrdaemonchannelsourcethread.h deleted file mode 100644 index 37892d69b..000000000 --- a/sdrdaemon/channel/sdrdaemonchannelsourcethread.h +++ /dev/null @@ -1,115 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon source channel (Tx) UDP receiver 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 . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCETHREAD_H_ -#define SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCETHREAD_H_ - -#include -#include -#include -#include - -#include "util/message.h" -#include "util/messagequeue.h" - -class SDRDaemonDataQueue; -class SDRDaemonDataBlock; -class QUdpSocket; - -class SDRDaemonChannelSourceThread : 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) - { } - }; - - class MsgDataBind : public Message { - MESSAGE_CLASS_DECLARATION - - public: - QHostAddress getAddress() const { return m_address; } - uint16_t getPort() const { return m_port; } - - static MsgDataBind* create(const QString& address, uint16_t port) { - return new MsgDataBind(address, port); - } - - protected: - QHostAddress m_address; - uint16_t m_port; - - MsgDataBind(const QString& address, uint16_t port) : - Message(), - m_port(port) - { - m_address.setAddress(address); - } - }; - - SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); - ~SDRDaemonChannelSourceThread(); - - void startStop(bool start); - void dataBind(const QString& address, uint16_t port); - -private: - QMutex m_startWaitMutex; - QWaitCondition m_startWaiter; - bool m_running; - - MessageQueue m_inputMessageQueue; - SDRDaemonDataQueue *m_dataQueue; - - QHostAddress m_address; - QUdpSocket *m_socket; - - static const uint32_t m_nbDataBlocks = 4; //!< number of data blocks in the ring buffer - SDRDaemonDataBlock *m_dataBlocks[m_nbDataBlocks]; //!< ring buffer of data blocks indexed by frame affinity - - void startWork(); - void stopWork(); - - void run(); - -private slots: - void handleInputMessages(); - void readPendingDatagrams(); -}; - - - -#endif /* SDRDAEMON_CHANNEL_SDRDAEMONCHANNELSOURCETHREAD_H_ */ diff --git a/sdrdaemon/sdrdaemonmain.cpp b/sdrdaemon/sdrdaemonmain.cpp deleted file mode 100644 index 17492a076..000000000 --- a/sdrdaemon/sdrdaemonmain.cpp +++ /dev/null @@ -1,387 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon instance // -// // -// 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 "dsp/dspengine.h" -#include "dsp/dspdevicesourceengine.h" -#include "dsp/dspdevicesinkengine.h" -#include "device/devicesourceapi.h" -#include "device/devicesinkapi.h" -#include "device/deviceenumerator.h" -#include "plugin/pluginmanager.h" -#include "util/message.h" -#include "loggerwithfile.h" - -#include "webapi/webapiadapterdaemon.h" -#include "webapi/webapirequestmapper.h" -#include "webapi/webapiserver.h" -#include "channel/sdrdaemonchannelsink.h" -#include "channel/sdrdaemonchannelsource.h" -#include "sdrdaemonparser.h" -#include "sdrdaemonmain.h" - -SDRDaemonMain *SDRDaemonMain::m_instance = 0; - -SDRDaemonMain::SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonParser& parser, QObject *parent) : - QObject(parent), - m_logger(logger), - m_settings(), - m_dspEngine(DSPEngine::instance()), - m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), - m_abort(false) -{ - qDebug() << "SDRDaemonMain::SDRDaemonMain: start"; - - m_instance = this; - - m_pluginManager = new PluginManager(this); - m_pluginManager->loadPluginsPart(QString("pluginssrv/samplesink")); - m_pluginManager->loadPluginsPart(QString("pluginssrv/samplesource")); - m_pluginManager->loadPluginsFinal(); - - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages()), Qt::QueuedConnection); - m_masterTimer.start(50); - - loadSettings(); - - QString applicationDirPath = QCoreApplication::instance()->applicationDirPath(); - - if (QResource::registerResource(applicationDirPath + "/sdrbase.rcc")) { - qDebug("SDRDaemonMain::SDRDaemonMain: registered resource file %s/%s", qPrintable(applicationDirPath), "sdrbase.rcc"); - } else { - qWarning("SDRDaemonMain::SDRDaemonMain: could not register resource file %s/%s", qPrintable(applicationDirPath), "sdrbase.rcc"); - } - - m_apiAdapter = new WebAPIAdapterDaemon(*this); - m_requestMapper = new SDRDaemon::WebAPIRequestMapper(this); - m_requestMapper->setAdapter(m_apiAdapter); - m_apiServer = new SDRDaemon::WebAPIServer(parser.getServerAddress(), parser.getServerPort(), m_requestMapper); - m_apiServer->start(); - - m_tx = parser.getTx(); - m_deviceType = parser.getDeviceType(); - m_deviceSerial = parser.hasSerial() ? parser.getSerial() : ""; - m_deviceSequence = parser.hasSequence() ? parser.getSequence() : -1; - m_deviceSourceEngine = 0; - m_deviceSinkEngine = 0; - m_deviceSourceAPI = 0; - m_deviceSinkAPI = 0; - m_channelSink = 0; - m_channelSource = 0; - - if (m_tx) - { - if (addSinkDevice()) - { - QString msg(tr("SDRDaemonMain::SDRDaemonMain: set sink %1").arg(m_deviceType)); - if (m_deviceSerial.length() > 0) { - msg += tr(" ser: %1").arg(m_deviceSerial); - } else if (m_deviceSequence >= 0) { - msg += tr(" seq: %1").arg(m_deviceSequence); - } else { - msg += " first device"; - } - QDebug info = qInfo(); - info.noquote(); - info << msg; - m_channelSource = new SDRDaemonChannelSource(m_deviceSinkAPI); - m_channelSource->setDataLink(parser.getDataAddress(), parser.getDataPort()); - } - else - { - qCritical("SDRDaemonMain::SDRDaemonMain: sink device not found aborting"); - m_abort = true; - } - } - else - { - if (addSourceDevice()) - { - QString msg(tr("SDRDaemonMain::SDRDaemonMain: set source %1").arg(m_deviceType)); - if (m_deviceSerial.length() > 0) { - msg += tr(" ser: %1").arg(m_deviceSerial); - } else if (m_deviceSequence >= 0) { - msg += tr(" seq: %1").arg(m_deviceSequence); - } else { - msg += " first device"; - } - QDebug info = qInfo(); - info.noquote(); - info << msg; - m_channelSink = new SDRDaemonChannelSink(m_deviceSourceAPI); - m_channelSink->setNbBlocksFEC(parser.getNbBlocksFEC()); - m_channelSink->setTxDelay(parser.getTxDelay()); - m_channelSink->setDataAddress(parser.getDataAddress()); - m_channelSink->setDataPort(parser.getDataPort()); - } - else - { - qCritical("SDRDaemonMain::SDRDaemonMain: source device not found aborting"); - m_abort = true; - } - } - - qDebug() << "SDRDaemonMain::SDRDaemonMain: end"; -} - -SDRDaemonMain::~SDRDaemonMain() -{ - removeDevice(); - m_apiServer->stop(); - m_settings.save(); - delete m_apiServer; - delete m_requestMapper; - delete m_apiAdapter; - - delete m_pluginManager; - - qDebug() << "SDRDaemonMain::~SDRDaemonMain: end"; - delete m_logger; -} - -void SDRDaemonMain::loadSettings() -{ - qDebug() << "SDRDaemonMain::loadSettings"; - - m_settings.load(); - setLoggingOptions(); -} - -void SDRDaemonMain::setLoggingOptions() -{ - m_logger->setConsoleMinMessageLevel(m_settings.getConsoleMinLogLevel()); - - if (m_settings.getUseLogFile()) - { - qtwebapp::FileLoggerSettings fileLoggerSettings; // default values - - if (m_logger->hasFileLogger()) { - fileLoggerSettings = m_logger->getFileLoggerSettings(); // values from file logger if it exists - } - - fileLoggerSettings.fileName = m_settings.getLogFileName(); // put new values - m_logger->createOrSetFileLogger(fileLoggerSettings, 2000); // create file logger if it does not exist and apply settings in any case - } - - if (m_logger->hasFileLogger()) { - m_logger->setFileMinMessageLevel(m_settings.getFileMinLogLevel()); - } - - m_logger->setUseFileLogger(m_settings.getUseLogFile()); - - if (m_settings.getUseLogFile()) - { -#if QT_VERSION >= 0x050400 - QString appInfoStr(tr("%1 %2 Qt %3 %4b %5 %6 DSP Rx:%7b Tx:%8b PID %9") - .arg(QCoreApplication::applicationName()) - .arg(QCoreApplication::applicationVersion()) - .arg(QT_VERSION_STR) - .arg(QT_POINTER_SIZE*8) - .arg(QSysInfo::currentCpuArchitecture()) - .arg(QSysInfo::prettyProductName()) - .arg(SDR_RX_SAMP_SZ) - .arg(SDR_TX_SAMP_SZ) - .arg(QCoreApplication::applicationPid())); -#else - QString appInfoStr(tr("%1 %2 Qt %3 %4b DSP Rx:%5b Tx:%6b PID %7") - .arg(QCoreApplication::applicationName()) - .arg(QCoreApplication::applicationVersion()) - .arg(QT_VERSION_STR) - .arg(QT_POINTER_SIZE*8) - .arg(SDR_RX_SAMP_SZ) - .arg(SDR_RX_SAMP_SZ) - .arg(QCoreApplication::applicationPid()); - #endif - m_logger->logToFile(QtInfoMsg, appInfoStr); - } -} - -bool SDRDaemonMain::addSinkDevice() -{ - int deviceIndex = getDeviceIndex(); - - if (deviceIndex >= 0) - { - DSPDeviceSinkEngine *dspDeviceSinkEngine = m_dspEngine->addDeviceSinkEngine(); - dspDeviceSinkEngine->start(); - - uint dspDeviceSinkEngineUID = dspDeviceSinkEngine->getUID(); - char uidCStr[16]; - sprintf(uidCStr, "UID:%d", dspDeviceSinkEngineUID); - - m_deviceSinkEngine = dspDeviceSinkEngine; - m_deviceSinkAPI = new DeviceSinkAPI(0, dspDeviceSinkEngine); - - PluginInterface::SamplingDevice samplingDevice = DeviceEnumerator::instance()->getTxSamplingDevice(deviceIndex); - m_deviceSinkAPI->setSampleSinkSequence(samplingDevice.sequence); - m_deviceSinkAPI->setNbItems(samplingDevice.deviceNbItems); - m_deviceSinkAPI->setItemIndex(samplingDevice.deviceItemIndex); - m_deviceSinkAPI->setHardwareId(samplingDevice.hardwareId); - m_deviceSinkAPI->setSampleSinkId(samplingDevice.id); - m_deviceSinkAPI->setSampleSinkSerial(samplingDevice.serial); - m_deviceSinkAPI->setSampleSinkDisplayName(samplingDevice.displayedName); - m_deviceSinkAPI->setSampleSinkPluginInterface(DeviceEnumerator::instance()->getTxPluginInterface(deviceIndex)); - - DeviceSampleSink *sink = m_deviceSinkAPI->getPluginInterface()->createSampleSinkPluginInstanceOutput( - m_deviceSinkAPI->getSampleSinkId(), m_deviceSinkAPI); - m_deviceSinkAPI->setSampleSink(sink); - return true; - } - - return false; -} - -bool SDRDaemonMain::addSourceDevice() -{ - int deviceIndex = getDeviceIndex(); - - if (deviceIndex >= 0) - { - DSPDeviceSourceEngine *dspDeviceSourceEngine = m_dspEngine->addDeviceSourceEngine(); - dspDeviceSourceEngine->start(); - - uint dspDeviceSourceEngineUID = dspDeviceSourceEngine->getUID(); - char uidCStr[16]; - sprintf(uidCStr, "UID:%d", dspDeviceSourceEngineUID); - - m_deviceSourceEngine = dspDeviceSourceEngine; - m_deviceSourceAPI = new DeviceSourceAPI(0, dspDeviceSourceEngine); - - PluginInterface::SamplingDevice samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(deviceIndex); - m_deviceSourceAPI->setSampleSourceSequence(samplingDevice.sequence); - m_deviceSourceAPI->setNbItems(samplingDevice.deviceNbItems); - m_deviceSourceAPI->setItemIndex(samplingDevice.deviceItemIndex); - m_deviceSourceAPI->setHardwareId(samplingDevice.hardwareId); - m_deviceSourceAPI->setSampleSourceId(samplingDevice.id); - m_deviceSourceAPI->setSampleSourceSerial(samplingDevice.serial); - m_deviceSourceAPI->setSampleSourceDisplayName(samplingDevice.displayedName); - m_deviceSourceAPI->setSampleSourcePluginInterface(DeviceEnumerator::instance()->getRxPluginInterface(deviceIndex)); - - DeviceSampleSource *source = m_deviceSourceAPI->getPluginInterface()->createSampleSourcePluginInstanceInput( - m_deviceSourceAPI->getSampleSourceId(), m_deviceSourceAPI); - m_deviceSourceAPI->setSampleSource(source); - return true; - } - - return false; -} - -void SDRDaemonMain::removeDevice() -{ - if (m_deviceSourceEngine) // source set - { - m_deviceSourceEngine->stopAcquistion(); - - // deletes old UI and input object - - if (m_channelSink) { - m_channelSink->destroy(); - } - - m_deviceSourceAPI->resetSampleSourceId(); - m_deviceSourceAPI->getPluginInterface()->deleteSampleSourcePluginInstanceInput( - m_deviceSourceAPI->getSampleSource()); - m_deviceSourceAPI->clearBuddiesLists(); // clear old API buddies lists - - m_deviceSourceEngine->stop(); - m_dspEngine->removeLastDeviceSourceEngine(); - - delete m_deviceSourceAPI; - m_deviceSourceAPI = 0; - } - else if (m_deviceSinkEngine) // sink set - { - m_deviceSinkEngine->stopGeneration(); - - // deletes old UI and output object - - if (m_channelSource) { - m_channelSource->destroy(); - } - - m_deviceSinkAPI->resetSampleSinkId(); - m_deviceSinkAPI->getPluginInterface()->deleteSampleSinkPluginInstanceOutput( - m_deviceSinkAPI->getSampleSink()); - m_deviceSinkAPI->clearBuddiesLists(); // clear old API buddies lists - - m_deviceSinkEngine->stop(); - m_dspEngine->removeLastDeviceSinkEngine(); - - delete m_deviceSinkAPI; - m_deviceSinkAPI = 0; - } -} - -int SDRDaemonMain::getDeviceIndex() -{ - int nbSamplingDevices = m_tx ? DeviceEnumerator::instance()->getNbTxSamplingDevices() : DeviceEnumerator::instance()->getNbRxSamplingDevices(); - - for (int i = 0; i < nbSamplingDevices; i++) - { - PluginInterface::SamplingDevice samplingDevice = m_tx ? DeviceEnumerator::instance()->getTxSamplingDevice(i) : DeviceEnumerator::instance()->getRxSamplingDevice(i); - if (samplingDevice.hardwareId == m_deviceType) - { - if (m_deviceSerial.length() > 0) - { - if (samplingDevice.serial == m_deviceSerial) { - return i; - } else { - continue; - } - } - else if (m_deviceSequence >= 0) - { - if (samplingDevice.sequence == m_deviceSequence) { - return i; - } else { - continue; - } - } - else - { - return i; - } - } - } - - return -1; // not found -} - -bool SDRDaemonMain::handleMessage(const Message& cmd __attribute__((unused))) -{ - return false; -} - -void SDRDaemonMain::handleMessages() -{ - Message* message; - - while ((message = m_inputMessageQueue.pop()) != 0) - { - qDebug("SDRDaemonMain::handleMessages: message: %s", message->getIdentifier()); - handleMessage(*message); - delete message; - } -} diff --git a/sdrdaemon/sdrdaemonmain.h b/sdrdaemon/sdrdaemonmain.h deleted file mode 100644 index cc4f987de..000000000 --- a/sdrdaemon/sdrdaemonmain.h +++ /dev/null @@ -1,114 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon instance // -// // -// 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 SDRDAEMON_SDRDAEMONMAIN_H_ -#define SDRDAEMON_SDRDAEMONMAIN_H_ - -#include -#include - -#include "sdrdaemonsettings.h" -#include "util/messagequeue.h" - -namespace SDRDaemon { - class WebAPIRequestMapper; - class WebAPIServer; -} - -namespace qtwebapp { - class LoggerWithFile; -} - -class SDRDaemonParser; -class DSPEngine; -class PluginManager; -class Message; -class WebAPIAdapterDaemon; -class DSPDeviceSourceEngine; -class DeviceSourceAPI; -class DSPDeviceSinkEngine; -class DeviceSinkAPI; -class SDRDaemonChannelSink; -class SDRDaemonChannelSource; - -class SDRDaemonMain : public QObject { - Q_OBJECT -public: - explicit SDRDaemonMain(qtwebapp::LoggerWithFile *logger, const SDRDaemonParser& parser, QObject *parent = 0); - ~SDRDaemonMain(); - static SDRDaemonMain *getInstance() { return m_instance; } // Main Core is de facto a singleton so this just returns its reference - - MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; } - - const QTimer& getMasterTimer() const { return m_masterTimer; } - const SDRDaemonSettings& getSettings() const { return m_settings; } - - bool addSourceDevice(); - bool addSinkDevice(); - void removeDevice(); - - bool doAbort() const { return m_abort; } - - friend class WebAPIAdapterDaemon; - -signals: - void finished(); - -private: - static SDRDaemonMain *m_instance; - qtwebapp::LoggerWithFile *m_logger; - SDRDaemonSettings m_settings; - DSPEngine* m_dspEngine; - int m_lastEngineState; - PluginManager* m_pluginManager; - MessageQueue m_inputMessageQueue; - QTimer m_masterTimer; - - SDRDaemon::WebAPIRequestMapper *m_requestMapper; - SDRDaemon::WebAPIServer *m_apiServer; - WebAPIAdapterDaemon *m_apiAdapter; - - bool m_tx; - QString m_deviceType; - QString m_deviceSerial; - int m_deviceSequence; - - DSPDeviceSourceEngine *m_deviceSourceEngine; - DeviceSourceAPI *m_deviceSourceAPI; - DSPDeviceSinkEngine *m_deviceSinkEngine; - DeviceSinkAPI *m_deviceSinkAPI; - SDRDaemonChannelSink *m_channelSink; - SDRDaemonChannelSource *m_channelSource; - - bool m_abort; - - void loadSettings(); - void setLoggingOptions(); - int getDeviceIndex(); - bool handleMessage(const Message& cmd); - void addChannelSink(); - -private slots: - void handleMessages(); -}; - -#endif /* SDRDAEMON_SDRDAEMONMAIN_H_ */ diff --git a/sdrdaemon/sdrdaemonparser.cpp b/sdrdaemon/sdrdaemonparser.cpp deleted file mode 100644 index a3fd6e222..000000000 --- a/sdrdaemon/sdrdaemonparser.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon command line parser // -// // -// 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 "sdrdaemonparser.h" - -#include -#include -#include - -SDRDaemonParser::SDRDaemonParser() : - m_serverAddressOption(QStringList() << "a" << "api-address", - "API server and data (Tx) address.", - "localAddress", - "127.0.0.1"), - m_serverPortOption(QStringList() << "p" << "api-port", - "Web API server port.", - "apiPort", - "9091"), - m_dataAddressOption(QStringList() << "A" << "data-address", - "Remote data address (Rx).", - "remoteAddress", - "127.0.0.1"), - m_dataPortOption(QStringList() << "D" << "data-port", - "UDP stream data port.", - "dataPort", - "9090"), - m_deviceTypeOption(QStringList() << "T" << "device-type", - "Device type.", - "deviceType", - "TestSource"), - m_txOption(QStringList() << "t" << "tx", - "Tx indicator."), - m_serialOption(QStringList() << "s" << "serial", - "Device serial number.", - "serial"), - m_sequenceOption(QStringList() << "i" << "sequence", - "Device sequence index in enumeration for the same device type.", - "sequence"), - m_txDelayOption(QStringList() << "d" << "tx-delay", - "delay between transmission of UDP blocks (ms).", - "txDelay", - "100"), - m_nbBlocksFECOption(QStringList() << "f" << "fec-blocks", - "Number of FEC blocks per frame.", - "nbBlocksFEC", - "8") -{ - m_serverAddress = "127.0.0.1"; - m_serverPort = 9091; - m_dataAddress = "127.0.0.1"; - m_dataPort = 9090; - m_deviceType = "TestSource"; - m_tx = false; - m_sequence = 0; - m_txDelay = 100; - m_nbBlocksFEC = 8; - m_hasSequence = false; - m_hasSerial = false; - - m_parser.setApplicationDescription("Software Defined Radio RF header server"); - m_parser.addHelpOption(); - m_parser.addVersionOption(); - - m_parser.addOption(m_serverAddressOption); - m_parser.addOption(m_serverPortOption); - m_parser.addOption(m_dataAddressOption); - m_parser.addOption(m_dataPortOption); - m_parser.addOption(m_deviceTypeOption); - m_parser.addOption(m_txOption); - m_parser.addOption(m_serialOption); - m_parser.addOption(m_sequenceOption); - m_parser.addOption(m_txDelayOption); - m_parser.addOption(m_nbBlocksFECOption); -} - -SDRDaemonParser::~SDRDaemonParser() -{ } - -void SDRDaemonParser::parse(const QCoreApplication& app) -{ - m_parser.process(app); - - int pos; - bool ok; - - // server address - - QString serverAddress = m_parser.value(m_serverAddressOption); - - QString ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"; - QRegExp ipRegex ("^" + ipRange - + "\\." + ipRange - + "\\." + ipRange - + "\\." + ipRange + "$"); - QRegExpValidator ipValidator(ipRegex); - - if (ipValidator.validate(serverAddress, pos) == QValidator::Acceptable) - { - m_serverAddress = serverAddress; - qDebug() << "SDRDaemonParser::parse: server address: " << m_serverAddress; - } - else - { - qWarning() << "SDRDaemonParser::parse: server address invalid. Defaulting to " << m_serverAddress; - } - - // server port - - QString serverPortStr = m_parser.value(m_serverPortOption); - int serverPort = serverPortStr.toInt(&ok); - - if (ok && (serverPort > 1023) && (serverPort < 65536)) - { - m_serverPort = serverPort; - qDebug() << "SDRDaemonParser::parse: server port: " << m_serverPort; - } - else - { - qWarning() << "SDRDaemonParser::parse: server port invalid. Defaulting to " << m_serverPort; - } - - // data address - - QString dataAddress = m_parser.value(m_dataAddressOption); - - if (ipValidator.validate(dataAddress, pos) == QValidator::Acceptable) - { - m_dataAddress = dataAddress; - qDebug() << "SDRDaemonParser::parse: data address: " << m_dataAddress; - } - else - { - qWarning() << "SDRDaemonParser::parse: data address invalid. Defaulting to " << m_dataAddress; - } - - // data port - - QString dataPortStr = m_parser.value(m_dataPortOption); - serverPort = dataPortStr.toInt(&ok); - - if (ok && (serverPort > 1023) && (serverPort < 65536)) - { - m_dataPort = serverPort; - qDebug() << "SDRDaemonParser::parse: data port: " << m_dataPort; - } - else - { - qWarning() << "SDRDaemonParser::parse: data port invalid. Defaulting to " << m_dataPort; - } - - // tx - m_tx = m_parser.isSet(m_txOption); - qDebug() << "SDRDaemonParser::parse: tx: " << m_tx; - - // device type - - if (m_parser.isSet(m_deviceTypeOption)) - { - QString deviceType = m_parser.value(m_deviceTypeOption); - - QRegExp deviceTypeRegex("^[A-Z][A-Za-z0-9]+$"); - QRegExpValidator deviceTypeValidator(deviceTypeRegex); - - if (deviceTypeValidator.validate(deviceType, pos) == QValidator::Acceptable) - { - m_deviceType = deviceType; - qDebug() << "SDRDaemonParser::parse: device type: " << m_deviceType; - } - else - { - m_deviceType = m_tx ? "FileSink" : "TestSource"; - qWarning() << "SDRDaemonParser::parse: device type invalid. Defaulting to " << m_deviceType; - } - } - else - { - m_deviceType = m_tx ? "FileSink" : "TestSource"; - qInfo() << "SDRDaemonParser::parse: device type not specified. defaulting to " << m_deviceType; - } - - - // serial - m_hasSerial = m_parser.isSet(m_serialOption); - - if (m_hasSerial) - { - m_serial = m_parser.value(m_serialOption); - qDebug() << "SDRDaemonParser::parse: serial: " << m_serial; - } - - // sequence - m_hasSequence = m_parser.isSet(m_sequenceOption); - - if (m_hasSequence) - { - QString sequenceStr = m_parser.value(m_sequenceOption); - int sequence = sequenceStr.toInt(&ok); - - if (ok && (sequence >= 0) && (sequence < 65536)) { - m_sequence = sequence; - qDebug() << "SDRDaemonParser::parse: sequence: " << m_sequence; - } else { - qWarning() << "SDRDaemonParser::parse: sequence invalid. Defaulting to " << m_sequence; - } - } - - // Tx delay - if (m_parser.isSet(m_txDelayOption)) - { - QString txDelayStr = m_parser.value(m_txDelayOption); - int txDelay = txDelayStr.toInt(&ok); - - if (ok && (txDelay >= 0)) - { - m_txDelay = txDelay; - qDebug() << "SDRDaemonParser::parse: Tx delay: " << m_txDelay; - } - else - { - qWarning() << "SDRDaemonParser::parse: Tx delay invalid. Defaulting to " << m_txDelay; - } - } - - // nb FEC blocks - if (m_parser.isSet(m_nbBlocksFECOption)) - { - QString nbBlocksFECStr = m_parser.value(m_nbBlocksFECOption); - int nbBlocksFEC = nbBlocksFECStr.toInt(&ok); - - if (ok && (nbBlocksFEC >= 0) && (nbBlocksFEC < 128)) - { - m_nbBlocksFEC = nbBlocksFEC; - qDebug() << "SDRDaemonParser::parse: number of FEC blocks: " << m_nbBlocksFEC; - } - else - { - qWarning() << "SDRDaemonParser::parse: number of FEC blocks invalid. Defaulting to " << m_nbBlocksFEC; - } - } -} - - - diff --git a/sdrdaemon/sdrdaemonparser.h b/sdrdaemon/sdrdaemonparser.h deleted file mode 100644 index 30b23729d..000000000 --- a/sdrdaemon/sdrdaemonparser.h +++ /dev/null @@ -1,80 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon command line parser // -// // -// 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 SDRDAEMON_SDRDAEMONPARSER_H_ -#define SDRDAEMON_SDRDAEMONPARSER_H_ - -#include -#include - -class SDRDaemonParser -{ -public: - SDRDaemonParser(); - ~SDRDaemonParser(); - - void parse(const QCoreApplication& app); - - const QString& getServerAddress() const { return m_serverAddress; } - uint16_t getServerPort() const { return m_serverPort; } - const QString& getDataAddress() const { return m_dataAddress; } - uint16_t getDataPort() const { return m_dataPort; } - const QString& getDeviceType() const { return m_deviceType; } - bool getTx() const { return m_tx; } - const QString& getSerial() const { return m_serial; } - uint16_t getSequence() const { return m_sequence; } - int getTxDelay() const { return m_txDelay; } - int getNbBlocksFEC() const { return m_nbBlocksFEC; } - - bool hasSequence() const { return m_hasSequence; } - bool hasSerial() const { return m_hasSerial; } - -private: - QString m_serverAddress; //!< Address of interface the API and UDP data (Tx) listens on - uint16_t m_serverPort; //!< Port the API listens on - QString m_dataAddress; //!< Address of destination of UDP stream (Rx) - uint16_t m_dataPort; //!< Destination port of UDP stream (Rx) or listening port (Tx) - QString m_deviceType; //!< Identifies the type of device - bool m_tx; //!< True for Tx - QString m_serial; //!< Serial number of the device - uint16_t m_sequence; //!< Sequence of the device for the same type of device in enumeration process - int m_txDelay; //!< Initial delay between transmission of UDP blocks in milliseconds - int m_nbBlocksFEC; //!< Number of FEC blocks per frame; - bool m_hasSerial; //!< True if serial was specified - bool m_hasSequence; //!< True if sequence was specified - - QCommandLineParser m_parser; - QCommandLineOption m_serverAddressOption; - QCommandLineOption m_serverPortOption; - QCommandLineOption m_dataAddressOption; - QCommandLineOption m_dataPortOption; - QCommandLineOption m_deviceTypeOption; - QCommandLineOption m_txOption; - QCommandLineOption m_serialOption; - QCommandLineOption m_sequenceOption; - QCommandLineOption m_txDelayOption; - QCommandLineOption m_nbBlocksFECOption; -}; - - - -#endif /* SDRDAEMON_SDRDAEMONPARSER_H_ */ diff --git a/sdrdaemon/sdrdaemonpreferences.cpp b/sdrdaemon/sdrdaemonpreferences.cpp deleted file mode 100644 index a194cc46f..000000000 --- a/sdrdaemon/sdrdaemonpreferences.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon instance // -// // -// 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 "sdrdaemonpreferences.h" - -SDRDaemonPreferences::SDRDaemonPreferences() -{ - resetToDefaults(); -} - -void SDRDaemonPreferences::resetToDefaults() -{ - m_useLogFile = false; - m_logFileName = "sdrangel.log"; - m_consoleMinLogLevel = QtDebugMsg; - m_fileMinLogLevel = QtDebugMsg; -} - -QByteArray SDRDaemonPreferences::serialize() const -{ - SimpleSerializer s(1); - s.writeS32(1, (int) m_consoleMinLogLevel); - s.writeBool(2, m_useLogFile); - s.writeString(3, m_logFileName); - s.writeS32(4, (int) m_fileMinLogLevel); - return s.final(); -} - -bool SDRDaemonPreferences::deserialize(const QByteArray& data) -{ - int tmpInt; - - SimpleDeserializer d(data); - - if(!d.isValid()) { - resetToDefaults(); - return false; - } - - if(d.getVersion() == 1) - { - d.readS32(1, &tmpInt, (int) QtDebugMsg); - - if ((tmpInt == (int) QtDebugMsg) || - (tmpInt == (int) QtInfoMsg) || - (tmpInt == (int) QtWarningMsg) || - (tmpInt == (int) QtCriticalMsg) || - (tmpInt == (int) QtFatalMsg)) { - m_consoleMinLogLevel = (QtMsgType) tmpInt; - } else { - m_consoleMinLogLevel = QtDebugMsg; - } - - d.readBool(2, &m_useLogFile, false); - d.readString(3, &m_logFileName, "sdrangel.log"); - - d.readS32(4, &tmpInt, (int) QtDebugMsg); - - if ((tmpInt == (int) QtDebugMsg) || - (tmpInt == (int) QtInfoMsg) || - (tmpInt == (int) QtWarningMsg) || - (tmpInt == (int) QtCriticalMsg) || - (tmpInt == (int) QtFatalMsg)) { - m_fileMinLogLevel = (QtMsgType) tmpInt; - } else { - m_fileMinLogLevel = QtDebugMsg; - } - - return true; - } else - { - resetToDefaults(); - return false; - } -} diff --git a/sdrdaemon/sdrdaemonpreferences.h b/sdrdaemon/sdrdaemonpreferences.h deleted file mode 100644 index c3ea5c1f2..000000000 --- a/sdrdaemon/sdrdaemonpreferences.h +++ /dev/null @@ -1,53 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon instance // -// // -// 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 SDRDAEMON_SDRDAEMONPREFERENCES_H_ -#define SDRDAEMON_SDRDAEMONPREFERENCES_H_ - -#include - -class SDRDaemonPreferences -{ -public: - SDRDaemonPreferences(); - - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); - - void setConsoleMinLogLevel(const QtMsgType& minLogLevel) { m_consoleMinLogLevel = minLogLevel; } - void setFileMinLogLevel(const QtMsgType& minLogLevel) { m_fileMinLogLevel = minLogLevel; } - void setUseLogFile(bool useLogFile) { m_useLogFile = useLogFile; } - void setLogFileName(const QString& value) { m_logFileName = value; } - QtMsgType getConsoleMinLogLevel() const { return m_consoleMinLogLevel; } - QtMsgType getFileMinLogLevel() const { return m_fileMinLogLevel; } - bool getUseLogFile() const { return m_useLogFile; } - const QString& getLogFileName() const { return m_logFileName; } - -private: - QtMsgType m_consoleMinLogLevel; - QtMsgType m_fileMinLogLevel; - bool m_useLogFile; - QString m_logFileName; -}; - -#endif /* SDRDAEMON_SDRDAEMONPREFERENCES_H_ */ diff --git a/sdrdaemon/sdrdaemonsettings.cpp b/sdrdaemon/sdrdaemonsettings.cpp deleted file mode 100644 index 2fc177701..000000000 --- a/sdrdaemon/sdrdaemonsettings.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon instance // -// // -// 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 "sdrdaemonpreferences.h" -#include "sdrdaemonsettings.h" - -SDRDaemonSettings::SDRDaemonSettings() -{ - resetToDefaults(); -} - -SDRDaemonSettings::~SDRDaemonSettings() -{} - -void SDRDaemonSettings::load() -{ - QSettings s; - m_preferences.deserialize(qUncompress(QByteArray::fromBase64(s.value("preferences").toByteArray()))); -} - -void SDRDaemonSettings::save() const -{ - QSettings s; - s.setValue("preferences", qCompress(m_preferences.serialize()).toBase64()); -} - -void SDRDaemonSettings::resetToDefaults() -{ - m_preferences.resetToDefaults(); -} diff --git a/sdrdaemon/sdrdaemonsettings.h b/sdrdaemon/sdrdaemonsettings.h deleted file mode 100644 index ef909a8fe..000000000 --- a/sdrdaemon/sdrdaemonsettings.h +++ /dev/null @@ -1,54 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon instance // -// // -// 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 SDRDAEMON_SDRDAEMONSETTINGS_H_ -#define SDRDAEMON_SDRDAEMONSETTINGS_H_ - -#include "sdrdaemonpreferences.h" - -class SDRDaemonSettings -{ -public: - SDRDaemonSettings(); - ~SDRDaemonSettings(); - - void load(); - void save() const; - - void resetToDefaults(); - - void setConsoleMinLogLevel(const QtMsgType& minLogLevel) { m_preferences.setConsoleMinLogLevel(minLogLevel); } - void setFileMinLogLevel(const QtMsgType& minLogLevel) { m_preferences.setFileMinLogLevel(minLogLevel); } - void setUseLogFile(bool useLogFile) { m_preferences.setUseLogFile(useLogFile); } - void setLogFileName(const QString& value) { m_preferences.setLogFileName(value); } - QtMsgType getConsoleMinLogLevel() const { return m_preferences.getConsoleMinLogLevel(); } - QtMsgType getFileMinLogLevel() const { return m_preferences.getFileMinLogLevel(); } - bool getUseLogFile() const { return m_preferences.getUseLogFile(); } - const QString& getLogFileName() const { return m_preferences.getLogFileName(); } - -private: - SDRDaemonPreferences m_preferences; -}; - - - -#endif /* SDRDAEMON_SDRDAEMONSETTINGS_H_ */ diff --git a/sdrdaemon/webapi/webapiadapterdaemon.cpp b/sdrdaemon/webapi/webapiadapterdaemon.cpp deleted file mode 100644 index dbcddb71b..000000000 --- a/sdrdaemon/webapi/webapiadapterdaemon.cpp +++ /dev/null @@ -1,561 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRDaemon Swagger server adapter interface // -// // -// 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 "SWGDaemonSummaryResponse.h" -#include "SWGLoggingInfo.h" -#include "SWGDeviceSettings.h" -#include "SWGDeviceState.h" -#include "SWGDeviceReport.h" -#include "SWGChannelReport.h" -#include "SWGChannelSettings.h" -#include "SWGErrorResponse.h" - -#include "dsp/dsptypes.h" -#include "dsp/dspdevicesourceengine.h" -#include "dsp/dspdevicesinkengine.h" -#include "device/devicesourceapi.h" -#include "device/devicesinkapi.h" -#include "channel/channelsourceapi.h" -#include "channel/channelsinkapi.h" -#include "dsp/devicesamplesink.h" -#include "dsp/devicesamplesource.h" -#include "webapiadapterdaemon.h" -#include "sdrdaemonmain.h" -#include "loggerwithfile.h" - -QString WebAPIAdapterDaemon::daemonInstanceSummaryURL = "/sdrdaemon"; -QString WebAPIAdapterDaemon::daemonInstanceLoggingURL = "/sdrdaemon/logging"; -QString WebAPIAdapterDaemon::daemonChannelSettingsURL = "/sdrdaemon/channel/settings"; -QString WebAPIAdapterDaemon::daemonDeviceSettingsURL = "/sdrdaemon/device/settings"; -QString WebAPIAdapterDaemon::daemonDeviceReportURL = "/sdrdaemon/device/report"; -QString WebAPIAdapterDaemon::daemonChannelReportURL = "/sdrdaemon/channel/report"; -QString WebAPIAdapterDaemon::daemonRunURL = "/sdrdaemon/run"; - -WebAPIAdapterDaemon::WebAPIAdapterDaemon(SDRDaemonMain& sdrDaemonMain) : - m_sdrDaemonMain(sdrDaemonMain) -{ -} - -WebAPIAdapterDaemon::~WebAPIAdapterDaemon() -{ -} - -int WebAPIAdapterDaemon::daemonInstanceSummary( - SWGSDRangel::SWGDaemonSummaryResponse& response, - SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) -{ - response.init(); - *response.getAppname() = QCoreApplication::applicationName(); - *response.getVersion() = QCoreApplication::applicationVersion(); - *response.getQtVersion() = QString(QT_VERSION_STR); - response.setDspRxBits(SDR_RX_SAMP_SZ); - response.setDspTxBits(SDR_TX_SAMP_SZ); - response.setPid(QCoreApplication::applicationPid()); -#if QT_VERSION >= 0x050400 - *response.getArchitecture() = QString(QSysInfo::currentCpuArchitecture()); - *response.getOs() = QString(QSysInfo::prettyProductName()); -#endif - - SWGSDRangel::SWGLoggingInfo *logging = response.getLogging(); - logging->init(); - logging->setDumpToFile(m_sdrDaemonMain.m_logger->getUseFileLogger() ? 1 : 0); - - if (logging->getDumpToFile()) { - m_sdrDaemonMain.m_logger->getLogFileName(*logging->getFileName()); - m_sdrDaemonMain.m_logger->getFileMinMessageLevelStr(*logging->getFileLevel()); - } - - m_sdrDaemonMain.m_logger->getConsoleMinMessageLevelStr(*logging->getConsoleLevel()); - - SWGSDRangel::SWGSamplingDevice *samplingDevice = response.getSamplingDevice(); - samplingDevice->setTx(m_sdrDaemonMain.m_tx ? 1 : 0); - samplingDevice->setHwType(new QString(m_sdrDaemonMain.m_deviceType)); - samplingDevice->setIndex(0); - - if (m_sdrDaemonMain.m_tx) - { - QString state; - m_sdrDaemonMain.m_deviceSinkAPI->getDeviceEngineStateStr(state); - samplingDevice->setState(new QString(state)); - samplingDevice->setSerial(new QString(m_sdrDaemonMain.m_deviceSinkAPI->getSampleSinkSerial())); - samplingDevice->setSequence(m_sdrDaemonMain.m_deviceSinkAPI->getSampleSinkSequence()); - samplingDevice->setNbStreams(m_sdrDaemonMain.m_deviceSinkAPI->getNbItems()); - samplingDevice->setStreamIndex(m_sdrDaemonMain.m_deviceSinkAPI->getItemIndex()); - DeviceSampleSink *sampleSink = m_sdrDaemonMain.m_deviceSinkEngine->getSink(); - - if (sampleSink) { - samplingDevice->setCenterFrequency(sampleSink->getCenterFrequency()); - samplingDevice->setBandwidth(sampleSink->getSampleRate()); - } - } - else - { - QString state; - m_sdrDaemonMain.m_deviceSourceAPI->getDeviceEngineStateStr(state); - samplingDevice->setState(new QString(state)); - samplingDevice->setSerial(new QString(m_sdrDaemonMain.m_deviceSourceAPI->getSampleSourceSerial())); - samplingDevice->setSequence(m_sdrDaemonMain.m_deviceSourceAPI->getSampleSourceSequence()); - samplingDevice->setNbStreams(m_sdrDaemonMain.m_deviceSourceAPI->getNbItems()); - samplingDevice->setStreamIndex(m_sdrDaemonMain.m_deviceSourceAPI->getItemIndex()); - DeviceSampleSource *sampleSource = m_sdrDaemonMain.m_deviceSourceEngine->getSource(); - - if (sampleSource) { - samplingDevice->setCenterFrequency(sampleSource->getCenterFrequency()); - samplingDevice->setBandwidth(sampleSource->getSampleRate()); - } - } - - return 200; -} - -int WebAPIAdapterDaemon::daemonInstanceLoggingGet( - SWGSDRangel::SWGLoggingInfo& response, - SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) -{ - response.init(); - response.setDumpToFile(m_sdrDaemonMain.m_logger->getUseFileLogger() ? 1 : 0); - - if (response.getDumpToFile()) { - m_sdrDaemonMain.m_logger->getLogFileName(*response.getFileName()); - m_sdrDaemonMain.m_logger->getFileMinMessageLevelStr(*response.getFileLevel()); - } - - m_sdrDaemonMain.m_logger->getConsoleMinMessageLevelStr(*response.getConsoleLevel()); - - return 200; -} - -int WebAPIAdapterDaemon::daemonInstanceLoggingPut( - SWGSDRangel::SWGLoggingInfo& query, - SWGSDRangel::SWGLoggingInfo& response, - SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) -{ - // response input is the query actually - bool dumpToFile = (query.getDumpToFile() != 0); - QString* consoleLevel = query.getConsoleLevel(); - QString* fileLevel = query.getFileLevel(); - QString* fileName = query.getFileName(); - - // perform actions - if (consoleLevel) { - m_sdrDaemonMain.m_settings.setConsoleMinLogLevel(getMsgTypeFromString(*consoleLevel)); - } - - if (fileLevel) { - m_sdrDaemonMain.m_settings.setFileMinLogLevel(getMsgTypeFromString(*fileLevel)); - } - - m_sdrDaemonMain.m_settings.setUseLogFile(dumpToFile); - - if (fileName) { - m_sdrDaemonMain.m_settings.setLogFileName(*fileName); - } - - m_sdrDaemonMain.setLoggingOptions(); - - // build response - response.init(); - getMsgTypeString(m_sdrDaemonMain.m_settings.getConsoleMinLogLevel(), *response.getConsoleLevel()); - response.setDumpToFile(m_sdrDaemonMain.m_settings.getUseLogFile() ? 1 : 0); - getMsgTypeString(m_sdrDaemonMain.m_settings.getFileMinLogLevel(), *response.getFileLevel()); - *response.getFileName() = m_sdrDaemonMain.m_settings.getLogFileName(); - - return 200; -} - -int WebAPIAdapterDaemon::daemonChannelSettingsGet( - SWGSDRangel::SWGChannelSettings& response, - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - ChannelSinkAPI *channelAPI = m_sdrDaemonMain.m_deviceSourceAPI->getChanelAPIAt(0); - - if (channelAPI == 0) - { - *error.getMessage() = QString("There is no channel"); - return 500; // a SDRDaemon sink channel should have been created so this is a server error - } - else - { - response.setChannelType(new QString()); - channelAPI->getIdentifier(*response.getChannelType()); - response.setTx(0); - return channelAPI->webapiSettingsGet(response, *error.getMessage()); - } - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - ChannelSourceAPI *channelAPI = m_sdrDaemonMain.m_deviceSinkAPI->getChanelAPIAt(0); - - if (channelAPI == 0) - { - *error.getMessage() = QString("There is no channel"); - return 500; // a SDRDaemon source channel should have been created so this is a server error - } - else - { - response.setChannelType(new QString()); - channelAPI->getIdentifier(*response.getChannelType()); - response.setTx(1); - return channelAPI->webapiSettingsGet(response, *error.getMessage()); - } - } - else - { - *error.getMessage() = QString("Device not created error"); - return 500; - } -} - -int WebAPIAdapterDaemon::daemonChannelSettingsPutPatch( - bool force, - const QStringList& channelSettingsKeys, - SWGSDRangel::SWGChannelSettings& response, - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - ChannelSinkAPI *channelAPI = m_sdrDaemonMain.m_deviceSourceAPI->getChanelAPIAt(0); - - if (channelAPI == 0) - { - *error.getMessage() = QString("There is no channel"); - return 500; - } - else - { - QString channelType; - channelAPI->getIdentifier(channelType); - - if (channelType == *response.getChannelType()) - { - return channelAPI->webapiSettingsPutPatch(force, channelSettingsKeys, response, *error.getMessage()); - } - else - { - *error.getMessage() = QString("Channel has wrong type. Found %1.").arg(channelType); - return 500; - } - } - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - ChannelSourceAPI *channelAPI = m_sdrDaemonMain.m_deviceSinkAPI->getChanelAPIAt(0); - - if (channelAPI == 0) - { - *error.getMessage() = QString("There is no channel"); - return 500; - } - else - { - QString channelType; - channelAPI->getIdentifier(channelType); - - if (channelType == *response.getChannelType()) - { - return channelAPI->webapiSettingsPutPatch(force, channelSettingsKeys, response, *error.getMessage()); - } - else - { - *error.getMessage() = QString("Channel has wrong type. Found %3.").arg(channelType); - return 500; - } - } - } - else - { - *error.getMessage() = QString("DeviceSet error"); - return 500; - } -} - -int WebAPIAdapterDaemon::daemonDeviceSettingsGet( - SWGSDRangel::SWGDeviceSettings& response __attribute__((unused)), - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - response.setDeviceHwType(new QString(m_sdrDaemonMain.m_deviceSourceAPI->getHardwareId())); - response.setTx(0); - DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); - return source->webapiSettingsGet(response, *error.getMessage()); - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - response.setDeviceHwType(new QString(m_sdrDaemonMain.m_deviceSinkAPI->getHardwareId())); - response.setTx(1); - DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); - return sink->webapiSettingsGet(response, *error.getMessage()); - } - else - { - *error.getMessage() = QString("Device error"); - return 500; - } -} - -int WebAPIAdapterDaemon::daemonDeviceSettingsPutPatch( - bool force, - const QStringList& deviceSettingsKeys, - SWGSDRangel::SWGDeviceSettings& response, - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - if (response.getTx() != 0) - { - *error.getMessage() = QString("Rx device found but Tx device requested"); - return 400; - } - if (m_sdrDaemonMain.m_deviceSourceAPI->getHardwareId() != *response.getDeviceHwType()) - { - *error.getMessage() = QString("Device mismatch. Found %1 input").arg(m_sdrDaemonMain.m_deviceSourceAPI->getHardwareId()); - return 400; - } - else - { - DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); - return source->webapiSettingsPutPatch(force, deviceSettingsKeys, response, *error.getMessage()); - } - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - if (response.getTx() == 0) - { - *error.getMessage() = QString("Tx device found but Rx device requested"); - return 400; - } - else if (m_sdrDaemonMain.m_deviceSinkAPI->getHardwareId() != *response.getDeviceHwType()) - { - *error.getMessage() = QString("Device mismatch. Found %1 output").arg(m_sdrDaemonMain.m_deviceSinkAPI->getHardwareId()); - return 400; - } - else - { - DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); - return sink->webapiSettingsPutPatch(force, deviceSettingsKeys, response, *error.getMessage()); - } - } - else - { - *error.getMessage() = QString("DeviceSet error"); - return 500; - } -} - -int WebAPIAdapterDaemon::daemonRunGet( - SWGSDRangel::SWGDeviceState& response, - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); - response.init(); - return source->webapiRunGet(response, *error.getMessage()); - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); - response.init(); - return sink->webapiRunGet(response, *error.getMessage()); - } - else - { - *error.getMessage() = QString("DeviceSet error"); - return 500; - } -} - -int WebAPIAdapterDaemon::daemonRunPost( - SWGSDRangel::SWGDeviceState& response, - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); - response.init(); - return source->webapiRun(true, response, *error.getMessage()); - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); - response.init(); - return sink->webapiRun(true, response, *error.getMessage()); - } - else - { - *error.getMessage() = QString("DeviceSet error"); - return 500; - } -} - -int WebAPIAdapterDaemon::daemonRunDelete( - SWGSDRangel::SWGDeviceState& response, - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); - response.init(); - return source->webapiRun(false, response, *error.getMessage()); - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); - response.init(); - return sink->webapiRun(false, response, *error.getMessage()); - } - else - { - *error.getMessage() = QString("DeviceSet error"); - return 500; - } -} - -int WebAPIAdapterDaemon::daemonDeviceReportGet( - SWGSDRangel::SWGDeviceReport& response, - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - response.setDeviceHwType(new QString(m_sdrDaemonMain.m_deviceSourceAPI->getHardwareId())); - response.setTx(0); - DeviceSampleSource *source = m_sdrDaemonMain.m_deviceSourceAPI->getSampleSource(); - return source->webapiReportGet(response, *error.getMessage()); - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - response.setDeviceHwType(new QString(m_sdrDaemonMain.m_deviceSinkAPI->getHardwareId())); - response.setTx(1); - DeviceSampleSink *sink = m_sdrDaemonMain.m_deviceSinkAPI->getSampleSink(); - return sink->webapiReportGet(response, *error.getMessage()); - } - else - { - *error.getMessage() = QString("DeviceSet error"); - return 500; - } -} - -int WebAPIAdapterDaemon::daemonChannelReportGet( - SWGSDRangel::SWGChannelReport& response, - SWGSDRangel::SWGErrorResponse& error) -{ - error.init(); - - if (m_sdrDaemonMain.m_deviceSourceEngine) // Rx - { - ChannelSinkAPI *channelAPI = m_sdrDaemonMain.m_deviceSourceAPI->getChanelAPIAt(0); - - if (channelAPI == 0) - { - *error.getMessage() = QString("There is no channel"); - return 500; // a SDRDaemon sink channel should have been created so this is a server error - } - else - { - response.setChannelType(new QString()); - channelAPI->getIdentifier(*response.getChannelType()); - response.setTx(0); - return channelAPI->webapiReportGet(response, *error.getMessage()); - } - } - else if (m_sdrDaemonMain.m_deviceSinkEngine) // Tx - { - ChannelSourceAPI *channelAPI = m_sdrDaemonMain.m_deviceSinkAPI->getChanelAPIAt(0); - - if (channelAPI == 0) - { - *error.getMessage() = QString("There is no channel"); - return 500; // a SDRDaemon source channel should have been created so this is a server error - } - else - { - response.setChannelType(new QString()); - channelAPI->getIdentifier(*response.getChannelType()); - response.setTx(1); - return channelAPI->webapiReportGet(response, *error.getMessage()); - } - } - else - { - *error.getMessage() = QString("Device not created error"); - return 500; - } -} - - -// TODO: put in library in common with SDRangel. Can be static. -QtMsgType WebAPIAdapterDaemon::getMsgTypeFromString(const QString& msgTypeString) -{ - if (msgTypeString == "debug") { - return QtDebugMsg; - } else if (msgTypeString == "info") { - return QtInfoMsg; - } else if (msgTypeString == "warning") { - return QtWarningMsg; - } else if (msgTypeString == "error") { - return QtCriticalMsg; - } else { - return QtDebugMsg; - } -} - -// TODO: put in library in common with SDRangel. Can be static. -void WebAPIAdapterDaemon::getMsgTypeString(const QtMsgType& msgType, QString& levelStr) -{ - switch (msgType) - { - case QtDebugMsg: - levelStr = "debug"; - break; - case QtInfoMsg: - levelStr = "info"; - break; - case QtWarningMsg: - levelStr = "warning"; - break; - case QtCriticalMsg: - case QtFatalMsg: - levelStr = "error"; - break; - default: - levelStr = "debug"; - break; - } -} diff --git a/sdrdaemon/webapi/webapiadapterdaemon.h b/sdrdaemon/webapi/webapiadapterdaemon.h deleted file mode 100644 index cbc3e6c51..000000000 --- a/sdrdaemon/webapi/webapiadapterdaemon.h +++ /dev/null @@ -1,116 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRDaemon Swagger server adapter interface // -// // -// 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 SDRDAEMON_WEBAPI_WEBAPIADAPTERDAEMON_H_ -#define SDRDAEMON_WEBAPI_WEBAPIADAPTERDAEMON_H_ - -#include -#include - -namespace SWGSDRangel -{ - class SWGDaemonSummaryResponse; - class SWGDeviceSet; - class SWGDeviceListItem; - class SWGDeviceSettings; - class SWGDeviceState; - class SWGDeviceReport; - class SWGChannelReport; - class SWGSuccessResponse; - class SWGErrorResponse; - class SWGLoggingInfo; - class SWGChannelSettings; -} - -class SDRDaemonMain; - -class WebAPIAdapterDaemon -{ -public: - WebAPIAdapterDaemon(SDRDaemonMain& sdrDaemonMain); - ~WebAPIAdapterDaemon(); - - int daemonInstanceSummary( - SWGSDRangel::SWGDaemonSummaryResponse& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonInstanceLoggingGet( - SWGSDRangel::SWGLoggingInfo& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonInstanceLoggingPut( - SWGSDRangel::SWGLoggingInfo& query, - SWGSDRangel::SWGLoggingInfo& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonChannelSettingsGet( - SWGSDRangel::SWGChannelSettings& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonChannelSettingsPutPatch( - bool force, - const QStringList& channelSettingsKeys, - SWGSDRangel::SWGChannelSettings& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonDeviceSettingsGet( - SWGSDRangel::SWGDeviceSettings& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonDeviceSettingsPutPatch( - bool force, - const QStringList& deviceSettingsKeys, - SWGSDRangel::SWGDeviceSettings& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonRunGet( - SWGSDRangel::SWGDeviceState& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonRunPost( - SWGSDRangel::SWGDeviceState& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonRunDelete( - SWGSDRangel::SWGDeviceState& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonDeviceReportGet( - SWGSDRangel::SWGDeviceReport& response, - SWGSDRangel::SWGErrorResponse& error); - - int daemonChannelReportGet( - SWGSDRangel::SWGChannelReport& response, - SWGSDRangel::SWGErrorResponse& error); - - static QString daemonInstanceSummaryURL; - static QString daemonInstanceLoggingURL; - static QString daemonChannelSettingsURL; - static QString daemonDeviceSettingsURL; - static QString daemonDeviceReportURL; - static QString daemonChannelReportURL; - static QString daemonRunURL; - -private: - SDRDaemonMain& m_sdrDaemonMain; - - static QtMsgType getMsgTypeFromString(const QString& msgTypeString); - static void getMsgTypeString(const QtMsgType& msgType, QString& level); -}; - -#endif /* SDRDAEMON_WEBAPI_WEBAPIADAPTERDAEMON_H_ */ diff --git a/sdrdaemon/webapi/webapirequestmapper.cpp b/sdrdaemon/webapi/webapirequestmapper.cpp deleted file mode 100644 index 25c582148..000000000 --- a/sdrdaemon/webapi/webapirequestmapper.cpp +++ /dev/null @@ -1,1097 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRDaemon Swagger server adapter interface // -// // -// 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 "httpdocrootsettings.h" -#include "webapirequestmapper.h" -#include "SWGDaemonSummaryResponse.h" -#include "SWGInstanceDevicesResponse.h" -#include "SWGChannelSettings.h" -#include "SWGChannelReport.h" -#include "SWGDeviceSettings.h" -#include "SWGDeviceState.h" -#include "SWGDeviceReport.h" -#include "SWGSuccessResponse.h" -#include "SWGErrorResponse.h" -#include "SWGLoggingInfo.h" - -#include "webapirequestmapper.h" -#include "webapiadapterdaemon.h" - -namespace SDRDaemon -{ - -WebAPIRequestMapper::WebAPIRequestMapper(QObject* parent) : - HttpRequestHandler(parent), - m_adapter(0) -{ - qtwebapp::HttpDocrootSettings docrootSettings; - docrootSettings.path = ":/webapi"; - m_staticFileController = new qtwebapp::StaticFileController(docrootSettings, parent); -} - -WebAPIRequestMapper::~WebAPIRequestMapper() -{ - delete m_staticFileController; -} - -void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) -{ - if (m_adapter == 0) // format service unavailable if adapter is null - { - SWGSDRangel::SWGErrorResponse errorResponse; - response.setHeader("Content-Type", "application/json"); - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setStatus(500,"Service not available"); - - errorResponse.init(); - *errorResponse.getMessage() = "Service not available"; - response.write(errorResponse.asJson().toUtf8()); - } - else // normal processing - { - QByteArray path=request.getPath(); - - // Handle pre-flight requests - if (request.getMethod() == "OPTIONS") - { - qDebug("WebAPIRequestMapper::service: method OPTIONS: assume pre-flight"); - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setHeader("Access-Control-Allow-Headers", "*"); - response.setHeader("Access-Control-Allow-Methods", "*"); - response.setStatus(200, "OK"); - return; - } - - if (path.startsWith("/sdrangel") && (path != "/sdrangel_logo.png")) - { - SWGSDRangel::SWGErrorResponse errorResponse; - response.setStatus(501,"Not implemented"); - errorResponse.init(); - *errorResponse.getMessage() = "Not implemented"; - response.write(errorResponse.asJson().toUtf8()); - return; - } - - if (path == WebAPIAdapterDaemon::daemonInstanceSummaryURL) { - daemonInstanceSummaryService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonInstanceLoggingURL) { - daemonInstanceLoggingService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonChannelSettingsURL) { - daemonChannelSettingsService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonChannelReportURL) { - daemonChannelReportService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonDeviceSettingsURL) { - daemonDeviceSettingsService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonDeviceReportURL) { - daemonDeviceReportService(request, response); - } else if (path == WebAPIAdapterDaemon::daemonRunURL) { - daemonRunService(request, response); - } else { - m_staticFileController->service(request, response); // serve static pages - } - } -} - -void WebAPIRequestMapper::daemonInstanceSummaryService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) -{ - SWGSDRangel::SWGErrorResponse errorResponse; - response.setHeader("Content-Type", "application/json"); - response.setHeader("Access-Control-Allow-Origin", "*"); - - if (request.getMethod() == "GET") - { - SWGSDRangel::SWGDaemonSummaryResponse normalResponse; - - int status = m_adapter->daemonInstanceSummary(normalResponse, errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(405,"Invalid HTTP method"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid HTTP method"; - response.write(errorResponse.asJson().toUtf8()); - } -} - -void WebAPIRequestMapper::daemonInstanceLoggingService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) -{ - SWGSDRangel::SWGLoggingInfo query; - SWGSDRangel::SWGLoggingInfo normalResponse; - SWGSDRangel::SWGErrorResponse errorResponse; - response.setHeader("Content-Type", "application/json"); - response.setHeader("Access-Control-Allow-Origin", "*"); - - if (request.getMethod() == "GET") - { - int status = m_adapter->daemonInstanceLoggingGet(normalResponse, errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else if (request.getMethod() == "PUT") - { - QString jsonStr = request.getBody(); - QJsonObject jsonObject; - - if (parseJsonBody(jsonStr, jsonObject, response)) - { - query.fromJson(jsonStr); - int status = m_adapter->daemonInstanceLoggingPut(query, normalResponse, errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(400,"Invalid JSON format"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON format"; - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(405,"Invalid HTTP method"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid HTTP method"; - response.write(errorResponse.asJson().toUtf8()); - } -} - -void WebAPIRequestMapper::daemonChannelSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) -{ - SWGSDRangel::SWGErrorResponse errorResponse; - response.setHeader("Content-Type", "application/json"); - response.setHeader("Access-Control-Allow-Origin", "*"); - - if (request.getMethod() == "GET") - { - SWGSDRangel::SWGChannelSettings normalResponse; - resetChannelSettings(normalResponse); - int status = m_adapter->daemonChannelSettingsGet(normalResponse, errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else if ((request.getMethod() == "PUT") || (request.getMethod() == "PATCH")) - { - QString jsonStr = request.getBody(); - QJsonObject jsonObject; - - if (parseJsonBody(jsonStr, jsonObject, response)) - { - SWGSDRangel::SWGChannelSettings normalResponse; - resetChannelSettings(normalResponse); - QStringList channelSettingsKeys; - - if (validateChannelSettings(normalResponse, jsonObject, channelSettingsKeys)) - { - int status = m_adapter->daemonChannelSettingsPutPatch( - (request.getMethod() == "PUT"), // force settings on PUT - channelSettingsKeys, - normalResponse, - errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(400,"Invalid JSON request"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON request"; - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(400,"Invalid JSON format"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON format"; - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(405,"Invalid HTTP method"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid HTTP method"; - response.write(errorResponse.asJson().toUtf8()); - } -} - -void WebAPIRequestMapper::daemonChannelReportService( - qtwebapp::HttpRequest& request, - qtwebapp::HttpResponse& response) -{ - SWGSDRangel::SWGErrorResponse errorResponse; - response.setHeader("Content-Type", "application/json"); - response.setHeader("Access-Control-Allow-Origin", "*"); - - if (request.getMethod() == "GET") - { - SWGSDRangel::SWGChannelReport normalResponse; - resetChannelReport(normalResponse); - int status = m_adapter->daemonChannelReportGet(normalResponse, errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } -} - -void WebAPIRequestMapper::daemonDeviceSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) -{ - SWGSDRangel::SWGErrorResponse errorResponse; - response.setHeader("Content-Type", "application/json"); - response.setHeader("Access-Control-Allow-Origin", "*"); - - if ((request.getMethod() == "PUT") || (request.getMethod() == "PATCH")) - { - QString jsonStr = request.getBody(); - QJsonObject jsonObject; - - if (parseJsonBody(jsonStr, jsonObject, response)) - { - SWGSDRangel::SWGDeviceSettings normalResponse; - resetDeviceSettings(normalResponse); - QStringList deviceSettingsKeys; - - if (validateDeviceSettings(normalResponse, jsonObject, deviceSettingsKeys)) - { - int status = m_adapter->daemonDeviceSettingsPutPatch( - (request.getMethod() == "PUT"), // force settings on PUT - deviceSettingsKeys, - normalResponse, - errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(400,"Invalid JSON request"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON request"; - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(400,"Invalid JSON format"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON format"; - response.write(errorResponse.asJson().toUtf8()); - } - } - else if (request.getMethod() == "GET") - { - SWGSDRangel::SWGDeviceSettings normalResponse; - resetDeviceSettings(normalResponse); - int status = m_adapter->daemonDeviceSettingsGet(normalResponse, errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(405,"Invalid HTTP method"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid HTTP method"; - response.write(errorResponse.asJson().toUtf8()); - } -} - -void WebAPIRequestMapper::daemonDeviceReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) -{ - SWGSDRangel::SWGErrorResponse errorResponse; - response.setHeader("Content-Type", "application/json"); - response.setHeader("Access-Control-Allow-Origin", "*"); - - if (request.getMethod() == "GET") - { - SWGSDRangel::SWGDeviceReport normalResponse; - resetDeviceReport(normalResponse); - int status = m_adapter->daemonDeviceReportGet(normalResponse, errorResponse); - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(405,"Invalid HTTP method"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid HTTP method"; - response.write(errorResponse.asJson().toUtf8()); - } -} - -void WebAPIRequestMapper::daemonRunService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) -{ - SWGSDRangel::SWGErrorResponse errorResponse; - response.setHeader("Content-Type", "application/json"); - response.setHeader("Access-Control-Allow-Origin", "*"); - - if (request.getMethod() == "GET") - { - SWGSDRangel::SWGDeviceState normalResponse; - int status = m_adapter->daemonRunGet(normalResponse, errorResponse); - - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else if (request.getMethod() == "POST") - { - SWGSDRangel::SWGDeviceState normalResponse; - int status = m_adapter->daemonRunPost(normalResponse, errorResponse); - - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else if (request.getMethod() == "DELETE") - { - SWGSDRangel::SWGDeviceState normalResponse; - int status = m_adapter->daemonRunDelete(normalResponse, errorResponse); - - response.setStatus(status); - - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { - response.write(errorResponse.asJson().toUtf8()); - } - } - else - { - response.setStatus(405,"Invalid HTTP method"); - errorResponse.init(); - *errorResponse.getMessage() = "Invalid HTTP method"; - response.write(errorResponse.asJson().toUtf8()); - } -} - -// TODO: put in library in common with SDRangel. Can be static. -bool WebAPIRequestMapper::validateChannelSettings( - SWGSDRangel::SWGChannelSettings& channelSettings, - QJsonObject& jsonObject, - QStringList& channelSettingsKeys) -{ - if (jsonObject.contains("tx")) { - channelSettings.setTx(jsonObject["tx"].toInt()); - } else { - channelSettings.setTx(0); // assume Rx - } - - if (jsonObject.contains("channelType") && jsonObject["channelType"].isString()) { - channelSettings.setChannelType(new QString(jsonObject["channelType"].toString())); - } else { - return false; - } - - QString *channelType = channelSettings.getChannelType(); - - if (*channelType == "AMDemod") - { - if (channelSettings.getTx() == 0) - { - QJsonObject amDemodSettingsJsonObject = jsonObject["AMDemodSettings"].toObject(); - channelSettingsKeys = amDemodSettingsJsonObject.keys(); - channelSettings.setAmDemodSettings(new SWGSDRangel::SWGAMDemodSettings()); - channelSettings.getAmDemodSettings()->fromJsonObject(amDemodSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "AMMod") - { - if (channelSettings.getTx() != 0) - { - QJsonObject amModSettingsJsonObject = jsonObject["AMModSettings"].toObject(); - channelSettingsKeys = amModSettingsJsonObject.keys(); - - if (channelSettingsKeys.contains("cwKeyer")) - { - QJsonObject cwKeyerSettingsJsonObject; - appendSettingsSubKeys(amModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); - } - - channelSettings.setAmModSettings(new SWGSDRangel::SWGAMModSettings()); - channelSettings.getAmModSettings()->fromJsonObject(amModSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "ATVMod") - { - if (channelSettings.getTx() != 0) - { - QJsonObject atvModSettingsJsonObject = jsonObject["ATVModSettings"].toObject(); - channelSettingsKeys = atvModSettingsJsonObject.keys(); - channelSettings.setAtvModSettings(new SWGSDRangel::SWGATVModSettings()); - channelSettings.getAtvModSettings()->fromJsonObject(atvModSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "BFMDemod") - { - if (channelSettings.getTx() == 0) - { - QJsonObject bfmDemodSettingsJsonObject = jsonObject["BFMDemodSettings"].toObject(); - channelSettingsKeys = bfmDemodSettingsJsonObject.keys(); - channelSettings.setBfmDemodSettings(new SWGSDRangel::SWGBFMDemodSettings()); - channelSettings.getBfmDemodSettings()->fromJsonObject(bfmDemodSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "DSDDemod") - { - if (channelSettings.getTx() == 0) - { - QJsonObject dsdDemodSettingsJsonObject = jsonObject["DSDDemodSettings"].toObject(); - channelSettingsKeys = dsdDemodSettingsJsonObject.keys(); - channelSettings.setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings()); - channelSettings.getDsdDemodSettings()->fromJsonObject(dsdDemodSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "NFMDemod") - { - if (channelSettings.getTx() == 0) - { - QJsonObject nfmDemodSettingsJsonObject = jsonObject["NFMDemodSettings"].toObject(); - channelSettingsKeys = nfmDemodSettingsJsonObject.keys(); - channelSettings.setNfmDemodSettings(new SWGSDRangel::SWGNFMDemodSettings()); - channelSettings.getNfmDemodSettings()->fromJsonObject(nfmDemodSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "NFMMod") - { - if (channelSettings.getTx() != 0) - { - QJsonObject nfmModSettingsJsonObject = jsonObject["NFMModSettings"].toObject(); - channelSettingsKeys = nfmModSettingsJsonObject.keys(); - - if (channelSettingsKeys.contains("cwKeyer")) - { - QJsonObject cwKeyerSettingsJsonObject; - appendSettingsSubKeys(nfmModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); - } - - channelSettings.setNfmModSettings(new SWGSDRangel::SWGNFMModSettings()); - channelSettings.getNfmModSettings()->fromJsonObject(nfmModSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "SDRDaemonChannelSink") - { - if (channelSettings.getTx() == 0) - { - QJsonObject sdrDaemonChannelSinkSettingsJsonObject = jsonObject["SDRDaemonChannelSinkSettings"].toObject(); - channelSettingsKeys = sdrDaemonChannelSinkSettingsJsonObject.keys(); - channelSettings.setDaemonSinkSettings(new SWGSDRangel::SWGDaemonSinkSettings()); - channelSettings.getDaemonSinkSettings()->fromJsonObject(sdrDaemonChannelSinkSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "SSBDemod") - { - if (channelSettings.getTx() == 0) - { - QJsonObject ssbDemodSettingsJsonObject = jsonObject["SSBDemodSettings"].toObject(); - channelSettingsKeys = ssbDemodSettingsJsonObject.keys(); - channelSettings.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings()); - channelSettings.getSsbDemodSettings()->fromJsonObject(ssbDemodSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "SSBMod") - { - if (channelSettings.getTx() != 0) - { - QJsonObject ssbModSettingsJsonObject = jsonObject["SSBModSettings"].toObject(); - channelSettingsKeys = ssbModSettingsJsonObject.keys(); - - if (channelSettingsKeys.contains("cwKeyer")) - { - QJsonObject cwKeyerSettingsJsonObject; - appendSettingsSubKeys(ssbModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); - } - - channelSettings.setSsbModSettings(new SWGSDRangel::SWGSSBModSettings()); - channelSettings.getSsbModSettings()->fromJsonObject(ssbModSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "UDPSink") - { - if (channelSettings.getTx() != 0) - { - QJsonObject udpSinkSettingsJsonObject = jsonObject["UDPSinkSettings"].toObject(); - channelSettingsKeys = udpSinkSettingsJsonObject.keys(); - channelSettings.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); - channelSettings.getUdpSinkSettings()->fromJsonObject(udpSinkSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "UDPSrc") - { - if (channelSettings.getTx() == 0) - { - QJsonObject udpSrcSettingsJsonObject = jsonObject["UDPSrcSettings"].toObject(); - channelSettingsKeys = udpSrcSettingsJsonObject.keys(); - channelSettings.setUdpSrcSettings(new SWGSDRangel::SWGUDPSrcSettings()); - channelSettings.getUdpSrcSettings()->fromJsonObject(udpSrcSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "WFMDemod") - { - if (channelSettings.getTx() == 0) - { - QJsonObject wfmDemodSettingsJsonObject = jsonObject["WFMDemodSettings"].toObject(); - channelSettingsKeys = wfmDemodSettingsJsonObject.keys(); - channelSettings.setWfmDemodSettings(new SWGSDRangel::SWGWFMDemodSettings()); - channelSettings.getWfmDemodSettings()->fromJsonObject(wfmDemodSettingsJsonObject); - return true; - } - else { - return false; - } - } - else if (*channelType == "WFMMod") - { - if (channelSettings.getTx() != 0) - { - QJsonObject wfmModSettingsJsonObject = jsonObject["WFMModSettings"].toObject(); - channelSettingsKeys = wfmModSettingsJsonObject.keys(); - - if (channelSettingsKeys.contains("cwKeyer")) - { - QJsonObject cwKeyerSettingsJsonObject; - appendSettingsSubKeys(wfmModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); - } - - channelSettings.setWfmModSettings(new SWGSDRangel::SWGWFMModSettings()); - channelSettings.getWfmModSettings()->fromJsonObject(wfmModSettingsJsonObject); - return true; - } - else { - return false; - } - } - else - { - return false; - } -} - -// TODO: put in library in common with SDRangel. Can be static. -bool WebAPIRequestMapper::validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys) -{ - if (jsonObject.contains("tx")) { - deviceSettings.setTx(jsonObject["tx"].toInt()); - } else { - deviceSettings.setTx(0); // assume Rx - } - - if (jsonObject.contains("deviceHwType") && jsonObject["deviceHwType"].isString()) { - deviceSettings.setDeviceHwType(new QString(jsonObject["deviceHwType"].toString())); - } else { - return false; - } - - QString *deviceHwType = deviceSettings.getDeviceHwType(); - - if ((*deviceHwType == "Airspy") && (deviceSettings.getTx() == 0)) - { - if (jsonObject.contains("airspySettings") && jsonObject["airspySettings"].isObject()) - { - QJsonObject airspySettingsJsonObject = jsonObject["airspySettings"].toObject(); - deviceSettingsKeys = airspySettingsJsonObject.keys(); - deviceSettings.setAirspySettings(new SWGSDRangel::SWGAirspySettings()); - deviceSettings.getAirspySettings()->fromJsonObject(airspySettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "AirspyHF") && (deviceSettings.getTx() == 0)) - { - if (jsonObject.contains("airspyHFSettings") && jsonObject["airspyHFSettings"].isObject()) - { - QJsonObject airspyHFSettingsJsonObject = jsonObject["airspyHFSettings"].toObject(); - deviceSettingsKeys = airspyHFSettingsJsonObject.keys(); - deviceSettings.setAirspyHfSettings(new SWGSDRangel::SWGAirspyHFSettings()); - deviceSettings.getAirspyHfSettings()->fromJsonObject(airspyHFSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "BladeRF") && (deviceSettings.getTx() == 0)) - { - if (jsonObject.contains("bladeRFInputSettings") && jsonObject["bladeRFInputSettings"].isObject()) - { - QJsonObject bladeRFInputSettingsJsonObject = jsonObject["bladeRFInputSettings"].toObject(); - deviceSettingsKeys = bladeRFInputSettingsJsonObject.keys(); - deviceSettings.setBladeRfInputSettings(new SWGSDRangel::SWGBladeRFInputSettings()); - deviceSettings.getBladeRfInputSettings()->fromJsonObject(bladeRFInputSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "BladeRF") && (deviceSettings.getTx() != 0)) - { - if (jsonObject.contains("bladeRFOutputSettings") && jsonObject["bladeRFOutputSettings"].isObject()) - { - QJsonObject bladeRFOutputSettingsJsonObject = jsonObject["bladeRFOutputSettings"].toObject(); - deviceSettingsKeys = bladeRFOutputSettingsJsonObject.keys(); - deviceSettings.setBladeRfOutputSettings(new SWGSDRangel::SWGBladeRFOutputSettings()); - deviceSettings.getBladeRfOutputSettings()->fromJsonObject(bladeRFOutputSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if (*deviceHwType == "FCDPro") - { - if (jsonObject.contains("fcdProSettings") && jsonObject["fcdProSettings"].isObject()) - { - QJsonObject fcdProSettingsJsonObject = jsonObject["fcdProSettings"].toObject(); - deviceSettingsKeys = fcdProSettingsJsonObject.keys(); - deviceSettings.setFcdProSettings(new SWGSDRangel::SWGFCDProSettings()); - deviceSettings.getFcdProSettings()->fromJsonObject(fcdProSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if (*deviceHwType == "FCDProPlus") - { - if (jsonObject.contains("fcdProPlusSettings") && jsonObject["fcdProPlusSettings"].isObject()) - { - QJsonObject fcdProPlusSettingsJsonObject = jsonObject["fcdProPlusSettings"].toObject(); - deviceSettingsKeys = fcdProPlusSettingsJsonObject.keys(); - deviceSettings.setFcdProPlusSettings(new SWGSDRangel::SWGFCDProPlusSettings()); - deviceSettings.getFcdProPlusSettings()->fromJsonObject(fcdProPlusSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if (*deviceHwType == "FileSource") - { - if (jsonObject.contains("fileSourceSettings") && jsonObject["fileSourceSettings"].isObject()) - { - QJsonObject fileSourceSettingsJsonObject = jsonObject["fileSourceSettings"].toObject(); - deviceSettingsKeys = fileSourceSettingsJsonObject.keys(); - deviceSettings.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings()); - deviceSettings.getFileSourceSettings()->fromJsonObject(fileSourceSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "HackRF") && (deviceSettings.getTx() == 0)) - { - if (jsonObject.contains("hackRFInputSettings") && jsonObject["hackRFInputSettings"].isObject()) - { - QJsonObject hackRFInputSettingsJsonObject = jsonObject["hackRFInputSettings"].toObject(); - deviceSettingsKeys = hackRFInputSettingsJsonObject.keys(); - deviceSettings.setHackRfInputSettings(new SWGSDRangel::SWGHackRFInputSettings()); - deviceSettings.getHackRfInputSettings()->fromJsonObject(hackRFInputSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "HackRF") && (deviceSettings.getTx() != 0)) - { - if (jsonObject.contains("hackRFOutputSettings") && jsonObject["hackRFOutputSettings"].isObject()) - { - QJsonObject hackRFOutputSettingsJsonObject = jsonObject["hackRFOutputSettings"].toObject(); - deviceSettingsKeys = hackRFOutputSettingsJsonObject.keys(); - deviceSettings.setHackRfOutputSettings(new SWGSDRangel::SWGHackRFOutputSettings()); - deviceSettings.getHackRfOutputSettings()->fromJsonObject(hackRFOutputSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "LimeSDR") && (deviceSettings.getTx() == 0)) - { - if (jsonObject.contains("limeSdrInputSettings") && jsonObject["limeSdrInputSettings"].isObject()) - { - QJsonObject limeSdrInputSettingsJsonObject = jsonObject["limeSdrInputSettings"].toObject(); - deviceSettingsKeys = limeSdrInputSettingsJsonObject.keys(); - deviceSettings.setLimeSdrInputSettings(new SWGSDRangel::SWGLimeSdrInputSettings()); - deviceSettings.getLimeSdrInputSettings()->fromJsonObject(limeSdrInputSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "LimeSDR") && (deviceSettings.getTx() != 0)) - { - if (jsonObject.contains("limeSdrOutputSettings") && jsonObject["limeSdrOutputSettings"].isObject()) - { - QJsonObject limeSdrOutputSettingsJsonObject = jsonObject["limeSdrOutputSettings"].toObject(); - deviceSettingsKeys = limeSdrOutputSettingsJsonObject.keys(); - deviceSettings.setLimeSdrOutputSettings(new SWGSDRangel::SWGLimeSdrOutputSettings()); - deviceSettings.getLimeSdrOutputSettings()->fromJsonObject(limeSdrOutputSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if (*deviceHwType == "Perseus") - { - if (jsonObject.contains("perseusSettings") && jsonObject["perseusSettings"].isObject()) - { - QJsonObject perseusSettingsJsonObject = jsonObject["perseusSettings"].toObject(); - deviceSettingsKeys = perseusSettingsJsonObject.keys(); - deviceSettings.setPerseusSettings(new SWGSDRangel::SWGPerseusSettings()); - deviceSettings.getPerseusSettings()->fromJsonObject(perseusSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "PlutoSDR") && (deviceSettings.getTx() == 0)) - { - if (jsonObject.contains("plutoSdrInputSettings") && jsonObject["plutoSdrInputSettings"].isObject()) - { - QJsonObject plutoSdrInputSettingsJsonObject = jsonObject["plutoSdrInputSettings"].toObject(); - deviceSettingsKeys = plutoSdrInputSettingsJsonObject.keys(); - deviceSettings.setPlutoSdrInputSettings(new SWGSDRangel::SWGPlutoSdrInputSettings()); - deviceSettings.getPlutoSdrInputSettings()->fromJsonObject(plutoSdrInputSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if ((*deviceHwType == "PlutoSDR") && (deviceSettings.getTx() != 0)) - { - if (jsonObject.contains("plutoSdrOutputSettings") && jsonObject["plutoSdrOutputSettings"].isObject()) - { - QJsonObject plutoSdrOutputSettingsJsonObject = jsonObject["plutoSdrOutputSettings"].toObject(); - deviceSettingsKeys = plutoSdrOutputSettingsJsonObject.keys(); - deviceSettings.setPlutoSdrOutputSettings(new SWGSDRangel::SWGPlutoSdrOutputSettings()); - deviceSettings.getPlutoSdrOutputSettings()->fromJsonObject(plutoSdrOutputSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if (*deviceHwType == "RTLSDR") - { - if (jsonObject.contains("rtlSdrSettings") && jsonObject["rtlSdrSettings"].isObject()) - { - QJsonObject rtlSdrSettingsJsonObject = jsonObject["rtlSdrSettings"].toObject(); - deviceSettingsKeys = rtlSdrSettingsJsonObject.keys(); - deviceSettings.setRtlSdrSettings(new SWGSDRangel::SWGRtlSdrSettings()); - deviceSettings.getRtlSdrSettings()->fromJsonObject(rtlSdrSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else if (*deviceHwType == "TestSource") - { - if (jsonObject.contains("testSourceSettings") && jsonObject["testSourceSettings"].isObject()) - { - QJsonObject testSourceSettingsJsonObject = jsonObject["testSourceSettings"].toObject(); - deviceSettingsKeys = testSourceSettingsJsonObject.keys(); - deviceSettings.setTestSourceSettings(new SWGSDRangel::SWGTestSourceSettings()); - deviceSettings.getTestSourceSettings()->fromJsonObject(testSourceSettingsJsonObject); - return true; - } - else - { - return false; - } - } - else - { - return false; - } -} - -// TODO: put in library in common with SDRangel. Can be static. -void WebAPIRequestMapper::appendSettingsSubKeys( - const QJsonObject& parentSettingsJsonObject, - QJsonObject& childSettingsJsonObject, - const QString& parentKey, - QStringList& keyList) -{ - childSettingsJsonObject = parentSettingsJsonObject[parentKey].toObject(); - QStringList childSettingsKeys = childSettingsJsonObject.keys(); - - for (int i = 0; i < childSettingsKeys.size(); i++) { - keyList.append(parentKey + QString(".") + childSettingsKeys.at(i)); - } -} - -// TODO: put in library in common with SDRangel. Can be static. -bool WebAPIRequestMapper::parseJsonBody(QString& jsonStr, QJsonObject& jsonObject, qtwebapp::HttpResponse& response) -{ - SWGSDRangel::SWGErrorResponse errorResponse; - - try - { - QByteArray jsonBytes(jsonStr.toStdString().c_str()); - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error); - - if (error.error == QJsonParseError::NoError) - { - jsonObject = doc.object(); - } - else - { - QString errorMsg = QString("Input JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); - errorResponse.init(); - *errorResponse.getMessage() = errorMsg; - response.setStatus(400, errorMsg.toUtf8()); - response.write(errorResponse.asJson().toUtf8()); - } - - return (error.error == QJsonParseError::NoError); - } - catch (const std::exception& ex) - { - QString errorMsg = QString("Error parsing request: ") + ex.what(); - errorResponse.init(); - *errorResponse.getMessage() = errorMsg; - response.setStatus(500, errorMsg.toUtf8()); - response.write(errorResponse.asJson().toUtf8()); - - return false; - } -} - -// TODO: put in library in common with SDRangel. Can be static. -void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings) -{ - channelSettings.cleanup(); - channelSettings.setChannelType(0); - channelSettings.setAmDemodSettings(0); - channelSettings.setAmModSettings(0); - channelSettings.setAtvModSettings(0); - channelSettings.setBfmDemodSettings(0); - channelSettings.setDsdDemodSettings(0); - channelSettings.setNfmDemodSettings(0); - channelSettings.setNfmModSettings(0); - channelSettings.setDaemonSinkSettings(0); - channelSettings.setDaemonSourceSettings(0); - channelSettings.setSsbDemodSettings(0); - channelSettings.setSsbModSettings(0); - channelSettings.setUdpSinkSettings(0); - channelSettings.setUdpSrcSettings(0); - channelSettings.setWfmDemodSettings(0); - channelSettings.setWfmModSettings(0); -} - -void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& channelReport) -{ - channelReport.cleanup(); - channelReport.setChannelType(0); - channelReport.setAmDemodReport(0); - channelReport.setAmModReport(0); - channelReport.setAtvModReport(0); - channelReport.setBfmDemodReport(0); - channelReport.setDsdDemodReport(0); - channelReport.setNfmDemodReport(0); - channelReport.setNfmModReport(0); - channelReport.setDaemonSourceReport(0); - channelReport.setSsbDemodReport(0); - channelReport.setSsbModReport(0); - channelReport.setUdpSinkReport(0); - channelReport.setUdpSrcReport(0); - channelReport.setWfmDemodReport(0); - channelReport.setWfmModReport(0); -} - -// TODO: put in library in common with SDRangel. Can be static. -void WebAPIRequestMapper::resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings) -{ - deviceSettings.cleanup(); - deviceSettings.setDeviceHwType(0); - deviceSettings.setAirspySettings(0); - deviceSettings.setAirspyHfSettings(0); - deviceSettings.setBladeRfInputSettings(0); - deviceSettings.setBladeRfOutputSettings(0); - deviceSettings.setFcdProPlusSettings(0); - deviceSettings.setFcdProSettings(0); - deviceSettings.setFileSourceSettings(0); - deviceSettings.setHackRfInputSettings(0); - deviceSettings.setHackRfOutputSettings(0); - deviceSettings.setLimeSdrInputSettings(0); - deviceSettings.setLimeSdrOutputSettings(0); - deviceSettings.setPerseusSettings(0); - deviceSettings.setPlutoSdrInputSettings(0); - deviceSettings.setPlutoSdrOutputSettings(0); - deviceSettings.setRtlSdrSettings(0); - deviceSettings.setSdrDaemonSinkSettings(0); - deviceSettings.setSdrDaemonSourceSettings(0); - deviceSettings.setSdrPlaySettings(0); - deviceSettings.setTestSourceSettings(0); -} - -// TODO: put in library in common with SDRangel. Can be static. -void WebAPIRequestMapper::resetDeviceReport(SWGSDRangel::SWGDeviceReport& deviceReport) -{ - deviceReport.cleanup(); - deviceReport.setDeviceHwType(0); - deviceReport.setAirspyHfReport(0); - deviceReport.setAirspyReport(0); - deviceReport.setFileSourceReport(0); - deviceReport.setLimeSdrInputReport(0); - deviceReport.setLimeSdrOutputReport(0); - deviceReport.setPerseusReport(0); - deviceReport.setPlutoSdrInputReport(0); - deviceReport.setPlutoSdrOutputReport(0); - deviceReport.setRtlSdrReport(0); - deviceReport.setSdrDaemonSinkReport(0); - deviceReport.setSdrDaemonSourceReport(0); - deviceReport.setSdrPlayReport(0); -} - -} // namespace SDRDaemon - - diff --git a/sdrdaemon/webapi/webapirequestmapper.h b/sdrdaemon/webapi/webapirequestmapper.h deleted file mode 100644 index 67030891b..000000000 --- a/sdrdaemon/webapi/webapirequestmapper.h +++ /dev/null @@ -1,83 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB. // -// // -// SDRDaemon Swagger server adapter interface // -// // -// 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 SDRDAEMON_WEBAPI_WEBAPIREQUESTMAPPER_H_ -#define SDRDAEMON_WEBAPI_WEBAPIREQUESTMAPPER_H_ - -#include - -#include "httprequesthandler.h" -#include "httprequest.h" -#include "httpresponse.h" -#include "staticfilecontroller.h" - -#include "export.h" - -namespace SWGSDRangel -{ - class SWGChannelSettings; - class SWGChannelReport; - class SWGDeviceSettings; - class SWGDeviceReport; -} - -class WebAPIAdapterDaemon; - -namespace SDRDaemon -{ - -class SDRBASE_API WebAPIRequestMapper : public qtwebapp::HttpRequestHandler { - Q_OBJECT -public: - WebAPIRequestMapper(QObject* parent=0); - ~WebAPIRequestMapper(); - void service(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void setAdapter(WebAPIAdapterDaemon *adapter) { m_adapter = adapter; } - -private: - WebAPIAdapterDaemon *m_adapter; - qtwebapp::StaticFileController *m_staticFileController; - - void daemonInstanceSummaryService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonInstanceLoggingService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonChannelSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonDeviceSettingsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonRunService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonDeviceReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void daemonChannelReportService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - - bool validateChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings, QJsonObject& jsonObject, QStringList& channelSettingsKeys); - bool validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys); - - void appendSettingsSubKeys( - const QJsonObject& parentSettingsJsonObject, - QJsonObject& childSettingsJsonObject, - const QString& parentKey, - QStringList& keyList); - - bool parseJsonBody(QString& jsonStr, QJsonObject& jsonObject, qtwebapp::HttpResponse& response); - - void resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings); - void resetChannelReport(SWGSDRangel::SWGChannelReport& deviceSettings); - void resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings); - void resetDeviceReport(SWGSDRangel::SWGDeviceReport& deviceReport); -}; - -} // namespace SDRdaemon - -#endif /* SDRDAEMON_WEBAPI_WEBAPIREQUESTMAPPER_H_ */ diff --git a/sdrdaemon/webapi/webapiserver.cpp b/sdrdaemon/webapi/webapiserver.cpp deleted file mode 100644 index b76993e61..000000000 --- a/sdrdaemon/webapi/webapiserver.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon Swagger server adapter interface // -// // -// 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 "httplistener.h" -#include "webapirequestmapper.h" -#include "webapiserver.h" - -namespace SDRDaemon { - -WebAPIServer::WebAPIServer(const QString& host, uint16_t port, WebAPIRequestMapper *requestMapper) : - m_requestMapper(requestMapper), - m_listener(0) -{ - m_settings.host = host; - m_settings.port = port; -} - -WebAPIServer::~WebAPIServer() -{ - if (m_listener) { delete m_listener; } -} - -void WebAPIServer::start() -{ - if (!m_listener) - { - m_listener = new qtwebapp::HttpListener(m_settings, m_requestMapper, qApp); - qInfo("WebAPIServer::start: starting web API server at http://%s:%d", qPrintable(m_settings.host), m_settings.port); - } -} - -void WebAPIServer::stop() -{ - if (m_listener) - { - delete m_listener; - m_listener = 0; - qInfo("WebAPIServer::stop: stopped web API server at http://%s:%d", qPrintable(m_settings.host), m_settings.port); - } -} - -void WebAPIServer::setHostAndPort(const QString& host, uint16_t port) -{ - stop(); - m_settings.host = host; - m_settings.port = port; - m_listener = new qtwebapp::HttpListener(m_settings, m_requestMapper, qApp); -} - -} // namespace SDRdaemon diff --git a/sdrdaemon/webapi/webapiserver.h b/sdrdaemon/webapi/webapiserver.h deleted file mode 100644 index a337412e6..000000000 --- a/sdrdaemon/webapi/webapiserver.h +++ /dev/null @@ -1,56 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB. // -// // -// SDRdaemon Swagger server adapter interface // -// // -// 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 SDRDAEMON_WEBAPI_WEBAPISERVER_H_ -#define SDRDAEMON_WEBAPI_WEBAPISERVER_H_ - -#include -#include "export.h" - -namespace qtwebapp -{ - class HttpListener; - class HttpListenerSettings; -} - -namespace SDRDaemon { - -class WebAPIRequestMapper; - -class SDRBASE_API WebAPIServer -{ -public: - WebAPIServer(const QString& host, uint16_t port, WebAPIRequestMapper *requestMapper); - ~WebAPIServer(); - - void start(); - void stop(); - - void setHostAndPort(const QString& host, uint16_t port); - const QString& getHost() const { return m_settings.host; } - int getPort() const { return m_settings.port; } - -private: - WebAPIRequestMapper *m_requestMapper; - qtwebapp::HttpListener *m_listener; - qtwebapp::HttpListenerSettings m_settings; -}; - -} // namespace SDRdaemon - -#endif /* SDRDAEMON_WEBAPI_WEBAPISERVER_H_ */ From 86598d1bd85b993c7e77a12a37ee0558109fb63d Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 14:44:03 +0200 Subject: [PATCH 730/956] REST API: cleanup of old generated code --- sdrbase/resources/webapi/doc/html2/index.html | 2 +- sdrbase/webapi/webapirequestmapper.cpp | 1 - sdrgui/webapi/webapiadaptergui.cpp | 1 - sdrsrv/webapi/webapiadaptersrv.cpp | 1 - swagger/sdrangel/code/html2/index.html | 2 +- .../client/SWGAirspyReport_sampleRates.cpp | 106 ------ .../qt5/client/SWGAirspyReport_sampleRates.h | 58 ---- .../code/qt5/client/SWGAudioDevice.cpp | 108 ------ .../sdrangel/code/qt5/client/SWGAudioDevice.h | 59 ---- .../code/qt5/client/SWGAudioDevicesSelect.cpp | 148 -------- .../code/qt5/client/SWGAudioDevicesSelect.h | 70 ---- .../sdrangel/code/qt5/client/SWGDSDDemod.cpp | 85 ----- .../sdrangel/code/qt5/client/SWGDSDDemod.h | 52 --- .../code/qt5/client/SWGRDSFrequency.cpp | 106 ------ .../code/qt5/client/SWGRDSFrequency.h | 58 ---- .../code/qt5/client/SWGRtlSdrReport_gains.cpp | 106 ------ .../code/qt5/client/SWGRtlSdrReport_gains.h | 58 ---- .../SWGSDRDaemonChannelSinkSettings.cpp | 171 ---------- .../client/SWGSDRDaemonChannelSinkSettings.h | 77 ----- .../SWGSDRDaemonChannelSourceReport.cpp | 316 ------------------ .../client/SWGSDRDaemonChannelSourceReport.h | 118 ------- .../SWGSDRDaemonChannelSourceSettings.cpp | 173 ---------- .../SWGSDRDaemonChannelSourceSettings.h | 77 ----- .../client/SWGSDRPlayReport_bandwidths.cpp | 106 ------ .../qt5/client/SWGSDRPlayReport_bandwidths.h | 58 ---- .../SWGSDRPlayReport_frequencyBands.cpp | 150 --------- .../client/SWGSDRPlayReport_frequencyBands.h | 71 ---- ...GSDRPlayReport_intermediateFrequencies.cpp | 106 ------ ...SWGSDRPlayReport_intermediateFrequencies.h | 58 ---- 29 files changed, 2 insertions(+), 2500 deletions(-) delete mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGAudioDevice.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGAudioDevice.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGDSDDemod.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGDSDDemod.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGRDSFrequency.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGRDSFrequency.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.h diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 522745737..9599f8ea7 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -28708,7 +28708,7 @@ except ApiException as e:
    - Generated 2018-09-08T22:13:50.971+02:00 + Generated 2018-09-11T14:35:37.698+02:00
    diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index d4ed9a3e2..12bfa4dc9 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -28,7 +28,6 @@ #include "SWGInstanceDevicesResponse.h" #include "SWGInstanceChannelsResponse.h" #include "SWGAudioDevices.h" -#include "SWGAudioDevicesSelect.h" #include "SWGLocationInformation.h" #include "SWGDVSeralDevices.h" #include "SWGPresets.h" diff --git a/sdrgui/webapi/webapiadaptergui.cpp b/sdrgui/webapi/webapiadaptergui.cpp index 5f62f617d..6cedfc190 100644 --- a/sdrgui/webapi/webapiadaptergui.cpp +++ b/sdrgui/webapi/webapiadaptergui.cpp @@ -42,7 +42,6 @@ #include "SWGInstanceChannelsResponse.h" #include "SWGDeviceListItem.h" #include "SWGAudioDevices.h" -#include "SWGAudioDevicesSelect.h" #include "SWGLocationInformation.h" #include "SWGDVSeralDevices.h" #include "SWGDVSerialDevice.h" diff --git a/sdrsrv/webapi/webapiadaptersrv.cpp b/sdrsrv/webapi/webapiadaptersrv.cpp index d37dbe16c..e097f96ed 100644 --- a/sdrsrv/webapi/webapiadaptersrv.cpp +++ b/sdrsrv/webapi/webapiadaptersrv.cpp @@ -28,7 +28,6 @@ #include "SWGInstanceChannelsResponse.h" #include "SWGLoggingInfo.h" #include "SWGAudioDevices.h" -#include "SWGAudioDevicesSelect.h" #include "SWGLocationInformation.h" #include "SWGDVSeralDevices.h" #include "SWGPresetImport.h" diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 522745737..9599f8ea7 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -28708,7 +28708,7 @@ except ApiException as e:
    - Generated 2018-09-08T22:13:50.971+02:00 + Generated 2018-09-11T14:35:37.698+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.cpp b/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.cpp deleted file mode 100644 index 66dcde762..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGAirspyReport_sampleRates.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGAirspyReport_sampleRates::SWGAirspyReport_sampleRates(QString* json) { - init(); - this->fromJson(*json); -} - -SWGAirspyReport_sampleRates::SWGAirspyReport_sampleRates() { - sample_rate = 0; - m_sample_rate_isSet = false; -} - -SWGAirspyReport_sampleRates::~SWGAirspyReport_sampleRates() { - this->cleanup(); -} - -void -SWGAirspyReport_sampleRates::init() { - sample_rate = 0; - m_sample_rate_isSet = false; -} - -void -SWGAirspyReport_sampleRates::cleanup() { - -} - -SWGAirspyReport_sampleRates* -SWGAirspyReport_sampleRates::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGAirspyReport_sampleRates::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); - -} - -QString -SWGAirspyReport_sampleRates::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGAirspyReport_sampleRates::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(m_sample_rate_isSet){ - obj->insert("sampleRate", QJsonValue(sample_rate)); - } - - return obj; -} - -qint32 -SWGAirspyReport_sampleRates::getSampleRate() { - return sample_rate; -} -void -SWGAirspyReport_sampleRates::setSampleRate(qint32 sample_rate) { - this->sample_rate = sample_rate; - this->m_sample_rate_isSet = true; -} - - -bool -SWGAirspyReport_sampleRates::isSet(){ - bool isObjectUpdated = false; - do{ - if(m_sample_rate_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.h b/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.h deleted file mode 100644 index 47c080614..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGAirspyReport_sampleRates.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGAirspyReport_sampleRates.h - * - * - */ - -#ifndef SWGAirspyReport_sampleRates_H_ -#define SWGAirspyReport_sampleRates_H_ - -#include - - - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGAirspyReport_sampleRates: public SWGObject { -public: - SWGAirspyReport_sampleRates(); - SWGAirspyReport_sampleRates(QString* json); - virtual ~SWGAirspyReport_sampleRates(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGAirspyReport_sampleRates* fromJson(QString &jsonString) override; - - qint32 getSampleRate(); - void setSampleRate(qint32 sample_rate); - - - virtual bool isSet() override; - -private: - qint32 sample_rate; - bool m_sample_rate_isSet; - -}; - -} - -#endif /* SWGAirspyReport_sampleRates_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioDevice.cpp deleted file mode 100644 index 8609d1119..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevice.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGAudioDevice.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGAudioDevice::SWGAudioDevice(QString* json) { - init(); - this->fromJson(*json); -} - -SWGAudioDevice::SWGAudioDevice() { - name = nullptr; - m_name_isSet = false; -} - -SWGAudioDevice::~SWGAudioDevice() { - this->cleanup(); -} - -void -SWGAudioDevice::init() { - name = new QString(""); - m_name_isSet = false; -} - -void -SWGAudioDevice::cleanup() { - if(name != nullptr) { - delete name; - } -} - -SWGAudioDevice* -SWGAudioDevice::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGAudioDevice::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&name, pJson["name"], "QString", "QString"); - -} - -QString -SWGAudioDevice::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGAudioDevice::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(name != nullptr && *name != QString("")){ - toJsonValue(QString("name"), name, obj, QString("QString")); - } - - return obj; -} - -QString* -SWGAudioDevice::getName() { - return name; -} -void -SWGAudioDevice::setName(QString* name) { - this->name = name; - this->m_name_isSet = true; -} - - -bool -SWGAudioDevice::isSet(){ - bool isObjectUpdated = false; - do{ - if(name != nullptr && *name != QString("")){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h deleted file mode 100644 index 796f3d3a8..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h +++ /dev/null @@ -1,59 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGAudioDevice.h - * - * Audio device - */ - -#ifndef SWGAudioDevice_H_ -#define SWGAudioDevice_H_ - -#include - - -#include - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGAudioDevice: public SWGObject { -public: - SWGAudioDevice(); - SWGAudioDevice(QString* json); - virtual ~SWGAudioDevice(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGAudioDevice* fromJson(QString &jsonString) override; - - QString* getName(); - void setName(QString* name); - - - virtual bool isSet() override; - -private: - QString* name; - bool m_name_isSet; - -}; - -} - -#endif /* SWGAudioDevice_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.cpp deleted file mode 100644 index 2ef463365..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGAudioDevicesSelect.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGAudioDevicesSelect::SWGAudioDevicesSelect(QString* json) { - init(); - this->fromJson(*json); -} - -SWGAudioDevicesSelect::SWGAudioDevicesSelect() { - input_volume = 0.0f; - m_input_volume_isSet = false; - input_index = 0; - m_input_index_isSet = false; - output_index = 0; - m_output_index_isSet = false; -} - -SWGAudioDevicesSelect::~SWGAudioDevicesSelect() { - this->cleanup(); -} - -void -SWGAudioDevicesSelect::init() { - input_volume = 0.0f; - m_input_volume_isSet = false; - input_index = 0; - m_input_index_isSet = false; - output_index = 0; - m_output_index_isSet = false; -} - -void -SWGAudioDevicesSelect::cleanup() { - - - -} - -SWGAudioDevicesSelect* -SWGAudioDevicesSelect::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGAudioDevicesSelect::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&input_volume, pJson["inputVolume"], "float", ""); - - ::SWGSDRangel::setValue(&input_index, pJson["inputIndex"], "qint32", ""); - - ::SWGSDRangel::setValue(&output_index, pJson["outputIndex"], "qint32", ""); - -} - -QString -SWGAudioDevicesSelect::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGAudioDevicesSelect::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(m_input_volume_isSet){ - obj->insert("inputVolume", QJsonValue(input_volume)); - } - if(m_input_index_isSet){ - obj->insert("inputIndex", QJsonValue(input_index)); - } - if(m_output_index_isSet){ - obj->insert("outputIndex", QJsonValue(output_index)); - } - - return obj; -} - -float -SWGAudioDevicesSelect::getInputVolume() { - return input_volume; -} -void -SWGAudioDevicesSelect::setInputVolume(float input_volume) { - this->input_volume = input_volume; - this->m_input_volume_isSet = true; -} - -qint32 -SWGAudioDevicesSelect::getInputIndex() { - return input_index; -} -void -SWGAudioDevicesSelect::setInputIndex(qint32 input_index) { - this->input_index = input_index; - this->m_input_index_isSet = true; -} - -qint32 -SWGAudioDevicesSelect::getOutputIndex() { - return output_index; -} -void -SWGAudioDevicesSelect::setOutputIndex(qint32 output_index) { - this->output_index = output_index; - this->m_output_index_isSet = true; -} - - -bool -SWGAudioDevicesSelect::isSet(){ - bool isObjectUpdated = false; - do{ - if(m_input_volume_isSet){ isObjectUpdated = true; break;} - if(m_input_index_isSet){ isObjectUpdated = true; break;} - if(m_output_index_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h deleted file mode 100644 index 1e2b5af55..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h +++ /dev/null @@ -1,70 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGAudioDevicesSelect.h - * - * Audio devices selected - */ - -#ifndef SWGAudioDevicesSelect_H_ -#define SWGAudioDevicesSelect_H_ - -#include - - - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGAudioDevicesSelect: public SWGObject { -public: - SWGAudioDevicesSelect(); - SWGAudioDevicesSelect(QString* json); - virtual ~SWGAudioDevicesSelect(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGAudioDevicesSelect* fromJson(QString &jsonString) override; - - float getInputVolume(); - void setInputVolume(float input_volume); - - qint32 getInputIndex(); - void setInputIndex(qint32 input_index); - - qint32 getOutputIndex(); - void setOutputIndex(qint32 output_index); - - - virtual bool isSet() override; - -private: - float input_volume; - bool m_input_volume_isSet; - - qint32 input_index; - bool m_input_index_isSet; - - qint32 output_index; - bool m_output_index_isSet; - -}; - -} - -#endif /* SWGAudioDevicesSelect_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemod.cpp b/swagger/sdrangel/code/qt5/client/SWGDSDDemod.cpp deleted file mode 100644 index 2bfe85a51..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemod.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGDSDDemod.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGDSDDemod::SWGDSDDemod(QString* json) { - init(); - this->fromJson(*json); -} - -SWGDSDDemod::SWGDSDDemod() { -} - -SWGDSDDemod::~SWGDSDDemod() { - this->cleanup(); -} - -void -SWGDSDDemod::init() { -} - -void -SWGDSDDemod::cleanup() { -} - -SWGDSDDemod* -SWGDSDDemod::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGDSDDemod::fromJsonObject(QJsonObject &pJson) { -} - -QString -SWGDSDDemod::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGDSDDemod::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - - return obj; -} - - -bool -SWGDSDDemod::isSet(){ - bool isObjectUpdated = false; - do{ - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGDSDDemod.h b/swagger/sdrangel/code/qt5/client/SWGDSDDemod.h deleted file mode 100644 index 6bb00ee4a..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGDSDDemod.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGDSDDemod.h - * - * - */ - -#ifndef SWGDSDDemod_H_ -#define SWGDSDDemod_H_ - -#include - - - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGDSDDemod: public SWGObject { -public: - SWGDSDDemod(); - SWGDSDDemod(QString* json); - virtual ~SWGDSDDemod(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGDSDDemod* fromJson(QString &jsonString) override; - - - virtual bool isSet() override; - -private: -}; - -} - -#endif /* SWGDSDDemod_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.cpp b/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.cpp deleted file mode 100644 index ba57f7724..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGRDSFrequency.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGRDSFrequency::SWGRDSFrequency(QString* json) { - init(); - this->fromJson(*json); -} - -SWGRDSFrequency::SWGRDSFrequency() { - frequency = 0.0f; - m_frequency_isSet = false; -} - -SWGRDSFrequency::~SWGRDSFrequency() { - this->cleanup(); -} - -void -SWGRDSFrequency::init() { - frequency = 0.0f; - m_frequency_isSet = false; -} - -void -SWGRDSFrequency::cleanup() { - -} - -SWGRDSFrequency* -SWGRDSFrequency::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGRDSFrequency::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&frequency, pJson["frequency"], "float", ""); - -} - -QString -SWGRDSFrequency::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGRDSFrequency::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(m_frequency_isSet){ - obj->insert("frequency", QJsonValue(frequency)); - } - - return obj; -} - -float -SWGRDSFrequency::getFrequency() { - return frequency; -} -void -SWGRDSFrequency::setFrequency(float frequency) { - this->frequency = frequency; - this->m_frequency_isSet = true; -} - - -bool -SWGRDSFrequency::isSet(){ - bool isObjectUpdated = false; - do{ - if(m_frequency_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.h b/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.h deleted file mode 100644 index 28f8670c3..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGRDSFrequency.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGRDSFrequency.h - * - * Frequency information - */ - -#ifndef SWGRDSFrequency_H_ -#define SWGRDSFrequency_H_ - -#include - - - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGRDSFrequency: public SWGObject { -public: - SWGRDSFrequency(); - SWGRDSFrequency(QString* json); - virtual ~SWGRDSFrequency(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGRDSFrequency* fromJson(QString &jsonString) override; - - float getFrequency(); - void setFrequency(float frequency); - - - virtual bool isSet() override; - -private: - float frequency; - bool m_frequency_isSet; - -}; - -} - -#endif /* SWGRDSFrequency_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.cpp b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.cpp deleted file mode 100644 index 001232465..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGRtlSdrReport_gains.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGRtlSdrReport_gains::SWGRtlSdrReport_gains(QString* json) { - init(); - this->fromJson(*json); -} - -SWGRtlSdrReport_gains::SWGRtlSdrReport_gains() { - gain = 0; - m_gain_isSet = false; -} - -SWGRtlSdrReport_gains::~SWGRtlSdrReport_gains() { - this->cleanup(); -} - -void -SWGRtlSdrReport_gains::init() { - gain = 0; - m_gain_isSet = false; -} - -void -SWGRtlSdrReport_gains::cleanup() { - -} - -SWGRtlSdrReport_gains* -SWGRtlSdrReport_gains::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGRtlSdrReport_gains::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&gain, pJson["gain"], "qint32", ""); - -} - -QString -SWGRtlSdrReport_gains::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGRtlSdrReport_gains::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(m_gain_isSet){ - obj->insert("gain", QJsonValue(gain)); - } - - return obj; -} - -qint32 -SWGRtlSdrReport_gains::getGain() { - return gain; -} -void -SWGRtlSdrReport_gains::setGain(qint32 gain) { - this->gain = gain; - this->m_gain_isSet = true; -} - - -bool -SWGRtlSdrReport_gains::isSet(){ - bool isObjectUpdated = false; - do{ - if(m_gain_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.h deleted file mode 100644 index b876573ca..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrReport_gains.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGRtlSdrReport_gains.h - * - * - */ - -#ifndef SWGRtlSdrReport_gains_H_ -#define SWGRtlSdrReport_gains_H_ - -#include - - - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGRtlSdrReport_gains: public SWGObject { -public: - SWGRtlSdrReport_gains(); - SWGRtlSdrReport_gains(QString* json); - virtual ~SWGRtlSdrReport_gains(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGRtlSdrReport_gains* fromJson(QString &jsonString) override; - - qint32 getGain(); - void setGain(qint32 gain); - - - virtual bool isSet() override; - -private: - qint32 gain; - bool m_gain_isSet; - -}; - -} - -#endif /* SWGRtlSdrReport_gains_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.cpp deleted file mode 100644 index 194d6a747..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGSDRDaemonChannelSinkSettings.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGSDRDaemonChannelSinkSettings::SWGSDRDaemonChannelSinkSettings(QString* json) { - init(); - this->fromJson(*json); -} - -SWGSDRDaemonChannelSinkSettings::SWGSDRDaemonChannelSinkSettings() { - nb_fec_blocks = 0; - m_nb_fec_blocks_isSet = false; - data_address = nullptr; - m_data_address_isSet = false; - data_port = 0; - m_data_port_isSet = false; - tx_delay = 0; - m_tx_delay_isSet = false; -} - -SWGSDRDaemonChannelSinkSettings::~SWGSDRDaemonChannelSinkSettings() { - this->cleanup(); -} - -void -SWGSDRDaemonChannelSinkSettings::init() { - nb_fec_blocks = 0; - m_nb_fec_blocks_isSet = false; - data_address = new QString(""); - m_data_address_isSet = false; - data_port = 0; - m_data_port_isSet = false; - tx_delay = 0; - m_tx_delay_isSet = false; -} - -void -SWGSDRDaemonChannelSinkSettings::cleanup() { - - if(data_address != nullptr) { - delete data_address; - } - - -} - -SWGSDRDaemonChannelSinkSettings* -SWGSDRDaemonChannelSinkSettings::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGSDRDaemonChannelSinkSettings::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); - - ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); - - ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); - - ::SWGSDRangel::setValue(&tx_delay, pJson["txDelay"], "qint32", ""); - -} - -QString -SWGSDRDaemonChannelSinkSettings::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGSDRDaemonChannelSinkSettings::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(m_nb_fec_blocks_isSet){ - obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); - } - if(data_address != nullptr && *data_address != QString("")){ - toJsonValue(QString("dataAddress"), data_address, obj, QString("QString")); - } - if(m_data_port_isSet){ - obj->insert("dataPort", QJsonValue(data_port)); - } - if(m_tx_delay_isSet){ - obj->insert("txDelay", QJsonValue(tx_delay)); - } - - return obj; -} - -qint32 -SWGSDRDaemonChannelSinkSettings::getNbFecBlocks() { - return nb_fec_blocks; -} -void -SWGSDRDaemonChannelSinkSettings::setNbFecBlocks(qint32 nb_fec_blocks) { - this->nb_fec_blocks = nb_fec_blocks; - this->m_nb_fec_blocks_isSet = true; -} - -QString* -SWGSDRDaemonChannelSinkSettings::getDataAddress() { - return data_address; -} -void -SWGSDRDaemonChannelSinkSettings::setDataAddress(QString* data_address) { - this->data_address = data_address; - this->m_data_address_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSinkSettings::getDataPort() { - return data_port; -} -void -SWGSDRDaemonChannelSinkSettings::setDataPort(qint32 data_port) { - this->data_port = data_port; - this->m_data_port_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSinkSettings::getTxDelay() { - return tx_delay; -} -void -SWGSDRDaemonChannelSinkSettings::setTxDelay(qint32 tx_delay) { - this->tx_delay = tx_delay; - this->m_tx_delay_isSet = true; -} - - -bool -SWGSDRDaemonChannelSinkSettings::isSet(){ - bool isObjectUpdated = false; - do{ - if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} - if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} - if(m_data_port_isSet){ isObjectUpdated = true; break;} - if(m_tx_delay_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.h deleted file mode 100644 index 5b7d56fb2..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSinkSettings.h +++ /dev/null @@ -1,77 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGSDRDaemonChannelSinkSettings.h - * - * Data handling details for SDRDaemon - */ - -#ifndef SWGSDRDaemonChannelSinkSettings_H_ -#define SWGSDRDaemonChannelSinkSettings_H_ - -#include - - -#include - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGSDRDaemonChannelSinkSettings: public SWGObject { -public: - SWGSDRDaemonChannelSinkSettings(); - SWGSDRDaemonChannelSinkSettings(QString* json); - virtual ~SWGSDRDaemonChannelSinkSettings(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGSDRDaemonChannelSinkSettings* fromJson(QString &jsonString) override; - - qint32 getNbFecBlocks(); - void setNbFecBlocks(qint32 nb_fec_blocks); - - QString* getDataAddress(); - void setDataAddress(QString* data_address); - - qint32 getDataPort(); - void setDataPort(qint32 data_port); - - qint32 getTxDelay(); - void setTxDelay(qint32 tx_delay); - - - virtual bool isSet() override; - -private: - qint32 nb_fec_blocks; - bool m_nb_fec_blocks_isSet; - - QString* data_address; - bool m_data_address_isSet; - - qint32 data_port; - bool m_data_port_isSet; - - qint32 tx_delay; - bool m_tx_delay_isSet; - -}; - -} - -#endif /* SWGSDRDaemonChannelSinkSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp deleted file mode 100644 index 8e4c213ea..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.cpp +++ /dev/null @@ -1,316 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGSDRDaemonChannelSourceReport.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGSDRDaemonChannelSourceReport::SWGSDRDaemonChannelSourceReport(QString* json) { - init(); - this->fromJson(*json); -} - -SWGSDRDaemonChannelSourceReport::SWGSDRDaemonChannelSourceReport() { - queue_length = 0; - m_queue_length_isSet = false; - queue_size = 0; - m_queue_size_isSet = false; - samples_count = 0; - m_samples_count_isSet = false; - correctable_errors_count = 0; - m_correctable_errors_count_isSet = false; - uncorrectable_errors_count = 0; - m_uncorrectable_errors_count_isSet = false; - tv_sec = 0; - m_tv_sec_isSet = false; - tv_u_sec = 0; - m_tv_u_sec_isSet = false; - nb_original_blocks = 0; - m_nb_original_blocks_isSet = false; - nb_fec_blocks = 0; - m_nb_fec_blocks_isSet = false; - center_freq = 0; - m_center_freq_isSet = false; - sample_rate = 0; - m_sample_rate_isSet = false; -} - -SWGSDRDaemonChannelSourceReport::~SWGSDRDaemonChannelSourceReport() { - this->cleanup(); -} - -void -SWGSDRDaemonChannelSourceReport::init() { - queue_length = 0; - m_queue_length_isSet = false; - queue_size = 0; - m_queue_size_isSet = false; - samples_count = 0; - m_samples_count_isSet = false; - correctable_errors_count = 0; - m_correctable_errors_count_isSet = false; - uncorrectable_errors_count = 0; - m_uncorrectable_errors_count_isSet = false; - tv_sec = 0; - m_tv_sec_isSet = false; - tv_u_sec = 0; - m_tv_u_sec_isSet = false; - nb_original_blocks = 0; - m_nb_original_blocks_isSet = false; - nb_fec_blocks = 0; - m_nb_fec_blocks_isSet = false; - center_freq = 0; - m_center_freq_isSet = false; - sample_rate = 0; - m_sample_rate_isSet = false; -} - -void -SWGSDRDaemonChannelSourceReport::cleanup() { - - - - - - - - - - - -} - -SWGSDRDaemonChannelSourceReport* -SWGSDRDaemonChannelSourceReport::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGSDRDaemonChannelSourceReport::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&queue_length, pJson["queueLength"], "qint32", ""); - - ::SWGSDRangel::setValue(&queue_size, pJson["queueSize"], "qint32", ""); - - ::SWGSDRangel::setValue(&samples_count, pJson["samplesCount"], "qint32", ""); - - ::SWGSDRangel::setValue(&correctable_errors_count, pJson["correctableErrorsCount"], "qint32", ""); - - ::SWGSDRangel::setValue(&uncorrectable_errors_count, pJson["uncorrectableErrorsCount"], "qint32", ""); - - ::SWGSDRangel::setValue(&tv_sec, pJson["tvSec"], "qint32", ""); - - ::SWGSDRangel::setValue(&tv_u_sec, pJson["tvUSec"], "qint32", ""); - - ::SWGSDRangel::setValue(&nb_original_blocks, pJson["nbOriginalBlocks"], "qint32", ""); - - ::SWGSDRangel::setValue(&nb_fec_blocks, pJson["nbFECBlocks"], "qint32", ""); - - ::SWGSDRangel::setValue(¢er_freq, pJson["centerFreq"], "qint32", ""); - - ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); - -} - -QString -SWGSDRDaemonChannelSourceReport::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGSDRDaemonChannelSourceReport::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(m_queue_length_isSet){ - obj->insert("queueLength", QJsonValue(queue_length)); - } - if(m_queue_size_isSet){ - obj->insert("queueSize", QJsonValue(queue_size)); - } - if(m_samples_count_isSet){ - obj->insert("samplesCount", QJsonValue(samples_count)); - } - if(m_correctable_errors_count_isSet){ - obj->insert("correctableErrorsCount", QJsonValue(correctable_errors_count)); - } - if(m_uncorrectable_errors_count_isSet){ - obj->insert("uncorrectableErrorsCount", QJsonValue(uncorrectable_errors_count)); - } - if(m_tv_sec_isSet){ - obj->insert("tvSec", QJsonValue(tv_sec)); - } - if(m_tv_u_sec_isSet){ - obj->insert("tvUSec", QJsonValue(tv_u_sec)); - } - if(m_nb_original_blocks_isSet){ - obj->insert("nbOriginalBlocks", QJsonValue(nb_original_blocks)); - } - if(m_nb_fec_blocks_isSet){ - obj->insert("nbFECBlocks", QJsonValue(nb_fec_blocks)); - } - if(m_center_freq_isSet){ - obj->insert("centerFreq", QJsonValue(center_freq)); - } - if(m_sample_rate_isSet){ - obj->insert("sampleRate", QJsonValue(sample_rate)); - } - - return obj; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getQueueLength() { - return queue_length; -} -void -SWGSDRDaemonChannelSourceReport::setQueueLength(qint32 queue_length) { - this->queue_length = queue_length; - this->m_queue_length_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getQueueSize() { - return queue_size; -} -void -SWGSDRDaemonChannelSourceReport::setQueueSize(qint32 queue_size) { - this->queue_size = queue_size; - this->m_queue_size_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getSamplesCount() { - return samples_count; -} -void -SWGSDRDaemonChannelSourceReport::setSamplesCount(qint32 samples_count) { - this->samples_count = samples_count; - this->m_samples_count_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getCorrectableErrorsCount() { - return correctable_errors_count; -} -void -SWGSDRDaemonChannelSourceReport::setCorrectableErrorsCount(qint32 correctable_errors_count) { - this->correctable_errors_count = correctable_errors_count; - this->m_correctable_errors_count_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getUncorrectableErrorsCount() { - return uncorrectable_errors_count; -} -void -SWGSDRDaemonChannelSourceReport::setUncorrectableErrorsCount(qint32 uncorrectable_errors_count) { - this->uncorrectable_errors_count = uncorrectable_errors_count; - this->m_uncorrectable_errors_count_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getTvSec() { - return tv_sec; -} -void -SWGSDRDaemonChannelSourceReport::setTvSec(qint32 tv_sec) { - this->tv_sec = tv_sec; - this->m_tv_sec_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getTvUSec() { - return tv_u_sec; -} -void -SWGSDRDaemonChannelSourceReport::setTvUSec(qint32 tv_u_sec) { - this->tv_u_sec = tv_u_sec; - this->m_tv_u_sec_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getNbOriginalBlocks() { - return nb_original_blocks; -} -void -SWGSDRDaemonChannelSourceReport::setNbOriginalBlocks(qint32 nb_original_blocks) { - this->nb_original_blocks = nb_original_blocks; - this->m_nb_original_blocks_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getNbFecBlocks() { - return nb_fec_blocks; -} -void -SWGSDRDaemonChannelSourceReport::setNbFecBlocks(qint32 nb_fec_blocks) { - this->nb_fec_blocks = nb_fec_blocks; - this->m_nb_fec_blocks_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getCenterFreq() { - return center_freq; -} -void -SWGSDRDaemonChannelSourceReport::setCenterFreq(qint32 center_freq) { - this->center_freq = center_freq; - this->m_center_freq_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceReport::getSampleRate() { - return sample_rate; -} -void -SWGSDRDaemonChannelSourceReport::setSampleRate(qint32 sample_rate) { - this->sample_rate = sample_rate; - this->m_sample_rate_isSet = true; -} - - -bool -SWGSDRDaemonChannelSourceReport::isSet(){ - bool isObjectUpdated = false; - do{ - if(m_queue_length_isSet){ isObjectUpdated = true; break;} - if(m_queue_size_isSet){ isObjectUpdated = true; break;} - if(m_samples_count_isSet){ isObjectUpdated = true; break;} - if(m_correctable_errors_count_isSet){ isObjectUpdated = true; break;} - if(m_uncorrectable_errors_count_isSet){ isObjectUpdated = true; break;} - if(m_tv_sec_isSet){ isObjectUpdated = true; break;} - if(m_tv_u_sec_isSet){ isObjectUpdated = true; break;} - if(m_nb_original_blocks_isSet){ isObjectUpdated = true; break;} - if(m_nb_fec_blocks_isSet){ isObjectUpdated = true; break;} - if(m_center_freq_isSet){ isObjectUpdated = true; break;} - if(m_sample_rate_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h deleted file mode 100644 index 6438c12ef..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceReport.h +++ /dev/null @@ -1,118 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGSDRDaemonChannelSourceReport.h - * - * SDRDaemon channel source report - */ - -#ifndef SWGSDRDaemonChannelSourceReport_H_ -#define SWGSDRDaemonChannelSourceReport_H_ - -#include - - - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGSDRDaemonChannelSourceReport: public SWGObject { -public: - SWGSDRDaemonChannelSourceReport(); - SWGSDRDaemonChannelSourceReport(QString* json); - virtual ~SWGSDRDaemonChannelSourceReport(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGSDRDaemonChannelSourceReport* fromJson(QString &jsonString) override; - - qint32 getQueueLength(); - void setQueueLength(qint32 queue_length); - - qint32 getQueueSize(); - void setQueueSize(qint32 queue_size); - - qint32 getSamplesCount(); - void setSamplesCount(qint32 samples_count); - - qint32 getCorrectableErrorsCount(); - void setCorrectableErrorsCount(qint32 correctable_errors_count); - - qint32 getUncorrectableErrorsCount(); - void setUncorrectableErrorsCount(qint32 uncorrectable_errors_count); - - qint32 getTvSec(); - void setTvSec(qint32 tv_sec); - - qint32 getTvUSec(); - void setTvUSec(qint32 tv_u_sec); - - qint32 getNbOriginalBlocks(); - void setNbOriginalBlocks(qint32 nb_original_blocks); - - qint32 getNbFecBlocks(); - void setNbFecBlocks(qint32 nb_fec_blocks); - - qint32 getCenterFreq(); - void setCenterFreq(qint32 center_freq); - - qint32 getSampleRate(); - void setSampleRate(qint32 sample_rate); - - - virtual bool isSet() override; - -private: - qint32 queue_length; - bool m_queue_length_isSet; - - qint32 queue_size; - bool m_queue_size_isSet; - - qint32 samples_count; - bool m_samples_count_isSet; - - qint32 correctable_errors_count; - bool m_correctable_errors_count_isSet; - - qint32 uncorrectable_errors_count; - bool m_uncorrectable_errors_count_isSet; - - qint32 tv_sec; - bool m_tv_sec_isSet; - - qint32 tv_u_sec; - bool m_tv_u_sec_isSet; - - qint32 nb_original_blocks; - bool m_nb_original_blocks_isSet; - - qint32 nb_fec_blocks; - bool m_nb_fec_blocks_isSet; - - qint32 center_freq; - bool m_center_freq_isSet; - - qint32 sample_rate; - bool m_sample_rate_isSet; - -}; - -} - -#endif /* SWGSDRDaemonChannelSourceReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp deleted file mode 100644 index a9da57775..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGSDRDaemonChannelSourceSettings.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGSDRDaemonChannelSourceSettings::SWGSDRDaemonChannelSourceSettings(QString* json) { - init(); - this->fromJson(*json); -} - -SWGSDRDaemonChannelSourceSettings::SWGSDRDaemonChannelSourceSettings() { - data_address = nullptr; - m_data_address_isSet = false; - data_port = 0; - m_data_port_isSet = false; - rgb_color = 0; - m_rgb_color_isSet = false; - title = nullptr; - m_title_isSet = false; -} - -SWGSDRDaemonChannelSourceSettings::~SWGSDRDaemonChannelSourceSettings() { - this->cleanup(); -} - -void -SWGSDRDaemonChannelSourceSettings::init() { - data_address = new QString(""); - m_data_address_isSet = false; - data_port = 0; - m_data_port_isSet = false; - rgb_color = 0; - m_rgb_color_isSet = false; - title = new QString(""); - m_title_isSet = false; -} - -void -SWGSDRDaemonChannelSourceSettings::cleanup() { - if(data_address != nullptr) { - delete data_address; - } - - - if(title != nullptr) { - delete title; - } -} - -SWGSDRDaemonChannelSourceSettings* -SWGSDRDaemonChannelSourceSettings::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGSDRDaemonChannelSourceSettings::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&data_address, pJson["dataAddress"], "QString", "QString"); - - ::SWGSDRangel::setValue(&data_port, pJson["dataPort"], "qint32", ""); - - ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); - - ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); - -} - -QString -SWGSDRDaemonChannelSourceSettings::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGSDRDaemonChannelSourceSettings::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(data_address != nullptr && *data_address != QString("")){ - toJsonValue(QString("dataAddress"), data_address, obj, QString("QString")); - } - if(m_data_port_isSet){ - obj->insert("dataPort", QJsonValue(data_port)); - } - if(m_rgb_color_isSet){ - obj->insert("rgbColor", QJsonValue(rgb_color)); - } - if(title != nullptr && *title != QString("")){ - toJsonValue(QString("title"), title, obj, QString("QString")); - } - - return obj; -} - -QString* -SWGSDRDaemonChannelSourceSettings::getDataAddress() { - return data_address; -} -void -SWGSDRDaemonChannelSourceSettings::setDataAddress(QString* data_address) { - this->data_address = data_address; - this->m_data_address_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceSettings::getDataPort() { - return data_port; -} -void -SWGSDRDaemonChannelSourceSettings::setDataPort(qint32 data_port) { - this->data_port = data_port; - this->m_data_port_isSet = true; -} - -qint32 -SWGSDRDaemonChannelSourceSettings::getRgbColor() { - return rgb_color; -} -void -SWGSDRDaemonChannelSourceSettings::setRgbColor(qint32 rgb_color) { - this->rgb_color = rgb_color; - this->m_rgb_color_isSet = true; -} - -QString* -SWGSDRDaemonChannelSourceSettings::getTitle() { - return title; -} -void -SWGSDRDaemonChannelSourceSettings::setTitle(QString* title) { - this->title = title; - this->m_title_isSet = true; -} - - -bool -SWGSDRDaemonChannelSourceSettings::isSet(){ - bool isObjectUpdated = false; - do{ - if(data_address != nullptr && *data_address != QString("")){ isObjectUpdated = true; break;} - if(m_data_port_isSet){ isObjectUpdated = true; break;} - if(m_rgb_color_isSet){ isObjectUpdated = true; break;} - if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h deleted file mode 100644 index fc881baea..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRDaemonChannelSourceSettings.h +++ /dev/null @@ -1,77 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- - * - * OpenAPI spec version: 4.1.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGSDRDaemonChannelSourceSettings.h - * - * Data handling details for SDRDaemon - */ - -#ifndef SWGSDRDaemonChannelSourceSettings_H_ -#define SWGSDRDaemonChannelSourceSettings_H_ - -#include - - -#include - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGSDRDaemonChannelSourceSettings: public SWGObject { -public: - SWGSDRDaemonChannelSourceSettings(); - SWGSDRDaemonChannelSourceSettings(QString* json); - virtual ~SWGSDRDaemonChannelSourceSettings(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGSDRDaemonChannelSourceSettings* fromJson(QString &jsonString) override; - - QString* getDataAddress(); - void setDataAddress(QString* data_address); - - qint32 getDataPort(); - void setDataPort(qint32 data_port); - - qint32 getRgbColor(); - void setRgbColor(qint32 rgb_color); - - QString* getTitle(); - void setTitle(QString* title); - - - virtual bool isSet() override; - -private: - QString* data_address; - bool m_data_address_isSet; - - qint32 data_port; - bool m_data_port_isSet; - - qint32 rgb_color; - bool m_rgb_color_isSet; - - QString* title; - bool m_title_isSet; - -}; - -} - -#endif /* SWGSDRDaemonChannelSourceSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.cpp deleted file mode 100644 index 9801e4a99..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGSDRPlayReport_bandwidths.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGSDRPlayReport_bandwidths::SWGSDRPlayReport_bandwidths(QString* json) { - init(); - this->fromJson(*json); -} - -SWGSDRPlayReport_bandwidths::SWGSDRPlayReport_bandwidths() { - bandwidth = 0; - m_bandwidth_isSet = false; -} - -SWGSDRPlayReport_bandwidths::~SWGSDRPlayReport_bandwidths() { - this->cleanup(); -} - -void -SWGSDRPlayReport_bandwidths::init() { - bandwidth = 0; - m_bandwidth_isSet = false; -} - -void -SWGSDRPlayReport_bandwidths::cleanup() { - -} - -SWGSDRPlayReport_bandwidths* -SWGSDRPlayReport_bandwidths::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGSDRPlayReport_bandwidths::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); - -} - -QString -SWGSDRPlayReport_bandwidths::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGSDRPlayReport_bandwidths::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(m_bandwidth_isSet){ - obj->insert("bandwidth", QJsonValue(bandwidth)); - } - - return obj; -} - -qint32 -SWGSDRPlayReport_bandwidths::getBandwidth() { - return bandwidth; -} -void -SWGSDRPlayReport_bandwidths::setBandwidth(qint32 bandwidth) { - this->bandwidth = bandwidth; - this->m_bandwidth_isSet = true; -} - - -bool -SWGSDRPlayReport_bandwidths::isSet(){ - bool isObjectUpdated = false; - do{ - if(m_bandwidth_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.h deleted file mode 100644 index 67b0161c2..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_bandwidths.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGSDRPlayReport_bandwidths.h - * - * - */ - -#ifndef SWGSDRPlayReport_bandwidths_H_ -#define SWGSDRPlayReport_bandwidths_H_ - -#include - - - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGSDRPlayReport_bandwidths: public SWGObject { -public: - SWGSDRPlayReport_bandwidths(); - SWGSDRPlayReport_bandwidths(QString* json); - virtual ~SWGSDRPlayReport_bandwidths(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGSDRPlayReport_bandwidths* fromJson(QString &jsonString) override; - - qint32 getBandwidth(); - void setBandwidth(qint32 bandwidth); - - - virtual bool isSet() override; - -private: - qint32 bandwidth; - bool m_bandwidth_isSet; - -}; - -} - -#endif /* SWGSDRPlayReport_bandwidths_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.cpp deleted file mode 100644 index 20c6cbf1d..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGSDRPlayReport_frequencyBands.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGSDRPlayReport_frequencyBands::SWGSDRPlayReport_frequencyBands(QString* json) { - init(); - this->fromJson(*json); -} - -SWGSDRPlayReport_frequencyBands::SWGSDRPlayReport_frequencyBands() { - band_name = nullptr; - m_band_name_isSet = false; - band_low = 0; - m_band_low_isSet = false; - band_high = 0; - m_band_high_isSet = false; -} - -SWGSDRPlayReport_frequencyBands::~SWGSDRPlayReport_frequencyBands() { - this->cleanup(); -} - -void -SWGSDRPlayReport_frequencyBands::init() { - band_name = new QString(""); - m_band_name_isSet = false; - band_low = 0; - m_band_low_isSet = false; - band_high = 0; - m_band_high_isSet = false; -} - -void -SWGSDRPlayReport_frequencyBands::cleanup() { - if(band_name != nullptr) { - delete band_name; - } - - -} - -SWGSDRPlayReport_frequencyBands* -SWGSDRPlayReport_frequencyBands::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGSDRPlayReport_frequencyBands::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&band_name, pJson["bandName"], "QString", "QString"); - - ::SWGSDRangel::setValue(&band_low, pJson["bandLow"], "qint32", ""); - - ::SWGSDRangel::setValue(&band_high, pJson["bandHigh"], "qint32", ""); - -} - -QString -SWGSDRPlayReport_frequencyBands::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGSDRPlayReport_frequencyBands::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(band_name != nullptr && *band_name != QString("")){ - toJsonValue(QString("bandName"), band_name, obj, QString("QString")); - } - if(m_band_low_isSet){ - obj->insert("bandLow", QJsonValue(band_low)); - } - if(m_band_high_isSet){ - obj->insert("bandHigh", QJsonValue(band_high)); - } - - return obj; -} - -QString* -SWGSDRPlayReport_frequencyBands::getBandName() { - return band_name; -} -void -SWGSDRPlayReport_frequencyBands::setBandName(QString* band_name) { - this->band_name = band_name; - this->m_band_name_isSet = true; -} - -qint32 -SWGSDRPlayReport_frequencyBands::getBandLow() { - return band_low; -} -void -SWGSDRPlayReport_frequencyBands::setBandLow(qint32 band_low) { - this->band_low = band_low; - this->m_band_low_isSet = true; -} - -qint32 -SWGSDRPlayReport_frequencyBands::getBandHigh() { - return band_high; -} -void -SWGSDRPlayReport_frequencyBands::setBandHigh(qint32 band_high) { - this->band_high = band_high; - this->m_band_high_isSet = true; -} - - -bool -SWGSDRPlayReport_frequencyBands::isSet(){ - bool isObjectUpdated = false; - do{ - if(band_name != nullptr && *band_name != QString("")){ isObjectUpdated = true; break;} - if(m_band_low_isSet){ isObjectUpdated = true; break;} - if(m_band_high_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.h deleted file mode 100644 index 29653f799..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_frequencyBands.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGSDRPlayReport_frequencyBands.h - * - * - */ - -#ifndef SWGSDRPlayReport_frequencyBands_H_ -#define SWGSDRPlayReport_frequencyBands_H_ - -#include - - -#include - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGSDRPlayReport_frequencyBands: public SWGObject { -public: - SWGSDRPlayReport_frequencyBands(); - SWGSDRPlayReport_frequencyBands(QString* json); - virtual ~SWGSDRPlayReport_frequencyBands(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGSDRPlayReport_frequencyBands* fromJson(QString &jsonString) override; - - QString* getBandName(); - void setBandName(QString* band_name); - - qint32 getBandLow(); - void setBandLow(qint32 band_low); - - qint32 getBandHigh(); - void setBandHigh(qint32 band_high); - - - virtual bool isSet() override; - -private: - QString* band_name; - bool m_band_name_isSet; - - qint32 band_low; - bool m_band_low_isSet; - - qint32 band_high; - bool m_band_high_isSet; - -}; - -} - -#endif /* SWGSDRPlayReport_frequencyBands_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.cpp b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.cpp deleted file mode 100644 index 653aae36c..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - - -#include "SWGSDRPlayReport_intermediateFrequencies.h" - -#include "SWGHelpers.h" - -#include -#include -#include -#include - -namespace SWGSDRangel { - -SWGSDRPlayReport_intermediateFrequencies::SWGSDRPlayReport_intermediateFrequencies(QString* json) { - init(); - this->fromJson(*json); -} - -SWGSDRPlayReport_intermediateFrequencies::SWGSDRPlayReport_intermediateFrequencies() { - intermediate_frequency = 0; - m_intermediate_frequency_isSet = false; -} - -SWGSDRPlayReport_intermediateFrequencies::~SWGSDRPlayReport_intermediateFrequencies() { - this->cleanup(); -} - -void -SWGSDRPlayReport_intermediateFrequencies::init() { - intermediate_frequency = 0; - m_intermediate_frequency_isSet = false; -} - -void -SWGSDRPlayReport_intermediateFrequencies::cleanup() { - -} - -SWGSDRPlayReport_intermediateFrequencies* -SWGSDRPlayReport_intermediateFrequencies::fromJson(QString &json) { - QByteArray array (json.toStdString().c_str()); - QJsonDocument doc = QJsonDocument::fromJson(array); - QJsonObject jsonObject = doc.object(); - this->fromJsonObject(jsonObject); - return this; -} - -void -SWGSDRPlayReport_intermediateFrequencies::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&intermediate_frequency, pJson["intermediateFrequency"], "qint32", ""); - -} - -QString -SWGSDRPlayReport_intermediateFrequencies::asJson () -{ - QJsonObject* obj = this->asJsonObject(); - - QJsonDocument doc(*obj); - QByteArray bytes = doc.toJson(); - delete obj; - return QString(bytes); -} - -QJsonObject* -SWGSDRPlayReport_intermediateFrequencies::asJsonObject() { - QJsonObject* obj = new QJsonObject(); - if(m_intermediate_frequency_isSet){ - obj->insert("intermediateFrequency", QJsonValue(intermediate_frequency)); - } - - return obj; -} - -qint32 -SWGSDRPlayReport_intermediateFrequencies::getIntermediateFrequency() { - return intermediate_frequency; -} -void -SWGSDRPlayReport_intermediateFrequencies::setIntermediateFrequency(qint32 intermediate_frequency) { - this->intermediate_frequency = intermediate_frequency; - this->m_intermediate_frequency_isSet = true; -} - - -bool -SWGSDRPlayReport_intermediateFrequencies::isSet(){ - bool isObjectUpdated = false; - do{ - if(m_intermediate_frequency_isSet){ isObjectUpdated = true; break;} - }while(false); - return isObjectUpdated; -} -} - diff --git a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.h b/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.h deleted file mode 100644 index 93504fc3e..000000000 --- a/swagger/sdrangel/code/qt5/client/SWGSDRPlayReport_intermediateFrequencies.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * SDRangel - * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- - * - * OpenAPI spec version: 4.0.0 - * Contact: f4exb06@gmail.com - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - */ - -/* - * SWGSDRPlayReport_intermediateFrequencies.h - * - * - */ - -#ifndef SWGSDRPlayReport_intermediateFrequencies_H_ -#define SWGSDRPlayReport_intermediateFrequencies_H_ - -#include - - - -#include "SWGObject.h" -#include "export.h" - -namespace SWGSDRangel { - -class SWG_API SWGSDRPlayReport_intermediateFrequencies: public SWGObject { -public: - SWGSDRPlayReport_intermediateFrequencies(); - SWGSDRPlayReport_intermediateFrequencies(QString* json); - virtual ~SWGSDRPlayReport_intermediateFrequencies(); - void init(); - void cleanup(); - - virtual QString asJson () override; - virtual QJsonObject* asJsonObject() override; - virtual void fromJsonObject(QJsonObject &json) override; - virtual SWGSDRPlayReport_intermediateFrequencies* fromJson(QString &jsonString) override; - - qint32 getIntermediateFrequency(); - void setIntermediateFrequency(qint32 intermediate_frequency); - - - virtual bool isSet() override; - -private: - qint32 intermediate_frequency; - bool m_intermediate_frequency_isSet; - -}; - -} - -#endif /* SWGSDRPlayReport_intermediateFrequencies_H_ */ From 9cfaf47a008db72be9eebefd323c4bddda0f00a1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 11 Sep 2018 14:52:16 +0200 Subject: [PATCH 731/956] REST API: removed old SDRDaemon code --- sdrbase/resources/webapi/doc/html2/index.html | 5644 +---------------- .../resources/webapi/doc/swagger/swagger.yaml | 528 +- swagger/sdrangel/api/swagger/swagger.yaml | 528 +- swagger/sdrangel/code/html2/index.html | 5644 +---------------- .../code/qt5/client/SWGAMDemodReport.cpp | 2 +- .../code/qt5/client/SWGAMDemodReport.h | 2 +- .../code/qt5/client/SWGAMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGAMDemodSettings.h | 2 +- .../code/qt5/client/SWGAMModReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGAMModReport.h | 2 +- .../code/qt5/client/SWGAMModSettings.cpp | 2 +- .../code/qt5/client/SWGAMModSettings.h | 2 +- .../code/qt5/client/SWGATVModReport.cpp | 2 +- .../code/qt5/client/SWGATVModReport.h | 2 +- .../code/qt5/client/SWGATVModSettings.cpp | 2 +- .../code/qt5/client/SWGATVModSettings.h | 2 +- .../code/qt5/client/SWGAirspyHFReport.cpp | 2 +- .../code/qt5/client/SWGAirspyHFReport.h | 2 +- .../code/qt5/client/SWGAirspyHFSettings.cpp | 2 +- .../code/qt5/client/SWGAirspyHFSettings.h | 2 +- .../code/qt5/client/SWGAirspyReport.cpp | 2 +- .../code/qt5/client/SWGAirspyReport.h | 2 +- .../code/qt5/client/SWGAirspySettings.cpp | 2 +- .../code/qt5/client/SWGAirspySettings.h | 2 +- .../code/qt5/client/SWGAudioDevices.cpp | 2 +- .../code/qt5/client/SWGAudioDevices.h | 2 +- .../code/qt5/client/SWGAudioInputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioInputDevice.h | 2 +- .../code/qt5/client/SWGAudioOutputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioOutputDevice.h | 2 +- .../code/qt5/client/SWGBFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGBFMDemodReport.h | 2 +- .../code/qt5/client/SWGBFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGBFMDemodSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.cpp | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.h | 2 +- .../qt5/client/SWGBladeRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGBladeRFInputSettings.h | 2 +- .../qt5/client/SWGBladeRFOutputSettings.cpp | 2 +- .../qt5/client/SWGBladeRFOutputSettings.h | 2 +- .../code/qt5/client/SWGCWKeyerSettings.cpp | 2 +- .../code/qt5/client/SWGCWKeyerSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGChannel.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGChannel.h | 2 +- .../code/qt5/client/SWGChannelListItem.cpp | 2 +- .../code/qt5/client/SWGChannelListItem.h | 2 +- .../code/qt5/client/SWGChannelReport.cpp | 2 +- .../code/qt5/client/SWGChannelReport.h | 2 +- .../code/qt5/client/SWGChannelSettings.cpp | 2 +- .../code/qt5/client/SWGChannelSettings.h | 2 +- .../code/qt5/client/SWGChannelsDetail.cpp | 2 +- .../code/qt5/client/SWGChannelsDetail.h | 2 +- .../code/qt5/client/SWGDSDDemodReport.cpp | 2 +- .../code/qt5/client/SWGDSDDemodReport.h | 2 +- .../code/qt5/client/SWGDSDDemodSettings.cpp | 2 +- .../code/qt5/client/SWGDSDDemodSettings.h | 2 +- .../code/qt5/client/SWGDVSeralDevices.cpp | 2 +- .../code/qt5/client/SWGDVSeralDevices.h | 2 +- .../code/qt5/client/SWGDVSerialDevice.cpp | 2 +- .../code/qt5/client/SWGDVSerialDevice.h | 2 +- .../sdrangel/code/qt5/client/SWGDaemonApi.cpp | 775 --- .../sdrangel/code/qt5/client/SWGDaemonApi.h | 123 - .../code/qt5/client/SWGDaemonSinkSettings.cpp | 2 +- .../code/qt5/client/SWGDaemonSinkSettings.h | 2 +- .../code/qt5/client/SWGDaemonSourceReport.cpp | 2 +- .../code/qt5/client/SWGDaemonSourceReport.h | 2 +- .../qt5/client/SWGDaemonSourceSettings.cpp | 2 +- .../code/qt5/client/SWGDaemonSourceSettings.h | 2 +- .../qt5/client/SWGDaemonSummaryResponse.cpp | 309 - .../qt5/client/SWGDaemonSummaryResponse.h | 115 - .../code/qt5/client/SWGDeviceListItem.cpp | 2 +- .../code/qt5/client/SWGDeviceListItem.h | 2 +- .../code/qt5/client/SWGDeviceReport.cpp | 2 +- .../code/qt5/client/SWGDeviceReport.h | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.h | 2 +- .../code/qt5/client/SWGDeviceSetApi.cpp | 2 +- .../code/qt5/client/SWGDeviceSetApi.h | 2 +- .../code/qt5/client/SWGDeviceSetList.cpp | 2 +- .../code/qt5/client/SWGDeviceSetList.h | 2 +- .../code/qt5/client/SWGDeviceSettings.cpp | 2 +- .../code/qt5/client/SWGDeviceSettings.h | 2 +- .../code/qt5/client/SWGDeviceState.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceState.h | 2 +- .../code/qt5/client/SWGErrorResponse.cpp | 2 +- .../code/qt5/client/SWGErrorResponse.h | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.h | 2 +- .../code/qt5/client/SWGFCDProSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProSettings.h | 2 +- .../code/qt5/client/SWGFileSourceReport.cpp | 2 +- .../code/qt5/client/SWGFileSourceReport.h | 2 +- .../code/qt5/client/SWGFileSourceSettings.cpp | 2 +- .../code/qt5/client/SWGFileSourceSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.cpp | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.h | 2 +- .../code/qt5/client/SWGFrequencyBand.cpp | 2 +- .../code/qt5/client/SWGFrequencyBand.h | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.h | 2 +- .../qt5/client/SWGHackRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFInputSettings.h | 2 +- .../qt5/client/SWGHackRFOutputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFOutputSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGHelpers.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGHelpers.h | 2 +- .../code/qt5/client/SWGHttpRequest.cpp | 2 +- .../sdrangel/code/qt5/client/SWGHttpRequest.h | 2 +- .../code/qt5/client/SWGInstanceApi.cpp | 2 +- .../sdrangel/code/qt5/client/SWGInstanceApi.h | 2 +- .../client/SWGInstanceChannelsResponse.cpp | 2 +- .../qt5/client/SWGInstanceChannelsResponse.h | 2 +- .../qt5/client/SWGInstanceDevicesResponse.cpp | 2 +- .../qt5/client/SWGInstanceDevicesResponse.h | 2 +- .../qt5/client/SWGInstanceSummaryResponse.cpp | 2 +- .../qt5/client/SWGInstanceSummaryResponse.h | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.h | 2 +- .../qt5/client/SWGLimeSdrInputSettings.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputSettings.h | 2 +- .../qt5/client/SWGLimeSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrOutputReport.h | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.h | 2 +- .../qt5/client/SWGLocationInformation.cpp | 2 +- .../code/qt5/client/SWGLocationInformation.h | 2 +- .../code/qt5/client/SWGLoggingInfo.cpp | 2 +- .../sdrangel/code/qt5/client/SWGLoggingInfo.h | 2 +- .../code/qt5/client/SWGModelFactory.h | 6 +- .../code/qt5/client/SWGNFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGNFMDemodReport.h | 2 +- .../code/qt5/client/SWGNFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGNFMDemodSettings.h | 2 +- .../code/qt5/client/SWGNFMModReport.cpp | 2 +- .../code/qt5/client/SWGNFMModReport.h | 2 +- .../code/qt5/client/SWGNFMModSettings.cpp | 2 +- .../code/qt5/client/SWGNFMModSettings.h | 2 +- swagger/sdrangel/code/qt5/client/SWGObject.h | 2 +- .../code/qt5/client/SWGPerseusReport.cpp | 2 +- .../code/qt5/client/SWGPerseusReport.h | 2 +- .../code/qt5/client/SWGPerseusSettings.cpp | 2 +- .../code/qt5/client/SWGPerseusSettings.h | 2 +- .../qt5/client/SWGPlutoSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrInputReport.h | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.h | 2 +- .../qt5/client/SWGPlutoSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrOutputReport.h | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.h | 2 +- .../code/qt5/client/SWGPresetExport.cpp | 2 +- .../code/qt5/client/SWGPresetExport.h | 2 +- .../code/qt5/client/SWGPresetGroup.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetGroup.h | 2 +- .../code/qt5/client/SWGPresetIdentifier.cpp | 2 +- .../code/qt5/client/SWGPresetIdentifier.h | 2 +- .../code/qt5/client/SWGPresetImport.cpp | 2 +- .../code/qt5/client/SWGPresetImport.h | 2 +- .../code/qt5/client/SWGPresetItem.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetItem.h | 2 +- .../code/qt5/client/SWGPresetTransfer.cpp | 2 +- .../code/qt5/client/SWGPresetTransfer.h | 2 +- .../sdrangel/code/qt5/client/SWGPresets.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGPresets.h | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.h | 2 +- .../client/SWGRDSReport_altFrequencies.cpp | 2 +- .../qt5/client/SWGRDSReport_altFrequencies.h | 2 +- .../code/qt5/client/SWGRtlSdrReport.cpp | 2 +- .../code/qt5/client/SWGRtlSdrReport.h | 2 +- .../code/qt5/client/SWGRtlSdrSettings.cpp | 2 +- .../code/qt5/client/SWGRtlSdrSettings.h | 2 +- .../code/qt5/client/SWGSDRPlayReport.cpp | 2 +- .../code/qt5/client/SWGSDRPlayReport.h | 2 +- .../code/qt5/client/SWGSDRPlaySettings.cpp | 2 +- .../code/qt5/client/SWGSDRPlaySettings.h | 2 +- .../qt5/client/SWGSDRdaemonSinkReport.cpp | 2 +- .../code/qt5/client/SWGSDRdaemonSinkReport.h | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSinkSettings.h | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceReport.h | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.cpp | 2 +- .../qt5/client/SWGSDRdaemonSourceSettings.h | 2 +- .../code/qt5/client/SWGSSBDemodReport.cpp | 2 +- .../code/qt5/client/SWGSSBDemodReport.h | 2 +- .../code/qt5/client/SWGSSBDemodSettings.cpp | 2 +- .../code/qt5/client/SWGSSBDemodSettings.h | 2 +- .../code/qt5/client/SWGSSBModReport.cpp | 2 +- .../code/qt5/client/SWGSSBModReport.h | 2 +- .../code/qt5/client/SWGSSBModSettings.cpp | 2 +- .../code/qt5/client/SWGSSBModSettings.h | 2 +- .../code/qt5/client/SWGSampleRate.cpp | 2 +- .../sdrangel/code/qt5/client/SWGSampleRate.h | 2 +- .../code/qt5/client/SWGSamplingDevice.cpp | 2 +- .../code/qt5/client/SWGSamplingDevice.h | 2 +- .../code/qt5/client/SWGSuccessResponse.cpp | 2 +- .../code/qt5/client/SWGSuccessResponse.h | 2 +- .../code/qt5/client/SWGTestSourceSettings.cpp | 2 +- .../code/qt5/client/SWGTestSourceSettings.h | 2 +- .../code/qt5/client/SWGUDPSinkReport.cpp | 2 +- .../code/qt5/client/SWGUDPSinkReport.h | 2 +- .../code/qt5/client/SWGUDPSinkSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSinkSettings.h | 2 +- .../code/qt5/client/SWGUDPSrcReport.cpp | 2 +- .../code/qt5/client/SWGUDPSrcReport.h | 2 +- .../code/qt5/client/SWGUDPSrcSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSrcSettings.h | 2 +- .../code/qt5/client/SWGWFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGWFMDemodReport.h | 2 +- .../code/qt5/client/SWGWFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGWFMDemodSettings.h | 2 +- .../code/qt5/client/SWGWFMModReport.cpp | 2 +- .../code/qt5/client/SWGWFMModReport.h | 2 +- .../code/qt5/client/SWGWFMModSettings.cpp | 2 +- .../code/qt5/client/SWGWFMModSettings.h | 2 +- 216 files changed, 384 insertions(+), 13702 deletions(-) delete mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonApi.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonApi.h delete mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.cpp delete mode 100644 swagger/sdrangel/code/qt5/client/SWGDaemonSummaryResponse.h diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 9599f8ea7..c18e79645 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1778,50 +1778,6 @@ margin-bottom: 20px; } }, "description" : "Daemon channel source settings" -}; - defs.DaemonSummaryResponse = { - "required" : [ "appname", "architecture", "dspRxBits", "dspTxBits", "os", "pid", "qtVersion", "version" ], - "properties" : { - "version" : { - "type" : "string", - "description" : "Current software version" - }, - "qtVersion" : { - "type" : "string", - "description" : "Qt version with which the software was compiled" - }, - "dspRxBits" : { - "type" : "integer", - "description" : "Number of samples significant bits in software Rx DSP" - }, - "dspTxBits" : { - "type" : "integer", - "description" : "Number of samples significant bits in software Tx DSP" - }, - "pid" : { - "type" : "integer", - "description" : "PID of the SDRangel instance" - }, - "appname" : { - "type" : "string", - "description" : "Application name: SDRangel for a GUI instance and SDRangelSrv for a server instance" - }, - "architecture" : { - "type" : "string", - "description" : "Codename of the CPU architecture on which the instance is running (available with Qt >= 5.4)" - }, - "os" : { - "type" : "string", - "description" : "Descriptive text of the operating system running the instance (available with Qt >= 5.4)" - }, - "logging" : { - "$ref" : "#/definitions/LoggingInfo" - }, - "samplingDevice" : { - "$ref" : "#/definitions/SamplingDevice" - } - }, - "description" : "Summarized information about this SDRdaemon instance" }; defs.DeviceListItem = { "properties" : { @@ -4095,49 +4051,6 @@ margin-bottom: 20px; -->
  • <9h^7;XN61-Pn`aanYYpxV=iF%^IP5-H?*5rFpjW-;&lv;^`VY4R7h>IUP$D{I(d z^bpL&OXpyKN-w5*{$Xb+jNEJdE$Pw&+`) zT|CFR>uMQCArW{oH77uJI%4<}ui{uS76%JyH;I;YPUsVKW6#NU#~k9$Qve1(Cm zrMmQVGvKs65H(XmD-)wAx@OeX0SnMRJ9avQ?^-qkV)9%j%))hO6|o7hkg9UnQFt^V z3z|2rw};i;$CruZ*PqYkW;3Bm3Meyk;-J3bc6D$K3kxPh@BmDYw9_KZEu23r63^M; zJt9_?>|rPtdO^YR>#&`|YfPd8+jS3WRvH_iYWh=84*{FIH;LT)3;`!XVxnm;tJ%bs4Vn2X?Y#(4zRnCGmbsoSOJ%kXAtaNm# z&JIlF^mDMQ@EES?>IQ48EJN7#^+(8$8U*7zya%AYmZMj`?e3y|Yab)^%gvMj*F7fk z+`TZ#^CtlB@>2SI!Ym|&j&@kog;R{%kO)li-d?gXRzFOIhBJq0d$}56UsDD1j8DdK zl75C~JVMpQcK*gWP9Nc(r_K{|`pG+>b;}(-ej-Qr@K8HDc+J9sNbS*6F%N6i*q6j{ ztY)@$=oKLYc<+^e6D>Y;6odHqeprr;9ly&;$XVR`2i~0*KcGa6Iey}jH=ty&eq><^H2v;%e`m_IoenY8@^F){4yF?G^Tah)!;!o;Q@ z7`#0_oZHjW&)Gb+0 z@N9h@{ZU&*A-lh}qbK!rVb+F4W5({<#N- zXT(65M@JiM=g1!Jci%QZwYd=xtgq#FaUL30YqPEwwzp&Qx3^Jy%H3OJ&`|2nXQz{< z)6xh#DY%L@`TMu9AZr_#)X75t;Q5o>cUdujHE$I`t#*z8+JIPf^A9_9>?wpS)T&YS zHg+sbF7CutUCk1icQ<3^dJ#ZBI1wGoxetzkiV~bOZ7rDT?QMjIghkU%M;DmksYAS9 zS*AM*>gyQKZk~Mq_0Poo=a07yXbC-h8~ph7SHxiW-#?Ddip*QZ-wReN;_j`8v#TO~ z{dlea07cw96fv3j-`z_Q8yiJZCMn_@sGn!t41QZ#EAp>j6?x}tMTVy+;_j))r@!l; z)6Q#YsYqa`BC+v`+`U1OwU2TR4{t?c;uUdmQ^d=x$nepMI65j47OjZcUlEr+H{ZXT z$Ibsg_KG4ef2hc)iF&)V`wIwGWchuH*w`xK?4rNt?!{|X)`~pzydvIaMQrV8yMM4E zuI}8=l)3ypWv(JYp?r7HaQ+@NoO7;xfY&496!Ge-h|$P*149*g@Hs^cMn&S275V6A z+UoAjwWiKfWOq8>^)~Z+#v*HKg4K#Fzh99>Yj|I4dqkWf<7e={lZzq|v5JhD%01cGC^B%U-ls9~iro7Y*ESdw zS$eM`8(vVvWXb-I)2Yy#_zYkQsk}``ZG5e6j}4AB7NKynJ|<7>FcLR)FArB z-IMEUZL+kat?u56^beyiEiDyU`xxJ`vSF30a=Xm++lOg|M8JdwsOI38+lWc(>O=(B#LmukR;}hnRpcx|LrIwo zt7&W1bIK}8aktl0v64AC<8I#e3mS*BD=T`|1z5_wRhWcj#q@7?HyX-ce-dh;ZS$3P zne*4zU`i}n2U{q*hLAsFAuDA>94ota%v^+%;Kxr`t+UfvrQ?Q)9NNiNRy%(uF38G; z;LE{Dn5-sC#Pr=;IJYP;rPdY%ZV^l%Z(mmbf?SwDNdfnoJRW8iIS`gvT>-=BXcw8W z7!&r~aTtoNJp~56&6qn0$(U)Ie&GG)Mobwk*RH9N^Myw6EK*OvatEt%W*zOiR@hK4 zQeMQhZ0s=Y_WnUAz=WB+zVbnSS69FkZEZ=?F29b(=jnr~nlv7U<>Z16mwE!G78V7A zzLZKoC5^}A&c37utd4>GMGiujgZkwr1Y%Xh*|rZNaJG?!g8t)fKQmlO=uE-6gVU zt(eapzQeR`xu5xWv#QwGDq`oL$n*vLZ|4ZZu(AEWnOH;|EFnHwk%j6??c%0>*Fll! z!JNZjVAZp8P$VKokxw@%GHH$?bC$s*;*%9gNLFN63ZLEZ0;{K+2j@#1&3$OsjUEDn z35(*pZXPfO8(Y|y!Jx=<@9{eKBlY8$!O#= zQ|Hq@I|oIEj)e6EhSK&q%k}%Z)62!~}j%UqBldtx;soQeOM|&pS-(mOLDtS>d!O_{*30-Q7hx%;I&hoIV~m z*PyDdKx1icV5JjLMOhCK2M1DJ6h&n06qwxb(Xg)Hz7a86z|2Yt(Z~vO@jCSjfQ2|Z z!Fcj7i@2)A`rJIJYn6GP_rjtO(VLrCLGv$@C)nP`xg4Bv2G~2Yx*AQ0)=IA$1S@D# z1`D(fzaVn-I@-}aTAB#QD9S?vb9IAheD^Q(ufa(O>G@Y+3qj#%L7g42q>+RPeeMcaVNfVKUb7nPo0~=EfR_K+w;$z=D@qaSZB<<-VI-j{Z+(g9 zH6#-KZ2c2NzuTyNb>aYhe(o5~jjRiL%$1D|`kR$Cde@{mY9$Xf{x1J24CLZjez&%8 zk9PL3Lw7IQ=+~e3mad1fxwz}qZ^NQsxy@>_xV0G z>PnuMuRmj^e;CZWwwmYX8^CzGcA5JP3?>e6@j9OQjKz#uM<;Z=s&XRXFP)=JmDlm+ z=I0W0y6j#Nzy4xQJ#t5gzvT{ZGY?Y}qaB@fL0$_)-0_!LiNKUMU)-_RFCZ8JGS;}mCHE5Z~6VSvoM!yEW1~vs1WaD#6TuOcb7hC=iiNZ z+|@~{OJ@hBpoInCXl0GLXsuTN^zcHo50A#Ij#N$6RU7aW&R7I66z8!3Ha1Y#%fCN> z|HU#K?fta}3UV;s^ z-2a+K>C3q*$Q>O&gE+H8yIEkmIsxFW4t^ef5%4n@`CQ>Ou4QY>f>2hBnRww8KrkSN z*Xrx_eATEyjEUw3fX~vJb`MFS#Lm90R z49@A(5ASnE8cq&JXG&MMH1RoiPoDFjMDFv6*JxKr1o3WZXIKozO&9a-jd#q$TkhwL z&vcW{q?#;C?}bup0`C9y^X3=2$UAx}zvGj2(bdHbTCa(-1_QL+WKv}MLQI3#KgGNn zIucWH?W26pWP+x<^wCYTMQaq9xkNXi5|T0FqK6>rYNkm{ydsuX+>50pzi%|>OeRGJ zM02eNpXc-1dz!f$KSGgpkL#}|BosH2dkF35`_5+ZXfdt`%G>aSZpPncMe0!!;jy z>3@%p2{Tpm))5EJ#990w5vQL+>r1Vz@yWUzF>VHCb6_ZarkSDdf6r&MvFYKhh>hLN z&YX#}^?S6k<}*oSa9G&bDDvvZi1Y>nruB{YoibT9=&RVPL2t{`~#kX6d5>-?@pbEo^~T3ZaU5+ zoUsTXwzA>cQ|9TOvmr_R9UIT2(=Dm9;*0LLzRS{DYI|n;RDlODlbW z)Lt7nR1v=*#(>etJw(KD9|i-9QRG1W9-2(MH7#)EL;u@TJZLz-?|Bkkb>2#V=-#K% zJjcyI@A3`gc_t*|rM9)xbx7@8S3B;2)-Cxn;eP;?hyFYnq6x4R$vVMc)3B&uSqA8p9Yl@dh?>!}YY4a@-6i>aY|<=m($U`~!z6 z5)#R0{DPQB8u*$0m^9vIMIL=wKmUEt05*e@2+hzYtj25>t$~5r*eVhpqp#dIIvETG zMV8$Mh+0|GHmywt1NUfS%THj4-oKi-ZD+?lxO>xH7dJ(``|_Tx9h1~#f~ovhu=a)d ztbG(Vtg#D?8T$v{3>fa-iu~hy!WR64Ip6ph`edIp=jP9A_cmxapfN0ketz_2SXx*V zeWX3l$T*&lgUTLfECK+>%>V#yJa=FSpNotG$Zvel#g+30g|fgI46xG;FX;Ca7^ +

    A section: settings

    + +

    A.1: Frequency shift from center frequency of reception

    Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews.Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. -

    2: Symbol (Baud) rate

    - -Here you can specify which symbol rate or Baud rate is expected. Choices are: - - - `2.4k`: 2400 S/s used for dPMR and 4800 b/s NXDN - - `4.8k`: 4800 S/s used for 9600 b/s NXDN, DMR, D-Star and YSF. - -

    3: Type of frame detected

    - -This can be one of the following: - - - `+DMRd`: non-inverted DMR data frame - - `+DMRv`: non-inverted DMR voice frame - - `+D-STAR`: non-inverted D-Star frame - - `-D-STAR`: inverted D-Star frame - - `+D-STAR_HD`: non-inverted D-Star header frame encountered - - `-D-STAR_HD`: inverted D-Star header frame encountered - - `+dPMR`: non-inverted dPMR non-packet frame - - `+NXDN`: non-inverted NXDN frame (detection only) - - `+YSF`: non-inverted Yaesu System Fusion frame (detection only) - -

    3a: Symbol PLL lock indicator

    - -Since dsdcc version 1.7.1 the synbol synchronization can be done with a PLL fed by a ringing filter (narrow passband) tuned at the symbol rate and itself fed with the squared magnitude of the discriminator signal. For signals strong enough to lock the PLL this works significantly better than with the ringing filter alone that was the only option in versions <= 1.6.0. Version 1.7.0 had the PLL enabled permanently. - -However with marginal signals the ringing filter alone and a few heuristics work better. This is why since DSDcc version 1.7.1 the PLL became optional. - -You can use this button to toggle between the two options: - - - with the locker icon in locked position: PLL is engaged - - with the locker icon in unlocked position: PLL is bypassed - -When in lock position the button lights itself in green when the PLL lock is acquired. Occasional drops may occur without noticeable impact on decoding. - -

    4: Symbol synchronization zero crossing hits in %

    - -This is the percentage per symbols for which a valid zero crossing has been detected. The more the better the symbol synchronization is tracked however the zero crossing shifts much not deviate too much from 0 (see next). - -With the PLL engaged the figure should be 100% all the time in presence of a locked signal. Occasional small drops may occur without noticeable impact on decoding. - -

    5: Zero crossing shift

    - -This is the current (at display polling time) zero crosing shift. It should be the closest to 0 as possible. However some jitter is acceptable for good symbol synchronization: - - - `2400 S/s`: +/- 5 inclusive - - `4800 S/s`: +/- 2 inclusive - -

    6: Matched filter toggle

    - -Normally you would always want to have a matched filter however on some strong D-Star signals more synchronization points could be obtained without. When engaged the background of the button is lit in orange. - -

    7: Transition constellation or symbol synchronization signal toggle

    - -Using this button you can either: - - - show the transitions constellation - - show a indicative signal about symbol synchronization - - when a zero crossing is detected the signal is set to estimated input discriminator signal maximum value - - when the symbol clock is 0 (start of symbol period) the signal is set to the estimated median point of the input discriminator signal - -

    8: Discriminator input signal median level in %

    - -This is the estimated median level (center) of the discriminator input signal in percentage of half the total range. When the signal is correctly aligned in the input range it should be 0 - -

    9: Discriminator input signal level range in %

    - -This is the estimated discriminator input signal level range (max - min) in percentage of half the total range. For optimal decoding it should be maintained close to 100. - -

    10: Channel power

    - -Total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. - -

    11: Channel bandwidth before discriminator

    +

    A.2: Channel bandwidth before discriminator

    This is the bandwidth of the pre-discriminator filter -

    12: Gain after discriminator

    +

    A.3: Channel power

    -This is the gain applied to the output of the discriminator before the decoder +Total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. -

    13: Audio volume

    +

    A.4: Channel power bar graph

    + +

    A.5: Audio volume

    When working with mbelib this is a linear multiplication factor. A value of zero triggers the auto gain feature. With the DV serial device(s) amplification factor in dB is given by `(value - 3.0)*5.0`. In most practical cases the middle value of 5.0 (+10 dB) is a comfortable level. -

    14: Maximum expected FM deviation

    - -This is the deviation in kHz leading to maximum (100%) deviation. You should aim for 30 to 50% (+/-300 to +/-500m) deviation on the scope display. - -

    15: Two slot TDMA handling

    - -This is useful for two slot TDMA modes that is only DMR at present. FDMA modes are treated as using slot #1 only. - -![DSD TDMA handling](../../../doc/img/DSDdemod_plugin_tdma.png) - -

    15.1: Slot #1 voice select

    - -Toggle button to select slot #1 voice output. When on waves appear on the icon. The icon turns green when voice frames are processed for this slot. For FDMA modes you may want to leave only this toggle on. - -

    15.2: Slot #2 voice select

    - -Toggle button to select slot #2 voice output. When on waves appear on the icon. The icon turns green when voice frames are processed for this slot. For FDMA modes you may want to leave this toggle off. - -

    15.3: TDMA stereo mode toggle

    - - - When off the icon shows a single loudspeaker. It mixes slot #1 and slot #2 voice as a mono audio signal - - When on the icon shows a pair of loudspeakers. It sends slot #1 vocie to the left stereo audio channel and slot #2 to the right one - -For FDMA standards you may want to leave this as mono mode. - -

    16: Squelch level

    +

    A.6: Squelch level

    The level corresponds to the channel power above which the squelch gate opens. -

    17: Squelch time gate

    +

    A.7: Squelch time gate

    Number of milliseconds following squelch gate opening after which the signal is actually fed to the decoder. 0 means no delay i.e. immediate feed. -

    18: High-pass filter for audio

    +

    A.8: High-pass filter for audio

    Use this switch to toggle high-pass filter on the audio -

    19: Audio mute and squelch indicator

    +

    A.9: Audio mute and squelch indicator

    Audio mute toggle button. This button lights in green when the squelch opens. -

    20: UDP output

    +

    A.10: UDP output

    Copies audio output to UDP. Output is stereo S16LE samples. Depending on which slots are active the output is the following: @@ -195,57 +102,57 @@ It cannot mix both channels when slot1+2 are active. UDP address and send port are specified in the basic channel settings. See: [here](https://github.com/f4exb/sdrangel/blob/master/sdrgui/readme.md#6-channels) -

    21: Format specific status display

    +

    a.11: Format specific status display

    When the display is active the background turns from the surrounding gray color to dark green. It shows informatory or status messages that are particular to each format. -

    21.1: D-Star status display

    +

    A11.1: D-Star status display

    ![DSD D-Star status](../../../doc/img/DSDdemod_plugin_dstar_status.png) -
    21.1.1: Origin (my) and destination (your) callsign
    +
    A11.1.1: Origin (my) and destination (your) callsign
    - at the left of the `>` sign is the origin callsign ` MY` with the 4 character informative suffix nest to the slash `/` - at the right of the `>` sign is the destination callsign `YOUR`. As per Icom standard this is `CQCQCQ` when a call is made to all stations - this information is retrieved from the header or the slow data if it can be decoded -
    21.1.2: Repeater callsign
    +
    A11.1.2: Repeater callsign
    - at the left of the `>` sign is the origin repeater or `RPT1` - at the right of the `>` sign is the destination repeater or `RPT2` - this information is retrieved from the header or the slow data if it can be decoded -
    21.1.3: Informative text
    +
    A11.1.3: Informative text
    When slow data can be decoded this is the 20 character string that is sent in the text frames -
    21.1.4: Geopositional data
    +
    A11.1.4: Geopositional data
    When a `$$CRC` frame that carries geographical position can be successfully decoded from the slow data the geopositional information is displayed: - at the left of the colon `:` is the QTH 6 character locator a.k.a. Maidenhead locator - at the right of the colon `:` is the bearing in degrees and distance in kilometers from the location entered in the main window `Preferences\My Position` dialog. The bearing and distance are separated by a slash `/`. -

    21.2: DMR status display

    +

    A11.2: DMR status display

    ![DSD DMR status](../../../doc/img/DSDdemod_plugin_dmr_status.png) - Note 1: statuses are polled at ~1s rate and therefore do not reflect values instantaneously. As a consequence some block types that occur during the conversation may not appear. - Note 2: status values remain unchanged until a new value is available for the channel or the transmissions stops then all values of both channels are cleared -
    21.2.1: Station role
    +
    A11.2.1: Station role
    - `BS`: base station - `MS`: mobile station - `NA`: not applicable or could not be determined (you should not see this normally) -
    21.2.2: TDMA slot #0 status
    +
    A11.2.2: TDMA slot #0 status
    For mobile stations on an inbound channel there is no channel identification (no CACH) so information goes there by default. -
    21.2.3: TDMA slot #1 status
    +
    A11.2.3: TDMA slot #1 status
    -
    21.2.4: Channel status and color code
    +
    A11.2.4: Channel status and color code
    This applies to base stations and mobile stations in continuous mode that is transmissions including the CACH sequences. @@ -260,7 +167,7 @@ This applies to base stations and mobile stations in continuous mode that is tra - The color code from 0 to 15 (4 bits) - `--`: The color code could not be decoded and information is missing -
    21.2.5: Slot type
    +
    A11.2.5: Slot type
    This is either: @@ -278,7 +185,7 @@ This is either: - `RES`: reserved data block - `UNK`: unknown data type or could not be decoded -
    21.2.6: Addressing information
    +
    A11.2.6: Addressing information
    String is in the form: `02223297>G00000222` @@ -288,11 +195,11 @@ String is in the form: `02223297>G00000222` - `U`: unit (individual) address - Next on the right is the target address (24 bits) as defined in the DMR ETSI standard -

    21.3: dPMR status display

    +

    A11.3: dPMR status display

    ![DSD dPMR status](../../../doc/img/DSDdemod_plugin_dpmr_status.png) -
    21.3.1: dPMR frame tyoe
    +
    A11.3.1: dPMR frame tyoe
    - `--`: undetermined - `HD`: Header of FS1 type @@ -304,23 +211,23 @@ String is in the form: `02223297>G00000222` - `XS`: Extended search: looking for a new payload frame when out of sequence - `EN`: End frame -
    21.3.2: Colour code
    +
    A11.3.2: Colour code
    Colour code in decimal (12 bits) -
    21.3.3: Own ID
    +
    A11.3.3: Own ID
    Sender's identification code in decimal (24 bits) -
    21.3.4: Called ID
    +
    A11.3.4: Called ID
    Called party's identification code in decimal (24 bits) -

    21.4: Yaesu System Fusion (YSF) status display

    +

    A11.4: Yaesu System Fusion (YSF) status display

    ![DSD YSF status](../../../doc/img/DSDdemod_plugin_ysf_status.png) -
    21.4.1: FICH data
    +
    A11.4.1: FICH data
    This displays a summary of FICH (Frame Identification CHannel) block data. From left to right: @@ -353,126 +260,203 @@ This displays a summary of FICH (Frame Identification CHannel) block data. From - `L`: local path (as inthe example) - last three characters are the YSF squelch code (0..127) or dashes `---` if the YSF squelch is not active -
    21.4.2: Origin and destination callsigns
    +
    A11.4.2: Origin and destination callsigns
    - at the left of the `>` sign is the origin callsign - at the right of the `>` sign is the destination callsign. It is filled with stars `*` when call is made to all stations (similar to the CQCQCQ in D-Star) -
    21.4.3: Origin and destination repeaters callsigns
    +
    A11.4.3: Origin and destination repeaters callsigns
    - at the left of the `>` sign is the origin repeater callsign - at the right of the `>` sign is the destination repeater callsign. -
    21.4.4: Originator radio ID
    +
    A11.4.4: Originator radio ID
    This is the unique character string assigned to the device by the manufacturer. -

    22: Discriminator output scope display

    +

    B section: digital

    -

    22.1 Transitions constellation display

    +

    B.1: FM signal scope

    -This is selected by the transition constellation or symbol synchronization signal toggle (see 7) +This display shows the sampled points of the demodulated FM signal in a XY plane with either: + + - X as the signal at time t and Y the signal at time t minus symbol time if "transitions constellation" is selected by button (B.13) + - X as the signal and Y as the synchronization signal if "symbol synchronization" is selected by button (B.13) + +The display shows 16 points as yellow crosses that can be used to tune the center frequency and FM deviation and gain so that symbol recovery can be done with the best conditions. In the rest of the documentation they will be referenced with numbers fron 0 to 15 starting at the top left corner and going from left to right and top to bottom. + +
    Transition constellation display
    + +This is selected by the transition constellation or symbol synchronization signal toggle (B.13) The discriminator signal at 48 kS/s is routed to the scope display with the following connections: - - I signal: the discriminator samples - - Q signal: the discriminator samples delayed by the baud rate i.e. one symbol delay: + - X input: the discriminator samples + - Y input: the discriminator samples delayed by the baud rate i.e. one symbol delay: - 2400 baud: 20 samples - 4800 baud: 10 samples - - 9600 baud: 5 samples -This allows the visualization of symbol transitions which depend on the type of modulation. - -![DSD scope](../../../doc/img/DSDdemod_plugin_scope.png) - -
    22.1.1: Setting the display
    - - - On the combo box you should choose IQ (lin) for the primary display and IQ (pol) for secondary display - - On the display buttons you should choose the side by side display - -On the same line you can choose any trace length. If it is too short the constellation points will not appear clearly and if it is too long the polar figure will be too dense. Usually 100ms give good results. - -
    22.1.2: IQ linear display
    - -The yellow trace (I) is the direct trace and the blue trace (Q) is the delayed trace. This can show how symbols differentiate between each other in a sort of eye diagram. - -
    22.1.3: IQ polar display
    - -This shows the constellation of transition points. You should adjust the frequency shift to center the figure and the maximum deviation and/or discriminator gain to contain the figure within the +/-0.4 square. +/- 0.1 to +/- 0.3 usually give the best results. +Depending on the type of modulation the figure will have different characteristic forms:
    2-FSK or 2-GFSK
    -This concerns the following formats: +![DSD Demodulator plugin GUI 2FSK](../../../doc/img/DSDdemod_plugin_2fsk.png) + +This concerns the following standards: - D-Star - -![DSD D-Star polar](../../../doc/img/DSDdemod_plugin_dstar_polar.png) - + There are 4 possible points corresponding to the 4 possible transitions. x represents the current symbol and y the previous symbol. The 4 points given by their (y,x) coordinates correspond to the following: - - (1, 1): upper right corner. The pointer can stay there or move to (1, -1) - - (1, -1): upper left corner. The pointer can move to (-1, -1) or (-1, 1) - - (-1, 1): lower right corner. The pointer can move to (1, -1) or (1, 1) - - (-1, -1): lower left corner. The pointer can stay there or move to (-1, 1) - + - (1, 1): upper right corner. The pointer can stay there or move to (1, -1). Ideally this should be placed at point 3. + - (1, -1): upper left corner. The pointer can move to (-1, -1) or (-1, 1). Ideally this should be placed at point 0. + - (-1, 1): lower right corner. The pointer can move to (1, -1) or (1, 1). Ideally this should be placed at point 15. + - (-1, -1): lower left corner. The pointer can stay there or move to (-1, 1). Ideally this should be placed at point 12. + As you can see the pointer can make all moves except between (-1, -1) and (1,1) hence all vertices between the 4 points can appear except the one between the lower left corner and the upper right corner.
    4-FSK or 4-GFSK
    -This concerns the following formats: +![DSD Demodulator plugin GUI 4FSK](../../../doc/img/DSDdemod_plugin_4fsk.png) + +This concerns the following standards: - DMR - YSF - dPMR - NXDN -![DSD DMR polar](../../../doc/img/DSDdemod_plugin_dmr_polar.png) - There are 16 possible points corresponding to the 16 possible transitions between the 4 dibits. The 4 dibits are equally spaced at relative positions of -3, -1, 1, 3 hence the 16 points are also equally spaced between each other on the IQ or (x,y) plane. -Because not all transitions are possible similarly to the 2-FSK case pointer moves from the lower left side of the diagonal to the upper right side are not possible. +Ideally the figure should show a cloud of persistent points at the locations marked by the yellow crosses (0 to 15). -
    22.1.4: I gain
    +
    Symbol synchronization display
    -You should set the slider to a unity (1) span (+/- 0.5) with no offset. This corresponds to full range in optimal conditions (100%). You can set the slider fully to the left (2) for a +/- 1.0 spn if you don't exactly match these conditions. +2-FSK -
    22.1.5: Q gain
    +![DSD Demodulator plugin GUI 2FSK symbols](../../../doc/img/DSDdemod_plugin_2fsk_sym.png) -You should set the slider to a unity (1) span (+/- 0.5) with no offset. This corresponds to full range in optimal conditions (100%). You can set the slider fully to the left (2) for a +/- 1.0 spn if you don't exactly match these conditions. +4-FSK -
    22.1.6: Trigger settings
    +![DSD Demodulator plugin GUI 4FSK symbols](../../../doc/img/DSDdemod_plugin_4fsk_sym.png) -You can leave the trigger free running or set it to I linear with a 0 threshold. +This is selected by the transition constellation or symbol synchronization signal toggle (B.13) -

    22.2: Symbol synchronization display

    +The X input is the discriminator signal and the Y input is the symbol synchronization signal that goes to the estimated maximum discriminator signal level when a zero crossing in the symbol synchronization control signal is detected and goes to mid position ((max - min) / 2) of the discriminator signal when a symbol period starts. -This is selected by the transition constellation or symbol synchronization signal toggle (see 7) +The symbol synchronization control signal is obtained by squaring the discriminator signal and passing it through a narrow second order bandpass filter centered on the symbol rate. Its zero crossing should occur close to the first fourth of a symbol period therefore when synchronization is ideal the Y input should go down to mid position in the first fourth of the symbol period. -![DSD scope](../../../doc/img/DSDdemod_plugin_scope2.png) +Ideally the figure should show a cloud of persistent points at the locations marked by points 0 to 3. Each one of these points represent an ideally decoded symbol. -
    22.2.1: IQ linear display
    +

    B.2: Symbol (Baud) rate

    -The I trace (yellow) is the discriminator signal and the Q trace (blue) is the symbol synchronization monitor trace that goes to the estimated maximum discriminator signal level when a zero crossing in the symbol synchronization control signal is detected and goes to mid position ((max - min) / 2) of the discriminator signal when a symbol period starts. +Here you can specify which symbol rate or Baud rate is expected. Choices are: -The symbol synchronization control signal is obtained by squaring the discriminator signal and passing it through a narrow second order bandpass filter centered on the symbol rate. Its zero crossing should occur close to the first fourth of a symbol period therefore when synchronization is ideal the Q trace (blue) should go down to mid position in the first fourth of the symbol period. + - `2.4k`: 2400 S/s used for dPMR and 4800 b/s NXDN + - `4.8k`: 4800 S/s used for 9600 b/s NXDN, DMR, D-Star and YSF. -
    22.2.2: Setting the display
    +

    B.3: Type of frame detected

    - - On the combo box you should choose IQ (lin) for the primary display and IQ (pol) for secondary display - - On the display buttons you should choose the first display (1) +This can be one of the following: -
    22.2.3: Timing settings
    + - `+DMRd`: non-inverted DMR data frame + - `+DMRv`: non-inverted DMR voice frame + - `+D-STAR`: non-inverted D-Star frame + - `-D-STAR`: inverted D-Star frame + - `+D-STAR_HD`: non-inverted D-Star header frame encountered + - `-D-STAR_HD`: inverted D-Star header frame encountered + - `+dPMR`: non-inverted dPMR non-packet frame + - `+NXDN`: non-inverted NXDN frame (detection only) + - `+YSF`: non-inverted Yaesu System Fusion frame (detection only) -You can choose any trace length with the third slider from the left however 100 ms will give you the best view. You may stretch further the display by reducing the full length to 20 ms or less using the first slider. You can move this 20 ms window across the 100 ms trace with the middle slider. +

    B.4: Matched filter toggle

    + +Normally you would always want to have a matched filter however on some strong D-Star signals more synchronization points could be obtained without. When engaged the background of the button is lit in orange. -
    22.2.4: I gain
    +

    B.5: Symbol PLL lock indicator

    -You should set the slider to a unity (1) span (+/- 0.5) with no offset. This corresponds to full range in optimal conditions (100%). You can set the slider fully to the left (2) for a +/- 1.0 spn if you don't exactly match these conditions. +Since dsdcc version 1.7.1 the synbol synchronization can be done with a PLL fed by a ringing filter (narrow passband) tuned at the symbol rate and itself fed with the squared magnitude of the discriminator signal. For signals strong enough to lock the PLL this works significantly better than with the ringing filter alone that was the only option in versions <= 1.6.0. Version 1.7.0 had the PLL enabled permanently. -
    22.2.5: Q gain
    +However with marginal signals the ringing filter alone and a few heuristics work better. This is why since DSDcc version 1.7.1 the PLL became optional. -You should set the slider to a unity (1) span (+/- 0.5) with no offset. This corresponds to full range in optimal conditions (100%). You can set the slider fully to the left (2) for a +/- 1.0 spn if you don't exactly match these conditions. +You can use this button to toggle between the two options: -
    22.2.6: Trigger settings
    + - with the locker icon in locked position: PLL is engaged + - with the locker icon in unlocked position: PLL is bypassed -You can leave the trigger free running or set it to I linear with a 0 threshold. +When in lock position the button lights itself in green when the PLL lock is acquired. Occasional drops may occur without noticeable impact on decoding. + +

    B.6: Symbol synchronization zero crossing hits in %

    + +This is the percentage per symbols for which a valid zero crossing has been detected. The more the better the symbol synchronization is tracked however the zero crossing shifts much not deviate too much from 0 (see next). + +With the PLL engaged the figure should be 100% all the time in presence of a locked signal. Occasional small drops may occur without noticeable impact on decoding. + +

    B.7: Zero crossing shift

    + +This is the current (at display polling time) zero crosing shift. It should be the closest to 0 as possible. However some jitter is acceptable for good symbol synchronization: + + - `2400 S/s`: +/- 5 inclusive + - `4800 S/s`: +/- 2 inclusive + +

    B.8: Discriminator input signal median level in %

    + +This is the estimated median level (center) of the discriminator input signal in percentage of half the total range. When the signal is correctly aligned in the input range it should be 0 + +

    B.9: Discriminator input signal level range in %

    + +This is the estimated discriminator input signal level range (max - min) in percentage of half the total range. For optimal decoding it should be maintained close to 100. + +

    B.10 to B.12: Two slot TDMA handling

    + +This is useful for two slot TDMA modes that is only DMR at present. FDMA modes are treated as using slot #1 only. + +![DSD TDMA handling](../../../doc/img/DSDdemod_plugin_tdma.png) + +
    B.10 (1): Slot #1 voice select
    + +Toggle button to select slot #1 voice output. When on waves appear on the icon. The icon turns green when voice frames are processed for this slot. For FDMA modes you may want to leave only this toggle on. + +
    B.11 (2): Slot #2 voice select
    + +Toggle button to select slot #2 voice output. When on waves appear on the icon. The icon turns green when voice frames are processed for this slot. For FDMA modes you may want to leave this toggle off. + +
    B.12 (3): TDMA stereo mode toggle
    + + - When off the icon shows a single loudspeaker. It mixes slot #1 and slot #2 voice as a mono audio signal + - When on the icon shows a pair of loudspeakers. It sends slot #1 vocie to the left stereo audio channel and slot #2 to the right one + +For FDMA standards you may want to leave this as mono mode. + +

    B.13: Transition constellation or symbol synchronization signal toggle

    + +Using this button you can either: + + - show the transitions constellation + - show a indicative signal about symbol synchronization + - when a zero crossing is detected the signal is set to estimated input discriminator signal maximum value + - when the symbol clock is 0 (start of symbol period) the signal is set to the estimated median point of the input discriminator signal + +

    B.14: Trace length

    + +This button tunes the length of the trace displayed on B.1. Units are milliseconds. Default value is 300. + +

    B.15: Trace stroke

    + +This button tunes the stroke of the points displayer on B.1. The trace has limited persistence based on alpha blending. This is the 8 bit unsigned integer value of the trace alpha blending. Default value is 100. + +

    B.16: Trace decay

    + +This button tunes the persistence decay of the points displayer on B.1. The trace has limited persistence based on alpha blending. This controls the alpha value of the black screen printed at the end of each trace and thus the trace points decay time. The value is 255 minus he displayed value using 8 bit unsigned integers. + + - A value of 0 yields no persistence + - A value of 255 yields infinite persistence + - Default value is 200s + +

    B.17: Maximum expected FM deviation

    + +This is the deviation in kHz leading to maximum (100%) deviation. You should aim for 30 to 50% (+/-300 to +/-500m) deviation on the scope display. + +

    B.18: Gain after discriminator

    + +This is the gain applied to the output of the discriminator before the decoder From a9887bcdaac74b199c46bb414b4739f2b08e0ecd Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 14 Mar 2018 06:30:45 +0100 Subject: [PATCH 109/956] LimeSDR input GUI: NCO and baseband center frequency now show actual values --- devices/limesdr/devicelimesdr.cpp | 2 +- .../limesdrinput/limesdrinputgui.cpp | 54 ++++++++++++++----- .../limesdrinput/limesdrinputgui.h | 4 +- .../limesdrinput/limesdrinputgui.ui | 23 ++++---- 4 files changed, 56 insertions(+), 27 deletions(-) diff --git a/devices/limesdr/devicelimesdr.cpp b/devices/limesdr/devicelimesdr.cpp index c091dbc7b..4e10cab52 100644 --- a/devices/limesdr/devicelimesdr.cpp +++ b/devices/limesdr/devicelimesdr.cpp @@ -50,7 +50,7 @@ bool DeviceLimeSDR::setNCOFrequency(lms_device_t *device, bool dir_tx, std::size return false; } - if (LMS_SetNCOIndex(device, dir_tx, chan, 0, !positive) < 0) + if (LMS_SetNCOIndex(device, dir_tx, chan, 0, positive) < 0) { fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot set conversion direction %sfreq\n", positive ? "+" : "-"); return false; diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index b440ab69b..d8385b32b 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -298,7 +298,7 @@ void LimeSDRInputGUI::displaySettings() ui->extClock->setExternalClockFrequency(m_settings.m_extClockFreq); ui->extClock->setExternalClockActive(m_settings.m_extClock); - ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + setCenterFrequencyDisplay(); ui->sampleRate->setValue(m_settings.m_devSampleRate); ui->dcOffset->setChecked(m_settings.m_dcBlock); @@ -347,11 +347,41 @@ void LimeSDRInputGUI::displaySettings() void LimeSDRInputGUI::setNCODisplay() { int ncoHalfRange = (m_settings.m_devSampleRate * (1<<(m_settings.m_log2HardDecim)))/2; - int lowBoundary = std::max(0, (int) m_settings.m_centerFrequency - ncoHalfRange); - ui->ncoFrequency->setValueRange(7, - lowBoundary/1000, - (m_settings.m_centerFrequency + ncoHalfRange)/1000); // frequency dial is in kHz - ui->ncoFrequency->setValue((m_settings.m_centerFrequency + m_settings.m_ncoFrequency)/1000); + ui->ncoFrequency->setValueRange( + false, + 8, + -ncoHalfRange, + ncoHalfRange); // frequency dial is in kHz + + ui->ncoFrequency->blockSignals(true); + ui->ncoFrequency->setValue(m_settings.m_ncoFrequency); + ui->ncoFrequency->blockSignals(false); +} + +void LimeSDRInputGUI::setCenterFrequencyDisplay() +{ + int64_t centerFrequency = m_settings.m_centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); + + if (m_settings.m_ncoEnable) { + centerFrequency += m_settings.m_ncoFrequency; + } + + ui->centerFrequency->blockSignals(true); + ui->centerFrequency->setValue(centerFrequency < 0 ? 0 : (uint64_t) centerFrequency/1000); // kHz + ui->centerFrequency->blockSignals(false); +} + +void LimeSDRInputGUI::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + if (m_settings.m_ncoEnable) { + centerFrequency -= m_settings.m_ncoFrequency; + } + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); } void LimeSDRInputGUI::sendSettings() @@ -455,28 +485,28 @@ void LimeSDRInputGUI::on_record_toggled(bool checked) void LimeSDRInputGUI::on_centerFrequency_changed(quint64 value) { - m_settings.m_centerFrequency = value * 1000; - setNCODisplay(); + setCenterFrequencySetting(value); sendSettings(); } -void LimeSDRInputGUI::on_ncoFrequency_changed(quint64 value) +void LimeSDRInputGUI::on_ncoFrequency_changed(qint64 value) { - m_settings.m_ncoFrequency = (int64_t) value - (int64_t) m_settings.m_centerFrequency/1000; - m_settings.m_ncoFrequency *= 1000; + m_settings.m_ncoFrequency = value; + setCenterFrequencyDisplay(); sendSettings(); } void LimeSDRInputGUI::on_ncoEnable_toggled(bool checked) { m_settings.m_ncoEnable = checked; + setCenterFrequencyDisplay(); sendSettings(); } void LimeSDRInputGUI::on_ncoReset_clicked(bool checked __attribute__((unused))) { m_settings.m_ncoFrequency = 0; - ui->ncoFrequency->setValue(m_settings.m_centerFrequency/1000); + ui->ncoFrequency->setValue(0); sendSettings(); } diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.h b/plugins/samplesource/limesdrinput/limesdrinputgui.h index 2bc328dfe..b8966d5ba 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.h +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.h @@ -69,6 +69,8 @@ private: void displaySettings(); void setNCODisplay(); + void setCenterFrequencyDisplay(); + void setCenterFrequencySetting(uint64_t kHzValue); void sendSettings(); void updateSampleRateAndFrequency(); void updateADCRate(); @@ -79,7 +81,7 @@ private slots: void on_startStop_toggled(bool checked); void on_record_toggled(bool checked); void on_centerFrequency_changed(quint64 value); - void on_ncoFrequency_changed(quint64 value); + void on_ncoFrequency_changed(qint64 value); void on_ncoEnable_toggled(bool checked); void on_ncoReset_clicked(bool checked); void on_dcOffset_toggled(bool checked); diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.ui b/plugins/samplesource/limesdrinput/limesdrinputgui.ui index 80c5b34ef..8abb76250 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.ui +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.ui @@ -35,16 +35,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -256,7 +247,7 @@ - + 0 @@ -281,14 +272,14 @@ PointingHandCursor - Center frequency with NCO engaged (kHz) + NCO frequency (Hz) - kHz + Hz @@ -1166,6 +1157,12 @@ QToolTip{background-color: white; color: black;} QToolButton
    gui/externalclockbutton.h
    + + ValueDialZ + QWidget +
    gui/valuedialz.h
    + 1 +
    From 5a07823b4efe5fc25cd42508b917d17fff7a432f Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 14 Mar 2018 06:54:27 +0100 Subject: [PATCH 110/956] LimeSDR output GUI: NCO and baseband center frequencies now show actual values --- .../limesdroutput/limesdroutputgui.cpp | 54 ++++++++++++++----- .../limesdroutput/limesdroutputgui.h | 4 +- .../limesdroutput/limesdroutputgui.ui | 23 ++++---- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp index 9cd33f6d1..b2c20c87f 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp @@ -298,7 +298,7 @@ void LimeSDROutputGUI::displaySettings() ui->extClock->setExternalClockFrequency(m_settings.m_extClockFreq); ui->extClock->setExternalClockActive(m_settings.m_extClock); - ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + setCenterFrequencyDisplay(); ui->sampleRate->setValue(m_settings.m_devSampleRate); ui->hwInterp->setCurrentIndex(m_settings.m_log2HardInterp); @@ -324,11 +324,41 @@ void LimeSDROutputGUI::displaySettings() void LimeSDROutputGUI::setNCODisplay() { int ncoHalfRange = (m_settings.m_devSampleRate * (1<<(m_settings.m_log2HardInterp)))/2; - int lowBoundary = std::max(0, (int) m_settings.m_centerFrequency - ncoHalfRange); - ui->ncoFrequency->setValueRange(7, - lowBoundary/1000, - (m_settings.m_centerFrequency + ncoHalfRange)/1000); // frequency dial is in kHz - ui->ncoFrequency->setValue((m_settings.m_centerFrequency + m_settings.m_ncoFrequency)/1000); + ui->ncoFrequency->setValueRange( + false, + 8, + -ncoHalfRange, + ncoHalfRange); // frequency dial is in kHz + + ui->ncoFrequency->blockSignals(true); + ui->ncoFrequency->setValue(m_settings.m_ncoFrequency); + ui->ncoFrequency->blockSignals(false); +} + +void LimeSDROutputGUI::setCenterFrequencyDisplay() +{ + int64_t centerFrequency = m_settings.m_centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); + + if (m_settings.m_ncoEnable) { + centerFrequency += m_settings.m_ncoFrequency; + } + + ui->centerFrequency->blockSignals(true); + ui->centerFrequency->setValue(centerFrequency < 0 ? 0 : (uint64_t) centerFrequency/1000); // kHz + ui->centerFrequency->blockSignals(false); +} + +void LimeSDROutputGUI::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + if (m_settings.m_ncoEnable) { + centerFrequency -= m_settings.m_ncoFrequency; + } + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); } void LimeSDROutputGUI::sendSettings() @@ -420,28 +450,28 @@ void LimeSDROutputGUI::on_startStop_toggled(bool checked) void LimeSDROutputGUI::on_centerFrequency_changed(quint64 value) { - m_settings.m_centerFrequency = value * 1000; - setNCODisplay(); + setCenterFrequencySetting(value); sendSettings(); } -void LimeSDROutputGUI::on_ncoFrequency_changed(quint64 value) +void LimeSDROutputGUI::on_ncoFrequency_changed(qint64 value) { - m_settings.m_ncoFrequency = (int64_t) value - (int64_t) m_settings.m_centerFrequency/1000; - m_settings.m_ncoFrequency *= 1000; + m_settings.m_ncoFrequency = value; + setCenterFrequencyDisplay(); sendSettings(); } void LimeSDROutputGUI::on_ncoEnable_toggled(bool checked) { m_settings.m_ncoEnable = checked; + setCenterFrequencyDisplay(); sendSettings(); } void LimeSDROutputGUI::on_ncoReset_clicked(bool checked __attribute__((unused))) { m_settings.m_ncoFrequency = 0; - ui->ncoFrequency->setValue(m_settings.m_centerFrequency/1000); + ui->ncoFrequency->setValue(0); sendSettings(); } diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.h b/plugins/samplesink/limesdroutput/limesdroutputgui.h index 301722131..59f050bc1 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.h +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.h @@ -70,6 +70,8 @@ private: void displaySettings(); void setNCODisplay(); + void setCenterFrequencyDisplay(); + void setCenterFrequencySetting(uint64_t kHzValue); void sendSettings(); void updateSampleRateAndFrequency(); void updateDACRate(); @@ -79,7 +81,7 @@ private slots: void handleInputMessages(); void on_startStop_toggled(bool checked); void on_centerFrequency_changed(quint64 value); - void on_ncoFrequency_changed(quint64 value); + void on_ncoFrequency_changed(qint64 value); void on_ncoEnable_toggled(bool checked); void on_ncoReset_clicked(bool checked); void on_sampleRate_changed(quint64 value); diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.ui b/plugins/samplesink/limesdroutput/limesdroutputgui.ui index 487855139..247eb4b5a 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.ui +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.ui @@ -35,16 +35,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -233,7 +224,7 @@ - + 0 @@ -256,14 +247,14 @@ PointingHandCursor - Center frequency with NCO engaged (kHz) + NCO frequency (Hz) - kHz + Hz @@ -938,6 +929,12 @@ QToolTip{background-color: white; color: black;} QToolButton
    gui/externalclockbutton.h
    + + ValueDialZ + QWidget +
    gui/valuedialz.h
    + 1 +
    From 71686e6c45b22edaaa928db854031f2147c6fe9c Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 14 Mar 2018 08:37:40 +0100 Subject: [PATCH 111/956] Revert "Removed LimeSDR support from all builds" This reverts commit d0599a2ec0fd5879d41e4cfc2dd8399de2c24f75. --- plugins/samplesink/CMakeLists.txt | 2 +- plugins/samplesource/CMakeLists.txt | 2 +- sdrangel.windows.pro | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index 73a0974b1..179ba540e 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -31,7 +31,7 @@ endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) if (BUILD_DEBIAN) add_subdirectory(bladerfoutput) add_subdirectory(hackrfoutput) -# add_subdirectory(limesdroutput) + add_subdirectory(limesdroutput) if (LIBNANOMSG_FOUND) add_subdirectory(sdrdaemonsink) endif (LIBNANOMSG_FOUND) diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index f5d393dde..3bfb5c675 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -87,7 +87,7 @@ if (BUILD_DEBIAN) add_subdirectory(rtlsdr) add_subdirectory(bladerfinput) add_subdirectory(sdrplay) -# add_subdirectory(limesdrinput) + add_subdirectory(limesdrinput) add_subdirectory(perseus) add_subdirectory(plutosdrinput) endif (BUILD_DEBIAN) diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index 5afd9d2ed..8bf10973c 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -36,7 +36,7 @@ SUBDIRS += plugins/samplesource/airspyhf SUBDIRS += plugins/samplesource/bladerfinput SUBDIRS += plugins/samplesource/filesource SUBDIRS += plugins/samplesource/hackrfinput -#SUBDIRS += plugins/samplesource/limesdrinput +SUBDIRS += plugins/samplesource/limesdrinput SUBDIRS += plugins/samplesource/plutosdrinput CONFIG(MINGW64)SUBDIRS += plugins/samplesource/sdrdaemonsource SUBDIRS += plugins/samplesource/rtlsdr @@ -44,7 +44,7 @@ SUBDIRS += plugins/samplesource/testsource SUBDIRS += plugins/samplesink/filesink SUBDIRS += plugins/samplesink/bladerfoutput SUBDIRS += plugins/samplesink/hackrfoutput -#SUBDIRS += plugins/samplesink/limesdroutput +SUBDIRS += plugins/samplesink/limesdroutput SUBDIRS += plugins/samplesink/plutosdroutput SUBDIRS += plugins/channelrx/chanalyzer SUBDIRS += plugins/channelrx/chanalyzerng From 5d7b56ffc7654b28a6b13745c1693d304cb5438e Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 14 Mar 2018 08:40:05 +0100 Subject: [PATCH 112/956] LimeSDR: updated documentation --- Readme.md | 2 -- doc/img/LimeSDRInput_plugin.png | Bin 35321 -> 34382 bytes doc/img/LimeSDRInput_plugin.xcf | Bin 198336 -> 197514 bytes doc/img/LimeSDRInput_plugin_2.png | Bin 8084 -> 7135 bytes doc/img/LimeSDRInput_plugin_2.xcf | Bin 36115 -> 52523 bytes doc/img/LimeSDROutput_plugin.png | Bin 35706 -> 35180 bytes doc/img/LimeSDROutput_plugin.xcf | Bin 201896 -> 233376 bytes plugins/samplesink/limesdroutput/readme.md | 4 ++-- plugins/samplesource/limesdrinput/readme.md | 2 +- 9 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Readme.md b/Readme.md index 014d269dc..8b3036135 100644 --- a/Readme.md +++ b/Readme.md @@ -117,8 +117,6 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3.

    LimeSDR

    -

    ⚠ Source code only. Due to instability of recent versions of Lime Suite the "official" support cannot be maintained. You can still compile the plugins from source and see for yourself

    - [LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next).

    ⚠ The plugins should work normally when running as single instances. Support of many Rx and/or Tx instances running concurrently is considered experimental. At least you should always have one of the streams running.

    diff --git a/doc/img/LimeSDRInput_plugin.png b/doc/img/LimeSDRInput_plugin.png index 32023ff73e7bd268ede9bc9a89ebeb3d537d9f0c..9cbe9b4092635260296509f8436cbdd51b7365a8 100644 GIT binary patch literal 34382 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
    {j+%QE-qsHnbuj{z-beQO`%*@p2 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 MRUVYydm8wE0Zpq7T>t<8 literal 35321 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 Q4gSbVDM{u(HhlNL059Vkj{pDw diff --git a/doc/img/LimeSDRInput_plugin.xcf b/doc/img/LimeSDRInput_plugin.xcf index 7aca4d4cda564eeba07b7a391eba0c9e26023f44..8c97946e5e8cccf81360c316017a6db372af3bee 100644 GIT binary patch delta 15908 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%0ADACEKgx59iIP=L5I$p~9=$yHZwgc@9t~|B% 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 diff --git a/doc/img/LimeSDROutput_plugin.png b/doc/img/LimeSDROutput_plugin.png index fb07270f8617d7968bf7f45ceae6b130516ab583..95367eba14b1cfc6395c69bb4d4854cccdb47081 100644 GIT binary patch literal 35180 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 diff --git a/doc/img/LimeSDROutput_plugin.xcf b/doc/img/LimeSDROutput_plugin.xcf index 766fba2251f1ce50dc66384a5605df22645ba86a..d9a396a016841a32495cb9046f53c9e438006a2b 100644 GIT binary patch delta 27654 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
    hUfL@>v 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 G{(k{zzvT@8 diff --git a/plugins/samplesink/limesdroutput/readme.md b/plugins/samplesink/limesdroutput/readme.md index c6a8c47b9..3b8d254ef 100644 --- a/plugins/samplesink/limesdroutput/readme.md +++ b/plugins/samplesink/limesdroutput/readme.md @@ -77,9 +77,9 @@ Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an ind Use this push button to reset the NCO frequency to 0 and thus center on the main passband of the DAC. -

    7: Center frequency with NCO engaged

    +

    7: NCO frequency

    -This is the center frequency of the mix of LO and NCO combined and is the sink passband center frequency when the NCO is engaged. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. +This is the NCO frequency. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. ☞ In the LMS7002M TSP block the NCO sits after the interpolator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual DAC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware interpolation factor. For example with a 4 MS/s device to host sample rate (10) and a hadrware interpolation of 16 (8) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). diff --git a/plugins/samplesource/limesdrinput/readme.md b/plugins/samplesource/limesdrinput/readme.md index ad2632985..60a807a50 100644 --- a/plugins/samplesource/limesdrinput/readme.md +++ b/plugins/samplesource/limesdrinput/readme.md @@ -83,7 +83,7 @@ USe this push button to reset the NCO frequency to 0 and thus center on the main

    2.3: Center frequency with NCO engaged

    -This is the center frequency of the mix of LO and NCO combined and is the source passband center frequency when the NCO is engaged. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. +This is the NCO frequency. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. ☞ In the LMS7002M TSP block the NCO sits before the decimator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual ADC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware decimation factor. For example with a 4 MS/s device to host sample rate (5) and a hadrware decimation of 16 (3) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). From c99693eacca9d7e511b4dc04763f70dd1060e09b Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 14 Mar 2018 11:03:22 +0100 Subject: [PATCH 113/956] LimeSDR: removed useless step information from range inspection functions --- .../samplesink/limesdroutput/limesdroutput.cpp | 15 ++++++--------- plugins/samplesink/limesdroutput/limesdroutput.h | 6 +++--- .../samplesink/limesdroutput/limesdroutputgui.cpp | 8 ++++---- .../samplesource/limesdrinput/limesdrinput.cpp | 15 ++++++--------- plugins/samplesource/limesdrinput/limesdrinput.h | 6 +++--- .../samplesource/limesdrinput/limesdrinputgui.cpp | 8 ++++---- 6 files changed, 26 insertions(+), 32 deletions(-) diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp index 6650f3618..b4151d43e 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp @@ -462,31 +462,28 @@ std::size_t LimeSDROutput::getChannelIndex() return m_deviceShared.m_channel; } -void LimeSDROutput::getLORange(float& minF, float& maxF, float& stepF) const +void LimeSDROutput::getLORange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_loRangeTx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDROutput::getLORange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDROutput::getLORange: min: %f max: %f", range.min, range.max); } -void LimeSDROutput::getSRRange(float& minF, float& maxF, float& stepF) const +void LimeSDROutput::getSRRange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_srRangeTx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDROutput::getSRRange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDROutput::getSRRange: min: %f max: %f", range.min, range.max); } -void LimeSDROutput::getLPRange(float& minF, float& maxF, float& stepF) const +void LimeSDROutput::getLPRange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_lpfRangeTx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDROutput::getLPRange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDROutput::getLPRange: min: %f max: %f", range.min, range.max); } uint32_t LimeSDROutput::getHWLog2Interp() const diff --git a/plugins/samplesink/limesdroutput/limesdroutput.h b/plugins/samplesink/limesdroutput/limesdroutput.h index 7c064b924..e51d58675 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.h +++ b/plugins/samplesink/limesdroutput/limesdroutput.h @@ -217,9 +217,9 @@ public: QString& errorMessage); std::size_t getChannelIndex(); - void getLORange(float& minF, float& maxF, float& stepF) const; - void getSRRange(float& minF, float& maxF, float& stepF) const; - void getLPRange(float& minF, float& maxF, float& stepF) const; + void getLORange(float& minF, float& maxF) const; + void getSRRange(float& minF, float& maxF) const; + void getLPRange(float& minF, float& maxF) const; uint32_t getHWLog2Interp() const; private: diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp index b2c20c87f..de4515204 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp @@ -42,17 +42,17 @@ LimeSDROutputGUI::LimeSDROutputGUI(DeviceUISet *deviceUISet, QWidget* parent) : ui->setupUi(this); - float minF, maxF, stepF; + float minF, maxF; - m_limeSDROutput->getLORange(minF, maxF, stepF); + m_limeSDROutput->getLORange(minF, maxF); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, ((uint32_t) minF)/1000, ((uint32_t) maxF)/1000); // frequency dial is in kHz - m_limeSDROutput->getSRRange(minF, maxF, stepF); + m_limeSDROutput->getSRRange(minF, maxF); ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); ui->sampleRate->setValueRange(8, (uint32_t) minF, (uint32_t) maxF); - m_limeSDROutput->getLPRange(minF, maxF, stepF); + m_limeSDROutput->getLPRange(minF, maxF); ui->lpf->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); ui->lpf->setValueRange(6, (minF/1000)+1, maxF/1000); diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index da15bf064..b5e6f2457 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -481,31 +481,28 @@ std::size_t LimeSDRInput::getChannelIndex() return m_deviceShared.m_channel; } -void LimeSDRInput::getLORange(float& minF, float& maxF, float& stepF) const +void LimeSDRInput::getLORange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_loRangeRx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDRInput::getLORange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDRInput::getLORange: min: %f max: %f", range.min, range.max); } -void LimeSDRInput::getSRRange(float& minF, float& maxF, float& stepF) const +void LimeSDRInput::getSRRange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_srRangeRx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDRInput::getSRRange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDRInput::getSRRange: min: %f max: %f", range.min, range.max); } -void LimeSDRInput::getLPRange(float& minF, float& maxF, float& stepF) const +void LimeSDRInput::getLPRange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_lpfRangeRx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDRInput::getLPRange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDRInput::getLPRange: min: %f max: %f", range.min, range.max); } uint32_t LimeSDRInput::getHWLog2Decim() const diff --git a/plugins/samplesource/limesdrinput/limesdrinput.h b/plugins/samplesource/limesdrinput/limesdrinput.h index 398e8496f..629a5f131 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.h +++ b/plugins/samplesource/limesdrinput/limesdrinput.h @@ -238,9 +238,9 @@ public: QString& errorMessage); std::size_t getChannelIndex(); - void getLORange(float& minF, float& maxF, float& stepF) const; - void getSRRange(float& minF, float& maxF, float& stepF) const; - void getLPRange(float& minF, float& maxF, float& stepF) const; + void getLORange(float& minF, float& maxF) const; + void getSRRange(float& minF, float& maxF) const; + void getLPRange(float& minF, float& maxF) const; uint32_t getHWLog2Decim() const; private: diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index d8385b32b..a4bec47d2 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -45,17 +45,17 @@ LimeSDRInputGUI::LimeSDRInputGUI(DeviceUISet *deviceUISet, QWidget* parent) : ui->setupUi(this); - float minF, maxF, stepF; + float minF, maxF; - m_limeSDRInput->getLORange(minF, maxF, stepF); + m_limeSDRInput->getLORange(minF, maxF); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, ((uint32_t) minF)/1000, ((uint32_t) maxF)/1000); // frequency dial is in kHz - m_limeSDRInput->getSRRange(minF, maxF, stepF); + m_limeSDRInput->getSRRange(minF, maxF); ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); ui->sampleRate->setValueRange(8, (uint32_t) minF, (uint32_t) maxF); - m_limeSDRInput->getLPRange(minF, maxF, stepF); + m_limeSDRInput->getLPRange(minF, maxF); ui->lpf->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); ui->lpf->setValueRange(6, (minF/1000)+1, maxF/1000); From 243235b75fcaccff7f9b12af2b2eaffb42793ec7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 14 Mar 2018 19:52:54 +0100 Subject: [PATCH 114/956] LimeSDR: GUI cosmetic changea --- .../limesdroutput/limesdroutputgui.cpp | 3 ++- .../limesdroutput/limesdroutputgui.ui | 6 +++--- plugins/samplesink/limesdroutput/readme.md | 4 ++-- .../limesdrinput/limesdrinputgui.cpp | 3 ++- .../limesdrinput/limesdrinputgui.ui | 19 ++++++++++++++++--- plugins/samplesource/limesdrinput/readme.md | 4 ++-- 6 files changed, 27 insertions(+), 12 deletions(-) diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp index de4515204..9e08cdaa4 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp @@ -328,9 +328,10 @@ void LimeSDROutputGUI::setNCODisplay() false, 8, -ncoHalfRange, - ncoHalfRange); // frequency dial is in kHz + ncoHalfRange); ui->ncoFrequency->blockSignals(true); + ui->ncoFrequency->setToolTip(QString("NCO frequency shift in Hz (Range: +/- %1 kHz)").arg(ncoHalfRange/1000)); ui->ncoFrequency->setValue(m_settings.m_ncoFrequency); ui->ncoFrequency->blockSignals(false); } diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.ui b/plugins/samplesink/limesdroutput/limesdroutputgui.ui index 247eb4b5a..008191154 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.ui +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.ui @@ -6,7 +6,7 @@ 0 0 - 350 + 360 290 @@ -18,7 +18,7 @@ - 350 + 360 290 @@ -247,7 +247,7 @@ PointingHandCursor - NCO frequency (Hz) + NCO frequency shift in Hz diff --git a/plugins/samplesink/limesdroutput/readme.md b/plugins/samplesink/limesdroutput/readme.md index 3b8d254ef..d21f885db 100644 --- a/plugins/samplesink/limesdroutput/readme.md +++ b/plugins/samplesink/limesdroutput/readme.md @@ -77,9 +77,9 @@ Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an ind Use this push button to reset the NCO frequency to 0 and thus center on the main passband of the DAC. -

    7: NCO frequency

    +

    7: NCO frequency shift

    -This is the NCO frequency. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. +This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of transmission minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. ☞ In the LMS7002M TSP block the NCO sits after the interpolator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual DAC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware interpolation factor. For example with a 4 MS/s device to host sample rate (10) and a hadrware interpolation of 16 (8) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index a4bec47d2..26b3b4824 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -351,9 +351,10 @@ void LimeSDRInputGUI::setNCODisplay() false, 8, -ncoHalfRange, - ncoHalfRange); // frequency dial is in kHz + ncoHalfRange); ui->ncoFrequency->blockSignals(true); + ui->ncoFrequency->setToolTip(QString("NCO frequency shift in Hz (Range: +/- %1 kHz)").arg(ncoHalfRange/1000)); ui->ncoFrequency->setValue(m_settings.m_ncoFrequency); ui->ncoFrequency->blockSignals(false); } diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.ui b/plugins/samplesource/limesdrinput/limesdrinputgui.ui index 8abb76250..3c493322c 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.ui +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.ui @@ -6,7 +6,7 @@ 0 0 - 374 + 360 290 @@ -18,7 +18,7 @@ - 350 + 360 290 @@ -272,7 +272,7 @@ PointingHandCursor - NCO frequency (Hz) + NCO frequency shift in Hz @@ -585,6 +585,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/plugins/samplesource/limesdrinput/readme.md b/plugins/samplesource/limesdrinput/readme.md index 60a807a50..3af01b446 100644 --- a/plugins/samplesource/limesdrinput/readme.md +++ b/plugins/samplesource/limesdrinput/readme.md @@ -81,9 +81,9 @@ Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an ind USe this push button to reset the NCO frequency to 0 and thus center on the main passband of the ADC. -

    2.3: Center frequency with NCO engaged

    +

    2.3: NCO frequency shift

    -This is the NCO frequency. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. +This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of reception minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. ☞ In the LMS7002M TSP block the NCO sits before the decimator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual ADC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware decimation factor. For example with a 4 MS/s device to host sample rate (5) and a hadrware decimation of 16 (3) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). From d1bb70a32cb715d72914e0a4c280a97653e3bb5e Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 14 Mar 2018 23:04:50 +0100 Subject: [PATCH 115/956] Updated Debian changelog and documentation --- Readme.md | 2 +- debian/changelog | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 8b3036135..8c0b4d340 100644 --- a/Readme.md +++ b/Readme.md @@ -123,7 +123,7 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3.

    ⚠ It seems LimeSDR mini has trouble working with host sample rates lower than 2.5 MS/s particularly in Tx mode.

    -You will need a minimal installation of LimeSuite. Presently commit 04b57e0 or later should be used with its corresponding firmware (v4) and gateware (v2.12) installed in the LimeSDR device: +You will need a minimal installation of LimeSuite. Presently commit 90c3991 or later should be used with its corresponding firmware (v4) and gateware (v2.14) installed in the LimeSDR device. LimeSDR Mini gateware v1.24 should be used. - `sudo apt-get install libsqlite3-dev` - `git clone https://github.com/myriadrf/LimeSuite.git` diff --git a/debian/changelog b/debian/changelog index b91448549..7aca10ad5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,6 +2,8 @@ sdrangel (3.13.0-1) unstable; urgency=medium * DATV (Digital Amateur TV) demodulator. * Option to use RTP protocol for UDP audio for AM, NFM, SSB, WFM. + * LimeSDR: show NCO and center frequency actual values + * DSD demod: new simplified symbol scope display. Reworked GUI. -- Edouard Griffiths, F4EXB Sun, 11 Mar 2018 06:14:18 +0100 From 8d984c2f09ba35b12f29c4bbdbfb6a24358b0e5a Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 15 Mar 2018 00:16:50 +0100 Subject: [PATCH 116/956] DSD demod: TV screen graticule optimization --- plugins/channelrx/demoddsd/dsddemodgui.cpp | 2 + sdrgui/dsp/scopevisxy.cpp | 51 ++++++++++++++++------ sdrgui/dsp/scopevisxy.h | 3 ++ sdrgui/gui/glshadertvarray.h | 2 - sdrgui/gui/tvscreen.cpp | 9 +++- sdrgui/gui/tvscreen.h | 3 ++ 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 7d658f146..387c406cf 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -317,6 +317,8 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban } } + m_scopeVisXY->calculateGraticule(200,200); + m_dsdDemod = (DSDDemod*) rxChannel; //new DSDDemod(m_deviceUISet->m_deviceSourceAPI); m_dsdDemod->setScopeXYSink(m_scopeVisXY); m_dsdDemod->setMessageQueueToGUI(getInputMessageQueue()); diff --git a/sdrgui/dsp/scopevisxy.cpp b/sdrgui/dsp/scopevisxy.cpp index 1a237f32a..78fd7c539 100644 --- a/sdrgui/dsp/scopevisxy.cpp +++ b/sdrgui/dsp/scopevisxy.cpp @@ -71,14 +71,22 @@ void ScopeVisXY::feed(const SampleVector::const_iterator& cbegin, const SampleVe if (m_pixelCount == m_pixelsPerFrame) { - drawGraticule(); + int rows, cols; + m_tvScreen->getSize(rows, cols); + + if ((rows != m_rows) || (cols != m_cols)) + { + calculateGraticule(rows, cols); + m_rows = rows; + m_cols = cols; + } + + drawGraticule(); m_tvScreen->renderImage(0); usleep(5000); - m_tvScreen->getSize(m_cols, m_rows); m_tvScreen->resetImage(m_alphaReset); m_pixelCount = 0; } - } } @@ -103,22 +111,37 @@ void ScopeVisXY::clearGraticule() { m_graticule.clear(); } -void ScopeVisXY::drawGraticule() +void ScopeVisXY::calculateGraticule(int rows, int cols) { - std::vector >::const_iterator grIt = m_graticule.begin(); + m_graticuleRows.clear(); + m_graticuleCols.clear(); - for (; grIt != m_graticule.end(); ++grIt) - { - int y = m_rows * ((1.0 - grIt->imag()) / 2.0); - int x = m_cols * ((1.0 + grIt->real()) / 2.0); + std::vector >::const_iterator grIt = m_graticule.begin(); + + for (; grIt != m_graticule.end(); ++grIt) + { + int y = rows * ((1.0 - grIt->imag()) / 2.0); + int x = cols * ((1.0 + grIt->real()) / 2.0); for (int d = -4; d <= 4; ++d) { - m_tvScreen->selectRow(y + d); - m_tvScreen->setDataColor(x, qRed(m_gridRGB), qGreen(m_gridRGB), qBlue(m_gridRGB)); - m_tvScreen->selectRow(y); - m_tvScreen->setDataColor(x + d, qRed(m_gridRGB), qGreen(m_gridRGB), qBlue(m_gridRGB)); + m_graticuleRows.push_back(y+d); + m_graticuleCols.push_back(x); + m_graticuleRows.push_back(y); + m_graticuleCols.push_back(x+d); } - } + } +} + +void ScopeVisXY::drawGraticule() +{ + std::vector::const_iterator rowIt = m_graticuleRows.begin(); + std::vector::const_iterator colIt = m_graticuleCols.begin(); + + for(; (rowIt != m_graticuleRows.end()) && (colIt != m_graticuleCols.end()); ++rowIt, ++colIt) + { + m_tvScreen->selectRow(*rowIt); + m_tvScreen->setDataColor(*colIt, qRed(m_gridRGB), qGreen(m_gridRGB), qBlue(m_gridRGB)); + } } diff --git a/sdrgui/dsp/scopevisxy.h b/sdrgui/dsp/scopevisxy.h index 4eedf3860..6a70f508c 100644 --- a/sdrgui/dsp/scopevisxy.h +++ b/sdrgui/dsp/scopevisxy.h @@ -47,6 +47,7 @@ public: void setGridRGB(const QRgb& gridRGB) { m_gridRGB = gridRGB; } void addGraticulePoint(const std::complex& z); + void calculateGraticule(int rows, int cols); void clearGraticule(); private: @@ -63,6 +64,8 @@ private: QRgb m_plotRGB; QRgb m_gridRGB; std::vector > m_graticule; + std::vector m_graticuleRows; + std::vector m_graticuleCols; }; diff --git a/sdrgui/gui/glshadertvarray.h b/sdrgui/gui/glshadertvarray.h index 5c5044743..f331ee4fa 100644 --- a/sdrgui/gui/glshadertvarray.h +++ b/sdrgui/gui/glshadertvarray.h @@ -46,8 +46,6 @@ public: void setAlphaBlend(bool blnAlphaBlend) { m_blnAlphaBlend = blnAlphaBlend; } void setAlphaReset() { m_blnAlphaReset = true; } void InitializeGL(int intCols, int intRows); - void ResizeContainer(int intCols, int intRows); - void getSize(int& intCols, int& intRows) const { intCols = m_intCols, intRows = m_intRows; } void Cleanup(); QRgb *GetRowBuffer(int intRow); void RenderPixels(unsigned char *chrData); diff --git a/sdrgui/gui/tvscreen.cpp b/sdrgui/gui/tvscreen.cpp index b41d471b0..1a81f36b4 100644 --- a/sdrgui/gui/tvscreen.cpp +++ b/sdrgui/gui/tvscreen.cpp @@ -43,7 +43,8 @@ TVScreen::TVScreen(bool blnColor, QWidget* parent) : //Par défaut m_intAskedCols = TV_COLS; m_intAskedRows = TV_ROWS; - + m_cols = TV_COLS; + m_rows = TV_ROWS; } TVScreen::~TVScreen() @@ -88,13 +89,17 @@ void TVScreen::resetImage(int alpha) void TVScreen::resizeTVScreen(int intCols, int intRows) { + qDebug("TVScreen::resizeTVScreen: cols: %d, rows: %d", intCols, intRows); m_intAskedCols = intCols; m_intAskedRows = intRows; + m_cols = intCols; + m_rows = intRows; } void TVScreen::getSize(int& intCols, int& intRows) const { - m_objGLShaderArray.getSize(intCols, intRows); + intCols = m_cols; + intRows = m_rows; } void TVScreen::initializeGL() diff --git a/sdrgui/gui/tvscreen.h b/sdrgui/gui/tvscreen.h index f100b9e66..c9969a475 100644 --- a/sdrgui/gui/tvscreen.h +++ b/sdrgui/gui/tvscreen.h @@ -84,6 +84,9 @@ private: GLShaderTVArray m_objGLShaderArray; + int m_cols; + int m_rows; + void initializeGL(); void resizeGL(int width, int height); void paintGL(); From 09905c362a86261a9835f8e7c05186dd7509b6ef Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 15 Mar 2018 00:58:18 +0100 Subject: [PATCH 117/956] DATV demod: TV screen graticule optimization --- .../channelrx/demoddatv/datvconstellation.h | 52 +++++++++++++------ plugins/channelrx/demoddatv/datvdemod.cpp | 1 + 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvconstellation.h b/plugins/channelrx/demoddatv/datvconstellation.h index 9cd92ecf3..3004a7196 100644 --- a/plugins/channelrx/demoddatv/datvconstellation.h +++ b/plugins/channelrx/demoddatv/datvconstellation.h @@ -20,6 +20,7 @@ #define DATVCONSTELLATION_H #include +#include #include "leansdr/framework.h" #include "gui/tvscreen.h" @@ -38,6 +39,8 @@ template struct datvconstellation: runnable TVScreen *m_objDATVScreen; pipereader > in; unsigned long phase; + std::vector cstln_rows; + std::vector cstln_cols; datvconstellation(scheduler *sch, pipebuf > &_in, T _xymin, T _xymax, const char *_name = 0, TVScreen *objDATVScreen = 0) : runnable(sch, _name ? _name : _in.name), @@ -45,7 +48,7 @@ template struct datvconstellation: runnable xymax(_xymax), decimation(DEFAULT_GUI_DECIMATION), pixels_per_frame(1024), - cstln(NULL), + cstln(0), m_objDATVScreen(objDATVScreen), in(_in), phase(0) @@ -66,9 +69,8 @@ template struct datvconstellation: runnable for (; p < pend; ++p) { - if (m_objDATVScreen != NULL) + if (m_objDATVScreen != 0) { - m_objDATVScreen->selectRow(256 * (p->re - xymin) / (xymax - xymin)); m_objDATVScreen->setDataColor(256 - 256 * ((p->im - xymin) / (xymax - xymin)), 255, 0, 255); } @@ -79,23 +81,17 @@ template struct datvconstellation: runnable { // Plot constellation points - for (int i = 0; i < (*cstln)->nsymbols; ++i) - { - complex *p = &(*cstln)->symbols[i]; - int x = 256 * (p->re - xymin) / (xymax - xymin); - int y = 256 - 256 * (p->im - xymin) / (xymax - xymin); + std::vector::const_iterator row_it = cstln_rows.begin(); + std::vector::const_iterator col_it = cstln_cols.begin(); - for (int d = -4; d <= 4; ++d) - { - m_objDATVScreen->selectRow(x + d); - m_objDATVScreen->setDataColor(y, 5, 250, 250); - m_objDATVScreen->selectRow(x); - m_objDATVScreen->setDataColor(y + d, 5, 250, 250); - } + for (; (row_it != cstln_rows.end()) && (col_it != cstln_cols.end()); ++row_it, ++col_it) + { + m_objDATVScreen->selectRow(*row_it); + m_objDATVScreen->setDataColor(*col_it, 250, 250, 5); } } - m_objDATVScreen->renderImage(NULL); + m_objDATVScreen->renderImage(0); } in.read(pixels_per_frame); @@ -111,6 +107,30 @@ template struct datvconstellation: runnable { } + void calculate_cstln_points() + { + if (!(*cstln)) { + return; + } + + cstln_rows.clear(); + cstln_cols.clear(); + + for (int i = 0; i < (*cstln)->nsymbols; ++i) + { + complex *p = &(*cstln)->symbols[i]; + int x = 256 * (p->re - xymin) / (xymax - xymin); + int y = 256 - 256 * (p->im - xymin) / (xymax - xymin); + + for (int d = -4; d <= 4; ++d) + { + cstln_rows.push_back(x + d); + cstln_cols.push_back(y); + cstln_rows.push_back(x); + cstln_cols.push_back(y + d); + } + } + } }; } diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 25d78b408..87caea8b7 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -701,6 +701,7 @@ void DATVDemod::InitDATVFramework() r_scope_symbols = new leansdr::datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredTVScreen); r_scope_symbols->decimation = 1; r_scope_symbols->cstln = &m_objDemodulator->cstln; + r_scope_symbols->calculate_cstln_points(); } // DECONVOLUTION AND SYNCHRONIZATION From 0157c950f477f5c0a897d688076165a9f015122c Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 15 Mar 2018 01:22:40 +0100 Subject: [PATCH 118/956] DSD demod: GUI cosmetic change --- plugins/channelrx/demoddsd/dsddemodgui.ui | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index a37a7064a..bbb015427 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -308,6 +308,19 @@
    + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -415,19 +428,6 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - From 50c977002c219f47821f733a451437e9d3a9e6fa Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 15 Mar 2018 17:37:36 +0100 Subject: [PATCH 119/956] Windows build: corrections and compile with latest version of LimeSuite --- liblimesuite/liblimesuite.pro | 120 ++++++++++-------------- liblimesuite/src/BuiltinConnections.cpp | 18 ++-- liblimesuite/src/SystemResources.cpp | 16 ++-- sdrgui/sdrgui.pro | 4 + 4 files changed, 69 insertions(+), 89 deletions(-) diff --git a/liblimesuite/liblimesuite.pro b/liblimesuite/liblimesuite.pro index e119a1a0a..e56d5e531 100644 --- a/liblimesuite/liblimesuite.pro +++ b/liblimesuite/liblimesuite.pro @@ -10,6 +10,7 @@ TEMPLATE = lib TARGET = liblimesuite DEFINES += ENOLINK=21 +DEFINES += "__unix__" QMAKE_CXXFLAGS += -fpermissive QMAKE_CXXFLAGS += -std=c++11 @@ -17,7 +18,7 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" CONFIG(MINGW32):INCLUDEPATH += "..\libsqlite3\src" @@ -34,89 +35,62 @@ INCLUDEPATH += $$LIBLIMESUITESRC/src/Si5351C INCLUDEPATH += $$LIBLIMESUITESRC/src/protocols INCLUDEPATH += $$LIBLIMESUITESRC/external/cpp-feather-ini-parser -SOURCES = $$LIBLIMESUITESRC/src/ADF4002/ADF4002.cpp\ - $$LIBLIMESUITESRC/src/API/lms7_api.cpp\ - $$LIBLIMESUITESRC/src/API/lms7_device.cpp\ - $$LIBLIMESUITESRC/src/API/LimeSDR_mini.cpp\ - $$LIBLIMESUITESRC/src/API/qLimeSDR.cpp\ - src/BuiltinConnections.cpp\ +SOURCES = $$LIBLIMESUITESRC/src/Logger.cpp\ + $$LIBLIMESUITESRC/src/ADF4002/ADF4002.cpp\ + $$LIBLIMESUITESRC/src/lms7002m_mcu/MCU_BD.cpp\ + $$LIBLIMESUITESRC/src/ConnectionRegistry/IConnection.cpp\ $$LIBLIMESUITESRC/src/ConnectionRegistry/ConnectionHandle.cpp\ $$LIBLIMESUITESRC/src/ConnectionRegistry/ConnectionRegistry.cpp\ - $$LIBLIMESUITESRC/src/ConnectionRegistry/IConnection.cpp\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAM.cpp\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAMImages.cpp\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAMing.cpp\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAMEntry.cpp\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/Connection_uLimeSDR.cpp\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/Connection_uLimeSDRing.cpp\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/Connection_uLimeSDREntry.cpp\ - $$LIBLIMESUITESRC/src/ConnectionXillybus/ConnectionXillybus.cpp\ - $$LIBLIMESUITESRC/src/ConnectionXillybus/ConnectionXillybusEntry.cpp\ - $$LIBLIMESUITESRC/src/ConnectionXillybus/ConnectionXillybusing.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RegistersMap.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_parameters.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RxTxCalibrations.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_BaseCalibrations.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/goert.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/mcu_dc_iq_calibration.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/CalibrationCache.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_filtersCalibration.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_gainCalibrations.cpp\ + $$LIBLIMESUITESRC/src/protocols/LMS64CProtocol.cpp\ + $$LIBLIMESUITESRC/src/protocols/Streamer.cpp\ + $$LIBLIMESUITESRC/src/protocols/ConnectionImages.cpp\ + $$LIBLIMESUITESRC/src/Si5351C/Si5351C.cpp\ + $$LIBLIMESUITESRC/src/kissFFT/kiss_fft.c\ + $$LIBLIMESUITESRC/src/API/lms7_api.cpp\ + $$LIBLIMESUITESRC/src/API/lms7_device.cpp\ + $$LIBLIMESUITESRC/src/API/LmsGeneric.cpp\ + $$LIBLIMESUITESRC/src/API/qLimeSDR.cpp\ + $$LIBLIMESUITESRC/src/API/LimeSDR_mini.cpp\ + $$LIBLIMESUITESRC/src/API/LimeSDR.cpp\ $$LIBLIMESUITESRC/src/FPGA_common/FPGA_common.cpp\ + $$LIBLIMESUITESRC/src/FPGA_common/FPGA_Mini.cpp\ + $$LIBLIMESUITESRC/src/FPGA_common/FPGA_Q.cpp\ $$LIBLIMESUITESRC/src/GFIR/corrections.c\ $$LIBLIMESUITESRC/src/GFIR/gfir_lms.c\ $$LIBLIMESUITESRC/src/GFIR/lms.c\ $$LIBLIMESUITESRC/src/GFIR/recipes.c\ $$LIBLIMESUITESRC/src/GFIR/rounding.c\ - $$LIBLIMESUITESRC/src/kissFFT/kiss_fft.c\ - $$LIBLIMESUITESRC/src/lms7002m/CalibrationCache.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/goert.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_BaseCalibrations.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_filtersCalibration.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_gainCalibrations.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_parameters.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RegistersMap.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RxTxCalibrations.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/mcu_dc_iq_calibration.cpp\ - $$LIBLIMESUITESRC/src/lms7002m_mcu/MCU_BD.cpp\ - $$LIBLIMESUITESRC/src/protocols/ILimeSDRStreaming.cpp\ - $$LIBLIMESUITESRC/src/protocols/LMS64CProtocol.cpp\ - $$LIBLIMESUITESRC/src/Si5351C/Si5351C.cpp\ - $$LIBLIMESUITESRC/src/ErrorReporting.cpp\ - $$LIBLIMESUITESRC/src/Logger.cpp\ + $$LIBLIMESUITESRC/src/windowFunction.cpp\ + $$LIBLIMESUITESRC/src/ConnectionFTDI/ConnectionFT601.cpp\ + $$LIBLIMESUITESRC/src/ConnectionFTDI//ConnectionFT601Entry.cpp\ + $$LIBLIMESUITESRC/src/ConnectionFX3/ConnectionFX3Entry.cpp\ + $$LIBLIMESUITESRC/src/ConnectionFX3/ConnectionFX3.cpp\ + src/BuiltinConnections.cpp\ src/SystemResources.cpp\ src/VersionInfo.cpp -HEADERS = $$LIBLIMESUITESRC/src/ADF4002/ADF4002.h\ - $$LIBLIMESUITESRC/src/API/lms7_device.h\ - $$LIBLIMESUITESRC/src/API/LimeSDR_mini.h\ - $$LIBLIMESUITESRC/src/API/qLimeSDR.h\ - $$LIBLIMESUITESRC/src/ConnectionRegistry/ConnectionHandle.h\ - $$LIBLIMESUITESRC/src/ConnectionRegistry/ConnectionRegistry.h\ - $$LIBLIMESUITESRC/src/ConnectionRegistry/IConnection.h\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAM.h\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/Connection_uLimeSDR.h\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/FTD3XXLibrary/FTD3XX.h\ - $$LIBLIMESUITESRC/src/ConnectionXillybus/ConnectionXillybus.h\ - $$LIBLIMESUITESRC/src/FPGA_common/FPGA_common.h\ - $$LIBLIMESUITESRC/src/GFIR/dfilter.h\ - $$LIBLIMESUITESRC/src/GFIR/lms_gfir.h\ - $$LIBLIMESUITESRC/src/GFIR/lms.h\ - $$LIBLIMESUITESRC/src/kissFFT/_kiss_fft_guts.h\ - $$LIBLIMESUITESRC/src/kissFFT/kiss_fft.h\ - $$LIBLIMESUITESRC/src/lms7002m/CalibrationCache.h\ - $$LIBLIMESUITESRC/src/lms7002m/goertzel.h\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M.h\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_parameters.h\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RegistersMap.h\ - $$LIBLIMESUITESRC/src/lms7002m/mcu_programs.h\ - $$LIBLIMESUITESRC/src/lms7002m_mcu/MCU_BD.h\ - $$LIBLIMESUITESRC/src/lms7002m_mcu/MCU_File.h\ - $$LIBLIMESUITESRC/src/protocols/ADCUnits.h\ - $$LIBLIMESUITESRC/src/protocols/dataTypes.h\ - $$LIBLIMESUITESRC/src/protocols/fifo.h\ - $$LIBLIMESUITESRC/src/protocols/ILimeSDRStreaming.h\ - $$LIBLIMESUITESRC/src/protocols/LMS64CCommands.h\ - $$LIBLIMESUITESRC/src/protocols/LMS64CProtocol.h\ - $$LIBLIMESUITESRC/src/protocols/LMSBoards.h\ - $$LIBLIMESUITESRC/src/Si5351C/Si5351C.h\ - $$LIBLIMESUITESRC/src/ErrorReporting.h\ - $$LIBLIMESUITESRC/src/Logger.h\ - $$LIBLIMESUITESRC/src/SystemResources.h\ - $$LIBLIMESUITESRC/src/VersionInfo.h\ - $$LIBLIMESUITESRC/src/lime/LimeSuite.h +HEADERS = $$LIBLIMESUITESRC/src/API/*.h\ + $$LIBLIMESUITESRC/src/GFIR/*.h\ + $$LIBLIMESUITESRC/src/protocols/*.h\ + $$LIBLIMESUITESRC/src/ConnectionRegistry/*.h\ + $$LIBLIMESUITESRC/src/lms7002m_mcu/*.h\ + $$LIBLIMESUITESRC/src/ADF4002/*.h\ + $$LIBLIMESUITESRC/src/Si5351C/*.h\ + $$LIBLIMESUITESRC/src/lms7002m/*.h\ + $$LIBLIMESUITESRC/src/FPGA_common/*.h\ + $$LIBLIMESUITESRC/src/lms7002m_mcu/*.h\ + $$LIBLIMESUITESRC/src/HPM7/*.h\ + $$LIBLIMESUITESRC/src/kissFFT/*.h CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 diff --git a/liblimesuite/src/BuiltinConnections.cpp b/liblimesuite/src/BuiltinConnections.cpp index 592650b3e..c7b3313f7 100644 --- a/liblimesuite/src/BuiltinConnections.cpp +++ b/liblimesuite/src/BuiltinConnections.cpp @@ -4,17 +4,17 @@ **********************************************************************/ /* #undef ENABLE_EVB7COM */ -#define ENABLE_STREAM +#define ENABLE_FX3 /* #undef ENABLE_STREAM_UNITE */ /* #undef ENABLE_NOVENARF7 */ -#define ENABLE_uLimeSDR -#define ENABLE_PCIE_XILLYBUS +#define ENABLE_FTDI +/* #undef ENABLE_PCIE_XILLYBUS */ void __loadConnectionEVB7COMEntry(void); -void __loadConnectionSTREAMEntry(void); +void __loadConnectionFX3Entry(void); void __loadConnectionSTREAM_UNITEEntry(void); void __loadConnectionNovenaRF7Entry(void); -void __loadConnection_uLimeSDREntry(void); +void __loadConnectionFT601Entry(void); void __loadConnectionXillybusEntry(void); void __loadAllConnections(void) @@ -23,16 +23,16 @@ void __loadAllConnections(void) __loadConnectionEVB7COMEntry(); #endif - #ifdef ENABLE_STREAM - __loadConnectionSTREAMEntry(); + #ifdef ENABLE_FX3 + __loadConnectionFX3Entry(); #endif #ifdef ENABLE_STREAM_UNITE __loadConnectionSTREAM_UNITEEntry(); #endif - #ifdef ENABLE_uLimeSDR - __loadConnection_uLimeSDREntry(); + #ifdef ENABLE_FTDI + __loadConnectionFT601Entry(); #endif #ifdef ENABLE_NOVENARF7 diff --git a/liblimesuite/src/SystemResources.cpp b/liblimesuite/src/SystemResources.cpp index 70d39e968..f7f07f35f 100644 --- a/liblimesuite/src/SystemResources.cpp +++ b/liblimesuite/src/SystemResources.cpp @@ -5,7 +5,7 @@ */ #include "SystemResources.h" -#include "ErrorReporting.h" +#include "Logger.h" #include //getenv, system #include @@ -23,9 +23,10 @@ #define W_OK 4 #endif -#ifdef __unix__ -#include +#ifdef __MINGW32__ #include +#elif __unix__ +#include #endif #include @@ -65,6 +66,7 @@ std::string lime::getLimeSuiteRoot(void) std::string lime::getHomeDirectory(void) { +#ifndef __MINGW32__ //first check the HOME environment variable const char *userHome = std::getenv("HOME"); if (userHome != nullptr) return userHome; @@ -74,7 +76,7 @@ std::string lime::getHomeDirectory(void) const char *pwDir = getpwuid(getuid())->pw_dir; if (pwDir != nullptr) return pwDir; #endif - +#endif return ""; } @@ -155,7 +157,7 @@ std::string lime::locateImageResource(const std::string &name) { for (const auto &searchPath : lime::listImageSearchPaths()) { - const std::string fullPath(searchPath + "/17.03/" + name); + const std::string fullPath(searchPath + "/18.02/" + name); if (access(fullPath.c_str(), R_OK) == 0) return fullPath; } return ""; @@ -163,9 +165,9 @@ std::string lime::locateImageResource(const std::string &name) int lime::downloadImageResource(const std::string &name) { - const std::string destDir(lime::getAppDataDirectory() + "/images/17.03"); + const std::string destDir(lime::getAppDataDirectory() + "/images/18.02"); const std::string destFile(destDir + "/" + name); - const std::string sourceUrl("http://downloads.myriadrf.org/project/limesuite/17.03/" + name); + const std::string sourceUrl("http://downloads.myriadrf.org/project/limesuite/18.02/" + name); //check if the directory already exists struct stat s; diff --git a/sdrgui/sdrgui.pro b/sdrgui/sdrgui.pro index 2810484c8..7732b60d8 100644 --- a/sdrgui/sdrgui.pro +++ b/sdrgui/sdrgui.pro @@ -43,6 +43,7 @@ SOURCES += mainwindow.cpp\ dsp/spectrumscopengcombovis.cpp\ dsp/scopevis.cpp\ dsp/scopevisng.cpp\ + dsp/scopevisxy.cpp\ dsp/spectrumvis.cpp\ gui/aboutdialog.cpp\ gui/addpresetdialog.cpp\ @@ -81,6 +82,7 @@ SOURCES += mainwindow.cpp\ gui/transverterbutton.cpp\ gui/transverterdialog.cpp\ gui/tickedslider.cpp\ + gui/tvscreen.cpp\ gui/valuedial.cpp\ gui/valuedialz.cpp\ webapi/webapiadaptergui.cpp @@ -93,6 +95,7 @@ HEADERS += mainwindow.h\ dsp/spectrumscopengcombovis.h\ dsp/scopevis.h\ dsp/scopevisng.h\ + dsp/scopevisxy.h\ dsp/spectrumvis.h\ gui/aboutdialog.h\ gui/addpresetdialog.h\ @@ -132,6 +135,7 @@ HEADERS += mainwindow.h\ gui/tickedslider.h\ gui/transverterbutton.h\ gui/transverterdialog.h\ + gui/tvscreen.h\ gui/valuedial.h\ gui/valuedialz.h\ webapi/webapiadaptergui.h From 55cd795eb45b3fc6f4c3bd8bbca4b5a421418653 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 15 Mar 2018 23:48:25 +0100 Subject: [PATCH 120/956] LimeSDR: fix NCO direction as Tx is opposed to Rx --- Readme.md | 2 +- devices/limesdr/devicelimesdr.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 8c0b4d340..99ac45cd3 100644 --- a/Readme.md +++ b/Readme.md @@ -119,7 +119,7 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3. [LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next). -

    ⚠ The plugins should work normally when running as single instances. Support of many Rx and/or Tx instances running concurrently is considered experimental. At least you should always have one of the streams running.

    +

    ⚠ The latest version of LimeSuite is used and must be considered experimental. Hence LimeSDR support in SDRangel is also experimental.

    ⚠ It seems LimeSDR mini has trouble working with host sample rates lower than 2.5 MS/s particularly in Tx mode.

    diff --git a/devices/limesdr/devicelimesdr.cpp b/devices/limesdr/devicelimesdr.cpp index 4e10cab52..25d42c359 100644 --- a/devices/limesdr/devicelimesdr.cpp +++ b/devices/limesdr/devicelimesdr.cpp @@ -50,7 +50,7 @@ bool DeviceLimeSDR::setNCOFrequency(lms_device_t *device, bool dir_tx, std::size return false; } - if (LMS_SetNCOIndex(device, dir_tx, chan, 0, positive) < 0) + if (LMS_SetNCOIndex(device, dir_tx, chan, 0, dir_tx^positive) < 0) { fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot set conversion direction %sfreq\n", positive ? "+" : "-"); return false; From 6e5e912c7426965234aeb95ba8e0704e21115d0f Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 10:11:32 +0100 Subject: [PATCH 121/956] TVScreen: correct debug messages --- sdrgui/gui/tvscreen.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdrgui/gui/tvscreen.cpp b/sdrgui/gui/tvscreen.cpp index 1a81f36b4..8d18dc665 100644 --- a/sdrgui/gui/tvscreen.cpp +++ b/sdrgui/gui/tvscreen.cpp @@ -112,19 +112,19 @@ void TVScreen::initializeGL() { if (QOpenGLContext::currentContext()->isValid()) { - qDebug() << "DATVScreen::initializeGL: context:" + qDebug() << "TVScreen::initializeGL: context:" << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion() << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion() << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no"); } else { - qDebug() << "DATVScreen::initializeGL: current context is invalid"; + qDebug() << "TVScreen::initializeGL: current context is invalid"; } } else { - qCritical() << "DATVScreen::initializeGL: no current context"; + qCritical() << "TVScreen::initializeGL: no current context"; return; } @@ -132,21 +132,21 @@ void TVScreen::initializeGL() if (objSurface == NULL) { - qCritical() << "DATVScreen::initializeGL: no surface attached"; + qCritical() << "TVScreen::initializeGL: no surface attached"; return; } else { if (objSurface->surfaceType() != QSurface::OpenGLSurface) { - qCritical() << "DATVScreen::initializeGL: surface is not an OpenGLSurface: " + qCritical() << "TVScreen::initializeGL: surface is not an OpenGLSurface: " << objSurface->surfaceType() << " cannot use an OpenGL context"; return; } else { - qDebug() << "DATVScreen::initializeGL: OpenGL surface:" + qDebug() << "TVScreen::initializeGL: OpenGL surface:" << " class: " << (objSurface->surfaceClass() == QSurface::Window ? "Window" : "Offscreen"); } } @@ -198,7 +198,7 @@ void TVScreen::tick() void TVScreen::connectTimer(const QTimer& objTimer) { - qDebug() << "DATVScreen::connectTimer"; + qDebug() << "TVScreen::connectTimer"; disconnect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); connect(&objTimer, SIGNAL(timeout()), this, SLOT(tick())); m_objTimer.stop(); From 904bcf2dc1acf3a3599da900bfc3847300fc2be6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 10:13:02 +0100 Subject: [PATCH 122/956] On DeviceUISet load channels delete all channels before adding new channels. This ensures channels are placed in the same order (sorted) as in the preset --- sdrgui/device/deviceuiset.cpp | 126 ++++++++++++---------------------- 1 file changed, 44 insertions(+), 82 deletions(-) diff --git a/sdrgui/device/deviceuiset.cpp b/sdrgui/device/deviceuiset.cpp index d34d7a74a..7c77bec2c 100644 --- a/sdrgui/device/deviceuiset.cpp +++ b/sdrgui/device/deviceuiset.cpp @@ -166,7 +166,7 @@ void DeviceUISet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginA { if (preset->isSourcePreset()) { - qDebug("DeviceUISet::loadChannelSettings: Loading preset [%s | %s]", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); + qDebug("DeviceUISet::loadRxChannelSettings: Loading preset [%s | %s]", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); // Available channel plugins PluginAPI::ChannelRegistrations *channelRegistrations = pluginAPI->getRxChannelRegistrations(); @@ -175,66 +175,47 @@ void DeviceUISet::loadRxChannelSettings(const Preset *preset, PluginAPI *pluginA ChannelInstanceRegistrations openChannels = m_rxChannelInstanceRegistrations; m_rxChannelInstanceRegistrations.clear(); - qDebug("DeviceUISet::loadChannelSettings: %d channel(s) in preset", preset->getChannelCount()); + for(int i = 0; i < openChannels.count(); i++) + { + qDebug("DeviceUISet::loadRxChannelSettings: destroying old channel [%s]", qPrintable(openChannels[i].m_channelName)); + openChannels[i].m_gui->destroy(); // FIXME: stop channel before + } - for(int i = 0; i < preset->getChannelCount(); i++) + qDebug("DeviceUISet::loadRxChannelSettings: %d channel(s) in preset", preset->getChannelCount()); + + for (int i = 0; i < preset->getChannelCount(); i++) { const Preset::ChannelConfig& channelConfig = preset->getChannelConfig(i); ChannelInstanceRegistration reg; - // if we have one instance available already, use it + // create channel instance - for(int i = 0; i < openChannels.count(); i++) + for(int i = 0; i < channelRegistrations->count(); i++) { - qDebug("DeviceUISet::loadChannelSettings: channels compare [%s] vs [%s]", qPrintable(openChannels[i].m_channelName), qPrintable(channelConfig.m_channelIdURI)); - - if(openChannels[i].m_channelName == channelConfig.m_channelIdURI) + if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) { - qDebug("DeviceSourceAPI::loadChannelSettings: channel [%s] found", qPrintable(openChannels[i].m_channelName)); - reg = openChannels.takeAt(i); - m_rxChannelInstanceRegistrations.append(reg); + qDebug("DeviceUISet::loadRxChannelSettings: creating new channel [%s]", qPrintable(channelConfig.m_channelIdURI)); + BasebandSampleSink *rxChannel = + (*channelRegistrations)[i].m_plugin->createRxChannelBS(m_deviceSourceAPI); + PluginInstanceGUI *rxChannelGUI = + (*channelRegistrations)[i].m_plugin->createRxChannelGUI(this, rxChannel); + reg = ChannelInstanceRegistration(channelConfig.m_channelIdURI, rxChannelGUI); break; } } - // if we haven't one already, create one - - if(reg.m_gui == NULL) + if (reg.m_gui != 0) { - for(int i = 0; i < channelRegistrations->count(); i++) - { - if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) - { - qDebug("DeviceUISet::loadChannelSettings: creating new channel [%s]", qPrintable(channelConfig.m_channelIdURI)); - BasebandSampleSink *rxChannel = - (*channelRegistrations)[i].m_plugin->createRxChannelBS(m_deviceSourceAPI); - PluginInstanceGUI *rxChannelGUI = - (*channelRegistrations)[i].m_plugin->createRxChannelGUI(this, rxChannel); - reg = ChannelInstanceRegistration(channelConfig.m_channelIdURI, rxChannelGUI); - break; - } - } - } - - if(reg.m_gui != NULL) - { - qDebug("DeviceUISet::loadChannelSettings: deserializing channel [%s]", qPrintable(channelConfig.m_channelIdURI)); + qDebug("DeviceUISet::loadRxChannelSettings: deserializing channel [%s]", qPrintable(channelConfig.m_channelIdURI)); reg.m_gui->deserialize(channelConfig.m_config); } } - // everything, that is still "available" is not needed anymore - for(int i = 0; i < openChannels.count(); i++) - { - qDebug("DeviceUISet::loadChannelSettings: destroying spare channel [%s]", qPrintable(openChannels[i].m_channelName)); - openChannels[i].m_gui->destroy(); - } - renameRxChannelInstances(); } else { - qDebug("DeviceUISet::loadChannelSettings: Loading preset [%s | %s] not a source preset", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); + qDebug("DeviceUISet::loadRxChannelSettings: Loading preset [%s | %s] not a source preset", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); } } @@ -246,13 +227,13 @@ void DeviceUISet::saveRxChannelSettings(Preset *preset) for(int i = 0; i < m_rxChannelInstanceRegistrations.count(); i++) { - qDebug("DeviceUISet::saveChannelSettings: channel [%s] saved", qPrintable(m_rxChannelInstanceRegistrations[i].m_channelName)); + qDebug("DeviceUISet::saveRxChannelSettings: channel [%s] saved", qPrintable(m_rxChannelInstanceRegistrations[i].m_channelName)); preset->addChannel(m_rxChannelInstanceRegistrations[i].m_channelName, m_rxChannelInstanceRegistrations[i].m_gui->serialize()); } } else { - qDebug("DeviceUISet::saveChannelSettings: not a source preset"); + qDebug("DeviceUISet::saveRxChannelSettings: not a source preset"); } } @@ -260,11 +241,11 @@ void DeviceUISet::loadTxChannelSettings(const Preset *preset, PluginAPI *pluginA { if (preset->isSourcePreset()) { - qDebug("DeviceUISet::loadChannelSettings: Loading preset [%s | %s] not a sink preset", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); + qDebug("DeviceUISet::loadTxChannelSettings: Loading preset [%s | %s] not a sink preset", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); } else { - qDebug("DeviceUISet::loadChannelSettings: Loading preset [%s | %s]", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); + qDebug("DeviceUISet::loadTxChannelSettings: Loading preset [%s | %s]", qPrintable(preset->getGroup()), qPrintable(preset->getDescription())); // Available channel plugins PluginAPI::ChannelRegistrations *channelRegistrations = pluginAPI->getTxChannelRegistrations(); @@ -273,61 +254,42 @@ void DeviceUISet::loadTxChannelSettings(const Preset *preset, PluginAPI *pluginA ChannelInstanceRegistrations openChannels = m_txChannelInstanceRegistrations; m_txChannelInstanceRegistrations.clear(); - qDebug("DeviceUISet::loadChannelSettings: %d channel(s) in preset", preset->getChannelCount()); + for(int i = 0; i < openChannels.count(); i++) + { + qDebug("DeviceUISet::loadTxChannelSettings: destroying old channel [%s]", qPrintable(openChannels[i].m_channelName)); + openChannels[i].m_gui->destroy(); + } + + qDebug("DeviceUISet::loadTxChannelSettings: %d channel(s) in preset", preset->getChannelCount()); for(int i = 0; i < preset->getChannelCount(); i++) { const Preset::ChannelConfig& channelConfig = preset->getChannelConfig(i); ChannelInstanceRegistration reg; - // if we have one instance available already, use it + // create channel instance - for(int i = 0; i < openChannels.count(); i++) + for(int i = 0; i < channelRegistrations->count(); i++) { - qDebug("DeviceUISet::loadChannelSettings: channels compare [%s] vs [%s]", qPrintable(openChannels[i].m_channelName), qPrintable(channelConfig.m_channelIdURI)); - - if(openChannels[i].m_channelName == channelConfig.m_channelIdURI) + if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) { - qDebug("DeviceUISet::loadChannelSettings: channel [%s] found", qPrintable(openChannels[i].m_channelName)); - reg = openChannels.takeAt(i); - m_txChannelInstanceRegistrations.append(reg); + qDebug("DeviceUISet::loadTxChannelSettings: creating new channel [%s]", qPrintable(channelConfig.m_channelIdURI)); + BasebandSampleSource *txChannel = + (*channelRegistrations)[i].m_plugin->createTxChannelBS(m_deviceSinkAPI); + PluginInstanceGUI *txChannelGUI = + (*channelRegistrations)[i].m_plugin->createTxChannelGUI(this, txChannel); + reg = ChannelInstanceRegistration(channelConfig.m_channelIdURI, txChannelGUI); break; } } - // if we haven't one already, create one - - if(reg.m_gui == 0) - { - for(int i = 0; i < channelRegistrations->count(); i++) - { - if((*channelRegistrations)[i].m_channelIdURI == channelConfig.m_channelIdURI) - { - qDebug("DeviceUISet::loadChannelSettings: creating new channel [%s]", qPrintable(channelConfig.m_channelIdURI)); - BasebandSampleSource *txChannel = - (*channelRegistrations)[i].m_plugin->createTxChannelBS(m_deviceSinkAPI); - PluginInstanceGUI *txChannelGUI = - (*channelRegistrations)[i].m_plugin->createTxChannelGUI(this, txChannel); - reg = ChannelInstanceRegistration(channelConfig.m_channelIdURI, txChannelGUI); - break; - } - } - } - if(reg.m_gui != 0) { - qDebug("DeviceUISet::loadChannelSettings: deserializing channel [%s]", qPrintable(channelConfig.m_channelIdURI)); + qDebug("DeviceUISet::loadTxChannelSettings: deserializing channel [%s]", qPrintable(channelConfig.m_channelIdURI)); reg.m_gui->deserialize(channelConfig.m_config); } } - // everything, that is still "available" is not needed anymore - for(int i = 0; i < openChannels.count(); i++) - { - qDebug("DeviceUISet::loadChannelSettings: destroying spare channel [%s]", qPrintable(openChannels[i].m_channelName)); - openChannels[i].m_gui->destroy(); - } - renameTxChannelInstances(); } } @@ -336,7 +298,7 @@ void DeviceUISet::saveTxChannelSettings(Preset *preset) { if (preset->isSourcePreset()) { - qDebug("DeviceUISet::saveChannelSettings: not a sink preset"); + qDebug("DeviceUISet::saveTxChannelSettings: not a sink preset"); } else { @@ -344,7 +306,7 @@ void DeviceUISet::saveTxChannelSettings(Preset *preset) for(int i = 0; i < m_txChannelInstanceRegistrations.count(); i++) { - qDebug("DeviceUISet::saveChannelSettings: channel [%s] saved", qPrintable(m_txChannelInstanceRegistrations[i].m_channelName)); + qDebug("DeviceUISet::saveTxChannelSettings: channel [%s] saved", qPrintable(m_txChannelInstanceRegistrations[i].m_channelName)); preset->addChannel(m_txChannelInstanceRegistrations[i].m_channelName, m_txChannelInstanceRegistrations[i].m_gui->serialize()); } } From 8d69272a76e5dbab6d181bbe2c7afce08f256905 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 10:15:35 +0100 Subject: [PATCH 123/956] Threaded baseband source/sink stop before delete --- sdrbase/dsp/threadedbasebandsamplesink.cpp | 1 + sdrbase/dsp/threadedbasebandsamplesource.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/sdrbase/dsp/threadedbasebandsamplesink.cpp b/sdrbase/dsp/threadedbasebandsamplesink.cpp index 71ded353e..536b3e6cd 100644 --- a/sdrbase/dsp/threadedbasebandsamplesink.cpp +++ b/sdrbase/dsp/threadedbasebandsamplesink.cpp @@ -88,6 +88,7 @@ ThreadedBasebandSampleSink::ThreadedBasebandSampleSink(BasebandSampleSink* sampl ThreadedBasebandSampleSink::~ThreadedBasebandSampleSink() { + stop(); delete m_threadedBasebandSampleSinkFifo; // Valgrind memcheck delete m_thread; } diff --git a/sdrbase/dsp/threadedbasebandsamplesource.cpp b/sdrbase/dsp/threadedbasebandsamplesource.cpp index 57b8b125a..d8fbf3c06 100644 --- a/sdrbase/dsp/threadedbasebandsamplesource.cpp +++ b/sdrbase/dsp/threadedbasebandsamplesource.cpp @@ -36,6 +36,7 @@ ThreadedBasebandSampleSource::ThreadedBasebandSampleSource(BasebandSampleSource* ThreadedBasebandSampleSource::~ThreadedBasebandSampleSource() { + stop(); delete m_thread; } From 170c59de4371c878b97f103c53261a85de874583 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 10:24:19 +0100 Subject: [PATCH 124/956] Channel Rx demods: in constructor make sure the thread is launched when all members have been properly initialized (i.e. do it last) --- plugins/channelrx/chanalyzerng/chanalyzerng.cpp | 4 ++-- plugins/channelrx/demodam/amdemod.cpp | 6 +++--- plugins/channelrx/demodatv/atvdemod.cpp | 4 ++-- plugins/channelrx/demodbfm/bfmdemod.cpp | 6 +++--- plugins/channelrx/demoddatv/datvdemod.cpp | 6 +++--- plugins/channelrx/demoddsd/dsddemod.cpp | 6 +++--- plugins/channelrx/demodnfm/nfmdemod.cpp | 7 ++++--- plugins/channelrx/demodssb/ssbdemod.cpp | 6 +++--- plugins/channelrx/demodwfm/wfmdemod.cpp | 6 +++--- plugins/channelrx/udpsrc/udpsrc.cpp | 6 +++--- 10 files changed, 29 insertions(+), 28 deletions(-) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp index 85a009219..5e91572b8 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp @@ -50,12 +50,12 @@ ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : SSBFilter = new fftfilt(m_config.m_LowCutoff / m_config.m_inputSampleRate, m_config.m_Bandwidth / m_config.m_inputSampleRate, ssbFftLen); DSBFilter = new fftfilt(m_config.m_Bandwidth / m_config.m_inputSampleRate, 2*ssbFftLen); + apply(true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - apply(true); } ChannelAnalyzerNG::~ChannelAnalyzerNG() diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index 7b9d2f7e0..a227b37f8 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -61,13 +61,13 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } AMDemod::~AMDemod() diff --git a/plugins/channelrx/demodatv/atvdemod.cpp b/plugins/channelrx/demodatv/atvdemod.cpp index 64b67cc71..48516cd83 100644 --- a/plugins/channelrx/demodatv/atvdemod.cpp +++ b/plugins/channelrx/demodatv/atvdemod.cpp @@ -85,14 +85,14 @@ ATVDemod::ATVDemod(DeviceSourceAPI *deviceAPI) : m_objPhaseDiscri.setFMScaling(1.0f); + applyStandard(); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - - applyStandard(); } ATVDemod::~ATVDemod() diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index 8bf7c5b7a..0ebd113d2 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -89,13 +89,13 @@ BFMDemod::BFMDemod(DeviceSourceAPI *deviceAPI) : m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); m_audioNetSink->setStereo(true); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } BFMDemod::~BFMDemod() diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 87caea8b7..859b4e387 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -58,15 +58,15 @@ DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : m_objRFFilter = new fftfilt(-256000.0 / 1024000.0, 256000.0 / 1024000.0, rfFilterFftLength); + //To setup correct Sample Rate + channelSampleRateChanged(); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - - //To setup correct Sample Rate - channelSampleRateChanged(); } DATVDemod::~DATVDemod() diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index cea17c719..9592b1e76 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -84,13 +84,13 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : m_audioFifo1.setAudioNetSink(m_audioNetSink); m_audioFifo2.setAudioNetSink(m_audioNetSink); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } DSDDemod::~DSDDemod() diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 7509cabdf..e252d8ef6 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -66,6 +66,7 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_audioFifo(48000), m_settingsMutex(QMutex::Recursive) { + qDebug("NFMDemod::NFMDemod"); setObjectName(m_channelId); m_audioBuffer.resize(1<<14); @@ -80,13 +81,13 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } NFMDemod::~NFMDemod() diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 5e90d4252..6209a7cc5 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -89,13 +89,13 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } SSBDemod::~SSBDemod() diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index 8aad0c527..fd74e0c9e 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -63,13 +63,13 @@ WFMDemod::WFMDemod(DeviceSourceAPI* deviceAPI) : m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } WFMDemod::~WFMDemod() diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsrc/udpsrc.cpp index 9a0fd98ff..c913421ea 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsrc/udpsrc.cpp @@ -100,13 +100,13 @@ UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : //DSPEngine::instance()->addAudioSink(&m_audioFifo); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } UDPSrc::~UDPSrc() From ea1d0077103349cf2f741af000dc01b2d312ee7f Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 10:26:49 +0100 Subject: [PATCH 125/956] Channel Tx modulators: in constructor make sure the thread is launched when all members have been properly initialized (i.e. do it last) --- plugins/channeltx/modam/ammod.cpp | 6 +++--- plugins/channeltx/modatv/atvmod.cpp | 6 +++--- plugins/channeltx/modnfm/nfmmod.cpp | 6 +++--- plugins/channeltx/modssb/ssbmod.cpp | 6 +++--- plugins/channeltx/modwfm/wfmmod.cpp | 6 +++--- plugins/channeltx/udpsink/udpsink.cpp | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index ac96b2a95..8d09466fd 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -73,13 +73,13 @@ AMMod::AMMod(DeviceSinkAPI *deviceAPI) : m_cwKeyer.setWPM(13); m_cwKeyer.setMode(CWKeyerSettings::CWNone); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } AMMod::~AMMod() diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index 56cb8f5b8..44a5187d9 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -90,13 +90,13 @@ ATVMod::ATVMod(DeviceSinkAPI *deviceAPI) : m_interpolatorDistanceRemain = 0.0f; m_interpolatorDistance = 1.0f; + applyChannelSettings(m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); // does applyStandard() too; + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); // does applyStandard() too; } ATVMod::~ATVMod() diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp index 347385c82..9f68c2a10 100644 --- a/plugins/channeltx/modnfm/nfmmod.cpp +++ b/plugins/channeltx/modnfm/nfmmod.cpp @@ -77,13 +77,13 @@ NFMMod::NFMMod(DeviceSinkAPI *deviceAPI) : m_cwKeyer.setWPM(13); m_cwKeyer.setMode(CWKeyerSettings::CWNone); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } NFMMod::~NFMMod() diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index ae997cd0f..3ecf9766e 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -99,13 +99,13 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : m_inAGC.setStepDownDelay(m_settings.m_agcThresholdDelay); m_inAGC.setClamping(true); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } SSBMod::~SSBMod() diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index ed30f65c6..ecd75409c 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -83,13 +83,13 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : m_cwKeyer.setMode(CWKeyerSettings::CWNone); m_cwKeyer.reset(); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } WFMMod::~WFMMod() diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index 24060a85f..467241bd6 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -67,13 +67,13 @@ UDPSink::UDPSink(DeviceSinkAPI *deviceAPI) : m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_settings.m_inputSampleRate, m_settings.m_rfBandwidth / m_settings.m_inputSampleRate, m_ssbFftLen); m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } UDPSink::~UDPSink() From cca17093b690957804d1311cdfc5a09326ac7bab Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 22:42:01 +0100 Subject: [PATCH 126/956] LimeSDR: updated latest status in documentation --- Readme.md | 2 +- plugins/samplesink/limesdroutput/readme.md | 2 +- plugins/samplesource/limesdrinput/readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index 99ac45cd3..6bbe0b09b 100644 --- a/Readme.md +++ b/Readme.md @@ -119,7 +119,7 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3. [LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next). -

    ⚠ The latest version of LimeSuite is used and must be considered experimental. Hence LimeSDR support in SDRangel is also experimental.

    +

    ⚠ The latest version of LimeSuite is used and must be considered experimental. Hence LimeSDR support in SDRangel is also experimental. Only single Rx works more or less.

    ⚠ It seems LimeSDR mini has trouble working with host sample rates lower than 2.5 MS/s particularly in Tx mode.

    diff --git a/plugins/samplesink/limesdroutput/readme.md b/plugins/samplesink/limesdroutput/readme.md index d21f885db..f243be7fe 100644 --- a/plugins/samplesink/limesdroutput/readme.md +++ b/plugins/samplesink/limesdroutput/readme.md @@ -4,7 +4,7 @@ This output sample sink plugin sends its samples to a [LimeSDR device](https://myriadrf.org/projects/limesdr/). -⚠ LimeSuite library is difficult to implement due to the lack of documentation. The plugins should work normally when running as single instances. Support of both Rx and/or both Rx running concurrently is experimental. +

    ⚠ The latest version of LimeSuite is used and must be considered experimental. Hence LimeSDR support in SDRangel is also experimental. Tx does not work.

    LimeSDR is a 2x2 MIMO device so it has two transmitting channels that can run concurrently. To activate the second channel when the first is already active just open a new sink tab in the main window (Devices -> Add sink device) and select the same LimeSDR device. diff --git a/plugins/samplesource/limesdrinput/readme.md b/plugins/samplesource/limesdrinput/readme.md index 3af01b446..807879419 100644 --- a/plugins/samplesource/limesdrinput/readme.md +++ b/plugins/samplesource/limesdrinput/readme.md @@ -4,7 +4,7 @@ This input sample source plugin gets its samples from a [LimeSDR device](https://myriadrf.org/projects/limesdr/). -⚠ LimeSuite library is difficult to implement due to the lack of documentation. The plugins should work normally when running as single instances. Support of both Rx and/or both Rx running concurrently is experimental. +

    ⚠ The latest version of LimeSuite is used and must be considered experimental. Hence LimeSDR support in SDRangel is also experimental. Only single Rx works more or less.

    LimeSDR is a 2x2 MIMO device so it has two receiving channels that can run concurrently. To activate the second channel when the first is already active just open a new source tab in the main window (Devices -> Add source device) and select the same LimeSDR device. From 6c0d6332f27beef8562a66ff2b259a14174699e7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 22:56:46 +0100 Subject: [PATCH 127/956] LimeSDR: Debian build cmake file for new LimeSuite --- liblimesuite/CMakeLists.txt | 148 ++++++++++++++-------------------- liblimesuite/liblimesuite.pro | 1 - 2 files changed, 61 insertions(+), 88 deletions(-) diff --git a/liblimesuite/CMakeLists.txt b/liblimesuite/CMakeLists.txt index c09750124..acd883790 100644 --- a/liblimesuite/CMakeLists.txt +++ b/liblimesuite/CMakeLists.txt @@ -4,93 +4,64 @@ find_package(LibUSB) find_package(SQLite3) set(limesuite_SOURCES - ${LIBLIMESUITESRC}/src/ADF4002/ADF4002.cpp - ${LIBLIMESUITESRC}/src/API/lms7_api.cpp - ${LIBLIMESUITESRC}/src/API/lms7_device.cpp - ${LIBLIMESUITESRC}/src/API/LimeSDR_mini.cpp - ${LIBLIMESUITESRC}/src/API/qLimeSDR.cpp + ${LIBLIMESUITESRC}/src/ + ${LIBLIMESUITESRC}/src/Logger.cpp\ + ${LIBLIMESUITESRC}/src/ADF4002/ADF4002.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_BD.cpp\ + ${LIBLIMESUITESRC}/src/ConnectionRegistry/IConnection.cpp\ + ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionHandle.cpp\ + ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionRegistry.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RegistersMap.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_parameters.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RxTxCalibrations.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_BaseCalibrations.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/goert.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/mcu_dc_iq_calibration.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/CalibrationCache.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_filtersCalibration.cpp\ + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_gainCalibrations.cpp\ + ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.cpp\ + ${LIBLIMESUITESRC}/src/protocols/Streamer.cpp\ + ${LIBLIMESUITESRC}/src/protocols/ConnectionImages.cpp\ + ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.cpp\ + ${LIBLIMESUITESRC}/src/kissFFT/kiss_fft.c\ + ${LIBLIMESUITESRC}/src/API/lms7_api.cpp\ + ${LIBLIMESUITESRC}/src/API/lms7_device.cpp\ + ${LIBLIMESUITESRC}/src/API/LmsGeneric.cpp\ + ${LIBLIMESUITESRC}/src/API/qLimeSDR.cpp\ + ${LIBLIMESUITESRC}/src/API/LimeSDR_mini.cpp\ + ${LIBLIMESUITESRC}/src/API/LimeSDR.cpp\ + ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_common.cpp\ + ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_Mini.cpp\ + ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_Q.cpp\ + ${LIBLIMESUITESRC}/src/GFIR/corrections.c\ + ${LIBLIMESUITESRC}/src/GFIR/gfir_lms.c\ + ${LIBLIMESUITESRC}/src/GFIR/lms.c\ + ${LIBLIMESUITESRC}/src/GFIR/recipes.c\ + ${LIBLIMESUITESRC}/src/GFIR/rounding.c\ + ${LIBLIMESUITESRC}/src/windowFunction.cpp\ + ${LIBLIMESUITESRC}/src/ConnectionFTDI/ConnectionFT601.cpp\ + ${LIBLIMESUITESRC}/src/ConnectionFTDI//ConnectionFT601Entry.cpp\ + ${LIBLIMESUITESRC}/src/ConnectionFX3/ConnectionFX3Entry.cpp\ + ${LIBLIMESUITESRC}/src/ConnectionFX3/ConnectionFX3.cpp src/BuiltinConnections.cpp - ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionHandle.cpp - ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionRegistry.cpp - ${LIBLIMESUITESRC}/src/ConnectionRegistry/IConnection.cpp - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAM.cpp - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAMEntry.cpp - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAMImages.cpp - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAMing.cpp - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/Connection_uLimeSDR.cpp - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/Connection_uLimeSDREntry.cpp - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/Connection_uLimeSDRing.cpp -# ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/DRV_DriverInterface.cpp - ${LIBLIMESUITESRC}/src/ConnectionXillybus/ConnectionXillybus.cpp - ${LIBLIMESUITESRC}/src/ConnectionXillybus/ConnectionXillybusEntry.cpp - ${LIBLIMESUITESRC}/src/ConnectionXillybus/ConnectionXillybusing.cpp - ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_common.cpp - ${LIBLIMESUITESRC}/src/GFIR/corrections.c - ${LIBLIMESUITESRC}/src/GFIR/gfir_lms.c - ${LIBLIMESUITESRC}/src/GFIR/lms.c - ${LIBLIMESUITESRC}/src/GFIR/recipes.c - ${LIBLIMESUITESRC}/src/GFIR/rounding.c - ${LIBLIMESUITESRC}/src/kissFFT/kiss_fft.c - ${LIBLIMESUITESRC}/src/lms7002m/CalibrationCache.cpp - ${LIBLIMESUITESRC}/src/lms7002m/goert.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_BaseCalibrations.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_filtersCalibration.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_gainCalibrations.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_parameters.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RegistersMap.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RxTxCalibrations.cpp - ${LIBLIMESUITESRC}/src/lms7002m/mcu_dc_iq_calibration.cpp - ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_BD.cpp - ${LIBLIMESUITESRC}/src/protocols/ILimeSDRStreaming.cpp - ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.cpp - ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.cpp - ${LIBLIMESUITESRC}/src/ErrorReporting.cpp - ${LIBLIMESUITESRC}/src/Logger.cpp src/SystemResources.cpp src/VersionInfo.cpp ) set(limesuite_HEADERS - ${LIBLIMESUITESRC}/src/ADF4002/ADF4002.h - ${LIBLIMESUITESRC}/src/API/lms7_device.h - ${LIBLIMESUITESRC}/src/API/LimeSDR_mini.h - ${LIBLIMESUITESRC}/src/API/qLimeSDR.h - ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionHandle.h - ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionRegistry.h - ${LIBLIMESUITESRC}/src/ConnectionRegistry/IConnection.h - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAM.h - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/Connection_uLimeSDR.h -# ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/DRV_DriverInterface.h - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/FTD3XXLibrary/FTD3XX.h - ${LIBLIMESUITESRC}/src/ConnectionXillybus/ConnectionXillybus.h - ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_common.h - ${LIBLIMESUITESRC}/src/GFIR/dfilter.h - ${LIBLIMESUITESRC}/src/GFIR/lms_gfir.h - ${LIBLIMESUITESRC}/src/GFIR/lms.h - ${LIBLIMESUITESRC}/src/kissFFT/_kiss_fft_guts.h - ${LIBLIMESUITESRC}/src/kissFFT/kiss_fft.h - ${LIBLIMESUITESRC}/src/lms7002m/CalibrationCache.h - ${LIBLIMESUITESRC}/src/lms7002m/goertzel.h - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M.h - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_parameters.h - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RegistersMap.h - ${LIBLIMESUITESRC}/src/lms7002m/mcu_programs.h - ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_BD.h - ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_File.h - ${LIBLIMESUITESRC}/src/protocols/ADCUnits.h - ${LIBLIMESUITESRC}/src/protocols/dataTypes.h - ${LIBLIMESUITESRC}/src/protocols/fifo.h - ${LIBLIMESUITESRC}/src/protocols/ILimeSDRStreaming.h - ${LIBLIMESUITESRC}/src/protocols/LMS64CCommands.h - ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.h - ${LIBLIMESUITESRC}/src/protocols/LMSBoards.h - ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.h - ${LIBLIMESUITESRC}/src/ErrorReporting.h - ${LIBLIMESUITESRC}/src/Logger.h - ${LIBLIMESUITESRC}/src/SystemResources.h - ${LIBLIMESUITESRC}/src/VersionInfo.h - ${LIBLIMESUITESRC}/src/lime/LimeSuite.h + ${LIBLIMESUITESRC}/src/API/*.h\ + ${LIBLIMESUITESRC}/src/GFIR/*.h\ + ${LIBLIMESUITESRC}/src/protocols/*.h\ + ${LIBLIMESUITESRC}/src/ConnectionRegistry/*.h\ + ${LIBLIMESUITESRC}/src/lms7002m_mcu/*.h\ + ${LIBLIMESUITESRC}/src/ADF4002/*.h\ + ${LIBLIMESUITESRC}/src/Si5351C/*.h\ + ${LIBLIMESUITESRC}/src/lms7002m/*.h\ + ${LIBLIMESUITESRC}/src/FPGA_common/*.h\ + ${LIBLIMESUITESRC}/src/HPM7/*.h\ + ${LIBLIMESUITESRC}/src/kissFFT/*.h ) include_directories( @@ -98,14 +69,17 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${LIBUSB_INCLUDE_DIR} ${LIBLIMESUITESRC}/src - ${LIBLIMESUITESRC}/src/ADF4002 - ${LIBLIMESUITESRC}/src/ConnectionRegistry - ${LIBLIMESUITESRC}/src/FPGA_common + ${LIBLIMESUITESRC}/src/API ${LIBLIMESUITESRC}/src/GFIR - ${LIBLIMESUITESRC}/src/lms7002m - ${LIBLIMESUITESRC}/src/lms7002m_mcu - ${LIBLIMESUITESRC}/src/Si5351C ${LIBLIMESUITESRC}/src/protocols + ${LIBLIMESUITESRC}/src/ConnectionRegistry + ${LIBLIMESUITESRC}/src/lms7002m_mcu + ${LIBLIMESUITESRC}/src/ADF4002 + ${LIBLIMESUITESRC}/src/Si5351C + ${LIBLIMESUITESRC}/src/lms7002m + ${LIBLIMESUITESRC}/src/FPGA_common + ${LIBLIMESUITESRC}/src/HPM7 + ${LIBLIMESUITESRC}/src/kissFFT ${LIBLIMESUITESRC}/external/cpp-feather-ini-parser ./include ) diff --git a/liblimesuite/liblimesuite.pro b/liblimesuite/liblimesuite.pro index e56d5e531..3e0d140e0 100644 --- a/liblimesuite/liblimesuite.pro +++ b/liblimesuite/liblimesuite.pro @@ -88,7 +88,6 @@ HEADERS = $$LIBLIMESUITESRC/src/API/*.h\ $$LIBLIMESUITESRC/src/Si5351C/*.h\ $$LIBLIMESUITESRC/src/lms7002m/*.h\ $$LIBLIMESUITESRC/src/FPGA_common/*.h\ - $$LIBLIMESUITESRC/src/lms7002m_mcu/*.h\ $$LIBLIMESUITESRC/src/HPM7/*.h\ $$LIBLIMESUITESRC/src/kissFFT/*.h From 791b93cb4ba9a12bc4ffb720f12f6b2b71c5467c Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 23:09:06 +0100 Subject: [PATCH 128/956] LimeSDR: Debian build cmake file for new LimeSuite (2) --- liblimesuite/CMakeLists.txt | 99 ++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 50 deletions(-) diff --git a/liblimesuite/CMakeLists.txt b/liblimesuite/CMakeLists.txt index acd883790..b29fdba47 100644 --- a/liblimesuite/CMakeLists.txt +++ b/liblimesuite/CMakeLists.txt @@ -4,46 +4,45 @@ find_package(LibUSB) find_package(SQLite3) set(limesuite_SOURCES - ${LIBLIMESUITESRC}/src/ - ${LIBLIMESUITESRC}/src/Logger.cpp\ - ${LIBLIMESUITESRC}/src/ADF4002/ADF4002.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_BD.cpp\ - ${LIBLIMESUITESRC}/src/ConnectionRegistry/IConnection.cpp\ - ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionHandle.cpp\ - ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionRegistry.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RegistersMap.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_parameters.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RxTxCalibrations.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_BaseCalibrations.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/goert.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/mcu_dc_iq_calibration.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/CalibrationCache.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_filtersCalibration.cpp\ - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_gainCalibrations.cpp\ - ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.cpp\ - ${LIBLIMESUITESRC}/src/protocols/Streamer.cpp\ - ${LIBLIMESUITESRC}/src/protocols/ConnectionImages.cpp\ - ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.cpp\ - ${LIBLIMESUITESRC}/src/kissFFT/kiss_fft.c\ - ${LIBLIMESUITESRC}/src/API/lms7_api.cpp\ - ${LIBLIMESUITESRC}/src/API/lms7_device.cpp\ - ${LIBLIMESUITESRC}/src/API/LmsGeneric.cpp\ - ${LIBLIMESUITESRC}/src/API/qLimeSDR.cpp\ - ${LIBLIMESUITESRC}/src/API/LimeSDR_mini.cpp\ - ${LIBLIMESUITESRC}/src/API/LimeSDR.cpp\ - ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_common.cpp\ - ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_Mini.cpp\ - ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_Q.cpp\ - ${LIBLIMESUITESRC}/src/GFIR/corrections.c\ - ${LIBLIMESUITESRC}/src/GFIR/gfir_lms.c\ - ${LIBLIMESUITESRC}/src/GFIR/lms.c\ - ${LIBLIMESUITESRC}/src/GFIR/recipes.c\ - ${LIBLIMESUITESRC}/src/GFIR/rounding.c\ - ${LIBLIMESUITESRC}/src/windowFunction.cpp\ - ${LIBLIMESUITESRC}/src/ConnectionFTDI/ConnectionFT601.cpp\ - ${LIBLIMESUITESRC}/src/ConnectionFTDI//ConnectionFT601Entry.cpp\ - ${LIBLIMESUITESRC}/src/ConnectionFX3/ConnectionFX3Entry.cpp\ + ${LIBLIMESUITESRC}/src/Logger.cpp + ${LIBLIMESUITESRC}/src/ADF4002/ADF4002.cpp + ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_BD.cpp + ${LIBLIMESUITESRC}/src/ConnectionRegistry/IConnection.cpp + ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionHandle.cpp + ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionRegistry.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RegistersMap.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_parameters.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RxTxCalibrations.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_BaseCalibrations.cpp + ${LIBLIMESUITESRC}/src/lms7002m/goert.cpp + ${LIBLIMESUITESRC}/src/lms7002m/mcu_dc_iq_calibration.cpp + ${LIBLIMESUITESRC}/src/lms7002m/CalibrationCache.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_filtersCalibration.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_gainCalibrations.cpp + ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.cpp + ${LIBLIMESUITESRC}/src/protocols/Streamer.cpp + ${LIBLIMESUITESRC}/src/protocols/ConnectionImages.cpp + ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.cpp + ${LIBLIMESUITESRC}/src/kissFFT/kiss_fft.c + ${LIBLIMESUITESRC}/src/API/lms7_api.cpp + ${LIBLIMESUITESRC}/src/API/lms7_device.cpp + ${LIBLIMESUITESRC}/src/API/LmsGeneric.cpp + ${LIBLIMESUITESRC}/src/API/qLimeSDR.cpp + ${LIBLIMESUITESRC}/src/API/LimeSDR_mini.cpp + ${LIBLIMESUITESRC}/src/API/LimeSDR.cpp + ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_common.cpp + ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_Mini.cpp + ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_Q.cpp + ${LIBLIMESUITESRC}/src/GFIR/corrections.c + ${LIBLIMESUITESRC}/src/GFIR/gfir_lms.c + ${LIBLIMESUITESRC}/src/GFIR/lms.c + ${LIBLIMESUITESRC}/src/GFIR/recipes.c + ${LIBLIMESUITESRC}/src/GFIR/rounding.c + ${LIBLIMESUITESRC}/src/windowFunction.cpp + ${LIBLIMESUITESRC}/src/ConnectionFTDI/ConnectionFT601.cpp + ${LIBLIMESUITESRC}/src/ConnectionFTDI//ConnectionFT601Entry.cpp + ${LIBLIMESUITESRC}/src/ConnectionFX3/ConnectionFX3Entry.cpp ${LIBLIMESUITESRC}/src/ConnectionFX3/ConnectionFX3.cpp src/BuiltinConnections.cpp src/SystemResources.cpp @@ -51,16 +50,16 @@ set(limesuite_SOURCES ) set(limesuite_HEADERS - ${LIBLIMESUITESRC}/src/API/*.h\ - ${LIBLIMESUITESRC}/src/GFIR/*.h\ - ${LIBLIMESUITESRC}/src/protocols/*.h\ - ${LIBLIMESUITESRC}/src/ConnectionRegistry/*.h\ - ${LIBLIMESUITESRC}/src/lms7002m_mcu/*.h\ - ${LIBLIMESUITESRC}/src/ADF4002/*.h\ - ${LIBLIMESUITESRC}/src/Si5351C/*.h\ - ${LIBLIMESUITESRC}/src/lms7002m/*.h\ - ${LIBLIMESUITESRC}/src/FPGA_common/*.h\ - ${LIBLIMESUITESRC}/src/HPM7/*.h\ + ${LIBLIMESUITESRC}/src/API/*.h + ${LIBLIMESUITESRC}/src/GFIR/*.h + ${LIBLIMESUITESRC}/src/protocols/*.h + ${LIBLIMESUITESRC}/src/ConnectionRegistry/*.h + ${LIBLIMESUITESRC}/src/lms7002m_mcu/*.h + ${LIBLIMESUITESRC}/src/ADF4002/*.h + ${LIBLIMESUITESRC}/src/Si5351C/*.h + ${LIBLIMESUITESRC}/src/lms7002m/*.h + ${LIBLIMESUITESRC}/src/FPGA_common/*.h + ${LIBLIMESUITESRC}/src/HPM7/*.h ${LIBLIMESUITESRC}/src/kissFFT/*.h ) From f4918ee201070681be5ee1f21190d574ed1ad801 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 16 Mar 2018 23:16:03 +0100 Subject: [PATCH 129/956] LimeSDR: Debian build fixed kiki --- liblimesuite/src/SystemResources.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/liblimesuite/src/SystemResources.cpp b/liblimesuite/src/SystemResources.cpp index f7f07f35f..75bbf7f1f 100644 --- a/liblimesuite/src/SystemResources.cpp +++ b/liblimesuite/src/SystemResources.cpp @@ -26,6 +26,7 @@ #ifdef __MINGW32__ #include #elif __unix__ +#include #include #endif From d042507c8f3576308d5f7f98e9dc52fa734ecbc1 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 17 Mar 2018 01:00:46 +0100 Subject: [PATCH 130/956] DATV demod: fixed initialization sequence in constructor --- plugins/channelrx/demoddatv/datvdemod.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 859b4e387..48a93886b 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -59,9 +59,9 @@ DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : m_objRFFilter = new fftfilt(-256000.0 / 1024000.0, 256000.0 / 1024000.0, rfFilterFftLength); //To setup correct Sample Rate + m_channelizer = new DownChannelizer(this); channelSampleRateChanged(); - m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); From eb2dcfb74b21f8daedbbaa3287d2bab157dcafbd Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 17 Mar 2018 19:56:07 +0100 Subject: [PATCH 131/956] RTLSDR: start direct mode frequency range at DC (0 kHz) --- plugins/samplesource/rtlsdr/rtlsdrinput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index 76156dc11..91f6aa427 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -34,7 +34,7 @@ MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgConfigureRTLSDR, Message) MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgFileRecord, Message) MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgStartStop, Message) -const quint64 RTLSDRInput::frequencyLowRangeMin = 1000UL; +const quint64 RTLSDRInput::frequencyLowRangeMin = 0UL; const quint64 RTLSDRInput::frequencyLowRangeMax = 275000UL; const quint64 RTLSDRInput::frequencyHighRangeMin = 24000UL; const quint64 RTLSDRInput::frequencyHighRangeMax = 1900000UL; From 733c213bf2f513c3c0cacb9f12b4e5c91cf0ea8d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 18 Mar 2018 11:16:39 +0100 Subject: [PATCH 132/956] RTLSDR: updated plugin version --- plugins/samplesource/rtlsdr/rtlsdrplugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp index 98034f378..bb75eb0ba 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp @@ -14,7 +14,7 @@ const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = { QString("RTL-SDR Input"), - QString("3.11.0"), + QString("3.13.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From d4e1521c903b0e271bbf45fc5629303b492f8448 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 18 Mar 2018 20:17:11 +0100 Subject: [PATCH 133/956] Web API: new entry point to get a channel report. Applied to NFM mod and demod --- plugins/channelrx/demodnfm/nfmdemod.cpp | 23 + plugins/channelrx/demodnfm/nfmdemod.h | 5 + plugins/channeltx/modnfm/nfmmod.cpp | 18 + plugins/channeltx/modnfm/nfmmod.h | 5 + sdrbase/channel/channelsinkapi.h | 6 + sdrbase/channel/channelsourceapi.h | 6 + sdrbase/resources/webapi/doc/html2/index.html | 553 +++++++++++++++++- .../webapi/doc/swagger/include/NFMDemod.yaml | 16 +- .../webapi/doc/swagger/include/NFMMod.yaml | 8 + .../resources/webapi/doc/swagger/swagger.yaml | 59 +- sdrbase/webapi/webapiadapterinterface.cpp | 1 + sdrbase/webapi/webapiadapterinterface.h | 18 + sdrbase/webapi/webapirequestmapper.cpp | 100 ++++ sdrbase/webapi/webapirequestmapper.h | 3 + sdrgui/webapi/webapiadaptergui.cpp | 61 ++ sdrgui/webapi/webapiadaptergui.h | 6 + .../api/swagger/include/NFMDemod.yaml | 16 +- .../sdrangel/api/swagger/include/NFMMod.yaml | 8 + swagger/sdrangel/api/swagger/swagger.yaml | 59 +- swagger/sdrangel/code/html2/index.html | 553 +++++++++++++++++- .../code/qt5/client/SWGChannelReport.cpp | 175 ++++++ .../code/qt5/client/SWGChannelReport.h | 78 +++ .../code/qt5/client/SWGChannelSettings.h | 2 +- .../code/qt5/client/SWGDeviceSetApi.cpp | 56 ++ .../code/qt5/client/SWGDeviceSetApi.h | 6 + .../code/qt5/client/SWGDeviceSettings.h | 2 +- .../code/qt5/client/SWGModelFactory.h | 12 + .../code/qt5/client/SWGNFMDemodReport.cpp | 148 +++++ .../code/qt5/client/SWGNFMDemodReport.h | 69 +++ .../code/qt5/client/SWGNFMModReport.cpp | 106 ++++ .../code/qt5/client/SWGNFMModReport.h | 57 ++ 31 files changed, 2221 insertions(+), 14 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGChannelReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGNFMModReport.h diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index e252d8ef6..93f91cf98 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -22,9 +22,12 @@ #include "SWGChannelSettings.h" #include "SWGNFMDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGNFMDemodReport.h" #include "dsp/downchannelizer.h" #include "util/stepfunctions.h" +#include "util/db.h" #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" @@ -643,6 +646,16 @@ int NFMDemod::webapiSettingsPutPatch( return 200; } +int NFMDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setNfmDemodReport(new SWGSDRangel::SWGNFMDemodReport()); + response.getNfmDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + void NFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMDemodSettings& settings) { response.getNfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth); @@ -675,3 +688,13 @@ void NFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp } } +void NFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getNfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getNfmDemodReport()->setCtcssTone(m_settings.m_ctcssOn ? (m_ctcssIndex ? 0 : m_ctcssDetector.getToneSet()[m_ctcssIndex-1]) : 0); + response.getNfmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); +} diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index eea637641..54e397495 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -136,6 +136,10 @@ public: SWGSDRangel::SWGChannelSettings& response, QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + const Real *getCtcssToneSet(int& nbTones) const { nbTones = m_ctcssDetector.getNTones(); return m_ctcssDetector.getToneSet(); @@ -220,6 +224,7 @@ private: void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const NFMDemodSettings& settings, bool force = false); void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; #endif // INCLUDE_NFMDEMOD_H diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp index 9f68c2a10..8648cb257 100644 --- a/plugins/channeltx/modnfm/nfmmod.cpp +++ b/plugins/channeltx/modnfm/nfmmod.cpp @@ -20,6 +20,8 @@ #include "SWGChannelSettings.h" #include "SWGCWKeyerSettings.h" +#include "SWGChannelReport.h" +#include "SWGNFMModReport.h" #include #include @@ -30,6 +32,7 @@ #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" #include "dsp/threadedbasebandsamplesource.h" +#include "util/db.h" #include "nfmmod.h" @@ -625,6 +628,16 @@ int NFMMod::webapiSettingsPutPatch( return 200; } +int NFMMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setNfmModReport(new SWGSDRangel::SWGNFMModReport()); + response.getNfmModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + void NFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMModSettings& settings) { response.getNfmModSettings()->setAfBandwidth(settings.m_afBandwidth); @@ -666,3 +679,8 @@ void NFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); } + +void NFMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getNfmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); +} diff --git a/plugins/channeltx/modnfm/nfmmod.h b/plugins/channeltx/modnfm/nfmmod.h index 9276b10b1..4cfcdf70d 100644 --- a/plugins/channeltx/modnfm/nfmmod.h +++ b/plugins/channeltx/modnfm/nfmmod.h @@ -227,6 +227,10 @@ public: SWGSDRangel::SWGChannelSettings& response, QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } CWKeyer *getCWKeyer() { return &m_cwKeyer; } @@ -302,6 +306,7 @@ private: void openFileStream(); void seekFileStream(int seekPercentage); void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; diff --git a/sdrbase/channel/channelsinkapi.h b/sdrbase/channel/channelsinkapi.h index a2c2f9b1b..fab5bc276 100644 --- a/sdrbase/channel/channelsinkapi.h +++ b/sdrbase/channel/channelsinkapi.h @@ -28,6 +28,7 @@ namespace SWGSDRangel { class SWGChannelSettings; + class SWGChannelReport; } class SDRBASE_API ChannelSinkAPI { @@ -57,6 +58,11 @@ public: QString& errorMessage) { errorMessage = "Not implemented"; return 501; } + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response __attribute__((unused)), + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + int getIndexInDeviceSet() const { return m_indexInDeviceSet; } void setIndexInDeviceSet(int indexInDeviceSet) { m_indexInDeviceSet = indexInDeviceSet; } uint64_t getUID() const { return m_uid; } diff --git a/sdrbase/channel/channelsourceapi.h b/sdrbase/channel/channelsourceapi.h index 18734b8f1..5d943ed6d 100644 --- a/sdrbase/channel/channelsourceapi.h +++ b/sdrbase/channel/channelsourceapi.h @@ -27,6 +27,7 @@ namespace SWGSDRangel { class SWGChannelSettings; + class SWGChannelReport; } class SDRBASE_API ChannelSourceAPI { @@ -56,6 +57,11 @@ public: QString& errorMessage) { errorMessage = "Not implemented"; return 501; } + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response __attribute__((unused)), + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + int getIndexInDeviceSet() const { return m_indexInDeviceSet; } void setIndexInDeviceSet(int indexInDeviceSet) { m_indexInDeviceSet = indexInDeviceSet; } uint64_t getUID() const { return m_uid; } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index faedb146f..8f924b78d 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -840,6 +840,27 @@ margin-bottom: 20px; } }, "description" : "Summarized information about channel plugin" +}; + defs.ChannelReport = { + "required" : [ "channelType", "tx" ], + "discriminator" : "channelType", + "properties" : { + "channelType" : { + "type" : "string", + "description" : "Channel type code" + }, + "tx" : { + "type" : "integer", + "description" : "Not zero if it is a tx channel else it is a rx channel" + }, + "NFMDemodReport" : { + "$ref" : "#/definitions/NFMDemodReport" + }, + "NFMModReport" : { + "$ref" : "#/definitions/NFMModReport" + } + }, + "description" : "Base channel report. The specific channel report present depends on channelType." }; defs.ChannelSettings = { "required" : [ "channelType", "tx" ], @@ -860,7 +881,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/NFMModSettings" } }, - "description" : "Base channel settings" + "description" : "Base channel settings. The specific channel settings present depends on channelType." }; defs.DVSeralDevices = { "required" : [ "nbDevices" ], @@ -1000,7 +1021,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/RtlSdrSettings" } }, - "description" : "Base device settings" + "description" : "Base device settings. The specific device settings present depends on deviceHwType." }; defs.DeviceState = { "required" : [ "state" ], @@ -1325,6 +1346,25 @@ margin-bottom: 20px; } }, "description" : "Logging parameters setting" +}; + defs.NFMDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power received in channel (dB)" + }, + "ctcssTone" : { + "type" : "number", + "format" : "float", + "description" : "CTCSS tone frequency if detected else 0" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + } + }, + "description" : "NFMDemod" }; defs.NFMDemodSettings = { "properties" : { @@ -1389,6 +1429,16 @@ margin-bottom: 20px; } }, "description" : "NFMDemod" +}; + defs.NFMModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + } + }, + "description" : "NFMMod" }; defs.NFMModSettings = { "properties" : { @@ -1694,6 +1744,9 @@ margin-bottom: 20px;
    +
  • + devicesetChannelReportGet +
  • devicesetChannelSettingsGet
  • @@ -2838,6 +2891,500 @@ $(document).ready(function() {
    +
    +
    +
    +

    devicesetChannelReportGet

    +

    +
    +
    +
    +

    +

    get a channel report

    +

    +
    +
    /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DeviceSetApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        Integer channelIndex = 56; // Integer | Index of the channel in the channels list for this device set
    +        try {
    +            ChannelReport result = apiInstance.devicesetChannelReportGet(deviceSetIndex, channelIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetChannelReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DeviceSetApi;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        Integer channelIndex = 56; // Integer | Index of the channel in the channels list for this device set
    +        try {
    +            ChannelReport result = apiInstance.devicesetChannelReportGet(deviceSetIndex, channelIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetChannelReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    Integer *deviceSetIndex = 56; // Index of device set in the device set list
    +Integer *channelIndex = 56; // Index of the channel in the channels list for this device set
    +
    +DeviceSetApi *apiInstance = [[DeviceSetApi alloc] init];
    +
    +[apiInstance devicesetChannelReportGetWith:deviceSetIndex
    +    channelIndex:channelIndex
    +              completionHandler: ^(ChannelReport output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DeviceSetApi()
    +
    +var deviceSetIndex = 56; // {Integer} Index of device set in the device set list
    +
    +var channelIndex = 56; // {Integer} Index of the channel in the channels list for this device set
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.devicesetChannelReportGet(deviceSetIndex, channelIndex, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class devicesetChannelReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DeviceSetApi();
    +            var deviceSetIndex = 56;  // Integer | Index of device set in the device set list
    +            var channelIndex = 56;  // Integer | Index of the channel in the channels list for this device set
    +
    +            try
    +            {
    +                ChannelReport result = apiInstance.devicesetChannelReportGet(deviceSetIndex, channelIndex);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DeviceSetApi.devicesetChannelReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DeviceSetApi();
    +$deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +$channelIndex = 56; // Integer | Index of the channel in the channels list for this device set
    +
    +try {
    +    $result = $api_instance->devicesetChannelReportGet($deviceSetIndex, $channelIndex);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DeviceSetApi->devicesetChannelReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DeviceSetApi;
    +
    +my $api_instance = SWGSDRangel::DeviceSetApi->new();
    +my $deviceSetIndex = 56; # Integer | Index of device set in the device set list
    +my $channelIndex = 56; # Integer | Index of the channel in the channels list for this device set
    +
    +eval { 
    +    my $result = $api_instance->devicesetChannelReportGet(deviceSetIndex => $deviceSetIndex, channelIndex => $channelIndex);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DeviceSetApi->devicesetChannelReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DeviceSetApi()
    +deviceSetIndex = 56 # Integer | Index of device set in the device set list
    +channelIndex = 56 # Integer | Index of the channel in the channels list for this device set
    +
    +try: 
    +    api_response = api_instance.deviceset_channel_report_get(deviceSetIndex, channelIndex)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DeviceSetApi->devicesetChannelReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + +
    Path parameters
    + + + + + + + + + + + + + +
    NameDescription
    deviceSetIndex* + + +
    +
    +
    + + Integer + + +
    + Index of device set in the device set list +
    +
    +
    + Required +
    +
    +
    +
    channelIndex* + + +
    +
    +
    + + Integer + + +
    + Index of the channel in the channels list for this device set +
    +
    +
    + Required +
    +
    +
    +
    + + + + + +

    Responses

    +

    Status: 200 - On success return channel report

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set or channel index

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device or channel not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -16925,7 +17472,7 @@ except ApiException as e:
    - Generated 2018-03-03T23:35:13.013+01:00 + Generated 2018-03-18T11:03:22.829+01:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml index c83d72e4e..5a66f9127 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml @@ -42,4 +42,18 @@ NFMDemodSettings: type: integer title: type: string - \ No newline at end of file + +NFMDemodReport: + description: NFMDemod + properties: + channelPowerDB: + description: power received in channel (dB) + type: number + format: float + ctcssTone: + description: CTCSS tone frequency if detected else 0 + type: number + format: float + squelch: + description: squelch status (1 if open else 0) + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml index e41bb718f..3148c36dc 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml @@ -37,4 +37,12 @@ NFMModSettings: type: integer cwKeyer: $ref: "/doc/swagger/include/CWKeyer.yaml#/CWKeyerSettings" + +NFMModReport: + description: NFMMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index adba3902f..64af08125 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -997,6 +997,43 @@ paths: "501": $ref: "#/responses/Response_501" + /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report: + x-swagger-router-controller: deviceset + get: + description: get a channel report + operationId: devicesetChannelReportGet + tags: + - DeviceSet + parameters: + - in: path + name: deviceSetIndex + type: integer + required: true + description: Index of device set in the device set list + - in: path + name: channelIndex + type: integer + required: true + description: Index of the channel in the channels list for this device set + responses: + "200": + description: On success return channel report + schema: + $ref: "#/definitions/ChannelReport" + "400": + description: Invalid device set or channel index + schema: + $ref: "#/definitions/ErrorResponse" + "404": + description: Device or channel not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + /swagger: x-swagger-pipe: swagger_raw @@ -1470,7 +1507,7 @@ definitions: $ref: "#/definitions/PresetIdentifier" DeviceSettings: - description: Base device settings + description: Base device settings. The specific device settings present depends on deviceHwType. discriminator: deviceHwType required: - deviceHwType @@ -1496,7 +1533,7 @@ definitions: $ref: "/doc/swagger/include/RtlSdr.yaml#/RtlSdrSettings" ChannelSettings: - description: Base channel settings + description: Base channel settings. The specific channel settings present depends on channelType. discriminator: channelType required: - channelType @@ -1513,6 +1550,24 @@ definitions: NFMModSettings: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" + ChannelReport: + description: Base channel report. The specific channel report present depends on channelType. + discriminator: channelType + required: + - channelType + - tx + properties: + channelType: + description: Channel type code + type: string + tx: + description: Not zero if it is a tx channel else it is a rx channel + type: integer + NFMDemodReport: + $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport" + NFMModReport: + $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModReport" + responses: Response_500: diff --git a/sdrbase/webapi/webapiadapterinterface.cpp b/sdrbase/webapi/webapiadapterinterface.cpp index d8a1935b8..9e2b2af3c 100644 --- a/sdrbase/webapi/webapiadapterinterface.cpp +++ b/sdrbase/webapi/webapiadapterinterface.cpp @@ -39,3 +39,4 @@ std::regex WebAPIAdapterInterface::devicesetDeviceRunURLRe("^/sdrangel/deviceset std::regex WebAPIAdapterInterface::devicesetChannelURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel$"); std::regex WebAPIAdapterInterface::devicesetChannelIndexURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel/([0-9]{1,2})$"); std::regex WebAPIAdapterInterface::devicesetChannelSettingsURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel/([0-9]{1,2})/settings$"); +std::regex WebAPIAdapterInterface::devicesetChannelReportURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel/([0-9]{1,2})/report"); diff --git a/sdrbase/webapi/webapiadapterinterface.h b/sdrbase/webapi/webapiadapterinterface.h index ac8f559c3..ca16f738e 100644 --- a/sdrbase/webapi/webapiadapterinterface.h +++ b/sdrbase/webapi/webapiadapterinterface.h @@ -47,6 +47,7 @@ namespace SWGSDRangel class SWGDeviceSettings; class SWGDeviceState; class SWGChannelSettings; + class SWGChannelReport; class SWGSuccessResponse; } @@ -515,6 +516,22 @@ public: return 501; } + + /** + * Handler of /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/settings (GET) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * returns the Http status code (default 501: not implemented) + */ + virtual int devicesetChannelReportGet( + int deviceSetIndex __attribute__((unused)), + int channelIndex __attribute__((unused)), + SWGSDRangel::SWGChannelReport& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) + { + error.init(); + *error.getMessage() = QString("Function not implemented"); + return 501; + } + static QString instanceSummaryURL; static QString instanceDevicesURL; static QString instanceChannelsURL; @@ -535,6 +552,7 @@ public: static std::regex devicesetChannelURLRe; static std::regex devicesetChannelIndexURLRe; static std::regex devicesetChannelSettingsURLRe; + static std::regex devicesetChannelReportURLRe; }; diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 5e4399da2..ad43613b8 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -39,6 +39,7 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGChannelSettings.h" +#include "SWGChannelReport.h" #include "SWGSuccessResponse.h" #include "SWGErrorResponse.h" @@ -118,6 +119,8 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http devicesetChannelIndexService(std::string(desc_match[1]), std::string(desc_match[2]), request, response); } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetChannelSettingsURLRe)) { devicesetChannelSettingsService(std::string(desc_match[1]), std::string(desc_match[2]), request, response); + } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetChannelReportURLRe)) { + devicesetChannelReportService(std::string(desc_match[1]), std::string(desc_match[2]), request, response); } else // serve static documentation pages { @@ -1270,6 +1273,43 @@ void WebAPIRequestMapper::devicesetChannelSettingsService( } } +void WebAPIRequestMapper::devicesetChannelReportService( + const std::string& deviceSetIndexStr, + const std::string& channelIndexStr, + qtwebapp::HttpRequest& request, + qtwebapp::HttpResponse& response) +{ + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + + try + { + int deviceSetIndex = boost::lexical_cast(deviceSetIndexStr); + int channelIndex = boost::lexical_cast(channelIndexStr); + + if (request.getMethod() == "GET") + { + SWGSDRangel::SWGChannelReport normalResponse; + resetChannelReport(normalResponse); + int status = m_adapter->devicesetChannelReportGet(deviceSetIndex, channelIndex, normalResponse, errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + } + catch (const boost::bad_lexical_cast &e) + { + errorResponse.init(); + *errorResponse.getMessage() = "Wrong integer conversion on index"; + response.setStatus(400,"Invalid data"); + response.write(errorResponse.asJson().toUtf8()); + } +} + bool WebAPIRequestMapper::parseJsonBody(QString& jsonStr, QJsonObject& jsonObject, qtwebapp::HttpResponse& response) { SWGSDRangel::SWGErrorResponse errorResponse; @@ -1569,6 +1609,59 @@ bool WebAPIRequestMapper::validateChannelSettings( } } +bool WebAPIRequestMapper::validateChannelReport( + SWGSDRangel::SWGChannelReport& channelReport, + QJsonObject& jsonObject, + QStringList& channelReportKeys) +{ + if (jsonObject.contains("tx")) { + channelReport.setTx(jsonObject["tx"].toInt()); + } else { + channelReport.setTx(0); // assume Rx + } + + if (jsonObject.contains("channelType") && jsonObject["channelType"].isString()) { + channelReport.setChannelType(new QString(jsonObject["channelType"].toString())); + } else { + return false; + } + + QString *channelType = channelReport.getChannelType(); + + if (*channelType == "NFMDemod") + { + if (channelReport.getTx() == 0) + { + QJsonObject nfmDemodReportJsonObject = jsonObject["NFMDemodReport"].toObject(); + channelReportKeys = nfmDemodReportJsonObject.keys(); + channelReport.setNfmDemodReport(new SWGSDRangel::SWGNFMDemodReport()); + channelReport.getNfmDemodReport()->fromJsonObject(nfmDemodReportJsonObject); + return true; + } + else { + return false; + } + } + else if (*channelType == "NFMMod") + { + if (channelReport.getTx() != 0) + { + QJsonObject nfmModReportJsonObject = jsonObject["NFMModReport"].toObject(); + channelReportKeys = nfmModReportJsonObject.keys(); + channelReport.setNfmModReport(new SWGSDRangel::SWGNFMModReport()); + channelReport.getNfmModReport()->fromJsonObject(nfmModReportJsonObject); + return true; + } + else { + return false; + } + } + else + { + return false; + } +} + void WebAPIRequestMapper::appendSettingsSubKeys( const QJsonObject& parentSettingsJsonObject, QJsonObject& childSettingsJsonObject, @@ -1603,3 +1696,10 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setNfmModSettings(0); } +void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& channelReport) +{ + channelReport.cleanup(); + channelReport.setChannelType(0); + channelReport.setNfmDemodReport(0); + channelReport.setNfmModReport(0); +} diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index bb95aa1fc..3c5aa8d07 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -68,6 +68,7 @@ private: void devicesetChannelService(const std::string& deviceSetIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetChannelIndexService(const std::string& deviceSetIndexStr, const std::string& channelIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetChannelSettingsService(const std::string& deviceSetIndexStr, const std::string& channelIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void devicesetChannelReportService(const std::string& deviceSetIndexStr, const std::string& channelIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); bool validatePresetTransfer(SWGSDRangel::SWGPresetTransfer& presetTransfer); bool validatePresetIdentifer(SWGSDRangel::SWGPresetIdentifier& presetIdentifier); @@ -75,6 +76,7 @@ private: bool validateDeviceListItem(SWGSDRangel::SWGDeviceListItem& deviceListItem, QJsonObject& jsonObject); bool validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys); bool validateChannelSettings(SWGSDRangel::SWGChannelSettings& deviceSettings, QJsonObject& jsonObject, QStringList& channelSettingsKeys); + bool validateChannelReport(SWGSDRangel::SWGChannelReport& deviceReport, QJsonObject& jsonObject, QStringList& channelReportKeys); void appendSettingsSubKeys( const QJsonObject& parentSettingsJsonObject, @@ -86,6 +88,7 @@ private: void resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings); void resetChannelSettings(SWGSDRangel::SWGChannelSettings& deviceSettings); + void resetChannelReport(SWGSDRangel::SWGChannelReport& deviceSettings); }; #endif /* SDRBASE_WEBAPI_WEBAPIREQUESTMAPPER_H_ */ diff --git a/sdrgui/webapi/webapiadaptergui.cpp b/sdrgui/webapi/webapiadaptergui.cpp index 0869cb82c..fa9144c95 100644 --- a/sdrgui/webapi/webapiadaptergui.cpp +++ b/sdrgui/webapi/webapiadaptergui.cpp @@ -54,6 +54,7 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" #include "SWGChannelSettings.h" +#include "SWGChannelReport.h" #include "SWGSuccessResponse.h" #include "SWGErrorResponse.h" #include "SWGDeviceState.h" @@ -1199,6 +1200,66 @@ int WebAPIAdapterGUI::devicesetChannelSettingsGet( } } + +int WebAPIAdapterGUI::devicesetChannelReportGet( + int deviceSetIndex, + int channelIndex, + SWGSDRangel::SWGChannelReport& response, + SWGSDRangel::SWGErrorResponse& error) +{ + error.init(); + + if ((deviceSetIndex >= 0) && (deviceSetIndex < (int) m_mainWindow.m_deviceUIs.size())) + { + DeviceUISet *deviceSet = m_mainWindow.m_deviceUIs[deviceSetIndex]; + + if (deviceSet->m_deviceSourceEngine) // Rx + { + ChannelSinkAPI *channelAPI = deviceSet->m_deviceSourceAPI->getChanelAPIAt(channelIndex); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel with index %1").arg(channelIndex); + return 404; + } + else + { + response.setChannelType(new QString()); + channelAPI->getIdentifier(*response.getChannelType()); + response.setTx(0); + return channelAPI->webapiReportGet(response, *error.getMessage()); + } + } + else if (deviceSet->m_deviceSinkEngine) // Tx + { + ChannelSourceAPI *channelAPI = deviceSet->m_deviceSinkAPI->getChanelAPIAt(channelIndex); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel with index %1").arg(channelIndex); + return 404; + } + else + { + response.setChannelType(new QString()); + channelAPI->getIdentifier(*response.getChannelType()); + response.setTx(1); + return channelAPI->webapiReportGet(response, *error.getMessage()); + } + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } + } + else + { + *error.getMessage() = QString("There is no device set with index %1").arg(deviceSetIndex); + return 404; + } +} + int WebAPIAdapterGUI::devicesetChannelSettingsPutPatch( int deviceSetIndex, int channelIndex, diff --git a/sdrgui/webapi/webapiadaptergui.h b/sdrgui/webapi/webapiadaptergui.h index b82124fff..37be03994 100644 --- a/sdrgui/webapi/webapiadaptergui.h +++ b/sdrgui/webapi/webapiadaptergui.h @@ -185,6 +185,12 @@ public: SWGSDRangel::SWGChannelSettings& response, SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetChannelReportGet( + int deviceSetIndex, + int channelIndex, + SWGSDRangel::SWGChannelReport& response, + SWGSDRangel::SWGErrorResponse& error); + private: MainWindow& m_mainWindow; diff --git a/swagger/sdrangel/api/swagger/include/NFMDemod.yaml b/swagger/sdrangel/api/swagger/include/NFMDemod.yaml index c83d72e4e..5a66f9127 100644 --- a/swagger/sdrangel/api/swagger/include/NFMDemod.yaml +++ b/swagger/sdrangel/api/swagger/include/NFMDemod.yaml @@ -42,4 +42,18 @@ NFMDemodSettings: type: integer title: type: string - \ No newline at end of file + +NFMDemodReport: + description: NFMDemod + properties: + channelPowerDB: + description: power received in channel (dB) + type: number + format: float + ctcssTone: + description: CTCSS tone frequency if detected else 0 + type: number + format: float + squelch: + description: squelch status (1 if open else 0) + type: integer diff --git a/swagger/sdrangel/api/swagger/include/NFMMod.yaml b/swagger/sdrangel/api/swagger/include/NFMMod.yaml index 0ce05274b..e121798f8 100644 --- a/swagger/sdrangel/api/swagger/include/NFMMod.yaml +++ b/swagger/sdrangel/api/swagger/include/NFMMod.yaml @@ -37,4 +37,12 @@ NFMModSettings: type: integer cwKeyer: $ref: "http://localhost:8081/api/swagger/include/CWKeyer.yaml#/CWKeyerSettings" + +NFMModReport: + description: NFMMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 3a0cc6387..e1ae42dc5 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -997,6 +997,43 @@ paths: "501": $ref: "#/responses/Response_501" + /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report: + x-swagger-router-controller: deviceset + get: + description: get a channel report + operationId: devicesetChannelReportGet + tags: + - DeviceSet + parameters: + - in: path + name: deviceSetIndex + type: integer + required: true + description: Index of device set in the device set list + - in: path + name: channelIndex + type: integer + required: true + description: Index of the channel in the channels list for this device set + responses: + "200": + description: On success return channel report + schema: + $ref: "#/definitions/ChannelReport" + "400": + description: Invalid device set or channel index + schema: + $ref: "#/definitions/ErrorResponse" + "404": + description: Device or channel not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + /swagger: x-swagger-pipe: swagger_raw @@ -1470,7 +1507,7 @@ definitions: $ref: "#/definitions/PresetIdentifier" DeviceSettings: - description: Base device settings + description: Base device settings. The specific device settings present depends on deviceHwType. discriminator: deviceHwType required: - deviceHwType @@ -1496,7 +1533,7 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/RtlSdr.yaml#/RtlSdrSettings" ChannelSettings: - description: Base channel settings + description: Base channel settings. The specific channel settings present depends on channelType. discriminator: channelType required: - channelType @@ -1513,6 +1550,24 @@ definitions: NFMModSettings: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" + ChannelReport: + description: Base channel report. The specific channel report present depends on channelType. + discriminator: channelType + required: + - channelType + - tx + properties: + channelType: + description: Channel type code + type: string + tx: + description: Not zero if it is a tx channel else it is a rx channel + type: integer + NFMDemodReport: + $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" + NFMModReport: + $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModReport" + responses: Response_500: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index faedb146f..8f924b78d 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -840,6 +840,27 @@ margin-bottom: 20px; } }, "description" : "Summarized information about channel plugin" +}; + defs.ChannelReport = { + "required" : [ "channelType", "tx" ], + "discriminator" : "channelType", + "properties" : { + "channelType" : { + "type" : "string", + "description" : "Channel type code" + }, + "tx" : { + "type" : "integer", + "description" : "Not zero if it is a tx channel else it is a rx channel" + }, + "NFMDemodReport" : { + "$ref" : "#/definitions/NFMDemodReport" + }, + "NFMModReport" : { + "$ref" : "#/definitions/NFMModReport" + } + }, + "description" : "Base channel report. The specific channel report present depends on channelType." }; defs.ChannelSettings = { "required" : [ "channelType", "tx" ], @@ -860,7 +881,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/NFMModSettings" } }, - "description" : "Base channel settings" + "description" : "Base channel settings. The specific channel settings present depends on channelType." }; defs.DVSeralDevices = { "required" : [ "nbDevices" ], @@ -1000,7 +1021,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/RtlSdrSettings" } }, - "description" : "Base device settings" + "description" : "Base device settings. The specific device settings present depends on deviceHwType." }; defs.DeviceState = { "required" : [ "state" ], @@ -1325,6 +1346,25 @@ margin-bottom: 20px; } }, "description" : "Logging parameters setting" +}; + defs.NFMDemodReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power received in channel (dB)" + }, + "ctcssTone" : { + "type" : "number", + "format" : "float", + "description" : "CTCSS tone frequency if detected else 0" + }, + "squelch" : { + "type" : "integer", + "description" : "squelch status (1 if open else 0)" + } + }, + "description" : "NFMDemod" }; defs.NFMDemodSettings = { "properties" : { @@ -1389,6 +1429,16 @@ margin-bottom: 20px; } }, "description" : "NFMDemod" +}; + defs.NFMModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + } + }, + "description" : "NFMMod" }; defs.NFMModSettings = { "properties" : { @@ -1694,6 +1744,9 @@ margin-bottom: 20px;
  • devicesetChannelPost
  • +
  • + devicesetChannelReportGet +
  • devicesetChannelSettingsGet
  • @@ -2838,6 +2891,500 @@ $(document).ready(function() {
    +
    +
    +
    +

    devicesetChannelReportGet

    +

    +
    +
    +
    +

    +

    get a channel report

    +

    +
    +
    /sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DeviceSetApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        Integer channelIndex = 56; // Integer | Index of the channel in the channels list for this device set
    +        try {
    +            ChannelReport result = apiInstance.devicesetChannelReportGet(deviceSetIndex, channelIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetChannelReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DeviceSetApi;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        Integer channelIndex = 56; // Integer | Index of the channel in the channels list for this device set
    +        try {
    +            ChannelReport result = apiInstance.devicesetChannelReportGet(deviceSetIndex, channelIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetChannelReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    Integer *deviceSetIndex = 56; // Index of device set in the device set list
    +Integer *channelIndex = 56; // Index of the channel in the channels list for this device set
    +
    +DeviceSetApi *apiInstance = [[DeviceSetApi alloc] init];
    +
    +[apiInstance devicesetChannelReportGetWith:deviceSetIndex
    +    channelIndex:channelIndex
    +              completionHandler: ^(ChannelReport output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DeviceSetApi()
    +
    +var deviceSetIndex = 56; // {Integer} Index of device set in the device set list
    +
    +var channelIndex = 56; // {Integer} Index of the channel in the channels list for this device set
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.devicesetChannelReportGet(deviceSetIndex, channelIndex, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class devicesetChannelReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DeviceSetApi();
    +            var deviceSetIndex = 56;  // Integer | Index of device set in the device set list
    +            var channelIndex = 56;  // Integer | Index of the channel in the channels list for this device set
    +
    +            try
    +            {
    +                ChannelReport result = apiInstance.devicesetChannelReportGet(deviceSetIndex, channelIndex);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DeviceSetApi.devicesetChannelReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DeviceSetApi();
    +$deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +$channelIndex = 56; // Integer | Index of the channel in the channels list for this device set
    +
    +try {
    +    $result = $api_instance->devicesetChannelReportGet($deviceSetIndex, $channelIndex);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DeviceSetApi->devicesetChannelReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DeviceSetApi;
    +
    +my $api_instance = SWGSDRangel::DeviceSetApi->new();
    +my $deviceSetIndex = 56; # Integer | Index of device set in the device set list
    +my $channelIndex = 56; # Integer | Index of the channel in the channels list for this device set
    +
    +eval { 
    +    my $result = $api_instance->devicesetChannelReportGet(deviceSetIndex => $deviceSetIndex, channelIndex => $channelIndex);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DeviceSetApi->devicesetChannelReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DeviceSetApi()
    +deviceSetIndex = 56 # Integer | Index of device set in the device set list
    +channelIndex = 56 # Integer | Index of the channel in the channels list for this device set
    +
    +try: 
    +    api_response = api_instance.deviceset_channel_report_get(deviceSetIndex, channelIndex)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DeviceSetApi->devicesetChannelReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + +
    Path parameters
    + + + + + + + + + + + + + +
    NameDescription
    deviceSetIndex* + + +
    +
    +
    + + Integer + + +
    + Index of device set in the device set list +
    +
    +
    + Required +
    +
    +
    +
    channelIndex* + + +
    +
    +
    + + Integer + + +
    + Index of the channel in the channels list for this device set +
    +
    +
    + Required +
    +
    +
    +
    + + + + + +

    Responses

    +

    Status: 200 - On success return channel report

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set or channel index

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device or channel not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -16925,7 +17472,7 @@ except ApiException as e:
    - Generated 2018-03-03T23:35:13.013+01:00 + Generated 2018-03-18T11:03:22.829+01:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp new file mode 100644 index 000000000..345541aec --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -0,0 +1,175 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGChannelReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGChannelReport::SWGChannelReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGChannelReport::SWGChannelReport() { + channel_type = nullptr; + m_channel_type_isSet = false; + tx = 0; + m_tx_isSet = false; + nfm_demod_report = nullptr; + m_nfm_demod_report_isSet = false; + nfm_mod_report = nullptr; + m_nfm_mod_report_isSet = false; +} + +SWGChannelReport::~SWGChannelReport() { + this->cleanup(); +} + +void +SWGChannelReport::init() { + channel_type = new QString(""); + m_channel_type_isSet = false; + tx = 0; + m_tx_isSet = false; + nfm_demod_report = new SWGNFMDemodReport(); + m_nfm_demod_report_isSet = false; + nfm_mod_report = new SWGNFMModReport(); + m_nfm_mod_report_isSet = false; +} + +void +SWGChannelReport::cleanup() { + if(channel_type != nullptr) { + delete channel_type; + } + + if(nfm_demod_report != nullptr) { + delete nfm_demod_report; + } + if(nfm_mod_report != nullptr) { + delete nfm_mod_report; + } +} + +SWGChannelReport* +SWGChannelReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGChannelReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_type, pJson["channelType"], "QString", "QString"); + + ::SWGSDRangel::setValue(&tx, pJson["tx"], "qint32", ""); + + ::SWGSDRangel::setValue(&nfm_demod_report, pJson["NFMDemodReport"], "SWGNFMDemodReport", "SWGNFMDemodReport"); + + ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); + +} + +QString +SWGChannelReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGChannelReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(channel_type != nullptr && *channel_type != QString("")){ + toJsonValue(QString("channelType"), channel_type, obj, QString("QString")); + } + if(m_tx_isSet){ + obj->insert("tx", QJsonValue(tx)); + } + if((nfm_demod_report != nullptr) && (nfm_demod_report->isSet())){ + toJsonValue(QString("NFMDemodReport"), nfm_demod_report, obj, QString("SWGNFMDemodReport")); + } + if((nfm_mod_report != nullptr) && (nfm_mod_report->isSet())){ + toJsonValue(QString("NFMModReport"), nfm_mod_report, obj, QString("SWGNFMModReport")); + } + + return obj; +} + +QString* +SWGChannelReport::getChannelType() { + return channel_type; +} +void +SWGChannelReport::setChannelType(QString* channel_type) { + this->channel_type = channel_type; + this->m_channel_type_isSet = true; +} + +qint32 +SWGChannelReport::getTx() { + return tx; +} +void +SWGChannelReport::setTx(qint32 tx) { + this->tx = tx; + this->m_tx_isSet = true; +} + +SWGNFMDemodReport* +SWGChannelReport::getNfmDemodReport() { + return nfm_demod_report; +} +void +SWGChannelReport::setNfmDemodReport(SWGNFMDemodReport* nfm_demod_report) { + this->nfm_demod_report = nfm_demod_report; + this->m_nfm_demod_report_isSet = true; +} + +SWGNFMModReport* +SWGChannelReport::getNfmModReport() { + return nfm_mod_report; +} +void +SWGChannelReport::setNfmModReport(SWGNFMModReport* nfm_mod_report) { + this->nfm_mod_report = nfm_mod_report; + this->m_nfm_mod_report_isSet = true; +} + + +bool +SWGChannelReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(channel_type != nullptr && *channel_type != QString("")){ isObjectUpdated = true; break;} + if(m_tx_isSet){ isObjectUpdated = true; break;} + if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} + if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h new file mode 100644 index 000000000..10ed20952 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -0,0 +1,78 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGChannelReport.h + * + * Base channel report. The specific channel report present depends on channelType. + */ + +#ifndef SWGChannelReport_H_ +#define SWGChannelReport_H_ + +#include + + +#include "SWGNFMDemodReport.h" +#include "SWGNFMModReport.h" +#include + +#include "SWGObject.h" + +namespace SWGSDRangel { + +class SWGChannelReport: public SWGObject { +public: + SWGChannelReport(); + SWGChannelReport(QString* json); + virtual ~SWGChannelReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGChannelReport* fromJson(QString &jsonString) override; + + QString* getChannelType(); + void setChannelType(QString* channel_type); + + qint32 getTx(); + void setTx(qint32 tx); + + SWGNFMDemodReport* getNfmDemodReport(); + void setNfmDemodReport(SWGNFMDemodReport* nfm_demod_report); + + SWGNFMModReport* getNfmModReport(); + void setNfmModReport(SWGNFMModReport* nfm_mod_report); + + + virtual bool isSet() override; + +private: + QString* channel_type; + bool m_channel_type_isSet; + + qint32 tx; + bool m_tx_isSet; + + SWGNFMDemodReport* nfm_demod_report; + bool m_nfm_demod_report_isSet; + + SWGNFMModReport* nfm_mod_report; + bool m_nfm_mod_report_isSet; + +}; + +} + +#endif /* SWGChannelReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index d1c9efec9..31d5b5c5e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -13,7 +13,7 @@ /* * SWGChannelSettings.h * - * Base channel settings + * Base channel settings. The specific channel settings present depends on channelType. */ #ifndef SWGChannelSettings_H_ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp index 3767e05cf..99afc3eb0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp @@ -141,6 +141,62 @@ SWGDeviceSetApi::devicesetChannelPostCallback(SWGHttpRequestWorker * worker) { } } +void +SWGDeviceSetApi::devicesetChannelReportGet(qint32 device_set_index, qint32 channel_index) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/report"); + + QString device_set_indexPathParam("{"); device_set_indexPathParam.append("deviceSetIndex").append("}"); + fullPath.replace(device_set_indexPathParam, stringValue(device_set_index)); + QString channel_indexPathParam("{"); channel_indexPathParam.append("channelIndex").append("}"); + fullPath.replace(channel_indexPathParam, stringValue(channel_index)); + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "GET"); + + + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDeviceSetApi::devicesetChannelReportGetCallback); + + worker->execute(&input); +} + +void +SWGDeviceSetApi::devicesetChannelReportGetCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGChannelReport* output = static_cast(create(json, QString("SWGChannelReport"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit devicesetChannelReportGetSignal(output); + } else { + emit devicesetChannelReportGetSignalE(output, error_type, error_str); + emit devicesetChannelReportGetSignalEFull(worker, error_type, error_str); + } +} + void SWGDeviceSetApi::devicesetChannelSettingsGet(qint32 device_set_index, qint32 channel_index) { QString fullPath; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h index c8fb702a3..9a552e786 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h @@ -15,6 +15,7 @@ #include "SWGHttpRequest.h" +#include "SWGChannelReport.h" #include "SWGChannelSettings.h" #include "SWGDeviceListItem.h" #include "SWGDeviceSet.h" @@ -41,6 +42,7 @@ public: void devicesetChannelDelete(qint32 device_set_index, qint32 channel_index); void devicesetChannelPost(qint32 device_set_index, SWGChannelSettings& body); + void devicesetChannelReportGet(qint32 device_set_index, qint32 channel_index); void devicesetChannelSettingsGet(qint32 device_set_index, qint32 channel_index); void devicesetChannelSettingsPatch(qint32 device_set_index, qint32 channel_index, SWGChannelSettings& body); void devicesetChannelSettingsPut(qint32 device_set_index, qint32 channel_index, SWGChannelSettings& body); @@ -59,6 +61,7 @@ public: private: void devicesetChannelDeleteCallback (SWGHttpRequestWorker * worker); void devicesetChannelPostCallback (SWGHttpRequestWorker * worker); + void devicesetChannelReportGetCallback (SWGHttpRequestWorker * worker); void devicesetChannelSettingsGetCallback (SWGHttpRequestWorker * worker); void devicesetChannelSettingsPatchCallback (SWGHttpRequestWorker * worker); void devicesetChannelSettingsPutCallback (SWGHttpRequestWorker * worker); @@ -77,6 +80,7 @@ private: signals: void devicesetChannelDeleteSignal(SWGChannelSettings* summary); void devicesetChannelPostSignal(SWGSuccessResponse* summary); + void devicesetChannelReportGetSignal(SWGChannelReport* summary); void devicesetChannelSettingsGetSignal(SWGChannelSettings* summary); void devicesetChannelSettingsPatchSignal(SWGChannelSettings* summary); void devicesetChannelSettingsPutSignal(SWGChannelSettings* summary); @@ -94,6 +98,7 @@ signals: void devicesetChannelDeleteSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelPostSignalE(SWGSuccessResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void devicesetChannelReportGetSignalE(SWGChannelReport* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsGetSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsPatchSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsPutSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); @@ -111,6 +116,7 @@ signals: void devicesetChannelDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelPostSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void devicesetChannelReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 1f926232b..460062ac6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -13,7 +13,7 @@ /* * SWGDeviceSettings.h * - * Base device settings + * Base device settings. The specific device settings present depends on deviceHwType. */ #ifndef SWGDeviceSettings_H_ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index e8cc0a960..4e59ed8b6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -20,6 +20,7 @@ #include "SWGCWKeyerSettings.h" #include "SWGChannel.h" #include "SWGChannelListItem.h" +#include "SWGChannelReport.h" #include "SWGChannelSettings.h" #include "SWGDVSeralDevices.h" #include "SWGDVSerialDevice.h" @@ -39,7 +40,9 @@ #include "SWGLimeSdrOutputSettings.h" #include "SWGLocationInformation.h" #include "SWGLoggingInfo.h" +#include "SWGNFMDemodReport.h" #include "SWGNFMDemodSettings.h" +#include "SWGNFMModReport.h" #include "SWGNFMModSettings.h" #include "SWGPresetExport.h" #include "SWGPresetGroup.h" @@ -73,6 +76,9 @@ namespace SWGSDRangel { if(QString("SWGChannelListItem").compare(type) == 0) { return new SWGChannelListItem(); } + if(QString("SWGChannelReport").compare(type) == 0) { + return new SWGChannelReport(); + } if(QString("SWGChannelSettings").compare(type) == 0) { return new SWGChannelSettings(); } @@ -130,9 +136,15 @@ namespace SWGSDRangel { if(QString("SWGLoggingInfo").compare(type) == 0) { return new SWGLoggingInfo(); } + if(QString("SWGNFMDemodReport").compare(type) == 0) { + return new SWGNFMDemodReport(); + } if(QString("SWGNFMDemodSettings").compare(type) == 0) { return new SWGNFMDemodSettings(); } + if(QString("SWGNFMModReport").compare(type) == 0) { + return new SWGNFMModReport(); + } if(QString("SWGNFMModSettings").compare(type) == 0) { return new SWGNFMModSettings(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp new file mode 100644 index 000000000..0e252580e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.cpp @@ -0,0 +1,148 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGNFMDemodReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGNFMDemodReport::SWGNFMDemodReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGNFMDemodReport::SWGNFMDemodReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + ctcss_tone = 0.0f; + m_ctcss_tone_isSet = false; + squelch = 0; + m_squelch_isSet = false; +} + +SWGNFMDemodReport::~SWGNFMDemodReport() { + this->cleanup(); +} + +void +SWGNFMDemodReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + ctcss_tone = 0.0f; + m_ctcss_tone_isSet = false; + squelch = 0; + m_squelch_isSet = false; +} + +void +SWGNFMDemodReport::cleanup() { + + + +} + +SWGNFMDemodReport* +SWGNFMDemodReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGNFMDemodReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&ctcss_tone, pJson["ctcssTone"], "float", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "qint32", ""); + +} + +QString +SWGNFMDemodReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGNFMDemodReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_ctcss_tone_isSet){ + obj->insert("ctcssTone", QJsonValue(ctcss_tone)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + + return obj; +} + +float +SWGNFMDemodReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGNFMDemodReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +float +SWGNFMDemodReport::getCtcssTone() { + return ctcss_tone; +} +void +SWGNFMDemodReport::setCtcssTone(float ctcss_tone) { + this->ctcss_tone = ctcss_tone; + this->m_ctcss_tone_isSet = true; +} + +qint32 +SWGNFMDemodReport::getSquelch() { + return squelch; +} +void +SWGNFMDemodReport::setSquelch(qint32 squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + + +bool +SWGNFMDemodReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_ctcss_tone_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h new file mode 100644 index 000000000..9b5fed600 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h @@ -0,0 +1,69 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGNFMDemodReport.h + * + * NFMDemod + */ + +#ifndef SWGNFMDemodReport_H_ +#define SWGNFMDemodReport_H_ + +#include + + + +#include "SWGObject.h" + +namespace SWGSDRangel { + +class SWGNFMDemodReport: public SWGObject { +public: + SWGNFMDemodReport(); + SWGNFMDemodReport(QString* json); + virtual ~SWGNFMDemodReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGNFMDemodReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + float getCtcssTone(); + void setCtcssTone(float ctcss_tone); + + qint32 getSquelch(); + void setSquelch(qint32 squelch); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + float ctcss_tone; + bool m_ctcss_tone_isSet; + + qint32 squelch; + bool m_squelch_isSet; + +}; + +} + +#endif /* SWGNFMDemodReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp new file mode 100644 index 000000000..c1a97558b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGNFMModReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGNFMModReport::SWGNFMModReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGNFMModReport::SWGNFMModReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; +} + +SWGNFMModReport::~SWGNFMModReport() { + this->cleanup(); +} + +void +SWGNFMModReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; +} + +void +SWGNFMModReport::cleanup() { + +} + +SWGNFMModReport* +SWGNFMModReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGNFMModReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + +} + +QString +SWGNFMModReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGNFMModReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + + return obj; +} + +float +SWGNFMModReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGNFMModReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + + +bool +SWGNFMModReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h new file mode 100644 index 000000000..fc68fcef3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h @@ -0,0 +1,57 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGNFMModReport.h + * + * NFMMod + */ + +#ifndef SWGNFMModReport_H_ +#define SWGNFMModReport_H_ + +#include + + + +#include "SWGObject.h" + +namespace SWGSDRangel { + +class SWGNFMModReport: public SWGObject { +public: + SWGNFMModReport(); + SWGNFMModReport(QString* json); + virtual ~SWGNFMModReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGNFMModReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + +}; + +} + +#endif /* SWGNFMModReport_H_ */ From 6fbbd14fd1d1e5050406b79b605316f1efb5d67e Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 19 Mar 2018 00:08:38 +0100 Subject: [PATCH 134/956] Web API: entry point to get all channel reports at once --- sdrbase/resources/webapi/doc/html2/index.html | 486 +++++++++++++++++- .../resources/webapi/doc/swagger/swagger.yaml | 49 ++ sdrbase/webapi/webapiadapterinterface.cpp | 1 + sdrbase/webapi/webapiadapterinterface.h | 16 + sdrbase/webapi/webapirequestmapper.cpp | 40 ++ sdrbase/webapi/webapirequestmapper.h | 1 + sdrgui/webapi/webapiadaptergui.cpp | 81 +++ sdrgui/webapi/webapiadaptergui.h | 6 + swagger/sdrangel/api/swagger/swagger.yaml | 54 +- swagger/sdrangel/code/html2/index.html | 486 +++++++++++++++++- .../sdrangel/code/qt5/client/SWGChannel.cpp | 23 + swagger/sdrangel/code/qt5/client/SWGChannel.h | 7 + .../code/qt5/client/SWGChannelsDetail.cpp | 133 +++++ .../code/qt5/client/SWGChannelsDetail.h | 65 +++ .../code/qt5/client/SWGDeviceSetApi.cpp | 54 ++ .../code/qt5/client/SWGDeviceSetApi.h | 6 + .../code/qt5/client/SWGModelFactory.h | 4 + 17 files changed, 1506 insertions(+), 6 deletions(-) create mode 100644 swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 8f924b78d..2a15ee373 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -807,6 +807,9 @@ margin-bottom: 20px; "deltaFrequency" : { "type" : "integer", "description" : "Channel shift frequency in Hz from the center of baseband" + }, + "report" : { + "$ref" : "#/definitions/ChannelReport" } }, "description" : "Channel summarized information" @@ -882,6 +885,23 @@ margin-bottom: 20px; } }, "description" : "Base channel settings. The specific channel settings present depends on channelType." +}; + defs.ChannelsDetail = { + "required" : [ "channelcount" ], + "properties" : { + "channelcount" : { + "type" : "integer", + "description" : "Number of channels in the set" + }, + "channels" : { + "type" : "array", + "description" : "Channels list", + "items" : { + "$ref" : "#/definitions/Channel" + } + } + }, + "description" : "All channels detailed information" }; defs.DVSeralDevices = { "required" : [ "nbDevices" ], @@ -1756,6 +1776,9 @@ margin-bottom: 20px;
  • devicesetChannelSettingsPut
  • +
  • + devicesetChannelsReportGet +
  • devicesetDevicePut
  • @@ -4971,6 +4994,467 @@ $(document).ready(function() {
    +
    +
    +
    +

    devicesetChannelsReportGet

    +

    +
    +
    +
    +

    +

    get channels report

    +

    +
    +
    /sdrangel/deviceset/{deviceSetIndex}/channels/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrangel/deviceset/{deviceSetIndex}/channels/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DeviceSetApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        try {
    +            ChannelsDetail result = apiInstance.devicesetChannelsReportGet(deviceSetIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetChannelsReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DeviceSetApi;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        try {
    +            ChannelsDetail result = apiInstance.devicesetChannelsReportGet(deviceSetIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetChannelsReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    Integer *deviceSetIndex = 56; // Index of device set in the device set list
    +
    +DeviceSetApi *apiInstance = [[DeviceSetApi alloc] init];
    +
    +[apiInstance devicesetChannelsReportGetWith:deviceSetIndex
    +              completionHandler: ^(ChannelsDetail output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DeviceSetApi()
    +
    +var deviceSetIndex = 56; // {Integer} Index of device set in the device set list
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.devicesetChannelsReportGet(deviceSetIndex, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class devicesetChannelsReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DeviceSetApi();
    +            var deviceSetIndex = 56;  // Integer | Index of device set in the device set list
    +
    +            try
    +            {
    +                ChannelsDetail result = apiInstance.devicesetChannelsReportGet(deviceSetIndex);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DeviceSetApi.devicesetChannelsReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DeviceSetApi();
    +$deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +
    +try {
    +    $result = $api_instance->devicesetChannelsReportGet($deviceSetIndex);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DeviceSetApi->devicesetChannelsReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DeviceSetApi;
    +
    +my $api_instance = SWGSDRangel::DeviceSetApi->new();
    +my $deviceSetIndex = 56; # Integer | Index of device set in the device set list
    +
    +eval { 
    +    my $result = $api_instance->devicesetChannelsReportGet(deviceSetIndex => $deviceSetIndex);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DeviceSetApi->devicesetChannelsReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DeviceSetApi()
    +deviceSetIndex = 56 # Integer | Index of device set in the device set list
    +
    +try: 
    +    api_response = api_instance.deviceset_channels_report_get(deviceSetIndex)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DeviceSetApi->devicesetChannelsReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + +
    Path parameters
    + + + + + + + + + +
    NameDescription
    deviceSetIndex* + + +
    +
    +
    + + Integer + + +
    + Index of device set in the device set list +
    +
    +
    + Required +
    +
    +
    +
    + + + + + +

    Responses

    +

    Status: 200 - On success return channels report information

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set index

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -17472,7 +17956,7 @@ except ApiException as e:
    - Generated 2018-03-18T11:03:22.829+01:00 + Generated 2018-03-18T22:42:33.187+01:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 64af08125..cf43b57cd 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -810,6 +810,38 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + + /sdrangel/deviceset/{deviceSetIndex}/channels/report: + x-swagger-router-controller: deviceset + get: + description: get channels report + operationId: devicesetChannelsReportGet + tags: + - DeviceSet + parameters: + - in: path + name: deviceSetIndex + type: integer + required: true + description: Index of device set in the device set list + responses: + "200": + description: On success return channels report information + schema: + $ref: "#/definitions/ChannelsDetail" + "400": + description: Invalid device set index + schema: + $ref: "#/definitions/ErrorResponse" + "404": + description: Device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + /sdrangel/deviceset/{deviceSetIndex}/channel: x-swagger-router-controller: deviceset @@ -1302,6 +1334,23 @@ definitions: deltaFrequency: description: "Channel shift frequency in Hz from the center of baseband" type: integer + report: + $ref: "#/definitions/ChannelReport" + + ChannelsDetail: + description: "All channels detailed information" + required: + - channelcount + properties: + channelcount: + description: "Number of channels in the set" + type: integer + channels: + description: "Channels list" + type: array + items: + $ref: "#/definitions/Channel" + AudioDevices: description: "List of audio devices available in the system" diff --git a/sdrbase/webapi/webapiadapterinterface.cpp b/sdrbase/webapi/webapiadapterinterface.cpp index 9e2b2af3c..169cc44c5 100644 --- a/sdrbase/webapi/webapiadapterinterface.cpp +++ b/sdrbase/webapi/webapiadapterinterface.cpp @@ -36,6 +36,7 @@ std::regex WebAPIAdapterInterface::devicesetFocusURLRe("^/sdrangel/deviceset/([0 std::regex WebAPIAdapterInterface::devicesetDeviceURLRe("^/sdrangel/deviceset/([0-9]{1,2})/device$"); std::regex WebAPIAdapterInterface::devicesetDeviceSettingsURLRe("^/sdrangel/deviceset/([0-9]{1,2})/device/settings$"); std::regex WebAPIAdapterInterface::devicesetDeviceRunURLRe("^/sdrangel/deviceset/([0-9]{1,2})/device/run"); +std::regex WebAPIAdapterInterface::devicesetChannelsReportURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channels/report$"); std::regex WebAPIAdapterInterface::devicesetChannelURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel$"); std::regex WebAPIAdapterInterface::devicesetChannelIndexURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel/([0-9]{1,2})$"); std::regex WebAPIAdapterInterface::devicesetChannelSettingsURLRe("^/sdrangel/deviceset/([0-9]{1,2})/channel/([0-9]{1,2})/settings$"); diff --git a/sdrbase/webapi/webapiadapterinterface.h b/sdrbase/webapi/webapiadapterinterface.h index ca16f738e..a7dd4a5dc 100644 --- a/sdrbase/webapi/webapiadapterinterface.h +++ b/sdrbase/webapi/webapiadapterinterface.h @@ -46,6 +46,7 @@ namespace SWGSDRangel class SWGDeviceListItem; class SWGDeviceSettings; class SWGDeviceState; + class SWGChannelsDetail; class SWGChannelSettings; class SWGChannelReport; class SWGSuccessResponse; @@ -454,6 +455,20 @@ public: return 501; } + /** + * Handler of /sdrangel/deviceset/{devicesetIndex}/channels/report (GET) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * returns the Http status code (default 501: not implemented) + */ + virtual int devicesetChannelsReportGet( + int deviceSetIndex __attribute__((unused)), + SWGSDRangel::SWGChannelsDetail& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) + { + error.init(); + *error.getMessage() = QString("Function not implemented"); + return 501; + } + /** * Handler of /sdrangel/deviceset/{deviceSetIndex}/channel (POST) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels * returns the Http status code (default 501: not implemented) @@ -553,6 +568,7 @@ public: static std::regex devicesetChannelIndexURLRe; static std::regex devicesetChannelSettingsURLRe; static std::regex devicesetChannelReportURLRe; + static std::regex devicesetChannelsReportURLRe; }; diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index ad43613b8..dfe7c8a3e 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -38,6 +38,7 @@ #include "SWGPresetExport.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGChannelsDetail.h" #include "SWGChannelSettings.h" #include "SWGChannelReport.h" #include "SWGSuccessResponse.h" @@ -113,6 +114,8 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http devicesetDeviceSettingsService(std::string(desc_match[1]), request, response); } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetDeviceRunURLRe)) { devicesetDeviceRunService(std::string(desc_match[1]), request, response); + } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetChannelsReportURLRe)) { + devicesetChannelsReportService(std::string(desc_match[1]), request, response); } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetChannelURLRe)) { devicesetChannelService(std::string(desc_match[1]), request, response); } else if (std::regex_match(pathStr, desc_match, WebAPIAdapterInterface::devicesetChannelIndexURLRe)) { @@ -1065,6 +1068,43 @@ void WebAPIRequestMapper::devicesetDeviceRunService(const std::string& indexStr, } } +void WebAPIRequestMapper::devicesetChannelsReportService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +{ + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + + if (request.getMethod() == "GET") + { + try + { + SWGSDRangel::SWGChannelsDetail normalResponse; + int deviceSetIndex = boost::lexical_cast(indexStr); + int status = m_adapter->devicesetChannelsReportGet(deviceSetIndex, normalResponse, errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + catch (const boost::bad_lexical_cast &e) + { + errorResponse.init(); + *errorResponse.getMessage() = "Wrong integer conversion on device set index"; + response.setStatus(400,"Invalid data"); + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(405,"Invalid HTTP method"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid HTTP method"; + response.write(errorResponse.asJson().toUtf8()); + } +} + void WebAPIRequestMapper::devicesetChannelService( const std::string& deviceSetIndexStr, qtwebapp::HttpRequest& request, diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index 3c5aa8d07..f4730362c 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -65,6 +65,7 @@ private: void devicesetDeviceService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetDeviceSettingsService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetDeviceRunService(const std::string& indexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void devicesetChannelsReportService(const std::string& deviceSetIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetChannelService(const std::string& deviceSetIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetChannelIndexService(const std::string& deviceSetIndexStr, const std::string& channelIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void devicesetChannelSettingsService(const std::string& deviceSetIndexStr, const std::string& channelIndexStr, qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); diff --git a/sdrgui/webapi/webapiadaptergui.cpp b/sdrgui/webapi/webapiadaptergui.cpp index fa9144c95..94db43e24 100644 --- a/sdrgui/webapi/webapiadaptergui.cpp +++ b/sdrgui/webapi/webapiadaptergui.cpp @@ -53,6 +53,7 @@ #include "SWGPresetIdentifier.h" #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGChannelsDetail.h" #include "SWGChannelSettings.h" #include "SWGChannelReport.h" #include "SWGSuccessResponse.h" @@ -983,6 +984,27 @@ int WebAPIAdapterGUI::devicesetDeviceRunDelete( } } +int WebAPIAdapterGUI::devicesetChannelsReportGet( + int deviceSetIndex, + SWGSDRangel::SWGChannelsDetail& response, + SWGSDRangel::SWGErrorResponse& error) +{ + if ((deviceSetIndex >= 0) && (deviceSetIndex < (int) m_mainWindow.m_deviceUIs.size())) + { + const DeviceUISet *deviceSet = m_mainWindow.m_deviceUIs[deviceSetIndex]; + getChannelsDetail(&response, deviceSet); + + return 200; + } + else + { + error.init(); + *error.getMessage() = QString("There is no device set with index %1").arg(deviceSetIndex); + + return 404; + } +} + int WebAPIAdapterGUI::devicesetChannelPost( int deviceSetIndex, SWGSDRangel::SWGChannelSettings& query, @@ -1435,6 +1457,65 @@ void WebAPIAdapterGUI::getDeviceSet(SWGSDRangel::SWGDeviceSet *deviceSet, const } } +void WebAPIAdapterGUI::getChannelsDetail(SWGSDRangel::SWGChannelsDetail *channelsDetail, const DeviceUISet* deviceUISet) +{ + channelsDetail->init(); + SWGSDRangel::SWGChannelReport *channelReport; + QString channelReportError; + + if (deviceUISet->m_deviceSinkEngine) // Tx data + { + channelsDetail->setChannelcount(deviceUISet->m_deviceSinkAPI->getNbChannels()); + QList *channels = channelsDetail->getChannels(); + + for (int i = 0; i < channelsDetail->getChannelcount(); i++) + { + channels->append(new SWGSDRangel::SWGChannel); + channels->back()->init(); + ChannelSourceAPI *channel = deviceUISet->m_deviceSinkAPI->getChanelAPIAt(i); + channels->back()->setDeltaFrequency(channel->getCenterFrequency()); + channels->back()->setIndex(channel->getIndexInDeviceSet()); + channels->back()->setUid(channel->getUID()); + channel->getIdentifier(*channels->back()->getId()); + channel->getTitle(*channels->back()->getTitle()); + + channelReport = new SWGSDRangel::SWGChannelReport(); + + if (channel->webapiReportGet(*channelReport, channelReportError) != 501) { + channels->back()->setReport(channelReport); + } else { + delete channelReport; + } + } + } + + if (deviceUISet->m_deviceSourceEngine) // Rx data + { + channelsDetail->setChannelcount(deviceUISet->m_deviceSourceAPI->getNbChannels()); + QList *channels = channelsDetail->getChannels(); + + for (int i = 0; i < channelsDetail->getChannelcount(); i++) + { + channels->append(new SWGSDRangel::SWGChannel); + channels->back()->init(); + ChannelSinkAPI *channel = deviceUISet->m_deviceSourceAPI->getChanelAPIAt(i); + channels->back()->setDeltaFrequency(channel->getCenterFrequency()); + channels->back()->setIndex(channel->getIndexInDeviceSet()); + channels->back()->setUid(channel->getUID()); + channel->getIdentifier(*channels->back()->getId()); + channel->getTitle(*channels->back()->getTitle()); + + channelReport = new SWGSDRangel::SWGChannelReport(); + + if (channel->webapiReportGet(*channelReport, channelReportError) != 501) { + channels->back()->setReport(channelReport); + } else { + delete channelReport; + } + } + } +} + QtMsgType WebAPIAdapterGUI::getMsgTypeFromString(const QString& msgTypeString) { if (msgTypeString == "debug") { diff --git a/sdrgui/webapi/webapiadaptergui.h b/sdrgui/webapi/webapiadaptergui.h index 37be03994..7edc56a98 100644 --- a/sdrgui/webapi/webapiadaptergui.h +++ b/sdrgui/webapi/webapiadaptergui.h @@ -159,6 +159,11 @@ public: SWGSDRangel::SWGDeviceState& response, SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetChannelsReportGet( + int deviceSetIndex, + SWGSDRangel::SWGChannelsDetail& response, + SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetChannelPost( int deviceSetIndex, SWGSDRangel::SWGChannelSettings& query, @@ -196,6 +201,7 @@ private: void getDeviceSetList(SWGSDRangel::SWGDeviceSetList* deviceSetList); void getDeviceSet(SWGSDRangel::SWGDeviceSet *deviceSet, const DeviceUISet* deviceUISet, int deviceUISetIndex); + void getChannelsDetail(SWGSDRangel::SWGChannelsDetail *channelsDetail, const DeviceUISet* deviceUISet); static QtMsgType getMsgTypeFromString(const QString& msgTypeString); static void getMsgTypeString(const QtMsgType& msgType, QString& level); }; diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index e1ae42dc5..a08f7e555 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -810,6 +810,38 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + + /sdrangel/deviceset/{deviceSetIndex}/channels/report: + x-swagger-router-controller: deviceset + get: + description: get channels report + operationId: devicesetChannelsReportGet + tags: + - DeviceSet + parameters: + - in: path + name: deviceSetIndex + type: integer + required: true + description: Index of device set in the device set list + responses: + "200": + description: On success return channels report information + schema: + $ref: "#/definitions/ChannelsDetail" + "400": + description: Invalid device set index + schema: + $ref: "#/definitions/ErrorResponse" + "404": + description: Device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + /sdrangel/deviceset/{deviceSetIndex}/channel: x-swagger-router-controller: deviceset @@ -1302,6 +1334,23 @@ definitions: deltaFrequency: description: "Channel shift frequency in Hz from the center of baseband" type: integer + report: + $ref: "#/definitions/ChannelReport" + + ChannelsDetail: + description: "All channels detailed information" + required: + - channelcount + properties: + channelcount: + description: "Number of channels in the set" + type: integer + channels: + description: "Channels list" + type: array + items: + $ref: "#/definitions/Channel" + AudioDevices: description: "List of audio devices available in the system" @@ -1551,11 +1600,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" ChannelReport: - description: Base channel report. The specific channel report present depends on channelType. + description: Base channel report. The specific channel report present depends on channelType or paremt context. discriminator: channelType - required: - - channelType - - tx properties: channelType: description: Channel type code diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 8f924b78d..2a15ee373 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -807,6 +807,9 @@ margin-bottom: 20px; "deltaFrequency" : { "type" : "integer", "description" : "Channel shift frequency in Hz from the center of baseband" + }, + "report" : { + "$ref" : "#/definitions/ChannelReport" } }, "description" : "Channel summarized information" @@ -882,6 +885,23 @@ margin-bottom: 20px; } }, "description" : "Base channel settings. The specific channel settings present depends on channelType." +}; + defs.ChannelsDetail = { + "required" : [ "channelcount" ], + "properties" : { + "channelcount" : { + "type" : "integer", + "description" : "Number of channels in the set" + }, + "channels" : { + "type" : "array", + "description" : "Channels list", + "items" : { + "$ref" : "#/definitions/Channel" + } + } + }, + "description" : "All channels detailed information" }; defs.DVSeralDevices = { "required" : [ "nbDevices" ], @@ -1756,6 +1776,9 @@ margin-bottom: 20px;
  • devicesetChannelSettingsPut
  • +
  • + devicesetChannelsReportGet +
  • devicesetDevicePut
  • @@ -4971,6 +4994,467 @@ $(document).ready(function() {
    +
    +
    +
    +

    devicesetChannelsReportGet

    +

    +
    +
    +
    +

    +

    get channels report

    +

    +
    +
    /sdrangel/deviceset/{deviceSetIndex}/channels/report
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET "http://localhost/sdrangel/deviceset/{deviceSetIndex}/channels/report"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.DeviceSetApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        try {
    +            ChannelsDetail result = apiInstance.devicesetChannelsReportGet(deviceSetIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetChannelsReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.DeviceSetApi;
    +
    +public class DeviceSetApiExample {
    +
    +    public static void main(String[] args) {
    +        DeviceSetApi apiInstance = new DeviceSetApi();
    +        Integer deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +        try {
    +            ChannelsDetail result = apiInstance.devicesetChannelsReportGet(deviceSetIndex);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DeviceSetApi#devicesetChannelsReportGet");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    Integer *deviceSetIndex = 56; // Index of device set in the device set list
    +
    +DeviceSetApi *apiInstance = [[DeviceSetApi alloc] init];
    +
    +[apiInstance devicesetChannelsReportGetWith:deviceSetIndex
    +              completionHandler: ^(ChannelsDetail output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.DeviceSetApi()
    +
    +var deviceSetIndex = 56; // {Integer} Index of device set in the device set list
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.devicesetChannelsReportGet(deviceSetIndex, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class devicesetChannelsReportGetExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new DeviceSetApi();
    +            var deviceSetIndex = 56;  // Integer | Index of device set in the device set list
    +
    +            try
    +            {
    +                ChannelsDetail result = apiInstance.devicesetChannelsReportGet(deviceSetIndex);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling DeviceSetApi.devicesetChannelsReportGet: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\DeviceSetApi();
    +$deviceSetIndex = 56; // Integer | Index of device set in the device set list
    +
    +try {
    +    $result = $api_instance->devicesetChannelsReportGet($deviceSetIndex);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DeviceSetApi->devicesetChannelsReportGet: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::DeviceSetApi;
    +
    +my $api_instance = SWGSDRangel::DeviceSetApi->new();
    +my $deviceSetIndex = 56; # Integer | Index of device set in the device set list
    +
    +eval { 
    +    my $result = $api_instance->devicesetChannelsReportGet(deviceSetIndex => $deviceSetIndex);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DeviceSetApi->devicesetChannelsReportGet: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.DeviceSetApi()
    +deviceSetIndex = 56 # Integer | Index of device set in the device set list
    +
    +try: 
    +    api_response = api_instance.deviceset_channels_report_get(deviceSetIndex)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DeviceSetApi->devicesetChannelsReportGet: %s\n" % e)
    +
    +
    + +

    Parameters

    + +
    Path parameters
    + + + + + + + + + +
    NameDescription
    deviceSetIndex* + + +
    +
    +
    + + Integer + + +
    + Index of device set in the device set list +
    +
    +
    + Required +
    +
    +
    +
    + + + + + +

    Responses

    +

    Status: 200 - On success return channels report information

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 400 - Invalid device set index

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    @@ -17472,7 +17956,7 @@ except ApiException as e:
    - Generated 2018-03-18T11:03:22.829+01:00 + Generated 2018-03-18T22:42:33.187+01:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannel.cpp b/swagger/sdrangel/code/qt5/client/SWGChannel.cpp index 85280b5c9..f8b666dcc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannel.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannel.cpp @@ -38,6 +38,8 @@ SWGChannel::SWGChannel() { m_title_isSet = false; delta_frequency = 0; m_delta_frequency_isSet = false; + report = nullptr; + m_report_isSet = false; } SWGChannel::~SWGChannel() { @@ -56,6 +58,8 @@ SWGChannel::init() { m_title_isSet = false; delta_frequency = 0; m_delta_frequency_isSet = false; + report = new SWGChannelReport(); + m_report_isSet = false; } void @@ -69,6 +73,9 @@ SWGChannel::cleanup() { delete title; } + if(report != nullptr) { + delete report; + } } SWGChannel* @@ -92,6 +99,8 @@ SWGChannel::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&delta_frequency, pJson["deltaFrequency"], "qint32", ""); + ::SWGSDRangel::setValue(&report, pJson["report"], "SWGChannelReport", "SWGChannelReport"); + } QString @@ -123,6 +132,9 @@ SWGChannel::asJsonObject() { if(m_delta_frequency_isSet){ obj->insert("deltaFrequency", QJsonValue(delta_frequency)); } + if((report != nullptr) && (report->isSet())){ + toJsonValue(QString("report"), report, obj, QString("SWGChannelReport")); + } return obj; } @@ -177,6 +189,16 @@ SWGChannel::setDeltaFrequency(qint32 delta_frequency) { this->m_delta_frequency_isSet = true; } +SWGChannelReport* +SWGChannel::getReport() { + return report; +} +void +SWGChannel::setReport(SWGChannelReport* report) { + this->report = report; + this->m_report_isSet = true; +} + bool SWGChannel::isSet(){ @@ -187,6 +209,7 @@ SWGChannel::isSet(){ if(m_uid_isSet){ isObjectUpdated = true; break;} if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} if(m_delta_frequency_isSet){ isObjectUpdated = true; break;} + if(report != nullptr && report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannel.h b/swagger/sdrangel/code/qt5/client/SWGChannel.h index 315b4b958..4dee7c18b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannel.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannel.h @@ -22,6 +22,7 @@ #include +#include "SWGChannelReport.h" #include #include "SWGObject.h" @@ -56,6 +57,9 @@ public: qint32 getDeltaFrequency(); void setDeltaFrequency(qint32 delta_frequency); + SWGChannelReport* getReport(); + void setReport(SWGChannelReport* report); + virtual bool isSet() override; @@ -75,6 +79,9 @@ private: qint32 delta_frequency; bool m_delta_frequency_isSet; + SWGChannelReport* report; + bool m_report_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp new file mode 100644 index 000000000..efdf56a16 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.cpp @@ -0,0 +1,133 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGChannelsDetail.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGChannelsDetail::SWGChannelsDetail(QString* json) { + init(); + this->fromJson(*json); +} + +SWGChannelsDetail::SWGChannelsDetail() { + channelcount = 0; + m_channelcount_isSet = false; + channels = nullptr; + m_channels_isSet = false; +} + +SWGChannelsDetail::~SWGChannelsDetail() { + this->cleanup(); +} + +void +SWGChannelsDetail::init() { + channelcount = 0; + m_channelcount_isSet = false; + channels = new QList(); + m_channels_isSet = false; +} + +void +SWGChannelsDetail::cleanup() { + + if(channels != nullptr) { + auto arr = channels; + for(auto o: *arr) { + delete o; + } + delete channels; + } +} + +SWGChannelsDetail* +SWGChannelsDetail::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGChannelsDetail::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channelcount, pJson["channelcount"], "qint32", ""); + + + ::SWGSDRangel::setValue(&channels, pJson["channels"], "QList", "SWGChannel"); +} + +QString +SWGChannelsDetail::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGChannelsDetail::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channelcount_isSet){ + obj->insert("channelcount", QJsonValue(channelcount)); + } + if(channels->size() > 0){ + toJsonArray((QList*)channels, obj, "channels", "SWGChannel"); + } + + return obj; +} + +qint32 +SWGChannelsDetail::getChannelcount() { + return channelcount; +} +void +SWGChannelsDetail::setChannelcount(qint32 channelcount) { + this->channelcount = channelcount; + this->m_channelcount_isSet = true; +} + +QList* +SWGChannelsDetail::getChannels() { + return channels; +} +void +SWGChannelsDetail::setChannels(QList* channels) { + this->channels = channels; + this->m_channels_isSet = true; +} + + +bool +SWGChannelsDetail::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channelcount_isSet){ isObjectUpdated = true; break;} + if(channels->size() > 0){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h new file mode 100644 index 000000000..6e7c03103 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h @@ -0,0 +1,65 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGChannelsDetail.h + * + * All channels detailed information + */ + +#ifndef SWGChannelsDetail_H_ +#define SWGChannelsDetail_H_ + +#include + + +#include "SWGChannel.h" +#include + +#include "SWGObject.h" + +namespace SWGSDRangel { + +class SWGChannelsDetail: public SWGObject { +public: + SWGChannelsDetail(); + SWGChannelsDetail(QString* json); + virtual ~SWGChannelsDetail(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGChannelsDetail* fromJson(QString &jsonString) override; + + qint32 getChannelcount(); + void setChannelcount(qint32 channelcount); + + QList* getChannels(); + void setChannels(QList* channels); + + + virtual bool isSet() override; + +private: + qint32 channelcount; + bool m_channelcount_isSet; + + QList* channels; + bool m_channels_isSet; + +}; + +} + +#endif /* SWGChannelsDetail_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp index 99afc3eb0..bb41cf948 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.cpp @@ -371,6 +371,60 @@ SWGDeviceSetApi::devicesetChannelSettingsPutCallback(SWGHttpRequestWorker * work } } +void +SWGDeviceSetApi::devicesetChannelsReportGet(qint32 device_set_index) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrangel/deviceset/{deviceSetIndex}/channels/report"); + + QString device_set_indexPathParam("{"); device_set_indexPathParam.append("deviceSetIndex").append("}"); + fullPath.replace(device_set_indexPathParam, stringValue(device_set_index)); + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "GET"); + + + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGDeviceSetApi::devicesetChannelsReportGetCallback); + + worker->execute(&input); +} + +void +SWGDeviceSetApi::devicesetChannelsReportGetCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGChannelsDetail* output = static_cast(create(json, QString("SWGChannelsDetail"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit devicesetChannelsReportGetSignal(output); + } else { + emit devicesetChannelsReportGetSignalE(output, error_type, error_str); + emit devicesetChannelsReportGetSignalEFull(worker, error_type, error_str); + } +} + void SWGDeviceSetApi::devicesetDevicePut(qint32 device_set_index, SWGDeviceListItem& body) { QString fullPath; diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h index 9a552e786..678114871 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetApi.h @@ -17,6 +17,7 @@ #include "SWGChannelReport.h" #include "SWGChannelSettings.h" +#include "SWGChannelsDetail.h" #include "SWGDeviceListItem.h" #include "SWGDeviceSet.h" #include "SWGDeviceSettings.h" @@ -46,6 +47,7 @@ public: void devicesetChannelSettingsGet(qint32 device_set_index, qint32 channel_index); void devicesetChannelSettingsPatch(qint32 device_set_index, qint32 channel_index, SWGChannelSettings& body); void devicesetChannelSettingsPut(qint32 device_set_index, qint32 channel_index, SWGChannelSettings& body); + void devicesetChannelsReportGet(qint32 device_set_index); void devicesetDevicePut(qint32 device_set_index, SWGDeviceListItem& body); void devicesetDeviceRunDelete(qint32 device_set_index); void devicesetDeviceRunGet(qint32 device_set_index); @@ -65,6 +67,7 @@ private: void devicesetChannelSettingsGetCallback (SWGHttpRequestWorker * worker); void devicesetChannelSettingsPatchCallback (SWGHttpRequestWorker * worker); void devicesetChannelSettingsPutCallback (SWGHttpRequestWorker * worker); + void devicesetChannelsReportGetCallback (SWGHttpRequestWorker * worker); void devicesetDevicePutCallback (SWGHttpRequestWorker * worker); void devicesetDeviceRunDeleteCallback (SWGHttpRequestWorker * worker); void devicesetDeviceRunGetCallback (SWGHttpRequestWorker * worker); @@ -84,6 +87,7 @@ signals: void devicesetChannelSettingsGetSignal(SWGChannelSettings* summary); void devicesetChannelSettingsPatchSignal(SWGChannelSettings* summary); void devicesetChannelSettingsPutSignal(SWGChannelSettings* summary); + void devicesetChannelsReportGetSignal(SWGChannelsDetail* summary); void devicesetDevicePutSignal(SWGDeviceListItem* summary); void devicesetDeviceRunDeleteSignal(SWGDeviceState* summary); void devicesetDeviceRunGetSignal(SWGDeviceState* summary); @@ -102,6 +106,7 @@ signals: void devicesetChannelSettingsGetSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsPatchSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsPutSignalE(SWGChannelSettings* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void devicesetChannelsReportGetSignalE(SWGChannelsDetail* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDevicePutSignalE(SWGDeviceListItem* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunDeleteSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunGetSignalE(SWGDeviceState* summary, QNetworkReply::NetworkError error_type, QString& error_str); @@ -120,6 +125,7 @@ signals: void devicesetChannelSettingsGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetChannelSettingsPutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void devicesetChannelsReportGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDevicePutSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void devicesetDeviceRunGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 4e59ed8b6..b14fd122f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -22,6 +22,7 @@ #include "SWGChannelListItem.h" #include "SWGChannelReport.h" #include "SWGChannelSettings.h" +#include "SWGChannelsDetail.h" #include "SWGDVSeralDevices.h" #include "SWGDVSerialDevice.h" #include "SWGDeviceListItem.h" @@ -82,6 +83,9 @@ namespace SWGSDRangel { if(QString("SWGChannelSettings").compare(type) == 0) { return new SWGChannelSettings(); } + if(QString("SWGChannelsDetail").compare(type) == 0) { + return new SWGChannelsDetail(); + } if(QString("SWGDVSeralDevices").compare(type) == 0) { return new SWGDVSeralDevices(); } From 5cb64c3daa35cfce13f92e1e0f941050395c8572 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 19 Mar 2018 00:20:54 +0100 Subject: [PATCH 135/956] Web API: implemented channel reporting entry points in server instance --- sdrbase/resources/webapi/doc/html2/index.html | 5 +- .../resources/webapi/doc/swagger/swagger.yaml | 5 +- sdrsrv/webapi/webapiadaptersrv.cpp | 141 ++++++++++++++++++ sdrsrv/webapi/webapiadaptersrv.h | 12 ++ swagger/sdrangel/code/html2/index.html | 5 +- .../code/qt5/client/SWGChannelReport.h | 2 +- 6 files changed, 159 insertions(+), 11 deletions(-) diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 2a15ee373..b1a78b888 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -845,7 +845,6 @@ margin-bottom: 20px; "description" : "Summarized information about channel plugin" }; defs.ChannelReport = { - "required" : [ "channelType", "tx" ], "discriminator" : "channelType", "properties" : { "channelType" : { @@ -863,7 +862,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/NFMModReport" } }, - "description" : "Base channel report. The specific channel report present depends on channelType." + "description" : "Base channel report. The specific channel report present depends on channelType or paremt context." }; defs.ChannelSettings = { "required" : [ "channelType", "tx" ], @@ -17956,7 +17955,7 @@ except ApiException as e:
    - Generated 2018-03-18T22:42:33.187+01:00 + Generated 2018-03-19T00:19:38.769+01:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index cf43b57cd..acd3b14d3 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1600,11 +1600,8 @@ definitions: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" ChannelReport: - description: Base channel report. The specific channel report present depends on channelType. + description: Base channel report. The specific channel report present depends on channelType or paremt context. discriminator: channelType - required: - - channelType - - tx properties: channelType: description: Channel type code diff --git a/sdrsrv/webapi/webapiadaptersrv.cpp b/sdrsrv/webapi/webapiadaptersrv.cpp index 7b46d88e4..48c99b65c 100644 --- a/sdrsrv/webapi/webapiadaptersrv.cpp +++ b/sdrsrv/webapi/webapiadaptersrv.cpp @@ -36,7 +36,9 @@ #include "SWGPresets.h" #include "SWGPresetTransfer.h" #include "SWGDeviceSettings.h" +#include "SWGChannelsDetail.h" #include "SWGChannelSettings.h" +#include "SWGChannelReport.h" #include "SWGSuccessResponse.h" #include "SWGErrorResponse.h" #include "SWGDeviceState.h" @@ -1087,6 +1089,27 @@ int WebAPIAdapterSrv::devicesetDeviceRunDelete( } } +int WebAPIAdapterSrv::devicesetChannelsReportGet( + int deviceSetIndex, + SWGSDRangel::SWGChannelsDetail& response, + SWGSDRangel::SWGErrorResponse& error) +{ + if ((deviceSetIndex >= 0) && (deviceSetIndex < (int) m_mainCore.m_deviceSets.size())) + { + const DeviceSet *deviceSet = m_mainCore.m_deviceSets[deviceSetIndex]; + getChannelsDetail(&response, deviceSet); + + return 200; + } + else + { + error.init(); + *error.getMessage() = QString("There is no device set with index %1").arg(deviceSetIndex); + + return 404; + } +} + int WebAPIAdapterSrv::devicesetChannelPost( int deviceSetIndex, SWGSDRangel::SWGChannelSettings& query, @@ -1304,6 +1327,65 @@ int WebAPIAdapterSrv::devicesetChannelSettingsGet( } } +int WebAPIAdapterSrv::devicesetChannelReportGet( + int deviceSetIndex, + int channelIndex, + SWGSDRangel::SWGChannelReport& response, + SWGSDRangel::SWGErrorResponse& error) +{ + error.init(); + + if ((deviceSetIndex >= 0) && (deviceSetIndex < (int) m_mainCore.m_deviceSets.size())) + { + DeviceSet *deviceSet = m_mainCore.m_deviceSets[deviceSetIndex]; + + if (deviceSet->m_deviceSourceEngine) // Rx + { + ChannelSinkAPI *channelAPI = deviceSet->m_deviceSourceAPI->getChanelAPIAt(channelIndex); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel with index %1").arg(channelIndex); + return 404; + } + else + { + response.setChannelType(new QString()); + channelAPI->getIdentifier(*response.getChannelType()); + response.setTx(0); + return channelAPI->webapiReportGet(response, *error.getMessage()); + } + } + else if (deviceSet->m_deviceSinkEngine) // Tx + { + ChannelSourceAPI *channelAPI = deviceSet->m_deviceSinkAPI->getChanelAPIAt(channelIndex); + + if (channelAPI == 0) + { + *error.getMessage() = QString("There is no channel with index %1").arg(channelIndex); + return 404; + } + else + { + response.setChannelType(new QString()); + channelAPI->getIdentifier(*response.getChannelType()); + response.setTx(1); + return channelAPI->webapiReportGet(response, *error.getMessage()); + } + } + else + { + *error.getMessage() = QString("DeviceSet error"); + return 500; + } + } + else + { + *error.getMessage() = QString("There is no device set with index %1").arg(deviceSetIndex); + return 404; + } +} + int WebAPIAdapterSrv::devicesetChannelSettingsPutPatch( int deviceSetIndex, int channelIndex, @@ -1474,6 +1556,65 @@ void WebAPIAdapterSrv::getDeviceSet(SWGSDRangel::SWGDeviceSet *swgDeviceSet, con } } +void WebAPIAdapterSrv::getChannelsDetail(SWGSDRangel::SWGChannelsDetail *channelsDetail, const DeviceSet* deviceSet) +{ + channelsDetail->init(); + SWGSDRangel::SWGChannelReport *channelReport; + QString channelReportError; + + if (deviceSet->m_deviceSinkEngine) // Tx data + { + channelsDetail->setChannelcount(deviceSet->m_deviceSinkAPI->getNbChannels()); + QList *channels = channelsDetail->getChannels(); + + for (int i = 0; i < channelsDetail->getChannelcount(); i++) + { + channels->append(new SWGSDRangel::SWGChannel); + channels->back()->init(); + ChannelSourceAPI *channel = deviceSet->m_deviceSinkAPI->getChanelAPIAt(i); + channels->back()->setDeltaFrequency(channel->getCenterFrequency()); + channels->back()->setIndex(channel->getIndexInDeviceSet()); + channels->back()->setUid(channel->getUID()); + channel->getIdentifier(*channels->back()->getId()); + channel->getTitle(*channels->back()->getTitle()); + + channelReport = new SWGSDRangel::SWGChannelReport(); + + if (channel->webapiReportGet(*channelReport, channelReportError) != 501) { + channels->back()->setReport(channelReport); + } else { + delete channelReport; + } + } + } + + if (deviceSet->m_deviceSourceEngine) // Rx data + { + channelsDetail->setChannelcount(deviceSet->m_deviceSourceAPI->getNbChannels()); + QList *channels = channelsDetail->getChannels(); + + for (int i = 0; i < channelsDetail->getChannelcount(); i++) + { + channels->append(new SWGSDRangel::SWGChannel); + channels->back()->init(); + ChannelSinkAPI *channel = deviceSet->m_deviceSourceAPI->getChanelAPIAt(i); + channels->back()->setDeltaFrequency(channel->getCenterFrequency()); + channels->back()->setIndex(channel->getIndexInDeviceSet()); + channels->back()->setUid(channel->getUID()); + channel->getIdentifier(*channels->back()->getId()); + channel->getTitle(*channels->back()->getTitle()); + + channelReport = new SWGSDRangel::SWGChannelReport(); + + if (channel->webapiReportGet(*channelReport, channelReportError) != 501) { + channels->back()->setReport(channelReport); + } else { + delete channelReport; + } + } + } +} + QtMsgType WebAPIAdapterSrv::getMsgTypeFromString(const QString& msgTypeString) { if (msgTypeString == "debug") { diff --git a/sdrsrv/webapi/webapiadaptersrv.h b/sdrsrv/webapi/webapiadaptersrv.h index e8240bafa..0aea35ba6 100644 --- a/sdrsrv/webapi/webapiadaptersrv.h +++ b/sdrsrv/webapi/webapiadaptersrv.h @@ -169,6 +169,11 @@ public: SWGSDRangel::SWGDeviceState& response, SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetChannelsReportGet( + int deviceSetIndex, + SWGSDRangel::SWGChannelsDetail& response, + SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetChannelPost( int deviceSetIndex, SWGSDRangel::SWGChannelSettings& query, @@ -195,11 +200,18 @@ public: SWGSDRangel::SWGChannelSettings& response, SWGSDRangel::SWGErrorResponse& error); + virtual int devicesetChannelReportGet( + int deviceSetIndex, + int channelIndex, + SWGSDRangel::SWGChannelReport& response, + SWGSDRangel::SWGErrorResponse& error); + private: MainCore& m_mainCore; void getDeviceSetList(SWGSDRangel::SWGDeviceSetList* deviceSetList); void getDeviceSet(SWGSDRangel::SWGDeviceSet *swgDeviceSet, const DeviceSet* deviceSet, int deviceUISetIndex); + void getChannelsDetail(SWGSDRangel::SWGChannelsDetail *channelsDetail, const DeviceSet* deviceSet); static QtMsgType getMsgTypeFromString(const QString& msgTypeString); static void getMsgTypeString(const QtMsgType& msgType, QString& level); }; diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 2a15ee373..b1a78b888 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -845,7 +845,6 @@ margin-bottom: 20px; "description" : "Summarized information about channel plugin" }; defs.ChannelReport = { - "required" : [ "channelType", "tx" ], "discriminator" : "channelType", "properties" : { "channelType" : { @@ -863,7 +862,7 @@ margin-bottom: 20px; "$ref" : "#/definitions/NFMModReport" } }, - "description" : "Base channel report. The specific channel report present depends on channelType." + "description" : "Base channel report. The specific channel report present depends on channelType or paremt context." }; defs.ChannelSettings = { "required" : [ "channelType", "tx" ], @@ -17956,7 +17955,7 @@ except ApiException as e:
    - Generated 2018-03-18T22:42:33.187+01:00 + Generated 2018-03-19T00:19:38.769+01:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 10ed20952..017b4e0e8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -13,7 +13,7 @@ /* * SWGChannelReport.h * - * Base channel report. The specific channel report present depends on channelType. + * Base channel report. The specific channel report present depends on channelType or paremt context. */ #ifndef SWGChannelReport_H_ From a2e1f674d753be17d01a48d25fdf334826bd7056 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 19 Mar 2018 02:19:36 +0100 Subject: [PATCH 136/956] Web API: NFM scanner Python script example (1) --- swagger/sdrangel/examples/Readme.md | 96 ++++++++++++++- swagger/sdrangel/examples/nfm_scanner.py | 143 +++++++++++++++++++++++ 2 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 swagger/sdrangel/examples/nfm_scanner.py diff --git a/swagger/sdrangel/examples/Readme.md b/swagger/sdrangel/examples/Readme.md index e50d1dd92..d367e37e5 100644 --- a/swagger/sdrangel/examples/Readme.md +++ b/swagger/sdrangel/examples/Readme.md @@ -64,6 +64,12 @@ It uses the following APIs: - URI: `/sdrangel/deviceset/{deviceSetIndex}/device/run` - HTTP method: `POST` +

    nfm_scanner.py

    + +Simple NFM scanner with multiple equally spaced NFM channels. Stops whenever any of the channels squelch opens. + +Requires numpy +

    nfm_test.py

    Example of creating NFM channels (demodulator and modulator) and changing the settings @@ -125,6 +131,53 @@ It uses the following APIs: - URI: `/sdrangel/deviceset/{deviceSetIndex}/device/settings` - HTTP method: `PATCH` +

    rx_test.py

    + +Sets specified Rx in existing source device set or create a new source device set with this Rx. Adds an NFM demodulator channel. + +It uses the following APIs: + + - Create a new device set: + - Operation ID: `devicesetPost` + - URI: `/sdrangel/deviceset` + - HTTP method: `POST` + - Get information on a device set: + - Operation ID: `devicesetGet` + - URI: `/sdrangel/deviceset/{deviceSetIndex}` + - HTTP method: `GET` + - To select a device in a device set: + - Operation ID: `devicesetDevicePut` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/device` + - HTTP method: `PUT` + - To get the settings of a device: + - OperationID: `devicesetDeviceSettingsGet` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/device/settings` + - HTTP method: `GET` + - To change the settings of a device: + - OperationID: `devicesetDeviceSettingsPatch` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/device/settings` + - HTTP method: `PATCH` + - To create a new channel: + - Operation ID: `devicesetChannelPost` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/channel` + - HTTP method: `POST` + - To get the settings of a channel: + - OperationID: `devicesetChannelSettingsGet` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/settings` + - HTTP method: `GET` + - To change the settings of a channel: + - OperationID: `devicesetChannelSettingsPatch` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/settings` + - HTTP method: `PATCH` + - Start a device streaming + - OperationID: `devicesetDeviceRunPost` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/device/run` + - HTTP method: `POST` + +

    rx_tx_test.py

    + +Combines `rx_test` and `tx_test` to create a pair of source and sink device sets. The APIs used are the same as in `rx_test` or `tx_test`. +

    start_stop.py

    Starts or stops a device in the specified device set @@ -155,4 +208,45 @@ It uses this API: - URI: `/sdrangel` - HTTP method: `DELETE` - \ No newline at end of file +

    tx_test.py

    + +Sets specified Tx in existing sink device set or create a new sink device set with this Tx. Adds an NFM modulator channel. + +It uses the following APIs: + + - Create a new device set: + - Operation ID: `devicesetPost` + - URI: `/sdrangel/deviceset` + - HTTP method: `POST` + - Get information on a device set: + - Operation ID: `devicesetGet` + - URI: `/sdrangel/deviceset/{deviceSetIndex}` + - HTTP method: `GET` + - To select a device in a device set: + - Operation ID: `devicesetDevicePut` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/device` + - HTTP method: `PUT` + - To get the settings of a device: + - OperationID: `devicesetDeviceSettingsGet` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/device/settings` + - HTTP method: `GET` + - To change the settings of a device: + - OperationID: `devicesetDeviceSettingsPatch` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/device/settings` + - HTTP method: `PATCH` + - To create a new channel: + - Operation ID: `devicesetChannelPost` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/channel` + - HTTP method: `POST` + - To get the settings of a channel: + - OperationID: `devicesetChannelSettingsGet` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/settings` + - HTTP method: `GET` + - To change the settings of a channel: + - OperationID: `devicesetChannelSettingsPatch` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/channel/{channelIndex}/settings` + - HTTP method: `PATCH` + - Start a device streaming + - OperationID: `devicesetDeviceRunPost` + - URI: `/sdrangel/deviceset/{deviceSetIndex}/device/run` + - HTTP method: `POST` diff --git a/swagger/sdrangel/examples/nfm_scanner.py b/swagger/sdrangel/examples/nfm_scanner.py new file mode 100644 index 000000000..fe90c0a9b --- /dev/null +++ b/swagger/sdrangel/examples/nfm_scanner.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python + +import requests, json, traceback, sys +from optparse import OptionParser +import time +import numpy as np + +base_url = "http://127.0.0.1:8091/sdrangel" +deviceset_url = "" + +requests_methods = { + "GET": requests.get, + "PATCH": requests.patch, + "POST": requests.post, + "PUT": requests.put, + "DELETE": requests.delete +} + +class ScanControl: + def __init__(self, num_channels, channel_step, start_freq, stop_freq, log2_decim): + self.channel_shifts = [] + if num_channels < 2: + self.channel_shifts = [0] + limit = 0 + else: + limit = ((num_channels-1)*channel_step) / 2 + self.channel_shifts = list(np.linspace(-limit, limit, num_channels)) + self.device_start_freq = start_freq + limit + self.device_stop_freq = stop_freq - limit + self.device_step_freq = 2*limit + channel_step + self.device_sample_rate = (2*limit + channel_step)*(1<> sys.stderr, tb + + +if __name__ == "__main__": + main() + From efcbf9b7cb8644a71083647d15d2b4986e64c6a2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 19 Mar 2018 08:44:23 +0100 Subject: [PATCH 137/956] Web API: NFM scanner Python script example (2) --- swagger/sdrangel/examples/nfm_scanner.py | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/swagger/sdrangel/examples/nfm_scanner.py b/swagger/sdrangel/examples/nfm_scanner.py index fe90c0a9b..fc418e805 100644 --- a/swagger/sdrangel/examples/nfm_scanner.py +++ b/swagger/sdrangel/examples/nfm_scanner.py @@ -16,6 +16,7 @@ requests_methods = { "DELETE": requests.delete } +# ====================================================================== class ScanControl: def __init__(self, num_channels, channel_step, start_freq, stop_freq, log2_decim): self.channel_shifts = [] @@ -54,6 +55,67 @@ def getInputOptions(): return options +# ====================================================================== +def setupDevice(scan_control, options): + settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") + if settings is None: + exit(-1) + + if options.device_hwid == "LimeSDR": + settings["limeSdrInputSettings"]["antennaPath"] = 0 + settings["limeSdrInputSettings"]["devSampleRate"] = scan_control.device_sample_rate + settings["limeSdrInputSettings"]["log2HardDecim"] = 4 + settings["limeSdrInputSettings"]["log2SoftDecim"] = options.log2_decim + settings["limeSdrInputSettings"]["centerFrequency"] = scan_control.device_start_freq + 500000 + settings["limeSdrInputSettings"]["ncoEnable"] = 1 + settings["limeSdrInputSettings"]["ncoFrequency"] = -500000 + settings["limeSdrInputSettings"]["lpfBW"] = 1450000 + settings["limeSdrInputSettings"]["lpfFIRBW"] = scan_control.device_step_freq + 100000 + settings["limeSdrInputSettings"]["lpfFIREnable"] = 1 + settings['limeSdrInputSettings']['dcBlock'] = 1 + elif options.device_hwid == "RTLSDR": + settings['rtlSdrSettings']['devSampleRate'] = scan_control.device_sample_rate + settings['rtlSdrSettings']['centerFrequency'] = scan_control.device_start_freq + settings['rtlSdrSettings']['gain'] = 496 + settings['rtlSdrSettings']['log2Decim'] = options.log2_decim + settings['rtlSdrSettings']['dcBlock'] = 1 + settings['rtlSdrSettings']['agc'] = 1 + elif options.device_hwid == "HackRF": + settings['hackRFInputSettings']['LOppmTenths'] = -51 + settings['hackRFInputSettings']['centerFrequency'] = scan_control.device_start_freq + settings['hackRFInputSettings']['dcBlock'] = 1 + settings['hackRFInputSettings']['devSampleRate'] = scan_control.device_sample_rate + settings['hackRFInputSettings']['lnaExt'] = 1 + settings['hackRFInputSettings']['lnaGain'] = 32 + settings['hackRFInputSettings']['log2Decim'] = options.log2_decim + settings['hackRFInputSettings']['vgaGain'] = 24 + + r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") + if r is None: + exit(-1) + +# ====================================================================== +def setupChannels(scan_control, options): + i = 0 + for shift in scan_control.channel_shifts: + settings = callAPI("/deviceset/0/channel", "POST", None, {"channelType": "NFMDemod", "tx": 0}, "Create NFM demod") + if settings is None: + exit(-1) + + settings = callAPI("/deviceset/0/channel/%d/settings" % i, "GET", None, None, "Get NFM demod settings") + if settings is None: + exit(-1) + + settings["NFMDemodSettings"]["inputFrequencyOffset"] = int(shift) + settings["NFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 + settings["NFMDemodSettings"]["rfBandwidth"] = options.rf_bw + + r = callAPI("/deviceset/0/channel/%d/settings" % i, "PATCH", None, settings, "Change NFM demod") + if r is None: + exit(-1) + + i += 1 + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -119,6 +181,11 @@ def main(): if r is None: exit(-1) + setupDevice(scan_control, options) + setupChannels(scan_control, options) + + exit(0) + settings = callAPI("/deviceset/0/channel", "POST", None, {"channelType": "NFMDemod", "tx": 0}, "Create NFM demod") From 0e669cf56a01ed263ae3f05e732e0be7d9861046 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 19 Mar 2018 18:39:55 +0100 Subject: [PATCH 138/956] Web API: NFM scanner Python script example (3) --- swagger/sdrangel/examples/nfm_scanner.py | 159 +++++++++++++++++------ 1 file changed, 116 insertions(+), 43 deletions(-) diff --git a/swagger/sdrangel/examples/nfm_scanner.py b/swagger/sdrangel/examples/nfm_scanner.py index fc418e805..56bb54ccf 100644 --- a/swagger/sdrangel/examples/nfm_scanner.py +++ b/swagger/sdrangel/examples/nfm_scanner.py @@ -7,6 +7,7 @@ import numpy as np base_url = "http://127.0.0.1:8091/sdrangel" deviceset_url = "" +verbosity = 2 requests_methods = { "GET": requests.get, @@ -47,6 +48,11 @@ def getInputOptions(): parser.add_option("-r", "--rf-bw", dest="rf_bw", help="RF babdwidth (Hz). Sets to nearest available", metavar="FREQUENCY", type="int", default=10000) parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="BOOLEAN", action="store_true", default=False) parser.add_option("-m", "--mock", dest="mock", help="just print calculated values and exit", metavar="BOOLEAN", action="store_true", default=False) + parser.add_option("--ppm", dest="lo_ppm", help="LO correction in PPM", metavar="PPM", type="float", default=0.0) + parser.add_option("-t", "--settling-time", dest="settling_time", help="Scan step settling time in seconds", metavar="SECONDS", type="float", default=1.0) + parser.add_option("--sq", dest="squelch_db", help="Squelsch threshold in dB", metavar="DECIBEL", type="float", default=-50.0) + parser.add_option("--sq-gate", dest="squelch_gate", help="Squelsch gate in ms", metavar="MILLISECONDS", type="int", default=50) + parser.add_option("--re-run", dest="rerun", help="re run with given parameters without setting up device and channels", metavar="BOOLEAN", action="store_true", default=False) (options, args) = parser.parse_args() @@ -79,11 +85,15 @@ def setupDevice(scan_control, options): settings['rtlSdrSettings']['gain'] = 496 settings['rtlSdrSettings']['log2Decim'] = options.log2_decim settings['rtlSdrSettings']['dcBlock'] = 1 + settings['rtlSdrSettings']['iqImbalance'] = 1 settings['rtlSdrSettings']['agc'] = 1 + settings['rtlSdrSettings']['loPpmCorrection'] = int(options.lo_ppm) + settings['rtlSdrSettings']['rfBandwidth'] = scan_control.device_step_freq + 100000 elif options.device_hwid == "HackRF": - settings['hackRFInputSettings']['LOppmTenths'] = -51 + settings['hackRFInputSettings']['LOppmTenths'] = options.lo_ppm * 10 # in tenths of PPM settings['hackRFInputSettings']['centerFrequency'] = scan_control.device_start_freq settings['hackRFInputSettings']['dcBlock'] = 1 + settings['hackRFInputSettings']['iqImbalance'] = 1 settings['hackRFInputSettings']['devSampleRate'] = scan_control.device_sample_rate settings['hackRFInputSettings']['lnaExt'] = 1 settings['hackRFInputSettings']['lnaGain'] = 32 @@ -94,28 +104,60 @@ def setupDevice(scan_control, options): if r is None: exit(-1) +def changeDeviceFrequency(fc, options): + settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") + if settings is None: + exit(-1) + + if options.device_hwid == "LimeSDR": + settings["limeSdrInputSettings"]["centerFrequency"] = fc + 500000 + elif options.device_hwid == "RTLSDR": + settings['rtlSdrSettings']['centerFrequency'] = fc + elif options.device_hwid == "HackRF": + settings['hackRFInputSettings']['centerFrequency'] = fc + + r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device center frequncy") + if r is None: + exit(-1) + # ====================================================================== def setupChannels(scan_control, options): i = 0 for shift in scan_control.channel_shifts: - settings = callAPI("/deviceset/0/channel", "POST", None, {"channelType": "NFMDemod", "tx": 0}, "Create NFM demod") + settings = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": "NFMDemod", "tx": 0}, "Create NFM demod") if settings is None: exit(-1) - settings = callAPI("/deviceset/0/channel/%d/settings" % i, "GET", None, None, "Get NFM demod settings") + settings = callAPI(deviceset_url + "/channel/%d/settings" % i, "GET", None, None, "Get NFM demod settings") if settings is None: exit(-1) settings["NFMDemodSettings"]["inputFrequencyOffset"] = int(shift) settings["NFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 settings["NFMDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["NFMDemodSettings"]["squelch"] = options.squelch_db * 10 # centi-Bels + settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate / 10 # 10's of ms + settings["NFMDemodSettings"]["title"] = "Channel %d" % i - r = callAPI("/deviceset/0/channel/%d/settings" % i, "PATCH", None, settings, "Change NFM demod") + r = callAPI(deviceset_url + "/channel/%d/settings" % i, "PATCH", None, settings, "Change NFM demod") if r is None: exit(-1) i += 1 +# ====================================================================== +def checkScanning(): + reports = callAPI(deviceset_url + "/channels/report", "GET", None, None, "Get channels report") + if reports is None: + exit(-1) + for i in range(reports["channelcount"]): + channel = reports["channels"][i] + if "report" in channel: + if "NFMDemodReport" in channel["report"]: + if channel["report"]["NFMDemodReport"]["squelch"] == 1: + return False # stop scanning + return True # continue scanning + # ====================================================================== def printResponse(response): content_type = response.headers.get("Content-Type", None) @@ -131,12 +173,16 @@ def callAPI(url, method, params, json, text): if request_method is not None: r = request_method(url=base_url+url, params=params, json=json) if r.status_code / 100 == 2: - print(text + " succeeded") - printResponse(r) + if verbosity >= 1: + print(text + " succeeded") + if verbosity >= 2: + printResponse(r) return r.json() # all 200 yield application/json response else: - print(text + " failed") - printResponse(r) + if verbosity >= 1: + print(text + " failed") + if verbosity >= 2: + printResponse(r) return None # ====================================================================== @@ -145,6 +191,8 @@ def main(): options = getInputOptions() scan_control = ScanControl(options.num_channels, options.freq_step, options.freq_start, options.freq_stop, options.log2_decim) + # Print calculated scan parameters + print("Channel shifts: %s" % scan_control.channel_shifts) print("Sample rate: %d" % scan_control.device_sample_rate) print("Start: %d" % scan_control.device_start_freq) @@ -155,51 +203,76 @@ def main(): print("Frequency error") exit(1) - if options.mock: - freqs = [] - fc = scan_control.device_start_freq - while fc <= scan_control.device_stop_freq: - freqs += [x+fc for x in scan_control.channel_shifts] - fc += scan_control.device_step_freq - print("Scanned frequencies: %s" % freqs) + freqs = [] + nb_steps = 1 + fc = scan_control.device_start_freq + while fc <= scan_control.device_stop_freq: + freqs += [x+fc for x in scan_control.channel_shifts] + fc += scan_control.device_step_freq + nb_steps += 1 + print("Scanned frequencies: %s" % freqs) + print("In %d steps" % nb_steps) + + if options.mock: # Stop there if we are just mocking (no API access) exit(0) global base_url base_url = "http://%s/sdrangel" % options.address - # Set Rx - - if options.create: - r = callAPI("/deviceset", "POST", {"tx": 0}, None, "Add Rx device set") - if r is None: - exit(-1) + # Set Rx global deviceset_url deviceset_url = "/deviceset/%d" % options.device_index - - r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "%s" % options.device_hwid, "tx": 0}, "setup device on Rx device set") - if r is None: - exit(-1) - setupDevice(scan_control, options) - setupChannels(scan_control, options) - - exit(0) - - - - settings = callAPI("/deviceset/0/channel", "POST", None, {"channelType": "NFMDemod", "tx": 0}, "Create NFM demod") - if settings is None: - exit(-1) - - settings["NFMDemodSettings"]["inputFrequencyOffset"] = 12500 - settings["NFMDemodSettings"]["afBandwidth"] = 5000 - - r = callAPI("/deviceset/0/channel/0/settings", "PATCH", None, settings, "Change NFM demod") - if r is None: - exit(-1) + if not options.rerun: # Skip device and channels settings in re-run mode + if options.create: + r = callAPI("/deviceset", "POST", {"tx": 0}, None, "Add Rx device set") + if r is None: + exit(-1) - + r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "%s" % options.device_hwid, "tx": 0}, "setup device on Rx device set") + if r is None: + exit(-1) + + # Set device and channels + + setupDevice(scan_control, options) + setupChannels(scan_control, options) + + # Start running and scanning + + r = callAPI(deviceset_url + "/device/run", "POST", None, None, "Start running device") + if r is None: + exit(-1) + fc = scan_control.device_start_freq + + global verbosity + verbosity = 0 + + print("Move center to %d Hz" % fc) + changeDeviceFrequency(fc, options) + + try: + while True: + time.sleep(options.settling_time) + if checkScanning(): # shall we move on ? + fc += scan_control.device_step_freq + if fc > scan_control.device_stop_freq: + fc = scan_control.device_start_freq + print("New pass") + print("Move center to %d Hz" % fc) + changeDeviceFrequency(fc, options) + except KeyboardInterrupt: + print("Terminated by user") + pass + finally: + verbosity = 2 + r = callAPI(deviceset_url + "/device/run", "DELETE", None, None, "Stop running device") + if r is None: + exit(-1) + + except KeyboardInterrupt: + pass except Exception, msg: tb = traceback.format_exc() print >> sys.stderr, tb From 3f4fc02065aef4ef050f4d835500814062078e40 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 20 Mar 2018 08:23:52 +0100 Subject: [PATCH 139/956] Web API: NFM scanner Python script example (4) --- swagger/sdrangel/examples/nfm_scanner.py | 34 +++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/swagger/sdrangel/examples/nfm_scanner.py b/swagger/sdrangel/examples/nfm_scanner.py index 56bb54ccf..666c8dc0a 100644 --- a/swagger/sdrangel/examples/nfm_scanner.py +++ b/swagger/sdrangel/examples/nfm_scanner.py @@ -1,5 +1,15 @@ #!/usr/bin/env python +""" + SDRangel REST API client script + + Simple scanner for NFM channels. Builds an array of equally spaced channels. Moves device center frequency + so that adjacent parts of the spectrum are scanned by the array of channels. Stops when any of the channels + is active. Resumes when none of the channels is active. + + Uses /sdrangel/deviceset/{deviceSetIndex}/channels/report API to get channel information (since v3.13.1) +""" + import requests, json, traceback, sys from optparse import OptionParser import time @@ -53,12 +63,23 @@ def getInputOptions(): parser.add_option("--sq", dest="squelch_db", help="Squelsch threshold in dB", metavar="DECIBEL", type="float", default=-50.0) parser.add_option("--sq-gate", dest="squelch_gate", help="Squelsch gate in ms", metavar="MILLISECONDS", type="int", default=50) parser.add_option("--re-run", dest="rerun", help="re run with given parameters without setting up device and channels", metavar="BOOLEAN", action="store_true", default=False) + parser.add_option("-x", "--excl-list", dest="excl_fstr", help="frequencies (in Hz) exclusion comma separated list", metavar="LIST", type="string") (options, args) = parser.parse_args() if (options.address == None): options.address = "127.0.0.1:8091" + if options.excl_fstr is not None: + excl_flist_str = options.excl_fstr.split(',') + try: + options.excl_flist = list(map(int, excl_flist_str)) + except ValueError: + print("Invalid exclusion frequencies list: %s" % options.excl_fstr) + options.excl_flist = [] + else: + options.excl_flist = [] + return options # ====================================================================== @@ -104,6 +125,7 @@ def setupDevice(scan_control, options): if r is None: exit(-1) +# ====================================================================== def changeDeviceFrequency(fc, options): settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") if settings is None: @@ -146,7 +168,7 @@ def setupChannels(scan_control, options): i += 1 # ====================================================================== -def checkScanning(): +def checkScanning(fc, options): reports = callAPI(deviceset_url + "/channels/report", "GET", None, None, "Get channels report") if reports is None: exit(-1) @@ -155,7 +177,10 @@ def checkScanning(): if "report" in channel: if "NFMDemodReport" in channel["report"]: if channel["report"]["NFMDemodReport"]["squelch"] == 1: - return False # stop scanning + f_channel = channel["deltaFrequency"]+fc + if f_channel not in options.excl_flist: + print("Stopped at %d Hz" % f_channel) + return False # stop scanning return True # continue scanning # ====================================================================== @@ -204,13 +229,14 @@ def main(): exit(1) freqs = [] - nb_steps = 1 + nb_steps = 0 fc = scan_control.device_start_freq while fc <= scan_control.device_stop_freq: freqs += [x+fc for x in scan_control.channel_shifts] fc += scan_control.device_step_freq nb_steps += 1 print("Scanned frequencies: %s" % freqs) + print("Skipped frequencies: %s" % options.excl_flist) print("In %d steps" % nb_steps) if options.mock: # Stop there if we are just mocking (no API access) @@ -255,7 +281,7 @@ def main(): try: while True: time.sleep(options.settling_time) - if checkScanning(): # shall we move on ? + if checkScanning(fc, options): # shall we move on ? fc += scan_control.device_step_freq if fc > scan_control.device_stop_freq: fc = scan_control.device_start_freq From afa7351811554e188e38fa93fe0bef9804bb8179 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 20 Mar 2018 08:45:03 +0100 Subject: [PATCH 140/956] LimeSDR: fixed LO frequency setting with the latest version of LimeSuite --- plugins/samplesink/limesdroutput/limesdroutput.cpp | 5 +---- plugins/samplesink/limesdroutput/limesdroutputplugin.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinput.cpp | 5 +---- plugins/samplesource/limesdrinput/limesdrinputplugin.cpp | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp index b4151d43e..c12e99b72 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp @@ -871,10 +871,7 @@ bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool fo if (m_deviceShared.m_deviceParams->getDevice() != 0 && m_channelAcquired) { - if (LMS_SetLOFrequency(m_deviceShared.m_deviceParams->getDevice(), - LMS_CH_TX, - m_deviceShared.m_channel, // same for both channels anyway but switches antenna port automatically - settings.m_centerFrequency) < 0) + if (LMS_SetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_SXT, settings.m_centerFrequency) < 0) { qCritical("LimeSDROutput::applySettings: could not set frequency to %lu", settings.m_centerFrequency); } diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp index d16fa3623..b8c353723 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp @@ -34,7 +34,7 @@ const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = { QString("LimeSDR Output"), - QString("3.13.0"), + QString("3.13.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index b5e6f2457..926b7bcf0 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -1024,10 +1024,7 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc if (m_deviceShared.m_deviceParams->getDevice() != 0 && m_channelAcquired) { - if (LMS_SetLOFrequency(m_deviceShared.m_deviceParams->getDevice(), - LMS_CH_RX, - m_deviceShared.m_channel, // same for both channels anyway but switches antenna port automatically - settings.m_centerFrequency) < 0) + if (LMS_SetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_SXR, settings.m_centerFrequency) < 0) { qCritical("LimeSDRInput::applySettings: could not set frequency to %lu", settings.m_centerFrequency); } diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 3ab77f042..6e895286f 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("3.13.0"), + QString("3.13.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 72e29fd3f8f86f33d779b98421c72a954fdf7312 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 20 Mar 2018 13:49:21 +0100 Subject: [PATCH 141/956] Moved export.h file to root of exports directory and removed util --- devices/bladerf/devicebladerf.h | 2 +- devices/bladerf/devicebladerfshared.h | 2 +- devices/bladerf/devicebladerfvalues.h | 2 +- devices/hackrf/devicehackrf.h | 2 +- devices/hackrf/devicehackrfshared.h | 2 +- devices/hackrf/devicehackrfvalues.h | 2 +- devices/limesdr/devicelimesdr.h | 2 +- devices/limesdr/devicelimesdrparam.h | 2 +- devices/limesdr/devicelimesdrshared.h | 2 +- devices/perseus/deviceperseus.h | 2 +- devices/perseus/deviceperseusscan.h | 2 +- devices/plutosdr/deviceplutosdr.h | 2 +- devices/plutosdr/deviceplutosdrbox.h | 2 +- devices/plutosdr/deviceplutosdrparams.h | 2 +- devices/plutosdr/deviceplutosdrscan.h | 2 +- devices/plutosdr/deviceplutosdrshared.h | 2 +- exports/{util => }/export.h | 0 httpserver/httpconnectionhandler.h | 2 +- httpserver/httpconnectionhandlerpool.h | 2 +- httpserver/httpcookie.h | 2 +- httpserver/httplistener.h | 2 +- httpserver/httprequest.h | 2 +- httpserver/httprequesthandler.h | 2 +- httpserver/httpresponse.h | 2 +- httpserver/httpsession.h | 2 +- httpserver/httpsessionstore.h | 2 +- httpserver/staticfilecontroller.h | 2 +- logging/dualfilelogger.h | 2 +- logging/filelogger.h | 2 +- logging/logger.h | 2 +- logging/loggerwithfile.h | 2 +- logging/logmessage.h | 2 +- qrtplib/rtcpapppacket.h | 2 +- qrtplib/rtcpbyepacket.h | 2 +- qrtplib/rtcpcompoundpacket.h | 2 +- qrtplib/rtcpcompoundpacketbuilder.h | 2 +- qrtplib/rtcppacketbuilder.h | 2 +- qrtplib/rtcprrpacket.h | 2 +- qrtplib/rtcpscheduler.h | 2 +- qrtplib/rtcpsdesinfo.h | 2 +- qrtplib/rtcpsdespacket.h | 2 +- qrtplib/rtcpsrpacket.h | 2 +- qrtplib/rtpaddress.h | 2 +- qrtplib/rtpcollisionlist.h | 2 +- qrtplib/rtpinternalsourcedata.h | 2 +- qrtplib/rtppacketbuilder.h | 2 +- qrtplib/rtprandom.h | 2 +- qrtplib/rtprandomrand48.h | 2 +- qrtplib/rtprandomrands.h | 2 +- qrtplib/rtprandomurandom.h | 2 +- qrtplib/rtpsession.h | 2 +- qrtplib/rtpsessionparams.h | 2 +- qrtplib/rtpsessionsources.h | 2 +- qrtplib/rtpsourcedata.h | 2 +- qrtplib/rtpsources.h | 2 +- qrtplib/rtptimeutilities.h | 2 +- qrtplib/rtpudptransmitter.h | 2 +- sdrbase/CMakeLists.txt | 2 +- sdrbase/audio/audiodeviceinfo.h | 2 +- sdrbase/audio/audiofifo.h | 2 +- sdrbase/audio/audioinput.h | 2 +- sdrbase/audio/audionetsink.h | 2 +- sdrbase/audio/audiooutput.h | 2 +- sdrbase/channel/channelsinkapi.h | 2 +- sdrbase/channel/channelsourceapi.h | 2 +- sdrbase/commands/command.h | 2 +- sdrbase/device/deviceenumerator.h | 2 +- sdrbase/device/devicesinkapi.h | 2 +- sdrbase/device/devicesourceapi.h | 2 +- sdrbase/dsp/afsquelch.h | 2 +- sdrbase/dsp/agc.h | 2 +- sdrbase/dsp/basebandsamplesink.h | 2 +- sdrbase/dsp/basebandsamplesource.h | 2 +- sdrbase/dsp/channelmarker.h | 2 +- sdrbase/dsp/ctcssdetector.h | 2 +- sdrbase/dsp/cwkeyer.h | 2 +- sdrbase/dsp/cwkeyersettings.h | 2 +- sdrbase/dsp/decimatorsf.h | 2 +- sdrbase/dsp/devicesamplesink.h | 2 +- sdrbase/dsp/devicesamplesource.h | 2 +- sdrbase/dsp/downchannelizer.h | 2 +- sdrbase/dsp/dspcommands.h | 2 +- sdrbase/dsp/dspdevicesinkengine.h | 2 +- sdrbase/dsp/dspdevicesourceengine.h | 2 +- sdrbase/dsp/dspengine.h | 2 +- sdrbase/dsp/dvserialengine.h | 2 +- sdrbase/dsp/dvserialworker.h | 2 +- sdrbase/dsp/fftengine.h | 2 +- sdrbase/dsp/fftfilt.h | 2 +- sdrbase/dsp/fftwengine.h | 2 +- sdrbase/dsp/fftwindow.h | 2 +- sdrbase/dsp/filerecord.h | 2 +- sdrbase/dsp/filtermbe.h | 2 +- sdrbase/dsp/filterrc.h | 2 +- sdrbase/dsp/hbfiltertraits.h | 2 +- sdrbase/dsp/interpolator.h | 2 +- sdrbase/dsp/inthalfbandfilter.h | 2 +- sdrbase/dsp/inthalfbandfilterdb.h | 2 +- sdrbase/dsp/inthalfbandfilterdbf.h | 2 +- sdrbase/dsp/inthalfbandfiltereo1.h | 2 +- sdrbase/dsp/inthalfbandfilterst.h | 2 +- sdrbase/dsp/kissengine.h | 2 +- sdrbase/dsp/nco.h | 2 +- sdrbase/dsp/ncof.h | 2 +- sdrbase/dsp/nullsink.h | 2 +- sdrbase/dsp/phaselock.h | 2 +- sdrbase/dsp/recursivefilters.h | 2 +- sdrbase/dsp/samplesinkfifo.h | 2 +- sdrbase/dsp/samplesinkfifodoublebuffered.h | 2 +- sdrbase/dsp/samplesourcefifo.h | 2 +- sdrbase/dsp/threadedbasebandsamplesink.h | 2 +- sdrbase/dsp/threadedbasebandsamplesource.h | 2 +- sdrbase/dsp/upchannelizer.h | 2 +- sdrbase/dsp/wfir.h | 2 +- sdrbase/mainparser.h | 2 +- sdrbase/plugin/pluginapi.h | 2 +- sdrbase/plugin/plugininstancegui.h | 2 +- sdrbase/plugin/plugininterface.h | 2 +- sdrbase/plugin/pluginmanager.h | 2 +- sdrbase/sdrbase.pro | 2 +- sdrbase/settings/mainsettings.h | 2 +- sdrbase/settings/preferences.h | 2 +- sdrbase/settings/preset.h | 2 +- sdrbase/util/CRC64.h | 2 +- sdrbase/util/db.h | 2 +- sdrbase/util/fixedtraits.h | 2 +- sdrbase/util/message.h | 2 +- sdrbase/util/messagequeue.h | 2 +- sdrbase/util/prettyprint.h | 2 +- sdrbase/util/rtpsink.h | 2 +- sdrbase/util/samplesourceserializer.h | 2 +- sdrbase/util/simpleserializer.h | 2 +- sdrbase/util/spinlock.h | 2 +- sdrbase/util/syncmessenger.h | 2 +- sdrbase/util/uid.h | 2 +- sdrbase/webapi/webapiadapterinterface.h | 2 +- sdrbase/webapi/webapirequestmapper.h | 2 +- sdrbase/webapi/webapiserver.h | 2 +- sdrgui/device/deviceuiset.h | 2 +- sdrgui/dsp/scopevis.h | 2 +- sdrgui/dsp/scopevismulti.h | 2 +- sdrgui/dsp/scopevisng.h | 2 +- sdrgui/dsp/scopevisxy.h | 2 +- sdrgui/dsp/spectrumscopecombovis.h | 2 +- sdrgui/dsp/spectrumscopengcombovis.h | 2 +- sdrgui/dsp/spectrumvis.h | 2 +- sdrgui/gui/aboutdialog.h | 2 +- sdrgui/gui/addpresetdialog.h | 2 +- sdrgui/gui/audiodialog.h | 2 +- sdrgui/gui/basicchannelsettingsdialog.h | 2 +- sdrgui/gui/buttonswitch.h | 2 +- sdrgui/gui/channelwindow.h | 2 +- sdrgui/gui/clickablelabel.h | 2 +- sdrgui/gui/colormapper.h | 2 +- sdrgui/gui/commanditem.h | 2 +- sdrgui/gui/commandkeyreceiver.h | 2 +- sdrgui/gui/commandoutputdialog.h | 2 +- sdrgui/gui/cwkeyergui.h | 2 +- sdrgui/gui/editcommanddialog.h | 2 +- sdrgui/gui/externalclockbutton.h | 2 +- sdrgui/gui/externalclockdialog.h | 2 +- sdrgui/gui/glscope.h | 2 +- sdrgui/gui/glscopegui.h | 2 +- sdrgui/gui/glscopemulti.h | 2 +- sdrgui/gui/glscopemultigui.h | 2 +- sdrgui/gui/glscopeng.h | 2 +- sdrgui/gui/glscopenggui.h | 2 +- sdrgui/gui/glshadersimple.h | 2 +- sdrgui/gui/glshadertextured.h | 2 +- sdrgui/gui/glspectrum.h | 2 +- sdrgui/gui/glspectrumgui.h | 2 +- sdrgui/gui/indicator.h | 2 +- sdrgui/gui/levelmeter.h | 2 +- sdrgui/gui/loggingdialog.h | 2 +- sdrgui/gui/mypositiondialog.h | 2 +- sdrgui/gui/pluginsdialog.h | 2 +- sdrgui/gui/presetitem.h | 2 +- sdrgui/gui/rollupwidget.h | 2 +- sdrgui/gui/samplingdevicecontrol.h | 2 +- sdrgui/gui/samplingdevicedialog.h | 2 +- sdrgui/gui/scaleengine.h | 2 +- sdrgui/gui/tickedslider.h | 2 +- sdrgui/gui/transverterbutton.h | 2 +- sdrgui/gui/transverterdialog.h | 2 +- sdrgui/gui/tvscreen.h | 2 +- sdrgui/gui/valuedial.h | 2 +- sdrgui/gui/valuedialz.h | 2 +- sdrgui/mainwindow.h | 2 +- sdrgui/webapi/webapiadaptergui.h | 2 +- sdrsrv/maincore.h | 2 +- 190 files changed, 189 insertions(+), 189 deletions(-) rename exports/{util => }/export.h (100%) diff --git a/devices/bladerf/devicebladerf.h b/devices/bladerf/devicebladerf.h index 73233a79b..20c2bab5a 100644 --- a/devices/bladerf/devicebladerf.h +++ b/devices/bladerf/devicebladerf.h @@ -19,7 +19,7 @@ #include -#include "util/export.h" +#include "export.h" class DEVICES_API DeviceBladeRF { diff --git a/devices/bladerf/devicebladerfshared.h b/devices/bladerf/devicebladerfshared.h index 996d7deca..2fe2d7f89 100644 --- a/devices/bladerf/devicebladerfshared.h +++ b/devices/bladerf/devicebladerfshared.h @@ -18,7 +18,7 @@ #define DEVICES_BLADERF_DEVICEHACKRFSHARED_H_ #include "util/message.h" -#include "util/export.h" +#include "export.h" class DEVICES_API DeviceBladeRFShared { diff --git a/devices/bladerf/devicebladerfvalues.h b/devices/bladerf/devicebladerfvalues.h index 23f57706e..2498e6cd6 100644 --- a/devices/bladerf/devicebladerfvalues.h +++ b/devices/bladerf/devicebladerfvalues.h @@ -17,7 +17,7 @@ #ifndef DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ #define DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ -#include "util/export.h" +#include "export.h" class DEVICES_API DeviceBladeRFBandwidths { public: diff --git a/devices/hackrf/devicehackrf.h b/devices/hackrf/devicehackrf.h index 72e78c048..4cbb39d92 100644 --- a/devices/hackrf/devicehackrf.h +++ b/devices/hackrf/devicehackrf.h @@ -19,7 +19,7 @@ #include "libhackrf/hackrf.h" -#include "util/export.h" +#include "export.h" class DEVICES_API DeviceHackRF { diff --git a/devices/hackrf/devicehackrfshared.h b/devices/hackrf/devicehackrfshared.h index cefa5c32a..249c30852 100644 --- a/devices/hackrf/devicehackrfshared.h +++ b/devices/hackrf/devicehackrfshared.h @@ -18,7 +18,7 @@ #define DEVICES_HACKRF_DEVICEHACKRFSHARED_H_ #include "util/message.h" -#include "util/export.h" +#include "export.h" class DEVICES_API DeviceHackRFShared { diff --git a/devices/hackrf/devicehackrfvalues.h b/devices/hackrf/devicehackrfvalues.h index d322e4d06..5f95ed859 100644 --- a/devices/hackrf/devicehackrfvalues.h +++ b/devices/hackrf/devicehackrfvalues.h @@ -17,7 +17,7 @@ #ifndef DEVICES_HACKRF_DEVICEHACKRFVALUES_H_ #define DEVICES_HACKRF_DEVICEHACKRFVALUES_H_ -#include "util/export.h" +#include "export.h" class DEVICES_API HackRFBandwidths { public: diff --git a/devices/limesdr/devicelimesdr.h b/devices/limesdr/devicelimesdr.h index f409b31ed..ed748457e 100644 --- a/devices/limesdr/devicelimesdr.h +++ b/devices/limesdr/devicelimesdr.h @@ -19,7 +19,7 @@ #include "lime/LimeSuite.h" -#include "util/export.h" +#include "export.h" class DEVICES_API DeviceLimeSDR { diff --git a/devices/limesdr/devicelimesdrparam.h b/devices/limesdr/devicelimesdrparam.h index e34251d1b..353b256e0 100644 --- a/devices/limesdr/devicelimesdrparam.h +++ b/devices/limesdr/devicelimesdrparam.h @@ -19,7 +19,7 @@ #include "lime/LimeSuite.h" -#include "util/export.h" +#include "export.h" /** * This structure refers to one physical device shared among parties (logical devices represented by diff --git a/devices/limesdr/devicelimesdrshared.h b/devices/limesdr/devicelimesdrshared.h index 6d90a63b5..c8df559e2 100644 --- a/devices/limesdr/devicelimesdrshared.h +++ b/devices/limesdr/devicelimesdrshared.h @@ -20,7 +20,7 @@ #include #include "devicelimesdrparam.h" #include "util/message.h" -#include "util/export.h" +#include "export.h" /** * Structure shared by a buddy with other buddies diff --git a/devices/perseus/deviceperseus.h b/devices/perseus/deviceperseus.h index 3305fdf54..1ac30fcdb 100644 --- a/devices/perseus/deviceperseus.h +++ b/devices/perseus/deviceperseus.h @@ -19,7 +19,7 @@ #include "deviceperseusscan.h" -#include "util/export.h" +#include "export.h" class DEVICES_API DevicePerseus { diff --git a/devices/perseus/deviceperseusscan.h b/devices/perseus/deviceperseusscan.h index 4c4e1b3d5..17bc8aca3 100644 --- a/devices/perseus/deviceperseusscan.h +++ b/devices/perseus/deviceperseusscan.h @@ -23,7 +23,7 @@ #include #include -#include "util/export.h" +#include "export.h" class DEVICES_API DevicePerseusScan { diff --git a/devices/plutosdr/deviceplutosdr.h b/devices/plutosdr/deviceplutosdr.h index dbae13e0d..bef2503ab 100644 --- a/devices/plutosdr/deviceplutosdr.h +++ b/devices/plutosdr/deviceplutosdr.h @@ -22,7 +22,7 @@ #include "deviceplutosdrscan.h" #include "deviceplutosdrbox.h" -#include "util/export.h" +#include "export.h" class DEVICES_API DevicePlutoSDR { diff --git a/devices/plutosdr/deviceplutosdrbox.h b/devices/plutosdr/deviceplutosdrbox.h index 4f6ae948e..4941ff30b 100644 --- a/devices/plutosdr/deviceplutosdrbox.h +++ b/devices/plutosdr/deviceplutosdrbox.h @@ -22,7 +22,7 @@ #include #include "deviceplutosdrscan.h" -#include "util/export.h" +#include "export.h" class DEVICES_API DevicePlutoSDRBox { diff --git a/devices/plutosdr/deviceplutosdrparams.h b/devices/plutosdr/deviceplutosdrparams.h index 69f83de66..113b05752 100644 --- a/devices/plutosdr/deviceplutosdrparams.h +++ b/devices/plutosdr/deviceplutosdrparams.h @@ -19,7 +19,7 @@ #include -#include "util/export.h" +#include "export.h" class DEVICES_API DevicePlutoSDRBox; diff --git a/devices/plutosdr/deviceplutosdrscan.h b/devices/plutosdr/deviceplutosdrscan.h index 1ad1d0d41..9d1b24988 100644 --- a/devices/plutosdr/deviceplutosdrscan.h +++ b/devices/plutosdr/deviceplutosdrscan.h @@ -21,7 +21,7 @@ #include #include -#include "util/export.h" +#include "export.h" class DEVICES_API DevicePlutoSDRScan { diff --git a/devices/plutosdr/deviceplutosdrshared.h b/devices/plutosdr/deviceplutosdrshared.h index 5ae24df29..9d98c66a0 100644 --- a/devices/plutosdr/deviceplutosdrshared.h +++ b/devices/plutosdr/deviceplutosdrshared.h @@ -20,7 +20,7 @@ #include #include "util/message.h" -#include "util/export.h" +#include "export.h" class DevicePlutoSDRParams; diff --git a/exports/util/export.h b/exports/export.h similarity index 100% rename from exports/util/export.h rename to exports/export.h diff --git a/httpserver/httpconnectionhandler.h b/httpserver/httpconnectionhandler.h index e205b6585..855956c8d 100644 --- a/httpserver/httpconnectionhandler.h +++ b/httpserver/httpconnectionhandler.h @@ -18,7 +18,7 @@ #include "httprequesthandler.h" #include "httplistenersettings.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/httpconnectionhandlerpool.h b/httpserver/httpconnectionhandlerpool.h index f264d650b..92d43f3b5 100644 --- a/httpserver/httpconnectionhandlerpool.h +++ b/httpserver/httpconnectionhandlerpool.h @@ -9,7 +9,7 @@ #include "httpconnectionhandler.h" #include "httplistenersettings.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/httpcookie.h b/httpserver/httpcookie.h index 58c84a66b..6d22d9ec1 100644 --- a/httpserver/httpcookie.h +++ b/httpserver/httpcookie.h @@ -10,7 +10,7 @@ #include #include "httpglobal.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/httplistener.h b/httpserver/httplistener.h index d7b5c0282..c10951ef6 100644 --- a/httpserver/httplistener.h +++ b/httpserver/httplistener.h @@ -15,7 +15,7 @@ #include "httprequesthandler.h" #include "httplistenersettings.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/httprequest.h b/httpserver/httprequest.h index acca03652..e35f6a65e 100644 --- a/httpserver/httprequest.h +++ b/httpserver/httprequest.h @@ -17,7 +17,7 @@ #include "httpglobal.h" #include "httplistenersettings.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/httprequesthandler.h b/httpserver/httprequesthandler.h index c2aa08736..86e22c7a9 100644 --- a/httpserver/httprequesthandler.h +++ b/httpserver/httprequesthandler.h @@ -10,7 +10,7 @@ #include "httprequest.h" #include "httpresponse.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/httpresponse.h b/httpserver/httpresponse.h index 4270a37cb..61060886c 100644 --- a/httpserver/httpresponse.h +++ b/httpserver/httpresponse.h @@ -12,7 +12,7 @@ #include "httpglobal.h" #include "httpcookie.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/httpsession.h b/httpserver/httpsession.h index ed7e673cc..0625a1171 100644 --- a/httpserver/httpsession.h +++ b/httpserver/httpsession.h @@ -11,7 +11,7 @@ #include #include "httpglobal.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/httpsessionstore.h b/httpserver/httpsessionstore.h index 301bc329d..2e789d7c4 100644 --- a/httpserver/httpsessionstore.h +++ b/httpserver/httpsessionstore.h @@ -16,7 +16,7 @@ #include "httprequest.h" #include "httpsessionssettings.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/httpserver/staticfilecontroller.h b/httpserver/staticfilecontroller.h index 2ff346e85..30f009d25 100644 --- a/httpserver/staticfilecontroller.h +++ b/httpserver/staticfilecontroller.h @@ -13,7 +13,7 @@ #include "httpresponse.h" #include "httprequesthandler.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/logging/dualfilelogger.h b/logging/dualfilelogger.h index 6599d6542..f8c0dfadf 100644 --- a/logging/dualfilelogger.h +++ b/logging/dualfilelogger.h @@ -13,7 +13,7 @@ #include "logger.h" #include "filelogger.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/logging/filelogger.h b/logging/filelogger.h index 62eb04cc0..3d1723a33 100644 --- a/logging/filelogger.h +++ b/logging/filelogger.h @@ -15,7 +15,7 @@ #include "logger.h" #include "fileloggersettings.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/logging/logger.h b/logging/logger.h index ba7de727b..663d4b315 100644 --- a/logging/logger.h +++ b/logging/logger.h @@ -15,7 +15,7 @@ #include "logglobal.h" #include "logmessage.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/logging/loggerwithfile.h b/logging/loggerwithfile.h index defa586cd..1de8f1b34 100644 --- a/logging/loggerwithfile.h +++ b/logging/loggerwithfile.h @@ -12,7 +12,7 @@ #include "logger.h" #include "filelogger.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/logging/logmessage.h b/logging/logmessage.h index 3552f6be0..91df741f4 100644 --- a/logging/logmessage.h +++ b/logging/logmessage.h @@ -11,7 +11,7 @@ #include #include "logglobal.h" -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/qrtplib/rtcpapppacket.h b/qrtplib/rtcpapppacket.h index d8f36efa0..6a94597e4 100644 --- a/qrtplib/rtcpapppacket.h +++ b/qrtplib/rtcpapppacket.h @@ -43,7 +43,7 @@ #include "rtpstructs.h" #include "rtpendian.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcpbyepacket.h b/qrtplib/rtcpbyepacket.h index f6f9493e0..16947943d 100644 --- a/qrtplib/rtcpbyepacket.h +++ b/qrtplib/rtcpbyepacket.h @@ -43,7 +43,7 @@ #include "rtpstructs.h" #include "rtpendian.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h index c15aaacd1..461a263b8 100644 --- a/qrtplib/rtcpcompoundpacket.h +++ b/qrtplib/rtcpcompoundpacket.h @@ -43,7 +43,7 @@ #include "rtpendian.h" #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h index 834599ad5..c66210ab8 100644 --- a/qrtplib/rtcpcompoundpacketbuilder.h +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -46,7 +46,7 @@ #include "rtpendian.h" #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcppacketbuilder.h b/qrtplib/rtcppacketbuilder.h index c19f5a725..593e477c5 100644 --- a/qrtplib/rtcppacketbuilder.h +++ b/qrtplib/rtcppacketbuilder.h @@ -44,7 +44,7 @@ #include "rtcpsdesinfo.h" #include "rtptimeutilities.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcprrpacket.h b/qrtplib/rtcprrpacket.h index 2ca047ffb..2b49ad40b 100644 --- a/qrtplib/rtcprrpacket.h +++ b/qrtplib/rtcprrpacket.h @@ -43,7 +43,7 @@ #include "rtpstructs.h" #include "rtpendian.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcpscheduler.h b/qrtplib/rtcpscheduler.h index a9272ed34..7557414bb 100644 --- a/qrtplib/rtcpscheduler.h +++ b/qrtplib/rtcpscheduler.h @@ -43,7 +43,7 @@ #include "rtprandom.h" #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h index 65cfaa0b7..16e9e2ab9 100644 --- a/qrtplib/rtcpsdesinfo.h +++ b/qrtplib/rtcpsdesinfo.h @@ -45,7 +45,7 @@ #include #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcpsdespacket.h b/qrtplib/rtcpsdespacket.h index eaa7c6744..f51cab448 100644 --- a/qrtplib/rtcpsdespacket.h +++ b/qrtplib/rtcpsdespacket.h @@ -44,7 +44,7 @@ #include "rtpdefines.h" #include "rtpendian.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtcpsrpacket.h b/qrtplib/rtcpsrpacket.h index 20131fc5e..5f8f9172f 100644 --- a/qrtplib/rtcpsrpacket.h +++ b/qrtplib/rtcpsrpacket.h @@ -44,7 +44,7 @@ #include "rtpstructs.h" #include "rtpendian.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h index 21b2caf3f..3d69f3941 100644 --- a/qrtplib/rtpaddress.h +++ b/qrtplib/rtpaddress.h @@ -43,7 +43,7 @@ #include #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtpcollisionlist.h b/qrtplib/rtpcollisionlist.h index e8c5e2616..e17e01c28 100644 --- a/qrtplib/rtpcollisionlist.h +++ b/qrtplib/rtpcollisionlist.h @@ -43,7 +43,7 @@ #include "rtptimeutilities.h" #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtpinternalsourcedata.h b/qrtplib/rtpinternalsourcedata.h index f36f8eba9..42b9e765f 100644 --- a/qrtplib/rtpinternalsourcedata.h +++ b/qrtplib/rtpinternalsourcedata.h @@ -44,7 +44,7 @@ #include "rtptimeutilities.h" #include "rtpsources.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib/rtppacketbuilder.h index 6fe1cd689..02c0e8c4d 100644 --- a/qrtplib/rtppacketbuilder.h +++ b/qrtplib/rtppacketbuilder.h @@ -45,7 +45,7 @@ #include "rtptimeutilities.h" #include "rtptypes.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtprandom.h b/qrtplib/rtprandom.h index 747f03cd3..fb8fb965a 100644 --- a/qrtplib/rtprandom.h +++ b/qrtplib/rtprandom.h @@ -42,7 +42,7 @@ #include "rtptypes.h" #include -#include "util/export.h" +#include "export.h" #define RTPRANDOM_2POWMIN63 1.08420217248550443400745280086994171142578125e-19 diff --git a/qrtplib/rtprandomrand48.h b/qrtplib/rtprandomrand48.h index 6795c86b3..a4282166a 100644 --- a/qrtplib/rtprandomrand48.h +++ b/qrtplib/rtprandomrand48.h @@ -42,7 +42,7 @@ #include "rtprandom.h" #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtprandomrands.h b/qrtplib/rtprandomrands.h index 58f692a6c..f5a0cf377 100644 --- a/qrtplib/rtprandomrands.h +++ b/qrtplib/rtprandomrands.h @@ -41,7 +41,7 @@ #include "rtpconfig.h" #include "rtprandom.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtprandomurandom.h b/qrtplib/rtprandomurandom.h index 4d5cf339c..67b2d1647 100644 --- a/qrtplib/rtprandomurandom.h +++ b/qrtplib/rtprandomurandom.h @@ -42,7 +42,7 @@ #include "rtprandom.h" #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index e81994c7a..d2393ddc9 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -49,7 +49,7 @@ #include "rtcpcompoundpacketbuilder.h" #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtpsessionparams.h b/qrtplib/rtpsessionparams.h index 9cf6cf790..b58cc424c 100644 --- a/qrtplib/rtpsessionparams.h +++ b/qrtplib/rtpsessionparams.h @@ -44,7 +44,7 @@ #include "rtptimeutilities.h" #include "rtpsources.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtpsessionsources.h b/qrtplib/rtpsessionsources.h index 92d6b727d..fbb6e22a6 100644 --- a/qrtplib/rtpsessionsources.h +++ b/qrtplib/rtpsessionsources.h @@ -41,7 +41,7 @@ #include "rtpconfig.h" #include "rtpsources.h" -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h index 8958cb393..b71256687 100644 --- a/qrtplib/rtpsourcedata.h +++ b/qrtplib/rtpsourcedata.h @@ -46,7 +46,7 @@ #include "rtpsources.h" #include -#include "util/export.h" +#include "export.h" namespace qrtplib { diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h index 7ddff9bca..4b5c2e1c3 100644 --- a/qrtplib/rtpsources.h +++ b/qrtplib/rtpsources.h @@ -43,7 +43,7 @@ #include "rtcpsdespacket.h" #include "rtptypes.h" -#include "util/export.h" +#include "export.h" #define RTPSOURCES_HASHSIZE 8317 diff --git a/qrtplib/rtptimeutilities.h b/qrtplib/rtptimeutilities.h index be885a0c0..d474ff2fc 100644 --- a/qrtplib/rtptimeutilities.h +++ b/qrtplib/rtptimeutilities.h @@ -46,7 +46,7 @@ #include #endif // RTP_HAVE_QUERYPERFORMANCECOUNTER -#include "util/export.h" +#include "export.h" #define RTP_NTPTIMEOFFSET 2208988800UL diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h index 223810a58..ded4f8a3c 100644 --- a/qrtplib/rtpudptransmitter.h +++ b/qrtplib/rtpudptransmitter.h @@ -34,7 +34,7 @@ #define QRTPLIB_RTPUDPTRANSMITTER_H_ #include "rtptransmitter.h" -#include "util/export.h" +#include "export.h" #include #include diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 9217c8647..c56bc4cba 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -168,7 +168,7 @@ set(sdrbase_HEADERS util/CRC64.h util/db.h util/doublebuffer.h - #util/export.h + #export.h util/fixedtraits.h util/message.h util/messagequeue.h diff --git a/sdrbase/audio/audiodeviceinfo.h b/sdrbase/audio/audiodeviceinfo.h index 02938b674..a25c90d75 100644 --- a/sdrbase/audio/audiodeviceinfo.h +++ b/sdrbase/audio/audiodeviceinfo.h @@ -22,7 +22,7 @@ #include #include -#include "util/export.h" +#include "export.h" class SDRBASE_API AudioDeviceInfo { public: diff --git a/sdrbase/audio/audiofifo.h b/sdrbase/audio/audiofifo.h index fb2760270..22da64b53 100644 --- a/sdrbase/audio/audiofifo.h +++ b/sdrbase/audio/audiofifo.h @@ -23,7 +23,7 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" //#include "util/udpsink.h" class AudioNetSink; diff --git a/sdrbase/audio/audioinput.h b/sdrbase/audio/audioinput.h index 6e3cc56b5..452ea3de7 100644 --- a/sdrbase/audio/audioinput.h +++ b/sdrbase/audio/audioinput.h @@ -22,7 +22,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" class QAudioInput; class AudioFifo; diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index 23cfe93a3..780d9dc49 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -19,7 +19,7 @@ #define SDRBASE_AUDIO_AUDIONETSINK_H_ #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include #include diff --git a/sdrbase/audio/audiooutput.h b/sdrbase/audio/audiooutput.h index 4fd1dade4..2840f4e53 100644 --- a/sdrbase/audio/audiooutput.h +++ b/sdrbase/audio/audiooutput.h @@ -23,7 +23,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" class QAudioOutput; class AudioFifo; diff --git a/sdrbase/channel/channelsinkapi.h b/sdrbase/channel/channelsinkapi.h index fab5bc276..ee0494c35 100644 --- a/sdrbase/channel/channelsinkapi.h +++ b/sdrbase/channel/channelsinkapi.h @@ -23,7 +23,7 @@ #include #include -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { diff --git a/sdrbase/channel/channelsourceapi.h b/sdrbase/channel/channelsourceapi.h index 5d943ed6d..2b5cf8d59 100644 --- a/sdrbase/channel/channelsourceapi.h +++ b/sdrbase/channel/channelsourceapi.h @@ -22,7 +22,7 @@ #include #include -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { diff --git a/sdrbase/commands/command.h b/sdrbase/commands/command.h index 491f89df6..8103b1791 100644 --- a/sdrbase/commands/command.h +++ b/sdrbase/commands/command.h @@ -25,7 +25,7 @@ #include #include -#include "util/export.h" +#include "export.h" class SDRBASE_API Command : public QObject { diff --git a/sdrbase/device/deviceenumerator.h b/sdrbase/device/deviceenumerator.h index b599dcb61..14dae4048 100644 --- a/sdrbase/device/deviceenumerator.h +++ b/sdrbase/device/deviceenumerator.h @@ -20,7 +20,7 @@ #include #include "plugin/plugininterface.h" -#include "util/export.h" +#include "export.h" class PluginManager; diff --git a/sdrbase/device/devicesinkapi.h b/sdrbase/device/devicesinkapi.h index 19434b824..1467dd64b 100644 --- a/sdrbase/device/devicesinkapi.h +++ b/sdrbase/device/devicesinkapi.h @@ -21,7 +21,7 @@ #include #include "dsp/dspdevicesinkengine.h" -#include "util/export.h" +#include "export.h" class BasebandSampleSource; class ThreadedBasebandSampleSource; diff --git a/sdrbase/device/devicesourceapi.h b/sdrbase/device/devicesourceapi.h index f8dd69ea3..72286b7ec 100644 --- a/sdrbase/device/devicesourceapi.h +++ b/sdrbase/device/devicesourceapi.h @@ -23,7 +23,7 @@ #include "dsp/dspdevicesourceengine.h" -#include "util/export.h" +#include "export.h" class BasebandSampleSink; class ThreadedBasebandSampleSink; diff --git a/sdrbase/dsp/afsquelch.h b/sdrbase/dsp/afsquelch.h index 72bca12c4..324fced9d 100644 --- a/sdrbase/dsp/afsquelch.h +++ b/sdrbase/dsp/afsquelch.h @@ -19,7 +19,7 @@ #include "dsp/dsptypes.h" #include "dsp/movingaverage.h" -#include "util/export.h" +#include "export.h" /** AFSquelch: AF squelch class based on the Modified Goertzel * algorithm. diff --git a/sdrbase/dsp/agc.h b/sdrbase/dsp/agc.h index 2f7a3d71d..3c0b464bd 100644 --- a/sdrbase/dsp/agc.h +++ b/sdrbase/dsp/agc.h @@ -10,7 +10,7 @@ #include "movingaverage.h" #include "util/movingaverage.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API AGC { diff --git a/sdrbase/dsp/basebandsamplesink.h b/sdrbase/dsp/basebandsamplesink.h index f70e93230..21e122434 100644 --- a/sdrbase/dsp/basebandsamplesink.h +++ b/sdrbase/dsp/basebandsamplesink.h @@ -20,7 +20,7 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include "util/messagequeue.h" #include "util/message.h" diff --git a/sdrbase/dsp/basebandsamplesource.h b/sdrbase/dsp/basebandsamplesource.h index a5b2e595c..f9f2a66a9 100644 --- a/sdrbase/dsp/basebandsamplesource.h +++ b/sdrbase/dsp/basebandsamplesource.h @@ -21,7 +21,7 @@ #include #include "dsp/dsptypes.h" #include "dsp/samplesourcefifo.h" -#include "util/export.h" +#include "export.h" #include "util/messagequeue.h" class Message; diff --git a/sdrbase/dsp/channelmarker.h b/sdrbase/dsp/channelmarker.h index 87bcb45e1..a2981a01e 100644 --- a/sdrbase/dsp/channelmarker.h +++ b/sdrbase/dsp/channelmarker.h @@ -6,7 +6,7 @@ #include #include "settings/serializable.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API ChannelMarker : public QObject, public Serializable { Q_OBJECT diff --git a/sdrbase/dsp/ctcssdetector.h b/sdrbase/dsp/ctcssdetector.h index 6f2fada0e..419b9ed10 100644 --- a/sdrbase/dsp/ctcssdetector.h +++ b/sdrbase/dsp/ctcssdetector.h @@ -10,7 +10,7 @@ #define INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" /** CTCSSDetector: Continuous Tone Coded Squelch System * tone detector class based on the Modified Goertzel diff --git a/sdrbase/dsp/cwkeyer.h b/sdrbase/dsp/cwkeyer.h index 35e2399ab..a7d580964 100644 --- a/sdrbase/dsp/cwkeyer.h +++ b/sdrbase/dsp/cwkeyer.h @@ -21,7 +21,7 @@ #include #include -#include "util/export.h" +#include "export.h" #include "util/message.h" #include "cwkeyersettings.h" diff --git a/sdrbase/dsp/cwkeyersettings.h b/sdrbase/dsp/cwkeyersettings.h index d4a4d9d4a..5ebaf9401 100644 --- a/sdrbase/dsp/cwkeyersettings.h +++ b/sdrbase/dsp/cwkeyersettings.h @@ -21,7 +21,7 @@ #include #include -#include "util/export.h" +#include "export.h" class SDRBASE_API CWKeyerSettings { diff --git a/sdrbase/dsp/decimatorsf.h b/sdrbase/dsp/decimatorsf.h index 751f39815..ba419fb40 100644 --- a/sdrbase/dsp/decimatorsf.h +++ b/sdrbase/dsp/decimatorsf.h @@ -18,7 +18,7 @@ #define SDRBASE_DSP_DECIMATORSF_H_ #include "dsp/inthalfbandfilterdbf.h" -#include "util/export.h" +#include "export.h" #define DECIMATORSF_HB_FILTER_ORDER 64 diff --git a/sdrbase/dsp/devicesamplesink.h b/sdrbase/dsp/devicesamplesink.h index 5171f06ef..8ed05e8b2 100644 --- a/sdrbase/dsp/devicesamplesink.h +++ b/sdrbase/dsp/devicesamplesink.h @@ -23,7 +23,7 @@ #include "samplesourcefifo.h" #include "util/message.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { diff --git a/sdrbase/dsp/devicesamplesource.h b/sdrbase/dsp/devicesamplesource.h index 909c74a4b..8ee7604ba 100644 --- a/sdrbase/dsp/devicesamplesource.h +++ b/sdrbase/dsp/devicesamplesource.h @@ -24,7 +24,7 @@ #include "samplesinkfifo.h" #include "util/message.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { diff --git a/sdrbase/dsp/downchannelizer.h b/sdrbase/dsp/downchannelizer.h index b1dad6012..afecca973 100644 --- a/sdrbase/dsp/downchannelizer.h +++ b/sdrbase/dsp/downchannelizer.h @@ -21,7 +21,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" #include "util/message.h" #ifdef SDR_RX_SAMPLE_24BIT #include "dsp/inthalfbandfilterdb.h" diff --git a/sdrbase/dsp/dspcommands.h b/sdrbase/dsp/dspcommands.h index 49199098b..e354498a2 100644 --- a/sdrbase/dsp/dspcommands.h +++ b/sdrbase/dsp/dspcommands.h @@ -21,7 +21,7 @@ #include #include "util/message.h" #include "fftwindow.h" -#include "util/export.h" +#include "export.h" class DeviceSampleSource; class BasebandSampleSink; diff --git a/sdrbase/dsp/dspdevicesinkengine.h b/sdrbase/dsp/dspdevicesinkengine.h index f148df672..f97560c2d 100644 --- a/sdrbase/dsp/dspdevicesinkengine.h +++ b/sdrbase/dsp/dspdevicesinkengine.h @@ -29,7 +29,7 @@ #include "dsp/fftwindow.h" #include "util/messagequeue.h" #include "util/syncmessenger.h" -#include "util/export.h" +#include "export.h" class DeviceSampleSink; class BasebandSampleSource; diff --git a/sdrbase/dsp/dspdevicesourceengine.h b/sdrbase/dsp/dspdevicesourceengine.h index a71a3ebbc..053930fd0 100644 --- a/sdrbase/dsp/dspdevicesourceengine.h +++ b/sdrbase/dsp/dspdevicesourceengine.h @@ -26,7 +26,7 @@ #include "dsp/fftwindow.h" #include "util/messagequeue.h" #include "util/syncmessenger.h" -#include "util/export.h" +#include "export.h" #include "util/movingaverage.h" class DeviceSampleSource; diff --git a/sdrbase/dsp/dspengine.h b/sdrbase/dsp/dspengine.h index 43fe1bf28..b5aaa399f 100644 --- a/sdrbase/dsp/dspengine.h +++ b/sdrbase/dsp/dspengine.h @@ -24,7 +24,7 @@ #include #include "audio/audiooutput.h" #include "audio/audioinput.h" -#include "util/export.h" +#include "export.h" #ifdef DSD_USE_SERIALDV #include "dsp/dvserialengine.h" #endif diff --git a/sdrbase/dsp/dvserialengine.h b/sdrbase/dsp/dvserialengine.h index b5c50af09..6de8ace91 100644 --- a/sdrbase/dsp/dvserialengine.h +++ b/sdrbase/dsp/dvserialengine.h @@ -24,7 +24,7 @@ #include #include -#include "util/export.h" +#include "export.h" class QThread; class DVSerialWorker; diff --git a/sdrbase/dsp/dvserialworker.h b/sdrbase/dsp/dvserialworker.h index 72dac23ad..c9ecaaded 100644 --- a/sdrbase/dsp/dvserialworker.h +++ b/sdrbase/dsp/dvserialworker.h @@ -29,7 +29,7 @@ #include "util/message.h" #include "util/syncmessenger.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" #include "dsp/filtermbe.h" #include "dsp/dsptypes.h" diff --git a/sdrbase/dsp/fftengine.h b/sdrbase/dsp/fftengine.h index abae57959..78028a8d5 100644 --- a/sdrbase/dsp/fftengine.h +++ b/sdrbase/dsp/fftengine.h @@ -2,7 +2,7 @@ #define INCLUDE_FFTENGINE_H #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API FFTEngine { public: diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index 2feabd9ce..b52d2bd83 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -7,7 +7,7 @@ #include #include "gfft.h" -#include "util/export.h" +#include "export.h" #undef M_PI #define M_PI 3.14159265358979323846 diff --git a/sdrbase/dsp/fftwengine.h b/sdrbase/dsp/fftwengine.h index 6000a7a7a..3d269aeb4 100644 --- a/sdrbase/dsp/fftwengine.h +++ b/sdrbase/dsp/fftwengine.h @@ -5,7 +5,7 @@ #include #include #include "dsp/fftengine.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API FFTWEngine : public FFTEngine { public: diff --git a/sdrbase/dsp/fftwindow.h b/sdrbase/dsp/fftwindow.h index cad15d052..4dece932e 100644 --- a/sdrbase/dsp/fftwindow.h +++ b/sdrbase/dsp/fftwindow.h @@ -22,7 +22,7 @@ #define _USE_MATH_DEFINES #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #undef M_PI #define M_PI 3.14159265358979323846 diff --git a/sdrbase/dsp/filerecord.h b/sdrbase/dsp/filerecord.h index ccb531a02..9272d07a7 100644 --- a/sdrbase/dsp/filerecord.h +++ b/sdrbase/dsp/filerecord.h @@ -7,7 +7,7 @@ #include #include -#include "util/export.h" +#include "export.h" class Message; diff --git a/sdrbase/dsp/filtermbe.h b/sdrbase/dsp/filtermbe.h index 9163e8523..a9ce75db3 100644 --- a/sdrbase/dsp/filtermbe.h +++ b/sdrbase/dsp/filtermbe.h @@ -58,7 +58,7 @@ */ #include "iirfilter.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API MBEAudioInterpolatorFilter { diff --git a/sdrbase/dsp/filterrc.h b/sdrbase/dsp/filterrc.h index 09da1cd64..1c4a574d1 100644 --- a/sdrbase/dsp/filterrc.h +++ b/sdrbase/dsp/filterrc.h @@ -19,7 +19,7 @@ #define INCLUDE_DSP_FILTERRC_H_ #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" /** First order low-pass IIR filter for real-valued signals. */ class SDRBASE_API LowPassFilterRC diff --git a/sdrbase/dsp/hbfiltertraits.h b/sdrbase/dsp/hbfiltertraits.h index 8d1848944..5d3136604 100644 --- a/sdrbase/dsp/hbfiltertraits.h +++ b/sdrbase/dsp/hbfiltertraits.h @@ -19,7 +19,7 @@ #define SDRBASE_DSP_HBFILTERTRAITS_H_ #include -#include "util/export.h" +#include "export.h" // uses Q1.14 format internally, input and output are S16 diff --git a/sdrbase/dsp/interpolator.h b/sdrbase/dsp/interpolator.h index 11a979e48..607b3fe97 100644 --- a/sdrbase/dsp/interpolator.h +++ b/sdrbase/dsp/interpolator.h @@ -5,7 +5,7 @@ #include #endif #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include #ifndef __WINDOWS__ #include diff --git a/sdrbase/dsp/inthalfbandfilter.h b/sdrbase/dsp/inthalfbandfilter.h index 6b482d2b2..b51189344 100644 --- a/sdrbase/dsp/inthalfbandfilter.h +++ b/sdrbase/dsp/inthalfbandfilter.h @@ -21,7 +21,7 @@ #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "util/export.h" +#include "export.h" template class SDRBASE_API IntHalfbandFilter { diff --git a/sdrbase/dsp/inthalfbandfilterdb.h b/sdrbase/dsp/inthalfbandfilterdb.h index f3bb13f53..a4b00bd8f 100644 --- a/sdrbase/dsp/inthalfbandfilterdb.h +++ b/sdrbase/dsp/inthalfbandfilterdb.h @@ -24,7 +24,7 @@ #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "util/export.h" +#include "export.h" template class SDRBASE_API IntHalfbandFilterDB { diff --git a/sdrbase/dsp/inthalfbandfilterdbf.h b/sdrbase/dsp/inthalfbandfilterdbf.h index 9b40edc40..5dc7bad7a 100644 --- a/sdrbase/dsp/inthalfbandfilterdbf.h +++ b/sdrbase/dsp/inthalfbandfilterdbf.h @@ -24,7 +24,7 @@ #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "util/export.h" +#include "export.h" template class SDRBASE_API IntHalfbandFilterDBF { diff --git a/sdrbase/dsp/inthalfbandfiltereo1.h b/sdrbase/dsp/inthalfbandfiltereo1.h index 4d627bed6..e76f3a871 100644 --- a/sdrbase/dsp/inthalfbandfiltereo1.h +++ b/sdrbase/dsp/inthalfbandfiltereo1.h @@ -27,7 +27,7 @@ #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" #include "dsp/inthalfbandfiltereo1i.h" -#include "util/export.h" +#include "export.h" template class SDRBASE_API IntHalfbandFilterEO1 { diff --git a/sdrbase/dsp/inthalfbandfilterst.h b/sdrbase/dsp/inthalfbandfilterst.h index 94336a4fd..b940675e0 100644 --- a/sdrbase/dsp/inthalfbandfilterst.h +++ b/sdrbase/dsp/inthalfbandfilterst.h @@ -25,7 +25,7 @@ #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" #include "dsp/inthalfbandfiltersti.h" -#include "util/export.h" +#include "export.h" template class SDRANGEL_API IntHalfbandFilterST { diff --git a/sdrbase/dsp/kissengine.h b/sdrbase/dsp/kissengine.h index 83d029745..0b8faba38 100644 --- a/sdrbase/dsp/kissengine.h +++ b/sdrbase/dsp/kissengine.h @@ -3,7 +3,7 @@ #include "dsp/fftengine.h" #include "dsp/kissfft.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API KissEngine : public FFTEngine { public: diff --git a/sdrbase/dsp/nco.h b/sdrbase/dsp/nco.h index 735a7f70a..cea728650 100644 --- a/sdrbase/dsp/nco.h +++ b/sdrbase/dsp/nco.h @@ -19,7 +19,7 @@ #define INCLUDE_NCO_H #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API NCO { private: diff --git a/sdrbase/dsp/ncof.h b/sdrbase/dsp/ncof.h index d07064e56..eee258242 100644 --- a/sdrbase/dsp/ncof.h +++ b/sdrbase/dsp/ncof.h @@ -18,7 +18,7 @@ #define INCLUDE_NCOF_H #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API NCOF { private: diff --git a/sdrbase/dsp/nullsink.h b/sdrbase/dsp/nullsink.h index 0bdbe76a2..2e26af373 100644 --- a/sdrbase/dsp/nullsink.h +++ b/sdrbase/dsp/nullsink.h @@ -2,7 +2,7 @@ #define INCLUDE_NULLSINK_H #include -#include "util/export.h" +#include "export.h" class Message; diff --git a/sdrbase/dsp/phaselock.h b/sdrbase/dsp/phaselock.h index 47e0ba692..06196c5e7 100644 --- a/sdrbase/dsp/phaselock.h +++ b/sdrbase/dsp/phaselock.h @@ -17,7 +17,7 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" /** Phase-locked loop mainly for broadcadt FM stereo pilot. */ class SDRBASE_API PhaseLock diff --git a/sdrbase/dsp/recursivefilters.h b/sdrbase/dsp/recursivefilters.h index 68f4b0448..1c457df22 100644 --- a/sdrbase/dsp/recursivefilters.h +++ b/sdrbase/dsp/recursivefilters.h @@ -17,7 +17,7 @@ #ifndef SDRBASE_DSP_RECURSIVEFILTERS_H_ #define SDRBASE_DSP_RECURSIVEFILTERS_H_ -#include "util/export.h" +#include "export.h" /** * \Brief: This is a second order bandpass filter using recursive method. r is in range ]0..1[ the higher the steeper the filter. diff --git a/sdrbase/dsp/samplesinkfifo.h b/sdrbase/dsp/samplesinkfifo.h index cdead7be5..1855a6d40 100644 --- a/sdrbase/dsp/samplesinkfifo.h +++ b/sdrbase/dsp/samplesinkfifo.h @@ -22,7 +22,7 @@ #include #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API SampleSinkFifo : public QObject { Q_OBJECT diff --git a/sdrbase/dsp/samplesinkfifodoublebuffered.h b/sdrbase/dsp/samplesinkfifodoublebuffered.h index d7421e512..aa37ac0a4 100644 --- a/sdrbase/dsp/samplesinkfifodoublebuffered.h +++ b/sdrbase/dsp/samplesinkfifodoublebuffered.h @@ -21,7 +21,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" #include "dsp/dsptypes.h" class SDRBASE_API SampleSinkFifoDoubleBuffered : public QObject { diff --git a/sdrbase/dsp/samplesourcefifo.h b/sdrbase/dsp/samplesourcefifo.h index 0f4f2e108..f856a631f 100644 --- a/sdrbase/dsp/samplesourcefifo.h +++ b/sdrbase/dsp/samplesourcefifo.h @@ -21,7 +21,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" #include "dsp/dsptypes.h" class SDRBASE_API SampleSourceFifo : public QObject { diff --git a/sdrbase/dsp/threadedbasebandsamplesink.h b/sdrbase/dsp/threadedbasebandsamplesink.h index 727d03903..0cd72b9a2 100644 --- a/sdrbase/dsp/threadedbasebandsamplesink.h +++ b/sdrbase/dsp/threadedbasebandsamplesink.h @@ -23,7 +23,7 @@ #include "samplesinkfifo.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" class BasebandSampleSink; class QThread; diff --git a/sdrbase/dsp/threadedbasebandsamplesource.h b/sdrbase/dsp/threadedbasebandsamplesource.h index e22673c97..0fa838516 100644 --- a/sdrbase/dsp/threadedbasebandsamplesource.h +++ b/sdrbase/dsp/threadedbasebandsamplesource.h @@ -22,7 +22,7 @@ #include "dsp/basebandsamplesource.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" class BasebandSampleSource; class QThread; diff --git a/sdrbase/dsp/upchannelizer.h b/sdrbase/dsp/upchannelizer.h index 5d16b2c00..63698f334 100644 --- a/sdrbase/dsp/upchannelizer.h +++ b/sdrbase/dsp/upchannelizer.h @@ -21,7 +21,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" #include "util/message.h" #ifdef USE_SSE4_1 #include "dsp/inthalfbandfiltereo1.h" diff --git a/sdrbase/dsp/wfir.h b/sdrbase/dsp/wfir.h index d01a46893..d0225cf83 100644 --- a/sdrbase/dsp/wfir.h +++ b/sdrbase/dsp/wfir.h @@ -53,7 +53,7 @@ #ifndef _WFIR_H_ #define _WFIR_H_ -#include "util/export.h" +#include "export.h" class SDRBASE_API WFIR { diff --git a/sdrbase/mainparser.h b/sdrbase/mainparser.h index 0112d3079..14c5921e0 100644 --- a/sdrbase/mainparser.h +++ b/sdrbase/mainparser.h @@ -21,7 +21,7 @@ #include #include -#include "util/export.h" +#include "export.h" class SDRBASE_API MainParser { diff --git a/sdrbase/plugin/pluginapi.h b/sdrbase/plugin/pluginapi.h index 6e72ca989..3f9206014 100644 --- a/sdrbase/plugin/pluginapi.h +++ b/sdrbase/plugin/pluginapi.h @@ -4,7 +4,7 @@ #include #include -#include "util/export.h" +#include "export.h" #include "plugin/plugininterface.h" class QString; diff --git a/sdrbase/plugin/plugininstancegui.h b/sdrbase/plugin/plugininstancegui.h index af8ccc59f..22cb59512 100644 --- a/sdrbase/plugin/plugininstancegui.h +++ b/sdrbase/plugin/plugininstancegui.h @@ -5,7 +5,7 @@ #include #include -#include "util/export.h" +#include "export.h" class Message; class MessageQueue; diff --git a/sdrbase/plugin/plugininterface.h b/sdrbase/plugin/plugininterface.h index 57dec2139..64d73793d 100644 --- a/sdrbase/plugin/plugininterface.h +++ b/sdrbase/plugin/plugininterface.h @@ -4,7 +4,7 @@ #include #include -#include "util/export.h" +#include "export.h" struct SDRBASE_API PluginDescriptor { // general plugin description diff --git a/sdrbase/plugin/pluginmanager.h b/sdrbase/plugin/pluginmanager.h index e202f5856..fafb25c34 100644 --- a/sdrbase/plugin/pluginmanager.h +++ b/sdrbase/plugin/pluginmanager.h @@ -9,7 +9,7 @@ #include "plugin/plugininterface.h" #include "plugin/pluginapi.h" -#include "util/export.h" +#include "export.h" class QComboBox; class QPluginLoader; diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index a2d0c640b..1a6c4d3b3 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -193,7 +193,7 @@ HEADERS += audio/audiodeviceinfo.h\ settings/mainsettings.h\ util/CRC64.h\ util/db.h\ - util/export.h\ + export.h\ util/message.h\ util/messagequeue.h\ util/prettyprint.h\ diff --git a/sdrbase/settings/mainsettings.h b/sdrbase/settings/mainsettings.h index 8734d81f5..b348462a4 100644 --- a/sdrbase/settings/mainsettings.h +++ b/sdrbase/settings/mainsettings.h @@ -5,7 +5,7 @@ #include "preferences.h" #include "preset.h" #include "audio/audiodeviceinfo.h" -#include "util/export.h" +#include "export.h" class Command; diff --git a/sdrbase/settings/preferences.h b/sdrbase/settings/preferences.h index ef7461ddb..5fef43012 100644 --- a/sdrbase/settings/preferences.h +++ b/sdrbase/settings/preferences.h @@ -3,7 +3,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRBASE_API Preferences { public: diff --git a/sdrbase/settings/preset.h b/sdrbase/settings/preset.h index 390fd63da..26be52469 100644 --- a/sdrbase/settings/preset.h +++ b/sdrbase/settings/preset.h @@ -5,7 +5,7 @@ #include #include -#include "util/export.h" +#include "export.h" class SDRBASE_API Preset { public: diff --git a/sdrbase/util/CRC64.h b/sdrbase/util/CRC64.h index 26924252a..525efa626 100644 --- a/sdrbase/util/CRC64.h +++ b/sdrbase/util/CRC64.h @@ -19,7 +19,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRBASE_API CRC64 { diff --git a/sdrbase/util/db.h b/sdrbase/util/db.h index fb08bcc41..93ff09130 100644 --- a/sdrbase/util/db.h +++ b/sdrbase/util/db.h @@ -18,7 +18,7 @@ #define INCLUDE_UTIL_DB_H_ #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API CalcDb { diff --git a/sdrbase/util/fixedtraits.h b/sdrbase/util/fixedtraits.h index 85c1327d5..0a8f38c18 100644 --- a/sdrbase/util/fixedtraits.h +++ b/sdrbase/util/fixedtraits.h @@ -20,7 +20,7 @@ #include -#include "util/export.h" +#include "export.h" template class FixedTraits diff --git a/sdrbase/util/message.h b/sdrbase/util/message.h index bbdeca69a..9dbb99e1c 100644 --- a/sdrbase/util/message.h +++ b/sdrbase/util/message.h @@ -19,7 +19,7 @@ #define INCLUDE_MESSAGE_H #include -#include "util/export.h" +#include "export.h" class SDRBASE_API Message { public: diff --git a/sdrbase/util/messagequeue.h b/sdrbase/util/messagequeue.h index 1301a13e6..860be2a02 100644 --- a/sdrbase/util/messagequeue.h +++ b/sdrbase/util/messagequeue.h @@ -21,7 +21,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" class Message; diff --git a/sdrbase/util/prettyprint.h b/sdrbase/util/prettyprint.h index 74002f86f..056ceb7f4 100644 --- a/sdrbase/util/prettyprint.h +++ b/sdrbase/util/prettyprint.h @@ -19,7 +19,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRBASE_API EscapeColors { diff --git a/sdrbase/util/rtpsink.h b/sdrbase/util/rtpsink.h index 0c4666a88..9ef8afc3f 100644 --- a/sdrbase/util/rtpsink.h +++ b/sdrbase/util/rtpsink.h @@ -31,7 +31,7 @@ #include "rtpsessionparams.h" #include "rtperrors.h" -#include "util/export.h" +#include "export.h" class QUdpSocket; diff --git a/sdrbase/util/samplesourceserializer.h b/sdrbase/util/samplesourceserializer.h index c303b7f38..920789ede 100644 --- a/sdrbase/util/samplesourceserializer.h +++ b/sdrbase/util/samplesourceserializer.h @@ -18,7 +18,7 @@ #define INCLUDE_UTIL_SAMPLESOURCESERIALIZER_H_ #include "util/simpleserializer.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API SampleSourceSerializer { diff --git a/sdrbase/util/simpleserializer.h b/sdrbase/util/simpleserializer.h index 24a026856..ca303b29a 100644 --- a/sdrbase/util/simpleserializer.h +++ b/sdrbase/util/simpleserializer.h @@ -4,7 +4,7 @@ #include #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" class SDRBASE_API SimpleSerializer { public: diff --git a/sdrbase/util/spinlock.h b/sdrbase/util/spinlock.h index 969a511ac..974339f48 100644 --- a/sdrbase/util/spinlock.h +++ b/sdrbase/util/spinlock.h @@ -3,7 +3,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRBASE_API Spinlock { public: diff --git a/sdrbase/util/syncmessenger.h b/sdrbase/util/syncmessenger.h index 0c6ed44f8..55f9b0a58 100644 --- a/sdrbase/util/syncmessenger.h +++ b/sdrbase/util/syncmessenger.h @@ -22,7 +22,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" class Message; diff --git a/sdrbase/util/uid.h b/sdrbase/util/uid.h index d612e7e51..5884fa6df 100644 --- a/sdrbase/util/uid.h +++ b/sdrbase/util/uid.h @@ -21,7 +21,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRBASE_API UidCalculator { diff --git a/sdrbase/webapi/webapiadapterinterface.h b/sdrbase/webapi/webapiadapterinterface.h index a7dd4a5dc..6695cb33e 100644 --- a/sdrbase/webapi/webapiadapterinterface.h +++ b/sdrbase/webapi/webapiadapterinterface.h @@ -24,7 +24,7 @@ #include "SWGErrorResponse.h" -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index f4730362c..42296a15d 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -27,7 +27,7 @@ #include "staticfilecontroller.h" #include "webapiadapterinterface.h" -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { diff --git a/sdrbase/webapi/webapiserver.h b/sdrbase/webapi/webapiserver.h index 1ae21efc0..43f5814ab 100644 --- a/sdrbase/webapi/webapiserver.h +++ b/sdrbase/webapi/webapiserver.h @@ -19,7 +19,7 @@ #ifndef SDRBASE_WEBAPI_WEBAPISERVER_H_ #define SDRBASE_WEBAPI_WEBAPISERVER_H_ -#include "util/export.h" +#include "export.h" namespace qtwebapp { diff --git a/sdrgui/device/deviceuiset.h b/sdrgui/device/deviceuiset.h index d38118795..2b5b38030 100644 --- a/sdrgui/device/deviceuiset.h +++ b/sdrgui/device/deviceuiset.h @@ -20,7 +20,7 @@ #include #include -#include "util/export.h" +#include "export.h" class SpectrumVis; class GLSpectrum; diff --git a/sdrgui/dsp/scopevis.h b/sdrgui/dsp/scopevis.h index 2520a4684..0c08e59bd 100644 --- a/sdrgui/dsp/scopevis.h +++ b/sdrgui/dsp/scopevis.h @@ -3,7 +3,7 @@ #include #include -#include "util/export.h" +#include "export.h" #include "util/message.h" class GLScope; diff --git a/sdrgui/dsp/scopevismulti.h b/sdrgui/dsp/scopevismulti.h index 28c42c43f..8f8447a59 100644 --- a/sdrgui/dsp/scopevismulti.h +++ b/sdrgui/dsp/scopevismulti.h @@ -30,7 +30,7 @@ #include #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include "util/message.h" #include "util/messagequeue.h" #include "util/doublebuffer.h" diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index 1c723a9ad..dc5ba22bd 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -29,7 +29,7 @@ #include #include "dsp/dsptypes.h" #include "dsp/basebandsamplesink.h" -#include "util/export.h" +#include "export.h" #include "util/message.h" #include "util/doublebuffer.h" diff --git a/sdrgui/dsp/scopevisxy.h b/sdrgui/dsp/scopevisxy.h index 6a70f508c..640b3e7bf 100644 --- a/sdrgui/dsp/scopevisxy.h +++ b/sdrgui/dsp/scopevisxy.h @@ -19,7 +19,7 @@ #define SDRGUI_DSP_SCOPEVISXY_H_ #include "dsp/basebandsamplesink.h" -#include "util/export.h" +#include "export.h" #include "util/message.h" #include diff --git a/sdrgui/dsp/spectrumscopecombovis.h b/sdrgui/dsp/spectrumscopecombovis.h index c9b5ababc..6e7b6f612 100644 --- a/sdrgui/dsp/spectrumscopecombovis.h +++ b/sdrgui/dsp/spectrumscopecombovis.h @@ -4,7 +4,7 @@ #include #include "dsp/spectrumvis.h" #include "dsp/scopevis.h" -#include "util/export.h" +#include "export.h" class Message; diff --git a/sdrgui/dsp/spectrumscopengcombovis.h b/sdrgui/dsp/spectrumscopengcombovis.h index 822db1f25..24ee8ce37 100644 --- a/sdrgui/dsp/spectrumscopengcombovis.h +++ b/sdrgui/dsp/spectrumscopengcombovis.h @@ -4,7 +4,7 @@ #include #include "dsp/spectrumvis.h" #include "dsp/scopevisng.h" -#include "util/export.h" +#include "export.h" class Message; diff --git a/sdrgui/dsp/spectrumvis.h b/sdrgui/dsp/spectrumvis.h index 92abd42df..2a7279e8d 100644 --- a/sdrgui/dsp/spectrumvis.h +++ b/sdrgui/dsp/spectrumvis.h @@ -5,7 +5,7 @@ #include #include "dsp/fftengine.h" #include "dsp/fftwindow.h" -#include "util/export.h" +#include "export.h" #include "util/message.h" class GLSpectrum; diff --git a/sdrgui/gui/aboutdialog.h b/sdrgui/gui/aboutdialog.h index 204eda624..acddff7d8 100644 --- a/sdrgui/gui/aboutdialog.h +++ b/sdrgui/gui/aboutdialog.h @@ -3,7 +3,7 @@ #include -#include "util/export.h" +#include "export.h" namespace Ui { class AboutDialog; diff --git a/sdrgui/gui/addpresetdialog.h b/sdrgui/gui/addpresetdialog.h index 3f7abdebf..f3595aff2 100644 --- a/sdrgui/gui/addpresetdialog.h +++ b/sdrgui/gui/addpresetdialog.h @@ -3,7 +3,7 @@ #include -#include "util/export.h" +#include "export.h" namespace Ui { class AddPresetDialog; diff --git a/sdrgui/gui/audiodialog.h b/sdrgui/gui/audiodialog.h index 1a2b54756..bd0c51022 100644 --- a/sdrgui/gui/audiodialog.h +++ b/sdrgui/gui/audiodialog.h @@ -3,7 +3,7 @@ #include -#include "util/export.h" +#include "export.h" class AudioDeviceInfo; diff --git a/sdrgui/gui/basicchannelsettingsdialog.h b/sdrgui/gui/basicchannelsettingsdialog.h index 2185980f4..4276a4c23 100644 --- a/sdrgui/gui/basicchannelsettingsdialog.h +++ b/sdrgui/gui/basicchannelsettingsdialog.h @@ -3,7 +3,7 @@ #include -#include "util/export.h" +#include "export.h" namespace Ui { class BasicChannelSettingsDialog; diff --git a/sdrgui/gui/buttonswitch.h b/sdrgui/gui/buttonswitch.h index d116b3e72..ef108438a 100644 --- a/sdrgui/gui/buttonswitch.h +++ b/sdrgui/gui/buttonswitch.h @@ -3,7 +3,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRGUI_API ButtonSwitch : public QToolButton { Q_OBJECT diff --git a/sdrgui/gui/channelwindow.h b/sdrgui/gui/channelwindow.h index 20c1521d0..2a3dd7566 100644 --- a/sdrgui/gui/channelwindow.h +++ b/sdrgui/gui/channelwindow.h @@ -3,7 +3,7 @@ #include -#include "util/export.h" +#include "export.h" class QBoxLayout; class QSpacerItem; diff --git a/sdrgui/gui/clickablelabel.h b/sdrgui/gui/clickablelabel.h index 41769c499..1c8bae13a 100644 --- a/sdrgui/gui/clickablelabel.h +++ b/sdrgui/gui/clickablelabel.h @@ -21,7 +21,7 @@ #include #include -#include "util/export.h" +#include "export.h" class SDRGUI_API ClickableLabel : public QLabel { diff --git a/sdrgui/gui/colormapper.h b/sdrgui/gui/colormapper.h index 50cb93241..ff13244dd 100644 --- a/sdrgui/gui/colormapper.h +++ b/sdrgui/gui/colormapper.h @@ -10,7 +10,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" class SDRGUI_API ColorMapper { diff --git a/sdrgui/gui/commanditem.h b/sdrgui/gui/commanditem.h index acf17398e..4cb56fa69 100644 --- a/sdrgui/gui/commanditem.h +++ b/sdrgui/gui/commanditem.h @@ -16,7 +16,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRGUI_API CommandItem : public QTreeWidgetItem { public: diff --git a/sdrgui/gui/commandkeyreceiver.h b/sdrgui/gui/commandkeyreceiver.h index 9b5ed3be6..27299aa6d 100644 --- a/sdrgui/gui/commandkeyreceiver.h +++ b/sdrgui/gui/commandkeyreceiver.h @@ -19,7 +19,7 @@ #include -#include "util/export.h" +#include "export.h" class QKeyEvent; diff --git a/sdrgui/gui/commandoutputdialog.h b/sdrgui/gui/commandoutputdialog.h index dc6f5e406..e702b9728 100644 --- a/sdrgui/gui/commandoutputdialog.h +++ b/sdrgui/gui/commandoutputdialog.h @@ -20,7 +20,7 @@ #include #include -#include "util/export.h" +#include "export.h" namespace Ui { class CommandOutputDialog; diff --git a/sdrgui/gui/cwkeyergui.h b/sdrgui/gui/cwkeyergui.h index d55d6982f..a9d273c5b 100644 --- a/sdrgui/gui/cwkeyergui.h +++ b/sdrgui/gui/cwkeyergui.h @@ -20,7 +20,7 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include "settings/serializable.h" namespace Ui { diff --git a/sdrgui/gui/editcommanddialog.h b/sdrgui/gui/editcommanddialog.h index 4f957c1a3..4aa57e5b3 100644 --- a/sdrgui/gui/editcommanddialog.h +++ b/sdrgui/gui/editcommanddialog.h @@ -20,7 +20,7 @@ #include #include -#include "util/export.h" +#include "export.h" namespace Ui { class EditCommandDialog; diff --git a/sdrgui/gui/externalclockbutton.h b/sdrgui/gui/externalclockbutton.h index 98598b906..bb531b3f2 100644 --- a/sdrgui/gui/externalclockbutton.h +++ b/sdrgui/gui/externalclockbutton.h @@ -23,7 +23,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRGUI_API ExternalClockButton : public QPushButton { Q_OBJECT diff --git a/sdrgui/gui/externalclockdialog.h b/sdrgui/gui/externalclockdialog.h index f77d77387..76d4ebcef 100644 --- a/sdrgui/gui/externalclockdialog.h +++ b/sdrgui/gui/externalclockdialog.h @@ -23,7 +23,7 @@ #include -#include "util/export.h" +#include "export.h" namespace Ui { class ExternalClockDialog; diff --git a/sdrgui/gui/glscope.h b/sdrgui/gui/glscope.h index 558705823..6e0e1ce30 100644 --- a/sdrgui/gui/glscope.h +++ b/sdrgui/gui/glscope.h @@ -32,7 +32,7 @@ #include "gui/scaleengine.h" #include "gui/glshadersimple.h" #include "gui/glshadertextured.h" -#include "util/export.h" +#include "export.h" #include "util/bitfieldindex.h" #include "util/incrementalarray.h" diff --git a/sdrgui/gui/glscopegui.h b/sdrgui/gui/glscopegui.h index 982f0bd3c..6b1c6d5aa 100644 --- a/sdrgui/gui/glscopegui.h +++ b/sdrgui/gui/glscopegui.h @@ -3,7 +3,7 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include "util/message.h" #include "dsp/scopevis.h" #include "settings/serializable.h" diff --git a/sdrgui/gui/glscopemulti.h b/sdrgui/gui/glscopemulti.h index 5a3cac400..4991b48eb 100644 --- a/sdrgui/gui/glscopemulti.h +++ b/sdrgui/gui/glscopemulti.h @@ -29,7 +29,7 @@ #include "gui/scaleengine.h" #include "gui/glshadersimple.h" #include "gui/glshadertextured.h" -#include "util/export.h" +#include "export.h" #include "util/bitfieldindex.h" #include "util/incrementalarray.h" diff --git a/sdrgui/gui/glscopemultigui.h b/sdrgui/gui/glscopemultigui.h index cdae7726f..291d1ca9b 100644 --- a/sdrgui/gui/glscopemultigui.h +++ b/sdrgui/gui/glscopemultigui.h @@ -22,7 +22,7 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include "util/message.h" #include "dsp/scopevismulti.h" diff --git a/sdrgui/gui/glscopeng.h b/sdrgui/gui/glscopeng.h index 317ddb522..929afe5b4 100644 --- a/sdrgui/gui/glscopeng.h +++ b/sdrgui/gui/glscopeng.h @@ -29,7 +29,7 @@ #include "gui/scaleengine.h" #include "gui/glshadersimple.h" #include "gui/glshadertextured.h" -#include "util/export.h" +#include "export.h" #include "util/bitfieldindex.h" #include "util/incrementalarray.h" diff --git a/sdrgui/gui/glscopenggui.h b/sdrgui/gui/glscopenggui.h index f82d9f90b..43f6ad660 100644 --- a/sdrgui/gui/glscopenggui.h +++ b/sdrgui/gui/glscopenggui.h @@ -22,7 +22,7 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include "util/message.h" #include "dsp/scopevisng.h" #include "settings/serializable.h" diff --git a/sdrgui/gui/glshadersimple.h b/sdrgui/gui/glshadersimple.h index 929d17f03..583e95c15 100644 --- a/sdrgui/gui/glshadersimple.h +++ b/sdrgui/gui/glshadersimple.h @@ -21,7 +21,7 @@ #include #include -#include "util/export.h" +#include "export.h" class QOpenGLShaderProgram; class QMatrix4x4; diff --git a/sdrgui/gui/glshadertextured.h b/sdrgui/gui/glshadertextured.h index 59d411fdd..dbbb92ae5 100644 --- a/sdrgui/gui/glshadertextured.h +++ b/sdrgui/gui/glshadertextured.h @@ -25,7 +25,7 @@ #include #include -#include "util/export.h" +#include "export.h" class QOpenGLShaderProgram; class QMatrix4x4; diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index 57c7a7456..3b2ef97ac 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -32,7 +32,7 @@ #include "gui/glshadersimple.h" #include "gui/glshadertextured.h" #include "dsp/channelmarker.h" -#include "util/export.h" +#include "export.h" #include "util/incrementalarray.h" class QOpenGLShaderProgram; diff --git a/sdrgui/gui/glspectrumgui.h b/sdrgui/gui/glspectrumgui.h index 3ee2cf481..69765777e 100644 --- a/sdrgui/gui/glspectrumgui.h +++ b/sdrgui/gui/glspectrumgui.h @@ -3,7 +3,7 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include "settings/serializable.h" namespace Ui { diff --git a/sdrgui/gui/indicator.h b/sdrgui/gui/indicator.h index ee9aa75f9..ba5cfe896 100644 --- a/sdrgui/gui/indicator.h +++ b/sdrgui/gui/indicator.h @@ -19,7 +19,7 @@ #define INCLUDE_INDICATOR_H #include -#include "util/export.h" +#include "export.h" class SDRGUI_API Indicator : public QWidget { private: diff --git a/sdrgui/gui/levelmeter.h b/sdrgui/gui/levelmeter.h index e7f6084ca..e4927e68d 100644 --- a/sdrgui/gui/levelmeter.h +++ b/sdrgui/gui/levelmeter.h @@ -52,7 +52,7 @@ #include "dsp/dsptypes.h" #include "gui/scaleengine.h" -#include "util/export.h" +#include "export.h" /** * Widget which displays a vertical audio level meter, indicating the diff --git a/sdrgui/gui/loggingdialog.h b/sdrgui/gui/loggingdialog.h index e5e016f2b..93b54c777 100644 --- a/sdrgui/gui/loggingdialog.h +++ b/sdrgui/gui/loggingdialog.h @@ -20,7 +20,7 @@ #include #include "settings/mainsettings.h" -#include "util/export.h" +#include "export.h" namespace Ui { class LoggingDialog; diff --git a/sdrgui/gui/mypositiondialog.h b/sdrgui/gui/mypositiondialog.h index c4f834f76..29f152482 100644 --- a/sdrgui/gui/mypositiondialog.h +++ b/sdrgui/gui/mypositiondialog.h @@ -23,7 +23,7 @@ #include #include "settings/mainsettings.h" -#include "util/export.h" +#include "export.h" namespace Ui { class MyPositionDialog; diff --git a/sdrgui/gui/pluginsdialog.h b/sdrgui/gui/pluginsdialog.h index 591d51127..ef1b7439a 100644 --- a/sdrgui/gui/pluginsdialog.h +++ b/sdrgui/gui/pluginsdialog.h @@ -3,7 +3,7 @@ #include #include "plugin/pluginmanager.h" -#include "util/export.h" +#include "export.h" namespace Ui { class PluginsDialog; diff --git a/sdrgui/gui/presetitem.h b/sdrgui/gui/presetitem.h index 8735b071f..ad011f546 100644 --- a/sdrgui/gui/presetitem.h +++ b/sdrgui/gui/presetitem.h @@ -17,7 +17,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRGUI_API PresetItem : public QTreeWidgetItem { public: diff --git a/sdrgui/gui/rollupwidget.h b/sdrgui/gui/rollupwidget.h index d5f746b08..377a32058 100644 --- a/sdrgui/gui/rollupwidget.h +++ b/sdrgui/gui/rollupwidget.h @@ -2,7 +2,7 @@ #define INCLUDE_ROLLUPWIDGET_H #include -#include "util/export.h" +#include "export.h" class SDRGUI_API RollupWidget : public QWidget { Q_OBJECT diff --git a/sdrgui/gui/samplingdevicecontrol.h b/sdrgui/gui/samplingdevicecontrol.h index 260ad9cc1..b491755f2 100644 --- a/sdrgui/gui/samplingdevicecontrol.h +++ b/sdrgui/gui/samplingdevicecontrol.h @@ -22,7 +22,7 @@ #include #include -#include "util/export.h" +#include "export.h" namespace Ui { class SamplingDeviceControl; diff --git a/sdrgui/gui/samplingdevicedialog.h b/sdrgui/gui/samplingdevicedialog.h index 059a1ac51..590fcf7d0 100644 --- a/sdrgui/gui/samplingdevicedialog.h +++ b/sdrgui/gui/samplingdevicedialog.h @@ -24,7 +24,7 @@ #include #include -#include "util/export.h" +#include "export.h" namespace Ui { class SamplingDeviceDialog; diff --git a/sdrgui/gui/scaleengine.h b/sdrgui/gui/scaleengine.h index 41874d1f4..cdfde9c3b 100644 --- a/sdrgui/gui/scaleengine.h +++ b/sdrgui/gui/scaleengine.h @@ -22,7 +22,7 @@ #include #include #include "physicalunit.h" -#include "util/export.h" +#include "export.h" class SDRGUI_API ScaleEngine { public: diff --git a/sdrgui/gui/tickedslider.h b/sdrgui/gui/tickedslider.h index 67b33b2d5..b788011e4 100644 --- a/sdrgui/gui/tickedslider.h +++ b/sdrgui/gui/tickedslider.h @@ -22,7 +22,7 @@ #include #include -#include "util/export.h" +#include "export.h" class SDRGUI_API TickedSlider : public QSlider { diff --git a/sdrgui/gui/transverterbutton.h b/sdrgui/gui/transverterbutton.h index c4b19a9fc..09ec2d607 100644 --- a/sdrgui/gui/transverterbutton.h +++ b/sdrgui/gui/transverterbutton.h @@ -23,7 +23,7 @@ #include -#include "util/export.h" +#include "export.h" class SDRGUI_API TransverterButton : public QPushButton { Q_OBJECT diff --git a/sdrgui/gui/transverterdialog.h b/sdrgui/gui/transverterdialog.h index 892f0c058..3baf7eb6b 100644 --- a/sdrgui/gui/transverterdialog.h +++ b/sdrgui/gui/transverterdialog.h @@ -23,7 +23,7 @@ #include -#include "util/export.h" +#include "export.h" namespace Ui { class TransverterDialog; diff --git a/sdrgui/gui/tvscreen.h b/sdrgui/gui/tvscreen.h index c9969a475..54a383e03 100644 --- a/sdrgui/gui/tvscreen.h +++ b/sdrgui/gui/tvscreen.h @@ -30,7 +30,7 @@ #include "dsp/dsptypes.h" #include "glshadertextured.h" #include "glshadertvarray.h" -#include "util/export.h" +#include "export.h" #include "util/bitfieldindex.h" class QPainter; diff --git a/sdrgui/gui/valuedial.h b/sdrgui/gui/valuedial.h index 6c9e020bf..c752269cc 100644 --- a/sdrgui/gui/valuedial.h +++ b/sdrgui/gui/valuedial.h @@ -18,7 +18,7 @@ #include #include #include "gui/colormapper.h" -#include "util/export.h" +#include "export.h" class SDRGUI_API ValueDial : public QWidget { Q_OBJECT diff --git a/sdrgui/gui/valuedialz.h b/sdrgui/gui/valuedialz.h index c313d550f..26a8625c1 100644 --- a/sdrgui/gui/valuedialz.h +++ b/sdrgui/gui/valuedialz.h @@ -21,7 +21,7 @@ #include #include #include "gui/colormapper.h" -#include "util/export.h" +#include "export.h" class SDRGUI_API ValueDialZ : public QWidget { Q_OBJECT diff --git a/sdrgui/mainwindow.h b/sdrgui/mainwindow.h index b4785d5c7..11700ffd0 100644 --- a/sdrgui/mainwindow.h +++ b/sdrgui/mainwindow.h @@ -25,7 +25,7 @@ #include "settings/mainsettings.h" #include "util/message.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" #include "mainparser.h" class QLabel; diff --git a/sdrgui/webapi/webapiadaptergui.h b/sdrgui/webapi/webapiadaptergui.h index 7edc56a98..c2200e021 100644 --- a/sdrgui/webapi/webapiadaptergui.h +++ b/sdrgui/webapi/webapiadaptergui.h @@ -22,7 +22,7 @@ #include #include "webapi/webapiadapterinterface.h" -#include "util/export.h" +#include "export.h" class MainWindow; diff --git a/sdrsrv/maincore.h b/sdrsrv/maincore.h index 34841682e..3b8b6d1b2 100644 --- a/sdrsrv/maincore.h +++ b/sdrsrv/maincore.h @@ -25,7 +25,7 @@ #include "settings/mainsettings.h" #include "util/message.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" #include "mainparser.h" class AudioDeviceInfo; From e6eecf3e28028c2829b46408036115ec0982dd4f Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 20 Mar 2018 20:14:04 +0100 Subject: [PATCH 142/956] Web API: use MSVC export prefix in swagger library --- exports/export.h | 12 ++++++++++++ sdrbase/resources/webapi/doc/html2/index.html | 2 +- swagger/CMakeLists.txt | 1 + swagger/sdrangel/code/html2/index.html | 2 +- swagger/sdrangel/code/qt5/client/SWGAudioDevice.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGAudioDevices.h | 3 ++- .../sdrangel/code/qt5/client/SWGAudioDevicesSelect.h | 3 ++- .../sdrangel/code/qt5/client/SWGCWKeyerSettings.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGChannel.h | 3 ++- .../sdrangel/code/qt5/client/SWGChannelListItem.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGChannelReport.h | 3 ++- .../sdrangel/code/qt5/client/SWGChannelSettings.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGDeviceSet.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGDeviceState.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGErrorResponse.h | 3 ++- .../sdrangel/code/qt5/client/SWGFileSourceSettings.h | 3 ++- .../code/qt5/client/SWGHackRFInputSettings.h | 3 ++- .../code/qt5/client/SWGHackRFOutputSettings.h | 3 ++- .../code/qt5/client/SWGInstanceChannelsResponse.h | 3 ++- .../code/qt5/client/SWGInstanceDevicesResponse.h | 3 ++- .../code/qt5/client/SWGInstanceSummaryResponse.h | 3 ++- .../code/qt5/client/SWGLimeSdrInputSettings.h | 3 ++- .../code/qt5/client/SWGLimeSdrOutputSettings.h | 3 ++- .../code/qt5/client/SWGLocationInformation.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h | 3 ++- .../sdrangel/code/qt5/client/SWGNFMDemodSettings.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGNFMModReport.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGPresetExport.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGPresetGroup.h | 3 ++- .../sdrangel/code/qt5/client/SWGPresetIdentifier.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGPresetImport.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGPresetItem.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGPresets.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h | 3 ++- swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h | 3 ++- .../sdrangel/code/qt5/client/SWGSuccessResponse.h | 3 ++- 45 files changed, 97 insertions(+), 43 deletions(-) diff --git a/exports/export.h b/exports/export.h index dea1f929c..10b79fea5 100644 --- a/exports/export.h +++ b/exports/export.h @@ -102,4 +102,16 @@ # define QRTPLIB_API #endif +/* the 'SWG_API' controls the import/export of 'swagger' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef swagger_EXPORTS +# define SWG_API __SDR_EXPORT +# else +# define SWG_API __SDR_IMPORT +# endif +#else +# define SWG_API +#endif + #endif /* __SDRANGEL_EXPORT_H */ diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index b1a78b888..2b183443d 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -17955,7 +17955,7 @@ except ApiException as e:
    - Generated 2018-03-19T00:19:38.769+01:00 + Generated 2018-03-20T20:09:28.335+01:00
    diff --git a/swagger/CMakeLists.txt b/swagger/CMakeLists.txt index 78b5c16f6..c906a88c8 100644 --- a/swagger/CMakeLists.txt +++ b/swagger/CMakeLists.txt @@ -21,6 +21,7 @@ add_library(swagger SHARED include_directories( ${CMAKE_CURRENT_BINARY_DIR} . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index b1a78b888..2b183443d 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -17955,7 +17955,7 @@ except ApiException as e:
    - Generated 2018-03-19T00:19:38.769+01:00 + Generated 2018-03-20T20:09:28.335+01:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h index 26d1f18f3..796f3d3a8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevice.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGAudioDevice: public SWGObject { +class SWG_API SWGAudioDevice: public SWGObject { public: SWGAudioDevice(); SWGAudioDevice(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h index ef6adb324..eee9d24cd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGAudioDevices: public SWGObject { +class SWG_API SWGAudioDevices: public SWGObject { public: SWGAudioDevices(); SWGAudioDevices(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h index 72badf455..1e2b5af55 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevicesSelect.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGAudioDevicesSelect: public SWGObject { +class SWG_API SWGAudioDevicesSelect: public SWGObject { public: SWGAudioDevicesSelect(); SWGAudioDevicesSelect(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h index 4c5550b96..461f57a1a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGCWKeyerSettings.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGCWKeyerSettings: public SWGObject { +class SWG_API SWGCWKeyerSettings: public SWGObject { public: SWGCWKeyerSettings(); SWGCWKeyerSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannel.h b/swagger/sdrangel/code/qt5/client/SWGChannel.h index 4dee7c18b..f5b38abd2 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannel.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannel.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGChannel: public SWGObject { +class SWG_API SWGChannel: public SWGObject { public: SWGChannel(); SWGChannel(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h index 71da54406..cf8c6b537 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelListItem.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGChannelListItem: public SWGObject { +class SWG_API SWGChannelListItem: public SWGObject { public: SWGChannelListItem(); SWGChannelListItem(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 017b4e0e8..0f507c6d3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -27,10 +27,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGChannelReport: public SWGObject { +class SWG_API SWGChannelReport: public SWGObject { public: SWGChannelReport(); SWGChannelReport(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 31d5b5c5e..2fbcdf811 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -27,10 +27,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGChannelSettings: public SWGObject { +class SWG_API SWGChannelSettings: public SWGObject { public: SWGChannelSettings(); SWGChannelSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h index 6e7c03103..44aa30aad 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelsDetail.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGChannelsDetail: public SWGObject { +class SWG_API SWGChannelsDetail: public SWGObject { public: SWGChannelsDetail(); SWGChannelsDetail(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h index 06415c4cf..5a103198b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGDVSeralDevices.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGDVSeralDevices: public SWGObject { +class SWG_API SWGDVSeralDevices: public SWGObject { public: SWGDVSeralDevices(); SWGDVSeralDevices(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h index a761de4a4..67629119c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGDVSerialDevice.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGDVSerialDevice: public SWGObject { +class SWG_API SWGDVSerialDevice: public SWGObject { public: SWGDVSerialDevice(); SWGDVSerialDevice(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h index 51a5e56d2..250712d62 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceListItem.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGDeviceListItem: public SWGObject { +class SWG_API SWGDeviceListItem: public SWGObject { public: SWGDeviceListItem(); SWGDeviceListItem(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h index f12f08ca9..ec9f90e44 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSet.h @@ -27,10 +27,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGDeviceSet: public SWGObject { +class SWG_API SWGDeviceSet: public SWGObject { public: SWGDeviceSet(); SWGDeviceSet(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h index 6f8f518d0..55710bfe6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSetList.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGDeviceSetList: public SWGObject { +class SWG_API SWGDeviceSetList: public SWGObject { public: SWGDeviceSetList(); SWGDeviceSetList(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 460062ac6..e0f890104 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -31,10 +31,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGDeviceSettings: public SWGObject { +class SWG_API SWGDeviceSettings: public SWGObject { public: SWGDeviceSettings(); SWGDeviceSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceState.h b/swagger/sdrangel/code/qt5/client/SWGDeviceState.h index 9978df7f5..738c97a34 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceState.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceState.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGDeviceState: public SWGObject { +class SWG_API SWGDeviceState: public SWGObject { public: SWGDeviceState(); SWGDeviceState(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h index 22e1db85b..a2c8d6ac9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGErrorResponse.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGErrorResponse: public SWGObject { +class SWG_API SWGErrorResponse: public SWGObject { public: SWGErrorResponse(); SWGErrorResponse(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h index 248bc61e9..ef10f881d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFileSourceSettings.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGFileSourceSettings: public SWGObject { +class SWG_API SWGFileSourceSettings: public SWGObject { public: SWGFileSourceSettings(); SWGFileSourceSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h index c2be99dec..13b790280 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFInputSettings.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGHackRFInputSettings: public SWGObject { +class SWG_API SWGHackRFInputSettings: public SWGObject { public: SWGHackRFInputSettings(); SWGHackRFInputSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h index 2511f50f7..ef752ccc7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGHackRFOutputSettings.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGHackRFOutputSettings: public SWGObject { +class SWG_API SWGHackRFOutputSettings: public SWGObject { public: SWGHackRFOutputSettings(); SWGHackRFOutputSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h index ca26f9fa8..b75c7b030 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceChannelsResponse.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGInstanceChannelsResponse: public SWGObject { +class SWG_API SWGInstanceChannelsResponse: public SWGObject { public: SWGInstanceChannelsResponse(); SWGInstanceChannelsResponse(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h index 85f4622cf..d796a4749 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceDevicesResponse.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGInstanceDevicesResponse: public SWGObject { +class SWG_API SWGInstanceDevicesResponse: public SWGObject { public: SWGInstanceDevicesResponse(); SWGInstanceDevicesResponse(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h index 9421a56ad..002ec00e5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceSummaryResponse.h @@ -27,10 +27,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGInstanceSummaryResponse: public SWGObject { +class SWG_API SWGInstanceSummaryResponse: public SWGObject { public: SWGInstanceSummaryResponse(); SWGInstanceSummaryResponse(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h index ef2e7e036..fbaa8e360 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGLimeSdrInputSettings: public SWGObject { +class SWG_API SWGLimeSdrInputSettings: public SWGObject { public: SWGLimeSdrInputSettings(); SWGLimeSdrInputSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h index 713090459..b036243ca 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGLimeSdrOutputSettings: public SWGObject { +class SWG_API SWGLimeSdrOutputSettings: public SWGObject { public: SWGLimeSdrOutputSettings(); SWGLimeSdrOutputSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h index 45f21c6c3..25f0d90d1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h +++ b/swagger/sdrangel/code/qt5/client/SWGLocationInformation.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGLocationInformation: public SWGObject { +class SWG_API SWGLocationInformation: public SWGObject { public: SWGLocationInformation(); SWGLocationInformation(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h index 7a9ca9cbc..04dd54c73 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h +++ b/swagger/sdrangel/code/qt5/client/SWGLoggingInfo.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGLoggingInfo: public SWGObject { +class SWG_API SWGLoggingInfo: public SWGObject { public: SWGLoggingInfo(); SWGLoggingInfo(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h index 9b5fed600..19c7b4356 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodReport.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGNFMDemodReport: public SWGObject { +class SWG_API SWGNFMDemodReport: public SWGObject { public: SWGNFMDemodReport(); SWGNFMDemodReport(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h index ba483569e..420934225 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGNFMDemodSettings: public SWGObject { +class SWG_API SWGNFMDemodSettings: public SWGObject { public: SWGNFMDemodSettings(); SWGNFMDemodSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h index fc68fcef3..e50e1b157 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGNFMModReport: public SWGObject { +class SWG_API SWGNFMModReport: public SWGObject { public: SWGNFMModReport(); SWGNFMModReport(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h index 26adc9fc7..475de4899 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGNFMModSettings: public SWGObject { +class SWG_API SWGNFMModSettings: public SWGObject { public: SWGNFMModSettings(); SWGNFMModSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetExport.h b/swagger/sdrangel/code/qt5/client/SWGPresetExport.h index 60e393c87..468446f8f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetExport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetExport.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGPresetExport: public SWGObject { +class SWG_API SWGPresetExport: public SWGObject { public: SWGPresetExport(); SWGPresetExport(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h index 22e678ef0..f3041a665 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetGroup.h @@ -27,10 +27,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGPresetGroup: public SWGObject { +class SWG_API SWGPresetGroup: public SWGObject { public: SWGPresetGroup(); SWGPresetGroup(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h index 891190190..29d507501 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetIdentifier.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGPresetIdentifier: public SWGObject { +class SWG_API SWGPresetIdentifier: public SWGObject { public: SWGPresetIdentifier(); SWGPresetIdentifier(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetImport.h b/swagger/sdrangel/code/qt5/client/SWGPresetImport.h index d228b8ae9..d86d13376 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetImport.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetImport.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGPresetImport: public SWGObject { +class SWG_API SWGPresetImport: public SWGObject { public: SWGPresetImport(); SWGPresetImport(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetItem.h b/swagger/sdrangel/code/qt5/client/SWGPresetItem.h index 4242fce2b..073e8586a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetItem.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGPresetItem: public SWGObject { +class SWG_API SWGPresetItem: public SWGObject { public: SWGPresetItem(); SWGPresetItem(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h index 8a81e0f79..c100478dc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresetTransfer.h @@ -25,10 +25,11 @@ #include "SWGPresetIdentifier.h" #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGPresetTransfer: public SWGObject { +class SWG_API SWGPresetTransfer: public SWGObject { public: SWGPresetTransfer(); SWGPresetTransfer(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGPresets.h b/swagger/sdrangel/code/qt5/client/SWGPresets.h index 4b138082f..5debf2e4b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGPresets.h +++ b/swagger/sdrangel/code/qt5/client/SWGPresets.h @@ -26,10 +26,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGPresets: public SWGObject { +class SWG_API SWGPresets: public SWGObject { public: SWGPresets(); SWGPresets(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h index 9cd27d8a3..b202d29eb 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGRtlSdrSettings.h @@ -24,10 +24,11 @@ #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGRtlSdrSettings: public SWGObject { +class SWG_API SWGRtlSdrSettings: public SWGObject { public: SWGRtlSdrSettings(); SWGRtlSdrSettings(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h index 04eaa2400..d32f97d21 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h +++ b/swagger/sdrangel/code/qt5/client/SWGSamplingDevice.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGSamplingDevice: public SWGObject { +class SWG_API SWGSamplingDevice: public SWGObject { public: SWGSamplingDevice(); SWGSamplingDevice(QString* json); diff --git a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h index 7ea7ec7ab..2b655872d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h +++ b/swagger/sdrangel/code/qt5/client/SWGSuccessResponse.h @@ -25,10 +25,11 @@ #include #include "SWGObject.h" +#include "export.h" namespace SWGSDRangel { -class SWGSuccessResponse: public SWGObject { +class SWG_API SWGSuccessResponse: public SWGObject { public: SWGSuccessResponse(); SWGSuccessResponse(QString* json); From 7ad8e50651470dc924d6ed1cc666dfa17ef382c9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 21 Mar 2018 23:49:16 +0100 Subject: [PATCH 143/956] Web API: AM demod: implemented settings and report entry points --- plugins/channelrx/demodam/CMakeLists.txt | 3 + plugins/channelrx/demodam/amdemod.cpp | 135 +++++++ plugins/channelrx/demodam/amdemod.h | 16 + plugins/channelrx/demodam/amdemodplugin.cpp | 2 +- sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 89 ++++- .../webapi/doc/swagger/include/AMDemod.yaml | 48 +++ .../webapi/doc/swagger/include/NFMDemod.yaml | 5 + .../resources/webapi/doc/swagger/swagger.yaml | 4 + .../sdrangel/api/swagger/include/AMDemod.yaml | 48 +++ .../api/swagger/include/NFMDemod.yaml | 5 + swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 89 ++++- .../code/qt5/client/SWGAMDemodReport.cpp | 127 ++++++ .../code/qt5/client/SWGAMDemodReport.h | 64 ++++ .../code/qt5/client/SWGAMDemodSettings.cpp | 362 ++++++++++++++++++ .../code/qt5/client/SWGAMDemodSettings.h | 131 +++++++ .../code/qt5/client/SWGChannelReport.cpp | 23 ++ .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 ++ .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + 22 files changed, 1188 insertions(+), 13 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/AMDemod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/AMDemod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGAMDemodReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGAMDemodReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h diff --git a/plugins/channelrx/demodam/CMakeLists.txt b/plugins/channelrx/demodam/CMakeLists.txt index 7ec8181dc..0465d3345 100644 --- a/plugins/channelrx/demodam/CMakeLists.txt +++ b/plugins/channelrx/demodam/CMakeLists.txt @@ -1,5 +1,7 @@ project(am) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(am_SOURCES amdemod.cpp amdemodgui.cpp @@ -21,6 +23,7 @@ set(am_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index a227b37f8..4972e0f58 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -22,12 +22,18 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGAMDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGAMDemodReport.h" + #include "dsp/downchannelizer.h" #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" +#include "util/db.h" MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureAMDemod, Message) MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureChannelizer, Message) @@ -313,3 +319,132 @@ bool AMDemod::deserialize(const QByteArray& data) } } +int AMDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAmDemodSettings(new SWGSDRangel::SWGAMDemodSettings()); + response.getAmDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int AMDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + AMDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getAmDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("audioSampleRate")) { + settings.m_audioSampleRate = response.getAmDemodSettings()->getAudioSampleRate(); + } + if (channelSettingsKeys.contains("copyAudioToUDP")) { + settings.m_copyAudioToUDP = response.getAmDemodSettings()->getCopyAudioToUdp() != 0; + } + if (channelSettingsKeys.contains("copyAudioUseRTP")) { + settings.m_copyAudioUseRTP = response.getAmDemodSettings()->getCopyAudioUseRtp() != 0; + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getAmDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getAmDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAmDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getAmDemodSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getAmDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("udpAddress")) { + settings.m_udpAddress = *response.getAmDemodSettings()->getUdpAddress(); + } + if (channelSettingsKeys.contains("udpPort")) { + settings.m_udpPort = response.getAmDemodSettings()->getUdpPort(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getAmDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("bandpassEnable")) { + settings.m_bandpassEnable = response.getAmDemodSettings()->getBandpassEnable() != 0; + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + 48000, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureAMDemod *msg = MsgConfigureAMDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAMDemod *msgToGUI = MsgConfigureAMDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int AMDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAmDemodReport(new SWGSDRangel::SWGAMDemodReport()); + response.getAmDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void AMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings) +{ + response.getAmDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getAmDemodSettings()->setAudioSampleRate(settings.m_audioSampleRate); + response.getAmDemodSettings()->setCopyAudioToUdp(settings.m_copyAudioToUDP ? 1 : 0); + response.getAmDemodSettings()->setCopyAudioUseRtp(settings.m_copyAudioUseRTP ? 1 : 0); + response.getAmDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getAmDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getAmDemodSettings()->setRgbColor(settings.m_rgbColor); + response.getAmDemodSettings()->setSquelch(settings.m_squelch); + response.getAmDemodSettings()->setUdpPort(settings.m_udpPort); + response.getAmDemodSettings()->setVolume(settings.m_volume); + response.getAmDemodSettings()->setBandpassEnable(settings.m_bandpassEnable ? 1 : 0); + + if (response.getAmDemodSettings()->getTitle()) { + *response.getAmDemodSettings()->getTitle() = settings.m_title; + } else { + response.getAmDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getAmDemodSettings()->getUdpAddress()) { + *response.getAmDemodSettings()->getUdpAddress() = settings.m_udpAddress; + } else { + response.getAmDemodSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + } +} + +void AMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getAmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getAmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); +} + diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index 5fec9042f..06e57d601 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -101,6 +101,20 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } bool getSquelchOpen() const { return m_squelchOpen; } @@ -162,6 +176,8 @@ private: void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const AMDemodSettings& settings, bool force = false); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); void processOneSample(Complex &ci) { diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index 09ae64f70..c3350a6aa 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -8,7 +8,7 @@ const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("3.12.0"), + QString("3.13.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 694ecf84a..a601873c7 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -6,6 +6,7 @@ webapi/doc/swagger/include/FileSource.yaml webapi/doc/swagger/include/HackRF.yaml webapi/doc/swagger/include/LimeSdr.yaml + webapi/doc/swagger/include/AMDemod.yaml webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml webapi/doc/swagger/include/RtlSdr.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 2b183443d..aeb9afb38 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -696,6 +696,72 @@ margin-bottom: 20px; -
    +
    @@ -11021,24 +11058,24 @@ $(document).ready(function() {

    Responses

    -

    Status: 200 - Success

    +

    Status: 200 - Success. Returns actual data in particular the actual sample rate.

    -
    -
    +
    +
    - + +
    +
    + +

    Status: 404 - Audio input device not found

    + + + +
    +
    +
    + +
    +
    @@ -11068,14 +11148,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11111,14 +11191,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - + +
    +
    + + +
    +
    +
    +
    +
    +

    instanceAudioOutputSetPatch

    +

    +
    +
    +
    +

    +

    Set audio output device parameters

    +

    +
    +
    /sdrangel/audio/output/set
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/output/set"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +        try {
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputSetPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +        try {
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputSetPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    AudioOutputDevice *body = ; // Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioOutputSetPatchWith:body
    +              completionHandler: ^(AudioOutputDevice output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var body = ; // {AudioOutputDevice} Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioOutputSetPatch(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioOutputSetPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +            var body = new AudioOutputDevice(); // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +            try
    +            {
    +                AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputSetPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +$body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +try {
    +    $result = $api_instance->instanceAudioOutputSetPatch($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioOutputSetPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +my $body = SWGSDRangel::Object::AudioOutputDevice->new(); # AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioOutputSetPatch(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioOutputSetPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +body =  # AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +try: 
    +    api_response = api_instance.instance_audio_output_set_patch(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioOutputSetPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - Success. Returns actual data in particular the actual sample rate.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Audio output device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    +
    @@ -18045,7 +18555,7 @@ except ApiException as e:
    - Generated 2018-03-28T09:46:11.248+02:00 + Generated 2018-03-29T00:32:59.208+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 992b7afa7..275ad9e2b 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -182,9 +182,11 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + /sdrangel/audio/input/set: + x-swagger-router-controller: instance patch: - description: Set audio devices - operationId: instanceAudioPatch + description: Set audio input device paramaters + operationId: instanceAudioInputSetPatch tags: - Instance consumes: @@ -192,15 +194,49 @@ paths: parameters: - name: body in: body - description: Select audio devices to use for this instance + description: Audio input parameters. Index is used to identify the device. Only settable fields are considered. required: true schema: - $ref: "#/definitions/AudioDevicesSelect" + $ref: "#/definitions/AudioInputDevice" responses: "200": - description: Success + description: Success. Returns actual data in particular the actual sample rate. schema: - $ref: "#/definitions/AudioDevicesSelect" + $ref: "#/definitions/AudioInputDevice" + "404": + description: Audio input device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + /sdrangel/audio/output/set: + x-swagger-router-controller: instance + patch: + description: Set audio output device parameters + operationId: instanceAudioOutputSetPatch + tags: + - Instance + consumes: + - application/json + parameters: + - name: body + in: body + description: Audio output parameters. Index is used to identify the device. Only settable fields are considered. + required: true + schema: + $ref: "#/definitions/AudioOutputDevice" + responses: + "200": + description: Success. Returns actual data in particular the actual sample rate. + schema: + $ref: "#/definitions/AudioOutputDevice" + "404": + description: Audio output device not found + schema: + $ref: "#/definitions/ErrorResponse" "500": $ref: "#/responses/Response_500" "501": @@ -1355,64 +1391,83 @@ definitions: AudioDevices: description: "List of audio devices available in the system" required: - - inputVolume - nbInputDevices - - inputDeviceSelectedIndex - nbOutputDevices - - outputDeviceSelectedIndex properties: - inputVolume: - description: "Audio input volume [0.0..1.0]" - type: number - format: float nbInputDevices: description: "Number of input audio devices" type: integer - inputDeviceSelectedIndex: - description: "Index of selected input audio devices (-1 if default)" - type: integer inputDevices: description: "List of input devices" type: array items: - $ref: "#/definitions/AudioDevice" + $ref: "#/definitions/AudioInputDevice" nbOutputDevices: description: "Number of output audio devices" type: integer - outputDeviceSelectedIndex: - description: "Index of selected output audio devices (-1 if default)" - type: integer outputDevices: description: "List of output devices" type: array items: - $ref: "#/definitions/AudioDevice" + $ref: "#/definitions/AudioOutputDevice" - AudioDevice: - description: "Audio device" + AudioInputDevice: + description: "Audio input device" properties: name: description: "Displayable name of the device" type: string - - AudioDevicesSelect: - description: "Audio devices selected" - required: - - inputVolume - - inputIndex - - outputIndex - properties: - inputVolume: + index: + description: "Index in attached devices list. -1 for system default" + type: integer + sampleRate: + description: "Device sample rate in S/s" + type: integer + isSystemDefault: + description: "1 if this device is the system default else 0" + type: integer + defaultUnregistered: + description: "1 if this device is unregistered and therefore will inherit default values else 0" + type: integer + volume: description: "Audio input volume [0.0..1.0]" type: number format: float - inputIndex: - description: "Index of the audio input device (-1 for default)" + + AudioOutputDevice: + description: "Audio output device" + properties: + name: + description: "Displayable name of the device" + type: string + index: + description: "Index in attached devices list. -1 for system default" type: integer - outputIndex: - description: "Index of the audio output device (-1 for default)" + sampleRate: + description: "Device sample rate in S/s" type: integer - + isSystemDefault: + description: "1 if this device is the system default else 0" + type: integer + defaultUnregistered: + description: "1 if this device is unregistered and therefore will inherit default values else 0" + type: integer + copyToUDP: + description: '1 if audio is copied to UDP else 0' + type: integer + udpUsesRTP: + description: '1 if RTP protocol is used over UDP else 0' + type: integer + udpChannelMode: + description: 'How audio data is copied to UDP: 0: left 1: right 2: mixed 3: stereo' + type: integer + udpAddress: + description: "UDP destination address" + type: string + udpPort: + description: "UDP destination port" + type: integer + LocationInformation: description: "Instance geolocation information" required: diff --git a/sdrbase/webapi/webapiadapterinterface.cpp b/sdrbase/webapi/webapiadapterinterface.cpp index 169cc44c5..8ffc45338 100644 --- a/sdrbase/webapi/webapiadapterinterface.cpp +++ b/sdrbase/webapi/webapiadapterinterface.cpp @@ -23,6 +23,10 @@ QString WebAPIAdapterInterface::instanceDevicesURL = "/sdrangel/devices"; QString WebAPIAdapterInterface::instanceChannelsURL = "/sdrangel/channels"; QString WebAPIAdapterInterface::instanceLoggingURL = "/sdrangel/logging"; QString WebAPIAdapterInterface::instanceAudioURL = "/sdrangel/audio"; +QString WebAPIAdapterInterface::instanceAudioInputSetURL = "/sdrangel/audio/input/set"; +QString WebAPIAdapterInterface::instanceAudioOutputSetURL = "/sdrangel/audio/output/set"; +QString WebAPIAdapterInterface::instanceAudioInputUnsetURL = "/sdrangel/audio/input/unset"; +QString WebAPIAdapterInterface::instanceAudioOutputUnsetURL = "/sdrangel/audio/output/unset"; QString WebAPIAdapterInterface::instanceLocationURL = "/sdrangel/location"; QString WebAPIAdapterInterface::instanceDVSerialURL = "/sdrangel/dvserial"; QString WebAPIAdapterInterface::instancePresetsURL = "/sdrangel/presets"; diff --git a/sdrbase/webapi/webapiadapterinterface.h b/sdrbase/webapi/webapiadapterinterface.h index 6695cb33e..9fa8e1dad 100644 --- a/sdrbase/webapi/webapiadapterinterface.h +++ b/sdrbase/webapi/webapiadapterinterface.h @@ -33,7 +33,8 @@ namespace SWGSDRangel class SWGInstanceChannelsResponse; class SWGLoggingInfo; class SWGAudioDevices; - class SWGAudioDevicesSelect; + class SWGAudioInputDevice; + class SWGAudioOutputDevice; class SWGLocationInformation; class SWGDVSeralDevices; class SWGPresets; @@ -152,11 +153,12 @@ public: } /** - * Handler of /sdrangel/audio (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * Handler of /sdrangel/audio/input (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels * returns the Http status code (default 501: not implemented) */ - virtual int instanceAudioPatch( - SWGSDRangel::SWGAudioDevicesSelect& response __attribute__((unused)), + virtual int instanceAudioInputPatch( + SWGSDRangel::SWGAudioInputDevice& response __attribute__((unused)), + const QStringList& audioInputKeys __attribute__((unused)), SWGSDRangel::SWGErrorResponse& error) { error.init(); @@ -164,6 +166,20 @@ public: return 501; } + /** + * Handler of /sdrangel/audio/output (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * returns the Http status code (default 501: not implemented) + */ + virtual int instanceAudioOutputPatch( + SWGSDRangel::SWGAudioOutputDevice& response __attribute__((unused)), + const QStringList& audioOutputKeys __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) + { + error.init(); + *error.getMessage() = QString("Function not implemented"); + return 501; + } + /** * Handler of /sdrangel/location (GET) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels * returns the Http status code (default 501: not implemented) @@ -552,6 +568,10 @@ public: static QString instanceChannelsURL; static QString instanceLoggingURL; static QString instanceAudioURL; + static QString instanceAudioInputSetURL; + static QString instanceAudioOutputSetURL; + static QString instanceAudioInputUnsetURL; + static QString instanceAudioOutputUnsetURL; static QString instanceLocationURL; static QString instanceDVSerialURL; static QString instancePresetsURL; diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index c5bfeae1d..3ed016cde 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -84,6 +84,10 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http instanceLoggingService(request, response); } else if (path == WebAPIAdapterInterface::instanceAudioURL) { instanceAudioService(request, response); + } else if (path == WebAPIAdapterInterface::instanceAudioInputSetURL) { + instanceAudioInputSetService(request, response); + } else if (path == WebAPIAdapterInterface::instanceAudioOutputSetURL) { + instanceAudioOutputSetService(request, response); } else if (path == WebAPIAdapterInterface::instanceLocationURL) { instanceLocationService(request, response); } else if (path == WebAPIAdapterInterface::instanceDVSerialURL) { @@ -315,21 +319,106 @@ void WebAPIRequestMapper::instanceAudioService(qtwebapp::HttpRequest& request, q response.write(errorResponse.asJson().toUtf8()); } } - else if (request.getMethod() == "PATCH") + else + { + response.setStatus(405,"Invalid HTTP method"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid HTTP method"; + response.write(errorResponse.asJson().toUtf8()); + } +} + +void WebAPIRequestMapper::instanceAudioInputSetService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +{ + // TODO + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + + if (request.getMethod() == "PATCH") { - SWGSDRangel::SWGAudioDevicesSelect normalResponse; QString jsonStr = request.getBody(); QJsonObject jsonObject; if (parseJsonBody(jsonStr, jsonObject, response)) { - normalResponse.fromJson(jsonStr); - int status = m_adapter->instanceAudioPatch(normalResponse, errorResponse); - response.setStatus(status); + SWGSDRangel::SWGAudioInputDevice normalResponse; + resetAudioInputDevice(normalResponse); + QStringList audioInputDeviceKeys; - if (status/100 == 2) { - response.write(normalResponse.asJson().toUtf8()); - } else { + if (validateAudioInputDevice(normalResponse, jsonObject, audioInputDeviceKeys)) + { + int status = m_adapter->instanceAudioInputPatch( + normalResponse, + audioInputDeviceKeys, + errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(400,"Invalid JSON request"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid JSON request"; + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(400,"Invalid JSON format"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid JSON format"; + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(405,"Invalid HTTP method"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid HTTP method"; + response.write(errorResponse.asJson().toUtf8()); + } +} + +void WebAPIRequestMapper::instanceAudioOutputSetService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +{ + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + + if (request.getMethod() == "PATCH") + { + QString jsonStr = request.getBody(); + QJsonObject jsonObject; + + if (parseJsonBody(jsonStr, jsonObject, response)) + { + SWGSDRangel::SWGAudioOutputDevice normalResponse; + resetAudioOutputDevice(normalResponse); + QStringList audioOutputDeviceKeys; + + if (validateAudioOutputDevice(normalResponse, jsonObject, audioOutputDeviceKeys)) + { + int status = m_adapter->instanceAudioOutputPatch( + normalResponse, + audioOutputDeviceKeys, + errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(400,"Invalid JSON request"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid JSON request"; response.write(errorResponse.asJson().toUtf8()); } } @@ -1745,6 +1834,72 @@ bool WebAPIRequestMapper::validateChannelReport( } } +bool WebAPIRequestMapper::validateAudioInputDevice( + SWGSDRangel::SWGAudioInputDevice& audioInputDevice, + QJsonObject& jsonObject, + QStringList& audioInputDeviceKeys) +{ + if (jsonObject.contains("index")) { + audioInputDevice.setIndex(jsonObject["index"].toInt()); + } else { + audioInputDevice.setIndex(-1); // assume systam default + } + if (jsonObject.contains("sampleRate")) + { + audioInputDevice.setSampleRate(jsonObject["sampleRate"].toInt()); + audioInputDeviceKeys.append("sampleRate"); + } + if (jsonObject.contains("volume")) + { + audioInputDevice.setVolume(jsonObject["volume"].toDouble()); + audioInputDeviceKeys.append("volume"); + } + return true; +} + +bool WebAPIRequestMapper::validateAudioOutputDevice( + SWGSDRangel::SWGAudioOutputDevice& audioOutputDevice, + QJsonObject& jsonObject, + QStringList& audioOutputDeviceKeys) +{ + if (jsonObject.contains("index")) { + audioOutputDevice.setIndex(jsonObject["index"].toInt()); + } else { + audioOutputDevice.setIndex(-1); // assume systam default + } + if (jsonObject.contains("sampleRate")) + { + audioOutputDevice.setSampleRate(jsonObject["sampleRate"].toInt()); + audioOutputDeviceKeys.append("sampleRate"); + } + if (jsonObject.contains("copyToUDP")) + { + audioOutputDevice.setCopyToUdp(jsonObject["copyToUDP"].toInt() == 0 ? 0 : 1); + audioOutputDeviceKeys.append("copyToUDP"); + } + if (jsonObject.contains("udpUsesRTP")) + { + audioOutputDevice.setUdpUsesRtp(jsonObject["udpUsesRTP"].toInt() == 0 ? 0 : 1); + audioOutputDeviceKeys.append("udpUsesRTP"); + } + if (jsonObject.contains("udpChannelMode")) + { + audioOutputDevice.setUdpChannelMode(jsonObject["udpChannelMode"].toInt()); + audioOutputDeviceKeys.append("udpChannelMode"); + } + if (jsonObject.contains("udpAddress")) + { + audioOutputDevice.setUdpAddress(new QString(jsonObject["udpAddress"].toString())); + audioOutputDeviceKeys.append("udpAddress"); + } + if (jsonObject.contains("udpPort")) + { + audioOutputDevice.setUdpPort(jsonObject["udpPort"].toInt()); + audioOutputDeviceKeys.append("udpPort"); + } + return true; +} + void WebAPIRequestMapper::appendSettingsSubKeys( const QJsonObject& parentSettingsJsonObject, QJsonObject& childSettingsJsonObject, @@ -1786,3 +1941,16 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan channelReport.setNfmDemodReport(0); channelReport.setNfmModReport(0); } + +void WebAPIRequestMapper::resetAudioInputDevice(SWGSDRangel::SWGAudioInputDevice& audioInputDevice) +{ + audioInputDevice.cleanup(); + audioInputDevice.setName(0); +} + +void WebAPIRequestMapper::resetAudioOutputDevice(SWGSDRangel::SWGAudioOutputDevice& audioOutputDevice) +{ + audioOutputDevice.cleanup(); + audioOutputDevice.setName(0); + audioOutputDevice.setUdpAddress(0); +} diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index 42296a15d..9162ed154 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -52,6 +52,8 @@ private: void instanceChannelsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instanceLoggingService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instanceAudioService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void instanceAudioInputSetService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void instanceAudioOutputSetService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instanceLocationService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instanceDVSerialService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instancePresetsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); @@ -78,6 +80,8 @@ private: bool validateDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings, QJsonObject& jsonObject, QStringList& deviceSettingsKeys); bool validateChannelSettings(SWGSDRangel::SWGChannelSettings& deviceSettings, QJsonObject& jsonObject, QStringList& channelSettingsKeys); bool validateChannelReport(SWGSDRangel::SWGChannelReport& deviceReport, QJsonObject& jsonObject, QStringList& channelReportKeys); + bool validateAudioInputDevice(SWGSDRangel::SWGAudioInputDevice& audioInputDevice, QJsonObject& jsonObject, QStringList& audioInputDeviceKeys); + bool validateAudioOutputDevice(SWGSDRangel::SWGAudioOutputDevice& audioOutputDevice, QJsonObject& jsonObject, QStringList& audioOutputDeviceKeys); void appendSettingsSubKeys( const QJsonObject& parentSettingsJsonObject, @@ -90,6 +94,8 @@ private: void resetDeviceSettings(SWGSDRangel::SWGDeviceSettings& deviceSettings); void resetChannelSettings(SWGSDRangel::SWGChannelSettings& deviceSettings); void resetChannelReport(SWGSDRangel::SWGChannelReport& deviceSettings); + void resetAudioInputDevice(SWGSDRangel::SWGAudioInputDevice& audioInputDevice); + void resetAudioOutputDevice(SWGSDRangel::SWGAudioOutputDevice& audioOutputDevice); }; #endif /* SDRBASE_WEBAPI_WEBAPIREQUESTMAPPER_H_ */ diff --git a/sdrgui/webapi/webapiadaptergui.cpp b/sdrgui/webapi/webapiadaptergui.cpp index 00ff5e649..016a32d38 100644 --- a/sdrgui/webapi/webapiadaptergui.cpp +++ b/sdrgui/webapi/webapiadaptergui.cpp @@ -234,51 +234,162 @@ int WebAPIAdapterGUI::instanceAudioGet( response.init(); response.setNbInputDevices(nbInputDevices); - response.setInputDeviceSelectedIndex(-1); // FIXME: remove response.setNbOutputDevices(nbOutputDevices); - response.setOutputDeviceSelectedIndex(-1); // FIXME: remove - response.setInputVolume(1.0f); // FIXME: remove - QList *inputDevices = response.getInputDevices(); - QList *outputDevices = response.getOutputDevices(); + QList *inputDevices = response.getInputDevices(); + QList *outputDevices = response.getOutputDevices(); + AudioDeviceManager::InputDeviceInfo inputDeviceInfo; + AudioDeviceManager::OutputDeviceInfo outputDeviceInfo; + // system default input device + inputDevices->append(new SWGSDRangel::SWGAudioInputDevice); + inputDevices->back()->init(); + bool found = m_mainWindow.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(AudioDeviceManager::m_defaultDeviceName, inputDeviceInfo); + *inputDevices->back()->getName() = AudioDeviceManager::m_defaultDeviceName; + inputDevices->back()->setIndex(-1); + inputDevices->back()->setSampleRate(inputDeviceInfo.sampleRate); + inputDevices->back()->setIsSystemDefault(0); + inputDevices->back()->setDefaultUnregistered(found ? 0 : 1); + inputDevices->back()->setVolume(inputDeviceInfo.volume); + + // real input devices for (int i = 0; i < nbInputDevices; i++) { - inputDevices->append(new SWGSDRangel::SWGAudioDevice); + inputDevices->append(new SWGSDRangel::SWGAudioInputDevice); inputDevices->back()->init(); + inputDeviceInfo.resetToDefaults(); + found = m_mainWindow.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(audioInputDevices.at(i).deviceName(), inputDeviceInfo); *inputDevices->back()->getName() = audioInputDevices.at(i).deviceName(); + inputDevices->back()->setIndex(i); + inputDevices->back()->setSampleRate(inputDeviceInfo.sampleRate); + inputDevices->back()->setIsSystemDefault(audioInputDevices.at(i).deviceName() == QAudioDeviceInfo::defaultInputDevice().deviceName() ? 1 : 0); + inputDevices->back()->setDefaultUnregistered(found ? 0 : 1); + inputDevices->back()->setVolume(inputDeviceInfo.volume); } + // system default output device + outputDevices->append(new SWGSDRangel::SWGAudioOutputDevice); + outputDevices->back()->init(); + found = m_mainWindow.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(AudioDeviceManager::m_defaultDeviceName, outputDeviceInfo); + *outputDevices->back()->getName() = AudioDeviceManager::m_defaultDeviceName; + outputDevices->back()->setIndex(-1); + outputDevices->back()->setSampleRate(outputDeviceInfo.sampleRate); + inputDevices->back()->setIsSystemDefault(0); + outputDevices->back()->setDefaultUnregistered(found ? 0 : 1); + outputDevices->back()->setCopyToUdp(outputDeviceInfo.copyToUDP ? 1 : 0); + outputDevices->back()->setUdpUsesRtp(outputDeviceInfo.udpUseRTP ? 1 : 0); + outputDevices->back()->setUdpChannelMode((int) outputDeviceInfo.udpChannelMode); + *outputDevices->back()->getUdpAddress() = outputDeviceInfo.udpAddress; + outputDevices->back()->setUdpPort(outputDeviceInfo.udpPort); + + // real output devices for (int i = 0; i < nbOutputDevices; i++) { - outputDevices->append(new SWGSDRangel::SWGAudioDevice); + outputDevices->append(new SWGSDRangel::SWGAudioOutputDevice); outputDevices->back()->init(); + outputDeviceInfo.resetToDefaults(); + found = m_mainWindow.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(audioOutputDevices.at(i).deviceName(), outputDeviceInfo); *outputDevices->back()->getName() = audioOutputDevices.at(i).deviceName(); + outputDevices->back()->setIndex(i); + outputDevices->back()->setSampleRate(outputDeviceInfo.sampleRate); + outputDevices->back()->setIsSystemDefault(audioOutputDevices.at(i).deviceName() == QAudioDeviceInfo::defaultOutputDevice().deviceName() ? 1 : 0); + outputDevices->back()->setDefaultUnregistered(found ? 0 : 1); + outputDevices->back()->setCopyToUdp(outputDeviceInfo.copyToUDP ? 1 : 0); + outputDevices->back()->setUdpUsesRtp(outputDeviceInfo.udpUseRTP ? 1 : 0); + outputDevices->back()->setUdpChannelMode((int) outputDeviceInfo.udpChannelMode); + *outputDevices->back()->getUdpAddress() = outputDeviceInfo.udpAddress; + outputDevices->back()->setUdpPort(outputDeviceInfo.udpPort); } return 200; } -int WebAPIAdapterGUI::instanceAudioPatch( - SWGSDRangel::SWGAudioDevicesSelect& response, - SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) +int WebAPIAdapterGUI::instanceAudioInputPatch( + SWGSDRangel::SWGAudioInputDevice& response, + const QStringList& audioInputKeys, + SWGSDRangel::SWGErrorResponse& error) { - // response input is the query actually - float inputVolume = response.getInputVolume(); - int inputIndex = response.getInputIndex(); - int outputIndex = response.getOutputIndex(); + // TODO + AudioDeviceManager::InputDeviceInfo inputDeviceInfo; + QString deviceName; + int deviceIndex = response.getIndex(); - const QList& audioInputDevices = m_mainWindow.m_dspEngine->getAudioDeviceManager()->getInputDevices(); - const QList& audioOutputDevices = m_mainWindow.m_dspEngine->getAudioDeviceManager()->getOutputDevices(); - int nbInputDevices = audioInputDevices.size(); - int nbOutputDevices = audioOutputDevices.size(); + if (!m_mainWindow.m_dspEngine->getAudioDeviceManager()->getInputDeviceName(deviceIndex, deviceName)) + { + error.init(); + *error.getMessage() = QString("There is no audio input device at index %1").arg(deviceIndex); + return 404; + } - inputVolume = inputVolume < 0.0 ? 0.0 : inputVolume > 1.0 ? 1.0 : inputVolume; - inputIndex = inputIndex < -1 ? -1 : inputIndex > nbInputDevices ? nbInputDevices-1 : inputIndex; - outputIndex = outputIndex < -1 ? -1 : outputIndex > nbOutputDevices ? nbOutputDevices-1 : outputIndex; + m_mainWindow.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(deviceName, inputDeviceInfo); - response.setInputVolume(1.0f); // FIXME: remove - response.setInputIndex(-1); // FIXME: remove - response.setOutputIndex(-1); // FIXME: remove + if (audioInputKeys.contains("sampleRate")) { + inputDeviceInfo.sampleRate = response.getSampleRate(); + } + if (audioInputKeys.contains("volume")) { + inputDeviceInfo.volume = response.getVolume(); + } + + m_mainWindow.m_dspEngine->getAudioDeviceManager()->setInputDeviceInfo(deviceIndex, inputDeviceInfo); + m_mainWindow.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(deviceName, inputDeviceInfo); + + response.setSampleRate(inputDeviceInfo.sampleRate); + response.setVolume(inputDeviceInfo.volume); + + return 200; +} + +int WebAPIAdapterGUI::instanceAudioOutputPatch( + SWGSDRangel::SWGAudioOutputDevice& response, + const QStringList& audioOutputKeys, + SWGSDRangel::SWGErrorResponse& error) +{ + AudioDeviceManager::OutputDeviceInfo outputDeviceInfo; + QString deviceName; + int deviceIndex = response.getIndex(); + + if (!m_mainWindow.m_dspEngine->getAudioDeviceManager()->getOutputDeviceName(deviceIndex, deviceName)) + { + error.init(); + *error.getMessage() = QString("There is no audio output device at index %1").arg(deviceIndex); + return 404; + } + + m_mainWindow.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(deviceName, outputDeviceInfo); + + if (audioOutputKeys.contains("sampleRate")) { + outputDeviceInfo.sampleRate = response.getSampleRate(); + } + if (audioOutputKeys.contains("copyToUDP")) { + outputDeviceInfo.copyToUDP = response.getCopyToUdp() == 0 ? 0 : 1; + } + if (audioOutputKeys.contains("udpUsesRTP")) { + outputDeviceInfo.udpUseRTP = response.getUdpUsesRtp() == 0 ? 0 : 1; + } + if (audioOutputKeys.contains("udpChannelMode")) { + outputDeviceInfo.udpChannelMode = static_cast(response.getUdpChannelMode() % 4); + } + if (audioOutputKeys.contains("udpAddress")) { + outputDeviceInfo.udpAddress = *response.getUdpAddress(); + } + if (audioOutputKeys.contains("udpPort")) { + outputDeviceInfo.udpPort = response.getUdpPort() % (1<<16); + } + + m_mainWindow.m_dspEngine->getAudioDeviceManager()->setOutputDeviceInfo(deviceIndex, outputDeviceInfo); + m_mainWindow.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(deviceName, outputDeviceInfo); + + response.setSampleRate(outputDeviceInfo.sampleRate); + response.setCopyToUdp(outputDeviceInfo.copyToUDP == 0 ? 0 : 1); + response.setUdpUsesRtp(outputDeviceInfo.udpUseRTP == 0 ? 0 : 1); + response.setUdpChannelMode(outputDeviceInfo.udpChannelMode % 4); + + if (response.getUdpAddress()) { + *response.getUdpAddress() = outputDeviceInfo.udpAddress; + } else { + response.setUdpAddress(new QString(outputDeviceInfo.udpAddress)); + } + + response.setUdpPort(outputDeviceInfo.udpPort % (1<<16)); return 200; } diff --git a/sdrgui/webapi/webapiadaptergui.h b/sdrgui/webapi/webapiadaptergui.h index c2200e021..29a6250bd 100644 --- a/sdrgui/webapi/webapiadaptergui.h +++ b/sdrgui/webapi/webapiadaptergui.h @@ -63,8 +63,14 @@ public: SWGSDRangel::SWGAudioDevices& response, SWGSDRangel::SWGErrorResponse& error); - virtual int instanceAudioPatch( - SWGSDRangel::SWGAudioDevicesSelect& response, + virtual int instanceAudioInputPatch( + SWGSDRangel::SWGAudioInputDevice& response, + const QStringList& audioInputKeys, + SWGSDRangel::SWGErrorResponse& error); + + virtual int instanceAudioOutputPatch( + SWGSDRangel::SWGAudioOutputDevice& response, + const QStringList& audioOutputKeys, SWGSDRangel::SWGErrorResponse& error); virtual int instanceLocationGet( diff --git a/sdrsrv/webapi/webapiadaptersrv.cpp b/sdrsrv/webapi/webapiadaptersrv.cpp index 5abc559ba..9ec6d7312 100644 --- a/sdrsrv/webapi/webapiadaptersrv.cpp +++ b/sdrsrv/webapi/webapiadaptersrv.cpp @@ -235,51 +235,162 @@ int WebAPIAdapterSrv::instanceAudioGet( response.init(); response.setNbInputDevices(nbInputDevices); - response.setInputDeviceSelectedIndex(-1); // FIXME: remove response.setNbOutputDevices(nbOutputDevices); - response.setOutputDeviceSelectedIndex(-1); // FIXME: remove - response.setInputVolume(1.0f); // FIXME: remove - QList *inputDevices = response.getInputDevices(); - QList *outputDevices = response.getOutputDevices(); + QList *inputDevices = response.getInputDevices(); + QList *outputDevices = response.getOutputDevices(); + AudioDeviceManager::InputDeviceInfo inputDeviceInfo; + AudioDeviceManager::OutputDeviceInfo outputDeviceInfo; + // system default input device + inputDevices->append(new SWGSDRangel::SWGAudioInputDevice); + inputDevices->back()->init(); + bool found = m_mainCore.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(AudioDeviceManager::m_defaultDeviceName, inputDeviceInfo); + *inputDevices->back()->getName() = AudioDeviceManager::m_defaultDeviceName; + inputDevices->back()->setIndex(-1); + inputDevices->back()->setSampleRate(inputDeviceInfo.sampleRate); + inputDevices->back()->setIsSystemDefault(0); + inputDevices->back()->setDefaultUnregistered(found ? 0 : 1); + inputDevices->back()->setVolume(inputDeviceInfo.volume); + + // real input devices for (int i = 0; i < nbInputDevices; i++) { - inputDevices->append(new SWGSDRangel::SWGAudioDevice); + inputDevices->append(new SWGSDRangel::SWGAudioInputDevice); inputDevices->back()->init(); + inputDeviceInfo.resetToDefaults(); + found = m_mainCore.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(audioInputDevices.at(i).deviceName(), inputDeviceInfo); *inputDevices->back()->getName() = audioInputDevices.at(i).deviceName(); + inputDevices->back()->setIndex(i); + inputDevices->back()->setSampleRate(inputDeviceInfo.sampleRate); + inputDevices->back()->setIsSystemDefault(audioInputDevices.at(i).deviceName() == QAudioDeviceInfo::defaultInputDevice().deviceName() ? 1 : 0); + inputDevices->back()->setDefaultUnregistered(found ? 0 : 1); + inputDevices->back()->setVolume(inputDeviceInfo.volume); } + // system default output device + outputDevices->append(new SWGSDRangel::SWGAudioOutputDevice); + outputDevices->back()->init(); + found = m_mainCore.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(AudioDeviceManager::m_defaultDeviceName, outputDeviceInfo); + *outputDevices->back()->getName() = AudioDeviceManager::m_defaultDeviceName; + outputDevices->back()->setIndex(-1); + outputDevices->back()->setSampleRate(outputDeviceInfo.sampleRate); + inputDevices->back()->setIsSystemDefault(0); + outputDevices->back()->setDefaultUnregistered(found ? 0 : 1); + outputDevices->back()->setCopyToUdp(outputDeviceInfo.copyToUDP ? 1 : 0); + outputDevices->back()->setUdpUsesRtp(outputDeviceInfo.udpUseRTP ? 1 : 0); + outputDevices->back()->setUdpChannelMode((int) outputDeviceInfo.udpChannelMode); + *outputDevices->back()->getUdpAddress() = outputDeviceInfo.udpAddress; + outputDevices->back()->setUdpPort(outputDeviceInfo.udpPort); + + // real output devices for (int i = 0; i < nbOutputDevices; i++) { - outputDevices->append(new SWGSDRangel::SWGAudioDevice); + outputDevices->append(new SWGSDRangel::SWGAudioOutputDevice); outputDevices->back()->init(); + outputDeviceInfo.resetToDefaults(); + found = m_mainCore.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(audioOutputDevices.at(i).deviceName(), outputDeviceInfo); *outputDevices->back()->getName() = audioOutputDevices.at(i).deviceName(); + outputDevices->back()->setIndex(i); + outputDevices->back()->setSampleRate(outputDeviceInfo.sampleRate); + outputDevices->back()->setIsSystemDefault(audioOutputDevices.at(i).deviceName() == QAudioDeviceInfo::defaultOutputDevice().deviceName() ? 1 : 0); + outputDevices->back()->setDefaultUnregistered(found ? 0 : 1); + outputDevices->back()->setCopyToUdp(outputDeviceInfo.copyToUDP ? 1 : 0); + outputDevices->back()->setUdpUsesRtp(outputDeviceInfo.udpUseRTP ? 1 : 0); + outputDevices->back()->setUdpChannelMode((int) outputDeviceInfo.udpChannelMode); + *outputDevices->back()->getUdpAddress() = outputDeviceInfo.udpAddress; + outputDevices->back()->setUdpPort(outputDeviceInfo.udpPort); } return 200; } -int WebAPIAdapterSrv::instanceAudioPatch( - SWGSDRangel::SWGAudioDevicesSelect& response, - SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) +int WebAPIAdapterSrv::instanceAudioInputPatch( + SWGSDRangel::SWGAudioInputDevice& response, + const QStringList& audioInputKeys, + SWGSDRangel::SWGErrorResponse& error) { - // response input is the query actually - float inputVolume = response.getInputVolume(); - int inputIndex = response.getInputIndex(); - int outputIndex = response.getOutputIndex(); + // TODO + AudioDeviceManager::InputDeviceInfo inputDeviceInfo; + QString deviceName; + int deviceIndex = response.getIndex(); - const QList& audioInputDevices = m_mainCore.m_dspEngine->getAudioDeviceManager()->getInputDevices(); - const QList& audioOutputDevices = m_mainCore.m_dspEngine->getAudioDeviceManager()->getOutputDevices(); - int nbInputDevices = audioInputDevices.size(); - int nbOutputDevices = audioOutputDevices.size(); + if (!m_mainCore.m_dspEngine->getAudioDeviceManager()->getInputDeviceName(deviceIndex, deviceName)) + { + error.init(); + *error.getMessage() = QString("There is no input audio device at index %1").arg(deviceIndex); + return 404; + } - inputVolume = inputVolume < 0.0 ? 0.0 : inputVolume > 1.0 ? 1.0 : inputVolume; - inputIndex = inputIndex < -1 ? -1 : inputIndex > nbInputDevices ? nbInputDevices-1 : inputIndex; - outputIndex = outputIndex < -1 ? -1 : outputIndex > nbOutputDevices ? nbOutputDevices-1 : outputIndex; + m_mainCore.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(deviceName, inputDeviceInfo); - response.setInputVolume(1.0f); // FIXME: remove - response.setInputIndex(-1); // FIXME: remove - response.setOutputIndex(-1); // FIXME: remove + if (audioInputKeys.contains("sampleRate")) { + inputDeviceInfo.sampleRate = response.getSampleRate(); + } + if (audioInputKeys.contains("volume")) { + inputDeviceInfo.volume = response.getVolume(); + } + + m_mainCore.m_dspEngine->getAudioDeviceManager()->setInputDeviceInfo(deviceIndex, inputDeviceInfo); + m_mainCore.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(deviceName, inputDeviceInfo); + + response.setSampleRate(inputDeviceInfo.sampleRate); + response.setVolume(inputDeviceInfo.volume); + + return 200; +} + +int WebAPIAdapterSrv::instanceAudioOutputPatch( + SWGSDRangel::SWGAudioOutputDevice& response, + const QStringList& audioOutputKeys, + SWGSDRangel::SWGErrorResponse& error) +{ + AudioDeviceManager::OutputDeviceInfo outputDeviceInfo; + QString deviceName; + int deviceIndex = response.getIndex(); + + if (!m_mainCore.m_dspEngine->getAudioDeviceManager()->getOutputDeviceName(deviceIndex, deviceName)) + { + error.init(); + *error.getMessage() = QString("There is no output audio device at index %1").arg(deviceIndex); + return 404; + } + + m_mainCore.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(deviceName, outputDeviceInfo); + + if (audioOutputKeys.contains("sampleRate")) { + outputDeviceInfo.sampleRate = response.getSampleRate(); + } + if (audioOutputKeys.contains("copyToUDP")) { + outputDeviceInfo.copyToUDP = response.getCopyToUdp() == 0 ? 0 : 1; + } + if (audioOutputKeys.contains("udpUsesRTP")) { + outputDeviceInfo.udpUseRTP = response.getUdpUsesRtp() == 0 ? 0 : 1; + } + if (audioOutputKeys.contains("udpChannelMode")) { + outputDeviceInfo.udpChannelMode = static_cast(response.getUdpChannelMode() % 4); + } + if (audioOutputKeys.contains("udpAddress")) { + outputDeviceInfo.udpAddress = *response.getUdpAddress(); + } + if (audioOutputKeys.contains("udpPort")) { + outputDeviceInfo.udpPort = response.getUdpPort() % (1<<16); + } + + m_mainCore.m_dspEngine->getAudioDeviceManager()->setOutputDeviceInfo(deviceIndex, outputDeviceInfo); + m_mainCore.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(deviceName, outputDeviceInfo); + + response.setSampleRate(outputDeviceInfo.sampleRate); + response.setCopyToUdp(outputDeviceInfo.copyToUDP == 0 ? 0 : 1); + response.setUdpUsesRtp(outputDeviceInfo.udpUseRTP == 0 ? 0 : 1); + response.setUdpChannelMode(outputDeviceInfo.udpChannelMode % 4); + + if (response.getUdpAddress()) { + *response.getUdpAddress() = outputDeviceInfo.udpAddress; + } else { + response.setUdpAddress(new QString(outputDeviceInfo.udpAddress)); + } + + response.setUdpPort(outputDeviceInfo.udpPort % (1<<16)); return 200; } diff --git a/sdrsrv/webapi/webapiadaptersrv.h b/sdrsrv/webapi/webapiadaptersrv.h index 0aea35ba6..62d9bcc6a 100644 --- a/sdrsrv/webapi/webapiadaptersrv.h +++ b/sdrsrv/webapi/webapiadaptersrv.h @@ -63,8 +63,14 @@ public: SWGSDRangel::SWGAudioDevices& response, SWGSDRangel::SWGErrorResponse& error); - virtual int instanceAudioPatch( - SWGSDRangel::SWGAudioDevicesSelect& response, + virtual int instanceAudioInputPatch( + SWGSDRangel::SWGAudioInputDevice& response, + const QStringList& audioInputKeys, + SWGSDRangel::SWGErrorResponse& error); + + virtual int instanceAudioOutputPatch( + SWGSDRangel::SWGAudioOutputDevice& response, + const QStringList& audioOutputKeys, SWGSDRangel::SWGErrorResponse& error); virtual int instanceLocationGet( diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index e30f9adf4..3a9eb191a 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -182,9 +182,11 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + /sdrangel/audio/input/set: + x-swagger-router-controller: instance patch: - description: Set audio devices - operationId: instanceAudioPatch + description: Set audio input device paramaters + operationId: instanceAudioInputSetPatch tags: - Instance consumes: @@ -192,15 +194,49 @@ paths: parameters: - name: body in: body - description: Select audio devices to use for this instance + description: Audio input parameters. Index is used to identify the device. Only settable fields are considered. required: true schema: - $ref: "#/definitions/AudioDevicesSelect" + $ref: "#/definitions/AudioInputDevice" responses: "200": - description: Success + description: Success. Returns actual data in particular the actual sample rate. schema: - $ref: "#/definitions/AudioDevicesSelect" + $ref: "#/definitions/AudioInputDevice" + "404": + description: Audio input device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + /sdrangel/audio/output/set: + x-swagger-router-controller: instance + patch: + description: Set audio output device parameters + operationId: instanceAudioOutputSetPatch + tags: + - Instance + consumes: + - application/json + parameters: + - name: body + in: body + description: Audio output parameters. Index is used to identify the device. Only settable fields are considered. + required: true + schema: + $ref: "#/definitions/AudioOutputDevice" + responses: + "200": + description: Success. Returns actual data in particular the actual sample rate. + schema: + $ref: "#/definitions/AudioOutputDevice" + "404": + description: Audio output device not found + schema: + $ref: "#/definitions/ErrorResponse" "500": $ref: "#/responses/Response_500" "501": @@ -1355,64 +1391,83 @@ definitions: AudioDevices: description: "List of audio devices available in the system" required: - - inputVolume - nbInputDevices - - inputDeviceSelectedIndex - nbOutputDevices - - outputDeviceSelectedIndex properties: - inputVolume: - description: "Audio input volume [0.0..1.0]" - type: number - format: float nbInputDevices: description: "Number of input audio devices" type: integer - inputDeviceSelectedIndex: - description: "Index of selected input audio devices (-1 if default)" - type: integer inputDevices: description: "List of input devices" type: array items: - $ref: "#/definitions/AudioDevice" + $ref: "#/definitions/AudioInputDevice" nbOutputDevices: description: "Number of output audio devices" type: integer - outputDeviceSelectedIndex: - description: "Index of selected output audio devices (-1 if default)" - type: integer outputDevices: description: "List of output devices" type: array items: - $ref: "#/definitions/AudioDevice" + $ref: "#/definitions/AudioOutputDevice" - AudioDevice: - description: "Audio device" + AudioInputDevice: + description: "Audio input device" properties: name: description: "Displayable name of the device" type: string - - AudioDevicesSelect: - description: "Audio devices selected" - required: - - inputVolume - - inputIndex - - outputIndex - properties: - inputVolume: + index: + description: "Index in attached devices list. -1 for system default" + type: integer + sampleRate: + description: "Device sample rate in S/s" + type: integer + isSystemDefault: + description: "1 if this device is the system default else 0" + type: integer + defaultUnregistered: + description: "1 if this device is unregistered and therefore will inherit default values else 0" + type: integer + volume: description: "Audio input volume [0.0..1.0]" type: number format: float - inputIndex: - description: "Index of the audio input device (-1 for default)" + + AudioOutputDevice: + description: "Audio output device" + properties: + name: + description: "Displayable name of the device" + type: string + index: + description: "Index in attached devices list. -1 for system default" type: integer - outputIndex: - description: "Index of the audio output device (-1 for default)" + sampleRate: + description: "Device sample rate in S/s" type: integer - + isSystemDefault: + description: "1 if this device is the system default else 0" + type: integer + defaultUnregistered: + description: "1 if this device is unregistered and therefore will inherit default values else 0" + type: integer + copyToUDP: + description: '1 if audio is copied to UDP else 0' + type: integer + udpUsesRTP: + description: '1 if RTP protocol is used over UDP else 0' + type: integer + udpChannelMode: + description: 'How audio data is copied to UDP: 0: left 1: right 2: mixed 3: stereo' + type: integer + udpAddress: + description: "UDP destination address" + type: string + udpPort: + description: "UDP destination port" + type: integer + LocationInformation: description: "Instance geolocation information" required: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index ef2be5151..cfd22bd01 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -780,75 +780,109 @@ margin-bottom: 20px; } }, "description" : "AirspyHF" -}; - defs.AudioDevice = { - "properties" : { - "name" : { - "type" : "string", - "description" : "Displayable name of the device" - } - }, - "description" : "Audio device" }; defs.AudioDevices = { - "required" : [ "inputDeviceSelectedIndex", "inputVolume", "nbInputDevices", "nbOutputDevices", "outputDeviceSelectedIndex" ], + "required" : [ "nbInputDevices", "nbOutputDevices" ], "properties" : { - "inputVolume" : { - "type" : "number", - "format" : "float", - "description" : "Audio input volume [0.0..1.0]" - }, "nbInputDevices" : { "type" : "integer", "description" : "Number of input audio devices" }, - "inputDeviceSelectedIndex" : { - "type" : "integer", - "description" : "Index of selected input audio devices (-1 if default)" - }, "inputDevices" : { "type" : "array", "description" : "List of input devices", "items" : { - "$ref" : "#/definitions/AudioDevice" + "$ref" : "#/definitions/AudioInputDevice" } }, "nbOutputDevices" : { "type" : "integer", "description" : "Number of output audio devices" }, - "outputDeviceSelectedIndex" : { - "type" : "integer", - "description" : "Index of selected output audio devices (-1 if default)" - }, "outputDevices" : { "type" : "array", "description" : "List of output devices", "items" : { - "$ref" : "#/definitions/AudioDevice" + "$ref" : "#/definitions/AudioOutputDevice" } } }, "description" : "List of audio devices available in the system" }; - defs.AudioDevicesSelect = { - "required" : [ "inputIndex", "inputVolume", "outputIndex" ], + defs.AudioInputDevice = { "properties" : { - "inputVolume" : { + "name" : { + "type" : "string", + "description" : "Displayable name of the device" + }, + "index" : { + "type" : "integer", + "description" : "Index in attached devices list. -1 for system default" + }, + "sampleRate" : { + "type" : "integer", + "description" : "Device sample rate in S/s" + }, + "isSystemDefault" : { + "type" : "integer", + "description" : "1 if this device is the system default else 0" + }, + "defaultUnregistered" : { + "type" : "integer", + "description" : "1 if this device is unregistered and therefore will inherit default values else 0" + }, + "volume" : { "type" : "number", "format" : "float", "description" : "Audio input volume [0.0..1.0]" - }, - "inputIndex" : { - "type" : "integer", - "description" : "Index of the audio input device (-1 for default)" - }, - "outputIndex" : { - "type" : "integer", - "description" : "Index of the audio output device (-1 for default)" } }, - "description" : "Audio devices selected" + "description" : "Audio input device" +}; + defs.AudioOutputDevice = { + "properties" : { + "name" : { + "type" : "string", + "description" : "Displayable name of the device" + }, + "index" : { + "type" : "integer", + "description" : "Index in attached devices list. -1 for system default" + }, + "sampleRate" : { + "type" : "integer", + "description" : "Device sample rate in S/s" + }, + "isSystemDefault" : { + "type" : "integer", + "description" : "1 if this device is the system default else 0" + }, + "defaultUnregistered" : { + "type" : "integer", + "description" : "1 if this device is unregistered and therefore will inherit default values else 0" + }, + "copyToUDP" : { + "type" : "integer", + "description" : "1 if audio is copied to UDP else 0" + }, + "udpUsesRTP" : { + "type" : "integer", + "description" : "1 if RTP protocol is used over UDP else 0" + }, + "udpChannelMode" : { + "type" : "integer", + "description" : "How audio data is copied to UDP: 0: left 1: right 2: mixed 3: stereo" + }, + "udpAddress" : { + "type" : "string", + "description" : "UDP destination address" + }, + "udpPort" : { + "type" : "integer", + "description" : "UDP destination port" + } + }, + "description" : "Audio output device" }; defs.CWKeyerSettings = { "properties" : { @@ -1905,8 +1939,11 @@ margin-bottom: 20px;
  • instanceAudioGet
  • -
  • - instanceAudioPatch +
  • + instanceAudioInputSetPatch +
  • +
  • + instanceAudioOutputSetPatch
  • instanceChannels @@ -10766,41 +10803,41 @@ except ApiException as e:

  • -
    -
    +
    +
    -

    instanceAudioPatch

    +

    instanceAudioInputSetPatch

    -

    Set audio devices

    +

    Set audio input device paramaters


    -
    /sdrangel/audio
    +
    /sdrangel/audio/input/set

    Usage and SDK Samples

    -
    -
    curl -X PATCH "http://localhost/sdrangel/audio"
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/input/set"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -10814,47 +10851,47 @@ public class InstanceApiExample {
         public static void main(String[] args) {
             
             InstanceApi apiInstance = new InstanceApi();
    -        AudioDevicesSelect body = ; // AudioDevicesSelect | Select audio devices to use for this instance
    +        AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioDevicesSelect result = apiInstance.instanceAudioPatch(body);
    +            AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputSetPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.InstanceApi;
     
     public class InstanceApiExample {
     
         public static void main(String[] args) {
             InstanceApi apiInstance = new InstanceApi();
    -        AudioDevicesSelect body = ; // AudioDevicesSelect | Select audio devices to use for this instance
    +        AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioDevicesSelect result = apiInstance.instanceAudioPatch(body);
    +            AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputSetPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    -
    AudioDevicesSelect *body = ; // Select audio devices to use for this instance
    +                            
    +
    AudioInputDevice *body = ; // Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -[apiInstance instanceAudioPatchWith:body
    -              completionHandler: ^(AudioDevicesSelect output, NSError* error) {
    +[apiInstance instanceAudioInputSetPatchWith:body
    +              completionHandler: ^(AudioInputDevice output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
                                 }
    @@ -10865,12 +10902,12 @@ InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.InstanceApi()
     
    -var body = ; // {AudioDevicesSelect} Select audio devices to use for this instance
    +var body = ; // {AudioInputDevice} Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     
     var callback = function(error, data, response) {
    @@ -10880,14 +10917,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.instanceAudioPatch(body, callback);
    +api.instanceAudioInputSetPatch(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -10896,22 +10933,22 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class instanceAudioPatchExample
    +    public class instanceAudioInputSetPatchExample
         {
             public void main()
             {
                 
                 var apiInstance = new InstanceApi();
    -            var body = new AudioDevicesSelect(); // AudioDevicesSelect | Select audio devices to use for this instance
    +            var body = new AudioInputDevice(); // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
                 try
                 {
    -                AudioDevicesSelect result = apiInstance.instanceAudioPatch(body);
    +                AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling InstanceApi.instanceAudioPatch: " + e.Message );
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioInputSetPatch: " + e.Message );
                 }
             }
         }
    @@ -10919,40 +10956,40 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
     $api_instance = new Swagger\Client\Api\InstanceApi();
    -$body = ; // AudioDevicesSelect | Select audio devices to use for this instance
    +$body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     try {
    -    $result = $api_instance->instanceAudioPatch($body);
    +    $result = $api_instance->instanceAudioInputSetPatch($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling InstanceApi->instanceAudioPatch: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling InstanceApi->instanceAudioInputSetPatch: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::InstanceApi;
     
     my $api_instance = SWGSDRangel::InstanceApi->new();
    -my $body = SWGSDRangel::Object::AudioDevicesSelect->new(); # AudioDevicesSelect | Select audio devices to use for this instance
    +my $body = SWGSDRangel::Object::AudioInputDevice->new(); # AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     eval { 
    -    my $result = $api_instance->instanceAudioPatch(body => $body);
    +    my $result = $api_instance->instanceAudioInputSetPatch(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling InstanceApi->instanceAudioPatch: $@\n";
    +    warn "Exception when calling InstanceApi->instanceAudioInputSetPatch: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -10961,13 +10998,13 @@ from pprint import pprint
     
     # create an instance of the API class
     api_instance = swagger_sdrangel.InstanceApi()
    -body =  # AudioDevicesSelect | Select audio devices to use for this instance
    +body =  # AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     try: 
    -    api_response = api_instance.instance_audio_patch(body)
    +    api_response = api_instance.instance_audio_input_set_patch(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling InstanceApi->instanceAudioPatch: %s\n" % e)
    + print("Exception when calling InstanceApi->instanceAudioInputSetPatch: %s\n" % e)
    @@ -10990,10 +11027,10 @@ $(document).ready(function() { var schemaWrapper = { "in" : "body", "name" : "body", - "description" : "Select audio devices to use for this instance", + "description" : "Audio input parameters. Index is used to identify the device. Only settable fields are considered.", "required" : true, "schema" : { - "$ref" : "#/definitions/AudioDevicesSelect" + "$ref" : "#/definitions/AudioInputDevice" } }; var schema = schemaWrapper.schema; @@ -11007,12 +11044,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_instanceAudioPatch_body'); + var result = $('#d2e199_instanceAudioInputSetPatch_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -11021,24 +11058,24 @@ $(document).ready(function() {

    Responses

    -

    Status: 200 - Success

    +

    Status: 200 - Success. Returns actual data in particular the actual sample rate.

    -
    -
    +
    +
    - + +
    +
    + +

    Status: 404 - Audio input device not found

    + + + +
    +
    +
    + +
    +
    @@ -11068,14 +11148,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11111,14 +11191,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioOutputSetPatch

    +

    +
    +
    +
    +

    +

    Set audio output device parameters

    +

    +
    +
    /sdrangel/audio/output/set
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/output/set"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +        try {
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputSetPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +        try {
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputSetPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    AudioOutputDevice *body = ; // Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioOutputSetPatchWith:body
    +              completionHandler: ^(AudioOutputDevice output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var body = ; // {AudioOutputDevice} Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioOutputSetPatch(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioOutputSetPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +            var body = new AudioOutputDevice(); // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +            try
    +            {
    +                AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputSetPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +$body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +try {
    +    $result = $api_instance->instanceAudioOutputSetPatch($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioOutputSetPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +my $body = SWGSDRangel::Object::AudioOutputDevice->new(); # AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioOutputSetPatch(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioOutputSetPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +body =  # AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
    +
    +try: 
    +    api_response = api_instance.instance_audio_output_set_patch(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioOutputSetPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - Success. Returns actual data in particular the actual sample rate.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Audio output device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    +
    @@ -18045,7 +18555,7 @@ except ApiException as e:
    - Generated 2018-03-28T09:46:11.248+02:00 + Generated 2018-03-29T00:32:59.208+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp index 26ee62aa0..c6cb84a07 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.cpp @@ -28,18 +28,12 @@ SWGAudioDevices::SWGAudioDevices(QString* json) { } SWGAudioDevices::SWGAudioDevices() { - input_volume = 0.0f; - m_input_volume_isSet = false; nb_input_devices = 0; m_nb_input_devices_isSet = false; - input_device_selected_index = 0; - m_input_device_selected_index_isSet = false; input_devices = nullptr; m_input_devices_isSet = false; nb_output_devices = 0; m_nb_output_devices_isSet = false; - output_device_selected_index = 0; - m_output_device_selected_index_isSet = false; output_devices = nullptr; m_output_devices_isSet = false; } @@ -50,27 +44,19 @@ SWGAudioDevices::~SWGAudioDevices() { void SWGAudioDevices::init() { - input_volume = 0.0f; - m_input_volume_isSet = false; nb_input_devices = 0; m_nb_input_devices_isSet = false; - input_device_selected_index = 0; - m_input_device_selected_index_isSet = false; - input_devices = new QList(); + input_devices = new QList(); m_input_devices_isSet = false; nb_output_devices = 0; m_nb_output_devices_isSet = false; - output_device_selected_index = 0; - m_output_device_selected_index_isSet = false; - output_devices = new QList(); + output_devices = new QList(); m_output_devices_isSet = false; } void SWGAudioDevices::cleanup() { - - if(input_devices != nullptr) { auto arr = input_devices; for(auto o: *arr) { @@ -79,7 +65,6 @@ SWGAudioDevices::cleanup() { delete input_devices; } - if(output_devices != nullptr) { auto arr = output_devices; for(auto o: *arr) { @@ -100,20 +85,14 @@ SWGAudioDevices::fromJson(QString &json) { void SWGAudioDevices::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&input_volume, pJson["inputVolume"], "float", ""); - ::SWGSDRangel::setValue(&nb_input_devices, pJson["nbInputDevices"], "qint32", ""); - ::SWGSDRangel::setValue(&input_device_selected_index, pJson["inputDeviceSelectedIndex"], "qint32", ""); - - ::SWGSDRangel::setValue(&input_devices, pJson["inputDevices"], "QList", "SWGAudioDevice"); + ::SWGSDRangel::setValue(&input_devices, pJson["inputDevices"], "QList", "SWGAudioInputDevice"); ::SWGSDRangel::setValue(&nb_output_devices, pJson["nbOutputDevices"], "qint32", ""); - ::SWGSDRangel::setValue(&output_device_selected_index, pJson["outputDeviceSelectedIndex"], "qint32", ""); - - ::SWGSDRangel::setValue(&output_devices, pJson["outputDevices"], "QList", "SWGAudioDevice"); + ::SWGSDRangel::setValue(&output_devices, pJson["outputDevices"], "QList", "SWGAudioOutputDevice"); } QString @@ -130,41 +109,22 @@ SWGAudioDevices::asJson () QJsonObject* SWGAudioDevices::asJsonObject() { QJsonObject* obj = new QJsonObject(); - if(m_input_volume_isSet){ - obj->insert("inputVolume", QJsonValue(input_volume)); - } if(m_nb_input_devices_isSet){ obj->insert("nbInputDevices", QJsonValue(nb_input_devices)); } - if(m_input_device_selected_index_isSet){ - obj->insert("inputDeviceSelectedIndex", QJsonValue(input_device_selected_index)); - } if(input_devices->size() > 0){ - toJsonArray((QList*)input_devices, obj, "inputDevices", "SWGAudioDevice"); + toJsonArray((QList*)input_devices, obj, "inputDevices", "SWGAudioInputDevice"); } if(m_nb_output_devices_isSet){ obj->insert("nbOutputDevices", QJsonValue(nb_output_devices)); } - if(m_output_device_selected_index_isSet){ - obj->insert("outputDeviceSelectedIndex", QJsonValue(output_device_selected_index)); - } if(output_devices->size() > 0){ - toJsonArray((QList*)output_devices, obj, "outputDevices", "SWGAudioDevice"); + toJsonArray((QList*)output_devices, obj, "outputDevices", "SWGAudioOutputDevice"); } return obj; } -float -SWGAudioDevices::getInputVolume() { - return input_volume; -} -void -SWGAudioDevices::setInputVolume(float input_volume) { - this->input_volume = input_volume; - this->m_input_volume_isSet = true; -} - qint32 SWGAudioDevices::getNbInputDevices() { return nb_input_devices; @@ -175,22 +135,12 @@ SWGAudioDevices::setNbInputDevices(qint32 nb_input_devices) { this->m_nb_input_devices_isSet = true; } -qint32 -SWGAudioDevices::getInputDeviceSelectedIndex() { - return input_device_selected_index; -} -void -SWGAudioDevices::setInputDeviceSelectedIndex(qint32 input_device_selected_index) { - this->input_device_selected_index = input_device_selected_index; - this->m_input_device_selected_index_isSet = true; -} - -QList* +QList* SWGAudioDevices::getInputDevices() { return input_devices; } void -SWGAudioDevices::setInputDevices(QList* input_devices) { +SWGAudioDevices::setInputDevices(QList* input_devices) { this->input_devices = input_devices; this->m_input_devices_isSet = true; } @@ -205,22 +155,12 @@ SWGAudioDevices::setNbOutputDevices(qint32 nb_output_devices) { this->m_nb_output_devices_isSet = true; } -qint32 -SWGAudioDevices::getOutputDeviceSelectedIndex() { - return output_device_selected_index; -} -void -SWGAudioDevices::setOutputDeviceSelectedIndex(qint32 output_device_selected_index) { - this->output_device_selected_index = output_device_selected_index; - this->m_output_device_selected_index_isSet = true; -} - -QList* +QList* SWGAudioDevices::getOutputDevices() { return output_devices; } void -SWGAudioDevices::setOutputDevices(QList* output_devices) { +SWGAudioDevices::setOutputDevices(QList* output_devices) { this->output_devices = output_devices; this->m_output_devices_isSet = true; } @@ -230,12 +170,9 @@ bool SWGAudioDevices::isSet(){ bool isObjectUpdated = false; do{ - if(m_input_volume_isSet){ isObjectUpdated = true; break;} if(m_nb_input_devices_isSet){ isObjectUpdated = true; break;} - if(m_input_device_selected_index_isSet){ isObjectUpdated = true; break;} if(input_devices->size() > 0){ isObjectUpdated = true; break;} if(m_nb_output_devices_isSet){ isObjectUpdated = true; break;} - if(m_output_device_selected_index_isSet){ isObjectUpdated = true; break;} if(output_devices->size() > 0){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h index eee9d24cd..b94eb307a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h +++ b/swagger/sdrangel/code/qt5/client/SWGAudioDevices.h @@ -22,7 +22,8 @@ #include -#include "SWGAudioDevice.h" +#include "SWGAudioInputDevice.h" +#include "SWGAudioOutputDevice.h" #include #include "SWGObject.h" @@ -43,50 +44,32 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGAudioDevices* fromJson(QString &jsonString) override; - float getInputVolume(); - void setInputVolume(float input_volume); - qint32 getNbInputDevices(); void setNbInputDevices(qint32 nb_input_devices); - qint32 getInputDeviceSelectedIndex(); - void setInputDeviceSelectedIndex(qint32 input_device_selected_index); - - QList* getInputDevices(); - void setInputDevices(QList* input_devices); + QList* getInputDevices(); + void setInputDevices(QList* input_devices); qint32 getNbOutputDevices(); void setNbOutputDevices(qint32 nb_output_devices); - qint32 getOutputDeviceSelectedIndex(); - void setOutputDeviceSelectedIndex(qint32 output_device_selected_index); - - QList* getOutputDevices(); - void setOutputDevices(QList* output_devices); + QList* getOutputDevices(); + void setOutputDevices(QList* output_devices); virtual bool isSet() override; private: - float input_volume; - bool m_input_volume_isSet; - qint32 nb_input_devices; bool m_nb_input_devices_isSet; - qint32 input_device_selected_index; - bool m_input_device_selected_index_isSet; - - QList* input_devices; + QList* input_devices; bool m_input_devices_isSet; qint32 nb_output_devices; bool m_nb_output_devices_isSet; - qint32 output_device_selected_index; - bool m_output_device_selected_index_isSet; - - QList* output_devices; + QList* output_devices; bool m_output_devices_isSet; }; diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp new file mode 100644 index 000000000..38526f6eb --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.cpp @@ -0,0 +1,213 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAudioInputDevice.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAudioInputDevice::SWGAudioInputDevice(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAudioInputDevice::SWGAudioInputDevice() { + name = nullptr; + m_name_isSet = false; + index = 0; + m_index_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + is_system_default = 0; + m_is_system_default_isSet = false; + default_unregistered = 0; + m_default_unregistered_isSet = false; + volume = 0.0f; + m_volume_isSet = false; +} + +SWGAudioInputDevice::~SWGAudioInputDevice() { + this->cleanup(); +} + +void +SWGAudioInputDevice::init() { + name = new QString(""); + m_name_isSet = false; + index = 0; + m_index_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + is_system_default = 0; + m_is_system_default_isSet = false; + default_unregistered = 0; + m_default_unregistered_isSet = false; + volume = 0.0f; + m_volume_isSet = false; +} + +void +SWGAudioInputDevice::cleanup() { + if(name != nullptr) { + delete name; + } + + + + + +} + +SWGAudioInputDevice* +SWGAudioInputDevice::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAudioInputDevice::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&name, pJson["name"], "QString", "QString"); + + ::SWGSDRangel::setValue(&index, pJson["index"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&is_system_default, pJson["isSystemDefault"], "qint32", ""); + + ::SWGSDRangel::setValue(&default_unregistered, pJson["defaultUnregistered"], "qint32", ""); + + ::SWGSDRangel::setValue(&volume, pJson["volume"], "float", ""); + +} + +QString +SWGAudioInputDevice::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAudioInputDevice::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(name != nullptr && *name != QString("")){ + toJsonValue(QString("name"), name, obj, QString("QString")); + } + if(m_index_isSet){ + obj->insert("index", QJsonValue(index)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + if(m_is_system_default_isSet){ + obj->insert("isSystemDefault", QJsonValue(is_system_default)); + } + if(m_default_unregistered_isSet){ + obj->insert("defaultUnregistered", QJsonValue(default_unregistered)); + } + if(m_volume_isSet){ + obj->insert("volume", QJsonValue(volume)); + } + + return obj; +} + +QString* +SWGAudioInputDevice::getName() { + return name; +} +void +SWGAudioInputDevice::setName(QString* name) { + this->name = name; + this->m_name_isSet = true; +} + +qint32 +SWGAudioInputDevice::getIndex() { + return index; +} +void +SWGAudioInputDevice::setIndex(qint32 index) { + this->index = index; + this->m_index_isSet = true; +} + +qint32 +SWGAudioInputDevice::getSampleRate() { + return sample_rate; +} +void +SWGAudioInputDevice::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + +qint32 +SWGAudioInputDevice::getIsSystemDefault() { + return is_system_default; +} +void +SWGAudioInputDevice::setIsSystemDefault(qint32 is_system_default) { + this->is_system_default = is_system_default; + this->m_is_system_default_isSet = true; +} + +qint32 +SWGAudioInputDevice::getDefaultUnregistered() { + return default_unregistered; +} +void +SWGAudioInputDevice::setDefaultUnregistered(qint32 default_unregistered) { + this->default_unregistered = default_unregistered; + this->m_default_unregistered_isSet = true; +} + +float +SWGAudioInputDevice::getVolume() { + return volume; +} +void +SWGAudioInputDevice::setVolume(float volume) { + this->volume = volume; + this->m_volume_isSet = true; +} + + +bool +SWGAudioInputDevice::isSet(){ + bool isObjectUpdated = false; + do{ + if(name != nullptr && *name != QString("")){ isObjectUpdated = true; break;} + if(m_index_isSet){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_is_system_default_isSet){ isObjectUpdated = true; break;} + if(m_default_unregistered_isSet){ isObjectUpdated = true; break;} + if(m_volume_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h new file mode 100644 index 000000000..93210cb22 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAudioInputDevice.h @@ -0,0 +1,89 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAudioInputDevice.h + * + * Audio input device + */ + +#ifndef SWGAudioInputDevice_H_ +#define SWGAudioInputDevice_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAudioInputDevice: public SWGObject { +public: + SWGAudioInputDevice(); + SWGAudioInputDevice(QString* json); + virtual ~SWGAudioInputDevice(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAudioInputDevice* fromJson(QString &jsonString) override; + + QString* getName(); + void setName(QString* name); + + qint32 getIndex(); + void setIndex(qint32 index); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + qint32 getIsSystemDefault(); + void setIsSystemDefault(qint32 is_system_default); + + qint32 getDefaultUnregistered(); + void setDefaultUnregistered(qint32 default_unregistered); + + float getVolume(); + void setVolume(float volume); + + + virtual bool isSet() override; + +private: + QString* name; + bool m_name_isSet; + + qint32 index; + bool m_index_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + + qint32 is_system_default; + bool m_is_system_default_isSet; + + qint32 default_unregistered; + bool m_default_unregistered_isSet; + + float volume; + bool m_volume_isSet; + +}; + +} + +#endif /* SWGAudioInputDevice_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp new file mode 100644 index 000000000..49c62d4ff --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.cpp @@ -0,0 +1,299 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAudioOutputDevice.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAudioOutputDevice::SWGAudioOutputDevice(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAudioOutputDevice::SWGAudioOutputDevice() { + name = nullptr; + m_name_isSet = false; + index = 0; + m_index_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + is_system_default = 0; + m_is_system_default_isSet = false; + default_unregistered = 0; + m_default_unregistered_isSet = false; + copy_to_udp = 0; + m_copy_to_udp_isSet = false; + udp_uses_rtp = 0; + m_udp_uses_rtp_isSet = false; + udp_channel_mode = 0; + m_udp_channel_mode_isSet = false; + udp_address = nullptr; + m_udp_address_isSet = false; + udp_port = 0; + m_udp_port_isSet = false; +} + +SWGAudioOutputDevice::~SWGAudioOutputDevice() { + this->cleanup(); +} + +void +SWGAudioOutputDevice::init() { + name = new QString(""); + m_name_isSet = false; + index = 0; + m_index_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + is_system_default = 0; + m_is_system_default_isSet = false; + default_unregistered = 0; + m_default_unregistered_isSet = false; + copy_to_udp = 0; + m_copy_to_udp_isSet = false; + udp_uses_rtp = 0; + m_udp_uses_rtp_isSet = false; + udp_channel_mode = 0; + m_udp_channel_mode_isSet = false; + udp_address = new QString(""); + m_udp_address_isSet = false; + udp_port = 0; + m_udp_port_isSet = false; +} + +void +SWGAudioOutputDevice::cleanup() { + if(name != nullptr) { + delete name; + } + + + + + + + + if(udp_address != nullptr) { + delete udp_address; + } + +} + +SWGAudioOutputDevice* +SWGAudioOutputDevice::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAudioOutputDevice::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&name, pJson["name"], "QString", "QString"); + + ::SWGSDRangel::setValue(&index, pJson["index"], "qint32", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&is_system_default, pJson["isSystemDefault"], "qint32", ""); + + ::SWGSDRangel::setValue(&default_unregistered, pJson["defaultUnregistered"], "qint32", ""); + + ::SWGSDRangel::setValue(©_to_udp, pJson["copyToUDP"], "qint32", ""); + + ::SWGSDRangel::setValue(&udp_uses_rtp, pJson["udpUsesRTP"], "qint32", ""); + + ::SWGSDRangel::setValue(&udp_channel_mode, pJson["udpChannelMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&udp_address, pJson["udpAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&udp_port, pJson["udpPort"], "qint32", ""); + +} + +QString +SWGAudioOutputDevice::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAudioOutputDevice::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(name != nullptr && *name != QString("")){ + toJsonValue(QString("name"), name, obj, QString("QString")); + } + if(m_index_isSet){ + obj->insert("index", QJsonValue(index)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + if(m_is_system_default_isSet){ + obj->insert("isSystemDefault", QJsonValue(is_system_default)); + } + if(m_default_unregistered_isSet){ + obj->insert("defaultUnregistered", QJsonValue(default_unregistered)); + } + if(m_copy_to_udp_isSet){ + obj->insert("copyToUDP", QJsonValue(copy_to_udp)); + } + if(m_udp_uses_rtp_isSet){ + obj->insert("udpUsesRTP", QJsonValue(udp_uses_rtp)); + } + if(m_udp_channel_mode_isSet){ + obj->insert("udpChannelMode", QJsonValue(udp_channel_mode)); + } + if(udp_address != nullptr && *udp_address != QString("")){ + toJsonValue(QString("udpAddress"), udp_address, obj, QString("QString")); + } + if(m_udp_port_isSet){ + obj->insert("udpPort", QJsonValue(udp_port)); + } + + return obj; +} + +QString* +SWGAudioOutputDevice::getName() { + return name; +} +void +SWGAudioOutputDevice::setName(QString* name) { + this->name = name; + this->m_name_isSet = true; +} + +qint32 +SWGAudioOutputDevice::getIndex() { + return index; +} +void +SWGAudioOutputDevice::setIndex(qint32 index) { + this->index = index; + this->m_index_isSet = true; +} + +qint32 +SWGAudioOutputDevice::getSampleRate() { + return sample_rate; +} +void +SWGAudioOutputDevice::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + +qint32 +SWGAudioOutputDevice::getIsSystemDefault() { + return is_system_default; +} +void +SWGAudioOutputDevice::setIsSystemDefault(qint32 is_system_default) { + this->is_system_default = is_system_default; + this->m_is_system_default_isSet = true; +} + +qint32 +SWGAudioOutputDevice::getDefaultUnregistered() { + return default_unregistered; +} +void +SWGAudioOutputDevice::setDefaultUnregistered(qint32 default_unregistered) { + this->default_unregistered = default_unregistered; + this->m_default_unregistered_isSet = true; +} + +qint32 +SWGAudioOutputDevice::getCopyToUdp() { + return copy_to_udp; +} +void +SWGAudioOutputDevice::setCopyToUdp(qint32 copy_to_udp) { + this->copy_to_udp = copy_to_udp; + this->m_copy_to_udp_isSet = true; +} + +qint32 +SWGAudioOutputDevice::getUdpUsesRtp() { + return udp_uses_rtp; +} +void +SWGAudioOutputDevice::setUdpUsesRtp(qint32 udp_uses_rtp) { + this->udp_uses_rtp = udp_uses_rtp; + this->m_udp_uses_rtp_isSet = true; +} + +qint32 +SWGAudioOutputDevice::getUdpChannelMode() { + return udp_channel_mode; +} +void +SWGAudioOutputDevice::setUdpChannelMode(qint32 udp_channel_mode) { + this->udp_channel_mode = udp_channel_mode; + this->m_udp_channel_mode_isSet = true; +} + +QString* +SWGAudioOutputDevice::getUdpAddress() { + return udp_address; +} +void +SWGAudioOutputDevice::setUdpAddress(QString* udp_address) { + this->udp_address = udp_address; + this->m_udp_address_isSet = true; +} + +qint32 +SWGAudioOutputDevice::getUdpPort() { + return udp_port; +} +void +SWGAudioOutputDevice::setUdpPort(qint32 udp_port) { + this->udp_port = udp_port; + this->m_udp_port_isSet = true; +} + + +bool +SWGAudioOutputDevice::isSet(){ + bool isObjectUpdated = false; + do{ + if(name != nullptr && *name != QString("")){ isObjectUpdated = true; break;} + if(m_index_isSet){ isObjectUpdated = true; break;} + if(m_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_is_system_default_isSet){ isObjectUpdated = true; break;} + if(m_default_unregistered_isSet){ isObjectUpdated = true; break;} + if(m_copy_to_udp_isSet){ isObjectUpdated = true; break;} + if(m_udp_uses_rtp_isSet){ isObjectUpdated = true; break;} + if(m_udp_channel_mode_isSet){ isObjectUpdated = true; break;} + if(udp_address != nullptr && *udp_address != QString("")){ isObjectUpdated = true; break;} + if(m_udp_port_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h new file mode 100644 index 000000000..a5e7d5841 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAudioOutputDevice.h @@ -0,0 +1,113 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAudioOutputDevice.h + * + * Audio output device + */ + +#ifndef SWGAudioOutputDevice_H_ +#define SWGAudioOutputDevice_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAudioOutputDevice: public SWGObject { +public: + SWGAudioOutputDevice(); + SWGAudioOutputDevice(QString* json); + virtual ~SWGAudioOutputDevice(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAudioOutputDevice* fromJson(QString &jsonString) override; + + QString* getName(); + void setName(QString* name); + + qint32 getIndex(); + void setIndex(qint32 index); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + qint32 getIsSystemDefault(); + void setIsSystemDefault(qint32 is_system_default); + + qint32 getDefaultUnregistered(); + void setDefaultUnregistered(qint32 default_unregistered); + + qint32 getCopyToUdp(); + void setCopyToUdp(qint32 copy_to_udp); + + qint32 getUdpUsesRtp(); + void setUdpUsesRtp(qint32 udp_uses_rtp); + + qint32 getUdpChannelMode(); + void setUdpChannelMode(qint32 udp_channel_mode); + + QString* getUdpAddress(); + void setUdpAddress(QString* udp_address); + + qint32 getUdpPort(); + void setUdpPort(qint32 udp_port); + + + virtual bool isSet() override; + +private: + QString* name; + bool m_name_isSet; + + qint32 index; + bool m_index_isSet; + + qint32 sample_rate; + bool m_sample_rate_isSet; + + qint32 is_system_default; + bool m_is_system_default_isSet; + + qint32 default_unregistered; + bool m_default_unregistered_isSet; + + qint32 copy_to_udp; + bool m_copy_to_udp_isSet; + + qint32 udp_uses_rtp; + bool m_udp_uses_rtp_isSet; + + qint32 udp_channel_mode; + bool m_udp_channel_mode_isSet; + + QString* udp_address; + bool m_udp_address_isSet; + + qint32 udp_port; + bool m_udp_port_isSet; + +}; + +} + +#endif /* SWGAudioOutputDevice_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp index 912ac09e3..31ba25920 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp @@ -81,9 +81,9 @@ SWGInstanceApi::instanceAudioGetCallback(SWGHttpRequestWorker * worker) { } void -SWGInstanceApi::instanceAudioPatch(SWGAudioDevicesSelect& body) { +SWGInstanceApi::instanceAudioInputSetPatch(SWGAudioInputDevice& body) { QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio"); + fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/input/set"); @@ -104,13 +104,13 @@ SWGInstanceApi::instanceAudioPatch(SWGAudioDevicesSelect& body) { connect(worker, &SWGHttpRequestWorker::on_execution_finished, this, - &SWGInstanceApi::instanceAudioPatchCallback); + &SWGInstanceApi::instanceAudioInputSetPatchCallback); worker->execute(&input); } void -SWGInstanceApi::instanceAudioPatchCallback(SWGHttpRequestWorker * worker) { +SWGInstanceApi::instanceAudioInputSetPatchCallback(SWGHttpRequestWorker * worker) { QString msg; QString error_str = worker->error_str; QNetworkReply::NetworkError error_type = worker->error_type; @@ -124,14 +124,69 @@ SWGInstanceApi::instanceAudioPatchCallback(SWGHttpRequestWorker * worker) { QString json(worker->response); - SWGAudioDevicesSelect* output = static_cast(create(json, QString("SWGAudioDevicesSelect"))); + SWGAudioInputDevice* output = static_cast(create(json, QString("SWGAudioInputDevice"))); worker->deleteLater(); if (worker->error_type == QNetworkReply::NoError) { - emit instanceAudioPatchSignal(output); + emit instanceAudioInputSetPatchSignal(output); } else { - emit instanceAudioPatchSignalE(output, error_type, error_str); - emit instanceAudioPatchSignalEFull(worker, error_type, error_str); + emit instanceAudioInputSetPatchSignalE(output, error_type, error_str); + emit instanceAudioInputSetPatchSignalEFull(worker, error_type, error_str); + } +} + +void +SWGInstanceApi::instanceAudioOutputSetPatch(SWGAudioOutputDevice& body) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/output/set"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "PATCH"); + + + + QString output = body.asJson(); + input.request_body.append(output); + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGInstanceApi::instanceAudioOutputSetPatchCallback); + + worker->execute(&input); +} + +void +SWGInstanceApi::instanceAudioOutputSetPatchCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGAudioOutputDevice* output = static_cast(create(json, QString("SWGAudioOutputDevice"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit instanceAudioOutputSetPatchSignal(output); + } else { + emit instanceAudioOutputSetPatchSignalE(output, error_type, error_str); + emit instanceAudioOutputSetPatchSignalEFull(worker, error_type, error_str); } } diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h index 43fc70af0..dad11e8a8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h @@ -16,7 +16,8 @@ #include "SWGHttpRequest.h" #include "SWGAudioDevices.h" -#include "SWGAudioDevicesSelect.h" +#include "SWGAudioInputDevice.h" +#include "SWGAudioOutputDevice.h" #include "SWGDVSeralDevices.h" #include "SWGDeviceSetList.h" #include "SWGErrorResponse.h" @@ -48,7 +49,8 @@ public: QMap defaultHeaders; void instanceAudioGet(); - void instanceAudioPatch(SWGAudioDevicesSelect& body); + void instanceAudioInputSetPatch(SWGAudioInputDevice& body); + void instanceAudioOutputSetPatch(SWGAudioOutputDevice& body); void instanceChannels(qint32 tx); void instanceDVSerialPatch(qint32 dvserial); void instanceDelete(); @@ -69,7 +71,8 @@ public: private: void instanceAudioGetCallback (SWGHttpRequestWorker * worker); - void instanceAudioPatchCallback (SWGHttpRequestWorker * worker); + void instanceAudioInputSetPatchCallback (SWGHttpRequestWorker * worker); + void instanceAudioOutputSetPatchCallback (SWGHttpRequestWorker * worker); void instanceChannelsCallback (SWGHttpRequestWorker * worker); void instanceDVSerialPatchCallback (SWGHttpRequestWorker * worker); void instanceDeleteCallback (SWGHttpRequestWorker * worker); @@ -90,7 +93,8 @@ private: signals: void instanceAudioGetSignal(SWGAudioDevices* summary); - void instanceAudioPatchSignal(SWGAudioDevicesSelect* summary); + void instanceAudioInputSetPatchSignal(SWGAudioInputDevice* summary); + void instanceAudioOutputSetPatchSignal(SWGAudioOutputDevice* summary); void instanceChannelsSignal(SWGInstanceChannelsResponse* summary); void instanceDVSerialPatchSignal(SWGDVSeralDevices* summary); void instanceDeleteSignal(SWGInstanceSummaryResponse* summary); @@ -110,7 +114,8 @@ signals: void instanceSummarySignal(SWGInstanceSummaryResponse* summary); void instanceAudioGetSignalE(SWGAudioDevices* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void instanceAudioPatchSignalE(SWGAudioDevicesSelect* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioInputSetPatchSignalE(SWGAudioInputDevice* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioOutputSetPatchSignalE(SWGAudioOutputDevice* summary, QNetworkReply::NetworkError error_type, QString& error_str); void instanceChannelsSignalE(SWGInstanceChannelsResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); void instanceDVSerialPatchSignalE(SWGDVSeralDevices* summary, QNetworkReply::NetworkError error_type, QString& error_str); void instanceDeleteSignalE(SWGInstanceSummaryResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); @@ -130,7 +135,8 @@ signals: void instanceSummarySignalE(SWGInstanceSummaryResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); void instanceAudioGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void instanceAudioPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioInputSetPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioOutputSetPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void instanceChannelsSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void instanceDVSerialPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void instanceDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index c611101e4..a4425de8e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -17,9 +17,9 @@ #include "SWGAMDemodReport.h" #include "SWGAMDemodSettings.h" #include "SWGAirspyHFSettings.h" -#include "SWGAudioDevice.h" #include "SWGAudioDevices.h" -#include "SWGAudioDevicesSelect.h" +#include "SWGAudioInputDevice.h" +#include "SWGAudioOutputDevice.h" #include "SWGCWKeyerSettings.h" #include "SWGChannel.h" #include "SWGChannelListItem.h" @@ -71,14 +71,14 @@ namespace SWGSDRangel { if(QString("SWGAirspyHFSettings").compare(type) == 0) { return new SWGAirspyHFSettings(); } - if(QString("SWGAudioDevice").compare(type) == 0) { - return new SWGAudioDevice(); - } if(QString("SWGAudioDevices").compare(type) == 0) { return new SWGAudioDevices(); } - if(QString("SWGAudioDevicesSelect").compare(type) == 0) { - return new SWGAudioDevicesSelect(); + if(QString("SWGAudioInputDevice").compare(type) == 0) { + return new SWGAudioInputDevice(); + } + if(QString("SWGAudioOutputDevice").compare(type) == 0) { + return new SWGAudioOutputDevice(); } if(QString("SWGCWKeyerSettings").compare(type) == 0) { return new SWGCWKeyerSettings(); From b7065c8c831fcd6c5299e29d6ffb2898d908955c Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 01:43:31 +0200 Subject: [PATCH 201/956] Multiple audio support: Web API: implemented all interfaces to AudioDeviceManager --- sdrbase/resources/webapi/doc/html2/index.html | 1824 +++++++++++++++-- .../resources/webapi/doc/swagger/swagger.yaml | 98 +- sdrbase/webapi/webapiadapterinterface.cpp | 8 +- sdrbase/webapi/webapiadapterinterface.h | 64 +- sdrbase/webapi/webapirequestmapper.cpp | 152 +- sdrbase/webapi/webapirequestmapper.h | 6 +- sdrgui/webapi/webapiadaptergui.cpp | 82 + sdrgui/webapi/webapiadaptergui.h | 16 + sdrsrv/webapi/webapiadaptersrv.cpp | 82 + sdrsrv/webapi/webapiadaptersrv.h | 16 + swagger/sdrangel/api/swagger/swagger.yaml | 98 +- swagger/sdrangel/code/html2/index.html | 1824 +++++++++++++++-- .../code/qt5/client/SWGInstanceApi.cpp | 242 ++- .../sdrangel/code/qt5/client/SWGInstanceApi.h | 41 +- 14 files changed, 4193 insertions(+), 360 deletions(-) diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index cfd22bd01..635859c5d 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1939,11 +1939,23 @@ margin-bottom: 20px;
  • instanceAudioGet
  • -
  • - instanceAudioInputSetPatch +
  • + instanceAudioInputCleanupPatch
  • -
  • - instanceAudioOutputSetPatch +
  • + instanceAudioInputDelete +
  • +
  • + instanceAudioInputPatch +
  • +
  • + instanceAudioOutputCleanupPatch +
  • +
  • + instanceAudioOutputDelete +
  • +
  • + instanceAudioOutputPatch
  • instanceChannels @@ -10803,10 +10815,775 @@ except ApiException as e:

  • -
    -
    +
    +
    -

    instanceAudioInputSetPatch

    +

    instanceAudioInputCleanupPatch

    +

    +
    +
    +
    +

    +

    Remove registered parameters for devices not in list of available input devices for this instance

    +

    +
    +
    /sdrangel/audio/input/cleanup
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/input/cleanup"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        try {
    +            SuccessResponse result = apiInstance.instanceAudioInputCleanupPatch();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputCleanupPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        try {
    +            SuccessResponse result = apiInstance.instanceAudioInputCleanupPatch();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputCleanupPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioInputCleanupPatchWithCompletionHandler: 
    +              ^(SuccessResponse output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioInputCleanupPatch(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioInputCleanupPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +
    +            try
    +            {
    +                SuccessResponse result = apiInstance.instanceAudioInputCleanupPatch();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioInputCleanupPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +
    +try {
    +    $result = $api_instance->instanceAudioInputCleanupPatch();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioInputCleanupPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioInputCleanupPatch();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioInputCleanupPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +
    +try: 
    +    api_response = api_instance.instance_audio_input_cleanup_patch()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioInputCleanupPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - Success.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioInputDelete

    +

    +
    +
    +
    +

    +

    Delete audio input device paramaters and return to defaults

    +

    +
    +
    /sdrangel/audio/input/parameters
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X DELETE "http://localhost/sdrangel/audio/input/parameters"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +        try {
    +            AudioInputDevice result = apiInstance.instanceAudioInputDelete(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputDelete");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +        try {
    +            AudioInputDevice result = apiInstance.instanceAudioInputDelete(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputDelete");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    AudioInputDevice *body = ; // Audio input parameters. Index is used to identify the device.
    +
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioInputDeleteWith:body
    +              completionHandler: ^(AudioInputDevice output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var body = ; // {AudioInputDevice} Audio input parameters. Index is used to identify the device.
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioInputDelete(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioInputDeleteExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +            var body = new AudioInputDevice(); // AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +
    +            try
    +            {
    +                AudioInputDevice result = apiInstance.instanceAudioInputDelete(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioInputDelete: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +$body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +
    +try {
    +    $result = $api_instance->instanceAudioInputDelete($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioInputDelete: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +my $body = SWGSDRangel::Object::AudioInputDevice->new(); # AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioInputDelete(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioInputDelete: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +body =  # AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +
    +try: 
    +    api_response = api_instance.instance_audio_input_delete(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioInputDelete: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - Success. Returns default parameters.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Audio input device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioInputPatch

    @@ -10815,29 +11592,29 @@ except ApiException as e:

    Set audio input device paramaters


    -
    /sdrangel/audio/input/set
    +
    /sdrangel/audio/input/parameters

    Usage and SDK Samples

    -
    -
    curl -X PATCH "http://localhost/sdrangel/audio/input/set"
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/input/parameters"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -10853,17 +11630,17 @@ public class InstanceApiExample {
             InstanceApi apiInstance = new InstanceApi();
             AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
    +            AudioInputDevice result = apiInstance.instanceAudioInputPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioInputSetPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.InstanceApi;
     
     public class InstanceApiExample {
    @@ -10872,25 +11649,25 @@ public class InstanceApiExample {
             InstanceApi apiInstance = new InstanceApi();
             AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
    +            AudioInputDevice result = apiInstance.instanceAudioInputPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioInputSetPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    AudioInputDevice *body = ; // Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -[apiInstance instanceAudioInputSetPatchWith:body
    +[apiInstance instanceAudioInputPatchWith:body
                   completionHandler: ^(AudioInputDevice output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
    @@ -10902,7 +11679,7 @@ InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.InstanceApi()
    @@ -10917,14 +11694,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.instanceAudioInputSetPatch(body, callback);
    +api.instanceAudioInputPatch(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -10933,7 +11710,7 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class instanceAudioInputSetPatchExample
    +    public class instanceAudioInputPatchExample
         {
             public void main()
             {
    @@ -10943,12 +11720,12 @@ namespace Example
     
                 try
                 {
    -                AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
    +                AudioInputDevice result = apiInstance.instanceAudioInputPatch(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling InstanceApi.instanceAudioInputSetPatch: " + e.Message );
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioInputPatch: " + e.Message );
                 }
             }
         }
    @@ -10956,7 +11733,7 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
    @@ -10964,15 +11741,15 @@ $api_instance = new Swagger\Client\Api\InstanceApi();
     $body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     try {
    -    $result = $api_instance->instanceAudioInputSetPatch($body);
    +    $result = $api_instance->instanceAudioInputPatch($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling InstanceApi->instanceAudioInputSetPatch: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling InstanceApi->instanceAudioInputPatch: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::InstanceApi;
    @@ -10981,15 +11758,15 @@ my $api_instance = SWGSDRangel::InstanceApi->new();
     my $body = SWGSDRangel::Object::AudioInputDevice->new(); # AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     eval { 
    -    my $result = $api_instance->instanceAudioInputSetPatch(body => $body);
    +    my $result = $api_instance->instanceAudioInputPatch(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling InstanceApi->instanceAudioInputSetPatch: $@\n";
    +    warn "Exception when calling InstanceApi->instanceAudioInputPatch: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -11001,10 +11778,10 @@ api_instance = swagger_sdrangel.InstanceApi()
     body =  # AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     try: 
    -    api_response = api_instance.instance_audio_input_set_patch(body)
    +    api_response = api_instance.instance_audio_input_patch(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling InstanceApi->instanceAudioInputSetPatch: %s\n" % e)
    + print("Exception when calling InstanceApi->instanceAudioInputPatch: %s\n" % e)
    @@ -11044,12 +11821,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_instanceAudioInputSetPatch_body'); + var result = $('#d2e199_instanceAudioInputPatch_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -11062,14 +11839,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11105,14 +11882,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11148,14 +11925,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11191,14 +11968,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +

    -
    -
    +
    +
    -

    instanceAudioOutputSetPatch

    +

    instanceAudioOutputCleanupPatch

    +

    +
    +
    +
    +

    +

    Remove registered parameters for devices not in list of available output devices for this instance

    +

    +
    +
    /sdrangel/audio/output/cleanup
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/output/cleanup"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        try {
    +            SuccessResponse result = apiInstance.instanceAudioOutputCleanupPatch();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputCleanupPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        try {
    +            SuccessResponse result = apiInstance.instanceAudioOutputCleanupPatch();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputCleanupPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioOutputCleanupPatchWithCompletionHandler: 
    +              ^(SuccessResponse output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioOutputCleanupPatch(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioOutputCleanupPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +
    +            try
    +            {
    +                SuccessResponse result = apiInstance.instanceAudioOutputCleanupPatch();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputCleanupPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +
    +try {
    +    $result = $api_instance->instanceAudioOutputCleanupPatch();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioOutputCleanupPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioOutputCleanupPatch();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioOutputCleanupPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +
    +try: 
    +    api_response = api_instance.instance_audio_output_cleanup_patch()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioOutputCleanupPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - Success.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioOutputDelete

    +

    +
    +
    +
    +

    +

    Delete audio output device paramaters and return to defaults

    +

    +
    +
    /sdrangel/audio/output/parameters
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X DELETE "http://localhost/sdrangel/audio/output/parameters"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +        try {
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputDelete(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputDelete");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +        try {
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputDelete(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputDelete");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    AudioOutputDevice *body = ; // Audio output parameters. Index is used to identify the device.
    +
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioOutputDeleteWith:body
    +              completionHandler: ^(AudioOutputDevice output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var body = ; // {AudioOutputDevice} Audio output parameters. Index is used to identify the device.
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioOutputDelete(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioOutputDeleteExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +            var body = new AudioOutputDevice(); // AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +
    +            try
    +            {
    +                AudioOutputDevice result = apiInstance.instanceAudioOutputDelete(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputDelete: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +$body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +
    +try {
    +    $result = $api_instance->instanceAudioOutputDelete($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioOutputDelete: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +my $body = SWGSDRangel::Object::AudioOutputDevice->new(); # AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioOutputDelete(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioOutputDelete: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +body =  # AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +
    +try: 
    +    api_response = api_instance.instance_audio_output_delete(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioOutputDelete: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - Success. Returns actual data in particular the actual sample rate.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Audio output device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioOutputPatch

    @@ -11245,29 +12787,29 @@ $(document).ready(function() {

    Set audio output device parameters


    -
    /sdrangel/audio/output/set
    +
    /sdrangel/audio/output/parameters

    Usage and SDK Samples

    -
    -
    curl -X PATCH "http://localhost/sdrangel/audio/output/set"
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/output/parameters"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -11283,17 +12825,17 @@ public class InstanceApiExample {
             InstanceApi apiInstance = new InstanceApi();
             AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioOutputSetPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.InstanceApi;
     
     public class InstanceApiExample {
    @@ -11302,25 +12844,25 @@ public class InstanceApiExample {
             InstanceApi apiInstance = new InstanceApi();
             AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioOutputSetPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    AudioOutputDevice *body = ; // Audio output parameters. Index is used to identify the device. Only settable fields are considered.
     
     InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -[apiInstance instanceAudioOutputSetPatchWith:body
    +[apiInstance instanceAudioOutputPatchWith:body
                   completionHandler: ^(AudioOutputDevice output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
    @@ -11332,7 +12874,7 @@ InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.InstanceApi()
    @@ -11347,14 +12889,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.instanceAudioOutputSetPatch(body, callback);
    +api.instanceAudioOutputPatch(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -11363,7 +12905,7 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class instanceAudioOutputSetPatchExample
    +    public class instanceAudioOutputPatchExample
         {
             public void main()
             {
    @@ -11373,12 +12915,12 @@ namespace Example
     
                 try
                 {
    -                AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +                AudioOutputDevice result = apiInstance.instanceAudioOutputPatch(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputSetPatch: " + e.Message );
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputPatch: " + e.Message );
                 }
             }
         }
    @@ -11386,7 +12928,7 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
    @@ -11394,15 +12936,15 @@ $api_instance = new Swagger\Client\Api\InstanceApi();
     $body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
     
     try {
    -    $result = $api_instance->instanceAudioOutputSetPatch($body);
    +    $result = $api_instance->instanceAudioOutputPatch($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling InstanceApi->instanceAudioOutputSetPatch: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling InstanceApi->instanceAudioOutputPatch: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::InstanceApi;
    @@ -11411,15 +12953,15 @@ my $api_instance = SWGSDRangel::InstanceApi->new();
     my $body = SWGSDRangel::Object::AudioOutputDevice->new(); # AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
     
     eval { 
    -    my $result = $api_instance->instanceAudioOutputSetPatch(body => $body);
    +    my $result = $api_instance->instanceAudioOutputPatch(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling InstanceApi->instanceAudioOutputSetPatch: $@\n";
    +    warn "Exception when calling InstanceApi->instanceAudioOutputPatch: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -11431,10 +12973,10 @@ api_instance = swagger_sdrangel.InstanceApi()
     body =  # AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
     
     try: 
    -    api_response = api_instance.instance_audio_output_set_patch(body)
    +    api_response = api_instance.instance_audio_output_patch(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling InstanceApi->instanceAudioOutputSetPatch: %s\n" % e)
    + print("Exception when calling InstanceApi->instanceAudioOutputPatch: %s\n" % e)
    @@ -11474,12 +13016,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_instanceAudioOutputSetPatch_body'); + var result = $('#d2e199_instanceAudioOutputPatch_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -11492,14 +13034,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11535,14 +13077,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11578,14 +13120,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11621,14 +13163,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -18555,7 +20097,7 @@ except ApiException as e:
    - Generated 2018-03-29T00:32:59.208+02:00 + Generated 2018-03-29T01:40:29.446+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 275ad9e2b..052ee876b 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -182,11 +182,12 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - /sdrangel/audio/input/set: + + /sdrangel/audio/input/parameters: x-swagger-router-controller: instance patch: description: Set audio input device paramaters - operationId: instanceAudioInputSetPatch + operationId: instanceAudioInputPatch tags: - Instance consumes: @@ -211,12 +212,57 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + delete: + description: Delete audio input device paramaters and return to defaults + operationId: instanceAudioInputDelete + tags: + - Instance + consumes: + - application/json + parameters: + - name: body + in: body + description: Audio input parameters. Index is used to identify the device. + required: true + schema: + $ref: "#/definitions/AudioInputDevice" + responses: + "200": + description: Success. Returns default parameters. + schema: + $ref: "#/definitions/AudioInputDevice" + "404": + description: Audio input device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" - /sdrangel/audio/output/set: + + /sdrangel/audio/input/cleanup: + x-swagger-router-controller: instance + patch: + description: Remove registered parameters for devices not in list of available input devices for this instance + operationId: instanceAudioInputCleanupPatch + tags: + - Instance + responses: + "200": + description: Success. + schema: + $ref: "#/definitions/SuccessResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + /sdrangel/audio/output/parameters: x-swagger-router-controller: instance patch: description: Set audio output device parameters - operationId: instanceAudioOutputSetPatch + operationId: instanceAudioOutputPatch tags: - Instance consumes: @@ -241,6 +287,50 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + delete: + description: Delete audio output device paramaters and return to defaults + operationId: instanceAudioOutputDelete + tags: + - Instance + consumes: + - application/json + parameters: + - name: body + in: body + description: Audio output parameters. Index is used to identify the device. + required: true + schema: + $ref: "#/definitions/AudioOutputDevice" + responses: + "200": + description: Success. Returns actual data in particular the actual sample rate. + schema: + $ref: "#/definitions/AudioOutputDevice" + "404": + description: Audio output device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + /sdrangel/audio/output/cleanup: + x-swagger-router-controller: instance + patch: + description: Remove registered parameters for devices not in list of available output devices for this instance + operationId: instanceAudioOutputCleanupPatch + tags: + - Instance + responses: + "200": + description: Success. + schema: + $ref: "#/definitions/SuccessResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" /sdrangel/location: x-swagger-router-controller: instance diff --git a/sdrbase/webapi/webapiadapterinterface.cpp b/sdrbase/webapi/webapiadapterinterface.cpp index 8ffc45338..7fdf79c9a 100644 --- a/sdrbase/webapi/webapiadapterinterface.cpp +++ b/sdrbase/webapi/webapiadapterinterface.cpp @@ -23,10 +23,10 @@ QString WebAPIAdapterInterface::instanceDevicesURL = "/sdrangel/devices"; QString WebAPIAdapterInterface::instanceChannelsURL = "/sdrangel/channels"; QString WebAPIAdapterInterface::instanceLoggingURL = "/sdrangel/logging"; QString WebAPIAdapterInterface::instanceAudioURL = "/sdrangel/audio"; -QString WebAPIAdapterInterface::instanceAudioInputSetURL = "/sdrangel/audio/input/set"; -QString WebAPIAdapterInterface::instanceAudioOutputSetURL = "/sdrangel/audio/output/set"; -QString WebAPIAdapterInterface::instanceAudioInputUnsetURL = "/sdrangel/audio/input/unset"; -QString WebAPIAdapterInterface::instanceAudioOutputUnsetURL = "/sdrangel/audio/output/unset"; +QString WebAPIAdapterInterface::instanceAudioInputParametersURL = "/sdrangel/audio/input/parameters"; +QString WebAPIAdapterInterface::instanceAudioOutputParametersURL = "/sdrangel/audio/output/parameters"; +QString WebAPIAdapterInterface::instanceAudioInputCleanupURL = "/sdrangel/audio/input/cleanup"; +QString WebAPIAdapterInterface::instanceAudioOutputCleanupURL = "/sdrangel/audio/output/cleanup"; QString WebAPIAdapterInterface::instanceLocationURL = "/sdrangel/location"; QString WebAPIAdapterInterface::instanceDVSerialURL = "/sdrangel/dvserial"; QString WebAPIAdapterInterface::instancePresetsURL = "/sdrangel/presets"; diff --git a/sdrbase/webapi/webapiadapterinterface.h b/sdrbase/webapi/webapiadapterinterface.h index 9fa8e1dad..d76dd8492 100644 --- a/sdrbase/webapi/webapiadapterinterface.h +++ b/sdrbase/webapi/webapiadapterinterface.h @@ -153,7 +153,7 @@ public: } /** - * Handler of /sdrangel/audio/input (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * Handler of /sdrangel/audio/input/parameters (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels * returns the Http status code (default 501: not implemented) */ virtual int instanceAudioInputPatch( @@ -167,7 +167,7 @@ public: } /** - * Handler of /sdrangel/audio/output (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * Handler of /sdrangel/audio/output/parameters (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels * returns the Http status code (default 501: not implemented) */ virtual int instanceAudioOutputPatch( @@ -180,6 +180,58 @@ public: return 501; } + /** + * Handler of /sdrangel/audio/input/parameters (DELETE) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * returns the Http status code (default 501: not implemented) + */ + virtual int instanceAudioInputDelete( + SWGSDRangel::SWGAudioInputDevice& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) + { + error.init(); + *error.getMessage() = QString("Function not implemented"); + return 501; + } + + /** + * Handler of /sdrangel/audio/output/paramaters (DELETE) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * returns the Http status code (default 501: not implemented) + */ + virtual int instanceAudioOutputDelete( + SWGSDRangel::SWGAudioOutputDevice& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) + { + error.init(); + *error.getMessage() = QString("Function not implemented"); + return 501; + } + + /** + * Handler of /sdrangel/audio/input/cleanup (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * returns the Http status code (default 501: not implemented) + */ + virtual int instanceAudioInputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) + { + error.init(); + *error.getMessage() = QString("Function not implemented"); + return 501; + } + + /** + * Handler of /sdrangel/audio/output/cleanup (PATCH) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels + * returns the Http status code (default 501: not implemented) + */ + virtual int instanceAudioOutputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response __attribute__((unused)), + SWGSDRangel::SWGErrorResponse& error) + { + error.init(); + *error.getMessage() = QString("Function not implemented"); + return 501; + } + /** * Handler of /sdrangel/location (GET) swagger/sdrangel/code/html2/index.html#api-Default-instanceChannels * returns the Http status code (default 501: not implemented) @@ -568,10 +620,10 @@ public: static QString instanceChannelsURL; static QString instanceLoggingURL; static QString instanceAudioURL; - static QString instanceAudioInputSetURL; - static QString instanceAudioOutputSetURL; - static QString instanceAudioInputUnsetURL; - static QString instanceAudioOutputUnsetURL; + static QString instanceAudioInputParametersURL; + static QString instanceAudioOutputParametersURL; + static QString instanceAudioInputCleanupURL; + static QString instanceAudioOutputCleanupURL; static QString instanceLocationURL; static QString instanceDVSerialURL; static QString instancePresetsURL; diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 3ed016cde..8423b1a83 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -84,10 +84,14 @@ void WebAPIRequestMapper::service(qtwebapp::HttpRequest& request, qtwebapp::Http instanceLoggingService(request, response); } else if (path == WebAPIAdapterInterface::instanceAudioURL) { instanceAudioService(request, response); - } else if (path == WebAPIAdapterInterface::instanceAudioInputSetURL) { - instanceAudioInputSetService(request, response); - } else if (path == WebAPIAdapterInterface::instanceAudioOutputSetURL) { - instanceAudioOutputSetService(request, response); + } else if (path == WebAPIAdapterInterface::instanceAudioInputParametersURL) { + instanceAudioInputParametersService(request, response); + } else if (path == WebAPIAdapterInterface::instanceAudioOutputParametersURL) { + instanceAudioOutputParametersService(request, response); + } else if (path == WebAPIAdapterInterface::instanceAudioInputCleanupURL) { + instanceAudioInputCleanupService(request, response); + } else if (path == WebAPIAdapterInterface::instanceAudioOutputCleanupURL) { + instanceAudioOutputCleanupService(request, response); } else if (path == WebAPIAdapterInterface::instanceLocationURL) { instanceLocationService(request, response); } else if (path == WebAPIAdapterInterface::instanceDVSerialURL) { @@ -328,24 +332,24 @@ void WebAPIRequestMapper::instanceAudioService(qtwebapp::HttpRequest& request, q } } -void WebAPIRequestMapper::instanceAudioInputSetService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +void WebAPIRequestMapper::instanceAudioInputParametersService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) { // TODO SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); - if (request.getMethod() == "PATCH") + QString jsonStr = request.getBody(); + QJsonObject jsonObject; + + if (parseJsonBody(jsonStr, jsonObject, response)) { - QString jsonStr = request.getBody(); - QJsonObject jsonObject; + SWGSDRangel::SWGAudioInputDevice normalResponse; + resetAudioInputDevice(normalResponse); + QStringList audioInputDeviceKeys; - if (parseJsonBody(jsonStr, jsonObject, response)) + if (validateAudioInputDevice(normalResponse, jsonObject, audioInputDeviceKeys)) { - SWGSDRangel::SWGAudioInputDevice normalResponse; - resetAudioInputDevice(normalResponse); - QStringList audioInputDeviceKeys; - - if (validateAudioInputDevice(normalResponse, jsonObject, audioInputDeviceKeys)) + if (request.getMethod() == "PATCH") { int status = m_adapter->instanceAudioInputPatch( normalResponse, @@ -359,48 +363,61 @@ void WebAPIRequestMapper::instanceAudioInputSetService(qtwebapp::HttpRequest& re response.write(errorResponse.asJson().toUtf8()); } } + else if (request.getMethod() == "DELETE") + { + int status = m_adapter->instanceAudioInputDelete( + normalResponse, + errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } else { - response.setStatus(400,"Invalid JSON request"); + response.setStatus(405,"Invalid HTTP method"); errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON request"; + *errorResponse.getMessage() = "Invalid HTTP method"; response.write(errorResponse.asJson().toUtf8()); } } else { - response.setStatus(400,"Invalid JSON format"); + response.setStatus(400,"Invalid JSON request"); errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON format"; + *errorResponse.getMessage() = "Invalid JSON request"; response.write(errorResponse.asJson().toUtf8()); } } else { - response.setStatus(405,"Invalid HTTP method"); + response.setStatus(400,"Invalid JSON format"); errorResponse.init(); - *errorResponse.getMessage() = "Invalid HTTP method"; + *errorResponse.getMessage() = "Invalid JSON format"; response.write(errorResponse.asJson().toUtf8()); } } -void WebAPIRequestMapper::instanceAudioOutputSetService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +void WebAPIRequestMapper::instanceAudioOutputParametersService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) { SWGSDRangel::SWGErrorResponse errorResponse; response.setHeader("Content-Type", "application/json"); - if (request.getMethod() == "PATCH") + QString jsonStr = request.getBody(); + QJsonObject jsonObject; + + if (parseJsonBody(jsonStr, jsonObject, response)) { - QString jsonStr = request.getBody(); - QJsonObject jsonObject; + SWGSDRangel::SWGAudioOutputDevice normalResponse; + resetAudioOutputDevice(normalResponse); + QStringList audioOutputDeviceKeys; - if (parseJsonBody(jsonStr, jsonObject, response)) + if (validateAudioOutputDevice(normalResponse, jsonObject, audioOutputDeviceKeys)) { - SWGSDRangel::SWGAudioOutputDevice normalResponse; - resetAudioOutputDevice(normalResponse); - QStringList audioOutputDeviceKeys; - - if (validateAudioOutputDevice(normalResponse, jsonObject, audioOutputDeviceKeys)) + if (request.getMethod() == "PATCH") { int status = m_adapter->instanceAudioOutputPatch( normalResponse, @@ -414,19 +431,86 @@ void WebAPIRequestMapper::instanceAudioOutputSetService(qtwebapp::HttpRequest& r response.write(errorResponse.asJson().toUtf8()); } } + else if (request.getMethod() == "DELETE") + { + int status = m_adapter->instanceAudioOutputDelete( + normalResponse, + errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } else { - response.setStatus(400,"Invalid JSON request"); + response.setStatus(405,"Invalid HTTP method"); errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON request"; + *errorResponse.getMessage() = "Invalid HTTP method"; response.write(errorResponse.asJson().toUtf8()); } } else { - response.setStatus(400,"Invalid JSON format"); + response.setStatus(400,"Invalid JSON request"); errorResponse.init(); - *errorResponse.getMessage() = "Invalid JSON format"; + *errorResponse.getMessage() = "Invalid JSON request"; + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(400,"Invalid JSON format"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid JSON format"; + response.write(errorResponse.asJson().toUtf8()); + } +} + +void WebAPIRequestMapper::instanceAudioInputCleanupService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +{ + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + + if (request.getMethod() == "PATCH") + { + SWGSDRangel::SWGSuccessResponse normalResponse; + + int status = m_adapter->instanceAudioInputCleanupPatch(normalResponse, errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { + response.write(errorResponse.asJson().toUtf8()); + } + } + else + { + response.setStatus(405,"Invalid HTTP method"); + errorResponse.init(); + *errorResponse.getMessage() = "Invalid HTTP method"; + response.write(errorResponse.asJson().toUtf8()); + } +} + +void WebAPIRequestMapper::instanceAudioOutputCleanupService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response) +{ + SWGSDRangel::SWGErrorResponse errorResponse; + response.setHeader("Content-Type", "application/json"); + + if (request.getMethod() == "PATCH") + { + SWGSDRangel::SWGSuccessResponse normalResponse; + + int status = m_adapter->instanceAudioOutputCleanupPatch(normalResponse, errorResponse); + response.setStatus(status); + + if (status/100 == 2) { + response.write(normalResponse.asJson().toUtf8()); + } else { response.write(errorResponse.asJson().toUtf8()); } } diff --git a/sdrbase/webapi/webapirequestmapper.h b/sdrbase/webapi/webapirequestmapper.h index 9162ed154..33a598d30 100644 --- a/sdrbase/webapi/webapirequestmapper.h +++ b/sdrbase/webapi/webapirequestmapper.h @@ -52,8 +52,10 @@ private: void instanceChannelsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instanceLoggingService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instanceAudioService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void instanceAudioInputSetService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); - void instanceAudioOutputSetService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void instanceAudioInputParametersService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void instanceAudioOutputParametersService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void instanceAudioInputCleanupService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); + void instanceAudioOutputCleanupService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instanceLocationService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instanceDVSerialService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); void instancePresetsService(qtwebapp::HttpRequest& request, qtwebapp::HttpResponse& response); diff --git a/sdrgui/webapi/webapiadaptergui.cpp b/sdrgui/webapi/webapiadaptergui.cpp index 016a32d38..53246a3b8 100644 --- a/sdrgui/webapi/webapiadaptergui.cpp +++ b/sdrgui/webapi/webapiadaptergui.cpp @@ -394,6 +394,88 @@ int WebAPIAdapterGUI::instanceAudioOutputPatch( return 200; } +int WebAPIAdapterGUI::instanceAudioInputDelete( + SWGSDRangel::SWGAudioInputDevice& response, + SWGSDRangel::SWGErrorResponse& error) +{ + AudioDeviceManager::InputDeviceInfo inputDeviceInfo; + QString deviceName; + int deviceIndex = response.getIndex(); + + if (!m_mainWindow.m_dspEngine->getAudioDeviceManager()->getInputDeviceName(deviceIndex, deviceName)) + { + error.init(); + *error.getMessage() = QString("There is no audio input device at index %1").arg(deviceIndex); + return 404; + } + + m_mainWindow.m_dspEngine->getAudioDeviceManager()->unsetInputDeviceInfo(deviceIndex); + m_mainWindow.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(deviceName, inputDeviceInfo); + + response.setSampleRate(inputDeviceInfo.sampleRate); + response.setVolume(inputDeviceInfo.volume); + + return 200; +} + +int WebAPIAdapterGUI::instanceAudioOutputDelete( + SWGSDRangel::SWGAudioOutputDevice& response, + SWGSDRangel::SWGErrorResponse& error) +{ + AudioDeviceManager::OutputDeviceInfo outputDeviceInfo; + QString deviceName; + int deviceIndex = response.getIndex(); + + if (!m_mainWindow.m_dspEngine->getAudioDeviceManager()->getOutputDeviceName(deviceIndex, deviceName)) + { + error.init(); + *error.getMessage() = QString("There is no audio output device at index %1").arg(deviceIndex); + return 404; + } + + m_mainWindow.m_dspEngine->getAudioDeviceManager()->unsetInputDeviceInfo(deviceIndex); + m_mainWindow.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(deviceName, outputDeviceInfo); + + response.setSampleRate(outputDeviceInfo.sampleRate); + response.setCopyToUdp(outputDeviceInfo.copyToUDP == 0 ? 0 : 1); + response.setUdpUsesRtp(outputDeviceInfo.udpUseRTP == 0 ? 0 : 1); + response.setUdpChannelMode(outputDeviceInfo.udpChannelMode % 4); + + if (response.getUdpAddress()) { + *response.getUdpAddress() = outputDeviceInfo.udpAddress; + } else { + response.setUdpAddress(new QString(outputDeviceInfo.udpAddress)); + } + + response.setUdpPort(outputDeviceInfo.udpPort % (1<<16)); + + return 200; +} + +int WebAPIAdapterGUI::instanceAudioInputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response, + SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) +{ + m_mainWindow.m_dspEngine->getAudioDeviceManager()->inputInfosCleanup(); + + response.init(); + *response.getMessage() = QString("Unregistered parameters for devices not in list of available input devices for this instance"); + + return 200; +} + +int WebAPIAdapterGUI::instanceAudioOutputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response, + SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) +{ + m_mainWindow.m_dspEngine->getAudioDeviceManager()->outputInfosCleanup(); + + response.init(); + *response.getMessage() = QString("Unregistered parameters for devices not in list of available output devices for this instance"); + + return 200; +} + int WebAPIAdapterGUI::instanceLocationGet( SWGSDRangel::SWGLocationInformation& response, SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) diff --git a/sdrgui/webapi/webapiadaptergui.h b/sdrgui/webapi/webapiadaptergui.h index 29a6250bd..0f8fde22e 100644 --- a/sdrgui/webapi/webapiadaptergui.h +++ b/sdrgui/webapi/webapiadaptergui.h @@ -73,6 +73,22 @@ public: const QStringList& audioOutputKeys, SWGSDRangel::SWGErrorResponse& error); + virtual int instanceAudioInputDelete( + SWGSDRangel::SWGAudioInputDevice& response, + SWGSDRangel::SWGErrorResponse& error); + + virtual int instanceAudioOutputDelete( + SWGSDRangel::SWGAudioOutputDevice& response, + SWGSDRangel::SWGErrorResponse& error); + + virtual int instanceAudioInputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response, + SWGSDRangel::SWGErrorResponse& error); + + virtual int instanceAudioOutputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response, + SWGSDRangel::SWGErrorResponse& error); + virtual int instanceLocationGet( SWGSDRangel::SWGLocationInformation& response, SWGSDRangel::SWGErrorResponse& error); diff --git a/sdrsrv/webapi/webapiadaptersrv.cpp b/sdrsrv/webapi/webapiadaptersrv.cpp index 9ec6d7312..269b268a0 100644 --- a/sdrsrv/webapi/webapiadaptersrv.cpp +++ b/sdrsrv/webapi/webapiadaptersrv.cpp @@ -395,6 +395,88 @@ int WebAPIAdapterSrv::instanceAudioOutputPatch( return 200; } +int WebAPIAdapterSrv::instanceAudioInputDelete( + SWGSDRangel::SWGAudioInputDevice& response, + SWGSDRangel::SWGErrorResponse& error) +{ + AudioDeviceManager::InputDeviceInfo inputDeviceInfo; + QString deviceName; + int deviceIndex = response.getIndex(); + + if (!m_mainCore.m_dspEngine->getAudioDeviceManager()->getInputDeviceName(deviceIndex, deviceName)) + { + error.init(); + *error.getMessage() = QString("There is no audio input device at index %1").arg(deviceIndex); + return 404; + } + + m_mainCore.m_dspEngine->getAudioDeviceManager()->unsetInputDeviceInfo(deviceIndex); + m_mainCore.m_dspEngine->getAudioDeviceManager()->getInputDeviceInfo(deviceName, inputDeviceInfo); + + response.setSampleRate(inputDeviceInfo.sampleRate); + response.setVolume(inputDeviceInfo.volume); + + return 200; +} + +int WebAPIAdapterSrv::instanceAudioOutputDelete( + SWGSDRangel::SWGAudioOutputDevice& response, + SWGSDRangel::SWGErrorResponse& error) +{ + AudioDeviceManager::OutputDeviceInfo outputDeviceInfo; + QString deviceName; + int deviceIndex = response.getIndex(); + + if (!m_mainCore.m_dspEngine->getAudioDeviceManager()->getOutputDeviceName(deviceIndex, deviceName)) + { + error.init(); + *error.getMessage() = QString("There is no audio output device at index %1").arg(deviceIndex); + return 404; + } + + m_mainCore.m_dspEngine->getAudioDeviceManager()->unsetInputDeviceInfo(deviceIndex); + m_mainCore.m_dspEngine->getAudioDeviceManager()->getOutputDeviceInfo(deviceName, outputDeviceInfo); + + response.setSampleRate(outputDeviceInfo.sampleRate); + response.setCopyToUdp(outputDeviceInfo.copyToUDP == 0 ? 0 : 1); + response.setUdpUsesRtp(outputDeviceInfo.udpUseRTP == 0 ? 0 : 1); + response.setUdpChannelMode(outputDeviceInfo.udpChannelMode % 4); + + if (response.getUdpAddress()) { + *response.getUdpAddress() = outputDeviceInfo.udpAddress; + } else { + response.setUdpAddress(new QString(outputDeviceInfo.udpAddress)); + } + + response.setUdpPort(outputDeviceInfo.udpPort % (1<<16)); + + return 200; +} + +int WebAPIAdapterSrv::instanceAudioInputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response, + SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) +{ + m_mainCore.m_dspEngine->getAudioDeviceManager()->inputInfosCleanup(); + + response.init(); + *response.getMessage() = QString("Unregistered parameters for devices not in list of available input devices for this instance"); + + return 200; +} + +int WebAPIAdapterSrv::instanceAudioOutputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response, + SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) +{ + m_mainCore.m_dspEngine->getAudioDeviceManager()->outputInfosCleanup(); + + response.init(); + *response.getMessage() = QString("Unregistered parameters for devices not in list of available output devices for this instance"); + + return 200; +} + int WebAPIAdapterSrv::instanceLocationGet( SWGSDRangel::SWGLocationInformation& response, SWGSDRangel::SWGErrorResponse& error __attribute__((unused))) diff --git a/sdrsrv/webapi/webapiadaptersrv.h b/sdrsrv/webapi/webapiadaptersrv.h index 62d9bcc6a..ff1c74bfa 100644 --- a/sdrsrv/webapi/webapiadaptersrv.h +++ b/sdrsrv/webapi/webapiadaptersrv.h @@ -73,6 +73,22 @@ public: const QStringList& audioOutputKeys, SWGSDRangel::SWGErrorResponse& error); + virtual int instanceAudioInputDelete( + SWGSDRangel::SWGAudioInputDevice& response, + SWGSDRangel::SWGErrorResponse& error); + + virtual int instanceAudioOutputDelete( + SWGSDRangel::SWGAudioOutputDevice& response, + SWGSDRangel::SWGErrorResponse& error); + + virtual int instanceAudioInputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response, + SWGSDRangel::SWGErrorResponse& error); + + virtual int instanceAudioOutputCleanupPatch( + SWGSDRangel::SWGSuccessResponse& response, + SWGSDRangel::SWGErrorResponse& error); + virtual int instanceLocationGet( SWGSDRangel::SWGLocationInformation& response, SWGSDRangel::SWGErrorResponse& error); diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 3a9eb191a..e2a9f5d88 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -182,11 +182,12 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" - /sdrangel/audio/input/set: + + /sdrangel/audio/input/parameters: x-swagger-router-controller: instance patch: description: Set audio input device paramaters - operationId: instanceAudioInputSetPatch + operationId: instanceAudioInputPatch tags: - Instance consumes: @@ -211,12 +212,57 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + delete: + description: Delete audio input device paramaters and return to defaults + operationId: instanceAudioInputDelete + tags: + - Instance + consumes: + - application/json + parameters: + - name: body + in: body + description: Audio input parameters. Index is used to identify the device. + required: true + schema: + $ref: "#/definitions/AudioInputDevice" + responses: + "200": + description: Success. Returns default parameters. + schema: + $ref: "#/definitions/AudioInputDevice" + "404": + description: Audio input device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" - /sdrangel/audio/output/set: + + /sdrangel/audio/input/cleanup: + x-swagger-router-controller: instance + patch: + description: Remove registered parameters for devices not in list of available input devices for this instance + operationId: instanceAudioInputCleanupPatch + tags: + - Instance + responses: + "200": + description: Success. + schema: + $ref: "#/definitions/SuccessResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + /sdrangel/audio/output/parameters: x-swagger-router-controller: instance patch: description: Set audio output device parameters - operationId: instanceAudioOutputSetPatch + operationId: instanceAudioOutputPatch tags: - Instance consumes: @@ -241,6 +287,50 @@ paths: $ref: "#/responses/Response_500" "501": $ref: "#/responses/Response_501" + delete: + description: Delete audio output device paramaters and return to defaults + operationId: instanceAudioOutputDelete + tags: + - Instance + consumes: + - application/json + parameters: + - name: body + in: body + description: Audio output parameters. Index is used to identify the device. + required: true + schema: + $ref: "#/definitions/AudioOutputDevice" + responses: + "200": + description: Success. Returns actual data in particular the actual sample rate. + schema: + $ref: "#/definitions/AudioOutputDevice" + "404": + description: Audio output device not found + schema: + $ref: "#/definitions/ErrorResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" + + /sdrangel/audio/output/cleanup: + x-swagger-router-controller: instance + patch: + description: Remove registered parameters for devices not in list of available output devices for this instance + operationId: instanceAudioOutputCleanupPatch + tags: + - Instance + responses: + "200": + description: Success. + schema: + $ref: "#/definitions/SuccessResponse" + "500": + $ref: "#/responses/Response_500" + "501": + $ref: "#/responses/Response_501" /sdrangel/location: x-swagger-router-controller: instance diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index cfd22bd01..635859c5d 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1939,11 +1939,23 @@ margin-bottom: 20px;
  • instanceAudioGet
  • -
  • - instanceAudioInputSetPatch +
  • + instanceAudioInputCleanupPatch
  • -
  • - instanceAudioOutputSetPatch +
  • + instanceAudioInputDelete +
  • +
  • + instanceAudioInputPatch +
  • +
  • + instanceAudioOutputCleanupPatch +
  • +
  • + instanceAudioOutputDelete +
  • +
  • + instanceAudioOutputPatch
  • instanceChannels @@ -10803,10 +10815,775 @@ except ApiException as e:

  • -
    -
    +
    +
    -

    instanceAudioInputSetPatch

    +

    instanceAudioInputCleanupPatch

    +

    +
    +
    +
    +

    +

    Remove registered parameters for devices not in list of available input devices for this instance

    +

    +
    +
    /sdrangel/audio/input/cleanup
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/input/cleanup"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        try {
    +            SuccessResponse result = apiInstance.instanceAudioInputCleanupPatch();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputCleanupPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        try {
    +            SuccessResponse result = apiInstance.instanceAudioInputCleanupPatch();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputCleanupPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioInputCleanupPatchWithCompletionHandler: 
    +              ^(SuccessResponse output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioInputCleanupPatch(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioInputCleanupPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +
    +            try
    +            {
    +                SuccessResponse result = apiInstance.instanceAudioInputCleanupPatch();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioInputCleanupPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +
    +try {
    +    $result = $api_instance->instanceAudioInputCleanupPatch();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioInputCleanupPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioInputCleanupPatch();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioInputCleanupPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +
    +try: 
    +    api_response = api_instance.instance_audio_input_cleanup_patch()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioInputCleanupPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - Success.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioInputDelete

    +

    +
    +
    +
    +

    +

    Delete audio input device paramaters and return to defaults

    +

    +
    +
    /sdrangel/audio/input/parameters
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X DELETE "http://localhost/sdrangel/audio/input/parameters"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +        try {
    +            AudioInputDevice result = apiInstance.instanceAudioInputDelete(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputDelete");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +        try {
    +            AudioInputDevice result = apiInstance.instanceAudioInputDelete(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputDelete");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    AudioInputDevice *body = ; // Audio input parameters. Index is used to identify the device.
    +
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioInputDeleteWith:body
    +              completionHandler: ^(AudioInputDevice output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var body = ; // {AudioInputDevice} Audio input parameters. Index is used to identify the device.
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioInputDelete(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioInputDeleteExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +            var body = new AudioInputDevice(); // AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +
    +            try
    +            {
    +                AudioInputDevice result = apiInstance.instanceAudioInputDelete(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioInputDelete: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +$body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +
    +try {
    +    $result = $api_instance->instanceAudioInputDelete($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioInputDelete: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +my $body = SWGSDRangel::Object::AudioInputDevice->new(); # AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioInputDelete(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioInputDelete: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +body =  # AudioInputDevice | Audio input parameters. Index is used to identify the device.
    +
    +try: 
    +    api_response = api_instance.instance_audio_input_delete(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioInputDelete: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - Success. Returns default parameters.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Audio input device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioInputPatch

    @@ -10815,29 +11592,29 @@ except ApiException as e:

    Set audio input device paramaters


    -
    /sdrangel/audio/input/set
    +
    /sdrangel/audio/input/parameters

    Usage and SDK Samples

    -
    -
    curl -X PATCH "http://localhost/sdrangel/audio/input/set"
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/input/parameters"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -10853,17 +11630,17 @@ public class InstanceApiExample {
             InstanceApi apiInstance = new InstanceApi();
             AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
    +            AudioInputDevice result = apiInstance.instanceAudioInputPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioInputSetPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.InstanceApi;
     
     public class InstanceApiExample {
    @@ -10872,25 +11649,25 @@ public class InstanceApiExample {
             InstanceApi apiInstance = new InstanceApi();
             AudioInputDevice body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
    +            AudioInputDevice result = apiInstance.instanceAudioInputPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioInputSetPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioInputPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    AudioInputDevice *body = ; // Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -[apiInstance instanceAudioInputSetPatchWith:body
    +[apiInstance instanceAudioInputPatchWith:body
                   completionHandler: ^(AudioInputDevice output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
    @@ -10902,7 +11679,7 @@ InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.InstanceApi()
    @@ -10917,14 +11694,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.instanceAudioInputSetPatch(body, callback);
    +api.instanceAudioInputPatch(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -10933,7 +11710,7 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class instanceAudioInputSetPatchExample
    +    public class instanceAudioInputPatchExample
         {
             public void main()
             {
    @@ -10943,12 +11720,12 @@ namespace Example
     
                 try
                 {
    -                AudioInputDevice result = apiInstance.instanceAudioInputSetPatch(body);
    +                AudioInputDevice result = apiInstance.instanceAudioInputPatch(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling InstanceApi.instanceAudioInputSetPatch: " + e.Message );
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioInputPatch: " + e.Message );
                 }
             }
         }
    @@ -10956,7 +11733,7 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
    @@ -10964,15 +11741,15 @@ $api_instance = new Swagger\Client\Api\InstanceApi();
     $body = ; // AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     try {
    -    $result = $api_instance->instanceAudioInputSetPatch($body);
    +    $result = $api_instance->instanceAudioInputPatch($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling InstanceApi->instanceAudioInputSetPatch: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling InstanceApi->instanceAudioInputPatch: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::InstanceApi;
    @@ -10981,15 +11758,15 @@ my $api_instance = SWGSDRangel::InstanceApi->new();
     my $body = SWGSDRangel::Object::AudioInputDevice->new(); # AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     eval { 
    -    my $result = $api_instance->instanceAudioInputSetPatch(body => $body);
    +    my $result = $api_instance->instanceAudioInputPatch(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling InstanceApi->instanceAudioInputSetPatch: $@\n";
    +    warn "Exception when calling InstanceApi->instanceAudioInputPatch: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -11001,10 +11778,10 @@ api_instance = swagger_sdrangel.InstanceApi()
     body =  # AudioInputDevice | Audio input parameters. Index is used to identify the device. Only settable fields are considered.
     
     try: 
    -    api_response = api_instance.instance_audio_input_set_patch(body)
    +    api_response = api_instance.instance_audio_input_patch(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling InstanceApi->instanceAudioInputSetPatch: %s\n" % e)
    + print("Exception when calling InstanceApi->instanceAudioInputPatch: %s\n" % e)
    @@ -11044,12 +11821,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_instanceAudioInputSetPatch_body'); + var result = $('#d2e199_instanceAudioInputPatch_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -11062,14 +11839,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11105,14 +11882,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11148,14 +11925,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11191,14 +11968,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +

    -
    -
    +
    +
    -

    instanceAudioOutputSetPatch

    +

    instanceAudioOutputCleanupPatch

    +

    +
    +
    +
    +

    +

    Remove registered parameters for devices not in list of available output devices for this instance

    +

    +
    +
    /sdrangel/audio/output/cleanup
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/output/cleanup"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        try {
    +            SuccessResponse result = apiInstance.instanceAudioOutputCleanupPatch();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputCleanupPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        try {
    +            SuccessResponse result = apiInstance.instanceAudioOutputCleanupPatch();
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputCleanupPatch");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioOutputCleanupPatchWithCompletionHandler: 
    +              ^(SuccessResponse output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioOutputCleanupPatch(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioOutputCleanupPatchExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +
    +            try
    +            {
    +                SuccessResponse result = apiInstance.instanceAudioOutputCleanupPatch();
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputCleanupPatch: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +
    +try {
    +    $result = $api_instance->instanceAudioOutputCleanupPatch();
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioOutputCleanupPatch: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioOutputCleanupPatch();
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioOutputCleanupPatch: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +
    +try: 
    +    api_response = api_instance.instance_audio_output_cleanup_patch()
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioOutputCleanupPatch: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    Status: 200 - Success.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioOutputDelete

    +

    +
    +
    +
    +

    +

    Delete audio output device paramaters and return to defaults

    +

    +
    +
    /sdrangel/audio/output/parameters
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X DELETE "http://localhost/sdrangel/audio/output/parameters"
    +
    +
    +
    import SWGSDRangel.*;
    +import SWGSDRangel.auth.*;
    +import SWGSDRangel.model.*;
    +import SWGSDRangel.api.InstanceApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +        try {
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputDelete(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputDelete");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    import SWGSDRangel.api.InstanceApi;
    +
    +public class InstanceApiExample {
    +
    +    public static void main(String[] args) {
    +        InstanceApi apiInstance = new InstanceApi();
    +        AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +        try {
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputDelete(body);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputDelete");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    AudioOutputDevice *body = ; // Audio output parameters. Index is used to identify the device.
    +
    +InstanceApi *apiInstance = [[InstanceApi alloc] init];
    +
    +[apiInstance instanceAudioOutputDeleteWith:body
    +              completionHandler: ^(AudioOutputDevice output, NSError* error) {
    +                            if (output) {
    +                                NSLog(@"%@", output);
    +                            }
    +                            if (error) {
    +                                NSLog(@"Error: %@", error);
    +                            }
    +                        }];
    +
    +
    + +
    +
    var SdRangel = require('sd_rangel');
    +
    +var api = new SdRangel.InstanceApi()
    +
    +var body = ; // {AudioOutputDevice} Audio output parameters. Index is used to identify the device.
    +
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.instanceAudioOutputDelete(body, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using SWGSDRangel.Api;
    +using SWGSDRangel.Client;
    +using SWGSDRangel.Model;
    +
    +namespace Example
    +{
    +    public class instanceAudioOutputDeleteExample
    +    {
    +        public void main()
    +        {
    +            
    +            var apiInstance = new InstanceApi();
    +            var body = new AudioOutputDevice(); // AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +
    +            try
    +            {
    +                AudioOutputDevice result = apiInstance.instanceAudioOutputDelete(body);
    +                Debug.WriteLine(result);
    +            }
    +            catch (Exception e)
    +            {
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputDelete: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +$api_instance = new Swagger\Client\Api\InstanceApi();
    +$body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +
    +try {
    +    $result = $api_instance->instanceAudioOutputDelete($body);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling InstanceApi->instanceAudioOutputDelete: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use SWGSDRangel::Configuration;
    +use SWGSDRangel::InstanceApi;
    +
    +my $api_instance = SWGSDRangel::InstanceApi->new();
    +my $body = SWGSDRangel::Object::AudioOutputDevice->new(); # AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +
    +eval { 
    +    my $result = $api_instance->instanceAudioOutputDelete(body => $body);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling InstanceApi->instanceAudioOutputDelete: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import swagger_sdrangel
    +from swagger_sdrangel.rest import ApiException
    +from pprint import pprint
    +
    +# create an instance of the API class
    +api_instance = swagger_sdrangel.InstanceApi()
    +body =  # AudioOutputDevice | Audio output parameters. Index is used to identify the device.
    +
    +try: 
    +    api_response = api_instance.instance_audio_output_delete(body)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling InstanceApi->instanceAudioOutputDelete: %s\n" % e)
    +
    +
    + +

    Parameters

    + + + +
    Body parameters
    + + + + + + + + + +
    NameDescription
    body * + + + +
    +
    + + + +

    Responses

    +

    Status: 200 - Success. Returns actual data in particular the actual sample rate.

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 404 - Audio output device not found

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 500 - Error

    + + + +
    +
    +
    + +
    + +
    +
    + +

    Status: 501 - Function not implemented

    + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +

    instanceAudioOutputPatch

    @@ -11245,29 +12787,29 @@ $(document).ready(function() {

    Set audio output device parameters


    -
    /sdrangel/audio/output/set
    +
    /sdrangel/audio/output/parameters

    Usage and SDK Samples

    -
    -
    curl -X PATCH "http://localhost/sdrangel/audio/output/set"
    +
    +
    curl -X PATCH "http://localhost/sdrangel/audio/output/parameters"
    -
    +
    import SWGSDRangel.*;
     import SWGSDRangel.auth.*;
     import SWGSDRangel.model.*;
    @@ -11283,17 +12825,17 @@ public class InstanceApiExample {
             InstanceApi apiInstance = new InstanceApi();
             AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioOutputSetPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    import SWGSDRangel.api.InstanceApi;
     
     public class InstanceApiExample {
    @@ -11302,25 +12844,25 @@ public class InstanceApiExample {
             InstanceApi apiInstance = new InstanceApi();
             AudioOutputDevice body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
             try {
    -            AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +            AudioOutputDevice result = apiInstance.instanceAudioOutputPatch(body);
                 System.out.println(result);
             } catch (ApiException e) {
    -            System.err.println("Exception when calling InstanceApi#instanceAudioOutputSetPatch");
    +            System.err.println("Exception when calling InstanceApi#instanceAudioOutputPatch");
                 e.printStackTrace();
             }
         }
     }
    -
    +
    AudioOutputDevice *body = ; // Audio output parameters. Index is used to identify the device. Only settable fields are considered.
     
     InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -[apiInstance instanceAudioOutputSetPatchWith:body
    +[apiInstance instanceAudioOutputPatchWith:body
                   completionHandler: ^(AudioOutputDevice output, NSError* error) {
                                 if (output) {
                                     NSLog(@"%@", output);
    @@ -11332,7 +12874,7 @@ InstanceApi *apiInstance = [[InstanceApi alloc] init];
     
    -
    +
    var SdRangel = require('sd_rangel');
     
     var api = new SdRangel.InstanceApi()
    @@ -11347,14 +12889,14 @@ var callback = function(error, data, response) {
         console.log('API called successfully. Returned data: ' + data);
       }
     };
    -api.instanceAudioOutputSetPatch(body, callback);
    +api.instanceAudioOutputPatch(body, callback);
     
    - -
    +
    using System;
     using System.Diagnostics;
     using SWGSDRangel.Api;
    @@ -11363,7 +12905,7 @@ using SWGSDRangel.Model;
     
     namespace Example
     {
    -    public class instanceAudioOutputSetPatchExample
    +    public class instanceAudioOutputPatchExample
         {
             public void main()
             {
    @@ -11373,12 +12915,12 @@ namespace Example
     
                 try
                 {
    -                AudioOutputDevice result = apiInstance.instanceAudioOutputSetPatch(body);
    +                AudioOutputDevice result = apiInstance.instanceAudioOutputPatch(body);
                     Debug.WriteLine(result);
                 }
                 catch (Exception e)
                 {
    -                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputSetPatch: " + e.Message );
    +                Debug.Print("Exception when calling InstanceApi.instanceAudioOutputPatch: " + e.Message );
                 }
             }
         }
    @@ -11386,7 +12928,7 @@ namespace Example
     
    -
    +
    <?php
     require_once(__DIR__ . '/vendor/autoload.php');
     
    @@ -11394,15 +12936,15 @@ $api_instance = new Swagger\Client\Api\InstanceApi();
     $body = ; // AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
     
     try {
    -    $result = $api_instance->instanceAudioOutputSetPatch($body);
    +    $result = $api_instance->instanceAudioOutputPatch($body);
         print_r($result);
     } catch (Exception $e) {
    -    echo 'Exception when calling InstanceApi->instanceAudioOutputSetPatch: ', $e->getMessage(), PHP_EOL;
    +    echo 'Exception when calling InstanceApi->instanceAudioOutputPatch: ', $e->getMessage(), PHP_EOL;
     }
     ?>
    -
    +
    use Data::Dumper;
     use SWGSDRangel::Configuration;
     use SWGSDRangel::InstanceApi;
    @@ -11411,15 +12953,15 @@ my $api_instance = SWGSDRangel::InstanceApi->new();
     my $body = SWGSDRangel::Object::AudioOutputDevice->new(); # AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
     
     eval { 
    -    my $result = $api_instance->instanceAudioOutputSetPatch(body => $body);
    +    my $result = $api_instance->instanceAudioOutputPatch(body => $body);
         print Dumper($result);
     };
     if ($@) {
    -    warn "Exception when calling InstanceApi->instanceAudioOutputSetPatch: $@\n";
    +    warn "Exception when calling InstanceApi->instanceAudioOutputPatch: $@\n";
     }
    -
    +
    from __future__ import print_statement
     import time
     import swagger_sdrangel
    @@ -11431,10 +12973,10 @@ api_instance = swagger_sdrangel.InstanceApi()
     body =  # AudioOutputDevice | Audio output parameters. Index is used to identify the device. Only settable fields are considered.
     
     try: 
    -    api_response = api_instance.instance_audio_output_set_patch(body)
    +    api_response = api_instance.instance_audio_output_patch(body)
         pprint(api_response)
     except ApiException as e:
    -    print("Exception when calling InstanceApi->instanceAudioOutputSetPatch: %s\n" % e)
    + print("Exception when calling InstanceApi->instanceAudioOutputPatch: %s\n" % e)
    @@ -11474,12 +13016,12 @@ $(document).ready(function() { } var view = new JSONSchemaView(schema,2,{isBodyParam: true}); - var result = $('#d2e199_instanceAudioOutputSetPatch_body'); + var result = $('#d2e199_instanceAudioOutputPatch_body'); result.empty(); result.append(view.render()); }); -
    +
    @@ -11492,14 +13034,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11535,14 +13077,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11578,14 +13120,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -11621,14 +13163,14 @@ $(document).ready(function() {
    -
    -
    +
    +
    - +
    @@ -18555,7 +20097,7 @@ except ApiException as e:
    - Generated 2018-03-29T00:32:59.208+02:00 + Generated 2018-03-29T01:40:29.446+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp index 31ba25920..cd301ae6d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.cpp @@ -81,9 +81,9 @@ SWGInstanceApi::instanceAudioGetCallback(SWGHttpRequestWorker * worker) { } void -SWGInstanceApi::instanceAudioInputSetPatch(SWGAudioInputDevice& body) { +SWGInstanceApi::instanceAudioInputCleanupPatch() { QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/input/set"); + fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/input/cleanup"); @@ -91,6 +91,58 @@ SWGInstanceApi::instanceAudioInputSetPatch(SWGAudioInputDevice& body) { SWGHttpRequestInput input(fullPath, "PATCH"); + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGInstanceApi::instanceAudioInputCleanupPatchCallback); + + worker->execute(&input); +} + +void +SWGInstanceApi::instanceAudioInputCleanupPatchCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGSuccessResponse* output = static_cast(create(json, QString("SWGSuccessResponse"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit instanceAudioInputCleanupPatchSignal(output); + } else { + emit instanceAudioInputCleanupPatchSignalE(output, error_type, error_str); + emit instanceAudioInputCleanupPatchSignalEFull(worker, error_type, error_str); + } +} + +void +SWGInstanceApi::instanceAudioInputDelete(SWGAudioInputDevice& body) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/input/parameters"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "DELETE"); + + QString output = body.asJson(); input.request_body.append(output); @@ -104,13 +156,13 @@ SWGInstanceApi::instanceAudioInputSetPatch(SWGAudioInputDevice& body) { connect(worker, &SWGHttpRequestWorker::on_execution_finished, this, - &SWGInstanceApi::instanceAudioInputSetPatchCallback); + &SWGInstanceApi::instanceAudioInputDeleteCallback); worker->execute(&input); } void -SWGInstanceApi::instanceAudioInputSetPatchCallback(SWGHttpRequestWorker * worker) { +SWGInstanceApi::instanceAudioInputDeleteCallback(SWGHttpRequestWorker * worker) { QString msg; QString error_str = worker->error_str; QNetworkReply::NetworkError error_type = worker->error_type; @@ -128,17 +180,17 @@ SWGInstanceApi::instanceAudioInputSetPatchCallback(SWGHttpRequestWorker * worker worker->deleteLater(); if (worker->error_type == QNetworkReply::NoError) { - emit instanceAudioInputSetPatchSignal(output); + emit instanceAudioInputDeleteSignal(output); } else { - emit instanceAudioInputSetPatchSignalE(output, error_type, error_str); - emit instanceAudioInputSetPatchSignalEFull(worker, error_type, error_str); + emit instanceAudioInputDeleteSignalE(output, error_type, error_str); + emit instanceAudioInputDeleteSignalEFull(worker, error_type, error_str); } } void -SWGInstanceApi::instanceAudioOutputSetPatch(SWGAudioOutputDevice& body) { +SWGInstanceApi::instanceAudioInputPatch(SWGAudioInputDevice& body) { QString fullPath; - fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/output/set"); + fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/input/parameters"); @@ -159,13 +211,120 @@ SWGInstanceApi::instanceAudioOutputSetPatch(SWGAudioOutputDevice& body) { connect(worker, &SWGHttpRequestWorker::on_execution_finished, this, - &SWGInstanceApi::instanceAudioOutputSetPatchCallback); + &SWGInstanceApi::instanceAudioInputPatchCallback); worker->execute(&input); } void -SWGInstanceApi::instanceAudioOutputSetPatchCallback(SWGHttpRequestWorker * worker) { +SWGInstanceApi::instanceAudioInputPatchCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGAudioInputDevice* output = static_cast(create(json, QString("SWGAudioInputDevice"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit instanceAudioInputPatchSignal(output); + } else { + emit instanceAudioInputPatchSignalE(output, error_type, error_str); + emit instanceAudioInputPatchSignalEFull(worker, error_type, error_str); + } +} + +void +SWGInstanceApi::instanceAudioOutputCleanupPatch() { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/output/cleanup"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "PATCH"); + + + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGInstanceApi::instanceAudioOutputCleanupPatchCallback); + + worker->execute(&input); +} + +void +SWGInstanceApi::instanceAudioOutputCleanupPatchCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGSuccessResponse* output = static_cast(create(json, QString("SWGSuccessResponse"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit instanceAudioOutputCleanupPatchSignal(output); + } else { + emit instanceAudioOutputCleanupPatchSignalE(output, error_type, error_str); + emit instanceAudioOutputCleanupPatchSignalEFull(worker, error_type, error_str); + } +} + +void +SWGInstanceApi::instanceAudioOutputDelete(SWGAudioOutputDevice& body) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/output/parameters"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "DELETE"); + + + + QString output = body.asJson(); + input.request_body.append(output); + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGInstanceApi::instanceAudioOutputDeleteCallback); + + worker->execute(&input); +} + +void +SWGInstanceApi::instanceAudioOutputDeleteCallback(SWGHttpRequestWorker * worker) { QString msg; QString error_str = worker->error_str; QNetworkReply::NetworkError error_type = worker->error_type; @@ -183,10 +342,65 @@ SWGInstanceApi::instanceAudioOutputSetPatchCallback(SWGHttpRequestWorker * worke worker->deleteLater(); if (worker->error_type == QNetworkReply::NoError) { - emit instanceAudioOutputSetPatchSignal(output); + emit instanceAudioOutputDeleteSignal(output); } else { - emit instanceAudioOutputSetPatchSignalE(output, error_type, error_str); - emit instanceAudioOutputSetPatchSignalEFull(worker, error_type, error_str); + emit instanceAudioOutputDeleteSignalE(output, error_type, error_str); + emit instanceAudioOutputDeleteSignalEFull(worker, error_type, error_str); + } +} + +void +SWGInstanceApi::instanceAudioOutputPatch(SWGAudioOutputDevice& body) { + QString fullPath; + fullPath.append(this->host).append(this->basePath).append("/sdrangel/audio/output/parameters"); + + + + SWGHttpRequestWorker *worker = new SWGHttpRequestWorker(); + SWGHttpRequestInput input(fullPath, "PATCH"); + + + + QString output = body.asJson(); + input.request_body.append(output); + + + + foreach(QString key, this->defaultHeaders.keys()) { + input.headers.insert(key, this->defaultHeaders.value(key)); + } + + connect(worker, + &SWGHttpRequestWorker::on_execution_finished, + this, + &SWGInstanceApi::instanceAudioOutputPatchCallback); + + worker->execute(&input); +} + +void +SWGInstanceApi::instanceAudioOutputPatchCallback(SWGHttpRequestWorker * worker) { + QString msg; + QString error_str = worker->error_str; + QNetworkReply::NetworkError error_type = worker->error_type; + + if (worker->error_type == QNetworkReply::NoError) { + msg = QString("Success! %1 bytes").arg(worker->response.length()); + } + else { + msg = "Error: " + worker->error_str; + } + + + QString json(worker->response); + SWGAudioOutputDevice* output = static_cast(create(json, QString("SWGAudioOutputDevice"))); + worker->deleteLater(); + + if (worker->error_type == QNetworkReply::NoError) { + emit instanceAudioOutputPatchSignal(output); + } else { + emit instanceAudioOutputPatchSignalE(output, error_type, error_str); + emit instanceAudioOutputPatchSignalEFull(worker, error_type, error_str); } } diff --git a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h index dad11e8a8..3554b2d4a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h +++ b/swagger/sdrangel/code/qt5/client/SWGInstanceApi.h @@ -31,6 +31,7 @@ #include "SWGPresetImport.h" #include "SWGPresetTransfer.h" #include "SWGPresets.h" +#include "SWGSuccessResponse.h" #include @@ -49,8 +50,12 @@ public: QMap defaultHeaders; void instanceAudioGet(); - void instanceAudioInputSetPatch(SWGAudioInputDevice& body); - void instanceAudioOutputSetPatch(SWGAudioOutputDevice& body); + void instanceAudioInputCleanupPatch(); + void instanceAudioInputDelete(SWGAudioInputDevice& body); + void instanceAudioInputPatch(SWGAudioInputDevice& body); + void instanceAudioOutputCleanupPatch(); + void instanceAudioOutputDelete(SWGAudioOutputDevice& body); + void instanceAudioOutputPatch(SWGAudioOutputDevice& body); void instanceChannels(qint32 tx); void instanceDVSerialPatch(qint32 dvserial); void instanceDelete(); @@ -71,8 +76,12 @@ public: private: void instanceAudioGetCallback (SWGHttpRequestWorker * worker); - void instanceAudioInputSetPatchCallback (SWGHttpRequestWorker * worker); - void instanceAudioOutputSetPatchCallback (SWGHttpRequestWorker * worker); + void instanceAudioInputCleanupPatchCallback (SWGHttpRequestWorker * worker); + void instanceAudioInputDeleteCallback (SWGHttpRequestWorker * worker); + void instanceAudioInputPatchCallback (SWGHttpRequestWorker * worker); + void instanceAudioOutputCleanupPatchCallback (SWGHttpRequestWorker * worker); + void instanceAudioOutputDeleteCallback (SWGHttpRequestWorker * worker); + void instanceAudioOutputPatchCallback (SWGHttpRequestWorker * worker); void instanceChannelsCallback (SWGHttpRequestWorker * worker); void instanceDVSerialPatchCallback (SWGHttpRequestWorker * worker); void instanceDeleteCallback (SWGHttpRequestWorker * worker); @@ -93,8 +102,12 @@ private: signals: void instanceAudioGetSignal(SWGAudioDevices* summary); - void instanceAudioInputSetPatchSignal(SWGAudioInputDevice* summary); - void instanceAudioOutputSetPatchSignal(SWGAudioOutputDevice* summary); + void instanceAudioInputCleanupPatchSignal(SWGSuccessResponse* summary); + void instanceAudioInputDeleteSignal(SWGAudioInputDevice* summary); + void instanceAudioInputPatchSignal(SWGAudioInputDevice* summary); + void instanceAudioOutputCleanupPatchSignal(SWGSuccessResponse* summary); + void instanceAudioOutputDeleteSignal(SWGAudioOutputDevice* summary); + void instanceAudioOutputPatchSignal(SWGAudioOutputDevice* summary); void instanceChannelsSignal(SWGInstanceChannelsResponse* summary); void instanceDVSerialPatchSignal(SWGDVSeralDevices* summary); void instanceDeleteSignal(SWGInstanceSummaryResponse* summary); @@ -114,8 +127,12 @@ signals: void instanceSummarySignal(SWGInstanceSummaryResponse* summary); void instanceAudioGetSignalE(SWGAudioDevices* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void instanceAudioInputSetPatchSignalE(SWGAudioInputDevice* summary, QNetworkReply::NetworkError error_type, QString& error_str); - void instanceAudioOutputSetPatchSignalE(SWGAudioOutputDevice* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioInputCleanupPatchSignalE(SWGSuccessResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioInputDeleteSignalE(SWGAudioInputDevice* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioInputPatchSignalE(SWGAudioInputDevice* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioOutputCleanupPatchSignalE(SWGSuccessResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioOutputDeleteSignalE(SWGAudioOutputDevice* summary, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioOutputPatchSignalE(SWGAudioOutputDevice* summary, QNetworkReply::NetworkError error_type, QString& error_str); void instanceChannelsSignalE(SWGInstanceChannelsResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); void instanceDVSerialPatchSignalE(SWGDVSeralDevices* summary, QNetworkReply::NetworkError error_type, QString& error_str); void instanceDeleteSignalE(SWGInstanceSummaryResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); @@ -135,8 +152,12 @@ signals: void instanceSummarySignalE(SWGInstanceSummaryResponse* summary, QNetworkReply::NetworkError error_type, QString& error_str); void instanceAudioGetSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void instanceAudioInputSetPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); - void instanceAudioOutputSetPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioInputCleanupPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioInputDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioInputPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioOutputCleanupPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioOutputDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); + void instanceAudioOutputPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void instanceChannelsSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void instanceDVSerialPatchSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); void instanceDeleteSignalEFull(SWGHttpRequestWorker* worker, QNetworkReply::NetworkError error_type, QString& error_str); From acb96acd85e74002ba5b07d018a48609758a7b41 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 07:55:51 +0200 Subject: [PATCH 202/956] Multiple audio support: Web API: AMDemod and NFMDemod: implemented audio device name in settings --- plugins/channelrx/demodam/amdemod.cpp | 9 ++++++++ plugins/channelrx/demodnfm/nfmdemod.cpp | 9 ++++++++ sdrbase/resources/webapi/doc/html2/index.html | 8 ++++++- .../webapi/doc/swagger/include/AMDemod.yaml | 2 ++ .../webapi/doc/swagger/include/NFMDemod.yaml | 2 ++ .../sdrangel/api/swagger/include/AMDemod.yaml | 2 ++ .../api/swagger/include/NFMDemod.yaml | 2 ++ swagger/sdrangel/code/html2/index.html | 8 ++++++- .../code/qt5/client/SWGAMDemodSettings.cpp | 23 +++++++++++++++++++ .../code/qt5/client/SWGAMDemodSettings.h | 6 +++++ .../code/qt5/client/SWGNFMDemodSettings.cpp | 23 +++++++++++++++++++ .../code/qt5/client/SWGNFMDemodSettings.h | 6 +++++ .../sdrangel/examples/devicesets_config.py | 2 +- 13 files changed, 99 insertions(+), 3 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index d92a73cc5..8ee8182e0 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -371,6 +371,9 @@ int AMDemod::webapiSettingsPutPatch( if (channelSettingsKeys.contains("bandpassEnable")) { settings.m_bandpassEnable = response.getAmDemodSettings()->getBandpassEnable() != 0; } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getAmDemodSettings()->getAudioDeviceName(); + } if (frequencyOffsetChanged) { @@ -419,6 +422,12 @@ void AMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respo } else { response.getAmDemodSettings()->setTitle(new QString(settings.m_title)); } + + if (response.getAmDemodSettings()->getAudioDeviceName()) { + *response.getAmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getAmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } } void AMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 55e477577..afa73f299 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -605,6 +605,9 @@ int NFMDemod::webapiSettingsPutPatch( if (channelSettingsKeys.contains("volume")) { settings.m_volume = response.getNfmDemodSettings()->getVolume(); } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getNfmDemodSettings()->getAudioDeviceName(); + } if (frequencyOffsetChanged) { @@ -657,6 +660,12 @@ void NFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp } else { response.getNfmDemodSettings()->setTitle(new QString(settings.m_title)); } + + if (response.getNfmDemodSettings()->getAudioDeviceName()) { + *response.getNfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getNfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } } void NFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 635859c5d..fd68e1c5a 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -749,6 +749,9 @@ margin-bottom: 20px; }, "title" : { "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" } }, "description" : "AMDemod" @@ -1569,6 +1572,9 @@ margin-bottom: 20px; }, "title" : { "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" } }, "description" : "NFMDemod" @@ -20097,7 +20103,7 @@ except ApiException as e:
    - Generated 2018-03-29T01:40:29.446+02:00 + Generated 2018-03-29T07:44:35.585+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/AMDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/AMDemod.yaml index 37a05eb34..9505df9c5 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/AMDemod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/AMDemod.yaml @@ -25,6 +25,8 @@ AMDemodSettings: type: integer title: type: string + audioDeviceName: + type: string AMDemodReport: description: AMDemod diff --git a/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml index 7d18d0cc2..bdbdde412 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/NFMDemod.yaml @@ -37,6 +37,8 @@ NFMDemodSettings: type: integer title: type: string + audioDeviceName: + type: string NFMDemodReport: description: NFMDemod diff --git a/swagger/sdrangel/api/swagger/include/AMDemod.yaml b/swagger/sdrangel/api/swagger/include/AMDemod.yaml index 37a05eb34..9505df9c5 100644 --- a/swagger/sdrangel/api/swagger/include/AMDemod.yaml +++ b/swagger/sdrangel/api/swagger/include/AMDemod.yaml @@ -25,6 +25,8 @@ AMDemodSettings: type: integer title: type: string + audioDeviceName: + type: string AMDemodReport: description: AMDemod diff --git a/swagger/sdrangel/api/swagger/include/NFMDemod.yaml b/swagger/sdrangel/api/swagger/include/NFMDemod.yaml index 7d18d0cc2..bdbdde412 100644 --- a/swagger/sdrangel/api/swagger/include/NFMDemod.yaml +++ b/swagger/sdrangel/api/swagger/include/NFMDemod.yaml @@ -37,6 +37,8 @@ NFMDemodSettings: type: integer title: type: string + audioDeviceName: + type: string NFMDemodReport: description: NFMDemod diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 635859c5d..fd68e1c5a 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -749,6 +749,9 @@ margin-bottom: 20px; }, "title" : { "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" } }, "description" : "AMDemod" @@ -1569,6 +1572,9 @@ margin-bottom: 20px; }, "title" : { "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" } }, "description" : "NFMDemod" @@ -20097,7 +20103,7 @@ except ApiException as e:
    - Generated 2018-03-29T01:40:29.446+02:00 + Generated 2018-03-29T07:44:35.585+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp index d9b74809e..a5b0c7b8e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.cpp @@ -44,6 +44,8 @@ SWGAMDemodSettings::SWGAMDemodSettings() { m_rgb_color_isSet = false; title = nullptr; m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; } SWGAMDemodSettings::~SWGAMDemodSettings() { @@ -68,6 +70,8 @@ SWGAMDemodSettings::init() { m_rgb_color_isSet = false; title = new QString(""); m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; } void @@ -82,6 +86,9 @@ SWGAMDemodSettings::cleanup() { if(title != nullptr) { delete title; } + if(audio_device_name != nullptr) { + delete audio_device_name; + } } SWGAMDemodSettings* @@ -111,6 +118,8 @@ SWGAMDemodSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + } QString @@ -151,6 +160,9 @@ SWGAMDemodSettings::asJsonObject() { if(title != nullptr && *title != QString("")){ toJsonValue(QString("title"), title, obj, QString("QString")); } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } return obj; } @@ -235,6 +247,16 @@ SWGAMDemodSettings::setTitle(QString* title) { this->m_title_isSet = true; } +QString* +SWGAMDemodSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGAMDemodSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + bool SWGAMDemodSettings::isSet(){ @@ -248,6 +270,7 @@ SWGAMDemodSettings::isSet(){ if(m_bandpass_enable_isSet){ isObjectUpdated = true; break;} if(m_rgb_color_isSet){ isObjectUpdated = true; break;} if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h index 44e6e82df..3fbd078a8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGAMDemodSettings.h @@ -66,6 +66,9 @@ public: QString* getTitle(); void setTitle(QString* title); + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + virtual bool isSet() override; @@ -94,6 +97,9 @@ private: QString* title; bool m_title_isSet; + QString* audio_device_name; + bool m_audio_device_name_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp index 7f142b703..6a7ced10d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.cpp @@ -54,6 +54,8 @@ SWGNFMDemodSettings::SWGNFMDemodSettings() { m_rgb_color_isSet = false; title = nullptr; m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; } SWGNFMDemodSettings::~SWGNFMDemodSettings() { @@ -88,6 +90,8 @@ SWGNFMDemodSettings::init() { m_rgb_color_isSet = false; title = new QString(""); m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; } void @@ -107,6 +111,9 @@ SWGNFMDemodSettings::cleanup() { if(title != nullptr) { delete title; } + if(audio_device_name != nullptr) { + delete audio_device_name; + } } SWGNFMDemodSettings* @@ -146,6 +153,8 @@ SWGNFMDemodSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + } QString @@ -201,6 +210,9 @@ SWGNFMDemodSettings::asJsonObject() { if(title != nullptr && *title != QString("")){ toJsonValue(QString("title"), title, obj, QString("QString")); } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } return obj; } @@ -335,6 +347,16 @@ SWGNFMDemodSettings::setTitle(QString* title) { this->m_title_isSet = true; } +QString* +SWGNFMDemodSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGNFMDemodSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + bool SWGNFMDemodSettings::isSet(){ @@ -353,6 +375,7 @@ SWGNFMDemodSettings::isSet(){ if(m_ctcss_index_isSet){ isObjectUpdated = true; break;} if(m_rgb_color_isSet){ isObjectUpdated = true; break;} if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h index 8ec59c048..d96cdd488 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMDemodSettings.h @@ -81,6 +81,9 @@ public: QString* getTitle(); void setTitle(QString* title); + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + virtual bool isSet() override; @@ -124,6 +127,9 @@ private: QString* title; bool m_title_isSet; + QString* audio_device_name; + bool m_audio_device_name_isSet; + }; } diff --git a/swagger/sdrangel/examples/devicesets_config.py b/swagger/sdrangel/examples/devicesets_config.py index 779059cf1..827074a25 100755 --- a/swagger/sdrangel/examples/devicesets_config.py +++ b/swagger/sdrangel/examples/devicesets_config.py @@ -13,7 +13,7 @@ base_url = "http://127.0.0.1:8091/sdrangel" # - Descriptive message fragment commands = [ ["/deviceset/0/device", "PUT", None, {"hwType": "BladeRF"}, "setup BladeRF on Rx 0"], - ["/preset", "PATCH", None, {"deviceSetIndex": 0, "preset": {"groupName": "OM144", "centerFrequency": 145640000, "type": "R", "name": "Repeaters extended"}}, "load preset on Rx 0"], + ["/preset", "PATCH", None, {"deviceSetIndex": 0, "preset": {"groupName": "OM144", "centerFrequency": 145480000, "type": "R", "name": "Rept + Simplex + DV"}}, "load preset on Rx 0"], ["/deviceset", "POST", None, None, "add Rx 1 device set"], ["/deviceset/1/device", "PUT", None, {"hwType": "SDRdaemonSource"}, "setup SDRdaemonSource on Rx 1"], ["/preset", "PATCH", None, {"deviceSetIndex": 1, "preset": {"groupName": "OM430", "centerFrequency": 439550000, "type": "R", "name": "F5ZKP Daemon RPi3 SUSE"}}, "load preset on Rx 1"], From dbc9e780775c473950288151c2dc90727ab2b6f9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 10:03:52 +0200 Subject: [PATCH 203/956] DATV demod: fixed message handling --- plugins/channelrx/demoddatv/datvdemod.cpp | 57 ++++++++++--------- plugins/channelrx/demoddatv/datvdemod.h | 3 - .../channelrx/demoddatv/datvdemodplugin.cpp | 2 +- sdrbase/dsp/downchannelizer.cpp | 12 +--- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 48a93886b..1c653bd0f 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -58,15 +58,10 @@ DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : m_objRFFilter = new fftfilt(-256000.0 / 1024000.0, 256000.0 / 1024000.0, rfFilterFftLength); - //To setup correct Sample Rate m_channelizer = new DownChannelizer(this); - channelSampleRateChanged(); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); } DATVDemod::~DATVDemod() @@ -98,20 +93,6 @@ DATVDemod::~DATVDemod() delete m_channelizer; } -void DATVDemod::channelSampleRateChanged() -{ - qDebug() << "DATVDemod::channelSampleRateChanged:" - << " sample rate: " << m_channelizer->getInputSampleRate(); - - if(m_objRunning.intMsps!=m_channelizer->getInputSampleRate()) - { - m_objRunning.intMsps = m_channelizer->getInputSampleRate(); - m_objRunning.intSampleRate = m_objRunning.intMsps; - - ApplySettings(); - } -} - bool DATVDemod::SetTVScreen(TVScreen *objScreen) { m_objRegisteredTVScreen = objScreen; @@ -904,10 +885,38 @@ void DATVDemod::stop() bool DATVDemod::handleMessage(const Message& cmd) { - qDebug() << "DATVDemod::handleMessage"; + if (DownChannelizer::MsgChannelizerNotification::match(cmd)) + { + DownChannelizer::MsgChannelizerNotification& objNotif = (DownChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "DATVDemod::handleMessage: MsgChannelizerNotification:" + << " m_intSampleRate: " << objNotif.getSampleRate() + << " m_intFrequencyOffset: " << objNotif.getFrequencyOffset(); - if (MsgConfigureDATVDemod::match(cmd)) + if (m_objRunning.intMsps != objNotif.getSampleRate()) + { + m_objRunning.intMsps = objNotif.getSampleRate(); + m_objRunning.intSampleRate = m_objRunning.intMsps; + + ApplySettings(); + } + + return true; + } + else if (MsgConfigureChannelizer::match(cmd)) + { + MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; + + m_channelizer->configure(m_channelizer->getInputMessageQueue(), + m_channelizer->getInputSampleRate(), + cfg.getCenterFrequency()); + + qDebug() << "DATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate() + << " centerFrequency: " << cfg.getCenterFrequency(); + + return true; + } + else if (MsgConfigureDATVDemod::match(cmd)) { MsgConfigureDATVDemod& objCfg = (MsgConfigureDATVDemod&) cmd; @@ -960,8 +969,7 @@ bool DATVDemod::handleMessage(const Message& cmd) << " intExcursion: " << objCfg.m_objMsgConfig.intExcursion; ApplySettings(); - } - + } return true; } @@ -973,13 +981,11 @@ bool DATVDemod::handleMessage(const Message& cmd) void DATVDemod::ApplySettings() { - if(m_objRunning.intMsps==0) { return; } - InitDATVParameters(m_objRunning.intMsps, m_objRunning.intRFBandwidth, m_objRunning.intCenterFrequency, @@ -996,7 +1002,6 @@ void DATVDemod::ApplySettings() m_objRunning.fltRollOff, m_objRunning.blnViterbi, m_objRunning.intExcursion); - } int DATVDemod::GetSampleRate() diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index dc2d36276..a5af83546 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -262,9 +262,6 @@ public: { } }; -private slots: - void channelSampleRateChanged(); - private: class MsgConfigureDATVDemod : public Message { diff --git a/plugins/channelrx/demoddatv/datvdemodplugin.cpp b/plugins/channelrx/demoddatv/datvdemodplugin.cpp index 2e3dbe018..94d3a849c 100644 --- a/plugins/channelrx/demoddatv/datvdemodplugin.cpp +++ b/plugins/channelrx/demoddatv/datvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DATVDemodPlugin::m_ptrPluginDescriptor = { QString("DATV Demodulator"), - QString("3.2.0"), + QString("3.14.0"), QString("(c) F4HKW for SDRAngel using LeanSDR framework (c) F4DAV"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/dsp/downchannelizer.cpp b/sdrbase/dsp/downchannelizer.cpp index 3b2772db0..56dc25807 100644 --- a/sdrbase/dsp/downchannelizer.cpp +++ b/sdrbase/dsp/downchannelizer.cpp @@ -110,8 +110,6 @@ void DownChannelizer::stop() bool DownChannelizer::handleMessage(const Message& cmd) { - qDebug() << "DownChannelizer::handleMessage: " << cmd.getIdentifier(); - // TODO: apply changes only if input sample rate or requested output sample rate change. Change of center frequency has no impact. if (DSPSignalNotification::match(cmd)) @@ -146,19 +144,13 @@ bool DownChannelizer::handleMessage(const Message& cmd) } else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) { + qDebug() << "DownChannelizer::handleMessage: MsgThreadedSink: forwarded to demod"; return m_sampleSink->handleMessage(cmd); // this message is passed to the demod } else { + qDebug() << "DownChannelizer::handleMessage: " << cmd.getIdentifier() << " unhandled"; return false; -// if (m_sampleSink != 0) -// { -// return m_sampleSink->handleMessage(cmd); -// } -// else -// { -// return false; -// } } } From 6551c1cf5ecd52c431fb07d63fac5cb6916c20d9 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 10:41:46 +0200 Subject: [PATCH 204/956] TV screen: fixed OpenGL warning --- sdrgui/gui/glshadertvarray.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/gui/glshadertvarray.cpp b/sdrgui/gui/glshadertvarray.cpp index 0e8cd1a5b..ebb6c47d6 100644 --- a/sdrgui/gui/glshadertvarray.cpp +++ b/sdrgui/gui/glshadertvarray.cpp @@ -113,7 +113,7 @@ void GLShaderTVArray::InitializeGL(int intCols, int intRows) m_objImage->fill(QColor(0, 0, 0)); m_objTexture = new QOpenGLTexture(*m_objImage); - m_objTexture->setFormat(QOpenGLTexture::RGBA8_UNorm); + //m_objTexture->setFormat(QOpenGLTexture::RGBA8_UNorm); avoids OpenGL warning and in fact is useless m_objTexture->setMinificationFilter(QOpenGLTexture::Linear); m_objTexture->setMagnificationFilter(QOpenGLTexture::Linear); m_objTexture->setWrapMode(QOpenGLTexture::ClampToEdge); From 3ca49aa849d4d53924adf3612b3cdc3ac03dbe02 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 12:15:33 +0200 Subject: [PATCH 205/956] LimeSDR: corrceted get/set center frequency taking NCO into account --- plugins/samplesink/limesdroutput/limesdroutput.cpp | 4 ++-- plugins/samplesink/limesdroutput/limesdroutputgui.cpp | 4 ++-- plugins/samplesource/limesdrinput/limesdrinput.cpp | 4 ++-- plugins/samplesource/limesdrinput/limesdrinputgui.cpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp index 29cbdc2b8..4b31712a1 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp @@ -439,13 +439,13 @@ int LimeSDROutput::getSampleRate() const quint64 LimeSDROutput::getCenterFrequency() const { - return m_settings.m_centerFrequency; + return m_settings.m_centerFrequency + (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); } void LimeSDROutput::setCenterFrequency(qint64 centerFrequency) { LimeSDROutputSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; + settings.m_centerFrequency = centerFrequency - (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); MsgConfigureLimeSDR* message = MsgConfigureLimeSDR::create(settings, false); m_inputMessageQueue.push(message); diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp index 9e08cdaa4..953e4d01b 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp @@ -109,12 +109,12 @@ void LimeSDROutputGUI::resetToDefaults() qint64 LimeSDROutputGUI::getCenterFrequency() const { - return m_settings.m_centerFrequency; + return m_settings.m_centerFrequency + (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); } void LimeSDROutputGUI::setCenterFrequency(qint64 centerFrequency) { - m_settings.m_centerFrequency = centerFrequency; + m_settings.m_centerFrequency = centerFrequency - (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); displaySettings(); sendSettings(); } diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 0b2b61e6c..18e44da12 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -458,13 +458,13 @@ int LimeSDRInput::getSampleRate() const quint64 LimeSDRInput::getCenterFrequency() const { - return m_settings.m_centerFrequency; + return m_settings.m_centerFrequency + (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); } void LimeSDRInput::setCenterFrequency(qint64 centerFrequency) { LimeSDRInputSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; + settings.m_centerFrequency = centerFrequency - (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); MsgConfigureLimeSDR* message = MsgConfigureLimeSDR::create(settings, false); m_inputMessageQueue.push(message); diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index 26b3b4824..911de2900 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -107,12 +107,12 @@ void LimeSDRInputGUI::resetToDefaults() qint64 LimeSDRInputGUI::getCenterFrequency() const { - return m_settings.m_centerFrequency; + return m_settings.m_centerFrequency + (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); } void LimeSDRInputGUI::setCenterFrequency(qint64 centerFrequency) { - m_settings.m_centerFrequency = centerFrequency; + m_settings.m_centerFrequency = centerFrequency - (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); displaySettings(); sendSettings(); } From e0db2adc6b1550c9837fb837ea0352908220e93f Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 14:54:14 +0200 Subject: [PATCH 206/956] LimeSDR plugins: bumped version --- plugins/samplesink/limesdroutput/limesdroutputplugin.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinputplugin.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp index b8c353723..1e9aa0edd 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp @@ -34,7 +34,7 @@ const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = { QString("LimeSDR Output"), - QString("3.13.1"), + QString("3.14.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 6e895286f..6337d86f5 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("3.13.1"), + QString("3.14.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 2380211533c7dc309d95cf8e661837086da16d69 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 15:20:38 +0200 Subject: [PATCH 207/956] Multiple audio support: AM modulator --- plugins/channeltx/modam/CMakeLists.txt | 2 + plugins/channeltx/modam/ammod.cpp | 74 ++++++++++++++++++----- plugins/channeltx/modam/ammod.h | 2 + plugins/channeltx/modam/ammodgui.cpp | 18 ++++++ plugins/channeltx/modam/ammodgui.h | 1 + plugins/channeltx/modam/ammodgui.ui | 13 +--- plugins/channeltx/modam/ammodsettings.cpp | 4 +- plugins/channeltx/modam/ammodsettings.h | 2 +- plugins/channeltx/modam/readme.md | 6 +- 9 files changed, 92 insertions(+), 30 deletions(-) diff --git a/plugins/channeltx/modam/CMakeLists.txt b/plugins/channeltx/modam/CMakeLists.txt index 0be430f84..7acec8bc9 100644 --- a/plugins/channeltx/modam/CMakeLists.txt +++ b/plugins/channeltx/modam/CMakeLists.txt @@ -1,5 +1,7 @@ project(modam) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(modam_SOURCES ammod.cpp ammodgui.cpp diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index 5fbf1cd07..924a04f54 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -65,11 +65,12 @@ AMMod::AMMod(DeviceSinkAPI *deviceAPI) : m_magsq = 0.0; - m_toneNco.setFreq(1000.0, m_settings.m_audioSampleRate); DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + m_toneNco.setFreq(1000.0, m_audioSampleRate); // CW keyer - m_cwKeyer.setSampleRate(m_settings.m_audioSampleRate); + m_cwKeyer.setSampleRate(m_audioSampleRate); m_cwKeyer.setWPM(13); m_cwKeyer.setMode(CWKeyerSettings::CWNone); @@ -139,7 +140,7 @@ void AMMod::pull(Sample& sample) void AMMod::pullAudio(int nbSamples) { // qDebug("AMMod::pullAudio: %d", nbSamples); - unsigned int nbAudioSamples = nbSamples * ((Real) m_settings.m_audioSampleRate / (Real) m_basebandSampleRate); + unsigned int nbAudioSamples = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); if (nbAudioSamples > m_audioBuffer.size()) { @@ -335,6 +336,20 @@ bool AMMod::handleMessage(const Message& cmd) return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "AMMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -380,6 +395,28 @@ void AMMod::seekFileStream(int seekPercentage) } } +void AMMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("AMMod::applyAudioSampleRate: %d", sampleRate); + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); + m_cwKeyer.setSampleRate(sampleRate); + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void AMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "AMMod::applyChannelSettings:" @@ -400,8 +437,8 @@ void AMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, i m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_settings.m_audioSampleRate / (Real) outputSampleRate; - m_interpolator.create(48, m_settings.m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } @@ -419,30 +456,37 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) << " m_toneFrequency: " << settings.m_toneFrequency << " m_volumeFactor: " << settings.m_volumeFactor << " m_audioMute: " << settings.m_channelMute - << " m_playLoop: " << settings.m_playLoop; + << " m_playLoop: " << settings.m_playLoop + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " force: " << force; - if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) m_outputSampleRate; - m_interpolator.create(48, settings.m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } - if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) { m_settingsMutex.lock(); - m_toneNco.setFreq(settings.m_toneFrequency, settings.m_audioSampleRate); + m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); m_settingsMutex.unlock(); } - if ((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { - m_cwKeyer.setSampleRate(settings.m_audioSampleRate); + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } } m_settings = settings; diff --git a/plugins/channeltx/modam/ammod.h b/plugins/channeltx/modam/ammod.h index 1ed8d3651..b0152069d 100644 --- a/plugins/channeltx/modam/ammod.h +++ b/plugins/channeltx/modam/ammod.h @@ -276,6 +276,7 @@ private: int m_outputSampleRate; int m_inputFrequencyOffset; AMModSettings m_settings; + quint32 m_audioSampleRate; NCO m_carrierNco; NCOF m_toneNco; @@ -309,6 +310,7 @@ private: static const int m_levelNbSamples; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const AMModSettings& settings, bool force = false); void pullAF(Real& sample); diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index 79027a693..2ee194a90 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -31,6 +31,8 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "dsp/dspengine.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "mainwindow.h" AMModGUI* AMModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) @@ -282,6 +284,9 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampl connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -391,6 +396,19 @@ void AMModGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void AMModGUI::audioSelect() +{ + qDebug("AMModGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void AMModGUI::tick() { double powDb = CalcDb::dbPower(m_amMod->getMagSq()); diff --git a/plugins/channeltx/modam/ammodgui.h b/plugins/channeltx/modam/ammodgui.h index 82b4efbf3..fcccb2c69 100644 --- a/plugins/channeltx/modam/ammodgui.h +++ b/plugins/channeltx/modam/ammodgui.h @@ -110,6 +110,7 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void configureFileName(); + void audioSelect(); void tick(); }; diff --git a/plugins/channeltx/modam/ammodgui.ui b/plugins/channeltx/modam/ammodgui.ui index 1b98dafa4..4321def42 100644 --- a/plugins/channeltx/modam/ammodgui.ui +++ b/plugins/channeltx/modam/ammodgui.ui @@ -56,16 +56,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -449,7 +440,7 @@ - Audio input + Left: Source audio input Right: Select audio input device ... diff --git a/plugins/channeltx/modam/ammodsettings.cpp b/plugins/channeltx/modam/ammodsettings.cpp index bee3ed0d8..aaaf572be 100644 --- a/plugins/channeltx/modam/ammodsettings.cpp +++ b/plugins/channeltx/modam/ammodsettings.cpp @@ -34,12 +34,12 @@ void AMModSettings::resetToDefaults() m_rfBandwidth = 12500.0; m_modFactor = 0.2f; m_toneFrequency = 1000.0f; - m_audioSampleRate = DSPEngine::instance()->getDefaultAudioSampleRate(); m_volumeFactor = 1.0f; m_channelMute = false; m_playLoop = false; m_rgbColor = QColor(255, 255, 0).rgb(); m_title = "AM Modulator"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray AMModSettings::serialize() const @@ -62,6 +62,7 @@ QByteArray AMModSettings::serialize() const } s.writeString(9, m_title); + s.writeString(10, m_audioDeviceName); return s.final(); } @@ -100,6 +101,7 @@ bool AMModSettings::deserialize(const QByteArray& data) } d.readString(9, &m_title, "AM Modulator"); + d.readString(10, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); return true; } diff --git a/plugins/channeltx/modam/ammodsettings.h b/plugins/channeltx/modam/ammodsettings.h index ab915d7bc..6aadcf6e3 100644 --- a/plugins/channeltx/modam/ammodsettings.h +++ b/plugins/channeltx/modam/ammodsettings.h @@ -28,11 +28,11 @@ struct AMModSettings float m_modFactor; float m_toneFrequency; float m_volumeFactor; - quint32 m_audioSampleRate; bool m_channelMute; bool m_playLoop; quint32 m_rgbColor; QString m_title; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_cwKeyerGUI; diff --git a/plugins/channeltx/modam/readme.md b/plugins/channeltx/modam/readme.md index 9f766ec36..b401c0ec2 100644 --- a/plugins/channeltx/modam/readme.md +++ b/plugins/channeltx/modam/readme.md @@ -56,9 +56,11 @@ Switches to the Morse keyer input. You must switch it off to make other inputs a Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps -

    9.4: Audio input select

    +

    9.4: Audio input select and select audio input device

    -Switches to the audio input. You must switch it off to make other inputs available. +Left click to switch to the audio input. You must switch it off to make other inputs available. + +Right click to select audio input device.

    10: CW (Morse) text

    From eb57c1aca6f0c7f6e74bb722adbd68459908ac62 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 15:42:20 +0200 Subject: [PATCH 208/956] Multiple audio support: NFM modulator --- plugins/channeltx/modnfm/nfmmod.cpp | 111 +++++++++++++----- plugins/channeltx/modnfm/nfmmod.h | 2 + plugins/channeltx/modnfm/nfmmodgui.cpp | 18 +++ plugins/channeltx/modnfm/nfmmodgui.h | 1 + plugins/channeltx/modnfm/nfmmodsettings.cpp | 5 +- plugins/channeltx/modnfm/nfmmodsettings.h | 2 +- plugins/channeltx/modnfm/readme.md | 6 +- sdrbase/resources/webapi/doc/html2/index.html | 11 +- .../webapi/doc/swagger/include/NFMMod.yaml | 6 +- .../sdrangel/api/swagger/include/NFMMod.yaml | 6 +- swagger/sdrangel/code/html2/index.html | 11 +- .../code/qt5/client/SWGNFMModReport.cpp | 42 +++++++ .../code/qt5/client/SWGNFMModReport.h | 12 ++ .../code/qt5/client/SWGNFMModSettings.cpp | 21 ---- .../code/qt5/client/SWGNFMModSettings.h | 6 - 15 files changed, 186 insertions(+), 74 deletions(-) diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp index 320f40b11..794975e5a 100644 --- a/plugins/channeltx/modnfm/nfmmod.cpp +++ b/plugins/channeltx/modnfm/nfmmod.cpp @@ -71,12 +71,14 @@ NFMMod::NFMMod(DeviceSinkAPI *deviceAPI) : m_magsq = 0.0; - m_toneNco.setFreq(1000.0, m_settings.m_audioSampleRate); - m_ctcssNco.setFreq(88.5, m_settings.m_audioSampleRate); DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + + m_toneNco.setFreq(1000.0, m_audioSampleRate); + m_ctcssNco.setFreq(88.5, m_audioSampleRate); // CW keyer - m_cwKeyer.setSampleRate(m_settings.m_audioSampleRate); + m_cwKeyer.setSampleRate(m_audioSampleRate); m_cwKeyer.setWPM(13); m_cwKeyer.setMode(CWKeyerSettings::CWNone); @@ -145,7 +147,7 @@ void NFMMod::pull(Sample& sample) void NFMMod::pullAudio(int nbSamples) { - unsigned int nbSamplesAudio = nbSamples * ((Real) m_settings.m_audioSampleRate / (Real) m_basebandSampleRate); + unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); if (nbSamplesAudio > m_audioBuffer.size()) { @@ -166,12 +168,12 @@ void NFMMod::modulateSample() if (m_settings.m_ctcssOn) { - m_modPhasor += (m_settings.m_fmDeviation / (float) m_settings.m_audioSampleRate) * (0.85f * m_bandpass.filter(t) + 0.15f * 378.0f * m_ctcssNco.next()) * (M_PI / 378.0f); + m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * (0.85f * m_bandpass.filter(t) + 0.15f * 378.0f * m_ctcssNco.next()) * (M_PI / 378.0f); } else { // 378 = 302 * 1.25; 302 = number of filter taps (established experimentally) - m_modPhasor += (m_settings.m_fmDeviation / (float) m_settings.m_audioSampleRate) * m_bandpass.filter(t) * (M_PI / 378.0f); + m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * m_bandpass.filter(t) * (M_PI / 378.0f); } m_modSample.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB @@ -345,6 +347,20 @@ bool NFMMod::handleMessage(const Message& cmd) return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "NFMMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -390,6 +406,31 @@ void NFMMod::seekFileStream(int seekPercentage) } } +void NFMMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("NFMMod::applyAudioSampleRate: %d", sampleRate); + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_lowpass.create(301, sampleRate, 250.0); + m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); + m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); + m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(m_settings.m_ctcssIndex), sampleRate); + m_cwKeyer.setSampleRate(sampleRate); + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void NFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "NFMMod::applyChannelSettings:" @@ -410,8 +451,8 @@ void NFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_settings.m_audioSampleRate / (Real) outputSampleRate; - m_interpolator.create(48, m_settings.m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } @@ -434,49 +475,53 @@ void NFMMod::applySettings(const NFMModSettings& settings, bool force) << " m_channelMute: " << settings.m_channelMute << " m_playLoop: " << settings.m_playLoop << " m_modAFInout " << settings.m_modAFInput + << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; - if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) m_outputSampleRate; - m_interpolator.create(48, settings.m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } - if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { m_settingsMutex.lock(); - m_lowpass.create(301, settings.m_audioSampleRate, 250.0); - m_bandpass.create(301, settings.m_audioSampleRate, 300.0, settings.m_afBandwidth); + m_lowpass.create(301, m_audioSampleRate, 250.0); + m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth); m_settingsMutex.unlock(); } - if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) { m_settingsMutex.lock(); - m_toneNco.setFreq(settings.m_toneFrequency, settings.m_audioSampleRate); + m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); m_settingsMutex.unlock(); } - if ((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) - { - m_cwKeyer.setSampleRate(settings.m_audioSampleRate); - } - - if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) { m_settingsMutex.lock(); - m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(settings.m_ctcssIndex), settings.m_audioSampleRate); + m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(settings.m_ctcssIndex), m_audioSampleRate); m_settingsMutex.unlock(); } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + m_settings = settings; } @@ -531,9 +576,6 @@ int NFMMod::webapiSettingsPutPatch( if (channelSettingsKeys.contains("afBandwidth")) { settings.m_afBandwidth = response.getNfmModSettings()->getAfBandwidth(); } - if (channelSettingsKeys.contains("audioSampleRate")) { - settings.m_audioSampleRate = response.getNfmModSettings()->getAudioSampleRate(); - } if (channelSettingsKeys.contains("channelMute")) { settings.m_channelMute = response.getNfmModSettings()->getChannelMute() != 0; } @@ -641,7 +683,6 @@ int NFMMod::webapiReportGet( void NFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMModSettings& settings) { response.getNfmModSettings()->setAfBandwidth(settings.m_afBandwidth); - response.getNfmModSettings()->setAudioSampleRate(settings.m_audioSampleRate); response.getNfmModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); response.getNfmModSettings()->setCtcssIndex(settings.m_ctcssIndex); response.getNfmModSettings()->setCtcssOn(settings.m_ctcssOn ? 1 : 0); @@ -677,10 +718,18 @@ void NFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); } + if (response.getNfmDemodSettings()->getAudioDeviceName()) { + *response.getNfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getNfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); } void NFMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { response.getNfmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getNfmModReport()->setAudioSampleRate(m_audioSampleRate); + response.getNfmModReport()->setChannelSampleRate(m_outputSampleRate); } diff --git a/plugins/channeltx/modnfm/nfmmod.h b/plugins/channeltx/modnfm/nfmmod.h index 4cfcdf70d..3505cf71e 100644 --- a/plugins/channeltx/modnfm/nfmmod.h +++ b/plugins/channeltx/modnfm/nfmmod.h @@ -262,6 +262,7 @@ private: int m_outputSampleRate; int m_inputFrequencyOffset; NFMModSettings m_settings; + quint32 m_audioSampleRate; NCO m_carrierNco; NCOF m_toneNco; @@ -298,6 +299,7 @@ private: CWKeyer m_cwKeyer; static const int m_levelNbSamples; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const NFMModSettings& settings, bool force = false); void pullAF(Real& sample); diff --git a/plugins/channeltx/modnfm/nfmmodgui.cpp b/plugins/channeltx/modnfm/nfmmodgui.cpp index 7d4553a67..133adf2c7 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.cpp +++ b/plugins/channeltx/modnfm/nfmmodgui.cpp @@ -26,6 +26,8 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "dsp/dspengine.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "mainwindow.h" #include "ui_nfmmodgui.h" @@ -320,6 +322,9 @@ NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -446,6 +451,19 @@ void NFMModGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void NFMModGUI::audioSelect() +{ + qDebug("NFMModGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void NFMModGUI::tick() { double powDb = CalcDb::dbPower(m_nfmMod->getMagSq()); diff --git a/plugins/channeltx/modnfm/nfmmodgui.h b/plugins/channeltx/modnfm/nfmmodgui.h index 8d144480d..8919352db 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.h +++ b/plugins/channeltx/modnfm/nfmmodgui.h @@ -112,6 +112,7 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void configureFileName(); + void audioSelect(); void tick(); }; diff --git a/plugins/channeltx/modnfm/nfmmodsettings.cpp b/plugins/channeltx/modnfm/nfmmodsettings.cpp index 5a80a1c8c..874c11a6e 100644 --- a/plugins/channeltx/modnfm/nfmmodsettings.cpp +++ b/plugins/channeltx/modnfm/nfmmodsettings.cpp @@ -50,7 +50,6 @@ void NFMModSettings::resetToDefaults() m_rfBandwidth = 12500.0f; m_fmDeviation = 5000.0f; m_toneFrequency = 1000.0f; - m_audioSampleRate = DSPEngine::instance()->getDefaultAudioSampleRate(); m_volumeFactor = 1.0f; m_channelMute = false; m_playLoop = false; @@ -59,6 +58,7 @@ void NFMModSettings::resetToDefaults() m_rgbColor = QColor(255, 0, 0).rgb(); m_title = "NFM Modulator"; m_modAFInput = NFMModInputAF::NFMModInputNone; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray NFMModSettings::serialize() const @@ -85,6 +85,7 @@ QByteArray NFMModSettings::serialize() const s.writeS32(10, m_ctcssIndex); s.writeString(12, m_title); s.writeS32(13, (int) m_modAFInput); + s.writeString(14, m_audioDeviceName); return s.final(); } @@ -135,6 +136,8 @@ bool NFMModSettings::deserialize(const QByteArray& data) m_modAFInput = (NFMModInputAF) tmp; } + d.readString(14, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + return true; } else diff --git a/plugins/channeltx/modnfm/nfmmodsettings.h b/plugins/channeltx/modnfm/nfmmodsettings.h index 889267b1d..83f264be8 100644 --- a/plugins/channeltx/modnfm/nfmmodsettings.h +++ b/plugins/channeltx/modnfm/nfmmodsettings.h @@ -43,7 +43,6 @@ struct NFMModSettings float m_fmDeviation; float m_toneFrequency; float m_volumeFactor; - quint32 m_audioSampleRate; bool m_channelMute; bool m_playLoop; bool m_ctcssOn; @@ -51,6 +50,7 @@ struct NFMModSettings quint32 m_rgbColor; QString m_title; NFMModInputAF m_modAFInput; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_cwKeyerGUI; diff --git a/plugins/channeltx/modnfm/readme.md b/plugins/channeltx/modnfm/readme.md index 6e3f7b3a6..6ea86686e 100644 --- a/plugins/channeltx/modnfm/readme.md +++ b/plugins/channeltx/modnfm/readme.md @@ -60,9 +60,11 @@ Switches to the Morse keyer input. You must switch it off to make other inputs a Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps -

    10.4: Audio input select

    +

    10.4: Audio input select and select audio input device

    -Switches to the audio input. You must switch it off to make other inputs available. +Left click to switch to the audio input. You must switch it off to make other inputs available. + +Right click to select audio input device.

    11: CTCSS switch

    diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index fd68e1c5a..cfff66bdd 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1585,6 +1585,12 @@ margin-bottom: 20px; "type" : "number", "format" : "float", "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" } }, "description" : "NFMMod" @@ -1615,9 +1621,6 @@ margin-bottom: 20px; "type" : "number", "format" : "float" }, - "audioSampleRate" : { - "type" : "integer" - }, "channelMute" : { "type" : "integer" }, @@ -20103,7 +20106,7 @@ except ApiException as e:
    - Generated 2018-03-29T07:44:35.585+02:00 + Generated 2018-03-29T15:31:47.724+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml index 3148c36dc..d608d47ed 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml @@ -19,8 +19,6 @@ NFMModSettings: volumeFactor: type: number format: float - audioSampleRate: - type: integer channelMute: type: integer playLoop: @@ -45,4 +43,8 @@ NFMModReport: description: power transmitted in channel (dB) type: number format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/NFMMod.yaml b/swagger/sdrangel/api/swagger/include/NFMMod.yaml index e121798f8..26482755b 100644 --- a/swagger/sdrangel/api/swagger/include/NFMMod.yaml +++ b/swagger/sdrangel/api/swagger/include/NFMMod.yaml @@ -19,8 +19,6 @@ NFMModSettings: volumeFactor: type: number format: float - audioSampleRate: - type: integer channelMute: type: integer playLoop: @@ -45,4 +43,8 @@ NFMModReport: description: power transmitted in channel (dB) type: number format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer \ No newline at end of file diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index fd68e1c5a..cfff66bdd 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1585,6 +1585,12 @@ margin-bottom: 20px; "type" : "number", "format" : "float", "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" } }, "description" : "NFMMod" @@ -1615,9 +1621,6 @@ margin-bottom: 20px; "type" : "number", "format" : "float" }, - "audioSampleRate" : { - "type" : "integer" - }, "channelMute" : { "type" : "integer" }, @@ -20103,7 +20106,7 @@ except ApiException as e:
    - Generated 2018-03-29T07:44:35.585+02:00 + Generated 2018-03-29T15:31:47.724+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp index c1a97558b..41b1b423a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.cpp @@ -30,6 +30,10 @@ SWGNFMModReport::SWGNFMModReport(QString* json) { SWGNFMModReport::SWGNFMModReport() { channel_power_db = 0.0f; m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; } SWGNFMModReport::~SWGNFMModReport() { @@ -40,11 +44,17 @@ void SWGNFMModReport::init() { channel_power_db = 0.0f; m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; } void SWGNFMModReport::cleanup() { + + } SWGNFMModReport* @@ -60,6 +70,10 @@ void SWGNFMModReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + } QString @@ -79,6 +93,12 @@ SWGNFMModReport::asJsonObject() { if(m_channel_power_db_isSet){ obj->insert("channelPowerDB", QJsonValue(channel_power_db)); } + if(m_audio_sample_rate_isSet){ + obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } return obj; } @@ -93,12 +113,34 @@ SWGNFMModReport::setChannelPowerDb(float channel_power_db) { this->m_channel_power_db_isSet = true; } +qint32 +SWGNFMModReport::getAudioSampleRate() { + return audio_sample_rate; +} +void +SWGNFMModReport::setAudioSampleRate(qint32 audio_sample_rate) { + this->audio_sample_rate = audio_sample_rate; + this->m_audio_sample_rate_isSet = true; +} + +qint32 +SWGNFMModReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGNFMModReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + bool SWGNFMModReport::isSet(){ bool isObjectUpdated = false; do{ if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h index e50e1b157..711b636c1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModReport.h @@ -44,6 +44,12 @@ public: float getChannelPowerDb(); void setChannelPowerDb(float channel_power_db); + qint32 getAudioSampleRate(); + void setAudioSampleRate(qint32 audio_sample_rate); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + virtual bool isSet() override; @@ -51,6 +57,12 @@ private: float channel_power_db; bool m_channel_power_db_isSet; + qint32 audio_sample_rate; + bool m_audio_sample_rate_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp index ca04ace80..70c7f4aba 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp @@ -40,8 +40,6 @@ SWGNFMModSettings::SWGNFMModSettings() { m_tone_frequency_isSet = false; volume_factor = 0.0f; m_volume_factor_isSet = false; - audio_sample_rate = 0; - m_audio_sample_rate_isSet = false; channel_mute = 0; m_channel_mute_isSet = false; play_loop = 0; @@ -78,8 +76,6 @@ SWGNFMModSettings::init() { m_tone_frequency_isSet = false; volume_factor = 0.0f; m_volume_factor_isSet = false; - audio_sample_rate = 0; - m_audio_sample_rate_isSet = false; channel_mute = 0; m_channel_mute_isSet = false; play_loop = 0; @@ -111,7 +107,6 @@ SWGNFMModSettings::cleanup() { - if(title != nullptr) { delete title; } @@ -144,8 +139,6 @@ SWGNFMModSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&volume_factor, pJson["volumeFactor"], "float", ""); - ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); - ::SWGSDRangel::setValue(&channel_mute, pJson["channelMute"], "qint32", ""); ::SWGSDRangel::setValue(&play_loop, pJson["playLoop"], "qint32", ""); @@ -196,9 +189,6 @@ SWGNFMModSettings::asJsonObject() { if(m_volume_factor_isSet){ obj->insert("volumeFactor", QJsonValue(volume_factor)); } - if(m_audio_sample_rate_isSet){ - obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); - } if(m_channel_mute_isSet){ obj->insert("channelMute", QJsonValue(channel_mute)); } @@ -287,16 +277,6 @@ SWGNFMModSettings::setVolumeFactor(float volume_factor) { this->m_volume_factor_isSet = true; } -qint32 -SWGNFMModSettings::getAudioSampleRate() { - return audio_sample_rate; -} -void -SWGNFMModSettings::setAudioSampleRate(qint32 audio_sample_rate) { - this->audio_sample_rate = audio_sample_rate; - this->m_audio_sample_rate_isSet = true; -} - qint32 SWGNFMModSettings::getChannelMute() { return channel_mute; @@ -388,7 +368,6 @@ SWGNFMModSettings::isSet(){ if(m_fm_deviation_isSet){ isObjectUpdated = true; break;} if(m_tone_frequency_isSet){ isObjectUpdated = true; break;} if(m_volume_factor_isSet){ isObjectUpdated = true; break;} - if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} if(m_channel_mute_isSet){ isObjectUpdated = true; break;} if(m_play_loop_isSet){ isObjectUpdated = true; break;} if(m_ctcss_on_isSet){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h index 475de4899..7fbe6aea3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h @@ -61,9 +61,6 @@ public: float getVolumeFactor(); void setVolumeFactor(float volume_factor); - qint32 getAudioSampleRate(); - void setAudioSampleRate(qint32 audio_sample_rate); - qint32 getChannelMute(); void setChannelMute(qint32 channel_mute); @@ -110,9 +107,6 @@ private: float volume_factor; bool m_volume_factor_isSet; - qint32 audio_sample_rate; - bool m_audio_sample_rate_isSet; - qint32 channel_mute; bool m_channel_mute_isSet; From 0d7f73f595bed8f8cf39c1ffe3fa396ea9986b46 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 16:57:42 +0200 Subject: [PATCH 209/956] Multiple audio support: SSB modulator --- plugins/channeltx/modssb/CMakeLists.txt | 2 + plugins/channeltx/modssb/ssbmod.cpp | 147 ++++++++++++++++---- plugins/channeltx/modssb/ssbmod.h | 3 + plugins/channeltx/modssb/ssbmodgui.cpp | 29 +++- plugins/channeltx/modssb/ssbmodgui.h | 1 + plugins/channeltx/modssb/ssbmodsettings.cpp | 4 +- plugins/channeltx/modssb/ssbmodsettings.h | 2 +- 7 files changed, 154 insertions(+), 34 deletions(-) diff --git a/plugins/channeltx/modssb/CMakeLists.txt b/plugins/channeltx/modssb/CMakeLists.txt index fc652b3a4..b8650ad5b 100644 --- a/plugins/channeltx/modssb/CMakeLists.txt +++ b/plugins/channeltx/modssb/CMakeLists.txt @@ -1,5 +1,7 @@ project(modssb) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(modssb_SOURCES ssbmod.cpp ssbmodgui.cpp diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index 51066ace9..a99bde786 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -70,8 +70,11 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : { setObjectName(m_channelId); - m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_settings.m_audioSampleRate, m_settings.m_bandwidth / m_settings.m_audioSampleRate, m_ssbFftLen); - m_DSBFilter = new fftfilt((2.0f * m_settings.m_bandwidth) / m_settings.m_audioSampleRate, 2 * m_ssbFftLen); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + + m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_audioSampleRate, m_settings.m_bandwidth / m_audioSampleRate, m_ssbFftLen); + m_DSBFilter = new fftfilt((2.0f * m_settings.m_bandwidth) / m_audioSampleRate, 2 * m_ssbFftLen); m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size m_DSBFilterBuffer = new Complex[m_ssbFftLen]; memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); @@ -87,11 +90,10 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : m_magsq = 0.0; - m_toneNco.setFreq(1000.0, m_settings.m_audioSampleRate); - DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_toneNco.setFreq(1000.0, m_audioSampleRate); // CW keyer - m_cwKeyer.setSampleRate(m_settings.m_audioSampleRate); + m_cwKeyer.setSampleRate(48000); m_cwKeyer.setWPM(13); m_cwKeyer.setMode(CWKeyerSettings::CWNone); @@ -175,7 +177,7 @@ void SSBMod::pull(Sample& sample) void SSBMod::pullAudio(int nbSamples) { - unsigned int nbSamplesAudio = nbSamples * ((Real) m_settings.m_audioSampleRate / (Real) m_basebandSampleRate); + unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); if (nbSamplesAudio > m_audioBuffer.size()) { @@ -580,9 +582,26 @@ bool SSBMod::handleMessage(const Message& cmd) samplesCount = m_ifstream.tellg() / sizeof(Real); } - MsgReportFileSourceStreamTiming *report; - report = MsgReportFileSourceStreamTiming::create(samplesCount); - getMessageQueueToGUI()->push(report); + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamTiming *report; + report = MsgReportFileSourceStreamTiming::create(samplesCount); + getMessageQueueToGUI()->push(report); + } + + return true; + } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "SSBMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } return true; } @@ -613,9 +632,12 @@ void SSBMod::openFileStream() << " fileSize: " << m_fileSize << "bytes" << " length: " << m_recordLength << " seconds"; - MsgReportFileSourceStreamData *report; - report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength); - getMessageQueueToGUI()->push(report); + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamData *report; + report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength); + getMessageQueueToGUI()->push(report); + } } void SSBMod::seekFileStream(int seekPercentage) @@ -631,6 +653,68 @@ void SSBMod::seekFileStream(int seekPercentage) } } +void SSBMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("SSBMod::applyAudioSampleRate: %d", sampleRate); + + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_bandwidth, 3.0); + + float band = m_settings.m_bandwidth; + float lowCutoff = m_settings.m_lowCutoff; + bool usb = m_settings.m_usb; + + if (band < 0) // negative means LSB + { + band = -band; // turn to positive + lowCutoff = -lowCutoff; + usb = false; // and take note of side band + } + else + { + usb = true; + } + + if (band < 100.0f) // at least 100 Hz + { + band = 100.0f; + lowCutoff = 0; + } + + if (band - lowCutoff < 100.0f) { + lowCutoff = band - 100.0f; + } + + m_SSBFilter->create_filter(lowCutoff / sampleRate, band / sampleRate); + m_DSBFilter->create_dsb_filter((2.0f * band) / sampleRate); + + m_settings.m_bandwidth = band; + m_settings.m_lowCutoff = lowCutoff; + m_settings.m_usb = usb; + + m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); + m_cwKeyer.setSampleRate(sampleRate); + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; + + if (getMessageQueueToGUI()) + { + DSPConfigureAudio *cfg = new DSPConfigureAudio(m_audioSampleRate); + getMessageQueueToGUI()->push(cfg); + } +} + void SSBMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "SSBMod::applyChannelSettings:" @@ -651,8 +735,8 @@ void SSBMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_settings.m_audioSampleRate / (Real) outputSampleRate; - m_interpolator.create(48, m_settings.m_audioSampleRate, m_settings.m_bandwidth, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_bandwidth, 3.0); m_settingsMutex.unlock(); } @@ -668,8 +752,7 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) bool usb = settings.m_usb; if ((settings.m_bandwidth != m_settings.m_bandwidth) || - (settings.m_lowCutoff != m_settings.m_lowCutoff) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + (settings.m_lowCutoff != m_settings.m_lowCutoff) || force) { if (band < 0) // negative means LSB { @@ -695,25 +778,17 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) m_outputSampleRate; - m_interpolator.create(48, settings.m_audioSampleRate, band, 3.0); - m_SSBFilter->create_filter(lowCutoff / settings.m_audioSampleRate, band / settings.m_audioSampleRate); - m_DSBFilter->create_dsb_filter((2.0f * band) / settings.m_audioSampleRate); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, band, 3.0); + m_SSBFilter->create_filter(lowCutoff / m_audioSampleRate, band / m_audioSampleRate); + m_DSBFilter->create_dsb_filter((2.0f * band) / m_audioSampleRate); m_settingsMutex.unlock(); } - if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) { m_settingsMutex.lock(); - m_toneNco.setFreq(settings.m_toneFrequency, settings.m_audioSampleRate); - m_settingsMutex.unlock(); - } - - if ((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) - { - m_settingsMutex.lock(); - m_cwKeyer.setSampleRate(settings.m_audioSampleRate); + m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); m_settingsMutex.unlock(); } @@ -759,6 +834,18 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) m_inAGC.setStepDownDelay(settings.m_agcThresholdDelay); } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + m_settings = settings; m_settings.m_bandwidth = band; m_settings.m_lowCutoff = lowCutoff; diff --git a/plugins/channeltx/modssb/ssbmod.h b/plugins/channeltx/modssb/ssbmod.h index 3929813a7..263420c1e 100644 --- a/plugins/channeltx/modssb/ssbmod.h +++ b/plugins/channeltx/modssb/ssbmod.h @@ -248,6 +248,7 @@ public: virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } double getMagSq() const { return m_magsq; } CWKeyer *getCWKeyer() { return &m_cwKeyer; } @@ -279,6 +280,7 @@ private: int m_outputSampleRate; int m_inputFrequencyOffset; SSBModSettings m_settings; + quint32 m_audioSampleRate; NCOF m_carrierNco; NCOF m_toneNco; @@ -327,6 +329,7 @@ private: static const int m_levelNbSamples; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const SSBModSettings& settings, bool force = false); void pullAF(Complex& sample); diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index 2ad877ce9..6f2819034 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -30,6 +30,9 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "mainwindow.h" SSBModGUI* SSBModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) @@ -108,6 +111,12 @@ bool SSBModGUI::handleMessage(const Message& message) updateWithStreamTime(); return true; } + else if (DSPConfigureAudio::match(message)) + { + qDebug("SSBModGUI::handleMessage: DSPConfigureAudio: %d", m_ssbMod->getAudioSampleRate()); + applyBandwidths(); // will update spectrum details with new sample rate + return true; + } else { return false; @@ -379,6 +388,9 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -453,10 +465,10 @@ void SSBModGUI::applyBandwidths(bool force) { bool dsb = ui->dsb->isChecked(); int spanLog2 = ui->spanLog2->value(); - m_spectrumRate = 48000 / (1<getAudioSampleRate() / (1<BW->value(); int lw = ui->lowCut->value(); - int bwMax = 480/(1<getAudioSampleRate() / (100*(1<getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void SSBModGUI::tick() { double powDb = CalcDb::dbPower(m_ssbMod->getMagSq()); diff --git a/plugins/channeltx/modssb/ssbmodgui.h b/plugins/channeltx/modssb/ssbmodgui.h index 55ba7360c..3ebb0d225 100644 --- a/plugins/channeltx/modssb/ssbmodgui.h +++ b/plugins/channeltx/modssb/ssbmodgui.h @@ -129,6 +129,7 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void configureFileName(); + void audioSelect(); void tick(); }; diff --git a/plugins/channeltx/modssb/ssbmodsettings.cpp b/plugins/channeltx/modssb/ssbmodsettings.cpp index f84a28b48..09fea178c 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.cpp +++ b/plugins/channeltx/modssb/ssbmodsettings.cpp @@ -51,7 +51,6 @@ void SSBModSettings::resetToDefaults() m_usb = true; m_toneFrequency = 1000.0; m_volumeFactor = 1.0; - m_audioSampleRate = DSPEngine::instance()->getDefaultAudioSampleRate(); m_spanLog2 = 3; m_audioBinaural = false; m_audioFlipChannels = false; @@ -69,6 +68,7 @@ void SSBModSettings::resetToDefaults() m_udpAddress = "127.0.0.1"; m_udpPort = 9999; m_title = "SSB Modulator"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray SSBModSettings::serialize() const @@ -106,6 +106,7 @@ QByteArray SSBModSettings::serialize() const } s.writeString(19, m_title); + s.writeString(20, m_audioDeviceName); return s.final(); } @@ -171,6 +172,7 @@ bool SSBModSettings::deserialize(const QByteArray& data) } d.readString(19, &m_title, "SSB Modulator"); + d.readString(20, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); return true; } diff --git a/plugins/channeltx/modssb/ssbmodsettings.h b/plugins/channeltx/modssb/ssbmodsettings.h index 2cab6cb18..1266b0edc 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.h +++ b/plugins/channeltx/modssb/ssbmodsettings.h @@ -34,7 +34,6 @@ struct SSBModSettings bool m_usb; float m_toneFrequency; float m_volumeFactor; - quint32 m_audioSampleRate; int m_spanLog2; bool m_audioBinaural; bool m_audioFlipChannels; @@ -54,6 +53,7 @@ struct SSBModSettings uint16_t m_udpPort; QString m_title; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_spectrumGUI; From d2a658d8a1e78c3385c3ec327c361050c8937b41 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 17:06:04 +0200 Subject: [PATCH 210/956] HackRF output: allow interpolation by 64 --- .../samplesink/hackrfoutput/hackrfoutputgui.ui | 16 ++++++---------- .../hackrfoutput/hackrfoutputplugin.cpp | 2 +- plugins/samplesink/hackrfoutput/readme.md | 1 + 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui index 1c98afb3c..68e0763d9 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui +++ b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui @@ -35,16 +35,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -400,6 +391,11 @@ 32 + + + 64 + + diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp b/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp index 75bf0f0aa..a1140bf03 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor HackRFOutputPlugin::m_pluginDescriptor = { QString("HackRF Output"), - QString("3.9.0"), + QString("3.14.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/hackrfoutput/readme.md b/plugins/samplesink/hackrfoutput/readme.md index e2754c06d..b769d7b69 100644 --- a/plugins/samplesink/hackrfoutput/readme.md +++ b/plugins/samplesink/hackrfoutput/readme.md @@ -52,6 +52,7 @@ The baseband stream is interpolated by this value before being sent to the HackR - **8**: multiply baseband stream sample rate by 8 - **16**: multiply baseband stream sample rate by 16 - **32**: multiply baseband stream sample rate by 32 + - **64**: multiply baseband stream sample rate by 64 The main samples buffer is based on the baseband sample rate and will introduce ~500ms delay for interpolation by 16 or lower and ~1s for interpolation by 32. From 75201ad303cbfa501e23cf9fbeb0b807cc57bbb8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 17:27:03 +0200 Subject: [PATCH 211/956] Multiple audio support: WFM modulator --- plugins/channeltx/modwfm/CMakeLists.txt | 2 + plugins/channeltx/modwfm/readme.md | 6 ++- plugins/channeltx/modwfm/wfmmod.cpp | 59 +++++++++++++++++---- plugins/channeltx/modwfm/wfmmod.h | 3 +- plugins/channeltx/modwfm/wfmmodgui.cpp | 18 +++++++ plugins/channeltx/modwfm/wfmmodgui.h | 1 + plugins/channeltx/modwfm/wfmmodsettings.cpp | 4 +- plugins/channeltx/modwfm/wfmmodsettings.h | 2 +- 8 files changed, 81 insertions(+), 14 deletions(-) diff --git a/plugins/channeltx/modwfm/CMakeLists.txt b/plugins/channeltx/modwfm/CMakeLists.txt index d712b8597..7f28858d4 100644 --- a/plugins/channeltx/modwfm/CMakeLists.txt +++ b/plugins/channeltx/modwfm/CMakeLists.txt @@ -1,5 +1,7 @@ project(modwfm) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(modwfm_SOURCES wfmmod.cpp wfmmodgui.cpp diff --git a/plugins/channeltx/modwfm/readme.md b/plugins/channeltx/modwfm/readme.md index 1ce694997..51b6a2cb2 100644 --- a/plugins/channeltx/modwfm/readme.md +++ b/plugins/channeltx/modwfm/readme.md @@ -60,9 +60,11 @@ Switches to the Morse keyer input. You must switch it off to make other inputs a Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps -

    10.4: Audio input select

    +

    10.4: Audio input select and select audio input device

    -Switches to the audio input. You must switch it off to make other inputs available. +Left click to switch to the audio input. You must switch it off to make other inputs available. + +Right click to select audio input device.

    11: CW (Morse) text

    diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index b78be126c..14dfcaf80 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -73,9 +73,10 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : m_magsq = 0.0; - m_toneNco.setFreq(1000.0, m_settings.m_audioSampleRate); - m_toneNcoRF.setFreq(1000.0, m_outputSampleRate); DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + + m_toneNcoRF.setFreq(1000.0, m_outputSampleRate); // CW keyer m_cwKeyer.setSampleRate(m_outputSampleRate); @@ -164,7 +165,7 @@ void WFMMod::pull(Sample& sample) void WFMMod::pullAudio(int nbSamples) { - unsigned int nbSamplesAudio = nbSamples * ((Real) m_settings.m_audioSampleRate / (Real) m_basebandSampleRate); + unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); if (nbSamplesAudio > m_audioBuffer.size()) { @@ -359,6 +360,20 @@ bool WFMMod::handleMessage(const Message& cmd) return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "WFMMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -404,6 +419,20 @@ void WFMMod::seekFileStream(int seekPercentage) } } +void WFMMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("WFMMod::applyAudioSampleRate: %d", sampleRate); + + m_settingsMutex.lock(); + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void WFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "WFMMod::applyChannelSettings:" @@ -424,8 +453,8 @@ void WFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_settings.m_audioSampleRate / (Real) outputSampleRate; - m_interpolator.create(48, m_settings.m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / outputSampleRate; Real hiCut = (m_settings.m_rfBandwidth / 2.0) / outputSampleRate; m_rfFilter->create_filter(lowCut, hiCut); @@ -451,16 +480,16 @@ void WFMMod::applySettings(const WFMModSettings& settings, bool force) << " m_toneFrequency: " << settings.m_toneFrequency << " m_channelMute: " << settings.m_channelMute << " m_playLoop: " << settings.m_playLoop + << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; - if((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || - (settings.m_afBandwidth != m_settings.m_afBandwidth) || force) + if((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) m_outputSampleRate; - m_interpolator.create(48, settings.m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } @@ -480,6 +509,18 @@ void WFMMod::applySettings(const WFMModSettings& settings, bool force) m_settingsMutex.unlock(); } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + m_settings = settings; } diff --git a/plugins/channeltx/modwfm/wfmmod.h b/plugins/channeltx/modwfm/wfmmod.h index b324c79dc..d47608181 100644 --- a/plugins/channeltx/modwfm/wfmmod.h +++ b/plugins/channeltx/modwfm/wfmmod.h @@ -277,9 +277,9 @@ private: int m_outputSampleRate; int m_inputFrequencyOffset; WFMModSettings m_settings; + quint32 m_audioSampleRate; NCO m_carrierNco; - NCOF m_toneNco; NCOF m_toneNcoRF; float m_modPhasor; //!< baseband modulator phasor Complex m_modSample; @@ -316,6 +316,7 @@ private: CWKeyer m_cwKeyer; static const int m_levelNbSamples; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const WFMModSettings& settings, bool force = false); void pullAF(Complex& sample); diff --git a/plugins/channeltx/modwfm/wfmmodgui.cpp b/plugins/channeltx/modwfm/wfmmodgui.cpp index d6a62b91a..aaf6d9ac6 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.cpp +++ b/plugins/channeltx/modwfm/wfmmodgui.cpp @@ -28,6 +28,8 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "dsp/dspengine.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "mainwindow.h" #include "ui_wfmmodgui.h" @@ -299,6 +301,9 @@ WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -411,6 +416,19 @@ void WFMModGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void WFMModGUI::audioSelect() +{ + qDebug("WFMModGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void WFMModGUI::tick() { double powDb = CalcDb::dbPower(m_wfmMod->getMagSq()); diff --git a/plugins/channeltx/modwfm/wfmmodgui.h b/plugins/channeltx/modwfm/wfmmodgui.h index aca95366b..1dc64de3c 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.h +++ b/plugins/channeltx/modwfm/wfmmodgui.h @@ -122,6 +122,7 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void configureFileName(); + void audioSelect(); void tick(); }; diff --git a/plugins/channeltx/modwfm/wfmmodsettings.cpp b/plugins/channeltx/modwfm/wfmmodsettings.cpp index afae3d54c..b4d596c80 100644 --- a/plugins/channeltx/modwfm/wfmmodsettings.cpp +++ b/plugins/channeltx/modwfm/wfmmodsettings.cpp @@ -42,12 +42,12 @@ void WFMModSettings::resetToDefaults() m_afBandwidth = 15000.0f; m_fmDeviation = 50000.0f; m_toneFrequency = 1000.0f; - m_audioSampleRate = DSPEngine::instance()->getDefaultAudioSampleRate(); m_volumeFactor = 1.0f; m_channelMute = false; m_playLoop = false; m_rgbColor = QColor(0, 0, 255).rgb(); m_title = "WFM Modulator"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray WFMModSettings::serialize() const @@ -71,6 +71,7 @@ QByteArray WFMModSettings::serialize() const } s.writeString(10, m_title); + s.writeString(11, m_audioDeviceName); return s.final(); } @@ -110,6 +111,7 @@ bool WFMModSettings::deserialize(const QByteArray& data) } d.readString(10, &m_title, "WFM Modulator"); + d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); return true; } diff --git a/plugins/channeltx/modwfm/wfmmodsettings.h b/plugins/channeltx/modwfm/wfmmodsettings.h index f83b8cf22..2b1cff3fe 100644 --- a/plugins/channeltx/modwfm/wfmmodsettings.h +++ b/plugins/channeltx/modwfm/wfmmodsettings.h @@ -32,11 +32,11 @@ struct WFMModSettings float m_fmDeviation; float m_toneFrequency; float m_volumeFactor; - quint32 m_audioSampleRate; bool m_channelMute; bool m_playLoop; quint32 m_rgbColor; QString m_title; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_cwKeyerGUI; From b2442226671daaf4cefd87b5b0d183fb8996fb0b Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 29 Mar 2018 19:55:03 +0200 Subject: [PATCH 212/956] qrtplib: fixed urandom calls --- qrtplib/rtppacket.cpp | 22 ++++++++--------- qrtplib/rtppacket.h | 47 +++++++++++++++++++++++++++--------- qrtplib/rtppacketbuilder.cpp | 41 ++++++++++++++++++++++++------- qrtplib/rtppacketbuilder.h | 28 ++++++++++++--------- qrtplib/rtprandom.h | 2 +- qrtplib/rtprandomrand48.cpp | 2 ++ qrtplib/rtprandomurandom.cpp | 36 +++++++++++++++++++++------ 7 files changed, 128 insertions(+), 50 deletions(-) diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp index b8aa717ed..a0545da34 100644 --- a/qrtplib/rtppacket.cpp +++ b/qrtplib/rtppacket.cpp @@ -70,7 +70,7 @@ RTPPacket::RTPPacket(RTPRawPacket &rawpack) : RTPPacket::RTPPacket( uint8_t payloadtype, const void *payloaddata, - std::size_t payloadlen, + unsigned int payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, @@ -81,7 +81,7 @@ RTPPacket::RTPPacket( uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, - std::size_t maxpacksize) : + unsigned int maxpacksize) : receivetime(0, 0) { Clear(); @@ -106,7 +106,7 @@ RTPPacket::RTPPacket( RTPPacket::RTPPacket( uint8_t payloadtype, const void *payloaddata, - std::size_t payloadlen, + unsigned int payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, @@ -118,7 +118,7 @@ RTPPacket::RTPPacket( uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, - std::size_t buffersize) : + unsigned int buffersize) : receivetime(0, 0) { Clear(); @@ -148,7 +148,7 @@ RTPPacket::RTPPacket( int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) { uint8_t *packetbytes; - std::size_t packetlen; + unsigned int packetlen; uint8_t payloadtype; RTPHeader *rtpheader; bool marker; @@ -264,7 +264,7 @@ uint32_t RTPPacket::GetCSRC(int num) const int RTPPacket::BuildPacket( uint8_t payloadtype, const void *payloaddata, - std::size_t payloadlen, + unsigned int payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, @@ -276,7 +276,7 @@ int RTPPacket::BuildPacket( uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, - std::size_t maxsize) + unsigned int maxsize) { if (numcsrcs > RTP_MAXCSRCS) return ERR_RTP_PACKET_TOOMANYCSRCS; @@ -287,11 +287,11 @@ int RTPPacket::BuildPacket( return ERR_RTP_PACKET_BADPAYLOADTYPE; packetlength = sizeof(RTPHeader); - packetlength += sizeof(uint32_t) * ((std::size_t) numcsrcs); + packetlength += sizeof(uint32_t) * ((unsigned int) numcsrcs); if (gotextension) { packetlength += sizeof(RTPExtensionHeader); - packetlength += sizeof(uint32_t) * ((std::size_t) extensionlen_numwords); + packetlength += sizeof(uint32_t) * ((unsigned int) extensionlen_numwords); } packetlength += payloadlen; @@ -330,7 +330,7 @@ int RTPPacket::BuildPacket( RTPPacket::ssrc = ssrc; RTPPacket::payloadlength = payloadlen; RTPPacket::extid = extensionid; - RTPPacket::extensionlength = ((std::size_t) extensionlen_numwords) * sizeof(uint32_t); + RTPPacket::extensionlength = ((unsigned int) extensionlen_numwords) * sizeof(uint32_t); rtphdr = (RTPHeader *) packet; rtphdr->version = RTP_VERSION; @@ -356,7 +356,7 @@ int RTPPacket::BuildPacket( for (i = 0; i < numcsrcs; i++, curcsrc++) *curcsrc = qToBigEndian(csrcs[i]); - payload = packet + sizeof(RTPHeader) + ((std::size_t) numcsrcs) * sizeof(uint32_t); + payload = packet + sizeof(RTPHeader) + ((unsigned int) numcsrcs) * sizeof(uint32_t); if (gotextension) { RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *) payload; diff --git a/qrtplib/rtppacket.h b/qrtplib/rtppacket.h index 8e8797d3f..46c632540 100644 --- a/qrtplib/rtppacket.h +++ b/qrtplib/rtppacket.h @@ -68,13 +68,38 @@ public: * \c maxpacksize. The arguments of the constructor are self-explanatory. Note that the size of a header * extension is specified in a number of 32-bit words. A memory manager can be installed. */ - RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, std::size_t maxpacksize); + RTPPacket(uint8_t payloadtype, + const void *payloaddata, + unsigned int payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + unsigned int maxpacksize); /** This constructor is similar to the other constructor, but here data is stored in an external buffer * \c buffer with size \c buffersize. */ - RTPPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, std::size_t buffersize); + RTPPacket(uint8_t payloadtype, + const void *payloaddata, + unsigned int payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + unsigned int buffersize); virtual ~RTPPacket() { @@ -163,13 +188,13 @@ public: } /** Returns the length of the entire packet. */ - std::size_t GetPacketLength() const + unsigned int GetPacketLength() const { return packetlength; } /** Returns the payload length. */ - std::size_t GetPayloadLength() const + unsigned int GetPayloadLength() const { return payloadlength; } @@ -187,7 +212,7 @@ public: } /** Returns the length of the header extension data. */ - std::size_t GetExtensionLength() const + unsigned int GetExtensionLength() const { return extensionlength; } @@ -204,8 +229,8 @@ public: private: void Clear(); int ParseRawPacket(RTPRawPacket &rawpack); - int BuildPacket(uint8_t payloadtype, const void *payloaddata, std::size_t payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, - const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, std::size_t maxsize); + int BuildPacket(uint8_t payloadtype, const void *payloaddata, unsigned int payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, unsigned int maxsize); RTPEndian m_endian; int error; @@ -216,11 +241,11 @@ private: uint8_t payloadtype; uint32_t extseqnr, timestamp, ssrc; uint8_t *packet, *payload; - std::size_t packetlength, payloadlength; + unsigned int packetlength, payloadlength; uint16_t extid; uint8_t *extension; - std::size_t extensionlength; + unsigned int extensionlength; bool externalbuffer; diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib/rtppacketbuilder.cpp index 10f97e4f4..a6974430a 100644 --- a/qrtplib/rtppacketbuilder.cpp +++ b/qrtplib/rtppacketbuilder.cpp @@ -41,9 +41,27 @@ namespace qrtplib { RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r) : - rtprnd(r), lastwallclocktime(0, 0) + rtprnd(r), + maxpacksize(0), + buffer(0), + packetlength(0), + lastwallclocktime(0, 0) { init = false; + deftsset = false; + defptset = false; + defmarkset = false; + defaultmark = false; + defaulttimestampinc = 0; + ssrc = 0; + timestamp = 0; + seqnr = 0; + prevrtptimestamp = 0; + lastrtptimestamp = 0; + defaultpayloadtype = 0; + numcsrcs = 0; + numpayloadbytes = 0; + numpackets = 0; timeinit.Dummy(); //std::cout << (void *)(&rtprnd) << std::endl; @@ -54,7 +72,7 @@ RTPPacketBuilder::~RTPPacketBuilder() Destroy(); } -int RTPPacketBuilder::Init(std::size_t max) +int RTPPacketBuilder::Init(unsigned int max) { if (init) return ERR_RTP_PACKBUILD_ALREADYINIT; @@ -66,6 +84,7 @@ int RTPPacketBuilder::Init(std::size_t max) if (buffer == 0) return ERR_RTP_OUTOFMEM; packetlength = 0; + numpackets = 0; CreateNewSSRC(); @@ -87,7 +106,7 @@ void RTPPacketBuilder::Destroy() init = false; } -int RTPPacketBuilder::SetMaximumPacketSize(std::size_t max) +int RTPPacketBuilder::SetMaximumPacketSize(unsigned int max) { uint8_t *newbuf; @@ -161,6 +180,8 @@ uint32_t RTPPacketBuilder::CreateNewSSRC() timestamp = rtprnd.GetRandom32(); seqnr = rtprnd.GetRandom16(); + qDebug("RTPPacketBuilder::CreateNewSSRC: timestamp: %u", timestamp); + // p 38: the count SHOULD be reset if the sender changes its SSRC identifier numpayloadbytes = 0; numpackets = 0; @@ -186,7 +207,7 @@ uint32_t RTPPacketBuilder::CreateNewSSRC(RTPSources &sources) return ssrc; } -int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len) +int RTPPacketBuilder::BuildPacket(const void *data, unsigned int len) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; @@ -199,14 +220,14 @@ int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len) return PrivateBuildPacket(data, len, defaultpayloadtype, defaultmark, defaulttimestampinc, false); } -int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc) +int RTPPacketBuilder::BuildPacket(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; return PrivateBuildPacket(data, len, pt, mark, timestampinc, false); } -int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) +int RTPPacketBuilder::BuildPacketEx(const void *data, unsigned int len, uint16_t hdrextID, const void *hdrextdata, unsigned int numhdrextwords) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; @@ -219,7 +240,7 @@ int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, uint16_t return PrivateBuildPacket(data, len, defaultpayloadtype, defaultmark, defaulttimestampinc, true, hdrextID, hdrextdata, numhdrextwords); } -int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) +int RTPPacketBuilder::BuildPacketEx(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, unsigned int numhdrextwords) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; @@ -227,8 +248,8 @@ int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, uint8_t p } -int RTPPacketBuilder::PrivateBuildPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID, const void *hdrextdata, - std::size_t numhdrextwords) +int RTPPacketBuilder::PrivateBuildPacket(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID, const void *hdrextdata, + unsigned int numhdrextwords) { RTPPacket p(pt, data, len, seqnr, timestamp, ssrc, mark, numcsrcs, csrcs, gotextension, hdrextID, (uint16_t) numhdrextwords, hdrextdata, buffer, maxpacksize); int status = p.GetCreationError(); @@ -255,6 +276,8 @@ int RTPPacketBuilder::PrivateBuildPacket(const void *data, std::size_t len, uint timestamp += timestampinc; seqnr++; + //qDebug("RTPPacketBuilder::PrivateBuildPacket: numpackets: %u timestamp: %u timestampinc: %u seqnr: %u", numpackets, timestamp, timestampinc, seqnr); + return 0; } diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib/rtppacketbuilder.h index 02c0e8c4d..a1cdedb12 100644 --- a/qrtplib/rtppacketbuilder.h +++ b/qrtplib/rtppacketbuilder.h @@ -65,7 +65,7 @@ public: ~RTPPacketBuilder(); /** Initializes the builder to only allow packets with a size below \c maxpacksize. */ - int Init(std::size_t maxpacksize); + int Init(unsigned int maxpacksize); /** Cleans up the builder. */ void Destroy(); @@ -87,7 +87,7 @@ public: } /** Sets the maximum allowed packet size to \c maxpacksize. */ - int SetMaximumPacketSize(std::size_t maxpacksize); + int SetMaximumPacketSize(unsigned int maxpacksize); /** Adds a CSRC to the CSRC list which will be stored in the RTP packets. */ int AddCSRC(uint32_t csrc); @@ -103,14 +103,14 @@ public: * and timestamp increment used will be those that have been set using the \c SetDefault * functions below. */ - int BuildPacket(const void *data, std::size_t len); + int BuildPacket(const void *data, unsigned int len); /** Builds a packet with payload \c data and payload length \c len. * Builds a packet with payload \c data and payload length \c len. The payload type will be * set to \c pt, the marker bit to \c mark and after building this packet, the timestamp will * be incremented with \c timestamp. */ - int BuildPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc); + int BuildPacket(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc); /** Builds a packet with payload \c data and payload length \c len. * Builds a packet with payload \c data and payload length \c len. The payload type, marker @@ -119,7 +119,7 @@ public: * \c hdrextID and data \c hdrextdata. The length of the header extension data is given by * \c numhdrextwords which expresses the length in a number of 32-bit words. */ - int BuildPacketEx(const void *data, std::size_t len, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); + int BuildPacketEx(const void *data, unsigned int len, uint16_t hdrextID, const void *hdrextdata, unsigned int numhdrextwords); /** Builds a packet with payload \c data and payload length \c len. * Builds a packet with payload \c data and payload length \c len. The payload type will be set @@ -128,7 +128,7 @@ public: * with identifier \c hdrextID and data \c hdrextdata. The length of the header extension * data is given by \c numhdrextwords which expresses the length in a number of 32-bit words. */ - int BuildPacketEx(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); + int BuildPacketEx(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, unsigned int numhdrextwords); /** Returns a pointer to the last built RTP packet data. */ uint8_t *GetPacket() @@ -139,7 +139,7 @@ public: } /** Returns the size of the last built RTP packet. */ - std::size_t GetPacketLength() + unsigned int GetPacketLength() { if (!init) return 0; @@ -237,13 +237,19 @@ public: ssrc = s; } private: - int PrivateBuildPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID = 0, const void *hdrextdata = 0, - std::size_t numhdrextwords = 0); + int PrivateBuildPacket( + const void *data, + unsigned int len, uint8_t pt, + bool mark, + uint32_t timestampinc, + bool gotextension, uint16_t hdrextID = 0, + const void *hdrextdata = 0, + unsigned int numhdrextwords = 0); RTPRandom &rtprnd; - std::size_t maxpacksize; + unsigned int maxpacksize; uint8_t *buffer; - std::size_t packetlength; + unsigned int packetlength; uint32_t numpayloadbytes; uint32_t numpackets; diff --git a/qrtplib/rtprandom.h b/qrtplib/rtprandom.h index fb8fb965a..249914c6a 100644 --- a/qrtplib/rtprandom.h +++ b/qrtplib/rtprandom.h @@ -44,7 +44,7 @@ #include "export.h" -#define RTPRANDOM_2POWMIN63 1.08420217248550443400745280086994171142578125e-19 +#define RTPRANDOM_2POWMIN63 1.08420217248550443400745280086994171142578125e-19 namespace qrtplib { diff --git a/qrtplib/rtprandomrand48.cpp b/qrtplib/rtprandomrand48.cpp index c80ccf6aa..8e634f939 100644 --- a/qrtplib/rtprandomrand48.cpp +++ b/qrtplib/rtprandomrand48.cpp @@ -31,6 +31,7 @@ */ #include "rtprandomrand48.h" +#include namespace qrtplib { @@ -72,6 +73,7 @@ uint32_t RTPRandomRand48::GetRandom32() { state = ((0x5DEECE66DULL * state) + 0xBULL) & 0x0000ffffffffffffULL; uint32_t x = (uint32_t) ((state >> 16) & 0xffffffffULL); + qDebug("RTPRandomRand48::GetRandom32: %u", x); return x; } diff --git a/qrtplib/rtprandomurandom.cpp b/qrtplib/rtprandomurandom.cpp index b96b5c6cf..6fbb9a291 100644 --- a/qrtplib/rtprandomurandom.cpp +++ b/qrtplib/rtprandomurandom.cpp @@ -32,6 +32,8 @@ #include "rtprandomurandom.h" #include "rtperrors.h" +#include +#include namespace qrtplib { @@ -43,18 +45,22 @@ RTPRandomURandom::RTPRandomURandom() RTPRandomURandom::~RTPRandomURandom() { - if (device) + if (device) { fclose(device); + } } int RTPRandomURandom::Init() { - if (device) + if (device) { return ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN; + } device = fopen("/dev/urandom", "rb"); - if (device == 0) + + if (device == 0) { return ERR_RTP_RTPRANDOMURANDOM_CANTOPEN; + } return 0; } @@ -62,12 +68,16 @@ int RTPRandomURandom::Init() uint8_t RTPRandomURandom::GetRandom8() { if (!device) + { + qWarning("RTPRandomURandom::GetRandom8: no device"); return 0; + } uint8_t value; - if (fread(&value, sizeof(uint8_t), 1, device) != sizeof(uint8_t)) + if (fread(&value, 1, sizeof(uint8_t), device) != sizeof(uint8_t)) { + qWarning("RTPRandomURandom::GetRandom8: cannot read unsigned 8 bit value from device"); return 0; } @@ -77,12 +87,16 @@ uint8_t RTPRandomURandom::GetRandom8() uint16_t RTPRandomURandom::GetRandom16() { if (!device) + { + qWarning("RTPRandomURandom::GetRandom16: no device"); return 0; + } uint16_t value; - if (fread(&value, sizeof(uint16_t), 1, device) != sizeof(uint16_t)) + if (fread(&value, 1, sizeof(uint16_t), device) != sizeof(uint16_t)) { + qWarning("RTPRandomURandom::GetRandom16: cannot read unsigned 16 bit value from device"); return 0; } @@ -92,12 +106,16 @@ uint16_t RTPRandomURandom::GetRandom16() uint32_t RTPRandomURandom::GetRandom32() { if (!device) + { + qWarning("RTPRandomURandom::GetRandom32: no device"); return 0; + } uint32_t value; - if (fread(&value, sizeof(uint32_t), 1, device) != sizeof(uint32_t)) + if (fread(&value, 1, sizeof(uint32_t), device) != sizeof(uint32_t)) { + qWarning("RTPRandomURandom::GetRandom32: cannot read unsigned 32 bit value from device"); return 0; } @@ -107,12 +125,16 @@ uint32_t RTPRandomURandom::GetRandom32() double RTPRandomURandom::GetRandomDouble() { if (!device) + { + qWarning("RTPRandomURandom::GetRandomDouble: no device"); return 0; + } uint64_t value; - if (fread(&value, sizeof(uint64_t), 1, device) != sizeof(uint64_t)) + if (fread(&value, 1, sizeof(uint64_t), device) != sizeof(uint64_t)) { + qWarning("RTPRandomURandom::GetRandomDouble: cannot read unsigned 64 bit value from device"); return 0; } From d22b5ecd056cdc71e93f28a361af2e640687c906 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 01:19:02 +0200 Subject: [PATCH 213/956] Fixed RTP stereo --- sdrbase/audio/audionetsink.cpp | 3 +-- sdrbase/util/rtpsink.cpp | 33 +++++++++++++++++++++++++++++++++ sdrbase/util/rtpsink.h | 1 + 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index 8cde5dda6..5b7afd47d 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -148,8 +148,7 @@ void AudioNetSink::write(qint16 lSample, qint16 rSample) } else if (m_type == SinkRTP) { - m_rtpBufferAudio->write((uint8_t *) &lSample); - m_rtpBufferAudio->write((uint8_t *) &rSample); + m_rtpBufferAudio->write((uint8_t *) &lSample, (uint8_t *) &rSample); } } diff --git a/sdrbase/util/rtpsink.cpp b/sdrbase/util/rtpsink.cpp index b298d1146..71ea98cbf 100644 --- a/sdrbase/util/rtpsink.cpp +++ b/sdrbase/util/rtpsink.cpp @@ -212,6 +212,39 @@ void RTPSink::write(const uint8_t *sampleByte) } } +void RTPSink::write(const uint8_t *sampleByteL, const uint8_t *sampleByteR) +{ + QMutexLocker locker(&m_mutex); + + if (m_sampleBufferIndex < m_packetSamples) + { + writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes], + sampleByteL, + elemLength(m_payloadType), + m_sampleBytes, + m_endianReverse); + writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes + elemLength(m_payloadType)], + sampleByteR, + elemLength(m_payloadType), + m_sampleBytes, + m_endianReverse); + m_sampleBufferIndex++; + } + else + { + int status = m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize); + + if (status < 0) { + qCritical("RTPSink::write: cannot write packet: %s", qrtplib::RTPGetErrorString(status).c_str()); + } + + writeNetBuf(&m_byteBuffer[0], sampleByteL, elemLength(m_payloadType), m_sampleBytes, m_endianReverse); + writeNetBuf(&m_byteBuffer[2], sampleByteR, elemLength(m_payloadType), m_sampleBytes, m_endianReverse); + m_sampleBufferIndex = 1; + } + +} + void RTPSink::write(const uint8_t *samples, int nbSamples) { int samplesIndex = 0; diff --git a/sdrbase/util/rtpsink.h b/sdrbase/util/rtpsink.h index cdfb66093..424dca772 100644 --- a/sdrbase/util/rtpsink.h +++ b/sdrbase/util/rtpsink.h @@ -55,6 +55,7 @@ public: void addDestination(const QString& address, uint16_t port); void write(const uint8_t *sampleByte); + void write(const uint8_t *sampleByteL, const uint8_t *sampleByteR); void write(const uint8_t *sampleByte, int nbSamples); protected: From c3f3d2391d7f37c00fa30109d368c5e50c3dbe0e Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 01:30:06 +0200 Subject: [PATCH 214/956] Audio dialog cosmetic changes --- sdrgui/gui/audiodialog.ui | 7 +++++-- sdrgui/gui/audioselectdialog.ui | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/sdrgui/gui/audiodialog.ui b/sdrgui/gui/audiodialog.ui index 3a9928ffc..15803603c 100644 --- a/sdrgui/gui/audiodialog.ui +++ b/sdrgui/gui/audiodialog.ui @@ -210,7 +210,7 @@ Copy audio to UDP - U + UDP @@ -222,6 +222,9 @@ 0 + + Channel copy mode + Left @@ -250,7 +253,7 @@ Use RTP protocol - R + RTP diff --git a/sdrgui/gui/audioselectdialog.ui b/sdrgui/gui/audioselectdialog.ui index f3952ed61..0b2a7b0d5 100644 --- a/sdrgui/gui/audioselectdialog.ui +++ b/sdrgui/gui/audioselectdialog.ui @@ -6,7 +6,7 @@ 0 0 - 400 + 527 349 @@ -42,6 +42,9 @@ Rate + + AlignRight|AlignVCenter + From 10fe136b5e3f1d7709c1b745053915e7171fdc44 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 02:07:03 +0200 Subject: [PATCH 215/956] SSB demod: fixed AGC constants audio sample rate dependency --- plugins/channelrx/demodssb/ssbdemod.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 9fcd12310..f149e6ef7 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -387,11 +387,30 @@ void SSBDemod::applyAudioSampleRate(int sampleRate) m_inputMessageQueue.push(channelConfigMsg); m_settingsMutex.lock(); + m_interpolator.create(16, m_inputSampleRate, m_Bandwidth * 1.5f, 2.0f); m_interpolatorDistanceRemain = 0; m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; + SSBFilter->create_filter(m_LowCutoff / (float) sampleRate, m_Bandwidth / (float) sampleRate); DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) sampleRate); + + int agcNbSamples = (sampleRate / 1000) * (1< Date: Fri, 30 Mar 2018 08:55:49 +0200 Subject: [PATCH 216/956] AM, NFM, SSB demods: make audio FIFO length 1s for any audio sample rate --- plugins/channelrx/demodam/amdemod.cpp | 1 + plugins/channelrx/demodnfm/nfmdemod.cpp | 1 + plugins/channelrx/demodssb/ssbdemod.cpp | 2 ++ 3 files changed, 4 insertions(+) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index 8ee8182e0..dd18cf468 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -230,6 +230,7 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_interpolatorDistanceRemain = 0; m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); + m_audioFifo.setSize(sampleRate); m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index afa73f299..e8e82a716 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -406,6 +406,7 @@ void NFMDemod::applyAudioSampleRate(int sampleRate) m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; m_lowpass.create(301, sampleRate, 250.0); m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); + m_audioFifo.setSize(sampleRate); m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index f149e6ef7..7f3a93dbf 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -411,6 +411,8 @@ void SSBDemod::applyAudioSampleRate(int sampleRate) m_agcThresholdGate = agcThresholdGate; } + m_audioFifo.setSize(sampleRate); + m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; From ff5b6af6cca15a5c6a3ebc4fa0faa053b13af701 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 09:42:29 +0200 Subject: [PATCH 217/956] Audio preferences dialog: removed useless indicator --- sdrgui/gui/audiodialog.cpp | 8 ++------ sdrgui/gui/audiodialog.ui | 32 -------------------------------- 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/sdrgui/gui/audiodialog.cpp b/sdrgui/gui/audiodialog.cpp index 08cf41250..781ac4201 100644 --- a/sdrgui/gui/audiodialog.cpp +++ b/sdrgui/gui/audiodialog.cpp @@ -122,15 +122,13 @@ void AudioDialogX::on_audioInTree_currentItemChanged( QString inDeviceName = currentItem->text(1); int newIndex = ui->audioInTree->indexOfTopLevelItem(currentItem); int oldIndex = ui->audioInTree->indexOfTopLevelItem(previousItem); - //qDebug("AudioDialogX::on_audioInTree_currentItemChanged: %s", qPrintable(inDeviceName)); if (newIndex != oldIndex) { ui->inputResetKey->setChecked(false); } - bool found = m_audioDeviceManager->getInputDeviceInfo(inDeviceName, inDeviceInfo); + m_audioDeviceManager->getInputDeviceInfo(inDeviceName, inDeviceInfo); m_inputDeviceInfo = inDeviceInfo; - ui->inputDefaultText->setText(found ? "" : "D"); updateInputDisplay(); } @@ -148,10 +146,8 @@ void AudioDialogX::on_audioOutTree_currentItemChanged( ui->outputResetKey->setChecked(false); } - //qDebug("AudioDialogX::on_audioOutTree_currentItemChanged: %s", qPrintable(outDeviceName)); - bool found = m_audioDeviceManager->getOutputDeviceInfo(outDeviceName, outDeviceInfo); + m_audioDeviceManager->getOutputDeviceInfo(outDeviceName, outDeviceInfo); m_outputDeviceInfo = outDeviceInfo; - ui->outputDefaultText->setText(found ? "" : "D"); updateOutputDisplay(); } diff --git a/sdrgui/gui/audiodialog.ui b/sdrgui/gui/audiodialog.ui index 15803603c..ce6312a16 100644 --- a/sdrgui/gui/audiodialog.ui +++ b/sdrgui/gui/audiodialog.ui @@ -100,22 +100,6 @@ - - - - - 16 - 0 - - - - Default values indicator - - - D - - - @@ -429,22 +413,6 @@ - - - - - 16 - 0 - - - - Default values indicator - - - D - - - From 6c205fca6546fcaa8c20a32a342b37d8cfb4b508 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 11:22:05 +0200 Subject: [PATCH 218/956] Multiple audio: updated documentation --- doc/img/AudioDialog_input.png | Bin 0 -> 37356 bytes doc/img/AudioDialog_input.xcf | Bin 0 -> 126285 bytes doc/img/AudioDialog_output.png | Bin 0 -> 39310 bytes doc/img/AudioDialog_output.xcf | Bin 0 -> 140233 bytes doc/img/AudioDialog_select.png | Bin 0 -> 30921 bytes doc/img/AudioDialog_select.xcf | Bin 0 -> 85041 bytes doc/img/MainWindow_PreferencesAudioInput.png | Bin 22707 -> 0 bytes doc/img/MainWindow_PreferencesAudioOutput.png | Bin 18611 -> 0 bytes sdrgui/audio.md | 213 ++++++++++++++++++ sdrgui/readme.md | 18 +- 10 files changed, 215 insertions(+), 16 deletions(-) create mode 100644 doc/img/AudioDialog_input.png create mode 100644 doc/img/AudioDialog_input.xcf create mode 100644 doc/img/AudioDialog_output.png create mode 100644 doc/img/AudioDialog_output.xcf create mode 100644 doc/img/AudioDialog_select.png create mode 100644 doc/img/AudioDialog_select.xcf delete mode 100644 doc/img/MainWindow_PreferencesAudioInput.png delete mode 100644 doc/img/MainWindow_PreferencesAudioOutput.png create mode 100644 sdrgui/audio.md diff --git a/doc/img/AudioDialog_input.png b/doc/img/AudioDialog_input.png new file mode 100644 index 0000000000000000000000000000000000000000..dc67ae4eabdc8a3143be29153ab644c1be777206 GIT binary patch literal 37356 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 Q!(Vaqs=E)%zxNgLUm&fo7ytkO literal 0 HcmV?d00001 diff --git a/doc/img/AudioDialog_output.png b/doc/img/AudioDialog_output.png new file mode 100644 index 0000000000000000000000000000000000000000..e5067dfaee81b56ef5968e780cb4382dd2f9eaa0 GIT binary patch literal 39310 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�u!W%JWYU@#BhD7=dnKn9n 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
    `0ngd37TsFY+s`8>ub@k^i-<T5=(A-E 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 literal 0 HcmV?d00001 diff --git a/doc/img/AudioDialog_select.png b/doc/img/AudioDialog_select.png new file mode 100644 index 0000000000000000000000000000000000000000..9b702310296679581038ecfc5c3d351e1d7ed762 GIT binary patch literal 30921 zcmcG$1yok+x<87EjfD~d7NQ^^AW9<&C?OzHk}4g7w6uyzh%`t`Nw+jufV8wor!>+H z_czzt>zpyp-TR*Z9pm0TR_#ytzB%9dywCHi_j@fPb$#zHs$C=`Bzwhf2+NX?Y_=pJ z+0?aTJ6^dIz#@cyZND#ZT^Rqy|CX-hJK?s$LlM$99&FXaAm7da#j zWPA8Htj>NeJX{?uGVcAZsVpp4$B&$4wyBDSTE#{_`gPE@F z?7MC2)*CX44T);`3iFc0!pQyl?s+OKz|!XZQ=UR~R=Zf@k%Wr>*Rm;Q3|ZvuYH)FN zon$i6)YNQM`TJpXe3iK+(NTWe(`aptDv$SKf1FIjZ@-;;VwyM-?GEwqO`f!KBi`!t z2Rk{Tj=4np`(Cc)6)l~&i;n1&sup`P`&)}d3yX+M&D08d2L#-3bQCypPncC2V)UzTA@0Eg~A zk}ahBqBM#f$)W`&IM`GrCz4JrfN_gv8!nLuH+$=QrtJ7c7kMm zbw+x1ZEdfUoN95++-S?|*RSt=dc0|2E^ThE?7Bl(eQu3J@AWzfEmTVUBD z_-V4gqr!2&;EjRBlcC#pA30Cb?Mx={wgMkDL0UR3NP6nfAxZgo-m0Yc@3l@@49(f< z#~rfH(ygT>8EK4j_V6IHva-_B(eZlw_S5S_3={fvd6uE1g=Ec9Ype5fEDn?7<2O^( z3omeTI^3;PzSY_^elO8=zemGTUb?`>syRug)5aQOw3y7o>8+MS!>f`H3r#>U38lU>DC0u~eP-1B3tU$L@3 z46=T4s0ydI#sB>=yKW!iM;ON_3oXxx;kxXY3W6S#vRhq(tLb;KEncQv$vDg z3tgO@w{YmzY(0MBgwRs-L|>nCY;0`0y5Pnt$4fH0GlGJ2moHzoogKP0kvG|%>pe82 zH`0<)%MfONUxqFsBI5Mv)8S19X<1n%U%xv0tK058bt;mV_A;Z2mG+23U#a^s0egxp zc8&-Mharab@)0BtPYF}uPG#eY6YRCp@M|)y>*Hk;}dDRrk-?6Fmd9;;}y zD|Am)na4!ic6NV1yAm8k#@Do&cZu|wwyd6 zZ*VtXzI-7`&B%y!+E~wg(01hUmoHa8nsn{ElAw@8!J)IqAnVv*crrW_yVnFczF2P%a=@>+BKZkwtX_M<$sC7;t5G^x6Ez3lu%K0N?4ouPI`ZG zgJjU9q+ip8oFw@z1+h1SV)~s79kJkc2DL5H<@}bmE?FgduItuWS(2{7pSAB4vPXn^ zJx=YfPt0t5&1-IMu2~-?HrAHyfs%J|5ohU$jfzUO8dt_WJeqIS&3!a=(#FQ-O1bml zE{6pglHAV&OI*%Ot%AuYdtDK8}&wj_&R(bv%t#Y39>Wvk%n$E-4q&0zMY zTPQsvqi)!uV$(%>`sgqAl`C&X=4`#Zy?qD98siSt)YOm^tWH-ZDrb!(Gh1diU-xHG z)BBZqjwGdMnbFhJlLTv2A1|N4KBE{t7H^a>Ki-ysCB~ASJ%9c*8=Gg)Ws}W6BCfsU zO|IvWp!bSjZRjg z_1W(`U&T@T2Y<`R$qC!r^Id;)tOvh&r98TDusX;)B;>(%wEq5P0NRl*s@W7#E za$$V#R*E{mcAo9r$hk0GUH>lnTk;jj6_=Ryj_lgK`}M0=-MuBQBmqG|4Y*nHBngqW zG4%zS6BAk$KIh0z|H^Gab3c33Ueh9An{TaL ztl~_gtE+3!6fYtw`r=?_`!fIe2j?Vi-lRWu%6WCw9<4`4hliJ!mtJNk85sk{3+kjU z@_OZ(vA-f+w8JP3ua#S+KdX0OV0lXMdc5TC-d@>>Wx2mZ8d4tD0|JFWeH=AqQdD&N%~O=kvNC_WjdtKEX<^~5B_$=R3&k5H-@or@dJrOny)P*# zInI=BhHrX8 zUUhp%$Ie~5WR*`F85un^Gt28U6;D#=vJC1NEjsnB?_p6gqoAOmbHVEBs#4m0ku1Lx zLTY;rYnq0|9ZQZxFfoMZe~oS_rVBsYOex_p`Cj(L9?CC)9J)E^aF>nSk6*s*&tp0A zBk>N$^6ap4R#sLz9lAa(@W(z{zV8E-{^ixx(Z(HlKik^+Mn+telUNpZmd97MeSDyz zorisLOG)WfbToZ(aTM}$J_h`*B70usw4+UB}M$#*MS`m9XlpzX2vciCRQ+8 zFCl#WIujYaPfbhAO=?uw&fU9Dp-_$lrhuKwwW0e zf86!)L7Un2k{ky$#*FIt^kY{WPairXnbWtF$1@$5%uaiq(RkEx!E|7BG{M5aHvj8A zUH$te*B)4w6d#_oy&^xozhs4Kc9WA*o( znwDbKlAk{*3TdZzPj6f<8hAh^dG?8_qd=B{ujFl2)ha`pMll zdM{ms_=8*eNh;Zn_nS*@T>6W8{3jCi55M~tcf6(9*v@iubA0n}Rgx>U6*t?an8;eE ziE9EDk9f_q;}$6+XPh0iwd`%#Nv@j;-t$_V`V1_IoIO7SY_rBBwziGfK&v73>;)pZ>!k&L;fe;>(M1HC+!05+!_d#|TTn+pO9| zD)TD;aH*$xc3P90$G7_ygiam*^!?SF1HQ$IEWErbA06orA3366s0J`2zP9^>use5= zVFGip^7A|@bNlSnRD#b;Z@i!2bxl^w7x*EPs8K9%ltuMZMa2kPQvZ#2mmY|>Bx|$Y zxtt*pkuR}_s_b;)X#d+ljUb70dv|aT*F;}z+huO_YgYRB_>-2x{nAg%HYiRS#9QoZ z$ke~Bn#bcS*3ew}{uB4}lljzo%+t3viQ2?`xIKD{=_A`n5Vlh8O;*qAUrq@YZLF_M zN{?GP*gNNa+p(iTpJr3Ns9tnQZ!;QkJFL{gWH46M)jN8gtkvUIB%Ak z_Wg5VEBz6p@lRqaXr@b zfo+J>oJ;%#q5yAY3@0}|{a`!98gwn@9L4CTy#4ZR7H-Nqk)vL+#xe|`rctxDYg=lI zXXh@=jT|to*vaNeP_IVlJ$&Ax|RyB!p&CK5KH5d(L^UMOV)4RW^T3t zW&5Wx$2G{0xn^I?XRhFQdYHq4l=^-nJGDGBf8O+9gMa2&`8uOe$ElY0BD+Q1sYGT5 ztIx5p3{DSJCaPq=0Vg8JrB3y`ZI2Za1^`usg@scsM->R#5^Ywr*tp~)MRRg;!W)Kt^krpbDFhw)0PDd6>x-S7jl8Q=SZ|~ET6jp3yo%PkFg)CZh#nWfb2&CH{YtQPJupM$!wIzoX6fza1=e&?d zZQ$Z@@ydpx!+?5z#m{ULIt|AqdQf?tZ?6v6|M^VnMtxa|*p3G4b5qubOr5vyP0h^I z0?X#9-fyqj8}zf3GnJXArl#!YniLz8RQ#RRXTqvN`SZW5R_0s0Zx{x@jf#l@ zHw8*mT z{u*ZEzp~OtuMDuP%|2rBJfYTiLy|+cY_e94%VT|VG^L)8Re0ddEwd$gzEAv#1}h5V zC*>?N>HXC!18>ULOch#eG#O-RX=@*#q7pB5T)V)*af+3d3`hPjhk^iJ;aX+QcDlXsDOjh>F^FuVQg_%!s~&Mq$t@Njmb zBZ$DWd(SfZy6mOCbm9KD9cQFNC9#2gE3?8J<|#pR-DvsW0oR!?O%E6dIuVk{M2<2u z_&qH!#ohKCQ(}{O2(DA`^5*l__Guo%r$2K(*;6Nw8_;lwtlUD?aL;m%p~TWaenF7cAog=9rc5PCFu4sRWn2P zcx@eDoym^;%R)jzMx6zL6CL?88|z&gx0PpDG>WQ*8)ESZT3TBB4;50m{U|U#Jr3>uH zz`=1yMMVY355VlXSByde#GUK04W=N!1EIw}K8KY*8kHB=%4Em7kocP(m^v?@ILSGRk?MF zlfsrc4O4}yI^kX~m*ipwCcWd$-b~sSh8Ef_q-&KQq`Ij0346%Y%&d2;HKW2t7aJ+l zplKI&9^`$9v+&+9ky@yoj83@MWLN;awDaG&#Ok} z;6=H-SyL{{6s3C`QhaLTz$wumn6^OPY1!Ga5K)A$QwuC<8X36-22x5%NfoTk zw>^6FC>3xwUNO(}05mmR^N%b;MRd1~hhsn4-oJmJV=;WtpH)K#E7+KdL+W)s)a&3T@|lKRwwp;Ac8qPmgi(EyuL)gIfNjGiT0-d(YPjZd8G{>$j%UV|x+E z8x4W6!~4&q0Gm_FZb{lles66ADGNrLS$t?D0FlbVlkdl|ed}Yq;akYLSb&(An1bmFrix4~O+Y}vkMPeM6$x)< zTD-@m9>mRXwm&W^Dq3ha@bmY7iE^WJT4f_DmX|lEczxNmDM68`0s3T`VGE11<4DZS zpi*>~1l3NW^of=R!4Ye4hKnm8JlyME_N*;G(HPJdOS`)0Zr{Fr@X(<<#g2vS8%s04 z%WG<4?tOi6Owh5Qon3r}5)%#Ee%tZ$=iOet+Cd1x3Q5W(*id*V44@+ovC=bbCdK8K z37~EEoSNo>RsQRW6GwfYA90Z%GKsG%!&Wxlmb-A2$EfY2JGc2@>VvjR%_$nSnfeVw zorS#Ei{Kc2gM-_zU?ITe`YTzBzy16ffEKG!qhTHKzMa(D@G%Mh*dm2hRg$Wk;_Gmh zv_2&-zS4RCStvKAR#uMT33m+*D{61M!a=D`7rpl?cZ8bwZ7RGU ze_0UR!6Ufh1X*9raMQam0Au?i`TXH2^@%vUA01nL=A+iQtgm+u20sZhpCi3rFXFsk zx{SCi(S1wZZ&}N%caDVaiN5xLKY2+|kHsX@{%zK&E`bxFe>7ix_`FIx6uNRlvHIuY zFiq1?+`f_Y^z?pLW|$3pLWzlqp}#Es9Y?-7B*-Tm0gHoPqiC?a^U#@x=wwpRP&Ebs zkbE#K_%a6^Whx10ggaI-G)-4T}Pg! z%|eH@%~Z)zO!}1Og0Ek`=yk342=*MKrA?mQcJ|qRX;+MPPamIeKId-l5eU1ee~{=Y zEG%!{zTNNKm!6x;4S9tVA}w@74zvCn&Nk4-Y5>w-d3X@`2%>xIaR(Rxgg6@zKyl=} zk_@wEii8ZqM4)n#&f4Lo5ZD?pdHq<_Toe)<7@Lbxu>b*{etz@c5*r|Fh#r>q;$PrN`B?@L0Kfdk@(7zzlT#=JIjCCq4FBdCcKvUA{|LzH0 zHy|g63j*NF!a{zPY~z8|r5OzJl zr_s{UD4)?w_`MAct(od8gM0(&hLptG+B!HmSSjb>i92`h*l(;Y0KQI-B&2Na?(6#s z7vi;>TmMK?!g-CN;1+eeo0ncdyqCIv|0K4<`8(;))C+AX+kf1Vll%6DhIh|XcOmEP zrm$6j-~%`fkHAVng+RzzD&ANTBq8_+z#}a!Ev607?=^Tp0sMBCywBWxk2dk_#f#|< z>n=(+53o5wXm`T3q5Ml_g6p3lk`jI!mXQy39c-5O?Uoc=504872*6IC*k2De23VB39+6 zJf^iSE-ruO=g%-RyGskM`JgZ}?tR^U?AS4qp9MA?;45Fgf6!_G!SW%7>&zv?W;Zar z+mO1k-p>aLmIi~sqKLwkQB`%r_L`rc-_CrE=p*nSREz8bG0DvE_@K9cudb%&;`$k0 z@<##4R0rIG_;ZM(;9`kUxj8xAgM+0o%&x*hMJ*8T3pLLtND(ZN!21Bp5c~*+fZ06j zmF_9=PZr?H$hxB+?}ZC*0UQ`N{RGr)Epo66WZ%Ar;uGxEpE;(d@XPRM7|EW=>7c}g zZ^p>R1qeC`m=9J(eE9G|($?0N2c@-@l++wp165UGH(5v;zEz{OwY6dvJ)=VY1gs9{ z%;|cQ@vQf=COb#g%Y@^*dnCX;SWaAm-HZ8_0eon0Z_nkhd=cP19Qz)&-;Z={3CE>@ zqaWs_6Rjs&2e(v14ir?@|v^y1NMf88B zp4ho_XT^%jhSkG|S3r@RHXH{y;J8^>SmfHY{`$pkH*fSS+r$IB08m9W&tmvVvd`SMbMF@q?qGc!|DC%Cx-PhEfG6C|KhMS&%| zf;|FwT~=A?S{-z`0Sp2(i6k{OwHuF~L`hkh@rf!RUSOLe%qTHQNx6EsVF^*b>uT>( zpzU}{NJ$O8q;tFrcd|kkJ?!4lj|k#PhdD04E-3JdP{c$j#RvkgvKVc42Us#+{L^D* zHz_49ejh86s9w1ILr4!f3AVK3}akiefH4u{p_AL zG&WBD%x~4)+)OT@Wtu%bp_^;TL&Ix5q4gtN2;4p0q)PyDaAnd0{5J)PD!~sAPCnaD zc5PvzV`_F*F8K-}O9Neo*Xa=w0qDYyA2RnfHJ^AT97D$}>F5Xn2=@#LaZgBKBFH2O z6xs@p-xz=pHrPOF1qC(4Sl7~MXlUF;BYR@Q9A)d_Rh$&+VD6tDz1ED`(`h%!ubinT z1W_Eib8pwi`emo}rC9cAD5^tk*{ok*?7huWxcQNdO9( zGJw)NXCwoz1YLT7g@aIVl$DirAbnzu;A~%Cl9g3e?RlK0S;nYOOb9`zQ1oogd-tAr zdLCd_&J0(}w;CAK*4Hn`0RCQC`L(LbjSw2P52Ig1rp2FE`uh2af{@Vu zG$Y-?r_t2XC%%sggySc2! zC^l`{Q~+`9ir7(U*239?@aP~j^o@_Z`}&fnrKKG{dh`$l#dnlIYI^$nn?aZMP+fe5 zQrq%yr19Lrg9ify9C!#31yzAzd`>apqfzUZR|ih%)xF<_9kRHzl&0Tsru8gzT^{UD z6m$TJ=nxG}HOy_Xn>Pp0@;UWxEyF>8KYh!wngqxt5Ikl-LKx^#uto@%rJ#V1P^XBb zfQ!r3$hwE{8U@xDHIFbW3tL!RBqgJbLa!g1TV7Fr2{RbDrVQgk65^6#vg)mRJSFtD z7(l9H+-A>EDQQNn?3inc#_G_hw7$PRfo_t4r-u2<>O7lk{(BmlLMk#80!#ge;ELbw zF>_cN@F!3WFdd=pE3ObL_zq~Ix4T=W$BY=9_#Hwws0rn-#K`a!ct_iR>A=-jFHdlA zc!T#2$jiuFk&@bX*`!m>vEaqCXN2^2|Ni{|9!o|?N5_gOtIh(OsMuKZ>3%T)C8gB6 zPs~CahAq&DUSPEjQBsyKPW4UA&B;%9qL8qY5wxHo?ucM7P+=-XbD!^ebh73wSdR8` zWvVad&R0quw!y!Q+r!s0UCHuGTX*J70Jb9DiwM$Hj$vzAx|JK{p zMhy67W-EaO6~>U*%R^16mv5(-Y$eNfBHj0E>`uLtn5LEcAzbX z4j%jh-3uj<`+L=xE~Tcjva(yOCEkZyyW+5ym)FqGv_6{3kjo~tm43_#jqEHc9}|B) z%~Vvkd5&hs9)_Ta2IawVUq$s3=gz&3iaJeDP^4WJfn{K~SIqkP`5`26;K&i33z-VQ z?yub437lu0sEHeGibaeEf6RstbFO8$`HuM2SFWz7PM;PY-b5lU>a@5#n(`ViV0?R? zx=vNq27eW*kqAuWm4(SzRMhril32 zlL6eQ8=WBSS(_c-Dl2!xt3!&zr;de?WG;8ve}xyv<+#d)08rb?k=Qk7-jvz(E4{fGkc_iUV`F?FE ze+=yGUbxeA_|my2PYJ2bL4NFqO;2X@XQ{$(7;w0=&<@J&dq9vcC*mt^FOL%Na?dfY zw}36Z0N!XiJNNC=DRChO-a6~dwz9GUuqcTwN(rzE*8Xkz)X-m@KC+BkIbM1mpdOh1N^hIv+LDI?F14I z=C@M_6aYqqi%3F_vp{3V2V9(yr7t_|UR znUIk1`N~4Hs;cTkL`q<*!HmQvRm#xW3+cZbkH66Hjyv^bmIg@;D!EQExS%>ZI-q2X zVcH1a0AeoBkGtae@mh_Y&2FP4wx9%5UZ-_?!VvwLl?YK}DlwJt3(7>G`_J zr<;kFx2^k;`mbB5@FIkiZH7Tx#FdA;1_uY(*Auo{F4T5*u7_TaCKwIwpkf6NjpN>z zXNM9DskQf^qyCEi)YpBl6&AnZt0z&sLrztEJ)H>fMz7kvN>YoHK z9SR0`p|n@v>Ff$2f`S-CQ1E@(R5pDy?x+YdDG~z*uvr<;-uqO5*qbjfam&lg(Uhaw zXW2AMFJLI!&X0+qQK+k{6VfZ_%GoFWaCyyWGcq&fHs^wXv7ZtGR`Uu7_z=~Sq@pOs zw5GC|XXnU7kZ9!R*4995?~aC5jE##qMVFD~B2lK#=sALifPWj|BxkkmEa z=Xr*no&Z0wu?z?gRVAquk(U^hFF->0Q=jV_4Kzw;@ zIoU~r3>`#`ZC9Y=4b?~Q2DSoEjI7gx1VDf3(vh>2=5`b9xjFgy=0iVD5NeU7rM#^j zTzh#cQVg+&R#v3|D$pY73p3eH@bFN%i+%-9iHcIoHA8qs;=ouL@T zL@OfX15hVAu|R+YuqJkc#ws&k~`V>GS2sT|X*Q}CmzU#!dYwYk4kok)UHV0P~ zy>jL8^c9sykGTF%LC=j`1M`0ed8WV(NQfANs2hb4E($@lueG%vKuYDJxpT)D9|s?on7<5zy)F?N!RuyOoucf~X@X2jVdhZAyVDhgwqT5TzQBa++x8 zZe@GEzxPB%Rn=(@j@vdG{{=s`-o@vj)CmBB27{pGr`lR+N(FNOkn_r!ub}UE`TCY1 zgXyEdi!e(7o2F8p#W}n$RD(Wb2Koyjxe#p&Yble^1TDzJ<{LuDhCv?Wv;$rc&zHU4ZCs2zI`m09{`Mr zXQUA*0k%A+64Z1k2C{!y|BsBy2NZ8HgM+$-g@wuCcj57?RTEiPw4WQ2lBaoi-l1of zzM*k_{kok0xAn>+OKiJ}Ls#UP%@MA`j7N&1jMtABQ53}16ymg1KN{W1+NKXYy$eJZ zKR^iBM3^vK$l1pNs0Y&ecQj|PtlxkI=hgCjAml0J72(tQRH37t0^m4zJBGquK)?R9 zR4|v2s_HT1UAFi61_cGpfIuEPdh|L94YciI01?g@`<3O2+~xdam-O$NkiGS>QXv%> zhp;#AeSW$Pxz5)82OH%(Kmx%(-ebfis^)sZcA6Q_?rMI;{u1F>^w#)jr#w;^s!Uuw zJn!rDl(G!SVU3p6)g5?@&=osiD6BaNFp#0HVnKp90OF+gAset)%hx z1SVG%kQZ{1W1WSFBQ}T2#*r-kpW}zK*Tik@>`)V~=zNHxSPcDmf*oC13aNGkxEsdd z{|+ExpoL>NBIJs6Hk)a$u;bdC;u~823gF!*UN5g9sP$)N=G6J~lEbUn;F)N6mkD7L zddv*al%l=^CKe_F;t8P$&Jcsi+FBQtMA#!RgUtF$_d*&%q%@12B?%?>PWx}!EUoH> z5b`^iq<$E!=`pi3IJhufP8rq{s4#ieBVB z88|t86*U~l&r*u$qZo*gCqyJaRt@z*#y49ndqas=kenrCkQIZy))aJL?y{Vl^heg7JY4=Ed3wB5y^- z2b=@#5{62$IP*1A)3kajKQFH@;8&O%^e0Y4RAmCDKtf|P`gsA5@V}*`Kcgwdomq*f zbfW9RyV^uZh0we4WPqwxKaId95}vyiCs!3F7zQ^4kxscY{QUeOPz2zr;t#;c2<4B2 zezBsaMj@uQs!EP$*CmM1H<(!YnV2LnW!s==l{)!`Ppr4LwRO#WI1Dy;`s~>oHeJNd zkPMKCW`~XKDjt3E;>E*{$J?^OgEvYUUE&l7OA$tGsLJs|(^wdNL5YE zw{th~PfaID$vu!20s;d)!@^Rc{4bpW;RCQ(9F7h1$prDaA!EHeH#XS|@a)W)GcdFp zAOI_*s8^$`xoxKVPn~P1so4j7LMX@}-99ZaiElAWe~yWc?im|PEbwq~=>?fX5`!8h zyPKOEp*}#f%ug}H1`)yDB^-Cf6m>#ALa2n@c2*CS#lRc7|7zoL4CD9LJ!mVqf1?kA z5&c2bXv=20UhhaO@qtlW)b0=q`p5lbOL3DA$s`@bK+ z(*|qRta~rAPzaMmvIi4m&|6v{LcbvfyTJ^=4OS21-?t5+lu%hGckkNuy}bOEal)N! zV;byoqmDdsXx7kn&~k6TXbT4ECL-{#Dlx*-Ky9O&q+%P`AVA{H#I>|eqnvTthtY;O zOM&{!&@9^ruLjuuE`k#2w~Qa`*&O=n`W^=dhlIo5fPyON`7rl;cVbEZzd!^nRNY_6 z1TPLLEl2_1eTI}Ogw;VQF33+zLIk|>lKZdIT0j#ONL$?y7e9$8ITG+!uU@4DHm|OO%Q)PgW;=>Fdc+RVQ+ym0u`V$a?k(izTULN=sKJ}@+Js9Nf9XzBjU%yfU z_vZ=_ zOg!LRKvd8Ki?TrdpFYm^e>dM`O3y9K&zH8e97TV?z8?M&u?2X~&mB;zY4|T86FUX! ze*lz@>FG+=u7-h1*i?Sq-FJcWm5Lqt(FHhe(D6OG06K9T@h03Zrs7uk|D>sDY4;$} zh;LZHpJ0I5jqX4wJ@9&b>{0zn1&`7``(FTN!l0n^xeTWMU&GxE{Iz2HQH1Cs{zdei zJsnAqnc=pZKi|{SgQ#LU91o)h57ItacFz6h_d?IRIR$l%Z??b>> z7~j$c28={%Muy#`{@>eK3#=giu18tx!Q$iP7rTmuakj%L#`N!CpoEK%7*Odid}H zlS1Mrs4)wjcG8zp@leh5FH&Lgrxq4iy^kC`_!Md@1P_rUg}=f~w6szPYu|i#5xLi= z8M?KM4cCQ*yL)>#U%}euBH#|JvwfIZ<>OXN?f*84c9oq++EcFl@9`X8&j%xj2q7dY zrENyRp~G`O`V;Q8J{V+@u2W4#l%uIB+xq%CdM;CvWp*1>MT|CRz8Mg0A;jy~y?<_W zjG*UI|B0RtB8~=>C9Z$=?A{{KLf>J~6W=#atE;Q`)>lZ>R#eE=i*NXaPdG(Mc(2?w zG0_;fbq+o;%n0FxCdM$eJPWaPY_J0E8Zl8cGyD!26c&>@LUK#y4=YC5?@8ft|m8<$WobwJkJ~!=hO? z{yjN;JC8zZCJwtmiX=oachPHldLO2*Xkaq^Pun7ay%X{ZQ(avh3CLmh*q9qqI!jAS z;>%$0n{X0QG_vkbgS6l|eJIA|lHdOwciT*4m0 z_X7pJ3rLP^Q4q>hQ&+c)wRm;&C*V;Ew)AlU zsHY;i*f-_Cnw>`vA4Uy+MRW9hv6GaP6oQcpwmH~3T#<8MAJhb0_7HJBVhXh%xf4R| zuQ!No7$%Ay5Q`pd2`mv23Eu+bgT;U`TGKR1p#$PHNh(yz*0b3%V9}PB38!#vpj`|K ziZ4O7U%flrNW-sLF{vw=z+9Vb{M^En$tPTjuNjD?qCLo(0bSwD@#EdEm8@}66am>r z{N$Zg%?^VqHdhwo=dx);jAaokbc%~Br#UfOgQ4FdWmU+QYYFCozM z_V{?|R{!5^WXT-iD&>cso7u>}V({i$Q~rw8@x<@m5NSU}O-(%@#(1j;0B#Di`n#2b z%bxkFK5Y;CxW7*8+Jfx9AmBAMwFG82l)HjIUv|r~eL}B|-P0s{Xoktx+uOWFU>#k4 z5%`RtX(okpM}W=5-(iD5gww>}gAXlAX`bqXF$i3#g*B@R;XO)C9VGr+%@gNmE`63K2%AoQ@5qLK$S<);vqYEWUDk*3r3~jEoGslbg$5%(*TZt={xAPJg>4@X=ZP z`hVL3-|o6uz+*02Up9mkvxAIG7fF)sGkEV{D5eMK=xVWBhud>AgqCr@4D@6Vtc~+Z zsU%s-%;YRWYIpD)2{RRl40f{v-LmOS17(QUg zlx~HiqoWd_nZ&WeFsF5H!Yg<*k^B3xm(=@M>986y!PQUD>C3-=zl-RPZ%sZQ;&x;L zi;rPTqLP-Deuo8eo5!$^Rd-Y*-oS`~KG2I`-hx`}Lc01>szZw`f%eYNv@{ird{Hrv zK%I3Ee{`eaJpwfka2yQXz5d+#bvlP<2+Kg2L2CL9#EW{29IwaylWBF*|BJccncQ&V zpDe)NMuXfkbAM=_in7Gtsh{F{-Z>Cr+8cLL3g@YC1u+OFs^(kWq#VsE&_;iX1e%56 z3O`{H=!EUkr9pUzB~4A_h;_b?j6}@AgwS<0b0Lrt{2#3et)~j$WO1sD4sm!L2m?4Z z{0hK8Ja8VzXNf3`hK2^t(*z)y4CT=1SLSQNB}7hyaCwL*GqQ|u*%GEdiOJ8j z%VzKY1wdf~C8HIY2jt(0ZVDQ}mrnhykg-fq@b{YU2K3NstSi zm-C%7fRO-l@d-I&-~*sa$Vk5IqWwtR5bSKIJ(mMM1L#`D{gPe8aeN*V?FDUL@e-)< z!|=i}UI|GX?{NW4JKBG_!oC&}(I*JIKmgd%4err{vj@bT;S4ezVx{|!9xa1jA_7am zor?25s0z}Id%^8``}*!cVyxwx*`rzN_PMUk6UqyriXxw~&1VnwBAPjo^&+Gk41sT$ z5((o<9KF8B^76>1D;l>CSuk?{a3!t5Z(`jJ($enpnSHYSnUoGEGpo^rDcQAlAZc|H z5&_h*Avu(<1M-#u9a=e(~w-><=q%vk~@{>mw%Cx(x_9g366o%5ae9aoQ%PR_-oLf_! z9G&5}SBQf}^eavCCCsrH;K9)l>&{y`Ix&Qoy%#4T$oOa9f<(om-VLx6rE5w+l}T96 zA(V7L6o(zA zW|fCAI09_*j>j?{4Ayw-1*F67BqX4d%UW4U8`kRP;y-rn-P`2KTJ!;&CLT>J+D}Am zr4>`h$KO8|rDTZw0*`6$GvdyW;lo8$1{gz85oLM?lLUI=?3dlFI0tcOdVU@u5${ak zfW0{OiA+*m@%kJq09rXNX?1am2M8OjX%=y^5o{p^sB{&-D>;vAq>zakN);e7%|@RhKAeVt@&*0Pym`z?_CpAtD^-I5;wX4g^%|Y}>wl#81WemZM`)oquSnQ8U=PZ%9ZIR550p2m9rXRJvo@UZ(aRPYESGd`eQ zp_BLttc-Es%5h+`YiOl8^n$mqulA~%j7?5n9@U9~`W|HPN~G*^dydxTXEryaSIyM- zC{}2(4=1D)$>OFUza|4$7JYl5=r_k>GaUs)b}FMa5iUQFO!9YKb4*0`plbZ(^5%^K z=H_^4b7-v@;z4q`x2N&NDMTY^LaIQ5&&N6%9VGTTt&K3e`gEo9 z({pp2HIz9&v1K{oGFAt$UBezy&NYh$(qshS1i+BS3i-i@z>PqQ8yFi?9gB#6%6b<& z8|zZ@XdFpY+<$)W9E@Y;Sy#q$=i*dXJPI?*Jg~f|YShYY7;mhmYP%84xr6rXcKRId z9Q#ZWus-67+z(b`E31!n$|Hk=I&$YByNHJ8TKuZqi>*wL5Il=jC=jm|^GwR>tK6J~ zF!bhlpx%-

    d*Q&Tud8vteWEUbBMUfxmM>GRUu9NC)kay9|yc}eWv*4o6@HP0iql(TA5%%~*I&@u^dGPs`XK}%P##)ECh|dm zG&NjV*o+Myx(W*m-Ip)ndq8eT@9*sF#3$GHo=FVA+Ca*H2zDpy^0C71+zprpaC%s6$2IeC%m*AuW12Au`Du$Vp| zAB5#rn|G;&dWzi=j}BH^(~nz3Q|POl>rHsY(rJ2HCCuJGUM|jSO8ZP}4cRi&3{!F< z%Ml8S4@m5ZVWHz?qs8M+D0o4NBB!NQAId@=;{!y{Qf^(8FO;a@x{*L%-)Q&|6fa33 zlw0HKmtWB}h=a_p<7D(gU{SVC%++`!b$GBsCzp+uo;=c}5)1sIslCZHE=k z+;e{Hh^|OiN7hyj1%L+;9UOcNizWpWnyNNg-l@;Ja0fZTdgko*`)*ECGYH7UVZK?7 z>na%5(H=(TCld$0@2}iXMx#jUn{3Uv4$+8}JrUywSK}KQ8F^rgMct$L{@5XXv$)d_ zmd-{j(xo0qy!LA?n+dW2zZh+yaf)5S`V_ z=(-l}bYNt}ppg|#jc9Z9VhGeI-nj7!!I8Urc=3fN5fdkmITjv=`VULk3s{sDv5KZ- zH5K&UdI)^V$(5$&=C_ex1XQ6H10_8>UwOp~=$lqPS@n)Wf;@H~wk7^Dv$j@5!PS+g z6>7YLouh~ob^Sd(5sdQjAE4lgx_-kLI(+cp{bRCr{hrr%-3;>blI?d;PEweg?9u@3 zj#tb7$YV987yQGCeunzO-N!gZu**S=_$nS$5N4S?8Ws}o7D%Ae2s)}t^UwKURQC7x zYqj*~iv23E$)J7Iy{YNLt!r?tMp`psO@-V_?$;s0*oYj2jje4wqT6^M&KD4tKH$Ie zRwnYXK=Gh0hVZ3Ix9yIu&Ohz6I`tC#BeIOvP7=ldIE6O|*kMXa{T6l!WM0m~o9E`@ z`u_Q|5U%=pa_RY>R*0JtO@E?z!%?%1Q@=jyhwg~ksO6NEIabBqz5oGLxj0#ROB_&` zaELtL;5UC~9m8_=$2GDz0vy1M81v1eAJ;`b2S3Ybu{)5!8c;~(FiW-A1N$c?lIvI$&?o%Bq*NBK1mP0MvT zIUkgN6!c7P9-er}=;%XrfGgUUg0hfU1}+uHsu3F#dh)&ClLu&MlF;uAarOzu*lprr zPEMKx|IPyo=b!p^i0C#zRGc`5fwAdx72h`U0X<$mMO_WTxh$dv@E)@qR;+FYvPY}> z-+nk50?NclPcH%>&Y?WULX;~mQ_`1FUe?Sk9i8i1bW9A+k=!IMf(WE)YisL6Z?a-u z0f_l=Tbe)GLty!aPR1SNmYa~bVHN|8L<<;Zs1D}7jUcK>@+TEc0?1}D1iV4Mmza$T z3Vy^iMJz+^h^q*A$>ro;=F!z%$;<8d^}0q{U=U){5FWgwWFzpAo6a)mmOi2i%l+Qc zSp7^G|M@iuu1Kwe?##h`t;8au$XfvP-Fu*I|3J<#0o+6q*&GCEJ?jjhUvLxGb?wC- zSU_P2O2tA5t(~wrT4s=C0NKYDyD0Nv&(#nC2W4~#&gL1j%Jbzwdu06cKQ&9cAbB(b zLc2*NWTdBCKrpEe;Z?*PfaG8NsD|<1gl|=dJaVZrKH~A8y%GT zi|pvnRc1yz_@TMShld|0rX@sFVp^hSsPvA%7XMYdt4LUx;F>Y-8+%H3uCKkBzej1kO+FxswrgZVauZsZNS2gUXp5xKa z0SAuYSmLp+anFiD>~9`HUrTAg|l z2TGnNyQDFEQ?spUZZhMrI;8M$g~;iv`Yz#-+#yC4Jm(5^m*Oal2#{DWAXnp`mS>ZL zQWsOChdW9_Z5IU9t!jUpMHovKUvwW1lN$QEx4Nt>_Is{PH1_uEDdz>zvK~c}%j6K% z2#^E^3GJ^1F;cggO`E!PN_!M}B?B%9pP{5`#l$c}z6 z$8*C`Fq~%8wtW8_z(*mzDM)cuVs-yE6OT!*OGm5cQGFsOGN?^ zCwZ8_%rHY*046~^E^u?NIk21J2yMhlw0!0gIV6ZW!<0gW5iTTmn&CTHbbs1(GfbfH z$J#8ut85lh#3(8TXF3=UN)gR^{Ab!Vd?8Vt7rlgA=+?17Ycb=(IfW#({N@~X@SA3B zWgSy-6*5IdMFN}K&5zZg?l9w%QIi`km1DLLCF_Z@9~`E&8)IWuFIbHE$=cQ)n!vW! zP&r=rLgpp=(66inOfOE9-qjSrg?960FhC?WP9&j)$*4Ky^30pf-4jW4Wjv@nGB%sy zv?c-LLIz~52`3|NLhb~fWSLhzr%Ig7T88{0aSUr^4IO}By7)?==Jz75vpAkgO>?yv z;mTcm_Q(NL%Vis@IpECvuVSZStFfOE#0CuHxDGmA5BhM?W?IW(w(j~!M}D^249mQt zud_2obSy}_A-)|C+POcDuZ067oa5x2ORqdHrayY`QqUZ@{SZ{$it1{4$ms;j$kMC3 ziVT4^OUCE`fFF5zN4>X%t%!<(`q_jEjn`031JIl^4 zivW5f8mA@Bf*;|C72Xf=iul>uK?tSYZ&u*}5h? zvcrcCMWXTQ1EQ9$wZn2lHlCGe*}(oS$et5(F)}Czx}O}e@A9pZ@Etasur#xbJAR#A zsUKAH!naDqK`gWV9b)Z+6WuTnu3};}TqEPxYGHRZ&z-|jhTGs&1fIb5hQ@URn1TKy ziJxU54l&I_-x-F3NT?;~$cB4nrl;#6E*!?!kKu~dvpcMU*b=)rIAI!$cNVG;1le|B zA_-a$^05zG+})cX&$>@xoU0kQs=#bHeBeL?+(*cH=MWsS9BCx>V+?+tcwi7dy|hLx z@8fEIW$1bk!U+l>?}zS`;Kq^%fR@99t|B#HTc6W&i2uXIzhz{Uh+-vV9O{Nlz+4;v zYXn@HL&WQ-vUIo31vuhx^o1e7G0xh;2%N>vWMX6#C*}-n&v=C-gV|77^#d;POY}p{v&M53MiukztqL1p26N&o$=+pSaJf=QQA}co=&ep*BRbs`Q=!{wnZ@tS68H_6?zxco{iNL(Sm_Eg}FR z6dnRa{dW0n9fypOB9bq1STsV*A<7LThxjN6Za(kc#RE6S0|r>pNf{#T8=>KcRhGw< z5}+1)8F-TFGBpcDXX?GL_hb3Z2i=e!5I^QPRUlE1Gcq&+_Bk?idBpDkOOK(Cv1HS3 zBi0htcMcpG4?`B>YhYy}7s>)j9v{r2_k^G&ljDT8F#yB|JwXqLgn-L6BJ`2-AwPC3 zk+?Hxi~ka=kP5ergKA4`1L!ygEgwU1(nbAI&o=bC8*mp|h&U>`u>mCQW%OUPHKxAQ z8LYM?z7Md&vPI1nU1$yyA^`?WX#w)bS%7%YU%VK>@st*f-vr_iWG(+fFmZhdj5#b1 z#}a^i*RFbkg*z-;bXc`LSe|s+AgCRs!{Fx2d3bdAYAJPG7&E&Opb7ytX{_5%06G(B zA7N)etpcO}tGn+GYC7Hbj_a`?EACM&i=wWE-tJLE={AG}5eOyHt+@0W5Rjtix<>>w zGywtWAWfx6M^`}srAd*lKq#RaiW1uW{5)sooVl}q+?hM~edm4uVFr?rU!LFde9NbN z)2-jiZHQF}*I8FWR6!^}FUQ&=!7F&pWxlh_CC)Vq`t5 zd}lKDc^X8%qQmcxF>&|h23QsG#vBgkuHhx{ zkrZ0+Ze`hvRX?KHW#j0oWgj^hc{)nPyBN$9@|rXt9&ms(G_?dW=d<`ly~LypUn{#K2@Y83-J&zCP#eRfP9qP_Hupa;(7KS9$0|aEKiAkciztCaoaU z;7m>FBvXalzYR}JNW&|Z2*WvnbeV~CN02d0q=63sN*-`fsL8y!XHGsL3BYGTkZyxg z0@RouduD0jE74vdz2_tMP-qxcS6aGUy5{7|X*f<3^@amp1wViOT({a(!S3bEnh#-e zlT4H={5&R?jVvkCl6J})pm7<2Z-a{&Zdr(I+Z9T^M;6iIBNmCxydulU)USUJiKha< zfxj(+5LNrZ1|7v0cLE6Vu}5isC+h`a`@3key=y@RL3fkVlpnDkpgsuHwX(by+v{z6 zyAfrp+_N?&^s7vWMG0o-PUb7S3COBKD zV7l*G=m-(auCZ|xPKxeuzzE9os;Phz@i{pT@gq{(H1B&97Z(%A&8^m~PrOUiLAt4t zSteFM!JC_hJF|7ij)%z@uiOFJAkgWFKI_!z#1?I(cu<){&aCo?i13-iWrfAYX5(&1$ja)21Gnxj z&WHW_(p?uT2}|rKq8+`shz=VW=rqa@1=xw=mom>;l+1MP|8xrA?_=V{3JS(34RAAJ zV!h{#ygAh(Rqm)|3ekS0AwD4=Qqiv>qLqA)byL*Qea8ifc79+W3Gy8xV4(E+u(JGB z6(=I&^fcl41hC}PVovZ?FrkeV0^9unG-+Q(%VWUY{TDXkti-?tgBVdIv)>DDRAU0X zDJ?6*N~aOUFgnE0ONy7^ZIS3Nje{0uYm^*xr*F%80g0OX%wINmtWN=w{AO&)HP zL~Ji6m$;(v67U6tI*K$A;qUK{a2b!pZ-XmDrD1?l0J>>O&{pWXI=$`t(fiK6-lClb zTLbnE(aYt#j?gGfVCn!vKsJhkxk-cgo86dr8<|g-Lex+Y&}XmPb4`aRN)_EQ3AF(> zd*8EZX^N|B9?_|zyaX_-g|rb;QBhI8G-py0ZuGD=>kSe50F#OB-Fp;xr+~6&9uTgr zKmRO70c;S6X@?i+kz1d4Q*+__y4VAR-LvoSBH1;|Kxqj+72^%HP?n%z4{x`XQ}J4p z<-#N?Ei|boK)Xgkn#987NAxyCA``0f1%Ti=7lQSk;?R8RuirN#HJd8i6gqT3VwNhq}QE+Csp8ZCG9~ z>>`z~U-K*418mPHx-%5W@Tw52f(k1ics;J!t|N~ECA##S9Dilxfy;N060<|;f-Vh zpu=3u_|^oJO+*K4h!&mTq)?U<5Dz*BSdzm4dl0u5pzn71&*fyu_8K7~CpvevlP8Cv zBKiO%KJYU^a)ARzDw38u`{XD>Qyb`OayuU#O6w1vU@HQ*xS~t6`dS)r5kLdNAS!TU zhzkgw58ZnxlX(Hy9Ie9>{LT{Q_cQ~IeT3pC1Nj~L_78{!WC|`7*{W>JJV*0+C##1J z;EAX*p+z;#&mKK39I}>g7kqA_Sk*M`I2B?c$zuEV9mhfZ02M$!Fd(9ooNd-4$P*Ox z2mq-F@afL18f+Ha1_B!)lKVJ}nb;`?C@nAL8#4gi5WFYB(xGn@I{5i2iL~PY@f`sz z5D_WQ4}|0ie@eNBk`pOTAQ*u1Ilk7C(Vm?2>* zMytFI#cu!ObKA~Sl%X!W^ui(YWW{eao%wyNE2OP4{*nnI$xPd)VC~G z6+I3kcTIkafAZtl0`-+`O*di+biUT|j7)i0Id88{WX{YeH!XA~w^V4Jcc)k^?%U`f zb*QYNzenP$W`FAzwtbM!*H-g%>nE;~RO30$#(Sfgw+{4Zt0x=yldq_E=7cW{YA8w< zr;cTBY1*WoB&7ZEhG{cJ%r-r5$*8(>mRYKyDM56LS4?R-53tmKo>lHF`JQ!B7SqFEHEAMZ~vF3HHn%dhr%FO$khv}|L<4?&Z z8S$ySovbQ8#lVQ z4U*okOICN--@M9qr=gp@er9n0!IiL{QG1)vLVbfxBloLlqnm8|*IuRcnhgsbP<}JF zQn_(Um|1e(nCfjWi_LZ7B^@^&k&}nE#T0yE9PbVkV7o~&QD1D<)?k=W&c|uA2w1z1 zPjG@4vlAxP7Jr+{AkTZ5`8+mJl5RIrO3od*_UrYE_BK&9zYP~(%Fqruj4X32H~IRO zC2BRoE@XAowhWE-lojK&_@_F4(@q59Zl!Maj^+VN_v|53tT0uMCUNg<8A}!B? zEd93nR$4@;6|YASUjgs_l$)K>Zbw|rx!R%QfACO>b5r=oM9RLXj^wYieRU>v$)l$6 zg`(Z6FHbF*IVG~*x#K@<=sF#rT^GY7w{_)hFqbf)?qUy029~zDdAP+I8UL+e&U2xd zHo!9y=qT2p$(`Bb8+&5v*iBiR&baBbyQMbTNNJ6|zAdn~Wffiha+_+!ZE|&Gbt`Rf z)N;Y=jl;xgw_%-Mlu*la|!m`-Rqtr0aT(@N{|~ zy}_K*pYoiqzcW8H6wumov7>$W8lQlWho=h!&z*XnFuO3U)9KIMLvk$3^m;ti%O`Br z@v1=GNy#i}MM;;lC@`<77%SoN%_hcfQ`e926G7^>!J0yqqe1&KT=V`|u9M5i3s)eC zmMxR~qNSR(MnxF{Vux%+iaa!{zcfuaBpuQlXw0qMT0iF+)$Hlk*!F&#$t$|kP`aJ> zVEy1gt<&=)u@<(F-p}^?*b~w%Y5S5{)4!})F27#4PuoUd&8knrKQf+1+XXw*mb$Hd zmp>bGu2wQWHdrp_8?m(AICyGLVcsTD-aT;@lf^DsWD@Cx)}spr&4<`~XCLUNP!ia( zALPG!-4P7m*36XJNFuFgw5c@bKUc2VYs_RnIkt*KiqF2V{-9a=rSs=IO59~k_(`Ni zDJN)x@d&ad>81jH{KUqOqiL(5?Q6zM{B+jg_v6HK*5m7h{_AtTzwp04=lcu)^?&~D zpa1Ls{Og~8f3JUg&i5C7f3JT%=N}jFKf1_&T>1aS3;17HrT-6a)1R)=_dot0+9!W{ z0sjk|`Oi+1U*Lyc50*Ei(SIQhU3cI+jMU58L~?t~|4=64)3ptK;?@u2l2gnh`A-&U zh|RQRuQun-J~~Vs3_R@78+YQEshWE%wdIlU6|vtl&YR6vsHOEk#JTp1CJ{;QK(%~d z+QV1ZyhGPT6wf>J$ZhvnYFlmI!FUny1DDrWPD3wXtrZams6R4p$Ar$6NvzS8eipEi zQNnlCYJ$7>&XCOC7dme#@KyXkB6a=c*YwqXJM|k(#OQWCPFut~l_wqqO<2>qDbEe^ zpXiQFEy%AM52|g;e4lJF5)~04f_^_UQ*y(5J~XnxTcMaMuvvWTJ6Sqqo2s(MsC=^0 zRRkYK=M>A^$AmI;HEi3kHP_4Lb7PdVGD|%2v~6h9*{qtnYQtawk(rzz;r9k^PZJ}h zEYSjs0eM+3A)0m8?(<=gBjBvv)|FUnx;k>z*-UL^o0;@v;t9=k@7hs1A3k5<{S#(I zalPSYDLQRQQ6c3@#wXCfQco+@uYKyD8u8pPCN2ItgHI!sN)@9)T>{|Gbypj?A=ni{SVNKjMnvmdb;91qetEgV+MFY3|^4(pRR>!GU-S8h9N zOy-S@a>n@7_8LP=x4=_H=5g<*HLG8}v#cJmM>P8+;A|BtQsMKnvm!mDtxiq=I5$I z{X-~53xZ*sz>If8f~;NV?$d`jWl{hwAr=5x|XL|cQ| z)zy8-WlkjOh<#q22w8(!tTglcL`g-iWtpN@s&=Ktf{wc&pxIhYa9M>DrPF6XF|YyD z6I1$TfXreJNOa8LzDl#Q;sLvnr|XgoqIrl~>}{Dr(5~*JQWs$%Y0f>#g4W_vy&&ph z({u_Cvy$V^1qZg%v3IF3sD3W+*$)!n1{w39t3SjAPN4>6>#WI@n%N331{wJ!X^!M?Y|<^x7~BuYLZr~ z{Fyv?J9nT_iEg+qgL6hf%q}FuIXJ>%)}idocIt!E!nbb9Hg;*NNIqM98|Tviv)qO? zi7MU;5^1Nj%BJUbQmMt|^wADIQ`XOHLV63_9ID{(9XGMM1QP`n%wwP@1pcs$um2Jk z5m5)80ywrAg8{-GKFoxupZM@x%qswt2z{jt#y?*4#CI8mLD&oUs@6K6jWufnvq)%F zMu8l#fiA-BnFv*8k4}8{kaxj~gtJ7<$;0PKjxNQHqYCw2E&~kh>|I?#>huuP(MSVU zx~swCFpD4_7FS8!!1=yQGL@=zM*4uHkh~EDkIw%gzk?nnSDuyb(<^9KvUJCwZN+IL zFfy?(2cE~kfu#dc58y_8iGdfP?1NOUY6{{MF?o3-<_wJPkAcWBlYmgCgV6#g7z;m8 zD7?~eZBS@HP6)LzNVKz1yZ~AW0hWXzBK8l=s$Y^B4(`7++ogY;3`31s$YbP^#mt}x zyN}FBZZg^|$oh08y#AHEb5@+pk$sWK$pl zJ`_7NFz~je#Q=8)lgywKzVe(XF0e3}(al z-cU+5Z6>zU1NryIg0c;TavVd1-n;2kU8|!|Bn0*COrsv!MAh0EwMjGsaHETYB)V?$9?hyxe<(C}s!-wg;*%l`8C(Jy43x&;>K$Za= z{zqN9#5ustz;Fmf6qK0?o-=9W5lBj;IlHBmFInds6S6Ug^x%h1e_Q$wed0Dk8u8W( zy2=^@-V3aBx_akI#&i9}rPx@^mKRCp?u5v(6@1N=@j0jrATdYgZ5R zzSf*uf6=;YKrYL?C{yQCo^h6f)8YfmGrpQ;dpz{Ns!la9yeHe9nG%>IYhYj)kQSrsb)Eq+wJV9W~LY+)#M+oW&Y$hDB**Mf< zVC@JR2;_4r5*s95?|yxnD{}2jw44@p%?Hr&;-KOX5`bQ6!xSAoSRa=IBeEf;#}Fn1 zM7SB;yq=#H8e@K;WgQ){O_(|1TIpLsA6U>Kzr#7wmt+PA0S` zYk0TuE7~Fy-dYZV(f}y*-3s0bU;0BJ$N+HpJ(TfPgWlX3v7t=U^0-$wGR)iTO9thM z)c#GcX~SVwj9aLB?Jjw)@ul~N#D?yL&<+>9y_T)yV09rmxGCc&716dkfQ4>Oj}R$+ z%RwWpnvW9+cgLma40*%u9a%fJieLLF+~Iefwr_h5w{o}O%=nH6G#~#~$ULx`C<*;F zyRT8&SxTx4<0|_vTV6);G{KNj+n*G#deIFn4O84An#MCj5<@^P4UCUxh4R<5>;^4o z2%6B;bj%^rzJ0Hj!BV1rDs8^>HlGxcO(iy-e`#6tm*|{Mj{BR)mYiiHDHR=@Zv%43 z-vem^83sn}Mgk3ck2Q+C7xLkaJ}pdWI)IP1fTB5G)Q+i#dic7HkF(e)x=#`xC~ z3)A&Gs?!jD^<#^y17vijItiTeBmU1MAFKJx=6~Dy+h(FhZ#(e2=}^GI?@esllZ1MY zUnh#${I3n3jQVXSieyTSd|y(tR_Lo1evj8D{;K}XcrE_yxRtfri6(u0by~wNQPj2n z*6x09L60_Rxy7G^k^f@?(Ga^QE`R~a;U0h;=bBddYSN3|c6>L4Agsjk*$GgHd@-=7 zg8J)YCy7)@DQ|PwCQNCKKCcE;|6>%1^ynQn;`e6eXyuERod1`5>i>SP^rubztCiJH YG=3etx+S0=t3aZtojI9({M?QI1Y!QhM*si- literal 0 HcmV?d00001 diff --git a/doc/img/AudioDialog_select.xcf b/doc/img/AudioDialog_select.xcf new file mode 100644 index 0000000000000000000000000000000000000000..cae47271d759ec4f5b0ad10656316abcc5bad84a GIT binary patch literal 85041 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 literal 0 HcmV?d00001 diff --git a/doc/img/MainWindow_PreferencesAudioInput.png b/doc/img/MainWindow_PreferencesAudioInput.png deleted file mode 100644 index f29421e91cab13ff83e63941a0809f7742e49a37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22707 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 management + +Audio devices can be controlled with two dialogs: + - The Audio preferences dialog: set audio device parameters. The dialog shows one tab for output and another tab for input devices + - The Audio selection dialog: each channel plugin having audio output or input can show this dialog to select which device to use to direct output or input streams. This dialog opens by right clicking on a button that depends on the plugin (see plugin documentation). + +

    1. Audio output preferences

    + +This dialog lets you set preferences for the audio output devices attached to the system. These devices may refer to actual physical devices or be virtual devices defined with Pulseaudio (Linux). + +Each device is represented by a row in the list. Move the cursor with the mouse or the arrow keys to select the device you want to configure. + +![Audio output preferences](../doc/img/AudioDialog_output.png) + +

    1.1 Indicators column

    + +In this column there are two indicators: + + - `S`: for system default device. This is the device that is defined as system default. You may configure it directly or via the ` System default device` entry.
    ☞ Note that (at least in Linux) you may affect different parameters to one or the other. + - `D`: the device is unregistered so if you associate an output stream to it it will be registered with default values. Default values are: + - Sample rate: 48000 S/s + - UDP address: 127.0.0.1 + - UDP port: 9998 + - Copy audio to UDP: unchecked (false) + - UDP copy channel mode: mono left channel (Left) + - Use RTP protocol: unchecked (false) + +A unset indicator is marked with an underscore character: `_` + +

    1.2 Device name

    + +This is the device name defined in the system. In Linux when you define virtual devices (null sinks) with Pulseaudio this is the name you have given when defining the device. + +

    1.3 System default device

    + +The device that is configured as system default is marked with a grey background behind its name + +

    1.4 Selected device

    + +The device currently selected is marked in the selection color (orange). The parameters below are updated with its corresponding values + +

    1.5 Sample rate

    + +This is the device sample rate in samples per second (S/s). + +

    1.6 Reset values to defaults

    + +By pushing this button the values are reset to the defaults (see 1.1 for actual default values) + +

    1.7 UDP address

    + +This is the destination address of the UDP stream + +

    1.8 UDP port

    + +This is the destination port of the UDP stream + +

    1.9 Copy audio to UDP stream toggle

    + +Use this button to activate or de-activate the copy of the audio stream to UDP stream + +

    1.10 UDP copy channel mode

    + + - `Left`: UDP stream is mono (1 channel) and the left audio channel is copied + - `Right`: UDP stream is mono (1 channel) and the right audio channel is copied + - `Mixed`: UDP stream is mono (1 channel) and the mix of left and right audio channels is copied + - `Stereo`: UDP stream is stereo (2 channels) and audio channels are copied to their UDP channel counterparts respectively + +

    1.11 Use RTP protocol over UDP

    + +Check this box to activate the RTP protocol over UDP. RTP parameters are as follows: + + - Payload type: 96 + - Sample rate: the sample rate of the corresponding audio device + - Sample format: 16 bit integer signed (S16LE) + - Channels: 1 for mono (Left, Right and Mixed copy channels mode); 2 for stereo (Stereo copy channels mode) + - Address and port: destination address and port (local on the client machine) + +You may read the RTP stream using a SDP file (extension `.sdp`) that can be read with any program supporting SDP files (VLC, MX player, ffmpeg, ...). For a mono 48000 S/s stream at address `192.168.0.34:9998` the contents of the file would be as follows: + +``` +c=IN IP4 192.168.0.34 +m=audio 9998 RTP/AVP 96 +a=rtpmap:96 L16/48000/1 +``` + +☞ Note that on Android clients VLC has trouble working with the RTP stream (choppy audio, hanging unexpectedly...) therefore [MX player](https://play.google.com/store/apps/details?id=com.mxtech.videoplayer.ad&hl=en) is recommended. + +

    1.12 Cleanup registrations not in the list

    + +Use this button to keep only the visible devices in the devices registrations. The devices registrations with custom parameters are kept in the preferences using the device names. This button makes some tidying up when devices are permanently removed. + +

    1.14 Unregister device

    + +Use this button to remove the device from the devices registrations returning it to the unregistered state. Therefore when associated to an output stream or selected it will initially take default values and appear with the `D` induicator in the list. + +

    1.15 OK button

    + +Use this button to confirm your changes and close dialog. Note that you can change parameters of only one device at a time. + +

    1.16 Cancel button

    + +Use this button to dismiss your changes and close dialog. + + +

    2. Audio input preferences

    + +This dialog lets you set preferences for the audio input devices attached to the system. These devices may refer to actual physical devices or be virtual devices defined with Pulseaudio (Linux). + +Each device is represented by a row in the list. Move the cursor with the mouse or the arrow keys to select the device you want to configure. + +![Audio input preferences](../doc/img/AudioDialog_input.png) + +

    2.1 Indicators column

    + +In this column there are two indicators: + + - `S`: for system default device. This is the device that is defined as system default. You may configure it directly or via the ` System default device` entry.
    ☞ Note that (at least in Linux) you may affect different parameters to one or the other. + - `D`: the device is unregistered so if you associate an input stream to it it will be registered with default values. Default values are: + - Sample rate: 48000 S/s + - Volume: 0.15 + +A unset indicator is marked with an underscore character: `_` + +

    2.2 Device name

    + +This is the device name defined in the system. In Linux when you define virtual devices (null sinks) with Pulseaudio an input device is automatically created with the `.monitor` extension. + +

    2.3 System default device

    + +The device that is configured as system default is marked with a grey background behind its name + +

    2.4 Selected device

    + +The device currently selected is marked in the selection color (orange). The parameters below are updated with its corresponding values + +

    2.5 Sample rate

    + +This is the device sample rate in samples per second (S/s). + +

    2.6 Input volume

    + +This factor in the range [0.01 .. 1.00] is applied to the input before modulation. + +

    2.7 Reset values to defaults

    + +By pushing this button the values are reset to the defaults (see 2.1 for actual default values) + +

    2.8 Cleanup registrations not in the list

    + +Use this button to keep only the visible devices in the devices registrations. The devices registrations with custom parameters are kept in the preferences using the device names. This button makes some tidying up when devices are permanently removed. + +

    2.9 Unregister device

    + +Use this button to remove the device from the devices registrations returning it to the unregistered state. Therefore when associated to an output stream or selected it will initially take default values and appear with the `D` induicator in the list. + +

    2.10 OK button

    + +Use this button to confirm your changes and close dialog. Note that you can change parameters of only one device at a time. + +

    2.11 Cancel button

    + +Use this button to dismiss your changes and close dialog. + +

    3 Audio device selection

    + +In plugins having audio input or output this dialog can be opened to select the input or output device for the audio stream. The exact button that opens the dialog by right clicking on it depends on the plugin. Generally this will be the audio in selection (microphone icon) for input plugins and the audio mute (loudspeaker icon) for output plugins. You may check the plugin documentation for confirmation. + +The dialog for input or output is similar. The screenshot below is taken from an output selection. + +![Audio device selection](../doc/img/AudioDialog_select.png) + + +

    3.1 Indicators column

    + +In this column there are two indicators: + + - `S`: for system default device. This is the device that is defined as system default. You may configure it directly or via the ` System default device` entry.
    ☞ Note that (at least in Linux) you may affect different parameters to one or the other. + - `D`: the device is unregistered so if you associate an input stream to it it will be registered with default values. Default values depend on the input or output nature and are listed in the 2.1 and 1.1 sections respectively. + +A unset indicator is marked with an underscore character: `_` + +

    3.2 Device name

    + +This is the device name defined in the system. + +

    3.3 Device sample rate

    + +This is the device sample rate in samples per seconds. Please note that the baseband sample rate should not be lower than this rate for correct audio operation of AM, NFM and SSB plugins. + +☞ Using devices with sample rates lower than 48000 S/s may allow baseband sample rates lower than 48 kS/s with AM, NFM and SSB plugins. This is not true for wideband plugins (BFM, WFM) that use baseband rates in accordance with the RF bandwidth required nor for DSD plugin that uses internally a fixed channel rate of 48 kS/s to be able to decode symbols properly. + +☞ The DSD demodulator plugin accepts only audio sample rates of 48000 or 8000 S/s to process audio properly. + +

    3.4 System default device

    + +The device that is configured as system default is marked with a grey background behind its name + +

    3.5 Selected device

    + +The device currently selected is marked in the selection color (orange). Use the mouse or the arrow keys to change selection. + +

    3.6 OK button

    + +Use this button to confirm your selection and close dialog. + +

    3.7 Cancel button

    + +Use this button to dismiss your selection and close dialog. + + + + diff --git a/sdrgui/readme.md b/sdrgui/readme.md index b5d8126d8..c54181eeb 100644 --- a/sdrgui/readme.md +++ b/sdrgui/readme.md @@ -49,28 +49,14 @@ The following items are presented hierarchically from left to right: - _Audio_: opens a dialog to choose the audio output device (see 1.1 below for details) - _Logging_: opens a dialog to choose logging options (see 1.2 below for details) - _DV Serial_: if you have one or more AMBE3000 serial devices for AMBE digital voice check to connect them. If unchecked DV decoding will resort to mbelib if available else no audio will be produced for AMBE digital voice - - _My Position_: opens a dialog to enter your station ("My Position") coordinates in decimal degrees with north latitudes positive and east longitudes positive. This is used whenever positional data is to be displayed (APRS, DPRS, ...). For it now only works with D-Star $$CRC frames. See [DSD demod plugin](../plugins/channel/demoddsd/readme.md) for details on how to decode Digital Voice modes. + - _My Position_: opens a dialog to enter your station ("My Position") coordinates in decimal degrees with north latitudes positive and east longitudes positive. This is used whenever positional data is to be displayed (APRS, DPRS, ...). For it now only works with D-Star $$CRC frames. See [DSD demod plugin](../plugins/channelrx/demoddsd/readme.md) for details on how to decode Digital Voice modes. - Help: - _Loaded Plugins_: shows details about the loaded plugins (see 1.3 below for details) - _About_: current version and blah blah.

    1.1. Preferences - Audio

    -Audio output preferences: - -![Main Window audio output preferences](../doc/img/MainWindow_PreferencesAudioOutput.png) - -In the "Audio Output" tab of the audio preferences dialog you can choose which device is used for audio output. This choice is global for the application and is persistent. If the device is not available anymore at a later stage it reverts to the default devuce (first row). - -Audio input preferences: - -![Main Window audio output preferences](../doc/img/MainWindow_PreferencesAudioInput.png) - -In the "Audio Input" tab of the dialog you can choose the device used for audio input in a similar way as you do for the output device. - -In addition you can adjust the global volume using the dial knob at the bottom. Audio input behaves slightly differently than audio output and when the audio engine is started the volume is forced to a value that by default is maximum (1.0). This is not always desirable and using this control you can set it at a lower level (0.00 to 1.00 in 0.01 steps). - -Eventually select "OK" to confirm the settings or "Cancel" to dismiss without change. +See the audio management documentation [here](audio.md).

    1.2. Logging preferences

    From e0356a39a563e814803c706b7cdff9b412550269 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 11:51:57 +0200 Subject: [PATCH 219/956] Multiple audio: updated plugins documentation --- doc/img/DSDdemod_plugin.png | Bin 130329 -> 129488 bytes doc/img/DSDdemod_plugin.xcf | Bin 317822 -> 316778 bytes plugins/channelrx/demodam/readme.md | 2 +- plugins/channelrx/demoddsd/readme.md | 18 ++++-------------- plugins/channelrx/demodnfm/nfmdemodgui.ui | 2 +- plugins/channelrx/demodnfm/readme.md | 2 +- plugins/channelrx/demodssb/readme.md | 2 +- plugins/channeltx/modam/readme.md | 2 +- plugins/channeltx/modnfm/readme.md | 2 +- plugins/channeltx/modssb/readme.md | 6 ++++-- plugins/channeltx/modwfm/readme.md | 2 +- 11 files changed, 15 insertions(+), 23 deletions(-) diff --git a/doc/img/DSDdemod_plugin.png b/doc/img/DSDdemod_plugin.png index 603de11bdf70294a61a583ea0a506870fd561df0..0240aa518938ce8b6bac1b63050b4b7f313efe06 100644 GIT binary patch literal 129488 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
  • <%;=1?f8l;&@=WPlwQ@cf1 zMm2AYR#p>woIfVPf*JG2j?~uDTF~hrTmkqz-%6yr08L4Xe>Vi@_Z&Y>^{uI>U5Df%sc}!FMyCM;DQKH8ANmlyO41c9I zKfU1aJ75YyP?u~00%~b%yRPR|RJ!hG+I~=ktK#>*5`t}*p;PA;O7b`uL@?svYuKTb z2w?}FH-Mi|a=}la!Q7>w=p(hRZGr``S3CL;aPGo_VT$*aE0pZ#uslrRrTMx?#erY= z`S=DJyuG8MqNHRMxvr)6E@{Unz8)Ms&-TU;5@J7}Go#rZM|d{#o148&epRFiaCro0 zndAKKln?WTZ+nngseB16?5Ok@UX=5`GJbHZTw*%=opWjnEm+}5wet?nL9 zR&Tb2JtfMzTjA!mX}GqGi~jnpfg5gnr_2$h^5&}gM;~Y}>~=^*H|q8}IC<2(LQ4MI zX)M9@p67qz`Z={$5S88g`ds(xr|9^2X&W2%bjb+T7d~Vl!oPTN*SstNM0kOAk*^*Tq;h8+-eQ)YQJ0=f|XM9=#0k z&)xa<{Sh_YuY;y1t91L}81*B630f}09RGYR|W7Kmf-hCGBntBR!o9qzqjR#3F5jS*�B!Qv`c!$IG?=t{qJ@8=Y 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�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%0DRj5gt*ac1uitz|GYb*Pxl$oL| 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 a(DT!vU~6$WkB)``q!MiN>VE*Jku7-u diff --git a/doc/img/DSDdemod_plugin.xcf b/doc/img/DSDdemod_plugin.xcf index ba0db41aaa7f6e4ad95a18e94dc03678fc5b8f90..c605021e34c70b868904c772ac9649ae977c8a6b 100644 GIT binary patch delta 18152 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 delta 16770 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 N^IwaYe^KxKe*gm1ckTcH diff --git a/plugins/channelrx/demodam/readme.md b/plugins/channelrx/demodam/readme.md index 31e6f202c..fc09b4b69 100644 --- a/plugins/channelrx/demodam/readme.md +++ b/plugins/channelrx/demodam/readme.md @@ -20,7 +20,7 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. -If you right click on it it will open a dialog to select the audio output device. +If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details.

    5: Level meter in dB

    diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index bae5367bd..da4830eff 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -86,23 +86,13 @@ Number of milliseconds following squelch gate opening after which the signal is Use this switch to toggle high-pass filter on the audio -

    A.9: Audio mute and squelch indicator

    +

    A.9: Audio mute, squelch indicator and select audio output device

    -Audio mute toggle button. This button lights in green when the squelch opens. +Left click to mute/unmute audio. This button lights in green when the squelch opens. -

    A.10: UDP output

    +If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. -Copies audio output to UDP. Output is stereo S16LE samples. Depending on which slots are active the output is the following: - - - Slot 1: slot 1 on left channel - - Slot 2: slot 2 on right channel - - Slot 1+2: slot 1 on left channel - -It cannot mix both channels when slot1+2 are active. - -UDP address and send port are specified in the basic channel settings. See: [here](https://github.com/f4exb/sdrangel/blob/master/sdrgui/readme.md#6-channels) - -

    a.11: Format specific status display

    +

    A.11: Format specific status display

    When the display is active the background turns from the surrounding gray color to dark green. It shows informatory or status messages that are particular to each format. diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.ui b/plugins/channelrx/demodnfm/nfmdemodgui.ui index 379ec2059..f59f367d2 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.ui +++ b/plugins/channelrx/demodnfm/nfmdemodgui.ui @@ -574,7 +574,7 @@ - Mute/Unmute audio + Left: Mute/Unmute audio Right: Select audio output device ... diff --git a/plugins/channelrx/demodnfm/readme.md b/plugins/channelrx/demodnfm/readme.md index 38c63ba6d..3e689e073 100644 --- a/plugins/channelrx/demodnfm/readme.md +++ b/plugins/channelrx/demodnfm/readme.md @@ -62,4 +62,4 @@ This is the value of the tone squelch received when the CTCSS is activated. It d Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. -If you right click on it it will open a dialog to select the audio output device. \ No newline at end of file +If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. \ No newline at end of file diff --git a/plugins/channelrx/demodssb/readme.md b/plugins/channelrx/demodssb/readme.md index eaf6995d9..82a181092 100644 --- a/plugins/channelrx/demodssb/readme.md +++ b/plugins/channelrx/demodssb/readme.md @@ -148,7 +148,7 @@ When the power threshold is close to the noise floor a few milliseconds help in Left click on this button to toggle audio mute for this channel. -If you right click on it a dialog will open to select the audio output device. +If you right click on it a dialog will open to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details.

    14: Spectrum display

    diff --git a/plugins/channeltx/modam/readme.md b/plugins/channeltx/modam/readme.md index b401c0ec2..17ba6987f 100644 --- a/plugins/channeltx/modam/readme.md +++ b/plugins/channeltx/modam/readme.md @@ -60,7 +60,7 @@ Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps Left click to switch to the audio input. You must switch it off to make other inputs available. -Right click to select audio input device. +Right click to select audio input device. See [audio management documentation](../../../sdrgui/audio.md) for details.

    10: CW (Morse) text

    diff --git a/plugins/channeltx/modnfm/readme.md b/plugins/channeltx/modnfm/readme.md index 6ea86686e..83e1b470b 100644 --- a/plugins/channeltx/modnfm/readme.md +++ b/plugins/channeltx/modnfm/readme.md @@ -64,7 +64,7 @@ Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps Left click to switch to the audio input. You must switch it off to make other inputs available. -Right click to select audio input device. +Right click to select audio input device. See [audio management documentation](../../../sdrgui/audio.md) for details.

    11: CTCSS switch

    diff --git a/plugins/channeltx/modssb/readme.md b/plugins/channeltx/modssb/readme.md index d1f246133..ba4ada59e 100644 --- a/plugins/channeltx/modssb/readme.md +++ b/plugins/channeltx/modssb/readme.md @@ -163,9 +163,11 @@ Switches to the Morse keyer input. You must switch it off to make other inputs a Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps -

    13.4: Audio input select

    +

    13.4: Audio input select and select audio input device

    -Switches to the audio input. You must switch it off to make other inputs available. +Left click to switch to the audio input. You must switch it off to make other inputs available. + +Right click to select audio input device. See [audio management documentation](../../../sdrgui/audio.md) for details.

    14: CW (Morse) text

    diff --git a/plugins/channeltx/modwfm/readme.md b/plugins/channeltx/modwfm/readme.md index 51b6a2cb2..5dcf946af 100644 --- a/plugins/channeltx/modwfm/readme.md +++ b/plugins/channeltx/modwfm/readme.md @@ -64,7 +64,7 @@ Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps Left click to switch to the audio input. You must switch it off to make other inputs available. -Right click to select audio input device. +Right click to select audio input device. See [audio management documentation](../../../sdrgui/audio.md) for details.

    11: CW (Morse) text

    From 53beb8a92d54ec606c536ac02e943c8271b60afb Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 19:06:27 +0200 Subject: [PATCH 220/956] Removed UDP address and port from Channel marker --- plugins/channelrx/demodam/amdemodgui.cpp | 2 - plugins/channelrx/demodbfm/bfmdemodgui.cpp | 2 - plugins/channelrx/demoddsd/dsddemodgui.cpp | 4 -- plugins/channelrx/demodnfm/nfmdemodgui.cpp | 2 - plugins/channelrx/demodssb/ssbdemodgui.cpp | 2 - plugins/channelrx/demodwfm/wfmdemodgui.cpp | 2 - plugins/channelrx/udpsrc/udpsrcgui.cpp | 14 ---- plugins/channelrx/udpsrc/udpsrcgui.h | 1 - plugins/channeltx/modam/ammodgui.cpp | 2 - plugins/channeltx/modatv/atvmodgui.cpp | 2 - plugins/channeltx/modnfm/nfmmodgui.cpp | 2 - plugins/channeltx/modssb/ssbmodgui.cpp | 4 -- plugins/channeltx/modwfm/wfmmodgui.cpp | 2 - plugins/channeltx/udpsink/udpsinkgui.cpp | 4 -- sdrbase/dsp/channelmarker.cpp | 37 ---------- sdrbase/dsp/channelmarker.h | 12 ---- sdrgui/gui/basicchannelsettingsdialog.cpp | 23 ------ sdrgui/gui/basicchannelsettingsdialog.ui | 83 ---------------------- 18 files changed, 200 deletions(-) diff --git a/plugins/channelrx/demodam/amdemodgui.cpp b/plugins/channelrx/demodam/amdemodgui.cpp index beb3584e8..d9488350d 100644 --- a/plugins/channelrx/demodam/amdemodgui.cpp +++ b/plugins/channelrx/demodam/amdemodgui.cpp @@ -230,8 +230,6 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS m_channelMarker.setBandwidth(5000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("AM Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index 395682b4f..6707b01dd 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -357,8 +357,6 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(12500); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("Broadcast FM Demod"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 4b282a557..b8c5e43ae 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -256,8 +256,6 @@ void DSDDemodGUI::onMenuDialogCalled(const QPoint &p) dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); @@ -328,8 +326,6 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(10000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("DSD Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.cpp b/plugins/channelrx/demodnfm/nfmdemodgui.cpp index ed0dc3a6e..753a222da 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodgui.cpp @@ -293,8 +293,6 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(5000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("NFM Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channelrx/demodssb/ssbdemodgui.cpp b/plugins/channelrx/demodssb/ssbdemodgui.cpp index c5b9cf720..9a8ad9c3a 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.cpp +++ b/plugins/channelrx/demodssb/ssbdemodgui.cpp @@ -287,8 +287,6 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(6000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("SSB Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.cpp b/plugins/channelrx/demodwfm/wfmdemodgui.cpp index 7c13a613a..6f78a3ad9 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.cpp +++ b/plugins/channelrx/demodwfm/wfmdemodgui.cpp @@ -190,8 +190,6 @@ WFMDemodGUI::WFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(WFMDemodSettings::getRFBW(4)); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("WFM Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.setColor(m_settings.m_rgbColor); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsrc/udpsrcgui.cpp index d2f40764a..518b6cb17 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsrc/udpsrcgui.cpp @@ -171,9 +171,6 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setBandwidth(16000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("UDP Sample Source"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); - m_channelMarker.setUDPReceivePort(9998); m_channelMarker.setColor(m_settings.m_rgbColor); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -221,7 +218,6 @@ void UDPSrcGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); - displayUDPAddress(); ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); @@ -249,13 +245,6 @@ void UDPSrcGUI::displaySettings() ui->gainText->setText(tr("%1").arg(ui->gain->value()/10.0, 0, 'f', 1)); ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); - - displayUDPAddress(); -} - -void UDPSrcGUI::displayUDPAddress() -{ - ui->addressText->setText(tr("%1:%2/%3").arg(m_channelMarker.getUDPAddress()).arg(m_channelMarker.getUDPSendPort()).arg(m_channelMarker.getUDPReceivePort())); } void UDPSrcGUI::setSampleFormatIndex(const UDPSrcSettings::SampleFormat& sampleFormat) @@ -552,14 +541,11 @@ void UDPSrcGUI::onMenuDialogCalled(const QPoint &p) m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); - displayUDPAddress(); applySettingsImmediate(); } diff --git a/plugins/channelrx/udpsrc/udpsrcgui.h b/plugins/channelrx/udpsrc/udpsrcgui.h index abeac01d6..0ae8fec13 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.h +++ b/plugins/channelrx/udpsrc/udpsrcgui.h @@ -85,7 +85,6 @@ private: void applySettings(bool force = false); void applySettingsImmediate(bool force = false); void displaySettings(); - void displayUDPAddress(); void setSampleFormat(int index); void setSampleFormatIndex(const UDPSrcSettings::SampleFormat& sampleFormat); diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index 2ee194a90..6d8316f96 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -296,8 +296,6 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampl m_channelMarker.setBandwidth(5000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("AM Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index 61329486c..165f1d63a 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -616,8 +616,6 @@ ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setBandwidth(5000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("ATV Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channeltx/modnfm/nfmmodgui.cpp b/plugins/channeltx/modnfm/nfmmodgui.cpp index 133adf2c7..7f48c49ac 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.cpp +++ b/plugins/channeltx/modnfm/nfmmodgui.cpp @@ -334,8 +334,6 @@ NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setBandwidth(12500); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("NFM Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index 6f2819034..847c77cd1 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -133,8 +133,6 @@ void SSBModGUI::channelMarkerChangedByCursor() void SSBModGUI::channelMarkerUpdate() { m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(); - m_settings.m_udpPort = m_channelMarker.getUDPReceivePort(); displaySettings(); applySettings(); } @@ -401,8 +399,6 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setSidebands(ChannelMarker::usb); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("SSB Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); diff --git a/plugins/channeltx/modwfm/wfmmodgui.cpp b/plugins/channeltx/modwfm/wfmmodgui.cpp index aaf6d9ac6..c9a11c1c9 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.cpp +++ b/plugins/channeltx/modwfm/wfmmodgui.cpp @@ -313,8 +313,6 @@ WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setBandwidth(125000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("WFM Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index f4741295b..dc8f8ab49 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -194,8 +194,6 @@ void UDPSinkGUI::displaySettings() m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); - m_channelMarker.setUDPAddress(m_settings.m_udpAddress); - m_channelMarker.setUDPReceivePort(m_settings.m_udpPort); // activate signal on the last setting only m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); @@ -429,8 +427,6 @@ void UDPSinkGUI::onMenuDialogCalled(const QPoint &p) dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPReceivePort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); setWindowTitle(m_channelMarker.getTitle()); diff --git a/sdrbase/dsp/channelmarker.cpp b/sdrbase/dsp/channelmarker.cpp index 4900dcdaa..1b242caf2 100644 --- a/sdrbase/dsp/channelmarker.cpp +++ b/sdrbase/dsp/channelmarker.cpp @@ -37,14 +37,11 @@ ChannelMarker::ChannelMarker(QObject* parent) : m_highlighted(false), m_color(m_colorTable[m_nextColor]), m_movable(true), - m_udpReceivePort(9999), - m_udpSendPort(9998), m_fScaleDisplayType(FScaleDisplay_freq) { ++m_nextColor; if(m_colorTable[m_nextColor] == 0) m_nextColor = 0; - setUDPAddress("127.0.0.1"); } void ChannelMarker::emitChangedByAPI() @@ -118,36 +115,11 @@ void ChannelMarker::setColor(const QColor& color) emit changedByAPI(); } -void ChannelMarker::setUDPAddress(const QString& udpAddress) -{ - m_udpAddress = udpAddress; - m_displayAddressReceive = QString(tr("%1:%2").arg(getUDPAddress()).arg(getUDPSendPort())); - m_displayAddressReceive = QString(tr("%1:%2").arg(getUDPAddress()).arg(getUDPReceivePort())); - emit changedByAPI(); -} - -void ChannelMarker::setUDPReceivePort(quint16 port) -{ - m_udpReceivePort = port; - m_displayAddressReceive = QString(tr("%1:%2").arg(getUDPAddress()).arg(getUDPReceivePort())); - emit changedByAPI(); -} - -void ChannelMarker::setUDPSendPort(quint16 port) -{ - m_udpSendPort = port; - m_displayAddressSend = QString(tr("%1:%2").arg(getUDPAddress()).arg(getUDPSendPort())); - emit changedByAPI(); -} - void ChannelMarker::resetToDefaults() { setTitle("Channel"); setCenterFrequency(0); setColor(Qt::white); - setUDPAddress("127.0.0.1"); - setUDPSendPort(9999); - setUDPReceivePort(9999); setFrequencyScaleDisplayType(FScaleDisplay_freq); } @@ -157,9 +129,6 @@ QByteArray ChannelMarker::serialize() const s.writeS32(1, getCenterFrequency()); s.writeU32(2, getColor().rgb()); s.writeString(3, getTitle()); - s.writeString(4, getUDPAddress()); - s.writeU32(5, (quint32) getUDPReceivePort()); - s.writeU32(6, (quint32) getUDPSendPort()); s.writeS32(7, (int) getFrequencyScaleDisplayType()); return s.final(); } @@ -189,12 +158,6 @@ bool ChannelMarker::deserialize(const QByteArray& data) } d.readString(3, &strtmp, "Channel"); setTitle(strtmp); - d.readString(4, &strtmp, "127.0.0.1"); - setUDPAddress(strtmp); - d.readU32(5, &u32tmp, 9999); - setUDPReceivePort(u32tmp); - d.readU32(6, &u32tmp, 9999); - setUDPSendPort(u32tmp); d.readS32(7, &tmp, 0); if ((tmp >= 0) && (tmp < FScaleDisplay_none)) { setFrequencyScaleDisplayType((frequencyScaleDisplay_t) tmp); diff --git a/sdrbase/dsp/channelmarker.h b/sdrbase/dsp/channelmarker.h index a2981a01e..125a4cf58 100644 --- a/sdrbase/dsp/channelmarker.h +++ b/sdrbase/dsp/channelmarker.h @@ -66,15 +66,6 @@ public: void setMovable(bool movable) { m_movable = movable; } bool getMovable() const { return m_movable; } - void setUDPAddress(const QString& udpAddress); - const QString& getUDPAddress() const { return m_udpAddress; } - - void setUDPReceivePort(quint16 port); - quint16 getUDPReceivePort() const { return m_udpReceivePort; } - - void setUDPSendPort(quint16 port); - quint16 getUDPSendPort() const { return m_udpSendPort; } - void setFrequencyScaleDisplayType(frequencyScaleDisplay_t type) { m_fScaleDisplayType = type; } frequencyScaleDisplay_t getFrequencyScaleDisplayType() const { return m_fScaleDisplayType; } @@ -100,9 +91,6 @@ protected: bool m_highlighted; QColor m_color; bool m_movable; - QString m_udpAddress; - quint16 m_udpReceivePort; - quint16 m_udpSendPort; frequencyScaleDisplay_t m_fScaleDisplayType; void resetToDefaults(); diff --git a/sdrgui/gui/basicchannelsettingsdialog.cpp b/sdrgui/gui/basicchannelsettingsdialog.cpp index 9069cfa64..313cf3dc2 100644 --- a/sdrgui/gui/basicchannelsettingsdialog.cpp +++ b/sdrgui/gui/basicchannelsettingsdialog.cpp @@ -14,9 +14,6 @@ BasicChannelSettingsDialog::BasicChannelSettingsDialog(ChannelMarker* marker, QW ui->setupUi(this); ui->title->setText(m_channelMarker->getTitle()); m_color = m_channelMarker->getColor(); - ui->udpAddress->setText(m_channelMarker->getUDPAddress()); - ui->udpPortReceive->setText(QString("%1").arg(m_channelMarker->getUDPReceivePort())); - ui->udpPortSend->setText(QString("%1").arg(m_channelMarker->getUDPSendPort())); ui->fScaleDisplayType->setCurrentIndex((int) m_channelMarker->getFrequencyScaleDisplayType()); paintColor(); } @@ -56,28 +53,8 @@ void BasicChannelSettingsDialog::accept() m_channelMarker->setColor(m_color); } - m_channelMarker->setUDPAddress(ui->udpAddress->text()); - - bool ok; - int udpPort = ui->udpPortReceive->text().toInt(&ok); - - if((!ok) || (udpPort < 1024) || (udpPort > 65535)) - { - udpPort = 9999; - } - - m_channelMarker->setUDPReceivePort(udpPort); - - udpPort = ui->udpPortSend->text().toInt(&ok); - - if((!ok) || (udpPort < 1024) || (udpPort > 65535)) - { - udpPort = 9999; - } - m_channelMarker->setFrequencyScaleDisplayType((ChannelMarker::frequencyScaleDisplay_t) ui->fScaleDisplayType->currentIndex()); m_channelMarker->blockSignals(false); - m_channelMarker->setUDPSendPort(udpPort); // activate signal on the last setting only m_hasChanged = true; QDialog::accept(); diff --git a/sdrgui/gui/basicchannelsettingsdialog.ui b/sdrgui/gui/basicchannelsettingsdialog.ui index dba382185..d2ccc0881 100644 --- a/sdrgui/gui/basicchannelsettingsdialog.ui +++ b/sdrgui/gui/basicchannelsettingsdialog.ui @@ -89,16 +89,6 @@ Title
    - - - AdSnd - - - - - AdRcv - - @@ -116,79 +106,6 @@ - - - - - - Addr - - - - - - - UDP address - - - 000.000.000.000 - - - 127.0.0.1 - - - - - - - Recv - - - - - - - - 60 - 16777215 - - - - UDP port - - - 00000 - - - 9999 - - - - - - - Send - - - - - - - - 60 - 16777215 - - - - 00000 - - - 9998 - - - - - From 7e37d91a499bb07de72a841a6af99d11137ecaf5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 19:20:34 +0200 Subject: [PATCH 221/956] Updated Debian changelog --- debian/changelog | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 1aa85ad37..3bcfee61b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,11 @@ sdrangel (3.14.0-1) unstable; urgency=medium * New audio devices management + * DATV demod: fixed message handling and thus screen initialization issue + * Removed UDP/RTP copy audio from channel sink plugins entirely + * Removed UDP address and port from Channel marker - -- Edouard Griffiths, F4EXB Sun, 1 Apr 2018 06:14:18 +0100 + -- Edouard Griffiths, F4EXB Fri, 30 Mar 2018 16:14:18 +0200 sdrangel (3.13.1-1) unstable; urgency=medium From c1f350c7e34ce7f789b7935c34cbdcd32ddeaff0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 19:41:41 +0200 Subject: [PATCH 222/956] Fixed roundf in audio dialog --- sdrgui/gui/audiodialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/gui/audiodialog.cpp b/sdrgui/gui/audiodialog.cpp index 781ac4201..b96072ace 100644 --- a/sdrgui/gui/audiodialog.cpp +++ b/sdrgui/gui/audiodialog.cpp @@ -172,7 +172,7 @@ void AudioDialogX::on_inputCleanup_clicked(bool checked __attribute__((unused))) void AudioDialogX::updateInputDisplay() { ui->inputSampleRate->setValue(m_inputDeviceInfo.sampleRate); - ui->inputVolume->setValue(roundf(m_inputDeviceInfo.volume * 100.0f)); + ui->inputVolume->setValue(round(m_inputDeviceInfo.volume * 100.0f)); ui->inputVolumeText->setText(QString("%1").arg(m_inputDeviceInfo.volume, 0, 'f', 2)); } From 1446ddb4175a617b7d621886d6215e6fcdc8c20d Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 30 Mar 2018 17:51:31 +0000 Subject: [PATCH 223/956] Fixed roundf in audio dialog (2) --- sdrgui/gui/audiodialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/sdrgui/gui/audiodialog.cpp b/sdrgui/gui/audiodialog.cpp index b96072ace..fe491f7e6 100644 --- a/sdrgui/gui/audiodialog.cpp +++ b/sdrgui/gui/audiodialog.cpp @@ -3,6 +3,7 @@ #include
  • - Generated 2018-03-29T15:31:47.724+02:00 + Generated 2018-03-31T18:20:57.874+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/BladeRF.yaml b/sdrbase/resources/webapi/doc/swagger/include/BladeRF.yaml new file mode 100644 index 000000000..8f75f9460 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/BladeRF.yaml @@ -0,0 +1,54 @@ +BladeRFInputSettings: + description: BladeRF + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + lnaGain: + type: integer + vga1: + type: integer + vga2: + type: integer + bandwidth: + type: integer + log2Decim: + type: integer + fcPos: + type: integer + xb200: + type: integer + xb200Path: + type: integer + xb200Filter: + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + +BladeRFOutputSettings: + description: BladeRF + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + vga1: + type: integer + vga2: + type: integer + bandwidth: + type: integer + log2Interp: + type: integer + xb200: + type: integer + xb200Path: + type: integer + xb200Filter: + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 052ee876b..d5380e57e 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1715,6 +1715,10 @@ definitions: type: integer airspyHFSettings: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFSettings" + bladeRFInputSettings: + $ref: "/doc/swagger/include/BladeRF.yaml#/BladeRFInputSettings" + bladeRFOutputSettings: + $ref: "/doc/swagger/include/BladeRF.yaml#/BladeRFOutputSettings" fileSourceSettings: $ref: "/doc/swagger/include/FileSource.yaml#/FileSourceSettings" hackRFInputSettings: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 8423b1a83..7c60fc521 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1696,6 +1696,36 @@ bool WebAPIRequestMapper::validateDeviceSettings( return false; } } + else if ((*deviceHwType == "BladeRF") && (deviceSettings.getTx() == 0)) + { + if (jsonObject.contains("bladeRFInputSettings") && jsonObject["bladeRFInputSettings"].isObject()) + { + QJsonObject bladeRFInputSettingsJsonObject = jsonObject["bladeRFInputSettings"].toObject(); + deviceSettingsKeys = bladeRFInputSettingsJsonObject.keys(); + deviceSettings.setBladeRfInputSettings(new SWGSDRangel::SWGBladeRFInputSettings()); + deviceSettings.getBladeRfInputSettings()->fromJsonObject(bladeRFInputSettingsJsonObject); + return true; + } + else + { + return false; + } + } + else if ((*deviceHwType == "BladeRF") && (deviceSettings.getTx() != 0)) + { + if (jsonObject.contains("bladeRFOutputSettings") && jsonObject["bladeRFOutputSettings"].isObject()) + { + QJsonObject bladeRFOutputSettingsJsonObject = jsonObject["bladeRFOutputSettings"].toObject(); + deviceSettingsKeys = bladeRFOutputSettingsJsonObject.keys(); + deviceSettings.setBladeRfOutputSettings(new SWGSDRangel::SWGBladeRFOutputSettings()); + deviceSettings.getBladeRfOutputSettings()->fromJsonObject(bladeRFOutputSettingsJsonObject); + return true; + } + else + { + return false; + } + } else if ((*deviceHwType == "HackRF") && (deviceSettings.getTx() == 0)) { if (jsonObject.contains("hackRFInputSettings") && jsonObject["hackRFInputSettings"].isObject()) diff --git a/swagger/sdrangel/api/swagger/include/BladeRF.yaml b/swagger/sdrangel/api/swagger/include/BladeRF.yaml new file mode 100644 index 000000000..8f75f9460 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/BladeRF.yaml @@ -0,0 +1,54 @@ +BladeRFInputSettings: + description: BladeRF + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + lnaGain: + type: integer + vga1: + type: integer + vga2: + type: integer + bandwidth: + type: integer + log2Decim: + type: integer + fcPos: + type: integer + xb200: + type: integer + xb200Path: + type: integer + xb200Filter: + type: integer + dcBlock: + type: integer + iqCorrection: + type: integer + +BladeRFOutputSettings: + description: BladeRF + properties: + centerFrequency: + type: integer + format: int64 + devSampleRate: + type: integer + vga1: + type: integer + vga2: + type: integer + bandwidth: + type: integer + log2Interp: + type: integer + xb200: + type: integer + xb200Path: + type: integer + xb200Filter: + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index e2a9f5d88..350eba899 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1715,6 +1715,10 @@ definitions: type: integer airspyHFSettings: $ref: "http://localhost:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFSettings" + bladeRFInputSettings: + $ref: "http://localhost:8081/api/swagger/include/BladeRF.yaml#/BladeRFInputSettings" + bladeRFOutputSettings: + $ref: "http://localhost:8081/api/swagger/include/BladeRF.yaml#/BladeRFOutputSettings" fileSourceSettings: $ref: "http://localhost:8081/api/swagger/include/FileSource.yaml#/FileSourceSettings" hackRFInputSettings: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index cfff66bdd..48b129fcf 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -886,6 +886,84 @@ margin-bottom: 20px; } }, "description" : "Audio output device" +}; + defs.BladeRFInputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "lnaGain" : { + "type" : "integer" + }, + "vga1" : { + "type" : "integer" + }, + "vga2" : { + "type" : "integer" + }, + "bandwidth" : { + "type" : "integer" + }, + "log2Decim" : { + "type" : "integer" + }, + "fcPos" : { + "type" : "integer" + }, + "xb200" : { + "type" : "integer" + }, + "xb200Path" : { + "type" : "integer" + }, + "xb200Filter" : { + "type" : "integer" + }, + "dcBlock" : { + "type" : "integer" + }, + "iqCorrection" : { + "type" : "integer" + } + }, + "description" : "BladeRF" +}; + defs.BladeRFOutputSettings = { + "properties" : { + "centerFrequency" : { + "type" : "integer", + "format" : "int64" + }, + "devSampleRate" : { + "type" : "integer" + }, + "vga1" : { + "type" : "integer" + }, + "vga2" : { + "type" : "integer" + }, + "bandwidth" : { + "type" : "integer" + }, + "log2Interp" : { + "type" : "integer" + }, + "xb200" : { + "type" : "integer" + }, + "xb200Path" : { + "type" : "integer" + }, + "xb200Filter" : { + "type" : "integer" + } + }, + "description" : "BladeRF" }; defs.CWKeyerSettings = { "properties" : { @@ -1152,6 +1230,12 @@ margin-bottom: 20px; "airspyHFSettings" : { "$ref" : "#/definitions/AirspyHFSettings" }, + "bladeRFInputSettings" : { + "$ref" : "#/definitions/BladeRFInputSettings" + }, + "bladeRFOutputSettings" : { + "$ref" : "#/definitions/BladeRFOutputSettings" + }, "fileSourceSettings" : { "$ref" : "#/definitions/FileSourceSettings" }, @@ -20106,7 +20190,7 @@ except ApiException as e:
    - Generated 2018-03-29T15:31:47.724+02:00 + Generated 2018-03-31T18:20:57.874+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp new file mode 100644 index 000000000..3485436f4 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.cpp @@ -0,0 +1,358 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBladeRFInputSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBladeRFInputSettings::SWGBladeRFInputSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBladeRFInputSettings::SWGBladeRFInputSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + lna_gain = 0; + m_lna_gain_isSet = false; + vga1 = 0; + m_vga1_isSet = false; + vga2 = 0; + m_vga2_isSet = false; + bandwidth = 0; + m_bandwidth_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + xb200 = 0; + m_xb200_isSet = false; + xb200_path = 0; + m_xb200_path_isSet = false; + xb200_filter = 0; + m_xb200_filter_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; +} + +SWGBladeRFInputSettings::~SWGBladeRFInputSettings() { + this->cleanup(); +} + +void +SWGBladeRFInputSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + lna_gain = 0; + m_lna_gain_isSet = false; + vga1 = 0; + m_vga1_isSet = false; + vga2 = 0; + m_vga2_isSet = false; + bandwidth = 0; + m_bandwidth_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + fc_pos = 0; + m_fc_pos_isSet = false; + xb200 = 0; + m_xb200_isSet = false; + xb200_path = 0; + m_xb200_path_isSet = false; + xb200_filter = 0; + m_xb200_filter_isSet = false; + dc_block = 0; + m_dc_block_isSet = false; + iq_correction = 0; + m_iq_correction_isSet = false; +} + +void +SWGBladeRFInputSettings::cleanup() { + + + + + + + + + + + + + +} + +SWGBladeRFInputSettings* +SWGBladeRFInputSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBladeRFInputSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&lna_gain, pJson["lnaGain"], "qint32", ""); + + ::SWGSDRangel::setValue(&vga1, pJson["vga1"], "qint32", ""); + + ::SWGSDRangel::setValue(&vga2, pJson["vga2"], "qint32", ""); + + ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&fc_pos, pJson["fcPos"], "qint32", ""); + + ::SWGSDRangel::setValue(&xb200, pJson["xb200"], "qint32", ""); + + ::SWGSDRangel::setValue(&xb200_path, pJson["xb200Path"], "qint32", ""); + + ::SWGSDRangel::setValue(&xb200_filter, pJson["xb200Filter"], "qint32", ""); + + ::SWGSDRangel::setValue(&dc_block, pJson["dcBlock"], "qint32", ""); + + ::SWGSDRangel::setValue(&iq_correction, pJson["iqCorrection"], "qint32", ""); + +} + +QString +SWGBladeRFInputSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBladeRFInputSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_dev_sample_rate_isSet){ + obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); + } + if(m_lna_gain_isSet){ + obj->insert("lnaGain", QJsonValue(lna_gain)); + } + if(m_vga1_isSet){ + obj->insert("vga1", QJsonValue(vga1)); + } + if(m_vga2_isSet){ + obj->insert("vga2", QJsonValue(vga2)); + } + if(m_bandwidth_isSet){ + obj->insert("bandwidth", QJsonValue(bandwidth)); + } + if(m_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_fc_pos_isSet){ + obj->insert("fcPos", QJsonValue(fc_pos)); + } + if(m_xb200_isSet){ + obj->insert("xb200", QJsonValue(xb200)); + } + if(m_xb200_path_isSet){ + obj->insert("xb200Path", QJsonValue(xb200_path)); + } + if(m_xb200_filter_isSet){ + obj->insert("xb200Filter", QJsonValue(xb200_filter)); + } + if(m_dc_block_isSet){ + obj->insert("dcBlock", QJsonValue(dc_block)); + } + if(m_iq_correction_isSet){ + obj->insert("iqCorrection", QJsonValue(iq_correction)); + } + + return obj; +} + +qint64 +SWGBladeRFInputSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGBladeRFInputSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getDevSampleRate() { + return dev_sample_rate; +} +void +SWGBladeRFInputSettings::setDevSampleRate(qint32 dev_sample_rate) { + this->dev_sample_rate = dev_sample_rate; + this->m_dev_sample_rate_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getLnaGain() { + return lna_gain; +} +void +SWGBladeRFInputSettings::setLnaGain(qint32 lna_gain) { + this->lna_gain = lna_gain; + this->m_lna_gain_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getVga1() { + return vga1; +} +void +SWGBladeRFInputSettings::setVga1(qint32 vga1) { + this->vga1 = vga1; + this->m_vga1_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getVga2() { + return vga2; +} +void +SWGBladeRFInputSettings::setVga2(qint32 vga2) { + this->vga2 = vga2; + this->m_vga2_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getBandwidth() { + return bandwidth; +} +void +SWGBladeRFInputSettings::setBandwidth(qint32 bandwidth) { + this->bandwidth = bandwidth; + this->m_bandwidth_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getLog2Decim() { + return log2_decim; +} +void +SWGBladeRFInputSettings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getFcPos() { + return fc_pos; +} +void +SWGBladeRFInputSettings::setFcPos(qint32 fc_pos) { + this->fc_pos = fc_pos; + this->m_fc_pos_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getXb200() { + return xb200; +} +void +SWGBladeRFInputSettings::setXb200(qint32 xb200) { + this->xb200 = xb200; + this->m_xb200_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getXb200Path() { + return xb200_path; +} +void +SWGBladeRFInputSettings::setXb200Path(qint32 xb200_path) { + this->xb200_path = xb200_path; + this->m_xb200_path_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getXb200Filter() { + return xb200_filter; +} +void +SWGBladeRFInputSettings::setXb200Filter(qint32 xb200_filter) { + this->xb200_filter = xb200_filter; + this->m_xb200_filter_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getDcBlock() { + return dc_block; +} +void +SWGBladeRFInputSettings::setDcBlock(qint32 dc_block) { + this->dc_block = dc_block; + this->m_dc_block_isSet = true; +} + +qint32 +SWGBladeRFInputSettings::getIqCorrection() { + return iq_correction; +} +void +SWGBladeRFInputSettings::setIqCorrection(qint32 iq_correction) { + this->iq_correction = iq_correction; + this->m_iq_correction_isSet = true; +} + + +bool +SWGBladeRFInputSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_lna_gain_isSet){ isObjectUpdated = true; break;} + if(m_vga1_isSet){ isObjectUpdated = true; break;} + if(m_vga2_isSet){ isObjectUpdated = true; break;} + if(m_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_log2_decim_isSet){ isObjectUpdated = true; break;} + if(m_fc_pos_isSet){ isObjectUpdated = true; break;} + if(m_xb200_isSet){ isObjectUpdated = true; break;} + if(m_xb200_path_isSet){ isObjectUpdated = true; break;} + if(m_xb200_filter_isSet){ isObjectUpdated = true; break;} + if(m_dc_block_isSet){ isObjectUpdated = true; break;} + if(m_iq_correction_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h new file mode 100644 index 000000000..89e8465b9 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFInputSettings.h @@ -0,0 +1,130 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBladeRFInputSettings.h + * + * BladeRF + */ + +#ifndef SWGBladeRFInputSettings_H_ +#define SWGBladeRFInputSettings_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBladeRFInputSettings: public SWGObject { +public: + SWGBladeRFInputSettings(); + SWGBladeRFInputSettings(QString* json); + virtual ~SWGBladeRFInputSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBladeRFInputSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getDevSampleRate(); + void setDevSampleRate(qint32 dev_sample_rate); + + qint32 getLnaGain(); + void setLnaGain(qint32 lna_gain); + + qint32 getVga1(); + void setVga1(qint32 vga1); + + qint32 getVga2(); + void setVga2(qint32 vga2); + + qint32 getBandwidth(); + void setBandwidth(qint32 bandwidth); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + qint32 getFcPos(); + void setFcPos(qint32 fc_pos); + + qint32 getXb200(); + void setXb200(qint32 xb200); + + qint32 getXb200Path(); + void setXb200Path(qint32 xb200_path); + + qint32 getXb200Filter(); + void setXb200Filter(qint32 xb200_filter); + + qint32 getDcBlock(); + void setDcBlock(qint32 dc_block); + + qint32 getIqCorrection(); + void setIqCorrection(qint32 iq_correction); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 dev_sample_rate; + bool m_dev_sample_rate_isSet; + + qint32 lna_gain; + bool m_lna_gain_isSet; + + qint32 vga1; + bool m_vga1_isSet; + + qint32 vga2; + bool m_vga2_isSet; + + qint32 bandwidth; + bool m_bandwidth_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + qint32 fc_pos; + bool m_fc_pos_isSet; + + qint32 xb200; + bool m_xb200_isSet; + + qint32 xb200_path; + bool m_xb200_path_isSet; + + qint32 xb200_filter; + bool m_xb200_filter_isSet; + + qint32 dc_block; + bool m_dc_block_isSet; + + qint32 iq_correction; + bool m_iq_correction_isSet; + +}; + +} + +#endif /* SWGBladeRFInputSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp new file mode 100644 index 000000000..f60d18998 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.cpp @@ -0,0 +1,274 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGBladeRFOutputSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGBladeRFOutputSettings::SWGBladeRFOutputSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGBladeRFOutputSettings::SWGBladeRFOutputSettings() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + vga1 = 0; + m_vga1_isSet = false; + vga2 = 0; + m_vga2_isSet = false; + bandwidth = 0; + m_bandwidth_isSet = false; + log2_interp = 0; + m_log2_interp_isSet = false; + xb200 = 0; + m_xb200_isSet = false; + xb200_path = 0; + m_xb200_path_isSet = false; + xb200_filter = 0; + m_xb200_filter_isSet = false; +} + +SWGBladeRFOutputSettings::~SWGBladeRFOutputSettings() { + this->cleanup(); +} + +void +SWGBladeRFOutputSettings::init() { + center_frequency = 0L; + m_center_frequency_isSet = false; + dev_sample_rate = 0; + m_dev_sample_rate_isSet = false; + vga1 = 0; + m_vga1_isSet = false; + vga2 = 0; + m_vga2_isSet = false; + bandwidth = 0; + m_bandwidth_isSet = false; + log2_interp = 0; + m_log2_interp_isSet = false; + xb200 = 0; + m_xb200_isSet = false; + xb200_path = 0; + m_xb200_path_isSet = false; + xb200_filter = 0; + m_xb200_filter_isSet = false; +} + +void +SWGBladeRFOutputSettings::cleanup() { + + + + + + + + + +} + +SWGBladeRFOutputSettings* +SWGBladeRFOutputSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGBladeRFOutputSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(¢er_frequency, pJson["centerFrequency"], "qint64", ""); + + ::SWGSDRangel::setValue(&dev_sample_rate, pJson["devSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&vga1, pJson["vga1"], "qint32", ""); + + ::SWGSDRangel::setValue(&vga2, pJson["vga2"], "qint32", ""); + + ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "qint32", ""); + + ::SWGSDRangel::setValue(&log2_interp, pJson["log2Interp"], "qint32", ""); + + ::SWGSDRangel::setValue(&xb200, pJson["xb200"], "qint32", ""); + + ::SWGSDRangel::setValue(&xb200_path, pJson["xb200Path"], "qint32", ""); + + ::SWGSDRangel::setValue(&xb200_filter, pJson["xb200Filter"], "qint32", ""); + +} + +QString +SWGBladeRFOutputSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGBladeRFOutputSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_center_frequency_isSet){ + obj->insert("centerFrequency", QJsonValue(center_frequency)); + } + if(m_dev_sample_rate_isSet){ + obj->insert("devSampleRate", QJsonValue(dev_sample_rate)); + } + if(m_vga1_isSet){ + obj->insert("vga1", QJsonValue(vga1)); + } + if(m_vga2_isSet){ + obj->insert("vga2", QJsonValue(vga2)); + } + if(m_bandwidth_isSet){ + obj->insert("bandwidth", QJsonValue(bandwidth)); + } + if(m_log2_interp_isSet){ + obj->insert("log2Interp", QJsonValue(log2_interp)); + } + if(m_xb200_isSet){ + obj->insert("xb200", QJsonValue(xb200)); + } + if(m_xb200_path_isSet){ + obj->insert("xb200Path", QJsonValue(xb200_path)); + } + if(m_xb200_filter_isSet){ + obj->insert("xb200Filter", QJsonValue(xb200_filter)); + } + + return obj; +} + +qint64 +SWGBladeRFOutputSettings::getCenterFrequency() { + return center_frequency; +} +void +SWGBladeRFOutputSettings::setCenterFrequency(qint64 center_frequency) { + this->center_frequency = center_frequency; + this->m_center_frequency_isSet = true; +} + +qint32 +SWGBladeRFOutputSettings::getDevSampleRate() { + return dev_sample_rate; +} +void +SWGBladeRFOutputSettings::setDevSampleRate(qint32 dev_sample_rate) { + this->dev_sample_rate = dev_sample_rate; + this->m_dev_sample_rate_isSet = true; +} + +qint32 +SWGBladeRFOutputSettings::getVga1() { + return vga1; +} +void +SWGBladeRFOutputSettings::setVga1(qint32 vga1) { + this->vga1 = vga1; + this->m_vga1_isSet = true; +} + +qint32 +SWGBladeRFOutputSettings::getVga2() { + return vga2; +} +void +SWGBladeRFOutputSettings::setVga2(qint32 vga2) { + this->vga2 = vga2; + this->m_vga2_isSet = true; +} + +qint32 +SWGBladeRFOutputSettings::getBandwidth() { + return bandwidth; +} +void +SWGBladeRFOutputSettings::setBandwidth(qint32 bandwidth) { + this->bandwidth = bandwidth; + this->m_bandwidth_isSet = true; +} + +qint32 +SWGBladeRFOutputSettings::getLog2Interp() { + return log2_interp; +} +void +SWGBladeRFOutputSettings::setLog2Interp(qint32 log2_interp) { + this->log2_interp = log2_interp; + this->m_log2_interp_isSet = true; +} + +qint32 +SWGBladeRFOutputSettings::getXb200() { + return xb200; +} +void +SWGBladeRFOutputSettings::setXb200(qint32 xb200) { + this->xb200 = xb200; + this->m_xb200_isSet = true; +} + +qint32 +SWGBladeRFOutputSettings::getXb200Path() { + return xb200_path; +} +void +SWGBladeRFOutputSettings::setXb200Path(qint32 xb200_path) { + this->xb200_path = xb200_path; + this->m_xb200_path_isSet = true; +} + +qint32 +SWGBladeRFOutputSettings::getXb200Filter() { + return xb200_filter; +} +void +SWGBladeRFOutputSettings::setXb200Filter(qint32 xb200_filter) { + this->xb200_filter = xb200_filter; + this->m_xb200_filter_isSet = true; +} + + +bool +SWGBladeRFOutputSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_center_frequency_isSet){ isObjectUpdated = true; break;} + if(m_dev_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_vga1_isSet){ isObjectUpdated = true; break;} + if(m_vga2_isSet){ isObjectUpdated = true; break;} + if(m_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_log2_interp_isSet){ isObjectUpdated = true; break;} + if(m_xb200_isSet){ isObjectUpdated = true; break;} + if(m_xb200_path_isSet){ isObjectUpdated = true; break;} + if(m_xb200_filter_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h new file mode 100644 index 000000000..c24a434df --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGBladeRFOutputSettings.h @@ -0,0 +1,106 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGBladeRFOutputSettings.h + * + * BladeRF + */ + +#ifndef SWGBladeRFOutputSettings_H_ +#define SWGBladeRFOutputSettings_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGBladeRFOutputSettings: public SWGObject { +public: + SWGBladeRFOutputSettings(); + SWGBladeRFOutputSettings(QString* json); + virtual ~SWGBladeRFOutputSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGBladeRFOutputSettings* fromJson(QString &jsonString) override; + + qint64 getCenterFrequency(); + void setCenterFrequency(qint64 center_frequency); + + qint32 getDevSampleRate(); + void setDevSampleRate(qint32 dev_sample_rate); + + qint32 getVga1(); + void setVga1(qint32 vga1); + + qint32 getVga2(); + void setVga2(qint32 vga2); + + qint32 getBandwidth(); + void setBandwidth(qint32 bandwidth); + + qint32 getLog2Interp(); + void setLog2Interp(qint32 log2_interp); + + qint32 getXb200(); + void setXb200(qint32 xb200); + + qint32 getXb200Path(); + void setXb200Path(qint32 xb200_path); + + qint32 getXb200Filter(); + void setXb200Filter(qint32 xb200_filter); + + + virtual bool isSet() override; + +private: + qint64 center_frequency; + bool m_center_frequency_isSet; + + qint32 dev_sample_rate; + bool m_dev_sample_rate_isSet; + + qint32 vga1; + bool m_vga1_isSet; + + qint32 vga2; + bool m_vga2_isSet; + + qint32 bandwidth; + bool m_bandwidth_isSet; + + qint32 log2_interp; + bool m_log2_interp_isSet; + + qint32 xb200; + bool m_xb200_isSet; + + qint32 xb200_path; + bool m_xb200_path_isSet; + + qint32 xb200_filter; + bool m_xb200_filter_isSet; + +}; + +} + +#endif /* SWGBladeRFOutputSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 28f28926b..d798ead5b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -34,6 +34,10 @@ SWGDeviceSettings::SWGDeviceSettings() { m_tx_isSet = false; airspy_hf_settings = nullptr; m_airspy_hf_settings_isSet = false; + blade_rf_input_settings = nullptr; + m_blade_rf_input_settings_isSet = false; + blade_rf_output_settings = nullptr; + m_blade_rf_output_settings_isSet = false; file_source_settings = nullptr; m_file_source_settings_isSet = false; hack_rf_input_settings = nullptr; @@ -60,6 +64,10 @@ SWGDeviceSettings::init() { m_tx_isSet = false; airspy_hf_settings = new SWGAirspyHFSettings(); m_airspy_hf_settings_isSet = false; + blade_rf_input_settings = new SWGBladeRFInputSettings(); + m_blade_rf_input_settings_isSet = false; + blade_rf_output_settings = new SWGBladeRFOutputSettings(); + m_blade_rf_output_settings_isSet = false; file_source_settings = new SWGFileSourceSettings(); m_file_source_settings_isSet = false; hack_rf_input_settings = new SWGHackRFInputSettings(); @@ -83,6 +91,12 @@ SWGDeviceSettings::cleanup() { if(airspy_hf_settings != nullptr) { delete airspy_hf_settings; } + if(blade_rf_input_settings != nullptr) { + delete blade_rf_input_settings; + } + if(blade_rf_output_settings != nullptr) { + delete blade_rf_output_settings; + } if(file_source_settings != nullptr) { delete file_source_settings; } @@ -120,6 +134,10 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&airspy_hf_settings, pJson["airspyHFSettings"], "SWGAirspyHFSettings", "SWGAirspyHFSettings"); + ::SWGSDRangel::setValue(&blade_rf_input_settings, pJson["bladeRFInputSettings"], "SWGBladeRFInputSettings", "SWGBladeRFInputSettings"); + + ::SWGSDRangel::setValue(&blade_rf_output_settings, pJson["bladeRFOutputSettings"], "SWGBladeRFOutputSettings", "SWGBladeRFOutputSettings"); + ::SWGSDRangel::setValue(&file_source_settings, pJson["fileSourceSettings"], "SWGFileSourceSettings", "SWGFileSourceSettings"); ::SWGSDRangel::setValue(&hack_rf_input_settings, pJson["hackRFInputSettings"], "SWGHackRFInputSettings", "SWGHackRFInputSettings"); @@ -157,6 +175,12 @@ SWGDeviceSettings::asJsonObject() { if((airspy_hf_settings != nullptr) && (airspy_hf_settings->isSet())){ toJsonValue(QString("airspyHFSettings"), airspy_hf_settings, obj, QString("SWGAirspyHFSettings")); } + if((blade_rf_input_settings != nullptr) && (blade_rf_input_settings->isSet())){ + toJsonValue(QString("bladeRFInputSettings"), blade_rf_input_settings, obj, QString("SWGBladeRFInputSettings")); + } + if((blade_rf_output_settings != nullptr) && (blade_rf_output_settings->isSet())){ + toJsonValue(QString("bladeRFOutputSettings"), blade_rf_output_settings, obj, QString("SWGBladeRFOutputSettings")); + } if((file_source_settings != nullptr) && (file_source_settings->isSet())){ toJsonValue(QString("fileSourceSettings"), file_source_settings, obj, QString("SWGFileSourceSettings")); } @@ -209,6 +233,26 @@ SWGDeviceSettings::setAirspyHfSettings(SWGAirspyHFSettings* airspy_hf_settings) this->m_airspy_hf_settings_isSet = true; } +SWGBladeRFInputSettings* +SWGDeviceSettings::getBladeRfInputSettings() { + return blade_rf_input_settings; +} +void +SWGDeviceSettings::setBladeRfInputSettings(SWGBladeRFInputSettings* blade_rf_input_settings) { + this->blade_rf_input_settings = blade_rf_input_settings; + this->m_blade_rf_input_settings_isSet = true; +} + +SWGBladeRFOutputSettings* +SWGDeviceSettings::getBladeRfOutputSettings() { + return blade_rf_output_settings; +} +void +SWGDeviceSettings::setBladeRfOutputSettings(SWGBladeRFOutputSettings* blade_rf_output_settings) { + this->blade_rf_output_settings = blade_rf_output_settings; + this->m_blade_rf_output_settings_isSet = true; +} + SWGFileSourceSettings* SWGDeviceSettings::getFileSourceSettings() { return file_source_settings; @@ -277,6 +321,8 @@ SWGDeviceSettings::isSet(){ if(device_hw_type != nullptr && *device_hw_type != QString("")){ isObjectUpdated = true; break;} if(m_tx_isSet){ isObjectUpdated = true; break;} if(airspy_hf_settings != nullptr && airspy_hf_settings->isSet()){ isObjectUpdated = true; break;} + if(blade_rf_input_settings != nullptr && blade_rf_input_settings->isSet()){ isObjectUpdated = true; break;} + if(blade_rf_output_settings != nullptr && blade_rf_output_settings->isSet()){ isObjectUpdated = true; break;} if(file_source_settings != nullptr && file_source_settings->isSet()){ isObjectUpdated = true; break;} if(hack_rf_input_settings != nullptr && hack_rf_input_settings->isSet()){ isObjectUpdated = true; break;} if(hack_rf_output_settings != nullptr && hack_rf_output_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index 2268a2c77..c9ec69600 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -23,6 +23,8 @@ #include "SWGAirspyHFSettings.h" +#include "SWGBladeRFInputSettings.h" +#include "SWGBladeRFOutputSettings.h" #include "SWGFileSourceSettings.h" #include "SWGHackRFInputSettings.h" #include "SWGHackRFOutputSettings.h" @@ -58,6 +60,12 @@ public: SWGAirspyHFSettings* getAirspyHfSettings(); void setAirspyHfSettings(SWGAirspyHFSettings* airspy_hf_settings); + SWGBladeRFInputSettings* getBladeRfInputSettings(); + void setBladeRfInputSettings(SWGBladeRFInputSettings* blade_rf_input_settings); + + SWGBladeRFOutputSettings* getBladeRfOutputSettings(); + void setBladeRfOutputSettings(SWGBladeRFOutputSettings* blade_rf_output_settings); + SWGFileSourceSettings* getFileSourceSettings(); void setFileSourceSettings(SWGFileSourceSettings* file_source_settings); @@ -89,6 +97,12 @@ private: SWGAirspyHFSettings* airspy_hf_settings; bool m_airspy_hf_settings_isSet; + SWGBladeRFInputSettings* blade_rf_input_settings; + bool m_blade_rf_input_settings_isSet; + + SWGBladeRFOutputSettings* blade_rf_output_settings; + bool m_blade_rf_output_settings_isSet; + SWGFileSourceSettings* file_source_settings; bool m_file_source_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index a4425de8e..5232fc21f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -20,6 +20,8 @@ #include "SWGAudioDevices.h" #include "SWGAudioInputDevice.h" #include "SWGAudioOutputDevice.h" +#include "SWGBladeRFInputSettings.h" +#include "SWGBladeRFOutputSettings.h" #include "SWGCWKeyerSettings.h" #include "SWGChannel.h" #include "SWGChannelListItem.h" @@ -80,6 +82,12 @@ namespace SWGSDRangel { if(QString("SWGAudioOutputDevice").compare(type) == 0) { return new SWGAudioOutputDevice(); } + if(QString("SWGBladeRFInputSettings").compare(type) == 0) { + return new SWGBladeRFInputSettings(); + } + if(QString("SWGBladeRFOutputSettings").compare(type) == 0) { + return new SWGBladeRFOutputSettings(); + } if(QString("SWGCWKeyerSettings").compare(type) == 0) { return new SWGCWKeyerSettings(); } From 4c31b2c59d3f03534cee1c51503bca8d0a41ae18 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 31 Mar 2018 20:08:22 +0200 Subject: [PATCH 230/956] Web API: scanner client example: added channel demod volume control --- swagger/sdrangel/examples/scanner.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/swagger/sdrangel/examples/scanner.py b/swagger/sdrangel/examples/scanner.py index adcf554c4..26fe228ab 100755 --- a/swagger/sdrangel/examples/scanner.py +++ b/swagger/sdrangel/examples/scanner.py @@ -57,7 +57,8 @@ def getInputOptions(): parser.add_option("-S", "--freq-start", dest="freq_start", help="frequency start (Hz)", metavar="FREQUENCY", type="int", default=446006250) parser.add_option("-T", "--freq-stop", dest="freq_stop", help="frequency stop (Hz)", metavar="FREQUENCY", type="int", default=446193750) parser.add_option("-b", "--af-bw", dest="af_bw", help="audio babdwidth (kHz)", metavar="FREQUENCY_KHZ", type="int" ,default=3) - parser.add_option("-r", "--rf-bw", dest="rf_bw", help="RF babdwidth (Hz). Sets to nearest available", metavar="FREQUENCY", type="int", default=10000) + parser.add_option("-r", "--rf-bw", dest="rf_bw", help="RF babdwidth (Hz). Sets to nearest available", metavar="FREQUENCY", type="int", default=10000) + parser.add_option("--vol", dest="volume", help="audio volume", metavar="VOLUME", type="float", default=1.0) parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="BOOLEAN", action="store_true", default=False) parser.add_option("-m", "--mock", dest="mock", help="just print calculated values and exit", metavar="BOOLEAN", action="store_true", default=False) parser.add_option("--ppm", dest="lo_ppm", help="LO correction in PPM", metavar="PPM", type="float", default=0.0) @@ -178,12 +179,14 @@ def setupChannels(scan_control, options): settings["NFMDemodSettings"]["inputFrequencyOffset"] = int(shift) settings["NFMDemodSettings"]["afBandwidth"] = options.af_bw * 1000 settings["NFMDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["NFMDemodSettings"]["volume"] = options.volume settings["NFMDemodSettings"]["squelch"] = options.squelch_db * 10 # centi-Bels settings["NFMDemodSettings"]["squelchGate"] = options.squelch_gate / 10 # 10's of ms settings["NFMDemodSettings"]["title"] = "Channel %d" % i elif options.channel_id == "AMDemod": settings["AMDemodSettings"]["inputFrequencyOffset"] = int(shift) settings["AMDemodSettings"]["rfBandwidth"] = options.rf_bw + settings["AMDemodSettings"]["volume"] = options.volume settings["AMDemodSettings"]["squelch"] = options.squelch_db settings["AMDemodSettings"]["title"] = "Channel %d" % i settings["AMDemodSettings"]["bandpassEnable"] = 1 # bandpass filter From a447193bd76f2154f04840d67d78812896e3116b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Apr 2018 09:33:08 +0200 Subject: [PATCH 231/956] DSD demod: make FM deviation independent of RF bandwidth. Bumped version to 3.14.1 --- app/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 8 ++++++ plugins/channelrx/demoddsd/dsddemod.cpp | 4 +-- plugins/channelrx/demoddsd/dsddemodgui.cpp | 4 +-- plugins/channelrx/demoddsd/dsddemodgui.ui | 27 ++++++++++--------- plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- .../channelrx/demoddsd/dsddemodsettings.cpp | 4 +-- plugins/channelrx/demoddsd/readme.md | 13 ++++++--- plugins/channelrx/demodnfm/nfmplugin.cpp | 2 +- plugins/channeltx/modnfm/nfmmodplugin.cpp | 2 +- 11 files changed, 44 insertions(+), 26 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index dcea8759c..13f5a1121 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.14.0"); + QCoreApplication::setApplicationVersion("3.14.1"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 235a11ff1..5b06ce273 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.14.0"); + QCoreApplication::setApplicationVersion("3.14.1"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 3bcfee61b..ef97c66af 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +sdrangel (3.14.1-1) unstable; urgency=medium + + * NFM: fixed lowpass filter initialization (CTCSS) + * DSD demod: set FM deviation independent from RF bandwidth + * DSD demod: implemented DMR negative with DSDcc v1.7.5 + + -- Edouard Griffiths, F4EXB Sun, 01 Apr 2018 12:14:18 +0200 + sdrangel (3.14.0-1) unstable; urgency=medium * New audio devices management diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index e9c2108db..518cc4767 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -456,13 +456,13 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) m_interpolator.create(16, m_inputSampleRate, (settings.m_rfBandwidth) / 2.2); m_interpolatorDistanceRemain = 0; m_interpolatorDistance = (Real) m_inputSampleRate / (Real) 48000; - m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation); + //m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation); m_settingsMutex.unlock(); } if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { - m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation); + m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation)); } if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index b8c5e43ae..d8e8e8edf 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -135,7 +135,7 @@ void DSDDemodGUI::on_demodGain_valueChanged(int value) void DSDDemodGUI::on_fmDeviation_valueChanged(int value) { m_settings.m_fmDeviation = value * 100.0; - ui->fmDeviationText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1)); + ui->fmDeviationText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(value / 10.0, 0, 'f', 1)); applySettings(); } @@ -384,7 +384,7 @@ void DSDDemodGUI::displaySettings() ui->rfBWText->setText(QString("%1k").arg(ui->rfBW->value() / 10.0, 0, 'f', 1)); ui->fmDeviation->setValue(m_settings.m_fmDeviation / 100.0); - ui->fmDeviationText->setText(QString("%1k").arg(ui->fmDeviation->value() / 10.0, 0, 'f', 1)); + ui->fmDeviationText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(ui->fmDeviation->value() / 10.0, 0, 'f', 1)); ui->squelch->setValue(m_settings.m_squelch * 10.0); ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 10.0, 0, 'f', 1)); diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index 2efd83113..8d1ed6a0c 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -827,7 +827,7 @@ 50 107 - 151 + 141 16 @@ -835,13 +835,16 @@ Maximum frequency deviation (kHz) - 500 + 100 1 + + 35 + - 50 + 35 Qt::Horizontal @@ -863,20 +866,20 @@ - 210 + 200 100 - 40 + 50 29 - 40 + 50 0 - 00.0k + +00.0k Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -900,7 +903,7 @@ 50 137 - 151 + 141 16 @@ -917,7 +920,7 @@ 50 - 400 + 200 1 @@ -1054,15 +1057,15 @@ - 210 + 200 130 - 40 + 50 29 - 35 + 50 0 diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index c3c5a6644..beff73f71 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("3.14.0"), + QString("3.14.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index cba976937..de7c76c80 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -32,8 +32,8 @@ void DSDDemodSettings::resetToDefaults() { m_inputFrequencyOffset = 0; m_rfBandwidth = 12500.0; - m_fmDeviation = 5000.0; - m_demodGain = 1.25; + m_fmDeviation = 3500.0; + m_demodGain = 1.0; m_volume = 2.0; m_baudRate = 4800; m_squelchGate = 5; // 10s of ms at 48000 Hz sample rate. Corresponds to 2400 for AGC attack diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index da4830eff..9eb14f29d 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -273,7 +273,7 @@ This display shows the sampled points of the demodulated FM signal in a XY plane - X as the signal at time t and Y the signal at time t minus symbol time if "transitions constellation" is selected by button (B.13) - X as the signal and Y as the synchronization signal if "symbol synchronization" is selected by button (B.13) -The display shows 16 points as yellow crosses that can be used to tune the center frequency and FM deviation and gain so that symbol recovery can be done with the best conditions. In the rest of the documentation they will be referenced with numbers fron 0 to 15 starting at the top left corner and going from left to right and top to bottom. +The display shows 16 points as yellow crosses that can be used to tune the center frequency (A.1) and FM deviation (B.17) so that symbol recovery can be done with the best conditions. In the rest of the documentation they will be referenced with numbers fron 0 to 15 starting at the top left corner and going from left to right and top to bottom.
    Transition constellation display
    @@ -351,6 +351,8 @@ This can be one of the following: - `+DMRd`: non-inverted DMR data frame - `+DMRv`: non-inverted DMR voice frame + - `-DMRd`: inverted DMR data frame + - `-DMRv`: inverted DMR voice frame - `+D-STAR`: non-inverted D-Star frame - `-D-STAR`: inverted D-Star frame - `+D-STAR_HD`: non-inverted D-Star header frame encountered @@ -445,8 +447,13 @@ This button tunes the persistence decay of the points displayer on B.1. The trac

    B.17: Maximum expected FM deviation

    -This is the deviation in kHz leading to maximum (100%) deviation. You should aim for 30 to 50% (+/-300 to +/-500m) deviation on the scope display. +This is the one side deviation in kHz (±) leading to maximum (100%) deviation. You should adjust this value to make the figure on the signal scope fill the entire screen as shown in the screenshots above. The typical deviations by mode for a unit gain (1.0 at B.18) are: + + - DMR: ±5.4k + - dPMR: ±2.7k + - D-Star: ±3.5k + - YSF: ±7.0k

    B.18: Gain after discriminator

    -This is the gain applied to the output of the discriminator before the decoder +This is the gain applied to the output of the discriminator before the decoder. Normally this would be set at unit gain 1.0 while the FM deviation is adjusted. However this can be used to extend the range of FM adjustment. diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index f474d5bbe..684f3835b 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -7,7 +7,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("3.14.0"), + QString("3.14.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp index d020c3594..8447328d6 100644 --- a/plugins/channeltx/modnfm/nfmmodplugin.cpp +++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp @@ -23,7 +23,7 @@ const PluginDescriptor NFMModPlugin::m_pluginDescriptor = { QString("NFM Modulator"), - QString("3.12.0"), + QString("3.14.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 8d64ca20687c4f191f62636f043f0cebd7f49779 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Apr 2018 22:14:11 +0200 Subject: [PATCH 232/956] DSD demod: new dialog to show status text log --- plugins/channelrx/demoddsd/CMakeLists.txt | 3 + plugins/channelrx/demoddsd/dsddemodgui.cpp | 13 +- plugins/channelrx/demoddsd/dsddemodgui.h | 4 + plugins/channelrx/demoddsd/dsddemodgui.ui | 75 ++++++- .../demoddsd/dsdstatustextdialog.cpp | 55 +++++ .../channelrx/demoddsd/dsdstatustextdialog.h | 45 +++++ .../channelrx/demoddsd/dsdstatustextdialog.ui | 178 +++++++++++++++++ sdrgui/resources/pin_last.png | Bin 0 -> 341 bytes sdrgui/resources/res.qrc | 188 +++++++++--------- sdrgui/resources/sweep.png | Bin 0 -> 371 bytes 10 files changed, 456 insertions(+), 105 deletions(-) create mode 100644 plugins/channelrx/demoddsd/dsdstatustextdialog.cpp create mode 100644 plugins/channelrx/demoddsd/dsdstatustextdialog.h create mode 100644 plugins/channelrx/demoddsd/dsdstatustextdialog.ui create mode 100644 sdrgui/resources/pin_last.png create mode 100644 sdrgui/resources/sweep.png diff --git a/plugins/channelrx/demoddsd/CMakeLists.txt b/plugins/channelrx/demoddsd/CMakeLists.txt index 87ea94025..c4c35e25a 100644 --- a/plugins/channelrx/demoddsd/CMakeLists.txt +++ b/plugins/channelrx/demoddsd/CMakeLists.txt @@ -9,6 +9,7 @@ set(dsddemod_SOURCES dsddemodbaudrates.cpp dsddemodsettings.cpp dsddecoder.cpp + dsdstatustextdialog.cpp ) set(dsddemod_HEADERS @@ -18,10 +19,12 @@ set(dsddemod_HEADERS dsddemodbaudrates.h dsddemodsettings.h dsddecoder.h + dsdstatustextdialog.h ) set(dsddemod_FORMS dsddemodgui.ui + dsdstatustextdialog.ui ) if (BUILD_DEBIAN) diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index d8e8e8edf..ccd4e2937 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -265,6 +265,12 @@ void DSDDemodGUI::onMenuDialogCalled(const QPoint &p) applySettings(); } +void DSDDemodGUI::on_viewStatusLog_clicked() +{ + qDebug("DSDDemodGUI::on_viewStatusLog_clicked"); + m_dsdStatusTextDialog.exec(); +} + DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : RollupWidget(parent), ui(new Ui::DSDDemodGUI), @@ -279,7 +285,8 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_slot2On(false), m_tdmaStereo(false), m_squelchOpen(false), - m_tickCount(0) + m_tickCount(0), + m_dsdStatusTextDialog(0) { ui->setupUi(this); ui->screenTV->setColor(true); @@ -681,6 +688,10 @@ void DSDDemodGUI::tick() formatStatusText(); ui->formatStatusText->setText(QString(m_formatStatusText)); + if (ui->activateStatusLog->isChecked()) { + m_dsdStatusTextDialog.addLine(QString(m_formatStatusText)); + } + if (m_formatStatusText[0] == '\0') { ui->formatStatusText->setStyleSheet("QLabel { background:rgb(53,53,53); }"); // turn off background } else { diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index 77edd0f09..728fdf446 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -28,6 +28,7 @@ #include "util/messagequeue.h" #include "dsddemodsettings.h" +#include "dsdstatustextdialog.h" class PluginAPI; class DeviceUISet; @@ -98,6 +99,8 @@ private: MessageQueue m_inputMessageQueue; + DSDStatusTextDialog m_dsdStatusTextDialog; + explicit DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~DSDDemodGUI(); @@ -132,6 +135,7 @@ private slots: void on_symbolPLLLock_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); + void on_viewStatusLog_clicked(); void audioSelect(); void tick(); }; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index 8d1ed6a0c..e110fc2e4 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -6,7 +6,7 @@ 0 0 - 610 + 613 392
    @@ -254,6 +254,62 @@ + + + + Activate status text log + + + + + + + + + + + 24 + 0 + + + + + 24 + 16777215 + + + + View status text log + + + + + + + :/listing.png:/listing.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + @@ -309,22 +365,16 @@ - + - Qt::Horizontal + Qt::Vertical - - - 40 - 20 - - - + - Sq + Sq @@ -487,6 +537,9 @@ 9 + + Status text + QFrame::Box diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp new file mode 100644 index 000000000..47a843e8c --- /dev/null +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 "dsdstatustextdialog.h" +#include "ui_dsdstatustextdialog.h" + +#include +#include + +DSDStatusTextDialog::DSDStatusTextDialog(QWidget* parent) : + QDialog(parent), + ui(new Ui::DSDStatusTextDialog) +{ + ui->setupUi(this); +} + +DSDStatusTextDialog::~DSDStatusTextDialog() +{ + delete ui; +} + +void DSDStatusTextDialog::addLine(const QString& line) +{ + if ((line.size() > 0) && (line != m_lastLine)) + { + QDateTime dt = QDateTime::currentDateTime(); + QString dateStr = dt.toString("hh:mm:ss"); + QTextCursor cursor = ui->logEdit->textCursor(); + cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + cursor.insertText(tr("%1 %2\n").arg(dateStr).arg(line)); + if (ui->pinToLastLine->isChecked()) { + ui->logEdit->verticalScrollBar()->setValue(ui->logEdit->verticalScrollBar()->maximum()); + } + m_lastLine = line; + } +} + +void DSDStatusTextDialog::on_clear_clicked() +{ + ui->logEdit->clear(); +} diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.h b/plugins/channelrx/demoddsd/dsdstatustextdialog.h new file mode 100644 index 000000000..f5d5a0718 --- /dev/null +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.h @@ -0,0 +1,45 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// 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 PLUGINS_CHANNELRX_DEMODDSD_DSDSTATUSTEXTDIALOG_H_ +#define PLUGINS_CHANNELRX_DEMODDSD_DSDSTATUSTEXTDIALOG_H_ + +#include + +namespace Ui { + class DSDStatusTextDialog; +} + +class DSDStatusTextDialog : public QDialog { + Q_OBJECT + +public: + explicit DSDStatusTextDialog(QWidget* parent = 0); + ~DSDStatusTextDialog(); + + void addLine(const QString& line); + +private: + Ui::DSDStatusTextDialog* ui; + QString m_lastLine; + +private slots: + void on_clear_clicked(); +}; + + +#endif /* PLUGINS_CHANNELRX_DEMODDSD_DSDSTATUSTEXTDIALOG_H_ */ diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.ui b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui new file mode 100644 index 000000000..675e41047 --- /dev/null +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui @@ -0,0 +1,178 @@ + + + DSDStatusTextDialog + + + + 0 + 0 + 740 + 380 + + + + + Sans Serif + 9 + + + + Status text log + + + + :/sdrangel_icon.png:/sdrangel_icon.png + + + + + + + + + 24 + 24 + + + + Clear log + + + + + + + :/sweep.png:/sweep.png + + + false + + + + + + + Pin to last line + + + + + + + :/pin_last.png:/pin_last.png + + + + + + + + 24 + 24 + + + + Save log to file + + + + + + + :/save.png:/save.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + true + + + + Monospace + + + + Log + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + ButtonSwitch + QToolButton +
    gui/buttonswitch.h
    +
    +
    + + buttonBox + + + + + + + buttonBox + accepted() + DSDStatusTextDialog + accept() + + + 257 + 194 + + + 157 + 203 + + + + + buttonBox + rejected() + DSDStatusTextDialog + reject() + + + 314 + 194 + + + 286 + 203 + + + + +
    diff --git a/sdrgui/resources/pin_last.png b/sdrgui/resources/pin_last.png new file mode 100644 index 0000000000000000000000000000000000000000..165a0f0b7140e4d14eab5a0c8bfa3f4588bbdbef GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAH3s;ExB}__|NpzYyGuz)&6_vR z%gf8n%}rEPR6syLLPBEh+_}@HOx~;X`uh4F zJ$h7DRl*!&%@FSFdh=h=q|$N&Tw#azUVwucwP+h{pNk1O*|3M^h4xXddkFmYL0? zEb>6eeR^Wbktr7$j5e-HKe9|JwoSgB)#ixR)JC4d4wY{U4nIp@v$0Wog=>K;UtVF! d0S7q-23bvpcl!cW4g(El@O1TaS?83{1OOZ@c)tJu literal 0 HcmV?d00001 diff --git a/sdrgui/resources/res.qrc b/sdrgui/resources/res.qrc index 245206864..9f6b124e8 100644 --- a/sdrgui/resources/res.qrc +++ b/sdrgui/resources/res.qrc @@ -1,95 +1,97 @@ - - minusrx.png - plusrx.png - microphone.png - checkmark.png - questionmark.png - export.png - import.png - compressed.png - locked.png - appicon.png - unlocked.png - histogram.png - waterfall.png - preset-load.png - preset-save.png - preset-update.png - preset-delete.png - horizontal.png - vertical.png - maxhold.png - grid.png - invertspectrum.png - preset-last.png - display1.png - display2.png - slopen_icon.png - slopep_icon.png - display1_w.png - display2_w.png - horizontal_w.png - vertical_w.png - current.png - slopeb_icon.png - clear.png - playloop.png - play.png - pause.png - stop.png - sdrangel_logo.png - sdrangel_icon.png - minus.png - plus.png - record_off.png - record_on.png - mem.png - minusw.png - plusw.png - mono.png - stereo.png - sound_off.png - sound_on.png - dsb.png - usb.png - flip_lr.png - flip_rl.png - carrier.png - rds.png - recycle.png - lsb.png - constellation.png - slot1_off.png - slot1_on.png - slot2_off.png - slot2_on.png - iambickey.png - morsekey.png - txoff.png - txon.png - arrow_down.png - arrow_up.png - film_reel.png - film.png - picture.png - camera.png - filter_bandpass.png - stream.png - antenna.png - link.png - choose.png - clocksource.png - flip_sidebands.png - filter_highpass.png - edit.png - listing.png - create.png - duplicate.png - bin.png - save.png - load.png - keyboard.png - kill.png - + + pin_last.png + sweep.png + minusrx.png + plusrx.png + microphone.png + checkmark.png + questionmark.png + export.png + import.png + compressed.png + locked.png + appicon.png + unlocked.png + histogram.png + waterfall.png + preset-load.png + preset-save.png + preset-update.png + preset-delete.png + horizontal.png + vertical.png + maxhold.png + grid.png + invertspectrum.png + preset-last.png + display1.png + display2.png + slopen_icon.png + slopep_icon.png + display1_w.png + display2_w.png + horizontal_w.png + vertical_w.png + current.png + slopeb_icon.png + clear.png + playloop.png + play.png + pause.png + stop.png + sdrangel_logo.png + sdrangel_icon.png + minus.png + plus.png + record_off.png + record_on.png + mem.png + minusw.png + plusw.png + mono.png + stereo.png + sound_off.png + sound_on.png + dsb.png + usb.png + flip_lr.png + flip_rl.png + carrier.png + rds.png + recycle.png + lsb.png + constellation.png + slot1_off.png + slot1_on.png + slot2_off.png + slot2_on.png + iambickey.png + morsekey.png + txoff.png + txon.png + arrow_down.png + arrow_up.png + film_reel.png + film.png + picture.png + camera.png + filter_bandpass.png + stream.png + antenna.png + link.png + choose.png + clocksource.png + flip_sidebands.png + filter_highpass.png + edit.png + listing.png + create.png + duplicate.png + bin.png + save.png + load.png + keyboard.png + kill.png + diff --git a/sdrgui/resources/sweep.png b/sdrgui/resources/sweep.png new file mode 100644 index 0000000000000000000000000000000000000000..4fa3db92c7e39b51bbecdac246567640566ec06e GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA)dl#3xB}__|Nk>HGn<>6A3S)F zpPzrhf(5f@&pvkSn1zLffPjFBiOHEWXXehG`{Kn59v+@^=gvh%MZJCdHaIvqGc&WY zvU16iB?<}(SFT*q)z#g%Z{PRt-=95urm3m9a^=d@)YRLzZ%ax_c6D`mdwVxFHcpx} zsjRGQPPM!)$rAI}NrjN7+LVz;yK_1sK`_6a#0 zq5kvQOgBXy@-xs;WBIUr=HzWE%O|_n-9At)c=AE1qQjH=%dAz88Rrx%zc>+SGlQqA KpUXO@geCxG9gdg) literal 0 HcmV?d00001 From 21aaeaedda8b8abc9f2a3371d4db3df2ea19c064 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Apr 2018 22:45:43 +0200 Subject: [PATCH 233/956] DSD demod: status text log dialog save to file --- .../demoddsd/dsdstatustextdialog.cpp | 32 +++++++++++++++++++ .../channelrx/demoddsd/dsdstatustextdialog.h | 1 + .../channelrx/demoddsd/dsdstatustextdialog.ui | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp index 47a843e8c..3b7e3d1da 100644 --- a/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include +#include DSDStatusTextDialog::DSDStatusTextDialog(QWidget* parent) : QDialog(parent), @@ -53,3 +56,32 @@ void DSDStatusTextDialog::on_clear_clicked() { ui->logEdit->clear(); } + +void DSDStatusTextDialog::on_saveLog_clicked() +{ + QString fileName = QFileDialog::getSaveFileName(this, + tr("Open log file"), ".", tr("Log files (*.log)")); + + if (fileName != "") + { + QFileInfo fileInfo(fileName); + + if (fileInfo.suffix() != "log") { + fileName += ".log"; + } + + QFile exportFile(fileName); + + if (exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QTextStream outstream(&exportFile); + outstream << ui->logEdit->toPlainText(); + exportFile.close(); + } + else + { + QMessageBox::information(this, tr("Message"), tr("Cannot open file for writing")); + } + } + +} diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.h b/plugins/channelrx/demoddsd/dsdstatustextdialog.h index f5d5a0718..23c18b1ae 100644 --- a/plugins/channelrx/demoddsd/dsdstatustextdialog.h +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.h @@ -39,6 +39,7 @@ private: private slots: void on_clear_clicked(); + void on_saveLog_clicked(); }; diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.ui b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui index 675e41047..69a45c4ca 100644 --- a/plugins/channelrx/demoddsd/dsdstatustextdialog.ui +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui @@ -64,7 +64,7 @@
    - + 24 From dd1686df9e46cb0b6a22377f39f02cb3dd81f469 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 1 Apr 2018 23:49:26 +0200 Subject: [PATCH 234/956] DSD demod: updated documentation and Debian changelog --- debian/changelog | 1 + doc/img/DSDdemod_plugin.png | Bin 129488 -> 130529 bytes doc/img/DSDdemod_plugin.xcf | Bin 316778 -> 334032 bytes doc/img/DSDdemod_plugin_status_text_log.png | Bin 0 -> 79936 bytes doc/img/DSDdemod_plugin_status_text_log.xcf | Bin 0 -> 209228 bytes plugins/channelrx/demoddsd/readme.md | 42 +++++++++++++++++--- 6 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 doc/img/DSDdemod_plugin_status_text_log.png create mode 100644 doc/img/DSDdemod_plugin_status_text_log.xcf diff --git a/debian/changelog b/debian/changelog index ef97c66af..d49c569fc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,7 @@ sdrangel (3.14.1-1) unstable; urgency=medium * NFM: fixed lowpass filter initialization (CTCSS) * DSD demod: set FM deviation independent from RF bandwidth * DSD demod: implemented DMR negative with DSDcc v1.7.5 + * DSD demod: implemented dialog to view the log of status text messages -- Edouard Griffiths, F4EXB Sun, 01 Apr 2018 12:14:18 +0200 diff --git a/doc/img/DSDdemod_plugin.png b/doc/img/DSDdemod_plugin.png index 0240aa518938ce8b6bac1b63050b4b7f313efe06..1e6d12cb229e81ab2148a87aadb6495f6b4c1919 100644 GIT binary patch literal 130529 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
    cT7%X5pu8}*@ru$jak+51pyO!Xk|B|~ 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 w{r2%+|MmaHoBjXwwEx%d@&6#Gddf=*-p~e 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
  • <%;=1?f8l;&@=WPlwQ@cf1 zMm2AYR#p>woIfVPf*JG2j?~uDTF~hrTmkqz-%6yr08L4Xe>Vi@_Z&Y>^{uI>U5Df%sc}!FMyCM;DQKH8ANmlyO41c9I zKfU1aJ75YyP?u~00%~b%yRPR|RJ!hG+I~=ktK#>*5`t}*p;PA;O7b`uL@?svYuKTb z2w?}FH-Mi|a=}la!Q7>w=p(hRZGr``S3CL;aPGo_VT$*aE0pZ#uslrRrTMx?#erY= z`S=DJyuG8MqNHRMxvr)6E@{Unz8)Ms&-TU;5@J7}Go#rZM|d{#o148&epRFiaCro0 zndAKKln?WTZ+nngseB16?5Ok@UX=5`GJbHZTw*%=opWjnEm+}5wet?nL9 zR&Tb2JtfMzTjA!mX}GqGi~jnpfg5gnr_2$h^5&}gM;~Y}>~=^*H|q8}IC<2(LQ4MI zX)M9@p67qz`Z={$5S88g`ds(xr|9^2X&W2%bjb+T7d~Vl!oPTN*SstNM0kOAk*^*Tq;h8+-eQ)YQJ0=f|XM9=#0k z&)xa<{Sh_YuY;y1t91L}81*B630f}09RGYR|W7Kmf-hCGBntBR!o9qzqjR#3F5jS*�B!Qv`c!$IG?=t{qJ@8=Y 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 delta 13999 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

    B&$MQZS~iC

    *HfbU^ck&Jdq%w8uwmzUi?(0tI(&cR z>2aq%vCeGVu%W(2Z!PnY67cdhJz~snIrl#@j@bX&p1Ab&ixZkUA{1@meWjATrggll zRIoH}(4Z6RMS4a)jh=Vo;p&)3C9-IgUdsj1sX>Exj@8;bSd7@$V&;x7_D}x4r9HY= zuc$e{TuB(xYtW!x9=(c2{(E@buFoj_i3Me9J{z-O&nls21(lp9hQ)k4oBOF=-{7VSHf?eMgHi?RV`v#Fe^sBws% zkd;g830XS8JlyP(rqM%Vd}E4+MzQLPEG>-s)Vjxa&ok?%(QY|9G}XGpcgHhp>QL7l zjY-??xxFZL&vs>d(azu<~n4Oz9 zAGmKz&Wp+aI(FrJ&V?K%)XP0r z9hcPqE1WqMYLFf5oN4DtPh zVawM@5Qd%q5{BHZ!f;;^#&Y~PUGEoRY|=^?&EE)P`%S_a`X>4acQ;|QJ`u*WCc>C*6~;XiFijZ0PZ!36=Y{dmR6G#I!*5|S)_}hUI((m_Ref&H z0SXM(o&${OJ1xeed5+_Dlw*5`Y5l)Vu=SI(un4QL*)7JH#j>m=*nnL)h&)`yLt#8- zL`Bp?OLRsbjKE~f!V+w-@UaUAk%y~zD2)7tl`*>_>Y*h%qYp-4GG<{3HeeSHA`e&b zP#AwSq9W>{B|4)IMqn~#VF@f7_}GPm$ir1U6vpF5R75?rL}&ED2u#K-EWrls!a?NW zDjo{s2_q_^9$KO^`d|bmV-}VOW2+MznAnAb$ir1Ut diff --git a/doc/img/DSDdemod_plugin_status_text_log.png b/doc/img/DSDdemod_plugin_status_text_log.png new file mode 100644 index 0000000000000000000000000000000000000000..890348a0845f810e518be9a2340a01489bcf8c4d GIT binary patch literal 79936 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 D0Uzxe literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_status_text_log.xcf b/doc/img/DSDdemod_plugin_status_text_log.xcf new file mode 100644 index 0000000000000000000000000000000000000000..7c1c193932662f19f0546483bde5ed41199d2c86 GIT binary patch literal 209228 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 U`Ztrmi|XyBw=N7%PPd2o9WbD;k^lez literal 0 HcmV?d00001 diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index 9eb14f29d..01d196659 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -68,31 +68,61 @@ Total power in dB relative to a +/- 1.0 amplitude signal received in the pass ba

    A.4: Channel power bar graph

    -

    A.5: Audio volume

    +

    A.5: Activate status text log

    + +Check to send the status text lines (A.12) to the log. Uncheck to dismiss sending. + +

    A.6 View status text log

    + +Click to open a dialog to view the status text lines log: + +![DSD Demodulator status text log GUI](../../../doc/img/DSDdemod_plugin_status_text_log.png) + +
    A.6.1 Clear log
    + +Push this button to clear the log + +
    A.6.2 Pin to last line
    + +Use this toggle to pin or unpin the log to the last line + +
    A.6.3 Save log to file
    + +Save the present log content to a file + +
    A.6.4 Timestamp
    + +Each line in the log starts with the timestamp when the status line was fetched from the decoder + +
    A.6.5 Status text
    + +One line per status text + +

    A.7: Audio volume

    When working with mbelib this is a linear multiplication factor. A value of zero triggers the auto gain feature. With the DV serial device(s) amplification factor in dB is given by `(value - 3.0)*5.0`. In most practical cases the middle value of 5.0 (+10 dB) is a comfortable level. -

    A.6: Squelch level

    +

    A.8: Squelch level

    The level corresponds to the channel power above which the squelch gate opens. -

    A.7: Squelch time gate

    +

    A.9: Squelch time gate

    Number of milliseconds following squelch gate opening after which the signal is actually fed to the decoder. 0 means no delay i.e. immediate feed. -

    A.8: High-pass filter for audio

    +

    A.10: High-pass filter for audio

    Use this switch to toggle high-pass filter on the audio -

    A.9: Audio mute, squelch indicator and select audio output device

    +

    A.11: Audio mute, squelch indicator and select audio output device

    Left click to mute/unmute audio. This button lights in green when the squelch opens. If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. -

    A.11: Format specific status display

    +

    A.12: Format specific status display

    When the display is active the background turns from the surrounding gray color to dark green. It shows informatory or status messages that are particular to each format. From 04d7e56f848d729620655421dbd683eaea5f9f08 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 2 Apr 2018 00:21:12 +0200 Subject: [PATCH 235/956] DSD demod: status text dialog: removed auto default on save button --- plugins/channelrx/demoddsd/dsdstatustextdialog.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.ui b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui index 69a45c4ca..9fc1c1954 100644 --- a/plugins/channelrx/demoddsd/dsdstatustextdialog.ui +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui @@ -81,6 +81,9 @@ :/save.png:/save.png + + false + From 5a5d9e497e54450ca2fc8c712b870f0ee2edf867 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 2 Apr 2018 00:35:56 +0200 Subject: [PATCH 236/956] DSD demod: Windows build --- plugins/channelrx/demoddsd/demoddsd.pro | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/channelrx/demoddsd/demoddsd.pro b/plugins/channelrx/demoddsd/demoddsd.pro index 07b8069ed..fb0c31393 100644 --- a/plugins/channelrx/demoddsd/demoddsd.pro +++ b/plugins/channelrx/demoddsd/demoddsd.pro @@ -44,16 +44,19 @@ dsddemod.cpp\ dsddemodgui.cpp\ dsddemodplugin.cpp\ dsddemodbaudrates.cpp\ -dsddemodsettings.cpp +dsddemodsettings.cpp\ +dsdstatustextdialog.cpp HEADERS = dsddecoder.h\ dsddemod.h\ dsddemodgui.h\ dsddemodplugin.h\ dsddemodbaudrates.h\ -dsddemodsettings.h +dsddemodsettings.h\ +dsdstatustextdialog.h -FORMS = dsddemodgui.ui +FORMS = dsddemodgui.ui\ +dsdstatustextdialog.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui From 62abc80a978baa55a88a95eec4d70e6307a35c54 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 2 Apr 2018 00:38:04 +0200 Subject: [PATCH 237/956] BladeRF: bumped version to v3.14.1 --- plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp | 2 +- plugins/samplesource/bladerfinput/bladerfinputplugin.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp index a8a484d99..9d1b2c6fe 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp @@ -26,7 +26,7 @@ const PluginDescriptor BladerfOutputPlugin::m_pluginDescriptor = { QString("BladeRF Output"), - QString("3.9.0"), + QString("3.14.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index 82c29131a..2cedb7e94 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { QString("BladeRF Input"), - QString("3.14.0"), + QString("3.14.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 306c09e1757312dbdfe44f3acd3aba70acbf17bf Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 2 Apr 2018 00:58:46 +0200 Subject: [PATCH 238/956] ScopeXY: moved the draw graticule position in drawing sequence so that it does not flicker --- sdrgui/dsp/scopevisxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdrgui/dsp/scopevisxy.cpp b/sdrgui/dsp/scopevisxy.cpp index f8599c070..2677d4690 100644 --- a/sdrgui/dsp/scopevisxy.cpp +++ b/sdrgui/dsp/scopevisxy.cpp @@ -81,11 +81,11 @@ void ScopeVisXY::feed(const SampleVector::const_iterator& cbegin, const SampleVe m_cols = cols; } - drawGraticule(); m_tvScreen->renderImage(0); m_tvScreen->update(); usleep(5000); m_tvScreen->resetImage(m_alphaReset); + drawGraticule(); m_pixelCount = 0; } } From af2e4864d0663d9459d1bb80116d38a3aa0ceb5d Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 3 Apr 2018 02:58:15 +0200 Subject: [PATCH 239/956] ScopeNG GUI: make clear the projection combo boxes are built by the GUI not in the UI form --- sdrgui/gui/glscopenggui.ui | 98 +++----------------------------------- 1 file changed, 6 insertions(+), 92 deletions(-) diff --git a/sdrgui/gui/glscopenggui.ui b/sdrgui/gui/glscopenggui.ui index 8c7dd0735..d09b03d22 100644 --- a/sdrgui/gui/glscopenggui.ui +++ b/sdrgui/gui/glscopenggui.ui @@ -646,16 +646,7 @@ kS/s 0 - - 0 - - - 0 - - - 0 - - + 0 @@ -720,32 +711,7 @@ kS/s - Real - - - - - Imag - - - - - Mag - - - - - MagdB - - - - - Phi - - - - - dPhi + TBD @@ -962,16 +928,7 @@ kS/s 2 - - 0 - - - 0 - - - 0 - - + 0 @@ -1296,16 +1253,7 @@ kS/s 0 - - 0 - - - 0 - - - 0 - - + 0 @@ -1370,32 +1318,7 @@ kS/s - Real - - - - - Imag - - - - - Mag - - - - - MagdB - - - - - Phi - - - - - dPhi + TBD @@ -1608,16 +1531,7 @@ kS/s 2 - - 0 - - - 0 - - - 0 - - + 0 From d26464fcd8a7d524443e6da7c733d74b823e3470 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 3 Apr 2018 02:59:36 +0200 Subject: [PATCH 240/956] Compile liquid-dsp from source internally (Linux) --- CMakeLists.txt | 1 + liquiddsp/CMakeLists.txt | 482 ++++++++++++++++++ liquiddsp/cmake/Modules/CheckCPUID.cmake | 49 ++ .../cmake/Modules/CheckRequiredFunction.cmake | 16 + .../cmake/Modules/LiquidBuildSamples.cmake | 12 + liquiddsp/cmake/Modules/cmcpuid.c | 309 +++++++++++ liquiddsp/cmake/Modules/cmcpuid2.c | 113 ++++ 7 files changed, 982 insertions(+) create mode 100644 liquiddsp/CMakeLists.txt create mode 100644 liquiddsp/cmake/Modules/CheckCPUID.cmake create mode 100644 liquiddsp/cmake/Modules/CheckRequiredFunction.cmake create mode 100644 liquiddsp/cmake/Modules/LiquidBuildSamples.cmake create mode 100644 liquiddsp/cmake/Modules/cmcpuid.c create mode 100644 liquiddsp/cmake/Modules/cmcpuid2.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 05203f365..544f8cec5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -218,6 +218,7 @@ add_subdirectory(httpserver) add_subdirectory(logging) add_subdirectory(qrtplib) add_subdirectory(swagger) +add_subdirectory(liquiddsp) include_directories( ${CMAKE_CURRENT_BINARY_DIR} diff --git a/liquiddsp/CMakeLists.txt b/liquiddsp/CMakeLists.txt new file mode 100644 index 000000000..2741e4a17 --- /dev/null +++ b/liquiddsp/CMakeLists.txt @@ -0,0 +1,482 @@ +project(liquiddsp) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") + +file(READ "${LIQUIDDSPSRC}/include/liquid.h" liquid_h) +string(REGEX MATCH "\\#define LIQUID_VERSION[ ]+\"([0-9]+\\.[0-9]+\\.[0-9]+)\"" LIQUID_VERSION_MATCHES "${liquid_h}") +if(NOT LIQUID_VERSION_MATCHES) + message(FATAL_ERROR "Failed to extract version number from liquid.h") +endif(NOT LIQUID_VERSION_MATCHES) +set(LIQUID_VERSION ${CMAKE_MATCH_1}) + +message("-- Configuring for liquid-dsp ${LIQUID_VERSION}") + +include(CheckCPUID) +include(CheckRequiredFunction) +include(CheckTypeSize) + +include_directories(${LIQUIDDSPSRC}) +include_directories(${LIQUIDDSPSRC}/include) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + +CHECK_INCLUDE_FILE(sys/resource.h HAVE_SYS_RESOURCE_H) + +CHECK_INCLUDE_FILE(complex.h HAVE_COMPLEX_H) +CHECK_INCLUDE_FILE(float.h HAVE_FLOAT_H) +CHECK_INCLUDE_FILE(getopt.h HAVE_GETOPT_H) +CHECK_INCLUDE_FILE(inttypes.h HAVE_INTTYPES_H) +CHECK_INCLUDE_FILE(memory.h HAVE_MEMORY_H) +CHECK_INCLUDE_FILE(unistd.h HAVE_UNISTD_H) + +CHECK_INCLUDE_FILE(mmintrin.h HAVE_MMINTRIN_H) +CHECK_INCLUDE_FILE(emmintrin.h HAVE_EMMINTRIN_H) +CHECK_INCLUDE_FILE(immintrin.h HAVE_IMMINTRIN_H) +CHECK_INCLUDE_FILE(pmmintrin.h HAVE_PMMINTRIN_H) +CHECK_INCLUDE_FILE(smmintrin.h HAVE_SMMINTRIN_H) +CHECK_INCLUDE_FILE(tmmintrin.h HAVE_TMMINTRIN_H) +CHECK_INCLUDE_FILE(xmmintrin.h HAVE_XMMINTRIN_H) + +CHECK_INCLUDE_FILE(fec.h HAVE_FEC_H) +CHECK_INCLUDE_FILE(fftw3.h HAVE_FFTW3_H) + +CHECK_TYPE_SIZE(int SIZEOF_INT) +CHECK_TYPE_SIZE("unsigned int" SIZEOF_UNSIGNED_INT) + +CHECK_REQUIRED_FUNCTION(sinf m HAVE_SINF) +CHECK_REQUIRED_FUNCTION(cosf m HAVE_COSF) +CHECK_REQUIRED_FUNCTION(expf m HAVE_EXPF) +CHECK_REQUIRED_FUNCTION(cargf m HAVE_CARGF) +CHECK_REQUIRED_FUNCTION(cexpf m HAVE_CEXPF) +CHECK_REQUIRED_FUNCTION(crealf m HAVE_CREALF) +CHECK_REQUIRED_FUNCTION(cimagf m HAVE_CIMAGF) +CHECK_REQUIRED_FUNCTION(sqrtf m HAVE_SQRTF) + +CHECK_LIBRARY_EXISTS(m sqrtf "" HAVE_LIBM) +if (HAVE_FEC_H) + CHECK_LIBRARY_EXISTS(fec create_viterbi27 "" HAVE_LIBFEC) + if (NOT HAVE_LIBFEC) + unset(HAVE_FEC_H CACHE) + endif () +endif () +if (HAVE_FFTW3_H) + CHECK_LIBRARY_EXISTS(fftw3f fftwf_plan_dft_1d "" HAVE_LIBFFTW3F) + if (NOT HAVE_LIBFFTW3F) + unset(HAVE_FFTW3_H CACHE) + endif () +endif () + +CHECK_CPUID("mmx" HAVE_MMX ${LIQUID_FORCE_X86_MMX}) +CHECK_CPUID("sse" HAVE_SSE ${LIQUID_FORCE_X86_SSE}) +CHECK_CPUID("sse2" HAVE_SSE2 ${LIQUID_FORCE_X86_SSE2}) +CHECK_CPUID("sse3" HAVE_SSE3 ${LIQUID_FORCE_X86_SSE3}) +CHECK_CPUID("ssse3" HAVE_SSSE3 ${LIQUID_FORCE_X86_SSSE3}) +CHECK_CPUID("sse41" HAVE_SSE41 ${LIQUID_FORCE_X86_SSE41}) +CHECK_CPUID("sse42" HAVE_SSE42 ${LIQUID_FORCE_X86_SSE42}) +CHECK_CPUID("avx" HAVE_AVX ${LIQUID_FORCE_X86_AVX}) +CHECK_CPUID("vmx" HAVE_ALTIVEC ${LIQUID_FORCE_PPC_ALTIVEC}) +CHECK_CPUID("neon" HAVE_NEON ${LIQUID_FORCE_ARM_NEON}) +CHECK_CPUID("neon64" HAVE_NEON64 ${LIQUID_FORCE_ARM64_NEON}) + +# +# Build compilation extra flags +# +if (CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") + if (HAVE_AVX) + set(_EXTRA_C_FLAGS "-mavx") + elseif (HAVE_SSE42) + set(_EXTRA_C_FLAGS "-msse4.2") + elseif (HAVE_SSE41) + set(_EXTRA_C_FLAGS "-msse4.1") + elseif (HAVE_SSSE3) + set(_EXTRA_C_FLAGS "-mssse3") + elseif (HAVE_SSE3) + set(_EXTRA_C_FLAGS "-msse3") + elseif (HAVE_SSE2) + set(_EXTRA_C_FLAGS "-msse2") + elseif (HAVE_SSE) + set(_EXTRA_C_FLAGS "-msse") + elseif (HAVE_MMX) + set(_EXTRA_C_FLAGS "-mmmx") + elseif (HAVE_ALTIVEC) + if (CMAKE_SYSTEM_NAME MATCHES "Darwin") + set(_EXTRA_C_FLAGS "-fno-common -faltivec") + else () + set(_EXTRA_C_FLAGS "-maltivec") + endif () + elseif (HAVE_NEON) + set(_EXTRA_C_FLAGS "-mfpu=neon-vfpv4") + elseif (HAVE_NEON64) + # No extra flags needed + endif () + set(_EXTRA_C_FLAGS "${_EXTRA_C_FLAGS} -ffast-math") +endif () + +# +# MODULE : agc - automatic gain control +# + +set(agc_SOURCES + ${LIQUIDDSPSRC}/src/agc/src/agc_crcf.c + ${LIQUIDDSPSRC}/src/agc/src/agc_rrrf.c + ) + +# +# MODULE : audio +# + +set(audio_SOURCES + ${LIQUIDDSPSRC}/src/audio/src/cvsd.c + ) + +# +# MODULE : buffer +# + +set(buffer_SOURCES + ${LIQUIDDSPSRC}/src/buffer/src/bufferf.c + ${LIQUIDDSPSRC}/src/buffer/src/buffercf.c + ) + +# +# MODULE : channel +# + +set(channel_SOURCES + ${LIQUIDDSPSRC}/src/channel/src/channel_cccf.c + ) + +# +# MODULE : dotprod +# + +set(dotprod_C_SOURCES + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_cccf.c + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_crcf.c + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_rrrf.c + ${LIQUIDDSPSRC}/src/dotprod/src/sumsq.c + ) + +# PowerPC AltiVec +set(dotprod_ALTIVEC_SOURCES + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_cccf.c + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_crcf.av.c + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_rrrf.av.c + ${LIQUIDDSPSRC}/src/dotprod/src/sumsq.c + ) + +# Intel MMX/SSE/AVX +set(dotprod_SSE_SOURCES + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_cccf.mmx.c + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_crcf.mmx.c + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_rrrf.mmx.c + ${LIQUIDDSPSRC}/src/dotprod/src/sumsq.mmx.c + ) + +# ARM NEON +set(dotprod_NEON_SOURCES + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_cccf.neon.c + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_crcf.neon.c + ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_rrrf.neon.c + ${LIQUIDDSPSRC}/src/dotprod/src/sumsq.c + ) + +if (LIQUID_SIMDOVERRIDE) + set(dotprod_SOURCES ${dotprod_C_SOURCES}) +elseif (HAVE_SSE41 AND HAVE_SMMINTRIN_H) + set(dotprod_SOURCES ${dotprod_SSE_SOURCES}) +elseif (HAVE_SSE3 AND HAVE_PMMINTRIN_H) + set(dotprod_SOURCES ${dotprod_SSE_SOURCES}) +elseif (HAVE_SSE2 AND HAVE_EMMINTRIN_H) + unset(HAVE_PMMINTRIN_H CACHE) # Unset otherwise SSE3 code kicks in. + set(dotprod_SOURCES ${dotprod_SSE_SOURCES}) +elseif (HAVE_ALTIVEC) + set(dotprod_SOURCES ${dotprod_ALTIVEC_SOURCES}) +elseif (HAVE_NEON OR HAVE_NEON64) + set(dotprod_SOURCES ${dotprod_NEON_SOURCES}) +else () + set(dotprod_SOURCES ${dotprod_C_SOURCES}) +endif () + +# +# MODULE : equalization +# + +set(equalization_SOURCES + ${LIQUIDDSPSRC}/src/equalization/src/equalizer_cccf.c + ${LIQUIDDSPSRC}/src/equalization/src/equalizer_rrrf.c + ) + +# +# MODULE : fec - forward error-correction +# +set(fec_SOURCES + ${LIQUIDDSPSRC}/src/fec/src/crc.c + ${LIQUIDDSPSRC}/src/fec/src/fec.c + ${LIQUIDDSPSRC}/src/fec/src/fec_conv.c + ${LIQUIDDSPSRC}/src/fec/src/fec_conv_poly.c + ${LIQUIDDSPSRC}/src/fec/src/fec_conv_pmatrix.c + ${LIQUIDDSPSRC}/src/fec/src/fec_conv_punctured.c + ${LIQUIDDSPSRC}/src/fec/src/fec_golay2412.c + ${LIQUIDDSPSRC}/src/fec/src/fec_hamming74.c + ${LIQUIDDSPSRC}/src/fec/src/fec_hamming84.c + ${LIQUIDDSPSRC}/src/fec/src/fec_hamming128.c + ${LIQUIDDSPSRC}/src/fec/src/fec_hamming1511.c + ${LIQUIDDSPSRC}/src/fec/src/fec_hamming3126.c + ${LIQUIDDSPSRC}/src/fec/src/fec_hamming128_gentab.c + ${LIQUIDDSPSRC}/src/fec/src/fec_pass.c + ${LIQUIDDSPSRC}/src/fec/src/fec_rep3.c + ${LIQUIDDSPSRC}/src/fec/src/fec_rep5.c + ${LIQUIDDSPSRC}/src/fec/src/fec_rs.c + ${LIQUIDDSPSRC}/src/fec/src/fec_secded2216.c + ${LIQUIDDSPSRC}/src/fec/src/fec_secded3932.c + ${LIQUIDDSPSRC}/src/fec/src/fec_secded7264.c + ${LIQUIDDSPSRC}/src/fec/src/interleaver.c + ${LIQUIDDSPSRC}/src/fec/src/packetizer.c + ${LIQUIDDSPSRC}/src/fec/src/sumproduct.c + ) + +# +# MODULE : fft - fast Fourier transforms, discrete sine/cosine transforms, etc. +# +set(fft_SOURCES + ${LIQUIDDSPSRC}/src/fft/src/fftf.c + ${LIQUIDDSPSRC}/src/fft/src/spgramcf.c + ${LIQUIDDSPSRC}/src/fft/src/spgramf.c + ${LIQUIDDSPSRC}/src/fft/src/fft_utilities.c + ) + +# +# MODULE : filter +# +set(filter_SOURCES + ${LIQUIDDSPSRC}/src/filter/src/bessel.c + ${LIQUIDDSPSRC}/src/filter/src/butter.c + ${LIQUIDDSPSRC}/src/filter/src/cheby1.c + ${LIQUIDDSPSRC}/src/filter/src/cheby2.c + ${LIQUIDDSPSRC}/src/filter/src/ellip.c + ${LIQUIDDSPSRC}/src/filter/src/filter_rrrf.c + ${LIQUIDDSPSRC}/src/filter/src/filter_crcf.c + ${LIQUIDDSPSRC}/src/filter/src/filter_cccf.c + ${LIQUIDDSPSRC}/src/filter/src/firdes.c + ${LIQUIDDSPSRC}/src/filter/src/firdespm.c + ${LIQUIDDSPSRC}/src/filter/src/fnyquist.c + ${LIQUIDDSPSRC}/src/filter/src/gmsk.c + ${LIQUIDDSPSRC}/src/filter/src/group_delay.c + ${LIQUIDDSPSRC}/src/filter/src/hM3.c + ${LIQUIDDSPSRC}/src/filter/src/iirdes.pll.c + ${LIQUIDDSPSRC}/src/filter/src/iirdes.c + ${LIQUIDDSPSRC}/src/filter/src/lpc.c + ${LIQUIDDSPSRC}/src/filter/src/rcos.c + ${LIQUIDDSPSRC}/src/filter/src/rkaiser.c + ${LIQUIDDSPSRC}/src/filter/src/rrcos.c + ) + +# +# MODULE : framing +# +set(framing_SOURCES + ${LIQUIDDSPSRC}/src/framing/src/bpacketgen.c + ${LIQUIDDSPSRC}/src/framing/src/bpacketsync.c + ${LIQUIDDSPSRC}/src/framing/src/bpresync_cccf.c + ${LIQUIDDSPSRC}/src/framing/src/bsync_rrrf.c + ${LIQUIDDSPSRC}/src/framing/src/bsync_crcf.c + ${LIQUIDDSPSRC}/src/framing/src/bsync_cccf.c + ${LIQUIDDSPSRC}/src/framing/src/detector_cccf.c + ${LIQUIDDSPSRC}/src/framing/src/framedatastats.c + ${LIQUIDDSPSRC}/src/framing/src/framesyncstats.c + ${LIQUIDDSPSRC}/src/framing/src/framegen64.c + ${LIQUIDDSPSRC}/src/framing/src/framesync64.c + ${LIQUIDDSPSRC}/src/framing/src/flexframegen.c + ${LIQUIDDSPSRC}/src/framing/src/flexframesync.c + ${LIQUIDDSPSRC}/src/framing/src/gmskframegen.c + ${LIQUIDDSPSRC}/src/framing/src/gmskframesync.c + ${LIQUIDDSPSRC}/src/framing/src/msourcecf.c + ${LIQUIDDSPSRC}/src/framing/src/ofdmflexframegen.c + ${LIQUIDDSPSRC}/src/framing/src/ofdmflexframesync.c + ${LIQUIDDSPSRC}/src/framing/src/presync_cccf.c + ${LIQUIDDSPSRC}/src/framing/src/symstreamcf.c + ${LIQUIDDSPSRC}/src/framing/src/symtrack_cccf.c + ${LIQUIDDSPSRC}/src/framing/src/qdetector_cccf.c + ${LIQUIDDSPSRC}/src/framing/src/qpacketmodem.c + ${LIQUIDDSPSRC}/src/framing/src/qpilotgen.c + ${LIQUIDDSPSRC}/src/framing/src/qpilotsync.c + ) + +# +# MODULE : math +# +set(math_SOURCES + ${LIQUIDDSPSRC}/src/math/src/poly.c + ${LIQUIDDSPSRC}/src/math/src/polyc.c + ${LIQUIDDSPSRC}/src/math/src/polyf.c + ${LIQUIDDSPSRC}/src/math/src/polycf.c + ${LIQUIDDSPSRC}/src/math/src/math.c + ${LIQUIDDSPSRC}/src/math/src/math.bessel.c + ${LIQUIDDSPSRC}/src/math/src/math.gamma.c + ${LIQUIDDSPSRC}/src/math/src/math.complex.c + ${LIQUIDDSPSRC}/src/math/src/math.trig.c + ${LIQUIDDSPSRC}/src/math/src/modular_arithmetic.c + ) + +# +# MODULE : matrix +# +set(matrix_SOURCES + ${LIQUIDDSPSRC}/src/matrix/src/matrix.c + ${LIQUIDDSPSRC}/src/matrix/src/matrixf.c + ${LIQUIDDSPSRC}/src/matrix/src/matrixc.c + ${LIQUIDDSPSRC}/src/matrix/src/matrixcf.c + ${LIQUIDDSPSRC}/src/matrix/src/smatrix.common.c + ${LIQUIDDSPSRC}/src/matrix/src/smatrixb.c + ${LIQUIDDSPSRC}/src/matrix/src/smatrixf.c + ${LIQUIDDSPSRC}/src/matrix/src/smatrixi.c + ) + +# +# MODULE : modem +# +set(modem_SOURCES + ${LIQUIDDSPSRC}/src/modem/src/ampmodem.c + ${LIQUIDDSPSRC}/src/modem/src/cpfskdem.c + ${LIQUIDDSPSRC}/src/modem/src/cpfskmod.c + ${LIQUIDDSPSRC}/src/modem/src/fskdem.c + ${LIQUIDDSPSRC}/src/modem/src/fskmod.c + ${LIQUIDDSPSRC}/src/modem/src/gmskdem.c + ${LIQUIDDSPSRC}/src/modem/src/gmskmod.c + ${LIQUIDDSPSRC}/src/modem/src/modemf.c + ${LIQUIDDSPSRC}/src/modem/src/modem_utilities.c + ${LIQUIDDSPSRC}/src/modem/src/modem_apsk_const.c + ${LIQUIDDSPSRC}/src/modem/src/modem_arb_const.c + ) + +# +# MODULE : multichannel +# +set(multichannel_SOURCES + ${LIQUIDDSPSRC}/src/multichannel/src/firpfbch_crcf.c + ${LIQUIDDSPSRC}/src/multichannel/src/firpfbch_cccf.c + ${LIQUIDDSPSRC}/src/multichannel/src/ofdmframe.common.c + ${LIQUIDDSPSRC}/src/multichannel/src/ofdmframegen.c + ${LIQUIDDSPSRC}/src/multichannel/src/ofdmframesync.c + ) + +# +# MODULE : nco - numerically-controlled oscillator +# +set(nco_SOURCES + ${LIQUIDDSPSRC}/src/nco/src/nco_crcf.c + ${LIQUIDDSPSRC}/src/nco/src/nco.utilities.c + ) + +# +# MODULE : optim - optimization +# +set(optim_SOURCES + ${LIQUIDDSPSRC}/src/optim/src/chromosome.c + ${LIQUIDDSPSRC}/src/optim/src/gasearch.c + ${LIQUIDDSPSRC}/src/optim/src/gradsearch.c + ${LIQUIDDSPSRC}/src/optim/src/optim.common.c + ${LIQUIDDSPSRC}/src/optim/src/qnsearch.c + ${LIQUIDDSPSRC}/src/optim/src/utilities.c + ) + +# +# MODULE : quantization +# +set(quantization_SOURCES + ${LIQUIDDSPSRC}/src/quantization/src/compand.c + ${LIQUIDDSPSRC}/src/quantization/src/quantizercf.c + ${LIQUIDDSPSRC}/src/quantization/src/quantizerf.c + ${LIQUIDDSPSRC}/src/quantization/src/quantizer.inline.c + ) + +# +# MODULE : random +# +set(random_SOURCES + ${LIQUIDDSPSRC}/src/random/src/rand.c + ${LIQUIDDSPSRC}/src/random/src/randn.c + ${LIQUIDDSPSRC}/src/random/src/randexp.c + ${LIQUIDDSPSRC}/src/random/src/randweib.c + ${LIQUIDDSPSRC}/src/random/src/randgamma.c + ${LIQUIDDSPSRC}/src/random/src/randnakm.c + ${LIQUIDDSPSRC}/src/random/src/randricek.c + ${LIQUIDDSPSRC}/src/random/src/scramble.c + ) + +# +# MODULE : sequence +# +set(sequence_SOURCES + ${LIQUIDDSPSRC}/src/sequence/src/bsequence.c + ${LIQUIDDSPSRC}/src/sequence/src/msequence.c + ) + +# +# MODULE : utility +# +set(utility_SOURCES + ${LIQUIDDSPSRC}/src/utility/src/bshift_array.c + ${LIQUIDDSPSRC}/src/utility/src/byte_utilities.c + ${LIQUIDDSPSRC}/src/utility/src/msb_index.c + ${LIQUIDDSPSRC}/src/utility/src/pack_bytes.c + ${LIQUIDDSPSRC}/src/utility/src/shift_array.c + ) + +# +# MODULE : vector +# +set(vector_SOURCES + ${LIQUIDDSPSRC}/src/vector/src/vectorf_add.port.c + ${LIQUIDDSPSRC}/src/vector/src/vectorf_norm.port.c + ${LIQUIDDSPSRC}/src/vector/src/vectorf_mul.port.c + ${LIQUIDDSPSRC}/src/vector/src/vectorf_trig.port.c + ${LIQUIDDSPSRC}/src/vector/src/vectorcf_add.port.c + ${LIQUIDDSPSRC}/src/vector/src/vectorcf_norm.port.c + ${LIQUIDDSPSRC}/src/vector/src/vectorcf_mul.port.c + ${LIQUIDDSPSRC}/src/vector/src/vectorcf_trig.port.c + ) + +# +# Library +# +set(liquid_SOURCES + ${LIQUIDDSPSRC}/src/libliquid.c + ${agc_SOURCES} + ${audio_SOURCES} + ${buffer_SOURCES} + ${channel_SOURCES} + ${dotprod_SOURCES} + ${equalization_SOURCES} + ${fec_SOURCES} + ${fft_SOURCES} + ${filter_SOURCES} + ${framing_SOURCES} + ${math_SOURCES} + ${matrix_SOURCES} + ${modem_SOURCES} + ${multichannel_SOURCES} + ${nco_SOURCES} + ${optim_SOURCES} + ${quantization_SOURCES} + ${random_SOURCES} + ${sequence_SOURCES} + ${utility_SOURCES} + ${vector_SOURCES} + ) + +add_library(liquid SHARED ${liquid_SOURCES}) +if (HAVE_LIBM) + target_link_libraries(liquid m) +endif () +if (HAVE_LIBFEC) + target_link_libraries(liquid fec) +endif () +if (NOT LIQUID_FFTOVERRIDE AND HAVE_LIBFFTW3F) + target_link_libraries(liquid fftw3f) +endif () + +set_property(TARGET liquid PROPERTY OUTPUT_NAME "liquid") +set_property(TARGET liquid PROPERTY SOVERSION "${LIQUID_VERSION}") +set_property(TARGET liquid PROPERTY COMPILE_FLAGS "${_EXTRA_C_FLAGS}") diff --git a/liquiddsp/cmake/Modules/CheckCPUID.cmake b/liquiddsp/cmake/Modules/CheckCPUID.cmake new file mode 100644 index 000000000..139c186b3 --- /dev/null +++ b/liquiddsp/cmake/Modules/CheckCPUID.cmake @@ -0,0 +1,49 @@ +# example usage: CHECK_CPUID("mmx" HAVE_MMX [TRUE|FALSE]) +macro(CHECK_CPUID CHARACTERISTIC VARIABLE) + if (${ARGC} GREATER 2) + set(_CPUID_FORCE ${ARGV2}) + else () + unset(_CPUID_FORCE) + endif () + + message("-- Checking for CPU characteristic ${CHARACTERISTIC}") + + if (DEFINED _CPUID_FORCE AND _CPUID_FORCE) + set(_CPUID_CHARACTERISTIC_FOUND ${_CPUID_FORCE}) + set(_FORCED "(forced)") + else () + if (CMAKE_CROSSCOMPILING) + # When cross compiling, we need to test each characteristic. + try_compile(CPUID_COMPILE_RESULT + ${CMAKE_CURRENT_BINARY_DIR} + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cmcpuid.c + COMPILE_DEFINITIONS "-DCROSS_COMPILING" "-DTEST_${CHARACTERISTIC}" + OUTPUT_VARIABLE CPUID_COMPILE_OUTPUT + ) + set(_CPUID_CHARACTERISTIC_FOUND ${CPUID_COMPILE_RESULT}) + else () + if (NOT _CPUID_CHARACTERISTICS) + try_run(CPUID_RUN_RESULT CPUID_COMPILE_RESULT + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cmcpuid.c + RUN_OUTPUT_VARIABLE CPUID_CHARACTERISTICS + ) + set(_CPUID_CHARACTERISTICS "${CPUID_CHARACTERISTICS}" CACHE INTERNAL "CPU Characteristics") + endif () + if (${_CPUID_CHARACTERISTICS} MATCHES "@${CHARACTERISTIC}@") + set(_CPUID_CHARACTERISTIC_FOUND TRUE) + else () + set(_CPUID_CHARACTERISTIC_FOUND FALSE) + endif () + endif () + unset(_FORCED) + endif () + + if (_CPUID_CHARACTERISTIC_FOUND) + message("-- Checking for CPU characteristic ${CHARACTERISTIC} - found ${_FORCED}") + else () + message("-- Checking for CPU characteristic ${CHARACTERISTIC} - not found") + endif () + set(${VARIABLE} ${_CPUID_CHARACTERISTIC_FOUND} CACHE INTERNAL "Check for CPU characteristic ${CHARACTERISTIC}" FORCE) +endmacro() + diff --git a/liquiddsp/cmake/Modules/CheckRequiredFunction.cmake b/liquiddsp/cmake/Modules/CheckRequiredFunction.cmake new file mode 100644 index 000000000..33f846a55 --- /dev/null +++ b/liquiddsp/cmake/Modules/CheckRequiredFunction.cmake @@ -0,0 +1,16 @@ +include(CheckFunctionExists) +include(CheckLibraryExists) + +macro(CHECK_REQUIRED_FUNCTION FUNCTION LIBRARY VARIABLE) + # First try without any library. + CHECK_FUNCTION_EXISTS("${FUNCTION}" ${VARIABLE}) + if (NOT ${VARIABLE}) + unset(${VARIABLE} CACHE) + # Retry with the library specified + CHECK_LIBRARY_EXISTS("${LIBRARY}" "${FUNCTION}" "" ${VARIABLE}) + endif () + if (NOT ${VARIABLE}) + message(FATAL_ERROR "Required function '${FUNCTION}' not found") + endif () +endmacro () + diff --git a/liquiddsp/cmake/Modules/LiquidBuildSamples.cmake b/liquiddsp/cmake/Modules/LiquidBuildSamples.cmake new file mode 100644 index 000000000..b64cf2663 --- /dev/null +++ b/liquiddsp/cmake/Modules/LiquidBuildSamples.cmake @@ -0,0 +1,12 @@ +function(LIQUID_BUILD_SAMPLES) + cmake_parse_arguments(LBS "" "SUITE" "SAMPLES" ${ARGN}) + if (NOT DEFINED LBS_SUITE) + message(FATAL_ERROR "Need to specify SUITE ") + endif () + foreach(_sample IN ITEMS ${LBS_SAMPLES}) + add_executable("${LBS_SUITE}_${_sample}" "${_sample}.c") + target_link_libraries("${LBS_SUITE}_${_sample}" ${LIQUID_LIBRARY}) + set_property(TARGET "${LBS_SUITE}_${_sample}" PROPERTY OUTPUT_NAME "${_sample}") + endforeach() +endfunction() + diff --git a/liquiddsp/cmake/Modules/cmcpuid.c b/liquiddsp/cmake/Modules/cmcpuid.c new file mode 100644 index 000000000..a2b22c83c --- /dev/null +++ b/liquiddsp/cmake/Modules/cmcpuid.c @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2016-present Orlando Bassotto + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CROSS_COMPILING +/* + * This is executed when targeting the host. + */ +#include +#include +#include +#if defined(_MSC_VER) +#include +#endif + +#define ISA_X86_MMX (1 << 0) +#define ISA_X86_SSE (1 << 1) +#define ISA_X86_SSE2 (1 << 2) +#define ISA_X86_SSE3 (1 << 3) +#define ISA_X86_SSSE3 (1 << 4) +#define ISA_X86_SSE41 (1 << 5) +#define ISA_X86_SSE42 (1 << 6) +#define ISA_X86_AVX1 (1 << 7) +#define ISA_X86_AVX2 (1 << 8) +#define ISA_PPC_VMX (1 << 9) +#define ISA_ARM_NEON (1 << 10) +#define ISA_ARM64_NEON (1 << 11) + +#if !(defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_AMD64)) +static jmp_buf return_jmp; + +static void sighandler(int signo) +{ + longjmp(return_jmp, 1); +} +#endif + +static void +siginit(void) +{ +#if !(defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_AMD64)) + signal(SIGSEGV, sighandler); +#ifdef SIGBUS + signal(SIGBUS, sighandler); +#endif + signal(SIGILL, sighandler); +#endif +} + +static unsigned +check_isa(void) +{ +#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_AMD64) +#if defined(_M_IX86) || defined(_M_AMD64) + int regs[4]; +#endif + unsigned eax, ebx, ecx, edx; + int supported; + unsigned isa = 0; + +#if defined(_M_IX86) || defined(_M_AMD64) + __cpuid(regs, 1); + ecx = regs[2]; + edx = regs[3]; +#else + eax = 0x00000001; + __asm__ __volatile__( + "cpuid" + :"=c"(ecx) // %ecx contains large feature flag set + :"0"(eax) // call with 0x1 + :"%eax","%ebx","%edx" + ); +#endif + + if (edx & (1 << 23)) { + /* MMX */ + isa |= ISA_X86_MMX; + } + + if (edx & (1 << 25)) { + /* SSE */ + isa |= ISA_X86_SSE; + } + + if (edx & (1 << 26)) { + /* SSE */ + isa |= ISA_X86_SSE2; + } + + if (ecx & (1 << 0)) { + /* SSE3 */ + isa |= ISA_X86_SSE3; + } + + if (ecx & (1 << 9)) { + /* SSSE3 */ + isa |= ISA_X86_SSSE3; + } + + if (ecx & (1 << 19)) { + /* SSE4.1 */ + isa |= ISA_X86_SSE41; + } + + if (ecx & (1 << 20)) { + /* SSE4.2 */ + isa |= ISA_X86_SSE42; + } + + if (ecx & (1 << 28)) { + /* AVX1 */ + isa |= ISA_X86_AVX1; + +#if defined(_M_IX86) || defined(_M_AMD64) + __cpuid(regs, 7); + ebx = regs[1]; +#else + eax = 0x00000007; + __asm__ __volatile__( + "cpuid" + : "=b"(ebx) + : "0"(eax) + : "%eax", "%ecx", "%edx" + ); +#endif + + if (ebx & (1 << 5)) { + /* AVX2 */ + isa |= ISA_X86_AVX2; + } + } + + return isa; +#elif defined(__powerpc__) || defined(__powerpc64__) || defined(__powerpc64le__) + if (setjmp(return_jmp) == 1) + return 0; + + __asm__ __volatile__(".long 0x10011000"); + + return ISA_PPC_VMX; +#elif defined(__arm__) || defined(_M_ARM) + /* Windows on ARM is always Thumb-2, and NEON is always available. */ +#if !defined(_M_ARM) + if (setjmp(return_jmp) == 1) + return 0; + +#if defined(__thumb__) + __asm__ __volatile__(".short 0xef12"); + __asm__ __volatile__(".short 0x0054"); +#else + __asm__ __volatile__(".word 0xf2120054"); +#endif +#endif + + return ISA_ARM_NEON; +#elif defined(__aarch64__) || defined(__arm64__) + return ISA_ARM64_NEON; +#else + return 0; +#endif +} + +int +main() +{ + unsigned isa; + + siginit(); + isa = check_isa(); + + if (isa & ISA_X86_MMX) { + printf("@mmx@"); + } + if (isa & ISA_X86_SSE) { + printf("@sse@"); + } + if (isa & ISA_X86_SSE2) { + printf("@sse2@"); + } + if (isa & ISA_X86_SSE3) { + printf("@sse3@"); + } + if (isa & ISA_X86_SSSE3) { + printf("@ssse3@"); + } + if (isa & ISA_X86_SSE41) { + printf("@sse41@"); + } + if (isa & ISA_X86_SSE42) { + printf("@sse42@"); + } + if (isa & ISA_X86_AVX1) { + printf("@avx@"); + } + if (isa & ISA_X86_AVX2) { + printf("@avx2@"); + } + if (isa & ISA_PPC_VMX) { + printf("@vmx@"); + } + if (isa & ISA_ARM_NEON) { + printf("@neon@"); + } + if (isa & ISA_ARM64_NEON) { + printf("@neon64@"); + } + printf("\n"); + return 0; +} +#else +/* + * This is just compiled when targeting an architecture/system different + * than the host, it is just compiled hence the checks are on the compiler + * features available and not on the real CPU characteristics, as such these + * tests may not do what you want, you may need to force the characteristics + * in that case. + * + * A define TEST_xyz must be defined in order to check the support for the + * specified characteristic. + */ + +#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_AMD64) +#define _X86 +#endif + +#if defined(__powerpc__) || defined(__powerpc64__) || defined(__powerpc64le__) +#define _PPC +#endif + +#if defined(__arm__) +#define _ARM +#endif + +#if defined(__aarch64__) || defined(__arm64__) +#define _ARM64 +#endif + +#if defined(TEST_mmx) && !(defined(_X86) && defined(__MMX__)) +#error "MMX not available" +#endif + +#if defined(TEST_sse) && !(defined(_X86) && defined(__SSE__)) +#error "SSE not available" +#endif + +#if defined(TEST_sse2) && !(defined(_X86) && defined(__SSE2__)) +#error "SSE2 not available" +#endif + +#if defined(TEST_sse3) && !(defined(_X86) && defined(__SSE3__)) +#error "SSE3 not available" +#endif + +#if defined(TEST_ssse3) && !(defined(_X86) && defined(__SSSE3__)) +#error "SSSE3 not available" +#endif + +#if defined(TEST_sse41) && !(defined(_X86) && defined(__SSE4_1__)) +#error "SSE4.1 not available" +#endif + +#if defined(TEST_sse42) && !(defined(_X86) && defined(__SSE4_2__)) +#error "SSE4.2 not available" +#endif + +#if defined(TEST_avx) && !(defined(_X86) && defined(__AVX__)) +#error "AVX1 not available" +#endif + +#if defined(TEST_avx2) && !(defined(_X86) && defined(__AVX2__)) +#error "AVX2 not available" +#endif + +#if defined(TEST_vmx) && !(defined(_PPC) && defined(__ALTIVEC__)) +#error "VMX not available" +#endif + +#if defined(TEST_neon) && !(defined(_ARM) && defined(__ARM_NEON__)) +#error "NEON not available" +#endif + +#if defined(TEST_neon64) && !defined(_ARM64) +#error "NEON64 not available" +#endif + +int main() +{ + return 0; +} +#endif + diff --git a/liquiddsp/cmake/Modules/cmcpuid2.c b/liquiddsp/cmake/Modules/cmcpuid2.c new file mode 100644 index 000000000..b6266bd61 --- /dev/null +++ b/liquiddsp/cmake/Modules/cmcpuid2.c @@ -0,0 +1,113 @@ +/** + * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ + +#include +#include +#include +#include + + +/** + * The supported instruction set on this machine for use in other functions. + * + * These must match up with the numbers used in MultiLib.cmake. + */ +typedef enum instructions_available_e +{ + supports_STANDARD = 1, + supports_SSE41 = 2, + supports_SSE42 = 3, + supports_AVX1 = 4, + supports_AVX2 = 5 +} instructions_available; + + +instructions_available getSupportedInstructionSet() { + + // probes of cpuid with %eax=0000_0001h + enum instructions_available_eax0000_0001h_e + { + probe01_SSE_3 = 1<<0, + probe01_SSE_4_1 = 1<<19, + probe01_SSE_4_2 = 1<<20, + probe01_AVX1 = 1<<28 + }; + + // probes of cpuid with %eax=0000_0007h + enum instructions_available_eax0000_0007h_e + { + probe07_AVX2 = 1<<5 + }; + + // the eax register + int32_t EAX; + // contends of the returned register + int32_t supported; + + // Call cpuid with eax=0x00000001 and get ecx + EAX = 0x00000001; + __asm__("cpuid" + :"=c"(supported) // %ecx contains large feature flag set + :"0"(EAX) // call with 0x1 + :"%eax","%ebx","%edx"); // clobbered + + if(supported & probe01_AVX1) // we have at least AVX1 + { + EAX = 0x00000007; + __asm__("cpuid" + :"=b"(supported) // %ebx contains feature flag AVX2 + :"0"(EAX) // call with 0x7 + :"%eax","%ecx","%edx"); // clobbered + + if(supported & probe07_AVX2) // we have at least AVX2 + { + printf("AVX2 SUPPORTED\n"); + return supports_AVX2; + } + printf("AVX1 SUPPORTED\n"); + return supports_AVX1; + } + else if(supported & probe01_SSE_4_1) // we have at least SSE4.1 + { + printf("SSE4.2 SUPPORTED\n"); + return supports_SSE42; + } + else // we have nothing specifically useful! + { + printf("STANDARD SUPPORTED\n"); + return supports_STANDARD; + } +} + + +int main(void) +{ + instructions_available ia; + ia = getSupportedInstructionSet(); + + switch(ia) + { + case supports_AVX2: + printf("AVX2\n"); + break; + case supports_AVX1: + printf("AVX1\n"); + break; + case supports_SSE42: + printf("SSE42\n"); + break; + case supports_SSE41: + printf("SSE41\n"); + break; + case supports_STANDARD: + printf("STANDARD\n"); + break; + default: + printf("Failed to find supported instruction set, this is an error!\n"); + exit(-1); + } + return ia; +} \ No newline at end of file From b856bc2aac346d187dabe2b6de9f40f13e3b9bd6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 3 Apr 2018 18:23:39 +0200 Subject: [PATCH 241/956] Liquid-dsp support correction. Created SymbolSynchronzier as an interface --- liquiddsp/CMakeLists.txt | 35 +++++++---- .../cmake/Modules/LiquidBuildSamples.cmake | 12 ---- sdrbase/CMakeLists.txt | 4 ++ sdrbase/dsp/symsync.cpp | 62 +++++++++++++++++++ sdrbase/dsp/symsync.h | 36 +++++++++++ 5 files changed, 125 insertions(+), 24 deletions(-) delete mode 100644 liquiddsp/cmake/Modules/LiquidBuildSamples.cmake create mode 100644 sdrbase/dsp/symsync.cpp create mode 100644 sdrbase/dsp/symsync.h diff --git a/liquiddsp/CMakeLists.txt b/liquiddsp/CMakeLists.txt index 2741e4a17..a1c3f9b1a 100644 --- a/liquiddsp/CMakeLists.txt +++ b/liquiddsp/CMakeLists.txt @@ -317,6 +317,7 @@ set(math_SOURCES ${LIQUIDDSPSRC}/src/math/src/math.complex.c ${LIQUIDDSPSRC}/src/math/src/math.trig.c ${LIQUIDDSPSRC}/src/math/src/modular_arithmetic.c + ${LIQUIDDSPSRC}/src/math/src/windows.c ) # @@ -441,7 +442,7 @@ set(vector_SOURCES # # Library # -set(liquid_SOURCES +set(liquiddsp_SOURCES ${LIQUIDDSPSRC}/src/libliquid.c ${agc_SOURCES} ${audio_SOURCES} @@ -466,17 +467,27 @@ set(liquid_SOURCES ${vector_SOURCES} ) -add_library(liquid SHARED ${liquid_SOURCES}) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_SHARED) + +add_library(liquiddsp SHARED + ${liquiddsp_SOURCES} + ${liquiddsp_HEADERS_MOC} +) + if (HAVE_LIBM) - target_link_libraries(liquid m) -endif () -if (HAVE_LIBFEC) - target_link_libraries(liquid fec) -endif () -if (NOT LIQUID_FFTOVERRIDE AND HAVE_LIBFFTW3F) - target_link_libraries(liquid fftw3f) + target_link_libraries(liquiddsp m) endif () -set_property(TARGET liquid PROPERTY OUTPUT_NAME "liquid") -set_property(TARGET liquid PROPERTY SOVERSION "${LIQUID_VERSION}") -set_property(TARGET liquid PROPERTY COMPILE_FLAGS "${_EXTRA_C_FLAGS}") +if (HAVE_LIBFEC) + target_link_libraries(liquiddsp fec) +endif () + +if (NOT LIQUID_FFTOVERRIDE AND HAVE_LIBFFTW3F) + target_link_libraries(liquiddsp fftw3f) +endif () + +#set_property(TARGET liquid PROPERTY OUTPUT_NAME "liquid") +#set_property(TARGET liquid PROPERTY SOVERSION "${LIQUID_VERSION}") +set_property(TARGET liquiddsp PROPERTY COMPILE_FLAGS "${_EXTRA_C_FLAGS}") diff --git a/liquiddsp/cmake/Modules/LiquidBuildSamples.cmake b/liquiddsp/cmake/Modules/LiquidBuildSamples.cmake deleted file mode 100644 index b64cf2663..000000000 --- a/liquiddsp/cmake/Modules/LiquidBuildSamples.cmake +++ /dev/null @@ -1,12 +0,0 @@ -function(LIQUID_BUILD_SAMPLES) - cmake_parse_arguments(LBS "" "SUITE" "SAMPLES" ${ARGN}) - if (NOT DEFINED LBS_SUITE) - message(FATAL_ERROR "Need to specify SUITE ") - endif () - foreach(_sample IN ITEMS ${LBS_SAMPLES}) - add_executable("${LBS_SUITE}_${_sample}" "${_sample}.c") - target_link_libraries("${LBS_SUITE}_${_sample}" ${LIQUID_LIBRARY}) - set_property(TARGET "${LBS_SUITE}_${_sample}" PROPERTY OUTPUT_NAME "${_sample}") - endforeach() -endfunction() - diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index cfae2ff19..57967a1d1 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -45,6 +45,7 @@ set(sdrbase_SOURCES dsp/basebandsamplesource.cpp dsp/nullsink.cpp dsp/recursivefilters.cpp + dsp/symsync.cpp dsp/threadedbasebandsamplesink.cpp dsp/threadedbasebandsamplesource.cpp dsp/wfir.cpp @@ -146,6 +147,7 @@ set(sdrbase_HEADERS dsp/basebandsamplesink.h dsp/basebandsamplesource.h dsp/nullsink.h + dsp/symsync.h dsp/threadedbasebandsamplesink.h dsp/threadedbasebandsamplesource.h dsp/wfir.h @@ -262,6 +264,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/httpserver ${CMAKE_SOURCE_DIR}/qrtplib ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIQUIDDSPSRC}/include ) target_link_libraries(sdrbase @@ -269,6 +272,7 @@ target_link_libraries(sdrbase httpserver qrtplib swagger + liquiddsp ) if(FFTW3F_FOUND) diff --git a/sdrbase/dsp/symsync.cpp b/sdrbase/dsp/symsync.cpp new file mode 100644 index 000000000..3b272f9ad --- /dev/null +++ b/sdrbase/dsp/symsync.cpp @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// Symbol synchronizer or symbol clock recovery mostly encapsulating // +// liquid-dsp's symsync "object" // +// // +// 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 "symsync.h" + +SymbolSynchronizer::SymbolSynchronizer() +{ + // For now use hardcoded values: + // - RRC filter + // - 4 samples per symbol + // - 5 sybols delay filter + // - 0.5 filter excess bandwidth factor + // - 32 filter elements for the internal polyphase filter + m_sync = symsync_crcf_create_rnyquist(LIQUID_FIRFILT_RRC, 4, 5, 0.5f, 32); + // - 0.02 loop filter bandwidth factor + symsync_crcf_set_lf_bw(m_sync, 0.02f); + // - 4 samples per symbol output rate + symsync_crcf_set_output_rate(m_sync, 4); + m_syncSampleCount = 0; +} + +SymbolSynchronizer::~SymbolSynchronizer() +{ + symsync_crcf_destroy(m_sync); +} + +Real SymbolSynchronizer::run(const Sample& s) +{ + unsigned int nn; + Real v = 0.0f; + liquid_float_complex y = (s.m_real / SDR_RX_SCALEF) + (s.m_imag / SDR_RX_SCALEF)*I; + symsync_crcf_execute(m_sync, &y, 1, m_z, &nn); + + for (unsigned int i = 0; i < nn; i++) + { + v = (m_syncSampleCount < 2) ? 1.0f : 0.0f; // actual sync is at 0 + + if (m_syncSampleCount < 4) { + m_syncSampleCount++; + } else { + m_syncSampleCount = 0; + } + } + + return v; +} \ No newline at end of file diff --git a/sdrbase/dsp/symsync.h b/sdrbase/dsp/symsync.h new file mode 100644 index 000000000..611b839fc --- /dev/null +++ b/sdrbase/dsp/symsync.h @@ -0,0 +1,36 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// Symbol synchronizer or symbol clock recovery mostly encapsulating // +// liquid-dsp's symsync "object" // +// // +// 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 "dsp/dsptypes.h" +#include "liquid.h" +#include + +class SymbolSynchronizer +{ +public: + SymbolSynchronizer(); + ~SymbolSynchronizer(); + + Real run(const Sample& s); + +private: + symsync_crcf m_sync; + liquid_float_complex m_z[4+4]; // 4 samples per symbol. One symbol plus extra space + int m_syncSampleCount; +}; \ No newline at end of file From 8ce1c76a40dc1642e3dc641f4bdb11956d4d65d8 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 3 Apr 2018 18:51:21 +0200 Subject: [PATCH 242/956] ScopeNG: moved Projector class to sdrbase to avoid excessive dependency on liquid-dsp --- sdrbase/CMakeLists.txt | 2 + sdrbase/dsp/projector.cpp | 92 +++++++++++++++++++++++++++ sdrbase/dsp/projector.h | 53 ++++++++++++++++ sdrgui/dsp/scopevisng.cpp | 18 +++--- sdrgui/dsp/scopevisng.h | 122 ++++-------------------------------- sdrgui/gui/glscopeng.cpp | 12 ++-- sdrgui/gui/glscopenggui.cpp | 36 +++++------ 7 files changed, 191 insertions(+), 144 deletions(-) create mode 100644 sdrbase/dsp/projector.cpp create mode 100644 sdrbase/dsp/projector.h diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 57967a1d1..e90989e35 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -38,6 +38,7 @@ set(sdrbase_SOURCES dsp/nco.cpp dsp/ncof.cpp dsp/phaselock.cpp + dsp/projector.cpp dsp/samplesinkfifo.cpp dsp/samplesourcefifo.cpp dsp/samplesinkfifodoublebuffered.cpp @@ -139,6 +140,7 @@ set(sdrbase_HEADERS dsp/ncof.h dsp/phasediscri.h dsp/phaselock.h + dsp/projector.h dsp/recursivefilters.h dsp/samplesinkfifo.h dsp/samplesourcefifo.h diff --git a/sdrbase/dsp/projector.cpp b/sdrbase/dsp/projector.cpp new file mode 100644 index 000000000..6eeea26cd --- /dev/null +++ b/sdrbase/dsp/projector.cpp @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 F4EXB // +// written by Edouard Griffiths // +// // +// 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 "projector.h" +#include "symsync.h" // dependency on liquid-dsp + +Projector::Projector(ProjectionType projectionType) : + m_projectionType(projectionType), + m_prevArg(0.0f), + m_cache(0), + m_cacheMaster(true) +{} + +Projector::~Projector() +{} + +Real Projector::run(const Sample& s) +{ + Real v; + + if ((m_cache) && !m_cacheMaster) { + return m_cache[(int) m_projectionType]; + } + else + { + switch (m_projectionType) + { + case ProjectionImag: + v = s.m_imag / SDR_RX_SCALEF; + break; + case ProjectionMagLin: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + v = std::sqrt(magsq); + } + break; + case ProjectionMagDB: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + v = log10f(magsq) * 10.0f; + } + break; + case ProjectionPhase: + v = std::atan2((float) s.m_imag, (float) s.m_real) / M_PI; + break; + case ProjectionDPhase: + { + Real curArg = std::atan2((float) s.m_imag, (float) s.m_real); + Real dPhi = (curArg - m_prevArg) / M_PI; + m_prevArg = curArg; + + if (dPhi < -1.0f) { + dPhi += 2.0f; + } else if (dPhi > 1.0f) { + dPhi -= 2.0f; + } + + v = dPhi; + } + break; + case ProjectionReal: + default: + v = s.m_real / SDR_RX_SCALEF; + break; + } + + if (m_cache) { + m_cache[(int) m_projectionType] = v; + } + + return v; + } +} + diff --git a/sdrbase/dsp/projector.h b/sdrbase/dsp/projector.h new file mode 100644 index 000000000..1370fca44 --- /dev/null +++ b/sdrbase/dsp/projector.h @@ -0,0 +1,53 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 F4EXB // +// written by Edouard Griffiths // +// // +// 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 "dsptypes.h" + +class SymbolSynchronizer; + +class Projector +{ +public: + enum ProjectionType + { + ProjectionReal = 0, //!< Extract real part + ProjectionImag, //!< Extract imaginary part + ProjectionMagLin, //!< Calculate linear magnitude or modulus + ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude + ProjectionPhase, //!< Calculate phase + ProjectionDPhase, //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate + ProjectionClock, //!< Clock projection (symbol synchronization) + nbProjectionTypes //!< Gives the number of projections in the enum + }; + + Projector(ProjectionType projectionType); + ~Projector(); + + ProjectionType getProjectionType() const { return m_projectionType; } + void settProjectionType(ProjectionType projectionType) { m_projectionType = projectionType; } + void setCache(Real *cache) { m_cache = cache; } + void setCacheMaster(bool cacheMaster) { m_cacheMaster = cacheMaster; } + + Real run(const Sample& s); + +private: + ProjectionType m_projectionType; + Real m_prevArg; + Real *m_cache; + bool m_cacheMaster; + SymbolSynchronizer *m_symSync; +}; diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index f44485055..c8fdb6ed2 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -62,7 +62,7 @@ ScopeVisNG::ScopeVisNG(GLScopeNG* glScope) : setObjectName("ScopeVisNG"); m_traceDiscreteMemory.resize(m_traceChunkSize); // arbitrary m_glScope->setTraces(&m_traces.m_tracesData, &m_traces.m_traces[0]); - for (int i = 0; i < (int) nbProjectionTypes; i++) { + for (int i = 0; i < (int) Projector::nbProjectionTypes; i++) { m_projectorCache[i] = 0.0; } } @@ -449,18 +449,18 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const continue; } - ProjectionType projectionType = itData->m_projectionType; + Projector::ProjectionType projectionType = itData->m_projectionType; if (itCtl->m_traceCount[m_traces.currentBufferIndex()] < m_traceSize) { uint32_t& traceCount = itCtl->m_traceCount[m_traces.currentBufferIndex()]; // reference for code clarity float v; - if (projectionType == ProjectionMagLin) + if (projectionType == Projector::ProjectionMagLin) { v = (itCtl->m_projector.run(*begin) - itData->m_ofs)*itData->m_amp - 1.0f; } - else if (projectionType == ProjectionMagDB) + else if (projectionType == Projector::ProjectionMagDB) { // there is no processing advantage in direct calculation without projector // uint32_t magsq = begin->m_real*begin->m_real + begin->m_imag*begin->m_imag; @@ -786,8 +786,8 @@ void ScopeVisNG::updateMaxTraceDelay() { int maxTraceDelay = 0; bool allocateCache = false; - uint32_t projectorCounts[(int) nbProjectionTypes]; - memset(projectorCounts, 0, ((int) nbProjectionTypes)*sizeof(uint32_t)); + uint32_t projectorCounts[(int) Projector::nbProjectionTypes]; + memset(projectorCounts, 0, ((int) Projector::nbProjectionTypes)*sizeof(uint32_t)); std::vector::iterator itData = m_traces.m_tracesData.begin(); std::vector::iterator itCtrl = m_traces.m_tracesControl.begin(); @@ -799,7 +799,7 @@ void ScopeVisNG::updateMaxTraceDelay() } if (itData->m_projectionType < 0) { - itData->m_projectionType = ProjectionReal; + itData->m_projectionType = Projector::ProjectionReal; } if (projectorCounts[(int) itData->m_projectionType] > 0) @@ -861,11 +861,11 @@ void ScopeVisNG::computeDisplayTriggerLevels() float levelPowerdB = (100.0f * (level - 1.0f)); float v; - if (itData->m_projectionType == ProjectionMagLin) + if (itData->m_projectionType == Projector::ProjectionMagLin) { v = (levelPowerLin - itData->m_ofs)*itData->m_amp - 1.0f; } - else if (itData->m_projectionType == ProjectionMagDB) + else if (itData->m_projectionType == Projector::ProjectionMagDB) { float ofsdB = itData->m_ofs * 100.0f; v = ((levelPowerdB + 100.0f - ofsdB)*itData->m_amp)/50.0f - 1.0f; diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index dc5ba22bd..5f022c2bc 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -29,6 +29,7 @@ #include #include "dsp/dsptypes.h" #include "dsp/basebandsamplesink.h" +#include "dsp/projector.h" #include "export.h" #include "util/message.h" #include "util/doublebuffer.h" @@ -41,20 +42,9 @@ class GLScopeNG; class SDRGUI_API ScopeVisNG : public BasebandSampleSink { public: - enum ProjectionType - { - ProjectionReal = 0, //!< Extract real part - ProjectionImag, //!< Extract imaginary part - ProjectionMagLin, //!< Calculate linear magnitude or modulus - ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude - ProjectionPhase, //!< Calculate phase - ProjectionDPhase, //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate - nbProjectionTypes //!< Gives the number of projections in the enum - }; - struct TraceData { - ProjectionType m_projectionType; //!< Complex to real projection type + Projector::ProjectionType m_projectionType; //!< Complex to real projection type uint32_t m_inputIndex; //!< Input or feed index this trace is associated with float m_amp; //!< Amplification factor uint32_t m_ampIndex; //!< Index in list of amplification factors @@ -74,7 +64,7 @@ public: bool m_viewTrace; //!< Trace visibility TraceData() : - m_projectionType(ProjectionReal), + m_projectionType(Projector::ProjectionReal), m_inputIndex(0), m_amp(1.0f), m_ampIndex(0), @@ -106,7 +96,7 @@ public: struct TriggerData { - ProjectionType m_projectionType; //!< Complex to real projection type + Projector::ProjectionType m_projectionType; //!< Complex to real projection type uint32_t m_inputIndex; //!< Input or feed index this trigger is associated with Real m_triggerLevel; //!< Level in real units int m_triggerLevelCoarse; @@ -124,7 +114,7 @@ public: float m_triggerColorB; //!< Trigger line display color - blue shortcut TriggerData() : - m_projectionType(ProjectionReal), + m_projectionType(Projector::ProjectionReal), m_inputIndex(0), m_triggerLevel(0.0f), m_triggerLevelCoarse(0), @@ -512,96 +502,6 @@ private: // --------------------------------------------- - /** - * Projection stuff - */ - class Projector - { - public: - Projector(ProjectionType projectionType) : - m_projectionType(projectionType), - m_prevArg(0.0f), - m_cache(0), - m_cacheMaster(true) - {} - - ~Projector() - {} - - ProjectionType getProjectionType() const { return m_projectionType; } - void settProjectionType(ProjectionType projectionType) { m_projectionType = projectionType; } - void setCache(Real *cache) { m_cache = cache; } - void setCacheMaster(bool cacheMaster) { m_cacheMaster = cacheMaster; } - - Real run(const Sample& s) - { - Real v; - - if ((m_cache) && !m_cacheMaster) { - return m_cache[(int) m_projectionType]; - } - else - { - switch (m_projectionType) - { - case ProjectionImag: - v = s.m_imag / SDR_RX_SCALEF; - break; - case ProjectionMagLin: - { - Real re = s.m_real / SDR_RX_SCALEF; - Real im = s.m_imag / SDR_RX_SCALEF; - Real magsq = re*re + im*im; - v = std::sqrt(magsq); - } - break; - case ProjectionMagDB: - { - Real re = s.m_real / SDR_RX_SCALEF; - Real im = s.m_imag / SDR_RX_SCALEF; - Real magsq = re*re + im*im; - v = log10f(magsq) * 10.0f; - } - break; - case ProjectionPhase: - v = std::atan2((float) s.m_imag, (float) s.m_real) / M_PI; - break; - case ProjectionDPhase: - { - Real curArg = std::atan2((float) s.m_imag, (float) s.m_real); - Real dPhi = (curArg - m_prevArg) / M_PI; - m_prevArg = curArg; - - if (dPhi < -1.0f) { - dPhi += 2.0f; - } else if (dPhi > 1.0f) { - dPhi -= 2.0f; - } - - v = dPhi; - } - break; - case ProjectionReal: - default: - v = s.m_real / SDR_RX_SCALEF; - break; - } - - if (m_cache) { - m_cache[(int) m_projectionType] = v; - } - - return v; - } - } - - private: - ProjectionType m_projectionType; - Real m_prevArg; - Real *m_cache; - bool m_cacheMaster; - }; - /** * Trigger stuff */ @@ -623,7 +523,7 @@ private: uint32_t m_triggerCounter; //!< Counter of trigger occurences TriggerCondition(const TriggerData& triggerData) : - m_projector(ProjectionReal), + m_projector(Projector::ProjectionReal), m_triggerData(triggerData), m_prevCondition(false), m_triggerDelayCount(0), @@ -785,7 +685,7 @@ private: Real m_sumPow; //!< Cumulative power over the current trace for MagDB overlay display int m_nbPow; //!< Number of power samples over the current trace for MagDB overlay display - TraceControl() : m_projector(ProjectionReal) + TraceControl() : m_projector(Projector::ProjectionReal) { reset(); } @@ -794,7 +694,7 @@ private: { } - void initProjector(ProjectionType projectionType) + void initProjector(Projector::ProjectionType projectionType) { m_projector.settProjectionType(projectionType); } @@ -967,9 +867,9 @@ private: bool condition, trigger; - if (triggerCondition.m_projector.getProjectionType() == ProjectionMagDB) { + if (triggerCondition.m_projector.getProjectionType() == Projector::ProjectionMagDB) { condition = triggerCondition.m_projector.run(s) > m_levelPowerDB; - } else if (triggerCondition.m_projector.getProjectionType() == ProjectionMagLin) { + } else if (triggerCondition.m_projector.getProjectionType() == Projector::ProjectionMagLin) { condition = triggerCondition.m_projector.run(s) > m_levelPowerLin; } else { condition = triggerCondition.m_projector.run(s) > m_level; @@ -1040,7 +940,7 @@ private: int m_maxTraceDelay; //!< Maximum trace delay TriggerComparator m_triggerComparator; //!< Compares sample level to trigger level QMutex m_mutex; - Real m_projectorCache[(int) nbProjectionTypes]; + Real m_projectorCache[(int) Projector::nbProjectionTypes]; bool m_triggerOneShot; //!< True when one shot mode is active bool m_triggerWaitForReset; //!< In one shot mode suspended until reset by UI uint32_t m_currentTraceMemoryIndex; //!< The current index of trace in memory (0: current) diff --git a/sdrgui/gui/glscopeng.cpp b/sdrgui/gui/glscopeng.cpp index 30f32bac0..3bda3ff10 100644 --- a/sdrgui/gui/glscopeng.cpp +++ b/sdrgui/gui/glscopeng.cpp @@ -1868,22 +1868,22 @@ void GLScopeNG::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex) switch (traceData.m_projectionType) { - case ScopeVisNG::ProjectionMagDB: // dB scale + case Projector::ProjectionMagDB: // dB scale scale.setRange(Unit::Decibel, pow_floor, pow_floor + pow_range); break; - case ScopeVisNG::ProjectionMagLin: + case Projector::ProjectionMagLin: if (amp_range < 2.0) { scale.setRange(Unit::None, amp_ofs * 1000.0, amp_range * 1000.0 + amp_ofs * 1000.0); } else { scale.setRange(Unit::None, amp_ofs, amp_range + amp_ofs); } break; - case ScopeVisNG::ProjectionPhase: // Phase or frequency - case ScopeVisNG::ProjectionDPhase: + case Projector::ProjectionPhase: // Phase or frequency + case Projector::ProjectionDPhase: scale.setRange(Unit::None, -1.0/traceData.m_amp + amp_ofs, 1.0/traceData.m_amp + amp_ofs); break; - case ScopeVisNG::ProjectionReal: // Linear generic - case ScopeVisNG::ProjectionImag: + case Projector::ProjectionReal: // Linear generic + case Projector::ProjectionImag: default: if (amp_range < 2.0) { scale.setRange(Unit::None, - amp_range * 500.0 + amp_ofs * 1000.0, amp_range * 500.0 + amp_ofs * 1000.0); diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 894d88ced..12f043b35 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -944,10 +944,10 @@ void GLScopeNGGUI::setTimeOfsDisplay() void GLScopeNGGUI::setAmpScaleDisplay() { - ScopeVisNG::ProjectionType projectionType = (ScopeVisNG::ProjectionType) ui->traceMode->currentIndex(); + Projector::ProjectionType projectionType = (Projector::ProjectionType) ui->traceMode->currentIndex(); double ampValue = amps[ui->amp->value()]; - if (projectionType == ScopeVisNG::ProjectionMagDB) + if (projectionType == Projector::ProjectionMagDB) { double displayValue = ampValue*500.0f; @@ -975,10 +975,10 @@ void GLScopeNGGUI::setAmpScaleDisplay() void GLScopeNGGUI::setAmpOfsDisplay() { - ScopeVisNG::ProjectionType projectionType = (ScopeVisNG::ProjectionType) ui->traceMode->currentIndex(); + Projector::ProjectionType projectionType = (Projector::ProjectionType) ui->traceMode->currentIndex(); double o = (ui->ofsCoarse->value() * 10.0f) + (ui->ofsFine->value() / 20.0f); - if (projectionType == ScopeVisNG::ProjectionMagDB) + if (projectionType == Projector::ProjectionMagDB) { ui->ofsText->setText(tr("%1\ndB").arg(o/10.0f - 100.0f, 0, 'f', 1)); } @@ -986,7 +986,7 @@ void GLScopeNGGUI::setAmpOfsDisplay() { double a; - if (projectionType == ScopeVisNG::ProjectionMagLin) + if (projectionType == Projector::ProjectionMagLin) { a = o/2000.0f; } @@ -1040,19 +1040,19 @@ void GLScopeNGGUI::setTrigIndexDisplay() void GLScopeNGGUI::setTrigLevelDisplay() { double t = (ui->trigLevelCoarse->value() / 100.0f) + (ui->trigLevelFine->value() / 20000.0f); - ScopeVisNG::ProjectionType projectionType = (ScopeVisNG::ProjectionType) ui->trigMode->currentIndex(); + Projector::ProjectionType projectionType = (Projector::ProjectionType) ui->trigMode->currentIndex(); ui->trigLevelCoarse->setToolTip(QString("Trigger level coarse: %1 %").arg(ui->trigLevelCoarse->value() / 100.0f)); ui->trigLevelFine->setToolTip(QString("Trigger level fine: %1 ppm").arg(ui->trigLevelFine->value() * 50)); - if (projectionType == ScopeVisNG::ProjectionMagDB) { + if (projectionType == Projector::ProjectionMagDB) { ui->trigLevelText->setText(tr("%1\ndB").arg(100.0 * (t - 1.0), 0, 'f', 1)); } else { double a; - if (projectionType == ScopeVisNG::ProjectionMagLin) { + if (projectionType == Projector::ProjectionMagLin) { a = 1.0 + t; } else { a = t; @@ -1146,12 +1146,12 @@ void GLScopeNGGUI::changeCurrentTrigger() void GLScopeNGGUI::fillProjectionCombo(QComboBox* comboBox) { - comboBox->addItem("Real", ScopeVisNG::ProjectionReal); - comboBox->addItem("Imag", ScopeVisNG::ProjectionImag); - comboBox->addItem("Mag", ScopeVisNG::ProjectionMagLin); - comboBox->addItem("MagdB", ScopeVisNG::ProjectionMagDB); - comboBox->addItem("Phi", ScopeVisNG::ProjectionPhase); - comboBox->addItem("dPhi", ScopeVisNG::ProjectionDPhase); + comboBox->addItem("Real", Projector::ProjectionReal); + comboBox->addItem("Imag", Projector::ProjectionImag); + comboBox->addItem("Mag", Projector::ProjectionMagLin); + comboBox->addItem("MagdB", Projector::ProjectionMagDB); + comboBox->addItem("Phi", Projector::ProjectionPhase); + comboBox->addItem("dPhi", Projector::ProjectionDPhase); } void GLScopeNGGUI::disableLiveMode(bool disable) @@ -1176,8 +1176,8 @@ void GLScopeNGGUI::disableLiveMode(bool disable) void GLScopeNGGUI::fillTraceData(ScopeVisNG::TraceData& traceData) { - traceData.m_projectionType = (ScopeVisNG::ProjectionType) ui->traceMode->currentIndex(); - traceData.m_hasTextOverlay = (traceData.m_projectionType == ScopeVisNG::ProjectionMagDB); + traceData.m_projectionType = (Projector::ProjectionType) ui->traceMode->currentIndex(); + traceData.m_hasTextOverlay = (traceData.m_projectionType == Projector::ProjectionMagDB); traceData.m_textOverlay.clear(); traceData.m_inputIndex = 0; traceData.m_amp = 0.2 / amps[ui->amp->value()]; @@ -1186,7 +1186,7 @@ void GLScopeNGGUI::fillTraceData(ScopeVisNG::TraceData& traceData) traceData.m_ofsCoarse = ui->ofsCoarse->value(); traceData.m_ofsFine = ui->ofsFine->value(); - if (traceData.m_projectionType == ScopeVisNG::ProjectionMagLin) { + if (traceData.m_projectionType == Projector::ProjectionMagLin) { traceData.m_ofs = ((10.0 * ui->ofsCoarse->value()) + (ui->ofsFine->value() / 20.0)) / 2000.0f; } else { traceData.m_ofs = ((10.0 * ui->ofsCoarse->value()) + (ui->ofsFine->value() / 20.0)) / 1000.0f; @@ -1201,7 +1201,7 @@ void GLScopeNGGUI::fillTraceData(ScopeVisNG::TraceData& traceData) void GLScopeNGGUI::fillTriggerData(ScopeVisNG::TriggerData& triggerData) { - triggerData.m_projectionType = (ScopeVisNG::ProjectionType) ui->trigMode->currentIndex(); + triggerData.m_projectionType = (Projector::ProjectionType) ui->trigMode->currentIndex(); triggerData.m_inputIndex = 0; triggerData.m_triggerLevel = (ui->trigLevelCoarse->value() / 100.0) + (ui->trigLevelFine->value() / 20000.0); triggerData.m_triggerLevelCoarse = ui->trigLevelCoarse->value(); From b8b2ceb47d67f59d898ace32d5c03a2e10ae5c17 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 3 Apr 2018 21:32:52 +0200 Subject: [PATCH 243/956] Removed SymbolSynchronizer from Projector --- sdrbase/dsp/projector.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdrbase/dsp/projector.h b/sdrbase/dsp/projector.h index 1370fca44..38db2f402 100644 --- a/sdrbase/dsp/projector.h +++ b/sdrbase/dsp/projector.h @@ -17,8 +17,6 @@ #include "dsptypes.h" -class SymbolSynchronizer; - class Projector { public: @@ -49,5 +47,4 @@ private: Real m_prevArg; Real *m_cache; bool m_cacheMaster; - SymbolSynchronizer *m_symSync; }; From 26b4b50d23a2d7cc996db887ed2b21051e107b0b Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 3 Apr 2018 22:29:09 +0200 Subject: [PATCH 244/956] ScopeNG: use dynamic storage for TraceControl objects --- sdrgui/dsp/scopevisng.cpp | 46 +++++++++++++++++++-------------------- sdrgui/dsp/scopevisng.h | 45 +++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index c8fdb6ed2..1fcd9dd81 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -439,7 +439,7 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const while ((begin < end) && (m_nbSamples > 0)) { - std::vector::iterator itCtl = m_traces.m_tracesControl.begin(); + std::vector::iterator itCtl = m_traces.m_tracesControl.begin(); std::vector::iterator itData = m_traces.m_tracesData.begin(); std::vector::iterator itTrace = m_traces.m_traces[m_traces.currentBufferIndex()].begin(); @@ -451,21 +451,21 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const Projector::ProjectionType projectionType = itData->m_projectionType; - if (itCtl->m_traceCount[m_traces.currentBufferIndex()] < m_traceSize) + if ((*itCtl)->m_traceCount[m_traces.currentBufferIndex()] < m_traceSize) { - uint32_t& traceCount = itCtl->m_traceCount[m_traces.currentBufferIndex()]; // reference for code clarity + uint32_t& traceCount = (*itCtl)->m_traceCount[m_traces.currentBufferIndex()]; // reference for code clarity float v; if (projectionType == Projector::ProjectionMagLin) { - v = (itCtl->m_projector.run(*begin) - itData->m_ofs)*itData->m_amp - 1.0f; + v = ((*itCtl)->m_projector.run(*begin) - itData->m_ofs)*itData->m_amp - 1.0f; } else if (projectionType == Projector::ProjectionMagDB) { // there is no processing advantage in direct calculation without projector // uint32_t magsq = begin->m_real*begin->m_real + begin->m_imag*begin->m_imag; // v = ((log10f(magsq/1073741824.0f)*0.2f - 2.0f*itData->m_ofs) + 2.0f)*itData->m_amp - 1.0f; - float pdB = itCtl->m_projector.run(*begin); + float pdB = (*itCtl)->m_projector.run(*begin); float p = pdB - (100.0f * itData->m_ofs); v = ((p/50.0f) + 2.0f)*itData->m_amp - 1.0f; @@ -473,34 +473,34 @@ int ScopeVisNG::processTraces(const SampleVector::const_iterator& cbegin, const { if (traceCount == shift) { - itCtl->m_maxPow = -200.0f; - itCtl->m_sumPow = 0.0f; - itCtl->m_nbPow = 1; + (*itCtl)->m_maxPow = -200.0f; + (*itCtl)->m_sumPow = 0.0f; + (*itCtl)->m_nbPow = 1; } if (pdB > -200.0f) { - if (pdB > itCtl->m_maxPow) + if (pdB > (*itCtl)->m_maxPow) { - itCtl->m_maxPow = pdB; + (*itCtl)->m_maxPow = pdB; } - itCtl->m_sumPow += pdB; - itCtl->m_nbPow++; + (*itCtl)->m_sumPow += pdB; + (*itCtl)->m_nbPow++; } } - if ((m_nbSamples == 1) && (itCtl->m_nbPow > 0)) // on last sample create power display overlay + if ((m_nbSamples == 1) && ((*itCtl)->m_nbPow > 0)) // on last sample create power display overlay { - double avgPow = itCtl->m_sumPow / itCtl->m_nbPow; - double peakToAvgPow = itCtl->m_maxPow - avgPow; - itData->m_textOverlay = QString("%1 %2 %3").arg(itCtl->m_maxPow, 0, 'f', 1).arg(avgPow, 0, 'f', 1).arg(peakToAvgPow, 4, 'f', 1, ' '); - itCtl->m_nbPow = 0; + double avgPow = (*itCtl)->m_sumPow / (*itCtl)->m_nbPow; + double peakToAvgPow = (*itCtl)->m_maxPow - avgPow; + itData->m_textOverlay = QString("%1 %2 %3").arg((*itCtl)->m_maxPow, 0, 'f', 1).arg(avgPow, 0, 'f', 1).arg(peakToAvgPow, 4, 'f', 1, ' '); + (*itCtl)->m_nbPow = 0; } } else { - v = (itCtl->m_projector.run(*begin) - itData->m_ofs) * itData->m_amp; + v = ((*itCtl)->m_projector.run(*begin) - itData->m_ofs) * itData->m_amp; } if(v > 1.0f) { @@ -789,7 +789,7 @@ void ScopeVisNG::updateMaxTraceDelay() uint32_t projectorCounts[(int) Projector::nbProjectionTypes]; memset(projectorCounts, 0, ((int) Projector::nbProjectionTypes)*sizeof(uint32_t)); std::vector::iterator itData = m_traces.m_tracesData.begin(); - std::vector::iterator itCtrl = m_traces.m_tracesControl.begin(); + std::vector::iterator itCtrl = m_traces.m_tracesControl.begin(); for (; itData != m_traces.m_tracesData.end(); ++itData, ++itCtrl) { @@ -805,11 +805,11 @@ void ScopeVisNG::updateMaxTraceDelay() if (projectorCounts[(int) itData->m_projectionType] > 0) { allocateCache = true; - itCtrl->m_projector.setCacheMaster(false); + (*itCtrl)->m_projector.setCacheMaster(false); } else { - itCtrl->m_projector.setCacheMaster(true); + (*itCtrl)->m_projector.setCacheMaster(true); } projectorCounts[(int) itData->m_projectionType]++; @@ -820,9 +820,9 @@ void ScopeVisNG::updateMaxTraceDelay() for (; itCtrl != m_traces.m_tracesControl.end(); ++itCtrl) { if (allocateCache) { - itCtrl->m_projector.setCache(m_projectorCache); + (*itCtrl)->m_projector.setCache(m_projectorCache); } else { - itCtrl->m_projector.setCache(0); + (*itCtrl)->m_projector.setCache(0); } } diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index 5f022c2bc..17d07cadb 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -529,10 +529,12 @@ private: m_triggerDelayCount(0), m_triggerCounter(0) { + qDebug("TriggerCondition"); } ~TriggerCondition() { + qDebug("~TriggerCondition"); } void initProjector() @@ -687,11 +689,13 @@ private: TraceControl() : m_projector(Projector::ProjectionReal) { + qDebug("TraceControl::TraceControl"); reset(); } ~TraceControl() { + qDebug("TraceControl::~TraceControl"); } void initProjector(Projector::ProjectionType projectionType) @@ -715,7 +719,7 @@ private: struct Traces { - std::vector m_tracesControl; //!< Corresponding traces control data + std::vector m_tracesControl; //!< Corresponding traces control data std::vector m_tracesData; //!< Corresponding traces data std::vector m_traces[2]; //!< Double buffer of traces processed by glScope int m_traceSize; //!< Current size of a trace in buffer @@ -750,11 +754,13 @@ private: { if (m_traces[0].size() < m_maxNbTraces) { + qDebug("ScopeVisNG::addTrace"); m_traces[0].push_back(0); m_traces[1].push_back(0); m_tracesData.push_back(traceData); - m_tracesControl.push_back(TraceControl()); - m_tracesControl.back().initProjector(traceData.m_projectionType); + m_tracesControl.push_back(new TraceControl()); + TraceControl *traceControl = m_tracesControl.back(); + traceControl->initProjector(traceData.m_projectionType); resize(traceSize); } @@ -763,8 +769,9 @@ private: void changeTrace(const TraceData& traceData, uint32_t traceIndex) { if (traceIndex < m_tracesControl.size()) { - m_tracesControl[traceIndex].releaseProjector(); - m_tracesControl[traceIndex].initProjector(traceData.m_projectionType); + TraceControl *traceControl = m_tracesControl[traceIndex]; + traceControl->releaseProjector(); + traceControl->initProjector(traceData.m_projectionType); m_tracesData[traceIndex] = traceData; } } @@ -773,11 +780,14 @@ private: { if (traceIndex < m_tracesControl.size()) { + qDebug("ScopeVisNG::removeTrace"); m_traces[0].erase(m_traces[0].begin() + traceIndex); m_traces[1].erase(m_traces[1].begin() + traceIndex); - m_tracesControl[traceIndex].releaseProjector(); + TraceControl *traceControl = m_tracesControl[traceIndex]; + traceControl->releaseProjector(); m_tracesControl.erase(m_tracesControl.begin() + traceIndex); m_tracesData.erase(m_tracesData.begin() + traceIndex); + delete traceControl; resize(m_traceSize); // reallocate pointers } @@ -792,19 +802,24 @@ private: int nextControlIndex = (traceIndex + (upElseDown ? 1 : -1)) % (m_tracesControl.size()); int nextDataIndex = (traceIndex + (upElseDown ? 1 : -1)) % (m_tracesData.size()); // should be the same - m_tracesControl[traceIndex].releaseProjector(); - m_tracesControl[nextControlIndex].releaseProjector(); + TraceControl *traceControl = m_tracesControl[traceIndex]; + TraceControl *nextTraceControl = m_tracesControl[nextControlIndex]; - TraceControl nextControl = m_tracesControl[nextControlIndex]; - m_tracesControl[nextControlIndex] = m_tracesControl[traceIndex]; - m_tracesControl[traceIndex] = nextControl; + traceControl->releaseProjector(); + nextTraceControl->releaseProjector(); + + m_tracesControl[nextControlIndex] = traceControl; + m_tracesControl[traceIndex] = nextTraceControl; TraceData nextData = m_tracesData[nextDataIndex]; m_tracesData[nextDataIndex] = m_tracesData[traceIndex]; m_tracesData[traceIndex] = nextData; - m_tracesControl[traceIndex].initProjector(m_tracesData[traceIndex].m_projectionType); - m_tracesControl[nextControlIndex].initProjector(m_tracesData[nextDataIndex].m_projectionType); + traceControl = m_tracesControl[traceIndex]; + nextTraceControl = m_tracesControl[nextControlIndex]; + + traceControl->initProjector(m_tracesData[traceIndex].m_projectionType); + nextTraceControl->initProjector(m_tracesData[nextDataIndex].m_projectionType); } void resize(int traceSize) @@ -838,9 +853,9 @@ private: { evenOddIndex = !evenOddIndex; - for (std::vector::iterator it = m_tracesControl.begin(); it != m_tracesControl.end(); ++it) + for (std::vector::iterator it = m_tracesControl.begin(); it != m_tracesControl.end(); ++it) { - it->m_traceCount[currentBufferIndex()] = 0; + (*it)->m_traceCount[currentBufferIndex()] = 0; } } From c9861c455b85f784bd04cbe5acb4b05a79d43840 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 3 Apr 2018 23:19:35 +0200 Subject: [PATCH 245/956] ScopeNG: use dynamic storage for TriggerCondition objects. Fixed destructors --- sdrgui/dsp/scopevisng.cpp | 48 ++++++++++++++++++++++----------------- sdrgui/dsp/scopevisng.h | 24 ++++++++++++-------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/sdrgui/dsp/scopevisng.cpp b/sdrgui/dsp/scopevisng.cpp index 1fcd9dd81..439fed933 100644 --- a/sdrgui/dsp/scopevisng.cpp +++ b/sdrgui/dsp/scopevisng.cpp @@ -69,6 +69,9 @@ ScopeVisNG::ScopeVisNG(GLScopeNG* glScope) : ScopeVisNG::~ScopeVisNG() { + for (std::vector::iterator it = m_triggerConditions.begin(); it != m_triggerConditions.end(); ++ it) { + delete *it; + } } void ScopeVisNG::setSampleRate(int sampleRate) @@ -267,15 +270,15 @@ void ScopeVisNG::processTrace(const SampleVector::const_iterator& cbegin, const { if ((m_triggerState == TriggerUntriggered) || (m_triggerState == TriggerDelay)) { - TriggerCondition& triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition + TriggerCondition* triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition while (begin < end) { if (m_triggerState == TriggerDelay) { - if (triggerCondition.m_triggerDelayCount > 0) // skip samples during delay period + if (triggerCondition->m_triggerDelayCount > 0) // skip samples during delay period { - triggerCondition.m_triggerDelayCount--; + triggerCondition->m_triggerDelayCount--; ++begin; continue; } @@ -301,11 +304,11 @@ void ScopeVisNG::processTrace(const SampleVector::const_iterator& cbegin, const } // look for trigger - if (m_triggerComparator.triggered(*begin, triggerCondition)) + if (m_triggerComparator.triggered(*begin, *triggerCondition)) { - if (triggerCondition.m_triggerData.m_triggerDelay > 0) + if (triggerCondition->m_triggerData.m_triggerDelay > 0) { - triggerCondition.m_triggerDelayCount = triggerCondition.m_triggerData.m_triggerDelay; // initialize delayed samples counter + triggerCondition->m_triggerDelayCount = triggerCondition->m_triggerData.m_triggerDelay; // initialize delayed samples counter m_triggerState = TriggerDelay; ++begin; continue; @@ -398,18 +401,18 @@ void ScopeVisNG::processTrace(const SampleVector::const_iterator& cbegin, const bool ScopeVisNG::nextTrigger() { - TriggerCondition& triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition + TriggerCondition *triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition - if (triggerCondition.m_triggerData.m_triggerRepeat > 0) + if (triggerCondition->m_triggerData.m_triggerRepeat > 0) { - if (triggerCondition.m_triggerCounter < triggerCondition.m_triggerData.m_triggerRepeat) + if (triggerCondition->m_triggerCounter < triggerCondition->m_triggerData.m_triggerRepeat) { - triggerCondition.m_triggerCounter++; + triggerCondition->m_triggerCounter++; return true; // not final keep going } else { - triggerCondition.m_triggerCounter = 0; // reset for next time + triggerCondition->m_triggerCounter = 0; // reset for next time } } @@ -623,8 +626,8 @@ bool ScopeVisNG::handleMessage(const Message& message) { QMutexLocker configLocker(&m_mutex); MsgScopeVisNGAddTrigger& conf = (MsgScopeVisNGAddTrigger&) message; - m_triggerConditions.push_back(TriggerCondition(conf.getTriggerData())); - m_triggerConditions.back().initProjector(); + m_triggerConditions.push_back(new TriggerCondition(conf.getTriggerData())); + m_triggerConditions.back()->initProjector(); return true; } else if (MsgScopeVisNGChangeTrigger::match(message)) @@ -635,12 +638,12 @@ bool ScopeVisNG::handleMessage(const Message& message) if (triggerIndex < m_triggerConditions.size()) { - m_triggerConditions[triggerIndex].setData(conf.getTriggerData()); + m_triggerConditions[triggerIndex]->setData(conf.getTriggerData()); if (triggerIndex == m_focusedTriggerIndex) { computeDisplayTriggerLevels(); - m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex].m_triggerData); + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex]->m_triggerData); updateGLScopeDisplay(); } } @@ -653,8 +656,11 @@ bool ScopeVisNG::handleMessage(const Message& message) MsgScopeVisNGRemoveTrigger& conf = (MsgScopeVisNGRemoveTrigger&) message; uint32_t triggerIndex = conf.getTriggerIndex(); - if (triggerIndex < m_triggerConditions.size()) { + if (triggerIndex < m_triggerConditions.size()) + { + TriggerCondition *triggerCondition = m_triggerConditions[triggerIndex]; m_triggerConditions.erase(m_triggerConditions.begin() + triggerIndex); + delete triggerCondition; } return true; @@ -671,12 +677,12 @@ bool ScopeVisNG::handleMessage(const Message& message) int nextTriggerIndex = (triggerIndex + (conf.getMoveUp() ? 1 : -1)) % m_triggerConditions.size(); - TriggerCondition nextTrigger = m_triggerConditions[nextTriggerIndex]; + TriggerCondition *nextTrigger = m_triggerConditions[nextTriggerIndex]; m_triggerConditions[nextTriggerIndex] = m_triggerConditions[triggerIndex]; m_triggerConditions[triggerIndex] = nextTrigger; computeDisplayTriggerLevels(); - m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex].m_triggerData); + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex]->m_triggerData); updateGLScopeDisplay(); return true; @@ -690,7 +696,7 @@ bool ScopeVisNG::handleMessage(const Message& message) { m_focusedTriggerIndex = triggerIndex; computeDisplayTriggerLevels(); - m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex].m_triggerData); + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex]->m_triggerData); updateGLScopeDisplay(); } @@ -854,9 +860,9 @@ void ScopeVisNG::computeDisplayTriggerLevels() for (; itData != m_traces.m_tracesData.end(); ++itData) { - if ((m_focusedTriggerIndex < m_triggerConditions.size()) && (m_triggerConditions[m_focusedTriggerIndex].m_projector.getProjectionType() == itData->m_projectionType)) + if ((m_focusedTriggerIndex < m_triggerConditions.size()) && (m_triggerConditions[m_focusedTriggerIndex]->m_projector.getProjectionType() == itData->m_projectionType)) { - float level = m_triggerConditions[m_focusedTriggerIndex].m_triggerData.m_triggerLevel; + float level = m_triggerConditions[m_focusedTriggerIndex]->m_triggerData.m_triggerLevel; float levelPowerLin = level + 1.0f; float levelPowerdB = (100.0f * (level - 1.0f)); float v; diff --git a/sdrgui/dsp/scopevisng.h b/sdrgui/dsp/scopevisng.h index 17d07cadb..59143be21 100644 --- a/sdrgui/dsp/scopevisng.h +++ b/sdrgui/dsp/scopevisng.h @@ -169,7 +169,7 @@ public: { if (triggerIndex < m_triggerConditions.size()) { - triggerData = m_triggerConditions[triggerIndex].m_triggerData; + triggerData = m_triggerConditions[triggerIndex]->m_triggerData; } } @@ -181,7 +181,7 @@ public: } } - const TriggerData& getTriggerData(uint32_t triggerIndex) const { return m_triggerConditions[triggerIndex].m_triggerData; } + const TriggerData& getTriggerData(uint32_t triggerIndex) const { return m_triggerConditions[triggerIndex]->m_triggerData; } const std::vector& getTracesData() const { return m_traces.m_tracesData; } uint32_t getNbTriggers() const { return m_triggerConditions.size(); } @@ -529,12 +529,10 @@ private: m_triggerDelayCount(0), m_triggerCounter(0) { - qDebug("TriggerCondition"); } ~TriggerCondition() { - qDebug("~TriggerCondition"); } void initProjector() @@ -689,13 +687,11 @@ private: TraceControl() : m_projector(Projector::ProjectionReal) { - qDebug("TraceControl::TraceControl"); reset(); } ~TraceControl() { - qDebug("TraceControl::~TraceControl"); } void initProjector(Projector::ProjectionType projectionType) @@ -737,8 +733,18 @@ private: ~Traces() { - if (m_x0) delete[] m_x0; - if (m_x1) delete[] m_x1; + for (std::vector::iterator it = m_tracesControl.begin(); it != m_tracesControl.end(); ++it) { + delete *it; + } + + if (m_x0) { + delete[] m_x0; + } + + if (m_x1) { + delete[] m_x1; + } + m_maxTraceSize = 0; } @@ -937,7 +943,7 @@ private: GLScopeNG* m_glScope; uint32_t m_preTriggerDelay; //!< Pre-trigger delay in number of samples - std::vector m_triggerConditions; //!< Chain of triggers + std::vector m_triggerConditions; //!< Chain of triggers uint32_t m_currentTriggerIndex; //!< Index of current index in the chain uint32_t m_focusedTriggerIndex; //!< Index of the trigger that has focus TriggerState m_triggerState; //!< Current trigger state From 827c9b6b731dd89017125bf4d9e8bf631dd5aaa6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 3 Apr 2018 23:33:55 +0200 Subject: [PATCH 246/956] ScopeNG: first symbol clock synchro implementation --- sdrbase/dsp/projector.cpp | 11 +++++++++-- sdrbase/dsp/projector.h | 3 +++ sdrgui/gui/glscopenggui.cpp | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/sdrbase/dsp/projector.cpp b/sdrbase/dsp/projector.cpp index 6eeea26cd..c7dae638e 100644 --- a/sdrbase/dsp/projector.cpp +++ b/sdrbase/dsp/projector.cpp @@ -23,10 +23,14 @@ Projector::Projector(ProjectionType projectionType) : m_prevArg(0.0f), m_cache(0), m_cacheMaster(true) -{} +{ + m_symSync = new SymbolSynchronizer(); +} Projector::~Projector() -{} +{ + delete m_symSync; +} Real Projector::run(const Sample& s) { @@ -76,6 +80,9 @@ Real Projector::run(const Sample& s) v = dPhi; } break; + case ProjectionClock: + v = m_symSync->run(s); + break; case ProjectionReal: default: v = s.m_real / SDR_RX_SCALEF; diff --git a/sdrbase/dsp/projector.h b/sdrbase/dsp/projector.h index 38db2f402..1370fca44 100644 --- a/sdrbase/dsp/projector.h +++ b/sdrbase/dsp/projector.h @@ -17,6 +17,8 @@ #include "dsptypes.h" +class SymbolSynchronizer; + class Projector { public: @@ -47,4 +49,5 @@ private: Real m_prevArg; Real *m_cache; bool m_cacheMaster; + SymbolSynchronizer *m_symSync; }; diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 12f043b35..863a0211b 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -1152,6 +1152,7 @@ void GLScopeNGGUI::fillProjectionCombo(QComboBox* comboBox) comboBox->addItem("MagdB", Projector::ProjectionMagDB); comboBox->addItem("Phi", Projector::ProjectionPhase); comboBox->addItem("dPhi", Projector::ProjectionDPhase); + comboBox->addItem("Clk", Projector::ProjectionClock); } void GLScopeNGGUI::disableLiveMode(bool disable) From 9fd33a4101b8971046ca3ea9d953d1981f3cdc47 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 4 Apr 2018 02:03:37 +0200 Subject: [PATCH 247/956] Removed liquid-dsp internal build and dependencies. Abandon clock recovery in ScopeNG --- CMakeLists.txt | 1 - liquiddsp/CMakeLists.txt | 493 ------------------ liquiddsp/cmake/Modules/CheckCPUID.cmake | 49 -- .../cmake/Modules/CheckRequiredFunction.cmake | 16 - liquiddsp/cmake/Modules/cmcpuid.c | 309 ----------- liquiddsp/cmake/Modules/cmcpuid2.c | 113 ---- sdrbase/CMakeLists.txt | 4 - sdrbase/dsp/projector.cpp | 6 - sdrbase/dsp/projector.h | 4 - sdrbase/dsp/symsync.cpp | 47 +- sdrbase/dsp/symsync.h | 4 +- sdrgui/gui/glscopenggui.cpp | 1 - 12 files changed, 43 insertions(+), 1004 deletions(-) delete mode 100644 liquiddsp/CMakeLists.txt delete mode 100644 liquiddsp/cmake/Modules/CheckCPUID.cmake delete mode 100644 liquiddsp/cmake/Modules/CheckRequiredFunction.cmake delete mode 100644 liquiddsp/cmake/Modules/cmcpuid.c delete mode 100644 liquiddsp/cmake/Modules/cmcpuid2.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 544f8cec5..05203f365 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -218,7 +218,6 @@ add_subdirectory(httpserver) add_subdirectory(logging) add_subdirectory(qrtplib) add_subdirectory(swagger) -add_subdirectory(liquiddsp) include_directories( ${CMAKE_CURRENT_BINARY_DIR} diff --git a/liquiddsp/CMakeLists.txt b/liquiddsp/CMakeLists.txt deleted file mode 100644 index a1c3f9b1a..000000000 --- a/liquiddsp/CMakeLists.txt +++ /dev/null @@ -1,493 +0,0 @@ -project(liquiddsp) - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") - -file(READ "${LIQUIDDSPSRC}/include/liquid.h" liquid_h) -string(REGEX MATCH "\\#define LIQUID_VERSION[ ]+\"([0-9]+\\.[0-9]+\\.[0-9]+)\"" LIQUID_VERSION_MATCHES "${liquid_h}") -if(NOT LIQUID_VERSION_MATCHES) - message(FATAL_ERROR "Failed to extract version number from liquid.h") -endif(NOT LIQUID_VERSION_MATCHES) -set(LIQUID_VERSION ${CMAKE_MATCH_1}) - -message("-- Configuring for liquid-dsp ${LIQUID_VERSION}") - -include(CheckCPUID) -include(CheckRequiredFunction) -include(CheckTypeSize) - -include_directories(${LIQUIDDSPSRC}) -include_directories(${LIQUIDDSPSRC}/include) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) - -CHECK_INCLUDE_FILE(sys/resource.h HAVE_SYS_RESOURCE_H) - -CHECK_INCLUDE_FILE(complex.h HAVE_COMPLEX_H) -CHECK_INCLUDE_FILE(float.h HAVE_FLOAT_H) -CHECK_INCLUDE_FILE(getopt.h HAVE_GETOPT_H) -CHECK_INCLUDE_FILE(inttypes.h HAVE_INTTYPES_H) -CHECK_INCLUDE_FILE(memory.h HAVE_MEMORY_H) -CHECK_INCLUDE_FILE(unistd.h HAVE_UNISTD_H) - -CHECK_INCLUDE_FILE(mmintrin.h HAVE_MMINTRIN_H) -CHECK_INCLUDE_FILE(emmintrin.h HAVE_EMMINTRIN_H) -CHECK_INCLUDE_FILE(immintrin.h HAVE_IMMINTRIN_H) -CHECK_INCLUDE_FILE(pmmintrin.h HAVE_PMMINTRIN_H) -CHECK_INCLUDE_FILE(smmintrin.h HAVE_SMMINTRIN_H) -CHECK_INCLUDE_FILE(tmmintrin.h HAVE_TMMINTRIN_H) -CHECK_INCLUDE_FILE(xmmintrin.h HAVE_XMMINTRIN_H) - -CHECK_INCLUDE_FILE(fec.h HAVE_FEC_H) -CHECK_INCLUDE_FILE(fftw3.h HAVE_FFTW3_H) - -CHECK_TYPE_SIZE(int SIZEOF_INT) -CHECK_TYPE_SIZE("unsigned int" SIZEOF_UNSIGNED_INT) - -CHECK_REQUIRED_FUNCTION(sinf m HAVE_SINF) -CHECK_REQUIRED_FUNCTION(cosf m HAVE_COSF) -CHECK_REQUIRED_FUNCTION(expf m HAVE_EXPF) -CHECK_REQUIRED_FUNCTION(cargf m HAVE_CARGF) -CHECK_REQUIRED_FUNCTION(cexpf m HAVE_CEXPF) -CHECK_REQUIRED_FUNCTION(crealf m HAVE_CREALF) -CHECK_REQUIRED_FUNCTION(cimagf m HAVE_CIMAGF) -CHECK_REQUIRED_FUNCTION(sqrtf m HAVE_SQRTF) - -CHECK_LIBRARY_EXISTS(m sqrtf "" HAVE_LIBM) -if (HAVE_FEC_H) - CHECK_LIBRARY_EXISTS(fec create_viterbi27 "" HAVE_LIBFEC) - if (NOT HAVE_LIBFEC) - unset(HAVE_FEC_H CACHE) - endif () -endif () -if (HAVE_FFTW3_H) - CHECK_LIBRARY_EXISTS(fftw3f fftwf_plan_dft_1d "" HAVE_LIBFFTW3F) - if (NOT HAVE_LIBFFTW3F) - unset(HAVE_FFTW3_H CACHE) - endif () -endif () - -CHECK_CPUID("mmx" HAVE_MMX ${LIQUID_FORCE_X86_MMX}) -CHECK_CPUID("sse" HAVE_SSE ${LIQUID_FORCE_X86_SSE}) -CHECK_CPUID("sse2" HAVE_SSE2 ${LIQUID_FORCE_X86_SSE2}) -CHECK_CPUID("sse3" HAVE_SSE3 ${LIQUID_FORCE_X86_SSE3}) -CHECK_CPUID("ssse3" HAVE_SSSE3 ${LIQUID_FORCE_X86_SSSE3}) -CHECK_CPUID("sse41" HAVE_SSE41 ${LIQUID_FORCE_X86_SSE41}) -CHECK_CPUID("sse42" HAVE_SSE42 ${LIQUID_FORCE_X86_SSE42}) -CHECK_CPUID("avx" HAVE_AVX ${LIQUID_FORCE_X86_AVX}) -CHECK_CPUID("vmx" HAVE_ALTIVEC ${LIQUID_FORCE_PPC_ALTIVEC}) -CHECK_CPUID("neon" HAVE_NEON ${LIQUID_FORCE_ARM_NEON}) -CHECK_CPUID("neon64" HAVE_NEON64 ${LIQUID_FORCE_ARM64_NEON}) - -# -# Build compilation extra flags -# -if (CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") - if (HAVE_AVX) - set(_EXTRA_C_FLAGS "-mavx") - elseif (HAVE_SSE42) - set(_EXTRA_C_FLAGS "-msse4.2") - elseif (HAVE_SSE41) - set(_EXTRA_C_FLAGS "-msse4.1") - elseif (HAVE_SSSE3) - set(_EXTRA_C_FLAGS "-mssse3") - elseif (HAVE_SSE3) - set(_EXTRA_C_FLAGS "-msse3") - elseif (HAVE_SSE2) - set(_EXTRA_C_FLAGS "-msse2") - elseif (HAVE_SSE) - set(_EXTRA_C_FLAGS "-msse") - elseif (HAVE_MMX) - set(_EXTRA_C_FLAGS "-mmmx") - elseif (HAVE_ALTIVEC) - if (CMAKE_SYSTEM_NAME MATCHES "Darwin") - set(_EXTRA_C_FLAGS "-fno-common -faltivec") - else () - set(_EXTRA_C_FLAGS "-maltivec") - endif () - elseif (HAVE_NEON) - set(_EXTRA_C_FLAGS "-mfpu=neon-vfpv4") - elseif (HAVE_NEON64) - # No extra flags needed - endif () - set(_EXTRA_C_FLAGS "${_EXTRA_C_FLAGS} -ffast-math") -endif () - -# -# MODULE : agc - automatic gain control -# - -set(agc_SOURCES - ${LIQUIDDSPSRC}/src/agc/src/agc_crcf.c - ${LIQUIDDSPSRC}/src/agc/src/agc_rrrf.c - ) - -# -# MODULE : audio -# - -set(audio_SOURCES - ${LIQUIDDSPSRC}/src/audio/src/cvsd.c - ) - -# -# MODULE : buffer -# - -set(buffer_SOURCES - ${LIQUIDDSPSRC}/src/buffer/src/bufferf.c - ${LIQUIDDSPSRC}/src/buffer/src/buffercf.c - ) - -# -# MODULE : channel -# - -set(channel_SOURCES - ${LIQUIDDSPSRC}/src/channel/src/channel_cccf.c - ) - -# -# MODULE : dotprod -# - -set(dotprod_C_SOURCES - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_cccf.c - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_crcf.c - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_rrrf.c - ${LIQUIDDSPSRC}/src/dotprod/src/sumsq.c - ) - -# PowerPC AltiVec -set(dotprod_ALTIVEC_SOURCES - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_cccf.c - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_crcf.av.c - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_rrrf.av.c - ${LIQUIDDSPSRC}/src/dotprod/src/sumsq.c - ) - -# Intel MMX/SSE/AVX -set(dotprod_SSE_SOURCES - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_cccf.mmx.c - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_crcf.mmx.c - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_rrrf.mmx.c - ${LIQUIDDSPSRC}/src/dotprod/src/sumsq.mmx.c - ) - -# ARM NEON -set(dotprod_NEON_SOURCES - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_cccf.neon.c - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_crcf.neon.c - ${LIQUIDDSPSRC}/src/dotprod/src/dotprod_rrrf.neon.c - ${LIQUIDDSPSRC}/src/dotprod/src/sumsq.c - ) - -if (LIQUID_SIMDOVERRIDE) - set(dotprod_SOURCES ${dotprod_C_SOURCES}) -elseif (HAVE_SSE41 AND HAVE_SMMINTRIN_H) - set(dotprod_SOURCES ${dotprod_SSE_SOURCES}) -elseif (HAVE_SSE3 AND HAVE_PMMINTRIN_H) - set(dotprod_SOURCES ${dotprod_SSE_SOURCES}) -elseif (HAVE_SSE2 AND HAVE_EMMINTRIN_H) - unset(HAVE_PMMINTRIN_H CACHE) # Unset otherwise SSE3 code kicks in. - set(dotprod_SOURCES ${dotprod_SSE_SOURCES}) -elseif (HAVE_ALTIVEC) - set(dotprod_SOURCES ${dotprod_ALTIVEC_SOURCES}) -elseif (HAVE_NEON OR HAVE_NEON64) - set(dotprod_SOURCES ${dotprod_NEON_SOURCES}) -else () - set(dotprod_SOURCES ${dotprod_C_SOURCES}) -endif () - -# -# MODULE : equalization -# - -set(equalization_SOURCES - ${LIQUIDDSPSRC}/src/equalization/src/equalizer_cccf.c - ${LIQUIDDSPSRC}/src/equalization/src/equalizer_rrrf.c - ) - -# -# MODULE : fec - forward error-correction -# -set(fec_SOURCES - ${LIQUIDDSPSRC}/src/fec/src/crc.c - ${LIQUIDDSPSRC}/src/fec/src/fec.c - ${LIQUIDDSPSRC}/src/fec/src/fec_conv.c - ${LIQUIDDSPSRC}/src/fec/src/fec_conv_poly.c - ${LIQUIDDSPSRC}/src/fec/src/fec_conv_pmatrix.c - ${LIQUIDDSPSRC}/src/fec/src/fec_conv_punctured.c - ${LIQUIDDSPSRC}/src/fec/src/fec_golay2412.c - ${LIQUIDDSPSRC}/src/fec/src/fec_hamming74.c - ${LIQUIDDSPSRC}/src/fec/src/fec_hamming84.c - ${LIQUIDDSPSRC}/src/fec/src/fec_hamming128.c - ${LIQUIDDSPSRC}/src/fec/src/fec_hamming1511.c - ${LIQUIDDSPSRC}/src/fec/src/fec_hamming3126.c - ${LIQUIDDSPSRC}/src/fec/src/fec_hamming128_gentab.c - ${LIQUIDDSPSRC}/src/fec/src/fec_pass.c - ${LIQUIDDSPSRC}/src/fec/src/fec_rep3.c - ${LIQUIDDSPSRC}/src/fec/src/fec_rep5.c - ${LIQUIDDSPSRC}/src/fec/src/fec_rs.c - ${LIQUIDDSPSRC}/src/fec/src/fec_secded2216.c - ${LIQUIDDSPSRC}/src/fec/src/fec_secded3932.c - ${LIQUIDDSPSRC}/src/fec/src/fec_secded7264.c - ${LIQUIDDSPSRC}/src/fec/src/interleaver.c - ${LIQUIDDSPSRC}/src/fec/src/packetizer.c - ${LIQUIDDSPSRC}/src/fec/src/sumproduct.c - ) - -# -# MODULE : fft - fast Fourier transforms, discrete sine/cosine transforms, etc. -# -set(fft_SOURCES - ${LIQUIDDSPSRC}/src/fft/src/fftf.c - ${LIQUIDDSPSRC}/src/fft/src/spgramcf.c - ${LIQUIDDSPSRC}/src/fft/src/spgramf.c - ${LIQUIDDSPSRC}/src/fft/src/fft_utilities.c - ) - -# -# MODULE : filter -# -set(filter_SOURCES - ${LIQUIDDSPSRC}/src/filter/src/bessel.c - ${LIQUIDDSPSRC}/src/filter/src/butter.c - ${LIQUIDDSPSRC}/src/filter/src/cheby1.c - ${LIQUIDDSPSRC}/src/filter/src/cheby2.c - ${LIQUIDDSPSRC}/src/filter/src/ellip.c - ${LIQUIDDSPSRC}/src/filter/src/filter_rrrf.c - ${LIQUIDDSPSRC}/src/filter/src/filter_crcf.c - ${LIQUIDDSPSRC}/src/filter/src/filter_cccf.c - ${LIQUIDDSPSRC}/src/filter/src/firdes.c - ${LIQUIDDSPSRC}/src/filter/src/firdespm.c - ${LIQUIDDSPSRC}/src/filter/src/fnyquist.c - ${LIQUIDDSPSRC}/src/filter/src/gmsk.c - ${LIQUIDDSPSRC}/src/filter/src/group_delay.c - ${LIQUIDDSPSRC}/src/filter/src/hM3.c - ${LIQUIDDSPSRC}/src/filter/src/iirdes.pll.c - ${LIQUIDDSPSRC}/src/filter/src/iirdes.c - ${LIQUIDDSPSRC}/src/filter/src/lpc.c - ${LIQUIDDSPSRC}/src/filter/src/rcos.c - ${LIQUIDDSPSRC}/src/filter/src/rkaiser.c - ${LIQUIDDSPSRC}/src/filter/src/rrcos.c - ) - -# -# MODULE : framing -# -set(framing_SOURCES - ${LIQUIDDSPSRC}/src/framing/src/bpacketgen.c - ${LIQUIDDSPSRC}/src/framing/src/bpacketsync.c - ${LIQUIDDSPSRC}/src/framing/src/bpresync_cccf.c - ${LIQUIDDSPSRC}/src/framing/src/bsync_rrrf.c - ${LIQUIDDSPSRC}/src/framing/src/bsync_crcf.c - ${LIQUIDDSPSRC}/src/framing/src/bsync_cccf.c - ${LIQUIDDSPSRC}/src/framing/src/detector_cccf.c - ${LIQUIDDSPSRC}/src/framing/src/framedatastats.c - ${LIQUIDDSPSRC}/src/framing/src/framesyncstats.c - ${LIQUIDDSPSRC}/src/framing/src/framegen64.c - ${LIQUIDDSPSRC}/src/framing/src/framesync64.c - ${LIQUIDDSPSRC}/src/framing/src/flexframegen.c - ${LIQUIDDSPSRC}/src/framing/src/flexframesync.c - ${LIQUIDDSPSRC}/src/framing/src/gmskframegen.c - ${LIQUIDDSPSRC}/src/framing/src/gmskframesync.c - ${LIQUIDDSPSRC}/src/framing/src/msourcecf.c - ${LIQUIDDSPSRC}/src/framing/src/ofdmflexframegen.c - ${LIQUIDDSPSRC}/src/framing/src/ofdmflexframesync.c - ${LIQUIDDSPSRC}/src/framing/src/presync_cccf.c - ${LIQUIDDSPSRC}/src/framing/src/symstreamcf.c - ${LIQUIDDSPSRC}/src/framing/src/symtrack_cccf.c - ${LIQUIDDSPSRC}/src/framing/src/qdetector_cccf.c - ${LIQUIDDSPSRC}/src/framing/src/qpacketmodem.c - ${LIQUIDDSPSRC}/src/framing/src/qpilotgen.c - ${LIQUIDDSPSRC}/src/framing/src/qpilotsync.c - ) - -# -# MODULE : math -# -set(math_SOURCES - ${LIQUIDDSPSRC}/src/math/src/poly.c - ${LIQUIDDSPSRC}/src/math/src/polyc.c - ${LIQUIDDSPSRC}/src/math/src/polyf.c - ${LIQUIDDSPSRC}/src/math/src/polycf.c - ${LIQUIDDSPSRC}/src/math/src/math.c - ${LIQUIDDSPSRC}/src/math/src/math.bessel.c - ${LIQUIDDSPSRC}/src/math/src/math.gamma.c - ${LIQUIDDSPSRC}/src/math/src/math.complex.c - ${LIQUIDDSPSRC}/src/math/src/math.trig.c - ${LIQUIDDSPSRC}/src/math/src/modular_arithmetic.c - ${LIQUIDDSPSRC}/src/math/src/windows.c - ) - -# -# MODULE : matrix -# -set(matrix_SOURCES - ${LIQUIDDSPSRC}/src/matrix/src/matrix.c - ${LIQUIDDSPSRC}/src/matrix/src/matrixf.c - ${LIQUIDDSPSRC}/src/matrix/src/matrixc.c - ${LIQUIDDSPSRC}/src/matrix/src/matrixcf.c - ${LIQUIDDSPSRC}/src/matrix/src/smatrix.common.c - ${LIQUIDDSPSRC}/src/matrix/src/smatrixb.c - ${LIQUIDDSPSRC}/src/matrix/src/smatrixf.c - ${LIQUIDDSPSRC}/src/matrix/src/smatrixi.c - ) - -# -# MODULE : modem -# -set(modem_SOURCES - ${LIQUIDDSPSRC}/src/modem/src/ampmodem.c - ${LIQUIDDSPSRC}/src/modem/src/cpfskdem.c - ${LIQUIDDSPSRC}/src/modem/src/cpfskmod.c - ${LIQUIDDSPSRC}/src/modem/src/fskdem.c - ${LIQUIDDSPSRC}/src/modem/src/fskmod.c - ${LIQUIDDSPSRC}/src/modem/src/gmskdem.c - ${LIQUIDDSPSRC}/src/modem/src/gmskmod.c - ${LIQUIDDSPSRC}/src/modem/src/modemf.c - ${LIQUIDDSPSRC}/src/modem/src/modem_utilities.c - ${LIQUIDDSPSRC}/src/modem/src/modem_apsk_const.c - ${LIQUIDDSPSRC}/src/modem/src/modem_arb_const.c - ) - -# -# MODULE : multichannel -# -set(multichannel_SOURCES - ${LIQUIDDSPSRC}/src/multichannel/src/firpfbch_crcf.c - ${LIQUIDDSPSRC}/src/multichannel/src/firpfbch_cccf.c - ${LIQUIDDSPSRC}/src/multichannel/src/ofdmframe.common.c - ${LIQUIDDSPSRC}/src/multichannel/src/ofdmframegen.c - ${LIQUIDDSPSRC}/src/multichannel/src/ofdmframesync.c - ) - -# -# MODULE : nco - numerically-controlled oscillator -# -set(nco_SOURCES - ${LIQUIDDSPSRC}/src/nco/src/nco_crcf.c - ${LIQUIDDSPSRC}/src/nco/src/nco.utilities.c - ) - -# -# MODULE : optim - optimization -# -set(optim_SOURCES - ${LIQUIDDSPSRC}/src/optim/src/chromosome.c - ${LIQUIDDSPSRC}/src/optim/src/gasearch.c - ${LIQUIDDSPSRC}/src/optim/src/gradsearch.c - ${LIQUIDDSPSRC}/src/optim/src/optim.common.c - ${LIQUIDDSPSRC}/src/optim/src/qnsearch.c - ${LIQUIDDSPSRC}/src/optim/src/utilities.c - ) - -# -# MODULE : quantization -# -set(quantization_SOURCES - ${LIQUIDDSPSRC}/src/quantization/src/compand.c - ${LIQUIDDSPSRC}/src/quantization/src/quantizercf.c - ${LIQUIDDSPSRC}/src/quantization/src/quantizerf.c - ${LIQUIDDSPSRC}/src/quantization/src/quantizer.inline.c - ) - -# -# MODULE : random -# -set(random_SOURCES - ${LIQUIDDSPSRC}/src/random/src/rand.c - ${LIQUIDDSPSRC}/src/random/src/randn.c - ${LIQUIDDSPSRC}/src/random/src/randexp.c - ${LIQUIDDSPSRC}/src/random/src/randweib.c - ${LIQUIDDSPSRC}/src/random/src/randgamma.c - ${LIQUIDDSPSRC}/src/random/src/randnakm.c - ${LIQUIDDSPSRC}/src/random/src/randricek.c - ${LIQUIDDSPSRC}/src/random/src/scramble.c - ) - -# -# MODULE : sequence -# -set(sequence_SOURCES - ${LIQUIDDSPSRC}/src/sequence/src/bsequence.c - ${LIQUIDDSPSRC}/src/sequence/src/msequence.c - ) - -# -# MODULE : utility -# -set(utility_SOURCES - ${LIQUIDDSPSRC}/src/utility/src/bshift_array.c - ${LIQUIDDSPSRC}/src/utility/src/byte_utilities.c - ${LIQUIDDSPSRC}/src/utility/src/msb_index.c - ${LIQUIDDSPSRC}/src/utility/src/pack_bytes.c - ${LIQUIDDSPSRC}/src/utility/src/shift_array.c - ) - -# -# MODULE : vector -# -set(vector_SOURCES - ${LIQUIDDSPSRC}/src/vector/src/vectorf_add.port.c - ${LIQUIDDSPSRC}/src/vector/src/vectorf_norm.port.c - ${LIQUIDDSPSRC}/src/vector/src/vectorf_mul.port.c - ${LIQUIDDSPSRC}/src/vector/src/vectorf_trig.port.c - ${LIQUIDDSPSRC}/src/vector/src/vectorcf_add.port.c - ${LIQUIDDSPSRC}/src/vector/src/vectorcf_norm.port.c - ${LIQUIDDSPSRC}/src/vector/src/vectorcf_mul.port.c - ${LIQUIDDSPSRC}/src/vector/src/vectorcf_trig.port.c - ) - -# -# Library -# -set(liquiddsp_SOURCES - ${LIQUIDDSPSRC}/src/libliquid.c - ${agc_SOURCES} - ${audio_SOURCES} - ${buffer_SOURCES} - ${channel_SOURCES} - ${dotprod_SOURCES} - ${equalization_SOURCES} - ${fec_SOURCES} - ${fft_SOURCES} - ${filter_SOURCES} - ${framing_SOURCES} - ${math_SOURCES} - ${matrix_SOURCES} - ${modem_SOURCES} - ${multichannel_SOURCES} - ${nco_SOURCES} - ${optim_SOURCES} - ${quantization_SOURCES} - ${random_SOURCES} - ${sequence_SOURCES} - ${utility_SOURCES} - ${vector_SOURCES} - ) - - -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_SHARED) - -add_library(liquiddsp SHARED - ${liquiddsp_SOURCES} - ${liquiddsp_HEADERS_MOC} -) - -if (HAVE_LIBM) - target_link_libraries(liquiddsp m) -endif () - -if (HAVE_LIBFEC) - target_link_libraries(liquiddsp fec) -endif () - -if (NOT LIQUID_FFTOVERRIDE AND HAVE_LIBFFTW3F) - target_link_libraries(liquiddsp fftw3f) -endif () - -#set_property(TARGET liquid PROPERTY OUTPUT_NAME "liquid") -#set_property(TARGET liquid PROPERTY SOVERSION "${LIQUID_VERSION}") -set_property(TARGET liquiddsp PROPERTY COMPILE_FLAGS "${_EXTRA_C_FLAGS}") diff --git a/liquiddsp/cmake/Modules/CheckCPUID.cmake b/liquiddsp/cmake/Modules/CheckCPUID.cmake deleted file mode 100644 index 139c186b3..000000000 --- a/liquiddsp/cmake/Modules/CheckCPUID.cmake +++ /dev/null @@ -1,49 +0,0 @@ -# example usage: CHECK_CPUID("mmx" HAVE_MMX [TRUE|FALSE]) -macro(CHECK_CPUID CHARACTERISTIC VARIABLE) - if (${ARGC} GREATER 2) - set(_CPUID_FORCE ${ARGV2}) - else () - unset(_CPUID_FORCE) - endif () - - message("-- Checking for CPU characteristic ${CHARACTERISTIC}") - - if (DEFINED _CPUID_FORCE AND _CPUID_FORCE) - set(_CPUID_CHARACTERISTIC_FOUND ${_CPUID_FORCE}) - set(_FORCED "(forced)") - else () - if (CMAKE_CROSSCOMPILING) - # When cross compiling, we need to test each characteristic. - try_compile(CPUID_COMPILE_RESULT - ${CMAKE_CURRENT_BINARY_DIR} - SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cmcpuid.c - COMPILE_DEFINITIONS "-DCROSS_COMPILING" "-DTEST_${CHARACTERISTIC}" - OUTPUT_VARIABLE CPUID_COMPILE_OUTPUT - ) - set(_CPUID_CHARACTERISTIC_FOUND ${CPUID_COMPILE_RESULT}) - else () - if (NOT _CPUID_CHARACTERISTICS) - try_run(CPUID_RUN_RESULT CPUID_COMPILE_RESULT - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cmcpuid.c - RUN_OUTPUT_VARIABLE CPUID_CHARACTERISTICS - ) - set(_CPUID_CHARACTERISTICS "${CPUID_CHARACTERISTICS}" CACHE INTERNAL "CPU Characteristics") - endif () - if (${_CPUID_CHARACTERISTICS} MATCHES "@${CHARACTERISTIC}@") - set(_CPUID_CHARACTERISTIC_FOUND TRUE) - else () - set(_CPUID_CHARACTERISTIC_FOUND FALSE) - endif () - endif () - unset(_FORCED) - endif () - - if (_CPUID_CHARACTERISTIC_FOUND) - message("-- Checking for CPU characteristic ${CHARACTERISTIC} - found ${_FORCED}") - else () - message("-- Checking for CPU characteristic ${CHARACTERISTIC} - not found") - endif () - set(${VARIABLE} ${_CPUID_CHARACTERISTIC_FOUND} CACHE INTERNAL "Check for CPU characteristic ${CHARACTERISTIC}" FORCE) -endmacro() - diff --git a/liquiddsp/cmake/Modules/CheckRequiredFunction.cmake b/liquiddsp/cmake/Modules/CheckRequiredFunction.cmake deleted file mode 100644 index 33f846a55..000000000 --- a/liquiddsp/cmake/Modules/CheckRequiredFunction.cmake +++ /dev/null @@ -1,16 +0,0 @@ -include(CheckFunctionExists) -include(CheckLibraryExists) - -macro(CHECK_REQUIRED_FUNCTION FUNCTION LIBRARY VARIABLE) - # First try without any library. - CHECK_FUNCTION_EXISTS("${FUNCTION}" ${VARIABLE}) - if (NOT ${VARIABLE}) - unset(${VARIABLE} CACHE) - # Retry with the library specified - CHECK_LIBRARY_EXISTS("${LIBRARY}" "${FUNCTION}" "" ${VARIABLE}) - endif () - if (NOT ${VARIABLE}) - message(FATAL_ERROR "Required function '${FUNCTION}' not found") - endif () -endmacro () - diff --git a/liquiddsp/cmake/Modules/cmcpuid.c b/liquiddsp/cmake/Modules/cmcpuid.c deleted file mode 100644 index a2b22c83c..000000000 --- a/liquiddsp/cmake/Modules/cmcpuid.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (c) 2016-present Orlando Bassotto - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef CROSS_COMPILING -/* - * This is executed when targeting the host. - */ -#include -#include -#include -#if defined(_MSC_VER) -#include -#endif - -#define ISA_X86_MMX (1 << 0) -#define ISA_X86_SSE (1 << 1) -#define ISA_X86_SSE2 (1 << 2) -#define ISA_X86_SSE3 (1 << 3) -#define ISA_X86_SSSE3 (1 << 4) -#define ISA_X86_SSE41 (1 << 5) -#define ISA_X86_SSE42 (1 << 6) -#define ISA_X86_AVX1 (1 << 7) -#define ISA_X86_AVX2 (1 << 8) -#define ISA_PPC_VMX (1 << 9) -#define ISA_ARM_NEON (1 << 10) -#define ISA_ARM64_NEON (1 << 11) - -#if !(defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_AMD64)) -static jmp_buf return_jmp; - -static void sighandler(int signo) -{ - longjmp(return_jmp, 1); -} -#endif - -static void -siginit(void) -{ -#if !(defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_AMD64)) - signal(SIGSEGV, sighandler); -#ifdef SIGBUS - signal(SIGBUS, sighandler); -#endif - signal(SIGILL, sighandler); -#endif -} - -static unsigned -check_isa(void) -{ -#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_AMD64) -#if defined(_M_IX86) || defined(_M_AMD64) - int regs[4]; -#endif - unsigned eax, ebx, ecx, edx; - int supported; - unsigned isa = 0; - -#if defined(_M_IX86) || defined(_M_AMD64) - __cpuid(regs, 1); - ecx = regs[2]; - edx = regs[3]; -#else - eax = 0x00000001; - __asm__ __volatile__( - "cpuid" - :"=c"(ecx) // %ecx contains large feature flag set - :"0"(eax) // call with 0x1 - :"%eax","%ebx","%edx" - ); -#endif - - if (edx & (1 << 23)) { - /* MMX */ - isa |= ISA_X86_MMX; - } - - if (edx & (1 << 25)) { - /* SSE */ - isa |= ISA_X86_SSE; - } - - if (edx & (1 << 26)) { - /* SSE */ - isa |= ISA_X86_SSE2; - } - - if (ecx & (1 << 0)) { - /* SSE3 */ - isa |= ISA_X86_SSE3; - } - - if (ecx & (1 << 9)) { - /* SSSE3 */ - isa |= ISA_X86_SSSE3; - } - - if (ecx & (1 << 19)) { - /* SSE4.1 */ - isa |= ISA_X86_SSE41; - } - - if (ecx & (1 << 20)) { - /* SSE4.2 */ - isa |= ISA_X86_SSE42; - } - - if (ecx & (1 << 28)) { - /* AVX1 */ - isa |= ISA_X86_AVX1; - -#if defined(_M_IX86) || defined(_M_AMD64) - __cpuid(regs, 7); - ebx = regs[1]; -#else - eax = 0x00000007; - __asm__ __volatile__( - "cpuid" - : "=b"(ebx) - : "0"(eax) - : "%eax", "%ecx", "%edx" - ); -#endif - - if (ebx & (1 << 5)) { - /* AVX2 */ - isa |= ISA_X86_AVX2; - } - } - - return isa; -#elif defined(__powerpc__) || defined(__powerpc64__) || defined(__powerpc64le__) - if (setjmp(return_jmp) == 1) - return 0; - - __asm__ __volatile__(".long 0x10011000"); - - return ISA_PPC_VMX; -#elif defined(__arm__) || defined(_M_ARM) - /* Windows on ARM is always Thumb-2, and NEON is always available. */ -#if !defined(_M_ARM) - if (setjmp(return_jmp) == 1) - return 0; - -#if defined(__thumb__) - __asm__ __volatile__(".short 0xef12"); - __asm__ __volatile__(".short 0x0054"); -#else - __asm__ __volatile__(".word 0xf2120054"); -#endif -#endif - - return ISA_ARM_NEON; -#elif defined(__aarch64__) || defined(__arm64__) - return ISA_ARM64_NEON; -#else - return 0; -#endif -} - -int -main() -{ - unsigned isa; - - siginit(); - isa = check_isa(); - - if (isa & ISA_X86_MMX) { - printf("@mmx@"); - } - if (isa & ISA_X86_SSE) { - printf("@sse@"); - } - if (isa & ISA_X86_SSE2) { - printf("@sse2@"); - } - if (isa & ISA_X86_SSE3) { - printf("@sse3@"); - } - if (isa & ISA_X86_SSSE3) { - printf("@ssse3@"); - } - if (isa & ISA_X86_SSE41) { - printf("@sse41@"); - } - if (isa & ISA_X86_SSE42) { - printf("@sse42@"); - } - if (isa & ISA_X86_AVX1) { - printf("@avx@"); - } - if (isa & ISA_X86_AVX2) { - printf("@avx2@"); - } - if (isa & ISA_PPC_VMX) { - printf("@vmx@"); - } - if (isa & ISA_ARM_NEON) { - printf("@neon@"); - } - if (isa & ISA_ARM64_NEON) { - printf("@neon64@"); - } - printf("\n"); - return 0; -} -#else -/* - * This is just compiled when targeting an architecture/system different - * than the host, it is just compiled hence the checks are on the compiler - * features available and not on the real CPU characteristics, as such these - * tests may not do what you want, you may need to force the characteristics - * in that case. - * - * A define TEST_xyz must be defined in order to check the support for the - * specified characteristic. - */ - -#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_AMD64) -#define _X86 -#endif - -#if defined(__powerpc__) || defined(__powerpc64__) || defined(__powerpc64le__) -#define _PPC -#endif - -#if defined(__arm__) -#define _ARM -#endif - -#if defined(__aarch64__) || defined(__arm64__) -#define _ARM64 -#endif - -#if defined(TEST_mmx) && !(defined(_X86) && defined(__MMX__)) -#error "MMX not available" -#endif - -#if defined(TEST_sse) && !(defined(_X86) && defined(__SSE__)) -#error "SSE not available" -#endif - -#if defined(TEST_sse2) && !(defined(_X86) && defined(__SSE2__)) -#error "SSE2 not available" -#endif - -#if defined(TEST_sse3) && !(defined(_X86) && defined(__SSE3__)) -#error "SSE3 not available" -#endif - -#if defined(TEST_ssse3) && !(defined(_X86) && defined(__SSSE3__)) -#error "SSSE3 not available" -#endif - -#if defined(TEST_sse41) && !(defined(_X86) && defined(__SSE4_1__)) -#error "SSE4.1 not available" -#endif - -#if defined(TEST_sse42) && !(defined(_X86) && defined(__SSE4_2__)) -#error "SSE4.2 not available" -#endif - -#if defined(TEST_avx) && !(defined(_X86) && defined(__AVX__)) -#error "AVX1 not available" -#endif - -#if defined(TEST_avx2) && !(defined(_X86) && defined(__AVX2__)) -#error "AVX2 not available" -#endif - -#if defined(TEST_vmx) && !(defined(_PPC) && defined(__ALTIVEC__)) -#error "VMX not available" -#endif - -#if defined(TEST_neon) && !(defined(_ARM) && defined(__ARM_NEON__)) -#error "NEON not available" -#endif - -#if defined(TEST_neon64) && !defined(_ARM64) -#error "NEON64 not available" -#endif - -int main() -{ - return 0; -} -#endif - diff --git a/liquiddsp/cmake/Modules/cmcpuid2.c b/liquiddsp/cmake/Modules/cmcpuid2.c deleted file mode 100644 index b6266bd61..000000000 --- a/liquiddsp/cmake/Modules/cmcpuid2.c +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies - * - * Please see distribution for license. - */ - -#include -#include -#include -#include - - -/** - * The supported instruction set on this machine for use in other functions. - * - * These must match up with the numbers used in MultiLib.cmake. - */ -typedef enum instructions_available_e -{ - supports_STANDARD = 1, - supports_SSE41 = 2, - supports_SSE42 = 3, - supports_AVX1 = 4, - supports_AVX2 = 5 -} instructions_available; - - -instructions_available getSupportedInstructionSet() { - - // probes of cpuid with %eax=0000_0001h - enum instructions_available_eax0000_0001h_e - { - probe01_SSE_3 = 1<<0, - probe01_SSE_4_1 = 1<<19, - probe01_SSE_4_2 = 1<<20, - probe01_AVX1 = 1<<28 - }; - - // probes of cpuid with %eax=0000_0007h - enum instructions_available_eax0000_0007h_e - { - probe07_AVX2 = 1<<5 - }; - - // the eax register - int32_t EAX; - // contends of the returned register - int32_t supported; - - // Call cpuid with eax=0x00000001 and get ecx - EAX = 0x00000001; - __asm__("cpuid" - :"=c"(supported) // %ecx contains large feature flag set - :"0"(EAX) // call with 0x1 - :"%eax","%ebx","%edx"); // clobbered - - if(supported & probe01_AVX1) // we have at least AVX1 - { - EAX = 0x00000007; - __asm__("cpuid" - :"=b"(supported) // %ebx contains feature flag AVX2 - :"0"(EAX) // call with 0x7 - :"%eax","%ecx","%edx"); // clobbered - - if(supported & probe07_AVX2) // we have at least AVX2 - { - printf("AVX2 SUPPORTED\n"); - return supports_AVX2; - } - printf("AVX1 SUPPORTED\n"); - return supports_AVX1; - } - else if(supported & probe01_SSE_4_1) // we have at least SSE4.1 - { - printf("SSE4.2 SUPPORTED\n"); - return supports_SSE42; - } - else // we have nothing specifically useful! - { - printf("STANDARD SUPPORTED\n"); - return supports_STANDARD; - } -} - - -int main(void) -{ - instructions_available ia; - ia = getSupportedInstructionSet(); - - switch(ia) - { - case supports_AVX2: - printf("AVX2\n"); - break; - case supports_AVX1: - printf("AVX1\n"); - break; - case supports_SSE42: - printf("SSE42\n"); - break; - case supports_SSE41: - printf("SSE41\n"); - break; - case supports_STANDARD: - printf("STANDARD\n"); - break; - default: - printf("Failed to find supported instruction set, this is an error!\n"); - exit(-1); - } - return ia; -} \ No newline at end of file diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index e90989e35..ec51b06f3 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -46,7 +46,6 @@ set(sdrbase_SOURCES dsp/basebandsamplesource.cpp dsp/nullsink.cpp dsp/recursivefilters.cpp - dsp/symsync.cpp dsp/threadedbasebandsamplesink.cpp dsp/threadedbasebandsamplesource.cpp dsp/wfir.cpp @@ -149,7 +148,6 @@ set(sdrbase_HEADERS dsp/basebandsamplesink.h dsp/basebandsamplesource.h dsp/nullsink.h - dsp/symsync.h dsp/threadedbasebandsamplesink.h dsp/threadedbasebandsamplesource.h dsp/wfir.h @@ -266,7 +264,6 @@ include_directories( ${CMAKE_SOURCE_DIR}/httpserver ${CMAKE_SOURCE_DIR}/qrtplib ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${LIQUIDDSPSRC}/include ) target_link_libraries(sdrbase @@ -274,7 +271,6 @@ target_link_libraries(sdrbase httpserver qrtplib swagger - liquiddsp ) if(FFTW3F_FOUND) diff --git a/sdrbase/dsp/projector.cpp b/sdrbase/dsp/projector.cpp index c7dae638e..a678409f7 100644 --- a/sdrbase/dsp/projector.cpp +++ b/sdrbase/dsp/projector.cpp @@ -16,7 +16,6 @@ /////////////////////////////////////////////////////////////////////////////////// #include "projector.h" -#include "symsync.h" // dependency on liquid-dsp Projector::Projector(ProjectionType projectionType) : m_projectionType(projectionType), @@ -24,12 +23,10 @@ Projector::Projector(ProjectionType projectionType) : m_cache(0), m_cacheMaster(true) { - m_symSync = new SymbolSynchronizer(); } Projector::~Projector() { - delete m_symSync; } Real Projector::run(const Sample& s) @@ -80,9 +77,6 @@ Real Projector::run(const Sample& s) v = dPhi; } break; - case ProjectionClock: - v = m_symSync->run(s); - break; case ProjectionReal: default: v = s.m_real / SDR_RX_SCALEF; diff --git a/sdrbase/dsp/projector.h b/sdrbase/dsp/projector.h index 1370fca44..b0cdf88af 100644 --- a/sdrbase/dsp/projector.h +++ b/sdrbase/dsp/projector.h @@ -17,8 +17,6 @@ #include "dsptypes.h" -class SymbolSynchronizer; - class Projector { public: @@ -30,7 +28,6 @@ public: ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude ProjectionPhase, //!< Calculate phase ProjectionDPhase, //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate - ProjectionClock, //!< Clock projection (symbol synchronization) nbProjectionTypes //!< Gives the number of projections in the enum }; @@ -49,5 +46,4 @@ private: Real m_prevArg; Real *m_cache; bool m_cacheMaster; - SymbolSynchronizer *m_symSync; }; diff --git a/sdrbase/dsp/symsync.cpp b/sdrbase/dsp/symsync.cpp index 3b272f9ad..837f24543 100644 --- a/sdrbase/dsp/symsync.cpp +++ b/sdrbase/dsp/symsync.cpp @@ -24,12 +24,12 @@ SymbolSynchronizer::SymbolSynchronizer() // For now use hardcoded values: // - RRC filter // - 4 samples per symbol - // - 5 sybols delay filter + // - 5 symbols delay filter // - 0.5 filter excess bandwidth factor // - 32 filter elements for the internal polyphase filter m_sync = symsync_crcf_create_rnyquist(LIQUID_FIRFILT_RRC, 4, 5, 0.5f, 32); // - 0.02 loop filter bandwidth factor - symsync_crcf_set_lf_bw(m_sync, 0.02f); + symsync_crcf_set_lf_bw(m_sync, 0.01f); // - 4 samples per symbol output rate symsync_crcf_set_output_rate(m_sync, 4); m_syncSampleCount = 0; @@ -43,20 +43,53 @@ SymbolSynchronizer::~SymbolSynchronizer() Real SymbolSynchronizer::run(const Sample& s) { unsigned int nn; - Real v = 0.0f; + Real v = -1.0f; liquid_float_complex y = (s.m_real / SDR_RX_SCALEF) + (s.m_imag / SDR_RX_SCALEF)*I; symsync_crcf_execute(m_sync, &y, 1, m_z, &nn); for (unsigned int i = 0; i < nn; i++) { - v = (m_syncSampleCount < 2) ? 1.0f : 0.0f; // actual sync is at 0 + if (nn != 1) { + qDebug("SymbolSynchronizer::run: %u", nn); + } - if (m_syncSampleCount < 4) { + if (m_syncSampleCount % 4 == 0) { + v = 1.0f; + } + + if (m_syncSampleCount < 4095) { + m_syncSampleCount++; + } else { + qDebug("SymbolSynchronizer::run: tau: %f", symsync_crcf_get_tau(m_sync)); + m_syncSampleCount = 0; + } + } + + return v; +} + +liquid_float_complex SymbolSynchronizer::runZ(const Sample& s) +{ + unsigned int nn; + liquid_float_complex y = (s.m_real / SDR_RX_SCALEF) + (s.m_imag / SDR_RX_SCALEF)*I; + symsync_crcf_execute(m_sync, &y, 1, m_z, &nn); + + for (unsigned int i = 0; i < nn; i++) + { + if (nn != 1) { + qDebug("SymbolSynchronizer::run: %u", nn); + } + + if (m_syncSampleCount == 0) { + m_z0 = m_z[i]; + } + + if (m_syncSampleCount < 3) { m_syncSampleCount++; } else { m_syncSampleCount = 0; } } - return v; -} \ No newline at end of file + return m_z0; +} diff --git a/sdrbase/dsp/symsync.h b/sdrbase/dsp/symsync.h index 611b839fc..3c4f5d908 100644 --- a/sdrbase/dsp/symsync.h +++ b/sdrbase/dsp/symsync.h @@ -28,9 +28,11 @@ public: ~SymbolSynchronizer(); Real run(const Sample& s); + liquid_float_complex runZ(const Sample& s); private: symsync_crcf m_sync; liquid_float_complex m_z[4+4]; // 4 samples per symbol. One symbol plus extra space + liquid_float_complex m_z0; int m_syncSampleCount; -}; \ No newline at end of file +}; diff --git a/sdrgui/gui/glscopenggui.cpp b/sdrgui/gui/glscopenggui.cpp index 863a0211b..12f043b35 100644 --- a/sdrgui/gui/glscopenggui.cpp +++ b/sdrgui/gui/glscopenggui.cpp @@ -1152,7 +1152,6 @@ void GLScopeNGGUI::fillProjectionCombo(QComboBox* comboBox) comboBox->addItem("MagdB", Projector::ProjectionMagDB); comboBox->addItem("Phi", Projector::ProjectionPhase); comboBox->addItem("dPhi", Projector::ProjectionDPhase); - comboBox->addItem("Clk", Projector::ProjectionClock); } void GLScopeNGGUI::disableLiveMode(bool disable) From 8dfdc1086c5a8cc041f01dcdb5c5a6716151462f Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 4 Apr 2018 23:39:31 +0200 Subject: [PATCH 248/956] PVS-Studio static analysis corrections (4) --- plugins/channelrx/demodbfm/rdsparser.cpp | 4 +- .../channelrx/demoddatv/datvconstellation.h | 2 +- plugins/channelrx/demoddatv/datvdemod.cpp | 12 --- .../demoddatv/leansdr/convolutional.h | 20 ++--- plugins/channelrx/demoddatv/leansdr/dsp.h | 2 +- plugins/channelrx/demoddatv/leansdr/dvb.h | 8 +- plugins/channelrx/demoddatv/leansdr/sdr.h | 2 +- plugins/channelrx/demoddsd/dsddemod.cpp | 2 +- .../samplesink/sdrdaemonsink/udpsinkfec.cpp | 15 ++-- plugins/samplesink/sdrdaemonsink/udpsinkfec.h | 4 +- .../sdrdaemonsource/sdrdaemonsourcebuffer.h | 4 +- qrtplib/rtpsession.cpp | 83 ++++++++++--------- qrtplib/rtpsession.h | 2 +- qrtplib/rtpudptransmitter.cpp | 3 + sdrbase/resources/webapi/doc/html2/index.html | 2 +- swagger/sdrangel/code/html2/index.html | 2 +- .../sdrangel/code/qt5/client/SWGHelpers.cpp | 23 ++--- 17 files changed, 93 insertions(+), 97 deletions(-) diff --git a/plugins/channelrx/demodbfm/rdsparser.cpp b/plugins/channelrx/demodbfm/rdsparser.cpp index 6909e7e7e..faf267806 100644 --- a/plugins/channelrx/demodbfm/rdsparser.cpp +++ b/plugins/channelrx/demodbfm/rdsparser.cpp @@ -933,15 +933,15 @@ void RDSParser::decode_optional_content(int no_groups, unsigned long int *free_f for (int i = no_groups; i == 0; i--) { - ff_pointer = 12 + 16; + ff_pointer = 12 + 16 - 4; while(ff_pointer > 0) { - ff_pointer -= 4; m_g8_label_index = (free_format[i] & (0xf << ff_pointer)); content_length = optional_content_lengths[m_g8_label_index]; ff_pointer -= content_length; m_g8_content = (free_format[i] & (int(std::pow(2, content_length) - 1) << ff_pointer)); + ff_pointer -= 4; /* qDebug() << "RDSParser::decode_optional_content: TMC optional content (" << label_descriptions[m_g8_label_index].c_str() diff --git a/plugins/channelrx/demoddatv/datvconstellation.h b/plugins/channelrx/demoddatv/datvconstellation.h index 3004a7196..f3d21b32c 100644 --- a/plugins/channelrx/demoddatv/datvconstellation.h +++ b/plugins/channelrx/demoddatv/datvconstellation.h @@ -61,7 +61,7 @@ template struct datvconstellation: runnable while (in.readable() >= pixels_per_frame) { - if (!phase) + if ((!phase) && m_objDATVScreen) { m_objDATVScreen->resetImage(); diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 1c653bd0f..94a228380 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -765,7 +765,6 @@ void DATVDemod::InitDATVFramework() void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { - qint16 * ptrBufferToRelease=NULL; float fltI; float fltQ; leansdr::cf32 objIQ; @@ -859,18 +858,7 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect } } - - //********** demodulation ********** - } - - if(ptrBufferToRelease!=NULL) - { - delete ptrBufferToRelease; - } - - - } void DATVDemod::start() diff --git a/plugins/channelrx/demoddatv/leansdr/convolutional.h b/plugins/channelrx/demoddatv/leansdr/convolutional.h index aa751dc06..f103fc420 100644 --- a/plugins/channelrx/demoddatv/leansdr/convolutional.h +++ b/plugins/channelrx/demoddatv/leansdr/convolutional.h @@ -137,10 +137,10 @@ struct deconvol_poly2 ++pin; \ } // Don't shift by more than the operand width - switch (sizeof(Thist) * 8) + switch (sizeof(Thist) /* 8*/) { #if 0 // Not needed yet - avoid compiler warnings - case 64: + case 8: LOOP(63); LOOP(62); LOOP(61); LOOP(60); LOOP(59); LOOP(58); LOOP(57); LOOP(56); LOOP(55); LOOP(54); LOOP(53); LOOP(52); @@ -151,7 +151,7 @@ struct deconvol_poly2 LOOP(35); LOOP(34); LOOP(33); LOOP(32); // Fall-through #endif - case 32: + case 4: LOOP(31) ; LOOP(30) @@ -185,7 +185,7 @@ struct deconvol_poly2 LOOP(16) ; // Fall-through - case 16: + case 2: LOOP(15) ; LOOP(14) @@ -203,7 +203,7 @@ struct deconvol_poly2 LOOP(8) ; // Fall-through - case 8: + case 1: LOOP(7) ; LOOP(6) @@ -227,24 +227,24 @@ struct deconvol_poly2 } #undef LOOP #endif - switch (sizeof(Thist) * 8) + switch (sizeof(Thist) /* 8*/) { #if 0 // Not needed yet - avoid compiler warnings - case 64: + case 8: *pout++ = wd >> 56; *pout++ = wd >> 48; *pout++ = wd >> 40; *pout++ = wd >> 32; // Fall-through #endif - case 32: + case 4: *pout++ = wd >> 24; *pout++ = wd >> 16; // Fall-through - case 16: + case 2: *pout++ = wd >> 8; // Fall-through - case 8: + case 1: *pout++ = wd; break; default: diff --git a/plugins/channelrx/demoddatv/leansdr/dsp.h b/plugins/channelrx/demoddatv/leansdr/dsp.h index f790212b8..03725abc5 100644 --- a/plugins/channelrx/demoddatv/leansdr/dsp.h +++ b/plugins/channelrx/demoddatv/leansdr/dsp.h @@ -334,7 +334,7 @@ private: { for (unsigned int i = 0; i < ncoeffs; ++i) { - float a = 2 * M_PI * f * (i - ncoeffs / 2); + float a = 2 * M_PI * f * (i - (ncoeffs / 2)); float c = cosf(a), s = sinf(a); // TBD Support T=complex shifted_coeffs[i].re = coeffs[i] * c; diff --git a/plugins/channelrx/demoddatv/leansdr/dvb.h b/plugins/channelrx/demoddatv/leansdr/dvb.h index 3a08afe06..da4e44fb8 100644 --- a/plugins/channelrx/demoddatv/leansdr/dvb.h +++ b/plugins/channelrx/demoddatv/leansdr/dvb.h @@ -106,8 +106,8 @@ inline cstln_lut<256> * make_dvbs2_constellation(cstln_lut<256>::predef c, } // EN 300 421, section 4.4.3, table 2 Punctured code, G1=0171, G2=0133 -static const int DVBS_G1 = 0171; -static const int DVBS_G2 = 0133; +static const int DVBS_G1 = 121; +static const int DVBS_G2 = 91; // G1 = 0b1111001 // G2 = 0b1011011 @@ -1286,7 +1286,7 @@ struct randomizer: runnable { // EN 300 421, section 4.4.1 Transport multiplex adaptation pattern[0] = 0xff; // Invert one in eight sync bytes - unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) + unsigned short st = 169; // 0b 000 000 010 101 001 (Fig 2 reversed) for (int i = 1; i < 188 * 8; ++i) { u8 out = 0; @@ -1338,7 +1338,7 @@ struct derandomizer: runnable { // EN 300 421, section 4.4.1 Transport multiplex adaptation pattern[0] = 0xff; // Restore the inverted sync byte - unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) + unsigned short st = 169; // 0b 000 000 010 101 001 (Fig 2 reversed) for (int i = 1; i < 188 * 8; ++i) { u8 out = 0; diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h index 6d4da2b57..6d87eb90e 100644 --- a/plugins/channelrx/demoddatv/leansdr/sdr.h +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -603,7 +603,7 @@ private: { // Average power in first quadrant with unit grid int q = m / 2; float avgpower = 2 - * (q * 0.25 + (q - 1) * q / 2 + * (q * 0.25 + (q - 1) * (q / 2) + (q - 1) * q * (2 * q - 1) / 6) / q; scale = 1.0 / sqrtf(avgpower); } diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 518cc4767..1cc5d941c 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -392,7 +392,7 @@ void DSDDemod::applyAudioSampleRate(int sampleRate) { qDebug("DSDDemod::applyAudioSampleRate: %d", sampleRate); - if ((sampleRate != 48000) || (sampleRate != 8000)) { + if ((sampleRate != 48000) && (sampleRate != 8000)) { qWarning("DSDDemod::applyAudioSampleRate: audio does not work properly with sample rates other than 48 or 8 kS/s"); } diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index ce7747b2e..7e268e338 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -40,6 +40,7 @@ UDPSinkFEC::UDPSinkFEC() : m_frameCount(0), m_sampleIndex(0) { + memset((char *) m_txBlocks, 0, 4*256); m_currentMetaFEC.init(); m_bufMeta = new uint8_t[m_udpSize]; m_buf = new uint8_t[m_udpSize]; @@ -115,11 +116,11 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk metaData.m_crc32 = crc32.checksum(); - memset((void *) &m_superBlock, 0, sizeof(m_superBlock)); + memset((char *) &m_superBlock, 0, sizeof(m_superBlock)); m_superBlock.header.frameIndex = m_frameCount; m_superBlock.header.blockIndex = m_txBlockIndex; - memcpy((void *) &m_superBlock.protectedBlock, (const void *) &metaData, sizeof(MetaDataFEC)); + memcpy((char *) &m_superBlock.protectedBlock, (const char *) &metaData, sizeof(MetaDataFEC)); if (!(metaData == m_currentMetaFEC)) { @@ -143,16 +144,16 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block { - memcpy((void *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], - (const void *) &(*it), + memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], + (const char *) &(*it), 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.protectedBlock.m_samples[m_sampleIndex], - (const void *) &(*it), + memcpy((char *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], + (const char *) &(*it), (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); it += samplesPerBlock - m_sampleIndex; m_sampleIndex = 0; @@ -283,7 +284,7 @@ void UDPSinkFECWorker::encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint1 for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; ++i) { if (i >= cm256Params.OriginalCount) { - memset((void *) &txBlockx[i].protectedBlock, 0, sizeof(UDPSinkFEC::ProtectedBlock)); + memset((char *) &txBlockx[i].protectedBlock, 0, sizeof(UDPSinkFEC::ProtectedBlock)); } txBlockx[i].header.frameIndex = frameIndex; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index 825a4b12a..0b00d6a52 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -57,12 +57,12 @@ public: bool operator==(const MetaDataFEC& rhs) { - return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant + return (memcmp((const char *) this, (const char *) &rhs, 12) == 0); // Only the 12 first bytes are relevant } void init() { - memset((void *) this, 0, sizeof(MetaDataFEC)); + memset((char *) this, 0, sizeof(MetaDataFEC)); m_nbFECBlocks = -1; } }; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h index c051061e9..3cfbd2e76 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h @@ -46,12 +46,12 @@ public: bool operator==(const MetaDataFEC& rhs) { - return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant + return (memcmp((const char *) this, (const char *) &rhs, 12) == 0); // Only the 12 first bytes are relevant } void init() { - memset((void *) this, 0, sizeof(MetaDataFEC)); + memset((char *) this, 0, sizeof(MetaDataFEC)); } }; diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp index 94dedda1d..643858cb8 100644 --- a/qrtplib/rtpsession.cpp +++ b/qrtplib/rtpsession.cpp @@ -84,28 +84,28 @@ RTPSession::~RTPSession() delete rtprnd; } -int RTPSession::Create(const RTPSessionParams &sessparams, const RTPTransmissionParams *transparams /* = 0 */, RTPTransmitter::TransmissionProtocol protocol) -{ - int status; - - if (created) - return ERR_RTP_SESSION_ALREADYCREATED; - - usingpollthread = sessparams.IsUsingPollThread(); - - useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); - sentpackets = false; - - // Check max packet size - - if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) - return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; - - // Initialize the transmission component - - rtptrans = 0; - switch (protocol) - { +//int RTPSession::Create(const RTPSessionParams &sessparams, const RTPTransmissionParams *transparams /* = 0 */, RTPTransmitter::TransmissionProtocol protocol) +//{ +// int status; +// +// if (created) +// return ERR_RTP_SESSION_ALREADYCREATED; +// +// usingpollthread = sessparams.IsUsingPollThread(); +// +// useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); +// sentpackets = false; +// +// // Check max packet size +// +// if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) +// return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; +// +// // Initialize the transmission component +// +// rtptrans = 0; +// switch (protocol) +// { // TODO: see if we keep this Create method or use the one with the transmitter specified // case RTPTransmitter::IPv4UDPProto: // rtptrans = new RTPUDPv4Transmitter(); @@ -121,26 +121,27 @@ int RTPSession::Create(const RTPSessionParams &sessparams, const RTPTransmission // case RTPTransmitter::TCPProto: // rtptrans = new RTPTCPTransmitter(); // break; - default: - return ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL; - } - if (rtptrans == 0) - return ERR_RTP_OUTOFMEM; - if ((status = rtptrans->Init()) < 0) - { - delete rtptrans; - return status; - } - if ((status = rtptrans->Create(maxpacksize, transparams)) < 0) - { - delete rtptrans; - return status; - } - - deletetransmitter = true; - return InternalCreate(sessparams); -} +// default: +// return ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL; +// } +// +// if (rtptrans == 0) +// return ERR_RTP_OUTOFMEM; +// if ((status = rtptrans->Init()) < 0) +// { +// delete rtptrans; +// return status; +// } +// if ((status = rtptrans->Create(maxpacksize, transparams)) < 0) +// { +// delete rtptrans; +// return status; +// } +// +// deletetransmitter = true; +// return InternalCreate(sessparams); +//} int RTPSession::Create(const RTPSessionParams &sessparams, RTPTransmitter *transmitter) { diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h index d2393ddc9..179609e8f 100644 --- a/qrtplib/rtpsession.h +++ b/qrtplib/rtpsession.h @@ -94,7 +94,7 @@ public: * proto is of type RTPTransmitter::UserDefinedProto, the NewUserDefinedTransmitter function must * be implemented. */ - int Create(const RTPSessionParams &sessparams, const RTPTransmissionParams *transparams = 0, RTPTransmitter::TransmissionProtocol proto = RTPTransmitter::IPv4UDPProto); + //int Create(const RTPSessionParams &sessparams, const RTPTransmissionParams *transparams = 0, RTPTransmitter::TransmissionProtocol proto = RTPTransmitter::IPv4UDPProto); /** Creates an RTP session using \c transmitter as transmission component. * This function creates an RTP session with parameters \c sessparams, which will use the diff --git a/qrtplib/rtpudptransmitter.cpp b/qrtplib/rtpudptransmitter.cpp index 957f62ec2..bcca9c584 100644 --- a/qrtplib/rtpudptransmitter.cpp +++ b/qrtplib/rtpudptransmitter.cpp @@ -53,6 +53,9 @@ RTPUDPTransmitter::RTPUDPTransmitter() : m_rtcpPort = 0; m_rtpPort = 0; m_receivemode = RTPTransmitter::AcceptAll; + m_maxpacksize = 0; + memset(m_rtpBuffer, 0, m_absoluteMaxPackSize); + memset(m_rtcpBuffer, 0, m_absoluteMaxPackSize); } RTPUDPTransmitter::~RTPUDPTransmitter() diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 48b129fcf..7c9914972 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -20190,7 +20190,7 @@ except ApiException as e:
  • - Generated 2018-03-31T18:20:57.874+02:00 + Generated 2018-04-04T22:16:33.651+02:00
    diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 48b129fcf..7c9914972 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -20190,7 +20190,7 @@ except ApiException as e:
    - Generated 2018-03-31T18:20:57.874+02:00 + Generated 2018-04-04T22:16:33.651+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp b/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp index 1c2d92e2f..e6f00e591 100644 --- a/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGHelpers.cpp @@ -388,16 +388,19 @@ toJsonValue(QString name, void* value, QJsonObject* output, QString type) { SWGObject *SWGobject = reinterpret_cast(value); if(SWGobject != nullptr) { QJsonObject* o = (*SWGobject).asJsonObject(); - if(name != nullptr) { - output->insert(name, *o); - if(o != nullptr) delete o; - } - else { - output->empty(); - for(QString key : o->keys()) { - output->insert(key, o->value(key)); - } - } + if (o != nullptr) + { + if(name != nullptr) { + output->insert(name, *o); + if(o != nullptr) delete o; + } + else { + output->empty(); + for(QString key : o->keys()) { + output->insert(key, o->value(key)); + } + } + } } } else if(QStringLiteral("QString").compare(type) == 0) { From 275a02081897d12073744c620b08c697e879832b Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 5 Apr 2018 20:13:05 +0200 Subject: [PATCH 249/956] AM demod: implemented server plugin. Corrections to NFM demod server plugin --- plugins/channelrx/demodam/amdemodplugin.cpp | 13 +++++- plugins/channelrx/demodnfm/nfmdemod.cpp | 1 - plugins/channelrx/demodnfm/nfmplugin.cpp | 2 + pluginssrv/channelrx/CMakeLists.txt | 1 + pluginssrv/channelrx/demodam/CMakeLists.txt | 42 ++++++++++++++++++++ pluginssrv/channelrx/demodnfm/CMakeLists.txt | 2 - 6 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 pluginssrv/channelrx/demodam/CMakeLists.txt diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index e293003cc..d8bbd0ac5 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -2,13 +2,15 @@ #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "amdemodgui.h" +#endif #include "amdemod.h" #include "amdemodplugin.h" const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("3.14.0"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -34,10 +36,19 @@ void AMDemodPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(AMDemod::m_channelIdURI, AMDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* AMDemodPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* AMDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return AMDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* AMDemodPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 6ab3ea518..fdbdeacd1 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -34,7 +34,6 @@ #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" -#include "nfmdemodgui.h" #include "nfmdemod.h" MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureNFMDemod, Message) diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index 684f3835b..6ba23343b 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -2,7 +2,9 @@ #include "plugin/pluginapi.h" #include "nfmplugin.h" +#ifndef SERVER_MODE #include "nfmdemodgui.h" +#endif #include "nfmdemod.h" const PluginDescriptor NFMPlugin::m_pluginDescriptor = { diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index c581fe7db..5e24cddff 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -1,3 +1,4 @@ project(demod) +add_subdirectory(demodam) add_subdirectory(demodnfm) diff --git a/pluginssrv/channelrx/demodam/CMakeLists.txt b/pluginssrv/channelrx/demodam/CMakeLists.txt new file mode 100644 index 000000000..aaa777cf1 --- /dev/null +++ b/pluginssrv/channelrx/demodam/CMakeLists.txt @@ -0,0 +1,42 @@ +project(am) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demodam") + +set(am_SOURCES + ${PLUGIN_PREFIX}/amdemod.cpp + ${PLUGIN_PREFIX}/amdemodsettings.cpp + ${PLUGIN_PREFIX}/amdemodplugin.cpp +) + +set(am_HEADERS + ${PLUGIN_PREFIX}/amdemod.h + ${PLUGIN_PREFIX}/amdemodsettings.h + ${PLUGIN_PREFIX}/amdemodplugin.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_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) + +add_library(demodamsrv SHARED + ${am_SOURCES} + ${am_HEADERS_MOC} +) + +target_link_libraries(demodamsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +qt5_use_modules(demodamsrv Core Widgets) + +install(TARGETS demodamsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file diff --git a/pluginssrv/channelrx/demodnfm/CMakeLists.txt b/pluginssrv/channelrx/demodnfm/CMakeLists.txt index b54567bb6..25c04df68 100644 --- a/pluginssrv/channelrx/demodnfm/CMakeLists.txt +++ b/pluginssrv/channelrx/demodnfm/CMakeLists.txt @@ -29,13 +29,11 @@ add_definitions(-DQT_SHARED) add_library(demodnfmsrv SHARED ${nfm_SOURCES} ${nfm_HEADERS_MOC} - ${nfm_FORMS_HEADERS} ) target_link_libraries(demodnfmsrv ${QT_LIBRARIES} sdrbase - sdrgui swagger ) From cc3483aabe5e96e2084805bc611875bf74494c47 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 5 Apr 2018 21:24:01 +0200 Subject: [PATCH 250/956] Build AirspyHF and BladeRF server plugins. Removed AirspyHFi --- plugins/samplesource/airspyhfi/CMakeLists.txt | 78 --- plugins/samplesource/airspyhfi/airspyhfi.pro | 50 -- .../samplesource/airspyhfi/airspyhfigui.cpp | 409 -------------- plugins/samplesource/airspyhfi/airspyhfigui.h | 95 ---- .../samplesource/airspyhfi/airspyhfigui.ui | 494 ----------------- .../samplesource/airspyhfi/airspyhfiinput.cpp | 507 ------------------ .../samplesource/airspyhfi/airspyhfiinput.h | 147 ----- .../airspyhfi/airspyhfiplugin.cpp | 127 ----- .../samplesource/airspyhfi/airspyhfiplugin.h | 53 -- .../airspyhfi/airspyhfisettings.cpp | 91 ---- .../airspyhfi/airspyhfisettings.h | 44 -- .../airspyhfi/airspyhfithread.cpp | 142 ----- .../samplesource/airspyhfi/airspyhfithread.h | 67 --- plugins/samplesource/airspyhfi/readme.md | 105 ---- pluginssrv/samplesink/CMakeLists.txt | 5 + .../samplesink/bladerfoutput/CMakeLists.txt | 68 +++ pluginssrv/samplesource/CMakeLists.txt | 13 +- .../samplesource/airspyhf/CMakeLists.txt | 64 +++ .../samplesource/bladerfinput/CMakeLists.txt | 68 +++ 19 files changed, 217 insertions(+), 2410 deletions(-) delete mode 100644 plugins/samplesource/airspyhfi/CMakeLists.txt delete mode 100644 plugins/samplesource/airspyhfi/airspyhfi.pro delete mode 100644 plugins/samplesource/airspyhfi/airspyhfigui.cpp delete mode 100644 plugins/samplesource/airspyhfi/airspyhfigui.h delete mode 100644 plugins/samplesource/airspyhfi/airspyhfigui.ui delete mode 100644 plugins/samplesource/airspyhfi/airspyhfiinput.cpp delete mode 100644 plugins/samplesource/airspyhfi/airspyhfiinput.h delete mode 100644 plugins/samplesource/airspyhfi/airspyhfiplugin.cpp delete mode 100644 plugins/samplesource/airspyhfi/airspyhfiplugin.h delete mode 100644 plugins/samplesource/airspyhfi/airspyhfisettings.cpp delete mode 100644 plugins/samplesource/airspyhfi/airspyhfisettings.h delete mode 100644 plugins/samplesource/airspyhfi/airspyhfithread.cpp delete mode 100644 plugins/samplesource/airspyhfi/airspyhfithread.h delete mode 100644 plugins/samplesource/airspyhfi/readme.md create mode 100644 pluginssrv/samplesink/bladerfoutput/CMakeLists.txt create mode 100644 pluginssrv/samplesource/airspyhf/CMakeLists.txt create mode 100644 pluginssrv/samplesource/bladerfinput/CMakeLists.txt diff --git a/plugins/samplesource/airspyhfi/CMakeLists.txt b/plugins/samplesource/airspyhfi/CMakeLists.txt deleted file mode 100644 index f03cd4ff8..000000000 --- a/plugins/samplesource/airspyhfi/CMakeLists.txt +++ /dev/null @@ -1,78 +0,0 @@ -project(airspyhfi) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - -set(airspyhfi_SOURCES - airspyhfigui.cpp - airspyhfiinput.cpp - airspyhfiplugin.cpp - airspyhfisettings.cpp - airspyhfithread.cpp -) - -set(airspyhfi_HEADERS - airspyhfigui.h - airspyhfiinput.h - airspyhfiplugin.h - airspyhfisettings.h - airspyhfithread.h -) - -set(airspyhfi_FORMS - airspyhfigui.ui -) - -if (BUILD_DEBIAN) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${LIBAIRSPYHFSRC} - ${LIBAIRSPYHFSRC}/libairspyhf/src -) -else (BUILD_DEBIAN) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${LIBAIRSPYHF_INCLUDE_DIR} -) -endif (BUILD_DEBIAN) - -#include(${QT_USE_FILE}) -#add_definitions(${QT_DEFINITIONS}) -add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -#qt4_wrap_cpp(airspyhf_HEADERS_MOC ${airspyhf_HEADERS}) -qt5_wrap_ui(airspyhfi_FORMS_HEADERS ${airspyhfi_FORMS}) - -add_library(inputairspyhfi SHARED - ${airspyhfi_SOURCES} - ${airspyhfi_HEADERS_MOC} - ${airspyhfi_FORMS_HEADERS} -) - -if (BUILD_DEBIAN) -target_link_libraries(inputairspyhfi - ${QT_LIBRARIES} - airspyhf - sdrbase - sdrgui - swagger -) -else (BUILD_DEBIAN) -target_link_libraries(inputairspyhfi - ${QT_LIBRARIES} - ${LIBAIRSPYHF_LIBRARIES} - sdrbase - sdrgui - swagger -) -endif (BUILD_DEBIAN) - - -qt5_use_modules(inputairspyhfi Core Widgets) - -install(TARGETS inputairspyhfi DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/airspyhfi/airspyhfi.pro b/plugins/samplesource/airspyhfi/airspyhfi.pro deleted file mode 100644 index 547c86b8e..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfi.pro +++ /dev/null @@ -1,50 +0,0 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -TEMPLATE = lib -CONFIG += plugin - -QT += core gui widgets multimedia opengl - -TARGET = inputairspyhfi - -CONFIG(MINGW32):LIBAIRSPYHFSRC = "D:\softs\airspyhf" -CONFIG(MINGW64):LIBAIRSPYHFSRC = "D:\softs\airspyhf" -INCLUDEPATH += $$PWD -INCLUDEPATH += ../../../sdrbase -INCLUDEPATH += ../../../sdrgui -INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client -INCLUDEPATH += $$LIBAIRSPYHFSRC - -DEFINES += USE_SSE2=1 -QMAKE_CXXFLAGS += -msse2 -DEFINES += USE_SSE4_1=1 -QMAKE_CXXFLAGS += -msse4.1 -QMAKE_CXXFLAGS += -std=c++11 - -CONFIG(Release):build_subdir = release -CONFIG(Debug):build_subdir = debug - -SOURCES += airspyhfigui.cpp\ - airspyhfiinput.cpp\ - airspyhfiplugin.cpp\ - airspyhfisettings.cpp\ - airspyhfithread.cpp - -HEADERS += airspyhfigui.h\ - airspyhfiinput.h\ - airspyhfiplugin.h\ - airspyhfisettings.h\ - airspyhfithread.h - -FORMS += airspyhfigui.ui - -LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase -LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui -LIBS += -L../../../swagger/$${build_subdir} -lswagger -LIBS += -L../../../libairspyhf/$${build_subdir} -llibairspyhf - -RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/samplesource/airspyhfi/airspyhfigui.cpp b/plugins/samplesource/airspyhfi/airspyhfigui.cpp deleted file mode 100644 index 20c3d9a8c..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfigui.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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 "device/deviceuiset.h" -#include - -#include "ui_airspyhfigui.h" -#include "gui/colormapper.h" -#include "gui/glspectrum.h" -#include "dsp/dspengine.h" -#include "dsp/dspcommands.h" -#include "airspyhfigui.h" - -AirspyHFIGui::AirspyHFIGui(DeviceUISet *deviceUISet, QWidget* parent) : - QWidget(parent), - ui(new Ui::AirspyHFIGui), - m_deviceUISet(deviceUISet), - m_doApplySettings(true), - m_forceSettings(true), - m_settings(), - m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) -{ - m_sampleSource = (AirspyHFIInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); - - ui->setupUi(this); - ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); - updateFrequencyLimits(); - - connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); - connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); - m_statusTimer.start(500); - - displaySettings(); - - m_rates = ((AirspyHFIInput*) m_sampleSource)->getSampleRates(); - displaySampleRates(); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); - - sendSettings(); -} - -AirspyHFIGui::~AirspyHFIGui() -{ - delete ui; -} - -void AirspyHFIGui::destroy() -{ - delete this; -} - -void AirspyHFIGui::setName(const QString& name) -{ - setObjectName(name); -} - -QString AirspyHFIGui::getName() const -{ - return objectName(); -} - -void AirspyHFIGui::resetToDefaults() -{ - m_settings.resetToDefaults(); - displaySettings(); - sendSettings(); -} - -qint64 AirspyHFIGui::getCenterFrequency() const -{ - return m_settings.m_centerFrequency; -} - -void AirspyHFIGui::setCenterFrequency(qint64 centerFrequency) -{ - m_settings.m_centerFrequency = centerFrequency; - displaySettings(); - sendSettings(); -} - -QByteArray AirspyHFIGui::serialize() const -{ - return m_settings.serialize(); -} - -bool AirspyHFIGui::deserialize(const QByteArray& data) -{ - if(m_settings.deserialize(data)) { - displaySettings(); - m_forceSettings = true; - sendSettings(); - return true; - } else { - resetToDefaults(); - return false; - } -} - -bool AirspyHFIGui::handleMessage(const Message& message) -{ - if (AirspyHFIInput::MsgConfigureAirspyHFI::match(message)) - { - const AirspyHFIInput::MsgConfigureAirspyHFI& cfg = (AirspyHFIInput::MsgConfigureAirspyHFI&) message; - m_settings = cfg.getSettings(); - blockApplySettings(true); - displaySettings(); - blockApplySettings(false); - return true; - } - else if (AirspyHFIInput::MsgStartStop::match(message)) - { - AirspyHFIInput::MsgStartStop& notif = (AirspyHFIInput::MsgStartStop&) message; - blockApplySettings(true); - ui->startStop->setChecked(notif.getStartStop()); - blockApplySettings(false); - - return true; - } - else - { - return false; - } -} - -void AirspyHFIGui::handleInputMessages() -{ - Message* message; - - while ((message = m_inputMessageQueue.pop()) != 0) - { - qDebug("AirspyHFGui::handleInputMessages: message: %s", message->getIdentifier()); - - if (DSPSignalNotification::match(*message)) - { - DSPSignalNotification* notif = (DSPSignalNotification*) message; - m_sampleRate = notif->getSampleRate(); - m_deviceCenterFrequency = notif->getCenterFrequency(); - qDebug("AirspyGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); - updateSampleRateAndFrequency(); - - delete message; - } - else - { - if (handleMessage(*message)) - { - delete message; - } - } - } -} - -void AirspyHFIGui::updateSampleRateAndFrequency() -{ - m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); - m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); - ui->deviceRateText->setText(tr("%1k").arg((float)m_sampleRate / 1000)); -} - -void AirspyHFIGui::updateFrequencyLimits() -{ - // values in kHz - qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; - - qint64 minLimit; - qint64 maxLimit; - - switch(m_settings.m_bandIndex) - { - case 1: - minLimit = AirspyHFIInput::loLowLimitFreqVHF/1000 + deltaFrequency; - maxLimit = AirspyHFIInput::loHighLimitFreqVHF/1000 + deltaFrequency; - break; - case 0: - default: - minLimit = AirspyHFIInput::loLowLimitFreqHF/1000 + deltaFrequency; - maxLimit = AirspyHFIInput::loHighLimitFreqHF/1000 + deltaFrequency; - break; - } - - minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; - maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; - - qDebug("AirspyHFGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); - - ui->centerFrequency->setValueRange(7, minLimit, maxLimit); -} - -void AirspyHFIGui::displaySettings() -{ - blockApplySettings(true); - ui->band->blockSignals(true); - m_settings.m_bandIndex = m_settings.m_centerFrequency <= 31000000UL ? 0 : 1; // override - ui->band->setCurrentIndex(m_settings.m_bandIndex); - updateFrequencyLimits(); - ui->transverter->setDeltaFrequency(m_settings.m_transverterDeltaFrequency); - ui->transverter->setDeltaFrequencyActive(m_settings.m_transverterMode); - ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); - ui->LOppm->setValue(m_settings.m_LOppmTenths); - ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); - ui->autoCorr->setCurrentIndex(m_settings.m_autoCorrOptions); - ui->sampleRate->setCurrentIndex(m_settings.m_devSampleRateIndex); - ui->decim->setCurrentIndex(m_settings.m_log2Decim); - ui->band->blockSignals(false); - blockApplySettings(false); -} - -void AirspyHFIGui::displaySampleRates() -{ - unsigned int savedIndex = m_settings.m_devSampleRateIndex; - ui->sampleRate->blockSignals(true); - - if (m_rates.size() > 0) - { - ui->sampleRate->clear(); - - for (unsigned int i = 0; i < m_rates.size(); i++) - { - int sampleRate = m_rates[i]/1000; - ui->sampleRate->addItem(QString("%1").arg(QString("%1").arg(sampleRate, 5, 10, QChar(' ')))); - } - } - - ui->sampleRate->blockSignals(false); - - if (savedIndex < m_rates.size()) - { - ui->sampleRate->setCurrentIndex(savedIndex); - } - else - { - ui->sampleRate->setCurrentIndex((int) m_rates.size()-1); - } -} - -void AirspyHFIGui::sendSettings() -{ - if(!m_updateTimer.isActive()) - m_updateTimer.start(100); -} - -void AirspyHFIGui::on_centerFrequency_changed(quint64 value) -{ - m_settings.m_centerFrequency = value * 1000; - sendSettings(); -} - -void AirspyHFIGui::on_LOppm_valueChanged(int value) -{ - m_settings.m_LOppmTenths = value; - ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); - sendSettings(); -} - -void AirspyHFIGui::on_resetLOppm_clicked() -{ - ui->LOppm->setValue(0); -} - -void AirspyHFIGui::on_autoCorr_currentIndexChanged(int index) -{ - if ((index < 0) || (index > AirspyHFISettings::AutoCorrLast)) { - return; - } - - m_settings.m_autoCorrOptions = (AirspyHFISettings::AutoCorrOptions) index; - sendSettings(); -} - -void AirspyHFIGui::on_sampleRate_currentIndexChanged(int index) -{ - m_settings.m_devSampleRateIndex = index; - sendSettings(); -} - -void AirspyHFIGui::on_decim_currentIndexChanged(int index) -{ - if ((index < 0) || (index > 5)) - return; - m_settings.m_log2Decim = index; - sendSettings(); -} - -void AirspyHFIGui::on_startStop_toggled(bool checked) -{ - if (m_doApplySettings) - { - AirspyHFIInput::MsgStartStop *message = AirspyHFIInput::MsgStartStop::create(checked); - m_sampleSource->getInputMessageQueue()->push(message); - } -} - -void AirspyHFIGui::on_record_toggled(bool checked) -{ - if (checked) { - ui->record->setStyleSheet("QToolButton { background-color : red; }"); - } else { - ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); - } - - AirspyHFIInput::MsgFileRecord* message = AirspyHFIInput::MsgFileRecord::create(checked); - m_sampleSource->getInputMessageQueue()->push(message); -} - -void AirspyHFIGui::on_transverter_clicked() -{ - m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); - m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); - qDebug("AirspyHFGui::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); - updateFrequencyLimits(); - m_settings.m_centerFrequency = ui->centerFrequency->getValueNew()*1000; - sendSettings(); -} - -void AirspyHFIGui::on_band_currentIndexChanged(int index) -{ - if ((index < 0) || (index > 1)) { - return; - } - - m_settings.m_bandIndex = index; - updateFrequencyLimits(); - qDebug("AirspyHFGui::on_band_currentIndexChanged: freq: %llu", ui->centerFrequency->getValueNew() * 1000); - m_settings.m_centerFrequency = ui->centerFrequency->getValueNew() * 1000; - sendSettings(); -} - -void AirspyHFIGui::updateHardware() -{ - qDebug() << "AirspyHFGui::updateHardware"; - AirspyHFIInput::MsgConfigureAirspyHFI* message = AirspyHFIInput::MsgConfigureAirspyHFI::create(m_settings, m_forceSettings); - m_sampleSource->getInputMessageQueue()->push(message); - m_forceSettings = false; - m_updateTimer.stop(); -} - -void AirspyHFIGui::updateStatus() -{ - int state = m_deviceUISet->m_deviceSourceAPI->state(); - - if(m_lastEngineState != state) - { - switch(state) - { - case DSPDeviceSourceEngine::StNotStarted: - ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); - break; - case DSPDeviceSourceEngine::StIdle: - ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); - break; - case DSPDeviceSourceEngine::StRunning: - ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); - break; - case DSPDeviceSourceEngine::StError: - ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); - QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSourceAPI->errorMessage()); - break; - default: - break; - } - - m_lastEngineState = state; - } -} - -uint32_t AirspyHFIGui::getDevSampleRate(unsigned int rate_index) -{ - if (rate_index < m_rates.size()) - { - return m_rates[rate_index]; - } - else - { - return m_rates[0]; - } -} - -int AirspyHFIGui::getDevSampleRateIndex(uint32_t sampeRate) -{ - for (unsigned int i=0; i < m_rates.size(); i++) - { - if (sampeRate == m_rates[i]) - { - return i; - } - } - - return -1; -} diff --git a/plugins/samplesource/airspyhfi/airspyhfigui.h b/plugins/samplesource/airspyhfi/airspyhfigui.h deleted file mode 100644 index 537714667..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfigui.h +++ /dev/null @@ -1,95 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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_AIRSPYHFIGUI_H -#define INCLUDE_AIRSPYHFIGUI_H - -#include -#include -#include - -#include "util/messagequeue.h" - -#include "airspyhfiinput.h" - -class DeviceUISet; - -namespace Ui { - class AirspyHFIGui; - class AirspyHFISampleRates; -} - -class AirspyHFIGui : public QWidget, public PluginInstanceGUI { - Q_OBJECT - -public: - explicit AirspyHFIGui(DeviceUISet *deviceUISet, QWidget* parent = 0); - virtual ~AirspyHFIGui(); - virtual void destroy(); - - void setName(const QString& name); - QString getName() const; - - void resetToDefaults(); - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); - virtual MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; } - virtual bool handleMessage(const Message& message); - uint32_t getDevSampleRate(unsigned int index); - int getDevSampleRateIndex(uint32_t sampleRate); - -private: - Ui::AirspyHFIGui* ui; - - DeviceUISet* m_deviceUISet; - bool m_doApplySettings; - bool m_forceSettings; - AirspyHFISettings m_settings; - QTimer m_updateTimer; - QTimer m_statusTimer; - std::vector m_rates; - DeviceSampleSource* m_sampleSource; - int m_sampleRate; - quint64 m_deviceCenterFrequency; //!< Center frequency in device - int m_lastEngineState; - MessageQueue m_inputMessageQueue; - - void blockApplySettings(bool block) { m_doApplySettings = !block; } - void displaySettings(); - void displaySampleRates(); - void sendSettings(); - void updateSampleRateAndFrequency(); - void updateFrequencyLimits(); - -private slots: - void on_centerFrequency_changed(quint64 value); - void on_LOppm_valueChanged(int value); - void on_resetLOppm_clicked(); - void on_autoCorr_currentIndexChanged(int index); - void on_sampleRate_currentIndexChanged(int index); - void on_decim_currentIndexChanged(int index); - void on_startStop_toggled(bool checked); - void on_record_toggled(bool checked); - void on_transverter_clicked(); - void on_band_currentIndexChanged(int index); - void updateHardware(); - void updateStatus(); - void handleInputMessages(); -}; - -#endif // INCLUDE_AIRSPYHFIGUI_H diff --git a/plugins/samplesource/airspyhfi/airspyhfigui.ui b/plugins/samplesource/airspyhfi/airspyhfigui.ui deleted file mode 100644 index 18a142606..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfigui.ui +++ /dev/null @@ -1,494 +0,0 @@ - - - AirspyHFIGui - - - - 0 - 0 - 324 - 174 - - - - - 0 - 0 - - - - - 320 - 132 - - - - - Sans Serif - 9 - - - - AirspyHF - - - - 3 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - 4 - - - - - - - - - start/stop acquisition - - - - - - - :/play.png - :/stop.png:/play.png - - - - - - - Toggle record I/Q samples from device - - - - - - - :/record_off.png:/record_off.png - - - - - - - - - - - I/Q sample rate kS/s - - - 00000k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - DejaVu Sans Mono - 20 - - - - PointingHandCursor - - - Qt::StrongFocus - - - Tuner center frequency in kHz - - - - - - - kHz - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - - - - - LO ppm - - - - - - - Local Oscillator ppm correction - - - -100 - - - 100 - - - 1 - - - Qt::Horizontal - - - - - - - - 36 - 0 - - - - LO correction value (ppm) - - - -00.0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 24 - 16777215 - - - - Rest LO ppm correction - - - R - - - - - - - - - - - Corr - - - - - - - DC offset and IQ correction options - - - - None - - - - - DC - - - - - DC+IQ - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Horizontal - - - - - - - - - Band - - - - - - - - 56 - 0 - - - - Band select - - - - HF - - - - - VHF - - - - - - - - - 0 - 0 - - - - S/R - - - - - - - - 60 - 0 - - - - - 60 - 16777215 - - - - Device sample rate in MS/s - - - - 0000 - - - - - - - - k - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Dec - - - - - - - - 50 - 16777215 - - - - Decimation factor - - - 0 - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - 32 - - - - - - - - - 24 - 24 - - - - Transverter frequency translation dialog - - - X - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - ValueDial - QWidget -
    gui/valuedial.h
    - 1 -
    - - ButtonSwitch - QToolButton -
    gui/buttonswitch.h
    -
    - - TransverterButton - QPushButton -
    gui/transverterbutton.h
    -
    -
    - - - - -
    diff --git a/plugins/samplesource/airspyhfi/airspyhfiinput.cpp b/plugins/samplesource/airspyhfi/airspyhfiinput.cpp deleted file mode 100644 index 883948e4a..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfiinput.cpp +++ /dev/null @@ -1,507 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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 "SWGDeviceSettings.h" -#include "SWGDeviceState.h" - -#include -#include -#include "dsp/dspcommands.h" -#include "dsp/dspengine.h" - -#include "airspyhfisettings.h" -#include "airspyhfiinput.h" - -#include "airspyhfiplugin.h" -#include "airspyhfithread.h" -#include "airspyhfigui.h" - -MESSAGE_CLASS_DEFINITION(AirspyHFIInput::MsgConfigureAirspyHFI, Message) -MESSAGE_CLASS_DEFINITION(AirspyHFIInput::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(AirspyHFIInput::MsgFileRecord, Message) - -const qint64 AirspyHFIInput::loLowLimitFreqHF = 9000L; -const qint64 AirspyHFIInput::loHighLimitFreqHF = 31000000L; -const qint64 AirspyHFIInput::loLowLimitFreqVHF = 60000000L; -const qint64 AirspyHFIInput::loHighLimitFreqVHF = 260000000L; - -AirspyHFIInput::AirspyHFIInput(DeviceSourceAPI *deviceAPI) : - m_deviceAPI(deviceAPI), - m_settings(), - m_dev(0), - m_airspyHFThread(0), - m_deviceDescription("AirspyHFI"), - m_running(false) -{ - openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); - m_deviceAPI->addSink(m_fileSink); -} - -AirspyHFIInput::~AirspyHFIInput() -{ - if (m_running) { stop(); } - m_deviceAPI->removeSink(m_fileSink); - delete m_fileSink; - closeDevice(); -} - -void AirspyHFIInput::destroy() -{ - delete this; -} - -bool AirspyHFIInput::openDevice() -{ - if (m_dev != 0) - { - closeDevice(); - } - - airspyhf_error rc; - - if (!m_sampleFifo.setSize(1<<19)) - { - qCritical("AirspyHFInput::start: could not allocate SampleFifo"); - return false; - } - - if ((m_dev = open_airspyhf_from_serial(m_deviceAPI->getSampleSourceSerial())) == 0) - { - qCritical("AirspyHFInput::start: could not open Airspy with serial %s", qPrintable(m_deviceAPI->getSampleSourceSerial())); - return false; - } - else - { - qDebug("AirspyHFInput::start: opened Airspy with serial %s", qPrintable(m_deviceAPI->getSampleSourceSerial())); - } - - uint32_t nbSampleRates; - uint32_t *sampleRates; - - rc = (airspyhf_error) airspyhf_get_samplerates(m_dev, &nbSampleRates, 0); - - if (rc == AIRSPYHF_SUCCESS) - { - qDebug("AirspyHFInput::start: %d sample rates for AirspyHF", nbSampleRates); - } - else - { - qCritical("AirspyHFInput::start: could not obtain the number of AirspyHF sample rates"); - return false; - } - - sampleRates = new uint32_t[nbSampleRates]; - - rc = (airspyhf_error) airspyhf_get_samplerates(m_dev, sampleRates, nbSampleRates); - - if (rc == AIRSPYHF_SUCCESS) - { - qDebug("AirspyHFInput::start: obtained AirspyHF sample rates"); - } - else - { - qCritical("AirspyHFInput::start: could not obtain AirspyHF sample rates"); - return false; - } - - m_sampleRates.clear(); - - for (unsigned int i = 0; i < nbSampleRates; i++) - { - m_sampleRates.push_back(sampleRates[i]); - qDebug("AirspyHFInput::start: sampleRates[%d] = %u Hz", i, sampleRates[i]); - } - - delete[] sampleRates; - - airspyhf_set_sample_type(m_dev, AIRSPYHF_SAMPLE_INT16_NDSP_IQ); - - return true; -} - -void AirspyHFIInput::init() -{ - applySettings(m_settings, true); -} - -bool AirspyHFIInput::start() -{ - QMutexLocker mutexLocker(&m_mutex); - - if (!m_dev) { - return false; - } - - if (m_running) { stop(); } - - if ((m_airspyHFThread = new AirspyHFIThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("AirspyHFInput::start: out of memory"); - stop(); - return false; - } - - m_airspyHFThread->setSamplerate(m_sampleRates[m_settings.m_devSampleRateIndex]); - m_airspyHFThread->setLog2Decimation(m_settings.m_log2Decim); - - m_airspyHFThread->startWork(); - - mutexLocker.unlock(); - - applySettings(m_settings, true); - - qDebug("AirspyHFInput::startInput: started"); - m_running = true; - - return true; -} - -void AirspyHFIInput::closeDevice() -{ - if (m_dev != 0) - { - airspyhf_stop(m_dev); - airspyhf_close(m_dev); - m_dev = 0; - } - - m_deviceDescription.clear(); -} - -void AirspyHFIInput::stop() -{ - qDebug("AirspyHFInput::stop"); - QMutexLocker mutexLocker(&m_mutex); - - if (m_airspyHFThread != 0) - { - m_airspyHFThread->stopWork(); - delete m_airspyHFThread; - m_airspyHFThread = 0; - } - - m_running = false; -} - -QByteArray AirspyHFIInput::serialize() const -{ - return m_settings.serialize(); -} - -bool AirspyHFIInput::deserialize(const QByteArray& data) -{ - bool success = true; - - if (!m_settings.deserialize(data)) - { - m_settings.resetToDefaults(); - success = false; - } - - MsgConfigureAirspyHFI* message = MsgConfigureAirspyHFI::create(m_settings, true); - m_inputMessageQueue.push(message); - - if (m_guiMessageQueue) - { - MsgConfigureAirspyHFI* messageToGUI = MsgConfigureAirspyHFI::create(m_settings, true); - m_guiMessageQueue->push(messageToGUI); - } - - return success; -} - -const QString& AirspyHFIInput::getDeviceDescription() const -{ - return m_deviceDescription; -} - -int AirspyHFIInput::getSampleRate() const -{ - int rate = m_sampleRates[m_settings.m_devSampleRateIndex]; - return (rate / (1<push(messageToGUI); - } -} - -bool AirspyHFIInput::handleMessage(const Message& message) -{ - if (MsgConfigureAirspyHFI::match(message)) - { - MsgConfigureAirspyHFI& conf = (MsgConfigureAirspyHFI&) message; - qDebug() << "MsgConfigureAirspyHF::handleMessage: MsgConfigureAirspyHF"; - - bool success = applySettings(conf.getSettings(), conf.getForce()); - - if (!success) - { - qDebug("MsgConfigureAirspyHF::handleMessage: AirspyHF config error"); - } - - return true; - } - else if (MsgStartStop::match(message)) - { - MsgStartStop& cmd = (MsgStartStop&) message; - qDebug() << "AirspyHFInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); - - if (cmd.getStartStop()) - { - if (m_deviceAPI->initAcquisition()) - { - m_deviceAPI->startAcquisition(); - } - } - else - { - m_deviceAPI->stopAcquisition(); - } - - return true; - } - else if (MsgFileRecord::match(message)) - { - MsgFileRecord& conf = (MsgFileRecord&) message; - qDebug() << "AirspyHFInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - - if (conf.getStartStop()) { - m_fileSink->startRecording(); - } else { - m_fileSink->stopRecording(); - } - - return true; - } - else - { - return false; - } -} - -void AirspyHFIInput::setDeviceCenterFrequency(quint64 freq_hz, const AirspyHFISettings& settings) -{ - switch(settings.m_bandIndex) - { - case 1: - freq_hz = freq_hz < loLowLimitFreqVHF ? loLowLimitFreqVHF : freq_hz > loHighLimitFreqVHF ? loHighLimitFreqVHF : freq_hz; - break; - case 0: - default: - freq_hz = freq_hz < loLowLimitFreqHF ? loLowLimitFreqHF : freq_hz > loHighLimitFreqHF ? loHighLimitFreqHF : freq_hz; - break; - } - - airspyhf_error rc = (airspyhf_error) airspyhf_set_freq(m_dev, static_cast(freq_hz)); - - if (rc == AIRSPYHF_SUCCESS) { - qDebug("AirspyHFInput::setDeviceCenterFrequency: frequency set to %llu Hz", freq_hz); - } else { - qWarning("AirspyHFInput::setDeviceCenterFrequency: could not frequency to %llu Hz", freq_hz); - } -} - -bool AirspyHFIInput::applySettings(const AirspyHFISettings& settings, bool force) -{ - QMutexLocker mutexLocker(&m_mutex); - - bool forwardChange = false; - airspyhf_error rc; - int sampleRateIndex = settings.m_devSampleRateIndex; - - qDebug() << "AirspyHFInput::applySettings"; - - if ((m_settings.m_autoCorrOptions != settings.m_autoCorrOptions) || force) - { - switch(settings.m_autoCorrOptions) - { - case AirspyHFISettings::AutoCorrDC: - m_deviceAPI->configureCorrections(true, false); - break; - case AirspyHFISettings::AutoCorrDCAndIQ: - m_deviceAPI->configureCorrections(true, true); - break; - case AirspyHFISettings::AutoCorrNone: - default: - m_deviceAPI->configureCorrections(false, false); - break; - } - } - - if ((m_settings.m_devSampleRateIndex != settings.m_devSampleRateIndex) || force) - { - forwardChange = true; - - if (settings.m_devSampleRateIndex >= m_sampleRates.size()) { - sampleRateIndex = m_sampleRates.size() - 1; - } - - if (m_dev != 0) - { - rc = (airspyhf_error) airspyhf_set_samplerate(m_dev, sampleRateIndex); - - if (rc != AIRSPYHF_SUCCESS) - { - qCritical("AirspyHFInput::applySettings: could not set sample rate index %u (%d S/s)", sampleRateIndex, m_sampleRates[sampleRateIndex]); - } - else if (m_airspyHFThread != 0) - { - qDebug("AirspyHFInput::applySettings: sample rate set to index: %u (%d S/s)", sampleRateIndex, m_sampleRates[sampleRateIndex]); - m_airspyHFThread->setSamplerate(m_sampleRates[sampleRateIndex]); - } - } - } - - if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) - { - forwardChange = true; - - if (m_airspyHFThread != 0) - { - m_airspyHFThread->setLog2Decimation(settings.m_log2Decim); - qDebug() << "AirspyInput: set decimation to " << (1<handleMessage(*notif); // forward to file sink - m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); - } - - m_settings = settings; - m_settings.m_devSampleRateIndex = sampleRateIndex; - return true; -} - -airspyhf_device_t *AirspyHFIInput::open_airspyhf_from_serial(const QString& serialStr) -{ - airspyhf_device_t *devinfo; - bool ok; - airspyhf_error rc; - - uint64_t serial = serialStr.toULongLong(&ok, 16); - - if (!ok) - { - qCritical("AirspyHFInput::open_airspyhf_from_serial: invalid serial %s", qPrintable(serialStr)); - return 0; - } - else - { - rc = (airspyhf_error) airspyhf_open_sn(&devinfo, serial); - - if (rc == AIRSPYHF_SUCCESS) { - return devinfo; - } else { - return 0; - } - } -} - -int AirspyHFIInput::webapiRunGet( - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage __attribute__((unused))) -{ - m_deviceAPI->getDeviceEngineStateStr(*response.getState()); - return 200; -} - -int AirspyHFIInput::webapiRun( - bool run, - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage __attribute__((unused))) -{ - m_deviceAPI->getDeviceEngineStateStr(*response.getState()); - MsgStartStop *message = MsgStartStop::create(run); - m_inputMessageQueue.push(message); - - if (m_guiMessageQueue) // forward to GUI if any - { - MsgStartStop *msgToGUI = MsgStartStop::create(run); - m_guiMessageQueue->push(msgToGUI); - } - - return 200; -} - diff --git a/plugins/samplesource/airspyhfi/airspyhfiinput.h b/plugins/samplesource/airspyhfi/airspyhfiinput.h deleted file mode 100644 index d0d0bbf99..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfiinput.h +++ /dev/null @@ -1,147 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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_AIRSPYHFIINPUT_H -#define INCLUDE_AIRSPYHFIINPUT_H - -#include -#include - -#include -#include - -#include "airspyhfisettings.h" - -class DeviceSourceAPI; -class AirspyHFIThread; -class FileRecord; - -class AirspyHFIInput : public DeviceSampleSource { -public: - class MsgConfigureAirspyHFI : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const AirspyHFISettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureAirspyHFI* create(const AirspyHFISettings& settings, bool force) - { - return new MsgConfigureAirspyHFI(settings, force); - } - - private: - AirspyHFISettings m_settings; - bool m_force; - - MsgConfigureAirspyHFI(const AirspyHFISettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - class MsgFileRecord : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getStartStop() const { return m_startStop; } - - static MsgFileRecord* create(bool startStop) { - return new MsgFileRecord(startStop); - } - - protected: - bool m_startStop; - - MsgFileRecord(bool startStop) : - Message(), - m_startStop(startStop) - { } - }; - - 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) - { } - }; - - AirspyHFIInput(DeviceSourceAPI *deviceAPI); - virtual ~AirspyHFIInput(); - virtual void destroy(); - - virtual void init(); - virtual bool start(); - virtual void stop(); - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } - virtual const QString& getDeviceDescription() const; - virtual int getSampleRate() const; - virtual quint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - const std::vector& getSampleRates() const { return m_sampleRates; } - - virtual bool handleMessage(const Message& message); - - virtual int webapiRunGet( - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage); - - virtual int webapiRun( - bool run, - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage); - - static const qint64 loLowLimitFreqHF; - static const qint64 loHighLimitFreqHF; - static const qint64 loLowLimitFreqVHF; - static const qint64 loHighLimitFreqVHF; - -private: - bool openDevice(); - void closeDevice(); - bool applySettings(const AirspyHFISettings& settings, bool force); - airspyhf_device_t *open_airspyhf_from_serial(const QString& serialStr); - void setDeviceCenterFrequency(quint64 freq, const AirspyHFISettings& settings); - - DeviceSourceAPI *m_deviceAPI; - QMutex m_mutex; - AirspyHFISettings m_settings; - airspyhf_device_t* m_dev; - AirspyHFIThread* m_airspyHFThread; - QString m_deviceDescription; - std::vector m_sampleRates; - bool m_running; - FileRecord *m_fileSink; //!< File sink to record device I/Q output -}; - -#endif // INCLUDE_AIRSPYHFIINPUT_H diff --git a/plugins/samplesource/airspyhfi/airspyhfiplugin.cpp b/plugins/samplesource/airspyhfi/airspyhfiplugin.cpp deleted file mode 100644 index 2df26e08f..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfiplugin.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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 "plugin/pluginapi.h" -#include "util/simpleserializer.h" -#include "airspyhfiplugin.h" - -#include "airspyhfigui.h" - - -const PluginDescriptor AirspyHFIPlugin::m_pluginDescriptor = { - QString("AirspyHF Input (int)"), - QString("3.12.0"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -const QString AirspyHFIPlugin::m_hardwareID = "AirspyHFI"; -const QString AirspyHFIPlugin::m_deviceTypeID = AIRSPYHFI_DEVICE_TYPE_ID; -const int AirspyHFIPlugin::m_maxDevices = 32; - -AirspyHFIPlugin::AirspyHFIPlugin(QObject* parent) : - QObject(parent) -{ -} - -const PluginDescriptor& AirspyHFIPlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void AirspyHFIPlugin::initPlugin(PluginAPI* pluginAPI) -{ - pluginAPI->registerSampleSource(m_deviceTypeID, this); -} - -PluginInterface::SamplingDevices AirspyHFIPlugin::enumSampleSources() -{ - SamplingDevices result; - int nbDevices; - uint64_t deviceSerials[m_maxDevices]; - - nbDevices = airspyhf_list_devices(deviceSerials, m_maxDevices); - - if (nbDevices < 0) - { - qCritical("AirspyHFIPlugin::enumSampleSources: failed to list Airspy HF devices"); - } - - for (int i = 0; i < nbDevices; i++) - { - if (deviceSerials[i]) - { - QString serial_str = QString::number(deviceSerials[i], 16); - QString displayedName(QString("AirspyHF(int)[%1] %2").arg(i).arg(serial_str)); - - result.append(SamplingDevice(displayedName, - m_hardwareID, - m_deviceTypeID, - serial_str, - i, - PluginInterface::SamplingDevice::PhysicalDevice, - true, - 1, - 0)); - - qDebug("AirspyHFIPlugin::enumSampleSources: enumerated Airspy device #%d", i); - } - else - { - qDebug("AirspyHFIPlugin::enumSampleSources: finished to enumerate Airspy HF. %d devices found", i); - break; // finished - } - } - - return result; -} - -PluginInstanceGUI* AirspyHFIPlugin::createSampleSourcePluginInstanceGUI( - const QString& sourceId, - QWidget **widget, - DeviceUISet *deviceUISet) -{ - if (sourceId == m_deviceTypeID) - { - AirspyHFIGui* gui = new AirspyHFIGui(deviceUISet); - *widget = gui; - return gui; - } - else - { - return 0; - } -} - -DeviceSampleSource *AirspyHFIPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) -{ - if (sourceId == m_deviceTypeID) - { - AirspyHFIInput* input = new AirspyHFIInput(deviceAPI); - return input; - } - else - { - return 0; - } -} diff --git a/plugins/samplesource/airspyhfi/airspyhfiplugin.h b/plugins/samplesource/airspyhfi/airspyhfiplugin.h deleted file mode 100644 index 33b73bc69..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfiplugin.h +++ /dev/null @@ -1,53 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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_AIRSPYHFIPLUGIN_H -#define INCLUDE_AIRSPYHFIPLUGIN_H - -#include -#include "plugin/plugininterface.h" - -#define AIRSPYHFI_DEVICE_TYPE_ID "sdrangel.samplesource.airspyhfi" - -class PluginAPI; - -class AirspyHFIPlugin : public QObject, public PluginInterface { - Q_OBJECT - Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID AIRSPYHFI_DEVICE_TYPE_ID) - -public: - explicit AirspyHFIPlugin(QObject* parent = 0); - - const PluginDescriptor& getPluginDescriptor() const; - void initPlugin(PluginAPI* pluginAPI); - - virtual SamplingDevices enumSampleSources(); - virtual PluginInstanceGUI* createSampleSourcePluginInstanceGUI( - const QString& sourceId, - QWidget **widget, - DeviceUISet *deviceUISet); - virtual DeviceSampleSource* createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI); - - static const QString m_hardwareID; - static const QString m_deviceTypeID; - static const int m_maxDevices; - -private: - static const PluginDescriptor m_pluginDescriptor; -}; - -#endif // INCLUDE_AIRSPYHFIPLUGIN_H diff --git a/plugins/samplesource/airspyhfi/airspyhfisettings.cpp b/plugins/samplesource/airspyhfi/airspyhfisettings.cpp deleted file mode 100644 index f0cc373d8..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfisettings.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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 "util/simpleserializer.h" -#include "airspyhfisettings.h" - -AirspyHFISettings::AirspyHFISettings() -{ - resetToDefaults(); -} - -void AirspyHFISettings::resetToDefaults() -{ - m_centerFrequency = 7150*1000; - m_LOppmTenths = 0; - m_devSampleRateIndex = 0; - m_log2Decim = 0; - m_transverterMode = false; - m_transverterDeltaFrequency = 0; - m_bandIndex = 0; - m_autoCorrOptions = AutoCorrNone; -} - -QByteArray AirspyHFISettings::serialize() const -{ - SimpleSerializer s(1); - - s.writeU32(1, m_devSampleRateIndex); - s.writeS32(2, m_LOppmTenths); - s.writeU32(3, m_log2Decim); - s.writeS32(4, (int) m_autoCorrOptions); - s.writeBool(7, m_transverterMode); - s.writeS64(8, m_transverterDeltaFrequency); - s.writeU32(9, m_bandIndex); - - return s.final(); -} - -bool AirspyHFISettings::deserialize(const QByteArray& data) -{ - SimpleDeserializer d(data); - - if (!d.isValid()) - { - resetToDefaults(); - return false; - } - - if (d.getVersion() == 1) - { - int intval; - quint32 uintval; - - d.readU32(1, &m_devSampleRateIndex, 0); - d.readS32(2, &m_LOppmTenths, 0); - d.readU32(3, &m_log2Decim, 0); - d.readS32(4, &intval, 0); - - if (intval < 0 || intval > (int) AutoCorrLast) { - m_autoCorrOptions = AutoCorrNone; - } else { - m_autoCorrOptions = (AutoCorrOptions) intval; - } - - d.readBool(7, &m_transverterMode, false); - d.readS64(8, &m_transverterDeltaFrequency, 0); - d.readU32(9, &uintval, 0); - m_bandIndex = uintval > 1 ? 1 : uintval; - - return true; - } - else - { - resetToDefaults(); - return false; - } -} diff --git a/plugins/samplesource/airspyhfi/airspyhfisettings.h b/plugins/samplesource/airspyhfi/airspyhfisettings.h deleted file mode 100644 index 3f0d625d7..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfisettings.h +++ /dev/null @@ -1,44 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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 _AIRSPYHF_AIRSPYHFISETTINGS_H_ -#define _AIRSPYHF_AIRSPYHFISETTINGS_H_ - -struct AirspyHFISettings -{ - typedef enum { - AutoCorrNone, - AutoCorrDC, - AutoCorrDCAndIQ, - AutoCorrLast, - } AutoCorrOptions; - - quint64 m_centerFrequency; - qint32 m_LOppmTenths; - quint32 m_devSampleRateIndex; - quint32 m_log2Decim; - bool m_transverterMode; - qint64 m_transverterDeltaFrequency; - quint32 m_bandIndex; - AutoCorrOptions m_autoCorrOptions; - - AirspyHFISettings(); - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); -}; - -#endif /* _AIRSPYHF_AIRSPYHFISETTINGS_H_ */ diff --git a/plugins/samplesource/airspyhfi/airspyhfithread.cpp b/plugins/samplesource/airspyhfi/airspyhfithread.cpp deleted file mode 100644 index f3953c70f..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfithread.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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 "dsp/samplesinkfifo.h" -#include "airspyhfithread.h" - -AirspyHFIThread *AirspyHFIThread::m_this = 0; - -AirspyHFIThread::AirspyHFIThread(airspyhf_device_t* dev, SampleSinkFifo* sampleFifo, QObject* parent) : - QThread(parent), - m_running(false), - m_dev(dev), - m_convertBuffer(AIRSPYHFI_BLOCKSIZE), - m_sampleFifo(sampleFifo), - m_samplerate(10), - m_log2Decim(0) -{ - m_this = this; -} - -AirspyHFIThread::~AirspyHFIThread() -{ - stopWork(); - m_this = 0; -} - -void AirspyHFIThread::startWork() -{ - m_startWaitMutex.lock(); - start(); - while(!m_running) - m_startWaiter.wait(&m_startWaitMutex, 100); - m_startWaitMutex.unlock(); -} - -void AirspyHFIThread::stopWork() -{ - qDebug("AirspyThread::stopWork"); - m_running = false; - wait(); -} - -void AirspyHFIThread::setSamplerate(uint32_t samplerate) -{ - m_samplerate = samplerate; -} - -void AirspyHFIThread::setLog2Decimation(unsigned int log2_decim) -{ - m_log2Decim = log2_decim; -} - -void AirspyHFIThread::run() -{ - airspyhf_error rc; - - m_running = true; - m_startWaiter.wakeAll(); - - rc = (airspyhf_error) airspyhf_start(m_dev, rx_callback, 0); - - if (rc != AIRSPYHF_SUCCESS) - { - qCritical("AirspyHFThread::run: failed to start Airspy Rx"); - } - else - { - while ((m_running) && (airspyhf_is_streaming(m_dev) != 0)) - { - sleep(1); - } - } - - rc = (airspyhf_error) airspyhf_stop(m_dev); - - if (rc == AIRSPYHF_SUCCESS) { - qDebug("AirspyHFThread::run: stopped Airspy Rx"); - } else { - qDebug("AirspyHFThread::run: failed to stop Airspy Rx"); - } - - m_running = false; -} - -// Decimate according to specified log2 (ex: log2=4 => decim=16) -void AirspyHFIThread::callback(const qint16* buf, qint32 len) -{ - SampleVector::iterator it = m_convertBuffer.begin(); - - switch (m_log2Decim) - { - case 0: - m_decimators.decimate1(&it, buf, len); - break; - case 1: - m_decimators.decimate2_cen(&it, buf, len); - break; - case 2: - m_decimators.decimate4_cen(&it, buf, len); - break; - case 3: - m_decimators.decimate8_cen(&it, buf, len); - break; - case 4: - m_decimators.decimate16_cen(&it, buf, len); - break; - case 5: - m_decimators.decimate32_cen(&it, buf, len); - break; - case 6: - m_decimators.decimate64_cen(&it, buf, len); - break; - default: - break; - } - - m_sampleFifo->write(m_convertBuffer.begin(), it); -} - - -int AirspyHFIThread::rx_callback(airspyhf_transfer_t* transfer) -{ - qint32 bytes_to_write = transfer->sample_count * sizeof(qint16); - m_this->callback((qint16 *) transfer->samples, bytes_to_write); - return 0; -} diff --git a/plugins/samplesource/airspyhfi/airspyhfithread.h b/plugins/samplesource/airspyhfi/airspyhfithread.h deleted file mode 100644 index 8893aa195..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfithread.h +++ /dev/null @@ -1,67 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 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 // -// // -// 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_AIRSPYHFITHREAD_H -#define INCLUDE_AIRSPYHFITHREAD_H - -#include -#include -#include -#include - -#include "dsp/samplesinkfifo.h" -#include "dsp/decimators.h" - -#define AIRSPYHFI_BLOCKSIZE (1<<17) - -class AirspyHFIThread : public QThread { - Q_OBJECT - -public: - AirspyHFIThread(airspyhf_device_t* dev, SampleSinkFifo* sampleFifo, QObject* parent = 0); - ~AirspyHFIThread(); - - void startWork(); - void stopWork(); - void setSamplerate(uint32_t samplerate); - void setLog2Decimation(unsigned int log2_decim); - -private: - QMutex m_startWaitMutex; - QWaitCondition m_startWaiter; - bool m_running; - - airspyhf_device_t* m_dev; - qint16 m_buf[2*AIRSPYHFI_BLOCKSIZE]; - SampleVector m_convertBuffer; - SampleSinkFifo* m_sampleFifo; - - int m_samplerate; - unsigned int m_log2Decim; - static AirspyHFIThread *m_this; - -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else - Decimators m_decimators; -#endif - - void run(); - void callback(const qint16* buf, qint32 len); - static int rx_callback(airspyhf_transfer_t* transfer); -}; - -#endif // INCLUDE_AIRSPYHFITHREAD_H diff --git a/plugins/samplesource/airspyhfi/readme.md b/plugins/samplesource/airspyhfi/readme.md deleted file mode 100644 index 6af205a0d..000000000 --- a/plugins/samplesource/airspyhfi/readme.md +++ /dev/null @@ -1,105 +0,0 @@ -

    AirspyHF input plugin

    - -

    Introduction

    - -This input sample source plugin gets its samples from a [Airspy HF+ device](https://airspy.com/airspy-hf-plus/). - -

    Build

    - -The plugin will be built only if the [Airspy HF library](https://github.com/f4exb/airspyhf) is installed in your system. Please note that you should use my fork as it deals with integer samples. The branch to check out is `intsamples` but this is the default in Github. - -If you build it from source and install it in a custom location say: `/opt/install/libairspyhf` you will have to add `-DLIBRTLSDR_INCLUDE_DIR=/opt/install/libairspyhf/include -DLIBRTLSDR_LIBRARIES=/opt/install/libairspyhf/lib/libairspyhf.so` to the cmake command line. - -Note: if you use binary distributions this is included in the bundle. - -

    Interface

    - -It has very few controls compared to other source interfaces. This is because a lot of things are handled automatically with the AirspyHF+: - - - gains (hardware) - - DC and IQ correction (library software) - -![AirspyHF input plugin GUI](../../../doc/img/AirspyHFInput_plugin.png) - -

    1: Common stream parameters

    - -![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_01.png) - -

    1.1: Frequency

    - -This is the center frequency of reception in kHz. - -

    1.2: Start/Stop

    - -Device start / stop button. - - - Blue triangle icon: device is ready and can be started - - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. - -

    1.3: Record

    - -Record baseband I/Q stream toggle button - -

    1.4: Stream sample rate

    - -Baseband I/Q sample rate in kS/s. This is the device to host sample rate (3) divided by the decimation factor (4). - -

    2: Lo ppm correction

    - -This is the correction factor in ppm applied to the local oscillator. The Airspy HF LO has 1 kHz increments so anything in between is obtained by mixing the signal with a Hz precision NCO. This is actually done in the AirspyHF library. - -On HF band the LO correction is not necessary because the LO is largely precise enough for the frequencies involved. You can disable the NCO in AirspyHF library by setting the value to zero. Since the LO control in SDRangel has a 1 kHz step the NCO correction will always be zero. In AirspyHF library (my fork) the NCO is not active (no extra complex multiplication) if the correction is zero. On HF band it is recommended not to use the LO correction (set it or leave it at 0). - -You can reset the ppm value anytime by pressing on button (3) - -

    3: Reset LO ppm correction

    - -THis resets the LO ppm correction (zero the value). By doing so the LO trimming NCO in AirspyHF libray is disabled. - -

    4: Band select

    - -Use this combo box to select the HF or VHF range. This will set the limits of the frequency dial (1.1) appropriately and possibly move the current frequency inside the limits. Limits are given by the AirspyHF+ specifications: - - - HF: 9 kHz to 31 MHz - - VHF: 60 to 260 MHz - -

    5: Device to hast sample rate

    - -This is the device to host sample rate in samples per second (S/s). - -Although the combo box is there to present a choice of sample rates at present the AirspyHF+ deals only with 768 kS/s. However the support library has provision to get a list of sample rates from the device incase of future developments. - -

    6: Decimation factor

    - -The I/Q stream from the AirspyHF to host is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. When using audio channel plugins (AM, DSD, NFM, SSB...) please make sure that the sample rate is not less than 48 kHz (no decimation by 32 or 64). - -

    7: Transverter mode open dialog

    - -This button opens a dialog to set the transverter mode frequency translation options: - -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) - -Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. - -

    7a.1: Translating frequency

    - -You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. - -The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. - -For example with the DX Patrol that has a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set at 7,130 kHz the RTLSDR of the DX Patrol will be set to 127.130 MHz. - -If you use a down converter to receive the 6 cm band narrowband center frequency of 5670 MHz at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. - -For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to receive the 10368 MHz frequency at 432 MHz you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. - -The Hz precision allows a fine tuning of the transverter LO offset - -

    7a.2: Translating frequency enable/disable

    - -Use this toggle button to activate or deactivate the frequency translation - -

    7a.3: Confirmation buttons

    - -Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. diff --git a/pluginssrv/samplesink/CMakeLists.txt b/pluginssrv/samplesink/CMakeLists.txt index fc12191ee..9a692f7f3 100644 --- a/pluginssrv/samplesink/CMakeLists.txt +++ b/pluginssrv/samplesink/CMakeLists.txt @@ -2,6 +2,11 @@ project(samplesink) find_package(LibUSB) +find_package(LibBLADERF) +if(LIBUSB_FOUND AND LIBBLADERF_FOUND) + add_subdirectory(bladerfoutput) +endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) + find_package(LibHACKRF) if(LIBUSB_FOUND AND LIBHACKRF_FOUND) add_subdirectory(hackrfoutput) diff --git a/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt b/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt new file mode 100644 index 000000000..82a66e3dd --- /dev/null +++ b/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt @@ -0,0 +1,68 @@ +project(bladerfoutput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesink/bladerfoutput") + +set(bladerfoutput_SOURCES + ${PLUGIN_PREFIX}/bladerfoutput.cpp + ${PLUGIN_PREFIX}/bladerfoutputplugin.cpp + ${PLUGIN_PREFIX}/bladerfoutputsettings.cpp + ${PLUGIN_PREFIX}/bladerfoutputthread.cpp +) + +set(bladerfoutput_HEADERS + ${PLUGIN_PREFIX}/bladerfoutput.h + ${PLUGIN_PREFIX}/bladerfoutputplugin.h + ${PLUGIN_PREFIX}/bladerfoutputsettings.h + ${PLUGIN_PREFIX}/bladerfoutputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(outputbladerfsrv SHARED + ${bladerfoutput_SOURCES} + ${bladerfoutput_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputbladerfsrv + ${QT_LIBRARIES} + bladerf + sdrbase + swagger + bladerfdevice +) +else (BUILD_DEBIAN) +target_link_libraries(outputbladerfsrv + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + swagger + bladerfdevice +) +endif (BUILD_DEBIAN) + +qt5_use_modules(outputbladerfsrv Core Widgets) + +install(TARGETS outputbladerfsrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 3cc921e76..075a0727f 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -8,11 +8,21 @@ if(V4L-RTL) # add_subdirectory(v4l-rtl) endif() if(V4L-MSI) - FIND_LIBRARY (LIBV4L2 v4l2) + FIND_LIBRARY (LIBV4L2 v4l2) FIND_PATH (LIBV4L2H libv4l2.h) # add_subdirectory(v4l-msi) endif() +find_package(LibAIRSPYHF) +if(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) + add_subdirectory(airspyhf) +endif(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) + +find_package(LibBLADERF) +if(LIBUSB_FOUND AND LIBBLADERF_FOUND) + add_subdirectory(bladerfinput) +endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) + find_package(LibHACKRF) if(LIBUSB_FOUND AND LIBHACKRF_FOUND) add_subdirectory(hackrfinput) @@ -29,6 +39,7 @@ if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) if (BUILD_DEBIAN) + add_subdirectory(airspyhf) add_subdirectory(hackrfinput) add_subdirectory(limesdrinput) add_subdirectory(rtlsdr) diff --git a/pluginssrv/samplesource/airspyhf/CMakeLists.txt b/pluginssrv/samplesource/airspyhf/CMakeLists.txt new file mode 100644 index 000000000..bc5885a12 --- /dev/null +++ b/pluginssrv/samplesource/airspyhf/CMakeLists.txt @@ -0,0 +1,64 @@ +project(airspyhf) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/airspyhf") + +set(airspyhf_SOURCES + ${PLUGIN_PREFIX}/airspyhfinput.cpp + ${PLUGIN_PREFIX}/airspyhfplugin.cpp + ${PLUGIN_PREFIX}/airspyhfsettings.cpp + ${PLUGIN_PREFIX}/airspyhfthread.cpp +) + +set(airspyhf_HEADERS + ${PLUGIN_PREFIX}/airspyhfinput.h + ${PLUGIN_PREFIX}/airspyhfplugin.h + ${PLUGIN_PREFIX}/airspyhfsettings.h + ${PLUGIN_PREFIX}/airspyhfthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBAIRSPYHFSRC} + ${LIBAIRSPYHFSRC}/libairspyhf/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBAIRSPYHF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputairspyhfsrv SHARED + ${airspyhf_SOURCES} + ${airspyhf_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputairspyhfsrv + ${QT_LIBRARIES} + airspyhf + sdrbase + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(inputairspyhfsrv + ${QT_LIBRARIES} + ${LIBAIRSPYHF_LIBRARIES} + sdrbase + swagger +) +endif (BUILD_DEBIAN) + +qt5_use_modules(inputairspyhfsrv Core Widgets) + +install(TARGETS inputairspyhfsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/bladerfinput/CMakeLists.txt b/pluginssrv/samplesource/bladerfinput/CMakeLists.txt new file mode 100644 index 000000000..d9b1ebfc9 --- /dev/null +++ b/pluginssrv/samplesource/bladerfinput/CMakeLists.txt @@ -0,0 +1,68 @@ +project(bladerfinput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/bladerfinput") + +set(bladerfinput_SOURCES + ${PLUGIN_PREFIX}/bladerfinput.cpp + ${PLUGIN_PREFIX}/bladerfinputplugin.cpp + ${PLUGIN_PREFIX}/bladerfinputsettings.cpp + ${PLUGIN_PREFIX}/bladerfinputthread.cpp +) + +set(bladerfinput_HEADERS + ${PLUGIN_PREFIX}/bladerfinput.h + ${PLUGIN_PREFIX}/bladerfinputplugin.h + ${PLUGIN_PREFIX}/bladerfinputsettings.h + ${PLUGIN_PREFIX}/bladerfinputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputbladerfsrv SHARED + ${bladerfinput_SOURCES} + ${bladerfinput_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputbladerfsrv + ${QT_LIBRARIES} + bladerf + sdrbase + swagger + bladerfdevice +) +else (BUILD_DEBIAN) +target_link_libraries(inputbladerfsrv + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + swagger + bladerfdevice +) +endif (BUILD_DEBIAN) + +qt5_use_modules(inputbladerfsrv Core Widgets) + +install(TARGETS inputbladerfsrv DESTINATION lib/pluginssrv/samplesource) From 376e0d9b1f138a24f7ef2ab1e08915698bbcd58a Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 5 Apr 2018 21:58:45 +0200 Subject: [PATCH 251/956] AM mod: put AF input mode in settings --- plugins/channeltx/modam/ammod.cpp | 22 +++++----------- plugins/channeltx/modam/ammod.h | 31 ----------------------- plugins/channeltx/modam/ammodgui.cpp | 25 ++++++++---------- plugins/channeltx/modam/ammodgui.h | 1 - plugins/channeltx/modam/ammodsettings.cpp | 9 +++++++ plugins/channeltx/modam/ammodsettings.h | 10 ++++++++ 6 files changed, 36 insertions(+), 62 deletions(-) diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index 924a04f54..e43849c2c 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -33,7 +33,6 @@ MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureAMMod, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceName, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureAFInput, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceStreamTiming, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgReportFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgReportFileSourceStreamTiming, Message) @@ -53,7 +52,6 @@ AMMod::AMMod(DeviceSinkAPI *deviceAPI) : m_fileSize(0), m_recordLength(0), m_sampleRate(48000), - m_afInput(AMModInputNone), m_levelCalcCount(0), m_peakLevel(0.0f), m_levelSum(0.0f) @@ -165,12 +163,12 @@ void AMMod::modulateSample() void AMMod::pullAF(Real& sample) { - switch (m_afInput) + switch (m_settings.m_modAFInput) { - case AMModInputTone: + case AMModSettings::AMModInputTone: sample = m_toneNco.next(); break; - case AMModInputFile: + case AMModSettings::AMModInputFile: // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw if (m_ifstream.is_open()) @@ -199,10 +197,10 @@ void AMMod::pullAF(Real& sample) sample = 0.0f; } break; - case AMModInputAudio: + case AMModSettings::AMModInputAudio: sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor; break; - case AMModInputCWTone: + case AMModSettings::AMModInputCWTone: Real fadeFactor; if (m_cwKeyer.getSample()) @@ -223,7 +221,7 @@ void AMMod::pullAF(Real& sample) } } break; - case AMModInputNone: + case AMModSettings::AMModInputNone: default: sample = 0.0f; break; @@ -313,13 +311,6 @@ bool AMMod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureAFInput::match(cmd)) - { - MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; - m_afInput = conf.getAFInput(); - - return true; - } else if (MsgConfigureFileSourceStreamTiming::match(cmd)) { std::size_t samplesCount; @@ -457,6 +448,7 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) << " m_volumeFactor: " << settings.m_volumeFactor << " m_audioMute: " << settings.m_channelMute << " m_playLoop: " << settings.m_playLoop + << " m_modAFInput " << settings.m_modAFInput << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; diff --git a/plugins/channeltx/modam/ammod.h b/plugins/channeltx/modam/ammod.h index b0152069d..5260e6986 100644 --- a/plugins/channeltx/modam/ammod.h +++ b/plugins/channeltx/modam/ammod.h @@ -89,15 +89,6 @@ public: { } }; - typedef enum - { - AMModInputNone, - AMModInputTone, - AMModInputFile, - AMModInputAudio, - AMModInputCWTone - } AMModInputAF; - class MsgConfigureFileSourceName : public Message { MESSAGE_CLASS_DECLARATION @@ -157,27 +148,6 @@ public: { } }; - class MsgConfigureAFInput : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - AMModInputAF getAFInput() const { return m_afInput; } - - static MsgConfigureAFInput* create(AMModInputAF afInput) - { - return new MsgConfigureAFInput(afInput); - } - - private: - AMModInputAF m_afInput; - - MsgConfigureAFInput(AMModInputAF afInput) : - Message(), - m_afInput(afInput) - { } - }; - class MsgReportFileSourceStreamTiming : public Message { MESSAGE_CLASS_DECLARATION @@ -302,7 +272,6 @@ private: quint32 m_recordLength; //!< record length in seconds computed from file size int m_sampleRate; - AMModInputAF m_afInput; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index 6d8316f96..97ac3fad5 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -186,9 +186,8 @@ void AMModGUI::on_play_toggled(bool checked) ui->tone->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->mic->setEnabled(!checked); - m_modAFInput = checked ? AMMod::AMModInputFile : AMMod::AMModInputNone; - AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); - m_amMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? AMModSettings::AMModInputFile : AMModSettings::AMModInputNone; + applySettings(); ui->navTimeSlider->setEnabled(!checked); m_enableNavTime = !checked; } @@ -198,9 +197,8 @@ void AMModGUI::on_tone_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->mic->setEnabled(!checked); - m_modAFInput = checked ? AMMod::AMModInputTone : AMMod::AMModInputNone; - AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); - m_amMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? AMModSettings::AMModInputTone : AMModSettings::AMModInputNone; + applySettings(); } void AMModGUI::on_morseKeyer_toggled(bool checked) @@ -208,9 +206,8 @@ void AMModGUI::on_morseKeyer_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->tone->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); - m_modAFInput = checked ? AMMod::AMModInputCWTone : AMMod::AMModInputNone; - AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); - m_amMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? AMModSettings::AMModInputCWTone : AMModSettings::AMModInputNone; + applySettings(); } void AMModGUI::on_mic_toggled(bool checked) @@ -218,9 +215,8 @@ void AMModGUI::on_mic_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->tone->setEnabled(!checked); // release other source inputs - m_modAFInput = checked ? AMMod::AMModInputAudio : AMMod::AMModInputNone; - AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); - m_amMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? AMModSettings::AMModInputAudio : AMModSettings::AMModInputNone; + applySettings(); } void AMModGUI::on_navTimeSlider_valueChanged(int value) @@ -272,8 +268,7 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampl m_recordSampleRate(48000), m_samplesCount(0), m_tickCount(0), - m_enableNavTime(false), - m_modAFInput(AMMod::AMModInputNone) + m_enableNavTime(false) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); @@ -413,7 +408,7 @@ void AMModGUI::tick() m_channelPowerDbAvg(powDb); ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1)); - if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == AMMod::AMModInputFile)) + if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == AMModSettings::AMModInputFile)) { AMMod::MsgConfigureFileSourceStreamTiming* message = AMMod::MsgConfigureFileSourceStreamTiming::create(); m_amMod->getInputMessageQueue()->push(message); diff --git a/plugins/channeltx/modam/ammodgui.h b/plugins/channeltx/modam/ammodgui.h index fcccb2c69..de1d79df4 100644 --- a/plugins/channeltx/modam/ammodgui.h +++ b/plugins/channeltx/modam/ammodgui.h @@ -74,7 +74,6 @@ private: int m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; - AMMod::AMModInputAF m_modAFInput; MessageQueue m_inputMessageQueue; explicit AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); diff --git a/plugins/channeltx/modam/ammodsettings.cpp b/plugins/channeltx/modam/ammodsettings.cpp index aaaf572be..da1d78a2e 100644 --- a/plugins/channeltx/modam/ammodsettings.cpp +++ b/plugins/channeltx/modam/ammodsettings.cpp @@ -39,6 +39,7 @@ void AMModSettings::resetToDefaults() m_playLoop = false; m_rgbColor = QColor(255, 255, 0).rgb(); m_title = "AM Modulator"; + m_modAFInput = AMModInputAF::AMModInputNone; m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } @@ -63,6 +64,7 @@ QByteArray AMModSettings::serialize() const s.writeString(9, m_title); s.writeString(10, m_audioDeviceName); + s.writeS32(11, (int) m_modAFInput); return s.final(); } @@ -103,6 +105,13 @@ bool AMModSettings::deserialize(const QByteArray& data) d.readString(9, &m_title, "AM Modulator"); d.readString(10, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + d.readS32(11, &tmp, 0); + if ((tmp < 0) || (tmp > (int) AMModInputAF::AMModInputTone)) { + m_modAFInput = AMModInputNone; + } else { + m_modAFInput = (AMModInputAF) tmp; + } + return true; } else diff --git a/plugins/channeltx/modam/ammodsettings.h b/plugins/channeltx/modam/ammodsettings.h index 6aadcf6e3..0ed6b435e 100644 --- a/plugins/channeltx/modam/ammodsettings.h +++ b/plugins/channeltx/modam/ammodsettings.h @@ -23,6 +23,15 @@ class Serializable; struct AMModSettings { + typedef enum + { + AMModInputNone, + AMModInputTone, + AMModInputFile, + AMModInputAudio, + AMModInputCWTone + } AMModInputAF; + qint64 m_inputFrequencyOffset; Real m_rfBandwidth; float m_modFactor; @@ -32,6 +41,7 @@ struct AMModSettings bool m_playLoop; quint32 m_rgbColor; QString m_title; + AMModInputAF m_modAFInput; QString m_audioDeviceName; Serializable *m_channelMarker; From 0ba86c0d2238bebc653e72d63df17367a34de4a0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 6 Apr 2018 00:54:18 +0200 Subject: [PATCH 252/956] AM demod: Web API: settings and report implementation. NFM demod: fixes --- plugins/channeltx/modam/CMakeLists.txt | 2 + plugins/channeltx/modam/ammod.cpp | 174 +++++++++ plugins/channeltx/modam/ammod.h | 16 + plugins/channeltx/modam/ammodgui.cpp | 25 ++ plugins/channeltx/modnfm/nfmmod.cpp | 8 +- sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 74 +++- .../webapi/doc/swagger/include/AMMod.yaml | 46 +++ .../webapi/doc/swagger/include/NFMMod.yaml | 2 + .../resources/webapi/doc/swagger/swagger.yaml | 4 + sdrbase/webapi/webapirequestmapper.cpp | 35 ++ .../sdrangel/api/swagger/include/AMMod.yaml | 46 +++ .../sdrangel/api/swagger/include/NFMMod.yaml | 2 + swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 74 +++- .../code/qt5/client/SWGAMModReport.cpp | 148 ++++++++ .../sdrangel/code/qt5/client/SWGAMModReport.h | 70 ++++ .../code/qt5/client/SWGAMModSettings.cpp | 343 ++++++++++++++++++ .../code/qt5/client/SWGAMModSettings.h | 126 +++++++ .../code/qt5/client/SWGChannelReport.cpp | 23 ++ .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 ++ .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../code/qt5/client/SWGNFMModSettings.cpp | 23 ++ .../code/qt5/client/SWGNFMModSettings.h | 6 + swagger/sdrangel/examples/tx_test.py | 147 +++++--- 27 files changed, 1389 insertions(+), 55 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/AMMod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/AMMod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGAMModReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGAMModSettings.h diff --git a/plugins/channeltx/modam/CMakeLists.txt b/plugins/channeltx/modam/CMakeLists.txt index 7acec8bc9..a9ac4cc4c 100644 --- a/plugins/channeltx/modam/CMakeLists.txt +++ b/plugins/channeltx/modam/CMakeLists.txt @@ -23,6 +23,7 @@ set(modam_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) add_definitions(${QT_DEFINITIONS}) @@ -41,6 +42,7 @@ target_link_libraries(modam ${QT_LIBRARIES} sdrbase sdrgui + swagger ) qt5_use_modules(modam Core Widgets) diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index e43849c2c..81f21c521 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -23,11 +23,16 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGAMModReport.h" + #include "dsp/upchannelizer.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" +#include "util/db.h" MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureAMMod, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureChannelizer, Message) @@ -505,3 +510,172 @@ bool AMMod::deserialize(const QByteArray& data) return false; } } + + +int AMMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAmModSettings(new SWGSDRangel::SWGAMModSettings()); + response.getAmModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int AMMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + AMModSettings settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getAmModSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getAmModSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("modAFInput")) { + settings.m_modAFInput = (AMModSettings::AMModInputAF) response.getAmModSettings()->getModAfInput(); + } + if (channelSettingsKeys.contains("playLoop")) { + settings.m_playLoop = response.getAmModSettings()->getPlayLoop() != 0; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getAmModSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAmModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getAmModSettings()->getTitle(); + } + if (channelSettingsKeys.contains("toneFrequency")) { + settings.m_toneFrequency = response.getAmModSettings()->getToneFrequency(); + } + if (channelSettingsKeys.contains("volumeFactor")) { + settings.m_volumeFactor = response.getAmModSettings()->getVolumeFactor(); + } + if (channelSettingsKeys.contains("modFactor")) { + settings.m_modFactor = response.getAmModSettings()->getModFactor(); + } + + if (channelSettingsKeys.contains("cwKeyer")) + { + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getAmModSettings()->getCwKeyer(); + CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings(); + + if (channelSettingsKeys.contains("cwKeyer.loop")) { + cwKeyerSettings.m_loop = apiCwKeyerSettings->getLoop() != 0; + } + if (channelSettingsKeys.contains("cwKeyer.mode")) { + cwKeyerSettings.m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode(); + } + if (channelSettingsKeys.contains("cwKeyer.text")) { + cwKeyerSettings.m_text = *apiCwKeyerSettings->getText(); + } + if (channelSettingsKeys.contains("cwKeyer.sampleRate")) { + cwKeyerSettings.m_sampleRate = apiCwKeyerSettings->getSampleRate(); + } + if (channelSettingsKeys.contains("cwKeyer.wpm")) { + cwKeyerSettings.m_wpm = apiCwKeyerSettings->getWpm(); + } + + m_cwKeyer.setLoop(cwKeyerSettings.m_loop); + m_cwKeyer.setMode(cwKeyerSettings.m_mode); + m_cwKeyer.setSampleRate(cwKeyerSettings.m_sampleRate); + m_cwKeyer.setText(cwKeyerSettings.m_text); + m_cwKeyer.setWPM(cwKeyerSettings.m_wpm); + + if (m_guiMessageQueue) // forward to GUI if any + { + CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force); + m_guiMessageQueue->push(msgCwKeyer); + } + } + + if (frequencyOffsetChanged) + { + AMMod::MsgConfigureChannelizer *msgChan = AMMod::MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureAMMod *msg = MsgConfigureAMMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAMMod *msgToGUI = MsgConfigureAMMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int AMMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAmModReport(new SWGSDRangel::SWGAMModReport()); + response.getAmModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void AMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMModSettings& settings) +{ + response.getAmModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getAmModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getAmModSettings()->setModAfInput((int) settings.m_modAFInput); + response.getAmModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0); + response.getAmModSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getAmModSettings()->setModFactor(settings.m_modFactor); + response.getAmModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getAmModSettings()->getTitle()) { + *response.getAmModSettings()->getTitle() = settings.m_title; + } else { + response.getAmModSettings()->setTitle(new QString(settings.m_title)); + } + + response.getAmModSettings()->setToneFrequency(settings.m_toneFrequency); + response.getAmModSettings()->setVolumeFactor(settings.m_volumeFactor); + + if (!response.getAmModSettings()->getCwKeyer()) { + response.getAmModSettings()->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings); + } + + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getAmModSettings()->getCwKeyer(); + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode((int) cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + + if (apiCwKeyerSettings->getText()) { + *apiCwKeyerSettings->getText() = cwKeyerSettings.m_text; + } else { + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + } + + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + + if (response.getAmModSettings()->getAudioDeviceName()) { + *response.getAmModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getAmModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void AMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getAmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getAmModReport()->setAudioSampleRate(m_audioSampleRate); + response.getAmModReport()->setChannelSampleRate(m_outputSampleRate); +} diff --git a/plugins/channeltx/modam/ammod.h b/plugins/channeltx/modam/ammod.h index 5260e6986..5c0e4a6c1 100644 --- a/plugins/channeltx/modam/ammod.h +++ b/plugins/channeltx/modam/ammod.h @@ -215,6 +215,20 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } CWKeyer *getCWKeyer() { return &m_cwKeyer; } @@ -287,6 +301,8 @@ private: void modulateSample(); void openFileStream(); void seekFileStream(int seekPercentage); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index 97ac3fad5..2df9076fe 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -106,6 +106,21 @@ bool AMModGUI::handleMessage(const Message& message) updateWithStreamTime(); return true; } + else if (AMMod::MsgConfigureAMMod::match(message)) + { + const AMMod::MsgConfigureAMMod& cfg = (AMMod::MsgConfigureAMMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (CWKeyer::MsgConfigureCWKeyer::match(message)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) message; + ui->cwKeyerGUI->displaySettings(cfg.getSettings()); + return true; + } else { return false; @@ -376,6 +391,16 @@ void AMModGUI::displaySettings() ui->channelMute->setChecked(m_settings.m_channelMute); ui->playLoop->setChecked(m_settings.m_playLoop); + ui->tone->setEnabled((m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputTone) || (m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputNone)); + ui->mic->setEnabled((m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputAudio) || (m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputNone)); + ui->play->setEnabled((m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputFile) || (m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputNone)); + ui->morseKeyer->setEnabled((m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputCWTone) || (m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputNone)); + + ui->tone->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputTone); + ui->mic->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputAudio); + ui->play->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputFile); + ui->morseKeyer->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputCWTone); + blockApplySettings(false); } diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp index 0f9032570..545ac693b 100644 --- a/plugins/channeltx/modnfm/nfmmod.cpp +++ b/plugins/channeltx/modnfm/nfmmod.cpp @@ -653,7 +653,7 @@ int NFMMod::webapiSettingsPutPatch( if (frequencyOffsetChanged) { NFMMod::MsgConfigureChannelizer *msgChan = NFMMod::MsgConfigureChannelizer::create( - 48000, settings.m_inputFrequencyOffset); + m_audioSampleRate, settings.m_inputFrequencyOffset); m_inputMessageQueue.push(msgChan); } @@ -719,10 +719,10 @@ void NFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); } - if (response.getNfmDemodSettings()->getAudioDeviceName()) { - *response.getNfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + if (response.getNfmModSettings()->getAudioDeviceName()) { + *response.getNfmModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; } else { - response.getNfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + response.getNfmModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); } apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index e825ce6dc..c41a268cd 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -9,6 +9,7 @@ webapi/doc/swagger/include/HackRF.yaml webapi/doc/swagger/include/LimeSdr.yaml webapi/doc/swagger/include/AMDemod.yaml + webapi/doc/swagger/include/AMMod.yaml webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml webapi/doc/swagger/include/RtlSdr.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 7c9914972..6bbaa05df 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -755,6 +755,69 @@ margin-bottom: 20px; } }, "description" : "AMDemod" +}; + defs.AMModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "AMMod" +}; + defs.AMModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "modFactor" : { + "type" : "number", + "format" : "float", + "description" : "modulation factor from 0.0 to 1.0" + }, + "toneFrequency" : { + "type" : "number", + "format" : "float" + }, + "volumeFactor" : { + "type" : "number", + "format" : "float" + }, + "channelMute" : { + "type" : "integer" + }, + "playLoop" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "modAFInput" : { + "type" : "integer" + }, + "cwKeyer" : { + "$ref" : "#/definitions/CWKeyerSettings" + } + }, + "description" : "AMMod" }; defs.AirspyHFSettings = { "properties" : { @@ -1058,6 +1121,9 @@ margin-bottom: 20px; "AMDemodReport" : { "$ref" : "#/definitions/AMDemodReport" }, + "AMModReport" : { + "$ref" : "#/definitions/AMModReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1082,6 +1148,9 @@ margin-bottom: 20px; "AMDemodSettings" : { "$ref" : "#/definitions/AMDemodSettings" }, + "AMModSettings" : { + "$ref" : "#/definitions/AMModSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -1723,6 +1792,9 @@ margin-bottom: 20px; "title" : { "type" : "string" }, + "audioDeviceName" : { + "type" : "string" + }, "modAFInput" : { "type" : "integer" }, @@ -20190,7 +20262,7 @@ except ApiException as e:
    - Generated 2018-04-04T22:16:33.651+02:00 + Generated 2018-04-06T00:10:47.664+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/AMMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/AMMod.yaml new file mode 100644 index 000000000..5b96deaaf --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/AMMod.yaml @@ -0,0 +1,46 @@ +AMModSettings: + description: AMMod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + modFactor: + description: modulation factor from 0.0 to 1.0 + type: number + format: float + toneFrequency: + type: number + format: float + volumeFactor: + type: number + format: float + channelMute: + type: integer + playLoop: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + modAFInput: + type: integer + cwKeyer: + $ref: "/doc/swagger/include/CWKeyer.yaml#/CWKeyerSettings" + +AMModReport: + description: AMMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml index d608d47ed..e476cc261 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/NFMMod.yaml @@ -31,6 +31,8 @@ NFMModSettings: type: integer title: type: string + audioDeviceName: + type: string modAFInput: type: integer cwKeyer: diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index d5380e57e..9d072491c 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1747,6 +1747,8 @@ definitions: type: integer AMDemodSettings: $ref: "/doc/swagger/include/AMDemod.yaml#/AMDemodSettings" + AMModSettings: + $ref: "/doc/swagger/include/AMMod.yaml#/AMModSettings" NFMDemodSettings: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: @@ -1764,6 +1766,8 @@ definitions: type: integer AMDemodReport: $ref: "/doc/swagger/include/AMDemod.yaml#/AMDemodReport" + AMModReport: + $ref: "/doc/swagger/include/AMMod.yaml#/AMModReport" NFMDemodReport: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 7c60fc521..ecb8a5579 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1840,6 +1840,27 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "AMMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject amModSettingsJsonObject = jsonObject["AMModSettings"].toObject(); + channelSettingsKeys = amModSettingsJsonObject.keys(); + + if (channelSettingsKeys.contains("cwKeyer")) + { + QJsonObject cwKeyerSettingsJsonObject; + appendSettingsSubKeys(amModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); + } + + channelSettings.setAmModSettings(new SWGSDRangel::SWGAMModSettings()); + channelSettings.getAmModSettings()->fromJsonObject(amModSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "NFMDemod") { if (channelSettings.getTx() == 0) @@ -1914,6 +1935,20 @@ bool WebAPIRequestMapper::validateChannelReport( return false; } } + else if (*channelType == "AMMod") + { + if (channelReport.getTx() != 0) + { + QJsonObject amModReportJsonObject = jsonObject["AMModReport"].toObject(); + channelReportKeys = amModReportJsonObject.keys(); + channelReport.setAmModReport(new SWGSDRangel::SWGAMModReport()); + channelReport.getAmModReport()->fromJsonObject(amModReportJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "NFMDemod") { if (channelReport.getTx() == 0) diff --git a/swagger/sdrangel/api/swagger/include/AMMod.yaml b/swagger/sdrangel/api/swagger/include/AMMod.yaml new file mode 100644 index 000000000..7393f2d7e --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/AMMod.yaml @@ -0,0 +1,46 @@ +AMModSettings: + description: AMMod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + modFactor: + description: modulation factor from 0.0 to 1.0 + type: number + format: float + toneFrequency: + type: number + format: float + volumeFactor: + type: number + format: float + channelMute: + type: integer + playLoop: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + modAFInput: + type: integer + cwKeyer: + $ref: "http://localhost:8081/api/swagger/include/CWKeyer.yaml#/CWKeyerSettings" + +AMModReport: + description: AMMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/include/NFMMod.yaml b/swagger/sdrangel/api/swagger/include/NFMMod.yaml index 26482755b..591eaa3b0 100644 --- a/swagger/sdrangel/api/swagger/include/NFMMod.yaml +++ b/swagger/sdrangel/api/swagger/include/NFMMod.yaml @@ -31,6 +31,8 @@ NFMModSettings: type: integer title: type: string + audioDeviceName: + type: string modAFInput: type: integer cwKeyer: diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 350eba899..944aef1a5 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1747,6 +1747,8 @@ definitions: type: integer AMDemodSettings: $ref: "http://localhost:8081/api/swagger/include/AMDemod.yaml#/AMDemodSettings" + AMModSettings: + $ref: "http://localhost:8081/api/swagger/include/AMMod.yaml#/AMModSettings" NFMDemodSettings: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: @@ -1764,6 +1766,8 @@ definitions: type: integer AMDemodReport: $ref: "http://localhost:8081/api/swagger/include/AMDemod.yaml#/AMDemodReport" + AMModReport: + $ref: "http://localhost:8081/api/swagger/include/AMMod.yaml#/AMModReport" NFMDemodReport: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 7c9914972..6bbaa05df 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -755,6 +755,69 @@ margin-bottom: 20px; } }, "description" : "AMDemod" +}; + defs.AMModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "AMMod" +}; + defs.AMModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "modFactor" : { + "type" : "number", + "format" : "float", + "description" : "modulation factor from 0.0 to 1.0" + }, + "toneFrequency" : { + "type" : "number", + "format" : "float" + }, + "volumeFactor" : { + "type" : "number", + "format" : "float" + }, + "channelMute" : { + "type" : "integer" + }, + "playLoop" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "modAFInput" : { + "type" : "integer" + }, + "cwKeyer" : { + "$ref" : "#/definitions/CWKeyerSettings" + } + }, + "description" : "AMMod" }; defs.AirspyHFSettings = { "properties" : { @@ -1058,6 +1121,9 @@ margin-bottom: 20px; "AMDemodReport" : { "$ref" : "#/definitions/AMDemodReport" }, + "AMModReport" : { + "$ref" : "#/definitions/AMModReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1082,6 +1148,9 @@ margin-bottom: 20px; "AMDemodSettings" : { "$ref" : "#/definitions/AMDemodSettings" }, + "AMModSettings" : { + "$ref" : "#/definitions/AMModSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -1723,6 +1792,9 @@ margin-bottom: 20px; "title" : { "type" : "string" }, + "audioDeviceName" : { + "type" : "string" + }, "modAFInput" : { "type" : "integer" }, @@ -20190,7 +20262,7 @@ except ApiException as e:
    - Generated 2018-04-04T22:16:33.651+02:00 + Generated 2018-04-06T00:10:47.664+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp new file mode 100644 index 000000000..17e18907e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAMModReport.cpp @@ -0,0 +1,148 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAMModReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAMModReport::SWGAMModReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAMModReport::SWGAMModReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGAMModReport::~SWGAMModReport() { + this->cleanup(); +} + +void +SWGAMModReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGAMModReport::cleanup() { + + + +} + +SWGAMModReport* +SWGAMModReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAMModReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGAMModReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAMModReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_audio_sample_rate_isSet){ + obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGAMModReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGAMModReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGAMModReport::getAudioSampleRate() { + return audio_sample_rate; +} +void +SWGAMModReport::setAudioSampleRate(qint32 audio_sample_rate) { + this->audio_sample_rate = audio_sample_rate; + this->m_audio_sample_rate_isSet = true; +} + +qint32 +SWGAMModReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGAMModReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGAMModReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModReport.h b/swagger/sdrangel/code/qt5/client/SWGAMModReport.h new file mode 100644 index 000000000..06d03ae2e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAMModReport.h @@ -0,0 +1,70 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAMModReport.h + * + * AMMod + */ + +#ifndef SWGAMModReport_H_ +#define SWGAMModReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAMModReport: public SWGObject { +public: + SWGAMModReport(); + SWGAMModReport(QString* json); + virtual ~SWGAMModReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAMModReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getAudioSampleRate(); + void setAudioSampleRate(qint32 audio_sample_rate); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 audio_sample_rate; + bool m_audio_sample_rate_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGAMModReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp new file mode 100644 index 000000000..50ca86273 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.cpp @@ -0,0 +1,343 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAMModSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAMModSettings::SWGAMModSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAMModSettings::SWGAMModSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + mod_factor = 0.0f; + m_mod_factor_isSet = false; + tone_frequency = 0.0f; + m_tone_frequency_isSet = false; + volume_factor = 0.0f; + m_volume_factor_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + play_loop = 0; + m_play_loop_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; + mod_af_input = 0; + m_mod_af_input_isSet = false; + cw_keyer = nullptr; + m_cw_keyer_isSet = false; +} + +SWGAMModSettings::~SWGAMModSettings() { + this->cleanup(); +} + +void +SWGAMModSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + mod_factor = 0.0f; + m_mod_factor_isSet = false; + tone_frequency = 0.0f; + m_tone_frequency_isSet = false; + volume_factor = 0.0f; + m_volume_factor_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + play_loop = 0; + m_play_loop_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; + mod_af_input = 0; + m_mod_af_input_isSet = false; + cw_keyer = new SWGCWKeyerSettings(); + m_cw_keyer_isSet = false; +} + +void +SWGAMModSettings::cleanup() { + + + + + + + + + if(title != nullptr) { + delete title; + } + if(audio_device_name != nullptr) { + delete audio_device_name; + } + + if(cw_keyer != nullptr) { + delete cw_keyer; + } +} + +SWGAMModSettings* +SWGAMModSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAMModSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&mod_factor, pJson["modFactor"], "float", ""); + + ::SWGSDRangel::setValue(&tone_frequency, pJson["toneFrequency"], "float", ""); + + ::SWGSDRangel::setValue(&volume_factor, pJson["volumeFactor"], "float", ""); + + ::SWGSDRangel::setValue(&channel_mute, pJson["channelMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&play_loop, pJson["playLoop"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&mod_af_input, pJson["modAFInput"], "qint32", ""); + + ::SWGSDRangel::setValue(&cw_keyer, pJson["cwKeyer"], "SWGCWKeyerSettings", "SWGCWKeyerSettings"); + +} + +QString +SWGAMModSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAMModSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_mod_factor_isSet){ + obj->insert("modFactor", QJsonValue(mod_factor)); + } + if(m_tone_frequency_isSet){ + obj->insert("toneFrequency", QJsonValue(tone_frequency)); + } + if(m_volume_factor_isSet){ + obj->insert("volumeFactor", QJsonValue(volume_factor)); + } + if(m_channel_mute_isSet){ + obj->insert("channelMute", QJsonValue(channel_mute)); + } + if(m_play_loop_isSet){ + obj->insert("playLoop", QJsonValue(play_loop)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } + if(m_mod_af_input_isSet){ + obj->insert("modAFInput", QJsonValue(mod_af_input)); + } + if((cw_keyer != nullptr) && (cw_keyer->isSet())){ + toJsonValue(QString("cwKeyer"), cw_keyer, obj, QString("SWGCWKeyerSettings")); + } + + return obj; +} + +qint64 +SWGAMModSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGAMModSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGAMModSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGAMModSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGAMModSettings::getModFactor() { + return mod_factor; +} +void +SWGAMModSettings::setModFactor(float mod_factor) { + this->mod_factor = mod_factor; + this->m_mod_factor_isSet = true; +} + +float +SWGAMModSettings::getToneFrequency() { + return tone_frequency; +} +void +SWGAMModSettings::setToneFrequency(float tone_frequency) { + this->tone_frequency = tone_frequency; + this->m_tone_frequency_isSet = true; +} + +float +SWGAMModSettings::getVolumeFactor() { + return volume_factor; +} +void +SWGAMModSettings::setVolumeFactor(float volume_factor) { + this->volume_factor = volume_factor; + this->m_volume_factor_isSet = true; +} + +qint32 +SWGAMModSettings::getChannelMute() { + return channel_mute; +} +void +SWGAMModSettings::setChannelMute(qint32 channel_mute) { + this->channel_mute = channel_mute; + this->m_channel_mute_isSet = true; +} + +qint32 +SWGAMModSettings::getPlayLoop() { + return play_loop; +} +void +SWGAMModSettings::setPlayLoop(qint32 play_loop) { + this->play_loop = play_loop; + this->m_play_loop_isSet = true; +} + +qint32 +SWGAMModSettings::getRgbColor() { + return rgb_color; +} +void +SWGAMModSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGAMModSettings::getTitle() { + return title; +} +void +SWGAMModSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +QString* +SWGAMModSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGAMModSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + +qint32 +SWGAMModSettings::getModAfInput() { + return mod_af_input; +} +void +SWGAMModSettings::setModAfInput(qint32 mod_af_input) { + this->mod_af_input = mod_af_input; + this->m_mod_af_input_isSet = true; +} + +SWGCWKeyerSettings* +SWGAMModSettings::getCwKeyer() { + return cw_keyer; +} +void +SWGAMModSettings::setCwKeyer(SWGCWKeyerSettings* cw_keyer) { + this->cw_keyer = cw_keyer; + this->m_cw_keyer_isSet = true; +} + + +bool +SWGAMModSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_mod_factor_isSet){ isObjectUpdated = true; break;} + if(m_tone_frequency_isSet){ isObjectUpdated = true; break;} + if(m_volume_factor_isSet){ isObjectUpdated = true; break;} + if(m_channel_mute_isSet){ isObjectUpdated = true; break;} + if(m_play_loop_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} + if(m_mod_af_input_isSet){ isObjectUpdated = true; break;} + if(cw_keyer != nullptr && cw_keyer->isSet()){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h new file mode 100644 index 000000000..a424b341e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAMModSettings.h @@ -0,0 +1,126 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAMModSettings.h + * + * AMMod + */ + +#ifndef SWGAMModSettings_H_ +#define SWGAMModSettings_H_ + +#include + + +#include "SWGCWKeyerSettings.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAMModSettings: public SWGObject { +public: + SWGAMModSettings(); + SWGAMModSettings(QString* json); + virtual ~SWGAMModSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAMModSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getModFactor(); + void setModFactor(float mod_factor); + + float getToneFrequency(); + void setToneFrequency(float tone_frequency); + + float getVolumeFactor(); + void setVolumeFactor(float volume_factor); + + qint32 getChannelMute(); + void setChannelMute(qint32 channel_mute); + + qint32 getPlayLoop(); + void setPlayLoop(qint32 play_loop); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + + qint32 getModAfInput(); + void setModAfInput(qint32 mod_af_input); + + SWGCWKeyerSettings* getCwKeyer(); + void setCwKeyer(SWGCWKeyerSettings* cw_keyer); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float mod_factor; + bool m_mod_factor_isSet; + + float tone_frequency; + bool m_tone_frequency_isSet; + + float volume_factor; + bool m_volume_factor_isSet; + + qint32 channel_mute; + bool m_channel_mute_isSet; + + qint32 play_loop; + bool m_play_loop_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + QString* audio_device_name; + bool m_audio_device_name_isSet; + + qint32 mod_af_input; + bool m_mod_af_input_isSet; + + SWGCWKeyerSettings* cw_keyer; + bool m_cw_keyer_isSet; + +}; + +} + +#endif /* SWGAMModSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 075d1f785..52ceb47c7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -34,6 +34,8 @@ SWGChannelReport::SWGChannelReport() { m_tx_isSet = false; am_demod_report = nullptr; m_am_demod_report_isSet = false; + am_mod_report = nullptr; + m_am_mod_report_isSet = false; nfm_demod_report = nullptr; m_nfm_demod_report_isSet = false; nfm_mod_report = nullptr; @@ -52,6 +54,8 @@ SWGChannelReport::init() { m_tx_isSet = false; am_demod_report = new SWGAMDemodReport(); m_am_demod_report_isSet = false; + am_mod_report = new SWGAMModReport(); + m_am_mod_report_isSet = false; nfm_demod_report = new SWGNFMDemodReport(); m_nfm_demod_report_isSet = false; nfm_mod_report = new SWGNFMModReport(); @@ -67,6 +71,9 @@ SWGChannelReport::cleanup() { if(am_demod_report != nullptr) { delete am_demod_report; } + if(am_mod_report != nullptr) { + delete am_mod_report; + } if(nfm_demod_report != nullptr) { delete nfm_demod_report; } @@ -92,6 +99,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&am_demod_report, pJson["AMDemodReport"], "SWGAMDemodReport", "SWGAMDemodReport"); + ::SWGSDRangel::setValue(&am_mod_report, pJson["AMModReport"], "SWGAMModReport", "SWGAMModReport"); + ::SWGSDRangel::setValue(&nfm_demod_report, pJson["NFMDemodReport"], "SWGNFMDemodReport", "SWGNFMDemodReport"); ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); @@ -121,6 +130,9 @@ SWGChannelReport::asJsonObject() { if((am_demod_report != nullptr) && (am_demod_report->isSet())){ toJsonValue(QString("AMDemodReport"), am_demod_report, obj, QString("SWGAMDemodReport")); } + if((am_mod_report != nullptr) && (am_mod_report->isSet())){ + toJsonValue(QString("AMModReport"), am_mod_report, obj, QString("SWGAMModReport")); + } if((nfm_demod_report != nullptr) && (nfm_demod_report->isSet())){ toJsonValue(QString("NFMDemodReport"), nfm_demod_report, obj, QString("SWGNFMDemodReport")); } @@ -161,6 +173,16 @@ SWGChannelReport::setAmDemodReport(SWGAMDemodReport* am_demod_report) { this->m_am_demod_report_isSet = true; } +SWGAMModReport* +SWGChannelReport::getAmModReport() { + return am_mod_report; +} +void +SWGChannelReport::setAmModReport(SWGAMModReport* am_mod_report) { + this->am_mod_report = am_mod_report; + this->m_am_mod_report_isSet = true; +} + SWGNFMDemodReport* SWGChannelReport::getNfmDemodReport() { return nfm_demod_report; @@ -189,6 +211,7 @@ SWGChannelReport::isSet(){ if(channel_type != nullptr && *channel_type != QString("")){ isObjectUpdated = true; break;} if(m_tx_isSet){ isObjectUpdated = true; break;} if(am_demod_report != nullptr && am_demod_report->isSet()){ isObjectUpdated = true; break;} + if(am_mod_report != nullptr && am_mod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 7b6674ba6..7de0c17da 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -23,6 +23,7 @@ #include "SWGAMDemodReport.h" +#include "SWGAMModReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" #include @@ -54,6 +55,9 @@ public: SWGAMDemodReport* getAmDemodReport(); void setAmDemodReport(SWGAMDemodReport* am_demod_report); + SWGAMModReport* getAmModReport(); + void setAmModReport(SWGAMModReport* am_mod_report); + SWGNFMDemodReport* getNfmDemodReport(); void setNfmDemodReport(SWGNFMDemodReport* nfm_demod_report); @@ -73,6 +77,9 @@ private: SWGAMDemodReport* am_demod_report; bool m_am_demod_report_isSet; + SWGAMModReport* am_mod_report; + bool m_am_mod_report_isSet; + SWGNFMDemodReport* nfm_demod_report; bool m_nfm_demod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index de72f48a5..30a8fc16c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -34,6 +34,8 @@ SWGChannelSettings::SWGChannelSettings() { m_tx_isSet = false; am_demod_settings = nullptr; m_am_demod_settings_isSet = false; + am_mod_settings = nullptr; + m_am_mod_settings_isSet = false; nfm_demod_settings = nullptr; m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; @@ -52,6 +54,8 @@ SWGChannelSettings::init() { m_tx_isSet = false; am_demod_settings = new SWGAMDemodSettings(); m_am_demod_settings_isSet = false; + am_mod_settings = new SWGAMModSettings(); + m_am_mod_settings_isSet = false; nfm_demod_settings = new SWGNFMDemodSettings(); m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); @@ -67,6 +71,9 @@ SWGChannelSettings::cleanup() { if(am_demod_settings != nullptr) { delete am_demod_settings; } + if(am_mod_settings != nullptr) { + delete am_mod_settings; + } if(nfm_demod_settings != nullptr) { delete nfm_demod_settings; } @@ -92,6 +99,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&am_demod_settings, pJson["AMDemodSettings"], "SWGAMDemodSettings", "SWGAMDemodSettings"); + ::SWGSDRangel::setValue(&am_mod_settings, pJson["AMModSettings"], "SWGAMModSettings", "SWGAMModSettings"); + ::SWGSDRangel::setValue(&nfm_demod_settings, pJson["NFMDemodSettings"], "SWGNFMDemodSettings", "SWGNFMDemodSettings"); ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); @@ -121,6 +130,9 @@ SWGChannelSettings::asJsonObject() { if((am_demod_settings != nullptr) && (am_demod_settings->isSet())){ toJsonValue(QString("AMDemodSettings"), am_demod_settings, obj, QString("SWGAMDemodSettings")); } + if((am_mod_settings != nullptr) && (am_mod_settings->isSet())){ + toJsonValue(QString("AMModSettings"), am_mod_settings, obj, QString("SWGAMModSettings")); + } if((nfm_demod_settings != nullptr) && (nfm_demod_settings->isSet())){ toJsonValue(QString("NFMDemodSettings"), nfm_demod_settings, obj, QString("SWGNFMDemodSettings")); } @@ -161,6 +173,16 @@ SWGChannelSettings::setAmDemodSettings(SWGAMDemodSettings* am_demod_settings) { this->m_am_demod_settings_isSet = true; } +SWGAMModSettings* +SWGChannelSettings::getAmModSettings() { + return am_mod_settings; +} +void +SWGChannelSettings::setAmModSettings(SWGAMModSettings* am_mod_settings) { + this->am_mod_settings = am_mod_settings; + this->m_am_mod_settings_isSet = true; +} + SWGNFMDemodSettings* SWGChannelSettings::getNfmDemodSettings() { return nfm_demod_settings; @@ -189,6 +211,7 @@ SWGChannelSettings::isSet(){ if(channel_type != nullptr && *channel_type != QString("")){ isObjectUpdated = true; break;} if(m_tx_isSet){ isObjectUpdated = true; break;} if(am_demod_settings != nullptr && am_demod_settings->isSet()){ isObjectUpdated = true; break;} + if(am_mod_settings != nullptr && am_mod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index f9aed5ef9..74db5e22f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -23,6 +23,7 @@ #include "SWGAMDemodSettings.h" +#include "SWGAMModSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" #include @@ -54,6 +55,9 @@ public: SWGAMDemodSettings* getAmDemodSettings(); void setAmDemodSettings(SWGAMDemodSettings* am_demod_settings); + SWGAMModSettings* getAmModSettings(); + void setAmModSettings(SWGAMModSettings* am_mod_settings); + SWGNFMDemodSettings* getNfmDemodSettings(); void setNfmDemodSettings(SWGNFMDemodSettings* nfm_demod_settings); @@ -73,6 +77,9 @@ private: SWGAMDemodSettings* am_demod_settings; bool m_am_demod_settings_isSet; + SWGAMModSettings* am_mod_settings; + bool m_am_mod_settings_isSet; + SWGNFMDemodSettings* nfm_demod_settings; bool m_nfm_demod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 5232fc21f..9b5d420f8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -16,6 +16,8 @@ #include "SWGAMDemodReport.h" #include "SWGAMDemodSettings.h" +#include "SWGAMModReport.h" +#include "SWGAMModSettings.h" #include "SWGAirspyHFSettings.h" #include "SWGAudioDevices.h" #include "SWGAudioInputDevice.h" @@ -70,6 +72,12 @@ namespace SWGSDRangel { if(QString("SWGAMDemodSettings").compare(type) == 0) { return new SWGAMDemodSettings(); } + if(QString("SWGAMModReport").compare(type) == 0) { + return new SWGAMModReport(); + } + if(QString("SWGAMModSettings").compare(type) == 0) { + return new SWGAMModSettings(); + } if(QString("SWGAirspyHFSettings").compare(type) == 0) { return new SWGAirspyHFSettings(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp index 70c7f4aba..da9cbac1e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.cpp @@ -52,6 +52,8 @@ SWGNFMModSettings::SWGNFMModSettings() { m_rgb_color_isSet = false; title = nullptr; m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; mod_af_input = 0; m_mod_af_input_isSet = false; cw_keyer = nullptr; @@ -88,6 +90,8 @@ SWGNFMModSettings::init() { m_rgb_color_isSet = false; title = new QString(""); m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; mod_af_input = 0; m_mod_af_input_isSet = false; cw_keyer = new SWGCWKeyerSettings(); @@ -110,6 +114,9 @@ SWGNFMModSettings::cleanup() { if(title != nullptr) { delete title; } + if(audio_device_name != nullptr) { + delete audio_device_name; + } if(cw_keyer != nullptr) { delete cw_keyer; @@ -151,6 +158,8 @@ SWGNFMModSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + ::SWGSDRangel::setValue(&mod_af_input, pJson["modAFInput"], "qint32", ""); ::SWGSDRangel::setValue(&cw_keyer, pJson["cwKeyer"], "SWGCWKeyerSettings", "SWGCWKeyerSettings"); @@ -207,6 +216,9 @@ SWGNFMModSettings::asJsonObject() { if(title != nullptr && *title != QString("")){ toJsonValue(QString("title"), title, obj, QString("QString")); } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } if(m_mod_af_input_isSet){ obj->insert("modAFInput", QJsonValue(mod_af_input)); } @@ -337,6 +349,16 @@ SWGNFMModSettings::setTitle(QString* title) { this->m_title_isSet = true; } +QString* +SWGNFMModSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGNFMModSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + qint32 SWGNFMModSettings::getModAfInput() { return mod_af_input; @@ -374,6 +396,7 @@ SWGNFMModSettings::isSet(){ if(m_ctcss_index_isSet){ isObjectUpdated = true; break;} if(m_rgb_color_isSet){ isObjectUpdated = true; break;} if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} if(m_mod_af_input_isSet){ isObjectUpdated = true; break;} if(cw_keyer != nullptr && cw_keyer->isSet()){ isObjectUpdated = true; break;} }while(false); diff --git a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h index 7fbe6aea3..b31390067 100644 --- a/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGNFMModSettings.h @@ -79,6 +79,9 @@ public: QString* getTitle(); void setTitle(QString* title); + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + qint32 getModAfInput(); void setModAfInput(qint32 mod_af_input); @@ -125,6 +128,9 @@ private: QString* title; bool m_title_isSet; + QString* audio_device_name; + bool m_audio_device_name_isSet; + qint32 mod_af_input; bool m_mod_af_input_isSet; diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index f1894b58d..2f450e42a 100644 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -4,6 +4,7 @@ import requests, json, traceback, sys from optparse import OptionParser base_url = "http://127.0.0.1:8091/sdrangel" +deviceset_url = "" requests_methods = { "GET": requests.get, @@ -20,6 +21,7 @@ def getInputOptions(): parser.add_option("-a", "--address", dest="address", help="address and port", metavar="ADDRESS", type="string") parser.add_option("-d", "--device-index", dest="device_index", help="device set index", metavar="INDEX", type="int") parser.add_option("-D", "--device-hwid", dest="device_hwid", help="device hardware id", metavar="HWID", type="string") + parser.add_option("-C", "--channel-id", dest="channel_id", help="channel id", metavar="ID", type="string", default="NFMDemod") parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") parser.add_option("-f", "--channel-freq", dest="channel_freq", help="channel center frequency (Hz)", metavar="FREQ", type="int") parser.add_option("-s", "--sample-rate", dest="sample_rate", help="host to device sample rate (kS/s)", metavar="RATE", type="int") @@ -74,6 +76,98 @@ def callAPI(url, method, params, json, text): printResponse(r) return None +# ====================================================================== +def setupBladeRFXB200(fc): + if fc < 50000: + return 5 # BLADERF_XB200_AUTO_3DB + elif fc < 54000: + return 0 # BLADERF_XB200_50M + elif fc < 144000: + return 5 # BLADERF_XB200_AUTO_3DB + elif fc < 148000: + return 1 # BLADERF_XB200_144M + elif fc < 222000: + return 5 # BLADERF_XB200_AUTO_3DB + elif fc < 225000: + return 2 # BLADERF_XB200_222M + else: + return 5 # BLADERF_XB200_AUTO_3DB + +# ====================================================================== +def setupDevice(options): + settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") + if settings is None: + exit(-1) + + print(options.sample_rate) + + if options.device_hwid == "BladeRF": + settings['bladeRFOutputSettings']['centerFrequency'] = options.device_freq*1000 + settings['bladeRFOutputSettings']['devSampleRate'] = options.sample_rate*1000 + settings['bladeRFOutputSettings']['vga1'] = -20 + settings['bladeRFOutputSettings']['vga2'] = 6 + settings['bladeRFOutputSettings']['bandwidth'] = 1500*1000 + settings['bladeRFOutputSettings']['log2Interp'] = 4 + settings['bladeRFOutputSettings']['xb200'] = 1 # assume XB200 is mounted + settings['bladeRFOutputSettings']['xb200Path'] = 1 if options.device_freq < 300000 else 0 + settings['bladeRFOutputSettings']['xb200Filter'] = setupBladeRFXB200(options.device_freq) + elif options.device_hwid == "LimeSDR": + settings["limeSdrOutputSettings"]["antennaPath"] = options.antenna_path + settings["limeSdrOutputSettings"]["devSampleRate"] = options.sample_rate*1000 + settings["limeSdrOutputSettings"]["log2HardInterp"] = 4 + settings["limeSdrOutputSettings"]["log2SoftInterp"] = 4 + settings["limeSdrOutputSettings"]["centerFrequency"] = options.device_freq*1000 + 500000 + settings["limeSdrOutputSettings"]["ncoEnable"] = 1 + settings["limeSdrOutputSettings"]["ncoFrequency"] = -500000 + settings["limeSdrOutputSettings"]["lpfBW"] = 4050000 + settings["limeSdrOutputSettings"]["lpfFIRBW"] = 100000 + settings["limeSdrOutputSettings"]["lpfFIREnable"] = 1 + elif options.device_hwid == "HackRF": + settings['hackRFOutputSettings']['LOppmTenths'] = -51 + settings['hackRFOutputSettings']['centerFrequency'] = options.device_freq*1000 + settings['hackRFOutputSettings']['devSampleRate'] = options.sample_rate*1000 + settings['hackRFOutputSettings']['lnaExt'] = 0 + settings['hackRFOutputSettings']['log2Interp'] = 4 + settings['hackRFOutputSettings']['vgaGain'] = 24 + + r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") + if r is None: + exit(-1) + +# ====================================================================== +def setupChannel(options): + r = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": options.channel_id, "tx": 1}, "Create modulator") + if r is None: + exit(-1) + + settings = callAPI(deviceset_url + "/channel/0/settings", "GET", None, None, "Get modulator settings") + if settings is None: + exit(-1) + + if options.channel_id == "NFMMod": + settings["NFMModSettings"]["title"] = "Test NFM" + settings["NFMModSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["NFMModSettings"]["cwKeyer"]["text"] = "VVV DE F4EXB " + settings["NFMModSettings"]["cwKeyer"]["loop"] = 1 + settings["NFMModSettings"]["cwKeyer"]["mode"] = 1 # text + settings["NFMModSettings"]["modAFInput"] = 4 # CW text + settings["NFMModSettings"]["toneFrequency"] = 600 + elif options.channel_id == "AMMod": + settings["AMModSettings"]["title"] = "Test AM" + settings["AMModSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["AMModSettings"]["cwKeyer"]["text"] = "VVV DE F4EXB " + settings["AMModSettings"]["cwKeyer"]["loop"] = 1 + settings["AMModSettings"]["cwKeyer"]["mode"] = 1 # text + settings["AMModSettings"]["modAFInput"] = 4 # CW text + settings["AMModSettings"]["toneFrequency"] = 600 + settings["AMModSettings"]["modFactor"] = 0.9 + settings["AMModSettings"]["rfBandwidth"] = 7500 + + r = callAPI(deviceset_url + "/channel/0/settings", "PATCH", None, settings, "Change modulator") + if r is None: + exit(-1) + + # ====================================================================== def main(): try: @@ -86,61 +180,16 @@ def main(): r = callAPI("/deviceset", "POST", {"tx": 1}, None, "Add Tx device set") if r is None: exit(-1) - + + global deviceset_url deviceset_url = "/deviceset/%d" % options.device_index r = callAPI(deviceset_url + "/device", "PUT", None, {"hwType": "%s" % options.device_hwid, "tx": 1}, "setup device on Tx device set") if r is None: exit(-1) - settings = callAPI(deviceset_url + "/device/settings", "GET", None, None, "Get device settings") - if settings is None: - exit(-1) - - print(options.sample_rate) - - if options.device_hwid == "LimeSDR": - settings["limeSdrOutputSettings"]["antennaPath"] = options.antenna_path - settings["limeSdrOutputSettings"]["devSampleRate"] = options.sample_rate*1000 - settings["limeSdrOutputSettings"]["log2HardInterp"] = 4 - settings["limeSdrOutputSettings"]["log2SoftInterp"] = 4 - settings["limeSdrOutputSettings"]["centerFrequency"] = options.device_freq*1000 + 500000 - settings["limeSdrOutputSettings"]["ncoEnable"] = 1 - settings["limeSdrOutputSettings"]["ncoFrequency"] = -500000 - settings["limeSdrOutputSettings"]["lpfBW"] = 4050000 - settings["limeSdrOutputSettings"]["lpfFIRBW"] = 100000 - settings["limeSdrOutputSettings"]["lpfFIREnable"] = 1 - elif options.device_hwid == "HackRF": - settings['hackRFOutputSettings']['LOppmTenths'] = -51 - settings['hackRFOutputSettings']['centerFrequency'] = options.device_freq*1000 - settings['hackRFOutputSettings']['devSampleRate'] = options.sample_rate*1000 - settings['hackRFOutputSettings']['lnaExt'] = 0 - settings['hackRFOutputSettings']['log2Interp'] = 4 - settings['hackRFOutputSettings']['vgaGain'] = 24 - - r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") - if r is None: - exit(-1) - - r = callAPI(deviceset_url + "/channel", "POST", None, {"channelType": "NFMMod", "tx": 1}, "Create NFM mod") - if r is None: - exit(-1) - - settings = callAPI(deviceset_url + "/channel/0/settings", "GET", None, None, "Get NFM mod settings") - if settings is None: - exit(-1) - - settings["NFMModSettings"]["title"] = "Test NFM" - settings["NFMModSettings"]["inputFrequencyOffset"] = options.channel_freq - settings["NFMModSettings"]["cwKeyer"]["text"] = "VVV DE F4EXB " - settings["NFMModSettings"]["cwKeyer"]["loop"] = 1 - settings["NFMModSettings"]["cwKeyer"]["mode"] = 1 # text - settings["NFMModSettings"]["modAFInput"] = 4 # CW text - settings["NFMModSettings"]["toneFrequency"] = 600 - - r = callAPI(deviceset_url + "/channel/0/settings", "PATCH", None, settings, "Change NFM mod") - if r is None: - exit(-1) + setupDevice(options) + setupChannel(options) r = callAPI(deviceset_url + "/device/run", "POST", None, None, "Start running device") if r is None: From b2d153ed9e542d7f97b7901acc9c2fd95a81a51f Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 6 Apr 2018 01:04:31 +0200 Subject: [PATCH 253/956] Build ModAM tx channel server plugin. Bumped version to 3.14.2 --- app/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 8 ++++ plugins/channeltx/modam/ammodplugin.cpp | 2 +- .../bladerfoutput/bladerfoutputplugin.cpp | 2 +- .../samplesource/airspyhf/airspyhfplugin.cpp | 2 +- .../bladerfinput/bladerfinputplugin.cpp | 2 +- pluginssrv/channeltx/CMakeLists.txt | 1 + pluginssrv/channeltx/modam/CMakeLists.txt | 41 +++++++++++++++++++ 9 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 pluginssrv/channeltx/modam/CMakeLists.txt diff --git a/app/main.cpp b/app/main.cpp index 13f5a1121..26cf88c1e 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.14.1"); + QCoreApplication::setApplicationVersion("3.14.2"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 5b06ce273..7ead47de4 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.14.1"); + QCoreApplication::setApplicationVersion("3.14.2"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index d49c569fc..b969dce39 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +sdrangel (3.14.1-1) unstable; urgency=medium + + * Web API: settings and report for AM mod + * Server: AirspyHF, BladeRF, AM support + * PVS-Studio static analysis corrections (4) + + -- Edouard Griffiths, F4EXB Sun, 08 Apr 2018 12:14:18 +0200 + sdrangel (3.14.1-1) unstable; urgency=medium * NFM: fixed lowpass filter initialization (CTCSS) diff --git a/plugins/channeltx/modam/ammodplugin.cpp b/plugins/channeltx/modam/ammodplugin.cpp index e2f5d903f..4913395e7 100644 --- a/plugins/channeltx/modam/ammodplugin.cpp +++ b/plugins/channeltx/modam/ammodplugin.cpp @@ -24,7 +24,7 @@ const PluginDescriptor AMModPlugin::m_pluginDescriptor = { QString("AM Modulator"), - QString("3.12.0"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp index 9d1b2c6fe..d606f2e05 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp @@ -26,7 +26,7 @@ const PluginDescriptor BladerfOutputPlugin::m_pluginDescriptor = { QString("BladeRF Output"), - QString("3.14.1"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/airspyhf/airspyhfplugin.cpp b/plugins/samplesource/airspyhf/airspyhfplugin.cpp index cb81e2874..d2b77c418 100644 --- a/plugins/samplesource/airspyhf/airspyhfplugin.cpp +++ b/plugins/samplesource/airspyhf/airspyhfplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor AirspyHFPlugin::m_pluginDescriptor = { QString("AirspyHF Input"), - QString("3.14.0"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index 2cedb7e94..e8c48b3f2 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { QString("BladeRF Input"), - QString("3.14.1"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/pluginssrv/channeltx/CMakeLists.txt b/pluginssrv/channeltx/CMakeLists.txt index 2bccaebd3..c43165da7 100644 --- a/pluginssrv/channeltx/CMakeLists.txt +++ b/pluginssrv/channeltx/CMakeLists.txt @@ -1,3 +1,4 @@ project(mod) +add_subdirectory(modam) add_subdirectory(modnfm) diff --git a/pluginssrv/channeltx/modam/CMakeLists.txt b/pluginssrv/channeltx/modam/CMakeLists.txt new file mode 100644 index 000000000..c7c948b84 --- /dev/null +++ b/pluginssrv/channeltx/modam/CMakeLists.txt @@ -0,0 +1,41 @@ +project(modam) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/modam") + +set(modam_SOURCES + ${PLUGIN_PREFIX}/ammod.cpp + ${PLUGIN_PREFIX}/ammodplugin.cpp + ${PLUGIN_PREFIX}/ammodsettings.cpp +) + +set(modam_HEADERS + ${PLUGIN_PREFIX}/ammod.h + ${PLUGIN_PREFIX}/ammodplugin.h + ${PLUGIN_PREFIX}/ammodsettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modamsrv SHARED + ${modam_SOURCES} + ${modam_HEADERS_MOC} +) + +target_link_libraries(modamsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +qt5_use_modules(modamsrv Core Widgets) + +install(TARGETS modamsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file From b4d7a0a905660b64004e98df518802770c99c666 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 9 Apr 2018 00:37:17 +0200 Subject: [PATCH 254/956] WFM demod: Web API: settings and report implementation --- plugins/channeltx/modwfm/CMakeLists.txt | 2 + plugins/channeltx/modwfm/wfmmod.cpp | 203 +++++++++- plugins/channeltx/modwfm/wfmmod.h | 47 +-- plugins/channeltx/modwfm/wfmmodgui.cpp | 50 ++- plugins/channeltx/modwfm/wfmmodgui.h | 1 - plugins/channeltx/modwfm/wfmmodsettings.cpp | 9 + plugins/channeltx/modwfm/wfmmodsettings.h | 10 + sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 74 +++- .../webapi/doc/swagger/include/WFMMod.yaml | 48 +++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + sdrbase/webapi/webapirequestmapper.cpp | 35 ++ .../sdrangel/api/swagger/include/WFMMod.yaml | 48 +++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 74 +++- .../code/qt5/client/SWGChannelReport.cpp | 23 ++ .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 ++ .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../code/qt5/client/SWGWFMModReport.cpp | 148 +++++++ .../code/qt5/client/SWGWFMModReport.h | 70 ++++ .../code/qt5/client/SWGWFMModSettings.cpp | 364 ++++++++++++++++++ .../code/qt5/client/SWGWFMModSettings.h | 132 +++++++ swagger/sdrangel/examples/tx_test.py | 11 + 25 files changed, 1338 insertions(+), 65 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/WFMMod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/WFMMod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGWFMModReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h diff --git a/plugins/channeltx/modwfm/CMakeLists.txt b/plugins/channeltx/modwfm/CMakeLists.txt index 7f28858d4..e9d104f88 100644 --- a/plugins/channeltx/modwfm/CMakeLists.txt +++ b/plugins/channeltx/modwfm/CMakeLists.txt @@ -23,6 +23,7 @@ set(modwfm_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) add_definitions(${QT_DEFINITIONS}) @@ -41,6 +42,7 @@ target_link_libraries(modwfm ${QT_LIBRARIES} sdrbase sdrgui + swagger ) qt5_use_modules(modwfm Core Widgets) diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index 14dfcaf80..09b4c5074 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -22,11 +22,16 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGAMModReport.h" + #include "dsp/upchannelizer.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" +#include "util/db.h" #include "wfmmod.h" @@ -34,7 +39,6 @@ MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureWFMMod, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceName, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureAFInput, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceStreamTiming, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamTiming, Message) @@ -56,7 +60,6 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : m_fileSize(0), m_recordLength(0), m_sampleRate(48000), - m_afInput(WFMModInputNone), m_levelCalcCount(0), m_peakLevel(0.0f), m_levelSum(0.0f) @@ -119,7 +122,8 @@ void WFMMod::pull(Sample& sample) m_settingsMutex.lock(); - if ((m_afInput == WFMModInputFile) || (m_afInput == WFMModInputAudio)) + if ((m_settings.m_modAFInput == WFMModSettings::WFMModInputFile) + || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAudio)) { if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ri)) { @@ -178,13 +182,13 @@ void WFMMod::pullAudio(int nbSamples) void WFMMod::pullAF(Complex& sample) { - switch (m_afInput) + switch (m_settings.m_modAFInput) { - case WFMModInputTone: + case WFMModSettings::WFMModInputTone: sample.real(m_toneNcoRF.next() * m_settings.m_volumeFactor); sample.imag(0.0f); break; - case WFMModInputFile: + case WFMModSettings::WFMModInputFile: // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw if (m_ifstream.is_open()) @@ -217,13 +221,13 @@ void WFMMod::pullAF(Complex& sample) sample.imag(0.0f); } break; - case WFMModInputAudio: + case WFMModSettings::WFMModInputAudio: { sample.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor); sample.imag(0.0f); } break; - case WFMModInputCWTone: + case WFMModSettings::WFMModInputCWTone: Real fadeFactor; if (m_cwKeyer.getSample()) @@ -247,7 +251,7 @@ void WFMMod::pullAF(Complex& sample) } } break; - case WFMModInputNone: + case WFMModSettings::WFMModInputNone: default: sample.real(0.0f); sample.imag(0.0f); @@ -337,13 +341,6 @@ bool WFMMod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureAFInput::match(cmd)) - { - MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; - m_afInput = conf.getAFInput(); - - return true; - } else if (MsgConfigureFileSourceStreamTiming::match(cmd)) { std::size_t samplesCount; @@ -545,3 +542,177 @@ bool WFMMod::deserialize(const QByteArray& data) return false; } } + +int WFMMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setWfmModSettings(new SWGSDRangel::SWGWFMModSettings()); + response.getWfmModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int WFMMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + WFMModSettings settings; + bool channelizerChange = false; + + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getWfmModSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getWfmModSettings()->getInputFrequencyOffset(); + channelizerChange = true; + } + if (channelSettingsKeys.contains("modAFInput")) { + settings.m_modAFInput = (WFMModSettings::WFMModInputAF) response.getWfmModSettings()->getModAfInput(); + } + if (channelSettingsKeys.contains("playLoop")) { + settings.m_playLoop = response.getWfmModSettings()->getPlayLoop() != 0; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getWfmModSettings()->getRfBandwidth(); + channelizerChange = true; + } + if (channelSettingsKeys.contains("afBandwidth")) { + settings.m_afBandwidth = response.getWfmModSettings()->getAfBandwidth(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getWfmModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getWfmModSettings()->getTitle(); + } + if (channelSettingsKeys.contains("toneFrequency")) { + settings.m_toneFrequency = response.getWfmModSettings()->getToneFrequency(); + } + if (channelSettingsKeys.contains("volumeFactor")) { + settings.m_volumeFactor = response.getWfmModSettings()->getVolumeFactor(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getWfmModSettings()->getFmDeviation(); + } + + if (channelSettingsKeys.contains("cwKeyer")) + { + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getWfmModSettings()->getCwKeyer(); + CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings(); + + if (channelSettingsKeys.contains("cwKeyer.loop")) { + cwKeyerSettings.m_loop = apiCwKeyerSettings->getLoop() != 0; + } + if (channelSettingsKeys.contains("cwKeyer.mode")) { + cwKeyerSettings.m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode(); + } + if (channelSettingsKeys.contains("cwKeyer.text")) { + cwKeyerSettings.m_text = *apiCwKeyerSettings->getText(); + } + if (channelSettingsKeys.contains("cwKeyer.sampleRate")) { + cwKeyerSettings.m_sampleRate = apiCwKeyerSettings->getSampleRate(); + } + if (channelSettingsKeys.contains("cwKeyer.wpm")) { + cwKeyerSettings.m_wpm = apiCwKeyerSettings->getWpm(); + } + + m_cwKeyer.setLoop(cwKeyerSettings.m_loop); + m_cwKeyer.setMode(cwKeyerSettings.m_mode); + m_cwKeyer.setSampleRate(cwKeyerSettings.m_sampleRate); + m_cwKeyer.setText(cwKeyerSettings.m_text); + m_cwKeyer.setWPM(cwKeyerSettings.m_wpm); + + if (m_guiMessageQueue) // forward to GUI if any + { + CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force); + m_guiMessageQueue->push(msgCwKeyer); + } + } + + if (channelizerChange) + { + WFMMod::MsgConfigureChannelizer *msgChan = WFMMod::MsgConfigureChannelizer::create( + settings.m_rfBandwidth, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureWFMMod *msg = MsgConfigureWFMMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureWFMMod *msgToGUI = MsgConfigureWFMMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int WFMMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setWfmModReport(new SWGSDRangel::SWGWFMModReport()); + response.getWfmModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void WFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMModSettings& settings) +{ + response.getWfmModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getWfmModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getWfmModSettings()->setModAfInput((int) settings.m_modAFInput); + response.getWfmModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0); + response.getWfmModSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getWfmModSettings()->setAfBandwidth(settings.m_afBandwidth); + response.getWfmModSettings()->setFmDeviation(settings.m_fmDeviation); + response.getWfmModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getWfmModSettings()->getTitle()) { + *response.getWfmModSettings()->getTitle() = settings.m_title; + } else { + response.getWfmModSettings()->setTitle(new QString(settings.m_title)); + } + + response.getWfmModSettings()->setToneFrequency(settings.m_toneFrequency); + response.getWfmModSettings()->setVolumeFactor(settings.m_volumeFactor); + + if (!response.getWfmModSettings()->getCwKeyer()) { + response.getWfmModSettings()->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings); + } + + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getWfmModSettings()->getCwKeyer(); + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode((int) cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + + if (apiCwKeyerSettings->getText()) { + *apiCwKeyerSettings->getText() = cwKeyerSettings.m_text; + } else { + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + } + + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + + if (response.getWfmModSettings()->getAudioDeviceName()) { + *response.getWfmModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getWfmModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void WFMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getWfmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getWfmModReport()->setAudioSampleRate(m_audioSampleRate); + response.getWfmModReport()->setChannelSampleRate(m_outputSampleRate); +} + diff --git a/plugins/channeltx/modwfm/wfmmod.h b/plugins/channeltx/modwfm/wfmmod.h index d47608181..b5393921e 100644 --- a/plugins/channeltx/modwfm/wfmmod.h +++ b/plugins/channeltx/modwfm/wfmmod.h @@ -44,15 +44,6 @@ class WFMMod : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: - typedef enum - { - WFMModInputNone, - WFMModInputTone, - WFMModInputFile, - WFMModInputAudio, - WFMModInputCWTone - } WFMModInputAF; - class MsgConfigureWFMMod : public Message { MESSAGE_CLASS_DECLARATION @@ -158,27 +149,6 @@ public: { } }; - class MsgConfigureAFInput : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - WFMModInputAF getAFInput() const { return m_afInput; } - - static MsgConfigureAFInput* create(WFMModInputAF afInput) - { - return new MsgConfigureAFInput(afInput); - } - - private: - WFMModInputAF m_afInput; - - MsgConfigureAFInput(WFMModInputAF afInput) : - Message(), - m_afInput(afInput) - { } - }; - class MsgReportFileSourceStreamTiming : public Message { MESSAGE_CLASS_DECLARATION @@ -246,6 +216,20 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } CWKeyer *getCWKeyer() { return &m_cwKeyer; } @@ -309,7 +293,6 @@ private: quint32 m_recordLength; //!< record length in seconds computed from file size int m_sampleRate; - WFMModInputAF m_afInput; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; @@ -323,6 +306,8 @@ private: void calculateLevel(const Real& sample); void openFileStream(); void seekFileStream(int seekPercentage); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; diff --git a/plugins/channeltx/modwfm/wfmmodgui.cpp b/plugins/channeltx/modwfm/wfmmodgui.cpp index c9a11c1c9..1134abbc2 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.cpp +++ b/plugins/channeltx/modwfm/wfmmodgui.cpp @@ -106,6 +106,21 @@ bool WFMModGUI::handleMessage(const Message& message) updateWithStreamTime(); return true; } + else if (WFMMod::MsgConfigureWFMMod::match(message)) + { + const WFMMod::MsgConfigureWFMMod& cfg = (WFMMod::MsgConfigureWFMMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (CWKeyer::MsgConfigureCWKeyer::match(message)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) message; + ui->cwKeyerGUI->displaySettings(cfg.getSettings()); + return true; + } else { return false; @@ -192,9 +207,8 @@ void WFMModGUI::on_play_toggled(bool checked) ui->tone->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); ui->morseKeyer->setEnabled(!checked); - m_modAFInput = checked ? WFMMod::WFMModInputFile : WFMMod::WFMModInputNone; - WFMMod::MsgConfigureAFInput* message = WFMMod::MsgConfigureAFInput::create(m_modAFInput); - m_wfmMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? WFMModSettings::WFMModInputFile : WFMModSettings::WFMModInputNone; + applySettings(); ui->navTimeSlider->setEnabled(!checked); m_enableNavTime = !checked; } @@ -204,9 +218,8 @@ void WFMModGUI::on_tone_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); ui->morseKeyer->setEnabled(!checked); - m_modAFInput = checked ? WFMMod::WFMModInputTone : WFMMod::WFMModInputNone; - WFMMod::MsgConfigureAFInput* message = WFMMod::MsgConfigureAFInput::create(m_modAFInput); - m_wfmMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? WFMModSettings::WFMModInputTone : WFMModSettings::WFMModInputNone; + applySettings(); } void WFMModGUI::on_morseKeyer_toggled(bool checked) @@ -214,9 +227,8 @@ void WFMModGUI::on_morseKeyer_toggled(bool checked) ui->tone->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); ui->play->setEnabled(!checked); - m_modAFInput = checked ? WFMMod::WFMModInputCWTone : WFMMod::WFMModInputNone; - WFMMod::MsgConfigureAFInput* message = WFMMod::MsgConfigureAFInput::create(m_modAFInput); - m_wfmMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? WFMModSettings::WFMModInputCWTone : WFMModSettings::WFMModInputNone; + applySettings(); } void WFMModGUI::on_mic_toggled(bool checked) @@ -224,9 +236,8 @@ void WFMModGUI::on_mic_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->tone->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); - m_modAFInput = checked ? WFMMod::WFMModInputAudio : WFMMod::WFMModInputNone; - WFMMod::MsgConfigureAFInput* message = WFMMod::MsgConfigureAFInput::create(m_modAFInput); - m_wfmMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? WFMModSettings::WFMModInputAudio : WFMModSettings::WFMModInputNone; + applySettings(); } void WFMModGUI::on_navTimeSlider_valueChanged(int value) @@ -278,8 +289,7 @@ WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_recordSampleRate(48000), m_samplesCount(0), m_tickCount(0), - m_enableNavTime(false), - m_modAFInput(WFMMod::WFMModInputNone) + m_enableNavTime(false) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); @@ -401,6 +411,16 @@ void WFMModGUI::displaySettings() ui->channelMute->setChecked(m_settings.m_channelMute); ui->playLoop->setChecked(m_settings.m_playLoop); + ui->tone->setEnabled((m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputTone) || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputNone)); + ui->mic->setEnabled((m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputAudio) || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputNone)); + ui->play->setEnabled((m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputFile) || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputNone)); + ui->morseKeyer->setEnabled((m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputCWTone) || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputNone)); + + ui->tone->setChecked(m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputTone); + ui->mic->setChecked(m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputAudio); + ui->play->setChecked(m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputFile); + ui->morseKeyer->setChecked(m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputCWTone); + blockApplySettings(false); } @@ -433,7 +453,7 @@ void WFMModGUI::tick() m_channelPowerDbAvg(powDb); ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1)); - if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == WFMMod::WFMModInputFile)) + if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == WFMModSettings::WFMModInputFile)) { WFMMod::MsgConfigureFileSourceStreamTiming* message = WFMMod::MsgConfigureFileSourceStreamTiming::create(); m_wfmMod->getInputMessageQueue()->push(message); diff --git a/plugins/channeltx/modwfm/wfmmodgui.h b/plugins/channeltx/modwfm/wfmmodgui.h index 1dc64de3c..0c10e2a6d 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.h +++ b/plugins/channeltx/modwfm/wfmmodgui.h @@ -76,7 +76,6 @@ private: int m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; - WFMMod::WFMModInputAF m_modAFInput; MessageQueue m_inputMessageQueue; explicit WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); diff --git a/plugins/channeltx/modwfm/wfmmodsettings.cpp b/plugins/channeltx/modwfm/wfmmodsettings.cpp index b4d596c80..6f9f06873 100644 --- a/plugins/channeltx/modwfm/wfmmodsettings.cpp +++ b/plugins/channeltx/modwfm/wfmmodsettings.cpp @@ -47,6 +47,7 @@ void WFMModSettings::resetToDefaults() m_playLoop = false; m_rgbColor = QColor(0, 0, 255).rgb(); m_title = "WFM Modulator"; + m_modAFInput = WFMModInputNone; m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } @@ -72,6 +73,7 @@ QByteArray WFMModSettings::serialize() const s.writeString(10, m_title); s.writeString(11, m_audioDeviceName); + s.writeS32(12, (int) m_modAFInput); return s.final(); } @@ -113,6 +115,13 @@ bool WFMModSettings::deserialize(const QByteArray& data) d.readString(10, &m_title, "WFM Modulator"); d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + d.readS32(12, &tmp, 0); + if ((tmp < 0) || (tmp > (int) WFMModInputAF::WFMModInputTone)) { + m_modAFInput = WFMModInputNone; + } else { + m_modAFInput = (WFMModInputAF) tmp; + } + return true; } else diff --git a/plugins/channeltx/modwfm/wfmmodsettings.h b/plugins/channeltx/modwfm/wfmmodsettings.h index 2b1cff3fe..d511e69db 100644 --- a/plugins/channeltx/modwfm/wfmmodsettings.h +++ b/plugins/channeltx/modwfm/wfmmodsettings.h @@ -23,6 +23,15 @@ class Serializable; struct WFMModSettings { + typedef enum + { + WFMModInputNone, + WFMModInputTone, + WFMModInputFile, + WFMModInputAudio, + WFMModInputCWTone + } WFMModInputAF; + static const int m_nbRfBW; static const int m_rfBW[]; @@ -36,6 +45,7 @@ struct WFMModSettings bool m_playLoop; quint32 m_rgbColor; QString m_title; + WFMModInputAF m_modAFInput; QString m_audioDeviceName; Serializable *m_channelMarker; diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index c41a268cd..c1b051e5e 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -12,6 +12,7 @@ webapi/doc/swagger/include/AMMod.yaml webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml + webapi/doc/swagger/include/WFMMod.yaml webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger-ui/swagger-ui.js.map webapi/doc/swagger-ui/swagger-ui.js diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 6bbaa05df..0ea7df856 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1129,6 +1129,9 @@ margin-bottom: 20px; }, "NFMModReport" : { "$ref" : "#/definitions/NFMModReport" + }, + "WFMModReport" : { + "$ref" : "#/definitions/WFMModReport" } }, "description" : "Base channel report. The specific channel report present depends on channelType or paremt context." @@ -1156,6 +1159,9 @@ margin-bottom: 20px; }, "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" + }, + "WFMModSettings" : { + "$ref" : "#/definitions/WFMModSettings" } }, "description" : "Base channel settings. The specific channel settings present depends on channelType." @@ -2028,6 +2034,72 @@ margin-bottom: 20px; "type" : "string" } } +}; + defs.WFMModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "WFMMod" +}; + defs.WFMModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "afBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "number", + "format" : "float" + }, + "toneFrequency" : { + "type" : "number", + "format" : "float" + }, + "volumeFactor" : { + "type" : "number", + "format" : "float" + }, + "channelMute" : { + "type" : "integer" + }, + "playLoop" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "modAFInput" : { + "type" : "integer" + }, + "cwKeyer" : { + "$ref" : "#/definitions/CWKeyerSettings" + } + }, + "description" : "WFMMod" }; @@ -20262,7 +20334,7 @@ except ApiException as e:
    - Generated 2018-04-06T00:10:47.664+02:00 + Generated 2018-04-08T18:14:01.611+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/WFMMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/WFMMod.yaml new file mode 100644 index 000000000..8fb9b6fc7 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/WFMMod.yaml @@ -0,0 +1,48 @@ +WFMModSettings: + description: WFMMod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + afBandwidth: + type: number + format: float + fmDeviation: + type: number + format: float + toneFrequency: + type: number + format: float + volumeFactor: + type: number + format: float + channelMute: + type: integer + playLoop: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + modAFInput: + type: integer + cwKeyer: + $ref: "/doc/swagger/include/CWKeyer.yaml#/CWKeyerSettings" + +WFMModReport: + description: WFMMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 9d072491c..b8e8deeae 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1753,6 +1753,8 @@ definitions: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" + WFMModSettings: + $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModSettings" ChannelReport: description: Base channel report. The specific channel report present depends on channelType or paremt context. @@ -1772,6 +1774,8 @@ definitions: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModReport" + WFMModReport: + $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModReport" responses: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index ecb8a5579..be9e4c99d 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1896,6 +1896,27 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "WFMMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject wfmModSettingsJsonObject = jsonObject["WFMModSettings"].toObject(); + channelSettingsKeys = wfmModSettingsJsonObject.keys(); + + if (channelSettingsKeys.contains("cwKeyer")) + { + QJsonObject cwKeyerSettingsJsonObject; + appendSettingsSubKeys(wfmModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); + } + + channelSettings.setWfmModSettings(new SWGSDRangel::SWGWFMModSettings()); + channelSettings.getWfmModSettings()->fromJsonObject(wfmModSettingsJsonObject); + return true; + } + else { + return false; + } + } else { return false; @@ -1977,6 +1998,20 @@ bool WebAPIRequestMapper::validateChannelReport( return false; } } + else if (*channelType == "WFMMod") + { + if (channelReport.getTx() != 0) + { + QJsonObject wfmModReportJsonObject = jsonObject["WFMModReport"].toObject(); + channelReportKeys = wfmModReportJsonObject.keys(); + channelReport.setWfmModReport(new SWGSDRangel::SWGWFMModReport()); + channelReport.getWfmModReport()->fromJsonObject(wfmModReportJsonObject); + return true; + } + else { + return false; + } + } else { return false; diff --git a/swagger/sdrangel/api/swagger/include/WFMMod.yaml b/swagger/sdrangel/api/swagger/include/WFMMod.yaml new file mode 100644 index 000000000..35d1e389d --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/WFMMod.yaml @@ -0,0 +1,48 @@ +WFMModSettings: + description: WFMMod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + afBandwidth: + type: number + format: float + fmDeviation: + type: number + format: float + toneFrequency: + type: number + format: float + volumeFactor: + type: number + format: float + channelMute: + type: integer + playLoop: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + modAFInput: + type: integer + cwKeyer: + $ref: "http://localhost:8081/api/swagger/include/CWKeyer.yaml#/CWKeyerSettings" + +WFMModReport: + description: WFMMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 944aef1a5..5d9861671 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1753,6 +1753,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" + WFMModSettings: + $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModSettings" ChannelReport: description: Base channel report. The specific channel report present depends on channelType or paremt context. @@ -1772,6 +1774,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModReport" + WFMModReport: + $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModReport" responses: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 6bbaa05df..0ea7df856 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1129,6 +1129,9 @@ margin-bottom: 20px; }, "NFMModReport" : { "$ref" : "#/definitions/NFMModReport" + }, + "WFMModReport" : { + "$ref" : "#/definitions/WFMModReport" } }, "description" : "Base channel report. The specific channel report present depends on channelType or paremt context." @@ -1156,6 +1159,9 @@ margin-bottom: 20px; }, "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" + }, + "WFMModSettings" : { + "$ref" : "#/definitions/WFMModSettings" } }, "description" : "Base channel settings. The specific channel settings present depends on channelType." @@ -2028,6 +2034,72 @@ margin-bottom: 20px; "type" : "string" } } +}; + defs.WFMModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "WFMMod" +}; + defs.WFMModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "afBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "number", + "format" : "float" + }, + "toneFrequency" : { + "type" : "number", + "format" : "float" + }, + "volumeFactor" : { + "type" : "number", + "format" : "float" + }, + "channelMute" : { + "type" : "integer" + }, + "playLoop" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "modAFInput" : { + "type" : "integer" + }, + "cwKeyer" : { + "$ref" : "#/definitions/CWKeyerSettings" + } + }, + "description" : "WFMMod" }; @@ -20262,7 +20334,7 @@ except ApiException as e:
    - Generated 2018-04-06T00:10:47.664+02:00 + Generated 2018-04-08T18:14:01.611+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 52ceb47c7..bbb3a9267 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -40,6 +40,8 @@ SWGChannelReport::SWGChannelReport() { m_nfm_demod_report_isSet = false; nfm_mod_report = nullptr; m_nfm_mod_report_isSet = false; + wfm_mod_report = nullptr; + m_wfm_mod_report_isSet = false; } SWGChannelReport::~SWGChannelReport() { @@ -60,6 +62,8 @@ SWGChannelReport::init() { m_nfm_demod_report_isSet = false; nfm_mod_report = new SWGNFMModReport(); m_nfm_mod_report_isSet = false; + wfm_mod_report = new SWGWFMModReport(); + m_wfm_mod_report_isSet = false; } void @@ -80,6 +84,9 @@ SWGChannelReport::cleanup() { if(nfm_mod_report != nullptr) { delete nfm_mod_report; } + if(wfm_mod_report != nullptr) { + delete wfm_mod_report; + } } SWGChannelReport* @@ -105,6 +112,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); + ::SWGSDRangel::setValue(&wfm_mod_report, pJson["WFMModReport"], "SWGWFMModReport", "SWGWFMModReport"); + } QString @@ -139,6 +148,9 @@ SWGChannelReport::asJsonObject() { if((nfm_mod_report != nullptr) && (nfm_mod_report->isSet())){ toJsonValue(QString("NFMModReport"), nfm_mod_report, obj, QString("SWGNFMModReport")); } + if((wfm_mod_report != nullptr) && (wfm_mod_report->isSet())){ + toJsonValue(QString("WFMModReport"), wfm_mod_report, obj, QString("SWGWFMModReport")); + } return obj; } @@ -203,6 +215,16 @@ SWGChannelReport::setNfmModReport(SWGNFMModReport* nfm_mod_report) { this->m_nfm_mod_report_isSet = true; } +SWGWFMModReport* +SWGChannelReport::getWfmModReport() { + return wfm_mod_report; +} +void +SWGChannelReport::setWfmModReport(SWGWFMModReport* wfm_mod_report) { + this->wfm_mod_report = wfm_mod_report; + this->m_wfm_mod_report_isSet = true; +} + bool SWGChannelReport::isSet(){ @@ -214,6 +236,7 @@ SWGChannelReport::isSet(){ if(am_mod_report != nullptr && am_mod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} + if(wfm_mod_report != nullptr && wfm_mod_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 7de0c17da..8a0810cac 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -26,6 +26,7 @@ #include "SWGAMModReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" +#include "SWGWFMModReport.h" #include #include "SWGObject.h" @@ -64,6 +65,9 @@ public: SWGNFMModReport* getNfmModReport(); void setNfmModReport(SWGNFMModReport* nfm_mod_report); + SWGWFMModReport* getWfmModReport(); + void setWfmModReport(SWGWFMModReport* wfm_mod_report); + virtual bool isSet() override; @@ -86,6 +90,9 @@ private: SWGNFMModReport* nfm_mod_report; bool m_nfm_mod_report_isSet; + SWGWFMModReport* wfm_mod_report; + bool m_wfm_mod_report_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 30a8fc16c..99a2cbf75 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -40,6 +40,8 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; m_nfm_mod_settings_isSet = false; + wfm_mod_settings = nullptr; + m_wfm_mod_settings_isSet = false; } SWGChannelSettings::~SWGChannelSettings() { @@ -60,6 +62,8 @@ SWGChannelSettings::init() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); m_nfm_mod_settings_isSet = false; + wfm_mod_settings = new SWGWFMModSettings(); + m_wfm_mod_settings_isSet = false; } void @@ -80,6 +84,9 @@ SWGChannelSettings::cleanup() { if(nfm_mod_settings != nullptr) { delete nfm_mod_settings; } + if(wfm_mod_settings != nullptr) { + delete wfm_mod_settings; + } } SWGChannelSettings* @@ -105,6 +112,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); + ::SWGSDRangel::setValue(&wfm_mod_settings, pJson["WFMModSettings"], "SWGWFMModSettings", "SWGWFMModSettings"); + } QString @@ -139,6 +148,9 @@ SWGChannelSettings::asJsonObject() { if((nfm_mod_settings != nullptr) && (nfm_mod_settings->isSet())){ toJsonValue(QString("NFMModSettings"), nfm_mod_settings, obj, QString("SWGNFMModSettings")); } + if((wfm_mod_settings != nullptr) && (wfm_mod_settings->isSet())){ + toJsonValue(QString("WFMModSettings"), wfm_mod_settings, obj, QString("SWGWFMModSettings")); + } return obj; } @@ -203,6 +215,16 @@ SWGChannelSettings::setNfmModSettings(SWGNFMModSettings* nfm_mod_settings) { this->m_nfm_mod_settings_isSet = true; } +SWGWFMModSettings* +SWGChannelSettings::getWfmModSettings() { + return wfm_mod_settings; +} +void +SWGChannelSettings::setWfmModSettings(SWGWFMModSettings* wfm_mod_settings) { + this->wfm_mod_settings = wfm_mod_settings; + this->m_wfm_mod_settings_isSet = true; +} + bool SWGChannelSettings::isSet(){ @@ -214,6 +236,7 @@ SWGChannelSettings::isSet(){ if(am_mod_settings != nullptr && am_mod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} + if(wfm_mod_settings != nullptr && wfm_mod_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 74db5e22f..153703644 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -26,6 +26,7 @@ #include "SWGAMModSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" +#include "SWGWFMModSettings.h" #include #include "SWGObject.h" @@ -64,6 +65,9 @@ public: SWGNFMModSettings* getNfmModSettings(); void setNfmModSettings(SWGNFMModSettings* nfm_mod_settings); + SWGWFMModSettings* getWfmModSettings(); + void setWfmModSettings(SWGWFMModSettings* wfm_mod_settings); + virtual bool isSet() override; @@ -86,6 +90,9 @@ private: SWGNFMModSettings* nfm_mod_settings; bool m_nfm_mod_settings_isSet; + SWGWFMModSettings* wfm_mod_settings; + bool m_wfm_mod_settings_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 9b5d420f8..9c19b6295 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -62,6 +62,8 @@ #include "SWGRtlSdrSettings.h" #include "SWGSamplingDevice.h" #include "SWGSuccessResponse.h" +#include "SWGWFMModReport.h" +#include "SWGWFMModSettings.h" namespace SWGSDRangel { @@ -210,6 +212,12 @@ namespace SWGSDRangel { if(QString("SWGSuccessResponse").compare(type) == 0) { return new SWGSuccessResponse(); } + if(QString("SWGWFMModReport").compare(type) == 0) { + return new SWGWFMModReport(); + } + if(QString("SWGWFMModSettings").compare(type) == 0) { + return new SWGWFMModSettings(); + } return nullptr; } diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp new file mode 100644 index 000000000..2b7a7d63a --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.cpp @@ -0,0 +1,148 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGWFMModReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGWFMModReport::SWGWFMModReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGWFMModReport::SWGWFMModReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGWFMModReport::~SWGWFMModReport() { + this->cleanup(); +} + +void +SWGWFMModReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGWFMModReport::cleanup() { + + + +} + +SWGWFMModReport* +SWGWFMModReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGWFMModReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGWFMModReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGWFMModReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_audio_sample_rate_isSet){ + obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGWFMModReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGWFMModReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGWFMModReport::getAudioSampleRate() { + return audio_sample_rate; +} +void +SWGWFMModReport::setAudioSampleRate(qint32 audio_sample_rate) { + this->audio_sample_rate = audio_sample_rate; + this->m_audio_sample_rate_isSet = true; +} + +qint32 +SWGWFMModReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGWFMModReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGWFMModReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h new file mode 100644 index 000000000..185320ee5 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModReport.h @@ -0,0 +1,70 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGWFMModReport.h + * + * WFMMod + */ + +#ifndef SWGWFMModReport_H_ +#define SWGWFMModReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGWFMModReport: public SWGObject { +public: + SWGWFMModReport(); + SWGWFMModReport(QString* json); + virtual ~SWGWFMModReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGWFMModReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getAudioSampleRate(); + void setAudioSampleRate(qint32 audio_sample_rate); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 audio_sample_rate; + bool m_audio_sample_rate_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGWFMModReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp new file mode 100644 index 000000000..9969a7907 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.cpp @@ -0,0 +1,364 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGWFMModSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGWFMModSettings::SWGWFMModSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGWFMModSettings::SWGWFMModSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + af_bandwidth = 0.0f; + m_af_bandwidth_isSet = false; + fm_deviation = 0.0f; + m_fm_deviation_isSet = false; + tone_frequency = 0.0f; + m_tone_frequency_isSet = false; + volume_factor = 0.0f; + m_volume_factor_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + play_loop = 0; + m_play_loop_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; + mod_af_input = 0; + m_mod_af_input_isSet = false; + cw_keyer = nullptr; + m_cw_keyer_isSet = false; +} + +SWGWFMModSettings::~SWGWFMModSettings() { + this->cleanup(); +} + +void +SWGWFMModSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + af_bandwidth = 0.0f; + m_af_bandwidth_isSet = false; + fm_deviation = 0.0f; + m_fm_deviation_isSet = false; + tone_frequency = 0.0f; + m_tone_frequency_isSet = false; + volume_factor = 0.0f; + m_volume_factor_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + play_loop = 0; + m_play_loop_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; + mod_af_input = 0; + m_mod_af_input_isSet = false; + cw_keyer = new SWGCWKeyerSettings(); + m_cw_keyer_isSet = false; +} + +void +SWGWFMModSettings::cleanup() { + + + + + + + + + + if(title != nullptr) { + delete title; + } + if(audio_device_name != nullptr) { + delete audio_device_name; + } + + if(cw_keyer != nullptr) { + delete cw_keyer; + } +} + +SWGWFMModSettings* +SWGWFMModSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGWFMModSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&af_bandwidth, pJson["afBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&fm_deviation, pJson["fmDeviation"], "float", ""); + + ::SWGSDRangel::setValue(&tone_frequency, pJson["toneFrequency"], "float", ""); + + ::SWGSDRangel::setValue(&volume_factor, pJson["volumeFactor"], "float", ""); + + ::SWGSDRangel::setValue(&channel_mute, pJson["channelMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&play_loop, pJson["playLoop"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&mod_af_input, pJson["modAFInput"], "qint32", ""); + + ::SWGSDRangel::setValue(&cw_keyer, pJson["cwKeyer"], "SWGCWKeyerSettings", "SWGCWKeyerSettings"); + +} + +QString +SWGWFMModSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGWFMModSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_af_bandwidth_isSet){ + obj->insert("afBandwidth", QJsonValue(af_bandwidth)); + } + if(m_fm_deviation_isSet){ + obj->insert("fmDeviation", QJsonValue(fm_deviation)); + } + if(m_tone_frequency_isSet){ + obj->insert("toneFrequency", QJsonValue(tone_frequency)); + } + if(m_volume_factor_isSet){ + obj->insert("volumeFactor", QJsonValue(volume_factor)); + } + if(m_channel_mute_isSet){ + obj->insert("channelMute", QJsonValue(channel_mute)); + } + if(m_play_loop_isSet){ + obj->insert("playLoop", QJsonValue(play_loop)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } + if(m_mod_af_input_isSet){ + obj->insert("modAFInput", QJsonValue(mod_af_input)); + } + if((cw_keyer != nullptr) && (cw_keyer->isSet())){ + toJsonValue(QString("cwKeyer"), cw_keyer, obj, QString("SWGCWKeyerSettings")); + } + + return obj; +} + +qint64 +SWGWFMModSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGWFMModSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGWFMModSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGWFMModSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGWFMModSettings::getAfBandwidth() { + return af_bandwidth; +} +void +SWGWFMModSettings::setAfBandwidth(float af_bandwidth) { + this->af_bandwidth = af_bandwidth; + this->m_af_bandwidth_isSet = true; +} + +float +SWGWFMModSettings::getFmDeviation() { + return fm_deviation; +} +void +SWGWFMModSettings::setFmDeviation(float fm_deviation) { + this->fm_deviation = fm_deviation; + this->m_fm_deviation_isSet = true; +} + +float +SWGWFMModSettings::getToneFrequency() { + return tone_frequency; +} +void +SWGWFMModSettings::setToneFrequency(float tone_frequency) { + this->tone_frequency = tone_frequency; + this->m_tone_frequency_isSet = true; +} + +float +SWGWFMModSettings::getVolumeFactor() { + return volume_factor; +} +void +SWGWFMModSettings::setVolumeFactor(float volume_factor) { + this->volume_factor = volume_factor; + this->m_volume_factor_isSet = true; +} + +qint32 +SWGWFMModSettings::getChannelMute() { + return channel_mute; +} +void +SWGWFMModSettings::setChannelMute(qint32 channel_mute) { + this->channel_mute = channel_mute; + this->m_channel_mute_isSet = true; +} + +qint32 +SWGWFMModSettings::getPlayLoop() { + return play_loop; +} +void +SWGWFMModSettings::setPlayLoop(qint32 play_loop) { + this->play_loop = play_loop; + this->m_play_loop_isSet = true; +} + +qint32 +SWGWFMModSettings::getRgbColor() { + return rgb_color; +} +void +SWGWFMModSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGWFMModSettings::getTitle() { + return title; +} +void +SWGWFMModSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +QString* +SWGWFMModSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGWFMModSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + +qint32 +SWGWFMModSettings::getModAfInput() { + return mod_af_input; +} +void +SWGWFMModSettings::setModAfInput(qint32 mod_af_input) { + this->mod_af_input = mod_af_input; + this->m_mod_af_input_isSet = true; +} + +SWGCWKeyerSettings* +SWGWFMModSettings::getCwKeyer() { + return cw_keyer; +} +void +SWGWFMModSettings::setCwKeyer(SWGCWKeyerSettings* cw_keyer) { + this->cw_keyer = cw_keyer; + this->m_cw_keyer_isSet = true; +} + + +bool +SWGWFMModSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_af_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_fm_deviation_isSet){ isObjectUpdated = true; break;} + if(m_tone_frequency_isSet){ isObjectUpdated = true; break;} + if(m_volume_factor_isSet){ isObjectUpdated = true; break;} + if(m_channel_mute_isSet){ isObjectUpdated = true; break;} + if(m_play_loop_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} + if(m_mod_af_input_isSet){ isObjectUpdated = true; break;} + if(cw_keyer != nullptr && cw_keyer->isSet()){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h new file mode 100644 index 000000000..162c02983 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGWFMModSettings.h @@ -0,0 +1,132 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGWFMModSettings.h + * + * WFMMod + */ + +#ifndef SWGWFMModSettings_H_ +#define SWGWFMModSettings_H_ + +#include + + +#include "SWGCWKeyerSettings.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGWFMModSettings: public SWGObject { +public: + SWGWFMModSettings(); + SWGWFMModSettings(QString* json); + virtual ~SWGWFMModSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGWFMModSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getAfBandwidth(); + void setAfBandwidth(float af_bandwidth); + + float getFmDeviation(); + void setFmDeviation(float fm_deviation); + + float getToneFrequency(); + void setToneFrequency(float tone_frequency); + + float getVolumeFactor(); + void setVolumeFactor(float volume_factor); + + qint32 getChannelMute(); + void setChannelMute(qint32 channel_mute); + + qint32 getPlayLoop(); + void setPlayLoop(qint32 play_loop); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + + qint32 getModAfInput(); + void setModAfInput(qint32 mod_af_input); + + SWGCWKeyerSettings* getCwKeyer(); + void setCwKeyer(SWGCWKeyerSettings* cw_keyer); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float af_bandwidth; + bool m_af_bandwidth_isSet; + + float fm_deviation; + bool m_fm_deviation_isSet; + + float tone_frequency; + bool m_tone_frequency_isSet; + + float volume_factor; + bool m_volume_factor_isSet; + + qint32 channel_mute; + bool m_channel_mute_isSet; + + qint32 play_loop; + bool m_play_loop_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + QString* audio_device_name; + bool m_audio_device_name_isSet; + + qint32 mod_af_input; + bool m_mod_af_input_isSet; + + SWGCWKeyerSettings* cw_keyer; + bool m_cw_keyer_isSet; + +}; + +} + +#endif /* SWGWFMModSettings_H_ */ diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index 2f450e42a..56ea228b2 100644 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -162,6 +162,17 @@ def setupChannel(options): settings["AMModSettings"]["toneFrequency"] = 600 settings["AMModSettings"]["modFactor"] = 0.9 settings["AMModSettings"]["rfBandwidth"] = 7500 + elif options.channel_id == "WFMMod": + settings["WFMModSettings"]["title"] = "Test WFM" + settings["WFMModSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["WFMModSettings"]["cwKeyer"]["text"] = "VVV DE F4EXB " + settings["WFMModSettings"]["cwKeyer"]["loop"] = 1 + settings["WFMModSettings"]["cwKeyer"]["mode"] = 1 # text + settings["WFMModSettings"]["modAFInput"] = 4 # CW text + settings["WFMModSettings"]["toneFrequency"] = 600 + settings["WFMModSettings"]["fmDeviation"] = 25000 + settings["WFMModSettings"]["rfBandwidth"] = 75000 + settings["WFMModSettings"]["afBandwidth"] = 3000 r = callAPI(deviceset_url + "/channel/0/settings", "PATCH", None, settings, "Change modulator") if r is None: From 83efddee9aecfce0668e8dc010cc8a3ede07c9a2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 9 Apr 2018 00:54:25 +0200 Subject: [PATCH 255/956] Build ModWFM tx channel server plugin and apply corrections to other server plugins --- plugins/channelrx/demodam/amdemodplugin.cpp | 1 - plugins/channeltx/modam/ammodplugin.cpp | 12 +++++- plugins/channeltx/modnfm/nfmmodplugin.cpp | 4 +- plugins/channeltx/modwfm/wfmmodplugin.cpp | 12 +++++- pluginssrv/channelrx/demodam/CMakeLists.txt | 2 +- pluginssrv/channeltx/CMakeLists.txt | 1 + pluginssrv/channeltx/modam/CMakeLists.txt | 2 +- pluginssrv/channeltx/modnfm/CMakeLists.txt | 2 - pluginssrv/channeltx/modwfm/CMakeLists.txt | 41 +++++++++++++++++++++ 9 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 pluginssrv/channeltx/modwfm/CMakeLists.txt diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index d8bbd0ac5..76149cd3c 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -1,5 +1,4 @@ #include -#include #include "plugin/pluginapi.h" #ifndef SERVER_MODE diff --git a/plugins/channeltx/modam/ammodplugin.cpp b/plugins/channeltx/modam/ammodplugin.cpp index 4913395e7..c50ef1d87 100644 --- a/plugins/channeltx/modam/ammodplugin.cpp +++ b/plugins/channeltx/modam/ammodplugin.cpp @@ -15,10 +15,11 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "ammodgui.h" +#endif #include "ammod.h" #include "ammodplugin.h" @@ -50,10 +51,19 @@ void AMModPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(AMMod::m_channelIdURI, AMMod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* AMModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* AMModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return AMModGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* AMModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp index 8447328d6..e6c05ab9e 100644 --- a/plugins/channeltx/modnfm/nfmmodplugin.cpp +++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp @@ -15,10 +15,12 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "nfmmodgui.h" +#endif +#include "nfmmod.h" #include "nfmmodplugin.h" const PluginDescriptor NFMModPlugin::m_pluginDescriptor = { diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp index ae13046d8..14ef9a471 100644 --- a/plugins/channeltx/modwfm/wfmmodplugin.cpp +++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp @@ -15,10 +15,11 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "wfmmodgui.h" +#endif #include "wfmmod.h" #include "wfmmodplugin.h" @@ -50,10 +51,19 @@ void WFMModPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(WFMMod::m_channelIdURI, WFMMod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* WFMModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* WFMModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return WFMModGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* WFMModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/pluginssrv/channelrx/demodam/CMakeLists.txt b/pluginssrv/channelrx/demodam/CMakeLists.txt index aaa777cf1..7f0ade068 100644 --- a/pluginssrv/channelrx/demodam/CMakeLists.txt +++ b/pluginssrv/channelrx/demodam/CMakeLists.txt @@ -37,6 +37,6 @@ target_link_libraries(demodamsrv swagger ) -qt5_use_modules(demodamsrv Core Widgets) +qt5_use_modules(demodamsrv Core) install(TARGETS demodamsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file diff --git a/pluginssrv/channeltx/CMakeLists.txt b/pluginssrv/channeltx/CMakeLists.txt index c43165da7..661d661ee 100644 --- a/pluginssrv/channeltx/CMakeLists.txt +++ b/pluginssrv/channeltx/CMakeLists.txt @@ -2,3 +2,4 @@ project(mod) add_subdirectory(modam) add_subdirectory(modnfm) +add_subdirectory(modwfm) diff --git a/pluginssrv/channeltx/modam/CMakeLists.txt b/pluginssrv/channeltx/modam/CMakeLists.txt index c7c948b84..5f0430448 100644 --- a/pluginssrv/channeltx/modam/CMakeLists.txt +++ b/pluginssrv/channeltx/modam/CMakeLists.txt @@ -36,6 +36,6 @@ target_link_libraries(modamsrv swagger ) -qt5_use_modules(modamsrv Core Widgets) +qt5_use_modules(modamsrv Core) install(TARGETS modamsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modnfm/CMakeLists.txt b/pluginssrv/channeltx/modnfm/CMakeLists.txt index ef8114298..7d1eb0d6f 100644 --- a/pluginssrv/channeltx/modnfm/CMakeLists.txt +++ b/pluginssrv/channeltx/modnfm/CMakeLists.txt @@ -28,13 +28,11 @@ add_definitions(-DQT_SHARED) add_library(modnfmsrv SHARED ${modnfm_SOURCES} ${modnfm_HEADERS_MOC} - ${modnfm_FORMS_HEADERS} ) target_link_libraries(modnfmsrv ${QT_LIBRARIES} sdrbase - sdrgui swagger ) diff --git a/pluginssrv/channeltx/modwfm/CMakeLists.txt b/pluginssrv/channeltx/modwfm/CMakeLists.txt new file mode 100644 index 000000000..090d63b99 --- /dev/null +++ b/pluginssrv/channeltx/modwfm/CMakeLists.txt @@ -0,0 +1,41 @@ +project(modwfm) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/modwfm") + +set(modwfm_SOURCES + ${PLUGIN_PREFIX}/wfmmod.cpp + ${PLUGIN_PREFIX}/wfmmodplugin.cpp + ${PLUGIN_PREFIX}/wfmmodsettings.cpp +) + +set(modwfm_HEADERS + ${PLUGIN_PREFIX}/wfmmod.h + ${PLUGIN_PREFIX}/wfmmodplugin.h + ${PLUGIN_PREFIX}/wfmmodsettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modwfmsrv SHARED + ${modwfm_SOURCES} + ${modwfm_HEADERS_MOC} +) + +target_link_libraries(modwfmsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +qt5_use_modules(modwfmsrv Core) + +install(TARGETS modwfmsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file From 4dc99f7ad82c6c2e9f3c1ee9a3cf79f8ccbeeb91 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 10 Apr 2018 22:27:34 +0200 Subject: [PATCH 256/956] SDRdaemon source: fixed decimation setting --- plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp | 2 +- plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 3ee77a64a..5652419c6 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -313,7 +313,7 @@ void SDRdaemonSourceInput::applySettings(const SDRdaemonSourceSettings& settings if (force || (m_settings.m_log2Decim != settings.m_log2Decim)) { if (nbArgs > 0) os << ","; - os << "decim=" << m_settings.m_log2Decim; + os << "decim=" << settings.m_log2Decim; nbArgs++; } diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp index 8f5b33517..4c2794252 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp @@ -26,7 +26,7 @@ const PluginDescriptor SDRdaemonSourcePlugin::m_pluginDescriptor = { QString("SDRdaemon source input"), - QString("3.11.1"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From dac48f9a6de312e0d09360872e2f6595e3074651 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 11 Apr 2018 01:31:48 +0200 Subject: [PATCH 257/956] NFM demod: corrections applied to the audio sample rate depedent parameters --- plugins/channelrx/demodnfm/nfmdemod.cpp | 39 ++++++++++++++---------- plugins/channelrx/demodnfm/nfmdemod.h | 1 + plugins/channelrx/demodnfm/nfmplugin.cpp | 2 +- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index fdbdeacd1..739e847d8 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -55,7 +55,8 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_ctcssIndex(0), m_sampleCount(0), m_squelchCount(0), - m_squelchGate(2), + m_squelchGate(4800), + m_squelchDecay(480), m_squelchLevel(-990), m_squelchOpen(false), m_afSquelchOpen(false), @@ -75,12 +76,12 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_agcLevel = 1.0; - m_ctcssDetector.setCoefficients(3000, 6000.0); // 0.5s / 2 Hz resolution - m_afSquelch.setCoefficients(24, 600, 48000.0, 200, 0); // 0.5ms test period, 300ms average span, 48kS/s SR, 100ms attack, no decay - DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); + m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution + m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + m_lowpass.create(301, m_audioSampleRate, 250.0); applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); @@ -136,6 +137,7 @@ Real angleDist(Real a, Real b) void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { Complex ci; + float f = (m_audioSampleRate / 48000.0f); if (!m_running) { return; @@ -174,13 +176,13 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_settings.m_deltaSquelch) { - if (m_afSquelch.analyze(demod)) { - m_afSquelchOpen = m_afSquelch.evaluate() ? m_squelchGate + 480 : 0; + if (m_afSquelch.analyze(demod * f)) { + m_afSquelchOpen = m_afSquelch.evaluate() ? m_squelchGate + m_squelchDecay : 0; } if (m_afSquelchOpen) { - if (m_squelchCount < m_squelchGate + 480) + if (m_squelchCount < m_squelchGate + m_squelchDecay) { m_squelchCount++; } @@ -204,7 +206,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { - if (m_squelchCount < m_squelchGate + 480) + if (m_squelchCount < m_squelchGate + m_squelchDecay) { m_squelchCount++; } @@ -217,7 +219,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { if (m_settings.m_ctcssOn) { - Real ctcss_sample = m_lowpass.filter(demod); + Real ctcss_sample = m_lowpass.filter(demod * f); if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k { @@ -257,8 +259,8 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { - demod = m_bandpass.filter(demod); - Real squelchFactor = StepFunctions::smootherstep((Real) (m_squelchCount - m_squelchGate) / 480.0f); + demod = m_bandpass.filter(demod * f); + Real squelchFactor = StepFunctions::smootherstep((Real) (m_squelchCount - m_squelchGate) / (Real) m_squelchDecay); sample = demod * m_settings.m_volume * squelchFactor; } } @@ -407,6 +409,12 @@ void NFMDemod::applyAudioSampleRate(int sampleRate) m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; m_lowpass.create(301, sampleRate, 250.0); m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); + m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate + m_squelchDecay = (sampleRate / 100); // decay is fixed at 10ms + m_squelchCount = 0; // reset squelch open counter + m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution + m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + m_phaseDiscri.setFMScaling((8.0f*sampleRate) / static_cast(m_settings.m_fmDeviation)); // integrate 4x factor m_audioFifo.setSize(sampleRate); m_settingsMutex.unlock(); @@ -464,10 +472,9 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) m_settingsMutex.unlock(); } - if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || - (settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { - m_phaseDiscri.setFMScaling((8.0f*settings.m_rfBandwidth) / static_cast(settings.m_fmDeviation)); // integrate 4x factor + m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast(settings.m_fmDeviation)); // integrate 4x factor } if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) @@ -479,7 +486,7 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { - m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate + m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate m_squelchCount = 0; // reset squelch open counter } @@ -614,7 +621,7 @@ int NFMDemod::webapiSettingsPutPatch( if (frequencyOffsetChanged) { MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - 48000, settings.m_inputFrequencyOffset); + m_audioSampleRate, settings.m_inputFrequencyOffset); m_inputMessageQueue.push(channelConfigMsg); } diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index 60dd6e8c1..27c9b61e9 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -193,6 +193,7 @@ private: int m_sampleCount; int m_squelchCount; int m_squelchGate; + int m_squelchDecay; Real m_squelchLevel; bool m_squelchOpen; diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index 6ba23343b..47feaed9f 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -9,7 +9,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("3.14.1"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 2d0e0290a06e24f7041ab6c674c486052a47a900 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 12 Apr 2018 00:12:51 +0200 Subject: [PATCH 258/956] BFM demod: fixed RDS parser bug when processing optional content (issue #157) --- plugins/channelrx/demodbfm/rdsparser.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/channelrx/demodbfm/rdsparser.cpp b/plugins/channelrx/demodbfm/rdsparser.cpp index faf267806..5f6f96598 100644 --- a/plugins/channelrx/demodbfm/rdsparser.cpp +++ b/plugins/channelrx/demodbfm/rdsparser.cpp @@ -931,21 +931,24 @@ void RDSParser::decode_optional_content(int no_groups, unsigned long int *free_f int content_length = 0; int ff_pointer = 0; - for (int i = no_groups; i == 0; i--) + if (no_groups == 0) { - ff_pointer = 12 + 16 - 4; + int i = 0; +// for (int i = no_groups; i == 0; i--) i is always 0 if no_groups is 0 and exit else skip +// { + ff_pointer = 12 + 16; - while(ff_pointer > 0) + while(ff_pointer >= 7) // ff_pointer must be >= 0 and is decreased by 7 in the loop { - m_g8_label_index = (free_format[i] & (0xf << ff_pointer)); - content_length = optional_content_lengths[m_g8_label_index]; - ff_pointer -= content_length; - m_g8_content = (free_format[i] & (int(std::pow(2, content_length) - 1) << ff_pointer)); ff_pointer -= 4; + m_g8_label_index = (free_format[i] && (0xf << ff_pointer)) ? 1 : 0; + content_length = 3; // optional_content_lengths[m_g8_label_index]; // always 3 + ff_pointer -= content_length; + m_g8_content = (free_format[i] && (int(std::pow(2, content_length) - 1) << ff_pointer)) ? 1 : 0; - /* - qDebug() << "RDSParser::decode_optional_content: TMC optional content (" << label_descriptions[m_g8_label_index].c_str() - << "):" << m_g8_content;*/ + qDebug() << "RDSParser::decode_optional_content:" + << " TMC optional content (" << label_descriptions[m_g8_label_index].c_str() << ")" + << ": " << m_g8_content; } } } From a37443fe58e565972733bce966cb515ed28d5a4c Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 12 Apr 2018 00:13:40 +0200 Subject: [PATCH 259/956] BFM demod: bumped version --- plugins/channelrx/demodbfm/bfmplugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/channelrx/demodbfm/bfmplugin.cpp b/plugins/channelrx/demodbfm/bfmplugin.cpp index 7e383e467..89a086c56 100644 --- a/plugins/channelrx/demodbfm/bfmplugin.cpp +++ b/plugins/channelrx/demodbfm/bfmplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor BFMPlugin::m_pluginDescriptor = { QString("Broadcast FM Demodulator"), - QString("3.14.0"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 6e1005018f98582378806634d36922f1dd35e488 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 12 Apr 2018 01:21:34 +0200 Subject: [PATCH 260/956] NFM demod: limit volume setting to a maximum of 2.0 with a default of 1.0 --- plugins/channelrx/demodnfm/nfmdemodgui.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.ui b/plugins/channelrx/demodnfm/nfmdemodgui.ui index f59f367d2..5dc1a095b 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.ui +++ b/plugins/channelrx/demodnfm/nfmdemodgui.ui @@ -330,13 +330,13 @@ Sound volume - 100 + 20 1 - 20 + 10 @@ -358,7 +358,7 @@ Sound volume - 2.0 + 1.0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter From b151b001820b6f07c2d63ee438a6cf8b7f3ad31e Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 12 Apr 2018 23:49:29 +0200 Subject: [PATCH 261/956] SSBMod YAML file --- plugins/channeltx/modssb/ssbmodsettings.cpp | 2 - plugins/channeltx/modssb/ssbmodsettings.h | 3 - sdrbase/resources/webapi/doc/html2/index.html | 107 +++++++++++++++++- .../resources/webapi/doc/swagger/swagger.yaml | 4 + swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 107 +++++++++++++++++- .../code/qt5/client/SWGChannelReport.cpp | 23 ++++ .../code/qt5/client/SWGChannelReport.h | 7 ++ .../code/qt5/client/SWGChannelSettings.cpp | 23 ++++ .../code/qt5/client/SWGChannelSettings.h | 7 ++ .../code/qt5/client/SWGModelFactory.h | 8 ++ 11 files changed, 288 insertions(+), 7 deletions(-) diff --git a/plugins/channeltx/modssb/ssbmodsettings.cpp b/plugins/channeltx/modssb/ssbmodsettings.cpp index 09fea178c..21540287b 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.cpp +++ b/plugins/channeltx/modssb/ssbmodsettings.cpp @@ -65,8 +65,6 @@ void SSBModSettings::resetToDefaults() m_agcThresholdGate = 192; m_agcThresholdDelay = 2400; m_rgbColor = QColor(0, 255, 0).rgb(); - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; m_title = "SSB Modulator"; m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } diff --git a/plugins/channeltx/modssb/ssbmodsettings.h b/plugins/channeltx/modssb/ssbmodsettings.h index 1266b0edc..8c994685e 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.h +++ b/plugins/channeltx/modssb/ssbmodsettings.h @@ -49,9 +49,6 @@ struct SSBModSettings int m_agcThresholdDelay; quint32 m_rgbColor; - QString m_udpAddress; - uint16_t m_udpPort; - QString m_title; QString m_audioDeviceName; diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 0ea7df856..b04303e39 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1130,6 +1130,9 @@ margin-bottom: 20px; "NFMModReport" : { "$ref" : "#/definitions/NFMModReport" }, + "SSBModReport" : { + "$ref" : "#/definitions/SSBModReport" + }, "WFMModReport" : { "$ref" : "#/definitions/WFMModReport" } @@ -1160,6 +1163,9 @@ margin-bottom: 20px; "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" }, + "SSBModSettings" : { + "$ref" : "#/definitions/SSBModSettings" + }, "WFMModSettings" : { "$ref" : "#/definitions/WFMModSettings" } @@ -1979,6 +1985,105 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SSBModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "AMMod" +}; + defs.SSBModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "bandwidth" : { + "type" : "number", + "format" : "float" + }, + "lowCutoff" : { + "type" : "number", + "format" : "float" + }, + "usb" : { + "type" : "integer" + }, + "toneFrequency" : { + "type" : "number", + "format" : "float" + }, + "volumeFactor" : { + "type" : "number", + "format" : "float" + }, + "spanLog2" : { + "type" : "integer" + }, + "audioBinaural" : { + "type" : "integer" + }, + "audioFlipChannels" : { + "type" : "integer" + }, + "dsb" : { + "type" : "integer" + }, + "audioMute" : { + "type" : "integer" + }, + "playLoop" : { + "type" : "integer" + }, + "agc" : { + "type" : "integer" + }, + "agcOrder" : { + "type" : "number", + "format" : "float" + }, + "agcTime" : { + "type" : "integer" + }, + "agcThresholdEnable" : { + "type" : "integer" + }, + "agcThreshold" : { + "type" : "integer" + }, + "agcThresholdGate" : { + "type" : "integer" + }, + "agcThresholdDelay" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "modAFInput" : { + "type" : "integer" + }, + "cwKeyer" : { + "$ref" : "#/definitions/CWKeyerSettings" + } + }, + "description" : "SSBMod" }; defs.SamplingDevice = { "required" : [ "bandwidth", "centerFrequency", "hwType", "index", "sequence", "serial", "state", "streamIndex" ], @@ -20334,7 +20439,7 @@ except ApiException as e:
    - Generated 2018-04-08T18:14:01.611+02:00 + Generated 2018-04-12T23:48:02.735+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index b8e8deeae..b6e1a15ae 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1753,6 +1753,8 @@ definitions: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" + SSBModSettings: + $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" WFMModSettings: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModSettings" @@ -1774,6 +1776,8 @@ definitions: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModReport" + SSBModReport: + $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" WFMModReport: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModReport" diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 5d9861671..50a63cb35 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1753,6 +1753,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" + SSBModSettings: + $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" WFMModSettings: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModSettings" @@ -1774,6 +1776,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModReport" + SSBModReport: + $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" WFMModReport: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModReport" diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 0ea7df856..b04303e39 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1130,6 +1130,9 @@ margin-bottom: 20px; "NFMModReport" : { "$ref" : "#/definitions/NFMModReport" }, + "SSBModReport" : { + "$ref" : "#/definitions/SSBModReport" + }, "WFMModReport" : { "$ref" : "#/definitions/WFMModReport" } @@ -1160,6 +1163,9 @@ margin-bottom: 20px; "NFMModSettings" : { "$ref" : "#/definitions/NFMModSettings" }, + "SSBModSettings" : { + "$ref" : "#/definitions/SSBModSettings" + }, "WFMModSettings" : { "$ref" : "#/definitions/WFMModSettings" } @@ -1979,6 +1985,105 @@ margin-bottom: 20px; } }, "description" : "RTLSDR" +}; + defs.SSBModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "AMMod" +}; + defs.SSBModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "bandwidth" : { + "type" : "number", + "format" : "float" + }, + "lowCutoff" : { + "type" : "number", + "format" : "float" + }, + "usb" : { + "type" : "integer" + }, + "toneFrequency" : { + "type" : "number", + "format" : "float" + }, + "volumeFactor" : { + "type" : "number", + "format" : "float" + }, + "spanLog2" : { + "type" : "integer" + }, + "audioBinaural" : { + "type" : "integer" + }, + "audioFlipChannels" : { + "type" : "integer" + }, + "dsb" : { + "type" : "integer" + }, + "audioMute" : { + "type" : "integer" + }, + "playLoop" : { + "type" : "integer" + }, + "agc" : { + "type" : "integer" + }, + "agcOrder" : { + "type" : "number", + "format" : "float" + }, + "agcTime" : { + "type" : "integer" + }, + "agcThresholdEnable" : { + "type" : "integer" + }, + "agcThreshold" : { + "type" : "integer" + }, + "agcThresholdGate" : { + "type" : "integer" + }, + "agcThresholdDelay" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "modAFInput" : { + "type" : "integer" + }, + "cwKeyer" : { + "$ref" : "#/definitions/CWKeyerSettings" + } + }, + "description" : "SSBMod" }; defs.SamplingDevice = { "required" : [ "bandwidth", "centerFrequency", "hwType", "index", "sequence", "serial", "state", "streamIndex" ], @@ -20334,7 +20439,7 @@ except ApiException as e:
    - Generated 2018-04-08T18:14:01.611+02:00 + Generated 2018-04-12T23:48:02.735+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index bbb3a9267..5740d89e8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -40,6 +40,8 @@ SWGChannelReport::SWGChannelReport() { m_nfm_demod_report_isSet = false; nfm_mod_report = nullptr; m_nfm_mod_report_isSet = false; + ssb_mod_report = nullptr; + m_ssb_mod_report_isSet = false; wfm_mod_report = nullptr; m_wfm_mod_report_isSet = false; } @@ -62,6 +64,8 @@ SWGChannelReport::init() { m_nfm_demod_report_isSet = false; nfm_mod_report = new SWGNFMModReport(); m_nfm_mod_report_isSet = false; + ssb_mod_report = new SWGSSBModReport(); + m_ssb_mod_report_isSet = false; wfm_mod_report = new SWGWFMModReport(); m_wfm_mod_report_isSet = false; } @@ -84,6 +88,9 @@ SWGChannelReport::cleanup() { if(nfm_mod_report != nullptr) { delete nfm_mod_report; } + if(ssb_mod_report != nullptr) { + delete ssb_mod_report; + } if(wfm_mod_report != nullptr) { delete wfm_mod_report; } @@ -112,6 +119,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); + ::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport"); + ::SWGSDRangel::setValue(&wfm_mod_report, pJson["WFMModReport"], "SWGWFMModReport", "SWGWFMModReport"); } @@ -148,6 +157,9 @@ SWGChannelReport::asJsonObject() { if((nfm_mod_report != nullptr) && (nfm_mod_report->isSet())){ toJsonValue(QString("NFMModReport"), nfm_mod_report, obj, QString("SWGNFMModReport")); } + if((ssb_mod_report != nullptr) && (ssb_mod_report->isSet())){ + toJsonValue(QString("SSBModReport"), ssb_mod_report, obj, QString("SWGSSBModReport")); + } if((wfm_mod_report != nullptr) && (wfm_mod_report->isSet())){ toJsonValue(QString("WFMModReport"), wfm_mod_report, obj, QString("SWGWFMModReport")); } @@ -215,6 +227,16 @@ SWGChannelReport::setNfmModReport(SWGNFMModReport* nfm_mod_report) { this->m_nfm_mod_report_isSet = true; } +SWGSSBModReport* +SWGChannelReport::getSsbModReport() { + return ssb_mod_report; +} +void +SWGChannelReport::setSsbModReport(SWGSSBModReport* ssb_mod_report) { + this->ssb_mod_report = ssb_mod_report; + this->m_ssb_mod_report_isSet = true; +} + SWGWFMModReport* SWGChannelReport::getWfmModReport() { return wfm_mod_report; @@ -236,6 +258,7 @@ SWGChannelReport::isSet(){ if(am_mod_report != nullptr && am_mod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} + if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_report != nullptr && wfm_mod_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 8a0810cac..a032b5278 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -26,6 +26,7 @@ #include "SWGAMModReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" +#include "SWGSSBModReport.h" #include "SWGWFMModReport.h" #include @@ -65,6 +66,9 @@ public: SWGNFMModReport* getNfmModReport(); void setNfmModReport(SWGNFMModReport* nfm_mod_report); + SWGSSBModReport* getSsbModReport(); + void setSsbModReport(SWGSSBModReport* ssb_mod_report); + SWGWFMModReport* getWfmModReport(); void setWfmModReport(SWGWFMModReport* wfm_mod_report); @@ -90,6 +94,9 @@ private: SWGNFMModReport* nfm_mod_report; bool m_nfm_mod_report_isSet; + SWGSSBModReport* ssb_mod_report; + bool m_ssb_mod_report_isSet; + SWGWFMModReport* wfm_mod_report; bool m_wfm_mod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 99a2cbf75..66c4f2e75 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -40,6 +40,8 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; m_nfm_mod_settings_isSet = false; + ssb_mod_settings = nullptr; + m_ssb_mod_settings_isSet = false; wfm_mod_settings = nullptr; m_wfm_mod_settings_isSet = false; } @@ -62,6 +64,8 @@ SWGChannelSettings::init() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); m_nfm_mod_settings_isSet = false; + ssb_mod_settings = new SWGSSBModSettings(); + m_ssb_mod_settings_isSet = false; wfm_mod_settings = new SWGWFMModSettings(); m_wfm_mod_settings_isSet = false; } @@ -84,6 +88,9 @@ SWGChannelSettings::cleanup() { if(nfm_mod_settings != nullptr) { delete nfm_mod_settings; } + if(ssb_mod_settings != nullptr) { + delete ssb_mod_settings; + } if(wfm_mod_settings != nullptr) { delete wfm_mod_settings; } @@ -112,6 +119,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); + ::SWGSDRangel::setValue(&ssb_mod_settings, pJson["SSBModSettings"], "SWGSSBModSettings", "SWGSSBModSettings"); + ::SWGSDRangel::setValue(&wfm_mod_settings, pJson["WFMModSettings"], "SWGWFMModSettings", "SWGWFMModSettings"); } @@ -148,6 +157,9 @@ SWGChannelSettings::asJsonObject() { if((nfm_mod_settings != nullptr) && (nfm_mod_settings->isSet())){ toJsonValue(QString("NFMModSettings"), nfm_mod_settings, obj, QString("SWGNFMModSettings")); } + if((ssb_mod_settings != nullptr) && (ssb_mod_settings->isSet())){ + toJsonValue(QString("SSBModSettings"), ssb_mod_settings, obj, QString("SWGSSBModSettings")); + } if((wfm_mod_settings != nullptr) && (wfm_mod_settings->isSet())){ toJsonValue(QString("WFMModSettings"), wfm_mod_settings, obj, QString("SWGWFMModSettings")); } @@ -215,6 +227,16 @@ SWGChannelSettings::setNfmModSettings(SWGNFMModSettings* nfm_mod_settings) { this->m_nfm_mod_settings_isSet = true; } +SWGSSBModSettings* +SWGChannelSettings::getSsbModSettings() { + return ssb_mod_settings; +} +void +SWGChannelSettings::setSsbModSettings(SWGSSBModSettings* ssb_mod_settings) { + this->ssb_mod_settings = ssb_mod_settings; + this->m_ssb_mod_settings_isSet = true; +} + SWGWFMModSettings* SWGChannelSettings::getWfmModSettings() { return wfm_mod_settings; @@ -236,6 +258,7 @@ SWGChannelSettings::isSet(){ if(am_mod_settings != nullptr && am_mod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} + if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_settings != nullptr && wfm_mod_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 153703644..0c0e011e8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -26,6 +26,7 @@ #include "SWGAMModSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" +#include "SWGSSBModSettings.h" #include "SWGWFMModSettings.h" #include @@ -65,6 +66,9 @@ public: SWGNFMModSettings* getNfmModSettings(); void setNfmModSettings(SWGNFMModSettings* nfm_mod_settings); + SWGSSBModSettings* getSsbModSettings(); + void setSsbModSettings(SWGSSBModSettings* ssb_mod_settings); + SWGWFMModSettings* getWfmModSettings(); void setWfmModSettings(SWGWFMModSettings* wfm_mod_settings); @@ -90,6 +94,9 @@ private: SWGNFMModSettings* nfm_mod_settings; bool m_nfm_mod_settings_isSet; + SWGSSBModSettings* ssb_mod_settings; + bool m_ssb_mod_settings_isSet; + SWGWFMModSettings* wfm_mod_settings; bool m_wfm_mod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 9c19b6295..7b7dffd6d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -60,6 +60,8 @@ #include "SWGPresetTransfer.h" #include "SWGPresets.h" #include "SWGRtlSdrSettings.h" +#include "SWGSSBModReport.h" +#include "SWGSSBModSettings.h" #include "SWGSamplingDevice.h" #include "SWGSuccessResponse.h" #include "SWGWFMModReport.h" @@ -206,6 +208,12 @@ namespace SWGSDRangel { if(QString("SWGRtlSdrSettings").compare(type) == 0) { return new SWGRtlSdrSettings(); } + if(QString("SWGSSBModReport").compare(type) == 0) { + return new SWGSSBModReport(); + } + if(QString("SWGSSBModSettings").compare(type) == 0) { + return new SWGSSBModSettings(); + } if(QString("SWGSamplingDevice").compare(type) == 0) { return new SWGSamplingDevice(); } From e3815e4076a68d89504a61d3ca305c3d0f895f79 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 13 Apr 2018 08:41:34 +0200 Subject: [PATCH 262/956] SSB mod: moved AF input mode in settings structure --- plugins/channeltx/modssb/ssbmod.cpp | 27 ++++++-------- plugins/channeltx/modssb/ssbmod.h | 31 ---------------- plugins/channeltx/modssb/ssbmodgui.cpp | 39 +++++++++++++-------- plugins/channeltx/modssb/ssbmodgui.h | 1 - plugins/channeltx/modssb/ssbmodplugin.cpp | 2 +- plugins/channeltx/modssb/ssbmodsettings.cpp | 9 +++++ plugins/channeltx/modssb/ssbmodsettings.h | 10 ++++++ 7 files changed, 54 insertions(+), 65 deletions(-) diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index a99bde786..ba5223b17 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -34,7 +34,6 @@ MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureSSBMod, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceName, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureAFInput, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceStreamTiming, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgReportFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgReportFileSourceStreamTiming, Message) @@ -62,7 +61,6 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : m_fileSize(0), m_recordLength(0), m_sampleRate(48000), - m_afInput(SSBModInputNone), m_levelCalcCount(0), m_peakLevel(0.0f), m_levelSum(0.0f), @@ -211,9 +209,9 @@ void SSBMod::pullAF(Complex& sample) int decim = 1<<(m_settings.m_spanLog2 - 1); unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) - switch (m_afInput) + switch (m_settings.m_modAFInput) { - case SSBModInputTone: + case SSBModSettings::SSBModInputTone: if (m_settings.m_dsb) { Real t = m_toneNco.next()/1.25; @@ -229,7 +227,7 @@ void SSBMod::pullAF(Complex& sample) } } break; - case SSBModInputFile: + case SSBModSettings::SSBModInputFile: // Monaural (mono): // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw @@ -295,7 +293,7 @@ void SSBMod::pullAF(Complex& sample) ci.imag(0.0f); } break; - case SSBModInputAudio: + case SSBModSettings::SSBModInputAudio: if (m_settings.m_audioBinaural) { if (m_settings.m_audioFlipChannels) @@ -326,7 +324,7 @@ void SSBMod::pullAF(Complex& sample) } break; - case SSBModInputCWTone: + case SSBModSettings::SSBModInputCWTone: Real fadeFactor; if (m_cwKeyer.getSample()) @@ -376,12 +374,13 @@ void SSBMod::pullAF(Complex& sample) } break; - case SSBModInputNone: + case SSBModSettings::SSBModInputNone: default: break; } - if ((m_afInput == SSBModInputFile) || (m_afInput == SSBModInputAudio)) // real audio + if ((m_settings.m_modAFInput == SSBModSettings::SSBModInputFile) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAudio)) // real audio { if (m_settings.m_dsb) { @@ -439,7 +438,8 @@ void SSBMod::pullAF(Complex& sample) } } } // Real audio - else if ((m_afInput == SSBModInputTone) || (m_afInput == SSBModInputCWTone)) // tone + else if ((m_settings.m_modAFInput == SSBModSettings::SSBModInputTone) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputCWTone)) // tone { m_sum += sample; @@ -565,13 +565,6 @@ bool SSBMod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureAFInput::match(cmd)) - { - MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; - m_afInput = conf.getAFInput(); - - return true; - } else if (MsgConfigureFileSourceStreamTiming::match(cmd)) { std::size_t samplesCount; diff --git a/plugins/channeltx/modssb/ssbmod.h b/plugins/channeltx/modssb/ssbmod.h index 263420c1e..5ce20caf3 100644 --- a/plugins/channeltx/modssb/ssbmod.h +++ b/plugins/channeltx/modssb/ssbmod.h @@ -44,15 +44,6 @@ class SSBMod : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: - typedef enum - { - SSBModInputNone, - SSBModInputTone, - SSBModInputFile, - SSBModInputAudio, - SSBModInputCWTone - } SSBModInputAF; - class MsgConfigureSSBMod : public Message { MESSAGE_CLASS_DECLARATION @@ -158,27 +149,6 @@ public: { } }; - class MsgConfigureAFInput : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - SSBModInputAF getAFInput() const { return m_afInput; } - - static MsgConfigureAFInput* create(SSBModInputAF afInput) - { - return new MsgConfigureAFInput(afInput); - } - - private: - SSBModInputAF m_afInput; - - MsgConfigureAFInput(SSBModInputAF afInput) : - Message(), - m_afInput(afInput) - { } - }; - class MsgReportFileSourceStreamTiming : public Message { MESSAGE_CLASS_DECLARATION @@ -319,7 +289,6 @@ private: quint32 m_recordLength; //!< record length in seconds computed from file size int m_sampleRate; - SSBModInputAF m_afInput; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index 847c77cd1..38990c54c 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -233,9 +233,8 @@ void SSBModGUI::on_play_toggled(bool checked) ui->tone->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->mic->setEnabled(!checked); - m_modAFInput = checked ? SSBMod::SSBModInputFile : SSBMod::SSBModInputNone; - SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput); - m_ssbMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? SSBModSettings::SSBModInputFile : SSBModSettings::SSBModInputNone; + applySettings(); ui->navTimeSlider->setEnabled(!checked); m_enableNavTime = !checked; } @@ -245,9 +244,8 @@ void SSBModGUI::on_tone_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->mic->setEnabled(!checked); - m_modAFInput = checked ? SSBMod::SSBModInputTone : SSBMod::SSBModInputNone; - SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput); - m_ssbMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? SSBModSettings::SSBModInputTone : SSBModSettings::SSBModInputNone; + applySettings(); } void SSBModGUI::on_morseKeyer_toggled(bool checked) @@ -255,9 +253,8 @@ void SSBModGUI::on_morseKeyer_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->tone->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); - m_modAFInput = checked ? SSBMod::SSBModInputCWTone : SSBMod::SSBModInputNone; - SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput); - m_ssbMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? SSBModSettings::SSBModInputCWTone : SSBModSettings::SSBModInputNone; + applySettings(); } void SSBModGUI::on_mic_toggled(bool checked) @@ -265,9 +262,8 @@ void SSBModGUI::on_mic_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->tone->setEnabled(!checked); // release other source inputs - m_modAFInput = checked ? SSBMod::SSBModInputAudio : SSBMod::SSBModInputNone; - SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput); - m_ssbMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? SSBModSettings::SSBModInputAudio : SSBModSettings::SSBModInputNone; + applySettings(); } void SSBModGUI::on_agc_toggled(bool checked) @@ -363,8 +359,7 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_recordSampleRate(48000), m_samplesCount(0), m_tickCount(0), - m_enableNavTime(false), - m_modAFInput(SSBMod::SSBModInputNone) + m_enableNavTime(false) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); @@ -648,6 +643,20 @@ void SSBModGUI::displaySettings() ui->volume->setValue(m_settings.m_volumeFactor * 10.0); ui->volumeText->setText(QString("%1").arg(m_settings.m_volumeFactor, 0, 'f', 1)); + ui->tone->setEnabled((m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputTone) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputNone)); + ui->mic->setEnabled((m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputAudio) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputNone)); + ui->play->setEnabled((m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputFile) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputNone)); + ui->morseKeyer->setEnabled((m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputCWTone) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputNone)); + + ui->tone->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputTone); + ui->mic->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputAudio); + ui->play->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputFile); + ui->morseKeyer->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputCWTone); + blockApplySettings(false); } @@ -695,7 +704,7 @@ void SSBModGUI::tick() m_channelPowerDbAvg(powDb); ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1)); - if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == SSBMod::SSBModInputFile)) + if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == SSBModSettings::SSBModInputFile)) { SSBMod::MsgConfigureFileSourceStreamTiming* message = SSBMod::MsgConfigureFileSourceStreamTiming::create(); m_ssbMod->getInputMessageQueue()->push(message); diff --git a/plugins/channeltx/modssb/ssbmodgui.h b/plugins/channeltx/modssb/ssbmodgui.h index 3ebb0d225..7bd200dff 100644 --- a/plugins/channeltx/modssb/ssbmodgui.h +++ b/plugins/channeltx/modssb/ssbmodgui.h @@ -77,7 +77,6 @@ private: int m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; - SSBMod::SSBModInputAF m_modAFInput; MessageQueue m_inputMessageQueue; QIcon m_iconDSBUSB; diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp index 8f68befb3..4dd9a59f8 100644 --- a/plugins/channeltx/modssb/ssbmodplugin.cpp +++ b/plugins/channeltx/modssb/ssbmodplugin.cpp @@ -24,7 +24,7 @@ const PluginDescriptor SSBModPlugin::m_pluginDescriptor = { QString("SSB Modulator"), - QString("3.12.0"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modssb/ssbmodsettings.cpp b/plugins/channeltx/modssb/ssbmodsettings.cpp index 21540287b..34b76d2fc 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.cpp +++ b/plugins/channeltx/modssb/ssbmodsettings.cpp @@ -66,6 +66,7 @@ void SSBModSettings::resetToDefaults() m_agcThresholdDelay = 2400; m_rgbColor = QColor(0, 255, 0).rgb(); m_title = "SSB Modulator"; + m_modAFInput = SSBModInputAF::SSBModInputNone; m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } @@ -105,6 +106,7 @@ QByteArray SSBModSettings::serialize() const s.writeString(19, m_title); s.writeString(20, m_audioDeviceName); + s.writeS32(21, (int) m_modAFInput); return s.final(); } @@ -172,6 +174,13 @@ bool SSBModSettings::deserialize(const QByteArray& data) d.readString(19, &m_title, "SSB Modulator"); d.readString(20, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + d.readS32(21, &tmp, 0); + if ((tmp < 0) || (tmp > (int) SSBModInputAF::SSBModInputTone)) { + m_modAFInput = SSBModInputNone; + } else { + m_modAFInput = (SSBModInputAF) tmp; + } + return true; } else diff --git a/plugins/channeltx/modssb/ssbmodsettings.h b/plugins/channeltx/modssb/ssbmodsettings.h index 8c994685e..0163f1ef0 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.h +++ b/plugins/channeltx/modssb/ssbmodsettings.h @@ -25,6 +25,15 @@ class Serializable; struct SSBModSettings { + typedef enum + { + SSBModInputNone, + SSBModInputTone, + SSBModInputFile, + SSBModInputAudio, + SSBModInputCWTone + } SSBModInputAF; + static const int m_nbAGCTimeConstants; static const int m_agcTimeConstant[]; @@ -50,6 +59,7 @@ struct SSBModSettings quint32 m_rgbColor; QString m_title; + SSBModInputAF m_modAFInput; QString m_audioDeviceName; Serializable *m_channelMarker; From 2af885dcda603e736b6b680f80e99e4611feb04d Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 13 Apr 2018 09:27:48 +0200 Subject: [PATCH 263/956] SSB mod: added missing new files --- .../webapi/doc/swagger/include/SSBMod.yaml | 69 ++ .../sdrangel/api/swagger/include/SSBMod.yaml | 69 ++ .../code/qt5/client/SWGSSBModReport.cpp | 148 +++++ .../code/qt5/client/SWGSSBModReport.h | 70 +++ .../code/qt5/client/SWGSSBModSettings.cpp | 595 ++++++++++++++++++ .../code/qt5/client/SWGSSBModSettings.h | 198 ++++++ 6 files changed, 1149 insertions(+) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/SSBMod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/SSBMod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSSBModReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h diff --git a/sdrbase/resources/webapi/doc/swagger/include/SSBMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/SSBMod.yaml new file mode 100644 index 000000000..2692d04aa --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/SSBMod.yaml @@ -0,0 +1,69 @@ +SSBModSettings: + description: SSBMod + properties: + inputFrequencyOffset: + type: integer + format: int64 + bandwidth: + type: number + format: float + lowCutoff: + type: number + format: float + usb: + type: integer + toneFrequency: + type: number + format: float + volumeFactor: + type: number + format: float + spanLog2: + type: integer + audioBinaural: + type: integer + audioFlipChannels: + type: integer + dsb: + type: integer + audioMute: + type: integer + playLoop: + type: integer + agc: + type: integer + agcOrder: + type: number + format: float + agcTime: + type: integer + agcThresholdEnable: + type: integer + agcThreshold: + type: integer + agcThresholdGate: + type: integer + agcThresholdDelay: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + modAFInput: + type: integer + cwKeyer: + $ref: "/doc/swagger/include/CWKeyer.yaml#/CWKeyerSettings" + +SSBModReport: + description: AMMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer diff --git a/swagger/sdrangel/api/swagger/include/SSBMod.yaml b/swagger/sdrangel/api/swagger/include/SSBMod.yaml new file mode 100644 index 000000000..bf3d75099 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/SSBMod.yaml @@ -0,0 +1,69 @@ +SSBModSettings: + description: SSBMod + properties: + inputFrequencyOffset: + type: integer + format: int64 + bandwidth: + type: number + format: float + lowCutoff: + type: number + format: float + usb: + type: integer + toneFrequency: + type: number + format: float + volumeFactor: + type: number + format: float + spanLog2: + type: integer + audioBinaural: + type: integer + audioFlipChannels: + type: integer + dsb: + type: integer + audioMute: + type: integer + playLoop: + type: integer + agc: + type: integer + agcOrder: + type: number + format: float + agcTime: + type: integer + agcThresholdEnable: + type: integer + agcThreshold: + type: integer + agcThresholdGate: + type: integer + agcThresholdDelay: + type: integer + rgbColor: + type: integer + title: + type: string + audioDeviceName: + type: string + modAFInput: + type: integer + cwKeyer: + $ref: "http://localhost:8081/api/swagger/include/CWKeyer.yaml#/CWKeyerSettings" + +SSBModReport: + description: AMMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + audioSampleRate: + type: integer + channelSampleRate: + type: integer diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp new file mode 100644 index 000000000..fbff1740b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.cpp @@ -0,0 +1,148 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSSBModReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSSBModReport::SWGSSBModReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSSBModReport::SWGSSBModReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGSSBModReport::~SWGSSBModReport() { + this->cleanup(); +} + +void +SWGSSBModReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + audio_sample_rate = 0; + m_audio_sample_rate_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGSSBModReport::cleanup() { + + + +} + +SWGSSBModReport* +SWGSSBModReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSSBModReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGSSBModReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSSBModReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_audio_sample_rate_isSet){ + obj->insert("audioSampleRate", QJsonValue(audio_sample_rate)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGSSBModReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGSSBModReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGSSBModReport::getAudioSampleRate() { + return audio_sample_rate; +} +void +SWGSSBModReport::setAudioSampleRate(qint32 audio_sample_rate) { + this->audio_sample_rate = audio_sample_rate; + this->m_audio_sample_rate_isSet = true; +} + +qint32 +SWGSSBModReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGSSBModReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGSSBModReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h new file mode 100644 index 000000000..233980633 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModReport.h @@ -0,0 +1,70 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSSBModReport.h + * + * AMMod + */ + +#ifndef SWGSSBModReport_H_ +#define SWGSSBModReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSSBModReport: public SWGObject { +public: + SWGSSBModReport(); + SWGSSBModReport(QString* json); + virtual ~SWGSSBModReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSSBModReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getAudioSampleRate(); + void setAudioSampleRate(qint32 audio_sample_rate); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 audio_sample_rate; + bool m_audio_sample_rate_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGSSBModReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp new file mode 100644 index 000000000..6af279670 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.cpp @@ -0,0 +1,595 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGSSBModSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGSSBModSettings::SWGSSBModSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGSSBModSettings::SWGSSBModSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + bandwidth = 0.0f; + m_bandwidth_isSet = false; + low_cutoff = 0.0f; + m_low_cutoff_isSet = false; + usb = 0; + m_usb_isSet = false; + tone_frequency = 0.0f; + m_tone_frequency_isSet = false; + volume_factor = 0.0f; + m_volume_factor_isSet = false; + span_log2 = 0; + m_span_log2_isSet = false; + audio_binaural = 0; + m_audio_binaural_isSet = false; + audio_flip_channels = 0; + m_audio_flip_channels_isSet = false; + dsb = 0; + m_dsb_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + play_loop = 0; + m_play_loop_isSet = false; + agc = 0; + m_agc_isSet = false; + agc_order = 0.0f; + m_agc_order_isSet = false; + agc_time = 0; + m_agc_time_isSet = false; + agc_threshold_enable = 0; + m_agc_threshold_enable_isSet = false; + agc_threshold = 0; + m_agc_threshold_isSet = false; + agc_threshold_gate = 0; + m_agc_threshold_gate_isSet = false; + agc_threshold_delay = 0; + m_agc_threshold_delay_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; + mod_af_input = 0; + m_mod_af_input_isSet = false; + cw_keyer = nullptr; + m_cw_keyer_isSet = false; +} + +SWGSSBModSettings::~SWGSSBModSettings() { + this->cleanup(); +} + +void +SWGSSBModSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + bandwidth = 0.0f; + m_bandwidth_isSet = false; + low_cutoff = 0.0f; + m_low_cutoff_isSet = false; + usb = 0; + m_usb_isSet = false; + tone_frequency = 0.0f; + m_tone_frequency_isSet = false; + volume_factor = 0.0f; + m_volume_factor_isSet = false; + span_log2 = 0; + m_span_log2_isSet = false; + audio_binaural = 0; + m_audio_binaural_isSet = false; + audio_flip_channels = 0; + m_audio_flip_channels_isSet = false; + dsb = 0; + m_dsb_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + play_loop = 0; + m_play_loop_isSet = false; + agc = 0; + m_agc_isSet = false; + agc_order = 0.0f; + m_agc_order_isSet = false; + agc_time = 0; + m_agc_time_isSet = false; + agc_threshold_enable = 0; + m_agc_threshold_enable_isSet = false; + agc_threshold = 0; + m_agc_threshold_isSet = false; + agc_threshold_gate = 0; + m_agc_threshold_gate_isSet = false; + agc_threshold_delay = 0; + m_agc_threshold_delay_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; + mod_af_input = 0; + m_mod_af_input_isSet = false; + cw_keyer = new SWGCWKeyerSettings(); + m_cw_keyer_isSet = false; +} + +void +SWGSSBModSettings::cleanup() { + + + + + + + + + + + + + + + + + + + + + if(title != nullptr) { + delete title; + } + if(audio_device_name != nullptr) { + delete audio_device_name; + } + + if(cw_keyer != nullptr) { + delete cw_keyer; + } +} + +SWGSSBModSettings* +SWGSSBModSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGSSBModSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&bandwidth, pJson["bandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&low_cutoff, pJson["lowCutoff"], "float", ""); + + ::SWGSDRangel::setValue(&usb, pJson["usb"], "qint32", ""); + + ::SWGSDRangel::setValue(&tone_frequency, pJson["toneFrequency"], "float", ""); + + ::SWGSDRangel::setValue(&volume_factor, pJson["volumeFactor"], "float", ""); + + ::SWGSDRangel::setValue(&span_log2, pJson["spanLog2"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_binaural, pJson["audioBinaural"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_flip_channels, pJson["audioFlipChannels"], "qint32", ""); + + ::SWGSDRangel::setValue(&dsb, pJson["dsb"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_mute, pJson["audioMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&play_loop, pJson["playLoop"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc, pJson["agc"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_order, pJson["agcOrder"], "float", ""); + + ::SWGSDRangel::setValue(&agc_time, pJson["agcTime"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_threshold_enable, pJson["agcThresholdEnable"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_threshold, pJson["agcThreshold"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_threshold_gate, pJson["agcThresholdGate"], "qint32", ""); + + ::SWGSDRangel::setValue(&agc_threshold_delay, pJson["agcThresholdDelay"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&mod_af_input, pJson["modAFInput"], "qint32", ""); + + ::SWGSDRangel::setValue(&cw_keyer, pJson["cwKeyer"], "SWGCWKeyerSettings", "SWGCWKeyerSettings"); + +} + +QString +SWGSSBModSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGSSBModSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_bandwidth_isSet){ + obj->insert("bandwidth", QJsonValue(bandwidth)); + } + if(m_low_cutoff_isSet){ + obj->insert("lowCutoff", QJsonValue(low_cutoff)); + } + if(m_usb_isSet){ + obj->insert("usb", QJsonValue(usb)); + } + if(m_tone_frequency_isSet){ + obj->insert("toneFrequency", QJsonValue(tone_frequency)); + } + if(m_volume_factor_isSet){ + obj->insert("volumeFactor", QJsonValue(volume_factor)); + } + if(m_span_log2_isSet){ + obj->insert("spanLog2", QJsonValue(span_log2)); + } + if(m_audio_binaural_isSet){ + obj->insert("audioBinaural", QJsonValue(audio_binaural)); + } + if(m_audio_flip_channels_isSet){ + obj->insert("audioFlipChannels", QJsonValue(audio_flip_channels)); + } + if(m_dsb_isSet){ + obj->insert("dsb", QJsonValue(dsb)); + } + if(m_audio_mute_isSet){ + obj->insert("audioMute", QJsonValue(audio_mute)); + } + if(m_play_loop_isSet){ + obj->insert("playLoop", QJsonValue(play_loop)); + } + if(m_agc_isSet){ + obj->insert("agc", QJsonValue(agc)); + } + if(m_agc_order_isSet){ + obj->insert("agcOrder", QJsonValue(agc_order)); + } + if(m_agc_time_isSet){ + obj->insert("agcTime", QJsonValue(agc_time)); + } + if(m_agc_threshold_enable_isSet){ + obj->insert("agcThresholdEnable", QJsonValue(agc_threshold_enable)); + } + if(m_agc_threshold_isSet){ + obj->insert("agcThreshold", QJsonValue(agc_threshold)); + } + if(m_agc_threshold_gate_isSet){ + obj->insert("agcThresholdGate", QJsonValue(agc_threshold_gate)); + } + if(m_agc_threshold_delay_isSet){ + obj->insert("agcThresholdDelay", QJsonValue(agc_threshold_delay)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } + if(m_mod_af_input_isSet){ + obj->insert("modAFInput", QJsonValue(mod_af_input)); + } + if((cw_keyer != nullptr) && (cw_keyer->isSet())){ + toJsonValue(QString("cwKeyer"), cw_keyer, obj, QString("SWGCWKeyerSettings")); + } + + return obj; +} + +qint64 +SWGSSBModSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGSSBModSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGSSBModSettings::getBandwidth() { + return bandwidth; +} +void +SWGSSBModSettings::setBandwidth(float bandwidth) { + this->bandwidth = bandwidth; + this->m_bandwidth_isSet = true; +} + +float +SWGSSBModSettings::getLowCutoff() { + return low_cutoff; +} +void +SWGSSBModSettings::setLowCutoff(float low_cutoff) { + this->low_cutoff = low_cutoff; + this->m_low_cutoff_isSet = true; +} + +qint32 +SWGSSBModSettings::getUsb() { + return usb; +} +void +SWGSSBModSettings::setUsb(qint32 usb) { + this->usb = usb; + this->m_usb_isSet = true; +} + +float +SWGSSBModSettings::getToneFrequency() { + return tone_frequency; +} +void +SWGSSBModSettings::setToneFrequency(float tone_frequency) { + this->tone_frequency = tone_frequency; + this->m_tone_frequency_isSet = true; +} + +float +SWGSSBModSettings::getVolumeFactor() { + return volume_factor; +} +void +SWGSSBModSettings::setVolumeFactor(float volume_factor) { + this->volume_factor = volume_factor; + this->m_volume_factor_isSet = true; +} + +qint32 +SWGSSBModSettings::getSpanLog2() { + return span_log2; +} +void +SWGSSBModSettings::setSpanLog2(qint32 span_log2) { + this->span_log2 = span_log2; + this->m_span_log2_isSet = true; +} + +qint32 +SWGSSBModSettings::getAudioBinaural() { + return audio_binaural; +} +void +SWGSSBModSettings::setAudioBinaural(qint32 audio_binaural) { + this->audio_binaural = audio_binaural; + this->m_audio_binaural_isSet = true; +} + +qint32 +SWGSSBModSettings::getAudioFlipChannels() { + return audio_flip_channels; +} +void +SWGSSBModSettings::setAudioFlipChannels(qint32 audio_flip_channels) { + this->audio_flip_channels = audio_flip_channels; + this->m_audio_flip_channels_isSet = true; +} + +qint32 +SWGSSBModSettings::getDsb() { + return dsb; +} +void +SWGSSBModSettings::setDsb(qint32 dsb) { + this->dsb = dsb; + this->m_dsb_isSet = true; +} + +qint32 +SWGSSBModSettings::getAudioMute() { + return audio_mute; +} +void +SWGSSBModSettings::setAudioMute(qint32 audio_mute) { + this->audio_mute = audio_mute; + this->m_audio_mute_isSet = true; +} + +qint32 +SWGSSBModSettings::getPlayLoop() { + return play_loop; +} +void +SWGSSBModSettings::setPlayLoop(qint32 play_loop) { + this->play_loop = play_loop; + this->m_play_loop_isSet = true; +} + +qint32 +SWGSSBModSettings::getAgc() { + return agc; +} +void +SWGSSBModSettings::setAgc(qint32 agc) { + this->agc = agc; + this->m_agc_isSet = true; +} + +float +SWGSSBModSettings::getAgcOrder() { + return agc_order; +} +void +SWGSSBModSettings::setAgcOrder(float agc_order) { + this->agc_order = agc_order; + this->m_agc_order_isSet = true; +} + +qint32 +SWGSSBModSettings::getAgcTime() { + return agc_time; +} +void +SWGSSBModSettings::setAgcTime(qint32 agc_time) { + this->agc_time = agc_time; + this->m_agc_time_isSet = true; +} + +qint32 +SWGSSBModSettings::getAgcThresholdEnable() { + return agc_threshold_enable; +} +void +SWGSSBModSettings::setAgcThresholdEnable(qint32 agc_threshold_enable) { + this->agc_threshold_enable = agc_threshold_enable; + this->m_agc_threshold_enable_isSet = true; +} + +qint32 +SWGSSBModSettings::getAgcThreshold() { + return agc_threshold; +} +void +SWGSSBModSettings::setAgcThreshold(qint32 agc_threshold) { + this->agc_threshold = agc_threshold; + this->m_agc_threshold_isSet = true; +} + +qint32 +SWGSSBModSettings::getAgcThresholdGate() { + return agc_threshold_gate; +} +void +SWGSSBModSettings::setAgcThresholdGate(qint32 agc_threshold_gate) { + this->agc_threshold_gate = agc_threshold_gate; + this->m_agc_threshold_gate_isSet = true; +} + +qint32 +SWGSSBModSettings::getAgcThresholdDelay() { + return agc_threshold_delay; +} +void +SWGSSBModSettings::setAgcThresholdDelay(qint32 agc_threshold_delay) { + this->agc_threshold_delay = agc_threshold_delay; + this->m_agc_threshold_delay_isSet = true; +} + +qint32 +SWGSSBModSettings::getRgbColor() { + return rgb_color; +} +void +SWGSSBModSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGSSBModSettings::getTitle() { + return title; +} +void +SWGSSBModSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +QString* +SWGSSBModSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGSSBModSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + +qint32 +SWGSSBModSettings::getModAfInput() { + return mod_af_input; +} +void +SWGSSBModSettings::setModAfInput(qint32 mod_af_input) { + this->mod_af_input = mod_af_input; + this->m_mod_af_input_isSet = true; +} + +SWGCWKeyerSettings* +SWGSSBModSettings::getCwKeyer() { + return cw_keyer; +} +void +SWGSSBModSettings::setCwKeyer(SWGCWKeyerSettings* cw_keyer) { + this->cw_keyer = cw_keyer; + this->m_cw_keyer_isSet = true; +} + + +bool +SWGSSBModSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_low_cutoff_isSet){ isObjectUpdated = true; break;} + if(m_usb_isSet){ isObjectUpdated = true; break;} + if(m_tone_frequency_isSet){ isObjectUpdated = true; break;} + if(m_volume_factor_isSet){ isObjectUpdated = true; break;} + if(m_span_log2_isSet){ isObjectUpdated = true; break;} + if(m_audio_binaural_isSet){ isObjectUpdated = true; break;} + if(m_audio_flip_channels_isSet){ isObjectUpdated = true; break;} + if(m_dsb_isSet){ isObjectUpdated = true; break;} + if(m_audio_mute_isSet){ isObjectUpdated = true; break;} + if(m_play_loop_isSet){ isObjectUpdated = true; break;} + if(m_agc_isSet){ isObjectUpdated = true; break;} + if(m_agc_order_isSet){ isObjectUpdated = true; break;} + if(m_agc_time_isSet){ isObjectUpdated = true; break;} + if(m_agc_threshold_enable_isSet){ isObjectUpdated = true; break;} + if(m_agc_threshold_isSet){ isObjectUpdated = true; break;} + if(m_agc_threshold_gate_isSet){ isObjectUpdated = true; break;} + if(m_agc_threshold_delay_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(audio_device_name != nullptr && *audio_device_name != QString("")){ isObjectUpdated = true; break;} + if(m_mod_af_input_isSet){ isObjectUpdated = true; break;} + if(cw_keyer != nullptr && cw_keyer->isSet()){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h new file mode 100644 index 000000000..31c185fc8 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGSSBModSettings.h @@ -0,0 +1,198 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGSSBModSettings.h + * + * SSBMod + */ + +#ifndef SWGSSBModSettings_H_ +#define SWGSSBModSettings_H_ + +#include + + +#include "SWGCWKeyerSettings.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGSSBModSettings: public SWGObject { +public: + SWGSSBModSettings(); + SWGSSBModSettings(QString* json); + virtual ~SWGSSBModSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGSSBModSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getBandwidth(); + void setBandwidth(float bandwidth); + + float getLowCutoff(); + void setLowCutoff(float low_cutoff); + + qint32 getUsb(); + void setUsb(qint32 usb); + + float getToneFrequency(); + void setToneFrequency(float tone_frequency); + + float getVolumeFactor(); + void setVolumeFactor(float volume_factor); + + qint32 getSpanLog2(); + void setSpanLog2(qint32 span_log2); + + qint32 getAudioBinaural(); + void setAudioBinaural(qint32 audio_binaural); + + qint32 getAudioFlipChannels(); + void setAudioFlipChannels(qint32 audio_flip_channels); + + qint32 getDsb(); + void setDsb(qint32 dsb); + + qint32 getAudioMute(); + void setAudioMute(qint32 audio_mute); + + qint32 getPlayLoop(); + void setPlayLoop(qint32 play_loop); + + qint32 getAgc(); + void setAgc(qint32 agc); + + float getAgcOrder(); + void setAgcOrder(float agc_order); + + qint32 getAgcTime(); + void setAgcTime(qint32 agc_time); + + qint32 getAgcThresholdEnable(); + void setAgcThresholdEnable(qint32 agc_threshold_enable); + + qint32 getAgcThreshold(); + void setAgcThreshold(qint32 agc_threshold); + + qint32 getAgcThresholdGate(); + void setAgcThresholdGate(qint32 agc_threshold_gate); + + qint32 getAgcThresholdDelay(); + void setAgcThresholdDelay(qint32 agc_threshold_delay); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + + qint32 getModAfInput(); + void setModAfInput(qint32 mod_af_input); + + SWGCWKeyerSettings* getCwKeyer(); + void setCwKeyer(SWGCWKeyerSettings* cw_keyer); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float bandwidth; + bool m_bandwidth_isSet; + + float low_cutoff; + bool m_low_cutoff_isSet; + + qint32 usb; + bool m_usb_isSet; + + float tone_frequency; + bool m_tone_frequency_isSet; + + float volume_factor; + bool m_volume_factor_isSet; + + qint32 span_log2; + bool m_span_log2_isSet; + + qint32 audio_binaural; + bool m_audio_binaural_isSet; + + qint32 audio_flip_channels; + bool m_audio_flip_channels_isSet; + + qint32 dsb; + bool m_dsb_isSet; + + qint32 audio_mute; + bool m_audio_mute_isSet; + + qint32 play_loop; + bool m_play_loop_isSet; + + qint32 agc; + bool m_agc_isSet; + + float agc_order; + bool m_agc_order_isSet; + + qint32 agc_time; + bool m_agc_time_isSet; + + qint32 agc_threshold_enable; + bool m_agc_threshold_enable_isSet; + + qint32 agc_threshold; + bool m_agc_threshold_isSet; + + qint32 agc_threshold_gate; + bool m_agc_threshold_gate_isSet; + + qint32 agc_threshold_delay; + bool m_agc_threshold_delay_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + QString* audio_device_name; + bool m_audio_device_name_isSet; + + qint32 mod_af_input; + bool m_mod_af_input_isSet; + + SWGCWKeyerSettings* cw_keyer; + bool m_cw_keyer_isSet; + +}; + +} + +#endif /* SWGSSBModSettings_H_ */ From 9196c6f0c703655e0891dce51e7746c0cf43d37f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 14 Apr 2018 04:45:22 +0200 Subject: [PATCH 264/956] NFM demod: adjusted deviations, scaling and audio volume --- plugins/channelrx/demodnfm/nfmdemod.cpp | 2 +- plugins/channelrx/demodnfm/nfmdemodgui.ui | 2 +- plugins/channelrx/demodnfm/nfmdemodsettings.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 739e847d8..67e1002d5 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -414,7 +414,7 @@ void NFMDemod::applyAudioSampleRate(int sampleRate) m_squelchCount = 0; // reset squelch open counter m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay - m_phaseDiscri.setFMScaling((8.0f*sampleRate) / static_cast(m_settings.m_fmDeviation)); // integrate 4x factor + m_phaseDiscri.setFMScaling(sampleRate / static_cast(m_settings.m_fmDeviation)); m_audioFifo.setSize(sampleRate); m_settingsMutex.unlock(); diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.ui b/plugins/channelrx/demodnfm/nfmdemodgui.ui index 5dc1a095b..8a1a94f51 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.ui +++ b/plugins/channelrx/demodnfm/nfmdemodgui.ui @@ -330,7 +330,7 @@ Sound volume
    - 20 + 40 1 diff --git a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp index cceb0540d..718404ecd 100644 --- a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp @@ -25,8 +25,8 @@ const int NFMDemodSettings::m_rfBW[] = { 5000, 6250, 8330, 10000, 12500, 15000, 20000, 25000, 40000 }; -const int NFMDemodSettings::m_fmDev[] = { // corresponding FM deviations - 1000, 1500, 2000, 2000, 2000, 2500, 3000, 3500, 5000 +const int NFMDemodSettings::m_fmDev[] = { // corresponding single side FM deviations at 0.4 * BW + 2000, 2500, 3330, 4000, 5000, 6000, 8000, 10000, 16000 }; const int NFMDemodSettings::m_nbRfBW = 9; From 9c7026ae5e8a6f16b9fe269f84a0489906a48032 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 14 Apr 2018 21:45:45 +0200 Subject: [PATCH 265/956] NFM demod: fixed AF squelch setting according to audio sample rate. Fixed discriminator. Added details to documentation. --- plugins/channelrx/demodnfm/nfmdemod.cpp | 28 ++++++++++++++++------ plugins/channelrx/demodnfm/nfmdemod.h | 1 + plugins/channelrx/demodnfm/readme.md | 26 ++++++++++++++++---- sdrbase/dsp/afsquelch.cpp | 32 +++++++++++++------------ sdrbase/dsp/afsquelch.h | 12 +++++----- 5 files changed, 67 insertions(+), 32 deletions(-) diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 67e1002d5..3c2d82ab3 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -44,6 +44,7 @@ const QString NFMDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.nfm"; const QString NFMDemod::m_channelId = "NFMDemod"; static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0}; +static const double afSqTones_lowrate[2] = {1000.0, 3500.0}; const int NFMDemod::m_udpBlockSize = 512; NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : @@ -64,7 +65,7 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_magsqSum(0.0f), m_magsqPeak(0.0f), m_magsqCount(0), - m_afSquelch(2, afSqTones), + m_afSquelch(), m_audioFifo(48000), m_settingsMutex(QMutex::Recursive) { @@ -78,9 +79,11 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); + m_discriCompensation = (m_audioSampleRate/48000.0f); + m_discriCompensation *= sqrt(m_discriCompensation); m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution - m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay m_lowpass.create(301, m_audioSampleRate, 250.0); @@ -137,7 +140,6 @@ Real angleDist(Real a, Real b) void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { Complex ci; - float f = (m_audioSampleRate / 48000.0f); if (!m_running) { return; @@ -176,7 +178,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_settings.m_deltaSquelch) { - if (m_afSquelch.analyze(demod * f)) { + if (m_afSquelch.analyze(demod * m_discriCompensation)) { m_afSquelchOpen = m_afSquelch.evaluate() ? m_squelchGate + m_squelchDecay : 0; } @@ -219,7 +221,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { if (m_settings.m_ctcssOn) { - Real ctcss_sample = m_lowpass.filter(demod * f); + Real ctcss_sample = m_lowpass.filter(demod * m_discriCompensation); if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k { @@ -259,7 +261,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { - demod = m_bandpass.filter(demod * f); + demod = m_bandpass.filter(demod * m_discriCompensation); Real squelchFactor = StepFunctions::smootherstep((Real) (m_squelchCount - m_squelchGate) / (Real) m_squelchDecay); sample = demod * m_settings.m_volume * squelchFactor; } @@ -404,6 +406,7 @@ void NFMDemod::applyAudioSampleRate(int sampleRate) m_inputMessageQueue.push(channelConfigMsg); m_settingsMutex.lock(); + m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); m_interpolatorDistanceRemain = 0; m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; @@ -413,9 +416,20 @@ void NFMDemod::applyAudioSampleRate(int sampleRate) m_squelchDecay = (sampleRate / 100); // decay is fixed at 10ms m_squelchCount = 0; // reset squelch open counter m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution - m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + + if (sampleRate < 16000) { + m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones_lowrate); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + + } else { + m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + } + + m_discriCompensation = (sampleRate/48000.0f); + m_discriCompensation *= sqrt(m_discriCompensation); + m_phaseDiscri.setFMScaling(sampleRate / static_cast(m_settings.m_fmDeviation)); m_audioFifo.setSize(sampleRate); + m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index 27c9b61e9..132b1ee25 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -179,6 +179,7 @@ private: int m_inputFrequencyOffset; NFMDemodSettings m_settings; uint32_t m_audioSampleRate; + float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s) bool m_running; NCO m_nco; diff --git a/plugins/channelrx/demodnfm/readme.md b/plugins/channelrx/demodnfm/readme.md index 3e689e073..ae37300f0 100644 --- a/plugins/channelrx/demodnfm/readme.md +++ b/plugins/channelrx/demodnfm/readme.md @@ -24,15 +24,19 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the

    4: RF bandwidth

    -This is the bandwidth in kHz of the channel signal before demodulation. It can be set in steps as 5, 6.25, 8.33, 10, 12.5, 15, 20, 25 and 40 kHz. +This is the bandwidth in kHz of the channel signal before demodulation. It can be set in steps as 5, 6.25, 8.33, 10, 12.5, 15, 20, 25 and 40 kHz. The expected one side frequency deviation is 0.4 times the bandwidth. + +☞ The demodulation is done at the channel sample rate which is guaranteed not to be lower than the requested audio sample rate but can possibly be equal to it. This means that for correct operaton in any case you must ensure that the sample rate of the audio device is not lower than the Nyquist rate required to process this channel bandwidth. + +☞ The channel sample rate is always the baseband signal rate divided by an integer power of two so depending on the baseband sample rate obtained from the sampling device you could also guarantee a minimal channel bandwidth. For example with a 125 kS/s baseband sample rate and a 8 kS/s audio sample rate the channel sample rate cannot be lower than 125/8 = 15.625 kS/s (125/16 = 7.8125 kS/s is too small) which is still OK for 5 or 6.25 kHz channel bandwidths.

    5: AF bandwidth

    -This is the bandwidth of the audio signal in kHz (i.e. after demodulation). It can be set in continuous kHz steps from 1 to 20 kHz. +This is the bandwidth of the audio signal in kHz (i.e. after demodulation). It can be set in continuous kHz steps from 1 to 20 kHz.

    6: Volume

    -This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button. +This is the volume of the audio signal from 0.0 (mute) to 4.0 (maximum). It can be varied continuously in 0.1 steps using the dial button.

    7: Delta/Level squelch

    @@ -40,7 +44,21 @@ Use this button to toggle between AF (on) and RF power (off) based squelch.

    8: Squelch threshold

    -This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button. +

    Power threshold mode

    + +Case when the delta/Level squelch control (7) is off (power). This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button. + +

    Audio frequency delta mode

    + +Case when the delta/Level squelch control (7) is on (delta). In this mode the squelch compares the power of the demodulated audio signal in a low frequency band and a high frequency band. In the absence of signal the discriminator response is nearly flat and the power in the two bands is more or less balanced. In the presence of a signal the lower band will receive more power than the higher band. The squelch does the ratio of both powers and the squelch is opened if this ratio is lower than the threshold given in percent. + +A ratio of 1 (100%) will always open the squelch and a ratio of 0 will always close it. The value can be varied to detect more distorted and thus weak signals towards the higher values. The button rotation runs from higher to lower as you turn it clockwise thus giving the same feel as in power mode. The best ratio for a standard NFM transmission is ~40%. + +The distinct advantage of this type of squelch is that it guarantees the quality level of the audio signal (optimized for voice) thus remaining closed for too noisy signals received on marginal conditions or bursts of noise independently of the signal power. + +☞ The signal used is the one before AF filtering and the bands are centered around 1000 Hz for the lower band and 6000 Hz for the higher band. This means that it will not work if your audio device runs at 8000 or 11025 Hz. You will need at least a 16000 Hz sample rate. Choose power squelch for lower audio rates. + +☞ The chosen bands around 1000 and 6000 Hz are optimized for standard voice signals in the 300-3000 Hz range.

    9: Squelch gate

    diff --git a/sdrbase/dsp/afsquelch.cpp b/sdrbase/dsp/afsquelch.cpp index 361478d12..dee0e4216 100644 --- a/sdrbase/dsp/afsquelch.cpp +++ b/sdrbase/dsp/afsquelch.cpp @@ -18,16 +18,16 @@ #include "dsp/afsquelch.h" #undef M_PI -#define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 -AFSquelch::AFSquelch(unsigned int nbTones, const double *tones) : +AFSquelch::AFSquelch() : m_nbAvg(128), - m_N(0), - m_sampleRate(0), + m_N(24), + m_sampleRate(48000), m_samplesProcessed(0), m_samplesAvgProcessed(0), m_maxPowerIndex(0), - m_nTones(nbTones), + m_nTones(2), m_samplesAttack(0), m_attackCount(0), m_samplesDecay(0), @@ -46,9 +46,9 @@ AFSquelch::AFSquelch(unsigned int nbTones, const double *tones) : for (unsigned int j = 0; j < m_nTones; ++j) { - m_toneSet[j] = tones[j]; - m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate; - m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate); + m_toneSet[j] = j == 0 ? 1000.0 : 6000.0; + m_k[j] = ((double)m_N * m_toneSet[j]) / (double) m_sampleRate; + m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double) m_sampleRate); m_u0[j] = 0.0; m_u1[j] = 0.0; m_power[j] = 0.0; @@ -67,19 +67,19 @@ AFSquelch::~AFSquelch() delete[] m_power; } - void AFSquelch::setCoefficients( unsigned int N, unsigned int nbAvg, - unsigned int _samplerate, - unsigned int _samplesAttack, - unsigned int _samplesDecay) + unsigned int sampleRate, + unsigned int samplesAttack, + unsigned int samplesDecay, + const double *tones) { m_N = N; // save the basic parameters for use during analysis m_nbAvg = nbAvg; - m_sampleRate = _samplerate; - m_samplesAttack = _samplesAttack; - m_samplesDecay = _samplesDecay; + m_sampleRate = sampleRate; + m_samplesAttack = samplesAttack; + m_samplesDecay = samplesDecay; m_movingAverages.resize(m_nTones, MovingAverage(m_nbAvg, 0.0)); m_samplesProcessed = 0; m_samplesAvgProcessed = 0; @@ -97,8 +97,10 @@ void AFSquelch::setCoefficients( // for later display. The tone set is specified in the // constructor. Notice that the resulting coefficients are // independent of N. + for (unsigned int j = 0; j < m_nTones; ++j) { + m_toneSet[j] = tones[j] < ((double) m_sampleRate) * 0.4 ? tones[j] : ((double) m_sampleRate) * 0.4; // guarantee 80% Nyquist rate m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate; m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate); m_u0[j] = 0.0; diff --git a/sdrbase/dsp/afsquelch.h b/sdrbase/dsp/afsquelch.h index 324fced9d..089a98c7c 100644 --- a/sdrbase/dsp/afsquelch.h +++ b/sdrbase/dsp/afsquelch.h @@ -26,18 +26,18 @@ */ class SDRBASE_API AFSquelch { public: - // allows user defined tone pair - AFSquelch(unsigned int nbTones, - const double *tones); + // constructor with default values + AFSquelch(); virtual ~AFSquelch(); // setup the basic parameters and coefficients void setCoefficients( unsigned int N, //!< the algorithm "block" size unsigned int nbAvg, //!< averaging size - unsigned int SampleRate, //!< input signal sample rate - unsigned int _samplesAttack, //!< number of results before squelch opens - unsigned int _samplesDecay); //!< number of results keeping squelch open + unsigned int sampleRate, //!< input signal sample rate + unsigned int samplesAttack, //!< number of results before squelch opens + unsigned int samplesDecay, //!< number of results keeping squelch open + const double *tones); //!< center frequency of tones tested // set the detection threshold void setThreshold(double _threshold); From b38d2a2a65be7921f8e03afc2e72a52b75026a44 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 14 Apr 2018 21:46:53 +0200 Subject: [PATCH 266/956] SSB mod: Web API: settings and report implementation --- plugins/channeltx/modam/ammod.cpp | 4 +- plugins/channeltx/modssb/CMakeLists.txt | 2 + plugins/channeltx/modssb/ssbmod.cpp | 224 ++++++++++++++++++++++++ plugins/channeltx/modssb/ssbmod.h | 16 ++ plugins/channeltx/modssb/ssbmodgui.cpp | 15 ++ sdrbase/webapi/webapirequestmapper.cpp | 35 ++++ swagger/sdrangel/examples/tx_test.py | 10 ++ 7 files changed, 305 insertions(+), 1 deletion(-) diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index 81f21c521..4992632e2 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -511,7 +511,6 @@ bool AMMod::deserialize(const QByteArray& data) } } - int AMMod::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) @@ -542,6 +541,9 @@ int AMMod::webapiSettingsPutPatch( if (channelSettingsKeys.contains("modAFInput")) { settings.m_modAFInput = (AMModSettings::AMModInputAF) response.getAmModSettings()->getModAfInput(); } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getAmModSettings()->getAudioDeviceName(); + } if (channelSettingsKeys.contains("playLoop")) { settings.m_playLoop = response.getAmModSettings()->getPlayLoop() != 0; } diff --git a/plugins/channeltx/modssb/CMakeLists.txt b/plugins/channeltx/modssb/CMakeLists.txt index b8650ad5b..631900497 100644 --- a/plugins/channeltx/modssb/CMakeLists.txt +++ b/plugins/channeltx/modssb/CMakeLists.txt @@ -23,6 +23,7 @@ set(modssb_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) add_definitions(${QT_DEFINITIONS}) @@ -41,6 +42,7 @@ target_link_libraries(modssb ${QT_LIBRARIES} sdrbase sdrgui + swagger ) qt5_use_modules(modssb Core Widgets) diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index ba5223b17..8c341e4be 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -23,6 +23,10 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGSSBModReport.h" + #include "dsp/upchannelizer.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesource.h" @@ -866,3 +870,223 @@ bool SSBMod::deserialize(const QByteArray& data) return false; } } + +int SSBMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSsbModSettings(new SWGSDRangel::SWGSSBModSettings()); + response.getSsbModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int SSBMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + SSBModSettings settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getSsbModSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getSsbModSettings()->getBandwidth(); + } + if (channelSettingsKeys.contains("lowCutoff")) { + settings.m_lowCutoff = response.getSsbModSettings()->getLowCutoff(); + } + if (channelSettingsKeys.contains("usb")) { + settings.m_usb = response.getSsbModSettings()->getUsb() != 0; + } + if (channelSettingsKeys.contains("toneFrequency")) { + settings.m_toneFrequency = response.getSsbModSettings()->getToneFrequency(); + } + if (channelSettingsKeys.contains("volumeFactor")) { + settings.m_volumeFactor = response.getSsbModSettings()->getVolumeFactor(); + } + if (channelSettingsKeys.contains("spanLog2")) { + settings.m_spanLog2 = response.getSsbModSettings()->getSpanLog2(); + } + if (channelSettingsKeys.contains("audioBinaural")) { + settings.m_audioBinaural = response.getSsbModSettings()->getAudioBinaural() != 0; + } + if (channelSettingsKeys.contains("audioFlipChannels")) { + settings.m_audioFlipChannels = response.getSsbModSettings()->getAudioFlipChannels() != 0; + } + if (channelSettingsKeys.contains("dsb")) { + settings.m_dsb = response.getSsbModSettings()->getDsb() != 0; + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getSsbModSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("playLoop")) { + settings.m_playLoop = response.getSsbModSettings()->getPlayLoop() != 0; + } + if (channelSettingsKeys.contains("agc")) { + settings.m_agc = response.getSsbModSettings()->getAgc() != 0; + } + if (channelSettingsKeys.contains("agcOrder")) { + settings.m_agcOrder = response.getSsbModSettings()->getAgcOrder(); + } + if (channelSettingsKeys.contains("agcTime")) { + settings.m_agcTime = response.getSsbModSettings()->getAgcTime(); + } + if (channelSettingsKeys.contains("agcThresholdEnable")) { + settings.m_agcThresholdEnable = response.getSsbModSettings()->getAgcThresholdEnable() != 0; + } + if (channelSettingsKeys.contains("agcThreshold")) { + settings.m_agcThreshold = response.getSsbModSettings()->getAgcThreshold(); + } + if (channelSettingsKeys.contains("agcThresholdGate")) { + settings.m_agcThresholdGate = response.getSsbModSettings()->getAgcThresholdGate(); + } + if (channelSettingsKeys.contains("agcThresholdDelay")) { + settings.m_agcThresholdDelay = response.getSsbModSettings()->getAgcThresholdDelay(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getSsbModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getSsbModSettings()->getTitle(); + } + if (channelSettingsKeys.contains("modAFInput")) { + settings.m_modAFInput = (SSBModSettings::SSBModInputAF) response.getSsbModSettings()->getModAfInput(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getSsbModSettings()->getAudioDeviceName(); + } + + if (channelSettingsKeys.contains("cwKeyer")) + { + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getSsbModSettings()->getCwKeyer(); + CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings(); + + if (channelSettingsKeys.contains("cwKeyer.loop")) { + cwKeyerSettings.m_loop = apiCwKeyerSettings->getLoop() != 0; + } + if (channelSettingsKeys.contains("cwKeyer.mode")) { + cwKeyerSettings.m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode(); + } + if (channelSettingsKeys.contains("cwKeyer.text")) { + cwKeyerSettings.m_text = *apiCwKeyerSettings->getText(); + } + if (channelSettingsKeys.contains("cwKeyer.sampleRate")) { + cwKeyerSettings.m_sampleRate = apiCwKeyerSettings->getSampleRate(); + } + if (channelSettingsKeys.contains("cwKeyer.wpm")) { + cwKeyerSettings.m_wpm = apiCwKeyerSettings->getWpm(); + } + + m_cwKeyer.setLoop(cwKeyerSettings.m_loop); + m_cwKeyer.setMode(cwKeyerSettings.m_mode); + m_cwKeyer.setSampleRate(cwKeyerSettings.m_sampleRate); + m_cwKeyer.setText(cwKeyerSettings.m_text); + m_cwKeyer.setWPM(cwKeyerSettings.m_wpm); + + if (m_guiMessageQueue) // forward to GUI if any + { + CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force); + m_guiMessageQueue->push(msgCwKeyer); + } + } + + if (frequencyOffsetChanged) + { + SSBMod::MsgConfigureChannelizer *msgChan = SSBMod::MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureSSBMod *msg = MsgConfigureSSBMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSSBMod *msgToGUI = MsgConfigureSSBMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int SSBMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSsbModReport(new SWGSDRangel::SWGSSBModReport()); + response.getSsbModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void SSBMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBModSettings& settings) +{ + response.getSsbModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getSsbModSettings()->setBandwidth(settings.m_bandwidth); + response.getSsbModSettings()->setLowCutoff(settings.m_lowCutoff); + response.getSsbModSettings()->setUsb(settings.m_usb ? 1 : 0); + response.getSsbModSettings()->setToneFrequency(settings.m_toneFrequency); + response.getSsbModSettings()->setVolumeFactor(settings.m_volumeFactor); + response.getSsbModSettings()->setSpanLog2(settings.m_spanLog2); + response.getSsbModSettings()->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); + response.getSsbModSettings()->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); + response.getSsbModSettings()->setDsb(settings.m_dsb ? 1 : 0); + response.getSsbModSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getSsbModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0); + response.getSsbModSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getSsbModSettings()->setAgcOrder(settings.m_agcOrder); + response.getSsbModSettings()->setAgcTime(settings.m_agcTime); + response.getSsbModSettings()->setAgcThresholdEnable(settings.m_agcThresholdEnable ? 1 : 0); + response.getSsbModSettings()->setAgcThreshold(settings.m_agcThreshold); + response.getSsbModSettings()->setAgcThresholdGate(settings.m_agcThresholdGate); + response.getSsbModSettings()->setAgcThresholdDelay(settings.m_agcThresholdDelay); + response.getSsbModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getSsbModSettings()->getTitle()) { + *response.getSsbModSettings()->getTitle() = settings.m_title; + } else { + response.getSsbModSettings()->setTitle(new QString(settings.m_title)); + } + + response.getSsbModSettings()->setModAfInput((int) settings.m_modAFInput); + + if (response.getSsbModSettings()->getAudioDeviceName()) { + *response.getSsbModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getSsbModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + + if (!response.getSsbModSettings()->getCwKeyer()) { + response.getSsbModSettings()->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings); + } + + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getSsbModSettings()->getCwKeyer(); + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode((int) cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + + if (apiCwKeyerSettings->getText()) { + *apiCwKeyerSettings->getText() = cwKeyerSettings.m_text; + } else { + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + } + + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); +} + +void SSBMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getSsbModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getSsbModReport()->setAudioSampleRate(m_audioSampleRate); + response.getSsbModReport()->setChannelSampleRate(m_outputSampleRate); +} + diff --git a/plugins/channeltx/modssb/ssbmod.h b/plugins/channeltx/modssb/ssbmod.h index 5ce20caf3..4276f3b30 100644 --- a/plugins/channeltx/modssb/ssbmod.h +++ b/plugins/channeltx/modssb/ssbmod.h @@ -218,6 +218,20 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } double getMagSq() const { return m_magsq; } @@ -306,6 +320,8 @@ private: void modulateSample(); void openFileStream(); void seekFileStream(int seekPercentage); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index 38990c54c..cfe276cc8 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -117,6 +117,21 @@ bool SSBModGUI::handleMessage(const Message& message) applyBandwidths(); // will update spectrum details with new sample rate return true; } + else if (SSBMod::MsgConfigureSSBMod::match(message)) + { + const SSBMod::MsgConfigureSSBMod& cfg = (SSBMod::MsgConfigureSSBMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (CWKeyer::MsgConfigureCWKeyer::match(message)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) message; + ui->cwKeyerGUI->displaySettings(cfg.getSettings()); + return true; + } else { return false; diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index be9e4c99d..48e6e3b41 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1896,6 +1896,27 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "SSBMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject ssbModSettingsJsonObject = jsonObject["SSBModSettings"].toObject(); + channelSettingsKeys = ssbModSettingsJsonObject.keys(); + + if (channelSettingsKeys.contains("cwKeyer")) + { + QJsonObject cwKeyerSettingsJsonObject; + appendSettingsSubKeys(ssbModSettingsJsonObject, cwKeyerSettingsJsonObject, "cwKeyer", channelSettingsKeys); + } + + channelSettings.setSsbModSettings(new SWGSDRangel::SWGSSBModSettings()); + channelSettings.getSsbModSettings()->fromJsonObject(ssbModSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "WFMMod") { if (channelSettings.getTx() != 0) @@ -1998,6 +2019,20 @@ bool WebAPIRequestMapper::validateChannelReport( return false; } } + else if (*channelType == "SSBMod") + { + if (channelReport.getTx() != 0) + { + QJsonObject ssbModReportJsonObject = jsonObject["SSBModReport"].toObject(); + channelReportKeys = ssbModReportJsonObject.keys(); + channelReport.setSsbModReport(new SWGSDRangel::SWGSSBModReport()); + channelReport.getSsbModReport()->fromJsonObject(ssbModReportJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "WFMMod") { if (channelReport.getTx() != 0) diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index 56ea228b2..f0ac3a4db 100644 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -162,6 +162,16 @@ def setupChannel(options): settings["AMModSettings"]["toneFrequency"] = 600 settings["AMModSettings"]["modFactor"] = 0.9 settings["AMModSettings"]["rfBandwidth"] = 7500 + elif options.channel_id == "SSBMod": + settings["SSBModSettings"]["title"] = "Test SSB" + settings["SSBModSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["SSBModSettings"]["cwKeyer"]["text"] = "VVV DE F4EXB " + settings["SSBModSettings"]["cwKeyer"]["loop"] = 1 + settings["SSBModSettings"]["cwKeyer"]["mode"] = 1 # text + settings["SSBModSettings"]["modAFInput"] = 4 # CW text + settings["SSBModSettings"]["toneFrequency"] = 600 + settings["SSBModSettings"]["bandwidth"] = 1000 + settings["SSBModSettings"]["lowCut"] = 300 elif options.channel_id == "WFMMod": settings["WFMModSettings"]["title"] = "Test WFM" settings["WFMModSettings"]["inputFrequencyOffset"] = options.channel_freq From b21ea9ab2f9c576859c2c848175755a0d06d04ca Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 00:52:39 +0200 Subject: [PATCH 267/956] ATV modulator: Web API: settings and report implementation --- plugins/channeltx/modatv/CMakeLists.txt | 4 + plugins/channeltx/modatv/atvmod.cpp | 159 ++++++ plugins/channeltx/modatv/atvmod.h | 17 + plugins/channeltx/modatv/atvmodgui.cpp | 9 + plugins/channeltx/modatv/atvmodplugin.cpp | 2 +- plugins/channeltx/modatv/atvmodsettings.cpp | 2 - plugins/channeltx/modatv/atvmodsettings.h | 3 - sdrbase/resources/res.qrc | 2 + sdrbase/resources/webapi/doc/html2/index.html | 92 +++- .../webapi/doc/swagger/include/ATVMod.yaml | 60 +++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + sdrbase/webapi/webapirequestmapper.cpp | 28 + .../sdrangel/api/swagger/include/ATVMod.yaml | 60 +++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 92 +++- .../code/qt5/client/SWGATVModReport.cpp | 127 +++++ .../code/qt5/client/SWGATVModReport.h | 64 +++ .../code/qt5/client/SWGATVModSettings.cpp | 509 ++++++++++++++++++ .../code/qt5/client/SWGATVModSettings.h | 173 ++++++ .../code/qt5/client/SWGChannelReport.cpp | 23 + .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 + .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + swagger/sdrangel/examples/tx_test.py | 33 +- 25 files changed, 1496 insertions(+), 16 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/ATVMod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/ATVMod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGATVModReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGATVModSettings.h diff --git a/plugins/channeltx/modatv/CMakeLists.txt b/plugins/channeltx/modatv/CMakeLists.txt index 4d89f8fe7..21aad47e1 100644 --- a/plugins/channeltx/modatv/CMakeLists.txt +++ b/plugins/channeltx/modatv/CMakeLists.txt @@ -1,5 +1,7 @@ project(modatv) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(modatv_SOURCES atvmod.cpp atvmodgui.cpp @@ -24,6 +26,7 @@ include_directories( . ${OpenCV_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) add_definitions(${QT_DEFINITIONS}) @@ -43,6 +46,7 @@ target_link_libraries(modatv ${QT_LIBRARIES} sdrbase sdrgui + swagger ) qt5_use_modules(modatv Core Widgets) diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index 44a5187d9..726f80cdc 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -17,12 +17,17 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGATVModReport.h" + #include "opencv2/imgproc/imgproc.hpp" #include "dsp/upchannelizer.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" +#include "util/db.h" #include "atvmod.h" @@ -1177,3 +1182,157 @@ bool ATVMod::deserialize(const QByteArray& data) return false; } } + +int ATVMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAtvModSettings(new SWGSDRangel::SWGATVModSettings()); + response.getAtvModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int ATVMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + ATVModSettings settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getAtvModSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getAtvModSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("rfOppBandwidth")) { + settings.m_rfOppBandwidth = response.getAtvModSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("atvStd")) { + settings.m_atvStd = (ATVModSettings::ATVStd) response.getAtvModSettings()->getAtvStd(); + } + if (channelSettingsKeys.contains("nbLines")) { + settings.m_nbLines = response.getAtvModSettings()->getNbLines(); + } + if (channelSettingsKeys.contains("fps")) { + settings.m_fps = response.getAtvModSettings()->getFps(); + } + if (channelSettingsKeys.contains("atvModInput")) { + settings.m_atvModInput = (ATVModSettings::ATVModInput) response.getAtvModSettings()->getAtvModInput(); + } + if (channelSettingsKeys.contains("uniformLevel")) { + settings.m_uniformLevel = response.getAtvModSettings()->getUniformLevel(); + } + if (channelSettingsKeys.contains("atvModulation")) { + settings.m_atvModulation = (ATVModSettings::ATVModulation) response.getAtvModSettings()->getAtvModulation(); + } + if (channelSettingsKeys.contains("videoPlayLoop")) { + settings.m_videoPlayLoop = response.getAtvModSettings()->getVideoPlayLoop() != 0; + } + if (channelSettingsKeys.contains("videoPlay")) { + settings.m_videoPlay = response.getAtvModSettings()->getVideoPlay() != 0; + } + if (channelSettingsKeys.contains("cameraPlay")) { + settings.m_cameraPlay = response.getAtvModSettings()->getCameraPlay() != 0; + } + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getAtvModSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("invertedVideo")) { + settings.m_invertedVideo = response.getAtvModSettings()->getInvertedVideo() != 0; + } + if (channelSettingsKeys.contains("rfScalingFactor")) { + settings.m_rfScalingFactor = response.getAtvModSettings()->getRfScalingFactor(); + } + if (channelSettingsKeys.contains("fmExcursion")) { + settings.m_fmExcursion = response.getAtvModSettings()->getFmExcursion(); + } + if (channelSettingsKeys.contains("forceDecimator")) { + settings.m_forceDecimator = response.getAtvModSettings()->getForceDecimator() != 0; + } + if (channelSettingsKeys.contains("overlayText")) { + settings.m_overlayText = *response.getAtvModSettings()->getOverlayText(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAtvModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getAtvModSettings()->getTitle(); + } + + if (frequencyOffsetChanged) + { + ATVMod::MsgConfigureChannelizer *msgChan = ATVMod::MsgConfigureChannelizer::create( + settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureATVMod *msg = MsgConfigureATVMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureATVMod *msgToGUI = MsgConfigureATVMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int ATVMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAtvModReport(new SWGSDRangel::SWGATVModReport()); + response.getAtvModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void ATVMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const ATVModSettings& settings) +{ + response.getAtvModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getAtvModSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getAtvModSettings()->setRfOppBandwidth(settings.m_rfOppBandwidth); + response.getAtvModSettings()->setAtvStd(settings.m_atvStd); + response.getAtvModSettings()->setNbLines(settings.m_nbLines); + response.getAtvModSettings()->setFps(settings.m_fps); + response.getAtvModSettings()->setAtvModInput(settings.m_atvModInput); + response.getAtvModSettings()->setUniformLevel(settings.m_uniformLevel); + response.getAtvModSettings()->setAtvModulation(settings.m_atvModulation); + response.getAtvModSettings()->setVideoPlayLoop(settings.m_videoPlayLoop ? 1 : 0); + response.getAtvModSettings()->setVideoPlay(settings.m_videoPlay ? 1 : 0); + response.getAtvModSettings()->setCameraPlay(settings.m_cameraPlay ? 1 : 0); + response.getAtvModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getAtvModSettings()->setInvertedVideo(settings.m_invertedVideo ? 1 : 0); + response.getAtvModSettings()->setRfScalingFactor(settings.m_rfScalingFactor); + response.getAtvModSettings()->setFmExcursion(settings.m_fmExcursion); + response.getAtvModSettings()->setForceDecimator(settings.m_forceDecimator ? 1 : 0); + + if (response.getAtvModSettings()->getOverlayText()) { + *response.getAtvModSettings()->getOverlayText() = settings.m_overlayText; + } else { + response.getAtvModSettings()->setOverlayText(new QString(settings.m_overlayText)); + } + + response.getAtvModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getAtvModSettings()->getTitle()) { + *response.getAtvModSettings()->getTitle() = settings.m_title; + } else { + response.getAtvModSettings()->setTitle(new QString(settings.m_title)); + } +} + +void ATVMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getAtvModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getAtvModReport()->setChannelSampleRate(m_outputSampleRate); +} diff --git a/plugins/channeltx/modatv/atvmod.h b/plugins/channeltx/modatv/atvmod.h index ff3c1fab4..34802047e 100644 --- a/plugins/channeltx/modatv/atvmod.h +++ b/plugins/channeltx/modatv/atvmod.h @@ -411,6 +411,20 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + int getEffectiveSampleRate() const { return m_tvSampleRate; }; double getMagSq() const { return m_movingAverage.asDouble(); } void getCameraNumbers(std::vector& numbers); @@ -583,6 +597,9 @@ private: void resizeCamera(); void mixImageAndText(cv::Mat& image); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const ATVModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + inline void pullImageLine(Real& sample, bool noHSync = false) { if (m_horizontalCount < m_pointsPerSync) // sync pulse diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index 165f1d63a..cb13c662d 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -149,6 +149,15 @@ bool ATVModGUI::handleMessage(const Message& message) setRFFiltersSlidersRange(sampleRate); return true; } + else if (ATVMod::MsgConfigureATVMod::match(message)) + { + const ATVMod::MsgConfigureATVMod& cfg = (ATVMod::MsgConfigureATVMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } else { return false; diff --git a/plugins/channeltx/modatv/atvmodplugin.cpp b/plugins/channeltx/modatv/atvmodplugin.cpp index b663bf2e9..757f09c09 100644 --- a/plugins/channeltx/modatv/atvmodplugin.cpp +++ b/plugins/channeltx/modatv/atvmodplugin.cpp @@ -24,7 +24,7 @@ const PluginDescriptor ATVModPlugin::m_pluginDescriptor = { QString("ATV Modulator"), - QString("3.12.0"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modatv/atvmodsettings.cpp b/plugins/channeltx/modatv/atvmodsettings.cpp index dc32d0371..d7859453f 100644 --- a/plugins/channeltx/modatv/atvmodsettings.cpp +++ b/plugins/channeltx/modatv/atvmodsettings.cpp @@ -49,8 +49,6 @@ void ATVModSettings::resetToDefaults() m_overlayText = "ATV"; m_rgbColor = QColor(255, 255, 255).rgb(); m_title = "ATV Modulator"; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; } QByteArray ATVModSettings::serialize() const diff --git a/plugins/channeltx/modatv/atvmodsettings.h b/plugins/channeltx/modatv/atvmodsettings.h index c54e1d4eb..694d2f4db 100644 --- a/plugins/channeltx/modatv/atvmodsettings.h +++ b/plugins/channeltx/modatv/atvmodsettings.h @@ -79,9 +79,6 @@ struct ATVModSettings quint32 m_rgbColor; QString m_title; - QString m_udpAddress; - uint16_t m_udpPort; - Serializable *m_channelMarker; ATVModSettings(); diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index c1b051e5e..65b0a992b 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -10,8 +10,10 @@ webapi/doc/swagger/include/LimeSdr.yaml webapi/doc/swagger/include/AMDemod.yaml webapi/doc/swagger/include/AMMod.yaml + webapi/doc/swagger/include/ATVMod.yaml webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml + webapi/doc/swagger/include/SSBMod.yaml webapi/doc/swagger/include/WFMMod.yaml webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger-ui/swagger-ui.js.map diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index b04303e39..f2d0a64eb 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -818,6 +818,90 @@ margin-bottom: 20px; } }, "description" : "AMMod" +}; + defs.ATVModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "ATVMod" +}; + defs.ATVModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "rfOppBandwidth" : { + "type" : "number", + "format" : "float" + }, + "atvStd" : { + "type" : "integer" + }, + "nbLines" : { + "type" : "integer" + }, + "fps" : { + "type" : "integer" + }, + "atvModInput" : { + "type" : "integer" + }, + "uniformLevel" : { + "type" : "number", + "format" : "float" + }, + "atvModulation" : { + "type" : "integer" + }, + "videoPlayLoop" : { + "type" : "integer" + }, + "videoPlay" : { + "type" : "integer" + }, + "cameraPlay" : { + "type" : "integer" + }, + "channelMute" : { + "type" : "integer" + }, + "invertedVideo" : { + "type" : "integer" + }, + "rfScalingFactor" : { + "type" : "number", + "format" : "float" + }, + "fmExcursion" : { + "type" : "number", + "format" : "float" + }, + "forceDecimator" : { + "type" : "integer" + }, + "overlayText" : { + "type" : "string" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + } + }, + "description" : "ATVMod" }; defs.AirspyHFSettings = { "properties" : { @@ -1124,6 +1208,9 @@ margin-bottom: 20px; "AMModReport" : { "$ref" : "#/definitions/AMModReport" }, + "ATVModReport" : { + "$ref" : "#/definitions/ATVModReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1157,6 +1244,9 @@ margin-bottom: 20px; "AMModSettings" : { "$ref" : "#/definitions/AMModSettings" }, + "ATVModSettings" : { + "$ref" : "#/definitions/ATVModSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -20439,7 +20529,7 @@ except ApiException as e:
    - Generated 2018-04-12T23:48:02.735+02:00 + Generated 2018-04-14T22:10:55.895+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/ATVMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/ATVMod.yaml new file mode 100644 index 000000000..ba9bea1cd --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/ATVMod.yaml @@ -0,0 +1,60 @@ +ATVModSettings: + description: ATVMod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + rfOppBandwidth: + type: number + format: float + atvStd: + type: integer + nbLines: + type: integer + fps: + type: integer + atvModInput: + type: integer + uniformLevel: + type: number + format: float + atvModulation: + type: integer + videoPlayLoop: + type: integer + videoPlay: + type: integer + cameraPlay: + type: integer + channelMute: + type: integer + invertedVideo: + type: integer + rfScalingFactor: + type: number + format: float + fmExcursion: + type: number + format: float + forceDecimator: + type: integer + overlayText: + type: string + rgbColor: + type: integer + title: + type: string + +ATVModReport: + description: ATVMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + channelSampleRate: + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index b6e1a15ae..99b4f4af8 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1749,6 +1749,8 @@ definitions: $ref: "/doc/swagger/include/AMDemod.yaml#/AMDemodSettings" AMModSettings: $ref: "/doc/swagger/include/AMMod.yaml#/AMModSettings" + ATVModSettings: + $ref: "/doc/swagger/include/ATVMod.yaml#/ATVModSettings" NFMDemodSettings: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: @@ -1772,6 +1774,8 @@ definitions: $ref: "/doc/swagger/include/AMDemod.yaml#/AMDemodReport" AMModReport: $ref: "/doc/swagger/include/AMMod.yaml#/AMModReport" + ATVModReport: + $ref: "/doc/swagger/include/ATVMod.yaml#/ATVModReport" NFMDemodReport: $ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 48e6e3b41..dd7692add 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1861,6 +1861,20 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "ATVMod") + { + if (channelSettings.getTx() != 0) + { + QJsonObject atvModSettingsJsonObject = jsonObject["ATVModSettings"].toObject(); + channelSettingsKeys = atvModSettingsJsonObject.keys(); + channelSettings.setAtvModSettings(new SWGSDRangel::SWGATVModSettings()); + channelSettings.getAtvModSettings()->fromJsonObject(atvModSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "NFMDemod") { if (channelSettings.getTx() == 0) @@ -1991,6 +2005,20 @@ bool WebAPIRequestMapper::validateChannelReport( return false; } } + else if (*channelType == "ATVMod") + { + if (channelReport.getTx() != 0) + { + QJsonObject atvModReportJsonObject = jsonObject["ATVModReport"].toObject(); + channelReportKeys = atvModReportJsonObject.keys(); + channelReport.setAtvModReport(new SWGSDRangel::SWGATVModReport()); + channelReport.getAtvModReport()->fromJsonObject(atvModReportJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "NFMDemod") { if (channelReport.getTx() == 0) diff --git a/swagger/sdrangel/api/swagger/include/ATVMod.yaml b/swagger/sdrangel/api/swagger/include/ATVMod.yaml new file mode 100644 index 000000000..ba9bea1cd --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/ATVMod.yaml @@ -0,0 +1,60 @@ +ATVModSettings: + description: ATVMod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + rfOppBandwidth: + type: number + format: float + atvStd: + type: integer + nbLines: + type: integer + fps: + type: integer + atvModInput: + type: integer + uniformLevel: + type: number + format: float + atvModulation: + type: integer + videoPlayLoop: + type: integer + videoPlay: + type: integer + cameraPlay: + type: integer + channelMute: + type: integer + invertedVideo: + type: integer + rfScalingFactor: + type: number + format: float + fmExcursion: + type: number + format: float + forceDecimator: + type: integer + overlayText: + type: string + rgbColor: + type: integer + title: + type: string + +ATVModReport: + description: ATVMod + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + channelSampleRate: + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 50a63cb35..edf4acd66 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1749,6 +1749,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/AMDemod.yaml#/AMDemodSettings" AMModSettings: $ref: "http://localhost:8081/api/swagger/include/AMMod.yaml#/AMModSettings" + ATVModSettings: + $ref: "http://localhost:8081/api/swagger/include/ATVMod.yaml#/ATVModSettings" NFMDemodSettings: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: @@ -1772,6 +1774,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/AMDemod.yaml#/AMDemodReport" AMModReport: $ref: "http://localhost:8081/api/swagger/include/AMMod.yaml#/AMModReport" + ATVModReport: + $ref: "http://localhost:8081/api/swagger/include/ATVMod.yaml#/ATVModReport" NFMDemodReport: $ref: "http://localhost:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index b04303e39..f2d0a64eb 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -818,6 +818,90 @@ margin-bottom: 20px; } }, "description" : "AMMod" +}; + defs.ATVModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "ATVMod" +}; + defs.ATVModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "rfOppBandwidth" : { + "type" : "number", + "format" : "float" + }, + "atvStd" : { + "type" : "integer" + }, + "nbLines" : { + "type" : "integer" + }, + "fps" : { + "type" : "integer" + }, + "atvModInput" : { + "type" : "integer" + }, + "uniformLevel" : { + "type" : "number", + "format" : "float" + }, + "atvModulation" : { + "type" : "integer" + }, + "videoPlayLoop" : { + "type" : "integer" + }, + "videoPlay" : { + "type" : "integer" + }, + "cameraPlay" : { + "type" : "integer" + }, + "channelMute" : { + "type" : "integer" + }, + "invertedVideo" : { + "type" : "integer" + }, + "rfScalingFactor" : { + "type" : "number", + "format" : "float" + }, + "fmExcursion" : { + "type" : "number", + "format" : "float" + }, + "forceDecimator" : { + "type" : "integer" + }, + "overlayText" : { + "type" : "string" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + } + }, + "description" : "ATVMod" }; defs.AirspyHFSettings = { "properties" : { @@ -1124,6 +1208,9 @@ margin-bottom: 20px; "AMModReport" : { "$ref" : "#/definitions/AMModReport" }, + "ATVModReport" : { + "$ref" : "#/definitions/ATVModReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1157,6 +1244,9 @@ margin-bottom: 20px; "AMModSettings" : { "$ref" : "#/definitions/AMModSettings" }, + "ATVModSettings" : { + "$ref" : "#/definitions/ATVModSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -20439,7 +20529,7 @@ except ApiException as e:
    - Generated 2018-04-12T23:48:02.735+02:00 + Generated 2018-04-14T22:10:55.895+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp b/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp new file mode 100644 index 000000000..303fac6a6 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGATVModReport.cpp @@ -0,0 +1,127 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGATVModReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGATVModReport::SWGATVModReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGATVModReport::SWGATVModReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGATVModReport::~SWGATVModReport() { + this->cleanup(); +} + +void +SWGATVModReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGATVModReport::cleanup() { + + +} + +SWGATVModReport* +SWGATVModReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGATVModReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGATVModReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGATVModReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGATVModReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGATVModReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGATVModReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGATVModReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGATVModReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModReport.h b/swagger/sdrangel/code/qt5/client/SWGATVModReport.h new file mode 100644 index 000000000..df8661cdc --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGATVModReport.h @@ -0,0 +1,64 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGATVModReport.h + * + * ATVMod + */ + +#ifndef SWGATVModReport_H_ +#define SWGATVModReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGATVModReport: public SWGObject { +public: + SWGATVModReport(); + SWGATVModReport(QString* json); + virtual ~SWGATVModReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGATVModReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGATVModReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp new file mode 100644 index 000000000..7d67f490c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp @@ -0,0 +1,509 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGATVModSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGATVModSettings::SWGATVModSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGATVModSettings::SWGATVModSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + rf_opp_bandwidth = 0.0f; + m_rf_opp_bandwidth_isSet = false; + atv_std = 0; + m_atv_std_isSet = false; + nb_lines = 0; + m_nb_lines_isSet = false; + fps = 0; + m_fps_isSet = false; + atv_mod_input = 0; + m_atv_mod_input_isSet = false; + uniform_level = 0.0f; + m_uniform_level_isSet = false; + atv_modulation = 0; + m_atv_modulation_isSet = false; + video_play_loop = 0; + m_video_play_loop_isSet = false; + video_play = 0; + m_video_play_isSet = false; + camera_play = 0; + m_camera_play_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + inverted_video = 0; + m_inverted_video_isSet = false; + rf_scaling_factor = 0.0f; + m_rf_scaling_factor_isSet = false; + fm_excursion = 0.0f; + m_fm_excursion_isSet = false; + force_decimator = 0; + m_force_decimator_isSet = false; + overlay_text = nullptr; + m_overlay_text_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; +} + +SWGATVModSettings::~SWGATVModSettings() { + this->cleanup(); +} + +void +SWGATVModSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + rf_opp_bandwidth = 0.0f; + m_rf_opp_bandwidth_isSet = false; + atv_std = 0; + m_atv_std_isSet = false; + nb_lines = 0; + m_nb_lines_isSet = false; + fps = 0; + m_fps_isSet = false; + atv_mod_input = 0; + m_atv_mod_input_isSet = false; + uniform_level = 0.0f; + m_uniform_level_isSet = false; + atv_modulation = 0; + m_atv_modulation_isSet = false; + video_play_loop = 0; + m_video_play_loop_isSet = false; + video_play = 0; + m_video_play_isSet = false; + camera_play = 0; + m_camera_play_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + inverted_video = 0; + m_inverted_video_isSet = false; + rf_scaling_factor = 0.0f; + m_rf_scaling_factor_isSet = false; + fm_excursion = 0.0f; + m_fm_excursion_isSet = false; + force_decimator = 0; + m_force_decimator_isSet = false; + overlay_text = new QString(""); + m_overlay_text_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; +} + +void +SWGATVModSettings::cleanup() { + + + + + + + + + + + + + + + + + + if(overlay_text != nullptr) { + delete overlay_text; + } + + if(title != nullptr) { + delete title; + } +} + +SWGATVModSettings* +SWGATVModSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGATVModSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&rf_opp_bandwidth, pJson["rfOppBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&atv_std, pJson["atvStd"], "qint32", ""); + + ::SWGSDRangel::setValue(&nb_lines, pJson["nbLines"], "qint32", ""); + + ::SWGSDRangel::setValue(&fps, pJson["fps"], "qint32", ""); + + ::SWGSDRangel::setValue(&atv_mod_input, pJson["atvModInput"], "qint32", ""); + + ::SWGSDRangel::setValue(&uniform_level, pJson["uniformLevel"], "float", ""); + + ::SWGSDRangel::setValue(&atv_modulation, pJson["atvModulation"], "qint32", ""); + + ::SWGSDRangel::setValue(&video_play_loop, pJson["videoPlayLoop"], "qint32", ""); + + ::SWGSDRangel::setValue(&video_play, pJson["videoPlay"], "qint32", ""); + + ::SWGSDRangel::setValue(&camera_play, pJson["cameraPlay"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_mute, pJson["channelMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&inverted_video, pJson["invertedVideo"], "qint32", ""); + + ::SWGSDRangel::setValue(&rf_scaling_factor, pJson["rfScalingFactor"], "float", ""); + + ::SWGSDRangel::setValue(&fm_excursion, pJson["fmExcursion"], "float", ""); + + ::SWGSDRangel::setValue(&force_decimator, pJson["forceDecimator"], "qint32", ""); + + ::SWGSDRangel::setValue(&overlay_text, pJson["overlayText"], "QString", "QString"); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + +} + +QString +SWGATVModSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGATVModSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_rf_opp_bandwidth_isSet){ + obj->insert("rfOppBandwidth", QJsonValue(rf_opp_bandwidth)); + } + if(m_atv_std_isSet){ + obj->insert("atvStd", QJsonValue(atv_std)); + } + if(m_nb_lines_isSet){ + obj->insert("nbLines", QJsonValue(nb_lines)); + } + if(m_fps_isSet){ + obj->insert("fps", QJsonValue(fps)); + } + if(m_atv_mod_input_isSet){ + obj->insert("atvModInput", QJsonValue(atv_mod_input)); + } + if(m_uniform_level_isSet){ + obj->insert("uniformLevel", QJsonValue(uniform_level)); + } + if(m_atv_modulation_isSet){ + obj->insert("atvModulation", QJsonValue(atv_modulation)); + } + if(m_video_play_loop_isSet){ + obj->insert("videoPlayLoop", QJsonValue(video_play_loop)); + } + if(m_video_play_isSet){ + obj->insert("videoPlay", QJsonValue(video_play)); + } + if(m_camera_play_isSet){ + obj->insert("cameraPlay", QJsonValue(camera_play)); + } + if(m_channel_mute_isSet){ + obj->insert("channelMute", QJsonValue(channel_mute)); + } + if(m_inverted_video_isSet){ + obj->insert("invertedVideo", QJsonValue(inverted_video)); + } + if(m_rf_scaling_factor_isSet){ + obj->insert("rfScalingFactor", QJsonValue(rf_scaling_factor)); + } + if(m_fm_excursion_isSet){ + obj->insert("fmExcursion", QJsonValue(fm_excursion)); + } + if(m_force_decimator_isSet){ + obj->insert("forceDecimator", QJsonValue(force_decimator)); + } + if(overlay_text != nullptr && *overlay_text != QString("")){ + toJsonValue(QString("overlayText"), overlay_text, obj, QString("QString")); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + + return obj; +} + +qint64 +SWGATVModSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGATVModSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGATVModSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGATVModSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGATVModSettings::getRfOppBandwidth() { + return rf_opp_bandwidth; +} +void +SWGATVModSettings::setRfOppBandwidth(float rf_opp_bandwidth) { + this->rf_opp_bandwidth = rf_opp_bandwidth; + this->m_rf_opp_bandwidth_isSet = true; +} + +qint32 +SWGATVModSettings::getAtvStd() { + return atv_std; +} +void +SWGATVModSettings::setAtvStd(qint32 atv_std) { + this->atv_std = atv_std; + this->m_atv_std_isSet = true; +} + +qint32 +SWGATVModSettings::getNbLines() { + return nb_lines; +} +void +SWGATVModSettings::setNbLines(qint32 nb_lines) { + this->nb_lines = nb_lines; + this->m_nb_lines_isSet = true; +} + +qint32 +SWGATVModSettings::getFps() { + return fps; +} +void +SWGATVModSettings::setFps(qint32 fps) { + this->fps = fps; + this->m_fps_isSet = true; +} + +qint32 +SWGATVModSettings::getAtvModInput() { + return atv_mod_input; +} +void +SWGATVModSettings::setAtvModInput(qint32 atv_mod_input) { + this->atv_mod_input = atv_mod_input; + this->m_atv_mod_input_isSet = true; +} + +float +SWGATVModSettings::getUniformLevel() { + return uniform_level; +} +void +SWGATVModSettings::setUniformLevel(float uniform_level) { + this->uniform_level = uniform_level; + this->m_uniform_level_isSet = true; +} + +qint32 +SWGATVModSettings::getAtvModulation() { + return atv_modulation; +} +void +SWGATVModSettings::setAtvModulation(qint32 atv_modulation) { + this->atv_modulation = atv_modulation; + this->m_atv_modulation_isSet = true; +} + +qint32 +SWGATVModSettings::getVideoPlayLoop() { + return video_play_loop; +} +void +SWGATVModSettings::setVideoPlayLoop(qint32 video_play_loop) { + this->video_play_loop = video_play_loop; + this->m_video_play_loop_isSet = true; +} + +qint32 +SWGATVModSettings::getVideoPlay() { + return video_play; +} +void +SWGATVModSettings::setVideoPlay(qint32 video_play) { + this->video_play = video_play; + this->m_video_play_isSet = true; +} + +qint32 +SWGATVModSettings::getCameraPlay() { + return camera_play; +} +void +SWGATVModSettings::setCameraPlay(qint32 camera_play) { + this->camera_play = camera_play; + this->m_camera_play_isSet = true; +} + +qint32 +SWGATVModSettings::getChannelMute() { + return channel_mute; +} +void +SWGATVModSettings::setChannelMute(qint32 channel_mute) { + this->channel_mute = channel_mute; + this->m_channel_mute_isSet = true; +} + +qint32 +SWGATVModSettings::getInvertedVideo() { + return inverted_video; +} +void +SWGATVModSettings::setInvertedVideo(qint32 inverted_video) { + this->inverted_video = inverted_video; + this->m_inverted_video_isSet = true; +} + +float +SWGATVModSettings::getRfScalingFactor() { + return rf_scaling_factor; +} +void +SWGATVModSettings::setRfScalingFactor(float rf_scaling_factor) { + this->rf_scaling_factor = rf_scaling_factor; + this->m_rf_scaling_factor_isSet = true; +} + +float +SWGATVModSettings::getFmExcursion() { + return fm_excursion; +} +void +SWGATVModSettings::setFmExcursion(float fm_excursion) { + this->fm_excursion = fm_excursion; + this->m_fm_excursion_isSet = true; +} + +qint32 +SWGATVModSettings::getForceDecimator() { + return force_decimator; +} +void +SWGATVModSettings::setForceDecimator(qint32 force_decimator) { + this->force_decimator = force_decimator; + this->m_force_decimator_isSet = true; +} + +QString* +SWGATVModSettings::getOverlayText() { + return overlay_text; +} +void +SWGATVModSettings::setOverlayText(QString* overlay_text) { + this->overlay_text = overlay_text; + this->m_overlay_text_isSet = true; +} + +qint32 +SWGATVModSettings::getRgbColor() { + return rgb_color; +} +void +SWGATVModSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGATVModSettings::getTitle() { + return title; +} +void +SWGATVModSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + + +bool +SWGATVModSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_rf_opp_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_atv_std_isSet){ isObjectUpdated = true; break;} + if(m_nb_lines_isSet){ isObjectUpdated = true; break;} + if(m_fps_isSet){ isObjectUpdated = true; break;} + if(m_atv_mod_input_isSet){ isObjectUpdated = true; break;} + if(m_uniform_level_isSet){ isObjectUpdated = true; break;} + if(m_atv_modulation_isSet){ isObjectUpdated = true; break;} + if(m_video_play_loop_isSet){ isObjectUpdated = true; break;} + if(m_video_play_isSet){ isObjectUpdated = true; break;} + if(m_camera_play_isSet){ isObjectUpdated = true; break;} + if(m_channel_mute_isSet){ isObjectUpdated = true; break;} + if(m_inverted_video_isSet){ isObjectUpdated = true; break;} + if(m_rf_scaling_factor_isSet){ isObjectUpdated = true; break;} + if(m_fm_excursion_isSet){ isObjectUpdated = true; break;} + if(m_force_decimator_isSet){ isObjectUpdated = true; break;} + if(overlay_text != nullptr && *overlay_text != QString("")){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h new file mode 100644 index 000000000..0239f8887 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h @@ -0,0 +1,173 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGATVModSettings.h + * + * ATVMod + */ + +#ifndef SWGATVModSettings_H_ +#define SWGATVModSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGATVModSettings: public SWGObject { +public: + SWGATVModSettings(); + SWGATVModSettings(QString* json); + virtual ~SWGATVModSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGATVModSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getRfOppBandwidth(); + void setRfOppBandwidth(float rf_opp_bandwidth); + + qint32 getAtvStd(); + void setAtvStd(qint32 atv_std); + + qint32 getNbLines(); + void setNbLines(qint32 nb_lines); + + qint32 getFps(); + void setFps(qint32 fps); + + qint32 getAtvModInput(); + void setAtvModInput(qint32 atv_mod_input); + + float getUniformLevel(); + void setUniformLevel(float uniform_level); + + qint32 getAtvModulation(); + void setAtvModulation(qint32 atv_modulation); + + qint32 getVideoPlayLoop(); + void setVideoPlayLoop(qint32 video_play_loop); + + qint32 getVideoPlay(); + void setVideoPlay(qint32 video_play); + + qint32 getCameraPlay(); + void setCameraPlay(qint32 camera_play); + + qint32 getChannelMute(); + void setChannelMute(qint32 channel_mute); + + qint32 getInvertedVideo(); + void setInvertedVideo(qint32 inverted_video); + + float getRfScalingFactor(); + void setRfScalingFactor(float rf_scaling_factor); + + float getFmExcursion(); + void setFmExcursion(float fm_excursion); + + qint32 getForceDecimator(); + void setForceDecimator(qint32 force_decimator); + + QString* getOverlayText(); + void setOverlayText(QString* overlay_text); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float rf_opp_bandwidth; + bool m_rf_opp_bandwidth_isSet; + + qint32 atv_std; + bool m_atv_std_isSet; + + qint32 nb_lines; + bool m_nb_lines_isSet; + + qint32 fps; + bool m_fps_isSet; + + qint32 atv_mod_input; + bool m_atv_mod_input_isSet; + + float uniform_level; + bool m_uniform_level_isSet; + + qint32 atv_modulation; + bool m_atv_modulation_isSet; + + qint32 video_play_loop; + bool m_video_play_loop_isSet; + + qint32 video_play; + bool m_video_play_isSet; + + qint32 camera_play; + bool m_camera_play_isSet; + + qint32 channel_mute; + bool m_channel_mute_isSet; + + qint32 inverted_video; + bool m_inverted_video_isSet; + + float rf_scaling_factor; + bool m_rf_scaling_factor_isSet; + + float fm_excursion; + bool m_fm_excursion_isSet; + + qint32 force_decimator; + bool m_force_decimator_isSet; + + QString* overlay_text; + bool m_overlay_text_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + +}; + +} + +#endif /* SWGATVModSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 5740d89e8..88ffdf71a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -36,6 +36,8 @@ SWGChannelReport::SWGChannelReport() { m_am_demod_report_isSet = false; am_mod_report = nullptr; m_am_mod_report_isSet = false; + atv_mod_report = nullptr; + m_atv_mod_report_isSet = false; nfm_demod_report = nullptr; m_nfm_demod_report_isSet = false; nfm_mod_report = nullptr; @@ -60,6 +62,8 @@ SWGChannelReport::init() { m_am_demod_report_isSet = false; am_mod_report = new SWGAMModReport(); m_am_mod_report_isSet = false; + atv_mod_report = new SWGATVModReport(); + m_atv_mod_report_isSet = false; nfm_demod_report = new SWGNFMDemodReport(); m_nfm_demod_report_isSet = false; nfm_mod_report = new SWGNFMModReport(); @@ -82,6 +86,9 @@ SWGChannelReport::cleanup() { if(am_mod_report != nullptr) { delete am_mod_report; } + if(atv_mod_report != nullptr) { + delete atv_mod_report; + } if(nfm_demod_report != nullptr) { delete nfm_demod_report; } @@ -115,6 +122,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&am_mod_report, pJson["AMModReport"], "SWGAMModReport", "SWGAMModReport"); + ::SWGSDRangel::setValue(&atv_mod_report, pJson["ATVModReport"], "SWGATVModReport", "SWGATVModReport"); + ::SWGSDRangel::setValue(&nfm_demod_report, pJson["NFMDemodReport"], "SWGNFMDemodReport", "SWGNFMDemodReport"); ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); @@ -151,6 +160,9 @@ SWGChannelReport::asJsonObject() { if((am_mod_report != nullptr) && (am_mod_report->isSet())){ toJsonValue(QString("AMModReport"), am_mod_report, obj, QString("SWGAMModReport")); } + if((atv_mod_report != nullptr) && (atv_mod_report->isSet())){ + toJsonValue(QString("ATVModReport"), atv_mod_report, obj, QString("SWGATVModReport")); + } if((nfm_demod_report != nullptr) && (nfm_demod_report->isSet())){ toJsonValue(QString("NFMDemodReport"), nfm_demod_report, obj, QString("SWGNFMDemodReport")); } @@ -207,6 +219,16 @@ SWGChannelReport::setAmModReport(SWGAMModReport* am_mod_report) { this->m_am_mod_report_isSet = true; } +SWGATVModReport* +SWGChannelReport::getAtvModReport() { + return atv_mod_report; +} +void +SWGChannelReport::setAtvModReport(SWGATVModReport* atv_mod_report) { + this->atv_mod_report = atv_mod_report; + this->m_atv_mod_report_isSet = true; +} + SWGNFMDemodReport* SWGChannelReport::getNfmDemodReport() { return nfm_demod_report; @@ -256,6 +278,7 @@ SWGChannelReport::isSet(){ if(m_tx_isSet){ isObjectUpdated = true; break;} if(am_demod_report != nullptr && am_demod_report->isSet()){ isObjectUpdated = true; break;} if(am_mod_report != nullptr && am_mod_report->isSet()){ isObjectUpdated = true; break;} + if(atv_mod_report != nullptr && atv_mod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index a032b5278..ece9277c9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -24,6 +24,7 @@ #include "SWGAMDemodReport.h" #include "SWGAMModReport.h" +#include "SWGATVModReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" #include "SWGSSBModReport.h" @@ -60,6 +61,9 @@ public: SWGAMModReport* getAmModReport(); void setAmModReport(SWGAMModReport* am_mod_report); + SWGATVModReport* getAtvModReport(); + void setAtvModReport(SWGATVModReport* atv_mod_report); + SWGNFMDemodReport* getNfmDemodReport(); void setNfmDemodReport(SWGNFMDemodReport* nfm_demod_report); @@ -88,6 +92,9 @@ private: SWGAMModReport* am_mod_report; bool m_am_mod_report_isSet; + SWGATVModReport* atv_mod_report; + bool m_atv_mod_report_isSet; + SWGNFMDemodReport* nfm_demod_report; bool m_nfm_demod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 66c4f2e75..1f7f4737b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -36,6 +36,8 @@ SWGChannelSettings::SWGChannelSettings() { m_am_demod_settings_isSet = false; am_mod_settings = nullptr; m_am_mod_settings_isSet = false; + atv_mod_settings = nullptr; + m_atv_mod_settings_isSet = false; nfm_demod_settings = nullptr; m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; @@ -60,6 +62,8 @@ SWGChannelSettings::init() { m_am_demod_settings_isSet = false; am_mod_settings = new SWGAMModSettings(); m_am_mod_settings_isSet = false; + atv_mod_settings = new SWGATVModSettings(); + m_atv_mod_settings_isSet = false; nfm_demod_settings = new SWGNFMDemodSettings(); m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); @@ -82,6 +86,9 @@ SWGChannelSettings::cleanup() { if(am_mod_settings != nullptr) { delete am_mod_settings; } + if(atv_mod_settings != nullptr) { + delete atv_mod_settings; + } if(nfm_demod_settings != nullptr) { delete nfm_demod_settings; } @@ -115,6 +122,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&am_mod_settings, pJson["AMModSettings"], "SWGAMModSettings", "SWGAMModSettings"); + ::SWGSDRangel::setValue(&atv_mod_settings, pJson["ATVModSettings"], "SWGATVModSettings", "SWGATVModSettings"); + ::SWGSDRangel::setValue(&nfm_demod_settings, pJson["NFMDemodSettings"], "SWGNFMDemodSettings", "SWGNFMDemodSettings"); ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); @@ -151,6 +160,9 @@ SWGChannelSettings::asJsonObject() { if((am_mod_settings != nullptr) && (am_mod_settings->isSet())){ toJsonValue(QString("AMModSettings"), am_mod_settings, obj, QString("SWGAMModSettings")); } + if((atv_mod_settings != nullptr) && (atv_mod_settings->isSet())){ + toJsonValue(QString("ATVModSettings"), atv_mod_settings, obj, QString("SWGATVModSettings")); + } if((nfm_demod_settings != nullptr) && (nfm_demod_settings->isSet())){ toJsonValue(QString("NFMDemodSettings"), nfm_demod_settings, obj, QString("SWGNFMDemodSettings")); } @@ -207,6 +219,16 @@ SWGChannelSettings::setAmModSettings(SWGAMModSettings* am_mod_settings) { this->m_am_mod_settings_isSet = true; } +SWGATVModSettings* +SWGChannelSettings::getAtvModSettings() { + return atv_mod_settings; +} +void +SWGChannelSettings::setAtvModSettings(SWGATVModSettings* atv_mod_settings) { + this->atv_mod_settings = atv_mod_settings; + this->m_atv_mod_settings_isSet = true; +} + SWGNFMDemodSettings* SWGChannelSettings::getNfmDemodSettings() { return nfm_demod_settings; @@ -256,6 +278,7 @@ SWGChannelSettings::isSet(){ if(m_tx_isSet){ isObjectUpdated = true; break;} if(am_demod_settings != nullptr && am_demod_settings->isSet()){ isObjectUpdated = true; break;} if(am_mod_settings != nullptr && am_mod_settings->isSet()){ isObjectUpdated = true; break;} + if(atv_mod_settings != nullptr && atv_mod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 0c0e011e8..d56fa501f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -24,6 +24,7 @@ #include "SWGAMDemodSettings.h" #include "SWGAMModSettings.h" +#include "SWGATVModSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" #include "SWGSSBModSettings.h" @@ -60,6 +61,9 @@ public: SWGAMModSettings* getAmModSettings(); void setAmModSettings(SWGAMModSettings* am_mod_settings); + SWGATVModSettings* getAtvModSettings(); + void setAtvModSettings(SWGATVModSettings* atv_mod_settings); + SWGNFMDemodSettings* getNfmDemodSettings(); void setNfmDemodSettings(SWGNFMDemodSettings* nfm_demod_settings); @@ -88,6 +92,9 @@ private: SWGAMModSettings* am_mod_settings; bool m_am_mod_settings_isSet; + SWGATVModSettings* atv_mod_settings; + bool m_atv_mod_settings_isSet; + SWGNFMDemodSettings* nfm_demod_settings; bool m_nfm_demod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 7b7dffd6d..a97d80f3a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -18,6 +18,8 @@ #include "SWGAMDemodSettings.h" #include "SWGAMModReport.h" #include "SWGAMModSettings.h" +#include "SWGATVModReport.h" +#include "SWGATVModSettings.h" #include "SWGAirspyHFSettings.h" #include "SWGAudioDevices.h" #include "SWGAudioInputDevice.h" @@ -82,6 +84,12 @@ namespace SWGSDRangel { if(QString("SWGAMModSettings").compare(type) == 0) { return new SWGAMModSettings(); } + if(QString("SWGATVModReport").compare(type) == 0) { + return new SWGATVModReport(); + } + if(QString("SWGATVModSettings").compare(type) == 0) { + return new SWGATVModSettings(); + } if(QString("SWGAirspyHFSettings").compare(type) == 0) { return new SWGAirspyHFSettings(); } diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index f0ac3a4db..1d8c48a2b 100644 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -24,7 +24,8 @@ def getInputOptions(): parser.add_option("-C", "--channel-id", dest="channel_id", help="channel id", metavar="ID", type="string", default="NFMDemod") parser.add_option("-F", "--device-freq", dest="device_freq", help="device center frequency (kHz)", metavar="FREQ", type="int") parser.add_option("-f", "--channel-freq", dest="channel_freq", help="channel center frequency (Hz)", metavar="FREQ", type="int") - parser.add_option("-s", "--sample-rate", dest="sample_rate", help="host to device sample rate (kS/s)", metavar="RATE", type="int") + parser.add_option("-s", "--sample-rate", dest="sample_rate", help="host to device sample rate (S/s)", metavar="RATE", type="int") + parser.add_option("-l", "--log2-interp", dest="log2_interp", help="log2 of interpolation factor", metavar="RATE", type="int") parser.add_option("-A", "--antenna-path", dest="antenna_path", help="antenna path number", metavar="NUMBER", type="int") parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="CREATE", action="store_true", default=False) @@ -46,8 +47,11 @@ def getInputOptions(): options.channel_freq = 0 if options.sample_rate == None: - options.sample_rate = 2600 + options.sample_rate = 2600000 + if options.log2_interp == None: + options.log2_interp = 4 + if options.antenna_path == None: options.antenna_path = 1 @@ -103,19 +107,19 @@ def setupDevice(options): if options.device_hwid == "BladeRF": settings['bladeRFOutputSettings']['centerFrequency'] = options.device_freq*1000 - settings['bladeRFOutputSettings']['devSampleRate'] = options.sample_rate*1000 + settings['bladeRFOutputSettings']['devSampleRate'] = options.sample_rate settings['bladeRFOutputSettings']['vga1'] = -20 settings['bladeRFOutputSettings']['vga2'] = 6 settings['bladeRFOutputSettings']['bandwidth'] = 1500*1000 - settings['bladeRFOutputSettings']['log2Interp'] = 4 + settings['bladeRFOutputSettings']['log2Interp'] = options.log2_interp settings['bladeRFOutputSettings']['xb200'] = 1 # assume XB200 is mounted settings['bladeRFOutputSettings']['xb200Path'] = 1 if options.device_freq < 300000 else 0 settings['bladeRFOutputSettings']['xb200Filter'] = setupBladeRFXB200(options.device_freq) elif options.device_hwid == "LimeSDR": settings["limeSdrOutputSettings"]["antennaPath"] = options.antenna_path - settings["limeSdrOutputSettings"]["devSampleRate"] = options.sample_rate*1000 + settings["limeSdrOutputSettings"]["devSampleRate"] = options.sample_rate settings["limeSdrOutputSettings"]["log2HardInterp"] = 4 - settings["limeSdrOutputSettings"]["log2SoftInterp"] = 4 + settings["limeSdrOutputSettings"]["log2SoftInterp"] = options.log2_interp settings["limeSdrOutputSettings"]["centerFrequency"] = options.device_freq*1000 + 500000 settings["limeSdrOutputSettings"]["ncoEnable"] = 1 settings["limeSdrOutputSettings"]["ncoFrequency"] = -500000 @@ -125,9 +129,9 @@ def setupDevice(options): elif options.device_hwid == "HackRF": settings['hackRFOutputSettings']['LOppmTenths'] = -51 settings['hackRFOutputSettings']['centerFrequency'] = options.device_freq*1000 - settings['hackRFOutputSettings']['devSampleRate'] = options.sample_rate*1000 + settings['hackRFOutputSettings']['devSampleRate'] = options.sample_rate settings['hackRFOutputSettings']['lnaExt'] = 0 - settings['hackRFOutputSettings']['log2Interp'] = 4 + settings['hackRFOutputSettings']['log2Interp'] = options.log2_interp settings['hackRFOutputSettings']['vgaGain'] = 24 r = callAPI(deviceset_url + "/device/settings", "PATCH", None, settings, "Patch device settings") @@ -162,6 +166,19 @@ def setupChannel(options): settings["AMModSettings"]["toneFrequency"] = 600 settings["AMModSettings"]["modFactor"] = 0.9 settings["AMModSettings"]["rfBandwidth"] = 7500 + elif options.channel_id == "ATVMod": + settings["ATVModSettings"]["title"] = "Test ATV" + settings["ATVModSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["ATVModSettings"]["rfBandwidth"] = 30000 + settings["ATVModSettings"]["forceDecimator"] = 1 # This is to engage filter + settings["ATVModSettings"]["atvStd"] = 5 # ATVStdHSkip + settings["ATVModSettings"]["atvModInput"] = 1 # ATVModInputHBars + settings["ATVModSettings"]["atvModulation"] = 1 # ATVModulationFM + settings["ATVModSettings"]["fps"] = 2 + settings["ATVModSettings"]["nbLines"] = 90 + settings["ATVModSettings"]["uniformLevel"] = 1.0 # 100% white + settings["ATVModSettings"]["fmExcursion"] = 0.2 # FM excursion is 20% of channel bandwidth + settings["ATVModSettings"]["overlayText"] = "F4EXB" elif options.channel_id == "SSBMod": settings["SSBModSettings"]["title"] = "Test SSB" settings["SSBModSettings"]["inputFrequencyOffset"] = options.channel_freq From 875cf59fdac9f03f10a6d95bd03f9f0e16fefdef Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 01:34:46 +0200 Subject: [PATCH 268/956] ATV modulator: pass text overlay switch in settings --- plugins/channeltx/modatv/atvmod.cpp | 49 +++++++++------------ plugins/channeltx/modatv/atvmod.h | 22 --------- plugins/channeltx/modatv/atvmodgui.cpp | 4 +- plugins/channeltx/modatv/atvmodsettings.cpp | 1 + plugins/channeltx/modatv/atvmodsettings.h | 1 + 5 files changed, 26 insertions(+), 51 deletions(-) diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index 726f80cdc..28feca73e 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -43,7 +43,6 @@ MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureCameraIndex, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureCameraData, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportCameraData, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureOverlayText, Message) -MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureShowOverlayText, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportEffectiveSampleRate, Message) const QString ATVMod::m_channelIdURI = "sdrangel.channeltx.modatv"; @@ -73,7 +72,7 @@ ATVMod::ATVMod(DeviceSinkAPI *deviceAPI) : m_videoEOF(false), m_videoOK(false), m_cameraIndex(-1), - m_showOverlayText(false), + //m_showOverlayText(false), m_SSBFilter(0), m_SSBFilterBuffer(0), m_SSBFilterBufferIndex(0), @@ -325,7 +324,7 @@ void ATVMod::pullVideo(Real& sample) if (!colorFrame.empty()) // some frames may not come out properly { - if (m_showOverlayText) { + if (m_settings.m_showOverlayText) { mixImageAndText(colorFrame); } @@ -445,7 +444,7 @@ void ATVMod::pullVideo(Real& sample) if (!colorFrame.empty()) // some frames may not come out properly { - if (m_showOverlayText) { + if (m_settings.m_showOverlayText) { mixImageAndText(colorFrame); } @@ -620,28 +619,6 @@ bool ATVMod::handleMessage(const Message& cmd) m_overlayText = cfg.getOverlayText().toStdString(); return true; } - else if (MsgConfigureShowOverlayText::match(cmd)) - { - MsgConfigureShowOverlayText& cfg = (MsgConfigureShowOverlayText&) cmd; - bool showOverlayText = cfg.getShowOverlayText(); - - if (!m_imageFromFile.empty()) - { - m_imageFromFile.copyTo(m_imageOriginal); - - if (showOverlayText) { - qDebug("ATVMod::handleMessage: overlay text"); - mixImageAndText(m_imageOriginal); - } else{ - qDebug("ATVMod::handleMessage: clear text"); - } - - resizeImage(); - } - - m_showOverlayText = showOverlayText; - return true; - } else if (DSPSignalNotification::match(cmd)) { return true; @@ -825,7 +802,7 @@ void ATVMod::openImage(const QString& fileName) { m_imageFromFile.copyTo(m_imageOriginal); - if (m_showOverlayText) { + if (m_settings.m_showOverlayText) { mixImageAndText(m_imageOriginal); } @@ -1106,6 +1083,7 @@ void ATVMod::applySettings(const ATVModSettings& settings, bool force) << " m_rfScalingFactor: " << settings.m_rfScalingFactor << " m_fmExcursion: " << settings.m_fmExcursion << " m_forceDecimator: " << settings.m_forceDecimator + << " m_showOverlayText: " << settings.m_showOverlayText << " force: " << force; if ((settings.m_atvStd != m_settings.m_atvStd) @@ -1158,6 +1136,23 @@ void ATVMod::applySettings(const ATVModSettings& settings, bool force) m_settingsMutex.unlock(); } + if ((settings.m_showOverlayText != m_settings.m_showOverlayText) || force) + { + if (!m_imageFromFile.empty()) + { + m_imageFromFile.copyTo(m_imageOriginal); + + if (settings.m_showOverlayText) { + qDebug("ATVMod::applySettings: set overlay text"); + mixImageAndText(m_imageOriginal); + } else{ + qDebug("ATVMod::applySettings: clear overlay text"); + } + + resizeImage(); + } + } + m_settings = settings; } diff --git a/plugins/channeltx/modatv/atvmod.h b/plugins/channeltx/modatv/atvmod.h index 34802047e..bcf3796f9 100644 --- a/plugins/channeltx/modatv/atvmod.h +++ b/plugins/channeltx/modatv/atvmod.h @@ -345,27 +345,6 @@ public: { } }; - class MsgConfigureShowOverlayText : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - bool getShowOverlayText() const { return m_showOverlayText; } - - static MsgConfigureShowOverlayText* create(bool showOverlayText) - { - return new MsgConfigureShowOverlayText(showOverlayText); - } - - private: - bool m_showOverlayText; - - MsgConfigureShowOverlayText(bool showOverlayText) : - Message(), - m_showOverlayText(showOverlayText) - { } - }; - class MsgReportEffectiveSampleRate : public Message { MESSAGE_CLASS_DECLARATION @@ -555,7 +534,6 @@ private: int m_cameraIndex; //!< curent camera index in list of available cameras std::string m_overlayText; - bool m_showOverlayText; // Used for standard SSB fftfilt* m_SSBFilter; diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index cb13c662d..6324c57ac 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -563,8 +563,8 @@ void ATVModGUI::on_cameraManualFPS_valueChanged(int value) void ATVModGUI::on_overlayTextShow_toggled(bool checked) { - ATVMod::MsgConfigureShowOverlayText* message = ATVMod::MsgConfigureShowOverlayText::create(checked); - m_atvMod->getInputMessageQueue()->push(message); + m_settings.m_showOverlayText = checked; + applySettings(); } void ATVModGUI::on_overlayText_textEdited(const QString& arg1 __attribute__((unused))) diff --git a/plugins/channeltx/modatv/atvmodsettings.cpp b/plugins/channeltx/modatv/atvmodsettings.cpp index d7859453f..e74212334 100644 --- a/plugins/channeltx/modatv/atvmodsettings.cpp +++ b/plugins/channeltx/modatv/atvmodsettings.cpp @@ -46,6 +46,7 @@ void ATVModSettings::resetToDefaults() m_rfScalingFactor = 0.891235351562f * SDR_TX_SCALEF; // -1dB m_fmExcursion = 0.5f; // half bandwidth m_forceDecimator = false; + m_showOverlayText = false; m_overlayText = "ATV"; m_rgbColor = QColor(255, 255, 255).rgb(); m_title = "ATV Modulator"; diff --git a/plugins/channeltx/modatv/atvmodsettings.h b/plugins/channeltx/modatv/atvmodsettings.h index 694d2f4db..633c179fd 100644 --- a/plugins/channeltx/modatv/atvmodsettings.h +++ b/plugins/channeltx/modatv/atvmodsettings.h @@ -75,6 +75,7 @@ struct ATVModSettings float m_rfScalingFactor; //!< Scaling factor from +/-1 to +/-2^15 float m_fmExcursion; //!< FM excursion factor relative to full bandwidth bool m_forceDecimator; //!< Forces decimator even when channel and source sample rates are equal + bool m_showOverlayText; //!< Show overlay text on image QString m_overlayText; quint32 m_rgbColor; QString m_title; From 6320716eb99af4b22d8e6eb04a2f7fa013d7a21b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 02:22:54 +0200 Subject: [PATCH 269/956] ATV modulator: Web API: add possibility to set image and video files --- plugins/channeltx/modatv/atvmod.cpp | 65 +++++++++++++++--- plugins/channeltx/modatv/atvmod.h | 23 +------ plugins/channeltx/modatv/atvmodgui.cpp | 19 ++++-- sdrbase/resources/webapi/doc/html2/index.html | 11 ++- .../webapi/doc/swagger/include/ATVMod.yaml | 6 ++ .../sdrangel/api/swagger/include/ATVMod.yaml | 6 ++ swagger/sdrangel/code/html2/index.html | 11 ++- .../code/qt5/client/SWGATVModSettings.cpp | 67 +++++++++++++++++++ .../code/qt5/client/SWGATVModSettings.h | 18 +++++ swagger/sdrangel/examples/tx_test.py | 4 +- 10 files changed, 191 insertions(+), 39 deletions(-) diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index 28feca73e..c98025b09 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -42,7 +42,6 @@ MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportVideoFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureCameraIndex, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureCameraData, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportCameraData, Message) -MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureOverlayText, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportEffectiveSampleRate, Message) const QString ATVMod::m_channelIdURI = "sdrangel.channeltx.modatv"; @@ -613,12 +612,6 @@ bool ATVMod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureOverlayText::match(cmd)) - { - MsgConfigureOverlayText& cfg = (MsgConfigureOverlayText&) cmd; - m_overlayText = cfg.getOverlayText().toStdString(); - return true; - } else if (DSPSignalNotification::match(cmd)) { return true; @@ -800,6 +793,7 @@ void ATVMod::openImage(const QString& fileName) if (m_imageOK) { + m_imageFileName = fileName; m_imageFromFile.copyTo(m_imageOriginal); if (m_settings.m_showOverlayText) { @@ -808,6 +802,11 @@ void ATVMod::openImage(const QString& fileName) resizeImage(); } + else + { + m_imageFileName.clear(); + qDebug("ATVMod::openImage: cannot open image file %s", qPrintable(fileName)); + } } void ATVMod::openVideo(const QString& fileName) @@ -818,6 +817,7 @@ void ATVMod::openVideo(const QString& fileName) if (m_videoOK) { + m_videoFileName = fileName; m_videoFPS = m_video.get(CV_CAP_PROP_FPS); m_videoWidth = (int) m_video.get(CV_CAP_PROP_FRAME_WIDTH); m_videoHeight = (int) m_video.get(CV_CAP_PROP_FRAME_HEIGHT); @@ -845,7 +845,8 @@ void ATVMod::openVideo(const QString& fileName) } else { - qDebug("ATVMod::openVideo: cannot open video file"); + m_videoFileName.clear(); + qDebug("ATVMod::openVideo: cannot open video file %s", qPrintable(fileName)); } } @@ -1001,13 +1002,13 @@ void ATVMod::mixImageAndText(cv::Mat& image) int baseline=0; fontScale = fontScale < 4.0f ? 4.0f : fontScale; // minimum size - cv::Size textSize = cv::getTextSize(m_overlayText, fontFace, fontScale, thickness, &baseline); + cv::Size textSize = cv::getTextSize(m_settings.m_overlayText.toStdString(), fontFace, fontScale, thickness, &baseline); baseline += thickness; // position the text in the top left corner cv::Point textOrg(6, textSize.height+10); // then put the text itself - cv::putText(image, m_overlayText, textOrg, fontFace, fontScale, cv::Scalar::all(255*m_settings.m_uniformLevel), thickness, CV_AA); + cv::putText(image, m_settings.m_overlayText.toStdString(), textOrg, fontFace, fontScale, cv::Scalar::all(255*m_settings.m_uniformLevel), thickness, CV_AA); } void ATVMod::applyChannelSettings(int outputSampleRate, int inputFrequencyOffset, bool force) @@ -1250,6 +1251,9 @@ int ATVMod::webapiSettingsPutPatch( if (channelSettingsKeys.contains("forceDecimator")) { settings.m_forceDecimator = response.getAtvModSettings()->getForceDecimator() != 0; } + if (channelSettingsKeys.contains("showOverlayText")) { + settings.m_showOverlayText = response.getAtvModSettings()->getShowOverlayText() != 0; + } if (channelSettingsKeys.contains("overlayText")) { settings.m_overlayText = *response.getAtvModSettings()->getOverlayText(); } @@ -1276,6 +1280,34 @@ int ATVMod::webapiSettingsPutPatch( m_guiMessageQueue->push(msgToGUI); } + if (channelSettingsKeys.contains("imageFileName")) + { + MsgConfigureImageFileName *msg = MsgConfigureImageFileName::create( + *response.getAtvModSettings()->getImageFileName()); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureImageFileName *msgToGUI = MsgConfigureImageFileName::create( + *response.getAtvModSettings()->getImageFileName()); + m_guiMessageQueue->push(msgToGUI); + } + } + + if (channelSettingsKeys.contains("videoFileName")) + { + MsgConfigureVideoFileName *msg = MsgConfigureVideoFileName::create( + *response.getAtvModSettings()->getVideoFileName()); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureVideoFileName *msgToGUI = MsgConfigureVideoFileName::create( + *response.getAtvModSettings()->getVideoFileName()); + m_guiMessageQueue->push(msgToGUI); + } + } + webapiFormatChannelSettings(response, settings); return 200; @@ -1310,6 +1342,7 @@ void ATVMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon response.getAtvModSettings()->setRfScalingFactor(settings.m_rfScalingFactor); response.getAtvModSettings()->setFmExcursion(settings.m_fmExcursion); response.getAtvModSettings()->setForceDecimator(settings.m_forceDecimator ? 1 : 0); + response.getAtvModSettings()->setShowOverlayText(settings.m_showOverlayText ? 1 : 0); if (response.getAtvModSettings()->getOverlayText()) { *response.getAtvModSettings()->getOverlayText() = settings.m_overlayText; @@ -1324,6 +1357,18 @@ void ATVMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon } else { response.getAtvModSettings()->setTitle(new QString(settings.m_title)); } + + if (response.getAtvModSettings()->getImageFileName()) { + *response.getAtvModSettings()->getImageFileName() = m_imageFileName; + } else { + response.getAtvModSettings()->setImageFileName(new QString(m_imageFileName)); + } + + if (response.getAtvModSettings()->getVideoFileName()) { + *response.getAtvModSettings()->getVideoFileName() = m_videoFileName; + } else { + response.getAtvModSettings()->setVideoFileName(new QString(m_videoFileName)); + } } void ATVMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) diff --git a/plugins/channeltx/modatv/atvmod.h b/plugins/channeltx/modatv/atvmod.h index bcf3796f9..55ba9d780 100644 --- a/plugins/channeltx/modatv/atvmod.h +++ b/plugins/channeltx/modatv/atvmod.h @@ -324,27 +324,6 @@ public: { } }; - class MsgConfigureOverlayText : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - const QString& getOverlayText() const { return m_overlayText; } - - static MsgConfigureOverlayText* create(const QString& overlayText) - { - return new MsgConfigureOverlayText(overlayText); - } - - private: - QString m_overlayText; - - MsgConfigureOverlayText(const QString& overlayText) : - Message(), - m_overlayText(overlayText) - { } - }; - class MsgReportEffectiveSampleRate : public Message { MESSAGE_CLASS_DECLARATION @@ -534,6 +513,8 @@ private: int m_cameraIndex; //!< curent camera index in list of available cameras std::string m_overlayText; + QString m_imageFileName; + QString m_videoFileName; // Used for standard SSB fftfilt* m_SSBFilter; diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index 6324c57ac..107e5832e 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -158,6 +158,18 @@ bool ATVModGUI::handleMessage(const Message& message) blockApplySettings(false); return true; } + else if (ATVMod::MsgConfigureImageFileName::match(message)) + { + const ATVMod::MsgConfigureImageFileName& cfg = (ATVMod::MsgConfigureImageFileName&) message; + ui->imageFileText->setText(cfg.getFileName()); + return true; + } + else if (ATVMod::MsgConfigureVideoFileName::match(message)) + { + const ATVMod::MsgConfigureVideoFileName& cfg = (ATVMod::MsgConfigureVideoFileName&) message; + ui->videoFileText->setText(cfg.getFileName()); + return true; + } else { return false; @@ -570,8 +582,7 @@ void ATVModGUI::on_overlayTextShow_toggled(bool checked) void ATVModGUI::on_overlayText_textEdited(const QString& arg1 __attribute__((unused))) { m_settings.m_overlayText = arg1; - ATVMod::MsgConfigureOverlayText* message = ATVMod::MsgConfigureOverlayText::create(ui->overlayText->text()); - m_atvMod->getInputMessageQueue()->push(message); + applySettings(); } void ATVModGUI::configureImageFileName() @@ -731,9 +742,7 @@ void ATVModGUI::displaySettings() ui->uniformLevelText->setText(QString("%1").arg(ui->uniformLevel->value())); ui->overlayText->setText(m_settings.m_overlayText); - - ATVMod::MsgConfigureOverlayText* message = ATVMod::MsgConfigureOverlayText::create(ui->overlayText->text()); - m_atvMod->getInputMessageQueue()->push(message); + ui->overlayTextShow->setChecked(m_settings.m_showOverlayText); blockApplySettings(false); } diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index f2d0a64eb..7c6cbfa73 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -891,6 +891,9 @@ margin-bottom: 20px; "forceDecimator" : { "type" : "integer" }, + "showOverlayText" : { + "type" : "integer" + }, "overlayText" : { "type" : "string" }, @@ -899,6 +902,12 @@ margin-bottom: 20px; }, "title" : { "type" : "string" + }, + "imageFileName" : { + "type" : "string" + }, + "videoFileName" : { + "type" : "string" } }, "description" : "ATVMod" @@ -20529,7 +20538,7 @@ except ApiException as e:
    - Generated 2018-04-14T22:10:55.895+02:00 + Generated 2018-04-15T01:40:30.919+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/ATVMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/ATVMod.yaml index ba9bea1cd..58acb2067 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/ATVMod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/ATVMod.yaml @@ -41,12 +41,18 @@ ATVModSettings: format: float forceDecimator: type: integer + showOverlayText: + type: integer overlayText: type: string rgbColor: type: integer title: type: string + imageFileName: + type: string + videoFileName: + type: string ATVModReport: description: ATVMod diff --git a/swagger/sdrangel/api/swagger/include/ATVMod.yaml b/swagger/sdrangel/api/swagger/include/ATVMod.yaml index ba9bea1cd..58acb2067 100644 --- a/swagger/sdrangel/api/swagger/include/ATVMod.yaml +++ b/swagger/sdrangel/api/swagger/include/ATVMod.yaml @@ -41,12 +41,18 @@ ATVModSettings: format: float forceDecimator: type: integer + showOverlayText: + type: integer overlayText: type: string rgbColor: type: integer title: type: string + imageFileName: + type: string + videoFileName: + type: string ATVModReport: description: ATVMod diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index f2d0a64eb..7c6cbfa73 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -891,6 +891,9 @@ margin-bottom: 20px; "forceDecimator" : { "type" : "integer" }, + "showOverlayText" : { + "type" : "integer" + }, "overlayText" : { "type" : "string" }, @@ -899,6 +902,12 @@ margin-bottom: 20px; }, "title" : { "type" : "string" + }, + "imageFileName" : { + "type" : "string" + }, + "videoFileName" : { + "type" : "string" } }, "description" : "ATVMod" @@ -20529,7 +20538,7 @@ except ApiException as e:
    - Generated 2018-04-14T22:10:55.895+02:00 + Generated 2018-04-15T01:40:30.919+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp index 7d67f490c..9272797a1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.cpp @@ -62,12 +62,18 @@ SWGATVModSettings::SWGATVModSettings() { m_fm_excursion_isSet = false; force_decimator = 0; m_force_decimator_isSet = false; + show_overlay_text = 0; + m_show_overlay_text_isSet = false; overlay_text = nullptr; m_overlay_text_isSet = false; rgb_color = 0; m_rgb_color_isSet = false; title = nullptr; m_title_isSet = false; + image_file_name = nullptr; + m_image_file_name_isSet = false; + video_file_name = nullptr; + m_video_file_name_isSet = false; } SWGATVModSettings::~SWGATVModSettings() { @@ -110,12 +116,18 @@ SWGATVModSettings::init() { m_fm_excursion_isSet = false; force_decimator = 0; m_force_decimator_isSet = false; + show_overlay_text = 0; + m_show_overlay_text_isSet = false; overlay_text = new QString(""); m_overlay_text_isSet = false; rgb_color = 0; m_rgb_color_isSet = false; title = new QString(""); m_title_isSet = false; + image_file_name = new QString(""); + m_image_file_name_isSet = false; + video_file_name = new QString(""); + m_video_file_name_isSet = false; } void @@ -137,6 +149,7 @@ SWGATVModSettings::cleanup() { + if(overlay_text != nullptr) { delete overlay_text; } @@ -144,6 +157,12 @@ SWGATVModSettings::cleanup() { if(title != nullptr) { delete title; } + if(image_file_name != nullptr) { + delete image_file_name; + } + if(video_file_name != nullptr) { + delete video_file_name; + } } SWGATVModSettings* @@ -191,12 +210,18 @@ SWGATVModSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&force_decimator, pJson["forceDecimator"], "qint32", ""); + ::SWGSDRangel::setValue(&show_overlay_text, pJson["showOverlayText"], "qint32", ""); + ::SWGSDRangel::setValue(&overlay_text, pJson["overlayText"], "QString", "QString"); ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + ::SWGSDRangel::setValue(&image_file_name, pJson["imageFileName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&video_file_name, pJson["videoFileName"], "QString", "QString"); + } QString @@ -264,6 +289,9 @@ SWGATVModSettings::asJsonObject() { if(m_force_decimator_isSet){ obj->insert("forceDecimator", QJsonValue(force_decimator)); } + if(m_show_overlay_text_isSet){ + obj->insert("showOverlayText", QJsonValue(show_overlay_text)); + } if(overlay_text != nullptr && *overlay_text != QString("")){ toJsonValue(QString("overlayText"), overlay_text, obj, QString("QString")); } @@ -273,6 +301,12 @@ SWGATVModSettings::asJsonObject() { if(title != nullptr && *title != QString("")){ toJsonValue(QString("title"), title, obj, QString("QString")); } + if(image_file_name != nullptr && *image_file_name != QString("")){ + toJsonValue(QString("imageFileName"), image_file_name, obj, QString("QString")); + } + if(video_file_name != nullptr && *video_file_name != QString("")){ + toJsonValue(QString("videoFileName"), video_file_name, obj, QString("QString")); + } return obj; } @@ -447,6 +481,16 @@ SWGATVModSettings::setForceDecimator(qint32 force_decimator) { this->m_force_decimator_isSet = true; } +qint32 +SWGATVModSettings::getShowOverlayText() { + return show_overlay_text; +} +void +SWGATVModSettings::setShowOverlayText(qint32 show_overlay_text) { + this->show_overlay_text = show_overlay_text; + this->m_show_overlay_text_isSet = true; +} + QString* SWGATVModSettings::getOverlayText() { return overlay_text; @@ -477,6 +521,26 @@ SWGATVModSettings::setTitle(QString* title) { this->m_title_isSet = true; } +QString* +SWGATVModSettings::getImageFileName() { + return image_file_name; +} +void +SWGATVModSettings::setImageFileName(QString* image_file_name) { + this->image_file_name = image_file_name; + this->m_image_file_name_isSet = true; +} + +QString* +SWGATVModSettings::getVideoFileName() { + return video_file_name; +} +void +SWGATVModSettings::setVideoFileName(QString* video_file_name) { + this->video_file_name = video_file_name; + this->m_video_file_name_isSet = true; +} + bool SWGATVModSettings::isSet(){ @@ -499,9 +563,12 @@ SWGATVModSettings::isSet(){ if(m_rf_scaling_factor_isSet){ isObjectUpdated = true; break;} if(m_fm_excursion_isSet){ isObjectUpdated = true; break;} if(m_force_decimator_isSet){ isObjectUpdated = true; break;} + if(m_show_overlay_text_isSet){ isObjectUpdated = true; break;} if(overlay_text != nullptr && *overlay_text != QString("")){ isObjectUpdated = true; break;} if(m_rgb_color_isSet){ isObjectUpdated = true; break;} if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + if(image_file_name != nullptr && *image_file_name != QString("")){ isObjectUpdated = true; break;} + if(video_file_name != nullptr && *video_file_name != QString("")){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h index 0239f8887..9f6208b2a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGATVModSettings.h @@ -93,6 +93,9 @@ public: qint32 getForceDecimator(); void setForceDecimator(qint32 force_decimator); + qint32 getShowOverlayText(); + void setShowOverlayText(qint32 show_overlay_text); + QString* getOverlayText(); void setOverlayText(QString* overlay_text); @@ -102,6 +105,12 @@ public: QString* getTitle(); void setTitle(QString* title); + QString* getImageFileName(); + void setImageFileName(QString* image_file_name); + + QString* getVideoFileName(); + void setVideoFileName(QString* video_file_name); + virtual bool isSet() override; @@ -157,6 +166,9 @@ private: qint32 force_decimator; bool m_force_decimator_isSet; + qint32 show_overlay_text; + bool m_show_overlay_text_isSet; + QString* overlay_text; bool m_overlay_text_isSet; @@ -166,6 +178,12 @@ private: QString* title; bool m_title_isSet; + QString* image_file_name; + bool m_image_file_name_isSet; + + QString* video_file_name; + bool m_video_file_name_isSet; + }; } diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index 1d8c48a2b..3a1e45390 100644 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -171,14 +171,16 @@ def setupChannel(options): settings["ATVModSettings"]["inputFrequencyOffset"] = options.channel_freq settings["ATVModSettings"]["rfBandwidth"] = 30000 settings["ATVModSettings"]["forceDecimator"] = 1 # This is to engage filter + settings["ATVModSettings"]["imageFileName"] = "/home/f4exb/sdrangel/lena_4.3.png" settings["ATVModSettings"]["atvStd"] = 5 # ATVStdHSkip - settings["ATVModSettings"]["atvModInput"] = 1 # ATVModInputHBars + settings["ATVModSettings"]["atvModInput"] = 6 # ATVModInputImage settings["ATVModSettings"]["atvModulation"] = 1 # ATVModulationFM settings["ATVModSettings"]["fps"] = 2 settings["ATVModSettings"]["nbLines"] = 90 settings["ATVModSettings"]["uniformLevel"] = 1.0 # 100% white settings["ATVModSettings"]["fmExcursion"] = 0.2 # FM excursion is 20% of channel bandwidth settings["ATVModSettings"]["overlayText"] = "F4EXB" + settings["ATVModSettings"]["showOverlayText"] = 1 elif options.channel_id == "SSBMod": settings["SSBModSettings"]["title"] = "Test SSB" settings["SSBModSettings"]["inputFrequencyOffset"] = options.channel_freq From 88bb596baffb30f74f676d3d385d3cc63c0cd95b Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 10:19:23 +0200 Subject: [PATCH 270/956] HackRF output: allow display of SR down to 1 MS/s --- plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp b/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp index bee0e306a..fa4e31b0c 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp @@ -50,7 +50,7 @@ HackRFOutputGui::HackRFOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : ui->centerFrequency->setValueRange(7, 0U, 7250000U); ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->sampleRate->setValueRange(8, 2400000U, 20000000U); + ui->sampleRate->setValueRange(8, 1000000U, 20000000U); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); From 416f852861d2e00be1ca5983ea33271ba4157282 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 10:20:13 +0200 Subject: [PATCH 271/956] ATV modulator: Web API: fixed video settings display in the GUI --- plugins/channeltx/modatv/atvmodgui.cpp | 4 ++++ swagger/sdrangel/examples/tx_test.py | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index 107e5832e..eb34ff478 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -744,6 +744,10 @@ void ATVModGUI::displaySettings() ui->overlayText->setText(m_settings.m_overlayText); ui->overlayTextShow->setChecked(m_settings.m_showOverlayText); + ui->playCamera->setChecked(m_settings.m_cameraPlay); + ui->playVideo->setChecked(m_settings.m_videoPlay); + ui->playLoop->setChecked(m_settings.m_videoPlayLoop); + blockApplySettings(false); } diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index 3a1e45390..ea317d9ee 100644 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -28,6 +28,9 @@ def getInputOptions(): parser.add_option("-l", "--log2-interp", dest="log2_interp", help="log2 of interpolation factor", metavar="RATE", type="int") parser.add_option("-A", "--antenna-path", dest="antenna_path", help="antenna path number", metavar="NUMBER", type="int") parser.add_option("-c", "--create", dest="create", help="create a new device set", metavar="CREATE", action="store_true", default=False) + parser.add_option("--ppm", dest="lo_ppm", help="LO correction in ppm", metavar="FILENAME", type="float", default=0) + parser.add_option("--image", dest="image_file", help="image file for ATV modulator (sends image)", metavar="FILENAME", type="string") + parser.add_option("--video", dest="video_file", help="video file for ATV modulator (sends video)", metavar="FILENAME", type="string") (options, args) = parser.parse_args() @@ -127,7 +130,7 @@ def setupDevice(options): settings["limeSdrOutputSettings"]["lpfFIRBW"] = 100000 settings["limeSdrOutputSettings"]["lpfFIREnable"] = 1 elif options.device_hwid == "HackRF": - settings['hackRFOutputSettings']['LOppmTenths'] = -51 + settings['hackRFOutputSettings']['LOppmTenths'] = round(options.lo_ppm*10) settings['hackRFOutputSettings']['centerFrequency'] = options.device_freq*1000 settings['hackRFOutputSettings']['devSampleRate'] = options.sample_rate settings['hackRFOutputSettings']['lnaExt'] = 0 @@ -171,9 +174,19 @@ def setupChannel(options): settings["ATVModSettings"]["inputFrequencyOffset"] = options.channel_freq settings["ATVModSettings"]["rfBandwidth"] = 30000 settings["ATVModSettings"]["forceDecimator"] = 1 # This is to engage filter - settings["ATVModSettings"]["imageFileName"] = "/home/f4exb/sdrangel/lena_4.3.png" + + if options.image_file is not None: + settings["ATVModSettings"]["imageFileName"] = options.image_file + settings["ATVModSettings"]["atvModInput"] = 6 # m_atvModulation + elif options.video_file is not None: + settings["ATVModSettings"]["videoFileName"] = options.video_file + settings["ATVModSettings"]["atvModInput"] = 7 # ATVModInputVideo + settings["ATVModSettings"]["videoPlayLoop"] = 1 + settings["ATVModSettings"]["videoPlay"] = 1 + else: + settings["ATVModSettings"]["atvModInput"] = 1 # ATVModInputHBars + settings["ATVModSettings"]["atvStd"] = 5 # ATVStdHSkip - settings["ATVModSettings"]["atvModInput"] = 6 # ATVModInputImage settings["ATVModSettings"]["atvModulation"] = 1 # ATVModulationFM settings["ATVModSettings"]["fps"] = 2 settings["ATVModSettings"]["nbLines"] = 90 From 889712b457fc8e3adbb2d6cb930f5844ae71e157 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 10:38:32 +0200 Subject: [PATCH 272/956] ATV modulator: Web API: fixed set windows title --- plugins/channeltx/modatv/atvmodgui.cpp | 19 +++++++++++++++++++ plugins/channeltx/modatv/atvmodgui.h | 1 + plugins/channeltx/modwfm/wfmmodplugin.cpp | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index eb34ff478..cd22db2b2 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -29,6 +29,7 @@ #include "util/simpleserializer.h" #include "dsp/dspengine.h" #include "util/db.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" #include "ui_atvmodgui.h" @@ -603,6 +604,22 @@ void ATVModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rol { } +void ATVModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::ATVModGUI), @@ -621,6 +638,7 @@ ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_atvMod = (ATVMod*) channelTx; //new ATVMod(m_deviceUISet->m_deviceSinkAPI); m_atvMod->setMessageQueueToGUI(getInputMessageQueue()); @@ -696,6 +714,7 @@ void ATVModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); setChannelMarkerBandwidth(); m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only diff --git a/plugins/channeltx/modatv/atvmodgui.h b/plugins/channeltx/modatv/atvmodgui.h index 6d262dca4..0a637bb77 100644 --- a/plugins/channeltx/modatv/atvmodgui.h +++ b/plugins/channeltx/modatv/atvmodgui.h @@ -129,6 +129,7 @@ private slots: void on_overlayText_textEdited(const QString& arg1); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureImageFileName(); void configureVideoFileName(); diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp index 14ef9a471..6aca86f4e 100644 --- a/plugins/channeltx/modwfm/wfmmodplugin.cpp +++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor WFMModPlugin::m_pluginDescriptor = { QString("WFM Modulator"), - QString("3.12.0"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From 8723bfb74bb66b377adce6e686c6b5cde967476d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 10:42:51 +0200 Subject: [PATCH 273/956] AM modulator: Web API: fixed set windows title --- plugins/channeltx/modam/ammodgui.cpp | 19 +++++++++++++++++++ plugins/channeltx/modam/ammodgui.h | 1 + 2 files changed, 20 insertions(+) diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index 2df9076fe..ec611423c 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -33,6 +33,7 @@ #include "dsp/dspengine.h" #include "gui/crightclickenabler.h" #include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" AMModGUI* AMModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) @@ -272,6 +273,22 @@ void AMModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool roll { } +void AMModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::AMModGUI), @@ -288,6 +305,7 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampl ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_amMod = (AMMod*) channelTx; //new AMMod(m_deviceUISet->m_deviceSinkAPI); m_amMod->setMessageQueueToGUI(getInputMessageQueue()); @@ -364,6 +382,7 @@ void AMModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only diff --git a/plugins/channeltx/modam/ammodgui.h b/plugins/channeltx/modam/ammodgui.h index de1d79df4..9220f9886 100644 --- a/plugins/channeltx/modam/ammodgui.h +++ b/plugins/channeltx/modam/ammodgui.h @@ -107,6 +107,7 @@ private slots: void on_showFileDialog_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureFileName(); void audioSelect(); From 9a6069b6b2dad2178976e2f3803931016bc1c73e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 10:46:11 +0200 Subject: [PATCH 274/956] NFM modulator: Web API: fixed set windows title --- plugins/channeltx/modnfm/nfmmodgui.cpp | 19 +++++++++++++++++++ plugins/channeltx/modnfm/nfmmodgui.h | 1 + 2 files changed, 20 insertions(+) diff --git a/plugins/channeltx/modnfm/nfmmodgui.cpp b/plugins/channeltx/modnfm/nfmmodgui.cpp index 7f48c49ac..a5d72cfd5 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.cpp +++ b/plugins/channeltx/modnfm/nfmmodgui.cpp @@ -28,6 +28,7 @@ #include "dsp/dspengine.h" #include "gui/crightclickenabler.h" #include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" #include "ui_nfmmodgui.h" @@ -289,6 +290,22 @@ void NFMModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rol { } +void NFMModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::NFMModGUI), @@ -316,6 +333,7 @@ NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam blockApplySettings(false); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_nfmMod = (NFMMod*) channelTx; //new NFMMod(m_deviceUISet->m_deviceSinkAPI); m_nfmMod->setMessageQueueToGUI(getInputMessageQueue()); @@ -394,6 +412,7 @@ void NFMModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only diff --git a/plugins/channeltx/modnfm/nfmmodgui.h b/plugins/channeltx/modnfm/nfmmodgui.h index 8919352db..0e4a45ba7 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.h +++ b/plugins/channeltx/modnfm/nfmmodgui.h @@ -110,6 +110,7 @@ private slots: void on_ctcssOn_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureFileName(); void audioSelect(); From 1c1b073eba31595d6370ab7cd5771334f07c3d63 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 10:54:22 +0200 Subject: [PATCH 275/956] SSB modulator: Web API: fixed set windows title --- plugins/channeltx/modssb/ssbmodgui.cpp | 19 +++++++++++++++++++ plugins/channeltx/modssb/ssbmodgui.h | 1 + 2 files changed, 20 insertions(+) diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index cfe276cc8..a61ae5bc5 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -33,6 +33,7 @@ #include "dsp/dspcommands.h" #include "gui/crightclickenabler.h" #include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" SSBModGUI* SSBModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) @@ -362,6 +363,22 @@ void SSBModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rol { } +void SSBModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::SSBModGUI), @@ -379,6 +396,7 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_spectrumVis = new SpectrumVis(SDR_TX_SCALEF, ui->glSpectrum); m_ssbMod = (SSBMod*) channelTx; //new SSBMod(m_deviceUISet->m_deviceSinkAPI); @@ -576,6 +594,7 @@ void SSBModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); m_channelMarker.setBandwidth(m_settings.m_bandwidth * 2); m_channelMarker.setLowCutoff(m_settings.m_lowCutoff); diff --git a/plugins/channeltx/modssb/ssbmodgui.h b/plugins/channeltx/modssb/ssbmodgui.h index 7bd200dff..58b5191e9 100644 --- a/plugins/channeltx/modssb/ssbmodgui.h +++ b/plugins/channeltx/modssb/ssbmodgui.h @@ -126,6 +126,7 @@ private slots: void on_showFileDialog_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureFileName(); void audioSelect(); From 62998101d3fc67902d275665cb1b687f71de680d Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 10:59:01 +0200 Subject: [PATCH 276/956] WFM modulator: Web API: fixed set windows title --- plugins/channeltx/modwfm/wfmmodgui.cpp | 19 +++++++++++++++++++ plugins/channeltx/modwfm/wfmmodgui.h | 1 + 2 files changed, 20 insertions(+) diff --git a/plugins/channeltx/modwfm/wfmmodgui.cpp b/plugins/channeltx/modwfm/wfmmodgui.cpp index 1134abbc2..f6701772f 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.cpp +++ b/plugins/channeltx/modwfm/wfmmodgui.cpp @@ -30,6 +30,7 @@ #include "dsp/dspengine.h" #include "gui/crightclickenabler.h" #include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" #include "ui_wfmmodgui.h" @@ -278,6 +279,22 @@ void WFMModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rol { } +void WFMModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::WFMModGUI), @@ -305,6 +322,7 @@ WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam blockApplySettings(false); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_wfmMod = (WFMMod*) channelTx; //new WFMMod(m_deviceUISet->m_deviceSinkAPI); m_wfmMod->setMessageQueueToGUI(getInputMessageQueue()); @@ -383,6 +401,7 @@ void WFMModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only diff --git a/plugins/channeltx/modwfm/wfmmodgui.h b/plugins/channeltx/modwfm/wfmmodgui.h index 0c10e2a6d..b5f39e2f0 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.h +++ b/plugins/channeltx/modwfm/wfmmodgui.h @@ -119,6 +119,7 @@ private slots: void on_showFileDialog_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureFileName(); void audioSelect(); From 274e6c645dd7a4489b6e0fd6258d574321cfde90 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 18:25:22 +0200 Subject: [PATCH 277/956] UDP Sink: Web API: settings and report implementation --- plugins/channeltx/udpsink/CMakeLists.txt | 4 + plugins/channeltx/udpsink/udpsink.cpp | 158 ++++++ plugins/channeltx/udpsink/udpsink.h | 17 + plugins/channeltx/udpsink/udpsinkgui.cpp | 18 +- plugins/channeltx/udpsink/udpsinkgui.ui | 40 +- sdrbase/resources/res.qrc | 1 + sdrbase/resources/webapi/doc/html2/index.html | 93 +++- .../webapi/doc/swagger/include/UDPSink.yaml | 62 +++ .../resources/webapi/doc/swagger/swagger.yaml | 4 + sdrbase/webapi/webapirequestmapper.cpp | 28 + .../sdrangel/api/swagger/include/UDPSink.yaml | 62 +++ swagger/sdrangel/api/swagger/swagger.yaml | 4 + swagger/sdrangel/code/html2/index.html | 93 +++- .../code/qt5/client/SWGChannelReport.cpp | 23 + .../code/qt5/client/SWGChannelReport.h | 7 + .../code/qt5/client/SWGChannelSettings.cpp | 23 + .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGModelFactory.h | 8 + .../code/qt5/client/SWGUDPSinkReport.cpp | 127 +++++ .../code/qt5/client/SWGUDPSinkReport.h | 64 +++ .../code/qt5/client/SWGUDPSinkSettings.cpp | 488 ++++++++++++++++++ .../code/qt5/client/SWGUDPSinkSettings.h | 167 ++++++ swagger/sdrangel/examples/tx_test.py | 11 + 23 files changed, 1476 insertions(+), 33 deletions(-) create mode 100644 sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml create mode 100644 swagger/sdrangel/api/swagger/include/UDPSink.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h diff --git a/plugins/channeltx/udpsink/CMakeLists.txt b/plugins/channeltx/udpsink/CMakeLists.txt index b856fa4af..d278e2484 100644 --- a/plugins/channeltx/udpsink/CMakeLists.txt +++ b/plugins/channeltx/udpsink/CMakeLists.txt @@ -1,5 +1,7 @@ project(udpsink) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(udpsink_SOURCES udpsink.cpp udpsinkgui.cpp @@ -25,6 +27,7 @@ set(udpsink_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) @@ -44,6 +47,7 @@ target_link_libraries(modudpsink ${QT_LIBRARIES} sdrbase sdrgui + swagger ) qt5_use_modules(modudpsink Core Widgets Network) diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsink/udpsink.cpp index 467241bd6..41bbe2542 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsink/udpsink.cpp @@ -16,6 +16,10 @@ #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGUDPSinkReport.h" + #include "device/devicesinkapi.h" #include "dsp/upchannelizer.h" #include "dsp/threadedbasebandsamplesource.h" @@ -590,3 +594,157 @@ bool UDPSink::deserialize(const QByteArray& data) return false; } } + +int UDPSink::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); + response.getUdpSinkSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int UDPSink::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + UDPSinkSettings settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("sampleFormat")) { + settings.m_sampleFormat = (UDPSinkSettings::SampleFormat) response.getUdpSinkSettings()->getSampleFormat(); + } + if (channelSettingsKeys.contains("inputSampleRate")) { + settings.m_inputSampleRate = response.getUdpSinkSettings()->getInputSampleRate(); + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getUdpSinkSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getUdpSinkSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("lowCutoff")) { + settings.m_lowCutoff = response.getUdpSinkSettings()->getLowCutoff(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getUdpSinkSettings()->getFmDeviation(); + } + if (channelSettingsKeys.contains("amModFactor")) { + settings.m_amModFactor = response.getUdpSinkSettings()->getAmModFactor(); + } + if (channelSettingsKeys.contains("amModFactor")) { + settings.m_amModFactor = response.getUdpSinkSettings()->getAmModFactor(); + } + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getUdpSinkSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("gainIn")) { + settings.m_gainIn = response.getUdpSinkSettings()->getGainIn(); + } + if (channelSettingsKeys.contains("gainOut")) { + settings.m_gainOut = response.getUdpSinkSettings()->getGainOut(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getUdpSinkSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("squelchGate")) { + settings.m_squelchGate = response.getUdpSinkSettings()->getSquelchGate(); + } + if (channelSettingsKeys.contains("squelchEnabled")) { + settings.m_squelchEnabled = response.getUdpSinkSettings()->getSquelchEnabled() != 0; + } + if (channelSettingsKeys.contains("autoRWBalance")) { + settings.m_autoRWBalance = response.getUdpSinkSettings()->getAutoRwBalance() != 0; + } + if (channelSettingsKeys.contains("stereoInput")) { + settings.m_stereoInput = response.getUdpSinkSettings()->getStereoInput() != 0; + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getUdpSinkSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("udpAddress")) { + settings.m_udpAddress = *response.getUdpSinkSettings()->getUdpAddress(); + } + if (channelSettingsKeys.contains("udpPort")) { + settings.m_udpPort = response.getUdpSinkSettings()->getUdpPort(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getUdpSinkSettings()->getTitle(); + } + + if (frequencyOffsetChanged) + { + UDPSink::MsgConfigureChannelizer *msgChan = UDPSink::MsgConfigureChannelizer::create( + settings.m_inputSampleRate, + settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureUDPSink *msgToGUI = MsgConfigureUDPSink::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int UDPSink::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setUdpSinkReport(new SWGSDRangel::SWGUDPSinkReport()); + response.getUdpSinkReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void UDPSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings) +{ + response.getUdpSinkSettings()->setSampleFormat((int) settings.m_sampleFormat); + response.getUdpSinkSettings()->setInputSampleRate(settings.m_inputSampleRate); + response.getUdpSinkSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getUdpSinkSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getUdpSinkSettings()->setLowCutoff(settings.m_lowCutoff); + response.getUdpSinkSettings()->setFmDeviation(settings.m_fmDeviation); + response.getUdpSinkSettings()->setAmModFactor(settings.m_amModFactor); + response.getUdpSinkSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getUdpSinkSettings()->setGainIn(settings.m_gainIn); + response.getUdpSinkSettings()->setGainOut(settings.m_gainOut); + response.getUdpSinkSettings()->setSquelch(settings.m_squelch); + response.getUdpSinkSettings()->setSquelchGate(settings.m_squelchGate); + response.getUdpSinkSettings()->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); + response.getUdpSinkSettings()->setAutoRwBalance(settings.m_autoRWBalance ? 1 : 0); + response.getUdpSinkSettings()->setStereoInput(settings.m_stereoInput ? 1 : 0); + response.getUdpSinkSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getUdpSinkSettings()->getUdpAddress()) { + *response.getUdpSinkSettings()->getUdpAddress() = settings.m_udpAddress; + } else { + response.getUdpSinkSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + } + + response.getUdpSinkSettings()->setUdpPort(settings.m_udpPort); + + if (response.getUdpSinkSettings()->getTitle()) { + *response.getUdpSinkSettings()->getTitle() = settings.m_title; + } else { + response.getUdpSinkSettings()->setTitle(new QString(settings.m_title)); + } +} + +void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getUdpSinkReport()->setChannelSampleRate(m_outputSampleRate); +} diff --git a/plugins/channeltx/udpsink/udpsink.h b/plugins/channeltx/udpsink/udpsink.h index 910936b33..e9166156b 100644 --- a/plugins/channeltx/udpsink/udpsink.h +++ b/plugins/channeltx/udpsink/udpsink.h @@ -106,6 +106,20 @@ public: 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); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } double getInMagSq() const { return m_inMagsq; } int32_t getBufferGauge() const { return m_udpHandler.getBufferGauge(); } @@ -225,6 +239,9 @@ private: void calculateLevel(Real sample); void calculateLevel(Complex sample); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + inline void calculateSquelch(double value) { if ((!m_settings.m_squelchEnabled) || (value > m_squelch)) diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsink/udpsinkgui.cpp index dc8f8ab49..02c370346 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsink/udpsinkgui.cpp @@ -85,8 +85,19 @@ bool UDPSinkGUI::deserialize(const QByteArray& data) bool UDPSinkGUI::handleMessage(const Message& message __attribute__((unused))) { - qDebug() << "UDPSinkGUI::handleMessage"; - return false; + if (UDPSink::MsgConfigureUDPSink::match(message)) + { + const UDPSink::MsgConfigureUDPSink& cfg = (UDPSink::MsgConfigureUDPSink&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } } void UDPSinkGUI::handleSourceMessages() @@ -234,6 +245,9 @@ void UDPSinkGUI::displaySettings() ui->addressText->setText(tr("%1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); + ui->applyBtn->setEnabled(false); + ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); + blockApplySettings(false); } diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsink/udpsinkgui.ui index e046480bf..f7a3d3492 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsink/udpsinkgui.ui @@ -6,13 +6,13 @@ 0 0 - 388 + 382 403
    - 342 + 382 0 @@ -31,12 +31,12 @@ UDP Sample Sink - - -1 - UDP Sample Sink + + -1 + @@ -56,16 +56,7 @@ Settings - - 2 - - - 2 - - - 2 - - + 2 @@ -92,7 +83,7 @@ Input sample rate (S/s) - 0009999 + 0009999; 48000 @@ -122,7 +113,7 @@ Signal bandwidth (Hz) - 0009999 + 0009999; 32000 @@ -506,7 +497,7 @@ FM deviation in Hz - 00000 + 00000; 2500 @@ -532,7 +523,7 @@ Percentage of AM modulation - 000 + 000; 95 @@ -897,16 +888,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 65b0a992b..4b9543477 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -14,6 +14,7 @@ webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml webapi/doc/swagger/include/SSBMod.yaml + webapi/doc/swagger/include/UDPSink.yaml webapi/doc/swagger/include/WFMMod.yaml webapi/doc/swagger/include/RtlSdr.yaml webapi/doc/swagger-ui/swagger-ui.js.map diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 7c6cbfa73..c66efd72b 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1229,6 +1229,9 @@ margin-bottom: 20px; "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" }, + "UDPSinkReport" : { + "$ref" : "#/definitions/UDPSinkReport" + }, "WFMModReport" : { "$ref" : "#/definitions/WFMModReport" } @@ -1265,6 +1268,9 @@ margin-bottom: 20px; "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" }, + "UDPSinkSettings" : { + "$ref" : "#/definitions/UDPSinkSettings" + }, "WFMModSettings" : { "$ref" : "#/definitions/WFMModSettings" } @@ -2238,6 +2244,91 @@ margin-bottom: 20px; "type" : "string" } } +}; + defs.UDPSinkReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "UDPSink" +}; + defs.UDPSinkSettings = { + "properties" : { + "sampleFormat" : { + "type" : "integer" + }, + "inputSampleRate" : { + "type" : "number", + "format" : "float" + }, + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "lowCutoff" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "integer" + }, + "amModFactor" : { + "type" : "number", + "format" : "float" + }, + "channelMute" : { + "type" : "integer" + }, + "gainIn" : { + "type" : "number", + "format" : "float" + }, + "gainOut" : { + "type" : "number", + "format" : "float" + }, + "squelch" : { + "type" : "number", + "format" : "float" + }, + "squelchGate" : { + "type" : "number", + "format" : "float" + }, + "squelchEnabled" : { + "type" : "integer" + }, + "autoRWBalance" : { + "type" : "integer" + }, + "stereoInput" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "udpAddress" : { + "type" : "string" + }, + "udpPort" : { + "type" : "integer", + "format" : "uint16" + }, + "title" : { + "type" : "string" + } + }, + "description" : "UDPSink" }; defs.WFMModReport = { "properties" : { @@ -20538,7 +20629,7 @@ except ApiException as e:
    - Generated 2018-04-15T01:40:30.919+02:00 + Generated 2018-04-15T11:16:57.480+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml b/sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml new file mode 100644 index 000000000..32cb1d0a3 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/UDPSink.yaml @@ -0,0 +1,62 @@ +UDPSinkSettings: + description: UDPSink + properties: + sampleFormat: + type: integer + inputSampleRate: + type: number + format: float + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + lowCutoff: + type: number + format: float + fmDeviation: + type: integer + amModFactor: + type: number + format: float + channelMute: + type: integer + gainIn: + type: number + format: float + gainOut: + type: number + format: float + squelch: + type: number + format: float + squelchGate: + type: number + format: float + squelchEnabled: + type: integer + autoRWBalance: + type: integer + stereoInput: + type: integer + rgbColor: + type: integer + udpAddress: + type: string + udpPort: + type: integer + format: uint16 + title: + type: string + +UDPSinkReport: + description: UDPSink + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + channelSampleRate: + type: integer + \ No newline at end of file diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index 99b4f4af8..855f814f0 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -1757,6 +1757,8 @@ definitions: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModSettings" SSBModSettings: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModSettings" + UDPSinkSettings: + $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkSettings" WFMModSettings: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModSettings" @@ -1782,6 +1784,8 @@ definitions: $ref: "/doc/swagger/include/NFMMod.yaml#/NFMModReport" SSBModReport: $ref: "/doc/swagger/include/SSBMod.yaml#/SSBModReport" + UDPSinkReport: + $ref: "/doc/swagger/include/UDPSink.yaml#/UDPSinkReport" WFMModReport: $ref: "/doc/swagger/include/WFMMod.yaml#/WFMModReport" diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index dd7692add..342bb0285 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -1931,6 +1931,20 @@ bool WebAPIRequestMapper::validateChannelSettings( return false; } } + else if (*channelType == "UDPSink") + { + if (channelSettings.getTx() != 0) + { + QJsonObject udpSinkSettingsJsonObject = jsonObject["UDPSinkSettings"].toObject(); + channelSettingsKeys = udpSinkSettingsJsonObject.keys(); + channelSettings.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); + channelSettings.getUdpSinkSettings()->fromJsonObject(udpSinkSettingsJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "WFMMod") { if (channelSettings.getTx() != 0) @@ -2061,6 +2075,20 @@ bool WebAPIRequestMapper::validateChannelReport( return false; } } + else if (*channelType == "UDPSink") + { + if (channelReport.getTx() != 0) + { + QJsonObject udpSinkReportJsonObject = jsonObject["UDPSinkReport"].toObject(); + channelReportKeys = udpSinkReportJsonObject.keys(); + channelReport.setUdpSinkReport(new SWGSDRangel::SWGUDPSinkReport()); + channelReport.getUdpSinkReport()->fromJsonObject(udpSinkReportJsonObject); + return true; + } + else { + return false; + } + } else if (*channelType == "WFMMod") { if (channelReport.getTx() != 0) diff --git a/swagger/sdrangel/api/swagger/include/UDPSink.yaml b/swagger/sdrangel/api/swagger/include/UDPSink.yaml new file mode 100644 index 000000000..32cb1d0a3 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/UDPSink.yaml @@ -0,0 +1,62 @@ +UDPSinkSettings: + description: UDPSink + properties: + sampleFormat: + type: integer + inputSampleRate: + type: number + format: float + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + lowCutoff: + type: number + format: float + fmDeviation: + type: integer + amModFactor: + type: number + format: float + channelMute: + type: integer + gainIn: + type: number + format: float + gainOut: + type: number + format: float + squelch: + type: number + format: float + squelchGate: + type: number + format: float + squelchEnabled: + type: integer + autoRWBalance: + type: integer + stereoInput: + type: integer + rgbColor: + type: integer + udpAddress: + type: string + udpPort: + type: integer + format: uint16 + title: + type: string + +UDPSinkReport: + description: UDPSink + properties: + channelPowerDB: + description: power transmitted in channel (dB) + type: number + format: float + channelSampleRate: + type: integer + \ No newline at end of file diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index edf4acd66..77c6224a4 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -1757,6 +1757,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" SSBModSettings: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModSettings" + UDPSinkSettings: + $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkSettings" WFMModSettings: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModSettings" @@ -1782,6 +1784,8 @@ definitions: $ref: "http://localhost:8081/api/swagger/include/NFMMod.yaml#/NFMModReport" SSBModReport: $ref: "http://localhost:8081/api/swagger/include/SSBMod.yaml#/SSBModReport" + UDPSinkReport: + $ref: "http://localhost:8081/api/swagger/include/UDPSink.yaml#/UDPSinkReport" WFMModReport: $ref: "http://localhost:8081/api/swagger/include/WFMMod.yaml#/WFMModReport" diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 7c6cbfa73..c66efd72b 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1229,6 +1229,9 @@ margin-bottom: 20px; "SSBModReport" : { "$ref" : "#/definitions/SSBModReport" }, + "UDPSinkReport" : { + "$ref" : "#/definitions/UDPSinkReport" + }, "WFMModReport" : { "$ref" : "#/definitions/WFMModReport" } @@ -1265,6 +1268,9 @@ margin-bottom: 20px; "SSBModSettings" : { "$ref" : "#/definitions/SSBModSettings" }, + "UDPSinkSettings" : { + "$ref" : "#/definitions/UDPSinkSettings" + }, "WFMModSettings" : { "$ref" : "#/definitions/WFMModSettings" } @@ -2238,6 +2244,91 @@ margin-bottom: 20px; "type" : "string" } } +}; + defs.UDPSinkReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "UDPSink" +}; + defs.UDPSinkSettings = { + "properties" : { + "sampleFormat" : { + "type" : "integer" + }, + "inputSampleRate" : { + "type" : "number", + "format" : "float" + }, + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "lowCutoff" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "integer" + }, + "amModFactor" : { + "type" : "number", + "format" : "float" + }, + "channelMute" : { + "type" : "integer" + }, + "gainIn" : { + "type" : "number", + "format" : "float" + }, + "gainOut" : { + "type" : "number", + "format" : "float" + }, + "squelch" : { + "type" : "number", + "format" : "float" + }, + "squelchGate" : { + "type" : "number", + "format" : "float" + }, + "squelchEnabled" : { + "type" : "integer" + }, + "autoRWBalance" : { + "type" : "integer" + }, + "stereoInput" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "udpAddress" : { + "type" : "string" + }, + "udpPort" : { + "type" : "integer", + "format" : "uint16" + }, + "title" : { + "type" : "string" + } + }, + "description" : "UDPSink" }; defs.WFMModReport = { "properties" : { @@ -20538,7 +20629,7 @@ except ApiException as e:
    - Generated 2018-04-15T01:40:30.919+02:00 + Generated 2018-04-15T11:16:57.480+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 88ffdf71a..c103eaa9b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -44,6 +44,8 @@ SWGChannelReport::SWGChannelReport() { m_nfm_mod_report_isSet = false; ssb_mod_report = nullptr; m_ssb_mod_report_isSet = false; + udp_sink_report = nullptr; + m_udp_sink_report_isSet = false; wfm_mod_report = nullptr; m_wfm_mod_report_isSet = false; } @@ -70,6 +72,8 @@ SWGChannelReport::init() { m_nfm_mod_report_isSet = false; ssb_mod_report = new SWGSSBModReport(); m_ssb_mod_report_isSet = false; + udp_sink_report = new SWGUDPSinkReport(); + m_udp_sink_report_isSet = false; wfm_mod_report = new SWGWFMModReport(); m_wfm_mod_report_isSet = false; } @@ -98,6 +102,9 @@ SWGChannelReport::cleanup() { if(ssb_mod_report != nullptr) { delete ssb_mod_report; } + if(udp_sink_report != nullptr) { + delete udp_sink_report; + } if(wfm_mod_report != nullptr) { delete wfm_mod_report; } @@ -130,6 +137,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport"); + ::SWGSDRangel::setValue(&udp_sink_report, pJson["UDPSinkReport"], "SWGUDPSinkReport", "SWGUDPSinkReport"); + ::SWGSDRangel::setValue(&wfm_mod_report, pJson["WFMModReport"], "SWGWFMModReport", "SWGWFMModReport"); } @@ -172,6 +181,9 @@ SWGChannelReport::asJsonObject() { if((ssb_mod_report != nullptr) && (ssb_mod_report->isSet())){ toJsonValue(QString("SSBModReport"), ssb_mod_report, obj, QString("SWGSSBModReport")); } + if((udp_sink_report != nullptr) && (udp_sink_report->isSet())){ + toJsonValue(QString("UDPSinkReport"), udp_sink_report, obj, QString("SWGUDPSinkReport")); + } if((wfm_mod_report != nullptr) && (wfm_mod_report->isSet())){ toJsonValue(QString("WFMModReport"), wfm_mod_report, obj, QString("SWGWFMModReport")); } @@ -259,6 +271,16 @@ SWGChannelReport::setSsbModReport(SWGSSBModReport* ssb_mod_report) { this->m_ssb_mod_report_isSet = true; } +SWGUDPSinkReport* +SWGChannelReport::getUdpSinkReport() { + return udp_sink_report; +} +void +SWGChannelReport::setUdpSinkReport(SWGUDPSinkReport* udp_sink_report) { + this->udp_sink_report = udp_sink_report; + this->m_udp_sink_report_isSet = true; +} + SWGWFMModReport* SWGChannelReport::getWfmModReport() { return wfm_mod_report; @@ -282,6 +304,7 @@ SWGChannelReport::isSet(){ if(nfm_demod_report != nullptr && nfm_demod_report->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_report != nullptr && nfm_mod_report->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_report != nullptr && ssb_mod_report->isSet()){ isObjectUpdated = true; break;} + if(udp_sink_report != nullptr && udp_sink_report->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_report != nullptr && wfm_mod_report->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index ece9277c9..5236d39b3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -28,6 +28,7 @@ #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" #include "SWGSSBModReport.h" +#include "SWGUDPSinkReport.h" #include "SWGWFMModReport.h" #include @@ -73,6 +74,9 @@ public: SWGSSBModReport* getSsbModReport(); void setSsbModReport(SWGSSBModReport* ssb_mod_report); + SWGUDPSinkReport* getUdpSinkReport(); + void setUdpSinkReport(SWGUDPSinkReport* udp_sink_report); + SWGWFMModReport* getWfmModReport(); void setWfmModReport(SWGWFMModReport* wfm_mod_report); @@ -104,6 +108,9 @@ private: SWGSSBModReport* ssb_mod_report; bool m_ssb_mod_report_isSet; + SWGUDPSinkReport* udp_sink_report; + bool m_udp_sink_report_isSet; + SWGWFMModReport* wfm_mod_report; bool m_wfm_mod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 1f7f4737b..b85dd6121 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -44,6 +44,8 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_mod_settings_isSet = false; ssb_mod_settings = nullptr; m_ssb_mod_settings_isSet = false; + udp_sink_settings = nullptr; + m_udp_sink_settings_isSet = false; wfm_mod_settings = nullptr; m_wfm_mod_settings_isSet = false; } @@ -70,6 +72,8 @@ SWGChannelSettings::init() { m_nfm_mod_settings_isSet = false; ssb_mod_settings = new SWGSSBModSettings(); m_ssb_mod_settings_isSet = false; + udp_sink_settings = new SWGUDPSinkSettings(); + m_udp_sink_settings_isSet = false; wfm_mod_settings = new SWGWFMModSettings(); m_wfm_mod_settings_isSet = false; } @@ -98,6 +102,9 @@ SWGChannelSettings::cleanup() { if(ssb_mod_settings != nullptr) { delete ssb_mod_settings; } + if(udp_sink_settings != nullptr) { + delete udp_sink_settings; + } if(wfm_mod_settings != nullptr) { delete wfm_mod_settings; } @@ -130,6 +137,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ssb_mod_settings, pJson["SSBModSettings"], "SWGSSBModSettings", "SWGSSBModSettings"); + ::SWGSDRangel::setValue(&udp_sink_settings, pJson["UDPSinkSettings"], "SWGUDPSinkSettings", "SWGUDPSinkSettings"); + ::SWGSDRangel::setValue(&wfm_mod_settings, pJson["WFMModSettings"], "SWGWFMModSettings", "SWGWFMModSettings"); } @@ -172,6 +181,9 @@ SWGChannelSettings::asJsonObject() { if((ssb_mod_settings != nullptr) && (ssb_mod_settings->isSet())){ toJsonValue(QString("SSBModSettings"), ssb_mod_settings, obj, QString("SWGSSBModSettings")); } + if((udp_sink_settings != nullptr) && (udp_sink_settings->isSet())){ + toJsonValue(QString("UDPSinkSettings"), udp_sink_settings, obj, QString("SWGUDPSinkSettings")); + } if((wfm_mod_settings != nullptr) && (wfm_mod_settings->isSet())){ toJsonValue(QString("WFMModSettings"), wfm_mod_settings, obj, QString("SWGWFMModSettings")); } @@ -259,6 +271,16 @@ SWGChannelSettings::setSsbModSettings(SWGSSBModSettings* ssb_mod_settings) { this->m_ssb_mod_settings_isSet = true; } +SWGUDPSinkSettings* +SWGChannelSettings::getUdpSinkSettings() { + return udp_sink_settings; +} +void +SWGChannelSettings::setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings) { + this->udp_sink_settings = udp_sink_settings; + this->m_udp_sink_settings_isSet = true; +} + SWGWFMModSettings* SWGChannelSettings::getWfmModSettings() { return wfm_mod_settings; @@ -282,6 +304,7 @@ SWGChannelSettings::isSet(){ if(nfm_demod_settings != nullptr && nfm_demod_settings->isSet()){ isObjectUpdated = true; break;} if(nfm_mod_settings != nullptr && nfm_mod_settings->isSet()){ isObjectUpdated = true; break;} if(ssb_mod_settings != nullptr && ssb_mod_settings->isSet()){ isObjectUpdated = true; break;} + if(udp_sink_settings != nullptr && udp_sink_settings->isSet()){ isObjectUpdated = true; break;} if(wfm_mod_settings != nullptr && wfm_mod_settings->isSet()){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index d56fa501f..94fdab7d0 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -28,6 +28,7 @@ #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" #include "SWGSSBModSettings.h" +#include "SWGUDPSinkSettings.h" #include "SWGWFMModSettings.h" #include @@ -73,6 +74,9 @@ public: SWGSSBModSettings* getSsbModSettings(); void setSsbModSettings(SWGSSBModSettings* ssb_mod_settings); + SWGUDPSinkSettings* getUdpSinkSettings(); + void setUdpSinkSettings(SWGUDPSinkSettings* udp_sink_settings); + SWGWFMModSettings* getWfmModSettings(); void setWfmModSettings(SWGWFMModSettings* wfm_mod_settings); @@ -104,6 +108,9 @@ private: SWGSSBModSettings* ssb_mod_settings; bool m_ssb_mod_settings_isSet; + SWGUDPSinkSettings* udp_sink_settings; + bool m_udp_sink_settings_isSet; + SWGWFMModSettings* wfm_mod_settings; bool m_wfm_mod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index a97d80f3a..992ff5081 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -66,6 +66,8 @@ #include "SWGSSBModSettings.h" #include "SWGSamplingDevice.h" #include "SWGSuccessResponse.h" +#include "SWGUDPSinkReport.h" +#include "SWGUDPSinkSettings.h" #include "SWGWFMModReport.h" #include "SWGWFMModSettings.h" @@ -228,6 +230,12 @@ namespace SWGSDRangel { if(QString("SWGSuccessResponse").compare(type) == 0) { return new SWGSuccessResponse(); } + if(QString("SWGUDPSinkReport").compare(type) == 0) { + return new SWGUDPSinkReport(); + } + if(QString("SWGUDPSinkSettings").compare(type) == 0) { + return new SWGUDPSinkSettings(); + } if(QString("SWGWFMModReport").compare(type) == 0) { return new SWGWFMModReport(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp new file mode 100644 index 000000000..572ad9793 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.cpp @@ -0,0 +1,127 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGUDPSinkReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGUDPSinkReport::SWGUDPSinkReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGUDPSinkReport::SWGUDPSinkReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGUDPSinkReport::~SWGUDPSinkReport() { + this->cleanup(); +} + +void +SWGUDPSinkReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGUDPSinkReport::cleanup() { + + +} + +SWGUDPSinkReport* +SWGUDPSinkReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGUDPSinkReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGUDPSinkReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGUDPSinkReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGUDPSinkReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGUDPSinkReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGUDPSinkReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGUDPSinkReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGUDPSinkReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ isObjectUpdated = true; break;} + if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h new file mode 100644 index 000000000..21f7a184b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkReport.h @@ -0,0 +1,64 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGUDPSinkReport.h + * + * UDPSink + */ + +#ifndef SWGUDPSinkReport_H_ +#define SWGUDPSinkReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGUDPSinkReport: public SWGObject { +public: + SWGUDPSinkReport(); + SWGUDPSinkReport(QString* json); + virtual ~SWGUDPSinkReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGUDPSinkReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGUDPSinkReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp new file mode 100644 index 000000000..312cbf7b1 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.cpp @@ -0,0 +1,488 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGUDPSinkSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGUDPSinkSettings::SWGUDPSinkSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGUDPSinkSettings::SWGUDPSinkSettings() { + sample_format = 0; + m_sample_format_isSet = false; + input_sample_rate = 0.0f; + m_input_sample_rate_isSet = false; + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + low_cutoff = 0.0f; + m_low_cutoff_isSet = false; + fm_deviation = 0; + m_fm_deviation_isSet = false; + am_mod_factor = 0.0f; + m_am_mod_factor_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + gain_in = 0.0f; + m_gain_in_isSet = false; + gain_out = 0.0f; + m_gain_out_isSet = false; + squelch = 0.0f; + m_squelch_isSet = false; + squelch_gate = 0.0f; + m_squelch_gate_isSet = false; + squelch_enabled = 0; + m_squelch_enabled_isSet = false; + auto_rw_balance = 0; + m_auto_rw_balance_isSet = false; + stereo_input = 0; + m_stereo_input_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + udp_address = nullptr; + m_udp_address_isSet = false; + udp_port = 0; + m_udp_port_isSet = false; + title = nullptr; + m_title_isSet = false; +} + +SWGUDPSinkSettings::~SWGUDPSinkSettings() { + this->cleanup(); +} + +void +SWGUDPSinkSettings::init() { + sample_format = 0; + m_sample_format_isSet = false; + input_sample_rate = 0.0f; + m_input_sample_rate_isSet = false; + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + low_cutoff = 0.0f; + m_low_cutoff_isSet = false; + fm_deviation = 0; + m_fm_deviation_isSet = false; + am_mod_factor = 0.0f; + m_am_mod_factor_isSet = false; + channel_mute = 0; + m_channel_mute_isSet = false; + gain_in = 0.0f; + m_gain_in_isSet = false; + gain_out = 0.0f; + m_gain_out_isSet = false; + squelch = 0.0f; + m_squelch_isSet = false; + squelch_gate = 0.0f; + m_squelch_gate_isSet = false; + squelch_enabled = 0; + m_squelch_enabled_isSet = false; + auto_rw_balance = 0; + m_auto_rw_balance_isSet = false; + stereo_input = 0; + m_stereo_input_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + udp_address = new QString(""); + m_udp_address_isSet = false; + udp_port = 0; + m_udp_port_isSet = false; + title = new QString(""); + m_title_isSet = false; +} + +void +SWGUDPSinkSettings::cleanup() { + + + + + + + + + + + + + + + + + if(udp_address != nullptr) { + delete udp_address; + } + + if(title != nullptr) { + delete title; + } +} + +SWGUDPSinkSettings* +SWGUDPSinkSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGUDPSinkSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&sample_format, pJson["sampleFormat"], "qint32", ""); + + ::SWGSDRangel::setValue(&input_sample_rate, pJson["inputSampleRate"], "float", ""); + + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&low_cutoff, pJson["lowCutoff"], "float", ""); + + ::SWGSDRangel::setValue(&fm_deviation, pJson["fmDeviation"], "qint32", ""); + + ::SWGSDRangel::setValue(&am_mod_factor, pJson["amModFactor"], "float", ""); + + ::SWGSDRangel::setValue(&channel_mute, pJson["channelMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&gain_in, pJson["gainIn"], "float", ""); + + ::SWGSDRangel::setValue(&gain_out, pJson["gainOut"], "float", ""); + + ::SWGSDRangel::setValue(&squelch, pJson["squelch"], "float", ""); + + ::SWGSDRangel::setValue(&squelch_gate, pJson["squelchGate"], "float", ""); + + ::SWGSDRangel::setValue(&squelch_enabled, pJson["squelchEnabled"], "qint32", ""); + + ::SWGSDRangel::setValue(&auto_rw_balance, pJson["autoRWBalance"], "qint32", ""); + + ::SWGSDRangel::setValue(&stereo_input, pJson["stereoInput"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&udp_address, pJson["udpAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&udp_port, pJson["udpPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + +} + +QString +SWGUDPSinkSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGUDPSinkSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_sample_format_isSet){ + obj->insert("sampleFormat", QJsonValue(sample_format)); + } + if(m_input_sample_rate_isSet){ + obj->insert("inputSampleRate", QJsonValue(input_sample_rate)); + } + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_low_cutoff_isSet){ + obj->insert("lowCutoff", QJsonValue(low_cutoff)); + } + if(m_fm_deviation_isSet){ + obj->insert("fmDeviation", QJsonValue(fm_deviation)); + } + if(m_am_mod_factor_isSet){ + obj->insert("amModFactor", QJsonValue(am_mod_factor)); + } + if(m_channel_mute_isSet){ + obj->insert("channelMute", QJsonValue(channel_mute)); + } + if(m_gain_in_isSet){ + obj->insert("gainIn", QJsonValue(gain_in)); + } + if(m_gain_out_isSet){ + obj->insert("gainOut", QJsonValue(gain_out)); + } + if(m_squelch_isSet){ + obj->insert("squelch", QJsonValue(squelch)); + } + if(m_squelch_gate_isSet){ + obj->insert("squelchGate", QJsonValue(squelch_gate)); + } + if(m_squelch_enabled_isSet){ + obj->insert("squelchEnabled", QJsonValue(squelch_enabled)); + } + if(m_auto_rw_balance_isSet){ + obj->insert("autoRWBalance", QJsonValue(auto_rw_balance)); + } + if(m_stereo_input_isSet){ + obj->insert("stereoInput", QJsonValue(stereo_input)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(udp_address != nullptr && *udp_address != QString("")){ + toJsonValue(QString("udpAddress"), udp_address, obj, QString("QString")); + } + if(m_udp_port_isSet){ + obj->insert("udpPort", QJsonValue(udp_port)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + + return obj; +} + +qint32 +SWGUDPSinkSettings::getSampleFormat() { + return sample_format; +} +void +SWGUDPSinkSettings::setSampleFormat(qint32 sample_format) { + this->sample_format = sample_format; + this->m_sample_format_isSet = true; +} + +float +SWGUDPSinkSettings::getInputSampleRate() { + return input_sample_rate; +} +void +SWGUDPSinkSettings::setInputSampleRate(float input_sample_rate) { + this->input_sample_rate = input_sample_rate; + this->m_input_sample_rate_isSet = true; +} + +qint64 +SWGUDPSinkSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGUDPSinkSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGUDPSinkSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGUDPSinkSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGUDPSinkSettings::getLowCutoff() { + return low_cutoff; +} +void +SWGUDPSinkSettings::setLowCutoff(float low_cutoff) { + this->low_cutoff = low_cutoff; + this->m_low_cutoff_isSet = true; +} + +qint32 +SWGUDPSinkSettings::getFmDeviation() { + return fm_deviation; +} +void +SWGUDPSinkSettings::setFmDeviation(qint32 fm_deviation) { + this->fm_deviation = fm_deviation; + this->m_fm_deviation_isSet = true; +} + +float +SWGUDPSinkSettings::getAmModFactor() { + return am_mod_factor; +} +void +SWGUDPSinkSettings::setAmModFactor(float am_mod_factor) { + this->am_mod_factor = am_mod_factor; + this->m_am_mod_factor_isSet = true; +} + +qint32 +SWGUDPSinkSettings::getChannelMute() { + return channel_mute; +} +void +SWGUDPSinkSettings::setChannelMute(qint32 channel_mute) { + this->channel_mute = channel_mute; + this->m_channel_mute_isSet = true; +} + +float +SWGUDPSinkSettings::getGainIn() { + return gain_in; +} +void +SWGUDPSinkSettings::setGainIn(float gain_in) { + this->gain_in = gain_in; + this->m_gain_in_isSet = true; +} + +float +SWGUDPSinkSettings::getGainOut() { + return gain_out; +} +void +SWGUDPSinkSettings::setGainOut(float gain_out) { + this->gain_out = gain_out; + this->m_gain_out_isSet = true; +} + +float +SWGUDPSinkSettings::getSquelch() { + return squelch; +} +void +SWGUDPSinkSettings::setSquelch(float squelch) { + this->squelch = squelch; + this->m_squelch_isSet = true; +} + +float +SWGUDPSinkSettings::getSquelchGate() { + return squelch_gate; +} +void +SWGUDPSinkSettings::setSquelchGate(float squelch_gate) { + this->squelch_gate = squelch_gate; + this->m_squelch_gate_isSet = true; +} + +qint32 +SWGUDPSinkSettings::getSquelchEnabled() { + return squelch_enabled; +} +void +SWGUDPSinkSettings::setSquelchEnabled(qint32 squelch_enabled) { + this->squelch_enabled = squelch_enabled; + this->m_squelch_enabled_isSet = true; +} + +qint32 +SWGUDPSinkSettings::getAutoRwBalance() { + return auto_rw_balance; +} +void +SWGUDPSinkSettings::setAutoRwBalance(qint32 auto_rw_balance) { + this->auto_rw_balance = auto_rw_balance; + this->m_auto_rw_balance_isSet = true; +} + +qint32 +SWGUDPSinkSettings::getStereoInput() { + return stereo_input; +} +void +SWGUDPSinkSettings::setStereoInput(qint32 stereo_input) { + this->stereo_input = stereo_input; + this->m_stereo_input_isSet = true; +} + +qint32 +SWGUDPSinkSettings::getRgbColor() { + return rgb_color; +} +void +SWGUDPSinkSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGUDPSinkSettings::getUdpAddress() { + return udp_address; +} +void +SWGUDPSinkSettings::setUdpAddress(QString* udp_address) { + this->udp_address = udp_address; + this->m_udp_address_isSet = true; +} + +qint32 +SWGUDPSinkSettings::getUdpPort() { + return udp_port; +} +void +SWGUDPSinkSettings::setUdpPort(qint32 udp_port) { + this->udp_port = udp_port; + this->m_udp_port_isSet = true; +} + +QString* +SWGUDPSinkSettings::getTitle() { + return title; +} +void +SWGUDPSinkSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + + +bool +SWGUDPSinkSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_sample_format_isSet){ isObjectUpdated = true; break;} + if(m_input_sample_rate_isSet){ isObjectUpdated = true; break;} + if(m_input_frequency_offset_isSet){ isObjectUpdated = true; break;} + if(m_rf_bandwidth_isSet){ isObjectUpdated = true; break;} + if(m_low_cutoff_isSet){ isObjectUpdated = true; break;} + if(m_fm_deviation_isSet){ isObjectUpdated = true; break;} + if(m_am_mod_factor_isSet){ isObjectUpdated = true; break;} + if(m_channel_mute_isSet){ isObjectUpdated = true; break;} + if(m_gain_in_isSet){ isObjectUpdated = true; break;} + if(m_gain_out_isSet){ isObjectUpdated = true; break;} + if(m_squelch_isSet){ isObjectUpdated = true; break;} + if(m_squelch_gate_isSet){ isObjectUpdated = true; break;} + if(m_squelch_enabled_isSet){ isObjectUpdated = true; break;} + if(m_auto_rw_balance_isSet){ isObjectUpdated = true; break;} + if(m_stereo_input_isSet){ isObjectUpdated = true; break;} + if(m_rgb_color_isSet){ isObjectUpdated = true; break;} + if(udp_address != nullptr && *udp_address != QString("")){ isObjectUpdated = true; break;} + if(m_udp_port_isSet){ isObjectUpdated = true; break;} + if(title != nullptr && *title != QString("")){ isObjectUpdated = true; break;} + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h new file mode 100644 index 000000000..f8486e24b --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGUDPSinkSettings.h @@ -0,0 +1,167 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Stopping instance i.e. /sdrangel with DELETE method is a server only feature. It allows stopping the instance nicely. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV demodulator, Channel Analyzer, Channel Analyzer NG, LoRa demodulator, TCP source * The content type returned is always application/json except in the following cases: * An incorrect URL was specified: this document is returned as text/html with a status 400 --- + * + * OpenAPI spec version: 4.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGUDPSinkSettings.h + * + * UDPSink + */ + +#ifndef SWGUDPSinkSettings_H_ +#define SWGUDPSinkSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGUDPSinkSettings: public SWGObject { +public: + SWGUDPSinkSettings(); + SWGUDPSinkSettings(QString* json); + virtual ~SWGUDPSinkSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGUDPSinkSettings* fromJson(QString &jsonString) override; + + qint32 getSampleFormat(); + void setSampleFormat(qint32 sample_format); + + float getInputSampleRate(); + void setInputSampleRate(float input_sample_rate); + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getLowCutoff(); + void setLowCutoff(float low_cutoff); + + qint32 getFmDeviation(); + void setFmDeviation(qint32 fm_deviation); + + float getAmModFactor(); + void setAmModFactor(float am_mod_factor); + + qint32 getChannelMute(); + void setChannelMute(qint32 channel_mute); + + float getGainIn(); + void setGainIn(float gain_in); + + float getGainOut(); + void setGainOut(float gain_out); + + float getSquelch(); + void setSquelch(float squelch); + + float getSquelchGate(); + void setSquelchGate(float squelch_gate); + + qint32 getSquelchEnabled(); + void setSquelchEnabled(qint32 squelch_enabled); + + qint32 getAutoRwBalance(); + void setAutoRwBalance(qint32 auto_rw_balance); + + qint32 getStereoInput(); + void setStereoInput(qint32 stereo_input); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getUdpAddress(); + void setUdpAddress(QString* udp_address); + + qint32 getUdpPort(); + void setUdpPort(qint32 udp_port); + + QString* getTitle(); + void setTitle(QString* title); + + + virtual bool isSet() override; + +private: + qint32 sample_format; + bool m_sample_format_isSet; + + float input_sample_rate; + bool m_input_sample_rate_isSet; + + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float low_cutoff; + bool m_low_cutoff_isSet; + + qint32 fm_deviation; + bool m_fm_deviation_isSet; + + float am_mod_factor; + bool m_am_mod_factor_isSet; + + qint32 channel_mute; + bool m_channel_mute_isSet; + + float gain_in; + bool m_gain_in_isSet; + + float gain_out; + bool m_gain_out_isSet; + + float squelch; + bool m_squelch_isSet; + + float squelch_gate; + bool m_squelch_gate_isSet; + + qint32 squelch_enabled; + bool m_squelch_enabled_isSet; + + qint32 auto_rw_balance; + bool m_auto_rw_balance_isSet; + + qint32 stereo_input; + bool m_stereo_input_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* udp_address; + bool m_udp_address_isSet; + + qint32 udp_port; + bool m_udp_port_isSet; + + QString* title; + bool m_title_isSet; + +}; + +} + +#endif /* SWGUDPSinkSettings_H_ */ diff --git a/swagger/sdrangel/examples/tx_test.py b/swagger/sdrangel/examples/tx_test.py index ea317d9ee..4332402ee 100644 --- a/swagger/sdrangel/examples/tx_test.py +++ b/swagger/sdrangel/examples/tx_test.py @@ -204,6 +204,17 @@ def setupChannel(options): settings["SSBModSettings"]["toneFrequency"] = 600 settings["SSBModSettings"]["bandwidth"] = 1000 settings["SSBModSettings"]["lowCut"] = 300 + elif options.channel_id == "UDPSink": + settings["UDPSinkSettings"]["title"] = "Test UDP Sink" + settings["UDPSinkSettings"]["inputFrequencyOffset"] = options.channel_freq + settings["UDPSinkSettings"]["rfBandwidth"] = 12500 + settings["UDPSinkSettings"]["fmDeviation"] = 5000 + settings["UDPSinkSettings"]["autoRWBalance"] = 0 + settings["UDPSinkSettings"]["stereoInput"] = 0 + settings["UDPSinkSettings"]["udpAddress"] = "127.0.0.1" + settings["UDPSinkSettings"]["udpPort"] = 9998 + settings["UDPSinkSettings"]["inputSampleRate"] = 24000 + settings["UDPSinkSettings"]["sampleFormat"] = 1 # FormatNFM elif options.channel_id == "WFMMod": settings["WFMModSettings"]["title"] = "Test WFM" settings["WFMModSettings"]["inputFrequencyOffset"] = options.channel_freq From 5c5c6d4b7ac5146d6bd43e99f49ebecb36ea2a5e Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 18:48:59 +0200 Subject: [PATCH 278/956] Added server plugins for ATV, SSB modulators and UDP sink --- plugins/channeltx/modatv/atvmodplugin.cpp | 12 +++++- plugins/channeltx/modssb/ssbmodplugin.cpp | 12 +++++- plugins/channeltx/udpsink/udpsinkplugin.cpp | 14 ++++++- pluginssrv/channeltx/CMakeLists.txt | 3 ++ pluginssrv/channeltx/modatv/CMakeLists.txt | 45 +++++++++++++++++++++ pluginssrv/channeltx/modssb/CMakeLists.txt | 41 +++++++++++++++++++ pluginssrv/channeltx/udpsink/CMakeLists.txt | 45 +++++++++++++++++++++ 7 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 pluginssrv/channeltx/modatv/CMakeLists.txt create mode 100644 pluginssrv/channeltx/modssb/CMakeLists.txt create mode 100644 pluginssrv/channeltx/udpsink/CMakeLists.txt diff --git a/plugins/channeltx/modatv/atvmodplugin.cpp b/plugins/channeltx/modatv/atvmodplugin.cpp index 757f09c09..d9e3e26dd 100644 --- a/plugins/channeltx/modatv/atvmodplugin.cpp +++ b/plugins/channeltx/modatv/atvmodplugin.cpp @@ -15,10 +15,11 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "atvmodgui.h" +#endif #include "atvmod.h" #include "atvmodplugin.h" @@ -50,10 +51,19 @@ void ATVModPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(ATVMod::m_channelIdURI, ATVMod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* ATVModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* ATVModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return ATVModGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* ATVModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp index 4dd9a59f8..c19c2cc04 100644 --- a/plugins/channeltx/modssb/ssbmodplugin.cpp +++ b/plugins/channeltx/modssb/ssbmodplugin.cpp @@ -15,10 +15,11 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "ssbmodgui.h" +#endif #include "ssbmod.h" #include "ssbmodplugin.h" @@ -50,10 +51,19 @@ void SSBModPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(SSBMod::m_channelIdURI, SSBMod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* SSBModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SSBModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return SSBModGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* SSBModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/plugins/channeltx/udpsink/udpsinkplugin.cpp b/plugins/channeltx/udpsink/udpsinkplugin.cpp index 60449a9b4..959feead3 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.cpp +++ b/plugins/channeltx/udpsink/udpsinkplugin.cpp @@ -20,11 +20,14 @@ #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "udpsinkgui.h" +#endif +#include "udpsink.h" const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { QString("UDP Channel Sink"), - QString("3.12.0"), + QString("3.14.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -50,10 +53,19 @@ void UDPSinkPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(UDPSink::m_channelIdURI, UDPSink::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* UDPSinkPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* UDPSinkPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return UDPSinkGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* UDPSinkPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/pluginssrv/channeltx/CMakeLists.txt b/pluginssrv/channeltx/CMakeLists.txt index 661d661ee..6e54c0637 100644 --- a/pluginssrv/channeltx/CMakeLists.txt +++ b/pluginssrv/channeltx/CMakeLists.txt @@ -1,5 +1,8 @@ project(mod) add_subdirectory(modam) +add_subdirectory(modatv) add_subdirectory(modnfm) +add_subdirectory(modssb) add_subdirectory(modwfm) +add_subdirectory(udpsink) diff --git a/pluginssrv/channeltx/modatv/CMakeLists.txt b/pluginssrv/channeltx/modatv/CMakeLists.txt new file mode 100644 index 000000000..4338f70fb --- /dev/null +++ b/pluginssrv/channeltx/modatv/CMakeLists.txt @@ -0,0 +1,45 @@ +project(modatv) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/modatv") + +set(modatv_SOURCES + ${PLUGIN_PREFIX}/atvmod.cpp + ${PLUGIN_PREFIX}/atvmodplugin.cpp + ${PLUGIN_PREFIX}/atvmodsettings.cpp +) + +set(modatv_HEADERS + ${PLUGIN_PREFIX}/atvmod.h + ${PLUGIN_PREFIX}/atvmodplugin.h + ${PLUGIN_PREFIX}/atvmodsettings.h +) + +# OpenCV variables defined in /usr/share/OpenCV/OpenCVConfig.cmake (Ubuntu) + +include_directories( + . + ${OpenCV_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modatvsrv SHARED + ${modatv_SOURCES} + ${modatv_HEADERS_MOC} +) + +target_link_libraries(modatvsrv + ${OpenCV_LIBS} + ${QT_LIBRARIES} + sdrbase + swagger +) + +qt5_use_modules(modatvsrv Core) + +install(TARGETS modatvsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modssb/CMakeLists.txt b/pluginssrv/channeltx/modssb/CMakeLists.txt new file mode 100644 index 000000000..4da444e3b --- /dev/null +++ b/pluginssrv/channeltx/modssb/CMakeLists.txt @@ -0,0 +1,41 @@ +project(modssb) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/modssb") + +set(modssb_SOURCES + ${PLUGIN_PREFIX}/ssbmod.cpp + ${PLUGIN_PREFIX}/ssbmodplugin.cpp + ${PLUGIN_PREFIX}/ssbmodsettings.cpp +) + +set(modssb_HEADERS + ${PLUGIN_PREFIX}/ssbmod.h + ${PLUGIN_PREFIX}/ssbmodplugin.h + ${PLUGIN_PREFIX}/ssbmodsettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modssbsrv SHARED + ${modssb_SOURCES} + ${modssb_HEADERS_MOC} +) + +target_link_libraries(modssbsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +qt5_use_modules(modssbsrv Core) + +install(TARGETS modssbsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/udpsink/CMakeLists.txt b/pluginssrv/channeltx/udpsink/CMakeLists.txt new file mode 100644 index 000000000..bb7f3b0f3 --- /dev/null +++ b/pluginssrv/channeltx/udpsink/CMakeLists.txt @@ -0,0 +1,45 @@ +project(udpsink) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/udpsink") + +set(udpsink_SOURCES + ${PLUGIN_PREFIX}/udpsink.cpp + ${PLUGIN_PREFIX}/udpsinkplugin.cpp + ${PLUGIN_PREFIX}/udpsinkudphandler.cpp + ${PLUGIN_PREFIX}/udpsinkmsg.cpp + ${PLUGIN_PREFIX}/udpsinksettings.cpp +) + +set(udpsink_HEADERS + ${PLUGIN_PREFIX}/udpsink.h + ${PLUGIN_PREFIX}/udpsinkplugin.h + ${PLUGIN_PREFIX}/udpsinkudphandler.h + ${PLUGIN_PREFIX}/udpsinkmsg.h + ${PLUGIN_PREFIX}/udpsinksettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modudpsinksrv SHARED + ${udpsink_SOURCES} + ${udpsink_HEADERS_MOC} +) + +target_link_libraries(modudpsinksrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +qt5_use_modules(modudpsinksrv Core Network) + +install(TARGETS modudpsinksrv DESTINATION lib/pluginssrv/channeltx) From f4b090062d187c5455b75c4952e137bd5e1967ff Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 18:56:29 +0200 Subject: [PATCH 279/956] Updated Debian changelog --- debian/changelog | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/debian/changelog b/debian/changelog index b969dce39..4b61336e0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,12 @@ -sdrangel (3.14.1-1) unstable; urgency=medium +sdrangel (3.14.2-1) unstable; urgency=medium - * Web API: settings and report for AM mod - * Server: AirspyHF, BladeRF, AM support + * Web API: settings and report for all channel Tx plugins + * Server: AirspyHF, BladeRF and all channel Tx plugins support * PVS-Studio static analysis corrections (4) + * NFM demod: fixed AF squelch and audio sample rate handling + * BFM demod: fixed segfault in RDS parser - -- Edouard Griffiths, F4EXB Sun, 08 Apr 2018 12:14:18 +0200 + -- Edouard Griffiths, F4EXB Sun, 15 Apr 2018 12:14:18 +0200 sdrangel (3.14.1-1) unstable; urgency=medium From 4df9aa93ab8b379c4bfe5cb71a43fa7f75d2d0ed Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 19:14:20 +0200 Subject: [PATCH 280/956] AirspyHF and BladeRF input server plugins: removed QWidget dependency --- plugins/samplesource/airspyhf/airspyhfinput.cpp | 1 - .../samplesource/airspyhf/airspyhfplugin.cpp | 15 ++++++++++++++- .../samplesource/bladerfinput/bladerfinput.cpp | 1 - .../bladerfinput/bladerfinputplugin.cpp | 17 +++++++++++++++-- pluginssrv/samplesource/airspyhf/CMakeLists.txt | 2 +- .../samplesource/bladerfinput/CMakeLists.txt | 2 +- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index 9b67930ea..564775a79 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -28,7 +28,6 @@ #include "airspyhfinput.h" -#include "airspyhfgui.h" #include "airspyhfplugin.h" #include "airspyhfsettings.h" #include "airspyhfthread.h" diff --git a/plugins/samplesource/airspyhf/airspyhfplugin.cpp b/plugins/samplesource/airspyhf/airspyhfplugin.cpp index d2b77c418..aa436df7c 100644 --- a/plugins/samplesource/airspyhf/airspyhfplugin.cpp +++ b/plugins/samplesource/airspyhf/airspyhfplugin.cpp @@ -15,14 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include #include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "airspyhfplugin.h" +#ifdef SERVER_MODE +#include "airspyhfinput.h" +#else #include "airspyhfgui.h" +#endif const PluginDescriptor AirspyHFPlugin::m_pluginDescriptor = { @@ -95,6 +98,15 @@ PluginInterface::SamplingDevices AirspyHFPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* AirspyHFPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* AirspyHFPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -111,6 +123,7 @@ PluginInstanceGUI* AirspyHFPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *AirspyHFPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerfinput/bladerfinput.cpp index aa0ef0ade..3837f57d8 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinput.cpp @@ -30,7 +30,6 @@ #include "device/devicesourceapi.h" #include "device/devicesinkapi.h" -#include "bladerfinputgui.h" #include "bladerfinputthread.h" MESSAGE_CLASS_DEFINITION(BladerfInput::MsgConfigureBladerf, Message) diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp index e8c48b3f2..256ff39b3 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp @@ -14,16 +14,19 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "../bladerfinput/bladerfinputplugin.h" +#include "bladerfinputplugin.h" #include -#include #include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include +#ifdef SERVER_MODE +#include "bladerfinput.h" +#else #include "bladerfinputgui.h" +#endif const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { QString("BladeRF Input"), @@ -82,6 +85,15 @@ PluginInterface::SamplingDevices BlderfInputPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* BlderfInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* BlderfInputPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -98,6 +110,7 @@ PluginInstanceGUI* BlderfInputPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *BlderfInputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/pluginssrv/samplesource/airspyhf/CMakeLists.txt b/pluginssrv/samplesource/airspyhf/CMakeLists.txt index bc5885a12..17a3ab6a9 100644 --- a/pluginssrv/samplesource/airspyhf/CMakeLists.txt +++ b/pluginssrv/samplesource/airspyhf/CMakeLists.txt @@ -59,6 +59,6 @@ target_link_libraries(inputairspyhfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputairspyhfsrv Core Widgets) +qt5_use_modules(inputairspyhfsrv Core) install(TARGETS inputairspyhfsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/bladerfinput/CMakeLists.txt b/pluginssrv/samplesource/bladerfinput/CMakeLists.txt index d9b1ebfc9..b468ae23b 100644 --- a/pluginssrv/samplesource/bladerfinput/CMakeLists.txt +++ b/pluginssrv/samplesource/bladerfinput/CMakeLists.txt @@ -63,6 +63,6 @@ target_link_libraries(inputbladerfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputbladerfsrv Core Widgets) +qt5_use_modules(inputbladerfsrv Core) install(TARGETS inputbladerfsrv DESTINATION lib/pluginssrv/samplesource) From 36c3c71c73dac2cd14af5289e198cf03653418d5 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 19:18:21 +0200 Subject: [PATCH 281/956] BladeRF output server plugins: removed QWidget dependency --- .../samplesink/bladerfoutput/bladerfoutput.cpp | 1 - .../bladerfoutput/bladerfoutputplugin.cpp | 16 +++++++++++++++- .../samplesink/bladerfoutput/CMakeLists.txt | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp index 670145da4..a695b67dc 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutput.cpp @@ -29,7 +29,6 @@ #include "bladerf/devicebladerfshared.h" #include "bladerfoutput.h" -#include "bladerfoutputgui.h" #include "bladerfoutputthread.h" MESSAGE_CLASS_DEFINITION(BladerfOutput::MsgConfigureBladerf, Message) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp index d606f2e05..cd861aea4 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp +++ b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp @@ -15,14 +15,18 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include #include "bladerfoutputplugin.h" + +#ifdef SERVER_MODE +#include "bladerfoutput.h" +#else #include "bladerfoutputgui.h" +#endif const PluginDescriptor BladerfOutputPlugin::m_pluginDescriptor = { QString("BladeRF Output"), @@ -81,6 +85,15 @@ PluginInterface::SamplingDevices BladerfOutputPlugin::enumSampleSinks() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* BladerfOutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* BladerfOutputPlugin::createSampleSinkPluginInstanceGUI( const QString& sinkId, QWidget **widget, @@ -97,6 +110,7 @@ PluginInstanceGUI* BladerfOutputPlugin::createSampleSinkPluginInstanceGUI( return 0; } } +#endif DeviceSampleSink* BladerfOutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) { diff --git a/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt b/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt index 82a66e3dd..7c1365553 100644 --- a/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt +++ b/pluginssrv/samplesink/bladerfoutput/CMakeLists.txt @@ -63,6 +63,6 @@ target_link_libraries(outputbladerfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(outputbladerfsrv Core Widgets) +qt5_use_modules(outputbladerfsrv Core) install(TARGETS outputbladerfsrv DESTINATION lib/pluginssrv/samplesink) From def7592053e5adc89ba31de97792534380c2ea20 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 20:56:46 +0200 Subject: [PATCH 282/956] Widnows build fixes --- plugins/channeltx/modam/modam.pro | 2 ++ plugins/channeltx/modssb/modssb.pro | 2 ++ plugins/channeltx/modwfm/modwfm.pro | 2 ++ plugins/channeltx/udpsink/udpsink.pro | 2 ++ sdrbase/sdrbase.pro | 3 ++- sdrgui/sdrgui.pro | 2 -- 6 files changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/channeltx/modam/modam.pro b/plugins/channeltx/modam/modam.pro index 529b82ede..3eb914740 100644 --- a/plugins/channeltx/modam/modam.pro +++ b/plugins/channeltx/modam/modam.pro @@ -21,6 +21,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -39,5 +40,6 @@ FORMS += ammodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channeltx/modssb/modssb.pro b/plugins/channeltx/modssb/modssb.pro index b1378fc83..460fc9dc9 100644 --- a/plugins/channeltx/modssb/modssb.pro +++ b/plugins/channeltx/modssb/modssb.pro @@ -21,6 +21,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -39,5 +40,6 @@ FORMS += ssbmodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channeltx/modwfm/modwfm.pro b/plugins/channeltx/modwfm/modwfm.pro index a2ade1bc1..4ab4b513c 100644 --- a/plugins/channeltx/modwfm/modwfm.pro +++ b/plugins/channeltx/modwfm/modwfm.pro @@ -21,6 +21,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -39,5 +40,6 @@ FORMS += wfmmodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channeltx/udpsink/udpsink.pro b/plugins/channeltx/udpsink/udpsink.pro index dce7d049b..6d93b1ddb 100644 --- a/plugins/channeltx/udpsink/udpsink.pro +++ b/plugins/channeltx/udpsink/udpsink.pro @@ -21,6 +21,7 @@ INCLUDEPATH += $$PWD INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -43,5 +44,6 @@ FORMS += udpsinkgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro index ada4502b9..77571c589 100644 --- a/sdrbase/sdrbase.pro +++ b/sdrbase/sdrbase.pro @@ -86,6 +86,7 @@ SOURCES += audio/audiodevicemanager.cpp\ dsp/nco.cpp\ dsp/ncof.cpp\ dsp/phaselock.cpp\ + dsp/projector.cpp\ dsp/recursivefilters.cpp\ dsp/samplesinkfifo.cpp\ dsp/samplesourcefifo.cpp\ @@ -171,6 +172,7 @@ HEADERS += audio/audiodevicemanager.h\ dsp/ncof.h\ dsp/phasediscri.h\ dsp/phaselock.h\ + dsp/projector.h\ dsp/recursivefilters.h\ dsp/samplesinkfifo.h\ dsp/samplesourcefifo.h\ @@ -193,7 +195,6 @@ HEADERS += audio/audiodevicemanager.h\ settings/mainsettings.h\ util/CRC64.h\ util/db.h\ - export.h\ util/message.h\ util/messagequeue.h\ util/prettyprint.h\ diff --git a/sdrgui/sdrgui.pro b/sdrgui/sdrgui.pro index 32d87b8dd..2d50bb7ec 100644 --- a/sdrgui/sdrgui.pro +++ b/sdrgui/sdrgui.pro @@ -90,8 +90,6 @@ SOURCES += mainwindow.cpp\ webapi/webapiadaptergui.cpp HEADERS += mainwindow.h\ - device/devicesourceapi.h\ - device/devicesinkapi.h\ device/deviceuiset.h\ dsp/spectrumscopecombovis.h\ dsp/spectrumscopengcombovis.h\ From eb047ba15ef1f213662cc21eb6be9a6b0a60364f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 21:13:47 +0200 Subject: [PATCH 283/956] DATV demod: removed reference to deleted method --- plugins/channelrx/demoddatv/datvdemodgui.h | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h index e6fad2764..4af8300ac 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.h +++ b/plugins/channelrx/demoddatv/datvdemodgui.h @@ -69,7 +69,6 @@ private slots: void channelMarkerChangedByCursor(); void channelMarkerHighlightedByCursor(); - void channelSampleRateChanged(); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDoubleClicked(); void tick(); From 01798c9d15cd8135e3bc6ab48e113c80f7ef3aa2 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 15 Apr 2018 22:03:31 +0200 Subject: [PATCH 284/956] BFM demod: RDS parser: fixed compiler warning --- plugins/channelrx/demodbfm/rdsparser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/channelrx/demodbfm/rdsparser.cpp b/plugins/channelrx/demodbfm/rdsparser.cpp index 5f6f96598..eaa95c98e 100644 --- a/plugins/channelrx/demodbfm/rdsparser.cpp +++ b/plugins/channelrx/demodbfm/rdsparser.cpp @@ -941,10 +941,10 @@ void RDSParser::decode_optional_content(int no_groups, unsigned long int *free_f while(ff_pointer >= 7) // ff_pointer must be >= 0 and is decreased by 7 in the loop { ff_pointer -= 4; - m_g8_label_index = (free_format[i] && (0xf << ff_pointer)) ? 1 : 0; + m_g8_label_index = (free_format[i] && ((0xf << ff_pointer) != 0)) ? 1 : 0; content_length = 3; // optional_content_lengths[m_g8_label_index]; // always 3 ff_pointer -= content_length; - m_g8_content = (free_format[i] && (int(std::pow(2, content_length) - 1) << ff_pointer)) ? 1 : 0; + m_g8_content = (free_format[i] && ((int(std::pow(2, content_length) - 1) << ff_pointer) != 0)) ? 1 : 0; qDebug() << "RDSParser::decode_optional_content:" << " TMC optional content (" << label_descriptions[m_g8_label_index].c_str() << ")" From 27ddcabd56de0c5f6c725959ccbea6290d94d610 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 16 Apr 2018 08:24:56 +0200 Subject: [PATCH 285/956] Bumped version to v3.14.3 --- app/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 ++++++ plugins/samplesink/limesdroutput/limesdroutputplugin.cpp | 2 +- plugins/samplesource/limesdrinput/limesdrinputplugin.cpp | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 26cf88c1e..14164fd21 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.14.2"); + QCoreApplication::setApplicationVersion("3.14.3"); #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 7ead47de4..608ede061 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.14.2"); + QCoreApplication::setApplicationVersion("3.14.3"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index 4b61336e0..9911466e7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (3.14.3-1) unstable; urgency=medium + + * LimeSDR: compiled with LimeSuite commit 67dcef1 + + -- Edouard Griffiths, F4EXB Sat, 21 Apr 2018 18:14:18 +0200 + sdrangel (3.14.2-1) unstable; urgency=medium * Web API: settings and report for all channel Tx plugins diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp index 1e9aa0edd..61a2c30a8 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp @@ -34,7 +34,7 @@ const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = { QString("LimeSDR Output"), - QString("3.14.0"), + QString("3.14.3"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 6337d86f5..28d7f5a6d 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("3.14.0"), + QString("3.14.3"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, From b9587273d84a70b33ac991a7949b6199caf9e15f Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 17 Apr 2018 00:25:19 +0200 Subject: [PATCH 286/956] LimeSDR input: implemented transverter shift --- .../limesdrinput/limesdrinput.cpp | 28 ++++++++++--- .../limesdrinput/limesdrinputgui.cpp | 35 ++++++++++++---- .../limesdrinput/limesdrinputgui.h | 3 +- .../limesdrinput/limesdrinputgui.ui | 34 ++++++++------- .../limesdrinput/limesdrinputsettings.cpp | 6 +++ .../limesdrinput/limesdrinputsettings.h | 2 + sdrbase/resources/webapi/doc/html2/index.html | 9 +++- .../webapi/doc/swagger/include/LimeSdr.yaml | 5 +++ .../sdrangel/api/swagger/include/LimeSdr.yaml | 5 +++ swagger/sdrangel/code/html2/index.html | 9 +++- .../qt5/client/SWGLimeSdrInputSettings.cpp | 42 +++++++++++++++++++ .../code/qt5/client/SWGLimeSdrInputSettings.h | 12 ++++++ 12 files changed, 159 insertions(+), 31 deletions(-) diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 18e44da12..5f4810cbf 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -735,6 +735,10 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc double clockGenFreq = 0.0; // QMutexLocker mutexLocker(&m_mutex); + qint64 deviceCenterFrequency = settings.m_centerFrequency; + deviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + if (LMS_GetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_CGEN, &clockGenFreq) != 0) { qCritical("LimeSDRInput::applySettings: could not get clock gen frequency"); @@ -1016,21 +1020,24 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc } } - if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || setAntennaAuto || force) + if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_transverterMode != settings.m_transverterMode) + || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) + || setAntennaAuto || force) { forwardChangeRxDSP = true; if (m_deviceShared.m_deviceParams->getDevice() != 0 && m_channelAcquired) { - if (LMS_SetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_SXR, settings.m_centerFrequency) < 0) + if (LMS_SetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_SXR, deviceCenterFrequency) < 0) { - qCritical("LimeSDRInput::applySettings: could not set frequency to %lu", settings.m_centerFrequency); + qCritical("LimeSDRInput::applySettings: could not set frequency to %lld", deviceCenterFrequency); } else { doCalibration = true; - m_deviceShared.m_centerFrequency = settings.m_centerFrequency; // for buddies - qDebug("LimeSDRInput::applySettings: frequency set to %lu", settings.m_centerFrequency); + m_deviceShared.m_centerFrequency = deviceCenterFrequency; // for buddies + qDebug("LimeSDRInput::applySettings: frequency set to %lld", deviceCenterFrequency); } } } @@ -1217,6 +1224,9 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc QLocale loc; qDebug().noquote() << "LimeSDRInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " deviceCenterFrequency: " << deviceCenterFrequency << " device stream sample rate: " << loc.toString(m_settings.m_devSampleRate) << "S/s" << " sample rate with soft decimation: " << loc.toString( m_settings.m_devSampleRate/(1<getTiaGain(); } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getLimeSdrInputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getLimeSdrInputSettings()->getTransverterMode() != 0; + } MsgConfigureLimeSDR *msg = MsgConfigureLimeSDR::create(settings, force); m_inputMessageQueue.push(msg); @@ -1349,6 +1365,8 @@ void LimeSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& re response.getLimeSdrInputSettings()->setNcoFrequency(settings.m_ncoFrequency); response.getLimeSdrInputSettings()->setPgaGain(settings.m_pgaGain); response.getLimeSdrInputSettings()->setTiaGain(settings.m_tiaGain); + response.getLimeSdrInputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getLimeSdrInputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); } int LimeSDRInput::webapiRunGet( diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index 911de2900..10a930672 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -242,6 +242,23 @@ bool LimeSDRInputGUI::handleMessage(const Message& message) } } +void LimeSDRInputGUI::updateFrequencyLimits() +{ + // values in kHz + float minF, maxF; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_limeSDRInput->getLORange(minF, maxF); + qint64 minLimit = minF/1000 + deltaFrequency; + qint64 maxLimit = maxF/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("LimeSDRInputGUI::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + void LimeSDRInputGUI::handleInputMessages() { Message* message; @@ -504,13 +521,6 @@ void LimeSDRInputGUI::on_ncoEnable_toggled(bool checked) sendSettings(); } -void LimeSDRInputGUI::on_ncoReset_clicked(bool checked __attribute__((unused))) -{ - m_settings.m_ncoFrequency = 0; - ui->ncoFrequency->setValue(0); - sendSettings(); -} - void LimeSDRInputGUI::on_dcOffset_toggled(bool checked) { m_settings.m_dcBlock = checked; @@ -632,3 +642,14 @@ void LimeSDRInputGUI::on_extClock_clicked() sendSettings(); } +void LimeSDRInputGUI::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("LimeSDRInputGUI::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + + diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.h b/plugins/samplesource/limesdrinput/limesdrinputgui.h index b8966d5ba..9c44963da 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.h +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.h @@ -75,6 +75,7 @@ private: void updateSampleRateAndFrequency(); void updateADCRate(); void blockApplySettings(bool block); + void updateFrequencyLimits(); private slots: void handleInputMessages(); @@ -83,7 +84,6 @@ private slots: void on_centerFrequency_changed(quint64 value); void on_ncoFrequency_changed(qint64 value); void on_ncoEnable_toggled(bool checked); - void on_ncoReset_clicked(bool checked); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); void on_sampleRate_changed(quint64 value); @@ -99,6 +99,7 @@ private slots: void on_pgaGain_valueChanged(int value); void on_antenna_currentIndexChanged(int index); void on_extClock_clicked(); + void on_transverter_clicked(); void updateHardware(); void updateStatus(); diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.ui b/plugins/samplesource/limesdrinput/limesdrinputgui.ui index 3c493322c..928c56fb7 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.ui +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.ui @@ -230,22 +230,6 @@
    - - - - - 22 - 22 - - - - Reset the NCO to zero frequency - - - R - - - @@ -316,6 +300,19 @@ + + + + + 24 + 24 + + + + X + + + @@ -1176,6 +1173,11 @@ QToolTip{background-color: white; color: black;}
    gui/valuedialz.h
    1 + + TransverterButton + QPushButton +
    gui/transverterbutton.h
    +
    diff --git a/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp b/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp index 025ce48f5..34f053d3b 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp @@ -43,6 +43,8 @@ void LimeSDRInputSettings::resetToDefaults() m_pgaGain = 16; m_extClock = false; m_extClockFreq = 10000000; // 10 MHz + m_transverterMode = false; + m_transverterDeltaFrequency = 0; } QByteArray LimeSDRInputSettings::serialize() const @@ -67,6 +69,8 @@ QByteArray LimeSDRInputSettings::serialize() const s.writeU32(17, m_pgaGain); s.writeBool(18, m_extClock); s.writeU32(19, m_extClockFreq); + s.writeBool(20, m_transverterMode); + s.writeS64(21, m_transverterDeltaFrequency); return s.final(); } @@ -105,6 +109,8 @@ bool LimeSDRInputSettings::deserialize(const QByteArray& data) d.readU32(17, &m_pgaGain, 16); d.readBool(18, &m_extClock, false); d.readU32(19, &m_extClockFreq, 10000000); + d.readBool(20, &m_transverterMode, false); + d.readS64(21, &m_transverterDeltaFrequency, 0); return true; } diff --git a/plugins/samplesource/limesdrinput/limesdrinputsettings.h b/plugins/samplesource/limesdrinput/limesdrinputsettings.h index e129af9c9..a82638954 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputsettings.h +++ b/plugins/samplesource/limesdrinput/limesdrinputsettings.h @@ -62,6 +62,8 @@ struct LimeSDRInputSettings uint32_t m_pgaGain; //!< Manual PGA gain bool m_extClock; //!< True if external clock source uint32_t m_extClockFreq; //!< Frequency (Hz) of external clock source + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; LimeSDRInputSettings(); void resetToDefaults(); diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index c66efd72b..b8d1ee6b1 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1681,6 +1681,13 @@ margin-bottom: 20px; }, "extClockFreq" : { "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" } }, "description" : "LimeSDR" @@ -20629,7 +20636,7 @@ except ApiException as e:
    - Generated 2018-04-15T11:16:57.480+02:00 + Generated 2018-04-17T00:16:15.209+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml index 532b36fd5..0d30f5ed4 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml @@ -40,6 +40,11 @@ LimeSdrInputSettings: type: integer extClockFreq: type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 LimeSdrOutputSettings: description: LimeSDR diff --git a/swagger/sdrangel/api/swagger/include/LimeSdr.yaml b/swagger/sdrangel/api/swagger/include/LimeSdr.yaml index 532b36fd5..0d30f5ed4 100644 --- a/swagger/sdrangel/api/swagger/include/LimeSdr.yaml +++ b/swagger/sdrangel/api/swagger/include/LimeSdr.yaml @@ -40,6 +40,11 @@ LimeSdrInputSettings: type: integer extClockFreq: type: integer + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 LimeSdrOutputSettings: description: LimeSDR diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index c66efd72b..b8d1ee6b1 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1681,6 +1681,13 @@ margin-bottom: 20px; }, "extClockFreq" : { "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" } }, "description" : "LimeSDR" @@ -20629,7 +20636,7 @@ except ApiException as e:
    - Generated 2018-04-15T11:16:57.480+02:00 + Generated 2018-04-17T00:16:15.209+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp index b010a7ef1..bffdd5215 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.cpp @@ -66,6 +66,10 @@ SWGLimeSdrInputSettings::SWGLimeSdrInputSettings() { m_ext_clock_isSet = false; ext_clock_freq = 0; m_ext_clock_freq_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; } SWGLimeSdrInputSettings::~SWGLimeSdrInputSettings() { @@ -112,6 +116,10 @@ SWGLimeSdrInputSettings::init() { m_ext_clock_isSet = false; ext_clock_freq = 0; m_ext_clock_freq_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; } void @@ -134,6 +142,8 @@ SWGLimeSdrInputSettings::cleanup() { + + } @@ -186,6 +196,10 @@ SWGLimeSdrInputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ext_clock_freq, pJson["extClockFreq"], "qint32", ""); + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + } QString @@ -259,6 +273,12 @@ SWGLimeSdrInputSettings::asJsonObject() { if(m_ext_clock_freq_isSet){ obj->insert("extClockFreq", QJsonValue(ext_clock_freq)); } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } return obj; } @@ -453,6 +473,26 @@ SWGLimeSdrInputSettings::setExtClockFreq(qint32 ext_clock_freq) { this->m_ext_clock_freq_isSet = true; } +qint32 +SWGLimeSdrInputSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGLimeSdrInputSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGLimeSdrInputSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGLimeSdrInputSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + bool SWGLimeSdrInputSettings::isSet(){ @@ -477,6 +517,8 @@ SWGLimeSdrInputSettings::isSet(){ if(m_pga_gain_isSet){ isObjectUpdated = true; break;} if(m_ext_clock_isSet){ isObjectUpdated = true; break;} if(m_ext_clock_freq_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h index fbaa8e360..5fd83cdd7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrInputSettings.h @@ -98,6 +98,12 @@ public: qint32 getExtClockFreq(); void setExtClockFreq(qint32 ext_clock_freq); + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + virtual bool isSet() override; @@ -159,6 +165,12 @@ private: qint32 ext_clock_freq; bool m_ext_clock_freq_isSet; + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + }; } From ff88a2e1dbde3e083750b493798aa855649ceb3e Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 17 Apr 2018 00:55:56 +0200 Subject: [PATCH 287/956] LimeSDR output: implemented transverter shift --- .../limesdroutput/limesdroutput.cpp | 32 ++++++++++---- .../limesdroutput/limesdroutputgui.cpp | 35 ++++++++++++---- .../limesdroutput/limesdroutputgui.h | 3 +- .../limesdroutput/limesdroutputgui.ui | 39 +++++++++-------- .../limesdroutput/limesdroutputsettings.cpp | 6 +++ .../limesdroutput/limesdroutputsettings.h | 2 + .../limesdrinput/limesdrinput.cpp | 6 +-- .../limesdrinput/limesdrinputgui.h | 2 +- .../limesdrinput/limesdrinputgui.ui | 3 ++ sdrbase/resources/webapi/doc/html2/index.html | 9 +++- .../webapi/doc/swagger/include/LimeSdr.yaml | 6 ++- .../sdrangel/api/swagger/include/LimeSdr.yaml | 6 ++- swagger/sdrangel/code/html2/index.html | 9 +++- .../qt5/client/SWGLimeSdrOutputSettings.cpp | 42 +++++++++++++++++++ .../qt5/client/SWGLimeSdrOutputSettings.h | 12 ++++++ 15 files changed, 172 insertions(+), 40 deletions(-) diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp index 4b31712a1..4e2c7f5dc 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp @@ -701,6 +701,10 @@ bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool fo double clockGenFreq = 0.0; // QMutexLocker mutexLocker(&m_mutex); + qint64 deviceCenterFrequency = settings.m_centerFrequency; + deviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + if (LMS_GetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_CGEN, &clockGenFreq) != 0) { qCritical("LimeSDROutput::applySettings: could not get clock gen frequency"); @@ -852,32 +856,35 @@ bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool fo settings.m_antennaPath)) { doCalibration = true; - qDebug("LimeSDRInput::applySettings: set antenna path to %d", + qDebug("LimeSDROutput::applySettings: set antenna path to %d", (int) settings.m_antennaPath); } else { - qCritical("LimeSDRInput::applySettings: could not set antenna path to %d", + qCritical("LimeSDROutput::applySettings: could not set antenna path to %d", (int) settings.m_antennaPath); } } } - if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || force) + if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_transverterMode != settings.m_transverterMode) + || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) + || force) { forwardChangeTxDSP = true; if (m_deviceShared.m_deviceParams->getDevice() != 0 && m_channelAcquired) { - if (LMS_SetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_SXT, settings.m_centerFrequency) < 0) + if (LMS_SetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_SXT, deviceCenterFrequency) < 0) { - qCritical("LimeSDROutput::applySettings: could not set frequency to %lu", settings.m_centerFrequency); + qCritical("LimeSDROutput::applySettings: could not set frequency to %lld", deviceCenterFrequency); } else { doCalibration = true; - m_deviceShared.m_centerFrequency = settings.m_centerFrequency; // for buddies - qDebug("LimeSDROutput::applySettings: frequency set to %lu", settings.m_centerFrequency); + m_deviceShared.m_centerFrequency = deviceCenterFrequency; // for buddies + qDebug("LimeSDROutput::applySettings: frequency set to %lld", deviceCenterFrequency); } } } @@ -1063,6 +1070,9 @@ bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool fo QLocale loc; qDebug().noquote() << "LimeSDROutput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " deviceCenterFrequency: " << deviceCenterFrequency << " device stream sample rate: " << loc.toString(m_settings.m_devSampleRate) << "S/s" << " sample rate with soft interpolation: " << loc.toString( m_settings.m_devSampleRate/(1<getNcoFrequency(); } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getLimeSdrOutputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getLimeSdrOutputSettings()->getTransverterMode() != 0; + } MsgConfigureLimeSDR *msg = MsgConfigureLimeSDR::create(settings, force); m_inputMessageQueue.push(msg); @@ -1171,6 +1187,8 @@ void LimeSDROutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& r response.getLimeSdrOutputSettings()->setLpfFirbw(settings.m_lpfFIRBW); response.getLimeSdrOutputSettings()->setNcoEnable(settings.m_ncoEnable ? 1 : 0); response.getLimeSdrOutputSettings()->setNcoFrequency(settings.m_ncoFrequency); + response.getLimeSdrOutputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getLimeSdrOutputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); } int LimeSDROutput::webapiRunGet( diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp index 953e4d01b..ed0a89575 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp @@ -140,6 +140,23 @@ bool LimeSDROutputGUI::deserialize(const QByteArray& data) } } +void LimeSDROutputGUI::updateFrequencyLimits() +{ + // values in kHz + float minF, maxF; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_limeSDROutput->getLORange(minF, maxF); + qint64 minLimit = minF/1000 + deltaFrequency; + qint64 maxLimit = maxF/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("LimeSDROutputGUI::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + bool LimeSDROutputGUI::handleMessage(const Message& message) { if (LimeSDROutput::MsgConfigureLimeSDR::match(message)) @@ -469,13 +486,6 @@ void LimeSDROutputGUI::on_ncoEnable_toggled(bool checked) sendSettings(); } -void LimeSDROutputGUI::on_ncoReset_clicked(bool checked __attribute__((unused))) -{ - m_settings.m_ncoFrequency = 0; - ui->ncoFrequency->setValue(0); - sendSettings(); -} - void LimeSDROutputGUI::on_sampleRate_changed(quint64 value) { m_settings.m_devSampleRate = value; @@ -543,3 +553,14 @@ void LimeSDROutputGUI::on_extClock_clicked() sendSettings(); } +void LimeSDROutputGUI::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("LimeSDRInputGUI::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + + diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.h b/plugins/samplesink/limesdroutput/limesdroutputgui.h index 59f050bc1..bfa1147b6 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.h +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.h @@ -75,6 +75,7 @@ private: void sendSettings(); void updateSampleRateAndFrequency(); void updateDACRate(); + void updateFrequencyLimits(); void blockApplySettings(bool block); private slots: @@ -83,7 +84,6 @@ private slots: void on_centerFrequency_changed(quint64 value); void on_ncoFrequency_changed(qint64 value); void on_ncoEnable_toggled(bool checked); - void on_ncoReset_clicked(bool checked); void on_sampleRate_changed(quint64 value); void on_hwInterp_currentIndexChanged(int index); void on_swInterp_currentIndexChanged(int index); @@ -93,6 +93,7 @@ private slots: void on_gain_valueChanged(int value); void on_antenna_currentIndexChanged(int index); void on_extClock_clicked(); + void on_transverter_clicked(); void updateHardware(); void updateStatus(); diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.ui b/plugins/samplesink/limesdroutput/limesdroutputgui.ui index 008191154..b7bb79f87 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.ui +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.ui @@ -29,7 +29,7 @@ - LimeSDR Input + LimeSDR output @@ -207,22 +207,6 @@ - - - - - 22 - 22 - - - - Reset the NCO to zero frequency - - - R - - - @@ -271,6 +255,22 @@ + + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + @@ -935,6 +935,11 @@ QToolTip{background-color: white; color: black;}
    gui/valuedialz.h
    1 + + TransverterButton + QPushButton +
    gui/transverterbutton.h
    +
    diff --git a/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp b/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp index b20991b78..96270d375 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp @@ -38,6 +38,8 @@ void LimeSDROutputSettings::resetToDefaults() m_antennaPath = PATH_RFE_NONE; m_extClock = false; m_extClockFreq = 10000000; // 10 MHz + m_transverterMode = false; + m_transverterDeltaFrequency = 0; } QByteArray LimeSDROutputSettings::serialize() const @@ -56,6 +58,8 @@ QByteArray LimeSDROutputSettings::serialize() const s.writeS32(13, (int) m_antennaPath); s.writeBool(14, m_extClock); s.writeU32(15, m_extClockFreq); + s.writeBool(16, m_transverterMode); + s.writeS64(17, m_transverterDeltaFrequency); return s.final(); } @@ -87,6 +91,8 @@ bool LimeSDROutputSettings::deserialize(const QByteArray& data) m_antennaPath = (PathRFE) intval; d.readBool(14, &m_extClock, false); d.readU32(15, &m_extClockFreq, 10000000); + d.readBool(16, &m_transverterMode, false); + d.readS64(17, &m_transverterDeltaFrequency, 0); return true; } diff --git a/plugins/samplesink/limesdroutput/limesdroutputsettings.h b/plugins/samplesink/limesdroutput/limesdroutputsettings.h index 72e47d61f..2fe21b054 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputsettings.h +++ b/plugins/samplesink/limesdroutput/limesdroutputsettings.h @@ -54,6 +54,8 @@ struct LimeSDROutputSettings PathRFE m_antennaPath; bool m_extClock; //!< True if external clock source uint32_t m_extClockFreq; //!< Frequency (Hz) of external clock source + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; LimeSDROutputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 5f4810cbf..05c5f15c5 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -1021,9 +1021,9 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc } if ((m_settings.m_centerFrequency != settings.m_centerFrequency) - || (m_settings.m_transverterMode != settings.m_transverterMode) - || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) - || setAntennaAuto || force) + || (m_settings.m_transverterMode != settings.m_transverterMode) + || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) + || setAntennaAuto || force) { forwardChangeRxDSP = true; diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.h b/plugins/samplesource/limesdrinput/limesdrinputgui.h index 9c44963da..3ded84e9f 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.h +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.h @@ -74,8 +74,8 @@ private: void sendSettings(); void updateSampleRateAndFrequency(); void updateADCRate(); - void blockApplySettings(bool block); void updateFrequencyLimits(); + void blockApplySettings(bool block); private slots: void handleInputMessages(); diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.ui b/plugins/samplesource/limesdrinput/limesdrinputgui.ui index 928c56fb7..5d79b1103 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.ui +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.ui @@ -308,6 +308,9 @@ 24 + + Transverter frequency translation dialog + X diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index b8d1ee6b1..4a5243eb7 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1733,6 +1733,13 @@ margin-bottom: 20px; }, "extClockFreq" : { "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" } }, "description" : "LimeSDR" @@ -20636,7 +20643,7 @@ except ApiException as e:
    - Generated 2018-04-17T00:16:15.209+02:00 + Generated 2018-04-17T00:43:20.797+02:00
    diff --git a/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml b/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml index 0d30f5ed4..a043b00d8 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/LimeSdr.yaml @@ -76,4 +76,8 @@ LimeSdrOutputSettings: type: integer extClockFreq: type: integer - \ No newline at end of file + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 diff --git a/swagger/sdrangel/api/swagger/include/LimeSdr.yaml b/swagger/sdrangel/api/swagger/include/LimeSdr.yaml index 0d30f5ed4..a043b00d8 100644 --- a/swagger/sdrangel/api/swagger/include/LimeSdr.yaml +++ b/swagger/sdrangel/api/swagger/include/LimeSdr.yaml @@ -76,4 +76,8 @@ LimeSdrOutputSettings: type: integer extClockFreq: type: integer - \ No newline at end of file + transverterMode: + type: integer + transverterDeltaFrequency: + type: integer + format: int64 diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index b8d1ee6b1..4a5243eb7 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1733,6 +1733,13 @@ margin-bottom: 20px; }, "extClockFreq" : { "type" : "integer" + }, + "transverterMode" : { + "type" : "integer" + }, + "transverterDeltaFrequency" : { + "type" : "integer", + "format" : "int64" } }, "description" : "LimeSDR" @@ -20636,7 +20643,7 @@ except ApiException as e:
    - Generated 2018-04-17T00:16:15.209+02:00 + Generated 2018-04-17T00:43:20.797+02:00
    diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp index 2b9712044..39738800d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.cpp @@ -54,6 +54,10 @@ SWGLimeSdrOutputSettings::SWGLimeSdrOutputSettings() { m_ext_clock_isSet = false; ext_clock_freq = 0; m_ext_clock_freq_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; } SWGLimeSdrOutputSettings::~SWGLimeSdrOutputSettings() { @@ -88,6 +92,10 @@ SWGLimeSdrOutputSettings::init() { m_ext_clock_isSet = false; ext_clock_freq = 0; m_ext_clock_freq_isSet = false; + transverter_mode = 0; + m_transverter_mode_isSet = false; + transverter_delta_frequency = 0L; + m_transverter_delta_frequency_isSet = false; } void @@ -105,6 +113,8 @@ SWGLimeSdrOutputSettings::cleanup() { + + } SWGLimeSdrOutputSettings* @@ -144,6 +154,10 @@ SWGLimeSdrOutputSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ext_clock_freq, pJson["extClockFreq"], "qint32", ""); + ::SWGSDRangel::setValue(&transverter_mode, pJson["transverterMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&transverter_delta_frequency, pJson["transverterDeltaFrequency"], "qint64", ""); + } QString @@ -199,6 +213,12 @@ SWGLimeSdrOutputSettings::asJsonObject() { if(m_ext_clock_freq_isSet){ obj->insert("extClockFreq", QJsonValue(ext_clock_freq)); } + if(m_transverter_mode_isSet){ + obj->insert("transverterMode", QJsonValue(transverter_mode)); + } + if(m_transverter_delta_frequency_isSet){ + obj->insert("transverterDeltaFrequency", QJsonValue(transverter_delta_frequency)); + } return obj; } @@ -333,6 +353,26 @@ SWGLimeSdrOutputSettings::setExtClockFreq(qint32 ext_clock_freq) { this->m_ext_clock_freq_isSet = true; } +qint32 +SWGLimeSdrOutputSettings::getTransverterMode() { + return transverter_mode; +} +void +SWGLimeSdrOutputSettings::setTransverterMode(qint32 transverter_mode) { + this->transverter_mode = transverter_mode; + this->m_transverter_mode_isSet = true; +} + +qint64 +SWGLimeSdrOutputSettings::getTransverterDeltaFrequency() { + return transverter_delta_frequency; +} +void +SWGLimeSdrOutputSettings::setTransverterDeltaFrequency(qint64 transverter_delta_frequency) { + this->transverter_delta_frequency = transverter_delta_frequency; + this->m_transverter_delta_frequency_isSet = true; +} + bool SWGLimeSdrOutputSettings::isSet(){ @@ -351,6 +391,8 @@ SWGLimeSdrOutputSettings::isSet(){ if(m_antenna_path_isSet){ isObjectUpdated = true; break;} if(m_ext_clock_isSet){ isObjectUpdated = true; break;} if(m_ext_clock_freq_isSet){ isObjectUpdated = true; break;} + if(m_transverter_mode_isSet){ isObjectUpdated = true; break;} + if(m_transverter_delta_frequency_isSet){ isObjectUpdated = true; break;} }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h index b036243ca..9679abaa9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGLimeSdrOutputSettings.h @@ -80,6 +80,12 @@ public: qint32 getExtClockFreq(); void setExtClockFreq(qint32 ext_clock_freq); + qint32 getTransverterMode(); + void setTransverterMode(qint32 transverter_mode); + + qint64 getTransverterDeltaFrequency(); + void setTransverterDeltaFrequency(qint64 transverter_delta_frequency); + virtual bool isSet() override; @@ -123,6 +129,12 @@ private: qint32 ext_clock_freq; bool m_ext_clock_freq_isSet; + qint32 transverter_mode; + bool m_transverter_mode_isSet; + + qint64 transverter_delta_frequency; + bool m_transverter_delta_frequency_isSet; + }; } From 31eb02ab7440c026389b33595b184c8d2d9e62a0 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 17 Apr 2018 01:31:12 +0200 Subject: [PATCH 288/956] LimeSDR: updated documentation with transverter shift --- debian/changelog | 1 + doc/img/LimeSDRInput_plugin.png | Bin 34382 -> 34366 bytes doc/img/LimeSDRInput_plugin.xcf | Bin 197514 -> 200382 bytes doc/img/LimeSDRInput_plugin_2.png | Bin 7135 -> 7176 bytes doc/img/LimeSDRInput_plugin_2.xcf | Bin 52523 -> 55771 bytes doc/img/LimeSDROutput_plugin.png | Bin 35180 -> 35323 bytes doc/img/LimeSDROutput_plugin.xcf | Bin 233376 -> 237928 bytes plugins/samplesink/limesdroutput/readme.md | 36 +++++++++++++++--- plugins/samplesource/limesdrinput/readme.md | 40 ++++++++++++++++---- 9 files changed, 65 insertions(+), 12 deletions(-) diff --git a/debian/changelog b/debian/changelog index 9911466e7..483635497 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,7 @@ sdrangel (3.14.3-1) unstable; urgency=medium * LimeSDR: compiled with LimeSuite commit 67dcef1 + * LimeSDR: implemented transverter dialog -- Edouard Griffiths, F4EXB Sat, 21 Apr 2018 18:14:18 +0200 diff --git a/doc/img/LimeSDRInput_plugin.png b/doc/img/LimeSDRInput_plugin.png index 9cbe9b4092635260296509f8436cbdd51b7365a8..87ae70250c4c106d19cabf9f6bbc0540fb682d9a 100644 GIT binary patch literal 34366 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
  • devicesetChannelPost
  • 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 GkpB-;#1FIp literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_4fsk.xcf b/doc/img/DSDdemod_plugin_4fsk.xcf new file mode 100644 index 0000000000000000000000000000000000000000..4e890e0f614f65520934e72382a6eb9f98e6680d GIT binary patch literal 95764 zcmd?Rd3Y1&y)Q0*XGR(g8ZQwO>^3$B*EP8cmdjz zJiq+z^Lz47=6Phj?>qA@^SF`71M~|Q6`Lg^R)|0~c&356i+3}%)fsVmZtSjW3O9R6lZKEBDmD_tp zyH|$GDy!u?-0Y)018rR$*B>R&{p8hx&(xSFW1< z${y}&Z(Hfian4?!Q*h1S%EYeipVM{W{>m>d$X$1gY-x-440f$_EzE_Dg*ip7`?VYx%YoXOPnK*(T*LX+5K(f9mCi(yX+5F$!#lBU1BQkY3I-&mTTJvM;2>5LAT$$Q2m_0T- zV0+t0M|PrTxFbH=Gc>ree{>l8{XIEerzB^N=WoeUrlj7nkFheftuU`z7KM_Il@H(#0#69>dvoDSq{3-tY3&Km6sVgM>WX?CYuWU%!2> zf-{=_c&(W7ySXCH_^?Bn`Qq{U*SlQ*c3vQhywpwjku{z^IMXGZX@OI4wpZs@g|lsbw=crQnOEd7WYWKU@tyO}Uc9b5UY{@G=&vj^ zKOzZW>eq8-o7HP62B|C2UX!5fRGUc2`E;y=$7aq4rK)QS_&Tvs}m-#xwb z^OR1Wy!~GlH=#G6_%(h$&rDm2?O&Rk^1g=L#J1xO$=40s#BQ5wT4-Z$k?S{d(|;~d z1ai}V{P_m%c<(x6eRiF(KAHJst^-Q+&t^WI(>bK9wLV2depZcP`7*YXXsc0IXZ}Nx zGpl+?MbgDj(HgsfjV`=?0f~Q>tB{tToj1X-`1W;eDf;^wh zkDtAB@ojlNG}ow0Gq2-(3krx-Twedu$N&Bf{@OdfP^L*fJqQ6;l?Hz~zHB+8*?j1EaCteHto`hw><&vAeD#rN`=XxAJ zpT{_|o$;j^$MrR298R`%$ylx!*rb2sY<}-z2K}E>b|5Pac ze9g4FJo7V!;%7Hh|L*SJ^!<2#8pueewMHi}U`xY?ob7 zC@x%ck&DOen@iB$su`j;+H+?^TXKv)VAi$L=ISPRD-WUY9oOJ6S-#?Ia zZ4%*>x#&1{RXm){C)Al z1&j_TzKd=Me#=HylH#L_ubqDzZ7{{#G7G$gLk-D;areFVhbeimvkxy@_Gp_z@gEqM zUxp0#A9(V~e^e;WJ&>II;HMacfAIa^P5c%Id%yQVh2nt}uHoGbg`)j8e|f=$W8JU` zvJQb+U+=4;;c}eui&TirRbQ5dJzql_Uj75zAo;q1hL^uP*R;^KTsBIPSZ4l;G<^QA zGO>Q}flRD37#&03`&#{@nX6Z4KDu82`}>z=?ekZ~GI^{w&=o(A1bgw)`Sa*gD=t2( zP@qGtxcJU1=VhXP8(rwx7}Yy?ABp+W!hICza(ynI`xXu+)A94E-}>Vpzm=*`yt$9# zZb2&l3kt2pe?hv~t>yM2aTtGf_3Ez-IK?e^|3!u3qxt)9?&Y}suOs&Hoew^E){3Ly zwXhYll^Q@h-H>UhzWtGlc;=QkN+7YTuE2U6Os(D;I7oUzFR?c#+#> zxqAiuS;H)S;)H*a8)^^9f8RFBk4R@`%d-1V7ny(=-n$zWz!s`ZNG|w7#*W1i!S;|=X%qOVaKSZ10^30V# zA#R4i7!m+E^DiIBl61~2xMn8In+;dy+LIIn%3_pSIEpL~KYUxEz&PYv2#6_Ah?0t5 z$gtT(ROgr8xgbYfuGt8(i7nd#>{bu0zf+t&Hh(ypD(}CS4E(*-TFlYD!Ue6mO#Ibz--zr z@=4)!1A(U9J=e6*miCxj=jTD$Sv(fzp;-K$MPX4^%>l4jn8RL^FxGvIl+m-n?z2ei zEXa!8X7N@0MqzqM7Ikx|Di-DdRqT!?!U8?ZP8JR@3!cv6rr2>7GR5y%v=sZz0i{@& z!$|*r{u^F#)&y8BztdMmp|U;d#n0#2qcZn@Sql9+Vxk$VaD(LQ1`5q6nrm8U%h({- z(HoNS(2xFt`hE@wMWKZlC?X3E#90*9Fb5EY{b9lS0R1OL5#ELBFq7cvtP1@HH#-E;}A&~|8o{gR9u_` z5wSDkhxiwvLmXlbHJk+uF?c3pg*e0CeM0Tf0_pe74xVdc1J)E{~{KMgUrEz zvj`yemcc*#n#28Y9h%up)P+}@^RvEE$WOLYzld68!TV*Y_3McEIgZGbh}R9&>bP&N zX`#*W-{d-ni6^rORM(aJ=apnAZ@K*Nmx1tPd~W$H1^#~+`-a8NDs-p3y01zy$|k3* zgFa88%WL?uwDxrb+j9QS|3Lwx$d#jV@-dqM>->})xSvJXP(sc^Yl?YH zO)(FqU9ZpIUwD2VF_YE$99s4o4mJ;yp{$(6#}xCxm|`9gyI!BazW{zMz+UsQ(s{s3 zF^_a9=3y$uJhr8n2eq!%7w#|MSM$i#tdhrogF^nuq|8>gjmUZW8x|6-zbdMhjSKnn zc~lM9K7To1m@h0W8V}I{^tgcw7h8nq=MCqdVxp58=7DT6(v{4js8~s;^`6?x)^3apLfqn_fNf(8DL5 zj+{UD$Q`?X{?_eG_s#onK78iX%RhSL^^V8C4X|a)?P65l9_66H4j=+V0pNqwB7sz< zcW?IU8nvRu0!i?g#g##sI1d1;PwPzuH}o}5kzmp3TJ=#K_6!yP;bX9OED9Ea1UJDH zvwZ4OD?up<697+Odyr@qNoc(fBy3=T#(_U%;U;U;sc8gGPaCn}w?%9tsBUKfD7#x^ zU>BGQ0o1ERAmC^9J*p@vc?&376tc!34>}FFqC;Eg`bwaVe0z%#YXpqw(#vq97g)2N zu^6Bgf)u_PApme5Js6EtvNB8_k1@JKrP#fb?1F&z=vT6S9+Sy5k`24&VG)wG#3LcF5V=oXs$W5!+Ens$gC#D*%W zd`GF*xg27;0VN6is5TB@0K#Cl1hbn=X|OK6zM!ywaL2i>dIFNwfi)}Y?eKW+@YqKF zaHs1QWvQSO9e}UU6^#;=T!qrpln<8Z6hPr-+nRRHv-j@Rf_7-&<(%n!kpgFw4VHH6 zS@5?=se!%YQ_QlF;QHt$v6F1ac}dDZv-q6^hb*xu#Zp{bO6l=kbS{L)TKsUl#?!Ht+xmJrl%ZBqlki|d^42T;G>745Qg<7M1OMP8? zqN%5)ez(Z#AQ0QF+-fyCU9^S1x<=F(2h+9$0cKABFl^zuy{ zsa8roIdu2F`>gdo9RAr6gQu0``dKZsXvM27pnr^sfW-zzQrz6x7^8TLLCAqnxwj&h zqacF~Q?7kFPoyy@yqKF(3*A0Ts_6XFwH^NvzrTA$}36MJ*Bg(@i`#M7_Fh=tLeHWoig{p;_i;@_4!&u z6oL+X#ba4+FsfVVWz1ch(k&{>T}p#~Y_h1#%DPLzV$ZfNIwD0p_U5Z5=SHBE5OpPU zkjJr6w~ldA#xkOrRl>R;OGAsLtJT-uP^h=Z4;i-|r@^RG=R!pH=-S%p>Bp}o7(LF< zA?+VETj_cuWsy7@P&Pvhf+VuW`c$!|%oZ!N4x#s(s|)$iYG?CxxKUezTp(I}iYFd!b0N9JVPTWEpUlDlL;ji;X=)AgLPQrml4w zgUUrn6}5*Ts_cZdau>6xeIqVWBv|`S zisuG#!#*IIlqk-XB9x{B(6X+0$#5JGh2Ga+vq}ZZXhuThI3w6OF7xnGs3=}6LZInx zKdK;HnHbZ-dN3-ngAlZ^kc*Q;bS;kZ2oHzsuoO>>RdU&ku!FUR!-iOwhXS2J>*VY# zp^B{uLLg{Vvlb*FJQ*D>PKkSw3=qr)svRj-=LcG6vFxyc!8Yk1+Ct&bf20U2*!U?) z0~Hp_xiGFDJ~l=LOAy#7-k*`F;*%g0qhJR3G3pd?ky>xEhn4<_cd#n+q=B{90vrH6 ztY`zb*J>TqeH+zeI%-hVMeu>YP-Ir)i_p-4R3(r0<9e+(s~PMDOOb4aDS8l6=dV) zs;$~!k)RDGPiO11<$XW~;7}o=EdW2n<$xhwX*V9E!IMt;OLh66^GxLzGer>OaCgOe zmV2@)9A-0M2_7}h`i~?K+X&oOfH2!wX~>B}Sm}x9L9}O+y@w539W4BV&aCWOj}!cn z(Xn{|p;D?>w~DYL834RbQfR!uVE{XW)HGawEn1#nuz6aaRDlCTB$(lv$LW4`Hv@b)ZZKyrlyVs0AditV)fZ^T&V%;!Vby z1(St0M}ZPk*eJ3HYK2}r?xg+_cQ10IaCMC;Q5yEo6OM_rHeHOKae zj1|_GAs_2wv<2-a(^{I634KcOG)`#|{ZS?8DEaj=a0FyKy+zxD#$cuhMh>{>Dq3T( z*lO8~Zc-;U$WH{p;h@4K*%GeZ7{$801ELyLs#pjbRAL$J+8n58$w6+6Dj6b*9H>)9 zmvriZW{eI>w-xBHrvkJw0OYQ86vre-S&`&fk`!(QU#{Ja6CGU3qS~lvMcv2U-DlgoY(1h&IWM@5O?>rNkm^tVGFu z9ArZfJE7PC(uJjugk>mBL6s((djA>+SQaJGV2ou^7vj`N6r(0Cv(y*o<6znHcJg+F3P) zYq2_lOhW~XjYRQPEzt)$K}iKoGF1ayj6w$st%tzm0@n_j;;5WRdDJ)@17Zb$l*;mb zK@f7Giz5b_@+&nc1UM_w!S885RIdRg$${k-7auZiVeC1}a8Xl9S9U`kt+YOC#Z6BY zk?C0j$tAA>@K`~$8m-41GU{l>WueuOp^vGdfh|^w#a7&4h}wW;O8c6iK`mNH@6uAc zEJDJ{)h4Q_ixqVe9n6*&#`_E)iZq$Stp(atMUv1Q7Tnt!22gzqOrB{>QAdTBwCJms9;L~RE7J=- z+Nkv?{|_NOuw<|)vtqSf*9NfIZ@}{^zuSVxrA{T~WygG(`YAn$T+6zHI=eE^IVf7R z+E7Q9wnIOX%L;iY1GB4%ZGwqdB{heT9Rxt}X`mKS(73XY1I8KDdAS3~-(+RBrgeCa z?yxl_tDI}dV{pR*@rQz4s^Lnm%i)6E45N;2vQxfLyKYPnlhtaE2N{cn{5VoX`ubX^ zDLv@WrIb5*qB&;pu#D2xl^u_Sy+Ci8(uJDqT;guyA~s+Jx<4H(D&Y{MhdH{uq51w^ zfMX22W{aN;w}yPZ+L7kP&=sV}tZBLs!UI%6Ep5wwu+pCj*``+6R)Rzov|G8OXP$z4 zm+w9&Q-I>M-6#B0EGJwo+;vi_BwOtg0eky5+r5w+LtO`75}Ain@F9 zE4ZnKeTOXLtp%`H>4P|NPu5kDmC?ch70I9XoaG z>@$!5{Jrn}%gFg30PNaz&KDQ9#+MKYC!mHwM+Qw2TgXVM?A!VS$(>7lHrZ8sEZvi> zv@S(MOe@&OZTRF(ozt?_DfA0*R3bjf4@5|Vy%TX%Qxy0JO=z*+rpL01NCnUnaY&cQ zK_3YN-$Abd^c?O8#tO7>#1?n6UBC-!GdBD-r0oRy{zYgeI*WZ8I0mUDNI*G0lm2JSYREc_N6%Yp^_a%(?fe^m%0_T>U}?$GGmQ|y+T$kj`V^yZMnu~f^@3vgUZ0UJ%_XX8#KpI*g~P20BtL!fhrV-xF4TtBssbP zKRD?0U_?Y!Y$AOb*)}0F*Rh>D zAo%i-@vFw-aU#mstNdMFu*IhVO8T>WL%jdwHw%s2%{zaQ|B%;5fjh31q-_EV;og|7 z^5kvD7a8_Qk$9hPlp4Z$Nu^1x#czxiq$lE(mg0vj(!oJc?gK6snqbn~zD}Lrbh?ByJ9o8h8XE52 z(bsmoNGrgG#I$O6hS}|5Z0aeWk6k%w-&IlBzse-sod7oO{)P)kA^Ppt?P{$$rEdwv zsA4-rX4}c{WVD5G_-FT+{Db7uiNzfBa=xiv5MI@^Kw1`SwYV5xHHN!?U z*kFZ?g{@FCDfr45jil;s&9;}^SLh0&OW7@dde5qD!Zx0E=ur;cC~Gl ze;thXvB0G*Th6ypJnT{PF!sYfe{Wrs)D}+*HebjE35|JGb!aL}(2Z(jzm(fHyt>xQ zt%!ppd%DM5#b9E38KdT@-YgrP{*=lj>^~Z;%hVQ2V9Q;d!QR){;&*mWS>5eGsUYq# z@KC^O<32&-rc4pCXR!(@q{V9JwQcJU4JFnI&aG!m2Od;|NkHGYMR+B=`N0RzPYr7X zoS#S9KWfcTx0@(i%+G?V8xkN<$QqFxT1w$hLw{Miy>(z+D?y-ClcvhbcAK}2cDtv9 zMLq$|o?2AC8dA9vKvR(y*iJg7ysWsE5TrW6%u+)P^wYhuR!8K16Hg!yWx;4pI>jgP zkD)I0qY#4a=w=tMwSW`YX`@O2A4Q#k7l9&4A}!?0P)`^b$npEOy@>ZD>qbI?YHpaH zSQWTU>qKElfo-!%qls*WI!G+`AFLX7lQn z88naOx`kcMlhH|?4XLFYyCrZZUq#8fd1nRi2iEy0EswjXf-2iix1_QiN$GWHzeRu< zH{r-=1#6-`L}UHtGawbC8AzZEGD#>q-(drJ)q;RFc$N{>$jzWh2SK|%NIB^97YGo> zgg9B7DhaAvA7l;#WO+;?IMLsNnMwTf+QE1Nv?>$OqhKPt`8bhEg5Z%dKoa6@qhUO+ ze0IHh2MZ_mq%J)Iuu3d2skA`aR6rv&z)v+K+TiHA=5>HlwM@I>Oc8go5*l-e%;+%< zyR{vc=p(ewBT79#i5vC-(XB#huJVC|rh-~l6)%|{!b4&1ys0IJ236d#)yMN@aPoZ4 zy*grJnDfDg?r)-v(1|PabqEjvvkE&HC7TvKB_xKp%;}Hl;fxb>cw(H2U#&6Tx;V4C zI;C^euLQxwxphvCq&s~QQo5PZqC1S51Dyc^RJa$Y%%#;p4I#k_WuRVWvmJDRDeGwX z=BfEmK?XSV(=irJ6A8j7moZd;D#gb#LaH;ODr*T$ zHs(BP(mFQ-+=Ya186t`UnHiJ94=}8dMObI`&j)2CK8+V&gn?nCDtWXYw{Zby59F?} z1@qLJ`$&}00+{UJT(N~~>Ah9wz~GqF=iyDb5t7=A(;n8Y1}9Yvmc$N#07+iDt~!;(@1l2a`afa;O}o0bBTiF0P5n~RZhW3j{(J5Yp)Owp;j^JUapx$(5 zt$x#nlynU8>=Lvs0`BZOQdu%oq4q^Yt6f5)5km#V+{Owuf)miG!hRpCcrEyXS{dB< zJ_Z~iFUCX0Y7_s8X;d;|K=F{S$*gLp*fQVXBe?5$)Gjc%;t7?La_XzF*X!tD+7UXy zOdM;wli(J&?eG zBOu!eHf}rGg4sU8%mW|KQL`qSW3$$=4HX%RF{DAr%cCMB)my!}JuXj=JK$qbqxwpK zMIGiC;zX$L75%6vlq?ZPfgz~kx)A{wjX94Jc7wXwUk^)gu$3sJP#lvymAMR7Xi3s| z4-s-ZeK;|VXSl55HX9nXS)i55`sJ|D`_wWtF>KW)@M_`$uR_s+UcUgN9$yibl42k5 zz!{PF)S$CMyj#BgUZ*rJkqAFwY*z9;jLVmI@A6XQY|?|JkBkOz7=gCq>%+gIiDrzG zwshzy!r)$lH>pvL=%F7xTV3ua@bQ1!L+Q*ApKi)P;}X<_hH>(aE@Xy0WIGHM#Bo?b zt)eV8Q=M@=xc8eNmogJsW`0Y=S2Z4YY8g3HkdY^85*y)`y>UR&h`%rjDoRS#$lD1Q z<1nm+!84HR0rx>Q#Vf^@+PHal2SKEwD`L~?CnPYsVT>nsc6(ziS{{4`^4IUx>!Ha9 zDvT?oop$T1&AV8qD3dpa3Z|h|t;+aeW+#e5AMjeOiR72(0QhY{%f@3q8P6L8$V8*T zA#^a1(1vJVC<8Yb5(v>S(gf;$#+Ob7bdpmRArX}~RarW=*eAr)(rS5O`j82HJ~b&W z4WqXn!?0dYjj?!tVkc^EqsEiE->W~WN!R)MRBBiO!5HYrZe?SJ#wBTBj!PJ7BEU(z zyo0Dmi%b{#0I^FoH=k>ULj04=yz%R45K3X$a9UABMc5^T) zQIzumbqUn$q}FX#J669KE3-j<>Y!{Y!EhrCt>W+d)?37PQcgT4Q-I>MGi3a@Q>y>`CJd3&Fg-6f=5e4 z)U@}neBym0w(6CKndKD1x27K6b>ZPV zhTpjGKkwP#e*C#h-+SlI(e9_u{N#I&{LiOs``kcL==AFtWC;S? z%Pk?>WFsKM@iVm7XQ;DDk!`GImXi#cY zZm=ojsD)pyycv67r0#3-$WMo;phBylc`n=J8fJ0!ngSEsLCHtz?27@jRhjj+d z5JmvY0REdnXB+}6Yg=a%W98XR;l!P5+9s8Ide?_RZx8J!9iXj+DAh7`OU&41C3NZK zxNm!`2%qOTOknY9w$M3B(Zi_ow{Yl#sZ1?92sYo#TT1KdONaU&gXCy%RoZdb(o)k= zunEyIjO--KFiDW3(Ma8q6;^Ysl?#a!qp{dTsW3DqfnF>OOBEHnZg&LNwUU6ZXfO?w z6~0MYxTCu#<%3h~Th*ek088>ZFd&*oLsX76Dvj*X&2?rLO+nLXIBnluac%2;5Oy8p!EV-e0Z_q7aTnOLC2jt)I$i={o2S zAL?rf^K1Lj>`XlQ@V;%UPy4TOhFo@yy;MdIrWAkbL{*N$f(xSbwi|->RFl!pl1-$3 zQ**aAx@07UJ2ib|-^g@!uVBcie{#n=jqH)Dx11VOud+VWpXNaagOR{>DjCGZ0}K^f zZ*k>=y;8qQSk@R0-8D5)(4N?R?+M}FqN+4FbxV$Zq{rKMHF((56@R+(@Z_WLOn^mr zLfi4s+NLP%X4RHbbQtSBN~L;_W`rLb+wqic%S88h0beLq7qslWqkd#>+11E+fjbg7 z*|;4ohrTUr>kY;t{pCASX1ITREC)d zKbjmF6V+W%51o`Y4|82GJX>U|)LKEE=UK%UDR0@o(c2U_9%*QN>G7TS9KH%yr;^ka zEz{3q%_@j?jJp7!MJvY)kk`T)CbdIO5iiY&Q(TTxj6znyav@8ol6lHMUZjTXr%Q#|1 zSxkvZCxSHKN0tlkj6D##tMW{Xbf`VUVSI$`+kVyGpi^h{UW`!%1`CB!G7zzgkCX$` zF21Z)iy;+lFtRCnON&yuqEG$onZ}n+g{0lj-rwb?M#gRpyfjNT-)Pr~(dPDy{npB& z2Oml|DE&3|zQj5GA;?vNG6FOrGl>kHvf04x2sNIqs#J zn@h5~we~VE3PEmcP^m3y&Wr~8_y!T|%I$d@bVupC;|y%lJ#^cN?DC4U-u2Fwd%RCJ zr>2cGmQGfKXX8dpkty944(p=LWT@TrU{BpZTo;uQtf?eSBU|hW&<2FIkPh07LTaRb zvsAHpjlQQmJ)ennn7gu)tED3JX>t>=!U)D%=(8NQHF@;3DGwZLI#ms;m0NsV^YQFv z!9OYGjo;mun5b(k_qzO%ON&=HwYEMo-)O~Ti{b`JTL?v@B^QQsjAd4zz5ot+xFK;_ zHe^`_M4szu>+s&aD`Lz$XfLIkbl%)n=s{c#{W39HZndTWy*An`_Pn3FfVyca$ULDjMF|P#7 zX>w#~O7RN8X(<&I)M3c2cs-&DycvTWpe)G|+XpZsMUH`Ba*gQdN=r4A5|gE-@RrNvL3Bh7dRSx2&C8{e zH#QXZwW6)4EZJgL^_Iy3`sYLlU9aA#G0kF8k!)&I z*$Cyu-kT8I)-Yk)UW0LSo}Sd%Wzgs|kz0Y5!<6ER3(VM!p{Ny*MTenMjoDST?Fi#p zfG?fO2JF!GGMu|fc8%PXG^1b4)$)lGgbAiDG z47ojpG|Fx$M(BZEMCfbED+;rTFgmuZKdt>yep$2wA6AJiXi@!+)E@Fu+I%5co^_B;20mLn(<9GVR{$(=@_rm zN98P|Q?9Q0H?2xVyO_p2Ev^8pF{_d1Ftq?J%mBdp)t@xF)EJ-rbS{6wJ(dY({b!4vpt=2XbU2G&8SPWYZV=CXWPz?qKTGvwgV5PQ- zGt`4S6%vK8GD>ZPdW?1$(F&%S!tAU{*`kF2E5;Uc{t*bO(EToo5p5ynNC{Sv`Qfz~-$Da+g&2$$0%?%i z^U|RySb@pMHn8+zj0rQw5X!TFPw4F-r)ZHFrd^maWWnrR%qUAo`P@rk4oL>8B5g{J zW?3r-9vf#A?SX0zPXz9^5Ioq!z?yd}Ey~pPGLNLx(I!${cNdr{Fi*&gia>=@&tf+m z!jvSPbt{mX70@bTV5Ew$ufS+eu+WY{6L~w(7h%|^&)?IR%VR;^m^+G79&I^Qck-n_DbS3Geo0@|J+g^b}HX5ugPe=MN zq*ap<64P-xBR7$~5rQL3HcEFrSdl#!t=^JLaXX0R<*K6oy`BeJsU!CFqxLiylNg3V zS5(YGQM}1PP;A zGoTEQGe%%CB&H5+#F*(l)og&3^!7S%YOQps3hay4+8Q+Vp4|PR;+)%=;XT_}ZET`3 z7%%y5QMv!#rf8z9x4w$9!(M+W?h&_%+|e84a-xLYh%(g#+;|2{`R$OZU*ccWTAaAe zZwN1~w79|iif>E*p>N+3SnYxGdSKG_4c>xh9bJzK4kH99`|$54#dx&qs{h3;9j{i^oa*bgbZxK`tgejNVrfV=bPyeV$*c%5 zJrXyjmzj)NrhRBr+1POT_(XqMD`{x-XThfN$iaK-Cz5yjnx}oORX1C4GsD#wX%|Ks zYaluuMU5s4jXRCj`e2ad*jE2GxA(A+w`u*R!#zEsz3<3NKM_Yp^t->&kUCh^x1}&S zfV^gi>YAq9l&FgdT@^qggqeIO!G~ztLY}+uOoh2?!}ERoj!NLlslL}E6+86a2huFm z9sRIXPOJQBPjXOC+PWy)!3;trLOLds=%75pSuN@8^Z#HCtlp0x@xi-0+eiGz2|=N;lbOg!Tim&81C}{dIT83Ws#f-C^p7tQ5bk{ zEeVLKs=ghoE#CVmYC>KH1~7pDGBpgW^6?s4j?RAIffBGu1Vc(FS3C=m0^~Wtm2#9< zN&9ZkD?qKZloa?Z$SUIw%3x(Y8Tl7ukf#VVNTl7YPM?_p-H3u}r_l70y0$<*;n+`fL7li^2GwQmf=@E%lX$V`~XHv1W4;(n843wpB zD{{IljT$iXA*tOb_2$6MB~m6%kO!F#R5ft&=|LjzUQ~-zD#p@$qo8r+nOr0r+1QR@ z_A<+eAF~EWfj5e5wku=o<|XMoEHBtM0dNKow9Kps#mR{##$*z!=jnY*vQ)v5(!S?ZJUq;tCB?vli<0qetlTBwd| zrcd18b7xv>Rou(9_QrrVn<~MXkCB313Gx9cxvY+pFizPSuMk&i95+#7&*(j6)n_Jp ztM%at3_)&v{iQo^DSWbQicb->KBpvu2N)?BO+(F$q#|Kp%DFrh4`Qt`YcH0{Vf77MCck1*LlT6O4mu|8b(?Drr;CaFTK5mksq=?P! z#h+*h*+xTmr247jx0MWyOh5aG@!8Ut9iBWb3VXH(x~8PF{%umhoNM0bM$zE&dr~?sb*u_|C4D{N>Im?E^OzxlTTkkx>xCj6fyy98f8Zeqp(Z&s#BEVSo(k zj={0I)z+6!m5r3Qzf$+|k2AC{?CIV+l2TWg?7A@JHQ6WjAD%Q&aEHmtaax$9D3kks zXM(t+mM2qd6PR+n))?DaM&{jHbEM;iswbn*^fsOu%FZ*tterSGRhAGK*Z6NcDF2Fd zv<_fHi?ir^wV)aD{hVWbRkYe<)~*!NdQ>XQ1oOjhb^YTLRgHK2@P%z<%02tPQStLx zviZi4;Ei_=WuMNBhF*NhzCu;joI5f6oNxx#s6f>MbO#W_*~zMI6MwL}>)WmR`#$U| z&fC2!QM1|`A74kr6Jz>g70xj(HxfWAXzBh*6<6Au9hWAO6F$T|4irR_ l?YJ_+XB@Ly%xP9}^t;um2z`{s@ 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
    (9w1`#8hs8R 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 literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_4fsk_sym.xcf b/doc/img/DSDdemod_plugin_4fsk_sym.xcf new file mode 100644 index 0000000000000000000000000000000000000000..cd378c5d1602a57a13b24ccb21cd3d5dc4968b87 GIT binary patch literal 72671 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 literal 0 HcmV?d00001 diff --git a/doc/img/DSDdemod_plugin_dmr_polar.png b/doc/img/DSDdemod_plugin_dmr_polar.png deleted file mode 100644 index 57b548684ef18374ec4a2aa7192cf24ab1668430..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41283 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 diff --git a/doc/img/DSDdemod_plugin_scope.png b/doc/img/DSDdemod_plugin_scope.png deleted file mode 100644 index be19ad4f70905754ff7f1e17127da56f57228adb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65705 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 Dl=(J7 diff --git a/doc/img/DSDdemod_plugin_scope.xcf b/doc/img/DSDdemod_plugin_scope.xcf deleted file mode 100644 index 18302b9229125994654bdc75022d1cee643ec374..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318472 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
  • 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

    nCcg^C5bJ*|?T0xCdiM}_w7 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 O)$i9Q%TZqm^Zx)NRj(QV diff --git a/doc/img/DSDdemod_plugin_scope2.png b/doc/img/DSDdemod_plugin_scope2.png deleted file mode 100644 index f71f7a3d436e79f0b33cd6cb5d66845e359da8da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42479 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: Frequency shift from center frequency of reception